[ARM] tegra: stingray: Rework Bluetooth rfkill driver.
authorJaikumar Ganesh <jaikumar@google.com>
Thu, 9 Dec 2010 21:22:15 +0000 (13:22 -0800)
committerJaikumar Ganesh <jaikumar@google.com>
Tue, 14 Dec 2010 01:53:56 +0000 (17:53 -0800)
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: Ib7474e25a1c4e5d07423abd6e820c011fa80b134
Signed-off-by: Jaikumar Ganesh <jaikumar@google.com>
arch/arm/mach-tegra/Makefile
arch/arm/mach-tegra/board-stingray-bluetooth.c [new file with mode: 0644]
arch/arm/mach-tegra/board-stingray-rfkill.c [deleted file]
arch/arm/mach-tegra/board-stingray.c
arch/arm/mach-tegra/include/mach/bcm_bt_lpm.h [new file with mode: 0644]

index 75a144d76d87ae0b17b3489d1036cba247544040..2c0e6d5442a8f1e3252726a956ef3eb2277cb55b 100644 (file)
@@ -66,7 +66,7 @@ obj-${CONFIG_MACH_STINGRAY}             += board-stingray-sensors.o
 obj-${CONFIG_MACH_STINGRAY}             += board-stingray-wlan_nvs.o
 obj-${CONFIG_MACH_STINGRAY}             += board-stingray-touch.o
 obj-${CONFIG_MACH_STINGRAY}             += board-stingray-power.o
-obj-${CONFIG_MACH_STINGRAY}             += board-stingray-rfkill.o
+obj-${CONFIG_MACH_STINGRAY}             += board-stingray-bluetooth.o
 obj-${CONFIG_MACH_STINGRAY}             += board-stingray-gps.o
 obj-${CONFIG_MACH_STINGRAY}             += board-stingray-usbnet.o
 obj-${CONFIG_MACH_STINGRAY}             += board-stingray-bootinfo.o
