summaryrefslogtreecommitdiff
path: root/drivers/usb/musb/ux500.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/musb/ux500.c')
-rw-r--r--drivers/usb/musb/ux500.c106
1 files changed, 106 insertions, 0 deletions
diff --git a/drivers/usb/musb/ux500.c b/drivers/usb/musb/ux500.c
index 13a392913769..2c80004e0a83 100644
--- a/drivers/usb/musb/ux500.c
+++ b/drivers/usb/musb/ux500.c
@@ -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)