Merge git://git.kernel.org/pub/scm/linux/kernel/git/kvalo/wireless-drivers.git
[platform/kernel/linux-starfive.git] / drivers / net / wireless / intel / iwlwifi / iwl-nvm-parse.c
index ca01746..1f31d7b 100644 (file)
@@ -8,6 +8,7 @@
  * Copyright(c) 2008 - 2014 Intel Corporation. All rights reserved.
  * Copyright(c) 2013 - 2015 Intel Mobile Communications GmbH
  * Copyright(c) 2016 - 2017 Intel Deutschland GmbH
+ * Copyright(c) 2018        Intel Corporation
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of version 2 of the GNU General Public License as
@@ -35,6 +36,7 @@
  * Copyright(c) 2005 - 2014 Intel Corporation. All rights reserved.
  * Copyright(c) 2013 - 2015 Intel Mobile Communications GmbH
  * Copyright(c) 2016 - 2017 Intel Deutschland GmbH
+ * Copyright(c) 2018        Intel Corporation
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -68,6 +70,7 @@
 #include <linux/export.h>
 #include <linux/etherdevice.h>
 #include <linux/pci.h>
+#include <linux/firmware.h>
 
 #include "iwl-drv.h"
 #include "iwl-modparams.h"
@@ -77,6 +80,9 @@
 #include "iwl-csr.h"
 #include "fw/acpi.h"
 #include "fw/api/nvm-reg.h"
