diff --git a/drivers/staging/greybus/bundle.c b/drivers/staging/greybus/bundle.c index 80f54c977509..5bd7731237f5 100644 --- a/drivers/staging/greybus/bundle.c +++ b/drivers/staging/greybus/bundle.c @@ -89,9 +89,89 @@ static void gb_bundle_release(struct device *dev) kfree(bundle); } +#ifdef CONFIG_PM_RUNTIME + +static void gb_bundle_disable_all_connections(struct gb_bundle *bundle) +{ + struct gb_connection *connection; + + list_for_each_entry(connection, &bundle->connections, bundle_links) + gb_connection_disable(connection); +} + +static void gb_bundle_enable_all_connections(struct gb_bundle *bundle) +{ + struct gb_connection *connection; + + list_for_each_entry(connection, &bundle->connections, bundle_links) + gb_connection_enable(connection); +} + +static int gb_bundle_suspend(struct device *dev) +{ + struct gb_bundle *bundle = to_gb_bundle(dev); + const struct dev_pm_ops *pm = dev->driver->pm; + int ret; + + if (pm && pm->runtime_suspend) { + ret = pm->runtime_suspend(&bundle->dev); + if (ret) + return ret; + } else { + gb_bundle_disable_all_connections(bundle); + } + + ret = gb_control_bundle_suspend(bundle->intf->control, bundle->id); + if (ret) { + if (pm && pm->runtime_resume) + ret = pm->runtime_resume(dev); + else + gb_bundle_enable_all_connections(bundle); + + return ret; + } + + return 0; +} + +static int gb_bundle_resume(struct device *dev) +{ + struct gb_bundle *bundle = to_gb_bundle(dev); + const struct dev_pm_ops *pm = dev->driver->pm; + int ret; + + ret = gb_control_bundle_resume(bundle->intf->control, bundle->id); + if (ret) + return ret; + + if (pm && pm->runtime_resume) { + ret = pm->runtime_resume(dev); + if (ret) + return ret; + } else { + gb_bundle_enable_all_connections(bundle); + } + + return 0; +} + +static int gb_bundle_idle(struct device *dev) +{ + pm_runtime_mark_last_busy(dev); + pm_request_autosuspend(dev); + + return 0; +} +#endif + +static const struct dev_pm_ops gb_bundle_pm_ops = { + SET_RUNTIME_PM_OPS(gb_bundle_suspend, gb_bundle_resume, gb_bundle_idle) +}; + struct device_type greybus_bundle_type = { .name = "greybus_bundle", .release = gb_bundle_release, + .pm = &gb_bundle_pm_ops, }; /* diff --git a/drivers/staging/greybus/bundle.h b/drivers/staging/greybus/bundle.h index 3895f94f43c4..349845ee893c 100644 --- a/drivers/staging/greybus/bundle.h +++ b/drivers/staging/greybus/bundle.h @@ -40,4 +40,51 @@ struct gb_bundle *gb_bundle_create(struct gb_interface *intf, u8 bundle_id, int gb_bundle_add(struct gb_bundle *bundle); void gb_bundle_destroy(struct gb_bundle *bundle); +/* Bundle Runtime PM wrappers */ +#ifdef CONFIG_PM_RUNTIME +static inline int gb_pm_runtime_get_sync(struct gb_bundle *bundle) +{ + int retval; + + retval = pm_runtime_get_sync(&bundle->dev); + if (retval < 0) { + dev_err(&bundle->dev, + "pm_runtime_get_sync failed: %d\n", retval); + pm_runtime_put_noidle(&bundle->dev); + return retval; + } + + return 0; +} + +static inline int gb_pm_runtime_put_autosuspend(struct gb_bundle *bundle) +{ + int retval; + + pm_runtime_mark_last_busy(&bundle->dev); + retval = pm_runtime_put_autosuspend(&bundle->dev); + + return retval; +} + +static inline void gb_pm_runtime_get_noresume(struct gb_bundle *bundle) +{ + pm_runtime_get_noresume(&bundle->dev); +} + +static inline void gb_pm_runtime_put_noidle(struct gb_bundle *bundle) +{ + pm_runtime_put_noidle(&bundle->dev); +} + +#else +static inline int gb_pm_runtime_get_sync(struct gb_bundle *bundle) +{ return 0; } +static inline int gb_pm_runtime_put_autosuspend(struct gb_bundle *bundle) +{ return 0; } + +static inline void gb_pm_runtime_get_noresume(struct gb_bundle *bundle) {} +static inline void gb_pm_runtime_put_noidle(struct gb_bundle *bundle) {} +#endif + #endif /* __BUNDLE_H */ diff --git a/drivers/staging/greybus/core.c b/drivers/staging/greybus/core.c index ca3bad910aae..53d9ba151aeb 100644 --- a/drivers/staging/greybus/core.c +++ b/drivers/staging/greybus/core.c @@ -13,6 +13,8 @@ #include "greybus.h" #include "greybus_trace.h" +#define GB_BUNDLE_AUTOSUSPEND_MS 3000 + /* Allow greybus to be disabled at boot if needed */ static bool nogreybus; #ifdef MODULE @@ -162,6 +164,12 @@ static int greybus_probe(struct device *dev) if (!id) return -ENODEV; + retval = pm_runtime_get_sync(&bundle->intf->dev); + if (retval < 0) { + pm_runtime_put_noidle(&bundle->intf->dev); + return retval; + } + /* * FIXME: We need to perform error handling on bundle activate call * below when firmware is ready. We just allow the activate operation to @@ -169,6 +177,19 @@ static int greybus_probe(struct device *dev) */ gb_control_bundle_activate(bundle->intf->control, bundle->id); + /* + * Unbound bundle devices are always deactivated. During probe, the + * Runtime PM is set to enabled and active and the usage count is + * incremented. If the driver supports runtime PM, it should call + * pm_runtime_put() in its probe routine and pm_runtime_get_sync() + * in remove routine. + */ + pm_runtime_set_autosuspend_delay(dev, GB_BUNDLE_AUTOSUSPEND_MS); + pm_runtime_use_autosuspend(dev); + pm_runtime_get_noresume(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + retval = driver->probe(bundle, id); if (retval) { /* @@ -178,11 +199,19 @@ static int greybus_probe(struct device *dev) gb_control_bundle_deactivate(bundle->intf->control, bundle->id); + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); + pm_runtime_put_noidle(dev); + pm_runtime_dont_use_autosuspend(dev); + pm_runtime_put(&bundle->intf->dev); + return retval; } gb_timesync_schedule_asynchronous(bundle->intf); + pm_runtime_put(&bundle->intf->dev); + return 0; } @@ -191,6 +220,11 @@ static int greybus_remove(struct device *dev) struct greybus_driver *driver = to_greybus_driver(dev->driver); struct gb_bundle *bundle = to_gb_bundle(dev); struct gb_connection *connection; + int retval; + + retval = pm_runtime_get_sync(dev); + if (retval < 0) + dev_err(dev, "failed to resume bundle: %d\n", retval); /* * Disable (non-offloaded) connections early in case the interface is @@ -215,6 +249,12 @@ static int greybus_remove(struct device *dev) if (!bundle->intf->disconnected) gb_control_bundle_deactivate(bundle->intf->control, bundle->id); + pm_runtime_put_noidle(dev); + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); + pm_runtime_dont_use_autosuspend(dev); + pm_runtime_put_noidle(dev); + return 0; }