diff options
Diffstat (limited to 'drivers/bluetooth/ath3k.c')
-rw-r--r-- | drivers/bluetooth/ath3k.c | 292 |
1 files changed, 287 insertions, 5 deletions
diff --git a/drivers/bluetooth/ath3k.c b/drivers/bluetooth/ath3k.c index a126e614601..5577ed656e2 100644 --- a/drivers/bluetooth/ath3k.c +++ b/drivers/bluetooth/ath3k.c @@ -31,6 +31,30 @@ #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 */ @@ -39,13 +63,34 @@ static struct usb_device_id ath3k_table[] = { /* Atheros AR3011 with sflash firmware*/ { USB_DEVICE(0x0CF3, 0x3002) }, + /* Atheros AR9285 Malbec with sflash firmware */ + { USB_DEVICE(0x03F0, 0x311D) }, + + /* Atheros AR3012 with sflash firmware*/ + { USB_DEVICE(0x0CF3, 0x3004) }, + + /* Atheros AR5BBU12 with sflash firmware */ + { USB_DEVICE(0x0489, 0xE02C) }, + { } /* 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 }, + + { } /* 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) @@ -101,28 +146,265 @@ error: 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; - if (request_firmware(&firmware, "ath3k-1.fw", &udev->dev) < 0) { - return -EIO; + /* 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; } - if (ath3k_load_firmware(udev, firmware)) { - release_firmware(firmware); + /* load patch and sysconfig files for AR3012 */ + if (id->driver_info & BTUSB_ATH3012) { + 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 0; + return ret; } static void ath3k_disconnect(struct usb_interface *intf) |