18c2ecf20Sopenharmony_ci/****************************************************************************** 28c2ecf20Sopenharmony_ci * 38c2ecf20Sopenharmony_ci * This file is provided under a dual BSD/GPLv2 license. When using or 48c2ecf20Sopenharmony_ci * redistributing this file, you may do so under either license. 58c2ecf20Sopenharmony_ci * 68c2ecf20Sopenharmony_ci * GPL LICENSE SUMMARY 78c2ecf20Sopenharmony_ci * 88c2ecf20Sopenharmony_ci * Copyright(c) 2012 - 2014 Intel Corporation. All rights reserved. 98c2ecf20Sopenharmony_ci * Copyright(c) 2013 - 2015 Intel Mobile Communications GmbH 108c2ecf20Sopenharmony_ci * Copyright(c) 2016 - 2017 Intel Deutschland GmbH 118c2ecf20Sopenharmony_ci * Copyright(c) 2018 Intel Corporation 128c2ecf20Sopenharmony_ci * 138c2ecf20Sopenharmony_ci * This program is free software; you can redistribute it and/or modify 148c2ecf20Sopenharmony_ci * it under the terms of version 2 of the GNU General Public License as 158c2ecf20Sopenharmony_ci * published by the Free Software Foundation. 168c2ecf20Sopenharmony_ci * 178c2ecf20Sopenharmony_ci * This program is distributed in the hope that it will be useful, but 188c2ecf20Sopenharmony_ci * WITHOUT ANY WARRANTY; without even the implied warranty of 198c2ecf20Sopenharmony_ci * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 208c2ecf20Sopenharmony_ci * General Public License for more details. 218c2ecf20Sopenharmony_ci * 228c2ecf20Sopenharmony_ci * The full GNU General Public License is included in this distribution 238c2ecf20Sopenharmony_ci * in the file called COPYING. 248c2ecf20Sopenharmony_ci * 258c2ecf20Sopenharmony_ci * Contact Information: 268c2ecf20Sopenharmony_ci * Intel Linux Wireless <linuxwifi@intel.com> 278c2ecf20Sopenharmony_ci * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 288c2ecf20Sopenharmony_ci * 298c2ecf20Sopenharmony_ci * BSD LICENSE 308c2ecf20Sopenharmony_ci * 318c2ecf20Sopenharmony_ci * Copyright(c) 2012 - 2014 Intel Corporation. All rights reserved. 328c2ecf20Sopenharmony_ci * Copyright(c) 2013 - 2015 Intel Mobile Communications GmbH 338c2ecf20Sopenharmony_ci * Copyright(c) 2016 - 2017 Intel Deutschland GmbH 348c2ecf20Sopenharmony_ci * Copyright(c) 2018 Intel Corporation 358c2ecf20Sopenharmony_ci * All rights reserved. 368c2ecf20Sopenharmony_ci * 378c2ecf20Sopenharmony_ci * Redistribution and use in source and binary forms, with or without 388c2ecf20Sopenharmony_ci * modification, are permitted provided that the following conditions 398c2ecf20Sopenharmony_ci * are met: 408c2ecf20Sopenharmony_ci * 418c2ecf20Sopenharmony_ci * * Redistributions of source code must retain the above copyright 428c2ecf20Sopenharmony_ci * notice, this list of conditions and the following disclaimer. 438c2ecf20Sopenharmony_ci * * Redistributions in binary form must reproduce the above copyright 448c2ecf20Sopenharmony_ci * notice, this list of conditions and the following disclaimer in 458c2ecf20Sopenharmony_ci * the documentation and/or other materials provided with the 468c2ecf20Sopenharmony_ci * distribution. 478c2ecf20Sopenharmony_ci * * Neither the name Intel Corporation nor the names of its 488c2ecf20Sopenharmony_ci * contributors may be used to endorse or promote products derived 498c2ecf20Sopenharmony_ci * from this software without specific prior written permission. 508c2ecf20Sopenharmony_ci * 518c2ecf20Sopenharmony_ci * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 528c2ecf20Sopenharmony_ci * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 538c2ecf20Sopenharmony_ci * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 548c2ecf20Sopenharmony_ci * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 558c2ecf20Sopenharmony_ci * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 568c2ecf20Sopenharmony_ci * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 578c2ecf20Sopenharmony_ci * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 588c2ecf20Sopenharmony_ci * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 598c2ecf20Sopenharmony_ci * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 608c2ecf20Sopenharmony_ci * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 618c2ecf20Sopenharmony_ci * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 628c2ecf20Sopenharmony_ci * 638c2ecf20Sopenharmony_ci *****************************************************************************/ 648c2ecf20Sopenharmony_ci#include <linux/firmware.h> 658c2ecf20Sopenharmony_ci#include <linux/rtnetlink.h> 668c2ecf20Sopenharmony_ci#include "iwl-trans.h" 678c2ecf20Sopenharmony_ci#include "iwl-csr.h" 688c2ecf20Sopenharmony_ci#include "mvm.h" 698c2ecf20Sopenharmony_ci#include "iwl-eeprom-parse.h" 708c2ecf20Sopenharmony_ci#include "iwl-eeprom-read.h" 718c2ecf20Sopenharmony_ci#include "iwl-nvm-parse.h" 728c2ecf20Sopenharmony_ci#include "iwl-prph.h" 738c2ecf20Sopenharmony_ci#include "fw/acpi.h" 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_ci/* Default NVM size to read */ 768c2ecf20Sopenharmony_ci#define IWL_NVM_DEFAULT_CHUNK_SIZE (2 * 1024) 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_ci#define NVM_WRITE_OPCODE 1 798c2ecf20Sopenharmony_ci#define NVM_READ_OPCODE 0 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_ci/* load nvm chunk response */ 828c2ecf20Sopenharmony_cienum { 838c2ecf20Sopenharmony_ci READ_NVM_CHUNK_SUCCEED = 0, 848c2ecf20Sopenharmony_ci READ_NVM_CHUNK_NOT_VALID_ADDRESS = 1 858c2ecf20Sopenharmony_ci}; 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci/* 888c2ecf20Sopenharmony_ci * prepare the NVM host command w/ the pointers to the nvm buffer 898c2ecf20Sopenharmony_ci * and send it to fw 908c2ecf20Sopenharmony_ci */ 918c2ecf20Sopenharmony_cistatic int iwl_nvm_write_chunk(struct iwl_mvm *mvm, u16 section, 928c2ecf20Sopenharmony_ci u16 offset, u16 length, const u8 *data) 938c2ecf20Sopenharmony_ci{ 948c2ecf20Sopenharmony_ci struct iwl_nvm_access_cmd nvm_access_cmd = { 958c2ecf20Sopenharmony_ci .offset = cpu_to_le16(offset), 968c2ecf20Sopenharmony_ci .length = cpu_to_le16(length), 978c2ecf20Sopenharmony_ci .type = cpu_to_le16(section), 988c2ecf20Sopenharmony_ci .op_code = NVM_WRITE_OPCODE, 998c2ecf20Sopenharmony_ci }; 1008c2ecf20Sopenharmony_ci struct iwl_host_cmd cmd = { 1018c2ecf20Sopenharmony_ci .id = NVM_ACCESS_CMD, 1028c2ecf20Sopenharmony_ci .len = { sizeof(struct iwl_nvm_access_cmd), length }, 1038c2ecf20Sopenharmony_ci .flags = CMD_WANT_SKB | CMD_SEND_IN_RFKILL, 1048c2ecf20Sopenharmony_ci .data = { &nvm_access_cmd, data }, 1058c2ecf20Sopenharmony_ci /* data may come from vmalloc, so use _DUP */ 1068c2ecf20Sopenharmony_ci .dataflags = { 0, IWL_HCMD_DFL_DUP }, 1078c2ecf20Sopenharmony_ci }; 1088c2ecf20Sopenharmony_ci struct iwl_rx_packet *pkt; 1098c2ecf20Sopenharmony_ci struct iwl_nvm_access_resp *nvm_resp; 1108c2ecf20Sopenharmony_ci int ret; 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_ci ret = iwl_mvm_send_cmd(mvm, &cmd); 1138c2ecf20Sopenharmony_ci if (ret) 1148c2ecf20Sopenharmony_ci return ret; 1158c2ecf20Sopenharmony_ci 1168c2ecf20Sopenharmony_ci pkt = cmd.resp_pkt; 1178c2ecf20Sopenharmony_ci /* Extract & check NVM write response */ 1188c2ecf20Sopenharmony_ci nvm_resp = (void *)pkt->data; 1198c2ecf20Sopenharmony_ci if (le16_to_cpu(nvm_resp->status) != READ_NVM_CHUNK_SUCCEED) { 1208c2ecf20Sopenharmony_ci IWL_ERR(mvm, 1218c2ecf20Sopenharmony_ci "NVM access write command failed for section %u (status = 0x%x)\n", 1228c2ecf20Sopenharmony_ci section, le16_to_cpu(nvm_resp->status)); 1238c2ecf20Sopenharmony_ci ret = -EIO; 1248c2ecf20Sopenharmony_ci } 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_ci iwl_free_resp(&cmd); 1278c2ecf20Sopenharmony_ci return ret; 1288c2ecf20Sopenharmony_ci} 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_cistatic int iwl_nvm_read_chunk(struct iwl_mvm *mvm, u16 section, 1318c2ecf20Sopenharmony_ci u16 offset, u16 length, u8 *data) 1328c2ecf20Sopenharmony_ci{ 1338c2ecf20Sopenharmony_ci struct iwl_nvm_access_cmd nvm_access_cmd = { 1348c2ecf20Sopenharmony_ci .offset = cpu_to_le16(offset), 1358c2ecf20Sopenharmony_ci .length = cpu_to_le16(length), 1368c2ecf20Sopenharmony_ci .type = cpu_to_le16(section), 1378c2ecf20Sopenharmony_ci .op_code = NVM_READ_OPCODE, 1388c2ecf20Sopenharmony_ci }; 1398c2ecf20Sopenharmony_ci struct iwl_nvm_access_resp *nvm_resp; 1408c2ecf20Sopenharmony_ci struct iwl_rx_packet *pkt; 1418c2ecf20Sopenharmony_ci struct iwl_host_cmd cmd = { 1428c2ecf20Sopenharmony_ci .id = NVM_ACCESS_CMD, 1438c2ecf20Sopenharmony_ci .flags = CMD_WANT_SKB | CMD_SEND_IN_RFKILL, 1448c2ecf20Sopenharmony_ci .data = { &nvm_access_cmd, }, 1458c2ecf20Sopenharmony_ci }; 1468c2ecf20Sopenharmony_ci int ret, bytes_read, offset_read; 1478c2ecf20Sopenharmony_ci u8 *resp_data; 1488c2ecf20Sopenharmony_ci 1498c2ecf20Sopenharmony_ci cmd.len[0] = sizeof(struct iwl_nvm_access_cmd); 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_ci ret = iwl_mvm_send_cmd(mvm, &cmd); 1528c2ecf20Sopenharmony_ci if (ret) 1538c2ecf20Sopenharmony_ci return ret; 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_ci pkt = cmd.resp_pkt; 1568c2ecf20Sopenharmony_ci 1578c2ecf20Sopenharmony_ci /* Extract NVM response */ 1588c2ecf20Sopenharmony_ci nvm_resp = (void *)pkt->data; 1598c2ecf20Sopenharmony_ci ret = le16_to_cpu(nvm_resp->status); 1608c2ecf20Sopenharmony_ci bytes_read = le16_to_cpu(nvm_resp->length); 1618c2ecf20Sopenharmony_ci offset_read = le16_to_cpu(nvm_resp->offset); 1628c2ecf20Sopenharmony_ci resp_data = nvm_resp->data; 1638c2ecf20Sopenharmony_ci if (ret) { 1648c2ecf20Sopenharmony_ci if ((offset != 0) && 1658c2ecf20Sopenharmony_ci (ret == READ_NVM_CHUNK_NOT_VALID_ADDRESS)) { 1668c2ecf20Sopenharmony_ci /* 1678c2ecf20Sopenharmony_ci * meaning of NOT_VALID_ADDRESS: 1688c2ecf20Sopenharmony_ci * driver try to read chunk from address that is 1698c2ecf20Sopenharmony_ci * multiple of 2K and got an error since addr is empty. 1708c2ecf20Sopenharmony_ci * meaning of (offset != 0): driver already 1718c2ecf20Sopenharmony_ci * read valid data from another chunk so this case 1728c2ecf20Sopenharmony_ci * is not an error. 1738c2ecf20Sopenharmony_ci */ 1748c2ecf20Sopenharmony_ci IWL_DEBUG_EEPROM(mvm->trans->dev, 1758c2ecf20Sopenharmony_ci "NVM access command failed on offset 0x%x since that section size is multiple 2K\n", 1768c2ecf20Sopenharmony_ci offset); 1778c2ecf20Sopenharmony_ci ret = 0; 1788c2ecf20Sopenharmony_ci } else { 1798c2ecf20Sopenharmony_ci IWL_DEBUG_EEPROM(mvm->trans->dev, 1808c2ecf20Sopenharmony_ci "NVM access command failed with status %d (device: %s)\n", 1818c2ecf20Sopenharmony_ci ret, mvm->trans->name); 1828c2ecf20Sopenharmony_ci ret = -ENODATA; 1838c2ecf20Sopenharmony_ci } 1848c2ecf20Sopenharmony_ci goto exit; 1858c2ecf20Sopenharmony_ci } 1868c2ecf20Sopenharmony_ci 1878c2ecf20Sopenharmony_ci if (offset_read != offset) { 1888c2ecf20Sopenharmony_ci IWL_ERR(mvm, "NVM ACCESS response with invalid offset %d\n", 1898c2ecf20Sopenharmony_ci offset_read); 1908c2ecf20Sopenharmony_ci ret = -EINVAL; 1918c2ecf20Sopenharmony_ci goto exit; 1928c2ecf20Sopenharmony_ci } 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_ci /* Write data to NVM */ 1958c2ecf20Sopenharmony_ci memcpy(data + offset, resp_data, bytes_read); 1968c2ecf20Sopenharmony_ci ret = bytes_read; 1978c2ecf20Sopenharmony_ci 1988c2ecf20Sopenharmony_ciexit: 1998c2ecf20Sopenharmony_ci iwl_free_resp(&cmd); 2008c2ecf20Sopenharmony_ci return ret; 2018c2ecf20Sopenharmony_ci} 2028c2ecf20Sopenharmony_ci 2038c2ecf20Sopenharmony_cistatic int iwl_nvm_write_section(struct iwl_mvm *mvm, u16 section, 2048c2ecf20Sopenharmony_ci const u8 *data, u16 length) 2058c2ecf20Sopenharmony_ci{ 2068c2ecf20Sopenharmony_ci int offset = 0; 2078c2ecf20Sopenharmony_ci 2088c2ecf20Sopenharmony_ci /* copy data in chunks of 2k (and remainder if any) */ 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_ci while (offset < length) { 2118c2ecf20Sopenharmony_ci int chunk_size, ret; 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_ci chunk_size = min(IWL_NVM_DEFAULT_CHUNK_SIZE, 2148c2ecf20Sopenharmony_ci length - offset); 2158c2ecf20Sopenharmony_ci 2168c2ecf20Sopenharmony_ci ret = iwl_nvm_write_chunk(mvm, section, offset, 2178c2ecf20Sopenharmony_ci chunk_size, data + offset); 2188c2ecf20Sopenharmony_ci if (ret < 0) 2198c2ecf20Sopenharmony_ci return ret; 2208c2ecf20Sopenharmony_ci 2218c2ecf20Sopenharmony_ci offset += chunk_size; 2228c2ecf20Sopenharmony_ci } 2238c2ecf20Sopenharmony_ci 2248c2ecf20Sopenharmony_ci return 0; 2258c2ecf20Sopenharmony_ci} 2268c2ecf20Sopenharmony_ci 2278c2ecf20Sopenharmony_ci/* 2288c2ecf20Sopenharmony_ci * Reads an NVM section completely. 2298c2ecf20Sopenharmony_ci * NICs prior to 7000 family doesn't have a real NVM, but just read 2308c2ecf20Sopenharmony_ci * section 0 which is the EEPROM. Because the EEPROM reading is unlimited 2318c2ecf20Sopenharmony_ci * by uCode, we need to manually check in this case that we don't 2328c2ecf20Sopenharmony_ci * overflow and try to read more than the EEPROM size. 2338c2ecf20Sopenharmony_ci * For 7000 family NICs, we supply the maximal size we can read, and 2348c2ecf20Sopenharmony_ci * the uCode fills the response with as much data as we can, 2358c2ecf20Sopenharmony_ci * without overflowing, so no check is needed. 2368c2ecf20Sopenharmony_ci */ 2378c2ecf20Sopenharmony_cistatic int iwl_nvm_read_section(struct iwl_mvm *mvm, u16 section, 2388c2ecf20Sopenharmony_ci u8 *data, u32 size_read) 2398c2ecf20Sopenharmony_ci{ 2408c2ecf20Sopenharmony_ci u16 length, offset = 0; 2418c2ecf20Sopenharmony_ci int ret; 2428c2ecf20Sopenharmony_ci 2438c2ecf20Sopenharmony_ci /* Set nvm section read length */ 2448c2ecf20Sopenharmony_ci length = IWL_NVM_DEFAULT_CHUNK_SIZE; 2458c2ecf20Sopenharmony_ci 2468c2ecf20Sopenharmony_ci ret = length; 2478c2ecf20Sopenharmony_ci 2488c2ecf20Sopenharmony_ci /* Read the NVM until exhausted (reading less than requested) */ 2498c2ecf20Sopenharmony_ci while (ret == length) { 2508c2ecf20Sopenharmony_ci /* Check no memory assumptions fail and cause an overflow */ 2518c2ecf20Sopenharmony_ci if ((size_read + offset + length) > 2528c2ecf20Sopenharmony_ci mvm->trans->trans_cfg->base_params->eeprom_size) { 2538c2ecf20Sopenharmony_ci IWL_ERR(mvm, "EEPROM size is too small for NVM\n"); 2548c2ecf20Sopenharmony_ci return -ENOBUFS; 2558c2ecf20Sopenharmony_ci } 2568c2ecf20Sopenharmony_ci 2578c2ecf20Sopenharmony_ci ret = iwl_nvm_read_chunk(mvm, section, offset, length, data); 2588c2ecf20Sopenharmony_ci if (ret < 0) { 2598c2ecf20Sopenharmony_ci IWL_DEBUG_EEPROM(mvm->trans->dev, 2608c2ecf20Sopenharmony_ci "Cannot read NVM from section %d offset %d, length %d\n", 2618c2ecf20Sopenharmony_ci section, offset, length); 2628c2ecf20Sopenharmony_ci return ret; 2638c2ecf20Sopenharmony_ci } 2648c2ecf20Sopenharmony_ci offset += ret; 2658c2ecf20Sopenharmony_ci } 2668c2ecf20Sopenharmony_ci 2678c2ecf20Sopenharmony_ci iwl_nvm_fixups(mvm->trans->hw_id, section, data, offset); 2688c2ecf20Sopenharmony_ci 2698c2ecf20Sopenharmony_ci IWL_DEBUG_EEPROM(mvm->trans->dev, 2708c2ecf20Sopenharmony_ci "NVM section %d read completed\n", section); 2718c2ecf20Sopenharmony_ci return offset; 2728c2ecf20Sopenharmony_ci} 2738c2ecf20Sopenharmony_ci 2748c2ecf20Sopenharmony_cistatic struct iwl_nvm_data * 2758c2ecf20Sopenharmony_ciiwl_parse_nvm_sections(struct iwl_mvm *mvm) 2768c2ecf20Sopenharmony_ci{ 2778c2ecf20Sopenharmony_ci struct iwl_nvm_section *sections = mvm->nvm_sections; 2788c2ecf20Sopenharmony_ci const __be16 *hw; 2798c2ecf20Sopenharmony_ci const __le16 *sw, *calib, *regulatory, *mac_override, *phy_sku; 2808c2ecf20Sopenharmony_ci int regulatory_type; 2818c2ecf20Sopenharmony_ci 2828c2ecf20Sopenharmony_ci /* Checking for required sections */ 2838c2ecf20Sopenharmony_ci if (mvm->trans->cfg->nvm_type == IWL_NVM) { 2848c2ecf20Sopenharmony_ci if (!mvm->nvm_sections[NVM_SECTION_TYPE_SW].data || 2858c2ecf20Sopenharmony_ci !mvm->nvm_sections[mvm->cfg->nvm_hw_section_num].data) { 2868c2ecf20Sopenharmony_ci IWL_ERR(mvm, "Can't parse empty OTP/NVM sections\n"); 2878c2ecf20Sopenharmony_ci return NULL; 2888c2ecf20Sopenharmony_ci } 2898c2ecf20Sopenharmony_ci } else { 2908c2ecf20Sopenharmony_ci if (mvm->trans->cfg->nvm_type == IWL_NVM_SDP) 2918c2ecf20Sopenharmony_ci regulatory_type = NVM_SECTION_TYPE_REGULATORY_SDP; 2928c2ecf20Sopenharmony_ci else 2938c2ecf20Sopenharmony_ci regulatory_type = NVM_SECTION_TYPE_REGULATORY; 2948c2ecf20Sopenharmony_ci 2958c2ecf20Sopenharmony_ci /* SW and REGULATORY sections are mandatory */ 2968c2ecf20Sopenharmony_ci if (!mvm->nvm_sections[NVM_SECTION_TYPE_SW].data || 2978c2ecf20Sopenharmony_ci !mvm->nvm_sections[regulatory_type].data) { 2988c2ecf20Sopenharmony_ci IWL_ERR(mvm, 2998c2ecf20Sopenharmony_ci "Can't parse empty family 8000 OTP/NVM sections\n"); 3008c2ecf20Sopenharmony_ci return NULL; 3018c2ecf20Sopenharmony_ci } 3028c2ecf20Sopenharmony_ci /* MAC_OVERRIDE or at least HW section must exist */ 3038c2ecf20Sopenharmony_ci if (!mvm->nvm_sections[mvm->cfg->nvm_hw_section_num].data && 3048c2ecf20Sopenharmony_ci !mvm->nvm_sections[NVM_SECTION_TYPE_MAC_OVERRIDE].data) { 3058c2ecf20Sopenharmony_ci IWL_ERR(mvm, 3068c2ecf20Sopenharmony_ci "Can't parse mac_address, empty sections\n"); 3078c2ecf20Sopenharmony_ci return NULL; 3088c2ecf20Sopenharmony_ci } 3098c2ecf20Sopenharmony_ci 3108c2ecf20Sopenharmony_ci /* PHY_SKU section is mandatory in B0 */ 3118c2ecf20Sopenharmony_ci if (mvm->trans->cfg->nvm_type == IWL_NVM_EXT && 3128c2ecf20Sopenharmony_ci !mvm->nvm_sections[NVM_SECTION_TYPE_PHY_SKU].data) { 3138c2ecf20Sopenharmony_ci IWL_ERR(mvm, 3148c2ecf20Sopenharmony_ci "Can't parse phy_sku in B0, empty sections\n"); 3158c2ecf20Sopenharmony_ci return NULL; 3168c2ecf20Sopenharmony_ci } 3178c2ecf20Sopenharmony_ci } 3188c2ecf20Sopenharmony_ci 3198c2ecf20Sopenharmony_ci hw = (const __be16 *)sections[mvm->cfg->nvm_hw_section_num].data; 3208c2ecf20Sopenharmony_ci sw = (const __le16 *)sections[NVM_SECTION_TYPE_SW].data; 3218c2ecf20Sopenharmony_ci calib = (const __le16 *)sections[NVM_SECTION_TYPE_CALIBRATION].data; 3228c2ecf20Sopenharmony_ci mac_override = 3238c2ecf20Sopenharmony_ci (const __le16 *)sections[NVM_SECTION_TYPE_MAC_OVERRIDE].data; 3248c2ecf20Sopenharmony_ci phy_sku = (const __le16 *)sections[NVM_SECTION_TYPE_PHY_SKU].data; 3258c2ecf20Sopenharmony_ci 3268c2ecf20Sopenharmony_ci regulatory = mvm->trans->cfg->nvm_type == IWL_NVM_SDP ? 3278c2ecf20Sopenharmony_ci (const __le16 *)sections[NVM_SECTION_TYPE_REGULATORY_SDP].data : 3288c2ecf20Sopenharmony_ci (const __le16 *)sections[NVM_SECTION_TYPE_REGULATORY].data; 3298c2ecf20Sopenharmony_ci 3308c2ecf20Sopenharmony_ci return iwl_parse_nvm_data(mvm->trans, mvm->cfg, mvm->fw, hw, sw, calib, 3318c2ecf20Sopenharmony_ci regulatory, mac_override, phy_sku, 3328c2ecf20Sopenharmony_ci mvm->fw->valid_tx_ant, mvm->fw->valid_rx_ant); 3338c2ecf20Sopenharmony_ci} 3348c2ecf20Sopenharmony_ci 3358c2ecf20Sopenharmony_ci/* Loads the NVM data stored in mvm->nvm_sections into the NIC */ 3368c2ecf20Sopenharmony_ciint iwl_mvm_load_nvm_to_nic(struct iwl_mvm *mvm) 3378c2ecf20Sopenharmony_ci{ 3388c2ecf20Sopenharmony_ci int i, ret = 0; 3398c2ecf20Sopenharmony_ci struct iwl_nvm_section *sections = mvm->nvm_sections; 3408c2ecf20Sopenharmony_ci 3418c2ecf20Sopenharmony_ci IWL_DEBUG_EEPROM(mvm->trans->dev, "'Write to NVM\n"); 3428c2ecf20Sopenharmony_ci 3438c2ecf20Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(mvm->nvm_sections); i++) { 3448c2ecf20Sopenharmony_ci if (!mvm->nvm_sections[i].data || !mvm->nvm_sections[i].length) 3458c2ecf20Sopenharmony_ci continue; 3468c2ecf20Sopenharmony_ci ret = iwl_nvm_write_section(mvm, i, sections[i].data, 3478c2ecf20Sopenharmony_ci sections[i].length); 3488c2ecf20Sopenharmony_ci if (ret < 0) { 3498c2ecf20Sopenharmony_ci IWL_ERR(mvm, "iwl_mvm_send_cmd failed: %d\n", ret); 3508c2ecf20Sopenharmony_ci break; 3518c2ecf20Sopenharmony_ci } 3528c2ecf20Sopenharmony_ci } 3538c2ecf20Sopenharmony_ci return ret; 3548c2ecf20Sopenharmony_ci} 3558c2ecf20Sopenharmony_ci 3568c2ecf20Sopenharmony_ciint iwl_nvm_init(struct iwl_mvm *mvm) 3578c2ecf20Sopenharmony_ci{ 3588c2ecf20Sopenharmony_ci int ret, section; 3598c2ecf20Sopenharmony_ci u32 size_read = 0; 3608c2ecf20Sopenharmony_ci u8 *nvm_buffer, *temp; 3618c2ecf20Sopenharmony_ci const char *nvm_file_C = mvm->cfg->default_nvm_file_C_step; 3628c2ecf20Sopenharmony_ci 3638c2ecf20Sopenharmony_ci if (WARN_ON_ONCE(mvm->cfg->nvm_hw_section_num >= NVM_MAX_NUM_SECTIONS)) 3648c2ecf20Sopenharmony_ci return -EINVAL; 3658c2ecf20Sopenharmony_ci 3668c2ecf20Sopenharmony_ci /* load NVM values from nic */ 3678c2ecf20Sopenharmony_ci /* Read From FW NVM */ 3688c2ecf20Sopenharmony_ci IWL_DEBUG_EEPROM(mvm->trans->dev, "Read from NVM\n"); 3698c2ecf20Sopenharmony_ci 3708c2ecf20Sopenharmony_ci nvm_buffer = kmalloc(mvm->trans->trans_cfg->base_params->eeprom_size, 3718c2ecf20Sopenharmony_ci GFP_KERNEL); 3728c2ecf20Sopenharmony_ci if (!nvm_buffer) 3738c2ecf20Sopenharmony_ci return -ENOMEM; 3748c2ecf20Sopenharmony_ci for (section = 0; section < NVM_MAX_NUM_SECTIONS; section++) { 3758c2ecf20Sopenharmony_ci /* we override the constness for initial read */ 3768c2ecf20Sopenharmony_ci ret = iwl_nvm_read_section(mvm, section, nvm_buffer, 3778c2ecf20Sopenharmony_ci size_read); 3788c2ecf20Sopenharmony_ci if (ret == -ENODATA) { 3798c2ecf20Sopenharmony_ci ret = 0; 3808c2ecf20Sopenharmony_ci continue; 3818c2ecf20Sopenharmony_ci } 3828c2ecf20Sopenharmony_ci if (ret < 0) 3838c2ecf20Sopenharmony_ci break; 3848c2ecf20Sopenharmony_ci size_read += ret; 3858c2ecf20Sopenharmony_ci temp = kmemdup(nvm_buffer, ret, GFP_KERNEL); 3868c2ecf20Sopenharmony_ci if (!temp) { 3878c2ecf20Sopenharmony_ci ret = -ENOMEM; 3888c2ecf20Sopenharmony_ci break; 3898c2ecf20Sopenharmony_ci } 3908c2ecf20Sopenharmony_ci 3918c2ecf20Sopenharmony_ci iwl_nvm_fixups(mvm->trans->hw_id, section, temp, ret); 3928c2ecf20Sopenharmony_ci 3938c2ecf20Sopenharmony_ci mvm->nvm_sections[section].data = temp; 3948c2ecf20Sopenharmony_ci mvm->nvm_sections[section].length = ret; 3958c2ecf20Sopenharmony_ci 3968c2ecf20Sopenharmony_ci#ifdef CONFIG_IWLWIFI_DEBUGFS 3978c2ecf20Sopenharmony_ci switch (section) { 3988c2ecf20Sopenharmony_ci case NVM_SECTION_TYPE_SW: 3998c2ecf20Sopenharmony_ci mvm->nvm_sw_blob.data = temp; 4008c2ecf20Sopenharmony_ci mvm->nvm_sw_blob.size = ret; 4018c2ecf20Sopenharmony_ci break; 4028c2ecf20Sopenharmony_ci case NVM_SECTION_TYPE_CALIBRATION: 4038c2ecf20Sopenharmony_ci mvm->nvm_calib_blob.data = temp; 4048c2ecf20Sopenharmony_ci mvm->nvm_calib_blob.size = ret; 4058c2ecf20Sopenharmony_ci break; 4068c2ecf20Sopenharmony_ci case NVM_SECTION_TYPE_PRODUCTION: 4078c2ecf20Sopenharmony_ci mvm->nvm_prod_blob.data = temp; 4088c2ecf20Sopenharmony_ci mvm->nvm_prod_blob.size = ret; 4098c2ecf20Sopenharmony_ci break; 4108c2ecf20Sopenharmony_ci case NVM_SECTION_TYPE_PHY_SKU: 4118c2ecf20Sopenharmony_ci mvm->nvm_phy_sku_blob.data = temp; 4128c2ecf20Sopenharmony_ci mvm->nvm_phy_sku_blob.size = ret; 4138c2ecf20Sopenharmony_ci break; 4148c2ecf20Sopenharmony_ci case NVM_SECTION_TYPE_REGULATORY_SDP: 4158c2ecf20Sopenharmony_ci case NVM_SECTION_TYPE_REGULATORY: 4168c2ecf20Sopenharmony_ci mvm->nvm_reg_blob.data = temp; 4178c2ecf20Sopenharmony_ci mvm->nvm_reg_blob.size = ret; 4188c2ecf20Sopenharmony_ci break; 4198c2ecf20Sopenharmony_ci default: 4208c2ecf20Sopenharmony_ci if (section == mvm->cfg->nvm_hw_section_num) { 4218c2ecf20Sopenharmony_ci mvm->nvm_hw_blob.data = temp; 4228c2ecf20Sopenharmony_ci mvm->nvm_hw_blob.size = ret; 4238c2ecf20Sopenharmony_ci break; 4248c2ecf20Sopenharmony_ci } 4258c2ecf20Sopenharmony_ci } 4268c2ecf20Sopenharmony_ci#endif 4278c2ecf20Sopenharmony_ci } 4288c2ecf20Sopenharmony_ci if (!size_read) 4298c2ecf20Sopenharmony_ci IWL_ERR(mvm, "OTP is blank\n"); 4308c2ecf20Sopenharmony_ci kfree(nvm_buffer); 4318c2ecf20Sopenharmony_ci 4328c2ecf20Sopenharmony_ci /* Only if PNVM selected in the mod param - load external NVM */ 4338c2ecf20Sopenharmony_ci if (mvm->nvm_file_name) { 4348c2ecf20Sopenharmony_ci /* read External NVM file from the mod param */ 4358c2ecf20Sopenharmony_ci ret = iwl_read_external_nvm(mvm->trans, mvm->nvm_file_name, 4368c2ecf20Sopenharmony_ci mvm->nvm_sections); 4378c2ecf20Sopenharmony_ci if (ret) { 4388c2ecf20Sopenharmony_ci mvm->nvm_file_name = nvm_file_C; 4398c2ecf20Sopenharmony_ci 4408c2ecf20Sopenharmony_ci if ((ret == -EFAULT || ret == -ENOENT) && 4418c2ecf20Sopenharmony_ci mvm->nvm_file_name) { 4428c2ecf20Sopenharmony_ci /* in case nvm file was failed try again */ 4438c2ecf20Sopenharmony_ci ret = iwl_read_external_nvm(mvm->trans, 4448c2ecf20Sopenharmony_ci mvm->nvm_file_name, 4458c2ecf20Sopenharmony_ci mvm->nvm_sections); 4468c2ecf20Sopenharmony_ci if (ret) 4478c2ecf20Sopenharmony_ci return ret; 4488c2ecf20Sopenharmony_ci } else { 4498c2ecf20Sopenharmony_ci return ret; 4508c2ecf20Sopenharmony_ci } 4518c2ecf20Sopenharmony_ci } 4528c2ecf20Sopenharmony_ci } 4538c2ecf20Sopenharmony_ci 4548c2ecf20Sopenharmony_ci /* parse the relevant nvm sections */ 4558c2ecf20Sopenharmony_ci mvm->nvm_data = iwl_parse_nvm_sections(mvm); 4568c2ecf20Sopenharmony_ci if (!mvm->nvm_data) 4578c2ecf20Sopenharmony_ci return -ENODATA; 4588c2ecf20Sopenharmony_ci IWL_DEBUG_EEPROM(mvm->trans->dev, "nvm version = %x\n", 4598c2ecf20Sopenharmony_ci mvm->nvm_data->nvm_version); 4608c2ecf20Sopenharmony_ci 4618c2ecf20Sopenharmony_ci return ret < 0 ? ret : 0; 4628c2ecf20Sopenharmony_ci} 4638c2ecf20Sopenharmony_ci 4648c2ecf20Sopenharmony_cistruct iwl_mcc_update_resp * 4658c2ecf20Sopenharmony_ciiwl_mvm_update_mcc(struct iwl_mvm *mvm, const char *alpha2, 4668c2ecf20Sopenharmony_ci enum iwl_mcc_source src_id) 4678c2ecf20Sopenharmony_ci{ 4688c2ecf20Sopenharmony_ci struct iwl_mcc_update_cmd mcc_update_cmd = { 4698c2ecf20Sopenharmony_ci .mcc = cpu_to_le16(alpha2[0] << 8 | alpha2[1]), 4708c2ecf20Sopenharmony_ci .source_id = (u8)src_id, 4718c2ecf20Sopenharmony_ci }; 4728c2ecf20Sopenharmony_ci struct iwl_mcc_update_resp *resp_cp; 4738c2ecf20Sopenharmony_ci struct iwl_rx_packet *pkt; 4748c2ecf20Sopenharmony_ci struct iwl_host_cmd cmd = { 4758c2ecf20Sopenharmony_ci .id = MCC_UPDATE_CMD, 4768c2ecf20Sopenharmony_ci .flags = CMD_WANT_SKB, 4778c2ecf20Sopenharmony_ci .data = { &mcc_update_cmd }, 4788c2ecf20Sopenharmony_ci }; 4798c2ecf20Sopenharmony_ci 4808c2ecf20Sopenharmony_ci int ret; 4818c2ecf20Sopenharmony_ci u32 status; 4828c2ecf20Sopenharmony_ci int resp_len, n_channels; 4838c2ecf20Sopenharmony_ci u16 mcc; 4848c2ecf20Sopenharmony_ci 4858c2ecf20Sopenharmony_ci if (WARN_ON_ONCE(!iwl_mvm_is_lar_supported(mvm))) 4868c2ecf20Sopenharmony_ci return ERR_PTR(-EOPNOTSUPP); 4878c2ecf20Sopenharmony_ci 4888c2ecf20Sopenharmony_ci cmd.len[0] = sizeof(struct iwl_mcc_update_cmd); 4898c2ecf20Sopenharmony_ci 4908c2ecf20Sopenharmony_ci IWL_DEBUG_LAR(mvm, "send MCC update to FW with '%c%c' src = %d\n", 4918c2ecf20Sopenharmony_ci alpha2[0], alpha2[1], src_id); 4928c2ecf20Sopenharmony_ci 4938c2ecf20Sopenharmony_ci ret = iwl_mvm_send_cmd(mvm, &cmd); 4948c2ecf20Sopenharmony_ci if (ret) 4958c2ecf20Sopenharmony_ci return ERR_PTR(ret); 4968c2ecf20Sopenharmony_ci 4978c2ecf20Sopenharmony_ci pkt = cmd.resp_pkt; 4988c2ecf20Sopenharmony_ci 4998c2ecf20Sopenharmony_ci /* Extract MCC response */ 5008c2ecf20Sopenharmony_ci if (fw_has_capa(&mvm->fw->ucode_capa, 5018c2ecf20Sopenharmony_ci IWL_UCODE_TLV_CAPA_MCC_UPDATE_11AX_SUPPORT)) { 5028c2ecf20Sopenharmony_ci struct iwl_mcc_update_resp *mcc_resp = (void *)pkt->data; 5038c2ecf20Sopenharmony_ci 5048c2ecf20Sopenharmony_ci n_channels = __le32_to_cpu(mcc_resp->n_channels); 5058c2ecf20Sopenharmony_ci if (iwl_rx_packet_payload_len(pkt) != 5068c2ecf20Sopenharmony_ci struct_size(mcc_resp, channels, n_channels)) { 5078c2ecf20Sopenharmony_ci resp_cp = ERR_PTR(-EINVAL); 5088c2ecf20Sopenharmony_ci goto exit; 5098c2ecf20Sopenharmony_ci } 5108c2ecf20Sopenharmony_ci resp_len = sizeof(struct iwl_mcc_update_resp) + 5118c2ecf20Sopenharmony_ci n_channels * sizeof(__le32); 5128c2ecf20Sopenharmony_ci resp_cp = kmemdup(mcc_resp, resp_len, GFP_KERNEL); 5138c2ecf20Sopenharmony_ci if (!resp_cp) { 5148c2ecf20Sopenharmony_ci resp_cp = ERR_PTR(-ENOMEM); 5158c2ecf20Sopenharmony_ci goto exit; 5168c2ecf20Sopenharmony_ci } 5178c2ecf20Sopenharmony_ci } else { 5188c2ecf20Sopenharmony_ci struct iwl_mcc_update_resp_v3 *mcc_resp_v3 = (void *)pkt->data; 5198c2ecf20Sopenharmony_ci 5208c2ecf20Sopenharmony_ci n_channels = __le32_to_cpu(mcc_resp_v3->n_channels); 5218c2ecf20Sopenharmony_ci if (iwl_rx_packet_payload_len(pkt) != 5228c2ecf20Sopenharmony_ci struct_size(mcc_resp_v3, channels, n_channels)) { 5238c2ecf20Sopenharmony_ci resp_cp = ERR_PTR(-EINVAL); 5248c2ecf20Sopenharmony_ci goto exit; 5258c2ecf20Sopenharmony_ci } 5268c2ecf20Sopenharmony_ci resp_len = sizeof(struct iwl_mcc_update_resp) + 5278c2ecf20Sopenharmony_ci n_channels * sizeof(__le32); 5288c2ecf20Sopenharmony_ci resp_cp = kzalloc(resp_len, GFP_KERNEL); 5298c2ecf20Sopenharmony_ci if (!resp_cp) { 5308c2ecf20Sopenharmony_ci resp_cp = ERR_PTR(-ENOMEM); 5318c2ecf20Sopenharmony_ci goto exit; 5328c2ecf20Sopenharmony_ci } 5338c2ecf20Sopenharmony_ci 5348c2ecf20Sopenharmony_ci resp_cp->status = mcc_resp_v3->status; 5358c2ecf20Sopenharmony_ci resp_cp->mcc = mcc_resp_v3->mcc; 5368c2ecf20Sopenharmony_ci resp_cp->cap = cpu_to_le16(mcc_resp_v3->cap); 5378c2ecf20Sopenharmony_ci resp_cp->source_id = mcc_resp_v3->source_id; 5388c2ecf20Sopenharmony_ci resp_cp->time = mcc_resp_v3->time; 5398c2ecf20Sopenharmony_ci resp_cp->geo_info = mcc_resp_v3->geo_info; 5408c2ecf20Sopenharmony_ci resp_cp->n_channels = mcc_resp_v3->n_channels; 5418c2ecf20Sopenharmony_ci memcpy(resp_cp->channels, mcc_resp_v3->channels, 5428c2ecf20Sopenharmony_ci n_channels * sizeof(__le32)); 5438c2ecf20Sopenharmony_ci } 5448c2ecf20Sopenharmony_ci 5458c2ecf20Sopenharmony_ci status = le32_to_cpu(resp_cp->status); 5468c2ecf20Sopenharmony_ci 5478c2ecf20Sopenharmony_ci mcc = le16_to_cpu(resp_cp->mcc); 5488c2ecf20Sopenharmony_ci 5498c2ecf20Sopenharmony_ci /* W/A for a FW/NVM issue - returns 0x00 for the world domain */ 5508c2ecf20Sopenharmony_ci if (mcc == 0) { 5518c2ecf20Sopenharmony_ci mcc = 0x3030; /* "00" - world */ 5528c2ecf20Sopenharmony_ci resp_cp->mcc = cpu_to_le16(mcc); 5538c2ecf20Sopenharmony_ci } 5548c2ecf20Sopenharmony_ci 5558c2ecf20Sopenharmony_ci IWL_DEBUG_LAR(mvm, 5568c2ecf20Sopenharmony_ci "MCC response status: 0x%x. new MCC: 0x%x ('%c%c') n_chans: %d\n", 5578c2ecf20Sopenharmony_ci status, mcc, mcc >> 8, mcc & 0xff, n_channels); 5588c2ecf20Sopenharmony_ci 5598c2ecf20Sopenharmony_ciexit: 5608c2ecf20Sopenharmony_ci iwl_free_resp(&cmd); 5618c2ecf20Sopenharmony_ci return resp_cp; 5628c2ecf20Sopenharmony_ci} 5638c2ecf20Sopenharmony_ci 5648c2ecf20Sopenharmony_ciint iwl_mvm_init_mcc(struct iwl_mvm *mvm) 5658c2ecf20Sopenharmony_ci{ 5668c2ecf20Sopenharmony_ci bool tlv_lar; 5678c2ecf20Sopenharmony_ci bool nvm_lar; 5688c2ecf20Sopenharmony_ci int retval; 5698c2ecf20Sopenharmony_ci struct ieee80211_regdomain *regd; 5708c2ecf20Sopenharmony_ci char mcc[3]; 5718c2ecf20Sopenharmony_ci 5728c2ecf20Sopenharmony_ci if (mvm->cfg->nvm_type == IWL_NVM_EXT) { 5738c2ecf20Sopenharmony_ci tlv_lar = fw_has_capa(&mvm->fw->ucode_capa, 5748c2ecf20Sopenharmony_ci IWL_UCODE_TLV_CAPA_LAR_SUPPORT); 5758c2ecf20Sopenharmony_ci nvm_lar = mvm->nvm_data->lar_enabled; 5768c2ecf20Sopenharmony_ci if (tlv_lar != nvm_lar) 5778c2ecf20Sopenharmony_ci IWL_INFO(mvm, 5788c2ecf20Sopenharmony_ci "Conflict between TLV & NVM regarding enabling LAR (TLV = %s NVM =%s)\n", 5798c2ecf20Sopenharmony_ci tlv_lar ? "enabled" : "disabled", 5808c2ecf20Sopenharmony_ci nvm_lar ? "enabled" : "disabled"); 5818c2ecf20Sopenharmony_ci } 5828c2ecf20Sopenharmony_ci 5838c2ecf20Sopenharmony_ci if (!iwl_mvm_is_lar_supported(mvm)) 5848c2ecf20Sopenharmony_ci return 0; 5858c2ecf20Sopenharmony_ci 5868c2ecf20Sopenharmony_ci /* 5878c2ecf20Sopenharmony_ci * try to replay the last set MCC to FW. If it doesn't exist, 5888c2ecf20Sopenharmony_ci * queue an update to cfg80211 to retrieve the default alpha2 from FW. 5898c2ecf20Sopenharmony_ci */ 5908c2ecf20Sopenharmony_ci retval = iwl_mvm_init_fw_regd(mvm); 5918c2ecf20Sopenharmony_ci if (retval != -ENOENT) 5928c2ecf20Sopenharmony_ci return retval; 5938c2ecf20Sopenharmony_ci 5948c2ecf20Sopenharmony_ci /* 5958c2ecf20Sopenharmony_ci * Driver regulatory hint for initial update, this also informs the 5968c2ecf20Sopenharmony_ci * firmware we support wifi location updates. 5978c2ecf20Sopenharmony_ci * Disallow scans that might crash the FW while the LAR regdomain 5988c2ecf20Sopenharmony_ci * is not set. 5998c2ecf20Sopenharmony_ci */ 6008c2ecf20Sopenharmony_ci mvm->lar_regdom_set = false; 6018c2ecf20Sopenharmony_ci 6028c2ecf20Sopenharmony_ci regd = iwl_mvm_get_current_regdomain(mvm, NULL); 6038c2ecf20Sopenharmony_ci if (IS_ERR_OR_NULL(regd)) 6048c2ecf20Sopenharmony_ci return -EIO; 6058c2ecf20Sopenharmony_ci 6068c2ecf20Sopenharmony_ci if (iwl_mvm_is_wifi_mcc_supported(mvm) && 6078c2ecf20Sopenharmony_ci !iwl_acpi_get_mcc(mvm->dev, mcc)) { 6088c2ecf20Sopenharmony_ci kfree(regd); 6098c2ecf20Sopenharmony_ci regd = iwl_mvm_get_regdomain(mvm->hw->wiphy, mcc, 6108c2ecf20Sopenharmony_ci MCC_SOURCE_BIOS, NULL); 6118c2ecf20Sopenharmony_ci if (IS_ERR_OR_NULL(regd)) 6128c2ecf20Sopenharmony_ci return -EIO; 6138c2ecf20Sopenharmony_ci } 6148c2ecf20Sopenharmony_ci 6158c2ecf20Sopenharmony_ci retval = regulatory_set_wiphy_regd_sync_rtnl(mvm->hw->wiphy, regd); 6168c2ecf20Sopenharmony_ci kfree(regd); 6178c2ecf20Sopenharmony_ci return retval; 6188c2ecf20Sopenharmony_ci} 6198c2ecf20Sopenharmony_ci 6208c2ecf20Sopenharmony_civoid iwl_mvm_rx_chub_update_mcc(struct iwl_mvm *mvm, 6218c2ecf20Sopenharmony_ci struct iwl_rx_cmd_buffer *rxb) 6228c2ecf20Sopenharmony_ci{ 6238c2ecf20Sopenharmony_ci struct iwl_rx_packet *pkt = rxb_addr(rxb); 6248c2ecf20Sopenharmony_ci struct iwl_mcc_chub_notif *notif = (void *)pkt->data; 6258c2ecf20Sopenharmony_ci enum iwl_mcc_source src; 6268c2ecf20Sopenharmony_ci char mcc[3]; 6278c2ecf20Sopenharmony_ci struct ieee80211_regdomain *regd; 6288c2ecf20Sopenharmony_ci int wgds_tbl_idx; 6298c2ecf20Sopenharmony_ci 6308c2ecf20Sopenharmony_ci lockdep_assert_held(&mvm->mutex); 6318c2ecf20Sopenharmony_ci 6328c2ecf20Sopenharmony_ci if (iwl_mvm_is_vif_assoc(mvm) && notif->source_id == MCC_SOURCE_WIFI) { 6338c2ecf20Sopenharmony_ci IWL_DEBUG_LAR(mvm, "Ignore mcc update while associated\n"); 6348c2ecf20Sopenharmony_ci return; 6358c2ecf20Sopenharmony_ci } 6368c2ecf20Sopenharmony_ci 6378c2ecf20Sopenharmony_ci if (WARN_ON_ONCE(!iwl_mvm_is_lar_supported(mvm))) 6388c2ecf20Sopenharmony_ci return; 6398c2ecf20Sopenharmony_ci 6408c2ecf20Sopenharmony_ci mcc[0] = le16_to_cpu(notif->mcc) >> 8; 6418c2ecf20Sopenharmony_ci mcc[1] = le16_to_cpu(notif->mcc) & 0xff; 6428c2ecf20Sopenharmony_ci mcc[2] = '\0'; 6438c2ecf20Sopenharmony_ci src = notif->source_id; 6448c2ecf20Sopenharmony_ci 6458c2ecf20Sopenharmony_ci IWL_DEBUG_LAR(mvm, 6468c2ecf20Sopenharmony_ci "RX: received chub update mcc cmd (mcc '%s' src %d)\n", 6478c2ecf20Sopenharmony_ci mcc, src); 6488c2ecf20Sopenharmony_ci regd = iwl_mvm_get_regdomain(mvm->hw->wiphy, mcc, src, NULL); 6498c2ecf20Sopenharmony_ci if (IS_ERR_OR_NULL(regd)) 6508c2ecf20Sopenharmony_ci return; 6518c2ecf20Sopenharmony_ci 6528c2ecf20Sopenharmony_ci wgds_tbl_idx = iwl_mvm_get_sar_geo_profile(mvm); 6538c2ecf20Sopenharmony_ci if (wgds_tbl_idx < 0) 6548c2ecf20Sopenharmony_ci IWL_DEBUG_INFO(mvm, "SAR WGDS is disabled (%d)\n", 6558c2ecf20Sopenharmony_ci wgds_tbl_idx); 6568c2ecf20Sopenharmony_ci else 6578c2ecf20Sopenharmony_ci IWL_DEBUG_INFO(mvm, "SAR WGDS: geo profile %d is configured\n", 6588c2ecf20Sopenharmony_ci wgds_tbl_idx); 6598c2ecf20Sopenharmony_ci 6608c2ecf20Sopenharmony_ci regulatory_set_wiphy_regd(mvm->hw->wiphy, regd); 6618c2ecf20Sopenharmony_ci kfree(regd); 6628c2ecf20Sopenharmony_ci} 663