xref: /linux/drivers/net/wireless/morsemicro/mm81x/fw.c (revision 91ec2035134982b98fab0609a9fd8480e8217dc1)
1*b1906ceaSLachlan Hodges // SPDX-License-Identifier: GPL-2.0-only
2*b1906ceaSLachlan Hodges /*
3*b1906ceaSLachlan Hodges  * Copyright (c) 2017-2026 Morse Micro
4*b1906ceaSLachlan Hodges  */
5*b1906ceaSLachlan Hodges #include <linux/kernel.h>
6*b1906ceaSLachlan Hodges #include <linux/firmware.h>
7*b1906ceaSLachlan Hodges #include <linux/slab.h>
8*b1906ceaSLachlan Hodges #include <linux/delay.h>
9*b1906ceaSLachlan Hodges #include <linux/iopoll.h>
10*b1906ceaSLachlan Hodges #include <linux/string_choices.h>
11*b1906ceaSLachlan Hodges #include <net/mac80211.h>
12*b1906ceaSLachlan Hodges #include <linux/elf.h>
13*b1906ceaSLachlan Hodges #include <linux/crc32.h>
14*b1906ceaSLachlan Hodges #include "fw.h"
15*b1906ceaSLachlan Hodges #include "mac.h"
16*b1906ceaSLachlan Hodges #include "bus.h"
17*b1906ceaSLachlan Hodges 
18*b1906ceaSLachlan Hodges /*
19*b1906ceaSLachlan Hodges  * Maximum wait time (microseconds) for firmware to boot (for host table
20*b1906ceaSLachlan Hodges  * pointer to be available)
21*b1906ceaSLachlan Hodges  */
22*b1906ceaSLachlan Hodges #define HOST_TABLE_PTR_POLL_TIMEOUT_US 1200000
23*b1906ceaSLachlan Hodges #define HOST_TABLE_PTR_POLL_PERIOD_US 10000
24*b1906ceaSLachlan Hodges 
25*b1906ceaSLachlan Hodges /* Number of times to attempt flashing FW */
26*b1906ceaSLachlan Hodges #define FW_FLASH_ATTEMPT_COUNT 3
27*b1906ceaSLachlan Hodges 
mm81x_fw_get_header(const u8 * data,Elf32_Ehdr * ehdr)28*b1906ceaSLachlan Hodges static int mm81x_fw_get_header(const u8 *data, Elf32_Ehdr *ehdr)
29*b1906ceaSLachlan Hodges {
30*b1906ceaSLachlan Hodges 	const struct mm81x_elf32_ehdr *p =
31*b1906ceaSLachlan Hodges 		(const struct mm81x_elf32_ehdr *)data;
32*b1906ceaSLachlan Hodges 
33*b1906ceaSLachlan Hodges 	/* Magic check */
34*b1906ceaSLachlan Hodges 	if (p->e_ident[EI_MAG0] != ELFMAG0 || p->e_ident[EI_MAG1] != ELFMAG1 ||
35*b1906ceaSLachlan Hodges 	    p->e_ident[EI_MAG2] != ELFMAG2 || p->e_ident[EI_MAG3] != ELFMAG3)
36*b1906ceaSLachlan Hodges 		return -EINVAL;
37*b1906ceaSLachlan Hodges 
38*b1906ceaSLachlan Hodges 	/* elf32 and little endian */
39*b1906ceaSLachlan Hodges 	if (p->e_ident[EI_DATA] != ELFDATA2LSB ||
40*b1906ceaSLachlan Hodges 	    p->e_ident[EI_CLASS] != ELFCLASS32)
41*b1906ceaSLachlan Hodges 		return -EINVAL;
42*b1906ceaSLachlan Hodges 
43*b1906ceaSLachlan Hodges 	ehdr->e_phoff = le32_to_cpu(p->e_phoff);
44*b1906ceaSLachlan Hodges 	ehdr->e_phentsize = le16_to_cpu(p->e_phentsize);
45*b1906ceaSLachlan Hodges 	ehdr->e_phnum = le16_to_cpu(p->e_phnum);
46*b1906ceaSLachlan Hodges 	ehdr->e_shoff = le32_to_cpu(p->e_shoff);
47*b1906ceaSLachlan Hodges 	ehdr->e_shentsize = le16_to_cpu(p->e_shentsize);
48*b1906ceaSLachlan Hodges 	ehdr->e_shnum = le16_to_cpu(p->e_shnum);
49*b1906ceaSLachlan Hodges 	ehdr->e_shstrndx = le16_to_cpu(p->e_shstrndx);
50*b1906ceaSLachlan Hodges 	ehdr->e_entry = le32_to_cpu(p->e_entry);
51*b1906ceaSLachlan Hodges 
52*b1906ceaSLachlan Hodges 	return 0;
53*b1906ceaSLachlan Hodges }
54*b1906ceaSLachlan Hodges 
mm81x_fw_parse_info(struct mm81x * mors,const u8 * data,int length)55*b1906ceaSLachlan Hodges static void mm81x_fw_parse_info(struct mm81x *mors, const u8 *data, int length)
56*b1906ceaSLachlan Hodges {
57*b1906ceaSLachlan Hodges 	const struct mm81x_fw_info_tlv *tlv =
58*b1906ceaSLachlan Hodges 		(const struct mm81x_fw_info_tlv *)data;
59*b1906ceaSLachlan Hodges 
60*b1906ceaSLachlan Hodges 	while ((u8 *)tlv < (data + length)) {
61*b1906ceaSLachlan Hodges 		switch (le16_to_cpu(tlv->type)) {
62*b1906ceaSLachlan Hodges 		case MM81X_FW_INFO_TLV_BCF_ADDR:
63*b1906ceaSLachlan Hodges 			mors->bcf_address = get_unaligned_le32(tlv->val);
64*b1906ceaSLachlan Hodges 			break;
65*b1906ceaSLachlan Hodges 		default:
66*b1906ceaSLachlan Hodges 			break;
67*b1906ceaSLachlan Hodges 		}
68*b1906ceaSLachlan Hodges 		tlv = (const struct mm81x_fw_info_tlv *)((u8 *)tlv +
69*b1906ceaSLachlan Hodges 							 le16_to_cpu(
70*b1906ceaSLachlan Hodges 								 tlv->length) +
71*b1906ceaSLachlan Hodges 							 sizeof(*tlv));
72*b1906ceaSLachlan Hodges 	}
73*b1906ceaSLachlan Hodges }
74*b1906ceaSLachlan Hodges 
mm81x_fw_get_section_header(const u8 * data,Elf32_Ehdr * ehdr,Elf32_Shdr * shdr,int i)75*b1906ceaSLachlan Hodges static int mm81x_fw_get_section_header(const u8 *data, Elf32_Ehdr *ehdr,
76*b1906ceaSLachlan Hodges 				       Elf32_Shdr *shdr, int i)
77*b1906ceaSLachlan Hodges {
78*b1906ceaSLachlan Hodges 	const struct mm81x_elf32_shdr *p =
79*b1906ceaSLachlan Hodges 		(void *)(data + ehdr->e_shoff + (i * ehdr->e_shentsize));
80*b1906ceaSLachlan Hodges 
81*b1906ceaSLachlan Hodges 	shdr->sh_name = le32_to_cpu(p->sh_name);
82*b1906ceaSLachlan Hodges 	shdr->sh_type = le32_to_cpu(p->sh_type);
83*b1906ceaSLachlan Hodges 	shdr->sh_offset = le32_to_cpu(p->sh_offset);
84*b1906ceaSLachlan Hodges 	shdr->sh_addr = le32_to_cpu(p->sh_addr);
85*b1906ceaSLachlan Hodges 	shdr->sh_size = le32_to_cpu(p->sh_size);
86*b1906ceaSLachlan Hodges 	shdr->sh_flags = le32_to_cpu(p->sh_flags);
87*b1906ceaSLachlan Hodges 
88*b1906ceaSLachlan Hodges 	return 0;
89*b1906ceaSLachlan Hodges }
90*b1906ceaSLachlan Hodges 
mm81x_fw_set_boot_addr(struct mm81x * mors,uint32_t addr)91*b1906ceaSLachlan Hodges static int mm81x_fw_set_boot_addr(struct mm81x *mors, uint32_t addr)
92*b1906ceaSLachlan Hodges {
93*b1906ceaSLachlan Hodges 	int status;
94*b1906ceaSLachlan Hodges 
95*b1906ceaSLachlan Hodges 	dev_dbg(mors->dev, "Overwriting boot address to 0x%x", addr);
96*b1906ceaSLachlan Hodges 	mm81x_claim_bus(mors);
97*b1906ceaSLachlan Hodges 	status = mm81x_reg32_write(mors, MM81X_REG_BOOT_ADDR(mors), addr);
98*b1906ceaSLachlan Hodges 	mm81x_release_bus(mors);
99*b1906ceaSLachlan Hodges 	return status;
100*b1906ceaSLachlan Hodges }
101*b1906ceaSLachlan Hodges 
mm81x_fw_load_fw(struct mm81x * mors,const struct firmware * fw)102*b1906ceaSLachlan Hodges static int mm81x_fw_load_fw(struct mm81x *mors, const struct firmware *fw)
103*b1906ceaSLachlan Hodges {
104*b1906ceaSLachlan Hodges 	int i;
105*b1906ceaSLachlan Hodges 	int ret = 0;
106*b1906ceaSLachlan Hodges 	Elf32_Ehdr ehdr;
107*b1906ceaSLachlan Hodges 	Elf32_Phdr phdr;
108*b1906ceaSLachlan Hodges 	Elf32_Shdr shdr;
109*b1906ceaSLachlan Hodges 	Elf32_Shdr sh_strtab;
110*b1906ceaSLachlan Hodges 	const char *sh_strs;
111*b1906ceaSLachlan Hodges 
112*b1906ceaSLachlan Hodges 	u8 *fw_buf = devm_kmalloc(mors->dev, ROUND_BYTES_TO_WORD(fw->size),
113*b1906ceaSLachlan Hodges 				  GFP_KERNEL);
114*b1906ceaSLachlan Hodges 
115*b1906ceaSLachlan Hodges 	if (!fw_buf)
116*b1906ceaSLachlan Hodges 		return -ENOMEM;
117*b1906ceaSLachlan Hodges 
118*b1906ceaSLachlan Hodges 	if (mm81x_fw_get_header(fw->data, &ehdr)) {
119*b1906ceaSLachlan Hodges 		dev_err(mors->dev, "Wrong file format");
120*b1906ceaSLachlan Hodges 		return -EINVAL;
121*b1906ceaSLachlan Hodges 	}
122*b1906ceaSLachlan Hodges 
123*b1906ceaSLachlan Hodges 	if (mm81x_fw_get_section_header(fw->data, &ehdr, &sh_strtab,
124*b1906ceaSLachlan Hodges 					ehdr.e_shstrndx)) {
125*b1906ceaSLachlan Hodges 		dev_err(mors->dev, "Invalid firmware. Missing string table");
126*b1906ceaSLachlan Hodges 		return -ENOENT;
127*b1906ceaSLachlan Hodges 	}
128*b1906ceaSLachlan Hodges 
129*b1906ceaSLachlan Hodges 	sh_strs = (const char *)fw->data + sh_strtab.sh_offset;
130*b1906ceaSLachlan Hodges 
131*b1906ceaSLachlan Hodges 	for (i = 0; i < ehdr.e_phnum; i++) {
132*b1906ceaSLachlan Hodges 		int status;
133*b1906ceaSLachlan Hodges 		int address;
134*b1906ceaSLachlan Hodges 		const struct mm81x_elf32_phdr *p =
135*b1906ceaSLachlan Hodges 			(void *)(fw->data + ehdr.e_phoff +
136*b1906ceaSLachlan Hodges 				 i * ehdr.e_phentsize);
137*b1906ceaSLachlan Hodges 
138*b1906ceaSLachlan Hodges 		phdr.p_type = le32_to_cpu(p->p_type);
139*b1906ceaSLachlan Hodges 		phdr.p_offset = le32_to_cpu(p->p_offset);
140*b1906ceaSLachlan Hodges 		phdr.p_paddr = le32_to_cpu(p->p_paddr);
141*b1906ceaSLachlan Hodges 		phdr.p_filesz = le32_to_cpu(p->p_filesz);
142*b1906ceaSLachlan Hodges 		phdr.p_memsz = le32_to_cpu(p->p_memsz);
143*b1906ceaSLachlan Hodges 
144*b1906ceaSLachlan Hodges 		address = phdr.p_paddr;
145*b1906ceaSLachlan Hodges 
146*b1906ceaSLachlan Hodges 		if (phdr.p_type != PT_LOAD || !phdr.p_memsz)
147*b1906ceaSLachlan Hodges 			continue;
148*b1906ceaSLachlan Hodges 
149*b1906ceaSLachlan Hodges 		if (phdr.p_filesz && phdr.p_offset &&
150*b1906ceaSLachlan Hodges 		    (phdr.p_offset + phdr.p_filesz) < fw->size) {
151*b1906ceaSLachlan Hodges 			u32 padded_size = ROUND_BYTES_TO_WORD(phdr.p_filesz);
152*b1906ceaSLachlan Hodges 
153*b1906ceaSLachlan Hodges 			memcpy(fw_buf, fw->data + phdr.p_offset, padded_size);
154*b1906ceaSLachlan Hodges 			/* Set padding to 0xff */
155*b1906ceaSLachlan Hodges 			memset(fw_buf + phdr.p_filesz, 0xff,
156*b1906ceaSLachlan Hodges 			       padded_size - phdr.p_filesz);
157*b1906ceaSLachlan Hodges 			mm81x_claim_bus(mors);
158*b1906ceaSLachlan Hodges 			status = mm81x_dm_write(mors, address, fw_buf,
159*b1906ceaSLachlan Hodges 						padded_size);
160*b1906ceaSLachlan Hodges 			mm81x_release_bus(mors);
161*b1906ceaSLachlan Hodges 			if (status) {
162*b1906ceaSLachlan Hodges 				ret = -EIO;
163*b1906ceaSLachlan Hodges 				break;
164*b1906ceaSLachlan Hodges 			}
165*b1906ceaSLachlan Hodges 		}
166*b1906ceaSLachlan Hodges 	}
167*b1906ceaSLachlan Hodges 
168*b1906ceaSLachlan Hodges 	for (i = 0; i < ehdr.e_shnum; i++) {
169*b1906ceaSLachlan Hodges 		if (mm81x_fw_get_section_header(fw->data, &ehdr, &shdr, i))
170*b1906ceaSLachlan Hodges 			continue;
171*b1906ceaSLachlan Hodges 
172*b1906ceaSLachlan Hodges 		/* This is the firmware info. Parse it */
173*b1906ceaSLachlan Hodges 		if (!strncmp(sh_strs + shdr.sh_name, ".fw_info",
174*b1906ceaSLachlan Hodges 			     sizeof(".fw_info")))
175*b1906ceaSLachlan Hodges 			mm81x_fw_parse_info(mors, fw->data + shdr.sh_offset,
176*b1906ceaSLachlan Hodges 					    shdr.sh_size);
177*b1906ceaSLachlan Hodges 	}
178*b1906ceaSLachlan Hodges 
179*b1906ceaSLachlan Hodges 	if (ehdr.e_entry)
180*b1906ceaSLachlan Hodges 		ret = mm81x_fw_set_boot_addr(mors, ehdr.e_entry);
181*b1906ceaSLachlan Hodges 
182*b1906ceaSLachlan Hodges 	devm_kfree(mors->dev, fw_buf);
183*b1906ceaSLachlan Hodges 	return ret;
184*b1906ceaSLachlan Hodges }
185*b1906ceaSLachlan Hodges 
__mm81x_fw_load_bcf(struct mm81x * mors,unsigned int addr,const void * src,size_t src_len,u8 * scratch,size_t scratch_cap)186*b1906ceaSLachlan Hodges static int __mm81x_fw_load_bcf(struct mm81x *mors, unsigned int addr,
187*b1906ceaSLachlan Hodges 			       const void *src, size_t src_len, u8 *scratch,
188*b1906ceaSLachlan Hodges 			       size_t scratch_cap)
189*b1906ceaSLachlan Hodges {
190*b1906ceaSLachlan Hodges 	size_t rounded = ROUND_BYTES_TO_WORD(src_len);
191*b1906ceaSLachlan Hodges 	int st;
192*b1906ceaSLachlan Hodges 
193*b1906ceaSLachlan Hodges 	if (rounded > scratch_cap)
194*b1906ceaSLachlan Hodges 		return -EINVAL;
195*b1906ceaSLachlan Hodges 	if (rounded > BCF_DATABASE_SIZE)
196*b1906ceaSLachlan Hodges 		return -EFBIG;
197*b1906ceaSLachlan Hodges 
198*b1906ceaSLachlan Hodges 	memcpy(scratch, src, src_len);
199*b1906ceaSLachlan Hodges 	if (rounded > src_len)
200*b1906ceaSLachlan Hodges 		memset(scratch + src_len, 0xff, rounded - src_len);
201*b1906ceaSLachlan Hodges 
202*b1906ceaSLachlan Hodges 	mm81x_claim_bus(mors);
203*b1906ceaSLachlan Hodges 	st = mm81x_dm_write(mors, addr, scratch, rounded);
204*b1906ceaSLachlan Hodges 	mm81x_release_bus(mors);
205*b1906ceaSLachlan Hodges 
206*b1906ceaSLachlan Hodges 	return st ? -EIO : 0;
207*b1906ceaSLachlan Hodges }
208*b1906ceaSLachlan Hodges 
mm81x_fw_load_bcf(struct mm81x * mors,const struct firmware * bcf,unsigned int bcf_address)209*b1906ceaSLachlan Hodges static int mm81x_fw_load_bcf(struct mm81x *mors, const struct firmware *bcf,
210*b1906ceaSLachlan Hodges 			     unsigned int bcf_address)
211*b1906ceaSLachlan Hodges {
212*b1906ceaSLachlan Hodges 	int i, ret = 0;
213*b1906ceaSLachlan Hodges 	size_t reg_prefix_len, cfg_len_rounded = 0, reg_len_rounded;
214*b1906ceaSLachlan Hodges 	Elf32_Ehdr ehdr;
215*b1906ceaSLachlan Hodges 	Elf32_Shdr shdr, sh_strtab;
216*b1906ceaSLachlan Hodges 	const char *sh_strs, *reg_prefix = ".regdom_", *reg_src;
217*b1906ceaSLachlan Hodges 	size_t reg_len;
218*b1906ceaSLachlan Hodges 	u8 *bcf_buf;
219*b1906ceaSLachlan Hodges 
220*b1906ceaSLachlan Hodges 	bcf_buf = devm_kmalloc(mors->dev, ROUND_BYTES_TO_WORD(bcf->size),
221*b1906ceaSLachlan Hodges 			       GFP_KERNEL);
222*b1906ceaSLachlan Hodges 	if (!bcf_buf)
223*b1906ceaSLachlan Hodges 		return -ENOMEM;
224*b1906ceaSLachlan Hodges 
225*b1906ceaSLachlan Hodges 	if (mm81x_fw_get_header(bcf->data, &ehdr)) {
226*b1906ceaSLachlan Hodges 		dev_err(mors->dev, "Wrong file format");
227*b1906ceaSLachlan Hodges 		ret = -EINVAL;
228*b1906ceaSLachlan Hodges 		goto out_free;
229*b1906ceaSLachlan Hodges 	}
230*b1906ceaSLachlan Hodges 
231*b1906ceaSLachlan Hodges 	if (mm81x_fw_get_section_header(bcf->data, &ehdr, &sh_strtab,
232*b1906ceaSLachlan Hodges 					ehdr.e_shstrndx)) {
233*b1906ceaSLachlan Hodges 		dev_err(mors->dev, "Invalid BCF - missing string table");
234*b1906ceaSLachlan Hodges 		ret = -ENOENT;
235*b1906ceaSLachlan Hodges 		goto out_free;
236*b1906ceaSLachlan Hodges 	}
237*b1906ceaSLachlan Hodges 
238*b1906ceaSLachlan Hodges 	sh_strs = (const char *)bcf->data + sh_strtab.sh_offset;
239*b1906ceaSLachlan Hodges 	reg_prefix_len = strlen(reg_prefix);
240*b1906ceaSLachlan Hodges 
241*b1906ceaSLachlan Hodges 	for (i = 0; i < ehdr.e_shnum; i++) {
242*b1906ceaSLachlan Hodges 		if (mm81x_fw_get_section_header(bcf->data, &ehdr, &shdr, i))
243*b1906ceaSLachlan Hodges 			continue;
244*b1906ceaSLachlan Hodges 		if (strcmp(sh_strs + shdr.sh_name, ".board_config"))
245*b1906ceaSLachlan Hodges 			continue;
246*b1906ceaSLachlan Hodges 
247*b1906ceaSLachlan Hodges 		cfg_len_rounded = ROUND_BYTES_TO_WORD(shdr.sh_size);
248*b1906ceaSLachlan Hodges 		dev_dbg(mors->dev,
249*b1906ceaSLachlan Hodges 			"Write BCF board_config - addr 0x%x size %zu",
250*b1906ceaSLachlan Hodges 			bcf_address, cfg_len_rounded);
251*b1906ceaSLachlan Hodges 
252*b1906ceaSLachlan Hodges 		ret = __mm81x_fw_load_bcf(mors, bcf_address,
253*b1906ceaSLachlan Hodges 					  bcf->data + shdr.sh_offset,
254*b1906ceaSLachlan Hodges 					  shdr.sh_size, bcf_buf,
255*b1906ceaSLachlan Hodges 					  ROUND_BYTES_TO_WORD(bcf->size));
256*b1906ceaSLachlan Hodges 		if (ret)
257*b1906ceaSLachlan Hodges 			goto out_free;
258*b1906ceaSLachlan Hodges 
259*b1906ceaSLachlan Hodges 		bcf_address += cfg_len_rounded;
260*b1906ceaSLachlan Hodges 		break;
261*b1906ceaSLachlan Hodges 	}
262*b1906ceaSLachlan Hodges 
263*b1906ceaSLachlan Hodges 	ret = -EINVAL;
264*b1906ceaSLachlan Hodges 	for (; i < ehdr.e_shnum; i++) {
265*b1906ceaSLachlan Hodges 		if (mm81x_fw_get_section_header(bcf->data, &ehdr, &shdr, i))
266*b1906ceaSLachlan Hodges 			continue;
267*b1906ceaSLachlan Hodges 		if (strncmp(sh_strs + shdr.sh_name, reg_prefix, reg_prefix_len))
268*b1906ceaSLachlan Hodges 			continue;
269*b1906ceaSLachlan Hodges 		if (strncmp(sh_strs + shdr.sh_name + reg_prefix_len,
270*b1906ceaSLachlan Hodges 			    mors->country, 2))
271*b1906ceaSLachlan Hodges 			continue;
272*b1906ceaSLachlan Hodges 
273*b1906ceaSLachlan Hodges 		reg_src = bcf->data + shdr.sh_offset;
274*b1906ceaSLachlan Hodges 		reg_len = shdr.sh_size;
275*b1906ceaSLachlan Hodges 		dev_dbg(mors->dev, "Write BCF %s - addr 0x%x size %zu",
276*b1906ceaSLachlan Hodges 			sh_strs + shdr.sh_name, bcf_address,
277*b1906ceaSLachlan Hodges 			ROUND_BYTES_TO_WORD(reg_len));
278*b1906ceaSLachlan Hodges 		ret = 0;
279*b1906ceaSLachlan Hodges 		break;
280*b1906ceaSLachlan Hodges 	}
281*b1906ceaSLachlan Hodges 
282*b1906ceaSLachlan Hodges 	if (ret)
283*b1906ceaSLachlan Hodges 		goto out_free;
284*b1906ceaSLachlan Hodges 
285*b1906ceaSLachlan Hodges 	reg_len_rounded = ROUND_BYTES_TO_WORD(reg_len);
286*b1906ceaSLachlan Hodges 	if ((cfg_len_rounded + reg_len_rounded) > BCF_DATABASE_SIZE) {
287*b1906ceaSLachlan Hodges 		ret = -EFBIG;
288*b1906ceaSLachlan Hodges 		goto out_free;
289*b1906ceaSLachlan Hodges 	}
290*b1906ceaSLachlan Hodges 
291*b1906ceaSLachlan Hodges 	ret = __mm81x_fw_load_bcf(mors, bcf_address, reg_src, reg_len, bcf_buf,
292*b1906ceaSLachlan Hodges 				  ROUND_BYTES_TO_WORD(bcf->size));
293*b1906ceaSLachlan Hodges 
294*b1906ceaSLachlan Hodges out_free:
295*b1906ceaSLachlan Hodges 	devm_kfree(mors->dev, bcf_buf);
296*b1906ceaSLachlan Hodges 	return ret;
297*b1906ceaSLachlan Hodges }
298*b1906ceaSLachlan Hodges 
mm81x_fw_clear_aon(struct mm81x * mors)299*b1906ceaSLachlan Hodges static void mm81x_fw_clear_aon(struct mm81x *mors)
300*b1906ceaSLachlan Hodges {
301*b1906ceaSLachlan Hodges 	int idx;
302*b1906ceaSLachlan Hodges 	u8 count = MM81X_REG_AON_COUNT(mors);
303*b1906ceaSLachlan Hodges 	u32 address = MM81X_REG_AON_ADDR(mors);
304*b1906ceaSLachlan Hodges 
305*b1906ceaSLachlan Hodges 	for (idx = 0; idx < count; idx++, address += 4) {
306*b1906ceaSLachlan Hodges 		if (mors->bus_type == MM81X_BUS_TYPE_USB && idx == 0)
307*b1906ceaSLachlan Hodges 			/* Keep the USB power domain enabled in AON. */
308*b1906ceaSLachlan Hodges 			mm81x_reg32_write(mors, address,
309*b1906ceaSLachlan Hodges 					  MM81X_REG_AON_USB_RESET(mors));
310*b1906ceaSLachlan Hodges 		else
311*b1906ceaSLachlan Hodges 			/* clear AON */
312*b1906ceaSLachlan Hodges 			mm81x_reg32_write(mors, address, 0x0);
313*b1906ceaSLachlan Hodges 	}
314*b1906ceaSLachlan Hodges 
315*b1906ceaSLachlan Hodges 	mm81x_hw_toggle_aon_latch(mors);
316*b1906ceaSLachlan Hodges }
317*b1906ceaSLachlan Hodges 
mm81x_fw_trigger(struct mm81x * mors)318*b1906ceaSLachlan Hodges static void mm81x_fw_trigger(struct mm81x *mors)
319*b1906ceaSLachlan Hodges {
320*b1906ceaSLachlan Hodges 	const unsigned int wait_after_msi_trigger_ms = 1;
321*b1906ceaSLachlan Hodges 
322*b1906ceaSLachlan Hodges 	mm81x_claim_bus(mors);
323*b1906ceaSLachlan Hodges 	/*
324*b1906ceaSLachlan Hodges 	 * If not coming from a full reset, some AON flags may be latched.
325*b1906ceaSLachlan Hodges 	 * Make sure to clear any hanging AON bits (can affect booting).
326*b1906ceaSLachlan Hodges 	 */
327*b1906ceaSLachlan Hodges 	mm81x_fw_clear_aon(mors);
328*b1906ceaSLachlan Hodges 
329*b1906ceaSLachlan Hodges 	if (MM81X_REG_CLK_CTRL(mors))
330*b1906ceaSLachlan Hodges 		mm81x_reg32_write(mors, MM81X_REG_CLK_CTRL(mors),
331*b1906ceaSLachlan Hodges 				  MM81X_REG_CLK_CTRL_VALUE(mors));
332*b1906ceaSLachlan Hodges 
333*b1906ceaSLachlan Hodges 	mm81x_reg32_write(mors, MM81X_REG_MSI(mors),
334*b1906ceaSLachlan Hodges 			  MM81X_REG_MSI_HOST_INT(mors));
335*b1906ceaSLachlan Hodges 	mm81x_release_bus(mors);
336*b1906ceaSLachlan Hodges 
337*b1906ceaSLachlan Hodges 	/* Give the chip a chance to boot */
338*b1906ceaSLachlan Hodges 	mdelay(wait_after_msi_trigger_ms);
339*b1906ceaSLachlan Hodges }
340*b1906ceaSLachlan Hodges 
mm81x_fw_verify_magic(struct mm81x * mors)341*b1906ceaSLachlan Hodges static int mm81x_fw_verify_magic(struct mm81x *mors)
342*b1906ceaSLachlan Hodges {
343*b1906ceaSLachlan Hodges 	int ret = 0;
344*b1906ceaSLachlan Hodges 	int magic = ~MM81X_REG_HOST_MAGIC_VALUE(mors);
345*b1906ceaSLachlan Hodges 
346*b1906ceaSLachlan Hodges 	mm81x_claim_bus(mors);
347*b1906ceaSLachlan Hodges 	mm81x_reg32_read(mors,
348*b1906ceaSLachlan Hodges 			 mors->host_table_ptr +
349*b1906ceaSLachlan Hodges 				 offsetof(struct host_table, magic_number),
350*b1906ceaSLachlan Hodges 			 &magic);
351*b1906ceaSLachlan Hodges 
352*b1906ceaSLachlan Hodges 	if (magic != MM81X_REG_HOST_MAGIC_VALUE(mors)) {
353*b1906ceaSLachlan Hodges 		dev_err(mors->dev, "FW magic mismatch 0x%08x:0x%08x",
354*b1906ceaSLachlan Hodges 			MM81X_REG_HOST_MAGIC_VALUE(mors), magic);
355*b1906ceaSLachlan Hodges 		ret = -EIO;
356*b1906ceaSLachlan Hodges 	}
357*b1906ceaSLachlan Hodges 
358*b1906ceaSLachlan Hodges 	mm81x_release_bus(mors);
359*b1906ceaSLachlan Hodges 	return ret;
360*b1906ceaSLachlan Hodges }
361*b1906ceaSLachlan Hodges 
mm81x_fw_get_flags(struct mm81x * mors)362*b1906ceaSLachlan Hodges static int mm81x_fw_get_flags(struct mm81x *mors)
363*b1906ceaSLachlan Hodges {
364*b1906ceaSLachlan Hodges 	int ret = 0;
365*b1906ceaSLachlan Hodges 	int fw_flags = 0;
366*b1906ceaSLachlan Hodges 
367*b1906ceaSLachlan Hodges 	mm81x_claim_bus(mors);
368*b1906ceaSLachlan Hodges 	ret = mm81x_reg32_read(mors,
369*b1906ceaSLachlan Hodges 			       mors->host_table_ptr +
370*b1906ceaSLachlan Hodges 				       offsetof(struct host_table, fw_flags),
371*b1906ceaSLachlan Hodges 			       &fw_flags);
372*b1906ceaSLachlan Hodges 	mors->fw_flags = fw_flags;
373*b1906ceaSLachlan Hodges 	mm81x_release_bus(mors);
374*b1906ceaSLachlan Hodges 
375*b1906ceaSLachlan Hodges 	return ret;
376*b1906ceaSLachlan Hodges }
377*b1906ceaSLachlan Hodges 
mm81x_fw_check_compatibility(struct mm81x * mors)378*b1906ceaSLachlan Hodges static int mm81x_fw_check_compatibility(struct mm81x *mors)
379*b1906ceaSLachlan Hodges {
380*b1906ceaSLachlan Hodges 	int ret = 0;
381*b1906ceaSLachlan Hodges 	u32 fw_version;
382*b1906ceaSLachlan Hodges 	u32 major;
383*b1906ceaSLachlan Hodges 	u32 minor;
384*b1906ceaSLachlan Hodges 	u32 patch;
385*b1906ceaSLachlan Hodges 
386*b1906ceaSLachlan Hodges 	mm81x_claim_bus(mors);
387*b1906ceaSLachlan Hodges 	ret = mm81x_reg32_read(mors,
388*b1906ceaSLachlan Hodges 			       mors->host_table_ptr +
389*b1906ceaSLachlan Hodges 				       offsetof(struct host_table,
390*b1906ceaSLachlan Hodges 						fw_version_number),
391*b1906ceaSLachlan Hodges 			       &fw_version);
392*b1906ceaSLachlan Hodges 	mm81x_release_bus(mors);
393*b1906ceaSLachlan Hodges 
394*b1906ceaSLachlan Hodges 	major = MM81X_SEMVER_GET_MAJOR(fw_version);
395*b1906ceaSLachlan Hodges 	minor = MM81X_SEMVER_GET_MINOR(fw_version);
396*b1906ceaSLachlan Hodges 	patch = MM81X_SEMVER_GET_PATCH(fw_version);
397*b1906ceaSLachlan Hodges 
398*b1906ceaSLachlan Hodges 	/* Firmware on device must match the firmware file we requested */
399*b1906ceaSLachlan Hodges 	if (ret == 0 && major != mors->fw_major) {
400*b1906ceaSLachlan Hodges 		dev_err(mors->dev,
401*b1906ceaSLachlan Hodges 			"Incompatible FW version: (Requested) v%u, (Chip) %d.%d.%d\n",
402*b1906ceaSLachlan Hodges 			mors->fw_major, major, minor, patch);
403*b1906ceaSLachlan Hodges 		ret = -EPERM;
404*b1906ceaSLachlan Hodges 	} else if (ret == 0 && major != HOST_CMD_SEMVER_MAJOR) {
405*b1906ceaSLachlan Hodges 		dev_warn(
406*b1906ceaSLachlan Hodges 			mors->dev,
407*b1906ceaSLachlan Hodges 			"Running FW v%d.%d.%d, driver supports up to v%d, some features might not be supported",
408*b1906ceaSLachlan Hodges 			major, minor, patch, HOST_CMD_SEMVER_MAJOR);
409*b1906ceaSLachlan Hodges 	} else if (ret == 0 && minor != HOST_CMD_SEMVER_MINOR) {
410*b1906ceaSLachlan Hodges 		dev_warn(
411*b1906ceaSLachlan Hodges 			mors->dev,
412*b1906ceaSLachlan Hodges 			"FW version mismatch, some features might not be supported: (Driver) %d.%d.%d, (Chip) %d.%d.%d",
413*b1906ceaSLachlan Hodges 			HOST_CMD_SEMVER_MAJOR, HOST_CMD_SEMVER_MINOR,
414*b1906ceaSLachlan Hodges 			HOST_CMD_SEMVER_PATCH, major, minor, patch);
415*b1906ceaSLachlan Hodges 	}
416*b1906ceaSLachlan Hodges 
417*b1906ceaSLachlan Hodges 	return ret;
418*b1906ceaSLachlan Hodges }
419*b1906ceaSLachlan Hodges 
mm81x_fw_invalidate_host_ptr(struct mm81x * mors)420*b1906ceaSLachlan Hodges static int mm81x_fw_invalidate_host_ptr(struct mm81x *mors)
421*b1906ceaSLachlan Hodges {
422*b1906ceaSLachlan Hodges 	int ret;
423*b1906ceaSLachlan Hodges 
424*b1906ceaSLachlan Hodges 	mors->host_table_ptr = 0;
425*b1906ceaSLachlan Hodges 	mm81x_claim_bus(mors);
426*b1906ceaSLachlan Hodges 	ret = mm81x_reg32_write(mors, MM81X_REG_HOST_MANIFEST_PTR(mors), 0);
427*b1906ceaSLachlan Hodges 	mm81x_release_bus(mors);
428*b1906ceaSLachlan Hodges 	return ret;
429*b1906ceaSLachlan Hodges }
430*b1906ceaSLachlan Hodges 
mm81x_fw_get_host_table_ptr(struct mm81x * mors)431*b1906ceaSLachlan Hodges static int mm81x_fw_get_host_table_ptr(struct mm81x *mors)
432*b1906ceaSLachlan Hodges {
433*b1906ceaSLachlan Hodges 	int ret, err;
434*b1906ceaSLachlan Hodges 
435*b1906ceaSLachlan Hodges 	mm81x_claim_bus(mors);
436*b1906ceaSLachlan Hodges 	ret = read_poll_timeout(mm81x_reg32_read, err,
437*b1906ceaSLachlan Hodges 				err || mors->host_table_ptr,
438*b1906ceaSLachlan Hodges 				HOST_TABLE_PTR_POLL_PERIOD_US,
439*b1906ceaSLachlan Hodges 				HOST_TABLE_PTR_POLL_TIMEOUT_US, false, mors,
440*b1906ceaSLachlan Hodges 				MM81X_REG_HOST_MANIFEST_PTR(mors),
441*b1906ceaSLachlan Hodges 				&mors->host_table_ptr);
442*b1906ceaSLachlan Hodges 	mm81x_release_bus(mors);
443*b1906ceaSLachlan Hodges 
444*b1906ceaSLachlan Hodges 	return ret ? ret : err;
445*b1906ceaSLachlan Hodges }
446*b1906ceaSLachlan Hodges 
mm81x_fw_read_ext_host_table(struct mm81x * mors,struct ext_host_tbl ** ext_host_table)447*b1906ceaSLachlan Hodges static int mm81x_fw_read_ext_host_table(struct mm81x *mors,
448*b1906ceaSLachlan Hodges 					struct ext_host_tbl **ext_host_table)
449*b1906ceaSLachlan Hodges {
450*b1906ceaSLachlan Hodges 	int ret = 0;
451*b1906ceaSLachlan Hodges 	u32 host_tbl_ptr = mors->host_table_ptr;
452*b1906ceaSLachlan Hodges 	u32 ext_host_tbl_ptr;
453*b1906ceaSLachlan Hodges 	u32 ext_host_tbl_ptr_addr =
454*b1906ceaSLachlan Hodges 		host_tbl_ptr + offsetof(struct host_table, ext_host_tbl_addr);
455*b1906ceaSLachlan Hodges 	u32 ext_host_tbl_len;
456*b1906ceaSLachlan Hodges 	u32 ext_host_tbl_len_ptr_addr;
457*b1906ceaSLachlan Hodges 	struct ext_host_tbl *host_tbl = NULL;
458*b1906ceaSLachlan Hodges 
459*b1906ceaSLachlan Hodges 	mm81x_claim_bus(mors);
460*b1906ceaSLachlan Hodges 	ret = mm81x_reg32_read(mors, ext_host_tbl_ptr_addr, &ext_host_tbl_ptr);
461*b1906ceaSLachlan Hodges 	if (ret)
462*b1906ceaSLachlan Hodges 		goto exit;
463*b1906ceaSLachlan Hodges 
464*b1906ceaSLachlan Hodges 	if (!ext_host_tbl_ptr) {
465*b1906ceaSLachlan Hodges 		ret = -ENXIO;
466*b1906ceaSLachlan Hodges 		goto exit;
467*b1906ceaSLachlan Hodges 	}
468*b1906ceaSLachlan Hodges 
469*b1906ceaSLachlan Hodges 	ext_host_tbl_len_ptr_addr =
470*b1906ceaSLachlan Hodges 		ext_host_tbl_ptr +
471*b1906ceaSLachlan Hodges 		offsetof(struct ext_host_tbl, ext_host_tbl_length);
472*b1906ceaSLachlan Hodges 
473*b1906ceaSLachlan Hodges 	ret = mm81x_reg32_read(mors, ext_host_tbl_len_ptr_addr,
474*b1906ceaSLachlan Hodges 			       &ext_host_tbl_len);
475*b1906ceaSLachlan Hodges 	if (ret)
476*b1906ceaSLachlan Hodges 		goto exit;
477*b1906ceaSLachlan Hodges 
478*b1906ceaSLachlan Hodges 	ext_host_tbl_len = ROUND_BYTES_TO_WORD(ext_host_tbl_len);
479*b1906ceaSLachlan Hodges 	if (WARN_ON(ext_host_tbl_len == 0 || ext_host_tbl_len > INT_MAX)) {
480*b1906ceaSLachlan Hodges 		ret = -EINVAL;
481*b1906ceaSLachlan Hodges 		goto exit;
482*b1906ceaSLachlan Hodges 	}
483*b1906ceaSLachlan Hodges 
484*b1906ceaSLachlan Hodges 	host_tbl = kmalloc(ext_host_tbl_len, GFP_KERNEL);
485*b1906ceaSLachlan Hodges 	if (!host_tbl) {
486*b1906ceaSLachlan Hodges 		ret = -ENOMEM;
487*b1906ceaSLachlan Hodges 		goto exit;
488*b1906ceaSLachlan Hodges 	}
489*b1906ceaSLachlan Hodges 
490*b1906ceaSLachlan Hodges 	ret = mm81x_dm_read(mors, ext_host_tbl_ptr, (u8 *)host_tbl,
491*b1906ceaSLachlan Hodges 			    (int)ext_host_tbl_len);
492*b1906ceaSLachlan Hodges 	if (ret)
493*b1906ceaSLachlan Hodges 		goto exit;
494*b1906ceaSLachlan Hodges 
495*b1906ceaSLachlan Hodges 	mm81x_release_bus(mors);
496*b1906ceaSLachlan Hodges 	*ext_host_table = host_tbl;
497*b1906ceaSLachlan Hodges 	return ret;
498*b1906ceaSLachlan Hodges 
499*b1906ceaSLachlan Hodges exit:
500*b1906ceaSLachlan Hodges 	mm81x_release_bus(mors);
501*b1906ceaSLachlan Hodges 	kfree(host_tbl);
502*b1906ceaSLachlan Hodges 	return ret;
503*b1906ceaSLachlan Hodges }
504*b1906ceaSLachlan Hodges 
mm81x_fw_update_capabilities(struct mm81x * mors,struct ext_host_tbl_s1g_caps * caps)505*b1906ceaSLachlan Hodges static void mm81x_fw_update_capabilities(struct mm81x *mors,
506*b1906ceaSLachlan Hodges 					 struct ext_host_tbl_s1g_caps *caps)
507*b1906ceaSLachlan Hodges {
508*b1906ceaSLachlan Hodges 	int i;
509*b1906ceaSLachlan Hodges 
510*b1906ceaSLachlan Hodges 	for (i = 0; i < FW_CAPABILITIES_FLAGS_WIDTH; i++) {
511*b1906ceaSLachlan Hodges 		mors->fw_caps.flags[i] = le32_to_cpu(caps->flags[i]);
512*b1906ceaSLachlan Hodges 		dev_dbg(mors->dev, "Firmware Manifest Flags%d: 0x%x", i,
513*b1906ceaSLachlan Hodges 			le32_to_cpu(caps->flags[i]));
514*b1906ceaSLachlan Hodges 	}
515*b1906ceaSLachlan Hodges 	mors->fw_caps.ampdu_mss = caps->ampdu_mss;
516*b1906ceaSLachlan Hodges 	mors->fw_caps.mm81x_mmss_offset = caps->mm81x_mmss_offset;
517*b1906ceaSLachlan Hodges 	mors->fw_caps.beamformee_sts_capability =
518*b1906ceaSLachlan Hodges 		caps->beamformee_sts_capability;
519*b1906ceaSLachlan Hodges 	mors->fw_caps.maximum_ampdu_length_exponent =
520*b1906ceaSLachlan Hodges 		caps->maximum_ampdu_length;
521*b1906ceaSLachlan Hodges 	mors->fw_caps.number_sounding_dimensions =
522*b1906ceaSLachlan Hodges 		caps->number_sounding_dimensions;
523*b1906ceaSLachlan Hodges 
524*b1906ceaSLachlan Hodges 	dev_dbg(mors->dev, "\tAMPDU Minimum start spacing: %u",
525*b1906ceaSLachlan Hodges 		caps->ampdu_mss);
526*b1906ceaSLachlan Hodges 	dev_dbg(mors->dev, "\tMorse Minimum Start Spacing offset: %u",
527*b1906ceaSLachlan Hodges 		caps->mm81x_mmss_offset);
528*b1906ceaSLachlan Hodges 	dev_dbg(mors->dev, "\tBeamformee STS Capability: %u",
529*b1906ceaSLachlan Hodges 		caps->beamformee_sts_capability);
530*b1906ceaSLachlan Hodges 	dev_dbg(mors->dev, "\tNumber of Sounding Dimensions: %u",
531*b1906ceaSLachlan Hodges 		caps->number_sounding_dimensions);
532*b1906ceaSLachlan Hodges 	dev_dbg(mors->dev, "\tMaximum AMPDU Length Exponent: %u",
533*b1906ceaSLachlan Hodges 		caps->maximum_ampdu_length);
534*b1906ceaSLachlan Hodges }
535*b1906ceaSLachlan Hodges 
mm81x_fw_update_validate_skb_checksum(struct mm81x * mors,struct ext_host_tbl_insert_skb_checksum * validate_checksum)536*b1906ceaSLachlan Hodges static void mm81x_fw_update_validate_skb_checksum(
537*b1906ceaSLachlan Hodges 	struct mm81x *mors,
538*b1906ceaSLachlan Hodges 	struct ext_host_tbl_insert_skb_checksum *validate_checksum)
539*b1906ceaSLachlan Hodges {
540*b1906ceaSLachlan Hodges 	mors->hif.validate_skb_checksum =
541*b1906ceaSLachlan Hodges 		validate_checksum->insert_and_validate_checksum;
542*b1906ceaSLachlan Hodges 	dev_dbg(mors->dev, "Validate checksum inserted by fw %s",
543*b1906ceaSLachlan Hodges 		str_enabled_disabled(mors->hif.validate_skb_checksum));
544*b1906ceaSLachlan Hodges }
545*b1906ceaSLachlan Hodges 
mm81x_fw_parse_ext_host_tbl(struct mm81x * mors)546*b1906ceaSLachlan Hodges int mm81x_fw_parse_ext_host_tbl(struct mm81x *mors)
547*b1906ceaSLachlan Hodges {
548*b1906ceaSLachlan Hodges 	int ret;
549*b1906ceaSLachlan Hodges 	u8 *head;
550*b1906ceaSLachlan Hodges 	u8 *end;
551*b1906ceaSLachlan Hodges 	struct ext_host_tbl *ext_host_table = NULL;
552*b1906ceaSLachlan Hodges 
553*b1906ceaSLachlan Hodges 	ret = mm81x_fw_read_ext_host_table(mors, &ext_host_table);
554*b1906ceaSLachlan Hodges 	if (ret || !ext_host_table)
555*b1906ceaSLachlan Hodges 		goto exit;
556*b1906ceaSLachlan Hodges 
557*b1906ceaSLachlan Hodges 	/* Parse the TLVs */
558*b1906ceaSLachlan Hodges 	head = ext_host_table->ext_host_table_data_tlvs;
559*b1906ceaSLachlan Hodges 	end = ((u8 *)ext_host_table) +
560*b1906ceaSLachlan Hodges 	      le32_to_cpu(ext_host_table->ext_host_tbl_length);
561*b1906ceaSLachlan Hodges 
562*b1906ceaSLachlan Hodges 	while (head < end) {
563*b1906ceaSLachlan Hodges 		struct ext_host_tbl_tlv_hdr *hdr =
564*b1906ceaSLachlan Hodges 			(struct ext_host_tbl_tlv_hdr *)head;
565*b1906ceaSLachlan Hodges 
566*b1906ceaSLachlan Hodges 		switch (le16_to_cpu(hdr->tag)) {
567*b1906ceaSLachlan Hodges 		case MM81X_FW_HOST_TABLE_TAG_S1G_CAPABILITIES:
568*b1906ceaSLachlan Hodges 			mm81x_fw_update_capabilities(
569*b1906ceaSLachlan Hodges 				mors, (struct ext_host_tbl_s1g_caps *)hdr);
570*b1906ceaSLachlan Hodges 			break;
571*b1906ceaSLachlan Hodges 
572*b1906ceaSLachlan Hodges 		case MM81X_FW_HOST_TABLE_TAG_INSERT_SKB_CHECKSUM:
573*b1906ceaSLachlan Hodges 			mm81x_fw_update_validate_skb_checksum(
574*b1906ceaSLachlan Hodges 				mors,
575*b1906ceaSLachlan Hodges 				(struct ext_host_tbl_insert_skb_checksum *)hdr);
576*b1906ceaSLachlan Hodges 			break;
577*b1906ceaSLachlan Hodges 
578*b1906ceaSLachlan Hodges 		case MM81X_FW_HOST_TABLE_TAG_YAPS_TABLE:
579*b1906ceaSLachlan Hodges 			mm81x_yaps_hw_read_table(
580*b1906ceaSLachlan Hodges 				mors, &((struct ext_host_tbl_yaps_table *)hdr)
581*b1906ceaSLachlan Hodges 					       ->yaps_table);
582*b1906ceaSLachlan Hodges 			break;
583*b1906ceaSLachlan Hodges 		default:
584*b1906ceaSLachlan Hodges 			break;
585*b1906ceaSLachlan Hodges 		}
586*b1906ceaSLachlan Hodges 
587*b1906ceaSLachlan Hodges 		head += le16_to_cpu(hdr->length);
588*b1906ceaSLachlan Hodges 		if (!hdr->length)
589*b1906ceaSLachlan Hodges 			break;
590*b1906ceaSLachlan Hodges 	}
591*b1906ceaSLachlan Hodges 
592*b1906ceaSLachlan Hodges 	kfree(ext_host_table);
593*b1906ceaSLachlan Hodges 	return ret;
594*b1906ceaSLachlan Hodges exit:
595*b1906ceaSLachlan Hodges 	dev_err(mors->dev, "failed to parse ext host table %d", ret);
596*b1906ceaSLachlan Hodges 	return ret;
597*b1906ceaSLachlan Hodges }
598*b1906ceaSLachlan Hodges 
__mm81x_fw_flash(struct mm81x * mors,const struct firmware * fw,const struct firmware * bcf,bool reset)599*b1906ceaSLachlan Hodges static int __mm81x_fw_flash(struct mm81x *mors, const struct firmware *fw,
600*b1906ceaSLachlan Hodges 			    const struct firmware *bcf, bool reset)
601*b1906ceaSLachlan Hodges {
602*b1906ceaSLachlan Hodges 	int ret;
603*b1906ceaSLachlan Hodges 
604*b1906ceaSLachlan Hodges 	if (reset || !mors->chip_was_reset) {
605*b1906ceaSLachlan Hodges 		ret = mm81x_hw_digital_reset(mors);
606*b1906ceaSLachlan Hodges 		if (ret)
607*b1906ceaSLachlan Hodges 			return ret;
608*b1906ceaSLachlan Hodges 	}
609*b1906ceaSLachlan Hodges 
610*b1906ceaSLachlan Hodges 	mm81x_hw_pre_firmware_ndr_hook(mors);
611*b1906ceaSLachlan Hodges 
612*b1906ceaSLachlan Hodges 	ret = mm81x_fw_invalidate_host_ptr(mors);
613*b1906ceaSLachlan Hodges 	if (ret)
614*b1906ceaSLachlan Hodges 		return ret;
615*b1906ceaSLachlan Hodges 
616*b1906ceaSLachlan Hodges 	ret = mm81x_fw_load_fw(mors, fw);
617*b1906ceaSLachlan Hodges 	if (ret)
618*b1906ceaSLachlan Hodges 		return ret;
619*b1906ceaSLachlan Hodges 
620*b1906ceaSLachlan Hodges 	ret = mm81x_fw_load_bcf(mors, bcf, mors->bcf_address);
621*b1906ceaSLachlan Hodges 	if (ret)
622*b1906ceaSLachlan Hodges 		return ret;
623*b1906ceaSLachlan Hodges 
624*b1906ceaSLachlan Hodges 	mm81x_fw_trigger(mors);
625*b1906ceaSLachlan Hodges 	mm81x_hw_post_firmware_ndr_hook(mors);
626*b1906ceaSLachlan Hodges 
627*b1906ceaSLachlan Hodges 	ret = mm81x_fw_get_host_table_ptr(mors);
628*b1906ceaSLachlan Hodges 	if (ret)
629*b1906ceaSLachlan Hodges 		return ret;
630*b1906ceaSLachlan Hodges 
631*b1906ceaSLachlan Hodges 	ret = mm81x_fw_verify_magic(mors);
632*b1906ceaSLachlan Hodges 	if (ret)
633*b1906ceaSLachlan Hodges 		return ret;
634*b1906ceaSLachlan Hodges 
635*b1906ceaSLachlan Hodges 	return mm81x_fw_check_compatibility(mors);
636*b1906ceaSLachlan Hodges }
637*b1906ceaSLachlan Hodges 
mm81x_fw_flash(struct mm81x * mors,const struct firmware * fw,const struct firmware * bcf,bool reset)638*b1906ceaSLachlan Hodges static int mm81x_fw_flash(struct mm81x *mors, const struct firmware *fw,
639*b1906ceaSLachlan Hodges 			  const struct firmware *bcf, bool reset)
640*b1906ceaSLachlan Hodges {
641*b1906ceaSLachlan Hodges 	int ret;
642*b1906ceaSLachlan Hodges 	int retries = FW_FLASH_ATTEMPT_COUNT;
643*b1906ceaSLachlan Hodges 
644*b1906ceaSLachlan Hodges 	while (retries--) {
645*b1906ceaSLachlan Hodges 		ret = __mm81x_fw_flash(mors, fw, bcf, reset);
646*b1906ceaSLachlan Hodges 		if (!ret)
647*b1906ceaSLachlan Hodges 			return 0;
648*b1906ceaSLachlan Hodges 
649*b1906ceaSLachlan Hodges 		mors->chip_was_reset = false;
650*b1906ceaSLachlan Hodges 	}
651*b1906ceaSLachlan Hodges 
652*b1906ceaSLachlan Hodges 	return ret;
653*b1906ceaSLachlan Hodges }
654*b1906ceaSLachlan Hodges 
binary_crc(const struct firmware * fw)655*b1906ceaSLachlan Hodges static uint32_t binary_crc(const struct firmware *fw)
656*b1906ceaSLachlan Hodges {
657*b1906ceaSLachlan Hodges 	return ~crc32_le(~0, (unsigned char const *)fw->data, fw->size) &
658*b1906ceaSLachlan Hodges 	       0xffffffff;
659*b1906ceaSLachlan Hodges }
660*b1906ceaSLachlan Hodges 
mm81x_fw_request(struct mm81x * mors,const struct firmware ** fw)661*b1906ceaSLachlan Hodges static int mm81x_fw_request(struct mm81x *mors, const struct firmware **fw)
662*b1906ceaSLachlan Hodges {
663*b1906ceaSLachlan Hodges 	int ret = -ENOENT;
664*b1906ceaSLachlan Hodges 	int ver;
665*b1906ceaSLachlan Hodges 	char *fw_path;
666*b1906ceaSLachlan Hodges 
667*b1906ceaSLachlan Hodges 	for (ver = MM81X_FW_VER_MAX; ver >= MM81X_FW_VER_MIN; ver--) {
668*b1906ceaSLachlan Hodges 		fw_path = mm81x_core_get_fw_path(mors->chip_id, ver);
669*b1906ceaSLachlan Hodges 		if (!fw_path)
670*b1906ceaSLachlan Hodges 			return -ENOMEM;
671*b1906ceaSLachlan Hodges 
672*b1906ceaSLachlan Hodges 		ret = firmware_request_nowarn(fw, fw_path, mors->dev);
673*b1906ceaSLachlan Hodges 		if (!ret) {
674*b1906ceaSLachlan Hodges 			dev_info(
675*b1906ceaSLachlan Hodges 				mors->dev,
676*b1906ceaSLachlan Hodges 				"Loaded firmware from %s, size %zu, crc32 0x%08x\n",
677*b1906ceaSLachlan Hodges 				fw_path, (*fw)->size, binary_crc(*fw));
678*b1906ceaSLachlan Hodges 			mors->fw_major = ver;
679*b1906ceaSLachlan Hodges 		}
680*b1906ceaSLachlan Hodges 
681*b1906ceaSLachlan Hodges 		kfree(fw_path);
682*b1906ceaSLachlan Hodges 		if (!ret)
683*b1906ceaSLachlan Hodges 			return 0;
684*b1906ceaSLachlan Hodges 	}
685*b1906ceaSLachlan Hodges 
686*b1906ceaSLachlan Hodges 	dev_err(mors->dev, "no firmware found (tried v%d down to v%d): %d\n",
687*b1906ceaSLachlan Hodges 		MM81X_FW_VER_MAX, MM81X_FW_VER_MIN, ret);
688*b1906ceaSLachlan Hodges 	return ret;
689*b1906ceaSLachlan Hodges }
690*b1906ceaSLachlan Hodges 
mm81x_fw_init(struct mm81x * mors,bool reset)691*b1906ceaSLachlan Hodges int mm81x_fw_init(struct mm81x *mors, bool reset)
692*b1906ceaSLachlan Hodges {
693*b1906ceaSLachlan Hodges 	int ret;
694*b1906ceaSLachlan Hodges 	int board_id;
695*b1906ceaSLachlan Hodges 	char *bcf_path = NULL;
696*b1906ceaSLachlan Hodges 	const struct firmware *fw = NULL;
697*b1906ceaSLachlan Hodges 	const struct firmware *bcf = NULL;
698*b1906ceaSLachlan Hodges 
699*b1906ceaSLachlan Hodges 	board_id = mm81x_hw_otp_get_board_type(mors);
700*b1906ceaSLachlan Hodges 
701*b1906ceaSLachlan Hodges 	if (!mm81x_hw_otp_valid_board_type(board_id)) {
702*b1906ceaSLachlan Hodges 		dev_err(mors->dev,
703*b1906ceaSLachlan Hodges 			"OTP not set, unable to determine BCF to use");
704*b1906ceaSLachlan Hodges 		ret = -EINVAL;
705*b1906ceaSLachlan Hodges 		goto out;
706*b1906ceaSLachlan Hodges 	}
707*b1906ceaSLachlan Hodges 
708*b1906ceaSLachlan Hodges 	dev_dbg(mors->dev, "Using board type 0x%04x from OTP", board_id);
709*b1906ceaSLachlan Hodges 
710*b1906ceaSLachlan Hodges 	ret = mm81x_fw_request(mors, &fw);
711*b1906ceaSLachlan Hodges 	if (ret)
712*b1906ceaSLachlan Hodges 		goto out;
713*b1906ceaSLachlan Hodges 
714*b1906ceaSLachlan Hodges 	bcf_path = kasprintf(GFP_KERNEL,
715*b1906ceaSLachlan Hodges 			     MM81X_FW_DIR
716*b1906ceaSLachlan Hodges 			     "/v%u/bcf_boardtype_%04x" MM81X_FW_EXT,
717*b1906ceaSLachlan Hodges 			     mors->fw_major, board_id);
718*b1906ceaSLachlan Hodges 	if (!bcf_path) {
719*b1906ceaSLachlan Hodges 		ret = -ENOMEM;
720*b1906ceaSLachlan Hodges 		goto out;
721*b1906ceaSLachlan Hodges 	}
722*b1906ceaSLachlan Hodges 
723*b1906ceaSLachlan Hodges 	ret = request_firmware(&bcf, bcf_path, mors->dev);
724*b1906ceaSLachlan Hodges 	if (ret) {
725*b1906ceaSLachlan Hodges 		if (ret == -ENOENT)
726*b1906ceaSLachlan Hodges 			dev_err(mors->dev, "BCF %s not found\n", bcf_path);
727*b1906ceaSLachlan Hodges 		goto out;
728*b1906ceaSLachlan Hodges 	}
729*b1906ceaSLachlan Hodges 
730*b1906ceaSLachlan Hodges 	dev_info(mors->dev, "Loaded BCF from %s, size %zu, crc32 0x%08x\n",
731*b1906ceaSLachlan Hodges 		 bcf_path, bcf->size, binary_crc(bcf));
732*b1906ceaSLachlan Hodges 
733*b1906ceaSLachlan Hodges 	ret = mm81x_fw_flash(mors, fw, bcf, reset);
734*b1906ceaSLachlan Hodges 	if (ret) {
735*b1906ceaSLachlan Hodges 		dev_err(mors->dev, "failed to flash firmware: %d", ret);
736*b1906ceaSLachlan Hodges 		goto out;
737*b1906ceaSLachlan Hodges 	}
738*b1906ceaSLachlan Hodges 
739*b1906ceaSLachlan Hodges 	ret = mm81x_fw_get_flags(mors);
740*b1906ceaSLachlan Hodges 
741*b1906ceaSLachlan Hodges out:
742*b1906ceaSLachlan Hodges 	release_firmware(fw);
743*b1906ceaSLachlan Hodges 	release_firmware(bcf);
744*b1906ceaSLachlan Hodges 	kfree(bcf_path);
745*b1906ceaSLachlan Hodges 
746*b1906ceaSLachlan Hodges 	if (ret)
747*b1906ceaSLachlan Hodges 		dev_err(mors->dev, "failed to init firmware: %d", ret);
748*b1906ceaSLachlan Hodges 	else
749*b1906ceaSLachlan Hodges 		dev_dbg(mors->dev, "firmware initialised");
750*b1906ceaSLachlan Hodges 
751*b1906ceaSLachlan Hodges 	return ret;
752*b1906ceaSLachlan Hodges }
753