USB: extend ehci-fsl and fsl_udc_core driver for OTG operation
authorAnatolij Gustschin <agust@denx.de>
Mon, 18 Apr 2011 20:02:00 +0000 (22:02 +0200)
committerGreg Kroah-Hartman <gregkh@suse.de>
Mon, 2 May 2011 23:59:38 +0000 (16:59 -0700)
Signed-off-by: Anatolij Gustschin <agust@denx.de>
Cc: Li Yang <leoli@freescale.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
drivers/usb/gadget/fsl_udc_core.c
drivers/usb/gadget/fsl_usb2_udc.h
drivers/usb/host/ehci-fsl.c
drivers/usb/host/ehci-hub.c
drivers/usb/host/ehci.h
include/linux/fsl_devices.h

index 28b3a9f25f3b92383cdeb6954072d7ffc57c060c..999eafe89653c063c0e6beef9839814cf0ad8b4b 100644 (file)
@@ -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,
        },
 };
 
index 5647cc21b84c1973ae23b0306607232a6de75906..1d51be83fda87402d4a77a28ff4fa636d17bff62 100644 (file)
@@ -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 */
index caf3d4ac42bddda44e6803a84acb0fdc43c77bda..623732a312dde329598db829a7046b51629a27bd 100644 (file)
@@ -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,
 
index 1a21799195afe8e503bbb13dfd7f764e5445b233..ea6184bf48d05d2730e899b901201bec985e11b3 100644 (file)
@@ -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)
index 168f1a88c4d0ec3d8245ba620af05800e638f364..e9ba8e25248947afc40c96383678a7d1b6aac7ae 100644 (file)
@@ -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 */
index 3773c5dab8f5423e6a92149cc50324d99ea7eb3c..fffdf00f87b91bd94f09071db3b999f7e5e5d210 100644 (file)
@@ -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 */