Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 13499593 authored by Santosh Sajjan's avatar Santosh Sajjan Committed by Stephen Boyd
Browse files

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: default avatarSantosh Sajjan <ssajjan@codeaurora.org>
Signed-off-by: default avatarRam Mohan Korukonda <rkorukon@codeaurora.org>
parent 4d4bd006
Loading
Loading
Loading
Loading
+261 −42
Original line number Diff line number Diff line
@@ -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, The Linux Foundation. All rights reserved.
 *
 *  Acknowledgements:
 *  This file is based on hci_h4.c, which was written
@@ -35,12 +36,53 @@
#include <linux/errno.h>
#include <linux/ioctl.h>
#include <linux/skbuff.h>
#include <linux/platform_device.h>
#include <linux/gpio.h>

#include <net/bluetooth/bluetooth.h>
#include <net/bluetooth/hci_core.h>

#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 int ath_wakeup_ar3k(struct tty_struct *tty)
static void hostwake_interrupt(unsigned long data)
{
	struct ktermios ktermios;
	int status = tty->driver->ops->tiocmget(tty);

	if (status & TIOCM_CTS)
		return status;

	/* Disable Automatic RTSCTS */
	ktermios = tty->termios;
	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);
	printk(KERN_INFO " wakeup host\n");
}

	status = tty->driver->ops->tiocmget(tty);
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);

	/* Disable Automatic RTSCTS */
	ktermios.c_cflag |= CRTSCTS;
	status = tty_set_termios(tty, &ktermios);
}

static int ath_wakeup_ar3k(struct tty_struct *tty)
{
	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,10 +211,11 @@ 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;

	ret = hci_uart_register_proto(&athp);

	if (!err)
	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);
}