diff --git a/arch/arm/mach-tegra/board-stingray-bluetooth.c b/arch/arm/mach-tegra/board-stingray-bluetooth.c
new file mode 100644 (file)
index 0000000..023e12c
--- /dev/null
@@ -0,0 +1,315 @@
+/*
+ * Bluetooth Broadcomm  and low power control via GPIO
+ *
+ *  Copyright (C) 2010 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>
+
+#include "gpio-names.h"
+
+#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;
+
+       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)
+{
+       // 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;
+}
+
+static const struct rfkill_ops bcm4329_bt_rfkill_ops = {
+       .set_block = bcm4329_bt_rfkill_set_power,
+};
+
+static void set_wake(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) {
+       set_wake(0);
+
+       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(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(struct uart_port *uport) {
+       // Release wake in 500 ms so that higher layers can take it.
+       wake_lock_timeout(&bt_lpm.wake_lock, HZ/2);
+}
+EXPORT_SYMBOL(bcm_bt_rx_done);
+
+static void update_host_wake(int host_wake)
+{
+       if (host_wake == bt_lpm.host_wake)
+               return;
+       bt_lpm.host_wake = host_wake;
+       if (host_wake)
+               wake_lock(&bt_lpm.wake_lock);
+       else  {
+               // 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;
+
+       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;
+       }
+
+       update_host_wake(host_wake);
+
+       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;
+       int ret = 0;
+
+       tegra_gpio_enable(BT_RESET_GPIO);
+       rc = gpio_request(BT_RESET_GPIO, "bcm4329_nreset_gpip");
+       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)) {
+               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, true);
+
+       bt_rfkill = rfkill_alloc("bcm4329 Bluetooth", &pdev->dev,
+                               RFKILL_TYPE_BLUETOOTH, &bcm4329_bt_rfkill_ops,
+                               NULL);
+
+       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, true, false);
+
+       rc = rfkill_register(bt_rfkill);
+
+       if (unlikely(rc)) {
+               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 -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_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_bluetooth_platform_driver = {
+       .probe = bcm4329_bluetooth_probe,
+       .remove = bcm4329_bluetooth_remove,
+       .driver = {
+                  .name = "bcm4329_bluetooth",
+                  .owner = THIS_MODULE,
+                  },
+};
+
+static int __init bcm4329_bluetooth_init(void)
+{
+       if (!machine_is_stingray())
+               return 0;
+
+       return platform_driver_register(&bcm4329_bluetooth_platform_driver);
+}
+
+static void __exit bcm4329_bluetooth_exit(void)
+{
+       platform_driver_unregister(&bcm4329_bluetooth_platform_driver);
+}
+
+
+module_init(bcm4329_bluetooth_init);
+module_exit(bcm4329_bluetooth_exit);
+
+MODULE_ALIAS("platform:bcm4329");
+MODULE_DESCRIPTION("bcm4329_bluetooth");
+MODULE_AUTHOR("Jaikumar Ganesh <jaikumar@google.com>");
+MODULE_LICENSE("GPL");
diff --git a/arch/arm/mach-tegra/board-stingray-rfkill.c b/arch/arm/mach-tegra/board-stingray-rfkill.c
deleted file mode 100644 (file)
index bdad693..0000000
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Bluetooth Broadcomm rfkill power control via GPIO
- *
- *  Copyright (C) 2010 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/rfkill.h>
-#include <linux/platform_device.h>
-#include <asm/mach-types.h>
-
-#include "gpio-names.h"
-
-#define BT_SHUTDOWN_GPIO TEGRA_GPIO_PI7
-#define BT_RESET_GPIO TEGRA_GPIO_PU0
-
-static struct rfkill *bt_rfkill;
-
-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 {
-               gpio_direction_output(BT_RESET_GPIO, 1);
-               gpio_direction_output(BT_SHUTDOWN_GPIO, 1);
-       }
-
-       return 0;
-}
-
-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)
-{
-       int rc = 0;
-       bool default_state = true;  /* off */
-
-       tegra_gpio_enable(BT_RESET_GPIO);
-       rc = gpio_request(BT_RESET_GPIO, "bcm4329_nreset_gpip");
-       if (unlikely(rc))
-               return rc;
-
-
-       tegra_gpio_enable(BT_SHUTDOWN_GPIO);
-       rc = gpio_request(BT_SHUTDOWN_GPIO, "bcm4329_nshutdown_gpio");
-       if (unlikely(rc))
-               return rc;
-
-       bcm4329_bt_rfkill_set_power(NULL, default_state);
-
-       bt_rfkill = rfkill_alloc("bcm4329 Bluetooth", &pdev->dev,
-                               RFKILL_TYPE_BLUETOOTH, &bcm4329_bt_rfkill_ops,
-                               NULL);
-
-       if (unlikely(!bt_rfkill))
-               return -ENOMEM;
-
-       rfkill_set_states(bt_rfkill, default_state, false);
-
-       rc = rfkill_register(bt_rfkill);
-
-       if (unlikely(rc))
-               rfkill_destroy(bt_rfkill);
-
-       return 0;
-}
-
-static int bcm4329_rfkill_remove(struct platform_device *pdev)
-{
-       rfkill_unregister(bt_rfkill);
-       rfkill_destroy(bt_rfkill);
-       gpio_free(BT_SHUTDOWN_GPIO);
-       gpio_free(BT_RESET_GPIO);
-
-       return 0;
-}
-
-static struct platform_driver bcm4329_rfkill_platform_driver = {
-       .probe = bcm4329_rfkill_probe,
-       .remove = bcm4329_rfkill_remove,
-       .driver = {
-                  .name = "bcm4329_rfkill",
-                  .owner = THIS_MODULE,
-                  },
-};
-
-static int __init bcm4329_rfkill_init(void)
-{
-       if (!machine_is_stingray())
-               return 0;
-       return platform_driver_register(&bcm4329_rfkill_platform_driver);
-}
-
-static void __exit bcm4329_rfkill_exit(void)
-{
-       platform_driver_unregister(&bcm4329_rfkill_platform_driver);
-}
-
-module_init(bcm4329_rfkill_init);
-module_exit(bcm4329_rfkill_exit);
-
-MODULE_ALIAS("platform:bcm4329");
-MODULE_DESCRIPTION("bcm4329_rfkill");
-MODULE_AUTHOR("Jaikumar Ganesh <jaikumar@google.com>");
-MODULE_LICENSE("GPL");
index 5a9eb432c7765fbab4d8fb21a690d9af74cf5bdd..2123fd81bb668fc11bde3361b9cdde3a78c00208 100644 (file)
@@ -54,7 +54,9 @@
 #include <mach/suspend.h>
 #include <mach/system.h>
 #include <mach/tegra_fiq_debugger.h>
+#include <mach/tegra_hsuart.h>
 #include <mach/nvmap.h>
+#include <mach/bcm_bt_lpm.h>
 
 #include <linux/usb/android_composite.h>
 
@@ -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,
+};
+
 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,
@@ -991,6 +998,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 (file)
index 0000000..3eabaf4
--- /dev/null
@@ -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 <linux/serial_core.h>
+
+/* 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(struct uart_port *uport);
+
+#endif