iwlwifi: mvm: take the MAC address from HW registers
[linux-2.6-microblaze.git] / drivers / net / wireless / iwlwifi / mvm / nvm.c
index d55fd8e..41189e5 100644 (file)
  *
  *****************************************************************************/
 #include <linux/firmware.h>
+#include <linux/rtnetlink.h>
 #include "iwl-trans.h"
 #include "iwl-csr.h"
 #include "mvm.h"
 #include "iwl-eeprom-parse.h"
 #include "iwl-eeprom-read.h"
 #include "iwl-nvm-parse.h"
+#include "iwl-prph.h"
 
 /* Default NVM size to read */
 #define IWL_NVM_DEFAULT_CHUNK_SIZE (2*1024)
@@ -262,7 +264,9 @@ static struct iwl_nvm_data *
 iwl_parse_nvm_sections(struct iwl_mvm *mvm)
 {
        struct iwl_nvm_section *sections = mvm->nvm_sections;
-       const __le16 *hw, *sw, *calib, *regulatory, *mac_override;
+       const __le16 *hw, *sw, *calib, *regulatory, *mac_override, *phy_sku;
+       bool is_family_8000_a_step = false, lar_enabled;
+       u32 mac_addr0, mac_addr1;
 
        /* Checking for required sections */
        if (mvm->trans->cfg->device_family != IWL_DEVICE_FAMILY_8000) {
@@ -286,22 +290,43 @@ iwl_parse_nvm_sections(struct iwl_mvm *mvm)
                                "Can't parse mac_address, empty sections\n");
                        return NULL;
                }
+
+               if (CSR_HW_REV_STEP(mvm->trans->hw_rev) == SILICON_A_STEP)
+                       is_family_8000_a_step = true;
+
+               /* PHY_SKU section is mandatory in B0 */
+               if (!is_family_8000_a_step &&
+                   !mvm->nvm_sections[NVM_SECTION_TYPE_PHY_SKU].data) {
+                       IWL_ERR(mvm,
+                               "Can't parse phy_sku in B0, empty sections\n");
+                       return NULL;
+               }
        }
 
        if (WARN_ON(!mvm->cfg))
                return NULL;
 
+       /* read the mac address from WFMP registers */
+       mac_addr0 = iwl_trans_read_prph(mvm->trans, WFMP_MAC_ADDR_0);
+       mac_addr1 = iwl_trans_read_prph(mvm->trans, WFMP_MAC_ADDR_1);
+
        hw = (const __le16 *)sections[mvm->cfg->nvm_hw_section_num].data;
        sw = (const __le16 *)sections[NVM_SECTION_TYPE_SW].data;
        calib = (const __le16 *)sections[NVM_SECTION_TYPE_CALIBRATION].data;
        regulatory = (const __le16 *)sections[NVM_SECTION_TYPE_REGULATORY].data;
        mac_override =
                (const __le16 *)sections[NVM_SECTION_TYPE_MAC_OVERRIDE].data;
+       phy_sku = (const __le16 *)sections[NVM_SECTION_TYPE_PHY_SKU].data;
+
+       lar_enabled = !iwlwifi_mod_params.lar_disable &&
+                     (mvm->fw->ucode_capa.capa[0] &
+                      IWL_UCODE_TLV_CAPA_LAR_SUPPORT);
 
        return iwl_parse_nvm_data(mvm->trans->dev, mvm->cfg, hw, sw, calib,
-                                 regulatory, mac_override,
-                                 mvm->fw->valid_tx_ant,
-                                 mvm->fw->valid_rx_ant);
+                                 regulatory, mac_override, phy_sku,
+                                 mvm->fw->valid_tx_ant, mvm->fw->valid_rx_ant,
+                                 lar_enabled, is_family_8000_a_step,
+                                 mac_addr0, mac_addr1);
 }
 
 #define MAX_NVM_FILE_LEN       16384
@@ -356,7 +381,7 @@ static int iwl_mvm_read_external_nvm(struct iwl_mvm *mvm)
                max_section_size = IWL_MAX_NVM_SECTION_SIZE;
        else if (CSR_HW_REV_STEP(mvm->trans->hw_rev) == SILICON_A_STEP)
                max_section_size = IWL_MAX_NVM_8000A_SECTION_SIZE;
