diff mbox

[13/31] usb: usbssp: addec procedure for handlin Port Status Change events.

Message ID 1532023084-28083-14-git-send-email-pawell@cadence.com (mailing list archive)
State New, archived
Headers show

Commit Message

Pawel Laszczak July 19, 2018, 5:57 p.m. UTC
This patch implement handle_port_status function and all related to
it other functions.

It is called in interrupt context from usbssp_irq function.

handle_port_status function is responsible for handling all events
related to USB port.

To correct handle some port events driver needs to add commands to
Command Ring. These commands are processed by HW. Each command
can take some time and driver need to wait for completion.
Therefor driver can’t handle all events in interrupt context and
Some of them must be deferred and will be handled in separate thread.
This additional thread will be introduced in separate patch.

Patch also introduces gadget-port.c file that contains functions related
to USB port, such as detecting port speed and setting link state.

Signed-off-by: Pawel Laszczak <pawell@cadence.com>
---
 drivers/usb/usbssp/Makefile      |   2 +-
 drivers/usb/usbssp/gadget-if.c   |  59 +++++
 drivers/usb/usbssp/gadget-port.c | 102 ++++++++
 drivers/usb/usbssp/gadget-ring.c | 414 ++++++++++++++++++++++++++++++-
 drivers/usb/usbssp/gadget.h      |  13 +
 5 files changed, 582 insertions(+), 8 deletions(-)
 create mode 100644 drivers/usb/usbssp/gadget-port.c
diff mbox

Patch

diff --git a/drivers/usb/usbssp/Makefile b/drivers/usb/usbssp/Makefile
index 4f5f12ab6420..f867124f286c 100644
--- a/drivers/usb/usbssp/Makefile
+++ b/drivers/usb/usbssp/Makefile
@@ -4,7 +4,7 @@  CFLAGS_gadget-trace.o := -I$(src)
 
 obj-$(CONFIG_USB_USBSSP_GADGET) += usbssp.o
 usbssp-y 			:= usbssp-plat.o gadget-ring.o \
-				   gadget.o gadget-mem.o \
+				   gadget.o gadget-mem.o gadget-port.o \
 				    gadget-dbg.o
 
 ifneq ($(CONFIG_TRACING),)
diff --git a/drivers/usb/usbssp/gadget-if.c b/drivers/usb/usbssp/gadget-if.c
index 70def978b085..6f42ad33979d 100644
--- a/drivers/usb/usbssp/gadget-if.c
+++ b/drivers/usb/usbssp/gadget-if.c
@@ -287,3 +287,62 @@  void usbssp_gadget_free_endpoint(struct usbssp_udc *usbssp_data)
 			list_del(&ep_priv->endpoint.ep_list);
 	}
 }
