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@xxxxxxxxxx> --- drivers/tty/serdev/core.c | 48 +++++++++++++++++++++++++++++++++++++-- include/linux/serdev.h | 1 + 2 files changed, 47 insertions(+), 2 deletions(-) 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) -- 2.34.1