summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCheney Ni <cheneyni@google.com>2020-12-17 14:57:24 +0800
committerCheney Ni <cheneyni@google.com>2021-01-06 11:04:25 +0800
commit5c01a5d30501fcb29227cb120c3736514420c8db (patch)
tree9eaf28cdf1e3a24862ba154e729b2a52f0f2edf5
parent8e7b8af19d88f96a535f077628fcde05de8a701f (diff)
downloadbroadcom-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.c203
1 files changed, 198 insertions, 5 deletions
diff --git a/nitrous.c b/nitrous.c
index 8a35346..0c78436 100644
--- a/nitrous.c
+++ b/nitrous.c
@@ -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;
}