From a66ea7e112d376d99eba71bf363b49c6ff733b9a Mon Sep 17 00:00:00 2001 From: Santosh Sajjan Date: Wed, 27 Jun 2012 19:10:49 +0530 Subject: [PATCH] Bluetooth: Add Sleep Support using GPIO for ATH3K driver. Two additional GPIO pins are used to support sleep mechanism between Host and Atheros BT SoC. Change-Id: If7d49e23d285b8e416af729314cffdf7c62da136 Signed-off-by: Santosh Sajjan Signed-off-by: Ram Mohan Korukonda --- drivers/bluetooth/hci_ath.c | 307 ++++++++++++++++++++++++++++++------ 1 file changed, 263 insertions(+), 44 deletions(-) diff --git a/drivers/bluetooth/hci_ath.c b/drivers/bluetooth/hci_ath.c index 4093935ddf4..2bb82ce3115 100644 --- a/drivers/bluetooth/hci_ath.c +++ b/drivers/bluetooth/hci_ath.c @@ -5,6 +5,7 @@ * power management protocol extension to H4 to support AR300x Bluetooth Chip. * * Copyright (c) 2009-2010 Atheros Communications Inc. + * Copyright (c) 2012, Code Aurora Forum. All rights reserved. * * Acknowledgements: * This file is based on hci_h4.c, which was written @@ -35,12 +36,53 @@ #include #include #include +#include +#include #include #include #include "hci_uart.h" +unsigned int enableuartsleep; +module_param(enableuartsleep, uint, 0644); +/* + * Global variables + */ +/** Global state flags */ +static unsigned long flags; + +/** Tasklet to respond to change in hostwake line */ +static struct tasklet_struct hostwake_task; + +/** Transmission timer */ +static void bluesleep_tx_timer_expire(unsigned long data); +static DEFINE_TIMER(tx_timer, bluesleep_tx_timer_expire, 0, 0); + +/** Lock for state transitions */ +static spinlock_t rw_lock; + +#define POLARITY_LOW 0 +#define POLARITY_HIGH 1 + +struct bluesleep_info { + unsigned host_wake; /* wake up host */ + unsigned ext_wake; /* wake up device */ + unsigned host_wake_irq; + int irq_polarity; +}; + +/* 1 second timeout */ +#define TX_TIMER_INTERVAL 1 + +/* state variable names and bit positions */ +#define BT_TXEXPIRED 0x01 +#define BT_SLEEPENABLE 0x02 +#define BT_SLEEPCMD 0x03 + +/* global pointer to a single hci device. */ +static struct bluesleep_info *bsi; + struct ath_struct { struct hci_uart *hu; unsigned int cur_sleep; @@ -49,35 +91,30 @@ struct ath_struct { struct work_struct ctxtsw; }; +static void hostwake_interrupt(unsigned long data) +{ + printk(KERN_INFO " wakeup host\n"); +} + +static void modify_timer_task(void) +{ + spin_lock(&rw_lock); + mod_timer(&tx_timer, jiffies + (TX_TIMER_INTERVAL * HZ)); + clear_bit(BT_TXEXPIRED, &flags); + spin_unlock(&rw_lock); + +} + static int ath_wakeup_ar3k(struct tty_struct *tty) { - struct ktermios ktermios; - int status = tty->driver->ops->tiocmget(tty); - - if (status & TIOCM_CTS) - return status; - - /* Disable Automatic RTSCTS */ - memcpy(&ktermios, tty->termios, sizeof(ktermios)); - ktermios.c_cflag &= ~CRTSCTS; - tty_set_termios(tty, &ktermios); - - /* Clear RTS first */ - status = tty->driver->ops->tiocmget(tty); - tty->driver->ops->tiocmset(tty, 0x00, TIOCM_RTS); - mdelay(20); - - /* Set RTS, wake up board */ - status = tty->driver->ops->tiocmget(tty); - tty->driver->ops->tiocmset(tty, TIOCM_RTS, 0x00); - mdelay(20); - - status = tty->driver->ops->tiocmget(tty); - - /* Disable Automatic RTSCTS */ - ktermios.c_cflag |= CRTSCTS; - status = tty_set_termios(tty, &ktermios); - + int status = 0; + if (test_bit(BT_TXEXPIRED, &flags)) { + printk(KERN_INFO "wakeup device\n"); + gpio_set_value(bsi->ext_wake, 1); + msleep(20); + gpio_set_value(bsi->ext_wake, 0); + } + modify_timer_task(); return status; } @@ -94,12 +131,8 @@ static void ath_hci_uart_work(struct work_struct *work) tty = hu->tty; /* verify and wake up controller */ - if (ath->cur_sleep) { + if (test_bit(BT_SLEEPENABLE, &flags)) status = ath_wakeup_ar3k(tty); - if (!(status & TIOCM_CTS)) - return; - } - /* Ready to send Data */ clear_bit(HCI_UART_SENDING, &hu->tx_state); hci_uart_tx_wakeup(hu); @@ -121,6 +154,11 @@ static int ath_open(struct hci_uart *hu) hu->priv = ath; ath->hu = hu; + ath->cur_sleep = enableuartsleep; + if (ath->cur_sleep == 1) { + set_bit(BT_SLEEPENABLE, &flags); + modify_timer_task(); + } INIT_WORK(&ath->ctxtsw, ath_hci_uart_work); return 0; @@ -173,9 +211,10 @@ static int ath_enqueue(struct hci_uart *hu, struct sk_buff *skb) */ if (bt_cb(skb)->pkt_type == HCI_COMMAND_PKT) { struct hci_command_hdr *hdr = (void *)skb->data; - - if (__le16_to_cpu(hdr->opcode) == HCI_OP_ATH_SLEEP) + if (__le16_to_cpu(hdr->opcode) == HCI_OP_ATH_SLEEP) { + set_bit(BT_SLEEPCMD, &flags); ath->cur_sleep = skb->data[HCI_COMMAND_HDR_SIZE]; + } } BT_DBG("hu %p skb %p", hu, skb); @@ -201,17 +240,57 @@ static struct sk_buff *ath_dequeue(struct hci_uart *hu) /* Recv data */ static int ath_recv(struct hci_uart *hu, void *data, int count) { - int ret; + struct ath_struct *ath = hu->priv; + unsigned int type; - ret = hci_recv_stream_fragment(hu->hdev, data, count); - if (ret < 0) { + if (hci_recv_stream_fragment(hu->hdev, data, count) < 0) BT_ERR("Frame Reassembly Failed"); - return ret; - } + if (count & test_bit(BT_SLEEPCMD, &flags)) { + struct sk_buff *skb = hu->hdev->reassembly[0]; + + if (!skb) { + struct { char type; } *pkt; + + /* Start of the frame */ + pkt = data; + type = pkt->type; + } else + type = bt_cb(skb)->pkt_type; + + if (type == HCI_EVENT_PKT) { + clear_bit(BT_SLEEPCMD, &flags); + printk(KERN_INFO "cur_sleep:%d\n", ath->cur_sleep); + if (ath->cur_sleep == 1) + set_bit(BT_SLEEPENABLE, &flags); + else + clear_bit(BT_SLEEPENABLE, &flags); + } + if (test_bit(BT_SLEEPENABLE, &flags)) + modify_timer_task(); + } return count; } +static void bluesleep_tx_timer_expire(unsigned long data) +{ + unsigned long irq_flags; + + if (!test_bit(BT_SLEEPENABLE, &flags)) + return; + BT_DBG("Tx timer expired"); + printk(KERN_INFO "Tx timer expired\n"); + + set_bit(BT_TXEXPIRED, &flags); +} + +static irqreturn_t bluesleep_hostwake_isr(int irq, void *dev_id) +{ + /* schedule a tasklet to handle the change in the host wake line */ + tasklet_schedule(&hostwake_task); + return IRQ_HANDLED; +} + static struct hci_uart_proto athp = { .id = HCI_UART_ATH3K, .open = ath_open, @@ -222,19 +301,159 @@ static struct hci_uart_proto athp = { .flush = ath_flush, }; +static int __init bluesleep_probe(struct platform_device *pdev) +{ + int ret; + struct resource *res; + + bsi = kzalloc(sizeof(struct bluesleep_info), GFP_KERNEL); + if (!bsi) { + ret = -ENOMEM; + goto failed; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_IO, + "gpio_host_wake"); + if (!res) { + BT_ERR("couldn't find host_wake gpio\n"); + ret = -ENODEV; + goto free_bsi; + } + bsi->host_wake = res->start; + + ret = gpio_request(bsi->host_wake, "bt_host_wake"); + if (ret) + goto free_bsi; + + /* configure host_wake as input */ + ret = gpio_direction_input(bsi->host_wake); + if (ret < 0) { + pr_err("%s: gpio_direction_input failed for GPIO %d, error %d\n", + __func__, bsi->host_wake, ret); + gpio_free(bsi->host_wake); + goto free_bsi; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_IO, + "gpio_ext_wake"); + if (!res) { + BT_ERR("couldn't find ext_wake gpio\n"); + ret = -ENODEV; + goto free_bt_host_wake; + } + bsi->ext_wake = res->start; + + ret = gpio_request(bsi->ext_wake, "bt_ext_wake"); + if (ret) + goto free_bt_host_wake; + + /* configure ext_wake as output mode*/ + ret = gpio_direction_output(bsi->ext_wake, 1); + if (ret < 0) { + pr_err("%s: gpio_direction_output failed for GPIO %d, error %d\n", + __func__, bsi->ext_wake, ret); + gpio_free(bsi->ext_wake); + goto free_bt_host_wake; + } + gpio_set_value(bsi->ext_wake, 0); + + bsi->host_wake_irq = platform_get_irq_byname(pdev, "host_wake"); + if (bsi->host_wake_irq < 0) { + BT_ERR("couldn't find host_wake irq\n"); + ret = -ENODEV; + goto free_bt_ext_wake; + } + + bsi->irq_polarity = POLARITY_LOW; /* low edge (falling edge) */ + + /* Initialize spinlock. */ + spin_lock_init(&rw_lock); + + /* Initialize timer */ + init_timer(&tx_timer); + tx_timer.function = bluesleep_tx_timer_expire; + tx_timer.data = 0; + + /* initialize host wake tasklet */ + tasklet_init(&hostwake_task, hostwake_interrupt, 0); + + if (bsi->irq_polarity == POLARITY_LOW) { + ret = request_irq(bsi->host_wake_irq, bluesleep_hostwake_isr, + IRQF_DISABLED | IRQF_TRIGGER_FALLING, + "bluetooth hostwake", NULL); + } else { + ret = request_irq(bsi->host_wake_irq, bluesleep_hostwake_isr, + IRQF_DISABLED | IRQF_TRIGGER_RISING, + "bluetooth hostwake", NULL); + } + if (ret < 0) { + BT_ERR("Couldn't acquire BT_HOST_WAKE IRQ"); + goto free_bt_timer; + } + + ret = enable_irq_wake(bsi->host_wake_irq); + if (ret < 0) { + BT_ERR("Couldn't enable BT_HOST_WAKE as wakeup interrupt"); + free_irq(bsi->host_wake_irq, NULL); + goto free_bt_timer; + } + + return 0; + +free_bt_timer: + del_timer(&tx_timer); +free_bt_ext_wake: + gpio_free(bsi->ext_wake); +free_bt_host_wake: + gpio_free(bsi->host_wake); +free_bsi: + kfree(bsi); +failed: + return ret; +} + +static int bluesleep_remove(struct platform_device *pdev) +{ + /* assert bt wake */ + gpio_set_value(bsi->ext_wake, 0); + if (disable_irq_wake(bsi->host_wake_irq)) + BT_ERR("Couldn't disable hostwake IRQ wakeup mode\n"); + free_irq(bsi->host_wake_irq, NULL); + del_timer_sync(&tx_timer); + gpio_free(bsi->host_wake); + gpio_free(bsi->ext_wake); + kfree(bsi); + return 0; +} + +static struct platform_driver bluesleep_driver = { + .remove = bluesleep_remove, + .driver = { + .name = "bluesleep", + .owner = THIS_MODULE, + }, +}; + int __init ath_init(void) { - int err = hci_uart_register_proto(&athp); + int ret; - if (!err) + ret = hci_uart_register_proto(&athp); + + if (!ret) BT_INFO("HCIATH3K protocol initialized"); - else + else { BT_ERR("HCIATH3K protocol registration failed"); - - return err; + return ret; + } + ret = platform_driver_probe(&bluesleep_driver, bluesleep_probe); + if (ret) + return ret; + return 0; } int __exit ath_deinit(void) { + platform_driver_unregister(&bluesleep_driver); return hci_uart_unregister_proto(&athp); }