[RFC] Bluetooth: Add firmware load infrastructure for BT devices

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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


[Index of Archives]     [Bluez Devel]     [Linux Wireless Networking]     [Linux Wireless Personal Area Networking]     [Linux ATH6KL]     [Linux USB Devel]     [Linux Media Drivers]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Big List of Linux Books]

  Powered by Linux