summaryrefslogtreecommitdiff
path: root/drivers/staging/csr/csr_wifi_hip_download.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2012-07-26 18:14:49 (GMT)
committerLinus Torvalds <torvalds@linux-foundation.org>2012-07-26 18:14:49 (GMT)
commitb13bc8dda81c54a66a1c84e66f60b8feba659f28 (patch)
tree100a26eada424fa5d9b0e5eaaf4e23b8fa036fc8 /drivers/staging/csr/csr_wifi_hip_download.c
parent9fc377799bc9bfd8d5cb35d0d1ea2e2458cbdbb3 (diff)
parent419e9266884fa853179ab726c27a63a9d3ae46e3 (diff)
downloadlinux-fsl-qoriq-b13bc8dda81c54a66a1c84e66f60b8feba659f28.tar.xz
Merge tag 'staging-3.6-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/staging
Pull staging tree patches from Greg Kroah-Hartman: "Here's the big staging tree merge for the 3.6-rc1 merge window. There are some patches in here outside of drivers/staging/, notibly the iio code (which is still stradeling the staging / not staging boundry), the pstore code, and the tracing code. All of these have gotten acks from the various subsystem maintainers to be included in this tree. The pstore and tracing patches are related, and are coming here as they replace one of the android staging drivers. Otherwise, the normal staging mess. Lots of cleanups and a few new drivers (some iio drivers, and the large csr wireless driver abomination.) Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>" Fixed up trivial conflicts in drivers/staging/comedi/drivers/s626.h and drivers/staging/gdm72xx/netlink_k.c * tag 'staging-3.6-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/staging: (1108 commits) staging: csr: delete a bunch of unused library functions staging: csr: remove csr_utf16.c staging: csr: remove csr_pmem.h staging: csr: remove CsrPmemAlloc staging: csr: remove CsrPmemFree() staging: csr: remove CsrMemAllocDma() staging: csr: remove CsrMemCalloc() staging: csr: remove CsrMemAlloc() staging: csr: remove CsrMemFree() and CsrMemFreeDma() staging: csr: remove csr_util.h staging: csr: remove CsrOffSetOf() stating: csr: remove unneeded #includes in csr_util.c staging: csr: make CsrUInt16ToHex static staging: csr: remove CsrMemCpy() staging: csr: remove CsrStrLen() staging: csr: remove CsrVsnprintf() staging: csr: remove CsrStrDup staging: csr: remove CsrStrChr() staging: csr: remove CsrStrNCmp staging: csr: remove CsrStrCmp ...
Diffstat (limited to 'drivers/staging/csr/csr_wifi_hip_download.c')
-rw-r--r--drivers/staging/csr/csr_wifi_hip_download.c835
1 files changed, 835 insertions, 0 deletions
diff --git a/drivers/staging/csr/csr_wifi_hip_download.c b/drivers/staging/csr/csr_wifi_hip_download.c
new file mode 100644
index 0000000..8e4a460
--- /dev/null
+++ b/drivers/staging/csr/csr_wifi_hip_download.c
@@ -0,0 +1,835 @@
+/*****************************************************************************
+
+ (c) Cambridge Silicon Radio Limited 2012
+ All rights reserved and confidential information of CSR
+
+ Refer to LICENSE.txt included with this source for details
+ on the license terms.
+
+*****************************************************************************/
+
+/*
+ * ---------------------------------------------------------------------------
+ * FILE: csr_wifi_hip_download.c
+ *
+ * PURPOSE:
+ * Routines for downloading firmware to UniFi.
+ *
+ * ---------------------------------------------------------------------------
+ */
+#include <linux/slab.h>
+#include "csr_wifi_hip_unifi.h"
+#include "csr_wifi_hip_unifiversion.h"
+#include "csr_wifi_hip_card.h"
+#include "csr_wifi_hip_xbv.h"
+
+#undef CSR_WIFI_IGNORE_PATCH_VERSION_MISMATCH
+
+static CsrResult do_patch_download(card_t *card, void *dlpriv,
+ xbv1_t *pfwinfo, u32 boot_ctrl_addr);
+
+static CsrResult do_patch_convert_download(card_t *card,
+ void *dlpriv, xbv1_t *pfwinfo);
+
+/*
+ * ---------------------------------------------------------------------------
+ * _find_in_slut
+ *
+ * Find the offset of the appropriate object in the SLUT of a card
+ *
+ * Arguments:
+ * card Pointer to card struct
+ * psym Pointer to symbol object.
+ * id set up by caller
+ * obj will be set up by this function
+ * pslut Pointer to SLUT address, if 0xffffffff then it must be
+ * read from the chip.
+ * Returns:
+ * CSR_RESULT_SUCCESS on success
+ * Non-zero on error,
+ * CSR_WIFI_HIP_RESULT_NOT_FOUND if not found
+ * ---------------------------------------------------------------------------
+ */
+static CsrResult _find_in_slut(card_t *card, symbol_t *psym, u32 *pslut)
+{
+ u32 slut_address;
+ u16 finger_print;
+ CsrResult r;
+ CsrResult csrResult;
+
+ /* Get SLUT address */
+ if (*pslut == 0xffffffff)
+ {
+ r = card_wait_for_firmware_to_start(card, &slut_address);
+ if (r == CSR_WIFI_HIP_RESULT_NO_DEVICE)
+ {
+ return r;
+ }
+ if (r != CSR_RESULT_SUCCESS)
+ {
+ unifi_error(card->ospriv, "Firmware hasn't started\n");
+ func_exit_r(r);
+ return r;
+ }
+ *pslut = slut_address;
+
+ /*
+ * Firmware has started so set the SDIO bus clock to the initial speed,
+ * faster than UNIFI_SDIO_CLOCK_SAFE_HZ, to speed up the f/w download.
+ */
+ csrResult = CsrSdioMaxBusClockFrequencySet(card->sdio_if, UNIFI_SDIO_CLOCK_INIT_HZ);
+ if (csrResult != CSR_RESULT_SUCCESS)
+ {
+ r = ConvertCsrSdioToCsrHipResult(card, csrResult);
+ func_exit_r(r);
+ return r;
+ }
+ card->sdio_clock_speed = UNIFI_SDIO_CLOCK_INIT_HZ;
+ }
+ else
+ {
+ slut_address = *pslut; /* Use previously discovered address */
+ }
+ unifi_trace(card->ospriv, UDBG4, "SLUT addr: 0x%lX\n", slut_address);
+
+ /*
+ * Check the SLUT fingerprint.
+ * The slut_address is a generic pointer so we must use unifi_card_read16().
+ */
+ unifi_trace(card->ospriv, UDBG4, "Looking for SLUT finger print\n");
+ finger_print = 0;
+ r = unifi_card_read16(card, slut_address, &finger_print);
+ if (r == CSR_WIFI_HIP_RESULT_NO_DEVICE)
+ {
+ return r;
+ }
+ if (r != CSR_RESULT_SUCCESS)
+ {
+ unifi_error(card->ospriv, "Failed to read SLUT finger print\n");
+ func_exit_r(r);
+ return r;
+ }
+
+ if (finger_print != SLUT_FINGERPRINT)
+ {
+ unifi_error(card->ospriv, "Failed to find SLUT fingerprint\n");
+ func_exit_r(CSR_RESULT_FAILURE);
+ return CSR_RESULT_FAILURE;
+ }
+
+ /* Symbol table starts imedately after the fingerprint */
+ slut_address += 2;
+
+ while (1)
+ {
+ u16 id;
+ u32 obj;
+
+ r = unifi_card_read16(card, slut_address, &id);
+ if (r != CSR_RESULT_SUCCESS)
+ {
+ func_exit_r(r);
+ return r;
+ }
+ slut_address += 2;
+
+ if (id == CSR_SLT_END)
+ {
+ /* End of table reached: not found */
+ r = CSR_WIFI_HIP_RESULT_RANGE;
+ break;
+ }
+
+ r = unifi_read32(card, slut_address, &obj);
+ if (r != CSR_RESULT_SUCCESS)
+ {
+ func_exit_r(r);
+ return r;
+ }
+ slut_address += 4;
+
+ unifi_trace(card->ospriv, UDBG3, " found SLUT id %02d.%08lx\n", id, obj);
+
+ r = CSR_WIFI_HIP_RESULT_NOT_FOUND;
+ /* Found search term? */
+ if (id == psym->id)
+ {
+ unifi_trace(card->ospriv, UDBG1, " matched SLUT id %02d.%08lx\n", id, obj);
+ psym->obj = obj;
+ r = CSR_RESULT_SUCCESS;
+ break;
+ }
+ }
+
+ func_exit_r(r);
+ return r;
+}
+
+
+/*
+ * ---------------------------------------------------------------------------
+ * do_patch_convert_download
+ *
+ * Download the given firmware image to the UniFi, converting from FWDL
+ * to PTDL XBV format.
+ *
+ * Arguments:
+ * card Pointer to card struct
+ * dlpriv Pointer to source firmware image
+ * fwinfo Pointer to source firmware info struct
+ *
+ * Returns:
+ * CSR_RESULT_SUCCESS on success, CSR error code on error
+ *
+ * Notes:
+ * ---------------------------------------------------------------------------
+ */
+static CsrResult do_patch_convert_download(card_t *card, void *dlpriv, xbv1_t *pfwinfo)
+{
+ CsrResult r;
+ u32 slut_base = 0xffffffff;
+ void *pfw;
+ u32 psize;
+ symbol_t sym;
+
+ /* Reset the chip to guarantee that the ROM loader is running */
+ r = unifi_init(card);
+ if (r != CSR_RESULT_SUCCESS)
+ {
+ unifi_error(card->ospriv,
+ "do_patch_convert_download: failed to re-init UniFi\n");
+ return r;
+ }
+
+ /* If no unifi_helper is running, the firmware version must be read */
+ if (card->build_id == 0)
+ {
+ u32 ver = 0;
+ sym.id = CSR_SLT_BUILD_ID_NUMBER;
+ sym.obj = 0; /* To be updated by _find_in_slut() */
+
+ unifi_trace(card->ospriv, UDBG1, "Need f/w version\n");
+
+ /* Find chip build id entry in SLUT */
+ r = _find_in_slut(card, &sym, &slut_base);
+ if (r != CSR_RESULT_SUCCESS)
+ {
+ unifi_error(card->ospriv, "Failed to find CSR_SLT_BUILD_ID_NUMBER\n");
+ return CSR_RESULT_FAILURE;
+ }
+
+ /* Read running f/w version */
+ r = unifi_read32(card, sym.obj, &ver);
+ if (r == CSR_WIFI_HIP_RESULT_NO_DEVICE)
+ {
+ return r;
+ }
+ if (r != CSR_RESULT_SUCCESS)
+ {
+ unifi_error(card->ospriv, "Failed to read f/w id\n");
+ return CSR_RESULT_FAILURE;
+ }
+ card->build_id = ver;
+ }
+
+ /* Convert the ptest firmware to a patch against the running firmware */
+ pfw = xbv_to_patch(card, unifi_fw_read, dlpriv, pfwinfo, &psize);
+ if (!pfw)
+ {
+ unifi_error(card->ospriv, "Failed to convert f/w to patch");
+ return CSR_WIFI_HIP_RESULT_NO_MEMORY;
+ }
+ else
+ {
+ void *desc;
+ sym.id = CSR_SLT_BOOT_LOADER_CONTROL;
+ sym.obj = 0; /* To be updated by _find_in_slut() */
+
+ /* Find boot loader control entry in SLUT */
+ r = _find_in_slut(card, &sym, &slut_base);
+ if (r != CSR_RESULT_SUCCESS)
+ {
+ unifi_error(card->ospriv, "Failed to find BOOT_LOADER_CONTROL\n");
+ return CSR_RESULT_FAILURE;
+ }
+
+ r = unifi_set_host_state(card, UNIFI_HOST_STATE_AWAKE);
+ if (r != CSR_RESULT_SUCCESS)
+ {
+ unifi_error(card->ospriv, "Failed to wake UniFi\n");
+ }
+
+ /* Get a dlpriv for the patch buffer so that unifi_fw_read() can
+ * access it.
+ */
+ desc = unifi_fw_open_buffer(card->ospriv, pfw, psize);
+ if (!desc)
+ {
+ return CSR_WIFI_HIP_RESULT_NO_MEMORY;
+ }
+
+ /* Download the patch */
+ unifi_info(card->ospriv, "Downloading converted f/w as patch\n");
+ r = unifi_dl_patch(card, desc, sym.obj);
+ kfree(pfw);
+ unifi_fw_close_buffer(card->ospriv, desc);
+
+ if (r != CSR_RESULT_SUCCESS)
+ {
+ unifi_error(card->ospriv, "Converted patch download failed\n");
+ func_exit_r(r);
+ return r;
+ }
+ else
+ {
+ unifi_trace(card->ospriv, UDBG1, "Converted patch downloaded\n");
+ }
+
+ /* This command starts the firmware */
+ r = unifi_do_loader_op(card, sym.obj + 6, UNIFI_BOOT_LOADER_RESTART);
+ if (r != CSR_RESULT_SUCCESS)
+ {
+ unifi_error(card->ospriv, "Failed to write loader restart cmd\n");
+ }
+
+ func_exit_r(r);
+ return r;
+ }
+}
+
+
+/*
+ * ---------------------------------------------------------------------------
+ * unifi_dl_firmware
+ *
+ * Download the given firmware image to the UniFi.
+ *
+ * Arguments:
+ * card Pointer to card struct
+ * dlpriv A context pointer from the calling function to be
+ * passed when calling unifi_fw_read().
+ *
+ * Returns:
+ * CSR_RESULT_SUCCESS on success,
+ * CSR_WIFI_HIP_RESULT_NO_MEMORY memory allocation failed
+ * CSR_WIFI_HIP_RESULT_INVALID_VALUE error in XBV file
+ * CSR_RESULT_FAILURE SDIO error
+ *
+ * Notes:
+ * Stops and resets the chip, does the download and runs the new
+ * firmware.
+ * ---------------------------------------------------------------------------
+ */
+CsrResult unifi_dl_firmware(card_t *card, void *dlpriv)
+{
+ xbv1_t *fwinfo;
+ CsrResult r;
+
+ func_enter();
+
+ fwinfo = kmalloc(sizeof(xbv1_t), GFP_KERNEL);
+ if (fwinfo == NULL)
+ {
+ unifi_error(card->ospriv, "Failed to allocate memory for firmware\n");
+ return CSR_WIFI_HIP_RESULT_NO_MEMORY;
+ }
+
+ /*
+ * Scan the firmware file to find the TLVs we are interested in.
+ * These are:
+ * - check we support the file format version in VERF
+ * - SLTP Symbol Lookup Table Pointer
+ * - FWDL firmware download segments
+ * - FWOV firmware overlay segment
+ * - VMEQ Register probe tests to verify matching h/w
+ */
+ r = xbv1_parse(card, unifi_fw_read, dlpriv, fwinfo);
+ if (r != CSR_RESULT_SUCCESS || fwinfo->mode != xbv_firmware)
+ {
+ unifi_error(card->ospriv, "File type is %s, expected firmware.\n",
+ fwinfo->mode == xbv_patch?"patch" : "unknown");
+ kfree(fwinfo);
+ return CSR_WIFI_HIP_RESULT_INVALID_VALUE;
+ }
+
+ /* UF6xxx doesn't accept firmware, only patches. Therefore we convert
+ * the file to patch format with version numbers matching the current
+ * running firmware, and then download via the patch mechanism.
+ * The sole purpose of this is to support production test firmware across
+ * different ROM releases, the test firmware being provided in non-patch
+ * format.
+ */
+ if (card->chip_id > SDIO_CARD_ID_UNIFI_2)
+ {
+ unifi_info(card->ospriv, "Must convert f/w to patch format\n");
+ r = do_patch_convert_download(card, dlpriv, fwinfo);
+ }
+ else
+ {
+ /* Older UniFi chips allowed firmware to be directly loaded onto the
+ * chip, which is no longer supported.
+ */
+ unifi_error(card->ospriv, "Only patch downloading supported\n");
+ r = CSR_WIFI_HIP_RESULT_INVALID_VALUE;
+ }
+
+ kfree(fwinfo);
+ func_exit_r(r);
+ return r;
+} /* unifi_dl_firmware() */
+
+
+/*
+ * ---------------------------------------------------------------------------
+ * unifi_dl_patch
+ *
+ * Load the given patch set into UniFi.
+ *
+ * Arguments:
+ * card Pointer to card struct
+ * dlpriv The os specific handle to the firmware file.
+ * boot_ctrl The address of the boot loader control structure.
+ *
+ * Returns:
+ * CSR_RESULT_SUCCESS on success,
+ * CSR_WIFI_HIP_RESULT_NO_MEMORY memory allocation failed
+ * CSR_WIFI_HIP_RESULT_INVALID_VALUE error in XBV file
+ * CSR_RESULT_FAILURE SDIO error
+ *
+ * Notes:
+ * This ends up telling UniFi to restart.
+ * ---------------------------------------------------------------------------
+ */
+CsrResult unifi_dl_patch(card_t *card, void *dlpriv, u32 boot_ctrl)
+{
+ xbv1_t *fwinfo;
+ CsrResult r;
+
+ func_enter();
+
+ unifi_info(card->ospriv, "unifi_dl_patch %p %08x\n", dlpriv, boot_ctrl);
+
+ fwinfo = kmalloc(sizeof(xbv1_t), GFP_KERNEL);
+ if (fwinfo == NULL)
+ {
+ unifi_error(card->ospriv, "Failed to allocate memory for patches\n");
+ func_exit();
+ return CSR_WIFI_HIP_RESULT_NO_MEMORY;
+ }
+
+ /*
+ * Scan the firmware file to find the TLVs we are interested in.
+ * These are:
+ * - check we support the file format version in VERF
+ * - FWID The build ID of the ROM that we can patch
+ * - PTDL patch download segments
+ */
+ r = xbv1_parse(card, unifi_fw_read, dlpriv, fwinfo);
+ if (r != CSR_RESULT_SUCCESS || fwinfo->mode != xbv_patch)
+ {
+ kfree(fwinfo);
+ unifi_error(card->ospriv, "Failed to read in patch file\n");
+ func_exit();
+ return CSR_WIFI_HIP_RESULT_INVALID_VALUE;
+ }
+
+ /*
+ * We have to check the build id read from the SLUT against that
+ * for the patch file. They have to match exactly.
+ * "card->build_id" == XBV1.PTCH.FWID
+ */
+ if (card->build_id != fwinfo->build_id)
+ {
+ unifi_error(card->ospriv, "Wrong patch file for chip (chip = %lu, file = %lu)\n",
+ card->build_id, fwinfo->build_id);
+ kfree(fwinfo);
+#ifndef CSR_WIFI_IGNORE_PATCH_VERSION_MISMATCH
+ func_exit();
+ return CSR_WIFI_HIP_RESULT_INVALID_VALUE;
+#else
+ fwinfo = NULL;
+ dlpriv = NULL;
+ return CSR_RESULT_SUCCESS;
+#endif
+ }
+
+ r = do_patch_download(card, dlpriv, fwinfo, boot_ctrl);
+ if (r != CSR_RESULT_SUCCESS)
+ {
+ unifi_error(card->ospriv, "Failed to patch image\n");
+ }
+
+ kfree(fwinfo);
+
+ func_exit_r(r);
+ return r;
+} /* unifi_dl_patch() */
+
+
+void* unifi_dl_fw_read_start(card_t *card, s8 is_fw)
+{
+ card_info_t card_info;
+
+ unifi_card_info(card, &card_info);
+ unifi_trace(card->ospriv, UDBG5,
+ "id=%d, ver=0x%x, fw_build=%u, fw_hip=0x%x, block_size=%d\n",
+ card_info.chip_id, card_info.chip_version,
+ card_info.fw_build, card_info.fw_hip_version,
+ card_info.sdio_block_size);
+
+ return unifi_fw_read_start(card->ospriv, is_fw, &card_info);
+}
+
+
+/*
+ * ---------------------------------------------------------------------------
+ * safe_read_shared_location
+ *
+ * Read a shared memory location repeatedly until we get two readings
+ * the same.
+ *
+ * Arguments:
+ * card Pointer to card context struct.
+ * unifi_addr UniFi shared-data-memory address to access.
+ * pdata Pointer to a byte variable for the value read.
+ *
+ *
+ * Returns:
+ * CSR_RESULT_SUCCESS on success, CSR error code on failure
+ * ---------------------------------------------------------------------------
+ */
+static CsrResult safe_read_shared_location(card_t *card, u32 address, u8 *pdata)
+{
+ CsrResult r;
+ u16 limit = 1000;
+ u8 b, b2;
+
+ *pdata = 0;
+
+ r = unifi_read_8_or_16(card, address, &b);
+ if (r != CSR_RESULT_SUCCESS)
+ {
+ return r;
+ }
+
+ while (limit--)
+ {
+ r = unifi_read_8_or_16(card, address, &b2);
+ if (r != CSR_RESULT_SUCCESS)
+ {
+ return r;
+ }
+
+ /* When we have a stable value, return it */
+ if (b == b2)
+ {
+ *pdata = b;
+ return CSR_RESULT_SUCCESS;
+ }
+
+ b = b2;
+ }
+
+ return CSR_RESULT_FAILURE;
+} /* safe_read_shared_location() */
+
+
+/*
+ * ---------------------------------------------------------------------------
+ * unifi_do_loader_op
+ *
+ * Send a loader / boot_loader command to the UniFi and wait for
+ * it to complete.
+ *
+ * Arguments:
+ * card Pointer to card context struct.
+ * op_addr The address of the loader operation control word.
+ * opcode The operation to perform.
+ *
+ * Returns:
+ * CSR_RESULT_SUCCESS on success
+ * CSR_RESULT_FAILURE SDIO error or SDIO/XAP timeout
+ * ---------------------------------------------------------------------------
+ */
+
+/*
+ * Ideally instead of sleeping, we want to busy wait.
+ * Currently there is no framework API to do this. When it becomes available,
+ * we can use it to busy wait using usecs
+ */
+#define OPERATION_TIMEOUT_LOOPS (100) /* when OPERATION_TIMEOUT_DELAY==1, (500) otherwise */
+#define OPERATION_TIMEOUT_DELAY 1 /* msec, or 200usecs */
+
+CsrResult unifi_do_loader_op(card_t *card, u32 op_addr, u8 opcode)
+{
+ CsrResult r;
+ s16 op_retries;
+
+ unifi_trace(card->ospriv, UDBG4, "Loader cmd 0x%0x -> 0x%08x\n", opcode, op_addr);
+
+ /* Set the Operation command byte to the opcode */
+ r = unifi_write_8_or_16(card, op_addr, opcode);
+ if (r != CSR_RESULT_SUCCESS)
+ {
+ unifi_error(card->ospriv, "Failed to write loader copy command\n");
+ return r;
+ }
+
+ /* Wait for Operation command byte to be Idle */
+ /* Typically takes ~100us */
+ op_retries = 0;
+ r = CSR_RESULT_SUCCESS;
+ while (1)
+ {
+ u8 op;
+
+ /*
+ * Read the memory location until two successive reads give
+ * the same value.
+ * Then handle it.
+ */
+ r = safe_read_shared_location(card, op_addr, &op);
+ if (r != CSR_RESULT_SUCCESS)
+ {
+ unifi_error(card->ospriv, "Failed to read loader status\n");
+ break;
+ }
+
+ if (op == UNIFI_LOADER_IDLE)
+ {
+ /* Success */
+ break;
+ }
+
+ if (op != opcode)
+ {
+ unifi_error(card->ospriv, "Error reported by loader: 0x%X\n", op);
+ r = CSR_RESULT_FAILURE;
+ break;
+ }
+
+ /* Allow 500us timeout */
+ if (++op_retries >= OPERATION_TIMEOUT_LOOPS)
+ {
+ unifi_error(card->ospriv, "Timeout waiting for loader to ack transfer\n");
+ /* Stop XAPs to aid post-mortem */
+ r = unifi_card_stop_processor(card, UNIFI_PROC_BOTH);
+ if (r != CSR_RESULT_SUCCESS)
+ {
+ unifi_error(card->ospriv, "Failed to stop UniFi processors\n");
+ }
+ else
+ {
+ r = CSR_RESULT_FAILURE;
+ }
+ break;
+ }
+ CsrThreadSleep(OPERATION_TIMEOUT_DELAY);
+ } /* Loop exits with r != CSR_RESULT_SUCCESS on error */
+
+ return r;
+} /* unifi_do_loader_op() */
+
+
+/*
+ * ---------------------------------------------------------------------------
+ * send_ptdl_to_unifi
+ *
+ * Copy a patch block from userland to the UniFi.
+ * This function reads data, 2K at a time, from userland and writes
+ * it to the UniFi.
+ *
+ * Arguments:
+ * card A pointer to the card structure
+ * dlpriv The os specific handle for the firmware file
+ * ptdl A pointer ot the PTDL block
+ * handle The buffer handle to use for the xfer
+ * op_addr The address of the loader operation control word
+ *
+ * Returns:
+ * Number of bytes sent (Positive) or negative value indicating
+ * error code:
+ * CSR_WIFI_HIP_RESULT_NO_MEMORY memory allocation failed
+ * CSR_WIFI_HIP_RESULT_INVALID_VALUE error in XBV file
+ * CSR_RESULT_FAILURE SDIO error
+ * ---------------------------------------------------------------------------
+ */
+static CsrResult send_ptdl_to_unifi(card_t *card, void *dlpriv,
+ const struct PTDL *ptdl, u32 handle,
+ u32 op_addr)
+{
+ u32 offset;
+ u8 *buf;
+ s32 data_len;
+ u32 write_len;
+ CsrResult r;
+ const u16 buf_size = 2 * 1024;
+
+ offset = ptdl->dl_offset;
+ data_len = ptdl->dl_size;
+
+ if (data_len > buf_size)
+ {
+ unifi_error(card->ospriv, "PTDL block is too large (%u)\n",
+ ptdl->dl_size);
+ return CSR_WIFI_HIP_RESULT_INVALID_VALUE;
+ }
+
+ buf = kmalloc(buf_size, GFP_KERNEL);
+ if (buf == NULL)
+ {
+ unifi_error(card->ospriv, "Failed to allocate transfer buffer for firmware download\n");
+ return CSR_WIFI_HIP_RESULT_NO_MEMORY;
+ }
+
+ r = CSR_RESULT_SUCCESS;
+
+ if (unifi_fw_read(card->ospriv, dlpriv, offset, buf, data_len) != data_len)
+ {
+ unifi_error(card->ospriv, "Failed to read from file\n");
+ }
+ else
+ {
+ /* We can always round these if the host wants to */
+ if (card->sdio_io_block_pad)
+ {
+ write_len = (data_len + (card->sdio_io_block_size - 1)) &
+ ~(card->sdio_io_block_size - 1);
+
+ /* Zero out the rest of the buffer (This isn't needed, but it
+ * makes debugging things later much easier). */
+ memset(buf + data_len, 0, write_len - data_len);
+ }
+ else
+ {
+ write_len = data_len;
+ }
+
+ r = unifi_bulk_rw_noretry(card, handle, buf, write_len, UNIFI_SDIO_WRITE);
+ if (r != CSR_RESULT_SUCCESS)
+ {
+ unifi_error(card->ospriv, "CMD53 failed writing %d bytes to handle %ld\n",
+ data_len, handle);
+ }
+ else
+ {
+ /*
+ * Can change the order of things to overlap read from file
+ * with copy to unifi
+ */
+ r = unifi_do_loader_op(card, op_addr, UNIFI_BOOT_LOADER_PATCH);
+ }
+ }
+
+ kfree(buf);
+
+ if (r != CSR_RESULT_SUCCESS && r != CSR_WIFI_HIP_RESULT_NO_DEVICE)
+ {
+ unifi_error(card->ospriv, "Failed to copy block of %u bytes to UniFi\n",
+ ptdl->dl_size);
+ }
+
+ return r;
+} /* send_ptdl_to_unifi() */
+
+
+/*
+ * ---------------------------------------------------------------------------
+ * do_patch_download
+ *
+ * This function downloads a set of patches to UniFi and then
+ * causes it to restart.
+ *
+ * Arguments:
+ * card Pointer to card struct.
+ * dlpriv A context pointer from the calling function to be
+ * used when reading the XBV file. This can be NULL
+ * in which case not patches are applied.
+ * pfwinfo Pointer to a fwinfo struct describing the f/w
+ * XBV file.
+ * boot_ctrl_addr The address of the boot loader control structure.
+ *
+ * Returns:
+ * 0 on success, or an error code
+ * CSR_WIFI_HIP_RESULT_INVALID_VALUE for a bad laoader version number
+ * ---------------------------------------------------------------------------
+ */
+static CsrResult do_patch_download(card_t *card, void *dlpriv, xbv1_t *pfwinfo, u32 boot_ctrl_addr)
+{
+ CsrResult r;
+ s32 i;
+ u16 loader_version;
+ u16 handle;
+ u32 total_bytes;
+
+ /*
+ * Read info from the SDIO Loader Control Data Structure
+ */
+ /* Check the loader version */
+ r = unifi_card_read16(card, boot_ctrl_addr, &loader_version);
+ if (r != CSR_RESULT_SUCCESS)
+ {
+ unifi_error(card->ospriv, "Patch download: Failed to read loader version\n");
+ return r;
+ }
+ unifi_trace(card->ospriv, UDBG2, "Patch download: boot loader version 0x%04X\n", loader_version);
+ switch (loader_version)
+ {
+ case 0x0000:
+ break;
+
+ default:
+ unifi_error(card->ospriv, "Patch loader version (0x%04X) is not supported by this driver\n",
+ loader_version);
+ return CSR_WIFI_HIP_RESULT_INVALID_VALUE;
+ }
+
+ /* Retrieve the handle to use with CMD53 */
+ r = unifi_card_read16(card, boot_ctrl_addr + 4, &handle);
+ if (r != CSR_RESULT_SUCCESS)
+ {
+ unifi_error(card->ospriv, "Patch download: Failed to read loader handle\n");
+ return r;
+ }
+
+ /* Set the mask of LEDs to flash */
+ if (card->loader_led_mask)
+ {
+ r = unifi_card_write16(card, boot_ctrl_addr + 2,
+ (u16)card->loader_led_mask);
+ if (r != CSR_RESULT_SUCCESS)
+ {
+ unifi_error(card->ospriv, "Patch download: Failed to write LED mask\n");
+ return r;
+ }
+ }
+
+ total_bytes = 0;
+
+ /* Copy download data to UniFi memory */
+ for (i = 0; i < pfwinfo->num_ptdl; i++)
+ {
+ unifi_trace(card->ospriv, UDBG3, "Patch download: %d Downloading for %d from offset %d\n",
+ i,
+ pfwinfo->ptdl[i].dl_size,
+ pfwinfo->ptdl[i].dl_offset);
+
+ r = send_ptdl_to_unifi(card, dlpriv, &pfwinfo->ptdl[i],
+ handle, boot_ctrl_addr + 6);
+ if (r == CSR_WIFI_HIP_RESULT_NO_DEVICE)
+ {
+ return r;
+ }
+ if (r != CSR_RESULT_SUCCESS)
+ {
+ unifi_error(card->ospriv, "Patch failed after %u bytes\n",
+ total_bytes);
+ return r;
+ }
+ total_bytes += pfwinfo->ptdl[i].dl_size;
+ }
+
+ return CSR_RESULT_SUCCESS;
+} /* do_patch_download() */
+
+