From b109f014ee735aebf126955d7e7c6ec3d186642e Mon Sep 17 00:00:00 2001 From: Jaikumar Ganesh Date: Thu, 9 Dec 2010 13:22:15 -0800 Subject: [PATCH] [ARM] tegra: stingray: Rework Bluetooth rfkill driver. Convert Bluetooth rfkill driver to both power up/down the chipset as well as enable low power mode. Add wake lock to lpm and gpio fixes. Change-Id: I9232cacdc68a603903e22c883e8a1531998d3a77 Signed-off-by: Jaikumar Ganesh --- .../arm/mach-tegra/board-stingray-bluetooth.c | 258 ++++++++++++++++-- arch/arm/mach-tegra/board-stingray.c | 14 +- arch/arm/mach-tegra/include/mach/bcm_bt_lpm.h | 27 ++ 3 files changed, 267 insertions(+), 32 deletions(-) create mode 100644 arch/arm/mach-tegra/include/mach/bcm_bt_lpm.h diff --git a/arch/arm/mach-tegra/board-stingray-bluetooth.c b/arch/arm/mach-tegra/board-stingray-bluetooth.c index bdad69390dbf..45c72bcc7a70 100644 --- a/arch/arm/mach-tegra/board-stingray-bluetooth.c +++ b/arch/arm/mach-tegra/board-stingray-bluetooth.c @@ -1,5 +1,5 @@ /* - * Bluetooth Broadcomm rfkill power control via GPIO + * Bluetooth Broadcomm and low power control via GPIO * * Copyright (C) 2010 Google, Inc. * @@ -22,8 +22,12 @@ #include #include #include +#include +#include #include #include +#include +#include #include #include "gpio-names.h" @@ -31,16 +35,34 @@ #define BT_SHUTDOWN_GPIO TEGRA_GPIO_PI7 #define BT_RESET_GPIO TEGRA_GPIO_PU0 +#define BT_WAKE_GPIO TEGRA_GPIO_PU1 +#define BT_HOST_WAKE_GPIO TEGRA_GPIO_PU6 + static struct rfkill *bt_rfkill; +struct bcm_bt_lpm { + int wake; + int host_wake; + bool rx_wake_lock_taken; + + struct hrtimer enter_lpm_timer; + ktime_t enter_lpm_delay; + + struct uart_port *uport; + + struct wake_lock wake_lock; + char wake_lock_name[100]; +} bt_lpm; + static int bcm4329_bt_rfkill_set_power(void *data, bool blocked) { - if (blocked) { - gpio_direction_output(BT_SHUTDOWN_GPIO, 0); - gpio_direction_output(BT_RESET_GPIO, 0); - } else { + // rfkill_ops callback. Turn transmitter on when blocked is false + if (!blocked) { gpio_direction_output(BT_RESET_GPIO, 1); gpio_direction_output(BT_SHUTDOWN_GPIO, 1); + } else { + gpio_direction_output(BT_SHUTDOWN_GPIO, 0); + gpio_direction_output(BT_RESET_GPIO, 0); } return 0; @@ -50,76 +72,254 @@ static const struct rfkill_ops bcm4329_bt_rfkill_ops = { .set_block = bcm4329_bt_rfkill_set_power, }; -static int bcm4329_rfkill_probe(struct platform_device *pdev) +static void set_wake_locked(int wake) +{ + bt_lpm.wake = wake; + + if (!wake) + wake_unlock(&bt_lpm.wake_lock); + + gpio_set_value(BT_WAKE_GPIO, wake); +} + +static enum hrtimer_restart enter_lpm(struct hrtimer *timer) { + unsigned long flags; + spin_lock_irqsave(&bt_lpm.uport->lock, flags); + set_wake_locked(0); + spin_unlock_irqrestore(&bt_lpm.uport->lock, flags); + + return HRTIMER_NORESTART; +} + +void bcm_bt_lpm_exit_lpm_locked(struct uart_port *uport) { + bt_lpm.uport = uport; + + hrtimer_try_to_cancel(&bt_lpm.enter_lpm_timer); + + set_wake_locked(1); + + hrtimer_start(&bt_lpm.enter_lpm_timer, bt_lpm.enter_lpm_delay, + HRTIMER_MODE_REL); +} +EXPORT_SYMBOL(bcm_bt_lpm_exit_lpm_locked); + +void bcm_bt_rx_done_locked(struct uart_port *uport) { + if (bt_lpm.host_wake) { + // Release wake in 500 ms so that higher layers can take it. + wake_lock_timeout(&bt_lpm.wake_lock, HZ/2); + bt_lpm.rx_wake_lock_taken = true; + } +} +EXPORT_SYMBOL(bcm_bt_rx_done_locked); + +static void update_host_wake_locked(int host_wake) +{ + if (host_wake == bt_lpm.host_wake) + return; + + bt_lpm.host_wake = host_wake; + + if (host_wake) { + bt_lpm.rx_wake_lock_taken = false; + wake_lock(&bt_lpm.wake_lock); + } else if (!bt_lpm.rx_wake_lock_taken) { + // Failsafe timeout of wakelock. + // If the host wake pin is asserted and no data is sent, + // when its deasserted we will enter this path + wake_lock_timeout(&bt_lpm.wake_lock, HZ/2); + } + +} + +static irqreturn_t host_wake_isr(int irq, void *dev) +{ + int host_wake; + unsigned long flags; + + host_wake = gpio_get_value(BT_HOST_WAKE_GPIO); + set_irq_type(irq, host_wake ? IRQF_TRIGGER_LOW : IRQF_TRIGGER_HIGH); + + if (!bt_lpm.uport) { + bt_lpm.host_wake = host_wake; + return IRQ_HANDLED; + } + + spin_lock_irqsave(&bt_lpm.uport->lock, flags); + update_host_wake_locked(host_wake); + spin_unlock_irqrestore(&bt_lpm.uport->lock, flags); + + return IRQ_HANDLED; +} + +static int bcm_bt_lpm_init(struct platform_device *pdev) +{ + int irq; + int ret; + int rc; + + tegra_gpio_enable(BT_WAKE_GPIO); + rc = gpio_request(BT_WAKE_GPIO, "bcm4329_wake_gpio"); + if (unlikely(rc)) { + tegra_gpio_disable(BT_WAKE_GPIO); + return rc; + } + + tegra_gpio_enable(BT_HOST_WAKE_GPIO); + rc = gpio_request(BT_HOST_WAKE_GPIO, "bcm4329_host_wake_gpio"); + if (unlikely(rc)) { + tegra_gpio_disable(BT_WAKE_GPIO); + tegra_gpio_disable(BT_HOST_WAKE_GPIO); + gpio_free(BT_WAKE_GPIO); + return rc; + } + + hrtimer_init(&bt_lpm.enter_lpm_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + bt_lpm.enter_lpm_delay = ktime_set(1, 0); /* 1 sec */ + bt_lpm.enter_lpm_timer.function = enter_lpm; + + bt_lpm.host_wake = 0; + + irq = gpio_to_irq(BT_HOST_WAKE_GPIO); + ret = request_irq(irq, host_wake_isr, IRQF_TRIGGER_HIGH, + "bt host_wake", NULL); + if (ret) { + tegra_gpio_disable(BT_WAKE_GPIO); + tegra_gpio_disable(BT_HOST_WAKE_GPIO); + + gpio_free(BT_WAKE_GPIO); + gpio_free(BT_HOST_WAKE_GPIO); + return ret; + } + + ret = set_irq_wake(irq, 1); + if (ret) { + tegra_gpio_disable(BT_WAKE_GPIO); + tegra_gpio_disable(BT_HOST_WAKE_GPIO); + + gpio_free(BT_WAKE_GPIO); + gpio_free(BT_HOST_WAKE_GPIO); + return ret; + } + + gpio_direction_output(BT_WAKE_GPIO, 0); + gpio_direction_input(BT_HOST_WAKE_GPIO); + + snprintf(bt_lpm.wake_lock_name, sizeof(bt_lpm.wake_lock_name), + "BTLowPower"); + wake_lock_init(&bt_lpm.wake_lock, WAKE_LOCK_SUSPEND, + bt_lpm.wake_lock_name); + return 0; +} + +static int bcm4329_bluetooth_probe(struct platform_device *pdev) { int rc = 0; - bool default_state = true; /* off */ + int ret = 0; tegra_gpio_enable(BT_RESET_GPIO); rc = gpio_request(BT_RESET_GPIO, "bcm4329_nreset_gpip"); - if (unlikely(rc)) + if (unlikely(rc)) { + tegra_gpio_disable(BT_RESET_GPIO); return rc; - + } tegra_gpio_enable(BT_SHUTDOWN_GPIO); rc = gpio_request(BT_SHUTDOWN_GPIO, "bcm4329_nshutdown_gpio"); - if (unlikely(rc)) + if (unlikely(rc)) { + tegra_gpio_disable(BT_RESET_GPIO); + tegra_gpio_disable(BT_SHUTDOWN_GPIO); + gpio_free(BT_RESET_GPIO); return rc; + } - bcm4329_bt_rfkill_set_power(NULL, default_state); + + bcm4329_bt_rfkill_set_power(NULL, true); bt_rfkill = rfkill_alloc("bcm4329 Bluetooth", &pdev->dev, RFKILL_TYPE_BLUETOOTH, &bcm4329_bt_rfkill_ops, NULL); - if (unlikely(!bt_rfkill)) + if (unlikely(!bt_rfkill)) { + tegra_gpio_disable(BT_RESET_GPIO); + tegra_gpio_disable(BT_SHUTDOWN_GPIO); + + gpio_free(BT_RESET_GPIO); + gpio_free(BT_SHUTDOWN_GPIO); return -ENOMEM; + } - rfkill_set_states(bt_rfkill, default_state, false); + rfkill_set_states(bt_rfkill, true, false); rc = rfkill_register(bt_rfkill); - if (unlikely(rc)) + if (unlikely(rc)) { rfkill_destroy(bt_rfkill); + tegra_gpio_disable(BT_RESET_GPIO); + tegra_gpio_disable(BT_SHUTDOWN_GPIO); - return 0; + gpio_free(BT_RESET_GPIO); + gpio_free(BT_SHUTDOWN_GPIO); + return -1; + } + + ret = bcm_bt_lpm_init(pdev); + if (ret) { + rfkill_unregister(bt_rfkill); + rfkill_destroy(bt_rfkill); + + tegra_gpio_disable(BT_RESET_GPIO); + tegra_gpio_disable(BT_SHUTDOWN_GPIO); + + gpio_free(BT_RESET_GPIO); + gpio_free(BT_SHUTDOWN_GPIO); + } + + return ret; } -static int bcm4329_rfkill_remove(struct platform_device *pdev) +static int bcm4329_bluetooth_remove(struct platform_device *pdev) { rfkill_unregister(bt_rfkill); rfkill_destroy(bt_rfkill); + + tegra_gpio_disable(BT_SHUTDOWN_GPIO); + tegra_gpio_disable(BT_RESET_GPIO); + tegra_gpio_disable(BT_WAKE_GPIO); + tegra_gpio_disable(BT_HOST_WAKE_GPIO); + gpio_free(BT_SHUTDOWN_GPIO); gpio_free(BT_RESET_GPIO); + gpio_free(BT_WAKE_GPIO); + gpio_free(BT_HOST_WAKE_GPIO); + wake_lock_destroy(&bt_lpm.wake_lock); return 0; } -static struct platform_driver bcm4329_rfkill_platform_driver = { - .probe = bcm4329_rfkill_probe, - .remove = bcm4329_rfkill_remove, +static struct platform_driver bcm4329_bluetooth_platform_driver = { + .probe = bcm4329_bluetooth_probe, + .remove = bcm4329_bluetooth_remove, .driver = { - .name = "bcm4329_rfkill", + .name = "bcm4329_bluetooth", .owner = THIS_MODULE, }, }; -static int __init bcm4329_rfkill_init(void) +static int __init bcm4329_bluetooth_init(void) { - if (!machine_is_stingray()) - return 0; - return platform_driver_register(&bcm4329_rfkill_platform_driver); + return platform_driver_register(&bcm4329_bluetooth_platform_driver); } -static void __exit bcm4329_rfkill_exit(void) +static void __exit bcm4329_bluetooth_exit(void) { - platform_driver_unregister(&bcm4329_rfkill_platform_driver); + platform_driver_unregister(&bcm4329_bluetooth_platform_driver); } -module_init(bcm4329_rfkill_init); -module_exit(bcm4329_rfkill_exit); + +module_init(bcm4329_bluetooth_init); +module_exit(bcm4329_bluetooth_exit); MODULE_ALIAS("platform:bcm4329"); -MODULE_DESCRIPTION("bcm4329_rfkill"); +MODULE_DESCRIPTION("bcm4329_bluetooth"); MODULE_AUTHOR("Jaikumar Ganesh "); MODULE_LICENSE("GPL"); diff --git a/arch/arm/mach-tegra/board-stingray.c b/arch/arm/mach-tegra/board-stingray.c index ebaa5e48b950..9ef8c700ec89 100644 --- a/arch/arm/mach-tegra/board-stingray.c +++ b/arch/arm/mach-tegra/board-stingray.c @@ -54,7 +54,9 @@ #include #include #include +#include #include +#include #include @@ -453,8 +455,8 @@ static struct platform_device tegra_gart_dev = { .resource = tegra_gart_resources }; -static struct platform_device bcm4329_rfkill = { - .name = "bcm4329_rfkill", +static struct platform_device bcm4329_bluetooth_device = { + .name = "bcm4329_bluetooth", .id = -1, }; @@ -528,10 +530,15 @@ static struct platform_device stingray_nvmap_device = { }, }; +static struct tegra_hsuart_platform_data tegra_uartc_pdata = { + .exit_lpm_cb = bcm_bt_lpm_exit_lpm_locked, + .rx_done_cb = bcm_bt_rx_done_locked, +}; + static struct platform_device *stingray_devices[] __initdata = { &cpcap_otg, &bq24617_device, - &bcm4329_rfkill, + &bcm4329_bluetooth_device, &tegra_uarta_device, &tegra_uartc_device, &tegra_uartd_device, @@ -992,6 +999,7 @@ static void __init tegra_stingray_init(void) tegra_spdif_device.dev.platform_data = &tegra_spdif_pdata; tegra_ehci1_device.dev.platform_data = &tegra_ehci_pdata[0]; + tegra_uartc_device.dev.platform_data = &tegra_uartc_pdata; res = platform_get_resource(&ram_console_device, IORESOURCE_MEM, 0); res->start = ramconsole_start; diff --git a/arch/arm/mach-tegra/include/mach/bcm_bt_lpm.h b/arch/arm/mach-tegra/include/mach/bcm_bt_lpm.h new file mode 100644 index 000000000000..d49a17510158 --- /dev/null +++ b/arch/arm/mach-tegra/include/mach/bcm_bt_lpm.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2009 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#ifndef __ASM_ARCH_BCM_BT_LPM_H +#define __ASM_ARCH_BCM_BT_LPM_H + +#include + +/* Uart driver must call this every time it beings TX, to ensure + * this driver keeps WAKE asserted during TX. Called with uart + * spinlock held. */ +extern void bcm_bt_lpm_exit_lpm_locked(struct uart_port *uport); + +/* Uart driver must call this when the rx is done.*/ +extern void bcm_bt_rx_done_locked(struct uart_port *uport); + +#endif -- 2.34.1