new file mode 100644
@@ -0,0 +1,301 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
+/* Copyright(c) 2019-2022, Celeno Communications Ltd. */
+
+#include "chip.h"
+#include "dfs.h"
+#include "core.h"
+#include "debug.h"
+#include "hw.h"
+#include "utils.h"
+#include "regdom.h"
+
+static struct ieee80211_regdomain cl_regdom_24g = {
+ .n_reg_rules = 2,
+ .alpha2 = "99",
+ .reg_rules = {
+ REG_RULE(2412 - 10, 2472 + 10, 40, 6, 20, 0),
+ REG_RULE(2484 - 10, 2484 + 10, 20, 6, 20, 0),
+ }
+};
+
+static struct ieee80211_regdomain cl_regdom_5g = {
+ .n_reg_rules = 1,
+ .alpha2 = "99",
+ .reg_rules = {
+ REG_RULE(5150 - 10, 5850 + 10, 80, 6, 30, 0),
+ }
+};
+
+static struct ieee80211_regdomain cl_regdom_6g = {
+ .n_reg_rules = 1,
+ .alpha2 = "99",
+ .reg_rules = {
+ REG_RULE(5935 - 10, 7115 + 10, 80, 6, 30, 0),
+ }
+};
+
+static int cl_regd_is_legal_bw(int freq_diff)
+{
+ int bw = 0;
+
+ for (bw = CHNL_BW_20; bw < CHNL_BW_MAX; bw++)
+ if (freq_diff == BW_TO_KHZ(bw))
+ return bw;
+
+ return -EINVAL;
+}
+
+static int cl_regd_domain_update_rule(struct cl_hw *cl_hw, struct ieee80211_regdomain *rd,
+ int freq, int power, u8 max_bw, u32 flags, u32 dfs_cac_ms)
+{
+ struct ieee80211_reg_rule *reg_rule = &rd->reg_rules[rd->n_reg_rules - 1];
+ struct ieee80211_power_rule *power_rule = ®_rule->power_rule;
+ int bw, diff;
+
+ reg_rule->freq_range.end_freq_khz = MHZ_TO_KHZ(freq + 10);
+ if (power_rule->max_eirp < DBM_TO_MBM(power))
+ power_rule->max_eirp = DBM_TO_MBM(power);
+
+ diff = reg_rule->freq_range.end_freq_khz - reg_rule->freq_range.start_freq_khz;
+ /* if freq diff is equal to legal BW then update max_bandwidth_khz */
+ bw = cl_regd_is_legal_bw(diff);
+ if (bw >= 0)
+ reg_rule->freq_range.max_bandwidth_khz = BW_TO_KHZ(min((u8)bw, max_bw));
+
+ reg_rule->flags |= flags;
+ reg_rule->dfs_cac_ms = max_t(u32, reg_rule->dfs_cac_ms, dfs_cac_ms);
+
+ return diff;
+}
+
+/*
+ * Add first rule with minimal BW and increase then in cl_regd_domain_update_rule
+ * if new freq range will added
+ */
+static void cl_regd_domain_add_rule(struct cl_hw *cl_hw, struct ieee80211_regdomain *rd,
+ int freq, int max_power, u8 min_bw, u32 flags, u32 dfs_cac_ms)
+{
+ struct ieee80211_reg_rule *reg_rule = &rd->reg_rules[rd->n_reg_rules];
+ struct ieee80211_freq_range *freq_range = ®_rule->freq_range;
+ struct ieee80211_power_rule *power_rule = ®_rule->power_rule;
+
+ freq_range->start_freq_khz = MHZ_TO_KHZ(freq - 10);
+ freq_range->end_freq_khz = MHZ_TO_KHZ(freq + 10);
+ freq_range->max_bandwidth_khz = BW_TO_KHZ(min_bw);
+
+ power_rule->max_eirp = DBM_TO_MBM(max_power);
+ power_rule->max_antenna_gain = DBI_TO_MBI(3);
+
+ reg_rule->flags |= flags;
+ reg_rule->dfs_cac_ms = dfs_cac_ms;
+
+ rd->n_reg_rules++;
+}
+
+static u32 cl_regd_map_reg_flags(u32 reg_flags)
+{
+ u32 flags = 0;
+
+ if (reg_flags & IEEE80211_CHAN_NO_IR)
+ flags = NL80211_RRF_NO_IR;
+
+ if (reg_flags & IEEE80211_CHAN_RADAR)
+ flags |= NL80211_RRF_DFS;
+
+ if (reg_flags & IEEE80211_CHAN_NO_OFDM)
+ flags |= NL80211_RRF_NO_OFDM;
+
+ if (reg_flags & IEEE80211_CHAN_INDOOR_ONLY)
+ flags |= NL80211_RRF_NO_OUTDOOR;
+
+ if (reg_flags & (IEEE80211_CHAN_NO_HT40PLUS | IEEE80211_CHAN_NO_HT40MINUS))
+ flags |= NL80211_RRF_NO_HT40;
+
+ if (reg_flags & IEEE80211_CHAN_NO_80MHZ)
+ flags |= NL80211_RRF_NO_80MHZ;
+
+ if (reg_flags & IEEE80211_CHAN_NO_160MHZ)
+ flags |= NL80211_RRF_NO_160MHZ;
+
+ return flags;
+}
+
+void cl_regd_set(struct cl_hw *cl_hw, struct ieee80211_regdomain *rd,
+ struct regulatory_request *request)
+{
+ int j = 0;
+ int power = 0, prev_power = 0;
+ u8 bw = 0, prev_bw = 0;
+ int freq = 0, prev_freq = 0;
+ u8 chan = 0;
+ u32 flags = 0, prev_flags = 0;
+ u32 dfs_cac_ms = 0;
+
+ spin_lock_bh(&cl_hw->channel_info_lock);
+
+ memset(rd, 0, sizeof(*rd) + NL80211_MAX_SUPP_REG_RULES * sizeof(struct ieee80211_reg_rule));
+ memcpy(rd->alpha2, request->alpha2, 2);
+
+ rd->dfs_region = request->dfs_region;
+
+ if (request->dfs_region == NL80211_DFS_FCC)
+ cl_hw->channel_info.standard = NL80211_DFS_FCC;
+ else if (request->dfs_region == NL80211_DFS_ETSI)
+ cl_hw->channel_info.standard = NL80211_DFS_ETSI;
+ else
+ cl_hw->channel_info.standard = NL80211_DFS_UNSET;
+
+ for (j = 0; j < cl_channel_num(cl_hw); j++) {
+ struct cl_chan_info *chan_info = &cl_hw->channel_info.channels[CHNL_BW_20][j];
+
+ chan = chan_info->channel;
+ if (!chan)
+ continue;
+
+ /* Translate from country_power (.25dBm) to max_power (1dBm) */
+ power = cl_hw->channel_info.channels[CHNL_BW_20][j].country_max_power_q2 >> 2;
+ bw = cl_chan_info_get_max_bw(cl_hw, chan);
+ freq = ieee80211_channel_to_frequency(chan, cl_hw->nl_band);
+ flags = cl_regd_map_reg_flags(chan_info->flags);
+ dfs_cac_ms = chan_info->dfs_cac_ms;
+ if (freq - prev_freq > 20 || prev_power != power || prev_bw != bw ||
+ prev_flags != flags)
+ cl_regd_domain_add_rule(cl_hw, rd, freq, power,
+ CHNL_BW_20, flags, dfs_cac_ms);
+ else
+ cl_regd_domain_update_rule(cl_hw, rd, freq, power, bw, flags, dfs_cac_ms);
+
+ prev_freq = freq;
+ prev_power = power;
+ prev_bw = bw;
+ prev_flags = flags;
+ }
+
+ spin_unlock_bh(&cl_hw->channel_info_lock);
+}
+
+static void cl_regd_update_channels(struct cl_hw *cl_hw, struct wiphy *wiphy)
+{
+ enum nl80211_band band;
+ const struct ieee80211_supported_band *cfg_band = NULL;
+
+ for (band = 0; band < NUM_NL80211_BANDS; band++) {
+ if (band != cl_hw->nl_band)
+ continue;
+
+ cfg_band = wiphy->bands[band];
+ if (!cfg_band)
+ continue;
+
+ cl_chan_update_channels_info(cl_hw, cfg_band);
+ }
+}
+
+static void cl_regd_set_by_user(struct cl_hw *cl_hw, struct wiphy *wiphy,
+ struct regulatory_request *request)
+{
+ if (!cl_hw->channel_info.use_channel_info)
+ return;
+
+ cl_regd_update_channels(cl_hw, wiphy);
+ /*
+ * Here is updated cl_hw->channel_info,
+ * let's generates new regdom rules into cl_hw->channel_info.rd
+ */
+ cl_regd_set(cl_hw, cl_hw->channel_info.rd, request);
+ if (cl_band_is_5g(cl_hw))
+ cl_dfs_reinit(cl_hw);
+ /* TODO: calib callback for channels update */
+}
+
+static bool cl_regd_dyn_mode_enabled(struct cl_hw *cl_hw)
+{
+ return !strcmp(cl_hw->chip->conf->ci_regdom_mode, "auto");
+}
+
+static void cl_regd_notifier_apply(struct cl_hw *cl_hw,
+ struct wiphy *wiphy,
+ struct regulatory_request *request)
+{
+ if (!request)
+ return;
+
+ switch (request->initiator) {
+ case NL80211_REGDOM_SET_BY_CORE:
+ break;
+ case NL80211_REGDOM_SET_BY_DRIVER:
+ break;
+ case NL80211_REGDOM_SET_BY_USER:
+ if (cl_regd_dyn_mode_enabled(cl_hw))
+ cl_regd_set_by_user(cl_hw, wiphy, request);
+ break;
+ case NL80211_REGDOM_SET_BY_COUNTRY_IE:
+ break;
+ default:
+ break;
+ }
+}
+
+static void cl_regd_notifier(struct wiphy *wiphy,
+ struct regulatory_request *request)
+{
+ struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
+
+ cl_regd_notifier_apply(hw->priv, wiphy, request);
+}
+
+static int _cl_regd_init(struct cl_hw *cl_hw, struct wiphy *wiphy,
+ void (*reg_notifier)(struct wiphy *wiphy,
+ struct regulatory_request *request))
+{
+ if (cl_regd_dyn_mode_enabled(cl_hw)) {
+ const struct ieee80211_regdomain *regd = cl_hw->channel_info.rd;
+
+ wiphy->reg_notifier = reg_notifier;
+ wiphy->regulatory_flags |= REGULATORY_CUSTOM_REG |
+ REGULATORY_DISABLE_BEACON_HINTS |
+ REGULATORY_COUNTRY_IE_IGNORE;
+
+ wiphy_apply_custom_regulatory(wiphy, regd);
+
+ return 0;
+ }
+
+ /* default is self managed mode */
+ wiphy->regulatory_flags |= REGULATORY_WIPHY_SELF_MANAGED;
+
+ return regulatory_set_wiphy_regd(wiphy, cl_hw->channel_info.rd);
+}
+
+int cl_regd_init(struct cl_hw *cl_hw, struct wiphy *wiphy)
+{
+ if (cl_hw->channel_info.use_channel_info) {
+ cl_hw->channel_info.rd = kzalloc(sizeof(*cl_hw->channel_info.rd) +
+ NL80211_MAX_SUPP_REG_RULES *
+ sizeof(struct ieee80211_reg_rule),
+ GFP_KERNEL);
+ if (!cl_hw->channel_info.rd) {
+ cl_dbg_err(cl_hw, "memory allocation failed!\n");
+ return -ENOMEM;
+ }
+
+ struct regulatory_request request = {
+ .alpha2[0] = cl_hw->chip->conf->ci_country_code[0],
+ .alpha2[1] = cl_hw->chip->conf->ci_country_code[1],
+ .alpha2[2] = 0,
+ .dfs_region = cl_hw->channel_info.standard,
+ };
+
+ cl_regd_set(cl_hw, cl_hw->channel_info.rd, &request);
+ } else {
+ if (cl_band_is_6g(cl_hw))
+ cl_hw->channel_info.rd = &cl_regdom_6g;
+ else if (cl_band_is_5g(cl_hw))
+ cl_hw->channel_info.rd = &cl_regdom_5g;
+ else
+ cl_hw->channel_info.rd = &cl_regdom_24g;
+ }
+
+ return _cl_regd_init(cl_hw, wiphy, cl_regd_notifier);
+}
+