]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
wifi: rtl8xxxu: Detect the maximum supported channel width
authorBitterblue Smith <rtl8821cerfe2@gmail.com>
Wed, 29 Apr 2026 12:02:48 +0000 (15:02 +0300)
committerPing-Ke Shih <pkshih@realtek.com>
Wed, 6 May 2026 07:54:20 +0000 (15:54 +0800)
Some devices malfunction when connected to a network with 40 MHz channel
width, because they don't support that.

RTL8188FU, RTL8192FU, and RTL8710BU (RTL8188GU) have a way to signal
this (and some other capabilities) to the driver. Get this information
from the hardware and advertise 40 MHz support only when the hardware
can handle it. We assume the other chips can always handle it.

RTL8710BU needs a different way to retrieve this information, which will
be implemented some other time.

Fixes: dbf9b7bb0edf ("wifi: rtl8xxxu: Enable 40 MHz width by default")
Cc: stable@vger.kernel.org
Closes: https://bugzilla.kernel.org/show_bug.cgi?id=221394
Signed-off-by: Bitterblue Smith <rtl8821cerfe2@gmail.com>
Reviewed-by: Ping-Ke Shih <pkshih@realtek.com>
Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
Link: https://patch.msgid.link/c57de68e-5d57-4c26-898f-8a284bb25381@gmail.com
drivers/net/wireless/realtek/rtl8xxxu/8188e.c
drivers/net/wireless/realtek/rtl8xxxu/8188f.c
drivers/net/wireless/realtek/rtl8xxxu/8192c.c
drivers/net/wireless/realtek/rtl8xxxu/8192e.c
drivers/net/wireless/realtek/rtl8xxxu/8192f.c
drivers/net/wireless/realtek/rtl8xxxu/8710b.c
drivers/net/wireless/realtek/rtl8xxxu/8723a.c
drivers/net/wireless/realtek/rtl8xxxu/8723b.c
drivers/net/wireless/realtek/rtl8xxxu/core.c
drivers/net/wireless/realtek/rtl8xxxu/regs.h
drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu.h

