Bluetooth: btnxpuart: Implement host-wakeup feature
authorNeeraj Sanjay Kale <neeraj.sanjaykale@nxp.com>
Mon, 14 Apr 2025 17:59:52 +0000 (23:29 +0530)
committerLuiz Augusto von Dentz <luiz.von.dentz@intel.com>
Wed, 21 May 2025 14:28:07 +0000 (10:28 -0400)
This implements host wakeup feature by reading the device tree property
wakeup-source and 'wakeup' interrupt, and nxp,wakeout-pin, and configuring
it as a FALLING EDGE triggered interrupt.

When host is suspended, a trigger from the WAKE_OUT pin of the
controller wakes it up.

To enable this feature, both device tree properties are needed to be
defined.

Signed-off-by: Neeraj Sanjay Kale <neeraj.sanjaykale@nxp.com>
Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
drivers/bluetooth/btnxpuart.c

index 604ab2bba231c5c883a743e06b51f97f07bebad3..b34623a69b8ae976645f619c3c843962a9311284 100644 (file)
@@ -17,6 +17,7 @@
 #include <linux/crc32.h>
 #include <linux/string_helpers.h>
 #include <linux/gpio/consumer.h>
+#include <linux/of_irq.h>
 
 #include <net/bluetooth/bluetooth.h>
 #include <net/bluetooth/hci_core.h>
@@ -143,7 +144,9 @@ struct ps_data {
        bool  driver_sent_cmd;
        u16   h2c_ps_interval;
        u16   c2h_ps_interval;
+       bool  wakeup_source;
        struct gpio_desc *h2c_ps_gpio;
+       s32 irq_handler;
        struct hci_dev *hdev;
        struct work_struct work;
        struct timer_list ps_timer;
@@ -476,12 +479,21 @@ static void ps_timeout_func(struct timer_list *t)
        }
 }
 
+static irqreturn_t ps_host_wakeup_irq_handler(int irq, void *priv)
+{
+       struct btnxpuart_dev *nxpdev = (struct btnxpuart_dev *)priv;
+
+       bt_dev_dbg(nxpdev->hdev, "Host wakeup interrupt");
+       return IRQ_HANDLED;
+}
 static int ps_setup(struct hci_dev *hdev)
 {
        struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev);
        struct serdev_device *serdev = nxpdev->serdev;
        struct ps_data *psdata = &nxpdev->psdata;
+       int ret;
 
+       /* Out-Of-Band Device Wakeup */
        psdata->h2c_ps_gpio = devm_gpiod_get_optional(&serdev->dev, "device-wakeup",
                                                      GPIOD_OUT_LOW);
        if (IS_ERR(psdata->h2c_ps_gpio)) {
@@ -493,11 +505,37 @@ static int ps_setup(struct hci_dev *hdev)
        if (device_property_read_u8(&serdev->dev, "nxp,wakein-pin", &psdata->h2c_wakeup_gpio)) {
                psdata->h2c_wakeup_gpio = 0xff; /* 0xff: use default pin/gpio */
        } else if (!psdata->h2c_ps_gpio) {
-               bt_dev_warn(hdev, "nxp,wakein-pin property without device-wakeup GPIO");
+               bt_dev_warn(hdev, "nxp,wakein-pin property without device-wakeup-gpios");
                psdata->h2c_wakeup_gpio = 0xff;
        }
 
-       device_property_read_u8(&serdev->dev, "nxp,wakeout-pin", &psdata->c2h_wakeup_gpio);
+       /* Out-Of-Band Host Wakeup */
+       if (of_property_read_bool(serdev->dev.of_node, "wakeup-source")) {
+               psdata->irq_handler = of_irq_get_byname(serdev->dev.of_node, "wakeup");
+               bt_dev_info(nxpdev->hdev, "irq_handler: %d", psdata->irq_handler);
+               if (psdata->irq_handler > 0)
+                       psdata->wakeup_source = true;
+       }
+
+       if (device_property_read_u8(&serdev->dev, "nxp,wakeout-pin", &psdata->c2h_wakeup_gpio)) {
+               psdata->c2h_wakeup_gpio = 0xff;
+               if (psdata->wakeup_source) {
+                       bt_dev_warn(hdev, "host wakeup interrupt without nxp,wakeout-pin");
+                       psdata->wakeup_source = false;
+               }
+       } else if (!psdata->wakeup_source) {
+               bt_dev_warn(hdev, "nxp,wakeout-pin property without host wakeup interrupt");
+               psdata->c2h_wakeup_gpio = 0xff;
+       }
+
+       if (psdata->wakeup_source) {
+               ret = devm_request_irq(&serdev->dev, psdata->irq_handler,
+                                       ps_host_wakeup_irq_handler,
+                                       IRQF_ONESHOT | IRQF_TRIGGER_FALLING,
+                                       dev_name(&serdev->dev), nxpdev);
+               disable_irq(psdata->irq_handler);
+               device_init_wakeup(&serdev->dev, true);
+       }
 
        psdata->hdev = hdev;
        INIT_WORK(&psdata->work, ps_work_func);
@@ -637,12 +675,10 @@ static void ps_init(struct hci_dev *hdev)
 
        psdata->ps_state = PS_STATE_AWAKE;
 
-       if (psdata->c2h_wakeup_gpio) {
+       if (psdata->c2h_wakeup_gpio != 0xff)
                psdata->c2h_wakeupmode = BT_HOST_WAKEUP_METHOD_GPIO;
-       } else {
+       else
                psdata->c2h_wakeupmode = BT_HOST_WAKEUP_METHOD_NONE;
-               psdata->c2h_wakeup_gpio = 0xff;
-       }
 
        psdata->cur_h2c_wakeupmode = WAKEUP_METHOD_INVALID;
        if (psdata->h2c_ps_gpio)
@@ -1821,6 +1857,11 @@ static int nxp_serdev_suspend(struct device *dev)
        struct ps_data *psdata = &nxpdev->psdata;
 
        ps_control(psdata->hdev, PS_STATE_SLEEP);
+
+       if (psdata->wakeup_source) {
+               enable_irq_wake(psdata->irq_handler);
+               enable_irq(psdata->irq_handler);
+       }
        return 0;
 }
 
@@ -1829,6 +1870,11 @@ static int nxp_serdev_resume(struct device *dev)
        struct btnxpuart_dev *nxpdev = dev_get_drvdata(dev);
        struct ps_data *psdata = &nxpdev->psdata;
 
+       if (psdata->wakeup_source) {
+               disable_irq(psdata->irq_handler);
+               disable_irq_wake(psdata->irq_handler);
+       }
+
        ps_control(psdata->hdev, PS_STATE_AWAKE);
        return 0;
 }