Linux wireless drivers development
 help / color / mirror / Atom feed
From: Lachlan Hodges <lachlan.hodges@morsemicro.com>
To: johannes@sipsolutions.net,
	Lachlan Hodges <lachlan.hodges@morsemicro.com>,
	Dan Callaghan <dan.callaghan@morsemicro.com>,
	Arien Judge <arien.judge@morsemicro.com>
Cc: ayman.grais@morsemicro.com, linux-wireless@vger.kernel.org,
	linux-kernel@vger.kernel.org
Subject: [PATCH wireless-next v2 07/31] wifi: mm81x: add fw.c
Date: Thu, 30 Apr 2026 14:55:33 +1000	[thread overview]
Message-ID: <20260430045615.334669-8-lachlan.hodges@morsemicro.com> (raw)
In-Reply-To: <20260430045615.334669-1-lachlan.hodges@morsemicro.com>

(Patches split per file for review, will be a single commit alongside
SDIO ids once review is complete. See cover letter for more
information)

Signed-off-by: Lachlan Hodges <lachlan.hodges@morsemicro.com>
---
 drivers/net/wireless/morsemicro/mm81x/fw.c | 744 +++++++++++++++++++++
 1 file changed, 744 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..9ab48a34115e
--- /dev/null
+++ b/drivers/net/wireless/morsemicro/mm81x/fw.c
@@ -0,0 +1,744 @@
+// 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 "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 *mors, 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:
+			mors->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 *mors, uint32_t addr)
+{
+	int status;
+
+	dev_dbg(mors->dev, "Overwriting boot address to 0x%x", addr);
+	mm81x_claim_bus(mors);
+	status = mm81x_reg32_write(mors, MM81X_REG_BOOT_ADDR(mors), addr);
+	mm81x_release_bus(mors);
+	return status;
+}
+
+static int mm81x_fw_load_fw(struct mm81x *mors, 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(mors->dev, ROUND_BYTES_TO_WORD(fw->size),
+				  GFP_KERNEL);
+
+	if (!fw_buf)
+		return -ENOMEM;
+
+	if (mm81x_fw_get_header(fw->data, &ehdr)) {
+		dev_err(mors->dev, "Wrong file format");
+		return -EINVAL;
+	}
+
+	if (mm81x_fw_get_section_header(fw->data, &ehdr, &sh_strtab,
+					ehdr.e_shstrndx)) {
+		dev_err(mors->dev, "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(mors);
+			status = mm81x_dm_write(mors, address, fw_buf,
+						padded_size);
+			mm81x_release_bus(mors);
+			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(mors, fw->data + shdr.sh_offset,
+					    shdr.sh_size);
+	}
+
+	if (ehdr.e_entry)
+		ret = mm81x_fw_set_boot_addr(mors, ehdr.e_entry);
+
+	devm_kfree(mors->dev, fw_buf);
+	return ret;
+}
+
+static int __mm81x_fw_load_bcf(struct mm81x *mors, 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(mors);
+	st = mm81x_dm_write(mors, addr, scratch, rounded);
+	mm81x_release_bus(mors);
+
+	return st ? -EIO : 0;
+}
+
+static int mm81x_fw_load_bcf(struct mm81x *mors, 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(mors->dev, ROUND_BYTES_TO_WORD(bcf->size),
+			       GFP_KERNEL);
+	if (!bcf_buf)
+		return -ENOMEM;
+
+	if (mm81x_fw_get_header(bcf->data, &ehdr)) {
+		dev_err(mors->dev, "Wrong file format");
+		ret = -EINVAL;
+		goto out_free;
+	}
+
+	if (mm81x_fw_get_section_header(bcf->data, &ehdr, &sh_strtab,
+					ehdr.e_shstrndx)) {
+		dev_err(mors->dev, "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);
+		dev_dbg(mors->dev,
+			"Write BCF board_config - addr 0x%x size %zu",
+			bcf_address, cfg_len_rounded);
+
+		ret = __mm81x_fw_load_bcf(mors, 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,
+			    mors->country, 2))
+			continue;
+
+		reg_src = bcf->data + shdr.sh_offset;
+		reg_len = shdr.sh_size;
+		dev_dbg(mors->dev, "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(mors, bcf_address, reg_src, reg_len, bcf_buf,
+				  ROUND_BYTES_TO_WORD(bcf->size));
+
+out_free:
+	devm_kfree(mors->dev, bcf_buf);
+	return ret;
+}
+
+static void mm81x_fw_clear_aon(struct mm81x *mors)
+{
+	int idx;
+	u8 count = MM81X_REG_AON_COUNT(mors);
+	u32 address = MM81X_REG_AON_ADDR(mors);
+
+	for (idx = 0; idx < count; idx++, address += 4) {
+		if (mors->bus_type == MM81X_BUS_TYPE_USB && idx == 0)
+			/* Keep the USB power domain enabled in AON. */
+			mm81x_reg32_write(mors, address,
+					  MM81X_REG_AON_USB_RESET(mors));
+		else
+			/* clear AON */
+			mm81x_reg32_write(mors, address, 0x0);
+	}
+
+	mm81x_hw_toggle_aon_latch(mors);
+}
+
+static void mm81x_fw_trigger(struct mm81x *mors)
+{
+	const unsigned int wait_after_msi_trigger_ms = 1;
+
+	mm81x_claim_bus(mors);
+	/*
+	 * 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(mors);
+
+	if (MM81X_REG_CLK_CTRL(mors))
+		mm81x_reg32_write(mors, MM81X_REG_CLK_CTRL(mors),
+				  MM81X_REG_CLK_CTRL_VALUE(mors));
+
+	mm81x_reg32_write(mors, MM81X_REG_MSI(mors),
+			  MM81X_REG_MSI_HOST_INT(mors));
+	mm81x_release_bus(mors);
+
+	/* Give the chip a chance to boot */
+	mdelay(wait_after_msi_trigger_ms);
+}
+
+static int mm81x_fw_verify_magic(struct mm81x *mors)
+{
+	int ret = 0;
+	int magic = ~MM81X_REG_HOST_MAGIC_VALUE(mors);
+
+	mm81x_claim_bus(mors);
+	mm81x_reg32_read(mors,
+			 mors->host_table_ptr +
+				 offsetof(struct host_table, magic_number),
+			 &magic);
+
+	if (magic != MM81X_REG_HOST_MAGIC_VALUE(mors)) {
+		dev_err(mors->dev, "FW magic mismatch 0x%08x:0x%08x",
+			MM81X_REG_HOST_MAGIC_VALUE(mors), magic);
+		ret = -EIO;
+	}
+
+	mm81x_release_bus(mors);
+	return ret;
+}
+
+static int mm81x_fw_get_flags(struct mm81x *mors)
+{
+	int ret = 0;
+	int fw_flags = 0;
+
+	mm81x_claim_bus(mors);
+	ret = mm81x_reg32_read(mors,
+			       mors->host_table_ptr +
+				       offsetof(struct host_table,
+						firmware_flags),
+			       &fw_flags);
+	mors->firmware_flags = fw_flags;
+	mm81x_release_bus(mors);
+
+	return ret;
+}
+
+static int mm81x_fw_check_compatibility(struct mm81x *mors)
+{
+	int ret = 0;
+	u32 fw_version;
+	u32 major;
+	u32 minor;
+	u32 patch;
+
+	mm81x_claim_bus(mors);
+	ret = mm81x_reg32_read(mors,
+			       mors->host_table_ptr +
+				       offsetof(struct host_table,
+						fw_version_number),
+			       &fw_version);
+	mm81x_release_bus(mors);
+
+	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) {
+		dev_err(mors->dev,
+			"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) {
+		dev_warn(
+			mors->dev,
+			"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 *mors)
+{
+	int ret;
+
+	mors->host_table_ptr = 0;
+	mm81x_claim_bus(mors);
+	ret = mm81x_reg32_write(mors, MM81X_REG_HOST_MANIFEST_PTR(mors), 0);
+	mm81x_release_bus(mors);
+	return ret;
+}
+
+static int mm81x_fw_get_host_table_ptr(struct mm81x *mors)
+{
+	int ret = 0;
+	unsigned long timeout =
+		jiffies + msecs_to_jiffies(MAX_WAIT_FOR_HOST_TABLE_PTR_MS);
+
+	mm81x_claim_bus(mors);
+	while (1) {
+		ret = mm81x_reg32_read(mors, MM81X_REG_HOST_MANIFEST_PTR(mors),
+				       &mors->host_table_ptr);
+
+		if (mors->host_table_ptr)
+			break;
+
+		if (time_after(jiffies, timeout)) {
+			ret = -EIO;
+			break;
+		}
+
+		usleep_range(5000, 10000);
+	}
+
+	mm81x_release_bus(mors);
+	return ret;
+}
+
+static int mm81x_fw_read_ext_host_table(struct mm81x *mors,
+					struct ext_host_tbl **ext_host_table)
+{
+	int ret = 0;
+	u32 host_tbl_ptr = mors->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(mors);
+	ret = mm81x_reg32_read(mors, 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(mors, 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(mors, ext_host_tbl_ptr, (u8 *)host_tbl,
+			    (int)ext_host_tbl_len);
+	if (ret)
+		goto exit;
+
+	mm81x_release_bus(mors);
+	*ext_host_table = host_tbl;
+	return ret;
+
+exit:
+	mm81x_release_bus(mors);
+	kfree(host_tbl);
+	return ret;
+}
+
+static void mm81x_fw_update_capabilities(struct mm81x *mors,
+					 struct ext_host_tbl_s1g_caps *caps)
+{
+	int i;
+
+	for (i = 0; i < FW_CAPABILITIES_FLAGS_WIDTH; i++) {
+		mors->fw_caps.flags[i] = le32_to_cpu(caps->flags[i]);
+		dev_dbg(mors->dev, "Firmware Manifest Flags%d: 0x%x", i,
+			le32_to_cpu(caps->flags[i]));
+	}
+	mors->fw_caps.ampdu_mss = caps->ampdu_mss;
+	mors->fw_caps.mm81x_mmss_offset = caps->mm81x_mmss_offset;
+	mors->fw_caps.beamformee_sts_capability =
+		caps->beamformee_sts_capability;
+	mors->fw_caps.maximum_ampdu_length_exponent =
+		caps->maximum_ampdu_length;
+	mors->fw_caps.number_sounding_dimensions =
+		caps->number_sounding_dimensions;
+
+	dev_dbg(mors->dev, "\tAMPDU Minimum start spacing: %u",
+		caps->ampdu_mss);
+	dev_dbg(mors->dev, "\tMorse Minimum Start Spacing offset: %u",
+		caps->mm81x_mmss_offset);
+	dev_dbg(mors->dev, "\tBeamformee STS Capability: %u",
+		caps->beamformee_sts_capability);
+	dev_dbg(mors->dev, "\tNumber of Sounding Dimensions: %u",
+		caps->number_sounding_dimensions);
+	dev_dbg(mors->dev, "\tMaximum AMPDU Length Exponent: %u",
+		caps->maximum_ampdu_length);
+}
+
+static void mm81x_fw_update_validate_skb_checksum(
+	struct mm81x *mors,
+	struct ext_host_tbl_insert_skb_checksum *validate_checksum)
+{
+	mors->hif.validate_skb_checksum =
+		validate_checksum->insert_and_validate_checksum;
+	dev_dbg(mors->dev, "Validate checksum inserted by fw %s",
+		str_enabled_disabled(mors->hif.validate_skb_checksum));
+}
+
+int mm81x_fw_parse_ext_host_tbl(struct mm81x *mors)
+{
+	int ret;
+	u8 *head;
+	u8 *end;
+	struct ext_host_tbl *ext_host_table = NULL;
+
+	ret = mm81x_fw_read_ext_host_table(mors, &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(
+				mors, (struct ext_host_tbl_s1g_caps *)hdr);
+			break;
+
+		case MM81X_FW_HOST_TABLE_TAG_INSERT_SKB_CHECKSUM:
+			mm81x_fw_update_validate_skb_checksum(
+				mors,
+				(struct ext_host_tbl_insert_skb_checksum *)hdr);
+			break;
+
+		case MM81X_FW_HOST_TABLE_TAG_YAPS_TABLE:
+			mm81x_yaps_hw_read_table(
+				mors, &((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:
+	dev_err(mors->dev, "failed to parse ext host table %d", ret);
+	return ret;
+}
+
+static int __mm81x_fw_flash(struct mm81x *mors, const struct firmware *fw,
+			    const struct firmware *bcf, bool reset)
+{
+	int ret;
+
+	if (reset || !mors->chip_was_reset) {
+		ret = mm81x_hw_digital_reset(mors);
+		if (ret)
+			return ret;
+	}
+
+	mm81x_hw_pre_firmware_ndr_hook(mors);
+
+	ret = mm81x_fw_invalidate_host_ptr(mors);
+	if (ret)
+		return ret;
+
+	ret = mm81x_fw_load_fw(mors, fw);
+	if (ret)
+		return ret;
+
+	ret = mm81x_fw_load_bcf(mors, bcf, mors->bcf_address);
+	if (ret)
+		return ret;
+
+	mm81x_fw_trigger(mors);
+	mm81x_hw_post_firmware_ndr_hook(mors);
+
+	ret = mm81x_fw_get_host_table_ptr(mors);
+	if (ret)
+		return ret;
+
+	ret = mm81x_fw_verify_magic(mors);
+	if (ret)
+		return ret;
+
+	return mm81x_fw_check_compatibility(mors);
+}
+
+static int mm81x_fw_flash(struct mm81x *mors, const struct firmware *fw,
+			  const struct firmware *bcf, bool reset)
+{
+	int ret;
+	int retries = FW_FLASH_ATTEMPT_COUNT;
+
+	while (retries--) {
+		ret = __mm81x_fw_flash(mors, fw, bcf, reset);
+		if (!ret)
+			return 0;
+
+		mors->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 *mors, 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(mors->chip_id);
+	if (!fw_path)
+		return -ENOMEM;
+
+	board_id = mm81x_hw_otp_get_board_type(mors);
+
+	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)) {
+		dev_dbg(mors->dev, "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 {
+		dev_err(mors->dev, "BCF or Serial parameters are not defined");
+		ret = -EINVAL;
+		goto out;
+	}
+
+	if (n < 0 || n >= sizeof(bcf_path)) {
+		dev_err(mors->dev, "Failed to create BCF path");
+		ret = -EINVAL;
+		goto out;
+	}
+
+	ret = request_firmware(&fw, fw_path, mors->dev);
+	if (ret) {
+		if (ret == -ENOENT)
+			dev_err(mors->dev, "Firmware %s not found\n", fw_path);
+		goto out;
+	}
+
+	dev_info(mors->dev, "Loaded firmware from %s, size %zu, crc32 0x%08x\n",
+		 fw_path, fw->size, binary_crc(fw));
+
+	ret = request_firmware(&bcf, bcf_path, mors->dev);
+	if (ret) {
+		if (ret == -ENOENT)
+			dev_err(mors->dev, "BCF %s not found\n", bcf_path);
+		goto out;
+	}
+
+	dev_info(mors->dev, "Loaded BCF from %s, size %zu, crc32 0x%08x\n",
+		 bcf_path, bcf->size, binary_crc(bcf));
+
+	ret = mm81x_fw_flash(mors, fw, bcf, reset);
+	if (ret) {
+		dev_err(mors->dev, "failed to flash firmware: %d", ret);
+		goto out;
+	}
+
+	ret = mm81x_fw_get_flags(mors);
+
+out:
+	release_firmware(fw);
+	release_firmware(bcf);
+	kfree(fw_path);
+
+	if (ret)
+		dev_err(mors->dev, "failed to init firmware: %d", ret);
+	else
+		dev_dbg(mors->dev, "firmware initialised");
+
+	return ret;
+}
-- 
2.43.0


  parent reply	other threads:[~2026-04-30  4:57 UTC|newest]

Thread overview: 36+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-04-30  4:55 [PATCH wireless-next v2 00/31] wifi: mm81x: add mm81x driver Lachlan Hodges
2026-04-30  4:55 ` [PATCH wireless-next v2 01/31] wifi: mm81x: add bus.h Lachlan Hodges
2026-04-30  4:55 ` [PATCH wireless-next v2 02/31] wifi: mm81x: add command.c Lachlan Hodges
2026-04-30  4:55 ` [PATCH wireless-next v2 03/31] wifi: mm81x: add command_defs.h Lachlan Hodges
2026-04-30  4:55 ` [PATCH wireless-next v2 04/31] wifi: mm81x: add command.h Lachlan Hodges
2026-04-30  4:55 ` [PATCH wireless-next v2 05/31] wifi: mm81x: add core.c Lachlan Hodges
2026-05-01  5:45   ` Lachlan Hodges
2026-04-30  4:55 ` [PATCH wireless-next v2 06/31] wifi: mm81x: add core.h Lachlan Hodges
2026-04-30  4:55 ` Lachlan Hodges [this message]
2026-04-30  4:55 ` [PATCH wireless-next v2 08/31] wifi: mm81x: add fw.h Lachlan Hodges
2026-04-30  4:55 ` [PATCH wireless-next v2 09/31] wifi: mm81x: add hif.h Lachlan Hodges
2026-04-30  4:55 ` [PATCH wireless-next v2 10/31] wifi: mm81x: add hw.c Lachlan Hodges
2026-04-30  4:55 ` [PATCH wireless-next v2 11/31] wifi: mm81x: add hw.h Lachlan Hodges
2026-04-30  4:55 ` [PATCH wireless-next v2 12/31] wifi: mm81x: add mac.c Lachlan Hodges
2026-04-30  4:55 ` [PATCH wireless-next v2 13/31] wifi: mm81x: add mac.h Lachlan Hodges
2026-04-30  4:55 ` [PATCH wireless-next v2 14/31] wifi: mm81x: add mmrc.c Lachlan Hodges
2026-04-30  4:55 ` [PATCH wireless-next v2 15/31] wifi: mm81x: add mmrc.h Lachlan Hodges
2026-04-30  4:55 ` [PATCH wireless-next v2 16/31] wifi: mm81x: add ps.c Lachlan Hodges
2026-04-30  4:55 ` [PATCH wireless-next v2 17/31] wifi: mm81x: add ps.h Lachlan Hodges
2026-04-30  4:55 ` [PATCH wireless-next v2 18/31] wifi: mm81x: add rate_code.h Lachlan Hodges
2026-04-30  4:55 ` [PATCH wireless-next v2 19/31] wifi: mm81x: add rc.c Lachlan Hodges
2026-04-30  4:55 ` [PATCH wireless-next v2 20/31] wifi: mm81x: add rc.h Lachlan Hodges
2026-04-30  4:55 ` [PATCH wireless-next v2 21/31] mmc: sdio: add Morse Micro vendor ids Lachlan Hodges
2026-05-11 14:54   ` Ulf Hansson
2026-04-30  4:55 ` [PATCH wireless-next v2 22/31] wifi: mm81x: add sdio.c Lachlan Hodges
2026-04-30  4:55 ` [PATCH wireless-next v2 23/31] wifi: mm81x: add skbq.c Lachlan Hodges
2026-04-30  4:55 ` [PATCH wireless-next v2 24/31] wifi: mm81x: add skbq.h Lachlan Hodges
2026-04-30  4:55 ` [PATCH wireless-next v2 25/31] wifi: mm81x: add usb.c Lachlan Hodges
2026-04-30  4:55 ` [PATCH wireless-next v2 26/31] wifi: mm81x: add yaps.c Lachlan Hodges
2026-04-30  4:55 ` [PATCH wireless-next v2 27/31] wifi: mm81x: add yaps.h Lachlan Hodges
2026-04-30  4:55 ` [PATCH wireless-next v2 28/31] wifi: mm81x: add yaps_hw.c Lachlan Hodges
2026-04-30  4:55 ` [PATCH wireless-next v2 29/31] wifi: mm81x: add yaps_hw.h Lachlan Hodges
2026-04-30  4:55 ` [PATCH wireless-next v2 30/31] wifi: mm81x: add Kconfig and Makefile Lachlan Hodges
2026-04-30  4:55 ` [PATCH wireless-next v2 31/31] wifi: mm81x: add MAINTAINERS entry Lachlan Hodges
2026-04-30  5:43 ` [PATCH wireless-next v2 00/31] wifi: mm81x: add mm81x driver Lachlan Hodges
2026-04-30  6:09   ` Johannes Berg

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260430045615.334669-8-lachlan.hodges@morsemicro.com \
    --to=lachlan.hodges@morsemicro.com \
    --cc=arien.judge@morsemicro.com \
    --cc=ayman.grais@morsemicro.com \
    --cc=dan.callaghan@morsemicro.com \
    --cc=johannes@sipsolutions.net \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-wireless@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox