From 0758dfa6bdd472d606ad08ffa837646c305946f9 Mon Sep 17 00:00:00 2001 From: Vyacheslav Bocharov Date: Tue, 30 May 2023 08:09:39 +0300 Subject: [PATCH] This add support for RTW88 SDIO driver via patches for 6.1/6.3 kernel Additional driver support: Realtek 8822BS SDIO wireless network adapter Realtek 8822BU USB wireless network adapter Realtek 8822CS SDIO wireless network adapter Realtek 8822CU USB wireless network adapter Realtek 8821CS SDIO wireless network adapter Realtek 8821CU USB wireless network adapter Revert "Revert "BPI-CM4 IO: RTW88: `Realtek 8822CS SDIO` `Wireless Support`"": This reverts commit 672bd5393af1d8d59cad02462bc52980f1a99dfd. This restore commit 3f169bcae25557e60fe9fec613ab2a5bcf8219ac. Signed-off-by: Patrick Yavitz Signed-off-by: Vyacheslav Bocharov --- config/kernel/linux-meson64-current.config | 2 + config/kernel/linux-meson64-edge.config | 2 + .../compilation/patch/drivers-harness.sh | 1 + .../compilation/patch/drivers_network.sh | 19 +- .../6.1/001-rtw88-linux-next-backport.patch | 2716 +++++++++++++++++ .../rtw88/6.1/002-rtw88-fix-rcu-lock.patch | 302 ++ patch/misc/rtw88/6.1/003-rtw88-rfc.patch | 2452 +++++++++++++++ .../misc/rtw88/6.1/004-rtw88-usb-fixes.patch | 86 + .../misc/rtw88/6.1/005-rtw88-mac-c-fix.patch | 146 + patch/misc/rtw88/6.3/001-rtw88-sdio-rfc.patch | 2394 +++++++++++++++ 10 files changed, 8119 insertions(+), 1 deletion(-) create mode 100644 patch/misc/rtw88/6.1/001-rtw88-linux-next-backport.patch create mode 100644 patch/misc/rtw88/6.1/002-rtw88-fix-rcu-lock.patch create mode 100644 patch/misc/rtw88/6.1/003-rtw88-rfc.patch create mode 100644 patch/misc/rtw88/6.1/004-rtw88-usb-fixes.patch create mode 100644 patch/misc/rtw88/6.1/005-rtw88-mac-c-fix.patch create mode 100644 patch/misc/rtw88/6.3/001-rtw88-sdio-rfc.patch diff --git a/config/kernel/linux-meson64-current.config b/config/kernel/linux-meson64-current.config index e4d717c3b4..6551e7a53c 100644 --- a/config/kernel/linux-meson64-current.config +++ b/config/kernel/linux-meson64-current.config @@ -3552,6 +3552,8 @@ CONFIG_RTW88_8821C=m # CONFIG_RTW88_8822CE is not set # CONFIG_RTW88_8723DE is not set CONFIG_RTW88_8821CE=m +CONFIG_RTW88_8821CS=m +CONFIG_RTW88_8822CS=m # CONFIG_RTW88_DEBUG is not set # CONFIG_RTW88_DEBUGFS is not set # CONFIG_RTW89 is not set diff --git a/config/kernel/linux-meson64-edge.config b/config/kernel/linux-meson64-edge.config index 0eb6d42505..1db3daa90c 100644 --- a/config/kernel/linux-meson64-edge.config +++ b/config/kernel/linux-meson64-edge.config @@ -3571,6 +3571,8 @@ CONFIG_RTW88_8821C=m # CONFIG_RTW88_8723DE is not set # CONFIG_RTW88_8723DU is not set CONFIG_RTW88_8821CE=m +CONFIG_RTW88_8821CS=m +CONFIG_RTW88_8822CS=m # CONFIG_RTW88_8821CU is not set # CONFIG_RTW88_DEBUG is not set # CONFIG_RTW88_DEBUGFS is not set diff --git a/lib/functions/compilation/patch/drivers-harness.sh b/lib/functions/compilation/patch/drivers-harness.sh index 0cdc3d9a9c..5da2d5a719 100644 --- a/lib/functions/compilation/patch/drivers-harness.sh +++ b/lib/functions/compilation/patch/drivers-harness.sh @@ -81,6 +81,7 @@ function kernel_drivers_prepare_harness() { driver_rtl8811CU_rtl8821C driver_rtl8188EU_rtl8188ETV driver_rtl88x2bu + driver_rtw88 driver_rtl88x2cs driver_rtl8822cs_bt driver_rtl8723DS diff --git a/lib/functions/compilation/patch/drivers_network.sh b/lib/functions/compilation/patch/drivers_network.sh index 1a1b67693e..4bbf250bb3 100644 --- a/lib/functions/compilation/patch/drivers_network.sh +++ b/lib/functions/compilation/patch/drivers_network.sh @@ -387,11 +387,27 @@ driver_rtl88x2bu() { } +driver_rtw88() { + # Upstream wireless RTW88 drivers + if linux-version compare "${version}" ge 6.1 && [ $EXTRAWIFI == yes ]; then + display_alert "Adding" "Upstream wireless RTW88 drivers" "info" + process_patch_file "${SRC}/patch/misc/rtw88/6.1/001-rtw88-linux-next-backport.patch" "applying" + process_patch_file "${SRC}/patch/misc/rtw88/6.1/002-rtw88-fix-rcu-lock.patch" "applying" + process_patch_file "${SRC}/patch/misc/rtw88/6.1/003-rtw88-rfc.patch" "applying" + process_patch_file "${SRC}/patch/misc/rtw88/6.1/004-rtw88-usb-fixes.patch" "applying" + process_patch_file "${SRC}/patch/misc/rtw88/6.1/005-rtw88-mac-c-fix.patch" "applying" + fi + if linux-version compare "${version}" ge 6.3 && [ $EXTRAWIFI == yes ]; then + display_alert "Adding" "Upstream wireless drivers for RTW88" "info" + process_patch_file "${SRC}/patch/misc/rtw88/6.3/001-rtw88-sdio-rfc.patch" "applying" + fi +} + driver_rtl88x2cs() { # Wireless drivers for Realtek 88x2cs chipsets - if linux-version compare "${version}" ge 5.9 && [ "$EXTRAWIFI" == yes ]; then + if linux-version compare "${version}" ge 5.9 && [ "$EXTRAWIFI" == no ]; then # attach to specifics tag or branch local rtl88x2csver="branch:tune_for_jethub" @@ -672,6 +688,7 @@ patch_drivers_network() { driver_rtl8811CU_rtl8821C driver_rtl8188EU_rtl8188ETV driver_rtl88x2bu + driver_rtw88 driver_rtl88x2cs driver_rtl8822cs_bt driver_rtl8723DS diff --git a/patch/misc/rtw88/6.1/001-rtw88-linux-next-backport.patch b/patch/misc/rtw88/6.1/001-rtw88-linux-next-backport.patch new file mode 100644 index 0000000000..a1b3500321 --- /dev/null +++ b/patch/misc/rtw88/6.1/001-rtw88-linux-next-backport.patch @@ -0,0 +1,2716 @@ +From 823092a53556ebf8656623d0e857626d30fe1e18 Mon Sep 17 00:00:00 2001 +From: Ji-Pin Jou +Date: Thu, 24 Nov 2022 14:44:42 +0800 +Subject: wifi: rtw88: fix race condition when doing H2C command + +For SDIO/USB interface, since the tranferring speed is slower than +that in PCIE, it may have race condition when the driver sets down +H2C command to the FW. + +In the function rtw_fw_send_h2c_command, before the patch, box_reg +is written first, then box_ex_reg is written. FW starts to work and +fetch the value of box_ex_reg, when the most significant byte of +box_reg(4 bytes) is written. Meanwhile, for SDIO/USB interface, +since the transferring speed is slow, the driver is still in writing +the new value of box_ex_reg through the bus, and FW may get the +wrong value of box_ex_reg at the moment. + +To prevent the above driver/FW racing situation, box_ex_reg is +written first then box_reg. Furthermore, it is written in 4 bytes at +a time, instead of written in one byte one by one. It can increase +the speed for SDIO/USB interface. + +Signed-off-by: Ji-Pin Jou +Signed-off-by: Ping-Ke Shih +Tested-by: Sascha Hauer +Signed-off-by: Kalle Valo +Link: https://lore.kernel.org/r/20221124064442.28042-1-pkshih@realtek.com +--- + drivers/net/wireless/realtek/rtw88/fw.c | 8 +++----- + drivers/net/wireless/realtek/rtw88/fw.h | 5 +++++ + 2 files changed, 8 insertions(+), 5 deletions(-) + +diff --git a/drivers/net/wireless/realtek/rtw88/fw.c b/drivers/net/wireless/realtek/rtw88/fw.c +index 0b5f903c0f366..b290811d75e1c 100644 +--- a/drivers/net/wireless/realtek/rtw88/fw.c ++++ b/drivers/net/wireless/realtek/rtw88/fw.c +@@ -311,10 +311,10 @@ EXPORT_SYMBOL(rtw_fw_c2h_cmd_isr); + static void rtw_fw_send_h2c_command(struct rtw_dev *rtwdev, + u8 *h2c) + { ++ struct rtw_h2c_cmd *h2c_cmd = (struct rtw_h2c_cmd *)h2c; + u8 box; + u8 box_state; + u32 box_reg, box_ex_reg; +- int idx; + int ret; + + rtw_dbg(rtwdev, RTW_DBG_FW, +@@ -356,10 +356,8 @@ static void rtw_fw_send_h2c_command(struct rtw_dev *rtwdev, + goto out; + } + +- for (idx = 0; idx < 4; idx++) +- rtw_write8(rtwdev, box_reg + idx, h2c[idx]); +- for (idx = 0; idx < 4; idx++) +- rtw_write8(rtwdev, box_ex_reg + idx, h2c[idx + 4]); ++ rtw_write32(rtwdev, box_ex_reg, le32_to_cpu(h2c_cmd->msg_ext)); ++ rtw_write32(rtwdev, box_reg, le32_to_cpu(h2c_cmd->msg)); + + if (++rtwdev->h2c.last_box_num >= 4) + rtwdev->h2c.last_box_num = 0; +diff --git a/drivers/net/wireless/realtek/rtw88/fw.h b/drivers/net/wireless/realtek/rtw88/fw.h +index a5a965803a3cc..bca610dc99ab7 100644 +--- a/drivers/net/wireless/realtek/rtw88/fw.h ++++ b/drivers/net/wireless/realtek/rtw88/fw.h +@@ -81,6 +81,11 @@ struct rtw_c2h_adaptivity { + u8 option; + } __packed; + ++struct rtw_h2c_cmd { ++ __le32 msg; ++ __le32 msg_ext; ++} __packed; ++ + enum rtw_rsvd_packet_type { + RSVD_BEACON, + RSVD_DUMMY, +-- +cgit + +From 1d89660494402168565e0637268486ae28bad642 Mon Sep 17 00:00:00 2001 +From: Sascha Hauer +Date: Fri, 2 Dec 2022 09:12:14 +0100 +Subject: wifi: rtw88: print firmware type in info message + +It's confusing to read two different firmware versions in the syslog +for the same device: + +rtw_8822cu 2-1:1.2: Firmware version 9.9.4, H2C version 15 +rtw_8822cu 2-1:1.2: Firmware version 9.9.11, H2C version 15 + +Print the firmware type in this message to make clear these are really +two different firmwares for different purposes: + +rtw_8822cu 1-1.4:1.2: WOW Firmware version 9.9.4, H2C version 15 +rtw_8822cu 1-1.4:1.2: Firmware version 9.9.11, H2C version 15 + +Signed-off-by: Sascha Hauer +Reviewed-by: Ping-Ke Shih +Signed-off-by: Kalle Valo +Link: https://lore.kernel.org/r/20221202081224.2779981-2-s.hauer@pengutronix.de +--- + drivers/net/wireless/realtek/rtw88/main.c | 4 +++- + drivers/net/wireless/realtek/rtw88/main.h | 1 + + 2 files changed, 4 insertions(+), 1 deletion(-) + +diff --git a/drivers/net/wireless/realtek/rtw88/main.c b/drivers/net/wireless/realtek/rtw88/main.c +index 67151dbf83842..a7331872e8530 100644 +--- a/drivers/net/wireless/realtek/rtw88/main.c ++++ b/drivers/net/wireless/realtek/rtw88/main.c +@@ -1731,7 +1731,8 @@ static void rtw_load_firmware_cb(const struct firmware *firmware, void *context) + update_firmware_info(rtwdev, fw); + complete_all(&fw->completion); + +- rtw_info(rtwdev, "Firmware version %u.%u.%u, H2C version %u\n", ++ rtw_info(rtwdev, "%sFirmware version %u.%u.%u, H2C version %u\n", ++ fw->type == RTW_WOWLAN_FW ? "WOW " : "", + fw->version, fw->sub_version, fw->sub_index, fw->h2c_version); + } + +@@ -1757,6 +1758,7 @@ static int rtw_load_firmware(struct rtw_dev *rtwdev, enum rtw_fw_type type) + return -ENOENT; + } + ++ fw->type = type; + fw->rtwdev = rtwdev; + init_completion(&fw->completion); + +diff --git a/drivers/net/wireless/realtek/rtw88/main.h b/drivers/net/wireless/realtek/rtw88/main.h +index bccd7b28f60c7..6e5875f6d07f4 100644 +--- a/drivers/net/wireless/realtek/rtw88/main.h ++++ b/drivers/net/wireless/realtek/rtw88/main.h +@@ -1851,6 +1851,7 @@ struct rtw_fw_state { + u16 h2c_version; + u32 feature; + u32 feature_ext; ++ enum rtw_fw_type type; + }; + + enum rtw_sar_sources { +-- +cgit + +From 69020957bcb783184af1a86c8483139557cec751 Mon Sep 17 00:00:00 2001 +From: Sascha Hauer +Date: Fri, 2 Dec 2022 09:12:15 +0100 +Subject: wifi: rtw88: Call rtw_fw_beacon_filter_config() with rtwdev->mutex + held + +rtw_fw_beacon_filter_config() is called once with rtwdev->mutex held +and once without the mutex held. Call it consistently with rtwdev->mutex +held. + +Signed-off-by: Sascha Hauer +Reviewed-by: Ping-Ke Shih +Signed-off-by: Kalle Valo +Link: https://lore.kernel.org/r/20221202081224.2779981-3-s.hauer@pengutronix.de +--- + drivers/net/wireless/realtek/rtw88/mac80211.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/drivers/net/wireless/realtek/rtw88/mac80211.c b/drivers/net/wireless/realtek/rtw88/mac80211.c +index 07578ccc4bab3..776a9a9884b5d 100644 +--- a/drivers/net/wireless/realtek/rtw88/mac80211.c ++++ b/drivers/net/wireless/realtek/rtw88/mac80211.c +@@ -487,8 +487,8 @@ static int rtw_ops_sta_remove(struct ieee80211_hw *hw, + { + struct rtw_dev *rtwdev = hw->priv; + +- rtw_fw_beacon_filter_config(rtwdev, false, vif); + mutex_lock(&rtwdev->mutex); ++ rtw_fw_beacon_filter_config(rtwdev, false, vif); + rtw_sta_remove(rtwdev, sta, true); + mutex_unlock(&rtwdev->mutex); + +-- +cgit + +From d57ca103e54e2b3eea7e2603548c58bcc4155541 Mon Sep 17 00:00:00 2001 +From: Sascha Hauer +Date: Fri, 2 Dec 2022 09:12:16 +0100 +Subject: wifi: rtw88: Drop rf_lock + +The rtwdev->rf_lock spinlock protects the rf register accesses in +rtw_read_rf() and rtw_write_rf(). Most callers of these functions hold +rtwdev->mutex already with the exception of the callsites in the debugfs +code. The debugfs code doesn't justify an extra lock, so acquire the mutex +there as well before calling rf register accessors and drop the now +unnecessary spinlock. + +Signed-off-by: Sascha Hauer +Reviewed-by: Ping-Ke Shih +Signed-off-by: Kalle Valo +Link: https://lore.kernel.org/r/20221202081224.2779981-4-s.hauer@pengutronix.de +--- + drivers/net/wireless/realtek/rtw88/debug.c | 11 +++++++++++ + drivers/net/wireless/realtek/rtw88/hci.h | 9 +++------ + drivers/net/wireless/realtek/rtw88/main.c | 1 - + drivers/net/wireless/realtek/rtw88/main.h | 3 --- + 4 files changed, 14 insertions(+), 10 deletions(-) + +diff --git a/drivers/net/wireless/realtek/rtw88/debug.c b/drivers/net/wireless/realtek/rtw88/debug.c +index 9ebe544e51d0d..70e19f2a1a355 100644 +--- a/drivers/net/wireless/realtek/rtw88/debug.c ++++ b/drivers/net/wireless/realtek/rtw88/debug.c +@@ -144,7 +144,9 @@ static int rtw_debugfs_get_rf_read(struct seq_file *m, void *v) + addr = debugfs_priv->rf_addr; + mask = debugfs_priv->rf_mask; + ++ mutex_lock(&rtwdev->mutex); + val = rtw_read_rf(rtwdev, path, addr, mask); ++ mutex_unlock(&rtwdev->mutex); + + seq_printf(m, "rf_read path:%d addr:0x%08x mask:0x%08x val=0x%08x\n", + path, addr, mask, val); +@@ -414,7 +416,9 @@ static ssize_t rtw_debugfs_set_rf_write(struct file *filp, + return count; + } + ++ mutex_lock(&rtwdev->mutex); + rtw_write_rf(rtwdev, path, addr, mask, val); ++ mutex_unlock(&rtwdev->mutex); + rtw_dbg(rtwdev, RTW_DBG_DEBUGFS, + "write_rf path:%d addr:0x%08x mask:0x%08x, val:0x%08x\n", + path, addr, mask, val); +@@ -519,6 +523,8 @@ static int rtw_debug_get_rf_dump(struct seq_file *m, void *v) + u32 addr, offset, data; + u8 path; + ++ mutex_lock(&rtwdev->mutex); ++ + for (path = 0; path < rtwdev->hal.rf_path_num; path++) { + seq_printf(m, "RF path:%d\n", path); + for (addr = 0; addr < 0x100; addr += 4) { +@@ -533,6 +539,8 @@ static int rtw_debug_get_rf_dump(struct seq_file *m, void *v) + seq_puts(m, "\n"); + } + ++ mutex_unlock(&rtwdev->mutex); ++ + return 0; + } + +@@ -1026,6 +1034,8 @@ static void dump_gapk_status(struct rtw_dev *rtwdev, struct seq_file *m) + dm_info->dm_flags & BIT(RTW_DM_CAP_TXGAPK) ? '-' : '+', + rtw_dm_cap_strs[RTW_DM_CAP_TXGAPK]); + ++ mutex_lock(&rtwdev->mutex); ++ + for (path = 0; path < rtwdev->hal.rf_path_num; path++) { + val = rtw_read_rf(rtwdev, path, RF_GAINTX, RFREG_MASK); + seq_printf(m, "path %d:\n0x%x = 0x%x\n", path, RF_GAINTX, val); +@@ -1035,6 +1045,7 @@ static void dump_gapk_status(struct rtw_dev *rtwdev, struct seq_file *m) + txgapk->rf3f_fs[path][i], i); + seq_puts(m, "\n"); + } ++ mutex_unlock(&rtwdev->mutex); + } + + static int rtw_debugfs_get_dm_cap(struct seq_file *m, void *v) +diff --git a/drivers/net/wireless/realtek/rtw88/hci.h b/drivers/net/wireless/realtek/rtw88/hci.h +index 4c6fc6fb3f83b..830d7532f2a35 100644 +--- a/drivers/net/wireless/realtek/rtw88/hci.h ++++ b/drivers/net/wireless/realtek/rtw88/hci.h +@@ -166,12 +166,11 @@ static inline u32 + rtw_read_rf(struct rtw_dev *rtwdev, enum rtw_rf_path rf_path, + u32 addr, u32 mask) + { +- unsigned long flags; + u32 val; + +- spin_lock_irqsave(&rtwdev->rf_lock, flags); ++ lockdep_assert_held(&rtwdev->mutex); ++ + val = rtwdev->chip->ops->read_rf(rtwdev, rf_path, addr, mask); +- spin_unlock_irqrestore(&rtwdev->rf_lock, flags); + + return val; + } +@@ -180,11 +179,9 @@ static inline void + rtw_write_rf(struct rtw_dev *rtwdev, enum rtw_rf_path rf_path, + u32 addr, u32 mask, u32 data) + { +- unsigned long flags; ++ lockdep_assert_held(&rtwdev->mutex); + +- spin_lock_irqsave(&rtwdev->rf_lock, flags); + rtwdev->chip->ops->write_rf(rtwdev, rf_path, addr, mask, data); +- spin_unlock_irqrestore(&rtwdev->rf_lock, flags); + } + + static inline u32 +diff --git a/drivers/net/wireless/realtek/rtw88/main.c b/drivers/net/wireless/realtek/rtw88/main.c +index a7331872e8530..710ddb0283c82 100644 +--- a/drivers/net/wireless/realtek/rtw88/main.c ++++ b/drivers/net/wireless/realtek/rtw88/main.c +@@ -2067,7 +2067,6 @@ int rtw_core_init(struct rtw_dev *rtwdev) + skb_queue_head_init(&rtwdev->coex.queue); + skb_queue_head_init(&rtwdev->tx_report.queue); + +- spin_lock_init(&rtwdev->rf_lock); + spin_lock_init(&rtwdev->h2c.lock); + spin_lock_init(&rtwdev->txq_lock); + spin_lock_init(&rtwdev->tx_report.q_lock); +diff --git a/drivers/net/wireless/realtek/rtw88/main.h b/drivers/net/wireless/realtek/rtw88/main.h +index 6e5875f6d07f4..f24d17f482aaa 100644 +--- a/drivers/net/wireless/realtek/rtw88/main.h ++++ b/drivers/net/wireless/realtek/rtw88/main.h +@@ -1995,9 +1995,6 @@ struct rtw_dev { + /* ensures exclusive access from mac80211 callbacks */ + struct mutex mutex; + +- /* read/write rf register */ +- spinlock_t rf_lock; +- + /* watch dog every 2 sec */ + struct delayed_work watch_dog_work; + u32 watch_dog_cnt; +-- +cgit + +From 1e2701f4079a7906ff3fb43a315925d303e289d8 Mon Sep 17 00:00:00 2001 +From: Sascha Hauer +Date: Fri, 2 Dec 2022 09:12:17 +0100 +Subject: wifi: rtw88: Drop h2c.lock + +The h2c.lock spinlock is used in rtw_fw_send_h2c_command() and +rtw_fw_send_h2c_packet(). Most callers call this with rtwdev->mutex +held, except from one callsite in the debugfs code. The debugfs code +alone doesn't justify the extra lock, so acquire rtwdev->mutex in +debugfs and drop the now unnecessary spinlock. + +Signed-off-by: Sascha Hauer +Signed-off-by: Kalle Valo +Link: https://lore.kernel.org/r/20221202081224.2779981-5-s.hauer@pengutronix.de +--- + drivers/net/wireless/realtek/rtw88/debug.c | 2 ++ + drivers/net/wireless/realtek/rtw88/fw.c | 13 ++++--------- + drivers/net/wireless/realtek/rtw88/main.c | 1 - + drivers/net/wireless/realtek/rtw88/main.h | 2 -- + 4 files changed, 6 insertions(+), 12 deletions(-) + +diff --git a/drivers/net/wireless/realtek/rtw88/debug.c b/drivers/net/wireless/realtek/rtw88/debug.c +index 70e19f2a1a355..f5b8a77ebc67b 100644 +--- a/drivers/net/wireless/realtek/rtw88/debug.c ++++ b/drivers/net/wireless/realtek/rtw88/debug.c +@@ -392,7 +392,9 @@ static ssize_t rtw_debugfs_set_h2c(struct file *filp, + return -EINVAL; + } + ++ mutex_lock(&rtwdev->mutex); + rtw_fw_h2c_cmd_dbg(rtwdev, param); ++ mutex_unlock(&rtwdev->mutex); + + return count; + } +diff --git a/drivers/net/wireless/realtek/rtw88/fw.c b/drivers/net/wireless/realtek/rtw88/fw.c +index 5e760c884f89d..82295ac6402ee 100644 +--- a/drivers/net/wireless/realtek/rtw88/fw.c ++++ b/drivers/net/wireless/realtek/rtw88/fw.c +@@ -322,7 +322,7 @@ static void rtw_fw_send_h2c_command(struct rtw_dev *rtwdev, + h2c[3], h2c[2], h2c[1], h2c[0], + h2c[7], h2c[6], h2c[5], h2c[4]); + +- spin_lock(&rtwdev->h2c.lock); ++ lockdep_assert_held(&rtwdev->mutex); + + box = rtwdev->h2c.last_box_num; + switch (box) { +@@ -344,7 +344,7 @@ static void rtw_fw_send_h2c_command(struct rtw_dev *rtwdev, + break; + default: + WARN(1, "invalid h2c mail box number\n"); +- goto out; ++ return; + } + + ret = read_poll_timeout_atomic(rtw_read8, box_state, +@@ -353,7 +353,7 @@ static void rtw_fw_send_h2c_command(struct rtw_dev *rtwdev, + + if (ret) { + rtw_err(rtwdev, "failed to send h2c command\n"); +- goto out; ++ return; + } + + rtw_write32(rtwdev, box_ex_reg, le32_to_cpu(h2c_cmd->msg_ext)); +@@ -361,9 +361,6 @@ static void rtw_fw_send_h2c_command(struct rtw_dev *rtwdev, + + if (++rtwdev->h2c.last_box_num >= 4) + rtwdev->h2c.last_box_num = 0; +- +-out: +- spin_unlock(&rtwdev->h2c.lock); + } + + void rtw_fw_h2c_cmd_dbg(struct rtw_dev *rtwdev, u8 *h2c) +@@ -375,15 +372,13 @@ static void rtw_fw_send_h2c_packet(struct rtw_dev *rtwdev, u8 *h2c_pkt) + { + int ret; + +- spin_lock(&rtwdev->h2c.lock); ++ lockdep_assert_held(&rtwdev->mutex); + + FW_OFFLOAD_H2C_SET_SEQ_NUM(h2c_pkt, rtwdev->h2c.seq); + ret = rtw_hci_write_data_h2c(rtwdev, h2c_pkt, H2C_PKT_SIZE); + if (ret) + rtw_err(rtwdev, "failed to send h2c packet\n"); + rtwdev->h2c.seq++; +- +- spin_unlock(&rtwdev->h2c.lock); + } + + void +diff --git a/drivers/net/wireless/realtek/rtw88/main.c b/drivers/net/wireless/realtek/rtw88/main.c +index 710ddb0283c82..c98e56890401c 100644 +--- a/drivers/net/wireless/realtek/rtw88/main.c ++++ b/drivers/net/wireless/realtek/rtw88/main.c +@@ -2067,7 +2067,6 @@ int rtw_core_init(struct rtw_dev *rtwdev) + skb_queue_head_init(&rtwdev->coex.queue); + skb_queue_head_init(&rtwdev->tx_report.queue); + +- spin_lock_init(&rtwdev->h2c.lock); + spin_lock_init(&rtwdev->txq_lock); + spin_lock_init(&rtwdev->tx_report.q_lock); + +diff --git a/drivers/net/wireless/realtek/rtw88/main.h b/drivers/net/wireless/realtek/rtw88/main.h +index f24d17f482aaa..4b57542bef1e9 100644 +--- a/drivers/net/wireless/realtek/rtw88/main.h ++++ b/drivers/net/wireless/realtek/rtw88/main.h +@@ -2020,8 +2020,6 @@ struct rtw_dev { + struct { + /* incicate the mail box to use with fw */ + u8 last_box_num; +- /* protect to send h2c to fw */ +- spinlock_t lock; + u32 seq; + } h2c; + +-- +cgit + +From 8647f7f0b9080bc2d2f6e02524782f2f02f159bc Mon Sep 17 00:00:00 2001 +From: Sascha Hauer +Date: Fri, 2 Dec 2022 09:12:18 +0100 +Subject: wifi: rtw88: Drop coex mutex + +coex->mutex is used in rtw_coex_info_request() only. Most callers of this +function hold rtwdev->mutex already, except for one callsite in the +debugfs code. The debugfs code alone doesn't justify the extra lock, so +acquire rtwdev->mutex there as well and drop the now unnecessary +spinlock. + +Signed-off-by: Sascha Hauer +Signed-off-by: Kalle Valo +Link: https://lore.kernel.org/r/20221202081224.2779981-6-s.hauer@pengutronix.de +--- + drivers/net/wireless/realtek/rtw88/coex.c | 3 +-- + drivers/net/wireless/realtek/rtw88/debug.c | 2 ++ + drivers/net/wireless/realtek/rtw88/main.c | 2 -- + drivers/net/wireless/realtek/rtw88/main.h | 2 -- + 4 files changed, 3 insertions(+), 6 deletions(-) + +diff --git a/drivers/net/wireless/realtek/rtw88/coex.c b/drivers/net/wireless/realtek/rtw88/coex.c +index 6276ad6242991..38697237ee5f0 100644 +--- a/drivers/net/wireless/realtek/rtw88/coex.c ++++ b/drivers/net/wireless/realtek/rtw88/coex.c +@@ -633,7 +633,7 @@ static struct sk_buff *rtw_coex_info_request(struct rtw_dev *rtwdev, + struct rtw_coex *coex = &rtwdev->coex; + struct sk_buff *skb_resp = NULL; + +- mutex_lock(&coex->mutex); ++ lockdep_assert_held(&rtwdev->mutex); + + rtw_fw_query_bt_mp_info(rtwdev, req); + +@@ -650,7 +650,6 @@ static struct sk_buff *rtw_coex_info_request(struct rtw_dev *rtwdev, + } + + out: +- mutex_unlock(&coex->mutex); + return skb_resp; + } + +diff --git a/drivers/net/wireless/realtek/rtw88/debug.c b/drivers/net/wireless/realtek/rtw88/debug.c +index f5b8a77ebc67b..fa3d73b333ba0 100644 +--- a/drivers/net/wireless/realtek/rtw88/debug.c ++++ b/drivers/net/wireless/realtek/rtw88/debug.c +@@ -841,7 +841,9 @@ static int rtw_debugfs_get_coex_info(struct seq_file *m, void *v) + struct rtw_debugfs_priv *debugfs_priv = m->private; + struct rtw_dev *rtwdev = debugfs_priv->rtwdev; + ++ mutex_lock(&rtwdev->mutex); + rtw_coex_display_coex_info(rtwdev, m); ++ mutex_unlock(&rtwdev->mutex); + + return 0; + } +diff --git a/drivers/net/wireless/realtek/rtw88/main.c b/drivers/net/wireless/realtek/rtw88/main.c +index c98e56890401c..0a2ce7f50f412 100644 +--- a/drivers/net/wireless/realtek/rtw88/main.c ++++ b/drivers/net/wireless/realtek/rtw88/main.c +@@ -2071,7 +2071,6 @@ int rtw_core_init(struct rtw_dev *rtwdev) + spin_lock_init(&rtwdev->tx_report.q_lock); + + mutex_init(&rtwdev->mutex); +- mutex_init(&rtwdev->coex.mutex); + mutex_init(&rtwdev->hal.tx_power_mutex); + + init_waitqueue_head(&rtwdev->coex.wait); +@@ -2143,7 +2142,6 @@ void rtw_core_deinit(struct rtw_dev *rtwdev) + } + + mutex_destroy(&rtwdev->mutex); +- mutex_destroy(&rtwdev->coex.mutex); + mutex_destroy(&rtwdev->hal.tx_power_mutex); + } + EXPORT_SYMBOL(rtw_core_deinit); +diff --git a/drivers/net/wireless/realtek/rtw88/main.h b/drivers/net/wireless/realtek/rtw88/main.h +index 4b57542bef1e9..77fd48b6cc453 100644 +--- a/drivers/net/wireless/realtek/rtw88/main.h ++++ b/drivers/net/wireless/realtek/rtw88/main.h +@@ -1501,8 +1501,6 @@ struct rtw_coex_stat { + }; + + struct rtw_coex { +- /* protects coex info request section */ +- struct mutex mutex; + struct sk_buff_head queue; + wait_queue_head_t wait; + +-- +cgit + +From 78d5bf925f30bf9f79a69ce77386902672defe68 Mon Sep 17 00:00:00 2001 +From: Sascha Hauer +Date: Fri, 2 Dec 2022 09:12:19 +0100 +Subject: wifi: rtw88: iterate over vif/sta list non-atomically + +The driver uses ieee80211_iterate_active_interfaces_atomic() +and ieee80211_iterate_stations_atomic() in several places and does +register accesses in the iterators. This doesn't cope with upcoming +USB support as registers can only be accessed non-atomically. + +Split these into a two stage process: First use the atomic iterator +functions to collect all active interfaces or stations on a list, then +iterate over the list non-atomically and call the iterator on each +entry. + +Signed-off-by: Sascha Hauer +Suggested-by: Ping-Ke shih +Reviewed-by: Ping-Ke Shih +Signed-off-by: Kalle Valo +Link: https://lore.kernel.org/r/20221202081224.2779981-7-s.hauer@pengutronix.de +--- + drivers/net/wireless/realtek/rtw88/phy.c | 6 +- + drivers/net/wireless/realtek/rtw88/ps.c | 2 +- + drivers/net/wireless/realtek/rtw88/util.c | 103 ++++++++++++++++++++++++++++++ + drivers/net/wireless/realtek/rtw88/util.h | 12 +++- + 4 files changed, 116 insertions(+), 7 deletions(-) + +diff --git a/drivers/net/wireless/realtek/rtw88/phy.c b/drivers/net/wireless/realtek/rtw88/phy.c +index bd7d05e080848..128e75a81bf3c 100644 +--- a/drivers/net/wireless/realtek/rtw88/phy.c ++++ b/drivers/net/wireless/realtek/rtw88/phy.c +@@ -300,7 +300,7 @@ static void rtw_phy_stat_rssi(struct rtw_dev *rtwdev) + + data.rtwdev = rtwdev; + data.min_rssi = U8_MAX; +- rtw_iterate_stas_atomic(rtwdev, rtw_phy_stat_rssi_iter, &data); ++ rtw_iterate_stas(rtwdev, rtw_phy_stat_rssi_iter, &data); + + dm_info->pre_min_rssi = dm_info->min_rssi; + dm_info->min_rssi = data.min_rssi; +@@ -544,7 +544,7 @@ static void rtw_phy_ra_info_update(struct rtw_dev *rtwdev) + if (rtwdev->watch_dog_cnt & 0x3) + return; + +- rtw_iterate_stas_atomic(rtwdev, rtw_phy_ra_info_update_iter, rtwdev); ++ rtw_iterate_stas(rtwdev, rtw_phy_ra_info_update_iter, rtwdev); + } + + static u32 rtw_phy_get_rrsr_mask(struct rtw_dev *rtwdev, u8 rate_idx) +@@ -597,7 +597,7 @@ static void rtw_phy_rrsr_update(struct rtw_dev *rtwdev) + struct rtw_dm_info *dm_info = &rtwdev->dm_info; + + dm_info->rrsr_mask_min = RRSR_RATE_ORDER_MAX; +- rtw_iterate_stas_atomic(rtwdev, rtw_phy_rrsr_mask_min_iter, rtwdev); ++ rtw_iterate_stas(rtwdev, rtw_phy_rrsr_mask_min_iter, rtwdev); + rtw_write32(rtwdev, REG_RRSR, dm_info->rrsr_val_init & dm_info->rrsr_mask_min); + } + +diff --git a/drivers/net/wireless/realtek/rtw88/ps.c b/drivers/net/wireless/realtek/rtw88/ps.c +index c93da743681fc..11594940d6b00 100644 +--- a/drivers/net/wireless/realtek/rtw88/ps.c ++++ b/drivers/net/wireless/realtek/rtw88/ps.c +@@ -61,7 +61,7 @@ int rtw_leave_ips(struct rtw_dev *rtwdev) + return ret; + } + +- rtw_iterate_vifs_atomic(rtwdev, rtw_restore_port_cfg_iter, rtwdev); ++ rtw_iterate_vifs(rtwdev, rtw_restore_port_cfg_iter, rtwdev); + + rtw_coex_ips_notify(rtwdev, COEX_IPS_LEAVE); + +diff --git a/drivers/net/wireless/realtek/rtw88/util.c b/drivers/net/wireless/realtek/rtw88/util.c +index cdfd66a85075a..ff3c269fb1a72 100644 +--- a/drivers/net/wireless/realtek/rtw88/util.c ++++ b/drivers/net/wireless/realtek/rtw88/util.c +@@ -105,3 +105,106 @@ void rtw_desc_to_mcsrate(u16 rate, u8 *mcs, u8 *nss) + *mcs = rate - DESC_RATEMCS0; + } + } ++ ++struct rtw_stas_entry { ++ struct list_head list; ++ struct ieee80211_sta *sta; ++}; ++ ++struct rtw_iter_stas_data { ++ struct rtw_dev *rtwdev; ++ struct list_head list; ++}; ++ ++static void rtw_collect_sta_iter(void *data, struct ieee80211_sta *sta) ++{ ++ struct rtw_iter_stas_data *iter_stas = data; ++ struct rtw_stas_entry *stas_entry; ++ ++ stas_entry = kmalloc(sizeof(*stas_entry), GFP_ATOMIC); ++ if (!stas_entry) ++ return; ++ ++ stas_entry->sta = sta; ++ list_add_tail(&stas_entry->list, &iter_stas->list); ++} ++ ++void rtw_iterate_stas(struct rtw_dev *rtwdev, ++ void (*iterator)(void *data, ++ struct ieee80211_sta *sta), ++ void *data) ++{ ++ struct rtw_iter_stas_data iter_data; ++ struct rtw_stas_entry *sta_entry, *tmp; ++ ++ /* &rtwdev->mutex makes sure no stations can be removed between ++ * collecting the stations and iterating over them. ++ */ ++ lockdep_assert_held(&rtwdev->mutex); ++ ++ iter_data.rtwdev = rtwdev; ++ INIT_LIST_HEAD(&iter_data.list); ++ ++ ieee80211_iterate_stations_atomic(rtwdev->hw, rtw_collect_sta_iter, ++ &iter_data); ++ ++ list_for_each_entry_safe(sta_entry, tmp, &iter_data.list, ++ list) { ++ list_del_init(&sta_entry->list); ++ iterator(data, sta_entry->sta); ++ kfree(sta_entry); ++ } ++} ++ ++struct rtw_vifs_entry { ++ struct list_head list; ++ struct ieee80211_vif *vif; ++ u8 mac[ETH_ALEN]; ++}; ++ ++struct rtw_iter_vifs_data { ++ struct rtw_dev *rtwdev; ++ struct list_head list; ++}; ++ ++static void rtw_collect_vif_iter(void *data, u8 *mac, struct ieee80211_vif *vif) ++{ ++ struct rtw_iter_vifs_data *iter_stas = data; ++ struct rtw_vifs_entry *vifs_entry; ++ ++ vifs_entry = kmalloc(sizeof(*vifs_entry), GFP_ATOMIC); ++ if (!vifs_entry) ++ return; ++ ++ vifs_entry->vif = vif; ++ ether_addr_copy(vifs_entry->mac, mac); ++ list_add_tail(&vifs_entry->list, &iter_stas->list); ++} ++ ++void rtw_iterate_vifs(struct rtw_dev *rtwdev, ++ void (*iterator)(void *data, u8 *mac, ++ struct ieee80211_vif *vif), ++ void *data) ++{ ++ struct rtw_iter_vifs_data iter_data; ++ struct rtw_vifs_entry *vif_entry, *tmp; ++ ++ /* &rtwdev->mutex makes sure no interfaces can be removed between ++ * collecting the interfaces and iterating over them. ++ */ ++ lockdep_assert_held(&rtwdev->mutex); ++ ++ iter_data.rtwdev = rtwdev; ++ INIT_LIST_HEAD(&iter_data.list); ++ ++ ieee80211_iterate_active_interfaces_atomic(rtwdev->hw, ++ IEEE80211_IFACE_ITER_NORMAL, ++ rtw_collect_vif_iter, &iter_data); ++ ++ list_for_each_entry_safe(vif_entry, tmp, &iter_data.list, ++ list) { ++ list_del_init(&vif_entry->list); ++ iterator(data, vif_entry->mac, vif_entry->vif); ++ kfree(vif_entry); ++ } ++} +diff --git a/drivers/net/wireless/realtek/rtw88/util.h b/drivers/net/wireless/realtek/rtw88/util.h +index 0c23b5069be0b..dc89655254002 100644 +--- a/drivers/net/wireless/realtek/rtw88/util.h ++++ b/drivers/net/wireless/realtek/rtw88/util.h +@@ -7,9 +7,6 @@ + + struct rtw_dev; + +-#define rtw_iterate_vifs(rtwdev, iterator, data) \ +- ieee80211_iterate_active_interfaces(rtwdev->hw, \ +- IEEE80211_IFACE_ITER_NORMAL, iterator, data) + #define rtw_iterate_vifs_atomic(rtwdev, iterator, data) \ + ieee80211_iterate_active_interfaces_atomic(rtwdev->hw, \ + IEEE80211_IFACE_ITER_NORMAL, iterator, data) +@@ -20,6 +17,15 @@ struct rtw_dev; + #define rtw_iterate_keys_rcu(rtwdev, vif, iterator, data) \ + ieee80211_iter_keys_rcu((rtwdev)->hw, vif, iterator, data) + ++void rtw_iterate_vifs(struct rtw_dev *rtwdev, ++ void (*iterator)(void *data, u8 *mac, ++ struct ieee80211_vif *vif), ++ void *data); ++void rtw_iterate_stas(struct rtw_dev *rtwdev, ++ void (*iterator)(void *data, ++ struct ieee80211_sta *sta), ++ void *data); ++ + static inline u8 *get_hdr_bssid(struct ieee80211_hdr *hdr) + { + __le16 fc = hdr->frame_control; +-- +cgit + +From a82dfd33d1237f6c0fb8a7077022189d1fc7ec98 Mon Sep 17 00:00:00 2001 +From: Sascha Hauer +Date: Fri, 2 Dec 2022 09:12:20 +0100 +Subject: wifi: rtw88: Add common USB chip support + +Add the common bits and pieces to add USB support to the RTW88 driver. +This is based on https://github.com/ulli-kroll/rtw88-usb.git which +itself is first written by Neo Jou. + +Signed-off-by: neo_jou +Signed-off-by: Hans Ulli Kroll +Signed-off-by: Sascha Hauer +Signed-off-by: Kalle Valo +Link: https://lore.kernel.org/r/20221202081224.2779981-8-s.hauer@pengutronix.de +--- + drivers/net/wireless/realtek/rtw88/Kconfig | 3 + + drivers/net/wireless/realtek/rtw88/Makefile | 3 + + drivers/net/wireless/realtek/rtw88/mac.c | 3 + + drivers/net/wireless/realtek/rtw88/main.c | 4 + + drivers/net/wireless/realtek/rtw88/main.h | 4 + + drivers/net/wireless/realtek/rtw88/reg.h | 1 + + drivers/net/wireless/realtek/rtw88/tx.h | 31 + + drivers/net/wireless/realtek/rtw88/usb.c | 911 ++++++++++++++++++++++++++++ + drivers/net/wireless/realtek/rtw88/usb.h | 107 ++++ + 9 files changed, 1067 insertions(+) + create mode 100644 drivers/net/wireless/realtek/rtw88/usb.c + create mode 100644 drivers/net/wireless/realtek/rtw88/usb.h + +diff --git a/drivers/net/wireless/realtek/rtw88/Kconfig b/drivers/net/wireless/realtek/rtw88/Kconfig +index e3d7cb6c12902..1624c5db69bac 100644 +--- a/drivers/net/wireless/realtek/rtw88/Kconfig ++++ b/drivers/net/wireless/realtek/rtw88/Kconfig +@@ -16,6 +16,9 @@ config RTW88_CORE + config RTW88_PCI + tristate + ++config RTW88_USB ++ tristate ++ + config RTW88_8822B + tristate + +diff --git a/drivers/net/wireless/realtek/rtw88/Makefile b/drivers/net/wireless/realtek/rtw88/Makefile +index 834c66ec0af9e..2c2b0e5133cdf 100644 +--- a/drivers/net/wireless/realtek/rtw88/Makefile ++++ b/drivers/net/wireless/realtek/rtw88/Makefile +@@ -46,3 +46,6 @@ rtw88_8821ce-objs := rtw8821ce.o + + obj-$(CONFIG_RTW88_PCI) += rtw88_pci.o + rtw88_pci-objs := pci.o ++ ++obj-$(CONFIG_RTW88_USB) += rtw88_usb.o ++rtw88_usb-objs := usb.o +diff --git a/drivers/net/wireless/realtek/rtw88/mac.c b/drivers/net/wireless/realtek/rtw88/mac.c +index c7e64f7036ac2..98777f294945f 100644 +--- a/drivers/net/wireless/realtek/rtw88/mac.c ++++ b/drivers/net/wireless/realtek/rtw88/mac.c +@@ -1048,6 +1048,9 @@ static int txdma_queue_mapping(struct rtw_dev *rtwdev) + if (rtw_chip_wcpu_11ac(rtwdev)) + rtw_write32(rtwdev, REG_H2CQ_CSR, BIT_H2CQ_FULL); + ++ if (rtw_hci_type(rtwdev) == RTW_HCI_TYPE_USB) ++ rtw_write8_set(rtwdev, REG_TXDMA_PQ_MAP, BIT_RXDMA_ARBBW_EN); ++ + return 0; + } + +diff --git a/drivers/net/wireless/realtek/rtw88/main.c b/drivers/net/wireless/realtek/rtw88/main.c +index 0a2ce7f50f412..888427cf3bdf9 100644 +--- a/drivers/net/wireless/realtek/rtw88/main.c ++++ b/drivers/net/wireless/realtek/rtw88/main.c +@@ -1783,6 +1783,10 @@ static int rtw_chip_parameter_setup(struct rtw_dev *rtwdev) + rtwdev->hci.rpwm_addr = 0x03d9; + rtwdev->hci.cpwm_addr = 0x03da; + break; ++ case RTW_HCI_TYPE_USB: ++ rtwdev->hci.rpwm_addr = 0xfe58; ++ rtwdev->hci.cpwm_addr = 0xfe57; ++ break; + default: + rtw_err(rtwdev, "unsupported hci type\n"); + return -EINVAL; +diff --git a/drivers/net/wireless/realtek/rtw88/main.h b/drivers/net/wireless/realtek/rtw88/main.h +index 77fd48b6cc453..165f299e8e1f9 100644 +--- a/drivers/net/wireless/realtek/rtw88/main.h ++++ b/drivers/net/wireless/realtek/rtw88/main.h +@@ -871,6 +871,10 @@ struct rtw_chip_ops { + bool is_tx2_path); + void (*config_txrx_mode)(struct rtw_dev *rtwdev, u8 tx_path, + u8 rx_path, bool is_tx2_path); ++ /* for USB/SDIO only */ ++ void (*fill_txdesc_checksum)(struct rtw_dev *rtwdev, ++ struct rtw_tx_pkt_info *pkt_info, ++ u8 *txdesc); + + /* for coex */ + void (*coex_set_init)(struct rtw_dev *rtwdev); +diff --git a/drivers/net/wireless/realtek/rtw88/reg.h b/drivers/net/wireless/realtek/rtw88/reg.h +index 03bd8dc53f72a..8852b24d6c2ac 100644 +--- a/drivers/net/wireless/realtek/rtw88/reg.h ++++ b/drivers/net/wireless/realtek/rtw88/reg.h +@@ -184,6 +184,7 @@ + #define BIT_TXDMA_VIQ_MAP(x) \ + (((x) & BIT_MASK_TXDMA_VIQ_MAP) << BIT_SHIFT_TXDMA_VIQ_MAP) + #define REG_TXDMA_PQ_MAP 0x010C ++#define BIT_RXDMA_ARBBW_EN BIT(0) + #define BIT_SHIFT_TXDMA_BEQ_MAP 8 + #define BIT_MASK_TXDMA_BEQ_MAP 0x3 + #define BIT_TXDMA_BEQ_MAP(x) \ +diff --git a/drivers/net/wireless/realtek/rtw88/tx.h b/drivers/net/wireless/realtek/rtw88/tx.h +index 8419603adce4a..a2f3ac326041b 100644 +--- a/drivers/net/wireless/realtek/rtw88/tx.h ++++ b/drivers/net/wireless/realtek/rtw88/tx.h +@@ -71,6 +71,14 @@ + le32p_replace_bits((__le32 *)(txdesc) + 0x03, value, BIT(15)) + #define SET_TX_DESC_BT_NULL(txdesc, value) \ + le32p_replace_bits((__le32 *)(txdesc) + 0x02, value, BIT(23)) ++#define SET_TX_DESC_TXDESC_CHECKSUM(txdesc, value) \ ++ le32p_replace_bits((__le32 *)(txdesc) + 0x07, value, GENMASK(15, 0)) ++#define SET_TX_DESC_DMA_TXAGG_NUM(txdesc, value) \ ++ le32p_replace_bits((__le32 *)(txdesc) + 0x07, value, GENMASK(31, 24)) ++#define GET_TX_DESC_PKT_OFFSET(txdesc) \ ++ le32_get_bits(*((__le32 *)(txdesc) + 0x01), GENMASK(28, 24)) ++#define GET_TX_DESC_QSEL(txdesc) \ ++ le32_get_bits(*((__le32 *)(txdesc) + 0x01), GENMASK(12, 8)) + + enum rtw_tx_desc_queue_select { + TX_DESC_QSEL_TID0 = 0, +@@ -123,4 +131,27 @@ rtw_tx_write_data_h2c_get(struct rtw_dev *rtwdev, + struct rtw_tx_pkt_info *pkt_info, + u8 *buf, u32 size); + ++static inline ++void fill_txdesc_checksum_common(u8 *txdesc, size_t words) ++{ ++ __le16 chksum = 0; ++ __le16 *data = (__le16 *)(txdesc); ++ ++ SET_TX_DESC_TXDESC_CHECKSUM(txdesc, 0x0000); ++ ++ while (words--) ++ chksum ^= *data++; ++ ++ SET_TX_DESC_TXDESC_CHECKSUM(txdesc, __le16_to_cpu(chksum)); ++} ++ ++static inline void rtw_tx_fill_txdesc_checksum(struct rtw_dev *rtwdev, ++ struct rtw_tx_pkt_info *pkt_info, ++ u8 *txdesc) ++{ ++ const struct rtw_chip_info *chip = rtwdev->chip; ++ ++ chip->ops->fill_txdesc_checksum(rtwdev, pkt_info, txdesc); ++} ++ + #endif +diff --git a/drivers/net/wireless/realtek/rtw88/usb.c b/drivers/net/wireless/realtek/rtw88/usb.c +new file mode 100644 +index 0000000000000..4ef38279b64c9 +--- /dev/null ++++ b/drivers/net/wireless/realtek/rtw88/usb.c +@@ -0,0 +1,911 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause ++/* Copyright(c) 2018-2019 Realtek Corporation ++ */ ++ ++#include ++#include ++#include ++#include "main.h" ++#include "debug.h" ++#include "reg.h" ++#include "tx.h" ++#include "rx.h" ++#include "fw.h" ++#include "ps.h" ++#include "usb.h" ++ ++#define RTW_USB_MAX_RXQ_LEN 512 ++ ++struct rtw_usb_txcb { ++ struct rtw_dev *rtwdev; ++ struct sk_buff_head tx_ack_queue; ++}; ++ ++static void rtw_usb_fill_tx_checksum(struct rtw_usb *rtwusb, ++ struct sk_buff *skb, int agg_num) ++{ ++ struct rtw_dev *rtwdev = rtwusb->rtwdev; ++ struct rtw_tx_pkt_info pkt_info; ++ ++ SET_TX_DESC_DMA_TXAGG_NUM(skb->data, agg_num); ++ pkt_info.pkt_offset = GET_TX_DESC_PKT_OFFSET(skb->data); ++ rtw_tx_fill_txdesc_checksum(rtwdev, &pkt_info, skb->data); ++} ++ ++static u32 rtw_usb_read(struct rtw_dev *rtwdev, u32 addr, u16 len) ++{ ++ struct rtw_usb *rtwusb = rtw_get_usb_priv(rtwdev); ++ struct usb_device *udev = rtwusb->udev; ++ __le32 *data; ++ unsigned long flags; ++ int idx, ret; ++ static int count; ++ ++ spin_lock_irqsave(&rtwusb->usb_lock, flags); ++ ++ idx = rtwusb->usb_data_index; ++ rtwusb->usb_data_index = (idx + 1) & (RTW_USB_MAX_RXTX_COUNT - 1); ++ ++ spin_unlock_irqrestore(&rtwusb->usb_lock, flags); ++ ++ data = &rtwusb->usb_data[idx]; ++ ++ ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), ++ RTW_USB_CMD_REQ, RTW_USB_CMD_READ, addr, ++ RTW_USB_VENQT_CMD_IDX, data, len, 1000); ++ if (ret < 0 && ret != -ENODEV && count++ < 4) ++ rtw_err(rtwdev, "read register 0x%x failed with %d\n", ++ addr, ret); ++ ++ return le32_to_cpu(*data); ++} ++ ++static u8 rtw_usb_read8(struct rtw_dev *rtwdev, u32 addr) ++{ ++ return (u8)rtw_usb_read(rtwdev, addr, 1); ++} ++ ++static u16 rtw_usb_read16(struct rtw_dev *rtwdev, u32 addr) ++{ ++ return (u16)rtw_usb_read(rtwdev, addr, 2); ++} ++ ++static u32 rtw_usb_read32(struct rtw_dev *rtwdev, u32 addr) ++{ ++ return (u32)rtw_usb_read(rtwdev, addr, 4); ++} ++ ++static void rtw_usb_write(struct rtw_dev *rtwdev, u32 addr, u32 val, int len) ++{ ++ struct rtw_usb *rtwusb = (struct rtw_usb *)rtwdev->priv; ++ struct usb_device *udev = rtwusb->udev; ++ unsigned long flags; ++ __le32 *data; ++ int idx, ret; ++ static int count; ++ ++ spin_lock_irqsave(&rtwusb->usb_lock, flags); ++ ++ idx = rtwusb->usb_data_index; ++ rtwusb->usb_data_index = (idx + 1) & (RTW_USB_MAX_RXTX_COUNT - 1); ++ ++ spin_unlock_irqrestore(&rtwusb->usb_lock, flags); ++ ++ data = &rtwusb->usb_data[idx]; ++ ++ *data = cpu_to_le32(val); ++ ++ ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), ++ RTW_USB_CMD_REQ, RTW_USB_CMD_WRITE, ++ addr, 0, data, len, 30000); ++ if (ret < 0 && ret != -ENODEV && count++ < 4) ++ rtw_err(rtwdev, "write register 0x%x failed with %d\n", ++ addr, ret); ++} ++ ++static void rtw_usb_write8(struct rtw_dev *rtwdev, u32 addr, u8 val) ++{ ++ rtw_usb_write(rtwdev, addr, val, 1); ++} ++ ++static void rtw_usb_write16(struct rtw_dev *rtwdev, u32 addr, u16 val) ++{ ++ rtw_usb_write(rtwdev, addr, val, 2); ++} ++ ++static void rtw_usb_write32(struct rtw_dev *rtwdev, u32 addr, u32 val) ++{ ++ rtw_usb_write(rtwdev, addr, val, 4); ++} ++ ++static int rtw_usb_parse(struct rtw_dev *rtwdev, ++ struct usb_interface *interface) ++{ ++ struct rtw_usb *rtwusb = rtw_get_usb_priv(rtwdev); ++ struct usb_host_interface *host_interface = &interface->altsetting[0]; ++ struct usb_interface_descriptor *interface_desc = &host_interface->desc; ++ struct usb_endpoint_descriptor *endpoint; ++ struct usb_device *usbd = interface_to_usbdev(interface); ++ int num_out_pipes = 0; ++ int i; ++ u8 num; ++ ++ for (i = 0; i < interface_desc->bNumEndpoints; i++) { ++ endpoint = &host_interface->endpoint[i].desc; ++ num = usb_endpoint_num(endpoint); ++ ++ if (usb_endpoint_dir_in(endpoint) && ++ usb_endpoint_xfer_bulk(endpoint)) { ++ if (rtwusb->pipe_in) { ++ rtw_err(rtwdev, "IN pipes overflow\n"); ++ return -EINVAL; ++ } ++ ++ rtwusb->pipe_in = num; ++ } ++ ++ if (usb_endpoint_dir_in(endpoint) && ++ usb_endpoint_xfer_int(endpoint)) { ++ if (rtwusb->pipe_interrupt) { ++ rtw_err(rtwdev, "INT pipes overflow\n"); ++ return -EINVAL; ++ } ++ ++ rtwusb->pipe_interrupt = num; ++ } ++ ++ if (usb_endpoint_dir_out(endpoint) && ++ usb_endpoint_xfer_bulk(endpoint)) { ++ if (num_out_pipes >= ARRAY_SIZE(rtwusb->out_ep)) { ++ rtw_err(rtwdev, "OUT pipes overflow\n"); ++ return -EINVAL; ++ } ++ ++ rtwusb->out_ep[num_out_pipes++] = num; ++ } ++ } ++ ++ switch (usbd->speed) { ++ case USB_SPEED_LOW: ++ case USB_SPEED_FULL: ++ rtwusb->bulkout_size = RTW_USB_FULL_SPEED_BULK_SIZE; ++ break; ++ case USB_SPEED_HIGH: ++ rtwusb->bulkout_size = RTW_USB_HIGH_SPEED_BULK_SIZE; ++ break; ++ case USB_SPEED_SUPER: ++ rtwusb->bulkout_size = RTW_USB_SUPER_SPEED_BULK_SIZE; ++ break; ++ default: ++ rtw_err(rtwdev, "failed to detect usb speed\n"); ++ return -EINVAL; ++ } ++ ++ rtwdev->hci.bulkout_num = num_out_pipes; ++ ++ switch (num_out_pipes) { ++ case 4: ++ case 3: ++ rtwusb->qsel_to_ep[TX_DESC_QSEL_TID0] = 2; ++ rtwusb->qsel_to_ep[TX_DESC_QSEL_TID1] = 2; ++ rtwusb->qsel_to_ep[TX_DESC_QSEL_TID2] = 2; ++ rtwusb->qsel_to_ep[TX_DESC_QSEL_TID3] = 2; ++ rtwusb->qsel_to_ep[TX_DESC_QSEL_TID4] = 1; ++ rtwusb->qsel_to_ep[TX_DESC_QSEL_TID5] = 1; ++ rtwusb->qsel_to_ep[TX_DESC_QSEL_TID6] = 0; ++ rtwusb->qsel_to_ep[TX_DESC_QSEL_TID7] = 0; ++ break; ++ case 2: ++ rtwusb->qsel_to_ep[TX_DESC_QSEL_TID0] = 1; ++ rtwusb->qsel_to_ep[TX_DESC_QSEL_TID1] = 1; ++ rtwusb->qsel_to_ep[TX_DESC_QSEL_TID2] = 1; ++ rtwusb->qsel_to_ep[TX_DESC_QSEL_TID3] = 1; ++ break; ++ case 1: ++ break; ++ default: ++ rtw_err(rtwdev, "failed to get out_pipes(%d)\n", num_out_pipes); ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static void rtw_usb_write_port_tx_complete(struct urb *urb) ++{ ++ struct rtw_usb_txcb *txcb = urb->context; ++ struct rtw_dev *rtwdev = txcb->rtwdev; ++ struct ieee80211_hw *hw = rtwdev->hw; ++ ++ while (true) { ++ struct sk_buff *skb = skb_dequeue(&txcb->tx_ack_queue); ++ struct ieee80211_tx_info *info; ++ struct rtw_usb_tx_data *tx_data; ++ ++ if (!skb) ++ break; ++ ++ info = IEEE80211_SKB_CB(skb); ++ tx_data = rtw_usb_get_tx_data(skb); ++ ++ /* enqueue to wait for tx report */ ++ if (info->flags & IEEE80211_TX_CTL_REQ_TX_STATUS) { ++ rtw_tx_report_enqueue(rtwdev, skb, tx_data->sn); ++ continue; ++ } ++ ++ /* always ACK for others, then they won't be marked as drop */ ++ ieee80211_tx_info_clear_status(info); ++ if (info->flags & IEEE80211_TX_CTL_NO_ACK) ++ info->flags |= IEEE80211_TX_STAT_NOACK_TRANSMITTED; ++ else ++ info->flags |= IEEE80211_TX_STAT_ACK; ++ ++ ieee80211_tx_status_irqsafe(hw, skb); ++ } ++ ++ kfree(txcb); ++} ++ ++static int qsel_to_ep(struct rtw_usb *rtwusb, unsigned int qsel) ++{ ++ if (qsel >= ARRAY_SIZE(rtwusb->qsel_to_ep)) ++ return 0; ++ ++ return rtwusb->qsel_to_ep[qsel]; ++} ++ ++static int rtw_usb_write_port(struct rtw_dev *rtwdev, u8 qsel, struct sk_buff *skb, ++ usb_complete_t cb, void *context) ++{ ++ struct rtw_usb *rtwusb = rtw_get_usb_priv(rtwdev); ++ struct usb_device *usbd = rtwusb->udev; ++ struct urb *urb; ++ unsigned int pipe; ++ int ret; ++ int ep = qsel_to_ep(rtwusb, qsel); ++ ++ pipe = usb_sndbulkpipe(usbd, rtwusb->out_ep[ep]); ++ urb = usb_alloc_urb(0, GFP_ATOMIC); ++ if (!urb) ++ return -ENOMEM; ++ ++ usb_fill_bulk_urb(urb, usbd, pipe, skb->data, skb->len, cb, context); ++ ret = usb_submit_urb(urb, GFP_ATOMIC); ++ ++ usb_free_urb(urb); ++ ++ return ret; ++} ++ ++static bool rtw_usb_tx_agg_skb(struct rtw_usb *rtwusb, struct sk_buff_head *list) ++{ ++ struct rtw_dev *rtwdev = rtwusb->rtwdev; ++ struct rtw_usb_txcb *txcb; ++ struct sk_buff *skb_head; ++ struct sk_buff *skb_iter; ++ int agg_num = 0; ++ unsigned int align_next = 0; ++ ++ if (skb_queue_empty(list)) ++ return false; ++ ++ txcb = kmalloc(sizeof(*txcb), GFP_ATOMIC); ++ if (!txcb) ++ return false; ++ ++ txcb->rtwdev = rtwdev; ++ skb_queue_head_init(&txcb->tx_ack_queue); ++ ++ skb_iter = skb_dequeue(list); ++ ++ if (skb_queue_empty(list)) { ++ skb_head = skb_iter; ++ goto queue; ++ } ++ ++ skb_head = dev_alloc_skb(RTW_USB_MAX_XMITBUF_SZ); ++ if (!skb_head) { ++ skb_head = skb_iter; ++ goto queue; ++ } ++ ++ while (skb_iter) { ++ unsigned long flags; ++ ++ skb_put(skb_head, align_next); ++ skb_put_data(skb_head, skb_iter->data, skb_iter->len); ++ ++ align_next = ALIGN(skb_iter->len, 8) - skb_iter->len; ++ ++ agg_num++; ++ ++ skb_queue_tail(&txcb->tx_ack_queue, skb_iter); ++ ++ spin_lock_irqsave(&list->lock, flags); ++ ++ skb_iter = skb_peek(list); ++ ++ if (skb_iter && skb_iter->len + skb_head->len <= RTW_USB_MAX_XMITBUF_SZ) ++ __skb_unlink(skb_iter, list); ++ else ++ skb_iter = NULL; ++ spin_unlock_irqrestore(&list->lock, flags); ++ } ++ ++ if (agg_num > 1) ++ rtw_usb_fill_tx_checksum(rtwusb, skb_head, agg_num); ++ ++queue: ++ skb_queue_tail(&txcb->tx_ack_queue, skb_head); ++ ++ rtw_usb_write_port(rtwdev, GET_TX_DESC_QSEL(skb_head->data), skb_head, ++ rtw_usb_write_port_tx_complete, txcb); ++ ++ return true; ++} ++ ++static void rtw_usb_tx_handler(struct work_struct *work) ++{ ++ struct rtw_usb *rtwusb = container_of(work, struct rtw_usb, tx_work); ++ int i, limit; ++ ++ for (i = ARRAY_SIZE(rtwusb->tx_queue) - 1; i >= 0; i--) { ++ for (limit = 0; limit < 200; limit++) { ++ struct sk_buff_head *list = &rtwusb->tx_queue[i]; ++ ++ if (!rtw_usb_tx_agg_skb(rtwusb, list)) ++ break; ++ } ++ } ++} ++ ++static void rtw_usb_tx_queue_purge(struct rtw_usb *rtwusb) ++{ ++ int i; ++ ++ for (i = 0; i < ARRAY_SIZE(rtwusb->tx_queue); i++) ++ skb_queue_purge(&rtwusb->tx_queue[i]); ++} ++ ++static void rtw_usb_write_port_complete(struct urb *urb) ++{ ++ struct sk_buff *skb = urb->context; ++ ++ dev_kfree_skb_any(skb); ++} ++ ++static int rtw_usb_write_data(struct rtw_dev *rtwdev, ++ struct rtw_tx_pkt_info *pkt_info, ++ u8 *buf) ++{ ++ const struct rtw_chip_info *chip = rtwdev->chip; ++ struct sk_buff *skb; ++ unsigned int desclen, headsize, size; ++ u8 qsel; ++ int ret = 0; ++ ++ size = pkt_info->tx_pkt_size; ++ qsel = pkt_info->qsel; ++ desclen = chip->tx_pkt_desc_sz; ++ headsize = pkt_info->offset ? pkt_info->offset : desclen; ++ ++ skb = dev_alloc_skb(headsize + size); ++ if (unlikely(!skb)) ++ return -ENOMEM; ++ ++ skb_reserve(skb, headsize); ++ skb_put_data(skb, buf, size); ++ skb_push(skb, headsize); ++ memset(skb->data, 0, headsize); ++ rtw_tx_fill_tx_desc(pkt_info, skb); ++ rtw_tx_fill_txdesc_checksum(rtwdev, pkt_info, skb->data); ++ ++ ret = rtw_usb_write_port(rtwdev, qsel, skb, ++ rtw_usb_write_port_complete, skb); ++ if (unlikely(ret)) ++ rtw_err(rtwdev, "failed to do USB write, ret=%d\n", ret); ++ ++ return ret; ++} ++ ++static int rtw_usb_write_data_rsvd_page(struct rtw_dev *rtwdev, u8 *buf, ++ u32 size) ++{ ++ const struct rtw_chip_info *chip = rtwdev->chip; ++ struct rtw_usb *rtwusb; ++ struct rtw_tx_pkt_info pkt_info = {0}; ++ u32 len, desclen; ++ ++ rtwusb = rtw_get_usb_priv(rtwdev); ++ ++ pkt_info.tx_pkt_size = size; ++ pkt_info.qsel = TX_DESC_QSEL_BEACON; ++ ++ desclen = chip->tx_pkt_desc_sz; ++ len = desclen + size; ++ if (len % rtwusb->bulkout_size == 0) { ++ len += RTW_USB_PACKET_OFFSET_SZ; ++ pkt_info.offset = desclen + RTW_USB_PACKET_OFFSET_SZ; ++ pkt_info.pkt_offset = 1; ++ } else { ++ pkt_info.offset = desclen; ++ } ++ ++ return rtw_usb_write_data(rtwdev, &pkt_info, buf); ++} ++ ++static int rtw_usb_write_data_h2c(struct rtw_dev *rtwdev, u8 *buf, u32 size) ++{ ++ struct rtw_tx_pkt_info pkt_info = {0}; ++ ++ pkt_info.tx_pkt_size = size; ++ pkt_info.qsel = TX_DESC_QSEL_H2C; ++ ++ return rtw_usb_write_data(rtwdev, &pkt_info, buf); ++} ++ ++static u8 rtw_usb_tx_queue_mapping_to_qsel(struct sk_buff *skb) ++{ ++ struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; ++ __le16 fc = hdr->frame_control; ++ u8 qsel; ++ ++ if (unlikely(ieee80211_is_mgmt(fc) || ieee80211_is_ctl(fc))) ++ qsel = TX_DESC_QSEL_MGMT; ++ else if (skb_get_queue_mapping(skb) <= IEEE80211_AC_BK) ++ qsel = skb->priority; ++ else ++ qsel = TX_DESC_QSEL_BEACON; ++ ++ return qsel; ++} ++ ++static int rtw_usb_tx_write(struct rtw_dev *rtwdev, ++ struct rtw_tx_pkt_info *pkt_info, ++ struct sk_buff *skb) ++{ ++ struct rtw_usb *rtwusb = rtw_get_usb_priv(rtwdev); ++ const struct rtw_chip_info *chip = rtwdev->chip; ++ struct rtw_usb_tx_data *tx_data; ++ u8 *pkt_desc; ++ int ep; ++ ++ pkt_desc = skb_push(skb, chip->tx_pkt_desc_sz); ++ memset(pkt_desc, 0, chip->tx_pkt_desc_sz); ++ pkt_info->qsel = rtw_usb_tx_queue_mapping_to_qsel(skb); ++ ep = qsel_to_ep(rtwusb, pkt_info->qsel); ++ rtw_tx_fill_tx_desc(pkt_info, skb); ++ rtw_tx_fill_txdesc_checksum(rtwdev, pkt_info, skb->data); ++ tx_data = rtw_usb_get_tx_data(skb); ++ tx_data->sn = pkt_info->sn; ++ ++ skb_queue_tail(&rtwusb->tx_queue[ep], skb); ++ ++ return 0; ++} ++ ++static void rtw_usb_tx_kick_off(struct rtw_dev *rtwdev) ++{ ++ struct rtw_usb *rtwusb = rtw_get_usb_priv(rtwdev); ++ ++ queue_work(rtwusb->txwq, &rtwusb->tx_work); ++} ++ ++static void rtw_usb_rx_handler(struct work_struct *work) ++{ ++ struct rtw_usb *rtwusb = container_of(work, struct rtw_usb, rx_work); ++ struct rtw_dev *rtwdev = rtwusb->rtwdev; ++ const struct rtw_chip_info *chip = rtwdev->chip; ++ struct rtw_rx_pkt_stat pkt_stat; ++ struct ieee80211_rx_status rx_status; ++ struct sk_buff *skb; ++ u32 pkt_desc_sz = chip->rx_pkt_desc_sz; ++ u32 pkt_offset; ++ u8 *rx_desc; ++ int limit; ++ ++ for (limit = 0; limit < 200; limit++) { ++ skb = skb_dequeue(&rtwusb->rx_queue); ++ if (!skb) ++ break; ++ ++ rx_desc = skb->data; ++ chip->ops->query_rx_desc(rtwdev, rx_desc, &pkt_stat, ++ &rx_status); ++ pkt_offset = pkt_desc_sz + pkt_stat.drv_info_sz + ++ pkt_stat.shift; ++ ++ if (pkt_stat.is_c2h) { ++ skb_put(skb, pkt_stat.pkt_len + pkt_offset); ++ rtw_fw_c2h_cmd_rx_irqsafe(rtwdev, pkt_offset, skb); ++ continue; ++ } ++ ++ if (skb_queue_len(&rtwusb->rx_queue) >= RTW_USB_MAX_RXQ_LEN) { ++ rtw_err(rtwdev, "failed to get rx_queue, overflow\n"); ++ dev_kfree_skb_any(skb); ++ continue; ++ } ++ ++ skb_put(skb, pkt_stat.pkt_len); ++ skb_reserve(skb, pkt_offset); ++ memcpy(skb->cb, &rx_status, sizeof(rx_status)); ++ ieee80211_rx_irqsafe(rtwdev->hw, skb); ++ } ++} ++ ++static void rtw_usb_read_port_complete(struct urb *urb); ++ ++static void rtw_usb_rx_resubmit(struct rtw_usb *rtwusb, struct rx_usb_ctrl_block *rxcb) ++{ ++ struct rtw_dev *rtwdev = rtwusb->rtwdev; ++ int error; ++ ++ rxcb->rx_skb = alloc_skb(RTW_USB_MAX_RECVBUF_SZ, GFP_ATOMIC); ++ if (!rxcb->rx_skb) ++ return; ++ ++ usb_fill_bulk_urb(rxcb->rx_urb, rtwusb->udev, ++ usb_rcvbulkpipe(rtwusb->udev, rtwusb->pipe_in), ++ rxcb->rx_skb->data, RTW_USB_MAX_RECVBUF_SZ, ++ rtw_usb_read_port_complete, rxcb); ++ ++ error = usb_submit_urb(rxcb->rx_urb, GFP_ATOMIC); ++ if (error) { ++ kfree_skb(rxcb->rx_skb); ++ if (error != -ENODEV) ++ rtw_err(rtwdev, "Err sending rx data urb %d\n", ++ error); ++ } ++} ++ ++static void rtw_usb_read_port_complete(struct urb *urb) ++{ ++ struct rx_usb_ctrl_block *rxcb = urb->context; ++ struct rtw_dev *rtwdev = rxcb->rtwdev; ++ struct rtw_usb *rtwusb = rtw_get_usb_priv(rtwdev); ++ struct sk_buff *skb = rxcb->rx_skb; ++ ++ if (urb->status == 0) { ++ if (urb->actual_length >= RTW_USB_MAX_RECVBUF_SZ || ++ urb->actual_length < 24) { ++ rtw_err(rtwdev, "failed to get urb length:%d\n", ++ urb->actual_length); ++ if (skb) ++ dev_kfree_skb_any(skb); ++ } else { ++ skb_queue_tail(&rtwusb->rx_queue, skb); ++ queue_work(rtwusb->rxwq, &rtwusb->rx_work); ++ } ++ rtw_usb_rx_resubmit(rtwusb, rxcb); ++ } else { ++ switch (urb->status) { ++ case -EINVAL: ++ case -EPIPE: ++ case -ENODEV: ++ case -ESHUTDOWN: ++ case -ENOENT: ++ case -EPROTO: ++ case -EILSEQ: ++ case -ETIME: ++ case -ECOMM: ++ case -EOVERFLOW: ++ case -EINPROGRESS: ++ break; ++ default: ++ rtw_err(rtwdev, "status %d\n", urb->status); ++ break; ++ } ++ if (skb) ++ dev_kfree_skb_any(skb); ++ } ++} ++ ++static void rtw_usb_cancel_rx_bufs(struct rtw_usb *rtwusb) ++{ ++ struct rx_usb_ctrl_block *rxcb; ++ int i; ++ ++ for (i = 0; i < RTW_USB_RXCB_NUM; i++) { ++ rxcb = &rtwusb->rx_cb[i]; ++ if (rxcb->rx_urb) ++ usb_kill_urb(rxcb->rx_urb); ++ } ++} ++ ++static void rtw_usb_free_rx_bufs(struct rtw_usb *rtwusb) ++{ ++ struct rx_usb_ctrl_block *rxcb; ++ int i; ++ ++ for (i = 0; i < RTW_USB_RXCB_NUM; i++) { ++ rxcb = &rtwusb->rx_cb[i]; ++ if (rxcb->rx_urb) { ++ usb_kill_urb(rxcb->rx_urb); ++ usb_free_urb(rxcb->rx_urb); ++ } ++ } ++} ++ ++static int rtw_usb_alloc_rx_bufs(struct rtw_usb *rtwusb) ++{ ++ int i; ++ ++ for (i = 0; i < RTW_USB_RXCB_NUM; i++) { ++ struct rx_usb_ctrl_block *rxcb = &rtwusb->rx_cb[i]; ++ ++ rxcb->n = i; ++ rxcb->rtwdev = rtwusb->rtwdev; ++ rxcb->rx_urb = usb_alloc_urb(0, GFP_KERNEL); ++ if (!rxcb->rx_urb) ++ goto err; ++ } ++ ++ return 0; ++err: ++ rtw_usb_free_rx_bufs(rtwusb); ++ return -ENOMEM; ++} ++ ++static int rtw_usb_setup(struct rtw_dev *rtwdev) ++{ ++ /* empty function for rtw_hci_ops */ ++ return 0; ++} ++ ++static int rtw_usb_start(struct rtw_dev *rtwdev) ++{ ++ return 0; ++} ++ ++static void rtw_usb_stop(struct rtw_dev *rtwdev) ++{ ++} ++ ++static void rtw_usb_deep_ps(struct rtw_dev *rtwdev, bool enter) ++{ ++ /* empty function for rtw_hci_ops */ ++} ++ ++static void rtw_usb_link_ps(struct rtw_dev *rtwdev, bool enter) ++{ ++ /* empty function for rtw_hci_ops */ ++} ++ ++static void rtw_usb_interface_cfg(struct rtw_dev *rtwdev) ++{ ++ /* empty function for rtw_hci_ops */ ++} ++ ++static struct rtw_hci_ops rtw_usb_ops = { ++ .tx_write = rtw_usb_tx_write, ++ .tx_kick_off = rtw_usb_tx_kick_off, ++ .setup = rtw_usb_setup, ++ .start = rtw_usb_start, ++ .stop = rtw_usb_stop, ++ .deep_ps = rtw_usb_deep_ps, ++ .link_ps = rtw_usb_link_ps, ++ .interface_cfg = rtw_usb_interface_cfg, ++ ++ .write8 = rtw_usb_write8, ++ .write16 = rtw_usb_write16, ++ .write32 = rtw_usb_write32, ++ .read8 = rtw_usb_read8, ++ .read16 = rtw_usb_read16, ++ .read32 = rtw_usb_read32, ++ ++ .write_data_rsvd_page = rtw_usb_write_data_rsvd_page, ++ .write_data_h2c = rtw_usb_write_data_h2c, ++}; ++ ++static int rtw_usb_init_rx(struct rtw_dev *rtwdev) ++{ ++ struct rtw_usb *rtwusb = rtw_get_usb_priv(rtwdev); ++ int i; ++ ++ rtwusb->rxwq = create_singlethread_workqueue("rtw88_usb: rx wq"); ++ if (!rtwusb->rxwq) { ++ rtw_err(rtwdev, "failed to create RX work queue\n"); ++ return -ENOMEM; ++ } ++ ++ skb_queue_head_init(&rtwusb->rx_queue); ++ ++ INIT_WORK(&rtwusb->rx_work, rtw_usb_rx_handler); ++ ++ for (i = 0; i < RTW_USB_RXCB_NUM; i++) { ++ struct rx_usb_ctrl_block *rxcb = &rtwusb->rx_cb[i]; ++ ++ rtw_usb_rx_resubmit(rtwusb, rxcb); ++ } ++ ++ return 0; ++} ++ ++static void rtw_usb_deinit_rx(struct rtw_dev *rtwdev) ++{ ++ struct rtw_usb *rtwusb = rtw_get_usb_priv(rtwdev); ++ ++ skb_queue_purge(&rtwusb->rx_queue); ++ ++ flush_workqueue(rtwusb->rxwq); ++ destroy_workqueue(rtwusb->rxwq); ++} ++ ++static int rtw_usb_init_tx(struct rtw_dev *rtwdev) ++{ ++ struct rtw_usb *rtwusb = rtw_get_usb_priv(rtwdev); ++ int i; ++ ++ rtwusb->txwq = create_singlethread_workqueue("rtw88_usb: tx wq"); ++ if (!rtwusb->txwq) { ++ rtw_err(rtwdev, "failed to create TX work queue\n"); ++ return -ENOMEM; ++ } ++ ++ for (i = 0; i < ARRAY_SIZE(rtwusb->tx_queue); i++) ++ skb_queue_head_init(&rtwusb->tx_queue[i]); ++ ++ INIT_WORK(&rtwusb->tx_work, rtw_usb_tx_handler); ++ ++ return 0; ++} ++ ++static void rtw_usb_deinit_tx(struct rtw_dev *rtwdev) ++{ ++ struct rtw_usb *rtwusb = rtw_get_usb_priv(rtwdev); ++ ++ rtw_usb_tx_queue_purge(rtwusb); ++ flush_workqueue(rtwusb->txwq); ++ destroy_workqueue(rtwusb->txwq); ++} ++ ++static int rtw_usb_intf_init(struct rtw_dev *rtwdev, ++ struct usb_interface *intf) ++{ ++ struct rtw_usb *rtwusb = rtw_get_usb_priv(rtwdev); ++ struct usb_device *udev = usb_get_dev(interface_to_usbdev(intf)); ++ int ret; ++ ++ rtwusb->udev = udev; ++ ret = rtw_usb_parse(rtwdev, intf); ++ if (ret) ++ return ret; ++ ++ rtwusb->usb_data = kcalloc(RTW_USB_MAX_RXTX_COUNT, sizeof(u32), ++ GFP_KERNEL); ++ if (!rtwusb->usb_data) ++ return -ENOMEM; ++ ++ usb_set_intfdata(intf, rtwdev->hw); ++ ++ SET_IEEE80211_DEV(rtwdev->hw, &intf->dev); ++ spin_lock_init(&rtwusb->usb_lock); ++ ++ return 0; ++} ++ ++static void rtw_usb_intf_deinit(struct rtw_dev *rtwdev, ++ struct usb_interface *intf) ++{ ++ struct rtw_usb *rtwusb = rtw_get_usb_priv(rtwdev); ++ ++ usb_put_dev(rtwusb->udev); ++ usb_set_intfdata(intf, NULL); ++} ++ ++int rtw_usb_probe(struct usb_interface *intf, const struct usb_device_id *id) ++{ ++ struct rtw_dev *rtwdev; ++ struct ieee80211_hw *hw; ++ struct rtw_usb *rtwusb; ++ int drv_data_size; ++ int ret; ++ ++ drv_data_size = sizeof(struct rtw_dev) + sizeof(struct rtw_usb); ++ hw = ieee80211_alloc_hw(drv_data_size, &rtw_ops); ++ if (!hw) ++ return -ENOMEM; ++ ++ rtwdev = hw->priv; ++ rtwdev->hw = hw; ++ rtwdev->dev = &intf->dev; ++ rtwdev->chip = (struct rtw_chip_info *)id->driver_info; ++ rtwdev->hci.ops = &rtw_usb_ops; ++ rtwdev->hci.type = RTW_HCI_TYPE_USB; ++ ++ rtwusb = rtw_get_usb_priv(rtwdev); ++ rtwusb->rtwdev = rtwdev; ++ ++ ret = rtw_usb_alloc_rx_bufs(rtwusb); ++ if (ret) ++ return ret; ++ ++ ret = rtw_core_init(rtwdev); ++ if (ret) ++ goto err_release_hw; ++ ++ ret = rtw_usb_intf_init(rtwdev, intf); ++ if (ret) { ++ rtw_err(rtwdev, "failed to init USB interface\n"); ++ goto err_deinit_core; ++ } ++ ++ ret = rtw_usb_init_tx(rtwdev); ++ if (ret) { ++ rtw_err(rtwdev, "failed to init USB TX\n"); ++ goto err_destroy_usb; ++ } ++ ++ ret = rtw_usb_init_rx(rtwdev); ++ if (ret) { ++ rtw_err(rtwdev, "failed to init USB RX\n"); ++ goto err_destroy_txwq; ++ } ++ ++ ret = rtw_chip_info_setup(rtwdev); ++ if (ret) { ++ rtw_err(rtwdev, "failed to setup chip information\n"); ++ goto err_destroy_rxwq; ++ } ++ ++ ret = rtw_register_hw(rtwdev, rtwdev->hw); ++ if (ret) { ++ rtw_err(rtwdev, "failed to register hw\n"); ++ goto err_destroy_rxwq; ++ } ++ ++ return 0; ++ ++err_destroy_rxwq: ++ rtw_usb_deinit_rx(rtwdev); ++ ++err_destroy_txwq: ++ rtw_usb_deinit_tx(rtwdev); ++ ++err_destroy_usb: ++ rtw_usb_intf_deinit(rtwdev, intf); ++ ++err_deinit_core: ++ rtw_core_deinit(rtwdev); ++ ++err_release_hw: ++ ieee80211_free_hw(hw); ++ ++ return ret; ++} ++EXPORT_SYMBOL(rtw_usb_probe); ++ ++void rtw_usb_disconnect(struct usb_interface *intf) ++{ ++ struct ieee80211_hw *hw = usb_get_intfdata(intf); ++ struct rtw_dev *rtwdev; ++ struct rtw_usb *rtwusb; ++ ++ if (!hw) ++ return; ++ ++ rtwdev = hw->priv; ++ rtwusb = rtw_get_usb_priv(rtwdev); ++ ++ rtw_usb_cancel_rx_bufs(rtwusb); ++ ++ rtw_unregister_hw(rtwdev, hw); ++ rtw_usb_deinit_tx(rtwdev); ++ rtw_usb_deinit_rx(rtwdev); ++ ++ if (rtwusb->udev->state != USB_STATE_NOTATTACHED) ++ usb_reset_device(rtwusb->udev); ++ ++ rtw_usb_free_rx_bufs(rtwusb); ++ ++ rtw_usb_intf_deinit(rtwdev, intf); ++ rtw_core_deinit(rtwdev); ++ ieee80211_free_hw(hw); ++} ++EXPORT_SYMBOL(rtw_usb_disconnect); ++ ++MODULE_AUTHOR("Realtek Corporation"); ++MODULE_DESCRIPTION("Realtek 802.11ac wireless USB driver"); ++MODULE_LICENSE("Dual BSD/GPL"); +diff --git a/drivers/net/wireless/realtek/rtw88/usb.h b/drivers/net/wireless/realtek/rtw88/usb.h +new file mode 100644 +index 0000000000000..30647f0dd61c6 +--- /dev/null ++++ b/drivers/net/wireless/realtek/rtw88/usb.h +@@ -0,0 +1,107 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ ++/* Copyright(c) 2018-2019 Realtek Corporation ++ */ ++ ++#ifndef __RTW_USB_H_ ++#define __RTW_USB_H_ ++ ++#define FW_8192C_START_ADDRESS 0x1000 ++#define FW_8192C_END_ADDRESS 0x5fff ++ ++#define RTW_USB_MAX_RXTX_COUNT 128 ++#define RTW_USB_VENQT_MAX_BUF_SIZE 254 ++#define MAX_USBCTRL_VENDORREQ_TIMES 10 ++ ++#define RTW_USB_CMD_READ 0xc0 ++#define RTW_USB_CMD_WRITE 0x40 ++#define RTW_USB_CMD_REQ 0x05 ++ ++#define RTW_USB_VENQT_CMD_IDX 0x00 ++ ++#define RTW_USB_SUPER_SPEED_BULK_SIZE 1024 ++#define RTW_USB_HIGH_SPEED_BULK_SIZE 512 ++#define RTW_USB_FULL_SPEED_BULK_SIZE 64 ++ ++#define RTW_USB_TX_SEL_HQ BIT(0) ++#define RTW_USB_TX_SEL_LQ BIT(1) ++#define RTW_USB_TX_SEL_NQ BIT(2) ++#define RTW_USB_TX_SEL_EQ BIT(3) ++ ++#define RTW_USB_BULK_IN_ADDR 0x80 ++#define RTW_USB_INT_IN_ADDR 0x81 ++ ++#define RTW_USB_HW_QUEUE_ENTRY 8 ++ ++#define RTW_USB_PACKET_OFFSET_SZ 8 ++#define RTW_USB_MAX_XMITBUF_SZ (1024 * 20) ++#define RTW_USB_MAX_RECVBUF_SZ 32768 ++ ++#define RTW_USB_RECVBUFF_ALIGN_SZ 8 ++ ++#define RTW_USB_RXAGG_SIZE 6 ++#define RTW_USB_RXAGG_TIMEOUT 10 ++ ++#define RTW_USB_RXCB_NUM 4 ++ ++#define RTW_USB_EP_MAX 4 ++ ++#define TX_DESC_QSEL_MAX 20 ++ ++#define RTW_USB_VENDOR_ID_REALTEK 0x0bda ++ ++static inline struct rtw_usb *rtw_get_usb_priv(struct rtw_dev *rtwdev) ++{ ++ return (struct rtw_usb *)rtwdev->priv; ++} ++ ++struct rx_usb_ctrl_block { ++ struct rtw_dev *rtwdev; ++ struct urb *rx_urb; ++ struct sk_buff *rx_skb; ++ int n; ++}; ++ ++struct rtw_usb_tx_data { ++ u8 sn; ++}; ++ ++struct rtw_usb { ++ struct rtw_dev *rtwdev; ++ struct usb_device *udev; ++ ++ /* protects usb_data_index */ ++ spinlock_t usb_lock; ++ __le32 *usb_data; ++ unsigned int usb_data_index; ++ ++ u32 bulkout_size; ++ u8 pipe_interrupt; ++ u8 pipe_in; ++ u8 out_ep[RTW_USB_EP_MAX]; ++ u8 qsel_to_ep[TX_DESC_QSEL_MAX]; ++ u8 usb_txagg_num; ++ ++ struct workqueue_struct *txwq, *rxwq; ++ ++ struct sk_buff_head tx_queue[RTW_USB_EP_MAX]; ++ struct work_struct tx_work; ++ ++ struct rx_usb_ctrl_block rx_cb[RTW_USB_RXCB_NUM]; ++ struct sk_buff_head rx_queue; ++ struct work_struct rx_work; ++}; ++ ++static inline struct rtw_usb_tx_data *rtw_usb_get_tx_data(struct sk_buff *skb) ++{ ++ struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); ++ ++ BUILD_BUG_ON(sizeof(struct rtw_usb_tx_data) > ++ sizeof(info->status.status_driver_data)); ++ ++ return (struct rtw_usb_tx_data *)info->status.status_driver_data; ++} ++ ++int rtw_usb_probe(struct usb_interface *intf, const struct usb_device_id *id); ++void rtw_usb_disconnect(struct usb_interface *intf); ++ ++#endif +-- +cgit + +From aff5ffd718de23cb8603f2e229204670e2644334 Mon Sep 17 00:00:00 2001 +From: Sascha Hauer +Date: Fri, 2 Dec 2022 09:12:21 +0100 +Subject: wifi: rtw88: Add rtw8821cu chipset support + +Add support for the rtw8821cu chipset based on +https://github.com/ulli-kroll/rtw88-usb.git + +Signed-off-by: Sascha Hauer +Signed-off-by: Kalle Valo +Link: https://lore.kernel.org/r/20221202081224.2779981-9-s.hauer@pengutronix.de +--- + drivers/net/wireless/realtek/rtw88/Kconfig | 11 ++++++ + drivers/net/wireless/realtek/rtw88/Makefile | 3 ++ + drivers/net/wireless/realtek/rtw88/rtw8821c.c | 18 ++++++++++ + drivers/net/wireless/realtek/rtw88/rtw8821c.h | 21 +++++++++++ + drivers/net/wireless/realtek/rtw88/rtw8821cu.c | 50 ++++++++++++++++++++++++++ + 5 files changed, 103 insertions(+) + create mode 100644 drivers/net/wireless/realtek/rtw88/rtw8821cu.c + +diff --git a/drivers/net/wireless/realtek/rtw88/Kconfig b/drivers/net/wireless/realtek/rtw88/Kconfig +index 1624c5db69bac..2b500dbefbc2d 100644 +--- a/drivers/net/wireless/realtek/rtw88/Kconfig ++++ b/drivers/net/wireless/realtek/rtw88/Kconfig +@@ -75,6 +75,17 @@ config RTW88_8821CE + + 802.11ac PCIe wireless network adapter + ++config RTW88_8821CU ++ tristate "Realtek 8821CU USB wireless network adapter" ++ depends on USB ++ select RTW88_CORE ++ select RTW88_USB ++ select RTW88_8821C ++ help ++ Select this option will enable support for 8821CU chipset ++ ++ 802.11ac USB wireless network adapter ++ + config RTW88_DEBUG + bool "Realtek rtw88 debug support" + depends on RTW88_CORE +diff --git a/drivers/net/wireless/realtek/rtw88/Makefile b/drivers/net/wireless/realtek/rtw88/Makefile +index 2c2b0e5133cdf..552661a638def 100644 +--- a/drivers/net/wireless/realtek/rtw88/Makefile ++++ b/drivers/net/wireless/realtek/rtw88/Makefile +@@ -44,6 +44,9 @@ rtw88_8821c-objs := rtw8821c.o rtw8821c_table.o + obj-$(CONFIG_RTW88_8821CE) += rtw88_8821ce.o + rtw88_8821ce-objs := rtw8821ce.o + ++obj-$(CONFIG_RTW88_8821CU) += rtw88_8821cu.o ++rtw88_8821cu-objs := rtw8821cu.o ++ + obj-$(CONFIG_RTW88_PCI) += rtw88_pci.o + rtw88_pci-objs := pci.o + +diff --git a/drivers/net/wireless/realtek/rtw88/rtw8821c.c b/drivers/net/wireless/realtek/rtw88/rtw8821c.c +index 9afdc5ce86b43..17f800f6efbd0 100644 +--- a/drivers/net/wireless/realtek/rtw88/rtw8821c.c ++++ b/drivers/net/wireless/realtek/rtw88/rtw8821c.c +@@ -26,6 +26,12 @@ static void rtw8821ce_efuse_parsing(struct rtw_efuse *efuse, + ether_addr_copy(efuse->addr, map->e.mac_addr); + } + ++static void rtw8821cu_efuse_parsing(struct rtw_efuse *efuse, ++ struct rtw8821c_efuse *map) ++{ ++ ether_addr_copy(efuse->addr, map->u.mac_addr); ++} ++ + enum rtw8821ce_rf_set { + SWITCH_TO_BTG, + SWITCH_TO_WLG, +@@ -68,6 +74,9 @@ static int rtw8821c_read_efuse(struct rtw_dev *rtwdev, u8 *log_map) + case RTW_HCI_TYPE_PCIE: + rtw8821ce_efuse_parsing(efuse, map); + break; ++ case RTW_HCI_TYPE_USB: ++ rtw8821cu_efuse_parsing(efuse, map); ++ break; + default: + /* unsupported now */ + return -ENOTSUPP; +@@ -1148,6 +1157,13 @@ static void rtw8821c_phy_cck_pd_set(struct rtw_dev *rtwdev, u8 new_lvl) + dm_info->cck_pd_default + new_lvl * 2); + } + ++static void rtw8821c_fill_txdesc_checksum(struct rtw_dev *rtwdev, ++ struct rtw_tx_pkt_info *pkt_info, ++ u8 *txdesc) ++{ ++ fill_txdesc_checksum_common(txdesc, 16); ++} ++ + static struct rtw_pwr_seq_cmd trans_carddis_to_cardemu_8821c[] = { + {0x0086, + RTW_PWR_CUT_ALL_MSK, +@@ -1521,6 +1537,7 @@ static const struct rtw_rfe_def rtw8821c_rfe_defs[] = { + [2] = RTW_DEF_RFE_EXT(8821c, 0, 0, 2), + [4] = RTW_DEF_RFE_EXT(8821c, 0, 0, 2), + [6] = RTW_DEF_RFE(8821c, 0, 0), ++ [34] = RTW_DEF_RFE(8821c, 0, 0), + }; + + static struct rtw_hw_reg rtw8821c_dig[] = { +@@ -1595,6 +1612,7 @@ static struct rtw_chip_ops rtw8821c_ops = { + .config_bfee = rtw8821c_bf_config_bfee, + .set_gid_table = rtw_bf_set_gid_table, + .cfg_csi_rate = rtw_bf_cfg_csi_rate, ++ .fill_txdesc_checksum = rtw8821c_fill_txdesc_checksum, + + .coex_set_init = rtw8821c_coex_cfg_init, + .coex_set_ant_switch = rtw8821c_coex_cfg_ant_switch, +diff --git a/drivers/net/wireless/realtek/rtw88/rtw8821c.h b/drivers/net/wireless/realtek/rtw88/rtw8821c.h +index 2698801fc35d5..1c81260f3a542 100644 +--- a/drivers/net/wireless/realtek/rtw88/rtw8821c.h ++++ b/drivers/net/wireless/realtek/rtw88/rtw8821c.h +@@ -9,6 +9,26 @@ + + #define RCR_VHT_ACK BIT(26) + ++struct rtw8821cu_efuse { ++ u8 res4[4]; /* 0xd0 */ ++ u8 usb_optional_function; ++ u8 res5[0x1e]; ++ u8 res6[2]; ++ u8 serial[0x0b]; /* 0xf5 */ ++ u8 vid; /* 0x100 */ ++ u8 res7; ++ u8 pid; ++ u8 res8[4]; ++ u8 mac_addr[ETH_ALEN]; /* 0x107 */ ++ u8 res9[2]; ++ u8 vendor_name[0x07]; ++ u8 res10[2]; ++ u8 device_name[0x14]; ++ u8 res11[0xcf]; ++ u8 package_type; /* 0x1fb */ ++ u8 res12[0x4]; ++}; ++ + struct rtw8821ce_efuse { + u8 mac_addr[ETH_ALEN]; /* 0xd0 */ + u8 vender_id[2]; +@@ -73,6 +93,7 @@ struct rtw8821c_efuse { + u8 res[3]; + union { + struct rtw8821ce_efuse e; ++ struct rtw8821cu_efuse u; + }; + }; + +diff --git a/drivers/net/wireless/realtek/rtw88/rtw8821cu.c b/drivers/net/wireless/realtek/rtw88/rtw8821cu.c +new file mode 100644 +index 0000000000000..7a5cbdc31ef79 +--- /dev/null ++++ b/drivers/net/wireless/realtek/rtw88/rtw8821cu.c +@@ -0,0 +1,50 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause ++/* Copyright(c) 2018-2019 Realtek Corporation ++ */ ++ ++#include ++#include ++#include "main.h" ++#include "rtw8821c.h" ++#include "usb.h" ++ ++static const struct usb_device_id rtw_8821cu_id_table[] = { ++ { USB_DEVICE_AND_INTERFACE_INFO(RTW_USB_VENDOR_ID_REALTEK, 0xb82b, 0xff, 0xff, 0xff), ++ .driver_info = (kernel_ulong_t)&(rtw8821c_hw_spec) }, /* 8821CU */ ++ { USB_DEVICE_AND_INTERFACE_INFO(RTW_USB_VENDOR_ID_REALTEK, 0xb820, 0xff, 0xff, 0xff), ++ .driver_info = (kernel_ulong_t)&(rtw8821c_hw_spec) }, /* 8821CU */ ++ { USB_DEVICE_AND_INTERFACE_INFO(RTW_USB_VENDOR_ID_REALTEK, 0xc821, 0xff, 0xff, 0xff), ++ .driver_info = (kernel_ulong_t)&(rtw8821c_hw_spec) }, /* 8821CU */ ++ { USB_DEVICE_AND_INTERFACE_INFO(RTW_USB_VENDOR_ID_REALTEK, 0xc820, 0xff, 0xff, 0xff), ++ .driver_info = (kernel_ulong_t)&(rtw8821c_hw_spec) }, /* 8821CU */ ++ { USB_DEVICE_AND_INTERFACE_INFO(RTW_USB_VENDOR_ID_REALTEK, 0xc82a, 0xff, 0xff, 0xff), ++ .driver_info = (kernel_ulong_t)&(rtw8821c_hw_spec) }, /* 8821CU */ ++ { USB_DEVICE_AND_INTERFACE_INFO(RTW_USB_VENDOR_ID_REALTEK, 0xc82b, 0xff, 0xff, 0xff), ++ .driver_info = (kernel_ulong_t)&(rtw8821c_hw_spec) }, /* 8821CU */ ++ { USB_DEVICE_AND_INTERFACE_INFO(RTW_USB_VENDOR_ID_REALTEK, 0xc811, 0xff, 0xff, 0xff), ++ .driver_info = (kernel_ulong_t)&(rtw8821c_hw_spec) }, /* 8811CU */ ++ { USB_DEVICE_AND_INTERFACE_INFO(RTW_USB_VENDOR_ID_REALTEK, 0x8811, 0xff, 0xff, 0xff), ++ .driver_info = (kernel_ulong_t)&(rtw8821c_hw_spec) }, /* 8811CU */ ++ { USB_DEVICE_AND_INTERFACE_INFO(RTW_USB_VENDOR_ID_REALTEK, 0x2006, 0xff, 0xff, 0xff), ++ .driver_info = (kernel_ulong_t)&(rtw8821c_hw_spec) }, /* TOTOLINK A650UA v3 */ ++ {}, ++}; ++MODULE_DEVICE_TABLE(usb, rtw_8821cu_id_table); ++ ++static int rtw_8821cu_probe(struct usb_interface *intf, ++ const struct usb_device_id *id) ++{ ++ return rtw_usb_probe(intf, id); ++} ++ ++static struct usb_driver rtw_8821cu_driver = { ++ .name = "rtw_8821cu", ++ .id_table = rtw_8821cu_id_table, ++ .probe = rtw_8821cu_probe, ++ .disconnect = rtw_usb_disconnect, ++}; ++module_usb_driver(rtw_8821cu_driver); ++ ++MODULE_AUTHOR("Hans Ulli Kroll "); ++MODULE_DESCRIPTION("Realtek 802.11ac wireless 8821cu driver"); ++MODULE_LICENSE("Dual BSD/GPL"); +-- +cgit + +From 45794099f5e1d7abc5eb07e6eec7e1e5c6cb540d Mon Sep 17 00:00:00 2001 +From: Sascha Hauer +Date: Fri, 2 Dec 2022 09:12:22 +0100 +Subject: wifi: rtw88: Add rtw8822bu chipset support + +Add support for the rtw8822bu chipset based on +https://github.com/ulli-kroll/rtw88-usb.git + +Signed-off-by: Sascha Hauer +Signed-off-by: Kalle Valo +Link: https://lore.kernel.org/r/20221202081224.2779981-10-s.hauer@pengutronix.de +--- + drivers/net/wireless/realtek/rtw88/Kconfig | 11 ++++ + drivers/net/wireless/realtek/rtw88/Makefile | 3 + + drivers/net/wireless/realtek/rtw88/rtw8822b.c | 19 ++++++ + drivers/net/wireless/realtek/rtw88/rtw8822bu.c | 90 ++++++++++++++++++++++++++ + 4 files changed, 123 insertions(+) + create mode 100644 drivers/net/wireless/realtek/rtw88/rtw8822bu.c + +diff --git a/drivers/net/wireless/realtek/rtw88/Kconfig b/drivers/net/wireless/realtek/rtw88/Kconfig +index 2b500dbefbc2d..10f4e7f88b858 100644 +--- a/drivers/net/wireless/realtek/rtw88/Kconfig ++++ b/drivers/net/wireless/realtek/rtw88/Kconfig +@@ -42,6 +42,17 @@ config RTW88_8822BE + + 802.11ac PCIe wireless network adapter + ++config RTW88_8822BU ++ tristate "Realtek 8822BU USB wireless network adapter" ++ depends on USB ++ select RTW88_CORE ++ select RTW88_USB ++ select RTW88_8822B ++ help ++ Select this option will enable support for 8822BU chipset ++ ++ 802.11ac USB wireless network adapter ++ + config RTW88_8822CE + tristate "Realtek 8822CE PCI wireless network adapter" + depends on PCI +diff --git a/drivers/net/wireless/realtek/rtw88/Makefile b/drivers/net/wireless/realtek/rtw88/Makefile +index 552661a638def..6984bfb7a3170 100644 +--- a/drivers/net/wireless/realtek/rtw88/Makefile ++++ b/drivers/net/wireless/realtek/rtw88/Makefile +@@ -26,6 +26,9 @@ rtw88_8822b-objs := rtw8822b.o rtw8822b_table.o + obj-$(CONFIG_RTW88_8822BE) += rtw88_8822be.o + rtw88_8822be-objs := rtw8822be.o + ++obj-$(CONFIG_RTW88_8822BU) += rtw88_8822bu.o ++rtw88_8822bu-objs := rtw8822bu.o ++ + obj-$(CONFIG_RTW88_8822C) += rtw88_8822c.o + rtw88_8822c-objs := rtw8822c.o rtw8822c_table.o + +diff --git a/drivers/net/wireless/realtek/rtw88/rtw8822b.c b/drivers/net/wireless/realtek/rtw88/rtw8822b.c +index 690e35c98f6e5..74dfb89b2c948 100644 +--- a/drivers/net/wireless/realtek/rtw88/rtw8822b.c ++++ b/drivers/net/wireless/realtek/rtw88/rtw8822b.c +@@ -26,6 +26,12 @@ static void rtw8822be_efuse_parsing(struct rtw_efuse *efuse, + ether_addr_copy(efuse->addr, map->e.mac_addr); + } + ++static void rtw8822bu_efuse_parsing(struct rtw_efuse *efuse, ++ struct rtw8822b_efuse *map) ++{ ++ ether_addr_copy(efuse->addr, map->u.mac_addr); ++} ++ + static int rtw8822b_read_efuse(struct rtw_dev *rtwdev, u8 *log_map) + { + struct rtw_efuse *efuse = &rtwdev->efuse; +@@ -56,6 +62,9 @@ static int rtw8822b_read_efuse(struct rtw_dev *rtwdev, u8 *log_map) + case RTW_HCI_TYPE_PCIE: + rtw8822be_efuse_parsing(efuse, map); + break; ++ case RTW_HCI_TYPE_USB: ++ rtw8822bu_efuse_parsing(efuse, map); ++ break; + default: + /* unsupported now */ + return -ENOTSUPP; +@@ -1588,6 +1597,15 @@ static void rtw8822b_adaptivity(struct rtw_dev *rtwdev) + rtw_phy_set_edcca_th(rtwdev, l2h, h2l); + } + ++static void rtw8822b_fill_txdesc_checksum(struct rtw_dev *rtwdev, ++ struct rtw_tx_pkt_info *pkt_info, ++ u8 *txdesc) ++{ ++ size_t words = 32 / 2; /* calculate the first 32 bytes (16 words) */ ++ ++ fill_txdesc_checksum_common(txdesc, words); ++} ++ + static const struct rtw_pwr_seq_cmd trans_carddis_to_cardemu_8822b[] = { + {0x0086, + RTW_PWR_CUT_ALL_MSK, +@@ -2163,6 +2181,7 @@ static struct rtw_chip_ops rtw8822b_ops = { + .cfg_csi_rate = rtw_bf_cfg_csi_rate, + .adaptivity_init = rtw8822b_adaptivity_init, + .adaptivity = rtw8822b_adaptivity, ++ .fill_txdesc_checksum = rtw8822b_fill_txdesc_checksum, + + .coex_set_init = rtw8822b_coex_cfg_init, + .coex_set_ant_switch = rtw8822b_coex_cfg_ant_switch, +diff --git a/drivers/net/wireless/realtek/rtw88/rtw8822bu.c b/drivers/net/wireless/realtek/rtw88/rtw8822bu.c +new file mode 100644 +index 0000000000000..ab620a0b1dfc6 +--- /dev/null ++++ b/drivers/net/wireless/realtek/rtw88/rtw8822bu.c +@@ -0,0 +1,90 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause ++/* Copyright(c) 2018-2019 Realtek Corporation ++ */ ++ ++#include ++#include ++#include "main.h" ++#include "rtw8822b.h" ++#include "usb.h" ++ ++static const struct usb_device_id rtw_8822bu_id_table[] = { ++ { USB_DEVICE_AND_INTERFACE_INFO(RTW_USB_VENDOR_ID_REALTEK, 0xb812, 0xff, 0xff, 0xff), ++ .driver_info = (kernel_ulong_t)&(rtw8822b_hw_spec) }, ++ { USB_DEVICE_AND_INTERFACE_INFO(RTW_USB_VENDOR_ID_REALTEK, 0xb82c, 0xff, 0xff, 0xff), ++ .driver_info = (kernel_ulong_t)&(rtw8822b_hw_spec) }, ++ { USB_DEVICE_AND_INTERFACE_INFO(RTW_USB_VENDOR_ID_REALTEK, 0x2102, 0xff, 0xff, 0xff), ++ .driver_info = (kernel_ulong_t)&(rtw8822b_hw_spec) }, /* CCNC */ ++ { USB_DEVICE_AND_INTERFACE_INFO(0x7392, 0xb822, 0xff, 0xff, 0xff), ++ .driver_info = (kernel_ulong_t)&(rtw8822b_hw_spec) }, /* Edimax EW-7822ULC */ ++ { USB_DEVICE_AND_INTERFACE_INFO(0x7392, 0xc822, 0xff, 0xff, 0xff), ++ .driver_info = (kernel_ulong_t)&(rtw8822b_hw_spec) }, /* Edimax EW-7822UTC */ ++ { USB_DEVICE_AND_INTERFACE_INFO(0x7392, 0xd822, 0xff, 0xff, 0xff), ++ .driver_info = (kernel_ulong_t)&(rtw8822b_hw_spec) }, /* Edimax */ ++ { USB_DEVICE_AND_INTERFACE_INFO(0x7392, 0xe822, 0xff, 0xff, 0xff), ++ .driver_info = (kernel_ulong_t)&(rtw8822b_hw_spec) }, /* Edimax */ ++ { USB_DEVICE_AND_INTERFACE_INFO(0x7392, 0xf822, 0xff, 0xff, 0xff), ++ .driver_info = (kernel_ulong_t)&(rtw8822b_hw_spec) }, /* Edimax EW-7822UAD */ ++ { USB_DEVICE_AND_INTERFACE_INFO(RTW_USB_VENDOR_ID_REALTEK, 0xb81a, 0xff, 0xff, 0xff), ++ .driver_info = (kernel_ulong_t)&(rtw8822b_hw_spec) }, /* Default ID */ ++ { USB_DEVICE_AND_INTERFACE_INFO(0x0b05, 0x1841, 0xff, 0xff, 0xff), ++ .driver_info = (kernel_ulong_t)&(rtw8822b_hw_spec) }, /* ASUS AC1300 USB-AC55 B1 */ ++ { USB_DEVICE_AND_INTERFACE_INFO(0x0b05, 0x184c, 0xff, 0xff, 0xff), ++ .driver_info = (kernel_ulong_t)&(rtw8822b_hw_spec) }, /* ASUS U2 */ ++ { USB_DEVICE_AND_INTERFACE_INFO(0x0B05, 0x19aa, 0xff, 0xff, 0xff), ++ .driver_info = (kernel_ulong_t)&(rtw8822b_hw_spec) }, /* ASUS - USB-AC58 rev A1 */ ++ { USB_DEVICE_AND_INTERFACE_INFO(0x0B05, 0x1870, 0xff, 0xff, 0xff), ++ .driver_info = (kernel_ulong_t)&(rtw8822b_hw_spec) }, /* ASUS */ ++ { USB_DEVICE_AND_INTERFACE_INFO(0x0B05, 0x1874, 0xff, 0xff, 0xff), ++ .driver_info = (kernel_ulong_t)&(rtw8822b_hw_spec) }, /* ASUS */ ++ { USB_DEVICE_AND_INTERFACE_INFO(0x2001, 0x331e, 0xff, 0xff, 0xff), ++ .driver_info = (kernel_ulong_t)&(rtw8822b_hw_spec) }, /* Dlink - DWA-181 */ ++ { USB_DEVICE_AND_INTERFACE_INFO(0x2001, 0x331c, 0xff, 0xff, 0xff), ++ .driver_info = (kernel_ulong_t)&(rtw8822b_hw_spec) }, /* Dlink - DWA-182 - D1 */ ++ {USB_DEVICE_AND_INTERFACE_INFO(0x2001, 0x331f, 0xff, 0xff, 0xff), ++ .driver_info = (kernel_ulong_t)&(rtw8822b_hw_spec)}, /* Dlink - DWA-183 D Ver */ ++ { USB_DEVICE_AND_INTERFACE_INFO(0x13b1, 0x0043, 0xff, 0xff, 0xff), ++ .driver_info = (kernel_ulong_t)&(rtw8822b_hw_spec) }, /* Linksys WUSB6400M */ ++ { USB_DEVICE_AND_INTERFACE_INFO(0x13b1, 0x0045, 0xff, 0xff, 0xff), ++ .driver_info = (kernel_ulong_t)&(rtw8822b_hw_spec) }, /* Linksys WUSB3600 v2 */ ++ { USB_DEVICE_AND_INTERFACE_INFO(0x2357, 0x012d, 0xff, 0xff, 0xff), ++ .driver_info = (kernel_ulong_t)&(rtw8822b_hw_spec) }, /* TP-Link Archer T3U v1 */ ++ { USB_DEVICE_AND_INTERFACE_INFO(0x2357, 0x0138, 0xff, 0xff, 0xff), ++ .driver_info = (kernel_ulong_t)&(rtw8822b_hw_spec) }, /* TP-Link Archer T3U Plus v1 */ ++ { USB_DEVICE_AND_INTERFACE_INFO(0x2357, 0x0115, 0xff, 0xff, 0xff), ++ .driver_info = (kernel_ulong_t)&(rtw8822b_hw_spec) }, /* TP-Link Archer T4U V3 */ ++ { USB_DEVICE_AND_INTERFACE_INFO(0x2357, 0x012e, 0xff, 0xff, 0xff), ++ .driver_info = (kernel_ulong_t)&(rtw8822b_hw_spec) }, /* TP-LINK */ ++ { USB_DEVICE_AND_INTERFACE_INFO(0x2357, 0x0116, 0xff, 0xff, 0xff), ++ .driver_info = (kernel_ulong_t)&(rtw8822b_hw_spec) }, /* TP-LINK */ ++ { USB_DEVICE_AND_INTERFACE_INFO(0x2357, 0x0117, 0xff, 0xff, 0xff), ++ .driver_info = (kernel_ulong_t)&(rtw8822b_hw_spec) }, /* TP-LINK */ ++ { USB_DEVICE_AND_INTERFACE_INFO(0x0846, 0x9055, 0xff, 0xff, 0xff), ++ .driver_info = (kernel_ulong_t)&(rtw8822b_hw_spec) }, /* Netgear A6150 */ ++ { USB_DEVICE_AND_INTERFACE_INFO(0x0e66, 0x0025, 0xff, 0xff, 0xff), ++ .driver_info = (kernel_ulong_t)&(rtw8822b_hw_spec) }, /* Hawking HW12ACU */ ++ { USB_DEVICE_AND_INTERFACE_INFO(0x04ca, 0x8602, 0xff, 0xff, 0xff), ++ .driver_info = (kernel_ulong_t)&(rtw8822b_hw_spec) }, /* LiteOn */ ++ { USB_DEVICE_AND_INTERFACE_INFO(0x20f4, 0x808a, 0xff, 0xff, 0xff), ++ .driver_info = (kernel_ulong_t)&(rtw8822b_hw_spec) }, /* TRENDnet TEW-808UBM */ ++ {}, ++}; ++MODULE_DEVICE_TABLE(usb, rtw_8822bu_id_table); ++ ++static int rtw8822bu_probe(struct usb_interface *intf, ++ const struct usb_device_id *id) ++{ ++ return rtw_usb_probe(intf, id); ++} ++ ++static struct usb_driver rtw_8822bu_driver = { ++ .name = "rtw_8822bu", ++ .id_table = rtw_8822bu_id_table, ++ .probe = rtw8822bu_probe, ++ .disconnect = rtw_usb_disconnect, ++}; ++module_usb_driver(rtw_8822bu_driver); ++ ++MODULE_AUTHOR("Realtek Corporation"); ++MODULE_DESCRIPTION("Realtek 802.11ac wireless 8822bu driver"); ++MODULE_LICENSE("Dual BSD/GPL"); +-- +cgit + +From 07cef03b8d44dee7488de3d1585387e603c78676 Mon Sep 17 00:00:00 2001 +From: Sascha Hauer +Date: Fri, 2 Dec 2022 09:12:23 +0100 +Subject: wifi: rtw88: Add rtw8822cu chipset support + +Add support for the rtw8822cu chipset based on +https://github.com/ulli-kroll/rtw88-usb.git + +Signed-off-by: Sascha Hauer +Signed-off-by: Kalle Valo +Link: https://lore.kernel.org/r/20221202081224.2779981-11-s.hauer@pengutronix.de +--- + drivers/net/wireless/realtek/rtw88/Kconfig | 11 +++++++ + drivers/net/wireless/realtek/rtw88/Makefile | 3 ++ + drivers/net/wireless/realtek/rtw88/rtw8822c.c | 24 ++++++++++++++ + drivers/net/wireless/realtek/rtw88/rtw8822cu.c | 44 ++++++++++++++++++++++++++ + 4 files changed, 82 insertions(+) + create mode 100644 drivers/net/wireless/realtek/rtw88/rtw8822cu.c + +diff --git a/drivers/net/wireless/realtek/rtw88/Kconfig b/drivers/net/wireless/realtek/rtw88/Kconfig +index 10f4e7f88b858..138289bc5ad0c 100644 +--- a/drivers/net/wireless/realtek/rtw88/Kconfig ++++ b/drivers/net/wireless/realtek/rtw88/Kconfig +@@ -64,6 +64,17 @@ config RTW88_8822CE + + 802.11ac PCIe wireless network adapter + ++config RTW88_8822CU ++ tristate "Realtek 8822CU USB wireless network adapter" ++ depends on USB ++ select RTW88_CORE ++ select RTW88_USB ++ select RTW88_8822C ++ help ++ Select this option will enable support for 8822CU chipset ++ ++ 802.11ac USB wireless network adapter ++ + config RTW88_8723DE + tristate "Realtek 8723DE PCI wireless network adapter" + depends on PCI +diff --git a/drivers/net/wireless/realtek/rtw88/Makefile b/drivers/net/wireless/realtek/rtw88/Makefile +index 6984bfb7a3170..fe2dd90204a78 100644 +--- a/drivers/net/wireless/realtek/rtw88/Makefile ++++ b/drivers/net/wireless/realtek/rtw88/Makefile +@@ -35,6 +35,9 @@ rtw88_8822c-objs := rtw8822c.o rtw8822c_table.o + obj-$(CONFIG_RTW88_8822CE) += rtw88_8822ce.o + rtw88_8822ce-objs := rtw8822ce.o + ++obj-$(CONFIG_RTW88_8822CU) += rtw88_8822cu.o ++rtw88_8822cu-objs := rtw8822cu.o ++ + obj-$(CONFIG_RTW88_8723D) += rtw88_8723d.o + rtw88_8723d-objs := rtw8723d.o rtw8723d_table.o + +diff --git a/drivers/net/wireless/realtek/rtw88/rtw8822c.c b/drivers/net/wireless/realtek/rtw88/rtw8822c.c +index fccb15dfb9595..964e27887fe2d 100644 +--- a/drivers/net/wireless/realtek/rtw88/rtw8822c.c ++++ b/drivers/net/wireless/realtek/rtw88/rtw8822c.c +@@ -29,6 +29,12 @@ static void rtw8822ce_efuse_parsing(struct rtw_efuse *efuse, + ether_addr_copy(efuse->addr, map->e.mac_addr); + } + ++static void rtw8822cu_efuse_parsing(struct rtw_efuse *efuse, ++ struct rtw8822c_efuse *map) ++{ ++ ether_addr_copy(efuse->addr, map->u.mac_addr); ++} ++ + static int rtw8822c_read_efuse(struct rtw_dev *rtwdev, u8 *log_map) + { + struct rtw_efuse *efuse = &rtwdev->efuse; +@@ -58,6 +64,9 @@ static int rtw8822c_read_efuse(struct rtw_dev *rtwdev, u8 *log_map) + case RTW_HCI_TYPE_PCIE: + rtw8822ce_efuse_parsing(efuse, map); + break; ++ case RTW_HCI_TYPE_USB: ++ rtw8822cu_efuse_parsing(efuse, map); ++ break; + default: + /* unsupported now */ + return -ENOTSUPP; +@@ -4557,6 +4566,18 @@ static void rtw8822c_adaptivity(struct rtw_dev *rtwdev) + rtw_phy_set_edcca_th(rtwdev, l2h, h2l); + } + ++static void rtw8822c_fill_txdesc_checksum(struct rtw_dev *rtwdev, ++ struct rtw_tx_pkt_info *pkt_info, ++ u8 *txdesc) ++{ ++ const struct rtw_chip_info *chip = rtwdev->chip; ++ size_t words; ++ ++ words = (pkt_info->pkt_offset * 8 + chip->tx_pkt_desc_sz) / 2; ++ ++ fill_txdesc_checksum_common(txdesc, words); ++} ++ + static const struct rtw_pwr_seq_cmd trans_carddis_to_cardemu_8822c[] = { + {0x0086, + RTW_PWR_CUT_ALL_MSK, +@@ -4895,6 +4916,8 @@ static const struct rtw_rfe_def rtw8822c_rfe_defs[] = { + [0] = RTW_DEF_RFE(8822c, 0, 0), + [1] = RTW_DEF_RFE(8822c, 0, 0), + [2] = RTW_DEF_RFE(8822c, 0, 0), ++ [3] = RTW_DEF_RFE(8822c, 0, 0), ++ [4] = RTW_DEF_RFE(8822c, 0, 0), + [5] = RTW_DEF_RFE(8822c, 0, 5), + [6] = RTW_DEF_RFE(8822c, 0, 0), + }; +@@ -4978,6 +5001,7 @@ static struct rtw_chip_ops rtw8822c_ops = { + .cfo_track = rtw8822c_cfo_track, + .config_tx_path = rtw8822c_config_tx_path, + .config_txrx_mode = rtw8822c_config_trx_mode, ++ .fill_txdesc_checksum = rtw8822c_fill_txdesc_checksum, + + .coex_set_init = rtw8822c_coex_cfg_init, + .coex_set_ant_switch = NULL, +diff --git a/drivers/net/wireless/realtek/rtw88/rtw8822cu.c b/drivers/net/wireless/realtek/rtw88/rtw8822cu.c +new file mode 100644 +index 0000000000000..af28ca09d41fb +--- /dev/null ++++ b/drivers/net/wireless/realtek/rtw88/rtw8822cu.c +@@ -0,0 +1,44 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause ++/* Copyright(c) 2018-2019 Realtek Corporation ++ */ ++ ++#include ++#include ++#include "main.h" ++#include "rtw8822c.h" ++#include "usb.h" ++ ++static const struct usb_device_id rtw_8822cu_id_table[] = { ++ { USB_DEVICE_AND_INTERFACE_INFO(RTW_USB_VENDOR_ID_REALTEK, 0xc82c, 0xff, 0xff, 0xff), ++ .driver_info = (kernel_ulong_t)&(rtw8822c_hw_spec) }, ++ { USB_DEVICE_AND_INTERFACE_INFO(RTW_USB_VENDOR_ID_REALTEK, 0xc812, 0xff, 0xff, 0xff), ++ .driver_info = (kernel_ulong_t)&(rtw8822c_hw_spec) }, ++ { USB_DEVICE_AND_INTERFACE_INFO(RTW_USB_VENDOR_ID_REALTEK, 0xc82e, 0xff, 0xff, 0xff), ++ .driver_info = (kernel_ulong_t)&(rtw8822c_hw_spec) }, ++ { USB_DEVICE_AND_INTERFACE_INFO(RTW_USB_VENDOR_ID_REALTEK, 0xd820, 0xff, 0xff, 0xff), ++ .driver_info = (kernel_ulong_t)&(rtw8822c_hw_spec) }, ++ { USB_DEVICE_AND_INTERFACE_INFO(RTW_USB_VENDOR_ID_REALTEK, 0xd82b, 0xff, 0xff, 0xff), ++ .driver_info = (kernel_ulong_t)&(rtw8822c_hw_spec) }, ++ { USB_DEVICE_AND_INTERFACE_INFO(0x13b1, 0x0043, 0xff, 0xff, 0xff), ++ .driver_info = (kernel_ulong_t)&(rtw8822c_hw_spec) }, /* Alpha - Alpha */ ++ {}, ++}; ++MODULE_DEVICE_TABLE(usb, rtw_8822cu_id_table); ++ ++static int rtw8822bu_probe(struct usb_interface *intf, ++ const struct usb_device_id *id) ++{ ++ return rtw_usb_probe(intf, id); ++} ++ ++static struct usb_driver rtw_8822cu_driver = { ++ .name = "rtw_8822cu", ++ .id_table = rtw_8822cu_id_table, ++ .probe = rtw8822bu_probe, ++ .disconnect = rtw_usb_disconnect, ++}; ++module_usb_driver(rtw_8822cu_driver); ++ ++MODULE_AUTHOR("Realtek Corporation"); ++MODULE_DESCRIPTION("Realtek 802.11ac wireless 8822cu driver"); ++MODULE_LICENSE("Dual BSD/GPL"); +-- +cgit + +From 87caeef032fc3921bc866ad7becb6ed51aa8b27b Mon Sep 17 00:00:00 2001 +From: Sascha Hauer +Date: Fri, 2 Dec 2022 09:12:24 +0100 +Subject: wifi: rtw88: Add rtw8723du chipset support + +Add support for the rtw8723du chipset based on +https://github.com/ulli-kroll/rtw88-usb.git + +Signed-off-by: Sascha Hauer +Signed-off-by: Kalle Valo +Link: https://lore.kernel.org/r/20221202081224.2779981-12-s.hauer@pengutronix.de +--- + drivers/net/wireless/realtek/rtw88/Kconfig | 11 ++++++++ + drivers/net/wireless/realtek/rtw88/Makefile | 3 +++ + drivers/net/wireless/realtek/rtw88/rtw8723d.c | 28 ++++++++++++++++++++ + drivers/net/wireless/realtek/rtw88/rtw8723d.h | 13 +++++++++- + drivers/net/wireless/realtek/rtw88/rtw8723du.c | 36 ++++++++++++++++++++++++++ + 5 files changed, 90 insertions(+), 1 deletion(-) + create mode 100644 drivers/net/wireless/realtek/rtw88/rtw8723du.c + +diff --git a/drivers/net/wireless/realtek/rtw88/Kconfig b/drivers/net/wireless/realtek/rtw88/Kconfig +index 138289bc5ad0c..651ab56d9c6bd 100644 +--- a/drivers/net/wireless/realtek/rtw88/Kconfig ++++ b/drivers/net/wireless/realtek/rtw88/Kconfig +@@ -86,6 +86,17 @@ config RTW88_8723DE + + 802.11n PCIe wireless network adapter + ++config RTW88_8723DU ++ tristate "Realtek 8723DU USB wireless network adapter" ++ depends on USB ++ select RTW88_CORE ++ select RTW88_USB ++ select RTW88_8723D ++ help ++ Select this option will enable support for 8723DU chipset ++ ++ 802.11n USB wireless network adapter ++ + config RTW88_8821CE + tristate "Realtek 8821CE PCI wireless network adapter" + depends on PCI +diff --git a/drivers/net/wireless/realtek/rtw88/Makefile b/drivers/net/wireless/realtek/rtw88/Makefile +index fe2dd90204a78..fe7293ee87b45 100644 +--- a/drivers/net/wireless/realtek/rtw88/Makefile ++++ b/drivers/net/wireless/realtek/rtw88/Makefile +@@ -44,6 +44,9 @@ rtw88_8723d-objs := rtw8723d.o rtw8723d_table.o + obj-$(CONFIG_RTW88_8723DE) += rtw88_8723de.o + rtw88_8723de-objs := rtw8723de.o + ++obj-$(CONFIG_RTW88_8723DU) += rtw88_8723du.o ++rtw88_8723du-objs := rtw8723du.o ++ + obj-$(CONFIG_RTW88_8821C) += rtw88_8821c.o + rtw88_8821c-objs := rtw8821c.o rtw8821c_table.o + +diff --git a/drivers/net/wireless/realtek/rtw88/rtw8723d.c b/drivers/net/wireless/realtek/rtw88/rtw8723d.c +index 0a4f770fcbb7e..2d2f768bae2ea 100644 +--- a/drivers/net/wireless/realtek/rtw88/rtw8723d.c ++++ b/drivers/net/wireless/realtek/rtw88/rtw8723d.c +@@ -210,6 +210,12 @@ static void rtw8723de_efuse_parsing(struct rtw_efuse *efuse, + ether_addr_copy(efuse->addr, map->e.mac_addr); + } + ++static void rtw8723du_efuse_parsing(struct rtw_efuse *efuse, ++ struct rtw8723d_efuse *map) ++{ ++ ether_addr_copy(efuse->addr, map->u.mac_addr); ++} ++ + static int rtw8723d_read_efuse(struct rtw_dev *rtwdev, u8 *log_map) + { + struct rtw_efuse *efuse = &rtwdev->efuse; +@@ -239,6 +245,9 @@ static int rtw8723d_read_efuse(struct rtw_dev *rtwdev, u8 *log_map) + case RTW_HCI_TYPE_PCIE: + rtw8723de_efuse_parsing(efuse, map); + break; ++ case RTW_HCI_TYPE_USB: ++ rtw8723du_efuse_parsing(efuse, map); ++ break; + default: + /* unsupported now */ + return -ENOTSUPP; +@@ -1945,6 +1954,24 @@ static void rtw8723d_pwr_track(struct rtw_dev *rtwdev) + dm_info->pwr_trk_triggered = false; + } + ++static void rtw8723d_fill_txdesc_checksum(struct rtw_dev *rtwdev, ++ struct rtw_tx_pkt_info *pkt_info, ++ u8 *txdesc) ++{ ++ size_t words = 32 / 2; /* calculate the first 32 bytes (16 words) */ ++ __le16 chksum = 0; ++ __le16 *data = (__le16 *)(txdesc); ++ ++ SET_TX_DESC_TXDESC_CHECKSUM(txdesc, 0x0000); ++ ++ while (words--) ++ chksum ^= *data++; ++ ++ chksum = ~chksum; ++ ++ SET_TX_DESC_TXDESC_CHECKSUM(txdesc, __le16_to_cpu(chksum)); ++} ++ + static struct rtw_chip_ops rtw8723d_ops = { + .phy_set_param = rtw8723d_phy_set_param, + .read_efuse = rtw8723d_read_efuse, +@@ -1965,6 +1992,7 @@ static struct rtw_chip_ops rtw8723d_ops = { + .config_bfee = NULL, + .set_gid_table = NULL, + .cfg_csi_rate = NULL, ++ .fill_txdesc_checksum = rtw8723d_fill_txdesc_checksum, + + .coex_set_init = rtw8723d_coex_cfg_init, + .coex_set_ant_switch = NULL, +diff --git a/drivers/net/wireless/realtek/rtw88/rtw8723d.h b/drivers/net/wireless/realtek/rtw88/rtw8723d.h +index 4641f6e047b41..a356318a5c15b 100644 +--- a/drivers/net/wireless/realtek/rtw88/rtw8723d.h ++++ b/drivers/net/wireless/realtek/rtw88/rtw8723d.h +@@ -41,6 +41,14 @@ struct rtw8723de_efuse { + u8 sub_device_id[2]; + }; + ++struct rtw8723du_efuse { ++ u8 res4[48]; /* 0xd0 */ ++ u8 vender_id[2]; /* 0x100 */ ++ u8 product_id[2]; /* 0x102 */ ++ u8 usb_option; /* 0x104 */ ++ u8 mac_addr[ETH_ALEN]; /* 0x107 */ ++}; ++ + struct rtw8723d_efuse { + __le16 rtl_id; + u8 rsvd[2]; +@@ -69,7 +77,10 @@ struct rtw8723d_efuse { + u8 rfe_option; + u8 country_code[2]; + u8 res[3]; +- struct rtw8723de_efuse e; ++ union { ++ struct rtw8723de_efuse e; ++ struct rtw8723du_efuse u; ++ }; + }; + + extern const struct rtw_chip_info rtw8723d_hw_spec; +diff --git a/drivers/net/wireless/realtek/rtw88/rtw8723du.c b/drivers/net/wireless/realtek/rtw88/rtw8723du.c +new file mode 100644 +index 0000000000000..322a805da76b0 +--- /dev/null ++++ b/drivers/net/wireless/realtek/rtw88/rtw8723du.c +@@ -0,0 +1,36 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause ++/* Copyright(c) 2018-2019 Realtek Corporation ++ */ ++ ++#include ++#include ++#include "main.h" ++#include "rtw8723d.h" ++#include "usb.h" ++ ++static const struct usb_device_id rtw_8723du_id_table[] = { ++ { USB_DEVICE_AND_INTERFACE_INFO(RTW_USB_VENDOR_ID_REALTEK, 0xd723, 0xff, 0xff, 0xff), ++ .driver_info = (kernel_ulong_t)&(rtw8723d_hw_spec) }, /* 8723DU 1*1 */ ++ { USB_DEVICE_AND_INTERFACE_INFO(0x7392, 0xd611, 0xff, 0xff, 0xff), ++ .driver_info = (kernel_ulong_t)&(rtw8723d_hw_spec) }, /* Edimax EW-7611ULB V2 */ ++ { }, ++}; ++MODULE_DEVICE_TABLE(usb, rtw_8723du_id_table); ++ ++static int rtw8723du_probe(struct usb_interface *intf, ++ const struct usb_device_id *id) ++{ ++ return rtw_usb_probe(intf, id); ++} ++ ++static struct usb_driver rtw_8723du_driver = { ++ .name = "rtw_8723du", ++ .id_table = rtw_8723du_id_table, ++ .probe = rtw8723du_probe, ++ .disconnect = rtw_usb_disconnect, ++}; ++module_usb_driver(rtw_8723du_driver); ++ ++MODULE_AUTHOR("Hans Ulli Kroll "); ++MODULE_DESCRIPTION("Realtek 802.11n wireless 8723du driver"); ++MODULE_LICENSE("Dual BSD/GPL"); +-- +cgit diff --git a/patch/misc/rtw88/6.1/002-rtw88-fix-rcu-lock.patch b/patch/misc/rtw88/6.1/002-rtw88-fix-rcu-lock.patch new file mode 100644 index 0000000000..0837b56bc9 --- /dev/null +++ b/patch/misc/rtw88/6.1/002-rtw88-fix-rcu-lock.patch @@ -0,0 +1,302 @@ +From: Martin Blumenstingl +To: linux-wireless@vger.kernel.org +Cc: tony0620emma@gmail.com, kvalo@kernel.org, pkshih@realtek.com, + s.hauer@pengutronix.de, netdev@vger.kernel.org, + linux-kernel@vger.kernel.org, + Martin Blumenstingl +Subject: [PATCH v3 0/3] wifi: rtw88: Three locking fixes for existing code +Date: Sun, 8 Jan 2023 22:13:21 +0100 +Message-Id: <20230108211324.442823-1-martin.blumenstingl@googlemail.com> +X-Mailer: git-send-email 2.39.0 +MIME-Version: 1.0 +Content-Transfer-Encoding: 8bit +Precedence: bulk +List-ID: +X-Mailing-List: linux-wireless@vger.kernel.org + +This series consists of three patches which are fixing existing +behavior (meaning: it either affects PCIe or USB or both) in the rtw88 +driver. +We previously had discussed patches for these locking issues while +working on SDIO support, but the problem never ocurred while testing +USB cards. It turns out that these are still needed and I think that +they also fix the same problems for USB users (it's not clear how often +it happens there though) - and possibly even PCIe card users. + +The issue fixed by the second and third patches have been spotted by a +user who tested rtw88 SDIO support. Everything is working fine for him +but there are warnings [1] and [2] in the kernel log stating "Voluntary +context switch within RCU read-side critical section!". + +The solution in the third and fourth patch was actually suggested by +Ping-Ke in [3]. Thanks again! + +These fixes are indepdent of my other series adding SDIO support to the +rtw88 driver, meaning they can be added to the wireless driver tree on +top of Linux 6.2-rc1 or linux-next. + + +Changes since v1 at [4]: +- Keep the u8 bitfields in patch 1 but split the res2 field into res2_1 + and res2_2 as suggested by Ping-Ke +- Added Ping-Ke's reviewed-by to patches 2-4 - thank you! +- Added a paragraph in the cover-letter to avoid confusion whether + these patches depend on the rtw88 SDIO support series + +Changes since v2 at [5]: +- Added Ping-Ke's Reviewed-by and Sascha's Tested-by (thanks to both of + you!) +- Dropped patch 1/4 "rtw88: Add packed attribute to the eFuse structs" + This requires more discussion. I'll send a separate patch for this. +- Updated cover letter title so it's clear that this is independent of + SDIO support code + + +[0] https://lore.kernel.org/linux-wireless/695c976e02ed44a2b2345a3ceb226fc4@realtek.com/ +[1] https://github.com/LibreELEC/LibreELEC.tv/pull/7301#issuecomment-1366421445 +[2] https://github.com/LibreELEC/LibreELEC.tv/pull/7301#issuecomment-1366610249 +[3] https://lore.kernel.org/lkml/e0aa1ba4336ab130712e1fcb425e6fd0adca4145.camel@realtek.com/ +[4] https://lore.kernel.org/linux-wireless/20221228133547.633797-1-martin.blumenstingl@googlemail.com/ +[5] https://lore.kernel.org/linux-wireless/20221229124845.1155429-1-martin.blumenstingl@googlemail.com/ + + +Martin Blumenstingl (3): + wifi: rtw88: Move register access from rtw_bf_assoc() outside the RCU + wifi: rtw88: Use rtw_iterate_vifs() for rtw_vif_watch_dog_iter() + wifi: rtw88: Use non-atomic sta iterator in rtw_ra_mask_info_update() + + drivers/net/wireless/realtek/rtw88/bf.c | 13 +++++++------ + drivers/net/wireless/realtek/rtw88/mac80211.c | 4 +++- + drivers/net/wireless/realtek/rtw88/main.c | 6 ++++-- + 3 files changed, 14 insertions(+), 9 deletions(-) + +-- +2.39.0 + +From: Martin Blumenstingl +To: linux-wireless@vger.kernel.org +Cc: tony0620emma@gmail.com, kvalo@kernel.org, pkshih@realtek.com, + s.hauer@pengutronix.de, netdev@vger.kernel.org, + linux-kernel@vger.kernel.org, + Martin Blumenstingl +Subject: [PATCH v3 1/3] wifi: rtw88: Move register access from rtw_bf_assoc() outside the RCU +Date: Sun, 8 Jan 2023 22:13:22 +0100 +Message-Id: <20230108211324.442823-2-martin.blumenstingl@googlemail.com> +X-Mailer: git-send-email 2.39.0 +In-Reply-To: <20230108211324.442823-1-martin.blumenstingl@googlemail.com> +References: <20230108211324.442823-1-martin.blumenstingl@googlemail.com> +MIME-Version: 1.0 +Content-Transfer-Encoding: 8bit +Precedence: bulk +List-ID: +X-Mailing-List: linux-wireless@vger.kernel.org + +USB and (upcoming) SDIO support may sleep in the read/write handlers. +Shrink the RCU critical section so it only cover the call to +ieee80211_find_sta() and finding the ic_vht_cap/vht_cap based on the +found station. This moves the chip's BFEE configuration outside the +rcu_read_lock section and thus prevent "scheduling while atomic" or +"Voluntary context switch within RCU read-side critical section!" +warnings when accessing the registers using an SDIO card (which is +where this issue has been spotted in the real world - but it also +affects USB cards). + +Reviewed-by: Ping-Ke Shih +Tested-by: Sascha Hauer +Signed-off-by: Martin Blumenstingl +--- +v1 -> v2: +- Added Ping-Ke's Reviewed-by (thank you!) + +v2 -> v3: +- Added Sascha's Tested-by (thank you!) +- added "wifi" prefix to the subject and reworded the title accordingly + + + drivers/net/wireless/realtek/rtw88/bf.c | 13 +++++++------ + 1 file changed, 7 insertions(+), 6 deletions(-) + +diff --git a/drivers/net/wireless/realtek/rtw88/bf.c b/drivers/net/wireless/realtek/rtw88/bf.c +index 038a30b170ef..c827c4a2814b 100644 +--- a/drivers/net/wireless/realtek/rtw88/bf.c ++++ b/drivers/net/wireless/realtek/rtw88/bf.c +@@ -49,19 +49,23 @@ void rtw_bf_assoc(struct rtw_dev *rtwdev, struct ieee80211_vif *vif, + + sta = ieee80211_find_sta(vif, bssid); + if (!sta) { ++ rcu_read_unlock(); ++ + rtw_warn(rtwdev, "failed to find station entry for bss %pM\n", + bssid); +- goto out_unlock; ++ return; + } + + ic_vht_cap = &hw->wiphy->bands[NL80211_BAND_5GHZ]->vht_cap; + vht_cap = &sta->deflink.vht_cap; + ++ rcu_read_unlock(); ++ + if ((ic_vht_cap->cap & IEEE80211_VHT_CAP_MU_BEAMFORMEE_CAPABLE) && + (vht_cap->cap & IEEE80211_VHT_CAP_MU_BEAMFORMER_CAPABLE)) { + if (bfinfo->bfer_mu_cnt >= chip->bfer_mu_max_num) { + rtw_dbg(rtwdev, RTW_DBG_BF, "mu bfer number over limit\n"); +- goto out_unlock; ++ return; + } + + ether_addr_copy(bfee->mac_addr, bssid); +@@ -75,7 +79,7 @@ void rtw_bf_assoc(struct rtw_dev *rtwdev, struct ieee80211_vif *vif, + (vht_cap->cap & IEEE80211_VHT_CAP_SU_BEAMFORMER_CAPABLE)) { + if (bfinfo->bfer_su_cnt >= chip->bfer_su_max_num) { + rtw_dbg(rtwdev, RTW_DBG_BF, "su bfer number over limit\n"); +- goto out_unlock; ++ return; + } + + sound_dim = vht_cap->cap & +@@ -98,9 +102,6 @@ void rtw_bf_assoc(struct rtw_dev *rtwdev, struct ieee80211_vif *vif, + + rtw_chip_config_bfee(rtwdev, rtwvif, bfee, true); + } +- +-out_unlock: +- rcu_read_unlock(); + } + + void rtw_bf_init_bfer_entry_mu(struct rtw_dev *rtwdev, +-- +2.39.0 + +From: Martin Blumenstingl +To: linux-wireless@vger.kernel.org +Cc: tony0620emma@gmail.com, kvalo@kernel.org, pkshih@realtek.com, + s.hauer@pengutronix.de, netdev@vger.kernel.org, + linux-kernel@vger.kernel.org, + Martin Blumenstingl +Subject: [PATCH v3 2/3] wifi: rtw88: Use rtw_iterate_vifs() for rtw_vif_watch_dog_iter() +Date: Sun, 8 Jan 2023 22:13:23 +0100 +Message-Id: <20230108211324.442823-3-martin.blumenstingl@googlemail.com> +X-Mailer: git-send-email 2.39.0 +In-Reply-To: <20230108211324.442823-1-martin.blumenstingl@googlemail.com> +References: <20230108211324.442823-1-martin.blumenstingl@googlemail.com> +MIME-Version: 1.0 +Content-Transfer-Encoding: 8bit +Precedence: bulk +List-ID: +X-Mailing-List: linux-wireless@vger.kernel.org + +USB and (upcoming) SDIO support may sleep in the read/write handlers. +Make rtw_watch_dog_work() use rtw_iterate_vifs() to prevent "scheduling +while atomic" or "Voluntary context switch within RCU read-side +critical section!" warnings when accessing the registers using an SDIO +card (which is where this issue has been spotted in the real world but +it also affects USB cards). + +Fixes: 78d5bf925f30 ("wifi: rtw88: iterate over vif/sta list non-atomically") +Suggested-by: Ping-Ke Shih +Reviewed-by: Ping-Ke Shih +Tested-by: Sascha Hauer +Signed-off-by: Martin Blumenstingl +--- +v1 -> v2: +- no change + +v2 -> v3: +- Added Ping-Ke's Reviewed-by (thank you!) +- Added Sascha's Tested-by (thank you!) +- added "wifi" prefix to the subject and reworded the title accordingly + + + drivers/net/wireless/realtek/rtw88/main.c | 6 ++++-- + 1 file changed, 4 insertions(+), 2 deletions(-) + +diff --git a/drivers/net/wireless/realtek/rtw88/main.c b/drivers/net/wireless/realtek/rtw88/main.c +index 888427cf3bdf..b2e78737bd5d 100644 +--- a/drivers/net/wireless/realtek/rtw88/main.c ++++ b/drivers/net/wireless/realtek/rtw88/main.c +@@ -241,8 +241,10 @@ static void rtw_watch_dog_work(struct work_struct *work) + rtw_phy_dynamic_mechanism(rtwdev); + + data.rtwdev = rtwdev; +- /* use atomic version to avoid taking local->iflist_mtx mutex */ +- rtw_iterate_vifs_atomic(rtwdev, rtw_vif_watch_dog_iter, &data); ++ /* rtw_iterate_vifs internally uses an atomic iterator which is needed ++ * to avoid taking local->iflist_mtx mutex ++ */ ++ rtw_iterate_vifs(rtwdev, rtw_vif_watch_dog_iter, &data); + + /* fw supports only one station associated to enter lps, if there are + * more than two stations associated to the AP, then we can not enter +-- +2.39.0 + +From: Martin Blumenstingl +To: linux-wireless@vger.kernel.org +Cc: tony0620emma@gmail.com, kvalo@kernel.org, pkshih@realtek.com, + s.hauer@pengutronix.de, netdev@vger.kernel.org, + linux-kernel@vger.kernel.org, + Martin Blumenstingl +Subject: [PATCH v3 3/3] wifi: rtw88: Use non-atomic sta iterator in rtw_ra_mask_info_update() +Date: Sun, 8 Jan 2023 22:13:24 +0100 +Message-Id: <20230108211324.442823-4-martin.blumenstingl@googlemail.com> +X-Mailer: git-send-email 2.39.0 +In-Reply-To: <20230108211324.442823-1-martin.blumenstingl@googlemail.com> +References: <20230108211324.442823-1-martin.blumenstingl@googlemail.com> +MIME-Version: 1.0 +Content-Transfer-Encoding: 8bit +Precedence: bulk +List-ID: +X-Mailing-List: linux-wireless@vger.kernel.org + +USB and (upcoming) SDIO support may sleep in the read/write handlers. +Use non-atomic rtw_iterate_stas() in rtw_ra_mask_info_update() because +the iterator function rtw_ra_mask_info_update_iter() needs to read and +write registers from within rtw_update_sta_info(). Using the non-atomic +iterator ensures that we can sleep during USB and SDIO register reads +and writes. This fixes "scheduling while atomic" or "Voluntary context +switch within RCU read-side critical section!" warnings as seen by SDIO +card users (but it also affects USB cards). + +Fixes: 78d5bf925f30 ("wifi: rtw88: iterate over vif/sta list non-atomically") +Suggested-by: Ping-Ke Shih +Reviewed-by: Ping-Ke Shih +Tested-by: Sascha Hauer +Signed-off-by: Martin Blumenstingl +--- +v1 -> v2: +- Added Ping-Ke's Reviewed-by (thank you!) + +v2 -> v3: +- Added Sascha's Tested-by (thank you!) +- added "wifi" prefix to the subject and reworded the title accordingly + + + drivers/net/wireless/realtek/rtw88/mac80211.c | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +diff --git a/drivers/net/wireless/realtek/rtw88/mac80211.c b/drivers/net/wireless/realtek/rtw88/mac80211.c +index 776a9a9884b5..3b92ac611d3f 100644 +--- a/drivers/net/wireless/realtek/rtw88/mac80211.c ++++ b/drivers/net/wireless/realtek/rtw88/mac80211.c +@@ -737,7 +737,7 @@ static void rtw_ra_mask_info_update(struct rtw_dev *rtwdev, + br_data.rtwdev = rtwdev; + br_data.vif = vif; + br_data.mask = mask; +- rtw_iterate_stas_atomic(rtwdev, rtw_ra_mask_info_update_iter, &br_data); ++ rtw_iterate_stas(rtwdev, rtw_ra_mask_info_update_iter, &br_data); + } + + static int rtw_ops_set_bitrate_mask(struct ieee80211_hw *hw, +@@ -746,7 +746,9 @@ static int rtw_ops_set_bitrate_mask(struct ieee80211_hw *hw, + { + struct rtw_dev *rtwdev = hw->priv; + ++ mutex_lock(&rtwdev->mutex); + rtw_ra_mask_info_update(rtwdev, vif, mask); ++ mutex_unlock(&rtwdev->mutex); + + return 0; + } +-- +2.39.0 diff --git a/patch/misc/rtw88/6.1/003-rtw88-rfc.patch b/patch/misc/rtw88/6.1/003-rtw88-rfc.patch new file mode 100644 index 0000000000..cc26d518f0 --- /dev/null +++ b/patch/misc/rtw88/6.1/003-rtw88-rfc.patch @@ -0,0 +1,2452 @@ +This makes it easier to understand which values are allowed for the +"queue" variable. + +Signed-off-by: Martin Blumenstingl +--- + drivers/net/wireless/realtek/rtw88/pci.c | 17 ++++++++++------- + 1 file changed, 10 insertions(+), 7 deletions(-) + +diff --git a/drivers/net/wireless/realtek/rtw88/pci.c b/drivers/net/wireless/realtek/rtw88/pci.c +index 45ce7e624c03..5492107fc85b 100644 +--- a/drivers/net/wireless/realtek/rtw88/pci.c ++++ b/drivers/net/wireless/realtek/rtw88/pci.c +@@ -30,7 +30,8 @@ static u32 rtw_pci_tx_queue_idx_addr[] = { + [RTW_TX_QUEUE_H2C] = RTK_PCI_TXBD_IDX_H2CQ, + }; + +-static u8 rtw_pci_get_tx_qsel(struct sk_buff *skb, u8 queue) ++static u8 rtw_pci_get_tx_qsel(struct sk_buff *skb, ++ enum rtw_tx_queue_type queue) + { + switch (queue) { + case RTW_TX_QUEUE_BCN: +@@ -542,7 +543,7 @@ static int rtw_pci_setup(struct rtw_dev *rtwdev) + static void rtw_pci_dma_release(struct rtw_dev *rtwdev, struct rtw_pci *rtwpci) + { + struct rtw_pci_tx_ring *tx_ring; +- u8 queue; ++ enum rtw_tx_queue_type queue; + + rtw_pci_reset_trx_ring(rtwdev); + for (queue = 0; queue < RTK_MAX_TX_QUEUE_NUM; queue++) { +@@ -608,8 +609,8 @@ static void rtw_pci_deep_ps_enter(struct rtw_dev *rtwdev) + { + struct rtw_pci *rtwpci = (struct rtw_pci *)rtwdev->priv; + struct rtw_pci_tx_ring *tx_ring; ++ enum rtw_tx_queue_type queue; + bool tx_empty = true; +- u8 queue; + + if (rtw_fw_feature_check(&rtwdev->fw, FW_FEATURE_TX_WAKE)) + goto enter_deep_ps; +@@ -803,7 +804,8 @@ static void rtw_pci_flush_queues(struct rtw_dev *rtwdev, u32 queues, bool drop) + __rtw_pci_flush_queues(rtwdev, pci_queues, drop); + } + +-static void rtw_pci_tx_kick_off_queue(struct rtw_dev *rtwdev, u8 queue) ++static void rtw_pci_tx_kick_off_queue(struct rtw_dev *rtwdev, ++ enum rtw_tx_queue_type queue) + { + struct rtw_pci *rtwpci = (struct rtw_pci *)rtwdev->priv; + struct rtw_pci_tx_ring *ring; +@@ -822,7 +824,7 @@ static void rtw_pci_tx_kick_off_queue(struct rtw_dev *rtwdev, u8 queue) + static void rtw_pci_tx_kick_off(struct rtw_dev *rtwdev) + { + struct rtw_pci *rtwpci = (struct rtw_pci *)rtwdev->priv; +- u8 queue; ++ enum rtw_tx_queue_type queue; + + for (queue = 0; queue < RTK_MAX_TX_QUEUE_NUM; queue++) + if (test_and_clear_bit(queue, rtwpci->tx_queued)) +@@ -831,7 +833,8 @@ static void rtw_pci_tx_kick_off(struct rtw_dev *rtwdev) + + static int rtw_pci_tx_write_data(struct rtw_dev *rtwdev, + struct rtw_tx_pkt_info *pkt_info, +- struct sk_buff *skb, u8 queue) ++ struct sk_buff *skb, ++ enum rtw_tx_queue_type queue) + { + struct rtw_pci *rtwpci = (struct rtw_pci *)rtwdev->priv; + const struct rtw_chip_info *chip = rtwdev->chip; +@@ -949,9 +952,9 @@ static int rtw_pci_tx_write(struct rtw_dev *rtwdev, + struct rtw_tx_pkt_info *pkt_info, + struct sk_buff *skb) + { ++ enum rtw_tx_queue_type queue = rtw_hw_queue_mapping(skb); + struct rtw_pci *rtwpci = (struct rtw_pci *)rtwdev->priv; + struct rtw_pci_tx_ring *ring; +- u8 queue = rtw_hw_queue_mapping(skb); + int ret; + + ret = rtw_pci_tx_write_data(rtwdev, pkt_info, skb, queue); +-- +2.39.0 + +rtw_hw_queue_mapping() and ac_to_hwq[] hold values of type enum +rtw_tx_queue_type. Change their types to reflect this to make it easier +to understand this part of the code. + +While here, also change the array to be static const as it is not +supposed to be modified at runtime. + +Signed-off-by: Martin Blumenstingl +--- + drivers/net/wireless/realtek/rtw88/pci.c | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +diff --git a/drivers/net/wireless/realtek/rtw88/pci.c b/drivers/net/wireless/realtek/rtw88/pci.c +index 0975d27240e4..45ce7e624c03 100644 +--- a/drivers/net/wireless/realtek/rtw88/pci.c ++++ b/drivers/net/wireless/realtek/rtw88/pci.c +@@ -669,7 +669,7 @@ static void rtw_pci_deep_ps(struct rtw_dev *rtwdev, bool enter) + spin_unlock_bh(&rtwpci->irq_lock); + } + +-static u8 ac_to_hwq[] = { ++static const enum rtw_tx_queue_type ac_to_hwq[] = { + [IEEE80211_AC_VO] = RTW_TX_QUEUE_VO, + [IEEE80211_AC_VI] = RTW_TX_QUEUE_VI, + [IEEE80211_AC_BE] = RTW_TX_QUEUE_BE, +@@ -678,12 +678,12 @@ static u8 ac_to_hwq[] = { + + static_assert(ARRAY_SIZE(ac_to_hwq) == IEEE80211_NUM_ACS); + +-static u8 rtw_hw_queue_mapping(struct sk_buff *skb) ++static enum rtw_tx_queue_type rtw_hw_queue_mapping(struct sk_buff *skb) + { + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + __le16 fc = hdr->frame_control; + u8 q_mapping = skb_get_queue_mapping(skb); +- u8 queue; ++ enum rtw_tx_queue_type queue; + + if (unlikely(ieee80211_is_beacon(fc))) + queue = RTW_TX_QUEUE_BCN; +-- +2.39.0 + +Add the SDIO vendor ID for Realtek and some device IDs extracted from +their GPL vendor driver. This will be useful in the future when the +rtw88 driver gains support for these chips. + +Signed-off-by: Martin Blumenstingl +--- + include/linux/mmc/sdio_ids.h | 9 +++++++++ + 1 file changed, 9 insertions(+) + +diff --git a/include/linux/mmc/sdio_ids.h b/include/linux/mmc/sdio_ids.h +index 74f9d9a6d330..bba39d4565da 100644 +--- a/include/linux/mmc/sdio_ids.h ++++ b/include/linux/mmc/sdio_ids.h +@@ -111,6 +111,15 @@ + #define SDIO_VENDOR_ID_MICROCHIP_WILC 0x0296 + #define SDIO_DEVICE_ID_MICROCHIP_WILC1000 0x5347 + ++#define SDIO_VENDOR_ID_REALTEK 0x024c ++#define SDIO_DEVICE_ID_REALTEK_RTW8723BS 0xb723 ++#define SDIO_DEVICE_ID_REALTEK_RTW8723DS 0xd723 ++#define SDIO_DEVICE_ID_REALTEK_RTW8821BS 0xb821 ++#define SDIO_DEVICE_ID_REALTEK_RTW8821CS 0xc821 ++#define SDIO_DEVICE_ID_REALTEK_RTW8821DS 0xd821 ++#define SDIO_DEVICE_ID_REALTEK_RTW8822BS 0xb822 ++#define SDIO_DEVICE_ID_REALTEK_RTW8822CS 0xc822 ++ + #define SDIO_VENDOR_ID_SIANO 0x039a + #define SDIO_DEVICE_ID_SIANO_NOVA_B0 0x0201 + #define SDIO_DEVICE_ID_SIANO_NICE 0x0202 +-- +2.39.0 + +This code is not specific to the PCIe bus type but can be re-used by USB +and SDIO bus types. Move it to tx.{c,h} to avoid code-duplication in the +future. While here, add checking of the ac argument in +rtw_tx_ac_to_hwq() so we're not accessing entries beyond the end of the +array. + +Signed-off-by: Martin Blumenstingl +--- + drivers/net/wireless/realtek/rtw88/pci.c | 35 ++------------------ + drivers/net/wireless/realtek/rtw88/tx.c | 41 ++++++++++++++++++++++++ + drivers/net/wireless/realtek/rtw88/tx.h | 3 ++ + 3 files changed, 46 insertions(+), 33 deletions(-) + +diff --git a/drivers/net/wireless/realtek/rtw88/pci.c b/drivers/net/wireless/realtek/rtw88/pci.c +index 5492107fc85b..b4bd831c9845 100644 +--- a/drivers/net/wireless/realtek/rtw88/pci.c ++++ b/drivers/net/wireless/realtek/rtw88/pci.c +@@ -670,37 +670,6 @@ static void rtw_pci_deep_ps(struct rtw_dev *rtwdev, bool enter) + spin_unlock_bh(&rtwpci->irq_lock); + } + +-static const enum rtw_tx_queue_type ac_to_hwq[] = { +- [IEEE80211_AC_VO] = RTW_TX_QUEUE_VO, +- [IEEE80211_AC_VI] = RTW_TX_QUEUE_VI, +- [IEEE80211_AC_BE] = RTW_TX_QUEUE_BE, +- [IEEE80211_AC_BK] = RTW_TX_QUEUE_BK, +-}; +- +-static_assert(ARRAY_SIZE(ac_to_hwq) == IEEE80211_NUM_ACS); +- +-static enum rtw_tx_queue_type rtw_hw_queue_mapping(struct sk_buff *skb) +-{ +- struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; +- __le16 fc = hdr->frame_control; +- u8 q_mapping = skb_get_queue_mapping(skb); +- enum rtw_tx_queue_type queue; +- +- if (unlikely(ieee80211_is_beacon(fc))) +- queue = RTW_TX_QUEUE_BCN; +- else if (unlikely(ieee80211_is_mgmt(fc) || ieee80211_is_ctl(fc))) +- queue = RTW_TX_QUEUE_MGMT; +- else if (is_broadcast_ether_addr(hdr->addr1) || +- is_multicast_ether_addr(hdr->addr1)) +- queue = RTW_TX_QUEUE_HI0; +- else if (WARN_ON_ONCE(q_mapping >= ARRAY_SIZE(ac_to_hwq))) +- queue = ac_to_hwq[IEEE80211_AC_BE]; +- else +- queue = ac_to_hwq[q_mapping]; +- +- return queue; +-} +- + static void rtw_pci_release_rsvd_page(struct rtw_pci *rtwpci, + struct rtw_pci_tx_ring *ring) + { +@@ -798,7 +767,7 @@ static void rtw_pci_flush_queues(struct rtw_dev *rtwdev, u32 queues, bool drop) + } else { + for (i = 0; i < rtwdev->hw->queues; i++) + if (queues & BIT(i)) +- pci_queues |= BIT(ac_to_hwq[i]); ++ pci_queues |= BIT(rtw_tx_ac_to_hwq(i)); + } + + __rtw_pci_flush_queues(rtwdev, pci_queues, drop); +@@ -952,7 +921,7 @@ static int rtw_pci_tx_write(struct rtw_dev *rtwdev, + struct rtw_tx_pkt_info *pkt_info, + struct sk_buff *skb) + { +- enum rtw_tx_queue_type queue = rtw_hw_queue_mapping(skb); ++ enum rtw_tx_queue_type queue = rtw_tx_queue_mapping(skb); + struct rtw_pci *rtwpci = (struct rtw_pci *)rtwdev->priv; + struct rtw_pci_tx_ring *ring; + int ret; +diff --git a/drivers/net/wireless/realtek/rtw88/tx.c b/drivers/net/wireless/realtek/rtw88/tx.c +index ab39245e9c2f..bb5c7492c98b 100644 +--- a/drivers/net/wireless/realtek/rtw88/tx.c ++++ b/drivers/net/wireless/realtek/rtw88/tx.c +@@ -682,3 +682,44 @@ void rtw_txq_cleanup(struct rtw_dev *rtwdev, struct ieee80211_txq *txq) + list_del_init(&rtwtxq->list); + spin_unlock_bh(&rtwdev->txq_lock); + } ++ ++static const enum rtw_tx_queue_type ac_to_hwq[] = { ++ [IEEE80211_AC_VO] = RTW_TX_QUEUE_VO, ++ [IEEE80211_AC_VI] = RTW_TX_QUEUE_VI, ++ [IEEE80211_AC_BE] = RTW_TX_QUEUE_BE, ++ [IEEE80211_AC_BK] = RTW_TX_QUEUE_BK, ++}; ++ ++static_assert(ARRAY_SIZE(ac_to_hwq) == IEEE80211_NUM_ACS); ++ ++enum rtw_tx_queue_type rtw_tx_ac_to_hwq(enum ieee80211_ac_numbers ac) ++{ ++ if (WARN_ON(unlikely(ac >= IEEE80211_NUM_ACS))) ++ return RTW_TX_QUEUE_BE; ++ ++ return ac_to_hwq[ac]; ++} ++EXPORT_SYMBOL(rtw_tx_ac_to_hwq); ++ ++enum rtw_tx_queue_type rtw_tx_queue_mapping(struct sk_buff *skb) ++{ ++ struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; ++ __le16 fc = hdr->frame_control; ++ u8 q_mapping = skb_get_queue_mapping(skb); ++ enum rtw_tx_queue_type queue; ++ ++ if (unlikely(ieee80211_is_beacon(fc))) ++ queue = RTW_TX_QUEUE_BCN; ++ else if (unlikely(ieee80211_is_mgmt(fc) || ieee80211_is_ctl(fc))) ++ queue = RTW_TX_QUEUE_MGMT; ++ else if (is_broadcast_ether_addr(hdr->addr1) || ++ is_multicast_ether_addr(hdr->addr1)) ++ queue = RTW_TX_QUEUE_HI0; ++ else if (WARN_ON_ONCE(q_mapping >= ARRAY_SIZE(ac_to_hwq))) ++ queue = ac_to_hwq[IEEE80211_AC_BE]; ++ else ++ queue = ac_to_hwq[q_mapping]; ++ ++ return queue; ++} ++EXPORT_SYMBOL(rtw_tx_queue_mapping); +diff --git a/drivers/net/wireless/realtek/rtw88/tx.h b/drivers/net/wireless/realtek/rtw88/tx.h +index a2f3ac326041..197d5868c8ad 100644 +--- a/drivers/net/wireless/realtek/rtw88/tx.h ++++ b/drivers/net/wireless/realtek/rtw88/tx.h +@@ -131,6 +131,9 @@ rtw_tx_write_data_h2c_get(struct rtw_dev *rtwdev, + struct rtw_tx_pkt_info *pkt_info, + u8 *buf, u32 size); + ++enum rtw_tx_queue_type rtw_tx_ac_to_hwq(enum ieee80211_ac_numbers ac); ++enum rtw_tx_queue_type rtw_tx_queue_mapping(struct sk_buff *skb); ++ + static inline + void fill_txdesc_checksum_common(u8 *txdesc, size_t words) + { +-- +2.39.0 + +The efuse of the SDIO RTL8821CS chip has only one known member: the mac +address is at offset 0x11a. Add a struct rtw8821cs_efuse describing this +and use it for copying the mac address when the SDIO bus is used. + +Signed-off-by: Martin Blumenstingl +--- + drivers/net/wireless/realtek/rtw88/rtw8821c.c | 9 +++++++++ + drivers/net/wireless/realtek/rtw88/rtw8821c.h | 6 ++++++ + 2 files changed, 15 insertions(+) + +diff --git a/drivers/net/wireless/realtek/rtw88/rtw8821c.c b/drivers/net/wireless/realtek/rtw88/rtw8821c.c +index 17f800f6efbd..dd01b22f9770 100644 +--- a/drivers/net/wireless/realtek/rtw88/rtw8821c.c ++++ b/drivers/net/wireless/realtek/rtw88/rtw8821c.c +@@ -26,6 +26,12 @@ static void rtw8821ce_efuse_parsing(struct rtw_efuse *efuse, + ether_addr_copy(efuse->addr, map->e.mac_addr); + } + ++static void rtw8821cs_efuse_parsing(struct rtw_efuse *efuse, ++ struct rtw8821c_efuse *map) ++{ ++ ether_addr_copy(efuse->addr, map->s.mac_addr); ++} ++ + static void rtw8821cu_efuse_parsing(struct rtw_efuse *efuse, + struct rtw8821c_efuse *map) + { +@@ -74,6 +80,9 @@ static int rtw8821c_read_efuse(struct rtw_dev *rtwdev, u8 *log_map) + case RTW_HCI_TYPE_PCIE: + rtw8821ce_efuse_parsing(efuse, map); + break; ++ case RTW_HCI_TYPE_SDIO: ++ rtw8821cs_efuse_parsing(efuse, map); ++ break; + case RTW_HCI_TYPE_USB: + rtw8821cu_efuse_parsing(efuse, map); + break; +diff --git a/drivers/net/wireless/realtek/rtw88/rtw8821c.h b/drivers/net/wireless/realtek/rtw88/rtw8821c.h +index 1c81260f3a54..1deea54575b5 100644 +--- a/drivers/net/wireless/realtek/rtw88/rtw8821c.h ++++ b/drivers/net/wireless/realtek/rtw88/rtw8821c.h +@@ -65,6 +65,11 @@ struct rtw8821ce_efuse { + u8 res7; + }; + ++struct rtw8821cs_efuse { ++ u8 res4[0x4a]; /* 0xd0 */ ++ u8 mac_addr[ETH_ALEN]; /* 0x11a */ ++}; ++ + struct rtw8821c_efuse { + __le16 rtl_id; + u8 res0[0x0e]; +@@ -93,6 +98,7 @@ struct rtw8821c_efuse { + u8 res[3]; + union { + struct rtw8821ce_efuse e; ++ struct rtw8821cs_efuse s; + struct rtw8821cu_efuse u; + }; + }; +-- +2.39.0 + +The efuse of the SDIO RTL8822BS chip has only one known member: the mac +address is at offset 0x11a. Add a struct rtw8822bs_efuse describing this +and use it for copying the mac address when the SDIO bus is used. + +Signed-off-by: Martin Blumenstingl +--- + drivers/net/wireless/realtek/rtw88/rtw8822b.c | 10 ++++++++++ + drivers/net/wireless/realtek/rtw88/rtw8822b.h | 6 ++++++ + 2 files changed, 16 insertions(+) + +diff --git a/drivers/net/wireless/realtek/rtw88/rtw8822b.c b/drivers/net/wireless/realtek/rtw88/rtw8822b.c +index 74dfb89b2c94..4ed5b98fab23 100644 +--- a/drivers/net/wireless/realtek/rtw88/rtw8822b.c ++++ b/drivers/net/wireless/realtek/rtw88/rtw8822b.c +@@ -26,10 +26,17 @@ static void rtw8822be_efuse_parsing(struct rtw_efuse *efuse, + ether_addr_copy(efuse->addr, map->e.mac_addr); + } + ++static void rtw8822bs_efuse_parsing(struct rtw_efuse *efuse, ++ struct rtw8822b_efuse *map) ++{ ++ ether_addr_copy(efuse->addr, map->s.mac_addr); ++} ++ + static void rtw8822bu_efuse_parsing(struct rtw_efuse *efuse, + struct rtw8822b_efuse *map) + { + ether_addr_copy(efuse->addr, map->u.mac_addr); ++ + } + + static int rtw8822b_read_efuse(struct rtw_dev *rtwdev, u8 *log_map) +@@ -62,6 +69,9 @@ static int rtw8822b_read_efuse(struct rtw_dev *rtwdev, u8 *log_map) + case RTW_HCI_TYPE_PCIE: + rtw8822be_efuse_parsing(efuse, map); + break; ++ case RTW_HCI_TYPE_SDIO: ++ rtw8822bs_efuse_parsing(efuse, map); ++ break; + case RTW_HCI_TYPE_USB: + rtw8822bu_efuse_parsing(efuse, map); + break; +diff --git a/drivers/net/wireless/realtek/rtw88/rtw8822b.h b/drivers/net/wireless/realtek/rtw88/rtw8822b.h +index 01d3644e0c94..f84bfb6b0df9 100644 +--- a/drivers/net/wireless/realtek/rtw88/rtw8822b.h ++++ b/drivers/net/wireless/realtek/rtw88/rtw8822b.h +@@ -65,6 +65,11 @@ struct rtw8822be_efuse { + u8 res7; + }; + ++struct rtw8822bs_efuse { ++ u8 res4[0x4a]; /* 0xd0 */ ++ u8 mac_addr[ETH_ALEN]; /* 0x11a */ ++}; ++ + struct rtw8822b_efuse { + __le16 rtl_id; + u8 res0[0x0e]; +@@ -94,6 +99,7 @@ struct rtw8822b_efuse { + union { + struct rtw8822bu_efuse u; + struct rtw8822be_efuse e; ++ struct rtw8822bs_efuse s; + }; + }; + +-- +2.39.0 + +The efuse of the SDIO RTL8822CS chip has only one known member: the mac +address is at offset 0x16a. Add a struct rtw8822cs_efuse describing this +and use it for copying the mac address when the SDIO bus is used. + +Signed-off-by: Martin Blumenstingl +--- + drivers/net/wireless/realtek/rtw88/rtw8822c.c | 9 +++++++++ + drivers/net/wireless/realtek/rtw88/rtw8822c.h | 6 ++++++ + 2 files changed, 15 insertions(+) + +diff --git a/drivers/net/wireless/realtek/rtw88/rtw8822c.c b/drivers/net/wireless/realtek/rtw88/rtw8822c.c +index 964e27887fe2..8ec779c7ab84 100644 +--- a/drivers/net/wireless/realtek/rtw88/rtw8822c.c ++++ b/drivers/net/wireless/realtek/rtw88/rtw8822c.c +@@ -29,6 +29,12 @@ static void rtw8822ce_efuse_parsing(struct rtw_efuse *efuse, + ether_addr_copy(efuse->addr, map->e.mac_addr); + } + ++static void rtw8822cs_efuse_parsing(struct rtw_efuse *efuse, ++ struct rtw8822c_efuse *map) ++{ ++ ether_addr_copy(efuse->addr, map->s.mac_addr); ++} ++ + static void rtw8822cu_efuse_parsing(struct rtw_efuse *efuse, + struct rtw8822c_efuse *map) + { +@@ -64,6 +70,9 @@ static int rtw8822c_read_efuse(struct rtw_dev *rtwdev, u8 *log_map) + case RTW_HCI_TYPE_PCIE: + rtw8822ce_efuse_parsing(efuse, map); + break; ++ case RTW_HCI_TYPE_SDIO: ++ rtw8822cs_efuse_parsing(efuse, map); ++ break; + case RTW_HCI_TYPE_USB: + rtw8822cu_efuse_parsing(efuse, map); + break; +diff --git a/drivers/net/wireless/realtek/rtw88/rtw8822c.h b/drivers/net/wireless/realtek/rtw88/rtw8822c.h +index 479d5d769c52..eec2e3074087 100644 +--- a/drivers/net/wireless/realtek/rtw88/rtw8822c.h ++++ b/drivers/net/wireless/realtek/rtw88/rtw8822c.h +@@ -16,6 +16,11 @@ struct rtw8822cu_efuse { + u8 res2[0x3d]; + }; + ++struct rtw8822cs_efuse { ++ u8 res0[0x4a]; /* 0x120 */ ++ u8 mac_addr[ETH_ALEN]; /* 0x16a */ ++}; ++ + struct rtw8822ce_efuse { + u8 mac_addr[ETH_ALEN]; /* 0x120 */ + u8 vender_id[2]; +@@ -92,6 +97,7 @@ struct rtw8822c_efuse { + u8 res10[0x42]; + union { + struct rtw8822cu_efuse u; ++ struct rtw8822cs_efuse s; + struct rtw8822ce_efuse e; + }; + }; +-- +2.39.0 + +32-bit SDIO bus reads/writes only work when the card is powered on. Add +an optional power_switch() callback to struct rtw_hci_ops where we +inform the HCI sub-driver that the chip is now powered on. Based on this +information the upcoming SDIO HCI implementation can then use the +appropriate 32-bit read/write accessors. + +Signed-off-by: Martin Blumenstingl +--- + drivers/net/wireless/realtek/rtw88/hci.h | 8 ++++++++ + drivers/net/wireless/realtek/rtw88/mac.c | 8 ++++++++ + 2 files changed, 16 insertions(+) + +diff --git a/drivers/net/wireless/realtek/rtw88/hci.h b/drivers/net/wireless/realtek/rtw88/hci.h +index 830d7532f2a3..602a6acc1ca1 100644 +--- a/drivers/net/wireless/realtek/rtw88/hci.h ++++ b/drivers/net/wireless/realtek/rtw88/hci.h +@@ -22,6 +22,8 @@ struct rtw_hci_ops { + int (*write_data_rsvd_page)(struct rtw_dev *rtwdev, u8 *buf, u32 size); + int (*write_data_h2c)(struct rtw_dev *rtwdev, u8 *buf, u32 size); + ++ void (*power_switch)(struct rtw_dev *rtwdev, bool on); ++ + u8 (*read8)(struct rtw_dev *rtwdev, u32 addr); + u16 (*read16)(struct rtw_dev *rtwdev, u32 addr); + u32 (*read32)(struct rtw_dev *rtwdev, u32 addr); +@@ -84,6 +86,12 @@ rtw_hci_write_data_h2c(struct rtw_dev *rtwdev, u8 *buf, u32 size) + return rtwdev->hci.ops->write_data_h2c(rtwdev, buf, size); + } + ++static inline void rtw_hci_power_switch(struct rtw_dev *rtwdev, bool on) ++{ ++ if (rtwdev->hci.ops->power_switch) ++ rtwdev->hci.ops->power_switch(rtwdev, on); ++} ++ + static inline u8 rtw_read8(struct rtw_dev *rtwdev, u32 addr) + { + return rtwdev->hci.ops->read8(rtwdev, addr); + +-- +2.39.0 + +Add a sub-driver for SDIO based chipsets which implements the following +functionality: +- register accessors for 8, 16 and 32 bits for all states of the card + (including usage of 4x 8 bit access for one 32 bit buffer if the card + is not fully powered on yet - or if it's fully powered on then 1x 32 + bit access is used) +- checking whether there's space in the TX FIFO queue to transmit data +- transfers from the host to the device for actual network traffic, + reserved pages (for firmware download) and H2C (host-to-card) + transfers +- receiving data from the device +- deep power saving state + +The transmit path is optimized so DMA-capable SDIO host controllers can +directly use the buffers provided because the buffer's physical +addresses are 8 byte aligned. + +The receive path is prepared to support RX aggregation where the +chipset combines multiple MAC frames into one bigger buffer to reduce +SDIO transfer overhead. + +Co-developed-by: Jernej Skrabec +Signed-off-by: Jernej Skrabec +Signed-off-by: Martin Blumenstingl +--- + drivers/net/wireless/realtek/rtw88/Kconfig | 3 + + drivers/net/wireless/realtek/rtw88/Makefile | 3 + + drivers/net/wireless/realtek/rtw88/debug.h | 1 + + drivers/net/wireless/realtek/rtw88/mac.h | 1 - + drivers/net/wireless/realtek/rtw88/reg.h | 10 + + drivers/net/wireless/realtek/rtw88/sdio.c | 1242 +++++++++++++++++++ + drivers/net/wireless/realtek/rtw88/sdio.h | 175 +++ + 7 files changed, 1434 insertions(+), 1 deletion(-) + create mode 100644 drivers/net/wireless/realtek/rtw88/sdio.c + create mode 100644 drivers/net/wireless/realtek/rtw88/sdio.h + +diff --git a/drivers/net/wireless/realtek/rtw88/Kconfig b/drivers/net/wireless/realtek/rtw88/Kconfig +index 651ab56d9c6b..cdf9cb478ee2 100644 +--- a/drivers/net/wireless/realtek/rtw88/Kconfig ++++ b/drivers/net/wireless/realtek/rtw88/Kconfig +@@ -16,6 +16,9 @@ config RTW88_CORE + config RTW88_PCI + tristate + ++config RTW88_SDIO ++ tristate ++ + config RTW88_USB + tristate + +diff --git a/drivers/net/wireless/realtek/rtw88/Makefile b/drivers/net/wireless/realtek/rtw88/Makefile +index fe7293ee87b4..892cad60ba31 100644 +--- a/drivers/net/wireless/realtek/rtw88/Makefile ++++ b/drivers/net/wireless/realtek/rtw88/Makefile +@@ -59,5 +59,8 @@ rtw88_8821cu-objs := rtw8821cu.o + obj-$(CONFIG_RTW88_PCI) += rtw88_pci.o + rtw88_pci-objs := pci.o + ++obj-$(CONFIG_RTW88_SDIO) += rtw88_sdio.o ++rtw88_sdio-objs := sdio.o ++ + obj-$(CONFIG_RTW88_USB) += rtw88_usb.o + rtw88_usb-objs := usb.o +diff --git a/drivers/net/wireless/realtek/rtw88/debug.h b/drivers/net/wireless/realtek/rtw88/debug.h +index 066792dd96af..a9149c6c2b48 100644 +--- a/drivers/net/wireless/realtek/rtw88/debug.h ++++ b/drivers/net/wireless/realtek/rtw88/debug.h +@@ -24,6 +24,7 @@ enum rtw_debug_mask { + RTW_DBG_ADAPTIVITY = 0x00008000, + RTW_DBG_HW_SCAN = 0x00010000, + RTW_DBG_STATE = 0x00020000, ++ RTW_DBG_SDIO = 0x00040000, + + RTW_DBG_ALL = 0xffffffff + }; +diff --git a/drivers/net/wireless/realtek/rtw88/mac.h b/drivers/net/wireless/realtek/rtw88/mac.h +index 3172aa5ac4de..58c3dccc14bb 100644 +--- a/drivers/net/wireless/realtek/rtw88/mac.h ++++ b/drivers/net/wireless/realtek/rtw88/mac.h +@@ -7,7 +7,6 @@ + + #define RTW_HW_PORT_NUM 5 + #define cut_version_to_mask(cut) (0x1 << ((cut) + 1)) +-#define SDIO_LOCAL_OFFSET 0x10250000 + #define DDMA_POLLING_COUNT 1000 + #define C2H_PKT_BUF 256 + #define REPORT_BUF 128 +diff --git a/drivers/net/wireless/realtek/rtw88/reg.h b/drivers/net/wireless/realtek/rtw88/reg.h +index 8852b24d6c2a..4ea2c6b491e9 100644 +--- a/drivers/net/wireless/realtek/rtw88/reg.h ++++ b/drivers/net/wireless/realtek/rtw88/reg.h +@@ -185,6 +185,9 @@ + (((x) & BIT_MASK_TXDMA_VIQ_MAP) << BIT_SHIFT_TXDMA_VIQ_MAP) + #define REG_TXDMA_PQ_MAP 0x010C + #define BIT_RXDMA_ARBBW_EN BIT(0) ++#define BIT_RXSHFT_EN BIT(1) ++#define BIT_RXDMA_AGG_EN BIT(2) ++#define BIT_TXDMA_BW_EN BIT(3) + #define BIT_SHIFT_TXDMA_BEQ_MAP 8 + #define BIT_MASK_TXDMA_BEQ_MAP 0x3 + #define BIT_TXDMA_BEQ_MAP(x) \ +@@ -283,10 +286,17 @@ + #define REG_H2C_TAIL 0x0248 + #define REG_H2C_READ_ADDR 0x024C + #define REG_H2C_INFO 0x0254 ++#define REG_RXDMA_AGG_PG_TH 0x0280 ++#define BIT_SHIFT_DMA_AGG_TO_V1 8 ++#define BIT_EN_PRE_CALC BIT(29) + #define REG_RXPKT_NUM 0x0284 + #define BIT_RXDMA_REQ BIT(19) + #define BIT_RW_RELEASE BIT(18) + #define BIT_RXDMA_IDLE BIT(17) ++#define REG_RXDMA_STATUS 0x0288 ++#define REG_RXDMA_DPR 0x028C ++#define REG_RXDMA_MODE 0x0290 ++#define BIT_DMA_MODE BIT(1) + #define REG_RXPKTNUM 0x02B0 + + #define REG_INT_MIG 0x0304 +diff --git a/drivers/net/wireless/realtek/rtw88/sdio.c b/drivers/net/wireless/realtek/rtw88/sdio.c +new file mode 100644 +index 000000000000..0e637ff2293f +--- /dev/null ++++ b/drivers/net/wireless/realtek/rtw88/sdio.c +@@ -0,0 +1,1242 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause ++/* Copyright (C) 2021 Martin Blumenstingl ++ * Copyright (C) 2021 Jernej Skrabec ++ * ++ * Based on rtw88/pci.c: ++ * Copyright(c) 2018-2019 Realtek Corporation ++ */ ++ ++#include ++#include ++#include ++#include "sdio.h" ++#include "reg.h" ++#include "tx.h" ++#include "rx.h" ++#include "fw.h" ++#include "ps.h" ++#include "debug.h" ++ ++#define RTW_SDIO_INDIRECT_RW_RETRIES 50 ++ ++static bool rtw_sdio_is_bus_addr(u32 addr) ++{ ++ return (addr & RTW_SDIO_BUS_MSK) != 0; ++} ++ ++static bool rtw_sdio_bus_claim_needed(struct rtw_sdio *rtwsdio) ++{ ++ return !rtwsdio->irq_thread || ++ rtwsdio->irq_thread != current; ++} ++ ++static u32 rtw_sdio_to_bus_offset(struct rtw_dev *rtwdev, u32 addr) ++{ ++ switch (addr & RTW_SDIO_BUS_MSK) { ++ case WLAN_IOREG_OFFSET: ++ addr &= WLAN_IOREG_REG_MSK; ++ addr |= FIELD_PREP(REG_SDIO_CMD_ADDR_MSK, ++ REG_SDIO_CMD_ADDR_MAC_REG); ++ break; ++ case SDIO_LOCAL_OFFSET: ++ addr &= SDIO_LOCAL_REG_MSK; ++ addr |= FIELD_PREP(REG_SDIO_CMD_ADDR_MSK, ++ REG_SDIO_CMD_ADDR_SDIO_REG); ++ break; ++ default: ++ rtw_warn(rtwdev, "Cannot convert addr 0x%08x to bus offset", ++ addr); ++ } ++ ++ return addr; ++} ++ ++static void rtw_sdio_writel(struct rtw_sdio *rtwsdio, u32 val, ++ u32 addr, int *ret) ++{ ++ u8 buf[4]; ++ int i; ++ ++ if (!(addr & 3) && rtwsdio->is_powered_on) { ++ sdio_writel(rtwsdio->sdio_func, val, addr, ret); ++ return; ++ } ++ ++ *(__le32 *)buf = cpu_to_le32(val); ++ ++ for (i = 0; i < 4; i++) { ++ sdio_writeb(rtwsdio->sdio_func, buf[i], addr + i, ret); ++ if (*ret) ++ return; ++ } ++} ++ ++static u32 rtw_sdio_readl(struct rtw_sdio *rtwsdio, u32 addr, int *ret) ++{ ++ u8 buf[4]; ++ int i; ++ ++ if (!(addr & 3) && rtwsdio->is_powered_on) ++ return sdio_readl(rtwsdio->sdio_func, addr, ret); ++ ++ for (i = 0; i < 4; i++) { ++ buf[i] = sdio_readb(rtwsdio->sdio_func, addr + i, ret); ++ if (*ret) ++ return 0; ++ } ++ ++ return le32_to_cpu(*(__le32 *)buf); ++} ++ ++static u8 rtw_sdio_read_indirect8(struct rtw_dev *rtwdev, u32 addr, int *ret) ++{ ++ struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv; ++ u32 reg_cfg, reg_data; ++ int retry; ++ u8 tmp; ++ ++ reg_cfg = rtw_sdio_to_bus_offset(rtwdev, REG_SDIO_INDIRECT_REG_CFG); ++ reg_data = rtw_sdio_to_bus_offset(rtwdev, REG_SDIO_INDIRECT_REG_DATA); ++ ++ rtw_sdio_writel(rtwsdio, BIT(19) | addr, reg_cfg, ret); ++ if (*ret) ++ return 0; ++ ++ for (retry = 0; retry < RTW_SDIO_INDIRECT_RW_RETRIES; retry++) { ++ tmp = sdio_readb(rtwsdio->sdio_func, reg_cfg + 2, ret); ++ if (!ret && tmp & BIT(4)) ++ break; ++ } ++ ++ if (*ret) ++ return 0; ++ ++ return sdio_readb(rtwsdio->sdio_func, reg_data, ret); ++} ++ ++static int rtw_sdio_read_indirect_bytes(struct rtw_dev *rtwdev, u32 addr, ++ u8 *buf, int count) ++{ ++ int i, ret; ++ ++ for (i = 0; i < count; i++) { ++ buf[0] = rtw_sdio_read_indirect8(rtwdev, addr + i, &ret); ++ if (ret) ++ break; ++ } ++ ++ return ret; ++} ++ ++static u32 rtw_sdio_read_indirect32(struct rtw_dev *rtwdev, u32 addr, int *ret) ++{ ++ struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv; ++ u32 reg_cfg, reg_data, val; ++ int retry; ++ ++ reg_cfg = rtw_sdio_to_bus_offset(rtwdev, REG_SDIO_INDIRECT_REG_CFG); ++ reg_data = rtw_sdio_to_bus_offset(rtwdev, REG_SDIO_INDIRECT_REG_DATA); ++ ++ rtw_sdio_writel(rtwsdio, BIT(19) | BIT(17) | addr, reg_cfg, ret); ++ if (*ret) ++ return 0; ++ ++ for (retry = 0; retry < RTW_SDIO_INDIRECT_RW_RETRIES; retry++) { ++ val = sdio_readb(rtwsdio->sdio_func, reg_cfg + 2, ret); ++ if (!ret && (val & BIT(4))) ++ break; ++ } ++ ++ if (!*ret) ++ val = rtw_sdio_readl(rtwsdio, reg_data, ret); ++ ++ return val; ++} ++ ++static u8 rtw_sdio_read8(struct rtw_dev *rtwdev, u32 addr) ++{ ++ struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv; ++ bool direct, bus_claim; ++ int ret; ++ u8 val; ++ ++ bus_claim = rtw_sdio_bus_claim_needed(rtwsdio); ++ direct = rtw_sdio_is_bus_addr(addr); ++ ++ if (bus_claim) ++ sdio_claim_host(rtwsdio->sdio_func); ++ ++ if (direct) { ++ addr = rtw_sdio_to_bus_offset(rtwdev, addr); ++ val = sdio_readb(rtwsdio->sdio_func, addr, &ret); ++ } else { ++ val = rtw_sdio_read_indirect8(rtwdev, addr, &ret); ++ } ++ ++ if (bus_claim) ++ sdio_release_host(rtwsdio->sdio_func); ++ ++ if (ret) ++ rtw_warn(rtwdev, "sdio read8 failed (0x%x): %d", addr, ret); ++ ++ return val; ++} ++ ++static u16 rtw_sdio_read16(struct rtw_dev *rtwdev, u32 addr) ++{ ++ struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv; ++ bool direct, bus_claim; ++ u8 buf[2]; ++ int ret; ++ u16 val; ++ ++ bus_claim = rtw_sdio_bus_claim_needed(rtwsdio); ++ direct = rtw_sdio_is_bus_addr(addr); ++ ++ if (bus_claim) ++ sdio_claim_host(rtwsdio->sdio_func); ++ ++ if (direct) { ++ addr = rtw_sdio_to_bus_offset(rtwdev, addr); ++ buf[0] = sdio_readb(rtwsdio->sdio_func, addr, &ret); ++ if (!ret) ++ buf[1] = sdio_readb(rtwsdio->sdio_func, addr + 1, &ret); ++ val = le16_to_cpu(*(__le16 *)buf); ++ } else if (addr & 1) { ++ ret = rtw_sdio_read_indirect_bytes(rtwdev, addr, buf, 2); ++ val = le16_to_cpu(*(__le16 *)buf); ++ } else { ++ val = rtw_sdio_read_indirect32(rtwdev, addr, &ret); ++ } ++ ++ if (bus_claim) ++ sdio_release_host(rtwsdio->sdio_func); ++ ++ if (ret) ++ rtw_warn(rtwdev, "sdio read16 failed (0x%x): %d", addr, ret); ++ ++ return val; ++} ++ ++static u32 rtw_sdio_read32(struct rtw_dev *rtwdev, u32 addr) ++{ ++ struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv; ++ bool direct, bus_claim; ++ u8 buf[4]; ++ u32 val; ++ int ret; ++ ++ bus_claim = rtw_sdio_bus_claim_needed(rtwsdio); ++ direct = rtw_sdio_is_bus_addr(addr); ++ ++ if (bus_claim) ++ sdio_claim_host(rtwsdio->sdio_func); ++ ++ if (direct) { ++ addr = rtw_sdio_to_bus_offset(rtwdev, addr); ++ val = rtw_sdio_readl(rtwsdio, addr, &ret); ++ } else if (addr & 3) { ++ ret = rtw_sdio_read_indirect_bytes(rtwdev, addr, buf, 4); ++ val = le32_to_cpu(*(__le32 *)buf); ++ } else { ++ val = rtw_sdio_read_indirect32(rtwdev, addr, &ret); ++ } ++ ++ if (bus_claim) ++ sdio_release_host(rtwsdio->sdio_func); ++ ++ if (ret) ++ rtw_warn(rtwdev, "sdio read32 failed (0x%x): %d", addr, ret); ++ ++ return val; ++} ++ ++static u32 rtw_sdio_to_write_address(struct rtw_dev *rtwdev, u32 addr) ++{ ++ if (!rtw_sdio_is_bus_addr(addr)) ++ addr |= WLAN_IOREG_OFFSET; ++ ++ return rtw_sdio_to_bus_offset(rtwdev, addr); ++} ++ ++static void rtw_sdio_write8(struct rtw_dev *rtwdev, u32 addr, u8 val) ++{ ++ struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv; ++ bool bus_claim; ++ int ret; ++ ++ addr = rtw_sdio_to_write_address(rtwdev, addr); ++ bus_claim = rtw_sdio_bus_claim_needed(rtwsdio); ++ ++ if (bus_claim) ++ sdio_claim_host(rtwsdio->sdio_func); ++ ++ sdio_writeb(rtwsdio->sdio_func, val, addr, &ret); ++ ++ if (bus_claim) ++ sdio_release_host(rtwsdio->sdio_func); ++ ++ if (ret) ++ rtw_warn(rtwdev, "sdio write8 failed (0x%x): %d", addr, ret); ++} ++ ++static void rtw_sdio_write16(struct rtw_dev *rtwdev, u32 addr, u16 val) ++{ ++ struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv; ++ bool bus_claim; ++ int ret; ++ ++ addr = rtw_sdio_to_write_address(rtwdev, addr); ++ bus_claim = rtw_sdio_bus_claim_needed(rtwsdio); ++ ++ if (bus_claim) ++ sdio_claim_host(rtwsdio->sdio_func); ++ ++ sdio_writeb(rtwsdio->sdio_func, val, addr, &ret); ++ if (!ret) ++ sdio_writeb(rtwsdio->sdio_func, val >> 8, addr + 1, &ret); ++ ++ if (bus_claim) ++ sdio_release_host(rtwsdio->sdio_func); ++ ++ if (ret) ++ rtw_warn(rtwdev, "sdio write16 failed (0x%x): %d", addr, ret); ++} ++ ++static void rtw_sdio_write32(struct rtw_dev *rtwdev, u32 addr, u32 val) ++{ ++ struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv; ++ bool bus_claim; ++ int ret; ++ ++ addr = rtw_sdio_to_write_address(rtwdev, addr); ++ bus_claim = rtw_sdio_bus_claim_needed(rtwsdio); ++ ++ if (bus_claim) ++ sdio_claim_host(rtwsdio->sdio_func); ++ ++ rtw_sdio_writel(rtwsdio, val, addr, &ret); ++ ++ if (bus_claim) ++ sdio_release_host(rtwsdio->sdio_func); ++ ++ if (ret) ++ rtw_warn(rtwdev, "sdio write32 failed (0x%x): %d", addr, ret); ++} ++ ++static u32 rtw_sdio_get_tx_addr(struct rtw_dev *rtwdev, size_t size, ++ enum rtw_tx_queue_type queue) ++{ ++ u32 txaddr; ++ ++ switch (queue) { ++ case RTW_TX_QUEUE_BCN: ++ case RTW_TX_QUEUE_H2C: ++ case RTW_TX_QUEUE_HI0: ++ txaddr = FIELD_PREP(REG_SDIO_CMD_ADDR_MSK, ++ REG_SDIO_CMD_ADDR_TXFF_HIGH); ++ break; ++ case RTW_TX_QUEUE_VI: ++ case RTW_TX_QUEUE_VO: ++ txaddr = FIELD_PREP(REG_SDIO_CMD_ADDR_MSK, ++ REG_SDIO_CMD_ADDR_TXFF_NORMAL); ++ break; ++ case RTW_TX_QUEUE_BE: ++ case RTW_TX_QUEUE_BK: ++ txaddr = FIELD_PREP(REG_SDIO_CMD_ADDR_MSK, ++ REG_SDIO_CMD_ADDR_TXFF_LOW); ++ break; ++ case RTW_TX_QUEUE_MGMT: ++ txaddr = FIELD_PREP(REG_SDIO_CMD_ADDR_MSK, ++ REG_SDIO_CMD_ADDR_TXFF_EXTRA); ++ break; ++ default: ++ rtw_warn(rtwdev, "Unsupported queue for TX addr: 0x%02x\n", ++ queue); ++ return 0; ++ } ++ ++ txaddr += DIV_ROUND_UP(size, 4); ++ ++ return txaddr; ++}; ++ ++static int rtw_sdio_read_port(struct rtw_dev *rtwdev, u8 *buf, size_t count) ++{ ++ struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv; ++ u32 rxaddr = rtwsdio->rx_addr++; ++ int ret; ++ ++ ret = sdio_memcpy_fromio(rtwsdio->sdio_func, buf, ++ RTW_SDIO_ADDR_RX_RX0FF_GEN(rxaddr), count); ++ if (ret) ++ rtw_warn(rtwdev, ++ "Failed to read %lu byte(s) from SDIO port 0x%08x", ++ count, rxaddr); ++ ++ return ret; ++} ++ ++static int rtw_sdio_check_free_txpg(struct rtw_dev *rtwdev, u8 queue, ++ size_t count) ++{ ++ unsigned int pages_free, pages_needed; ++ ++ if (rtw_chip_wcpu_11n(rtwdev)) { ++ u32 free_txpg; ++ ++ free_txpg = rtw_sdio_read32(rtwdev, REG_SDIO_FREE_TXPG); ++ ++ switch (queue) { ++ case RTW_TX_QUEUE_BCN: ++ case RTW_TX_QUEUE_H2C: ++ case RTW_TX_QUEUE_HI0: ++ case RTW_TX_QUEUE_MGMT: ++ /* high */ ++ pages_free = free_txpg & 0xff; ++ break; ++ case RTW_TX_QUEUE_VI: ++ case RTW_TX_QUEUE_VO: ++ /* normal */ ++ pages_free = (free_txpg >> 8) & 0xff; ++ break; ++ case RTW_TX_QUEUE_BE: ++ case RTW_TX_QUEUE_BK: ++ /* low */ ++ pages_free = (free_txpg >> 16) & 0xff; ++ break; ++ default: ++ rtw_warn(rtwdev, "Unknown mapping for queue %u\n", queue); ++ break; ++ } ++ ++ /* add the pages from the public queue */ ++ pages_free += (free_txpg >> 24) & 0xff; ++ } else { ++ u32 free_txpg[3]; ++ ++ free_txpg[0] = rtw_sdio_read32(rtwdev, REG_SDIO_FREE_TXPG); ++ free_txpg[1] = rtw_sdio_read32(rtwdev, REG_SDIO_FREE_TXPG + 4); ++ free_txpg[2] = rtw_sdio_read32(rtwdev, REG_SDIO_FREE_TXPG + 8); ++ ++ switch (queue) { ++ case RTW_TX_QUEUE_BCN: ++ case RTW_TX_QUEUE_H2C: ++ case RTW_TX_QUEUE_HI0: ++ /* high */ ++ pages_free = free_txpg[0] & 0xfff; ++ break; ++ case RTW_TX_QUEUE_VI: ++ case RTW_TX_QUEUE_VO: ++ /* normal */ ++ pages_free = (free_txpg[0] >> 16) & 0xfff; ++ break; ++ case RTW_TX_QUEUE_BE: ++ case RTW_TX_QUEUE_BK: ++ /* low */ ++ pages_free = free_txpg[1] & 0xfff; ++ break; ++ case RTW_TX_QUEUE_MGMT: ++ /* extra */ ++ pages_free = free_txpg[2] & 0xfff; ++ break; ++ default: ++ rtw_warn(rtwdev, "Unknown mapping for queue %u\n", queue); ++ return -EINVAL; ++ } ++ ++ /* add the pages from the public queue */ ++ pages_free += (free_txpg[1] >> 16) & 0xfff; ++ } ++ ++ pages_needed = DIV_ROUND_UP(count, rtwdev->chip->page_size); ++ ++ if (pages_needed > pages_free) { ++ rtw_dbg(rtwdev, RTW_DBG_SDIO, ++ "Not enough free pages (%u needed, %u free) in queue %u for %zu bytes\n", ++ pages_needed, pages_free, queue, count); ++ return -EBUSY; ++ } ++ ++ return 0; ++} ++ ++static int rtw_sdio_write_port(struct rtw_dev *rtwdev, struct sk_buff *skb, ++ enum rtw_tx_queue_type queue) ++{ ++ struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv; ++ bool bus_claim; ++ size_t txsize; ++ u32 txaddr; ++ int ret; ++ ++ txaddr = rtw_sdio_get_tx_addr(rtwdev, skb->len, queue); ++ if (!txaddr) ++ return -EINVAL; ++ ++ txsize = sdio_align_size(rtwsdio->sdio_func, skb->len); ++ ++ ret = rtw_sdio_check_free_txpg(rtwdev, queue, txsize); ++ if (ret) ++ return ret; ++ ++ if (!IS_ALIGNED((unsigned long)skb->data, RTW_SDIO_DATA_PTR_ALIGN)) ++ rtw_warn(rtwdev, "Got unaligned SKB in %s() for queue %u\n", ++ __func__, queue); ++ ++ bus_claim = rtw_sdio_bus_claim_needed(rtwsdio); ++ ++ if (bus_claim) ++ sdio_claim_host(rtwsdio->sdio_func); ++ ++ ret = sdio_memcpy_toio(rtwsdio->sdio_func, txaddr, skb->data, txsize); ++ ++ if (bus_claim) ++ sdio_release_host(rtwsdio->sdio_func); ++ ++ if (ret) ++ rtw_warn(rtwdev, ++ "Failed to write %lu byte(s) to SDIO port 0x%08x", ++ txsize, txaddr); ++ ++ return ret; ++} ++ ++static void rtw_sdio_init(struct rtw_dev *rtwdev) ++{ ++ struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv; ++ ++ rtwsdio->irq_mask = REG_SDIO_HIMR_RX_REQUEST | REG_SDIO_HIMR_CPWM1; ++} ++ ++static void rtw_sdio_rx_aggregation(struct rtw_dev *rtwdev, bool enable) ++{ ++ u8 size, timeout; ++ ++ if (enable) { ++ if (rtwdev->chip->id == RTW_CHIP_TYPE_8822C) { ++ size = 0xff; ++ timeout = 0x20; ++ } else { ++ size = 0x6; ++ timeout = 0x6; ++ } ++ ++ /* Make the firmware honor the size limit configured below */ ++ rtw_write32_set(rtwdev, REG_RXDMA_AGG_PG_TH, BIT_EN_PRE_CALC); ++ ++ rtw_write8_set(rtwdev, REG_TXDMA_PQ_MAP, BIT_RXDMA_AGG_EN); ++ ++ rtw_write16(rtwdev, REG_RXDMA_AGG_PG_TH, size | ++ (timeout << BIT_SHIFT_DMA_AGG_TO_V1)); ++ ++ rtw_write8_set(rtwdev, REG_RXDMA_MODE, BIT_DMA_MODE); ++ } else { ++ rtw_write32_clr(rtwdev, REG_RXDMA_AGG_PG_TH, BIT_EN_PRE_CALC); ++ rtw_write8_clr(rtwdev, REG_TXDMA_PQ_MAP, BIT_RXDMA_AGG_EN); ++ rtw_write8_clr(rtwdev, REG_RXDMA_MODE, BIT_DMA_MODE); ++ } ++} ++ ++static void rtw_sdio_enable_interrupt(struct rtw_dev *rtwdev) ++{ ++ struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv; ++ ++ rtw_write32(rtwdev, REG_SDIO_HIMR, rtwsdio->irq_mask); ++} ++ ++static void rtw_sdio_disable_interrupt(struct rtw_dev *rtwdev) ++{ ++ rtw_write32(rtwdev, REG_SDIO_HIMR, 0x0); ++} ++ ++static u8 rtw_sdio_get_tx_qsel(struct rtw_dev *rtwdev, struct sk_buff *skb, ++ u8 queue) ++{ ++ switch (queue) { ++ case RTW_TX_QUEUE_BCN: ++ return TX_DESC_QSEL_BEACON; ++ case RTW_TX_QUEUE_H2C: ++ return TX_DESC_QSEL_H2C; ++ case RTW_TX_QUEUE_MGMT: ++ if (rtw_chip_wcpu_11n(rtwdev)) ++ return TX_DESC_QSEL_HIGH; ++ else ++ return TX_DESC_QSEL_MGMT; ++ case RTW_TX_QUEUE_HI0: ++ return TX_DESC_QSEL_HIGH; ++ default: ++ return skb->priority; ++ } ++}; ++ ++static int rtw_sdio_setup(struct rtw_dev *rtwdev) ++{ ++ /* nothing to do */ ++ return 0; ++} ++ ++static int rtw_sdio_start(struct rtw_dev *rtwdev) ++{ ++ rtw_sdio_rx_aggregation(rtwdev, false); ++ rtw_sdio_enable_interrupt(rtwdev); ++ ++ return 0; ++} ++ ++static void rtw_sdio_stop(struct rtw_dev *rtwdev) ++{ ++ rtw_sdio_disable_interrupt(rtwdev); ++} ++ ++static void rtw_sdio_deep_ps_enter(struct rtw_dev *rtwdev) ++{ ++ struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv; ++ bool tx_empty = true; ++ u8 queue; ++ ++ if (!rtw_fw_feature_check(&rtwdev->fw, FW_FEATURE_TX_WAKE)) { ++ /* Deep PS state is not allowed to TX-DMA */ ++ for (queue = 0; queue < RTK_MAX_TX_QUEUE_NUM; queue++) { ++ /* BCN queue is rsvd page, does not have DMA interrupt ++ * H2C queue is managed by firmware ++ */ ++ if (queue == RTW_TX_QUEUE_BCN || ++ queue == RTW_TX_QUEUE_H2C) ++ continue; ++ ++ /* check if there is any skb DMAing */ ++ if (skb_queue_len(&rtwsdio->tx_queue[queue])) { ++ tx_empty = false; ++ break; ++ } ++ } ++ } ++ ++ if (!tx_empty) { ++ rtw_dbg(rtwdev, RTW_DBG_PS, ++ "TX path not empty, cannot enter deep power save state\n"); ++ return; ++ } ++ ++ set_bit(RTW_FLAG_LEISURE_PS_DEEP, rtwdev->flags); ++ rtw_power_mode_change(rtwdev, true); ++} ++ ++static void rtw_sdio_deep_ps_leave(struct rtw_dev *rtwdev) ++{ ++ if (test_and_clear_bit(RTW_FLAG_LEISURE_PS_DEEP, rtwdev->flags)) ++ rtw_power_mode_change(rtwdev, false); ++} ++ ++static void rtw_sdio_deep_ps(struct rtw_dev *rtwdev, bool enter) ++{ ++ if (enter && !test_bit(RTW_FLAG_LEISURE_PS_DEEP, rtwdev->flags)) ++ rtw_sdio_deep_ps_enter(rtwdev); ++ ++ if (!enter && test_bit(RTW_FLAG_LEISURE_PS_DEEP, rtwdev->flags)) ++ rtw_sdio_deep_ps_leave(rtwdev); ++} ++ ++static void rtw_sdio_tx_kick_off(struct rtw_dev *rtwdev) ++{ ++ struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv; ++ ++ queue_work(rtwsdio->txwq, &rtwsdio->tx_handler_data->work); ++} ++ ++static void rtw_sdio_link_ps(struct rtw_dev *rtwdev, bool enter) ++{ ++ /* nothing to do */ ++} ++ ++static void rtw_sdio_interface_cfg(struct rtw_dev *rtwdev) ++{ ++ u32 val; ++ ++ rtw_read32(rtwdev, REG_SDIO_FREE_TXPG); ++ ++ val = rtw_read32(rtwdev, REG_SDIO_TX_CTRL); ++ val &= 0xfff8; ++ rtw_write32(rtwdev, REG_SDIO_TX_CTRL, val); ++} ++ ++static void rtw_sdio_power_switch(struct rtw_dev *rtwdev, bool on) ++{ ++ struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv; ++ ++ rtwsdio->is_powered_on = on; ++} ++ ++static struct rtw_sdio_tx_data *rtw_sdio_get_tx_data(struct sk_buff *skb) ++{ ++ struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); ++ ++ BUILD_BUG_ON(sizeof(struct rtw_sdio_tx_data) > ++ sizeof(info->status.status_driver_data)); ++ ++ return (struct rtw_sdio_tx_data *)info->status.status_driver_data; ++} ++ ++static void rtw_sdio_tx_skb_prepare(struct rtw_dev *rtwdev, ++ struct rtw_tx_pkt_info *pkt_info, ++ struct sk_buff *skb, ++ enum rtw_tx_queue_type queue) ++{ ++ const struct rtw_chip_info *chip = rtwdev->chip; ++ unsigned long data_addr, aligned_addr; ++ size_t offset; ++ u8 *pkt_desc; ++ ++ pkt_desc = skb_push(skb, chip->tx_pkt_desc_sz); ++ ++ data_addr = (unsigned long)pkt_desc; ++ aligned_addr = ALIGN(data_addr, RTW_SDIO_DATA_PTR_ALIGN); ++ ++ if (data_addr != aligned_addr) { ++ /* Ensure that the start of the pkt_desc is always aligned at ++ * RTW_SDIO_DATA_PTR_ALIGN. ++ */ ++ offset = RTW_SDIO_DATA_PTR_ALIGN - (aligned_addr - data_addr); ++ ++ pkt_desc = skb_push(skb, offset); ++ ++ /* By inserting padding to align the start of the pkt_desc we ++ * need to inform the firmware that the actual data starts at ++ * a different offset than normal. ++ */ ++ pkt_info->offset += offset; ++ } ++ ++ memset(pkt_desc, 0, chip->tx_pkt_desc_sz); ++ ++ pkt_info->qsel = rtw_sdio_get_tx_qsel(rtwdev, skb, queue); ++ ++ rtw_tx_fill_tx_desc(pkt_info, skb); ++ chip->ops->fill_txdesc_checksum(rtwdev, pkt_info, pkt_desc); ++} ++ ++static int rtw_sdio_write_data(struct rtw_dev *rtwdev, ++ struct rtw_tx_pkt_info *pkt_info, ++ struct sk_buff *skb, ++ enum rtw_tx_queue_type queue) ++{ ++ int ret; ++ ++ rtw_sdio_tx_skb_prepare(rtwdev, pkt_info, skb, queue); ++ ++ ret = rtw_sdio_write_port(rtwdev, skb, queue); ++ dev_kfree_skb_any(skb); ++ ++ return ret; ++} ++ ++static int rtw_sdio_write_data_rsvd_page(struct rtw_dev *rtwdev, u8 *buf, ++ u32 size) ++{ ++ struct rtw_tx_pkt_info pkt_info = {}; ++ struct sk_buff *skb; ++ ++ skb = rtw_tx_write_data_rsvd_page_get(rtwdev, &pkt_info, buf, size); ++ if (!skb) ++ return -ENOMEM; ++ ++ return rtw_sdio_write_data(rtwdev, &pkt_info, skb, RTW_TX_QUEUE_BCN); ++} ++ ++static int rtw_sdio_write_data_h2c(struct rtw_dev *rtwdev, u8 *buf, u32 size) ++{ ++ struct rtw_tx_pkt_info pkt_info = {}; ++ struct sk_buff *skb; ++ ++ skb = rtw_tx_write_data_h2c_get(rtwdev, &pkt_info, buf, size); ++ if (!skb) ++ return -ENOMEM; ++ ++ return rtw_sdio_write_data(rtwdev, &pkt_info, skb, RTW_TX_QUEUE_H2C); ++} ++ ++static int rtw_sdio_tx_write(struct rtw_dev *rtwdev, ++ struct rtw_tx_pkt_info *pkt_info, ++ struct sk_buff *skb) ++{ ++ struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv; ++ enum rtw_tx_queue_type queue = rtw_tx_queue_mapping(skb); ++ struct rtw_sdio_tx_data *tx_data; ++ ++ rtw_sdio_tx_skb_prepare(rtwdev, pkt_info, skb, queue); ++ ++ tx_data = rtw_sdio_get_tx_data(skb); ++ tx_data->sn = pkt_info->sn; ++ ++ skb_queue_tail(&rtwsdio->tx_queue[queue], skb); ++ ++ return 0; ++} ++ ++static void rtw_sdio_tx_err_isr(struct rtw_dev *rtwdev) ++{ ++ u32 val = rtw_read32(rtwdev, REG_TXDMA_STATUS); ++ ++ rtw_write32(rtwdev, REG_TXDMA_STATUS, val); ++} ++ ++static void rtw_sdio_rx_skb(struct rtw_dev *rtwdev, struct sk_buff *skb, ++ u32 pkt_offset, struct rtw_rx_pkt_stat *pkt_stat, ++ struct ieee80211_rx_status *rx_status) ++{ ++ memcpy(IEEE80211_SKB_RXCB(skb), rx_status, sizeof(*rx_status)); ++ ++ if (pkt_stat->is_c2h) { ++ skb_put(skb, pkt_stat->pkt_len + pkt_offset); ++ rtw_fw_c2h_cmd_rx_irqsafe(rtwdev, pkt_offset, skb); ++ return; ++ } ++ ++ skb_put(skb, pkt_stat->pkt_len); ++ skb_reserve(skb, pkt_offset); ++ ++ rtw_rx_stats(rtwdev, pkt_stat->vif, skb); ++ ++ ieee80211_rx_irqsafe(rtwdev->hw, skb); ++} ++ ++static void rtw_sdio_rxfifo_recv(struct rtw_dev *rtwdev, u32 rx_len) ++{ ++ struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv; ++ const struct rtw_chip_info *chip = rtwdev->chip; ++ u32 pkt_desc_sz = chip->rx_pkt_desc_sz; ++ struct ieee80211_rx_status rx_status; ++ struct rtw_rx_pkt_stat pkt_stat; ++ struct sk_buff *skb, *split_skb; ++ u32 pkt_offset, curr_pkt_len; ++ size_t bufsz; ++ u8 *rx_desc; ++ int ret; ++ ++ bufsz = sdio_align_size(rtwsdio->sdio_func, rx_len); ++ ++ skb = dev_alloc_skb(bufsz); ++ if (!skb) ++ return; ++ ++ ret = rtw_sdio_read_port(rtwdev, skb->data, bufsz); ++ if (ret) { ++ dev_kfree_skb_any(skb); ++ return; ++ } ++ ++ while (true) { ++ rx_desc = skb->data; ++ chip->ops->query_rx_desc(rtwdev, rx_desc, &pkt_stat, ++ &rx_status); ++ pkt_offset = pkt_desc_sz + pkt_stat.drv_info_sz + ++ pkt_stat.shift; ++ ++ curr_pkt_len = ALIGN(pkt_offset + pkt_stat.pkt_len, ++ RTW_SDIO_DATA_PTR_ALIGN); ++ ++ if ((curr_pkt_len + pkt_desc_sz) >= rx_len) { ++ /* Use the original skb (with it's adjusted offset) ++ * when processing the last (or even the only) entry to ++ * have it's memory freed automatically. ++ */ ++ rtw_sdio_rx_skb(rtwdev, skb, pkt_offset, &pkt_stat, ++ &rx_status); ++ break; ++ } ++ ++ split_skb = dev_alloc_skb(curr_pkt_len); ++ if (!split_skb) { ++ rtw_sdio_rx_skb(rtwdev, skb, pkt_offset, &pkt_stat, ++ &rx_status); ++ break; ++ } ++ ++ skb_copy_header(split_skb, skb); ++ memcpy(split_skb->data, skb->data, curr_pkt_len); ++ ++ rtw_sdio_rx_skb(rtwdev, split_skb, pkt_offset, &pkt_stat, ++ &rx_status); ++ ++ /* Move to the start of the next RX descriptor */ ++ skb_reserve(skb, curr_pkt_len); ++ rx_len -= curr_pkt_len; ++ } ++} ++ ++static void rtw_sdio_rx_isr(struct rtw_dev *rtwdev) ++{ ++ u32 rx_len; ++ ++ while (true) { ++ if (rtw_chip_wcpu_11n(rtwdev)) ++ rx_len = rtw_read16(rtwdev, REG_SDIO_RX0_REQ_LEN); ++ else ++ rx_len = rtw_read32(rtwdev, REG_SDIO_RX0_REQ_LEN); ++ ++ if (!rx_len) ++ break; ++ ++ rtw_sdio_rxfifo_recv(rtwdev, rx_len); ++ } ++} ++ ++static void rtw_sdio_handle_interrupt(struct sdio_func *sdio_func) ++{ ++ struct ieee80211_hw *hw = sdio_get_drvdata(sdio_func); ++ struct rtw_dev *rtwdev = hw->priv; ++ struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv; ++ u32 hisr; ++ ++ rtwsdio->irq_thread = current; ++ ++ hisr = rtw_read32(rtwdev, REG_SDIO_HISR); ++ ++ if (hisr & REG_SDIO_HISR_TXERR) ++ rtw_sdio_tx_err_isr(rtwdev); ++ if (hisr & REG_SDIO_HISR_RX_REQUEST) { ++ hisr &= ~REG_SDIO_HISR_RX_REQUEST; ++ rtw_sdio_rx_isr(rtwdev); ++ } ++ ++ rtw_write32(rtwdev, REG_SDIO_HISR, hisr); ++ ++ rtwsdio->irq_thread = NULL; ++} ++ ++static int __maybe_unused rtw_sdio_suspend(struct device *dev) ++{ ++ return 0; ++} ++ ++static int __maybe_unused rtw_sdio_resume(struct device *dev) ++{ ++ return 0; ++} ++ ++SIMPLE_DEV_PM_OPS(rtw_sdio_pm_ops, rtw_sdio_suspend, rtw_sdio_resume); ++EXPORT_SYMBOL(rtw_sdio_pm_ops); ++ ++static int rtw_sdio_claim(struct rtw_dev *rtwdev, struct sdio_func *sdio_func) ++{ ++ struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv; ++ int ret; ++ ++ sdio_claim_host(sdio_func); ++ ++ ret = sdio_enable_func(sdio_func); ++ if (ret) { ++ rtw_err(rtwdev, "Failed to enable SDIO func"); ++ goto err_release_host; ++ } ++ ++ ret = sdio_set_block_size(sdio_func, RTW_SDIO_BLOCK_SIZE); ++ if (ret) { ++ rtw_err(rtwdev, "Failed to set SDIO block size to 512"); ++ goto err_disable_func; ++ } ++ ++ rtwsdio->sdio_func = sdio_func; ++ ++ rtwsdio->sdio3_bus_mode = mmc_card_uhs(sdio_func->card); ++ ++ sdio_set_drvdata(sdio_func, rtwdev->hw); ++ SET_IEEE80211_DEV(rtwdev->hw, &sdio_func->dev); ++ ++ sdio_release_host(sdio_func); ++ ++ return 0; ++ ++err_disable_func: ++ sdio_disable_func(sdio_func); ++err_release_host: ++ sdio_release_host(sdio_func); ++ return ret; ++} ++ ++static void rtw_sdio_declaim(struct rtw_dev *rtwdev, ++ struct sdio_func *sdio_func) ++{ ++ sdio_claim_host(sdio_func); ++ sdio_disable_func(sdio_func); ++ sdio_release_host(sdio_func); ++} ++ ++static struct rtw_hci_ops rtw_sdio_ops = { ++ .tx_write = rtw_sdio_tx_write, ++ .tx_kick_off = rtw_sdio_tx_kick_off, ++ .setup = rtw_sdio_setup, ++ .start = rtw_sdio_start, ++ .stop = rtw_sdio_stop, ++ .deep_ps = rtw_sdio_deep_ps, ++ .link_ps = rtw_sdio_link_ps, ++ .interface_cfg = rtw_sdio_interface_cfg, ++ ++ .power_switch = rtw_sdio_power_switch, ++ ++ .read8 = rtw_sdio_read8, ++ .read16 = rtw_sdio_read16, ++ .read32 = rtw_sdio_read32, ++ .write8 = rtw_sdio_write8, ++ .write16 = rtw_sdio_write16, ++ .write32 = rtw_sdio_write32, ++ .write_data_rsvd_page = rtw_sdio_write_data_rsvd_page, ++ .write_data_h2c = rtw_sdio_write_data_h2c, ++}; ++ ++static int rtw_sdio_request_irq(struct rtw_dev *rtwdev, ++ struct sdio_func *sdio_func) ++{ ++ int ret; ++ ++ sdio_claim_host(sdio_func); ++ ret = sdio_claim_irq(sdio_func, &rtw_sdio_handle_interrupt); ++ sdio_release_host(sdio_func); ++ ++ if (ret) { ++ rtw_err(rtwdev, "failed to claim SDIO IRQ"); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static void rtw_sdio_indicate_tx_status(struct rtw_dev *rtwdev, ++ struct sk_buff *skb) ++{ ++ struct rtw_sdio_tx_data *tx_data = rtw_sdio_get_tx_data(skb); ++ struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); ++ struct ieee80211_hw *hw = rtwdev->hw; ++ ++ /* enqueue to wait for tx report */ ++ if (info->flags & IEEE80211_TX_CTL_REQ_TX_STATUS) { ++ rtw_tx_report_enqueue(rtwdev, skb, tx_data->sn); ++ return; ++ } ++ ++ /* always ACK for others, then they won't be marked as drop */ ++ ieee80211_tx_info_clear_status(info); ++ if (info->flags & IEEE80211_TX_CTL_NO_ACK) ++ info->flags |= IEEE80211_TX_STAT_NOACK_TRANSMITTED; ++ else ++ info->flags |= IEEE80211_TX_STAT_ACK; ++ ++ ieee80211_tx_status_irqsafe(hw, skb); ++} ++ ++static void rtw_sdio_process_tx_queue(struct rtw_dev *rtwdev, ++ enum rtw_tx_queue_type queue) ++{ ++ struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv; ++ struct sk_buff *skb; ++ int ret; ++ ++ while (true) { ++ skb = skb_dequeue(&rtwsdio->tx_queue[queue]); ++ if (!skb) ++ break; ++ ++ ret = rtw_sdio_write_port(rtwdev, skb, queue); ++ if (ret) { ++ skb_queue_head(&rtwsdio->tx_queue[queue], skb); ++ break; ++ } ++ ++ if (queue <= RTW_TX_QUEUE_VO) ++ rtw_sdio_indicate_tx_status(rtwdev, skb); ++ else ++ dev_kfree_skb_any(skb); ++ } ++} ++ ++static void rtw_sdio_tx_handler(struct work_struct *work) ++{ ++ struct rtw_sdio_work_data *work_data = ++ container_of(work, struct rtw_sdio_work_data, work); ++ struct rtw_dev *rtwdev = work_data->rtwdev; ++ struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv; ++ bool has_more_tx_data; ++ int queue; ++ ++ if (!rtw_fw_feature_check(&rtwdev->fw, FW_FEATURE_TX_WAKE)) ++ rtw_sdio_deep_ps_leave(rtwdev); ++ ++ do { ++ has_more_tx_data = false; ++ ++ for (queue = RTK_MAX_TX_QUEUE_NUM - 1; queue >= 0; queue--) { ++ rtw_sdio_process_tx_queue(rtwdev, queue); ++ ++ if (!skb_queue_empty(&rtwsdio->tx_queue[queue])) ++ has_more_tx_data = true; ++ } ++ } while (has_more_tx_data); ++} ++ ++static void rtw_sdio_free_irq(struct rtw_dev *rtwdev, ++ struct sdio_func *sdio_func) ++{ ++ sdio_release_irq(sdio_func); ++} ++ ++static int rtw_sdio_init_tx(struct rtw_dev *rtwdev) ++{ ++ struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv; ++ int i; ++ ++ rtwsdio->txwq = create_singlethread_workqueue("rtw88_sdio: tx wq"); ++ if (!rtwsdio->txwq) { ++ rtw_err(rtwdev, "failed to create TX work queue\n"); ++ return -ENOMEM; ++ } ++ ++ for (i = 0; i < RTK_MAX_TX_QUEUE_NUM; i++) ++ skb_queue_head_init(&rtwsdio->tx_queue[i]); ++ rtwsdio->tx_handler_data = kmalloc(sizeof(*rtwsdio->tx_handler_data), ++ GFP_KERNEL); ++ if (!rtwsdio->tx_handler_data) ++ goto err_destroy_wq; ++ ++ rtwsdio->tx_handler_data->rtwdev = rtwdev; ++ INIT_WORK(&rtwsdio->tx_handler_data->work, rtw_sdio_tx_handler); ++ ++ return 0; ++ ++err_destroy_wq: ++ destroy_workqueue(rtwsdio->txwq); ++ return -ENOMEM; ++} ++ ++static void rtw_sdio_deinit_tx(struct rtw_dev *rtwdev) ++{ ++ struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv; ++ int i; ++ ++ for (i = 0; i < RTK_MAX_TX_QUEUE_NUM; i++) ++ skb_queue_purge(&rtwsdio->tx_queue[i]); ++ ++ flush_workqueue(rtwsdio->txwq); ++ destroy_workqueue(rtwsdio->txwq); ++ kfree(rtwsdio->tx_handler_data); ++} ++ ++int rtw_sdio_probe(struct sdio_func *sdio_func, ++ const struct sdio_device_id *id) ++{ ++ struct ieee80211_hw *hw; ++ struct rtw_dev *rtwdev; ++ int drv_data_size; ++ int ret; ++ ++ drv_data_size = sizeof(struct rtw_dev) + sizeof(struct rtw_sdio); ++ hw = ieee80211_alloc_hw(drv_data_size, &rtw_ops); ++ if (!hw) { ++ dev_err(&sdio_func->dev, "failed to allocate hw"); ++ return -ENOMEM; ++ } ++ ++ rtwdev = hw->priv; ++ rtwdev->hw = hw; ++ rtwdev->dev = &sdio_func->dev; ++ rtwdev->chip = (struct rtw_chip_info *)id->driver_data; ++ rtwdev->hci.ops = &rtw_sdio_ops; ++ rtwdev->hci.type = RTW_HCI_TYPE_SDIO; ++ ++ ret = rtw_core_init(rtwdev); ++ if (ret) ++ goto err_release_hw; ++ ++ rtw_dbg(rtwdev, RTW_DBG_SDIO, ++ "rtw88 SDIO probe: vendor=0x%04x device=%04x class=%02x", ++ id->vendor, id->device, id->class); ++ ++ ret = rtw_sdio_claim(rtwdev, sdio_func); ++ if (ret) { ++ rtw_err(rtwdev, "failed to claim SDIO device"); ++ goto err_deinit_core; ++ } ++ ++ rtw_sdio_init(rtwdev); ++ ++ ret = rtw_sdio_init_tx(rtwdev); ++ if (ret) { ++ rtw_err(rtwdev, "failed to init SDIO TX queue\n"); ++ goto err_sdio_declaim; ++ } ++ ++ ret = rtw_chip_info_setup(rtwdev); ++ if (ret) { ++ rtw_err(rtwdev, "failed to setup chip information"); ++ goto err_destroy_txwq; ++ } ++ ++ ret = rtw_register_hw(rtwdev, hw); ++ if (ret) { ++ rtw_err(rtwdev, "failed to register hw"); ++ goto err_destroy_txwq; ++ } ++ ++ ret = rtw_sdio_request_irq(rtwdev, sdio_func); ++ if (ret) ++ goto err_unregister_hw; ++ ++ return 0; ++ ++err_unregister_hw: ++ rtw_unregister_hw(rtwdev, hw); ++err_destroy_txwq: ++ rtw_sdio_deinit_tx(rtwdev); ++err_sdio_declaim: ++ rtw_sdio_declaim(rtwdev, sdio_func); ++err_deinit_core: ++ rtw_core_deinit(rtwdev); ++err_release_hw: ++ ieee80211_free_hw(hw); ++ ++ return ret; ++} ++EXPORT_SYMBOL(rtw_sdio_probe); ++ ++void rtw_sdio_remove(struct sdio_func *sdio_func) ++{ ++ struct ieee80211_hw *hw = sdio_get_drvdata(sdio_func); ++ struct rtw_dev *rtwdev; ++ ++ if (!hw) ++ return; ++ ++ rtwdev = hw->priv; ++ ++ rtw_unregister_hw(rtwdev, hw); ++ rtw_sdio_disable_interrupt(rtwdev); ++ rtw_sdio_declaim(rtwdev, sdio_func); ++ rtw_sdio_free_irq(rtwdev, sdio_func); ++ rtw_sdio_deinit_tx(rtwdev); ++ rtw_core_deinit(rtwdev); ++ ieee80211_free_hw(hw); ++} ++EXPORT_SYMBOL(rtw_sdio_remove); ++ ++void rtw_sdio_shutdown(struct device *dev) ++{ ++ struct sdio_func *sdio_func = dev_to_sdio_func(dev); ++ struct ieee80211_hw *hw = sdio_get_drvdata(sdio_func); ++ const struct rtw_chip_info *chip; ++ struct rtw_dev *rtwdev; ++ ++ if (!hw) ++ return; ++ ++ rtwdev = hw->priv; ++ chip = rtwdev->chip; ++ ++ if (chip->ops->shutdown) ++ chip->ops->shutdown(rtwdev); ++} ++EXPORT_SYMBOL(rtw_sdio_shutdown); ++ ++MODULE_AUTHOR("Martin Blumenstingl"); ++MODULE_AUTHOR("Jernej Skrabec"); ++MODULE_DESCRIPTION("Realtek 802.11ac wireless SDIO driver"); ++MODULE_LICENSE("Dual BSD/GPL"); +diff --git a/drivers/net/wireless/realtek/rtw88/sdio.h b/drivers/net/wireless/realtek/rtw88/sdio.h +new file mode 100644 +index 000000000000..7339e35f808a +--- /dev/null ++++ b/drivers/net/wireless/realtek/rtw88/sdio.h +@@ -0,0 +1,175 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ ++/* Copyright (C) 2021 Martin Blumenstingl ++ * Copyright (C) 2021 Jernej Skrabec ++ */ ++ ++#ifndef __REG_SDIO_H_ ++#define __REG_SDIO_H_ ++ ++#include "main.h" ++ ++/* I/O bus domain address mapping */ ++#define SDIO_LOCAL_OFFSET 0x10250000 ++#define WLAN_IOREG_OFFSET 0x10260000 ++#define FIRMWARE_FIFO_OFFSET 0x10270000 ++#define TX_HIQ_OFFSET 0x10310000 ++#define TX_MIQ_OFFSET 0x10320000 ++#define TX_LOQ_OFFSET 0x10330000 ++#define TX_EPQ_OFFSET 0x10350000 ++#define RX_RX0FF_OFFSET 0x10340000 ++ ++#define RTW_SDIO_BUS_MSK 0xffff0000 ++#define SDIO_LOCAL_REG_MSK 0x00000fff ++#define WLAN_IOREG_REG_MSK 0x0000ffff ++ ++/* SDIO Tx Control */ ++#define REG_SDIO_TX_CTRL (SDIO_LOCAL_OFFSET + 0x0000) ++ ++/*SDIO status timeout*/ ++#define REG_SDIO_TIMEOUT (SDIO_LOCAL_OFFSET + 0x0002) ++ ++/* SDIO Host Interrupt Mask */ ++#define REG_SDIO_HIMR (SDIO_LOCAL_OFFSET + 0x0014) ++#define REG_SDIO_HIMR_RX_REQUEST BIT(0) ++#define REG_SDIO_HIMR_AVAL BIT(1) ++#define REG_SDIO_HIMR_TXERR BIT(2) ++#define REG_SDIO_HIMR_RXERR BIT(3) ++#define REG_SDIO_HIMR_TXFOVW BIT(4) ++#define REG_SDIO_HIMR_RXFOVW BIT(5) ++#define REG_SDIO_HIMR_TXBCNOK BIT(6) ++#define REG_SDIO_HIMR_TXBCNERR BIT(7) ++#define REG_SDIO_HIMR_BCNERLY_INT BIT(16) ++#define REG_SDIO_HIMR_C2HCMD BIT(17) ++#define REG_SDIO_HIMR_CPWM1 BIT(18) ++#define REG_SDIO_HIMR_CPWM2 BIT(19) ++#define REG_SDIO_HIMR_HSISR_IND BIT(20) ++#define REG_SDIO_HIMR_GTINT3_IND BIT(21) ++#define REG_SDIO_HIMR_GTINT4_IND BIT(22) ++#define REG_SDIO_HIMR_PSTIMEOUT BIT(23) ++#define REG_SDIO_HIMR_OCPINT BIT(24) ++#define REG_SDIO_HIMR_ATIMEND BIT(25) ++#define REG_SDIO_HIMR_ATIMEND_E BIT(26) ++#define REG_SDIO_HIMR_CTWEND BIT(27) ++/* the following two are RTL8188 SDIO Specific */ ++#define REG_SDIO_HIMR_MCU_ERR BIT(28) ++#define REG_SDIO_HIMR_TSF_BIT32_TOGGLE BIT(29) ++ ++/* SDIO Host Interrupt Service Routine */ ++#define REG_SDIO_HISR (SDIO_LOCAL_OFFSET + 0x0018) ++#define REG_SDIO_HISR_RX_REQUEST BIT(0) ++#define REG_SDIO_HISR_AVAL BIT(1) ++#define REG_SDIO_HISR_TXERR BIT(2) ++#define REG_SDIO_HISR_RXERR BIT(3) ++#define REG_SDIO_HISR_TXFOVW BIT(4) ++#define REG_SDIO_HISR_RXFOVW BIT(5) ++#define REG_SDIO_HISR_TXBCNOK BIT(6) ++#define REG_SDIO_HISR_TXBCNERR BIT(7) ++#define REG_SDIO_HISR_BCNERLY_INT BIT(16) ++#define REG_SDIO_HISR_C2HCMD BIT(17) ++#define REG_SDIO_HISR_CPWM1 BIT(18) ++#define REG_SDIO_HISR_CPWM2 BIT(19) ++#define REG_SDIO_HISR_HSISR_IND BIT(20) ++#define REG_SDIO_HISR_GTINT3_IND BIT(21) ++#define REG_SDIO_HISR_GTINT4_IND BIT(22) ++#define REG_SDIO_HISR_PSTIMEOUT BIT(23) ++#define REG_SDIO_HISR_OCPINT BIT(24) ++#define REG_SDIO_HISR_ATIMEND BIT(25) ++#define REG_SDIO_HISR_ATIMEND_E BIT(26) ++#define REG_SDIO_HISR_CTWEND BIT(27) ++/* the following two are RTL8188 SDIO Specific */ ++#define REG_SDIO_HISR_MCU_ERR BIT(28) ++#define REG_SDIO_HISR_TSF_BIT32_TOGGLE BIT(29) ++ ++/* HCI Current Power Mode */ ++#define REG_SDIO_HCPWM (SDIO_LOCAL_OFFSET + 0x0019) ++/* RXDMA Request Length */ ++#define REG_SDIO_RX0_REQ_LEN (SDIO_LOCAL_OFFSET + 0x001C) ++/* OQT Free Page */ ++#define REG_SDIO_OQT_FREE_PG (SDIO_LOCAL_OFFSET + 0x001E) ++/* Free Tx Buffer Page */ ++#define REG_SDIO_FREE_TXPG (SDIO_LOCAL_OFFSET + 0x0020) ++/* HCI Current Power Mode 1 */ ++#define REG_SDIO_HCPWM1 (SDIO_LOCAL_OFFSET + 0x0024) ++/* HCI Current Power Mode 2 */ ++#define REG_SDIO_HCPWM2 (SDIO_LOCAL_OFFSET + 0x0026) ++/* Free Tx Page Sequence */ ++#define REG_SDIO_FREE_TXPG_SEQ (SDIO_LOCAL_OFFSET + 0x0028) ++/* HTSF Informaion */ ++#define REG_SDIO_HTSFR_INFO (SDIO_LOCAL_OFFSET + 0x0030) ++#define REG_SDIO_HCPWM1_V2 (SDIO_LOCAL_OFFSET + 0x0038) ++/* H2C */ ++#define REG_SDIO_H2C (SDIO_LOCAL_OFFSET + 0x0060) ++/* HCI Request Power Mode 1 */ ++#define REG_SDIO_HRPWM1 (SDIO_LOCAL_OFFSET + 0x0080) ++/* HCI Request Power Mode 2 */ ++#define REG_SDIO_HRPWM2 (SDIO_LOCAL_OFFSET + 0x0082) ++/* HCI Power Save Clock */ ++#define REG_SDIO_HPS_CLKR (SDIO_LOCAL_OFFSET + 0x0084) ++/* SDIO HCI Suspend Control */ ++#define REG_SDIO_HSUS_CTRL (SDIO_LOCAL_OFFSET + 0x0086) ++/* SDIO Host Extension Interrupt Mask Always */ ++#define REG_SDIO_HIMR_ON (SDIO_LOCAL_OFFSET + 0x0090) ++/* SDIO Host Extension Interrupt Status Always */ ++#define REG_SDIO_HISR_ON (SDIO_LOCAL_OFFSET + 0x0091) ++ ++#define REG_SDIO_INDIRECT_REG_CFG (SDIO_LOCAL_OFFSET + 0x0040) ++#define REG_SDIO_INDIRECT_REG_DATA (SDIO_LOCAL_OFFSET + 0x0044) ++ ++/* Sdio Address for SDIO Local Reg, TRX FIFO, MAC Reg */ ++#define REG_SDIO_CMD_ADDR_MSK GENMASK(16, 13) ++#define REG_SDIO_CMD_ADDR_SDIO_REG 0 ++#define REG_SDIO_CMD_ADDR_MAC_REG 8 ++#define REG_SDIO_CMD_ADDR_TXFF_HIGH 4 ++#define REG_SDIO_CMD_ADDR_TXFF_LOW 6 ++#define REG_SDIO_CMD_ADDR_TXFF_NORMAL 5 ++#define REG_SDIO_CMD_ADDR_TXFF_EXTRA 7 ++#define REG_SDIO_CMD_ADDR_RXFF 7 ++ ++#define RTW_SDIO_BLOCK_SIZE 512 ++#define RTW_SDIO_ADDR_RX_RX0FF_GEN(_id) (0x0e000 | ((_id) & 0x3)) ++ ++#define RTW_SDIO_DATA_PTR_ALIGN 8 ++ ++struct sdio_func; ++struct sdio_device_id; ++ ++struct rtw_sdio_tx_data { ++ u8 sn; ++}; ++ ++struct rtw_sdio_work_data { ++ struct work_struct work; ++ struct rtw_dev *rtwdev; ++}; ++ ++struct rtw_sdio { ++ struct sdio_func *sdio_func; ++ ++ u32 irq_mask; ++ u8 rx_addr; ++ bool sdio3_bus_mode; ++ bool is_powered_on; ++ ++ void *irq_thread; ++ ++ struct workqueue_struct *txwq; ++ ++ struct sk_buff_head tx_queue[RTK_MAX_TX_QUEUE_NUM]; ++ struct rtw_sdio_work_data *tx_handler_data; ++}; ++ ++extern const struct dev_pm_ops rtw_sdio_pm_ops; ++ ++int rtw_sdio_probe(struct sdio_func *sdio_func, ++ const struct sdio_device_id *id); ++void rtw_sdio_remove(struct sdio_func *sdio_func); ++void rtw_sdio_shutdown(struct device *dev); ++ ++static inline bool rtw_sdio_is_sdio30_supported(struct rtw_dev *rtwdev) ++{ ++ struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv; ++ ++ return rtwsdio->sdio3_bus_mode; ++} ++ ++#endif +-- +2.39.0 + +Initialize the rpwm_addr and cpwm_addr for power-saving support on SDIO +based chipsets. + +Signed-off-by: Martin Blumenstingl +--- + drivers/net/wireless/realtek/rtw88/main.c | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/drivers/net/wireless/realtek/rtw88/main.c b/drivers/net/wireless/realtek/rtw88/main.c +index 888427cf3bdf..9435cb43d1dc 100644 +--- a/drivers/net/wireless/realtek/rtw88/main.c ++++ b/drivers/net/wireless/realtek/rtw88/main.c +@@ -18,6 +18,7 @@ + #include "debug.h" + #include "bf.h" + #include "sar.h" ++#include "sdio.h" + + bool rtw_disable_lps_deep_mode; + EXPORT_SYMBOL(rtw_disable_lps_deep_mode); +@@ -1783,6 +1784,10 @@ static int rtw_chip_parameter_setup(struct rtw_dev *rtwdev) + rtwdev->hci.rpwm_addr = 0x03d9; + rtwdev->hci.cpwm_addr = 0x03da; + break; ++ case RTW_HCI_TYPE_SDIO: ++ rtwdev->hci.rpwm_addr = REG_SDIO_HRPWM1; ++ rtwdev->hci.cpwm_addr = REG_SDIO_HCPWM1_V2; ++ break; + case RTW_HCI_TYPE_USB: + rtwdev->hci.rpwm_addr = 0xfe58; + rtwdev->hci.cpwm_addr = 0xfe57; +-- +2.39.0 + +For SDIO host controllers with DMA support the TX buffer physical memory +address need to be aligned at an 8-byte boundary. Reserve 8 bytes of +extra TX headroom so we can align the data without re-allocating the +transmit buffer. + +While here, also remove the TODO comment regarding extra headroom for +USB and SDIO. For SDIO the extra headroom is now handled and for USB it +was not needed so far. + +Signed-off-by: Martin Blumenstingl +--- + drivers/net/wireless/realtek/rtw88/main.c | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +diff --git a/drivers/net/wireless/realtek/rtw88/main.c b/drivers/net/wireless/realtek/rtw88/main.c +index 9435cb43d1dc..bcdf1f8c8450 100644 +--- a/drivers/net/wireless/realtek/rtw88/main.c ++++ b/drivers/net/wireless/realtek/rtw88/main.c +@@ -2161,9 +2161,11 @@ int rtw_register_hw(struct rtw_dev *rtwdev, struct ieee80211_hw *hw) + int max_tx_headroom = 0; + int ret; + +- /* TODO: USB & SDIO may need extra room? */ + max_tx_headroom = rtwdev->chip->tx_pkt_desc_sz; + ++ if (rtw_hci_type(rtwdev) == RTW_HCI_TYPE_SDIO) ++ max_tx_headroom += RTW_SDIO_DATA_PTR_ALIGN; ++ + hw->extra_tx_headroom = max_tx_headroom; + hw->queues = IEEE80211_NUM_ACS; + hw->txq_data_size = sizeof(struct rtw_txq); +-- +2.39.0 + +Increase LEAVE_LPS_TRY_CNT to give SDIO based chipsets more time to +enter or leave deep sleep mode. SDIO communication is often slower than +PCIe transfers so extra time is needed. + +Signed-off-by: Jernej Skrabec +Signed-off-by: Martin Blumenstingl +--- + drivers/net/wireless/realtek/rtw88/ps.h | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/drivers/net/wireless/realtek/rtw88/ps.h b/drivers/net/wireless/realtek/rtw88/ps.h +index c194386f6db5..b79bef32b169 100644 +--- a/drivers/net/wireless/realtek/rtw88/ps.h ++++ b/drivers/net/wireless/realtek/rtw88/ps.h +@@ -12,7 +12,7 @@ + #define POWER_TX_WAKE BIT(1) + #define POWER_MODE_LCLK BIT(0) + +-#define LEAVE_LPS_TRY_CNT 5 ++#define LEAVE_LPS_TRY_CNT 10 + #define LEAVE_LPS_TIMEOUT msecs_to_jiffies(100) + + int rtw_enter_ips(struct rtw_dev *rtwdev); +-- +2.39.0 + +Wire up RTL8822BS chipset support using the new rtw88 SDIO HCI code as +well as the existing RTL8822B chipset code. + +Signed-off-by: Jernej Skrabec +Signed-off-by: Martin Blumenstingl +--- + drivers/net/wireless/realtek/rtw88/Kconfig | 11 ++++++ + drivers/net/wireless/realtek/rtw88/Makefile | 3 ++ + .../net/wireless/realtek/rtw88/rtw8822bs.c | 34 +++++++++++++++++++ + 3 files changed, 48 insertions(+) + create mode 100644 drivers/net/wireless/realtek/rtw88/rtw8822bs.c + +diff --git a/drivers/net/wireless/realtek/rtw88/Kconfig b/drivers/net/wireless/realtek/rtw88/Kconfig +index cdf9cb478ee2..0cfc68dcc416 100644 +--- a/drivers/net/wireless/realtek/rtw88/Kconfig ++++ b/drivers/net/wireless/realtek/rtw88/Kconfig +@@ -45,6 +45,17 @@ config RTW88_8822BE + + 802.11ac PCIe wireless network adapter + ++config RTW88_8822BS ++ tristate "Realtek 8822BS SDIO wireless network adapter" ++ depends on MMC ++ select RTW88_CORE ++ select RTW88_SDIO ++ select RTW88_8822B ++ help ++ Select this option will enable support for 8822BS chipset ++ ++ 802.11ac SDIO wireless network adapter ++ + config RTW88_8822BU + tristate "Realtek 8822BU USB wireless network adapter" + depends on USB +diff --git a/drivers/net/wireless/realtek/rtw88/Makefile b/drivers/net/wireless/realtek/rtw88/Makefile +index 892cad60ba31..2b8f4dd9707f 100644 +--- a/drivers/net/wireless/realtek/rtw88/Makefile ++++ b/drivers/net/wireless/realtek/rtw88/Makefile +@@ -26,6 +26,9 @@ rtw88_8822b-objs := rtw8822b.o rtw8822b_table.o + obj-$(CONFIG_RTW88_8822BE) += rtw88_8822be.o + rtw88_8822be-objs := rtw8822be.o + ++obj-$(CONFIG_RTW88_8822BS) += rtw88_8822bs.o ++rtw88_8822bs-objs := rtw8822bs.o ++ + obj-$(CONFIG_RTW88_8822BU) += rtw88_8822bu.o + rtw88_8822bu-objs := rtw8822bu.o + +diff --git a/drivers/net/wireless/realtek/rtw88/rtw8822bs.c b/drivers/net/wireless/realtek/rtw88/rtw8822bs.c +new file mode 100644 +index 000000000000..4c74ad2d2e5e +--- /dev/null ++++ b/drivers/net/wireless/realtek/rtw88/rtw8822bs.c +@@ -0,0 +1,34 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause ++// Copyright(c) Jernej Skrabec ++ ++#include ++#include ++#include ++#include "sdio.h" ++#include "rtw8822b.h" ++ ++static const struct sdio_device_id rtw_8822bs_id_table[] = { ++ { ++ SDIO_DEVICE(SDIO_VENDOR_ID_REALTEK, ++ SDIO_DEVICE_ID_REALTEK_RTW8822BS), ++ .driver_data = (kernel_ulong_t)&rtw8822b_hw_spec, ++ }, ++ {} ++}; ++MODULE_DEVICE_TABLE(sdio, rtw_8822bs_id_table); ++ ++static struct sdio_driver rtw_8822bs_driver = { ++ .name = "rtw_8822bs", ++ .probe = rtw_sdio_probe, ++ .remove = rtw_sdio_remove, ++ .id_table = rtw_8822bs_id_table, ++ .drv = { ++ .pm = &rtw_sdio_pm_ops, ++ .shutdown = rtw_sdio_shutdown, ++ } ++}; ++module_sdio_driver(rtw_8822bs_driver); ++ ++MODULE_AUTHOR("Jernej Skrabec "); ++MODULE_DESCRIPTION("Realtek 802.11ac wireless 8822bs driver"); ++MODULE_LICENSE("Dual BSD/GPL"); +-- +2.39.0 + +Wire up RTL8822CS chipset support using the new rtw88 SDIO HCI code as +well as the existing RTL8822C chipset code. + +Signed-off-by: Martin Blumenstingl +--- + drivers/net/wireless/realtek/rtw88/Kconfig | 11 ++++++ + drivers/net/wireless/realtek/rtw88/Makefile | 3 ++ + .../net/wireless/realtek/rtw88/rtw8822cs.c | 34 +++++++++++++++++++ + 3 files changed, 48 insertions(+) + create mode 100644 drivers/net/wireless/realtek/rtw88/rtw8822cs.c + +diff --git a/drivers/net/wireless/realtek/rtw88/Kconfig b/drivers/net/wireless/realtek/rtw88/Kconfig +index 0cfc68dcc416..6b65da81127f 100644 +--- a/drivers/net/wireless/realtek/rtw88/Kconfig ++++ b/drivers/net/wireless/realtek/rtw88/Kconfig +@@ -78,6 +78,17 @@ config RTW88_8822CE + + 802.11ac PCIe wireless network adapter + ++config RTW88_8822CS ++ tristate "Realtek 8822CS SDIO wireless network adapter" ++ depends on MMC ++ select RTW88_CORE ++ select RTW88_SDIO ++ select RTW88_8822C ++ help ++ Select this option will enable support for 8822CS chipset ++ ++ 802.11ac SDIO wireless network adapter ++ + config RTW88_8822CU + tristate "Realtek 8822CU USB wireless network adapter" + depends on USB +diff --git a/drivers/net/wireless/realtek/rtw88/Makefile b/drivers/net/wireless/realtek/rtw88/Makefile +index 2b8f4dd9707f..6105c2745bda 100644 +--- a/drivers/net/wireless/realtek/rtw88/Makefile ++++ b/drivers/net/wireless/realtek/rtw88/Makefile +@@ -38,6 +38,9 @@ rtw88_8822c-objs := rtw8822c.o rtw8822c_table.o + obj-$(CONFIG_RTW88_8822CE) += rtw88_8822ce.o + rtw88_8822ce-objs := rtw8822ce.o + ++obj-$(CONFIG_RTW88_8822CS) += rtw88_8822cs.o ++rtw88_8822cs-objs := rtw8822cs.o ++ + obj-$(CONFIG_RTW88_8822CU) += rtw88_8822cu.o + rtw88_8822cu-objs := rtw8822cu.o + +diff --git a/drivers/net/wireless/realtek/rtw88/rtw8822cs.c b/drivers/net/wireless/realtek/rtw88/rtw8822cs.c +new file mode 100644 +index 000000000000..3d7279d70aa9 +--- /dev/null ++++ b/drivers/net/wireless/realtek/rtw88/rtw8822cs.c +@@ -0,0 +1,34 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause ++// Copyright(c) Martin Blumenstingl ++ ++#include ++#include ++#include ++#include "sdio.h" ++#include "rtw8822c.h" ++ ++static const struct sdio_device_id rtw_8822cs_id_table[] = { ++ { ++ SDIO_DEVICE(SDIO_VENDOR_ID_REALTEK, ++ SDIO_DEVICE_ID_REALTEK_RTW8822CS), ++ .driver_data = (kernel_ulong_t)&rtw8822c_hw_spec, ++ }, ++ {} ++}; ++MODULE_DEVICE_TABLE(sdio, rtw_8822cs_id_table); ++ ++static struct sdio_driver rtw_8822cs_driver = { ++ .name = "rtw_8822cs", ++ .probe = rtw_sdio_probe, ++ .remove = rtw_sdio_remove, ++ .id_table = rtw_8822cs_id_table, ++ .drv = { ++ .pm = &rtw_sdio_pm_ops, ++ .shutdown = rtw_sdio_shutdown, ++ } ++}; ++module_sdio_driver(rtw_8822cs_driver); ++ ++MODULE_AUTHOR("Martin Blumenstingl "); ++MODULE_DESCRIPTION("Realtek 802.11ac wireless 8822cs driver"); ++MODULE_LICENSE("Dual BSD/GPL"); +-- +2.39.0 + +Wire up RTL8821CS chipset support using the new rtw88 SDIO HCI code as +well as the existing RTL8821C chipset code. + +Signed-off-by: Martin Blumenstingl +--- + drivers/net/wireless/realtek/rtw88/Kconfig | 11 ++++++ + drivers/net/wireless/realtek/rtw88/Makefile | 3 ++ + .../net/wireless/realtek/rtw88/rtw8821cs.c | 34 +++++++++++++++++++ + 3 files changed, 48 insertions(+) + create mode 100644 drivers/net/wireless/realtek/rtw88/rtw8821cs.c + +diff --git a/drivers/net/wireless/realtek/rtw88/Kconfig b/drivers/net/wireless/realtek/rtw88/Kconfig +index 6b65da81127f..29eb2f8e0eb7 100644 +--- a/drivers/net/wireless/realtek/rtw88/Kconfig ++++ b/drivers/net/wireless/realtek/rtw88/Kconfig +@@ -133,6 +133,17 @@ config RTW88_8821CE + + 802.11ac PCIe wireless network adapter + ++config RTW88_8821CS ++ tristate "Realtek 8821CS SDIO wireless network adapter" ++ depends on MMC ++ select RTW88_CORE ++ select RTW88_SDIO ++ select RTW88_8821C ++ help ++ Select this option will enable support for 8821CS chipset ++ ++ 802.11ac SDIO wireless network adapter ++ + config RTW88_8821CU + tristate "Realtek 8821CU USB wireless network adapter" + depends on USB +diff --git a/drivers/net/wireless/realtek/rtw88/Makefile b/drivers/net/wireless/realtek/rtw88/Makefile +index 6105c2745bda..82979b30ae8d 100644 +--- a/drivers/net/wireless/realtek/rtw88/Makefile ++++ b/drivers/net/wireless/realtek/rtw88/Makefile +@@ -59,6 +59,9 @@ rtw88_8821c-objs := rtw8821c.o rtw8821c_table.o + obj-$(CONFIG_RTW88_8821CE) += rtw88_8821ce.o + rtw88_8821ce-objs := rtw8821ce.o + ++obj-$(CONFIG_RTW88_8821CS) += rtw88_8821cs.o ++rtw88_8821cs-objs := rtw8821cs.o ++ + obj-$(CONFIG_RTW88_8821CU) += rtw88_8821cu.o + rtw88_8821cu-objs := rtw8821cu.o + +diff --git a/drivers/net/wireless/realtek/rtw88/rtw8821cs.c b/drivers/net/wireless/realtek/rtw88/rtw8821cs.c +new file mode 100644 +index 000000000000..61f82b38cda4 +--- /dev/null ++++ b/drivers/net/wireless/realtek/rtw88/rtw8821cs.c +@@ -0,0 +1,34 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause ++// Copyright(c) Martin Blumenstingl ++ ++#include ++#include ++#include ++#include "sdio.h" ++#include "rtw8821c.h" ++ ++static const struct sdio_device_id rtw_8821cs_id_table[] = { ++ { ++ SDIO_DEVICE(SDIO_VENDOR_ID_REALTEK, ++ SDIO_DEVICE_ID_REALTEK_RTW8821CS), ++ .driver_data = (kernel_ulong_t)&rtw8821c_hw_spec, ++ }, ++ {} ++}; ++MODULE_DEVICE_TABLE(sdio, rtw_8821cs_id_table); ++ ++static struct sdio_driver rtw_8821cs_driver = { ++ .name = "rtw_8821cs", ++ .probe = rtw_sdio_probe, ++ .remove = rtw_sdio_remove, ++ .id_table = rtw_8821cs_id_table, ++ .drv = { ++ .pm = &rtw_sdio_pm_ops, ++ .shutdown = rtw_sdio_shutdown, ++ } ++}; ++module_sdio_driver(rtw_8821cs_driver); ++ ++MODULE_AUTHOR("Martin Blumenstingl "); ++MODULE_DESCRIPTION("Realtek 802.11ac wireless 8821cs driver"); ++MODULE_LICENSE("Dual BSD/GPL"); +-- +2.39.0 diff --git a/patch/misc/rtw88/6.1/004-rtw88-usb-fixes.patch b/patch/misc/rtw88/6.1/004-rtw88-usb-fixes.patch new file mode 100644 index 0000000000..561af0a97c --- /dev/null +++ b/patch/misc/rtw88/6.1/004-rtw88-usb-fixes.patch @@ -0,0 +1,86 @@ +We have to extract qsel from the skb before doing skb_push() on it, +otherwise qsel will always be 0. + +Fixes: a82dfd33d1237 ("wifi: rtw88: Add common USB chip support") +Signed-off-by: Sascha Hauer +--- + drivers/net/wireless/realtek/rtw88/usb.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/drivers/net/wireless/realtek/rtw88/usb.c b/drivers/net/wireless/realtek/rtw88/usb.c +index 4ef38279b64c9..d9e995544e405 100644 +--- a/drivers/net/wireless/realtek/rtw88/usb.c ++++ b/drivers/net/wireless/realtek/rtw88/usb.c +@@ -471,9 +471,9 @@ static int rtw_usb_tx_write(struct rtw_dev *rtwdev, + u8 *pkt_desc; + int ep; + ++ pkt_info->qsel = rtw_usb_tx_queue_mapping_to_qsel(skb); + pkt_desc = skb_push(skb, chip->tx_pkt_desc_sz); + memset(pkt_desc, 0, chip->tx_pkt_desc_sz); +- pkt_info->qsel = rtw_usb_tx_queue_mapping_to_qsel(skb); + ep = qsel_to_ep(rtwusb, pkt_info->qsel); + rtw_tx_fill_tx_desc(pkt_info, skb); + rtw_tx_fill_txdesc_checksum(rtwdev, pkt_info, skb->data); + +Zero length packets are necessary when sending URBs with size +multiple of bulkout_size, otherwise the hardware just stalls. + +Fixes: a82dfd33d1237 ("wifi: rtw88: Add common USB chip support") +Signed-off-by: Sascha Hauer +--- + drivers/net/wireless/realtek/rtw88/usb.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/drivers/net/wireless/realtek/rtw88/usb.c b/drivers/net/wireless/realtek/rtw88/usb.c +index d9e995544e405..1a09c9288198a 100644 +--- a/drivers/net/wireless/realtek/rtw88/usb.c ++++ b/drivers/net/wireless/realtek/rtw88/usb.c +@@ -271,6 +271,7 @@ static int rtw_usb_write_port(struct rtw_dev *rtwdev, u8 qsel, struct sk_buff *s + return -ENOMEM; + + usb_fill_bulk_urb(urb, usbd, pipe, skb->data, skb->len, cb, context); ++ urb->transfer_flags |= URB_ZERO_PACKET; + ret = usb_submit_urb(urb, GFP_ATOMIC); + + usb_free_urb(urb); + +Now that we send URBs with the URB_ZERO_PACKET flag set we no longer +need to make sure that the URB sizes are not multiple of the +bulkout_size. Drop the check. + +Signed-off-by: Sascha Hauer +--- + drivers/net/wireless/realtek/rtw88/usb.c | 15 +-------------- + 1 file changed, 1 insertion(+), 14 deletions(-) + +diff --git a/drivers/net/wireless/realtek/rtw88/usb.c b/drivers/net/wireless/realtek/rtw88/usb.c +index 1a09c9288198a..2a8336b1847a5 100644 +--- a/drivers/net/wireless/realtek/rtw88/usb.c ++++ b/drivers/net/wireless/realtek/rtw88/usb.c +@@ -414,24 +414,11 @@ static int rtw_usb_write_data_rsvd_page(struct rtw_dev *rtwdev, u8 *buf, + u32 size) + { + const struct rtw_chip_info *chip = rtwdev->chip; +- struct rtw_usb *rtwusb; + struct rtw_tx_pkt_info pkt_info = {0}; +- u32 len, desclen; +- +- rtwusb = rtw_get_usb_priv(rtwdev); + + pkt_info.tx_pkt_size = size; + pkt_info.qsel = TX_DESC_QSEL_BEACON; +- +- desclen = chip->tx_pkt_desc_sz; +- len = desclen + size; +- if (len % rtwusb->bulkout_size == 0) { +- len += RTW_USB_PACKET_OFFSET_SZ; +- pkt_info.offset = desclen + RTW_USB_PACKET_OFFSET_SZ; +- pkt_info.pkt_offset = 1; +- } else { +- pkt_info.offset = desclen; +- } ++ pkt_info.offset = chip->tx_pkt_desc_sz; + + return rtw_usb_write_data(rtwdev, &pkt_info, buf); + } diff --git a/patch/misc/rtw88/6.1/005-rtw88-mac-c-fix.patch b/patch/misc/rtw88/6.1/005-rtw88-mac-c-fix.patch new file mode 100644 index 0000000000..5b0e51a424 --- /dev/null +++ b/patch/misc/rtw88/6.1/005-rtw88-mac-c-fix.patch @@ -0,0 +1,146 @@ +diff -Naur a/drivers/net/wireless/realtek/rtw88/mac.c b/drivers/net/wireless/realtek/rtw88/mac.c +--- a/drivers/net/wireless/realtek/rtw88/mac.c 2023-05-17 05:54:00.000000000 -0400 ++++ b/drivers/net/wireless/realtek/rtw88/mac.c 2023-05-22 12:58:36.127309015 -0400 +@@ -7,6 +7,7 @@ + #include "reg.h" + #include "fw.h" + #include "debug.h" ++#include "sdio.h" + + void rtw_set_channel_mac(struct rtw_dev *rtwdev, u8 channel, u8 bw, + u8 primary_ch_idx) +@@ -60,6 +61,7 @@ + + static int rtw_mac_pre_system_cfg(struct rtw_dev *rtwdev) + { ++ unsigned int retry; + u32 value32; + u8 value8; + +@@ -77,6 +79,26 @@ + case RTW_HCI_TYPE_PCIE: + rtw_write32_set(rtwdev, REG_HCI_OPT_CTRL, BIT_USB_SUS_DIS); + break; ++ case RTW_HCI_TYPE_SDIO: ++ rtw_write8_clr(rtwdev, REG_SDIO_HSUS_CTRL, BIT(0)); ++ ++ for (retry = 0; retry < RTW_PWR_POLLING_CNT; retry++) { ++ if (rtw_read8(rtwdev, REG_SDIO_HSUS_CTRL) & BIT(1)) ++ break; ++ ++ usleep_range(10, 50); ++ } ++ ++ if (retry == RTW_PWR_POLLING_CNT) { ++ rtw_err(rtwdev, "failed to poll REG_SDIO_HSUS_CTRL[1]"); ++ return -ETIMEDOUT; ++ } ++ ++ if (rtw_sdio_is_sdio30_supported(rtwdev)) ++ rtw_write8_set(rtwdev, REG_HCI_OPT_CTRL + 2, BIT(2)); ++ else ++ rtw_write8_clr(rtwdev, REG_HCI_OPT_CTRL + 2, BIT(2)); ++ break; + case RTW_HCI_TYPE_USB: + break; + default: +@@ -217,10 +239,13 @@ + cut_mask = cut_version_to_mask(cut); + switch (rtw_hci_type(rtwdev)) { + case RTW_HCI_TYPE_PCIE: +- intf_mask = BIT(2); ++ intf_mask = RTW_PWR_INTF_PCI_MSK; + break; + case RTW_HCI_TYPE_USB: +- intf_mask = BIT(1); ++ intf_mask = RTW_PWR_INTF_USB_MSK; ++ break; ++ case RTW_HCI_TYPE_SDIO: ++ intf_mask = RTW_PWR_INTF_SDIO_MSK; + break; + default: + return -EINVAL; +@@ -245,6 +270,7 @@ + { + const struct rtw_chip_info *chip = rtwdev->chip; + const struct rtw_pwr_seq_cmd **pwr_seq; ++ u32 imr; + u8 rpwm; + bool cur_pwr; + int ret; +@@ -270,16 +296,28 @@ + if (pwr_on == cur_pwr) + return -EALREADY; + ++ /* Always signal power off before power sequence. This way ++ * read/write functions will take path which works in both ++ * states. State will change in the middle of the sequence. ++ */ ++ rtw_hci_power_switch(rtwdev, false); ++ ++ imr = rtw_read32(rtwdev, REG_SDIO_HIMR); ++ rtw_write32(rtwdev, REG_SDIO_HIMR, 0); ++ + pwr_seq = pwr_on ? chip->pwr_on_seq : chip->pwr_off_seq; +- ret = rtw_pwr_seq_parser(rtwdev, pwr_seq); +- if (ret) +- return ret; ++ if (rtw_pwr_seq_parser(rtwdev, pwr_seq)) { ++ rtw_write32(rtwdev, REG_SDIO_HIMR, imr); ++ return -EINVAL; ++ } + + if (pwr_on) + set_bit(RTW_FLAG_POWERON, rtwdev->flags); + else + clear_bit(RTW_FLAG_POWERON, rtwdev->flags); + ++ rtw_hci_power_switch(rtwdev, pwr_on); ++ + return 0; + } + +@@ -451,6 +489,9 @@ + rtw_write16(rtwdev, REG_FIFOPAGE_INFO_1, 0x200); + rtw_write32(rtwdev, REG_RQPN_CTRL_2, bckp[bckp_idx - 1].val); + ++ if (rtw_hci_type(rtwdev) == RTW_HCI_TYPE_SDIO) ++ rtw_read32(rtwdev, REG_SDIO_FREE_TXPG); ++ + /* Disable beacon related functions */ + tmp = rtw_read8(rtwdev, REG_BCN_CTRL); + bckp[bckp_idx].len = 1; +@@ -1026,6 +1067,9 @@ + else + return -EINVAL; + break; ++ case RTW_HCI_TYPE_SDIO: ++ rqpn = &chip->rqpn_table[0]; ++ break; + default: + return -EINVAL; + } +@@ -1044,6 +1088,13 @@ + if (rtw_chip_wcpu_11ac(rtwdev)) + rtw_write32(rtwdev, REG_H2CQ_CSR, BIT_H2CQ_FULL); + ++ if (rtw_hci_type(rtwdev) == RTW_HCI_TYPE_SDIO) { ++ rtw_read32(rtwdev, REG_SDIO_FREE_TXPG); ++ rtw_write32(rtwdev, REG_SDIO_TX_CTRL, 0); ++ } else if (rtw_hci_type(rtwdev) == RTW_HCI_TYPE_USB) { ++ rtw_write8_set(rtwdev, REG_TXDMA_PQ_MAP, BIT_RXDMA_ARBBW_EN); ++ } ++ + return 0; + } + +@@ -1185,6 +1236,9 @@ + else + return -EINVAL; + break; ++ case RTW_HCI_TYPE_SDIO: ++ pg_tbl = &chip->page_table[0]; ++ break; + default: + return -EINVAL; + } diff --git a/patch/misc/rtw88/6.3/001-rtw88-sdio-rfc.patch b/patch/misc/rtw88/6.3/001-rtw88-sdio-rfc.patch new file mode 100644 index 0000000000..4a56931c66 --- /dev/null +++ b/patch/misc/rtw88/6.3/001-rtw88-sdio-rfc.patch @@ -0,0 +1,2394 @@ +Add the SDIO vendor ID for Realtek and some device IDs extracted from +their GPL vendor driver. This will be useful in the future when the +rtw88 driver gains support for these chips. + +Signed-off-by: Martin Blumenstingl +--- + include/linux/mmc/sdio_ids.h | 9 +++++++++ + 1 file changed, 9 insertions(+) + +diff --git a/include/linux/mmc/sdio_ids.h b/include/linux/mmc/sdio_ids.h +index 74f9d9a6d330..bba39d4565da 100644 +--- a/include/linux/mmc/sdio_ids.h ++++ b/include/linux/mmc/sdio_ids.h +@@ -115,6 +115,15 @@ + #define SDIO_VENDOR_ID_MICROCHIP_WILC 0x0296 + #define SDIO_DEVICE_ID_MICROCHIP_WILC1000 0x5347 + ++#define SDIO_VENDOR_ID_REALTEK 0x024c ++#define SDIO_DEVICE_ID_REALTEK_RTW8723BS 0xb723 ++#define SDIO_DEVICE_ID_REALTEK_RTW8723DS 0xd723 ++#define SDIO_DEVICE_ID_REALTEK_RTW8821BS 0xb821 ++#define SDIO_DEVICE_ID_REALTEK_RTW8821CS 0xc821 ++#define SDIO_DEVICE_ID_REALTEK_RTW8821DS 0xd821 ++#define SDIO_DEVICE_ID_REALTEK_RTW8822BS 0xb822 ++#define SDIO_DEVICE_ID_REALTEK_RTW8822CS 0xc822 ++ + #define SDIO_VENDOR_ID_SIANO 0x039a + #define SDIO_DEVICE_ID_SIANO_NOVA_B0 0x0201 + #define SDIO_DEVICE_ID_SIANO_NICE 0x0202 +-- +2.39.0 + +The efuse of the SDIO RTL8821CS chip has only one known member: the mac +address is at offset 0x11a. Add a struct rtw8821cs_efuse describing this +and use it for copying the mac address when the SDIO bus is used. + +Signed-off-by: Martin Blumenstingl +--- + drivers/net/wireless/realtek/rtw88/rtw8821c.c | 9 +++++++++ + drivers/net/wireless/realtek/rtw88/rtw8821c.h | 6 ++++++ + 2 files changed, 15 insertions(+) + +diff --git a/drivers/net/wireless/realtek/rtw88/rtw8821c.c b/drivers/net/wireless/realtek/rtw88/rtw8821c.c +index 17f800f6efbd..dd01b22f9770 100644 +--- a/drivers/net/wireless/realtek/rtw88/rtw8821c.c ++++ b/drivers/net/wireless/realtek/rtw88/rtw8821c.c +@@ -26,6 +26,12 @@ static void rtw8821ce_efuse_parsing(struct rtw_efuse *efuse, + ether_addr_copy(efuse->addr, map->e.mac_addr); + } + ++static void rtw8821cs_efuse_parsing(struct rtw_efuse *efuse, ++ struct rtw8821c_efuse *map) ++{ ++ ether_addr_copy(efuse->addr, map->s.mac_addr); ++} ++ + static void rtw8821cu_efuse_parsing(struct rtw_efuse *efuse, + struct rtw8821c_efuse *map) + { +@@ -74,6 +80,9 @@ static int rtw8821c_read_efuse(struct rtw_dev *rtwdev, u8 *log_map) + case RTW_HCI_TYPE_PCIE: + rtw8821ce_efuse_parsing(efuse, map); + break; ++ case RTW_HCI_TYPE_SDIO: ++ rtw8821cs_efuse_parsing(efuse, map); ++ break; + case RTW_HCI_TYPE_USB: + rtw8821cu_efuse_parsing(efuse, map); + break; +diff --git a/drivers/net/wireless/realtek/rtw88/rtw8821c.h b/drivers/net/wireless/realtek/rtw88/rtw8821c.h +index 1c81260f3a54..1deea54575b5 100644 +--- a/drivers/net/wireless/realtek/rtw88/rtw8821c.h ++++ b/drivers/net/wireless/realtek/rtw88/rtw8821c.h +@@ -65,6 +65,11 @@ struct rtw8821ce_efuse { + u8 res7; + }; + ++struct rtw8821cs_efuse { ++ u8 res4[0x4a]; /* 0xd0 */ ++ u8 mac_addr[ETH_ALEN]; /* 0x11a */ ++}; ++ + struct rtw8821c_efuse { + __le16 rtl_id; + u8 res0[0x0e]; +@@ -93,6 +98,7 @@ struct rtw8821c_efuse { + u8 res[3]; + union { + struct rtw8821ce_efuse e; ++ struct rtw8821cs_efuse s; + struct rtw8821cu_efuse u; + }; + }; +-- +2.39.0 + +The efuse of the SDIO RTL8822BS chip has only one known member: the mac +address is at offset 0x11a. Add a struct rtw8822bs_efuse describing this +and use it for copying the mac address when the SDIO bus is used. + +Signed-off-by: Martin Blumenstingl +--- + drivers/net/wireless/realtek/rtw88/rtw8822b.c | 10 ++++++++++ + drivers/net/wireless/realtek/rtw88/rtw8822b.h | 6 ++++++ + 2 files changed, 16 insertions(+) + +diff --git a/drivers/net/wireless/realtek/rtw88/rtw8822b.c b/drivers/net/wireless/realtek/rtw88/rtw8822b.c +index 74dfb89b2c94..4ed5b98fab23 100644 +--- a/drivers/net/wireless/realtek/rtw88/rtw8822b.c ++++ b/drivers/net/wireless/realtek/rtw88/rtw8822b.c +@@ -26,10 +26,17 @@ static void rtw8822be_efuse_parsing(struct rtw_efuse *efuse, + ether_addr_copy(efuse->addr, map->e.mac_addr); + } + ++static void rtw8822bs_efuse_parsing(struct rtw_efuse *efuse, ++ struct rtw8822b_efuse *map) ++{ ++ ether_addr_copy(efuse->addr, map->s.mac_addr); ++} ++ + static void rtw8822bu_efuse_parsing(struct rtw_efuse *efuse, + struct rtw8822b_efuse *map) + { + ether_addr_copy(efuse->addr, map->u.mac_addr); ++ + } + + static int rtw8822b_read_efuse(struct rtw_dev *rtwdev, u8 *log_map) +@@ -62,6 +69,9 @@ static int rtw8822b_read_efuse(struct rtw_dev *rtwdev, u8 *log_map) + case RTW_HCI_TYPE_PCIE: + rtw8822be_efuse_parsing(efuse, map); + break; ++ case RTW_HCI_TYPE_SDIO: ++ rtw8822bs_efuse_parsing(efuse, map); ++ break; + case RTW_HCI_TYPE_USB: + rtw8822bu_efuse_parsing(efuse, map); + break; +diff --git a/drivers/net/wireless/realtek/rtw88/rtw8822b.h b/drivers/net/wireless/realtek/rtw88/rtw8822b.h +index 01d3644e0c94..f84bfb6b0df9 100644 +--- a/drivers/net/wireless/realtek/rtw88/rtw8822b.h ++++ b/drivers/net/wireless/realtek/rtw88/rtw8822b.h +@@ -65,6 +65,11 @@ struct rtw8822be_efuse { + u8 res7; + }; + ++struct rtw8822bs_efuse { ++ u8 res4[0x4a]; /* 0xd0 */ ++ u8 mac_addr[ETH_ALEN]; /* 0x11a */ ++}; ++ + struct rtw8822b_efuse { + __le16 rtl_id; + u8 res0[0x0e]; +@@ -94,6 +99,7 @@ struct rtw8822b_efuse { + union { + struct rtw8822bu_efuse u; + struct rtw8822be_efuse e; ++ struct rtw8822bs_efuse s; + }; + }; + +-- +2.39.0 + +rtw_pwr_seq_parser() needs to know about the HCI bus interface mask for +the SDIO bus so it can parse the chip state change sequences. + +Signed-off-by: Martin Blumenstingl +--- + drivers/net/wireless/realtek/rtw88/mac.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/drivers/net/wireless/realtek/rtw88/mac.c b/drivers/net/wireless/realtek/rtw88/mac.c +index bf1291902661..c04938691add 100644 +--- a/drivers/net/wireless/realtek/rtw88/mac.c ++++ b/drivers/net/wireless/realtek/rtw88/mac.c +@@ -222,6 +222,9 @@ static int rtw_pwr_seq_parser(struct rtw_dev *rtwdev, + case RTW_HCI_TYPE_USB: + intf_mask = RTW_PWR_INTF_USB_MSK; + break; ++ case RTW_HCI_TYPE_SDIO: ++ intf_mask = RTW_PWR_INTF_SDIO_MSK; ++ break; + default: + return -EINVAL; + } +-- +2.39.0 + +The efuse of the SDIO RTL8822CS chip has only one known member: the mac +address is at offset 0x16a. Add a struct rtw8822cs_efuse describing this +and use it for copying the mac address when the SDIO bus is used. + +Signed-off-by: Martin Blumenstingl +--- + drivers/net/wireless/realtek/rtw88/rtw8822c.c | 9 +++++++++ + drivers/net/wireless/realtek/rtw88/rtw8822c.h | 6 ++++++ + 2 files changed, 15 insertions(+) + +diff --git a/drivers/net/wireless/realtek/rtw88/rtw8822c.c b/drivers/net/wireless/realtek/rtw88/rtw8822c.c +index 964e27887fe2..8ec779c7ab84 100644 +--- a/drivers/net/wireless/realtek/rtw88/rtw8822c.c ++++ b/drivers/net/wireless/realtek/rtw88/rtw8822c.c +@@ -29,6 +29,12 @@ static void rtw8822ce_efuse_parsing(struct rtw_efuse *efuse, + ether_addr_copy(efuse->addr, map->e.mac_addr); + } + ++static void rtw8822cs_efuse_parsing(struct rtw_efuse *efuse, ++ struct rtw8822c_efuse *map) ++{ ++ ether_addr_copy(efuse->addr, map->s.mac_addr); ++} ++ + static void rtw8822cu_efuse_parsing(struct rtw_efuse *efuse, + struct rtw8822c_efuse *map) + { +@@ -64,6 +70,9 @@ static int rtw8822c_read_efuse(struct rtw_dev *rtwdev, u8 *log_map) + case RTW_HCI_TYPE_PCIE: + rtw8822ce_efuse_parsing(efuse, map); + break; ++ case RTW_HCI_TYPE_SDIO: ++ rtw8822cs_efuse_parsing(efuse, map); ++ break; + case RTW_HCI_TYPE_USB: + rtw8822cu_efuse_parsing(efuse, map); + break; +diff --git a/drivers/net/wireless/realtek/rtw88/rtw8822c.h b/drivers/net/wireless/realtek/rtw88/rtw8822c.h +index 479d5d769c52..eec2e3074087 100644 +--- a/drivers/net/wireless/realtek/rtw88/rtw8822c.h ++++ b/drivers/net/wireless/realtek/rtw88/rtw8822c.h +@@ -16,6 +16,11 @@ struct rtw8822cu_efuse { + u8 res2[0x3d]; + }; + ++struct rtw8822cs_efuse { ++ u8 res0[0x4a]; /* 0x120 */ ++ u8 mac_addr[ETH_ALEN]; /* 0x16a */ ++}; ++ + struct rtw8822ce_efuse { + u8 mac_addr[ETH_ALEN]; /* 0x120 */ + u8 vender_id[2]; +@@ -92,6 +97,7 @@ struct rtw8822c_efuse { + u8 res10[0x42]; + union { + struct rtw8822cu_efuse u; ++ struct rtw8822cs_efuse s; + struct rtw8822ce_efuse e; + }; + }; +-- +2.39.0 + +32-bit SDIO bus reads/writes only work when the card is powered on. Add +an optional power_switch() callback to struct rtw_hci_ops where we +inform the HCI sub-driver that the chip is now powered on. Based on this +information the upcoming SDIO HCI implementation can then use the +appropriate 32-bit read/write accessors. + +Signed-off-by: Martin Blumenstingl +--- + drivers/net/wireless/realtek/rtw88/hci.h | 8 ++++++++ + drivers/net/wireless/realtek/rtw88/mac.c | 8 ++++++++ + 2 files changed, 16 insertions(+) + +diff --git a/drivers/net/wireless/realtek/rtw88/hci.h b/drivers/net/wireless/realtek/rtw88/hci.h +index 830d7532f2a3..602a6acc1ca1 100644 +--- a/drivers/net/wireless/realtek/rtw88/hci.h ++++ b/drivers/net/wireless/realtek/rtw88/hci.h +@@ -22,6 +22,8 @@ struct rtw_hci_ops { + int (*write_data_rsvd_page)(struct rtw_dev *rtwdev, u8 *buf, u32 size); + int (*write_data_h2c)(struct rtw_dev *rtwdev, u8 *buf, u32 size); + ++ void (*power_switch)(struct rtw_dev *rtwdev, bool on); ++ + u8 (*read8)(struct rtw_dev *rtwdev, u32 addr); + u16 (*read16)(struct rtw_dev *rtwdev, u32 addr); + u32 (*read32)(struct rtw_dev *rtwdev, u32 addr); +@@ -84,6 +86,12 @@ rtw_hci_write_data_h2c(struct rtw_dev *rtwdev, u8 *buf, u32 size) + return rtwdev->hci.ops->write_data_h2c(rtwdev, buf, size); + } + ++static inline void rtw_hci_power_switch(struct rtw_dev *rtwdev, bool on) ++{ ++ if (rtwdev->hci.ops->power_switch) ++ rtwdev->hci.ops->power_switch(rtwdev, on); ++} ++ + static inline u8 rtw_read8(struct rtw_dev *rtwdev, u32 addr) + { + return rtwdev->hci.ops->read8(rtwdev, addr); +diff --git a/drivers/net/wireless/realtek/rtw88/mac.c b/drivers/net/wireless/realtek/rtw88/mac.c +index 4e5c194aac29..bf1291902661 100644 +--- a/drivers/net/wireless/realtek/rtw88/mac.c ++++ b/drivers/net/wireless/realtek/rtw88/mac.c +@@ -273,16 +273,24 @@ static int rtw_mac_power_switch(struct rtw_dev *rtwdev, bool pwr_on) + if (pwr_on == cur_pwr) + return -EALREADY; + ++ /* Always signal power off before power sequence. This way ++ * read/write functions will take path which works in both ++ * states. State will change in the middle of the sequence. ++ */ ++ rtw_hci_power_switch(rtwdev, false); ++ + pwr_seq = pwr_on ? chip->pwr_on_seq : chip->pwr_off_seq; + ret = rtw_pwr_seq_parser(rtwdev, pwr_seq); + if (ret) + return ret; + + if (pwr_on) + set_bit(RTW_FLAG_POWERON, rtwdev->flags); + else + clear_bit(RTW_FLAG_POWERON, rtwdev->flags); + ++ rtw_hci_power_switch(rtwdev, pwr_on); ++ + return 0; + } + +-- +2.39.0 + +txdma_queue_mapping() and priority_queue_cfg() can use the first entry +of each chip's rqpn_table and page_table. Add this mapping so data +transmission is possible on SDIO based chipsets. + +Signed-off-by: Martin Blumenstingl +--- + drivers/net/wireless/realtek/rtw88/mac.c | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/drivers/net/wireless/realtek/rtw88/mac.c b/drivers/net/wireless/realtek/rtw88/mac.c +index c04938691add..8e1fa824b32b 100644 +--- a/drivers/net/wireless/realtek/rtw88/mac.c ++++ b/drivers/net/wireless/realtek/rtw88/mac.c +@@ -1053,6 +1053,9 @@ static int txdma_queue_mapping(struct rtw_dev *rtwdev) + else + return -EINVAL; + break; ++ case RTW_HCI_TYPE_SDIO: ++ rqpn = &chip->rqpn_table[0]; ++ break; + default: + return -EINVAL; + } +@@ -1215,6 +1218,9 @@ static int priority_queue_cfg(struct rtw_dev *rtwdev) + else + return -EINVAL; + break; ++ case RTW_HCI_TYPE_SDIO: ++ pg_tbl = &chip->page_table[0]; ++ break; + default: + return -EINVAL; + } +-- +2.39.0 + +Add the code specific to SDIO HCI in the MAC power on sequence. This is +based on the RTL8822BS and RTL8822CS vendor drivers. + +Co-developed-by: Jernej Skrabec +Signed-off-by: Jernej Skrabec +Signed-off-by: Martin Blumenstingl +--- + drivers/net/wireless/realtek/rtw88/mac.c | 41 ++++++++++++++++++++++-- + 1 file changed, 39 insertions(+), 2 deletions(-) + +diff --git a/drivers/net/wireless/realtek/rtw88/mac.c b/drivers/net/wireless/realtek/rtw88/mac.c +index 8e1fa824b32b..ad71f9838d1d 100644 +--- a/drivers/net/wireless/realtek/rtw88/mac.c ++++ b/drivers/net/wireless/realtek/rtw88/mac.c +@@ -7,6 +7,7 @@ + #include "reg.h" + #include "fw.h" + #include "debug.h" ++#include "sdio.h" + + void rtw_set_channel_mac(struct rtw_dev *rtwdev, u8 channel, u8 bw, + u8 primary_ch_idx) +@@ -60,6 +61,7 @@ EXPORT_SYMBOL(rtw_set_channel_mac); + + static int rtw_mac_pre_system_cfg(struct rtw_dev *rtwdev) + { ++ unsigned int retry; + u32 value32; + u8 value8; + +@@ -77,6 +79,26 @@ static int rtw_mac_pre_system_cfg(struct rtw_dev *rtwdev) + case RTW_HCI_TYPE_PCIE: + rtw_write32_set(rtwdev, REG_HCI_OPT_CTRL, BIT_USB_SUS_DIS); + break; ++ case RTW_HCI_TYPE_SDIO: ++ rtw_write8_clr(rtwdev, REG_SDIO_HSUS_CTRL, BIT(0)); ++ ++ for (retry = 0; retry < RTW_PWR_POLLING_CNT; retry++) { ++ if (rtw_read8(rtwdev, REG_SDIO_HSUS_CTRL) & BIT(1)) ++ break; ++ ++ usleep_range(10, 50); ++ } ++ ++ if (retry == RTW_PWR_POLLING_CNT) { ++ rtw_err(rtwdev, "failed to poll REG_SDIO_HSUS_CTRL[1]"); ++ return -ETIMEDOUT; ++ } ++ ++ if (rtw_sdio_is_sdio30_supported(rtwdev)) ++ rtw_write8_set(rtwdev, REG_HCI_OPT_CTRL + 2, BIT(2)); ++ else ++ rtw_write8_clr(rtwdev, REG_HCI_OPT_CTRL + 2, BIT(2)); ++ break; + case RTW_HCI_TYPE_USB: + break; + default: +@@ -248,6 +270,7 @@ static int rtw_mac_power_switch(struct rtw_dev *rtwdev, bool pwr_on) + { + const struct rtw_chip_info *chip = rtwdev->chip; + const struct rtw_pwr_seq_cmd **pwr_seq; ++ u32 imr; + u8 rpwm; + bool cur_pwr; + int ret; +@@ -279,18 +302,25 @@ static int rtw_mac_power_switch(struct rtw_dev *rtwdev, bool pwr_on) + */ + rtw_hci_power_switch(rtwdev, false); + ++ imr = rtw_read32(rtwdev, REG_SDIO_HIMR); ++ rtw_write32(rtwdev, REG_SDIO_HIMR, 0); ++ + pwr_seq = pwr_on ? chip->pwr_on_seq : chip->pwr_off_seq; + ret = rtw_pwr_seq_parser(rtwdev, pwr_seq); +- if (ret) ++ if (ret) { ++ rtw_write32(rtwdev, REG_SDIO_HIMR, imr); + return ret; ++ } + + if (pwr_on) + set_bit(RTW_FLAG_POWERON, rtwdev->flags); + else + clear_bit(RTW_FLAG_POWERON, rtwdev->flags); + + rtw_hci_power_switch(rtwdev, pwr_on); + ++ rtw_write32(rtwdev, REG_SDIO_HIMR, imr); ++ + return 0; + } + +@@ -462,6 +492,9 @@ static void download_firmware_reg_backup(struct rtw_dev *rtwdev, + rtw_write16(rtwdev, REG_FIFOPAGE_INFO_1, 0x200); + rtw_write32(rtwdev, REG_RQPN_CTRL_2, bckp[bckp_idx - 1].val); + ++ if (rtw_hci_type(rtwdev) == RTW_HCI_TYPE_SDIO) ++ rtw_read32(rtwdev, REG_SDIO_FREE_TXPG); ++ + /* Disable beacon related functions */ + tmp = rtw_read8(rtwdev, REG_BCN_CTRL); + bckp[bckp_idx].len = 1; +@@ -1074,8 +1107,12 @@ static int txdma_queue_mapping(struct rtw_dev *rtwdev) + if (rtw_chip_wcpu_11ac(rtwdev)) + rtw_write32(rtwdev, REG_H2CQ_CSR, BIT_H2CQ_FULL); + +- if (rtw_hci_type(rtwdev) == RTW_HCI_TYPE_USB) ++ if (rtw_hci_type(rtwdev) == RTW_HCI_TYPE_SDIO) { ++ rtw_read32(rtwdev, REG_SDIO_FREE_TXPG); ++ rtw_write32(rtwdev, REG_SDIO_TX_CTRL, 0); ++ } else if (rtw_hci_type(rtwdev) == RTW_HCI_TYPE_USB) { + rtw_write8_set(rtwdev, REG_TXDMA_PQ_MAP, BIT_RXDMA_ARBBW_EN); ++ } + + return 0; + } +-- +2.39.0 + +Add a sub-driver for SDIO based chipsets which implements the following +functionality: +- register accessors for 8, 16 and 32 bits for all states of the card + (including usage of 4x 8 bit access for one 32 bit buffer if the card + is not fully powered on yet - or if it's fully powered on then 1x 32 + bit access is used) +- checking whether there's space in the TX FIFO queue to transmit data +- transfers from the host to the device for actual network traffic, + reserved pages (for firmware download) and H2C (host-to-card) + transfers +- receiving data from the device +- deep power saving state + +The transmit path is optimized so DMA-capable SDIO host controllers can +directly use the buffers provided because the buffer's physical +addresses are 8 byte aligned. + +The receive path is prepared to support RX aggregation where the +chipset combines multiple MAC frames into one bigger buffer to reduce +SDIO transfer overhead. + +Co-developed-by: Jernej Skrabec +Signed-off-by: Jernej Skrabec +Signed-off-by: Martin Blumenstingl +--- + drivers/net/wireless/realtek/rtw88/Kconfig | 3 + + drivers/net/wireless/realtek/rtw88/Makefile | 3 + + drivers/net/wireless/realtek/rtw88/debug.h | 1 + + drivers/net/wireless/realtek/rtw88/mac.h | 1 - + drivers/net/wireless/realtek/rtw88/reg.h | 10 + + drivers/net/wireless/realtek/rtw88/sdio.c | 1242 +++++++++++++++++++ + drivers/net/wireless/realtek/rtw88/sdio.h | 175 +++ + 7 files changed, 1434 insertions(+), 1 deletion(-) + create mode 100644 drivers/net/wireless/realtek/rtw88/sdio.c + create mode 100644 drivers/net/wireless/realtek/rtw88/sdio.h + +diff --git a/drivers/net/wireless/realtek/rtw88/Kconfig b/drivers/net/wireless/realtek/rtw88/Kconfig +index 651ab56d9c6b..cdf9cb478ee2 100644 +--- a/drivers/net/wireless/realtek/rtw88/Kconfig ++++ b/drivers/net/wireless/realtek/rtw88/Kconfig +@@ -16,6 +16,9 @@ config RTW88_CORE + config RTW88_PCI + tristate + ++config RTW88_SDIO ++ tristate ++ + config RTW88_USB + tristate + +diff --git a/drivers/net/wireless/realtek/rtw88/Makefile b/drivers/net/wireless/realtek/rtw88/Makefile +index fe7293ee87b4..892cad60ba31 100644 +--- a/drivers/net/wireless/realtek/rtw88/Makefile ++++ b/drivers/net/wireless/realtek/rtw88/Makefile +@@ -59,5 +59,8 @@ rtw88_8821cu-objs := rtw8821cu.o + obj-$(CONFIG_RTW88_PCI) += rtw88_pci.o + rtw88_pci-objs := pci.o + ++obj-$(CONFIG_RTW88_SDIO) += rtw88_sdio.o ++rtw88_sdio-objs := sdio.o ++ + obj-$(CONFIG_RTW88_USB) += rtw88_usb.o + rtw88_usb-objs := usb.o +diff --git a/drivers/net/wireless/realtek/rtw88/debug.h b/drivers/net/wireless/realtek/rtw88/debug.h +index 066792dd96af..a9149c6c2b48 100644 +--- a/drivers/net/wireless/realtek/rtw88/debug.h ++++ b/drivers/net/wireless/realtek/rtw88/debug.h +@@ -24,6 +24,7 @@ enum rtw_debug_mask { + RTW_DBG_ADAPTIVITY = 0x00008000, + RTW_DBG_HW_SCAN = 0x00010000, + RTW_DBG_STATE = 0x00020000, ++ RTW_DBG_SDIO = 0x00040000, + + RTW_DBG_ALL = 0xffffffff + }; +diff --git a/drivers/net/wireless/realtek/rtw88/mac.h b/drivers/net/wireless/realtek/rtw88/mac.h +index 3172aa5ac4de..58c3dccc14bb 100644 +--- a/drivers/net/wireless/realtek/rtw88/mac.h ++++ b/drivers/net/wireless/realtek/rtw88/mac.h +@@ -7,7 +7,6 @@ + + #define RTW_HW_PORT_NUM 5 + #define cut_version_to_mask(cut) (0x1 << ((cut) + 1)) +-#define SDIO_LOCAL_OFFSET 0x10250000 + #define DDMA_POLLING_COUNT 1000 + #define C2H_PKT_BUF 256 + #define REPORT_BUF 128 +diff --git a/drivers/net/wireless/realtek/rtw88/reg.h b/drivers/net/wireless/realtek/rtw88/reg.h +index 8852b24d6c2a..4ea2c6b491e9 100644 +--- a/drivers/net/wireless/realtek/rtw88/reg.h ++++ b/drivers/net/wireless/realtek/rtw88/reg.h +@@ -185,6 +185,9 @@ + (((x) & BIT_MASK_TXDMA_VIQ_MAP) << BIT_SHIFT_TXDMA_VIQ_MAP) + #define REG_TXDMA_PQ_MAP 0x010C + #define BIT_RXDMA_ARBBW_EN BIT(0) ++#define BIT_RXSHFT_EN BIT(1) ++#define BIT_RXDMA_AGG_EN BIT(2) ++#define BIT_TXDMA_BW_EN BIT(3) + #define BIT_SHIFT_TXDMA_BEQ_MAP 8 + #define BIT_MASK_TXDMA_BEQ_MAP 0x3 + #define BIT_TXDMA_BEQ_MAP(x) \ +@@ -283,10 +286,17 @@ + #define REG_H2C_TAIL 0x0248 + #define REG_H2C_READ_ADDR 0x024C + #define REG_H2C_INFO 0x0254 ++#define REG_RXDMA_AGG_PG_TH 0x0280 ++#define BIT_SHIFT_DMA_AGG_TO_V1 8 ++#define BIT_EN_PRE_CALC BIT(29) + #define REG_RXPKT_NUM 0x0284 + #define BIT_RXDMA_REQ BIT(19) + #define BIT_RW_RELEASE BIT(18) + #define BIT_RXDMA_IDLE BIT(17) ++#define REG_RXDMA_STATUS 0x0288 ++#define REG_RXDMA_DPR 0x028C ++#define REG_RXDMA_MODE 0x0290 ++#define BIT_DMA_MODE BIT(1) + #define REG_RXPKTNUM 0x02B0 + + #define REG_INT_MIG 0x0304 +diff --git a/drivers/net/wireless/realtek/rtw88/sdio.c b/drivers/net/wireless/realtek/rtw88/sdio.c +new file mode 100644 +index 000000000000..0e637ff2293f +--- /dev/null ++++ b/drivers/net/wireless/realtek/rtw88/sdio.c +@@ -0,0 +1,1242 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause ++/* Copyright (C) 2021 Martin Blumenstingl ++ * Copyright (C) 2021 Jernej Skrabec ++ * ++ * Based on rtw88/pci.c: ++ * Copyright(c) 2018-2019 Realtek Corporation ++ */ ++ ++#include ++#include ++#include ++#include "sdio.h" ++#include "reg.h" ++#include "tx.h" ++#include "rx.h" ++#include "fw.h" ++#include "ps.h" ++#include "debug.h" ++ ++#define RTW_SDIO_INDIRECT_RW_RETRIES 50 ++ ++static bool rtw_sdio_is_bus_addr(u32 addr) ++{ ++ return (addr & RTW_SDIO_BUS_MSK) != 0; ++} ++ ++static bool rtw_sdio_bus_claim_needed(struct rtw_sdio *rtwsdio) ++{ ++ return !rtwsdio->irq_thread || ++ rtwsdio->irq_thread != current; ++} ++ ++static u32 rtw_sdio_to_bus_offset(struct rtw_dev *rtwdev, u32 addr) ++{ ++ switch (addr & RTW_SDIO_BUS_MSK) { ++ case WLAN_IOREG_OFFSET: ++ addr &= WLAN_IOREG_REG_MSK; ++ addr |= FIELD_PREP(REG_SDIO_CMD_ADDR_MSK, ++ REG_SDIO_CMD_ADDR_MAC_REG); ++ break; ++ case SDIO_LOCAL_OFFSET: ++ addr &= SDIO_LOCAL_REG_MSK; ++ addr |= FIELD_PREP(REG_SDIO_CMD_ADDR_MSK, ++ REG_SDIO_CMD_ADDR_SDIO_REG); ++ break; ++ default: ++ rtw_warn(rtwdev, "Cannot convert addr 0x%08x to bus offset", ++ addr); ++ } ++ ++ return addr; ++} ++ ++static void rtw_sdio_writel(struct rtw_sdio *rtwsdio, u32 val, ++ u32 addr, int *ret) ++{ ++ u8 buf[4]; ++ int i; ++ ++ if (!(addr & 3) && rtwsdio->is_powered_on) { ++ sdio_writel(rtwsdio->sdio_func, val, addr, ret); ++ return; ++ } ++ ++ *(__le32 *)buf = cpu_to_le32(val); ++ ++ for (i = 0; i < 4; i++) { ++ sdio_writeb(rtwsdio->sdio_func, buf[i], addr + i, ret); ++ if (*ret) ++ return; ++ } ++} ++ ++static u32 rtw_sdio_readl(struct rtw_sdio *rtwsdio, u32 addr, int *ret) ++{ ++ u8 buf[4]; ++ int i; ++ ++ if (!(addr & 3) && rtwsdio->is_powered_on) ++ return sdio_readl(rtwsdio->sdio_func, addr, ret); ++ ++ for (i = 0; i < 4; i++) { ++ buf[i] = sdio_readb(rtwsdio->sdio_func, addr + i, ret); ++ if (*ret) ++ return 0; ++ } ++ ++ return le32_to_cpu(*(__le32 *)buf); ++} ++ ++static u8 rtw_sdio_read_indirect8(struct rtw_dev *rtwdev, u32 addr, int *ret) ++{ ++ struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv; ++ u32 reg_cfg, reg_data; ++ int retry; ++ u8 tmp; ++ ++ reg_cfg = rtw_sdio_to_bus_offset(rtwdev, REG_SDIO_INDIRECT_REG_CFG); ++ reg_data = rtw_sdio_to_bus_offset(rtwdev, REG_SDIO_INDIRECT_REG_DATA); ++ ++ rtw_sdio_writel(rtwsdio, BIT(19) | addr, reg_cfg, ret); ++ if (*ret) ++ return 0; ++ ++ for (retry = 0; retry < RTW_SDIO_INDIRECT_RW_RETRIES; retry++) { ++ tmp = sdio_readb(rtwsdio->sdio_func, reg_cfg + 2, ret); ++ if (!ret && tmp & BIT(4)) ++ break; ++ } ++ ++ if (*ret) ++ return 0; ++ ++ return sdio_readb(rtwsdio->sdio_func, reg_data, ret); ++} ++ ++static int rtw_sdio_read_indirect_bytes(struct rtw_dev *rtwdev, u32 addr, ++ u8 *buf, int count) ++{ ++ int i, ret; ++ ++ for (i = 0; i < count; i++) { ++ buf[0] = rtw_sdio_read_indirect8(rtwdev, addr + i, &ret); ++ if (ret) ++ break; ++ } ++ ++ return ret; ++} ++ ++static u32 rtw_sdio_read_indirect32(struct rtw_dev *rtwdev, u32 addr, int *ret) ++{ ++ struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv; ++ u32 reg_cfg, reg_data, val; ++ int retry; ++ ++ reg_cfg = rtw_sdio_to_bus_offset(rtwdev, REG_SDIO_INDIRECT_REG_CFG); ++ reg_data = rtw_sdio_to_bus_offset(rtwdev, REG_SDIO_INDIRECT_REG_DATA); ++ ++ rtw_sdio_writel(rtwsdio, BIT(19) | BIT(17) | addr, reg_cfg, ret); ++ if (*ret) ++ return 0; ++ ++ for (retry = 0; retry < RTW_SDIO_INDIRECT_RW_RETRIES; retry++) { ++ val = sdio_readb(rtwsdio->sdio_func, reg_cfg + 2, ret); ++ if (!ret && (val & BIT(4))) ++ break; ++ } ++ ++ if (!*ret) ++ val = rtw_sdio_readl(rtwsdio, reg_data, ret); ++ ++ return val; ++} ++ ++static u8 rtw_sdio_read8(struct rtw_dev *rtwdev, u32 addr) ++{ ++ struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv; ++ bool direct, bus_claim; ++ int ret; ++ u8 val; ++ ++ bus_claim = rtw_sdio_bus_claim_needed(rtwsdio); ++ direct = rtw_sdio_is_bus_addr(addr); ++ ++ if (bus_claim) ++ sdio_claim_host(rtwsdio->sdio_func); ++ ++ if (direct) { ++ addr = rtw_sdio_to_bus_offset(rtwdev, addr); ++ val = sdio_readb(rtwsdio->sdio_func, addr, &ret); ++ } else { ++ val = rtw_sdio_read_indirect8(rtwdev, addr, &ret); ++ } ++ ++ if (bus_claim) ++ sdio_release_host(rtwsdio->sdio_func); ++ ++ if (ret) ++ rtw_warn(rtwdev, "sdio read8 failed (0x%x): %d", addr, ret); ++ ++ return val; ++} ++ ++static u16 rtw_sdio_read16(struct rtw_dev *rtwdev, u32 addr) ++{ ++ struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv; ++ bool direct, bus_claim; ++ u8 buf[2]; ++ int ret; ++ u16 val; ++ ++ bus_claim = rtw_sdio_bus_claim_needed(rtwsdio); ++ direct = rtw_sdio_is_bus_addr(addr); ++ ++ if (bus_claim) ++ sdio_claim_host(rtwsdio->sdio_func); ++ ++ if (direct) { ++ addr = rtw_sdio_to_bus_offset(rtwdev, addr); ++ buf[0] = sdio_readb(rtwsdio->sdio_func, addr, &ret); ++ if (!ret) ++ buf[1] = sdio_readb(rtwsdio->sdio_func, addr + 1, &ret); ++ val = le16_to_cpu(*(__le16 *)buf); ++ } else if (addr & 1) { ++ ret = rtw_sdio_read_indirect_bytes(rtwdev, addr, buf, 2); ++ val = le16_to_cpu(*(__le16 *)buf); ++ } else { ++ val = rtw_sdio_read_indirect32(rtwdev, addr, &ret); ++ } ++ ++ if (bus_claim) ++ sdio_release_host(rtwsdio->sdio_func); ++ ++ if (ret) ++ rtw_warn(rtwdev, "sdio read16 failed (0x%x): %d", addr, ret); ++ ++ return val; ++} ++ ++static u32 rtw_sdio_read32(struct rtw_dev *rtwdev, u32 addr) ++{ ++ struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv; ++ bool direct, bus_claim; ++ u8 buf[4]; ++ u32 val; ++ int ret; ++ ++ bus_claim = rtw_sdio_bus_claim_needed(rtwsdio); ++ direct = rtw_sdio_is_bus_addr(addr); ++ ++ if (bus_claim) ++ sdio_claim_host(rtwsdio->sdio_func); ++ ++ if (direct) { ++ addr = rtw_sdio_to_bus_offset(rtwdev, addr); ++ val = rtw_sdio_readl(rtwsdio, addr, &ret); ++ } else if (addr & 3) { ++ ret = rtw_sdio_read_indirect_bytes(rtwdev, addr, buf, 4); ++ val = le32_to_cpu(*(__le32 *)buf); ++ } else { ++ val = rtw_sdio_read_indirect32(rtwdev, addr, &ret); ++ } ++ ++ if (bus_claim) ++ sdio_release_host(rtwsdio->sdio_func); ++ ++ if (ret) ++ rtw_warn(rtwdev, "sdio read32 failed (0x%x): %d", addr, ret); ++ ++ return val; ++} ++ ++static u32 rtw_sdio_to_write_address(struct rtw_dev *rtwdev, u32 addr) ++{ ++ if (!rtw_sdio_is_bus_addr(addr)) ++ addr |= WLAN_IOREG_OFFSET; ++ ++ return rtw_sdio_to_bus_offset(rtwdev, addr); ++} ++ ++static void rtw_sdio_write8(struct rtw_dev *rtwdev, u32 addr, u8 val) ++{ ++ struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv; ++ bool bus_claim; ++ int ret; ++ ++ addr = rtw_sdio_to_write_address(rtwdev, addr); ++ bus_claim = rtw_sdio_bus_claim_needed(rtwsdio); ++ ++ if (bus_claim) ++ sdio_claim_host(rtwsdio->sdio_func); ++ ++ sdio_writeb(rtwsdio->sdio_func, val, addr, &ret); ++ ++ if (bus_claim) ++ sdio_release_host(rtwsdio->sdio_func); ++ ++ if (ret) ++ rtw_warn(rtwdev, "sdio write8 failed (0x%x): %d", addr, ret); ++} ++ ++static void rtw_sdio_write16(struct rtw_dev *rtwdev, u32 addr, u16 val) ++{ ++ struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv; ++ bool bus_claim; ++ int ret; ++ ++ addr = rtw_sdio_to_write_address(rtwdev, addr); ++ bus_claim = rtw_sdio_bus_claim_needed(rtwsdio); ++ ++ if (bus_claim) ++ sdio_claim_host(rtwsdio->sdio_func); ++ ++ sdio_writeb(rtwsdio->sdio_func, val, addr, &ret); ++ if (!ret) ++ sdio_writeb(rtwsdio->sdio_func, val >> 8, addr + 1, &ret); ++ ++ if (bus_claim) ++ sdio_release_host(rtwsdio->sdio_func); ++ ++ if (ret) ++ rtw_warn(rtwdev, "sdio write16 failed (0x%x): %d", addr, ret); ++} ++ ++static void rtw_sdio_write32(struct rtw_dev *rtwdev, u32 addr, u32 val) ++{ ++ struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv; ++ bool bus_claim; ++ int ret; ++ ++ addr = rtw_sdio_to_write_address(rtwdev, addr); ++ bus_claim = rtw_sdio_bus_claim_needed(rtwsdio); ++ ++ if (bus_claim) ++ sdio_claim_host(rtwsdio->sdio_func); ++ ++ rtw_sdio_writel(rtwsdio, val, addr, &ret); ++ ++ if (bus_claim) ++ sdio_release_host(rtwsdio->sdio_func); ++ ++ if (ret) ++ rtw_warn(rtwdev, "sdio write32 failed (0x%x): %d", addr, ret); ++} ++ ++static u32 rtw_sdio_get_tx_addr(struct rtw_dev *rtwdev, size_t size, ++ enum rtw_tx_queue_type queue) ++{ ++ u32 txaddr; ++ ++ switch (queue) { ++ case RTW_TX_QUEUE_BCN: ++ case RTW_TX_QUEUE_H2C: ++ case RTW_TX_QUEUE_HI0: ++ txaddr = FIELD_PREP(REG_SDIO_CMD_ADDR_MSK, ++ REG_SDIO_CMD_ADDR_TXFF_HIGH); ++ break; ++ case RTW_TX_QUEUE_VI: ++ case RTW_TX_QUEUE_VO: ++ txaddr = FIELD_PREP(REG_SDIO_CMD_ADDR_MSK, ++ REG_SDIO_CMD_ADDR_TXFF_NORMAL); ++ break; ++ case RTW_TX_QUEUE_BE: ++ case RTW_TX_QUEUE_BK: ++ txaddr = FIELD_PREP(REG_SDIO_CMD_ADDR_MSK, ++ REG_SDIO_CMD_ADDR_TXFF_LOW); ++ break; ++ case RTW_TX_QUEUE_MGMT: ++ txaddr = FIELD_PREP(REG_SDIO_CMD_ADDR_MSK, ++ REG_SDIO_CMD_ADDR_TXFF_EXTRA); ++ break; ++ default: ++ rtw_warn(rtwdev, "Unsupported queue for TX addr: 0x%02x\n", ++ queue); ++ return 0; ++ } ++ ++ txaddr += DIV_ROUND_UP(size, 4); ++ ++ return txaddr; ++}; ++ ++static int rtw_sdio_read_port(struct rtw_dev *rtwdev, u8 *buf, size_t count) ++{ ++ struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv; ++ u32 rxaddr = rtwsdio->rx_addr++; ++ int ret; ++ ++ ret = sdio_memcpy_fromio(rtwsdio->sdio_func, buf, ++ RTW_SDIO_ADDR_RX_RX0FF_GEN(rxaddr), count); ++ if (ret) ++ rtw_warn(rtwdev, ++ "Failed to read %lu byte(s) from SDIO port 0x%08x", ++ count, rxaddr); ++ ++ return ret; ++} ++ ++static int rtw_sdio_check_free_txpg(struct rtw_dev *rtwdev, u8 queue, ++ size_t count) ++{ ++ unsigned int pages_free, pages_needed; ++ ++ if (rtw_chip_wcpu_11n(rtwdev)) { ++ u32 free_txpg; ++ ++ free_txpg = rtw_sdio_read32(rtwdev, REG_SDIO_FREE_TXPG); ++ ++ switch (queue) { ++ case RTW_TX_QUEUE_BCN: ++ case RTW_TX_QUEUE_H2C: ++ case RTW_TX_QUEUE_HI0: ++ case RTW_TX_QUEUE_MGMT: ++ /* high */ ++ pages_free = free_txpg & 0xff; ++ break; ++ case RTW_TX_QUEUE_VI: ++ case RTW_TX_QUEUE_VO: ++ /* normal */ ++ pages_free = (free_txpg >> 8) & 0xff; ++ break; ++ case RTW_TX_QUEUE_BE: ++ case RTW_TX_QUEUE_BK: ++ /* low */ ++ pages_free = (free_txpg >> 16) & 0xff; ++ break; ++ default: ++ rtw_warn(rtwdev, "Unknown mapping for queue %u\n", queue); ++ break; ++ } ++ ++ /* add the pages from the public queue */ ++ pages_free += (free_txpg >> 24) & 0xff; ++ } else { ++ u32 free_txpg[3]; ++ ++ free_txpg[0] = rtw_sdio_read32(rtwdev, REG_SDIO_FREE_TXPG); ++ free_txpg[1] = rtw_sdio_read32(rtwdev, REG_SDIO_FREE_TXPG + 4); ++ free_txpg[2] = rtw_sdio_read32(rtwdev, REG_SDIO_FREE_TXPG + 8); ++ ++ switch (queue) { ++ case RTW_TX_QUEUE_BCN: ++ case RTW_TX_QUEUE_H2C: ++ case RTW_TX_QUEUE_HI0: ++ /* high */ ++ pages_free = free_txpg[0] & 0xfff; ++ break; ++ case RTW_TX_QUEUE_VI: ++ case RTW_TX_QUEUE_VO: ++ /* normal */ ++ pages_free = (free_txpg[0] >> 16) & 0xfff; ++ break; ++ case RTW_TX_QUEUE_BE: ++ case RTW_TX_QUEUE_BK: ++ /* low */ ++ pages_free = free_txpg[1] & 0xfff; ++ break; ++ case RTW_TX_QUEUE_MGMT: ++ /* extra */ ++ pages_free = free_txpg[2] & 0xfff; ++ break; ++ default: ++ rtw_warn(rtwdev, "Unknown mapping for queue %u\n", queue); ++ return -EINVAL; ++ } ++ ++ /* add the pages from the public queue */ ++ pages_free += (free_txpg[1] >> 16) & 0xfff; ++ } ++ ++ pages_needed = DIV_ROUND_UP(count, rtwdev->chip->page_size); ++ ++ if (pages_needed > pages_free) { ++ rtw_dbg(rtwdev, RTW_DBG_SDIO, ++ "Not enough free pages (%u needed, %u free) in queue %u for %zu bytes\n", ++ pages_needed, pages_free, queue, count); ++ return -EBUSY; ++ } ++ ++ return 0; ++} ++ ++static int rtw_sdio_write_port(struct rtw_dev *rtwdev, struct sk_buff *skb, ++ enum rtw_tx_queue_type queue) ++{ ++ struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv; ++ bool bus_claim; ++ size_t txsize; ++ u32 txaddr; ++ int ret; ++ ++ txaddr = rtw_sdio_get_tx_addr(rtwdev, skb->len, queue); ++ if (!txaddr) ++ return -EINVAL; ++ ++ txsize = sdio_align_size(rtwsdio->sdio_func, skb->len); ++ ++ ret = rtw_sdio_check_free_txpg(rtwdev, queue, txsize); ++ if (ret) ++ return ret; ++ ++ if (!IS_ALIGNED((unsigned long)skb->data, RTW_SDIO_DATA_PTR_ALIGN)) ++ rtw_warn(rtwdev, "Got unaligned SKB in %s() for queue %u\n", ++ __func__, queue); ++ ++ bus_claim = rtw_sdio_bus_claim_needed(rtwsdio); ++ ++ if (bus_claim) ++ sdio_claim_host(rtwsdio->sdio_func); ++ ++ ret = sdio_memcpy_toio(rtwsdio->sdio_func, txaddr, skb->data, txsize); ++ ++ if (bus_claim) ++ sdio_release_host(rtwsdio->sdio_func); ++ ++ if (ret) ++ rtw_warn(rtwdev, ++ "Failed to write %lu byte(s) to SDIO port 0x%08x", ++ txsize, txaddr); ++ ++ return ret; ++} ++ ++static void rtw_sdio_init(struct rtw_dev *rtwdev) ++{ ++ struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv; ++ ++ rtwsdio->irq_mask = REG_SDIO_HIMR_RX_REQUEST | REG_SDIO_HIMR_CPWM1; ++} ++ ++static void rtw_sdio_rx_aggregation(struct rtw_dev *rtwdev, bool enable) ++{ ++ u8 size, timeout; ++ ++ if (enable) { ++ if (rtwdev->chip->id == RTW_CHIP_TYPE_8822C) { ++ size = 0xff; ++ timeout = 0x20; ++ } else { ++ size = 0x6; ++ timeout = 0x6; ++ } ++ ++ /* Make the firmware honor the size limit configured below */ ++ rtw_write32_set(rtwdev, REG_RXDMA_AGG_PG_TH, BIT_EN_PRE_CALC); ++ ++ rtw_write8_set(rtwdev, REG_TXDMA_PQ_MAP, BIT_RXDMA_AGG_EN); ++ ++ rtw_write16(rtwdev, REG_RXDMA_AGG_PG_TH, size | ++ (timeout << BIT_SHIFT_DMA_AGG_TO_V1)); ++ ++ rtw_write8_set(rtwdev, REG_RXDMA_MODE, BIT_DMA_MODE); ++ } else { ++ rtw_write32_clr(rtwdev, REG_RXDMA_AGG_PG_TH, BIT_EN_PRE_CALC); ++ rtw_write8_clr(rtwdev, REG_TXDMA_PQ_MAP, BIT_RXDMA_AGG_EN); ++ rtw_write8_clr(rtwdev, REG_RXDMA_MODE, BIT_DMA_MODE); ++ } ++} ++ ++static void rtw_sdio_enable_interrupt(struct rtw_dev *rtwdev) ++{ ++ struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv; ++ ++ rtw_write32(rtwdev, REG_SDIO_HIMR, rtwsdio->irq_mask); ++} ++ ++static void rtw_sdio_disable_interrupt(struct rtw_dev *rtwdev) ++{ ++ rtw_write32(rtwdev, REG_SDIO_HIMR, 0x0); ++} ++ ++static u8 rtw_sdio_get_tx_qsel(struct rtw_dev *rtwdev, struct sk_buff *skb, ++ u8 queue) ++{ ++ switch (queue) { ++ case RTW_TX_QUEUE_BCN: ++ return TX_DESC_QSEL_BEACON; ++ case RTW_TX_QUEUE_H2C: ++ return TX_DESC_QSEL_H2C; ++ case RTW_TX_QUEUE_MGMT: ++ if (rtw_chip_wcpu_11n(rtwdev)) ++ return TX_DESC_QSEL_HIGH; ++ else ++ return TX_DESC_QSEL_MGMT; ++ case RTW_TX_QUEUE_HI0: ++ return TX_DESC_QSEL_HIGH; ++ default: ++ return skb->priority; ++ } ++}; ++ ++static int rtw_sdio_setup(struct rtw_dev *rtwdev) ++{ ++ /* nothing to do */ ++ return 0; ++} ++ ++static int rtw_sdio_start(struct rtw_dev *rtwdev) ++{ ++ rtw_sdio_rx_aggregation(rtwdev, false); ++ rtw_sdio_enable_interrupt(rtwdev); ++ ++ return 0; ++} ++ ++static void rtw_sdio_stop(struct rtw_dev *rtwdev) ++{ ++ rtw_sdio_disable_interrupt(rtwdev); ++} ++ ++static void rtw_sdio_deep_ps_enter(struct rtw_dev *rtwdev) ++{ ++ struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv; ++ bool tx_empty = true; ++ u8 queue; ++ ++ if (!rtw_fw_feature_check(&rtwdev->fw, FW_FEATURE_TX_WAKE)) { ++ /* Deep PS state is not allowed to TX-DMA */ ++ for (queue = 0; queue < RTK_MAX_TX_QUEUE_NUM; queue++) { ++ /* BCN queue is rsvd page, does not have DMA interrupt ++ * H2C queue is managed by firmware ++ */ ++ if (queue == RTW_TX_QUEUE_BCN || ++ queue == RTW_TX_QUEUE_H2C) ++ continue; ++ ++ /* check if there is any skb DMAing */ ++ if (skb_queue_len(&rtwsdio->tx_queue[queue])) { ++ tx_empty = false; ++ break; ++ } ++ } ++ } ++ ++ if (!tx_empty) { ++ rtw_dbg(rtwdev, RTW_DBG_PS, ++ "TX path not empty, cannot enter deep power save state\n"); ++ return; ++ } ++ ++ set_bit(RTW_FLAG_LEISURE_PS_DEEP, rtwdev->flags); ++ rtw_power_mode_change(rtwdev, true); ++} ++ ++static void rtw_sdio_deep_ps_leave(struct rtw_dev *rtwdev) ++{ ++ if (test_and_clear_bit(RTW_FLAG_LEISURE_PS_DEEP, rtwdev->flags)) ++ rtw_power_mode_change(rtwdev, false); ++} ++ ++static void rtw_sdio_deep_ps(struct rtw_dev *rtwdev, bool enter) ++{ ++ if (enter && !test_bit(RTW_FLAG_LEISURE_PS_DEEP, rtwdev->flags)) ++ rtw_sdio_deep_ps_enter(rtwdev); ++ ++ if (!enter && test_bit(RTW_FLAG_LEISURE_PS_DEEP, rtwdev->flags)) ++ rtw_sdio_deep_ps_leave(rtwdev); ++} ++ ++static void rtw_sdio_tx_kick_off(struct rtw_dev *rtwdev) ++{ ++ struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv; ++ ++ queue_work(rtwsdio->txwq, &rtwsdio->tx_handler_data->work); ++} ++ ++static void rtw_sdio_link_ps(struct rtw_dev *rtwdev, bool enter) ++{ ++ /* nothing to do */ ++} ++ ++static void rtw_sdio_interface_cfg(struct rtw_dev *rtwdev) ++{ ++ u32 val; ++ ++ rtw_read32(rtwdev, REG_SDIO_FREE_TXPG); ++ ++ val = rtw_read32(rtwdev, REG_SDIO_TX_CTRL); ++ val &= 0xfff8; ++ rtw_write32(rtwdev, REG_SDIO_TX_CTRL, val); ++} ++ ++static void rtw_sdio_power_switch(struct rtw_dev *rtwdev, bool on) ++{ ++ struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv; ++ ++ rtwsdio->is_powered_on = on; ++} ++ ++static struct rtw_sdio_tx_data *rtw_sdio_get_tx_data(struct sk_buff *skb) ++{ ++ struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); ++ ++ BUILD_BUG_ON(sizeof(struct rtw_sdio_tx_data) > ++ sizeof(info->status.status_driver_data)); ++ ++ return (struct rtw_sdio_tx_data *)info->status.status_driver_data; ++} ++ ++static void rtw_sdio_tx_skb_prepare(struct rtw_dev *rtwdev, ++ struct rtw_tx_pkt_info *pkt_info, ++ struct sk_buff *skb, ++ enum rtw_tx_queue_type queue) ++{ ++ const struct rtw_chip_info *chip = rtwdev->chip; ++ unsigned long data_addr, aligned_addr; ++ size_t offset; ++ u8 *pkt_desc; ++ ++ pkt_desc = skb_push(skb, chip->tx_pkt_desc_sz); ++ ++ data_addr = (unsigned long)pkt_desc; ++ aligned_addr = ALIGN(data_addr, RTW_SDIO_DATA_PTR_ALIGN); ++ ++ if (data_addr != aligned_addr) { ++ /* Ensure that the start of the pkt_desc is always aligned at ++ * RTW_SDIO_DATA_PTR_ALIGN. ++ */ ++ offset = RTW_SDIO_DATA_PTR_ALIGN - (aligned_addr - data_addr); ++ ++ pkt_desc = skb_push(skb, offset); ++ ++ /* By inserting padding to align the start of the pkt_desc we ++ * need to inform the firmware that the actual data starts at ++ * a different offset than normal. ++ */ ++ pkt_info->offset += offset; ++ } ++ ++ memset(pkt_desc, 0, chip->tx_pkt_desc_sz); ++ ++ pkt_info->qsel = rtw_sdio_get_tx_qsel(rtwdev, skb, queue); ++ ++ rtw_tx_fill_tx_desc(pkt_info, skb); ++ chip->ops->fill_txdesc_checksum(rtwdev, pkt_info, pkt_desc); ++} ++ ++static int rtw_sdio_write_data(struct rtw_dev *rtwdev, ++ struct rtw_tx_pkt_info *pkt_info, ++ struct sk_buff *skb, ++ enum rtw_tx_queue_type queue) ++{ ++ int ret; ++ ++ rtw_sdio_tx_skb_prepare(rtwdev, pkt_info, skb, queue); ++ ++ ret = rtw_sdio_write_port(rtwdev, skb, queue); ++ dev_kfree_skb_any(skb); ++ ++ return ret; ++} ++ ++static int rtw_sdio_write_data_rsvd_page(struct rtw_dev *rtwdev, u8 *buf, ++ u32 size) ++{ ++ struct rtw_tx_pkt_info pkt_info = {}; ++ struct sk_buff *skb; ++ ++ skb = rtw_tx_write_data_rsvd_page_get(rtwdev, &pkt_info, buf, size); ++ if (!skb) ++ return -ENOMEM; ++ ++ return rtw_sdio_write_data(rtwdev, &pkt_info, skb, RTW_TX_QUEUE_BCN); ++} ++ ++static int rtw_sdio_write_data_h2c(struct rtw_dev *rtwdev, u8 *buf, u32 size) ++{ ++ struct rtw_tx_pkt_info pkt_info = {}; ++ struct sk_buff *skb; ++ ++ skb = rtw_tx_write_data_h2c_get(rtwdev, &pkt_info, buf, size); ++ if (!skb) ++ return -ENOMEM; ++ ++ return rtw_sdio_write_data(rtwdev, &pkt_info, skb, RTW_TX_QUEUE_H2C); ++} ++ ++static int rtw_sdio_tx_write(struct rtw_dev *rtwdev, ++ struct rtw_tx_pkt_info *pkt_info, ++ struct sk_buff *skb) ++{ ++ struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv; ++ enum rtw_tx_queue_type queue = rtw_tx_queue_mapping(skb); ++ struct rtw_sdio_tx_data *tx_data; ++ ++ rtw_sdio_tx_skb_prepare(rtwdev, pkt_info, skb, queue); ++ ++ tx_data = rtw_sdio_get_tx_data(skb); ++ tx_data->sn = pkt_info->sn; ++ ++ skb_queue_tail(&rtwsdio->tx_queue[queue], skb); ++ ++ return 0; ++} ++ ++static void rtw_sdio_tx_err_isr(struct rtw_dev *rtwdev) ++{ ++ u32 val = rtw_read32(rtwdev, REG_TXDMA_STATUS); ++ ++ rtw_write32(rtwdev, REG_TXDMA_STATUS, val); ++} ++ ++static void rtw_sdio_rx_skb(struct rtw_dev *rtwdev, struct sk_buff *skb, ++ u32 pkt_offset, struct rtw_rx_pkt_stat *pkt_stat, ++ struct ieee80211_rx_status *rx_status) ++{ ++ memcpy(IEEE80211_SKB_RXCB(skb), rx_status, sizeof(*rx_status)); ++ ++ if (pkt_stat->is_c2h) { ++ skb_put(skb, pkt_stat->pkt_len + pkt_offset); ++ rtw_fw_c2h_cmd_rx_irqsafe(rtwdev, pkt_offset, skb); ++ return; ++ } ++ ++ skb_put(skb, pkt_stat->pkt_len); ++ skb_reserve(skb, pkt_offset); ++ ++ rtw_rx_stats(rtwdev, pkt_stat->vif, skb); ++ ++ ieee80211_rx_irqsafe(rtwdev->hw, skb); ++} ++ ++static void rtw_sdio_rxfifo_recv(struct rtw_dev *rtwdev, u32 rx_len) ++{ ++ struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv; ++ const struct rtw_chip_info *chip = rtwdev->chip; ++ u32 pkt_desc_sz = chip->rx_pkt_desc_sz; ++ struct ieee80211_rx_status rx_status; ++ struct rtw_rx_pkt_stat pkt_stat; ++ struct sk_buff *skb, *split_skb; ++ u32 pkt_offset, curr_pkt_len; ++ size_t bufsz; ++ u8 *rx_desc; ++ int ret; ++ ++ bufsz = sdio_align_size(rtwsdio->sdio_func, rx_len); ++ ++ skb = dev_alloc_skb(bufsz); ++ if (!skb) ++ return; ++ ++ ret = rtw_sdio_read_port(rtwdev, skb->data, bufsz); ++ if (ret) { ++ dev_kfree_skb_any(skb); ++ return; ++ } ++ ++ while (true) { ++ rx_desc = skb->data; ++ chip->ops->query_rx_desc(rtwdev, rx_desc, &pkt_stat, ++ &rx_status); ++ pkt_offset = pkt_desc_sz + pkt_stat.drv_info_sz + ++ pkt_stat.shift; ++ ++ curr_pkt_len = ALIGN(pkt_offset + pkt_stat.pkt_len, ++ RTW_SDIO_DATA_PTR_ALIGN); ++ ++ if ((curr_pkt_len + pkt_desc_sz) >= rx_len) { ++ /* Use the original skb (with it's adjusted offset) ++ * when processing the last (or even the only) entry to ++ * have it's memory freed automatically. ++ */ ++ rtw_sdio_rx_skb(rtwdev, skb, pkt_offset, &pkt_stat, ++ &rx_status); ++ break; ++ } ++ ++ split_skb = dev_alloc_skb(curr_pkt_len); ++ if (!split_skb) { ++ rtw_sdio_rx_skb(rtwdev, skb, pkt_offset, &pkt_stat, ++ &rx_status); ++ break; ++ } ++ ++ skb_copy_header(split_skb, skb); ++ memcpy(split_skb->data, skb->data, curr_pkt_len); ++ ++ rtw_sdio_rx_skb(rtwdev, split_skb, pkt_offset, &pkt_stat, ++ &rx_status); ++ ++ /* Move to the start of the next RX descriptor */ ++ skb_reserve(skb, curr_pkt_len); ++ rx_len -= curr_pkt_len; ++ } ++} ++ ++static void rtw_sdio_rx_isr(struct rtw_dev *rtwdev) ++{ ++ u32 rx_len; ++ ++ while (true) { ++ if (rtw_chip_wcpu_11n(rtwdev)) ++ rx_len = rtw_read16(rtwdev, REG_SDIO_RX0_REQ_LEN); ++ else ++ rx_len = rtw_read32(rtwdev, REG_SDIO_RX0_REQ_LEN); ++ ++ if (!rx_len) ++ break; ++ ++ rtw_sdio_rxfifo_recv(rtwdev, rx_len); ++ } ++} ++ ++static void rtw_sdio_handle_interrupt(struct sdio_func *sdio_func) ++{ ++ struct ieee80211_hw *hw = sdio_get_drvdata(sdio_func); ++ struct rtw_dev *rtwdev = hw->priv; ++ struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv; ++ u32 hisr; ++ ++ rtwsdio->irq_thread = current; ++ ++ hisr = rtw_read32(rtwdev, REG_SDIO_HISR); ++ ++ if (hisr & REG_SDIO_HISR_TXERR) ++ rtw_sdio_tx_err_isr(rtwdev); ++ if (hisr & REG_SDIO_HISR_RX_REQUEST) { ++ hisr &= ~REG_SDIO_HISR_RX_REQUEST; ++ rtw_sdio_rx_isr(rtwdev); ++ } ++ ++ rtw_write32(rtwdev, REG_SDIO_HISR, hisr); ++ ++ rtwsdio->irq_thread = NULL; ++} ++ ++static int __maybe_unused rtw_sdio_suspend(struct device *dev) ++{ ++ return 0; ++} ++ ++static int __maybe_unused rtw_sdio_resume(struct device *dev) ++{ ++ return 0; ++} ++ ++SIMPLE_DEV_PM_OPS(rtw_sdio_pm_ops, rtw_sdio_suspend, rtw_sdio_resume); ++EXPORT_SYMBOL(rtw_sdio_pm_ops); ++ ++static int rtw_sdio_claim(struct rtw_dev *rtwdev, struct sdio_func *sdio_func) ++{ ++ struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv; ++ int ret; ++ ++ sdio_claim_host(sdio_func); ++ ++ ret = sdio_enable_func(sdio_func); ++ if (ret) { ++ rtw_err(rtwdev, "Failed to enable SDIO func"); ++ goto err_release_host; ++ } ++ ++ ret = sdio_set_block_size(sdio_func, RTW_SDIO_BLOCK_SIZE); ++ if (ret) { ++ rtw_err(rtwdev, "Failed to set SDIO block size to 512"); ++ goto err_disable_func; ++ } ++ ++ rtwsdio->sdio_func = sdio_func; ++ ++ rtwsdio->sdio3_bus_mode = mmc_card_uhs(sdio_func->card); ++ ++ sdio_set_drvdata(sdio_func, rtwdev->hw); ++ SET_IEEE80211_DEV(rtwdev->hw, &sdio_func->dev); ++ ++ sdio_release_host(sdio_func); ++ ++ return 0; ++ ++err_disable_func: ++ sdio_disable_func(sdio_func); ++err_release_host: ++ sdio_release_host(sdio_func); ++ return ret; ++} ++ ++static void rtw_sdio_declaim(struct rtw_dev *rtwdev, ++ struct sdio_func *sdio_func) ++{ ++ sdio_claim_host(sdio_func); ++ sdio_disable_func(sdio_func); ++ sdio_release_host(sdio_func); ++} ++ ++static struct rtw_hci_ops rtw_sdio_ops = { ++ .tx_write = rtw_sdio_tx_write, ++ .tx_kick_off = rtw_sdio_tx_kick_off, ++ .setup = rtw_sdio_setup, ++ .start = rtw_sdio_start, ++ .stop = rtw_sdio_stop, ++ .deep_ps = rtw_sdio_deep_ps, ++ .link_ps = rtw_sdio_link_ps, ++ .interface_cfg = rtw_sdio_interface_cfg, ++ ++ .power_switch = rtw_sdio_power_switch, ++ ++ .read8 = rtw_sdio_read8, ++ .read16 = rtw_sdio_read16, ++ .read32 = rtw_sdio_read32, ++ .write8 = rtw_sdio_write8, ++ .write16 = rtw_sdio_write16, ++ .write32 = rtw_sdio_write32, ++ .write_data_rsvd_page = rtw_sdio_write_data_rsvd_page, ++ .write_data_h2c = rtw_sdio_write_data_h2c, ++}; ++ ++static int rtw_sdio_request_irq(struct rtw_dev *rtwdev, ++ struct sdio_func *sdio_func) ++{ ++ int ret; ++ ++ sdio_claim_host(sdio_func); ++ ret = sdio_claim_irq(sdio_func, &rtw_sdio_handle_interrupt); ++ sdio_release_host(sdio_func); ++ ++ if (ret) { ++ rtw_err(rtwdev, "failed to claim SDIO IRQ"); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static void rtw_sdio_indicate_tx_status(struct rtw_dev *rtwdev, ++ struct sk_buff *skb) ++{ ++ struct rtw_sdio_tx_data *tx_data = rtw_sdio_get_tx_data(skb); ++ struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); ++ struct ieee80211_hw *hw = rtwdev->hw; ++ ++ /* enqueue to wait for tx report */ ++ if (info->flags & IEEE80211_TX_CTL_REQ_TX_STATUS) { ++ rtw_tx_report_enqueue(rtwdev, skb, tx_data->sn); ++ return; ++ } ++ ++ /* always ACK for others, then they won't be marked as drop */ ++ ieee80211_tx_info_clear_status(info); ++ if (info->flags & IEEE80211_TX_CTL_NO_ACK) ++ info->flags |= IEEE80211_TX_STAT_NOACK_TRANSMITTED; ++ else ++ info->flags |= IEEE80211_TX_STAT_ACK; ++ ++ ieee80211_tx_status_irqsafe(hw, skb); ++} ++ ++static void rtw_sdio_process_tx_queue(struct rtw_dev *rtwdev, ++ enum rtw_tx_queue_type queue) ++{ ++ struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv; ++ struct sk_buff *skb; ++ int ret; ++ ++ while (true) { ++ skb = skb_dequeue(&rtwsdio->tx_queue[queue]); ++ if (!skb) ++ break; ++ ++ ret = rtw_sdio_write_port(rtwdev, skb, queue); ++ if (ret) { ++ skb_queue_head(&rtwsdio->tx_queue[queue], skb); ++ break; ++ } ++ ++ if (queue <= RTW_TX_QUEUE_VO) ++ rtw_sdio_indicate_tx_status(rtwdev, skb); ++ else ++ dev_kfree_skb_any(skb); ++ } ++} ++ ++static void rtw_sdio_tx_handler(struct work_struct *work) ++{ ++ struct rtw_sdio_work_data *work_data = ++ container_of(work, struct rtw_sdio_work_data, work); ++ struct rtw_dev *rtwdev = work_data->rtwdev; ++ struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv; ++ bool has_more_tx_data; ++ int queue; ++ ++ if (!rtw_fw_feature_check(&rtwdev->fw, FW_FEATURE_TX_WAKE)) ++ rtw_sdio_deep_ps_leave(rtwdev); ++ ++ do { ++ has_more_tx_data = false; ++ ++ for (queue = RTK_MAX_TX_QUEUE_NUM - 1; queue >= 0; queue--) { ++ rtw_sdio_process_tx_queue(rtwdev, queue); ++ ++ if (!skb_queue_empty(&rtwsdio->tx_queue[queue])) ++ has_more_tx_data = true; ++ } ++ } while (has_more_tx_data); ++} ++ ++static void rtw_sdio_free_irq(struct rtw_dev *rtwdev, ++ struct sdio_func *sdio_func) ++{ ++ sdio_release_irq(sdio_func); ++} ++ ++static int rtw_sdio_init_tx(struct rtw_dev *rtwdev) ++{ ++ struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv; ++ int i; ++ ++ rtwsdio->txwq = create_singlethread_workqueue("rtw88_sdio: tx wq"); ++ if (!rtwsdio->txwq) { ++ rtw_err(rtwdev, "failed to create TX work queue\n"); ++ return -ENOMEM; ++ } ++ ++ for (i = 0; i < RTK_MAX_TX_QUEUE_NUM; i++) ++ skb_queue_head_init(&rtwsdio->tx_queue[i]); ++ rtwsdio->tx_handler_data = kmalloc(sizeof(*rtwsdio->tx_handler_data), ++ GFP_KERNEL); ++ if (!rtwsdio->tx_handler_data) ++ goto err_destroy_wq; ++ ++ rtwsdio->tx_handler_data->rtwdev = rtwdev; ++ INIT_WORK(&rtwsdio->tx_handler_data->work, rtw_sdio_tx_handler); ++ ++ return 0; ++ ++err_destroy_wq: ++ destroy_workqueue(rtwsdio->txwq); ++ return -ENOMEM; ++} ++ ++static void rtw_sdio_deinit_tx(struct rtw_dev *rtwdev) ++{ ++ struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv; ++ int i; ++ ++ for (i = 0; i < RTK_MAX_TX_QUEUE_NUM; i++) ++ skb_queue_purge(&rtwsdio->tx_queue[i]); ++ ++ flush_workqueue(rtwsdio->txwq); ++ destroy_workqueue(rtwsdio->txwq); ++ kfree(rtwsdio->tx_handler_data); ++} ++ ++int rtw_sdio_probe(struct sdio_func *sdio_func, ++ const struct sdio_device_id *id) ++{ ++ struct ieee80211_hw *hw; ++ struct rtw_dev *rtwdev; ++ int drv_data_size; ++ int ret; ++ ++ drv_data_size = sizeof(struct rtw_dev) + sizeof(struct rtw_sdio); ++ hw = ieee80211_alloc_hw(drv_data_size, &rtw_ops); ++ if (!hw) { ++ dev_err(&sdio_func->dev, "failed to allocate hw"); ++ return -ENOMEM; ++ } ++ ++ rtwdev = hw->priv; ++ rtwdev->hw = hw; ++ rtwdev->dev = &sdio_func->dev; ++ rtwdev->chip = (struct rtw_chip_info *)id->driver_data; ++ rtwdev->hci.ops = &rtw_sdio_ops; ++ rtwdev->hci.type = RTW_HCI_TYPE_SDIO; ++ ++ ret = rtw_core_init(rtwdev); ++ if (ret) ++ goto err_release_hw; ++ ++ rtw_dbg(rtwdev, RTW_DBG_SDIO, ++ "rtw88 SDIO probe: vendor=0x%04x device=%04x class=%02x", ++ id->vendor, id->device, id->class); ++ ++ ret = rtw_sdio_claim(rtwdev, sdio_func); ++ if (ret) { ++ rtw_err(rtwdev, "failed to claim SDIO device"); ++ goto err_deinit_core; ++ } ++ ++ rtw_sdio_init(rtwdev); ++ ++ ret = rtw_sdio_init_tx(rtwdev); ++ if (ret) { ++ rtw_err(rtwdev, "failed to init SDIO TX queue\n"); ++ goto err_sdio_declaim; ++ } ++ ++ ret = rtw_chip_info_setup(rtwdev); ++ if (ret) { ++ rtw_err(rtwdev, "failed to setup chip information"); ++ goto err_destroy_txwq; ++ } ++ ++ ret = rtw_register_hw(rtwdev, hw); ++ if (ret) { ++ rtw_err(rtwdev, "failed to register hw"); ++ goto err_destroy_txwq; ++ } ++ ++ ret = rtw_sdio_request_irq(rtwdev, sdio_func); ++ if (ret) ++ goto err_unregister_hw; ++ ++ return 0; ++ ++err_unregister_hw: ++ rtw_unregister_hw(rtwdev, hw); ++err_destroy_txwq: ++ rtw_sdio_deinit_tx(rtwdev); ++err_sdio_declaim: ++ rtw_sdio_declaim(rtwdev, sdio_func); ++err_deinit_core: ++ rtw_core_deinit(rtwdev); ++err_release_hw: ++ ieee80211_free_hw(hw); ++ ++ return ret; ++} ++EXPORT_SYMBOL(rtw_sdio_probe); ++ ++void rtw_sdio_remove(struct sdio_func *sdio_func) ++{ ++ struct ieee80211_hw *hw = sdio_get_drvdata(sdio_func); ++ struct rtw_dev *rtwdev; ++ ++ if (!hw) ++ return; ++ ++ rtwdev = hw->priv; ++ ++ rtw_unregister_hw(rtwdev, hw); ++ rtw_sdio_disable_interrupt(rtwdev); ++ rtw_sdio_declaim(rtwdev, sdio_func); ++ rtw_sdio_free_irq(rtwdev, sdio_func); ++ rtw_sdio_deinit_tx(rtwdev); ++ rtw_core_deinit(rtwdev); ++ ieee80211_free_hw(hw); ++} ++EXPORT_SYMBOL(rtw_sdio_remove); ++ ++void rtw_sdio_shutdown(struct device *dev) ++{ ++ struct sdio_func *sdio_func = dev_to_sdio_func(dev); ++ struct ieee80211_hw *hw = sdio_get_drvdata(sdio_func); ++ const struct rtw_chip_info *chip; ++ struct rtw_dev *rtwdev; ++ ++ if (!hw) ++ return; ++ ++ rtwdev = hw->priv; ++ chip = rtwdev->chip; ++ ++ if (chip->ops->shutdown) ++ chip->ops->shutdown(rtwdev); ++} ++EXPORT_SYMBOL(rtw_sdio_shutdown); ++ ++MODULE_AUTHOR("Martin Blumenstingl"); ++MODULE_AUTHOR("Jernej Skrabec"); ++MODULE_DESCRIPTION("Realtek 802.11ac wireless SDIO driver"); ++MODULE_LICENSE("Dual BSD/GPL"); +diff --git a/drivers/net/wireless/realtek/rtw88/sdio.h b/drivers/net/wireless/realtek/rtw88/sdio.h +new file mode 100644 +index 000000000000..7339e35f808a +--- /dev/null ++++ b/drivers/net/wireless/realtek/rtw88/sdio.h +@@ -0,0 +1,175 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ ++/* Copyright (C) 2021 Martin Blumenstingl ++ * Copyright (C) 2021 Jernej Skrabec ++ */ ++ ++#ifndef __REG_SDIO_H_ ++#define __REG_SDIO_H_ ++ ++#include "main.h" ++ ++/* I/O bus domain address mapping */ ++#define SDIO_LOCAL_OFFSET 0x10250000 ++#define WLAN_IOREG_OFFSET 0x10260000 ++#define FIRMWARE_FIFO_OFFSET 0x10270000 ++#define TX_HIQ_OFFSET 0x10310000 ++#define TX_MIQ_OFFSET 0x10320000 ++#define TX_LOQ_OFFSET 0x10330000 ++#define TX_EPQ_OFFSET 0x10350000 ++#define RX_RX0FF_OFFSET 0x10340000 ++ ++#define RTW_SDIO_BUS_MSK 0xffff0000 ++#define SDIO_LOCAL_REG_MSK 0x00000fff ++#define WLAN_IOREG_REG_MSK 0x0000ffff ++ ++/* SDIO Tx Control */ ++#define REG_SDIO_TX_CTRL (SDIO_LOCAL_OFFSET + 0x0000) ++ ++/*SDIO status timeout*/ ++#define REG_SDIO_TIMEOUT (SDIO_LOCAL_OFFSET + 0x0002) ++ ++/* SDIO Host Interrupt Mask */ ++#define REG_SDIO_HIMR (SDIO_LOCAL_OFFSET + 0x0014) ++#define REG_SDIO_HIMR_RX_REQUEST BIT(0) ++#define REG_SDIO_HIMR_AVAL BIT(1) ++#define REG_SDIO_HIMR_TXERR BIT(2) ++#define REG_SDIO_HIMR_RXERR BIT(3) ++#define REG_SDIO_HIMR_TXFOVW BIT(4) ++#define REG_SDIO_HIMR_RXFOVW BIT(5) ++#define REG_SDIO_HIMR_TXBCNOK BIT(6) ++#define REG_SDIO_HIMR_TXBCNERR BIT(7) ++#define REG_SDIO_HIMR_BCNERLY_INT BIT(16) ++#define REG_SDIO_HIMR_C2HCMD BIT(17) ++#define REG_SDIO_HIMR_CPWM1 BIT(18) ++#define REG_SDIO_HIMR_CPWM2 BIT(19) ++#define REG_SDIO_HIMR_HSISR_IND BIT(20) ++#define REG_SDIO_HIMR_GTINT3_IND BIT(21) ++#define REG_SDIO_HIMR_GTINT4_IND BIT(22) ++#define REG_SDIO_HIMR_PSTIMEOUT BIT(23) ++#define REG_SDIO_HIMR_OCPINT BIT(24) ++#define REG_SDIO_HIMR_ATIMEND BIT(25) ++#define REG_SDIO_HIMR_ATIMEND_E BIT(26) ++#define REG_SDIO_HIMR_CTWEND BIT(27) ++/* the following two are RTL8188 SDIO Specific */ ++#define REG_SDIO_HIMR_MCU_ERR BIT(28) ++#define REG_SDIO_HIMR_TSF_BIT32_TOGGLE BIT(29) ++ ++/* SDIO Host Interrupt Service Routine */ ++#define REG_SDIO_HISR (SDIO_LOCAL_OFFSET + 0x0018) ++#define REG_SDIO_HISR_RX_REQUEST BIT(0) ++#define REG_SDIO_HISR_AVAL BIT(1) ++#define REG_SDIO_HISR_TXERR BIT(2) ++#define REG_SDIO_HISR_RXERR BIT(3) ++#define REG_SDIO_HISR_TXFOVW BIT(4) ++#define REG_SDIO_HISR_RXFOVW BIT(5) ++#define REG_SDIO_HISR_TXBCNOK BIT(6) ++#define REG_SDIO_HISR_TXBCNERR BIT(7) ++#define REG_SDIO_HISR_BCNERLY_INT BIT(16) ++#define REG_SDIO_HISR_C2HCMD BIT(17) ++#define REG_SDIO_HISR_CPWM1 BIT(18) ++#define REG_SDIO_HISR_CPWM2 BIT(19) ++#define REG_SDIO_HISR_HSISR_IND BIT(20) ++#define REG_SDIO_HISR_GTINT3_IND BIT(21) ++#define REG_SDIO_HISR_GTINT4_IND BIT(22) ++#define REG_SDIO_HISR_PSTIMEOUT BIT(23) ++#define REG_SDIO_HISR_OCPINT BIT(24) ++#define REG_SDIO_HISR_ATIMEND BIT(25) ++#define REG_SDIO_HISR_ATIMEND_E BIT(26) ++#define REG_SDIO_HISR_CTWEND BIT(27) ++/* the following two are RTL8188 SDIO Specific */ ++#define REG_SDIO_HISR_MCU_ERR BIT(28) ++#define REG_SDIO_HISR_TSF_BIT32_TOGGLE BIT(29) ++ ++/* HCI Current Power Mode */ ++#define REG_SDIO_HCPWM (SDIO_LOCAL_OFFSET + 0x0019) ++/* RXDMA Request Length */ ++#define REG_SDIO_RX0_REQ_LEN (SDIO_LOCAL_OFFSET + 0x001C) ++/* OQT Free Page */ ++#define REG_SDIO_OQT_FREE_PG (SDIO_LOCAL_OFFSET + 0x001E) ++/* Free Tx Buffer Page */ ++#define REG_SDIO_FREE_TXPG (SDIO_LOCAL_OFFSET + 0x0020) ++/* HCI Current Power Mode 1 */ ++#define REG_SDIO_HCPWM1 (SDIO_LOCAL_OFFSET + 0x0024) ++/* HCI Current Power Mode 2 */ ++#define REG_SDIO_HCPWM2 (SDIO_LOCAL_OFFSET + 0x0026) ++/* Free Tx Page Sequence */ ++#define REG_SDIO_FREE_TXPG_SEQ (SDIO_LOCAL_OFFSET + 0x0028) ++/* HTSF Informaion */ ++#define REG_SDIO_HTSFR_INFO (SDIO_LOCAL_OFFSET + 0x0030) ++#define REG_SDIO_HCPWM1_V2 (SDIO_LOCAL_OFFSET + 0x0038) ++/* H2C */ ++#define REG_SDIO_H2C (SDIO_LOCAL_OFFSET + 0x0060) ++/* HCI Request Power Mode 1 */ ++#define REG_SDIO_HRPWM1 (SDIO_LOCAL_OFFSET + 0x0080) ++/* HCI Request Power Mode 2 */ ++#define REG_SDIO_HRPWM2 (SDIO_LOCAL_OFFSET + 0x0082) ++/* HCI Power Save Clock */ ++#define REG_SDIO_HPS_CLKR (SDIO_LOCAL_OFFSET + 0x0084) ++/* SDIO HCI Suspend Control */ ++#define REG_SDIO_HSUS_CTRL (SDIO_LOCAL_OFFSET + 0x0086) ++/* SDIO Host Extension Interrupt Mask Always */ ++#define REG_SDIO_HIMR_ON (SDIO_LOCAL_OFFSET + 0x0090) ++/* SDIO Host Extension Interrupt Status Always */ ++#define REG_SDIO_HISR_ON (SDIO_LOCAL_OFFSET + 0x0091) ++ ++#define REG_SDIO_INDIRECT_REG_CFG (SDIO_LOCAL_OFFSET + 0x0040) ++#define REG_SDIO_INDIRECT_REG_DATA (SDIO_LOCAL_OFFSET + 0x0044) ++ ++/* Sdio Address for SDIO Local Reg, TRX FIFO, MAC Reg */ ++#define REG_SDIO_CMD_ADDR_MSK GENMASK(16, 13) ++#define REG_SDIO_CMD_ADDR_SDIO_REG 0 ++#define REG_SDIO_CMD_ADDR_MAC_REG 8 ++#define REG_SDIO_CMD_ADDR_TXFF_HIGH 4 ++#define REG_SDIO_CMD_ADDR_TXFF_LOW 6 ++#define REG_SDIO_CMD_ADDR_TXFF_NORMAL 5 ++#define REG_SDIO_CMD_ADDR_TXFF_EXTRA 7 ++#define REG_SDIO_CMD_ADDR_RXFF 7 ++ ++#define RTW_SDIO_BLOCK_SIZE 512 ++#define RTW_SDIO_ADDR_RX_RX0FF_GEN(_id) (0x0e000 | ((_id) & 0x3)) ++ ++#define RTW_SDIO_DATA_PTR_ALIGN 8 ++ ++struct sdio_func; ++struct sdio_device_id; ++ ++struct rtw_sdio_tx_data { ++ u8 sn; ++}; ++ ++struct rtw_sdio_work_data { ++ struct work_struct work; ++ struct rtw_dev *rtwdev; ++}; ++ ++struct rtw_sdio { ++ struct sdio_func *sdio_func; ++ ++ u32 irq_mask; ++ u8 rx_addr; ++ bool sdio3_bus_mode; ++ bool is_powered_on; ++ ++ void *irq_thread; ++ ++ struct workqueue_struct *txwq; ++ ++ struct sk_buff_head tx_queue[RTK_MAX_TX_QUEUE_NUM]; ++ struct rtw_sdio_work_data *tx_handler_data; ++}; ++ ++extern const struct dev_pm_ops rtw_sdio_pm_ops; ++ ++int rtw_sdio_probe(struct sdio_func *sdio_func, ++ const struct sdio_device_id *id); ++void rtw_sdio_remove(struct sdio_func *sdio_func); ++void rtw_sdio_shutdown(struct device *dev); ++ ++static inline bool rtw_sdio_is_sdio30_supported(struct rtw_dev *rtwdev) ++{ ++ struct rtw_sdio *rtwsdio = (struct rtw_sdio *)rtwdev->priv; ++ ++ return rtwsdio->sdio3_bus_mode; ++} ++ ++#endif +-- +2.39.0 + +Initialize the rpwm_addr and cpwm_addr for power-saving support on SDIO +based chipsets. + +Signed-off-by: Martin Blumenstingl +--- + drivers/net/wireless/realtek/rtw88/main.c | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/drivers/net/wireless/realtek/rtw88/main.c b/drivers/net/wireless/realtek/rtw88/main.c +index 888427cf3bdf..9435cb43d1dc 100644 +--- a/drivers/net/wireless/realtek/rtw88/main.c ++++ b/drivers/net/wireless/realtek/rtw88/main.c +@@ -18,6 +18,7 @@ + #include "debug.h" + #include "bf.h" + #include "sar.h" ++#include "sdio.h" + + bool rtw_disable_lps_deep_mode; + EXPORT_SYMBOL(rtw_disable_lps_deep_mode); +@@ -1785,6 +1786,10 @@ static int rtw_chip_parameter_setup(struct rtw_dev *rtwdev) + rtwdev->hci.rpwm_addr = 0x03d9; + rtwdev->hci.cpwm_addr = 0x03da; + break; ++ case RTW_HCI_TYPE_SDIO: ++ rtwdev->hci.rpwm_addr = REG_SDIO_HRPWM1; ++ rtwdev->hci.cpwm_addr = REG_SDIO_HCPWM1_V2; ++ break; + case RTW_HCI_TYPE_USB: + rtwdev->hci.rpwm_addr = 0xfe58; + rtwdev->hci.cpwm_addr = 0xfe57; +-- +2.39.0 + +For SDIO host controllers with DMA support the TX buffer physical memory +address need to be aligned at an 8-byte boundary. Reserve 8 bytes of +extra TX headroom so we can align the data without re-allocating the +transmit buffer. + +While here, also remove the TODO comment regarding extra headroom for +USB and SDIO. For SDIO the extra headroom is now handled and for USB it +was not needed so far. + +Signed-off-by: Martin Blumenstingl +--- + drivers/net/wireless/realtek/rtw88/main.c | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +diff --git a/drivers/net/wireless/realtek/rtw88/main.c b/drivers/net/wireless/realtek/rtw88/main.c +index 9435cb43d1dc..bcdf1f8c8450 100644 +--- a/drivers/net/wireless/realtek/rtw88/main.c ++++ b/drivers/net/wireless/realtek/rtw88/main.c +@@ -2163,9 +2163,11 @@ int rtw_register_hw(struct rtw_dev *rtwdev, struct ieee80211_hw *hw) + int max_tx_headroom = 0; + int ret; + +- /* TODO: USB & SDIO may need extra room? */ + max_tx_headroom = rtwdev->chip->tx_pkt_desc_sz; + ++ if (rtw_hci_type(rtwdev) == RTW_HCI_TYPE_SDIO) ++ max_tx_headroom += RTW_SDIO_DATA_PTR_ALIGN; ++ + hw->extra_tx_headroom = max_tx_headroom; + hw->queues = IEEE80211_NUM_ACS; + hw->txq_data_size = sizeof(struct rtw_txq); +-- +2.39.0 + +Increase LEAVE_LPS_TRY_CNT to give SDIO based chipsets more time to +enter or leave deep sleep mode. SDIO communication is often slower than +PCIe transfers so extra time is needed. + +Signed-off-by: Jernej Skrabec +Signed-off-by: Martin Blumenstingl +--- + drivers/net/wireless/realtek/rtw88/ps.h | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/drivers/net/wireless/realtek/rtw88/ps.h b/drivers/net/wireless/realtek/rtw88/ps.h +index c194386f6db5..b79bef32b169 100644 +--- a/drivers/net/wireless/realtek/rtw88/ps.h ++++ b/drivers/net/wireless/realtek/rtw88/ps.h +@@ -12,7 +12,7 @@ + #define POWER_TX_WAKE BIT(1) + #define POWER_MODE_LCLK BIT(0) + +-#define LEAVE_LPS_TRY_CNT 5 ++#define LEAVE_LPS_TRY_CNT 10 + #define LEAVE_LPS_TIMEOUT msecs_to_jiffies(100) + + int rtw_enter_ips(struct rtw_dev *rtwdev); +-- +2.39.0 + +Wire up RTL8822BS chipset support using the new rtw88 SDIO HCI code as +well as the existing RTL8822B chipset code. + +Signed-off-by: Jernej Skrabec +Signed-off-by: Martin Blumenstingl +--- + drivers/net/wireless/realtek/rtw88/Kconfig | 11 ++++++ + drivers/net/wireless/realtek/rtw88/Makefile | 3 ++ + .../net/wireless/realtek/rtw88/rtw8822bs.c | 34 +++++++++++++++++++ + 3 files changed, 48 insertions(+) + create mode 100644 drivers/net/wireless/realtek/rtw88/rtw8822bs.c + +diff --git a/drivers/net/wireless/realtek/rtw88/Kconfig b/drivers/net/wireless/realtek/rtw88/Kconfig +index cdf9cb478ee2..0cfc68dcc416 100644 +--- a/drivers/net/wireless/realtek/rtw88/Kconfig ++++ b/drivers/net/wireless/realtek/rtw88/Kconfig +@@ -45,6 +45,17 @@ config RTW88_8822BE + + 802.11ac PCIe wireless network adapter + ++config RTW88_8822BS ++ tristate "Realtek 8822BS SDIO wireless network adapter" ++ depends on MMC ++ select RTW88_CORE ++ select RTW88_SDIO ++ select RTW88_8822B ++ help ++ Select this option will enable support for 8822BS chipset ++ ++ 802.11ac SDIO wireless network adapter ++ + config RTW88_8822BU + tristate "Realtek 8822BU USB wireless network adapter" + depends on USB +diff --git a/drivers/net/wireless/realtek/rtw88/Makefile b/drivers/net/wireless/realtek/rtw88/Makefile +index 892cad60ba31..2b8f4dd9707f 100644 +--- a/drivers/net/wireless/realtek/rtw88/Makefile ++++ b/drivers/net/wireless/realtek/rtw88/Makefile +@@ -26,6 +26,9 @@ rtw88_8822b-objs := rtw8822b.o rtw8822b_table.o + obj-$(CONFIG_RTW88_8822BE) += rtw88_8822be.o + rtw88_8822be-objs := rtw8822be.o + ++obj-$(CONFIG_RTW88_8822BS) += rtw88_8822bs.o ++rtw88_8822bs-objs := rtw8822bs.o ++ + obj-$(CONFIG_RTW88_8822BU) += rtw88_8822bu.o + rtw88_8822bu-objs := rtw8822bu.o + +diff --git a/drivers/net/wireless/realtek/rtw88/rtw8822bs.c b/drivers/net/wireless/realtek/rtw88/rtw8822bs.c +new file mode 100644 +index 000000000000..4c74ad2d2e5e +--- /dev/null ++++ b/drivers/net/wireless/realtek/rtw88/rtw8822bs.c +@@ -0,0 +1,34 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause ++// Copyright(c) Jernej Skrabec ++ ++#include ++#include ++#include ++#include "sdio.h" ++#include "rtw8822b.h" ++ ++static const struct sdio_device_id rtw_8822bs_id_table[] = { ++ { ++ SDIO_DEVICE(SDIO_VENDOR_ID_REALTEK, ++ SDIO_DEVICE_ID_REALTEK_RTW8822BS), ++ .driver_data = (kernel_ulong_t)&rtw8822b_hw_spec, ++ }, ++ {} ++}; ++MODULE_DEVICE_TABLE(sdio, rtw_8822bs_id_table); ++ ++static struct sdio_driver rtw_8822bs_driver = { ++ .name = "rtw_8822bs", ++ .probe = rtw_sdio_probe, ++ .remove = rtw_sdio_remove, ++ .id_table = rtw_8822bs_id_table, ++ .drv = { ++ .pm = &rtw_sdio_pm_ops, ++ .shutdown = rtw_sdio_shutdown, ++ } ++}; ++module_sdio_driver(rtw_8822bs_driver); ++ ++MODULE_AUTHOR("Jernej Skrabec "); ++MODULE_DESCRIPTION("Realtek 802.11ac wireless 8822bs driver"); ++MODULE_LICENSE("Dual BSD/GPL"); +-- +2.39.0 + +Wire up RTL8822CS chipset support using the new rtw88 SDIO HCI code as +well as the existing RTL8822C chipset code. + +Signed-off-by: Martin Blumenstingl +--- + drivers/net/wireless/realtek/rtw88/Kconfig | 11 ++++++ + drivers/net/wireless/realtek/rtw88/Makefile | 3 ++ + .../net/wireless/realtek/rtw88/rtw8822cs.c | 34 +++++++++++++++++++ + 3 files changed, 48 insertions(+) + create mode 100644 drivers/net/wireless/realtek/rtw88/rtw8822cs.c + +diff --git a/drivers/net/wireless/realtek/rtw88/Kconfig b/drivers/net/wireless/realtek/rtw88/Kconfig +index 0cfc68dcc416..6b65da81127f 100644 +--- a/drivers/net/wireless/realtek/rtw88/Kconfig ++++ b/drivers/net/wireless/realtek/rtw88/Kconfig +@@ -78,6 +78,17 @@ config RTW88_8822CE + + 802.11ac PCIe wireless network adapter + ++config RTW88_8822CS ++ tristate "Realtek 8822CS SDIO wireless network adapter" ++ depends on MMC ++ select RTW88_CORE ++ select RTW88_SDIO ++ select RTW88_8822C ++ help ++ Select this option will enable support for 8822CS chipset ++ ++ 802.11ac SDIO wireless network adapter ++ + config RTW88_8822CU + tristate "Realtek 8822CU USB wireless network adapter" + depends on USB +diff --git a/drivers/net/wireless/realtek/rtw88/Makefile b/drivers/net/wireless/realtek/rtw88/Makefile +index 2b8f4dd9707f..6105c2745bda 100644 +--- a/drivers/net/wireless/realtek/rtw88/Makefile ++++ b/drivers/net/wireless/realtek/rtw88/Makefile +@@ -38,6 +38,9 @@ rtw88_8822c-objs := rtw8822c.o rtw8822c_table.o + obj-$(CONFIG_RTW88_8822CE) += rtw88_8822ce.o + rtw88_8822ce-objs := rtw8822ce.o + ++obj-$(CONFIG_RTW88_8822CS) += rtw88_8822cs.o ++rtw88_8822cs-objs := rtw8822cs.o ++ + obj-$(CONFIG_RTW88_8822CU) += rtw88_8822cu.o + rtw88_8822cu-objs := rtw8822cu.o + +diff --git a/drivers/net/wireless/realtek/rtw88/rtw8822cs.c b/drivers/net/wireless/realtek/rtw88/rtw8822cs.c +new file mode 100644 +index 000000000000..3d7279d70aa9 +--- /dev/null ++++ b/drivers/net/wireless/realtek/rtw88/rtw8822cs.c +@@ -0,0 +1,34 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause ++// Copyright(c) Martin Blumenstingl ++ ++#include ++#include ++#include ++#include "sdio.h" ++#include "rtw8822c.h" ++ ++static const struct sdio_device_id rtw_8822cs_id_table[] = { ++ { ++ SDIO_DEVICE(SDIO_VENDOR_ID_REALTEK, ++ SDIO_DEVICE_ID_REALTEK_RTW8822CS), ++ .driver_data = (kernel_ulong_t)&rtw8822c_hw_spec, ++ }, ++ {} ++}; ++MODULE_DEVICE_TABLE(sdio, rtw_8822cs_id_table); ++ ++static struct sdio_driver rtw_8822cs_driver = { ++ .name = "rtw_8822cs", ++ .probe = rtw_sdio_probe, ++ .remove = rtw_sdio_remove, ++ .id_table = rtw_8822cs_id_table, ++ .drv = { ++ .pm = &rtw_sdio_pm_ops, ++ .shutdown = rtw_sdio_shutdown, ++ } ++}; ++module_sdio_driver(rtw_8822cs_driver); ++ ++MODULE_AUTHOR("Martin Blumenstingl "); ++MODULE_DESCRIPTION("Realtek 802.11ac wireless 8822cs driver"); ++MODULE_LICENSE("Dual BSD/GPL"); +-- +2.39.0 + +Wire up RTL8821CS chipset support using the new rtw88 SDIO HCI code as +well as the existing RTL8821C chipset code. + +Signed-off-by: Martin Blumenstingl +--- + drivers/net/wireless/realtek/rtw88/Kconfig | 11 ++++++ + drivers/net/wireless/realtek/rtw88/Makefile | 3 ++ + .../net/wireless/realtek/rtw88/rtw8821cs.c | 34 +++++++++++++++++++ + 3 files changed, 48 insertions(+) + create mode 100644 drivers/net/wireless/realtek/rtw88/rtw8821cs.c + +diff --git a/drivers/net/wireless/realtek/rtw88/Kconfig b/drivers/net/wireless/realtek/rtw88/Kconfig +index 6b65da81127f..29eb2f8e0eb7 100644 +--- a/drivers/net/wireless/realtek/rtw88/Kconfig ++++ b/drivers/net/wireless/realtek/rtw88/Kconfig +@@ -133,6 +133,17 @@ config RTW88_8821CE + + 802.11ac PCIe wireless network adapter + ++config RTW88_8821CS ++ tristate "Realtek 8821CS SDIO wireless network adapter" ++ depends on MMC ++ select RTW88_CORE ++ select RTW88_SDIO ++ select RTW88_8821C ++ help ++ Select this option will enable support for 8821CS chipset ++ ++ 802.11ac SDIO wireless network adapter ++ + config RTW88_8821CU + tristate "Realtek 8821CU USB wireless network adapter" + depends on USB +diff --git a/drivers/net/wireless/realtek/rtw88/Makefile b/drivers/net/wireless/realtek/rtw88/Makefile +index 6105c2745bda..82979b30ae8d 100644 +--- a/drivers/net/wireless/realtek/rtw88/Makefile ++++ b/drivers/net/wireless/realtek/rtw88/Makefile +@@ -59,6 +59,9 @@ rtw88_8821c-objs := rtw8821c.o rtw8821c_table.o + obj-$(CONFIG_RTW88_8821CE) += rtw88_8821ce.o + rtw88_8821ce-objs := rtw8821ce.o + ++obj-$(CONFIG_RTW88_8821CS) += rtw88_8821cs.o ++rtw88_8821cs-objs := rtw8821cs.o ++ + obj-$(CONFIG_RTW88_8821CU) += rtw88_8821cu.o + rtw88_8821cu-objs := rtw8821cu.o + +diff --git a/drivers/net/wireless/realtek/rtw88/rtw8821cs.c b/drivers/net/wireless/realtek/rtw88/rtw8821cs.c +new file mode 100644 +index 000000000000..61f82b38cda4 +--- /dev/null ++++ b/drivers/net/wireless/realtek/rtw88/rtw8821cs.c +@@ -0,0 +1,34 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause ++// Copyright(c) Martin Blumenstingl ++ ++#include ++#include ++#include ++#include "sdio.h" ++#include "rtw8821c.h" ++ ++static const struct sdio_device_id rtw_8821cs_id_table[] = { ++ { ++ SDIO_DEVICE(SDIO_VENDOR_ID_REALTEK, ++ SDIO_DEVICE_ID_REALTEK_RTW8821CS), ++ .driver_data = (kernel_ulong_t)&rtw8821c_hw_spec, ++ }, ++ {} ++}; ++MODULE_DEVICE_TABLE(sdio, rtw_8821cs_id_table); ++ ++static struct sdio_driver rtw_8821cs_driver = { ++ .name = "rtw_8821cs", ++ .probe = rtw_sdio_probe, ++ .remove = rtw_sdio_remove, ++ .id_table = rtw_8821cs_id_table, ++ .drv = { ++ .pm = &rtw_sdio_pm_ops, ++ .shutdown = rtw_sdio_shutdown, ++ } ++}; ++module_sdio_driver(rtw_8821cs_driver); ++ ++MODULE_AUTHOR("Martin Blumenstingl "); ++MODULE_DESCRIPTION("Realtek 802.11ac wireless 8821cs driver"); ++MODULE_LICENSE("Dual BSD/GPL"); +-- +2.39.0