Merge tag 'dt-5.15' of git://git.kernel.org/pub/scm/linux/kernel/git/soc/soc
[linux-2.6-microblaze.git] / drivers / bluetooth / hci_h5.c
index e052063..0c0dede 100644 (file)
@@ -12,6 +12,7 @@
 #include <linux/kernel.h>
 #include <linux/mod_devicetable.h>
 #include <linux/of_device.h>
+#include <linux/pm_runtime.h>
 #include <linux/serdev.h>
 #include <linux/skbuff.h>
 
@@ -21,6 +22,8 @@
 #include "btrtl.h"
 #include "hci_uart.h"
 
+#define SUSPEND_TIMEOUT_MS     6000
+
 #define HCI_3WIRE_ACK_PKT      0
 #define HCI_3WIRE_LINK_PKT     15
 
 
 /* H5 state flags */
 enum {
-       H5_RX_ESC,      /* SLIP escape mode */
-       H5_TX_ACK_REQ,  /* Pending ack to send */
+       H5_RX_ESC,              /* SLIP escape mode */
+       H5_TX_ACK_REQ,          /* Pending ack to send */
+       H5_WAKEUP_DISABLE,      /* Device cannot wake host */
+       H5_HW_FLOW_CONTROL,     /* Use HW flow control */
 };
 
 struct h5 {
@@ -97,6 +102,10 @@ struct h5 {
        struct gpio_desc *device_wake_gpio;
 };
 
+enum h5_driver_info {
+       H5_INFO_WAKEUP_DISABLE = BIT(0),
+};
+
 struct h5_vnd {
        int (*setup)(struct h5 *h5);
        void (*open)(struct h5 *h5);
@@ -106,6 +115,11 @@ struct h5_vnd {
        const struct acpi_gpio_mapping *acpi_gpio_map;
 };
 
+struct h5_device_data {
+       uint32_t driver_info;
+       struct h5_vnd *vnd;
+};
+
 static void h5_reset_rx(struct h5 *h5);
 
 static void h5_link_control(struct hci_uart *hu, const void *data, size_t len)
@@ -573,6 +587,10 @@ static int h5_recv(struct hci_uart *hu, const void *data, int count)
                count -= processed;
        }
 
+       pm_runtime_get(&hu->serdev->dev);
+       pm_runtime_mark_last_busy(&hu->serdev->dev);
+       pm_runtime_put_autosuspend(&hu->serdev->dev);
+
        return 0;
 }
 
@@ -609,6 +627,10 @@ static int h5_enqueue(struct hci_uart *hu, struct sk_buff *skb)
                break;
        }
 
+       pm_runtime_get_sync(&hu->serdev->dev);
+       pm_runtime_mark_last_busy(&hu->serdev->dev);
+       pm_runtime_put_autosuspend(&hu->serdev->dev);
+
        return 0;
 }
 
@@ -791,6 +813,8 @@ static int h5_serdev_probe(struct serdev_device *serdev)
 {
        struct device *dev = &serdev->dev;
        struct h5 *h5;
+       const struct h5_device_data *data;
+       int err;
 
        h5 = devm_kzalloc(dev, sizeof(*h5), GFP_KERNEL);
        if (!h5)
@@ -807,20 +831,19 @@ static int h5_serdev_probe(struct serdev_device *serdev)
                if (!match)
                        return -ENODEV;
 
-               h5->vnd = (const struct h5_vnd *)match->driver_data;
+               data = (const struct h5_device_data *)match->driver_data;
+               h5->vnd = data->vnd;
                h5->id  = (char *)match->id;
 
                if (h5->vnd->acpi_gpio_map)
                        devm_acpi_dev_add_driver_gpios(dev,
                                                       h5->vnd->acpi_gpio_map);
        } else {
-               const void *data;
-
                data = of_device_get_match_data(dev);
                if (!data)
                        return -ENODEV;
 
-               h5->vnd = (const struct h5_vnd *)data;
+               h5->vnd = data->vnd;
        }
 
 
