diff options
author | Mark Salyzyn <salyzyn@google.com> | 2021-01-14 06:52:19 -0800 |
---|---|---|
committer | Mark Salyzyn <salyzyn@google.com> | 2021-01-14 06:52:19 -0800 |
commit | f07ffa9f37369c3b23a2482b88d996a63939a27b (patch) | |
tree | 4dcbb948b6ee1d1babb52465148c191b8e925108 | |
parent | 16a8555679b457dc5336485bc627c66d4345c51d (diff) | |
parent | 9caf5d430d0b2e752446f6d2c00fbf11a55c58dc (diff) | |
download | broadcom-f07ffa9f37369c3b23a2482b88d996a63939a27b.tar.gz |
Merge partner/android-gs-pixel-mainline into partner/android-gs-pixel-5.10-stabilization
* partner/android-gs-pixel-mainline:
Nitrous: Control BTBCM power via rfkill
Nitrous: Apply preferred BT pinctrl state
Nitrous: BCM Bluetooth Power Management for Rx
Nitrous: Export BCM LPM control via proc nodes
Nitrous: Add delay while doing BTBCM power via rfkill
Signed-off-by: Mark Salyzyn <salyzyn@google.com>
Bug: 157051117
Bug: 168446075
Bug: 172975224
Bug: 172976543
Bug: 172977479
Change-Id: I1a93efc906a825c6ff7ac6ae67b854ad5300f9ec
-rw-r--r-- | Kbuild | 3 | ||||
-rw-r--r-- | Makefile | 7 | ||||
-rw-r--r-- | nitrous.c | 670 |
3 files changed, 364 insertions, 316 deletions
@@ -0,0 +1,3 @@ +obj-$(CONFIG_NITROUS) := nitrous.o + +ccflags-y += -I$(abspath $(KERNEL_SRC)/$(M)) diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5a16e0f --- /dev/null +++ b/Makefile @@ -0,0 +1,7 @@ +KERNEL_SRC ?= /lib/modules/$(shell uname -r)/build +M ?= $(shell pwd) + +KBUILD_OPTIONS := CONFIG_NITROUS=m + +modules modules_install clean: + $(MAKE) -C $(KERNEL_SRC) M=$(M) W=1 $(KBUILD_OPTIONS) $(@) @@ -1,332 +1,385 @@ +// SPDX-License-Identifier: GPL-2.0 /* * Bluetooth low power control via GPIO * - * Copyright (C) 2015 Google, Inc. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - * The current implementation is very specific to Qualcomm serial driver - * with BCM chipset. + * Copyright 2015-2020 Google LLC. */ #include <linux/delay.h> -#include <linux/gpio.h> -#include <linux/hrtimer.h> -#include <linux/init.h> +#include <linux/gpio/consumer.h> #include <linux/interrupt.h> -#include <linux/irq.h> #include <linux/module.h> -#include <linux/of_gpio.h> -#include <linux/platform_data/msm_serial_hs.h> +#include <linux/of.h> +#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> -#include <linux/wakelock.h> -/* Timeout on UART Tx traffic before releasing wakelock. */ -static const int UART_TIMEOUT_SEC = 1; +#define NITROUS_AUTOSUSPEND_DELAY 1000 /* autosleep delay 1000 ms */ + +struct nitrous_lpm_proc; struct nitrous_bt_lpm { - int gpio_dev_wake; /* host -> dev wake gpio */ - int gpio_host_wake; /* dev -> host wake gpio */ - int gpio_power; /* gpio to control power */ - int irq_host_wake; /* IRQ associated with host wake gpio */ - int dev_wake_pol; /* 0: active low; 1: active high */ - int host_wake_pol; /* 0: active low; 1: active high */ - - struct wake_lock dev_lock; /* Wakelock held during Tx */ - struct wake_lock host_lock; /* Wakelock held during Rx */ - struct hrtimer tx_lpm_timer; /* timer for going into LPM during Tx */ + struct pinctrl *pinctrls; + struct pinctrl_state *pinctrl_default_state; + struct gpio_desc *gpio_dev_wake; /* Host -> Dev WAKE GPIO */ + struct gpio_desc *gpio_host_wake; /* Dev -> Host WAKE GPIO */ + struct gpio_desc *gpio_power; /* GPIO to control power */ + int irq_host_wake; /* IRQ associated with HOST_WAKE GPIO */ + int wake_polarity; /* 0: active low; 1: active high */ + bool is_suspended; /* driver is in suspend state */ bool pending_irq; /* pending host wake IRQ during suspend */ - struct uart_port *uart_port; - struct platform_device *pdev; + struct device *dev; struct rfkill *rfkill; - bool rfkill_blocked; /* blocked: off; not blocked: on */ + bool rfkill_blocked; /* blocked: OFF; not blocked: ON */ + bool lpm_enabled; + struct nitrous_lpm_proc *proc; }; -static struct nitrous_bt_lpm *bt_lpm; /* Reference for internal data */ +#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; -/* Helper to fetch gpio information from the device tree and then register it */ -static int read_and_request_gpio(struct platform_device *pdev, - const char *dt_name, int *gpio, - unsigned long gpio_flags, const char *label) -{ - int rc; - - *gpio = of_get_named_gpio(pdev->dev.of_node, dt_name, 0); - if (unlikely(*gpio < 0)) { - pr_err("%s: %s not in device tree", __func__, dt_name); - return -EINVAL; - } - - if (!gpio_is_valid(*gpio)) { - pr_err("%s: %s is invalid", __func__, dt_name); - return -EINVAL; - } - - rc = gpio_request_one(*gpio, gpio_flags, label); - if (unlikely(rc < 0)) { - pr_err("%s: failed to request gpio: %d (%s), error: %d", - __func__, *gpio, label, rc); - return -EINVAL; - } - - return 0; -} +struct nitrous_lpm_proc { + long operation; + struct nitrous_bt_lpm *lpm; +}; /* - * Power up or down UART driver and hold Tx/Rx wakelock. - * - * Note that the use of pm_runtime_get here is not ideal as the call is not - * blocking. By the time the UART driver is powered up, the serial core might - * have attempted to do a Tx at the UART driver already. There is currently a - * workaround in the MSM serial driver to catch this race. Over time the clock - * on and Tx sequence should be made synchronous. + * Wake up or sleep BT device for Tx. */ -static bool nitrous_uart_power(struct uart_port *uart_port, - struct wake_lock *lock, bool on) +static inline void nitrous_wake_controller(struct nitrous_bt_lpm *lpm, bool wake) { - if (wake_lock_active(lock) == on) - return false; - - if (on) { - wake_lock(lock); - pm_runtime_get(uart_port->dev); - } else { - pm_runtime_put(uart_port->dev); - wake_unlock(lock); - } - - return true; + int assert_level = (wake == lpm->wake_polarity); + pr_debug("[BT] DEV_WAKE: %s", (assert_level ? "Assert" : "Dessert")); + gpiod_set_value_cansleep(lpm->gpio_dev_wake, assert_level); } /* - * Wake up or sleep UART and BT device for Tx. + * Called before UART driver starts transmitting data out. UART and BT resources + * are requested to allow a transmission. */ -static inline void nitrous_wake_device_locked(struct nitrous_bt_lpm *lpm, - bool wake) +static void nitrous_prepare_uart_tx_locked(struct nitrous_bt_lpm *lpm) { - if (nitrous_uart_power(lpm->uart_port, &lpm->dev_lock, wake)) { - /* - * If there is a UART power on/off, assert/deassert dev wake - * gpio accordingly. - */ - int assert_level = (wake == lpm->dev_wake_pol); - gpio_set_value(lpm->gpio_dev_wake, assert_level); + 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); } /* - * Wake up or sleep UART for Rx. + * ISR to handle host wake line from the BT chip. + * + * If an interrupt is received during system suspend, the handling of the + * interrupt will be delayed until the driver is resumed. This allows the use + * of pm runtime framework to wake the serial driver. */ -static inline void nitrous_wake_uart(struct nitrous_bt_lpm *lpm, bool wake) +static irqreturn_t nitrous_host_wake_isr(int irq, void *data) { - nitrous_uart_power(lpm->uart_port, &lpm->host_lock, wake); + struct nitrous_bt_lpm *lpm = data; + + pr_debug("[BT] Host wake IRQ: %u\n", gpiod_get_value(lpm->gpio_host_wake)); + if (lpm->rfkill_blocked) { + pr_err("[BT] %s: Unexpected Host wake IRQ\n", __func__); + return IRQ_HANDLED; + } + + pm_runtime_get(lpm->dev); + pm_runtime_mark_last_busy(lpm->dev); + pm_runtime_put_autosuspend(lpm->dev); + + return IRQ_HANDLED; } -/* - * Called when the tx_lpm_timer expires and the last Tx transaction should have - * been started about UART_TIMEOUT_SEC second(s) ago. At this time, the Tx - * should have been completed. - */ -static enum hrtimer_restart nitrous_tx_lpm_handler(struct hrtimer *timer) +static int nitrous_lpm_runtime_enable(struct nitrous_bt_lpm *lpm) { - unsigned long flags; + int rc; + + if (lpm->irq_host_wake <= 0) + return -EOPNOTSUPP; - if (!bt_lpm) { - pr_err("%s: missing bt_lpm\n", __func__); - return HRTIMER_NORESTART; + if (lpm->rfkill_blocked) { + pr_err("[BT] Unexpected LPM request\n"); + return -EINVAL; } - pr_debug("%s\n", __func__); + if (lpm->lpm_enabled) { + pr_warn("[BT] Try to request LPM twice\n"); + return 0; + } + + rc = devm_request_irq(lpm->dev, lpm->irq_host_wake, nitrous_host_wake_isr, + lpm->wake_polarity ? IRQF_TRIGGER_RISING : IRQF_TRIGGER_FALLING, + "bt_host_wake", lpm); + if (unlikely(rc)) { + pr_err("[BT] Unable to request IRQ for bt_host_wake GPIO\n"); + lpm->irq_host_wake = rc; + return rc; + } - /* Release UART and BT resources */ - spin_lock_irqsave(&bt_lpm->uart_port->lock, flags); - nitrous_wake_device_locked(bt_lpm, false); - spin_unlock_irqrestore(&bt_lpm->uart_port->lock, flags); + device_init_wakeup(lpm->dev, true); + pm_runtime_set_autosuspend_delay(lpm->dev, NITROUS_AUTOSUSPEND_DELAY); + pm_runtime_use_autosuspend(lpm->dev); + pm_runtime_set_active(lpm->dev); + pm_runtime_enable(lpm->dev); - return HRTIMER_NORESTART; + lpm->lpm_enabled = true; + + return rc; } -/* - * Called before UART driver starts transmitting data out. UART and BT resources - * are requested to allow a transmission. - * - * Note that the calling context from the serial core should have the - * uart_port locked. - */ -void nitrous_prepare_uart_tx_locked(struct uart_port *port) +static void nitrous_lpm_runtime_disable(struct nitrous_bt_lpm *lpm) { - if (!bt_lpm) { - pr_err("%s: missing bt_lpm\n", __func__); + if (lpm->irq_host_wake <= 0) return; - } - if (bt_lpm->rfkill_blocked) { - pr_err("%s: unexpected Tx when rfkill is blocked\n", __func__); + if (!lpm->lpm_enabled) return; - } - hrtimer_cancel(&bt_lpm->tx_lpm_timer); - nitrous_wake_device_locked(bt_lpm, true); - hrtimer_start(&bt_lpm->tx_lpm_timer, ktime_set(UART_TIMEOUT_SEC, 0), - HRTIMER_MODE_REL); + devm_free_irq(lpm->dev, lpm->irq_host_wake, lpm); + device_init_wakeup(lpm->dev, false); + pm_runtime_disable(lpm->dev); + pm_runtime_set_suspended(lpm->dev); + + lpm->lpm_enabled = false; } -EXPORT_SYMBOL(nitrous_prepare_uart_tx_locked); -/* - * ISR to handle host wake line from the BT chip. - * - * If an interrupt is received during system suspend, the handling of the - * interrupt will be delayed until the driver is resumed. This allows the use - * of pm runtime framework to wake the serial driver. - */ -static irqreturn_t nitrous_host_wake_isr(int irq, void *dev) +static int nitrous_proc_show(struct seq_file *m, void *v) { - int host_wake, rc; - struct platform_device *pdev = container_of(dev, - struct platform_device, dev); - struct nitrous_bt_lpm *lpm = platform_get_drvdata(pdev); - - if (!lpm) { - pr_err("%s: missing lpm\n", __func__); - return IRQ_HANDLED; + 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; +} - if (lpm->rfkill_blocked) { - pr_err("%s: unexpected host wake IRQ\n", __func__); - return IRQ_HANDLED; +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; +} - host_wake = gpio_get_value(lpm->gpio_host_wake); - pr_debug("%s: host wake gpio: %d\n", __func__, host_wake); +static const struct proc_ops nitrous_proc_read_fops = { + .proc_open = nitrous_proc_open, + .proc_read = seq_read, + .proc_release = single_release, +}; - /* Invert the interrupt type to catch the next edge */ - rc = irq_set_irq_type(irq, - host_wake ? IRQF_TRIGGER_LOW : IRQF_TRIGGER_HIGH); - if (unlikely(rc)) - pr_err("%s: error setting irq type %d\n", __func__, rc); +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, +}; - if (lpm->is_suspended) { - /* Mark pending irq flag to delay processing. */ - lpm->pending_irq = true; - } else { - /* Wake up UART right the way if not suspended. */ - bool uart_enable = (host_wake == lpm->host_wake_pol); - nitrous_wake_uart(lpm, uart_enable); +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; } - - return IRQ_HANDLED; } static int nitrous_lpm_init(struct nitrous_bt_lpm *lpm) { int rc; + struct proc_dir_entry *entry; + struct nitrous_lpm_proc *data; - hrtimer_init(&lpm->tx_lpm_timer, CLOCK_MONOTONIC, - HRTIMER_MODE_REL); - lpm->tx_lpm_timer.function = nitrous_tx_lpm_handler; - - lpm->irq_host_wake = gpio_to_irq(lpm->gpio_host_wake); + 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")); - rc = request_irq(lpm->irq_host_wake, nitrous_host_wake_isr, - lpm->host_wake_pol ? IRQF_TRIGGER_LOW : IRQF_TRIGGER_HIGH, - "bt_host_wake", &lpm->pdev->dev); - if (rc < 0) { - pr_err("%s: unable to request IRQ for bt_host_wake GPIO\n", - __func__); - goto err_request_irq; + 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; - wake_lock_init(&lpm->dev_lock, WAKE_LOCK_SUSPEND, "bt_dev_tx_wake"); - wake_lock_init(&lpm->host_lock, WAKE_LOCK_SUSPEND, "bt_host_rx_wake"); - - /* Configure wake peer callback to be called at the onset of Tx. */ - msm_hs_set_wake_peer(lpm->uart_port, nitrous_prepare_uart_tx_locked); + 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; -err_request_irq: - lpm->irq_host_wake = 0; +fail: + nitrous_lpm_remove_proc_entries(lpm); return rc; } static void nitrous_lpm_cleanup(struct nitrous_bt_lpm *lpm) { - free_irq(lpm->irq_host_wake, NULL); + nitrous_lpm_runtime_disable(lpm); lpm->irq_host_wake = 0; - msm_hs_set_wake_peer(lpm->uart_port, NULL); - wake_lock_destroy(&lpm->dev_lock); - wake_lock_destroy(&lpm->host_lock); + + nitrous_lpm_remove_proc_entries(lpm); } /* - * Set BT power on/off (blocked is true: off; blocked is false: on) + * Set BT power on/off (blocked is true: OFF; blocked is false: ON) */ static int nitrous_rfkill_set_power(void *data, bool blocked) { struct nitrous_bt_lpm *lpm = data; if (!lpm) { - pr_err("%s: missing lpm\n", __func__); + pr_err("[BT] %s: missing lpm\n", __func__); return -EINVAL; } - pr_info("%s: %s (blocked=%d)\n", __func__, blocked ? "off" : "on", + pr_info("[BT] %s: %s (blocked=%d)\n", __func__, blocked ? "off" : "on", blocked); if (blocked == lpm->rfkill_blocked) { - pr_info("%s already in requested state. Ignoring.\n", __func__); + pr_info("[BT] %s(%s) already in requested state\n", __func__, + blocked ? "off" : "on"); return 0; } - if (!blocked) { - int rc; + /* Reset to make sure LPM is disabled */ + nitrous_lpm_runtime_disable(lpm); - /* - * Power up the BT chip. Datasheet of BCM4343W suggests at - * least a 10ms time delay between consecutive toggles. - */ - gpio_set_value(lpm->gpio_power, 0); + if (!blocked) { + /* Power up the BT chip. delay between consecutive toggles. */ + pr_debug("[BT] REG_ON: Low"); + gpiod_set_value_cansleep(lpm->gpio_power, false); msleep(30); - gpio_set_value(lpm->gpio_power, 1); + pr_debug("[BT] REG_ON: High"); + gpiod_set_value_cansleep(lpm->gpio_power, true); - /* Enable host_wake irq to get ready */ - rc = irq_set_irq_type(lpm->irq_host_wake, - lpm->host_wake_pol ? - IRQF_TRIGGER_LOW : IRQF_TRIGGER_HIGH); - if (unlikely(rc)) - pr_err("%s: error setting irq type %d\n", __func__, rc); - enable_irq(lpm->irq_host_wake); + pr_debug("[BT] DEV_WAKE: High"); + gpiod_set_value_cansleep(lpm->gpio_dev_wake, true); } else { - /* Disable host wake IRQ and release Rx wakelock*/ - disable_irq(lpm->irq_host_wake); - nitrous_wake_device_locked(lpm, false); - - /* Cancel pending LPM timer and release Tx wakelock*/ - hrtimer_cancel(&lpm->tx_lpm_timer); - nitrous_wake_uart(lpm, false); + pr_debug("[BT] DEV_WAKE: Low"); + gpiod_set_value_cansleep(lpm->gpio_dev_wake, false); /* Power down the BT chip */ - gpio_set_value(lpm->gpio_power, 0); + pr_debug("[BT] REG_ON: Low"); + gpiod_set_value_cansleep(lpm->gpio_power, false); } - lpm->rfkill_blocked = blocked; + /* wait for device to power cycle and come out of reset */ + usleep_range(10000, 20000); + return 0; } @@ -334,32 +387,23 @@ static const struct rfkill_ops nitrous_rfkill_ops = { .set_block = nitrous_rfkill_set_power, }; -static int nitrous_rfkill_init(struct platform_device *pdev, - struct nitrous_bt_lpm *lpm) +static int nitrous_rfkill_init(struct nitrous_bt_lpm *lpm) { int rc; - rc = read_and_request_gpio( - pdev, - "power-gpio", - &lpm->gpio_power, - GPIOF_OUT_INIT_LOW, - "power_gpio" - ); - if (unlikely(rc < 0)) - goto err_gpio_power_reg; + lpm->gpio_power = devm_gpiod_get_optional(lpm->dev, "shutdown", GPIOD_OUT_LOW); + if (IS_ERR(lpm->gpio_power)) + return PTR_ERR(lpm->gpio_power); lpm->rfkill = rfkill_alloc( "nitrous_bluetooth", - &pdev->dev, + lpm->dev, RFKILL_TYPE_BLUETOOTH, &nitrous_rfkill_ops, lpm ); - if (unlikely(!lpm->rfkill)) { - rc = -ENOMEM; - goto err_rfkill_alloc; - } + if (unlikely(!lpm->rfkill)) + return -ENOMEM; /* Make sure rfkill core is initialized to be blocked initially. */ rfkill_init_sw_state(lpm->rfkill, true); @@ -374,10 +418,6 @@ static int nitrous_rfkill_init(struct platform_device *pdev, err_rfkill_register: rfkill_destroy(lpm->rfkill); lpm->rfkill = NULL; -err_rfkill_alloc: - gpio_free(lpm->gpio_power); -err_gpio_power_reg: - lpm->gpio_power = 0; return rc; } @@ -387,74 +427,61 @@ static void nitrous_rfkill_cleanup(struct nitrous_bt_lpm *lpm) rfkill_unregister(lpm->rfkill); rfkill_destroy(lpm->rfkill); lpm->rfkill = NULL; - gpio_free(lpm->gpio_power); - lpm->gpio_power = 0; } static int nitrous_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct nitrous_bt_lpm *lpm; - struct device_node *np = dev->of_node; - u32 port_number; int rc = 0; + pr_debug("[BT] %s\n", __func__); + lpm = devm_kzalloc(dev, sizeof(struct nitrous_bt_lpm), GFP_KERNEL); if (!lpm) return -ENOMEM; - lpm->pdev = pdev; + lpm->dev = dev; - if (of_property_read_u32(np, "uart-port", &port_number)) { - pr_err("%s: UART port not in dev tree\n", __func__); - return -EINVAL; + if (device_property_read_u32(dev, "wake-polarity", &lpm->wake_polarity)) { + pr_warn("[BT] Wake polarity not in dev tree\n"); + lpm->wake_polarity = 1; } - lpm->uart_port = msm_hs_get_uart_port(port_number); - if (of_property_read_u32(np, "host-wake-polarity", - &lpm->host_wake_pol)) { - pr_err("%s: host wake polarity not in dev tree.\n", __func__); - return -EINVAL; - } - - if (of_property_read_u32(np, "dev-wake-polarity", - &lpm->dev_wake_pol)) { - pr_err("%s: dev wake polarity not in dev tree\n", __func__); - return -EINVAL; + lpm->pinctrls = devm_pinctrl_get(lpm->dev); + if (IS_ERR(lpm->pinctrls)) { + pr_warn("[BT] Can't get pinctrl\n"); + } else { + lpm->pinctrl_default_state = + pinctrl_lookup_state(lpm->pinctrls, "default"); + if (IS_ERR(lpm->pinctrl_default_state)) + pr_warn("[BT] Can't get default pinctrl state\n"); } - rc = read_and_request_gpio( - pdev, - "dev-wake-gpio", - &lpm->gpio_dev_wake, - GPIOF_OUT_INIT_LOW, - "dev_wake_gpio" - ); - if (unlikely(rc < 0)) - goto err_gpio_dev_req; - - rc = read_and_request_gpio( - pdev, - "host-wake-gpio", - &lpm->gpio_host_wake, - GPIOF_IN, - "host_wake_gpio" - ); - if (unlikely(rc < 0)) - goto err_gpio_host_req; + lpm->gpio_dev_wake = devm_gpiod_get_optional(dev, "device-wakeup", GPIOD_OUT_LOW); + if (IS_ERR(lpm->gpio_dev_wake)) + return PTR_ERR(lpm->gpio_dev_wake); - device_init_wakeup(dev, true); + lpm->gpio_host_wake = devm_gpiod_get_optional(dev, "host-wakeup", GPIOD_IN); + if (IS_ERR(lpm->gpio_host_wake)) + return PTR_ERR(lpm->gpio_host_wake); rc = nitrous_lpm_init(lpm); if (unlikely(rc)) goto err_lpm_init; - rc = nitrous_rfkill_init(pdev, lpm); + rc = nitrous_rfkill_init(lpm); if (unlikely(rc)) goto err_rfkill_init; + if (!IS_ERR_OR_NULL(lpm->pinctrl_default_state)) { + rc = pinctrl_select_state(lpm->pinctrls, + lpm->pinctrl_default_state); + if (unlikely(rc)) + pr_warn("[BT] Can't set default pinctrl state\n"); + } + platform_set_drvdata(pdev, lpm); - bt_lpm = lpm; return rc; @@ -462,13 +489,6 @@ err_rfkill_init: nitrous_rfkill_cleanup(lpm); err_lpm_init: nitrous_lpm_cleanup(lpm); - device_init_wakeup(dev, false); - gpio_free(lpm->gpio_host_wake); -err_gpio_host_req: - lpm->gpio_host_wake = 0; - gpio_free(lpm->gpio_dev_wake); -err_gpio_dev_req: - lpm->gpio_dev_wake = 0; devm_kfree(dev, lpm); return rc; } @@ -478,67 +498,85 @@ static int nitrous_remove(struct platform_device *pdev) struct nitrous_bt_lpm *lpm = platform_get_drvdata(pdev); if (!lpm) { - pr_err("%s: missing lpm\n", __func__); + pr_err("[BT] %s: missing lpm\n", __func__); return -EINVAL; } nitrous_rfkill_cleanup(lpm); nitrous_lpm_cleanup(lpm); - gpio_free(lpm->gpio_dev_wake); - gpio_free(lpm->gpio_host_wake); - lpm->gpio_dev_wake = 0; - lpm->gpio_host_wake = 0; devm_kfree(&pdev->dev, lpm); return 0; } +static int nitrous_suspend_device(struct device *dev) +{ + struct nitrous_bt_lpm *lpm = dev_get_drvdata(dev); + + pr_debug("[BT] %s from %s\n", __func__, + (lpm->is_suspended ? "asleep" : "awake")); + + nitrous_wake_controller(lpm, false); + lpm->is_suspended = true; + + return 0; +} + +static int nitrous_resume_device(struct device *dev) +{ + struct nitrous_bt_lpm *lpm = dev_get_drvdata(dev); + + pr_debug("[BT] %s from %s\n", __func__, + (lpm->is_suspended ? "asleep" : "awake")); + + nitrous_wake_controller(lpm, true); + lpm->is_suspended = false; + + return 0; +} + static int nitrous_suspend(struct device *dev) { - struct platform_device *pdev = to_platform_device(dev); - struct nitrous_bt_lpm *lpm = platform_get_drvdata(pdev); + struct nitrous_bt_lpm *lpm = dev_get_drvdata(dev); - if (device_may_wakeup(&pdev->dev) && !lpm->rfkill_blocked) { - enable_irq_wake(lpm->irq_host_wake); + pr_debug("[BT] %s\n", __func__); - /* Reset flag to capture pending irq before resume */ - lpm->pending_irq = false; - } + if (pm_runtime_active(dev)) + nitrous_suspend_device(dev); - lpm->is_suspended = true; + if (device_may_wakeup(dev) && lpm->lpm_enabled) { + enable_irq_wake(lpm->irq_host_wake); + pr_debug("[BT] Host wake IRQ enabled\n"); + } return 0; } static int nitrous_resume(struct device *dev) { - struct platform_device *pdev = to_platform_device(dev); - struct nitrous_bt_lpm *lpm = platform_get_drvdata(pdev); + struct nitrous_bt_lpm *lpm = dev_get_drvdata(dev); - if (device_may_wakeup(&pdev->dev) && !lpm->rfkill_blocked) { - disable_irq_wake(lpm->irq_host_wake); + pr_debug("[BT] %s\n", __func__); - /* Handle pending host wake irq. */ - if (lpm->pending_irq) { - pr_info("%s: pending host_wake irq\n", __func__); - nitrous_wake_uart(lpm, true); - lpm->pending_irq = false; - } + if (device_may_wakeup(dev) && lpm->lpm_enabled) { + disable_irq_wake(lpm->irq_host_wake); + pr_debug("[BT] Host wake IRQ disabled\n"); } - lpm->is_suspended = false; + nitrous_resume_device(dev); return 0; } static struct of_device_id nitrous_match_table[] = { - {.compatible = "goog,nitrous"}, + { .compatible = "goog,nitrous" }, {} }; static const struct dev_pm_ops nitrous_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(nitrous_suspend, nitrous_resume) + SET_RUNTIME_PM_OPS(nitrous_suspend_device, nitrous_resume_device, NULL) }; static struct platform_driver nitrous_platform_driver = { |