+
+void usbssp_suspend_gadget(struct usbssp_udc *usbssp_data)
+{
+	if (usbssp_data->gadget_driver && usbssp_data->gadget_driver->suspend) {
+		spin_unlock(&usbssp_data->lock);
+		usbssp_data->gadget_driver->suspend(&usbssp_data->gadget);
+		spin_lock(&usbssp_data->lock);
+	}
+}
+
+void usbssp_resume_gadget(struct usbssp_udc *usbssp_data)
+{
+	if (usbssp_data->gadget_driver && usbssp_data->gadget_driver->resume) {
+		spin_unlock(&usbssp_data->lock);
+		usbssp_data->gadget_driver->resume(&usbssp_data->gadget);
+		spin_lock(&usbssp_data->lock);
+	}
+}
+
+static void usbssp_reset_gadget(struct usbssp_udc *usbssp_data)
+{
+	if (!usbssp_data->gadget_driver)
+		return;
+
+	if (usbssp_data->gadget.speed != USB_SPEED_UNKNOWN) {
+		spin_unlock(&usbssp_data->lock);
+		usb_gadget_udc_reset(&usbssp_data->gadget,
+				usbssp_data->gadget_driver);
+		spin_lock(&usbssp_data->lock);
+	}
+}
+void usbssp_gadget_reset_interrupt(struct usbssp_udc *usbssp_data)
+{
+	usbssp_reset_gadget(usbssp_data);
+	switch (usbssp_data->gadget.speed) {
+	case USB_SPEED_SUPER_PLUS:
+		usbssp_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(512);
+		usbssp_data->gadget.ep0->maxpacket = 512;
+		break;
+	case USB_SPEED_SUPER:
+		usbssp_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(512);
+		usbssp_data->gadget.ep0->maxpacket = 512;
+		break;
+	case USB_SPEED_HIGH:
+		usbssp_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(64);
+		usbssp_data->gadget.ep0->maxpacket = 64;
+		break;
+	case USB_SPEED_FULL:
+		usbssp_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(64);
+		usbssp_data->gadget.ep0->maxpacket = 64;
+		break;
+	case USB_SPEED_LOW:
+		usbssp_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(8);
+		usbssp_data->gadget.ep0->maxpacket = 8;
+		break;
+	default:
+		break;
+	}
+}
diff --git a/drivers/usb/usbssp/gadget-port.c b/drivers/usb/usbssp/gadget-port.c
new file mode 100644
index 000000000000..09ea5b574ae0
--- /dev/null
+++ b/drivers/usb/usbssp/gadget-port.c
@@ -0,0 +1,102 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * USBSSP device controller driver
+ *
+ * Copyright (C) 2018 Cadence.
+ *
+ * Author: Pawel Laszczak
+ *
+ * A lot of code based on Linux XHCI driver.
+ * Origin: Copyright (C) 2008 Intel Corp
+ */
+
+#include <linux/slab.h>
+#include <asm/unaligned.h>
+
+#include "gadget-trace.h"
+#include "gadget.h"
+
+unsigned int usbssp_port_speed(unsigned int port_status)
+{
+	/*Detect gadget speed*/
+	if (DEV_SUPERSPEEDPLUS(port_status))
+		return USB_SPEED_SUPER_PLUS;
+	else if (DEV_SUPERSPEED(port_status))
+		return USB_SPEED_SUPER;
+	else if (DEV_HIGHSPEED(port_status))
+		return USB_SPEED_HIGH;
+	else if (DEV_FULLSPEED(port_status))
+		return USB_SPEED_FULL;
+	else if (DEV_LOWSPEED(port_status))
+		return USB_SPEED_LOW;
+
+	/*if device is detached then speed will be USB_SPEED_UNKNOWN*/
+	return USB_SPEED_UNKNOWN;
+}
+
+/*
+ * These bits are Read Only (RO) and should be saved and written to the
+ * registers: 0, 3, 10:13, 30
+ * connect status, over-current status and port speed.
+ * connect status and port speed are also sticky - meaning they're in
+ * the AUX well and they aren't changed by a hot and warm.
+ */
+#define USBSSP_PORT_RO	(PORT_CONNECT | PORT_OC | DEV_SPEED_MASK)
+
+/*
+ * These bits are RW; writing a 0 clears the bit, writing a 1 sets the bit:
+ * bits 5:8, 9, 14:15, 25:27
+ * link state, port power, port indicator state, "wake on" enable state
+ */
+#define USBSSP_PORT_RWS	(PORT_PLS_MASK | PORT_POWER | PORT_WKCONN_E | \
+			PORT_WKDISC_E | PORT_WKOC_E)
+
+/*
+ * Given a port state, this function returns a value that would result in the
+ * port being in the same state, if the value was written to the port status
+ * control register.
+ * Save Read Only (RO) bits and save read/write bits where
+ * writing a 0 clears the bit and writing a 1 sets the bit (RWS).
+ * For all other types (RW1S, RW1CS, RW, and RZ), writing a '0' has no effect.
+ */
+u32 usbssp_port_state_to_neutral(u32 state)
+{
+	/* Save read-only status and port state */
+	return (state & USBSSP_PORT_RO) | (state & USBSSP_PORT_RWS);
+}
+
+__le32 __iomem *usbssp_get_port_io_addr(struct usbssp_udc *usbssp_data)
+{
+	if (usbssp_data->port_major_revision == 0x03)
+		return usbssp_data->usb3_ports;
+	else
+		return usbssp_data->usb2_ports;
+}
+
+void usbssp_set_link_state(struct usbssp_udc *usbssp_data,
+			   __le32 __iomem *port_regs,
+			   u32 link_state)
+{
+	u32 temp;
+
+	temp = readl(port_regs);
+	temp = usbssp_port_state_to_neutral(temp);
+	temp &= ~PORT_PLS_MASK;
+	temp |= PORT_LINK_STROBE | link_state;
+	writel(temp, port_regs);
+}
+
+/* Test and clear port RWC bit */
+void usbssp_test_and_clear_bit(struct usbssp_udc *usbssp_data,
+			       __le32 __iomem *port_regs,
+			       u32 port_bit)
+{
+	u32 temp;
+
+	temp = readl(port_regs);
+	if (temp & port_bit) {
+		temp = usbssp_port_state_to_neutral(temp);
+		temp |= port_bit;
+		writel(temp, port_regs);
+	}
+}
diff --git a/drivers/usb/usbssp/gadget-ring.c b/drivers/usb/usbssp/gadget-ring.c
index f6a6269e071d..6679d8ec7152 100644
--- a/drivers/usb/usbssp/gadget-ring.c
+++ b/drivers/usb/usbssp/gadget-ring.c
@@ -102,6 +102,11 @@  static bool link_trb_toggles_cycle(union usbssp_trb *trb)
 	return le32_to_cpu(trb->link.control) & LINK_TOGGLE;
 }
 
