new file mode 100644
@@ -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", ¶m1, ¶m2, ¶m3) == 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;
+}
+