patch-2.4.19 linux-2.4.19/drivers/usb/hcd/ehci-hub.c
Next file: linux-2.4.19/drivers/usb/hcd/ehci-mem.c
Previous file: linux-2.4.19/drivers/usb/hcd/ehci-hcd.c
Back to the patch index
Back to the overall index
- Lines: 344
- Date:
Fri Aug 2 17:39:44 2002
- Orig file:
linux-2.4.18/drivers/usb/hcd/ehci-hub.c
- Orig date:
Wed Dec 31 16:00:00 1969
diff -urN linux-2.4.18/drivers/usb/hcd/ehci-hub.c linux-2.4.19/drivers/usb/hcd/ehci-hub.c
@@ -0,0 +1,343 @@
+/*
+ * Copyright (c) 2001-2002 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
+ */
+
+/*-------------------------------------------------------------------------*/
+
+static int check_reset_complete (
+ struct ehci_hcd *ehci,
+ int index,
+ 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)) {
+ dbg ("%s port %d full speed, give to companion, 0x%x",
+ ehci->hcd.bus_name, index + 1, port_status);
+
+ // what happens if HCS_N_CC(params) == 0 ?
+ port_status |= PORT_OWNER;
+ writel (port_status, &ehci->regs->port_status [index]);
+
+ } else
+ dbg ("%s port %d high speed", ehci->hcd.bus_name, index + 1);
+
+ return port_status;
+}
+
+/*-------------------------------------------------------------------------*/
+
+
+/* build "status change" packet (one or two bytes) from HC registers */
+
+static int
+ehci_hub_status_data (struct usb_hcd *hcd, char *buf)
+{
+ struct ehci_hcd *ehci = hcd_to_ehci (hcd);
+ u32 temp, status = 0;
+ int ports, i, retval = 1;
+ unsigned long flags;
+
+ /* init status to no-changes */
+ buf [0] = 0;
+ ports = HCS_N_PORTS (ehci->hcs_params);
+ if (ports > 7) {
+ buf [1] = 0;
+ retval++;
+ }
+
+ /* no hub change reports (bit 0) for now (power, ...) */
+
+ /* port N changes (bit N)? */
+ spin_lock_irqsave (&ehci->lock, flags);
+ for (i = 0; i < ports; i++) {
+ temp = readl (&ehci->regs->port_status [i]);
+ if (temp & PORT_OWNER) {
+ /* don't report this in GetPortStatus */
+ if (temp & PORT_CSC) {
+ temp &= ~PORT_CSC;
+ writel (temp, &ehci->regs->port_status [i]);
+ }
+ continue;
+ }
+ if (!(temp & PORT_CONNECT))
+ ehci->reset_done [i] = 0;
+ if ((temp & (PORT_CSC | PORT_PEC | PORT_OCC)) != 0) {
+ if (i < 7)
+ buf [0] |= 1 << (i + 1);
+ else
+ buf [1] |= 1 << (i - 7);
+ status = STS_PCD;
+ }
+ }
+ spin_unlock_irqrestore (&ehci->lock, flags);
+ return status ? retval : 0;
+}
+
+/*-------------------------------------------------------------------------*/
+
+static void
+ehci_hub_descriptor (
+ struct ehci_hcd *ehci,
+ struct usb_hub_descriptor *desc
+) {
+ int ports = HCS_N_PORTS (ehci->hcs_params);
+ u16 temp;
+
+ desc->bDescriptorType = 0x29;
+ desc->bPwrOn2PwrGood = 0; /* FIXME: f(system power) */
+ desc->bHubContrCurrent = 0;
+
+ desc->bNbrPorts = ports;
+ temp = 1 + (ports / 8);
+ desc->bDescLength = 7 + 2 * temp;
+
+ /* two bitmaps: ports removable, and usb 1.0 legacy PortPwrCtrlMask */
+ memset (&desc->bitmap [0], 0, temp);
+ memset (&desc->bitmap [temp], 0xff, temp);
+
+ temp = 0x0008; /* per-port overcurrent reporting */
+ if (HCS_PPC (ehci->hcs_params))
+ temp |= 0x0001; /* per-port power control */
+ if (HCS_INDICATOR (ehci->hcs_params))
+ temp |= 0x0080; /* per-port indicators (LEDs) */
+ desc->wHubCharacteristics = cpu_to_le16 (temp);
+}
+
+/*-------------------------------------------------------------------------*/
+
+static int ehci_hub_control (
+ struct usb_hcd *hcd,
+ u16 typeReq,
+ u16 wValue,
+ u16 wIndex,
+ char *buf,
+ u16 wLength
+) {
+ struct ehci_hcd *ehci = hcd_to_ehci (hcd);
+ int ports = HCS_N_PORTS (ehci->hcs_params);
+ u32 temp, status;
+ unsigned long flags;
+ int retval = 0;
+
+ /*
+ * FIXME: support SetPortFeatures USB_PORT_FEAT_INDICATOR.
+ * HCS_INDICATOR may say we can change LEDs to off/amber/green.
+ * (track current state ourselves) ... blink for diagnostics,
+ * power, "this is the one", etc. EHCI spec supports this.
+ */
+
+ spin_lock_irqsave (&ehci->lock, flags);
+ switch (typeReq) {
+ case ClearHubFeature:
+ switch (wValue) {
+ case C_HUB_LOCAL_POWER:
+ case C_HUB_OVER_CURRENT:
+ /* no hub-wide feature/status flags */
+ break;
+ default:
+ goto error;
+ }
+ break;
+ case ClearPortFeature:
+ if (!wIndex || wIndex > ports)
+ goto error;
+ wIndex--;
+ temp = readl (&ehci->regs->port_status [wIndex]);
+ if (temp & PORT_OWNER)
+ break;
+
+ switch (wValue) {
+ case USB_PORT_FEAT_ENABLE:
+ writel (temp & ~PORT_PE,
+ &ehci->regs->port_status [wIndex]);
+ break;
+ case USB_PORT_FEAT_C_ENABLE:
+ writel (temp | PORT_PEC,
+ &ehci->regs->port_status [wIndex]);
+ break;
+ case USB_PORT_FEAT_SUSPEND:
+ case USB_PORT_FEAT_C_SUSPEND:
+ /* ? */
+ break;
+ case USB_PORT_FEAT_POWER:
+ if (HCS_PPC (ehci->hcs_params))
+ writel (temp & ~PORT_POWER,
+ &ehci->regs->port_status [wIndex]);
+ break;
+ case USB_PORT_FEAT_C_CONNECTION:
+ writel (temp | PORT_CSC,
+ &ehci->regs->port_status [wIndex]);
+ break;
+ case USB_PORT_FEAT_C_OVER_CURRENT:
+ writel (temp | PORT_OCC,
+ &ehci->regs->port_status [wIndex]);
+ break;
+ case USB_PORT_FEAT_C_RESET:
+ /* GetPortStatus clears reset */
+ break;
+ default:
+ goto error;
+ }
+ readl (&ehci->regs->command); /* unblock posted write */
+ break;
+ case GetHubDescriptor:
+ ehci_hub_descriptor (ehci, (struct usb_hub_descriptor *)
+ buf);
+ break;
+ case GetHubStatus:
+ /* no hub-wide feature/status flags */
+ memset (buf, 0, 4);
+ //cpu_to_le32s ((u32 *) buf);
+ break;
+ case GetPortStatus:
+ if (!wIndex || wIndex > ports)
+ goto error;
+ wIndex--;
+ status = 0;
+ temp = readl (&ehci->regs->port_status [wIndex]);
+
+ // wPortChange bits
+ if (temp & PORT_CSC)
+ status |= 1 << USB_PORT_FEAT_C_CONNECTION;
+ if (temp & PORT_PEC)
+ status |= 1 << USB_PORT_FEAT_C_ENABLE;
+ // USB_PORT_FEAT_C_SUSPEND
+ if (temp & PORT_OCC)
+ status |= 1 << USB_PORT_FEAT_C_OVER_CURRENT;
+
+ /* whoever resets must GetPortStatus to complete it!! */
+ if ((temp & PORT_RESET)
+ && jiffies > ehci->reset_done [wIndex]) {
+ status |= 1 << USB_PORT_FEAT_C_RESET;
+
+ /* force reset to complete */
+ writel (temp & ~PORT_RESET,
+ &ehci->regs->port_status [wIndex]);
+ do {
+ temp = readl (
+ &ehci->regs->port_status [wIndex]);
+ udelay (10);
+ } while (temp & PORT_RESET);
+
+ /* see what we found out */
+ temp = check_reset_complete (ehci, wIndex, temp);
+ }
+
+ // don't show wPortStatus if it's owned by a companion hc
+ if (!(temp & PORT_OWNER)) {
+ if (temp & PORT_CONNECT) {
+ status |= 1 << USB_PORT_FEAT_CONNECTION;
+ status |= 1 << USB_PORT_FEAT_HIGHSPEED;
+ }
+ if (temp & PORT_PE)
+ status |= 1 << USB_PORT_FEAT_ENABLE;
+ if (temp & PORT_SUSPEND)
+ status |= 1 << USB_PORT_FEAT_SUSPEND;
+ if (temp & PORT_OC)
+ status |= 1 << USB_PORT_FEAT_OVER_CURRENT;
+ if (temp & PORT_RESET)
+ status |= 1 << USB_PORT_FEAT_RESET;
+ if (temp & PORT_POWER)
+ status |= 1 << USB_PORT_FEAT_POWER;
+ }
+
+#ifndef EHCI_VERBOSE_DEBUG
+ if (status & ~0xffff) /* only if wPortChange is interesting */
+#endif
+ dbg_port (hcd, "GetStatus", wIndex + 1, temp);
+ // we "know" this alignment is good, caller used kmalloc()...
+ *((u32 *) buf) = cpu_to_le32 (status);
+ break;
+ case SetHubFeature:
+ switch (wValue) {
+ case C_HUB_LOCAL_POWER:
+ case C_HUB_OVER_CURRENT:
+ /* no hub-wide feature/status flags */
+ break;
+ default:
+ goto error;
+ }
+ break;
+ case SetPortFeature:
+ if (!wIndex || wIndex > ports)
+ goto error;
+ wIndex--;
+ temp = readl (&ehci->regs->port_status [wIndex]);
+ if (temp & PORT_OWNER)
+ break;
+
+ switch (wValue) {
+ case USB_PORT_FEAT_SUSPEND:
+ writel (temp | PORT_SUSPEND,
+ &ehci->regs->port_status [wIndex]);
+ break;
+ case USB_PORT_FEAT_POWER:
+ if (HCS_PPC (ehci->hcs_params))
+ writel (temp | PORT_POWER,
+ &ehci->regs->port_status [wIndex]);
+ break;
+ case USB_PORT_FEAT_RESET:
+ /* line status bits may report this as low speed */
+ if ((temp & (PORT_PE|PORT_CONNECT)) == PORT_CONNECT
+ && PORT_USB11 (temp)) {
+ dbg ("%s port %d low speed, give to companion",
+ hcd->bus_name, wIndex + 1);
+ temp |= PORT_OWNER;
+ } else {
+ vdbg ("%s port %d reset",
+ hcd->bus_name, wIndex + 1);
+ temp |= PORT_RESET;
+ temp &= ~PORT_PE;
+
+ /*
+ * caller must wait, then call GetPortStatus
+ * usb 2.0 spec says 50 ms resets on root
+ */
+ ehci->reset_done [wIndex] = jiffies
+ + ((50 /* msec */ * HZ) / 1000);
+ }
+ writel (temp, &ehci->regs->port_status [wIndex]);
+ break;
+ default:
+ goto error;
+ }
+ readl (&ehci->regs->command); /* unblock posted writes */
+ break;
+
+ default:
+error:
+ /* "stall" on error */
+ retval = -EPIPE;
+ }
+ spin_unlock_irqrestore (&ehci->lock, flags);
+ return retval;
+}
FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)