Adds support for the ohci controllers found on the IBM STB04xxx and Freescale MPC52xx embedded chips. In addition to the low level hcd support added in drivers/usb/host/ohci-ocp.c, it contains code for 2 quirks of these OHCI implementations.
Quirk 1: The IBM and Freescale implementations differ on half of the 32-bit hcca->frame_no field contains the frame number. I created an inline ohci_frame_no function to centralize the handling of this quirk. Quirk 2: On both big-endian controllers, the order that the hardware accesses the td->hwPSW is strange. The problem is that while td->hwPSW is an array of u16, the OHCI spec (incorrectly, IMHO) describes it as an array of u32, with each element containing 2 u16 fields in little endian order. IBM and Freescale implemented according to the spec and the result is an array of big-endian u16 elements which the OHCI hardware uses in this order: hwPSW[1], hwPSW[0], hwPSW[3], hwPSW[2], etc. Since we only use one of the hwPSW array elements, we need to allocate two because the hardware accesses hwPSW[1]. I centralized the handling of this quirk in the ohci_hwPSW function. Signed-off-by: Dale Farnsworth <[EMAIL PROTECTED]> diff -Nru a/drivers/usb/Kconfig b/drivers/usb/Kconfig --- a/drivers/usb/Kconfig 2004-08-19 18:00:41 -07:00 +++ b/drivers/usb/Kconfig 2004-08-19 18:00:41 -07:00 @@ -7,7 +7,7 @@ # ARM SA1111 chips have a non-PCI based "OHCI-compatible" USB host interface. config USB tristate "Support for Host-side USB" - depends on PCI || SA1111 || ARCH_OMAP1510 || ARCH_OMAP1610 || ARCH_LH7A404 + depends on PCI || SA1111 || ARCH_OMAP1510 || ARCH_OMAP1610 || ARCH_LH7A404 || STB03xxx || PPC_MPC52xx ---help--- Universal Serial Bus (USB) is a specification for a serial bus subsystem which offers higher speeds and more features than the diff -Nru a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig --- a/drivers/usb/host/Kconfig 2004-08-19 18:00:41 -07:00 +++ b/drivers/usb/host/Kconfig 2004-08-19 18:00:41 -07:00 @@ -65,6 +65,32 @@ To compile this driver as a module, choose M here: the module will be called ohci-hcd. +config USB_OHCI_HCD_OCP + bool "OHCI support for on-chip USB controller" + depends on USB_OHCI_HCD && PPC_OCP + default y + ---help--- + Enables support for the USB controller on the Processor chip + If unsure, say Y. + +config USB_OHCI_HCD_PCI + bool "OHCI support for PCI-bus USB controllers" + depends on PCI && PPC_OCP + default y + ---help--- + Enables support for PCI-bus plug-in USB controller cards + If unsure, say Y. + +config USB_OHCI_BIG_ENDIAN + bool + depends on USB_OHCI_HCD_OCP + default y + +config USB_OHCI_LITTLE_ENDIAN + bool + depends on USB_OHCI_HCD_PCI + default y + config USB_UHCI_HCD tristate "UHCI HCD (most Intel and VIA) support" depends on USB diff -Nru a/drivers/usb/host/ohci-dbg.c b/drivers/usb/host/ohci-dbg.c --- a/drivers/usb/host/ohci-dbg.c 2004-08-19 18:00:41 -07:00 +++ b/drivers/usb/host/ohci-dbg.c 2004-08-19 18:00:41 -07:00 @@ -276,7 +276,7 @@ ohci_dump_status (controller, NULL, NULL); if (controller->hcca) ohci_dbg (controller, - "hcca frame #%04x\n", OHCI_FRAME_NO(controller->hcca)); + "hcca frame #%04x\n", ohci_frame_no(controller)); ohci_dump_roothub (controller, 1, NULL, NULL); } @@ -328,7 +328,7 @@ ohci32_to_cpup (ohci, &td->hwCBP) & ~0x0fff, ohci32_to_cpup (ohci, &td->hwBE)); for (i = 0; i < MAXPSW; i++) { - u16 psw = ohci16_to_cpup (ohci, &td->hwPSW [i]); + u16 psw = ohci_hwPSW(ohci, td, i); int cc = (psw >> 12) & 0x0f; ohci_dbg (ohci, " psw [%d] = %2x, CC=%x %s=%d\n", i, psw, cc, @@ -643,7 +643,7 @@ /* hcca */ if (ohci->hcca) ohci_dbg_sw (ohci, &next, &size, - "hcca frame 0x%04x\n", OHCI_FRAME_NO(ohci->hcca)); + "hcca frame 0x%04x\n", ohci_frame_no(ohci)); /* other registers mostly affect frame timings */ rdata = ohci_readl (ohci, ®s->fminterval); diff -Nru a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c --- a/drivers/usb/host/ohci-hcd.c 2004-08-19 18:00:41 -07:00 +++ b/drivers/usb/host/ohci-hcd.c 2004-08-19 18:00:41 -07:00 @@ -240,7 +240,7 @@ if (retval < 0) goto fail0; if (ed->type == PIPE_ISOCHRONOUS) { - u16 frame = OHCI_FRAME_NO(ohci->hcca); + u16 frame = ohci_frame_no(ohci); /* delay a few frames before the first TD */ frame += max_t (u16, 8, ed->interval); @@ -383,7 +383,7 @@ { struct ohci_hcd *ohci = hcd_to_ohci (hcd); - return OHCI_FRAME_NO(ohci->hcca); + return ohci_frame_no(ohci); } /*-------------------------------------------------------------------------* @@ -646,7 +646,7 @@ */ spin_lock (&ohci->lock); if (ohci->ed_rm_list) - finish_unlinks (ohci, OHCI_FRAME_NO(ohci->hcca), + finish_unlinks (ohci, ohci_frame_no(ohci), ptregs); if ((ints & OHCI_INTR_SF) != 0 && !ohci->ed_rm_list && HCD_IS_RUNNING(ohci->hcd.state)) @@ -811,10 +811,15 @@ #include "ohci-lh7a404.c" #endif +#ifdef CONFIG_USB_OHCI_HCD_OCP +#include "ohci-ocp.c" +#endif + #if !(defined(CONFIG_PCI) \ || defined(CONFIG_SA1111) \ || defined(CONFIG_ARCH_OMAP) \ || defined (CONFIG_ARCH_LH7A404) \ + || defined (CONFIG_USB_OHCI_HCD_OCP) \ ) #error "missing bus glue for ohci-hcd" #endif diff -Nru a/drivers/usb/host/ohci-hub.c b/drivers/usb/host/ohci-hub.c --- a/drivers/usb/host/ohci-hub.c 2004-08-19 18:00:41 -07:00 +++ b/drivers/usb/host/ohci-hub.c 2004-08-19 18:00:41 -07:00 @@ -128,7 +128,7 @@ mdelay (7); } dl_done_list (ohci, NULL); - finish_unlinks (ohci, OHCI_FRAME_NO(ohci->hcca), NULL); + finish_unlinks (ohci, ohci_frame_no(ohci), NULL); ohci_writel (ohci, ohci_readl (ohci, &ohci->regs->intrstatus), &ohci->regs->intrstatus); diff -Nru a/drivers/usb/host/ohci-ocp.c b/drivers/usb/host/ohci-ocp.c --- /dev/null Wed Dec 31 16:00:00 196900 +++ b/drivers/usb/host/ohci-ocp.c 2004-08-19 18:00:41 -07:00 @@ -0,0 +1,313 @@ +/* + * OHCI HCD (Host Controller Driver) for USB. + * + * (C) Copyright 1999 Roman Weissgaerber <[EMAIL PROTECTED]> + * (C) Copyright 2000-2002 David Brownell <[EMAIL PROTECTED]> + * (C) Copyright 2002 Hewlett-Packard Company + * (C) Copyright 2003 MontaVista Software Inc. + * + * Bus Glue for PPC OCP driver + * + * Modified for ocp from ohci-sa1111.c + * by Dale Farnsworth <[EMAIL PROTECTED]> + * + * This file is licenced under the GPL. + */ + +#define DEBUG + +#include <asm/ocp.h> + +/* + * Hardware-specific initialization + */ +static void ocp_start_hc(struct ocp_device *ocp) +{ + printk(KERN_DEBUG __FILE__ ": starting OCP OHCI USB Controller\n"); +} + +/* + * Hardware-specific cleanup + */ +static void ocp_stop_hc(struct ocp_device *ocp) +{ + printk(KERN_DEBUG __FILE__ ": stopping OCP OHCI USB Controller\n"); +} + +void usb_hcd_ocp_remove (struct usb_hcd *, struct ocp_device *); + +/* configure so an HC device and id are always provided */ +/* always called with process context; sleeping is OK */ + +/** + * usb_hcd_ocp_probe - initialize OCP-based HCDs + * Context: !in_interrupt() + * + * Allocates basic resources for this USB host controller, and + * then invokes the start() method for the HCD associated with it + * through the hotplug entry's driver_data. + * + * Store this function in the HCD's struct pci_driver as probe(). + */ +int usb_hcd_ocp_probe (const struct hc_driver *driver, + struct usb_hcd **hcd_out, + struct ocp_device *ocp) +{ + int retval; + struct usb_hcd *hcd = 0; + struct ohci_hcd *ohci; + phys_addr_t addr = ocp->def->paddr; /* really a vaddr */ + static u64 ocp_dma_mask = 0xffffffffULL; + + if (!request_mem_region (addr, sizeof (struct ohci_regs), + driver->description)) { + dbg("request_mem_region failed"); + return -EBUSY; + } + + ocp_start_hc(ocp); + + hcd = driver->hcd_alloc (); + if (hcd == NULL){ + dbg ("hcd_alloc failed"); + retval = -ENOMEM; + goto err1; + } + + ohci = hcd_to_ohci (hcd); + ohci->flags |= OHCI_BIG_ENDIAN; + + hcd->driver = (struct hc_driver *) driver; + hcd->description = driver->description; + hcd->irq = ocp->def->irq; + hcd->regs = (struct ohci_regs *) addr; + hcd->self.controller = &ocp->dev; + + hcd->self.controller->dma_mask = &ocp_dma_mask; + hcd->self.controller->coherent_dma_mask = 0xffffffffULL; + + retval = hcd_buffer_create (hcd); + if (retval != 0) { + dbg ("pool alloc fail"); + goto err1; + } + + retval = request_irq (hcd->irq, usb_hcd_irq, SA_INTERRUPT, + hcd->description, hcd); + if (retval != 0) { + dbg("request_irq failed"); + retval = -EBUSY; + goto err2; + } + + dbg ("%s (OCP OHCI) at 0x%p, irq %d", + hcd->description, hcd->regs, hcd->irq); + + usb_bus_init (&hcd->self); + hcd->self.op = &usb_hcd_operations; + hcd->self.hcpriv = (void *) hcd; + hcd->self.bus_name = "ocp"; + + INIT_LIST_HEAD (&hcd->dev_list); + + usb_register_bus (&hcd->self); + + if ((retval = driver->start (hcd)) < 0) + { + usb_hcd_ocp_remove(hcd, ocp); + return retval; + } + + *hcd_out = hcd; + return 0; + + err2: + hcd_buffer_destroy (hcd); + if (hcd) + driver->hcd_free(hcd); + err1: + ocp_stop_hc(ocp); + release_mem_region(addr, sizeof (struct ohci_regs)); + return retval; +} + + +/* may be called without controller electrically present */ +/* may be called with controller, bus, and devices active */ + +/** + * usb_hcd_ocp_remove - shutdown processing for OCP-based HCDs + * @dev: USB Host Controller being removed + * Context: !in_interrupt() + * + * Reverses the effect of usb_hcd_ocp_probe(), first invoking + * the HCD's stop() method. It is always called from a thread + * context, normally "rmmod", "apmd", or something similar. + * + */ +void usb_hcd_ocp_remove (struct usb_hcd *hcd, struct ocp_device *ocp) +{ + dbg ("remove: %s, state %x", hcd->self.bus_name, hcd->state); + + if (in_interrupt ()) + BUG (); + + hcd->state = USB_STATE_QUIESCING; + + dbg ("%s: roothub graceful disconnect", hcd->self.bus_name); + usb_disconnect (&hcd->self.root_hub); + + hcd->driver->stop (hcd); + hcd->state = USB_STATE_HALT; + + free_irq (hcd->irq, hcd); + hcd_buffer_destroy (hcd); + + usb_deregister_bus (&hcd->self); + + hcd->driver->hcd_free (hcd); + + ocp_stop_hc(ocp); + release_mem_region(ocp->def->paddr, sizeof (struct ohci_regs)); +} + +static int __devinit +ohci_ocp_start (struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci (hcd); + int ret; + + ohci_dbg (ohci, "ohci_ocp_start, ohci:%p", ohci); + + ohci->hcca = dma_alloc_coherent (hcd->self.controller, + sizeof *ohci->hcca, &ohci->hcca_dma, 0); + if (!ohci->hcca) + return -ENOMEM; + + ohci_dbg (ohci, "ohci_ocp_start, ohci->hcca:%p", ohci->hcca); + + 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.self.bus_name); + ohci_stop (hcd); + return -EBUSY; + } + create_debug_files (ohci); + +#ifdef DEBUG + ohci_dump (ohci, 1); +#endif + return 0; +} + +static const struct hc_driver ohci_ocp_hc_driver = { + .description = "ocp_ohci", + + /* + * generic hardware linkage + */ + .irq = ohci_irq, + .flags = HCD_USB11, + + /* + * basic lifecycle operations + */ + .start = ohci_ocp_start, +#ifdef CONFIG_PM + /* suspend: ohci_ocp_suspend, -- tbd */ + /* resume: ohci_ocp_resume, -- tbd */ +#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, + .endpoint_disable = ohci_endpoint_disable, + + /* + * scheduling support + */ + .get_frame_number = ohci_get_frame, + + /* + * root hub support + */ + .hub_status_data = ohci_hub_status_data, + .hub_control = ohci_hub_control, +}; + +static int ohci_hcd_ocp_drv_probe(struct ocp_device *ocp) +{ + struct usb_hcd *hcd = NULL; + int ret; + + if (usb_disabled()) + return -ENODEV; + + ret = usb_hcd_ocp_probe(&ohci_ocp_hc_driver, &hcd, ocp); + + if (ret == 0) + dev_set_drvdata(&ocp->dev, hcd); + + return ret; +} + +static void ohci_hcd_ocp_drv_remove(struct ocp_device *ocp) +{ + struct usb_hcd *hcd = dev_get_drvdata(&ocp->dev); + + usb_hcd_ocp_remove(hcd, ocp); + + dev_set_drvdata(&ocp->dev, NULL); +} + +static struct ocp_device_id ohci_hcd_ocp_ids[] __devinitdata = { + { .vendor = OCP_VENDOR_IBM, .function = OCP_FUNC_USB }, + { .vendor = OCP_VENDOR_FREESCALE, .function = OCP_FUNC_USB }, + { .vendor = OCP_VENDOR_INVALID /* Terminating entry */ } +} + +MODULE_DEVICE_TABLE(ocp, ohci_hcd_ocp_ids); + +static struct ocp_driver ohci_hcd_ocp_driver = { + .name = "ocp-ohci", + .id_table = ohci_hcd_ocp_ids, + .probe = ohci_hcd_ocp_drv_probe, + .remove = ohci_hcd_ocp_drv_remove, +}; + +static int __init ohci_hcd_ocp_init (void) +{ + dbg (DRIVER_INFO " (OCP)"); + dbg ("block sizes: ed %d td %d", + sizeof (struct ed), sizeof (struct td)); + + return ocp_register_driver(&ohci_hcd_ocp_driver); +} + +static void __exit ohci_hcd_ocp_cleanup (void) +{ + ocp_unregister_driver(&ohci_hcd_ocp_driver); +} + +module_init (ohci_hcd_ocp_init); +module_exit (ohci_hcd_ocp_cleanup); diff -Nru a/drivers/usb/host/ohci-q.c b/drivers/usb/host/ohci-q.c --- a/drivers/usb/host/ohci-q.c 2004-08-19 18:00:41 -07:00 +++ b/drivers/usb/host/ohci-q.c 2004-08-19 18:00:41 -07:00 @@ -496,7 +496,7 @@ * behave. frame_no wraps every 2^16 msec, and changes right before * SF is triggered. */ - ed->tick = OHCI_FRAME_NO(ohci->hcca) + 1; + ed->tick = ohci_frame_no(ohci) + 1; } @@ -551,7 +551,7 @@ td->hwINFO = cpu_to_ohci32 (ohci, info); if (is_iso) { td->hwCBP = cpu_to_ohci32 (ohci, data & 0xFFFFF000); - td->hwPSW [0] = cpu_to_ohci16 (ohci, (data & 0x0FFF) | 0xE000); + *ohci_hwPSWp(ohci, td, 0) = cpu_to_ohci16 (ohci, (data & 0x0FFF) | 0xE000); td->ed->last_iso = info & 0xffff; } else { td->hwCBP = cpu_to_ohci32 (ohci, data); @@ -723,7 +723,7 @@ /* ISO ... drivers see per-TD length/status */ if (tdINFO & TD_ISO) { - u16 tdPSW = ohci16_to_cpu (ohci, td->hwPSW [0]); + u16 tdPSW = ohci_hwPSW(ohci, td, 0); int dlen = 0; /* NOTE: assumes FC in tdINFO == 0 (and MAXPSW == 1) */ diff -Nru a/drivers/usb/host/ohci.h b/drivers/usb/host/ohci.h --- a/drivers/usb/host/ohci.h 2004-08-19 18:00:41 -07:00 +++ b/drivers/usb/host/ohci.h 2004-08-19 18:00:41 -07:00 @@ -105,7 +105,11 @@ __u32 hwBE; /* Memory Buffer End Pointer */ /* PSW is only for ISO */ -#define MAXPSW 1 /* hardware allows 8 */ +/* + * Only 1 PSW entry is used, but on IBM ocp hardware, it is + * the second entry, so we need space for 2. + */ +#define MAXPSW (1 + 1) /* hardware allows 8 */ __u16 hwPSW [MAXPSW]; /* rest are purely for the driver's use */ @@ -176,10 +180,9 @@ /* * OHCI defines u16 frame_no, followed by u16 zero pad. * Since some processors can't do 16 bit bus accesses, - * portable access must be a 32 bit byteswapped access. + * portable access must be a 32 bit access. */ u32 frame_no; /* current frame number */ -#define OHCI_FRAME_NO(hccap) ((u16)le32_to_cpup(&(hccap)->frame_no)) __u32 done_head; /* info returned for an interrupt */ u8 reserved_for_hc [116]; u8 what [4]; /* spec only identifies 252 bytes :) */ @@ -506,6 +509,32 @@ const __u32 *x) { return big_endian(ohci) ? be32_to_cpup(x) : le32_to_cpup(x); +} + +#ifdef CONFIG_PPC_MPC52xx +#define FRAME_NO_SHIFT 0 +#else +#define FRAME_NO_SHIFT 16 +#endif + +static inline __u16 ohci_frame_no(const struct ohci_hcd *ohci) +{ + return (__u16)(big_endian(ohci) ? + be32_to_cpup(&ohci->hcca->frame_no) >> FRAME_NO_SHIFT : + le32_to_cpup(&ohci->hcca->frame_no)); +} + +static inline __u16 *ohci_hwPSWp(const struct ohci_hcd *ohci, + const struct td *td, int index) +{ + return (__u16 *)(big_endian(ohci) ? + &td->hwPSW[index ^ 1] : &td->hwPSW[index]); +} + +static inline __u16 ohci_hwPSW(const struct ohci_hcd *ohci, + const struct td *td, int index) +{ + return ohci16_to_cpup(ohci, ohci_hwPSWp(ohci, td, index)); } /*-------------------------------------------------------------------------*/ ------------------------------------------------------- SF.Net email is sponsored by Shop4tech.com-Lowest price on Blank Media 100pk Sonic DVD-R 4x for only $29 -100pk Sonic DVD+R for only $33 Save 50% off Retail on Ink & Toner - Free Shipping and Free Gift. http://www.shop4tech.com/z/Inkjet_Cartridges/9_108_r285 _______________________________________________ [EMAIL PROTECTED] To unsubscribe, use the last form field at: https://lists.sourceforge.net/lists/listinfo/linux-usb-devel