--- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -230,6 +230,19 @@ config USB_OXU210HP_HCD To compile this driver as a module, choose M here: the module will be called oxu210hp-hcd. +config USB_EHCI_HCD_SSB + bool "EHCI support for Broadcom SSB EHCI core" + depends on USB_EHCI_HCD && (SSB = y || SSB = USB_EHCI_HCD) && EXPERIMENTAL + default n + ---help--- + Support for the Sonics Silicon Backplane (SSB) attached + Broadcom USB EHCI core. + + This device is present in some embedded devices with + Broadcom based SSB bus. + + If unsure, say N. + config USB_ISP116X_HCD tristate "ISP116X HCD support" depends on USB --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -1286,9 +1286,14 @@ MODULE_LICENSE ("GPL"); #define PLATFORM_DRIVER ehci_grlib_driver #endif +#ifdef CONFIG_USB_EHCI_HCD_SSB +#include "ehci-ssb.c" +#define SSB_EHCI_DRIVER ssb_ehci_driver +#endif + #if !defined(PCI_DRIVER) && !defined(PLATFORM_DRIVER) && \ !defined(PS3_SYSTEM_BUS_DRIVER) && !defined(OF_PLATFORM_DRIVER) && \ - !defined(XILINX_OF_PLATFORM_DRIVER) + !defined(XILINX_OF_PLATFORM_DRIVER) && !defined(SSB_EHCI_DRIVER) #error "missing bus glue for ehci-hcd" #endif @@ -1348,10 +1353,20 @@ static int __init ehci_hcd_init(void) if (retval < 0) goto clean4; #endif + +#ifdef SSB_EHCI_DRIVER + retval = ssb_driver_register(&SSB_EHCI_DRIVER); + if (retval < 0) + goto clean5; +#endif return retval; +#ifdef SSB_EHCI_DRIVER + /* ssb_driver_unregister(&SSB_EHCI_DRIVER); */ +clean5: +#endif #ifdef XILINX_OF_PLATFORM_DRIVER - /* platform_driver_unregister(&XILINX_OF_PLATFORM_DRIVER); */ + platform_driver_unregister(&XILINX_OF_PLATFORM_DRIVER); clean4: #endif #ifdef OF_PLATFORM_DRIVER @@ -1382,6 +1397,9 @@ module_init(ehci_hcd_init); static void __exit ehci_hcd_cleanup(void) { +#ifdef SSB_EHCI_DRIVER + ssb_driver_unregister(&SSB_EHCI_DRIVER); +#endif #ifdef XILINX_OF_PLATFORM_DRIVER platform_driver_unregister(&XILINX_OF_PLATFORM_DRIVER); #endif --- /dev/null +++ b/drivers/usb/host/ehci-ssb.c @@ -0,0 +1,255 @@ +/* + * Sonics Silicon Backplane + * Broadcom USB-core EHCI driver (SSB bus glue) + * + * Copyright 2007 Steven Brown + * Copyright 2010 Hauke Mehrtens + * + * Derived from the OHCI-SSB driver + * Copyright 2007 Michael Buesch + * + * Derived from the EHCI-PCI driver + * Copyright (c) 2000-2004 by David Brownell + * + * Derived from the OHCI-PCI driver + * Copyright 1999 Roman Weissgaerber + * Copyright 2000-2002 David Brownell + * Copyright 1999 Linus Torvalds + * Copyright 1999 Gregory P. Smith + * + * Derived from the USBcore related parts of Broadcom-SB + * Copyright 2005 Broadcom Corporation + * + * Licensed under the GNU/GPL. See COPYING for details. + */ +#include + + +struct ssb_ehci_device { + struct ehci_hcd ehci; /* _must_ be at the beginning. */ +}; + +static inline +struct ssb_ehci_device *hcd_to_ssb_ehci(struct usb_hcd *hcd) +{ + return (struct ssb_ehci_device *)(hcd->hcd_priv); +} + +static int ssb_ehci_reset(struct usb_hcd *hcd) +{ + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + int err; + + ehci->caps = hcd->regs; + ehci->regs = hcd->regs + + HC_LENGTH(ehci, ehci_readl(ehci, &ehci->caps->hc_capbase)); + + dbg_hcs_params(ehci, "reset"); + dbg_hcc_params(ehci, "reset"); + + ehci->hcs_params = ehci_readl(ehci, &ehci->caps->hcs_params); + + err = ehci_halt(ehci); + + if (err) + return err; + + err = ehci_init(hcd); + + if (err) + return err; + + ehci_reset(ehci); + + return err; +} + +static const struct hc_driver ssb_ehci_hc_driver = { + .description = "ssb-usb-ehci", + .product_desc = "SSB EHCI Controller", + .hcd_priv_size = sizeof(struct ssb_ehci_device), + + .irq = ehci_irq, + .flags = HCD_MEMORY | HCD_USB2, + + .reset = ssb_ehci_reset, + .start = ehci_run, + .stop = ehci_stop, + .shutdown = ehci_shutdown, + + .urb_enqueue = ehci_urb_enqueue, + .urb_dequeue = ehci_urb_dequeue, + .endpoint_disable = ehci_endpoint_disable, + .endpoint_reset = ehci_endpoint_reset, + + .get_frame_number = ehci_get_frame, + + .hub_status_data = ehci_hub_status_data, + .hub_control = ehci_hub_control, +#if defined(CONFIG_PM) + .bus_suspend = ehci_bus_suspend, + .bus_resume = ehci_bus_resume, +#endif + .relinquish_port = ehci_relinquish_port, + .port_handed_over = ehci_port_handed_over, + + .clear_tt_buffer_complete = ehci_clear_tt_buffer_complete, +}; + +static void ssb_ehci_detach(struct ssb_device *dev) +{ + struct usb_hcd *hcd = ssb_get_drvdata(dev); + + if (hcd->driver->shutdown) + hcd->driver->shutdown(hcd); + usb_remove_hcd(hcd); + iounmap(hcd->regs); + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); + usb_put_hcd(hcd); + ssb_device_disable(dev, 0); +} + +static int ssb_ehci_attach(struct ssb_device *dev) +{ + struct ssb_ehci_device *ehcidev; + struct usb_hcd *hcd; + int err = -ENOMEM; + u32 tmp; + + if (dma_set_mask(dev->dma_dev, DMA_BIT_MASK(32)) || + dma_set_coherent_mask(dev->dma_dev, DMA_BIT_MASK(32))) + return -EOPNOTSUPP; + + /* + * USB 2.0 special considerations: + * + * In addition to the standard SSB reset sequence, the Host Control + * Register must be programmed to bring the USB core and various phy + * components out of reset. + */ + ssb_device_enable(dev, 0); + ssb_write32(dev, 0x200, 0x7ff); + + /* Change Flush control reg */ + tmp = ssb_read32(dev, 0x400); + tmp &= ~8; + ssb_write32(dev, 0x400, tmp); + tmp = ssb_read32(dev, 0x400); + + /* Change Shim control reg */ + tmp = ssb_read32(dev, 0x304); + tmp &= ~0x100; + ssb_write32(dev, 0x304, tmp); + tmp = ssb_read32(dev, 0x304); + + udelay(1); + + /* Work around for 5354 failures */ + if (dev->id.revision == 2 && dev->bus->chip_id == 0x5354) { + /* Change syn01 reg */ + tmp = 0x00fe00fe; + ssb_write32(dev, 0x894, tmp); + + /* Change syn03 reg */ + tmp = ssb_read32(dev, 0x89c); + tmp |= 0x1; + ssb_write32(dev, 0x89c, tmp); + } + + hcd = usb_create_hcd(&ssb_ehci_hc_driver, dev->dev, + dev_name(dev->dev)); + if (!hcd) + goto err_dev_disable; + + ehcidev = hcd_to_ssb_ehci(hcd); + tmp = ssb_read32(dev, SSB_ADMATCH0); + hcd->rsrc_start = ssb_admatch_base(tmp) + 0x800; /* ehci core offset */ + hcd->rsrc_len = 0x100; /* ehci reg block size */ + /* + * start & size modified per sbutils.c + */ + hcd->regs = ioremap_nocache(hcd->rsrc_start, hcd->rsrc_len); + if (!hcd->regs) + goto err_put_hcd; + err = usb_add_hcd(hcd, dev->irq, IRQF_DISABLED | IRQF_SHARED); + if (err) + goto err_iounmap; + + ssb_set_drvdata(dev, hcd); + + return err; + +err_iounmap: + iounmap(hcd->regs); +err_put_hcd: + usb_put_hcd(hcd); +err_dev_disable: + ssb_device_disable(dev, 0); + return err; +} + +static int ssb_ehci_probe(struct ssb_device *dev, + const struct ssb_device_id *id) +{ + int err; + u16 chipid_top; + + /* USBcores are only connected on embedded devices. */ + chipid_top = (dev->bus->chip_id & 0xFF00); + if (chipid_top != 0x4700 && chipid_top != 0x5300) + return -ENODEV; + + /* TODO: Probably need checks here; is the core connected? */ + + if (usb_disabled()) + return -ENODEV; + + err = ssb_ehci_attach(dev); + + return err; +} + +static void ssb_ehci_remove(struct ssb_device *dev) +{ + ssb_ehci_detach(dev); +} + +#ifdef CONFIG_PM + +static int ssb_ehci_suspend(struct ssb_device *dev, pm_message_t state) +{ + ssb_device_disable(dev, 0); + + return 0; +} + +static int ssb_ehci_resume(struct ssb_device *dev) +{ + struct usb_hcd *hcd = ssb_get_drvdata(dev); + struct ssb_ehci_device *ehcidev = hcd_to_ssb_ehci(hcd); + + ssb_device_enable(dev, 0); + + ehci_finish_controller_resume(hcd); + return 0; +} + +#else /* !CONFIG_PM */ +#define ssb_ehci_suspend NULL +#define ssb_ehci_resume NULL +#endif /* CONFIG_PM */ + +static const struct ssb_device_id ssb_ehci_table[] = { + SSB_DEVICE(SSB_VENDOR_BROADCOM, SSB_DEV_USB20_HOST, SSB_ANY_REV), + SSB_DEVTABLE_END +}; +MODULE_DEVICE_TABLE(ssb, ssb_ehci_table); + +static struct ssb_driver ssb_ehci_driver = { + .name = KBUILD_MODNAME, + .id_table = ssb_ehci_table, + .probe = ssb_ehci_probe, + .remove = ssb_ehci_remove, + .suspend = ssb_ehci_suspend, + .resume = ssb_ehci_resume, +};