Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 56192531 authored by Andiry Xu's avatar Andiry Xu Committed by Greg Kroah-Hartman
Browse files

USB: xHCI: port remote wakeup implementation



This commit implements port remote wakeup.

When a port is in U3 state and resume signaling is detected from a device,
the port transitions to the Resume state, and the xHC generates a Port Status
Change Event.

For USB3 port, software write a '0' to the PLS field to complete the resume
signaling. For USB2 port, the resume should be signaling for at least 20ms,
irq handler set a timer for port remote wakeup, and then finishes process in
hub_control GetPortStatus.

Some codes are borrowed from EHCI code.

Signed-off-by: default avatarAndiry Xu <andiry.xu@amd.com>
Signed-off-by: default avatarSarah Sharp <sarah.a.sharp@linux.intel.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent be88fe4f
Loading
Loading
Loading
Loading
+39 −5
Original line number Diff line number Diff line
@@ -123,7 +123,7 @@ static unsigned int xhci_port_speed(unsigned int port_status)
 * 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.
 */
static u32 xhci_port_state_to_neutral(u32 state)
u32 xhci_port_state_to_neutral(u32 state)
{
	/* Save read-only status and port state */
	return (state & XHCI_PORT_RO) | (state & XHCI_PORT_RWS);
@@ -132,7 +132,7 @@ static u32 xhci_port_state_to_neutral(u32 state)
/*
 * find slot id based on port number.
 */
static int xhci_find_slot_id_by_port(struct xhci_hcd *xhci, u16 port)
int xhci_find_slot_id_by_port(struct xhci_hcd *xhci, u16 port)
{
	int slot_id;
	int i;
@@ -210,7 +210,7 @@ command_cleanup:
/*
 * Ring device, it rings the all doorbells unconditionally.
 */
static void xhci_ring_device(struct xhci_hcd *xhci, int slot_id)
void xhci_ring_device(struct xhci_hcd *xhci, int slot_id)
{
	int i;

@@ -276,7 +276,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
	struct xhci_hcd	*xhci = hcd_to_xhci(hcd);
	int ports;
	unsigned long flags;
	u32 temp, status;
	u32 temp, temp1, status;
	int retval = 0;
	u32 __iomem *addr;
	int slot_id;
@@ -315,6 +315,34 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
		if ((temp & PORT_PLS_MASK) == XDEV_U3
			&& (temp & PORT_POWER))
			status |= 1 << USB_PORT_FEAT_SUSPEND;
		if ((temp & PORT_PLS_MASK) == XDEV_RESUME) {
			if ((temp & PORT_RESET) || !(temp & PORT_PE))
				goto error;
			if (!DEV_SUPERSPEED(temp) && time_after_eq(jiffies,
						xhci->resume_done[wIndex])) {
				xhci_dbg(xhci, "Resume USB2 port %d\n",
					wIndex + 1);
				xhci->resume_done[wIndex] = 0;
				temp1 = xhci_port_state_to_neutral(temp);
				temp1 &= ~PORT_PLS_MASK;
				temp1 |= PORT_LINK_STROBE | XDEV_U0;
				xhci_writel(xhci, temp1, addr);

				xhci_dbg(xhci, "set port %d resume\n",
					wIndex + 1);
				slot_id = xhci_find_slot_id_by_port(xhci,
								 wIndex + 1);
				if (!slot_id) {
					xhci_dbg(xhci, "slot_id is zero\n");
					goto error;
				}
				xhci_ring_device(xhci, slot_id);
				xhci->port_c_suspend[wIndex >> 5] |=
						1 << (wIndex & 31);
				xhci->suspended_ports[wIndex >> 5] &=
						~(1 << (wIndex & 31));
			}
		}
		if ((temp & PORT_PLS_MASK) == XDEV_U0
			&& (temp & PORT_POWER)
			&& (xhci->suspended_ports[wIndex >> 5] &
@@ -500,6 +528,7 @@ int xhci_hub_status_data(struct usb_hcd *hcd, char *buf)
{
	unsigned long flags;
	u32 temp, status;
	u32 mask;
	int i, retval;
	struct xhci_hcd	*xhci = hcd_to_xhci(hcd);
	int ports;
@@ -512,13 +541,18 @@ int xhci_hub_status_data(struct usb_hcd *hcd, char *buf)
	memset(buf, 0, retval);
	status = 0;

	mask = PORT_CSC | PORT_PEC | PORT_OCC;

	spin_lock_irqsave(&xhci->lock, flags);
	/* For each port, did anything change?  If so, set that bit in buf. */
	for (i = 0; i < ports; i++) {
		addr = &xhci->op_regs->port_status_base +
			NUM_PORT_REGS*i;
		temp = xhci_readl(xhci, addr);
		if (temp & (PORT_CSC | PORT_PEC | PORT_OCC)) {
		if ((temp & mask) != 0 ||
			(xhci->port_c_suspend[i >> 5] &	1 << (i & 31)) ||
			(xhci->resume_done[i] && time_after_eq(
			    jiffies, xhci->resume_done[i]))) {
			buf[(i + 1) / 8] |= 1 << (i + 1) % 8;
			status = 1;
		}
+2 −0
Original line number Diff line number Diff line
@@ -1803,6 +1803,8 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags)
	init_completion(&xhci->addr_dev);
	for (i = 0; i < MAX_HC_SLOTS; ++i)
		xhci->devs[i] = NULL;
	for (i = 0; i < MAX_HC_PORTS; ++i)
		xhci->resume_done[i] = 0;

	if (scratchpad_alloc(xhci, flags))
		goto fail;
+56 −1
Original line number Diff line number Diff line
@@ -1165,17 +1165,72 @@ static void handle_vendor_event(struct xhci_hcd *xhci,
static void handle_port_status(struct xhci_hcd *xhci,
		union xhci_trb *event)
{
	struct usb_hcd *hcd = xhci_to_hcd(xhci);
	u32 port_id;
	u32 temp, temp1;
	u32 __iomem *addr;
	int ports;
	int slot_id;

	/* Port status change events always have a successful completion code */
	if (GET_COMP_CODE(event->generic.field[2]) != COMP_SUCCESS) {
		xhci_warn(xhci, "WARN: xHC returned failed port status event\n");
		xhci->error_bitmask |= 1 << 8;
	}
	/* FIXME: core doesn't care about all port link state changes yet */
	port_id = GET_PORT_ID(event->generic.field[0]);
	xhci_dbg(xhci, "Port Status Change Event for port %d\n", port_id);

	ports = HCS_MAX_PORTS(xhci->hcs_params1);
	if ((port_id <= 0) || (port_id > ports)) {
		xhci_warn(xhci, "Invalid port id %d\n", port_id);
		goto cleanup;
	}

	addr = &xhci->op_regs->port_status_base + NUM_PORT_REGS * (port_id - 1);
	temp = xhci_readl(xhci, addr);
	if ((temp & PORT_CONNECT) && (hcd->state == HC_STATE_SUSPENDED)) {
		xhci_dbg(xhci, "resume root hub\n");
		usb_hcd_resume_root_hub(hcd);
	}

	if ((temp & PORT_PLC) && (temp & PORT_PLS_MASK) == XDEV_RESUME) {
		xhci_dbg(xhci, "port resume event for port %d\n", port_id);

		temp1 = xhci_readl(xhci, &xhci->op_regs->command);
		if (!(temp1 & CMD_RUN)) {
			xhci_warn(xhci, "xHC is not running.\n");
			goto cleanup;
		}

		if (DEV_SUPERSPEED(temp)) {
			xhci_dbg(xhci, "resume SS port %d\n", port_id);
			temp = xhci_port_state_to_neutral(temp);
			temp &= ~PORT_PLS_MASK;
			temp |= PORT_LINK_STROBE | XDEV_U0;
			xhci_writel(xhci, temp, addr);
			slot_id = xhci_find_slot_id_by_port(xhci, port_id);
			if (!slot_id) {
				xhci_dbg(xhci, "slot_id is zero\n");
				goto cleanup;
			}
			xhci_ring_device(xhci, slot_id);
			xhci_dbg(xhci, "resume SS port %d finished\n", port_id);
			/* Clear PORT_PLC */
			temp = xhci_readl(xhci, addr);
			temp = xhci_port_state_to_neutral(temp);
			temp |= PORT_PLC;
			xhci_writel(xhci, temp, addr);
		} else {
			xhci_dbg(xhci, "resume HS port %d\n", port_id);
			xhci->resume_done[port_id - 1] = jiffies +
				msecs_to_jiffies(20);
			mod_timer(&hcd->rh_timer,
				  xhci->resume_done[port_id - 1]);
			/* Do the rest in GetPortStatus */
		}
	}

cleanup:
	/* Update event ring dequeue pointer before dropping the lock */
	inc_deq(xhci, xhci->event_ring, true);

+4 −0
Original line number Diff line number Diff line
@@ -1215,6 +1215,7 @@ struct xhci_hcd {
	u32			port_c_suspend[8];	/* port suspend change*/
	u32			suspended_ports[8];	/* which ports are
							   suspended */
	unsigned long		resume_done[MAX_HC_PORTS];
};

/* For testing purposes */
@@ -1459,6 +1460,9 @@ void xhci_ring_ep_doorbell(struct xhci_hcd *xhci, unsigned int slot_id,
int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, u16 wIndex,
		char *buf, u16 wLength);
int xhci_hub_status_data(struct usb_hcd *hcd, char *buf);
u32 xhci_port_state_to_neutral(u32 state);
int xhci_find_slot_id_by_port(struct xhci_hcd *xhci, u16 port);
void xhci_ring_device(struct xhci_hcd *xhci, int slot_id);

/* xHCI contexts */
struct xhci_input_control_ctx *xhci_get_input_control_ctx(struct xhci_hcd *xhci, struct xhci_container_ctx *ctx);