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

Commit 383975d7 authored by Alan Stern's avatar Alan Stern Committed by Greg Kroah-Hartman
Browse files

USB: EHCI, OHCI: handover changes



This patch (as887) changes the way ehci-hcd and ohci-hcd handle a loss
of VBUS power during suspend.  In order for the USB-persist facility
to work correctly, it is necessary for low- and full-speed devices
attached to a high-speed port to be handed back to the companion
controller during resume processing.

This entails three changes: adding code to ehci-hcd to perform the
handover, removing code from ohci-hcd to turn off ports during
root-hub reinit, and adding code to ohci-hcd to turn on ports during
PCI controller resume.  (Other bus glue resume methods for platforms
supporting high-speed controllers would need a similar change, if any
existed.)

Signed-off-by: default avatarAlan Stern <stern@rowland.harvard.edu>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent 0458d5b4
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -399,6 +399,8 @@ static void ehci_port_power (struct ehci_hcd *ehci, int is_on)
				is_on ? SetPortFeature : ClearPortFeature,
				USB_PORT_FEAT_POWER,
				port--, NULL, 0);
	/* Flush those writes */
	ehci_readl(ehci, &ehci->regs->command);
	msleep(20);
}

+94 −5
Original line number Diff line number Diff line
@@ -28,6 +28,87 @@

/*-------------------------------------------------------------------------*/

#ifdef	CONFIG_USB_PERSIST

static int ehci_hub_control(
	struct usb_hcd	*hcd,
	u16		typeReq,
	u16		wValue,
	u16		wIndex,
	char		*buf,
	u16		wLength
);

/* After a power loss, ports that were owned by the companion must be
 * reset so that the companion can still own them.
 */
static void ehci_handover_companion_ports(struct ehci_hcd *ehci)
{
	u32 __iomem	*reg;
	u32		status;
	int		port;
	__le32		buf;
	struct usb_hcd	*hcd = ehci_to_hcd(ehci);

	if (!ehci->owned_ports)
		return;

	/* Give the connections some time to appear */
	msleep(20);

	port = HCS_N_PORTS(ehci->hcs_params);
	while (port--) {
		if (test_bit(port, &ehci->owned_ports)) {
			reg = &ehci->regs->port_status[port];
			status = ehci_readl(ehci, reg);

			/* Port already owned by companion? */
			if (status & PORT_OWNER)
				clear_bit(port, &ehci->owned_ports);
			else
				ehci_hub_control(hcd, SetPortFeature,
						USB_PORT_FEAT_RESET, port + 1,
						NULL, 0);
		}
	}

	if (!ehci->owned_ports)
		return;
	msleep(90);		/* Wait for resets to complete */

	port = HCS_N_PORTS(ehci->hcs_params);
	while (port--) {
		if (test_bit(port, &ehci->owned_ports)) {
			ehci_hub_control(hcd, GetPortStatus,
					0, port + 1,
					(char *) &buf, sizeof(buf));

			/* The companion should now own the port,
			 * but if something went wrong the port must not
			 * remain enabled.
			 */
			reg = &ehci->regs->port_status[port];
			status = ehci_readl(ehci, reg) & ~PORT_RWC_BITS;
			if (status & PORT_OWNER)
				ehci_writel(ehci, status | PORT_CSC, reg);
			else {
				ehci_dbg(ehci, "failed handover port %d: %x\n",
						port + 1, status);
				ehci_writel(ehci, status & ~PORT_PE, reg);
			}
		}
	}

	ehci->owned_ports = 0;
}

#else	/* CONFIG_USB_PERSIST */

static inline void ehci_handover_companion_ports(struct ehci_hcd *ehci)
{ }

#endif

#ifdef	CONFIG_PM

