diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 885b7c7f..a5356e0e 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -4,3 +4,4 @@ obj-m += mods/ obj-m += nvsciipc/ obj-m += tegra-pcie-dma-test.o +obj-m += bluedroid_pm.o diff --git a/drivers/misc/bluedroid_pm.c b/drivers/misc/bluedroid_pm.c new file mode 100644 index 00000000..ee51d813 --- /dev/null +++ b/drivers/misc/bluedroid_pm.c @@ -0,0 +1,633 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2019-2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "bluedroid_pm.h" + +#define PROC_DIR "bluetooth/sleep" + +/* 5 seconds of Min CPU configurations during resume */ +#define DEFAULT_RESUME_CPU_TIMEOUT 5000000 + +#define TX_TIMER_INTERVAL 5 + +/* Macro to enable or disable debug logging */ +/* #define BLUEDROID_PM_DBG */ +#ifndef BLUEDROID_PM_DBG +#define BDP_DBG(fmt, ...) pr_debug("%s: " fmt, __func__, ##__VA_ARGS__) +#else +#define BDP_DBG(fmt, ...) pr_warn("%s: " fmt, __func__, ##__VA_ARGS__) +#endif + +#define BDP_WARN(fmt, ...) pr_warn("%s: " fmt, __func__, ##__VA_ARGS__) +#define BDP_ERR(fmt, ...) pr_err("%s: " fmt, __func__, ##__VA_ARGS__) + +/* status flags for bluedroid_pm_driver */ +#define BT_WAKE 0x01 + +struct bluedroid_pm_data { + int gpio_reset; + int gpio_shutdown; + int host_wake; + int ext_wake; + int is_blocked; + unsigned int resume_min_frequency; + unsigned long flags; + int host_wake_irq; + struct regulator *vdd_3v3; + struct regulator *vdd_1v8; + struct rfkill *rfkill; + struct wakeup_source wake_lock; + struct pm_qos_request resume_cpu_freq_req; + bool resumed; + struct work_struct work; + spinlock_t lock; + struct device *dev; + struct timer_list bluedroid_pm_timer; +}; + +static struct proc_dir_entry *proc_bt_dir, *bluetooth_sleep_dir; +static bool bluedroid_pm_blocked = 1; + +static DEFINE_MUTEX(bt_wlan_sync); + +void bt_wlan_lock(void) +{ + mutex_lock(&bt_wlan_sync); +} +EXPORT_SYMBOL(bt_wlan_lock); + +void bt_wlan_unlock(void) +{ + mutex_unlock(&bt_wlan_sync); +} +EXPORT_SYMBOL(bt_wlan_unlock); + +/** bluedroid_m busy timer */ + +static void bluedroid_pm_timer_expire(struct timer_list *timer); +static DEFINE_TIMER(bluedroid_pm_timer, bluedroid_pm_timer_expire); + +static void bluedroid_work(struct work_struct *data) +{ + struct bluedroid_pm_data *bluedroid_pm = + container_of(data, struct bluedroid_pm_data, work); + struct device *dev = bluedroid_pm->dev; + char *resumed[2] = { "BT_STATE=RESUMED", NULL }; + char **uevent_envp = NULL; + unsigned long flags; + + spin_lock_irqsave(&bluedroid_pm->lock, flags); + if (!bluedroid_pm->is_blocked && bluedroid_pm->resumed) + uevent_envp = resumed; + spin_unlock_irqrestore(&bluedroid_pm->lock, flags); + + if (uevent_envp) { + kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, uevent_envp); + BDP_DBG("is_blocked=%d, resumed=%d, uevent %s\n", + bluedroid_pm->is_blocked, bluedroid_pm->resumed, + uevent_envp[0]); + } else { + BDP_DBG("is_blocked=%d, resumed=%d, no uevent\n", + bluedroid_pm->is_blocked, bluedroid_pm->resumed); + } +} + +static irqreturn_t bluedroid_pm_hostwake_isr(int irq, void *dev_id) +{ + /* schedule a tasklet to handle the change in the host wake line */ + return IRQ_HANDLED; +} + +static int bluedroid_pm_gpio_get_value(unsigned int gpio) +{ + if (gpio_cansleep(gpio)) + return gpio_get_value_cansleep(gpio); + else + return gpio_get_value(gpio); +} + +static void bluedroid_pm_gpio_set_value(unsigned int gpio, int value) +{ + if (gpio_cansleep(gpio)) + gpiod_set_value_cansleep(gpio_to_desc(gpio), value); + else + gpiod_set_value(gpio_to_desc(gpio), value); +} + +/** + * Handles bluedroid_pm busy timer expiration. + * @param data: bluedroid_pm structure. + */ +static void bluedroid_pm_timer_expire(struct timer_list *timer) +{ + + struct bluedroid_pm_data *bluedroid_pm = + from_timer(bluedroid_pm, timer, + bluedroid_pm_timer); + + /* + * if bluedroid_pm data is NULL or timer is deleted with TX busy. + * return from the function. + */ + if (!bluedroid_pm || test_bit(BT_WAKE, &bluedroid_pm->flags)) + return; + + if (!bluedroid_pm_gpio_get_value(bluedroid_pm->host_wake)) { + /* BT can sleep */ + BDP_DBG("Tx and Rx are idle, BT sleeping"); + bluedroid_pm_gpio_set_value(bluedroid_pm->ext_wake, 0); + __pm_relax(&bluedroid_pm->wake_lock); + } else { + /* BT Rx is busy, Reset Timer */ + BDP_DBG("Rx is busy, restarting the timer"); + mod_timer(&bluedroid_pm_timer, + jiffies + (TX_TIMER_INTERVAL * HZ)); + } +} + +static int bluedroid_pm_rfkill_set_power(void *data, bool blocked) +{ + struct bluedroid_pm_data *bluedroid_pm = data; + int ret = 0; + + mdelay(100); + if (blocked) { + if (gpio_is_valid(bluedroid_pm->gpio_shutdown)) + bluedroid_pm_gpio_set_value( + bluedroid_pm->gpio_shutdown, 0); + if (gpio_is_valid(bluedroid_pm->gpio_reset)) + bluedroid_pm_gpio_set_value( + bluedroid_pm->gpio_reset, 0); + if (bluedroid_pm->vdd_3v3) + ret |= regulator_disable(bluedroid_pm->vdd_3v3); + if (bluedroid_pm->vdd_1v8) + ret |= regulator_disable(bluedroid_pm->vdd_1v8); + if (gpio_is_valid(bluedroid_pm->ext_wake)) + __pm_relax(&bluedroid_pm->wake_lock); + if (bluedroid_pm->resume_min_frequency) + cpu_latency_qos_remove_request(&bluedroid_pm->resume_cpu_freq_req); + } else { + if (bluedroid_pm->vdd_3v3) + ret |= regulator_enable(bluedroid_pm->vdd_3v3); + if (bluedroid_pm->vdd_1v8) + ret |= regulator_enable(bluedroid_pm->vdd_1v8); + if (gpio_is_valid(bluedroid_pm->gpio_shutdown)) + bluedroid_pm_gpio_set_value( + bluedroid_pm->gpio_shutdown, 1); + if (gpio_is_valid(bluedroid_pm->gpio_reset)) + bluedroid_pm_gpio_set_value( + bluedroid_pm->gpio_reset, 1); + if (bluedroid_pm->resume_min_frequency) + cpu_latency_qos_add_request(&bluedroid_pm->resume_cpu_freq_req, + PM_QOS_DEFAULT_VALUE); + + } + bluedroid_pm->is_blocked = blocked; + mdelay(100); + + return ret; +} + +static const struct rfkill_ops bluedroid_pm_rfkill_ops = { + .set_block = bluedroid_pm_rfkill_set_power, +}; + +static ssize_t lpm_read_proc(struct file *file, char __user *buf, size_t size, + loff_t *ppos) +{ + char msg[50]; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 17, 0) + struct bluedroid_pm_data *bluedroid_pm = pde_data(file_inode(file)); +#else + struct bluedroid_pm_data *bluedroid_pm = PDE_DATA(file_inode(file)); +#endif + + sprintf(msg, "BT LPM Status: TX %x, RX %x\n", + bluedroid_pm_gpio_get_value(bluedroid_pm->ext_wake), + bluedroid_pm_gpio_get_value(bluedroid_pm->host_wake)); + return simple_read_from_buffer(buf, size, ppos, msg, strlen(msg)); +} + +static ssize_t lpm_write_proc(struct file *file, const char __user *buffer, + size_t count, loff_t *ppos) +{ + char *buf; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 17, 0) + struct bluedroid_pm_data *bluedroid_pm = pde_data(file_inode(file)); +#else + struct bluedroid_pm_data *bluedroid_pm = PDE_DATA(file_inode(file)); +#endif + + if (count < 1) + return -EINVAL; + + buf = kmalloc(count, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + if (copy_from_user(buf, buffer, count)) { + kfree(buf); + return -EFAULT; + } + + if (!bluedroid_pm->is_blocked) { + if (buf[0] == '0') { + if (!bluedroid_pm_gpio_get_value( + bluedroid_pm->host_wake)) { + /* BT can sleep */ + BDP_DBG("Tx and Rx are idle, BT sleeping"); + bluedroid_pm_gpio_set_value( + bluedroid_pm->ext_wake, 0); + __pm_relax(&bluedroid_pm->wake_lock); + } else { + /* Reset Timer */ + BDP_DBG("Rx is busy, restarting the timer"); + mod_timer(&bluedroid_pm_timer, + jiffies + (TX_TIMER_INTERVAL * HZ)); + } + clear_bit(BT_WAKE, &bluedroid_pm->flags); + } else if (buf[0] == '1') { + BDP_DBG("Tx is busy, wake_lock taken, delete timer"); + bluedroid_pm_gpio_set_value( + bluedroid_pm->ext_wake, 1); + __pm_stay_awake(&bluedroid_pm->wake_lock); + del_timer(&bluedroid_pm_timer); + set_bit(BT_WAKE, &bluedroid_pm->flags); + } else { + kfree(buf); + return -EINVAL; + } + } + + kfree(buf); + return count; +} + +static const struct proc_ops lpm_fops = { + .proc_read = lpm_read_proc, + .proc_write = lpm_write_proc, + .proc_lseek = default_llseek, +}; + +static void remove_bt_proc_interface(void) +{ + remove_proc_entry("lpm", bluetooth_sleep_dir); + remove_proc_entry("sleep", proc_bt_dir); + remove_proc_entry("bluetooth", NULL); +} + +static int create_bt_proc_interface(void *drv_data) +{ + int retval; + struct proc_dir_entry *ent; + + proc_bt_dir = proc_mkdir("bluetooth", NULL); + if (proc_bt_dir == NULL) { + BDP_ERR("Unable to create /proc/bluetooth directory"); + return -ENOMEM; + } + + bluetooth_sleep_dir = proc_mkdir("sleep", proc_bt_dir); + if (bluetooth_sleep_dir == NULL) { + BDP_ERR("Unable to create /proc/bluetooth directory"); + retval = -ENOMEM; + goto free_bluetooth; + } + + /* Creating read/write "btwake" entry */ + ent = proc_create_data("lpm", 0622, bluetooth_sleep_dir, &lpm_fops, drv_data); + if (ent == NULL) { + BDP_ERR("Unable to create /proc/%s/btwake entry", PROC_DIR); + retval = -ENOMEM; + goto free_sleep; + } + return 0; + +free_sleep: + remove_proc_entry("sleep", proc_bt_dir); +free_bluetooth: + remove_proc_entry("bluetooth", NULL); + return retval; +} + +/* + * This API is added to set block state by ext driver, + * when bluedroid_pm rfkill is not used but host_wake functionality to be used. + * Eg: btwilink driver + */ +void bluedroid_pm_set_ext_state(bool blocked) +{ + bluedroid_pm_blocked = blocked; +} +EXPORT_SYMBOL(bluedroid_pm_set_ext_state); + +static int bluedroid_pm_probe(struct platform_device *pdev) +{ + static struct bluedroid_pm_data *bluedroid_pm; + struct rfkill *rfkill; + int ret; + bool enable = false; /* off */ + struct device_node *node; + enum of_gpio_flags of_flags; + unsigned long flags; + + bluedroid_pm = devm_kzalloc(&pdev->dev, sizeof(*bluedroid_pm), GFP_KERNEL); + if (!bluedroid_pm) + return -ENOMEM; + + node = pdev->dev.of_node; + + if (of_get_property(node, "avdd-supply", NULL)) { + bluedroid_pm->vdd_3v3 = regulator_get(&pdev->dev, "avdd"); + if (IS_ERR(bluedroid_pm->vdd_3v3)) { + pr_warn("%s: regulator avdd not available\n", __func__); + bluedroid_pm->vdd_3v3 = NULL; + } + } + if (of_get_property(node, "dvdd-supply", NULL)) { + bluedroid_pm->vdd_1v8 = regulator_get(&pdev->dev, "dvdd"); + if (IS_ERR(bluedroid_pm->vdd_1v8)) { + pr_warn("%s: regulator dvdd not available\n", __func__); + bluedroid_pm->vdd_1v8 = NULL; + } + } + + bluedroid_pm->gpio_reset = + of_get_named_gpio_flags(node, "bluedroid_pm,reset-gpio", + 0, &of_flags); + bluedroid_pm->gpio_shutdown = + of_get_named_gpio(node, "bluedroid_pm,shutdown-gpio", 0); + bluedroid_pm->host_wake = + of_get_named_gpio(node, "bluedroid_pm,host-wake-gpio", 0); + bluedroid_pm->host_wake_irq = platform_get_irq(pdev, 0); + bluedroid_pm->ext_wake = + of_get_named_gpio(node, "bluedroid_pm,ext-wake-gpio", 0); + /* Update resume_min_frequency, if pdata is passed from board files */ + of_property_read_u32(node, "resume_min_frequency", + &bluedroid_pm->resume_min_frequency); + + if (gpio_is_valid(bluedroid_pm->gpio_reset)) { + flags = (of_flags == OF_GPIO_ACTIVE_LOW) ? GPIOF_ACTIVE_LOW : 0; + ret = gpio_request_one(bluedroid_pm->gpio_reset, flags, + "reset_gpio"); + if (ret) { + BDP_ERR("Failed to get reset gpio\n"); + goto free_bluedriod_pm; + } + gpio_direction_output(bluedroid_pm->gpio_reset, enable); + } else + BDP_DBG("Reset gpio not registered.\n"); + + if (gpio_is_valid(bluedroid_pm->gpio_shutdown)) { + ret = gpio_request(bluedroid_pm->gpio_shutdown, + "shutdown_gpio"); + if (ret) { + BDP_ERR("Failed to get shutdown gpio\n"); + goto free_gpio_reset; + } + gpio_direction_output(bluedroid_pm->gpio_shutdown, enable); + } else + BDP_DBG("shutdown gpio not registered\n"); + + /* + * make sure at-least one of the GPIO or regulators avaiable to + * register with rfkill is defined + */ + if ((gpio_is_valid(bluedroid_pm->gpio_reset)) || + (gpio_is_valid(bluedroid_pm->gpio_shutdown)) || + bluedroid_pm->vdd_1v8 || bluedroid_pm->vdd_3v3) { + rfkill = rfkill_alloc(pdev->name, &pdev->dev, + RFKILL_TYPE_BLUETOOTH, &bluedroid_pm_rfkill_ops, + bluedroid_pm); + + if (unlikely(!rfkill)) + goto free_gpio_shutdown; + + bluedroid_pm->is_blocked = !enable; + rfkill_set_states(rfkill, bluedroid_pm->is_blocked, false); + + ret = rfkill_register(rfkill); + + if (unlikely(ret)) + goto free_rfkill_alloc; + + bluedroid_pm->rfkill = rfkill; + } + + if (gpio_is_valid(bluedroid_pm->host_wake)) { + ret = gpio_request(bluedroid_pm->host_wake, "bt_host_wake"); + if (ret) { + BDP_ERR("Failed to get host_wake gpio\n"); + goto free_rfkill_reg; + } + /* configure host_wake as input */ + gpio_direction_input(bluedroid_pm->host_wake); + } else + BDP_DBG("gpio_host_wake not registered\n"); + + if (bluedroid_pm->host_wake_irq > -1) { + BDP_DBG("found host_wake irq\n"); + ret = request_irq(bluedroid_pm->host_wake_irq, + bluedroid_pm_hostwake_isr, + IRQF_TRIGGER_RISING, + "bluetooth hostwake", bluedroid_pm); + if (ret) { + BDP_ERR("Failed to get host_wake irq\n"); + goto free_host_wake; + } + } else + BDP_DBG("host_wake not registered\n"); + + if (gpio_is_valid(bluedroid_pm->ext_wake)) { + ret = gpio_request(bluedroid_pm->ext_wake, "bt_ext_wake"); + if (ret) { + BDP_ERR("Failed to get ext_wake gpio\n"); + goto free_host_wake_irq; + } + /* configure ext_wake as output mode*/ + gpio_direction_output(bluedroid_pm->ext_wake, 1); + if (create_bt_proc_interface(bluedroid_pm)) { + BDP_ERR("Failed to create proc interface"); + goto free_ext_wake; + } + /* initialize wake lock */ + wakeup_source_add(&bluedroid_pm->wake_lock); + /* Initialize timer */ + timer_setup(&bluedroid_pm->bluedroid_pm_timer, + bluedroid_pm_timer_expire, 0); + + } else + BDP_DBG("gpio_ext_wake not registered\n"); + + + INIT_WORK(&bluedroid_pm->work, bluedroid_work); + spin_lock_init(&bluedroid_pm->lock); + + bluedroid_pm->dev = &pdev->dev; + platform_set_drvdata(pdev, bluedroid_pm); + BDP_DBG("driver successfully registered"); + + return 0; + +free_ext_wake: + if (gpio_is_valid(bluedroid_pm->ext_wake)) + gpio_free(bluedroid_pm->ext_wake); +free_host_wake_irq: + if (bluedroid_pm->host_wake_irq > -1) + free_irq(bluedroid_pm->host_wake_irq, bluedroid_pm); +free_host_wake: + if (gpio_is_valid(bluedroid_pm->host_wake)) + gpio_free(bluedroid_pm->host_wake); +free_rfkill_reg: + if (bluedroid_pm->rfkill) + rfkill_unregister(bluedroid_pm->rfkill); +free_rfkill_alloc: + if (bluedroid_pm->rfkill) + rfkill_destroy(bluedroid_pm->rfkill); +free_gpio_shutdown: + if (gpio_is_valid(bluedroid_pm->gpio_shutdown)) + gpio_free(bluedroid_pm->gpio_shutdown); +free_gpio_reset: + if (gpio_is_valid(bluedroid_pm->gpio_reset)) + gpio_free(bluedroid_pm->gpio_reset); +free_bluedriod_pm: + if (bluedroid_pm->vdd_3v3) + regulator_put(bluedroid_pm->vdd_3v3); + if (bluedroid_pm->vdd_1v8) + regulator_put(bluedroid_pm->vdd_1v8); + + return -ENODEV; +} + +static int bluedroid_pm_remove(struct platform_device *pdev) +{ + struct bluedroid_pm_data *bluedroid_pm = platform_get_drvdata(pdev); + + cancel_work_sync(&bluedroid_pm->work); + if (bluedroid_pm->host_wake_irq > -1) + free_irq(bluedroid_pm->host_wake_irq, bluedroid_pm); + if (gpio_is_valid(bluedroid_pm->host_wake)) + gpio_free(bluedroid_pm->host_wake); + if (gpio_is_valid(bluedroid_pm->ext_wake)) { + wakeup_source_destroy(&bluedroid_pm->wake_lock); + gpio_free(bluedroid_pm->ext_wake); + remove_bt_proc_interface(); + del_timer(&bluedroid_pm_timer); + } + if ((gpio_is_valid(bluedroid_pm->gpio_reset)) || + (gpio_is_valid(bluedroid_pm->gpio_shutdown)) || + bluedroid_pm->vdd_1v8 || bluedroid_pm->vdd_3v3) { + rfkill_unregister(bluedroid_pm->rfkill); + rfkill_destroy(bluedroid_pm->rfkill); + } + if (gpio_is_valid(bluedroid_pm->gpio_shutdown)) + gpio_free(bluedroid_pm->gpio_shutdown); + if (gpio_is_valid(bluedroid_pm->gpio_reset)) + gpio_free(bluedroid_pm->gpio_reset); + if (bluedroid_pm->vdd_3v3) + regulator_put(bluedroid_pm->vdd_3v3); + if (bluedroid_pm->vdd_1v8) + regulator_put(bluedroid_pm->vdd_1v8); + + return 0; +} + +static int bluedroid_pm_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct bluedroid_pm_data *bluedroid_pm = platform_get_drvdata(pdev); + unsigned long flags; + int ret = 0; + + if (bluedroid_pm->host_wake) { + if (!bluedroid_pm->is_blocked || !bluedroid_pm_blocked) { + ret = enable_irq_wake(bluedroid_pm->host_wake_irq); + if (ret < 0) { + BDP_ERR("Failed to enable irq wake for irq %d, %d\n", + bluedroid_pm->host_wake_irq, ret); + return ret; + } + } + } + + spin_lock_irqsave(&bluedroid_pm->lock, flags); + bluedroid_pm->resumed = false; + spin_unlock_irqrestore(&bluedroid_pm->lock, flags); + cancel_work_sync(&bluedroid_pm->work); + + return 0; +} + +static int bluedroid_pm_resume(struct platform_device *pdev) +{ + struct bluedroid_pm_data *bluedroid_pm = platform_get_drvdata(pdev); + unsigned long flags; + + if (bluedroid_pm->host_wake) + if (!bluedroid_pm->is_blocked || !bluedroid_pm_blocked) + disable_irq_wake(bluedroid_pm->host_wake_irq); + + spin_lock_irqsave(&bluedroid_pm->lock, flags); + bluedroid_pm->resumed = true; + schedule_work(&bluedroid_pm->work); + spin_unlock_irqrestore(&bluedroid_pm->lock, flags); + + return 0; +} + +static void bluedroid_pm_shutdown(struct platform_device *pdev) +{ + struct bluedroid_pm_data *bluedroid_pm = platform_get_drvdata(pdev); + + cancel_work_sync(&bluedroid_pm->work); + + if (gpio_is_valid(bluedroid_pm->gpio_shutdown)) + bluedroid_pm_gpio_set_value( + bluedroid_pm->gpio_shutdown, 0); + if (gpio_is_valid(bluedroid_pm->gpio_reset)) + bluedroid_pm_gpio_set_value( + bluedroid_pm->gpio_reset, 0); + if (bluedroid_pm->vdd_3v3) + regulator_disable(bluedroid_pm->vdd_3v3); + if (bluedroid_pm->vdd_1v8) + regulator_disable(bluedroid_pm->vdd_1v8); + +} + +static struct of_device_id bdroid_of_match[] = { + { .compatible = "nvidia,tegra-bluedroid_pm", }, + { }, +}; +MODULE_DEVICE_TABLE(of, bdroid_of_match); + +static struct platform_driver bluedroid_pm_driver = { + .probe = bluedroid_pm_probe, + .remove = bluedroid_pm_remove, + .suspend = bluedroid_pm_suspend, + .resume = bluedroid_pm_resume, + .shutdown = bluedroid_pm_shutdown, + .driver = { + .name = "bluedroid_pm", + .of_match_table = of_match_ptr(bdroid_of_match), + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(bluedroid_pm_driver); + +MODULE_DESCRIPTION("bluedroid PM"); +MODULE_AUTHOR("NVIDIA"); +MODULE_LICENSE("GPL"); diff --git a/drivers/misc/bluedroid_pm.h b/drivers/misc/bluedroid_pm.h new file mode 100644 index 00000000..8ecc85e8 --- /dev/null +++ b/drivers/misc/bluedroid_pm.h @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2019-2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved */ + +#ifndef _BLUEDROID_PM_H +#define _BLUEDROID_PM_H +void bluedroid_pm_set_ext_state(bool blocked); +void bt_wlan_lock(void); +void bt_wlan_unlock(void); +#endif /* _BLUEDROID_PM_H */