[PATCH wireless-next 09/35] wifi: mm81x: add fw.c
From: Lachlan Hodges
Date: Thu Feb 26 2026 - 23:17:45 EST
(Patches split per file for review, see cover letter for more
information)
Signed-off-by: Lachlan Hodges <lachlan.hodges@xxxxxxxxxxxxxx>
---
drivers/net/wireless/morsemicro/mm81x/fw.c | 743 +++++++++++++++++++++
1 file changed, 743 insertions(+)
create mode 100644 drivers/net/wireless/morsemicro/mm81x/fw.c
diff --git a/drivers/net/wireless/morsemicro/mm81x/fw.c b/drivers/net/wireless/morsemicro/mm81x/fw.c
new file mode 100644
index 000000000000..6d138419abce
--- /dev/null
+++ b/drivers/net/wireless/morsemicro/mm81x/fw.c
@@ -0,0 +1,743 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2017-2026 Morse Micro
+ */
+#include <linux/kernel.h>
+#include <linux/firmware.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/string_choices.h>
+#include <net/mac80211.h>
+#include <linux/elf.h>
+#include <linux/crc32.h>
+#include "debug.h"
+#include "fw.h"
+#include "mac.h"
+#include "bus.h"
+
+/*
+ * Maximum wait time (milliseconds) for firmware to boot (for host table
+ * pointer to be available)
+ */
+#define MAX_WAIT_FOR_HOST_TABLE_PTR_MS 1200
+
+/* Number of times to attempt flashing FW */
+#define FW_FLASH_ATTEMPT_COUNT 3
+
+static int mm81x_fw_get_header(const u8 *data, mm81x_elf_ehdr *ehdr)
+{
+ mm81x_elf_ehdr *p = (mm81x_elf_ehdr *)data;
+
+ /* Magic check */
+ if (p->e_ident[EI_MAG0] != ELFMAG0 || p->e_ident[EI_MAG1] != ELFMAG1 ||
+ p->e_ident[EI_MAG2] != ELFMAG2 || p->e_ident[EI_MAG3] != ELFMAG3)
+ return -EINVAL;
+
+ /* elf32 and little endian */
+ if (p->e_ident[EI_DATA] != ELFDATA2LSB ||
+ p->e_ident[EI_CLASS] != ELFCLASS32)
+ return -EINVAL;
+
+ ehdr->e_phoff = mm81x_fle32_to_cpu(p->e_phoff);
+ ehdr->e_phentsize = mm81x_fle16_to_cpu(p->e_phentsize);
+ ehdr->e_phnum = mm81x_fle16_to_cpu(p->e_phnum);
+ ehdr->e_shoff = mm81x_fle32_to_cpu(p->e_shoff);
+ ehdr->e_shentsize = mm81x_fle16_to_cpu(p->e_shentsize);
+ ehdr->e_shnum = mm81x_fle16_to_cpu(p->e_shnum);
+ ehdr->e_shstrndx = mm81x_fle16_to_cpu(p->e_shstrndx);
+ ehdr->e_entry = mm81x_fle32_to_cpu(p->e_entry);
+
+ return 0;
+}
+
+static void mm81x_fw_parse_info(struct mm81x *mm, const u8 *data, int length)
+{
+ const struct mm81x_fw_info_tlv *tlv =
+ (const struct mm81x_fw_info_tlv *)data;
+
+ while ((u8 *)tlv < (data + length)) {
+ switch (le16_to_cpu(tlv->type)) {
+ case MM81X_FW_INFO_TLV_BCF_ADDR:
+ mm->bcf_address =
+ get_unaligned_le32((__force __le32 *)tlv->val);
+ break;
+ default:
+ break;
+ }
+ tlv = (const struct mm81x_fw_info_tlv *)((u8 *)tlv +
+ le16_to_cpu(
+ tlv->length) +
+ sizeof(*tlv));
+ }
+}
+
+static int mm81x_fw_get_section_header(const u8 *data, mm81x_elf_ehdr *ehdr,
+ mm81x_elf_shdr *shdr, int i)
+{
+ mm81x_elf_shdr *p = (mm81x_elf_shdr *)(data + ehdr->e_shoff +
+ (i * ehdr->e_shentsize));
+
+ shdr->sh_name = mm81x_fle32_to_cpu(p->sh_name);
+ shdr->sh_type = mm81x_fle32_to_cpu(p->sh_type);
+ shdr->sh_offset = mm81x_fle32_to_cpu(p->sh_offset);
+ shdr->sh_addr = mm81x_fle32_to_cpu(p->sh_addr);
+ shdr->sh_size = mm81x_fle32_to_cpu(p->sh_size);
+ shdr->sh_flags = mm81x_fle32_to_cpu(p->sh_flags);
+
+ return 0;
+}
+
+static int mm81x_fw_set_boot_addr(struct mm81x *mm, uint32_t addr)
+{
+ int status;
+
+ mm81x_dbg(mm, MM81X_DBG_FW, "Overwriting boot address to 0x%x", addr);
+ mm81x_claim_bus(mm);
+ status = mm81x_reg32_write(mm, MM81X_REG_BOOT_ADDR(mm), addr);
+ mm81x_release_bus(mm);
+ return status;
+}
+
+static int mm81x_fw_load_fw(struct mm81x *mm, const struct firmware *fw)
+{
+ int i;
+ int ret = 0;
+ mm81x_elf_ehdr ehdr;
+ mm81x_elf_phdr phdr;
+ mm81x_elf_shdr shdr;
+ mm81x_elf_shdr sh_strtab;
+ const char *sh_strs;
+
+ u8 *fw_buf = devm_kmalloc(mm->dev, ROUND_BYTES_TO_WORD(fw->size),
+ GFP_KERNEL);
+
+ if (!fw_buf)
+ return -ENOMEM;
+
+ if (mm81x_fw_get_header(fw->data, &ehdr)) {
+ mm81x_err(mm, "Wrong file format");
+ return -EINVAL;
+ }
+
+ if (mm81x_fw_get_section_header(fw->data, &ehdr, &sh_strtab,
+ ehdr.e_shstrndx)) {
+ mm81x_err(mm, "Invalid firmware. Missing string table");
+ return -ENOENT;
+ }
+
+ sh_strs = (const char *)fw->data + sh_strtab.sh_offset;
+
+ for (i = 0; i < ehdr.e_phnum; i++) {
+ int status;
+ int address;
+
+ mm81x_elf_phdr *p = (mm81x_elf_phdr *)(fw->data + ehdr.e_phoff +
+ i * ehdr.e_phentsize);
+
+ phdr.p_type = le32_to_cpu((__force __le32)p->p_type);
+ phdr.p_offset = le32_to_cpu((__force __le32)p->p_offset);
+ phdr.p_paddr = le32_to_cpu((__force __le32)p->p_paddr);
+ phdr.p_filesz = le32_to_cpu((__force __le32)p->p_filesz);
+ phdr.p_memsz = le32_to_cpu((__force __le32)p->p_memsz);
+
+ address = phdr.p_paddr;
+ if (address == IFLASH_BASE_ADDR || address == DFLASH_BASE_ADDR)
+ continue;
+
+ if (phdr.p_type != PT_LOAD || !phdr.p_memsz)
+ continue;
+
+ if (phdr.p_filesz && phdr.p_offset &&
+ (phdr.p_offset + phdr.p_filesz) < fw->size) {
+ u32 padded_size = ROUND_BYTES_TO_WORD(phdr.p_filesz);
+
+ memcpy(fw_buf, fw->data + phdr.p_offset, padded_size);
+ /* Set padding to 0xff */
+ memset(fw_buf + phdr.p_filesz, 0xff,
+ padded_size - phdr.p_filesz);
+ mm81x_claim_bus(mm);
+ status = mm81x_dm_write(mm, address, fw_buf,
+ padded_size);
+ mm81x_release_bus(mm);
+ if (status) {
+ ret = -EIO;
+ break;
+ }
+ }
+ }
+
+ for (i = 0; i < ehdr.e_shnum; i++) {
+ if (mm81x_fw_get_section_header(fw->data, &ehdr, &shdr, i))
+ continue;
+
+ /* This is the firmware info. Parse it */
+ if (!strncmp(sh_strs + shdr.sh_name, ".fw_info",
+ sizeof(".fw_info")))
+ mm81x_fw_parse_info(mm, fw->data + shdr.sh_offset,
+ shdr.sh_size);
+ }
+
+ if (ehdr.e_entry)
+ ret = mm81x_fw_set_boot_addr(mm, ehdr.e_entry);
+
+ devm_kfree(mm->dev, fw_buf);
+ return ret;
+}
+
+static int __mm81x_fw_load_bcf(struct mm81x *mm, unsigned int addr,
+ const void *src, size_t src_len, u8 *scratch,
+ size_t scratch_cap)
+{
+ size_t rounded = ROUND_BYTES_TO_WORD(src_len);
+ int st;
+
+ if (rounded > scratch_cap)
+ return -EINVAL;
+ if (rounded > BCF_DATABASE_SIZE)
+ return -EFBIG;
+
+ memcpy(scratch, src, src_len);
+ if (rounded > src_len)
+ memset(scratch + src_len, 0xff, rounded - src_len);
+
+ mm81x_claim_bus(mm);
+ st = mm81x_dm_write(mm, addr, scratch, rounded);
+ mm81x_release_bus(mm);
+
+ return st ? -EIO : 0;
+}
+
+static int mm81x_fw_load_bcf(struct mm81x *mm, const struct firmware *bcf,
+ unsigned int bcf_address)
+{
+ int i, ret = 0;
+ size_t reg_prefix_len, cfg_len_rounded = 0, reg_len_rounded;
+ mm81x_elf_ehdr ehdr;
+ mm81x_elf_shdr shdr, sh_strtab;
+ const char *sh_strs, *reg_prefix = ".regdom_", *reg_src;
+ size_t reg_len;
+ u8 *bcf_buf;
+
+ bcf_buf = devm_kmalloc(mm->dev, ROUND_BYTES_TO_WORD(bcf->size),
+ GFP_KERNEL);
+ if (!bcf_buf)
+ return -ENOMEM;
+
+ if (mm81x_fw_get_header(bcf->data, &ehdr)) {
+ mm81x_err(mm, "Wrong file format");
+ ret = -EINVAL;
+ goto out_free;
+ }
+
+ if (mm81x_fw_get_section_header(bcf->data, &ehdr, &sh_strtab,
+ ehdr.e_shstrndx)) {
+ mm81x_err(mm, "Invalid BCF - missing string table");
+ ret = -ENOENT;
+ goto out_free;
+ }
+
+ sh_strs = (const char *)bcf->data + sh_strtab.sh_offset;
+ reg_prefix_len = strlen(reg_prefix);
+
+ for (i = 0; i < ehdr.e_shnum; i++) {
+ if (mm81x_fw_get_section_header(bcf->data, &ehdr, &shdr, i))
+ continue;
+ if (strcmp(sh_strs + shdr.sh_name, ".board_config"))
+ continue;
+
+ cfg_len_rounded = ROUND_BYTES_TO_WORD(shdr.sh_size);
+ mm81x_dbg(mm, MM81X_DBG_FW,
+ "Write BCF board_config - addr 0x%x size %zu",
+ bcf_address, cfg_len_rounded);
+
+ ret = __mm81x_fw_load_bcf(mm, bcf_address,
+ bcf->data + shdr.sh_offset,
+ shdr.sh_size, bcf_buf,
+ ROUND_BYTES_TO_WORD(bcf->size));
+ if (ret)
+ goto out_free;
+
+ bcf_address += cfg_len_rounded;
+ break;
+ }
+
+ ret = -EINVAL;
+ for (; i < ehdr.e_shnum; i++) {
+ if (mm81x_fw_get_section_header(bcf->data, &ehdr, &shdr, i))
+ continue;
+ if (strncmp(sh_strs + shdr.sh_name, reg_prefix, reg_prefix_len))
+ continue;
+ if (strncmp(sh_strs + shdr.sh_name + reg_prefix_len,
+ mm->country, 2))
+ continue;
+
+ reg_src = bcf->data + shdr.sh_offset;
+ reg_len = shdr.sh_size;
+ mm81x_dbg(mm, MM81X_DBG_FW, "Write BCF %s - addr 0x%x size %zu",
+ sh_strs + shdr.sh_name, bcf_address,
+ ROUND_BYTES_TO_WORD(reg_len));
+ ret = 0;
+ break;
+ }
+
+ if (ret)
+ goto out_free;
+
+ reg_len_rounded = ROUND_BYTES_TO_WORD(reg_len);
+ if ((cfg_len_rounded + reg_len_rounded) > BCF_DATABASE_SIZE) {
+ ret = -EFBIG;
+ goto out_free;
+ }
+
+ ret = __mm81x_fw_load_bcf(mm, bcf_address, reg_src, reg_len, bcf_buf,
+ ROUND_BYTES_TO_WORD(bcf->size));
+
+out_free:
+ devm_kfree(mm->dev, bcf_buf);
+ return ret;
+}
+
+static void mm81x_fw_clear_aon(struct mm81x *mm)
+{
+ int idx;
+ u8 count = MM81X_REG_AON_COUNT(mm);
+ u32 address = MM81X_REG_AON_ADDR(mm);
+
+ if (address) {
+ for (idx = 0; idx < count; idx++, address += 4) {
+ if (mm->bus_type == MM81X_BUS_TYPE_USB && idx == 0)
+ /* Keep the USB power domain enabled in AON. */
+ mm81x_reg32_write(mm, address,
+ MM81X_REG_AON_USB_RESET(mm));
+ else
+ /* clear AON */
+ mm81x_reg32_write(mm, address, 0x0);
+ }
+ }
+
+ mm81x_hw_toggle_aon_latch(mm);
+}
+
+static void mm81x_fw_trigger(struct mm81x *mm)
+{
+ const unsigned int wait_after_msi_trigger_ms = 1;
+
+ mm81x_claim_bus(mm);
+ /*
+ * If not coming from a full reset, some AON flags may be latched.
+ * Make sure to clear any hanging AON bits (can affect booting).
+ */
+ mm81x_fw_clear_aon(mm);
+
+ if (MM81X_REG_CLK_CTRL(mm))
+ mm81x_reg32_write(mm, MM81X_REG_CLK_CTRL(mm),
+ MM81X_REG_CLK_CTRL_VALUE(mm));
+
+ mm81x_reg32_write(mm, MM81X_REG_MSI(mm), MM81X_REG_MSI_HOST_INT(mm));
+ mm81x_release_bus(mm);
+
+ /* Give the chip a chance to boot */
+ mdelay(wait_after_msi_trigger_ms);
+}
+
+static int mm81x_fw_verify_magic(struct mm81x *mm)
+{
+ int ret = 0;
+ int magic = ~MM81X_REG_HOST_MAGIC_VALUE(mm);
+
+ mm81x_claim_bus(mm);
+ mm81x_reg32_read(mm,
+ mm->host_table_ptr +
+ offsetof(struct host_table, magic_number),
+ &magic);
+
+ if (magic != MM81X_REG_HOST_MAGIC_VALUE(mm)) {
+ mm81x_err(mm, "FW magic mismatch 0x%08x:0x%08x",
+ MM81X_REG_HOST_MAGIC_VALUE(mm), magic);
+ ret = -EIO;
+ }
+
+ mm81x_release_bus(mm);
+ return ret;
+}
+
+static int mm81x_fw_get_flags(struct mm81x *mm)
+{
+ int ret = 0;
+ int fw_flags = 0;
+
+ mm81x_claim_bus(mm);
+ ret = mm81x_reg32_read(mm,
+ mm->host_table_ptr + offsetof(struct host_table,
+ firmware_flags),
+ &fw_flags);
+ mm->firmware_flags = fw_flags;
+ mm81x_release_bus(mm);
+
+ return ret;
+}
+
+static int mm81x_fw_check_compatibility(struct mm81x *mm)
+{
+ int ret = 0;
+ u32 fw_version;
+ u32 major;
+ u32 minor;
+ u32 patch;
+
+ mm81x_claim_bus(mm);
+ ret = mm81x_reg32_read(mm,
+ mm->host_table_ptr + offsetof(struct host_table,
+ fw_version_number),
+ &fw_version);
+ mm81x_release_bus(mm);
+
+ major = MM81X_SEMVER_GET_MAJOR(fw_version);
+ minor = MM81X_SEMVER_GET_MINOR(fw_version);
+ patch = MM81X_SEMVER_GET_PATCH(fw_version);
+
+ /* Firmware on device must be recent enough for driver */
+ if (ret == 0 && major != HOST_CMD_SEMVER_MAJOR) {
+ mm81x_err(
+ mm,
+ "Incompatible FW version: (Driver) %d.%d.%d, (Chip) %d.%d.%d\n",
+ HOST_CMD_SEMVER_MAJOR, HOST_CMD_SEMVER_MINOR,
+ HOST_CMD_SEMVER_PATCH, major, minor, patch);
+ ret = -EPERM;
+ } else if (ret == 0 && minor != HOST_CMD_SEMVER_MINOR) {
+ mm81x_warn(
+ mm,
+ "FW version mismatch, some features might not be supported: (Driver) %d.%d.%d, (Chip) %d.%d.%d",
+ HOST_CMD_SEMVER_MAJOR, HOST_CMD_SEMVER_MINOR,
+ HOST_CMD_SEMVER_PATCH, major, minor, patch);
+ }
+
+ return ret;
+}
+
+static int mm81x_fw_invalidate_host_ptr(struct mm81x *mm)
+{
+ int ret;
+
+ mm->host_table_ptr = 0;
+ mm81x_claim_bus(mm);
+ ret = mm81x_reg32_write(mm, MM81X_REG_HOST_MANIFEST_PTR(mm), 0);
+ mm81x_release_bus(mm);
+ return ret;
+}
+
+static int mm81x_fw_get_host_table_ptr(struct mm81x *mm)
+{
+ int ret = 0;
+ unsigned long timeout =
+ jiffies + msecs_to_jiffies(MAX_WAIT_FOR_HOST_TABLE_PTR_MS);
+
+ mm81x_claim_bus(mm);
+ while (1) {
+ ret = mm81x_reg32_read(mm, MM81X_REG_HOST_MANIFEST_PTR(mm),
+ &mm->host_table_ptr);
+
+ if (mm->host_table_ptr)
+ break;
+
+ if (time_after(jiffies, timeout)) {
+ ret = -EIO;
+ break;
+ }
+
+ usleep_range(5000, 10000);
+ }
+
+ mm81x_release_bus(mm);
+ return ret;
+}
+
+static int mm81x_fw_read_ext_host_table(struct mm81x *mm,
+ struct ext_host_tbl **ext_host_table)
+{
+ int ret = 0;
+ u32 host_tbl_ptr = mm->host_table_ptr;
+ u32 ext_host_tbl_ptr;
+ u32 ext_host_tbl_ptr_addr =
+ host_tbl_ptr + offsetof(struct host_table, ext_host_tbl_addr);
+ u32 ext_host_tbl_len;
+ u32 ext_host_tbl_len_ptr_addr;
+ struct ext_host_tbl *host_tbl = NULL;
+
+ mm81x_claim_bus(mm);
+ ret = mm81x_reg32_read(mm, ext_host_tbl_ptr_addr, &ext_host_tbl_ptr);
+ if (ret)
+ goto exit;
+
+ if (!ext_host_tbl_ptr) {
+ ret = -ENXIO;
+ goto exit;
+ }
+
+ ext_host_tbl_len_ptr_addr =
+ ext_host_tbl_ptr +
+ offsetof(struct ext_host_tbl, ext_host_tbl_length);
+
+ ret = mm81x_reg32_read(mm, ext_host_tbl_len_ptr_addr,
+ &ext_host_tbl_len);
+ if (ret)
+ goto exit;
+
+ ext_host_tbl_len = ROUND_BYTES_TO_WORD(ext_host_tbl_len);
+ if (WARN_ON(ext_host_tbl_len == 0 || ext_host_tbl_len > INT_MAX)) {
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ host_tbl = kmalloc(ext_host_tbl_len, GFP_KERNEL);
+ if (!host_tbl) {
+ ret = -ENOMEM;
+ goto exit;
+ }
+
+ ret = mm81x_dm_read(mm, ext_host_tbl_ptr, (u8 *)host_tbl,
+ (int)ext_host_tbl_len);
+ if (ret)
+ goto exit;
+
+ mm81x_release_bus(mm);
+ *ext_host_table = host_tbl;
+ return ret;
+
+exit:
+ mm81x_release_bus(mm);
+ kfree(host_tbl);
+ return ret;
+}
+
+static void mm81x_fw_update_capabilities(struct mm81x *mm,
+ struct ext_host_tbl_s1g_caps *caps)
+{
+ int i;
+
+ for (i = 0; i < FW_CAPABILITIES_FLAGS_WIDTH; i++) {
+ mm->fw_caps.flags[i] = le32_to_cpu(caps->flags[i]);
+ mm81x_dbg(mm, MM81X_DBG_FW, "Firmware Manifest Flags%d: 0x%x",
+ i, le32_to_cpu(caps->flags[i]));
+ }
+ mm->fw_caps.ampdu_mss = caps->ampdu_mss;
+ mm->fw_caps.mm81x_mmss_offset = caps->mm81x_mmss_offset;
+ mm->fw_caps.beamformee_sts_capability = caps->beamformee_sts_capability;
+ mm->fw_caps.maximum_ampdu_length_exponent = caps->maximum_ampdu_length;
+ mm->fw_caps.number_sounding_dimensions =
+ caps->number_sounding_dimensions;
+
+ mm81x_dbg(mm, MM81X_DBG_FW, "\tAMPDU Minimum start spacing: %u",
+ caps->ampdu_mss);
+ mm81x_dbg(mm, MM81X_DBG_FW, "\tMorse Minimum Start Spacing offset: %u",
+ caps->mm81x_mmss_offset);
+ mm81x_dbg(mm, MM81X_DBG_FW, "\tBeamformee STS Capability: %u",
+ caps->beamformee_sts_capability);
+ mm81x_dbg(mm, MM81X_DBG_FW, "\tNumber of Sounding Dimensions: %u",
+ caps->number_sounding_dimensions);
+ mm81x_dbg(mm, MM81X_DBG_FW, "\tMaximum AMPDU Length Exponent: %u",
+ caps->maximum_ampdu_length);
+}
+
+static void mm81x_fw_update_validate_skb_checksum(
+ struct mm81x *mm,
+ struct ext_host_tbl_insert_skb_checksum *validate_checksum)
+{
+ mm->hif.validate_skb_checksum =
+ validate_checksum->insert_and_validate_checksum;
+ mm81x_dbg(mm, MM81X_DBG_ANY, "Validate checksum inserted by fw %s",
+ str_enabled_disabled(mm->hif.validate_skb_checksum));
+}
+
+int mm81x_fw_parse_ext_host_tbl(struct mm81x *mm)
+{
+ int ret;
+ u8 *head;
+ u8 *end;
+ struct ext_host_tbl *ext_host_table = NULL;
+
+ ret = mm81x_fw_read_ext_host_table(mm, &ext_host_table);
+ if (ret || !ext_host_table)
+ goto exit;
+
+ /* Parse the TLVs */
+ head = ext_host_table->ext_host_table_data_tlvs;
+ end = ((u8 *)ext_host_table) +
+ le32_to_cpu(ext_host_table->ext_host_tbl_length);
+
+ while (head < end) {
+ struct ext_host_tbl_tlv_hdr *hdr =
+ (struct ext_host_tbl_tlv_hdr *)head;
+
+ switch (le16_to_cpu(hdr->tag)) {
+ case MM81X_FW_HOST_TABLE_TAG_S1G_CAPABILITIES:
+ mm81x_fw_update_capabilities(
+ mm, (struct ext_host_tbl_s1g_caps *)hdr);
+ break;
+
+ case MM81X_FW_HOST_TABLE_TAG_INSERT_SKB_CHECKSUM:
+ mm81x_fw_update_validate_skb_checksum(
+ mm,
+ (struct ext_host_tbl_insert_skb_checksum *)hdr);
+ break;
+
+ case MM81X_FW_HOST_TABLE_TAG_YAPS_TABLE:
+ mm81x_yaps_hw_read_table(
+ mm, &((struct ext_host_tbl_yaps_table *)hdr)
+ ->yaps_table);
+ break;
+ default:
+ break;
+ }
+
+ head += le16_to_cpu(hdr->length);
+ if (!hdr->length)
+ break;
+ }
+
+ kfree(ext_host_table);
+ return ret;
+exit:
+ mm81x_err(mm, "failed to parse ext host table %d", ret);
+ return ret;
+}
+
+static int __mm81x_fw_flash(struct mm81x *mm, const struct firmware *fw,
+ const struct firmware *bcf, bool reset)
+{
+ int ret;
+
+ if (reset || !mm->chip_was_reset) {
+ ret = mm81x_hw_digital_reset(mm);
+ if (ret)
+ return ret;
+ }
+
+ mm81x_hw_pre_firmware_ndr_hook(mm);
+
+ ret = mm81x_fw_invalidate_host_ptr(mm);
+ if (ret)
+ return ret;
+
+ ret = mm81x_fw_load_fw(mm, fw);
+ if (ret)
+ return ret;
+
+ ret = mm81x_fw_load_bcf(mm, bcf, mm->bcf_address);
+ if (ret)
+ return ret;
+
+ mm81x_fw_trigger(mm);
+ mm81x_hw_post_firmware_ndr_hook(mm);
+
+ ret = mm81x_fw_get_host_table_ptr(mm);
+ if (ret)
+ return ret;
+
+ ret = mm81x_fw_verify_magic(mm);
+ if (ret)
+ return ret;
+
+ return mm81x_fw_check_compatibility(mm);
+}
+
+static int mm81x_fw_flash(struct mm81x *mm, const struct firmware *fw,
+ const struct firmware *bcf, bool reset)
+{
+ int ret;
+ int retries = FW_FLASH_ATTEMPT_COUNT;
+
+ while (retries--) {
+ ret = __mm81x_fw_flash(mm, fw, bcf, reset);
+ if (!ret)
+ return 0;
+
+ mm->chip_was_reset = false;
+ }
+
+ return ret;
+}
+
+static uint32_t binary_crc(const struct firmware *fw)
+{
+ return ~crc32_le(~0, (unsigned char const *)fw->data, fw->size) &
+ 0xffffffff;
+}
+
+int mm81x_fw_init(struct mm81x *mm, bool reset)
+{
+ int ret;
+ int n;
+ int board_id;
+ char *fw_path;
+ char bcf_path[MAX_BCF_NAME_LEN];
+ const struct firmware *fw = NULL;
+ const struct firmware *bcf = NULL;
+
+ fw_path = mm81x_core_get_fw_path(mm->chip_id);
+ if (!fw_path)
+ return -ENOMEM;
+
+ board_id = mm81x_hw_otp_get_board_type(mm);
+
+ if (strlen(board_config_file) > 0) {
+ n = snprintf(bcf_path, sizeof(bcf_path), "%s/%s", MM81X_FW_DIR,
+ board_config_file);
+ } else if (mm81x_hw_otp_valid_board_type(board_id)) {
+ mm81x_dbg(mm, MM81X_DBG_FW, "Using board type 0x%04x from OTP",
+ board_id);
+ n = snprintf(bcf_path, sizeof(bcf_path),
+ "%s/bcf_boardtype_%04x.bin", MM81X_FW_DIR,
+ board_id);
+ } else {
+ mm81x_err(mm, "BCF or Serial parameters are not defined");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (n < 0 || n >= sizeof(bcf_path)) {
+ mm81x_err(mm, "Failed to create BCF path");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ret = request_firmware(&fw, fw_path, mm->dev);
+ if (ret) {
+ if (ret == -ENOENT)
+ dev_err(mm->dev, "Firmware %s not found\n", fw_path);
+ goto out;
+ }
+
+ dev_info(mm->dev, "Loaded firmware from %s, size %zu, crc32 0x%08x\n",
+ fw_path, fw->size, binary_crc(fw));
+
+ ret = request_firmware(&bcf, bcf_path, mm->dev);
+ if (ret) {
+ if (ret == -ENOENT)
+ dev_err(mm->dev, "BCF %s not found\n", bcf_path);
+ goto out;
+ }
+
+ dev_info(mm->dev, "Loaded BCF from %s, size %zu, crc32 0x%08x\n",
+ bcf_path, bcf->size, binary_crc(bcf));
+
+ ret = mm81x_fw_flash(mm, fw, bcf, reset);
+ if (ret) {
+ mm81x_err(mm, "failed to flash firmware: %d", ret);
+ goto out;
+ }
+
+ ret = mm81x_fw_get_flags(mm);
+
+out:
+ release_firmware(fw);
+ release_firmware(bcf);
+ kfree(fw_path);
+
+ if (ret)
+ mm81x_err(mm, "failed to init firmware: %d", ret);
+ else
+ mm81x_dbg(mm, MM81X_DBG_FW, "firmware initialised");
+
+ return ret;
+}
--
2.43.0