* [PATCH 1/6] wifi: rtw88: 8812a, 8814a: enable STBC for HT transmissions
2026-10-29 5:52 [PATCH 0/6] wifi: rtw88: extend 88xxA support Ho Jie Feng
@ 2026-10-29 5:52 ` Ho Jie Feng
2026-10-29 5:52 ` [PATCH 2/6] wifi: rtw88: 88xxa: receive VHT SU beamformed transmissions Ho Jie Feng
` (4 subsequent siblings)
5 siblings, 0 replies; 9+ messages in thread
From: Ho Jie Feng @ 2026-10-29 5:52 UTC (permalink / raw)
To: Ping-Ke Shih; +Cc: linux-wireless, Ho Jie Feng, linux-kernel
8812a and 8814a support STBC, enable it.
Signed-off-by: Ho Jie Feng <hjf3108@gmail.com>
---
drivers/net/wireless/realtek/rtw88/main.c | 2 +-
drivers/net/wireless/realtek/rtw88/rtw8812a.c | 1 +
drivers/net/wireless/realtek/rtw88/rtw8814a.c | 1 +
3 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/drivers/net/wireless/realtek/rtw88/main.c b/drivers/net/wireless/realtek/rtw88/main.c
index 0f23498b5c96e..f7d7b66844862 100644
--- a/drivers/net/wireless/realtek/rtw88/main.c
+++ b/drivers/net/wireless/realtek/rtw88/main.c
@@ -1585,7 +1585,7 @@ static void rtw_init_ht_cap(struct rtw_dev *rtwdev,
if (rtw_chip_has_rx_ldpc(rtwdev))
ht_cap->cap |= IEEE80211_HT_CAP_LDPC_CODING;
- if (rtw_chip_has_tx_stbc(rtwdev))
+ if (rtw_chip_has_tx_stbc(rtwdev) && rtwdev->hal.rf_path_num > 1)
ht_cap->cap |= IEEE80211_HT_CAP_TX_STBC;
if (efuse->hw_cap.bw & BIT(RTW_CHANNEL_WIDTH_40))
diff --git a/drivers/net/wireless/realtek/rtw88/rtw8812a.c b/drivers/net/wireless/realtek/rtw88/rtw8812a.c
index 781b66ccde675..55f1d3d3240be 100644
--- a/drivers/net/wireless/realtek/rtw88/rtw8812a.c
+++ b/drivers/net/wireless/realtek/rtw88/rtw8812a.c
@@ -1077,6 +1077,7 @@ const struct rtw_chip_info rtw8812a_hw_spec = {
.rfe_defs = rtw8812a_rfe_defs,
.rfe_defs_size = ARRAY_SIZE(rtw8812a_rfe_defs),
.rx_ldpc = false,
+ .tx_stbc = true,
.amsdu_in_ampdu = true,
.hw_feature_report = false,
.c2h_ra_report_size = 4,
diff --git a/drivers/net/wireless/realtek/rtw88/rtw8814a.c b/drivers/net/wireless/realtek/rtw88/rtw8814a.c
index fa02aa299b5e9..5bb55606f14a1 100644
--- a/drivers/net/wireless/realtek/rtw88/rtw8814a.c
+++ b/drivers/net/wireless/realtek/rtw88/rtw8814a.c
@@ -2200,6 +2200,7 @@ const struct rtw_chip_info rtw8814a_hw_spec = {
.txgi_factor = 1,
.is_pwr_by_rate_dec = true,
.rx_ldpc = true,
+ .tx_stbc = true,
.max_power_index = 0x3f,
.ampdu_density = IEEE80211_HT_MPDU_DENSITY_2,
.amsdu_in_ampdu = false, /* RX speed is better without AMSDU */
--
2.55.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* [PATCH 2/6] wifi: rtw88: 88xxa: receive VHT SU beamformed transmissions
2026-10-29 5:52 [PATCH 0/6] wifi: rtw88: extend 88xxA support Ho Jie Feng
2026-10-29 5:52 ` [PATCH 1/6] wifi: rtw88: 8812a, 8814a: enable STBC for HT transmissions Ho Jie Feng
@ 2026-10-29 5:52 ` Ho Jie Feng
2026-10-29 12:33 ` Bitterblue Smith
2026-10-29 5:52 ` [PATCH 3/6] wifi: rtw88: prepare frequency-offset correction for more chips Ho Jie Feng
` (3 subsequent siblings)
5 siblings, 1 reply; 9+ messages in thread
From: Ho Jie Feng @ 2026-10-29 5:52 UTC (permalink / raw)
To: Ping-Ke Shih; +Cc: linux-wireless, Ho Jie Feng, linux-kernel
Implement beamformee support for 88xxa since they have
support for it.
Signed-off-by: Ho Jie Feng <hjf3108@gmail.com>
---
drivers/net/wireless/realtek/rtw88/bf.h | 2 +
drivers/net/wireless/realtek/rtw88/main.c | 12 ++--
drivers/net/wireless/realtek/rtw88/reg.h | 1 +
drivers/net/wireless/realtek/rtw88/rtw8812a.c | 3 +-
drivers/net/wireless/realtek/rtw88/rtw8814a.c | 36 ++++++++++-
drivers/net/wireless/realtek/rtw88/rtw8821a.c | 3 +-
drivers/net/wireless/realtek/rtw88/rtw88xxa.c | 62 +++++++++++++++++++
drivers/net/wireless/realtek/rtw88/rtw88xxa.h | 2 +
8 files changed, 112 insertions(+), 9 deletions(-)
diff --git a/drivers/net/wireless/realtek/rtw88/bf.h b/drivers/net/wireless/realtek/rtw88/bf.h
index a5d3010e6be6e..0aa78fb6a7e64 100644
--- a/drivers/net/wireless/realtek/rtw88/bf.h
+++ b/drivers/net/wireless/realtek/rtw88/bf.h
@@ -12,6 +12,8 @@
#define REG_ASSOCIATED_BFMER0_INFO 0x06E4
#define REG_ASSOCIATED_BFMER1_INFO 0x06EC
#define REG_TX_CSI_RPT_PARAM_BW20 0x06F4
+#define REG_TX_CSI_RPT_PARAM_BW40 0x06F8
+#define REG_TX_CSI_RPT_PARAM_BW80 0x06FC
#define REG_SND_PTCL_CTRL 0x0718
#define BIT_DIS_CHK_VHTSIGB_CRC BIT(6)
#define BIT_DIS_CHK_VHTSIGA_CRC BIT(5)
diff --git a/drivers/net/wireless/realtek/rtw88/main.c b/drivers/net/wireless/realtek/rtw88/main.c
index f7d7b66844862..0a1b5575b7299 100644
--- a/drivers/net/wireless/realtek/rtw88/main.c
+++ b/drivers/net/wireless/realtek/rtw88/main.c
@@ -1605,6 +1605,7 @@ static void rtw_init_ht_cap(struct rtw_dev *rtwdev,
static void rtw_init_vht_cap(struct rtw_dev *rtwdev,
struct ieee80211_sta_vht_cap *vht_cap)
{
+ const struct rtw_chip_info *chip = rtwdev->chip;
struct rtw_efuse *efuse = &rtwdev->efuse;
u16 mcs_map = 0;
__le16 highest;
@@ -1623,10 +1624,13 @@ static void rtw_init_vht_cap(struct rtw_dev *rtwdev,
0;
if (rtwdev->hal.rf_path_num > 1)
vht_cap->cap |= IEEE80211_VHT_CAP_TXSTBC;
- vht_cap->cap |= IEEE80211_VHT_CAP_MU_BEAMFORMEE_CAPABLE |
- IEEE80211_VHT_CAP_SU_BEAMFORMEE_CAPABLE;
- vht_cap->cap |= (rtwdev->hal.bfee_sts_cap <<
- IEEE80211_VHT_CAP_BEAMFORMEE_STS_SHIFT);
+ if (rtw_bf_support && chip->bfer_su_max_num) {
+ vht_cap->cap |= IEEE80211_VHT_CAP_SU_BEAMFORMEE_CAPABLE;
+ vht_cap->cap |= rtwdev->hal.bfee_sts_cap <<
+ IEEE80211_VHT_CAP_BEAMFORMEE_STS_SHIFT;
+ }
+ if (rtw_bf_support && chip->bfer_mu_max_num)
+ vht_cap->cap |= IEEE80211_VHT_CAP_MU_BEAMFORMEE_CAPABLE;
if (rtw_chip_has_rx_ldpc(rtwdev))
vht_cap->cap |= IEEE80211_VHT_CAP_RXLDPC;
diff --git a/drivers/net/wireless/realtek/rtw88/reg.h b/drivers/net/wireless/realtek/rtw88/reg.h
index afcdb66fdb9a1..f8ec1e2931f63 100644
--- a/drivers/net/wireless/realtek/rtw88/reg.h
+++ b/drivers/net/wireless/realtek/rtw88/reg.h
@@ -671,6 +671,7 @@
#define REG_IQK_COM96 0x0984
#define REG_FAS 0x09a4
+#define REG_CSI_CONTENT_VALUE 0x09b4
#define REG_RXSB 0x0a00
#define BIT_RXSB_ANA_DIV BIT(15)
#define REG_CCK_RX 0x0a04
diff --git a/drivers/net/wireless/realtek/rtw88/rtw8812a.c b/drivers/net/wireless/realtek/rtw88/rtw8812a.c
index 55f1d3d3240be..5d58dff4fcee7 100644
--- a/drivers/net/wireless/realtek/rtw88/rtw8812a.c
+++ b/drivers/net/wireless/realtek/rtw88/rtw8812a.c
@@ -931,7 +931,7 @@ static const struct rtw_chip_ops rtw8812a_ops = {
.phy_calibration = rtw8812a_phy_calibration,
.cck_pd_set = rtw_phy_cck_pd_set,
.pwr_track = rtw8812a_pwr_track,
- .config_bfee = NULL,
+ .config_bfee = rtw88xxa_bf_config_bfee,
.set_gid_table = NULL,
.cfg_csi_rate = NULL,
.led_set = rtw8812a_led_set,
@@ -1084,6 +1084,7 @@ const struct rtw_chip_info rtw8812a_hw_spec = {
.old_datarate_fb_limit = true,
.usb_tx_agg_desc_num = 1,
.iqk_threshold = 8,
+ .bfer_su_max_num = 2,
.ampdu_density = IEEE80211_HT_MPDU_DENSITY_16,
.max_scan_ie_len = IEEE80211_MAX_DATA_LEN,
diff --git a/drivers/net/wireless/realtek/rtw88/rtw8814a.c b/drivers/net/wireless/realtek/rtw88/rtw8814a.c
index 5bb55606f14a1..2db05a83766d6 100644
--- a/drivers/net/wireless/realtek/rtw88/rtw8814a.c
+++ b/drivers/net/wireless/realtek/rtw88/rtw8814a.c
@@ -4,6 +4,7 @@
#include <linux/usb.h>
#include "main.h"
+#include "bf.h"
#include "coex.h"
#include "tx.h"
#include "phy.h"
@@ -115,10 +116,13 @@ static void rtw8814a_init_hwcap(struct rtw_dev *rtwdev)
BIT(RTW_CHANNEL_WIDTH_80);
efuse->hw_cap.ptcl = EFUSE_HW_CAP_PTCL_VHT;
- if (hal->rf_type == RF_3T3R)
+ if (hal->rf_type == RF_3T3R) {
efuse->hw_cap.nss = 3;
- else
+ hal->bfee_sts_cap = 2;
+ } else {
efuse->hw_cap.nss = 2;
+ hal->bfee_sts_cap = 1;
+ }
rtw_dbg(rtwdev, RTW_DBG_EFUSE,
"hw cap: hci=0x%02x, bw=0x%02x, ptcl=0x%02x, ant_num=%d, nss=%d\n",
@@ -2039,6 +2043,31 @@ static void rtw8814a_led_set(struct led_classdev *led,
rtw_write32(rtwdev, REG_GPIO_PIN_CTRL_2, led_gpio_cfg);
}
+#define RTW_NDP_RX_STANDBY_TIME_8814A 0x40
+#define RTW_SND_CTRL_SOUNDING_8814A 0xDB
+
+static void rtw8814a_bf_enable_bfee_su(struct rtw_dev *rtwdev,
+ struct rtw_vif *vif,
+ struct rtw_bfee *bfee)
+{
+ rtw_bf_enable_bfee_su(rtwdev, vif, bfee);
+
+ rtw_write16(rtwdev, REG_SND_PTCL_CTRL + 1, 0x0202);
+ rtw_write8(rtwdev, REG_SND_PTCL_CTRL, RTW_SND_CTRL_SOUNDING_8814A);
+ rtw_write8(rtwdev, REG_SND_PTCL_CTRL + 3,
+ RTW_NDP_RX_STANDBY_TIME_8814A);
+}
+
+static void rtw8814a_bf_config_bfee(struct rtw_dev *rtwdev,
+ struct rtw_vif *vif,
+ struct rtw_bfee *bfee, bool enable)
+{
+ if (enable)
+ rtw8814a_bf_enable_bfee_su(rtwdev, vif, bfee);
+ else
+ rtw_bf_remove_bfee_su(rtwdev, bfee);
+}
+
static void rtw8814a_fill_txdesc_checksum(struct rtw_dev *rtwdev,
struct rtw_tx_pkt_info *pkt_info,
u8 *txdesc)
@@ -2068,7 +2097,7 @@ static const struct rtw_chip_ops rtw8814a_ops = {
.phy_calibration = rtw8814a_phy_calibration,
.cck_pd_set = rtw8814a_phy_cck_pd_set,
.pwr_track = rtw8814a_pwr_track,
- .config_bfee = NULL,
+ .config_bfee = rtw8814a_bf_config_bfee,
.set_gid_table = NULL,
.cfg_csi_rate = NULL,
.led_set = rtw8814a_led_set,
@@ -2231,6 +2260,7 @@ const struct rtw_chip_info rtw8814a_hw_spec = {
.rfe_defs = rtw8814a_rfe_defs,
.rfe_defs_size = ARRAY_SIZE(rtw8814a_rfe_defs),
.iqk_threshold = 8,
+ .bfer_su_max_num = 2,
.max_scan_ie_len = IEEE80211_MAX_DATA_LEN,
.coex_para_ver = 0,
diff --git a/drivers/net/wireless/realtek/rtw88/rtw8821a.c b/drivers/net/wireless/realtek/rtw88/rtw8821a.c
index 6096d3b8e41be..6d106ae70d914 100644
--- a/drivers/net/wireless/realtek/rtw88/rtw8821a.c
+++ b/drivers/net/wireless/realtek/rtw88/rtw8821a.c
@@ -877,7 +877,7 @@ static const struct rtw_chip_ops rtw8821a_ops = {
.phy_calibration = rtw8821a_phy_calibration,
.cck_pd_set = rtw_phy_cck_pd_set,
.pwr_track = rtw8821a_pwr_track,
- .config_bfee = NULL,
+ .config_bfee = rtw88xxa_bf_config_bfee,
.set_gid_table = NULL,
.cfg_csi_rate = NULL,
.led_set = rtw8821a_led_set,
@@ -1188,6 +1188,7 @@ const struct rtw_chip_info rtw8821a_hw_spec = {
.old_datarate_fb_limit = true,
.usb_tx_agg_desc_num = 6,
.iqk_threshold = 8,
+ .bfer_su_max_num = 2,
.ampdu_density = IEEE80211_HT_MPDU_DENSITY_16,
.max_scan_ie_len = IEEE80211_MAX_DATA_LEN,
diff --git a/drivers/net/wireless/realtek/rtw88/rtw88xxa.c b/drivers/net/wireless/realtek/rtw88/rtw88xxa.c
index c298fa0fb2a6b..d3dd0ebfb4024 100644
--- a/drivers/net/wireless/realtek/rtw88/rtw88xxa.c
+++ b/drivers/net/wireless/realtek/rtw88/rtw88xxa.c
@@ -138,6 +138,7 @@ static void rtw88xxa_read_usb_type(struct rtw_dev *rtwdev)
efuse->hw_cap.nss = 1;
else
efuse->hw_cap.nss = 2;
+ hal->bfee_sts_cap = 1;
if (rtwdev->chip->id == RTW_CHIP_TYPE_8821A)
goto print_hw_cap;
@@ -197,6 +198,67 @@ static void rtw88xxa_read_usb_type(struct rtw_dev *rtwdev)
efuse->hw_cap.ant_num, efuse->hw_cap.nss);
}
+#define RTW_NDP_RX_STANDBY_TIME_88XXA 0x50
+#define RTW_SND_CTRL_REMOVE_88XXA 0xC8
+#define RTW_SND_CTRL_SOUNDING_88XXA 0xCB
+
+void rtw88xxa_bf_config_bfee(struct rtw_dev *rtwdev, struct rtw_vif *vif,
+ struct rtw_bfee *bfee, bool enable)
+{
+ static const u32 csi_rpt_regs[] = {
+ REG_TX_CSI_RPT_PARAM_BW20,
+ REG_TX_CSI_RPT_PARAM_BW40,
+ REG_TX_CSI_RPT_PARAM_BW80,
+ };
+ struct rtw_bf_info *bf_info = &rtwdev->bf_info;
+ u8 slot = bfee->su_reg_index;
+ u32 addr_bfer_info;
+ u32 csi_param;
+ int i;
+
+ if (bfee->role != RTW_BFEE_SU || slot >= 2)
+ return;
+
+ addr_bfer_info = slot ? REG_ASSOCIATED_BFMER1_INFO :
+ REG_ASSOCIATED_BFMER0_INFO;
+
+ rtw_dbg(rtwdev, RTW_DBG_BF, "chip %d SU beamformee %s slot %u\n",
+ rtwdev->chip->id, enable ? "enable" : "disable", slot);
+
+ if (!enable) {
+ rtw_write32(rtwdev, addr_bfer_info, 0);
+ rtw_write16(rtwdev, addr_bfer_info + 4, 0);
+
+ clear_bit(slot, bf_info->bfer_su_reg_maping);
+ bfee->su_reg_index = 0xff;
+
+ if (!bf_info->bfer_su_cnt)
+ rtw_write8(rtwdev, REG_SND_PTCL_CTRL,
+ RTW_SND_CTRL_REMOVE_88XXA);
+ return;
+ }
+
+ if (hweight8(rtwdev->hal.antenna_rx) == 2) {
+ rtw_write32(rtwdev, REG_CSI_CONTENT_VALUE, 0);
+ csi_param = 0x01090109;
+ } else {
+ rtw_write32(rtwdev, REG_CSI_CONTENT_VALUE, 0x01081008);
+ csi_param = 0x01080108;
+ }
+
+ rtw_write8(rtwdev, REG_SND_PTCL_CTRL,
+ RTW_SND_CTRL_SOUNDING_88XXA);
+ for (i = 0; i < ETH_ALEN; i++)
+ rtw_write8(rtwdev, addr_bfer_info + i, bfee->mac_addr[i]);
+
+ for (i = 0; i < ARRAY_SIZE(csi_rpt_regs); i++)
+ rtw_write32(rtwdev, csi_rpt_regs[i], csi_param);
+
+ rtw_write8(rtwdev, REG_SND_PTCL_CTRL + 3,
+ RTW_NDP_RX_STANDBY_TIME_88XXA);
+}
+EXPORT_SYMBOL(rtw88xxa_bf_config_bfee);
+
int rtw88xxa_read_efuse(struct rtw_dev *rtwdev, u8 *log_map)
{
const struct rtw_chip_info *chip = rtwdev->chip;
diff --git a/drivers/net/wireless/realtek/rtw88/rtw88xxa.h b/drivers/net/wireless/realtek/rtw88/rtw88xxa.h
index d9c8db58909ca..0aaf7aa4aafdd 100644
--- a/drivers/net/wireless/realtek/rtw88/rtw88xxa.h
+++ b/drivers/net/wireless/realtek/rtw88/rtw88xxa.h
@@ -170,5 +170,7 @@ bool rtw88xxa_iqk_finish(int average, int threshold,
void rtw88xxa_phy_pwrtrack(struct rtw_dev *rtwdev,
void (*do_lck)(struct rtw_dev *rtwdev),
void (*do_iqk)(struct rtw_dev *rtwdev));
+void rtw88xxa_bf_config_bfee(struct rtw_dev *rtwdev, struct rtw_vif *vif,
+ struct rtw_bfee *bfee, bool enable);
#endif
--
2.55.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* Re: [PATCH 2/6] wifi: rtw88: 88xxa: receive VHT SU beamformed transmissions
2026-10-29 5:52 ` [PATCH 2/6] wifi: rtw88: 88xxa: receive VHT SU beamformed transmissions Ho Jie Feng
@ 2026-10-29 12:33 ` Bitterblue Smith
2026-10-29 14:13 ` Ho Jie Feng
0 siblings, 1 reply; 9+ messages in thread
From: Bitterblue Smith @ 2026-10-29 12:33 UTC (permalink / raw)
To: linux-wireless
On 29/10/2026 07:52, Ho Jie Feng wrote:
> Implement beamformee support for 88xxa since they have
> support for it.
>
> Signed-off-by: Ho Jie Feng <hjf3108@gmail.com>
> ---
> drivers/net/wireless/realtek/rtw88/bf.h | 2 +
> drivers/net/wireless/realtek/rtw88/main.c | 12 ++--
> drivers/net/wireless/realtek/rtw88/reg.h | 1 +
> drivers/net/wireless/realtek/rtw88/rtw8812a.c | 3 +-
> drivers/net/wireless/realtek/rtw88/rtw8814a.c | 36 ++++++++++-
> drivers/net/wireless/realtek/rtw88/rtw8821a.c | 3 +-
> drivers/net/wireless/realtek/rtw88/rtw88xxa.c | 62 +++++++++++++++++++
> drivers/net/wireless/realtek/rtw88/rtw88xxa.h | 2 +
> 8 files changed, 112 insertions(+), 9 deletions(-)
>
Is it really this simple? The vendor driver has a lot more
beamforming code than this.
> diff --git a/drivers/net/wireless/realtek/rtw88/bf.h b/drivers/net/wireless/realtek/rtw88/bf.h
> index a5d3010e6be6e..0aa78fb6a7e64 100644
> --- a/drivers/net/wireless/realtek/rtw88/bf.h
> +++ b/drivers/net/wireless/realtek/rtw88/bf.h
> @@ -12,6 +12,8 @@
> #define REG_ASSOCIATED_BFMER0_INFO 0x06E4
> #define REG_ASSOCIATED_BFMER1_INFO 0x06EC
> #define REG_TX_CSI_RPT_PARAM_BW20 0x06F4
> +#define REG_TX_CSI_RPT_PARAM_BW40 0x06F8
> +#define REG_TX_CSI_RPT_PARAM_BW80 0x06FC
> #define REG_SND_PTCL_CTRL 0x0718
> #define BIT_DIS_CHK_VHTSIGB_CRC BIT(6)
> #define BIT_DIS_CHK_VHTSIGA_CRC BIT(5)
> diff --git a/drivers/net/wireless/realtek/rtw88/main.c b/drivers/net/wireless/realtek/rtw88/main.c
> index f7d7b66844862..0a1b5575b7299 100644
> --- a/drivers/net/wireless/realtek/rtw88/main.c
> +++ b/drivers/net/wireless/realtek/rtw88/main.c
> @@ -1605,6 +1605,7 @@ static void rtw_init_ht_cap(struct rtw_dev *rtwdev,
> static void rtw_init_vht_cap(struct rtw_dev *rtwdev,
> struct ieee80211_sta_vht_cap *vht_cap)
> {
> + const struct rtw_chip_info *chip = rtwdev->chip;
> struct rtw_efuse *efuse = &rtwdev->efuse;
> u16 mcs_map = 0;
> __le16 highest;
> @@ -1623,10 +1624,13 @@ static void rtw_init_vht_cap(struct rtw_dev *rtwdev,
> 0;
> if (rtwdev->hal.rf_path_num > 1)
> vht_cap->cap |= IEEE80211_VHT_CAP_TXSTBC;
> - vht_cap->cap |= IEEE80211_VHT_CAP_MU_BEAMFORMEE_CAPABLE |
> - IEEE80211_VHT_CAP_SU_BEAMFORMEE_CAPABLE;
> - vht_cap->cap |= (rtwdev->hal.bfee_sts_cap <<
> - IEEE80211_VHT_CAP_BEAMFORMEE_STS_SHIFT);
> + if (rtw_bf_support && chip->bfer_su_max_num) {
> + vht_cap->cap |= IEEE80211_VHT_CAP_SU_BEAMFORMEE_CAPABLE;
> + vht_cap->cap |= rtwdev->hal.bfee_sts_cap <<
> + IEEE80211_VHT_CAP_BEAMFORMEE_STS_SHIFT;
> + }
> + if (rtw_bf_support && chip->bfer_mu_max_num)
> + vht_cap->cap |= IEEE80211_VHT_CAP_MU_BEAMFORMEE_CAPABLE;
>
> if (rtw_chip_has_rx_ldpc(rtwdev))
> vht_cap->cap |= IEEE80211_VHT_CAP_RXLDPC;
This could be a separate patch, since it doesn't look related to 88xxa.
> diff --git a/drivers/net/wireless/realtek/rtw88/reg.h b/drivers/net/wireless/realtek/rtw88/reg.h
> index afcdb66fdb9a1..f8ec1e2931f63 100644
> --- a/drivers/net/wireless/realtek/rtw88/reg.h
> +++ b/drivers/net/wireless/realtek/rtw88/reg.h
> @@ -671,6 +671,7 @@
> #define REG_IQK_COM96 0x0984
>
> #define REG_FAS 0x09a4
> +#define REG_CSI_CONTENT_VALUE 0x09b4
> #define REG_RXSB 0x0a00
> #define BIT_RXSB_ANA_DIV BIT(15)
> #define REG_CCK_RX 0x0a04
> diff --git a/drivers/net/wireless/realtek/rtw88/rtw8812a.c b/drivers/net/wireless/realtek/rtw88/rtw8812a.c
> index 55f1d3d3240be..5d58dff4fcee7 100644
> --- a/drivers/net/wireless/realtek/rtw88/rtw8812a.c
> +++ b/drivers/net/wireless/realtek/rtw88/rtw8812a.c
> @@ -931,7 +931,7 @@ static const struct rtw_chip_ops rtw8812a_ops = {
> .phy_calibration = rtw8812a_phy_calibration,
> .cck_pd_set = rtw_phy_cck_pd_set,
> .pwr_track = rtw8812a_pwr_track,
> - .config_bfee = NULL,
> + .config_bfee = rtw88xxa_bf_config_bfee,
> .set_gid_table = NULL,
> .cfg_csi_rate = NULL,
> .led_set = rtw8812a_led_set,
> @@ -1084,6 +1084,7 @@ const struct rtw_chip_info rtw8812a_hw_spec = {
> .old_datarate_fb_limit = true,
> .usb_tx_agg_desc_num = 1,
> .iqk_threshold = 8,
> + .bfer_su_max_num = 2,
> .ampdu_density = IEEE80211_HT_MPDU_DENSITY_16,
> .max_scan_ie_len = IEEE80211_MAX_DATA_LEN,
>
> diff --git a/drivers/net/wireless/realtek/rtw88/rtw8814a.c b/drivers/net/wireless/realtek/rtw88/rtw8814a.c
> index 5bb55606f14a1..2db05a83766d6 100644
> --- a/drivers/net/wireless/realtek/rtw88/rtw8814a.c
> +++ b/drivers/net/wireless/realtek/rtw88/rtw8814a.c
> @@ -4,6 +4,7 @@
>
> #include <linux/usb.h>
> #include "main.h"
> +#include "bf.h"
> #include "coex.h"
> #include "tx.h"
> #include "phy.h"
> @@ -115,10 +116,13 @@ static void rtw8814a_init_hwcap(struct rtw_dev *rtwdev)
> BIT(RTW_CHANNEL_WIDTH_80);
> efuse->hw_cap.ptcl = EFUSE_HW_CAP_PTCL_VHT;
>
> - if (hal->rf_type == RF_3T3R)
> + if (hal->rf_type == RF_3T3R) {
> efuse->hw_cap.nss = 3;
> - else
> + hal->bfee_sts_cap = 2;
> + } else {
> efuse->hw_cap.nss = 2;
> + hal->bfee_sts_cap = 1;
> + }
>
> rtw_dbg(rtwdev, RTW_DBG_EFUSE,
> "hw cap: hci=0x%02x, bw=0x%02x, ptcl=0x%02x, ant_num=%d, nss=%d\n",
> @@ -2039,6 +2043,31 @@ static void rtw8814a_led_set(struct led_classdev *led,
> rtw_write32(rtwdev, REG_GPIO_PIN_CTRL_2, led_gpio_cfg);
> }
>
> +#define RTW_NDP_RX_STANDBY_TIME_8814A 0x40
> +#define RTW_SND_CTRL_SOUNDING_8814A 0xDB
> +
> +static void rtw8814a_bf_enable_bfee_su(struct rtw_dev *rtwdev,
> + struct rtw_vif *vif,
> + struct rtw_bfee *bfee)
> +{
> + rtw_bf_enable_bfee_su(rtwdev, vif, bfee);
> +
> + rtw_write16(rtwdev, REG_SND_PTCL_CTRL + 1, 0x0202);
> + rtw_write8(rtwdev, REG_SND_PTCL_CTRL, RTW_SND_CTRL_SOUNDING_8814A);
> + rtw_write8(rtwdev, REG_SND_PTCL_CTRL + 3,
> + RTW_NDP_RX_STANDBY_TIME_8814A);
> +}
> +
> +static void rtw8814a_bf_config_bfee(struct rtw_dev *rtwdev,
> + struct rtw_vif *vif,
> + struct rtw_bfee *bfee, bool enable)
> +{
> + if (enable)
> + rtw8814a_bf_enable_bfee_su(rtwdev, vif, bfee);
> + else
> + rtw_bf_remove_bfee_su(rtwdev, bfee);
> +}
> +
> static void rtw8814a_fill_txdesc_checksum(struct rtw_dev *rtwdev,
> struct rtw_tx_pkt_info *pkt_info,
> u8 *txdesc)
> @@ -2068,7 +2097,7 @@ static const struct rtw_chip_ops rtw8814a_ops = {
> .phy_calibration = rtw8814a_phy_calibration,
> .cck_pd_set = rtw8814a_phy_cck_pd_set,
> .pwr_track = rtw8814a_pwr_track,
> - .config_bfee = NULL,
> + .config_bfee = rtw8814a_bf_config_bfee,
> .set_gid_table = NULL,
> .cfg_csi_rate = NULL,
> .led_set = rtw8814a_led_set,
> @@ -2231,6 +2260,7 @@ const struct rtw_chip_info rtw8814a_hw_spec = {
> .rfe_defs = rtw8814a_rfe_defs,
> .rfe_defs_size = ARRAY_SIZE(rtw8814a_rfe_defs),
> .iqk_threshold = 8,
> + .bfer_su_max_num = 2,
> .max_scan_ie_len = IEEE80211_MAX_DATA_LEN,
>
> .coex_para_ver = 0,
> diff --git a/drivers/net/wireless/realtek/rtw88/rtw8821a.c b/drivers/net/wireless/realtek/rtw88/rtw8821a.c
> index 6096d3b8e41be..6d106ae70d914 100644
> --- a/drivers/net/wireless/realtek/rtw88/rtw8821a.c
> +++ b/drivers/net/wireless/realtek/rtw88/rtw8821a.c
> @@ -877,7 +877,7 @@ static const struct rtw_chip_ops rtw8821a_ops = {
> .phy_calibration = rtw8821a_phy_calibration,
> .cck_pd_set = rtw_phy_cck_pd_set,
> .pwr_track = rtw8821a_pwr_track,
> - .config_bfee = NULL,
> + .config_bfee = rtw88xxa_bf_config_bfee,
> .set_gid_table = NULL,
> .cfg_csi_rate = NULL,
> .led_set = rtw8821a_led_set,
> @@ -1188,6 +1188,7 @@ const struct rtw_chip_info rtw8821a_hw_spec = {
> .old_datarate_fb_limit = true,
> .usb_tx_agg_desc_num = 6,
> .iqk_threshold = 8,
> + .bfer_su_max_num = 2,
> .ampdu_density = IEEE80211_HT_MPDU_DENSITY_16,
> .max_scan_ie_len = IEEE80211_MAX_DATA_LEN,
>
> diff --git a/drivers/net/wireless/realtek/rtw88/rtw88xxa.c b/drivers/net/wireless/realtek/rtw88/rtw88xxa.c
> index c298fa0fb2a6b..d3dd0ebfb4024 100644
> --- a/drivers/net/wireless/realtek/rtw88/rtw88xxa.c
> +++ b/drivers/net/wireless/realtek/rtw88/rtw88xxa.c
> @@ -138,6 +138,7 @@ static void rtw88xxa_read_usb_type(struct rtw_dev *rtwdev)
> efuse->hw_cap.nss = 1;
> else
> efuse->hw_cap.nss = 2;
> + hal->bfee_sts_cap = 1;
>
> if (rtwdev->chip->id == RTW_CHIP_TYPE_8821A)
> goto print_hw_cap;
> @@ -197,6 +198,67 @@ static void rtw88xxa_read_usb_type(struct rtw_dev *rtwdev)
> efuse->hw_cap.ant_num, efuse->hw_cap.nss);
> }
>
> +#define RTW_NDP_RX_STANDBY_TIME_88XXA 0x50
> +#define RTW_SND_CTRL_REMOVE_88XXA 0xC8
> +#define RTW_SND_CTRL_SOUNDING_88XXA 0xCB
> +
> +void rtw88xxa_bf_config_bfee(struct rtw_dev *rtwdev, struct rtw_vif *vif,
> + struct rtw_bfee *bfee, bool enable)
> +{
> + static const u32 csi_rpt_regs[] = {
> + REG_TX_CSI_RPT_PARAM_BW20,
> + REG_TX_CSI_RPT_PARAM_BW40,
> + REG_TX_CSI_RPT_PARAM_BW80,
> + };
> + struct rtw_bf_info *bf_info = &rtwdev->bf_info;
> + u8 slot = bfee->su_reg_index;
> + u32 addr_bfer_info;
> + u32 csi_param;
> + int i;
> +
> + if (bfee->role != RTW_BFEE_SU || slot >= 2)
> + return;
> +
> + addr_bfer_info = slot ? REG_ASSOCIATED_BFMER1_INFO :
> + REG_ASSOCIATED_BFMER0_INFO;
> +
> + rtw_dbg(rtwdev, RTW_DBG_BF, "chip %d SU beamformee %s slot %u\n",
> + rtwdev->chip->id, enable ? "enable" : "disable", slot);
> +
> + if (!enable) {
> + rtw_write32(rtwdev, addr_bfer_info, 0);
> + rtw_write16(rtwdev, addr_bfer_info + 4, 0);
> +
> + clear_bit(slot, bf_info->bfer_su_reg_maping);
> + bfee->su_reg_index = 0xff;
> +
> + if (!bf_info->bfer_su_cnt)
> + rtw_write8(rtwdev, REG_SND_PTCL_CTRL,
> + RTW_SND_CTRL_REMOVE_88XXA);
> + return;
> + }
> +
> + if (hweight8(rtwdev->hal.antenna_rx) == 2) {
> + rtw_write32(rtwdev, REG_CSI_CONTENT_VALUE, 0);
> + csi_param = 0x01090109;
> + } else {
> + rtw_write32(rtwdev, REG_CSI_CONTENT_VALUE, 0x01081008);
> + csi_param = 0x01080108;
> + }
> +
> + rtw_write8(rtwdev, REG_SND_PTCL_CTRL,
> + RTW_SND_CTRL_SOUNDING_88XXA);
> + for (i = 0; i < ETH_ALEN; i++)
> + rtw_write8(rtwdev, addr_bfer_info + i, bfee->mac_addr[i]);
> +
> + for (i = 0; i < ARRAY_SIZE(csi_rpt_regs); i++)
> + rtw_write32(rtwdev, csi_rpt_regs[i], csi_param);
> +
> + rtw_write8(rtwdev, REG_SND_PTCL_CTRL + 3,
> + RTW_NDP_RX_STANDBY_TIME_88XXA);
> +}
> +EXPORT_SYMBOL(rtw88xxa_bf_config_bfee);
> +
> int rtw88xxa_read_efuse(struct rtw_dev *rtwdev, u8 *log_map)
> {
> const struct rtw_chip_info *chip = rtwdev->chip;
> diff --git a/drivers/net/wireless/realtek/rtw88/rtw88xxa.h b/drivers/net/wireless/realtek/rtw88/rtw88xxa.h
> index d9c8db58909ca..0aaf7aa4aafdd 100644
> --- a/drivers/net/wireless/realtek/rtw88/rtw88xxa.h
> +++ b/drivers/net/wireless/realtek/rtw88/rtw88xxa.h
> @@ -170,5 +170,7 @@ bool rtw88xxa_iqk_finish(int average, int threshold,
> void rtw88xxa_phy_pwrtrack(struct rtw_dev *rtwdev,
> void (*do_lck)(struct rtw_dev *rtwdev),
> void (*do_iqk)(struct rtw_dev *rtwdev));
> +void rtw88xxa_bf_config_bfee(struct rtw_dev *rtwdev, struct rtw_vif *vif,
> + struct rtw_bfee *bfee, bool enable);
>
> #endif
^ permalink raw reply [flat|nested] 9+ messages in thread* Re: [PATCH 2/6] wifi: rtw88: 88xxa: receive VHT SU beamformed transmissions
2026-10-29 12:33 ` Bitterblue Smith
@ 2026-10-29 14:13 ` Ho Jie Feng
0 siblings, 0 replies; 9+ messages in thread
From: Ho Jie Feng @ 2026-10-29 14:13 UTC (permalink / raw)
To: linux-wireless, Bitterblue Smith; +Cc: Ping-Ke Shih
On 29/10/2026 14:33, Bitterblue Smith wrote:
> On 29/10/2026 07:52, Ho Jie Feng wrote:
>> Implement beamformee support for 88xxa since they have
>> support for it.
>>
>> Signed-off-by: Ho Jie Feng <hjf3108@gmail.com>
>> ---
>> drivers/net/wireless/realtek/rtw88/bf.h | 2 +
>> drivers/net/wireless/realtek/rtw88/main.c | 12 ++--
>> drivers/net/wireless/realtek/rtw88/reg.h | 1 +
>> drivers/net/wireless/realtek/rtw88/rtw8812a.c | 3 +-
>> drivers/net/wireless/realtek/rtw88/rtw8814a.c | 36 ++++++++++-
>> drivers/net/wireless/realtek/rtw88/rtw8821a.c | 3 +-
>> drivers/net/wireless/realtek/rtw88/rtw88xxa.c | 62 +++++++++++++++++++
>> drivers/net/wireless/realtek/rtw88/rtw88xxa.h | 2 +
>> 8 files changed, 112 insertions(+), 9 deletions(-)
>>
>
> Is it really this simple? The vendor driver has a lot more
> beamforming code than this.
Yes, this is the beamformee code, not the beamformer,
which has way more code than this. The RTL8812AU is acting as
the target for the AP to beamform to for this one.
I have the beamformer code, which the RTL8812AU
beamforms, but I dropped it since it reduces throughput.
I can put the patch out if you wish to see it, but
won't be enabling for 88xxa.
Reference:
https://github.com/aircrack-ng/rtl8812au/blob/v5.6.4.2/hal/phydm/txbf/haltxbfjaguar.c#L229
Vendor driver and rtw88 can be sometimes confusing for
the bfee vs bfer distinction.
>> ...
>> if (rtwdev->hal.rf_path_num > 1)
>> vht_cap->cap |= IEEE80211_VHT_CAP_TXSTBC;
>> - vht_cap->cap |= IEEE80211_VHT_CAP_MU_BEAMFORMEE_CAPABLE |
>> - IEEE80211_VHT_CAP_SU_BEAMFORMEE_CAPABLE;
>> - vht_cap->cap |= (rtwdev->hal.bfee_sts_cap <<
>> - IEEE80211_VHT_CAP_BEAMFORMEE_STS_SHIFT);
>> + if (rtw_bf_support && chip->bfer_su_max_num) {
>> + vht_cap->cap |= IEEE80211_VHT_CAP_SU_BEAMFORMEE_CAPABLE;
>> + vht_cap->cap |= rtwdev->hal.bfee_sts_cap <<
>> + IEEE80211_VHT_CAP_BEAMFORMEE_STS_SHIFT;
>> + }
>> + if (rtw_bf_support && chip->bfer_mu_max_num)
>> + vht_cap->cap |= IEEE80211_VHT_CAP_MU_BEAMFORMEE_CAPABLE;
>>
>> if (rtw_chip_has_rx_ldpc(rtwdev))
>> vht_cap->cap |= IEEE80211_VHT_CAP_RXLDPC;
>
> This could be a separate patch, since it doesn't look related to 88xxa.
I can put this in v2.
>> ...
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH 3/6] wifi: rtw88: prepare frequency-offset correction for more chips
2026-10-29 5:52 [PATCH 0/6] wifi: rtw88: extend 88xxA support Ho Jie Feng
2026-10-29 5:52 ` [PATCH 1/6] wifi: rtw88: 8812a, 8814a: enable STBC for HT transmissions Ho Jie Feng
2026-10-29 5:52 ` [PATCH 2/6] wifi: rtw88: 88xxa: receive VHT SU beamformed transmissions Ho Jie Feng
@ 2026-10-29 5:52 ` Ho Jie Feng
2026-10-29 5:52 ` [PATCH 4/6] wifi: rtw88: 88xxa: correct carrier-frequency offset Ho Jie Feng
` (2 subsequent siblings)
5 siblings, 0 replies; 9+ messages in thread
From: Ho Jie Feng @ 2026-10-29 5:52 UTC (permalink / raw)
To: Ping-Ke Shih; +Cc: linux-wireless, Ho Jie Feng, linux-kernel
Promote chip specific functions into the common core for cfo tracking.
These become helpers which can be used by the other drivers.
Signed-off-by: Ho Jie Feng <hjf3108@gmail.com>
---
drivers/net/wireless/realtek/rtw88/phy.c | 106 ++++++++++++++++++
drivers/net/wireless/realtek/rtw88/phy.h | 2 +
drivers/net/wireless/realtek/rtw88/rtw8822c.c | 100 +----------------
drivers/net/wireless/realtek/rtw88/rtw8822c.h | 3 -
4 files changed, 113 insertions(+), 98 deletions(-)
diff --git a/drivers/net/wireless/realtek/rtw88/phy.c b/drivers/net/wireless/realtek/rtw88/phy.c
index e2515a662bb30..d0c1fb65be956 100644
--- a/drivers/net/wireless/realtek/rtw88/phy.c
+++ b/drivers/net/wireless/realtek/rtw88/phy.c
@@ -720,6 +720,112 @@ void rtw_phy_parsing_cfo(struct rtw_dev *rtwdev,
}
EXPORT_SYMBOL(rtw_phy_parsing_cfo);
+#define CFO_TRK_ENABLE_TH 20
+#define CFO_TRK_STOP_TH 10
+#define CFO_TRK_ADJ_TH 10
+
+static u8 rtw_phy_cfo_tracking_reset(struct rtw_dev *rtwdev)
+{
+ struct rtw_dm_info *dm_info = &rtwdev->dm_info;
+ struct rtw_cfo_track *cfo = &dm_info->cfo_track;
+
+ cfo->is_adjust = true;
+
+ if (cfo->crystal_cap > rtwdev->efuse.crystal_cap)
+ return cfo->crystal_cap - 1;
+ else if (cfo->crystal_cap < rtwdev->efuse.crystal_cap)
+ return cfo->crystal_cap + 1;
+
+ return cfo->crystal_cap;
+}
+
+void rtw_phy_cfo_xcap_init(struct rtw_dev *rtwdev)
+{
+ struct rtw_dm_info *dm_info = &rtwdev->dm_info;
+ struct rtw_cfo_track *cfo = &dm_info->cfo_track;
+
+ cfo->crystal_cap = rtwdev->efuse.crystal_cap;
+ cfo->is_adjust = true;
+}
+EXPORT_SYMBOL(rtw_phy_cfo_xcap_init);
+
+#define REPORT_TO_KHZ(val) ({typeof(val) _v = (val); (_v << 1) + (_v >> 1); })
+static s32 rtw_phy_cfo_calc_avg(struct rtw_dev *rtwdev, u8 path_num)
+{
+ struct rtw_dm_info *dm_info = &rtwdev->dm_info;
+ struct rtw_cfo_track *cfo = &dm_info->cfo_track;
+ s32 cfo_avg, cfo_path_sum = 0, cfo_rpt_sum;
+ u8 i;
+
+ for (i = 0; i < path_num; i++) {
+ cfo_rpt_sum = REPORT_TO_KHZ(cfo->cfo_tail[i]);
+
+ if (cfo->cfo_cnt[i])
+ cfo_avg = cfo_rpt_sum / cfo->cfo_cnt[i];
+ else
+ cfo_avg = 0;
+
+ cfo_path_sum += cfo_avg;
+ }
+
+ for (i = 0; i < path_num; i++) {
+ cfo->cfo_tail[i] = 0;
+ cfo->cfo_cnt[i] = 0;
+ }
+
+ return cfo_path_sum / path_num;
+}
+
+static void rtw_phy_cfo_need_adjust(struct rtw_dev *rtwdev, s32 cfo_avg)
+{
+ struct rtw_dm_info *dm_info = &rtwdev->dm_info;
+ struct rtw_cfo_track *cfo = &dm_info->cfo_track;
+
+ if (!cfo->is_adjust) {
+ if (abs(cfo_avg) > CFO_TRK_ENABLE_TH)
+ cfo->is_adjust = true;
+ } else {
+ if (abs(cfo_avg) <= CFO_TRK_STOP_TH)
+ cfo->is_adjust = false;
+ }
+}
+
+u8 rtw_phy_cfo_xcap_track(struct rtw_dev *rtwdev, u8 xcap_max, bool bt_hold)
+{
+ struct rtw_dm_info *dm_info = &rtwdev->dm_info;
+ struct rtw_cfo_track *cfo = &dm_info->cfo_track;
+ u8 path_num = rtwdev->hal.rf_path_num;
+ s8 crystal_cap = cfo->crystal_cap;
+ s32 cfo_avg = 0;
+
+ if (rtwdev->sta_cnt != 1)
+ return rtw_phy_cfo_tracking_reset(rtwdev);
+
+ if (cfo->packet_count == cfo->packet_count_pre)
+ return cfo->crystal_cap;
+
+ cfo->packet_count_pre = cfo->packet_count;
+ cfo_avg = rtw_phy_cfo_calc_avg(rtwdev, path_num);
+ rtw_phy_cfo_need_adjust(rtwdev, cfo_avg);
+
+ if (bt_hold) {
+ cfo->is_adjust = false;
+ return rtwdev->efuse.crystal_cap;
+ }
+
+ if (cfo->is_adjust) {
+ if (cfo_avg > CFO_TRK_ADJ_TH)
+ crystal_cap++;
+ else if (cfo_avg < -CFO_TRK_ADJ_TH)
+ crystal_cap--;
+
+ crystal_cap = clamp_t(s8, crystal_cap, 0, xcap_max);
+ }
+
+ return (u8)crystal_cap;
+}
+EXPORT_SYMBOL(rtw_phy_cfo_xcap_track);
+
static void rtw_phy_cfo_track(struct rtw_dev *rtwdev)
{
const struct rtw_chip_info *chip = rtwdev->chip;
diff --git a/drivers/net/wireless/realtek/rtw88/phy.h b/drivers/net/wireless/realtek/rtw88/phy.h
index 8a843aac7350a..b551cb860d625 100644
--- a/drivers/net/wireless/realtek/rtw88/phy.h
+++ b/drivers/net/wireless/realtek/rtw88/phy.h
@@ -68,6 +68,8 @@ void rtw_phy_adaptivity_set_mode(struct rtw_dev *rtwdev);
void rtw_phy_cck_pd_set(struct rtw_dev *rtwdev, u8 new_lvl);
void rtw_phy_parsing_cfo(struct rtw_dev *rtwdev,
struct rtw_rx_pkt_stat *pkt_stat);
+void rtw_phy_cfo_xcap_init(struct rtw_dev *rtwdev);
+u8 rtw_phy_cfo_xcap_track(struct rtw_dev *rtwdev, u8 xcap_max, bool bt_hold);
void rtw_phy_tx_path_diversity(struct rtw_dev *rtwdev);
struct rtw_txpwr_lmt_cfg_pair {
diff --git a/drivers/net/wireless/realtek/rtw88/rtw8822c.c b/drivers/net/wireless/realtek/rtw88/rtw8822c.c
index 32d9709f2c243..54f012a238391 100644
--- a/drivers/net/wireless/realtek/rtw88/rtw8822c.c
+++ b/drivers/net/wireless/realtek/rtw88/rtw8822c.c
@@ -4243,103 +4243,13 @@ static void rtw8822c_set_crystal_cap(struct rtw_dev *rtwdev, u8 crystal_cap)
rtw8822c_set_crystal_cap_reg(rtwdev, crystal_cap);
}
-static void rtw8822c_cfo_tracking_reset(struct rtw_dev *rtwdev)
-{
- struct rtw_dm_info *dm_info = &rtwdev->dm_info;
- struct rtw_cfo_track *cfo = &dm_info->cfo_track;
-
- cfo->is_adjust = true;
-
- if (cfo->crystal_cap > rtwdev->efuse.crystal_cap)
- rtw8822c_set_crystal_cap(rtwdev, cfo->crystal_cap - 1);
- else if (cfo->crystal_cap < rtwdev->efuse.crystal_cap)
- rtw8822c_set_crystal_cap(rtwdev, cfo->crystal_cap + 1);
-}
-
-static void rtw8822c_cfo_init(struct rtw_dev *rtwdev)
-{
- struct rtw_dm_info *dm_info = &rtwdev->dm_info;
- struct rtw_cfo_track *cfo = &dm_info->cfo_track;
-
- cfo->crystal_cap = rtwdev->efuse.crystal_cap;
- cfo->is_adjust = true;
-}
-
-#define REPORT_TO_KHZ(val) ({typeof(val) _v = (val); (_v << 1) + (_v >> 1); })
-static s32 rtw8822c_cfo_calc_avg(struct rtw_dev *rtwdev, u8 path_num)
-{
- struct rtw_dm_info *dm_info = &rtwdev->dm_info;
- struct rtw_cfo_track *cfo = &dm_info->cfo_track;
- s32 cfo_avg, cfo_path_sum = 0, cfo_rpt_sum;
- u8 i;
-
- for (i = 0; i < path_num; i++) {
- cfo_rpt_sum = REPORT_TO_KHZ(cfo->cfo_tail[i]);
-
- if (cfo->cfo_cnt[i])
- cfo_avg = cfo_rpt_sum / cfo->cfo_cnt[i];
- else
- cfo_avg = 0;
-
- cfo_path_sum += cfo_avg;
- }
-
- for (i = 0; i < path_num; i++) {
- cfo->cfo_tail[i] = 0;
- cfo->cfo_cnt[i] = 0;
- }
-
- return cfo_path_sum / path_num;
-}
-
-static void rtw8822c_cfo_need_adjust(struct rtw_dev *rtwdev, s32 cfo_avg)
-{
- struct rtw_dm_info *dm_info = &rtwdev->dm_info;
- struct rtw_cfo_track *cfo = &dm_info->cfo_track;
-
- if (!cfo->is_adjust) {
- if (abs(cfo_avg) > CFO_TRK_ENABLE_TH)
- cfo->is_adjust = true;
- } else {
- if (abs(cfo_avg) <= CFO_TRK_STOP_TH)
- cfo->is_adjust = false;
- }
-
- if (!rtw_coex_disabled(rtwdev)) {
- cfo->is_adjust = false;
- rtw8822c_set_crystal_cap(rtwdev, rtwdev->efuse.crystal_cap);
- }
-}
-
static void rtw8822c_cfo_track(struct rtw_dev *rtwdev)
{
- struct rtw_dm_info *dm_info = &rtwdev->dm_info;
- struct rtw_cfo_track *cfo = &dm_info->cfo_track;
- u8 path_num = rtwdev->hal.rf_path_num;
- s8 crystal_cap = cfo->crystal_cap;
- s32 cfo_avg = 0;
-
- if (rtwdev->sta_cnt != 1) {
- rtw8822c_cfo_tracking_reset(rtwdev);
- return;
- }
-
- if (cfo->packet_count == cfo->packet_count_pre)
- return;
-
- cfo->packet_count_pre = cfo->packet_count;
- cfo_avg = rtw8822c_cfo_calc_avg(rtwdev, path_num);
- rtw8822c_cfo_need_adjust(rtwdev, cfo_avg);
-
- if (cfo->is_adjust) {
- if (cfo_avg > CFO_TRK_ADJ_TH)
- crystal_cap++;
- else if (cfo_avg < -CFO_TRK_ADJ_TH)
- crystal_cap--;
+ u8 crystal_cap;
- crystal_cap = clamp_t(s8, crystal_cap, 0, XCAP_MASK);
- rtw8822c_set_crystal_cap(rtwdev, (u8)crystal_cap);
- }
+ crystal_cap = rtw_phy_cfo_xcap_track(rtwdev, XCAP_MASK,
+ !rtw_coex_disabled(rtwdev));
+ rtw8822c_set_crystal_cap(rtwdev, crystal_cap);
}
static const struct rtw_phy_cck_pd_reg
@@ -4985,7 +4895,7 @@ static const struct rtw_chip_ops rtw8822c_ops = {
.cfg_csi_rate = rtw_bf_cfg_csi_rate,
.adaptivity_init = rtw8822c_adaptivity_init,
.adaptivity = rtw8822c_adaptivity,
- .cfo_init = rtw8822c_cfo_init,
+ .cfo_init = rtw_phy_cfo_xcap_init,
.cfo_track = rtw8822c_cfo_track,
.config_tx_path = rtw8822c_config_tx_path,
.config_txrx_mode = rtw8822c_config_trx_mode,
diff --git a/drivers/net/wireless/realtek/rtw88/rtw8822c.h b/drivers/net/wireless/realtek/rtw88/rtw8822c.h
index fc62b67a15f21..c27e4c394a435 100644
--- a/drivers/net/wireless/realtek/rtw88/rtw8822c.h
+++ b/drivers/net/wireless/realtek/rtw88/rtw8822c.h
@@ -181,9 +181,6 @@ const struct rtw_table name ## _tbl = { \
#define REG_ANAPARLDO_POW_MAC 0x0029
#define BIT_LDOE25_PON BIT(0)
#define XCAP_MASK GENMASK(6, 0)
-#define CFO_TRK_ENABLE_TH 20
-#define CFO_TRK_STOP_TH 10
-#define CFO_TRK_ADJ_TH 10
#define REG_TXDFIR0 0x808
#define REG_DFIRBW 0x810
--
2.55.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* [PATCH 4/6] wifi: rtw88: 88xxa: correct carrier-frequency offset
2026-10-29 5:52 [PATCH 0/6] wifi: rtw88: extend 88xxA support Ho Jie Feng
` (2 preceding siblings ...)
2026-10-29 5:52 ` [PATCH 3/6] wifi: rtw88: prepare frequency-offset correction for more chips Ho Jie Feng
@ 2026-10-29 5:52 ` Ho Jie Feng
2026-10-29 5:52 ` [PATCH 5/6] wifi: rtw88: 88xxa: adapt channel sensing to receiver gain Ho Jie Feng
2026-10-29 5:52 ` [PATCH 6/6] wifi: rtw88: 8812a, 8821a: respect peer A-MPDU size limits Ho Jie Feng
5 siblings, 0 replies; 9+ messages in thread
From: Ho Jie Feng @ 2026-10-29 5:52 UTC (permalink / raw)
To: Ping-Ke Shih; +Cc: linux-wireless, Ho Jie Feng, linux-kernel
Use the newly promoted helpers to implement support for 88xxa
Signed-off-by: Ho Jie Feng <hjf3108@gmail.com>
---
drivers/net/wireless/realtek/rtw88/phy.c | 5 +-
drivers/net/wireless/realtek/rtw88/rtw8812a.c | 2 +
drivers/net/wireless/realtek/rtw88/rtw8814a.c | 31 ++++++++++--
drivers/net/wireless/realtek/rtw88/rtw8821a.c | 2 +
drivers/net/wireless/realtek/rtw88/rtw88xxa.c | 47 +++++++++++++++----
drivers/net/wireless/realtek/rtw88/rtw88xxa.h | 1 +
6 files changed, 73 insertions(+), 15 deletions(-)
diff --git a/drivers/net/wireless/realtek/rtw88/phy.c b/drivers/net/wireless/realtek/rtw88/phy.c
index d0c1fb65be956..69998c0d7c1f8 100644
--- a/drivers/net/wireless/realtek/rtw88/phy.c
+++ b/drivers/net/wireless/realtek/rtw88/phy.c
@@ -687,13 +687,14 @@ static void rtw_phy_parsing_cfo_iter(void *data, u8 *mac,
struct rtw_rx_pkt_stat *pkt_stat = iter_data->pkt_stat;
struct rtw_dm_info *dm_info = &rtwdev->dm_info;
struct rtw_cfo_track *cfo = &dm_info->cfo_track;
+ u8 path_num = min_t(u8, rtwdev->hal.rf_path_num, 2);
u8 *bssid = iter_data->bssid;
u8 i;
if (!ether_addr_equal(vif->bss_conf.bssid, bssid))
return;
- for (i = 0; i < rtwdev->hal.rf_path_num; i++) {
+ for (i = 0; i < path_num; i++) {
cfo->cfo_tail[i] += pkt_stat->cfo_tail[i];
cfo->cfo_cnt[i]++;
}
@@ -794,7 +795,7 @@ u8 rtw_phy_cfo_xcap_track(struct rtw_dev *rtwdev, u8 xcap_max, bool bt_hold)
{
struct rtw_dm_info *dm_info = &rtwdev->dm_info;
struct rtw_cfo_track *cfo = &dm_info->cfo_track;
- u8 path_num = rtwdev->hal.rf_path_num;
+ u8 path_num = min_t(u8, rtwdev->hal.rf_path_num, 2);
s8 crystal_cap = cfo->crystal_cap;
s32 cfo_avg = 0;
diff --git a/drivers/net/wireless/realtek/rtw88/rtw8812a.c b/drivers/net/wireless/realtek/rtw88/rtw8812a.c
index 5d58dff4fcee7..c8008cf43da9d 100644
--- a/drivers/net/wireless/realtek/rtw88/rtw8812a.c
+++ b/drivers/net/wireless/realtek/rtw88/rtw8812a.c
@@ -943,6 +943,8 @@ static const struct rtw_chip_ops rtw8812a_ops = {
.coex_set_rfe_type = rtw8821a_coex_cfg_rfe_type,
.coex_set_wl_tx_power = rtw8821a_coex_cfg_wl_tx_power,
.coex_set_wl_rx_gain = rtw8821a_coex_cfg_wl_rx_gain,
+ .cfo_init = rtw_phy_cfo_xcap_init,
+ .cfo_track = rtw88xxa_cfo_track,
};
static const struct rtw_page_table page_table_8812a[] = {
diff --git a/drivers/net/wireless/realtek/rtw88/rtw8814a.c b/drivers/net/wireless/realtek/rtw88/rtw8814a.c
index 2db05a83766d6..3c65cd838ce15 100644
--- a/drivers/net/wireless/realtek/rtw88/rtw8814a.c
+++ b/drivers/net/wireless/realtek/rtw88/rtw8814a.c
@@ -145,7 +145,7 @@ static int rtw8814a_read_efuse(struct rtw_dev *rtwdev, u8 *log_map)
efuse->usb_mode_switch = u8_get_bits(map->usb_mode, BIT(4));
efuse->rfe_option = map->rfe_option;
efuse->rf_board_option = map->rf_board_option;
- efuse->crystal_cap = map->xtal_k;
+ efuse->crystal_cap = map->xtal_k & 0x3f;
efuse->channel_plan = map->channel_plan;
efuse->country_code[0] = map->country_code[0];
efuse->country_code[1] = map->country_code[1];
@@ -284,9 +284,15 @@ static void rtw8814a_config_cck_rx_antenna_init(struct rtw_dev *rtwdev)
rtw_write32_mask(rtwdev, REG_CCKTX, BIT_CMB_CCA_2R, 1);
}
+static void rtw8814a_set_crystal_cap(struct rtw_dev *rtwdev, u8 crystal_cap)
+{
+ rtw_write32_mask(rtwdev, REG_AFE_CTRL3, 0x07ff8000,
+ crystal_cap | (crystal_cap << 6));
+}
+
static void rtw8814a_phy_set_param(struct rtw_dev *rtwdev)
{
- u32 crystal_cap, val32;
+ u32 val32;
u8 val8, rf_path;
/* power on BB/RF domain */
@@ -308,9 +314,7 @@ static void rtw8814a_phy_set_param(struct rtw_dev *rtwdev)
rtw_load_table(rtwdev, rtwdev->chip->bb_tbl);
rtw_load_table(rtwdev, rtwdev->chip->agc_tbl);
- crystal_cap = rtwdev->efuse.crystal_cap & 0x3F;
- crystal_cap |= crystal_cap << 6;
- rtw_write32_mask(rtwdev, REG_AFE_CTRL3, 0x07ff8000, crystal_cap);
+ rtw8814a_set_crystal_cap(rtwdev, rtwdev->efuse.crystal_cap);
rtw8814a_config_trx_path(rtwdev);
@@ -1269,6 +1273,8 @@ static void rtw8814a_query_phy_status(struct rtw_dev *rtwdev, u8 *phy_status,
pkt_stat->cfo_tail[i] = cfo[i];
dm_info->cfo_tail[i] = (cfo[i] * 5) >> 1;
}
+
+ rtw_phy_parsing_cfo(rtwdev, pkt_stat);
}
}
@@ -2068,6 +2074,19 @@ static void rtw8814a_bf_config_bfee(struct rtw_dev *rtwdev,
rtw_bf_remove_bfee_su(rtwdev, bfee);
}
+static void rtw8814a_cfo_track(struct rtw_dev *rtwdev)
+{
+ struct rtw_cfo_track *cfo = &rtwdev->dm_info.cfo_track;
+ u8 crystal_cap;
+
+ crystal_cap = rtw_phy_cfo_xcap_track(rtwdev, 0x3f, false);
+ if (crystal_cap == cfo->crystal_cap)
+ return;
+
+ cfo->crystal_cap = crystal_cap;
+ rtw8814a_set_crystal_cap(rtwdev, crystal_cap);
+}
+
static void rtw8814a_fill_txdesc_checksum(struct rtw_dev *rtwdev,
struct rtw_tx_pkt_info *pkt_info,
u8 *txdesc)
@@ -2102,6 +2121,8 @@ static const struct rtw_chip_ops rtw8814a_ops = {
.cfg_csi_rate = NULL,
.led_set = rtw8814a_led_set,
.fill_txdesc_checksum = rtw8814a_fill_txdesc_checksum,
+ .cfo_init = rtw_phy_cfo_xcap_init,
+ .cfo_track = rtw8814a_cfo_track,
.coex_set_init = rtw8814a_coex_cfg_init,
.coex_set_ant_switch = rtw8814a_coex_cfg_ant_switch,
diff --git a/drivers/net/wireless/realtek/rtw88/rtw8821a.c b/drivers/net/wireless/realtek/rtw88/rtw8821a.c
index 6d106ae70d914..17d3677ca463c 100644
--- a/drivers/net/wireless/realtek/rtw88/rtw8821a.c
+++ b/drivers/net/wireless/realtek/rtw88/rtw8821a.c
@@ -889,6 +889,8 @@ static const struct rtw_chip_ops rtw8821a_ops = {
.coex_set_rfe_type = rtw8821a_coex_cfg_rfe_type,
.coex_set_wl_tx_power = rtw8821a_coex_cfg_wl_tx_power,
.coex_set_wl_rx_gain = rtw8821a_coex_cfg_wl_rx_gain,
+ .cfo_init = rtw_phy_cfo_xcap_init,
+ .cfo_track = rtw88xxa_cfo_track,
};
static const struct rtw_page_table page_table_8821a[] = {
diff --git a/drivers/net/wireless/realtek/rtw88/rtw88xxa.c b/drivers/net/wireless/realtek/rtw88/rtw88xxa.c
index d3dd0ebfb4024..586bb57a10061 100644
--- a/drivers/net/wireless/realtek/rtw88/rtw88xxa.c
+++ b/drivers/net/wireless/realtek/rtw88/rtw88xxa.c
@@ -279,6 +279,7 @@ int rtw88xxa_read_efuse(struct rtw_dev *rtwdev, u8 *log_map)
efuse->crystal_cap = map->xtal_k;
if (efuse->crystal_cap == 0xff)
efuse->crystal_cap = 0x20;
+ efuse->crystal_cap &= 0x3F;
efuse->pa_type_2g = map->pa_type;
efuse->pa_type_5g = map->pa_type;
efuse->lna_type_2g = map->lna_type_2g;
@@ -632,9 +633,19 @@ static void rtw88xxa_init_beacon_parameters(struct rtw_dev *rtwdev)
rtw_write16(rtwdev, REG_BCNTCFG, 0x4413);
}
+static void rtw88xxa_set_crystal_cap(struct rtw_dev *rtwdev, u8 crystal_cap)
+{
+ if (rtwdev->chip->id == RTW_CHIP_TYPE_8812A)
+ rtw_write32_mask(rtwdev, REG_AFE_CTRL3, 0x7FF80000,
+ crystal_cap | (crystal_cap << 6));
+ else
+ rtw_write32_mask(rtwdev, REG_AFE_CTRL3, 0x00FFF000,
+ crystal_cap | (crystal_cap << 6));
+}
+
static void rtw88xxa_phy_bb_config(struct rtw_dev *rtwdev)
{
- u8 val8, crystal_cap;
+ u8 val8;
/* power on BB/RF domain */
val8 = rtw_read8(rtwdev, REG_SYS_FUNC_EN);
@@ -653,13 +664,7 @@ static void rtw88xxa_phy_bb_config(struct rtw_dev *rtwdev)
rtw_load_table(rtwdev, rtwdev->chip->bb_tbl);
rtw_load_table(rtwdev, rtwdev->chip->agc_tbl);
- crystal_cap = rtwdev->efuse.crystal_cap & 0x3F;
- if (rtwdev->chip->id == RTW_CHIP_TYPE_8812A)
- rtw_write32_mask(rtwdev, REG_AFE_CTRL3, 0x7FF80000,
- crystal_cap | (crystal_cap << 6));
- else
- rtw_write32_mask(rtwdev, REG_AFE_CTRL3, 0x00FFF000,
- crystal_cap | (crystal_cap << 6));
+ rtw88xxa_set_crystal_cap(rtwdev, rtwdev->efuse.crystal_cap & 0x3F);
}
static void rtw88xxa_phy_rf_config(struct rtw_dev *rtwdev)
@@ -1621,6 +1626,17 @@ void rtw88xxa_query_phy_status(struct rtw_dev *rtwdev, u8 *phy_status,
power_b = power_a;
pkt_stat->signal_power = max3(power_a, power_b, min_rx_power);
+
+ pkt_stat->cfo_tail[RF_PATH_A] =
+ (s8)le32_get_bits(rpt->w2, RTW_JGRPHY_W2_CFO_TAIL_A);
+ pkt_stat->cfo_tail[RF_PATH_B] =
+ (s8)le32_get_bits(rpt->w2, RTW_JGRPHY_W2_CFO_TAIL_B);
+
+ /* convert to kHz (used only for debugfs) */
+ for (i = RF_PATH_A; i < rtwdev->hal.rf_path_num; i++)
+ dm_info->cfo_tail[i] = (pkt_stat->cfo_tail[i] * 5) >> 1;
+
+ rtw_phy_parsing_cfo(rtwdev, pkt_stat);
}
}
EXPORT_SYMBOL(rtw88xxa_query_phy_status);
@@ -2009,6 +2025,21 @@ void rtw88xxa_phy_pwrtrack(struct rtw_dev *rtwdev,
}
EXPORT_SYMBOL(rtw88xxa_phy_pwrtrack);
+void rtw88xxa_cfo_track(struct rtw_dev *rtwdev)
+{
+ struct rtw_cfo_track *cfo = &rtwdev->dm_info.cfo_track;
+ bool bt_hold = rtwdev->efuse.btcoex && !rtw_coex_disabled(rtwdev);
+ u8 crystal_cap;
+
+ crystal_cap = rtw_phy_cfo_xcap_track(rtwdev, 0x3F, bt_hold);
+ if (crystal_cap == cfo->crystal_cap)
+ return;
+
+ cfo->crystal_cap = crystal_cap;
+ rtw88xxa_set_crystal_cap(rtwdev, crystal_cap);
+}
+EXPORT_SYMBOL(rtw88xxa_cfo_track);
+
MODULE_AUTHOR("Realtek Corporation");
MODULE_DESCRIPTION("Realtek 802.11ac wireless 8821a/8811a/8812a common code");
MODULE_LICENSE("Dual BSD/GPL");
diff --git a/drivers/net/wireless/realtek/rtw88/rtw88xxa.h b/drivers/net/wireless/realtek/rtw88/rtw88xxa.h
index 0aaf7aa4aafdd..09b72d33f62e1 100644
--- a/drivers/net/wireless/realtek/rtw88/rtw88xxa.h
+++ b/drivers/net/wireless/realtek/rtw88/rtw88xxa.h
@@ -172,5 +172,6 @@ void rtw88xxa_phy_pwrtrack(struct rtw_dev *rtwdev,
void (*do_iqk)(struct rtw_dev *rtwdev));
void rtw88xxa_bf_config_bfee(struct rtw_dev *rtwdev, struct rtw_vif *vif,
struct rtw_bfee *bfee, bool enable);
+void rtw88xxa_cfo_track(struct rtw_dev *rtwdev);
#endif
--
2.55.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* [PATCH 5/6] wifi: rtw88: 88xxa: adapt channel sensing to receiver gain
2026-10-29 5:52 [PATCH 0/6] wifi: rtw88: extend 88xxA support Ho Jie Feng
` (3 preceding siblings ...)
2026-10-29 5:52 ` [PATCH 4/6] wifi: rtw88: 88xxa: correct carrier-frequency offset Ho Jie Feng
@ 2026-10-29 5:52 ` Ho Jie Feng
2026-10-29 5:52 ` [PATCH 6/6] wifi: rtw88: 8812a, 8821a: respect peer A-MPDU size limits Ho Jie Feng
5 siblings, 0 replies; 9+ messages in thread
From: Ho Jie Feng @ 2026-10-29 5:52 UTC (permalink / raw)
To: Ping-Ke Shih; +Cc: linux-wireless, Ho Jie Feng, linux-kernel
Implement adaptivity for 88xxa, since they support it in
the hardware.
Signed-off-by: Ho Jie Feng <hjf3108@gmail.com>
---
drivers/net/wireless/realtek/rtw88/main.h | 1 +
drivers/net/wireless/realtek/rtw88/rtw8812a.c | 10 ++
drivers/net/wireless/realtek/rtw88/rtw8821a.c | 10 ++
drivers/net/wireless/realtek/rtw88/rtw88xxa.c | 123 ++++++++++++++++++
drivers/net/wireless/realtek/rtw88/rtw88xxa.h | 5 +
5 files changed, 149 insertions(+)
diff --git a/drivers/net/wireless/realtek/rtw88/main.h b/drivers/net/wireless/realtek/rtw88/main.h
index d59f6e323adf0..796fea308fdf7 100644
--- a/drivers/net/wireless/realtek/rtw88/main.h
+++ b/drivers/net/wireless/realtek/rtw88/main.h
@@ -1787,6 +1787,7 @@ struct rtw_dm_info {
bool is_bt_iqk_timeout;
s8 l2h_th_ini;
+ s8 l2h_th_lb;
enum rtw_edcca_mode edcca_mode;
u8 scan_density;
};
diff --git a/drivers/net/wireless/realtek/rtw88/rtw8812a.c b/drivers/net/wireless/realtek/rtw88/rtw8812a.c
index c8008cf43da9d..d93c4776e0303 100644
--- a/drivers/net/wireless/realtek/rtw88/rtw8812a.c
+++ b/drivers/net/wireless/realtek/rtw88/rtw8812a.c
@@ -945,6 +945,8 @@ static const struct rtw_chip_ops rtw8812a_ops = {
.coex_set_wl_rx_gain = rtw8821a_coex_cfg_wl_rx_gain,
.cfo_init = rtw_phy_cfo_xcap_init,
.cfo_track = rtw88xxa_cfo_track,
+ .adaptivity_init = rtw88xxa_adaptivity_init,
+ .adaptivity = rtw88xxa_adaptivity,
};
static const struct rtw_page_table page_table_8812a[] = {
@@ -999,6 +1001,11 @@ static const struct rtw_hw_reg rtw8812a_dig[] = {
[1] = { .addr = REG_RXIGI_B, .mask = 0x7f },
};
+static const struct rtw_hw_reg_offset rtw8812a_edcca_th[] = {
+ [EDCCA_TH_L2H_IDX] = {{.addr = 0x8a4, .mask = MASKBYTE0}, .offset = 0},
+ [EDCCA_TH_H2L_IDX] = {{.addr = 0x8a4, .mask = MASKBYTE1}, .offset = 0},
+};
+
static const struct rtw_rfe_def rtw8812a_rfe_defs[] = {
[0] = { .phy_pg_tbl = &rtw8812a_bb_pg_tbl,
.txpwr_lmt_tbl = &rtw8812a_txpwr_lmt_tbl,
@@ -1089,6 +1096,9 @@ const struct rtw_chip_info rtw8812a_hw_spec = {
.bfer_su_max_num = 2,
.ampdu_density = IEEE80211_HT_MPDU_DENSITY_16,
.max_scan_ie_len = IEEE80211_MAX_DATA_LEN,
+ .edcca_th = rtw8812a_edcca_th,
+ .l2h_th_ini_cs = 10,
+ .l2h_th_ini_ad = -17,
.coex_para_ver = 0, /* no coex code in 8812au driver */
.bt_desired_ver = 0,
diff --git a/drivers/net/wireless/realtek/rtw88/rtw8821a.c b/drivers/net/wireless/realtek/rtw88/rtw8821a.c
index 17d3677ca463c..f7391f6aa3039 100644
--- a/drivers/net/wireless/realtek/rtw88/rtw8821a.c
+++ b/drivers/net/wireless/realtek/rtw88/rtw8821a.c
@@ -891,6 +891,8 @@ static const struct rtw_chip_ops rtw8821a_ops = {
.coex_set_wl_rx_gain = rtw8821a_coex_cfg_wl_rx_gain,
.cfo_init = rtw_phy_cfo_xcap_init,
.cfo_track = rtw88xxa_cfo_track,
+ .adaptivity_init = rtw88xxa_adaptivity_init,
+ .adaptivity = rtw88xxa_adaptivity,
};
static const struct rtw_page_table page_table_8821a[] = {
@@ -944,6 +946,11 @@ static const struct rtw_hw_reg rtw8821a_dig[] = {
[0] = { .addr = REG_RXIGI_A, .mask = 0x7f },
};
+static const struct rtw_hw_reg_offset rtw8821a_edcca_th[] = {
+ [EDCCA_TH_L2H_IDX] = {{.addr = 0x8a4, .mask = MASKBYTE0}, .offset = 0},
+ [EDCCA_TH_H2L_IDX] = {{.addr = 0x8a4, .mask = MASKBYTE1}, .offset = 0},
+};
+
static const struct rtw_rfe_def rtw8821a_rfe_defs[] = {
[0] = { .phy_pg_tbl = &rtw8821a_bb_pg_tbl,
.txpwr_lmt_tbl = &rtw8821a_txpwr_lmt_tbl,
@@ -1193,6 +1200,9 @@ const struct rtw_chip_info rtw8821a_hw_spec = {
.bfer_su_max_num = 2,
.ampdu_density = IEEE80211_HT_MPDU_DENSITY_16,
.max_scan_ie_len = IEEE80211_MAX_DATA_LEN,
+ .edcca_th = rtw8821a_edcca_th,
+ .l2h_th_ini_cs = 10,
+ .l2h_th_ini_ad = -17,
.coex_para_ver = 20190509, /* glcoex_ver_date_8821a_1ant */
.bt_desired_ver = 0x62, /* But for 2 ant it's 0x5c */
diff --git a/drivers/net/wireless/realtek/rtw88/rtw88xxa.c b/drivers/net/wireless/realtek/rtw88/rtw88xxa.c
index 586bb57a10061..aff62d61f4719 100644
--- a/drivers/net/wireless/realtek/rtw88/rtw88xxa.c
+++ b/drivers/net/wireless/realtek/rtw88/rtw88xxa.c
@@ -2040,6 +2040,129 @@ void rtw88xxa_cfo_track(struct rtw_dev *rtwdev)
}
EXPORT_SYMBOL(rtw88xxa_cfo_track);
+#define EDCCA_TH_MAX_88XXA 0x7f
+#define EDCCA_L2H_CAP_88XXA 10
+#define EDCCA_L2H_INI_NORMAL_88XXA 20
+#define EDCCA_DBG_PORT_88XXA 0x209
+#define EDCCA_DC_BACKOFF_88XXA 2
+#define EDCCA_LB_START_88XXA (EDCCA_L2H_H2L_DIFF - 30)
+#define EDCCA_LB_RETRY_88XXA (EDCCA_LB_START_88XXA + 18)
+
+static void rtw88xxa_set_rf_mode_rx(struct rtw_dev *rtwdev, u32 data0,
+ u32 data1)
+{
+ u8 path;
+
+ for (path = RF_PATH_A; path < rtwdev->hal.rf_path_num; path++) {
+ rtw_write_rf(rtwdev, path, RF_LUTWE, BIT(19), 1);
+ rtw_write_rf(rtwdev, path, RF_MODE_TABLE_ADDR, RFREG_MASK,
+ 0x18000);
+ rtw_write_rf(rtwdev, path, RF_MODE_TABLE_DATA0, RFREG_MASK,
+ data0);
+ rtw_write_rf(rtwdev, path, RF_MODE_TABLE_DATA1, RFREG_MASK,
+ data1);
+ rtw_write_rf(rtwdev, path, RF_LUTWE, BIT(19), 0);
+ }
+}
+
+static void rtw88xxa_set_lna(struct rtw_dev *rtwdev, bool enable)
+{
+ if (rtwdev->chip->id == RTW_CHIP_TYPE_8812A)
+ rtw88xxa_set_rf_mode_rx(rtwdev, 0x3f7ff,
+ enable ? 0xc26bf : 0xc22bf);
+ else
+ rtw88xxa_set_rf_mode_rx(rtwdev, 0x0002f,
+ enable ? 0xfb0bb : 0xfb09b);
+}
+
+static u8 rtw88xxa_edcca_count(struct rtw_dev *rtwdev)
+{
+ u8 cnt = 0;
+ int i;
+
+ rtw_write32(rtwdev, REG_DBGSEL, 0);
+ for (i = 0; i < 3 &&
+ rtw_read32(rtwdev, REG_DBGRPT) & BIT_DBG_CCA_BUSY; i++)
+ usleep_range(3000, 3500);
+
+ rtw_write32(rtwdev, REG_DBGSEL, EDCCA_DBG_PORT_88XXA);
+ for (i = 0; i < 20; i++)
+ if (rtw_read32(rtwdev, REG_DBGRPT) & BIT_DBG_EDCCA_BUSY)
+ cnt++;
+
+ return cnt;
+}
+
+static s8 rtw88xxa_edcca_search_floor(struct rtw_dev *rtwdev)
+{
+ s8 l2h = EDCCA_LB_START_88XXA;
+
+ rtw_phy_set_edcca_th(rtwdev, l2h, l2h - EDCCA_L2H_H2L_DIFF);
+ msleep(30);
+
+ while (l2h < EDCCA_L2H_CAP_88XXA && rtw88xxa_edcca_count(rtwdev) > 1) {
+ l2h++;
+ rtw_phy_set_edcca_th(rtwdev, l2h, l2h - EDCCA_L2H_H2L_DIFF);
+ }
+
+ return l2h;
+}
+
+void rtw88xxa_adaptivity_init(struct rtw_dev *rtwdev)
+{
+ struct rtw_dm_info *dm_info = &rtwdev->dm_info;
+ u32 dbgsel = rtw_read32(rtwdev, REG_DBGSEL);
+ s8 l2h;
+
+ rtw_write32_clr(rtwdev, REG_TX_PTCL_CTRL, BIT_DIS_EDCCA);
+
+ rtw88xxa_set_lna(rtwdev, false);
+ l2h = rtw88xxa_edcca_search_floor(rtwdev);
+ if (l2h >= EDCCA_LB_RETRY_88XXA)
+ l2h = rtw88xxa_edcca_search_floor(rtwdev);
+ rtw88xxa_set_lna(rtwdev, true);
+ rtw_write32(rtwdev, REG_DBGSEL, dbgsel);
+
+ dm_info->l2h_th_lb = l2h + EDCCA_DC_BACKOFF_88XXA;
+ rtw_phy_set_edcca_th(rtwdev, EDCCA_TH_MAX_88XXA, EDCCA_TH_MAX_88XXA);
+
+ rtw_dbg(rtwdev, RTW_DBG_ADAPTIVITY, "EDCCA L2H floor %d\n",
+ dm_info->l2h_th_lb);
+}
+EXPORT_SYMBOL(rtw88xxa_adaptivity_init);
+
+void rtw88xxa_adaptivity(struct rtw_dev *rtwdev)
+{
+ struct rtw_dm_info *dm_info = &rtwdev->dm_info;
+ int igi_diff = EDCCA_IGI_BASE - dm_info->igi_history[0];
+ s8 l2h_lb = dm_info->l2h_th_lb;
+ s8 l2h, h2l;
+
+ /* toggle the ADC mask to work around an EDCCA hang */
+ if (rtwdev->chip->id == RTW_CHIP_TYPE_8812A) {
+ rtw_write32_set(rtwdev, REG_FPGA0_RFMOD, BIT(10));
+ rtw_write32_clr(rtwdev, REG_FPGA0_RFMOD, BIT(10));
+ }
+
+ if (dm_info->edcca_mode == RTW_EDCCA_ADAPTIVITY) {
+ l2h = min(dm_info->l2h_th_ini + igi_diff, EDCCA_L2H_CAP_88XXA);
+ h2l = l2h - EDCCA_L2H_H2L_DIFF;
+ } else {
+ l2h = EDCCA_L2H_INI_NORMAL_88XXA + igi_diff;
+ h2l = l2h - EDCCA_L2H_H2L_DIFF_NORMAL;
+ }
+
+ l2h = max(l2h, l2h_lb);
+ h2l = max_t(s8, h2l, l2h_lb - EDCCA_L2H_H2L_DIFF);
+
+ rtw_phy_set_edcca_th(rtwdev, l2h, h2l);
+
+ rtw_dbg(rtwdev, RTW_DBG_ADAPTIVITY,
+ "EDCCA mode %d, IGI 0x%x, L2H %d, H2L %d\n",
+ dm_info->edcca_mode, dm_info->igi_history[0], l2h, h2l);
+}
+EXPORT_SYMBOL(rtw88xxa_adaptivity);
+
MODULE_AUTHOR("Realtek Corporation");
MODULE_DESCRIPTION("Realtek 802.11ac wireless 8821a/8811a/8812a common code");
MODULE_LICENSE("Dual BSD/GPL");
diff --git a/drivers/net/wireless/realtek/rtw88/rtw88xxa.h b/drivers/net/wireless/realtek/rtw88/rtw88xxa.h
index 09b72d33f62e1..b8719c278df73 100644
--- a/drivers/net/wireless/realtek/rtw88/rtw88xxa.h
+++ b/drivers/net/wireless/realtek/rtw88/rtw88xxa.h
@@ -139,6 +139,9 @@ struct rtw_jaguar_phy_status_rpt {
#define RF18_BW_MASK (BIT(11) | BIT(10))
+#define BIT_DBG_CCA_BUSY BIT(3)
+#define BIT_DBG_EDCCA_BUSY BIT(29)
+
void rtw88xxa_efuse_grant(struct rtw_dev *rtwdev, bool on);
int rtw88xxa_read_efuse(struct rtw_dev *rtwdev, u8 *log_map);
void rtw88xxa_power_off(struct rtw_dev *rtwdev,
@@ -173,5 +176,7 @@ void rtw88xxa_phy_pwrtrack(struct rtw_dev *rtwdev,
void rtw88xxa_bf_config_bfee(struct rtw_dev *rtwdev, struct rtw_vif *vif,
struct rtw_bfee *bfee, bool enable);
void rtw88xxa_cfo_track(struct rtw_dev *rtwdev);
+void rtw88xxa_adaptivity_init(struct rtw_dev *rtwdev);
+void rtw88xxa_adaptivity(struct rtw_dev *rtwdev);
#endif
--
2.55.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* [PATCH 6/6] wifi: rtw88: 8812a, 8821a: respect peer A-MPDU size limits
2026-10-29 5:52 [PATCH 0/6] wifi: rtw88: extend 88xxA support Ho Jie Feng
` (4 preceding siblings ...)
2026-10-29 5:52 ` [PATCH 5/6] wifi: rtw88: 88xxa: adapt channel sensing to receiver gain Ho Jie Feng
@ 2026-10-29 5:52 ` Ho Jie Feng
5 siblings, 0 replies; 9+ messages in thread
From: Ho Jie Feng @ 2026-10-29 5:52 UTC (permalink / raw)
To: Ping-Ke Shih; +Cc: linux-wireless, Ho Jie Feng, linux-kernel
Honor the peer-advertised receive limit for transmitted A-MPDUs.
Signed-off-by: Ho Jie Feng <hjf3108@gmail.com>
---
drivers/net/wireless/realtek/rtw88/rtw8812a.c | 9 ++++++++-
drivers/net/wireless/realtek/rtw88/rtw8821a.c | 9 ++++++++-
2 files changed, 16 insertions(+), 2 deletions(-)
diff --git a/drivers/net/wireless/realtek/rtw88/rtw8812a.c b/drivers/net/wireless/realtek/rtw88/rtw8812a.c
index d93c4776e0303..a3b5fae5608e0 100644
--- a/drivers/net/wireless/realtek/rtw88/rtw8812a.c
+++ b/drivers/net/wireless/realtek/rtw88/rtw8812a.c
@@ -78,6 +78,13 @@ static void rtw8812a_cfg_ldo25(struct rtw_dev *rtwdev, bool enable)
{
}
+static void rtw8812a_set_ampdu_factor(struct rtw_dev *rtwdev, u8 factor)
+{
+ factor = min_t(u8, factor, IEEE80211_VHT_MAX_AMPDU_128K);
+
+ rtw_write32(rtwdev, REG_AMPDU_MAX_LENGTH, (8192 << factor) - 1);
+}
+
static void rtw8812a_do_lck(struct rtw_dev *rtwdev)
{
u32 cont_tx, lc_cal, i;
@@ -926,7 +933,7 @@ static const struct rtw_chip_ops rtw8812a_ops = {
.set_tx_power_index = rtw88xxa_set_tx_power_index,
.cfg_ldo25 = rtw8812a_cfg_ldo25,
.efuse_grant = rtw88xxa_efuse_grant,
- .set_ampdu_factor = NULL,
+ .set_ampdu_factor = rtw8812a_set_ampdu_factor,
.false_alarm_statistics = rtw88xxa_false_alarm_statistics,
.phy_calibration = rtw8812a_phy_calibration,
.cck_pd_set = rtw_phy_cck_pd_set,
diff --git a/drivers/net/wireless/realtek/rtw88/rtw8821a.c b/drivers/net/wireless/realtek/rtw88/rtw8821a.c
index f7391f6aa3039..f597edec290f6 100644
--- a/drivers/net/wireless/realtek/rtw88/rtw8821a.c
+++ b/drivers/net/wireless/realtek/rtw88/rtw8821a.c
@@ -49,6 +49,13 @@ static void rtw8821a_cfg_ldo25(struct rtw_dev *rtwdev, bool enable)
{
}
+static void rtw8821a_set_ampdu_factor(struct rtw_dev *rtwdev, u8 factor)
+{
+ factor = min_t(u8, factor, IEEE80211_HT_MAX_AMPDU_64K);
+
+ rtw_write32(rtwdev, REG_AMPDU_MAX_LENGTH, (8192 << factor) - 1);
+}
+
#define CAL_NUM_8821A 3
#define MACBB_REG_NUM_8821A 8
#define AFE_REG_NUM_8821A 4
@@ -872,7 +879,7 @@ static const struct rtw_chip_ops rtw8821a_ops = {
.set_tx_power_index = rtw88xxa_set_tx_power_index,
.cfg_ldo25 = rtw8821a_cfg_ldo25,
.efuse_grant = rtw88xxa_efuse_grant,
- .set_ampdu_factor = NULL,
+ .set_ampdu_factor = rtw8821a_set_ampdu_factor,
.false_alarm_statistics = rtw88xxa_false_alarm_statistics,
.phy_calibration = rtw8821a_phy_calibration,
.cck_pd_set = rtw_phy_cck_pd_set,
--
2.55.0
^ permalink raw reply related [flat|nested] 9+ messages in thread