/*-------------------------------------------------------------------------*/
+#define PORT_WAKE_BITS (PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E)
+
#ifdef CONFIG_PM
+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_RWC_BITS;
+
+ /* Port already owned by companion? */
+ if (status & PORT_OWNER)
+ clear_bit(port, &ehci->owned_ports);
+ else if (test_bit(port, &ehci->companion_ports))
+ ehci_writel(ehci, status & ~PORT_PE, reg);
+ 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;
+}
+
static int ehci_bus_suspend (struct usb_hcd *hcd)
{
struct ehci_hcd *ehci = hcd_to_ehci (hcd);
if (time_before (jiffies, ehci->next_statechange))
msleep(5);
+ del_timer_sync(&ehci->watchdog);
+ del_timer_sync(&ehci->iaa_watchdog);
port = HCS_N_PORTS (ehci->hcs_params);
spin_lock_irq (&ehci->lock);
hcd->state = HC_STATE_QUIESCING;
}
ehci->command = ehci_readl(ehci, &ehci->regs->command);
- if (ehci->reclaim)
- ehci->reclaim_ready = 1;
ehci_work(ehci);
/* Unlike other USB host controller types, EHCI doesn't have
* 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);
}
/* enable remote wakeup on all ports */
- if (device_may_wakeup(&hcd->self.root_hub->dev))
- t2 |= PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E;
+ if (hcd->self.root_hub->do_remote_wakeup)
+ t2 |= PORT_WAKE_BITS;
else
- t2 &= ~(PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E);
+ t2 &= ~PORT_WAKE_BITS;
if (t1 != t2) {
ehci_vdbg (ehci, "port %d, %08x -> %08x\n",
}
}
+ /* Apparently some devices need a >= 1-uframe delay here */
+ if (ehci->bus_suspended)
+ udelay(150);
+
/* turn off now-idle HC */
- del_timer_sync (&ehci->watchdog);
ehci_halt (ehci);
hcd->state = HC_STATE_SUSPENDED;
+ if (ehci->reclaim)
+ end_unlink_async(ehci);
+
/* allow remote wakeup */
mask = INTR_MASK;
- if (!device_may_wakeup(&hcd->self.root_hub->dev))
+ if (!hcd->self.root_hub->do_remote_wakeup)
mask &= ~STS_PCD;
ehci_writel(ehci, mask, &ehci->regs->intr_enable);
ehci_readl(ehci, &ehci->regs->intr_enable);
{
struct ehci_hcd *ehci = hcd_to_ehci (hcd);
u32 temp;
+ u32 power_okay;
int i;
if (time_before (jiffies, ehci->next_statechange))
msleep(5);
spin_lock_irq (&ehci->lock);
+ if (!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags)) {
+ spin_unlock_irq(&ehci->lock);
+ return -ESHUTDOWN;
+ }
/* Ideally and we've got a real resume here, and no port's power
* was lost. (For PCI, that means Vaux was maintained.) But we
* 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.
/* restore CMD_RUN, framelist size, and irq threshold */
ehci_writel(ehci, ehci->command, &ehci->regs->command);
+ /* Some controller/firmware combinations need a delay during which
+ * they set up the port statuses. See Bugzilla #8190. */
+ mdelay(8);
+
/* manually resume the ports we suspended during bus_suspend() */
i = HCS_N_PORTS (ehci->hcs_params);
while (i--) {
temp = ehci_readl(ehci, &ehci->regs->port_status [i]);
- temp &= ~(PORT_RWC_BITS
- | PORT_WKOC_E | PORT_WKDISC_E | PORT_WKCONN_E);
+ temp &= ~(PORT_RWC_BITS | PORT_WAKE_BITS);
if (test_bit(i, &ehci->bus_suspended) &&
(temp & PORT_SUSPEND)) {
ehci->reset_done [i] = jiffies + msecs_to_jiffies (20);
ehci_writel(ehci, INTR_MASK, &ehci->regs->intr_enable);
spin_unlock_irq (&ehci->lock);
+ ehci_handover_companion_ports(ehci);
return 0;
}
/*-------------------------------------------------------------------------*/
/* Display the ports dedicated to the companion controller */
-static ssize_t show_companion(struct class_device *class_dev, char *buf)
+static ssize_t show_companion(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
{
struct ehci_hcd *ehci;
int nports, index, n;
int count = PAGE_SIZE;
char *ptr = buf;
- ehci = hcd_to_ehci(bus_to_hcd(class_get_devdata(class_dev)));
+ ehci = hcd_to_ehci(bus_to_hcd(dev_get_drvdata(dev)));
nports = HCS_N_PORTS(ehci->hcs_params);
for (index = 0; index < nports; ++index) {
}
/*
- * Dedicate or undedicate a port to the companion controller.
- * Syntax is "[-]portnum", where a leading '-' sign means
- * return control of the port to the EHCI controller.
+ * Sets the owner of a port
*/
-static ssize_t store_companion(struct class_device *class_dev,
- const char *buf, size_t count)
+static void set_owner(struct ehci_hcd *ehci, int portnum, int new_owner)
{
- struct ehci_hcd *ehci;
- int portnum, new_owner, try;
u32 __iomem *status_reg;
u32 port_status;
+ int try;
- ehci = hcd_to_ehci(bus_to_hcd(class_get_devdata(class_dev)));
- new_owner = PORT_OWNER; /* Owned by companion */
- if (sscanf(buf, "%d", &portnum) != 1)
- return -EINVAL;
- if (portnum < 0) {
- portnum = - portnum;
- new_owner = 0; /* Owned by EHCI */
- }
- if (portnum <= 0 || portnum > HCS_N_PORTS(ehci->hcs_params))
- return -ENOENT;
- status_reg = &ehci->regs->port_status[--portnum];
- if (new_owner)
- set_bit(portnum, &ehci->companion_ports);
- else
- clear_bit(portnum, &ehci->companion_ports);
+ status_reg = &ehci->regs->port_status[portnum];
/*
* The controller won't set the OWNER bit if the port is
* enabled, so this loop will sometimes require at least two
* iterations: one to disable the port and one to set OWNER.
*/
-
for (try = 4; try > 0; --try) {
spin_lock_irq(&ehci->lock);
port_status = ehci_readl(ehci, status_reg);
if (try > 1)
msleep(5);
}
+}
+
+/*
+ * Dedicate or undedicate a port to the companion controller.
+ * Syntax is "[-]portnum", where a leading '-' sign means
+ * return control of the port to the EHCI controller.
+ */
+static ssize_t store_companion(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct ehci_hcd *ehci;
+ int portnum, new_owner;
+
+ ehci = hcd_to_ehci(bus_to_hcd(dev_get_drvdata(dev)));
+ new_owner = PORT_OWNER; /* Owned by companion */
+ if (sscanf(buf, "%d", &portnum) != 1)
+ return -EINVAL;
+ if (portnum < 0) {
+ portnum = - portnum;
+ new_owner = 0; /* Owned by EHCI */
+ }
+ if (portnum <= 0 || portnum > HCS_N_PORTS(ehci->hcs_params))
+ return -ENOENT;
+ portnum--;
+ if (new_owner)
+ set_bit(portnum, &ehci->companion_ports);
+ else
+ clear_bit(portnum, &ehci->companion_ports);
+ set_owner(ehci, portnum, new_owner);
return count;
}
-static CLASS_DEVICE_ATTR(companion, 0644, show_companion, store_companion);
+static DEVICE_ATTR(companion, 0644, show_companion, store_companion);
static inline void create_companion_file(struct ehci_hcd *ehci)
{
/* with integrated TT there is no companion! */
if (!ehci_is_TDI(ehci))
- i = class_device_create_file(ehci_to_hcd(ehci)->self.class_dev,
- &class_device_attr_companion);
+ i = device_create_file(ehci_to_hcd(ehci)->self.dev,
+ &dev_attr_companion);
}
static inline void remove_companion_file(struct ehci_hcd *ehci)
{
/* with integrated TT there is no companion! */
if (!ehci_is_TDI(ehci))
- class_device_remove_file(ehci_to_hcd(ehci)->self.class_dev,
- &class_device_attr_companion);
+ device_remove_file(ehci_to_hcd(ehci)->self.dev,
+ &dev_attr_companion);
}
u32 __iomem *status_reg,
int port_status
) {
- if (!(port_status & PORT_CONNECT)) {
- ehci->reset_done [index] = 0;
+ if (!(port_status & PORT_CONNECT))
return port_status;
- }
/* if reset finished and it's still not enabled -- handoff */
if (!(port_status & PORT_PE)) {
/* Some boards (mostly VIA?) report bogus overcurrent indications,
* causing massive log spam unless we completely ignore them. It
- * may be relevant that VIA VT8235 controlers, where PORT_POWER is
+ * may be relevant that VIA VT8235 controllers, where PORT_POWER is
* always set, seem to clear PORT_OCC and PORT_CSC when writing to
* PORT_POWER; that's surprising, but maybe within-spec.
*/
* controller by the user.
*/
- if (!(temp & PORT_CONNECT))
- ehci->reset_done [i] = 0;
if ((temp & mask) != 0
|| ((temp & PORT_RESUME) != 0
&& time_after_eq(jiffies,
if (HCS_INDICATOR (ehci->hcs_params))
temp |= 0x0080; /* per-port indicators (LEDs) */
#endif
- desc->wHubCharacteristics = (__force __u16)cpu_to_le16 (temp);
+ desc->wHubCharacteristics = cpu_to_le16(temp);
}
/*-------------------------------------------------------------------------*/
-#define PORT_WAKE_BITS (PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E)
-
static int ehci_hub_control (
struct usb_hcd *hcd,
u16 typeReq,
) {
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;
}
break;
case USB_PORT_FEAT_C_SUSPEND:
- /* we auto-clear this feature */
+ clear_bit(wIndex, &ehci->port_c_suspend);
break;
case USB_PORT_FEAT_POWER:
if (HCS_PPC (ehci->hcs_params))
status |= 1 << USB_PORT_FEAT_C_CONNECTION;
if (temp & PORT_PEC)
status |= 1 << USB_PORT_FEAT_C_ENABLE;
- if ((temp & PORT_OCC) && !ignore_oc)
+
+ if ((temp & PORT_OCC) && !ignore_oc){
status |= 1 << USB_PORT_FEAT_C_OVER_CURRENT;
+ /*
+ * Hubs should disable port power on over-current.
+ * However, not all EHCI implementations do this
+ * automatically, even if they _do_ support per-port
+ * power switching; they're allowed to just limit the
+ * current. khubd will turn the power back on.
+ */
+ if (HCS_PPC (ehci->hcs_params)){
+ ehci_writel(ehci,
+ temp & ~(PORT_RWC_BITS | PORT_POWER),
+ status_reg);
+ }
+ }
+
/* whoever resumes must GetPortStatus to complete it!! */
if (temp & PORT_RESUME) {
/* resume completed? */
else if (time_after_eq(jiffies,
ehci->reset_done[wIndex])) {
- status |= 1 << USB_PORT_FEAT_C_SUSPEND;
+ set_bit(wIndex, &ehci->port_c_suspend);
ehci->reset_done[wIndex] = 0;
/* stop resume signaling */
status |= 1 << USB_PORT_FEAT_RESET;
if (temp & PORT_POWER)
status |= 1 << USB_PORT_FEAT_POWER;
+ if (test_bit(wIndex, &ehci->port_c_suspend))
+ status |= 1 << USB_PORT_FEAT_C_SUSPEND;
-#ifndef EHCI_VERBOSE_DEBUG
+#ifndef VERBOSE_DEBUG
if (status & ~0xffff) /* only if wPortChange is interesting */
#endif
dbg_port (ehci, "GetStatus", wIndex + 1, temp);
- // we "know" this alignment is good, caller used kmalloc()...
- *((__le32 *) buf) = cpu_to_le32 (status);
+ put_unaligned_le32(status, buf);
break;
case SetHubFeature:
switch (wValue) {
if ((temp & PORT_PE) == 0
|| (temp & PORT_RESET) != 0)
goto error;
- if (device_may_wakeup(&hcd->self.root_hub->dev))
- temp |= PORT_WAKE_BITS;
ehci_writel(ehci, temp | PORT_SUSPEND, status_reg);
break;
case USB_PORT_FEAT_POWER:
spin_unlock_irqrestore (&ehci->lock, flags);
return retval;
}
+
+static void ehci_relinquish_port(struct usb_hcd *hcd, int portnum)
+{
+ struct ehci_hcd *ehci = hcd_to_ehci(hcd);
+
+ if (ehci_is_TDI(ehci))
+ return;
+ set_owner(ehci, --portnum, PORT_OWNER);
+}
+
+static int ehci_port_handed_over(struct usb_hcd *hcd, int portnum)
+{
+ struct ehci_hcd *ehci = hcd_to_ehci(hcd);
+ u32 __iomem *reg;
+
+ if (ehci_is_TDI(ehci))
+ return 0;
+ reg = &ehci->regs->port_status[portnum - 1];
+ return ehci_readl(ehci, reg) & PORT_OWNER;
+}