]> git.kernelconcepts.de Git - karo-tx-linux.git/blobdiff - drivers/net/wireless/ath/ath10k/wmi.c
Merge git://git.kernel.org/pub/scm/linux/kernel/git/davem/ide
[karo-tx-linux.git] / drivers / net / wireless / ath / ath10k / wmi.c
index ce01107ef37aaacd413b0bb98affbc0dc481afff..7569db0f69b5eed8f69d65c3e881f56bf54129b4 100644 (file)
@@ -148,6 +148,7 @@ static struct wmi_cmd_map wmi_cmd_map = {
        .gpio_config_cmdid = WMI_GPIO_CONFIG_CMDID,
        .gpio_output_cmdid = WMI_GPIO_OUTPUT_CMDID,
        .pdev_get_temperature_cmdid = WMI_CMD_UNSUPPORTED,
+       .pdev_enable_adaptive_cca_cmdid = WMI_CMD_UNSUPPORTED,
        .scan_update_request_cmdid = WMI_CMD_UNSUPPORTED,
        .vdev_standby_response_cmdid = WMI_CMD_UNSUPPORTED,
        .vdev_resume_response_cmdid = WMI_CMD_UNSUPPORTED,
@@ -313,6 +314,7 @@ static struct wmi_cmd_map wmi_10x_cmd_map = {
        .gpio_config_cmdid = WMI_10X_GPIO_CONFIG_CMDID,
        .gpio_output_cmdid = WMI_10X_GPIO_OUTPUT_CMDID,
        .pdev_get_temperature_cmdid = WMI_CMD_UNSUPPORTED,
+       .pdev_enable_adaptive_cca_cmdid = WMI_CMD_UNSUPPORTED,
        .scan_update_request_cmdid = WMI_CMD_UNSUPPORTED,
        .vdev_standby_response_cmdid = WMI_CMD_UNSUPPORTED,
        .vdev_resume_response_cmdid = WMI_CMD_UNSUPPORTED,
@@ -477,6 +479,7 @@ static struct wmi_cmd_map wmi_10_2_4_cmd_map = {
        .gpio_config_cmdid = WMI_10_2_GPIO_CONFIG_CMDID,
        .gpio_output_cmdid = WMI_10_2_GPIO_OUTPUT_CMDID,
        .pdev_get_temperature_cmdid = WMI_10_2_PDEV_GET_TEMPERATURE_CMDID,
+       .pdev_enable_adaptive_cca_cmdid = WMI_10_2_SET_CCA_PARAMS,
        .scan_update_request_cmdid = WMI_CMD_UNSUPPORTED,
        .vdev_standby_response_cmdid = WMI_CMD_UNSUPPORTED,
        .vdev_resume_response_cmdid = WMI_CMD_UNSUPPORTED,
@@ -1407,6 +1410,7 @@ static struct wmi_cmd_map wmi_10_2_cmd_map = {
        .gpio_config_cmdid = WMI_10_2_GPIO_CONFIG_CMDID,
        .gpio_output_cmdid = WMI_10_2_GPIO_OUTPUT_CMDID,
        .pdev_get_temperature_cmdid = WMI_CMD_UNSUPPORTED,
+       .pdev_enable_adaptive_cca_cmdid = WMI_CMD_UNSUPPORTED,
        .scan_update_request_cmdid = WMI_CMD_UNSUPPORTED,
        .vdev_standby_response_cmdid = WMI_CMD_UNSUPPORTED,
        .vdev_resume_response_cmdid = WMI_CMD_UNSUPPORTED,
@@ -2475,6 +2479,47 @@ void ath10k_wmi_pull_pdev_stats_tx(const struct wmi_pdev_stats_tx *src,
        dst->txop_ovf = __le32_to_cpu(src->txop_ovf);
 }
 
+static void
+ath10k_wmi_10_4_pull_pdev_stats_tx(const struct wmi_10_4_pdev_stats_tx *src,
+                                  struct ath10k_fw_stats_pdev *dst)
+{
+       dst->comp_queued = __le32_to_cpu(src->comp_queued);
+       dst->comp_delivered = __le32_to_cpu(src->comp_delivered);
+       dst->msdu_enqued = __le32_to_cpu(src->msdu_enqued);
+       dst->mpdu_enqued = __le32_to_cpu(src->mpdu_enqued);
+       dst->wmm_drop = __le32_to_cpu(src->wmm_drop);
+       dst->local_enqued = __le32_to_cpu(src->local_enqued);
+       dst->local_freed = __le32_to_cpu(src->local_freed);
+       dst->hw_queued = __le32_to_cpu(src->hw_queued);
+       dst->hw_reaped = __le32_to_cpu(src->hw_reaped);
+       dst->underrun = __le32_to_cpu(src->underrun);
+       dst->tx_abort = __le32_to_cpu(src->tx_abort);
+       dst->mpdus_requed = __le32_to_cpu(src->mpdus_requed);
+       dst->tx_ko = __le32_to_cpu(src->tx_ko);
+       dst->data_rc = __le32_to_cpu(src->data_rc);
+       dst->self_triggers = __le32_to_cpu(src->self_triggers);
+       dst->sw_retry_failure = __le32_to_cpu(src->sw_retry_failure);
+       dst->illgl_rate_phy_err = __le32_to_cpu(src->illgl_rate_phy_err);
+       dst->pdev_cont_xretry = __le32_to_cpu(src->pdev_cont_xretry);
+       dst->pdev_tx_timeout = __le32_to_cpu(src->pdev_tx_timeout);
+       dst->pdev_resets = __le32_to_cpu(src->pdev_resets);
+       dst->phy_underrun = __le32_to_cpu(src->phy_underrun);
+       dst->txop_ovf = __le32_to_cpu(src->txop_ovf);
+       dst->hw_paused = __le32_to_cpu(src->hw_paused);
+       dst->seq_posted = __le32_to_cpu(src->seq_posted);
+       dst->seq_failed_queueing =
+               __le32_to_cpu(src->seq_failed_queueing);
+       dst->seq_completed = __le32_to_cpu(src->seq_completed);
+       dst->seq_restarted = __le32_to_cpu(src->seq_restarted);
+       dst->mu_seq_posted = __le32_to_cpu(src->mu_seq_posted);
+       dst->mpdus_sw_flush = __le32_to_cpu(src->mpdus_sw_flush);
+       dst->mpdus_hw_filter = __le32_to_cpu(src->mpdus_hw_filter);
+       dst->mpdus_truncated = __le32_to_cpu(src->mpdus_truncated);
+       dst->mpdus_ack_failed = __le32_to_cpu(src->mpdus_ack_failed);
+       dst->mpdus_hw_filter = __le32_to_cpu(src->mpdus_hw_filter);
+       dst->mpdus_expired = __le32_to_cpu(src->mpdus_expired);
+}
+
 void ath10k_wmi_pull_pdev_stats_rx(const struct wmi_pdev_stats_rx *src,
                                   struct ath10k_fw_stats_pdev *dst)
 {
@@ -2785,6 +2830,86 @@ static int ath10k_wmi_10_2_4_op_pull_fw_stats(struct ath10k *ar,
        return 0;
 }
 
+static int ath10k_wmi_10_4_op_pull_fw_stats(struct ath10k *ar,
+                                           struct sk_buff *skb,
+                                           struct ath10k_fw_stats *stats)
+{
+       const struct wmi_10_2_stats_event *ev = (void *)skb->data;
+       u32 num_pdev_stats;
+       u32 num_pdev_ext_stats;
+       u32 num_vdev_stats;
+       u32 num_peer_stats;
+       int i;
+
+       if (!skb_pull(skb, sizeof(*ev)))
+               return -EPROTO;
+
+       num_pdev_stats = __le32_to_cpu(ev->num_pdev_stats);
+       num_pdev_ext_stats = __le32_to_cpu(ev->num_pdev_ext_stats);
+       num_vdev_stats = __le32_to_cpu(ev->num_vdev_stats);
+       num_peer_stats = __le32_to_cpu(ev->num_peer_stats);
+
+       for (i = 0; i < num_pdev_stats; i++) {
+               const struct wmi_10_4_pdev_stats *src;
+               struct ath10k_fw_stats_pdev *dst;
+
+               src = (void *)skb->data;
+               if (!skb_pull(skb, sizeof(*src)))
+                       return -EPROTO;
+
+               dst = kzalloc(sizeof(*dst), GFP_ATOMIC);
+               if (!dst)
+                       continue;
+
+               ath10k_wmi_pull_pdev_stats_base(&src->base, dst);
+               ath10k_wmi_10_4_pull_pdev_stats_tx(&src->tx, dst);
+               ath10k_wmi_pull_pdev_stats_rx(&src->rx, dst);
+               dst->rx_ovfl_errs = __le32_to_cpu(src->rx_ovfl_errs);
+               ath10k_wmi_pull_pdev_stats_extra(&src->extra, dst);
+
+               list_add_tail(&dst->list, &stats->pdevs);
+       }
+
+       for (i = 0; i < num_pdev_ext_stats; i++) {
+               const struct wmi_10_2_pdev_ext_stats *src;
+
+               src = (void *)skb->data;
+               if (!skb_pull(skb, sizeof(*src)))
+                       return -EPROTO;
+
+               /* FIXME: expose values to userspace
+                *
+                * Note: Even though this loop seems to do nothing it is
+                * required to parse following sub-structures properly.
+                */
+       }
+
+       /* fw doesn't implement vdev stats */
+
+       for (i = 0; i < num_peer_stats; i++) {
+               const struct wmi_10_4_peer_stats *src;
+               struct ath10k_fw_stats_peer *dst;
+
+               src = (void *)skb->data;
+               if (!skb_pull(skb, sizeof(*src)))
+                       return -EPROTO;
+
+               dst = kzalloc(sizeof(*dst), GFP_ATOMIC);
+               if (!dst)
+                       continue;
+
+               ether_addr_copy(dst->peer_macaddr, src->peer_macaddr.addr);
+               dst->peer_rssi = __le32_to_cpu(src->peer_rssi);
+               dst->peer_tx_rate = __le32_to_cpu(src->peer_tx_rate);
+               dst->peer_rx_rate = __le32_to_cpu(src->peer_rx_rate);
+               /* FIXME: expose 10.4 specific values */
+
+               list_add_tail(&dst->list, &stats->peers);
+       }
+
+       return 0;
+}
+
 void ath10k_wmi_event_update_stats(struct ath10k *ar, struct sk_buff *skb)
 {
        ath10k_dbg(ar, ATH10K_DBG_WMI, "WMI_UPDATE_STATS_EVENTID\n");
@@ -3018,8 +3143,6 @@ static void ath10k_wmi_update_noa(struct ath10k *ar, struct ath10k_vif *arvif,
                        memcpy(skb_put(bcn, arvif->u.ap.noa_len),
                               arvif->u.ap.noa_data,
                               arvif->u.ap.noa_len);
-
-       return;
 }
 
 static int ath10k_wmi_op_pull_swba_ev(struct ath10k *ar, struct sk_buff *skb,
@@ -3507,7 +3630,7 @@ void ath10k_wmi_event_spectral_scan(struct ath10k *ar,
                                                          tsf);
                        if (res < 0) {
                                ath10k_dbg(ar, ATH10K_DBG_WMI, "failed to process fft report: %d\n",
-                                           res);
+                                          res);
                                return;
                        }
                        break;
@@ -3835,9 +3958,258 @@ void ath10k_wmi_event_dcs_interference(struct ath10k *ar, struct sk_buff *skb)
        ath10k_dbg(ar, ATH10K_DBG_WMI, "WMI_DCS_INTERFERENCE_EVENTID\n");
 }
 
+static u8 ath10k_tpc_config_get_rate(struct ath10k *ar,
+                                    struct wmi_pdev_tpc_config_event *ev,
+                                    u32 rate_idx, u32 num_chains,
+                                    u32 rate_code, u8 type)
+{
+       u8 tpc, num_streams, preamble, ch, stm_idx;
+
+       num_streams = ATH10K_HW_NSS(rate_code);
+       preamble = ATH10K_HW_PREAMBLE(rate_code);
+       ch = num_chains - 1;
+
+       tpc = min_t(u8, ev->rates_array[rate_idx], ev->max_reg_allow_pow[ch]);
+
+       if (__le32_to_cpu(ev->num_tx_chain) <= 1)
+               goto out;
+
+       if (preamble == WMI_RATE_PREAMBLE_CCK)
+               goto out;
+
+       stm_idx = num_streams - 1;
+       if (num_chains <= num_streams)
+               goto out;
+
+       switch (type) {
+       case WMI_TPC_TABLE_TYPE_STBC:
+               tpc = min_t(u8, tpc,
+                           ev->max_reg_allow_pow_agstbc[ch - 1][stm_idx]);
+               break;
+       case WMI_TPC_TABLE_TYPE_TXBF:
+               tpc = min_t(u8, tpc,
+                           ev->max_reg_allow_pow_agtxbf[ch - 1][stm_idx]);
+               break;
+       case WMI_TPC_TABLE_TYPE_CDD:
+               tpc = min_t(u8, tpc,
+                           ev->max_reg_allow_pow_agcdd[ch - 1][stm_idx]);
+               break;
+       default:
+               ath10k_warn(ar, "unknown wmi tpc table type: %d\n", type);
+               tpc = 0;
+               break;
+       }
+
+out:
+       return tpc;
+}
+
+static void ath10k_tpc_config_disp_tables(struct ath10k *ar,
+                                         struct wmi_pdev_tpc_config_event *ev,
+                                         struct ath10k_tpc_stats *tpc_stats,
+                                         u8 *rate_code, u16 *pream_table, u8 type)
+{
+       u32 i, j, pream_idx, flags;
+       u8 tpc[WMI_TPC_TX_N_CHAIN];
+       char tpc_value[WMI_TPC_TX_N_CHAIN * WMI_TPC_BUF_SIZE];
+       char buff[WMI_TPC_BUF_SIZE];
+
+       flags = __le32_to_cpu(ev->flags);
+
+       switch (type) {
+       case WMI_TPC_TABLE_TYPE_CDD:
+               if (!(flags & WMI_TPC_CONFIG_EVENT_FLAG_TABLE_CDD)) {
+                       ath10k_dbg(ar, ATH10K_DBG_WMI, "CDD not supported\n");
+                       tpc_stats->flag[type] = ATH10K_TPC_TABLE_TYPE_FLAG;
+                       return;
+               }
+               break;
+       case WMI_TPC_TABLE_TYPE_STBC:
+               if (!(flags & WMI_TPC_CONFIG_EVENT_FLAG_TABLE_STBC)) {
+                       ath10k_dbg(ar, ATH10K_DBG_WMI, "STBC not supported\n");
+                       tpc_stats->flag[type] = ATH10K_TPC_TABLE_TYPE_FLAG;
+                       return;
+               }
+               break;
+       case WMI_TPC_TABLE_TYPE_TXBF:
+               if (!(flags & WMI_TPC_CONFIG_EVENT_FLAG_TABLE_TXBF)) {
+                       ath10k_dbg(ar, ATH10K_DBG_WMI, "TXBF not supported\n");
+                       tpc_stats->flag[type] = ATH10K_TPC_TABLE_TYPE_FLAG;
+                       return;
+               }
+               break;
+       default:
+               ath10k_dbg(ar, ATH10K_DBG_WMI,
+                          "invalid table type in wmi tpc event: %d\n", type);
+               return;
+       }
+
+       pream_idx = 0;
+       for (i = 0; i < __le32_to_cpu(ev->rate_max); i++) {
+               memset(tpc_value, 0, sizeof(tpc_value));
+               memset(buff, 0, sizeof(buff));
+               if (i == pream_table[pream_idx])
+                       pream_idx++;
+
+               for (j = 0; j < WMI_TPC_TX_N_CHAIN; j++) {
+                       if (j >= __le32_to_cpu(ev->num_tx_chain))
+                               break;
+
+                       tpc[j] = ath10k_tpc_config_get_rate(ar, ev, i, j + 1,
+                                                           rate_code[i],
+                                                           type);
+                       snprintf(buff, sizeof(buff), "%8d ", tpc[j]);
+                       strncat(tpc_value, buff, strlen(buff));
+               }
+               tpc_stats->tpc_table[type].pream_idx[i] = pream_idx;
+               tpc_stats->tpc_table[type].rate_code[i] = rate_code[i];
+               memcpy(tpc_stats->tpc_table[type].tpc_value[i],
+                      tpc_value, sizeof(tpc_value));
+       }
+}
+
 void ath10k_wmi_event_pdev_tpc_config(struct ath10k *ar, struct sk_buff *skb)
 {
-       ath10k_dbg(ar, ATH10K_DBG_WMI, "WMI_PDEV_TPC_CONFIG_EVENTID\n");
+       u32 i, j, pream_idx, num_tx_chain;
+       u8 rate_code[WMI_TPC_RATE_MAX], rate_idx;
+       u16 pream_table[WMI_TPC_PREAM_TABLE_MAX];
+       struct wmi_pdev_tpc_config_event *ev;
+       struct ath10k_tpc_stats *tpc_stats;
+
+       ev = (struct wmi_pdev_tpc_config_event *)skb->data;
+
+       tpc_stats = kzalloc(sizeof(*tpc_stats), GFP_ATOMIC);
+       if (!tpc_stats)
+               return;
+
+       /* Create the rate code table based on the chains supported */
+       rate_idx = 0;
+       pream_idx = 0;
+
+       /* Fill CCK rate code */
+       for (i = 0; i < 4; i++) {
+               rate_code[rate_idx] =
+                       ATH10K_HW_RATECODE(i, 0, WMI_RATE_PREAMBLE_CCK);
+               rate_idx++;
+       }
+       pream_table[pream_idx] = rate_idx;
+       pream_idx++;
+
+       /* Fill OFDM rate code */
+       for (i = 0; i < 8; i++) {
+               rate_code[rate_idx] =
+                       ATH10K_HW_RATECODE(i, 0, WMI_RATE_PREAMBLE_OFDM);
+               rate_idx++;
+       }
+       pream_table[pream_idx] = rate_idx;
+       pream_idx++;
+
+       num_tx_chain = __le32_to_cpu(ev->num_tx_chain);
+
+       /* Fill HT20 rate code */
+       for (i = 0; i < num_tx_chain; i++) {
+               for (j = 0; j < 8; j++) {
+                       rate_code[rate_idx] =
+                       ATH10K_HW_RATECODE(j, i, WMI_RATE_PREAMBLE_HT);
+                       rate_idx++;
+               }
+       }
+       pream_table[pream_idx] = rate_idx;
+       pream_idx++;
+
+       /* Fill HT40 rate code */
+       for (i = 0; i < num_tx_chain; i++) {
+               for (j = 0; j < 8; j++) {
+                       rate_code[rate_idx] =
+                       ATH10K_HW_RATECODE(j, i, WMI_RATE_PREAMBLE_HT);
+                       rate_idx++;
+               }
+       }
+       pream_table[pream_idx] = rate_idx;
+       pream_idx++;
+
+       /* Fill VHT20 rate code */
+       for (i = 0; i < __le32_to_cpu(ev->num_tx_chain); i++) {
+               for (j = 0; j < 10; j++) {
+                       rate_code[rate_idx] =
+                       ATH10K_HW_RATECODE(j, i, WMI_RATE_PREAMBLE_VHT);
+                       rate_idx++;
+               }
+       }
+       pream_table[pream_idx] = rate_idx;
+       pream_idx++;
+
+       /* Fill VHT40 rate code */
+       for (i = 0; i < num_tx_chain; i++) {
+               for (j = 0; j < 10; j++) {
+                       rate_code[rate_idx] =
+                       ATH10K_HW_RATECODE(j, i, WMI_RATE_PREAMBLE_VHT);
+                       rate_idx++;
+               }
+       }
+       pream_table[pream_idx] = rate_idx;
+       pream_idx++;
+
+       /* Fill VHT80 rate code */
+       for (i = 0; i < num_tx_chain; i++) {
+               for (j = 0; j < 10; j++) {
+                       rate_code[rate_idx] =
+                       ATH10K_HW_RATECODE(j, i, WMI_RATE_PREAMBLE_VHT);
+                       rate_idx++;
+               }
+       }
+       pream_table[pream_idx] = rate_idx;
+       pream_idx++;
+
+       rate_code[rate_idx++] =
+               ATH10K_HW_RATECODE(0, 0, WMI_RATE_PREAMBLE_CCK);
+       rate_code[rate_idx++] =
+               ATH10K_HW_RATECODE(0, 0, WMI_RATE_PREAMBLE_OFDM);
+       rate_code[rate_idx++] =
+               ATH10K_HW_RATECODE(0, 0, WMI_RATE_PREAMBLE_CCK);
+       rate_code[rate_idx++] =
+               ATH10K_HW_RATECODE(0, 0, WMI_RATE_PREAMBLE_OFDM);
+       rate_code[rate_idx++] =
+               ATH10K_HW_RATECODE(0, 0, WMI_RATE_PREAMBLE_OFDM);
+
+       pream_table[pream_idx] = ATH10K_TPC_PREAM_TABLE_END;
+
+       tpc_stats->chan_freq = __le32_to_cpu(ev->chan_freq);
+       tpc_stats->phy_mode = __le32_to_cpu(ev->phy_mode);
+       tpc_stats->ctl = __le32_to_cpu(ev->ctl);
+       tpc_stats->reg_domain = __le32_to_cpu(ev->reg_domain);
+       tpc_stats->twice_antenna_gain = a_sle32_to_cpu(ev->twice_antenna_gain);
+       tpc_stats->twice_antenna_reduction =
+               __le32_to_cpu(ev->twice_antenna_reduction);
+       tpc_stats->power_limit = __le32_to_cpu(ev->power_limit);
+       tpc_stats->twice_max_rd_power = __le32_to_cpu(ev->twice_max_rd_power);
+       tpc_stats->num_tx_chain = __le32_to_cpu(ev->num_tx_chain);
+       tpc_stats->rate_max = __le32_to_cpu(ev->rate_max);
+
+       ath10k_tpc_config_disp_tables(ar, ev, tpc_stats,
+                                     rate_code, pream_table,
+                                     WMI_TPC_TABLE_TYPE_CDD);
+       ath10k_tpc_config_disp_tables(ar, ev,  tpc_stats,
+                                     rate_code, pream_table,
+                                     WMI_TPC_TABLE_TYPE_STBC);
+       ath10k_tpc_config_disp_tables(ar, ev, tpc_stats,
+                                     rate_code, pream_table,
+                                     WMI_TPC_TABLE_TYPE_TXBF);
+
+       ath10k_debug_tpc_stats_process(ar, tpc_stats);
+
+       ath10k_dbg(ar, ATH10K_DBG_WMI,
+                  "wmi event tpc config channel %d mode %d ctl %d regd %d gain %d %d limit %d max_power %d tx_chanins %d rates %d\n",
+                  __le32_to_cpu(ev->chan_freq),
+                  __le32_to_cpu(ev->phy_mode),
+                  __le32_to_cpu(ev->ctl),
+                  __le32_to_cpu(ev->reg_domain),
+                  a_sle32_to_cpu(ev->twice_antenna_gain),
+                  __le32_to_cpu(ev->twice_antenna_reduction),
+                  __le32_to_cpu(ev->power_limit),
+                  __le32_to_cpu(ev->twice_max_rd_power) / 2,
+                  __le32_to_cpu(ev->num_tx_chain),
+                  __le32_to_cpu(ev->rate_max));
 }
 
 void ath10k_wmi_event_pdev_ftm_intg(struct ath10k *ar, struct sk_buff *skb)
@@ -3917,6 +4289,53 @@ static int ath10k_wmi_alloc_host_mem(struct ath10k *ar, u32 req_id,
        return 0;
 }
 
+static bool
+ath10k_wmi_is_host_mem_allocated(struct ath10k *ar,
+                                const struct wlan_host_mem_req **mem_reqs,
+                                u32 num_mem_reqs)
+{
+       u32 req_id, num_units, unit_size, num_unit_info;
+       u32 pool_size;
+       int i, j;
+       bool found;
+
+       if (ar->wmi.num_mem_chunks != num_mem_reqs)
+               return false;
+
+       for (i = 0; i < num_mem_reqs; ++i) {
+               req_id = __le32_to_cpu(mem_reqs[i]->req_id);
+               num_units = __le32_to_cpu(mem_reqs[i]->num_units);
+               unit_size = __le32_to_cpu(mem_reqs[i]->unit_size);
+               num_unit_info = __le32_to_cpu(mem_reqs[i]->num_unit_info);
+
+               if (num_unit_info & NUM_UNITS_IS_NUM_ACTIVE_PEERS) {
+                       if (ar->num_active_peers)
+                               num_units = ar->num_active_peers + 1;
+                       else
+                               num_units = ar->max_num_peers + 1;
+               } else if (num_unit_info & NUM_UNITS_IS_NUM_PEERS) {
+                       num_units = ar->max_num_peers + 1;
+               } else if (num_unit_info & NUM_UNITS_IS_NUM_VDEVS) {
+                       num_units = ar->max_num_vdevs + 1;
+               }
+
+               found = false;
+               for (j = 0; j < ar->wmi.num_mem_chunks; j++) {
+                       if (ar->wmi.mem_chunks[j].req_id == req_id) {
+                               pool_size = num_units * round_up(unit_size, 4);
+                               if (ar->wmi.mem_chunks[j].len == pool_size) {
+                                       found = true;
+                                       break;
+                               }
+                       }
+               }
+               if (!found)
+                       return false;
+       }
+
+       return true;
+}
+
 static int
 ath10k_wmi_main_op_pull_svc_rdy_ev(struct ath10k *ar, struct sk_buff *skb,
                                   struct wmi_svc_rdy_ev_arg *arg)
@@ -3997,6 +4416,7 @@ static void ath10k_wmi_event_service_ready_work(struct work_struct *work)
        struct wmi_svc_rdy_ev_arg arg = {};
        u32 num_units, req_id, unit_size, num_mem_reqs, num_unit_info, i;
        int ret;
+       bool allocated;
 
        if (!skb) {
                ath10k_warn(ar, "invalid service ready event skb\n");
@@ -4040,8 +4460,10 @@ static void ath10k_wmi_event_service_ready_work(struct work_struct *work)
                ar->num_rf_chains = ar->max_spatial_stream;
        }
 
-       ar->supp_tx_chainmask = (1 << ar->num_rf_chains) - 1;
-       ar->supp_rx_chainmask = (1 << ar->num_rf_chains) - 1;
+       if (!ar->cfg_tx_chainmask) {
+               ar->cfg_tx_chainmask = (1 << ar->num_rf_chains) - 1;
+               ar->cfg_rx_chainmask = (1 << ar->num_rf_chains) - 1;
+       }
 
        if (strlen(ar->hw->wiphy->fw_version) == 0) {
                snprintf(ar->hw->wiphy->fw_version,
@@ -4073,6 +4495,18 @@ static void ath10k_wmi_event_service_ready_work(struct work_struct *work)
         * and WMI_SERVICE_IRAM_TIDS, etc.
         */
 
+       allocated = ath10k_wmi_is_host_mem_allocated(ar, arg.mem_reqs,
+                                                    num_mem_reqs);
+       if (allocated)
+               goto skip_mem_alloc;
+
+       /* Either this event is received during boot time or there is a change
+        * in memory requirement from firmware when compared to last request.
+        * Free any old memory and do a fresh allocation based on the current
+        * memory requirement.
+        */
+       ath10k_wmi_free_host_mem(ar);
+
        for (i = 0; i < num_mem_reqs; ++i) {
                req_id = __le32_to_cpu(arg.mem_reqs[i]->req_id);
                num_units = __le32_to_cpu(arg.mem_reqs[i]->num_units);
@@ -4108,6 +4542,7 @@ static void ath10k_wmi_event_service_ready_work(struct work_struct *work)
                        return;
        }
 
+skip_mem_alloc:
        ath10k_dbg(ar, ATH10K_DBG_WMI,
                   "wmi event service ready min_tx_power 0x%08x max_tx_power 0x%08x ht_cap 0x%08x vht_cap 0x%08x sw_ver0 0x%08x sw_ver1 0x%08x fw_build 0x%08x phy_capab 0x%08x num_rf_chains 0x%08x eeprom_rd 0x%08x num_mem_reqs 0x%08x\n",
                   __le32_to_cpu(arg.min_tx_power),
@@ -4623,6 +5058,9 @@ static void ath10k_wmi_10_4_op_rx(struct ath10k *ar, struct sk_buff *skb)
                ath10k_dbg(ar, ATH10K_DBG_WMI,
                           "received event id %d not implemented\n", id);
                break;
+       case WMI_10_4_UPDATE_STATS_EVENTID:
+               ath10k_wmi_event_update_stats(ar, skb);
+               break;
        default:
                ath10k_warn(ar, "Unknown eventid: %d\n", id);
                break;
@@ -5029,7 +5467,7 @@ static struct sk_buff *ath10k_wmi_10_4_op_gen_init(struct ath10k *ar)
        config.rx_timeout_pri[2] = __cpu_to_le32(TARGET_10_4_RX_TIMEOUT_LO_PRI);
        config.rx_timeout_pri[3] = __cpu_to_le32(TARGET_10_4_RX_TIMEOUT_HI_PRI);
 
-       config.rx_decap_mode        = __cpu_to_le32(TARGET_10_4_RX_DECAP_MODE);
+       config.rx_decap_mode        = __cpu_to_le32(ar->wmi.rx_decap_mode);
        config.scan_max_pending_req = __cpu_to_le32(TARGET_10_4_SCAN_MAX_REQS);
        config.bmiss_offload_max_vdev =
                        __cpu_to_le32(TARGET_10_4_BMISS_OFFLOAD_MAX_VDEV);
@@ -6295,6 +6733,505 @@ ath10k_wmi_op_gen_delba_send(struct ath10k *ar, u32 vdev_id, const u8 *mac,
        return skb;
 }
 
+static struct sk_buff *
+ath10k_wmi_10_2_4_op_gen_pdev_get_tpc_config(struct ath10k *ar, u32 param)
+{
+       struct wmi_pdev_get_tpc_config_cmd *cmd;
+       struct sk_buff *skb;
+
+       skb = ath10k_wmi_alloc_skb(ar, sizeof(*cmd));
+       if (!skb)
+               return ERR_PTR(-ENOMEM);
+
+       cmd = (struct wmi_pdev_get_tpc_config_cmd *)skb->data;
+       cmd->param = __cpu_to_le32(param);
+
+       ath10k_dbg(ar, ATH10K_DBG_WMI,
+                  "wmi pdev get tcp config param:%d\n", param);
+       return skb;
+}
+
+size_t ath10k_wmi_fw_stats_num_peers(struct list_head *head)
+{
+       struct ath10k_fw_stats_peer *i;
+       size_t num = 0;
+
+       list_for_each_entry(i, head, list)
+               ++num;
+
+       return num;
+}
+
+size_t ath10k_wmi_fw_stats_num_vdevs(struct list_head *head)
+{
+       struct ath10k_fw_stats_vdev *i;
+       size_t num = 0;
+
+       list_for_each_entry(i, head, list)
+               ++num;
+
+       return num;
+}
+
+static void
+ath10k_wmi_fw_pdev_base_stats_fill(const struct ath10k_fw_stats_pdev *pdev,
+                                  char *buf, u32 *length)
+{
+       u32 len = *length;
+       u32 buf_len = ATH10K_FW_STATS_BUF_SIZE;
+
+       len += scnprintf(buf + len, buf_len - len, "\n");
+       len += scnprintf(buf + len, buf_len - len, "%30s\n",
+                       "ath10k PDEV stats");
+       len += scnprintf(buf + len, buf_len - len, "%30s\n\n",
+                       "=================");
+
+       len += scnprintf(buf + len, buf_len - len, "%30s %10d\n",
+                       "Channel noise floor", pdev->ch_noise_floor);
+       len += scnprintf(buf + len, buf_len - len, "%30s %10u\n",
+                       "Channel TX power", pdev->chan_tx_power);
+       len += scnprintf(buf + len, buf_len - len, "%30s %10u\n",
+                       "TX frame count", pdev->tx_frame_count);
+       len += scnprintf(buf + len, buf_len - len, "%30s %10u\n",
+                       "RX frame count", pdev->rx_frame_count);
+       len += scnprintf(buf + len, buf_len - len, "%30s %10u\n",
+                       "RX clear count", pdev->rx_clear_count);
+       len += scnprintf(buf + len, buf_len - len, "%30s %10u\n",
+                       "Cycle count", pdev->cycle_count);
+       len += scnprintf(buf + len, buf_len - len, "%30s %10u\n",
+                       "PHY error count", pdev->phy_err_count);
+
+       *length = len;
+}
+
+static void
+ath10k_wmi_fw_pdev_extra_stats_fill(const struct ath10k_fw_stats_pdev *pdev,
+                                   char *buf, u32 *length)
+{
+       u32 len = *length;
+       u32 buf_len = ATH10K_FW_STATS_BUF_SIZE;
+
+       len += scnprintf(buf + len, buf_len - len, "%30s %10u\n",
+                       "RTS bad count", pdev->rts_bad);
+       len += scnprintf(buf + len, buf_len - len, "%30s %10u\n",
+                       "RTS good count", pdev->rts_good);
+       len += scnprintf(buf + len, buf_len - len, "%30s %10u\n",
+                       "FCS bad count", pdev->fcs_bad);
+       len += scnprintf(buf + len, buf_len - len, "%30s %10u\n",
+                       "No beacon count", pdev->no_beacons);
+       len += scnprintf(buf + len, buf_len - len, "%30s %10u\n",
+                       "MIB int count", pdev->mib_int_count);
+
+       len += scnprintf(buf + len, buf_len - len, "\n");
+       *length = len;
+}
+
+static void
+ath10k_wmi_fw_pdev_tx_stats_fill(const struct ath10k_fw_stats_pdev *pdev,
+                                char *buf, u32 *length)
+{
+       u32 len = *length;
+       u32 buf_len = ATH10K_FW_STATS_BUF_SIZE;
+
+       len += scnprintf(buf + len, buf_len - len, "\n%30s\n",
+                        "ath10k PDEV TX stats");
+       len += scnprintf(buf + len, buf_len - len, "%30s\n\n",
+                                "=================");
+
+       len += scnprintf(buf + len, buf_len - len, "%30s %10d\n",
+                        "HTT cookies queued", pdev->comp_queued);
+       len += scnprintf(buf + len, buf_len - len, "%30s %10d\n",
+                        "HTT cookies disp.", pdev->comp_delivered);
+       len += scnprintf(buf + len, buf_len - len, "%30s %10d\n",
+                        "MSDU queued", pdev->msdu_enqued);
+       len += scnprintf(buf + len, buf_len - len, "%30s %10d\n",
+                        "MPDU queued", pdev->mpdu_enqued);
+       len += scnprintf(buf + len, buf_len - len, "%30s %10d\n",
+                        "MSDUs dropped", pdev->wmm_drop);
+       len += scnprintf(buf + len, buf_len - len, "%30s %10d\n",
+                        "Local enqued", pdev->local_enqued);
+       len += scnprintf(buf + len, buf_len - len, "%30s %10d\n",
+                        "Local freed", pdev->local_freed);
+       len += scnprintf(buf + len, buf_len - len, "%30s %10d\n",
+                        "HW queued", pdev->hw_queued);
+       len += scnprintf(buf + len, buf_len - len, "%30s %10d\n",
+                        "PPDUs reaped", pdev->hw_reaped);
+       len += scnprintf(buf + len, buf_len - len, "%30s %10d\n",
+                        "Num underruns", pdev->underrun);
+       len += scnprintf(buf + len, buf_len - len, "%30s %10d\n",
+                        "PPDUs cleaned", pdev->tx_abort);
+       len += scnprintf(buf + len, buf_len - len, "%30s %10d\n",
+                        "MPDUs requed", pdev->mpdus_requed);
+       len += scnprintf(buf + len, buf_len - len, "%30s %10d\n",
+                        "Excessive retries", pdev->tx_ko);
+       len += scnprintf(buf + len, buf_len - len, "%30s %10d\n",
+                        "HW rate", pdev->data_rc);
+       len += scnprintf(buf + len, buf_len - len, "%30s %10d\n",
+                        "Sched self tiggers", pdev->self_triggers);
+       len += scnprintf(buf + len, buf_len - len, "%30s %10d\n",
+                        "Dropped due to SW retries",
+                        pdev->sw_retry_failure);
+       len += scnprintf(buf + len, buf_len - len, "%30s %10d\n",
+                        "Illegal rate phy errors",
+                        pdev->illgl_rate_phy_err);
+       len += scnprintf(buf + len, buf_len - len, "%30s %10d\n",
+                        "Pdev continuous xretry", pdev->pdev_cont_xretry);
+       len += scnprintf(buf + len, buf_len - len, "%30s %10d\n",
+                        "TX timeout", pdev->pdev_tx_timeout);
+       len += scnprintf(buf + len, buf_len - len, "%30s %10d\n",
+                        "PDEV resets", pdev->pdev_resets);
+       len += scnprintf(buf + len, buf_len - len, "%30s %10d\n",
+                        "PHY underrun", pdev->phy_underrun);
+       len += scnprintf(buf + len, buf_len - len, "%30s %10d\n",
+                        "MPDU is more than txop limit", pdev->txop_ovf);
+       *length = len;
+}
+
+static void
+ath10k_wmi_fw_pdev_rx_stats_fill(const struct ath10k_fw_stats_pdev *pdev,
+                                char *buf, u32 *length)
+{
+       u32 len = *length;
+       u32 buf_len = ATH10K_FW_STATS_BUF_SIZE;
+
+       len += scnprintf(buf + len, buf_len - len, "\n%30s\n",
+                        "ath10k PDEV RX stats");
+       len += scnprintf(buf + len, buf_len - len, "%30s\n\n",
+                                "=================");
+
+       len += scnprintf(buf + len, buf_len - len, "%30s %10d\n",
+                        "Mid PPDU route change",
+                        pdev->mid_ppdu_route_change);
+       len += scnprintf(buf + len, buf_len - len, "%30s %10d\n",
+                        "Tot. number of statuses", pdev->status_rcvd);
+       len += scnprintf(buf + len, buf_len - len, "%30s %10d\n",
+                        "Extra frags on rings 0", pdev->r0_frags);
+       len += scnprintf(buf + len, buf_len - len, "%30s %10d\n",
+                        "Extra frags on rings 1", pdev->r1_frags);
+       len += scnprintf(buf + len, buf_len - len, "%30s %10d\n",
+                        "Extra frags on rings 2", pdev->r2_frags);
+       len += scnprintf(buf + len, buf_len - len, "%30s %10d\n",
+                        "Extra frags on rings 3", pdev->r3_frags);
+       len += scnprintf(buf + len, buf_len - len, "%30s %10d\n",
+                        "MSDUs delivered to HTT", pdev->htt_msdus);
+       len += scnprintf(buf + len, buf_len - len, "%30s %10d\n",
+                        "MPDUs delivered to HTT", pdev->htt_mpdus);
+       len += scnprintf(buf + len, buf_len - len, "%30s %10d\n",
+                        "MSDUs delivered to stack", pdev->loc_msdus);
+       len += scnprintf(buf + len, buf_len - len, "%30s %10d\n",
+                        "MPDUs delivered to stack", pdev->loc_mpdus);
+       len += scnprintf(buf + len, buf_len - len, "%30s %10d\n",
+                        "Oversized AMSUs", pdev->oversize_amsdu);
+       len += scnprintf(buf + len, buf_len - len, "%30s %10d\n",
+                        "PHY errors", pdev->phy_errs);
+       len += scnprintf(buf + len, buf_len - len, "%30s %10d\n",
+                        "PHY errors drops", pdev->phy_err_drop);
+       len += scnprintf(buf + len, buf_len - len, "%30s %10d\n",
+                        "MPDU errors (FCS, MIC, ENC)", pdev->mpdu_errs);
+       *length = len;
+}
+
+static void
+ath10k_wmi_fw_vdev_stats_fill(const struct ath10k_fw_stats_vdev *vdev,
+                             char *buf, u32 *length)
+{
+       u32 len = *length;
+       u32 buf_len = ATH10K_FW_STATS_BUF_SIZE;
+       int i;
+
+       len += scnprintf(buf + len, buf_len - len, "%30s %u\n",
+                       "vdev id", vdev->vdev_id);
+       len += scnprintf(buf + len, buf_len - len, "%30s %u\n",
+                       "beacon snr", vdev->beacon_snr);
+       len += scnprintf(buf + len, buf_len - len, "%30s %u\n",
+                       "data snr", vdev->data_snr);
+       len += scnprintf(buf + len, buf_len - len, "%30s %u\n",
+                       "num rx frames", vdev->num_rx_frames);
+       len += scnprintf(buf + len, buf_len - len, "%30s %u\n",
+                       "num rts fail", vdev->num_rts_fail);
+       len += scnprintf(buf + len, buf_len - len, "%30s %u\n",
+                       "num rts success", vdev->num_rts_success);
+       len += scnprintf(buf + len, buf_len - len, "%30s %u\n",
+                       "num rx err", vdev->num_rx_err);
+       len += scnprintf(buf + len, buf_len - len, "%30s %u\n",
+                       "num rx discard", vdev->num_rx_discard);
+       len += scnprintf(buf + len, buf_len - len, "%30s %u\n",
+                       "num tx not acked", vdev->num_tx_not_acked);
+
+       for (i = 0 ; i < ARRAY_SIZE(vdev->num_tx_frames); i++)
+               len += scnprintf(buf + len, buf_len - len,
+                               "%25s [%02d] %u\n",
+                               "num tx frames", i,
+                               vdev->num_tx_frames[i]);
+
+       for (i = 0 ; i < ARRAY_SIZE(vdev->num_tx_frames_retries); i++)
+               len += scnprintf(buf + len, buf_len - len,
+                               "%25s [%02d] %u\n",
+                               "num tx frames retries", i,
+                               vdev->num_tx_frames_retries[i]);
+
+       for (i = 0 ; i < ARRAY_SIZE(vdev->num_tx_frames_failures); i++)
+               len += scnprintf(buf + len, buf_len - len,
+                               "%25s [%02d] %u\n",
+                               "num tx frames failures", i,
+                               vdev->num_tx_frames_failures[i]);
+
+       for (i = 0 ; i < ARRAY_SIZE(vdev->tx_rate_history); i++)
+               len += scnprintf(buf + len, buf_len - len,
+                               "%25s [%02d] 0x%08x\n",
+                               "tx rate history", i,
+                               vdev->tx_rate_history[i]);
+
+       for (i = 0 ; i < ARRAY_SIZE(vdev->beacon_rssi_history); i++)
+               len += scnprintf(buf + len, buf_len - len,
+                               "%25s [%02d] %u\n",
+                               "beacon rssi history", i,
+                               vdev->beacon_rssi_history[i]);
+
+       len += scnprintf(buf + len, buf_len - len, "\n");
+       *length = len;
+}
+
+static void
+ath10k_wmi_fw_peer_stats_fill(const struct ath10k_fw_stats_peer *peer,
+                             char *buf, u32 *length)
+{
+       u32 len = *length;
+       u32 buf_len = ATH10K_FW_STATS_BUF_SIZE;
+
+       len += scnprintf(buf + len, buf_len - len, "%30s %pM\n",
+                       "Peer MAC address", peer->peer_macaddr);
+       len += scnprintf(buf + len, buf_len - len, "%30s %u\n",
+                       "Peer RSSI", peer->peer_rssi);
+       len += scnprintf(buf + len, buf_len - len, "%30s %u\n",
+                       "Peer TX rate", peer->peer_tx_rate);
+       len += scnprintf(buf + len, buf_len - len, "%30s %u\n",
+                       "Peer RX rate", peer->peer_rx_rate);
+       len += scnprintf(buf + len, buf_len - len, "\n");
+       *length = len;
+}
+
+void ath10k_wmi_main_op_fw_stats_fill(struct ath10k *ar,
+                                     struct ath10k_fw_stats *fw_stats,
+                                     char *buf)
+{
+       u32 len = 0;
+       u32 buf_len = ATH10K_FW_STATS_BUF_SIZE;
+       const struct ath10k_fw_stats_pdev *pdev;
+       const struct ath10k_fw_stats_vdev *vdev;
+       const struct ath10k_fw_stats_peer *peer;
+       size_t num_peers;
+       size_t num_vdevs;
+
+       spin_lock_bh(&ar->data_lock);
+
+       pdev = list_first_entry_or_null(&fw_stats->pdevs,
+                                       struct ath10k_fw_stats_pdev, list);
+       if (!pdev) {
+               ath10k_warn(ar, "failed to get pdev stats\n");
+               goto unlock;
+       }
+
+       num_peers = ath10k_wmi_fw_stats_num_peers(&fw_stats->peers);
+       num_vdevs = ath10k_wmi_fw_stats_num_vdevs(&fw_stats->vdevs);
+
+       ath10k_wmi_fw_pdev_base_stats_fill(pdev, buf, &len);
+       ath10k_wmi_fw_pdev_tx_stats_fill(pdev, buf, &len);
+       ath10k_wmi_fw_pdev_rx_stats_fill(pdev, buf, &len);
+
+       len += scnprintf(buf + len, buf_len - len, "\n");
+       len += scnprintf(buf + len, buf_len - len, "%30s (%zu)\n",
+                        "ath10k VDEV stats", num_vdevs);
+       len += scnprintf(buf + len, buf_len - len, "%30s\n\n",
+                                "=================");
+
+       list_for_each_entry(vdev, &fw_stats->vdevs, list) {
+               ath10k_wmi_fw_vdev_stats_fill(vdev, buf, &len);
+       }
+
+       len += scnprintf(buf + len, buf_len - len, "\n");
+       len += scnprintf(buf + len, buf_len - len, "%30s (%zu)\n",
+                        "ath10k PEER stats", num_peers);
+       len += scnprintf(buf + len, buf_len - len, "%30s\n\n",
+                                "=================");
+
+       list_for_each_entry(peer, &fw_stats->peers, list) {
+               ath10k_wmi_fw_peer_stats_fill(peer, buf, &len);
+       }
+
+unlock:
+       spin_unlock_bh(&ar->data_lock);
+
+       if (len >= buf_len)
+               buf[len - 1] = 0;
+       else
+               buf[len] = 0;
+}
+
+void ath10k_wmi_10x_op_fw_stats_fill(struct ath10k *ar,
+                                    struct ath10k_fw_stats *fw_stats,
+                                    char *buf)
+{
+       unsigned int len = 0;
+       unsigned int buf_len = ATH10K_FW_STATS_BUF_SIZE;
+       const struct ath10k_fw_stats_pdev *pdev;
+       const struct ath10k_fw_stats_vdev *vdev;
+       const struct ath10k_fw_stats_peer *peer;
+       size_t num_peers;
+       size_t num_vdevs;
+
+       spin_lock_bh(&ar->data_lock);
+
+       pdev = list_first_entry_or_null(&fw_stats->pdevs,
+                                       struct ath10k_fw_stats_pdev, list);
+       if (!pdev) {
+               ath10k_warn(ar, "failed to get pdev stats\n");
+               goto unlock;
+       }
+
+       num_peers = ath10k_wmi_fw_stats_num_peers(&fw_stats->peers);
+       num_vdevs = ath10k_wmi_fw_stats_num_vdevs(&fw_stats->vdevs);
+
+       ath10k_wmi_fw_pdev_base_stats_fill(pdev, buf, &len);
+       ath10k_wmi_fw_pdev_extra_stats_fill(pdev, buf, &len);
+       ath10k_wmi_fw_pdev_tx_stats_fill(pdev, buf, &len);
+       ath10k_wmi_fw_pdev_rx_stats_fill(pdev, buf, &len);
+
+       len += scnprintf(buf + len, buf_len - len, "\n");
+       len += scnprintf(buf + len, buf_len - len, "%30s (%zu)\n",
+                        "ath10k VDEV stats", num_vdevs);
+       len += scnprintf(buf + len, buf_len - len, "%30s\n\n",
+                                "=================");
+
+       list_for_each_entry(vdev, &fw_stats->vdevs, list) {
+               ath10k_wmi_fw_vdev_stats_fill(vdev, buf, &len);
+       }
+
+       len += scnprintf(buf + len, buf_len - len, "\n");
+       len += scnprintf(buf + len, buf_len - len, "%30s (%zu)\n",
+                        "ath10k PEER stats", num_peers);
+       len += scnprintf(buf + len, buf_len - len, "%30s\n\n",
+                                "=================");
+
+       list_for_each_entry(peer, &fw_stats->peers, list) {
+               ath10k_wmi_fw_peer_stats_fill(peer, buf, &len);
+       }
+
+unlock:
+       spin_unlock_bh(&ar->data_lock);
+
+       if (len >= buf_len)
+               buf[len - 1] = 0;
+       else
+               buf[len] = 0;
+}
+
+static struct sk_buff *
+ath10k_wmi_op_gen_pdev_enable_adaptive_cca(struct ath10k *ar, u8 enable,
+                                          u32 detect_level, u32 detect_margin)
+{
+       struct wmi_pdev_set_adaptive_cca_params *cmd;
+       struct sk_buff *skb;
+
+       skb = ath10k_wmi_alloc_skb(ar, sizeof(*cmd));
+       if (!skb)
+               return ERR_PTR(-ENOMEM);
+
+       cmd = (struct wmi_pdev_set_adaptive_cca_params *)skb->data;
+       cmd->enable = __cpu_to_le32(enable);
+       cmd->cca_detect_level = __cpu_to_le32(detect_level);
+       cmd->cca_detect_margin = __cpu_to_le32(detect_margin);
+
+       ath10k_dbg(ar, ATH10K_DBG_WMI,
+                  "wmi pdev set adaptive cca params enable:%d detection level:%d detection margin:%d\n",
+                  enable, detect_level, detect_margin);
+       return skb;
+}
+
+void ath10k_wmi_10_4_op_fw_stats_fill(struct ath10k *ar,
+                                     struct ath10k_fw_stats *fw_stats,
+                                     char *buf)
+{
+       u32 len = 0;
+       u32 buf_len = ATH10K_FW_STATS_BUF_SIZE;
+       const struct ath10k_fw_stats_pdev *pdev;
+       const struct ath10k_fw_stats_vdev *vdev;
+       const struct ath10k_fw_stats_peer *peer;
+       size_t num_peers;
+       size_t num_vdevs;
+
+       spin_lock_bh(&ar->data_lock);
+
+       pdev = list_first_entry_or_null(&fw_stats->pdevs,
+                                       struct ath10k_fw_stats_pdev, list);
+       if (!pdev) {
+               ath10k_warn(ar, "failed to get pdev stats\n");
+               goto unlock;
+       }
+
+       num_peers = ath10k_wmi_fw_stats_num_peers(&fw_stats->peers);
+       num_vdevs = ath10k_wmi_fw_stats_num_vdevs(&fw_stats->vdevs);
+
+       ath10k_wmi_fw_pdev_base_stats_fill(pdev, buf, &len);
+       ath10k_wmi_fw_pdev_extra_stats_fill(pdev, buf, &len);
+       ath10k_wmi_fw_pdev_tx_stats_fill(pdev, buf, &len);
+
+       len += scnprintf(buf + len, buf_len - len, "%30s %10d\n",
+                       "HW paused", pdev->hw_paused);
+       len += scnprintf(buf + len, buf_len - len, "%30s %10d\n",
+                       "Seqs posted", pdev->seq_posted);
+       len += scnprintf(buf + len, buf_len - len, "%30s %10d\n",
+                       "Seqs failed queueing", pdev->seq_failed_queueing);
+       len += scnprintf(buf + len, buf_len - len, "%30s %10d\n",
+                       "Seqs completed", pdev->seq_completed);
+       len += scnprintf(buf + len, buf_len - len, "%30s %10d\n",
+                       "Seqs restarted", pdev->seq_restarted);
+       len += scnprintf(buf + len, buf_len - len, "%30s %10d\n",
+                       "MU Seqs posted", pdev->mu_seq_posted);
+       len += scnprintf(buf + len, buf_len - len, "%30s %10d\n",
+                       "MPDUs SW flushed", pdev->mpdus_sw_flush);
+       len += scnprintf(buf + len, buf_len - len, "%30s %10d\n",
+                       "MPDUs HW filtered", pdev->mpdus_hw_filter);
+       len += scnprintf(buf + len, buf_len - len, "%30s %10d\n",
+                       "MPDUs truncated", pdev->mpdus_truncated);
+       len += scnprintf(buf + len, buf_len - len, "%30s %10d\n",
+                       "MPDUs receive no ACK", pdev->mpdus_ack_failed);
+       len += scnprintf(buf + len, buf_len - len, "%30s %10d\n",
+                       "MPDUs expired", pdev->mpdus_expired);
+
+       ath10k_wmi_fw_pdev_rx_stats_fill(pdev, buf, &len);
+       len += scnprintf(buf + len, buf_len - len, "%30s %10d\n",
+                       "Num Rx Overflow errors", pdev->rx_ovfl_errs);
+
+       len += scnprintf(buf + len, buf_len - len, "\n");
+       len += scnprintf(buf + len, buf_len - len, "%30s (%zu)\n",
+                       "ath10k VDEV stats", num_vdevs);
+       len += scnprintf(buf + len, buf_len - len, "%30s\n\n",
+                               "=================");
+
+       list_for_each_entry(vdev, &fw_stats->vdevs, list) {
+               ath10k_wmi_fw_vdev_stats_fill(vdev, buf, &len);
+       }
+
+       len += scnprintf(buf + len, buf_len - len, "\n");
+       len += scnprintf(buf + len, buf_len - len, "%30s (%zu)\n",
+                       "ath10k PEER stats", num_peers);
+       len += scnprintf(buf + len, buf_len - len, "%30s\n\n",
+                               "=================");
+
+       list_for_each_entry(peer, &fw_stats->peers, list) {
+               ath10k_wmi_fw_peer_stats_fill(peer, buf, &len);
+       }
+
+unlock:
+       spin_unlock_bh(&ar->data_lock);
+
+       if (len >= buf_len)
+               buf[len - 1] = 0;
+       else
+               buf[len] = 0;
+}
+
 static const struct wmi_ops wmi_ops = {
        .rx = ath10k_wmi_op_rx,
        .map_svc = wmi_main_svc_map,
@@ -6353,10 +7290,12 @@ static const struct wmi_ops wmi_ops = {
        .gen_addba_send = ath10k_wmi_op_gen_addba_send,
        .gen_addba_set_resp = ath10k_wmi_op_gen_addba_set_resp,
        .gen_delba_send = ath10k_wmi_op_gen_delba_send,
+       .fw_stats_fill = ath10k_wmi_main_op_fw_stats_fill,
        /* .gen_bcn_tmpl not implemented */
        /* .gen_prb_tmpl not implemented */
        /* .gen_p2p_go_bcn_ie not implemented */
        /* .gen_adaptive_qcs not implemented */
+       /* .gen_pdev_enable_adaptive_cca not implemented */
 };
 
 static const struct wmi_ops wmi_10_1_ops = {
@@ -6418,10 +7357,12 @@ static const struct wmi_ops wmi_10_1_ops = {
        .gen_addba_send = ath10k_wmi_op_gen_addba_send,
        .gen_addba_set_resp = ath10k_wmi_op_gen_addba_set_resp,
        .gen_delba_send = ath10k_wmi_op_gen_delba_send,
+       .fw_stats_fill = ath10k_wmi_10x_op_fw_stats_fill,
        /* .gen_bcn_tmpl not implemented */
        /* .gen_prb_tmpl not implemented */
        /* .gen_p2p_go_bcn_ie not implemented */
        /* .gen_adaptive_qcs not implemented */
+       /* .gen_pdev_enable_adaptive_cca not implemented */
 };
 
 static const struct wmi_ops wmi_10_2_ops = {
@@ -6484,6 +7425,8 @@ static const struct wmi_ops wmi_10_2_ops = {
        .gen_addba_send = ath10k_wmi_op_gen_addba_send,
        .gen_addba_set_resp = ath10k_wmi_op_gen_addba_set_resp,
        .gen_delba_send = ath10k_wmi_op_gen_delba_send,
+       .fw_stats_fill = ath10k_wmi_10x_op_fw_stats_fill,
+       /* .gen_pdev_enable_adaptive_cca not implemented */
 };
 
 static const struct wmi_ops wmi_10_2_4_ops = {
@@ -6545,6 +7488,10 @@ static const struct wmi_ops wmi_10_2_4_ops = {
        .gen_addba_send = ath10k_wmi_op_gen_addba_send,
        .gen_addba_set_resp = ath10k_wmi_op_gen_addba_set_resp,
        .gen_delba_send = ath10k_wmi_op_gen_delba_send,
+       .gen_pdev_get_tpc_config = ath10k_wmi_10_2_4_op_gen_pdev_get_tpc_config,
+       .fw_stats_fill = ath10k_wmi_10x_op_fw_stats_fill,
+       .gen_pdev_enable_adaptive_cca =
+               ath10k_wmi_op_gen_pdev_enable_adaptive_cca,
        /* .gen_bcn_tmpl not implemented */
        /* .gen_prb_tmpl not implemented */
        /* .gen_p2p_go_bcn_ie not implemented */
@@ -6555,6 +7502,7 @@ static const struct wmi_ops wmi_10_4_ops = {
        .rx = ath10k_wmi_10_4_op_rx,
        .map_svc = wmi_10_4_svc_map,
 
+       .pull_fw_stats = ath10k_wmi_10_4_op_pull_fw_stats,
        .pull_scan = ath10k_wmi_op_pull_scan_ev,
        .pull_mgmt_rx = ath10k_wmi_10_4_op_pull_mgmt_rx_ev,
        .pull_ch_info = ath10k_wmi_10_4_op_pull_ch_info_ev,
@@ -6604,9 +7552,11 @@ static const struct wmi_ops wmi_10_4_ops = {
        .gen_addba_send = ath10k_wmi_op_gen_addba_send,
        .gen_addba_set_resp = ath10k_wmi_op_gen_addba_set_resp,
        .gen_delba_send = ath10k_wmi_op_gen_delba_send,
+       .fw_stats_fill = ath10k_wmi_10_4_op_fw_stats_fill,
 
        /* shared with 10.2 */
        .gen_peer_assoc = ath10k_wmi_10_2_op_gen_peer_assoc,
+       .gen_request_stats = ath10k_wmi_op_gen_request_stats,
 };
 
 int ath10k_wmi_attach(struct ath10k *ar)
@@ -6660,15 +7610,10 @@ int ath10k_wmi_attach(struct ath10k *ar)
        return 0;
 }
 
-void ath10k_wmi_detach(struct ath10k *ar)
+void ath10k_wmi_free_host_mem(struct ath10k *ar)
 {
        int i;
 
-       cancel_work_sync(&ar->svc_rdy_work);
-
-       if (ar->svc_rdy_skb)
-               dev_kfree_skb(ar->svc_rdy_skb);
-
        /* free the host memory chunks requested by firmware */
        for (i = 0; i < ar->wmi.num_mem_chunks; i++) {
                dma_free_coherent(ar->dev,
@@ -6679,3 +7624,11 @@ void ath10k_wmi_detach(struct ath10k *ar)
 
        ar->wmi.num_mem_chunks = 0;
 }
+
+void ath10k_wmi_detach(struct ath10k *ar)
+{
+       cancel_work_sync(&ar->svc_rdy_work);
+
+       if (ar->svc_rdy_skb)
+               dev_kfree_skb(ar->svc_rdy_skb);
+}