#include <net/mac80211.h>
 #include <linux/etherdevice.h>
 #include <linux/acpi.h>
+#include <linux/of.h>
 
 #include "hif.h"
 #include "core.h"
        return ret;
 }
 
+static int __ath10k_fetch_bb_timing_dt(struct ath10k *ar,
+                                      struct wmi_bb_timing_cfg_arg *bb_timing)
+{
+       struct device_node *node;
+       const char *fem_name;
+       int ret;
+
+       node = ar->dev->of_node;
+       if (!node)
+               return -ENOENT;
+
+       ret = of_property_read_string_index(node, "ext-fem-name", 0, &fem_name);
+       if (ret)
+               return -ENOENT;
+
+       /*
+        * If external Front End module used in hardware, then default base band timing
+        * parameter cannot be used since they were fine tuned for reference hardware,
+        * so choosing different value suitable for that external FEM.
+        */
+       if (!strcmp("microsemi-lx5586", fem_name)) {
+               bb_timing->bb_tx_timing = 0x00;
+               bb_timing->bb_xpa_timing = 0x0101;
+       } else {
+               return -ENOENT;
+       }
+
+       ath10k_dbg(ar, ATH10K_DBG_BOOT, "boot bb_tx_timing 0x%x bb_xpa_timing 0x%x\n",
+                  bb_timing->bb_tx_timing, bb_timing->bb_xpa_timing);
+       return 0;
+}
+
 static int ath10k_start(struct ieee80211_hw *hw)
 {
        struct ath10k *ar = hw->priv;
        u32 param;
        int ret = 0;
+       struct wmi_bb_timing_cfg_arg bb_timing = {0};
 
        /*
         * This makes sense only when restarting hw. It is harmless to call
                clear_bit(ATH10K_FLAG_BTCOEX, &ar->dev_flags);
        }
 
+       if (test_bit(WMI_SERVICE_BB_TIMING_CONFIG_SUPPORT, ar->wmi.svc_map)) {
+               ret = __ath10k_fetch_bb_timing_dt(ar, &bb_timing);
+               if (!ret) {
+                       ret = ath10k_wmi_pdev_bb_timing(ar, &bb_timing);
+                       if (ret) {
+                               ath10k_warn(ar,
+                                           "failed to set bb timings: %d\n",
+                                           ret);
+                               goto err_core_stop;
+                       }
+               }
+       }
+
        ar->num_started_vdevs = 0;
        ath10k_regd_update(ar);
 
 
        struct sk_buff *(*gen_echo)(struct ath10k *ar, u32 value);
        struct sk_buff *(*gen_pdev_get_tpc_table_cmdid)(struct ath10k *ar,
                                                        u32 param);
+       struct sk_buff *(*gen_bb_timing)
+                       (struct ath10k *ar,
+                        const struct wmi_bb_timing_cfg_arg *arg);
 
 };
 
                                   ar->wmi.cmd->radar_found_cmdid);
 }
 
+static inline int
+ath10k_wmi_pdev_bb_timing(struct ath10k *ar,
+                         const struct wmi_bb_timing_cfg_arg *arg)
+{
+       struct sk_buff *skb;
+
+       if (!ar->wmi.ops->gen_bb_timing)
+               return -EOPNOTSUPP;
+
+       skb = ar->wmi.ops->gen_bb_timing(ar, arg);
+
+       if (IS_ERR(skb))
+               return PTR_ERR(skb);
+
+       return ath10k_wmi_cmd_send(ar, skb,
+                                  ar->wmi.cmd->set_bb_timing_cmdid);
+}
 #endif
 
                WMI_10_2_PDEV_BSS_CHAN_INFO_REQUEST_CMDID,
        .pdev_get_tpc_table_cmdid = WMI_CMD_UNSUPPORTED,
        .radar_found_cmdid = WMI_CMD_UNSUPPORTED,
+       .set_bb_timing_cmdid = WMI_10_2_PDEV_SET_BB_TIMING_CONFIG_CMDID,
 };
 
 /* 10.4 WMI cmd track */
        return 0;
 }
 
