Added support to load firmware to target RAM from Bluetooth USB transport driver. Each BT device vendor need to specify the product ID, firmware file, load and unload function. When the device is inserted, btusb will call appropriate firmware load function to load firmware to target RAM. This framework is needed for devices that are detected as BT devices when powered on and still require firmware to be downaloded. Signed-off-by: Bala Shanmugam <sbalashanmugam@xxxxxxxxxxx> --- drivers/bluetooth/Makefile | 1 + drivers/bluetooth/btusb.c | 50 ++++++++++++ drivers/bluetooth/fwload.c | 187 ++++++++++++++++++++++++++++++++++++++++++++ drivers/bluetooth/fwload.h | 37 +++++++++ 4 files changed, 275 insertions(+), 0 deletions(-) create mode 100644 drivers/bluetooth/fwload.c create mode 100644 drivers/bluetooth/fwload.h diff --git a/drivers/bluetooth/Makefile b/drivers/bluetooth/Makefile index 7e5aed5..43df710 100644 --- a/drivers/bluetooth/Makefile +++ b/drivers/bluetooth/Makefile @@ -13,6 +13,7 @@ obj-$(CONFIG_BT_HCIBLUECARD) += bluecard_cs.o obj-$(CONFIG_BT_HCIBTUART) += btuart_cs.o obj-$(CONFIG_BT_HCIBTUSB) += btusb.o +obj-$(CONFIG_BT_HCIBTUSB) += fwload.o obj-$(CONFIG_BT_HCIBTSDIO) += btsdio.o obj-$(CONFIG_BT_ATH3K) += ath3k.o diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index 5d9cc53..816c772 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -34,6 +34,7 @@ #include <net/bluetooth/bluetooth.h> #include <net/bluetooth/hci_core.h> +#include "fwload.h" #define VERSION "0.6" @@ -55,6 +56,24 @@ static struct usb_driver btusb_driver; #define BTUSB_BROKEN_ISOC 0x20 #define BTUSB_WRONG_SCO_MTU 0x40 +static struct usb_device_id ath_table[] = { + /* Atheros AR3011 */ + { USB_DEVICE(0x0CF3, 0x3002) }, + { } /* Terminating entry */ +}; + +/* Add firmware file, load and unload function + * to download the firmware to target RAM + */ +static struct fw_cb_config btusb_fwcbs[] = { + { + .fwfile = "ath3k-1.fw", + .usb_id_table = ath_table, + .fwload = ath_fw_load, + .fwunload = ath_fw_unload, + }, + {} +}; static struct usb_device_id btusb_table[] = { /* Generic Bluetooth USB device */ { USB_DEVICE_INFO(0xe0, 0x01, 0x01) }, @@ -862,6 +881,7 @@ static int btusb_probe(struct usb_interface *intf, struct btusb_data *data; struct hci_dev *hdev; int i, err; + const struct usb_device_id *match; BT_DBG("intf %p id %p", intf, id); @@ -921,6 +941,15 @@ static int btusb_probe(struct usb_interface *intf, data->udev = interface_to_usbdev(intf); data->intf = intf; + for (i = 0; btusb_fwcbs[i].fwfile; i++) { + match = usb_match_id(intf, btusb_fwcbs[i].usb_id_table); + if (match && btusb_fwcbs[i].fwload) { + btusb_fwcbs[i].data = btusb_fwcbs[i].fwload(intf, + btusb_fwcbs[i].fwfile); + break; + } + } + spin_lock_init(&data->lock); INIT_WORK(&data->work, btusb_work); @@ -1029,12 +1058,25 @@ static void btusb_disconnect(struct usb_interface *intf) { struct btusb_data *data = usb_get_intfdata(intf); struct hci_dev *hdev; + const struct usb_device_id *match; + int i; BT_DBG("intf %p", intf); if (!data) return; + for (i = 0; btusb_fwcbs[i].fwfile; i++) { + match = usb_match_id(intf, btusb_fwcbs[i].usb_id_table); + if (match) { + if (btusb_fwcbs[i].fwunload) { + btusb_fwcbs[i].fwunload(btusb_fwcbs[i].data); + btusb_fwcbs[i].data = NULL; + } + break; + } + } + hdev = data->hdev; __hci_dev_hold(hdev); @@ -1178,6 +1220,14 @@ static int __init btusb_init(void) static void __exit btusb_exit(void) { + int i; + + for (i = 0; btusb_fwcbs[i].fwfile; i++) { + if (btusb_fwcbs[i].fwunload && btusb_fwcbs[i].data) { + btusb_fwcbs[i].fwunload(btusb_fwcbs[i].data); + btusb_fwcbs[i].data = NULL; + } + } usb_deregister(&btusb_driver); } diff --git a/drivers/bluetooth/fwload.c b/drivers/bluetooth/fwload.c new file mode 100644 index 0000000..1135abc --- /dev/null +++ b/drivers/bluetooth/fwload.c @@ -0,0 +1,187 @@ +/* + * + * Generic Bluetooth USB DFU driver to download firmware to target RAM + * + * Copyright (c) 2009-2010 Atheros Communications Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/device.h> +#include <linux/firmware.h> +#include <linux/usb.h> +#include <net/bluetooth/bluetooth.h> + +#define USB_REQ_DFU_DNLOAD 1 +#define USB_REQ_GET_STATE 5 +#define USB_FIRMWARE_RAM_MODE 11 +#define USB_FIRMWARE_FLASH_MODE 12 +#define BULK_SIZE 4096 +#define VERSION "1.0" + +struct firmware_data { + struct usb_device *udev; + u8 *fw_data; + u32 fw_size; + u32 fw_sent; +}; + +static int load_firmware(struct firmware_data *data, + unsigned char *firmware, + int count) +{ + u8 *send_buf; + int err, pipe, len, size, sent = 0; + char ucFirmware = 0; + + BT_DBG("ath3k %p udev %p", data, data->udev); + + if ((usb_control_msg(data->udev, usb_rcvctrlpipe(data->udev, 0), + USB_REQ_GET_STATE, + USB_TYPE_VENDOR | USB_DIR_IN, 0, 0, + &ucFirmware, 1, USB_CTRL_SET_TIMEOUT)) < 0) { + BT_ERR("Can't change to loading configuration err"); + return -EBUSY; + } + + if (ucFirmware == USB_FIRMWARE_RAM_MODE) { + /* RAM based firmware is available in the target. + * No need to load the firmware to RAM */ + BT_DBG("RAM based firmware is available"); + return 0; + } + + pipe = usb_sndctrlpipe(data->udev, 0); + if ((usb_control_msg(data->udev, pipe, + USB_REQ_DFU_DNLOAD, + USB_TYPE_VENDOR, 0, 0, + firmware, 20, USB_CTRL_SET_TIMEOUT)) < 0) { + BT_ERR("Can't change to loading configuration err"); + return -EBUSY; + } + sent += 20; + count -= 20; + + send_buf = kmalloc(BULK_SIZE, GFP_ATOMIC); + if (!send_buf) { + BT_ERR("Can't allocate memory chunk for firmware"); + return -ENOMEM; + } + + while (count) { + size = min_t(uint, count, BULK_SIZE); + pipe = usb_sndbulkpipe(data->udev, 0x02); + memcpy(send_buf, firmware + sent, size); + + err = usb_bulk_msg(data->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; + } + + kfree(send_buf); + return 0; + +error: + kfree(send_buf); + return err; +} + +void *ath_fw_load(struct usb_interface *intf, const char *fwfile) +{ + const struct firmware *firmware; + struct usb_device *udev = interface_to_usbdev(intf); + struct firmware_data *data; + int size; + + BT_DBG("intf %p ", intf); + + if (intf->cur_altsetting->desc.bInterfaceNumber != 0) + return NULL; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return NULL; + + data->udev = udev; + + if (request_firmware(&firmware, fwfile, &udev->dev) < 0) { + kfree(data); + return NULL; + } + + size = max_t(uint, firmware->size, 4096); + data->fw_data = kmalloc(size, GFP_KERNEL); + if (!data->fw_data) { + release_firmware(firmware); + kfree(data); + return NULL; + } + + memcpy(data->fw_data, firmware->data, firmware->size); + data->fw_size = firmware->size; + data->fw_sent = 0; + release_firmware(firmware); + + if (load_firmware(data, data->fw_data, data->fw_size)) { + kfree(data->fw_data); + kfree(data); + return NULL; + } + return data; +} +EXPORT_SYMBOL(ath_fw_load); + +void ath_fw_unload(void *pdata) +{ + struct firmware_data *data = (struct firmware_data *)pdata; + if (data == NULL) + return; + kfree(data->fw_data); + kfree(data); +} +EXPORT_SYMBOL(ath_fw_unload); + +static int __init fwload_init(void) +{ + BT_INFO("Firmware load driver init. Version:%s", VERSION); + return 0; +} + +static void __exit fwload_deinit(void) +{ + BT_INFO("Firmware load driver deinit"); +} + +module_init(fwload_init); +module_exit(fwload_deinit); + +MODULE_AUTHOR("Atheros Communications"); +MODULE_DESCRIPTION("Firmware load driver"); +MODULE_VERSION(VERSION); +MODULE_LICENSE("GPL"); diff --git a/drivers/bluetooth/fwload.h b/drivers/bluetooth/fwload.h new file mode 100644 index 0000000..ce1c841 --- /dev/null +++ b/drivers/bluetooth/fwload.h @@ -0,0 +1,37 @@ +/* + * + * Generic Bluetooth USB DFU driver to download firmware to target RAM + * + * Copyright (c) 2009-2010 Atheros Communications Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef __FWLOAD_H_ +#define __FWLOAD_H_ + +/* callbacks to load firmware to BT device RAM + * when it is inserted */ +struct fw_cb_config { + const char *fwfile; + void * (*fwload)(struct usb_interface *intf, const char *fwfile); + void (*fwunload)(void *); + const struct usb_device_id *usb_id_table; + void *data; +}; +void *ath_fw_load(struct usb_interface *intf, const char *); +void ath_fw_unload(void *pdata); + +#endif /* __FWLOAD_H_ */ -- 1.6.3.3 -- To unsubscribe from this list: send the line "unsubscribe linux-bluetooth" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html