diff mbox series

serdev: Add support for wakeup-source

Message ID 20250213143430.3893651-1-loic.poulain@linaro.org
State New
Headers show
Series serdev: Add support for wakeup-source | expand

Commit Message

Loic Poulain Feb. 13, 2025, 2:34 p.m. UTC
This brings support for dedicated interrupt as wakeup source into the
serdev core, similarly to the I2C bus, and aligned with the documentation:
Documentation/devicetree/bindings/power/wakeup-source.txt

As an example, this can be used for bluetooth serial devices with dedicated
controller-to-host wakeup pin.

Signed-off-by: Loic Poulain <loic.poulain@linaro.org>
---
 drivers/tty/serdev/core.c | 48 +++++++++++++++++++++++++++++++++++++--
 include/linux/serdev.h    |  1 +
 2 files changed, 47 insertions(+), 2 deletions(-)
diff mbox series

Patch

diff --git a/drivers/tty/serdev/core.c b/drivers/tty/serdev/core.c
index ebf0bbc2cff2..2d016fa546ca 100644
--- a/drivers/tty/serdev/core.c
+++ b/drivers/tty/serdev/core.c
@@ -13,8 +13,10 @@ 
 #include <linux/module.h>
 #include <linux/of.h>
 #include <linux/of_device.h>
+#include <linux/of_irq.h>
 #include <linux/pm_domain.h>
 #include <linux/pm_runtime.h>
+#include <linux/pm_wakeirq.h>
 #include <linux/property.h>
 #include <linux/sched.h>
 #include <linux/serdev.h>
@@ -164,6 +166,9 @@  int serdev_device_open(struct serdev_device *serdev)
 		goto err_close;
 	}
 
+	if (serdev->wakeup_source)
+		device_wakeup_enable(&serdev->dev);
+
 	return 0;
 
 err_close:
@@ -181,6 +186,9 @@  void serdev_device_close(struct serdev_device *serdev)
 	if (!ctrl || !ctrl->ops->close)
 		return;
 
+	if (serdev->wakeup_source)
+		device_wakeup_disable(&serdev->dev);
+
 	pm_runtime_put(&ctrl->dev);
 
 	ctrl->ops->close(ctrl);
@@ -406,18 +414,52 @@  int serdev_device_break_ctl(struct serdev_device *serdev, int break_state)
 }
 EXPORT_SYMBOL_GPL(serdev_device_break_ctl);
 
+static int serdev_wakeup_attach(struct device *dev)
+{
+	int wakeirq;
+
+	if (!of_property_read_bool(dev->of_node, "wakeup-source"))
+		return 0;
+
+	to_serdev_device(dev)->wakeup_source = true;
+
+	device_set_wakeup_capable(dev, true);
+
+	wakeirq = of_irq_get_byname(dev->of_node, "wakeup");
+	if (wakeirq == -EPROBE_DEFER)
+		return -EPROBE_DEFER;
+	else if (wakeirq > 0)
+		return dev_pm_set_dedicated_wake_irq(dev, wakeirq);
+
+	return 0;
+}
+
+static void serdev_wakeup_detach(struct device *dev)
+{
+	device_init_wakeup(dev, false);
+	dev_pm_clear_wake_irq(dev);
+}
+
 static int serdev_drv_probe(struct device *dev)
 {
 	const struct serdev_device_driver *sdrv = to_serdev_device_driver(dev->driver);
 	int ret;
 
-	ret = dev_pm_domain_attach(dev, true);
+	ret = serdev_wakeup_attach(dev);
 	if (ret)
 		return ret;
 
+	ret = dev_pm_domain_attach(dev, true);
+	if (ret) {
+		serdev_wakeup_detach(dev);
+		return ret;
+	}
+
 	ret = sdrv->probe(to_serdev_device(dev));
-	if (ret)
+	if (ret) {
 		dev_pm_domain_detach(dev, true);
+		serdev_wakeup_detach(dev);
+	}
 
 	return ret;
 }
@@ -429,6 +471,8 @@  static void serdev_drv_remove(struct device *dev)
 		sdrv->remove(to_serdev_device(dev));
 
 	dev_pm_domain_detach(dev, true);
+
+	serdev_wakeup_detach(dev);
 }
 
 static const struct bus_type serdev_bus_type = {
diff --git a/include/linux/serdev.h b/include/linux/serdev.h
index ff78efc1f60d..2b3ee7b2c141 100644
--- a/include/linux/serdev.h
+++ b/include/linux/serdev.h
@@ -47,6 +47,7 @@  struct serdev_device {
 	const struct serdev_device_ops *ops;
 	struct completion write_comp;
 	struct mutex write_lock;
+	bool wakeup_source;
 };
 
 static inline struct serdev_device *to_serdev_device(struct device *d)