فهرست منبع

greybus: gbphy: add gbphy runtime pm support

Since GBphy is a child of the Bundle device driver, for those runtime pm
settings that are common to all the protocol drivers need to go in to
the GBphy bus driver.

Testing Done:
 - Check gbphy driver can be autosuspended

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>
David Lin 9 سال پیش
والد
کامیت
af5dc7f8c0
2فایلهای تغییر یافته به همراه98 افزوده شده و 1 حذف شده
  1. 58 1
      drivers/staging/greybus/gbphy.c
  2. 40 0
      drivers/staging/greybus/gbphy.h

+ 58 - 1
drivers/staging/greybus/gbphy.c

@@ -19,6 +19,8 @@
 #include "greybus.h"
 #include "gbphy.h"
 
+#define GB_GBPHY_AUTOSUSPEND_MS	3000
+
 struct gbphy_host {
 	struct gb_bundle *bundle;
 	struct list_head devices;
@@ -50,9 +52,25 @@ static void gbphy_dev_release(struct device *dev)
 	kfree(gbphy_dev);
 }
 
+#ifdef CONFIG_PM_RUNTIME
+static int gb_gbphy_idle(struct device *dev)
+{
+	pm_runtime_mark_last_busy(dev);
+	pm_request_autosuspend(dev);
+	return 0;
+}
+#endif
+
+static const struct dev_pm_ops gb_gbphy_pm_ops = {
+	SET_RUNTIME_PM_OPS(pm_generic_runtime_suspend,
+			   pm_generic_runtime_resume,
+			   gb_gbphy_idle)
+};
+
 static struct device_type greybus_gbphy_dev_type = {
 	.name	 =	"gbphy_device",
 	.release =	gbphy_dev_release,
+	.pm	=	&gb_gbphy_pm_ops,
 };
 
 static int gbphy_dev_uevent(struct device *dev, struct kobj_uevent_env *env)
@@ -118,12 +136,38 @@ static int gbphy_dev_probe(struct device *dev)
 	struct gbphy_driver *gbphy_drv = to_gbphy_driver(dev->driver);
 	struct gbphy_device *gbphy_dev = to_gbphy_dev(dev);
 	const struct gbphy_device_id *id;
+	int ret;
 
 	id = gbphy_dev_match_id(gbphy_dev, gbphy_drv);
 	if (!id)
 		return -ENODEV;
 
-	return gbphy_drv->probe(gbphy_dev, id);
+	/* for old kernels we need get_sync to resume parent devices */
+	ret = gb_pm_runtime_get_sync(gbphy_dev->bundle);
+	if (ret < 0)
+		return ret;
+
+	pm_runtime_set_autosuspend_delay(dev, GB_GBPHY_AUTOSUSPEND_MS);
+	pm_runtime_use_autosuspend(dev);
+	pm_runtime_get_noresume(dev);
+	pm_runtime_set_active(dev);
+	pm_runtime_enable(dev);
+
+	/*
+	 * Drivers should call put on the gbphy dev before returning
+	 * from probe if they support runtime pm.
+	 */
+	ret = gbphy_drv->probe(gbphy_dev, id);
+	if (ret) {
+		pm_runtime_disable(dev);
+		pm_runtime_set_suspended(dev);
+		pm_runtime_put_noidle(dev);
+		pm_runtime_dont_use_autosuspend(dev);
+	}
+
+	gb_pm_runtime_put_autosuspend(gbphy_dev->bundle);
+
+	return ret;
 }
 
 static int gbphy_dev_remove(struct device *dev)
@@ -132,6 +176,12 @@ static int gbphy_dev_remove(struct device *dev)
 	struct gbphy_device *gbphy_dev = to_gbphy_dev(dev);
 
 	gbphy_drv->remove(gbphy_dev);
+
+	pm_runtime_disable(dev);
+	pm_runtime_set_suspended(dev);
+	pm_runtime_put_noidle(dev);
+	pm_runtime_dont_use_autosuspend(dev);
+
 	return 0;
 }
 
@@ -211,6 +261,11 @@ static void gb_gbphy_disconnect(struct gb_bundle *bundle)
 {
 	struct gbphy_host *gbphy_host = greybus_get_drvdata(bundle);
 	struct gbphy_device *gbphy_dev, *temp;
+	int ret;
+
+	ret = gb_pm_runtime_get_sync(bundle);
+	if (ret < 0)
+		gb_pm_runtime_get_noresume(bundle);
 
 	list_for_each_entry_safe(gbphy_dev, temp, &gbphy_host->devices, list) {
 		list_del(&gbphy_dev->list);
@@ -251,6 +306,8 @@ static int gb_gbphy_probe(struct gb_bundle *bundle,
 		list_add(&gbphy_dev->list, &gbphy_host->devices);
 	}
 
+	gb_pm_runtime_put_autosuspend(bundle);
+
 	return 0;
 }
 

+ 40 - 0
drivers/staging/greybus/gbphy.h

@@ -66,5 +66,45 @@ void gb_gbphy_deregister_driver(struct gbphy_driver *driver);
 #define module_gbphy_driver(__gbphy_driver)	\
 	module_driver(__gbphy_driver, gb_gbphy_register, gb_gbphy_deregister)
 
+#ifdef CONFIG_PM_RUNTIME
+static inline int gbphy_runtime_get_sync(struct gbphy_device *gbphy_dev)
+{
+	struct device *dev = &gbphy_dev->dev;
+	int ret;
+
+	ret = pm_runtime_get_sync(dev);
+	if (ret < 0) {
+		dev_err(dev, "pm_runtime_get_sync failed: %d\n", ret);
+		pm_runtime_put_noidle(dev);
+		return ret;
+	}
+
+	return 0;
+}
+
+static inline void gbphy_runtime_put_autosuspend(struct gbphy_device *gbphy_dev)
+{
+	struct device *dev = &gbphy_dev->dev;
+
+	pm_runtime_mark_last_busy(dev);
+	pm_runtime_put_autosuspend(dev);
+}
+
+static inline void gbphy_runtime_get_noresume(struct gbphy_device *gbphy_dev)
+{
+	pm_runtime_get_noresume(&gbphy_dev->dev);
+}
+
+static inline void gbphy_runtime_put_noidle(struct gbphy_device *gbphy_dev)
+{
+	pm_runtime_put_noidle(&gbphy_dev->dev);
+}
+#else
+static inline int gbphy_runtime_get_sync(struct device *dev) { return 0; }
+static inline void gbphy_runtime_put_autosuspend(struct device *dev) {}
+static inline void gbphy_runtime_get_noresume(struct gbphy_device *gbphy_dev) {}
+static inline void gbphy_runtime_put_noidle(struct gbphy_device *gbphy_dev) {}
+#endif
+
 #endif /* __GBPHY_H */