@@ -833,7 +856,14 @@ static int h5_serdev_probe(struct serdev_device *serdev)
        if (IS_ERR(h5->device_wake_gpio))
                return PTR_ERR(h5->device_wake_gpio);
 
-       return hci_uart_register_device(&h5->serdev_hu, &h5p);
+       err = hci_uart_register_device(&h5->serdev_hu, &h5p);
+       if (err)
+               return err;
+
+       if (data->driver_info & H5_INFO_WAKEUP_DISABLE)
+               set_bit(H5_WAKEUP_DISABLE, &h5->flags);
+
+       return 0;
 }
 
 static void h5_serdev_remove(struct serdev_device *serdev)
@@ -902,6 +932,9 @@ static int h5_btrtl_setup(struct h5 *h5)
        serdev_device_set_baudrate(h5->hu->serdev, controller_baudrate);
        serdev_device_set_flow_control(h5->hu->serdev, flow_control);
 
+       if (flow_control)
+               set_bit(H5_HW_FLOW_CONTROL, &h5->flags);
+
        err = btrtl_download_firmware(h5->hu->hdev, btrtl_dev);
        /* Give the device some time before the hci-core sends it a reset */
        usleep_range(10000, 20000);
@@ -916,11 +949,25 @@ out_free:
 
 static void h5_btrtl_open(struct h5 *h5)
 {
+       /*
+        * Since h5_btrtl_resume() does a device_reprobe() the suspend handling
+        * done by the hci_suspend_notifier is not necessary; it actually causes
+        * delays and a bunch of errors to get logged, so disable it.
+        */
+       if (test_bit(H5_WAKEUP_DISABLE, &h5->flags))
+               set_bit(HCI_UART_NO_SUSPEND_NOTIFIER, &h5->hu->flags);
+
        /* Devices always start with these fixed parameters */
        serdev_device_set_flow_control(h5->hu->serdev, false);
        serdev_device_set_parity(h5->hu->serdev, SERDEV_PARITY_EVEN);
        serdev_device_set_baudrate(h5->hu->serdev, 115200);
 
+       pm_runtime_set_active(&h5->hu->serdev->dev);
+       pm_runtime_use_autosuspend(&h5->hu->serdev->dev);
+       pm_runtime_set_autosuspend_delay(&h5->hu->serdev->dev,
+                                        SUSPEND_TIMEOUT_MS);
+       pm_runtime_enable(&h5->hu->serdev->dev);
+
        /* The controller needs up to 500ms to wakeup */
        gpiod_set_value_cansleep(h5->enable_gpio, 1);
        gpiod_set_value_cansleep(h5->device_wake_gpio, 1);
@@ -929,21 +976,26 @@ static void h5_btrtl_open(struct h5 *h5)
 
 static void h5_btrtl_close(struct h5 *h5)
 {
+       pm_runtime_disable(&h5->hu->serdev->dev);
+
        gpiod_set_value_cansleep(h5->device_wake_gpio, 0);
        gpiod_set_value_cansleep(h5->enable_gpio, 0);
 }
 
 /* Suspend/resume support. On many devices the RTL BT device loses power during
  * suspend/resume, causing it to lose its firmware and all state. So we simply
- * turn it off on suspend and reprobe on resume.  This mirrors how RTL devices
- * are handled in the USB driver, where the USB_QUIRK_RESET_RESUME is used which
+ * turn it off on suspend and reprobe on resume. This mirrors how RTL devices
+ * are handled in the USB driver, where the BTUSB_WAKEUP_DISABLE is used which
  * also causes a reprobe on resume.
  */
 static int h5_btrtl_suspend(struct h5 *h5)
 {
        serdev_device_set_flow_control(h5->hu->serdev, false);
        gpiod_set_value_cansleep(h5->device_wake_gpio, 0);
-       gpiod_set_value_cansleep(h5->enable_gpio, 0);
+
+       if (test_bit(H5_WAKEUP_DISABLE, &h5->flags))
+               gpiod_set_value_cansleep(h5->enable_gpio, 0);
+
        return 0;
 }
 
@@ -969,17 +1021,25 @@ static void h5_btrtl_reprobe_worker(struct work_struct *work)
 
 static int h5_btrtl_resume(struct h5 *h5)
 {
-       struct h5_btrtl_reprobe *reprobe;
+       if (test_bit(H5_WAKEUP_DISABLE, &h5->flags)) {
+               struct h5_btrtl_reprobe *reprobe;
 
-       reprobe = kzalloc(sizeof(*reprobe), GFP_KERNEL);
-       if (!reprobe)
-               return -ENOMEM;
+               reprobe = kzalloc(sizeof(*reprobe), GFP_KERNEL);
+               if (!reprobe)
+                       return -ENOMEM;
 
-       __module_get(THIS_MODULE);
+               __module_get(THIS_MODULE);
+
+               INIT_WORK(&reprobe->work, h5_btrtl_reprobe_worker);
+               reprobe->dev = get_device(&h5->hu->serdev->dev);
+               queue_work(system_long_wq, &reprobe->work);
+       } else {
+               gpiod_set_value_cansleep(h5->device_wake_gpio, 1);
+
+               if (test_bit(H5_HW_FLOW_CONTROL, &h5->flags))
+                       serdev_device_set_flow_control(h5->hu->serdev, true);
+       }
 
-       INIT_WORK(&reprobe->work, h5_btrtl_reprobe_worker);
-       reprobe->dev = get_device(&h5->hu->serdev->dev);
-       queue_work(system_long_wq, &reprobe->work);
        return 0;
 }
 
@@ -1001,13 +1061,22 @@ static struct h5_vnd rtl_vnd = {
        .resume         = h5_btrtl_resume,
        .acpi_gpio_map  = acpi_btrtl_gpios,
 };
+
+static const struct h5_device_data h5_data_rtl8822cs = {
+       .vnd = &rtl_vnd,
+};
+
+static const struct h5_device_data h5_data_rtl8723bs = {
+       .driver_info = H5_INFO_WAKEUP_DISABLE,
+       .vnd = &rtl_vnd,
+};
 #endif
 
 #ifdef CONFIG_ACPI
 static const struct acpi_device_id h5_acpi_match[] = {
 #ifdef CONFIG_BT_HCIUART_RTL
-       { "OBDA0623", (kernel_ulong_t)&rtl_vnd },
-       { "OBDA8723", (kernel_ulong_t)&rtl_vnd },
+       { "OBDA0623", (kernel_ulong_t)&h5_data_rtl8723bs },
+       { "OBDA8723", (kernel_ulong_t)&h5_data_rtl8723bs },
 #endif
        { },
 };
@@ -1016,16 +1085,17 @@ MODULE_DEVICE_TABLE(acpi, h5_acpi_match);
 
 static const struct dev_pm_ops h5_serdev_pm_ops = {
        SET_SYSTEM_SLEEP_PM_OPS(h5_serdev_suspend, h5_serdev_resume)
+       SET_RUNTIME_PM_OPS(h5_serdev_suspend, h5_serdev_resume, NULL)
 };
 
 static const struct of_device_id rtl_bluetooth_of_match[] = {
 #ifdef CONFIG_BT_HCIUART_RTL
        { .compatible = "realtek,rtl8822cs-bt",
-         .data = (const void *)&rtl_vnd },
+         .data = (const void *)&h5_data_rtl8822cs },
        { .compatible = "realtek,rtl8723bs-bt",
-         .data = (const void *)&rtl_vnd },
+         .data = (const void *)&h5_data_rtl8723bs },
        { .compatible = "realtek,rtl8723ds-bt",
-         .data = (const void *)&rtl_vnd },
+         .data = (const void *)&h5_data_rtl8723bs },
 #endif
        { },
 };