#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>
#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 {
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);
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)
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;
}
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;
}
{
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)
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;
}
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)
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);
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);
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;
}
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;
}
.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
{ },
};
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
{ },
};