]> asedeno.scripts.mit.edu Git - linux.git/commitdiff
greybus: interface: add runtime pm support
authorDavid Lin <dtwlin@google.com>
Thu, 14 Jul 2016 20:13:00 +0000 (15:13 -0500)
committerAlex Elder <elder@linaro.org>
Thu, 14 Jul 2016 21:53:55 +0000 (16:53 -0500)
Configure and enable runtime pm support for the Interface. Refer to the
12.2. The Interface Lifecycle of the Greybus specification for details
on the requirements for transitioning from ENUMERATED to SUSPEND and
vice versa. All the Bundles for the Interface have to be either OFF or
SUSPENDED before the Interface can be autosuspended.

Testing Done:
 - Check the runtime_status of an interface driver and validate the
   suspend current of a module.

Signed-off-by: David Lin <dtwlin@google.com>
Signed-off-by: Axel Haslam <ahaslam@baylibre.com>
Reviewed-by: Johan Hovold <johan@hovoldconsulting.com>
Signed-off-by: Alex Elder <elder@linaro.org>
drivers/staging/greybus/greybus.h
drivers/staging/greybus/interface.c
drivers/staging/greybus/kernel_ver.h

index dbc9be05afc6220495b9698ea4e33a34e01e3f80..3e32028832c194756cf91218a797e590cce9aca2 100644 (file)
@@ -18,6 +18,7 @@
 #include <linux/slab.h>
 #include <linux/device.h>
 #include <linux/module.h>
+#include <linux/pm_runtime.h>
 #include <linux/idr.h>
 
 #include "kernel_ver.h"
index e7efc541895a3ddd8abb48e6df38392b59d12750..16e268f1b109592a9ef7cdcda635fc60e8434a2f 100644 (file)
@@ -7,6 +7,8 @@
  * Released under the GPLv2 only.
  */
 
+#include <linux/delay.h>
+
 #include "greybus.h"
 #include "greybus_trace.h"
 
 
 #define GB_INTERFACE_DEVICE_ID_BAD     0xff
 
+#define GB_INTERFACE_AUTOSUSPEND_MS                    3000
+
+/* Time required for interface to enter standby before disabling REFCLK */
+#define GB_INTERFACE_SUSPEND_HIBERNATE_DELAY_MS                        20
+
 /* Don't-care selector index */
 #define DME_SELECTOR_INDEX_NULL                0
 
@@ -36,6 +43,8 @@
 #define TOSHIBA_ES3_APBRIDGE_DPID      0x1001
 #define TOSHIBA_ES3_GBPHY_DPID 0x1002
 
+static int gb_interface_hibernate_link(struct gb_interface *intf);
+static int gb_interface_refclk_set(struct gb_interface *intf, bool enable);
 
 static int gb_interface_dme_attr_get(struct gb_interface *intf,
                                                        u16 attr, u32 *val)
@@ -505,9 +514,92 @@ static void gb_interface_release(struct device *dev)
        kfree(intf);
 }
 
