[PATCH] usb: musb: support for OFF-mode

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



using a wrapper between the transceiver driver and the controller
driver to signal the controller driver to turn on/off the controller
when VBUS event is detected.

based-on: Heikki Krogerus <ext-heikki.krogerus@xxxxxxxxx>
Signed-off-by: Arnaud Mandy <ext-arnaud.2.mandy@xxxxxxxxx>
---
 drivers/usb/musb/musb_core.c  |   30 +++++++++++++++++-
 drivers/usb/musb/musb_core.h  |   17 +++++++++-
 drivers/usb/musb/omap2430.c   |   66 ++++++++++++++++++++++++++++++++++++++++-
 drivers/usb/otg/otg.c         |   16 ++++++++++
 drivers/usb/otg/twl4030-usb.c |    2 +
 include/linux/usb/otg.h       |   11 +++++++
 6 files changed, 137 insertions(+), 5 deletions(-)

diff --git a/drivers/usb/musb/musb_core.c b/drivers/usb/musb/musb_core.c
index 2f59892..ab97a02 100644
--- a/drivers/usb/musb/musb_core.c
+++ b/drivers/usb/musb/musb_core.c
@@ -965,6 +965,8 @@ void musb_start(struct musb *musb)
 	}
 	musb_platform_enable(musb);
 	musb_writeb(regs, MUSB_DEVCTL, devctl);
+
+	musb_save_context(musb);
 }
 
 
@@ -1948,6 +1950,25 @@ static void musb_free(struct musb *musb)
 #endif
 }
 
+int musb_power_controller(struct otg_controller *controller, bool vbus)
+{
+	struct musb *musb = dev_to_musb(controller->dev);
+
+	if (!musb->off_mode_support)
+		return 0;
+
+	if (vbus)
+		musb_power_on_controller(musb);
+	else
+		musb_power_off_controller(musb);
+
+	return 0;
+}
+
+struct otg_controller musb_controller = {
+	.power_controller = musb_power_controller,
+};
+
 /*
  * Perform generic per-controller initialization.
  *
@@ -2044,6 +2065,9 @@ bad_config:
 		goto fail2;
 	}
 
+	musb_controller.dev = dev;
+	musb->xceiv->controller = &musb_controller;
+
 #ifndef CONFIG_MUSB_PIO_ONLY
 	if (use_dma && dev->dma_mask) {
 		struct dma_controller	*c;
@@ -2313,7 +2337,8 @@ void musb_save_context(struct musb *musb)
 
 	musb_writeb(musb_base, MUSB_INDEX, musb_context.index);
 
-	musb_platform_save_context(musb, &musb_context);
+	if (!musb->off_mode)
+		musb_platform_save_context(musb, &musb_context);
 }
 
 void musb_restore_context(struct musb *musb)
@@ -2322,7 +2347,8 @@ void musb_restore_context(struct musb *musb)
 	void __iomem *musb_base = musb->mregs;
 	void __iomem *ep_target_regs;
 
-	musb_platform_restore_context(musb, &musb_context);
+	if (!musb->off_mode)
+		musb_platform_restore_context(musb, &musb_context);
 
 	if (is_host_enabled(musb)) {
 		musb_writew(musb_base, MUSB_FRAME, musb_context.frame);
diff --git a/drivers/usb/musb/musb_core.h b/drivers/usb/musb/musb_core.h
index 1e3da4e..8105a47 100644
--- a/drivers/usb/musb/musb_core.h
+++ b/drivers/usb/musb/musb_core.h
@@ -448,6 +448,11 @@ struct musb {
 	struct usb_gadget	g;			/* the gadget */
 	struct usb_gadget_driver *gadget_driver;	/* its driver */
 #endif
+	/* true if off-mode is supported */
+	unsigned		off_mode_support:1;
+
+	/* true if off-mode is requested */
+	unsigned                off_mode:1;
 
 	/* true if we're using dma */
 	unsigned		use_dma:1;
