ehci-hub.c
来自「linux 内核源代码」· C语言 代码 · 共 867 行 · 第 1/2 页
C
867 行
/* * Copyright (C) 2001-2004 by David Brownell * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. *//* this file is part of ehci-hcd.c *//*-------------------------------------------------------------------------*//* * EHCI Root Hub ... the nonsharable stuff * * Registers don't need cpu_to_le32, that happens transparently *//*-------------------------------------------------------------------------*/#ifdef CONFIG_USB_PERSISTstatic 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;}#else /* CONFIG_USB_PERSIST */static inline void ehci_handover_companion_ports(struct ehci_hcd *ehci){ }#endif#ifdef CONFIG_PMstatic int ehci_bus_suspend (struct usb_hcd *hcd){ struct ehci_hcd *ehci = hcd_to_ehci (hcd); int port; int mask; ehci_dbg(ehci, "suspend root hub\n"); if (time_before (jiffies, ehci->next_statechange)) msleep(5); port = HCS_N_PORTS (ehci->hcs_params); spin_lock_irq (&ehci->lock); /* stop schedules, clean any completed work */ if (HC_IS_RUNNING(hcd->state)) { ehci_quiesce (ehci); 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 * any notion of "global" or bus-wide suspend. The driver has * to manually suspend all the active unsuspended ports, and * 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_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; else t2 &= ~(PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E); if (t1 != t2) { ehci_vdbg (ehci, "port %d, %08x -> %08x\n", port + 1, t1, t2); ehci_writel(ehci, t2, reg); } } /* turn off now-idle HC */ del_timer_sync (&ehci->watchdog); ehci_halt (ehci); hcd->state = HC_STATE_SUSPENDED; /* allow remote wakeup */ mask = INTR_MASK; if (!device_may_wakeup(&hcd->self.root_hub->dev)) mask &= ~STS_PCD; ehci_writel(ehci, mask, &ehci->regs->intr_enable); ehci_readl(ehci, &ehci->regs->intr_enable); ehci->next_statechange = jiffies + msecs_to_jiffies(10); spin_unlock_irq (&ehci->lock); return 0;}/* caller has locked the root hub, and should reset/reinit on error */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)) 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 * could instead be restoring a swsusp snapshot -- so that BIOS was * the last user of the controller, not reset/pm hardware keeping * state we gave to it. */ 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. */ ehci_writel(ehci, 0, &ehci->regs->intr_enable); /* re-init operational registers */ ehci_writel(ehci, 0, &ehci->regs->segment); ehci_writel(ehci, ehci->periodic_dma, &ehci->regs->frame_list); ehci_writel(ehci, (u32) ehci->async->qh_dma, &ehci->regs->async_next); /* 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); if (test_bit(i, &ehci->bus_suspended) && (temp & PORT_SUSPEND)) { ehci->reset_done [i] = jiffies + msecs_to_jiffies (20); temp |= PORT_RESUME; } ehci_writel(ehci, temp, &ehci->regs->port_status [i]); } i = HCS_N_PORTS (ehci->hcs_params); mdelay (20); while (i--) { temp = ehci_readl(ehci, &ehci->regs->port_status [i]); if (test_bit(i, &ehci->bus_suspended) && (temp & PORT_SUSPEND)) { temp &= ~(PORT_RWC_BITS | PORT_RESUME); ehci_writel(ehci, temp, &ehci->regs->port_status [i]); ehci_vdbg (ehci, "resumed port %d\n", i + 1); } } (void) ehci_readl(ehci, &ehci->regs->command); /* maybe re-activate the schedule(s) */ temp = 0; if (ehci->async->qh_next.qh) temp |= CMD_ASE; if (ehci->periodic_sched) temp |= CMD_PSE; if (temp) { ehci->command |= temp; ehci_writel(ehci, ehci->command, &ehci->regs->command); } ehci->next_statechange = jiffies + msecs_to_jiffies(5); hcd->state = HC_STATE_RUNNING; /* Now we can safely re-enable irqs */ ehci_writel(ehci, INTR_MASK, &ehci->regs->intr_enable); spin_unlock_irq (&ehci->lock); if (!power_okay) ehci_handover_companion_ports(ehci); return 0;}#else#define ehci_bus_suspend NULL#define ehci_bus_resume NULL#endif /* CONFIG_PM *//*-------------------------------------------------------------------------*//* Display the ports dedicated to the companion controller */static ssize_t show_companion(struct class_device *class_dev, 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))); nports = HCS_N_PORTS(ehci->hcs_params); for (index = 0; index < nports; ++index) { if (test_bit(index, &ehci->companion_ports)) { n = scnprintf(ptr, count, "%d\n", index + 1); ptr += n; count -= n; } } return ptr - buf;}/* * 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 class_device *class_dev, const char *buf, size_t count){ struct ehci_hcd *ehci; int portnum, new_owner, try; u32 __iomem *status_reg; u32 port_status; 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); /* * 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 ((port_status & PORT_OWNER) == new_owner || (port_status & (PORT_OWNER | PORT_CONNECT)) == 0) try = 0; else { port_status ^= PORT_OWNER; port_status &= ~(PORT_PE | PORT_RWC_BITS); ehci_writel(ehci, port_status, status_reg); } spin_unlock_irq(&ehci->lock); if (try > 1) msleep(5); } return count;}static CLASS_DEVICE_ATTR(companion, 0644, show_companion, store_companion);static inline void create_companion_file(struct ehci_hcd *ehci){ int i; /* 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);}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);}/*-------------------------------------------------------------------------*/static int check_reset_complete ( struct ehci_hcd *ehci, int index, u32 __iomem *status_reg, int port_status) { if (!(port_status & PORT_CONNECT)) { ehci->reset_done [index] = 0; return port_status; } /* if reset finished and it's still not enabled -- handoff */ if (!(port_status & PORT_PE)) { /* with integrated TT, there's nobody to hand it to! */ if (ehci_is_TDI(ehci)) { ehci_dbg (ehci, "Failed to enable port %d on root hub TT\n", index+1); return port_status; } ehci_dbg (ehci, "port %d full speed --> companion\n", index + 1); // what happens if HCS_N_CC(params) == 0 ? port_status |= PORT_OWNER; port_status &= ~PORT_RWC_BITS; ehci_writel(ehci, port_status, status_reg); } else ehci_dbg (ehci, "port %d high speed\n", index + 1); return port_status;}/*-------------------------------------------------------------------------*//* build "status change" packet (one or two bytes) from HC registers */static intehci_hub_status_data (struct usb_hcd *hcd, char *buf){ struct ehci_hcd *ehci = hcd_to_ehci (hcd);
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?