@@ -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 = {
@@ -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)
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(-)