+#ifdef CONFIG_PM_RUNTIME
+static int gb_interface_suspend(struct device *dev)
+{
+       struct gb_interface *intf = to_gb_interface(dev);
+       int ret, timesync_ret;
+
+       ret = gb_control_interface_suspend_prepare(intf->control);
+       if (ret)
+               return ret;
+
+       gb_timesync_interface_remove(intf);
+
+       ret = gb_control_suspend(intf->control);
+       if (ret)
+               goto err_hibernate_abort;
+
+       ret = gb_interface_hibernate_link(intf);
+       if (ret)
+               return ret;
+
+       /* Delay to allow interface to enter standby before disabling refclk */
+       msleep(GB_INTERFACE_SUSPEND_HIBERNATE_DELAY_MS);
+
+       ret = gb_interface_refclk_set(intf, false);
+       if (ret)
+               return ret;
+
+       return 0;
+
+err_hibernate_abort:
+       gb_control_interface_hibernate_abort(intf->control);
+
+       timesync_ret = gb_timesync_interface_add(intf);
+       if (timesync_ret) {
+               dev_err(dev, "failed to add to timesync: %d\n", timesync_ret);
+               return timesync_ret;
+       }
+
+       return ret;
+}
+
+static int gb_interface_resume(struct device *dev)
+{
+       struct gb_interface *intf = to_gb_interface(dev);
+       struct gb_svc *svc = intf->hd->svc;
+       int ret;
+
+       ret = gb_interface_refclk_set(intf, true);
+       if (ret)
+               return ret;
+
+       ret = gb_svc_intf_resume(svc, intf->interface_id);
+       if (ret)
+               return ret;
+
+       ret = gb_control_resume(intf->control);
+       if (ret)
+               return ret;
+
+       ret = gb_timesync_interface_add(intf);
+       if (ret) {
+               dev_err(dev, "failed to add to timesync: %d\n", ret);
+               return ret;
+       }
+
+       return 0;
+}
+
+static int gb_interface_runtime_idle(struct device *dev)
+{
+       pm_runtime_mark_last_busy(dev);
+       pm_request_autosuspend(dev);
+
+       return 0;
+}
+#endif
+
+static const struct dev_pm_ops gb_interface_pm_ops = {
+       SET_RUNTIME_PM_OPS(gb_interface_suspend, gb_interface_resume,
+                          gb_interface_runtime_idle)
+};
+
 struct device_type greybus_interface_type = {
        .name =         "greybus_interface",
        .release =      gb_interface_release,
+       .pm =           &gb_interface_pm_ops,
 };
 
 /*
@@ -553,6 +645,9 @@ struct gb_interface *gb_interface_create(struct gb_module *module,
        dev_set_name(&intf->dev, "%s.%u", dev_name(&module->dev),
                        interface_id);
 
+       pm_runtime_set_autosuspend_delay(&intf->dev,
+                                        GB_INTERFACE_AUTOSUSPEND_MS);
+
        trace_gb_interface_create(intf);
 
        return intf;
@@ -809,6 +904,11 @@ int gb_interface_enable(struct gb_interface *intf)
                goto err_destroy_bundles;
        }
 
+       pm_runtime_use_autosuspend(&intf->dev);
+       pm_runtime_get_noresume(&intf->dev);
+       pm_runtime_set_active(&intf->dev);
+       pm_runtime_enable(&intf->dev);
+
        list_for_each_entry_safe_reverse(bundle, tmp, &intf->bundles, links) {
                ret = gb_bundle_add(bundle);
                if (ret) {
@@ -821,6 +921,8 @@ int gb_interface_enable(struct gb_interface *intf)
 
        intf->enabled = true;
 
+       pm_runtime_put(&intf->dev);
+
        trace_gb_interface_enable(intf);
 
        return 0;
@@ -854,6 +956,8 @@ void gb_interface_disable(struct gb_interface *intf)
 
        trace_gb_interface_disable(intf);
 
+       pm_runtime_get_sync(&intf->dev);
+
        /* Set disconnected flag to avoid I/O during connection tear down. */
        if (intf->quirks & GB_INTERFACE_QUIRK_FORCED_DISABLE)
                intf->disconnected = true;
@@ -871,6 +975,11 @@ void gb_interface_disable(struct gb_interface *intf)
        intf->control = NULL;
 
        intf->enabled = false;
+
+       pm_runtime_disable(&intf->dev);
+       pm_runtime_set_suspended(&intf->dev);
+       pm_runtime_dont_use_autosuspend(&intf->dev);
+       pm_runtime_put_noidle(&intf->dev);
 }
 
 /* Enable TimeSync on an Interface control connection. */
index 80ed27c9a650a8f7fdc743bfced98753e242fb9c..84beb2f6334cd88ed729673ab0e43c5b934164cd 100644 (file)
@@ -389,4 +389,17 @@ static inline int kstrtobool(const char *s, bool *res)
 }
 #endif
 
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0)
+/*
+ * After commit b2b49ccbdd54 (PM: Kconfig: Set PM_RUNTIME if PM_SLEEP is
+ * selected) PM_RUNTIME is always set if PM is set, so files that are build
+ * conditionally if CONFIG_PM_RUNTIME is set may now be build if CONFIG_PM is
+ * set.
+ */
+
+#ifdef CONFIG_PM
+#define CONFIG_PM_RUNTIME
+#endif /* CONFIG_PM */
+#endif
+
 #endif /* __GREYBUS_KERNEL_VER_H */