summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCheney Ni <cheneyni@google.com>2020-12-17 14:28:32 +0800
committerCheney Ni <cheneyni@google.com>2020-12-22 20:20:05 +0800
commit8e7b8af19d88f96a535f077628fcde05de8a701f (patch)
tree452de16573d55d68d37ae47f367cb9b2868a6e21
parentba9cc89ce274c7c8d4c39ab86d964fa40bcecd56 (diff)
downloadbroadcom-8e7b8af19d88f96a535f077628fcde05de8a701f.tar.gz
Nitrous: BCM Bluetooth Power Management for Rx
This change brings PM to manage the BTBCM controller power state, and is able to wake up by Rx interrupt. Please note there is no Tx information, but it still needs another approach to complete LPM functionality. Bug: 172977479 Bug: 172975224 Signed-off-by: Cheney Ni <cheneyni@google.com> Change-Id: I5a726f3e62890987ef6fb9d22fbdec470ae4c16f
-rw-r--r--nitrous.c188
1 files changed, 187 insertions, 1 deletions
diff --git a/nitrous.c b/nitrous.c
index 0810a5d..8a35346 100644
--- a/nitrous.c
+++ b/nitrous.c
@@ -7,6 +7,7 @@
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
+#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/pinctrl/consumer.h>
@@ -15,20 +16,128 @@
#include <linux/property.h>
#include <linux/rfkill.h>
+#define NITROUS_AUTOSUSPEND_DELAY 1000 /* autosleep delay 1000 ms */
+
struct nitrous_bt_lpm {
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 device *dev;
struct rfkill *rfkill;
bool rfkill_blocked; /* blocked: OFF; not blocked: ON */
+ bool lpm_enabled;
};
/*
+ * Wake up or sleep BT device for Tx.
+ */
+static inline void nitrous_wake_controller(struct nitrous_bt_lpm *lpm, bool wake)
+{
+ 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);
+}
+
+/*
+ * 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 *data)
+{
+ 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;
+}
+
+static int nitrous_lpm_runtime_enable(struct nitrous_bt_lpm *lpm)
+{
+ int rc;
+
+ if (lpm->irq_host_wake <= 0)
+ return -EOPNOTSUPP;
+
+ if (lpm->rfkill_blocked) {
+ pr_err("[BT] Unexpected LPM request\n");
+ return -EINVAL;
+ }
+
+ 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;
+ }
+
+ 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);
+
+ bt_lpm->lpm_enabled = true;
+
+ return rc;
+}
+
+static void nitrous_lpm_runtime_disable(struct nitrous_bt_lpm *lpm)
+{
+ if (lpm->irq_host_wake <= 0)
+ return;
+
+ if (!lpm->lpm_enabled)
+ return;
+
+ 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);
+
+ bt_lpm->lpm_enabled = false;
+}
+
+static int nitrous_lpm_init(struct nitrous_bt_lpm *lpm)
+{
+ 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"));
+ return 0;
+}
+
+static void nitrous_lpm_cleanup(struct nitrous_bt_lpm *lpm)
+{
+ nitrous_lpm_runtime_disable(lpm);
+ lpm->irq_host_wake = 0;
+}
+
+/*
* Set BT power on/off (blocked is true: OFF; blocked is false: ON)
*/
static int nitrous_rfkill_set_power(void *data, bool blocked)
@@ -49,6 +158,9 @@ static int nitrous_rfkill_set_power(void *data, bool blocked)
return 0;
}
+ /* Reset to make sure LPM is disabled */
+ nitrous_lpm_runtime_disable(lpm);
+
if (!blocked) {
/* Power up the BT chip. delay between consecutive toggles. */
pr_debug("[BT] REG_ON: Low");
@@ -69,6 +181,9 @@ 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;
}
@@ -155,6 +270,10 @@ static int nitrous_probe(struct platform_device *pdev)
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(lpm);
if (unlikely(rc))
goto err_rfkill_init;
@@ -172,6 +291,8 @@ static int nitrous_probe(struct platform_device *pdev)
err_rfkill_init:
nitrous_rfkill_cleanup(lpm);
+err_lpm_init:
+ nitrous_lpm_cleanup(lpm);
devm_kfree(dev, lpm);
return rc;
}
@@ -186,17 +307,82 @@ static int nitrous_remove(struct platform_device *pdev)
}
nitrous_rfkill_cleanup(lpm);
+ nitrous_lpm_cleanup(lpm);
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 nitrous_bt_lpm *lpm = dev_get_drvdata(dev);
+
+ pr_debug("[BT] %s\n", __func__);
+
+ if (pm_runtime_active(dev))
+ nitrous_suspend_device(dev);
+
+ 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 nitrous_bt_lpm *lpm = dev_get_drvdata(dev);
+
+ pr_debug("[BT] %s\n", __func__);
+
+ if (device_may_wakeup(dev) && lpm->lpm_enabled) {
+ disable_irq_wake(lpm->irq_host_wake);
+ pr_debug("[BT] Host wake IRQ disabled\n");
+ }
+
+ nitrous_resume_device(dev);
+
+ return 0;
+}
+
static struct of_device_id nitrous_match_table[] = {
{ .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 = {
.probe = nitrous_probe,
.remove = nitrous_remove,
@@ -204,7 +390,7 @@ static struct platform_driver nitrous_platform_driver = {
.name = "nitrous_bluetooth",
.owner = THIS_MODULE,
.of_match_table = nitrous_match_table,
- .pm = NULL,
+ .pm = &nitrous_pm_ops,
},
};