162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (C) 2012-2014, 2018-2019, 2021 Intel Corporation
462306a36Sopenharmony_ci * Copyright (C) 2013-2015 Intel Mobile Communications GmbH
562306a36Sopenharmony_ci * Copyright (C) 2016-2017 Intel Deutschland GmbH
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci#include <linux/firmware.h>
862306a36Sopenharmony_ci#include <linux/rtnetlink.h>
962306a36Sopenharmony_ci#include "iwl-trans.h"
1062306a36Sopenharmony_ci#include "iwl-csr.h"
1162306a36Sopenharmony_ci#include "mvm.h"
1262306a36Sopenharmony_ci#include "iwl-eeprom-parse.h"
1362306a36Sopenharmony_ci#include "iwl-eeprom-read.h"
1462306a36Sopenharmony_ci#include "iwl-nvm-parse.h"
1562306a36Sopenharmony_ci#include "iwl-prph.h"
1662306a36Sopenharmony_ci#include "fw/acpi.h"
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci/* Default NVM size to read */
1962306a36Sopenharmony_ci#define IWL_NVM_DEFAULT_CHUNK_SIZE (2 * 1024)
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci#define NVM_WRITE_OPCODE 1
2262306a36Sopenharmony_ci#define NVM_READ_OPCODE 0
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci/* load nvm chunk response */
2562306a36Sopenharmony_cienum {
2662306a36Sopenharmony_ci	READ_NVM_CHUNK_SUCCEED = 0,
2762306a36Sopenharmony_ci	READ_NVM_CHUNK_NOT_VALID_ADDRESS = 1
2862306a36Sopenharmony_ci};
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ci/*
3162306a36Sopenharmony_ci * prepare the NVM host command w/ the pointers to the nvm buffer
3262306a36Sopenharmony_ci * and send it to fw
3362306a36Sopenharmony_ci */
3462306a36Sopenharmony_cistatic int iwl_nvm_write_chunk(struct iwl_mvm *mvm, u16 section,
3562306a36Sopenharmony_ci			       u16 offset, u16 length, const u8 *data)
3662306a36Sopenharmony_ci{
3762306a36Sopenharmony_ci	struct iwl_nvm_access_cmd nvm_access_cmd = {
3862306a36Sopenharmony_ci		.offset = cpu_to_le16(offset),
3962306a36Sopenharmony_ci		.length = cpu_to_le16(length),
4062306a36Sopenharmony_ci		.type = cpu_to_le16(section),
4162306a36Sopenharmony_ci		.op_code = NVM_WRITE_OPCODE,
4262306a36Sopenharmony_ci	};
4362306a36Sopenharmony_ci	struct iwl_host_cmd cmd = {
4462306a36Sopenharmony_ci		.id = NVM_ACCESS_CMD,
4562306a36Sopenharmony_ci		.len = { sizeof(struct iwl_nvm_access_cmd), length },
4662306a36Sopenharmony_ci		.flags = CMD_WANT_SKB | CMD_SEND_IN_RFKILL,
4762306a36Sopenharmony_ci		.data = { &nvm_access_cmd, data },
4862306a36Sopenharmony_ci		/* data may come from vmalloc, so use _DUP */
4962306a36Sopenharmony_ci		.dataflags = { 0, IWL_HCMD_DFL_DUP },
5062306a36Sopenharmony_ci	};
5162306a36Sopenharmony_ci	struct iwl_rx_packet *pkt;
5262306a36Sopenharmony_ci	struct iwl_nvm_access_resp *nvm_resp;
5362306a36Sopenharmony_ci	int ret;
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci	ret = iwl_mvm_send_cmd(mvm, &cmd);
5662306a36Sopenharmony_ci	if (ret)
5762306a36Sopenharmony_ci		return ret;
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci	pkt = cmd.resp_pkt;
6062306a36Sopenharmony_ci	/* Extract & check NVM write response */
6162306a36Sopenharmony_ci	nvm_resp = (void *)pkt->data;
6262306a36Sopenharmony_ci	if (le16_to_cpu(nvm_resp->status) != READ_NVM_CHUNK_SUCCEED) {
6362306a36Sopenharmony_ci		IWL_ERR(mvm,
6462306a36Sopenharmony_ci			"NVM access write command failed for section %u (status = 0x%x)\n",
6562306a36Sopenharmony_ci			section, le16_to_cpu(nvm_resp->status));
6662306a36Sopenharmony_ci		ret = -EIO;
6762306a36Sopenharmony_ci	}
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci	iwl_free_resp(&cmd);
7062306a36Sopenharmony_ci	return ret;
7162306a36Sopenharmony_ci}
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_cistatic int iwl_nvm_read_chunk(struct iwl_mvm *mvm, u16 section,
7462306a36Sopenharmony_ci			      u16 offset, u16 length, u8 *data)
7562306a36Sopenharmony_ci{
7662306a36Sopenharmony_ci	struct iwl_nvm_access_cmd nvm_access_cmd = {
7762306a36Sopenharmony_ci		.offset = cpu_to_le16(offset),
7862306a36Sopenharmony_ci		.length = cpu_to_le16(length),
7962306a36Sopenharmony_ci		.type = cpu_to_le16(section),
8062306a36Sopenharmony_ci		.op_code = NVM_READ_OPCODE,
8162306a36Sopenharmony_ci	};
8262306a36Sopenharmony_ci	struct iwl_nvm_access_resp *nvm_resp;
8362306a36Sopenharmony_ci	struct iwl_rx_packet *pkt;
8462306a36Sopenharmony_ci	struct iwl_host_cmd cmd = {
8562306a36Sopenharmony_ci		.id = NVM_ACCESS_CMD,
8662306a36Sopenharmony_ci		.flags = CMD_WANT_SKB | CMD_SEND_IN_RFKILL,
8762306a36Sopenharmony_ci		.data = { &nvm_access_cmd, },
8862306a36Sopenharmony_ci	};
8962306a36Sopenharmony_ci	int ret, bytes_read, offset_read;
9062306a36Sopenharmony_ci	u8 *resp_data;
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci	cmd.len[0] = sizeof(struct iwl_nvm_access_cmd);
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci	ret = iwl_mvm_send_cmd(mvm, &cmd);
9562306a36Sopenharmony_ci	if (ret)
9662306a36Sopenharmony_ci		return ret;
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci	pkt = cmd.resp_pkt;
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci	/* Extract NVM response */
10162306a36Sopenharmony_ci	nvm_resp = (void *)pkt->data;
10262306a36Sopenharmony_ci	ret = le16_to_cpu(nvm_resp->status);
10362306a36Sopenharmony_ci	bytes_read = le16_to_cpu(nvm_resp->length);
10462306a36Sopenharmony_ci	offset_read = le16_to_cpu(nvm_resp->offset);
10562306a36Sopenharmony_ci	resp_data = nvm_resp->data;
10662306a36Sopenharmony_ci	if (ret) {
10762306a36Sopenharmony_ci		if ((offset != 0) &&
10862306a36Sopenharmony_ci		    (ret == READ_NVM_CHUNK_NOT_VALID_ADDRESS)) {
10962306a36Sopenharmony_ci			/*
11062306a36Sopenharmony_ci			 * meaning of NOT_VALID_ADDRESS:
11162306a36Sopenharmony_ci			 * driver try to read chunk from address that is
11262306a36Sopenharmony_ci			 * multiple of 2K and got an error since addr is empty.
11362306a36Sopenharmony_ci			 * meaning of (offset != 0): driver already
11462306a36Sopenharmony_ci			 * read valid data from another chunk so this case
11562306a36Sopenharmony_ci			 * is not an error.
11662306a36Sopenharmony_ci			 */
11762306a36Sopenharmony_ci			IWL_DEBUG_EEPROM(mvm->trans->dev,
11862306a36Sopenharmony_ci					 "NVM access command failed on offset 0x%x since that section size is multiple 2K\n",
11962306a36Sopenharmony_ci					 offset);
12062306a36Sopenharmony_ci			ret = 0;
12162306a36Sopenharmony_ci		} else {
12262306a36Sopenharmony_ci			IWL_DEBUG_EEPROM(mvm->trans->dev,
12362306a36Sopenharmony_ci					 "NVM access command failed with status %d (device: %s)\n",
12462306a36Sopenharmony_ci					 ret, mvm->trans->name);
12562306a36Sopenharmony_ci			ret = -ENODATA;
12662306a36Sopenharmony_ci		}
12762306a36Sopenharmony_ci		goto exit;
12862306a36Sopenharmony_ci	}
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci	if (offset_read != offset) {
13162306a36Sopenharmony_ci		IWL_ERR(mvm, "NVM ACCESS response with invalid offset %d\n",
13262306a36Sopenharmony_ci			offset_read);
13362306a36Sopenharmony_ci		ret = -EINVAL;
13462306a36Sopenharmony_ci		goto exit;
13562306a36Sopenharmony_ci	}
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci	/* Write data to NVM */
13862306a36Sopenharmony_ci	memcpy(data + offset, resp_data, bytes_read);
13962306a36Sopenharmony_ci	ret = bytes_read;
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ciexit:
14262306a36Sopenharmony_ci	iwl_free_resp(&cmd);
14362306a36Sopenharmony_ci	return ret;
14462306a36Sopenharmony_ci}
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_cistatic int iwl_nvm_write_section(struct iwl_mvm *mvm, u16 section,
14762306a36Sopenharmony_ci				 const u8 *data, u16 length)
14862306a36Sopenharmony_ci{
14962306a36Sopenharmony_ci	int offset = 0;
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ci	/* copy data in chunks of 2k (and remainder if any) */
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_ci	while (offset < length) {
15462306a36Sopenharmony_ci		int chunk_size, ret;
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci		chunk_size = min(IWL_NVM_DEFAULT_CHUNK_SIZE,
15762306a36Sopenharmony_ci				 length - offset);
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci		ret = iwl_nvm_write_chunk(mvm, section, offset,
16062306a36Sopenharmony_ci					  chunk_size, data + offset);
16162306a36Sopenharmony_ci		if (ret < 0)
16262306a36Sopenharmony_ci			return ret;
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci		offset += chunk_size;
16562306a36Sopenharmony_ci	}
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci	return 0;
16862306a36Sopenharmony_ci}
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci/*
17162306a36Sopenharmony_ci * Reads an NVM section completely.
17262306a36Sopenharmony_ci * NICs prior to 7000 family doesn't have a real NVM, but just read
17362306a36Sopenharmony_ci * section 0 which is the EEPROM. Because the EEPROM reading is unlimited
17462306a36Sopenharmony_ci * by uCode, we need to manually check in this case that we don't
17562306a36Sopenharmony_ci * overflow and try to read more than the EEPROM size.
17662306a36Sopenharmony_ci * For 7000 family NICs, we supply the maximal size we can read, and
17762306a36Sopenharmony_ci * the uCode fills the response with as much data as we can,
17862306a36Sopenharmony_ci * without overflowing, so no check is needed.
17962306a36Sopenharmony_ci */
18062306a36Sopenharmony_cistatic int iwl_nvm_read_section(struct iwl_mvm *mvm, u16 section,
18162306a36Sopenharmony_ci				u8 *data, u32 size_read)
18262306a36Sopenharmony_ci{
18362306a36Sopenharmony_ci	u16 length, offset = 0;
18462306a36Sopenharmony_ci	int ret;
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci	/* Set nvm section read length */
18762306a36Sopenharmony_ci	length = IWL_NVM_DEFAULT_CHUNK_SIZE;
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci	ret = length;
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_ci	/* Read the NVM until exhausted (reading less than requested) */
19262306a36Sopenharmony_ci	while (ret == length) {
19362306a36Sopenharmony_ci		/* Check no memory assumptions fail and cause an overflow */
19462306a36Sopenharmony_ci		if ((size_read + offset + length) >
19562306a36Sopenharmony_ci		    mvm->trans->trans_cfg->base_params->eeprom_size) {
19662306a36Sopenharmony_ci			IWL_ERR(mvm, "EEPROM size is too small for NVM\n");
19762306a36Sopenharmony_ci			return -ENOBUFS;
19862306a36Sopenharmony_ci		}
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci		ret = iwl_nvm_read_chunk(mvm, section, offset, length, data);
20162306a36Sopenharmony_ci		if (ret < 0) {
20262306a36Sopenharmony_ci			IWL_DEBUG_EEPROM(mvm->trans->dev,
20362306a36Sopenharmony_ci					 "Cannot read NVM from section %d offset %d, length %d\n",
20462306a36Sopenharmony_ci					 section, offset, length);
20562306a36Sopenharmony_ci			return ret;
20662306a36Sopenharmony_ci		}
20762306a36Sopenharmony_ci		offset += ret;
20862306a36Sopenharmony_ci	}
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_ci	iwl_nvm_fixups(mvm->trans->hw_id, section, data, offset);
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_ci	IWL_DEBUG_EEPROM(mvm->trans->dev,
21362306a36Sopenharmony_ci			 "NVM section %d read completed\n", section);
21462306a36Sopenharmony_ci	return offset;
21562306a36Sopenharmony_ci}
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_cistatic struct iwl_nvm_data *
21862306a36Sopenharmony_ciiwl_parse_nvm_sections(struct iwl_mvm *mvm)
21962306a36Sopenharmony_ci{
22062306a36Sopenharmony_ci	struct iwl_nvm_section *sections = mvm->nvm_sections;
22162306a36Sopenharmony_ci	const __be16 *hw;
22262306a36Sopenharmony_ci	const __le16 *sw, *calib, *regulatory, *mac_override, *phy_sku;
22362306a36Sopenharmony_ci	int regulatory_type;
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_ci	/* Checking for required sections */
22662306a36Sopenharmony_ci	if (mvm->trans->cfg->nvm_type == IWL_NVM) {
22762306a36Sopenharmony_ci		if (!mvm->nvm_sections[NVM_SECTION_TYPE_SW].data ||
22862306a36Sopenharmony_ci		    !mvm->nvm_sections[mvm->cfg->nvm_hw_section_num].data) {
22962306a36Sopenharmony_ci			IWL_ERR(mvm, "Can't parse empty OTP/NVM sections\n");
23062306a36Sopenharmony_ci			return NULL;
23162306a36Sopenharmony_ci		}
23262306a36Sopenharmony_ci	} else {
23362306a36Sopenharmony_ci		if (mvm->trans->cfg->nvm_type == IWL_NVM_SDP)
23462306a36Sopenharmony_ci			regulatory_type = NVM_SECTION_TYPE_REGULATORY_SDP;
23562306a36Sopenharmony_ci		else
23662306a36Sopenharmony_ci			regulatory_type = NVM_SECTION_TYPE_REGULATORY;
23762306a36Sopenharmony_ci
23862306a36Sopenharmony_ci		/* SW and REGULATORY sections are mandatory */
23962306a36Sopenharmony_ci		if (!mvm->nvm_sections[NVM_SECTION_TYPE_SW].data ||
24062306a36Sopenharmony_ci		    !mvm->nvm_sections[regulatory_type].data) {
24162306a36Sopenharmony_ci			IWL_ERR(mvm,
24262306a36Sopenharmony_ci				"Can't parse empty family 8000 OTP/NVM sections\n");
24362306a36Sopenharmony_ci			return NULL;
24462306a36Sopenharmony_ci		}
24562306a36Sopenharmony_ci		/* MAC_OVERRIDE or at least HW section must exist */
24662306a36Sopenharmony_ci		if (!mvm->nvm_sections[mvm->cfg->nvm_hw_section_num].data &&
24762306a36Sopenharmony_ci		    !mvm->nvm_sections[NVM_SECTION_TYPE_MAC_OVERRIDE].data) {
24862306a36Sopenharmony_ci			IWL_ERR(mvm,
24962306a36Sopenharmony_ci				"Can't parse mac_address, empty sections\n");
25062306a36Sopenharmony_ci			return NULL;
25162306a36Sopenharmony_ci		}
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_ci		/* PHY_SKU section is mandatory in B0 */
25462306a36Sopenharmony_ci		if (mvm->trans->cfg->nvm_type == IWL_NVM_EXT &&
25562306a36Sopenharmony_ci		    !mvm->nvm_sections[NVM_SECTION_TYPE_PHY_SKU].data) {
25662306a36Sopenharmony_ci			IWL_ERR(mvm,
25762306a36Sopenharmony_ci				"Can't parse phy_sku in B0, empty sections\n");
25862306a36Sopenharmony_ci			return NULL;
25962306a36Sopenharmony_ci		}
26062306a36Sopenharmony_ci	}
26162306a36Sopenharmony_ci
26262306a36Sopenharmony_ci	hw = (const __be16 *)sections[mvm->cfg->nvm_hw_section_num].data;
26362306a36Sopenharmony_ci	sw = (const __le16 *)sections[NVM_SECTION_TYPE_SW].data;
26462306a36Sopenharmony_ci	calib = (const __le16 *)sections[NVM_SECTION_TYPE_CALIBRATION].data;
26562306a36Sopenharmony_ci	mac_override =
26662306a36Sopenharmony_ci		(const __le16 *)sections[NVM_SECTION_TYPE_MAC_OVERRIDE].data;
26762306a36Sopenharmony_ci	phy_sku = (const __le16 *)sections[NVM_SECTION_TYPE_PHY_SKU].data;
26862306a36Sopenharmony_ci
26962306a36Sopenharmony_ci	regulatory = mvm->trans->cfg->nvm_type == IWL_NVM_SDP ?
27062306a36Sopenharmony_ci		(const __le16 *)sections[NVM_SECTION_TYPE_REGULATORY_SDP].data :
27162306a36Sopenharmony_ci		(const __le16 *)sections[NVM_SECTION_TYPE_REGULATORY].data;
27262306a36Sopenharmony_ci
27362306a36Sopenharmony_ci	return iwl_parse_nvm_data(mvm->trans, mvm->cfg, mvm->fw, hw, sw, calib,
27462306a36Sopenharmony_ci				  regulatory, mac_override, phy_sku,
27562306a36Sopenharmony_ci				  mvm->fw->valid_tx_ant, mvm->fw->valid_rx_ant);
27662306a36Sopenharmony_ci}
27762306a36Sopenharmony_ci
27862306a36Sopenharmony_ci/* Loads the NVM data stored in mvm->nvm_sections into the NIC */
27962306a36Sopenharmony_ciint iwl_mvm_load_nvm_to_nic(struct iwl_mvm *mvm)
28062306a36Sopenharmony_ci{
28162306a36Sopenharmony_ci	int i, ret = 0;
28262306a36Sopenharmony_ci	struct iwl_nvm_section *sections = mvm->nvm_sections;
28362306a36Sopenharmony_ci
28462306a36Sopenharmony_ci	IWL_DEBUG_EEPROM(mvm->trans->dev, "'Write to NVM\n");
28562306a36Sopenharmony_ci
28662306a36Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(mvm->nvm_sections); i++) {
28762306a36Sopenharmony_ci		if (!mvm->nvm_sections[i].data || !mvm->nvm_sections[i].length)
28862306a36Sopenharmony_ci			continue;
28962306a36Sopenharmony_ci		ret = iwl_nvm_write_section(mvm, i, sections[i].data,
29062306a36Sopenharmony_ci					    sections[i].length);
29162306a36Sopenharmony_ci		if (ret < 0) {
29262306a36Sopenharmony_ci			IWL_ERR(mvm, "iwl_mvm_send_cmd failed: %d\n", ret);
29362306a36Sopenharmony_ci			break;
29462306a36Sopenharmony_ci		}
29562306a36Sopenharmony_ci	}
29662306a36Sopenharmony_ci	return ret;
29762306a36Sopenharmony_ci}
29862306a36Sopenharmony_ci
29962306a36Sopenharmony_ciint iwl_nvm_init(struct iwl_mvm *mvm)
30062306a36Sopenharmony_ci{
30162306a36Sopenharmony_ci	int ret, section;
30262306a36Sopenharmony_ci	u32 size_read = 0;
30362306a36Sopenharmony_ci	u8 *nvm_buffer, *temp;
30462306a36Sopenharmony_ci	const char *nvm_file_C = mvm->cfg->default_nvm_file_C_step;
30562306a36Sopenharmony_ci
30662306a36Sopenharmony_ci	if (WARN_ON_ONCE(mvm->cfg->nvm_hw_section_num >= NVM_MAX_NUM_SECTIONS))
30762306a36Sopenharmony_ci		return -EINVAL;
30862306a36Sopenharmony_ci
30962306a36Sopenharmony_ci	/* load NVM values from nic */
31062306a36Sopenharmony_ci	/* Read From FW NVM */
31162306a36Sopenharmony_ci	IWL_DEBUG_EEPROM(mvm->trans->dev, "Read from NVM\n");
31262306a36Sopenharmony_ci
31362306a36Sopenharmony_ci	nvm_buffer = kmalloc(mvm->trans->trans_cfg->base_params->eeprom_size,
31462306a36Sopenharmony_ci			     GFP_KERNEL);
31562306a36Sopenharmony_ci	if (!nvm_buffer)
31662306a36Sopenharmony_ci		return -ENOMEM;
31762306a36Sopenharmony_ci	for (section = 0; section < NVM_MAX_NUM_SECTIONS; section++) {
31862306a36Sopenharmony_ci		/* we override the constness for initial read */
31962306a36Sopenharmony_ci		ret = iwl_nvm_read_section(mvm, section, nvm_buffer,
32062306a36Sopenharmony_ci					   size_read);
32162306a36Sopenharmony_ci		if (ret == -ENODATA) {
32262306a36Sopenharmony_ci			ret = 0;
32362306a36Sopenharmony_ci			continue;
32462306a36Sopenharmony_ci		}
32562306a36Sopenharmony_ci		if (ret < 0)
32662306a36Sopenharmony_ci			break;
32762306a36Sopenharmony_ci		size_read += ret;
32862306a36Sopenharmony_ci		temp = kmemdup(nvm_buffer, ret, GFP_KERNEL);
32962306a36Sopenharmony_ci		if (!temp) {
33062306a36Sopenharmony_ci			ret = -ENOMEM;
33162306a36Sopenharmony_ci			break;
33262306a36Sopenharmony_ci		}
33362306a36Sopenharmony_ci
33462306a36Sopenharmony_ci		iwl_nvm_fixups(mvm->trans->hw_id, section, temp, ret);
33562306a36Sopenharmony_ci
33662306a36Sopenharmony_ci		mvm->nvm_sections[section].data = temp;
33762306a36Sopenharmony_ci		mvm->nvm_sections[section].length = ret;
33862306a36Sopenharmony_ci
33962306a36Sopenharmony_ci#ifdef CONFIG_IWLWIFI_DEBUGFS
34062306a36Sopenharmony_ci		switch (section) {
34162306a36Sopenharmony_ci		case NVM_SECTION_TYPE_SW:
34262306a36Sopenharmony_ci			mvm->nvm_sw_blob.data = temp;
34362306a36Sopenharmony_ci			mvm->nvm_sw_blob.size  = ret;
34462306a36Sopenharmony_ci			break;
34562306a36Sopenharmony_ci		case NVM_SECTION_TYPE_CALIBRATION:
34662306a36Sopenharmony_ci			mvm->nvm_calib_blob.data = temp;
34762306a36Sopenharmony_ci			mvm->nvm_calib_blob.size  = ret;
34862306a36Sopenharmony_ci			break;
34962306a36Sopenharmony_ci		case NVM_SECTION_TYPE_PRODUCTION:
35062306a36Sopenharmony_ci			mvm->nvm_prod_blob.data = temp;
35162306a36Sopenharmony_ci			mvm->nvm_prod_blob.size  = ret;
35262306a36Sopenharmony_ci			break;
35362306a36Sopenharmony_ci		case NVM_SECTION_TYPE_PHY_SKU:
35462306a36Sopenharmony_ci			mvm->nvm_phy_sku_blob.data = temp;
35562306a36Sopenharmony_ci			mvm->nvm_phy_sku_blob.size  = ret;
35662306a36Sopenharmony_ci			break;
35762306a36Sopenharmony_ci		case NVM_SECTION_TYPE_REGULATORY_SDP:
35862306a36Sopenharmony_ci		case NVM_SECTION_TYPE_REGULATORY:
35962306a36Sopenharmony_ci			mvm->nvm_reg_blob.data = temp;
36062306a36Sopenharmony_ci			mvm->nvm_reg_blob.size  = ret;
36162306a36Sopenharmony_ci			break;
36262306a36Sopenharmony_ci		default:
36362306a36Sopenharmony_ci			if (section == mvm->cfg->nvm_hw_section_num) {
36462306a36Sopenharmony_ci				mvm->nvm_hw_blob.data = temp;
36562306a36Sopenharmony_ci				mvm->nvm_hw_blob.size = ret;
36662306a36Sopenharmony_ci				break;
36762306a36Sopenharmony_ci			}
36862306a36Sopenharmony_ci		}
36962306a36Sopenharmony_ci#endif
37062306a36Sopenharmony_ci	}
37162306a36Sopenharmony_ci	if (!size_read)
37262306a36Sopenharmony_ci		IWL_ERR(mvm, "OTP is blank\n");
37362306a36Sopenharmony_ci	kfree(nvm_buffer);
37462306a36Sopenharmony_ci
37562306a36Sopenharmony_ci	/* Only if PNVM selected in the mod param - load external NVM  */
37662306a36Sopenharmony_ci	if (mvm->nvm_file_name) {
37762306a36Sopenharmony_ci		/* read External NVM file from the mod param */
37862306a36Sopenharmony_ci		ret = iwl_read_external_nvm(mvm->trans, mvm->nvm_file_name,
37962306a36Sopenharmony_ci					    mvm->nvm_sections);
38062306a36Sopenharmony_ci		if (ret) {
38162306a36Sopenharmony_ci			mvm->nvm_file_name = nvm_file_C;
38262306a36Sopenharmony_ci
38362306a36Sopenharmony_ci			if ((ret == -EFAULT || ret == -ENOENT) &&
38462306a36Sopenharmony_ci			    mvm->nvm_file_name) {
38562306a36Sopenharmony_ci				/* in case nvm file was failed try again */
38662306a36Sopenharmony_ci				ret = iwl_read_external_nvm(mvm->trans,
38762306a36Sopenharmony_ci							    mvm->nvm_file_name,
38862306a36Sopenharmony_ci							    mvm->nvm_sections);
38962306a36Sopenharmony_ci				if (ret)
39062306a36Sopenharmony_ci					return ret;
39162306a36Sopenharmony_ci			} else {
39262306a36Sopenharmony_ci				return ret;
39362306a36Sopenharmony_ci			}
39462306a36Sopenharmony_ci		}
39562306a36Sopenharmony_ci	}
39662306a36Sopenharmony_ci
39762306a36Sopenharmony_ci	/* parse the relevant nvm sections */
39862306a36Sopenharmony_ci	mvm->nvm_data = iwl_parse_nvm_sections(mvm);
39962306a36Sopenharmony_ci	if (!mvm->nvm_data)
40062306a36Sopenharmony_ci		return -ENODATA;
40162306a36Sopenharmony_ci	IWL_DEBUG_EEPROM(mvm->trans->dev, "nvm version = %x\n",
40262306a36Sopenharmony_ci			 mvm->nvm_data->nvm_version);
40362306a36Sopenharmony_ci
40462306a36Sopenharmony_ci	return ret < 0 ? ret : 0;
40562306a36Sopenharmony_ci}
40662306a36Sopenharmony_ci
40762306a36Sopenharmony_cistruct iwl_mcc_update_resp_v8 *
40862306a36Sopenharmony_ciiwl_mvm_update_mcc(struct iwl_mvm *mvm, const char *alpha2,
40962306a36Sopenharmony_ci		   enum iwl_mcc_source src_id)
41062306a36Sopenharmony_ci{
41162306a36Sopenharmony_ci	struct iwl_mcc_update_cmd mcc_update_cmd = {
41262306a36Sopenharmony_ci		.mcc = cpu_to_le16(alpha2[0] << 8 | alpha2[1]),
41362306a36Sopenharmony_ci		.source_id = (u8)src_id,
41462306a36Sopenharmony_ci	};
41562306a36Sopenharmony_ci	struct iwl_mcc_update_resp_v8 *resp_cp;
41662306a36Sopenharmony_ci	struct iwl_rx_packet *pkt;
41762306a36Sopenharmony_ci	struct iwl_host_cmd cmd = {
41862306a36Sopenharmony_ci		.id = MCC_UPDATE_CMD,
41962306a36Sopenharmony_ci		.flags = CMD_WANT_SKB | CMD_SEND_IN_RFKILL,
42062306a36Sopenharmony_ci		.data = { &mcc_update_cmd },
42162306a36Sopenharmony_ci	};
42262306a36Sopenharmony_ci
42362306a36Sopenharmony_ci	int ret, resp_ver;
42462306a36Sopenharmony_ci	u32 status;
42562306a36Sopenharmony_ci	int resp_len, n_channels;
42662306a36Sopenharmony_ci	u16 mcc;
42762306a36Sopenharmony_ci
42862306a36Sopenharmony_ci	if (WARN_ON_ONCE(!iwl_mvm_is_lar_supported(mvm)))
42962306a36Sopenharmony_ci		return ERR_PTR(-EOPNOTSUPP);
43062306a36Sopenharmony_ci
43162306a36Sopenharmony_ci	cmd.len[0] = sizeof(struct iwl_mcc_update_cmd);
43262306a36Sopenharmony_ci
43362306a36Sopenharmony_ci	IWL_DEBUG_LAR(mvm, "send MCC update to FW with '%c%c' src = %d\n",
43462306a36Sopenharmony_ci		      alpha2[0], alpha2[1], src_id);
43562306a36Sopenharmony_ci
43662306a36Sopenharmony_ci	ret = iwl_mvm_send_cmd(mvm, &cmd);
43762306a36Sopenharmony_ci	if (ret)
43862306a36Sopenharmony_ci		return ERR_PTR(ret);
43962306a36Sopenharmony_ci
44062306a36Sopenharmony_ci	pkt = cmd.resp_pkt;
44162306a36Sopenharmony_ci
44262306a36Sopenharmony_ci	resp_ver = iwl_fw_lookup_notif_ver(mvm->fw, IWL_ALWAYS_LONG_GROUP,
44362306a36Sopenharmony_ci					   MCC_UPDATE_CMD, 0);
44462306a36Sopenharmony_ci
44562306a36Sopenharmony_ci	/* Extract MCC response */
44662306a36Sopenharmony_ci	if (resp_ver >= 8) {
44762306a36Sopenharmony_ci		struct iwl_mcc_update_resp_v8 *mcc_resp_v8 = (void *)pkt->data;
44862306a36Sopenharmony_ci
44962306a36Sopenharmony_ci		n_channels =  __le32_to_cpu(mcc_resp_v8->n_channels);
45062306a36Sopenharmony_ci		if (iwl_rx_packet_payload_len(pkt) !=
45162306a36Sopenharmony_ci		    struct_size(mcc_resp_v8, channels, n_channels)) {
45262306a36Sopenharmony_ci			resp_cp = ERR_PTR(-EINVAL);
45362306a36Sopenharmony_ci			goto exit;
45462306a36Sopenharmony_ci		}
45562306a36Sopenharmony_ci		resp_len = struct_size(resp_cp, channels, n_channels);
45662306a36Sopenharmony_ci		resp_cp = kzalloc(resp_len, GFP_KERNEL);
45762306a36Sopenharmony_ci		if (!resp_cp) {
45862306a36Sopenharmony_ci			resp_cp = ERR_PTR(-ENOMEM);
45962306a36Sopenharmony_ci			goto exit;
46062306a36Sopenharmony_ci		}
46162306a36Sopenharmony_ci		resp_cp->status = mcc_resp_v8->status;
46262306a36Sopenharmony_ci		resp_cp->mcc = mcc_resp_v8->mcc;
46362306a36Sopenharmony_ci		resp_cp->cap = mcc_resp_v8->cap;
46462306a36Sopenharmony_ci		resp_cp->source_id = mcc_resp_v8->source_id;
46562306a36Sopenharmony_ci		resp_cp->time = mcc_resp_v8->time;
46662306a36Sopenharmony_ci		resp_cp->geo_info = mcc_resp_v8->geo_info;
46762306a36Sopenharmony_ci		resp_cp->n_channels = mcc_resp_v8->n_channels;
46862306a36Sopenharmony_ci		memcpy(resp_cp->channels, mcc_resp_v8->channels,
46962306a36Sopenharmony_ci		       n_channels * sizeof(__le32));
47062306a36Sopenharmony_ci	} else if (fw_has_capa(&mvm->fw->ucode_capa,
47162306a36Sopenharmony_ci			       IWL_UCODE_TLV_CAPA_MCC_UPDATE_11AX_SUPPORT)) {
47262306a36Sopenharmony_ci		struct iwl_mcc_update_resp_v4 *mcc_resp_v4 = (void *)pkt->data;
47362306a36Sopenharmony_ci
47462306a36Sopenharmony_ci		n_channels =  __le32_to_cpu(mcc_resp_v4->n_channels);
47562306a36Sopenharmony_ci		if (iwl_rx_packet_payload_len(pkt) !=
47662306a36Sopenharmony_ci		    struct_size(mcc_resp_v4, channels, n_channels)) {
47762306a36Sopenharmony_ci			resp_cp = ERR_PTR(-EINVAL);
47862306a36Sopenharmony_ci			goto exit;
47962306a36Sopenharmony_ci		}
48062306a36Sopenharmony_ci		resp_len = struct_size(resp_cp, channels, n_channels);
48162306a36Sopenharmony_ci		resp_cp = kzalloc(resp_len, GFP_KERNEL);
48262306a36Sopenharmony_ci		if (!resp_cp) {
48362306a36Sopenharmony_ci			resp_cp = ERR_PTR(-ENOMEM);
48462306a36Sopenharmony_ci			goto exit;
48562306a36Sopenharmony_ci		}
48662306a36Sopenharmony_ci
48762306a36Sopenharmony_ci		resp_cp->status = mcc_resp_v4->status;
48862306a36Sopenharmony_ci		resp_cp->mcc = mcc_resp_v4->mcc;
48962306a36Sopenharmony_ci		resp_cp->cap = cpu_to_le32(le16_to_cpu(mcc_resp_v4->cap));
49062306a36Sopenharmony_ci		resp_cp->source_id = mcc_resp_v4->source_id;
49162306a36Sopenharmony_ci		resp_cp->time = mcc_resp_v4->time;
49262306a36Sopenharmony_ci		resp_cp->geo_info = mcc_resp_v4->geo_info;
49362306a36Sopenharmony_ci		resp_cp->n_channels = mcc_resp_v4->n_channels;
49462306a36Sopenharmony_ci		memcpy(resp_cp->channels, mcc_resp_v4->channels,
49562306a36Sopenharmony_ci		       n_channels * sizeof(__le32));
49662306a36Sopenharmony_ci	} else {
49762306a36Sopenharmony_ci		struct iwl_mcc_update_resp_v3 *mcc_resp_v3 = (void *)pkt->data;
49862306a36Sopenharmony_ci
49962306a36Sopenharmony_ci		n_channels =  __le32_to_cpu(mcc_resp_v3->n_channels);
50062306a36Sopenharmony_ci		if (iwl_rx_packet_payload_len(pkt) !=
50162306a36Sopenharmony_ci		    struct_size(mcc_resp_v3, channels, n_channels)) {
50262306a36Sopenharmony_ci			resp_cp = ERR_PTR(-EINVAL);
50362306a36Sopenharmony_ci			goto exit;
50462306a36Sopenharmony_ci		}
50562306a36Sopenharmony_ci		resp_len = struct_size(resp_cp, channels, n_channels);
50662306a36Sopenharmony_ci		resp_cp = kzalloc(resp_len, GFP_KERNEL);
50762306a36Sopenharmony_ci		if (!resp_cp) {
50862306a36Sopenharmony_ci			resp_cp = ERR_PTR(-ENOMEM);
50962306a36Sopenharmony_ci			goto exit;
51062306a36Sopenharmony_ci		}
51162306a36Sopenharmony_ci
51262306a36Sopenharmony_ci		resp_cp->status = mcc_resp_v3->status;
51362306a36Sopenharmony_ci		resp_cp->mcc = mcc_resp_v3->mcc;
51462306a36Sopenharmony_ci		resp_cp->cap = cpu_to_le32(mcc_resp_v3->cap);
51562306a36Sopenharmony_ci		resp_cp->source_id = mcc_resp_v3->source_id;
51662306a36Sopenharmony_ci		resp_cp->time = mcc_resp_v3->time;
51762306a36Sopenharmony_ci		resp_cp->geo_info = mcc_resp_v3->geo_info;
51862306a36Sopenharmony_ci		resp_cp->n_channels = mcc_resp_v3->n_channels;
51962306a36Sopenharmony_ci		memcpy(resp_cp->channels, mcc_resp_v3->channels,
52062306a36Sopenharmony_ci		       n_channels * sizeof(__le32));
52162306a36Sopenharmony_ci	}
52262306a36Sopenharmony_ci
52362306a36Sopenharmony_ci	status = le32_to_cpu(resp_cp->status);
52462306a36Sopenharmony_ci
52562306a36Sopenharmony_ci	mcc = le16_to_cpu(resp_cp->mcc);
52662306a36Sopenharmony_ci
52762306a36Sopenharmony_ci	/* W/A for a FW/NVM issue - returns 0x00 for the world domain */
52862306a36Sopenharmony_ci	if (mcc == 0) {
52962306a36Sopenharmony_ci		mcc = 0x3030;  /* "00" - world */
53062306a36Sopenharmony_ci		resp_cp->mcc = cpu_to_le16(mcc);
53162306a36Sopenharmony_ci	}
53262306a36Sopenharmony_ci
53362306a36Sopenharmony_ci	IWL_DEBUG_LAR(mvm,
53462306a36Sopenharmony_ci		      "MCC response status: 0x%x. new MCC: 0x%x ('%c%c') n_chans: %d\n",
53562306a36Sopenharmony_ci		      status, mcc, mcc >> 8, mcc & 0xff, n_channels);
53662306a36Sopenharmony_ci
53762306a36Sopenharmony_ciexit:
53862306a36Sopenharmony_ci	iwl_free_resp(&cmd);
53962306a36Sopenharmony_ci	return resp_cp;
54062306a36Sopenharmony_ci}
54162306a36Sopenharmony_ci
54262306a36Sopenharmony_ciint iwl_mvm_init_mcc(struct iwl_mvm *mvm)
54362306a36Sopenharmony_ci{
54462306a36Sopenharmony_ci	bool tlv_lar;
54562306a36Sopenharmony_ci	bool nvm_lar;
54662306a36Sopenharmony_ci	int retval;
54762306a36Sopenharmony_ci	struct ieee80211_regdomain *regd;
54862306a36Sopenharmony_ci	char mcc[3];
54962306a36Sopenharmony_ci
55062306a36Sopenharmony_ci	if (mvm->cfg->nvm_type == IWL_NVM_EXT) {
55162306a36Sopenharmony_ci		tlv_lar = fw_has_capa(&mvm->fw->ucode_capa,
55262306a36Sopenharmony_ci				      IWL_UCODE_TLV_CAPA_LAR_SUPPORT);
55362306a36Sopenharmony_ci		nvm_lar = mvm->nvm_data->lar_enabled;
55462306a36Sopenharmony_ci		if (tlv_lar != nvm_lar)
55562306a36Sopenharmony_ci			IWL_INFO(mvm,
55662306a36Sopenharmony_ci				 "Conflict between TLV & NVM regarding enabling LAR (TLV = %s NVM =%s)\n",
55762306a36Sopenharmony_ci				 tlv_lar ? "enabled" : "disabled",
55862306a36Sopenharmony_ci				 nvm_lar ? "enabled" : "disabled");
55962306a36Sopenharmony_ci	}
56062306a36Sopenharmony_ci
56162306a36Sopenharmony_ci	if (!iwl_mvm_is_lar_supported(mvm))
56262306a36Sopenharmony_ci		return 0;
56362306a36Sopenharmony_ci
56462306a36Sopenharmony_ci	/*
56562306a36Sopenharmony_ci	 * try to replay the last set MCC to FW. If it doesn't exist,
56662306a36Sopenharmony_ci	 * queue an update to cfg80211 to retrieve the default alpha2 from FW.
56762306a36Sopenharmony_ci	 */
56862306a36Sopenharmony_ci	retval = iwl_mvm_init_fw_regd(mvm);
56962306a36Sopenharmony_ci	if (retval != -ENOENT)
57062306a36Sopenharmony_ci		return retval;
57162306a36Sopenharmony_ci
57262306a36Sopenharmony_ci	/*
57362306a36Sopenharmony_ci	 * Driver regulatory hint for initial update, this also informs the
57462306a36Sopenharmony_ci	 * firmware we support wifi location updates.
57562306a36Sopenharmony_ci	 * Disallow scans that might crash the FW while the LAR regdomain
57662306a36Sopenharmony_ci	 * is not set.
57762306a36Sopenharmony_ci	 */
57862306a36Sopenharmony_ci	mvm->lar_regdom_set = false;
57962306a36Sopenharmony_ci
58062306a36Sopenharmony_ci	regd = iwl_mvm_get_current_regdomain(mvm, NULL);
58162306a36Sopenharmony_ci	if (IS_ERR_OR_NULL(regd))
58262306a36Sopenharmony_ci		return -EIO;
58362306a36Sopenharmony_ci
58462306a36Sopenharmony_ci	if (iwl_mvm_is_wifi_mcc_supported(mvm) &&
58562306a36Sopenharmony_ci	    !iwl_acpi_get_mcc(mvm->dev, mcc)) {
58662306a36Sopenharmony_ci		kfree(regd);
58762306a36Sopenharmony_ci		regd = iwl_mvm_get_regdomain(mvm->hw->wiphy, mcc,
58862306a36Sopenharmony_ci					     MCC_SOURCE_BIOS, NULL);
58962306a36Sopenharmony_ci		if (IS_ERR_OR_NULL(regd))
59062306a36Sopenharmony_ci			return -EIO;
59162306a36Sopenharmony_ci	}
59262306a36Sopenharmony_ci
59362306a36Sopenharmony_ci	retval = regulatory_set_wiphy_regd_sync(mvm->hw->wiphy, regd);
59462306a36Sopenharmony_ci	kfree(regd);
59562306a36Sopenharmony_ci	return retval;
59662306a36Sopenharmony_ci}
59762306a36Sopenharmony_ci
59862306a36Sopenharmony_civoid iwl_mvm_rx_chub_update_mcc(struct iwl_mvm *mvm,
59962306a36Sopenharmony_ci				struct iwl_rx_cmd_buffer *rxb)
60062306a36Sopenharmony_ci{
60162306a36Sopenharmony_ci	struct iwl_rx_packet *pkt = rxb_addr(rxb);
60262306a36Sopenharmony_ci	struct iwl_mcc_chub_notif *notif = (void *)pkt->data;
60362306a36Sopenharmony_ci	enum iwl_mcc_source src;
60462306a36Sopenharmony_ci	char mcc[3];
60562306a36Sopenharmony_ci	struct ieee80211_regdomain *regd;
60662306a36Sopenharmony_ci	int wgds_tbl_idx;
60762306a36Sopenharmony_ci
60862306a36Sopenharmony_ci	lockdep_assert_held(&mvm->mutex);
60962306a36Sopenharmony_ci
61062306a36Sopenharmony_ci	if (iwl_mvm_is_vif_assoc(mvm) && notif->source_id == MCC_SOURCE_WIFI) {
61162306a36Sopenharmony_ci		IWL_DEBUG_LAR(mvm, "Ignore mcc update while associated\n");
61262306a36Sopenharmony_ci		return;
61362306a36Sopenharmony_ci	}
61462306a36Sopenharmony_ci
61562306a36Sopenharmony_ci	if (WARN_ON_ONCE(!iwl_mvm_is_lar_supported(mvm)))
61662306a36Sopenharmony_ci		return;
61762306a36Sopenharmony_ci
61862306a36Sopenharmony_ci	mcc[0] = le16_to_cpu(notif->mcc) >> 8;
61962306a36Sopenharmony_ci	mcc[1] = le16_to_cpu(notif->mcc) & 0xff;
62062306a36Sopenharmony_ci	mcc[2] = '\0';
62162306a36Sopenharmony_ci	src = notif->source_id;
62262306a36Sopenharmony_ci
62362306a36Sopenharmony_ci	IWL_DEBUG_LAR(mvm,
62462306a36Sopenharmony_ci		      "RX: received chub update mcc cmd (mcc '%s' src %d)\n",
62562306a36Sopenharmony_ci		      mcc, src);
62662306a36Sopenharmony_ci	regd = iwl_mvm_get_regdomain(mvm->hw->wiphy, mcc, src, NULL);
62762306a36Sopenharmony_ci	if (IS_ERR_OR_NULL(regd))
62862306a36Sopenharmony_ci		return;
62962306a36Sopenharmony_ci
63062306a36Sopenharmony_ci	wgds_tbl_idx = iwl_mvm_get_sar_geo_profile(mvm);
63162306a36Sopenharmony_ci	if (wgds_tbl_idx < 1)
63262306a36Sopenharmony_ci		IWL_DEBUG_INFO(mvm,
63362306a36Sopenharmony_ci			       "SAR WGDS is disabled or error received (%d)\n",
63462306a36Sopenharmony_ci			       wgds_tbl_idx);
63562306a36Sopenharmony_ci	else
63662306a36Sopenharmony_ci		IWL_DEBUG_INFO(mvm, "SAR WGDS: geo profile %d is configured\n",
63762306a36Sopenharmony_ci			       wgds_tbl_idx);
63862306a36Sopenharmony_ci
63962306a36Sopenharmony_ci	regulatory_set_wiphy_regd(mvm->hw->wiphy, regd);
64062306a36Sopenharmony_ci	kfree(regd);
64162306a36Sopenharmony_ci}
642