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 */