usb: musb: do multiple irq processing passes
[pandora-kernel.git] / drivers / usb / musb / musb_core.c
index b63ab15..b7e37a5 100644 (file)
@@ -137,6 +137,9 @@ static int musb_ulpi_read(struct otg_transceiver *otg, u32 offset)
        int     i = 0;
        u8      r;
        u8      power;
+       int     ret;
+
+       pm_runtime_get_sync(otg->io_dev);
 
        /* Make sure the transceiver is not in low power mode */
        power = musb_readb(addr, MUSB_POWER);
@@ -154,15 +157,22 @@ static int musb_ulpi_read(struct otg_transceiver *otg, u32 offset)
        while (!(musb_readb(addr, MUSB_ULPI_REG_CONTROL)
                                & MUSB_ULPI_REG_CMPLT)) {
                i++;
-               if (i == 10000)
-                       return -ETIMEDOUT;
+               if (i == 10000) {
+                       ret = -ETIMEDOUT;
+                       goto out;
+               }
 
        }
        r = musb_readb(addr, MUSB_ULPI_REG_CONTROL);
        r &= ~MUSB_ULPI_REG_CMPLT;
        musb_writeb(addr, MUSB_ULPI_REG_CONTROL, r);
 
-       return musb_readb(addr, MUSB_ULPI_REG_DATA);
+       ret = musb_readb(addr, MUSB_ULPI_REG_DATA);
+
+out:
+       pm_runtime_put(otg->io_dev);
+
+       return ret;
 }
 
 static int musb_ulpi_write(struct otg_transceiver *otg,
@@ -172,6 +182,9 @@ static int musb_ulpi_write(struct otg_transceiver *otg,
        int     i = 0;
        u8      r = 0;
        u8      power;
+       int     ret = 0;
+
+       pm_runtime_get_sync(otg->io_dev);
 
        /* Make sure the transceiver is not in low power mode */
        power = musb_readb(addr, MUSB_POWER);
@@ -185,15 +198,20 @@ static int musb_ulpi_write(struct otg_transceiver *otg,
        while (!(musb_readb(addr, MUSB_ULPI_REG_CONTROL)
                                & MUSB_ULPI_REG_CMPLT)) {
                i++;
-               if (i == 10000)
-                       return -ETIMEDOUT;
+               if (i == 10000) {
+                       ret = -ETIMEDOUT;
+                       goto out;
+               }
        }
 
        r = musb_readb(addr, MUSB_ULPI_REG_CONTROL);
        r &= ~MUSB_ULPI_REG_CMPLT;
        musb_writeb(addr, MUSB_ULPI_REG_CONTROL, r);
 
-       return 0;
+out:
+       pm_runtime_put(otg->io_dev);
+
+       return ret;
 }
 #else
 #define musb_ulpi_read         NULL
@@ -574,9 +592,10 @@ static irqreturn_t musb_stage0_irq(struct musb *musb, u8 int_usb,
                        break;
                }
 
-               dev_dbg(musb->controller, "VBUS_ERROR in %s (%02x, %s), retry #%d, port1 %08x\n",
+               dev_printk(ignore ? KERN_DEBUG : KERN_ERR, musb->controller,
+                               "VBUS_ERROR in %s (%02x, %02x, %s), retry #%d, port1 %08x\n",
                                otg_state_string(musb->xceiv->state),
-                               devctl,
+                               devctl, power,
                                ({ char *s;
                                switch (devctl & MUSB_DEVCTL_VBUS) {
                                case 0 << MUSB_DEVCTL_VBUS_SHIFT:
@@ -654,6 +673,15 @@ static irqreturn_t musb_stage0_irq(struct musb *musb, u8 int_usb,
                        musb->is_active = 0;
                        break;
                }
+
+               switch (musb->xceiv->state) {
+               case OTG_STATE_B_IDLE:
+               case OTG_STATE_B_PERIPHERAL:
+                       cancel_delayed_work(&musb->vbus_workaround_work);
+                       schedule_delayed_work(&musb->vbus_workaround_work, HZ / 2);
+               default:
+                       break;
+               }
        }
 
        if (int_usb & MUSB_INTR_CONNECT) {
@@ -916,8 +944,8 @@ void musb_start(struct musb *musb)
                 */
                if ((devctl & MUSB_DEVCTL_VBUS) == MUSB_DEVCTL_VBUS)
                        musb->is_active = 1;
-               else
-                       devctl |= MUSB_DEVCTL_SESSION;
+               //else
+               //      devctl |= MUSB_DEVCTL_SESSION;
 
        } else if (is_host_enabled(musb)) {
                /* assume ID pin is hard-wired to ground */
@@ -982,6 +1010,9 @@ static void musb_shutdown(struct platform_device *pdev)
        unsigned long   flags;
 
        pm_runtime_get_sync(musb->controller);
+
+       musb_gadget_cleanup(musb);
+
        spin_lock_irqsave(&musb->lock, flags);
        musb_platform_disable(musb);
        musb_generic_disable(musb);
@@ -993,6 +1024,9 @@ static void musb_shutdown(struct platform_device *pdev)
        musb_platform_exit(musb);
 
        pm_runtime_put(musb->controller);
+
+       cancel_delayed_work(&musb->vbus_workaround_work);
+
        /* FIXME power down */
 }
 
@@ -1484,15 +1518,22 @@ static irqreturn_t generic_interrupt(int irq, void *__hci)
        unsigned long   flags;
        irqreturn_t     retval = IRQ_NONE;
        struct musb     *musb = __hci;
+       int             i;
 
        spin_lock_irqsave(&musb->lock, flags);
 
-       musb->int_usb = musb_readb(musb->mregs, MUSB_INTRUSB);
-       musb->int_tx = musb_readw(musb->mregs, MUSB_INTRTX);
-       musb->int_rx = musb_readw(musb->mregs, MUSB_INTRRX);
+       for (i = 0; i < 8; i++) {
+               musb->int_usb = musb_readb(musb->mregs, MUSB_INTRUSB);
+               /* SOF is not enabled, but status is still often set */
+               musb->int_usb &= ~MUSB_INTR_SOF;
+               musb->int_tx = musb_readw(musb->mregs, MUSB_INTRTX);
+               musb->int_rx = musb_readw(musb->mregs, MUSB_INTRRX);
 
-       if (musb->int_usb || musb->int_tx || musb->int_rx)
-               retval = musb_interrupt(musb);
+               if (musb->int_usb || musb->int_tx || musb->int_rx)
+                       retval = musb_interrupt(musb);
+               else
+                       break;
+       }
 
        spin_unlock_irqrestore(&musb->lock, flags);
 
@@ -1770,6 +1811,45 @@ static void musb_irq_work(struct work_struct *data)
        }
 }
 
+#include <linux/usb/ulpi.h>
+
+static void musb_vbus_workaround_work(struct work_struct *work)
+{
+       struct musb *musb = container_of(work, struct musb, vbus_workaround_work.work);
+       u8 devctl;
+       int ret;
+
+       if (musb_ulpi_access.write == NULL)
+               return;
+
+       pm_runtime_get_sync(musb->controller);
+
+       devctl = musb_readb(musb->mregs, MUSB_DEVCTL);
+
+       /*
+        * I don't really know why but VBUS sometimes gets stuck and
+        * causes session to never end. It would look like some pullup
+        * is enabled when it shouldn't be on certain PHY states.
+        * Turning on pulldowns magically drains VBUS to zero and allows
+        * session to end, so let's do that here.
+        *
+        * XXX: probably better check VBUS on TWL?
+        * beagle sometimes has session bit set but no VBUS on twl?
+        */
+       if ((musb->xceiv->state == OTG_STATE_B_PERIPHERAL ||
+            musb->xceiv->state == OTG_STATE_B_IDLE) &&
+           (devctl & MUSB_DEVCTL_VBUS) != (3 << MUSB_DEVCTL_VBUS_SHIFT) &&
+           (devctl & MUSB_DEVCTL_VBUS) != (0 << MUSB_DEVCTL_VBUS_SHIFT)) {
+               dev_dbg(musb->controller, "VBUS workaround..\n");
+               ret = musb_ulpi_access.write(musb->xceiv, ULPI_SET(ULPI_OTG_CTRL),
+                       ULPI_OTG_CTRL_DM_PULLDOWN | ULPI_OTG_CTRL_DP_PULLDOWN);
+               //if (ret)
+               //      dev_err(musb->controller, "VBUS workaround error\n");
+       }
+
+       pm_runtime_put(musb->controller);
+}
+
 /* --------------------------------------------------------------------------
  * Init support
  */
@@ -1827,8 +1907,6 @@ static void musb_free(struct musb *musb)
        sysfs_remove_group(&musb->controller->kobj, &musb_attr_group);
 #endif
 
-       musb_gadget_cleanup(musb);
-
        if (musb->nIrq >= 0) {
                if (musb->irq_wake)
                        disable_irq_wake(musb->nIrq);
@@ -1841,7 +1919,7 @@ static void musb_free(struct musb *musb)
                dma_controller_destroy(c);
        }
 
-       kfree(musb);
+       usb_put_hcd(musb_to_hcd(musb));
 }
 
 /*
@@ -1907,6 +1985,7 @@ musb_init_controller(struct device *dev, int nIrq, void __iomem *ctrl)
        }
 
        if (!musb->xceiv->io_ops) {
+               musb->xceiv->io_dev = musb->controller;
                musb->xceiv->io_priv = musb->mregs;
                musb->xceiv->io_ops = &musb_ulpi_access;
        }
@@ -1941,6 +2020,8 @@ musb_init_controller(struct device *dev, int nIrq, void __iomem *ctrl)
        /* Init IRQ workqueue before request_irq */
        INIT_WORK(&musb->irq_work, musb_irq_work);
 
+       INIT_DELAYED_WORK(&musb->vbus_workaround_work, musb_vbus_workaround_work);
+
        /* attach to the IRQ */
        if (request_irq(nIrq, musb->isr, 0, dev_name(dev), musb)) {
                dev_err(dev, "request_irq %d failed!\n", nIrq);
@@ -1997,9 +2078,13 @@ musb_init_controller(struct device *dev, int nIrq, void __iomem *ctrl)
                                ? 'B' : 'A'));
 
        } else /* peripheral is enabled */ {
-               MUSB_DEV_MODE(musb);
-               musb->xceiv->default_a = 0;
-               musb->xceiv->state = OTG_STATE_B_IDLE;
+               if (musb->xceiv->default_a) {
+                       MUSB_HST_MODE(musb);
+                       musb->xceiv->state = OTG_STATE_A_IDLE;
+               } else {
+                       MUSB_DEV_MODE(musb);
+                       musb->xceiv->state = OTG_STATE_B_IDLE;
+               }
 
                status = musb_gadget_setup(musb);
 
@@ -2113,11 +2198,9 @@ static int __exit musb_remove(struct platform_device *pdev)
         *  - Peripheral mode: peripheral is deactivated (or never-activated)
         *  - OTG mode: both roles are deactivated (or never-activated)
         */
-       pm_runtime_get_sync(musb->controller);
        musb_exit_debugfs(musb);
        musb_shutdown(pdev);
 
-       pm_runtime_put(musb->controller);
        musb_free(musb);
        iounmap(ctrl_base);
        device_init_wakeup(&pdev->dev, 0);
@@ -2158,6 +2241,7 @@ static void musb_save_context(struct musb *musb)
                if (!epio)
                        continue;
 
+               musb_writeb(musb_base, MUSB_INDEX, i);
                musb->context.index_regs[i].txmaxp =
                        musb_readw(epio, MUSB_TXMAXP);
                musb->context.index_regs[i].txcsr =
@@ -2233,6 +2317,7 @@ static void musb_restore_context(struct musb *musb)
                if (!epio)
                        continue;
 
+               musb_writeb(musb_base, MUSB_INDEX, i);
                musb_writew(epio, MUSB_TXMAXP,
                        musb->context.index_regs[i].txmaxp);
                musb_writew(epio, MUSB_TXCSR,
@@ -2288,21 +2373,32 @@ static int musb_suspend(struct device *dev)
 {
        struct musb     *musb = dev_to_musb(dev);
        unsigned long   flags;
+       int             ret = 0;
 
        spin_lock_irqsave(&musb->lock, flags);
 
-       if (is_peripheral_active(musb)) {
+       {
                /* FIXME force disconnect unless we know USB will wake
                 * the system up quickly enough to respond ...
                 */
-       } else if (is_host_active(musb)) {
-               /* we know all the children are suspended; sometimes
-                * they will even be wakeup-enabled.
+               /*
+                * FIXME: musb must be already runtime suspended at this point.
+                * If it's not, framework will try to suspend it late when
+                * i2c will be off, and twl4030 will want to access it for it's
+                * stuff, causing data abort.
                 */
+               int pm_usage_count =
+                       atomic_read(&musb->controller->power.usage_count);
+               if (pm_usage_count > 1) {
+                       dev_err(dev, "can't suspend while still active, "
+                               "try removing gadget drivers (usage_count %d)\n",
+                               pm_usage_count);
+                       ret = -EBUSY;
+               }
        }
 
        spin_unlock_irqrestore(&musb->lock, flags);
-       return 0;
+       return ret;
 }
 
 static int musb_resume_noirq(struct device *dev)
@@ -2374,10 +2470,7 @@ static int __init musb_init(void)
        if (usb_disabled())
                return 0;
 
-       pr_info("%s: version " MUSB_VERSION ", "
-               "?dma?"
-               ", "
-               "otg (peripheral+host)",
+       pr_info("%s: version " MUSB_VERSION ", ?dma?, otg (peripheral+host)\n",
                musb_driver_name);
        return platform_driver_probe(&musb_driver, musb_probe);
 }