diff options
Diffstat (limited to 'usr.sbin/bluetooth/ath3kfw/ath3k_hw.c')
-rw-r--r-- | usr.sbin/bluetooth/ath3kfw/ath3k_hw.c | 358 |
1 files changed, 358 insertions, 0 deletions
diff --git a/usr.sbin/bluetooth/ath3kfw/ath3k_hw.c b/usr.sbin/bluetooth/ath3kfw/ath3k_hw.c new file mode 100644 index 0000000..890e2db --- /dev/null +++ b/usr.sbin/bluetooth/ath3kfw/ath3k_hw.c @@ -0,0 +1,358 @@ +/*- + * Copyright (c) 2013 Adrian Chadd <adrian@freebsd.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any + * redistribution must be conditioned upon including a substantially + * similar Disclaimer requirement for further binary redistribution. + * + * NO WARRANTY + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGES. + * + * $FreeBSD$ + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <err.h> +#include <fcntl.h> +#include <sys/endian.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include <libusb.h> + +#include "ath3k_fw.h" +#include "ath3k_hw.h" +#include "ath3k_dbg.h" + +#define XMIN(x, y) ((x) < (y) ? (x) : (y)) + +int +ath3k_load_fwfile(struct libusb_device_handle *hdl, + const struct ath3k_firmware *fw) +{ + int size, count, sent = 0; + int ret, r; + + count = fw->len; + + size = XMIN(count, FW_HDR_SIZE); + + ath3k_debug("%s: file=%s, size=%d\n", + __func__, fw->fwname, count); + + /* + * Flip the device over to configuration mode. + */ + ret = libusb_control_transfer(hdl, + LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_OUT, + ATH3K_DNLOAD, + 0, + 0, + fw->buf + sent, + size, + 1000); /* XXX timeout */ + + if (ret != size) { + fprintf(stderr, "Can't switch to config mode; ret=%d\n", + ret); + return (-1); + } + + sent += size; + count -= size; + + /* Load in the rest of the data */ + while (count) { + size = XMIN(count, BULK_SIZE); + ath3k_debug("%s: transferring %d bytes, offset %d\n", + __func__, + sent, + size); + + ret = libusb_bulk_transfer(hdl, + 0x2, + fw->buf + sent, + size, + &r, + 1000); + + if (ret < 0 || r != size) { + fprintf(stderr, "Can't load firmware: err=%s, size=%d\n", + libusb_strerror(ret), + size); + return (-1); + } + sent += size; + count -= size; + } + return (0); +} + +int +ath3k_get_state(struct libusb_device_handle *hdl, unsigned char *state) +{ + int ret; + + ret = libusb_control_transfer(hdl, + LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_IN, + ATH3K_GETSTATE, + 0, + 0, + state, + 1, + 1000); /* XXX timeout */ + + if (ret < 0) { + fprintf(stderr, + "%s: libusb_control_transfer() failed: code=%d\n", + __func__, + ret); + return (0); + } + + return (ret == 1); +} + +int +ath3k_get_version(struct libusb_device_handle *hdl, + struct ath3k_version *version) +{ + int ret; + + ret = libusb_control_transfer(hdl, + LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_IN, + ATH3K_GETVERSION, + 0, + 0, + (unsigned char *) version, + sizeof(struct ath3k_version), + 1000); /* XXX timeout */ + + if (ret < 0) { + fprintf(stderr, + "%s: libusb_control_transfer() failed: code=%d\n", + __func__, + ret); + return (0); + } + + /* XXX endian fix! */ + + return (ret == sizeof(struct ath3k_version)); +} + +int +ath3k_load_patch(libusb_device_handle *hdl, const char *fw_path) +{ + int ret; + unsigned char fw_state; + struct ath3k_version fw_ver, pt_ver; + char fwname[FILENAME_MAX]; + struct ath3k_firmware fw; + uint32_t tmp; + + ret = ath3k_get_state(hdl, &fw_state); + if (ret < 0) { + ath3k_err("%s: Can't get state\n", __func__); + return (ret); + } + + if (fw_state & ATH3K_PATCH_UPDATE) { + ath3k_info("%s: Patch already downloaded\n", + __func__); + return (0); + } + + ret = ath3k_get_version(hdl, &fw_ver); + if (ret < 0) { + ath3k_debug("%s: Can't get version\n", __func__); + return (ret); + } + + /* XXX path info? */ + snprintf(fwname, FILENAME_MAX, "%s/ar3k/AthrBT_0x%08x.dfu", + fw_path, + fw_ver.rom_version); + + /* Read in the firmware */ + if (ath3k_fw_read(&fw, fwname) <= 0) { + ath3k_debug("%s: ath3k_fw_read() failed\n", + __func__); + return (-1); + } + + /* + * Extract the ROM/build version from the patch file. + */ + memcpy(&tmp, fw.buf + fw.len - 8, sizeof(tmp)); + pt_ver.rom_version = le32toh(tmp); + memcpy(&tmp, fw.buf + fw.len - 4, sizeof(tmp)); + pt_ver.build_version = le32toh(tmp); + + ath3k_info("%s: file %s: rom_ver=%d, build_ver=%d\n", + __func__, + fwname, + (int) pt_ver.rom_version, + (int) pt_ver.build_version); + + /* Check the ROM/build version against the firmware */ + if ((pt_ver.rom_version != fw_ver.rom_version) || + (pt_ver.build_version <= fw_ver.build_version)) { + ath3k_debug("Patch file version mismatch!\n"); + ath3k_fw_free(&fw); + return (-1); + } + + /* Load in the firmware */ + ret = ath3k_load_fwfile(hdl, &fw); + + /* free it */ + ath3k_fw_free(&fw); + + return (ret); +} + +int +ath3k_load_syscfg(libusb_device_handle *hdl, const char *fw_path) +{ + unsigned char fw_state; + char filename[FILENAME_MAX]; + struct ath3k_firmware fw; + struct ath3k_version fw_ver; + int clk_value, ret; + + ret = ath3k_get_state(hdl, &fw_state); + if (ret < 0) { + ath3k_err("Can't get state to change to load configuration err"); + return (-EBUSY); + } + + ret = ath3k_get_version(hdl, &fw_ver); + if (ret < 0) { + ath3k_err("Can't get version to change to load ram patch err"); + return (ret); + } + + switch (fw_ver.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, FILENAME_MAX, "%s/ar3k/ramps_0x%08x_%d%s", + fw_path, + fw_ver.rom_version, + clk_value, + ".dfu"); + + ath3k_info("%s: syscfg file = %s\n", + __func__, + filename); + + /* Read in the firmware */ + if (ath3k_fw_read(&fw, filename) <= 0) { + ath3k_err("%s: ath3k_fw_read() failed\n", + __func__); + return (-1); + } + + ret = ath3k_load_fwfile(hdl, &fw); + + ath3k_fw_free(&fw); + return (ret); +} + +int +ath3k_set_normal_mode(libusb_device_handle *hdl) +{ + int ret; + unsigned char fw_state; + + ret = ath3k_get_state(hdl, &fw_state); + if (ret < 0) { + ath3k_err("%s: can't get state\n", __func__); + return (ret); + } + + /* + * This isn't a fatal error - the device may have detached + * already. + */ + if ((fw_state & ATH3K_MODE_MASK) == ATH3K_NORMAL_MODE) { + ath3k_debug("%s: firmware is already in normal mode\n", + __func__); + return (0); + } + + ret = libusb_control_transfer(hdl, + LIBUSB_REQUEST_TYPE_VENDOR, /* XXX out direction? */ + ATH3K_SET_NORMAL_MODE, + 0, + 0, + NULL, + 0, + 1000); /* XXX timeout */ + + if (ret < 0) { + ath3k_err("%s: libusb_control_transfer() failed: code=%d\n", + __func__, + ret); + return (0); + } + + return (ret == 0); +} + +int +ath3k_switch_pid(libusb_device_handle *hdl) +{ + int ret; + ret = libusb_control_transfer(hdl, + LIBUSB_REQUEST_TYPE_VENDOR, /* XXX set an out flag? */ + USB_REG_SWITCH_VID_PID, + 0, + 0, + NULL, + 0, + 1000); /* XXX timeout */ + + if (ret < 0) { + ath3k_debug("%s: libusb_control_transfer() failed: code=%d\n", + __func__, + ret); + return (0); + } + + return (ret == 0); +} |