+static void inc_td_cnt(struct usbssp_request *priv_req)
+{
+	priv_req->num_tds_done++;
+}
+
 /*
  * See Cycle bit rules. SW is the consumer for the event ring only.
  * Don't make a ring full of link TRBs. That would be dumb and this would loop.
@@ -220,18 +225,82 @@  static bool usbssp_mod_cmd_timer(struct usbssp_udc *usbssp_data,
 	return 0;
 }
 
-irqreturn_t usbssp_irq(int irq, void *priv)
+static void usbssp_kill_ring_requests(struct usbssp_udc *usbssp_data,
+				      struct usbssp_ring *ring)
 {
-	struct usbssp_udc *usbssp_data = (struct usbssp_udc *)priv;
-	irqreturn_t ret = IRQ_NONE;
-	unsigned long flags;
+	struct usbssp_td *cur_td;
+	struct usbssp_td *tmp;
 
-	spin_lock_irqsave(&usbssp_data->lock, flags);
+	list_for_each_entry_safe(cur_td, tmp, &ring->td_list, td_list) {
+		list_del_init(&cur_td->td_list);
+		inc_td_cnt(cur_td->priv_request);
+	}
+}
 
-	spin_unlock_irqrestore(&usbssp_data->lock, flags);
-	return ret;
+void usbssp_kill_endpoint_request(struct usbssp_udc *usbssp_data,
+				  int ep_index)
+{
+	struct usbssp_ep *ep;
+	struct usbssp_ring *ring;
+
+	ep = &usbssp_data->devs.eps[ep_index];
+	if ((ep->ep_state & EP_HAS_STREAMS) ||
+	    (ep->ep_state & EP_GETTING_NO_STREAMS)) {
+		int stream_id;
+
+		for (stream_id = 0; stream_id < ep->stream_info->num_streams;
+		     stream_id++) {
+
+			ring = ep->stream_info->stream_rings[stream_id];
+			if (!ring)
+				continue;
+
+			usbssp_dbg_trace(usbssp_data,
+				trace_usbssp_dbg_cancel_request,
+				"Killing Requests for slot ID %u,"
+				"ep index %u, stream %u",
+				usbssp_data->slot_id, ep_index, stream_id + 1);
+			usbssp_kill_ring_requests(usbssp_data, ring);
+		}
+	} else {
+		ring = ep->ring;
+		if (!ring)
+			return;
+
+		usbssp_dbg_trace(usbssp_data, trace_usbssp_dbg_cancel_request,
+				"Killing Requests for slot ID %u, ep index %u",
+				usbssp_data->slot_id, ep_index);
+		usbssp_kill_ring_requests(usbssp_data, ring);
+	}
 }
 
+/*
+ * USBSSP controller died, register read returns 0xffffffff
+ * Complete pending commands, mark them ABORTED.
+ * USB requests need to be given back as gadget core might be waiting with
+ * device lock held for the Requests to finish during device disconnect,
+ * blocking device remove.
+ *
+ */
+void usbssp_udc_died(struct usbssp_udc *usbssp_data)
+{
+	int i;
+
+	if (usbssp_data->usbssp_state & USBSSP_STATE_DYING)
+		return;
+
+	dev_err(usbssp_data->dev,
+			"USBSSP controller not responding, assume dead\n");
+	usbssp_data->usbssp_state |= USBSSP_STATE_DYING;
+
+	usbssp_cleanup_command_queue(usbssp_data);
+
+	/* return any pending requests, remove may be waiting for them */
+	for (i = 0; i < 31; i++)
+		usbssp_kill_endpoint_request(usbssp_data, i);
+}
+
+
 void usbssp_handle_command_timeout(struct work_struct *work)
 {
 	/*TODO: implements function*/
@@ -258,6 +327,166 @@  void usbssp_cleanup_command_queue(struct usbssp_udc *usbssp_data)
 		usbssp_complete_del_and_free_cmd(cur_cmd, COMP_COMMAND_ABORTED);
 }
 
