| /* SPDX-License-Identifier: BSD-3-Clause-Clear */ |
| /* |
| * Copyright (c) 2015, 2021 The Linux Foundation. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted (subject to the limitations in the |
| * disclaimer below) provided that the following conditions are met: |
| * |
| * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * |
| * Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the |
| * distribution. |
| * |
| * Neither the name of The Linux Foundation nor the names of its |
| * contributors may be used to endorse or promote products derived |
| * from this software without specific prior written permission. |
| |
| * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE |
| * GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT |
| * HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED |
| * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF |
| * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
| * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR |
| * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, |
| * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE |
| * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN |
| * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "debug.h" |
| #include "peer.h" |
| #include "smart_ant.h" |
| #include "wmi.h" |
| #include "ath11k_smart_ant_api.h" |
| |
| struct ath11k_smart_ant_handler { |
| struct ath11k_base *ab; |
| struct ath11k_smartantenna_ops ops; |
| }; |
| |
| static struct ath11k_smart_ant_handler g_smart_ant_obj = { |
| .ab = NULL, .ops = {.sa_api_version = SA_API_VERSION_UNDEF}}; |
| |
| bool ath11k_enable_sa_api = true; |
| |
| /** |
| * Smart antenna api callback register |
| * |
| * This function register the 3rd-party smart antenna module callback |
| * functions to the WLAN driver. |
| * |
| * Returns 0 for success, -1 in case of failure |
| */ |
| int ath11k_smart_ant_api_ops_register(struct ath11k_smartantenna_ops *ops) |
| { |
| struct ath11k_base *ab = g_smart_ant_obj.ab; |
| struct ath11k_smartantenna_ops *clb = &(g_smart_ant_obj.ops); |
| |
| if (ab == NULL) { |
| printk("%s: ERROR: Smart Antenna API not ready\n", __func__); |
| return -1; |
| } |
| ath11k_dbg(ab, ATH11K_DBG_SMART_ANT, "%s:+\n", __func__); |
| if (clb->sa_api_version != SA_API_VERSION_UNDEF) { |
| ath11k_warn(ab, "WARN: reject double sa api ops registration\n"); |
| return -1; |
| } |
| |
| if (ops == NULL) { |
| ath11k_warn(ab, "WARN: reject empty clb registration\n"); |
| return -1; |
| } |
| |
| if (ops->sa_api_version != SA_API_VERSION) { |
| ath11k_warn(ab, |
| "WARN: reject callbacks for sa_api version mismatch: " |
| "exp:%d got %d\n", |
| SA_API_VERSION, ops->sa_api_version); |
| return -1; |
| } |
| |
| clb->sa_api_version = ops->sa_api_version; |
| clb->sa_radio_init = ops->sa_radio_init; |
| clb->sa_radio_deinit = ops->sa_radio_deinit; |
| clb->sa_node_connect = ops->sa_node_connect; |
| clb->sa_node_disconnect = ops->sa_node_disconnect; |
| clb->sa_update_txfeedback = ops->sa_update_txfeedback; |
| clb->sa_update_rxfeedback = ops->sa_update_rxfeedback; |
| clb->sa_get_txantenna = ops->sa_get_txantenna; |
| clb->sa_get_rxantenna = ops->sa_get_rxantenna; |
| clb->sa_get_txantenna_default = ops->sa_get_txantenna_default; |
| |
| ath11k_info(ab, |
| "Smart Antenna API layer: " |
| "clb registration successful (ver:%d)\n", |
| clb->sa_api_version); |
| return 0; |
| } |
| EXPORT_SYMBOL(ath11k_smart_ant_api_ops_register); |
| |
| /** |
| * Smart antenna api callback deregister |
| * |
| * This function remove the hooks to the 3rd party smart antenna module |
| * attached to the WLAN driver. |
| * |
| * Returns 0 for success, -1 in case of failure |
| */ |
| int ath11k_smart_ant_api_ops_deregister(void) |
| { |
| struct ath11k_base *ab = g_smart_ant_obj.ab; |
| struct ath11k_smartantenna_ops *clb = &(g_smart_ant_obj.ops); |
| |
| if (ab == NULL) { |
| // Smart Antenna component is not enabled yet |
| // impossible to deregister the callbacks |
| return -1; |
| } |
| |
| if (clb->sa_api_version == SA_API_VERSION_UNDEF) { |
| ath11k_warn(ab, |
| "Smart Antenna API layer: " |
| "clb deregistering attempt with no effect\n"); |
| } |
| |
| // clean-up the callback pointers |
| memset(clb, 0, sizeof(*clb)); |
| clb->sa_api_version = SA_API_VERSION_UNDEF; |
| |
| ath11k_info(ab, "Smart Antenna API layer: callback deregistered.\n"); |
| return 0; |
| } |
| EXPORT_SYMBOL(ath11k_smart_ant_api_ops_deregister); |
| |
| /** |
| * ath11k smart antenna api object handler: attach function |
| * |
| * This function will be called by the ath11k base initializer |
| * and init the sa_api component. |
| * The component allows 3rd party module to attach the WLAN driver. |
| * |
| * Returns 0 for success |
| */ |
| int ath11k_sa_api_attach(struct ath11k_base *ab) |
| { |
| if (ab == NULL) { |
| return -1; |
| } |
| |
| if (!test_bit(WMI_TLV_SERVICE_SMART_ANTENNA_SW_SUPPORT, |
| ab->wmi_ab.svc_map)) { |
| ath11k_warn(ab, "test hw capabilities failed\n"); |
| return -1; |
| } |
| |
| if (!test_bit(WMI_TLV_SERVICE_SMART_ANTENNA_HW_SUPPORT, |
| ab->wmi_ab.svc_map)) { |
| ath11k_warn(ab, "test sw failed\n"); |
| return -1; |
| } |
| |
| if (!ath11k_enable_sa_api) { |
| ath11k_warn(ab, "configuration capabilities failed\n"); |
| return -1; |
| } |
| |
| g_smart_ant_obj.ab = ab; |
| ath11k_info(ab, |
| "Smart Antenna API layer: Callback registration available\n"); |
| return 0; |
| } |
| |
| /** |
| * ath11k smart antenna api object handler: detach function |
| * |
| * This function can be called by the ath11k base initializer |
| * in case of deinitialization. |
| * |
| * Returns 0 for success |
| */ |
| int ath11k_sa_api_detach(struct ath11k_base *ab) |
| { |
| ath11k_smart_ant_api_ops_deregister(); |
| |
| g_smart_ant_obj.ab = NULL; |
| g_smart_ant_obj.ops.sa_api_version = SA_API_VERSION_UNDEF; |
| |
| ath11k_info(ab, "Smart Antenna API layer: disabled.\n"); |
| return 0; |
| } |
| |
| static void ath11k_sa_api_radio_info_fill(struct ath11k_vif *arvif, |
| struct sa_radio_info *info) |
| { |
| struct ath11k *ar = NULL; |
| if ((arvif == NULL) || (info == NULL)) { |
| return; |
| } |
| |
| ar = arvif->ar; |
| if (ar == NULL) { |
| return; |
| } |
| |
| info->opmode = arvif->vdev_type; |
| info->radio_id = arvif->vdev_id; |
| info->chainmask = get_num_chains(ar->cfg_tx_chainmask) ?: 1; |
| } |
| |
| int ath11k_sa_api_radio_init(struct ath11k_smart_ant_handler *sa_api, |
| struct ath11k_vif *arvif) |
| { |
| struct sa_radio_info info = {0}; |
| |
| if ((arvif == NULL) || (sa_api == NULL)) { |
| return SMART_ANT_STATUS_FAILURE; |
| } |
| |
| if (sa_api->ops.sa_radio_init) { |
| ath11k_sa_api_radio_info_fill(arvif, &info); |
| return sa_api->ops.sa_radio_init(&info); |
| } |
| |
| return SMART_ANT_STATUS_FAILURE; |
| } |
| |
| int ath11k_sa_api_radio_deinit(struct ath11k_smart_ant_handler *sa_api, |
| struct ath11k_vif *arvif) |
| { |
| struct sa_radio_info info = {0}; |
| |
| if ((arvif == NULL) || (sa_api == NULL)) { |
| return SMART_ANT_STATUS_FAILURE; |
| } |
| |
| if (sa_api->ops.sa_radio_deinit) { |
| ath11k_sa_api_radio_info_fill(arvif, &info); |
| return sa_api->ops.sa_radio_deinit(&info); |
| } |
| |
| return SMART_ANT_STATUS_FAILURE; |
| } |
| |
| int ath11k_sa_api_node_connect(struct ath11k_smart_ant_handler *sa_api, |
| struct ieee80211_sta *sta, |
| struct ath11k *ar) |
| { |
| struct sa_node_info node = {0}; |
| |
| if ((sa_api == NULL) || (sa_api->ab == NULL)) { |
| return SMART_ANT_STATUS_FAILURE; |
| } |
| if (sta == NULL) { |
| ath11k_warn(sa_api->ab, "Station is NULL!!\n"); |
| return SMART_ANT_STATUS_FAILURE; |
| } |
| |
| ether_addr_copy(node.mac_addr, sta->addr); |
| node.radio_id = 0; |
| |
| if (sa_api->ops.sa_node_connect == NULL) { |
| ath11k_warn(sa_api->ab, "sa_node_connect clb is NULL!!\n"); |
| return SMART_ANT_STATUS_FAILURE; |
| } |
| return sa_api->ops.sa_node_connect(&node); |
| } |
| |
| int ath11k_sa_api_node_disconnect(struct ath11k_smart_ant_handler *sa_api, |
| struct ieee80211_sta *sta) |
| { |
| struct sa_node_info node = {0}; |
| struct ath11k_sta *arsta = NULL; |
| uint8_t status = 0; |
| int ret; |
| if ((sa_api == NULL) || (sa_api->ab == NULL)) { |
| return SMART_ANT_STATUS_FAILURE; |
| } |
| if (sta == NULL) { |
| ath11k_warn(sa_api->ab, "Station is NULL!!\n"); |
| return SMART_ANT_STATUS_FAILURE; |
| } |
| |
| ether_addr_copy(node.mac_addr, sta->addr); |
| node.radio_id = 0; |
| |
| if (sa_api->ops.sa_node_disconnect == NULL) { |
| ath11k_warn(sa_api->ab, "sa_node_disconnect clb is NULL!!\n"); |
| return SMART_ANT_STATUS_FAILURE; |
| } |
| |
| ret = sa_api->ops.sa_node_disconnect(&node, &status); |
| |
| if (status & SMART_ANT_RX_CONFIG_REQUIRED) { |
| arsta = (struct ath11k_sta *)sta->drv_priv; |
| if ((arsta == NULL) || (arsta->arvif == NULL)) { |
| ath11k_dbg(sa_api->ab, ATH11K_DBG_SMART_ANT, "%s: missing operand: %p\n", |
| __func__, arsta); |
| return SMART_ANT_STATUS_FAILURE; |
| } |
| ath11k_smart_ant_alg_set_default(arsta->arvif); |
| } |
| return ret; |
| } |
| |
| int ath11k_sa_api_get_txantenna(struct ath11k_smart_ant_handler *sa_api, |
| struct ieee80211_sta *sta, uint32_t *antenna) |
| { |
| struct sa_node_info info = {0}; |
| |
| if ((sa_api == NULL) || (sa_api->ab == NULL)) { |
| return SMART_ANT_STATUS_FAILURE; |
| } |
| |
| info.radio_id = 0; |
| ether_addr_copy(info.mac_addr, sta->addr); |
| |
| if (sa_api->ops.sa_get_txantenna == NULL) { |
| ath11k_warn(sa_api->ab, "sa_get_txantenna clb is NULL!!\n"); |
| return SMART_ANT_STATUS_FAILURE; |
| } |
| return sa_api->ops.sa_get_txantenna(&info, antenna); |
| } |
| |
| int ath11k_sa_api_get_rxantenna(struct ath11k_smart_ant_handler *sa_api, |
| struct ath11k_vif *arvif, uint32_t *antenna) |
| { |
| struct sa_radio_info info = {0}; |
| |
| if ((arvif == NULL) || (sa_api == NULL)) { |
| return SMART_ANT_STATUS_FAILURE; |
| } |
| |
| if (sa_api->ops.sa_get_rxantenna == NULL) { |
| return SMART_ANT_STATUS_FAILURE; |
| } |
| |
| ath11k_sa_api_radio_info_fill(arvif, &info); |
| |
| if (sa_api->ops.sa_get_rxantenna == NULL) { |
| ath11k_warn(sa_api->ab, "sa_get_rxantenna clb is NULL!!\n"); |
| return SMART_ANT_STATUS_FAILURE; |
| } |
| return sa_api->ops.sa_get_rxantenna(&info, antenna); |
| } |
| |
| int ath11k_sa_api_get_txdefault_antenna(struct ath11k_smart_ant_handler *sa_api, |
| struct ath11k_vif *arvif, |
| uint32_t *antenna) |
| { |
| struct sa_radio_info info = {0}; |
| if ((arvif == NULL) || (sa_api == NULL) || (sa_api->ab == NULL)) { |
| return SMART_ANT_STATUS_FAILURE; |
| } |
| |
| if (sa_api->ops.sa_get_txantenna_default == NULL) { |
| return SMART_ANT_STATUS_FAILURE; |
| } |
| |
| ath11k_sa_api_radio_info_fill(arvif, &info); |
| if (sa_api->ops.sa_get_txantenna_default == NULL) { |
| ath11k_warn(sa_api->ab, "sa_get_txantenna_default clb is NULL!!\n"); |
| return SMART_ANT_STATUS_FAILURE; |
| } |
| return sa_api->ops.sa_get_txantenna_default(&info, antenna); |
| } |
| |
| static int ath11k_sa_api_update_txfeedback( |
| struct ath11k_smart_ant_handler *sa_api, struct ieee80211_sta *sta, |
| struct ath11k_smart_ant_tx_feedback *fb) |
| { |
| struct ath11k_base *ab = NULL; |
| struct ath11k_sta *arsta = NULL; |
| struct sa_node_info node = {0}; |
| int ret = 0; |
| uint8_t status = 0; |
| |
| if ((sa_api == NULL) || (sta == NULL) || (fb == NULL)) { |
| return SMART_ANT_STATUS_FAILURE; |
| } |
| |
| ab = sa_api->ab; |
| if (ab == NULL) { |
| return SMART_ANT_STATUS_FAILURE; |
| } |
| |
| // fill sa_node_info structure |
| memset(&node, 0, sizeof(node)); |
| ether_addr_copy(node.mac_addr, sta->addr); |
| |
| if (sa_api->ops.sa_update_txfeedback == NULL) { |
| ath11k_dbg(ab, ATH11K_DBG_SMART_ANT, |
| "sa_update_txfeedback missing clb\n"); |
| return SMART_ANT_STATUS_FAILURE; |
| } |
| |
| ret = sa_api->ops.sa_update_txfeedback(&node, fb, &status); |
| if (ret < 0) { |
| ath11k_dbg(ab, ATH11K_DBG_SMART_ANT, "failure: %d\n", ret); |
| return ret; |
| } |
| |
| // processing status information |
| if (status & SMART_ANT_RX_CONFIG_REQUIRED) { |
| arsta = (struct ath11k_sta *)sta->drv_priv; |
| if ((arsta == NULL) || (arsta->arvif == NULL)) { |
| ath11k_dbg(ab, ATH11K_DBG_SMART_ANT, "%s: missing operand: %p\n", |
| __func__, arsta); |
| return SMART_ANT_STATUS_FAILURE; |
| } |
| spin_unlock_bh(&ab->base_lock); |
| ath11k_smart_ant_alg_set_default(arsta->arvif); |
| spin_lock_bh(&ab->base_lock); |
| } |
| |
| if (status & SMART_ANT_TX_CONFIG_REQUIRED) { |
| spin_unlock_bh(&ab->base_lock); |
| ath11k_smart_ant_alg_set_txantenna(sta, NULL); |
| spin_lock_bh(&ab->base_lock); |
| } |
| |
| return 0; |
| } |
| |
| static int ath11k_sa_api_update_rxfeedback( |
| struct ath11k_smart_ant_handler *sa_api, struct ieee80211_sta *sta, |
| struct ath11k_smart_ant_rx_feedback *fb) |
| { |
| struct ath11k_base *ab = NULL; |
| struct ath11k_sta *arsta = NULL; |
| struct sa_node_info node = {0}; |
| int ret = 0; |
| uint8_t status = 0; |
| |
| if ((sa_api == NULL) || (sta == NULL) || (fb == NULL)) { |
| return -1; |
| } |
| |
| ab = sa_api->ab; |
| if (ab == NULL) { |
| return SMART_ANT_STATUS_FAILURE; |
| } |
| |
| if (sa_api->ops.sa_update_rxfeedback == NULL) { |
| ath11k_dbg(ab, ATH11K_DBG_SMART_ANT, |
| "sa_update_rxfeedback missing clb\n"); |
| return SMART_ANT_STATUS_FAILURE; |
| } |
| |
| // fill sa_node_info structure |
| memset(&node, 0, sizeof(node)); |
| ether_addr_copy(node.mac_addr, sta->addr); |
| |
| ret = sa_api->ops.sa_update_rxfeedback(&node, fb, &status); |
| if (ret < 0) { |
| ath11k_dbg(ab, ATH11K_DBG_SMART_ANT, "%s: failure:%d\n", __func__, ret); |
| return ret; |
| } |
| |
| if (status & SMART_ANT_RX_CONFIG_REQUIRED) { |
| arsta = (struct ath11k_sta *)sta->drv_priv; |
| if ((arsta == NULL) || (arsta->arvif == NULL)) { |
| ath11k_dbg(ab, ATH11K_DBG_SMART_ANT, "%s: missing operand: %p\n", |
| __func__, arsta); |
| return -1; |
| } |
| |
| spin_unlock_bh(&ab->base_lock); |
| ath11k_smart_ant_alg_set_default(arsta->arvif); |
| spin_lock_bh(&ab->base_lock); |
| } |
| |
| // NOTYET: this can be enabled too |
| /* if (status & SMART_ANT_TX_CONFIG_REQUIRED) { |
| ath11k_smart_ant_alg_set_txantenna(sta, NULL); |
| }*/ |
| return 0; |
| } |
| |
| static int smart_ant_alg_get_streams(u32 nss) |
| { |
| u32 num_chains = 0, supp_tx_chainmask = (1 << nss) - 1; |
| int i; |
| |
| for (i = 0; i < ATH11K_SMART_ANT_MAX_CHAINS; i++) { |
| if (supp_tx_chainmask & (1 << i)) |
| num_chains++; |
| } |
| |
| return min(num_chains, nss); |
| } |
| |
| static void smart_ant_alg_init_param(struct ath11k *ar) |
| { |
| struct ath11k_smart_ant_info *info = &ar->smart_ant_info; |
| struct ath11k_smart_ant_params *sa_params = |
| &ar->smart_ant_info.smart_ant_params; |
| u32 nss; |
| |
| nss = get_num_chains(ar->cfg_tx_chainmask) ? : 1; |
| info->mode = WMI_SMART_ANT_MODE_PARALLEL; |
| info->default_ant = ATH11K_SMART_ANT_DEFAULT_ANT; |
| info->num_fallback_rate = ATH11K_SMART_ANT_FALLBACK_RATE_DEFAULT; |
| info->txrx_feedback = ATH11K_SMART_ANT_TX_FEEDBACK | |
| ATH11K_SMART_ANT_RX_FEEDBACK; |
| |
| sa_params->low_rate_threshold = ATH11K_SMART_ANT_PER_MIN_THRESHOLD; |
| sa_params->hi_rate_threshold = ATH11K_SMART_ANT_PER_MAX_THRESHOLD; |
| sa_params->per_diff_threshold = ATH11K_SMART_ANT_PER_DIFF_THRESHOLD; |
| sa_params->num_train_pkts = 0; |
| sa_params->pkt_len = ATH11K_SMART_ANT_PKT_LEN_DEFAULT; |
| sa_params->num_tx_ant_comb = 1 << smart_ant_alg_get_streams(nss); |
| sa_params->num_min_pkt = ATH11K_SMART_ANT_NUM_PKT_MIN; |
| sa_params->retrain_interval = msecs_to_jiffies( |
| ATH11K_SMART_ANT_RETRAIN_INTVL); |
| sa_params->perf_train_interval = msecs_to_jiffies( |
| ATH11K_SMART_ANT_PERF_TRAIN_INTVL); |
| sa_params->max_perf_delta = ATH11K_SMART_ANT_TPUT_DELTA_DEFAULT; |
| sa_params->hysteresis = ATH11K_SMART_ANT_HYSTERISYS_DEFAULT; |
| sa_params->min_goodput_threshold = |
| ATH11K_SMART_ANT_MIN_GOODPUT_THRESHOLD; |
| sa_params->avg_goodput_interval = ATH11K_SMART_ANT_GOODPUT_INTVL_AVG; |
| sa_params->ignore_goodput_interval = |
| ATH11K_SMART_ANT_IGNORE_GOODPUT_INTVL; |
| sa_params->num_pretrain_pkts = ATH11K_SMART_ANT_PRETRAIN_PKTS_MAX; |
| sa_params->num_other_bw_pkts_threshold = ATH11K_SMART_ANT_BW_THRESHOLD; |
| sa_params->enabled_train = ATH11K_SMART_ANT_TRAIN_INIT | |
| ATH11K_SMART_ANT_TRAIN_TRIGGER_PERIODIC | |
| ATH11K_SMART_ANT_TRAIN_TRIGGER_PERF | |
| ATH11K_SMART_ANT_TRAIN_TRIGGER_RX; |
| sa_params->num_pkt_min_threshod[ATH11K_SMART_ANT_BW_20] = |
| ATH11K_SMART_ANT_NUM_PKT_THRESHOLD_20; |
| sa_params->num_pkt_min_threshod[ATH11K_SMART_ANT_BW_40] = |
| ATH11K_SMART_ANT_NUM_PKT_THRESHOLD_40; |
| sa_params->num_pkt_min_threshod[ATH11K_SMART_ANT_BW_80] = |
| ATH11K_SMART_ANT_NUM_PKT_THRESHOLD_80; |
| sa_params->default_tx_ant = ATH11K_SMART_ANT_DEFAULT_ANT; |
| sa_params->ant_change_ind = 0; |
| sa_params->max_train_ppdu = ATH11K_SMART_ANT_NUM_TRAIN_PPDU_MAX; |
| sa_params->num_rx_chain = nss; |
| if (nss > ATH11K_SMART_ANT_MAX_RX_CHAIN) |
| sa_params->num_rx_chain = ATH11K_SMART_ANT_MAX_RX_CHAIN; |
| } |
| |
| void ath11k_smart_ant_alg_sta_disconnect(struct ath11k *ar, |
| struct ieee80211_sta *sta) |
| { |
| struct ath11k_sta *arsta = (struct ath11k_sta *)sta->drv_priv; |
| |
| if (!ath11k_smart_ant_enabled(ar) || !arsta->smart_ant_sta) |
| return; |
| |
| ath11k_dbg(ar->ab, ATH11K_DBG_SMART_ANT, |
| "Smart antenna disconnect for %pM\n", sta->addr); |
| |
| kfree(arsta->smart_ant_sta); |
| |
| // smart antenna callback node disconnect |
| ath11k_sa_api_node_disconnect(&g_smart_ant_obj, sta); |
| } |
| |
| int ath11k_smart_ant_alg_sta_connect(struct ath11k *ar, |
| struct ath11k_vif *arvif, |
| struct ieee80211_sta *sta) |
| { |
| struct ath11k_sta *arsta = (struct ath11k_sta *)sta->drv_priv; |
| struct ath11k_smart_ant_node_config_params params; |
| struct ath11k_smart_ant_sta *smart_ant_sta; |
| int ret; |
| u8 mac_addr[ETH_ALEN]; |
| if( g_smart_ant_obj.ops.sa_api_version == SA_API_VERSION_UNDEF) |
| return 0; |
| lockdep_assert_held(&ar->conf_mutex); |
| |
| if (arvif->vdev_type != WMI_VDEV_TYPE_AP || |
| arvif->vdev_subtype != WMI_VDEV_SUBTYPE_NONE) |
| return 0; |
| |
| memset(¶ms, 0, sizeof(params)); |
| |
| params.cmd_id = 1; |
| params.arg_count = 1; |
| params.vdev_id = arsta->arvif->vdev_id; |
| params.arg_arr = ATH11K_SMART_ANT_TX_FEEDBACK_CONFIG_DEFAULT; |
| ether_addr_copy(mac_addr, sta->addr); |
| |
| ret = ath11k_wmi_peer_set_smart_ant_node_config(ar, mac_addr, ¶ms); |
| if (ret) { |
| ath11k_warn(ar->ab, "Failed to set feedback config\n"); |
| return ret; |
| } |
| |
| smart_ant_sta = kzalloc(sizeof(struct ath11k_smart_ant_sta), GFP_ATOMIC); |
| if (!smart_ant_sta) { |
| ath11k_warn(ar->ab, "Failed to allocate smart ant sta\n"); |
| ret = -EINVAL; |
| return ret; |
| } |
| |
| arsta->smart_ant_sta = smart_ant_sta; |
| |
| // smart antenna callback node connect |
| ath11k_sa_api_node_connect(&g_smart_ant_obj, sta, ar); |
| |
| return 0; |
| } |
| |
| int ath11k_smart_ant_alg_set_txantenna(struct ieee80211_sta *sta, |
| const u32 *tx_antenna) |
| { |
| struct ath11k_sta *arsta = (struct ath11k_sta *)sta->drv_priv; |
| struct ath11k_vif *arvif = arsta->arvif; |
| struct ath11k *ar = arvif->ar; |
| u32 tx_ants[ATH11K_SMART_ANT_MAX_RATE_SERIES] = {0}; |
| u8 macaddr[ETH_ALEN]; |
| |
| ether_addr_copy(macaddr, sta->addr); |
| |
| if (tx_antenna == NULL) { // tx_antenna array input is ignored |
| // smart antenna callback get tx antenna |
| ath11k_sa_api_get_txantenna(&g_smart_ant_obj, sta, tx_ants); |
| } |
| |
| return ath11k_wmi_peer_set_smart_tx_ant(ar, arvif->vdev_id, macaddr, |
| tx_ants); |
| } |
| |
| int ath11k_smart_ant_alg_set_default(struct ath11k_vif *arvif) |
| { |
| struct ath11k *ar = arvif->ar; |
| struct ath11k_smart_ant_info *info = &ar->smart_ant_info; |
| int ret, i; |
| u32 tx_ants[ATH11K_SMART_ANT_MAX_RATE_SERIES]; |
| |
| lockdep_assert_held(&ar->conf_mutex); |
| |
| if (!ath11k_smart_ant_enabled(ar)) |
| return 0; |
| |
| if (!info->enabled) |
| return 0; |
| |
| if (arvif->vdev_type != WMI_VDEV_TYPE_AP || |
| arvif->vdev_subtype != WMI_VDEV_SUBTYPE_NONE) { |
| ath11k_dbg(ar->ab, ATH11K_DBG_SMART_ANT, |
| "Smart antenna logic not enabled for non-AP interface\n"); |
| return 0; |
| } |
| |
| // smart antenna callback get rx antenna |
| ath11k_sa_api_get_rxantenna(&g_smart_ant_obj, arvif, &info->default_ant); |
| |
| /* Set default tx/rx antennas to start with */ |
| ret = ath11k_wmi_pdev_set_rx_ant(ar, info->default_ant); |
| if (ret) { |
| ath11k_warn(ar->ab, "Failed to set rx antenna\n"); |
| return ret; |
| } |
| ret = ath11k_wmi_pdev_set_param(ar, WMI_PDEV_PARAM_SMART_ANTENNA_DEFAULT_ANTENNA, |
| info->default_ant, |
| ar->pdev->pdev_id); |
| if (ret) { |
| ath11k_warn(ar->ab, |
| "Failed to set default antenna\n", |
| ret); |
| } |
| ath11k_wmi_pdev_set_param(ar, |
| WMI_PDEV_PARAM_SA_PARALLEL_MODE_GPIO_DRIVE_CFG, |
| info->default_ant, ar->pdev->pdev_id); |
| |
| // smart antenna callback get default tx antenna |
| ath11k_sa_api_get_txdefault_antenna(&g_smart_ant_obj, arvif, &info->default_ant); |
| |
| /* Tx antenna for every fallback rate series */ |
| |
| for (i = 0; i < info->num_fallback_rate; i++) |
| tx_ants[i] = info->default_ant; |
| |
| ret = ath11k_wmi_peer_set_smart_tx_ant(ar, arvif->vdev_id, |
| arvif->vif->addr, tx_ants); |
| if (ret) |
| ath11k_warn(ar->ab, "Failed to set tx antenna\n"); |
| |
| return ret; |
| } |
| |
| void ath11k_smart_ant_alg_disable(struct ath11k_vif *arvif) |
| { |
| struct ath11k *ar = arvif->ar; |
| struct ath11k_smart_ant_info *info = &ar->smart_ant_info; |
| int ret; |
| |
| lockdep_assert_held(&ar->conf_mutex); |
| |
| if (!ath11k_smart_ant_enabled(ar)) |
| return; |
| |
| if (!info->enabled) |
| return; |
| |
| if (arvif->vdev_type != WMI_VDEV_TYPE_AP || |
| arvif->vdev_subtype != WMI_VDEV_SUBTYPE_NONE) |
| return; |
| |
| /* See if this is the last vif requesting to disable smart antenna */ |
| info->num_enabled_vif--; |
| if (info->num_enabled_vif != 0) |
| return; |
| |
| /* Disable smart antenna logic in fw */ |
| ret = ath11k_wmi_pdev_disable_smart_ant(ar, ATH11K_SMART_ANT_DISABLE, info); |
| if (ret) { |
| ath11k_err(ar->ab, "Wmi command to disable smart antenna is failed\n"); |
| return; |
| } |
| |
| // smart antenna callback radio deinit |
| ath11k_sa_api_radio_deinit(&g_smart_ant_obj, arvif); |
| |
| info->enabled = false; |
| } |
| |
| int ath11k_smart_ant_alg_enable(struct ath11k_vif *arvif) |
| { |
| struct ath11k *ar = arvif->ar; |
| struct ath11k_smart_ant_info *info = &ar->smart_ant_info; |
| int ret; |
| |
| lockdep_assert_held(&ar->conf_mutex); |
| |
| if (!ath11k_smart_ant_enabled(ar)) |
| return 0; |
| |
| /* Smart antenna is tested with only AP mode, it can also be enabled |
| * for other modes, just needs more testing. |
| */ |
| if (arvif->vdev_type != WMI_VDEV_TYPE_AP || |
| arvif->vdev_subtype != WMI_VDEV_SUBTYPE_NONE) { |
| ath11k_dbg(ar->ab, ATH11K_DBG_SMART_ANT, |
| "Smart antenna logic not enabled for non-AP interface\n"); |
| return 0; |
| } |
| |
| info->num_enabled_vif++; |
| if (info->enabled) |
| return 0; |
| |
| smart_ant_alg_init_param(ar); |
| |
| ath11k_dbg(ar->ab, ATH11K_DBG_SMART_ANT, |
| "Hw supports Smart antenna, enabling it in driver\n"); |
| |
| info->enabled = true; |
| |
| ath11k_dbg(ar->ab, ATH11K_DBG_SMART_ANT, |
| "smart mode: %d num_fallback_rate: %d num_rx_chain: %d info_enable: %d\n", |
| ar->smart_ant_info.mode, ar->smart_ant_info.num_fallback_rate, |
| ar->smart_ant_info.smart_ant_params.num_rx_chain, info->enabled); |
| |
| /* Enable smart antenna logic in fw with mode and default antenna */ |
| ret = ath11k_wmi_pdev_enable_smart_ant(ar, ATH11K_SMART_ANT_ENABLE, info); |
| if (ret) { |
| ath11k_err(ar->ab, "Wmi command to enable smart antenna is failed\n"); |
| return ret; |
| } |
| |
| /* Set the smart antenna GPIO 14 configuration in fw */ |
| ret = ath11k_wmi_set_gpio_smart_ant(ar, info, ATH11K_SMART_ANT_PIN0); |
| if (ret) { |
| ath11k_err(ar->ab, "Wmi command to set gpio %d for smart antenna is failed\n", |
| ATH11K_SMART_ANT_PIN0); |
| return ret; |
| } |
| |
| /* Set the smart antenna GPIO 15 configuration in fw */ |
| ret = ath11k_wmi_set_gpio_smart_ant(ar, info, ATH11K_SMART_ANT_PIN1); |
| if (ret) { |
| ath11k_err(ar->ab, "Wmi command to set gpio %d for smart antenna is failed\n", |
| ATH11K_SMART_ANT_PIN1); |
| return ret; |
| } |
| |
| ret = ath11k_wmi_pdev_set_param(ar, WMI_PDEV_PARAM_SA_PARALLEL_MODE_GPIO_DRIVE_CFG, |
| ATH11K_SMART_ANT_GPIO_VALUE, |
| ar->pdev->pdev_id); |
| if (ret) { |
| ath11k_warn(ar->ab, |
| "Failed to set sa parallel mode gpio drive config\n", |
| ret); |
| return ret; |
| } |
| |
| // smart antenna callback radio init |
| ath11k_sa_api_radio_init(&g_smart_ant_obj, arvif); |
| |
| return 0; |
| } |
| |
| static void ath11k_smart_ant_dbg_tx_feedback(struct ath11k_base *ab, |
| struct ath11k_smart_ant_tx_feedback *fb) |
| { |
| if (fb->npackets) |
| ath11k_dbg(ab, ATH11K_DBG_SMART_ANT, |
| "Tx feedback npkts: %d nbad: %d S-Retry: %d L-Retry: %d " |
| "tx_antenna: %d train pkt: %s goodput: %d rate_maxphy: [0x%x|0x%x|0x%x|0x%x] " |
| "RSSI: [%d %d %d %d %d %d %d %d] rate_mcs: %d rate_nss: %d rate_index: %d\n", |
| fb->npackets, fb->nbad, fb->nshort_retries, fb->nlong_retries, |
| fb->tx_antenna, fb->is_trainpkt ? "True" : "False", fb->goodput, |
| fb->ratemaxphy[0], fb->ratemaxphy[1], fb->ratemaxphy[2], |
| fb->ratemaxphy[3], fb->rssi[0], fb->rssi[1], fb->rssi[2], |
| fb->rssi[3], fb->rssi[4], fb->rssi[5], fb->rssi[6], |
| fb->rssi[7], fb->rate_mcs[0], fb->rate_mcs[1], |
| fb->rate_index); |
| } |
| |
| static void ath11k_smart_ant_tx_fb_fill(struct ath11k *ar, u32 *data, |
| struct ath11k_smart_ant_tx_feedback *fb, |
| struct ath11k_sta *arsta) |
| { |
| struct htt_ppdu_stats_usr_cmpltn_cmn *cmpltn_cmn; |
| int i; |
| |
| cmpltn_cmn = (struct htt_ppdu_stats_usr_cmpltn_cmn *)data; |
| |
| fb->npackets = cmpltn_cmn->mpdu_tried; |
| fb->nbad = cmpltn_cmn->mpdu_tried - cmpltn_cmn->mpdu_success; |
| fb->rate_mcs[0] = arsta->txrate.mcs; |
| fb->rate_mcs[1] = arsta->txrate.nss; |
| fb->rate_mcs[2] = (cmpltn_cmn->status & 0x1U); |
| fb->rate_index = arsta->txrate.bw; |
| |
| fb->nlong_retries = TXFD_MS(cmpltn_cmn->flags, ATH11K_SMART_ANT_LRETRY); |
| fb->nshort_retries = TXFD_MS(cmpltn_cmn->flags, ATH11K_SMART_ANT_SRETRY); |
| |
| for (i = 0; i < ATH11K_SMART_ANT_MAX_SA_RSSI_CHAINS; i++) |
| fb->rssi[i] = cmpltn_cmn->chain_rssi[i]; |
| |
| fb->tx_antenna = cmpltn_cmn->tx_antenna_mask; |
| fb->is_trainpkt = TXFD_MS(cmpltn_cmn->pending_training_pkts, |
| ATH11K_SMART_ANT_IS_TRAIN_PKT); |
| fb->goodput = TXFD_MS(cmpltn_cmn->pending_training_pkts, |
| ATH11K_SMART_ANT_TRAIN_PKTS); |
| |
| for (i = 0; i < ATH11K_SMART_ANT_MAX_RATE_COUNTERS; i++) |
| fb->ratemaxphy[i] = cmpltn_cmn->max_rates[i]; |
| |
| if (fb->npackets) |
| ath11k_dbg(ar->ab, ATH11K_DBG_SMART_ANT, "Tx feedback from sta: %pM\n", |
| arsta->smart_ant_sta->mac_addr); |
| |
| ath11k_smart_ant_dbg_tx_feedback(ar->ab, fb); |
| } |
| |
| void |
| ath11k_smart_ant_alg_proc_tx_feedback(struct ath11k_base *ab, |
| u32 *data, |
| u16 peer_id) |
| { |
| struct ath11k_sta *arsta; |
| struct ath11k_peer *peer = NULL; |
| struct ath11k *ar; |
| struct ath11k_smart_ant_tx_feedback tx_feedback; |
| struct ieee80211_supported_band *band; |
| |
| rcu_read_lock(); |
| spin_lock_bh(&ab->base_lock); |
| peer = ath11k_peer_find_by_id(ab, peer_id); |
| if(peer && peer->sta) { |
| arsta = (struct ath11k_sta *)peer->sta->drv_priv; |
| |
| if (!arsta->smart_ant_sta) |
| goto exit; |
| |
| memset(&tx_feedback, 0, sizeof(tx_feedback)); |
| ar = arsta->arvif->ar; |
| if (!ar) |
| goto exit; |
| |
| band = &ar->mac.sbands[NL80211_BAND_5GHZ]; |
| if (band->band == NL80211_BAND_5GHZ) { |
| ath11k_smart_ant_tx_fb_fill(ar, data, &tx_feedback, arsta); |
| // smart antenna callback tx feedback |
| if (ath11k_sa_api_update_txfeedback(&g_smart_ant_obj, peer->sta, |
| &tx_feedback) < 0) { |
| ath11k_warn(ab, ATH11K_DBG_SMART_ANT, |
| "Tx feedback from sta: %pM: FAILURE\n", peer->addr); |
| } |
| } |
| } |
| exit: |
| spin_unlock_bh(&ab->base_lock); |
| rcu_read_unlock(); |
| } |
| |
| static void ath11k_smart_ant_dbg_rx_feedback(struct ath11k_base *ab, |
| struct ath11k_smart_ant_rx_feedback *fb, |
| u32 evm_count) |
| { |
| int i, j; |
| |
| ath11k_dbg(ab, ATH11K_DBG_SMART_ANT, |
| "Rx feedback npkts: %d rx_antenna: %d rate_index: %d " |
| "rx_rate_mcs: %d rx_rate_nss: %d pilot_count: %d nss_count: %d", |
| fb->npackets, fb->rx_antenna, fb->rx_rate_index, |
| fb->rx_rate_mcs, fb->rx_rate_nss, fb->pilot_count, fb->nss_count); |
| |
| if (fb->pilot_count * fb->nss_count) { |
| switch (fb->rx_rate_index) { |
| case ATH11K_BW_20: |
| if (evm_count == ATH11K_SMART_ANT_MAX_EVM_1SS_SIZE) |
| ath11k_dbg(ab, ATH11K_DBG_SMART_ANT, "Rx feedback 20MHZ NSS1 rx_acc_linear_evm | pilot_error\n"); |
| if (evm_count == ATH11K_SMART_ANT_MAX_EVM_2SS_SIZE) |
| ath11k_dbg(ab, ATH11K_DBG_SMART_ANT, "Rx feedback 20MHZ NSS2 rx_acc_linear_evm | pilot_error\n"); |
| break; |
| case ATH11K_BW_40: |
| if (evm_count == ATH11K_SMART_ANT_MAX_EVM_1SS_SIZE) |
| ath11k_dbg(ab, ATH11K_DBG_SMART_ANT, "Rx feedback 40MHZ NSS1 rx_acc_linear_evm | pilot_error\n"); |
| if (evm_count == ATH11K_SMART_ANT_MAX_EVM_2SS_SIZE) |
| ath11k_dbg(ab, ATH11K_DBG_SMART_ANT, "Rx feedback 40MHZ NSS2 rx_acc_linear_evm | pilot_error\n"); |
| break; |
| case ATH11K_BW_80: |
| if (evm_count == ATH11K_SMART_ANT_MAX_EVM_1SS_SIZE) |
| ath11k_dbg(ab, ATH11K_DBG_SMART_ANT, "Rx feedback 80MHZ NSS1 rx_acc_linear_evm | pilot_error\n"); |
| if (evm_count == ATH11K_SMART_ANT_MAX_EVM_2SS_SIZE) |
| ath11k_dbg(ab, ATH11K_DBG_SMART_ANT, "Rx feedback 80MHZ NSS2 rx_acc_linear_evm | pilot_error\n"); |
| break; |
| case ATH11K_BW_160: |
| if (evm_count == ATH11K_SMART_ANT_MAX_EVM_1SS_SIZE) |
| ath11k_dbg(ab, ATH11K_DBG_SMART_ANT, "Rx feedback 160MHZ NSS1 rx_acc_linear_evm | pilot_error\n"); |
| if (evm_count == ATH11K_SMART_ANT_MAX_EVM_2SS_SIZE) |
| ath11k_dbg(ab, ATH11K_DBG_SMART_ANT, "Rx feedback 160MHZ NSS2 rx_acc_linear_evm | pilot_error\n"); |
| break; |
| } |
| |
| |
| for (i = 0; i < fb->pilot_count * fb->nss_count; i++) { |
| ath11k_dbg(ab, ATH11K_DBG_SMART_ANT, "Rx feedback %-4d | %-4d ", |
| fb->rx_evm[i], fb->rx_pilot_err[i]); |
| } |
| } |
| |
| |
| ath11k_dbg(ab, ATH11K_DBG_SMART_ANT, "Rx feedback RSSI: %-5s %-5s %-5s %-5s \n", |
| "bw0", "bw1", "bw2", "bw4"); |
| for (i = 0; i < ATH11K_SMART_ANT_MAX_SA_RSSI_CHAINS; i++) { |
| for (j = 0; j < ATH11K_SMART_ANT_MAX_BW; j++) { |
| ath11k_dbg(ab, ATH11K_DBG_SMART_ANT, |
| "Rx feedback Chain%d [%-5d %-5d %-5d %-5d]", |
| i, fb->rx_rssi[i][j], fb->rx_rssi[i][j + 1], |
| fb->rx_rssi[i][j + 2], fb->rx_rssi[i][j + 3]); |
| j += 3; |
| } |
| } |
| } |
| |
| static void ath11k_smart_ant_rx_fb_fill(struct ath11k_base *ab, |
| struct hal_rx_mon_ppdu_info *ppdu_info, |
| struct ath11k_smart_ant_rx_feedback *fb, |
| struct ath11k_sta *arsta) |
| { |
| u32 evm_count; |
| int i, j; |
| |
| fb->rx_rate_index = arsta->smart_ant_sta->rx_feedback.rx_rate_index; |
| fb->rx_rate_mcs = arsta->smart_ant_sta->rx_feedback.rx_rate_mcs; |
| fb->rx_rate_nss = arsta->smart_ant_sta->rx_feedback.rx_rate_nss; |
| fb->rx_antenna = arsta->smart_ant_sta->rx_feedback.rx_antenna; |
| fb->npackets = arsta->smart_ant_sta->rx_feedback.npackets; |
| arsta->smart_ant_sta->rx_feedback.npackets = 0; |
| fb->nss_count = ppdu_info->evm_info.nss_count; |
| fb->pilot_count = ppdu_info->evm_info.pilot_count; |
| |
| if (ppdu_info->evm_info.nss_count == 1) { |
| evm_count = HAL_RX_MAX_SU_EVM_COUNT_1SS; |
| for (i = 0; i < evm_count; i++) { |
| fb->rx_evm[i] = |
| ppdu_info->evm_info.evm_1ss[i].acc_linear_evm; |
| fb->rx_pilot_err[i]= |
| ppdu_info->evm_info.evm_1ss[i].pilot_error; |
| } |
| } |
| |
| if (ppdu_info->evm_info.nss_count == 2) { |
| evm_count = HAL_RX_MAX_SU_EVM_COUNT_2SS; |
| for (i = 0; i < evm_count; i++) { |
| fb->rx_evm[i] = |
| ppdu_info->evm_info.evm_2ss[i].acc_linear_evm; |
| fb->rx_pilot_err[i]= |
| ppdu_info->evm_info.evm_2ss[i].pilot_error; |
| } |
| } |
| |
| for (i = 0; i < ATH11K_SMART_ANT_MAX_SA_RSSI_CHAINS; i++) { |
| for (j = 0; j < ATH11K_SMART_ANT_MAX_BW; j++) { |
| fb->rx_rssi[i][j] = ppdu_info->rssi_chain[i][j]; |
| } |
| } |
| |
| ath11k_smart_ant_dbg_rx_feedback(ab, fb, evm_count); |
| } |
| |
| void |
| ath11k_smart_ant_alg_proc_rx_feedback(struct ath11k_base *ab, |
| struct hal_rx_mon_ppdu_info *ppdu_info) |
| { |
| struct ath11k_sta *arsta; |
| struct ath11k_peer *peer = NULL; |
| struct ath11k *ar; |
| struct ath11k_smart_ant_rx_feedback rx_feedback; |
| struct ieee80211_supported_band *band; |
| |
| rcu_read_lock(); |
| spin_lock_bh(&ab->base_lock); |
| peer = ath11k_peer_find_by_id(ab, ppdu_info->peer_id); |
| if (peer && peer->sta) { |
| arsta = (struct ath11k_sta *)peer->sta->drv_priv; |
| if ((!arsta) || (!arsta->smart_ant_sta)) |
| goto exit; |
| |
| if (!arsta->arvif) |
| goto exit; |
| |
| ar = arsta->arvif->ar; |
| if (!ar) |
| goto exit; |
| |
| band = &ar->mac.sbands[NL80211_BAND_5GHZ]; |
| if ((band) && (band->band == NL80211_BAND_5GHZ)) { |
| memset(&rx_feedback, 0, sizeof(rx_feedback)); |
| ath11k_dbg(ab, ATH11K_DBG_SMART_ANT, "Rx feedback from sta: %pM\n", peer->addr); |
| ath11k_smart_ant_rx_fb_fill(ab, ppdu_info, &rx_feedback, arsta); |
| // smart antenna callback rx feedback |
| ath11k_sa_api_update_rxfeedback(&g_smart_ant_obj, peer->sta, &rx_feedback); |
| } |
| } |
| exit: |
| spin_unlock_bh(&ab->base_lock); |
| rcu_read_unlock(); |
| } |