index 766a7a7c7d281670c9ed0fde91d731a26db4ffd8..fea5aec9ced9982cfffb8273c2bdce2a233a7d1a 100644 (file)
@@ -1866,6 +1866,7 @@ struct rtl8xxxu_fileops rtl8188eu_fops = {
        .has_tx_report = 1,
        .init_reg_pkt_life_time = 1,
        .gen2_thermal_meter = 1,
+       .hw_feature_report = 0,
        .max_sec_cam_num = 32,
        .adda_1t_init = 0x0b1b25a0,
        .adda_1t_path_on = 0x0bdb25a0,
index 3abf14d7044f37b69bf0b7337cfc944d43f65d47..5556aad1a7ad3bf3dd51b5cacd0cef698ea4d2f7 100644 (file)
@@ -1745,6 +1745,7 @@ struct rtl8xxxu_fileops rtl8188fu_fops = {
        .init_reg_rxfltmap = 1,
        .init_reg_pkt_life_time = 1,
        .init_reg_hmtfr = 1,
+       .hw_feature_report = 1,
        .ampdu_max_time = 0x70,
        .ustime_tsf_edca = 0x28,
        .max_aggr_num = 0x0c14,
index 444872131c663d31c403529b34ea92f4a151bac8..508322a8f310894b2c1b0ca62010d29386fe734c 100644 (file)
@@ -723,6 +723,7 @@ struct rtl8xxxu_fileops rtl8192cu_fops = {
        .tx_desc_size = sizeof(struct rtl8xxxu_txdesc32),
        .rx_desc_size = sizeof(struct rtl8xxxu_rxdesc16),
        .supports_ap = 1,
+       .hw_feature_report = 0,
        .max_macid_num = 32,
        .max_sec_cam_num = 32,
        .adda_1t_init = 0x0b1b25a0,
index 8e123bbfc66594e60e65ca06bcb918ffbe41248b..690ace3031503dfc8abc86a33a3a9d15827a9099 100644 (file)
@@ -1751,6 +1751,7 @@ struct rtl8xxxu_fileops rtl8192eu_fops = {
        .has_s0s1 = 0,
        .gen2_thermal_meter = 1,
        .needs_full_init = 1,
+       .hw_feature_report = 0,
        .supports_ap = 1,
        .max_macid_num = 128,
        .max_sec_cam_num = 64,
index cd2156b7a57ab29208f96310f511db1deb8ebe52..5f076da4b5506ede9489735a00409245c8a45565 100644 (file)
@@ -2074,6 +2074,7 @@ struct rtl8xxxu_fileops rtl8192fu_fops = {
        .init_reg_rxfltmap = 1,
        .init_reg_pkt_life_time = 1,
        .init_reg_hmtfr = 1,
+       .hw_feature_report = 1,
        .ampdu_max_time = 0x5e,
        .ustime_tsf_edca = 0x50,
        .max_aggr_num = 0x1f1f,
index 11c63c320eaeb45179b0c4e144dc6c964b0d5ea4..a87e692bfc812ae293c0b06f5f631f888bdd9d57 100644 (file)
@@ -1852,6 +1852,7 @@ struct rtl8xxxu_fileops rtl8710bu_fops = {
        .init_reg_rxfltmap = 1,
        .init_reg_pkt_life_time = 1,
        .init_reg_hmtfr = 1,
+       .hw_feature_report = 0, /* TODO, it's different */
        .ampdu_max_time = 0x5e,
        /*
         * The RTL8710BU vendor driver uses 0x50 here and it works fine,
index 4f4493d0bfc2d4b2da2598d4bad4b02039fb89a3..18e038b82f49033fe95d6aedd2eb01088b5e571a 100644 (file)
@@ -632,6 +632,7 @@ struct rtl8xxxu_fileops rtl8723au_fops = {
        .rx_agg_buf_size = 16000,
        .tx_desc_size = sizeof(struct rtl8xxxu_txdesc32),
        .rx_desc_size = sizeof(struct rtl8xxxu_rxdesc16),
+       .hw_feature_report = 0,
        .max_sec_cam_num = 32,
        .adda_1t_init = 0x0b1b25a0,
        .adda_1t_path_on = 0x0bdb25a0,
index cc2e60b06f6474df906168dd6ff9883fc44ddc92..e314ef991b388a4577dcb185d30008189a8e837f 100644 (file)
@@ -1746,6 +1746,7 @@ struct rtl8xxxu_fileops rtl8723bu_fops = {
        .gen2_thermal_meter = 1,
        .needs_full_init = 1,
        .init_reg_hmtfr = 1,
+       .hw_feature_report = 0,
        .ampdu_max_time = 0x5e,
        .ustime_tsf_edca = 0x50,
        .max_aggr_num = 0x0c14,
index 508137e4a87aa1f191778b216c3fabd523c70a74..646fe76b086e75d51f250182bdc477a8c789617f 100644 (file)
@@ -14,6 +14,7 @@
  */
 
 #include <linux/firmware.h>
+#include <linux/iopoll.h>
 #include "regs.h"
 #include "rtl8xxxu.h"
 
@@ -3915,6 +3916,46 @@ static inline u8 rtl8xxxu_get_macid(struct rtl8xxxu_priv *priv,
        return sta_info->macid;
 }
 
+static void rtl8xxxu_request_hw_feature(struct rtl8xxxu_priv *priv)
+{
+       if (!priv->fops->hw_feature_report)
+               return;
+
+       rtl8xxxu_write8(priv, REG_C2HEVT_MSG_NORMAL, C2H_HW_FEATURE_DUMP);
+}
+
+static int rtl8xxxu_dump_hw_feature(struct rtl8xxxu_priv *priv)
+{
+       static const u8 bw_map[8] = { 0, 0, 160, 5, 10, 20, 40, 80 };
+       struct rtl8xxxu_hw_feature *hw_feature = &priv->hw_feature;
+       u8 feature[13];
+       int i, ret;
+       u8 id, bw;
+
+       if (!priv->fops->hw_feature_report) {
+               hw_feature->max_bw = 40;
+               return 0;
+       }
+
+       ret = read_poll_timeout(rtl8xxxu_read8, id,
+                               id == C2H_HW_FEATURE_REPORT,
+                               10000, 800000, false,
+                               priv, REG_C2HEVT_MSG_NORMAL);
+       if (ret)
+               return ret;
+
+       for (i = 0; i < ARRAY_SIZE(feature); i++)
+               feature[i] = rtl8xxxu_read8(priv, REG_C2HEVT_MSG_NORMAL + 2 + i);
+
+       rtl8xxxu_write8(priv, REG_C2HEVT_MSG_NORMAL, 0);
+
+       bw = u8_get_bits(feature[6], GENMASK(2, 0));
+
+       hw_feature->max_bw = bw_map[bw];
+
+       return 0;
+}
+
 static int rtl8xxxu_init_device(struct ieee80211_hw *hw)
 {
        struct rtl8xxxu_priv *priv = hw->priv;
@@ -3961,6 +4002,8 @@ static int rtl8xxxu_init_device(struct ieee80211_hw *hw)
         */
        rtl8xxxu_write16(priv, REG_TRXFF_BNDY + 2, fops->trxff_boundary);
 
+       rtl8xxxu_request_hw_feature(priv);
+
        for (int retry = 5; retry >= 0 ; retry--) {
                ret = rtl8xxxu_download_firmware(priv);
                dev_dbg(dev, "%s: download_firmware %i\n", __func__, ret);
@@ -3976,6 +4019,12 @@ static int rtl8xxxu_init_device(struct ieee80211_hw *hw)
        if (ret)
                goto exit;
 
+       ret = rtl8xxxu_dump_hw_feature(priv);
+       if (ret) {
+               dev_err(dev, "failed to dump hw feature\n");
+               goto exit;
+       }
+
        if (fops->phy_init_antenna_selection)
                fops->phy_init_antenna_selection(priv);
 
@@ -7844,15 +7893,20 @@ static int rtl8xxxu_probe(struct usb_interface *interface,
        sband->ht_cap.ht_supported = true;
        sband->ht_cap.ampdu_factor = IEEE80211_HT_MAX_AMPDU_64K;
        sband->ht_cap.ampdu_density = IEEE80211_HT_MPDU_DENSITY_16;
-       sband->ht_cap.cap = IEEE80211_HT_CAP_SGI_20 | IEEE80211_HT_CAP_SGI_40 |
-                           IEEE80211_HT_CAP_SUP_WIDTH_20_40;
+       sband->ht_cap.cap = IEEE80211_HT_CAP_SGI_20;
+
+       if (priv->hw_feature.max_bw >= 40) {
+               sband->ht_cap.cap |= IEEE80211_HT_CAP_SGI_40;
+               sband->ht_cap.cap |= IEEE80211_HT_CAP_SUP_WIDTH_20_40;
+       } else {
+               dev_info(&udev->dev, "hardware doesn't support HT40\n");
+       }
+
        memset(&sband->ht_cap.mcs, 0, sizeof(sband->ht_cap.mcs));
        sband->ht_cap.mcs.rx_mask[0] = 0xff;
        sband->ht_cap.mcs.rx_mask[4] = 0x01;
-       if (priv->rf_paths > 1) {
+       if (priv->rf_paths > 1)
                sband->ht_cap.mcs.rx_mask[1] = 0xff;
-               sband->ht_cap.cap |= IEEE80211_HT_CAP_SGI_40;
-       }
        sband->ht_cap.mcs.tx_params = IEEE80211_HT_MCS_TX_DEFINED;
 
        hw->wiphy->bands[NL80211_BAND_2GHZ] = sband;
index 0741db8d08bff7813cece6b162d09e7cef5f6ff1..eafaef1438b971a9f010e02323bd2c1781250fcb 100644 (file)
 /* 8188EU */
 #define REG_32K_CTRL                   0x0194
 #define REG_C2HEVT_MSG_NORMAL          0x01a0
+#define C2H_HW_FEATURE_REPORT          0x19
+#define C2H_HW_FEATURE_DUMP            0xfd
 /* 8192EU/8723BU/8812 */
 #define REG_C2HEVT_CMD_ID_8723B                0x01ae
 #define REG_C2HEVT_CLEAR               0x01af
index 9fb2583ffffc3570e4f077b691784f817a96811a..eeb18eb0e4c0f47cd867fd3f10db9b78dde647e0 100644 (file)
@@ -1789,11 +1789,17 @@ struct rtl8xxxu_cfo_tracking {
 #define RTL8XXXU_BC_MC_MACID1  1
 #define RTL8XXXU_MAX_SEC_CAM_NUM       64
 
+struct rtl8xxxu_hw_feature {
+       u8 max_bw;
+};
+
 struct rtl8xxxu_priv {
        struct ieee80211_hw *hw;
        struct usb_device *udev;
        struct rtl8xxxu_fileops *fops;
 
+       struct rtl8xxxu_hw_feature hw_feature;
+
        spinlock_t tx_urb_lock;
        struct list_head tx_urb_free_list;
        int tx_urb_free_count;
@@ -2009,6 +2015,7 @@ struct rtl8xxxu_fileops {
        u8 init_reg_pkt_life_time:1;
        u8 init_reg_hmtfr:1;
        u8 supports_concurrent:1;
+       u8 hw_feature_report:1;
        u8 ampdu_max_time;
        u8 ustime_tsf_edca;
        u16 max_aggr_num;