+static void handle_port_status(struct usbssp_udc *usbssp_data,
+			       union usbssp_trb *event)
+{
+	u32 port_id;
+	u32 portsc, cmd_regs;
+	int max_ports;
+	u8 major_revision;
+	__le32 __iomem *port_regs;
+
+	/* Port status change events always have a successful completion code */
+	if (GET_COMP_CODE(le32_to_cpu(event->generic.field[2])) != COMP_SUCCESS)
+		dev_err(usbssp_data->dev,
+			"WARN: USBSSP returned failed port status event\n");
+
+
+	port_id = GET_PORT_ID(le32_to_cpu(event->generic.field[0]));
+	dev_dbg(usbssp_data->dev,
+		"Port Status Change Event for port %d\n", port_id);
+
+	usbssp_data->devs.port_num = port_id;
+	max_ports = HCS_MAX_PORTS(usbssp_data->hcs_params1);
+
+	if ((port_id <= 0) || (port_id > max_ports)) {
+		dev_err(usbssp_data->dev, "Invalid port id %d\n", port_id);
+		inc_deq(usbssp_data, usbssp_data->event_ring);
+		return;
+	}
+
+	if (!usbssp_data->port_major_revision) {
+		/*
+		 * Figure out to which USB port device is attached:
+		 * is it a USB 3.0 port or a USB 2.0/1.1 port?
+		 */
+		major_revision = usbssp_data->port_array[port_id - 1];
+
+		if (major_revision == 0) {
+			dev_warn(usbssp_data->dev, "Event for port %u not in "
+					"Extended Capabilities, ignoring.\n",
+					port_id);
+			goto cleanup;
+		}
+
+		usbssp_data->port_major_revision = major_revision;
+	}
+
+	port_regs = usbssp_get_port_io_addr(usbssp_data);
+
+	portsc = readl(port_regs);
+	trace_usbssp_handle_port_status(usbssp_data->devs.port_num, portsc);
+	usbssp_data->gadget.speed = usbssp_port_speed(portsc);
+	dev_dbg(usbssp_data->dev, "PORTSC info: %s\n",
+			usbssp_decode_portsc(portsc));
+
+	if ((portsc & PORT_PLC) && (portsc & PORT_PLS_MASK) == XDEV_RESUME) {
+		dev_dbg(usbssp_data->dev, "port resume event for port %d\n",
+				port_id);
+		cmd_regs = readl(&usbssp_data->op_regs->command);
+		if (!(cmd_regs & CMD_RUN)) {
+			dev_warn(usbssp_data->dev, "DC is not running.\n");
+			goto cleanup;
+		}
+		if (DEV_SUPERSPEED_ANY(portsc)) {
+			dev_dbg(usbssp_data->dev, "remote wake SS port %d\n",
+					port_id);
+			usbssp_test_and_clear_bit(usbssp_data, port_regs,
+					PORT_PLC);
+			usbssp_set_link_state(usbssp_data, port_regs, XDEV_U0);
+			usbssp_resume_gadget(usbssp_data);
+			goto cleanup;
+		}
+	}
+
+	if ((portsc & PORT_PLC) && (portsc & PORT_PLS_MASK) == XDEV_U0 &&
+	     DEV_SUPERSPEED_ANY(portsc)) {
+		dev_dbg(usbssp_data->dev, "resume SS port %d\n", port_id);
+		usbssp_test_and_clear_bit(usbssp_data, port_regs, PORT_PLC);
+	}
+
+	if ((portsc & PORT_PLC) && (portsc & PORT_PLS_MASK) == XDEV_U1 &&
+	     DEV_SUPERSPEED_ANY(portsc)) {
+		dev_dbg(usbssp_data->dev, "suspend U1 SS port %d\n", port_id);
+		usbssp_test_and_clear_bit(usbssp_data, port_regs, PORT_PLC);
+		usbssp_suspend_gadget(usbssp_data);
+	}
+
+	if ((portsc & PORT_PLC) && ((portsc & PORT_PLS_MASK) == XDEV_U2 ||
+	    (portsc & PORT_PLS_MASK) == XDEV_U3)) {
+		dev_dbg(usbssp_data->dev, "resume SS port %d finished\n",
+				port_id);
+		usbssp_test_and_clear_bit(usbssp_data, port_regs, PORT_PLC);
+		usbssp_suspend_gadget(usbssp_data);
+	}
+
+	/*Attach device */
+	if ((portsc & PORT_CSC) && (portsc & PORT_CONNECT)) {
+		dev_dbg(usbssp_data->dev, "Port status change: Device Attached\n");
+		usbssp_data->defered_event |= EVENT_DEV_CONNECTED;
+		queue_work(usbssp_data->bottom_irq_wq,
+				&usbssp_data->bottom_irq);
+		usbssp_test_and_clear_bit(usbssp_data, port_regs, PORT_CSC);
+	}
+
+	/*Detach device*/
+	if ((portsc & PORT_CSC) && !(portsc & PORT_CONNECT)) {
+		dev_dbg(usbssp_data->dev,
+				"Port status change: Device Deattached\n");
+		usbssp_data->defered_event |= EVENT_DEV_DISCONECTED;
+		queue_work(usbssp_data->bottom_irq_wq,
+				&usbssp_data->bottom_irq);
+		usbssp_test_and_clear_bit(usbssp_data, port_regs, PORT_CSC);
+	}
+
+	/*Port Reset Change - port is in reset state */
+	if ((portsc & PORT_RC) && (portsc & PORT_RESET)) {
+		dev_dbg(usbssp_data->dev,
+			"Port status change: Port reset signaling detected\n");
+		usbssp_test_and_clear_bit(usbssp_data, port_regs, PORT_RC);
+	}
+
+	/*Port Reset Change - port is not in reset state */
+	if ((portsc & PORT_RC) && !(portsc & PORT_RESET)) {
+		dev_dbg(usbssp_data->dev,
+			"Port status change: Port reset completion detected\n");
+		usbssp_gadget_reset_interrupt(usbssp_data);
+		usbssp_data->defered_event |= EVENT_USB_RESET;
+		queue_work(usbssp_data->bottom_irq_wq,
+				&usbssp_data->bottom_irq);
+		usbssp_test_and_clear_bit(usbssp_data, port_regs, PORT_RC);
+	}
+
+	/*Port Warm Reset Change*/
+	if (portsc & PORT_WRC) {
+		dev_dbg(usbssp_data->dev,
+			"Port status change: Port Warm Reset detected\n");
+		usbssp_test_and_clear_bit(usbssp_data, port_regs, PORT_WRC);
+	}
+
+	/*Port Over-Curretn Change*/
+	if (portsc & PORT_OCC) {
+		dev_dbg(usbssp_data->dev,
+			"Port status change: Port Over Current detected\n");
+		usbssp_test_and_clear_bit(usbssp_data, port_regs, PORT_OCC);
+	}
+
+	/*Port Configure Error Change*/
+	if (portsc & PORT_CEC) {
+		dev_dbg(usbssp_data->dev,
+			"Port status change: Port Configure Error detected\n");
+		usbssp_test_and_clear_bit(usbssp_data, port_regs, PORT_CEC);
+	}
+
+	if (usbssp_data->port_major_revision == 0x02)
+		usbssp_test_and_clear_bit(usbssp_data, port_regs,
+					PORT_PLC);
+
+cleanup:
+	/* Update event ring dequeue pointer before dropping the lock */
+	inc_deq(usbssp_data, usbssp_data->event_ring);
+}
+
 /*
  * This TD is defined by the TRBs starting at start_trb in start_seg and ending
  * at end_trb, which may be in another segment. If the suspect DMA address is a
@@ -334,6 +563,177 @@  struct usbssp_segment *usbssp_trb_in_td(struct usbssp_udc *usbssp_data,
 	return NULL;
 }
 
+/*
+ * This function handles all events on the event ring.
+ * Function can defers handling of some events to kernel thread.
+ * Returns >0 for "possibly more events to process" (caller should call again),
+ * otherwise 0 if done. In future, <0 returns should indicate error code.
+ */
+int usbssp_handle_event(struct usbssp_udc *usbssp_data)
+{
+	union usbssp_trb *event;
+	int update_ptrs = 1;
+	__le32 cycle_bit;
+
+	if (!usbssp_data->event_ring || !usbssp_data->event_ring->dequeue) {
+		dev_err(usbssp_data->dev, "ERROR event ring not ready\n");
+		return -ENOMEM;
+	}
+
+	event = usbssp_data->event_ring->dequeue;
+
+	cycle_bit = (le32_to_cpu(event->event_cmd.flags) & TRB_CYCLE);
+	/* Does the USBSSP or Driver own the TRB? */
+	if (cycle_bit != usbssp_data->event_ring->cycle_state)
+		return 0;
+
+	trace_usbssp_handle_event(usbssp_data->event_ring, &event->generic);
+
+	/*
+	 * Barrier between reading the TRB_CYCLE (valid) flag above and any
+	 * speculative reads of the event's flags/data below.
+	 */
+	rmb();
+
+	switch ((le32_to_cpu(event->event_cmd.flags) & TRB_TYPE_BITMASK)) {
+	case TRB_TYPE(TRB_PORT_STATUS):
+		handle_port_status(usbssp_data, event);
+		update_ptrs = 0;
+		break;
+	default:
+		dev_warn(usbssp_data->dev, "ERROR unknown event type %ld\n",
+			TRB_FIELD_TO_TYPE(
+			le32_to_cpu(event->event_cmd.flags)));
+	}
+
+
+	/*
+	 * Any of the above functions may drop and re-acquire the lock, so check
+	 * to make sure a watchdog timer didn't mark the device as
+	 * non-responsive.
+	 */
+	if (usbssp_data->usbssp_state & USBSSP_STATE_DYING) {
+		dev_dbg(usbssp_data->dev, "USBSSP device dying, returning from "
+			"event handle.\n");
+		return 0;
+	}
+
+	if (update_ptrs) {
+		/* Update SW event ring dequeue pointer */
+		inc_deq(usbssp_data, usbssp_data->event_ring);
+	}
+
+	/*
+	 * Are there more items on the event ring? Caller will call us again to
+	 * check.
+	 */
+	return 1;
+}
+
+irqreturn_t usbssp_irq(int irq, void *priv)
+{
+	struct usbssp_udc *usbssp_data = (struct usbssp_udc *)priv;
+	union usbssp_trb *event_ring_deq;
+	irqreturn_t ret = IRQ_NONE;
+	unsigned long flags;
+	dma_addr_t deq;
+	u64 temp_64;
+	u32 status;
+
+	spin_lock_irqsave(&usbssp_data->lock, flags);
+
+	/*
+	 * Check if the USBSSP controller generated the interrupt,
+	 * or the irq is shared
+	 */
+	status = readl(&usbssp_data->op_regs->status);
+	if (status == ~(u32)0) {
+		usbssp_udc_died(usbssp_data);
+		ret = IRQ_HANDLED;
+		goto out;
+	}
+
+	if (!(status & STS_EINT))
+		goto out;
+
+	if (status & STS_FATAL) {
+		dev_warn(usbssp_data->dev, "WARNING: Device Controller Error\n");
+		usbssp_halt(usbssp_data);
+		ret = IRQ_HANDLED;
+		goto out;
+	}
+
+	/*
+	 * Clear the op reg interrupt status first,
+	 * so we can receive interrupts from other MSI-X interrupters.
+	 * Write 1 to clear the interrupt status.
+	 */
+	status |= STS_EINT;
+	writel(status, &usbssp_data->op_regs->status);
+
+	if (usbssp_data->msi_enabled) {
+		u32 irq_pending;
+
+		irq_pending = readl(&usbssp_data->ir_set->irq_pending);
+		irq_pending |= IMAN_IP;
+		writel(irq_pending, &usbssp_data->ir_set->irq_pending);
+	}
+
+	if (usbssp_data->usbssp_state & USBSSP_STATE_DYING ||
+			usbssp_data->usbssp_state & USBSSP_STATE_HALTED) {
+		dev_dbg(usbssp_data->dev,
+				"USBSSP controller dying, ignoring interrupt. "
+				"Shouldn't IRQs be disabled?\n");
+		/*
+		 * Clear the event handler busy flag (RW1C);
+		 * the event ring should be empty.
+		 */
+		temp_64 = usbssp_read_64(usbssp_data,
+				&usbssp_data->ir_set->erst_dequeue);
+		usbssp_write_64(usbssp_data, temp_64 | ERST_EHB,
+				&usbssp_data->ir_set->erst_dequeue);
+		ret = IRQ_HANDLED;
+		goto out;
+	}
+
+	event_ring_deq = usbssp_data->event_ring->dequeue;
+
+	while ((ret = usbssp_handle_event(usbssp_data)) == 1) {
+	}
+
+	temp_64 = usbssp_read_64(usbssp_data,
+			&usbssp_data->ir_set->erst_dequeue);
+	/* If necessary, update the HW's version of the event ring deq ptr. */
+	if (event_ring_deq != usbssp_data->event_ring->dequeue) {
+
+		deq = usbssp_trb_virt_to_dma(usbssp_data->event_ring->deq_seg,
+				usbssp_data->event_ring->dequeue);
+
+		if (deq == 0)
+			dev_warn(usbssp_data->dev,
+					"WARN something wrong with SW event "
+					"ring dequeue ptr.\n");
+		/* Update USBSSP event ring dequeue pointer */
+		temp_64 &= ERST_PTR_MASK;
+		temp_64 |= ((u64) deq & (u64) ~ERST_PTR_MASK);
+	}
+
+	/* Clear the event handler busy flag (RW1C); event ring is empty. */
+	temp_64 |= ERST_EHB;
+	usbssp_write_64(usbssp_data, temp_64,
+			&usbssp_data->ir_set->erst_dequeue);
+	ret = IRQ_HANDLED;
+
+out:
+	spin_unlock_irqrestore(&usbssp_data->lock, flags);
+	return ret;
+}
+
+irqreturn_t usbssp_msi_irq(int irq, void *usbssp_data)
+{
+	return usbssp_irq(irq, usbssp_data);
+}
+
 /****		Endpoint Ring Operations	****/
 
 /*
diff --git a/drivers/usb/usbssp/gadget.h b/drivers/usb/usbssp/gadget.h
index 0970c7ddcd38..33eccffc885d 100644
--- a/drivers/usb/usbssp/gadget.h
+++ b/drivers/usb/usbssp/gadget.h
@@ -1725,11 +1725,22 @@  struct usbssp_segment *usbssp_trb_in_td(struct usbssp_udc *usbssp_data,
 void usbssp_handle_command_timeout(struct work_struct *work);
 
 void usbssp_cleanup_command_queue(struct usbssp_udc *usbssp_data);
+/* USBSSP port code */
+void usbssp_set_link_state(struct usbssp_udc *usbssp_data,
+			__le32 __iomem *port_regs, u32 link_state);
+
+void usbssp_test_and_clear_bit(struct usbssp_udc *usbssp_data,
+			__le32 __iomem *port_regs, u32 port_bit);
+
 /* USBSSP gadget interface*/
+void usbssp_suspend_gadget(struct usbssp_udc *usbssp_data);
+void usbssp_resume_gadget(struct usbssp_udc *usbssp_data);
 int usbssp_gadget_init(struct usbssp_udc *usbssp_data);
 int usbssp_gadget_exit(struct usbssp_udc *usbssp_data);
 void usbssp_gadget_free_endpoint(struct usbssp_udc *usbssp_data);
 int usbssp_gadget_init_endpoint(struct usbssp_udc *usbssp_data);
+unsigned int usbssp_port_speed(unsigned int port_status);
+void usbssp_gadget_reset_interrupt(struct usbssp_udc *usbssp_data);
 
 static inline char *usbssp_slot_state_string(u32 state)
 {
@@ -2203,4 +2214,6 @@  struct usbssp_udc;
 
 #define to_usbssp_request(r) (container_of(r, struct usbssp_request, request))
 
+__le32 __iomem *usbssp_get_port_io_addr(struct usbssp_udc *usbssp_data);
+
 #endif /* __LINUX_USBSSP_GADGET_H */