]> www.infradead.org Git - users/dwmw2/linux.git/commitdiff
Bluetooth: btnxpuart: Add GPIO support to power save feature
authorNeeraj Sanjay Kale <neeraj.sanjaykale@nxp.com>
Tue, 8 Oct 2024 09:11:59 +0000 (14:41 +0530)
committerLuiz Augusto von Dentz <luiz.von.dentz@intel.com>
Thu, 14 Nov 2024 20:29:37 +0000 (15:29 -0500)
This adds support for driving the chip into sleep or wakeup with a GPIO.

If the device tree property device-wakeup-gpios is defined, the driver
utilizes this GPIO for controlling the chip's power save state, else it
uses the default UART-break method.

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 4f493be763b8815fd909694c5dcd8fa210b23057..569f5b7d6e46f2b61c792e7b7beea5f9af7ca217 100644 (file)
@@ -16,6 +16,7 @@
 #include <linux/crc8.h>
 #include <linux/crc32.h>
 #include <linux/string_helpers.h>
+#include <linux/gpio/consumer.h>
 
 #include <net/bluetooth/bluetooth.h>
 #include <net/bluetooth/hci_core.h>
@@ -82,6 +83,7 @@
 #define WAKEUP_METHOD_BREAK     1
 #define WAKEUP_METHOD_EXT_BREAK 2
 #define WAKEUP_METHOD_RTS       3
+#define WAKEUP_METHOD_GPIO      4
 #define WAKEUP_METHOD_INVALID   0xff
 
 /* power save mode status */
@@ -135,6 +137,7 @@ struct ps_data {
        bool  driver_sent_cmd;
        u16   h2c_ps_interval;
        u16   c2h_ps_interval;
+       struct gpio_desc *h2c_ps_gpio;
        struct hci_dev *hdev;
        struct work_struct work;
        struct timer_list ps_timer;
@@ -365,7 +368,7 @@ static void ps_control(struct hci_dev *hdev, u8 ps_state)
 {
        struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev);
        struct ps_data *psdata = &nxpdev->psdata;
-       int status;
+       int status = 0;
 
        if (psdata->ps_state == ps_state ||
            !test_bit(BTNXPUART_SERDEV_OPEN, &nxpdev->tx_state))
@@ -373,6 +376,14 @@ static void ps_control(struct hci_dev *hdev, u8 ps_state)
 
        mutex_lock(&psdata->ps_lock);
        switch (psdata->cur_h2c_wakeupmode) {
+       case WAKEUP_METHOD_GPIO:
+               if (ps_state == PS_STATE_AWAKE)
+                       gpiod_set_value_cansleep(psdata->h2c_ps_gpio, 0);
+               else
+                       gpiod_set_value_cansleep(psdata->h2c_ps_gpio, 1);
+               bt_dev_dbg(hdev, "Set h2c_ps_gpio: %s",
+                          str_high_low(ps_state == PS_STATE_SLEEP));
+               break;
        case WAKEUP_METHOD_DTR:
                if (ps_state == PS_STATE_AWAKE)
                        status = serdev_device_set_tiocm(nxpdev->serdev, TIOCM_DTR, 0);
@@ -422,15 +433,29 @@ static void ps_timeout_func(struct timer_list *t)
        }
 }
 
-static void ps_setup(struct hci_dev *hdev)
+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;
 
+       psdata->h2c_ps_gpio = devm_gpiod_get_optional(&serdev->dev, "device-wakeup",
+                                                     GPIOD_OUT_LOW);
+       if (IS_ERR(psdata->h2c_ps_gpio)) {
+               bt_dev_err(hdev, "Error fetching device-wakeup-gpios: %ld",
+                          PTR_ERR(psdata->h2c_ps_gpio));
+               return PTR_ERR(psdata->h2c_ps_gpio);
+       }
+
+       if (!psdata->h2c_ps_gpio)
+               psdata->h2c_wakeup_gpio = 0xff;
+
        psdata->hdev = hdev;
        INIT_WORK(&psdata->work, ps_work_func);
        mutex_init(&psdata->ps_lock);
        timer_setup(&psdata->ps_timer, ps_timeout_func, 0);
