Merge remote-tracking branch 'lsk/v3.10/topic/arm64-misc' into linux-linaro-lsk
[firefly-linux-kernel-4.4.55.git] / drivers / usb / musb / ux500.c
index 13a392913769ad6e11ad36d5a24a7c0233eb10a9..2c80004e0a83029b52b7ea13f2d4c97194f40412 100644 (file)
@@ -26,6 +26,7 @@
 #include <linux/err.h>
 #include <linux/io.h>
 #include <linux/platform_device.h>
+#include <linux/usb/musb-ux500.h>
 
 #include "musb_core.h"
 
@@ -36,6 +37,98 @@ struct ux500_glue {
 };
 #define glue_to_musb(g)        platform_get_drvdata(g->musb)
 
+static void ux500_musb_set_vbus(struct musb *musb, int is_on)
+{
+       u8            devctl;
+       unsigned long timeout = jiffies + msecs_to_jiffies(1000);
+       /* HDRC controls CPEN, but beware current surges during device
+        * connect.  They can trigger transient overcurrent conditions
+        * that must be ignored.
+        */
+
+       devctl = musb_readb(musb->mregs, MUSB_DEVCTL);
+
+       if (is_on) {
+               if (musb->xceiv->state == OTG_STATE_A_IDLE) {
+                       /* start the session */
+                       devctl |= MUSB_DEVCTL_SESSION;
+                       musb_writeb(musb->mregs, MUSB_DEVCTL, devctl);
+                       /*
+                        * Wait for the musb to set as A device to enable the
+                        * VBUS
+                        */
+                       while (musb_readb(musb->mregs, MUSB_DEVCTL) & 0x80) {
+
+                               if (time_after(jiffies, timeout)) {
+                                       dev_err(musb->controller,
+                                       "configured as A device timeout");
+                                       break;
+                               }
+                       }
+
+               } else {
+                       musb->is_active = 1;
+                       musb->xceiv->otg->default_a = 1;
+                       musb->xceiv->state = OTG_STATE_A_WAIT_VRISE;
+                       devctl |= MUSB_DEVCTL_SESSION;
+                       MUSB_HST_MODE(musb);
+               }
+       } else {
+               musb->is_active = 0;
+
+               /* NOTE: we're skipping A_WAIT_VFALL -> A_IDLE and jumping
+                * right to B_IDLE...
+                */
+               musb->xceiv->otg->default_a = 0;
+               devctl &= ~MUSB_DEVCTL_SESSION;
+               MUSB_DEV_MODE(musb);
+       }
+       musb_writeb(musb->mregs, MUSB_DEVCTL, devctl);
+
+       /*
+        * Devctl values will be updated after vbus goes below
+        * session_valid. The time taken depends on the capacitance
+        * on VBUS line. The max discharge time can be upto 1 sec
+        * as per the spec. Typically on our platform, it is 200ms
+        */
+       if (!is_on)
+               mdelay(200);
+
+       dev_dbg(musb->controller, "VBUS %s, devctl %02x\n",
+               usb_otg_state_string(musb->xceiv->state),
+               musb_readb(musb->mregs, MUSB_DEVCTL));
+}
+
+static int musb_otg_notifications(struct notifier_block *nb,
+               unsigned long event, void *unused)
+{
+       struct musb *musb = container_of(nb, struct musb, nb);
+
+       dev_dbg(musb->controller, "musb_otg_notifications %ld %s\n",
+                       event, usb_otg_state_string(musb->xceiv->state));
+
+       switch (event) {
+       case UX500_MUSB_ID:
+               dev_dbg(musb->controller, "ID GND\n");
+               ux500_musb_set_vbus(musb, 1);
+               break;
+       case UX500_MUSB_VBUS:
+               dev_dbg(musb->controller, "VBUS Connect\n");
+               break;
+       case UX500_MUSB_NONE:
+               dev_dbg(musb->controller, "VBUS Disconnect\n");
+               if (is_host_active(musb))
+                       ux500_musb_set_vbus(musb, 0);
+               else
+                       musb->xceiv->state = OTG_STATE_B_IDLE;
+               break;
+       default:
+               dev_dbg(musb->controller, "ID float\n");
+               return NOTIFY_DONE;
+       }
+       return NOTIFY_OK;
+}
+
 static irqreturn_t ux500_musb_interrupt(int irq, void *__hci)
 {
        unsigned long   flags;
@@ -58,12 +151,21 @@ static irqreturn_t ux500_musb_interrupt(int irq, void *__hci)
 
 static int ux500_musb_init(struct musb *musb)
 {
+       int status;
+
        musb->xceiv = usb_get_phy(USB_PHY_TYPE_USB2);
        if (IS_ERR_OR_NULL(musb->xceiv)) {
                pr_err("HS USB OTG: no transceiver configured\n");
                return -EPROBE_DEFER;
        }
 
+       musb->nb.notifier_call = musb_otg_notifications;
+       status = usb_register_notifier(musb->xceiv, &musb->nb);
+       if (status < 0) {
+               dev_dbg(musb->controller, "notification register failed\n");
+               return status;
+       }
+
        musb->isr = ux500_musb_interrupt;
 
        return 0;
@@ -71,6 +173,8 @@ static int ux500_musb_init(struct musb *musb)
 
 static int ux500_musb_exit(struct musb *musb)
 {
+       usb_unregister_notifier(musb->xceiv, &musb->nb);
+
        usb_put_phy(musb->xceiv);
 
        return 0;
@@ -79,6 +183,8 @@ static int ux500_musb_exit(struct musb *musb)
 static const struct musb_platform_ops ux500_ops = {
        .init           = ux500_musb_init,
        .exit           = ux500_musb_exit,
+
+       .set_vbus       = ux500_musb_set_vbus,
 };
 
 static int ux500_probe(struct platform_device *pdev)