X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=drivers%2Fusb%2Fhost%2Fohci-hub.c;h=1c296c3adcbae6e0e5b45f42ead56ab8cc402df7;hb=6a77f38946aaee1cd85eeec6cf4229b204c15071;hp=424971c4bc275b160b2dfe8a22b7593cc263a3fc;hpb=87fc8d1bb10cd459024a742c6a10961fefcef18f;p=linux-2.6.git diff --git a/drivers/usb/host/ohci-hub.c b/drivers/usb/host/ohci-hub.c index 424971c4b..1c296c3ad 100644 --- a/drivers/usb/host/ohci-hub.c +++ b/drivers/usb/host/ohci-hub.c @@ -2,7 +2,7 @@ * OHCI HCD (Host Controller Driver) for USB. * * (C) Copyright 1999 Roman Weissgaerber - * (C) Copyright 2000-2002 David Brownell + * (C) Copyright 2000-2004 David Brownell * * This file is licenced under GPL */ @@ -11,34 +11,8 @@ /* * OHCI Root Hub ... the nonsharable stuff - * - * Registers don't need cpu_to_le32, that happens transparently */ -/* AMD-756 (D2 rev) reports corrupt register contents in some cases. - * The erratum (#4) description is incorrect. AMD's workaround waits - * till some bits (mostly reserved) are clear; ok for all revs. - */ -#define read_roothub(hc, register, mask) ({ \ - u32 temp = ohci_readl (&hc->regs->roothub.register); \ - if (temp == -1) \ - disable (hc); \ - else if (hc->flags & OHCI_QUIRK_AMD756) \ - while (temp & mask) \ - temp = ohci_readl (&hc->regs->roothub.register); \ - temp; }) - -static u32 roothub_a (struct ohci_hcd *hc) - { return read_roothub (hc, a, 0xfc0fe000); } -static inline u32 roothub_b (struct ohci_hcd *hc) - { return ohci_readl (&hc->regs->roothub.b); } -static inline u32 roothub_status (struct ohci_hcd *hc) - { return ohci_readl (&hc->regs->roothub.status); } -static u32 roothub_portstatus (struct ohci_hcd *hc, int i) - { return read_roothub (hc, portstatus [i], 0xffe0fce0); } - -/*-------------------------------------------------------------------------*/ - #define dbg_port(hc,label,num,value) \ ohci_dbg (hc, \ "%s roothub.portstatus [%d] " \ @@ -69,48 +43,44 @@ static u32 roothub_portstatus (struct ohci_hcd *hc, int i) static void dl_done_list (struct ohci_hcd *, struct pt_regs *); static void finish_unlinks (struct ohci_hcd *, u16 , struct pt_regs *); +static int ohci_restart (struct ohci_hcd *ohci); static int ohci_hub_suspend (struct usb_hcd *hcd) { struct ohci_hcd *ohci = hcd_to_ohci (hcd); - struct usb_device *root = hcd_to_bus (&ohci->hcd)->root_hub; int status = 0; + unsigned long flags; - if (root->dev.power.power_state != 0) - return 0; - if (time_before (jiffies, ohci->next_statechange)) - return -EAGAIN; - - spin_lock_irq (&ohci->lock); + spin_lock_irqsave (&ohci->lock, flags); - ohci->hc_control = ohci_readl (&ohci->regs->control); + ohci->hc_control = ohci_readl (ohci, &ohci->regs->control); switch (ohci->hc_control & OHCI_CTRL_HCFS) { case OHCI_USB_RESUME: ohci_dbg (ohci, "resume/suspend?\n"); ohci->hc_control &= ~OHCI_CTRL_HCFS; ohci->hc_control |= OHCI_USB_RESET; - writel (ohci->hc_control, &ohci->regs->control); - (void) ohci_readl (&ohci->regs->control); + ohci_writel (ohci, ohci->hc_control, &ohci->regs->control); + (void) ohci_readl (ohci, &ohci->regs->control); /* FALL THROUGH */ case OHCI_USB_RESET: status = -EBUSY; ohci_dbg (ohci, "needs reinit!\n"); goto done; case OHCI_USB_SUSPEND: - ohci_dbg (ohci, "already suspended?\n"); - goto succeed; + ohci_dbg (ohci, "already suspended\n"); + goto done; } ohci_dbg (ohci, "suspend root hub\n"); /* First stop any processing */ - ohci->hcd.state = USB_STATE_QUIESCING; + hcd->state = USB_STATE_QUIESCING; if (ohci->hc_control & OHCI_SCHED_ENABLES) { int limit; ohci->hc_control &= ~OHCI_SCHED_ENABLES; - writel (ohci->hc_control, &ohci->regs->control); - ohci->hc_control = ohci_readl (&ohci->regs->control); - writel (OHCI_INTR_SF, &ohci->regs->intrstatus); + ohci_writel (ohci, ohci->hc_control, &ohci->regs->control); + ohci->hc_control = ohci_readl (ohci, &ohci->regs->control); + ohci_writel (ohci, OHCI_INTR_SF, &ohci->regs->intrstatus); /* sched disables take effect on the next frame, * then the last WDH could take 6+ msec @@ -120,18 +90,20 @@ static int ohci_hub_suspend (struct usb_hcd *hcd) while (limit > 0) { udelay (250); limit =- 250; - if (ohci_readl (&ohci->regs->intrstatus) & OHCI_INTR_SF) + if (ohci_readl (ohci, &ohci->regs->intrstatus) + & OHCI_INTR_SF) break; } dl_done_list (ohci, NULL); mdelay (7); } dl_done_list (ohci, NULL); - finish_unlinks (ohci, OHCI_FRAME_NO(ohci->hcca), NULL); - writel (ohci_readl (&ohci->regs->intrstatus), &ohci->regs->intrstatus); + finish_unlinks (ohci, ohci_frame_no(ohci), NULL); + ohci_writel (ohci, ohci_readl (ohci, &ohci->regs->intrstatus), + &ohci->regs->intrstatus); /* maybe resume can wake root hub */ - if (ohci->hcd.remote_wakeup) + if (hcd->remote_wakeup) ohci->hc_control |= OHCI_CTRL_RWE; else ohci->hc_control &= ~OHCI_CTRL_RWE; @@ -139,19 +111,16 @@ static int ohci_hub_suspend (struct usb_hcd *hcd) /* Suspend hub */ ohci->hc_control &= ~OHCI_CTRL_HCFS; ohci->hc_control |= OHCI_USB_SUSPEND; - writel (ohci->hc_control, &ohci->regs->control); - (void) ohci_readl (&ohci->regs->control); + ohci_writel (ohci, ohci->hc_control, &ohci->regs->control); + (void) ohci_readl (ohci, &ohci->regs->control); /* no resumes until devices finish suspending */ ohci->next_statechange = jiffies + msecs_to_jiffies (5); -succeed: - /* it's not USB_STATE_SUSPENDED unless access to this - * hub from the non-usb side (PCI, SOC, etc) stopped - */ - root->dev.power.power_state = 3; done: - spin_unlock_irq (&ohci->lock); + if (status == 0) + hcd->state = HCD_STATE_SUSPENDED; + spin_unlock_irqrestore (&ohci->lock, flags); return status; } @@ -163,29 +132,36 @@ static inline struct ed *find_head (struct ed *ed) return ed; } -static int hc_restart (struct ohci_hcd *ohci); - -/* caller owns root->serialize */ +/* caller has locked the root hub */ static int ohci_hub_resume (struct usb_hcd *hcd) { struct ohci_hcd *ohci = hcd_to_ohci (hcd); - struct usb_device *root = hcd_to_bus (&ohci->hcd)->root_hub; u32 temp, enables; int status = -EINPROGRESS; - if (!root->dev.power.power_state) - return 0; if (time_before (jiffies, ohci->next_statechange)) - return -EAGAIN; + msleep(5); spin_lock_irq (&ohci->lock); - ohci->hc_control = ohci_readl (&ohci->regs->control); - switch (ohci->hc_control & OHCI_CTRL_HCFS) { + ohci->hc_control = ohci_readl (ohci, &ohci->regs->control); + + if (ohci->hc_control & (OHCI_CTRL_IR | OHCI_SCHED_ENABLES)) { + /* this can happen after suspend-to-disk */ + if (hcd->state == USB_STATE_RESUMING) { + ohci_dbg (ohci, "BIOS/SMM active, control %03x\n", + ohci->hc_control); + status = -EBUSY; + /* this happens when pmcore resumes HC then root */ + } else { + ohci_dbg (ohci, "duplicate resume\n"); + status = 0; + } + } else switch (ohci->hc_control & OHCI_CTRL_HCFS) { case OHCI_USB_SUSPEND: ohci->hc_control &= ~(OHCI_CTRL_HCFS|OHCI_SCHED_ENABLES); ohci->hc_control |= OHCI_USB_RESUME; - writel (ohci->hc_control, &ohci->regs->control); - (void) ohci_readl (&ohci->regs->control); + ohci_writel (ohci, ohci->hc_control, &ohci->regs->control); + (void) ohci_readl (ohci, &ohci->regs->control); ohci_dbg (ohci, "resume root hub\n"); break; case OHCI_USB_RESUME: @@ -194,7 +170,6 @@ static int ohci_hub_resume (struct usb_hcd *hcd) break; case OHCI_USB_OPER: ohci_dbg (ohci, "odd resume\n"); - root->dev.power.power_state = 0; status = 0; break; default: /* RESET, we lost power */ @@ -202,27 +177,31 @@ static int ohci_hub_resume (struct usb_hcd *hcd) status = -EBUSY; } spin_unlock_irq (&ohci->lock); - if (status == -EBUSY) - return hc_restart (ohci); + if (status == -EBUSY) { + (void) ohci_init (ohci); + return ohci_restart (ohci); + } if (status != -EINPROGRESS) return status; temp = roothub_a (ohci) & RH_A_NDP; enables = 0; while (temp--) { - u32 stat = ohci_readl (&ohci->regs->roothub.portstatus [temp]); + u32 stat = ohci_readl (ohci, + &ohci->regs->roothub.portstatus [temp]); /* force global, not selective, resume */ if (!(stat & RH_PS_PSS)) continue; - writel (RH_PS_POCI, &ohci->regs->roothub.portstatus [temp]); + ohci_writel (ohci, RH_PS_POCI, + &ohci->regs->roothub.portstatus [temp]); } /* Some controllers (lucent) need extra-long delays */ - ohci->hcd.state = USB_STATE_RESUMING; + hcd->state = USB_STATE_RESUMING; mdelay (20 /* usb 11.5.1.10 */ + 15); - temp = ohci_readl (&ohci->regs->control); + temp = ohci_readl (ohci, &ohci->regs->control); temp &= OHCI_CTRL_HCFS; if (temp != OHCI_USB_RESUME) { ohci_err (ohci, "controller won't resume\n"); @@ -230,36 +209,36 @@ static int ohci_hub_resume (struct usb_hcd *hcd) } /* disable old schedule state, reinit from scratch */ - writel (0, &ohci->regs->ed_controlhead); - writel (0, &ohci->regs->ed_controlcurrent); - writel (0, &ohci->regs->ed_bulkhead); - writel (0, &ohci->regs->ed_bulkcurrent); - writel (0, &ohci->regs->ed_periodcurrent); - writel ((u32) ohci->hcca_dma, &ohci->regs->hcca); + ohci_writel (ohci, 0, &ohci->regs->ed_controlhead); + ohci_writel (ohci, 0, &ohci->regs->ed_controlcurrent); + ohci_writel (ohci, 0, &ohci->regs->ed_bulkhead); + ohci_writel (ohci, 0, &ohci->regs->ed_bulkcurrent); + ohci_writel (ohci, 0, &ohci->regs->ed_periodcurrent); + ohci_writel (ohci, (u32) ohci->hcca_dma, &ohci->regs->hcca); periodic_reinit (ohci); /* interrupts might have been disabled */ - writel (OHCI_INTR_INIT, &ohci->regs->intrenable); + ohci_writel (ohci, OHCI_INTR_INIT, &ohci->regs->intrenable); if (ohci->ed_rm_list) - writel (OHCI_INTR_SF, &ohci->regs->intrenable); - writel (ohci_readl (&ohci->regs->intrstatus), &ohci->regs->intrstatus); + ohci_writel (ohci, OHCI_INTR_SF, &ohci->regs->intrenable); + ohci_writel (ohci, ohci_readl (ohci, &ohci->regs->intrstatus), + &ohci->regs->intrstatus); /* Then re-enable operations */ - writel (OHCI_USB_OPER, &ohci->regs->control); - (void) ohci_readl (&ohci->regs->control); + ohci_writel (ohci, OHCI_USB_OPER, &ohci->regs->control); + (void) ohci_readl (ohci, &ohci->regs->control); msleep (3); temp = OHCI_CONTROL_INIT | OHCI_USB_OPER; - if (ohci->hcd.can_wakeup) + if (hcd->can_wakeup) temp |= OHCI_CTRL_RWC; ohci->hc_control = temp; - writel (temp, &ohci->regs->control); - (void) ohci_readl (&ohci->regs->control); + ohci_writel (ohci, temp, &ohci->regs->control); + (void) ohci_readl (ohci, &ohci->regs->control); /* TRSMRCY */ msleep (10); - root->dev.power.power_state = 0; /* keep it alive for ~5x suspend + resume costs */ ohci->next_statechange = jiffies + msecs_to_jiffies (250); @@ -269,31 +248,31 @@ static int ohci_hub_resume (struct usb_hcd *hcd) temp = 0; if (!ohci->ed_rm_list) { if (ohci->ed_controltail) { - writel (find_head (ohci->ed_controltail)->dma, - &ohci->regs->ed_controlhead); + ohci_writel (ohci, + find_head (ohci->ed_controltail)->dma, + &ohci->regs->ed_controlhead); enables |= OHCI_CTRL_CLE; temp |= OHCI_CLF; } if (ohci->ed_bulktail) { - writel (find_head (ohci->ed_bulktail)->dma, + ohci_writel (ohci, find_head (ohci->ed_bulktail)->dma, &ohci->regs->ed_bulkhead); enables |= OHCI_CTRL_BLE; temp |= OHCI_BLF; } } - if (hcd_to_bus (&ohci->hcd)->bandwidth_isoc_reqs - || hcd_to_bus (&ohci->hcd)->bandwidth_int_reqs) + if (hcd->self.bandwidth_isoc_reqs || hcd->self.bandwidth_int_reqs) enables |= OHCI_CTRL_PLE|OHCI_CTRL_IE; if (enables) { ohci_dbg (ohci, "restarting schedules ... %08x\n", enables); ohci->hc_control |= enables; - writel (ohci->hc_control, &ohci->regs->control); + ohci_writel (ohci, ohci->hc_control, &ohci->regs->control); if (temp) - writel (status, &ohci->regs->cmdstatus); - (void) ohci_readl (&ohci->regs->control); + ohci_writel (ohci, temp, &ohci->regs->cmdstatus); + (void) ohci_readl (ohci, &ohci->regs->control); } - ohci->hcd.state = USB_STATE_RUNNING; + hcd->state = USB_STATE_RUNNING; return 0; } @@ -301,9 +280,9 @@ static void ohci_rh_resume (void *_hcd) { struct usb_hcd *hcd = _hcd; - down (&hcd->self.root_hub->serialize); + usb_lock_device (hcd->self.root_hub); (void) ohci_hub_resume (hcd); - up (&hcd->self.root_hub->serialize); + usb_unlock_device (hcd->self.root_hub); } #else @@ -325,16 +304,26 @@ ohci_hub_status_data (struct usb_hcd *hcd, char *buf) { struct ohci_hcd *ohci = hcd_to_ohci (hcd); int ports, i, changed = 0, length = 1; - int can_suspend = 1; + int can_suspend = hcd->can_wakeup; + unsigned long flags; + + spin_lock_irqsave (&ohci->lock, flags); + + /* handle autosuspended root: finish resuming before + * letting khubd or root hub timer see state changes. + */ + if ((ohci->hc_control & OHCI_CTRL_HCFS) != OHCI_USB_OPER + || !HCD_IS_RUNNING(hcd->state)) { + can_suspend = 0; + goto done; + } ports = roothub_a (ohci) & RH_A_NDP; if (ports > MAX_ROOT_PORTS) { - if (!HCD_IS_RUNNING(ohci->hcd.state)) - return -ESHUTDOWN; - ohci_err (ohci, "bogus NDP=%d, rereads as NDP=%d\n", - ports, ohci_readl (&ohci->regs->roothub.a) & RH_A_NDP); + ohci_err (ohci, "bogus NDP=%d, rereads as NDP=%d\n", ports, + ohci_readl (ohci, &ohci->regs->roothub.a) & RH_A_NDP); /* retry later; "should not happen" */ - return 0; + goto done; } /* init status */ @@ -366,14 +355,17 @@ ohci_hub_status_data (struct usb_hcd *hcd, char *buf) */ if (!(status & RH_PS_CCS)) continue; - if ((status & RH_PS_PSS) && ohci->hcd.remote_wakeup) + if ((status & RH_PS_PSS) && hcd->remote_wakeup) continue; can_suspend = 0; } +done: + spin_unlock_irqrestore (&ohci->lock, flags); #ifdef CONFIG_PM /* save power by suspending idle root hubs; * INTR_RD wakes us when there's work + * NOTE: if we can do this, we don't need a root hub timer! */ if (can_suspend && !changed @@ -381,12 +373,13 @@ ohci_hub_status_data (struct usb_hcd *hcd, char *buf) && ((OHCI_CTRL_HCFS | OHCI_SCHED_ENABLES) & ohci->hc_control) == OHCI_USB_OPER - && down_trylock (&hcd->self.root_hub->serialize) == 0 + && time_after (jiffies, ohci->next_statechange) + && usb_trylock_device (hcd->self.root_hub) ) { ohci_vdbg (ohci, "autosuspend\n"); - (void) ohci_hub_suspend (&ohci->hcd); - ohci->hcd.state = USB_STATE_RUNNING; - up (&hcd->self.root_hub->serialize); + (void) ohci_hub_suspend (hcd); + hcd->state = USB_STATE_RUNNING; + usb_unlock_device (hcd->self.root_hub); } #endif @@ -421,7 +414,7 @@ ohci_hub_descriptor ( temp |= 0x0010; else if (rh & RH_A_OCPM) /* per-port overcurrent reporting? */ temp |= 0x0008; - desc->wHubCharacteristics = cpu_to_le16 (temp); + desc->wHubCharacteristics = (__force __u16)cpu_to_hc16(ohci, temp); /* two bitmaps: ports removable, and usb 1.0 legacy PortPwrCtrlMask */ rh = roothub_b (ohci); @@ -447,12 +440,12 @@ static int ohci_start_port_reset (struct usb_hcd *hcd, unsigned port) port--; /* start port reset before HNP protocol times out */ - status = ohci_readl(&ohci->regs->roothub.portstatus [port]); + status = ohci_readl(ohci, &ohci->regs->roothub.portstatus [port]); if (!(status & RH_PS_CCS)) return -ENODEV; /* khubd will finish the reset later */ - writel(RH_PS_PRS, &ohci->regs->roothub.portstatus [port]); + ohci_writel(ohci, RH_PS_PRS, &ohci->regs->roothub.portstatus [port]); return 0; } @@ -481,15 +474,15 @@ static void start_hnp(struct ohci_hcd *ohci); /* this timer value might be vendor-specific ... */ #define PORT_RESET_HW_MSEC 10 -/* wrap-aware logic stolen from */ -#define tick_before(t1,t2) ((((s16)(t1))-((s16)(t2))) < 0) +/* wrap-aware logic morphed from */ +#define tick_before(t1,t2) ((s16)(((s16)(t1))-((s16)(t2))) < 0) /* called from some task, normally khubd */ static inline void root_port_reset (struct ohci_hcd *ohci, unsigned port) { - u32 __iomem *portstat = &ohci->regs->roothub.portstatus [port]; + __hc32 __iomem *portstat = &ohci->regs->roothub.portstatus [port]; u32 temp; - u16 now = readl(&ohci->regs->fmnumber); + u16 now = ohci_readl(ohci, &ohci->regs->fmnumber); u16 reset_done = now + PORT_RESET_MSEC; /* build a "continuous enough" reset signal, with up to @@ -499,7 +492,7 @@ static inline void root_port_reset (struct ohci_hcd *ohci, unsigned port) do { /* spin until any current reset finishes */ for (;;) { - temp = ohci_readl (portstat); + temp = ohci_readl (ohci, portstat); if (!(temp & RH_PS_PRS)) break; udelay (500); @@ -508,12 +501,12 @@ static inline void root_port_reset (struct ohci_hcd *ohci, unsigned port) if (!(temp & RH_PS_CCS)) break; if (temp & RH_PS_PRSC) - writel (RH_PS_PRSC, portstat); + ohci_writel (ohci, RH_PS_PRSC, portstat); /* start the next reset, sleep till it's probably done */ - writel (RH_PS_PRS, portstat); + ohci_writel (ohci, RH_PS_PRS, portstat); msleep(PORT_RESET_HW_MSEC); - now = readl(&ohci->regs->fmnumber); + now = ohci_readl(ohci, &ohci->regs->fmnumber); } while (tick_before(now, reset_done)); /* caller synchronizes using PRSC */ } @@ -535,7 +528,8 @@ static int ohci_hub_control ( case ClearHubFeature: switch (wValue) { case C_HUB_OVER_CURRENT: - writel (RH_HS_OCIC, &ohci->regs->roothub.status); + ohci_writel (ohci, RH_HS_OCIC, + &ohci->regs->roothub.status); case C_HUB_LOCAL_POWER: break; default: @@ -578,8 +572,9 @@ static int ohci_hub_control ( default: goto error; } - writel (temp, &ohci->regs->roothub.portstatus [wIndex]); - // ohci_readl (&ohci->regs->roothub.portstatus [wIndex]); + ohci_writel (ohci, temp, + &ohci->regs->roothub.portstatus [wIndex]); + // ohci_readl (ohci, &ohci->regs->roothub.portstatus [wIndex]); break; case GetHubDescriptor: ohci_hub_descriptor (ohci, (struct usb_hub_descriptor *) buf); @@ -617,16 +612,16 @@ static int ohci_hub_control ( switch (wValue) { case USB_PORT_FEAT_SUSPEND: #ifdef CONFIG_USB_OTG - if (ohci->hcd.self.otg_port == (wIndex + 1) - && ohci->hcd.self.b_hnp_enable) + if (hcd->self.otg_port == (wIndex + 1) + && hcd->self.b_hnp_enable) start_hnp(ohci); else #endif - writel (RH_PS_PSS, + ohci_writel (ohci, RH_PS_PSS, &ohci->regs->roothub.portstatus [wIndex]); break; case USB_PORT_FEAT_POWER: - writel (RH_PS_PPS, + ohci_writel (ohci, RH_PS_PPS, &ohci->regs->roothub.portstatus [wIndex]); break; case USB_PORT_FEAT_RESET: