diff mbox series

[RFC,v1,059/256] cl8k: add dfs/dfs.c

Message ID 20210617160223.160998-60-viktor.barna@celeno.com
State New
Headers show
Series wireless: cl8k driver for Celeno IEEE 802.11ax devices | expand

Commit Message

Viktor Barna June 17, 2021, 3:59 p.m. UTC
From: Viktor Barna <viktor.barna@celeno.com>

(Part of the split. Please, take a look at the cover letter for more
details).

Signed-off-by: Viktor Barna <viktor.barna@celeno.com>
---
 drivers/net/wireless/celeno/cl8k/dfs/dfs.c | 977 +++++++++++++++++++++
 1 file changed, 977 insertions(+)
 create mode 100644 drivers/net/wireless/celeno/cl8k/dfs/dfs.c

--
2.30.0
diff mbox series

Patch

diff --git a/drivers/net/wireless/celeno/cl8k/dfs/dfs.c b/drivers/net/wireless/celeno/cl8k/dfs/dfs.c
new file mode 100644
index 000000000000..b2717a8be26a
--- /dev/null
+++ b/drivers/net/wireless/celeno/cl8k/dfs/dfs.c
@@ -0,0 +1,977 @@ 
+// SPDX-License-Identifier: MIT
+/* Copyright(c) 2019-2021, Celeno Communications Ltd. */
+
+#include "dfs/dfs.h"
+#include "dfs/dfs_db.h"
+#include "dfs/radar.h"
+#include "chip.h"
+#include "channel.h"
+#include "chan_info.h"
+#include "env_det.h"
+#include "debug.h"
+#include "utils/timer.h"
+#include "tx/tx.h"
+#include "temperature.h"
+#include "utils/math.h"
+#include "calib.h"
+#include "traffic.h"
+#include "utils/utils.h"
+#include "reg/reg_riu.h"
+#include "reg/reg_mac_hw.h"
+#include "band.h"
+#include "chandef.h"
+#include "utils/string.h"
+#include "utils/file.h"
+#include "config.h"
+
+#define dfs_pr(cl_hw, level, ...) \
+       do { \
+               if ((level) <= (cl_hw)->dfs_db.dbg_lvl) \
+                       pr_debug(__VA_ARGS__); \
+       } while (0)
+
+#define dfs_pr_verbose(cl_hw, ...) dfs_pr((cl_hw), DBG_LVL_VERBOSE, ##__VA_ARGS__)
+#define dfs_pr_err(cl_hw, ...)     dfs_pr((cl_hw), DBG_LVL_ERROR, ##__VA_ARGS__)
+#define dfs_pr_warn(cl_hw, ...)    dfs_pr((cl_hw), DBG_LVL_WARNING, ##__VA_ARGS__)
+#define dfs_pr_trace(cl_hw, ...)   dfs_pr((cl_hw), DBG_LVL_TRACE, ##__VA_ARGS__)
+#define dfs_pr_info(cl_hw, ...)    dfs_pr((cl_hw), DBG_LVL_INFO, ##__VA_ARGS__)
+
+#define COUNTRY_CODE_LEN 2
+
+/*
+ * ID  Min    Max    Tol    Min  Max  Tol  Tol   MIN    PPB  Trig   Type
+ *     Width  Width  Width  PRI  PRI  PRI  FREQ  Burst       Count
+ */
+
+/* ETSI Radar Types v1.8.2 */
+static struct cl_radar_type radar_type_etsi[] = {
+
+       {0, 1,  1,  1, 1428, 1428, 1, 1, 1, 18, 10, RADAR_WAVEFORM_SHORT},
+       {1, 1,  5,  1, 1000, 5000, 1, 1, 1, 10, 5,  RADAR_WAVEFORM_SHORT},
+       {2, 1,  15, 1, 625,  5000, 1, 1, 1, 15, 8,  RADAR_WAVEFORM_SHORT},
+       {3, 1,  15, 1, 250,  435,  1, 1, 1, 25, 10, RADAR_WAVEFORM_SHORT},
+       {4, 10, 30, 1, 250,  500,  1, 1, 1, 20, 10, RADAR_WAVEFORM_SHORT},
+       {5, 1,  5,  1, 2500, 3334, 1, 1, 2, 10, 5,  RADAR_WAVEFORM_STAGGERED},
+       {6, 1,  5,  1, 833,  2500, 1, 1, 2, 15, 8,  RADAR_WAVEFORM_STAGGERED},
+};
+
+/* FCC Radar Types 8/14 */
+static struct cl_radar_type radar_type_fcc[] = {
+       {0, 1,  1,   0,  1428, 1428, 1, 1, 1, 18, 10, RADAR_WAVEFORM_SHORT},
+       {1, 1,  5,   3,  518,  3066, 3, 1, 1, 18, 10, RADAR_WAVEFORM_SHORT},
+       {2, 1,  5,   3,  150,  230,  3, 1, 1, 23, 10, RADAR_WAVEFORM_SHORT},
+       {3, 3,  10,  3,  200,  500,  3, 1, 1, 16, 6,  RADAR_WAVEFORM_SHORT},
+       {4, 6,  20,  3,  200,  500,  3, 1, 1, 12, 6,  RADAR_WAVEFORM_SHORT},
+       {5, 50, 100, 50, 1000, 2000, 1, 1, 2, 10, 5,  RADAR_WAVEFORM_LONG},
+       {6, 1,  1,   0,  333,  333,  1, 1, 2, 30, 10, RADAR_WAVEFORM_LONG},
+};
+
+static void cl_dfs_fw_en(struct cl_hw *cl_hw, u8 dfs_en)
+{
+       struct cl_dfs_db *dfs_db = &cl_hw->dfs_db;
+       struct cl_tcv_conf *conf = cl_hw->conf;
+
+       cl_msg_tx_set_dfs(cl_hw, dfs_en, dfs_db->dfs_standard,
+                         conf->ci_dfs_initial_gain, conf->ci_dfs_agc_cd_th);
+}
+
+static int cl_dfs_print_tbl(struct cl_hw *cl_hw)
+{
+       int i;
+       struct cl_dfs_db *dfs_db = &cl_hw->dfs_db;
+       char *buf = NULL;
+       ssize_t buf_size;
+       int err = 0;
+       int len = 0;
+
+       cl_snprintf(&buf, &len, &buf_size,
+                   "-------------------------------------------------------------------------"
+                   "---------------\n"
+                   "|    | Min   | Max   | Tol   |  Min  |  Max  | Tol | Tol  | Min   |     |"
+                   " Trig  |      |\n"
+                   "| ID | Width | Width | Width |  PRI  |  PRI  | PRI | FREQ | Burst | PPB |"
+                   " Count | Type |\n"
+                   "-------------------------------------------------------------------------"
+                   "---------------\n");
+
+       for (i = 0; i < dfs_db->radar_type_cnt; i++) {
+               cl_snprintf(&buf, &len, &buf_size,
+                           "| %2u | %5d  | %5d  | %5d  | %5d  | %5d  | %3d  | %4d  | %5u |"
+                           " %3u | %5u | %4d |\n",
+                           dfs_db->radar_type[i].id,
+                           dfs_db->radar_type[i].min_width,
+                           dfs_db->radar_type[i].max_width,
+                           dfs_db->radar_type[i].tol_width,
+                           dfs_db->radar_type[i].min_pri,
+                           dfs_db->radar_type[i].max_pri,
+                           dfs_db->radar_type[i].tol_pri,
+                           dfs_db->radar_type[i].tol_freq,
+                           dfs_db->radar_type[i].min_burst,
+                           dfs_db->radar_type[i].ppb,
+                           dfs_db->radar_type[i].trig_count,
+                           dfs_db->radar_type[i].waveform);
+               cl_snprintf(&buf, &len, &buf_size,
+                           "-----------------------------------------------------------------"
+                           "-----------------------\n");
+       }
+
+       err = cl_vendor_reply(cl_hw, buf, len);
+       kfree(buf);
+
+       return err;
+}
+
+static bool cl_dfs_create_detection_buffer(struct cl_hw *cl_hw, struct cl_dfs_db *dfs_db,
+                                          struct cl_dfs_pulse *pulse_buffer, u8 *samples_cnt,
+                                          unsigned long time)
+{
+       u8 i;
+       u8 pulse_idx;
+       /* Init First index to last */
+       u8 first_pulse_idx = (dfs_db->buf_idx - 1 + CL_DFS_PULSE_BUF_SIZE) & CL_DFS_PULSE_BUF_MASK;
+
+       /* Find Start Pulse indexes */
+       for (i = 0; i < CL_DFS_PULSE_BUF_SIZE; i++) {
+               pulse_idx = (i + dfs_db->buf_idx) & CL_DFS_PULSE_BUF_MASK;
+
+               if ((time - dfs_db->dfs_pulse[pulse_idx].time) < dfs_db->search_window) {
+                       first_pulse_idx = pulse_idx;
+                       break;
+               }
+       }
+
+       dfs_pr_info(cl_hw, "DFS: First pulse idx = %u, Last  pulse idx = %u\n",
+                   first_pulse_idx, dfs_db->buf_idx - 1);
+
+       if (dfs_db->buf_idx - 1 >= first_pulse_idx)
+               if ((dfs_db->buf_idx - first_pulse_idx - 1) < (dfs_db->min_pulse_eeq - 1)) {
+                       /* Return if buffer don't hold enough valid samples */
+                       dfs_pr_warn(cl_hw, "DFS: Not enough pulses in buffer\n");
+
+                       return false;
+               }
+
+       /* Copy the processed samples to local Buf to avoid index castings */
+       for (i = 0; pulse_idx != ((dfs_db->buf_idx - 1 + CL_DFS_PULSE_BUF_SIZE)
+               & CL_DFS_PULSE_BUF_MASK); i++) {
+               pulse_idx = (i + first_pulse_idx) & CL_DFS_PULSE_BUF_MASK;
+               memcpy(&pulse_buffer[i], &dfs_db->dfs_pulse[pulse_idx], sizeof(pulse_buffer[i]));
+       }
+       *samples_cnt = i + 1;
+
+       return true;
+}
+
+static void cl_dfs_add_pulses_to_global_buffer(struct cl_hw *cl_hw, struct cl_dfs_db *dfs_db,
+                                              struct cl_radar_pulse *pulse, u8 pulse_cnt,
+                                              unsigned long time)
+{
+       int  i;
+
+       for (i = 0; i < pulse_cnt; i++)
+               dfs_pr_info(cl_hw, "Pulse=%d, Width=%u, PRI=%u, FREQ=%d, Time=%lu, FOM=%x\n",
+                           i, pulse[i].len, pulse[i].rep, pulse[i].freq, time, pulse[i].fom);
+
+       /* Maintain cyclic pulse buffer */
+       for (i = 0; i < pulse_cnt; i++) {
+               dfs_db->dfs_pulse[dfs_db->buf_idx].freq = pulse[i].freq;
+               dfs_db->dfs_pulse[dfs_db->buf_idx].width = pulse[i].len;
+               dfs_db->dfs_pulse[dfs_db->buf_idx].pri = pulse[i].rep;
+               dfs_db->dfs_pulse[dfs_db->buf_idx].occ = 0; /* occ temp disabled. */
+               dfs_db->dfs_pulse[dfs_db->buf_idx].time = time;
+
+               dfs_db->buf_idx++;
+               dfs_db->buf_idx &= CL_DFS_PULSE_BUF_MASK;
+       }
+}
+
+static bool cl_dfs_buf_maintain(struct cl_hw *cl_hw, struct cl_radar_pulse *pulse,
+                               struct cl_dfs_pulse *pulse_buffer, u8 pulse_cnt,
+                               unsigned long time, u8 *samples_cnt, struct cl_dfs_db *dfs_db)
+{
+       int  i;
+
+       cl_dfs_add_pulses_to_global_buffer(cl_hw, dfs_db, pulse, pulse_cnt, time);
+       if (!cl_dfs_create_detection_buffer(cl_hw, dfs_db, pulse_buffer, samples_cnt, time))
+               return false;
+
+       for (i = 0; i < *samples_cnt; i++)
+               dfs_pr_info(cl_hw, "DFS: pulse[%d]: width=%u, pri=%u, freq=%d\n",
+                           i, pulse_buffer[i].width, pulse_buffer[i].pri, pulse_buffer[i].freq);
+
+       return true;
+}
+
+static inline bool cl_dfs_pulse_match(s32 pulse_val, s32 spec_min_val,
+                                     s32 spec_max_val, s32 spec_tol)
+{
+       return ((pulse_val >= (spec_min_val - spec_tol)) &&
+               (pulse_val <= (spec_max_val + spec_tol)));
+}
+
+static u8 cl_dfs_is_stag_pulse(struct cl_hw *cl_hw, struct cl_dfs_db *dfs_db,
+                              struct cl_dfs_pulse *pulse)
+{
+       int  i;
+       struct cl_radar_type *radar_type;
+
+       for (i = 0; i < dfs_db->radar_type_cnt; i++) {
+               radar_type = &dfs_db->radar_type[i];
+
+               if (radar_type->waveform != RADAR_WAVEFORM_STAGGERED)
+                       continue;
+
+               if (cl_dfs_pulse_match((s32)pulse->width, radar_type->min_width,
+                                      radar_type->max_width, radar_type->tol_width) &&
+                   cl_dfs_pulse_match((s32)pulse->pri, radar_type->min_pri,
+                                      radar_type->max_pri, radar_type->tol_pri)) {
+                       /* Search for the second burst */
+                       if (abs(pulse[0].pri - pulse[2].pri) <= dfs_db->radar_type[i].tol_pri &&
+                           abs(pulse[1].pri - pulse[3].pri) <= radar_type->tol_pri &&
+                           abs(pulse[0].pri - pulse[1].pri) > radar_type->tol_pri &&
+                           abs(pulse[2].pri - pulse[3].pri) > radar_type->tol_pri) {
+                               dfs_pr_info(cl_hw, "DFS: Found match type %d\n", i);
+                               return (i + 1);
+                       } else if (abs(pulse[0].pri - pulse[3].pri) <= radar_type->tol_pri &&
+                                  abs(pulse[1].pri - pulse[4].pri) <= radar_type->tol_pri &&
+                                  abs(pulse[0].pri - pulse[1].pri) > radar_type->tol_pri &&
+                                  abs(pulse[3].pri - pulse[4].pri) > radar_type->tol_pri) {
+                               dfs_pr_info(cl_hw, "DFS: Found match radar %d\n", i);
+                               return (i + 1);
+                       }
+               }
+       }
+
+       return 0;
+}
+
+static u8 cl_dfs_is_non_stag_pulse(struct cl_hw *cl_hw, struct cl_dfs_db *dfs_db,
+                                  struct cl_dfs_pulse *pulse)
+{
+       int  i;
+       struct cl_radar_type *radar_type;
+
+       for (i = 0; i < dfs_db->radar_type_cnt; i++) {
+               radar_type = &dfs_db->radar_type[i];
+
+               if (radar_type->waveform == RADAR_WAVEFORM_STAGGERED)
+                       continue;
+
+               if (cl_dfs_pulse_match((s32)pulse->width, radar_type->min_width,
+                                      radar_type->max_width, radar_type->tol_width) &&
+                   cl_dfs_pulse_match((s32)pulse->pri, radar_type->min_pri,
+                                      radar_type->max_pri, radar_type->tol_pri)) {
+                       dfs_pr_info(cl_hw, "DFS: Found match type %d\n", i);
+                       return (i + 1);
+               }
+       }
+
+       dfs_pr_warn(cl_hw, "DFS: Match not found\n");
+
+       return 0;
+}
+
+static u8 cl_dfs_get_pulse_type(struct cl_hw *cl_hw, struct cl_dfs_pulse *pulse,
+                               bool stag_candidate)
+{
+       struct cl_dfs_db *dfs_db = &cl_hw->dfs_db;
+
+       if (stag_candidate) {
+               u8 pulse_type = cl_dfs_is_stag_pulse(cl_hw, dfs_db, pulse);
+
+               if (pulse_type)
+                       return pulse_type;
+       }
+
+       return cl_dfs_is_non_stag_pulse(cl_hw, dfs_db, pulse);
+}
+
+static bool cl_dfs_compare_cand(struct cl_hw *cl_hw, struct cl_dfs_db *dfs_db, u8 pulse_type,
+                               struct cl_dfs_pulse radar_cand, u8 *match, int idx,
+                               u8 *occ_ch_cand)
+{
+       int i;
+
+       if (!(abs(dfs_db->pulse_buffer[idx].width - radar_cand.width) <=
+           dfs_db->radar_type[pulse_type].tol_width))
+               goto end;
+
+       if (!(abs(dfs_db->pulse_buffer[idx].freq - radar_cand.freq) <=
+           dfs_db->radar_type[pulse_type].tol_freq))
+               goto end;
+
+       for (i = 1; i < CL_DFS_CONCEAL_CNT; i++)
+               if (abs(dfs_db->pulse_buffer[idx].pri - i * radar_cand.pri) <=
+                   dfs_db->radar_type[pulse_type].tol_pri)
+                       break;
+
+       if (i == CL_DFS_CONCEAL_CNT)
+               goto end;
+
+       (*match)++;
+       (*occ_ch_cand) += dfs_db->pulse_buffer[i].occ;
+
+end:
+       dfs_pr_info(cl_hw, "DFS: compared pulse - width=%u, pri=%u, freq=%u match: %u "
+                   "trig cnt: %u\n",
+                   dfs_db->pulse_buffer[idx].width, dfs_db->pulse_buffer[idx].pri,
+                   dfs_db->pulse_buffer[idx].freq, *match,
+                   dfs_db->radar_type[pulse_type].trig_count);
+
+       if (*match < dfs_db->radar_type[pulse_type].trig_count)
+               return false;
+
+       return true;
+}
+
+static bool cl_dfs_check_cand(struct cl_hw *cl_hw, struct cl_dfs_db *dfs_db, u8 pulse_type,
+                             struct cl_dfs_pulse radar_cand, u8 samples_cnt)
+{
+       u8 occ_ch_cand = 0;
+       u8 match = 0;
+       int i;
+
+       dfs_pr_info(cl_hw, "DFS: candidate pulse - width=%u, pri=%u, freq=%u\n",
+                   radar_cand.width, radar_cand.pri, radar_cand.freq);
+
+       for (i = 0; i < samples_cnt; i++) {
+               if (!cl_dfs_compare_cand(cl_hw, dfs_db, pulse_type, radar_cand, &match, i,
+                                        &occ_ch_cand))
+                       continue;
+
+               dfs_pr_verbose(cl_hw, "DFS: Radar detected - type %u\n", pulse_type);
+               return true;
+       }
+
+       return false;
+}
+
+static bool cl_dfs_short_pulse_search(struct cl_hw *cl_hw, struct cl_radar_pulse *pulse,
+                                     u8 pulse_cnt, unsigned long time, struct cl_dfs_db *dfs_db)
+{
+       int i;
+       bool stag_candidate;
+       u8 samples_cnt = 0;
+       u8 pulse_type;
+
+       /* Return if not enough pulses in the buffer */
+       if (!cl_dfs_buf_maintain(cl_hw, pulse, dfs_db->pulse_buffer, pulse_cnt, time,
+                                &samples_cnt, dfs_db))
+               return false;
+
+       for (i = 0; i < samples_cnt; i++) {
+               struct cl_dfs_pulse radar_cand;
+
+               stag_candidate = false;
+
+               /* Make sure there is enough samples to staggered check */
+               if (dfs_db->dfs_standard == CL_STANDARD_ETSI &&
+                   (samples_cnt - i) > CL_DFS_STAGGERED_CHEC_LEN)
+                       stag_candidate = true;
+
+               pulse_type = cl_dfs_get_pulse_type(cl_hw, &dfs_db->pulse_buffer[i], stag_candidate);
+
+               if (!pulse_type)
+                       continue;
+
+               radar_cand.width = dfs_db->pulse_buffer[i].width;
+               radar_cand.pri = dfs_db->pulse_buffer[i].pri;
+               radar_cand.freq = dfs_db->pulse_buffer[i].freq;
+
+               if (cl_dfs_check_cand(cl_hw, dfs_db, pulse_type - 1, radar_cand, samples_cnt))
+                       return true;
+       }
+
+       return false;
+}
+
+static bool cl_dfs_long_pulse_search(struct cl_hw *cl_hw, struct cl_radar_pulse *pulse,
+                                    u8 pulse_cnt, unsigned long time)
+{
+       u32 prev_pulse_time_diff;
+       struct cl_dfs_db *dfs_db = &cl_hw->dfs_db;
+       struct cl_tcv_conf *conf = cl_hw->conf;
+       int i;
+
+       for (i = 0; i < pulse_cnt; i++) {
+               if (pulse[i].len > CL_DFS_LONG_MIN_WIDTH) {
+                       prev_pulse_time_diff = time - dfs_db->last_long_pulse_ts;
+
+                       if (pulse[i].rep >= dfs_db->radar_type[5].min_pri &&
+                           pulse[i].rep <= dfs_db->radar_type[5].max_pri)
+                               dfs_db->long_pri_match_count += 1;
+
+                       dfs_pr_info(cl_hw, "DFS: Long pulse search: width = %u, delta_time = %u\n",
+                                   pulse[i].len, prev_pulse_time_diff);
+
+                       if (dfs_db->long_pulse_count == 0 ||
+                           (prev_pulse_time_diff >= conf->ci_dfs_long_pulse_min &&
+                            prev_pulse_time_diff <= conf->ci_dfs_long_pulse_max)) {
+                               dfs_db->long_pulse_count += 1;
+                       } else if (prev_pulse_time_diff > min(dfs_db->max_interrupt_diff,
+                                                    conf->ci_dfs_long_pulse_min)) {
+                               dfs_db->long_pulse_count = 0;
+                               dfs_db->short_pulse_count = 0;
+                               dfs_db->long_pri_match_count = 0;
+                       }
+                       dfs_db->last_long_pulse_ts = time;
+               } else if (pulse[i].len < CL_DFS_LONG_FALSE_WIDTH) {
+                       dfs_db->short_pulse_count++;
+
+                       if (dfs_db->short_pulse_count > CL_DFS_LONG_FALSE_IND) {
+                               dfs_db->long_pulse_count = 0;
+                               dfs_db->short_pulse_count = 0;
+                               dfs_db->long_pri_match_count = 0;
+
+                               dfs_pr_warn(cl_hw, "DFS: Restart long sequence search\n");
+                       }
+               }
+       }
+
+       if (dfs_db->long_pulse_count >= dfs_db->radar_type[5].trig_count &&
+           dfs_db->long_pri_match_count >= (dfs_db->radar_type[5].trig_count - 1)) {
+               dfs_db->short_pulse_count = 0;
+               dfs_db->long_pulse_count = 0;
+               dfs_db->long_pri_match_count = 0;
+               return true;
+       } else {
+               return false;
+       }
+}
+
+static bool cl_dfs_post_detection(struct cl_hw *cl_hw)
+{
+       struct cl_dfs_db *dfs_db = &cl_hw->dfs_db;
+       struct cl_tcv_conf *conf = cl_hw->conf;
+
+       /* Make sure firmware sets the DFS registers */
+       cl_radar_flush(cl_hw);
+       cl_msg_tx_set_dfs(cl_hw, false, dfs_db->dfs_standard,
+                         conf->ci_dfs_initial_gain, conf->ci_dfs_agc_cd_th);
+
+       ieee80211_radar_detected(cl_hw->hw);
+
+       return true;
+}
+
+bool cl_dfs_pulse_process(struct cl_hw *cl_hw, struct cl_radar_pulse *pulse, u8 pulse_cnt,
+                         unsigned long time)
+{
+       struct cl_dfs_db *dfs_db = &cl_hw->dfs_db;
+
+       dfs_db->pulse_cnt += pulse_cnt;
+
+       if (dfs_db->dfs_standard == CL_STANDARD_FCC &&
+           cl_dfs_long_pulse_search(cl_hw, pulse, pulse_cnt, time)) {
+               dfs_pr_verbose(cl_hw, "DFS: Radar detected - long\n");
+               return cl_dfs_post_detection(cl_hw);
+       } else if (cl_dfs_short_pulse_search(cl_hw, pulse, pulse_cnt, time, dfs_db)) {
+               dfs_pr_verbose(cl_hw, "DFS: Radar detected - short\n");
+               return cl_dfs_post_detection(cl_hw);
+       }
+
+       return false;
+}
+
+static void cl_dfs_set_min_pulse(struct cl_hw *cl_hw)
+{
+       int i;
+       struct cl_dfs_db *dfs_db = &cl_hw->dfs_db;
+
+       dfs_db->min_pulse_eeq = U8_MAX;
+
+       for (i = 0; i < dfs_db->radar_type_cnt; i++) {
+               if (dfs_db->radar_type[i].trig_count < dfs_db->min_pulse_eeq)
+                       dfs_db->min_pulse_eeq = dfs_db->radar_type[i].trig_count;
+       }
+       dfs_db->min_pulse_eeq = max(dfs_db->min_pulse_eeq, (u8)CL_DFS_MIN_PULSE_TRIG);
+}
+
+static void cl_dfs_set_region(struct cl_hw *cl_hw, enum cl_reg_standard std)
+{
+       struct cl_dfs_db *dfs_db = &cl_hw->dfs_db;
+
+       dfs_db->dfs_standard = std;
+
+       if (dfs_db->dfs_standard == CL_STANDARD_FCC) {
+               dfs_db->csa_cnt = CL_DFS_FCC_CSA_CNT;
+               dfs_db->radar_type = radar_type_fcc;
+               dfs_db->radar_type_cnt = sizeof(radar_type_fcc) / sizeof(struct cl_radar_type);
+       } else {
+               dfs_db->csa_cnt = CL_DFS_CE_CSA_CNT;
+               dfs_db->radar_type = radar_type_etsi;
+               dfs_db->radar_type_cnt = sizeof(radar_type_etsi) / sizeof(struct cl_radar_type);
+       }
+}
+
+static void cl_dfs_start_cac(struct cl_dfs_db *db)
+{
+       db->cac.started = true;
+}
+
+static void cl_dfs_end_cac(struct cl_dfs_db *db)
+{
+       db->cac.started = false;
+}
+
+void cl_dfs_radar_listen_start(struct cl_hw *cl_hw)
+{
+       set_bit(CL_DEV_RADAR_LISTEN, &cl_hw->drv_flags);
+
+       cl_dfs_fw_en(cl_hw, true);
+
+       dfs_pr_verbose(cl_hw, "DFS: Started radar listening\n");
+}
+
+void cl_dfs_radar_listen_end(struct cl_hw *cl_hw)
+{
+       clear_bit(CL_DEV_RADAR_LISTEN, &cl_hw->drv_flags);
+
+       cl_dfs_fw_en(cl_hw, false);
+
+       dfs_pr_verbose(cl_hw, "DFS: Ended radar listening\n");
+}
+
+void cl_dfs_force_cac_start(struct cl_hw *cl_hw)
+{
+       bool is_listening = test_bit(CL_DEV_RADAR_LISTEN, &cl_hw->drv_flags);
+
+       cl_dfs_start_cac(&cl_hw->dfs_db);
+
+       /* Reset request state upon completion */
+       cl_dfs_request_cac(cl_hw, false);
+
+       /* Disable all the TX flow - be silent */
+       cl_tx_en(cl_hw, CL_TX_EN_DFS, false);
+
+       /* If for some reason we are still not listening radar, do it */
+       if (unlikely(!is_listening && cl_hw->hw->conf.radar_enabled))
+               cl_dfs_radar_listen_start(cl_hw);
+
+       dfs_pr_verbose(cl_hw, "DFS: CAC started\n");
+}
+
+void cl_dfs_force_cac_end(struct cl_hw *cl_hw)
+{
+       bool is_listening = test_bit(CL_DEV_RADAR_LISTEN, &cl_hw->drv_flags);
+
+       /* Enable all the TX flow */
+       cl_tx_en(cl_hw, CL_TX_EN_DFS, true);
+
+       /*
+        * If for some reason we are still listening and mac80211 does not
+        * require to listen radar - disable it
+        */
+       if (unlikely(is_listening && !cl_hw->hw->conf.radar_enabled))
+               cl_dfs_radar_listen_end(cl_hw);
+
+       cl_dfs_end_cac(&cl_hw->dfs_db);
+
+       dfs_pr_verbose(cl_hw, "DFS: CAC ended\n");
+}
+
+static u16 cl_dfs_get_remain_cac_time(struct cl_hw *cl_hw)
+{
+       struct cl_vif *cl_vif = cl_vif_get_first_ap(cl_hw);
+       struct wireless_dev *wdev = cl_vif ? ieee80211_vif_to_wdev(cl_vif->vif) : NULL;
+
+       if (wdev && wdev->cac_started)
+               return (jiffies_to_msecs(jiffies - wdev->cac_start_time) / 1000U);
+
+       return 0;
+}
+
+bool __must_check cl_dfs_is_en(struct cl_hw *cl_hw)
+{
+       return cl_hw->dfs_db.en;
+}
+
+bool __must_check cl_dfs_is_in_cac(struct cl_hw *cl_hw)
+{
+       return cl_hw->dfs_db.cac.started;
+}
+
+bool __must_check cl_dfs_radar_listening(struct cl_hw *cl_hw)
+{
+       return test_bit(CL_DEV_RADAR_LISTEN, &cl_hw->drv_flags);
+}
+
+bool __must_check cl_dfs_requested_cac(struct cl_hw *cl_hw)
+{
+       return cl_hw->dfs_db.cac.requested;
+}
+
+void cl_dfs_request_cac(struct cl_hw *cl_hw, bool should_do)
+{
+       cl_hw->dfs_db.cac.requested = should_do;
+}
+
+static void cl_dfs_edit_tbl(struct cl_hw *cl_hw, u8 row, u8 line, s16 val)
+{
+       struct cl_dfs_db *dfs_db = &cl_hw->dfs_db;
+
+       if (row > dfs_db->radar_type_cnt) {
+               dfs_pr_err(cl_hw, "Invalid row number (%u) [0 - %u]\n", line,
+                          dfs_db->radar_type_cnt - 1);
+               return;
+       }
+
+       if (line == 0 || line > CL_DFS_MAX_TBL_LINE) {
+               dfs_pr_err(cl_hw, "Invalid line number (%u) [1 - %u]\n", line,
+                          CL_DFS_MAX_TBL_LINE - 1);
+               return;
+       }
+
+       if (line == 1)
+               dfs_db->radar_type[row].min_width = (s32)val;
+       else if (line == 2)
+               dfs_db->radar_type[row].max_width = (s32)val;
+       else if (line == 3)
+               dfs_db->radar_type[row].tol_width = (s32)val;
+       else if (line == 4)
+               dfs_db->radar_type[row].min_pri = (s32)val;
+       else if (line == 5)
+               dfs_db->radar_type[row].max_pri = (s32)val;
+       else if (line == 6)
+               dfs_db->radar_type[row].tol_pri = (s32)val;
+       else if (line == 7)
+               dfs_db->radar_type[row].tol_freq = (s32)val;
+       else if (line == 8)
+               dfs_db->radar_type[row].min_burst = (u8)val;
+       else if (line == 9)
+               dfs_db->radar_type[row].ppb = (u8)val;
+       else if (line == 10)
+               dfs_db->radar_type[row].trig_count = (u8)val;
+       else if (line == 11)
+               dfs_db->radar_type[row].waveform = (enum cl_radar_waveform)val;
+
+       /* Verify if min_pulse_eeq was changed */
+       cl_dfs_set_min_pulse(cl_hw);
+}
+
+static void cl_dfs_tbl_overwrite_set(struct cl_hw *cl_hw)
+{
+       s8 *tok = NULL, *saveptr = NULL;
+       u8 param1 = 0;
+       u8 param2 = 0;
+       s16 param3 = 0;
+       char str[64];
+
+       if (strlen(cl_hw->conf->ce_dfs_tbl_overwrite) == 0)
+               return;
+
+       snprintf(str, sizeof(str), cl_hw->conf->ce_dfs_tbl_overwrite);
+
+       tok = cl_strtok_r(str, ",", &saveptr);
+       while (tok) {
+               if (sscanf(tok, "%hhd,%hhd,%hd", &param1, &param2, &param3) == 3)
+                       cl_dfs_edit_tbl(cl_hw, param1, param2, param3);
+               tok = cl_strtok_r(NULL, ",", &saveptr);
+       }
+}
+
+void cl_dfs_close(struct cl_hw *cl_hw)
+{
+       if (!cl_band_is_5g(cl_hw))
+               return;
+
+       cl_hw->dfs_db.en = false;
+}
+
+void cl_dfs_init(struct cl_hw *cl_hw)
+{
+       struct cl_dfs_db *dfs_db = &cl_hw->dfs_db;
+       struct cl_tcv_conf *conf = cl_hw->conf;
+
+       if (!cl_band_is_5g(cl_hw))
+               return;
+
+       dfs_db->en = conf->ci_ieee80211h;
+
+       /*
+        * Setting min window size to avoid the case where the second interrupt
+        * within the burst is setting the counter to 0. the max is between jiffies
+        * unit and max PRI in ms.
+        */
+       dfs_db->max_interrupt_diff = max(1000 / HZ, 2);
+       dfs_db->search_window = CL_DFS_PULSE_WINDOW;
+
+       cl_dfs_set_region(cl_hw, cl_hw->channel_info.standard);
+       cl_dfs_set_min_pulse(cl_hw);
+       cl_dfs_tbl_overwrite_set(cl_hw);
+}
+
+void cl_dfs_recovery(struct cl_hw *cl_hw)
+{
+       /* Re-enable DFS after recovery */
+       if (cl_dfs_is_in_cac(cl_hw)) {
+               cl_dfs_fw_en(cl_hw, true);
+
+               /* If recovery happened during CAC make sure to disable beacon backup */
+               cl_tx_en(cl_hw, CL_TX_EN_DFS, false);
+       }
+}
+
+static int cl_dfs_print_pulse_buffer(struct cl_hw *cl_hw, bool clear_buf)
+{
+       int i;
+       struct cl_dfs_db *dfs_db = &cl_hw->dfs_db;
+       char *buf = NULL;
+       ssize_t buf_size;
+       int err = 0;
+       int len = 0;
+
+       cl_snprintf(&buf, &len, &buf_size,
+                   "DFS Pulse Count = %u\n", dfs_db->pulse_cnt);
+
+       for (i = 0; i < ARRAY_SIZE(dfs_db->dfs_pulse); i++) {
+               cl_snprintf(&buf, &len, &buf_size,
+                           "Pulse Buffer: i=%d, Width=%u, PRI=%u, FREQ=%d, OCC=%u, Time=%lu\n",
+                           i, dfs_db->dfs_pulse[i].width,
+                           dfs_db->dfs_pulse[i].pri,
+                           dfs_db->dfs_pulse[i].freq,
+                           dfs_db->dfs_pulse[i].occ,
+                           dfs_db->dfs_pulse[i].time);
+       }
+
+       if (clear_buf) {
+               dfs_db->pulse_cnt = 0;
+               memset(dfs_db->dfs_pulse, 0, sizeof(dfs_db->dfs_pulse));
+       }
+
+       err = cl_vendor_reply(cl_hw, buf, len);
+       kfree(buf);
+
+       return err;
+}
+
+static void cl_dfs_cli_force_detection(struct cl_hw *cl_hw, struct cl_dfs_db *dfs_db)
+{
+       dfs_pr_verbose(cl_hw, "DFS: Force Radar Detection\n");
+       cl_dfs_post_detection(cl_hw);
+}
+
+static void cl_dfs_cli_set_cac(struct cl_hw *cl_hw, bool start)
+{
+       if (start)
+               cl_dfs_force_cac_start(cl_hw);
+       else
+               cl_dfs_force_cac_end(cl_hw);
+}
+
+static int cl_dfs_cli_help(struct cl_hw *cl_hw)
+{
+       char *buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
+       int err = 0;
+
+       if (!buf)
+               return -ENOMEM;
+
+       snprintf(buf, PAGE_SIZE,
+                "dfs usage:\n"
+                "-b : Print pulse buffer [(0)Read / (1)Read and clear]\n"
+                "-c : Stop/Start CAC[(0)Stop / (1)Start]\n"
+                "-d : DFS debug mode[0: Verbose 1: Error, 2: Warning, 3: Trace, 4: Info]\n"
+                "-e : Enable/Disable DFS [(0)Disable DFS / (1)Enable DFS]\n"
+                "-f : Force radar detection\n"
+                "-i : Set initial gain\n"
+                "-j : Set agc cd threshold\n"
+                "-k : return remaining cac time (in seconds)\n"
+                "-m : Set Min/Max Windows for Long radar types\n"
+                "-p : Print radar tables [0:Detection Table, 1:Channel info]\n"
+                "-q : Print jumpable channel list\n"
+                "-s : Simulate radar detection table "
+                       "[width][pri][freq][time]\n"
+                "-t : Edit radar detection table [row][line][value]\n"
+                "-w : Set search window size\n");
+
+       err = cl_vendor_reply(cl_hw, buf, strlen(buf));
+       kfree(buf);
+
+       return err;
+}
+
+int cl_dfs_cli(struct cl_hw *cl_hw, struct cli_params *cli_params, u8 *ret_buf, u16 *ret_buf_len)
+{
+       s32 *params = cli_params->params;
+       u32 expected_params = 0;
+       struct cl_dfs_db *dfs_db = &cl_hw->dfs_db;
+       struct cl_tcv_conf *conf = cl_hw->conf;
+       bool print_buf = false;
+       bool dbg_lvl = false;
+       bool en = false;
+       bool force = false;
+       bool print_tbl = false;
+       bool return_remain_cac_time = false;
+       bool radar_sim = false;
+       bool edit_tbl = false;
+       bool win_set = false;
+       bool cac = false;
+       bool long_prms = false;
+       bool init_gain = false;
+       bool agc_cd_hh = false;
+
+       switch (cli_params->option) {
+       case 'b':
+               print_buf = true;
+               expected_params = 1;
+               break;
+       case 'c':
+               cac = true;
+               expected_params = 1;
+               break;
+       case 'd':
+               dbg_lvl = true;
+               expected_params = 1;
+               break;
+       case 'e':
+               en = true;
+               expected_params = 1;
+               break;
+       case 'f':
+               force = true;
+               expected_params = 0;
+               break;
+       case 'i':
+               init_gain = true;
+               expected_params = 1;
+               break;
+       case 'j':
+               agc_cd_hh = true;
+               expected_params = 1;
+               break;
+       case 'k':
+               return_remain_cac_time = true;
+               expected_params = 0;
+               break;
+       case 'm':
+               long_prms = true;
+               expected_params = 2;
+               break;
+       case 'p':
+               print_tbl = true;
+               expected_params = 1;
+               break;
+       case 's':
+               radar_sim = true;
+               expected_params = 4;
+               break;
+       case 't':
+               edit_tbl = true;
+               expected_params = 3;
+               break;
+       case 'w':
+               win_set = true;
+               expected_params = 1;
+               break;
+       case '?':
+               return cl_dfs_cli_help(cl_hw);
+       default:
+               cl_dbg_err(cl_hw, "Illegal option (%c) - try '?' for help\n", cli_params->option);
+               goto out_err;
+       }
+
+       if (expected_params != cli_params->num_params) {
+               cl_dbg_err(cl_hw, "Wrong number of arguments (expected %u) (actual %u)\n",
+                          expected_params, cli_params->num_params);
+               goto out_err;
+       }
+
+       if (cac) {
+               bool start = (bool)params[0];
+
+               cl_dfs_cli_set_cac(cl_hw, start);
+               return 0;
+       }
+
+       if (print_buf)
+               return cl_dfs_print_pulse_buffer(cl_hw, (bool)params[0]);
+
+       if (dbg_lvl) {
+               s32 dbg_lvl = params[0];
+
+               if (dbg_lvl > 0 && dbg_lvl < DBG_LVL_MAX) {
+                       dfs_db->dbg_lvl = dbg_lvl;
+                       dfs_pr_verbose(cl_hw, "Debug level = %d\n", dbg_lvl);
+               } else {
+                       dfs_pr_err(cl_hw, "Invalid debug level (%d)\n", dbg_lvl);
+               }
+
+               return 0;
+       }
+
+       if (en) {
+               dfs_db->en = (bool)(params[0]);
+               dfs_pr_verbose(cl_hw, "DFS = %s\n", dfs_db->en ? "Enabled" : "Disabled");
+               cl_dfs_fw_en(cl_hw, dfs_db->en);
+               return 0;
+       }
+
+       if (force) {
+               cl_dfs_cli_force_detection(cl_hw, dfs_db);
+               return 0;
+       }
+
+       if (print_tbl) {
+               u8 table = (u8)params[0];
+
+               if (table == 0)
+                       return cl_dfs_print_tbl(cl_hw);
+               return 0;
+       }
+
+       if (return_remain_cac_time) {
+               u16 cac_time = cl_dfs_get_remain_cac_time(cl_hw);
+
+               snprintf(ret_buf, PAGE_SIZE, "%u", cac_time);
+               *ret_buf_len = strlen(ret_buf);
+               return 1;
+       }
+
+       if (radar_sim) {
+               struct cl_radar_pulse pulse[CL_DFS_MAX_PULSE];
+               unsigned long time;
+
+               pulse[0].len  = (u32)params[0];
+               pulse[0].rep  = (u32)params[1];
+               pulse[0].freq = (s32)params[2];
+               time = (unsigned long)params[3];
+               cl_dfs_pulse_process(cl_hw, pulse, 1, time);
+               return 0;
+       }
+
+       if (edit_tbl) {
+               cl_dfs_edit_tbl(cl_hw, (u8)params[0], (u8)params[1], (s16)params[2]);
+               return 0;
+       }
+
+       if (win_set) {
+               dfs_db->search_window = (u16)params[0];
+               dfs_pr_verbose(cl_hw, "Search window size = %u\n", dfs_db->search_window);
+               return 0;
+       }
+
+       if (long_prms) {
+               conf->ci_dfs_long_pulse_min = (u16)params[0];
+               conf->ci_dfs_long_pulse_max = (u16)params[1];
+               dfs_pr_verbose(cl_hw, "Long pulse min = %u\n", conf->ci_dfs_long_pulse_min);
+               dfs_pr_verbose(cl_hw, "Long pulse max = %u\n", conf->ci_dfs_long_pulse_max);
+               return 0;
+       }
+
+       if (init_gain) {
+               conf->ci_dfs_initial_gain = (u8)params[0];
+               dfs_pr_verbose(cl_hw, "Initial gain = %u\n", conf->ci_dfs_initial_gain);
+               return 0;
+       }
+
+       if (agc_cd_hh) {
+               conf->ci_dfs_agc_cd_th = (u8)params[0];
+               dfs_pr_verbose(cl_hw, "AGC CD threshold = %u\n", conf->ci_dfs_agc_cd_th);
+               return 0;
+       }
+
+out_err:
+       return -EIO;
+}
+