From: Greg KH To: torvalds@transmeta.com Cc: linux-usb-devel@lists.sourceforge.net Subject: [PATCH 2 of 6] USB ohci-hcd driver added Hi, Here's a patch against 2.5.3-pre2 that adds the ohci-hcd driver to the kernel. This is based on the current ohci driver, but converted to use the hcd common code that is now in the usb core. This patch was done by David Brownell. thanks, greg k-h diff -Nru a/drivers/usb/Makefile b/drivers/usb/Makefile --- a/drivers/usb/Makefile Mon Jan 21 10:48:38 2002 +++ b/drivers/usb/Makefile Mon Jan 21 10:48:38 2002 @@ -46,6 +46,10 @@ obj-y += hcd/ehci-hcd.o endif +ifeq ($(CONFIG_USB_OHCI_HCD),y) + obj-y += hcd/ohci-hcd.o +endif + obj-$(CONFIG_USB_UHCI) += usb-uhci.o obj-$(CONFIG_USB_UHCI_ALT) += uhci.o obj-$(CONFIG_USB_OHCI) += usb-ohci.o @@ -85,6 +89,7 @@ mod-subdirs := serial hcd subdir-$(CONFIG_USB_EHCI_HCD) += hcd +subdir-$(CONFIG_USB_OHCI_HCD) += hcd subdir-$(CONFIG_USB_SERIAL) += serial subdir-$(CONFIG_USB_STORAGE) += storage diff -Nru a/drivers/usb/hcd/Makefile b/drivers/usb/hcd/Makefile --- a/drivers/usb/hcd/Makefile Mon Jan 21 10:48:39 2002 +++ b/drivers/usb/hcd/Makefile Mon Jan 21 10:48:39 2002 @@ -6,7 +6,7 @@ O_TARGET := obj-$(CONFIG_USB_EHCI_HCD) += ehci-hcd.o -# obj-$(CONFIG_USB_OHCI_HCD) += ohci-hcd.o +obj-$(CONFIG_USB_OHCI_HCD) += ohci-hcd.o # obj-$(CONFIG_USB_UHCI_HCD) += uhci-hcd.o # Extract lists of the multi-part drivers. diff -Nru a/drivers/usb/hcd/ohci-dbg.c b/drivers/usb/hcd/ohci-dbg.c --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/drivers/usb/hcd/ohci-dbg.c Mon Jan 21 10:48:39 2002 @@ -0,0 +1,236 @@ +/* + * OHCI HCD (Host Controller Driver) for USB. + * + * (C) Copyright 1999 Roman Weissgaerber + * (C) Copyright 2000-2001 David Brownell + * + * This file is licenced under GPL + * $Id: ohci-dbg.c,v 1.2 2002/01/19 00:15:45 dbrownell Exp $ + */ + +/*-------------------------------------------------------------------------*/ + +#ifdef DEBUG + +#define pipestring(pipe) ({ char *temp; \ + switch (usb_pipetype (pipe)) { \ + case PIPE_CONTROL: temp = "CTRL"; break; \ + case PIPE_BULK: temp = "BULK"; break; \ + case PIPE_INTERRUPT: temp = "INTR"; break; \ + default: temp = "ISOC"; break; \ + }; temp;}) + +/* debug| print the main components of an URB + * small: 0) header + data packets 1) just header + */ +static void urb_print (struct urb * urb, char * str, int small) +{ + unsigned int pipe= urb->pipe; + + if (!urb->dev || !urb->dev->bus) { + dbg("%s URB: no dev", str); + return; + } + +#ifndef OHCI_VERBOSE_DEBUG + if (urb->status != 0) +#endif + dbg("%s:[%4x] dev:%d,ep=%d-%c,%s,flags:%4x,len:%d/%d,stat:%d", + str, + usb_get_current_frame_number (urb->dev), + usb_pipedevice (pipe), + usb_pipeendpoint (pipe), + usb_pipeout (pipe)? 'O': 'I', + pipestring (pipe), + urb->transfer_flags, + urb->actual_length, + urb->transfer_buffer_length, + urb->status); + +#ifdef OHCI_VERBOSE_DEBUG + if (!small) { + int i, len; + + if (usb_pipecontrol (pipe)) { + printk (KERN_DEBUG __FILE__ ": cmd(8):"); + for (i = 0; i < 8 ; i++) + printk (" %02x", ((__u8 *) urb->setup_packet) [i]); + printk ("\n"); + } + if (urb->transfer_buffer_length > 0 && urb->transfer_buffer) { + printk (KERN_DEBUG __FILE__ ": data(%d/%d):", + urb->actual_length, + urb->transfer_buffer_length); + len = usb_pipeout (pipe)? + urb->transfer_buffer_length: urb->actual_length; + for (i = 0; i < 16 && i < len; i++) + printk (" %02x", ((__u8 *) urb->transfer_buffer) [i]); + printk ("%s stat:%d\n", i < len? "...": "", urb->status); + } + } +#endif +} + +static inline struct ed * +dma_to_ed (struct ohci_hcd *hc, dma_addr_t ed_dma); + +/* print non-empty branches of the periodic ed tree */ +void ep_print_int_eds (struct ohci_hcd *ohci, char * str) +{ + int i, j; + __u32 * ed_p; + for (i= 0; i < 32; i++) { + j = 5; + ed_p = &(ohci->hcca->int_table [i]); + if (*ed_p == 0) + continue; + printk (KERN_DEBUG __FILE__ ": %s branch int %2d(%2x):", + str, i, i); + while (*ed_p != 0 && j--) { + struct ed *ed = dma_to_ed (ohci, le32_to_cpup(ed_p)); + printk (" ed: %4x;", ed->hwINFO); + ed_p = &ed->hwNextED; + } + printk ("\n"); + } +} + + +static void ohci_dump_intr_mask (char *label, __u32 mask) +{ + dbg ("%s: 0x%08x%s%s%s%s%s%s%s%s%s", + label, + mask, + (mask & OHCI_INTR_MIE) ? " MIE" : "", + (mask & OHCI_INTR_OC) ? " OC" : "", + (mask & OHCI_INTR_RHSC) ? " RHSC" : "", + (mask & OHCI_INTR_FNO) ? " FNO" : "", + (mask & OHCI_INTR_UE) ? " UE" : "", + (mask & OHCI_INTR_RD) ? " RD" : "", + (mask & OHCI_INTR_SF) ? " SF" : "", + (mask & OHCI_INTR_WDH) ? " WDH" : "", + (mask & OHCI_INTR_SO) ? " SO" : "" + ); +} + +static void maybe_print_eds (char *label, __u32 value) +{ + if (value) + dbg ("%s %08x", label, value); +} + +static char *hcfs2string (int state) +{ + switch (state) { + case OHCI_USB_RESET: return "reset"; + case OHCI_USB_RESUME: return "resume"; + case OHCI_USB_OPER: return "operational"; + case OHCI_USB_SUSPEND: return "suspend"; + } + return "?"; +} + +// dump control and status registers +static void ohci_dump_status (struct ohci_hcd *controller) +{ + struct ohci_regs *regs = controller->regs; + __u32 temp; + + temp = readl (®s->revision) & 0xff; + if (temp != 0x10) + dbg ("spec %d.%d", (temp >> 4), (temp & 0x0f)); + + temp = readl (®s->control); + dbg ("control: 0x%08x%s%s%s HCFS=%s%s%s%s%s CBSR=%d", temp, + (temp & OHCI_CTRL_RWE) ? " RWE" : "", + (temp & OHCI_CTRL_RWC) ? " RWC" : "", + (temp & OHCI_CTRL_IR) ? " IR" : "", + hcfs2string (temp & OHCI_CTRL_HCFS), + (temp & OHCI_CTRL_BLE) ? " BLE" : "", + (temp & OHCI_CTRL_CLE) ? " CLE" : "", + (temp & OHCI_CTRL_IE) ? " IE" : "", + (temp & OHCI_CTRL_PLE) ? " PLE" : "", + temp & OHCI_CTRL_CBSR + ); + + temp = readl (®s->cmdstatus); + dbg ("cmdstatus: 0x%08x SOC=%d%s%s%s%s", temp, + (temp & OHCI_SOC) >> 16, + (temp & OHCI_OCR) ? " OCR" : "", + (temp & OHCI_BLF) ? " BLF" : "", + (temp & OHCI_CLF) ? " CLF" : "", + (temp & OHCI_HCR) ? " HCR" : "" + ); + + ohci_dump_intr_mask ("intrstatus", readl (®s->intrstatus)); + ohci_dump_intr_mask ("intrenable", readl (®s->intrenable)); + // intrdisable always same as intrenable + // ohci_dump_intr_mask ("intrdisable", readl (®s->intrdisable)); + + maybe_print_eds ("ed_periodcurrent", readl (®s->ed_periodcurrent)); + + maybe_print_eds ("ed_controlhead", readl (®s->ed_controlhead)); + maybe_print_eds ("ed_controlcurrent", readl (®s->ed_controlcurrent)); + + maybe_print_eds ("ed_bulkhead", readl (®s->ed_bulkhead)); + maybe_print_eds ("ed_bulkcurrent", readl (®s->ed_bulkcurrent)); + + maybe_print_eds ("donehead", readl (®s->donehead)); +} + +static void ohci_dump_roothub (struct ohci_hcd *controller, int verbose) +{ + __u32 temp, ndp, i; + + temp = roothub_a (controller); + ndp = (temp & RH_A_NDP); + + if (verbose) { + dbg ("roothub.a: %08x POTPGT=%d%s%s%s%s%s NDP=%d", temp, + ((temp & RH_A_POTPGT) >> 24) & 0xff, + (temp & RH_A_NOCP) ? " NOCP" : "", + (temp & RH_A_OCPM) ? " OCPM" : "", + (temp & RH_A_DT) ? " DT" : "", + (temp & RH_A_NPS) ? " NPS" : "", + (temp & RH_A_PSM) ? " PSM" : "", + ndp + ); + temp = roothub_b (controller); + dbg ("roothub.b: %08x PPCM=%04x DR=%04x", + temp, + (temp & RH_B_PPCM) >> 16, + (temp & RH_B_DR) + ); + temp = roothub_status (controller); + dbg ("roothub.status: %08x%s%s%s%s%s%s", + temp, + (temp & RH_HS_CRWE) ? " CRWE" : "", + (temp & RH_HS_OCIC) ? " OCIC" : "", + (temp & RH_HS_LPSC) ? " LPSC" : "", + (temp & RH_HS_DRWE) ? " DRWE" : "", + (temp & RH_HS_OCI) ? " OCI" : "", + (temp & RH_HS_LPS) ? " LPS" : "" + ); + } + + for (i = 0; i < ndp; i++) { + temp = roothub_portstatus (controller, i); + dbg_port (controller, "", i, temp); + } +} + +static void ohci_dump (struct ohci_hcd *controller, int verbose) +{ + dbg ("OHCI controller %s state", controller->hcd.bus_name); + + // dumps some of the state we know about + ohci_dump_status (controller); + if (verbose) + ep_print_int_eds (controller, "hcca"); + dbg ("hcca frame #%04x", controller->hcca->frame_no); + ohci_dump_roothub (controller, 1); +} + + +#endif + diff -Nru a/drivers/usb/hcd/ohci-hcd.c b/drivers/usb/hcd/ohci-hcd.c --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/drivers/usb/hcd/ohci-hcd.c Mon Jan 21 10:48:39 2002 @@ -0,0 +1,973 @@ +/* + * OHCI HCD (Host Controller Driver) for USB. + * + * (C) Copyright 1999 Roman Weissgaerber + * (C) Copyright 2000-2001 David Brownell + * + * [ Initialisation is based on Linus' ] + * [ uhci code and gregs ohci fragments ] + * [ (C) Copyright 1999 Linus Torvalds ] + * [ (C) Copyright 1999 Gregory P. Smith] + * + * + * History: + * + * 2002/01/18 package as a patch for 2.5.3; this should match the + * 2.4.17 kernel modulo some bugs being fixed. + * + * 2001/10/18 merge pmac cleanup (Benjamin Herrenschmidt) and bugfixes + * from post-2.4.5 patches. + * 2001/09/20 USB_ZERO_PACKET support; hcca_dma portability, OPTi warning + * 2001/09/07 match PCI PM changes, errnos from Linus' tree + * 2001/05/05 fork 2.4.5 version into "hcd" framework, cleanup, simplify; + * pbook pci quirks gone (please fix pbook pci sw!) (db) + * + * 2001/04/08 Identify version on module load (gb) + * 2001/03/24 td/ed hashing to remove bus_to_virt (Steve Longerbeam); + pci_map_single (db) + * 2001/03/21 td and dev/ed allocation uses new pci_pool API (db) + * 2001/03/07 hcca allocation uses pci_alloc_consistent (Steve Longerbeam) + * + * 2000/09/26 fixed races in removing the private portion of the urb + * 2000/09/07 disable bulk and control lists when unlinking the last + * endpoint descriptor in order to avoid unrecoverable errors on + * the Lucent chips. (rwc@sgi) + * 2000/08/29 use bandwidth claiming hooks (thanks Randy!), fix some + * urb unlink probs, indentation fixes + * 2000/08/11 various oops fixes mostly affecting iso and cleanup from + * device unplugs. + * 2000/06/28 use PCI hotplug framework, for better power management + * and for Cardbus support (David Brownell) + * 2000/earlier: fixes for NEC/Lucent chips; suspend/resume handling + * when the controller loses power; handle UE; cleanup; ... + * + * v5.2 1999/12/07 URB 3rd preview, + * v5.1 1999/11/30 URB 2nd preview, cpia, (usb-scsi) + * v5.0 1999/11/22 URB Technical preview, Paul Mackerras powerbook susp/resume + * i386: HUB, Keyboard, Mouse, Printer + * + * v4.3 1999/10/27 multiple HCs, bulk_request + * v4.2 1999/09/05 ISO API alpha, new dev alloc, neg Error-codes + * v4.1 1999/08/27 Randy Dunlap's - ISO API first impl. + * v4.0 1999/08/18 + * v3.0 1999/06/25 + * v2.1 1999/05/09 code clean up + * v2.0 1999/05/04 + * v1.0 1999/04/27 initial release + * + * This file is licenced under GPL + * $Id: ohci-hcd.c,v 1.7 2002/01/19 00:20:56 dbrownell Exp $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* for in_interrupt () */ + +#ifndef CONFIG_USB_DEBUG + #define CONFIG_USB_DEBUG /* this is still experimental! */ +#endif + +#ifdef CONFIG_USB_DEBUG + #define DEBUG +#else + #undef DEBUG +#endif + +#include +#include "../hcd.h" + +#include +#include +#include +#include + +#ifdef CONFIG_PMAC_PBOOK +#include +#include +#include +#ifndef CONFIG_PM +# define CONFIG_PM +#endif +#endif + +/* + * TO DO: + * + * - "disabled" should be the hcd state + * - bandwidth alloc to generic code + * - lots more testing!! + */ + +#define DRIVER_VERSION "$Revision: 1.7 $" +#define DRIVER_AUTHOR "Roman Weissgaerber , David Brownell" +#define DRIVER_DESC "USB 1.1 'Open' Host Controller (OHCI) Driver" + +/*-------------------------------------------------------------------------*/ + +#define OHCI_USE_NPS // force NoPowerSwitching mode +// #define OHCI_VERBOSE_DEBUG /* not always helpful */ + +/* For initializing controller (mask in an HCFS mode too) */ +#define OHCI_CONTROL_INIT \ + (OHCI_CTRL_CBSR & 0x3) | OHCI_CTRL_IE | OHCI_CTRL_PLE + +#define OHCI_UNLINK_TIMEOUT (HZ / 10) + +/*-------------------------------------------------------------------------*/ + +#include "ohci.h" + +#include "ohci-hub.c" +#include "ohci-dbg.c" +#include "ohci-mem.c" +#include "ohci-q.c" + +/*-------------------------------------------------------------------------*/ + +/* + * queue up an urb for anything except the root hub + */ +static int ohci_urb_enqueue ( + struct usb_hcd *hcd, + struct urb *urb, + int mem_flags +) { + struct ohci_hcd *ohci = hcd_to_ohci (hcd); + struct ed *ed; + urb_priv_t *urb_priv; + unsigned int pipe = urb->pipe; + int i, size = 0; + unsigned long flags; + int bustime = 0; + +#ifdef OHCI_VERBOSE_DEBUG + urb_print (urb, "SUB", usb_pipein (pipe)); +#endif + + /* every endpoint has a ed, locate and fill it */ + if (! (ed = ep_add_ed (urb->dev, pipe, urb->interval, 1, mem_flags))) { + usb_dec_dev_use (urb->dev); + return -ENOMEM; + } + + /* for the private part of the URB we need the number of TDs (size) */ + switch (usb_pipetype (pipe)) { + case PIPE_CONTROL: + /* 1 TD for setup, 1 for ACK, plus ... */ + size = 2; + /* FALLTHROUGH */ + case PIPE_BULK: + /* one TD for every 4096 Bytes (can be upto 8K) */ + size += urb->transfer_buffer_length / 4096; + /* ... and for any remaining bytes ... */ + if ((urb->transfer_buffer_length % 4096) != 0) + size++; + /* ... and maybe a zero length packet to wrap it up */ + if (size == 0) + size++; + else if ((urb->transfer_flags & USB_ZERO_PACKET) != 0 + && (urb->transfer_buffer_length + % usb_maxpacket (urb->dev, pipe, + usb_pipeout (pipe))) != 0) + size++; + break; + case PIPE_ISOCHRONOUS: /* number of packets from URB */ + size = urb->number_of_packets; + if (size <= 0) { + usb_dec_dev_use (urb->dev); + return -EINVAL; + } + for (i = 0; i < urb->number_of_packets; i++) { + urb->iso_frame_desc [i].actual_length = 0; + urb->iso_frame_desc [i].status = -EXDEV; + } + break; + case PIPE_INTERRUPT: /* one TD */ + size = 1; + break; + } + + /* allocate the private part of the URB */ + urb_priv = kmalloc (sizeof (urb_priv_t) + size * sizeof (struct td *), + mem_flags); + if (!urb_priv) { + usb_dec_dev_use (urb->dev); + return -ENOMEM; + } + memset (urb_priv, 0, sizeof (urb_priv_t) + size * sizeof (struct td *)); + + /* fill the private part of the URB */ + urb_priv->length = size; + urb_priv->ed = ed; + + /* allocate the TDs (updating hash chains) */ + spin_lock_irqsave (&ohci->lock, flags); + for (i = 0; i < size; i++) { + urb_priv->td [i] = td_alloc (ohci, SLAB_ATOMIC); + if (!urb_priv->td [i]) { + urb_priv->length = i; + urb_free_priv (ohci, urb_priv); + spin_unlock_irqrestore (&ohci->lock, flags); + usb_dec_dev_use (urb->dev); + return -ENOMEM; + } + } + +// FIXME: much of this switch should be generic, move to hcd code ... + + /* allocate and claim bandwidth if needed; ISO + * needs start frame index if it was't provided. + */ + switch (usb_pipetype (pipe)) { + case PIPE_ISOCHRONOUS: + if (urb->transfer_flags & USB_ISO_ASAP) { + urb->start_frame = ( (ed->state == ED_OPER) + ? (ed->last_iso + 1) + : (le16_to_cpu (ohci->hcca->frame_no) + + 10)) & 0xffff; + } + /* FALLTHROUGH */ + case PIPE_INTERRUPT: + if (urb->bandwidth == 0) { + bustime = usb_check_bandwidth (urb->dev, urb); + } + if (bustime < 0) { + urb_free_priv (ohci, urb_priv); + spin_unlock_irqrestore (&ohci->lock, flags); + usb_dec_dev_use (urb->dev); + return bustime; + } + usb_claim_bandwidth (urb->dev, urb, + bustime, usb_pipeisoc (urb->pipe)); + } + + urb->hcpriv = urb_priv; + + /* link the ed into a chain if is not already */ + if (ed->state != ED_OPER) + ep_link (ohci, ed); + + /* fill the TDs and link it to the ed */ + td_submit_urb (urb); + + spin_unlock_irqrestore (&ohci->lock, flags); + + return 0; +} + +/* + * decouple the URB from the HC queues (TDs, urb_priv); it's + * already marked for deletion. reporting is always done + * asynchronously, and we might be dealing with an urb that's + * almost completed anyway... + */ +static int ohci_urb_dequeue (struct usb_hcd *hcd, struct urb *urb) +{ + struct ohci_hcd *ohci = hcd_to_ohci (hcd); + unsigned long flags; + +#ifdef DEBUG + urb_print (urb, "UNLINK", 1); +#endif + + if (!ohci->disabled) { + urb_priv_t *urb_priv; + + /* flag the urb's data for deletion in some upcoming + * SF interrupt's delete list processing + */ + spin_lock_irqsave (&ohci->lock, flags); + urb_priv = urb->hcpriv; + + if (!urb_priv || (urb_priv->state == URB_DEL)) { + spin_unlock_irqrestore (&ohci->lock, flags); + return 0; + } + + urb_priv->state = URB_DEL; + ed_unlink (urb->dev, urb_priv->ed); + spin_unlock_irqrestore (&ohci->lock, flags); + } else { + /* + * with HC dead, we won't respect hc queue pointers + * any more ... just clean up every urb's memory. + */ + finish_urb (ohci, urb); + } + return 0; +} + +/*-------------------------------------------------------------------------*/ + +static void +ohci_free_config (struct usb_hcd *hcd, struct usb_device *udev) +{ + struct ohci_hcd *ohci = hcd_to_ohci (hcd); + struct hcd_dev *dev = (struct hcd_dev *) udev->hcpriv; + int i; + unsigned long flags; + + /* free any eds, and dummy tds, still hanging around */ + spin_lock_irqsave (&ohci->lock, flags); + for (i = 0; i < 32; i++) { + struct ed *ed = dev->ep [i]; + struct td *tdTailP; + + if (!ed) + continue; + + ed->state &= ~ED_URB_DEL; + if (ohci->disabled && ed->state == ED_OPER) + ed->state = ED_UNLINK; + switch (ed->state) { + case ED_NEW: + break; + case ED_UNLINK: + tdTailP = dma_to_td (ohci, + le32_to_cpup (&ed->hwTailP) & 0xfffffff0); + td_free (ohci, tdTailP); /* free dummy td */ + hash_free_ed (ohci, ed); + break; + + case ED_OPER: + default: + err ("illegal ED %d state in free_config, %d", + i, ed->state); +#ifdef DEBUG + BUG (); +#endif + } + ed_free (ohci, ed); + } + spin_unlock_irqrestore (&ohci->lock, flags); +} + +static int ohci_get_frame (struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci (hcd); + + dbg ("%s: ohci_get_frame", hcd->bus_name); + return le16_to_cpu (ohci->hcca->frame_no); +} + +/*-------------------------------------------------------------------------* + * HC functions + *-------------------------------------------------------------------------*/ + +/* reset the HC and BUS */ + +static int hc_reset (struct ohci_hcd *ohci) +{ + int timeout = 30; + int smm_timeout = 50; /* 0,5 sec */ + + if (readl (&ohci->regs->control) & OHCI_CTRL_IR) { /* SMM owns the HC */ + writel (OHCI_INTR_OC, &ohci->regs->intrenable); + writel (OHCI_OCR, &ohci->regs->cmdstatus); + dbg ("USB HC TakeOver from SMM"); + while (readl (&ohci->regs->control) & OHCI_CTRL_IR) { + wait_ms (10); + if (--smm_timeout == 0) { + err ("USB HC TakeOver failed!"); + return -1; + } + } + } + + /* Disable HC interrupts */ + writel (OHCI_INTR_MIE, &ohci->regs->intrdisable); + + dbg ("USB HC reset_hc %s: ctrl = 0x%x ;", + ohci->hcd.bus_name, + readl (&ohci->regs->control)); + + /* Reset USB (needed by some controllers) */ + writel (0, &ohci->regs->control); + + /* HC Reset requires max 10 ms delay */ + writel (OHCI_HCR, &ohci->regs->cmdstatus); + while ((readl (&ohci->regs->cmdstatus) & OHCI_HCR) != 0) { + if (--timeout == 0) { + err ("USB HC reset timed out!"); + return -1; + } + udelay (1); + } + return 0; +} + +/*-------------------------------------------------------------------------*/ + +/* Start an OHCI controller, set the BUS operational + * enable interrupts + * connect the virtual root hub + */ +static int hc_start (struct ohci_hcd *ohci) +{ + __u32 mask; + unsigned int fminterval; + struct usb_device *udev; + + spin_lock_init (&ohci->lock); + ohci->disabled = 1; + ohci->sleeping = 0; + + /* Tell the controller where the control and bulk lists are + * The lists are empty now. */ + + writel (0, &ohci->regs->ed_controlhead); + writel (0, &ohci->regs->ed_bulkhead); + + /* a reset clears this */ + writel ((u32) ohci->hcca_dma, &ohci->regs->hcca); + + fminterval = 0x2edf; + writel ((fminterval * 9) / 10, &ohci->regs->periodicstart); + fminterval |= ((((fminterval - 210) * 6) / 7) << 16); + writel (fminterval, &ohci->regs->fminterval); + writel (0x628, &ohci->regs->lsthresh); + + /* start controller operations */ + ohci->hc_control = OHCI_CONTROL_INIT | OHCI_USB_OPER; + ohci->disabled = 0; + writel (ohci->hc_control, &ohci->regs->control); + + /* Choose the interrupts we care about now, others later on demand */ + mask = OHCI_INTR_MIE | OHCI_INTR_UE | OHCI_INTR_WDH | OHCI_INTR_SO; + writel (mask, &ohci->regs->intrstatus); + writel (mask, &ohci->regs->intrenable); + +#ifdef OHCI_USE_NPS + /* required for AMD-756 and some Mac platforms */ + writel ((roothub_a (ohci) | RH_A_NPS) & ~RH_A_PSM, + &ohci->regs->roothub.a); + writel (RH_HS_LPSC, &ohci->regs->roothub.status); +#endif /* OHCI_USE_NPS */ + + // POTPGT delay is bits 24-31, in 2 ms units. + mdelay ((roothub_a (ohci) >> 23) & 0x1fe); + + /* connect the virtual root hub */ + ohci->hcd.bus->root_hub = udev = usb_alloc_dev (NULL, ohci->hcd.bus); + ohci->hcd.state = USB_STATE_READY; + if (!udev) { + ohci->disabled = 1; +// FIXME cleanup + return -ENOMEM; + } + + usb_connect (udev); + udev->speed = USB_SPEED_FULL; + if (usb_new_device (udev) != 0) { + usb_free_dev (udev); + ohci->disabled = 1; +// FIXME cleanup + return -ENODEV; + } + + return 0; +} + +/*-------------------------------------------------------------------------*/ + +/* an interrupt happens */ + +static void ohci_irq (struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci (hcd); + struct ohci_regs *regs = ohci->regs; + int ints; + + if ((ohci->hcca->done_head != 0) + && ! (le32_to_cpup (&ohci->hcca->done_head) & 0x01)) { + ints = OHCI_INTR_WDH; + } else if ((ints = (readl (®s->intrstatus) + & readl (®s->intrenable))) == 0) { + return; + } + + // dbg ("Interrupt: %x frame: %x", ints, le16_to_cpu (ohci->hcca->frame_no)); + + if (ints & OHCI_INTR_UE) { + ohci->disabled++; + err ("OHCI Unrecoverable Error, %s disabled", hcd->bus_name); + // e.g. due to PCI Master/Target Abort + +#ifdef DEBUG + ohci_dump (ohci, 1); +#endif + hc_reset (ohci); + } + + if (ints & OHCI_INTR_WDH) { + writel (OHCI_INTR_WDH, ®s->intrdisable); + dl_done_list (ohci, dl_reverse_done_list (ohci)); + writel (OHCI_INTR_WDH, ®s->intrenable); + } + + if (ints & OHCI_INTR_SO) { + dbg ("USB Schedule overrun"); + writel (OHCI_INTR_SO, ®s->intrenable); + } + + // FIXME: this assumes SOF (1/ms) interrupts don't get lost... + if (ints & OHCI_INTR_SF) { + unsigned int frame = le16_to_cpu (ohci->hcca->frame_no) & 1; + writel (OHCI_INTR_SF, ®s->intrdisable); + if (ohci->ed_rm_list [!frame] != NULL) { + dl_del_list (ohci, !frame); + } + if (ohci->ed_rm_list [frame] != NULL) + writel (OHCI_INTR_SF, ®s->intrenable); + } + + writel (ints, ®s->intrstatus); + writel (OHCI_INTR_MIE, ®s->intrenable); +} + +/*-------------------------------------------------------------------------*/ + +static void ohci_stop (struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci (hcd); + + dbg ("%s: stop %s controller%s", + hcd->bus_name, + hcfs2string (ohci->hc_control & OHCI_CTRL_HCFS), + ohci->disabled ? " (disabled)" : "" + ); +#ifdef DEBUG + ohci_dump (ohci, 1); +#endif + + if (!ohci->disabled) + hc_reset (ohci); + + ohci_mem_cleanup (ohci); + +#ifdef CONFIG_PCI + pci_free_consistent (ohci->hcd.pdev, sizeof *ohci->hcca, + ohci->hcca, ohci->hcca_dma); +#endif +} + +/*-------------------------------------------------------------------------*/ + +static int __devinit +ohci_start (struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci (hcd); + int ret; + +#ifdef CONFIG_PCI + if (hcd->pdev) { + ohci->hcca = pci_alloc_consistent (hcd->pdev, + sizeof *ohci->hcca, &ohci->hcca_dma); + if (!ohci->hcca) + return -ENOMEM; + + /* AMD 756, for most chips (early revs), corrupts register + * values on read ... so enable the vendor workaround. + */ + if (hcd->pdev->vendor == 0x1022 + && hcd->pdev->device == 0x740c) { + ohci->flags = OHCI_QUIRK_AMD756; + info ("%s: AMD756 erratum 4 workaround", + hcd->bus_name); + } + + /* Apple's OHCI driver has a lot of bizarre workarounds + * for this chip. Evidently control and bulk lists + * can get confused. (B&W G3 models, and ...) + */ + else if (hcd->pdev->vendor == 0x1045 + && hcd->pdev->device == 0xc861) { + info ("%s: WARNING: OPTi workarounds unavailable", + hcd->bus_name); + } + } +#else +# error "where's hcca coming from?" +#endif /* CONFIG_PCI */ + + memset (ohci->hcca, 0, sizeof (struct ohci_hcca)); + if ((ret = ohci_mem_init (ohci)) < 0) { + ohci_stop (hcd); + return ret; + } + ohci->regs = hcd->regs; + + if (hc_reset (ohci) < 0) { + ohci_stop (hcd); + return -ENODEV; + } + + if (hc_start (ohci) < 0) { + err ("can't start %s", ohci->hcd.bus_name); + ohci_stop (hcd); + return -EBUSY; + } + +#ifdef DEBUG + ohci_dump (ohci, 1); +#endif + return 0; +} + +/*-------------------------------------------------------------------------*/ + +#ifdef CONFIG_PM + +static int ohci_suspend (struct usb_hcd *hcd, u32 state) +{ + struct ohci_hcd *ohci = hcd_to_ohci (hcd); + unsigned long flags; + u16 cmd; + + if ((ohci->hc_control & OHCI_CTRL_HCFS) != OHCI_USB_OPER) { + dbg ("can't suspend %s (state is %s)", hcd->bus_name, + hcfs2string (ohci->hc_control & OHCI_CTRL_HCFS)); + return -EIO; + } + + /* act as if usb suspend can always be used */ + dbg ("%s: suspend to %d", hcd->bus_name, state); + ohci->sleeping = 1; + + /* First stop processing */ + spin_lock_irqsave (&ohci->lock, flags); + ohci->hc_control &= + ~(OHCI_CTRL_PLE|OHCI_CTRL_CLE|OHCI_CTRL_BLE|OHCI_CTRL_IE); + writel (ohci->hc_control, &ohci->regs->control); + writel (OHCI_INTR_SF, &ohci->regs->intrstatus); + (void) readl (&ohci->regs->intrstatus); + spin_unlock_irqrestore (&ohci->lock, flags); + + /* Wait a frame or two */ + mdelay (1); + if (!readl (&ohci->regs->intrstatus) & OHCI_INTR_SF) + mdelay (1); + + #ifdef CONFIG_PMAC_PBOOK + if (_machine == _MACH_Pmac) + disable_irq (ohci->irq); + /* else, 2.4 assumes shared irqs -- don't disable */ + #endif + + /* Enable remote wakeup */ + writel (readl (&ohci->regs->intrenable) | OHCI_INTR_RD, + &ohci->regs->intrenable); + + /* Suspend chip and let things settle down a bit */ + ohci->hc_control = OHCI_USB_SUSPEND; + writel (ohci->hc_control, &ohci->regs->control); + (void) readl (&ohci->regs->control); + mdelay (500); /* No schedule here ! */ + + switch (readl (&ohci->regs->control) & OHCI_CTRL_HCFS) { + case OHCI_USB_RESET: + dbg ("%s suspend->reset ?", hcd->bus_name); + break; + case OHCI_USB_RESUME: + dbg ("%s suspend->resume ?", hcd->bus_name); + break; + case OHCI_USB_OPER: + dbg ("%s suspend->operational ?", hcd->bus_name); + break; + case OHCI_USB_SUSPEND: + dbg ("%s suspended", hcd->bus_name); + break; + } + + /* In some rare situations, Apple's OHCI have happily trashed + * memory during sleep. We disable its bus master bit during + * suspend + */ + pci_read_config_word (hcd->pdev, PCI_COMMAND, &cmd); + cmd &= ~PCI_COMMAND_MASTER; + pci_write_config_word (hcd->pdev, PCI_COMMAND, cmd); +#ifdef CONFIG_PMAC_PBOOK + { + struct device_node *of_node; + + /* Disable USB PAD & cell clock */ + of_node = pci_device_to_OF_node (hcd->pdev); + if (of_node) + pmac_call_feature(PMAC_FTR_USB_ENABLE, of_node, 0, 0); + } +#endif + return 0; +} + + +// FIXME: this restart logic should be generic, +// and handle full hcd state cleanup + +/* controller died; cleanup debris, then restart */ +/* must not be called from interrupt context */ + +static int hc_restart (struct ohci_hcd *ohci) +{ + int temp; + int i; + + ohci->disabled = 1; + ohci->sleeping = 0; + if (ohci->hcd.bus->root_hub) + usb_disconnect (&ohci->hcd.bus->root_hub); + + /* empty the interrupt branches */ + for (i = 0; i < NUM_INTS; i++) ohci->ohci_int_load [i] = 0; + for (i = 0; i < NUM_INTS; i++) ohci->hcca->int_table [i] = 0; + + /* no EDs to remove */ + ohci->ed_rm_list [0] = NULL; + ohci->ed_rm_list [1] = NULL; + + /* empty control and bulk lists */ + ohci->ed_isotail = NULL; + ohci->ed_controltail = NULL; + ohci->ed_bulktail = NULL; + + if ((temp = hc_reset (ohci)) < 0 || (temp = hc_start (ohci)) < 0) { + err ("can't restart %s, %d", ohci->hcd.bus_name, temp); + return temp; + } else + dbg ("restart %s completed", ohci->hcd.bus_name); + return 0; +} + +static int ohci_resume (struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci (hcd); + int temp; + int retval = 0; + unsigned long flags; + +#ifdef CONFIG_PMAC_PBOOK + { + struct device_node *of_node; + + /* Re-enable USB PAD & cell clock */ + of_node = pci_device_to_OF_node (hcd->pdev); + if (of_node) + pmac_call_feature (PMAC_FTR_USB_ENABLE, of_node, 0, 1); + } +#endif + /* did we suspend, or were we powered off? */ + ohci->hc_control = readl (&ohci->regs->control); + temp = ohci->hc_control & OHCI_CTRL_HCFS; + +#ifdef DEBUG + /* the registers may look crazy here */ + ohci_dump_status (ohci); +#endif + + /* Re-enable bus mastering */ + pci_set_master (ohci->hcd.pdev); + + switch (temp) { + + case OHCI_USB_RESET: // lost power + info ("USB restart: %s", hcd->bus_name); + retval = hc_restart (ohci); + break; + + case OHCI_USB_SUSPEND: // host wakeup + case OHCI_USB_RESUME: // remote wakeup + info ("USB continue: %s from %s wakeup", hcd->bus_name, + (temp == OHCI_USB_SUSPEND) + ? "host" : "remote"); + ohci->hc_control = OHCI_USB_RESUME; + writel (ohci->hc_control, &ohci->regs->control); + (void) readl (&ohci->regs->control); + mdelay (20); /* no schedule here ! */ + /* Some controllers (lucent) need a longer delay here */ + mdelay (15); + + temp = readl (&ohci->regs->control); + temp = ohci->hc_control & OHCI_CTRL_HCFS; + if (temp != OHCI_USB_RESUME) { + err ("controller %s won't resume", hcd->bus_name); + ohci->disabled = 1; + retval = -EIO; + break; + } + + /* Some chips likes being resumed first */ + writel (OHCI_USB_OPER, &ohci->regs->control); + (void) readl (&ohci->regs->control); + mdelay (3); + + /* Then re-enable operations */ + spin_lock_irqsave (&ohci->lock, flags); + ohci->disabled = 0; + ohci->sleeping = 0; + ohci->hc_control = OHCI_CONTROL_INIT | OHCI_USB_OPER; + if (!ohci->ed_rm_list [0] && !ohci->ed_rm_list [1]) { + if (ohci->ed_controltail) + ohci->hc_control |= OHCI_CTRL_CLE; + if (ohci->ed_bulktail) + ohci->hc_control |= OHCI_CTRL_BLE; + } + hcd->state = USB_STATE_READY; + writel (ohci->hc_control, &ohci->regs->control); + + /* trigger a start-frame interrupt (why?) */ + writel (OHCI_INTR_SF, &ohci->regs->intrstatus); + writel (OHCI_INTR_SF, &ohci->regs->intrenable); + + /* Check for a pending done list */ + writel (OHCI_INTR_WDH, &ohci->regs->intrdisable); + (void) readl (&ohci->regs->intrdisable); + spin_unlock_irqrestore (&ohci->lock, flags); + + #ifdef CONFIG_PMAC_PBOOK + if (_machine == _MACH_Pmac) + enable_irq (ohci->irq); + #endif + if (ohci->hcca->done_head) + dl_done_list (ohci, dl_reverse_done_list (ohci)); + writel (OHCI_INTR_WDH, &ohci->regs->intrenable); + +// writel (OHCI_BLF, &ohci->regs->cmdstatus); +// writel (OHCI_CLF, &ohci->regs->cmdstatus); +ohci_dump_status (ohci); +dbg ("sleeping = %d, disabled = %d", ohci->sleeping, ohci->disabled); + break; + + default: + warn ("odd PCI resume for %s", hcd->bus_name); + } + return retval; +} + +#endif /* CONFIG_PM */ + + +/*-------------------------------------------------------------------------*/ + +static const char hcd_name [] = "ohci-hcd"; + +static const struct hc_driver ohci_driver = { + description: hcd_name, + + /* + * generic hardware linkage + */ + irq: ohci_irq, + flags: HCD_MEMORY | HCD_USB11, + + /* + * basic lifecycle operations + */ + start: ohci_start, +#ifdef CONFIG_PM + suspend: ohci_suspend, + resume: ohci_resume, +#endif + stop: ohci_stop, + + /* + * memory lifecycle (except per-request) + */ + hcd_alloc: ohci_hcd_alloc, + hcd_free: ohci_hcd_free, + + /* + * managing i/o requests and associated device resources + */ + urb_enqueue: ohci_urb_enqueue, + urb_dequeue: ohci_urb_dequeue, + free_config: ohci_free_config, + + /* + * scheduling support + */ + get_frame_number: ohci_get_frame, + + /* + * root hub support + */ + hub_status_data: ohci_hub_status_data, + hub_control: ohci_hub_control, +}; + +#define DRIVER_INFO DRIVER_VERSION " " DRIVER_DESC + +EXPORT_NO_SYMBOLS; +MODULE_AUTHOR (DRIVER_AUTHOR); +MODULE_DESCRIPTION (DRIVER_INFO); +MODULE_LICENSE ("GPL"); + +/*-------------------------------------------------------------------------*/ + +#ifdef CONFIG_PCI + +/* There do exist non-PCI implementations of OHCI ... + * Examples include the SA-1111 (ARM) and some MIPS + * and related hardware. + */ + +static const struct pci_device_id __devinitdata pci_ids [] = { { + + /* handle any USB OHCI controller */ + class: (PCI_CLASS_SERIAL_USB << 8) | 0x10, + class_mask: ~0, + driver_data: (unsigned long) &ohci_driver, + + /* no matter who makes it */ + vendor: PCI_ANY_ID, + device: PCI_ANY_ID, + subvendor: PCI_ANY_ID, + subdevice: PCI_ANY_ID, + + }, { /* end: all zeroes */ } +}; +MODULE_DEVICE_TABLE (pci, pci_ids); + +/* pci driver glue; this is a "new style" PCI driver module */ +static struct pci_driver ohci_pci_driver = { + name: (char *) hcd_name, + id_table: pci_ids, + + probe: usb_hcd_pci_probe, + remove: usb_hcd_pci_remove, + +#ifdef CONFIG_PM + suspend: usb_hcd_pci_suspend, + resume: usb_hcd_pci_resume, +#endif +}; + + +static int __init ohci_hcd_init (void) +{ + dbg (DRIVER_INFO); + dbg ("block sizes: ed %d td %d", + sizeof (struct ed), sizeof (struct td)); + return pci_module_init (&ohci_pci_driver); +} +module_init (ohci_hcd_init); + +/*-------------------------------------------------------------------------*/ + +static void __exit ohci_hcd_cleanup (void) +{ + pci_unregister_driver (&ohci_pci_driver); +} +module_exit (ohci_hcd_cleanup); + +#endif /* CONFIG_PCI */ + diff -Nru a/drivers/usb/hcd/ohci-hub.c b/drivers/usb/hcd/ohci-hub.c --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/drivers/usb/hcd/ohci-hub.c Mon Jan 21 10:48:39 2002 @@ -0,0 +1,267 @@ +/* + * OHCI HCD (Host Controller Driver) for USB. + * + * (C) Copyright 1999 Roman Weissgaerber + * (C) Copyright 2000-2001 David Brownell + * + * This file is licenced under GPL + * $Id: ohci-hub.c,v 1.2 2002/01/19 00:21:49 dbrownell Exp $ + */ + +/*-------------------------------------------------------------------------*/ + +/* + * 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 = readl (&hc->regs->roothub.register); \ + if (hc->flags & OHCI_QUIRK_AMD756) \ + while (temp & mask) \ + temp = 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 readl (&hc->regs->roothub.b); } +static inline u32 roothub_status (struct ohci_hcd *hc) + { return 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) \ + dbg ("%s: %s roothub.portstatus [%d] " \ + "= 0x%08x%s%s%s%s%s%s%s%s%s%s%s%s", \ + hc->hcd.bus_name, label, num, temp, \ + (temp & RH_PS_PRSC) ? " PRSC" : "", \ + (temp & RH_PS_OCIC) ? " OCIC" : "", \ + (temp & RH_PS_PSSC) ? " PSSC" : "", \ + (temp & RH_PS_PESC) ? " PESC" : "", \ + (temp & RH_PS_CSC) ? " CSC" : "", \ + \ + (temp & RH_PS_LSDA) ? " LSDA" : "", \ + (temp & RH_PS_PPS) ? " PPS" : "", \ + (temp & RH_PS_PRS) ? " PRS" : "", \ + (temp & RH_PS_POCI) ? " POCI" : "", \ + (temp & RH_PS_PSS) ? " PSS" : "", \ + \ + (temp & RH_PS_PES) ? " PES" : "", \ + (temp & RH_PS_CCS) ? " CCS" : "" \ + ); + + +/*-------------------------------------------------------------------------*/ + +/* build "status change" packet (one or two bytes) from HC registers */ + +static int +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; + + ports = roothub_a (ohci) & RH_A_NDP; + if (ports > MAX_ROOT_PORTS) { + err ("%s: bogus NDP=%d", hcd->bus_name, ports); + err ("rereads as NDP=%d", + readl (&ohci->regs->roothub.a) & RH_A_NDP); + /* retry later; "should not happen" */ + return 0; + } + + /* init status */ + if (roothub_status (ohci) & (RH_HS_LPSC | RH_HS_OCIC)) + buf [0] = changed = 1; + else + buf [0] = 0; + if (ports > 7) { + buf [1] = 0; + length++; + } + + /* look at each port */ + for (i = 0; i < ports; i++) { + u32 status = roothub_portstatus (ohci, i); + + status &= RH_PS_CSC | RH_PS_PESC | RH_PS_PSSC + | RH_PS_OCIC | RH_PS_PRSC; + if (status) { + changed = 1; + set_bit (i + 1, buf); + } + } + return changed ? length : 0; +} + +/*-------------------------------------------------------------------------*/ + +static void +ohci_hub_descriptor ( + struct ohci_hcd *ohci, + struct usb_hub_descriptor *desc +) { + u32 rh = roothub_a (ohci); + int ports = rh & RH_A_NDP; + u16 temp; + + desc->bDescriptorType = 0x29; + desc->bPwrOn2PwrGood = (rh & RH_A_POTPGT) >> 24; + desc->bHubContrCurrent = 0; + + desc->bNbrPorts = ports; + temp = 1 + (ports / 8); + desc->bDescLength = 7 + 2 * temp; + + temp = 0; + if (rh & RH_A_PSM) /* per-port power switching? */ + temp |= 0x0001; + if (rh & RH_A_NOCP) /* no overcurrent reporting? */ + temp |= 0x0010; + else if (rh & RH_A_OCPM) /* per-port overcurrent reporting? */ + temp |= 0x0008; + desc->wHubCharacteristics = cpu_to_le16 (temp); + + /* two bitmaps: ports removable, and usb 1.0 legacy PortPwrCtrlMask */ + rh = roothub_b (ohci); + desc->bitmap [0] = rh & RH_B_DR; + if (ports > 7) { + desc->bitmap [1] = (rh & RH_B_DR) >> 8; + desc->bitmap [2] = desc->bitmap [3] = 0xff; + } else + desc->bitmap [1] = 0xff; +} + +/*-------------------------------------------------------------------------*/ + +static int ohci_hub_control ( + struct usb_hcd *hcd, + u16 typeReq, + u16 wValue, + u16 wIndex, + char *buf, + u16 wLength +) { + struct ohci_hcd *ohci = hcd_to_ohci (hcd); + int ports; + u32 temp; + int retval = 0; + + // if (port request) + ports = roothub_a (ohci) & RH_A_NDP; + switch (typeReq) { + case ClearHubFeature: + switch (wValue) { + case C_HUB_OVER_CURRENT: + writel (RH_HS_OCIC, &ohci->regs->roothub.status); + case C_HUB_LOCAL_POWER: + break; + default: + goto error; + } + break; + case ClearPortFeature: + if (!wIndex || wIndex > ports) + goto error; + wIndex--; + + switch (wValue) { + case USB_PORT_FEAT_ENABLE: + temp = RH_PS_CCS; + break; + case USB_PORT_FEAT_C_ENABLE: + temp = RH_PS_PESC; + break; + case USB_PORT_FEAT_SUSPEND: + temp = RH_PS_POCI; + break; + case USB_PORT_FEAT_C_SUSPEND: + temp = RH_PS_PSSC; + break; + case USB_PORT_FEAT_POWER: + temp = RH_PS_LSDA; + break; + case USB_PORT_FEAT_C_CONNECTION: + temp = RH_PS_CSC; + break; + case USB_PORT_FEAT_C_OVER_CURRENT: + temp = RH_PS_OCIC; + break; + case USB_PORT_FEAT_C_RESET: + temp = RH_PS_PRSC; + break; + default: + goto error; + } + writel (temp, &ohci->regs->roothub.portstatus [wIndex]); + // readl (&ohci->regs->roothub.portstatus [wIndex]); + break; + case GetHubDescriptor: + ohci_hub_descriptor (ohci, (struct usb_hub_descriptor *) buf); + break; + case GetHubStatus: + temp = roothub_status (ohci) & ~(RH_HS_CRWE | RH_HS_DRWE); + *(u32 *) buf = cpu_to_le32 (temp); + break; + case GetPortStatus: + if (!wIndex || wIndex > ports) + goto error; + wIndex--; + temp = roothub_portstatus (ohci, wIndex); + *(u32 *) buf = cpu_to_le32 (temp); + +#ifndef OHCI_VERBOSE_DEBUG + if (*(u16*)(buf+2)) /* only if wPortChange is interesting */ +#endif + dbg_port (ohci, "GetStatus", wIndex + 1, temp); + break; + case SetHubFeature: + switch (wValue) { + case C_HUB_OVER_CURRENT: + // FIXME: this can be cleared, yes? + case C_HUB_LOCAL_POWER: + break; + default: + goto error; + } + break; + case SetPortFeature: + if (!wIndex || wIndex > ports) + goto error; + wIndex--; + switch (wValue) { + case USB_PORT_FEAT_SUSPEND: + writel (RH_PS_PSS, + &ohci->regs->roothub.portstatus [wIndex]); + break; + case USB_PORT_FEAT_POWER: + writel (RH_PS_PPS, + &ohci->regs->roothub.portstatus [wIndex]); + break; + case USB_PORT_FEAT_RESET: + temp = readl (&ohci->regs->roothub.portstatus [wIndex]); + if (temp & RH_PS_CCS) + writel (RH_PS_PRS, + &ohci->regs->roothub.portstatus [wIndex]); + break; + default: + goto error; + } + break; + + default: +error: + /* "protocol stall" on error */ + retval = -EPIPE; + } + return retval; +} + diff -Nru a/drivers/usb/hcd/ohci-mem.c b/drivers/usb/hcd/ohci-mem.c --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/drivers/usb/hcd/ohci-mem.c Mon Jan 21 10:48:39 2002 @@ -0,0 +1,251 @@ +/* + * OHCI HCD (Host Controller Driver) for USB. + * + * (C) Copyright 1999 Roman Weissgaerber + * (C) Copyright 2000-2001 David Brownell + * + * This file is licenced under GPL + * $Id: ohci-mem.c,v 1.2 2002/01/19 00:22:13 dbrownell Exp $ + */ + +/*-------------------------------------------------------------------------*/ + +/* + * There's basically three types of memory: + * - data used only by the HCD ... kmalloc is fine + * - async and periodic schedules, shared by HC and HCD ... these + * need to use pci_pool or pci_alloc_consistent + * - driver buffers, read/written by HC ... single shot DMA mapped + * + * There's also PCI "register" data, which is memory mapped. + * No memory seen by this driver is pagable. + */ + +/*-------------------------------------------------------------------------*/ + +static struct usb_hcd *ohci_hcd_alloc (void) +{ + struct ohci_hcd *ohci; + + ohci = (struct ohci_hcd *) kmalloc (sizeof *ohci, GFP_KERNEL); + if (ohci != 0) { + memset (ohci, 0, sizeof (struct ohci_hcd)); + return &ohci->hcd; + } + return 0; +} + +static void ohci_hcd_free (struct usb_hcd *hcd) +{ + kfree (hcd_to_ohci (hcd)); +} + +/*-------------------------------------------------------------------------*/ + +#ifdef DEBUG +# define OHCI_MEM_FLAGS SLAB_POISON +#else +# define OHCI_MEM_FLAGS 0 +#endif + +#ifndef CONFIG_PCI +# error "usb-ohci currently requires PCI-based controllers" + /* to support non-PCI OHCIs, you need custom bus/mem/... glue */ +#endif + + +/* Recover a TD/ED using its collision chain */ +static inline void * +dma_to_ed_td (struct hash_list_t * entry, dma_addr_t dma) +{ + struct hash_t * scan = entry->head; + while (scan && scan->dma != dma) + scan = scan->next; + return scan->virt; +} + +static inline struct ed * +dma_to_ed (struct ohci_hcd *hc, dma_addr_t ed_dma) +{ + return (struct ed *) dma_to_ed_td(&(hc->ed_hash [ED_HASH_FUNC(ed_dma)]), + ed_dma); +} + +static inline struct td * +dma_to_td (struct ohci_hcd *hc, dma_addr_t td_dma) +{ + return (struct td *) dma_to_ed_td(&(hc->td_hash [TD_HASH_FUNC(td_dma)]), + td_dma); +} + +// FIXME: when updating the hashtables this way, mem_flags is unusable... + +/* Add a hash entry for a TD/ED; return true on success */ +static int +hash_add_ed_td ( + struct hash_list_t *entry, + void *virt, + dma_addr_t dma, + int mem_flags +) +{ + struct hash_t * scan; + + scan = (struct hash_t *) kmalloc (sizeof *scan, mem_flags); + if (!scan) + return 0; + + if (!entry->tail) { + entry->head = entry->tail = scan; + } else { + entry->tail->next = scan; + entry->tail = scan; + } + + scan->virt = virt; + scan->dma = dma; + scan->next = NULL; + return 1; +} + +static inline int +hash_add_ed (struct ohci_hcd *hc, struct ed *ed, int mem_flags) +{ + return hash_add_ed_td (&(hc->ed_hash [ED_HASH_FUNC (ed->dma)]), + ed, ed->dma, mem_flags); +} + +static inline int +hash_add_td (struct ohci_hcd *hc, struct td *td, int mem_flags) +{ + return hash_add_ed_td (&(hc->td_hash [TD_HASH_FUNC (td->td_dma)]), + td, td->td_dma, mem_flags); +} + + +static void +hash_free_ed_td (struct hash_list_t *entry, void *virt) +{ + struct hash_t *scan, *prev; + scan = prev = entry->head; + + // Find and unlink hash entry + while (scan && scan->virt != virt) { + prev = scan; + scan = scan->next; + } + if (scan) { + if (scan == entry->head) { + if (entry->head == entry->tail) + entry->head = entry->tail = NULL; + else + entry->head = scan->next; + } else if (scan == entry->tail) { + entry->tail = prev; + prev->next = NULL; + } else + prev->next = scan->next; + kfree(scan); + } +} + +static inline void +hash_free_ed (struct ohci_hcd *hc, struct ed * ed) +{ + hash_free_ed_td (&(hc->ed_hash[ED_HASH_FUNC(ed->dma)]), ed); +} + +static inline void +hash_free_td (struct ohci_hcd *hc, struct td * td) +{ + hash_free_ed_td (&(hc->td_hash[TD_HASH_FUNC(td->td_dma)]), td); +} + + +static int ohci_mem_init (struct ohci_hcd *ohci) +{ + ohci->td_cache = pci_pool_create ("ohci_td", ohci->hcd.pdev, + sizeof (struct td), + 32 /* byte alignment */, + 0 /* no page-crossing issues */, + GFP_KERNEL | OHCI_MEM_FLAGS); + if (!ohci->td_cache) + return -ENOMEM; + ohci->ed_cache = pci_pool_create ("ohci_ed", ohci->hcd.pdev, + sizeof (struct ed), + 16 /* byte alignment */, + 0 /* no page-crossing issues */, + GFP_KERNEL | OHCI_MEM_FLAGS); + if (!ohci->ed_cache) { + pci_pool_destroy (ohci->td_cache); + return -ENOMEM; + } + return 0; +} + +static void ohci_mem_cleanup (struct ohci_hcd *ohci) +{ + if (ohci->td_cache) { + pci_pool_destroy (ohci->td_cache); + ohci->td_cache = 0; + } + if (ohci->ed_cache) { + pci_pool_destroy (ohci->ed_cache); + ohci->ed_cache = 0; + } +} + +/* TDs ... */ +static struct td * +td_alloc (struct ohci_hcd *hc, int mem_flags) +{ + dma_addr_t dma; + struct td *td; + + td = pci_pool_alloc (hc->td_cache, mem_flags, &dma); + if (td) { + td->td_dma = dma; + /* hash it for later reverse mapping */ + if (!hash_add_td (hc, td, mem_flags)) { + pci_pool_free (hc->td_cache, td, dma); + return NULL; + } + } + return td; +} + +static inline void +td_free (struct ohci_hcd *hc, struct td *td) +{ + hash_free_td (hc, td); + pci_pool_free (hc->td_cache, td, td->td_dma); +} + + +/* EDs ... */ +static struct ed * +ed_alloc (struct ohci_hcd *hc, int mem_flags) +{ + dma_addr_t dma; + struct ed *ed; + + ed = pci_pool_alloc (hc->ed_cache, mem_flags, &dma); + if (ed) { + memset (ed, 0, sizeof (*ed)); + ed->dma = dma; + /* hash it for later reverse mapping */ + if (!hash_add_ed (hc, ed, mem_flags)) { + pci_pool_free (hc->ed_cache, ed, dma); + return NULL; + } + } + return ed; +} + +static inline void +ed_free (struct ohci_hcd *hc, struct ed *ed) +{ + hash_free_ed (hc, ed); + pci_pool_free (hc->ed_cache, ed, ed->dma); +} + diff -Nru a/drivers/usb/hcd/ohci-q.c b/drivers/usb/hcd/ohci-q.c --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/drivers/usb/hcd/ohci-q.c Mon Jan 21 10:48:39 2002 @@ -0,0 +1,1000 @@ +/* + * OHCI HCD (Host Controller Driver) for USB. + * + * (C) Copyright 1999 Roman Weissgaerber + * (C) Copyright 2000-2001 David Brownell + * + * This file is licenced under GPL + * $Id: ohci-q.c,v 1.6 2002/01/19 00:23:15 dbrownell Exp $ + */ + +static void urb_free_priv (struct ohci_hcd *hc, urb_priv_t *urb_priv) +{ + int last = urb_priv->length - 1; + + if (last >= 0) { + int i; + struct td *td = urb_priv->td [0]; +#ifdef CONFIG_PCI + int len = td->urb->transfer_buffer_length; + int dir = usb_pipeout (td->urb->pipe) + ? PCI_DMA_TODEVICE + : PCI_DMA_FROMDEVICE; + + /* unmap CTRL URB setup buffer (always td 0) */ + if (usb_pipecontrol (td->urb->pipe)) { + pci_unmap_single (hc->hcd.pdev, + td->data_dma, 8, PCI_DMA_TODEVICE); + + /* CTRL data buffer starts at td 1 if len > 0 */ + if (len && last > 0) + td = urb_priv->td [1]; + } + /* else: ISOC, BULK, INTR data buffer starts at td 0 */ + + /* unmap data buffer */ + if (len && td->data_dma) + pci_unmap_single (hc->hcd.pdev, + td->data_dma, len, dir); +#else +# warning "assuming no buffer unmapping is needed" +#endif + + for (i = 0; i <= last; i++) { + td = urb_priv->td [i]; + if (td) + td_free (hc, td); + } + } + + kfree (urb_priv); +} + +/*-------------------------------------------------------------------------*/ + +/* + * URB goes back to driver, and isn't reissued. + * It's completely gone from HC data structures, so no locking + * is needed ... or desired! (Giveback can call back to hcd.) + */ +static inline void finish_urb (struct ohci_hcd *ohci, struct urb *urb) +{ + if (urb->hcpriv) { + urb_free_priv (ohci, urb->hcpriv); + urb->hcpriv = NULL; + } + usb_hcd_giveback_urb (&ohci->hcd, urb); +} + +static void td_submit_urb (struct urb *urb); + +/* + * URB is reported to driver, is reissued if it's periodic. + */ +static int return_urb (struct ohci_hcd *hc, struct urb *urb) +{ + urb_priv_t *urb_priv = urb->hcpriv; + struct urb *urbt; + unsigned long flags; + int i; + +#ifdef DEBUG + if (!urb_priv) { + err ("already unlinked!"); + BUG (); + } + + /* just to be sure */ + if (!urb->complete) { + err ("no completion!"); + BUG (); + } +#endif + +#ifdef OHCI_VERBOSE_DEBUG + urb_print (urb, "RET", usb_pipeout (urb->pipe)); +#endif + +// FIXME: but if urb->status says it was was unlinked ... + + switch (usb_pipetype (urb->pipe)) { + case PIPE_INTERRUPT: +#ifdef CONFIG_PCI + pci_unmap_single (hc->hcd.pdev, + urb_priv->td [0]->data_dma, + urb->transfer_buffer_length, + usb_pipeout (urb->pipe) + ? PCI_DMA_TODEVICE + : PCI_DMA_FROMDEVICE); +#endif + urb->complete (urb); + + /* implicitly requeued */ + urb->actual_length = 0; + urb->status = -EINPROGRESS; + if (urb_priv->state != URB_DEL) { + spin_lock_irqsave (&hc->lock, flags); + td_submit_urb (urb); + spin_unlock_irqrestore (&hc->lock, flags); + } + break; + + case PIPE_ISOCHRONOUS: + for (urbt = urb->next; + urbt && (urbt != urb); + urbt = urbt->next) + continue; + if (urbt) { /* send the reply and requeue URB */ +#ifdef CONFIG_PCI +// FIXME this style unmap is only done on this route ... + pci_unmap_single (hc->hcd.pdev, + urb_priv->td [0]->data_dma, + urb->transfer_buffer_length, + usb_pipeout (urb->pipe) + ? PCI_DMA_TODEVICE + : PCI_DMA_FROMDEVICE); +#endif + urb->complete (urb); + spin_lock_irqsave (&hc->lock, flags); + urb->actual_length = 0; + urb->status = -EINPROGRESS; + urb->start_frame = urb_priv->ed->last_iso + 1; + if (urb_priv->state != URB_DEL) { + for (i = 0; i < urb->number_of_packets; + i++) { + urb->iso_frame_desc [i] + .actual_length = 0; + urb->iso_frame_desc [i] + .status = -EXDEV; + } + td_submit_urb (urb); + } +// FIXME if not deleted, should have been "finished" + spin_unlock_irqrestore (&hc->lock, flags); + + } else { /* not reissued */ + finish_urb (hc, urb); + } + break; + + /* + * C/B requests that get here are never reissued. + */ + case PIPE_BULK: + case PIPE_CONTROL: + finish_urb (hc, urb); + break; + } + return 0; +} + + +/*-------------------------------------------------------------------------* + * ED handling functions + *-------------------------------------------------------------------------*/ + +/* search for the right branch to insert an interrupt ed into the int tree + * do some load balancing; + * returns the branch and + * sets the interval to interval = 2^integer (ld (interval)) + */ +static int ep_int_balance (struct ohci_hcd *ohci, int interval, int load) +{ + int i, branch = 0; + + /* search for the least loaded interrupt endpoint branch */ + for (i = 0; i < NUM_INTS ; i++) + if (ohci->ohci_int_load [branch] > ohci->ohci_int_load [i]) + branch = i; + + branch = branch % interval; + for (i = branch; i < NUM_INTS; i += interval) + ohci->ohci_int_load [i] += load; + + return branch; +} + +/*-------------------------------------------------------------------------*/ + +/* 2^int ( ld (inter)) */ + +static int ep_2_n_interval (int inter) +{ + int i; + + for (i = 0; ((inter >> i) > 1 ) && (i < 5); i++) + continue; + return 1 << i; +} + +/*-------------------------------------------------------------------------*/ + +/* the int tree is a binary tree + * in order to process it sequentially the indexes of the branches have + * to be mapped the mapping reverses the bits of a word of num_bits length + */ +static int ep_rev (int num_bits, int word) +{ + int i, wout = 0; + + for (i = 0; i < num_bits; i++) + wout |= (( (word >> i) & 1) << (num_bits - i - 1)); + return wout; +} + +/*-------------------------------------------------------------------------*/ + +/* link an ed into one of the HC chains */ + +static int ep_link (struct ohci_hcd *ohci, struct ed *edi) +{ + int int_branch, i; + int inter, interval, load; + __u32 *ed_p; + volatile struct ed *ed = edi; + + ed->state = ED_OPER; + + switch (ed->type) { + case PIPE_CONTROL: + ed->hwNextED = 0; + if (ohci->ed_controltail == NULL) { + writel (ed->dma, &ohci->regs->ed_controlhead); + } else { + ohci->ed_controltail->hwNextED = cpu_to_le32 (ed->dma); + } + ed->ed_prev = ohci->ed_controltail; + if (!ohci->ed_controltail + && !ohci->ed_rm_list [0] + && !ohci->ed_rm_list [1] + && !ohci->sleeping + ) { + ohci->hc_control |= OHCI_CTRL_CLE; + writel (ohci->hc_control, &ohci->regs->control); + } + ohci->ed_controltail = edi; + break; + + case PIPE_BULK: + ed->hwNextED = 0; + if (ohci->ed_bulktail == NULL) { + writel (ed->dma, &ohci->regs->ed_bulkhead); + } else { + ohci->ed_bulktail->hwNextED = cpu_to_le32 (ed->dma); + } + ed->ed_prev = ohci->ed_bulktail; + if (!ohci->ed_bulktail + && !ohci->ed_rm_list [0] + && !ohci->ed_rm_list [1] + && !ohci->sleeping + ) { + ohci->hc_control |= OHCI_CTRL_BLE; + writel (ohci->hc_control, &ohci->regs->control); + } + ohci->ed_bulktail = edi; + break; + + case PIPE_INTERRUPT: + load = ed->int_load; + interval = ep_2_n_interval (ed->int_period); + ed->int_interval = interval; + int_branch = ep_int_balance (ohci, interval, load); + ed->int_branch = int_branch; + + for (i = 0; i < ep_rev (6, interval); i += inter) { + inter = 1; + for (ed_p = & (ohci->hcca->int_table [ep_rev (5, i) + int_branch]); + (*ed_p != 0) && ((dma_to_ed (ohci, le32_to_cpup (ed_p)))->int_interval >= interval); + ed_p = & ((dma_to_ed (ohci, le32_to_cpup (ed_p)))->hwNextED)) + inter = ep_rev (6, (dma_to_ed (ohci, le32_to_cpup (ed_p)))->int_interval); + ed->hwNextED = *ed_p; + *ed_p = cpu_to_le32 (ed->dma); + } +#ifdef DEBUG + ep_print_int_eds (ohci, "LINK_INT"); +#endif + break; + + case PIPE_ISOCHRONOUS: + ed->hwNextED = 0; + ed->int_interval = 1; + if (ohci->ed_isotail != NULL) { + ohci->ed_isotail->hwNextED = cpu_to_le32 (ed->dma); + ed->ed_prev = ohci->ed_isotail; + } else { + for ( i = 0; i < NUM_INTS; i += inter) { + inter = 1; + for (ed_p = & (ohci->hcca->int_table [ep_rev (5, i)]); + *ed_p != 0; + ed_p = & ((dma_to_ed (ohci, le32_to_cpup (ed_p)))->hwNextED)) + inter = ep_rev (6, (dma_to_ed (ohci, le32_to_cpup (ed_p)))->int_interval); + *ed_p = cpu_to_le32 (ed->dma); + } + ed->ed_prev = NULL; + } + ohci->ed_isotail = edi; +#ifdef DEBUG + ep_print_int_eds (ohci, "LINK_ISO"); +#endif + break; + } + return 0; +} + +/*-------------------------------------------------------------------------*/ + +/* unlink an ed from one of the HC chains. + * just the link to the ed is unlinked. + * the link from the ed still points to another operational ed or 0 + * so the HC can eventually finish the processing of the unlinked ed + */ +static int ep_unlink (struct ohci_hcd *ohci, struct ed *ed) +{ + int int_branch; + int i; + int inter; + int interval; + __u32 *ed_p; + + ed->hwINFO |= __constant_cpu_to_le32 (OHCI_ED_SKIP); + + switch (ed->type) { + case PIPE_CONTROL: + if (ed->ed_prev == NULL) { + if (!ed->hwNextED) { + ohci->hc_control &= ~OHCI_CTRL_CLE; + writel (ohci->hc_control, &ohci->regs->control); + } + writel (le32_to_cpup (&ed->hwNextED), + &ohci->regs->ed_controlhead); + } else { + ed->ed_prev->hwNextED = ed->hwNextED; + } + if (ohci->ed_controltail == ed) { + ohci->ed_controltail = ed->ed_prev; + } else { + (dma_to_ed (ohci, le32_to_cpup (&ed->hwNextED))) + ->ed_prev = ed->ed_prev; + } + break; + + case PIPE_BULK: + if (ed->ed_prev == NULL) { + if (!ed->hwNextED) { + ohci->hc_control &= ~OHCI_CTRL_BLE; + writel (ohci->hc_control, &ohci->regs->control); + } + writel (le32_to_cpup (&ed->hwNextED), + &ohci->regs->ed_bulkhead); + } else { + ed->ed_prev->hwNextED = ed->hwNextED; + } + if (ohci->ed_bulktail == ed) { + ohci->ed_bulktail = ed->ed_prev; + } else { + (dma_to_ed (ohci, le32_to_cpup (&ed->hwNextED))) + ->ed_prev = ed->ed_prev; + } + break; + + case PIPE_INTERRUPT: + int_branch = ed->int_branch; + interval = ed->int_interval; + + for (i = 0; i < ep_rev (6, interval); i += inter) { + for (ed_p = & (ohci->hcca->int_table [ep_rev (5, i) + int_branch]), inter = 1; + (*ed_p != 0) && (*ed_p != ed->hwNextED); + ed_p = & ((dma_to_ed (ohci, le32_to_cpup (ed_p)))->hwNextED), + inter = ep_rev (6, (dma_to_ed (ohci, le32_to_cpup (ed_p)))->int_interval)) { + if ((dma_to_ed (ohci, le32_to_cpup (ed_p))) == ed) { + *ed_p = ed->hwNextED; + break; + } + } + } + for (i = int_branch; i < NUM_INTS; i += interval) + ohci->ohci_int_load [i] -= ed->int_load; +#ifdef DEBUG + ep_print_int_eds (ohci, "UNLINK_INT"); +#endif + break; + + case PIPE_ISOCHRONOUS: + if (ohci->ed_isotail == ed) + ohci->ed_isotail = ed->ed_prev; + if (ed->hwNextED != 0) + (dma_to_ed (ohci, le32_to_cpup (&ed->hwNextED))) + ->ed_prev = ed->ed_prev; + + if (ed->ed_prev != NULL) { + ed->ed_prev->hwNextED = ed->hwNextED; + } else { + for (i = 0; i < NUM_INTS; i++) { + for (ed_p = & (ohci->hcca->int_table [ep_rev (5, i)]); + *ed_p != 0; + ed_p = & ((dma_to_ed (ohci, le32_to_cpup (ed_p)))->hwNextED)) { + // inter = ep_rev (6, (dma_to_ed (ohci, le32_to_cpup (ed_p)))->int_interval); + if ((dma_to_ed (ohci, le32_to_cpup (ed_p))) == ed) { + *ed_p = ed->hwNextED; + break; + } + } + } + } +#ifdef DEBUG + ep_print_int_eds (ohci, "UNLINK_ISO"); +#endif + break; + } + ed->state = ED_UNLINK; + return 0; +} + + +/*-------------------------------------------------------------------------*/ + +/* (re)init an endpoint; this _should_ be done once at the + * usb_set_configuration command, but the USB stack is a bit stateless + * so we do it at every transaction. + * if the state of the ed is ED_NEW then a dummy td is added and the + * state is changed to ED_UNLINK + * in all other cases the state is left unchanged + * the ed info fields are set even though most of them should + * not change + */ +static struct ed *ep_add_ed ( + struct usb_device *udev, + unsigned int pipe, + int interval, + int load, + int mem_flags +) { + struct ohci_hcd *ohci = hcd_to_ohci (udev->bus->hcpriv); + struct hcd_dev *dev = (struct hcd_dev *) udev->hcpriv; + struct td *td; + struct ed *ed; + unsigned ep; + unsigned long flags; + + spin_lock_irqsave (&ohci->lock, flags); + + ep = usb_pipeendpoint (pipe) << 1; + if (!usb_pipecontrol (pipe) && usb_pipeout (pipe)) + ep |= 1; + if (!(ed = dev->ep [ep])) { + ed = ed_alloc (ohci, SLAB_ATOMIC); + if (!ed) { + /* out of memory */ + spin_unlock_irqrestore (&ohci->lock, flags); + return NULL; + } + dev->ep [ep] = ed; + } + + if (ed->state & ED_URB_DEL) { + /* pending unlink request */ + spin_unlock_irqrestore (&ohci->lock, flags); + return NULL; + } + + if (ed->state == ED_NEW) { + ed->hwINFO = __constant_cpu_to_le32 (OHCI_ED_SKIP); + /* dummy td; end of td list for ed */ + td = td_alloc (ohci, SLAB_ATOMIC); + if (!td) { + /* out of memory */ + spin_unlock_irqrestore (&ohci->lock, flags); + return NULL; + } + ed->hwTailP = cpu_to_le32 (td->td_dma); + ed->hwHeadP = ed->hwTailP; + ed->state = ED_UNLINK; + ed->type = usb_pipetype (pipe); + } + + ohci->dev [usb_pipedevice (pipe)] = udev; + +// FIXME: don't do this if it's linked to the HC, +// we might clobber data toggle or other state ... + + ed->hwINFO = cpu_to_le32 (usb_pipedevice (pipe) + | usb_pipeendpoint (pipe) << 7 + | (usb_pipeisoc (pipe)? 0x8000: 0) + | (usb_pipecontrol (pipe) + ? 0: (usb_pipeout (pipe)? 0x800: 0x1000)) + | (udev->speed == USB_SPEED_LOW) << 13 + | usb_maxpacket (udev, pipe, usb_pipeout (pipe)) + << 16); + + if (ed->type == PIPE_INTERRUPT && ed->state == ED_UNLINK) { + ed->int_period = interval; + ed->int_load = load; + } + + spin_unlock_irqrestore (&ohci->lock, flags); + return ed; +} + +/*-------------------------------------------------------------------------*/ + +/* request unlinking of an endpoint from an operational HC. + * put the ep on the rm_list and stop the bulk or ctrl list + * real work is done at the next start frame (SF) hardware interrupt + */ +static void ed_unlink (struct usb_device *usb_dev, struct ed *ed) +{ + unsigned int frame; + struct ohci_hcd *ohci = hcd_to_ohci (usb_dev->bus->hcpriv); + + /* already pending? */ + if (ed->state & ED_URB_DEL) + return; + ed->state |= ED_URB_DEL; + + ed->hwINFO |= __constant_cpu_to_le32 (OHCI_ED_SKIP); + + switch (ed->type) { + case PIPE_CONTROL: /* stop control list */ + ohci->hc_control &= ~OHCI_CTRL_CLE; + writel (ohci->hc_control, + &ohci->regs->control); + break; + case PIPE_BULK: /* stop bulk list */ + ohci->hc_control &= ~OHCI_CTRL_BLE; + writel (ohci->hc_control, + &ohci->regs->control); + break; + } + + frame = le16_to_cpu (ohci->hcca->frame_no) & 0x1; + ed->ed_rm_list = ohci->ed_rm_list [frame]; + ohci->ed_rm_list [frame] = ed; + + /* enable SOF interrupt */ + if (!ohci->sleeping) { + writel (OHCI_INTR_SF, &ohci->regs->intrstatus); + writel (OHCI_INTR_SF, &ohci->regs->intrenable); + } +} + +/*-------------------------------------------------------------------------* + * TD handling functions + *-------------------------------------------------------------------------*/ + +/* enqueue next TD for this URB (OHCI spec 5.2.8.2) */ + +static void +td_fill (struct ohci_hcd *ohci, unsigned int info, + dma_addr_t data, int len, + struct urb *urb, int index) +{ + volatile struct td *td, *td_pt; + urb_priv_t *urb_priv = urb->hcpriv; + + if (index >= urb_priv->length) { + err ("internal OHCI error: TD index > length"); + return; + } + + /* use this td as the next dummy */ + td_pt = urb_priv->td [index]; + td_pt->hwNextTD = 0; + + /* fill the old dummy TD */ + td = urb_priv->td [index] = dma_to_td (ohci, + le32_to_cpup (&urb_priv->ed->hwTailP) & ~0xf); + + td->ed = urb_priv->ed; + td->next_dl_td = NULL; + td->index = index; + td->urb = urb; + td->data_dma = data; + if (!len) + data = 0; + + td->hwINFO = cpu_to_le32 (info); + if ((td->ed->type) == PIPE_ISOCHRONOUS) { + td->hwCBP = cpu_to_le32 (data & 0xFFFFF000); + td->ed->last_iso = info & 0xffff; + } else { + td->hwCBP = cpu_to_le32 (data); + } + if (data) + td->hwBE = cpu_to_le32 (data + len - 1); + else + td->hwBE = 0; + td->hwNextTD = cpu_to_le32 (td_pt->td_dma); + td->hwPSW [0] = cpu_to_le16 ((data & 0x0FFF) | 0xE000); + + /* append to queue */ + td->ed->hwTailP = td->hwNextTD; +} + +/*-------------------------------------------------------------------------*/ + +/* prepare all TDs of a transfer */ + +static void td_submit_urb (struct urb *urb) +{ + urb_priv_t *urb_priv = urb->hcpriv; + struct ohci_hcd *ohci = hcd_to_ohci (urb->dev->bus->hcpriv); + dma_addr_t data; + int data_len = urb->transfer_buffer_length; + int cnt = 0; + __u32 info = 0; + unsigned int toggle = 0; + + /* OHCI handles the DATA-toggles itself, we just use the + * USB-toggle bits for resetting + */ + if (usb_gettoggle (urb->dev, usb_pipeendpoint (urb->pipe), + usb_pipeout (urb->pipe))) { + toggle = TD_T_TOGGLE; + } else { + toggle = TD_T_DATA0; + usb_settoggle (urb->dev, usb_pipeendpoint (urb->pipe), + usb_pipeout (urb->pipe), 1); + } + + urb_priv->td_cnt = 0; + + if (data_len) { +#ifdef CONFIG_PCI + data = pci_map_single (ohci->hcd.pdev, + urb->transfer_buffer, data_len, + usb_pipeout (urb->pipe) + ? PCI_DMA_TODEVICE + : PCI_DMA_FROMDEVICE + ); +#else +# error "what dma addr to use" +#endif + } else + data = 0; + + switch (usb_pipetype (urb->pipe)) { + case PIPE_BULK: + info = usb_pipeout (urb->pipe) + ? TD_CC | TD_DP_OUT + : TD_CC | TD_DP_IN ; + while (data_len > 4096) { + td_fill (ohci, + info | (cnt? TD_T_TOGGLE:toggle), + data, 4096, urb, cnt); + data += 4096; data_len -= 4096; cnt++; + } + info = usb_pipeout (urb->pipe)? + TD_CC | TD_DP_OUT : TD_CC | TD_R | TD_DP_IN ; + td_fill (ohci, info | (cnt? TD_T_TOGGLE:toggle), + data, data_len, urb, cnt); + cnt++; + if ((urb->transfer_flags & USB_ZERO_PACKET) + && cnt < urb_priv->length) { + td_fill (ohci, info | (cnt? TD_T_TOGGLE:toggle), + 0, 0, urb, cnt); + cnt++; + } + /* start bulk list */ + if (!ohci->sleeping) + writel (OHCI_BLF, &ohci->regs->cmdstatus); + break; + + case PIPE_INTERRUPT: + info = TD_CC | toggle; + info |= usb_pipeout (urb->pipe) + ? TD_DP_OUT + : TD_R | TD_DP_IN; + td_fill (ohci, info, data, data_len, urb, cnt++); + break; + + case PIPE_CONTROL: + info = TD_CC | TD_DP_SETUP | TD_T_DATA0; + td_fill (ohci, info, +#ifdef CONFIG_PCI + pci_map_single (ohci->hcd.pdev, + urb->setup_packet, 8, + PCI_DMA_TODEVICE), +#else +# error "what dma addr to use" +#endif + 8, urb, cnt++); + if (data_len > 0) { + info = TD_CC | TD_R | TD_T_DATA1; + info |= usb_pipeout (urb->pipe) + ? TD_DP_OUT + : TD_DP_IN; + /* NOTE: mishandles transfers >8K, some >4K */ + td_fill (ohci, info, data, data_len, + urb, cnt++); + } + info = usb_pipeout (urb->pipe) + ? TD_CC | TD_DP_IN | TD_T_DATA1 + : TD_CC | TD_DP_OUT | TD_T_DATA1; + td_fill (ohci, info, data, 0, urb, cnt++); + /* start control list */ + if (!ohci->sleeping) + writel (OHCI_CLF, &ohci->regs->cmdstatus); + break; + + case PIPE_ISOCHRONOUS: + for (cnt = 0; cnt < urb->number_of_packets; cnt++) { + td_fill (ohci, TD_CC | TD_ISO + | ((urb->start_frame + cnt) & 0xffff), + data + urb->iso_frame_desc [cnt].offset, + urb->iso_frame_desc [cnt].length, urb, cnt); + } + break; + } + if (urb_priv->length != cnt) + dbg ("TD LENGTH %d != CNT %d", urb_priv->length, cnt); +} + +/*-------------------------------------------------------------------------* + * Done List handling functions + *-------------------------------------------------------------------------*/ + +/* calculate the transfer length and update the urb */ + +static void dl_transfer_length (struct td *td) +{ + __u32 tdINFO, tdBE, tdCBP; + __u16 tdPSW; + struct urb *urb = td->urb; + urb_priv_t *urb_priv = urb->hcpriv; + int dlen = 0; + int cc = 0; + + tdINFO = le32_to_cpup (&td->hwINFO); + tdBE = le32_to_cpup (&td->hwBE); + tdCBP = le32_to_cpup (&td->hwCBP); + + + if (tdINFO & TD_ISO) { + tdPSW = le16_to_cpu (td->hwPSW [0]); + cc = (tdPSW >> 12) & 0xF; + if (cc < 0xE) { + if (usb_pipeout (urb->pipe)) { + dlen = urb->iso_frame_desc [td->index].length; + } else { + dlen = tdPSW & 0x3ff; + } + urb->actual_length += dlen; + urb->iso_frame_desc [td->index].actual_length = dlen; + if (! (urb->transfer_flags & USB_DISABLE_SPD) + && (cc == TD_DATAUNDERRUN)) + cc = TD_CC_NOERROR; + + urb->iso_frame_desc [td->index].status + = cc_to_error [cc]; + } + } else { /* BULK, INT, CONTROL DATA */ + if (! (usb_pipetype (urb->pipe) == PIPE_CONTROL && + ((td->index == 0) + || (td->index == urb_priv->length - 1)))) { + if (tdBE != 0) { + urb->actual_length += (td->hwCBP == 0) + ? (tdBE - td->data_dma + 1) + : (tdCBP - td->data_dma); + } + } + } +} + +/*-------------------------------------------------------------------------*/ + +/* replies to the request have to be on a FIFO basis so + * we unreverse the hc-reversed done-list + */ +static struct td *dl_reverse_done_list (struct ohci_hcd *ohci) +{ + __u32 td_list_hc; + struct td *td_rev = NULL; + struct td *td_list = NULL; + urb_priv_t *urb_priv = NULL; + unsigned long flags; + + spin_lock_irqsave (&ohci->lock, flags); + + td_list_hc = le32_to_cpup (&ohci->hcca->done_head) & 0xfffffff0; + ohci->hcca->done_head = 0; + + while (td_list_hc) { + td_list = dma_to_td (ohci, td_list_hc); + + if (TD_CC_GET (le32_to_cpup (&td_list->hwINFO))) { + urb_priv = (urb_priv_t *) td_list->urb->hcpriv; + dbg (" USB-error/status: %x : %p", + TD_CC_GET (le32_to_cpup (&td_list->hwINFO)), + td_list); + if (td_list->ed->hwHeadP + & __constant_cpu_to_le32 (0x1)) { + if (urb_priv && ((td_list->index + 1) + < urb_priv->length)) { + td_list->ed->hwHeadP = + (urb_priv->td [urb_priv->length - 1]->hwNextTD + & __constant_cpu_to_le32 (0xfffffff0)) + | (td_list->ed->hwHeadP + & __constant_cpu_to_le32 (0x2)); + urb_priv->td_cnt += urb_priv->length + - td_list->index - 1; + } else + td_list->ed->hwHeadP &= + __constant_cpu_to_le32 (0xfffffff2); + } + } + + td_list->next_dl_td = td_rev; + td_rev = td_list; + td_list_hc = le32_to_cpup (&td_list->hwNextTD) & 0xfffffff0; + } + spin_unlock_irqrestore (&ohci->lock, flags); + return td_list; +} + +/*-------------------------------------------------------------------------*/ + +/* there are some pending requests to unlink + * - some URBs/TDs if urb_priv->state == URB_DEL + */ +static void dl_del_list (struct ohci_hcd *ohci, unsigned int frame) +{ + unsigned long flags; + struct ed *ed; + __u32 edINFO; + __u32 tdINFO; + struct td *td = NULL, *td_next = NULL, + *tdHeadP = NULL, *tdTailP; + __u32 *td_p; + int ctrl = 0, bulk = 0; + + spin_lock_irqsave (&ohci->lock, flags); + + for (ed = ohci->ed_rm_list [frame]; ed != NULL; ed = ed->ed_rm_list) { + + tdTailP = dma_to_td (ohci, + le32_to_cpup (&ed->hwTailP) & 0xfffffff0); + tdHeadP = dma_to_td (ohci, + le32_to_cpup (&ed->hwHeadP) & 0xfffffff0); + edINFO = le32_to_cpup (&ed->hwINFO); + td_p = &ed->hwHeadP; + + for (td = tdHeadP; td != tdTailP; td = td_next) { + struct urb *urb = td->urb; + urb_priv_t *urb_priv = td->urb->hcpriv; + + td_next = dma_to_td (ohci, + le32_to_cpup (&td->hwNextTD) & 0xfffffff0); + if ((urb_priv->state == URB_DEL)) { + tdINFO = le32_to_cpup (&td->hwINFO); + if (TD_CC_GET (tdINFO) < 0xE) + dl_transfer_length (td); + *td_p = td->hwNextTD | (*td_p + & __constant_cpu_to_le32 (0x3)); + + /* URB is done; clean up */ + if (++ (urb_priv->td_cnt) == urb_priv->length) +// FIXME: we shouldn't hold ohci->lock here, else the +// completion function can't talk to this hcd ... + finish_urb (ohci, urb); + } else { + td_p = &td->hwNextTD; + } + } + + ed->state &= ~ED_URB_DEL; + tdHeadP = dma_to_td (ohci, + le32_to_cpup (&ed->hwHeadP) & 0xfffffff0); + + if (tdHeadP == tdTailP) { + if (ed->state == ED_OPER) + ep_unlink (ohci, ed); + td_free (ohci, tdTailP); + ed->hwINFO = __constant_cpu_to_le32 (OHCI_ED_SKIP); + ed->state = ED_NEW; + } else + ed->hwINFO &= ~__constant_cpu_to_le32 (OHCI_ED_SKIP); + + switch (ed->type) { + case PIPE_CONTROL: + ctrl = 1; + break; + case PIPE_BULK: + bulk = 1; + break; + } + } + + /* maybe reenable control and bulk lists */ + if (!ohci->disabled) { + if (ctrl) /* reset control list */ + writel (0, &ohci->regs->ed_controlcurrent); + if (bulk) /* reset bulk list */ + writel (0, &ohci->regs->ed_bulkcurrent); + if (!ohci->ed_rm_list [!frame]) { + if (ohci->ed_controltail) + ohci->hc_control |= OHCI_CTRL_CLE; + if (ohci->ed_bulktail) + ohci->hc_control |= OHCI_CTRL_BLE; + writel (ohci->hc_control, &ohci->regs->control); + } + } + + ohci->ed_rm_list [frame] = NULL; + spin_unlock_irqrestore (&ohci->lock, flags); +} + + + +/*-------------------------------------------------------------------------*/ + +/* + * process normal completions (error or success) and some unlinked eds + * this is the main path for handing urbs back to drivers + */ +static void dl_done_list (struct ohci_hcd *ohci, struct td *td_list) +{ + struct td *td_list_next = NULL; + struct ed *ed; + int cc = 0; + struct urb *urb; + urb_priv_t *urb_priv; + __u32 tdINFO, edHeadP, edTailP; + + unsigned long flags; + + while (td_list) { + td_list_next = td_list->next_dl_td; + + urb = td_list->urb; + urb_priv = urb->hcpriv; + tdINFO = le32_to_cpup (&td_list->hwINFO); + + ed = td_list->ed; + + dl_transfer_length (td_list); + + /* error code of transfer */ + cc = TD_CC_GET (tdINFO); + if (cc == TD_CC_STALL) + usb_endpoint_halt (urb->dev, + usb_pipeendpoint (urb->pipe), + usb_pipeout (urb->pipe)); + + if (! (urb->transfer_flags & USB_DISABLE_SPD) + && (cc == TD_DATAUNDERRUN)) + cc = TD_CC_NOERROR; + + if (++ (urb_priv->td_cnt) == urb_priv->length) { + /* + * Except for periodic transfers, both branches do + * the same thing. Periodic urbs get reissued until + * they're "deleted" with usb_unlink_urb. + */ + if ((ed->state & (ED_OPER | ED_UNLINK)) + && (urb_priv->state != URB_DEL)) { + spin_lock (&urb->lock); + if (urb->status == -EINPROGRESS) + urb->status = cc_to_error [cc]; + spin_unlock (&urb->lock); + return_urb (ohci, urb); + } else + finish_urb (ohci, urb); + } + + spin_lock_irqsave (&ohci->lock, flags); + if (ed->state != ED_NEW) { + edHeadP = le32_to_cpup (&ed->hwHeadP) & 0xfffffff0; + edTailP = le32_to_cpup (&ed->hwTailP); + +// FIXME: ED_UNLINK is very fuzzy w.r.t. what the hc knows... + + /* unlink eds if they are not busy */ + if ((edHeadP == edTailP) && (ed->state == ED_OPER)) + ep_unlink (ohci, ed); + } + spin_unlock_irqrestore (&ohci->lock, flags); + + td_list = td_list_next; + } +} + diff -Nru a/drivers/usb/hcd/ohci.h b/drivers/usb/hcd/ohci.h --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/drivers/usb/hcd/ohci.h Mon Jan 21 10:48:39 2002 @@ -0,0 +1,360 @@ +/* + * OHCI HCD (Host Controller Driver) for USB. + * + * (C) Copyright 1999 Roman Weissgaerber + * (C) Copyright 2000-2001 David Brownell + * + * This file is licenced under GPL + * $Id: ohci.h,v 1.5 2002/01/19 00:24:01 dbrownell Exp $ + */ + +static const int cc_to_error [16] = { + +/* map OHCI status to errno values */ + /* No Error */ 0, + /* CRC Error */ -EILSEQ, + /* Bit Stuff */ -EPROTO, + /* Data Togg */ -EILSEQ, + /* Stall */ -EPIPE, + /* DevNotResp */ -ETIMEDOUT, + /* PIDCheck */ -EPROTO, + /* UnExpPID */ -EPROTO, + /* DataOver */ -EOVERFLOW, + /* DataUnder */ -EREMOTEIO, + /* (for hw) */ -EIO, + /* (for hw) */ -EIO, + /* BufferOver */ -ECOMM, + /* BuffUnder */ -ENOSR, + /* (for HCD) */ -EALREADY, + /* (for HCD) */ -EALREADY +}; + + +/* ED States */ + +#define ED_NEW 0x00 /* unused, no dummy td */ +#define ED_UNLINK 0x01 /* dummy td, maybe linked to hc */ +#define ED_OPER 0x02 /* dummy td, _is_ linked to hc */ + +#define ED_URB_DEL 0x08 /* masked in */ + +/* usb_ohci_ed */ +struct ed { + /* first fields are hardware-specified */ + __u32 hwINFO; + __u32 hwTailP; + __u32 hwHeadP; + __u32 hwNextED; + + struct ed * ed_prev; + __u8 int_period; + __u8 int_branch; + __u8 int_load; + __u8 int_interval; + __u8 state; // ED_{NEW,UNLINK,OPER} + __u8 type; + __u16 last_iso; + struct ed * ed_rm_list; + + dma_addr_t dma; + __u32 unused [3]; +} __attribute((aligned(16))); + + +/* TD info field */ +#define TD_CC 0xf0000000 +#define TD_CC_GET(td_p) ((td_p >>28) & 0x0f) +#define TD_CC_SET(td_p, cc) (td_p) = ((td_p) & 0x0fffffff) | (((cc) & 0x0f) << 28) +#define TD_EC 0x0C000000 +#define TD_T 0x03000000 +#define TD_T_DATA0 0x02000000 +#define TD_T_DATA1 0x03000000 +#define TD_T_TOGGLE 0x00000000 +#define TD_R 0x00040000 +#define TD_DI 0x00E00000 +#define TD_DI_SET(X) (((X) & 0x07)<< 21) +#define TD_DP 0x00180000 +#define TD_DP_SETUP 0x00000000 +#define TD_DP_IN 0x00100000 +#define TD_DP_OUT 0x00080000 + +#define TD_ISO 0x00010000 +#define TD_DEL 0x00020000 + +/* CC Codes */ +#define TD_CC_NOERROR 0x00 +#define TD_CC_CRC 0x01 +#define TD_CC_BITSTUFFING 0x02 +#define TD_CC_DATATOGGLEM 0x03 +#define TD_CC_STALL 0x04 +#define TD_DEVNOTRESP 0x05 +#define TD_PIDCHECKFAIL 0x06 +#define TD_UNEXPECTEDPID 0x07 +#define TD_DATAOVERRUN 0x08 +#define TD_DATAUNDERRUN 0x09 + /* 0x0A, 0x0B reserved for hardware */ +#define TD_BUFFEROVERRUN 0x0C +#define TD_BUFFERUNDERRUN 0x0D + /* 0x0E, 0x0F reserved for HCD */ +#define TD_NOTACCESSED 0x0F + + +#define MAXPSW 1 + +struct td { + /* first hardware fields are in all tds */ + __u32 hwINFO; + __u32 hwCBP; /* Current Buffer Pointer */ + __u32 hwNextTD; /* Next TD Pointer */ + __u32 hwBE; /* Memory Buffer End Pointer */ + + __u16 hwPSW [MAXPSW]; /* PSW is only for ISO */ + + __u8 unused; + __u8 index; + struct ed *ed; + struct td *next_dl_td; + struct urb *urb; + + dma_addr_t td_dma; + dma_addr_t data_dma; + __u32 unused2 [2]; +} __attribute((aligned(32))); /* iso needs 32 */ + +#define OHCI_ED_SKIP (1 << 14) + +/* + * The HCCA (Host Controller Communications Area) is a 256 byte + * structure defined in the OHCI spec. The host controller is + * told the base address of it. It must be 256-byte aligned. + */ +#define NUM_INTS 32 /* part of the OHCI standard */ +struct ohci_hcca { + __u32 int_table [NUM_INTS]; /* Interrupt ED table */ + __u16 frame_no; /* current frame number */ + __u16 pad1; /* set to 0 on each frame_no change */ + __u32 done_head; /* info returned for an interrupt */ + u8 reserved_for_hc [116]; +} __attribute((aligned(256))); + + +/* + * Maximum number of root hub ports. + */ +#define MAX_ROOT_PORTS 15 /* maximum OHCI root hub ports */ + +/* + * This is the structure of the OHCI controller's memory mapped I/O + * region. This is Memory Mapped I/O. You must use the readl() and + * writel() macros defined in asm/io.h to access these!! + */ +struct ohci_regs { + /* control and status registers */ + __u32 revision; + __u32 control; + __u32 cmdstatus; + __u32 intrstatus; + __u32 intrenable; + __u32 intrdisable; + + /* memory pointers */ + __u32 hcca; + __u32 ed_periodcurrent; + __u32 ed_controlhead; + __u32 ed_controlcurrent; + __u32 ed_bulkhead; + __u32 ed_bulkcurrent; + __u32 donehead; + + /* frame counters */ + __u32 fminterval; + __u32 fmremaining; + __u32 fmnumber; + __u32 periodicstart; + __u32 lsthresh; + + /* Root hub ports */ + struct ohci_roothub_regs { + __u32 a; + __u32 b; + __u32 status; + __u32 portstatus [MAX_ROOT_PORTS]; + } roothub; + + /* and some optional registers for legacy compatibility */ +} __attribute((aligned(32))); + + +/* OHCI CONTROL AND STATUS REGISTER MASKS */ + +/* + * HcControl (control) register masks + */ +#define OHCI_CTRL_CBSR (3 << 0) /* control/bulk service ratio */ +#define OHCI_CTRL_PLE (1 << 2) /* periodic list enable */ +#define OHCI_CTRL_IE (1 << 3) /* isochronous enable */ +#define OHCI_CTRL_CLE (1 << 4) /* control list enable */ +#define OHCI_CTRL_BLE (1 << 5) /* bulk list enable */ +#define OHCI_CTRL_HCFS (3 << 6) /* host controller functional state */ +#define OHCI_CTRL_IR (1 << 8) /* interrupt routing */ +#define OHCI_CTRL_RWC (1 << 9) /* remote wakeup connected */ +#define OHCI_CTRL_RWE (1 << 10) /* remote wakeup enable */ + +/* pre-shifted values for HCFS */ +# define OHCI_USB_RESET (0 << 6) +# define OHCI_USB_RESUME (1 << 6) +# define OHCI_USB_OPER (2 << 6) +# define OHCI_USB_SUSPEND (3 << 6) + +/* + * HcCommandStatus (cmdstatus) register masks + */ +#define OHCI_HCR (1 << 0) /* host controller reset */ +#define OHCI_CLF (1 << 1) /* control list filled */ +#define OHCI_BLF (1 << 2) /* bulk list filled */ +#define OHCI_OCR (1 << 3) /* ownership change request */ +#define OHCI_SOC (3 << 16) /* scheduling overrun count */ + +/* + * masks used with interrupt registers: + * HcInterruptStatus (intrstatus) + * HcInterruptEnable (intrenable) + * HcInterruptDisable (intrdisable) + */ +#define OHCI_INTR_SO (1 << 0) /* scheduling overrun */ +#define OHCI_INTR_WDH (1 << 1) /* writeback of done_head */ +#define OHCI_INTR_SF (1 << 2) /* start frame */ +#define OHCI_INTR_RD (1 << 3) /* resume detect */ +#define OHCI_INTR_UE (1 << 4) /* unrecoverable error */ +#define OHCI_INTR_FNO (1 << 5) /* frame number overflow */ +#define OHCI_INTR_RHSC (1 << 6) /* root hub status change */ +#define OHCI_INTR_OC (1 << 30) /* ownership change */ +#define OHCI_INTR_MIE (1 << 31) /* master interrupt enable */ + + +/* OHCI ROOT HUB REGISTER MASKS */ + +/* roothub.portstatus [i] bits */ +#define RH_PS_CCS 0x00000001 /* current connect status */ +#define RH_PS_PES 0x00000002 /* port enable status*/ +#define RH_PS_PSS 0x00000004 /* port suspend status */ +#define RH_PS_POCI 0x00000008 /* port over current indicator */ +#define RH_PS_PRS 0x00000010 /* port reset status */ +#define RH_PS_PPS 0x00000100 /* port power status */ +#define RH_PS_LSDA 0x00000200 /* low speed device attached */ +#define RH_PS_CSC 0x00010000 /* connect status change */ +#define RH_PS_PESC 0x00020000 /* port enable status change */ +#define RH_PS_PSSC 0x00040000 /* port suspend status change */ +#define RH_PS_OCIC 0x00080000 /* over current indicator change */ +#define RH_PS_PRSC 0x00100000 /* port reset status change */ + +/* roothub.status bits */ +#define RH_HS_LPS 0x00000001 /* local power status */ +#define RH_HS_OCI 0x00000002 /* over current indicator */ +#define RH_HS_DRWE 0x00008000 /* device remote wakeup enable */ +#define RH_HS_LPSC 0x00010000 /* local power status change */ +#define RH_HS_OCIC 0x00020000 /* over current indicator change */ +#define RH_HS_CRWE 0x80000000 /* clear remote wakeup enable */ + +/* roothub.b masks */ +#define RH_B_DR 0x0000ffff /* device removable flags */ +#define RH_B_PPCM 0xffff0000 /* port power control mask */ + +/* roothub.a masks */ +#define RH_A_NDP (0xff << 0) /* number of downstream ports */ +#define RH_A_PSM (1 << 8) /* power switching mode */ +#define RH_A_NPS (1 << 9) /* no power switching */ +#define RH_A_DT (1 << 10) /* device type (mbz) */ +#define RH_A_OCPM (1 << 11) /* over current protection mode */ +#define RH_A_NOCP (1 << 12) /* no over current protection */ +#define RH_A_POTPGT (0xff << 24) /* power on to power good time */ + + +/* urb */ +typedef struct urb_priv +{ + struct ed *ed; + __u16 length; // # tds in this request + __u16 td_cnt; // tds already serviced + int state; + struct td *td [0]; // all TDs in this request + +} urb_priv_t; + +#define URB_DEL 1 + + +/* Hash struct used for TD/ED hashing */ +struct hash_t { + void *virt; + dma_addr_t dma; + struct hash_t *next; // chaining for collision cases +}; + +/* List of TD/ED hash entries */ +struct hash_list_t { + struct hash_t *head; + struct hash_t *tail; +}; + +#define TD_HASH_SIZE 64 /* power'o'two */ +#define ED_HASH_SIZE 64 /* power'o'two */ + +#define TD_HASH_FUNC(td_dma) ((td_dma ^ (td_dma >> 5)) % TD_HASH_SIZE) +#define ED_HASH_FUNC(ed_dma) ((ed_dma ^ (ed_dma >> 5)) % ED_HASH_SIZE) + + +/* + * This is the full ohci controller description + * + * Note how the "proper" USB information is just + * a subset of what the full implementation needs. (Linus) + */ + +struct ohci_hcd { + spinlock_t lock; + + /* + * I/O memory used to communicate with the HC (uncached); + */ + struct ohci_regs *regs; + + /* + * main memory used to communicate with the HC (uncached) + */ + struct ohci_hcca *hcca; + dma_addr_t hcca_dma; + + struct ed *ed_rm_list [2]; /* to be removed */ + + struct ed *ed_bulktail; /* last in bulk list */ + struct ed *ed_controltail; /* last in ctrl list */ + struct ed *ed_isotail; /* last in iso list */ + +#ifdef CONFIG_PCI + struct pci_pool *td_cache; + struct pci_pool *ed_cache; + struct hash_list_t td_hash [TD_HASH_SIZE]; + struct hash_list_t ed_hash [ED_HASH_SIZE]; +#endif + + /* + * driver state + */ + int disabled; /* e.g. got a UE, we're hung */ + int sleeping; + int ohci_int_load [NUM_INTS]; + u32 hc_control; /* copy of hc control reg */ + struct usb_device *dev [128]; + + unsigned long flags; /* for HC bugs */ +#define OHCI_QUIRK_AMD756 0x01 /* erratum #4 */ + + /* + * framework state + */ + struct usb_hcd hcd; +}; + +#define hcd_to_ohci(hcd_ptr) list_entry(hcd_ptr, struct ohci_hcd, hcd) + diff -Nru a/drivers/usb/hcd/Config.in b/drivers/usb/hcd/Config.in --- a/drivers/usb/hcd/Config.in Mon Jan 21 10:48:39 2002 +++ b/drivers/usb/hcd/Config.in Mon Jan 21 10:48:39 2002 @@ -2,6 +2,6 @@ # USB Host Controller Drivers # dep_tristate ' EHCI HCD (USB 2.0) support (EXPERIMENTAL)' CONFIG_USB_EHCI_HCD $CONFIG_USB $CONFIG_EXPERIMENTAL -# dep_tristate ' OHCI HCD support (EXPERIMENTAL)' CONFIG_USB_OHCI_HCD $CONFIG_USB $CONFIG_EXPERIMENTAL +dep_tristate ' OHCI HCD support (EXPERIMENTAL)' CONFIG_USB_OHCI_HCD $CONFIG_USB $CONFIG_EXPERIMENTAL # dep_tristate ' UHCI HCD (most Intel and VIA) support (EXPERIMENTAL)' CONFIG_USB_UHCI_HCD $CONFIG_USB $CONFIG_EXPERIMENTAL