Merge branch 'stable-3.2' into pandora-3.2
[pandora-kernel.git] / drivers / usb / otg / twl4030-usb.c
index 14f66c3..296e553 100644 (file)
@@ -163,6 +163,8 @@ struct twl4030_usb {
        bool                    vbus_supplied;
        u8                      asleep;
        bool                    irq_enabled;
+
+       struct delayed_work     id_workaround_work;
 };
 
 /* internal define on top of container_of */
@@ -246,10 +248,30 @@ twl4030_usb_clear_bits(struct twl4030_usb *twl, u8 reg, u8 bits)
 
 /*-------------------------------------------------------------------------*/
 
+static bool twl4030_is_driving_vbus(struct twl4030_usb *twl)
+{
+       int ret;
+
+       ret = twl4030_usb_read(twl, PHY_CLK_CTRL_STS);
+       if (ret < 0 || !(ret & PHY_DPLL_CLK))
+               /*
+                * if clocks are off, registers are not updated,
+                * but we can assume we don't drive VBUS in this case
+                */
+               return false;
+
+       ret = twl4030_usb_read(twl, ULPI_OTG_CTRL);
+       if (ret < 0)
+               return false;
+
+       return (ret & (ULPI_OTG_DRVVBUS | ULPI_OTG_CHRGVBUS)) ? true : false;
+}
+
 static enum usb_xceiv_events twl4030_usb_linkstat(struct twl4030_usb *twl)
 {
        int     status;
        int     linkstat = USB_EVENT_NONE;
+       bool    driving_vbus = false;
 
        twl->vbus_supplied = false;
 
@@ -263,23 +285,31 @@ static enum usb_xceiv_events twl4030_usb_linkstat(struct twl4030_usb *twl)
         * signal is active, the OTG module is activated, and
         * its interrupt may be raised (may wake the system).
         */
+       msleep(50);
        status = twl4030_readb(twl, TWL4030_MODULE_PM_MASTER,
                        STS_HW_CONDITIONS);
        if (status < 0)
                dev_err(twl->dev, "USB link status err %d\n", status);
        else if (status & (BIT(7) | BIT(2))) {
-               if (status & (BIT(7)))
-                        twl->vbus_supplied = true;
+               if (status & BIT(7)) {
+                       driving_vbus = twl4030_is_driving_vbus(twl);
+                       if (driving_vbus)
+                               status &= ~BIT(7);
+               }
 
                if (status & BIT(2))
                        linkstat = USB_EVENT_ID;
-               else
+               else if (status & BIT(7)) {
                        linkstat = USB_EVENT_VBUS;
-       } else
-               linkstat = USB_EVENT_NONE;
+                       twl->vbus_supplied = true;
+               }
+       }
+
+       dev_dbg(twl->dev, "HW_CONDITIONS 0x%02x; link %d, driving_vbus %d\n",
+                       status, linkstat, driving_vbus);
 
-       dev_dbg(twl->dev, "HW_CONDITIONS 0x%02x/%d; link %d\n",
-                       status, status, linkstat);
+       if (twl->otg.last_event == linkstat)
+               return linkstat;
 
        twl->otg.last_event = linkstat;
 
@@ -419,7 +449,16 @@ static void twl4030_phy_resume(struct twl4030_usb *twl)
                return;
        __twl4030_phy_resume(twl);
        twl->asleep = 0;
-       dev_dbg(twl->dev, "%s\n", __func__);
+
+       /*
+        * XXX When VBUS gets driven after musb goes to A mode,
+        * ID_PRES related interrupts no longer arrive, why?
+        * Register itself is updated fine though, so we must poll.
+        */
+       if (twl->otg.last_event == USB_EVENT_ID) {
+               cancel_delayed_work(&twl->id_workaround_work);
+               schedule_delayed_work(&twl->id_workaround_work, HZ);
+       }
 }
 
 static int twl4030_usb_ldo_init(struct twl4030_usb *twl)
@@ -497,9 +536,47 @@ static ssize_t twl4030_usb_vbus_show(struct device *dev,
 }
 static DEVICE_ATTR(vbus, 0444, twl4030_usb_vbus_show, NULL);
 
+static ssize_t twl4030_usb_id_show(struct device *dev,
+               struct device_attribute *attr, char *buf)
+{
+       int ret;
+       int n = 0;
+       struct twl4030_usb *twl = dev_get_drvdata(dev);
+       twl4030_i2c_access(twl, 1);
+       ret = twl4030_usb_read(twl, ULPI_OTG_CTRL);
+       if ((ret < 0) || (!(ret & ULPI_OTG_ID_PULLUP))) {
+               /*
+                * enable ID pullup so that the id pin state can be measured,
+                * seems to be disabled sometimes for some reasons
+                */
+               dev_dbg(dev, "ULPI_OTG_ID_PULLUP not set (%x)\n", ret);
+               twl4030_usb_set_bits(twl, ULPI_OTG_CTRL, ULPI_OTG_ID_PULLUP);
+               mdelay(100);
+       }
+       ret = twl4030_usb_read(twl, ID_STATUS);
+       twl4030_i2c_access(twl, 0);
+       if (ret < 0)
+               return ret;
+       if (ret & ID_RES_FLOAT)
+               n = scnprintf(buf, PAGE_SIZE, "%s\n", "floating");
+       else if (ret & ID_RES_440K)
+               n = scnprintf(buf, PAGE_SIZE, "%s\n", "440k");
+       else if (ret & ID_RES_200K)
+               n = scnprintf(buf, PAGE_SIZE, "%s\n", "200k");
+       else if (ret & ID_RES_102K)
+               n = scnprintf(buf, PAGE_SIZE, "%s\n", "102k");
+       else if (ret & ID_RES_GND)
+               n = scnprintf(buf, PAGE_SIZE, "%s\n", "GND");
+       else
+               n = scnprintf(buf, PAGE_SIZE, "unknown: id=0x%x\n", ret);
+       return n;
+}
+static DEVICE_ATTR(id, 0444, twl4030_usb_id_show, NULL);
+
 static irqreturn_t twl4030_usb_irq(int irq, void *_twl)
 {
        struct twl4030_usb *twl = _twl;
+       int status_old = twl->otg.last_event;
        int status;
 
        status = twl4030_usb_linkstat(twl);
@@ -515,12 +592,8 @@ static irqreturn_t twl4030_usb_irq(int irq, void *_twl)
                 * USB_LINK_VBUS state.  musb_hdrc won't care until it
                 * starts to handle softconnect right.
                 */
-               if (status == USB_EVENT_NONE)
-                       twl4030_phy_suspend(twl, 0);
-               else
-                       twl4030_phy_resume(twl);
-
-               atomic_notifier_call_chain(&twl->otg.notifier, status,
+               if (status != status_old)
+                       atomic_notifier_call_chain(&twl->otg.notifier, status,
                                twl->otg.gadget);
        }
        sysfs_notify(&twl->dev->kobj, NULL, "vbus");
@@ -528,23 +601,44 @@ static irqreturn_t twl4030_usb_irq(int irq, void *_twl)
        return IRQ_HANDLED;
 }
 
-static void twl4030_usb_phy_init(struct twl4030_usb *twl)
+static void twl4030_id_workaround_work(struct work_struct *work)
 {
+       struct twl4030_usb *twl = container_of(work, struct twl4030_usb,
+               id_workaround_work.work);
+       int status_old = twl->otg.last_event;
        int status;
 
        status = twl4030_usb_linkstat(twl);
-       if (status >= 0) {
-               if (status == USB_EVENT_NONE) {
-                       __twl4030_phy_power(twl, 0);
-                       twl->asleep = 1;
-               } else {
-                       __twl4030_phy_resume(twl);
-                       twl->asleep = 0;
-               }
+       if (status != status_old) {
+               dev_dbg(twl->dev, "handle missing status change: %d->%d\n",
+                       status_old, status);
+               twl->otg.last_event = status_old;
+               twl4030_usb_irq(0, twl);
+       }
 
-               atomic_notifier_call_chain(&twl->otg.notifier, status,
-                               twl->otg.gadget);
+       /* don't schedule during sleep - irq works right then */
+       if (status == USB_EVENT_ID && !twl->asleep) {
+               cancel_delayed_work(&twl->id_workaround_work);
+               schedule_delayed_work(&twl->id_workaround_work, HZ);
        }
+}
+
+static void twl4030_usb_phy_init(struct twl4030_usb *twl)
+{
+       int status;
+
+       /*
+        * Start in sleep state, we'll get otg.set_suspend(false) call
+        * and power up when musb runtime_pm enable kicks in.
+        */
+       __twl4030_phy_power(twl, 0);
+       twl->asleep = 1;
+
+       status = twl4030_usb_linkstat(twl);
+       if (status >= 0 && status != USB_EVENT_NONE)
+               atomic_notifier_call_chain(&twl->otg.notifier, status,
+                       twl->otg.gadget);
+
        sysfs_notify(&twl->dev->kobj, NULL, "vbus");
 }
 
@@ -620,6 +714,8 @@ static int __devinit twl4030_usb_probe(struct platform_device *pdev)
        /* init spinlock for workqueue */
        spin_lock_init(&twl->lock);
 
+       INIT_DELAYED_WORK(&twl->id_workaround_work, twl4030_id_workaround_work);
+
        err = twl4030_usb_ldo_init(twl);
        if (err) {
                dev_err(&pdev->dev, "ldo init failed\n");
@@ -631,6 +727,8 @@ static int __devinit twl4030_usb_probe(struct platform_device *pdev)
        platform_set_drvdata(pdev, twl);
        if (device_create_file(&pdev->dev, &dev_attr_vbus))
                dev_warn(&pdev->dev, "could not create sysfs file\n");
+       if (device_create_file(&pdev->dev, &dev_attr_id))
+               dev_warn(&pdev->dev, "could not create sysfs file\n");
 
        ATOMIC_INIT_NOTIFIER_HEAD(&twl->otg.notifier);
 
@@ -653,9 +751,6 @@ static int __devinit twl4030_usb_probe(struct platform_device *pdev)
                return status;
        }
 
-       /* Power down phy or make it work according to
-        * current link state.
-        */
        twl4030_usb_phy_init(twl);
 
        dev_info(&pdev->dev, "Initialized TWL4030 USB module\n");
@@ -667,7 +762,9 @@ static int __exit twl4030_usb_remove(struct platform_device *pdev)
        struct twl4030_usb *twl = platform_get_drvdata(pdev);
        int val;
 
+       cancel_delayed_work(&twl->id_workaround_work);
        free_irq(twl->irq, twl);
+       device_remove_file(twl->dev, &dev_attr_id);
        device_remove_file(twl->dev, &dev_attr_vbus);
 
        /* set transceiver mode to power on defaults */