diff --git a/drivers/net/wireless/realtek/rtw89/chan.c b/drivers/net/wireless/realtek/rtw89/chan.c
index be6d73273910..f60e93870b09 100644
--- a/drivers/net/wireless/realtek/rtw89/chan.c
+++ b/drivers/net/wireless/realtek/rtw89/chan.c
@@ -8,6 +8,7 @@
 #include "fw.h"
 #include "mac.h"
 #include "ps.h"
+#include "sar.h"
 #include "util.h"
 
 static void rtw89_swap_chanctx(struct rtw89_dev *rtwdev,
@@ -2673,6 +2674,7 @@ int rtw89_chanctx_ops_assign_vif(struct rtw89_dev *rtwdev,
 	struct rtw89_hal *hal = &rtwdev->hal;
 	struct rtw89_entity_mgnt *mgnt = &hal->entity_mgnt;
 	struct rtw89_entity_weight w = {};
+	int ret;
 
 	rtwvif_link->chanctx_idx = cfg->idx;
 	rtwvif_link->chanctx_assigned = true;
@@ -2692,7 +2694,13 @@ int rtw89_chanctx_ops_assign_vif(struct rtw89_dev *rtwdev,
 	rtw89_swap_chanctx(rtwdev, cfg->idx, RTW89_CHANCTX_0);
 
 out:
-	return rtw89_set_channel(rtwdev);
+	ret = rtw89_set_channel(rtwdev);
+	if (ret)
+		return ret;
+
+	rtw89_tas_reset(rtwdev, true);
+
+	return 0;
 }
 
 void rtw89_chanctx_ops_unassign_vif(struct rtw89_dev *rtwdev,
diff --git a/drivers/net/wireless/realtek/rtw89/core.c b/drivers/net/wireless/realtek/rtw89/core.c
index 4d286182e21d..cc9b014457ac 100644
--- a/drivers/net/wireless/realtek/rtw89/core.c
+++ b/drivers/net/wireless/realtek/rtw89/core.c
@@ -4601,8 +4601,6 @@ int rtw89_core_start(struct rtw89_dev *rtwdev)
 	rtw89_mac_cfg_phy_rpt_bands(rtwdev, true);
 	rtw89_mac_update_rts_threshold(rtwdev);
 
-	rtw89_tas_reset(rtwdev);
-
 	ret = rtw89_hci_start(rtwdev);
 	if (ret) {
 		rtw89_err(rtwdev, "failed to start hci\n");
@@ -4956,6 +4954,7 @@ void rtw89_core_scan_start(struct rtw89_dev *rtwdev, struct rtw89_vif_link *rtwv
 	rtw89_chip_rfk_scan(rtwdev, rtwvif_link, true);
 	rtw89_hci_recalc_int_mit(rtwdev);
 	rtw89_phy_config_edcca(rtwdev, bb, true);
+	rtw89_tas_scan(rtwdev, true);
 
 	rtw89_fw_h2c_cam(rtwdev, rtwvif_link, NULL, mac_addr);
 }
@@ -4982,6 +4981,7 @@ void rtw89_core_scan_complete(struct rtw89_dev *rtwdev,
 	rtw89_btc_ntfy_scan_finish(rtwdev, rtwvif_link->phy_idx);
 	bb = rtw89_get_bb_ctx(rtwdev, rtwvif_link->phy_idx);
 	rtw89_phy_config_edcca(rtwdev, bb, false);
+	rtw89_tas_scan(rtwdev, false);
 
 	rtwdev->scanning = false;
 	rtw89_for_each_active_bb(rtwdev, bb)
diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h
index 3f62df657e1f..074c865e6d03 100644
--- a/drivers/net/wireless/realtek/rtw89/core.h
+++ b/drivers/net/wireless/realtek/rtw89/core.h
@@ -4240,6 +4240,7 @@ enum rtw89_chanctx_state {
 enum rtw89_chanctx_callbacks {
 	RTW89_CHANCTX_CALLBACK_PLACEHOLDER,
 	RTW89_CHANCTX_CALLBACK_RFK,
+	RTW89_CHANCTX_CALLBACK_TAS,
 
 	NUM_OF_RTW89_CHANCTX_CALLBACKS,
 };
@@ -4281,6 +4282,7 @@ struct rtw89_chip_info {
 	bool support_unii4;
 	bool support_rnr;
 	bool support_ant_gain;
+	bool support_tas;
 	bool ul_tb_waveform_ctrl;
 	bool ul_tb_pwr_diff;
 	bool rx_freq_frome_ie;
@@ -4679,18 +4681,29 @@ struct rtw89_6ghz_span {
 enum rtw89_tas_state {
 	RTW89_TAS_STATE_DPR_OFF,
 	RTW89_TAS_STATE_DPR_ON,
-	RTW89_TAS_STATE_DPR_FORBID,
+	RTW89_TAS_STATE_STATIC_SAR,
 };
 
-#define RTW89_TAS_MAX_WINDOW 50
+#define RTW89_TAS_TX_RATIO_WINDOW 6
+#define RTW89_TAS_TXPWR_WINDOW 180
 struct rtw89_tas_info {
-	s16 txpwr_history[RTW89_TAS_MAX_WINDOW];
-	s32 total_txpwr;
-	u8 cur_idx;
-	s8 dpr_gap;
-	s8 delta;
+	u16 tx_ratio_history[RTW89_TAS_TX_RATIO_WINDOW];
+	u64 txpwr_history[RTW89_TAS_TXPWR_WINDOW];
+	u8 txpwr_head_idx;
+	u8 txpwr_tail_idx;
+	u8 tx_ratio_idx;
+	u16 total_tx_ratio;
+	u64 total_txpwr;
+	u64 instant_txpwr;
+	u32 window_size;
+	s8 dpr_on_threshold;
+	s8 dpr_off_threshold;
+	enum rtw89_tas_state backup_state;
 	enum rtw89_tas_state state;
+	bool keep_history;
+	bool block_regd;
 	bool enable;
+	bool pause;
 };
 
 struct rtw89_chanctx_cfg {
@@ -4748,6 +4761,7 @@ struct rtw89_edcca_bak {
 enum rtw89_dm_type {
 	RTW89_DM_DYNAMIC_EDCCA,
 	RTW89_DM_THERMAL_PROTECT,
+	RTW89_DM_TAS,
 };
 
 #define RTW89_THERMAL_PROT_LV_MAX 5
diff --git a/drivers/net/wireless/realtek/rtw89/debug.c b/drivers/net/wireless/realtek/rtw89/debug.c
index 885a5ebeb6cd..f2c5753fd386 100644
--- a/drivers/net/wireless/realtek/rtw89/debug.c
+++ b/drivers/net/wireless/realtek/rtw89/debug.c
@@ -4154,6 +4154,7 @@ static const struct rtw89_disabled_dm_info {
 } rtw89_disabled_dm_infos[] = {
 	DM_INFO(DYNAMIC_EDCCA),
 	DM_INFO(THERMAL_PROTECT),
+	DM_INFO(TAS),
 };
 
 static ssize_t
diff --git a/drivers/net/wireless/realtek/rtw89/fw.h b/drivers/net/wireless/realtek/rtw89/fw.h
index e0faed076150..55255b48bdb7 100644
--- a/drivers/net/wireless/realtek/rtw89/fw.h
+++ b/drivers/net/wireless/realtek/rtw89/fw.h
@@ -4543,6 +4543,12 @@ struct rtw89_c2h_rfk_report {
 	u8 version;
 } __packed;
 
+struct rtw89_c2h_rf_tas_info {
+	struct rtw89_c2h_hdr hdr;
+	__le32 cur_idx;
+	__le16 txpwr_history[20];
+} __packed;
+
 #define RTW89_FW_RSVD_PLE_SIZE 0x800
 
 #define RTW89_FW_BACKTRACE_INFO_SIZE 8
diff --git a/drivers/net/wireless/realtek/rtw89/phy.c b/drivers/net/wireless/realtek/rtw89/phy.c
index e4b690a5febc..abc2a7e989eb 100644
--- a/drivers/net/wireless/realtek/rtw89/phy.c
+++ b/drivers/net/wireless/realtek/rtw89/phy.c
@@ -3459,6 +3459,30 @@ rtw89_phy_c2h_rfk_report_state(struct rtw89_dev *rtwdev, struct sk_buff *c2h, u3
 static void
 rtw89_phy_c2h_rfk_log_tas_pwr(struct rtw89_dev *rtwdev, struct sk_buff *c2h, u32 len)
 {
+	const struct rtw89_c2h_rf_tas_info *rf_tas =
+		(const struct rtw89_c2h_rf_tas_info *)c2h->data;
+	const enum rtw89_sar_sources src = rtwdev->sar.src;
+	struct rtw89_tas_info *tas = &rtwdev->tas;
+	u64 linear = 0;
+	u32 i, cur_idx;
+	s16 txpwr;
+
+	if (!tas->enable || src == RTW89_SAR_SOURCE_NONE)
+		return;
+
+	cur_idx = le32_to_cpu(rf_tas->cur_idx);
+	for (i = 0; i < cur_idx; i++) {
+		txpwr = (s16)le16_to_cpu(rf_tas->txpwr_history[i]);
+		linear += rtw89_db_quarter_to_linear(txpwr);
+
+		rtw89_debug(rtwdev, RTW89_DBG_SAR,
+			    "tas: index: %u, txpwr: %d\n", i, txpwr);
+	}
+
+	if (cur_idx == 0)
+		tas->instant_txpwr = rtw89_db_to_linear(0);
+	else
+		tas->instant_txpwr = DIV_ROUND_DOWN_ULL(linear, cur_idx);
 }
 
 static
diff --git a/drivers/net/wireless/realtek/rtw89/regd.c b/drivers/net/wireless/realtek/rtw89/regd.c
index 0e67d0f128dd..d31403f9009e 100644
--- a/drivers/net/wireless/realtek/rtw89/regd.c
+++ b/drivers/net/wireless/realtek/rtw89/regd.c
@@ -721,6 +721,18 @@ static void rtw89_regd_apply_policy_6ghz(struct rtw89_dev *rtwdev,
 		sband->channels[i].flags |= IEEE80211_CHAN_DISABLED;
 }
 
+static void rtw89_regd_apply_policy_tas(struct rtw89_dev *rtwdev)
+{
+	struct rtw89_regulatory_info *regulatory = &rtwdev->regulatory;
+	const struct rtw89_regd *regd = regulatory->regd;
+	struct rtw89_tas_info *tas = &rtwdev->tas;
+
+	if (!tas->enable)
+		return;
+
+	tas->block_regd = !test_bit(RTW89_REGD_FUNC_TAS, regd->func_bitmap);
+}
+
 static void rtw89_regd_notifier_apply(struct rtw89_dev *rtwdev,
 				      struct wiphy *wiphy,
 				      struct regulatory_request *request)
@@ -738,6 +750,7 @@ static void rtw89_regd_notifier_apply(struct rtw89_dev *rtwdev,
 
 	rtw89_regd_apply_policy_unii4(rtwdev, wiphy);
 	rtw89_regd_apply_policy_6ghz(rtwdev, wiphy);
+	rtw89_regd_apply_policy_tas(rtwdev);
 }
 
 static
diff --git a/drivers/net/wireless/realtek/rtw89/rtw8851b.c b/drivers/net/wireless/realtek/rtw89/rtw8851b.c
index b709dd71b06a..0d482cd57f6e 100644
--- a/drivers/net/wireless/realtek/rtw89/rtw8851b.c
+++ b/drivers/net/wireless/realtek/rtw89/rtw8851b.c
@@ -2498,6 +2498,7 @@ const struct rtw89_chip_info rtw8851b_chip_info = {
 				  BIT(NL80211_CHAN_WIDTH_80),
 	.support_unii4		= true,
 	.support_ant_gain	= false,
+	.support_tas		= false,
 	.ul_tb_waveform_ctrl	= true,
 	.ul_tb_pwr_diff		= false,
 	.rx_freq_frome_ie	= true,
diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852a.c b/drivers/net/wireless/realtek/rtw89/rtw8852a.c
index 638236bffebb..286334e26c84 100644
--- a/drivers/net/wireless/realtek/rtw89/rtw8852a.c
+++ b/drivers/net/wireless/realtek/rtw89/rtw8852a.c
@@ -2216,6 +2216,7 @@ const struct rtw89_chip_info rtw8852a_chip_info = {
 				  BIT(NL80211_CHAN_WIDTH_80),
 	.support_unii4		= false,
 	.support_ant_gain	= false,
+	.support_tas		= false,
 	.ul_tb_waveform_ctrl	= false,
 	.ul_tb_pwr_diff		= false,
 	.rx_freq_frome_ie	= true,
diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852b.c b/drivers/net/wireless/realtek/rtw89/rtw8852b.c
index 8bec74716f84..eceb4fb9880d 100644
--- a/drivers/net/wireless/realtek/rtw89/rtw8852b.c
+++ b/drivers/net/wireless/realtek/rtw89/rtw8852b.c
@@ -852,6 +852,7 @@ const struct rtw89_chip_info rtw8852b_chip_info = {
 				  BIT(NL80211_CHAN_WIDTH_80),
 	.support_unii4		= true,
 	.support_ant_gain	= true,
+	.support_tas		= false,
 	.ul_tb_waveform_ctrl	= true,
 	.ul_tb_pwr_diff		= false,
 	.rx_freq_frome_ie	= true,
diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852bt.c b/drivers/net/wireless/realtek/rtw89/rtw8852bt.c
index e62810660005..bbf37442c492 100644
--- a/drivers/net/wireless/realtek/rtw89/rtw8852bt.c
+++ b/drivers/net/wireless/realtek/rtw89/rtw8852bt.c
@@ -785,6 +785,7 @@ const struct rtw89_chip_info rtw8852bt_chip_info = {
 				  BIT(NL80211_CHAN_WIDTH_80),
 	.support_unii4		= true,
 	.support_ant_gain	= true,
+	.support_tas		= false,
 	.ul_tb_waveform_ctrl	= true,
 	.ul_tb_pwr_diff		= false,
 	.rx_freq_frome_ie	= true,
diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852c.c b/drivers/net/wireless/realtek/rtw89/rtw8852c.c
index 5d3e105dc87f..08bcdf246382 100644
--- a/drivers/net/wireless/realtek/rtw89/rtw8852c.c
+++ b/drivers/net/wireless/realtek/rtw89/rtw8852c.c
@@ -12,6 +12,7 @@
 #include "rtw8852c.h"
 #include "rtw8852c_rfk.h"
 #include "rtw8852c_table.h"
+#include "sar.h"
 #include "util.h"
 
 #define RTW8852C_FW_FORMAT_MAX 1
@@ -2880,6 +2881,7 @@ static int rtw8852c_mac_disable_bb_rf(struct rtw89_dev *rtwdev)
 
 static const struct rtw89_chanctx_listener rtw8852c_chanctx_listener = {
 	.callbacks[RTW89_CHANCTX_CALLBACK_RFK] = rtw8852c_rfk_chanctx_cb,
+	.callbacks[RTW89_CHANCTX_CALLBACK_TAS] = rtw89_tas_chanctx_cb,
 };
 
 #ifdef CONFIG_PM
@@ -3011,6 +3013,7 @@ const struct rtw89_chip_info rtw8852c_chip_info = {
 				  BIT(NL80211_CHAN_WIDTH_160),
 	.support_unii4		= true,
 	.support_ant_gain	= true,
+	.support_tas		= true,
 	.ul_tb_waveform_ctrl	= false,
 	.ul_tb_pwr_diff		= true,
 	.rx_freq_frome_ie	= false,
diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922a.c b/drivers/net/wireless/realtek/rtw89/rtw8922a.c
index 210fa818ffe8..d90af2daebcf 100644
--- a/drivers/net/wireless/realtek/rtw89/rtw8922a.c
+++ b/drivers/net/wireless/realtek/rtw89/rtw8922a.c
@@ -2770,6 +2770,7 @@ const struct rtw89_chip_info rtw8922a_chip_info = {
 				  BIT(NL80211_CHAN_WIDTH_160),
 	.support_unii4		= true,
 	.support_ant_gain	= false,
+	.support_tas		= false,
 	.ul_tb_waveform_ctrl	= false,
 	.ul_tb_pwr_diff		= false,
 	.rx_freq_frome_ie	= false,
diff --git a/drivers/net/wireless/realtek/rtw89/sar.c b/drivers/net/wireless/realtek/rtw89/sar.c
index 24a32dc4e67d..0b5af9528702 100644
--- a/drivers/net/wireless/realtek/rtw89/sar.c
+++ b/drivers/net/wireless/realtek/rtw89/sar.c
@@ -7,10 +7,16 @@
 #include "phy.h"
 #include "reg.h"
 #include "sar.h"
+#include "util.h"
 
 #define RTW89_TAS_FACTOR 2 /* unit: 0.25 dBm */
+#define RTW89_TAS_SAR_GAP (1 << RTW89_TAS_FACTOR)
 #define RTW89_TAS_DPR_GAP (1 << RTW89_TAS_FACTOR)
 #define RTW89_TAS_DELTA (2 << RTW89_TAS_FACTOR)
+#define RTW89_TAS_TX_RATIO_THRESHOLD 70
+#define RTW89_TAS_DFLT_TX_RATIO 80
+#define RTW89_TAS_DPR_ON_OFFSET (RTW89_TAS_DELTA + RTW89_TAS_SAR_GAP)
+#define RTW89_TAS_DPR_OFF_OFFSET (4 << RTW89_TAS_FACTOR)
 
 static enum rtw89_sar_subband rtw89_sar_get_subband(struct rtw89_dev *rtwdev,
 						    u32 center_freq)
@@ -117,8 +123,8 @@ static s8 rtw89_txpwr_sar_to_mac(struct rtw89_dev *rtwdev, u8 fct, s32 cfg)
 			   RTW89_SAR_TXPWR_MAC_MAX);
 }
 
-static s8 rtw89_txpwr_tas_to_sar(const struct rtw89_sar_handler *sar_hdl,
-				 s8 cfg)
+static s32 rtw89_txpwr_tas_to_sar(const struct rtw89_sar_handler *sar_hdl,
+				  s32 cfg)
 {
 	const u8 fct = sar_hdl->txpwr_factor_sar;
 
@@ -128,8 +134,8 @@ static s8 rtw89_txpwr_tas_to_sar(const struct rtw89_sar_handler *sar_hdl,
 		return cfg >> (RTW89_TAS_FACTOR - fct);
 }
 
-static s8 rtw89_txpwr_sar_to_tas(const struct rtw89_sar_handler *sar_hdl,
-				 s8 cfg)
+static s32 rtw89_txpwr_sar_to_tas(const struct rtw89_sar_handler *sar_hdl,
+				  s32 cfg)
 {
 	const u8 fct = sar_hdl->txpwr_factor_sar;
 
@@ -139,13 +145,43 @@ static s8 rtw89_txpwr_sar_to_tas(const struct rtw89_sar_handler *sar_hdl,
 		return cfg << (RTW89_TAS_FACTOR - fct);
 }
 
+static bool rtw89_tas_is_active(struct rtw89_dev *rtwdev)
+{
+	struct rtw89_tas_info *tas = &rtwdev->tas;
+	struct rtw89_vif *rtwvif;
+
+	if (!tas->enable)
+		return false;
+
+	rtw89_for_each_rtwvif(rtwdev, rtwvif) {
+		if (ieee80211_vif_is_mld(rtwvif_to_vif(rtwvif)))
+			return false;
+	}
+
+	return true;
+}
+
+static const char *rtw89_tas_state_str(enum rtw89_tas_state state)
+{
+	switch (state) {
+	case RTW89_TAS_STATE_DPR_OFF:
+		return "DPR OFF";
+	case RTW89_TAS_STATE_DPR_ON:
+		return "DPR ON";
+	case RTW89_TAS_STATE_STATIC_SAR:
+		return "STATIC SAR";
+	default:
+		return NULL;
+	}
+}
+
 s8 rtw89_query_sar(struct rtw89_dev *rtwdev, u32 center_freq)
 {
 	const enum rtw89_sar_sources src = rtwdev->sar.src;
 	/* its members are protected by rtw89_sar_set_src() */
 	const struct rtw89_sar_handler *sar_hdl = &rtw89_sar_handlers[src];
 	struct rtw89_tas_info *tas = &rtwdev->tas;
-	s8 delta;
+	s32 offset;
 	int ret;
 	s32 cfg;
 	u8 fct;
@@ -159,15 +195,17 @@ s8 rtw89_query_sar(struct rtw89_dev *rtwdev, u32 center_freq)
 	if (ret)
 		return RTW89_SAR_TXPWR_MAC_MAX;
 
-	if (tas->enable) {
+	if (rtw89_tas_is_active(rtwdev)) {
 		switch (tas->state) {
 		case RTW89_TAS_STATE_DPR_OFF:
-			return RTW89_SAR_TXPWR_MAC_MAX;
+			offset = rtw89_txpwr_tas_to_sar(sar_hdl, RTW89_TAS_DPR_OFF_OFFSET);
+			cfg += offset;
+			break;
 		case RTW89_TAS_STATE_DPR_ON:
-			delta = rtw89_txpwr_tas_to_sar(sar_hdl, tas->delta);
-			cfg -= delta;
+			offset = rtw89_txpwr_tas_to_sar(sar_hdl, RTW89_TAS_DPR_ON_OFFSET);
+			cfg -= offset;
 			break;
-		case RTW89_TAS_STATE_DPR_FORBID:
+		case RTW89_TAS_STATE_STATIC_SAR:
 		default:
 			break;
 		}
@@ -223,13 +261,23 @@ int rtw89_print_tas(struct rtw89_dev *rtwdev, char *buf, size_t bufsz)
 	struct rtw89_tas_info *tas = &rtwdev->tas;
 	char *p = buf, *end = buf + bufsz;
 
-	if (!tas->enable) {
+	if (!rtw89_tas_is_active(rtwdev)) {
 		p += scnprintf(p, end - p, "no TAS is applied\n");
 		goto out;
 	}
 
-	p += scnprintf(p, end - p, "DPR gap: %d\n", tas->dpr_gap);
-	p += scnprintf(p, end - p, "TAS delta: %d\n", tas->delta);
+	p += scnprintf(p, end - p, "State: %s\n",
+		       rtw89_tas_state_str(tas->state));
+	p += scnprintf(p, end - p, "Average time: %d\n",
+		       tas->window_size * 2);
+	p += scnprintf(p, end - p, "SAR gap: %d dBm\n",
+		       RTW89_TAS_SAR_GAP >> RTW89_TAS_FACTOR);
+	p += scnprintf(p, end - p, "DPR gap: %d dBm\n",
+		       RTW89_TAS_DPR_GAP >> RTW89_TAS_FACTOR);
+	p += scnprintf(p, end - p, "DPR ON offset: %d dBm\n",
+		       RTW89_TAS_DPR_ON_OFFSET >> RTW89_TAS_FACTOR);
+	p += scnprintf(p, end - p, "DPR OFF offset: %d dBm\n",
+		       RTW89_TAS_DPR_OFF_OFFSET >> RTW89_TAS_FACTOR);
 
 out:
 	return p - buf;
@@ -250,6 +298,7 @@ static int rtw89_apply_sar_common(struct rtw89_dev *rtwdev,
 
 	rtw89_sar_set_src(rtwdev, RTW89_SAR_SOURCE_COMMON, cfg_common, sar);
 	rtw89_core_set_chip_txpwr(rtwdev);
+	rtw89_tas_reset(rtwdev, false);
 
 	return 0;
 }
@@ -314,65 +363,174 @@ int rtw89_ops_set_sar_specs(struct ieee80211_hw *hw,
 	return rtw89_apply_sar_common(rtwdev, &sar_common);
 }
 
-static void rtw89_tas_state_update(struct rtw89_dev *rtwdev)
+static bool rtw89_tas_query_sar_config(struct rtw89_dev *rtwdev, s32 *cfg)
 {
+	const struct rtw89_chan *chan = rtw89_chan_get(rtwdev, RTW89_CHANCTX_0);
 	const enum rtw89_sar_sources src = rtwdev->sar.src;
 	/* its members are protected by rtw89_sar_set_src() */
 	const struct rtw89_sar_handler *sar_hdl = &rtw89_sar_handlers[src];
-	struct rtw89_tas_info *tas = &rtwdev->tas;
-	s32 txpwr_avg = tas->total_txpwr / RTW89_TAS_MAX_WINDOW / PERCENT;
-	s32 dpr_on_threshold, dpr_off_threshold, cfg;
-	enum rtw89_tas_state state = tas->state;
-	const struct rtw89_chan *chan;
 	int ret;
 
-	lockdep_assert_wiphy(rtwdev->hw->wiphy);
-
 	if (src == RTW89_SAR_SOURCE_NONE)
-		return;
+		return false;
 
-	chan = rtw89_chan_get(rtwdev, RTW89_CHANCTX_0);
-	ret = sar_hdl->query_sar_config(rtwdev, chan->freq, &cfg);
+	ret = sar_hdl->query_sar_config(rtwdev, chan->freq, cfg);
 	if (ret)
+		return false;
+
+	*cfg = rtw89_txpwr_sar_to_tas(sar_hdl, *cfg);
+
+	return true;
+}
+
+static void rtw89_tas_state_update(struct rtw89_dev *rtwdev,
+				   enum rtw89_tas_state state)
+{
+	struct rtw89_tas_info *tas = &rtwdev->tas;
+
+	if (tas->state == state)
 		return;
 
-	cfg = rtw89_txpwr_sar_to_tas(sar_hdl, cfg);
+	rtw89_debug(rtwdev, RTW89_DBG_SAR, "tas: switch state: %s -> %s\n",
+		    rtw89_tas_state_str(tas->state), rtw89_tas_state_str(state));
+
+	tas->state = state;
+	rtw89_core_set_chip_txpwr(rtwdev);
+}
 
-	if (tas->delta >= cfg) {
+static u32 rtw89_tas_get_window_size(struct rtw89_dev *rtwdev)
+{
+	const struct rtw89_chan *chan = rtw89_chan_get(rtwdev, RTW89_CHANCTX_0);
+	u8 band = chan->band_type;
+	u8 regd = rtw89_regd_get(rtwdev, band);
+
+	switch (regd) {
+	default:
 		rtw89_debug(rtwdev, RTW89_DBG_SAR,
-			    "TAS delta exceed SAR limit\n");
-		state = RTW89_TAS_STATE_DPR_FORBID;
-		goto out;
+			    "tas: regd: %u is unhandled\n", regd);
+		fallthrough;
+	case RTW89_IC:
+	case RTW89_KCC:
+		return 180;
+	case RTW89_FCC:
+		switch (band) {
+		case RTW89_BAND_2G:
+			return 50;
+		case RTW89_BAND_5G:
+			return 30;
+		case RTW89_BAND_6G:
+		default:
+			return 15;
+		}
+		break;
 	}
+}
+
+static void rtw89_tas_window_update(struct rtw89_dev *rtwdev)
+{
+	u32 window_size = rtw89_tas_get_window_size(rtwdev);
+	struct rtw89_tas_info *tas = &rtwdev->tas;
+	u64 total_txpwr = 0;
+	u8 head_idx;
+	u32 i, j;
+
+	WARN_ON_ONCE(tas->window_size > RTW89_TAS_TXPWR_WINDOW);
+
+	if (tas->window_size == window_size)
+		return;
+
+	rtw89_debug(rtwdev, RTW89_DBG_SAR, "tas: window update: %u -> %u\n",
+		    tas->window_size, window_size);
+
+	head_idx = (tas->txpwr_tail_idx - window_size + 1 + RTW89_TAS_TXPWR_WINDOW) %
+		   RTW89_TAS_TXPWR_WINDOW;
+	for (i = 0; i < window_size; i++) {
+		j = (head_idx + i) % RTW89_TAS_TXPWR_WINDOW;
+		total_txpwr += tas->txpwr_history[j];
+	}
+
+	tas->window_size = window_size;
+	tas->total_txpwr = total_txpwr;
+	tas->txpwr_head_idx = head_idx;
+}
+
+static void rtw89_tas_history_update(struct rtw89_dev *rtwdev)
+{
+	struct rtw89_bb_ctx *bb = rtw89_get_bb_ctx(rtwdev, RTW89_PHY_0);
+	struct rtw89_env_monitor_info *env = &bb->env_monitor;
+	struct rtw89_tas_info *tas = &rtwdev->tas;
+	u8 tx_ratio = env->ifs_clm_tx_ratio;
+	u64 instant_txpwr, txpwr;
+
+	/* txpwr in unit of linear(mW) multiply by percentage */
+	if (tx_ratio == 0) {
+		/* special case: idle tx power
+		 * use -40 dBm * 100 tx ratio
+		 */
+		instant_txpwr = rtw89_db_to_linear(-40);
+		txpwr = instant_txpwr * 100;
+	} else {
+		instant_txpwr = tas->instant_txpwr;
+		txpwr = instant_txpwr * tx_ratio;
+	}
+
+	tas->total_txpwr += txpwr - tas->txpwr_history[tas->txpwr_head_idx];
+	tas->total_tx_ratio += tx_ratio - tas->tx_ratio_history[tas->tx_ratio_idx];
+	tas->tx_ratio_history[tas->tx_ratio_idx] = tx_ratio;
+
+	tas->txpwr_head_idx = (tas->txpwr_head_idx + 1) % RTW89_TAS_TXPWR_WINDOW;
+	tas->txpwr_tail_idx = (tas->txpwr_tail_idx + 1) % RTW89_TAS_TXPWR_WINDOW;
+	tas->tx_ratio_idx = (tas->tx_ratio_idx + 1) % RTW89_TAS_TX_RATIO_WINDOW;
+	tas->txpwr_history[tas->txpwr_tail_idx] = txpwr;
 
-	dpr_on_threshold = cfg;
-	dpr_off_threshold = cfg - tas->dpr_gap;
 	rtw89_debug(rtwdev, RTW89_DBG_SAR,
-		    "DPR_ON thold: %d, DPR_OFF thold: %d, txpwr_avg: %d\n",
-		    dpr_on_threshold, dpr_off_threshold, txpwr_avg);
+		    "tas: instant_txpwr: %d, tx_ratio: %u, txpwr: %d\n",
+		    rtw89_linear_to_db_quarter(instant_txpwr), tx_ratio,
+		    rtw89_linear_to_db_quarter(div_u64(txpwr, PERCENT)));
+}
 
-	if (txpwr_avg >= dpr_on_threshold)
+static void rtw89_tas_rolling_average(struct rtw89_dev *rtwdev)
+{
+	struct rtw89_tas_info *tas = &rtwdev->tas;
+	s32 dpr_on_threshold, dpr_off_threshold;
+	enum rtw89_tas_state state;
+	u16 tx_ratio_avg;
+	s32 txpwr_avg;
+	u64 linear;
+
+	linear = DIV_ROUND_DOWN_ULL(tas->total_txpwr, tas->window_size * PERCENT);
+	txpwr_avg = rtw89_linear_to_db_quarter(linear);
+	tx_ratio_avg = tas->total_tx_ratio / RTW89_TAS_TX_RATIO_WINDOW;
+	dpr_on_threshold = tas->dpr_on_threshold;
+	dpr_off_threshold = tas->dpr_off_threshold;
+
+	rtw89_debug(rtwdev, RTW89_DBG_SAR,
+		    "tas: DPR_ON: %d, DPR_OFF: %d, txpwr_avg: %d, tx_ratio_avg: %u\n",
+		    dpr_on_threshold, dpr_off_threshold, txpwr_avg, tx_ratio_avg);
+
+	if (tx_ratio_avg >= RTW89_TAS_TX_RATIO_THRESHOLD)
+		state = RTW89_TAS_STATE_STATIC_SAR;
+	else if (txpwr_avg >= dpr_on_threshold)
 		state = RTW89_TAS_STATE_DPR_ON;
 	else if (txpwr_avg < dpr_off_threshold)
 		state = RTW89_TAS_STATE_DPR_OFF;
-
-out:
-	if (tas->state == state)
+	else
 		return;
 
-	rtw89_debug(rtwdev, RTW89_DBG_SAR,
-		    "TAS old state: %d, new state: %d\n", tas->state, state);
-	tas->state = state;
-	rtw89_core_set_chip_txpwr(rtwdev);
+	rtw89_tas_state_update(rtwdev, state);
 }
 
 void rtw89_tas_init(struct rtw89_dev *rtwdev)
 {
+	const struct rtw89_chip_info *chip = rtwdev->chip;
 	struct rtw89_tas_info *tas = &rtwdev->tas;
 	struct rtw89_acpi_dsm_result res = {};
 	int ret;
 	u8 val;
 
+	if (!chip->support_tas)
+		return;
+
 	ret = rtw89_acpi_evaluate_dsm(rtwdev, RTW89_ACPI_DSM_FUNC_TAS_EN, &res);
 	if (ret) {
 		rtw89_debug(rtwdev, RTW89_DBG_SAR,
@@ -396,65 +554,116 @@ void rtw89_tas_init(struct rtw89_dev *rtwdev)
 		rtw89_debug(rtwdev, RTW89_DBG_SAR, "TAS not enable\n");
 		return;
 	}
-
-	tas->dpr_gap = RTW89_TAS_DPR_GAP;
-	tas->delta = RTW89_TAS_DELTA;
 }
 
-void rtw89_tas_reset(struct rtw89_dev *rtwdev)
+void rtw89_tas_reset(struct rtw89_dev *rtwdev, bool force)
 {
+	const struct rtw89_chan *chan = rtw89_chan_get(rtwdev, RTW89_CHANCTX_0);
 	struct rtw89_tas_info *tas = &rtwdev->tas;
+	u64 linear;
+	s32 cfg;
+	int i;
 
-	if (!tas->enable)
+	if (!rtw89_tas_is_active(rtwdev))
+		return;
+
+	if (!rtw89_tas_query_sar_config(rtwdev, &cfg))
+		return;
+
+	tas->dpr_on_threshold = cfg - RTW89_TAS_SAR_GAP;
+	tas->dpr_off_threshold = cfg - RTW89_TAS_SAR_GAP - RTW89_TAS_DPR_GAP;
+
+	/* avoid history reset after new SAR apply */
+	if (!force && tas->keep_history)
 		return;
 
-	memset(&tas->txpwr_history, 0, sizeof(tas->txpwr_history));
-	tas->total_txpwr = 0;
-	tas->cur_idx = 0;
+	linear = rtw89_db_quarter_to_linear(cfg) * RTW89_TAS_DFLT_TX_RATIO;
+	for (i = 0; i < RTW89_TAS_TXPWR_WINDOW; i++)
+		tas->txpwr_history[i] = linear;
+
+	for (i = 0; i < RTW89_TAS_TX_RATIO_WINDOW; i++)
+		tas->tx_ratio_history[i] = RTW89_TAS_DFLT_TX_RATIO;
+
+	tas->total_tx_ratio = RTW89_TAS_DFLT_TX_RATIO * RTW89_TAS_TX_RATIO_WINDOW;
+	tas->total_txpwr = linear * RTW89_TAS_TXPWR_WINDOW;
+	tas->window_size = RTW89_TAS_TXPWR_WINDOW;
+	tas->txpwr_head_idx = 0;
+	tas->txpwr_tail_idx = RTW89_TAS_TXPWR_WINDOW - 1;
+	tas->tx_ratio_idx = 0;
 	tas->state = RTW89_TAS_STATE_DPR_OFF;
-}
+	tas->backup_state = RTW89_TAS_STATE_DPR_OFF;
+	tas->keep_history = true;
 
-static const struct rtw89_reg_def txpwr_regs[] = {
-	{R_PATH0_TXPWR, B_PATH0_TXPWR},
-	{R_PATH1_TXPWR, B_PATH1_TXPWR},
-};
+	rtw89_debug(rtwdev, RTW89_DBG_SAR,
+		    "tas: band: %u, freq: %u\n", chan->band_type, chan->freq);
+}
 
 void rtw89_tas_track(struct rtw89_dev *rtwdev)
 {
-	struct rtw89_bb_ctx *bb = rtw89_get_bb_ctx(rtwdev, RTW89_PHY_0);
-	struct rtw89_env_monitor_info *env = &bb->env_monitor;
-	const enum rtw89_sar_sources src = rtwdev->sar.src;
-	u8 max_nss_num = rtwdev->chip->rf_path_num;
 	struct rtw89_tas_info *tas = &rtwdev->tas;
-	s16 tmp, txpwr, instant_txpwr = 0;
-	u32 val;
-	int i;
+	struct rtw89_hal *hal = &rtwdev->hal;
+	s32 cfg;
 
-	if (!tas->enable || src == RTW89_SAR_SOURCE_NONE)
+	if (hal->disabled_dm_bitmap & BIT(RTW89_DM_TAS))
 		return;
 
-	if (env->ccx_watchdog_result != RTW89_PHY_ENV_MON_IFS_CLM)
+	if (!rtw89_tas_is_active(rtwdev))
 		return;
 
-	for (i = 0; i < max_nss_num; i++) {
-		val = rtw89_phy_read32_mask(rtwdev, txpwr_regs[i].addr,
-					    txpwr_regs[i].mask);
-		tmp = sign_extend32(val, 8);
-		if (tmp <= 0)
-			return;
-		instant_txpwr += tmp;
+	if (!rtw89_tas_query_sar_config(rtwdev, &cfg) || tas->block_regd) {
+		rtw89_tas_state_update(rtwdev, RTW89_TAS_STATE_STATIC_SAR);
+		return;
 	}
 
-	instant_txpwr /= max_nss_num;
-	/* in unit of 0.25 dBm multiply by percentage */
-	txpwr = instant_txpwr * env->ifs_clm_tx_ratio;
-	tas->total_txpwr += txpwr - tas->txpwr_history[tas->cur_idx];
-	tas->txpwr_history[tas->cur_idx] = txpwr;
-	rtw89_debug(rtwdev, RTW89_DBG_SAR,
-		    "instant_txpwr: %d, tx_ratio: %d, txpwr: %d\n",
-		    instant_txpwr, env->ifs_clm_tx_ratio, txpwr);
+	if (tas->pause)
+		return;
+
+	rtw89_tas_window_update(rtwdev);
+	rtw89_tas_history_update(rtwdev);
+	rtw89_tas_rolling_average(rtwdev);
+}
+
+void rtw89_tas_scan(struct rtw89_dev *rtwdev, bool start)
+{
+	struct rtw89_tas_info *tas = &rtwdev->tas;
+	s32 cfg;
+
+	if (!rtw89_tas_is_active(rtwdev))
+		return;
 
-	tas->cur_idx = (tas->cur_idx + 1) % RTW89_TAS_MAX_WINDOW;
+	if (!rtw89_tas_query_sar_config(rtwdev, &cfg))
+		return;
 
-	rtw89_tas_state_update(rtwdev);
+	if (start) {
+		tas->backup_state = tas->state;
+		rtw89_tas_state_update(rtwdev, RTW89_TAS_STATE_STATIC_SAR);
+	} else {
+		rtw89_tas_state_update(rtwdev, tas->backup_state);
+	}
+}
+
+void rtw89_tas_chanctx_cb(struct rtw89_dev *rtwdev,
+			  enum rtw89_chanctx_state state)
+{
+	struct rtw89_tas_info *tas = &rtwdev->tas;
+	s32 cfg;
+
+	if (!rtw89_tas_is_active(rtwdev))
+		return;
+
+	if (!rtw89_tas_query_sar_config(rtwdev, &cfg))
+		return;
+
+	switch (state) {
+	case RTW89_CHANCTX_STATE_MCC_START:
+		tas->pause = true;
+		rtw89_tas_state_update(rtwdev, RTW89_TAS_STATE_STATIC_SAR);
+		break;
+	case RTW89_CHANCTX_STATE_MCC_STOP:
+		tas->pause = false;
+		break;
+	default:
+		break;
+	}
 }
+EXPORT_SYMBOL(rtw89_tas_chanctx_cb);
diff --git a/drivers/net/wireless/realtek/rtw89/sar.h b/drivers/net/wireless/realtek/rtw89/sar.h
index 095277df5a71..0df1661db9a8 100644
--- a/drivers/net/wireless/realtek/rtw89/sar.h
+++ b/drivers/net/wireless/realtek/rtw89/sar.h
@@ -25,7 +25,10 @@ int rtw89_print_tas(struct rtw89_dev *rtwdev, char *buf, size_t bufsz);
 int rtw89_ops_set_sar_specs(struct ieee80211_hw *hw,
 			    const struct cfg80211_sar_specs *sar);
 void rtw89_tas_init(struct rtw89_dev *rtwdev);
-void rtw89_tas_reset(struct rtw89_dev *rtwdev);
+void rtw89_tas_reset(struct rtw89_dev *rtwdev, bool force);
 void rtw89_tas_track(struct rtw89_dev *rtwdev);
+void rtw89_tas_scan(struct rtw89_dev *rtwdev, bool start);
+void rtw89_tas_chanctx_cb(struct rtw89_dev *rtwdev,
+			  enum rtw89_chanctx_state state);
 
 #endif
