diff options
-rw-r--r-- | drivers/usb/gadget/fsl_udc_core.c | 147 | ||||
-rw-r--r-- | drivers/usb/gadget/fsl_usb2_udc.h | 2 | ||||
-rw-r--r-- | drivers/usb/host/ehci-fsl.c | 66 | ||||
-rw-r--r-- | drivers/usb/host/ehci-hub.c | 8 | ||||
-rw-r--r-- | drivers/usb/host/ehci.h | 4 | ||||
-rw-r--r-- | include/linux/fsl_devices.h | 1 |
6 files changed, 213 insertions, 15 deletions
diff --git a/drivers/usb/gadget/fsl_udc_core.c b/drivers/usb/gadget/fsl_udc_core.c index 28b3a9f..999eafe 100644 --- a/drivers/usb/gadget/fsl_udc_core.c +++ b/drivers/usb/gadget/fsl_udc_core.c @@ -353,6 +353,19 @@ static void dr_controller_stop(struct fsl_udc *udc) { unsigned int tmp; + pr_debug("%s\n", __func__); + + /* if we're in OTG mode, and the Host is currently using the port, + * stop now and don't rip the controller out from under the + * ehci driver + */ + if (udc->gadget.is_otg) { + if (!(fsl_readl(&dr_regs->otgsc) & OTGSC_STS_USB_ID)) { + pr_debug("udc: Leaving early\n"); + return; + } + } + /* disable all INTR */ fsl_writel(0, &dr_regs->usbintr); @@ -1668,6 +1681,9 @@ static void port_change_irq(struct fsl_udc *udc) { u32 speed; + if (udc->bus_reset) + udc->bus_reset = 0; + /* Bus resetting is finished */ if (!(fsl_readl(&dr_regs->portsc1) & PORTSCX_PORT_RESET)) { /* Get the speed */ @@ -1775,6 +1791,8 @@ static void reset_irq(struct fsl_udc *udc) if (fsl_readl(&dr_regs->portsc1) & PORTSCX_PORT_RESET) { VDBG("Bus reset"); + /* Bus is reseting */ + udc->bus_reset = 1; /* Reset all the queues, include XD, dTD, EP queue * head and TR Queue */ reset_queues(udc); @@ -1852,6 +1870,7 @@ static irqreturn_t fsl_udc_irq(int irq, void *_udc) /* Reset Received */ if (irq_src & USB_STS_RESET) { + VDBG("reset int"); reset_irq(udc); status = IRQ_HANDLED; } @@ -1909,11 +1928,30 @@ int usb_gadget_probe_driver(struct usb_gadget_driver *driver, goto out; } - /* Enable DR IRQ reg and Set usbcmd reg Run bit */ - dr_controller_run(udc_controller); - udc_controller->usb_state = USB_STATE_ATTACHED; - udc_controller->ep0_state = WAIT_FOR_SETUP; - udc_controller->ep0_dir = 0; + if (udc_controller->transceiver) { + /* Suspend the controller until OTG enable it */ + udc_controller->stopped = 1; + printk(KERN_INFO "Suspend udc for OTG auto detect\n"); + + /* connect to bus through transceiver */ + if (udc_controller->transceiver) { + retval = otg_set_peripheral(udc_controller->transceiver, + &udc_controller->gadget); + if (retval < 0) { + ERR("can't bind to transceiver\n"); + driver->unbind(&udc_controller->gadget); + udc_controller->gadget.dev.driver = 0; + udc_controller->driver = 0; + return retval; + } + } + } else { + /* Enable DR IRQ reg and set USBCMD reg Run bit */ + dr_controller_run(udc_controller); + udc_controller->usb_state = USB_STATE_ATTACHED; + udc_controller->ep0_state = WAIT_FOR_SETUP; + udc_controller->ep0_dir = 0; + } printk(KERN_INFO "%s: bind to driver %s\n", udc_controller->gadget.name, driver->driver.name); @@ -2374,17 +2412,30 @@ static int __init fsl_udc_probe(struct platform_device *pdev) spin_lock_init(&udc_controller->lock); udc_controller->stopped = 1; +#ifdef CONFIG_USB_OTG + if (pdata->operating_mode == FSL_USB2_DR_OTG) { + udc_controller->transceiver = otg_get_transceiver(); + if (!udc_controller->transceiver) { + ERR("Can't find OTG driver!\n"); + ret = -ENODEV; + goto err_kfree; + } + } +#endif + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { ret = -ENXIO; goto err_kfree; } - if (!request_mem_region(res->start, res->end - res->start + 1, - driver_name)) { - ERR("request mem region for %s failed\n", pdev->name); - ret = -EBUSY; - goto err_kfree; + if (pdata->operating_mode == FSL_USB2_DR_DEVICE) { + if (!request_mem_region(res->start, res->end - res->start + 1, + driver_name)) { + ERR("request mem region for %s failed\n", pdev->name); + ret = -EBUSY; + goto err_kfree; + } } dr_regs = ioremap(res->start, resource_size(res)); @@ -2455,9 +2506,11 @@ static int __init fsl_udc_probe(struct platform_device *pdev) goto err_free_irq; } - /* initialize usb hw reg except for regs for EP, - * leave usbintr reg untouched */ - dr_controller_setup(udc_controller); + if (!udc_controller->transceiver) { + /* initialize usb hw reg except for regs for EP, + * leave usbintr reg untouched */ + dr_controller_setup(udc_controller); + } fsl_udc_clk_finalize(pdev); @@ -2477,6 +2530,9 @@ static int __init fsl_udc_probe(struct platform_device *pdev) if (ret < 0) goto err_free_irq; + if (udc_controller->transceiver) + udc_controller->gadget.is_otg = 1; + /* setup QH and epctrl for ep0 */ ep0_setup(udc_controller); @@ -2521,7 +2577,8 @@ err_iounmap: err_iounmap_noclk: iounmap(dr_regs); err_release_mem_region: - release_mem_region(res->start, res->end - res->start + 1); + if (pdata->operating_mode == FSL_USB2_DR_DEVICE) + release_mem_region(res->start, res->end - res->start + 1); err_kfree: kfree(udc_controller); udc_controller = NULL; @@ -2555,7 +2612,8 @@ static int __exit fsl_udc_remove(struct platform_device *pdev) dma_pool_destroy(udc_controller->td_pool); free_irq(udc_controller->irq, udc_controller); iounmap(dr_regs); - release_mem_region(res->start, res->end - res->start + 1); + if (pdata->operating_mode == FSL_USB2_DR_DEVICE) + release_mem_region(res->start, res->end - res->start + 1); device_unregister(&udc_controller->gadget.dev); /* free udc --wait for the release() finished */ @@ -2598,6 +2656,62 @@ static int fsl_udc_resume(struct platform_device *pdev) return 0; } +static int fsl_udc_otg_suspend(struct device *dev, pm_message_t state) +{ + struct fsl_udc *udc = udc_controller; + u32 mode, usbcmd; + + mode = fsl_readl(&dr_regs->usbmode) & USB_MODE_CTRL_MODE_MASK; + + pr_debug("%s(): mode 0x%x stopped %d\n", __func__, mode, udc->stopped); + + /* + * If the controller is already stopped, then this must be a + * PM suspend. Remember this fact, so that we will leave the + * controller stopped at PM resume time. + */ + if (udc->stopped) { + pr_debug("gadget already stopped, leaving early\n"); + udc->already_stopped = 1; + return 0; + } + + if (mode != USB_MODE_CTRL_MODE_DEVICE) { + pr_debug("gadget not in device mode, leaving early\n"); + return 0; + } + + /* stop the controller */ + usbcmd = fsl_readl(&dr_regs->usbcmd) & ~USB_CMD_RUN_STOP; + fsl_writel(usbcmd, &dr_regs->usbcmd); + + udc->stopped = 1; + + pr_info("USB Gadget suspended\n"); + + return 0; +} + +static int fsl_udc_otg_resume(struct device *dev) +{ + pr_debug("%s(): stopped %d already_stopped %d\n", __func__, + udc_controller->stopped, udc_controller->already_stopped); + + /* + * If the controller was stopped at suspend time, then + * don't resume it now. + */ + if (udc_controller->already_stopped) { + udc_controller->already_stopped = 0; + pr_debug("gadget was already stopped, leaving early\n"); + return 0; + } + + pr_info("USB Gadget resume\n"); + + return fsl_udc_resume(NULL); +} + /*------------------------------------------------------------------------- Register entry point for the peripheral controller driver --------------------------------------------------------------------------*/ @@ -2610,6 +2724,9 @@ static struct platform_driver udc_driver = { .driver = { .name = (char *)driver_name, .owner = THIS_MODULE, + /* udc suspend/resume called from OTG driver */ + .suspend = fsl_udc_otg_suspend, + .resume = fsl_udc_otg_resume, }, }; diff --git a/drivers/usb/gadget/fsl_usb2_udc.h b/drivers/usb/gadget/fsl_usb2_udc.h index 5647cc21..1d51be8 100644 --- a/drivers/usb/gadget/fsl_usb2_udc.h +++ b/drivers/usb/gadget/fsl_usb2_udc.h @@ -476,6 +476,7 @@ struct fsl_udc { unsigned vbus_active:1; unsigned stopped:1; unsigned remote_wakeup:1; + unsigned already_stopped:1; unsigned big_endian_desc:1; struct ep_queue_head *ep_qh; /* Endpoints Queue-Head */ @@ -487,6 +488,7 @@ struct fsl_udc { dma_addr_t ep_qh_dma; /* dma address of QH */ u32 max_pipes; /* Device max pipes */ + u32 bus_reset; /* Device is bus resetting */ u32 resume_state; /* USB state to resume */ u32 usb_state; /* USB current state */ u32 ep0_state; /* Endpoint zero state */ diff --git a/drivers/usb/host/ehci-fsl.c b/drivers/usb/host/ehci-fsl.c index caf3d4a..623732a 100644 --- a/drivers/usb/host/ehci-fsl.c +++ b/drivers/usb/host/ehci-fsl.c @@ -117,6 +117,9 @@ static int usb_hcd_fsl_probe(const struct hc_driver *driver, pdata->regs = hcd->regs; + if (pdata->power_budget) + hcd->power_budget = pdata->power_budget; + /* * do platform specific init: check the clock, grab/config pins, etc. */ @@ -134,6 +137,30 @@ static int usb_hcd_fsl_probe(const struct hc_driver *driver, retval = usb_add_hcd(hcd, irq, IRQF_DISABLED | IRQF_SHARED); if (retval != 0) goto err4; + +#ifdef CONFIG_USB_OTG + if (pdata->operating_mode == FSL_USB2_DR_OTG) { + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + + ehci->transceiver = otg_get_transceiver(); + dev_dbg(&pdev->dev, "hcd=0x%p ehci=0x%p, transceiver=0x%p\n", + hcd, ehci, ehci->transceiver); + + if (ehci->transceiver) { + retval = otg_set_host(ehci->transceiver, + &ehci_to_hcd(ehci)->self); + if (retval) { + if (ehci->transceiver) + put_device(ehci->transceiver->dev); + goto err4; + } + } else { + dev_err(&pdev->dev, "can't find transceiver\n"); + retval = -ENODEV; + goto err4; + } + } +#endif return retval; err4: @@ -164,6 +191,12 @@ static void usb_hcd_fsl_remove(struct usb_hcd *hcd, struct platform_device *pdev) { struct fsl_usb2_platform_data *pdata = pdev->dev.platform_data; + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + + if (ehci->transceiver) { + otg_set_host(ehci->transceiver, NULL); + put_device(ehci->transceiver->dev); + } usb_remove_hcd(hcd); @@ -544,6 +577,38 @@ static struct dev_pm_ops ehci_fsl_pm_ops = { #define EHCI_FSL_PM_OPS NULL #endif /* CONFIG_PM */ +#ifdef CONFIG_USB_OTG +static int ehci_start_port_reset(struct usb_hcd *hcd, unsigned port) +{ + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + u32 status; + + if (!port) + return -EINVAL; + + port--; + + /* start port reset before HNP protocol time out */ + status = readl(&ehci->regs->port_status[port]); + if (!(status & PORT_CONNECT)) + return -ENODEV; + + /* khubd will finish the reset later */ + if (ehci_is_TDI(ehci)) { + writel(PORT_RESET | + (status & ~(PORT_CSC | PORT_PEC | PORT_OCC)), + &ehci->regs->port_status[port]); + } else { + writel(PORT_RESET, &ehci->regs->port_status[port]); + } + + return 0; +} +#else +#define ehci_start_port_reset NULL +#endif /* CONFIG_USB_OTG */ + + static const struct hc_driver ehci_fsl_hc_driver = { .description = hcd_name, .product_desc = "Freescale On-Chip EHCI Host Controller", @@ -583,6 +648,7 @@ static const struct hc_driver ehci_fsl_hc_driver = { .hub_control = ehci_hub_control, .bus_suspend = ehci_bus_suspend, .bus_resume = ehci_bus_resume, + .start_port_reset = ehci_start_port_reset, .relinquish_port = ehci_relinquish_port, .port_handed_over = ehci_port_handed_over, diff --git a/drivers/usb/host/ehci-hub.c b/drivers/usb/host/ehci-hub.c index 1a21799..ea6184b 100644 --- a/drivers/usb/host/ehci-hub.c +++ b/drivers/usb/host/ehci-hub.c @@ -27,6 +27,7 @@ */ /*-------------------------------------------------------------------------*/ +#include <linux/usb/otg.h> #define PORT_WAKE_BITS (PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E) @@ -801,6 +802,13 @@ static int ehci_hub_control ( goto error; if (ehci->no_selective_suspend) break; +#ifdef CONFIG_USB_OTG + if ((hcd->self.otg_port == (wIndex + 1)) + && hcd->self.b_hnp_enable) { + otg_start_hnp(ehci->transceiver); + break; + } +#endif if (!(temp & PORT_SUSPEND)) break; if ((temp & PORT_PE) == 0) diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index 168f1a8..e9ba8e2 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h @@ -161,6 +161,10 @@ struct ehci_hcd { /* one per controller */ #ifdef DEBUG struct dentry *debug_dir; #endif + /* + * OTG controllers and transceivers need software interaction + */ + struct otg_transceiver *transceiver; }; /* convert between an HCD pointer and the corresponding EHCI_HCD */ diff --git a/include/linux/fsl_devices.h b/include/linux/fsl_devices.h index 3773c5d..fffdf00 100644 --- a/include/linux/fsl_devices.h +++ b/include/linux/fsl_devices.h @@ -72,6 +72,7 @@ struct fsl_usb2_platform_data { void (*exit)(struct platform_device *); void __iomem *regs; /* ioremap'd register base */ struct clk *clk; + unsigned power_budget; /* hcd->power_budget */ unsigned big_endian_mmio:1; unsigned big_endian_desc:1; unsigned es:1; /* need USBMODE:ES */ |