diff options
author | Jaikumar Ganesh <jaikumar@google.com> | 2011-06-01 14:19:53 -0700 |
---|---|---|
committer | Jaikumar Ganesh <jaikumar@google.com> | 2011-06-23 15:42:42 -0700 |
commit | 09ffcedb2fd6dfafd5ef4bb3ae51136111aa07c3 (patch) | |
tree | 0a4ae62b75730c1ba332fa7e5488b1774b3d9ac6 | |
parent | ea95afa05f889d39122d09e0b20b83998ca15618 (diff) | |
download | kernel_samsung_tuna-09ffcedb2fd6dfafd5ef4bb3ae51136111aa07c3.zip kernel_samsung_tuna-09ffcedb2fd6dfafd5ef4bb3ae51136111aa07c3.tar.gz kernel_samsung_tuna-09ffcedb2fd6dfafd5ef4bb3ae51136111aa07c3.tar.bz2 |
ARM: omap4: tuna: Enable BRCM 4330 bluetooth.
Contributions from: sj0822.lee <sj0822.lee@samsung.com>
Change-Id: Iaad054d988370661804f3d572e86912bc15c9199
Signed-off-by: Jaikumar Ganesh <jaikumar@google.com>
-rw-r--r-- | arch/arm/mach-omap2/Makefile | 1 | ||||
-rw-r--r-- | arch/arm/mach-omap2/board-tuna-bluetooth.c | 295 | ||||
-rwxr-xr-x | arch/arm/mach-omap2/board-tuna.c | 19 |
3 files changed, 315 insertions, 0 deletions
diff --git a/arch/arm/mach-omap2/Makefile b/arch/arm/mach-omap2/Makefile index e89711e..348c869 100644 --- a/arch/arm/mach-omap2/Makefile +++ b/arch/arm/mach-omap2/Makefile @@ -256,6 +256,7 @@ obj-$(CONFIG_MACH_TUNA) += board-tuna-nfc.o obj-$(CONFIG_MACH_TUNA) += board-tuna-power.o obj-$(CONFIG_MACH_TUNA) += board-tuna-sensors.o obj-$(CONFIG_MACH_TUNA) += board-tuna-wifi.o +obj-$(CONFIG_MACH_TUNA) += board-tuna-bluetooth.o obj-$(CONFIG_MACH_OMAP3517EVM) += board-am3517evm.o \ diff --git a/arch/arm/mach-omap2/board-tuna-bluetooth.c b/arch/arm/mach-omap2/board-tuna-bluetooth.c new file mode 100644 index 0000000..ec58454 --- /dev/null +++ b/arch/arm/mach-omap2/board-tuna-bluetooth.c @@ -0,0 +1,295 @@ +/* + * Bluetooth Broadcomm and low power control via GPIO + * + * Copyright (C) 2011 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 + * + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/gpio.h> +#include <linux/hrtimer.h> +#include <linux/irq.h> +#include <linux/rfkill.h> +#include <linux/platform_device.h> +#include <linux/serial_core.h> +#include <linux/wakelock.h> +#include <asm/mach-types.h> + +#define BT_REG_GPIO 103 +#define BT_RESET_GPIO 42 + +#define BT_WAKE_GPIO 27 +#define BT_HOST_WAKE_GPIO 177 + +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 bcm4330_bt_rfkill_set_power(void *data, bool blocked) +{ + // rfkill_ops callback. Turn transmitter on when blocked is false + if (!blocked) { + gpio_set_value(BT_REG_GPIO, 1); + gpio_set_value(BT_RESET_GPIO, 1); + + } else { + gpio_set_value(BT_RESET_GPIO, 0); + gpio_set_value(BT_REG_GPIO, 0); + } + + return 0; +} + +static const struct rfkill_ops bcm4330_bt_rfkill_ops = { + .set_block = bcm4330_bt_rfkill_set_power, +}; + +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); + irq_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; + + rc = gpio_request(BT_WAKE_GPIO, "bcm4330_wake_gpio"); + if (unlikely(rc)) { + return rc; + } + + rc = gpio_request(BT_HOST_WAKE_GPIO, "bcm4330_host_wake_gpio"); + if (unlikely(rc)) { + 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) { + gpio_free(BT_WAKE_GPIO); + gpio_free(BT_HOST_WAKE_GPIO); + return ret; + } + + ret = irq_set_irq_wake(irq, 1); + if (ret) { + 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 bcm4330_bluetooth_probe(struct platform_device *pdev) +{ + int rc = 0; + int ret = 0; + + rc = gpio_request(BT_RESET_GPIO, "bcm4330_nreset_gpip"); + if (unlikely(rc)) { + return rc; + } + + rc = gpio_request(BT_REG_GPIO, "bcm4330_nshutdown_gpio"); + if (unlikely(rc)) { + gpio_free(BT_RESET_GPIO); + return rc; + } + + gpio_direction_output(BT_REG_GPIO, 1); + gpio_direction_output(BT_RESET_GPIO, 1); + + bt_rfkill = rfkill_alloc("bcm4330 Bluetooth", &pdev->dev, + RFKILL_TYPE_BLUETOOTH, &bcm4330_bt_rfkill_ops, + NULL); + + if (unlikely(!bt_rfkill)) { + gpio_free(BT_RESET_GPIO); + gpio_free(BT_REG_GPIO); + return -ENOMEM; + } + + rc = rfkill_register(bt_rfkill); + + if (unlikely(rc)) { + rfkill_destroy(bt_rfkill); + gpio_free(BT_RESET_GPIO); + gpio_free(BT_REG_GPIO); + return -1; + } + + rfkill_set_states(bt_rfkill, true, false); + bcm4330_bt_rfkill_set_power(NULL, true); + + ret = bcm_bt_lpm_init(pdev); + if (ret) { + rfkill_unregister(bt_rfkill); + rfkill_destroy(bt_rfkill); + + gpio_free(BT_RESET_GPIO); + gpio_free(BT_REG_GPIO); + } + + return ret; +} + +static int bcm4330_bluetooth_remove(struct platform_device *pdev) +{ + rfkill_unregister(bt_rfkill); + rfkill_destroy(bt_rfkill); + + gpio_free(BT_REG_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 bcm4330_bluetooth_platform_driver = { + .probe = bcm4330_bluetooth_probe, + .remove = bcm4330_bluetooth_remove, + .driver = { + .name = "bcm4330_bluetooth", + .owner = THIS_MODULE, + }, +}; + +static int __init bcm4330_bluetooth_init(void) +{ + return platform_driver_register(&bcm4330_bluetooth_platform_driver); +} + +static void __exit bcm4330_bluetooth_exit(void) +{ + platform_driver_unregister(&bcm4330_bluetooth_platform_driver); +} + + +module_init(bcm4330_bluetooth_init); +module_exit(bcm4330_bluetooth_exit); + +MODULE_ALIAS("platform:bcm4330"); +MODULE_DESCRIPTION("bcm4330_bluetooth"); +MODULE_AUTHOR("Jaikumar Ganesh <jaikumar@google.com>"); +MODULE_LICENSE("GPL"); diff --git a/arch/arm/mach-omap2/board-tuna.c b/arch/arm/mach-omap2/board-tuna.c index 8b3eed9..8239ef4 100755 --- a/arch/arm/mach-omap2/board-tuna.c +++ b/arch/arm/mach-omap2/board-tuna.c @@ -137,9 +137,27 @@ static struct platform_device ramconsole_device = { .resource = ramconsole_resources, }; +static struct platform_device bcm4330_bluetooth_device = { + .name = "bcm4330_bluetooth", + .id = -1, +}; + +static void __init tuna_bt_init(void) +{ + /* BT_EN - GPIO 104 */ + omap_mux_init_signal("gpmc_ncs6.gpio_103", OMAP_PIN_OUTPUT); + /*BT_nRST - GPIO 42 */ + omap_mux_init_signal("gpmc_a18.gpio_42", OMAP_PIN_OUTPUT); + /* BT_WAKE - GPIO 27 */ + omap_mux_init_signal("dpm_emu16.gpio_27", OMAP_PIN_OUTPUT); + /* BT_HOST_WAKE - GPIO 177 */ + omap_mux_init_signal("kpd_row5.gpio_177", OMAP_PIN_INPUT); +} + static struct platform_device *tuna_devices[] __initdata = { &ramconsole_device, &wl1271_device, + &bcm4330_bluetooth_device, }; static void __init tuna_init_early(void) @@ -571,6 +589,7 @@ static void __init tuna_init(void) tuna_wlan_init(); tuna_audio_init(); tuna_i2c_init(); + tuna_bt_init(); platform_add_devices(tuna_devices, ARRAY_SIZE(tuna_devices)); board_serial_init(); omap2_hsmmc_init(mmc); |