diff options
author | root <root@artemis.panaceas.org> | 2015-12-25 04:40:36 +0000 |
---|---|---|
committer | root <root@artemis.panaceas.org> | 2015-12-25 04:40:36 +0000 |
commit | 849369d6c66d3054688672f97d31fceb8e8230fb (patch) | |
tree | 6135abc790ca67dedbe07c39806591e70eda81ce /drivers/bluetooth | |
download | linux-3.0.35-kobo-849369d6c66d3054688672f97d31fceb8e8230fb.tar.gz linux-3.0.35-kobo-849369d6c66d3054688672f97d31fceb8e8230fb.tar.bz2 linux-3.0.35-kobo-849369d6c66d3054688672f97d31fceb8e8230fb.zip |
initial_commit
Diffstat (limited to 'drivers/bluetooth')
25 files changed, 12765 insertions, 0 deletions
diff --git a/drivers/bluetooth/Kconfig b/drivers/bluetooth/Kconfig new file mode 100644 index 00000000..11b41fd4 --- /dev/null +++ b/drivers/bluetooth/Kconfig @@ -0,0 +1,232 @@ + +menu "Bluetooth device drivers" + depends on BT + +config BT_HCIBTUSB + tristate "HCI USB driver" + depends on USB + help + Bluetooth HCI USB driver. + This driver is required if you want to use Bluetooth devices with + USB interface. + + Say Y here to compile support for Bluetooth USB devices into the + kernel or say M to compile it as module (btusb). + +config BT_HCIBTSDIO + tristate "HCI SDIO driver" + depends on MMC + help + Bluetooth HCI SDIO driver. + This driver is required if you want to use Bluetooth device with + SDIO interface. + + Say Y here to compile support for Bluetooth SDIO devices into the + kernel or say M to compile it as module (btsdio). + +config BT_HCIUART + tristate "HCI UART driver" + help + Bluetooth HCI UART driver. + This driver is required if you want to use Bluetooth devices with + serial port interface. You will also need this driver if you have + UART based Bluetooth PCMCIA and CF devices like Xircom Credit Card + adapter and BrainBoxes Bluetooth PC Card. + + Say Y here to compile support for Bluetooth UART devices into the + kernel or say M to compile it as module (hci_uart). + +config BT_HCIUART_H4 + bool "UART (H4) protocol support" + depends on BT_HCIUART + help + UART (H4) is serial protocol for communication between Bluetooth + device and host. This protocol is required for most Bluetooth devices + with UART interface, including PCMCIA and CF cards. + + Say Y here to compile support for HCI UART (H4) protocol. + +config BT_HCIUART_BCSP + bool "BCSP protocol support" + depends on BT_HCIUART + select BITREVERSE + help + BCSP (BlueCore Serial Protocol) is serial protocol for communication + between Bluetooth device and host. This protocol is required for non + USB Bluetooth devices based on CSR BlueCore chip, including PCMCIA and + CF cards. + + Say Y here to compile support for HCI BCSP protocol. + +config BT_HCIUART_ATH3K + bool "Atheros AR300x serial support" + depends on BT_HCIUART + help + HCIATH3K (HCI Atheros AR300x) is a serial protocol for + communication between host and Atheros AR300x Bluetooth devices. + This protocol enables AR300x chips to be enabled with + power management support. + Enable this if you have Atheros AR300x serial Bluetooth device. + + Say Y here to compile support for HCI UART ATH3K protocol. + +config BT_HCIUART_LL + bool "HCILL protocol support" + depends on BT_HCIUART + help + HCILL (HCI Low Level) is a serial protocol for communication + between Bluetooth device and host. This protocol is required for + serial Bluetooth devices that are based on Texas Instruments' + BRF chips. + + Say Y here to compile support for HCILL protocol. + +config BT_HCIBCM203X + tristate "HCI BCM203x USB driver" + depends on USB + select FW_LOADER + help + Bluetooth HCI BCM203x USB driver. + This driver provides the firmware loading mechanism for the Broadcom + Blutonium based devices. + + Say Y here to compile support for HCI BCM203x devices into the + kernel or say M to compile it as module (bcm203x). + +config BT_HCIBPA10X + tristate "HCI BPA10x USB driver" + depends on USB + help + Bluetooth HCI BPA10x USB driver. + This driver provides support for the Digianswer BPA 100/105 Bluetooth + sniffer devices. + + Say Y here to compile support for HCI BPA10x devices into the + kernel or say M to compile it as module (bpa10x). + +config BT_HCIBFUSB + tristate "HCI BlueFRITZ! USB driver" + depends on USB + select FW_LOADER + help + Bluetooth HCI BlueFRITZ! USB driver. + This driver provides support for Bluetooth USB devices with AVM + interface: + AVM BlueFRITZ! USB + + Say Y here to compile support for HCI BFUSB devices into the + kernel or say M to compile it as module (bfusb). + +config BT_HCIDTL1 + tristate "HCI DTL1 (PC Card) driver" + depends on PCMCIA + help + Bluetooth HCI DTL1 (PC Card) driver. + This driver provides support for Bluetooth PCMCIA devices with + Nokia DTL1 interface: + Nokia Bluetooth Card + Socket Bluetooth CF Card + + Say Y here to compile support for HCI DTL1 devices into the + kernel or say M to compile it as module (dtl1_cs). + +config BT_HCIBT3C + tristate "HCI BT3C (PC Card) driver" + depends on PCMCIA + select FW_LOADER + help + Bluetooth HCI BT3C (PC Card) driver. + This driver provides support for Bluetooth PCMCIA devices with + 3Com BT3C interface: + 3Com Bluetooth Card (3CRWB6096) + HP Bluetooth Card + + Say Y here to compile support for HCI BT3C devices into the + kernel or say M to compile it as module (bt3c_cs). + +config BT_HCIBLUECARD + tristate "HCI BlueCard (PC Card) driver" + depends on PCMCIA + help + Bluetooth HCI BlueCard (PC Card) driver. + This driver provides support for Bluetooth PCMCIA devices with + Anycom BlueCard interface: + Anycom Bluetooth PC Card + Anycom Bluetooth CF Card + + Say Y here to compile support for HCI BlueCard devices into the + kernel or say M to compile it as module (bluecard_cs). + +config BT_HCIBTUART + tristate "HCI UART (PC Card) device driver" + depends on PCMCIA + help + Bluetooth HCI UART (PC Card) driver. + This driver provides support for Bluetooth PCMCIA devices with + an UART interface: + Xircom CreditCard Bluetooth Adapter + Xircom RealPort2 Bluetooth Adapter + Sphinx PICO Card + H-Soft blue+Card + Cyber-blue Compact Flash Card + + Say Y here to compile support for HCI UART devices into the + kernel or say M to compile it as module (btuart_cs). + +config BT_HCIVHCI + tristate "HCI VHCI (Virtual HCI device) driver" + help + Bluetooth Virtual HCI device driver. + This driver is required if you want to use HCI Emulation software. + + Say Y here to compile support for virtual HCI devices into the + kernel or say M to compile it as module (hci_vhci). + +config BT_MRVL + tristate "Marvell Bluetooth driver support" + help + The core driver to support Marvell Bluetooth devices. + + This driver is required if you want to support + Marvell Bluetooth devices, such as 8688/8787. + + Say Y here to compile Marvell Bluetooth driver + into the kernel or say M to compile it as module. + +config BT_MRVL_SDIO + tristate "Marvell BT-over-SDIO driver" + depends on BT_MRVL && MMC + select FW_LOADER + help + The driver for Marvell Bluetooth chipsets with SDIO interface. + + This driver is required if you want to use Marvell Bluetooth + devices with SDIO interface. Currently SD8688/SD8787 chipsets are + supported. + + Say Y here to compile support for Marvell BT-over-SDIO driver + into the kernel or say M to compile it as module. + +config BT_ATH3K + tristate "Atheros firmware download driver" + depends on BT_HCIBTUSB + select FW_LOADER + help + Bluetooth firmware download driver. + This driver loads the firmware into the Atheros Bluetooth + chipset. + + Say Y here to compile support for "Atheros firmware download driver" + into the kernel or say M to compile it as module (ath3k). + +config BT_WILINK + tristate "Texas Instruments WiLink7 driver" + depends on TI_ST + help + This enables the Bluetooth driver for Texas Instrument's BT/FM/GPS + combo devices. This makes use of shared transport line discipline + core driver to communicate with the BT core of the combo chip. + + Say Y here to compile support for Texas Instrument's WiLink7 driver + into the kernel or say M to compile it as module. +endmenu diff --git a/drivers/bluetooth/Makefile b/drivers/bluetooth/Makefile new file mode 100644 index 00000000..f4460f4f --- /dev/null +++ b/drivers/bluetooth/Makefile @@ -0,0 +1,31 @@ +# +# Makefile for the Linux Bluetooth HCI device drivers. +# + +obj-$(CONFIG_BT_HCIVHCI) += hci_vhci.o +obj-$(CONFIG_BT_HCIUART) += hci_uart.o +obj-$(CONFIG_BT_HCIBCM203X) += bcm203x.o +obj-$(CONFIG_BT_HCIBPA10X) += bpa10x.o +obj-$(CONFIG_BT_HCIBFUSB) += bfusb.o +obj-$(CONFIG_BT_HCIDTL1) += dtl1_cs.o +obj-$(CONFIG_BT_HCIBT3C) += bt3c_cs.o +obj-$(CONFIG_BT_HCIBLUECARD) += bluecard_cs.o +obj-$(CONFIG_BT_HCIBTUART) += btuart_cs.o + +obj-$(CONFIG_BT_HCIBTUSB) += btusb.o +obj-$(CONFIG_BT_HCIBTSDIO) += btsdio.o + +obj-$(CONFIG_BT_ATH3K) += ath3k.o +obj-$(CONFIG_BT_MRVL) += btmrvl.o +obj-$(CONFIG_BT_MRVL_SDIO) += btmrvl_sdio.o +obj-$(CONFIG_BT_WILINK) += btwilink.o + +btmrvl-y := btmrvl_main.o +btmrvl-$(CONFIG_DEBUG_FS) += btmrvl_debugfs.o + +hci_uart-y := hci_ldisc.o +hci_uart-$(CONFIG_BT_HCIUART_H4) += hci_h4.o +hci_uart-$(CONFIG_BT_HCIUART_BCSP) += hci_bcsp.o +hci_uart-$(CONFIG_BT_HCIUART_LL) += hci_ll.o +hci_uart-$(CONFIG_BT_HCIUART_ATH3K) += hci_ath.o +hci_uart-objs := $(hci_uart-y) diff --git a/drivers/bluetooth/ath3k.c b/drivers/bluetooth/ath3k.c new file mode 100644 index 00000000..305e7526 --- /dev/null +++ b/drivers/bluetooth/ath3k.c @@ -0,0 +1,457 @@ +/* + * Copyright (c) 2008-2009 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/types.h> +#include <linux/errno.h> +#include <linux/device.h> +#include <linux/firmware.h> +#include <linux/usb.h> +#include <net/bluetooth/bluetooth.h> + +#define VERSION "1.0" + +#define ATH3K_DNLOAD 0x01 +#define ATH3K_GETSTATE 0x05 +#define ATH3K_SET_NORMAL_MODE 0x07 +#define ATH3K_GETVERSION 0x09 +#define USB_REG_SWITCH_VID_PID 0x0a + +#define ATH3K_MODE_MASK 0x3F +#define ATH3K_NORMAL_MODE 0x0E + +#define ATH3K_PATCH_UPDATE 0x80 +#define ATH3K_SYSCFG_UPDATE 0x40 + +#define ATH3K_XTAL_FREQ_26M 0x00 +#define ATH3K_XTAL_FREQ_40M 0x01 +#define ATH3K_XTAL_FREQ_19P2 0x02 +#define ATH3K_NAME_LEN 0xFF + +struct ath3k_version { + unsigned int rom_version; + unsigned int build_version; + unsigned int ram_version; + unsigned char ref_clock; + unsigned char reserved[0x07]; +}; + +static struct usb_device_id ath3k_table[] = { + /* Atheros AR3011 */ + { USB_DEVICE(0x0CF3, 0x3000) }, + + /* Atheros AR3011 with sflash firmware*/ + { USB_DEVICE(0x0CF3, 0x3002) }, + { USB_DEVICE(0x13d3, 0x3304) }, + { USB_DEVICE(0x0930, 0x0215) }, + { USB_DEVICE(0x0489, 0xE03D) }, + + /* Atheros AR9285 Malbec with sflash firmware */ + { USB_DEVICE(0x03F0, 0x311D) }, + + /* Atheros AR3012 with sflash firmware*/ + { USB_DEVICE(0x0CF3, 0x3004) }, + { USB_DEVICE(0x0CF3, 0x311D) }, + { USB_DEVICE(0x13d3, 0x3375) }, + { USB_DEVICE(0x04CA, 0x3005) }, + + /* Atheros AR5BBU12 with sflash firmware */ + { USB_DEVICE(0x0489, 0xE02C) }, + + /* Atheros AR5BBU22 with sflash firmware */ + { USB_DEVICE(0x0489, 0xE03C) }, + + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, ath3k_table); + +#define BTUSB_ATH3012 0x80 +/* This table is to load patch and sysconfig files + * for AR3012 */ +static struct usb_device_id ath3k_blist_tbl[] = { + + /* Atheros AR3012 with sflash firmware*/ + { USB_DEVICE(0x0cf3, 0x3004), .driver_info = BTUSB_ATH3012 }, + { USB_DEVICE(0x0cf3, 0x311D), .driver_info = BTUSB_ATH3012 }, + { USB_DEVICE(0x13d3, 0x3375), .driver_info = BTUSB_ATH3012 }, + { USB_DEVICE(0x04ca, 0x3005), .driver_info = BTUSB_ATH3012 }, + + /* Atheros AR5BBU22 with sflash firmware */ + { USB_DEVICE(0x0489, 0xE03C), .driver_info = BTUSB_ATH3012 }, + + { } /* Terminating entry */ +}; + +#define USB_REQ_DFU_DNLOAD 1 +#define BULK_SIZE 4096 +#define FW_HDR_SIZE 20 + +static int ath3k_load_firmware(struct usb_device *udev, + const struct firmware *firmware) +{ + u8 *send_buf; + int err, pipe, len, size, sent = 0; + int count = firmware->size; + + BT_DBG("udev %p", udev); + + pipe = usb_sndctrlpipe(udev, 0); + + send_buf = kmalloc(BULK_SIZE, GFP_ATOMIC); + if (!send_buf) { + BT_ERR("Can't allocate memory chunk for firmware"); + return -ENOMEM; + } + + memcpy(send_buf, firmware->data, 20); + if ((err = usb_control_msg(udev, pipe, + USB_REQ_DFU_DNLOAD, + USB_TYPE_VENDOR, 0, 0, + send_buf, 20, USB_CTRL_SET_TIMEOUT)) < 0) { + BT_ERR("Can't change to loading configuration err"); + goto error; + } + sent += 20; + count -= 20; + + while (count) { + size = min_t(uint, count, BULK_SIZE); + pipe = usb_sndbulkpipe(udev, 0x02); + memcpy(send_buf, firmware->data + sent, size); + + err = usb_bulk_msg(udev, pipe, send_buf, size, + &len, 3000); + + if (err || (len != size)) { + BT_ERR("Error in firmware loading err = %d," + "len = %d, size = %d", err, len, size); + goto error; + } + + sent += size; + count -= size; + } + +error: + kfree(send_buf); + return err; +} + +static int ath3k_get_state(struct usb_device *udev, unsigned char *state) +{ + int pipe = 0; + + pipe = usb_rcvctrlpipe(udev, 0); + return usb_control_msg(udev, pipe, ATH3K_GETSTATE, + USB_TYPE_VENDOR | USB_DIR_IN, 0, 0, + state, 0x01, USB_CTRL_SET_TIMEOUT); +} + +static int ath3k_get_version(struct usb_device *udev, + struct ath3k_version *version) +{ + int pipe = 0; + + pipe = usb_rcvctrlpipe(udev, 0); + return usb_control_msg(udev, pipe, ATH3K_GETVERSION, + USB_TYPE_VENDOR | USB_DIR_IN, 0, 0, version, + sizeof(struct ath3k_version), + USB_CTRL_SET_TIMEOUT); +} + +static int ath3k_load_fwfile(struct usb_device *udev, + const struct firmware *firmware) +{ + u8 *send_buf; + int err, pipe, len, size, count, sent = 0; + int ret; + + count = firmware->size; + + send_buf = kmalloc(BULK_SIZE, GFP_ATOMIC); + if (!send_buf) { + BT_ERR("Can't allocate memory chunk for firmware"); + return -ENOMEM; + } + + size = min_t(uint, count, FW_HDR_SIZE); + memcpy(send_buf, firmware->data, size); + + pipe = usb_sndctrlpipe(udev, 0); + ret = usb_control_msg(udev, pipe, ATH3K_DNLOAD, + USB_TYPE_VENDOR, 0, 0, send_buf, + size, USB_CTRL_SET_TIMEOUT); + if (ret < 0) { + BT_ERR("Can't change to loading configuration err"); + kfree(send_buf); + return ret; + } + + sent += size; + count -= size; + + while (count) { + size = min_t(uint, count, BULK_SIZE); + pipe = usb_sndbulkpipe(udev, 0x02); + + memcpy(send_buf, firmware->data + sent, size); + + err = usb_bulk_msg(udev, pipe, send_buf, size, + &len, 3000); + if (err || (len != size)) { + BT_ERR("Error in firmware loading err = %d," + "len = %d, size = %d", err, len, size); + kfree(send_buf); + return err; + } + sent += size; + count -= size; + } + + kfree(send_buf); + return 0; +} + +static int ath3k_switch_pid(struct usb_device *udev) +{ + int pipe = 0; + + pipe = usb_sndctrlpipe(udev, 0); + return usb_control_msg(udev, pipe, USB_REG_SWITCH_VID_PID, + USB_TYPE_VENDOR, 0, 0, + NULL, 0, USB_CTRL_SET_TIMEOUT); +} + +static int ath3k_set_normal_mode(struct usb_device *udev) +{ + unsigned char fw_state; + int pipe = 0, ret; + + ret = ath3k_get_state(udev, &fw_state); + if (ret < 0) { + BT_ERR("Can't get state to change to normal mode err"); + return ret; + } + + if ((fw_state & ATH3K_MODE_MASK) == ATH3K_NORMAL_MODE) { + BT_DBG("firmware was already in normal mode"); + return 0; + } + + pipe = usb_sndctrlpipe(udev, 0); + return usb_control_msg(udev, pipe, ATH3K_SET_NORMAL_MODE, + USB_TYPE_VENDOR, 0, 0, + NULL, 0, USB_CTRL_SET_TIMEOUT); +} + +static int ath3k_load_patch(struct usb_device *udev) +{ + unsigned char fw_state; + char filename[ATH3K_NAME_LEN] = {0}; + const struct firmware *firmware; + struct ath3k_version fw_version, pt_version; + int ret; + + ret = ath3k_get_state(udev, &fw_state); + if (ret < 0) { + BT_ERR("Can't get state to change to load ram patch err"); + return ret; + } + + if (fw_state & ATH3K_PATCH_UPDATE) { + BT_DBG("Patch was already downloaded"); + return 0; + } + + ret = ath3k_get_version(udev, &fw_version); + if (ret < 0) { + BT_ERR("Can't get version to change to load ram patch err"); + return ret; + } + + snprintf(filename, ATH3K_NAME_LEN, "ar3k/AthrBT_0x%08x.dfu", + fw_version.rom_version); + + ret = request_firmware(&firmware, filename, &udev->dev); + if (ret < 0) { + BT_ERR("Patch file not found %s", filename); + return ret; + } + + pt_version.rom_version = *(int *)(firmware->data + firmware->size - 8); + pt_version.build_version = *(int *) + (firmware->data + firmware->size - 4); + + if ((pt_version.rom_version != fw_version.rom_version) || + (pt_version.build_version <= fw_version.build_version)) { + BT_ERR("Patch file version did not match with firmware"); + release_firmware(firmware); + return -EINVAL; + } + + ret = ath3k_load_fwfile(udev, firmware); + release_firmware(firmware); + + return ret; +} + +static int ath3k_load_syscfg(struct usb_device *udev) +{ + unsigned char fw_state; + char filename[ATH3K_NAME_LEN] = {0}; + const struct firmware *firmware; + struct ath3k_version fw_version; + int clk_value, ret; + + ret = ath3k_get_state(udev, &fw_state); + if (ret < 0) { + BT_ERR("Can't get state to change to load configration err"); + return -EBUSY; + } + + ret = ath3k_get_version(udev, &fw_version); + if (ret < 0) { + BT_ERR("Can't get version to change to load ram patch err"); + return ret; + } + + switch (fw_version.ref_clock) { + + case ATH3K_XTAL_FREQ_26M: + clk_value = 26; + break; + case ATH3K_XTAL_FREQ_40M: + clk_value = 40; + break; + case ATH3K_XTAL_FREQ_19P2: + clk_value = 19; + break; + default: + clk_value = 0; + break; + } + + snprintf(filename, ATH3K_NAME_LEN, "ar3k/ramps_0x%08x_%d%s", + fw_version.rom_version, clk_value, ".dfu"); + + ret = request_firmware(&firmware, filename, &udev->dev); + if (ret < 0) { + BT_ERR("Configuration file not found %s", filename); + return ret; + } + + ret = ath3k_load_fwfile(udev, firmware); + release_firmware(firmware); + + return ret; +} + +static int ath3k_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + const struct firmware *firmware; + struct usb_device *udev = interface_to_usbdev(intf); + int ret; + + BT_DBG("intf %p id %p", intf, id); + + if (intf->cur_altsetting->desc.bInterfaceNumber != 0) + return -ENODEV; + + /* match device ID in ath3k blacklist table */ + if (!id->driver_info) { + const struct usb_device_id *match; + match = usb_match_id(intf, ath3k_blist_tbl); + if (match) + id = match; + } + + /* load patch and sysconfig files for AR3012 */ + if (id->driver_info & BTUSB_ATH3012) { + + /* New firmware with patch and sysconfig files already loaded */ + if (le16_to_cpu(udev->descriptor.bcdDevice) > 0x0001) + return -ENODEV; + + ret = ath3k_load_patch(udev); + if (ret < 0) { + BT_ERR("Loading patch file failed"); + return ret; + } + ret = ath3k_load_syscfg(udev); + if (ret < 0) { + BT_ERR("Loading sysconfig file failed"); + return ret; + } + ret = ath3k_set_normal_mode(udev); + if (ret < 0) { + BT_ERR("Set normal mode failed"); + return ret; + } + ath3k_switch_pid(udev); + return 0; + } + + if (request_firmware(&firmware, "ath3k-1.fw", &udev->dev) < 0) { + BT_ERR("Error loading firmware"); + return -EIO; + } + + ret = ath3k_load_firmware(udev, firmware); + release_firmware(firmware); + + return ret; +} + +static void ath3k_disconnect(struct usb_interface *intf) +{ + BT_DBG("ath3k_disconnect intf %p", intf); +} + +static struct usb_driver ath3k_driver = { + .name = "ath3k", + .probe = ath3k_probe, + .disconnect = ath3k_disconnect, + .id_table = ath3k_table, +}; + +static int __init ath3k_init(void) +{ + BT_INFO("Atheros AR30xx firmware driver ver %s", VERSION); + return usb_register(&ath3k_driver); +} + +static void __exit ath3k_exit(void) +{ + usb_deregister(&ath3k_driver); +} + +module_init(ath3k_init); +module_exit(ath3k_exit); + +MODULE_AUTHOR("Atheros Communications"); +MODULE_DESCRIPTION("Atheros AR30xx firmware driver"); +MODULE_VERSION(VERSION); +MODULE_LICENSE("GPL"); +MODULE_FIRMWARE("ath3k-1.fw"); diff --git a/drivers/bluetooth/bcm203x.c b/drivers/bluetooth/bcm203x.c new file mode 100644 index 00000000..8b1b643a --- /dev/null +++ b/drivers/bluetooth/bcm203x.c @@ -0,0 +1,300 @@ +/* + * + * Broadcom Blutonium firmware driver + * + * Copyright (C) 2003 Maxim Krasnyansky <maxk@qualcomm.com> + * Copyright (C) 2003 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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/types.h> +#include <linux/errno.h> + +#include <linux/device.h> +#include <linux/firmware.h> + +#include <linux/usb.h> + +#include <net/bluetooth/bluetooth.h> + +#define VERSION "1.2" + +static const struct usb_device_id bcm203x_table[] = { + /* Broadcom Blutonium (BCM2033) */ + { USB_DEVICE(0x0a5c, 0x2033) }, + + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, bcm203x_table); + +#define BCM203X_ERROR 0 +#define BCM203X_RESET 1 +#define BCM203X_LOAD_MINIDRV 2 +#define BCM203X_SELECT_MEMORY 3 +#define BCM203X_CHECK_MEMORY 4 +#define BCM203X_LOAD_FIRMWARE 5 +#define BCM203X_CHECK_FIRMWARE 6 + +#define BCM203X_IN_EP 0x81 +#define BCM203X_OUT_EP 0x02 + +struct bcm203x_data { + struct usb_device *udev; + + unsigned long state; + + struct work_struct work; + + struct urb *urb; + unsigned char *buffer; + + unsigned char *fw_data; + unsigned int fw_size; + unsigned int fw_sent; +}; + +static void bcm203x_complete(struct urb *urb) +{ + struct bcm203x_data *data = urb->context; + struct usb_device *udev = urb->dev; + int len; + + BT_DBG("udev %p urb %p", udev, urb); + + if (urb->status) { + BT_ERR("URB failed with status %d", urb->status); + data->state = BCM203X_ERROR; + return; + } + + switch (data->state) { + case BCM203X_LOAD_MINIDRV: + memcpy(data->buffer, "#", 1); + + usb_fill_bulk_urb(urb, udev, usb_sndbulkpipe(udev, BCM203X_OUT_EP), + data->buffer, 1, bcm203x_complete, data); + + data->state = BCM203X_SELECT_MEMORY; + + schedule_work(&data->work); + break; + + case BCM203X_SELECT_MEMORY: + usb_fill_int_urb(urb, udev, usb_rcvintpipe(udev, BCM203X_IN_EP), + data->buffer, 32, bcm203x_complete, data, 1); + + data->state = BCM203X_CHECK_MEMORY; + + if (usb_submit_urb(data->urb, GFP_ATOMIC) < 0) + BT_ERR("Can't submit URB"); + break; + + case BCM203X_CHECK_MEMORY: + if (data->buffer[0] != '#') { + BT_ERR("Memory select failed"); + data->state = BCM203X_ERROR; + break; + } + + data->state = BCM203X_LOAD_FIRMWARE; + + case BCM203X_LOAD_FIRMWARE: + if (data->fw_sent == data->fw_size) { + usb_fill_int_urb(urb, udev, usb_rcvintpipe(udev, BCM203X_IN_EP), + data->buffer, 32, bcm203x_complete, data, 1); + + data->state = BCM203X_CHECK_FIRMWARE; + } else { + len = min_t(uint, data->fw_size - data->fw_sent, 4096); + + usb_fill_bulk_urb(urb, udev, usb_sndbulkpipe(udev, BCM203X_OUT_EP), + data->fw_data + data->fw_sent, len, bcm203x_complete, data); + + data->fw_sent += len; + } + + if (usb_submit_urb(data->urb, GFP_ATOMIC) < 0) + BT_ERR("Can't submit URB"); + break; + + case BCM203X_CHECK_FIRMWARE: + if (data->buffer[0] != '.') { + BT_ERR("Firmware loading failed"); + data->state = BCM203X_ERROR; + break; + } + + data->state = BCM203X_RESET; + break; + } +} + +static void bcm203x_work(struct work_struct *work) +{ + struct bcm203x_data *data = + container_of(work, struct bcm203x_data, work); + + if (usb_submit_urb(data->urb, GFP_ATOMIC) < 0) + BT_ERR("Can't submit URB"); +} + +static int bcm203x_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + const struct firmware *firmware; + struct usb_device *udev = interface_to_usbdev(intf); + struct bcm203x_data *data; + int size; + + BT_DBG("intf %p id %p", intf, id); + + if (intf->cur_altsetting->desc.bInterfaceNumber != 0) + return -ENODEV; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) { + BT_ERR("Can't allocate memory for data structure"); + return -ENOMEM; + } + + data->udev = udev; + data->state = BCM203X_LOAD_MINIDRV; + + data->urb = usb_alloc_urb(0, GFP_KERNEL); + if (!data->urb) { + BT_ERR("Can't allocate URB"); + kfree(data); + return -ENOMEM; + } + + if (request_firmware(&firmware, "BCM2033-MD.hex", &udev->dev) < 0) { + BT_ERR("Mini driver request failed"); + usb_free_urb(data->urb); + kfree(data); + return -EIO; + } + + BT_DBG("minidrv data %p size %zu", firmware->data, firmware->size); + + size = max_t(uint, firmware->size, 4096); + + data->buffer = kmalloc(size, GFP_KERNEL); + if (!data->buffer) { + BT_ERR("Can't allocate memory for mini driver"); + release_firmware(firmware); + usb_free_urb(data->urb); + kfree(data); + return -ENOMEM; + } + + memcpy(data->buffer, firmware->data, firmware->size); + + usb_fill_bulk_urb(data->urb, udev, usb_sndbulkpipe(udev, BCM203X_OUT_EP), + data->buffer, firmware->size, bcm203x_complete, data); + + release_firmware(firmware); + + if (request_firmware(&firmware, "BCM2033-FW.bin", &udev->dev) < 0) { + BT_ERR("Firmware request failed"); + usb_free_urb(data->urb); + kfree(data->buffer); + kfree(data); + return -EIO; + } + + BT_DBG("firmware data %p size %zu", firmware->data, firmware->size); + + data->fw_data = kmemdup(firmware->data, firmware->size, GFP_KERNEL); + if (!data->fw_data) { + BT_ERR("Can't allocate memory for firmware image"); + release_firmware(firmware); + usb_free_urb(data->urb); + kfree(data->buffer); + kfree(data); + return -ENOMEM; + } + + data->fw_size = firmware->size; + data->fw_sent = 0; + + release_firmware(firmware); + + INIT_WORK(&data->work, bcm203x_work); + + usb_set_intfdata(intf, data); + + schedule_work(&data->work); + + return 0; +} + +static void bcm203x_disconnect(struct usb_interface *intf) +{ + struct bcm203x_data *data = usb_get_intfdata(intf); + + BT_DBG("intf %p", intf); + + usb_kill_urb(data->urb); + + usb_set_intfdata(intf, NULL); + + usb_free_urb(data->urb); + kfree(data->fw_data); + kfree(data->buffer); + kfree(data); +} + +static struct usb_driver bcm203x_driver = { + .name = "bcm203x", + .probe = bcm203x_probe, + .disconnect = bcm203x_disconnect, + .id_table = bcm203x_table, +}; + +static int __init bcm203x_init(void) +{ + int err; + + BT_INFO("Broadcom Blutonium firmware driver ver %s", VERSION); + + err = usb_register(&bcm203x_driver); + if (err < 0) + BT_ERR("Failed to register USB driver"); + + return err; +} + +static void __exit bcm203x_exit(void) +{ + usb_deregister(&bcm203x_driver); +} + +module_init(bcm203x_init); +module_exit(bcm203x_exit); + +MODULE_AUTHOR("Marcel Holtmann <marcel@holtmann.org>"); +MODULE_DESCRIPTION("Broadcom Blutonium firmware driver ver " VERSION); +MODULE_VERSION(VERSION); +MODULE_LICENSE("GPL"); +MODULE_FIRMWARE("BCM2033-MD.hex"); +MODULE_FIRMWARE("BCM2033-FW.bin"); diff --git a/drivers/bluetooth/bfusb.c b/drivers/bluetooth/bfusb.c new file mode 100644 index 00000000..005919ab --- /dev/null +++ b/drivers/bluetooth/bfusb.c @@ -0,0 +1,791 @@ +/* + * + * AVM BlueFRITZ! USB driver + * + * Copyright (C) 2003-2006 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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/types.h> +#include <linux/errno.h> +#include <linux/skbuff.h> + +#include <linux/device.h> +#include <linux/firmware.h> + +#include <linux/usb.h> + +#include <net/bluetooth/bluetooth.h> +#include <net/bluetooth/hci_core.h> + +#define VERSION "1.2" + +static struct usb_driver bfusb_driver; + +static struct usb_device_id bfusb_table[] = { + /* AVM BlueFRITZ! USB */ + { USB_DEVICE(0x057c, 0x2200) }, + + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, bfusb_table); + +#define BFUSB_MAX_BLOCK_SIZE 256 + +#define BFUSB_BLOCK_TIMEOUT 3000 + +#define BFUSB_TX_PROCESS 1 +#define BFUSB_TX_WAKEUP 2 + +#define BFUSB_MAX_BULK_TX 2 +#define BFUSB_MAX_BULK_RX 2 + +struct bfusb_data { + struct hci_dev *hdev; + + unsigned long state; + + struct usb_device *udev; + + unsigned int bulk_in_ep; + unsigned int bulk_out_ep; + unsigned int bulk_pkt_size; + + rwlock_t lock; + + struct sk_buff_head transmit_q; + + struct sk_buff *reassembly; + + atomic_t pending_tx; + struct sk_buff_head pending_q; + struct sk_buff_head completed_q; +}; + +struct bfusb_data_scb { + struct urb *urb; +}; + +static void bfusb_tx_complete(struct urb *urb); +static void bfusb_rx_complete(struct urb *urb); + +static struct urb *bfusb_get_completed(struct bfusb_data *data) +{ + struct sk_buff *skb; + struct urb *urb = NULL; + + BT_DBG("bfusb %p", data); + + skb = skb_dequeue(&data->completed_q); + if (skb) { + urb = ((struct bfusb_data_scb *) skb->cb)->urb; + kfree_skb(skb); + } + + return urb; +} + +static void bfusb_unlink_urbs(struct bfusb_data *data) +{ + struct sk_buff *skb; + struct urb *urb; + + BT_DBG("bfusb %p", data); + + while ((skb = skb_dequeue(&data->pending_q))) { + urb = ((struct bfusb_data_scb *) skb->cb)->urb; + usb_kill_urb(urb); + skb_queue_tail(&data->completed_q, skb); + } + + while ((urb = bfusb_get_completed(data))) + usb_free_urb(urb); +} + +static int bfusb_send_bulk(struct bfusb_data *data, struct sk_buff *skb) +{ + struct bfusb_data_scb *scb = (void *) skb->cb; + struct urb *urb = bfusb_get_completed(data); + int err, pipe; + + BT_DBG("bfusb %p skb %p len %d", data, skb, skb->len); + + if (!urb && !(urb = usb_alloc_urb(0, GFP_ATOMIC))) + return -ENOMEM; + + pipe = usb_sndbulkpipe(data->udev, data->bulk_out_ep); + + usb_fill_bulk_urb(urb, data->udev, pipe, skb->data, skb->len, + bfusb_tx_complete, skb); + + scb->urb = urb; + + skb_queue_tail(&data->pending_q, skb); + + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err) { + BT_ERR("%s bulk tx submit failed urb %p err %d", + data->hdev->name, urb, err); + skb_unlink(skb, &data->pending_q); + usb_free_urb(urb); + } else + atomic_inc(&data->pending_tx); + + return err; +} + +static void bfusb_tx_wakeup(struct bfusb_data *data) +{ + struct sk_buff *skb; + + BT_DBG("bfusb %p", data); + + if (test_and_set_bit(BFUSB_TX_PROCESS, &data->state)) { + set_bit(BFUSB_TX_WAKEUP, &data->state); + return; + } + + do { + clear_bit(BFUSB_TX_WAKEUP, &data->state); + + while ((atomic_read(&data->pending_tx) < BFUSB_MAX_BULK_TX) && + (skb = skb_dequeue(&data->transmit_q))) { + if (bfusb_send_bulk(data, skb) < 0) { + skb_queue_head(&data->transmit_q, skb); + break; + } + } + + } while (test_bit(BFUSB_TX_WAKEUP, &data->state)); + + clear_bit(BFUSB_TX_PROCESS, &data->state); +} + +static void bfusb_tx_complete(struct urb *urb) +{ + struct sk_buff *skb = (struct sk_buff *) urb->context; + struct bfusb_data *data = (struct bfusb_data *) skb->dev; + + BT_DBG("bfusb %p urb %p skb %p len %d", data, urb, skb, skb->len); + + atomic_dec(&data->pending_tx); + + if (!test_bit(HCI_RUNNING, &data->hdev->flags)) + return; + + if (!urb->status) + data->hdev->stat.byte_tx += skb->len; + else + data->hdev->stat.err_tx++; + + read_lock(&data->lock); + + skb_unlink(skb, &data->pending_q); + skb_queue_tail(&data->completed_q, skb); + + bfusb_tx_wakeup(data); + + read_unlock(&data->lock); +} + + +static int bfusb_rx_submit(struct bfusb_data *data, struct urb *urb) +{ + struct bfusb_data_scb *scb; + struct sk_buff *skb; + int err, pipe, size = HCI_MAX_FRAME_SIZE + 32; + + BT_DBG("bfusb %p urb %p", data, urb); + + if (!urb && !(urb = usb_alloc_urb(0, GFP_ATOMIC))) + return -ENOMEM; + + skb = bt_skb_alloc(size, GFP_ATOMIC); + if (!skb) { + usb_free_urb(urb); + return -ENOMEM; + } + + skb->dev = (void *) data; + + scb = (struct bfusb_data_scb *) skb->cb; + scb->urb = urb; + + pipe = usb_rcvbulkpipe(data->udev, data->bulk_in_ep); + + usb_fill_bulk_urb(urb, data->udev, pipe, skb->data, size, + bfusb_rx_complete, skb); + + skb_queue_tail(&data->pending_q, skb); + + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err) { + BT_ERR("%s bulk rx submit failed urb %p err %d", + data->hdev->name, urb, err); + skb_unlink(skb, &data->pending_q); + kfree_skb(skb); + usb_free_urb(urb); + } + + return err; +} + +static inline int bfusb_recv_block(struct bfusb_data *data, int hdr, unsigned char *buf, int len) +{ + BT_DBG("bfusb %p hdr 0x%02x data %p len %d", data, hdr, buf, len); + + if (hdr & 0x10) { + BT_ERR("%s error in block", data->hdev->name); + kfree_skb(data->reassembly); + data->reassembly = NULL; + return -EIO; + } + + if (hdr & 0x04) { + struct sk_buff *skb; + unsigned char pkt_type; + int pkt_len = 0; + + if (data->reassembly) { + BT_ERR("%s unexpected start block", data->hdev->name); + kfree_skb(data->reassembly); + data->reassembly = NULL; + } + + if (len < 1) { + BT_ERR("%s no packet type found", data->hdev->name); + return -EPROTO; + } + + pkt_type = *buf++; len--; + + switch (pkt_type) { + case HCI_EVENT_PKT: + if (len >= HCI_EVENT_HDR_SIZE) { + struct hci_event_hdr *hdr = (struct hci_event_hdr *) buf; + pkt_len = HCI_EVENT_HDR_SIZE + hdr->plen; + } else { + BT_ERR("%s event block is too short", data->hdev->name); + return -EILSEQ; + } + break; + + case HCI_ACLDATA_PKT: + if (len >= HCI_ACL_HDR_SIZE) { + struct hci_acl_hdr *hdr = (struct hci_acl_hdr *) buf; + pkt_len = HCI_ACL_HDR_SIZE + __le16_to_cpu(hdr->dlen); + } else { + BT_ERR("%s data block is too short", data->hdev->name); + return -EILSEQ; + } + break; + + case HCI_SCODATA_PKT: + if (len >= HCI_SCO_HDR_SIZE) { + struct hci_sco_hdr *hdr = (struct hci_sco_hdr *) buf; + pkt_len = HCI_SCO_HDR_SIZE + hdr->dlen; + } else { + BT_ERR("%s audio block is too short", data->hdev->name); + return -EILSEQ; + } + break; + } + + skb = bt_skb_alloc(pkt_len, GFP_ATOMIC); + if (!skb) { + BT_ERR("%s no memory for the packet", data->hdev->name); + return -ENOMEM; + } + + skb->dev = (void *) data->hdev; + bt_cb(skb)->pkt_type = pkt_type; + + data->reassembly = skb; + } else { + if (!data->reassembly) { + BT_ERR("%s unexpected continuation block", data->hdev->name); + return -EIO; + } + } + + if (len > 0) + memcpy(skb_put(data->reassembly, len), buf, len); + + if (hdr & 0x08) { + hci_recv_frame(data->reassembly); + data->reassembly = NULL; + } + + return 0; +} + +static void bfusb_rx_complete(struct urb *urb) +{ + struct sk_buff *skb = (struct sk_buff *) urb->context; + struct bfusb_data *data = (struct bfusb_data *) skb->dev; + unsigned char *buf = urb->transfer_buffer; + int count = urb->actual_length; + int err, hdr, len; + + BT_DBG("bfusb %p urb %p skb %p len %d", data, urb, skb, skb->len); + + read_lock(&data->lock); + + if (!test_bit(HCI_RUNNING, &data->hdev->flags)) + goto unlock; + + if (urb->status || !count) + goto resubmit; + + data->hdev->stat.byte_rx += count; + + skb_put(skb, count); + + while (count) { + hdr = buf[0] | (buf[1] << 8); + + if (hdr & 0x4000) { + len = 0; + count -= 2; + buf += 2; + } else { + len = (buf[2] == 0) ? 256 : buf[2]; + count -= 3; + buf += 3; + } + + if (count < len) { + BT_ERR("%s block extends over URB buffer ranges", + data->hdev->name); + } + + if ((hdr & 0xe1) == 0xc1) + bfusb_recv_block(data, hdr, buf, len); + + count -= len; + buf += len; + } + + skb_unlink(skb, &data->pending_q); + kfree_skb(skb); + + bfusb_rx_submit(data, urb); + + read_unlock(&data->lock); + + return; + +resubmit: + urb->dev = data->udev; + + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err) { + BT_ERR("%s bulk resubmit failed urb %p err %d", + data->hdev->name, urb, err); + } + +unlock: + read_unlock(&data->lock); +} + +static int bfusb_open(struct hci_dev *hdev) +{ + struct bfusb_data *data = hdev->driver_data; + unsigned long flags; + int i, err; + + BT_DBG("hdev %p bfusb %p", hdev, data); + + if (test_and_set_bit(HCI_RUNNING, &hdev->flags)) + return 0; + + write_lock_irqsave(&data->lock, flags); + + err = bfusb_rx_submit(data, NULL); + if (!err) { + for (i = 1; i < BFUSB_MAX_BULK_RX; i++) + bfusb_rx_submit(data, NULL); + } else { + clear_bit(HCI_RUNNING, &hdev->flags); + } + + write_unlock_irqrestore(&data->lock, flags); + + return err; +} + +static int bfusb_flush(struct hci_dev *hdev) +{ + struct bfusb_data *data = hdev->driver_data; + + BT_DBG("hdev %p bfusb %p", hdev, data); + + skb_queue_purge(&data->transmit_q); + + return 0; +} + +static int bfusb_close(struct hci_dev *hdev) +{ + struct bfusb_data *data = hdev->driver_data; + unsigned long flags; + + BT_DBG("hdev %p bfusb %p", hdev, data); + + if (!test_and_clear_bit(HCI_RUNNING, &hdev->flags)) + return 0; + + write_lock_irqsave(&data->lock, flags); + write_unlock_irqrestore(&data->lock, flags); + + bfusb_unlink_urbs(data); + bfusb_flush(hdev); + + return 0; +} + +static int bfusb_send_frame(struct sk_buff *skb) +{ + struct hci_dev *hdev = (struct hci_dev *) skb->dev; + struct bfusb_data *data; + struct sk_buff *nskb; + unsigned char buf[3]; + int sent = 0, size, count; + + BT_DBG("hdev %p skb %p type %d len %d", hdev, skb, bt_cb(skb)->pkt_type, skb->len); + + if (!hdev) { + BT_ERR("Frame for unknown HCI device (hdev=NULL)"); + return -ENODEV; + } + + if (!test_bit(HCI_RUNNING, &hdev->flags)) + return -EBUSY; + + data = hdev->driver_data; + + switch (bt_cb(skb)->pkt_type) { + case HCI_COMMAND_PKT: + hdev->stat.cmd_tx++; + break; + case HCI_ACLDATA_PKT: + hdev->stat.acl_tx++; + break; + case HCI_SCODATA_PKT: + hdev->stat.sco_tx++; + break; + }; + + /* Prepend skb with frame type */ + memcpy(skb_push(skb, 1), &bt_cb(skb)->pkt_type, 1); + + count = skb->len; + + /* Max HCI frame size seems to be 1511 + 1 */ + nskb = bt_skb_alloc(count + 32, GFP_ATOMIC); + if (!nskb) { + BT_ERR("Can't allocate memory for new packet"); + return -ENOMEM; + } + + nskb->dev = (void *) data; + + while (count) { + size = min_t(uint, count, BFUSB_MAX_BLOCK_SIZE); + + buf[0] = 0xc1 | ((sent == 0) ? 0x04 : 0) | ((count == size) ? 0x08 : 0); + buf[1] = 0x00; + buf[2] = (size == BFUSB_MAX_BLOCK_SIZE) ? 0 : size; + + memcpy(skb_put(nskb, 3), buf, 3); + skb_copy_from_linear_data_offset(skb, sent, skb_put(nskb, size), size); + + sent += size; + count -= size; + } + + /* Don't send frame with multiple size of bulk max packet */ + if ((nskb->len % data->bulk_pkt_size) == 0) { + buf[0] = 0xdd; + buf[1] = 0x00; + memcpy(skb_put(nskb, 2), buf, 2); + } + + read_lock(&data->lock); + + skb_queue_tail(&data->transmit_q, nskb); + bfusb_tx_wakeup(data); + + read_unlock(&data->lock); + + kfree_skb(skb); + + return 0; +} + +static void bfusb_destruct(struct hci_dev *hdev) +{ + struct bfusb_data *data = hdev->driver_data; + + BT_DBG("hdev %p bfusb %p", hdev, data); + + kfree(data); +} + +static int bfusb_ioctl(struct hci_dev *hdev, unsigned int cmd, unsigned long arg) +{ + return -ENOIOCTLCMD; +} + +static int bfusb_load_firmware(struct bfusb_data *data, + const unsigned char *firmware, int count) +{ + unsigned char *buf; + int err, pipe, len, size, sent = 0; + + BT_DBG("bfusb %p udev %p", data, data->udev); + + BT_INFO("BlueFRITZ! USB loading firmware"); + + pipe = usb_sndctrlpipe(data->udev, 0); + + if (usb_control_msg(data->udev, pipe, USB_REQ_SET_CONFIGURATION, + 0, 1, 0, NULL, 0, USB_CTRL_SET_TIMEOUT) < 0) { + BT_ERR("Can't change to loading configuration"); + return -EBUSY; + } + + data->udev->toggle[0] = data->udev->toggle[1] = 0; + + buf = kmalloc(BFUSB_MAX_BLOCK_SIZE + 3, GFP_ATOMIC); + if (!buf) { + BT_ERR("Can't allocate memory chunk for firmware"); + return -ENOMEM; + } + + pipe = usb_sndbulkpipe(data->udev, data->bulk_out_ep); + + while (count) { + size = min_t(uint, count, BFUSB_MAX_BLOCK_SIZE + 3); + + memcpy(buf, firmware + sent, size); + + err = usb_bulk_msg(data->udev, pipe, buf, size, + &len, BFUSB_BLOCK_TIMEOUT); + + if (err || (len != size)) { + BT_ERR("Error in firmware loading"); + goto error; + } + + sent += size; + count -= size; + } + + err = usb_bulk_msg(data->udev, pipe, NULL, 0, + &len, BFUSB_BLOCK_TIMEOUT); + if (err < 0) { + BT_ERR("Error in null packet request"); + goto error; + } + + pipe = usb_sndctrlpipe(data->udev, 0); + + err = usb_control_msg(data->udev, pipe, USB_REQ_SET_CONFIGURATION, + 0, 2, 0, NULL, 0, USB_CTRL_SET_TIMEOUT); + if (err < 0) { + BT_ERR("Can't change to running configuration"); + goto error; + } + + data->udev->toggle[0] = data->udev->toggle[1] = 0; + + BT_INFO("BlueFRITZ! USB device ready"); + + kfree(buf); + return 0; + +error: + kfree(buf); + + pipe = usb_sndctrlpipe(data->udev, 0); + + usb_control_msg(data->udev, pipe, USB_REQ_SET_CONFIGURATION, + 0, 0, 0, NULL, 0, USB_CTRL_SET_TIMEOUT); + + return err; +} + +static int bfusb_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + const struct firmware *firmware; + struct usb_device *udev = interface_to_usbdev(intf); + struct usb_host_endpoint *bulk_out_ep; + struct usb_host_endpoint *bulk_in_ep; + struct hci_dev *hdev; + struct bfusb_data *data; + + BT_DBG("intf %p id %p", intf, id); + + /* Check number of endpoints */ + if (intf->cur_altsetting->desc.bNumEndpoints < 2) + return -EIO; + + bulk_out_ep = &intf->cur_altsetting->endpoint[0]; + bulk_in_ep = &intf->cur_altsetting->endpoint[1]; + + if (!bulk_out_ep || !bulk_in_ep) { + BT_ERR("Bulk endpoints not found"); + goto done; + } + + /* Initialize control structure and load firmware */ + data = kzalloc(sizeof(struct bfusb_data), GFP_KERNEL); + if (!data) { + BT_ERR("Can't allocate memory for control structure"); + goto done; + } + + data->udev = udev; + data->bulk_in_ep = bulk_in_ep->desc.bEndpointAddress; + data->bulk_out_ep = bulk_out_ep->desc.bEndpointAddress; + data->bulk_pkt_size = le16_to_cpu(bulk_out_ep->desc.wMaxPacketSize); + + rwlock_init(&data->lock); + + data->reassembly = NULL; + + skb_queue_head_init(&data->transmit_q); + skb_queue_head_init(&data->pending_q); + skb_queue_head_init(&data->completed_q); + + if (request_firmware(&firmware, "bfubase.frm", &udev->dev) < 0) { + BT_ERR("Firmware request failed"); + goto error; + } + + BT_DBG("firmware data %p size %zu", firmware->data, firmware->size); + + if (bfusb_load_firmware(data, firmware->data, firmware->size) < 0) { + BT_ERR("Firmware loading failed"); + goto release; + } + + release_firmware(firmware); + + /* Initialize and register HCI device */ + hdev = hci_alloc_dev(); + if (!hdev) { + BT_ERR("Can't allocate HCI device"); + goto error; + } + + data->hdev = hdev; + + hdev->bus = HCI_USB; + hdev->driver_data = data; + SET_HCIDEV_DEV(hdev, &intf->dev); + + hdev->open = bfusb_open; + hdev->close = bfusb_close; + hdev->flush = bfusb_flush; + hdev->send = bfusb_send_frame; + hdev->destruct = bfusb_destruct; + hdev->ioctl = bfusb_ioctl; + + hdev->owner = THIS_MODULE; + + if (hci_register_dev(hdev) < 0) { + BT_ERR("Can't register HCI device"); + hci_free_dev(hdev); + goto error; + } + + usb_set_intfdata(intf, data); + + return 0; + +release: + release_firmware(firmware); + +error: + kfree(data); + +done: + return -EIO; +} + +static void bfusb_disconnect(struct usb_interface *intf) +{ + struct bfusb_data *data = usb_get_intfdata(intf); + struct hci_dev *hdev = data->hdev; + + BT_DBG("intf %p", intf); + + if (!hdev) + return; + + usb_set_intfdata(intf, NULL); + + bfusb_close(hdev); + + if (hci_unregister_dev(hdev) < 0) + BT_ERR("Can't unregister HCI device %s", hdev->name); + + hci_free_dev(hdev); +} + +static struct usb_driver bfusb_driver = { + .name = "bfusb", + .probe = bfusb_probe, + .disconnect = bfusb_disconnect, + .id_table = bfusb_table, +}; + +static int __init bfusb_init(void) +{ + int err; + + BT_INFO("BlueFRITZ! USB driver ver %s", VERSION); + + err = usb_register(&bfusb_driver); + if (err < 0) + BT_ERR("Failed to register BlueFRITZ! USB driver"); + + return err; +} + +static void __exit bfusb_exit(void) +{ + usb_deregister(&bfusb_driver); +} + +module_init(bfusb_init); +module_exit(bfusb_exit); + +MODULE_AUTHOR("Marcel Holtmann <marcel@holtmann.org>"); +MODULE_DESCRIPTION("BlueFRITZ! USB driver ver " VERSION); +MODULE_VERSION(VERSION); +MODULE_LICENSE("GPL"); +MODULE_FIRMWARE("bfubase.frm"); diff --git a/drivers/bluetooth/bluecard_cs.c b/drivers/bluetooth/bluecard_cs.c new file mode 100644 index 00000000..aed1904e --- /dev/null +++ b/drivers/bluetooth/bluecard_cs.c @@ -0,0 +1,961 @@ +/* + * + * Bluetooth driver for the Anycom BlueCard (LSE039/LSE041) + * + * Copyright (C) 2001-2002 Marcel Holtmann <marcel@holtmann.org> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation; + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The initial developer of the original code is David A. Hinds + * <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + * + */ + +#include <linux/module.h> + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/sched.h> +#include <linux/delay.h> +#include <linux/timer.h> +#include <linux/errno.h> +#include <linux/ptrace.h> +#include <linux/ioport.h> +#include <linux/spinlock.h> +#include <linux/moduleparam.h> +#include <linux/wait.h> + +#include <linux/skbuff.h> +#include <linux/io.h> + +#include <pcmcia/cistpl.h> +#include <pcmcia/ciscode.h> +#include <pcmcia/ds.h> +#include <pcmcia/cisreg.h> + +#include <net/bluetooth/bluetooth.h> +#include <net/bluetooth/hci_core.h> + + + +/* ======================== Module parameters ======================== */ + + +MODULE_AUTHOR("Marcel Holtmann <marcel@holtmann.org>"); +MODULE_DESCRIPTION("Bluetooth driver for the Anycom BlueCard (LSE039/LSE041)"); +MODULE_LICENSE("GPL"); + + + +/* ======================== Local structures ======================== */ + + +typedef struct bluecard_info_t { + struct pcmcia_device *p_dev; + + struct hci_dev *hdev; + + spinlock_t lock; /* For serializing operations */ + struct timer_list timer; /* For LED control */ + + struct sk_buff_head txq; + unsigned long tx_state; + + unsigned long rx_state; + unsigned long rx_count; + struct sk_buff *rx_skb; + + unsigned char ctrl_reg; + unsigned long hw_state; /* Status of the hardware and LED control */ +} bluecard_info_t; + + +static int bluecard_config(struct pcmcia_device *link); +static void bluecard_release(struct pcmcia_device *link); + +static void bluecard_detach(struct pcmcia_device *p_dev); + + +/* Default baud rate: 57600, 115200, 230400 or 460800 */ +#define DEFAULT_BAUD_RATE 230400 + + +/* Hardware states */ +#define CARD_READY 1 +#define CARD_HAS_PCCARD_ID 4 +#define CARD_HAS_POWER_LED 5 +#define CARD_HAS_ACTIVITY_LED 6 + +/* Transmit states */ +#define XMIT_SENDING 1 +#define XMIT_WAKEUP 2 +#define XMIT_BUFFER_NUMBER 5 /* unset = buffer one, set = buffer two */ +#define XMIT_BUF_ONE_READY 6 +#define XMIT_BUF_TWO_READY 7 +#define XMIT_SENDING_READY 8 + +/* Receiver states */ +#define RECV_WAIT_PACKET_TYPE 0 +#define RECV_WAIT_EVENT_HEADER 1 +#define RECV_WAIT_ACL_HEADER 2 +#define RECV_WAIT_SCO_HEADER 3 +#define RECV_WAIT_DATA 4 + +/* Special packet types */ +#define PKT_BAUD_RATE_57600 0x80 +#define PKT_BAUD_RATE_115200 0x81 +#define PKT_BAUD_RATE_230400 0x82 +#define PKT_BAUD_RATE_460800 0x83 + + +/* These are the register offsets */ +#define REG_COMMAND 0x20 +#define REG_INTERRUPT 0x21 +#define REG_CONTROL 0x22 +#define REG_RX_CONTROL 0x24 +#define REG_CARD_RESET 0x30 +#define REG_LED_CTRL 0x30 + +/* REG_COMMAND */ +#define REG_COMMAND_TX_BUF_ONE 0x01 +#define REG_COMMAND_TX_BUF_TWO 0x02 +#define REG_COMMAND_RX_BUF_ONE 0x04 +#define REG_COMMAND_RX_BUF_TWO 0x08 +#define REG_COMMAND_RX_WIN_ONE 0x00 +#define REG_COMMAND_RX_WIN_TWO 0x10 + +/* REG_CONTROL */ +#define REG_CONTROL_BAUD_RATE_57600 0x00 +#define REG_CONTROL_BAUD_RATE_115200 0x01 +#define REG_CONTROL_BAUD_RATE_230400 0x02 +#define REG_CONTROL_BAUD_RATE_460800 0x03 +#define REG_CONTROL_RTS 0x04 +#define REG_CONTROL_BT_ON 0x08 +#define REG_CONTROL_BT_RESET 0x10 +#define REG_CONTROL_BT_RES_PU 0x20 +#define REG_CONTROL_INTERRUPT 0x40 +#define REG_CONTROL_CARD_RESET 0x80 + +/* REG_RX_CONTROL */ +#define RTS_LEVEL_SHIFT_BITS 0x02 + + + +/* ======================== LED handling routines ======================== */ + + +static void bluecard_activity_led_timeout(u_long arg) +{ + bluecard_info_t *info = (bluecard_info_t *)arg; + unsigned int iobase = info->p_dev->resource[0]->start; + + if (!test_bit(CARD_HAS_PCCARD_ID, &(info->hw_state))) + return; + + if (test_bit(CARD_HAS_ACTIVITY_LED, &(info->hw_state))) { + /* Disable activity LED */ + outb(0x08 | 0x20, iobase + 0x30); + } else { + /* Disable power LED */ + outb(0x00, iobase + 0x30); + } +} + + +static void bluecard_enable_activity_led(bluecard_info_t *info) +{ + unsigned int iobase = info->p_dev->resource[0]->start; + + if (!test_bit(CARD_HAS_PCCARD_ID, &(info->hw_state))) + return; + + if (test_bit(CARD_HAS_ACTIVITY_LED, &(info->hw_state))) { + /* Enable activity LED */ + outb(0x10 | 0x40, iobase + 0x30); + + /* Stop the LED after HZ/4 */ + mod_timer(&(info->timer), jiffies + HZ / 4); + } else { + /* Enable power LED */ + outb(0x08 | 0x20, iobase + 0x30); + + /* Stop the LED after HZ/2 */ + mod_timer(&(info->timer), jiffies + HZ / 2); + } +} + + + +/* ======================== Interrupt handling ======================== */ + + +static int bluecard_write(unsigned int iobase, unsigned int offset, __u8 *buf, int len) +{ + int i, actual; + + actual = (len > 15) ? 15 : len; + + outb_p(actual, iobase + offset); + + for (i = 0; i < actual; i++) + outb_p(buf[i], iobase + offset + i + 1); + + return actual; +} + + +static void bluecard_write_wakeup(bluecard_info_t *info) +{ + if (!info) { + BT_ERR("Unknown device"); + return; + } + + if (!test_bit(XMIT_SENDING_READY, &(info->tx_state))) + return; + + if (test_and_set_bit(XMIT_SENDING, &(info->tx_state))) { + set_bit(XMIT_WAKEUP, &(info->tx_state)); + return; + } + + do { + register unsigned int iobase = info->p_dev->resource[0]->start; + register unsigned int offset; + register unsigned char command; + register unsigned long ready_bit; + register struct sk_buff *skb; + register int len; + + clear_bit(XMIT_WAKEUP, &(info->tx_state)); + + if (!pcmcia_dev_present(info->p_dev)) + return; + + if (test_bit(XMIT_BUFFER_NUMBER, &(info->tx_state))) { + if (!test_bit(XMIT_BUF_TWO_READY, &(info->tx_state))) + break; + offset = 0x10; + command = REG_COMMAND_TX_BUF_TWO; + ready_bit = XMIT_BUF_TWO_READY; + } else { + if (!test_bit(XMIT_BUF_ONE_READY, &(info->tx_state))) + break; + offset = 0x00; + command = REG_COMMAND_TX_BUF_ONE; + ready_bit = XMIT_BUF_ONE_READY; + } + + if (!(skb = skb_dequeue(&(info->txq)))) + break; + + if (bt_cb(skb)->pkt_type & 0x80) { + /* Disable RTS */ + info->ctrl_reg |= REG_CONTROL_RTS; + outb(info->ctrl_reg, iobase + REG_CONTROL); + } + + /* Activate LED */ + bluecard_enable_activity_led(info); + + /* Send frame */ + len = bluecard_write(iobase, offset, skb->data, skb->len); + + /* Tell the FPGA to send the data */ + outb_p(command, iobase + REG_COMMAND); + + /* Mark the buffer as dirty */ + clear_bit(ready_bit, &(info->tx_state)); + + if (bt_cb(skb)->pkt_type & 0x80) { + DECLARE_WAIT_QUEUE_HEAD_ONSTACK(wq); + DEFINE_WAIT(wait); + + unsigned char baud_reg; + + switch (bt_cb(skb)->pkt_type) { + case PKT_BAUD_RATE_460800: + baud_reg = REG_CONTROL_BAUD_RATE_460800; + break; + case PKT_BAUD_RATE_230400: + baud_reg = REG_CONTROL_BAUD_RATE_230400; + break; + case PKT_BAUD_RATE_115200: + baud_reg = REG_CONTROL_BAUD_RATE_115200; + break; + case PKT_BAUD_RATE_57600: + /* Fall through... */ + default: + baud_reg = REG_CONTROL_BAUD_RATE_57600; + break; + } + + /* Wait until the command reaches the baseband */ + prepare_to_wait(&wq, &wait, TASK_INTERRUPTIBLE); + schedule_timeout(HZ/10); + finish_wait(&wq, &wait); + + /* Set baud on baseband */ + info->ctrl_reg &= ~0x03; + info->ctrl_reg |= baud_reg; + outb(info->ctrl_reg, iobase + REG_CONTROL); + + /* Enable RTS */ + info->ctrl_reg &= ~REG_CONTROL_RTS; + outb(info->ctrl_reg, iobase + REG_CONTROL); + + /* Wait before the next HCI packet can be send */ + prepare_to_wait(&wq, &wait, TASK_INTERRUPTIBLE); + schedule_timeout(HZ); + finish_wait(&wq, &wait); + } + + if (len == skb->len) { + kfree_skb(skb); + } else { + skb_pull(skb, len); + skb_queue_head(&(info->txq), skb); + } + + info->hdev->stat.byte_tx += len; + + /* Change buffer */ + change_bit(XMIT_BUFFER_NUMBER, &(info->tx_state)); + + } while (test_bit(XMIT_WAKEUP, &(info->tx_state))); + + clear_bit(XMIT_SENDING, &(info->tx_state)); +} + + +static int bluecard_read(unsigned int iobase, unsigned int offset, __u8 *buf, int size) +{ + int i, n, len; + + outb(REG_COMMAND_RX_WIN_ONE, iobase + REG_COMMAND); + + len = inb(iobase + offset); + n = 0; + i = 1; + + while (n < len) { + + if (i == 16) { + outb(REG_COMMAND_RX_WIN_TWO, iobase + REG_COMMAND); + i = 0; + } + + buf[n] = inb(iobase + offset + i); + + n++; + i++; + + } + + return len; +} + + +static void bluecard_receive(bluecard_info_t *info, unsigned int offset) +{ + unsigned int iobase; + unsigned char buf[31]; + int i, len; + + if (!info) { + BT_ERR("Unknown device"); + return; + } + + iobase = info->p_dev->resource[0]->start; + + if (test_bit(XMIT_SENDING_READY, &(info->tx_state))) + bluecard_enable_activity_led(info); + + len = bluecard_read(iobase, offset, buf, sizeof(buf)); + + for (i = 0; i < len; i++) { + + /* Allocate packet */ + if (info->rx_skb == NULL) { + info->rx_state = RECV_WAIT_PACKET_TYPE; + info->rx_count = 0; + if (!(info->rx_skb = bt_skb_alloc(HCI_MAX_FRAME_SIZE, GFP_ATOMIC))) { + BT_ERR("Can't allocate mem for new packet"); + return; + } + } + + if (info->rx_state == RECV_WAIT_PACKET_TYPE) { + + info->rx_skb->dev = (void *) info->hdev; + bt_cb(info->rx_skb)->pkt_type = buf[i]; + + switch (bt_cb(info->rx_skb)->pkt_type) { + + case 0x00: + /* init packet */ + if (offset != 0x00) { + set_bit(XMIT_BUF_ONE_READY, &(info->tx_state)); + set_bit(XMIT_BUF_TWO_READY, &(info->tx_state)); + set_bit(XMIT_SENDING_READY, &(info->tx_state)); + bluecard_write_wakeup(info); + } + + kfree_skb(info->rx_skb); + info->rx_skb = NULL; + break; + + case HCI_EVENT_PKT: + info->rx_state = RECV_WAIT_EVENT_HEADER; + info->rx_count = HCI_EVENT_HDR_SIZE; + break; + + case HCI_ACLDATA_PKT: + info->rx_state = RECV_WAIT_ACL_HEADER; + info->rx_count = HCI_ACL_HDR_SIZE; + break; + + case HCI_SCODATA_PKT: + info->rx_state = RECV_WAIT_SCO_HEADER; + info->rx_count = HCI_SCO_HDR_SIZE; + break; + + default: + /* unknown packet */ + BT_ERR("Unknown HCI packet with type 0x%02x received", bt_cb(info->rx_skb)->pkt_type); + info->hdev->stat.err_rx++; + + kfree_skb(info->rx_skb); + info->rx_skb = NULL; + break; + + } + + } else { + + *skb_put(info->rx_skb, 1) = buf[i]; + info->rx_count--; + + if (info->rx_count == 0) { + + int dlen; + struct hci_event_hdr *eh; + struct hci_acl_hdr *ah; + struct hci_sco_hdr *sh; + + switch (info->rx_state) { + + case RECV_WAIT_EVENT_HEADER: + eh = hci_event_hdr(info->rx_skb); + info->rx_state = RECV_WAIT_DATA; + info->rx_count = eh->plen; + break; + + case RECV_WAIT_ACL_HEADER: + ah = hci_acl_hdr(info->rx_skb); + dlen = __le16_to_cpu(ah->dlen); + info->rx_state = RECV_WAIT_DATA; + info->rx_count = dlen; + break; + + case RECV_WAIT_SCO_HEADER: + sh = hci_sco_hdr(info->rx_skb); + info->rx_state = RECV_WAIT_DATA; + info->rx_count = sh->dlen; + break; + + case RECV_WAIT_DATA: + hci_recv_frame(info->rx_skb); + info->rx_skb = NULL; + break; + + } + + } + + } + + + } + + info->hdev->stat.byte_rx += len; +} + + +static irqreturn_t bluecard_interrupt(int irq, void *dev_inst) +{ + bluecard_info_t *info = dev_inst; + unsigned int iobase; + unsigned char reg; + + if (!info || !info->hdev) + /* our irq handler is shared */ + return IRQ_NONE; + + if (!test_bit(CARD_READY, &(info->hw_state))) + return IRQ_HANDLED; + + iobase = info->p_dev->resource[0]->start; + + spin_lock(&(info->lock)); + + /* Disable interrupt */ + info->ctrl_reg &= ~REG_CONTROL_INTERRUPT; + outb(info->ctrl_reg, iobase + REG_CONTROL); + + reg = inb(iobase + REG_INTERRUPT); + + if ((reg != 0x00) && (reg != 0xff)) { + + if (reg & 0x04) { + bluecard_receive(info, 0x00); + outb(0x04, iobase + REG_INTERRUPT); + outb(REG_COMMAND_RX_BUF_ONE, iobase + REG_COMMAND); + } + + if (reg & 0x08) { + bluecard_receive(info, 0x10); + outb(0x08, iobase + REG_INTERRUPT); + outb(REG_COMMAND_RX_BUF_TWO, iobase + REG_COMMAND); + } + + if (reg & 0x01) { + set_bit(XMIT_BUF_ONE_READY, &(info->tx_state)); + outb(0x01, iobase + REG_INTERRUPT); + bluecard_write_wakeup(info); + } + + if (reg & 0x02) { + set_bit(XMIT_BUF_TWO_READY, &(info->tx_state)); + outb(0x02, iobase + REG_INTERRUPT); + bluecard_write_wakeup(info); + } + + } + + /* Enable interrupt */ + info->ctrl_reg |= REG_CONTROL_INTERRUPT; + outb(info->ctrl_reg, iobase + REG_CONTROL); + + spin_unlock(&(info->lock)); + + return IRQ_HANDLED; +} + + + +/* ======================== Device specific HCI commands ======================== */ + + +static int bluecard_hci_set_baud_rate(struct hci_dev *hdev, int baud) +{ + bluecard_info_t *info = (bluecard_info_t *)(hdev->driver_data); + struct sk_buff *skb; + + /* Ericsson baud rate command */ + unsigned char cmd[] = { HCI_COMMAND_PKT, 0x09, 0xfc, 0x01, 0x03 }; + + if (!(skb = bt_skb_alloc(HCI_MAX_FRAME_SIZE, GFP_ATOMIC))) { + BT_ERR("Can't allocate mem for new packet"); + return -1; + } + + switch (baud) { + case 460800: + cmd[4] = 0x00; + bt_cb(skb)->pkt_type = PKT_BAUD_RATE_460800; + break; + case 230400: + cmd[4] = 0x01; + bt_cb(skb)->pkt_type = PKT_BAUD_RATE_230400; + break; + case 115200: + cmd[4] = 0x02; + bt_cb(skb)->pkt_type = PKT_BAUD_RATE_115200; + break; + case 57600: + /* Fall through... */ + default: + cmd[4] = 0x03; + bt_cb(skb)->pkt_type = PKT_BAUD_RATE_57600; + break; + } + + memcpy(skb_put(skb, sizeof(cmd)), cmd, sizeof(cmd)); + + skb_queue_tail(&(info->txq), skb); + + bluecard_write_wakeup(info); + + return 0; +} + + + +/* ======================== HCI interface ======================== */ + + +static int bluecard_hci_flush(struct hci_dev *hdev) +{ + bluecard_info_t *info = (bluecard_info_t *)(hdev->driver_data); + + /* Drop TX queue */ + skb_queue_purge(&(info->txq)); + + return 0; +} + + +static int bluecard_hci_open(struct hci_dev *hdev) +{ + bluecard_info_t *info = (bluecard_info_t *)(hdev->driver_data); + unsigned int iobase = info->p_dev->resource[0]->start; + + if (test_bit(CARD_HAS_PCCARD_ID, &(info->hw_state))) + bluecard_hci_set_baud_rate(hdev, DEFAULT_BAUD_RATE); + + if (test_and_set_bit(HCI_RUNNING, &(hdev->flags))) + return 0; + + if (test_bit(CARD_HAS_PCCARD_ID, &(info->hw_state))) { + /* Enable LED */ + outb(0x08 | 0x20, iobase + 0x30); + } + + return 0; +} + + +static int bluecard_hci_close(struct hci_dev *hdev) +{ + bluecard_info_t *info = (bluecard_info_t *)(hdev->driver_data); + unsigned int iobase = info->p_dev->resource[0]->start; + + if (!test_and_clear_bit(HCI_RUNNING, &(hdev->flags))) + return 0; + + bluecard_hci_flush(hdev); + + if (test_bit(CARD_HAS_PCCARD_ID, &(info->hw_state))) { + /* Disable LED */ + outb(0x00, iobase + 0x30); + } + + return 0; +} + + +static int bluecard_hci_send_frame(struct sk_buff *skb) +{ + bluecard_info_t *info; + struct hci_dev *hdev = (struct hci_dev *)(skb->dev); + + if (!hdev) { + BT_ERR("Frame for unknown HCI device (hdev=NULL)"); + return -ENODEV; + } + + info = (bluecard_info_t *)(hdev->driver_data); + + switch (bt_cb(skb)->pkt_type) { + case HCI_COMMAND_PKT: + hdev->stat.cmd_tx++; + break; + case HCI_ACLDATA_PKT: + hdev->stat.acl_tx++; + break; + case HCI_SCODATA_PKT: + hdev->stat.sco_tx++; + break; + }; + + /* Prepend skb with frame type */ + memcpy(skb_push(skb, 1), &bt_cb(skb)->pkt_type, 1); + skb_queue_tail(&(info->txq), skb); + + bluecard_write_wakeup(info); + + return 0; +} + + +static void bluecard_hci_destruct(struct hci_dev *hdev) +{ +} + + +static int bluecard_hci_ioctl(struct hci_dev *hdev, unsigned int cmd, unsigned long arg) +{ + return -ENOIOCTLCMD; +} + + + +/* ======================== Card services HCI interaction ======================== */ + + +static int bluecard_open(bluecard_info_t *info) +{ + unsigned int iobase = info->p_dev->resource[0]->start; + struct hci_dev *hdev; + unsigned char id; + + spin_lock_init(&(info->lock)); + + init_timer(&(info->timer)); + info->timer.function = &bluecard_activity_led_timeout; + info->timer.data = (u_long)info; + + skb_queue_head_init(&(info->txq)); + + info->rx_state = RECV_WAIT_PACKET_TYPE; + info->rx_count = 0; + info->rx_skb = NULL; + + /* Initialize HCI device */ + hdev = hci_alloc_dev(); + if (!hdev) { + BT_ERR("Can't allocate HCI device"); + return -ENOMEM; + } + + info->hdev = hdev; + + hdev->bus = HCI_PCCARD; + hdev->driver_data = info; + SET_HCIDEV_DEV(hdev, &info->p_dev->dev); + + hdev->open = bluecard_hci_open; + hdev->close = bluecard_hci_close; + hdev->flush = bluecard_hci_flush; + hdev->send = bluecard_hci_send_frame; + hdev->destruct = bluecard_hci_destruct; + hdev->ioctl = bluecard_hci_ioctl; + + hdev->owner = THIS_MODULE; + + id = inb(iobase + 0x30); + + if ((id & 0x0f) == 0x02) + set_bit(CARD_HAS_PCCARD_ID, &(info->hw_state)); + + if (id & 0x10) + set_bit(CARD_HAS_POWER_LED, &(info->hw_state)); + + if (id & 0x20) + set_bit(CARD_HAS_ACTIVITY_LED, &(info->hw_state)); + + /* Reset card */ + info->ctrl_reg = REG_CONTROL_BT_RESET | REG_CONTROL_CARD_RESET; + outb(info->ctrl_reg, iobase + REG_CONTROL); + + /* Turn FPGA off */ + outb(0x80, iobase + 0x30); + + /* Wait some time */ + msleep(10); + + /* Turn FPGA on */ + outb(0x00, iobase + 0x30); + + /* Activate card */ + info->ctrl_reg = REG_CONTROL_BT_ON | REG_CONTROL_BT_RES_PU; + outb(info->ctrl_reg, iobase + REG_CONTROL); + + /* Enable interrupt */ + outb(0xff, iobase + REG_INTERRUPT); + info->ctrl_reg |= REG_CONTROL_INTERRUPT; + outb(info->ctrl_reg, iobase + REG_CONTROL); + + if ((id & 0x0f) == 0x03) { + /* Disable RTS */ + info->ctrl_reg |= REG_CONTROL_RTS; + outb(info->ctrl_reg, iobase + REG_CONTROL); + + /* Set baud rate */ + info->ctrl_reg |= 0x03; + outb(info->ctrl_reg, iobase + REG_CONTROL); + + /* Enable RTS */ + info->ctrl_reg &= ~REG_CONTROL_RTS; + outb(info->ctrl_reg, iobase + REG_CONTROL); + + set_bit(XMIT_BUF_ONE_READY, &(info->tx_state)); + set_bit(XMIT_BUF_TWO_READY, &(info->tx_state)); + set_bit(XMIT_SENDING_READY, &(info->tx_state)); + } + + /* Start the RX buffers */ + outb(REG_COMMAND_RX_BUF_ONE, iobase + REG_COMMAND); + outb(REG_COMMAND_RX_BUF_TWO, iobase + REG_COMMAND); + + /* Signal that the hardware is ready */ + set_bit(CARD_READY, &(info->hw_state)); + + /* Drop TX queue */ + skb_queue_purge(&(info->txq)); + + /* Control the point at which RTS is enabled */ + outb((0x0f << RTS_LEVEL_SHIFT_BITS) | 1, iobase + REG_RX_CONTROL); + + /* Timeout before it is safe to send the first HCI packet */ + msleep(1250); + + /* Register HCI device */ + if (hci_register_dev(hdev) < 0) { + BT_ERR("Can't register HCI device"); + info->hdev = NULL; + hci_free_dev(hdev); + return -ENODEV; + } + + return 0; +} + + +static int bluecard_close(bluecard_info_t *info) +{ + unsigned int iobase = info->p_dev->resource[0]->start; + struct hci_dev *hdev = info->hdev; + + if (!hdev) + return -ENODEV; + + bluecard_hci_close(hdev); + + clear_bit(CARD_READY, &(info->hw_state)); + + /* Reset card */ + info->ctrl_reg = REG_CONTROL_BT_RESET | REG_CONTROL_CARD_RESET; + outb(info->ctrl_reg, iobase + REG_CONTROL); + + /* Turn FPGA off */ + outb(0x80, iobase + 0x30); + + if (hci_unregister_dev(hdev) < 0) + BT_ERR("Can't unregister HCI device %s", hdev->name); + + hci_free_dev(hdev); + + return 0; +} + +static int bluecard_probe(struct pcmcia_device *link) +{ + bluecard_info_t *info; + + /* Create new info device */ + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->p_dev = link; + link->priv = info; + + link->config_flags |= CONF_ENABLE_IRQ; + + return bluecard_config(link); +} + + +static void bluecard_detach(struct pcmcia_device *link) +{ + bluecard_info_t *info = link->priv; + + bluecard_release(link); + kfree(info); +} + + +static int bluecard_config(struct pcmcia_device *link) +{ + bluecard_info_t *info = link->priv; + int i, n; + + link->config_index = 0x20; + + link->resource[0]->flags |= IO_DATA_PATH_WIDTH_8; + link->resource[0]->end = 64; + link->io_lines = 6; + + for (n = 0; n < 0x400; n += 0x40) { + link->resource[0]->start = n ^ 0x300; + i = pcmcia_request_io(link); + if (i == 0) + break; + } + + if (i != 0) + goto failed; + + i = pcmcia_request_irq(link, bluecard_interrupt); + if (i != 0) + goto failed; + + i = pcmcia_enable_device(link); + if (i != 0) + goto failed; + + if (bluecard_open(info) != 0) + goto failed; + + return 0; + +failed: + bluecard_release(link); + return -ENODEV; +} + + +static void bluecard_release(struct pcmcia_device *link) +{ + bluecard_info_t *info = link->priv; + + bluecard_close(info); + + del_timer(&(info->timer)); + + pcmcia_disable_device(link); +} + +static const struct pcmcia_device_id bluecard_ids[] = { + PCMCIA_DEVICE_PROD_ID12("BlueCard", "LSE041", 0xbaf16fbf, 0x657cc15e), + PCMCIA_DEVICE_PROD_ID12("BTCFCARD", "LSE139", 0xe3987764, 0x2524b59c), + PCMCIA_DEVICE_PROD_ID12("WSS", "LSE039", 0x0a0736ec, 0x24e6dfab), + PCMCIA_DEVICE_NULL +}; +MODULE_DEVICE_TABLE(pcmcia, bluecard_ids); + +static struct pcmcia_driver bluecard_driver = { + .owner = THIS_MODULE, + .name = "bluecard_cs", + .probe = bluecard_probe, + .remove = bluecard_detach, + .id_table = bluecard_ids, +}; + +static int __init init_bluecard_cs(void) +{ + return pcmcia_register_driver(&bluecard_driver); +} + + +static void __exit exit_bluecard_cs(void) +{ + pcmcia_unregister_driver(&bluecard_driver); +} + +module_init(init_bluecard_cs); +module_exit(exit_bluecard_cs); diff --git a/drivers/bluetooth/bpa10x.c b/drivers/bluetooth/bpa10x.c new file mode 100644 index 00000000..751b338d --- /dev/null +++ b/drivers/bluetooth/bpa10x.c @@ -0,0 +1,542 @@ +/* + * + * Digianswer Bluetooth USB driver + * + * Copyright (C) 2004-2007 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/sched.h> +#include <linux/errno.h> +#include <linux/skbuff.h> + +#include <linux/usb.h> + +#include <net/bluetooth/bluetooth.h> +#include <net/bluetooth/hci_core.h> + +#define VERSION "0.10" + +static struct usb_device_id bpa10x_table[] = { + /* Tektronix BPA 100/105 (Digianswer) */ + { USB_DEVICE(0x08fd, 0x0002) }, + + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, bpa10x_table); + +struct bpa10x_data { + struct hci_dev *hdev; + struct usb_device *udev; + + struct usb_anchor tx_anchor; + struct usb_anchor rx_anchor; + + struct sk_buff *rx_skb[2]; +}; + +#define HCI_VENDOR_HDR_SIZE 5 + +struct hci_vendor_hdr { + __u8 type; + __le16 snum; + __le16 dlen; +} __packed; + +static int bpa10x_recv(struct hci_dev *hdev, int queue, void *buf, int count) +{ + struct bpa10x_data *data = hdev->driver_data; + + BT_DBG("%s queue %d buffer %p count %d", hdev->name, + queue, buf, count); + + if (queue < 0 || queue > 1) + return -EILSEQ; + + hdev->stat.byte_rx += count; + + while (count) { + struct sk_buff *skb = data->rx_skb[queue]; + struct { __u8 type; int expect; } *scb; + int type, len = 0; + + if (!skb) { + /* Start of the frame */ + + type = *((__u8 *) buf); + count--; buf++; + + switch (type) { + case HCI_EVENT_PKT: + if (count >= HCI_EVENT_HDR_SIZE) { + struct hci_event_hdr *h = buf; + len = HCI_EVENT_HDR_SIZE + h->plen; + } else + return -EILSEQ; + break; + + case HCI_ACLDATA_PKT: + if (count >= HCI_ACL_HDR_SIZE) { + struct hci_acl_hdr *h = buf; + len = HCI_ACL_HDR_SIZE + + __le16_to_cpu(h->dlen); + } else + return -EILSEQ; + break; + + case HCI_SCODATA_PKT: + if (count >= HCI_SCO_HDR_SIZE) { + struct hci_sco_hdr *h = buf; + len = HCI_SCO_HDR_SIZE + h->dlen; + } else + return -EILSEQ; + break; + + case HCI_VENDOR_PKT: + if (count >= HCI_VENDOR_HDR_SIZE) { + struct hci_vendor_hdr *h = buf; + len = HCI_VENDOR_HDR_SIZE + + __le16_to_cpu(h->dlen); + } else + return -EILSEQ; + break; + } + + skb = bt_skb_alloc(len, GFP_ATOMIC); + if (!skb) { + BT_ERR("%s no memory for packet", hdev->name); + return -ENOMEM; + } + + skb->dev = (void *) hdev; + + data->rx_skb[queue] = skb; + + scb = (void *) skb->cb; + scb->type = type; + scb->expect = len; + } else { + /* Continuation */ + + scb = (void *) skb->cb; + len = scb->expect; + } + + len = min(len, count); + + memcpy(skb_put(skb, len), buf, len); + + scb->expect -= len; + + if (scb->expect == 0) { + /* Complete frame */ + + data->rx_skb[queue] = NULL; + + bt_cb(skb)->pkt_type = scb->type; + hci_recv_frame(skb); + } + + count -= len; buf += len; + } + + return 0; +} + +static void bpa10x_tx_complete(struct urb *urb) +{ + struct sk_buff *skb = urb->context; + struct hci_dev *hdev = (struct hci_dev *) skb->dev; + + BT_DBG("%s urb %p status %d count %d", hdev->name, + urb, urb->status, urb->actual_length); + + if (!test_bit(HCI_RUNNING, &hdev->flags)) + goto done; + + if (!urb->status) + hdev->stat.byte_tx += urb->transfer_buffer_length; + else + hdev->stat.err_tx++; + +done: + kfree(urb->setup_packet); + + kfree_skb(skb); +} + +static void bpa10x_rx_complete(struct urb *urb) +{ + struct hci_dev *hdev = urb->context; + struct bpa10x_data *data = hdev->driver_data; + int err; + + BT_DBG("%s urb %p status %d count %d", hdev->name, + urb, urb->status, urb->actual_length); + + if (!test_bit(HCI_RUNNING, &hdev->flags)) + return; + + if (urb->status == 0) { + if (bpa10x_recv(hdev, usb_pipebulk(urb->pipe), + urb->transfer_buffer, + urb->actual_length) < 0) { + BT_ERR("%s corrupted event packet", hdev->name); + hdev->stat.err_rx++; + } + } + + usb_anchor_urb(urb, &data->rx_anchor); + + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err < 0) { + BT_ERR("%s urb %p failed to resubmit (%d)", + hdev->name, urb, -err); + usb_unanchor_urb(urb); + } +} + +static inline int bpa10x_submit_intr_urb(struct hci_dev *hdev) +{ + struct bpa10x_data *data = hdev->driver_data; + struct urb *urb; + unsigned char *buf; + unsigned int pipe; + int err, size = 16; + + BT_DBG("%s", hdev->name); + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) + return -ENOMEM; + + buf = kmalloc(size, GFP_KERNEL); + if (!buf) { + usb_free_urb(urb); + return -ENOMEM; + } + + pipe = usb_rcvintpipe(data->udev, 0x81); + + usb_fill_int_urb(urb, data->udev, pipe, buf, size, + bpa10x_rx_complete, hdev, 1); + + urb->transfer_flags |= URB_FREE_BUFFER; + + usb_anchor_urb(urb, &data->rx_anchor); + + err = usb_submit_urb(urb, GFP_KERNEL); + if (err < 0) { + BT_ERR("%s urb %p submission failed (%d)", + hdev->name, urb, -err); + usb_unanchor_urb(urb); + } + + usb_free_urb(urb); + + return err; +} + +static inline int bpa10x_submit_bulk_urb(struct hci_dev *hdev) +{ + struct bpa10x_data *data = hdev->driver_data; + struct urb *urb; + unsigned char *buf; + unsigned int pipe; + int err, size = 64; + + BT_DBG("%s", hdev->name); + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) + return -ENOMEM; + + buf = kmalloc(size, GFP_KERNEL); + if (!buf) { + usb_free_urb(urb); + return -ENOMEM; + } + + pipe = usb_rcvbulkpipe(data->udev, 0x82); + + usb_fill_bulk_urb(urb, data->udev, pipe, + buf, size, bpa10x_rx_complete, hdev); + + urb->transfer_flags |= URB_FREE_BUFFER; + + usb_anchor_urb(urb, &data->rx_anchor); + + err = usb_submit_urb(urb, GFP_KERNEL); + if (err < 0) { + BT_ERR("%s urb %p submission failed (%d)", + hdev->name, urb, -err); + usb_unanchor_urb(urb); + } + + usb_free_urb(urb); + + return err; +} + +static int bpa10x_open(struct hci_dev *hdev) +{ + struct bpa10x_data *data = hdev->driver_data; + int err; + + BT_DBG("%s", hdev->name); + + if (test_and_set_bit(HCI_RUNNING, &hdev->flags)) + return 0; + + err = bpa10x_submit_intr_urb(hdev); + if (err < 0) + goto error; + + err = bpa10x_submit_bulk_urb(hdev); + if (err < 0) + goto error; + + return 0; + +error: + usb_kill_anchored_urbs(&data->rx_anchor); + + clear_bit(HCI_RUNNING, &hdev->flags); + + return err; +} + +static int bpa10x_close(struct hci_dev *hdev) +{ + struct bpa10x_data *data = hdev->driver_data; + + BT_DBG("%s", hdev->name); + + if (!test_and_clear_bit(HCI_RUNNING, &hdev->flags)) + return 0; + + usb_kill_anchored_urbs(&data->rx_anchor); + + return 0; +} + +static int bpa10x_flush(struct hci_dev *hdev) +{ + struct bpa10x_data *data = hdev->driver_data; + + BT_DBG("%s", hdev->name); + + usb_kill_anchored_urbs(&data->tx_anchor); + + return 0; +} + +static int bpa10x_send_frame(struct sk_buff *skb) +{ + struct hci_dev *hdev = (struct hci_dev *) skb->dev; + struct bpa10x_data *data = hdev->driver_data; + struct usb_ctrlrequest *dr; + struct urb *urb; + unsigned int pipe; + int err; + + BT_DBG("%s", hdev->name); + + if (!test_bit(HCI_RUNNING, &hdev->flags)) + return -EBUSY; + + urb = usb_alloc_urb(0, GFP_ATOMIC); + if (!urb) + return -ENOMEM; + + /* Prepend skb with frame type */ + *skb_push(skb, 1) = bt_cb(skb)->pkt_type; + + switch (bt_cb(skb)->pkt_type) { + case HCI_COMMAND_PKT: + dr = kmalloc(sizeof(*dr), GFP_ATOMIC); + if (!dr) { + usb_free_urb(urb); + return -ENOMEM; + } + + dr->bRequestType = USB_TYPE_VENDOR; + dr->bRequest = 0; + dr->wIndex = 0; + dr->wValue = 0; + dr->wLength = __cpu_to_le16(skb->len); + + pipe = usb_sndctrlpipe(data->udev, 0x00); + + usb_fill_control_urb(urb, data->udev, pipe, (void *) dr, + skb->data, skb->len, bpa10x_tx_complete, skb); + + hdev->stat.cmd_tx++; + break; + + case HCI_ACLDATA_PKT: + pipe = usb_sndbulkpipe(data->udev, 0x02); + + usb_fill_bulk_urb(urb, data->udev, pipe, + skb->data, skb->len, bpa10x_tx_complete, skb); + + hdev->stat.acl_tx++; + break; + + case HCI_SCODATA_PKT: + pipe = usb_sndbulkpipe(data->udev, 0x02); + + usb_fill_bulk_urb(urb, data->udev, pipe, + skb->data, skb->len, bpa10x_tx_complete, skb); + + hdev->stat.sco_tx++; + break; + + default: + usb_free_urb(urb); + return -EILSEQ; + } + + usb_anchor_urb(urb, &data->tx_anchor); + + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err < 0) { + BT_ERR("%s urb %p submission failed", hdev->name, urb); + kfree(urb->setup_packet); + usb_unanchor_urb(urb); + } + + usb_free_urb(urb); + + return 0; +} + +static void bpa10x_destruct(struct hci_dev *hdev) +{ + struct bpa10x_data *data = hdev->driver_data; + + BT_DBG("%s", hdev->name); + + kfree_skb(data->rx_skb[0]); + kfree_skb(data->rx_skb[1]); + kfree(data); +} + +static int bpa10x_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + struct bpa10x_data *data; + struct hci_dev *hdev; + int err; + + BT_DBG("intf %p id %p", intf, id); + + if (intf->cur_altsetting->desc.bInterfaceNumber != 0) + return -ENODEV; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->udev = interface_to_usbdev(intf); + + init_usb_anchor(&data->tx_anchor); + init_usb_anchor(&data->rx_anchor); + + hdev = hci_alloc_dev(); + if (!hdev) { + kfree(data); + return -ENOMEM; + } + + hdev->bus = HCI_USB; + hdev->driver_data = data; + + data->hdev = hdev; + + SET_HCIDEV_DEV(hdev, &intf->dev); + + hdev->open = bpa10x_open; + hdev->close = bpa10x_close; + hdev->flush = bpa10x_flush; + hdev->send = bpa10x_send_frame; + hdev->destruct = bpa10x_destruct; + + hdev->owner = THIS_MODULE; + + set_bit(HCI_QUIRK_NO_RESET, &hdev->quirks); + + err = hci_register_dev(hdev); + if (err < 0) { + hci_free_dev(hdev); + kfree(data); + return err; + } + + usb_set_intfdata(intf, data); + + return 0; +} + +static void bpa10x_disconnect(struct usb_interface *intf) +{ + struct bpa10x_data *data = usb_get_intfdata(intf); + + BT_DBG("intf %p", intf); + + if (!data) + return; + + usb_set_intfdata(intf, NULL); + + hci_unregister_dev(data->hdev); + + hci_free_dev(data->hdev); +} + +static struct usb_driver bpa10x_driver = { + .name = "bpa10x", + .probe = bpa10x_probe, + .disconnect = bpa10x_disconnect, + .id_table = bpa10x_table, +}; + +static int __init bpa10x_init(void) +{ + BT_INFO("Digianswer Bluetooth USB driver ver %s", VERSION); + + return usb_register(&bpa10x_driver); +} + +static void __exit bpa10x_exit(void) +{ + usb_deregister(&bpa10x_driver); +} + +module_init(bpa10x_init); +module_exit(bpa10x_exit); + +MODULE_AUTHOR("Marcel Holtmann <marcel@holtmann.org>"); +MODULE_DESCRIPTION("Digianswer Bluetooth USB driver ver " VERSION); +MODULE_VERSION(VERSION); +MODULE_LICENSE("GPL"); diff --git a/drivers/bluetooth/bt3c_cs.c b/drivers/bluetooth/bt3c_cs.c new file mode 100644 index 00000000..4fc01949 --- /dev/null +++ b/drivers/bluetooth/bt3c_cs.c @@ -0,0 +1,790 @@ +/* + * + * Driver for the 3Com Bluetooth PCMCIA card + * + * Copyright (C) 2001-2002 Marcel Holtmann <marcel@holtmann.org> + * Jose Orlando Pereira <jop@di.uminho.pt> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation; + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The initial developer of the original code is David A. Hinds + * <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + * + */ + +#include <linux/module.h> + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/ptrace.h> +#include <linux/ioport.h> +#include <linux/spinlock.h> +#include <linux/moduleparam.h> + +#include <linux/skbuff.h> +#include <linux/string.h> +#include <linux/serial.h> +#include <linux/serial_reg.h> +#include <linux/bitops.h> +#include <asm/system.h> +#include <asm/io.h> + +#include <linux/device.h> +#include <linux/firmware.h> + +#include <pcmcia/cistpl.h> +#include <pcmcia/ciscode.h> +#include <pcmcia/ds.h> +#include <pcmcia/cisreg.h> + +#include <net/bluetooth/bluetooth.h> +#include <net/bluetooth/hci_core.h> + + + +/* ======================== Module parameters ======================== */ + + +MODULE_AUTHOR("Marcel Holtmann <marcel@holtmann.org>"); +MODULE_DESCRIPTION("Bluetooth driver for the 3Com Bluetooth PCMCIA card"); +MODULE_LICENSE("GPL"); +MODULE_FIRMWARE("BT3CPCC.bin"); + + + +/* ======================== Local structures ======================== */ + + +typedef struct bt3c_info_t { + struct pcmcia_device *p_dev; + + struct hci_dev *hdev; + + spinlock_t lock; /* For serializing operations */ + + struct sk_buff_head txq; + unsigned long tx_state; + + unsigned long rx_state; + unsigned long rx_count; + struct sk_buff *rx_skb; +} bt3c_info_t; + + +static int bt3c_config(struct pcmcia_device *link); +static void bt3c_release(struct pcmcia_device *link); + +static void bt3c_detach(struct pcmcia_device *p_dev); + + +/* Transmit states */ +#define XMIT_SENDING 1 +#define XMIT_WAKEUP 2 +#define XMIT_WAITING 8 + +/* Receiver states */ +#define RECV_WAIT_PACKET_TYPE 0 +#define RECV_WAIT_EVENT_HEADER 1 +#define RECV_WAIT_ACL_HEADER 2 +#define RECV_WAIT_SCO_HEADER 3 +#define RECV_WAIT_DATA 4 + + + +/* ======================== Special I/O functions ======================== */ + + +#define DATA_L 0 +#define DATA_H 1 +#define ADDR_L 2 +#define ADDR_H 3 +#define CONTROL 4 + + +static inline void bt3c_address(unsigned int iobase, unsigned short addr) +{ + outb(addr & 0xff, iobase + ADDR_L); + outb((addr >> 8) & 0xff, iobase + ADDR_H); +} + + +static inline void bt3c_put(unsigned int iobase, unsigned short value) +{ + outb(value & 0xff, iobase + DATA_L); + outb((value >> 8) & 0xff, iobase + DATA_H); +} + + +static inline void bt3c_io_write(unsigned int iobase, unsigned short addr, unsigned short value) +{ + bt3c_address(iobase, addr); + bt3c_put(iobase, value); +} + + +static inline unsigned short bt3c_get(unsigned int iobase) +{ + unsigned short value = inb(iobase + DATA_L); + + value |= inb(iobase + DATA_H) << 8; + + return value; +} + + +static inline unsigned short bt3c_read(unsigned int iobase, unsigned short addr) +{ + bt3c_address(iobase, addr); + + return bt3c_get(iobase); +} + + + +/* ======================== Interrupt handling ======================== */ + + +static int bt3c_write(unsigned int iobase, int fifo_size, __u8 *buf, int len) +{ + int actual = 0; + + bt3c_address(iobase, 0x7080); + + /* Fill FIFO with current frame */ + while (actual < len) { + /* Transmit next byte */ + bt3c_put(iobase, buf[actual]); + actual++; + } + + bt3c_io_write(iobase, 0x7005, actual); + + return actual; +} + + +static void bt3c_write_wakeup(bt3c_info_t *info) +{ + if (!info) { + BT_ERR("Unknown device"); + return; + } + + if (test_and_set_bit(XMIT_SENDING, &(info->tx_state))) + return; + + do { + register unsigned int iobase = info->p_dev->resource[0]->start; + register struct sk_buff *skb; + register int len; + + if (!pcmcia_dev_present(info->p_dev)) + break; + + + if (!(skb = skb_dequeue(&(info->txq)))) { + clear_bit(XMIT_SENDING, &(info->tx_state)); + break; + } + + /* Send frame */ + len = bt3c_write(iobase, 256, skb->data, skb->len); + + if (len != skb->len) { + BT_ERR("Very strange"); + } + + kfree_skb(skb); + + info->hdev->stat.byte_tx += len; + + } while (0); +} + + +static void bt3c_receive(bt3c_info_t *info) +{ + unsigned int iobase; + int size = 0, avail; + + if (!info) { + BT_ERR("Unknown device"); + return; + } + + iobase = info->p_dev->resource[0]->start; + + avail = bt3c_read(iobase, 0x7006); + //printk("bt3c_cs: receiving %d bytes\n", avail); + + bt3c_address(iobase, 0x7480); + while (size < avail) { + size++; + info->hdev->stat.byte_rx++; + + /* Allocate packet */ + if (info->rx_skb == NULL) { + info->rx_state = RECV_WAIT_PACKET_TYPE; + info->rx_count = 0; + if (!(info->rx_skb = bt_skb_alloc(HCI_MAX_FRAME_SIZE, GFP_ATOMIC))) { + BT_ERR("Can't allocate mem for new packet"); + return; + } + } + + + if (info->rx_state == RECV_WAIT_PACKET_TYPE) { + + info->rx_skb->dev = (void *) info->hdev; + bt_cb(info->rx_skb)->pkt_type = inb(iobase + DATA_L); + inb(iobase + DATA_H); + //printk("bt3c: PACKET_TYPE=%02x\n", bt_cb(info->rx_skb)->pkt_type); + + switch (bt_cb(info->rx_skb)->pkt_type) { + + case HCI_EVENT_PKT: + info->rx_state = RECV_WAIT_EVENT_HEADER; + info->rx_count = HCI_EVENT_HDR_SIZE; + break; + + case HCI_ACLDATA_PKT: + info->rx_state = RECV_WAIT_ACL_HEADER; + info->rx_count = HCI_ACL_HDR_SIZE; + break; + + case HCI_SCODATA_PKT: + info->rx_state = RECV_WAIT_SCO_HEADER; + info->rx_count = HCI_SCO_HDR_SIZE; + break; + + default: + /* Unknown packet */ + BT_ERR("Unknown HCI packet with type 0x%02x received", bt_cb(info->rx_skb)->pkt_type); + info->hdev->stat.err_rx++; + clear_bit(HCI_RUNNING, &(info->hdev->flags)); + + kfree_skb(info->rx_skb); + info->rx_skb = NULL; + break; + + } + + } else { + + __u8 x = inb(iobase + DATA_L); + + *skb_put(info->rx_skb, 1) = x; + inb(iobase + DATA_H); + info->rx_count--; + + if (info->rx_count == 0) { + + int dlen; + struct hci_event_hdr *eh; + struct hci_acl_hdr *ah; + struct hci_sco_hdr *sh; + + switch (info->rx_state) { + + case RECV_WAIT_EVENT_HEADER: + eh = hci_event_hdr(info->rx_skb); + info->rx_state = RECV_WAIT_DATA; + info->rx_count = eh->plen; + break; + + case RECV_WAIT_ACL_HEADER: + ah = hci_acl_hdr(info->rx_skb); + dlen = __le16_to_cpu(ah->dlen); + info->rx_state = RECV_WAIT_DATA; + info->rx_count = dlen; + break; + + case RECV_WAIT_SCO_HEADER: + sh = hci_sco_hdr(info->rx_skb); + info->rx_state = RECV_WAIT_DATA; + info->rx_count = sh->dlen; + break; + + case RECV_WAIT_DATA: + hci_recv_frame(info->rx_skb); + info->rx_skb = NULL; + break; + + } + + } + + } + + } + + bt3c_io_write(iobase, 0x7006, 0x0000); +} + + +static irqreturn_t bt3c_interrupt(int irq, void *dev_inst) +{ + bt3c_info_t *info = dev_inst; + unsigned int iobase; + int iir; + irqreturn_t r = IRQ_NONE; + + if (!info || !info->hdev) + /* our irq handler is shared */ + return IRQ_NONE; + + iobase = info->p_dev->resource[0]->start; + + spin_lock(&(info->lock)); + + iir = inb(iobase + CONTROL); + if (iir & 0x80) { + int stat = bt3c_read(iobase, 0x7001); + + if ((stat & 0xff) == 0x7f) { + BT_ERR("Very strange (stat=0x%04x)", stat); + } else if ((stat & 0xff) != 0xff) { + if (stat & 0x0020) { + int status = bt3c_read(iobase, 0x7002) & 0x10; + BT_INFO("%s: Antenna %s", info->hdev->name, + status ? "out" : "in"); + } + if (stat & 0x0001) + bt3c_receive(info); + if (stat & 0x0002) { + //BT_ERR("Ack (stat=0x%04x)", stat); + clear_bit(XMIT_SENDING, &(info->tx_state)); + bt3c_write_wakeup(info); + } + + bt3c_io_write(iobase, 0x7001, 0x0000); + + outb(iir, iobase + CONTROL); + } + r = IRQ_HANDLED; + } + + spin_unlock(&(info->lock)); + + return r; +} + + + +/* ======================== HCI interface ======================== */ + + +static int bt3c_hci_flush(struct hci_dev *hdev) +{ + bt3c_info_t *info = (bt3c_info_t *)(hdev->driver_data); + + /* Drop TX queue */ + skb_queue_purge(&(info->txq)); + + return 0; +} + + +static int bt3c_hci_open(struct hci_dev *hdev) +{ + set_bit(HCI_RUNNING, &(hdev->flags)); + + return 0; +} + + +static int bt3c_hci_close(struct hci_dev *hdev) +{ + if (!test_and_clear_bit(HCI_RUNNING, &(hdev->flags))) + return 0; + + bt3c_hci_flush(hdev); + + return 0; +} + + +static int bt3c_hci_send_frame(struct sk_buff *skb) +{ + bt3c_info_t *info; + struct hci_dev *hdev = (struct hci_dev *)(skb->dev); + unsigned long flags; + + if (!hdev) { + BT_ERR("Frame for unknown HCI device (hdev=NULL)"); + return -ENODEV; + } + + info = (bt3c_info_t *) (hdev->driver_data); + + switch (bt_cb(skb)->pkt_type) { + case HCI_COMMAND_PKT: + hdev->stat.cmd_tx++; + break; + case HCI_ACLDATA_PKT: + hdev->stat.acl_tx++; + break; + case HCI_SCODATA_PKT: + hdev->stat.sco_tx++; + break; + }; + + /* Prepend skb with frame type */ + memcpy(skb_push(skb, 1), &bt_cb(skb)->pkt_type, 1); + skb_queue_tail(&(info->txq), skb); + + spin_lock_irqsave(&(info->lock), flags); + + bt3c_write_wakeup(info); + + spin_unlock_irqrestore(&(info->lock), flags); + + return 0; +} + + +static void bt3c_hci_destruct(struct hci_dev *hdev) +{ +} + + +static int bt3c_hci_ioctl(struct hci_dev *hdev, unsigned int cmd, unsigned long arg) +{ + return -ENOIOCTLCMD; +} + + + +/* ======================== Card services HCI interaction ======================== */ + + +static int bt3c_load_firmware(bt3c_info_t *info, const unsigned char *firmware, + int count) +{ + char *ptr = (char *) firmware; + char b[9]; + unsigned int iobase, size, addr, fcs, tmp; + int i, err = 0; + + iobase = info->p_dev->resource[0]->start; + + /* Reset */ + bt3c_io_write(iobase, 0x8040, 0x0404); + bt3c_io_write(iobase, 0x8040, 0x0400); + + udelay(1); + + bt3c_io_write(iobase, 0x8040, 0x0404); + + udelay(17); + + /* Load */ + while (count) { + if (ptr[0] != 'S') { + BT_ERR("Bad address in firmware"); + err = -EFAULT; + goto error; + } + + memset(b, 0, sizeof(b)); + memcpy(b, ptr + 2, 2); + size = simple_strtoul(b, NULL, 16); + + memset(b, 0, sizeof(b)); + memcpy(b, ptr + 4, 8); + addr = simple_strtoul(b, NULL, 16); + + memset(b, 0, sizeof(b)); + memcpy(b, ptr + (size * 2) + 2, 2); + fcs = simple_strtoul(b, NULL, 16); + + memset(b, 0, sizeof(b)); + for (tmp = 0, i = 0; i < size; i++) { + memcpy(b, ptr + (i * 2) + 2, 2); + tmp += simple_strtol(b, NULL, 16); + } + + if (((tmp + fcs) & 0xff) != 0xff) { + BT_ERR("Checksum error in firmware"); + err = -EILSEQ; + goto error; + } + + if (ptr[1] == '3') { + bt3c_address(iobase, addr); + + memset(b, 0, sizeof(b)); + for (i = 0; i < (size - 4) / 2; i++) { + memcpy(b, ptr + (i * 4) + 12, 4); + tmp = simple_strtoul(b, NULL, 16); + bt3c_put(iobase, tmp); + } + } + + ptr += (size * 2) + 6; + count -= (size * 2) + 6; + } + + udelay(17); + + /* Boot */ + bt3c_address(iobase, 0x3000); + outb(inb(iobase + CONTROL) | 0x40, iobase + CONTROL); + +error: + udelay(17); + + /* Clear */ + bt3c_io_write(iobase, 0x7006, 0x0000); + bt3c_io_write(iobase, 0x7005, 0x0000); + bt3c_io_write(iobase, 0x7001, 0x0000); + + return err; +} + + +static int bt3c_open(bt3c_info_t *info) +{ + const struct firmware *firmware; + struct hci_dev *hdev; + int err; + + spin_lock_init(&(info->lock)); + + skb_queue_head_init(&(info->txq)); + + info->rx_state = RECV_WAIT_PACKET_TYPE; + info->rx_count = 0; + info->rx_skb = NULL; + + /* Initialize HCI device */ + hdev = hci_alloc_dev(); + if (!hdev) { + BT_ERR("Can't allocate HCI device"); + return -ENOMEM; + } + + info->hdev = hdev; + + hdev->bus = HCI_PCCARD; + hdev->driver_data = info; + SET_HCIDEV_DEV(hdev, &info->p_dev->dev); + + hdev->open = bt3c_hci_open; + hdev->close = bt3c_hci_close; + hdev->flush = bt3c_hci_flush; + hdev->send = bt3c_hci_send_frame; + hdev->destruct = bt3c_hci_destruct; + hdev->ioctl = bt3c_hci_ioctl; + + hdev->owner = THIS_MODULE; + + /* Load firmware */ + err = request_firmware(&firmware, "BT3CPCC.bin", &info->p_dev->dev); + if (err < 0) { + BT_ERR("Firmware request failed"); + goto error; + } + + err = bt3c_load_firmware(info, firmware->data, firmware->size); + + release_firmware(firmware); + + if (err < 0) { + BT_ERR("Firmware loading failed"); + goto error; + } + + /* Timeout before it is safe to send the first HCI packet */ + msleep(1000); + + /* Register HCI device */ + err = hci_register_dev(hdev); + if (err < 0) { + BT_ERR("Can't register HCI device"); + goto error; + } + + return 0; + +error: + info->hdev = NULL; + hci_free_dev(hdev); + return err; +} + + +static int bt3c_close(bt3c_info_t *info) +{ + struct hci_dev *hdev = info->hdev; + + if (!hdev) + return -ENODEV; + + bt3c_hci_close(hdev); + + if (hci_unregister_dev(hdev) < 0) + BT_ERR("Can't unregister HCI device %s", hdev->name); + + hci_free_dev(hdev); + + return 0; +} + +static int bt3c_probe(struct pcmcia_device *link) +{ + bt3c_info_t *info; + + /* Create new info device */ + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->p_dev = link; + link->priv = info; + + link->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_SET_VPP | + CONF_AUTO_SET_IO; + + return bt3c_config(link); +} + + +static void bt3c_detach(struct pcmcia_device *link) +{ + bt3c_info_t *info = link->priv; + + bt3c_release(link); + kfree(info); +} + +static int bt3c_check_config(struct pcmcia_device *p_dev, void *priv_data) +{ + int *try = priv_data; + + if (try == 0) + p_dev->io_lines = 16; + + if ((p_dev->resource[0]->end != 8) || (p_dev->resource[0]->start == 0)) + return -EINVAL; + + p_dev->resource[0]->end = 8; + p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH; + p_dev->resource[0]->flags |= IO_DATA_PATH_WIDTH_8; + + return pcmcia_request_io(p_dev); +} + +static int bt3c_check_config_notpicky(struct pcmcia_device *p_dev, + void *priv_data) +{ + static unsigned int base[5] = { 0x3f8, 0x2f8, 0x3e8, 0x2e8, 0x0 }; + int j; + + if (p_dev->io_lines > 3) + return -ENODEV; + + p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH; + p_dev->resource[0]->flags |= IO_DATA_PATH_WIDTH_8; + p_dev->resource[0]->end = 8; + + for (j = 0; j < 5; j++) { + p_dev->resource[0]->start = base[j]; + p_dev->io_lines = base[j] ? 16 : 3; + if (!pcmcia_request_io(p_dev)) + return 0; + } + return -ENODEV; +} + +static int bt3c_config(struct pcmcia_device *link) +{ + bt3c_info_t *info = link->priv; + int i; + unsigned long try; + + /* First pass: look for a config entry that looks normal. + Two tries: without IO aliases, then with aliases */ + for (try = 0; try < 2; try++) + if (!pcmcia_loop_config(link, bt3c_check_config, (void *) try)) + goto found_port; + + /* Second pass: try to find an entry that isn't picky about + its base address, then try to grab any standard serial port + address, and finally try to get any free port. */ + if (!pcmcia_loop_config(link, bt3c_check_config_notpicky, NULL)) + goto found_port; + + BT_ERR("No usable port range found"); + goto failed; + +found_port: + i = pcmcia_request_irq(link, &bt3c_interrupt); + if (i != 0) + goto failed; + + i = pcmcia_enable_device(link); + if (i != 0) + goto failed; + + if (bt3c_open(info) != 0) + goto failed; + + return 0; + +failed: + bt3c_release(link); + return -ENODEV; +} + + +static void bt3c_release(struct pcmcia_device *link) +{ + bt3c_info_t *info = link->priv; + + bt3c_close(info); + + pcmcia_disable_device(link); +} + + +static const struct pcmcia_device_id bt3c_ids[] = { + PCMCIA_DEVICE_PROD_ID13("3COM", "Bluetooth PC Card", 0xefce0a31, 0xd4ce9b02), + PCMCIA_DEVICE_NULL +}; +MODULE_DEVICE_TABLE(pcmcia, bt3c_ids); + +static struct pcmcia_driver bt3c_driver = { + .owner = THIS_MODULE, + .name = "bt3c_cs", + .probe = bt3c_probe, + .remove = bt3c_detach, + .id_table = bt3c_ids, +}; + +static int __init init_bt3c_cs(void) +{ + return pcmcia_register_driver(&bt3c_driver); +} + + +static void __exit exit_bt3c_cs(void) +{ + pcmcia_unregister_driver(&bt3c_driver); +} + +module_init(init_bt3c_cs); +module_exit(exit_bt3c_cs); diff --git a/drivers/bluetooth/btmrvl_debugfs.c b/drivers/bluetooth/btmrvl_debugfs.c new file mode 100644 index 00000000..8ecf4c6c --- /dev/null +++ b/drivers/bluetooth/btmrvl_debugfs.c @@ -0,0 +1,454 @@ +/** + * Marvell Bluetooth driver: debugfs related functions + * + * Copyright (C) 2009, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the + * worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + * + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + **/ + +#include <linux/debugfs.h> +#include <linux/slab.h> + +#include <net/bluetooth/bluetooth.h> +#include <net/bluetooth/hci_core.h> + +#include "btmrvl_drv.h" + +struct btmrvl_debugfs_data { + struct dentry *config_dir; + struct dentry *status_dir; + + /* config */ + struct dentry *psmode; + struct dentry *pscmd; + struct dentry *hsmode; + struct dentry *hscmd; + struct dentry *gpiogap; + struct dentry *hscfgcmd; + + /* status */ + struct dentry *curpsmode; + struct dentry *hsstate; + struct dentry *psstate; + struct dentry *txdnldready; +}; + +static int btmrvl_open_generic(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static ssize_t btmrvl_hscfgcmd_write(struct file *file, + const char __user *ubuf, size_t count, loff_t *ppos) +{ + struct btmrvl_private *priv = file->private_data; + char buf[16]; + long result, ret; + + memset(buf, 0, sizeof(buf)); + + if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count))) + return -EFAULT; + + ret = strict_strtol(buf, 10, &result); + if (ret) + return ret; + + priv->btmrvl_dev.hscfgcmd = result; + + if (priv->btmrvl_dev.hscfgcmd) { + btmrvl_prepare_command(priv); + wake_up_interruptible(&priv->main_thread.wait_q); + } + + return count; +} + +static ssize_t btmrvl_hscfgcmd_read(struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct btmrvl_private *priv = file->private_data; + char buf[16]; + int ret; + + ret = snprintf(buf, sizeof(buf) - 1, "%d\n", + priv->btmrvl_dev.hscfgcmd); + + return simple_read_from_buffer(userbuf, count, ppos, buf, ret); +} + +static const struct file_operations btmrvl_hscfgcmd_fops = { + .read = btmrvl_hscfgcmd_read, + .write = btmrvl_hscfgcmd_write, + .open = btmrvl_open_generic, + .llseek = default_llseek, +}; + +static ssize_t btmrvl_psmode_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct btmrvl_private *priv = file->private_data; + char buf[16]; + long result, ret; + + memset(buf, 0, sizeof(buf)); + + if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count))) + return -EFAULT; + + ret = strict_strtol(buf, 10, &result); + if (ret) + return ret; + + priv->btmrvl_dev.psmode = result; + + return count; +} + +static ssize_t btmrvl_psmode_read(struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct btmrvl_private *priv = file->private_data; + char buf[16]; + int ret; + + ret = snprintf(buf, sizeof(buf) - 1, "%d\n", + priv->btmrvl_dev.psmode); + + return simple_read_from_buffer(userbuf, count, ppos, buf, ret); +} + +static const struct file_operations btmrvl_psmode_fops = { + .read = btmrvl_psmode_read, + .write = btmrvl_psmode_write, + .open = btmrvl_open_generic, + .llseek = default_llseek, +}; + +static ssize_t btmrvl_pscmd_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct btmrvl_private *priv = file->private_data; + char buf[16]; + long result, ret; + + memset(buf, 0, sizeof(buf)); + + if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count))) + return -EFAULT; + + ret = strict_strtol(buf, 10, &result); + if (ret) + return ret; + + priv->btmrvl_dev.pscmd = result; + + if (priv->btmrvl_dev.pscmd) { + btmrvl_prepare_command(priv); + wake_up_interruptible(&priv->main_thread.wait_q); + } + + return count; + +} + +static ssize_t btmrvl_pscmd_read(struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct btmrvl_private *priv = file->private_data; + char buf[16]; + int ret; + + ret = snprintf(buf, sizeof(buf) - 1, "%d\n", priv->btmrvl_dev.pscmd); + + return simple_read_from_buffer(userbuf, count, ppos, buf, ret); +} + +static const struct file_operations btmrvl_pscmd_fops = { + .read = btmrvl_pscmd_read, + .write = btmrvl_pscmd_write, + .open = btmrvl_open_generic, + .llseek = default_llseek, +}; + +static ssize_t btmrvl_gpiogap_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct btmrvl_private *priv = file->private_data; + char buf[16]; + long result, ret; + + memset(buf, 0, sizeof(buf)); + + if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count))) + return -EFAULT; + + ret = strict_strtol(buf, 16, &result); + if (ret) + return ret; + + priv->btmrvl_dev.gpio_gap = result; + + return count; +} + +static ssize_t btmrvl_gpiogap_read(struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct btmrvl_private *priv = file->private_data; + char buf[16]; + int ret; + + ret = snprintf(buf, sizeof(buf) - 1, "0x%x\n", + priv->btmrvl_dev.gpio_gap); + + return simple_read_from_buffer(userbuf, count, ppos, buf, ret); +} + +static const struct file_operations btmrvl_gpiogap_fops = { + .read = btmrvl_gpiogap_read, + .write = btmrvl_gpiogap_write, + .open = btmrvl_open_generic, + .llseek = default_llseek, +}; + +static ssize_t btmrvl_hscmd_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct btmrvl_private *priv = file->private_data; + char buf[16]; + long result, ret; + + memset(buf, 0, sizeof(buf)); + + if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count))) + return -EFAULT; + + ret = strict_strtol(buf, 10, &result); + if (ret) + return ret; + + priv->btmrvl_dev.hscmd = result; + if (priv->btmrvl_dev.hscmd) { + btmrvl_prepare_command(priv); + wake_up_interruptible(&priv->main_thread.wait_q); + } + + return count; +} + +static ssize_t btmrvl_hscmd_read(struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct btmrvl_private *priv = file->private_data; + char buf[16]; + int ret; + + ret = snprintf(buf, sizeof(buf) - 1, "%d\n", priv->btmrvl_dev.hscmd); + + return simple_read_from_buffer(userbuf, count, ppos, buf, ret); +} + +static const struct file_operations btmrvl_hscmd_fops = { + .read = btmrvl_hscmd_read, + .write = btmrvl_hscmd_write, + .open = btmrvl_open_generic, + .llseek = default_llseek, +}; + +static ssize_t btmrvl_hsmode_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct btmrvl_private *priv = file->private_data; + char buf[16]; + long result, ret; + + memset(buf, 0, sizeof(buf)); + + if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count))) + return -EFAULT; + + ret = strict_strtol(buf, 10, &result); + if (ret) + return ret; + + priv->btmrvl_dev.hsmode = result; + + return count; +} + +static ssize_t btmrvl_hsmode_read(struct file *file, char __user * userbuf, + size_t count, loff_t *ppos) +{ + struct btmrvl_private *priv = file->private_data; + char buf[16]; + int ret; + + ret = snprintf(buf, sizeof(buf) - 1, "%d\n", priv->btmrvl_dev.hsmode); + + return simple_read_from_buffer(userbuf, count, ppos, buf, ret); +} + +static const struct file_operations btmrvl_hsmode_fops = { + .read = btmrvl_hsmode_read, + .write = btmrvl_hsmode_write, + .open = btmrvl_open_generic, + .llseek = default_llseek, +}; + +static ssize_t btmrvl_curpsmode_read(struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct btmrvl_private *priv = file->private_data; + char buf[16]; + int ret; + + ret = snprintf(buf, sizeof(buf) - 1, "%d\n", priv->adapter->psmode); + + return simple_read_from_buffer(userbuf, count, ppos, buf, ret); +} + +static const struct file_operations btmrvl_curpsmode_fops = { + .read = btmrvl_curpsmode_read, + .open = btmrvl_open_generic, + .llseek = default_llseek, +}; + +static ssize_t btmrvl_psstate_read(struct file *file, char __user * userbuf, + size_t count, loff_t *ppos) +{ + struct btmrvl_private *priv = file->private_data; + char buf[16]; + int ret; + + ret = snprintf(buf, sizeof(buf) - 1, "%d\n", priv->adapter->ps_state); + + return simple_read_from_buffer(userbuf, count, ppos, buf, ret); +} + +static const struct file_operations btmrvl_psstate_fops = { + .read = btmrvl_psstate_read, + .open = btmrvl_open_generic, + .llseek = default_llseek, +}; + +static ssize_t btmrvl_hsstate_read(struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct btmrvl_private *priv = file->private_data; + char buf[16]; + int ret; + + ret = snprintf(buf, sizeof(buf) - 1, "%d\n", priv->adapter->hs_state); + + return simple_read_from_buffer(userbuf, count, ppos, buf, ret); +} + +static const struct file_operations btmrvl_hsstate_fops = { + .read = btmrvl_hsstate_read, + .open = btmrvl_open_generic, + .llseek = default_llseek, +}; + +static ssize_t btmrvl_txdnldready_read(struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct btmrvl_private *priv = file->private_data; + char buf[16]; + int ret; + + ret = snprintf(buf, sizeof(buf) - 1, "%d\n", + priv->btmrvl_dev.tx_dnld_rdy); + + return simple_read_from_buffer(userbuf, count, ppos, buf, ret); +} + +static const struct file_operations btmrvl_txdnldready_fops = { + .read = btmrvl_txdnldready_read, + .open = btmrvl_open_generic, + .llseek = default_llseek, +}; + +void btmrvl_debugfs_init(struct hci_dev *hdev) +{ + struct btmrvl_private *priv = hdev->driver_data; + struct btmrvl_debugfs_data *dbg; + + if (!hdev->debugfs) + return; + + dbg = kzalloc(sizeof(*dbg), GFP_KERNEL); + priv->debugfs_data = dbg; + + if (!dbg) { + BT_ERR("Can not allocate memory for btmrvl_debugfs_data."); + return; + } + + dbg->config_dir = debugfs_create_dir("config", hdev->debugfs); + + dbg->psmode = debugfs_create_file("psmode", 0644, dbg->config_dir, + hdev->driver_data, &btmrvl_psmode_fops); + dbg->pscmd = debugfs_create_file("pscmd", 0644, dbg->config_dir, + hdev->driver_data, &btmrvl_pscmd_fops); + dbg->gpiogap = debugfs_create_file("gpiogap", 0644, dbg->config_dir, + hdev->driver_data, &btmrvl_gpiogap_fops); + dbg->hsmode = debugfs_create_file("hsmode", 0644, dbg->config_dir, + hdev->driver_data, &btmrvl_hsmode_fops); + dbg->hscmd = debugfs_create_file("hscmd", 0644, dbg->config_dir, + hdev->driver_data, &btmrvl_hscmd_fops); + dbg->hscfgcmd = debugfs_create_file("hscfgcmd", 0644, dbg->config_dir, + hdev->driver_data, &btmrvl_hscfgcmd_fops); + + dbg->status_dir = debugfs_create_dir("status", hdev->debugfs); + dbg->curpsmode = debugfs_create_file("curpsmode", 0444, + dbg->status_dir, + hdev->driver_data, + &btmrvl_curpsmode_fops); + dbg->psstate = debugfs_create_file("psstate", 0444, dbg->status_dir, + hdev->driver_data, &btmrvl_psstate_fops); + dbg->hsstate = debugfs_create_file("hsstate", 0444, dbg->status_dir, + hdev->driver_data, &btmrvl_hsstate_fops); + dbg->txdnldready = debugfs_create_file("txdnldready", 0444, + dbg->status_dir, + hdev->driver_data, + &btmrvl_txdnldready_fops); +} + +void btmrvl_debugfs_remove(struct hci_dev *hdev) +{ + struct btmrvl_private *priv = hdev->driver_data; + struct btmrvl_debugfs_data *dbg = priv->debugfs_data; + + if (!dbg) + return; + + debugfs_remove(dbg->psmode); + debugfs_remove(dbg->pscmd); + debugfs_remove(dbg->gpiogap); + debugfs_remove(dbg->hsmode); + debugfs_remove(dbg->hscmd); + debugfs_remove(dbg->hscfgcmd); + debugfs_remove(dbg->config_dir); + + debugfs_remove(dbg->curpsmode); + debugfs_remove(dbg->psstate); + debugfs_remove(dbg->hsstate); + debugfs_remove(dbg->txdnldready); + debugfs_remove(dbg->status_dir); + + kfree(dbg); +} diff --git a/drivers/bluetooth/btmrvl_drv.h b/drivers/bluetooth/btmrvl_drv.h new file mode 100644 index 00000000..90bda50d --- /dev/null +++ b/drivers/bluetooth/btmrvl_drv.h @@ -0,0 +1,148 @@ +/* + * Marvell Bluetooth driver: global definitions & declarations + * + * Copyright (C) 2009, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the + * worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + * + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + * + */ + +#include <linux/kthread.h> +#include <linux/bitops.h> +#include <linux/slab.h> +#include <net/bluetooth/bluetooth.h> + +#define BTM_HEADER_LEN 4 +#define BTM_UPLD_SIZE 2312 + +/* Time to wait until Host Sleep state change in millisecond */ +#define WAIT_UNTIL_HS_STATE_CHANGED 5000 +/* Time to wait for command response in millisecond */ +#define WAIT_UNTIL_CMD_RESP 5000 + +struct btmrvl_thread { + struct task_struct *task; + wait_queue_head_t wait_q; + void *priv; +}; + +struct btmrvl_device { + void *card; + struct hci_dev *hcidev; + + u8 dev_type; + + u8 tx_dnld_rdy; + + u8 psmode; + u8 pscmd; + u8 hsmode; + u8 hscmd; + + /* Low byte is gap, high byte is GPIO */ + u16 gpio_gap; + + u8 hscfgcmd; + u8 sendcmdflag; +}; + +struct btmrvl_adapter { + u32 int_count; + struct sk_buff_head tx_queue; + u8 psmode; + u8 ps_state; + u8 hs_state; + u8 wakeup_tries; + wait_queue_head_t cmd_wait_q; + u8 cmd_complete; +}; + +struct btmrvl_private { + struct btmrvl_device btmrvl_dev; + struct btmrvl_adapter *adapter; + struct btmrvl_thread main_thread; + int (*hw_host_to_card) (struct btmrvl_private *priv, + u8 *payload, u16 nb); + int (*hw_wakeup_firmware) (struct btmrvl_private *priv); + int (*hw_process_int_status) (struct btmrvl_private *priv); + spinlock_t driver_lock; /* spinlock used by driver */ +#ifdef CONFIG_DEBUG_FS + void *debugfs_data; +#endif +}; + +#define MRVL_VENDOR_PKT 0xFE + +/* Bluetooth commands */ +#define BT_CMD_AUTO_SLEEP_MODE 0x23 +#define BT_CMD_HOST_SLEEP_CONFIG 0x59 +#define BT_CMD_HOST_SLEEP_ENABLE 0x5A +#define BT_CMD_MODULE_CFG_REQ 0x5B + +/* Sub-commands: Module Bringup/Shutdown Request/Response */ +#define MODULE_BRINGUP_REQ 0xF1 +#define MODULE_BROUGHT_UP 0x00 +#define MODULE_ALREADY_UP 0x0C + +#define MODULE_SHUTDOWN_REQ 0xF2 + +#define BT_EVENT_POWER_STATE 0x20 + +/* Bluetooth Power States */ +#define BT_PS_ENABLE 0x02 +#define BT_PS_DISABLE 0x03 +#define BT_PS_SLEEP 0x01 + +#define OGF 0x3F + +/* Host Sleep states */ +#define HS_ACTIVATED 0x01 +#define HS_DEACTIVATED 0x00 + +/* Power Save modes */ +#define PS_SLEEP 0x01 +#define PS_AWAKE 0x00 + +struct btmrvl_cmd { + __le16 ocf_ogf; + u8 length; + u8 data[4]; +} __packed; + +struct btmrvl_event { + u8 ec; /* event counter */ + u8 length; + u8 data[4]; +} __packed; + +/* Prototype of global function */ + +int btmrvl_register_hdev(struct btmrvl_private *priv); +struct btmrvl_private *btmrvl_add_card(void *card); +int btmrvl_remove_card(struct btmrvl_private *priv); + +void btmrvl_interrupt(struct btmrvl_private *priv); + +void btmrvl_check_evtpkt(struct btmrvl_private *priv, struct sk_buff *skb); +int btmrvl_process_event(struct btmrvl_private *priv, struct sk_buff *skb); + +int btmrvl_send_module_cfg_cmd(struct btmrvl_private *priv, int subcmd); +int btmrvl_enable_ps(struct btmrvl_private *priv); +int btmrvl_prepare_command(struct btmrvl_private *priv); + +#ifdef CONFIG_DEBUG_FS +void btmrvl_debugfs_init(struct hci_dev *hdev); +void btmrvl_debugfs_remove(struct hci_dev *hdev); +#endif diff --git a/drivers/bluetooth/btmrvl_main.c b/drivers/bluetooth/btmrvl_main.c new file mode 100644 index 00000000..548d1d9e --- /dev/null +++ b/drivers/bluetooth/btmrvl_main.c @@ -0,0 +1,660 @@ +/** + * Marvell Bluetooth driver + * + * Copyright (C) 2009, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the + * worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + * + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + **/ + +#include <net/bluetooth/bluetooth.h> +#include <net/bluetooth/hci_core.h> + +#include "btmrvl_drv.h" + +#define VERSION "1.0" + +/* + * This function is called by interface specific interrupt handler. + * It updates Power Save & Host Sleep states, and wakes up the main + * thread. + */ +void btmrvl_interrupt(struct btmrvl_private *priv) +{ + priv->adapter->ps_state = PS_AWAKE; + + priv->adapter->wakeup_tries = 0; + + priv->adapter->int_count++; + + wake_up_interruptible(&priv->main_thread.wait_q); +} +EXPORT_SYMBOL_GPL(btmrvl_interrupt); + +void btmrvl_check_evtpkt(struct btmrvl_private *priv, struct sk_buff *skb) +{ + struct hci_event_hdr *hdr = (void *) skb->data; + struct hci_ev_cmd_complete *ec; + u16 opcode, ocf; + + if (hdr->evt == HCI_EV_CMD_COMPLETE) { + ec = (void *) (skb->data + HCI_EVENT_HDR_SIZE); + opcode = __le16_to_cpu(ec->opcode); + ocf = hci_opcode_ocf(opcode); + if (ocf == BT_CMD_MODULE_CFG_REQ && + priv->btmrvl_dev.sendcmdflag) { + priv->btmrvl_dev.sendcmdflag = false; + priv->adapter->cmd_complete = true; + wake_up_interruptible(&priv->adapter->cmd_wait_q); + } + } +} +EXPORT_SYMBOL_GPL(btmrvl_check_evtpkt); + +int btmrvl_process_event(struct btmrvl_private *priv, struct sk_buff *skb) +{ + struct btmrvl_adapter *adapter = priv->adapter; + struct btmrvl_event *event; + int ret = 0; + + event = (struct btmrvl_event *) skb->data; + if (event->ec != 0xff) { + BT_DBG("Not Marvell Event=%x", event->ec); + ret = -EINVAL; + goto exit; + } + + switch (event->data[0]) { + case BT_CMD_AUTO_SLEEP_MODE: + if (!event->data[2]) { + if (event->data[1] == BT_PS_ENABLE) + adapter->psmode = 1; + else + adapter->psmode = 0; + BT_DBG("PS Mode:%s", + (adapter->psmode) ? "Enable" : "Disable"); + } else { + BT_DBG("PS Mode command failed"); + } + break; + + case BT_CMD_HOST_SLEEP_CONFIG: + if (!event->data[3]) + BT_DBG("gpio=%x, gap=%x", event->data[1], + event->data[2]); + else + BT_DBG("HSCFG command failed"); + break; + + case BT_CMD_HOST_SLEEP_ENABLE: + if (!event->data[1]) { + adapter->hs_state = HS_ACTIVATED; + if (adapter->psmode) + adapter->ps_state = PS_SLEEP; + wake_up_interruptible(&adapter->cmd_wait_q); + BT_DBG("HS ACTIVATED!"); + } else { + BT_DBG("HS Enable failed"); + } + break; + + case BT_CMD_MODULE_CFG_REQ: + if (priv->btmrvl_dev.sendcmdflag && + event->data[1] == MODULE_BRINGUP_REQ) { + BT_DBG("EVENT:%s", + ((event->data[2] == MODULE_BROUGHT_UP) || + (event->data[2] == MODULE_ALREADY_UP)) ? + "Bring-up succeed" : "Bring-up failed"); + + if (event->length > 3 && event->data[3]) + priv->btmrvl_dev.dev_type = HCI_AMP; + else + priv->btmrvl_dev.dev_type = HCI_BREDR; + + BT_DBG("dev_type: %d", priv->btmrvl_dev.dev_type); + } else if (priv->btmrvl_dev.sendcmdflag && + event->data[1] == MODULE_SHUTDOWN_REQ) { + BT_DBG("EVENT:%s", (event->data[2]) ? + "Shutdown failed" : "Shutdown succeed"); + } else { + BT_DBG("BT_CMD_MODULE_CFG_REQ resp for APP"); + ret = -EINVAL; + } + break; + + case BT_EVENT_POWER_STATE: + if (event->data[1] == BT_PS_SLEEP) + adapter->ps_state = PS_SLEEP; + BT_DBG("EVENT:%s", + (adapter->ps_state) ? "PS_SLEEP" : "PS_AWAKE"); + break; + + default: + BT_DBG("Unknown Event=%d", event->data[0]); + ret = -EINVAL; + break; + } + +exit: + if (!ret) + kfree_skb(skb); + + return ret; +} +EXPORT_SYMBOL_GPL(btmrvl_process_event); + +int btmrvl_send_module_cfg_cmd(struct btmrvl_private *priv, int subcmd) +{ + struct sk_buff *skb; + struct btmrvl_cmd *cmd; + int ret = 0; + + skb = bt_skb_alloc(sizeof(*cmd), GFP_ATOMIC); + if (skb == NULL) { + BT_ERR("No free skb"); + return -ENOMEM; + } + + cmd = (struct btmrvl_cmd *) skb_put(skb, sizeof(*cmd)); + cmd->ocf_ogf = cpu_to_le16(hci_opcode_pack(OGF, BT_CMD_MODULE_CFG_REQ)); + cmd->length = 1; + cmd->data[0] = subcmd; + + bt_cb(skb)->pkt_type = MRVL_VENDOR_PKT; + + skb->dev = (void *) priv->btmrvl_dev.hcidev; + skb_queue_head(&priv->adapter->tx_queue, skb); + + priv->btmrvl_dev.sendcmdflag = true; + + priv->adapter->cmd_complete = false; + + BT_DBG("Queue module cfg Command"); + + wake_up_interruptible(&priv->main_thread.wait_q); + + if (!wait_event_interruptible_timeout(priv->adapter->cmd_wait_q, + priv->adapter->cmd_complete, + msecs_to_jiffies(WAIT_UNTIL_CMD_RESP))) { + ret = -ETIMEDOUT; + BT_ERR("module_cfg_cmd(%x): timeout: %d", + subcmd, priv->btmrvl_dev.sendcmdflag); + } + + BT_DBG("module cfg Command done"); + + return ret; +} +EXPORT_SYMBOL_GPL(btmrvl_send_module_cfg_cmd); + +int btmrvl_enable_ps(struct btmrvl_private *priv) +{ + struct sk_buff *skb; + struct btmrvl_cmd *cmd; + + skb = bt_skb_alloc(sizeof(*cmd), GFP_ATOMIC); + if (skb == NULL) { + BT_ERR("No free skb"); + return -ENOMEM; + } + + cmd = (struct btmrvl_cmd *) skb_put(skb, sizeof(*cmd)); + cmd->ocf_ogf = cpu_to_le16(hci_opcode_pack(OGF, + BT_CMD_AUTO_SLEEP_MODE)); + cmd->length = 1; + + if (priv->btmrvl_dev.psmode) + cmd->data[0] = BT_PS_ENABLE; + else + cmd->data[0] = BT_PS_DISABLE; + + bt_cb(skb)->pkt_type = MRVL_VENDOR_PKT; + + skb->dev = (void *) priv->btmrvl_dev.hcidev; + skb_queue_head(&priv->adapter->tx_queue, skb); + + BT_DBG("Queue PSMODE Command:%d", cmd->data[0]); + + return 0; +} +EXPORT_SYMBOL_GPL(btmrvl_enable_ps); + +static int btmrvl_enable_hs(struct btmrvl_private *priv) +{ + struct sk_buff *skb; + struct btmrvl_cmd *cmd; + int ret = 0; + + skb = bt_skb_alloc(sizeof(*cmd), GFP_ATOMIC); + if (skb == NULL) { + BT_ERR("No free skb"); + return -ENOMEM; + } + + cmd = (struct btmrvl_cmd *) skb_put(skb, sizeof(*cmd)); + cmd->ocf_ogf = cpu_to_le16(hci_opcode_pack(OGF, BT_CMD_HOST_SLEEP_ENABLE)); + cmd->length = 0; + + bt_cb(skb)->pkt_type = MRVL_VENDOR_PKT; + + skb->dev = (void *) priv->btmrvl_dev.hcidev; + skb_queue_head(&priv->adapter->tx_queue, skb); + + BT_DBG("Queue hs enable Command"); + + wake_up_interruptible(&priv->main_thread.wait_q); + + if (!wait_event_interruptible_timeout(priv->adapter->cmd_wait_q, + priv->adapter->hs_state, + msecs_to_jiffies(WAIT_UNTIL_HS_STATE_CHANGED))) { + ret = -ETIMEDOUT; + BT_ERR("timeout: %d, %d,%d", priv->adapter->hs_state, + priv->adapter->ps_state, + priv->adapter->wakeup_tries); + } + + return ret; +} + +int btmrvl_prepare_command(struct btmrvl_private *priv) +{ + struct sk_buff *skb = NULL; + struct btmrvl_cmd *cmd; + int ret = 0; + + if (priv->btmrvl_dev.hscfgcmd) { + priv->btmrvl_dev.hscfgcmd = 0; + + skb = bt_skb_alloc(sizeof(*cmd), GFP_ATOMIC); + if (skb == NULL) { + BT_ERR("No free skb"); + return -ENOMEM; + } + + cmd = (struct btmrvl_cmd *) skb_put(skb, sizeof(*cmd)); + cmd->ocf_ogf = cpu_to_le16(hci_opcode_pack(OGF, BT_CMD_HOST_SLEEP_CONFIG)); + cmd->length = 2; + cmd->data[0] = (priv->btmrvl_dev.gpio_gap & 0xff00) >> 8; + cmd->data[1] = (u8) (priv->btmrvl_dev.gpio_gap & 0x00ff); + + bt_cb(skb)->pkt_type = MRVL_VENDOR_PKT; + + skb->dev = (void *) priv->btmrvl_dev.hcidev; + skb_queue_head(&priv->adapter->tx_queue, skb); + + BT_DBG("Queue HSCFG Command, gpio=0x%x, gap=0x%x", + cmd->data[0], cmd->data[1]); + } + + if (priv->btmrvl_dev.pscmd) { + priv->btmrvl_dev.pscmd = 0; + btmrvl_enable_ps(priv); + } + + if (priv->btmrvl_dev.hscmd) { + priv->btmrvl_dev.hscmd = 0; + + if (priv->btmrvl_dev.hsmode) { + ret = btmrvl_enable_hs(priv); + } else { + ret = priv->hw_wakeup_firmware(priv); + priv->adapter->hs_state = HS_DEACTIVATED; + } + } + + return ret; +} + +static int btmrvl_tx_pkt(struct btmrvl_private *priv, struct sk_buff *skb) +{ + int ret = 0; + + if (!skb || !skb->data) + return -EINVAL; + + if (!skb->len || ((skb->len + BTM_HEADER_LEN) > BTM_UPLD_SIZE)) { + BT_ERR("Tx Error: Bad skb length %d : %d", + skb->len, BTM_UPLD_SIZE); + return -EINVAL; + } + + if (skb_headroom(skb) < BTM_HEADER_LEN) { + struct sk_buff *tmp = skb; + + skb = skb_realloc_headroom(skb, BTM_HEADER_LEN); + if (!skb) { + BT_ERR("Tx Error: realloc_headroom failed %d", + BTM_HEADER_LEN); + skb = tmp; + return -EINVAL; + } + + kfree_skb(tmp); + } + + skb_push(skb, BTM_HEADER_LEN); + + /* header type: byte[3] + * HCI_COMMAND = 1, ACL_DATA = 2, SCO_DATA = 3, 0xFE = Vendor + * header length: byte[2][1][0] + */ + + skb->data[0] = (skb->len & 0x0000ff); + skb->data[1] = (skb->len & 0x00ff00) >> 8; + skb->data[2] = (skb->len & 0xff0000) >> 16; + skb->data[3] = bt_cb(skb)->pkt_type; + + if (priv->hw_host_to_card) + ret = priv->hw_host_to_card(priv, skb->data, skb->len); + + return ret; +} + +static void btmrvl_init_adapter(struct btmrvl_private *priv) +{ + skb_queue_head_init(&priv->adapter->tx_queue); + + priv->adapter->ps_state = PS_AWAKE; + + init_waitqueue_head(&priv->adapter->cmd_wait_q); +} + +static void btmrvl_free_adapter(struct btmrvl_private *priv) +{ + skb_queue_purge(&priv->adapter->tx_queue); + + kfree(priv->adapter); + + priv->adapter = NULL; +} + +static int btmrvl_ioctl(struct hci_dev *hdev, + unsigned int cmd, unsigned long arg) +{ + return -ENOIOCTLCMD; +} + +static void btmrvl_destruct(struct hci_dev *hdev) +{ +} + +static int btmrvl_send_frame(struct sk_buff *skb) +{ + struct hci_dev *hdev = (struct hci_dev *) skb->dev; + struct btmrvl_private *priv = NULL; + + BT_DBG("type=%d, len=%d", skb->pkt_type, skb->len); + + if (!hdev || !hdev->driver_data) { + BT_ERR("Frame for unknown HCI device"); + return -ENODEV; + } + + priv = (struct btmrvl_private *) hdev->driver_data; + if (!test_bit(HCI_RUNNING, &hdev->flags)) { + BT_ERR("Failed testing HCI_RUNING, flags=%lx", hdev->flags); + print_hex_dump_bytes("data: ", DUMP_PREFIX_OFFSET, + skb->data, skb->len); + return -EBUSY; + } + + switch (bt_cb(skb)->pkt_type) { + case HCI_COMMAND_PKT: + hdev->stat.cmd_tx++; + break; + + case HCI_ACLDATA_PKT: + hdev->stat.acl_tx++; + break; + + case HCI_SCODATA_PKT: + hdev->stat.sco_tx++; + break; + } + + skb_queue_tail(&priv->adapter->tx_queue, skb); + + wake_up_interruptible(&priv->main_thread.wait_q); + + return 0; +} + +static int btmrvl_flush(struct hci_dev *hdev) +{ + struct btmrvl_private *priv = hdev->driver_data; + + skb_queue_purge(&priv->adapter->tx_queue); + + return 0; +} + +static int btmrvl_close(struct hci_dev *hdev) +{ + struct btmrvl_private *priv = hdev->driver_data; + + if (!test_and_clear_bit(HCI_RUNNING, &hdev->flags)) + return 0; + + skb_queue_purge(&priv->adapter->tx_queue); + + return 0; +} + +static int btmrvl_open(struct hci_dev *hdev) +{ + set_bit(HCI_RUNNING, &hdev->flags); + + return 0; +} + +/* + * This function handles the event generated by firmware, rx data + * received from firmware, and tx data sent from kernel. + */ +static int btmrvl_service_main_thread(void *data) +{ + struct btmrvl_thread *thread = data; + struct btmrvl_private *priv = thread->priv; + struct btmrvl_adapter *adapter = priv->adapter; + wait_queue_t wait; + struct sk_buff *skb; + ulong flags; + + init_waitqueue_entry(&wait, current); + + current->flags |= PF_NOFREEZE; + + for (;;) { + add_wait_queue(&thread->wait_q, &wait); + + set_current_state(TASK_INTERRUPTIBLE); + + if (adapter->wakeup_tries || + ((!adapter->int_count) && + (!priv->btmrvl_dev.tx_dnld_rdy || + skb_queue_empty(&adapter->tx_queue)))) { + BT_DBG("main_thread is sleeping..."); + schedule(); + } + + set_current_state(TASK_RUNNING); + + remove_wait_queue(&thread->wait_q, &wait); + + BT_DBG("main_thread woke up"); + + if (kthread_should_stop()) { + BT_DBG("main_thread: break from main thread"); + break; + } + + spin_lock_irqsave(&priv->driver_lock, flags); + if (adapter->int_count) { + adapter->int_count = 0; + spin_unlock_irqrestore(&priv->driver_lock, flags); + priv->hw_process_int_status(priv); + } else if (adapter->ps_state == PS_SLEEP && + !skb_queue_empty(&adapter->tx_queue)) { + spin_unlock_irqrestore(&priv->driver_lock, flags); + adapter->wakeup_tries++; + priv->hw_wakeup_firmware(priv); + continue; + } else { + spin_unlock_irqrestore(&priv->driver_lock, flags); + } + + if (adapter->ps_state == PS_SLEEP) + continue; + + if (!priv->btmrvl_dev.tx_dnld_rdy) + continue; + + skb = skb_dequeue(&adapter->tx_queue); + if (skb) { + if (btmrvl_tx_pkt(priv, skb)) + priv->btmrvl_dev.hcidev->stat.err_tx++; + else + priv->btmrvl_dev.hcidev->stat.byte_tx += skb->len; + + kfree_skb(skb); + } + } + + return 0; +} + +int btmrvl_register_hdev(struct btmrvl_private *priv) +{ + struct hci_dev *hdev = NULL; + int ret; + + hdev = hci_alloc_dev(); + if (!hdev) { + BT_ERR("Can not allocate HCI device"); + goto err_hdev; + } + + priv->btmrvl_dev.hcidev = hdev; + hdev->driver_data = priv; + + hdev->bus = HCI_SDIO; + hdev->open = btmrvl_open; + hdev->close = btmrvl_close; + hdev->flush = btmrvl_flush; + hdev->send = btmrvl_send_frame; + hdev->destruct = btmrvl_destruct; + hdev->ioctl = btmrvl_ioctl; + hdev->owner = THIS_MODULE; + + btmrvl_send_module_cfg_cmd(priv, MODULE_BRINGUP_REQ); + + hdev->dev_type = priv->btmrvl_dev.dev_type; + + ret = hci_register_dev(hdev); + if (ret < 0) { + BT_ERR("Can not register HCI device"); + goto err_hci_register_dev; + } + +#ifdef CONFIG_DEBUG_FS + btmrvl_debugfs_init(hdev); +#endif + + return 0; + +err_hci_register_dev: + hci_free_dev(hdev); + +err_hdev: + /* Stop the thread servicing the interrupts */ + kthread_stop(priv->main_thread.task); + + btmrvl_free_adapter(priv); + kfree(priv); + + return -ENOMEM; +} +EXPORT_SYMBOL_GPL(btmrvl_register_hdev); + +struct btmrvl_private *btmrvl_add_card(void *card) +{ + struct btmrvl_private *priv; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) { + BT_ERR("Can not allocate priv"); + goto err_priv; + } + + priv->adapter = kzalloc(sizeof(*priv->adapter), GFP_KERNEL); + if (!priv->adapter) { + BT_ERR("Allocate buffer for btmrvl_adapter failed!"); + goto err_adapter; + } + + btmrvl_init_adapter(priv); + + BT_DBG("Starting kthread..."); + priv->main_thread.priv = priv; + spin_lock_init(&priv->driver_lock); + + init_waitqueue_head(&priv->main_thread.wait_q); + priv->main_thread.task = kthread_run(btmrvl_service_main_thread, + &priv->main_thread, "btmrvl_main_service"); + + priv->btmrvl_dev.card = card; + priv->btmrvl_dev.tx_dnld_rdy = true; + + return priv; + +err_adapter: + kfree(priv); + +err_priv: + return NULL; +} +EXPORT_SYMBOL_GPL(btmrvl_add_card); + +int btmrvl_remove_card(struct btmrvl_private *priv) +{ + struct hci_dev *hdev; + + hdev = priv->btmrvl_dev.hcidev; + + wake_up_interruptible(&priv->adapter->cmd_wait_q); + + kthread_stop(priv->main_thread.task); + +#ifdef CONFIG_DEBUG_FS + btmrvl_debugfs_remove(hdev); +#endif + + hci_unregister_dev(hdev); + + hci_free_dev(hdev); + + priv->btmrvl_dev.hcidev = NULL; + + btmrvl_free_adapter(priv); + + kfree(priv); + + return 0; +} +EXPORT_SYMBOL_GPL(btmrvl_remove_card); + +MODULE_AUTHOR("Marvell International Ltd."); +MODULE_DESCRIPTION("Marvell Bluetooth driver ver " VERSION); +MODULE_VERSION(VERSION); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/bluetooth/btmrvl_sdio.c b/drivers/bluetooth/btmrvl_sdio.c new file mode 100644 index 00000000..7f521d4a --- /dev/null +++ b/drivers/bluetooth/btmrvl_sdio.c @@ -0,0 +1,1077 @@ +/** + * Marvell BT-over-SDIO driver: SDIO interface related functions. + * + * Copyright (C) 2009, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the + * worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + * + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + **/ + +#include <linux/firmware.h> +#include <linux/slab.h> + +#include <linux/mmc/sdio_ids.h> +#include <linux/mmc/sdio_func.h> + +#include <net/bluetooth/bluetooth.h> +#include <net/bluetooth/hci_core.h> + +#include "btmrvl_drv.h" +#include "btmrvl_sdio.h" + +#define VERSION "1.0" + +/* The btmrvl_sdio_remove() callback function is called + * when user removes this module from kernel space or ejects + * the card from the slot. The driver handles these 2 cases + * differently. + * If the user is removing the module, a MODULE_SHUTDOWN_REQ + * command is sent to firmware and interrupt will be disabled. + * If the card is removed, there is no need to send command + * or disable interrupt. + * + * The variable 'user_rmmod' is used to distinguish these two + * scenarios. This flag is initialized as FALSE in case the card + * is removed, and will be set to TRUE for module removal when + * module_exit function is called. + */ +static u8 user_rmmod; +static u8 sdio_ireg; + +static const struct btmrvl_sdio_card_reg btmrvl_reg_8688 = { + .cfg = 0x03, + .host_int_mask = 0x04, + .host_intstatus = 0x05, + .card_status = 0x20, + .sq_read_base_addr_a0 = 0x10, + .sq_read_base_addr_a1 = 0x11, + .card_fw_status0 = 0x40, + .card_fw_status1 = 0x41, + .card_rx_len = 0x42, + .card_rx_unit = 0x43, + .io_port_0 = 0x00, + .io_port_1 = 0x01, + .io_port_2 = 0x02, +}; +static const struct btmrvl_sdio_card_reg btmrvl_reg_8787 = { + .cfg = 0x00, + .host_int_mask = 0x02, + .host_intstatus = 0x03, + .card_status = 0x30, + .sq_read_base_addr_a0 = 0x40, + .sq_read_base_addr_a1 = 0x41, + .card_revision = 0x5c, + .card_fw_status0 = 0x60, + .card_fw_status1 = 0x61, + .card_rx_len = 0x62, + .card_rx_unit = 0x63, + .io_port_0 = 0x78, + .io_port_1 = 0x79, + .io_port_2 = 0x7a, +}; + +static const struct btmrvl_sdio_device btmrvl_sdio_sd6888 = { + .helper = "sd8688_helper.bin", + .firmware = "sd8688.bin", + .reg = &btmrvl_reg_8688, + .sd_blksz_fw_dl = 64, +}; + +static const struct btmrvl_sdio_device btmrvl_sdio_sd8787 = { + .helper = NULL, + .firmware = "mrvl/sd8787_uapsta.bin", + .reg = &btmrvl_reg_8787, + .sd_blksz_fw_dl = 256, +}; + +static const struct sdio_device_id btmrvl_sdio_ids[] = { + /* Marvell SD8688 Bluetooth device */ + { SDIO_DEVICE(SDIO_VENDOR_ID_MARVELL, 0x9105), + .driver_data = (unsigned long) &btmrvl_sdio_sd6888 }, + /* Marvell SD8787 Bluetooth device */ + { SDIO_DEVICE(SDIO_VENDOR_ID_MARVELL, 0x911A), + .driver_data = (unsigned long) &btmrvl_sdio_sd8787 }, + + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(sdio, btmrvl_sdio_ids); + +static int btmrvl_sdio_get_rx_unit(struct btmrvl_sdio_card *card) +{ + u8 reg; + int ret; + + reg = sdio_readb(card->func, card->reg->card_rx_unit, &ret); + if (!ret) + card->rx_unit = reg; + + return ret; +} + +static int btmrvl_sdio_read_fw_status(struct btmrvl_sdio_card *card, u16 *dat) +{ + u8 fws0, fws1; + int ret; + + *dat = 0; + + fws0 = sdio_readb(card->func, card->reg->card_fw_status0, &ret); + if (ret) + return -EIO; + + fws1 = sdio_readb(card->func, card->reg->card_fw_status1, &ret); + if (ret) + return -EIO; + + *dat = (((u16) fws1) << 8) | fws0; + + return 0; +} + +static int btmrvl_sdio_read_rx_len(struct btmrvl_sdio_card *card, u16 *dat) +{ + u8 reg; + int ret; + + reg = sdio_readb(card->func, card->reg->card_rx_len, &ret); + if (!ret) + *dat = (u16) reg << card->rx_unit; + + return ret; +} + +static int btmrvl_sdio_enable_host_int_mask(struct btmrvl_sdio_card *card, + u8 mask) +{ + int ret; + + sdio_writeb(card->func, mask, card->reg->host_int_mask, &ret); + if (ret) { + BT_ERR("Unable to enable the host interrupt!"); + ret = -EIO; + } + + return ret; +} + +static int btmrvl_sdio_disable_host_int_mask(struct btmrvl_sdio_card *card, + u8 mask) +{ + u8 host_int_mask; + int ret; + + host_int_mask = sdio_readb(card->func, card->reg->host_int_mask, &ret); + if (ret) + return -EIO; + + host_int_mask &= ~mask; + + sdio_writeb(card->func, host_int_mask, card->reg->host_int_mask, &ret); + if (ret < 0) { + BT_ERR("Unable to disable the host interrupt!"); + return -EIO; + } + + return 0; +} + +static int btmrvl_sdio_poll_card_status(struct btmrvl_sdio_card *card, u8 bits) +{ + unsigned int tries; + u8 status; + int ret; + + for (tries = 0; tries < MAX_POLL_TRIES * 1000; tries++) { + status = sdio_readb(card->func, card->reg->card_status, &ret); + if (ret) + goto failed; + if ((status & bits) == bits) + return ret; + + udelay(1); + } + + ret = -ETIMEDOUT; + +failed: + BT_ERR("FAILED! ret=%d", ret); + + return ret; +} + +static int btmrvl_sdio_verify_fw_download(struct btmrvl_sdio_card *card, + int pollnum) +{ + int ret = -ETIMEDOUT; + u16 firmwarestat; + unsigned int tries; + + /* Wait for firmware to become ready */ + for (tries = 0; tries < pollnum; tries++) { + if (btmrvl_sdio_read_fw_status(card, &firmwarestat) < 0) + continue; + + if (firmwarestat == FIRMWARE_READY) { + ret = 0; + break; + } else { + msleep(10); + } + } + + return ret; +} + +static int btmrvl_sdio_download_helper(struct btmrvl_sdio_card *card) +{ + const struct firmware *fw_helper = NULL; + const u8 *helper = NULL; + int ret; + void *tmphlprbuf = NULL; + int tmphlprbufsz, hlprblknow, helperlen; + u8 *helperbuf; + u32 tx_len; + + ret = request_firmware(&fw_helper, card->helper, + &card->func->dev); + if ((ret < 0) || !fw_helper) { + BT_ERR("request_firmware(helper) failed, error code = %d", + ret); + ret = -ENOENT; + goto done; + } + + helper = fw_helper->data; + helperlen = fw_helper->size; + + BT_DBG("Downloading helper image (%d bytes), block size %d bytes", + helperlen, SDIO_BLOCK_SIZE); + + tmphlprbufsz = ALIGN_SZ(BTM_UPLD_SIZE, BTSDIO_DMA_ALIGN); + + tmphlprbuf = kzalloc(tmphlprbufsz, GFP_KERNEL); + if (!tmphlprbuf) { + BT_ERR("Unable to allocate buffer for helper." + " Terminating download"); + ret = -ENOMEM; + goto done; + } + + helperbuf = (u8 *) ALIGN_ADDR(tmphlprbuf, BTSDIO_DMA_ALIGN); + + /* Perform helper data transfer */ + tx_len = (FIRMWARE_TRANSFER_NBLOCK * SDIO_BLOCK_SIZE) + - SDIO_HEADER_LEN; + hlprblknow = 0; + + do { + ret = btmrvl_sdio_poll_card_status(card, + CARD_IO_READY | DN_LD_CARD_RDY); + if (ret < 0) { + BT_ERR("Helper download poll status timeout @ %d", + hlprblknow); + goto done; + } + + /* Check if there is more data? */ + if (hlprblknow >= helperlen) + break; + + if (helperlen - hlprblknow < tx_len) + tx_len = helperlen - hlprblknow; + + /* Little-endian */ + helperbuf[0] = ((tx_len & 0x000000ff) >> 0); + helperbuf[1] = ((tx_len & 0x0000ff00) >> 8); + helperbuf[2] = ((tx_len & 0x00ff0000) >> 16); + helperbuf[3] = ((tx_len & 0xff000000) >> 24); + + memcpy(&helperbuf[SDIO_HEADER_LEN], &helper[hlprblknow], + tx_len); + + /* Now send the data */ + ret = sdio_writesb(card->func, card->ioport, helperbuf, + FIRMWARE_TRANSFER_NBLOCK * SDIO_BLOCK_SIZE); + if (ret < 0) { + BT_ERR("IO error during helper download @ %d", + hlprblknow); + goto done; + } + + hlprblknow += tx_len; + } while (true); + + BT_DBG("Transferring helper image EOF block"); + + memset(helperbuf, 0x0, SDIO_BLOCK_SIZE); + + ret = sdio_writesb(card->func, card->ioport, helperbuf, + SDIO_BLOCK_SIZE); + if (ret < 0) { + BT_ERR("IO error in writing helper image EOF block"); + goto done; + } + + ret = 0; + +done: + kfree(tmphlprbuf); + if (fw_helper) + release_firmware(fw_helper); + + return ret; +} + +static int btmrvl_sdio_download_fw_w_helper(struct btmrvl_sdio_card *card) +{ + const struct firmware *fw_firmware = NULL; + const u8 *firmware = NULL; + int firmwarelen, tmpfwbufsz, ret; + unsigned int tries, offset; + u8 base0, base1; + void *tmpfwbuf = NULL; + u8 *fwbuf; + u16 len, blksz_dl = card->sd_blksz_fw_dl; + int txlen = 0, tx_blocks = 0, count = 0; + + ret = request_firmware(&fw_firmware, card->firmware, + &card->func->dev); + if ((ret < 0) || !fw_firmware) { + BT_ERR("request_firmware(firmware) failed, error code = %d", + ret); + ret = -ENOENT; + goto done; + } + + firmware = fw_firmware->data; + firmwarelen = fw_firmware->size; + + BT_DBG("Downloading FW image (%d bytes)", firmwarelen); + + tmpfwbufsz = ALIGN_SZ(BTM_UPLD_SIZE, BTSDIO_DMA_ALIGN); + tmpfwbuf = kzalloc(tmpfwbufsz, GFP_KERNEL); + if (!tmpfwbuf) { + BT_ERR("Unable to allocate buffer for firmware." + " Terminating download"); + ret = -ENOMEM; + goto done; + } + + /* Ensure aligned firmware buffer */ + fwbuf = (u8 *) ALIGN_ADDR(tmpfwbuf, BTSDIO_DMA_ALIGN); + + /* Perform firmware data transfer */ + offset = 0; + do { + ret = btmrvl_sdio_poll_card_status(card, + CARD_IO_READY | DN_LD_CARD_RDY); + if (ret < 0) { + BT_ERR("FW download with helper poll status" + " timeout @ %d", offset); + goto done; + } + + /* Check if there is more data ? */ + if (offset >= firmwarelen) + break; + + for (tries = 0; tries < MAX_POLL_TRIES; tries++) { + base0 = sdio_readb(card->func, + card->reg->sq_read_base_addr_a0, &ret); + if (ret) { + BT_ERR("BASE0 register read failed:" + " base0 = 0x%04X(%d)." + " Terminating download", + base0, base0); + ret = -EIO; + goto done; + } + base1 = sdio_readb(card->func, + card->reg->sq_read_base_addr_a1, &ret); + if (ret) { + BT_ERR("BASE1 register read failed:" + " base1 = 0x%04X(%d)." + " Terminating download", + base1, base1); + ret = -EIO; + goto done; + } + + len = (((u16) base1) << 8) | base0; + if (len) + break; + + udelay(10); + } + + if (!len) + break; + else if (len > BTM_UPLD_SIZE) { + BT_ERR("FW download failure @%d, invalid length %d", + offset, len); + ret = -EINVAL; + goto done; + } + + txlen = len; + + if (len & BIT(0)) { + count++; + if (count > MAX_WRITE_IOMEM_RETRY) { + BT_ERR("FW download failure @%d, " + "over max retry count", offset); + ret = -EIO; + goto done; + } + BT_ERR("FW CRC error indicated by the helper: " + "len = 0x%04X, txlen = %d", len, txlen); + len &= ~BIT(0); + /* Set txlen to 0 so as to resend from same offset */ + txlen = 0; + } else { + count = 0; + + /* Last block ? */ + if (firmwarelen - offset < txlen) + txlen = firmwarelen - offset; + + tx_blocks = (txlen + blksz_dl - 1) / blksz_dl; + + memcpy(fwbuf, &firmware[offset], txlen); + } + + ret = sdio_writesb(card->func, card->ioport, fwbuf, + tx_blocks * blksz_dl); + + if (ret < 0) { + BT_ERR("FW download, writesb(%d) failed @%d", + count, offset); + sdio_writeb(card->func, HOST_CMD53_FIN, + card->reg->cfg, &ret); + if (ret) + BT_ERR("writeb failed (CFG)"); + } + + offset += txlen; + } while (true); + + BT_DBG("FW download over, size %d bytes", offset); + + ret = 0; + +done: + kfree(tmpfwbuf); + + if (fw_firmware) + release_firmware(fw_firmware); + + return ret; +} + +static int btmrvl_sdio_card_to_host(struct btmrvl_private *priv) +{ + u16 buf_len = 0; + int ret, buf_block_len, blksz; + struct sk_buff *skb = NULL; + u32 type; + u8 *payload = NULL; + struct hci_dev *hdev = priv->btmrvl_dev.hcidev; + struct btmrvl_sdio_card *card = priv->btmrvl_dev.card; + + if (!card || !card->func) { + BT_ERR("card or function is NULL!"); + ret = -EINVAL; + goto exit; + } + + /* Read the length of data to be transferred */ + ret = btmrvl_sdio_read_rx_len(card, &buf_len); + if (ret < 0) { + BT_ERR("read rx_len failed"); + ret = -EIO; + goto exit; + } + + blksz = SDIO_BLOCK_SIZE; + buf_block_len = (buf_len + blksz - 1) / blksz; + + if (buf_len <= SDIO_HEADER_LEN + || (buf_block_len * blksz) > ALLOC_BUF_SIZE) { + BT_ERR("invalid packet length: %d", buf_len); + ret = -EINVAL; + goto exit; + } + + /* Allocate buffer */ + skb = bt_skb_alloc(buf_block_len * blksz + BTSDIO_DMA_ALIGN, + GFP_ATOMIC); + if (skb == NULL) { + BT_ERR("No free skb"); + goto exit; + } + + if ((unsigned long) skb->data & (BTSDIO_DMA_ALIGN - 1)) { + skb_put(skb, (unsigned long) skb->data & + (BTSDIO_DMA_ALIGN - 1)); + skb_pull(skb, (unsigned long) skb->data & + (BTSDIO_DMA_ALIGN - 1)); + } + + payload = skb->data; + + ret = sdio_readsb(card->func, payload, card->ioport, + buf_block_len * blksz); + if (ret < 0) { + BT_ERR("readsb failed: %d", ret); + ret = -EIO; + goto exit; + } + + /* This is SDIO specific header length: byte[2][1][0], type: byte[3] + * (HCI_COMMAND = 1, ACL_DATA = 2, SCO_DATA = 3, 0xFE = Vendor) + */ + + buf_len = payload[0]; + buf_len |= (u16) payload[1] << 8; + type = payload[3]; + + switch (type) { + case HCI_ACLDATA_PKT: + case HCI_SCODATA_PKT: + case HCI_EVENT_PKT: + bt_cb(skb)->pkt_type = type; + skb->dev = (void *)hdev; + skb_put(skb, buf_len); + skb_pull(skb, SDIO_HEADER_LEN); + + if (type == HCI_EVENT_PKT) + btmrvl_check_evtpkt(priv, skb); + + hci_recv_frame(skb); + hdev->stat.byte_rx += buf_len; + break; + + case MRVL_VENDOR_PKT: + bt_cb(skb)->pkt_type = HCI_VENDOR_PKT; + skb->dev = (void *)hdev; + skb_put(skb, buf_len); + skb_pull(skb, SDIO_HEADER_LEN); + + if (btmrvl_process_event(priv, skb)) + hci_recv_frame(skb); + + hdev->stat.byte_rx += buf_len; + break; + + default: + BT_ERR("Unknown packet type:%d", type); + print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, payload, + blksz * buf_block_len); + + kfree_skb(skb); + skb = NULL; + break; + } + +exit: + if (ret) { + hdev->stat.err_rx++; + if (skb) + kfree_skb(skb); + } + + return ret; +} + +static int btmrvl_sdio_process_int_status(struct btmrvl_private *priv) +{ + ulong flags; + u8 ireg; + struct btmrvl_sdio_card *card = priv->btmrvl_dev.card; + + spin_lock_irqsave(&priv->driver_lock, flags); + ireg = sdio_ireg; + sdio_ireg = 0; + spin_unlock_irqrestore(&priv->driver_lock, flags); + + sdio_claim_host(card->func); + if (ireg & DN_LD_HOST_INT_STATUS) { + if (priv->btmrvl_dev.tx_dnld_rdy) + BT_DBG("tx_done already received: " + " int_status=0x%x", ireg); + else + priv->btmrvl_dev.tx_dnld_rdy = true; + } + + if (ireg & UP_LD_HOST_INT_STATUS) + btmrvl_sdio_card_to_host(priv); + + sdio_release_host(card->func); + + return 0; +} + +static void btmrvl_sdio_interrupt(struct sdio_func *func) +{ + struct btmrvl_private *priv; + struct btmrvl_sdio_card *card; + ulong flags; + u8 ireg = 0; + int ret; + + card = sdio_get_drvdata(func); + if (!card || !card->priv) { + BT_ERR("sbi_interrupt(%p) card or priv is " + "NULL, card=%p\n", func, card); + return; + } + + priv = card->priv; + + ireg = sdio_readb(card->func, card->reg->host_intstatus, &ret); + if (ret) { + BT_ERR("sdio_readb: read int status register failed"); + return; + } + + if (ireg != 0) { + /* + * DN_LD_HOST_INT_STATUS and/or UP_LD_HOST_INT_STATUS + * Clear the interrupt status register and re-enable the + * interrupt. + */ + BT_DBG("ireg = 0x%x", ireg); + + sdio_writeb(card->func, ~(ireg) & (DN_LD_HOST_INT_STATUS | + UP_LD_HOST_INT_STATUS), + card->reg->host_intstatus, &ret); + if (ret) { + BT_ERR("sdio_writeb: clear int status register failed"); + return; + } + } + + spin_lock_irqsave(&priv->driver_lock, flags); + sdio_ireg |= ireg; + spin_unlock_irqrestore(&priv->driver_lock, flags); + + btmrvl_interrupt(priv); +} + +static int btmrvl_sdio_register_dev(struct btmrvl_sdio_card *card) +{ + struct sdio_func *func; + u8 reg; + int ret = 0; + + if (!card || !card->func) { + BT_ERR("Error: card or function is NULL!"); + ret = -EINVAL; + goto failed; + } + + func = card->func; + + sdio_claim_host(func); + + ret = sdio_enable_func(func); + if (ret) { + BT_ERR("sdio_enable_func() failed: ret=%d", ret); + ret = -EIO; + goto release_host; + } + + ret = sdio_claim_irq(func, btmrvl_sdio_interrupt); + if (ret) { + BT_ERR("sdio_claim_irq failed: ret=%d", ret); + ret = -EIO; + goto disable_func; + } + + ret = sdio_set_block_size(card->func, SDIO_BLOCK_SIZE); + if (ret) { + BT_ERR("cannot set SDIO block size"); + ret = -EIO; + goto release_irq; + } + + reg = sdio_readb(func, card->reg->io_port_0, &ret); + if (ret < 0) { + ret = -EIO; + goto release_irq; + } + + card->ioport = reg; + + reg = sdio_readb(func, card->reg->io_port_1, &ret); + if (ret < 0) { + ret = -EIO; + goto release_irq; + } + + card->ioport |= (reg << 8); + + reg = sdio_readb(func, card->reg->io_port_2, &ret); + if (ret < 0) { + ret = -EIO; + goto release_irq; + } + + card->ioport |= (reg << 16); + + BT_DBG("SDIO FUNC%d IO port: 0x%x", func->num, card->ioport); + + sdio_set_drvdata(func, card); + + sdio_release_host(func); + + return 0; + +release_irq: + sdio_release_irq(func); + +disable_func: + sdio_disable_func(func); + +release_host: + sdio_release_host(func); + +failed: + return ret; +} + +static int btmrvl_sdio_unregister_dev(struct btmrvl_sdio_card *card) +{ + if (card && card->func) { + sdio_claim_host(card->func); + sdio_release_irq(card->func); + sdio_disable_func(card->func); + sdio_release_host(card->func); + sdio_set_drvdata(card->func, NULL); + } + + return 0; +} + +static int btmrvl_sdio_enable_host_int(struct btmrvl_sdio_card *card) +{ + int ret; + + if (!card || !card->func) + return -EINVAL; + + sdio_claim_host(card->func); + + ret = btmrvl_sdio_enable_host_int_mask(card, HIM_ENABLE); + + btmrvl_sdio_get_rx_unit(card); + + sdio_release_host(card->func); + + return ret; +} + +static int btmrvl_sdio_disable_host_int(struct btmrvl_sdio_card *card) +{ + int ret; + + if (!card || !card->func) + return -EINVAL; + + sdio_claim_host(card->func); + + ret = btmrvl_sdio_disable_host_int_mask(card, HIM_DISABLE); + + sdio_release_host(card->func); + + return ret; +} + +static int btmrvl_sdio_host_to_card(struct btmrvl_private *priv, + u8 *payload, u16 nb) +{ + struct btmrvl_sdio_card *card = priv->btmrvl_dev.card; + int ret = 0; + int buf_block_len; + int blksz; + int i = 0; + u8 *buf = NULL; + void *tmpbuf = NULL; + int tmpbufsz; + + if (!card || !card->func) { + BT_ERR("card or function is NULL!"); + return -EINVAL; + } + + buf = payload; + if ((unsigned long) payload & (BTSDIO_DMA_ALIGN - 1)) { + tmpbufsz = ALIGN_SZ(nb, BTSDIO_DMA_ALIGN); + tmpbuf = kzalloc(tmpbufsz, GFP_KERNEL); + if (!tmpbuf) + return -ENOMEM; + buf = (u8 *) ALIGN_ADDR(tmpbuf, BTSDIO_DMA_ALIGN); + memcpy(buf, payload, nb); + } + + blksz = SDIO_BLOCK_SIZE; + buf_block_len = (nb + blksz - 1) / blksz; + + sdio_claim_host(card->func); + + do { + /* Transfer data to card */ + ret = sdio_writesb(card->func, card->ioport, buf, + buf_block_len * blksz); + if (ret < 0) { + i++; + BT_ERR("i=%d writesb failed: %d", i, ret); + print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, + payload, nb); + ret = -EIO; + if (i > MAX_WRITE_IOMEM_RETRY) + goto exit; + } + } while (ret); + + priv->btmrvl_dev.tx_dnld_rdy = false; + +exit: + sdio_release_host(card->func); + kfree(tmpbuf); + + return ret; +} + +static int btmrvl_sdio_download_fw(struct btmrvl_sdio_card *card) +{ + int ret = 0; + u8 fws0; + int pollnum = MAX_POLL_TRIES; + + if (!card || !card->func) { + BT_ERR("card or function is NULL!"); + return -EINVAL; + } + sdio_claim_host(card->func); + + if (!btmrvl_sdio_verify_fw_download(card, 1)) { + BT_DBG("Firmware already downloaded!"); + goto done; + } + + /* Check if other function driver is downloading the firmware */ + fws0 = sdio_readb(card->func, card->reg->card_fw_status0, &ret); + if (ret) { + BT_ERR("Failed to read FW downloading status!"); + ret = -EIO; + goto done; + } + if (fws0) { + BT_DBG("BT not the winner (%#x). Skip FW downloading", fws0); + + /* Give other function more time to download the firmware */ + pollnum *= 10; + } else { + if (card->helper) { + ret = btmrvl_sdio_download_helper(card); + if (ret) { + BT_ERR("Failed to download helper!"); + ret = -EIO; + goto done; + } + } + + if (btmrvl_sdio_download_fw_w_helper(card)) { + BT_ERR("Failed to download firmware!"); + ret = -EIO; + goto done; + } + } + + if (btmrvl_sdio_verify_fw_download(card, pollnum)) { + BT_ERR("FW failed to be active in time!"); + ret = -ETIMEDOUT; + goto done; + } + +done: + sdio_release_host(card->func); + + return ret; +} + +static int btmrvl_sdio_wakeup_fw(struct btmrvl_private *priv) +{ + struct btmrvl_sdio_card *card = priv->btmrvl_dev.card; + int ret = 0; + + if (!card || !card->func) { + BT_ERR("card or function is NULL!"); + return -EINVAL; + } + + sdio_claim_host(card->func); + + sdio_writeb(card->func, HOST_POWER_UP, card->reg->cfg, &ret); + + sdio_release_host(card->func); + + BT_DBG("wake up firmware"); + + return ret; +} + +static int btmrvl_sdio_probe(struct sdio_func *func, + const struct sdio_device_id *id) +{ + int ret = 0; + struct btmrvl_private *priv = NULL; + struct btmrvl_sdio_card *card = NULL; + + BT_INFO("vendor=0x%x, device=0x%x, class=%d, fn=%d", + id->vendor, id->device, id->class, func->num); + + card = kzalloc(sizeof(*card), GFP_KERNEL); + if (!card) { + ret = -ENOMEM; + goto done; + } + + card->func = func; + + if (id->driver_data) { + struct btmrvl_sdio_device *data = (void *) id->driver_data; + card->helper = data->helper; + card->firmware = data->firmware; + card->reg = data->reg; + card->sd_blksz_fw_dl = data->sd_blksz_fw_dl; + } + + if (btmrvl_sdio_register_dev(card) < 0) { + BT_ERR("Failed to register BT device!"); + ret = -ENODEV; + goto free_card; + } + + /* Disable the interrupts on the card */ + btmrvl_sdio_disable_host_int(card); + + if (btmrvl_sdio_download_fw(card)) { + BT_ERR("Downloading firmware failed!"); + ret = -ENODEV; + goto unreg_dev; + } + + msleep(100); + + btmrvl_sdio_enable_host_int(card); + + priv = btmrvl_add_card(card); + if (!priv) { + BT_ERR("Initializing card failed!"); + ret = -ENODEV; + goto disable_host_int; + } + + card->priv = priv; + + /* Initialize the interface specific function pointers */ + priv->hw_host_to_card = btmrvl_sdio_host_to_card; + priv->hw_wakeup_firmware = btmrvl_sdio_wakeup_fw; + priv->hw_process_int_status = btmrvl_sdio_process_int_status; + + if (btmrvl_register_hdev(priv)) { + BT_ERR("Register hdev failed!"); + ret = -ENODEV; + goto disable_host_int; + } + + priv->btmrvl_dev.psmode = 1; + btmrvl_enable_ps(priv); + + return 0; + +disable_host_int: + btmrvl_sdio_disable_host_int(card); +unreg_dev: + btmrvl_sdio_unregister_dev(card); +free_card: + kfree(card); +done: + return ret; +} + +static void btmrvl_sdio_remove(struct sdio_func *func) +{ + struct btmrvl_sdio_card *card; + + if (func) { + card = sdio_get_drvdata(func); + if (card) { + /* Send SHUTDOWN command & disable interrupt + * if user removes the module. + */ + if (user_rmmod) { + btmrvl_send_module_cfg_cmd(card->priv, + MODULE_SHUTDOWN_REQ); + btmrvl_sdio_disable_host_int(card); + } + BT_DBG("unregester dev"); + btmrvl_sdio_unregister_dev(card); + btmrvl_remove_card(card->priv); + kfree(card); + } + } +} + +static struct sdio_driver bt_mrvl_sdio = { + .name = "btmrvl_sdio", + .id_table = btmrvl_sdio_ids, + .probe = btmrvl_sdio_probe, + .remove = btmrvl_sdio_remove, +}; + +static int __init btmrvl_sdio_init_module(void) +{ + if (sdio_register_driver(&bt_mrvl_sdio) != 0) { + BT_ERR("SDIO Driver Registration Failed"); + return -ENODEV; + } + + /* Clear the flag in case user removes the card. */ + user_rmmod = 0; + + return 0; +} + +static void __exit btmrvl_sdio_exit_module(void) +{ + /* Set the flag as user is removing this module. */ + user_rmmod = 1; + + sdio_unregister_driver(&bt_mrvl_sdio); +} + +module_init(btmrvl_sdio_init_module); +module_exit(btmrvl_sdio_exit_module); + +MODULE_AUTHOR("Marvell International Ltd."); +MODULE_DESCRIPTION("Marvell BT-over-SDIO driver ver " VERSION); +MODULE_VERSION(VERSION); +MODULE_LICENSE("GPL v2"); +MODULE_FIRMWARE("sd8688_helper.bin"); +MODULE_FIRMWARE("sd8688.bin"); +MODULE_FIRMWARE("mrvl/sd8787_uapsta.bin"); diff --git a/drivers/bluetooth/btmrvl_sdio.h b/drivers/bluetooth/btmrvl_sdio.h new file mode 100644 index 00000000..43d35a60 --- /dev/null +++ b/drivers/bluetooth/btmrvl_sdio.h @@ -0,0 +1,112 @@ +/** + * Marvell BT-over-SDIO driver: SDIO interface related definitions + * + * Copyright (C) 2009, Marvell International Ltd. + * + * This software file (the "File") is distributed by Marvell International + * Ltd. under the terms of the GNU General Public License Version 2, June 1991 + * (the "License"). You may use, redistribute and/or modify this File in + * accordance with the terms and conditions of the License, a copy of which + * is available by writing to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the + * worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + * + * + * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE + * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE + * ARE EXPRESSLY DISCLAIMED. The License provides additional details about + * this warranty disclaimer. + * + **/ + +#define SDIO_HEADER_LEN 4 + +/* SD block size can not bigger than 64 due to buf size limit in firmware */ +/* define SD block size for data Tx/Rx */ +#define SDIO_BLOCK_SIZE 64 + +/* Number of blocks for firmware transfer */ +#define FIRMWARE_TRANSFER_NBLOCK 2 + +/* This is for firmware specific length */ +#define FW_EXTRA_LEN 36 + +#define MRVDRV_SIZE_OF_CMD_BUFFER (2 * 1024) + +#define MRVDRV_BT_RX_PACKET_BUFFER_SIZE \ + (HCI_MAX_FRAME_SIZE + FW_EXTRA_LEN) + +#define ALLOC_BUF_SIZE (((max_t (int, MRVDRV_BT_RX_PACKET_BUFFER_SIZE, \ + MRVDRV_SIZE_OF_CMD_BUFFER) + SDIO_HEADER_LEN \ + + SDIO_BLOCK_SIZE - 1) / SDIO_BLOCK_SIZE) \ + * SDIO_BLOCK_SIZE) + +/* The number of times to try when polling for status */ +#define MAX_POLL_TRIES 100 + +/* Max retry number of CMD53 write */ +#define MAX_WRITE_IOMEM_RETRY 2 + +/* register bitmasks */ +#define HOST_POWER_UP BIT(1) +#define HOST_CMD53_FIN BIT(2) + +#define HIM_DISABLE 0xff +#define HIM_ENABLE (BIT(0) | BIT(1)) + +#define UP_LD_HOST_INT_STATUS BIT(0) +#define DN_LD_HOST_INT_STATUS BIT(1) + +#define DN_LD_CARD_RDY BIT(0) +#define CARD_IO_READY BIT(3) + +#define FIRMWARE_READY 0xfedc + + +struct btmrvl_sdio_card_reg { + u8 cfg; + u8 host_int_mask; + u8 host_intstatus; + u8 card_status; + u8 sq_read_base_addr_a0; + u8 sq_read_base_addr_a1; + u8 card_revision; + u8 card_fw_status0; + u8 card_fw_status1; + u8 card_rx_len; + u8 card_rx_unit; + u8 io_port_0; + u8 io_port_1; + u8 io_port_2; +}; + +struct btmrvl_sdio_card { + struct sdio_func *func; + u32 ioport; + const char *helper; + const char *firmware; + const struct btmrvl_sdio_card_reg *reg; + u16 sd_blksz_fw_dl; + u8 rx_unit; + struct btmrvl_private *priv; +}; + +struct btmrvl_sdio_device { + const char *helper; + const char *firmware; + const struct btmrvl_sdio_card_reg *reg; + u16 sd_blksz_fw_dl; +}; + + +/* Platform specific DMA alignment */ +#define BTSDIO_DMA_ALIGN 8 + +/* Macros for Data Alignment : size */ +#define ALIGN_SZ(p, a) \ + (((p) + ((a) - 1)) & ~((a) - 1)) + +/* Macros for Data Alignment : address */ +#define ALIGN_ADDR(p, a) \ + ((((unsigned long)(p)) + (((unsigned long)(a)) - 1)) & \ + ~(((unsigned long)(a)) - 1)) diff --git a/drivers/bluetooth/btsdio.c b/drivers/bluetooth/btsdio.c new file mode 100644 index 00000000..792e32d2 --- /dev/null +++ b/drivers/bluetooth/btsdio.c @@ -0,0 +1,408 @@ +/* + * + * Generic Bluetooth SDIO driver + * + * Copyright (C) 2007 Cambridge Silicon Radio Ltd. + * Copyright (C) 2007 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/sched.h> +#include <linux/errno.h> +#include <linux/skbuff.h> + +#include <linux/mmc/sdio_ids.h> +#include <linux/mmc/sdio_func.h> + +#include <net/bluetooth/bluetooth.h> +#include <net/bluetooth/hci_core.h> + +#define VERSION "0.1" + +static const struct sdio_device_id btsdio_table[] = { + /* Generic Bluetooth Type-A SDIO device */ + { SDIO_DEVICE_CLASS(SDIO_CLASS_BT_A) }, + + /* Generic Bluetooth Type-B SDIO device */ + { SDIO_DEVICE_CLASS(SDIO_CLASS_BT_B) }, + + /* Generic Bluetooth AMP controller */ + { SDIO_DEVICE_CLASS(SDIO_CLASS_BT_AMP) }, + + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(sdio, btsdio_table); + +struct btsdio_data { + struct hci_dev *hdev; + struct sdio_func *func; + + struct work_struct work; + + struct sk_buff_head txq; +}; + +#define REG_RDAT 0x00 /* Receiver Data */ +#define REG_TDAT 0x00 /* Transmitter Data */ +#define REG_PC_RRT 0x10 /* Read Packet Control */ +#define REG_PC_WRT 0x11 /* Write Packet Control */ +#define REG_RTC_STAT 0x12 /* Retry Control Status */ +#define REG_RTC_SET 0x12 /* Retry Control Set */ +#define REG_INTRD 0x13 /* Interrupt Indication */ +#define REG_CL_INTRD 0x13 /* Interrupt Clear */ +#define REG_EN_INTRD 0x14 /* Interrupt Enable */ +#define REG_MD_STAT 0x20 /* Bluetooth Mode Status */ + +static int btsdio_tx_packet(struct btsdio_data *data, struct sk_buff *skb) +{ + int err; + + BT_DBG("%s", data->hdev->name); + + /* Prepend Type-A header */ + skb_push(skb, 4); + skb->data[0] = (skb->len & 0x0000ff); + skb->data[1] = (skb->len & 0x00ff00) >> 8; + skb->data[2] = (skb->len & 0xff0000) >> 16; + skb->data[3] = bt_cb(skb)->pkt_type; + + err = sdio_writesb(data->func, REG_TDAT, skb->data, skb->len); + if (err < 0) { + skb_pull(skb, 4); + sdio_writeb(data->func, 0x01, REG_PC_WRT, NULL); + return err; + } + + data->hdev->stat.byte_tx += skb->len; + + kfree_skb(skb); + + return 0; +} + +static void btsdio_work(struct work_struct *work) +{ + struct btsdio_data *data = container_of(work, struct btsdio_data, work); + struct sk_buff *skb; + int err; + + BT_DBG("%s", data->hdev->name); + + sdio_claim_host(data->func); + + while ((skb = skb_dequeue(&data->txq))) { + err = btsdio_tx_packet(data, skb); + if (err < 0) { + data->hdev->stat.err_tx++; + skb_queue_head(&data->txq, skb); + break; + } + } + + sdio_release_host(data->func); +} + +static int btsdio_rx_packet(struct btsdio_data *data) +{ + u8 hdr[4] __attribute__ ((aligned(4))); + struct sk_buff *skb; + int err, len; + + BT_DBG("%s", data->hdev->name); + + err = sdio_readsb(data->func, hdr, REG_RDAT, 4); + if (err < 0) + return err; + + len = hdr[0] | (hdr[1] << 8) | (hdr[2] << 16); + if (len < 4 || len > 65543) + return -EILSEQ; + + skb = bt_skb_alloc(len - 4, GFP_KERNEL); + if (!skb) { + /* Out of memory. Prepare a read retry and just + * return with the expectation that the next time + * we're called we'll have more memory. */ + return -ENOMEM; + } + + skb_put(skb, len - 4); + + err = sdio_readsb(data->func, skb->data, REG_RDAT, len - 4); + if (err < 0) { + kfree_skb(skb); + return err; + } + + data->hdev->stat.byte_rx += len; + + skb->dev = (void *) data->hdev; + bt_cb(skb)->pkt_type = hdr[3]; + + err = hci_recv_frame(skb); + if (err < 0) + return err; + + sdio_writeb(data->func, 0x00, REG_PC_RRT, NULL); + + return 0; +} + +static void btsdio_interrupt(struct sdio_func *func) +{ + struct btsdio_data *data = sdio_get_drvdata(func); + int intrd; + + BT_DBG("%s", data->hdev->name); + + intrd = sdio_readb(func, REG_INTRD, NULL); + if (intrd & 0x01) { + sdio_writeb(func, 0x01, REG_CL_INTRD, NULL); + + if (btsdio_rx_packet(data) < 0) { + data->hdev->stat.err_rx++; + sdio_writeb(data->func, 0x01, REG_PC_RRT, NULL); + } + } +} + +static int btsdio_open(struct hci_dev *hdev) +{ + struct btsdio_data *data = hdev->driver_data; + int err; + + BT_DBG("%s", hdev->name); + + if (test_and_set_bit(HCI_RUNNING, &hdev->flags)) + return 0; + + sdio_claim_host(data->func); + + err = sdio_enable_func(data->func); + if (err < 0) { + clear_bit(HCI_RUNNING, &hdev->flags); + goto release; + } + + err = sdio_claim_irq(data->func, btsdio_interrupt); + if (err < 0) { + sdio_disable_func(data->func); + clear_bit(HCI_RUNNING, &hdev->flags); + goto release; + } + + if (data->func->class == SDIO_CLASS_BT_B) + sdio_writeb(data->func, 0x00, REG_MD_STAT, NULL); + + sdio_writeb(data->func, 0x01, REG_EN_INTRD, NULL); + +release: + sdio_release_host(data->func); + + return err; +} + +static int btsdio_close(struct hci_dev *hdev) +{ + struct btsdio_data *data = hdev->driver_data; + + BT_DBG("%s", hdev->name); + + if (!test_and_clear_bit(HCI_RUNNING, &hdev->flags)) + return 0; + + sdio_claim_host(data->func); + + sdio_writeb(data->func, 0x00, REG_EN_INTRD, NULL); + + sdio_release_irq(data->func); + sdio_disable_func(data->func); + + sdio_release_host(data->func); + + return 0; +} + +static int btsdio_flush(struct hci_dev *hdev) +{ + struct btsdio_data *data = hdev->driver_data; + + BT_DBG("%s", hdev->name); + + skb_queue_purge(&data->txq); + + return 0; +} + +static int btsdio_send_frame(struct sk_buff *skb) +{ + struct hci_dev *hdev = (struct hci_dev *) skb->dev; + struct btsdio_data *data = hdev->driver_data; + + BT_DBG("%s", hdev->name); + + if (!test_bit(HCI_RUNNING, &hdev->flags)) + return -EBUSY; + + switch (bt_cb(skb)->pkt_type) { + case HCI_COMMAND_PKT: + hdev->stat.cmd_tx++; + break; + + case HCI_ACLDATA_PKT: + hdev->stat.acl_tx++; + break; + + case HCI_SCODATA_PKT: + hdev->stat.sco_tx++; + break; + + default: + return -EILSEQ; + } + + skb_queue_tail(&data->txq, skb); + + schedule_work(&data->work); + + return 0; +} + +static void btsdio_destruct(struct hci_dev *hdev) +{ + struct btsdio_data *data = hdev->driver_data; + + BT_DBG("%s", hdev->name); + + kfree(data); +} + +static int btsdio_probe(struct sdio_func *func, + const struct sdio_device_id *id) +{ + struct btsdio_data *data; + struct hci_dev *hdev; + struct sdio_func_tuple *tuple = func->tuples; + int err; + + BT_DBG("func %p id %p class 0x%04x", func, id, func->class); + + while (tuple) { + BT_DBG("code 0x%x size %d", tuple->code, tuple->size); + tuple = tuple->next; + } + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->func = func; + + INIT_WORK(&data->work, btsdio_work); + + skb_queue_head_init(&data->txq); + + hdev = hci_alloc_dev(); + if (!hdev) { + kfree(data); + return -ENOMEM; + } + + hdev->bus = HCI_SDIO; + hdev->driver_data = data; + + if (id->class == SDIO_CLASS_BT_AMP) + hdev->dev_type = HCI_AMP; + else + hdev->dev_type = HCI_BREDR; + + data->hdev = hdev; + + SET_HCIDEV_DEV(hdev, &func->dev); + + hdev->open = btsdio_open; + hdev->close = btsdio_close; + hdev->flush = btsdio_flush; + hdev->send = btsdio_send_frame; + hdev->destruct = btsdio_destruct; + + hdev->owner = THIS_MODULE; + + err = hci_register_dev(hdev); + if (err < 0) { + hci_free_dev(hdev); + kfree(data); + return err; + } + + sdio_set_drvdata(func, data); + + return 0; +} + +static void btsdio_remove(struct sdio_func *func) +{ + struct btsdio_data *data = sdio_get_drvdata(func); + struct hci_dev *hdev; + + BT_DBG("func %p", func); + + if (!data) + return; + + hdev = data->hdev; + + sdio_set_drvdata(func, NULL); + + hci_unregister_dev(hdev); + + hci_free_dev(hdev); +} + +static struct sdio_driver btsdio_driver = { + .name = "btsdio", + .probe = btsdio_probe, + .remove = btsdio_remove, + .id_table = btsdio_table, +}; + +static int __init btsdio_init(void) +{ + BT_INFO("Generic Bluetooth SDIO driver ver %s", VERSION); + + return sdio_register_driver(&btsdio_driver); +} + +static void __exit btsdio_exit(void) +{ + sdio_unregister_driver(&btsdio_driver); +} + +module_init(btsdio_init); +module_exit(btsdio_exit); + +MODULE_AUTHOR("Marcel Holtmann <marcel@holtmann.org>"); +MODULE_DESCRIPTION("Generic Bluetooth SDIO driver ver " VERSION); +MODULE_VERSION(VERSION); +MODULE_LICENSE("GPL"); diff --git a/drivers/bluetooth/btuart_cs.c b/drivers/bluetooth/btuart_cs.c new file mode 100644 index 00000000..526b6180 --- /dev/null +++ b/drivers/bluetooth/btuart_cs.c @@ -0,0 +1,718 @@ +/* + * + * Driver for Bluetooth PCMCIA cards with HCI UART interface + * + * Copyright (C) 2001-2002 Marcel Holtmann <marcel@holtmann.org> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation; + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The initial developer of the original code is David A. Hinds + * <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + * + */ + +#include <linux/module.h> + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/ptrace.h> +#include <linux/ioport.h> +#include <linux/spinlock.h> +#include <linux/moduleparam.h> + +#include <linux/skbuff.h> +#include <linux/string.h> +#include <linux/serial.h> +#include <linux/serial_reg.h> +#include <linux/bitops.h> +#include <asm/system.h> +#include <asm/io.h> + +#include <pcmcia/cistpl.h> +#include <pcmcia/ciscode.h> +#include <pcmcia/ds.h> +#include <pcmcia/cisreg.h> + +#include <net/bluetooth/bluetooth.h> +#include <net/bluetooth/hci_core.h> + + + +/* ======================== Module parameters ======================== */ + + +MODULE_AUTHOR("Marcel Holtmann <marcel@holtmann.org>"); +MODULE_DESCRIPTION("Bluetooth driver for Bluetooth PCMCIA cards with HCI UART interface"); +MODULE_LICENSE("GPL"); + + + +/* ======================== Local structures ======================== */ + + +typedef struct btuart_info_t { + struct pcmcia_device *p_dev; + + struct hci_dev *hdev; + + spinlock_t lock; /* For serializing operations */ + + struct sk_buff_head txq; + unsigned long tx_state; + + unsigned long rx_state; + unsigned long rx_count; + struct sk_buff *rx_skb; +} btuart_info_t; + + +static int btuart_config(struct pcmcia_device *link); +static void btuart_release(struct pcmcia_device *link); + +static void btuart_detach(struct pcmcia_device *p_dev); + + +/* Maximum baud rate */ +#define SPEED_MAX 115200 + +/* Default baud rate: 57600, 115200, 230400 or 460800 */ +#define DEFAULT_BAUD_RATE 115200 + + +/* Transmit states */ +#define XMIT_SENDING 1 +#define XMIT_WAKEUP 2 +#define XMIT_WAITING 8 + +/* Receiver states */ +#define RECV_WAIT_PACKET_TYPE 0 +#define RECV_WAIT_EVENT_HEADER 1 +#define RECV_WAIT_ACL_HEADER 2 +#define RECV_WAIT_SCO_HEADER 3 +#define RECV_WAIT_DATA 4 + + + +/* ======================== Interrupt handling ======================== */ + + +static int btuart_write(unsigned int iobase, int fifo_size, __u8 *buf, int len) +{ + int actual = 0; + + /* Tx FIFO should be empty */ + if (!(inb(iobase + UART_LSR) & UART_LSR_THRE)) + return 0; + + /* Fill FIFO with current frame */ + while ((fifo_size-- > 0) && (actual < len)) { + /* Transmit next byte */ + outb(buf[actual], iobase + UART_TX); + actual++; + } + + return actual; +} + + +static void btuart_write_wakeup(btuart_info_t *info) +{ + if (!info) { + BT_ERR("Unknown device"); + return; + } + + if (test_and_set_bit(XMIT_SENDING, &(info->tx_state))) { + set_bit(XMIT_WAKEUP, &(info->tx_state)); + return; + } + + do { + register unsigned int iobase = info->p_dev->resource[0]->start; + register struct sk_buff *skb; + register int len; + + clear_bit(XMIT_WAKEUP, &(info->tx_state)); + + if (!pcmcia_dev_present(info->p_dev)) + return; + + if (!(skb = skb_dequeue(&(info->txq)))) + break; + + /* Send frame */ + len = btuart_write(iobase, 16, skb->data, skb->len); + set_bit(XMIT_WAKEUP, &(info->tx_state)); + + if (len == skb->len) { + kfree_skb(skb); + } else { + skb_pull(skb, len); + skb_queue_head(&(info->txq), skb); + } + + info->hdev->stat.byte_tx += len; + + } while (test_bit(XMIT_WAKEUP, &(info->tx_state))); + + clear_bit(XMIT_SENDING, &(info->tx_state)); +} + + +static void btuart_receive(btuart_info_t *info) +{ + unsigned int iobase; + int boguscount = 0; + + if (!info) { + BT_ERR("Unknown device"); + return; + } + + iobase = info->p_dev->resource[0]->start; + + do { + info->hdev->stat.byte_rx++; + + /* Allocate packet */ + if (info->rx_skb == NULL) { + info->rx_state = RECV_WAIT_PACKET_TYPE; + info->rx_count = 0; + if (!(info->rx_skb = bt_skb_alloc(HCI_MAX_FRAME_SIZE, GFP_ATOMIC))) { + BT_ERR("Can't allocate mem for new packet"); + return; + } + } + + if (info->rx_state == RECV_WAIT_PACKET_TYPE) { + + info->rx_skb->dev = (void *) info->hdev; + bt_cb(info->rx_skb)->pkt_type = inb(iobase + UART_RX); + + switch (bt_cb(info->rx_skb)->pkt_type) { + + case HCI_EVENT_PKT: + info->rx_state = RECV_WAIT_EVENT_HEADER; + info->rx_count = HCI_EVENT_HDR_SIZE; + break; + + case HCI_ACLDATA_PKT: + info->rx_state = RECV_WAIT_ACL_HEADER; + info->rx_count = HCI_ACL_HDR_SIZE; + break; + + case HCI_SCODATA_PKT: + info->rx_state = RECV_WAIT_SCO_HEADER; + info->rx_count = HCI_SCO_HDR_SIZE; + break; + + default: + /* Unknown packet */ + BT_ERR("Unknown HCI packet with type 0x%02x received", bt_cb(info->rx_skb)->pkt_type); + info->hdev->stat.err_rx++; + clear_bit(HCI_RUNNING, &(info->hdev->flags)); + + kfree_skb(info->rx_skb); + info->rx_skb = NULL; + break; + + } + + } else { + + *skb_put(info->rx_skb, 1) = inb(iobase + UART_RX); + info->rx_count--; + + if (info->rx_count == 0) { + + int dlen; + struct hci_event_hdr *eh; + struct hci_acl_hdr *ah; + struct hci_sco_hdr *sh; + + + switch (info->rx_state) { + + case RECV_WAIT_EVENT_HEADER: + eh = hci_event_hdr(info->rx_skb); + info->rx_state = RECV_WAIT_DATA; + info->rx_count = eh->plen; + break; + + case RECV_WAIT_ACL_HEADER: + ah = hci_acl_hdr(info->rx_skb); + dlen = __le16_to_cpu(ah->dlen); + info->rx_state = RECV_WAIT_DATA; + info->rx_count = dlen; + break; + + case RECV_WAIT_SCO_HEADER: + sh = hci_sco_hdr(info->rx_skb); + info->rx_state = RECV_WAIT_DATA; + info->rx_count = sh->dlen; + break; + + case RECV_WAIT_DATA: + hci_recv_frame(info->rx_skb); + info->rx_skb = NULL; + break; + + } + + } + + } + + /* Make sure we don't stay here too long */ + if (boguscount++ > 16) + break; + + } while (inb(iobase + UART_LSR) & UART_LSR_DR); +} + + +static irqreturn_t btuart_interrupt(int irq, void *dev_inst) +{ + btuart_info_t *info = dev_inst; + unsigned int iobase; + int boguscount = 0; + int iir, lsr; + irqreturn_t r = IRQ_NONE; + + if (!info || !info->hdev) + /* our irq handler is shared */ + return IRQ_NONE; + + iobase = info->p_dev->resource[0]->start; + + spin_lock(&(info->lock)); + + iir = inb(iobase + UART_IIR) & UART_IIR_ID; + while (iir) { + r = IRQ_HANDLED; + + /* Clear interrupt */ + lsr = inb(iobase + UART_LSR); + + switch (iir) { + case UART_IIR_RLSI: + BT_ERR("RLSI"); + break; + case UART_IIR_RDI: + /* Receive interrupt */ + btuart_receive(info); + break; + case UART_IIR_THRI: + if (lsr & UART_LSR_THRE) { + /* Transmitter ready for data */ + btuart_write_wakeup(info); + } + break; + default: + BT_ERR("Unhandled IIR=%#x", iir); + break; + } + + /* Make sure we don't stay here too long */ + if (boguscount++ > 100) + break; + + iir = inb(iobase + UART_IIR) & UART_IIR_ID; + + } + + spin_unlock(&(info->lock)); + + return r; +} + + +static void btuart_change_speed(btuart_info_t *info, unsigned int speed) +{ + unsigned long flags; + unsigned int iobase; + int fcr; /* FIFO control reg */ + int lcr; /* Line control reg */ + int divisor; + + if (!info) { + BT_ERR("Unknown device"); + return; + } + + iobase = info->p_dev->resource[0]->start; + + spin_lock_irqsave(&(info->lock), flags); + + /* Turn off interrupts */ + outb(0, iobase + UART_IER); + + divisor = SPEED_MAX / speed; + + fcr = UART_FCR_ENABLE_FIFO | UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT; + + /* + * Use trigger level 1 to avoid 3 ms. timeout delay at 9600 bps, and + * almost 1,7 ms at 19200 bps. At speeds above that we can just forget + * about this timeout since it will always be fast enough. + */ + + if (speed < 38400) + fcr |= UART_FCR_TRIGGER_1; + else + fcr |= UART_FCR_TRIGGER_14; + + /* Bluetooth cards use 8N1 */ + lcr = UART_LCR_WLEN8; + + outb(UART_LCR_DLAB | lcr, iobase + UART_LCR); /* Set DLAB */ + outb(divisor & 0xff, iobase + UART_DLL); /* Set speed */ + outb(divisor >> 8, iobase + UART_DLM); + outb(lcr, iobase + UART_LCR); /* Set 8N1 */ + outb(fcr, iobase + UART_FCR); /* Enable FIFO's */ + + /* Turn on interrupts */ + outb(UART_IER_RLSI | UART_IER_RDI | UART_IER_THRI, iobase + UART_IER); + + spin_unlock_irqrestore(&(info->lock), flags); +} + + + +/* ======================== HCI interface ======================== */ + + +static int btuart_hci_flush(struct hci_dev *hdev) +{ + btuart_info_t *info = (btuart_info_t *)(hdev->driver_data); + + /* Drop TX queue */ + skb_queue_purge(&(info->txq)); + + return 0; +} + + +static int btuart_hci_open(struct hci_dev *hdev) +{ + set_bit(HCI_RUNNING, &(hdev->flags)); + + return 0; +} + + +static int btuart_hci_close(struct hci_dev *hdev) +{ + if (!test_and_clear_bit(HCI_RUNNING, &(hdev->flags))) + return 0; + + btuart_hci_flush(hdev); + + return 0; +} + + +static int btuart_hci_send_frame(struct sk_buff *skb) +{ + btuart_info_t *info; + struct hci_dev *hdev = (struct hci_dev *)(skb->dev); + + if (!hdev) { + BT_ERR("Frame for unknown HCI device (hdev=NULL)"); + return -ENODEV; + } + + info = (btuart_info_t *)(hdev->driver_data); + + switch (bt_cb(skb)->pkt_type) { + case HCI_COMMAND_PKT: + hdev->stat.cmd_tx++; + break; + case HCI_ACLDATA_PKT: + hdev->stat.acl_tx++; + break; + case HCI_SCODATA_PKT: + hdev->stat.sco_tx++; + break; + }; + + /* Prepend skb with frame type */ + memcpy(skb_push(skb, 1), &bt_cb(skb)->pkt_type, 1); + skb_queue_tail(&(info->txq), skb); + + btuart_write_wakeup(info); + + return 0; +} + + +static void btuart_hci_destruct(struct hci_dev *hdev) +{ +} + + +static int btuart_hci_ioctl(struct hci_dev *hdev, unsigned int cmd, unsigned long arg) +{ + return -ENOIOCTLCMD; +} + + + +/* ======================== Card services HCI interaction ======================== */ + + +static int btuart_open(btuart_info_t *info) +{ + unsigned long flags; + unsigned int iobase = info->p_dev->resource[0]->start; + struct hci_dev *hdev; + + spin_lock_init(&(info->lock)); + + skb_queue_head_init(&(info->txq)); + + info->rx_state = RECV_WAIT_PACKET_TYPE; + info->rx_count = 0; + info->rx_skb = NULL; + + /* Initialize HCI device */ + hdev = hci_alloc_dev(); + if (!hdev) { + BT_ERR("Can't allocate HCI device"); + return -ENOMEM; + } + + info->hdev = hdev; + + hdev->bus = HCI_PCCARD; + hdev->driver_data = info; + SET_HCIDEV_DEV(hdev, &info->p_dev->dev); + + hdev->open = btuart_hci_open; + hdev->close = btuart_hci_close; + hdev->flush = btuart_hci_flush; + hdev->send = btuart_hci_send_frame; + hdev->destruct = btuart_hci_destruct; + hdev->ioctl = btuart_hci_ioctl; + + hdev->owner = THIS_MODULE; + + spin_lock_irqsave(&(info->lock), flags); + + /* Reset UART */ + outb(0, iobase + UART_MCR); + + /* Turn off interrupts */ + outb(0, iobase + UART_IER); + + /* Initialize UART */ + outb(UART_LCR_WLEN8, iobase + UART_LCR); /* Reset DLAB */ + outb((UART_MCR_DTR | UART_MCR_RTS | UART_MCR_OUT2), iobase + UART_MCR); + + /* Turn on interrupts */ + // outb(UART_IER_RLSI | UART_IER_RDI | UART_IER_THRI, iobase + UART_IER); + + spin_unlock_irqrestore(&(info->lock), flags); + + btuart_change_speed(info, DEFAULT_BAUD_RATE); + + /* Timeout before it is safe to send the first HCI packet */ + msleep(1000); + + /* Register HCI device */ + if (hci_register_dev(hdev) < 0) { + BT_ERR("Can't register HCI device"); + info->hdev = NULL; + hci_free_dev(hdev); + return -ENODEV; + } + + return 0; +} + + +static int btuart_close(btuart_info_t *info) +{ + unsigned long flags; + unsigned int iobase = info->p_dev->resource[0]->start; + struct hci_dev *hdev = info->hdev; + + if (!hdev) + return -ENODEV; + + btuart_hci_close(hdev); + + spin_lock_irqsave(&(info->lock), flags); + + /* Reset UART */ + outb(0, iobase + UART_MCR); + + /* Turn off interrupts */ + outb(0, iobase + UART_IER); + + spin_unlock_irqrestore(&(info->lock), flags); + + if (hci_unregister_dev(hdev) < 0) + BT_ERR("Can't unregister HCI device %s", hdev->name); + + hci_free_dev(hdev); + + return 0; +} + +static int btuart_probe(struct pcmcia_device *link) +{ + btuart_info_t *info; + + /* Create new info device */ + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->p_dev = link; + link->priv = info; + + link->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_SET_VPP | + CONF_AUTO_SET_IO; + + return btuart_config(link); +} + + +static void btuart_detach(struct pcmcia_device *link) +{ + btuart_info_t *info = link->priv; + + btuart_release(link); + kfree(info); +} + +static int btuart_check_config(struct pcmcia_device *p_dev, void *priv_data) +{ + int *try = priv_data; + + if (try == 0) + p_dev->io_lines = 16; + + if ((p_dev->resource[0]->end != 8) || (p_dev->resource[0]->start == 0)) + return -EINVAL; + + p_dev->resource[0]->end = 8; + p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH; + p_dev->resource[0]->flags |= IO_DATA_PATH_WIDTH_8; + + return pcmcia_request_io(p_dev); +} + +static int btuart_check_config_notpicky(struct pcmcia_device *p_dev, + void *priv_data) +{ + static unsigned int base[5] = { 0x3f8, 0x2f8, 0x3e8, 0x2e8, 0x0 }; + int j; + + if (p_dev->io_lines > 3) + return -ENODEV; + + p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH; + p_dev->resource[0]->flags |= IO_DATA_PATH_WIDTH_8; + p_dev->resource[0]->end = 8; + + for (j = 0; j < 5; j++) { + p_dev->resource[0]->start = base[j]; + p_dev->io_lines = base[j] ? 16 : 3; + if (!pcmcia_request_io(p_dev)) + return 0; + } + return -ENODEV; +} + +static int btuart_config(struct pcmcia_device *link) +{ + btuart_info_t *info = link->priv; + int i; + int try; + + /* First pass: look for a config entry that looks normal. + Two tries: without IO aliases, then with aliases */ + for (try = 0; try < 2; try++) + if (!pcmcia_loop_config(link, btuart_check_config, &try)) + goto found_port; + + /* Second pass: try to find an entry that isn't picky about + its base address, then try to grab any standard serial port + address, and finally try to get any free port. */ + if (!pcmcia_loop_config(link, btuart_check_config_notpicky, NULL)) + goto found_port; + + BT_ERR("No usable port range found"); + goto failed; + +found_port: + i = pcmcia_request_irq(link, btuart_interrupt); + if (i != 0) + goto failed; + + i = pcmcia_enable_device(link); + if (i != 0) + goto failed; + + if (btuart_open(info) != 0) + goto failed; + + return 0; + +failed: + btuart_release(link); + return -ENODEV; +} + + +static void btuart_release(struct pcmcia_device *link) +{ + btuart_info_t *info = link->priv; + + btuart_close(info); + + pcmcia_disable_device(link); +} + +static const struct pcmcia_device_id btuart_ids[] = { + /* don't use this driver. Use serial_cs + hci_uart instead */ + PCMCIA_DEVICE_NULL +}; +MODULE_DEVICE_TABLE(pcmcia, btuart_ids); + +static struct pcmcia_driver btuart_driver = { + .owner = THIS_MODULE, + .name = "btuart_cs", + .probe = btuart_probe, + .remove = btuart_detach, + .id_table = btuart_ids, +}; + +static int __init init_btuart_cs(void) +{ + return pcmcia_register_driver(&btuart_driver); +} + + +static void __exit exit_btuart_cs(void) +{ + pcmcia_unregister_driver(&btuart_driver); +} + +module_init(init_btuart_cs); +module_exit(exit_btuart_cs); diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c new file mode 100644 index 00000000..f4585b90 --- /dev/null +++ b/drivers/bluetooth/btusb.c @@ -0,0 +1,1267 @@ +/* + * + * Generic Bluetooth USB driver + * + * Copyright (C) 2005-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/sched.h> +#include <linux/errno.h> +#include <linux/skbuff.h> + +#include <linux/usb.h> + +#include <net/bluetooth/bluetooth.h> +#include <net/bluetooth/hci_core.h> + +#define VERSION "0.6" + +static int ignore_dga; +static int ignore_csr; +static int ignore_sniffer; +static int disable_scofix; +static int force_scofix; + +static int reset = 1; + +static struct usb_driver btusb_driver; + +#define BTUSB_IGNORE 0x01 +#define BTUSB_DIGIANSWER 0x02 +#define BTUSB_CSR 0x04 +#define BTUSB_SNIFFER 0x08 +#define BTUSB_BCM92035 0x10 +#define BTUSB_BROKEN_ISOC 0x20 +#define BTUSB_WRONG_SCO_MTU 0x40 +#define BTUSB_ATH3012 0x80 + +static struct usb_device_id btusb_table[] = { + /* Generic Bluetooth USB device */ + { USB_DEVICE_INFO(0xe0, 0x01, 0x01) }, + + /* Broadcom SoftSailing reporting vendor specific */ + { USB_DEVICE(0x0a5c, 0x21e1) }, + + /* Apple MacBookPro 7,1 */ + { USB_DEVICE(0x05ac, 0x8213) }, + + /* Apple iMac11,1 */ + { USB_DEVICE(0x05ac, 0x8215) }, + + /* Apple MacBookPro6,2 */ + { USB_DEVICE(0x05ac, 0x8218) }, + + /* Apple MacBookAir3,1, MacBookAir3,2 */ + { USB_DEVICE(0x05ac, 0x821b) }, + + /* Apple MacBookAir4,1 */ + { USB_DEVICE(0x05ac, 0x821f) }, + + /* Apple MacBookPro8,2 */ + { USB_DEVICE(0x05ac, 0x821a) }, + + /* Apple MacMini5,1 */ + { USB_DEVICE(0x05ac, 0x8281) }, + + /* AVM BlueFRITZ! USB v2.0 */ + { USB_DEVICE(0x057c, 0x3800) }, + + /* Bluetooth Ultraport Module from IBM */ + { USB_DEVICE(0x04bf, 0x030a) }, + + /* ALPS Modules with non-standard id */ + { USB_DEVICE(0x044e, 0x3001) }, + { USB_DEVICE(0x044e, 0x3002) }, + + /* Ericsson with non-standard id */ + { USB_DEVICE(0x0bdb, 0x1002) }, + + /* Canyon CN-BTU1 with HID interfaces */ + { USB_DEVICE(0x0c10, 0x0000) }, + + /* Broadcom BCM20702A0 */ + { USB_DEVICE(0x0489, 0xe042) }, + { USB_DEVICE(0x0a5c, 0x21e3) }, + { USB_DEVICE(0x0a5c, 0x21e6) }, + { USB_DEVICE(0x0a5c, 0x21e8) }, + { USB_DEVICE(0x0a5c, 0x21f3) }, + { USB_DEVICE(0x413c, 0x8197) }, + + /* Foxconn - Hon Hai */ + { USB_DEVICE(0x0489, 0xe033) }, + + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, btusb_table); + +static struct usb_device_id blacklist_table[] = { + /* CSR BlueCore devices */ + { USB_DEVICE(0x0a12, 0x0001), .driver_info = BTUSB_CSR }, + + /* Broadcom BCM2033 without firmware */ + { USB_DEVICE(0x0a5c, 0x2033), .driver_info = BTUSB_IGNORE }, + + /* Atheros 3011 with sflash firmware */ + { USB_DEVICE(0x0cf3, 0x3002), .driver_info = BTUSB_IGNORE }, + { USB_DEVICE(0x13d3, 0x3304), .driver_info = BTUSB_IGNORE }, + { USB_DEVICE(0x0930, 0x0215), .driver_info = BTUSB_IGNORE }, + { USB_DEVICE(0x0489, 0xe03d), .driver_info = BTUSB_IGNORE }, + + /* Atheros AR9285 Malbec with sflash firmware */ + { USB_DEVICE(0x03f0, 0x311d), .driver_info = BTUSB_IGNORE }, + + /* Atheros 3012 with sflash firmware */ + { USB_DEVICE(0x0cf3, 0x3004), .driver_info = BTUSB_ATH3012 }, + { USB_DEVICE(0x0cf3, 0x311d), .driver_info = BTUSB_ATH3012 }, + { USB_DEVICE(0x13d3, 0x3375), .driver_info = BTUSB_ATH3012 }, + { USB_DEVICE(0x04ca, 0x3005), .driver_info = BTUSB_ATH3012 }, + + /* Atheros AR5BBU12 with sflash firmware */ + { USB_DEVICE(0x0489, 0xe02c), .driver_info = BTUSB_IGNORE }, + + /* Atheros AR5BBU12 with sflash firmware */ + { USB_DEVICE(0x0489, 0xe03c), .driver_info = BTUSB_ATH3012 }, + + /* Broadcom BCM2035 */ + { USB_DEVICE(0x0a5c, 0x2035), .driver_info = BTUSB_WRONG_SCO_MTU }, + { USB_DEVICE(0x0a5c, 0x200a), .driver_info = BTUSB_WRONG_SCO_MTU }, + { USB_DEVICE(0x0a5c, 0x2009), .driver_info = BTUSB_BCM92035 }, + + /* Broadcom BCM2045 */ + { USB_DEVICE(0x0a5c, 0x2039), .driver_info = BTUSB_WRONG_SCO_MTU }, + { USB_DEVICE(0x0a5c, 0x2101), .driver_info = BTUSB_WRONG_SCO_MTU }, + + /* IBM/Lenovo ThinkPad with Broadcom chip */ + { USB_DEVICE(0x0a5c, 0x201e), .driver_info = BTUSB_WRONG_SCO_MTU }, + { USB_DEVICE(0x0a5c, 0x2110), .driver_info = BTUSB_WRONG_SCO_MTU }, + + /* HP laptop with Broadcom chip */ + { USB_DEVICE(0x03f0, 0x171d), .driver_info = BTUSB_WRONG_SCO_MTU }, + + /* Dell laptop with Broadcom chip */ + { USB_DEVICE(0x413c, 0x8126), .driver_info = BTUSB_WRONG_SCO_MTU }, + + /* Dell Wireless 370 and 410 devices */ + { USB_DEVICE(0x413c, 0x8152), .driver_info = BTUSB_WRONG_SCO_MTU }, + { USB_DEVICE(0x413c, 0x8156), .driver_info = BTUSB_WRONG_SCO_MTU }, + + /* Belkin F8T012 and F8T013 devices */ + { USB_DEVICE(0x050d, 0x0012), .driver_info = BTUSB_WRONG_SCO_MTU }, + { USB_DEVICE(0x050d, 0x0013), .driver_info = BTUSB_WRONG_SCO_MTU }, + + /* Asus WL-BTD202 device */ + { USB_DEVICE(0x0b05, 0x1715), .driver_info = BTUSB_WRONG_SCO_MTU }, + + /* Kensington Bluetooth USB adapter */ + { USB_DEVICE(0x047d, 0x105e), .driver_info = BTUSB_WRONG_SCO_MTU }, + + /* RTX Telecom based adapters with buggy SCO support */ + { USB_DEVICE(0x0400, 0x0807), .driver_info = BTUSB_BROKEN_ISOC }, + { USB_DEVICE(0x0400, 0x080a), .driver_info = BTUSB_BROKEN_ISOC }, + + /* CONWISE Technology based adapters with buggy SCO support */ + { USB_DEVICE(0x0e5e, 0x6622), .driver_info = BTUSB_BROKEN_ISOC }, + + /* Digianswer devices */ + { USB_DEVICE(0x08fd, 0x0001), .driver_info = BTUSB_DIGIANSWER }, + { USB_DEVICE(0x08fd, 0x0002), .driver_info = BTUSB_IGNORE }, + + /* CSR BlueCore Bluetooth Sniffer */ + { USB_DEVICE(0x0a12, 0x0002), .driver_info = BTUSB_SNIFFER }, + + /* Frontline ComProbe Bluetooth Sniffer */ + { USB_DEVICE(0x16d3, 0x0002), .driver_info = BTUSB_SNIFFER }, + + { } /* Terminating entry */ +}; + +#define BTUSB_MAX_ISOC_FRAMES 10 + +#define BTUSB_INTR_RUNNING 0 +#define BTUSB_BULK_RUNNING 1 +#define BTUSB_ISOC_RUNNING 2 +#define BTUSB_SUSPENDING 3 +#define BTUSB_DID_ISO_RESUME 4 + +struct btusb_data { + struct hci_dev *hdev; + struct usb_device *udev; + struct usb_interface *intf; + struct usb_interface *isoc; + + spinlock_t lock; + + unsigned long flags; + + struct work_struct work; + struct work_struct waker; + + struct usb_anchor tx_anchor; + struct usb_anchor intr_anchor; + struct usb_anchor bulk_anchor; + struct usb_anchor isoc_anchor; + struct usb_anchor deferred; + int tx_in_flight; + spinlock_t txlock; + + struct usb_endpoint_descriptor *intr_ep; + struct usb_endpoint_descriptor *bulk_tx_ep; + struct usb_endpoint_descriptor *bulk_rx_ep; + struct usb_endpoint_descriptor *isoc_tx_ep; + struct usb_endpoint_descriptor *isoc_rx_ep; + + __u8 cmdreq_type; + + unsigned int sco_num; + int isoc_altsetting; + int suspend_count; +}; + +static int inc_tx(struct btusb_data *data) +{ + unsigned long flags; + int rv; + + spin_lock_irqsave(&data->txlock, flags); + rv = test_bit(BTUSB_SUSPENDING, &data->flags); + if (!rv) + data->tx_in_flight++; + spin_unlock_irqrestore(&data->txlock, flags); + + return rv; +} + +static void btusb_intr_complete(struct urb *urb) +{ + struct hci_dev *hdev = urb->context; + struct btusb_data *data = hdev->driver_data; + int err; + + BT_DBG("%s urb %p status %d count %d", hdev->name, + urb, urb->status, urb->actual_length); + + if (!test_bit(HCI_RUNNING, &hdev->flags)) + return; + + if (urb->status == 0) { + hdev->stat.byte_rx += urb->actual_length; + + if (hci_recv_fragment(hdev, HCI_EVENT_PKT, + urb->transfer_buffer, + urb->actual_length) < 0) { + BT_ERR("%s corrupted event packet", hdev->name); + hdev->stat.err_rx++; + } + } + + if (!test_bit(BTUSB_INTR_RUNNING, &data->flags)) + return; + + usb_mark_last_busy(data->udev); + usb_anchor_urb(urb, &data->intr_anchor); + + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err < 0) { + if (err != -EPERM) + BT_ERR("%s urb %p failed to resubmit (%d)", + hdev->name, urb, -err); + usb_unanchor_urb(urb); + } +} + +static int btusb_submit_intr_urb(struct hci_dev *hdev, gfp_t mem_flags) +{ + struct btusb_data *data = hdev->driver_data; + struct urb *urb; + unsigned char *buf; + unsigned int pipe; + int err, size; + + BT_DBG("%s", hdev->name); + + if (!data->intr_ep) + return -ENODEV; + + urb = usb_alloc_urb(0, mem_flags); + if (!urb) + return -ENOMEM; + + size = le16_to_cpu(data->intr_ep->wMaxPacketSize); + + buf = kmalloc(size, mem_flags); + if (!buf) { + usb_free_urb(urb); + return -ENOMEM; + } + + pipe = usb_rcvintpipe(data->udev, data->intr_ep->bEndpointAddress); + + usb_fill_int_urb(urb, data->udev, pipe, buf, size, + btusb_intr_complete, hdev, + data->intr_ep->bInterval); + + urb->transfer_flags |= URB_FREE_BUFFER; + + usb_anchor_urb(urb, &data->intr_anchor); + + err = usb_submit_urb(urb, mem_flags); + if (err < 0) { + BT_ERR("%s urb %p submission failed (%d)", + hdev->name, urb, -err); + usb_unanchor_urb(urb); + } + + usb_free_urb(urb); + + return err; +} + +static void btusb_bulk_complete(struct urb *urb) +{ + struct hci_dev *hdev = urb->context; + struct btusb_data *data = hdev->driver_data; + int err; + + BT_DBG("%s urb %p status %d count %d", hdev->name, + urb, urb->status, urb->actual_length); + + if (!test_bit(HCI_RUNNING, &hdev->flags)) + return; + + if (urb->status == 0) { + hdev->stat.byte_rx += urb->actual_length; + + if (hci_recv_fragment(hdev, HCI_ACLDATA_PKT, + urb->transfer_buffer, + urb->actual_length) < 0) { + BT_ERR("%s corrupted ACL packet", hdev->name); + hdev->stat.err_rx++; + } + } + + if (!test_bit(BTUSB_BULK_RUNNING, &data->flags)) + return; + + usb_anchor_urb(urb, &data->bulk_anchor); + usb_mark_last_busy(data->udev); + + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err < 0) { + if (err != -EPERM) + BT_ERR("%s urb %p failed to resubmit (%d)", + hdev->name, urb, -err); + usb_unanchor_urb(urb); + } +} + +static int btusb_submit_bulk_urb(struct hci_dev *hdev, gfp_t mem_flags) +{ + struct btusb_data *data = hdev->driver_data; + struct urb *urb; + unsigned char *buf; + unsigned int pipe; + int err, size = HCI_MAX_FRAME_SIZE; + + BT_DBG("%s", hdev->name); + + if (!data->bulk_rx_ep) + return -ENODEV; + + urb = usb_alloc_urb(0, mem_flags); + if (!urb) + return -ENOMEM; + + buf = kmalloc(size, mem_flags); + if (!buf) { + usb_free_urb(urb); + return -ENOMEM; + } + + pipe = usb_rcvbulkpipe(data->udev, data->bulk_rx_ep->bEndpointAddress); + + usb_fill_bulk_urb(urb, data->udev, pipe, + buf, size, btusb_bulk_complete, hdev); + + urb->transfer_flags |= URB_FREE_BUFFER; + + usb_mark_last_busy(data->udev); + usb_anchor_urb(urb, &data->bulk_anchor); + + err = usb_submit_urb(urb, mem_flags); + if (err < 0) { + BT_ERR("%s urb %p submission failed (%d)", + hdev->name, urb, -err); + usb_unanchor_urb(urb); + } + + usb_free_urb(urb); + + return err; +} + +static void btusb_isoc_complete(struct urb *urb) +{ + struct hci_dev *hdev = urb->context; + struct btusb_data *data = hdev->driver_data; + int i, err; + + BT_DBG("%s urb %p status %d count %d", hdev->name, + urb, urb->status, urb->actual_length); + + if (!test_bit(HCI_RUNNING, &hdev->flags)) + return; + + if (urb->status == 0) { + for (i = 0; i < urb->number_of_packets; i++) { + unsigned int offset = urb->iso_frame_desc[i].offset; + unsigned int length = urb->iso_frame_desc[i].actual_length; + + if (urb->iso_frame_desc[i].status) + continue; + + hdev->stat.byte_rx += length; + + if (hci_recv_fragment(hdev, HCI_SCODATA_PKT, + urb->transfer_buffer + offset, + length) < 0) { + BT_ERR("%s corrupted SCO packet", hdev->name); + hdev->stat.err_rx++; + } + } + } + + if (!test_bit(BTUSB_ISOC_RUNNING, &data->flags)) + return; + + usb_anchor_urb(urb, &data->isoc_anchor); + + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err < 0) { + if (err != -EPERM) + BT_ERR("%s urb %p failed to resubmit (%d)", + hdev->name, urb, -err); + usb_unanchor_urb(urb); + } +} + +static inline void __fill_isoc_descriptor(struct urb *urb, int len, int mtu) +{ + int i, offset = 0; + + BT_DBG("len %d mtu %d", len, mtu); + + for (i = 0; i < BTUSB_MAX_ISOC_FRAMES && len >= mtu; + i++, offset += mtu, len -= mtu) { + urb->iso_frame_desc[i].offset = offset; + urb->iso_frame_desc[i].length = mtu; + } + + if (len && i < BTUSB_MAX_ISOC_FRAMES) { + urb->iso_frame_desc[i].offset = offset; + urb->iso_frame_desc[i].length = len; + i++; + } + + urb->number_of_packets = i; +} + +static int btusb_submit_isoc_urb(struct hci_dev *hdev, gfp_t mem_flags) +{ + struct btusb_data *data = hdev->driver_data; + struct urb *urb; + unsigned char *buf; + unsigned int pipe; + int err, size; + + BT_DBG("%s", hdev->name); + + if (!data->isoc_rx_ep) + return -ENODEV; + + urb = usb_alloc_urb(BTUSB_MAX_ISOC_FRAMES, mem_flags); + if (!urb) + return -ENOMEM; + + size = le16_to_cpu(data->isoc_rx_ep->wMaxPacketSize) * + BTUSB_MAX_ISOC_FRAMES; + + buf = kmalloc(size, mem_flags); + if (!buf) { + usb_free_urb(urb); + return -ENOMEM; + } + + pipe = usb_rcvisocpipe(data->udev, data->isoc_rx_ep->bEndpointAddress); + + usb_fill_int_urb(urb, data->udev, pipe, buf, size, btusb_isoc_complete, + hdev, data->isoc_rx_ep->bInterval); + + urb->transfer_flags = URB_FREE_BUFFER | URB_ISO_ASAP; + + __fill_isoc_descriptor(urb, size, + le16_to_cpu(data->isoc_rx_ep->wMaxPacketSize)); + + usb_anchor_urb(urb, &data->isoc_anchor); + + err = usb_submit_urb(urb, mem_flags); + if (err < 0) { + BT_ERR("%s urb %p submission failed (%d)", + hdev->name, urb, -err); + usb_unanchor_urb(urb); + } + + usb_free_urb(urb); + + return err; +} + +static void btusb_tx_complete(struct urb *urb) +{ + struct sk_buff *skb = urb->context; + struct hci_dev *hdev = (struct hci_dev *) skb->dev; + struct btusb_data *data = hdev->driver_data; + + BT_DBG("%s urb %p status %d count %d", hdev->name, + urb, urb->status, urb->actual_length); + + if (!test_bit(HCI_RUNNING, &hdev->flags)) + goto done; + + if (!urb->status) + hdev->stat.byte_tx += urb->transfer_buffer_length; + else + hdev->stat.err_tx++; + +done: + spin_lock(&data->txlock); + data->tx_in_flight--; + spin_unlock(&data->txlock); + + kfree(urb->setup_packet); + + kfree_skb(skb); +} + +static void btusb_isoc_tx_complete(struct urb *urb) +{ + struct sk_buff *skb = urb->context; + struct hci_dev *hdev = (struct hci_dev *) skb->dev; + + BT_DBG("%s urb %p status %d count %d", hdev->name, + urb, urb->status, urb->actual_length); + + if (!test_bit(HCI_RUNNING, &hdev->flags)) + goto done; + + if (!urb->status) + hdev->stat.byte_tx += urb->transfer_buffer_length; + else + hdev->stat.err_tx++; + +done: + kfree(urb->setup_packet); + + kfree_skb(skb); +} + +static int btusb_open(struct hci_dev *hdev) +{ + struct btusb_data *data = hdev->driver_data; + int err; + + BT_DBG("%s", hdev->name); + + err = usb_autopm_get_interface(data->intf); + if (err < 0) + return err; + + data->intf->needs_remote_wakeup = 1; + + if (test_and_set_bit(HCI_RUNNING, &hdev->flags)) + goto done; + + if (test_and_set_bit(BTUSB_INTR_RUNNING, &data->flags)) + goto done; + + err = btusb_submit_intr_urb(hdev, GFP_KERNEL); + if (err < 0) + goto failed; + + err = btusb_submit_bulk_urb(hdev, GFP_KERNEL); + if (err < 0) { + usb_kill_anchored_urbs(&data->intr_anchor); + goto failed; + } + + set_bit(BTUSB_BULK_RUNNING, &data->flags); + btusb_submit_bulk_urb(hdev, GFP_KERNEL); + +done: + usb_autopm_put_interface(data->intf); + return 0; + +failed: + clear_bit(BTUSB_INTR_RUNNING, &data->flags); + clear_bit(HCI_RUNNING, &hdev->flags); + usb_autopm_put_interface(data->intf); + return err; +} + +static void btusb_stop_traffic(struct btusb_data *data) +{ + usb_kill_anchored_urbs(&data->intr_anchor); + usb_kill_anchored_urbs(&data->bulk_anchor); + usb_kill_anchored_urbs(&data->isoc_anchor); +} + +static int btusb_close(struct hci_dev *hdev) +{ + struct btusb_data *data = hdev->driver_data; + int err; + + BT_DBG("%s", hdev->name); + + if (!test_and_clear_bit(HCI_RUNNING, &hdev->flags)) + return 0; + + cancel_work_sync(&data->work); + cancel_work_sync(&data->waker); + + clear_bit(BTUSB_ISOC_RUNNING, &data->flags); + clear_bit(BTUSB_BULK_RUNNING, &data->flags); + clear_bit(BTUSB_INTR_RUNNING, &data->flags); + + btusb_stop_traffic(data); + err = usb_autopm_get_interface(data->intf); + if (err < 0) + goto failed; + + data->intf->needs_remote_wakeup = 0; + usb_autopm_put_interface(data->intf); + +failed: + usb_scuttle_anchored_urbs(&data->deferred); + return 0; +} + +static int btusb_flush(struct hci_dev *hdev) +{ + struct btusb_data *data = hdev->driver_data; + + BT_DBG("%s", hdev->name); + + usb_kill_anchored_urbs(&data->tx_anchor); + + return 0; +} + +static int btusb_send_frame(struct sk_buff *skb) +{ + struct hci_dev *hdev = (struct hci_dev *) skb->dev; + struct btusb_data *data = hdev->driver_data; + struct usb_ctrlrequest *dr; + struct urb *urb; + unsigned int pipe; + int err; + + BT_DBG("%s", hdev->name); + + if (!test_bit(HCI_RUNNING, &hdev->flags)) + return -EBUSY; + + switch (bt_cb(skb)->pkt_type) { + case HCI_COMMAND_PKT: + urb = usb_alloc_urb(0, GFP_ATOMIC); + if (!urb) + return -ENOMEM; + + dr = kmalloc(sizeof(*dr), GFP_ATOMIC); + if (!dr) { + usb_free_urb(urb); + return -ENOMEM; + } + + dr->bRequestType = data->cmdreq_type; + dr->bRequest = 0; + dr->wIndex = 0; + dr->wValue = 0; + dr->wLength = __cpu_to_le16(skb->len); + + pipe = usb_sndctrlpipe(data->udev, 0x00); + + usb_fill_control_urb(urb, data->udev, pipe, (void *) dr, + skb->data, skb->len, btusb_tx_complete, skb); + + hdev->stat.cmd_tx++; + break; + + case HCI_ACLDATA_PKT: + if (!data->bulk_tx_ep || (hdev->conn_hash.acl_num < 1 && + hdev->conn_hash.le_num < 1)) + return -ENODEV; + + urb = usb_alloc_urb(0, GFP_ATOMIC); + if (!urb) + return -ENOMEM; + + pipe = usb_sndbulkpipe(data->udev, + data->bulk_tx_ep->bEndpointAddress); + + usb_fill_bulk_urb(urb, data->udev, pipe, + skb->data, skb->len, btusb_tx_complete, skb); + + hdev->stat.acl_tx++; + break; + + case HCI_SCODATA_PKT: + if (!data->isoc_tx_ep || hdev->conn_hash.sco_num < 1) + return -ENODEV; + + urb = usb_alloc_urb(BTUSB_MAX_ISOC_FRAMES, GFP_ATOMIC); + if (!urb) + return -ENOMEM; + + pipe = usb_sndisocpipe(data->udev, + data->isoc_tx_ep->bEndpointAddress); + + usb_fill_int_urb(urb, data->udev, pipe, + skb->data, skb->len, btusb_isoc_tx_complete, + skb, data->isoc_tx_ep->bInterval); + + urb->transfer_flags = URB_ISO_ASAP; + + __fill_isoc_descriptor(urb, skb->len, + le16_to_cpu(data->isoc_tx_ep->wMaxPacketSize)); + + hdev->stat.sco_tx++; + goto skip_waking; + + default: + return -EILSEQ; + } + + err = inc_tx(data); + if (err) { + usb_anchor_urb(urb, &data->deferred); + schedule_work(&data->waker); + err = 0; + goto done; + } + +skip_waking: + usb_anchor_urb(urb, &data->tx_anchor); + + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err < 0) { + BT_ERR("%s urb %p submission failed", hdev->name, urb); + kfree(urb->setup_packet); + usb_unanchor_urb(urb); + } else { + usb_mark_last_busy(data->udev); + } + + usb_free_urb(urb); + +done: + return err; +} + +static void btusb_destruct(struct hci_dev *hdev) +{ + struct btusb_data *data = hdev->driver_data; + + BT_DBG("%s", hdev->name); + + kfree(data); +} + +static void btusb_notify(struct hci_dev *hdev, unsigned int evt) +{ + struct btusb_data *data = hdev->driver_data; + + BT_DBG("%s evt %d", hdev->name, evt); + + if (hdev->conn_hash.sco_num != data->sco_num) { + data->sco_num = hdev->conn_hash.sco_num; + schedule_work(&data->work); + } +} + +static inline int __set_isoc_interface(struct hci_dev *hdev, int altsetting) +{ + struct btusb_data *data = hdev->driver_data; + struct usb_interface *intf = data->isoc; + struct usb_endpoint_descriptor *ep_desc; + int i, err; + + if (!data->isoc) + return -ENODEV; + + err = usb_set_interface(data->udev, 1, altsetting); + if (err < 0) { + BT_ERR("%s setting interface failed (%d)", hdev->name, -err); + return err; + } + + data->isoc_altsetting = altsetting; + + data->isoc_tx_ep = NULL; + data->isoc_rx_ep = NULL; + + for (i = 0; i < intf->cur_altsetting->desc.bNumEndpoints; i++) { + ep_desc = &intf->cur_altsetting->endpoint[i].desc; + + if (!data->isoc_tx_ep && usb_endpoint_is_isoc_out(ep_desc)) { + data->isoc_tx_ep = ep_desc; + continue; + } + + if (!data->isoc_rx_ep && usb_endpoint_is_isoc_in(ep_desc)) { + data->isoc_rx_ep = ep_desc; + continue; + } + } + + if (!data->isoc_tx_ep || !data->isoc_rx_ep) { + BT_ERR("%s invalid SCO descriptors", hdev->name); + return -ENODEV; + } + + return 0; +} + +static void btusb_work(struct work_struct *work) +{ + struct btusb_data *data = container_of(work, struct btusb_data, work); + struct hci_dev *hdev = data->hdev; + int err; + + if (hdev->conn_hash.sco_num > 0) { + if (!test_bit(BTUSB_DID_ISO_RESUME, &data->flags)) { + err = usb_autopm_get_interface(data->isoc ? data->isoc : data->intf); + if (err < 0) { + clear_bit(BTUSB_ISOC_RUNNING, &data->flags); + usb_kill_anchored_urbs(&data->isoc_anchor); + return; + } + + set_bit(BTUSB_DID_ISO_RESUME, &data->flags); + } + if (data->isoc_altsetting != 2) { + clear_bit(BTUSB_ISOC_RUNNING, &data->flags); + usb_kill_anchored_urbs(&data->isoc_anchor); + + if (__set_isoc_interface(hdev, 2) < 0) + return; + } + + if (!test_and_set_bit(BTUSB_ISOC_RUNNING, &data->flags)) { + if (btusb_submit_isoc_urb(hdev, GFP_KERNEL) < 0) + clear_bit(BTUSB_ISOC_RUNNING, &data->flags); + else + btusb_submit_isoc_urb(hdev, GFP_KERNEL); + } + } else { + clear_bit(BTUSB_ISOC_RUNNING, &data->flags); + usb_kill_anchored_urbs(&data->isoc_anchor); + + __set_isoc_interface(hdev, 0); + if (test_and_clear_bit(BTUSB_DID_ISO_RESUME, &data->flags)) + usb_autopm_put_interface(data->isoc ? data->isoc : data->intf); + } +} + +static void btusb_waker(struct work_struct *work) +{ + struct btusb_data *data = container_of(work, struct btusb_data, waker); + int err; + + err = usb_autopm_get_interface(data->intf); + if (err < 0) + return; + + usb_autopm_put_interface(data->intf); +} + +static int btusb_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_endpoint_descriptor *ep_desc; + struct btusb_data *data; + struct hci_dev *hdev; + int i, err; + + BT_DBG("intf %p id %p", intf, id); + + /* interface numbers are hardcoded in the spec */ + if (intf->cur_altsetting->desc.bInterfaceNumber != 0) + return -ENODEV; + + if (!id->driver_info) { + const struct usb_device_id *match; + match = usb_match_id(intf, blacklist_table); + if (match) + id = match; + } + + if (id->driver_info == BTUSB_IGNORE) + return -ENODEV; + + if (ignore_dga && id->driver_info & BTUSB_DIGIANSWER) + return -ENODEV; + + if (ignore_csr && id->driver_info & BTUSB_CSR) + return -ENODEV; + + if (ignore_sniffer && id->driver_info & BTUSB_SNIFFER) + return -ENODEV; + + if (id->driver_info & BTUSB_ATH3012) { + struct usb_device *udev = interface_to_usbdev(intf); + + /* Old firmware would otherwise let ath3k driver load + * patch and sysconfig files */ + if (le16_to_cpu(udev->descriptor.bcdDevice) <= 0x0001) + return -ENODEV; + } + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + for (i = 0; i < intf->cur_altsetting->desc.bNumEndpoints; i++) { + ep_desc = &intf->cur_altsetting->endpoint[i].desc; + + if (!data->intr_ep && usb_endpoint_is_int_in(ep_desc)) { + data->intr_ep = ep_desc; + continue; + } + + if (!data->bulk_tx_ep && usb_endpoint_is_bulk_out(ep_desc)) { + data->bulk_tx_ep = ep_desc; + continue; + } + + if (!data->bulk_rx_ep && usb_endpoint_is_bulk_in(ep_desc)) { + data->bulk_rx_ep = ep_desc; + continue; + } + } + + if (!data->intr_ep || !data->bulk_tx_ep || !data->bulk_rx_ep) { + kfree(data); + return -ENODEV; + } + + data->cmdreq_type = USB_TYPE_CLASS; + + data->udev = interface_to_usbdev(intf); + data->intf = intf; + + spin_lock_init(&data->lock); + + INIT_WORK(&data->work, btusb_work); + INIT_WORK(&data->waker, btusb_waker); + spin_lock_init(&data->txlock); + + init_usb_anchor(&data->tx_anchor); + init_usb_anchor(&data->intr_anchor); + init_usb_anchor(&data->bulk_anchor); + init_usb_anchor(&data->isoc_anchor); + init_usb_anchor(&data->deferred); + + hdev = hci_alloc_dev(); + if (!hdev) { + kfree(data); + return -ENOMEM; + } + + hdev->bus = HCI_USB; + hdev->driver_data = data; + + data->hdev = hdev; + + SET_HCIDEV_DEV(hdev, &intf->dev); + + hdev->open = btusb_open; + hdev->close = btusb_close; + hdev->flush = btusb_flush; + hdev->send = btusb_send_frame; + hdev->destruct = btusb_destruct; + hdev->notify = btusb_notify; + + hdev->owner = THIS_MODULE; + + /* Interface numbers are hardcoded in the specification */ + data->isoc = usb_ifnum_to_if(data->udev, 1); + + if (!reset) + set_bit(HCI_QUIRK_NO_RESET, &hdev->quirks); + + if (force_scofix || id->driver_info & BTUSB_WRONG_SCO_MTU) { + if (!disable_scofix) + set_bit(HCI_QUIRK_FIXUP_BUFFER_SIZE, &hdev->quirks); + } + + if (id->driver_info & BTUSB_BROKEN_ISOC) + data->isoc = NULL; + + if (id->driver_info & BTUSB_DIGIANSWER) { + data->cmdreq_type = USB_TYPE_VENDOR; + set_bit(HCI_QUIRK_NO_RESET, &hdev->quirks); + } + + if (id->driver_info & BTUSB_CSR) { + struct usb_device *udev = data->udev; + + /* Old firmware would otherwise execute USB reset */ + if (le16_to_cpu(udev->descriptor.bcdDevice) < 0x117) + set_bit(HCI_QUIRK_NO_RESET, &hdev->quirks); + } + + if (id->driver_info & BTUSB_SNIFFER) { + struct usb_device *udev = data->udev; + + /* New sniffer firmware has crippled HCI interface */ + if (le16_to_cpu(udev->descriptor.bcdDevice) > 0x997) + set_bit(HCI_QUIRK_RAW_DEVICE, &hdev->quirks); + + data->isoc = NULL; + } + + if (id->driver_info & BTUSB_BCM92035) { + unsigned char cmd[] = { 0x3b, 0xfc, 0x01, 0x00 }; + struct sk_buff *skb; + + skb = bt_skb_alloc(sizeof(cmd), GFP_KERNEL); + if (skb) { + memcpy(skb_put(skb, sizeof(cmd)), cmd, sizeof(cmd)); + skb_queue_tail(&hdev->driver_init, skb); + } + } + + if (data->isoc) { + err = usb_driver_claim_interface(&btusb_driver, + data->isoc, data); + if (err < 0) { + hci_free_dev(hdev); + kfree(data); + return err; + } + } + + err = hci_register_dev(hdev); + if (err < 0) { + hci_free_dev(hdev); + kfree(data); + return err; + } + + usb_set_intfdata(intf, data); + + return 0; +} + +static void btusb_disconnect(struct usb_interface *intf) +{ + struct btusb_data *data = usb_get_intfdata(intf); + struct hci_dev *hdev; + + BT_DBG("intf %p", intf); + + if (!data) + return; + + hdev = data->hdev; + + __hci_dev_hold(hdev); + + usb_set_intfdata(data->intf, NULL); + + if (data->isoc) + usb_set_intfdata(data->isoc, NULL); + + hci_unregister_dev(hdev); + + if (intf == data->isoc) + usb_driver_release_interface(&btusb_driver, data->intf); + else if (data->isoc) + usb_driver_release_interface(&btusb_driver, data->isoc); + + __hci_dev_put(hdev); + + hci_free_dev(hdev); +} + +#ifdef CONFIG_PM +static int btusb_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct btusb_data *data = usb_get_intfdata(intf); + + BT_DBG("intf %p", intf); + + if (data->suspend_count++) + return 0; + + spin_lock_irq(&data->txlock); + if (!((message.event & PM_EVENT_AUTO) && data->tx_in_flight)) { + set_bit(BTUSB_SUSPENDING, &data->flags); + spin_unlock_irq(&data->txlock); + } else { + spin_unlock_irq(&data->txlock); + data->suspend_count--; + return -EBUSY; + } + + cancel_work_sync(&data->work); + + btusb_stop_traffic(data); + usb_kill_anchored_urbs(&data->tx_anchor); + + return 0; +} + +static void play_deferred(struct btusb_data *data) +{ + struct urb *urb; + int err; + + while ((urb = usb_get_from_anchor(&data->deferred))) { + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err < 0) + break; + + data->tx_in_flight++; + } + usb_scuttle_anchored_urbs(&data->deferred); +} + +static int btusb_resume(struct usb_interface *intf) +{ + struct btusb_data *data = usb_get_intfdata(intf); + struct hci_dev *hdev = data->hdev; + int err = 0; + + BT_DBG("intf %p", intf); + + if (--data->suspend_count) + return 0; + + if (!test_bit(HCI_RUNNING, &hdev->flags)) + goto done; + + if (test_bit(BTUSB_INTR_RUNNING, &data->flags)) { + err = btusb_submit_intr_urb(hdev, GFP_NOIO); + if (err < 0) { + clear_bit(BTUSB_INTR_RUNNING, &data->flags); + goto failed; + } + } + + if (test_bit(BTUSB_BULK_RUNNING, &data->flags)) { + err = btusb_submit_bulk_urb(hdev, GFP_NOIO); + if (err < 0) { + clear_bit(BTUSB_BULK_RUNNING, &data->flags); + goto failed; + } + + btusb_submit_bulk_urb(hdev, GFP_NOIO); + } + + if (test_bit(BTUSB_ISOC_RUNNING, &data->flags)) { + if (btusb_submit_isoc_urb(hdev, GFP_NOIO) < 0) + clear_bit(BTUSB_ISOC_RUNNING, &data->flags); + else + btusb_submit_isoc_urb(hdev, GFP_NOIO); + } + + spin_lock_irq(&data->txlock); + play_deferred(data); + clear_bit(BTUSB_SUSPENDING, &data->flags); + spin_unlock_irq(&data->txlock); + schedule_work(&data->work); + + return 0; + +failed: + usb_scuttle_anchored_urbs(&data->deferred); +done: + spin_lock_irq(&data->txlock); + clear_bit(BTUSB_SUSPENDING, &data->flags); + spin_unlock_irq(&data->txlock); + + return err; +} +#endif + +static struct usb_driver btusb_driver = { + .name = "btusb", + .probe = btusb_probe, + .disconnect = btusb_disconnect, +#ifdef CONFIG_PM + .suspend = btusb_suspend, + .resume = btusb_resume, +#endif + .id_table = btusb_table, + .supports_autosuspend = 1, +}; + +static int __init btusb_init(void) +{ + BT_INFO("Generic Bluetooth USB driver ver %s", VERSION); + + return usb_register(&btusb_driver); +} + +static void __exit btusb_exit(void) +{ + usb_deregister(&btusb_driver); +} + +module_init(btusb_init); +module_exit(btusb_exit); + +module_param(ignore_dga, bool, 0644); +MODULE_PARM_DESC(ignore_dga, "Ignore devices with id 08fd:0001"); + +module_param(ignore_csr, bool, 0644); +MODULE_PARM_DESC(ignore_csr, "Ignore devices with id 0a12:0001"); + +module_param(ignore_sniffer, bool, 0644); +MODULE_PARM_DESC(ignore_sniffer, "Ignore devices with id 0a12:0002"); + +module_param(disable_scofix, bool, 0644); +MODULE_PARM_DESC(disable_scofix, "Disable fixup of wrong SCO buffer size"); + +module_param(force_scofix, bool, 0644); +MODULE_PARM_DESC(force_scofix, "Force fixup of wrong SCO buffers size"); + +module_param(reset, bool, 0644); +MODULE_PARM_DESC(reset, "Send HCI reset command on initialization"); + +MODULE_AUTHOR("Marcel Holtmann <marcel@holtmann.org>"); +MODULE_DESCRIPTION("Generic Bluetooth USB driver ver " VERSION); +MODULE_VERSION(VERSION); +MODULE_LICENSE("GPL"); diff --git a/drivers/bluetooth/btwilink.c b/drivers/bluetooth/btwilink.c new file mode 100644 index 00000000..65d27aff --- /dev/null +++ b/drivers/bluetooth/btwilink.c @@ -0,0 +1,395 @@ +/* + * Texas Instrument's Bluetooth Driver For Shared Transport. + * + * Bluetooth Driver acts as interface between HCI core and + * TI Shared Transport Layer. + * + * Copyright (C) 2009-2010 Texas Instruments + * Author: Raja Mani <raja_mani@ti.com> + * Pavan Savoy <pavan_savoy@ti.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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 + * + */ +#define DEBUG +#include <linux/platform_device.h> +#include <net/bluetooth/bluetooth.h> +#include <net/bluetooth/hci_core.h> +#include <net/bluetooth/hci.h> + +#include <linux/ti_wilink_st.h> + +/* Bluetooth Driver Version */ +#define VERSION "1.0" +#define MAX_BT_CHNL_IDS 3 + +/* Number of seconds to wait for registration completion + * when ST returns PENDING status. + */ +#define BT_REGISTER_TIMEOUT 6000 /* 6 sec */ + +/** + * struct ti_st - driver operation structure + * @hdev: hci device pointer which binds to bt driver + * @reg_status: ST registration callback status + * @st_write: write function provided by the ST driver + * to be used by the driver during send_frame. + * @wait_reg_completion - completion sync between ti_st_open + * and st_reg_completion_cb. + */ +struct ti_st { + struct hci_dev *hdev; + char reg_status; + long (*st_write) (struct sk_buff *); + struct completion wait_reg_completion; +}; + +/* Increments HCI counters based on pocket ID (cmd,acl,sco) */ +static inline void ti_st_tx_complete(struct ti_st *hst, int pkt_type) +{ + struct hci_dev *hdev = hst->hdev; + + /* Update HCI stat counters */ + switch (pkt_type) { + case HCI_COMMAND_PKT: + hdev->stat.cmd_tx++; + break; + + case HCI_ACLDATA_PKT: + hdev->stat.acl_tx++; + break; + + case HCI_SCODATA_PKT: + hdev->stat.sco_tx++; + break; + } +} + +/* ------- Interfaces to Shared Transport ------ */ + +/* Called by ST layer to indicate protocol registration completion + * status.ti_st_open() function will wait for signal from this + * API when st_register() function returns ST_PENDING. + */ +static void st_reg_completion_cb(void *priv_data, char data) +{ + struct ti_st *lhst = priv_data; + + /* Save registration status for use in ti_st_open() */ + lhst->reg_status = data; + /* complete the wait in ti_st_open() */ + complete(&lhst->wait_reg_completion); +} + +/* Called by Shared Transport layer when receive data is + * available */ +static long st_receive(void *priv_data, struct sk_buff *skb) +{ + struct ti_st *lhst = priv_data; + int err; + + if (!skb) + return -EFAULT; + + if (!lhst) { + kfree_skb(skb); + return -EFAULT; + } + + skb->dev = (void *) lhst->hdev; + + /* Forward skb to HCI core layer */ + err = hci_recv_frame(skb); + if (err < 0) { + BT_ERR("Unable to push skb to HCI core(%d)", err); + return err; + } + + lhst->hdev->stat.byte_rx += skb->len; + + return 0; +} + +/* ------- Interfaces to HCI layer ------ */ +/* protocol structure registered with shared transport */ +static struct st_proto_s ti_st_proto[MAX_BT_CHNL_IDS] = { + { + .chnl_id = HCI_ACLDATA_PKT, /* ACL */ + .hdr_len = sizeof(struct hci_acl_hdr), + .offset_len_in_hdr = offsetof(struct hci_acl_hdr, dlen), + .len_size = 2, /* sizeof(dlen) in struct hci_acl_hdr */ + .reserve = 8, + }, + { + .chnl_id = HCI_SCODATA_PKT, /* SCO */ + .hdr_len = sizeof(struct hci_sco_hdr), + .offset_len_in_hdr = offsetof(struct hci_sco_hdr, dlen), + .len_size = 1, /* sizeof(dlen) in struct hci_sco_hdr */ + .reserve = 8, + }, + { + .chnl_id = HCI_EVENT_PKT, /* HCI Events */ + .hdr_len = sizeof(struct hci_event_hdr), + .offset_len_in_hdr = offsetof(struct hci_event_hdr, plen), + .len_size = 1, /* sizeof(plen) in struct hci_event_hdr */ + .reserve = 8, + }, +}; + +/* Called from HCI core to initialize the device */ +static int ti_st_open(struct hci_dev *hdev) +{ + unsigned long timeleft; + struct ti_st *hst; + int err, i; + + BT_DBG("%s %p", hdev->name, hdev); + + if (test_and_set_bit(HCI_RUNNING, &hdev->flags)) + return -EBUSY; + + /* provide contexts for callbacks from ST */ + hst = hdev->driver_data; + + for (i = 0; i < MAX_BT_CHNL_IDS; i++) { + ti_st_proto[i].priv_data = hst; + ti_st_proto[i].max_frame_size = HCI_MAX_FRAME_SIZE; + ti_st_proto[i].recv = st_receive; + ti_st_proto[i].reg_complete_cb = st_reg_completion_cb; + + /* Prepare wait-for-completion handler */ + init_completion(&hst->wait_reg_completion); + /* Reset ST registration callback status flag, + * this value will be updated in + * st_reg_completion_cb() + * function whenever it called from ST driver. + */ + hst->reg_status = -EINPROGRESS; + + err = st_register(&ti_st_proto[i]); + if (!err) + goto done; + + if (err != -EINPROGRESS) { + clear_bit(HCI_RUNNING, &hdev->flags); + BT_ERR("st_register failed %d", err); + return err; + } + + /* ST is busy with either protocol + * registration or firmware download. + */ + BT_DBG("waiting for registration " + "completion signal from ST"); + timeleft = wait_for_completion_timeout + (&hst->wait_reg_completion, + msecs_to_jiffies(BT_REGISTER_TIMEOUT)); + if (!timeleft) { + clear_bit(HCI_RUNNING, &hdev->flags); + BT_ERR("Timeout(%d sec),didn't get reg " + "completion signal from ST", + BT_REGISTER_TIMEOUT / 1000); + return -ETIMEDOUT; + } + + /* Is ST registration callback + * called with ERROR status? */ + if (hst->reg_status != 0) { + clear_bit(HCI_RUNNING, &hdev->flags); + BT_ERR("ST registration completed with invalid " + "status %d", hst->reg_status); + return -EAGAIN; + } + +done: + hst->st_write = ti_st_proto[i].write; + if (!hst->st_write) { + BT_ERR("undefined ST write function"); + clear_bit(HCI_RUNNING, &hdev->flags); + for (i = 0; i < MAX_BT_CHNL_IDS; i++) { + /* Undo registration with ST */ + err = st_unregister(&ti_st_proto[i]); + if (err) + BT_ERR("st_unregister() failed with " + "error %d", err); + hst->st_write = NULL; + } + return -EIO; + } + } + return 0; +} + +/* Close device */ +static int ti_st_close(struct hci_dev *hdev) +{ + int err, i; + struct ti_st *hst = hdev->driver_data; + + if (!test_and_clear_bit(HCI_RUNNING, &hdev->flags)) + return 0; + + for (i = 0; i < MAX_BT_CHNL_IDS; i++) { + err = st_unregister(&ti_st_proto[i]); + if (err) + BT_ERR("st_unregister(%d) failed with error %d", + ti_st_proto[i].chnl_id, err); + } + + hst->st_write = NULL; + + return err; +} + +static int ti_st_send_frame(struct sk_buff *skb) +{ + struct hci_dev *hdev; + struct ti_st *hst; + long len; + + hdev = (struct hci_dev *)skb->dev; + + if (!test_bit(HCI_RUNNING, &hdev->flags)) + return -EBUSY; + + hst = hdev->driver_data; + + /* Prepend skb with frame type */ + memcpy(skb_push(skb, 1), &bt_cb(skb)->pkt_type, 1); + + BT_DBG("%s: type %d len %d", hdev->name, bt_cb(skb)->pkt_type, + skb->len); + + /* Insert skb to shared transport layer's transmit queue. + * Freeing skb memory is taken care in shared transport layer, + * so don't free skb memory here. + */ + len = hst->st_write(skb); + if (len < 0) { + kfree_skb(skb); + BT_ERR("ST write failed (%ld)", len); + /* Try Again, would only fail if UART has gone bad */ + return -EAGAIN; + } + + /* ST accepted our skb. So, Go ahead and do rest */ + hdev->stat.byte_tx += len; + ti_st_tx_complete(hst, bt_cb(skb)->pkt_type); + + return 0; +} + +static void ti_st_destruct(struct hci_dev *hdev) +{ + BT_DBG("%s", hdev->name); + /* do nothing here, since platform remove + * would free the hdev->driver_data + */ +} + +static int bt_ti_probe(struct platform_device *pdev) +{ + static struct ti_st *hst; + struct hci_dev *hdev; + int err; + + hst = kzalloc(sizeof(struct ti_st), GFP_KERNEL); + if (!hst) + return -ENOMEM; + + /* Expose "hciX" device to user space */ + hdev = hci_alloc_dev(); + if (!hdev) { + kfree(hst); + return -ENOMEM; + } + + BT_DBG("hdev %p", hdev); + + hst->hdev = hdev; + hdev->bus = HCI_UART; + hdev->driver_data = hst; + hdev->open = ti_st_open; + hdev->close = ti_st_close; + hdev->flush = NULL; + hdev->send = ti_st_send_frame; + hdev->destruct = ti_st_destruct; + hdev->owner = THIS_MODULE; + + err = hci_register_dev(hdev); + if (err < 0) { + BT_ERR("Can't register HCI device error %d", err); + kfree(hst); + hci_free_dev(hdev); + return err; + } + + BT_DBG("HCI device registered (hdev %p)", hdev); + + dev_set_drvdata(&pdev->dev, hst); + return err; +} + +static int bt_ti_remove(struct platform_device *pdev) +{ + struct hci_dev *hdev; + struct ti_st *hst = dev_get_drvdata(&pdev->dev); + + if (!hst) + return -EFAULT; + + BT_DBG("%s", hst->hdev->name); + + hdev = hst->hdev; + ti_st_close(hdev); + hci_unregister_dev(hdev); + + hci_free_dev(hdev); + kfree(hst); + + dev_set_drvdata(&pdev->dev, NULL); + return 0; +} + +static struct platform_driver btwilink_driver = { + .probe = bt_ti_probe, + .remove = bt_ti_remove, + .driver = { + .name = "btwilink", + .owner = THIS_MODULE, + }, +}; + +/* ------- Module Init/Exit interfaces ------ */ +static int __init btwilink_init(void) +{ + BT_INFO("Bluetooth Driver for TI WiLink - Version %s", VERSION); + + return platform_driver_register(&btwilink_driver); +} + +static void __exit btwilink_exit(void) +{ + platform_driver_unregister(&btwilink_driver); +} + +module_init(btwilink_init); +module_exit(btwilink_exit); + +/* ------ Module Info ------ */ + +MODULE_AUTHOR("Raja Mani <raja_mani@ti.com>"); +MODULE_DESCRIPTION("Bluetooth Driver for TI Shared Transport" VERSION); +MODULE_VERSION(VERSION); +MODULE_LICENSE("GPL"); diff --git a/drivers/bluetooth/dtl1_cs.c b/drivers/bluetooth/dtl1_cs.c new file mode 100644 index 00000000..5e4c2de9 --- /dev/null +++ b/drivers/bluetooth/dtl1_cs.c @@ -0,0 +1,668 @@ +/* + * + * A driver for Nokia Connectivity Card DTL-1 devices + * + * Copyright (C) 2001-2002 Marcel Holtmann <marcel@holtmann.org> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation; + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The initial developer of the original code is David A. Hinds + * <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + * + */ + +#include <linux/module.h> + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/ptrace.h> +#include <linux/ioport.h> +#include <linux/spinlock.h> +#include <linux/moduleparam.h> + +#include <linux/skbuff.h> +#include <linux/string.h> +#include <linux/serial.h> +#include <linux/serial_reg.h> +#include <linux/bitops.h> +#include <asm/system.h> +#include <asm/io.h> + +#include <pcmcia/cistpl.h> +#include <pcmcia/ciscode.h> +#include <pcmcia/ds.h> +#include <pcmcia/cisreg.h> + +#include <net/bluetooth/bluetooth.h> +#include <net/bluetooth/hci_core.h> + + + +/* ======================== Module parameters ======================== */ + + +MODULE_AUTHOR("Marcel Holtmann <marcel@holtmann.org>"); +MODULE_DESCRIPTION("Bluetooth driver for Nokia Connectivity Card DTL-1"); +MODULE_LICENSE("GPL"); + + + +/* ======================== Local structures ======================== */ + + +typedef struct dtl1_info_t { + struct pcmcia_device *p_dev; + + struct hci_dev *hdev; + + spinlock_t lock; /* For serializing operations */ + + unsigned long flowmask; /* HCI flow mask */ + int ri_latch; + + struct sk_buff_head txq; + unsigned long tx_state; + + unsigned long rx_state; + unsigned long rx_count; + struct sk_buff *rx_skb; +} dtl1_info_t; + + +static int dtl1_config(struct pcmcia_device *link); +static void dtl1_release(struct pcmcia_device *link); + +static void dtl1_detach(struct pcmcia_device *p_dev); + + +/* Transmit states */ +#define XMIT_SENDING 1 +#define XMIT_WAKEUP 2 +#define XMIT_WAITING 8 + +/* Receiver States */ +#define RECV_WAIT_NSH 0 +#define RECV_WAIT_DATA 1 + + +typedef struct { + u8 type; + u8 zero; + u16 len; +} __packed nsh_t; /* Nokia Specific Header */ + +#define NSHL 4 /* Nokia Specific Header Length */ + + + +/* ======================== Interrupt handling ======================== */ + + +static int dtl1_write(unsigned int iobase, int fifo_size, __u8 *buf, int len) +{ + int actual = 0; + + /* Tx FIFO should be empty */ + if (!(inb(iobase + UART_LSR) & UART_LSR_THRE)) + return 0; + + /* Fill FIFO with current frame */ + while ((fifo_size-- > 0) && (actual < len)) { + /* Transmit next byte */ + outb(buf[actual], iobase + UART_TX); + actual++; + } + + return actual; +} + + +static void dtl1_write_wakeup(dtl1_info_t *info) +{ + if (!info) { + BT_ERR("Unknown device"); + return; + } + + if (test_bit(XMIT_WAITING, &(info->tx_state))) { + set_bit(XMIT_WAKEUP, &(info->tx_state)); + return; + } + + if (test_and_set_bit(XMIT_SENDING, &(info->tx_state))) { + set_bit(XMIT_WAKEUP, &(info->tx_state)); + return; + } + + do { + register unsigned int iobase = info->p_dev->resource[0]->start; + register struct sk_buff *skb; + register int len; + + clear_bit(XMIT_WAKEUP, &(info->tx_state)); + + if (!pcmcia_dev_present(info->p_dev)) + return; + + if (!(skb = skb_dequeue(&(info->txq)))) + break; + + /* Send frame */ + len = dtl1_write(iobase, 32, skb->data, skb->len); + + if (len == skb->len) { + set_bit(XMIT_WAITING, &(info->tx_state)); + kfree_skb(skb); + } else { + skb_pull(skb, len); + skb_queue_head(&(info->txq), skb); + } + + info->hdev->stat.byte_tx += len; + + } while (test_bit(XMIT_WAKEUP, &(info->tx_state))); + + clear_bit(XMIT_SENDING, &(info->tx_state)); +} + + +static void dtl1_control(dtl1_info_t *info, struct sk_buff *skb) +{ + u8 flowmask = *(u8 *)skb->data; + int i; + + printk(KERN_INFO "Bluetooth: Nokia control data ="); + for (i = 0; i < skb->len; i++) { + printk(" %02x", skb->data[i]); + } + printk("\n"); + + /* transition to active state */ + if (((info->flowmask & 0x07) == 0) && ((flowmask & 0x07) != 0)) { + clear_bit(XMIT_WAITING, &(info->tx_state)); + dtl1_write_wakeup(info); + } + + info->flowmask = flowmask; + + kfree_skb(skb); +} + + +static void dtl1_receive(dtl1_info_t *info) +{ + unsigned int iobase; + nsh_t *nsh; + int boguscount = 0; + + if (!info) { + BT_ERR("Unknown device"); + return; + } + + iobase = info->p_dev->resource[0]->start; + + do { + info->hdev->stat.byte_rx++; + + /* Allocate packet */ + if (info->rx_skb == NULL) + if (!(info->rx_skb = bt_skb_alloc(HCI_MAX_FRAME_SIZE, GFP_ATOMIC))) { + BT_ERR("Can't allocate mem for new packet"); + info->rx_state = RECV_WAIT_NSH; + info->rx_count = NSHL; + return; + } + + *skb_put(info->rx_skb, 1) = inb(iobase + UART_RX); + nsh = (nsh_t *)info->rx_skb->data; + + info->rx_count--; + + if (info->rx_count == 0) { + + switch (info->rx_state) { + case RECV_WAIT_NSH: + info->rx_state = RECV_WAIT_DATA; + info->rx_count = nsh->len + (nsh->len & 0x0001); + break; + case RECV_WAIT_DATA: + bt_cb(info->rx_skb)->pkt_type = nsh->type; + + /* remove PAD byte if it exists */ + if (nsh->len & 0x0001) { + info->rx_skb->tail--; + info->rx_skb->len--; + } + + /* remove NSH */ + skb_pull(info->rx_skb, NSHL); + + switch (bt_cb(info->rx_skb)->pkt_type) { + case 0x80: + /* control data for the Nokia Card */ + dtl1_control(info, info->rx_skb); + break; + case 0x82: + case 0x83: + case 0x84: + /* send frame to the HCI layer */ + info->rx_skb->dev = (void *) info->hdev; + bt_cb(info->rx_skb)->pkt_type &= 0x0f; + hci_recv_frame(info->rx_skb); + break; + default: + /* unknown packet */ + BT_ERR("Unknown HCI packet with type 0x%02x received", bt_cb(info->rx_skb)->pkt_type); + kfree_skb(info->rx_skb); + break; + } + + info->rx_state = RECV_WAIT_NSH; + info->rx_count = NSHL; + info->rx_skb = NULL; + break; + } + + } + + /* Make sure we don't stay here too long */ + if (boguscount++ > 32) + break; + + } while (inb(iobase + UART_LSR) & UART_LSR_DR); +} + + +static irqreturn_t dtl1_interrupt(int irq, void *dev_inst) +{ + dtl1_info_t *info = dev_inst; + unsigned int iobase; + unsigned char msr; + int boguscount = 0; + int iir, lsr; + irqreturn_t r = IRQ_NONE; + + if (!info || !info->hdev) + /* our irq handler is shared */ + return IRQ_NONE; + + iobase = info->p_dev->resource[0]->start; + + spin_lock(&(info->lock)); + + iir = inb(iobase + UART_IIR) & UART_IIR_ID; + while (iir) { + + r = IRQ_HANDLED; + /* Clear interrupt */ + lsr = inb(iobase + UART_LSR); + + switch (iir) { + case UART_IIR_RLSI: + BT_ERR("RLSI"); + break; + case UART_IIR_RDI: + /* Receive interrupt */ + dtl1_receive(info); + break; + case UART_IIR_THRI: + if (lsr & UART_LSR_THRE) { + /* Transmitter ready for data */ + dtl1_write_wakeup(info); + } + break; + default: + BT_ERR("Unhandled IIR=%#x", iir); + break; + } + + /* Make sure we don't stay here too long */ + if (boguscount++ > 100) + break; + + iir = inb(iobase + UART_IIR) & UART_IIR_ID; + + } + + msr = inb(iobase + UART_MSR); + + if (info->ri_latch ^ (msr & UART_MSR_RI)) { + info->ri_latch = msr & UART_MSR_RI; + clear_bit(XMIT_WAITING, &(info->tx_state)); + dtl1_write_wakeup(info); + r = IRQ_HANDLED; + } + + spin_unlock(&(info->lock)); + + return r; +} + + + +/* ======================== HCI interface ======================== */ + + +static int dtl1_hci_open(struct hci_dev *hdev) +{ + set_bit(HCI_RUNNING, &(hdev->flags)); + + return 0; +} + + +static int dtl1_hci_flush(struct hci_dev *hdev) +{ + dtl1_info_t *info = (dtl1_info_t *)(hdev->driver_data); + + /* Drop TX queue */ + skb_queue_purge(&(info->txq)); + + return 0; +} + + +static int dtl1_hci_close(struct hci_dev *hdev) +{ + if (!test_and_clear_bit(HCI_RUNNING, &(hdev->flags))) + return 0; + + dtl1_hci_flush(hdev); + + return 0; +} + + +static int dtl1_hci_send_frame(struct sk_buff *skb) +{ + dtl1_info_t *info; + struct hci_dev *hdev = (struct hci_dev *)(skb->dev); + struct sk_buff *s; + nsh_t nsh; + + if (!hdev) { + BT_ERR("Frame for unknown HCI device (hdev=NULL)"); + return -ENODEV; + } + + info = (dtl1_info_t *)(hdev->driver_data); + + switch (bt_cb(skb)->pkt_type) { + case HCI_COMMAND_PKT: + hdev->stat.cmd_tx++; + nsh.type = 0x81; + break; + case HCI_ACLDATA_PKT: + hdev->stat.acl_tx++; + nsh.type = 0x82; + break; + case HCI_SCODATA_PKT: + hdev->stat.sco_tx++; + nsh.type = 0x83; + break; + default: + return -EILSEQ; + }; + + nsh.zero = 0; + nsh.len = skb->len; + + s = bt_skb_alloc(NSHL + skb->len + 1, GFP_ATOMIC); + if (!s) + return -ENOMEM; + + skb_reserve(s, NSHL); + skb_copy_from_linear_data(skb, skb_put(s, skb->len), skb->len); + if (skb->len & 0x0001) + *skb_put(s, 1) = 0; /* PAD */ + + /* Prepend skb with Nokia frame header and queue */ + memcpy(skb_push(s, NSHL), &nsh, NSHL); + skb_queue_tail(&(info->txq), s); + + dtl1_write_wakeup(info); + + kfree_skb(skb); + + return 0; +} + + +static void dtl1_hci_destruct(struct hci_dev *hdev) +{ +} + + +static int dtl1_hci_ioctl(struct hci_dev *hdev, unsigned int cmd, unsigned long arg) +{ + return -ENOIOCTLCMD; +} + + + +/* ======================== Card services HCI interaction ======================== */ + + +static int dtl1_open(dtl1_info_t *info) +{ + unsigned long flags; + unsigned int iobase = info->p_dev->resource[0]->start; + struct hci_dev *hdev; + + spin_lock_init(&(info->lock)); + + skb_queue_head_init(&(info->txq)); + + info->rx_state = RECV_WAIT_NSH; + info->rx_count = NSHL; + info->rx_skb = NULL; + + set_bit(XMIT_WAITING, &(info->tx_state)); + + /* Initialize HCI device */ + hdev = hci_alloc_dev(); + if (!hdev) { + BT_ERR("Can't allocate HCI device"); + return -ENOMEM; + } + + info->hdev = hdev; + + hdev->bus = HCI_PCCARD; + hdev->driver_data = info; + SET_HCIDEV_DEV(hdev, &info->p_dev->dev); + + hdev->open = dtl1_hci_open; + hdev->close = dtl1_hci_close; + hdev->flush = dtl1_hci_flush; + hdev->send = dtl1_hci_send_frame; + hdev->destruct = dtl1_hci_destruct; + hdev->ioctl = dtl1_hci_ioctl; + + hdev->owner = THIS_MODULE; + + spin_lock_irqsave(&(info->lock), flags); + + /* Reset UART */ + outb(0, iobase + UART_MCR); + + /* Turn off interrupts */ + outb(0, iobase + UART_IER); + + /* Initialize UART */ + outb(UART_LCR_WLEN8, iobase + UART_LCR); /* Reset DLAB */ + outb((UART_MCR_DTR | UART_MCR_RTS | UART_MCR_OUT2), iobase + UART_MCR); + + info->ri_latch = inb(info->p_dev->resource[0]->start + UART_MSR) + & UART_MSR_RI; + + /* Turn on interrupts */ + outb(UART_IER_RLSI | UART_IER_RDI | UART_IER_THRI, iobase + UART_IER); + + spin_unlock_irqrestore(&(info->lock), flags); + + /* Timeout before it is safe to send the first HCI packet */ + msleep(2000); + + /* Register HCI device */ + if (hci_register_dev(hdev) < 0) { + BT_ERR("Can't register HCI device"); + info->hdev = NULL; + hci_free_dev(hdev); + return -ENODEV; + } + + return 0; +} + + +static int dtl1_close(dtl1_info_t *info) +{ + unsigned long flags; + unsigned int iobase = info->p_dev->resource[0]->start; + struct hci_dev *hdev = info->hdev; + + if (!hdev) + return -ENODEV; + + dtl1_hci_close(hdev); + + spin_lock_irqsave(&(info->lock), flags); + + /* Reset UART */ + outb(0, iobase + UART_MCR); + + /* Turn off interrupts */ + outb(0, iobase + UART_IER); + + spin_unlock_irqrestore(&(info->lock), flags); + + if (hci_unregister_dev(hdev) < 0) + BT_ERR("Can't unregister HCI device %s", hdev->name); + + hci_free_dev(hdev); + + return 0; +} + +static int dtl1_probe(struct pcmcia_device *link) +{ + dtl1_info_t *info; + + /* Create new info device */ + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->p_dev = link; + link->priv = info; + + link->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_SET_IO; + + return dtl1_config(link); +} + + +static void dtl1_detach(struct pcmcia_device *link) +{ + dtl1_info_t *info = link->priv; + + dtl1_release(link); + + kfree(info); +} + +static int dtl1_confcheck(struct pcmcia_device *p_dev, void *priv_data) +{ + if ((p_dev->resource[1]->end) || (p_dev->resource[1]->end < 8)) + return -ENODEV; + + p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH; + p_dev->resource[0]->flags |= IO_DATA_PATH_WIDTH_8; + + return pcmcia_request_io(p_dev); +} + +static int dtl1_config(struct pcmcia_device *link) +{ + dtl1_info_t *info = link->priv; + int i; + + /* Look for a generic full-sized window */ + link->resource[0]->end = 8; + if (pcmcia_loop_config(link, dtl1_confcheck, NULL) < 0) + goto failed; + + i = pcmcia_request_irq(link, dtl1_interrupt); + if (i != 0) + goto failed; + + i = pcmcia_enable_device(link); + if (i != 0) + goto failed; + + if (dtl1_open(info) != 0) + goto failed; + + return 0; + +failed: + dtl1_release(link); + return -ENODEV; +} + + +static void dtl1_release(struct pcmcia_device *link) +{ + dtl1_info_t *info = link->priv; + + dtl1_close(info); + + pcmcia_disable_device(link); +} + + +static const struct pcmcia_device_id dtl1_ids[] = { + PCMCIA_DEVICE_PROD_ID12("Nokia Mobile Phones", "DTL-1", 0xe1bfdd64, 0xe168480d), + PCMCIA_DEVICE_PROD_ID12("Nokia Mobile Phones", "DTL-4", 0xe1bfdd64, 0x9102bc82), + PCMCIA_DEVICE_PROD_ID12("Socket", "CF", 0xb38bcc2e, 0x44ebf863), + PCMCIA_DEVICE_PROD_ID12("Socket", "CF+ Personal Network Card", 0xb38bcc2e, 0xe732bae3), + PCMCIA_DEVICE_NULL +}; +MODULE_DEVICE_TABLE(pcmcia, dtl1_ids); + +static struct pcmcia_driver dtl1_driver = { + .owner = THIS_MODULE, + .name = "dtl1_cs", + .probe = dtl1_probe, + .remove = dtl1_detach, + .id_table = dtl1_ids, +}; + +static int __init init_dtl1_cs(void) +{ + return pcmcia_register_driver(&dtl1_driver); +} + + +static void __exit exit_dtl1_cs(void) +{ + pcmcia_unregister_driver(&dtl1_driver); +} + +module_init(init_dtl1_cs); +module_exit(exit_dtl1_cs); diff --git a/drivers/bluetooth/hci_ath.c b/drivers/bluetooth/hci_ath.c new file mode 100644 index 00000000..4093935d --- /dev/null +++ b/drivers/bluetooth/hci_ath.c @@ -0,0 +1,240 @@ +/* + * Atheros Communication Bluetooth HCIATH3K UART protocol + * + * HCIATH3K (HCI Atheros AR300x Protocol) is a Atheros Communication's + * power management protocol extension to H4 to support AR300x Bluetooth Chip. + * + * Copyright (c) 2009-2010 Atheros Communications Inc. + * + * Acknowledgements: + * This file is based on hci_h4.c, which was written + * by Maxim Krasnyansky and Marcel Holtmann. + * + * 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" + +struct ath_struct { + struct hci_uart *hu; + unsigned int cur_sleep; + + struct sk_buff_head txq; + struct work_struct ctxtsw; +}; + +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); + + return status; +} + +static void ath_hci_uart_work(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_ar3k(tty); + if (!(status & TIOCM_CTS)) + return; + } + + /* Ready to send Data */ + clear_bit(HCI_UART_SENDING, &hu->tx_state); + hci_uart_tx_wakeup(hu); +} + +/* 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); + + hu->priv = ath; + ath->hu = hu; + + INIT_WORK(&ath->ctxtsw, ath_hci_uart_work); + + 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); + + cancel_work_sync(&ath->ctxtsw); + + hu->priv = NULL; + kfree(ath); + + return 0; +} + +#define HCI_OP_ATH_SLEEP 0xFC04 + +/* 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) { + kfree_skb(skb); + return 0; + } + + /* + * Update power management enable flag with parameters of + * HCI sleep enable vendor specific HCI command. + */ + 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) + ath->cur_sleep = skb->data[HCI_COMMAND_HDR_SIZE]; + } + + 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; + + return skb_dequeue(&ath->txq); +} + +/* Recv data */ +static int ath_recv(struct hci_uart *hu, void *data, int count) +{ + int ret; + + ret = hci_recv_stream_fragment(hu->hdev, data, count); + if (ret < 0) { + BT_ERR("Frame Reassembly Failed"); + return ret; + } + + return count; +} + +static struct hci_uart_proto athp = { + .id = HCI_UART_ATH3K, + .open = ath_open, + .close = ath_close, + .recv = ath_recv, + .enqueue = ath_enqueue, + .dequeue = ath_dequeue, + .flush = ath_flush, +}; + +int __init ath_init(void) +{ + int err = hci_uart_register_proto(&athp); + + if (!err) + BT_INFO("HCIATH3K protocol initialized"); + else + BT_ERR("HCIATH3K protocol registration failed"); + + return err; +} + +int __exit ath_deinit(void) +{ + return hci_uart_unregister_proto(&athp); +} diff --git a/drivers/bluetooth/hci_bcsp.c b/drivers/bluetooth/hci_bcsp.c new file mode 100644 index 00000000..9c5b2dc3 --- /dev/null +++ b/drivers/bluetooth/hci_bcsp.c @@ -0,0 +1,763 @@ +/* + * + * Bluetooth HCI UART driver + * + * Copyright (C) 2002-2003 Fabrizio Gennari <fabrizio.gennari@philips.com> + * Copyright (C) 2004-2005 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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/types.h> +#include <linux/fcntl.h> +#include <linux/interrupt.h> +#include <linux/ptrace.h> +#include <linux/poll.h> + +#include <linux/slab.h> +#include <linux/tty.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/signal.h> +#include <linux/ioctl.h> +#include <linux/skbuff.h> +#include <linux/bitrev.h> +#include <asm/unaligned.h> + +#include <net/bluetooth/bluetooth.h> +#include <net/bluetooth/hci_core.h> + +#include "hci_uart.h" + +#define VERSION "0.3" + +static int txcrc = 1; +static int hciextn = 1; + +#define BCSP_TXWINSIZE 4 + +#define BCSP_ACK_PKT 0x05 +#define BCSP_LE_PKT 0x06 + +struct bcsp_struct { + struct sk_buff_head unack; /* Unack'ed packets queue */ + struct sk_buff_head rel; /* Reliable packets queue */ + struct sk_buff_head unrel; /* Unreliable packets queue */ + + unsigned long rx_count; + struct sk_buff *rx_skb; + u8 rxseq_txack; /* rxseq == txack. */ + u8 rxack; /* Last packet sent by us that the peer ack'ed */ + struct timer_list tbcsp; + + enum { + BCSP_W4_PKT_DELIMITER, + BCSP_W4_PKT_START, + BCSP_W4_BCSP_HDR, + BCSP_W4_DATA, + BCSP_W4_CRC + } rx_state; + + enum { + BCSP_ESCSTATE_NOESC, + BCSP_ESCSTATE_ESC + } rx_esc_state; + + u8 use_crc; + u16 message_crc; + u8 txack_req; /* Do we need to send ack's to the peer? */ + + /* Reliable packet sequence number - used to assign seq to each rel pkt. */ + u8 msgq_txseq; +}; + +/* ---- BCSP CRC calculation ---- */ + +/* Table for calculating CRC for polynomial 0x1021, LSB processed first, +initial value 0xffff, bits shifted in reverse order. */ + +static const u16 crc_table[] = { + 0x0000, 0x1081, 0x2102, 0x3183, + 0x4204, 0x5285, 0x6306, 0x7387, + 0x8408, 0x9489, 0xa50a, 0xb58b, + 0xc60c, 0xd68d, 0xe70e, 0xf78f +}; + +/* Initialise the crc calculator */ +#define BCSP_CRC_INIT(x) x = 0xffff + +/* + Update crc with next data byte + + Implementation note + The data byte is treated as two nibbles. The crc is generated + in reverse, i.e., bits are fed into the register from the top. +*/ +static void bcsp_crc_update(u16 *crc, u8 d) +{ + u16 reg = *crc; + + reg = (reg >> 4) ^ crc_table[(reg ^ d) & 0x000f]; + reg = (reg >> 4) ^ crc_table[(reg ^ (d >> 4)) & 0x000f]; + + *crc = reg; +} + +/* ---- BCSP core ---- */ + +static void bcsp_slip_msgdelim(struct sk_buff *skb) +{ + const char pkt_delim = 0xc0; + + memcpy(skb_put(skb, 1), &pkt_delim, 1); +} + +static void bcsp_slip_one_byte(struct sk_buff *skb, u8 c) +{ + const char esc_c0[2] = { 0xdb, 0xdc }; + const char esc_db[2] = { 0xdb, 0xdd }; + + switch (c) { + case 0xc0: + memcpy(skb_put(skb, 2), &esc_c0, 2); + break; + case 0xdb: + memcpy(skb_put(skb, 2), &esc_db, 2); + break; + default: + memcpy(skb_put(skb, 1), &c, 1); + } +} + +static int bcsp_enqueue(struct hci_uart *hu, struct sk_buff *skb) +{ + struct bcsp_struct *bcsp = hu->priv; + + if (skb->len > 0xFFF) { + BT_ERR("Packet too long"); + kfree_skb(skb); + return 0; + } + + switch (bt_cb(skb)->pkt_type) { + case HCI_ACLDATA_PKT: + case HCI_COMMAND_PKT: + skb_queue_tail(&bcsp->rel, skb); + break; + + case HCI_SCODATA_PKT: + skb_queue_tail(&bcsp->unrel, skb); + break; + + default: + BT_ERR("Unknown packet type"); + kfree_skb(skb); + break; + } + + return 0; +} + +static struct sk_buff *bcsp_prepare_pkt(struct bcsp_struct *bcsp, u8 *data, + int len, int pkt_type) +{ + struct sk_buff *nskb; + u8 hdr[4], chan; + u16 BCSP_CRC_INIT(bcsp_txmsg_crc); + int rel, i; + + switch (pkt_type) { + case HCI_ACLDATA_PKT: + chan = 6; /* BCSP ACL channel */ + rel = 1; /* reliable channel */ + break; + case HCI_COMMAND_PKT: + chan = 5; /* BCSP cmd/evt channel */ + rel = 1; /* reliable channel */ + break; + case HCI_SCODATA_PKT: + chan = 7; /* BCSP SCO channel */ + rel = 0; /* unreliable channel */ + break; + case BCSP_LE_PKT: + chan = 1; /* BCSP LE channel */ + rel = 0; /* unreliable channel */ + break; + case BCSP_ACK_PKT: + chan = 0; /* BCSP internal channel */ + rel = 0; /* unreliable channel */ + break; + default: + BT_ERR("Unknown packet type"); + return NULL; + } + + if (hciextn && chan == 5) { + __le16 opcode = ((struct hci_command_hdr *)data)->opcode; + + /* Vendor specific commands */ + if (hci_opcode_ogf(__le16_to_cpu(opcode)) == 0x3f) { + u8 desc = *(data + HCI_COMMAND_HDR_SIZE); + if ((desc & 0xf0) == 0xc0) { + data += HCI_COMMAND_HDR_SIZE + 1; + len -= HCI_COMMAND_HDR_SIZE + 1; + chan = desc & 0x0f; + } + } + } + + /* Max len of packet: (original len +4(bcsp hdr) +2(crc))*2 + (because bytes 0xc0 and 0xdb are escaped, worst case is + when the packet is all made of 0xc0 and 0xdb :) ) + + 2 (0xc0 delimiters at start and end). */ + + nskb = alloc_skb((len + 6) * 2 + 2, GFP_ATOMIC); + if (!nskb) + return NULL; + + bt_cb(nskb)->pkt_type = pkt_type; + + bcsp_slip_msgdelim(nskb); + + hdr[0] = bcsp->rxseq_txack << 3; + bcsp->txack_req = 0; + BT_DBG("We request packet no %u to card", bcsp->rxseq_txack); + + if (rel) { + hdr[0] |= 0x80 + bcsp->msgq_txseq; + BT_DBG("Sending packet with seqno %u", bcsp->msgq_txseq); + bcsp->msgq_txseq = (bcsp->msgq_txseq + 1) & 0x07; + } + + if (bcsp->use_crc) + hdr[0] |= 0x40; + + hdr[1] = ((len << 4) & 0xff) | chan; + hdr[2] = len >> 4; + hdr[3] = ~(hdr[0] + hdr[1] + hdr[2]); + + /* Put BCSP header */ + for (i = 0; i < 4; i++) { + bcsp_slip_one_byte(nskb, hdr[i]); + + if (bcsp->use_crc) + bcsp_crc_update(&bcsp_txmsg_crc, hdr[i]); + } + + /* Put payload */ + for (i = 0; i < len; i++) { + bcsp_slip_one_byte(nskb, data[i]); + + if (bcsp->use_crc) + bcsp_crc_update(&bcsp_txmsg_crc, data[i]); + } + + /* Put CRC */ + if (bcsp->use_crc) { + bcsp_txmsg_crc = bitrev16(bcsp_txmsg_crc); + bcsp_slip_one_byte(nskb, (u8) ((bcsp_txmsg_crc >> 8) & 0x00ff)); + bcsp_slip_one_byte(nskb, (u8) (bcsp_txmsg_crc & 0x00ff)); + } + + bcsp_slip_msgdelim(nskb); + return nskb; +} + +/* This is a rewrite of pkt_avail in ABCSP */ +static struct sk_buff *bcsp_dequeue(struct hci_uart *hu) +{ + struct bcsp_struct *bcsp = hu->priv; + unsigned long flags; + struct sk_buff *skb; + + /* First of all, check for unreliable messages in the queue, + since they have priority */ + + if ((skb = skb_dequeue(&bcsp->unrel)) != NULL) { + struct sk_buff *nskb = bcsp_prepare_pkt(bcsp, skb->data, skb->len, bt_cb(skb)->pkt_type); + if (nskb) { + kfree_skb(skb); + return nskb; + } else { + skb_queue_head(&bcsp->unrel, skb); + BT_ERR("Could not dequeue pkt because alloc_skb failed"); + } + } + + /* Now, try to send a reliable pkt. We can only send a + reliable packet if the number of packets sent but not yet ack'ed + is < than the winsize */ + + spin_lock_irqsave_nested(&bcsp->unack.lock, flags, SINGLE_DEPTH_NESTING); + + if (bcsp->unack.qlen < BCSP_TXWINSIZE && (skb = skb_dequeue(&bcsp->rel)) != NULL) { + struct sk_buff *nskb = bcsp_prepare_pkt(bcsp, skb->data, skb->len, bt_cb(skb)->pkt_type); + if (nskb) { + __skb_queue_tail(&bcsp->unack, skb); + mod_timer(&bcsp->tbcsp, jiffies + HZ / 4); + spin_unlock_irqrestore(&bcsp->unack.lock, flags); + return nskb; + } else { + skb_queue_head(&bcsp->rel, skb); + BT_ERR("Could not dequeue pkt because alloc_skb failed"); + } + } + + spin_unlock_irqrestore(&bcsp->unack.lock, flags); + + /* We could not send a reliable packet, either because there are + none or because there are too many unack'ed pkts. Did we receive + any packets we have not acknowledged yet ? */ + + if (bcsp->txack_req) { + /* if so, craft an empty ACK pkt and send it on BCSP unreliable + channel 0 */ + struct sk_buff *nskb = bcsp_prepare_pkt(bcsp, NULL, 0, BCSP_ACK_PKT); + return nskb; + } + + /* We have nothing to send */ + return NULL; +} + +static int bcsp_flush(struct hci_uart *hu) +{ + BT_DBG("hu %p", hu); + return 0; +} + +/* Remove ack'ed packets */ +static void bcsp_pkt_cull(struct bcsp_struct *bcsp) +{ + struct sk_buff *skb, *tmp; + unsigned long flags; + int i, pkts_to_be_removed; + u8 seqno; + + spin_lock_irqsave(&bcsp->unack.lock, flags); + + pkts_to_be_removed = skb_queue_len(&bcsp->unack); + seqno = bcsp->msgq_txseq; + + while (pkts_to_be_removed) { + if (bcsp->rxack == seqno) + break; + pkts_to_be_removed--; + seqno = (seqno - 1) & 0x07; + } + + if (bcsp->rxack != seqno) + BT_ERR("Peer acked invalid packet"); + + BT_DBG("Removing %u pkts out of %u, up to seqno %u", + pkts_to_be_removed, skb_queue_len(&bcsp->unack), + (seqno - 1) & 0x07); + + i = 0; + skb_queue_walk_safe(&bcsp->unack, skb, tmp) { + if (i >= pkts_to_be_removed) + break; + i++; + + __skb_unlink(skb, &bcsp->unack); + kfree_skb(skb); + } + + if (skb_queue_empty(&bcsp->unack)) + del_timer(&bcsp->tbcsp); + + spin_unlock_irqrestore(&bcsp->unack.lock, flags); + + if (i != pkts_to_be_removed) + BT_ERR("Removed only %u out of %u pkts", i, pkts_to_be_removed); +} + +/* Handle BCSP link-establishment packets. When we + detect a "sync" packet, symptom that the BT module has reset, + we do nothing :) (yet) */ +static void bcsp_handle_le_pkt(struct hci_uart *hu) +{ + struct bcsp_struct *bcsp = hu->priv; + u8 conf_pkt[4] = { 0xad, 0xef, 0xac, 0xed }; + u8 conf_rsp_pkt[4] = { 0xde, 0xad, 0xd0, 0xd0 }; + u8 sync_pkt[4] = { 0xda, 0xdc, 0xed, 0xed }; + + /* spot "conf" pkts and reply with a "conf rsp" pkt */ + if (bcsp->rx_skb->data[1] >> 4 == 4 && bcsp->rx_skb->data[2] == 0 && + !memcmp(&bcsp->rx_skb->data[4], conf_pkt, 4)) { + struct sk_buff *nskb = alloc_skb(4, GFP_ATOMIC); + + BT_DBG("Found a LE conf pkt"); + if (!nskb) + return; + memcpy(skb_put(nskb, 4), conf_rsp_pkt, 4); + bt_cb(nskb)->pkt_type = BCSP_LE_PKT; + + skb_queue_head(&bcsp->unrel, nskb); + hci_uart_tx_wakeup(hu); + } + /* Spot "sync" pkts. If we find one...disaster! */ + else if (bcsp->rx_skb->data[1] >> 4 == 4 && bcsp->rx_skb->data[2] == 0 && + !memcmp(&bcsp->rx_skb->data[4], sync_pkt, 4)) { + BT_ERR("Found a LE sync pkt, card has reset"); + } +} + +static inline void bcsp_unslip_one_byte(struct bcsp_struct *bcsp, unsigned char byte) +{ + const u8 c0 = 0xc0, db = 0xdb; + + switch (bcsp->rx_esc_state) { + case BCSP_ESCSTATE_NOESC: + switch (byte) { + case 0xdb: + bcsp->rx_esc_state = BCSP_ESCSTATE_ESC; + break; + default: + memcpy(skb_put(bcsp->rx_skb, 1), &byte, 1); + if ((bcsp->rx_skb-> data[0] & 0x40) != 0 && + bcsp->rx_state != BCSP_W4_CRC) + bcsp_crc_update(&bcsp->message_crc, byte); + bcsp->rx_count--; + } + break; + + case BCSP_ESCSTATE_ESC: + switch (byte) { + case 0xdc: + memcpy(skb_put(bcsp->rx_skb, 1), &c0, 1); + if ((bcsp->rx_skb-> data[0] & 0x40) != 0 && + bcsp->rx_state != BCSP_W4_CRC) + bcsp_crc_update(&bcsp-> message_crc, 0xc0); + bcsp->rx_esc_state = BCSP_ESCSTATE_NOESC; + bcsp->rx_count--; + break; + + case 0xdd: + memcpy(skb_put(bcsp->rx_skb, 1), &db, 1); + if ((bcsp->rx_skb-> data[0] & 0x40) != 0 && + bcsp->rx_state != BCSP_W4_CRC) + bcsp_crc_update(&bcsp-> message_crc, 0xdb); + bcsp->rx_esc_state = BCSP_ESCSTATE_NOESC; + bcsp->rx_count--; + break; + + default: + BT_ERR ("Invalid byte %02x after esc byte", byte); + kfree_skb(bcsp->rx_skb); + bcsp->rx_skb = NULL; + bcsp->rx_state = BCSP_W4_PKT_DELIMITER; + bcsp->rx_count = 0; + } + } +} + +static void bcsp_complete_rx_pkt(struct hci_uart *hu) +{ + struct bcsp_struct *bcsp = hu->priv; + int pass_up; + + if (bcsp->rx_skb->data[0] & 0x80) { /* reliable pkt */ + BT_DBG("Received seqno %u from card", bcsp->rxseq_txack); + bcsp->rxseq_txack++; + bcsp->rxseq_txack %= 0x8; + bcsp->txack_req = 1; + + /* If needed, transmit an ack pkt */ + hci_uart_tx_wakeup(hu); + } + + bcsp->rxack = (bcsp->rx_skb->data[0] >> 3) & 0x07; + BT_DBG("Request for pkt %u from card", bcsp->rxack); + + bcsp_pkt_cull(bcsp); + if ((bcsp->rx_skb->data[1] & 0x0f) == 6 && + bcsp->rx_skb->data[0] & 0x80) { + bt_cb(bcsp->rx_skb)->pkt_type = HCI_ACLDATA_PKT; + pass_up = 1; + } else if ((bcsp->rx_skb->data[1] & 0x0f) == 5 && + bcsp->rx_skb->data[0] & 0x80) { + bt_cb(bcsp->rx_skb)->pkt_type = HCI_EVENT_PKT; + pass_up = 1; + } else if ((bcsp->rx_skb->data[1] & 0x0f) == 7) { + bt_cb(bcsp->rx_skb)->pkt_type = HCI_SCODATA_PKT; + pass_up = 1; + } else if ((bcsp->rx_skb->data[1] & 0x0f) == 1 && + !(bcsp->rx_skb->data[0] & 0x80)) { + bcsp_handle_le_pkt(hu); + pass_up = 0; + } else + pass_up = 0; + + if (!pass_up) { + struct hci_event_hdr hdr; + u8 desc = (bcsp->rx_skb->data[1] & 0x0f); + + if (desc != 0 && desc != 1) { + if (hciextn) { + desc |= 0xc0; + skb_pull(bcsp->rx_skb, 4); + memcpy(skb_push(bcsp->rx_skb, 1), &desc, 1); + + hdr.evt = 0xff; + hdr.plen = bcsp->rx_skb->len; + memcpy(skb_push(bcsp->rx_skb, HCI_EVENT_HDR_SIZE), &hdr, HCI_EVENT_HDR_SIZE); + bt_cb(bcsp->rx_skb)->pkt_type = HCI_EVENT_PKT; + + hci_recv_frame(bcsp->rx_skb); + } else { + BT_ERR ("Packet for unknown channel (%u %s)", + bcsp->rx_skb->data[1] & 0x0f, + bcsp->rx_skb->data[0] & 0x80 ? + "reliable" : "unreliable"); + kfree_skb(bcsp->rx_skb); + } + } else + kfree_skb(bcsp->rx_skb); + } else { + /* Pull out BCSP hdr */ + skb_pull(bcsp->rx_skb, 4); + + hci_recv_frame(bcsp->rx_skb); + } + + bcsp->rx_state = BCSP_W4_PKT_DELIMITER; + bcsp->rx_skb = NULL; +} + +static u16 bscp_get_crc(struct bcsp_struct *bcsp) +{ + return get_unaligned_be16(&bcsp->rx_skb->data[bcsp->rx_skb->len - 2]); +} + +/* Recv data */ +static int bcsp_recv(struct hci_uart *hu, void *data, int count) +{ + struct bcsp_struct *bcsp = hu->priv; + register unsigned char *ptr; + + BT_DBG("hu %p count %d rx_state %d rx_count %ld", + hu, count, bcsp->rx_state, bcsp->rx_count); + + ptr = data; + while (count) { + if (bcsp->rx_count) { + if (*ptr == 0xc0) { + BT_ERR("Short BCSP packet"); + kfree_skb(bcsp->rx_skb); + bcsp->rx_state = BCSP_W4_PKT_START; + bcsp->rx_count = 0; + } else + bcsp_unslip_one_byte(bcsp, *ptr); + + ptr++; count--; + continue; + } + + switch (bcsp->rx_state) { + case BCSP_W4_BCSP_HDR: + if ((0xff & (u8) ~ (bcsp->rx_skb->data[0] + bcsp->rx_skb->data[1] + + bcsp->rx_skb->data[2])) != bcsp->rx_skb->data[3]) { + BT_ERR("Error in BCSP hdr checksum"); + kfree_skb(bcsp->rx_skb); + bcsp->rx_state = BCSP_W4_PKT_DELIMITER; + bcsp->rx_count = 0; + continue; + } + if (bcsp->rx_skb->data[0] & 0x80 /* reliable pkt */ + && (bcsp->rx_skb->data[0] & 0x07) != bcsp->rxseq_txack) { + BT_ERR ("Out-of-order packet arrived, got %u expected %u", + bcsp->rx_skb->data[0] & 0x07, bcsp->rxseq_txack); + + kfree_skb(bcsp->rx_skb); + bcsp->rx_state = BCSP_W4_PKT_DELIMITER; + bcsp->rx_count = 0; + continue; + } + bcsp->rx_state = BCSP_W4_DATA; + bcsp->rx_count = (bcsp->rx_skb->data[1] >> 4) + + (bcsp->rx_skb->data[2] << 4); /* May be 0 */ + continue; + + case BCSP_W4_DATA: + if (bcsp->rx_skb->data[0] & 0x40) { /* pkt with crc */ + bcsp->rx_state = BCSP_W4_CRC; + bcsp->rx_count = 2; + } else + bcsp_complete_rx_pkt(hu); + continue; + + case BCSP_W4_CRC: + if (bitrev16(bcsp->message_crc) != bscp_get_crc(bcsp)) { + BT_ERR ("Checksum failed: computed %04x received %04x", + bitrev16(bcsp->message_crc), + bscp_get_crc(bcsp)); + + kfree_skb(bcsp->rx_skb); + bcsp->rx_state = BCSP_W4_PKT_DELIMITER; + bcsp->rx_count = 0; + continue; + } + skb_trim(bcsp->rx_skb, bcsp->rx_skb->len - 2); + bcsp_complete_rx_pkt(hu); + continue; + + case BCSP_W4_PKT_DELIMITER: + switch (*ptr) { + case 0xc0: + bcsp->rx_state = BCSP_W4_PKT_START; + break; + default: + /*BT_ERR("Ignoring byte %02x", *ptr);*/ + break; + } + ptr++; count--; + break; + + case BCSP_W4_PKT_START: + switch (*ptr) { + case 0xc0: + ptr++; count--; + break; + + default: + bcsp->rx_state = BCSP_W4_BCSP_HDR; + bcsp->rx_count = 4; + bcsp->rx_esc_state = BCSP_ESCSTATE_NOESC; + BCSP_CRC_INIT(bcsp->message_crc); + + /* Do not increment ptr or decrement count + * Allocate packet. Max len of a BCSP pkt= + * 0xFFF (payload) +4 (header) +2 (crc) */ + + bcsp->rx_skb = bt_skb_alloc(0x1005, GFP_ATOMIC); + if (!bcsp->rx_skb) { + BT_ERR("Can't allocate mem for new packet"); + bcsp->rx_state = BCSP_W4_PKT_DELIMITER; + bcsp->rx_count = 0; + return 0; + } + bcsp->rx_skb->dev = (void *) hu->hdev; + break; + } + break; + } + } + return count; +} + + /* Arrange to retransmit all messages in the relq. */ +static void bcsp_timed_event(unsigned long arg) +{ + struct hci_uart *hu = (struct hci_uart *) arg; + struct bcsp_struct *bcsp = hu->priv; + struct sk_buff *skb; + unsigned long flags; + + BT_DBG("hu %p retransmitting %u pkts", hu, bcsp->unack.qlen); + + spin_lock_irqsave_nested(&bcsp->unack.lock, flags, SINGLE_DEPTH_NESTING); + + while ((skb = __skb_dequeue_tail(&bcsp->unack)) != NULL) { + bcsp->msgq_txseq = (bcsp->msgq_txseq - 1) & 0x07; + skb_queue_head(&bcsp->rel, skb); + } + + spin_unlock_irqrestore(&bcsp->unack.lock, flags); + + hci_uart_tx_wakeup(hu); +} + +static int bcsp_open(struct hci_uart *hu) +{ + struct bcsp_struct *bcsp; + + BT_DBG("hu %p", hu); + + bcsp = kzalloc(sizeof(*bcsp), GFP_ATOMIC); + if (!bcsp) + return -ENOMEM; + + hu->priv = bcsp; + skb_queue_head_init(&bcsp->unack); + skb_queue_head_init(&bcsp->rel); + skb_queue_head_init(&bcsp->unrel); + + init_timer(&bcsp->tbcsp); + bcsp->tbcsp.function = bcsp_timed_event; + bcsp->tbcsp.data = (u_long) hu; + + bcsp->rx_state = BCSP_W4_PKT_DELIMITER; + + if (txcrc) + bcsp->use_crc = 1; + + return 0; +} + +static int bcsp_close(struct hci_uart *hu) +{ + struct bcsp_struct *bcsp = hu->priv; + hu->priv = NULL; + + BT_DBG("hu %p", hu); + + skb_queue_purge(&bcsp->unack); + skb_queue_purge(&bcsp->rel); + skb_queue_purge(&bcsp->unrel); + del_timer(&bcsp->tbcsp); + + kfree(bcsp); + return 0; +} + +static struct hci_uart_proto bcsp = { + .id = HCI_UART_BCSP, + .open = bcsp_open, + .close = bcsp_close, + .enqueue = bcsp_enqueue, + .dequeue = bcsp_dequeue, + .recv = bcsp_recv, + .flush = bcsp_flush +}; + +int __init bcsp_init(void) +{ + int err = hci_uart_register_proto(&bcsp); + + if (!err) + BT_INFO("HCI BCSP protocol initialized"); + else + BT_ERR("HCI BCSP protocol registration failed"); + + return err; +} + +int __exit bcsp_deinit(void) +{ + return hci_uart_unregister_proto(&bcsp); +} + +module_param(txcrc, bool, 0644); +MODULE_PARM_DESC(txcrc, "Transmit CRC with every BCSP packet"); + +module_param(hciextn, bool, 0644); +MODULE_PARM_DESC(hciextn, "Convert HCI Extensions into BCSP packets"); diff --git a/drivers/bluetooth/hci_h4.c b/drivers/bluetooth/hci_h4.c new file mode 100644 index 00000000..2fcd8b38 --- /dev/null +++ b/drivers/bluetooth/hci_h4.c @@ -0,0 +1,196 @@ +/* + * + * Bluetooth HCI UART driver + * + * Copyright (C) 2000-2001 Qualcomm Incorporated + * Copyright (C) 2002-2003 Maxim Krasnyansky <maxk@qualcomm.com> + * Copyright (C) 2004-2005 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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/types.h> +#include <linux/fcntl.h> +#include <linux/interrupt.h> +#include <linux/ptrace.h> +#include <linux/poll.h> + +#include <linux/slab.h> +#include <linux/tty.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/signal.h> +#include <linux/ioctl.h> +#include <linux/skbuff.h> + +#include <net/bluetooth/bluetooth.h> +#include <net/bluetooth/hci_core.h> + +#include "hci_uart.h" + +#define VERSION "1.2" + +struct h4_struct { + unsigned long rx_state; + unsigned long rx_count; + struct sk_buff *rx_skb; + struct sk_buff_head txq; +}; + +/* H4 receiver States */ +#define H4_W4_PACKET_TYPE 0 +#define H4_W4_EVENT_HDR 1 +#define H4_W4_ACL_HDR 2 +#define H4_W4_SCO_HDR 3 +#define H4_W4_DATA 4 + +/* Initialize protocol */ +static int h4_open(struct hci_uart *hu) +{ + struct h4_struct *h4; + + BT_DBG("hu %p", hu); + + h4 = kzalloc(sizeof(*h4), GFP_ATOMIC); + if (!h4) + return -ENOMEM; + + skb_queue_head_init(&h4->txq); + + hu->priv = h4; + return 0; +} + +/* Flush protocol data */ +static int h4_flush(struct hci_uart *hu) +{ + struct h4_struct *h4 = hu->priv; + + BT_DBG("hu %p", hu); + + skb_queue_purge(&h4->txq); + + return 0; +} + +/* Close protocol */ +static int h4_close(struct hci_uart *hu) +{ + struct h4_struct *h4 = hu->priv; + + hu->priv = NULL; + + BT_DBG("hu %p", hu); + + skb_queue_purge(&h4->txq); + + kfree_skb(h4->rx_skb); + + hu->priv = NULL; + kfree(h4); + + return 0; +} + +/* Enqueue frame for transmittion (padding, crc, etc) */ +static int h4_enqueue(struct hci_uart *hu, struct sk_buff *skb) +{ + struct h4_struct *h4 = hu->priv; + + 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(&h4->txq, skb); + + return 0; +} + +static inline int h4_check_data_len(struct h4_struct *h4, int len) +{ + register int room = skb_tailroom(h4->rx_skb); + + BT_DBG("len %d room %d", len, room); + + if (!len) { + hci_recv_frame(h4->rx_skb); + } else if (len > room) { + BT_ERR("Data length is too large"); + kfree_skb(h4->rx_skb); + } else { + h4->rx_state = H4_W4_DATA; + h4->rx_count = len; + return len; + } + + h4->rx_state = H4_W4_PACKET_TYPE; + h4->rx_skb = NULL; + h4->rx_count = 0; + + return 0; +} + +/* Recv data */ +static int h4_recv(struct hci_uart *hu, void *data, int count) +{ + int ret; + + ret = hci_recv_stream_fragment(hu->hdev, data, count); + if (ret < 0) { + BT_ERR("Frame Reassembly Failed"); + return ret; + } + + return count; +} + +static struct sk_buff *h4_dequeue(struct hci_uart *hu) +{ + struct h4_struct *h4 = hu->priv; + return skb_dequeue(&h4->txq); +} + +static struct hci_uart_proto h4p = { + .id = HCI_UART_H4, + .open = h4_open, + .close = h4_close, + .recv = h4_recv, + .enqueue = h4_enqueue, + .dequeue = h4_dequeue, + .flush = h4_flush, +}; + +int __init h4_init(void) +{ + int err = hci_uart_register_proto(&h4p); + + if (!err) + BT_INFO("HCI H4 protocol initialized"); + else + BT_ERR("HCI H4 protocol registration failed"); + + return err; +} + +int __exit h4_deinit(void) +{ + return hci_uart_unregister_proto(&h4p); +} diff --git a/drivers/bluetooth/hci_ldisc.c b/drivers/bluetooth/hci_ldisc.c new file mode 100644 index 00000000..8f3d6dbe --- /dev/null +++ b/drivers/bluetooth/hci_ldisc.c @@ -0,0 +1,604 @@ +/* + * + * Bluetooth HCI UART driver + * + * Copyright (C) 2000-2001 Qualcomm Incorporated + * Copyright (C) 2002-2003 Maxim Krasnyansky <maxk@qualcomm.com> + * Copyright (C) 2004-2005 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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/types.h> +#include <linux/fcntl.h> +#include <linux/interrupt.h> +#include <linux/ptrace.h> +#include <linux/poll.h> + +#include <linux/slab.h> +#include <linux/tty.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/signal.h> +#include <linux/ioctl.h> +#include <linux/skbuff.h> + +#include <net/bluetooth/bluetooth.h> +#include <net/bluetooth/hci_core.h> + +#include "hci_uart.h" + +#define VERSION "2.2" + +static int reset = 0; + +static struct hci_uart_proto *hup[HCI_UART_MAX_PROTO]; + +int hci_uart_register_proto(struct hci_uart_proto *p) +{ + if (p->id >= HCI_UART_MAX_PROTO) + return -EINVAL; + + if (hup[p->id]) + return -EEXIST; + + hup[p->id] = p; + + return 0; +} + +int hci_uart_unregister_proto(struct hci_uart_proto *p) +{ + if (p->id >= HCI_UART_MAX_PROTO) + return -EINVAL; + + if (!hup[p->id]) + return -EINVAL; + + hup[p->id] = NULL; + + return 0; +} + +static struct hci_uart_proto *hci_uart_get_proto(unsigned int id) +{ + if (id >= HCI_UART_MAX_PROTO) + return NULL; + + return hup[id]; +} + +static inline void hci_uart_tx_complete(struct hci_uart *hu, int pkt_type) +{ + struct hci_dev *hdev = hu->hdev; + + /* Update HCI stat counters */ + switch (pkt_type) { + case HCI_COMMAND_PKT: + hdev->stat.cmd_tx++; + break; + + case HCI_ACLDATA_PKT: + hdev->stat.acl_tx++; + break; + + case HCI_SCODATA_PKT: + hdev->stat.sco_tx++; + break; + } +} + +static inline struct sk_buff *hci_uart_dequeue(struct hci_uart *hu) +{ + struct sk_buff *skb = hu->tx_skb; + + if (!skb) + skb = hu->proto->dequeue(hu); + else + hu->tx_skb = NULL; + + return skb; +} + +int hci_uart_tx_wakeup(struct hci_uart *hu) +{ + struct tty_struct *tty = hu->tty; + struct hci_dev *hdev = hu->hdev; + struct sk_buff *skb; + + if (test_and_set_bit(HCI_UART_SENDING, &hu->tx_state)) { + set_bit(HCI_UART_TX_WAKEUP, &hu->tx_state); + return 0; + } + + BT_DBG(""); + +restart: + clear_bit(HCI_UART_TX_WAKEUP, &hu->tx_state); + + while ((skb = hci_uart_dequeue(hu))) { + int len; + + set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); + len = tty->ops->write(tty, skb->data, skb->len); + hdev->stat.byte_tx += len; + + skb_pull(skb, len); + if (skb->len) { + hu->tx_skb = skb; + break; + } + + hci_uart_tx_complete(hu, bt_cb(skb)->pkt_type); + kfree_skb(skb); + } + + if (test_bit(HCI_UART_TX_WAKEUP, &hu->tx_state)) + goto restart; + + clear_bit(HCI_UART_SENDING, &hu->tx_state); + return 0; +} + +/* ------- Interface to HCI layer ------ */ +/* Initialize device */ +static int hci_uart_open(struct hci_dev *hdev) +{ + BT_DBG("%s %p", hdev->name, hdev); + + /* Nothing to do for UART driver */ + + set_bit(HCI_RUNNING, &hdev->flags); + + return 0; +} + +/* Reset device */ +static int hci_uart_flush(struct hci_dev *hdev) +{ + struct hci_uart *hu = (struct hci_uart *) hdev->driver_data; + struct tty_struct *tty = hu->tty; + + BT_DBG("hdev %p tty %p", hdev, tty); + + if (hu->tx_skb) { + kfree_skb(hu->tx_skb); hu->tx_skb = NULL; + } + + /* Flush any pending characters in the driver and discipline. */ + tty_ldisc_flush(tty); + tty_driver_flush_buffer(tty); + + if (test_bit(HCI_UART_PROTO_SET, &hu->flags)) + hu->proto->flush(hu); + + return 0; +} + +/* Close device */ +static int hci_uart_close(struct hci_dev *hdev) +{ + BT_DBG("hdev %p", hdev); + + if (!test_and_clear_bit(HCI_RUNNING, &hdev->flags)) + return 0; + + hci_uart_flush(hdev); + hdev->flush = NULL; + return 0; +} + +/* Send frames from HCI layer */ +static int hci_uart_send_frame(struct sk_buff *skb) +{ + struct hci_dev* hdev = (struct hci_dev *) skb->dev; + struct hci_uart *hu; + + if (!hdev) { + BT_ERR("Frame for unknown device (hdev=NULL)"); + return -ENODEV; + } + + if (!test_bit(HCI_RUNNING, &hdev->flags)) + return -EBUSY; + + hu = (struct hci_uart *) hdev->driver_data; + + BT_DBG("%s: type %d len %d", hdev->name, bt_cb(skb)->pkt_type, skb->len); + + hu->proto->enqueue(hu, skb); + + hci_uart_tx_wakeup(hu); + + return 0; +} + +static void hci_uart_destruct(struct hci_dev *hdev) +{ + if (!hdev) + return; + + BT_DBG("%s", hdev->name); +} + +/* ------ LDISC part ------ */ +/* hci_uart_tty_open + * + * Called when line discipline changed to HCI_UART. + * + * Arguments: + * tty pointer to tty info structure + * Return Value: + * 0 if success, otherwise error code + */ +static int hci_uart_tty_open(struct tty_struct *tty) +{ + struct hci_uart *hu = (void *) tty->disc_data; + + BT_DBG("tty %p", tty); + + /* FIXME: This btw is bogus, nothing requires the old ldisc to clear + the pointer */ + if (hu) + return -EEXIST; + + /* Error if the tty has no write op instead of leaving an exploitable + hole */ + if (tty->ops->write == NULL) + return -EOPNOTSUPP; + + if (!(hu = kzalloc(sizeof(struct hci_uart), GFP_KERNEL))) { + BT_ERR("Can't allocate control structure"); + return -ENFILE; + } + + tty->disc_data = hu; + hu->tty = tty; + tty->receive_room = 65536; + + spin_lock_init(&hu->rx_lock); + + /* Flush any pending characters in the driver and line discipline. */ + + /* FIXME: why is this needed. Note don't use ldisc_ref here as the + open path is before the ldisc is referencable */ + + if (tty->ldisc->ops->flush_buffer) + tty->ldisc->ops->flush_buffer(tty); + tty_driver_flush_buffer(tty); + + return 0; +} + +/* hci_uart_tty_close() + * + * Called when the line discipline is changed to something + * else, the tty is closed, or the tty detects a hangup. + */ +static void hci_uart_tty_close(struct tty_struct *tty) +{ + struct hci_uart *hu = (void *)tty->disc_data; + + BT_DBG("tty %p", tty); + + /* Detach from the tty */ + tty->disc_data = NULL; + + if (hu) { + struct hci_dev *hdev = hu->hdev; + + if (hdev) + hci_uart_close(hdev); + + if (test_and_clear_bit(HCI_UART_PROTO_SET, &hu->flags)) { + if (hdev) { + hci_unregister_dev(hdev); + hci_free_dev(hdev); + } + hu->proto->close(hu); + } + kfree(hu); + } +} + +/* hci_uart_tty_wakeup() + * + * Callback for transmit wakeup. Called when low level + * device driver can accept more send data. + * + * Arguments: tty pointer to associated tty instance data + * Return Value: None + */ +static void hci_uart_tty_wakeup(struct tty_struct *tty) +{ + struct hci_uart *hu = (void *)tty->disc_data; + + BT_DBG(""); + + if (!hu) + return; + + clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); + + if (tty != hu->tty) + return; + + if (test_bit(HCI_UART_PROTO_SET, &hu->flags)) + hci_uart_tx_wakeup(hu); +} + +/* hci_uart_tty_receive() + * + * Called by tty low level driver when receive data is + * available. + * + * Arguments: tty pointer to tty isntance data + * data pointer to received data + * flags pointer to flags for data + * count count of received data in bytes + * + * Return Value: None + */ +static void hci_uart_tty_receive(struct tty_struct *tty, const u8 *data, char *flags, int count) +{ + struct hci_uart *hu = (void *)tty->disc_data; + + if (!hu || tty != hu->tty) + return; + + if (!test_bit(HCI_UART_PROTO_SET, &hu->flags)) + return; + + spin_lock(&hu->rx_lock); + hu->proto->recv(hu, (void *) data, count); + hu->hdev->stat.byte_rx += count; + spin_unlock(&hu->rx_lock); + + tty_unthrottle(tty); +} + +static int hci_uart_register_dev(struct hci_uart *hu) +{ + struct hci_dev *hdev; + + BT_DBG(""); + + /* Initialize and register HCI device */ + hdev = hci_alloc_dev(); + if (!hdev) { + BT_ERR("Can't allocate HCI device"); + return -ENOMEM; + } + + hu->hdev = hdev; + + hdev->bus = HCI_UART; + hdev->driver_data = hu; + + hdev->open = hci_uart_open; + hdev->close = hci_uart_close; + hdev->flush = hci_uart_flush; + hdev->send = hci_uart_send_frame; + hdev->destruct = hci_uart_destruct; + hdev->parent = hu->tty->dev; + + hdev->owner = THIS_MODULE; + + if (!reset) + set_bit(HCI_QUIRK_NO_RESET, &hdev->quirks); + + if (test_bit(HCI_UART_RAW_DEVICE, &hu->hdev_flags)) + set_bit(HCI_QUIRK_RAW_DEVICE, &hdev->quirks); + + if (hci_register_dev(hdev) < 0) { + BT_ERR("Can't register HCI device"); + hci_free_dev(hdev); + return -ENODEV; + } + + return 0; +} + +static int hci_uart_set_proto(struct hci_uart *hu, int id) +{ + struct hci_uart_proto *p; + int err; + + p = hci_uart_get_proto(id); + if (!p) + return -EPROTONOSUPPORT; + + err = p->open(hu); + if (err) + return err; + + hu->proto = p; + + err = hci_uart_register_dev(hu); + if (err) { + p->close(hu); + return err; + } + + return 0; +} + +/* hci_uart_tty_ioctl() + * + * Process IOCTL system call for the tty device. + * + * Arguments: + * + * tty pointer to tty instance data + * file pointer to open file object for device + * cmd IOCTL command code + * arg argument for IOCTL call (cmd dependent) + * + * Return Value: Command dependent + */ +static int hci_uart_tty_ioctl(struct tty_struct *tty, struct file * file, + unsigned int cmd, unsigned long arg) +{ + struct hci_uart *hu = (void *)tty->disc_data; + int err = 0; + + BT_DBG(""); + + /* Verify the status of the device */ + if (!hu) + return -EBADF; + + switch (cmd) { + case HCIUARTSETPROTO: + if (!test_and_set_bit(HCI_UART_PROTO_SET, &hu->flags)) { + err = hci_uart_set_proto(hu, arg); + if (err) { + clear_bit(HCI_UART_PROTO_SET, &hu->flags); + return err; + } + } else + return -EBUSY; + break; + + case HCIUARTGETPROTO: + if (test_bit(HCI_UART_PROTO_SET, &hu->flags)) + return hu->proto->id; + return -EUNATCH; + + case HCIUARTGETDEVICE: + if (test_bit(HCI_UART_PROTO_SET, &hu->flags)) + return hu->hdev->id; + return -EUNATCH; + + case HCIUARTSETFLAGS: + if (test_bit(HCI_UART_PROTO_SET, &hu->flags)) + return -EBUSY; + hu->hdev_flags = arg; + break; + + case HCIUARTGETFLAGS: + return hu->hdev_flags; + + default: + err = n_tty_ioctl_helper(tty, file, cmd, arg); + break; + }; + + return err; +} + +/* + * We don't provide read/write/poll interface for user space. + */ +static ssize_t hci_uart_tty_read(struct tty_struct *tty, struct file *file, + unsigned char __user *buf, size_t nr) +{ + return 0; +} + +static ssize_t hci_uart_tty_write(struct tty_struct *tty, struct file *file, + const unsigned char *data, size_t count) +{ + return 0; +} + +static unsigned int hci_uart_tty_poll(struct tty_struct *tty, + struct file *filp, poll_table *wait) +{ + return 0; +} + +static int __init hci_uart_init(void) +{ + static struct tty_ldisc_ops hci_uart_ldisc; + int err; + + BT_INFO("HCI UART driver ver %s", VERSION); + + /* Register the tty discipline */ + + memset(&hci_uart_ldisc, 0, sizeof (hci_uart_ldisc)); + hci_uart_ldisc.magic = TTY_LDISC_MAGIC; + hci_uart_ldisc.name = "n_hci"; + hci_uart_ldisc.open = hci_uart_tty_open; + hci_uart_ldisc.close = hci_uart_tty_close; + hci_uart_ldisc.read = hci_uart_tty_read; + hci_uart_ldisc.write = hci_uart_tty_write; + hci_uart_ldisc.ioctl = hci_uart_tty_ioctl; + hci_uart_ldisc.poll = hci_uart_tty_poll; + hci_uart_ldisc.receive_buf = hci_uart_tty_receive; + hci_uart_ldisc.write_wakeup = hci_uart_tty_wakeup; + hci_uart_ldisc.owner = THIS_MODULE; + + if ((err = tty_register_ldisc(N_HCI, &hci_uart_ldisc))) { + BT_ERR("HCI line discipline registration failed. (%d)", err); + return err; + } + +#ifdef CONFIG_BT_HCIUART_H4 + h4_init(); +#endif +#ifdef CONFIG_BT_HCIUART_BCSP + bcsp_init(); +#endif +#ifdef CONFIG_BT_HCIUART_LL + ll_init(); +#endif +#ifdef CONFIG_BT_HCIUART_ATH3K + ath_init(); +#endif + + return 0; +} + +static void __exit hci_uart_exit(void) +{ + int err; + +#ifdef CONFIG_BT_HCIUART_H4 + h4_deinit(); +#endif +#ifdef CONFIG_BT_HCIUART_BCSP + bcsp_deinit(); +#endif +#ifdef CONFIG_BT_HCIUART_LL + ll_deinit(); +#endif +#ifdef CONFIG_BT_HCIUART_ATH3K + ath_deinit(); +#endif + + /* Release tty registration of line discipline */ + if ((err = tty_unregister_ldisc(N_HCI))) + BT_ERR("Can't unregister HCI line discipline (%d)", err); +} + +module_init(hci_uart_init); +module_exit(hci_uart_exit); + +module_param(reset, bool, 0644); +MODULE_PARM_DESC(reset, "Send HCI reset command on initialization"); + +MODULE_AUTHOR("Marcel Holtmann <marcel@holtmann.org>"); +MODULE_DESCRIPTION("Bluetooth HCI UART driver ver " VERSION); +MODULE_VERSION(VERSION); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_LDISC(N_HCI); diff --git a/drivers/bluetooth/hci_ll.c b/drivers/bluetooth/hci_ll.c new file mode 100644 index 00000000..7e4b435f --- /dev/null +++ b/drivers/bluetooth/hci_ll.c @@ -0,0 +1,535 @@ +/* + * Texas Instruments' Bluetooth HCILL UART protocol + * + * HCILL (HCI Low Level) is a Texas Instruments' power management + * protocol extension to H4. + * + * Copyright (C) 2007 Texas Instruments, Inc. + * + * Written by Ohad Ben-Cohen <ohad@bencohen.org> + * + * Acknowledgements: + * This file is based on hci_h4.c, which was written + * by Maxim Krasnyansky and Marcel Holtmann. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation + * + * 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/sched.h> +#include <linux/types.h> +#include <linux/fcntl.h> +#include <linux/interrupt.h> +#include <linux/ptrace.h> +#include <linux/poll.h> + +#include <linux/slab.h> +#include <linux/tty.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/signal.h> +#include <linux/ioctl.h> +#include <linux/skbuff.h> + +#include <net/bluetooth/bluetooth.h> +#include <net/bluetooth/hci_core.h> + +#include "hci_uart.h" + +/* HCILL commands */ +#define HCILL_GO_TO_SLEEP_IND 0x30 +#define HCILL_GO_TO_SLEEP_ACK 0x31 +#define HCILL_WAKE_UP_IND 0x32 +#define HCILL_WAKE_UP_ACK 0x33 + +/* HCILL receiver States */ +#define HCILL_W4_PACKET_TYPE 0 +#define HCILL_W4_EVENT_HDR 1 +#define HCILL_W4_ACL_HDR 2 +#define HCILL_W4_SCO_HDR 3 +#define HCILL_W4_DATA 4 + +/* HCILL states */ +enum hcill_states_e { + HCILL_ASLEEP, + HCILL_ASLEEP_TO_AWAKE, + HCILL_AWAKE, + HCILL_AWAKE_TO_ASLEEP +}; + +struct hcill_cmd { + u8 cmd; +} __packed; + +struct ll_struct { + unsigned long rx_state; + unsigned long rx_count; + struct sk_buff *rx_skb; + struct sk_buff_head txq; + spinlock_t hcill_lock; /* HCILL state lock */ + unsigned long hcill_state; /* HCILL power state */ + struct sk_buff_head tx_wait_q; /* HCILL wait queue */ +}; + +/* + * Builds and sends an HCILL command packet. + * These are very simple packets with only 1 cmd byte + */ +static int send_hcill_cmd(u8 cmd, struct hci_uart *hu) +{ + int err = 0; + struct sk_buff *skb = NULL; + struct ll_struct *ll = hu->priv; + struct hcill_cmd *hcill_packet; + + BT_DBG("hu %p cmd 0x%x", hu, cmd); + + /* allocate packet */ + skb = bt_skb_alloc(1, GFP_ATOMIC); + if (!skb) { + BT_ERR("cannot allocate memory for HCILL packet"); + err = -ENOMEM; + goto out; + } + + /* prepare packet */ + hcill_packet = (struct hcill_cmd *) skb_put(skb, 1); + hcill_packet->cmd = cmd; + skb->dev = (void *) hu->hdev; + + /* send packet */ + skb_queue_tail(&ll->txq, skb); +out: + return err; +} + +/* Initialize protocol */ +static int ll_open(struct hci_uart *hu) +{ + struct ll_struct *ll; + + BT_DBG("hu %p", hu); + + ll = kzalloc(sizeof(*ll), GFP_ATOMIC); + if (!ll) + return -ENOMEM; + + skb_queue_head_init(&ll->txq); + skb_queue_head_init(&ll->tx_wait_q); + spin_lock_init(&ll->hcill_lock); + + ll->hcill_state = HCILL_AWAKE; + + hu->priv = ll; + + return 0; +} + +/* Flush protocol data */ +static int ll_flush(struct hci_uart *hu) +{ + struct ll_struct *ll = hu->priv; + + BT_DBG("hu %p", hu); + + skb_queue_purge(&ll->tx_wait_q); + skb_queue_purge(&ll->txq); + + return 0; +} + +/* Close protocol */ +static int ll_close(struct hci_uart *hu) +{ + struct ll_struct *ll = hu->priv; + + BT_DBG("hu %p", hu); + + skb_queue_purge(&ll->tx_wait_q); + skb_queue_purge(&ll->txq); + + kfree_skb(ll->rx_skb); + + hu->priv = NULL; + + kfree(ll); + + return 0; +} + +/* + * internal function, which does common work of the device wake up process: + * 1. places all pending packets (waiting in tx_wait_q list) in txq list. + * 2. changes internal state to HCILL_AWAKE. + * Note: assumes that hcill_lock spinlock is taken, + * shouldn't be called otherwise! + */ +static void __ll_do_awake(struct ll_struct *ll) +{ + struct sk_buff *skb = NULL; + + while ((skb = skb_dequeue(&ll->tx_wait_q))) + skb_queue_tail(&ll->txq, skb); + + ll->hcill_state = HCILL_AWAKE; +} + +/* + * Called upon a wake-up-indication from the device + */ +static void ll_device_want_to_wakeup(struct hci_uart *hu) +{ + unsigned long flags; + struct ll_struct *ll = hu->priv; + + BT_DBG("hu %p", hu); + + /* lock hcill state */ + spin_lock_irqsave(&ll->hcill_lock, flags); + + switch (ll->hcill_state) { + case HCILL_ASLEEP_TO_AWAKE: + /* + * This state means that both the host and the BRF chip + * have simultaneously sent a wake-up-indication packet. + * Traditionally, in this case, receiving a wake-up-indication + * was enough and an additional wake-up-ack wasn't needed. + * This has changed with the BRF6350, which does require an + * explicit wake-up-ack. Other BRF versions, which do not + * require an explicit ack here, do accept it, thus it is + * perfectly safe to always send one. + */ + BT_DBG("dual wake-up-indication"); + /* deliberate fall-through - do not add break */ + case HCILL_ASLEEP: + /* acknowledge device wake up */ + if (send_hcill_cmd(HCILL_WAKE_UP_ACK, hu) < 0) { + BT_ERR("cannot acknowledge device wake up"); + goto out; + } + break; + default: + /* any other state is illegal */ + BT_ERR("received HCILL_WAKE_UP_IND in state %ld", ll->hcill_state); + break; + } + + /* send pending packets and change state to HCILL_AWAKE */ + __ll_do_awake(ll); + +out: + spin_unlock_irqrestore(&ll->hcill_lock, flags); + + /* actually send the packets */ + hci_uart_tx_wakeup(hu); +} + +/* + * Called upon a sleep-indication from the device + */ +static void ll_device_want_to_sleep(struct hci_uart *hu) +{ + unsigned long flags; + struct ll_struct *ll = hu->priv; + + BT_DBG("hu %p", hu); + + /* lock hcill state */ + spin_lock_irqsave(&ll->hcill_lock, flags); + + /* sanity check */ + if (ll->hcill_state != HCILL_AWAKE) + BT_ERR("ERR: HCILL_GO_TO_SLEEP_IND in state %ld", ll->hcill_state); + + /* acknowledge device sleep */ + if (send_hcill_cmd(HCILL_GO_TO_SLEEP_ACK, hu) < 0) { + BT_ERR("cannot acknowledge device sleep"); + goto out; + } + + /* update state */ + ll->hcill_state = HCILL_ASLEEP; + +out: + spin_unlock_irqrestore(&ll->hcill_lock, flags); + + /* actually send the sleep ack packet */ + hci_uart_tx_wakeup(hu); +} + +/* + * Called upon wake-up-acknowledgement from the device + */ +static void ll_device_woke_up(struct hci_uart *hu) +{ + unsigned long flags; + struct ll_struct *ll = hu->priv; + + BT_DBG("hu %p", hu); + + /* lock hcill state */ + spin_lock_irqsave(&ll->hcill_lock, flags); + + /* sanity check */ + if (ll->hcill_state != HCILL_ASLEEP_TO_AWAKE) + BT_ERR("received HCILL_WAKE_UP_ACK in state %ld", ll->hcill_state); + + /* send pending packets and change state to HCILL_AWAKE */ + __ll_do_awake(ll); + + spin_unlock_irqrestore(&ll->hcill_lock, flags); + + /* actually send the packets */ + hci_uart_tx_wakeup(hu); +} + +/* Enqueue frame for transmittion (padding, crc, etc) */ +/* may be called from two simultaneous tasklets */ +static int ll_enqueue(struct hci_uart *hu, struct sk_buff *skb) +{ + unsigned long flags = 0; + struct ll_struct *ll = hu->priv; + + BT_DBG("hu %p skb %p", hu, skb); + + /* Prepend skb with frame type */ + memcpy(skb_push(skb, 1), &bt_cb(skb)->pkt_type, 1); + + /* lock hcill state */ + spin_lock_irqsave(&ll->hcill_lock, flags); + + /* act according to current state */ + switch (ll->hcill_state) { + case HCILL_AWAKE: + BT_DBG("device awake, sending normally"); + skb_queue_tail(&ll->txq, skb); + break; + case HCILL_ASLEEP: + BT_DBG("device asleep, waking up and queueing packet"); + /* save packet for later */ + skb_queue_tail(&ll->tx_wait_q, skb); + /* awake device */ + if (send_hcill_cmd(HCILL_WAKE_UP_IND, hu) < 0) { + BT_ERR("cannot wake up device"); + break; + } + ll->hcill_state = HCILL_ASLEEP_TO_AWAKE; + break; + case HCILL_ASLEEP_TO_AWAKE: + BT_DBG("device waking up, queueing packet"); + /* transient state; just keep packet for later */ + skb_queue_tail(&ll->tx_wait_q, skb); + break; + default: + BT_ERR("illegal hcill state: %ld (losing packet)", ll->hcill_state); + kfree_skb(skb); + break; + } + + spin_unlock_irqrestore(&ll->hcill_lock, flags); + + return 0; +} + +static inline int ll_check_data_len(struct ll_struct *ll, int len) +{ + register int room = skb_tailroom(ll->rx_skb); + + BT_DBG("len %d room %d", len, room); + + if (!len) { + hci_recv_frame(ll->rx_skb); + } else if (len > room) { + BT_ERR("Data length is too large"); + kfree_skb(ll->rx_skb); + } else { + ll->rx_state = HCILL_W4_DATA; + ll->rx_count = len; + return len; + } + + ll->rx_state = HCILL_W4_PACKET_TYPE; + ll->rx_skb = NULL; + ll->rx_count = 0; + + return 0; +} + +/* Recv data */ +static int ll_recv(struct hci_uart *hu, void *data, int count) +{ + struct ll_struct *ll = hu->priv; + register char *ptr; + struct hci_event_hdr *eh; + struct hci_acl_hdr *ah; + struct hci_sco_hdr *sh; + register int len, type, dlen; + + BT_DBG("hu %p count %d rx_state %ld rx_count %ld", hu, count, ll->rx_state, ll->rx_count); + + ptr = data; + while (count) { + if (ll->rx_count) { + len = min_t(unsigned int, ll->rx_count, count); + memcpy(skb_put(ll->rx_skb, len), ptr, len); + ll->rx_count -= len; count -= len; ptr += len; + + if (ll->rx_count) + continue; + + switch (ll->rx_state) { + case HCILL_W4_DATA: + BT_DBG("Complete data"); + hci_recv_frame(ll->rx_skb); + + ll->rx_state = HCILL_W4_PACKET_TYPE; + ll->rx_skb = NULL; + continue; + + case HCILL_W4_EVENT_HDR: + eh = hci_event_hdr(ll->rx_skb); + + BT_DBG("Event header: evt 0x%2.2x plen %d", eh->evt, eh->plen); + + ll_check_data_len(ll, eh->plen); + continue; + + case HCILL_W4_ACL_HDR: + ah = hci_acl_hdr(ll->rx_skb); + dlen = __le16_to_cpu(ah->dlen); + + BT_DBG("ACL header: dlen %d", dlen); + + ll_check_data_len(ll, dlen); + continue; + + case HCILL_W4_SCO_HDR: + sh = hci_sco_hdr(ll->rx_skb); + + BT_DBG("SCO header: dlen %d", sh->dlen); + + ll_check_data_len(ll, sh->dlen); + continue; + } + } + + /* HCILL_W4_PACKET_TYPE */ + switch (*ptr) { + case HCI_EVENT_PKT: + BT_DBG("Event packet"); + ll->rx_state = HCILL_W4_EVENT_HDR; + ll->rx_count = HCI_EVENT_HDR_SIZE; + type = HCI_EVENT_PKT; + break; + + case HCI_ACLDATA_PKT: + BT_DBG("ACL packet"); + ll->rx_state = HCILL_W4_ACL_HDR; + ll->rx_count = HCI_ACL_HDR_SIZE; + type = HCI_ACLDATA_PKT; + break; + + case HCI_SCODATA_PKT: + BT_DBG("SCO packet"); + ll->rx_state = HCILL_W4_SCO_HDR; + ll->rx_count = HCI_SCO_HDR_SIZE; + type = HCI_SCODATA_PKT; + break; + + /* HCILL signals */ + case HCILL_GO_TO_SLEEP_IND: + BT_DBG("HCILL_GO_TO_SLEEP_IND packet"); + ll_device_want_to_sleep(hu); + ptr++; count--; + continue; + + case HCILL_GO_TO_SLEEP_ACK: + /* shouldn't happen */ + BT_ERR("received HCILL_GO_TO_SLEEP_ACK (in state %ld)", ll->hcill_state); + ptr++; count--; + continue; + + case HCILL_WAKE_UP_IND: + BT_DBG("HCILL_WAKE_UP_IND packet"); + ll_device_want_to_wakeup(hu); + ptr++; count--; + continue; + + case HCILL_WAKE_UP_ACK: + BT_DBG("HCILL_WAKE_UP_ACK packet"); + ll_device_woke_up(hu); + ptr++; count--; + continue; + + default: + BT_ERR("Unknown HCI packet type %2.2x", (__u8)*ptr); + hu->hdev->stat.err_rx++; + ptr++; count--; + continue; + }; + + ptr++; count--; + + /* Allocate packet */ + ll->rx_skb = bt_skb_alloc(HCI_MAX_FRAME_SIZE, GFP_ATOMIC); + if (!ll->rx_skb) { + BT_ERR("Can't allocate mem for new packet"); + ll->rx_state = HCILL_W4_PACKET_TYPE; + ll->rx_count = 0; + return -ENOMEM; + } + + ll->rx_skb->dev = (void *) hu->hdev; + bt_cb(ll->rx_skb)->pkt_type = type; + } + + return count; +} + +static struct sk_buff *ll_dequeue(struct hci_uart *hu) +{ + struct ll_struct *ll = hu->priv; + return skb_dequeue(&ll->txq); +} + +static struct hci_uart_proto llp = { + .id = HCI_UART_LL, + .open = ll_open, + .close = ll_close, + .recv = ll_recv, + .enqueue = ll_enqueue, + .dequeue = ll_dequeue, + .flush = ll_flush, +}; + +int __init ll_init(void) +{ + int err = hci_uart_register_proto(&llp); + + if (!err) + BT_INFO("HCILL protocol initialized"); + else + BT_ERR("HCILL protocol registration failed"); + + return err; +} + +int __exit ll_deinit(void) +{ + return hci_uart_unregister_proto(&llp); +} diff --git a/drivers/bluetooth/hci_uart.h b/drivers/bluetooth/hci_uart.h new file mode 100644 index 00000000..99fb3523 --- /dev/null +++ b/drivers/bluetooth/hci_uart.h @@ -0,0 +1,104 @@ +/* + * + * Bluetooth HCI UART driver + * + * Copyright (C) 2000-2001 Qualcomm Incorporated + * Copyright (C) 2002-2003 Maxim Krasnyansky <maxk@qualcomm.com> + * Copyright (C) 2004-2005 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 + * + */ + +#ifndef N_HCI +#define N_HCI 15 +#endif + +/* Ioctls */ +#define HCIUARTSETPROTO _IOW('U', 200, int) +#define HCIUARTGETPROTO _IOR('U', 201, int) +#define HCIUARTGETDEVICE _IOR('U', 202, int) +#define HCIUARTSETFLAGS _IOW('U', 203, int) +#define HCIUARTGETFLAGS _IOR('U', 204, int) + +/* UART protocols */ +#define HCI_UART_MAX_PROTO 6 + +#define HCI_UART_H4 0 +#define HCI_UART_BCSP 1 +#define HCI_UART_3WIRE 2 +#define HCI_UART_H4DS 3 +#define HCI_UART_LL 4 +#define HCI_UART_ATH3K 5 + +#define HCI_UART_RAW_DEVICE 0 + +struct hci_uart; + +struct hci_uart_proto { + unsigned int id; + int (*open)(struct hci_uart *hu); + int (*close)(struct hci_uart *hu); + int (*flush)(struct hci_uart *hu); + int (*recv)(struct hci_uart *hu, void *data, int len); + int (*enqueue)(struct hci_uart *hu, struct sk_buff *skb); + struct sk_buff *(*dequeue)(struct hci_uart *hu); +}; + +struct hci_uart { + struct tty_struct *tty; + struct hci_dev *hdev; + unsigned long flags; + unsigned long hdev_flags; + + struct hci_uart_proto *proto; + void *priv; + + struct sk_buff *tx_skb; + unsigned long tx_state; + spinlock_t rx_lock; +}; + +/* HCI_UART proto flag bits */ +#define HCI_UART_PROTO_SET 0 + +/* TX states */ +#define HCI_UART_SENDING 1 +#define HCI_UART_TX_WAKEUP 2 + +int hci_uart_register_proto(struct hci_uart_proto *p); +int hci_uart_unregister_proto(struct hci_uart_proto *p); +int hci_uart_tx_wakeup(struct hci_uart *hu); + +#ifdef CONFIG_BT_HCIUART_H4 +int h4_init(void); +int h4_deinit(void); +#endif + +#ifdef CONFIG_BT_HCIUART_BCSP +int bcsp_init(void); +int bcsp_deinit(void); +#endif + +#ifdef CONFIG_BT_HCIUART_LL +int ll_init(void); +int ll_deinit(void); +#endif + +#ifdef CONFIG_BT_HCIUART_ATH3K +int ath_init(void); +int ath_deinit(void); +#endif diff --git a/drivers/bluetooth/hci_vhci.c b/drivers/bluetooth/hci_vhci.c new file mode 100644 index 00000000..67c180c2 --- /dev/null +++ b/drivers/bluetooth/hci_vhci.c @@ -0,0 +1,312 @@ +/* + * + * Bluetooth virtual HCI driver + * + * Copyright (C) 2000-2001 Qualcomm Incorporated + * Copyright (C) 2002-2003 Maxim Krasnyansky <maxk@qualcomm.com> + * Copyright (C) 2004-2006 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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/types.h> +#include <linux/errno.h> +#include <linux/sched.h> +#include <linux/poll.h> + +#include <linux/skbuff.h> +#include <linux/miscdevice.h> + +#include <net/bluetooth/bluetooth.h> +#include <net/bluetooth/hci_core.h> + +#define VERSION "1.3" + +struct vhci_data { + struct hci_dev *hdev; + + unsigned long flags; + + wait_queue_head_t read_wait; + struct sk_buff_head readq; +}; + +static int vhci_open_dev(struct hci_dev *hdev) +{ + set_bit(HCI_RUNNING, &hdev->flags); + + return 0; +} + +static int vhci_close_dev(struct hci_dev *hdev) +{ + struct vhci_data *data = hdev->driver_data; + + if (!test_and_clear_bit(HCI_RUNNING, &hdev->flags)) + return 0; + + skb_queue_purge(&data->readq); + + return 0; +} + +static int vhci_flush(struct hci_dev *hdev) +{ + struct vhci_data *data = hdev->driver_data; + + skb_queue_purge(&data->readq); + + return 0; +} + +static int vhci_send_frame(struct sk_buff *skb) +{ + struct hci_dev* hdev = (struct hci_dev *) skb->dev; + struct vhci_data *data; + + if (!hdev) { + BT_ERR("Frame for unknown HCI device (hdev=NULL)"); + return -ENODEV; + } + + if (!test_bit(HCI_RUNNING, &hdev->flags)) + return -EBUSY; + + data = hdev->driver_data; + + memcpy(skb_push(skb, 1), &bt_cb(skb)->pkt_type, 1); + skb_queue_tail(&data->readq, skb); + + wake_up_interruptible(&data->read_wait); + + return 0; +} + +static void vhci_destruct(struct hci_dev *hdev) +{ + kfree(hdev->driver_data); +} + +static inline ssize_t vhci_get_user(struct vhci_data *data, + const char __user *buf, size_t count) +{ + struct sk_buff *skb; + + if (count > HCI_MAX_FRAME_SIZE) + return -EINVAL; + + skb = bt_skb_alloc(count, GFP_KERNEL); + if (!skb) + return -ENOMEM; + + if (copy_from_user(skb_put(skb, count), buf, count)) { + kfree_skb(skb); + return -EFAULT; + } + + skb->dev = (void *) data->hdev; + bt_cb(skb)->pkt_type = *((__u8 *) skb->data); + skb_pull(skb, 1); + + hci_recv_frame(skb); + + return count; +} + +static inline ssize_t vhci_put_user(struct vhci_data *data, + struct sk_buff *skb, char __user *buf, int count) +{ + char __user *ptr = buf; + int len, total = 0; + + len = min_t(unsigned int, skb->len, count); + + if (copy_to_user(ptr, skb->data, len)) + return -EFAULT; + + total += len; + + data->hdev->stat.byte_tx += len; + + switch (bt_cb(skb)->pkt_type) { + case HCI_COMMAND_PKT: + data->hdev->stat.cmd_tx++; + break; + + case HCI_ACLDATA_PKT: + data->hdev->stat.acl_tx++; + break; + + case HCI_SCODATA_PKT: + data->hdev->stat.sco_tx++; + break; + }; + + return total; +} + +static ssize_t vhci_read(struct file *file, + char __user *buf, size_t count, loff_t *pos) +{ + struct vhci_data *data = file->private_data; + struct sk_buff *skb; + ssize_t ret = 0; + + while (count) { + skb = skb_dequeue(&data->readq); + if (skb) { + ret = vhci_put_user(data, skb, buf, count); + if (ret < 0) + skb_queue_head(&data->readq, skb); + else + kfree_skb(skb); + break; + } + + if (file->f_flags & O_NONBLOCK) { + ret = -EAGAIN; + break; + } + + ret = wait_event_interruptible(data->read_wait, + !skb_queue_empty(&data->readq)); + if (ret < 0) + break; + } + + return ret; +} + +static ssize_t vhci_write(struct file *file, + const char __user *buf, size_t count, loff_t *pos) +{ + struct vhci_data *data = file->private_data; + + return vhci_get_user(data, buf, count); +} + +static unsigned int vhci_poll(struct file *file, poll_table *wait) +{ + struct vhci_data *data = file->private_data; + + poll_wait(file, &data->read_wait, wait); + + if (!skb_queue_empty(&data->readq)) + return POLLIN | POLLRDNORM; + + return POLLOUT | POLLWRNORM; +} + +static int vhci_open(struct inode *inode, struct file *file) +{ + struct vhci_data *data; + struct hci_dev *hdev; + + data = kzalloc(sizeof(struct vhci_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + skb_queue_head_init(&data->readq); + init_waitqueue_head(&data->read_wait); + + hdev = hci_alloc_dev(); + if (!hdev) { + kfree(data); + return -ENOMEM; + } + + data->hdev = hdev; + + hdev->bus = HCI_VIRTUAL; + hdev->driver_data = data; + + hdev->open = vhci_open_dev; + hdev->close = vhci_close_dev; + hdev->flush = vhci_flush; + hdev->send = vhci_send_frame; + hdev->destruct = vhci_destruct; + + hdev->owner = THIS_MODULE; + + if (hci_register_dev(hdev) < 0) { + BT_ERR("Can't register HCI device"); + kfree(data); + hci_free_dev(hdev); + return -EBUSY; + } + + file->private_data = data; + + return nonseekable_open(inode, file); +} + +static int vhci_release(struct inode *inode, struct file *file) +{ + struct vhci_data *data = file->private_data; + struct hci_dev *hdev = data->hdev; + + if (hci_unregister_dev(hdev) < 0) { + BT_ERR("Can't unregister HCI device %s", hdev->name); + } + + hci_free_dev(hdev); + + file->private_data = NULL; + + return 0; +} + +static const struct file_operations vhci_fops = { + .owner = THIS_MODULE, + .read = vhci_read, + .write = vhci_write, + .poll = vhci_poll, + .open = vhci_open, + .release = vhci_release, + .llseek = no_llseek, +}; + +static struct miscdevice vhci_miscdev= { + .name = "vhci", + .fops = &vhci_fops, + .minor = MISC_DYNAMIC_MINOR, +}; + +static int __init vhci_init(void) +{ + BT_INFO("Virtual HCI driver ver %s", VERSION); + + return misc_register(&vhci_miscdev); +} + +static void __exit vhci_exit(void) +{ + misc_deregister(&vhci_miscdev); +} + +module_init(vhci_init); +module_exit(vhci_exit); + +MODULE_AUTHOR("Marcel Holtmann <marcel@holtmann.org>"); +MODULE_DESCRIPTION("Bluetooth virtual HCI driver ver " VERSION); +MODULE_VERSION(VERSION); +MODULE_LICENSE("GPL"); |