@@ -498,8 +503,16 @@ extern void musb_platform_restore_context(struct musb *musb,
 #define musb_platform_save_context(m, x)	do {} while (0)
 #define musb_platform_restore_context(m, x)	do {} while (0)
 #endif
-
-#endif
+extern void musb_power_on_controller(struct musb *musb);
+extern void musb_power_off_controller(struct musb *musb);
+void musb_save_context(struct musb *musb);
+void musb_restore_context(struct musb *musb);
+#else
+static inline void musb_power_on_controller(struct musb *musb) {};
+static inline void musb_power_off_controller(struct musb *musb) {};
+static inline void musb_save_context(struct musb *musb) {};
+static inline void musb_restore_context(struct musb *musb) {};
+#endif /* CONFIG_PM */
 
 static inline void musb_set_vbus(struct musb *musb, int is_on)
 {
diff --git a/drivers/usb/musb/omap2430.c b/drivers/usb/musb/omap2430.c
index 21cff53..3cc894b 100644
--- a/drivers/usb/musb/omap2430.c
+++ b/drivers/usb/musb/omap2430.c
@@ -220,6 +220,9 @@ int __init musb_platform_init(struct musb *musb)
 
 	musb_platform_resume(musb);
 
+#ifdef CONFIG_PM
+	musb->off_mode_support = 1;
+#endif
 	l = musb_readl(musb->mregs, OTG_SYSCONFIG);
 	l &= ~ENABLEWAKEUP;	/* disable wakeup */
 	l &= ~NOSTDBY;		/* remove possible nostdby */
@@ -271,7 +274,68 @@ void musb_platform_restore_context(struct musb *musb,
 	musb_writel(musb->mregs, OTG_SYSCONFIG, musb_context->otg_sysconfig);
 	musb_writel(musb->mregs, OTG_FORCESTDBY, musb_context->otg_forcestandby);
 }
-#endif
+
+void musb_power_off_controller(struct musb *musb)
+{
+	u32 l;
+
+	DBG(3, "allow OFF-mode\n");
+
+	l = musb_readl(musb->mregs, OTG_FORCESTDBY);
+	l |= ENABLEFORCE;	/* enable MSTANDBY */
+	musb_writel(musb->mregs, OTG_FORCESTDBY, l);
+
+	l = musb_readl(musb->mregs, OTG_FORCESTDBY);
+	l |= ENABLEWAKEUP;	/* enable wakeup */
+	l &= ~NOSTDBY;		/* disable nostdby */
+	l |= SMARTSTDBY;	/* enable smart standby */
+
+	l |= AUTOIDLE;		/* enable auto idle */
+	l &= ~NOIDLE;		/* disable noidle */
+	l |= SMARTIDLE;		/* enable smart idle */
+
+	musb_writel(musb->mregs, OTG_SYSCONFIG, l);
+
+	if (!cpu_is_omap3430())
+		l |= AUTOIDLE;		/* enable auto idle */
+	musb_writel(musb->mregs, OTG_SYSCONFIG, l);
+}
+EXPORT_SYMBOL_GPL(musb_power_off_controller);
+
+void musb_power_on_controller(struct musb *musb)
+{
+	u32 l;
+
+	DBG(3, "wake-up from OFF-mode\n");
+
+	l = musb_readl(musb->mregs, OTG_SYSCONFIG);
+	l &= ~ENABLEWAKEUP;	/* disable wakeup */
+	musb_writel(musb->mregs, OTG_SYSCONFIG, l);
+
+	l = musb_readl(musb->mregs, OTG_FORCESTDBY);
+	l &= ~ENABLEFORCE;	/* disable MSTANDBY */
+	musb_writel(musb->mregs, OTG_FORCESTDBY, l);
+
+	l = musb_readl(musb->mregs, OTG_SYSCONFIG);
+	l |= NOSTDBY;		/* enable nostdby */
+	l &= ~SMARTSTDBY;	/* disable smart standby */
+
+	l &= ~AUTOIDLE;		/* disable auto idle */
+	l |= NOIDLE;		/* enable noidle */
+	l &= ~SMARTIDLE;	/* disable smart idle */
+	musb_writel(musb->mregs, OTG_SYSCONFIG, l);
+
+	l = musb_readl(musb->mregs, OTG_INTERFSEL);
+	l |= ULPI_12PIN;
+	musb_writel(musb->mregs, OTG_INTERFSEL, l);
+
+	/* Restore register context */
+	musb->off_mode = 1;
+	musb_restore_context(musb);
+	musb->off_mode = 0;
+}
+EXPORT_SYMBOL_GPL(musb_power_on_controller);
+#endif /* CONFIG_PM */
 
 int musb_platform_suspend(struct musb *musb)
 {
diff --git a/drivers/usb/otg/otg.c b/drivers/usb/otg/otg.c
index 0a43a7d..8ab45a7 100644
--- a/drivers/usb/otg/otg.c
+++ b/drivers/usb/otg/otg.c
@@ -64,3 +64,19 @@ int otg_set_transceiver(struct otg_transceiver *x)
 	return 0;
 }
 EXPORT_SYMBOL(otg_set_transceiver);
+
+/**
+ * otg_power_controller - control power on/off of tranceiver.
+ * @x: the USB OTG transceiver to be used; or NULL
+ * @vbus: VBUS presence.
+ * This call is to save and power off the usb controller based on
+ * VBUS level detected by the transceiver.
+ */
+int otg_power_controller(struct otg_transceiver *x, bool vbus)
+{
+	if (x->controller && x->controller->power_controller)
+		x->controller->power_controller(x->controller, vbus);
+	return 0;
+}
+EXPORT_SYMBOL(otg_power_controller);
+
diff --git a/drivers/usb/otg/twl4030-usb.c b/drivers/usb/otg/twl4030-usb.c
index 488b0a3..3aa84ea 100644
--- a/drivers/usb/otg/twl4030-usb.c
+++ b/drivers/usb/otg/twl4030-usb.c
@@ -503,6 +503,7 @@ static void twl4030_phy_suspend(struct twl4030_usb *twl, int controller_off)
 
 	twl4030_phy_power(twl, 0);
 	twl->asleep = 1;
+	otg_power_controller(&twl->otg, 0);
 }
 
 static void twl4030_phy_resume(struct twl4030_usb *twl)
@@ -516,6 +517,7 @@ static void twl4030_phy_resume(struct twl4030_usb *twl)
 	if (twl->usb_mode == T2_USB_MODE_ULPI)
 		twl4030_i2c_access(twl, 0);
 	twl->asleep = 0;
+	otg_power_controller(&twl->otg, 1);
 }
 
 static int twl4030_usb_ldo_init(struct twl4030_usb *twl)
