diff options
Diffstat (limited to 'drivers/bluetooth/ti_bluesleep.c')
| -rw-r--r-- | drivers/bluetooth/ti_bluesleep.c | 371 |
1 files changed, 371 insertions, 0 deletions
diff --git a/drivers/bluetooth/ti_bluesleep.c b/drivers/bluetooth/ti_bluesleep.c new file mode 100644 index 00000000000..d86fd261e67 --- /dev/null +++ b/drivers/bluetooth/ti_bluesleep.c | |||
| @@ -0,0 +1,371 @@ | |||
| 1 | /* | ||
| 2 | * TI Bluesleep driver | ||
| 3 | * Kernel module responsible for Wake up of Host | ||
| 4 | * Copyright (C) 2009-2010 Texas Instruments | ||
| 5 | |||
| 6 | |||
| 7 | * This program is free software; you can redistribute it and/or modify | ||
| 8 | * it under the terms of the GNU General Public License version 2 as | ||
| 9 | * published by the Free Software Foundation. | ||
| 10 | * | ||
| 11 | * You should have received a copy of the GNU General Public License | ||
| 12 | * along with this program; if not, write to the Free Software | ||
| 13 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
| 14 | * | ||
| 15 | * This program is distributed in the hope that it will be useful, but | ||
| 16 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY | ||
| 17 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | ||
| 18 | * for more details. | ||
| 19 | * | ||
| 20 | * Copyright (C) 2006-2007 - Motorola | ||
| 21 | * Copyright (c) 2008-2009, Code Aurora Forum. All rights reserved. | ||
| 22 | * | ||
| 23 | * Date Author Comment | ||
| 24 | * ----------- -------------- -------------------------------- | ||
| 25 | * 2006-Apr-28 Motorola The kernel module for running the Bluetooth(R) | ||
| 26 | * Sleep-Mode Protocol from the Host side | ||
| 27 | * 2006-Sep-08 Motorola Added workqueue for handling sleep work. | ||
| 28 | * 2007-Jan-24 Motorola Added mbm_handle_ioi() call to ISR. | ||
| 29 | * 2009-Aug-10 Motorola Changed "add_timer" to "mod_timer" to solve | ||
| 30 | * race when flurry of queued work comes in. | ||
| 31 | */ | ||
| 32 | |||
| 33 | #include <linux/module.h> /* kernel module definitions */ | ||
| 34 | #include <linux/errno.h> | ||
| 35 | #include <linux/init.h> | ||
| 36 | #include <linux/interrupt.h> | ||
| 37 | #include <linux/kernel.h> | ||
| 38 | #include <linux/notifier.h> | ||
| 39 | #include <linux/proc_fs.h> | ||
| 40 | #include <linux/spinlock.h> | ||
| 41 | #include <linux/timer.h> | ||
| 42 | #include <linux/uaccess.h> | ||
| 43 | #include <linux/version.h> | ||
| 44 | #include <linux/workqueue.h> | ||
| 45 | #include <linux/platform_device.h> | ||
| 46 | |||
| 47 | #include <linux/irq.h> | ||
| 48 | #include <linux/ioport.h> | ||
| 49 | #include <linux/param.h> | ||
| 50 | #include <linux/bitops.h> | ||
| 51 | #include <linux/termios.h> | ||
| 52 | #include <linux/wakelock.h> | ||
| 53 | #include <mach/gpio.h> | ||
| 54 | #include <linux/serial_core.h> | ||
| 55 | #include <linux/tegra_uart.h> | ||
| 56 | |||
| 57 | #include <net/bluetooth/bluetooth.h> | ||
| 58 | #include <net/bluetooth/hci_core.h> /* event notifications */ | ||
| 59 | #include "hci_uart.h" | ||
| 60 | |||
| 61 | /* | ||
| 62 | * Defines | ||
| 63 | */ | ||
| 64 | |||
| 65 | #define VERSION "1.1" | ||
| 66 | |||
| 67 | #define POLARITY_LOW 0 | ||
| 68 | #define POLARITY_HIGH 1 | ||
| 69 | |||
| 70 | struct bluesleep_info { | ||
| 71 | unsigned host_wake_irq; | ||
| 72 | struct uart_port *uport; | ||
| 73 | struct wake_lock wake_lock; | ||
| 74 | int irq_polarity; | ||
| 75 | int has_ext_wake; | ||
| 76 | }; | ||
| 77 | |||
| 78 | |||
| 79 | /* state variable names and bit positions */ | ||
| 80 | #define FLAG_RESET 0x00 | ||
| 81 | #define BT_ACTIVE 0x02 | ||
| 82 | #define BT_SUSPEND 0x04 | ||
| 83 | |||
| 84 | |||
| 85 | /* work function */ | ||
| 86 | static void hostwake_sleep_work(struct work_struct *work); | ||
| 87 | |||
| 88 | /* work queue */ | ||
| 89 | DECLARE_DELAYED_WORK(ti_sleep_workqueue, hostwake_sleep_work); | ||
| 90 | |||
| 91 | /* Macros for handling sleep work */ | ||
| 92 | #define hostwake_workqueue() schedule_delayed_work(&ti_sleep_workqueue, 0) | ||
| 93 | |||
| 94 | static struct bluesleep_info *bsi; | ||
| 95 | |||
| 96 | /* module usage */ | ||
| 97 | static atomic_t open_count = ATOMIC_INIT(1); | ||
| 98 | |||
| 99 | /* | ||
| 100 | * Global variables | ||
| 101 | */ | ||
| 102 | /** Global state flags */ | ||
| 103 | static unsigned long flags; | ||
| 104 | |||
| 105 | /** Tasklet to respond to change in hostwake line */ | ||
| 106 | static struct tasklet_struct hostwake_task; | ||
| 107 | |||
| 108 | /** Lock for state transitions */ | ||
| 109 | static spinlock_t rw_lock; | ||
| 110 | |||
| 111 | /* | ||
| 112 | * Local functions | ||
| 113 | */ | ||
| 114 | static void hsuart_power(int on) | ||
| 115 | { | ||
| 116 | pr_debug("%s", __func__); | ||
| 117 | |||
| 118 | if (on) { | ||
| 119 | tegra_uart_request_clock_on(bsi->uport); | ||
| 120 | tegra_uart_set_mctrl(bsi->uport, TIOCM_RTS); | ||
| 121 | } else { | ||
| 122 | tegra_uart_set_mctrl(bsi->uport, 0); | ||
| 123 | tegra_uart_request_clock_off(bsi->uport); | ||
| 124 | } | ||
| 125 | } | ||
| 126 | |||
| 127 | |||
| 128 | |||
| 129 | /** | ||
| 130 | * @brief@ main sleep work handling function which update the flags | ||
| 131 | * and activate and deactivate UART . | ||
| 132 | */ | ||
| 133 | |||
| 134 | static void hostwake_sleep_work(struct work_struct *work) | ||
| 135 | { | ||
| 136 | pr_debug("%s", __func__); | ||
| 137 | free_irq(bsi->host_wake_irq, "tibluesleep"); | ||
| 138 | /*Activating UART */ | ||
| 139 | if (test_bit(BT_SUSPEND, &flags)) { | ||
| 140 | BT_DBG("Activate UART"); | ||
| 141 | hsuart_power(1); | ||
| 142 | |||
| 143 | } | ||
| 144 | bsi->has_ext_wake = 0; | ||
| 145 | clear_bit(BT_SUSPEND, &flags); | ||
| 146 | set_bit(BT_ACTIVE, &flags); | ||
| 147 | |||
| 148 | } | ||
| 149 | |||
| 150 | |||
| 151 | /** | ||
| 152 | * A tasklet function that runs in tasklet context | ||
| 153 | * @param data Not used. | ||
| 154 | */ | ||
| 155 | static void bluesleep_hostwake_task(unsigned long data) | ||
| 156 | { | ||
| 157 | pr_debug("%s", __func__); | ||
| 158 | disable_irq(bsi->host_wake_irq); | ||
| 159 | spin_lock(&rw_lock); | ||
| 160 | hostwake_workqueue(); | ||
| 161 | spin_unlock(&rw_lock); | ||
| 162 | } | ||
| 163 | |||
| 164 | |||
| 165 | /** | ||
| 166 | * Schedules a tasklet to run when receiving an interrupt on the | ||
| 167 | * <code>HOST_WAKE</code> GPIO pin. | ||
| 168 | * @param irq Not used. | ||
| 169 | * @param dev_id Not used. | ||
| 170 | */ | ||
| 171 | static irqreturn_t bluesleep_hostwake_isr(int irq, void *dev_id) | ||
| 172 | { | ||
| 173 | |||
| 174 | pr_debug("%s", __func__); | ||
| 175 | /* schedule a tasklet to handle the change in the host wake line */ | ||
| 176 | bsi->has_ext_wake = 1; | ||
| 177 | tasklet_schedule(&hostwake_task); | ||
| 178 | return IRQ_HANDLED; | ||
| 179 | } | ||
| 180 | |||
| 181 | /** | ||
| 182 | * Starts the Sleep-Mode Protocol on the Host. | ||
| 183 | * @return On success, 0. On error, -1, and <code>errno</code> is set | ||
| 184 | * appropriately. | ||
| 185 | */ | ||
| 186 | |||
| 187 | int bluesleep_start(struct uart_port *uport) | ||
| 188 | { | ||
| 189 | int retval; | ||
| 190 | bsi->uport = uport; | ||
| 191 | pr_debug("%s", __func__); | ||
| 192 | |||
| 193 | if (test_bit(BT_SUSPEND, &flags)) { | ||
| 194 | BT_DBG("bluesleep_acquire irq"); | ||
| 195 | if (bsi->irq_polarity == POLARITY_LOW) { | ||
| 196 | retval = request_irq(bsi->host_wake_irq, bluesleep_hostwake_isr, | ||
| 197 | IRQF_DISABLED | IRQF_TRIGGER_FALLING, | ||
| 198 | "bluetooth hostwake", "tibluesleep"); | ||
| 199 | } else { | ||
| 200 | retval = request_irq(bsi->host_wake_irq, bluesleep_hostwake_isr, | ||
| 201 | IRQF_DISABLED | IRQF_TRIGGER_RISING, | ||
| 202 | "bluetooth hostwake", "tibluesleep"); | ||
| 203 | } | ||
| 204 | if (retval < 0) { | ||
| 205 | BT_ERR("Couldn't acquire BT_HOST_WAKE IRQ"); | ||
| 206 | goto fail; | ||
| 207 | } | ||
| 208 | |||
| 209 | retval = enable_irq_wake(bsi->host_wake_irq); | ||
| 210 | if (retval < 0) { | ||
| 211 | BT_ERR("Couldn't enable BT_HOST_WAKE as wakeup" | ||
| 212 | "interrupt retval %d\n", retval); | ||
| 213 | free_irq(bsi->host_wake_irq, NULL); | ||
| 214 | goto fail; | ||
| 215 | } | ||
| 216 | } | ||
| 217 | |||
| 218 | return 0; | ||
| 219 | fail: | ||
| 220 | atomic_inc(&open_count); | ||
| 221 | return retval; | ||
| 222 | } | ||
| 223 | |||
| 224 | /** | ||
| 225 | * Stops the Sleep-Mode Protocol on the Host. | ||
| 226 | */ | ||
| 227 | void bluesleep_stop(void) | ||
| 228 | { | ||
| 229 | |||
| 230 | pr_debug("%s", __func__); | ||
| 231 | |||
| 232 | if (disable_irq_wake(bsi->host_wake_irq)) | ||
| 233 | BT_ERR("Couldn't disable hostwake IRQ wakeup mode\n"); | ||
| 234 | |||
| 235 | free_irq(bsi->host_wake_irq, NULL); | ||
| 236 | } | ||
| 237 | |||
| 238 | static int bluesleep_probe(struct platform_device *pdev) | ||
| 239 | { | ||
| 240 | int ret; | ||
| 241 | struct resource *res; | ||
| 242 | |||
| 243 | bsi = kzalloc(sizeof(struct bluesleep_info), GFP_KERNEL); | ||
| 244 | if (!bsi) | ||
| 245 | return -ENOMEM; | ||
| 246 | |||
| 247 | res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, | ||
| 248 | "host_wake"); | ||
| 249 | if (!res) { | ||
| 250 | BT_ERR("couldn't find host_wake irq\n"); | ||
| 251 | ret = -ENODEV; | ||
| 252 | goto free_bsi; | ||
| 253 | } | ||
| 254 | |||
| 255 | bsi->host_wake_irq = res->start; | ||
| 256 | |||
| 257 | if (bsi->host_wake_irq < 0) { | ||
| 258 | BT_ERR("couldn't find host_wake irq"); | ||
| 259 | ret = -ENODEV; | ||
| 260 | goto free_bsi; | ||
| 261 | } | ||
| 262 | if (res->flags & IORESOURCE_IRQ_LOWEDGE) | ||
| 263 | bsi->irq_polarity = POLARITY_LOW;/*low edge (falling edge)*/ | ||
| 264 | else | ||
| 265 | bsi->irq_polarity = POLARITY_HIGH;/*anything else*/ | ||
| 266 | |||
| 267 | wake_lock_init(&bsi->wake_lock, WAKE_LOCK_SUSPEND, "bluesleep"); | ||
| 268 | clear_bit(BT_SUSPEND, &flags); | ||
| 269 | set_bit(BT_ACTIVE, &flags); | ||
| 270 | |||
| 271 | return 0; | ||
| 272 | |||
| 273 | free_bsi: | ||
| 274 | kfree(bsi); | ||
| 275 | return ret; | ||
| 276 | } | ||
| 277 | |||
| 278 | static int bluesleep_remove(struct platform_device *pdev) | ||
| 279 | { | ||
| 280 | pr_debug("%s", __func__); | ||
| 281 | kfree(bsi); | ||
| 282 | return 0; | ||
| 283 | } | ||
| 284 | |||
| 285 | |||
| 286 | static int bluesleep_resume(struct platform_device *pdev) | ||
| 287 | { | ||
| 288 | |||
| 289 | pr_debug("%s", __func__); | ||
| 290 | if (test_bit(BT_SUSPEND, &flags)) { | ||
| 291 | |||
| 292 | if ((bsi->uport != NULL) && (bsi->has_ext_wake)) { | ||
| 293 | tegra_uart_request_clock_on(bsi->uport); | ||
| 294 | tegra_uart_set_mctrl(bsi->uport, TIOCM_RTS); | ||
| 295 | } | ||
| 296 | clear_bit(BT_SUSPEND, &flags); | ||
| 297 | set_bit(BT_ACTIVE, &flags); | ||
| 298 | } | ||
| 299 | |||
| 300 | return 0; | ||
| 301 | } | ||
| 302 | |||
| 303 | static int bluesleep_suspend(struct platform_device *pdev, pm_message_t state) | ||
| 304 | { | ||
| 305 | pr_debug("%s", __func__); | ||
| 306 | set_bit(BT_SUSPEND, &flags); | ||
| 307 | return 0; | ||
| 308 | } | ||
| 309 | |||
| 310 | static struct platform_driver bluesleep_driver = { | ||
| 311 | .probe = bluesleep_probe, | ||
| 312 | .remove = bluesleep_remove, | ||
| 313 | .suspend = bluesleep_suspend, | ||
| 314 | .resume = bluesleep_resume, | ||
| 315 | .driver = { | ||
| 316 | .name = "tibluesleep", | ||
| 317 | .owner = THIS_MODULE, | ||
| 318 | }, | ||
| 319 | }; | ||
| 320 | /** | ||
| 321 | * Initializes the module. | ||
| 322 | * @return On success, 0. On error, -1, and <code>errno</code> is set | ||
| 323 | * appropriately. | ||
| 324 | */ | ||
| 325 | static int __init bluesleep_init(void) | ||
| 326 | { | ||
| 327 | int retval; | ||
| 328 | |||
| 329 | BT_INFO("BlueSleep Mode Driver Ver %s", VERSION); | ||
| 330 | |||
| 331 | retval = platform_driver_register(&bluesleep_driver); | ||
| 332 | if (retval) | ||
| 333 | goto fail; | ||
| 334 | |||
| 335 | if (bsi == NULL) | ||
| 336 | return 0; | ||
| 337 | |||
| 338 | flags = FLAG_RESET; /* clear all status bits */ | ||
| 339 | |||
| 340 | /* Initialize spinlock. */ | ||
| 341 | spin_lock_init(&rw_lock); | ||
| 342 | |||
| 343 | /* initialize host wake tasklet */ | ||
| 344 | tasklet_init(&hostwake_task, bluesleep_hostwake_task, 0); | ||
| 345 | |||
| 346 | return 0; | ||
| 347 | fail: | ||
| 348 | return retval; | ||
| 349 | } | ||
| 350 | |||
| 351 | /** | ||
| 352 | * Cleans up the module. | ||
| 353 | */ | ||
| 354 | |||
| 355 | static void __exit bluesleep_exit(void) | ||
| 356 | { | ||
| 357 | if (bsi == NULL) | ||
| 358 | return; | ||
| 359 | /* assert bt wake */ | ||
| 360 | free_irq(bsi->host_wake_irq, NULL); | ||
| 361 | platform_driver_unregister(&bluesleep_driver); | ||
| 362 | |||
| 363 | } | ||
| 364 | |||
| 365 | module_init(bluesleep_init); | ||
| 366 | module_exit(bluesleep_exit); | ||
| 367 | |||
| 368 | MODULE_DESCRIPTION("TI Bluetooth Sleep Mode Driver ver %s " VERSION); | ||
| 369 | #ifdef MODULE_LICENSE | ||
| 370 | MODULE_LICENSE("GPL"); | ||
| 371 | #endif | ||