-       else /* Family 8000 B-step */
+       else /* Family 8000 B-step or C-step */
                max_section_size = IWL_MAX_NVM_8000B_SECTION_SIZE;
 
        /*
@@ -565,6 +590,162 @@ int iwl_nvm_init(struct iwl_mvm *mvm, bool read_nvm_from_nic)
        mvm->nvm_data = iwl_parse_nvm_sections(mvm);
        if (!mvm->nvm_data)
                return -ENODATA;
+       IWL_DEBUG_EEPROM(mvm->trans->dev, "nvm version = %x\n",
+                        mvm->nvm_data->nvm_version);
+
+       return 0;
+}
+
+struct iwl_mcc_update_resp *
+iwl_mvm_update_mcc(struct iwl_mvm *mvm, const char *alpha2,
+                  enum iwl_mcc_source src_id)
+{
+       struct iwl_mcc_update_cmd mcc_update_cmd = {
+               .mcc = cpu_to_le16(alpha2[0] << 8 | alpha2[1]),
+               .source_id = (u8)src_id,
+       };
+       struct iwl_mcc_update_resp *mcc_resp, *resp_cp = NULL;
+       struct iwl_rx_packet *pkt;
+       struct iwl_host_cmd cmd = {
+               .id = MCC_UPDATE_CMD,
+               .flags = CMD_WANT_SKB,
+               .data = { &mcc_update_cmd },
+       };
+
+       int ret;
+       u32 status;
+       int resp_len, n_channels;
+       u16 mcc;
+
+       if (WARN_ON_ONCE(!iwl_mvm_is_lar_supported(mvm)))
+               return ERR_PTR(-EOPNOTSUPP);
+
+       cmd.len[0] = sizeof(struct iwl_mcc_update_cmd);
+
+       IWL_DEBUG_LAR(mvm, "send MCC update to FW with '%c%c' src = %d\n",
+                     alpha2[0], alpha2[1], src_id);
+
+       ret = iwl_mvm_send_cmd(mvm, &cmd);
+       if (ret)
+               return ERR_PTR(ret);
+
+       pkt = cmd.resp_pkt;
+       if (pkt->hdr.flags & IWL_CMD_FAILED_MSK) {
+               IWL_ERR(mvm, "Bad return from MCC_UPDATE_COMMAND (0x%08X)\n",
+                       pkt->hdr.flags);
+               ret = -EIO;
+               goto exit;
+       }
+
+       /* Extract MCC response */
+       mcc_resp = (void *)pkt->data;
+       status = le32_to_cpu(mcc_resp->status);
+
+       mcc = le16_to_cpu(mcc_resp->mcc);
+
+       /* W/A for a FW/NVM issue - returns 0x00 for the world domain */
+       if (mcc == 0) {
+               mcc = 0x3030;  /* "00" - world */
+               mcc_resp->mcc = cpu_to_le16(mcc);
+       }
+
+       n_channels =  __le32_to_cpu(mcc_resp->n_channels);
+       IWL_DEBUG_LAR(mvm,
+                     "MCC response status: 0x%x. new MCC: 0x%x ('%c%c') change: %d n_chans: %d\n",
+                     status, mcc, mcc >> 8, mcc & 0xff,
+                     !!(status == MCC_RESP_SAME_CHAN_PROFILE), n_channels);
+
+       resp_len = sizeof(*mcc_resp) + n_channels * sizeof(__le32);
+       resp_cp = kmemdup(mcc_resp, resp_len, GFP_KERNEL);
+       if (!resp_cp) {
+               ret = -ENOMEM;
+               goto exit;
+       }
+
+       ret = 0;
+exit:
+       iwl_free_resp(&cmd);
+       if (ret)
+               return ERR_PTR(ret);
+       return resp_cp;
+}
+
+int iwl_mvm_init_mcc(struct iwl_mvm *mvm)
+{
+       bool tlv_lar;
+       bool nvm_lar;
+       int retval;
+       struct ieee80211_regdomain *regd;
+
+       if (mvm->cfg->device_family == IWL_DEVICE_FAMILY_8000) {
+               tlv_lar = mvm->fw->ucode_capa.capa[0] &
+                       IWL_UCODE_TLV_CAPA_LAR_SUPPORT;
+               nvm_lar = mvm->nvm_data->lar_enabled;
+               if (tlv_lar != nvm_lar)
+                       IWL_INFO(mvm,
+                                "Conflict between TLV & NVM regarding enabling LAR (TLV = %s NVM =%s)\n",
+                                tlv_lar ? "enabled" : "disabled",
+                                nvm_lar ? "enabled" : "disabled");
+       }
+
+       if (!iwl_mvm_is_lar_supported(mvm))
+               return 0;
+
+       /*
+        * During HW restart, only replay the last set MCC to FW. Otherwise,
+        * queue an update to cfg80211 to retrieve the default alpha2 from FW.
+        */
+       if (test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status)) {
+               /* This should only be called during vif up and hold RTNL */
+               return iwl_mvm_init_fw_regd(mvm);
+       }
+
+       /*
+        * Driver regulatory hint for initial update, this also informs the
+        * firmware we support wifi location updates.
+        * Disallow scans that might crash the FW while the LAR regdomain
+        * is not set.
+        */
+       mvm->lar_regdom_set = false;
+
+       regd = iwl_mvm_get_current_regdomain(mvm);
+       if (IS_ERR_OR_NULL(regd))
+               return -EIO;
+
+       retval = regulatory_set_wiphy_regd_sync_rtnl(mvm->hw->wiphy, regd);
+       kfree(regd);
+       return retval;
+}
+
+int iwl_mvm_rx_chub_update_mcc(struct iwl_mvm *mvm,
+                              struct iwl_rx_cmd_buffer *rxb,
+                              struct iwl_device_cmd *cmd)
+{
+       struct iwl_rx_packet *pkt = rxb_addr(rxb);
+       struct iwl_mcc_chub_notif *notif = (void *)pkt->data;
+       enum iwl_mcc_source src;
+       char mcc[3];
+       struct ieee80211_regdomain *regd;
+
+       lockdep_assert_held(&mvm->mutex);
+
+       if (WARN_ON_ONCE(!iwl_mvm_is_lar_supported(mvm)))
+               return 0;
+
+       mcc[0] = notif->mcc >> 8;
+       mcc[1] = notif->mcc & 0xff;
+       mcc[2] = '\0';
+       src = notif->source_id;
+
+       IWL_DEBUG_LAR(mvm,
+                     "RX: received chub update mcc cmd (mcc '%s' src %d)\n",
+                     mcc, src);
+       regd = iwl_mvm_get_regdomain(mvm->hw->wiphy, mcc, src);
+       if (IS_ERR_OR_NULL(regd))
+               return 0;
+
+       regulatory_set_wiphy_regd(mvm->hw->wiphy, regd);
+       kfree(regd);
 
        return 0;
 }