/****************************************************************************** * * Copyright(c) 2019 Realtek Corporation. * * This program is free software; you can redistribute it and/or modify it * under the terms of version 2 of the GNU General Public License as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * *****************************************************************************/ #define _PHL_DFS_C_ #include "phl_headers.h" #ifdef CONFIG_PHL_DFS struct dfs_rd_ctl_param { /* * DFS region domain set from core * >=DFS_REGD_NUM => not set (keep original) * < DFS_REGD_NUM => change domain (if needed) and ignore other parameters */ enum dfs_regd_t domain; /* * enable=true, cac=1 => enable radar detect and is under CAC * enable=true, cac=0 => enalbe radar detect, in-service monitoring * enable=true, cac<0 => enable radar detect w/o changing CAC status * enable=false, cac=any => disable radar detect */ bool enable; /* * CAC status set from core * < 0: not set (keep original) * 0: not under CAC * 1: under CAC */ s8 cac; /* * configuration for specific radar detect range (5G band is implicit), the value of sp_ch: * < 0: not set (keep original) * 0: all detectable range * > 0: specific detect range (by ch, bw, offset) */ s16 sp_ch; enum channel_width sp_bw; enum chan_offset sp_offset; /* * configuration for specific radar detect range in freqency, valid when sp_ch < 0 * 0: not set (keep original) */ u32 sp_freq_hi; u32 sp_freq_lo; }; static bool phl_radar_detect_range_specified(struct rtw_dfs_t *dfs_info) { return dfs_info->sp_detect_range_hi != 0; } static bool phl_overlap_radar_detect_range(struct rtw_dfs_t *dfs_info , enum band_type band, u8 ch, enum channel_width bw, enum chan_offset offset) { bool ret = false; u32 hi = 0, lo = 0; int i; if (!rtw_phl_bchbw_to_freq_range(band, ch, bw, offset, &hi, &lo)) { _os_warn_on(1); goto exit; } if (RANGE_OVERLAP(hi, lo, dfs_info->sp_detect_range_hi, dfs_info->sp_detect_range_lo)) ret = true; exit: return ret; } static bool phl_should_radar_detect_enable_by_ch(struct phl_info_t *phl_info, u8 band_idx, enum band_type band, u8 channel, enum channel_width bwmode, enum chan_offset offset) { struct rtw_phl_com_t *phl_com = phl_info->phl_com; struct rtw_dfs_t *dfs_info = &phl_com->dfs_info; if (dfs_info->enable && !PHL_DFS_REGD_IS_UNKNOWN(dfs_info->region_domain) && rtw_hal_in_radar_domain(phl_info->hal, band, channel, bwmode, offset) ) { if (!phl_radar_detect_range_specified(dfs_info) || phl_overlap_radar_detect_range(dfs_info, band, channel, bwmode, offset)) return true; } return false; } static void phl_dfs_handle_pending_domain_change(struct phl_info_t *phl_info, u8 band_idx , enum band_type band, u8 ch, enum channel_width bw, enum chan_offset offset) { struct rtw_phl_com_t *phl_com = phl_info->phl_com; struct rtw_dfs_t *dfs_info = &phl_com->dfs_info; if (dfs_info->pending_domain_change) { rtw_hal_bb_dfs_change_domain(phl_info->hal, band, ch, bw, offset); dfs_info->pending_domain_change = false; } } static enum rtw_phl_status phl_radar_detect_switch(struct phl_info_t *phl_info, u8 band_idx, bool enable) { struct rtw_phl_com_t *phl_com = phl_info->phl_com; struct rtw_dfs_t *dfs_info = &phl_com->dfs_info; if (rtw_hal_radar_detect_cfg(phl_info->hal, band_idx, enable) == RTW_HAL_STATUS_SUCCESS) { dfs_info->radar_detect_enabled = enable; return RTW_PHL_STATUS_SUCCESS; } return RTW_PHL_STATUS_FAILURE; } bool rtw_phl_is_radar_detect_enabled(struct rtw_phl_com_t *phl_com, u8 band_idx) { return phl_com->dfs_info.radar_detect_enabled; } static void phl_set_under_cac(struct phl_info_t *phl_info, u8 band_idx, bool under) { if (under) SET_STATUS_FLAG(phl_info->phl_com->dev_state, RTW_DEV_IN_DFS_CAC_PERIOD); else CLEAR_STATUS_FLAG(phl_info->phl_com->dev_state, RTW_DEV_IN_DFS_CAC_PERIOD); } bool rtw_phl_is_under_cac(struct rtw_phl_com_t *phl_com, u8 band_idx) { return TEST_STATUS_FLAG(phl_com->dev_state, RTW_DEV_IN_DFS_CAC_PERIOD); } static enum rtw_phl_status phl_cac_tx_pause_switch(struct phl_info_t *phl_info, u8 band_idx, bool enable) { struct rtw_phl_com_t *phl_com = phl_info->phl_com; struct rtw_dfs_t *dfs_info = &phl_com->dfs_info; if (rtw_hal_dfs_pause_tx(phl_info->hal, band_idx, enable, PAUSE_RSON_DFS_CAC) == RTW_HAL_STATUS_SUCCESS) { dfs_info->cac_tx_paused = enable; return RTW_PHL_STATUS_SUCCESS; } return RTW_PHL_STATUS_FAILURE; } bool phl_is_cac_tx_paused(struct phl_info_t *phl_info, u8 band_idx) { return phl_info->phl_com->dfs_info.cac_tx_paused; } void phl_dfs_rd_setting_before_ch_switch(struct phl_info_t *phl_info, u8 band_idx , enum band_type band, u8 ch, enum channel_width bw, enum chan_offset offset, struct dfs_rd_ch_switch_ctx *ctx) { struct rtw_phl_com_t *phl_com = phl_info->phl_com; ctx->should_rd_en_on_new_ch = phl_should_radar_detect_enable_by_ch(phl_info , band_idx, band, ch, bw, offset); ctx->under_cac = rtw_phl_is_under_cac(phl_com, band_idx); ctx->cac_tx_paused = phl_is_cac_tx_paused(phl_info, band_idx); ctx->rd_enabled = rtw_phl_is_radar_detect_enabled(phl_com, band_idx); if (!ctx->should_rd_en_on_new_ch && ctx->rd_enabled) { /* turn off radar detect before channel setting (ex: leaving detection range) */ enum rtw_phl_status rst = phl_radar_detect_switch(phl_info, band_idx, false); PHL_TRACE(COMP_PHL_DBG, (rst == RTW_PHL_STATUS_SUCCESS ? _PHL_INFO_ : _PHL_ERR_) , "[DFS] new ch=%d,%u,%d,%d disable radar detect%s\n" , band, ch, bw, offset, rst != RTW_PHL_STATUS_SUCCESS ? " failed" : ""); ctx->rd_enabled = rtw_phl_is_radar_detect_enabled(phl_com, band_idx); } if (ctx->should_rd_en_on_new_ch && ctx->under_cac && !ctx->cac_tx_paused) { /* turn on CAC tx pause before channel setting (ex: entering detection range) */ enum rtw_phl_status rst = phl_cac_tx_pause_switch(phl_info, band_idx, true); PHL_TRACE(COMP_PHL_DBG, (rst == RTW_PHL_STATUS_SUCCESS ? _PHL_INFO_ : _PHL_ERR_) , "[DFS] new ch=%d,%u,%d,%d enable CAC tx pause%s\n" , band, ch, bw, offset, rst != RTW_PHL_STATUS_SUCCESS ? " failed" : ""); } } void phl_dfs_rd_setting_after_ch_switch(struct phl_info_t *phl_info, u8 band_idx , enum band_type band, u8 ch, enum channel_width bw, enum chan_offset offset, struct dfs_rd_ch_switch_ctx *ctx) { if (ctx->should_rd_en_on_new_ch && !ctx->rd_enabled) { /* turn on radar detect after channel setting (ex: entered radar detect range) */ enum rtw_phl_status rst = phl_radar_detect_switch(phl_info, band_idx, true); PHL_TRACE(COMP_PHL_DBG, (rst == RTW_PHL_STATUS_SUCCESS ? _PHL_INFO_ : _PHL_ERR_) , "[DFS] new ch=%d,%u,%d,%d enable radar detect%s\n" , band, ch, bw, offset, rst != RTW_PHL_STATUS_SUCCESS ? " failed" : ""); } if ((!ctx->should_rd_en_on_new_ch || !ctx->under_cac) && ctx->cac_tx_paused) { /* turn off CAC tx pause after channel setting (ex: leaved detection range) */ enum rtw_phl_status rst = phl_cac_tx_pause_switch(phl_info, band_idx, false); PHL_TRACE(COMP_PHL_DBG, (rst == RTW_PHL_STATUS_SUCCESS ? _PHL_INFO_ : _PHL_ERR_) , "[DFS] new ch=%d,%u,%d,%d disable CAC tx pause%s\n" , band, ch, bw, offset, rst != RTW_PHL_STATUS_SUCCESS ? " failed" : ""); } } static enum rtw_phl_status phl_radar_detect_confs_apply(struct phl_info_t *phl_info, u8 band_idx) { struct rtw_phl_com_t *phl_com = phl_info->phl_com; struct rtw_dfs_t *dfs_info = &phl_com->dfs_info; enum rtw_phl_status rd_rst = RTW_PHL_STATUS_SUCCESS; enum rtw_phl_status cac_rst = RTW_PHL_STATUS_SUCCESS; struct rtw_chan_def chdef; bool should_rd_enable; bool under_cac; bool cac_tx_paused; rtw_hal_get_cur_chdef(phl_info->hal, band_idx, &chdef); phl_dfs_handle_pending_domain_change(phl_info, band_idx , chdef.band, chdef.chan, chdef.bw, chdef.offset); should_rd_enable = phl_should_radar_detect_enable_by_ch(phl_info, band_idx , chdef.band, chdef.chan, chdef.bw, chdef.offset); under_cac = rtw_phl_is_under_cac(phl_com, band_idx); cac_tx_paused = phl_is_cac_tx_paused(phl_info, band_idx); if (!dfs_info->radar_detect_enabled) { if (should_rd_enable) { rd_rst = phl_radar_detect_switch(phl_info, band_idx, true); PHL_TRACE(COMP_PHL_DBG, (rd_rst == RTW_PHL_STATUS_SUCCESS ? _PHL_INFO_ : _PHL_ERR_) , "[DFS] ch=%d,%u,%d,%d enable radar detect%s\n" , chdef.band, chdef.chan, chdef.bw, chdef.offset , rd_rst != RTW_PHL_STATUS_SUCCESS ? " failed" : ""); } } else if (dfs_info->radar_detect_enabled) { if (!should_rd_enable) { rd_rst = phl_radar_detect_switch(phl_info, band_idx, false); PHL_TRACE(COMP_PHL_DBG, (rd_rst == RTW_PHL_STATUS_SUCCESS ? _PHL_INFO_ : _PHL_ERR_) , "[DFS] ch=%d,%u,%d,%d disable radar detect%s\n" , chdef.band, chdef.chan, chdef.bw, chdef.offset , rd_rst != RTW_PHL_STATUS_SUCCESS ? " failed" : ""); } } if (!cac_tx_paused) { if (under_cac && dfs_info->radar_detect_enabled) { cac_rst = phl_cac_tx_pause_switch(phl_info, band_idx, true); PHL_TRACE(COMP_PHL_DBG, (cac_rst == RTW_PHL_STATUS_SUCCESS ? _PHL_INFO_ : _PHL_ERR_) , "[DFS] ch=%d,%u,%d,%d enable CAC tx pause%s\n" , chdef.band, chdef.chan, chdef.bw, chdef.offset , cac_rst != RTW_PHL_STATUS_SUCCESS ? " failed" : ""); } } else if (cac_tx_paused) { if (!under_cac) { /* * Release CAC tx pause only when not under CAC * Keep CAC tx pause when under CAC and radar detect is turned off by * specifying new detect range which doesn't overlap current channel setting * (ex: operating channel switching to new DFS channel) */ cac_rst = phl_cac_tx_pause_switch(phl_info, band_idx, false); PHL_TRACE(COMP_PHL_DBG, (cac_rst == RTW_PHL_STATUS_SUCCESS ? _PHL_INFO_ : _PHL_ERR_) , "[DFS] ch=%d,%u,%d,%d disable CAC tx pause%s\n" , chdef.band, chdef.chan, chdef.bw, chdef.offset , cac_rst != RTW_PHL_STATUS_SUCCESS ? " failed" : ""); } } if (rd_rst == RTW_PHL_STATUS_SUCCESS && cac_rst == RTW_PHL_STATUS_SUCCESS) return RTW_PHL_STATUS_SUCCESS; return RTW_PHL_STATUS_FAILURE; } static enum rtw_phl_status _phl_dfs_rd_ctl_hdl(void *phl, u8 *param) { struct phl_info_t *phl_info = (struct phl_info_t *)phl; struct rtw_phl_com_t *phl_com = phl_info->phl_com; struct rtw_dfs_t *dfs_info = &phl_com->dfs_info; struct dfs_rd_ctl_param *rd_ctl_param = (struct dfs_rd_ctl_param *)param; u8 band_idx = HW_BAND_0; /* multi band/phy capable? */ if (rd_ctl_param->domain < DFS_REGD_NUM) { if (rd_ctl_param->domain != dfs_info->region_domain) { PHL_INFO("%s set domain to %d\n", __func__, rd_ctl_param->domain); dfs_info->region_domain = rd_ctl_param->domain; dfs_info->pending_domain_change = true; } goto apply; } if (rd_ctl_param->enable) { if (!dfs_info->enable) { PHL_INFO("%s enable\n", __func__); dfs_info->enable = true; } if (rd_ctl_param->cac == 1) { if (!rtw_phl_is_under_cac(phl_com, band_idx)) { PHL_INFO("%s under CAC\n", __func__); phl_set_under_cac(phl_info, band_idx, true); } } else if (rd_ctl_param->cac == 0) { if (rtw_phl_is_under_cac(phl_com, band_idx)) { PHL_INFO("%s CAC done\n", __func__); phl_set_under_cac(phl_info, band_idx, false); } } } else { if (dfs_info->enable) { PHL_INFO("%s disable\n", __func__); dfs_info->enable = false; } phl_set_under_cac(phl_info, band_idx, false); } if (rd_ctl_param->sp_ch > 0) { u32 hi, lo; if (rtw_phl_bchbw_to_freq_range(BAND_ON_5G , rd_ctl_param->sp_ch, rd_ctl_param->sp_bw, rd_ctl_param->sp_offset , &hi, &lo) ) { if (dfs_info->sp_detect_range_hi != hi || dfs_info->sp_detect_range_lo != lo) { PHL_INFO("%s sp_ch:%u,%d,%d is set\n", __func__ , rd_ctl_param->sp_ch, rd_ctl_param->sp_bw, rd_ctl_param->sp_offset); dfs_info->sp_detect_range_hi = hi; dfs_info->sp_detect_range_lo = lo; } } else { PHL_WARN("%s sp_ch:%u,%d,%d to freq range fail, all range applied\n", __func__ , rd_ctl_param->sp_ch, rd_ctl_param->sp_bw, rd_ctl_param->sp_offset); dfs_info->sp_detect_range_hi = 0; } } else if (rd_ctl_param->sp_ch == 0) { if (dfs_info->sp_detect_range_hi != 0) { PHL_INFO("%s all range applied\n", __func__); dfs_info->sp_detect_range_hi = 0; } } else if (rd_ctl_param->sp_freq_hi) { if (rd_ctl_param->sp_freq_hi <= rd_ctl_param->sp_freq_lo) { PHL_WARN("%s sp_freq_hi:%u <= sp_freq_lo:%u, all range applied\n", __func__ , rd_ctl_param->sp_freq_hi, rd_ctl_param->sp_freq_lo); } else { if (dfs_info->sp_detect_range_hi != rd_ctl_param->sp_freq_hi || dfs_info->sp_detect_range_lo != rd_ctl_param->sp_freq_lo ) { PHL_INFO("%s sp_freq %u to %u is set\n", __func__ , rd_ctl_param->sp_freq_lo, rd_ctl_param->sp_freq_hi); dfs_info->sp_detect_range_hi = rd_ctl_param->sp_freq_hi; dfs_info->sp_detect_range_lo = rd_ctl_param->sp_freq_lo; } } } apply: /* apply new configs on cur channel */ return phl_radar_detect_confs_apply(phl_info, band_idx); } enum rtw_phl_status phl_cmd_dfs_rd_ctl_hdl(struct phl_info_t *phl_info, u8 *param) { return _phl_dfs_rd_ctl_hdl(phl_info, param); } static void _phl_dfs_rd_ctl_done(void *drv_priv, u8 *cmd, u32 cmd_len, enum rtw_phl_status status) { if (cmd) { _os_kmem_free(drv_priv, cmd, cmd_len); cmd = NULL; PHL_INFO("%s.....\n", __func__); } } static enum rtw_phl_status _rtw_phl_cmd_dfs_rd_ctl(struct phl_info_t *phl_info, enum phl_band_idx hw_band , enum dfs_regd_t domain, bool enable, s8 cac, s16 sp_ch, enum channel_width sp_bw, enum chan_offset sp_offset , u32 sp_freq_hi, u32 sp_freq_lo , enum phl_cmd_type cmd_type, u32 cmd_timeout) { #ifdef CONFIG_CMD_DISP void *drv = phl_info->phl_com->drv_priv; enum rtw_phl_status psts = RTW_PHL_STATUS_FAILURE; struct dfs_rd_ctl_param *param = NULL; u32 param_len; param_len = sizeof(struct dfs_rd_ctl_param); param = _os_kmem_alloc(drv, param_len); if (param == NULL) { PHL_ERR("%s: alloc param failed!\n", __func__); goto _exit; } param->domain = domain; param->enable = enable; param->cac = cac; param->sp_ch = sp_ch; param->sp_bw = sp_bw; param->sp_offset = sp_offset; param->sp_freq_hi = sp_freq_hi; param->sp_freq_lo = sp_freq_lo; if (cmd_type == PHL_CMD_DIRECTLY) { psts = phl_cmd_dfs_rd_ctl_hdl(phl_info, (u8 *)param); _phl_dfs_rd_ctl_done(drv, (u8 *)param, param_len, psts); goto _exit; } psts = phl_cmd_enqueue(phl_info, hw_band, MSG_EVT_DFS_RD_SETUP, (u8 *)param, param_len, _phl_dfs_rd_ctl_done, cmd_type, cmd_timeout); if (is_cmd_failure(psts)) { /* Send cmd success, but wait cmd fail*/ psts = RTW_PHL_STATUS_FAILURE; } else if (psts != RTW_PHL_STATUS_SUCCESS) { /* Send cmd fail */ _os_kmem_free(drv, param, param_len); psts = RTW_PHL_STATUS_FAILURE; } _exit: return psts; #else PHL_ERR("%s(), CONFIG_CMD_DISP need to be enabled for MSG_EVT_DFS_RD_SETUP \n",__func__); return RTW_PHL_STATUS_FAILURE; #endif } enum rtw_phl_status rtw_phl_cmd_dfs_change_domain(void *phl_info, enum phl_band_idx hw_band , enum dfs_regd_t domain, enum phl_cmd_type cmd_type, u32 cmd_timeout) { if (domain >= DFS_REGD_NUM) { PHL_WARN("%s(), invalid domain:%d\n", __func__, domain); return RTW_PHL_STATUS_INVALID_PARAM; } return _rtw_phl_cmd_dfs_rd_ctl(phl_info, hw_band , domain /* change domain, other parameters will be ignored */ , false, 0, -1, 0, 0, 0, 0 , cmd_type, cmd_timeout); } enum rtw_phl_status rtw_phl_cmd_dfs_rd_enable_all_range(void *phl_info, enum phl_band_idx hw_band , enum phl_cmd_type cmd_type, u32 cmd_timeout) { return _rtw_phl_cmd_dfs_rd_ctl(phl_info, hw_band , DFS_REGD_NUM , true, -1 /* enable radar detect w/o changing CAC status */ , 0, 0, 0, 0, 0 , cmd_type, cmd_timeout); } enum rtw_phl_status rtw_phl_cmd_dfs_rd_enable_with_sp_chbw(void *phl_info, enum phl_band_idx hw_band , bool cac, u8 sp_ch, enum channel_width sp_bw, enum chan_offset sp_offset , enum phl_cmd_type cmd_type, u32 cmd_timeout) { return _rtw_phl_cmd_dfs_rd_ctl(phl_info, hw_band , DFS_REGD_NUM , true, cac ? 1 : 0, sp_ch, sp_bw, sp_offset, 0, 0 , cmd_type, cmd_timeout); } enum rtw_phl_status rtw_phl_cmd_dfs_rd_enable_with_sp_freq_range(void *phl_info, enum phl_band_idx hw_band , bool cac, u32 sp_freq_hi, u32 sp_freq_lo , enum phl_cmd_type cmd_type, u32 cmd_timeout) { return _rtw_phl_cmd_dfs_rd_ctl(phl_info, hw_band , DFS_REGD_NUM , true, cac ? 1 : 0, -1, 0, 0, sp_freq_hi, sp_freq_lo , cmd_type, cmd_timeout); } enum rtw_phl_status rtw_phl_cmd_dfs_rd_set_cac_status(void *phl_info, enum phl_band_idx hw_band , bool cac, enum phl_cmd_type cmd_type, u32 cmd_timeout) { return _rtw_phl_cmd_dfs_rd_ctl(phl_info, hw_band , DFS_REGD_NUM , true /* CAC status only valid when radar detect enable */ , cac ? 1 : 0, -1, 0, 0, 0, 0 , cmd_type, cmd_timeout); } enum rtw_phl_status rtw_phl_cmd_dfs_rd_disable(void *phl_info, enum phl_band_idx hw_band , enum phl_cmd_type cmd_type, u32 cmd_timeout) { return _rtw_phl_cmd_dfs_rd_ctl(phl_info, hw_band , DFS_REGD_NUM , false, 0, -1, 0, 0, 0, 0 , cmd_type, cmd_timeout); } #endif