diff options
author | Cheney Ni <cheneyni@google.com> | 2020-12-17 14:57:24 +0800 |
---|---|---|
committer | Cheney Ni <cheneyni@google.com> | 2021-01-06 11:04:25 +0800 |
commit | 5c01a5d30501fcb29227cb120c3736514420c8db (patch) | |
tree | 9eaf28cdf1e3a24862ba154e729b2a52f0f2edf5 | |
parent | 8e7b8af19d88f96a535f077628fcde05de8a701f (diff) | |
download | broadcom-5c01a5d30501fcb29227cb120c3736514420c8db.tar.gz |
Nitrous: Export BCM LPM control via proc nodes
There are three nodes to control BTBCM LPM.
* lpm - enable or disable BTBCM LPM pins
* btwrite - wake the controller after LPM enabled
* btwake - check LPM information
Bug: 172977479
Bug: 172975224
Signed-off-by: Cheney Ni <cheneyni@google.com>
Change-Id: Id0c7e71b176635adc27113cdab5d4f7f7f0eda4e
-rw-r--r-- | nitrous.c | 203 |
1 files changed, 198 insertions, 5 deletions
@@ -13,11 +13,14 @@ #include <linux/pinctrl/consumer.h> #include <linux/platform_device.h> #include <linux/pm_runtime.h> +#include <linux/proc_fs.h> #include <linux/property.h> #include <linux/rfkill.h> #define NITROUS_AUTOSUSPEND_DELAY 1000 /* autosleep delay 1000 ms */ +struct nitrous_lpm_proc; + struct nitrous_bt_lpm { struct pinctrl *pinctrls; struct pinctrl_state *pinctrl_default_state; @@ -34,6 +37,18 @@ struct nitrous_bt_lpm { struct rfkill *rfkill; bool rfkill_blocked; /* blocked: OFF; not blocked: ON */ bool lpm_enabled; + struct nitrous_lpm_proc *proc; +}; + +#define PROC_BTWAKE 0 +#define PROC_LPM 1 +#define PROC_BTWRITE 2 +#define PROC_DIR "bluetooth/sleep" +struct proc_dir_entry *bluetooth_dir, *sleep_dir; + +struct nitrous_lpm_proc { + long operation; + struct nitrous_bt_lpm *lpm; }; /* @@ -47,6 +62,23 @@ static inline void nitrous_wake_controller(struct nitrous_bt_lpm *lpm, bool wake } /* + * Called before UART driver starts transmitting data out. UART and BT resources + * are requested to allow a transmission. + */ +static void nitrous_prepare_uart_tx_locked(struct nitrous_bt_lpm *lpm) +{ + if (lpm->rfkill_blocked) { + pr_err("unexpected Tx when rfkill is blocked\n"); + return; + } + + pm_runtime_get_sync(lpm->dev); + /* Shall be resumed here */ + pm_runtime_mark_last_busy(lpm->dev); + pm_runtime_put_autosuspend(lpm->dev); +} + +/* * ISR to handle host wake line from the BT chip. * * If an interrupt is received during system suspend, the handling of the @@ -102,7 +134,7 @@ static int nitrous_lpm_runtime_enable(struct nitrous_bt_lpm *lpm) pm_runtime_set_active(lpm->dev); pm_runtime_enable(lpm->dev); - bt_lpm->lpm_enabled = true; + lpm->lpm_enabled = true; return rc; } @@ -120,21 +152,185 @@ static void nitrous_lpm_runtime_disable(struct nitrous_bt_lpm *lpm) pm_runtime_disable(lpm->dev); pm_runtime_set_suspended(lpm->dev); - bt_lpm->lpm_enabled = false; + lpm->lpm_enabled = false; +} + +static int nitrous_proc_show(struct seq_file *m, void *v) +{ + struct nitrous_lpm_proc *data = m->private; + struct nitrous_bt_lpm *lpm = data->lpm; + + switch (data->operation) { + case PROC_BTWAKE: + seq_printf(m, "LPM: %s\nPolarity: %s\nHOST_WAKE: %u\nDEV_WAKE: %u\n", + (lpm->lpm_enabled ? "Enabled" : "Disabled"), + (lpm->wake_polarity ? "High" : "Low"), + gpiod_get_value(lpm->gpio_host_wake), + gpiod_get_value(lpm->gpio_dev_wake)); + break; + case PROC_LPM: + case PROC_BTWRITE: + seq_printf(m, "REG_ON: %s\nLPM: %s\nState: %s\n", + (lpm->rfkill_blocked ? "OFF" : "ON"), + (lpm->lpm_enabled ? "Enabled" : "Disabled"), + (lpm->is_suspended ? "asleep" : "awake")); + break; + default: + return 0; + } + return 0; +} + +static int nitrous_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, nitrous_proc_show, PDE_DATA(inode)); +} + +static ssize_t nitrous_proc_write(struct file *file, const char *buf, + size_t count, loff_t *pos) +{ + struct nitrous_lpm_proc *data = PDE_DATA(file_inode(file)); + struct nitrous_bt_lpm *lpm = data->lpm; + char lbuf[4]; + int rc; + + if (count >= sizeof(lbuf)) + count = sizeof(lbuf) - 1; + if (copy_from_user(lbuf, buf, count)) + return -EFAULT; + + switch (data->operation) { + case PROC_LPM: + if (lbuf[0] == '1') { + pr_info("[BT] LPM enabling\n"); + rc = nitrous_lpm_runtime_enable(lpm); + if (unlikely(rc)) + return rc; + } else if (lbuf[0] == '0') { + pr_info("[BT] LPM disabling\n"); + nitrous_lpm_runtime_disable(lpm); + } else { + pr_warn("[BT] Unknown LPM operation\n"); + return -EFAULT; + } + break; + case PROC_BTWRITE: + if (!lpm->lpm_enabled) { + pr_info("[BT] LPM not enabled\n"); + return count; + } + pr_debug("[BT] LPM waking up for Tx\n"); + nitrous_prepare_uart_tx_locked(lpm); + break; + default: + return 0; + } + return count; +} + +static const struct proc_ops nitrous_proc_read_fops = { + .proc_open = nitrous_proc_open, + .proc_read = seq_read, + .proc_release = single_release, +}; + +static const struct proc_ops nitrous_proc_readwrite_fops = { + .proc_open = nitrous_proc_open, + .proc_read = seq_read, + .proc_write = nitrous_proc_write, + .proc_release = single_release, +}; + +static void nitrous_lpm_remove_proc_entries(struct nitrous_bt_lpm *lpm) +{ + if (bluetooth_dir == NULL) + return; + if (sleep_dir) { + remove_proc_entry("btwrite", sleep_dir); + remove_proc_entry("lpm", sleep_dir); + remove_proc_entry("btwake", sleep_dir); + remove_proc_entry("sleep", bluetooth_dir); + } + remove_proc_entry("bluetooth", 0); + if (lpm->proc) { + devm_kfree(lpm->dev, lpm->proc); + lpm->proc = NULL; + } } static int nitrous_lpm_init(struct nitrous_bt_lpm *lpm) { + int rc; + struct proc_dir_entry *entry; + struct nitrous_lpm_proc *data; + lpm->irq_host_wake = gpiod_to_irq(lpm->gpio_host_wake); pr_info("[BT] IRQ: %d active: %s\n", lpm->irq_host_wake, (lpm->wake_polarity ? "High" : "Low")); + + data = devm_kzalloc(lpm->dev, sizeof(struct nitrous_lpm_proc) * 3, GFP_KERNEL); + if (data == NULL) { + pr_err("[BT] %s: Unable to alloc memory", __func__); + return -ENOMEM; + } + lpm->proc = data; + + bluetooth_dir = proc_mkdir("bluetooth", NULL); + if (bluetooth_dir == NULL) { + pr_err("[BT] Unable to create /proc/bluetooth directory"); + rc = -ENOMEM; + goto fail; + } + sleep_dir = proc_mkdir("sleep", bluetooth_dir); + if (sleep_dir == NULL) { + pr_err("[BT] Unable to create /proc/%s directory", PROC_DIR); + rc = -ENOMEM; + goto fail; + } + /* Creating read only proc entries "btwake" showing GPIOs state */ + data[0].operation = PROC_BTWAKE; + data[0].lpm = lpm; + entry = proc_create_data("btwake", (S_IRUSR | S_IRGRP), sleep_dir, + &nitrous_proc_read_fops, data); + if (entry == NULL) { + pr_err("[BT] Unable to create /proc/%s/btwake entry", PROC_DIR); + rc = -ENOMEM; + goto fail; + } + /* read/write proc entries "lpm" */ + data[1].operation = PROC_LPM; + data[1].lpm = lpm; + entry = proc_create_data("lpm", (S_IRUSR | S_IRGRP | S_IWUSR), + sleep_dir, &nitrous_proc_readwrite_fops, data + 1); + if (entry == NULL) { + pr_err("[BT] Unable to create /proc/%s/lpm entry", PROC_DIR); + rc = -ENOMEM; + goto fail; + } + /* read/write proc entries "btwrite" */ + data[2].operation = PROC_BTWRITE; + data[2].lpm = lpm; + entry = proc_create_data("btwrite", (S_IRUSR | S_IRGRP | S_IWUSR), + sleep_dir, &nitrous_proc_readwrite_fops, data + 2); + if (entry == NULL) { + pr_err("[BT] Unable to create /proc/%s/btwrite entry", PROC_DIR); + rc = -ENOMEM; + goto fail; + } + return 0; + +fail: + nitrous_lpm_remove_proc_entries(lpm); + return rc; } static void nitrous_lpm_cleanup(struct nitrous_bt_lpm *lpm) { nitrous_lpm_runtime_disable(lpm); lpm->irq_host_wake = 0; + + nitrous_lpm_remove_proc_entries(lpm); } /* @@ -181,9 +377,6 @@ static int nitrous_rfkill_set_power(void *data, bool blocked) } lpm->rfkill_blocked = blocked; - if (!lpm->rfkill_blocked) - nitrous_lpm_runtime_enable(lpm); - return 0; } |