+#include "fw/api/commands.h"
+#include "fw/api/cmdhdr.h"
+#include "fw/img.h"
 
 /* NVM offsets (in words) definitions */
 enum nvm_offsets {
@@ -292,7 +298,7 @@ static u32 iwl_get_channel_flags(u8 ch_num, int ch_idx, bool is_5ghz,
 static int iwl_init_channel_map(struct device *dev, const struct iwl_cfg *cfg,
                                struct iwl_nvm_data *data,
                                const __le16 * const nvm_ch_flags,
-                               bool lar_supported, bool no_wide_in_5ghz)
+                               u32 sbands_flags)
 {
        int ch_idx;
        int n_channels = 0;
@@ -316,11 +322,12 @@ static int iwl_init_channel_map(struct device *dev, const struct iwl_cfg *cfg,
 
                ch_flags = __le16_to_cpup(nvm_ch_flags + ch_idx);
 
-               if (is_5ghz && !data->sku_cap_band_52GHz_enable)
+               if (is_5ghz && !data->sku_cap_band_52ghz_enable)
                        continue;
 
                /* workaround to disable wide channels in 5GHz */
-               if (no_wide_in_5ghz && is_5ghz) {
+               if ((sbands_flags & IWL_NVM_SBANDS_FLAGS_NO_WIDE_IN_5GHZ) &&
+                   is_5ghz) {
                        ch_flags &= ~(NVM_CHANNEL_40MHZ |
                                     NVM_CHANNEL_80MHZ |
                                     NVM_CHANNEL_160MHZ);
@@ -329,7 +336,8 @@ static int iwl_init_channel_map(struct device *dev, const struct iwl_cfg *cfg,
                if (ch_flags & NVM_CHANNEL_160MHZ)
                        data->vht160_supported = true;
 
-               if (!lar_supported && !(ch_flags & NVM_CHANNEL_VALID)) {
+               if (!(sbands_flags & IWL_NVM_SBANDS_FLAGS_LAR) &&
+                   !(ch_flags & NVM_CHANNEL_VALID)) {
                        /*
                         * Channels might become valid later if lar is
                         * supported, hence we still want to add them to
@@ -359,7 +367,7 @@ static int iwl_init_channel_map(struct device *dev, const struct iwl_cfg *cfg,
                channel->max_power = IWL_DEFAULT_MAX_TX_POWER;
 
                /* don't put limitations in case we're using LAR */
-               if (!lar_supported)
+               if (!(sbands_flags & IWL_NVM_SBANDS_FLAGS_LAR))
                        channel->flags = iwl_get_channel_flags(nvm_chan[ch_idx],
                                                               ch_idx, is_5ghz,
                                                               ch_flags, cfg);
@@ -455,17 +463,17 @@ static void iwl_init_vht_hw_capab(const struct iwl_cfg *cfg,
        vht_cap->vht_mcs.tx_mcs_map = vht_cap->vht_mcs.rx_mcs_map;
 }
 
-void iwl_init_sbands(struct device *dev, const struct iwl_cfg *cfg,
-                    struct iwl_nvm_data *data, const __le16 *nvm_ch_flags,
-                    u8 tx_chains, u8 rx_chains, bool lar_supported,
-                    bool no_wide_in_5ghz)
+static void iwl_init_sbands(struct device *dev, const struct iwl_cfg *cfg,
+                           struct iwl_nvm_data *data,
+                           const __le16 *nvm_ch_flags, u8 tx_chains,
+                           u8 rx_chains, u32 sbands_flags)
 {
        int n_channels;
        int n_used = 0;
        struct ieee80211_supported_band *sband;
 
        n_channels = iwl_init_channel_map(dev, cfg, data, nvm_ch_flags,
-                                         lar_supported, no_wide_in_5ghz);
+                                         sbands_flags);
        sband = &data->bands[NL80211_BAND_2GHZ];
        sband->band = NL80211_BAND_2GHZ;
        sband->bitrates = &iwl_cfg80211_rates[RATES_24_OFFS];
@@ -491,7 +499,6 @@ void iwl_init_sbands(struct device *dev, const struct iwl_cfg *cfg,
                IWL_ERR_DEV(dev, "NVM: used only %d of %d channels\n",
                            n_used, n_channels);
 }
-IWL_EXPORT_SYMBOL(iwl_init_sbands);
 
 static int iwl_get_sku(const struct iwl_cfg *cfg, const __le16 *nvm_sw,
                       const __le16 *phy_sku)
@@ -569,8 +576,8 @@ static void iwl_flip_hw_address(__le32 mac_addr0, __le32 mac_addr1, u8 *dest)
        dest[5] = hw_addr[0];
 }
 
-void iwl_set_hw_address_from_csr(struct iwl_trans *trans,
-                                struct iwl_nvm_data *data)
+static void iwl_set_hw_address_from_csr(struct iwl_trans *trans,
+                                       struct iwl_nvm_data *data)
 {
        __le32 mac_addr0 = cpu_to_le32(iwl_read32(trans, CSR_MAC_ADDR0_STRAP));
        __le32 mac_addr1 = cpu_to_le32(iwl_read32(trans, CSR_MAC_ADDR1_STRAP));
@@ -588,7 +595,6 @@ void iwl_set_hw_address_from_csr(struct iwl_trans *trans,
 
        iwl_flip_hw_address(mac_addr0, mac_addr1, data->hw_addr);
 }
-IWL_EXPORT_SYMBOL(iwl_set_hw_address_from_csr);
 
 static void iwl_set_hw_address_family_8000(struct iwl_trans *trans,
                                           const struct iwl_cfg *cfg,
@@ -713,8 +719,8 @@ iwl_parse_nvm_data(struct iwl_trans *trans, const struct iwl_cfg *cfg,
        struct device *dev = trans->dev;
        struct iwl_nvm_data *data;
        bool lar_enabled;
-       bool no_wide_in_5ghz = iwl_nvm_no_wide_in_5ghz(dev, cfg, nvm_hw);
        u32 sku, radio_cfg;
+       u32 sbands_flags = 0;
        u16 lar_config;
        const __le16 *ch_section;
 
@@ -741,8 +747,8 @@ iwl_parse_nvm_data(struct iwl_trans *trans, const struct iwl_cfg *cfg,
                rx_chains &= data->valid_rx_ant;
 
        sku = iwl_get_sku(cfg, nvm_sw, phy_sku);
-       data->sku_cap_band_24GHz_enable = sku & NVM_SKU_CAP_BAND_24GHZ;
-       data->sku_cap_band_52GHz_enable = sku & NVM_SKU_CAP_BAND_52GHZ;
+       data->sku_cap_band_24ghz_enable = sku & NVM_SKU_CAP_BAND_24GHZ;
+       data->sku_cap_band_52ghz_enable = sku & NVM_SKU_CAP_BAND_52GHZ;
        data->sku_cap_11n_enable = sku & NVM_SKU_CAP_11N_ENABLE;
        if (iwlwifi_mod_params.disable_11n & IWL_DISABLE_HT_ALL)
                data->sku_cap_11n_enable = false;
@@ -787,8 +793,14 @@ iwl_parse_nvm_data(struct iwl_trans *trans, const struct iwl_cfg *cfg,
                return NULL;
        }
 
+       if (lar_fw_supported && lar_enabled)
+               sbands_flags |= IWL_NVM_SBANDS_FLAGS_LAR;
+
+       if (iwl_nvm_no_wide_in_5ghz(dev, cfg, nvm_hw))
+               sbands_flags |= IWL_NVM_SBANDS_FLAGS_NO_WIDE_IN_5GHZ;
+
        iwl_init_sbands(dev, cfg, data, ch_section, tx_chains, rx_chains,
-                       lar_fw_supported && lar_enabled, no_wide_in_5ghz);
+                       sbands_flags);
        data->calib_version = 255;
 
        return data;
@@ -1017,3 +1029,294 @@ out:
        return copy_rd;
 }
 IWL_EXPORT_SYMBOL(iwl_parse_nvm_mcc_info);
+
+#define IWL_MAX_NVM_SECTION_SIZE       0x1b58
+#define IWL_MAX_EXT_NVM_SECTION_SIZE   0x1ffc
+#define MAX_NVM_FILE_LEN       16384
+
+void iwl_nvm_fixups(u32 hw_id, unsigned int section, u8 *data,
+                   unsigned int len)
+{
+#define IWL_4165_DEVICE_ID     0x5501
+#define NVM_SKU_CAP_MIMO_DISABLE BIT(5)
+
+       if (section == NVM_SECTION_TYPE_PHY_SKU &&
+           hw_id == IWL_4165_DEVICE_ID && data && len >= 5 &&
+           (data[4] & NVM_SKU_CAP_MIMO_DISABLE))
+               /* OTP 0x52 bug work around: it's a 1x1 device */
+               data[3] = ANT_B | (ANT_B << 4);
+}
+IWL_EXPORT_SYMBOL(iwl_nvm_fixups);
+
+/*
+ * Reads external NVM from a file into mvm->nvm_sections
+ *
+ * HOW TO CREATE THE NVM FILE FORMAT:
+ * ------------------------------
+ * 1. create hex file, format:
+ *      3800 -> header
+ *      0000 -> header
+ *      5a40 -> data
+ *
+ *   rev - 6 bit (word1)
+ *   len - 10 bit (word1)
+ *   id - 4 bit (word2)
+ *   rsv - 12 bit (word2)
+ *
+ * 2. flip 8bits with 8 bits per line to get the right NVM file format
+ *
+ * 3. create binary file from the hex file
+ *
+ * 4. save as "iNVM_xxx.bin" under /lib/firmware
+ */
+int iwl_read_external_nvm(struct iwl_trans *trans,
+                         const char *nvm_file_name,
+                         struct iwl_nvm_section *nvm_sections)
+{
+       int ret, section_size;
+       u16 section_id;
+       const struct firmware *fw_entry;
+       const struct {
+               __le16 word1;
+               __le16 word2;
+               u8 data[];
+       } *file_sec;
+       const u8 *eof;
+       u8 *temp;
+       int max_section_size;
+       const __le32 *dword_buff;
+
+#define NVM_WORD1_LEN(x) (8 * (x & 0x03FF))
+#define NVM_WORD2_ID(x) (x >> 12)
+#define EXT_NVM_WORD2_LEN(x) (2 * (((x) & 0xFF) << 8 | (x) >> 8))
+#define EXT_NVM_WORD1_ID(x) ((x) >> 4)
+#define NVM_HEADER_0   (0x2A504C54)
+#define NVM_HEADER_1   (0x4E564D2A)
+#define NVM_HEADER_SIZE        (4 * sizeof(u32))
+
+       IWL_DEBUG_EEPROM(trans->dev, "Read from external NVM\n");
+
+       /* Maximal size depends on NVM version */
+       if (trans->cfg->nvm_type != IWL_NVM_EXT)
+               max_section_size = IWL_MAX_NVM_SECTION_SIZE;
+       else
+               max_section_size = IWL_MAX_EXT_NVM_SECTION_SIZE;
+
+       /*
+        * Obtain NVM image via request_firmware. Since we already used
+        * request_firmware_nowait() for the firmware binary load and only
+        * get here after that we assume the NVM request can be satisfied
+        * synchronously.
+        */
+       ret = request_firmware(&fw_entry, nvm_file_name, trans->dev);
+       if (ret) {
+               IWL_ERR(trans, "ERROR: %s isn't available %d\n",
+                       nvm_file_name, ret);
+               return ret;
+       }
+
+       IWL_INFO(trans, "Loaded NVM file %s (%zu bytes)\n",
+                nvm_file_name, fw_entry->size);
+
+       if (fw_entry->size > MAX_NVM_FILE_LEN) {
+               IWL_ERR(trans, "NVM file too large\n");
+               ret = -EINVAL;
+               goto out;
+       }
+
+       eof = fw_entry->data + fw_entry->size;
+       dword_buff = (__le32 *)fw_entry->data;
+
+       /* some NVM file will contain a header.
+        * The header is identified by 2 dwords header as follow:
+        * dword[0] = 0x2A504C54
+        * dword[1] = 0x4E564D2A
+        *
+        * This header must be skipped when providing the NVM data to the FW.
+        */
+       if (fw_entry->size > NVM_HEADER_SIZE &&
+           dword_buff[0] == cpu_to_le32(NVM_HEADER_0) &&
+           dword_buff[1] == cpu_to_le32(NVM_HEADER_1)) {
+               file_sec = (void *)(fw_entry->data + NVM_HEADER_SIZE);
+               IWL_INFO(trans, "NVM Version %08X\n", le32_to_cpu(dword_buff[2]));
+               IWL_INFO(trans, "NVM Manufacturing date %08X\n",
+                        le32_to_cpu(dword_buff[3]));
+
+               /* nvm file validation, dword_buff[2] holds the file version */
+               if (trans->cfg->device_family == IWL_DEVICE_FAMILY_8000 &&
+                   CSR_HW_REV_STEP(trans->hw_rev) == SILICON_C_STEP &&
+                   le32_to_cpu(dword_buff[2]) < 0xE4A) {
+                       ret = -EFAULT;
+                       goto out;
+               }
+       } else {
+               file_sec = (void *)fw_entry->data;
+       }
+
+       while (true) {
+               if (file_sec->data > eof) {
+                       IWL_ERR(trans,
+                               "ERROR - NVM file too short for section header\n");
+                       ret = -EINVAL;
+                       break;
+               }
+
+               /* check for EOF marker */
+               if (!file_sec->word1 && !file_sec->word2) {
+                       ret = 0;
+                       break;
+               }
+
+               if (trans->cfg->nvm_type != IWL_NVM_EXT) {
+                       section_size =
+                               2 * NVM_WORD1_LEN(le16_to_cpu(file_sec->word1));
+                       section_id = NVM_WORD2_ID(le16_to_cpu(file_sec->word2));
+               } else {
+                       section_size = 2 * EXT_NVM_WORD2_LEN(
+                                               le16_to_cpu(file_sec->word2));
+                       section_id = EXT_NVM_WORD1_ID(
+                                               le16_to_cpu(file_sec->word1));
+               }
+
+               if (section_size > max_section_size) {
+                       IWL_ERR(trans, "ERROR - section too large (%d)\n",
+                               section_size);
+                       ret = -EINVAL;
+                       break;
+               }
+
+               if (!section_size) {
+                       IWL_ERR(trans, "ERROR - section empty\n");
+                       ret = -EINVAL;
+                       break;
+               }
+
+               if (file_sec->data + section_size > eof) {
+                       IWL_ERR(trans,
+                               "ERROR - NVM file too short for section (%d bytes)\n",
+                               section_size);
+                       ret = -EINVAL;
+                       break;
+               }
+
+               if (WARN(section_id >= NVM_MAX_NUM_SECTIONS,
+                        "Invalid NVM section ID %d\n", section_id)) {
+                       ret = -EINVAL;
+                       break;
+               }
+
+               temp = kmemdup(file_sec->data, section_size, GFP_KERNEL);
+               if (!temp) {
+                       ret = -ENOMEM;
+                       break;
+               }
+
+               iwl_nvm_fixups(trans->hw_id, section_id, temp, section_size);
+
+               kfree(nvm_sections[section_id].data);
+               nvm_sections[section_id].data = temp;
+               nvm_sections[section_id].length = section_size;
+
+               /* advance to the next section */
+               file_sec = (void *)(file_sec->data + section_size);
+       }
+out:
+       release_firmware(fw_entry);
+       return ret;
+}
+IWL_EXPORT_SYMBOL(iwl_read_external_nvm);
+
+struct iwl_nvm_data *iwl_get_nvm(struct iwl_trans *trans,
+                                const struct iwl_fw *fw)
+{
+       struct iwl_nvm_get_info cmd = {};
+       struct iwl_nvm_get_info_rsp *rsp;
+       struct iwl_nvm_data *nvm;
+       struct iwl_host_cmd hcmd = {
+               .flags = CMD_WANT_SKB | CMD_SEND_IN_RFKILL,
+               .data = { &cmd, },
+               .len = { sizeof(cmd) },
+               .id = WIDE_ID(REGULATORY_AND_NVM_GROUP, NVM_GET_INFO)
+       };
+       int  ret;
+       bool lar_fw_supported = !iwlwifi_mod_params.lar_disable &&
+                               fw_has_capa(&fw->ucode_capa,
+                                           IWL_UCODE_TLV_CAPA_LAR_SUPPORT);
+       u32 mac_flags;
+       u32 sbands_flags = 0;
+
+       ret = iwl_trans_send_cmd(trans, &hcmd);
+       if (ret)
+               return ERR_PTR(ret);
+
+       if (WARN(iwl_rx_packet_payload_len(hcmd.resp_pkt) != sizeof(*rsp),
+                "Invalid payload len in NVM response from FW %d",
+                iwl_rx_packet_payload_len(hcmd.resp_pkt))) {
+               ret = -EINVAL;
+               goto out;
+       }
+
+       rsp = (void *)hcmd.resp_pkt->data;
+       if (le32_to_cpu(rsp->general.flags) & NVM_GENERAL_FLAGS_EMPTY_OTP)
+               IWL_INFO(trans, "OTP is empty\n");
+
+       nvm = kzalloc(sizeof(*nvm) +
+                     sizeof(struct ieee80211_channel) * IWL_NUM_CHANNELS,
+                     GFP_KERNEL);
+       if (!nvm) {
+               ret = -ENOMEM;
+               goto out;
+       }
+
+       iwl_set_hw_address_from_csr(trans, nvm);
+       /* TODO: if platform NVM has MAC address - override it here */
+
+       if (!is_valid_ether_addr(nvm->hw_addr)) {
+               IWL_ERR(trans, "no valid mac address was found\n");
+               ret = -EINVAL;
+               goto err_free;
+       }
+
+       IWL_INFO(trans, "base HW address: %pM\n", nvm->hw_addr);
+
+       /* Initialize general data */
+       nvm->nvm_version = le16_to_cpu(rsp->general.nvm_version);
+
+       /* Initialize MAC sku data */
+       mac_flags = le32_to_cpu(rsp->mac_sku.mac_sku_flags);
+       nvm->sku_cap_11ac_enable =
+               !!(mac_flags & NVM_MAC_SKU_FLAGS_802_11AC_ENABLED);
+       nvm->sku_cap_11n_enable =
+               !!(mac_flags & NVM_MAC_SKU_FLAGS_802_11N_ENABLED);
+       nvm->sku_cap_band_24ghz_enable =
+               !!(mac_flags & NVM_MAC_SKU_FLAGS_BAND_2_4_ENABLED);
+       nvm->sku_cap_band_52ghz_enable =
+               !!(mac_flags & NVM_MAC_SKU_FLAGS_BAND_5_2_ENABLED);
+       nvm->sku_cap_mimo_disabled =
+               !!(mac_flags & NVM_MAC_SKU_FLAGS_MIMO_DISABLED);
+
+       /* Initialize PHY sku data */
+       nvm->valid_tx_ant = (u8)le32_to_cpu(rsp->phy_sku.tx_chains);
+       nvm->valid_rx_ant = (u8)le32_to_cpu(rsp->phy_sku.rx_chains);
+
+       if (le32_to_cpu(rsp->regulatory.lar_enabled) && lar_fw_supported) {
+               nvm->lar_enabled = true;
+               sbands_flags |= IWL_NVM_SBANDS_FLAGS_LAR;
+       }
+
+       iwl_init_sbands(trans->dev, trans->cfg, nvm,
+                       rsp->regulatory.channel_profile,
+                       nvm->valid_tx_ant & fw->valid_tx_ant,
+                       nvm->valid_rx_ant & fw->valid_rx_ant,
+                       sbands_flags);
+
+       iwl_free_resp(&hcmd);
+       return nvm;
+
+err_free:
+       kfree(nvm);
+out:
+       iwl_free_resp(&hcmd);
+       return ERR_PTR(ret);
+}
+IWL_EXPORT_SYMBOL(iwl_get_nvm);