+static struct sk_buff *
+ath10k_wmi_10_2_4_op_gen_bb_timing(struct ath10k *ar,
+                                  const struct wmi_bb_timing_cfg_arg *arg)
+{
+       struct wmi_pdev_bb_timing_cfg_cmd *cmd;
+       struct sk_buff *skb;
+
+       skb = ath10k_wmi_alloc_skb(ar, sizeof(*cmd));
+       if (!skb)
+               return ERR_PTR(-ENOMEM);
+
+       cmd = (struct wmi_pdev_bb_timing_cfg_cmd *)skb->data;
+       cmd->bb_tx_timing = __cpu_to_le32(arg->bb_tx_timing);
+       cmd->bb_xpa_timing = __cpu_to_le32(arg->bb_xpa_timing);
+
+       ath10k_dbg(ar, ATH10K_DBG_WMI,
+                  "wmi pdev bb_tx_timing 0x%x bb_xpa_timing 0x%x\n",
+                  arg->bb_tx_timing, arg->bb_xpa_timing);
+       return skb;
+}
+
 static const struct wmi_ops wmi_ops = {
        .rx = ath10k_wmi_op_rx,
        .map_svc = wmi_main_svc_map,
        .gen_pdev_enable_adaptive_cca =
                ath10k_wmi_op_gen_pdev_enable_adaptive_cca,
        .get_vdev_subtype = ath10k_wmi_10_2_4_op_get_vdev_subtype,
+       .gen_bb_timing = ath10k_wmi_10_2_4_op_gen_bb_timing,
        /* .gen_bcn_tmpl not implemented */
        /* .gen_prb_tmpl not implemented */
        /* .gen_p2p_go_bcn_ie not implemented */
 
        WMI_SERVICE_TX_DATA_ACK_RSSI,
        WMI_SERVICE_VDEV_DIFFERENT_BEACON_INTERVAL_SUPPORT,
        WMI_SERVICE_VDEV_DISABLE_4_ADDR_SRC_LRN_SUPPORT,
+       WMI_SERVICE_BB_TIMING_CONFIG_SUPPORT,
 
        /* keep last */
        WMI_SERVICE_MAX,
        WMI_10X_SERVICE_PEER_STATS,
        WMI_10X_SERVICE_RESET_CHIP,
        WMI_10X_SERVICE_HTT_MGMT_TX_COMP_VALID_FLAGS,
+       WMI_10X_SERVICE_VDEV_BCN_RATE_CONTROL,
+       WMI_10X_SERVICE_PER_PACKET_SW_ENCRYPT,
+       WMI_10X_SERVICE_BB_TIMING_CONFIG_SUPPORT,
 };
 
 enum wmi_main_service {
               WMI_SERVICE_RESET_CHIP, len);
        SVCMAP(WMI_10X_SERVICE_HTT_MGMT_TX_COMP_VALID_FLAGS,
               WMI_SERVICE_HTT_MGMT_TX_COMP_VALID_FLAGS, len);
+       SVCMAP(WMI_10X_SERVICE_BB_TIMING_CONFIG_SUPPORT,
+              WMI_SERVICE_BB_TIMING_CONFIG_SUPPORT, len);
 }
 
 static inline void wmi_main_svc_map(const __le32 *in, unsigned long *out,
        u32 pdev_wds_entry_list_cmdid;
        u32 tdls_set_offchan_mode_cmdid;
        u32 radar_found_cmdid;
+       u32 set_bb_timing_cmdid;
 };
 
 /*
        WMI_10_2_SET_LTEU_CONFIG_CMDID,
        WMI_10_2_SET_CCA_PARAMS,
        WMI_10_2_PDEV_BSS_CHAN_INFO_REQUEST_CMDID,
+       WMI_10_2_FWTEST_CMDID,
+       WMI_10_2_PDEV_SET_BB_TIMING_CONFIG_CMDID,
        WMI_10_2_PDEV_UTF_CMDID = WMI_10_2_END_CMDID - 1,
 };
 
        __le32 reserved;
 } __packed;
 
+/* bb timing register configurations */
+struct wmi_bb_timing_cfg_arg {
+       /* Tx_end to pa off timing */
+       u32 bb_tx_timing;
+
+       /* Tx_end to external pa off timing */
+       u32 bb_xpa_timing;
+};
+
+struct wmi_pdev_bb_timing_cfg_cmd {
+       /* Tx_end to pa off timing */
+       __le32 bb_tx_timing;
+
+       /* Tx_end to external pa off timing */
+       __le32 bb_xpa_timing;
+} __packed;
+
 struct ath10k;
 struct ath10k_vif;
 struct ath10k_fw_stats_pdev;