Index: drivers/bluetooth/hci_ath.c |
diff --git a/drivers/bluetooth/hci_ath.c b/drivers/bluetooth/hci_ath.c |
new file mode 100644 |
index 0000000000000000000000000000000000000000..f1a26823a4d526d824e8e5d09d3c6c77d53aa572 |
--- /dev/null |
+++ b/drivers/bluetooth/hci_ath.c |
@@ -0,0 +1,353 @@ |
+/* |
+ * Copyright (c) 2009-2010 Atheros Communications 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/kernel.h> |
+ |
+#include <linux/init.h> |
+#include <linux/slab.h> |
+#include <linux/tty.h> |
+#include <linux/errno.h> |
+#include <linux/ioctl.h> |
+#include <linux/skbuff.h> |
+ |
+#include <net/bluetooth/bluetooth.h> |
+#include <net/bluetooth/hci_core.h> |
+ |
+#include "hci_uart.h" |
+ |
+ |
+/* HCIATH receiver States */ |
+#define HCIATH_W4_PACKET_TYPE 0 |
+#define HCIATH_W4_EVENT_HDR 1 |
+#define HCIATH_W4_ACL_HDR 2 |
+#define HCIATH_W4_SCO_HDR 3 |
+#define HCIATH_W4_DATA 4 |
+ |
+struct ath_struct { |
+ struct hci_uart *hu; |
+ unsigned int rx_state; |
+ unsigned int rx_count; |
+ unsigned int cur_sleep; |
+ |
+ spinlock_t hciath_lock; |
+ struct sk_buff *rx_skb; |
+ struct sk_buff_head txq; |
+ wait_queue_head_t wqevt; |
+ struct work_struct ctxtsw; |
+}; |
+ |
+int ath_wakeup_ar3001(struct tty_struct *tty) |
+{ |
+ struct termios settings; |
+ int status = 0x00; |
+ mm_segment_t oldfs; |
+ status = tty->driver->ops->tiocmget(tty, NULL); |
+ |
+ if ((status & TIOCM_CTS)) |
+ return status; |
+ |
+ oldfs = get_fs(); |
+ set_fs(KERNEL_DS); |
+ n_tty_ioctl_helper(tty, NULL, TCGETS, (unsigned long)&settings); |
+ |
+ settings.c_cflag &= ~CRTSCTS; |
+ n_tty_ioctl_helper(tty, NULL, TCSETS, (unsigned long)&settings); |
+ set_fs(oldfs); |
+ status = tty->driver->ops->tiocmget(tty, NULL); |
+ |
+ /* Wake up board */ |
+ tty->driver->ops->tiocmset(tty, NULL, 0x00, TIOCM_RTS); |
+ mdelay(20); |
+ |
+ status = tty->driver->ops->tiocmget(tty, NULL); |
+ |
+ tty->driver->ops->tiocmset(tty, NULL, TIOCM_RTS, 0x00); |
+ mdelay(20); |
+ |
+ status = tty->driver->ops->tiocmget(tty, NULL); |
+ oldfs = get_fs(); |
+ set_fs(KERNEL_DS); |
+ n_tty_ioctl_helper(tty, NULL, TCGETS, (unsigned long)&settings); |
+ |
+ settings.c_cflag |= CRTSCTS; |
+ n_tty_ioctl_helper(tty, NULL, TCSETS, (unsigned long)&settings); |
+ set_fs(oldfs); |
+ return status; |
+} |
+ |
+static void ath_context_switch(struct work_struct *work) |
+{ |
+ int status; |
+ struct ath_struct *ath; |
+ struct hci_uart *hu; |
+ struct tty_struct *tty; |
+ |
+ ath = container_of(work, struct ath_struct, ctxtsw); |
+ |
+ hu = ath->hu; |
+ tty = hu->tty; |
+ |
+ /* verify and wake up controller */ |
+ if (ath->cur_sleep) { |
+ |
+ status = ath_wakeup_ar3001(tty); |
+ if (!(status & TIOCM_CTS)) |
+ return; |
+ } |
+ |
+ /* Ready to send Data */ |
+ clear_bit(HCI_UART_SENDING, &hu->tx_state); |
+ hci_uart_tx_wakeup(hu); |
+} |
+ |
+int ath_check_sleep_cmd(struct ath_struct *ath, unsigned char *packet) |
+{ |
+ if (packet[0] == 0x04 && packet[1] == 0xFC) |
+ ath->cur_sleep = packet[3]; |
+ |
+ return 0; |
+} |
+ |
+ |
+/* Initialize protocol */ |
+static int ath_open(struct hci_uart *hu) |
+{ |
+ struct ath_struct *ath; |
+ BT_DBG("hu %p", hu); |
+ |
+ ath = kzalloc(sizeof(*ath), GFP_ATOMIC); |
+ if (!ath) |
+ return -ENOMEM; |
+ |
+ skb_queue_head_init(&ath->txq); |
+ spin_lock_init(&ath->hciath_lock); |
+ |
+ ath->cur_sleep = 0; |
+ hu->priv = ath; |
+ ath->hu = hu; |
+ |
+ init_waitqueue_head(&ath->wqevt); |
+ INIT_WORK(&ath->ctxtsw, ath_context_switch); |
+ return 0; |
+} |
+ |
+/* Flush protocol data */ |
+static int ath_flush(struct hci_uart *hu) |
+{ |
+ struct ath_struct *ath = hu->priv; |
+ BT_DBG("hu %p", hu); |
+ skb_queue_purge(&ath->txq); |
+ |
+ return 0; |
+} |
+ |
+/* Close protocol */ |
+static int ath_close(struct hci_uart *hu) |
+{ |
+ struct ath_struct *ath = hu->priv; |
+ BT_DBG("hu %p", hu); |
+ |
+ skb_queue_purge(&ath->txq); |
+ |
+ if (ath->rx_skb) |
+ kfree_skb(ath->rx_skb); |
+ |
+ wake_up_interruptible(&ath->wqevt); |
+ hu->priv = NULL; |
+ kfree(ath); |
+ return 0; |
+} |
+ |
+/* Enqueue frame for transmittion */ |
+static int ath_enqueue(struct hci_uart *hu, struct sk_buff *skb) |
+{ |
+ struct ath_struct *ath = hu->priv; |
+ if (bt_cb(skb)->pkt_type == HCI_SCODATA_PKT) { |
+ |
+ /* Discard SCO packet.AR3001 does not support SCO over HCI */ |
+ BT_DBG("SCO Packet over HCI received Dropping\n"); |
+ kfree(skb); |
+ return 0; |
+ } |
+ BT_DBG("hu %p skb %p", hu, skb); |
+ |
+ /* Prepend skb with frame type */ |
+ memcpy(skb_push(skb, 1), &bt_cb(skb)->pkt_type, 1); |
+ |
+ skb_queue_tail(&ath->txq, skb); |
+ set_bit(HCI_UART_SENDING, &hu->tx_state); |
+ |
+ schedule_work(&ath->ctxtsw); |
+ return 0; |
+} |
+ |
+static struct sk_buff *ath_dequeue(struct hci_uart *hu) |
+{ |
+ struct ath_struct *ath = hu->priv; |
+ struct sk_buff *skbuf; |
+ |
+ skbuf = skb_dequeue(&ath->txq); |
+ if (skbuf != NULL) |
+ ath_check_sleep_cmd(ath, &skbuf->data[1]); |
+ |
+ return skbuf; |
+} |
+ |
+static inline int ath_check_data_len(struct ath_struct *ath, int len) |
+{ |
+ register int room = skb_tailroom(ath->rx_skb); |
+ BT_DBG("len %d room %d", len, room); |
+ |
+ if (len > room) { |
+ BT_ERR("Data length is too large"); |
+ kfree_skb(ath->rx_skb); |
+ ath->rx_state = HCIATH_W4_PACKET_TYPE; |
+ ath->rx_skb = NULL; |
+ ath->rx_count = 0; |
+ } else { |
+ ath->rx_state = HCIATH_W4_DATA; |
+ ath->rx_count = len; |
+ return len; |
+ } |
+ |
+ return 0; |
+} |
+ |
+/* Recv data */ |
+static int ath_recv(struct hci_uart *hu, void *data, int count) |
+{ |
+ struct ath_struct *ath = hu->priv; |
+ register char *ptr; |
+ struct hci_event_hdr *eh; |
+ struct hci_acl_hdr *ah; |
+ struct hci_sco_hdr *sh; |
+ struct sk_buff *skbuf; |
+ register int len, type, dlen; |
+ |
+ skbuf = NULL; |
+ BT_DBG("hu %p count %d rx_state %d rx_count %d", hu, count, |
+ ath->rx_state, ath->rx_count); |
+ ptr = data; |
+ while (count) { |
+ if (ath->rx_count) { |
+ |
+ len = min_t(unsigned int, ath->rx_count, count); |
+ memcpy(skb_put(ath->rx_skb, len), ptr, len); |
+ ath->rx_count -= len; |
+ count -= len; |
+ ptr += len; |
+ |
+ if (ath->rx_count) |
+ continue; |
+ switch (ath->rx_state) { |
+ case HCIATH_W4_DATA: |
+ hci_recv_frame(ath->rx_skb); |
+ ath->rx_state = HCIATH_W4_PACKET_TYPE; |
+ ath->rx_skb = NULL; |
+ ath->rx_count = 0; |
+ continue; |
+ case HCIATH_W4_EVENT_HDR: |
+ eh = (struct hci_event_hdr *)ath->rx_skb->data; |
+ BT_DBG("Event header: evt 0x%2.2x plen %d", |
+ eh->evt, eh->plen); |
+ ath_check_data_len(ath, eh->plen); |
+ continue; |
+ case HCIATH_W4_ACL_HDR: |
+ ah = (struct hci_acl_hdr *)ath->rx_skb->data; |
+ dlen = __le16_to_cpu(ah->dlen); |
+ BT_DBG("ACL header: dlen %d", dlen); |
+ ath_check_data_len(ath, dlen); |
+ continue; |
+ case HCIATH_W4_SCO_HDR: |
+ sh = (struct hci_sco_hdr *)ath->rx_skb->data; |
+ BT_DBG("SCO header: dlen %d", sh->dlen); |
+ ath_check_data_len(ath, sh->dlen); |
+ continue; |
+ } |
+ } |
+ |
+ /* HCIATH_W4_PACKET_TYPE */ |
+ switch (*ptr) { |
+ case HCI_EVENT_PKT: |
+ BT_DBG("Event packet"); |
+ ath->rx_state = HCIATH_W4_EVENT_HDR; |
+ ath->rx_count = HCI_EVENT_HDR_SIZE; |
+ type = HCI_EVENT_PKT; |
+ break; |
+ case HCI_ACLDATA_PKT: |
+ BT_DBG("ACL packet"); |
+ ath->rx_state = HCIATH_W4_ACL_HDR; |
+ ath->rx_count = HCI_ACL_HDR_SIZE; |
+ type = HCI_ACLDATA_PKT; |
+ break; |
+ case HCI_SCODATA_PKT: |
+ BT_DBG("SCO packet"); |
+ ath->rx_state = HCIATH_W4_SCO_HDR; |
+ ath->rx_count = HCI_SCO_HDR_SIZE; |
+ type = HCI_SCODATA_PKT; |
+ break; |
+ default: |
+ BT_ERR("Unknown HCI packet type %2.2x", (__u8) *ptr); |
+ hu->hdev->stat.err_rx++; |
+ ptr++; |
+ count--; |
+ continue; |
+ }; |
+ ptr++; |
+ count--; |
+ |
+ /* Allocate packet */ |
+ ath->rx_skb = bt_skb_alloc(HCI_MAX_FRAME_SIZE, GFP_ATOMIC); |
+ if (!ath->rx_skb) { |
+ BT_ERR("Can't allocate mem for new packet"); |
+ ath->rx_state = HCIATH_W4_PACKET_TYPE; |
+ ath->rx_count = 0; |
+ return -ENOMEM; |
+ } |
+ ath->rx_skb->dev = (void *)hu->hdev; |
+ bt_cb(ath->rx_skb)->pkt_type = type; |
+ } return count; |
+} |
+ |
+static struct hci_uart_proto athp = { |
+ .id = HCI_UART_ATH, |
+ .open = ath_open, |
+ .close = ath_close, |
+ .recv = ath_recv, |
+ .enqueue = ath_enqueue, |
+ .dequeue = ath_dequeue, |
+ .flush = ath_flush, |
+}; |
+ |
+int ath_init(void) |
+{ |
+ int err = hci_uart_register_proto(&athp); |
+ if (!err) |
+ BT_INFO("HCIATH protocol initialized"); |
+ |
+ else |
+ BT_ERR("HCIATH protocol registration failed with err %d", err); |
+ return err; |
+} |
+ |
+int ath_deinit(void) |
+{ |
+ return hci_uart_unregister_proto(&athp); |
+} |