iwlwifi: create regdomain from mcc_update_cmd response
authorArik Nemtsov <arik@wizery.com>
Wed, 5 Mar 2014 10:19:10 +0000 (12:19 +0200)
committerEmmanuel Grumbach <emmanuel.grumbach@intel.com>
Thu, 12 Mar 2015 07:57:22 +0000 (09:57 +0200)
Parse the NVM channel data and create a regulatory domain with a rule
for every 20Mhz channel. Use the AUTO_BW flag so the regulatory core
can unify single-channel rules into ranges.

Signed-off-by: Arik Nemtsov <arikx.nemtsov@intel.com>
Signed-off-by: Emmanuel Grumbach <emmanuel.grumbach@intel.com>
drivers/net/wireless/iwlwifi/iwl-nvm-parse.c
drivers/net/wireless/iwlwifi/iwl-nvm-parse.h

index c74f1a4..d8d9a97 100644 (file)
@@ -643,3 +643,156 @@ iwl_parse_nvm_data(struct device *dev, const struct iwl_cfg *cfg,
        return data;
 }
 IWL_EXPORT_SYMBOL(iwl_parse_nvm_data);
+
+static u32 iwl_nvm_get_regdom_bw_flags(const u8 *nvm_chan,
+                                      int ch_idx, u16 nvm_flags)
+{
+       u32 flags = NL80211_RRF_NO_HT40;
+
+       if (ch_idx < NUM_2GHZ_CHANNELS &&
+           (nvm_flags & NVM_CHANNEL_40MHZ)) {
+               if (nvm_chan[ch_idx] <= LAST_2GHZ_HT_PLUS)
+                       flags &= ~NL80211_RRF_NO_HT40PLUS;
+               if (nvm_chan[ch_idx] >= FIRST_2GHZ_HT_MINUS)
+                       flags &= ~NL80211_RRF_NO_HT40MINUS;
+       } else if (nvm_chan[ch_idx] <= LAST_5GHZ_HT &&
+                  (nvm_flags & NVM_CHANNEL_40MHZ)) {
+               if ((ch_idx - NUM_2GHZ_CHANNELS) % 2 == 0)
+                       flags &= ~NL80211_RRF_NO_HT40PLUS;
+               else
+                       flags &= ~NL80211_RRF_NO_HT40MINUS;
+       }
+
+       if (!(nvm_flags & NVM_CHANNEL_80MHZ))
+               flags |= NL80211_RRF_NO_80MHZ;
+       if (!(nvm_flags & NVM_CHANNEL_160MHZ))
+               flags |= NL80211_RRF_NO_160MHZ;
+
+       if (!(nvm_flags & NVM_CHANNEL_IBSS))
+               flags |= NL80211_RRF_NO_IR;
+
+       if (!(nvm_flags & NVM_CHANNEL_ACTIVE))
+               flags |= NL80211_RRF_NO_IR;
+
+       if (nvm_flags & NVM_CHANNEL_RADAR)
+               flags |= NL80211_RRF_DFS;
+
+       if (nvm_flags & NVM_CHANNEL_INDOOR_ONLY)
+               flags |= NL80211_RRF_NO_OUTDOOR;
+
+       /* Set the GO concurrent flag only in case that NO_IR is set.
+        * Otherwise it is meaningless
+        */
+       if ((nvm_flags & NVM_CHANNEL_GO_CONCURRENT) &&
+           (flags & NL80211_RRF_NO_IR))
+               flags |= NL80211_RRF_GO_CONCURRENT;
+
+       return flags;
+}
+
+struct ieee80211_regdomain *
+iwl_parse_nvm_mcc_info(struct device *dev, int num_of_ch, __le32 *channels,
+                      u16 fw_mcc)
+{
+       int ch_idx;
+       u16 ch_flags, prev_ch_flags = 0;
+       const u8 *nvm_chan = iwl_nvm_channels; /* TODO: 8000 series differs */
+       struct ieee80211_regdomain *regd;
+       int size_of_regd;
+       struct ieee80211_reg_rule *rule;
+       enum ieee80211_band band;
+       int center_freq, prev_center_freq = 0;
+       int valid_rules = 0;
+       bool new_rule;
+
+       if (WARN_ON_ONCE(num_of_ch > NL80211_MAX_SUPP_REG_RULES))
+               return ERR_PTR(-EINVAL);
+
+       IWL_DEBUG_DEV(dev, IWL_DL_LAR, "building regdom for %d channels\n",
+                     num_of_ch);
+
+       /* build a regdomain rule for every valid channel */
+       size_of_regd =
+               sizeof(struct ieee80211_regdomain) +
+               num_of_ch * sizeof(struct ieee80211_reg_rule);
+
+       regd = kzalloc(size_of_regd, GFP_KERNEL);
+       if (!regd)
+               return ERR_PTR(-ENOMEM);
+
+       for (ch_idx = 0; ch_idx < num_of_ch; ch_idx++) {
+               ch_flags = (u16)__le32_to_cpup(channels + ch_idx);
+               band = (ch_idx < NUM_2GHZ_CHANNELS) ?
+                      IEEE80211_BAND_2GHZ : IEEE80211_BAND_5GHZ;
+               center_freq = ieee80211_channel_to_frequency(nvm_chan[ch_idx],
+                                                            band);
+               new_rule = false;
+
+               if (!(ch_flags & NVM_CHANNEL_VALID)) {
+                       IWL_DEBUG_DEV(dev, IWL_DL_LAR,
+                                     "Ch. %d Flags %x [%sGHz] - No traffic\n",
+                                     nvm_chan[ch_idx],
+                                     ch_flags,
+                                     (ch_idx >= NUM_2GHZ_CHANNELS) ?
+                                     "5.2" : "2.4");
+                       continue;
+               }
+
+               /* we can't continue the same rule */
+               if (ch_idx == 0 || prev_ch_flags != ch_flags ||
+                   center_freq - prev_center_freq > 20) {
+                       valid_rules++;
+                       new_rule = true;
+               }
+
+               rule = &regd->reg_rules[valid_rules - 1];
+
+               if (new_rule)
+                       rule->freq_range.start_freq_khz =
+                                               MHZ_TO_KHZ(center_freq - 10);
+
+               rule->freq_range.end_freq_khz = MHZ_TO_KHZ(center_freq + 10);
+
+               /* this doesn't matter - not used by FW */
+               rule->power_rule.max_antenna_gain = DBI_TO_MBI(6);
+               rule->power_rule.max_eirp = DBM_TO_MBM(20);
+
+               rule->flags = iwl_nvm_get_regdom_bw_flags(nvm_chan, ch_idx,
+                                                         ch_flags);
+
+               /* rely on auto-calculation to merge BW of contiguous chans */
+               rule->flags |= NL80211_RRF_AUTO_BW;
+               rule->freq_range.max_bandwidth_khz = 0;
+
+               prev_ch_flags = ch_flags;
+               prev_center_freq = center_freq;
+
+               IWL_DEBUG_DEV(dev, IWL_DL_LAR,
+                             "Ch. %d [%sGHz] %s%s%s%s%s%s%s%s%s%s(0x%02x): Ad-Hoc %ssupported\n",
+                             center_freq,
+                             band == IEEE80211_BAND_5GHZ ? "5.2" : "2.4",
+                             CHECK_AND_PRINT_I(VALID),
+                             CHECK_AND_PRINT_I(IBSS),
+                             CHECK_AND_PRINT_I(ACTIVE),
+                             CHECK_AND_PRINT_I(RADAR),
+                             CHECK_AND_PRINT_I(WIDE),
+                             CHECK_AND_PRINT_I(40MHZ),
+                             CHECK_AND_PRINT_I(80MHZ),
+                             CHECK_AND_PRINT_I(160MHZ),
+                             CHECK_AND_PRINT_I(INDOOR_ONLY),
+                             CHECK_AND_PRINT_I(GO_CONCURRENT),
+                             ch_flags,
+                             ((ch_flags & NVM_CHANNEL_IBSS) &&
+                              !(ch_flags & NVM_CHANNEL_RADAR))
+                                        ? "" : "not ");
+       }
+
+       regd->n_reg_rules = valid_rules;
+
+       /* set alpha2 from FW. */
+       regd->alpha2[0] = fw_mcc >> 8;
+       regd->alpha2[1] = fw_mcc & 0xff;
+
+       return regd;
+}
+IWL_EXPORT_SYMBOL(iwl_parse_nvm_mcc_info);
index c9c45a3..fa493ce 100644 (file)
@@ -62,6 +62,7 @@
 #ifndef __iwl_nvm_parse_h__
 #define __iwl_nvm_parse_h__
 
+#include <net/cfg80211.h>
 #include "iwl-eeprom-parse.h"
 
 /**
@@ -78,4 +79,17 @@ iwl_parse_nvm_data(struct device *dev, const struct iwl_cfg *cfg,
                   const __le16 *nvm_calib, const __le16 *regulatory,
                   const __le16 *mac_override, u8 tx_chains, u8 rx_chains);
 
+/**
+ * iwl_parse_mcc_info - parse MCC (mobile country code) info coming from FW
+ *
+ * This function parses the regulatory channel data received as a
+ * MCC_UPDATE_CMD command. It returns a newly allocation regulatory domain,
+ * to be fed into the regulatory core. An ERR_PTR is returned on error.
+ * If not given to the regulatory core, the user is responsible for freeing
+ * the regdomain returned here with kfree.
+ */
+struct ieee80211_regdomain *
+iwl_parse_nvm_mcc_info(struct device *dev, int num_of_ch, __le32 *channels,
+                      u16 fw_mcc);
+
 #endif /* __iwl_nvm_parse_h__ */