diff --git a/include/linux/usb/otg.h b/include/linux/usb/otg.h
index e328a89..77c4bdc 100644
--- a/include/linux/usb/otg.h
+++ b/include/linux/usb/otg.h
@@ -60,6 +60,14 @@ struct otg_io_access_ops {
 	int (*write)(struct otg_transceiver *otg, u32 val, u32 reg);
 };
 
+/* the otg driver needs to inform the usb controller driver when vbus
+ * is present or not.
+ */
+struct otg_controller {
+	struct device *dev;
+	int (*power_controller)(struct otg_controller *otg, bool vbus);
+};
+
 /*
  * the otg driver needs to interact with both device side and host side
  * usb controllers.  it decides which controller is active at a given
@@ -93,6 +101,8 @@ struct otg_transceiver {
 	u16			port_status;
 	u16			port_change;
 
+	struct otg_controller   *controller;
+
 	/* initialize/shutdown the OTG controller */
 	int	(*init)(struct otg_transceiver *otg);
 	void	(*shutdown)(struct otg_transceiver *otg);
@@ -171,6 +181,7 @@ otg_shutdown(struct otg_transceiver *otg)
 /* for usb host and peripheral controller drivers */
 extern struct otg_transceiver *otg_get_transceiver(void);
 extern void otg_put_transceiver(struct otg_transceiver *);
+extern int otg_power_controller(struct otg_transceiver *, bool);
 
 /* Context: can sleep */
 static inline int
-- 
1.6.0.4

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux