--- a/drivers/net/wireless/ath/ath11k/core.h
+++ b/drivers/net/wireless/ath/ath11k/core.h
@@ -24,6 +24,7 @@
 #include "thermal.h"
 #include "dbring.h"
 #include "spectral.h"
+#include "vendor.h"
 
 #define SM(_v, _f) (((_v) << _f##_LSB) & _f##_MASK)
 
@@ -462,6 +463,11 @@ struct ath11k_coex_info {
 	u32 pta_priority;
 };
 
+enum ath11k_ap_ps_state {
+	ATH11K_AP_PS_STATE_OFF,
+	ATH11K_AP_PS_STATE_ON,
+};
+
 struct ath11k {
 	struct ath11k_base *ab;
 	struct ath11k_pdev *pdev;
@@ -591,6 +597,8 @@ struct ath11k {
 	bool dfs_block_radar_events;
 	struct ath11k_thermal thermal;
 	struct completion fw_mode_reset;
+	int ap_ps_enabled;
+	enum ath11k_ap_ps_state ap_ps_state;
 };
 
 struct ath11k_band_cap {
--- a/drivers/net/wireless/ath/ath11k/mac.c
+++ b/drivers/net/wireless/ath/ath11k/mac.c
@@ -3111,6 +3111,33 @@ static void ath11k_mac_dec_num_stations(
 	ar->num_stations--;
 }
 
+int ath11k_mac_ap_ps_recalc(struct ath11k *ar)
+{
+	struct ath11k_vif *arvif;
+	bool has_sta_iface = false;
+	enum ath11k_ap_ps_state state = ATH11K_AP_PS_STATE_OFF;
+	int ret = 0;
+
+	list_for_each_entry(arvif, &ar->arvifs, list) {
+		if (arvif->vdev_type == WMI_VDEV_TYPE_STA) {
+			has_sta_iface = true;
+			break;
+		}
+	}
+
+	if (!has_sta_iface && !ar->num_stations && ar->ap_ps_enabled)
+		state = ATH11K_AP_PS_STATE_ON;
+
+	if (ar->ap_ps_state == state)
+		return ret;
+
+	ret = ath11k_wmi_pdev_ap_ps_cmd_send(ar, ar->pdev->pdev_id, state);
+	if (!ret)
+		ar->ap_ps_state = state;
+
+	return ret;
+}
+
 static int ath11k_mac_station_add(struct ath11k *ar,
 				  struct ieee80211_vif *vif,
 				  struct ieee80211_sta *sta)
@@ -3150,6 +3177,12 @@ static int ath11k_mac_station_add(struct
 	ath11k_dbg(ab, ATH11K_DBG_MAC, "Added peer: %pM for VDEV: %d\n",
 		   sta->addr, arvif->vdev_id);
 
+	ret = ath11k_mac_ap_ps_recalc(ar);
+	if (ret) {
+		ath11k_warn(ar->ab, "failed to send ap ps ret %d\n", ret);
+		goto exit;
+	}
+
 	if (ath11k_debugfs_is_extd_tx_stats_enabled(ar)) {
 		arsta->tx_stats = kzalloc(sizeof(*arsta->tx_stats), GFP_KERNEL);
 		if (!arsta->tx_stats) {
@@ -3258,6 +3291,9 @@ static int ath11k_mac_op_sta_state(struc
 
 		kfree(arsta->tx_stats);
 		arsta->tx_stats = NULL;
+		ret = ath11k_mac_ap_ps_recalc(ar);
+		if (ret)
+			ath11k_warn(ar->ab, "failed to send ap ps ret %d\n", ret);
 
 		kfree(arsta->rx_stats);
 		arsta->rx_stats = NULL;
@@ -4446,6 +4482,7 @@ static void ath11k_mac_op_stop(struct ie
 
 	clear_bit(ATH11K_CAC_RUNNING, &ar->dev_flags);
 	ar->state = ATH11K_STATE_OFF;
+	ar->ap_ps_state = ATH11K_AP_PS_STATE_OFF;
 	mutex_unlock(&ar->conf_mutex);
 
 	cancel_delayed_work_sync(&ar->scan.timeout);
@@ -4677,7 +4714,6 @@ static int ath11k_mac_op_add_interface(s
 			    arvif->vdev_id, ret);
 		goto err;
 	}
-
 	ar->num_created_vdevs++;
 	ath11k_dbg(ab, ATH11K_DBG_MAC, "vdev %pM created, vdev_id %d\n",
 		   vif->addr, arvif->vdev_id);
@@ -4776,6 +4812,10 @@ static int ath11k_mac_op_add_interface(s
 
 	ath11k_dp_vdev_tx_attach(ar, arvif);
 
+	ret = ath11k_mac_ap_ps_recalc(ar);
+	if (ret)
+		ath11k_warn(ar->ab, "failed to set ap ps ret %d\n", ret);
+
 	mutex_unlock(&ar->conf_mutex);
 
 	return 0;
@@ -4890,6 +4930,7 @@ err_vdev_del:
 
 	/* Recalc txpower for remaining vdev */
 	ath11k_mac_txpower_recalc(ar);
+	ath11k_mac_ap_ps_recalc(ar);
 	clear_bit(ATH11K_FLAG_MONITOR_ENABLED, &ar->monitor_flags);
 
 	/* TODO: recal traffic pause state based on the available vdevs */
--- a/drivers/net/wireless/ath/ath11k/mac.h
+++ b/drivers/net/wireless/ath/ath11k/mac.h
@@ -124,6 +124,7 @@ struct ath11k_generic_iter {
 
 extern const struct htt_rx_ring_tlv_filter ath11k_mac_mon_status_filter_default;
 
+int ath11k_mac_ap_ps_recalc(struct ath11k *ar);
 void ath11k_mac_destroy(struct ath11k_base *ab);
 void ath11k_mac_unregister(struct ath11k_base *ab);
 int ath11k_mac_register(struct ath11k_base *ab);
--- a/drivers/net/wireless/ath/ath11k/vendor.c
+++ b/drivers/net/wireless/ath/ath11k/vendor.c
@@ -6,7 +6,6 @@
 #include <net/netlink.h>
 #include <net/mac80211.h>
 #include "core.h"
-#include "vendor.h"
 #include "debug.h"
 
 static const struct nla_policy
@@ -21,6 +20,11 @@ ath11k_vendor_wlan_prio_policy[QCA_WLAN_
 	[QCA_WLAN_VENDOR_ATTR_WLAN_PRIO_WEIGHT] = { .type = NLA_U8 },
 };
 
+static const struct nla_policy
+ath11k_vendor_set_wifi_config_policy[QCA_WLAN_VENDOR_ATTR_CONFIG_MAX + 1] = {
+	[QCA_WLAN_VENDOR_ATTR_CONFIG_GTX] = {.type = NLA_FLAG}
+};
+
 static int ath11k_vendor_btcoex_configure(struct wiphy *wiphy,
 					  struct wireless_dev *wdev,
 					  const void *data,
@@ -101,6 +105,51 @@ out:
 	return ret;
 }
 
+static int ath11k_vendor_set_wifi_config(struct wiphy *wihpy,
+					 struct wireless_dev *wdev,
+					 const void *data,
+					 int data_len)
+{
+	struct ieee80211_vif *vif;
+	struct ath11k_vif *arvif;
+	struct ath11k *ar;
+	struct nlattr *tb[QCA_WLAN_VENDOR_ATTR_CONFIG_MAX + 1];
+	int ret = 0;
+
+	if (!wdev)
+		return -EINVAL;
+
+	vif = wdev_to_ieee80211_vif(wdev);
+	if (!vif)
+		return -EINVAL;
+
+	arvif = (struct ath11k_vif*)vif->drv_priv;
+	if (!arvif)
+		return -EINVAL;
+
+	ar = arvif->ar;
+
+	mutex_lock(&ar->conf_mutex);
+
+	ret = nla_parse(tb, QCA_WLAN_VENDOR_ATTR_CONFIG_MAX, data, data_len,
+			ath11k_vendor_set_wifi_config_policy, NULL);
+	if (ret) {
+		ath11k_warn(ar->ab, "invalid set wifi config policy attribute\n");
+		goto exit;
+	}
+
+	ar->ap_ps_enabled = nla_get_flag(tb[QCA_WLAN_VENDOR_ATTR_CONFIG_GTX]);
+	ret = ath11k_mac_ap_ps_recalc(ar);
+	if (ret) {
+		ath11k_warn(ar->ab, "failed to send ap ps ret %d\n", ret);
+		goto exit;
+	}
+
+exit:
+	mutex_unlock(&ar->conf_mutex);
+	return ret;
+}
+
 static struct wiphy_vendor_command ath11k_vendor_commands[] = {
 	{
 		.info.vendor_id = QCA_NL80211_VENDOR_ID,
@@ -108,8 +157,18 @@ static struct wiphy_vendor_command ath11
 		.flags = WIPHY_VENDOR_CMD_NEED_WDEV |
 			WIPHY_VENDOR_CMD_NEED_RUNNING,
 		.doit = ath11k_vendor_btcoex_configure,
-		.policy = ath11k_vendor_btcoex_config_policy
-	}
+		.policy = ath11k_vendor_btcoex_config_policy,
+		.maxattr = QCA_WLAN_VENDOR_ATTR_BTCOEX_CONFIG_MAX
+	},
+	{
+		.info.vendor_id = QCA_NL80211_VENDOR_ID,
+		.info.subcmd = QCA_NL80211_VENDOR_SUBCMD_SET_WIFI_CONFIGURATION,
+		.flags = WIPHY_VENDOR_CMD_NEED_WDEV |
+			WIPHY_VENDOR_CMD_NEED_RUNNING,
+		.doit = ath11k_vendor_set_wifi_config,
+		.policy = ath11k_vendor_set_wifi_config_policy,
+		.maxattr = QCA_WLAN_VENDOR_ATTR_CONFIG_MAX
+	},
 };
 
 int ath11k_vendor_register(struct ath11k *ar)
--- a/drivers/net/wireless/ath/ath11k/vendor.h
+++ b/drivers/net/wireless/ath/ath11k/vendor.h
@@ -9,6 +9,9 @@
 #define QCA_NL80211_VENDOR_ID 0x001374
 
 enum qca_nl80211_vendor_subcmds {
+	/* Wi-Fi configuration subcommand */
+	QCA_NL80211_VENDOR_SUBCMD_SET_WIFI_CONFIGURATION = 74,
+
 	/* QCA_NL80211_VENDOR_SUBCMD_BTCOEX_CONFIG: This command is used to
 	 * enable/disable BTCOEX and set priority for different type of WLAN
 	 * traffic over BT low priority traffic. This uses attributes in
@@ -58,7 +61,17 @@ enum qca_wlan_vendor_attr_wlan_prio {
                QCA_WLAN_VENDOR_ATTR_WLAN_PRIO_LAST - 1,
 };
 
+/* Attributes for data used by
+ * QCA_NL80211_VENDOR_SUBCMD_SET_WIFI_CONFIGURATION
+ */
+enum qca_wlan_vendor_attr_config {
+	QCA_WLAN_VENDOR_ATTR_CONFIG_GTX = 57,
 
+	/* keep last */
+	QCA_WLAN_VENDOR_ATTR_CONFIG_AFTER_LAST,
+	QCA_WLAN_VENDOR_ATTR_CONFIG_MAX =
+		QCA_WLAN_VENDOR_ATTR_CONFIG_AFTER_LAST - 1,
+};
 
 /**
  * enum qca_wlan_vendor_attr_btcoex_config - Used by the vendor command
--- a/drivers/net/wireless/ath/ath11k/wmi.c
+++ b/drivers/net/wireless/ath/ath11k/wmi.c
@@ -1197,6 +1197,38 @@ ath11k_wmi_rx_reord_queue_remove(struct
 	return ret;
 }
 
+int ath11k_wmi_pdev_ap_ps_cmd_send(struct ath11k *ar, u8 pdev_id,
+				   u32 param_value)
+{
+	struct ath11k_pdev_wmi *wmi = ar->wmi;
+	struct wmi_pdev_ap_ps_cmd *cmd;
+	struct sk_buff *skb;
+	int ret;
+
+	skb = ath11k_wmi_alloc_skb(wmi->wmi_ab, sizeof(*cmd));
+	if (!skb)
+		return -ENOMEM;
+
+	cmd = (struct wmi_pdev_ap_ps_cmd *)skb->data;
+	cmd->tlv_header = FIELD_PREP(WMI_TLV_TAG,
+				     WMI_TAG_PDEV_GREEN_AP_PS_ENABLE_CMD) |
+			  FIELD_PREP(WMI_TLV_LEN, sizeof(*cmd) - TLV_HDR_SIZE);
+	cmd->pdev_id = pdev_id;
+	cmd->param_value = param_value;
+
+	ret = ath11k_wmi_cmd_send(wmi, skb, WMI_PDEV_GREEN_AP_PS_ENABLE_CMDID);
+	if (ret) {
+		ath11k_warn(ar->ab, "failed to send ap ps enable/disable cmd\n");
+		dev_kfree_skb(skb);
+	}
+
+	ath11k_dbg(ar->ab, ATH11K_DBG_WMI,
+		   "wmi pdev ap ps set pdev id %d value %d\n",
+		   pdev_id, param_value);
+
+	return ret;
+}
+
 int ath11k_wmi_pdev_set_param(struct ath11k *ar, u32 param_id,
 			      u32 param_value, u8 pdev_id)
 {
--- a/drivers/net/wireless/ath/ath11k/wmi.h
+++ b/drivers/net/wireless/ath/ath11k/wmi.h
@@ -2926,6 +2926,12 @@ struct set_fwtest_params {
 	u32 value;
 };
 
+struct wmi_pdev_ap_ps_cmd {
+	u32 tlv_header;
+	u32 pdev_id;
+	u32 param_value;
+} __packed;
+
 struct wmi_fwtest_set_param_cmd_param {
 	u32 tlv_header;
 	u32 param_id;
@@ -5426,6 +5432,7 @@ int ath11k_wmi_pdev_non_srg_obss_bssid_e
 						     u32 *bitmap);
 int ath11k_send_coex_config_cmd(struct ath11k *ar,
 			       struct coex_config_arg *coex_config);
+int ath11k_wmi_pdev_ap_ps_cmd_send(struct ath11k *ar, u8 pdev_id, u32 value);
 int ath11k_wmi_send_obss_color_collision_cfg_cmd(struct ath11k *ar, u32 vdev_id,
 						 u8 bss_color, u32 period,
 						 bool enable);
