summaryrefslogtreecommitdiff
path: root/drivers/usb/musb
diff options
context:
space:
mode:
authorTony Lindgren <tony@atomide.com>2016-09-12 21:48:28 -0500
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2016-09-13 17:20:02 +0200
commit467d5c980709b262df288a0d0e7780f295c56882 (patch)
treeba071464e0bb05325008b1f14865b2f225cc35e7 /drivers/usb/musb
parent4d719209be3676936c27184cb9bff10f93f8fa64 (diff)
usb: musb: Implement session bit based runtime PM for musb-core
We want to keep musb enabled always when the session bit is set. This simplifies the PM runtime and allows making it more generic across the various glue layers. So far the only exception to just following the session bit is host mode disconnect where the session bit stays set. In that case, just allow PM and let the PM runtime autoidle timeout deal with it. Signed-off-by: Tony Lindgren <tony@atomide.com> [b-liu@ti.com: changed using dev_dbg() to musb_dbg()] Signed-off-by: Bin Liu <b-liu@ti.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/usb/musb')
-rw-r--r--drivers/usb/musb/musb_core.c66
-rw-r--r--drivers/usb/musb/musb_core.h1
2 files changed, 67 insertions, 0 deletions
diff --git a/drivers/usb/musb/musb_core.c b/drivers/usb/musb/musb_core.c
index 74fc3069cb42..7104604914ab 100644
--- a/drivers/usb/musb/musb_core.c
+++ b/drivers/usb/musb/musb_core.c
@@ -1831,11 +1831,77 @@ static const struct attribute_group musb_attr_group = {
.attrs = musb_attributes,
};
+#define MUSB_QUIRK_B_INVALID_VBUS_91 (MUSB_DEVCTL_BDEVICE | \
+ (2 << MUSB_DEVCTL_VBUS_SHIFT) | \
+ MUSB_DEVCTL_SESSION)
+#define MUSB_QUIRK_A_DISCONNECT_19 ((3 << MUSB_DEVCTL_VBUS_SHIFT) | \
+ MUSB_DEVCTL_SESSION)
+
+/*
+ * Check the musb devctl session bit to determine if we want to
+ * allow PM runtime for the device. In general, we want to keep things
+ * active when the session bit is set except after host disconnect.
+ *
+ * Only called from musb_irq_work. If this ever needs to get called
+ * elsewhere, proper locking must be implemented for musb->session.
+ */
+static void musb_pm_runtime_check_session(struct musb *musb)
+{
+ u8 devctl, s;
+ int error;
+
+ devctl = musb_readb(musb->mregs, MUSB_DEVCTL);
+
+ /* Handle session status quirks first */
+ s = MUSB_DEVCTL_FSDEV | MUSB_DEVCTL_LSDEV |
+ MUSB_DEVCTL_HR;
+ switch (devctl & ~s) {
+ case MUSB_QUIRK_B_INVALID_VBUS_91:
+ if (musb->session)
+ break;
+ musb_dbg(musb, "Allow PM as device with invalid vbus: %02x",
+ devctl);
+ return;
+ case MUSB_QUIRK_A_DISCONNECT_19:
+ if (!musb->session)
+ break;
+ musb_dbg(musb, "Allow PM on possible host mode disconnect");
+ pm_runtime_mark_last_busy(musb->controller);
+ pm_runtime_put_autosuspend(musb->controller);
+ musb->session = false;
+ return;
+ default:
+ break;
+ }
+
+ /* No need to do anything if session has not changed */
+ s = devctl & MUSB_DEVCTL_SESSION;
+ if (s == musb->session)
+ return;
+
+ /* Block PM or allow PM? */
+ if (s) {
+ musb_dbg(musb, "Block PM on active session: %02x", devctl);
+ error = pm_runtime_get_sync(musb->controller);
+ if (error < 0)
+ dev_err(musb->controller, "Could not enable: %i\n",
+ error);
+ } else {
+ musb_dbg(musb, "Allow PM with no session: %02x", devctl);
+ pm_runtime_mark_last_busy(musb->controller);
+ pm_runtime_put_autosuspend(musb->controller);
+ }
+
+ musb->session = s;
+}
+
/* Only used to provide driver mode change events */
static void musb_irq_work(struct work_struct *data)
{
struct musb *musb = container_of(data, struct musb, irq_work);
+ musb_pm_runtime_check_session(musb);
+
if (musb->xceiv->otg->state != musb->xceiv_old_state) {
musb->xceiv_old_state = musb->xceiv->otg->state;
sysfs_notify(&musb->controller->kobj, NULL, "mode");
diff --git a/drivers/usb/musb/musb_core.h b/drivers/usb/musb/musb_core.h
index b55a776b03eb..65288a53c19b 100644
--- a/drivers/usb/musb/musb_core.h
+++ b/drivers/usb/musb/musb_core.h
@@ -378,6 +378,7 @@ struct musb {
u8 min_power; /* vbus for periph, in mA/2 */
int port_mode; /* MUSB_PORT_MODE_* */
+ bool session;
bool is_host;
int a_wait_bcon; /* VBUS timeout in msecs */