android_kernel_cmhtcleo/drivers/serial/bcm_bt_lpm.c
2010-08-27 11:19:57 +02:00

177 lines
4.6 KiB
C

/*
* 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.
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/hrtimer.h>
#include <linux/irq.h>
#include <linux/serial_core.h>
#include <mach/bcm_bt_lpm.h>
#include <asm/gpio.h>
/*
* Manage WAKE and HOST_WAKE low power mode signals for Broadcom
* Bluetooth chipsets.
*
* This driver needs to be tightly coupled with a uart driver that supports
* request_clock_off_locked() and request_clock_on_locked(), to clock off and
* on the uart indepdently of Linux suspend/resume.
*
* The uart driver needs to call bcm_bt_lpm_exit_lpm_locked() every time it
* begins TX, to ensure this driver keeps WAKE asserted during TX.
*
* The callbacks and hijacking of the uart_port struct are not a clean API,
* but the Linux tty and serial core layers do not have a better alternative
* right now: there is no good way to plumb uart clock control through these
* layers. See http://lkml.org/lkml/2008/12/19/213 for more background.
*/
struct bcm_bt_lpm {
unsigned int gpio_wake;
unsigned int gpio_host_wake;
int wake;
int host_wake;
struct hrtimer enter_lpm_timer;
ktime_t enter_lpm_delay;
struct uart_port *uport;
void (*request_clock_off_locked)(struct uart_port *uport);
void (*request_clock_on_locked)(struct uart_port *uport);
} bt_lpm;
static void set_wake_locked(int wake)
{
if (wake == bt_lpm.wake)
return;
bt_lpm.wake = wake;
if (wake || bt_lpm.host_wake)
bt_lpm.request_clock_on_locked(bt_lpm.uport);
else
bt_lpm.request_clock_off_locked(bt_lpm.uport);
gpio_set_value(bt_lpm.gpio_wake, 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);
static void update_host_wake_locked(int host_wake)
{
if (host_wake == bt_lpm.host_wake)
return;
bt_lpm.host_wake = host_wake;
if (bt_lpm.wake || host_wake)
bt_lpm.request_clock_on_locked(bt_lpm.uport);
else
bt_lpm.request_clock_off_locked(bt_lpm.uport);
}
static irqreturn_t host_wake_isr(int irq, void *dev)
{
int host_wake;
unsigned long flags;
host_wake = gpio_get_value(bt_lpm.gpio_host_wake);
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_probe(struct platform_device *pdev)
{
int irq;
int ret;
struct bcm_bt_lpm_platform_data *pdata = pdev->dev.platform_data;
if (bt_lpm.request_clock_off_locked != NULL) {
printk(KERN_ERR "Cannot register two bcm_bt_lpm drivers\n");
return -EINVAL;
}
bt_lpm.gpio_wake = pdata->gpio_wake;
bt_lpm.gpio_host_wake = pdata->gpio_host_wake;
bt_lpm.request_clock_off_locked = pdata->request_clock_off_locked;
bt_lpm.request_clock_on_locked = pdata->request_clock_on_locked;
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;
gpio_set_value(bt_lpm.gpio_wake, 0);
bt_lpm.host_wake = 0;
irq = gpio_to_irq(bt_lpm.gpio_host_wake);
ret = request_irq(irq, host_wake_isr, IRQF_TRIGGER_HIGH,
"bt host_wake", NULL);
if (ret)
return ret;
ret = set_irq_wake(irq, 1);
if (ret)
return ret;
return 0;
}
static struct platform_driver bcm_bt_lpm_driver = {
.probe = bcm_bt_lpm_probe,
.driver = {
.name = "bcm_bt_lpm",
.owner = THIS_MODULE,
},
};
static int __init bcm_bt_lpm_init(void)
{
return platform_driver_register(&bcm_bt_lpm_driver);
}
module_init(bcm_bt_lpm_init);
MODULE_DESCRIPTION("Broadcom Bluetooth low power mode driver");
MODULE_AUTHOR("Nick Pelly <npelly@google.com>");
MODULE_LICENSE("GPL");