summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTed Wang <tedwang@google.com>2021-02-25 17:50:11 +0800
committerTed Wang <tedwang@google.com>2021-04-26 10:55:41 +0800
commit552e8f4cf76556b485ad983ac1c3823d027c87f4 (patch)
treee56f37364c786ec349ea0603a88cfcda075dcd4a
parent5d7321f9bcad18bdbdf5225a2a1792202641d899 (diff)
downloadbroadcom-552e8f4cf76556b485ad983ac1c3823d027c87f4.tar.gz
Add handling for TimeSync GPIO
Check if timesync feature supported by detecting if timesync GPIO is defined in device tree with name "timesync-gpio". If supported, create an irq handler for timesync featture to handle interrupt of timesync GPIO and add a queue to store system time when there is an interrupt comes from timesync GPIO. Also, create a file node for user space to get the timestamp in queue. Bug: 175836015 Test: make Change-Id: I9ff3a6aa1d525d0872958458c4dffbd44fdb23c7
-rw-r--r--nitrous.c109
1 files changed, 106 insertions, 3 deletions
diff --git a/nitrous.c b/nitrous.c
index 3251711..cd07a65 100644
--- a/nitrous.c
+++ b/nitrous.c
@@ -18,8 +18,14 @@
#include <linux/rfkill.h>
#include <linux/rtc.h>
#include <misc/logbuffer.h>
+#include <linux/kfifo.h>
+#include <linux/slab.h>
-#define NITROUS_AUTOSUSPEND_DELAY 1000 /* autosleep delay 1000 ms */
+#define NITROUS_AUTOSUSPEND_DELAY 1000 /* autosleep delay 1000 ms */
+#define TIMESYNC_TIMESTAMP_MAX_QUEUE 16
+#define TIMESYNC_NOT_SUPPORTED 0
+#define TIMESYNC_SUPPORTED 1
+#define TIMESYNC_ENABLED 2
struct nitrous_lpm_proc;
@@ -29,12 +35,17 @@ struct nitrous_bt_lpm {
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 */
+ struct gpio_desc *gpio_timesync; /* GPIO for timesync */
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 */
+ int irq_timesync; /* IRQ associated with TIMESYNC GPIO*/
+ int timesync_state;
+ struct kfifo timestamp_queue;
+
struct device *dev;
struct rfkill *rfkill;
bool rfkill_blocked; /* blocked: OFF; not blocked: ON */
@@ -46,6 +57,7 @@ struct nitrous_bt_lpm {
#define PROC_BTWAKE 0
#define PROC_LPM 1
#define PROC_BTWRITE 2
+#define PROC_TIMESYNC 3
#define PROC_DIR "bluetooth/sleep"
struct proc_dir_entry *bluetooth_dir, *sleep_dir;
@@ -110,6 +122,22 @@ static irqreturn_t nitrous_host_wake_isr(int irq, void *data)
return IRQ_HANDLED;
}
+static irqreturn_t ntirous_timesync_isr(int irq, void *data)
+{
+ struct nitrous_bt_lpm *lpm = data;
+ ktime_t timestamp;
+ dev_dbg(lpm->dev, "Timesync IRQ: %u\n", gpiod_get_value(lpm->gpio_timesync));
+ if (unlikely(lpm->rfkill_blocked)) {
+ dev_err(lpm->dev, "Unexpected Timesync IRQ\n");
+ return IRQ_HANDLED;
+ }
+
+ timestamp = ktime_get_boottime();
+ kfifo_in(&lpm->timestamp_queue, &timestamp, sizeof(ktime_t));
+ logbuffer_log(lpm->log, "Timesync: %lld\n", ktime_to_us(timestamp));
+ return IRQ_HANDLED;
+}
+
static int nitrous_lpm_runtime_enable(struct nitrous_bt_lpm *lpm)
{
int rc;
@@ -169,6 +197,7 @@ 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;
+ ktime_t timestamp;
switch (data->operation) {
case PROC_BTWAKE:
@@ -185,6 +214,10 @@ static int nitrous_proc_show(struct seq_file *m, void *v)
(lpm->lpm_enabled ? "Enabled" : "Disabled"),
(lpm->is_suspended ? "asleep" : "awake"));
break;
+ case PROC_TIMESYNC:
+ kfifo_out(&lpm->timestamp_queue, &timestamp, sizeof(ktime_t));
+ seq_printf(m, "%lld", ktime_to_us(timestamp));
+ break;
default:
return 0;
}
@@ -268,6 +301,10 @@ static void nitrous_lpm_remove_proc_entries(struct nitrous_bt_lpm *lpm)
remove_proc_entry("btwake", sleep_dir);
remove_proc_entry("sleep", bluetooth_dir);
}
+
+ if (lpm->timesync_state) {
+ remove_proc_entry("timesync", bluetooth_dir);
+ }
remove_proc_entry("bluetooth", 0);
if (lpm->proc) {
devm_kfree(lpm->dev, lpm->proc);
@@ -277,7 +314,8 @@ static void nitrous_lpm_remove_proc_entries(struct nitrous_bt_lpm *lpm)
static int nitrous_lpm_init(struct nitrous_bt_lpm *lpm)
{
- int rc;
+ int rc, proc_size = 3;
+ unsigned long fifo_size = 0;
struct proc_dir_entry *entry;
struct nitrous_lpm_proc *data;
@@ -287,7 +325,22 @@ static int nitrous_lpm_init(struct nitrous_bt_lpm *lpm)
logbuffer_log(lpm->log, "init: IRQ: %d active: %s", lpm->irq_host_wake,
(lpm->wake_polarity ? "High" : "Low"));
- data = devm_kzalloc(lpm->dev, sizeof(struct nitrous_lpm_proc) * 3, GFP_KERNEL);
+
+ if (lpm->timesync_state) {
+ lpm->irq_timesync = gpiod_to_irq(lpm->gpio_timesync);
+
+ fifo_size = TIMESYNC_TIMESTAMP_MAX_QUEUE * sizeof(ktime_t);
+ fifo_size = roundup_pow_of_two(fifo_size);
+ if (kfifo_alloc(&lpm->timestamp_queue, fifo_size, GFP_KERNEL)) {
+ dev_err(lpm->dev, "Failed to alloc queue for Timesync");
+ logbuffer_log(lpm->log, "Failed to alloc queue for Timesync");
+ return -ENOMEM;
+ }
+
+ proc_size += 1;
+ }
+
+ data = devm_kzalloc(lpm->dev, sizeof(struct nitrous_lpm_proc) * proc_size, GFP_KERNEL);
if (data == NULL) {
dev_err(lpm->dev, "Unable to alloc memory");
logbuffer_log(lpm->log, "Unable to alloc memory");
@@ -343,6 +396,19 @@ static int nitrous_lpm_init(struct nitrous_bt_lpm *lpm)
goto fail;
}
+ if (lpm->timesync_state) {
+ /* read/write proc entries "timesync" */
+ data[3].operation = PROC_TIMESYNC;
+ data[3].lpm = lpm;
+ entry = proc_create_data("timesync", (S_IRUSR | S_IRGRP),
+ bluetooth_dir, &nitrous_proc_read_fops, data + 3);
+ if (entry == NULL) {
+ dev_err(lpm->dev, "Unable to create /proc/bluetooth/timesync entry");
+ logbuffer_log(lpm->log, "Unable to create /proc/bluetooth/timesync entry");
+ rc = -ENOMEM;
+ goto fail;
+ }
+ }
return 0;
fail:
@@ -354,10 +420,36 @@ static void nitrous_lpm_cleanup(struct nitrous_bt_lpm *lpm)
{
nitrous_lpm_runtime_disable(lpm);
lpm->irq_host_wake = 0;
+ if (lpm->timesync_state) {
+ lpm->irq_timesync = 0;
+ kfifo_free(&lpm->timestamp_queue);
+ }
nitrous_lpm_remove_proc_entries(lpm);
}
+static void toggle_timesync(struct nitrous_bt_lpm *lpm, bool enable) {
+ int rc;
+
+ if (!lpm || lpm->timesync_state == TIMESYNC_NOT_SUPPORTED)
+ return;
+ if (enable) {
+ rc = devm_request_irq(lpm->dev, lpm->irq_timesync, ntirous_timesync_isr,
+ IRQF_TRIGGER_RISING, "bt_timesync", lpm);
+ if (unlikely(rc)) {
+ lpm->timesync_state = TIMESYNC_SUPPORTED;
+ dev_err(lpm->dev, "Unable to request IRQ for bt_timesync GPIO\n");
+ logbuffer_log(lpm->log, "Unable to request IRQ for bt_timesync GPIO");
+ } else {
+ lpm->timesync_state = TIMESYNC_ENABLED;
+ }
+ } else {
+ if (lpm->timesync_state != TIMESYNC_ENABLED)
+ return;
+ devm_free_irq(lpm->dev, lpm->irq_timesync, lpm);
+ }
+}
+
/*
* Set BT power on/off (blocked is true: OFF; blocked is false: ON)
*/
@@ -408,6 +500,8 @@ static int nitrous_rfkill_set_power(void *data, bool blocked)
}
lpm->rfkill_blocked = blocked;
+ toggle_timesync(lpm, !blocked);
+
/* wait for device to power cycle and come out of reset */
usleep_range(10000, 20000);
@@ -497,6 +591,15 @@ static int nitrous_probe(struct platform_device *pdev)
if (IS_ERR(lpm->gpio_host_wake))
return PTR_ERR(lpm->gpio_host_wake);
+ lpm->gpio_timesync = devm_gpiod_get_optional(dev, "timesync", GPIOD_IN);
+ lpm->timesync_state = TIMESYNC_NOT_SUPPORTED;
+ if (IS_ERR(lpm->gpio_timesync)) {
+ dev_warn(lpm->dev, "Can't get Timesync GPIO descriptor\n");
+ } else if (lpm->gpio_timesync) {
+ lpm->timesync_state = TIMESYNC_SUPPORTED;
+ }
+ dev_dbg(lpm->dev, "Timesync support: %x", lpm->timesync_state);
+
lpm->log = logbuffer_register("btlpm");
if (IS_ERR_OR_NULL(lpm->log)) {
dev_info(lpm->dev, "logbuffer get failed\n");