| /* | |
| * PMC MSP EHCI (Host Controller Driver) for USB. | |
| * | |
| * (C) Copyright 2006-2010 PMC-Sierra Inc | |
| * | |
| * This file is subject to the terms and conditions of the GNU General Public | |
| * License. See the file "COPYING" in the main directory of this archive | |
| * for more details. | |
| * | |
| */ | |
| /* includes */ | |
| #include <linux/platform_device.h> | |
| #include <linux/gpio.h> | |
| #include <linux/usb.h> | |
| #include <msp_usb.h> | |
| /* stream disable*/ | |
| #define USB_CTRL_MODE_STREAM_DISABLE 0x10 | |
| /* threshold */ | |
| #define USB_CTRL_FIFO_THRESH 0x00300000 | |
| /* register offset for usb_mode */ | |
| #define USB_EHCI_REG_USB_MODE 0x68 | |
| /* register offset for usb fifo */ | |
| #define USB_EHCI_REG_USB_FIFO 0x24 | |
| /* register offset for usb status */ | |
| #define USB_EHCI_REG_USB_STATUS 0x44 | |
| /* serial/parallel transceiver */ | |
| #define USB_EHCI_REG_BIT_STAT_STS (1<<29) | |
| /* TWI USB0 host device pin */ | |
| #define MSP_PIN_USB0_HOST_DEV 49 | |
| /* TWI USB1 host device pin */ | |
| #define MSP_PIN_USB1_HOST_DEV 50 | |
| static void usb_hcd_tdi_set_mode(struct ehci_hcd *ehci) | |
| { | |
| u8 *base; | |
| u8 *statreg; | |
| u8 *fiforeg; | |
| u32 val; | |
| struct ehci_regs *reg_base = ehci->regs; | |
| /* get register base */ | |
| base = (u8 *)reg_base + USB_EHCI_REG_USB_MODE; | |
| statreg = (u8 *)reg_base + USB_EHCI_REG_USB_STATUS; | |
| fiforeg = (u8 *)reg_base + USB_EHCI_REG_USB_FIFO; | |
| /* Disable controller mode stream */ | |
| val = ehci_readl(ehci, (u32 *)base); | |
| ehci_writel(ehci, (val | USB_CTRL_MODE_STREAM_DISABLE), | |
| (u32 *)base); | |
| /* clear STS to select parallel transceiver interface */ | |
| val = ehci_readl(ehci, (u32 *)statreg); | |
| val = val & ~USB_EHCI_REG_BIT_STAT_STS; | |
| ehci_writel(ehci, val, (u32 *)statreg); | |
| /* write to set the proper fifo threshold */ | |
| ehci_writel(ehci, USB_CTRL_FIFO_THRESH, (u32 *)fiforeg); | |
| /* set TWI GPIO USB_HOST_DEV pin high */ | |
| gpio_direction_output(MSP_PIN_USB0_HOST_DEV, 1); | |
| } | |
| /* called during probe() after chip reset completes */ | |
| static int ehci_msp_setup(struct usb_hcd *hcd) | |
| { | |
| struct ehci_hcd *ehci = hcd_to_ehci(hcd); | |
| int retval; | |
| ehci->big_endian_mmio = 1; | |
| ehci->big_endian_desc = 1; | |
| ehci->caps = hcd->regs; | |
| hcd->has_tt = 1; | |
| retval = ehci_setup(hcd); | |
| if (retval) | |
| return retval; | |
| usb_hcd_tdi_set_mode(ehci); | |
| return retval; | |
| } | |
| /* configure so an HC device and id are always provided | |
| * always called with process context; sleeping is OK | |
| */ | |
| static int usb_hcd_msp_map_regs(struct mspusb_device *dev) | |
| { | |
| struct resource *res; | |
| struct platform_device *pdev = &dev->dev; | |
| u32 res_len; | |
| int retval; | |
| /* MAB register space */ | |
| res = platform_get_resource(pdev, IORESOURCE_MEM, 1); | |
| if (res == NULL) | |
| return -ENOMEM; | |
| res_len = resource_size(res); | |
| if (!request_mem_region(res->start, res_len, "mab regs")) | |
| return -EBUSY; | |
| dev->mab_regs = ioremap_nocache(res->start, res_len); | |
| if (dev->mab_regs == NULL) { | |
| retval = -ENOMEM; | |
| goto err1; | |
| } | |
| /* MSP USB register space */ | |
| res = platform_get_resource(pdev, IORESOURCE_MEM, 2); | |
| if (res == NULL) { | |
| retval = -ENOMEM; | |
| goto err2; | |
| } | |
| res_len = resource_size(res); | |
| if (!request_mem_region(res->start, res_len, "usbid regs")) { | |
| retval = -EBUSY; | |
| goto err2; | |
| } | |
| dev->usbid_regs = ioremap_nocache(res->start, res_len); | |
| if (dev->usbid_regs == NULL) { | |
| retval = -ENOMEM; | |
| goto err3; | |
| } | |
| return 0; | |
| err3: | |
| res = platform_get_resource(pdev, IORESOURCE_MEM, 2); | |
| res_len = resource_size(res); | |
| release_mem_region(res->start, res_len); | |
| err2: | |
| iounmap(dev->mab_regs); | |
| err1: | |
| res = platform_get_resource(pdev, IORESOURCE_MEM, 1); | |
| res_len = resource_size(res); | |
| release_mem_region(res->start, res_len); | |
| dev_err(&pdev->dev, "Failed to map non-EHCI regs.\n"); | |
| return retval; | |
| } | |
| /** | |
| * usb_hcd_msp_probe - initialize PMC MSP-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. | |
| * | |
| */ | |
| int usb_hcd_msp_probe(const struct hc_driver *driver, | |
| struct platform_device *dev) | |
| { | |
| int retval; | |
| struct usb_hcd *hcd; | |
| struct resource *res; | |
| struct ehci_hcd *ehci ; | |
| hcd = usb_create_hcd(driver, &dev->dev, "pmcmsp"); | |
| if (!hcd) | |
| return -ENOMEM; | |
| res = platform_get_resource(dev, IORESOURCE_MEM, 0); | |
| if (res == NULL) { | |
| pr_debug("No IOMEM resource info for %s.\n", dev->name); | |
| retval = -ENOMEM; | |
| goto err1; | |
| } | |
| hcd->rsrc_start = res->start; | |
| hcd->rsrc_len = resource_size(res); | |
| if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, dev->name)) { | |
| retval = -EBUSY; | |
| goto err1; | |
| } | |
| hcd->regs = ioremap_nocache(hcd->rsrc_start, hcd->rsrc_len); | |
| if (!hcd->regs) { | |
| pr_debug("ioremap failed"); | |
| retval = -ENOMEM; | |
| goto err2; | |
| } | |
| res = platform_get_resource(dev, IORESOURCE_IRQ, 0); | |
| if (res == NULL) { | |
| dev_err(&dev->dev, "No IRQ resource info for %s.\n", dev->name); | |
| retval = -ENOMEM; | |
| goto err3; | |
| } | |
| /* Map non-EHCI register spaces */ | |
| retval = usb_hcd_msp_map_regs(to_mspusb_device(dev)); | |
| if (retval != 0) | |
| goto err3; | |
| ehci = hcd_to_ehci(hcd); | |
| ehci->big_endian_mmio = 1; | |
| ehci->big_endian_desc = 1; | |
| retval = usb_add_hcd(hcd, res->start, IRQF_SHARED); | |
| if (retval == 0) { | |
| device_wakeup_enable(hcd->self.controller); | |
| return 0; | |
| } | |
| usb_remove_hcd(hcd); | |
| err3: | |
| iounmap(hcd->regs); | |
| err2: | |
| release_mem_region(hcd->rsrc_start, hcd->rsrc_len); | |
| err1: | |
| usb_put_hcd(hcd); | |
| return retval; | |
| } | |
| /** | |
| * usb_hcd_msp_remove - shutdown processing for PMC MSP-based HCDs | |
| * @dev: USB Host Controller being removed | |
| * Context: !in_interrupt() | |
| * | |
| * Reverses the effect of usb_hcd_msp_probe(), first invoking | |
| * the HCD's stop() method. It is always called from a thread | |
| * context, normally "rmmod", "apmd", or something similar. | |
| * | |
| * may be called without controller electrically present | |
| * may be called with controller, bus, and devices active | |
| */ | |
| void usb_hcd_msp_remove(struct usb_hcd *hcd, struct platform_device *dev) | |
| { | |
| usb_remove_hcd(hcd); | |
| iounmap(hcd->regs); | |
| release_mem_region(hcd->rsrc_start, hcd->rsrc_len); | |
| usb_put_hcd(hcd); | |
| } | |
| static const struct hc_driver ehci_msp_hc_driver = { | |
| .description = hcd_name, | |
| .product_desc = "PMC MSP EHCI", | |
| .hcd_priv_size = sizeof(struct ehci_hcd), | |
| /* | |
| * generic hardware linkage | |
| */ | |
| .irq = ehci_irq, | |
| .flags = HCD_MEMORY | HCD_USB2 | HCD_BH, | |
| /* | |
| * basic lifecycle operations | |
| */ | |
| .reset = ehci_msp_setup, | |
| .shutdown = ehci_shutdown, | |
| .start = ehci_run, | |
| .stop = ehci_stop, | |
| /* | |
| * managing i/o requests and associated device resources | |
| */ | |
| .urb_enqueue = ehci_urb_enqueue, | |
| .urb_dequeue = ehci_urb_dequeue, | |
| .endpoint_disable = ehci_endpoint_disable, | |
| .endpoint_reset = ehci_endpoint_reset, | |
| /* | |
| * scheduling support | |
| */ | |
| .get_frame_number = ehci_get_frame, | |
| /* | |
| * root hub support | |
| */ | |
| .hub_status_data = ehci_hub_status_data, | |
| .hub_control = ehci_hub_control, | |
| .bus_suspend = ehci_bus_suspend, | |
| .bus_resume = ehci_bus_resume, | |
| .relinquish_port = ehci_relinquish_port, | |
| .port_handed_over = ehci_port_handed_over, | |
| .clear_tt_buffer_complete = ehci_clear_tt_buffer_complete, | |
| }; | |
| static int ehci_hcd_msp_drv_probe(struct platform_device *pdev) | |
| { | |
| int ret; | |
| pr_debug("In ehci_hcd_msp_drv_probe"); | |
| if (usb_disabled()) | |
| return -ENODEV; | |
| gpio_request(MSP_PIN_USB0_HOST_DEV, "USB0_HOST_DEV_GPIO"); | |
| ret = usb_hcd_msp_probe(&ehci_msp_hc_driver, pdev); | |
| return ret; | |
| } | |
| static int ehci_hcd_msp_drv_remove(struct platform_device *pdev) | |
| { | |
| struct usb_hcd *hcd = platform_get_drvdata(pdev); | |
| usb_hcd_msp_remove(hcd, pdev); | |
| /* free TWI GPIO USB_HOST_DEV pin */ | |
| gpio_free(MSP_PIN_USB0_HOST_DEV); | |
| return 0; | |
| } | |
| MODULE_ALIAS("pmcmsp-ehci"); | |
| static struct platform_driver ehci_hcd_msp_driver = { | |
| .probe = ehci_hcd_msp_drv_probe, | |
| .remove = ehci_hcd_msp_drv_remove, | |
| .driver = { | |
| .name = "pmcmsp-ehci", | |
| }, | |
| }; |