From 30a3bf7b30d86b94ad4fbdcf9cdce1dcf5037c58 Mon Sep 17 00:00:00 2001 From: David Lin Date: Thu, 14 Jul 2016 15:13:00 -0500 Subject: [PATCH] greybus: interface: add runtime pm support 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 Signed-off-by: Axel Haslam Reviewed-by: Johan Hovold Signed-off-by: Alex Elder --- drivers/staging/greybus/greybus.h | 1 + drivers/staging/greybus/interface.c | 109 +++++++++++++++++++++++++++ drivers/staging/greybus/kernel_ver.h | 13 ++++ 3 files changed, 123 insertions(+) diff --git a/drivers/staging/greybus/greybus.h b/drivers/staging/greybus/greybus.h index dbc9be05afc6..3e32028832c1 100644 --- a/drivers/staging/greybus/greybus.h +++ b/drivers/staging/greybus/greybus.h @@ -18,6 +18,7 @@ #include #include #include +#include #include #include "kernel_ver.h" diff --git a/drivers/staging/greybus/interface.c b/drivers/staging/greybus/interface.c index e7efc541895a..16e268f1b109 100644 --- a/drivers/staging/greybus/interface.c +++ b/drivers/staging/greybus/interface.c @@ -7,6 +7,8 @@ * Released under the GPLv2 only. */ +#include + #include "greybus.h" #include "greybus_trace.h" @@ -14,6 +16,11 @@ #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. */ diff --git a/drivers/staging/greybus/kernel_ver.h b/drivers/staging/greybus/kernel_ver.h index 80ed27c9a650..84beb2f6334c 100644 --- a/drivers/staging/greybus/kernel_ver.h +++ b/drivers/staging/greybus/kernel_ver.h @@ -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 */ -- 2.39.5