static int ehci_bus_suspend (struct usb_hcd *hcd)
@@ -60,14 +141,16 @@ static int ehci_bus_suspend (struct usb_hcd *hcd)
	 * then manually resume them in the bus_resume() routine.
	 */
	ehci->bus_suspended = 0;
	ehci->owned_ports = 0;
	while (port--) {
		u32 __iomem	*reg = &ehci->regs->port_status [port];
		u32		t1 = ehci_readl(ehci, reg) & ~PORT_RWC_BITS;
		u32		t2 = t1;

		/* keep track of which ports we suspend */
		if ((t1 & PORT_PE) && !(t1 & PORT_OWNER) &&
				!(t1 & PORT_SUSPEND)) {
		if (t1 & PORT_OWNER)
			set_bit(port, &ehci->owned_ports);
		else if ((t1 & PORT_PE) && !(t1 & PORT_SUSPEND)) {
			t2 |= PORT_SUSPEND;
			set_bit(port, &ehci->bus_suspended);
		}
@@ -108,6 +191,7 @@ static int ehci_bus_resume (struct usb_hcd *hcd)
{
	struct ehci_hcd		*ehci = hcd_to_ehci (hcd);
	u32			temp;
	u32			power_okay;
	int			i;

	if (time_before (jiffies, ehci->next_statechange))
@@ -120,8 +204,9 @@ static int ehci_bus_resume (struct usb_hcd *hcd)
	 * the last user of the controller, not reset/pm hardware keeping
	 * state we gave to it.
	 */
	temp = ehci_readl(ehci, &ehci->regs->intr_enable);
	ehci_dbg(ehci, "resume root hub%s\n", temp ? "" : " after power loss");
	power_okay = ehci_readl(ehci, &ehci->regs->intr_enable);
	ehci_dbg(ehci, "resume root hub%s\n",
			power_okay ? "" : " after power loss");

	/* at least some APM implementations will try to deliver
	 * IRQs right away, so delay them until we're ready.
@@ -184,6 +269,9 @@ static int ehci_bus_resume (struct usb_hcd *hcd)
	ehci_writel(ehci, INTR_MASK, &ehci->regs->intr_enable);

	spin_unlock_irq (&ehci->lock);

	if (!power_okay)
		ehci_handover_companion_ports(ehci);
	return 0;
}

@@ -448,7 +536,8 @@ static int ehci_hub_control (
) {
	struct ehci_hcd	*ehci = hcd_to_ehci (hcd);
	int		ports = HCS_N_PORTS (ehci->hcs_params);
	u32 __iomem	*status_reg = &ehci->regs->port_status[wIndex - 1];
	u32 __iomem	*status_reg = &ehci->regs->port_status[
				(wIndex & 0xff) - 1];
	u32		temp, status;
	unsigned long	flags;
	int		retval = 0;
+4 −3
Original line number Diff line number Diff line
@@ -312,13 +312,14 @@ static int ehci_pci_resume(struct usb_hcd *hcd)
	ehci_work(ehci);
	spin_unlock_irq(&ehci->lock);

	/* here we "know" root ports should always stay powered */
	ehci_port_power(ehci, 1);

	ehci_writel(ehci, ehci->command, &ehci->regs->command);
	ehci_writel(ehci, FLAG_CF, &ehci->regs->configured_flag);
	ehci_readl(ehci, &ehci->regs->command);	/* unblock posted writes */

	/* here we "know" root ports should always stay powered */
	ehci_port_power(ehci, 1);
	ehci_handover_companion_ports(ehci);

	hcd->state = HC_STATE_SUSPENDED;
	return 0;
}
+3 −0
Original line number Diff line number Diff line
@@ -96,11 +96,14 @@ struct ehci_hcd { /* one per controller */

	/* per root hub port */
	unsigned long		reset_done [EHCI_MAX_ROOT_PORTS];

	/* bit vectors (one bit per port) */
	unsigned long		bus_suspended;		/* which ports were
			already suspended at the start of a bus suspend */
	unsigned long		companion_ports;	/* which ports are
			dedicated to the companion controller */
	unsigned long		owned_ports;		/* which ports are
			owned by the companion during a bus suspend */

	/* per-HC memory pools (could be per-bus, but ...) */
	struct dma_pool		*qh_pool;	/* qh per active urb */
+2 −19
Original line number Diff line number Diff line
@@ -510,15 +510,7 @@ static int ohci_run (struct ohci_hcd *ohci)
	// flush the writes
	(void) ohci_readl (ohci, &ohci->regs->control);
	msleep(temp);
	temp = roothub_a (ohci);
	if (!(temp & RH_A_NPS)) {
		/* power down each port */
		for (temp = 0; temp < ohci->num_ports; temp++)
			ohci_writel (ohci, RH_PS_LSDA,
				&ohci->regs->roothub.portstatus [temp]);
	}
	// flush those writes
	(void) ohci_readl (ohci, &ohci->regs->control);

	memset (ohci->hcca, 0, sizeof (struct ohci_hcca));

	/* 2msec timelimit here means no irqs/preempt */
@@ -826,17 +818,8 @@ static int ohci_restart (struct ohci_hcd *ohci)
	if ((temp = ohci_run (ohci)) < 0) {
		ohci_err (ohci, "can't restart, %d\n", temp);
		return temp;
	} else {
		/* here we "know" root ports should always stay powered,
		 * and that if we try to turn them back on the root hub
		 * will respond to CSC processing.
		 */
		i = ohci->num_ports;
		while (i--)
			ohci_writel (ohci, RH_PS_PSS,
				&ohci->regs->roothub.portstatus [i]);
		ohci_dbg (ohci, "restart complete\n");
	}
	ohci_dbg(ohci, "restart complete\n");
	return 0;
}
#endif
Loading