+
+       return 0;
 }
 
 static bool ps_wakeup(struct btnxpuart_dev *nxpdev)
@@ -516,6 +541,9 @@ static int send_wakeup_method_cmd(struct hci_dev *hdev, void *data)
        pcmd.c2h_wakeupmode = psdata->c2h_wakeupmode;
        pcmd.c2h_wakeup_gpio = psdata->c2h_wakeup_gpio;
        switch (psdata->h2c_wakeupmode) {
+       case WAKEUP_METHOD_GPIO:
+               pcmd.h2c_wakeupmode = BT_CTRL_WAKEUP_METHOD_GPIO;
+               break;
        case WAKEUP_METHOD_DTR:
                pcmd.h2c_wakeupmode = BT_CTRL_WAKEUP_METHOD_DSR;
                break;
@@ -550,6 +578,7 @@ static void ps_init(struct hci_dev *hdev)
 {
        struct btnxpuart_dev *nxpdev = hci_get_drvdata(hdev);
        struct ps_data *psdata = &nxpdev->psdata;
+       u8 default_h2c_wakeup_mode = DEFAULT_H2C_WAKEUP_MODE;
 
        serdev_device_set_tiocm(nxpdev->serdev, 0, TIOCM_RTS);
        usleep_range(5000, 10000);
@@ -561,8 +590,17 @@ static void ps_init(struct hci_dev *hdev)
        psdata->c2h_wakeup_gpio = 0xff;
 
        psdata->cur_h2c_wakeupmode = WAKEUP_METHOD_INVALID;
+       if (psdata->h2c_ps_gpio)
+               default_h2c_wakeup_mode = WAKEUP_METHOD_GPIO;
+
        psdata->h2c_ps_interval = PS_DEFAULT_TIMEOUT_PERIOD_MS;
-       switch (DEFAULT_H2C_WAKEUP_MODE) {
+
+       switch (default_h2c_wakeup_mode) {
+       case WAKEUP_METHOD_GPIO:
+               psdata->h2c_wakeupmode = WAKEUP_METHOD_GPIO;
+               gpiod_set_value_cansleep(psdata->h2c_ps_gpio, 0);
+               usleep_range(5000, 10000);
+               break;
        case WAKEUP_METHOD_DTR:
                psdata->h2c_wakeupmode = WAKEUP_METHOD_DTR;
                serdev_device_set_tiocm(nxpdev->serdev, 0, TIOCM_DTR);
@@ -1279,6 +1317,9 @@ static int nxp_enqueue(struct hci_dev *hdev, struct sk_buff *skb)
                                psdata->c2h_wakeup_gpio = wakeup_parm.c2h_wakeup_gpio;
                                psdata->h2c_wakeup_gpio = wakeup_parm.h2c_wakeup_gpio;
                                switch (wakeup_parm.h2c_wakeupmode) {
+                               case BT_CTRL_WAKEUP_METHOD_GPIO:
+                                       psdata->h2c_wakeupmode = WAKEUP_METHOD_GPIO;
+                                       break;
                                case BT_CTRL_WAKEUP_METHOD_DSR:
                                        psdata->h2c_wakeupmode = WAKEUP_METHOD_DTR;
                                        break;
@@ -1509,13 +1550,17 @@ static int nxp_serdev_probe(struct serdev_device *serdev)
 
        if (hci_register_dev(hdev) < 0) {
                dev_err(&serdev->dev, "Can't register HCI device\n");
-               hci_free_dev(hdev);
-               return -ENODEV;
+               goto probe_fail;
        }
 
-       ps_setup(hdev);
+       if (ps_setup(hdev))
+               goto probe_fail;
 
        return 0;
+
+probe_fail:
+       hci_free_dev(hdev);
+       return -ENODEV;
 }
 
 static void nxp_serdev_remove(struct serdev_device *serdev)