Linux wireless drivers development
 help / color / mirror / Atom feed
* RE: [PATCH v2] wifi: rtw89: usb: fix TX flow control by tracking in-flight URBs
From: Ping-Ke Shih @ 2026-03-23  9:31 UTC (permalink / raw)
  To: Lucid Duck, linux-wireless@vger.kernel.org; +Cc: Bitterblue Smith
In-Reply-To: <20260321040000.31192-1-lucid_duck@justthetip.ca>

Lucid Duck <lucid_duck@justthetip.ca> wrote:
> From: Lucid Duck <lucid_duck@justthetip.ca>
> Date: Thu, 20 Mar 2026 20:00:00 -0700
> Subject: [PATCH v2] wifi: rtw89: usb: fix TX flow control by tracking
>  in-flight URBs

What is your mailer? Not 'git send-email'?
I'm not sure if I can apply this by patchwork tool.

> 
> rtw89_usb_ops_check_and_reclaim_tx_resource() returns a hardcoded
> placeholder value (42) instead of actual TX resource availability.
> This violates mac80211's flow control contract, preventing backpressure
> and causing uncontrolled URB accumulation under sustained TX load.
> 
> Fix by adding per-channel atomic counters (tx_inflight[]) that track
> in-flight URBs:
> 
> - Increment before usb_submit_urb() with rollback on failure
> - Decrement in completion callback
> - Return (MAX_URBS - inflight) to mac80211, or 0 when at capacity
> - Exclude firmware command channel (CH12) from tracking
> 
> The pre-increment pattern prevents a race where the USB core completes
> the URB (possibly on another CPU) before the submitting code increments
> the counter.
> 
> Tested on D-Link DWA-X1850 (RTL8832AU), kernel 6.18.3:
> 
>                      Unpatched -> Patched
>   USB3 5GHz DL:      494 -> 709 Mbps (+44%)
>   USB3 5GHz retx:    8 -> 1 (-88%)
>   USB3 2.4GHz DL:    54 -> 68 Mbps (+25%)
>   USB2 5GHz DL:      196 -> 225 Mbps (+15%)
>   USB2 2.4GHz DL:    123 -> 131 Mbps (+6%)

As this patch does TX flow control, could you share the uplink data
as well?

> 
> Signed-off-by: Lucid Duck <lucid_duck@justthetip.ca>
> ---
> Resending v2. This was prepared in late January after addressing v1
> review feedback, but the send failed silently (SMTP misconfiguration)
> and never appeared on lore.kernel.org. Apologies for the delay.
> 
> Changes since v1:
> - Removed duplicate "TX flow control" comment (Ping-Ke Shih)
> - Added test results to commit message (Ping-Ke Shih)
> 
> Bitterblue's CH12 question from v1: the CH12 guards in tx_kick_off()
> and write_port_complete() are a matched pair. tx_kick_off() skips
> atomic_inc for CH12, so the completion handler must skip atomic_dec
> to match. Removing only the completion side causes counter underflow.
> 
> Additional validation: 100-iteration stress test, 50-iteration
> teardown (rmmod/modprobe under load), 10x hot-unplug during active
> TX, and 30-minute soak -- all pass with counters balanced at idle.
> 
> The 32-URB-per-channel limit is based on similar USB wireless drivers
> (mt76, ath9k_htc). The fixed value works well for both USB2 and USB3.

Can increasing 32 get better performance? The stress test with small
packets might yield low throughput? (Just a question)

> 
>  drivers/net/wireless/realtek/rtw89/usb.c | 26 ++++++++++++++++++-----
>  drivers/net/wireless/realtek/rtw89/usb.h |  6 ++++++
>  2 files changed, 27 insertions(+), 5 deletions(-)
> 
> diff --git a/drivers/net/wireless/realtek/rtw89/usb.c
> b/drivers/net/wireless/realtek/rtw89/usb.c
> index eb489df..faafa3c 100644
> --- a/drivers/net/wireless/realtek/rtw89/usb.c
> +++ b/drivers/net/wireless/realtek/rtw89/usb.c
> @@ -161,16 +161,25 @@ static u32
>  rtw89_usb_ops_check_and_reclaim_tx_resource(struct rtw89_dev *rtwdev,
>                                             u8 txch)
>  {
> +       struct rtw89_usb *rtwusb = rtw89_usb_priv(rtwdev);
> +       int inflight;
> +
> +       /* Firmware command channel is not tracked */
>         if (txch == RTW89_TXCH_CH12)
>                 return 1;
> 
> -       return 42; /* TODO some kind of calculation? */
> +       inflight = atomic_read(&rtwusb->tx_inflight[txch]);
> +       if (inflight >= RTW89_USB_MAX_TX_URBS_PER_CH)

Out of curiosity. Is it possible inflight > RTW89_USB_MAX_TX_URBS_PER_CH?

> +               return 0;
> +
> +       return RTW89_USB_MAX_TX_URBS_PER_CH - inflight;
>  }
> 
>  static void rtw89_usb_write_port_complete(struct urb *urb)
>  {
>         struct rtw89_usb_tx_ctrl_block *txcb = urb->context;
>         struct rtw89_dev *rtwdev = txcb->rtwdev;
> +       struct rtw89_usb *rtwusb = rtw89_usb_priv(rtwdev);
>         struct ieee80211_tx_info *info;
>         struct rtw89_txwd_body *txdesc;
>         struct sk_buff *skb;
> @@ -229,6 +238,10 @@ static void rtw89_usb_write_port_complete(struct urb *urb)
>                 break;
>         }
> 
> +       /* Decrement in-flight counter (skip firmware command channel) */
> +       if (txcb->txch != RTW89_TXCH_CH12)
> +               atomic_dec(&rtwusb->tx_inflight[txcb->txch]);
> +
>         kfree(txcb);
>  }
> 
> @@ -306,9 +319,17 @@ static void rtw89_usb_ops_tx_kick_off(struct rtw89_dev
> *rtwdev, u8 txch)
> 
>                 skb_queue_tail(&txcb->tx_ack_queue, skb);
> 
> +               /* Increment BEFORE submit to avoid race with completion */
> +               if (txch != RTW89_TXCH_CH12)
> +                       atomic_inc(&rtwusb->tx_inflight[txch]);
> +
>                 ret = rtw89_usb_write_port(rtwdev, txch, skb->data, skb->len,
>                                            txcb);
>                 if (ret) {
> +                       /* Rollback increment on failure */
> +                       if (txch != RTW89_TXCH_CH12)
> +                               atomic_dec(&rtwusb->tx_inflight[txch]);
> +
>                         if (ret != -ENODEV)
>                                 rtw89_err(rtwdev, "write port txch %d failed:
> %d\n",
>                                           txch, ret);
> @@ -666,8 +687,10 @@ static void rtw89_usb_init_tx(struct rtw89_dev *rtwdev)
>         struct rtw89_usb *rtwusb = rtw89_usb_priv(rtwdev);
>         int i;
> 
> -       for (i = 0; i < ARRAY_SIZE(rtwusb->tx_queue); i++)
> +       for (i = 0; i < ARRAY_SIZE(rtwusb->tx_queue); i++) {
>                 skb_queue_head_init(&rtwusb->tx_queue[i]);
> +               atomic_set(&rtwusb->tx_inflight[i], 0);
> +       }
>  }
> 
>  static void rtw89_usb_deinit_tx(struct rtw89_dev *rtwdev)
> diff --git a/drivers/net/wireless/realtek/rtw89/usb.h
> b/drivers/net/wireless/realtek/rtw89/usb.h
> index 9f554b5..1459122 100644
> --- a/drivers/net/wireless/realtek/rtw89/usb.h
> +++ b/drivers/net/wireless/realtek/rtw89/usb.h
> @@ -20,6 +20,9 @@
>  #define RTW89_MAX_ENDPOINT_NUM         9
>  #define RTW89_MAX_BULKOUT_NUM          7
> 
> +/* TX flow control: max in-flight URBs per channel */

I think the code self-explain this already. No need this comment. 
It'd be helpful if you can explain how you decide 32. 
(In commit message, you mentioned this imitate other drivers, so
no need comment about that.)

> +#define RTW89_USB_MAX_TX_URBS_PER_CH   32
> +
>  struct rtw89_usb_info {
>         u32 usb_host_request_2;
>         u32 usb_wlan0_1;
> @@ -63,6 +66,9 @@ struct rtw89_usb {
>         struct usb_anchor tx_submitted;
> 
>         struct sk_buff_head tx_queue[RTW89_TXCH_NUM];
> +
> +       /* TX flow control: track in-flight URBs per channel */

I'd not prefer this comment neither. 

> +       atomic_t tx_inflight[RTW89_TXCH_NUM];
>  };
> 
>  static inline struct rtw89_usb *rtw89_usb_priv(struct rtw89_dev *rtwdev)
> --
> 2.53.0

^ permalink raw reply

* Re: ath12k: desc_va endianness problem
From: Baochen Qiang @ 2026-03-23  9:31 UTC (permalink / raw)
  To: Alexander Wilhelm, Jeff Johnson; +Cc: ath12k, linux-wireless, linux-kernel
In-Reply-To: <ab0Y9v6BkH4HDrFr@FUE-ALEWI-WINX>



On 3/20/2026 5:52 PM, Alexander Wilhelm wrote:
> Hello ath12k developers,
> 
> I have another fix for the big endian platform, but unfortunately the data types
> do not match here, so I need your support. The problem is the following: the
> structs `hal_reo_dest_ring`, `hal_wbm_completion_ring`, and
> `hal_wbm_release_ring_cc_rx` all define the members `buf_va_lo` and `buf_va_hi`
> as `__le32`. At first glance this seems correct, because the entire structure
> contains only little endian fields. The local variable `desc_va` in each
> function (see patch below) is of type `u64`, so it makes sense that I would need
> to convert from little endian to CPU endian. Unfortunately, this leads to the
> following crashes, in `tx_completion` and `rx_process_wbm`, respectivally:
> 
> 
>     Kernel attempted to read user page (40dcdf) - exploit attempt? (uid: 0)
>     BUG: Unable to handle kernel data access on read at 0x0040dcdf
>     Faulting instruction address: 0xe209290c
>     Oops: Kernel access of bad area, sig: 11 [#1]
>     BE PAGE_SIZE=4K SMP NR_CPUS=4 CoreNet Generic
>     Modules linked in: ath12k(O) mac80211(O) cfg80211(O) compat(O) ...
>     CPU: 1 PID: 10200 Comm: jshn Tainted: G           O       6.6.73 #0
>     Hardware name: CyBoxAP-A e5500 0x80241021 CoreNet Generic
>     NIP:  e209290c LR: e2092854 CTR: c08d3190
>     REGS: dffe3d40 TRAP: 0300   Tainted: G           O        (6.6.73)
>     MSR:  00029002 <CE,EE,ME>  CR: 44004804  XER: 00000000
>     DEAR: 0040dcdf ESR: 00000000 
>     GPR00: e2092854 dffe3e30 c328a500 e2092854 0040dcce 00000008 00070000 cf900000 
>     GPR08: 00000000 cf900004 40000000 c8e52c4c c08d3190 1002801c 0fcf5000 c0ab85f8 
>     GPR16: d0d1f7a0 c12a9080 00000001 df7b7f80 00000003 cf900000 e1bc0000 e1ccb988 
>     GPR24: ffffffff c8ed0000 e1cc0220 00000000 c8ec0000 c8ec0000 c8ec0f50 c8ec0000 
>     NIP [e209290c] ath12k_dp_tx_completion_handler+0x22c/0x720 [ath12k]
>     LR [e2092854] ath12k_dp_tx_completion_handler+0x174/0x720 [ath12k]
>     Call Trace:
>     [dffe3e30] [e2092854] ath12k_dp_tx_completion_handler+0x174/0x720 [ath12k] (unreliable)
>     [dffe3e80] [e208fe18] ath12k_dp_service_srng+0x58/0x380 [ath12k]
>     [dffe3ed0] [e20a1490] ath12k_pci_hif_resume+0x520/0x8a0 [ath12k]
>     [dffe3f00] [c067404c] __napi_poll+0x4c/0x260
>     [dffe3f30] [c06746f8] net_rx_action+0x188/0x340
>     [dffe3fa0] [c003a3d8] handle_softirqs+0x128/0x280
>     [dffe3ff0] [c00045b0] do_softirq_own_stack+0x30/0x50
>     [d0f2fb70] [00000000] 0x0
>     [d0f2fb90] [c003a7d0] irq_exit+0x70/0xa0
>     [d0f2fba0] [c0000c84] ExternalInput+0x144/0x160
>     --- interrupt: 500 at percpu_counter_add_batch+0x9c/0x150
>     NIP:  c0425e8c LR: c01a5964 CTR: c01764e0
>     REGS: d0f2fbb0 TRAP: 0500   Tainted: G           O        (6.6.73)
>     MSR:  00029002 <CE,EE,ME>  CR: 48008802  XER: 20000000
> 
>     GPR00: c01a5a00 d0f2fca0 c328a500 c1db7300 dffc0f20 00000000 fffffffc 00021002 
>     GPR08: 1e763000 e1091054 00000007 c12b0530 88002808 1002801c 0fcf5000 c0ab85f8 
>     GPR16: d0d1f7a0 dffc0f20 00000000 000003fe 00000000 f92412bd 00000003 c9525480 
>     GPR24: d0f2fd74 c8a501f8 c12b0530 00029002 00000007 00000000 0000000b c1db7300 
>     NIP [c0425e8c] percpu_counter_add_batch+0x9c/0x150
>     LR [c01a5964] unmap_page_range+0x484/0x820
>     --- interrupt: 500
>     [d0f2fca0] [00000001] 0x1 (unreliable)
>     [d0f2fcd0] [c01a5a00] unmap_page_range+0x520/0x820
>     [d0f2fd60] [c01a5d9c] unmap_vmas+0x9c/0xe0
>     [d0f2fda0] [c01afef4] exit_mmap+0xb4/0x2a0
>     [d0f2fe40] [c0031610] mmput+0x40/0x140
>     [d0f2fe60] [c0038df4] do_exit+0x2b4/0x990
>     [d0f2feb0] [c00396c4] do_group_exit+0x34/0xa0
>     [d0f2fed0] [c0039748] sys_exit_group+0x18/0x20
>     [d0f2fee0] [c000dbac] system_call_exception+0xac/0x1f0
>     [d0f2ff00] [c00110e8] ret_from_syscall+0x0/0x28
>     --- interrupt: c00 at 0xfded438
>     NIP:  0fded438 LR: 0ff23958 CTR: 0fd94930
>     REGS: d0f2ff10 TRAP: 0c00   Tainted: G           O        (6.6.73)
>     MSR:  0002f902 <CE,EE,PR,FP,ME>  CR: 28002402  XER: 20000000
> 
>     GPR00: 000000ea bff93390 b0316520 00000000 113e8af0 113e8af0 00000000 00000000 
>     GPR08: 00000000 00000000 00000000 ffffffff b02ccb04 1002801c 100a0000 bfbc4260 
>     GPR16: 114974b0 00000000 114a4de0 00000000 b02cc900 00000001 00000000 00000001 
>     GPR24: 0ff239a0 00000000 00000001 00000000 b030f52c fffff000 0ff23958 00000000 
>     NIP [0fded438] 0xfded438
>     LR [0ff23958] 0xff23958
>     --- interrupt: c00
>     Code: 512a421e 2e140000 512a463e 40f20008 555b9f3e 39350004 754a4000 7c804c2c 41c20224 7c87442c 2c040000 41c20230 <88a40011> 7fc3f378 83a40008 8a640010
>     ---[ end trace 0000000000000000 ]---
> 
>     Kernel panic - not syncing: Fatal exception
>     ---[ end Kernel panic - not syncing: Fatal exception ]---
> 
> 
>     user@root:~# Kernel attempted to read user page (c011de) - exploit attempt? (uid: 0)
>     BUG: Unable to handle kernel data access on read at 0x00c011de
>     Faulting instruction address: 0xe1e3dc44
>     Oops: Kernel access of bad area, sig: 11 [#1]
>     BE PAGE_SIZE=4K SMP NR_CPUS=4 CoreNet Generic
>     Modules linked in: ...
>     CPU: 1 PID: 0 Comm: swapper/1 Tainted: G           O       6.6.73 #0
>     Hardware name: CyBoxAP-A e5500 0x80241021 CoreNet Generic
>     NIP:  e1e3dc44 LR: e1e3dc30 CTR: c08d40e0
>     REGS: dffe3ce0 TRAP: 0300   Tainted: G           O        (6.6.73)
>     MSR:  00029002 <CE,EE,ME>  CR: 44004402  XER: 00000000
>     DEAR: 00c011de ESR: 00000000 
>     GPR00: e1e33154 dffe3dd0 c1870000 00000000 cebe0000 00000000 00000000 00c011ce 
>     GPR08: 00000001 00000000 00020000 c30a294c c08d40e0 00000000 00000001 00000000 
>     GPR16: e1ce2668 c9270000 c9269a18 c92664d0 e1ce26dc 00000000 babababa dffe3df4 
>     GPR24: 00000040 00000000 c9266480 dffe3dec dffe3e04 c9260000 00c011ce c9269a18 
>     NIP [e1e3dc44] ath12k_dp_rx_process_wbm_err+0x124/0x600 [ath12k]
>     LR [e1e3dc30] ath12k_dp_rx_process_wbm_err+0x110/0x600 [ath12k]
>     Call Trace:
>     [dffe3dd0] [c0ab8e30] 0xc0ab8e30 (unreliable)
>     [dffe3e80] [e1e33154] ath12k_dp_service_srng+0x314/0x380 [ath12k]
>     [dffe3ed0] [e1e44540] ath12k_pci_hif_resume+0x520/0x8a0 [ath12k]
>     [dffe3f00] [c0674c7c] __napi_poll+0x4c/0x260
>     [dffe3f30] [c0675328] net_rx_action+0x188/0x340
>     [dffe3fa0] [c003a3d8] handle_softirqs+0x128/0x280
>     [dffe3ff0] [c00045b0] do_softirq_own_stack+0x30/0x50
>     [c18c7e10] [c12b040c] 0xc12b040c
>     [c18c7e30] [c003a7d0] irq_exit+0x70/0xa0
>     [c18c7e40] [c0000c84] ExternalInput+0x144/0x160
>     --- interrupt: 500 at arch_cpu_idle+0x24/0x50
>     NIP:  c00071f4 LR: c00071f4 CTR: c000fe14
>     REGS: c18c7e50 TRAP: 0500   Tainted: G           O        (6.6.73)
>     MSR:  0002b002 <CE,EE,FP,ME>  CR: 84000402  XER: 00000000
> 
>     GPR00: c08cc978 c18c7f40 c1870000 00000005 00000001 40000000 c328becc c12b0530 
>     GPR08: c12b0530 c000fe14 0098ca91 00154674 24000402 00000000 00000001 00000000 
>     GPR16: 00000000 00000000 c00119a0 dffee5f0 00000001 00000000 ffffffff c1050254 
>     GPR24: c12c0000 c0011970 c0011940 c12d0000 00000004 c12b040c c12b0000 00000001 
>     NIP [c00071f4] arch_cpu_idle+0x24/0x50
>     LR [c00071f4] arch_cpu_idle+0x24/0x50
>     --- interrupt: 500
>     [c18c7f40] [c0a367e0] 0xc0a367e0 (unreliable)
>     [c18c7f50] [c08cc978] default_idle_call+0x38/0x58
>     [c18c7f60] [c007b3b0] do_idle+0xf0/0x130
>     [c18c7f80] [c007b580] cpu_startup_entry+0x30/0x40
>     [c18c7fa0] [c001325c] start_secondary+0x48c/0x930
>     [c18c7ff0] [c0002870] __secondary_start+0x90/0xdc
>     Code: 7fa3eb78 4bfcba59 7c641b79 41c20144 38a10044 7fa3eb78 4bfcdb85 7c651b79 40c2026c 83c10058 2c1e0000 41c202d0 <813e0010> 7c09b000 41c20010 7e84a378
>     ---[ end trace 0000000000000000 ]---
> 
>     Kernel panic - not syncing: Fatal exception
>     ---[ end Kernel panic - not syncing: Fatal exception ]---
> 
> 
> My fix, as shown in the patch below, is to remove the conversion. But then the
> member variables `buf_va_lo` and `buf_va_hi` must be `u32`, which is obviously
> wrong. Alternatively, `desc_va` must be `__le64`, but that is likely also
> incorrect, because the address is simply dereferenced, and this clearly requires
> CPU endianness. What I also do not fully understand is who actually fills these
> addresses and at which stage this happens. I hope you can help clarify this so
> that I can provide a correct patch for this issue afterward.
> 
> 

hmm, i am not sure here, but can you please try

diff --git a/drivers/net/wireless/ath/ath12k/dp.c b/drivers/net/wireless/ath/ath12k/dp.c
index 1c82d927d27b..f142759a217b 100644
--- a/drivers/net/wireless/ath/ath12k/dp.c
+++ b/drivers/net/wireless/ath/ath12k/dp.c
@@ -1246,7 +1246,7 @@ static int ath12k_dp_cc_desc_init(struct ath12k_base *ab)

                        /* Update descriptor VA in SPT */
                        rx_desc_addr = ath12k_dp_cc_get_desc_addr_ptr(dp, ppt_idx, j);
-                       *rx_desc_addr = &rx_descs[j];
+                       *rx_desc_addr = (struct ath12k_rx_desc_info
*)cpu_to_le64(&rx_descs[j]);
                }
        }

@@ -1286,7 +1286,7 @@ static int ath12k_dp_cc_desc_init(struct ath12k_base *ab)
                                /* Update descriptor VA in SPT */
                                tx_desc_addr =
                                        ath12k_dp_cc_get_desc_addr_ptr(dp, ppt_idx, j);
-                               *tx_desc_addr = &tx_descs[j];
+                               *tx_desc_addr = (struct ath12k_tx_desc_info
*)cpu_to_le64(&tx_descs[j]);
                        }
                }
                spin_unlock_bh(&dp->tx_desc_lock[pool_id]);


^ permalink raw reply related

* Re: [PATCH 11/12] carl9170: skip cross-band channel changes during software scan
From: Johannes Berg @ 2026-03-23  8:21 UTC (permalink / raw)
  To: Christian Lamparter, Masi Osmani; +Cc: linux-wireless
In-Reply-To: <99567d484a37d7101ae51ea6f022f40ef469d81d.camel@sipsolutions.net>

On Mon, 2026-03-23 at 09:17 +0100, Johannes Berg wrote:
> On Sat, 2026-03-21 at 22:58 +0100, Christian Lamparter wrote:
> > > 
> > > When the adapter is associated on a specific band, scanning channels
> > > on the other band produces no useful roaming candidates for the
> > > current BSS.
> 
> This sentence is so clearly wrong, I don't really even know what to say.

Well, actually ... I read "current BSS" as "current ESS" since otherwise
the sentence is true but nonsense, since roaming obviously involves
leaving the current BSS and joining a different BSS in the same ESS.

johannes

^ permalink raw reply

* Re: [PATCH 11/12] carl9170: skip cross-band channel changes during software scan
From: Johannes Berg @ 2026-03-23  8:17 UTC (permalink / raw)
  To: Christian Lamparter, Masi Osmani; +Cc: linux-wireless
In-Reply-To: <73153743-e0e8-4f2d-8774-066f53460511@gmail.com>

On Sat, 2026-03-21 at 22:58 +0100, Christian Lamparter wrote:
> > 
> > When the adapter is associated on a specific band, scanning channels
> > on the other band produces no useful roaming candidates for the
> > current BSS.

This sentence is so clearly wrong, I don't really even know what to say.

> @Johannes: Is this "stay within the band" something the driver should do,
> or could this be moved up to mac80211/cfg80211?

It's something neither should do.

johannes

^ permalink raw reply

* [PATCH v2] wifi: wl1251: validate packet IDs before indexing tx_frames
From: Pengpeng Hou @ 2026-03-23  8:08 UTC (permalink / raw)
  To: Michael Nemanov, Johannes Berg, linux-wireless
  Cc: linux-kernel, Kees Cook, Abdun Nihaal, Pengpeng Hou

wl1251_tx_packet_cb() uses the firmware completion ID directly to index
the fixed 16-entry wl->tx_frames[] array. The ID is a raw u8 from the
completion block, and the callback does not currently verify that it
fits the array before dereferencing it.

Reject completion IDs that fall outside wl->tx_frames[] and keep the
existing NULL check in the same guard. This keeps the fix local to the
trust boundary and avoids touching the rest of the completion flow.

Signed-off-by: Pengpeng Hou <pengpeng@iscas.ac.cn>
---
v2:
- add commit message context and fix rationale
- no code changes

 drivers/net/wireless/ti/wl1251/tx.c | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/drivers/net/wireless/ti/wl1251/tx.c b/drivers/net/wireless/ti/wl1251/tx.c
index 2da8c0d5105b..4489aa77bb0f 100644
--- a/drivers/net/wireless/ti/wl1251/tx.c
+++ b/drivers/net/wireless/ti/wl1251/tx.c
@@ -402,12 +402,14 @@ static void wl1251_tx_packet_cb(struct wl1251 *wl,
 	int hdrlen;
 	u8 *frame;
 
-	skb = wl->tx_frames[result->id];
-	if (skb == NULL) {
-		wl1251_error("SKB for packet %d is NULL", result->id);
+	if (unlikely(result->id >= ARRAY_SIZE(wl->tx_frames) ||
+		     wl->tx_frames[result->id] == NULL)) {
+		wl1251_error("invalid packet id %u", result->id);
 		return;
 	}
 
+	skb = wl->tx_frames[result->id];
+
 	info = IEEE80211_SKB_CB(skb);
 
 	if (!(info->flags & IEEE80211_TX_CTL_NO_ACK) &&
-- 
2.50.1 (Apple Git-155)


^ permalink raw reply related

* [PATCH] wifi: wl1251: validate packet IDs before indexing tx_frames
From: Pengpeng Hou @ 2026-03-23  8:03 UTC (permalink / raw)
  To: Michael Nemanov, Johannes Berg, linux-wireless
  Cc: linux-kernel, Kees Cook, Abdun Nihaal, Pengpeng Hou

Signed-off-by: Pengpeng Hou <pengpeng@iscas.ac.cn>
---
 drivers/net/wireless/ti/wl1251/tx.c | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/drivers/net/wireless/ti/wl1251/tx.c b/drivers/net/wireless/ti/wl1251/tx.c
index 2da8c0d5105b..4489aa77bb0f 100644
--- a/drivers/net/wireless/ti/wl1251/tx.c
+++ b/drivers/net/wireless/ti/wl1251/tx.c
@@ -402,12 +402,14 @@ static void wl1251_tx_packet_cb(struct wl1251 *wl,
 	int hdrlen;
 	u8 *frame;
 
-	skb = wl->tx_frames[result->id];
-	if (skb == NULL) {
-		wl1251_error("SKB for packet %d is NULL", result->id);
+	if (unlikely(result->id >= ARRAY_SIZE(wl->tx_frames) ||
+		     wl->tx_frames[result->id] == NULL)) {
+		wl1251_error("invalid packet id %u", result->id);
 		return;
 	}
 
+	skb = wl->tx_frames[result->id];
+
 	info = IEEE80211_SKB_CB(skb);
 
 	if (!(info->flags & IEEE80211_TX_CTL_NO_ACK) &&
-- 
2.50.1 (Apple Git-155)


^ permalink raw reply related

* [PATCH] brcmfmac: validate bsscfg indices in IF events
From: Pengpeng Hou @ 2026-03-23  7:45 UTC (permalink / raw)
  To: arend.vanspriel
  Cc: johannes.berg, chi-hsien.lin, chung-hsien.hsu, linux-wireless,
	brcm80211, brcm80211-dev-list.pdl, linux-kernel, kees, pengpeng

brcmf_fweh_handle_if_event() validates the firmware-provided interface
index before it touches drvr->iflist[], but it still uses the raw
bsscfgidx field as an array index without a matching range check.

Reject IF events whose bsscfg index does not fit in drvr->iflist[]
before indexing the interface array.

Signed-off-by: Pengpeng Hou <pengpeng@iscas.ac.cn>
---
 drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.c
index 984886481f4e..1cff4ba76943 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.c
@@ -153,6 +153,11 @@ static void brcmf_fweh_handle_if_event(struct brcmf_pub *drvr,
 		bphy_err(drvr, "invalid interface index: %u\n", ifevent->ifidx);
 		return;
 	}
+	if (ifevent->bsscfgidx >= BRCMF_MAX_IFS) {
+		bphy_err(drvr, "invalid bsscfg index: %u\n",
+			 ifevent->bsscfgidx);
+		return;
+	}
 
 	ifp = drvr->iflist[ifevent->bsscfgidx];
 
-- 
2.50.1 (Apple Git-155)


^ permalink raw reply related

* Re: [PATCH] wireless-regdb: Update regulatory rules for India (IN) on 6GHz
From: Chen-Yu Tsai @ 2026-03-23  5:12 UTC (permalink / raw)
  To: Chen-Yu Tsai
  Cc: druth, Ping-Ke Shih, wireless-regdb, linux-wireless,
	Johannes Berg, Gaurav Kansal, Degrader Snehil
In-Reply-To: <20260311085141.1634383-1-wens@kernel.org>

On Wed, 11 Mar 2026 16:51:40 +0800, Chen-Yu Tsai wrote:
> The Government of India has officially opened this band for licence-exempt
> use via Gazette Notification G.S.R. 47(E), dated January 20, 2026,
> published January 21, 2026, issued by the Ministry of Communications
> under the Indian Telegraph Act, 1885 and the Indian Wireless Telegraphy
> Act, 1933.
> 
> Gazette details:
>   Title   : Use of Low Power and Very Low Power Wireless Access System
>             including Radio Local Area Network in Lower 6 GHz Band
>             (Exemption from Licensing Requirement) Rules, 2026
>   File No : 24-04/2025-UBB
>   Gazette : No. 47, CG-DL-E-21012026-269488
>   Signed  : Devendra Kumar Rai, Joint Secretary, Ministry of Communications
>   URL : https://www.dot.gov.in/static/uploads/2026/02/88f0ac8c74eb6f6907934d17d0015ab5.pdf
> 
> [...]

Late notification.

Applied to master in wens/wireless-regdb.git, thanks!

[1/1] wireless-regdb: Update regulatory rules for India (IN) on 6GHz
      https://git.kernel.org/wens/wireless-regdb/c/5db6ce73e616

Best regards,
-- 
Chen-Yu Tsai <wens@kernel.org>

^ permalink raw reply

* Re: [PATCH v2] wireless-regdb: Add regulatory info for CEPT countries FO, GI, IM, SM and VA listed by WiFi Alliance
From: Chen-Yu Tsai @ 2026-03-23  5:13 UTC (permalink / raw)
  To: Ping-Ke Shih; +Cc: linux-wireless, wireless-regdb
In-Reply-To: <20260319023538.7707-1-pkshih@gmail.com>

On Thu, 19 Mar 2026 10:35:38 +0800, Ping-Ke Shih wrote:
> In commit 5a8ced5ad313 ("wireless-regdb: Update regulatory info for CEPT
> countries for 6GHz listed by WiFi Alliance"), the following are skipped as
> they do not have corresponding entries in the database yet.
> 
>      - Faroe Islands (FO)
>      - Gibraltar (GI)
>      - Isle of Man (IM)
>      - San Marino (SM)
>      - Holy See (Vatican City State) (VA)
> 
> [...]

Applied to master in wens/wireless-regdb.git, thanks!

[1/1] wireless-regdb: Add regulatory info for CEPT countries FO, GI, IM, SM and VA listed by WiFi Alliance
      https://git.kernel.org/wens/wireless-regdb/c/3ceaf3bc0a3d

Best regards,
-- 
Chen-Yu Tsai <wens@kernel.org>


^ permalink raw reply

* Re: [PATCH] wireless-regdb: Port RSA signing to cryptography lib
From: Chen-Yu Tsai @ 2026-03-23  5:07 UTC (permalink / raw)
  To: Bastian Germann; +Cc: linux-wireless, wireless-regdb
In-Reply-To: <20260125212622.28370-1-bage@debian.org>

On Mon, Jan 26, 2026 at 5:26 AM Bastian Germann <bage@debian.org> wrote:
>
> Port the RSA signing from the deprecated M2Crypto library to the
> cryptography library.
>
> M2Crypto is no longer actively maintained. The cryptography library is
> the recommended replacement, offering better maintenance.
>
> Remove unused hashlib import.
>
> Signed-off-by: Bastian Germann <bage@debian.org>

Sorry for getting to this just now. I somehow accidentally archived it,
so I ended up taking Ben Hutching's patch instead.


Thanks
ChenYu

> ---
>  db2bin.py | 25 ++++++++++++++++---------
>  1 file changed, 16 insertions(+), 9 deletions(-)
>
> diff --git a/db2bin.py b/db2bin.py
> index 29ae313..a4fa3e5 100755
> --- a/db2bin.py
> +++ b/db2bin.py
> @@ -2,7 +2,6 @@
>
>  from io import BytesIO, open
>  import struct
> -import hashlib
>  from dbparse import DBParser
>  import sys
>
> @@ -125,19 +124,27 @@ if len(sys.argv) > 3:
>      # Load RSA only now so people can use this script
>      # without having those libraries installed to verify
>      # their SQL changes
> -    from M2Crypto import RSA
> +    from cryptography.hazmat.primitives import hashes, serialization
> +    from cryptography.hazmat.primitives.asymmetric import padding
> +
> +    # load the private key
> +    with open(sys.argv[3], 'rb') as key_file:
> +        key = serialization.load_pem_private_key(key_file.read(), password=None)
>
>      # determine signature length
> -    key = RSA.load_key(sys.argv[3])
> -    hash = hashlib.sha1()
> -    hash.update(output.getvalue())
> -    sig = key.sign(hash.digest())
> +    sig = key.sign(
> +        output.getvalue(),
> +        padding.PKCS1v15(),
> +        hashes.SHA1()
> +    )
>      # write it to file
>      siglen.set(len(sig))
>      # sign again
> -    hash = hashlib.sha1()
> -    hash.update(output.getvalue())
> -    sig = key.sign(hash.digest())
> +    sig = key.sign(
> +        output.getvalue(),
> +        padding.PKCS1v15(),
> +        hashes.SHA1()
> +    )
>
>      output.write(sig)
>  else:
>

^ permalink raw reply

* Re: [PATCH] wireless-regdb: Update Australia (AU) 6 GHz band to 6585 MHz
From: Chen-Yu Tsai @ 2026-03-23  5:07 UTC (permalink / raw)
  To: bruce; +Cc: linux-wireless, wireless-regdb
In-Reply-To: <C9C14F68-BFFF-4BF6-A8AA-60E5EB445F4E@fitzsimons.org>

On Sun, Dec 7, 2025 at 5:06 PM <bruce@fitzsimons.org> wrote:
>
>
> Extend the 6 GHz band allocation for Australia from 5925-6425 MHz
> to 5925-6585 MHz, adding 160 MHz of additional spectrum.
>
> This change reflects the Australian Radiofrequency Spectrum Plan
> Variation 2025 (No. 1), which took effect on 1 October 2025.
>
> References:
> - ACMA Outcomes Paper: Future use of the upper 6 GHz band (December 2024)
>   https://www.acma.gov.au/sites/default/files/2024-12/Outcomes%20paper%20-%20Future%20use%20of%20the%20upper%206%20GHz%20band_0.pdf
>
> Signed-off-by: Bruce Fitzsimons <bruce@fitzsimons.org>

Sorry for getting to this just now. I somehow accidentally archived it.

Thanks for the patch. I believe it was already covered by a previous
change updating all the rules for AU.

> ---
> db.txt | 3 ++-
> 1 file changed, 2 insertions(+), 1 deletion(-)
>
> diff --git a/db.txt b/db.txt
> index fdc2c13..7eac515 100644
> --- a/db.txt
> +++ b/db.txt
> @@ -159,7 +159,8 @@ country AU: DFS-ETSI
> (5850 - 5875 @ 20), (25 mW), AUTO-BW
>
> # Item 63AA (25 mW if outdoors)
> - (5925 - 6425 @ 160), (250 mW), NO-OUTDOOR
> + # Extended to 6585 MHz as of 1 October 2025
> + (5925 - 6585 @ 160), (250 mW), NO-OUTDOOR
>
> # Item 65
> (57000 - 71000 @ 2160), (20000 mW), NO-OUTDOOR
> --
> 2.43.0

^ permalink raw reply

* [PATCH rtw-next 7/7] wifi: rtw89: 8922d: add set channel of RF part
From: Ping-Ke Shih @ 2026-03-23  3:25 UTC (permalink / raw)
  To: linux-wireless
In-Reply-To: <20260323032556.19825-1-pkshih@realtek.com>

The set channel of RF part is to configure channel and bandwidth on a
register. The function to encode channel and bandwidth into register
value will be implemented by coming patch.

Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
---
 drivers/net/wireless/realtek/rtw89/rtw8922d.c |  1 +
 .../net/wireless/realtek/rtw89/rtw8922d_rfk.c | 33 +++++++++++++++++++
 .../net/wireless/realtek/rtw89/rtw8922d_rfk.h | 14 ++++++++
 3 files changed, 48 insertions(+)
 create mode 100644 drivers/net/wireless/realtek/rtw89/rtw8922d_rfk.c
 create mode 100644 drivers/net/wireless/realtek/rtw89/rtw8922d_rfk.h

diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922d.c b/drivers/net/wireless/realtek/rtw89/rtw8922d.c
index f6b36b90dec8..137e98b8883e 100644
--- a/drivers/net/wireless/realtek/rtw89/rtw8922d.c
+++ b/drivers/net/wireless/realtek/rtw89/rtw8922d.c
@@ -8,6 +8,7 @@
 #include "phy.h"
 #include "reg.h"
 #include "rtw8922d.h"
+#include "rtw8922d_rfk.h"
 #include "util.h"
 
 #define RTW8922D_FW_FORMAT_MAX 0
diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922d_rfk.c b/drivers/net/wireless/realtek/rtw89/rtw8922d_rfk.c
new file mode 100644
index 000000000000..6b35d196cb81
--- /dev/null
+++ b/drivers/net/wireless/realtek/rtw89/rtw8922d_rfk.c
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+/* Copyright(c) 2026  Realtek Corporation
+ */
+
+#include "phy.h"
+#include "reg.h"
+#include "rtw8922d.h"
+#include "rtw8922d_rfk.h"
+
+static
+void rtw8922d_ctl_band_ch_bw(struct rtw89_dev *rtwdev, enum rtw89_phy_idx phy,
+			     const struct rtw89_chan *chan)
+{
+	u8 synpath;
+	u32 rf18;
+
+	synpath = rtw89_phy_get_syn_sel(rtwdev, phy);
+	rf18 = rtw89_chip_chan_to_rf18_val(rtwdev, chan);
+
+	rtw89_write_rf(rtwdev, synpath, RR_RSV1, RFREG_MASK, 0x0);
+	rtw89_write_rf(rtwdev, synpath, RR_MOD, RFREG_MASK, 0x30000);
+	rtw89_write_rf(rtwdev, synpath, RR_CFGCH, RFREG_MASK, rf18);
+	fsleep(400);
+	rtw89_write_rf(rtwdev, synpath, RR_RSV1, RFREG_MASK, 0x1);
+	rtw89_write_rf(rtwdev, synpath, RR_CFGCH_V1, RFREG_MASK, rf18);
+}
+
+void rtw8922d_set_channel_rf(struct rtw89_dev *rtwdev,
+			     const struct rtw89_chan *chan,
+			     enum rtw89_phy_idx phy_idx)
+{
+	rtw8922d_ctl_band_ch_bw(rtwdev, phy_idx, chan);
+}
diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922d_rfk.h b/drivers/net/wireless/realtek/rtw89/rtw8922d_rfk.h
new file mode 100644
index 000000000000..03af1f0497ac
--- /dev/null
+++ b/drivers/net/wireless/realtek/rtw89/rtw8922d_rfk.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
+/* Copyright(c) 2026  Realtek Corporation
+ */
+
+#ifndef __RTW89_8922D_RFK_H__
+#define __RTW89_8922D_RFK_H__
+
+#include "core.h"
+
+void rtw8922d_set_channel_rf(struct rtw89_dev *rtwdev,
+			     const struct rtw89_chan *chan,
+			     enum rtw89_phy_idx phy_idx);
+
+#endif
-- 
2.25.1


^ permalink raw reply related

* [PATCH rtw-next 6/7] wifi: rtw89: 8922d: add set channel of BB part
From: Ping-Ke Shih @ 2026-03-23  3:25 UTC (permalink / raw)
  To: linux-wireless
In-Reply-To: <20260323032556.19825-1-pkshih@realtek.com>

The set channel of BB part is the main part, which according to channel
and bandwidth to configure CCK SCO, RX gain of LNA and TIA programmed
in efuse for CCK and OFDM rate, and spur elimination of CSI and NBI tones.

Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
---
 drivers/net/wireless/realtek/rtw89/core.h     |   2 +
 drivers/net/wireless/realtek/rtw89/reg.h      |  64 +-
 drivers/net/wireless/realtek/rtw89/rtw8922d.c | 820 ++++++++++++++++++
 3 files changed, 885 insertions(+), 1 deletion(-)

diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h
index cde46ed21d32..696a1d92d62b 100644
--- a/drivers/net/wireless/realtek/rtw89/core.h
+++ b/drivers/net/wireless/realtek/rtw89/core.h
@@ -5933,6 +5933,8 @@ struct rtw89_phy_efuse_gain {
 	s8 offset2[RF_PATH_MAX][RTW89_GAIN_OFFSET_NR]; /* S(8, 0) */
 	s8 offset_base[RTW89_PHY_NUM]; /* S(8, 4) */
 	s8 rssi_base[RTW89_PHY_NUM]; /* S(8, 4) */
+	s8 ref_gain_base[RTW89_PHY_NUM]; /* S(8, 2) */
+	s8 cck_rpl_base[RTW89_PHY_NUM]; /* S(8, 0) */
 	s8 comp[RF_PATH_MAX][RTW89_SUBBAND_NR]; /* S(8, 0) */
 };
 
diff --git a/drivers/net/wireless/realtek/rtw89/reg.h b/drivers/net/wireless/realtek/rtw89/reg.h
index 2195f576facc..5d284f310069 100644
--- a/drivers/net/wireless/realtek/rtw89/reg.h
+++ b/drivers/net/wireless/realtek/rtw89/reg.h
@@ -8775,6 +8775,7 @@
 #define B_P0_HW_ANTSW_DIS_BY_GNT_BT BIT(12)
 #define B_P0_TRSW_TX_EXTEND GENMASK(3, 0)
 #define R_MAC_PIN_SEL 0x0734
+#define R_MAC_PIN_SEL_BE4 0x20734
 #define B_CH_IDX_SEG0 GENMASK(23, 16)
 #define R_PLCP_HISTOGRAM 0x0738
 #define R_PLCP_HISTOGRAM_BE_V1 0x20738
@@ -10461,6 +10462,13 @@
 #define B_SW_SI_DATA_DAT_BE4 GENMASK(19, 0)
 #define R_SW_SI_READ_ADDR_BE4 0x20378
 #define B_SW_SI_READ_ADDR_BE4 GENMASK(10, 0)
+#define R_RXBW67_BE4 0x2040C
+#define B_RXBW6_BE4 GENMASK(22, 20)
+#define B_RXBW7_BE4 GENMASK(25, 23)
+#define R_RXBW_BE4 0x20410
+#define B_RXBW_BE4 GENMASK(29, 27)
+#define R_ENABLE_CCK0_BE4 0x20700
+#define B_ENABLE_CCK0_BE4 BIT(5)
 #define R_EDCCA_RPT_SEL_BE4 0x20780
 #define R_EDCCA_RPT_SEL_BE4_C1 0x21780
 #define B_EDCCA_RPT_SEL_BE4_MSK 0xE0000
@@ -10486,21 +10494,75 @@
 #define B_IFS_T3_HIS_BE4 GENMASK(15, 0)
 #define B_IFS_T4_HIS_BE4 GENMASK(31, 16)
 
+#define R_TXPWR_RSTB0_BE4 0x2250C
+#define B_TXPWR_RSTB0_BE4 BIT(16)
+#define R_TXPWR_RSTB1_BE4 0x2260C
+#define B_TXPWR_RSTB1_BE4 BIT(16)
+
+#define R_OFDM_OFST_P0_BE4 0x240C8
+#define B_OFDM_OFST_P0_BE4 GENMASK(31, 24)
 #define R_PATH0_RXIDX_INIT_BE4 0x24108
 #define B_PATH0_RXIDX_INIT_BE4 GENMASK(29, 25)
 #define R_PATH0_LNA_INIT_BE4 0x24158
 #define B_PATH0_LNA_INIT_IDX_BE4 GENMASK(14, 12)
+#define R_BAND_SEL0_BE4 0x24160
+#define B_BAND_SEL0_BE4 BIT(26)
 #define R_PATH0_TIA_INIT_BE4 0x24168
 #define B_PATH0_TIA_INIT_IDX_BE4 BIT(18)
+#define R_OFDM_RPL_BIAS_P0_BE4 0x2420C
+#define B_OFDM_RPL_BIAS_P0_BE4 GENMASK(11, 2)
+#define R_OFDM_OFST_P1_BE4 0x244C8
+#define B_OFDM_OFST_P1_BE4 GENMASK(31, 24)
 #define R_PATH1_RXIDX_INIT_BE4 0x24508
 #define B_PATH1_RXIDX_INIT_BE4 GENMASK(29, 25)
 #define R_PATH1_LNA_INIT_BE4 0x24558
 #define B_PATH1_LNA_INIT_IDX_BE4 GENMASK(14, 12)
+#define R_BAND_SEL1_BE4 0x24560
+#define B_BAND_SEL1_BE4 BIT(26)
 #define R_PATH1_TIA_INIT_BE4 0x24568
 #define B_PATH1_TIA_INIT_IDX_BE4 BIT(18)
+#define R_OFDM_RPL_BIAS_P1_BE4 0x2460C
+#define B_OFDM_RPL_BIAS_P1_BE4 GENMASK(11, 2)
 #define R_TX_CFR_MANUAL_EN_BE4 0x2483C
 #define B_TX_CFR_MANUAL_EN_BE4_M BIT(30)
-
+#define R_PCOEFF0_BE4 0x24880
+#define B_PCOEFF01_BE4 GENMASK(23, 0)
+#define R_PCOEFF2_BE4 0x24884
+#define B_PCOEFF23_BE4 GENMASK(23, 0)
+#define R_PCOEFF4_BE4 0x24888
+#define B_PCOEFF45_BE4 GENMASK(23, 0)
+#define R_PCOEFF6_BE4 0x2488C
+#define B_PCOEFF67_BE4 GENMASK(23, 0)
+#define R_PCOEFF8_BE4 0x24890
+#define B_PCOEFF89_BE4 GENMASK(23, 0)
+#define R_PCOEFF10_BE4 0x24894
+#define B_PCOEFF10_BE4 GENMASK(23, 0)
+#define R_PCOEFF12_BE4 0x24898
+#define B_PCOEFF12_BE4 GENMASK(23, 0)
+#define R_PCOEFF14_BE4 0x2489C
+#define B_PCOEFF14_BE4 GENMASK(23, 0)
+#define R_BW_BE4 0x24EE4
+#define B_BW_BE4 GENMASK(6, 4)
+#define B_PRISB_BE4 GENMASK(3, 0)
+#define R_FC0_BE4 0x24EE8
+#define B_FC0_BE4 GENMASK(12, 0)
+#define R_FC0_INV_BE4 0x24EF4
+#define B_FC0_INV_BE4 GENMASK(15, 0)
+
+#define R_CCK_RPL_OFST_BE4 0x26084
+#define B_CCK_RPL_OFST_BE4 GENMASK(7, 0)
+#define R_BK_FC0_INV_BE4 0x2608C
+#define B_BK_FC0_INV_BE4 GENMASK(18, 0)
+#define R_CCK_FC0_INV_BE4 0x26090
+#define B_CCK_FC0_INV_BE4 GENMASK(18, 0)
+#define R_GAIN_BIAS_BE4 0x260A0
+#define B_GAIN_BIAS_BW20_BE4 GENMASK(11, 6)
+#define B_GAIN_BIAS_BW40_BE4 GENMASK(17, 12)
+#define R_AWGN_DET_BE4 0x2668C
+#define B_AWGN_DET_BE4 GENMASK(17, 9)
+#define R_CSI_WGT_BE4 0x26770
+#define B_CSI_WGT_EN_BE4 BIT(0)
+#define B_CSI_WGT_IDX_BE4 GENMASK(31, 20)
 #define R_CHINFO_OPT_BE4 0x267C8
 #define B_CHINFO_OPT_BE4 GENMASK(14, 13)
 #define R_CHINFO_NX_BE4 0x267D0
diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922d.c b/drivers/net/wireless/realtek/rtw89/rtw8922d.c
index a341734ef54d..f6b36b90dec8 100644
--- a/drivers/net/wireless/realtek/rtw89/rtw8922d.c
+++ b/drivers/net/wireless/realtek/rtw89/rtw8922d.c
@@ -8,6 +8,7 @@
 #include "phy.h"
 #include "reg.h"
 #include "rtw8922d.h"
+#include "util.h"
 
 #define RTW8922D_FW_FORMAT_MAX 0
 #define RTW8922D_FW_BASENAME "rtw89/rtw8922d_fw"
@@ -927,6 +928,825 @@ static void rtw8922d_set_channel_mac(struct rtw89_dev *rtwdev,
 	rtw89_write32_mask(rtwdev, reg, B_BE_SIFS_MACTXEN_TB_T1_DOT05US_MASK, sifs);
 }
 
+static const u32 rtw8922d_sco_barker_threshold[14] = {
+	0x1fe4f, 0x1ff5e, 0x2006c, 0x2017b, 0x2028a, 0x20399, 0x204a8, 0x205b6,
+	0x206c5, 0x207d4, 0x208e3, 0x209f2, 0x20b00, 0x20d8a
+};
+
+static const u32 rtw8922d_sco_cck_threshold[14] = {
+	0x2bdac, 0x2bf21, 0x2c095, 0x2c209, 0x2c37e, 0x2c4f2, 0x2c666, 0x2c7db,
+	0x2c94f, 0x2cac3, 0x2cc38, 0x2cdac, 0x2cf21, 0x2d29e
+};
+
+static int rtw8922d_ctrl_sco_cck(struct rtw89_dev *rtwdev,
+				 u8 primary_ch, enum rtw89_bandwidth bw,
+				 enum rtw89_phy_idx phy_idx)
+{
+	u8 ch_element;
+
+	if (primary_ch >= 14)
+		return -EINVAL;
+
+	ch_element = primary_ch - 1;
+
+	rtw89_phy_write32_idx(rtwdev, R_BK_FC0_INV_BE4, B_BK_FC0_INV_BE4,
+			      rtw8922d_sco_barker_threshold[ch_element],
+			      phy_idx);
+	rtw89_phy_write32_idx(rtwdev, R_CCK_FC0_INV_BE4, B_CCK_FC0_INV_BE4,
+			      rtw8922d_sco_cck_threshold[ch_element],
+			      phy_idx);
+
+	return 0;
+}
+
+static void rtw8922d_ctrl_ch_core(struct rtw89_dev *rtwdev,
+				  const struct rtw89_chan *chan,
+				  enum rtw89_phy_idx phy_idx)
+{
+	u16 central_freq = chan->freq;
+	u16 sco;
+
+	if (chan->band_type == RTW89_BAND_2G) {
+		rtw89_phy_write32_idx(rtwdev, R_BAND_SEL0_BE4, B_BAND_SEL0_BE4,
+				      1, phy_idx);
+		rtw89_phy_write32_idx(rtwdev, R_BAND_SEL1_BE4, B_BAND_SEL1_BE4,
+				      1, phy_idx);
+		rtw89_phy_write32_idx(rtwdev, R_ENABLE_CCK0_BE4, B_ENABLE_CCK0_BE4,
+				      1, phy_idx);
+	} else {
+		rtw89_phy_write32_idx(rtwdev, R_BAND_SEL0_BE4, B_BAND_SEL0_BE4,
+				      0, phy_idx);
+		rtw89_phy_write32_idx(rtwdev, R_BAND_SEL1_BE4, B_BAND_SEL1_BE4,
+				      0, phy_idx);
+		rtw89_phy_write32_idx(rtwdev, R_ENABLE_CCK0_BE4, B_ENABLE_CCK0_BE4,
+				      0, phy_idx);
+	}
+
+	rtw89_phy_write32_idx(rtwdev, R_FC0_BE4, B_FC0_BE4, central_freq, phy_idx);
+
+	sco = phy_div((BIT(0) << 27) + (central_freq / 2), central_freq);
+	rtw89_phy_write32_idx(rtwdev, R_FC0_INV_BE4, B_FC0_INV_BE4, sco, phy_idx);
+}
+
+struct rtw8922d_bb_gain {
+	u32 gain_g[BB_PATH_NUM_8922D];
+	u32 gain_a[BB_PATH_NUM_8922D];
+	u32 gain_g_mask;
+	u32 gain_a_mask;
+};
+
+static const struct rtw89_reg_def rpl_comp_bw160[RTW89_BW20_SC_160M] = {
+	{ .addr = 0x241E8, .mask = 0xFF00},
+	{ .addr = 0x241E8, .mask = 0xFF0000},
+	{ .addr = 0x241E8, .mask = 0xFF000000},
+	{ .addr = 0x241EC, .mask = 0xFF},
+	{ .addr = 0x241EC, .mask = 0xFF00},
+	{ .addr = 0x241EC, .mask = 0xFF0000},
+	{ .addr = 0x241EC, .mask = 0xFF000000},
+	{ .addr = 0x241F0, .mask = 0xFF}
+};
+
+static const struct rtw89_reg_def rpl_comp_bw80[RTW89_BW20_SC_80M] = {
+	{ .addr = 0x241F4, .mask = 0xFF},
+	{ .addr = 0x241F4, .mask = 0xFF00},
+	{ .addr = 0x241F4, .mask = 0xFF0000},
+	{ .addr = 0x241F4, .mask = 0xFF000000}
+};
+
+static const struct rtw89_reg_def rpl_comp_bw40[RTW89_BW20_SC_40M] = {
+	{ .addr = 0x241F0, .mask = 0xFF0000},
+	{ .addr = 0x241F0, .mask = 0xFF000000}
+};
+
+static const struct rtw89_reg_def rpl_comp_bw20[RTW89_BW20_SC_20M] = {
+	{ .addr = 0x241F0, .mask = 0xFF00}
+};
+
+static const struct rtw8922d_bb_gain bb_gain_lna[LNA_GAIN_NUM] = {
+	{ .gain_g = {0x2409C, 0x2449C}, .gain_a = {0x2406C, 0x2446C},
+	  .gain_g_mask = 0xFF00, .gain_a_mask = 0xFF},
+	{ .gain_g = {0x2409C, 0x2449C}, .gain_a = {0x2406C, 0x2446C},
+	  .gain_g_mask = 0xFF000000, .gain_a_mask = 0xFF0000},
+	{ .gain_g = {0x240A0, 0x244A0}, .gain_a = {0x24070, 0x24470},
+	  .gain_g_mask = 0xFF00, .gain_a_mask = 0xFF},
+	{ .gain_g = {0x240A0, 0x244A0}, .gain_a = {0x24070, 0x24470},
+	  .gain_g_mask = 0xFF000000, .gain_a_mask = 0xFF0000},
+	{ .gain_g = {0x240A4, 0x244A4}, .gain_a = {0x24074, 0x24474},
+	  .gain_g_mask = 0xFF00, .gain_a_mask = 0xFF},
+	{ .gain_g = {0x240A4, 0x244A4}, .gain_a = {0x24074, 0x24474},
+	  .gain_g_mask = 0xFF000000, .gain_a_mask = 0xFF0000},
+	{ .gain_g = {0x240A8, 0x244A8}, .gain_a = {0x24078, 0x24478},
+	  .gain_g_mask = 0xFF00, .gain_a_mask = 0xFF},
+};
+
+static const struct rtw8922d_bb_gain bb_gain_tia[TIA_GAIN_NUM] = {
+	{ .gain_g = {0x24054, 0x24454}, .gain_a = {0x24054, 0x24454},
+	  .gain_g_mask = 0x7FC0000, .gain_a_mask = 0x1FF},
+	{ .gain_g = {0x24058, 0x24458}, .gain_a = {0x24054, 0x24454},
+	  .gain_g_mask = 0x1FF, .gain_a_mask = 0x3FE00 },
+};
+
+static const struct rtw8922d_bb_gain bb_op1db_lna[LNA_GAIN_NUM] = {
+	{ .gain_g = {0x240AC, 0x244AC}, .gain_a = {0x24078, 0x24478},
+	  .gain_g_mask = 0xFF00, .gain_a_mask = 0xFF000000},
+	{ .gain_g = {0x240AC, 0x244AC}, .gain_a = {0x2407C, 0x2447C},
+	  .gain_g_mask = 0xFF0000, .gain_a_mask = 0xFF},
+	{ .gain_g = {0x240AC, 0x244AC}, .gain_a = {0x2407C, 0x2447C},
+	  .gain_g_mask = 0xFF000000, .gain_a_mask = 0xFF00},
+	{ .gain_g = {0x240B0, 0x244B0}, .gain_a = {0x2407C, 0x2447C},
+	  .gain_g_mask = 0xFF, .gain_a_mask = 0xFF0000},
+	{ .gain_g = {0x240B0, 0x244B0}, .gain_a = {0x2407C, 0x2447C},
+	  .gain_g_mask = 0xFF00, .gain_a_mask = 0xFF000000},
+	{ .gain_g = {0x240B0, 0x244B0}, .gain_a = {0x24080, 0x24480},
+	  .gain_g_mask = 0xFF0000, .gain_a_mask = 0xFF},
+	{ .gain_g = {0x240B0, 0x244B0}, .gain_a = {0x24080, 0x24480},
+	  .gain_g_mask = 0xFF000000, .gain_a_mask = 0xFF00},
+};
+
+static const struct rtw8922d_bb_gain bb_op1db_tia_lna[TIA_LNA_OP1DB_NUM] = {
+	{ .gain_g = {0x240B4, 0x244B4}, .gain_a = {0x24080, 0x24480},
+	  .gain_g_mask = 0xFF0000, .gain_a_mask = 0xFF000000},
+	{ .gain_g = {0x240B4, 0x244B4}, .gain_a = {0x24084, 0x24484},
+	  .gain_g_mask = 0xFF000000, .gain_a_mask = 0xFF},
+	{ .gain_g = {0x240B8, 0x244B8}, .gain_a = {0x24084, 0x24484},
+	  .gain_g_mask = 0xFF, .gain_a_mask = 0xFF00},
+	{ .gain_g = {0x240B8, 0x244B8}, .gain_a = {0x24084, 0x24484},
+	  .gain_g_mask = 0xFF00, .gain_a_mask = 0xFF0000},
+	{ .gain_g = {0x240B8, 0x244B8}, .gain_a = {0x24084, 0x24484},
+	  .gain_g_mask = 0xFF0000, .gain_a_mask = 0xFF000000},
+	{ .gain_g = {0x240B8, 0x244B8}, .gain_a = {0x24088, 0x24488},
+	  .gain_g_mask = 0xFF000000, .gain_a_mask = 0xFF},
+	{ .gain_g = {0x240BC, 0x244BC}, .gain_a = {0x24088, 0x24488},
+	  .gain_g_mask = 0xFF, .gain_a_mask = 0xFF00},
+	{ .gain_g = {0x240BC, 0x244BC}, .gain_a = {0x24088, 0x24488},
+	  .gain_g_mask = 0xFF00, .gain_a_mask = 0xFF0000},
+};
+
+static void rtw8922d_set_rpl_gain(struct rtw89_dev *rtwdev,
+				  const struct rtw89_chan *chan,
+				  enum rtw89_rf_path path,
+				  enum rtw89_phy_idx phy_idx)
+{
+	const struct rtw89_phy_bb_gain_info_be *gain = &rtwdev->bb_gain.be;
+	u8 gain_band = rtw89_subband_to_gain_band_be(chan->subband_type);
+	u32 reg_path_ofst = 0;
+	u32 mask;
+	s32 val;
+	u32 reg;
+	int i;
+
+	if (path == RF_PATH_B)
+		reg_path_ofst = 0x400;
+
+	for (i = 0; i < RTW89_BW20_SC_160M; i++) {
+		reg = rpl_comp_bw160[i].addr | reg_path_ofst;
+		mask = rpl_comp_bw160[i].mask;
+		val = gain->rpl_ofst_160[gain_band][path][i];
+		rtw89_phy_write32_idx(rtwdev, reg, mask, val, phy_idx);
+	}
+
+	for (i = 0; i < RTW89_BW20_SC_80M; i++) {
+		reg = rpl_comp_bw80[i].addr | reg_path_ofst;
+		mask = rpl_comp_bw80[i].mask;
+		val = gain->rpl_ofst_80[gain_band][path][i];
+		rtw89_phy_write32_idx(rtwdev, reg, mask, val, phy_idx);
+	}
+
+	for (i = 0; i < RTW89_BW20_SC_40M; i++) {
+		reg = rpl_comp_bw40[i].addr | reg_path_ofst;
+		mask = rpl_comp_bw40[i].mask;
+		val = gain->rpl_ofst_40[gain_band][path][i];
+		rtw89_phy_write32_idx(rtwdev, reg, mask, val, phy_idx);
+	}
+
+	for (i = 0; i < RTW89_BW20_SC_20M; i++) {
+		reg = rpl_comp_bw20[i].addr | reg_path_ofst;
+		mask = rpl_comp_bw20[i].mask;
+		val = gain->rpl_ofst_20[gain_band][path][i];
+		rtw89_phy_write32_idx(rtwdev, reg, mask, val, phy_idx);
+	}
+}
+
+static void rtw8922d_set_lna_tia_gain(struct rtw89_dev *rtwdev,
+				      const struct rtw89_chan *chan,
+				      enum rtw89_rf_path path,
+				      enum rtw89_phy_idx phy_idx)
+{
+	const struct rtw89_phy_bb_gain_info_be *gain = &rtwdev->bb_gain.be;
+	u8 gain_band = rtw89_subband_to_gain_band_be(chan->subband_type);
+	enum rtw89_phy_bb_bw_be bw_type;
+	u32 mask;
+	s32 val;
+	u32 reg;
+	int i;
+
+	bw_type = chan->band_width <= RTW89_CHANNEL_WIDTH_40 ?
+		  RTW89_BB_BW_20_40 : RTW89_BB_BW_80_160_320;
+
+	for (i = 0; i < LNA_GAIN_NUM; i++) {
+		if (chan->band_type == RTW89_BAND_2G) {
+			reg = bb_gain_lna[i].gain_g[path];
+			mask = bb_gain_lna[i].gain_g_mask;
+		} else {
+			reg = bb_gain_lna[i].gain_a[path];
+			mask = bb_gain_lna[i].gain_a_mask;
+		}
+		val = gain->lna_gain[gain_band][bw_type][path][i];
+		rtw89_phy_write32_idx(rtwdev, reg, mask, val, phy_idx);
+	}
+
+	for (i = 0; i < TIA_GAIN_NUM; i++) {
+		if (chan->band_type == RTW89_BAND_2G) {
+			reg = bb_gain_tia[i].gain_g[path];
+			mask = bb_gain_tia[i].gain_g_mask;
+		} else {
+			reg = bb_gain_tia[i].gain_a[path];
+			mask = bb_gain_tia[i].gain_a_mask;
+		}
+		val = gain->tia_gain[gain_band][bw_type][path][i];
+		rtw89_phy_write32_idx(rtwdev, reg, mask, val, phy_idx);
+	}
+}
+
+static void rtw8922d_set_op1db(struct rtw89_dev *rtwdev,
+			       const struct rtw89_chan *chan,
+			       enum rtw89_rf_path path,
+			       enum rtw89_phy_idx phy_idx)
+{
+	const struct rtw89_phy_bb_gain_info_be *gain = &rtwdev->bb_gain.be;
+	u8 gain_band = rtw89_subband_to_gain_band_be(chan->subband_type);
+	enum rtw89_phy_bb_bw_be bw_type;
+	u32 mask;
+	s32 val;
+	u32 reg;
+	int i;
+
+	bw_type = chan->band_width <= RTW89_CHANNEL_WIDTH_40 ?
+		  RTW89_BB_BW_20_40 : RTW89_BB_BW_80_160_320;
+
+	for (i = 0; i < LNA_GAIN_NUM; i++) {
+		if (chan->band_type == RTW89_BAND_2G) {
+			reg = bb_op1db_lna[i].gain_g[path];
+			mask = bb_op1db_lna[i].gain_g_mask;
+		} else {
+			reg = bb_op1db_lna[i].gain_a[path];
+			mask = bb_op1db_lna[i].gain_a_mask;
+		}
+		val = gain->lna_op1db[gain_band][bw_type][path][i];
+		rtw89_phy_write32_idx(rtwdev, reg, mask, val, phy_idx);
+	}
+
+	for (i = 0; i < TIA_LNA_OP1DB_NUM; i++) {
+		if (chan->band_type == RTW89_BAND_2G) {
+			reg = bb_op1db_tia_lna[i].gain_g[path];
+			mask = bb_op1db_tia_lna[i].gain_g_mask;
+		} else {
+			reg = bb_op1db_tia_lna[i].gain_a[path];
+			mask = bb_op1db_tia_lna[i].gain_a_mask;
+		}
+		val = gain->tia_lna_op1db[gain_band][bw_type][path][i];
+		rtw89_phy_write32_idx(rtwdev, reg, mask, val, phy_idx);
+	}
+}
+
+static void rtw8922d_set_gain(struct rtw89_dev *rtwdev,
+			      const struct rtw89_chan *chan,
+			      enum rtw89_rf_path path,
+			      enum rtw89_phy_idx phy_idx)
+{
+	rtw8922d_set_rpl_gain(rtwdev, chan, path, phy_idx);
+	rtw8922d_set_lna_tia_gain(rtwdev, chan, path, phy_idx);
+	rtw8922d_set_op1db(rtwdev, chan, path, phy_idx);
+}
+
+static s8 rtw8922d_get_rx_gain_by_chan(struct rtw89_dev *rtwdev,
+				       const struct rtw89_chan *chan,
+				       enum rtw89_rf_path path, bool is_cck)
+{
+	struct rtw89_phy_efuse_gain *gain = &rtwdev->efuse_gain;
+	enum rtw89_gain_offset band;
+	u8 fc_ch = chan->channel;
+	s8 normal_efuse = 0;
+
+	if (path > RF_PATH_B)
+		return 0;
+
+	if (is_cck) {
+		if (fc_ch >= 1 && fc_ch <= 7)
+			return gain->offset[path][RTW89_GAIN_OFFSET_2G_CCK];
+		else if (fc_ch >= 8 && fc_ch <= 14)
+			return gain->offset2[path][RTW89_GAIN_OFFSET_2G_CCK];
+
+		return 0;
+	}
+
+	band = rtw89_subband_to_gain_offset_band_of_ofdm(chan->subband_type);
+
+	if (band == RTW89_GAIN_OFFSET_2G_OFDM) {
+		if (fc_ch >= 1 && fc_ch <= 7)
+			normal_efuse = gain->offset[path][band];
+		else if (fc_ch >= 8 && fc_ch <= 14)
+			normal_efuse = gain->offset2[path][band];
+	} else if (band == RTW89_GAIN_OFFSET_5G_LOW) {
+		if (fc_ch == 50)
+			normal_efuse = (gain->offset[path][band] + gain->offset2[path][band]) >> 1;
+		else if (fc_ch >= 36 && fc_ch <= 48)
+			normal_efuse = gain->offset[path][band];
+		else if (fc_ch >= 52 && fc_ch <= 64)
+			normal_efuse = gain->offset2[path][band];
+
+	} else if (band == RTW89_GAIN_OFFSET_5G_MID) {
+		if (fc_ch == 122)
+			normal_efuse = (gain->offset[path][band] + gain->offset2[path][band]) >> 1;
+		else if (fc_ch >= 100 && fc_ch <= 120)
+			normal_efuse = gain->offset[path][band];
+		else if (fc_ch >= 124 && fc_ch <= 144)
+			normal_efuse = gain->offset2[path][band];
+	} else if (band == RTW89_GAIN_OFFSET_5G_HIGH) {
+		if (fc_ch == 163)
+			normal_efuse = (gain->offset[path][band] + gain->offset2[path][band]) >> 1;
+		else if (fc_ch >= 149 && fc_ch <= 161)
+			normal_efuse = gain->offset[path][band];
+		else if (fc_ch >= 165 && fc_ch <= 177)
+			normal_efuse = gain->offset2[path][band];
+	} else if (band == RTW89_GAIN_OFFSET_6G_L0) {
+		if (fc_ch == 15)
+			normal_efuse = (gain->offset[path][band] + gain->offset2[path][band]) >> 1;
+		else if (fc_ch >= 1 && fc_ch <= 13)
+			normal_efuse = gain->offset[path][band];
+		else if (fc_ch >= 17 && fc_ch <= 29)
+			normal_efuse = gain->offset2[path][band];
+	} else if (band == RTW89_GAIN_OFFSET_6G_L1) {
+		if (fc_ch == 47)
+			normal_efuse = (gain->offset[path][band] + gain->offset2[path][band]) >> 1;
+		else if (fc_ch >= 33 && fc_ch <= 45)
+			normal_efuse = gain->offset[path][band];
+		else if (fc_ch >= 49 && fc_ch <= 61)
+			normal_efuse = gain->offset2[path][band];
+	} else if (band == RTW89_GAIN_OFFSET_6G_M0) {
+		if (fc_ch == 79)
+			normal_efuse = (gain->offset[path][band] + gain->offset2[path][band]) >> 1;
+		else if (fc_ch >= 65 && fc_ch <= 77)
+			normal_efuse = gain->offset[path][band];
+		else if (fc_ch >= 81 && fc_ch <= 93)
+			normal_efuse = gain->offset2[path][band];
+	} else if (band == RTW89_GAIN_OFFSET_6G_M1) {
+		if (fc_ch == 111)
+			normal_efuse = (gain->offset[path][band] + gain->offset2[path][band]) >> 1;
+		else if (fc_ch >= 97 && fc_ch <= 109)
+			normal_efuse = gain->offset[path][band];
+		else if (fc_ch >= 113 && fc_ch <= 125)
+			normal_efuse = gain->offset2[path][band];
+	} else if (band == RTW89_GAIN_OFFSET_6G_H0) {
+		if (fc_ch == 143)
+			normal_efuse = (gain->offset[path][band] + gain->offset2[path][band]) >> 1;
+		else if (fc_ch >= 129 && fc_ch <= 141)
+			normal_efuse = gain->offset[path][band];
+		else if (fc_ch >= 145 && fc_ch <= 157)
+			normal_efuse = gain->offset2[path][band];
+	} else if (band == RTW89_GAIN_OFFSET_6G_H1) {
+		if (fc_ch == 175)
+			normal_efuse = (gain->offset[path][band] + gain->offset2[path][band]) >> 1;
+		else if (fc_ch >= 161 && fc_ch <= 173)
+			normal_efuse = gain->offset[path][band];
+		else if (fc_ch >= 177 && fc_ch <= 189)
+			normal_efuse = gain->offset2[path][band];
+	} else if (band == RTW89_GAIN_OFFSET_6G_UH0) {
+		if (fc_ch == 207)
+			normal_efuse = (gain->offset[path][band] + gain->offset2[path][band]) >> 1;
+		else if (fc_ch >= 193 && fc_ch <= 205)
+			normal_efuse = gain->offset[path][band];
+		else if (fc_ch >= 209 && fc_ch <= 221)
+			normal_efuse = gain->offset2[path][band];
+	} else if (band == RTW89_GAIN_OFFSET_6G_UH1) {
+		if (fc_ch == 239)
+			normal_efuse = (gain->offset[path][band] + gain->offset2[path][band]) >> 1;
+		else if (fc_ch >= 225 && fc_ch <= 237)
+			normal_efuse = gain->offset[path][band];
+		else if (fc_ch >= 241 && fc_ch <= 253)
+			normal_efuse = gain->offset2[path][band];
+	} else {
+		normal_efuse = gain->offset[path][band];
+	}
+
+	return normal_efuse;
+}
+
+static void rtw8922d_calc_rx_gain_normal_cck(struct rtw89_dev *rtwdev,
+					     const struct rtw89_chan *chan,
+					     enum rtw89_rf_path path,
+					     enum rtw89_phy_idx phy_idx,
+					     struct rtw89_phy_calc_efuse_gain *calc)
+{
+	struct rtw89_phy_efuse_gain *gain = &rtwdev->efuse_gain;
+	s8 rx_gain_offset;
+
+	rx_gain_offset = -rtw8922d_get_rx_gain_by_chan(rtwdev, chan, path, true);
+
+	if (chan->band_width == RTW89_CHANNEL_WIDTH_40)
+		rx_gain_offset += (3 << 2); /* compensate RPL loss of 3dB */
+
+	calc->cck_mean_gain_bias = (rx_gain_offset & 0x3) << 1;
+	calc->cck_rpl_ofst = (rx_gain_offset >> 2) + gain->cck_rpl_base[phy_idx];
+}
+
+static void rtw8922d_set_rx_gain_normal_cck(struct rtw89_dev *rtwdev,
+					    const struct rtw89_chan *chan,
+					    enum rtw89_rf_path path,
+					    enum rtw89_phy_idx phy_idx)
+{
+	struct rtw89_phy_calc_efuse_gain calc = {};
+
+	rtw8922d_calc_rx_gain_normal_cck(rtwdev, chan, path, phy_idx, &calc);
+
+	rtw89_phy_write32_idx(rtwdev, R_GAIN_BIAS_BE4, B_GAIN_BIAS_BW20_BE4,
+			      calc.cck_mean_gain_bias, phy_idx);
+	rtw89_phy_write32_idx(rtwdev, R_GAIN_BIAS_BE4, B_GAIN_BIAS_BW40_BE4,
+			      calc.cck_mean_gain_bias, phy_idx);
+	rtw89_phy_write32_idx(rtwdev, R_CCK_RPL_OFST_BE4, B_CCK_RPL_OFST_BE4,
+			      calc.cck_rpl_ofst, phy_idx);
+}
+
+static void rtw8922d_calc_rx_gain_normal_ofdm(struct rtw89_dev *rtwdev,
+					      const struct rtw89_chan *chan,
+					      enum rtw89_rf_path path,
+					      enum rtw89_phy_idx phy_idx,
+					      struct rtw89_phy_calc_efuse_gain *calc)
+{
+	struct rtw89_phy_efuse_gain *gain = &rtwdev->efuse_gain;
+	s8 rx_gain_offset;
+
+	rx_gain_offset = rtw8922d_get_rx_gain_by_chan(rtwdev, chan, path, false);
+	calc->rssi_ofst = (rx_gain_offset + gain->ref_gain_base[phy_idx]) & 0xff;
+}
+
+static void rtw8922d_set_rx_gain_normal_ofdm(struct rtw89_dev *rtwdev,
+					     const struct rtw89_chan *chan,
+					     enum rtw89_rf_path path,
+					     enum rtw89_phy_idx phy_idx)
+{
+	static const u32 rssi_ofst_addr[2] = {R_OFDM_OFST_P0_BE4, R_OFDM_OFST_P1_BE4};
+	static const u32 rssi_ofst_addr_m[2] = {B_OFDM_OFST_P0_BE4, B_OFDM_OFST_P1_BE4};
+	static const u32 rpl_bias_comp[2] = {R_OFDM_RPL_BIAS_P0_BE4, R_OFDM_RPL_BIAS_P1_BE4};
+	static const u32 rpl_bias_comp_m[2] = {B_OFDM_RPL_BIAS_P0_BE4, B_OFDM_RPL_BIAS_P1_BE4};
+	struct rtw89_phy_calc_efuse_gain calc = {};
+
+	rtw8922d_calc_rx_gain_normal_ofdm(rtwdev, chan, path, phy_idx, &calc);
+
+	rtw89_phy_write32_idx(rtwdev, rssi_ofst_addr[path], rssi_ofst_addr_m[path],
+			      calc.rssi_ofst, phy_idx);
+	rtw89_phy_write32_idx(rtwdev, rpl_bias_comp[path], rpl_bias_comp_m[path], 0, phy_idx);
+}
+
+static void rtw8922d_set_rx_gain_normal(struct rtw89_dev *rtwdev,
+					const struct rtw89_chan *chan,
+					enum rtw89_rf_path path,
+					enum rtw89_phy_idx phy_idx)
+{
+	struct rtw89_phy_efuse_gain *gain = &rtwdev->efuse_gain;
+
+	if (!gain->offset_valid)
+		return;
+
+	if (chan->band_type == RTW89_BAND_2G)
+		rtw8922d_set_rx_gain_normal_cck(rtwdev, chan, path, phy_idx);
+
+	rtw8922d_set_rx_gain_normal_ofdm(rtwdev, chan, path, phy_idx);
+}
+
+static void rtw8922d_set_cck_parameters(struct rtw89_dev *rtwdev,
+					const struct rtw89_chan *chan,
+					enum rtw89_phy_idx phy_idx)
+{
+	u8 regd = rtw89_regd_get(rtwdev, chan->band_type);
+	u8 central_ch = chan->channel;
+
+	if (central_ch == 14) {
+		rtw89_phy_write32_idx(rtwdev, R_PCOEFF0_BE4, B_PCOEFF01_BE4, 0x3b13ff, phy_idx);
+		rtw89_phy_write32_idx(rtwdev, R_PCOEFF2_BE4, B_PCOEFF23_BE4, 0x1c42de, phy_idx);
+		rtw89_phy_write32_idx(rtwdev, R_PCOEFF4_BE4, B_PCOEFF45_BE4, 0xfdb0ad, phy_idx);
+		rtw89_phy_write32_idx(rtwdev, R_PCOEFF6_BE4, B_PCOEFF67_BE4, 0xf60f6e, phy_idx);
+		rtw89_phy_write32_idx(rtwdev, R_PCOEFF8_BE4, B_PCOEFF89_BE4, 0xfd8f92, phy_idx);
+		rtw89_phy_write32_idx(rtwdev, R_PCOEFF10_BE4, B_PCOEFF10_BE4, 0x2d011, phy_idx);
+		rtw89_phy_write32_idx(rtwdev, R_PCOEFF12_BE4, B_PCOEFF12_BE4, 0x1c02c, phy_idx);
+		rtw89_phy_write32_idx(rtwdev, R_PCOEFF14_BE4, B_PCOEFF14_BE4, 0xfff00a, phy_idx);
+
+		return;
+	}
+
+	if (regd == RTW89_FCC) {
+		rtw89_phy_write32_idx(rtwdev, R_PCOEFF0_BE4, B_PCOEFF01_BE4, 0x39A3BC, phy_idx);
+		rtw89_phy_write32_idx(rtwdev, R_PCOEFF2_BE4, B_PCOEFF23_BE4, 0x2AA339, phy_idx);
+		rtw89_phy_write32_idx(rtwdev, R_PCOEFF4_BE4, B_PCOEFF45_BE4, 0x15B202, phy_idx);
+		rtw89_phy_write32_idx(rtwdev, R_PCOEFF6_BE4, B_PCOEFF67_BE4, 0x0550C7, phy_idx);
+		rtw89_phy_write32_idx(rtwdev, R_PCOEFF8_BE4, B_PCOEFF89_BE4, 0xfe0009, phy_idx);
+		rtw89_phy_write32_idx(rtwdev, R_PCOEFF10_BE4, B_PCOEFF10_BE4, 0xfd7fd3, phy_idx);
+		rtw89_phy_write32_idx(rtwdev, R_PCOEFF12_BE4, B_PCOEFF12_BE4, 0xfeffe2, phy_idx);
+		rtw89_phy_write32_idx(rtwdev, R_PCOEFF14_BE4, B_PCOEFF14_BE4, 0xffeff8, phy_idx);
+	} else {
+		rtw89_phy_write32_idx(rtwdev, R_PCOEFF0_BE4, B_PCOEFF01_BE4, 0x3d23ff, phy_idx);
+		rtw89_phy_write32_idx(rtwdev, R_PCOEFF2_BE4, B_PCOEFF23_BE4, 0x29b354, phy_idx);
+		rtw89_phy_write32_idx(rtwdev, R_PCOEFF4_BE4, B_PCOEFF45_BE4, 0xfc1c8, phy_idx);
+		rtw89_phy_write32_idx(rtwdev, R_PCOEFF6_BE4, B_PCOEFF67_BE4, 0xfdb053, phy_idx);
+		rtw89_phy_write32_idx(rtwdev, R_PCOEFF8_BE4, B_PCOEFF89_BE4, 0xf86f9a, phy_idx);
+		rtw89_phy_write32_idx(rtwdev, R_PCOEFF10_BE4, B_PCOEFF10_BE4, 0xfaef92, phy_idx);
+		rtw89_phy_write32_idx(rtwdev, R_PCOEFF12_BE4, B_PCOEFF12_BE4, 0xfe5fcc, phy_idx);
+		rtw89_phy_write32_idx(rtwdev, R_PCOEFF14_BE4, B_PCOEFF14_BE4, 0xffdff5, phy_idx);
+	}
+}
+
+static void rtw8922d_ctrl_ch(struct rtw89_dev *rtwdev,
+			     const struct rtw89_chan *chan,
+			     enum rtw89_phy_idx phy_idx)
+{
+	u16 central_freq = chan->freq;
+	u8 band = chan->band_type;
+	u8 chan_idx;
+
+	if (!central_freq) {
+		rtw89_warn(rtwdev, "Invalid central_freq\n");
+		return;
+	}
+
+	rtw8922d_ctrl_ch_core(rtwdev, chan, phy_idx);
+
+	chan_idx = rtw89_encode_chan_idx(rtwdev, chan->primary_channel, band);
+	rtw89_phy_write32_idx(rtwdev, R_MAC_PIN_SEL_BE4, B_CH_IDX_SEG0, chan_idx, phy_idx);
+
+	rtw8922d_set_gain(rtwdev, chan, RF_PATH_A, phy_idx);
+	rtw8922d_set_gain(rtwdev, chan, RF_PATH_B, phy_idx);
+
+	rtw8922d_set_rx_gain_normal(rtwdev, chan, RF_PATH_A, phy_idx);
+	rtw8922d_set_rx_gain_normal(rtwdev, chan, RF_PATH_B, phy_idx);
+
+	if (band == RTW89_BAND_2G)
+		rtw8922d_set_cck_parameters(rtwdev, chan, phy_idx);
+}
+
+static void rtw8922d_ctrl_bw(struct rtw89_dev *rtwdev, u8 pri_sb, u8 bw,
+			     enum rtw89_phy_idx phy_idx)
+{
+	switch (bw) {
+	default:
+	case RTW89_CHANNEL_WIDTH_20:
+		rtw89_phy_write32_idx(rtwdev, R_BW_BE4, B_BW_BE4, 0x0, phy_idx);
+		rtw89_phy_write32_idx(rtwdev, R_BW_BE4, B_PRISB_BE4, 0x0, phy_idx);
+		rtw89_phy_write32_idx(rtwdev, R_RXBW_BE4, B_RXBW_BE4, 0x2, phy_idx);
+		rtw89_phy_write32_idx(rtwdev, R_RXBW67_BE4, B_RXBW6_BE4, 0x2, phy_idx);
+		rtw89_phy_write32_idx(rtwdev, R_RXBW67_BE4, B_RXBW7_BE4, 0x2, phy_idx);
+		break;
+	case RTW89_CHANNEL_WIDTH_40:
+		rtw89_phy_write32_idx(rtwdev, R_BW_BE4, B_BW_BE4, 0x1, phy_idx);
+		rtw89_phy_write32_idx(rtwdev, R_BW_BE4, B_PRISB_BE4, pri_sb, phy_idx);
+		rtw89_phy_write32_idx(rtwdev, R_RXBW_BE4, B_RXBW_BE4, 0x3, phy_idx);
+		rtw89_phy_write32_idx(rtwdev, R_RXBW67_BE4, B_RXBW6_BE4, 0x3, phy_idx);
+		rtw89_phy_write32_idx(rtwdev, R_RXBW67_BE4, B_RXBW7_BE4, 0x3, phy_idx);
+		break;
+	case RTW89_CHANNEL_WIDTH_80:
+		rtw89_phy_write32_idx(rtwdev, R_BW_BE4, B_BW_BE4, 0x2, phy_idx);
+		rtw89_phy_write32_idx(rtwdev, R_BW_BE4, B_PRISB_BE4, pri_sb, phy_idx);
+		rtw89_phy_write32_idx(rtwdev, R_RXBW_BE4, B_RXBW_BE4, 0x4, phy_idx);
+		rtw89_phy_write32_idx(rtwdev, R_RXBW67_BE4, B_RXBW6_BE4, 0x4, phy_idx);
+		rtw89_phy_write32_idx(rtwdev, R_RXBW67_BE4, B_RXBW7_BE4, 0x4, phy_idx);
+		break;
+	case RTW89_CHANNEL_WIDTH_160:
+		rtw89_phy_write32_idx(rtwdev, R_BW_BE4, B_BW_BE4, 0x3, phy_idx);
+		rtw89_phy_write32_idx(rtwdev, R_BW_BE4, B_PRISB_BE4, pri_sb, phy_idx);
+		rtw89_phy_write32_idx(rtwdev, R_RXBW_BE4, B_RXBW_BE4, 0x5, phy_idx);
+		rtw89_phy_write32_idx(rtwdev, R_RXBW67_BE4, B_RXBW6_BE4, 0x5, phy_idx);
+		rtw89_phy_write32_idx(rtwdev, R_RXBW67_BE4, B_RXBW7_BE4, 0x5, phy_idx);
+		break;
+	}
+}
+
+static const u16 spur_nbi_a[] = {6400};
+static const u16 spur_csi[] = {6400};
+
+static u32 rtw8922d_spur_freq(struct rtw89_dev *rtwdev, const struct rtw89_chan *chan,
+			      bool nbi_or_csi, enum rtw89_rf_path path)
+{
+	static const u16 cbw[RTW89_CHANNEL_WIDTH_ORDINARY_NUM] = {
+		20, 40, 80, 160, 320,
+	};
+	u16 freq_lower, freq_upper, freq;
+	const u16 *spur_freq;
+	int spur_freq_nr, i;
+
+	if (rtwdev->hal.aid != RTL8922D_AID7060)
+		return 0;
+
+	if (nbi_or_csi && path == RF_PATH_A) {
+		spur_freq = spur_nbi_a;
+		spur_freq_nr = ARRAY_SIZE(spur_nbi_a);
+	} else if (!nbi_or_csi) {
+		spur_freq = spur_csi;
+		spur_freq_nr = ARRAY_SIZE(spur_csi);
+	} else {
+		return 0;
+	}
+
+	if (chan->band_width >= RTW89_CHANNEL_WIDTH_ORDINARY_NUM)
+		return 0;
+
+	freq_lower = chan->freq - cbw[chan->band_width] / 2;
+	freq_upper = chan->freq + cbw[chan->band_width] / 2;
+
+	for (i = 0; i < spur_freq_nr; i++) {
+		freq = spur_freq[i];
+
+		if (freq >= freq_lower && freq <= freq_upper)
+			return freq;
+	}
+
+	return 0;
+}
+
+#define CARRIER_SPACING_312_5 312500 /* 312.5 kHz */
+#define CARRIER_SPACING_78_125 78125 /* 78.125 kHz */
+#define MAX_TONE_NUM 2048
+
+static void rtw8922d_set_csi_tone_idx(struct rtw89_dev *rtwdev,
+				      const struct rtw89_chan *chan,
+				      enum rtw89_phy_idx phy_idx)
+{
+	s32 freq_diff, csi_idx, csi_tone_idx;
+	u32 spur_freq;
+
+	spur_freq = rtw8922d_spur_freq(rtwdev, chan, false, RF_PATH_AB);
+	if (spur_freq == 0) {
+		rtw89_phy_write32_idx(rtwdev, R_CSI_WGT_BE4, B_CSI_WGT_EN_BE4,
+				      0, phy_idx);
+		return;
+	}
+
+	freq_diff = (spur_freq - chan->freq) * 1000000;
+	csi_idx = s32_div_u32_round_closest(freq_diff, CARRIER_SPACING_78_125);
+	s32_div_u32_round_down(csi_idx, MAX_TONE_NUM, &csi_tone_idx);
+
+	rtw89_phy_write32_idx(rtwdev, R_CSI_WGT_BE4, B_CSI_WGT_IDX_BE4,
+			      csi_tone_idx, phy_idx);
+	rtw89_phy_write32_idx(rtwdev, R_CSI_WGT_BE4, B_CSI_WGT_EN_BE4, 1, phy_idx);
+}
+
+static const struct rtw89_nbi_reg_def rtw8922d_nbi_reg_def[] = {
+	[RF_PATH_A] = {
+		.notch1_idx = {0x241A0, 0xFF},
+		.notch1_frac_idx = {0x241A0, 0xC00},
+		.notch1_en = {0x241A0, 0x1000},
+		.notch2_idx = {0x241AC, 0xFF},
+		.notch2_frac_idx = {0x241AC, 0xC00},
+		.notch2_en = {0x241AC, 0x1000},
+	},
+	[RF_PATH_B] = {
+		.notch1_idx = {0x245A0, 0xFF},
+		.notch1_frac_idx = {0x241A0, 0xC00},
+		.notch1_en = {0x245A0, 0x1000},
+		.notch2_idx = {0x245AC, 0xFF},
+		.notch2_frac_idx = {0x245AC, 0xC00},
+		.notch2_en = {0x245AC, 0x1000},
+	},
+};
+
+static void rtw8922d_set_nbi_tone_idx(struct rtw89_dev *rtwdev,
+				      const struct rtw89_chan *chan,
+				      enum rtw89_rf_path path,
+				      enum rtw89_phy_idx phy_idx)
+{
+	const struct rtw89_nbi_reg_def *nbi = &rtw8922d_nbi_reg_def[path];
+	s32 nbi_frac_idx, nbi_frac_tone_idx;
+	s32 nbi_idx, nbi_tone_idx;
+	bool notch2_chk = false;
+	u32 spur_freq, fc;
+	s32 freq_diff;
+
+	spur_freq = rtw8922d_spur_freq(rtwdev, chan, true, path);
+	if (spur_freq == 0) {
+		rtw89_phy_write32_idx(rtwdev, nbi->notch1_en.addr,
+				      nbi->notch1_en.mask, 0, phy_idx);
+		rtw89_phy_write32_idx(rtwdev, nbi->notch2_en.addr,
+				      nbi->notch2_en.mask, 0, phy_idx);
+		return;
+	}
+
+	fc = chan->freq;
+	if (chan->band_width == RTW89_CHANNEL_WIDTH_160) {
+		fc = (spur_freq > fc) ? fc + 40 : fc - 40;
+		if ((fc > spur_freq &&
+		     chan->channel < chan->primary_channel) ||
+		    (fc < spur_freq &&
+		     chan->channel > chan->primary_channel))
+			notch2_chk = true;
+	}
+
+	freq_diff = (spur_freq - fc) * 1000000;
+	nbi_idx = s32_div_u32_round_down(freq_diff, CARRIER_SPACING_312_5,
+					 &nbi_frac_idx);
+
+	if (chan->band_width == RTW89_CHANNEL_WIDTH_20) {
+		s32_div_u32_round_down(nbi_idx + 32, 64, &nbi_tone_idx);
+	} else {
+		u16 tone_para = (chan->band_width == RTW89_CHANNEL_WIDTH_40) ?
+				128 : 256;
+
+		s32_div_u32_round_down(nbi_idx, tone_para, &nbi_tone_idx);
+	}
+	nbi_frac_tone_idx =
+		s32_div_u32_round_closest(nbi_frac_idx, CARRIER_SPACING_78_125);
+
+	if (chan->band_width == RTW89_CHANNEL_WIDTH_160 && notch2_chk) {
+		rtw89_phy_write32_idx(rtwdev, nbi->notch2_idx.addr,
+				      nbi->notch2_idx.mask, nbi_tone_idx, phy_idx);
+		rtw89_phy_write32_idx(rtwdev, nbi->notch2_frac_idx.addr,
+				      nbi->notch2_frac_idx.mask, nbi_frac_tone_idx,
+				      phy_idx);
+		rtw89_phy_write32_idx(rtwdev, nbi->notch2_en.addr,
+				      nbi->notch2_en.mask, 0, phy_idx);
+		rtw89_phy_write32_idx(rtwdev, nbi->notch2_en.addr,
+				      nbi->notch2_en.mask, 1, phy_idx);
+		rtw89_phy_write32_idx(rtwdev, nbi->notch1_en.addr,
+				      nbi->notch1_en.mask, 0, phy_idx);
+	} else {
+		rtw89_phy_write32_idx(rtwdev, nbi->notch1_idx.addr,
+				      nbi->notch1_idx.mask, nbi_tone_idx, phy_idx);
+		rtw89_phy_write32_idx(rtwdev, nbi->notch1_frac_idx.addr,
+				      nbi->notch1_frac_idx.mask, nbi_frac_tone_idx,
+				      phy_idx);
+		rtw89_phy_write32_idx(rtwdev, nbi->notch1_en.addr,
+				      nbi->notch1_en.mask, 0, phy_idx);
+		rtw89_phy_write32_idx(rtwdev, nbi->notch1_en.addr,
+				      nbi->notch1_en.mask, 1, phy_idx);
+		rtw89_phy_write32_idx(rtwdev, nbi->notch2_en.addr,
+				      nbi->notch2_en.mask, 0, phy_idx);
+	}
+}
+
+static void rtw8922d_spur_elimination(struct rtw89_dev *rtwdev,
+				      const struct rtw89_chan *chan,
+				      enum rtw89_phy_idx phy_idx)
+{
+	rtw8922d_set_csi_tone_idx(rtwdev, chan, phy_idx);
+	rtw8922d_set_nbi_tone_idx(rtwdev, chan, RF_PATH_A, phy_idx);
+	rtw8922d_set_nbi_tone_idx(rtwdev, chan, RF_PATH_B, phy_idx);
+}
+
+static void rtw8922d_tssi_reset(struct rtw89_dev *rtwdev,
+				enum rtw89_rf_path path,
+				enum rtw89_phy_idx phy_idx)
+{
+	if (rtwdev->mlo_dbcc_mode == MLO_1_PLUS_1_1RF) {
+		if (phy_idx == RTW89_PHY_0) {
+			rtw89_phy_write32_mask(rtwdev, R_TXPWR_RSTB0_BE4,
+					       B_TXPWR_RSTB0_BE4, 0x0);
+			rtw89_phy_write32_mask(rtwdev, R_TXPWR_RSTB0_BE4,
+					       B_TXPWR_RSTB0_BE4, 0x1);
+		} else {
+			rtw89_phy_write32_mask(rtwdev, R_TXPWR_RSTB1_BE4,
+					       B_TXPWR_RSTB1_BE4, 0x0);
+			rtw89_phy_write32_mask(rtwdev, R_TXPWR_RSTB1_BE4,
+					       B_TXPWR_RSTB1_BE4, 0x1);
+		}
+	} else {
+		rtw89_phy_write32_mask(rtwdev, R_TXPWR_RSTB0_BE4, B_TXPWR_RSTB0_BE4, 0x0);
+		rtw89_phy_write32_mask(rtwdev, R_TXPWR_RSTB0_BE4, B_TXPWR_RSTB0_BE4, 0x1);
+		rtw89_phy_write32_mask(rtwdev, R_TXPWR_RSTB1_BE4, B_TXPWR_RSTB1_BE4, 0x0);
+		rtw89_phy_write32_mask(rtwdev, R_TXPWR_RSTB1_BE4, B_TXPWR_RSTB1_BE4, 0x1);
+	}
+}
+
+static void rtw8922d_set_channel_bb(struct rtw89_dev *rtwdev,
+				    const struct rtw89_chan *chan,
+				    enum rtw89_phy_idx phy_idx)
+{
+	struct rtw89_hal *hal = &rtwdev->hal;
+	bool cck_en = chan->band_type == RTW89_BAND_2G;
+	u8 pri_sb = chan->pri_sb_idx;
+	u32 val;
+
+	rtw89_phy_bb_wrap_set_rfsi_ct_opt(rtwdev, phy_idx);
+	rtw8922d_ctrl_ch(rtwdev, chan, phy_idx);
+	rtw8922d_ctrl_bw(rtwdev, pri_sb, chan->band_width, phy_idx);
+	rtw89_phy_bb_wrap_set_rfsi_bandedge_ch(rtwdev, chan, phy_idx);
+
+	if (cck_en)
+		rtw8922d_ctrl_sco_cck(rtwdev, chan->primary_channel,
+				      chan->band_width, phy_idx);
+
+	rtw8922d_spur_elimination(rtwdev, chan, phy_idx);
+
+	if (hal->cid == RTL8922D_CID7025) {
+		if (chan->band_width == RTW89_CHANNEL_WIDTH_160)
+			val = 0x1f9;
+		else if (chan->band_width == RTW89_CHANNEL_WIDTH_80)
+			val = 0x1f5;
+		else
+			val = 0x1e2;
+
+		rtw89_phy_write32_idx(rtwdev, R_AWGN_DET_BE4, B_AWGN_DET_BE4, val, phy_idx);
+	}
+
+	rtw8922d_tssi_reset(rtwdev, RF_PATH_AB, phy_idx);
+}
+
 MODULE_FIRMWARE(RTW8922D_MODULE_FIRMWARE);
 MODULE_FIRMWARE(RTW8922DS_MODULE_FIRMWARE);
 MODULE_AUTHOR("Realtek Corporation");
-- 
2.25.1


^ permalink raw reply related

* [PATCH rtw-next 5/7] wifi: rtw89: 8922d: add set channel of MAC part
From: Ping-Ke Shih @ 2026-03-23  3:25 UTC (permalink / raw)
  To: linux-wireless
In-Reply-To: <20260323032556.19825-1-pkshih@realtek.com>

The set channel is a key function to switch to specific operating channel.
For MAC part, configure hardware according to channel bandwidth, and
enable CCK rate for 2GHz band only.

Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
---
 drivers/net/wireless/realtek/rtw89/reg.h      |  1 +
 drivers/net/wireless/realtek/rtw89/rtw8922d.c | 92 +++++++++++++++++++
 2 files changed, 93 insertions(+)

diff --git a/drivers/net/wireless/realtek/rtw89/reg.h b/drivers/net/wireless/realtek/rtw89/reg.h
index 179006c8e499..2195f576facc 100644
--- a/drivers/net/wireless/realtek/rtw89/reg.h
+++ b/drivers/net/wireless/realtek/rtw89/reg.h
@@ -6801,6 +6801,7 @@
 #define R_BE_MUEDCA_EN 0x10370
 #define R_BE_MUEDCA_EN_C1 0x14370
 #define B_BE_SIFS_TIMEOUT_TB_T2_MASK GENMASK(30, 24)
+#define B_BE_SIFS_MACTXEN_TB_T1_DOT05US_MASK GENMASK(23, 16)
 #define B_BE_SIFS_MACTXEN_TB_T1_MASK GENMASK(22, 16)
 #define B_BE_MUEDCA_WMM_SEL BIT(8)
 #define B_BE_SET_MUEDCATIMER_TF_MASK GENMASK(5, 4)
diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922d.c b/drivers/net/wireless/realtek/rtw89/rtw8922d.c
index cbe8e067ae55..a341734ef54d 100644
--- a/drivers/net/wireless/realtek/rtw89/rtw8922d.c
+++ b/drivers/net/wireless/realtek/rtw89/rtw8922d.c
@@ -5,6 +5,7 @@
 #include "debug.h"
 #include "efuse.h"
 #include "mac.h"
+#include "phy.h"
 #include "reg.h"
 #include "rtw8922d.h"
 
@@ -835,6 +836,97 @@ static void rtw8922d_power_trim(struct rtw89_dev *rtwdev)
 	rtw8922d_pad_bias_trim(rtwdev);
 }
 
+static void rtw8922d_set_channel_mac(struct rtw89_dev *rtwdev,
+				     const struct rtw89_chan *chan,
+				     u8 mac_idx)
+{
+	u32 sub_carr = rtw89_mac_reg_by_idx(rtwdev, R_BE_TX_SUB_BAND_VALUE, mac_idx);
+	u32 chk_rate = rtw89_mac_reg_by_idx(rtwdev, R_BE_TXRATE_CHK, mac_idx);
+	u32 rf_mod = rtw89_mac_reg_by_idx(rtwdev, R_BE_WMAC_RFMOD, mac_idx);
+	u8 txsb20 = 0, txsb40 = 0, txsb80 = 0;
+	u8 rf_mod_val, chk_rate_mask, sifs;
+	u32 txsb;
+	u32 reg;
+
+	switch (chan->band_width) {
+	case RTW89_CHANNEL_WIDTH_160:
+		txsb80 = rtw89_phy_get_txsb(rtwdev, chan, RTW89_CHANNEL_WIDTH_80);
+		fallthrough;
+	case RTW89_CHANNEL_WIDTH_80:
+		txsb40 = rtw89_phy_get_txsb(rtwdev, chan, RTW89_CHANNEL_WIDTH_40);
+		fallthrough;
+	case RTW89_CHANNEL_WIDTH_40:
+		txsb20 = rtw89_phy_get_txsb(rtwdev, chan, RTW89_CHANNEL_WIDTH_20);
+		break;
+	default:
+		break;
+	}
+
+	switch (chan->band_width) {
+	case RTW89_CHANNEL_WIDTH_160:
+		rf_mod_val = BE_WMAC_RFMOD_160M;
+		txsb = u32_encode_bits(txsb20, B_BE_TXSB_20M_MASK) |
+		       u32_encode_bits(txsb40, B_BE_TXSB_40M_MASK) |
+		       u32_encode_bits(txsb80, B_BE_TXSB_80M_MASK);
+		break;
+	case RTW89_CHANNEL_WIDTH_80:
+		rf_mod_val = BE_WMAC_RFMOD_80M;
+		txsb = u32_encode_bits(txsb20, B_BE_TXSB_20M_MASK) |
+		       u32_encode_bits(txsb40, B_BE_TXSB_40M_MASK);
+		break;
+	case RTW89_CHANNEL_WIDTH_40:
+		rf_mod_val = BE_WMAC_RFMOD_40M;
+		txsb = u32_encode_bits(txsb20, B_BE_TXSB_20M_MASK);
+		break;
+	case RTW89_CHANNEL_WIDTH_20:
+	default:
+		rf_mod_val = BE_WMAC_RFMOD_20M;
+		txsb = 0;
+		break;
+	}
+
+	if (txsb20 <= BE_PRI20_BITMAP_MAX)
+		txsb |= u32_encode_bits(BIT(txsb20), B_BE_PRI20_BITMAP_MASK);
+
+	rtw89_write8_mask(rtwdev, rf_mod, B_BE_WMAC_RFMOD_MASK, rf_mod_val);
+	rtw89_write32(rtwdev, sub_carr, txsb);
+
+	switch (chan->band_type) {
+	case RTW89_BAND_2G:
+		chk_rate_mask = B_BE_BAND_MODE;
+		break;
+	case RTW89_BAND_5G:
+	case RTW89_BAND_6G:
+		chk_rate_mask = B_BE_CHECK_CCK_EN | B_BE_RTS_LIMIT_IN_OFDM6;
+		break;
+	default:
+		rtw89_warn(rtwdev, "Invalid band_type:%d\n", chan->band_type);
+		return;
+	}
+
+	rtw89_write8_clr(rtwdev, chk_rate, B_BE_BAND_MODE | B_BE_CHECK_CCK_EN |
+					   B_BE_RTS_LIMIT_IN_OFDM6);
+	rtw89_write8_set(rtwdev, chk_rate, chk_rate_mask);
+
+	switch (chan->band_width) {
+	case RTW89_CHANNEL_WIDTH_160:
+		sifs = 0x8C;
+		break;
+	case RTW89_CHANNEL_WIDTH_80:
+		sifs = 0x8A;
+		break;
+	case RTW89_CHANNEL_WIDTH_40:
+		sifs = 0x84;
+		break;
+	case RTW89_CHANNEL_WIDTH_20:
+	default:
+		sifs = 0x82;
+	}
+
+	reg = rtw89_mac_reg_by_idx(rtwdev, R_BE_MUEDCA_EN, mac_idx);
+	rtw89_write32_mask(rtwdev, reg, B_BE_SIFS_MACTXEN_TB_T1_DOT05US_MASK, sifs);
+}
+
 MODULE_FIRMWARE(RTW8922D_MODULE_FIRMWARE);
 MODULE_FIRMWARE(RTW8922DS_MODULE_FIRMWARE);
 MODULE_AUTHOR("Realtek Corporation");
-- 
2.25.1


^ permalink raw reply related

* [PATCH rtw-next 4/7] wifi: rtw89: 8922d: read and configure RF by calibration data from efuse physical map
From: Ping-Ke Shih @ 2026-03-23  3:25 UTC (permalink / raw)
  To: linux-wireless
In-Reply-To: <20260323032556.19825-1-pkshih@realtek.com>

The calibration data is from physical map, including 1) thermal trim to
align output thermal value across chips, and 2) PA bias to transmit
expected power by controller.

Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
---
 drivers/net/wireless/realtek/rtw89/core.h     |   2 +
 drivers/net/wireless/realtek/rtw89/reg.h      |   2 +
 drivers/net/wireless/realtek/rtw89/rtw8922d.c | 205 ++++++++++++++++++
 3 files changed, 209 insertions(+)

diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h
index d0ae3e15253b..cde46ed21d32 100644
--- a/drivers/net/wireless/realtek/rtw89/core.h
+++ b/drivers/net/wireless/realtek/rtw89/core.h
@@ -5595,9 +5595,11 @@ struct rtw89_tssi_info {
 struct rtw89_power_trim_info {
 	bool pg_thermal_trim;
 	bool pg_pa_bias_trim;
+	bool pg_vco_trim;
 	u8 thermal_trim[RF_PATH_MAX];
 	u8 pa_bias_trim[RF_PATH_MAX];
 	u8 pad_bias_trim[RF_PATH_MAX];
+	u8 vco_trim[RF_PATH_MAX];
 };
 
 enum rtw89_regd_func {
diff --git a/drivers/net/wireless/realtek/rtw89/reg.h b/drivers/net/wireless/realtek/rtw89/reg.h
index b6fd7b434de9..179006c8e499 100644
--- a/drivers/net/wireless/realtek/rtw89/reg.h
+++ b/drivers/net/wireless/realtek/rtw89/reg.h
@@ -8517,6 +8517,7 @@
 #define RR_LUTWD0_LB GENMASK(5, 0)
 #define RR_TM 0x42
 #define RR_TM_TRI BIT(19)
+#define RR_TM_TRM GENMASK(17, 11)
 #define RR_TM_VAL_V1 GENMASK(7, 0)
 #define RR_TM_VAL GENMASK(6, 1)
 #define RR_TM2 0x43
@@ -8649,6 +8650,7 @@
 #define RR_LDO 0xb1
 #define RR_LDO_SEL GENMASK(8, 6)
 #define RR_VCO 0xb2
+#define RR_VCO_VAL GENMASK(18, 14)
 #define RR_VCO_SEL GENMASK(9, 8)
 #define RR_VCI 0xb3
 #define RR_VCI_ON BIT(7)
diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922d.c b/drivers/net/wireless/realtek/rtw89/rtw8922d.c
index 0ae34a4f8d79..cbe8e067ae55 100644
--- a/drivers/net/wireless/realtek/rtw89/rtw8922d.c
+++ b/drivers/net/wireless/realtek/rtw89/rtw8922d.c
@@ -630,6 +630,211 @@ static int rtw8922d_read_efuse(struct rtw89_dev *rtwdev, u8 *log_map,
 	}
 }
 
+static void rtw8922d_phycap_parsing_vco_trim(struct rtw89_dev *rtwdev,
+					     u8 *phycap_map)
+{
+	static const u32 vco_trim_addr[RF_PATH_NUM_8922D] = {0x175E, 0x175F};
+	struct rtw89_power_trim_info *info = &rtwdev->pwr_trim;
+	u32 addr = rtwdev->chip->phycap_addr;
+	const u32 vco_check_addr = 0x1700;
+	u8 val;
+
+	val = phycap_map[vco_check_addr - addr];
+	if (val & BIT(1))
+		return;
+
+	info->pg_vco_trim = true;
+
+	info->vco_trim[0] = u8_get_bits(phycap_map[vco_trim_addr[0] - addr], GENMASK(4, 0));
+	info->vco_trim[1] = u8_get_bits(phycap_map[vco_trim_addr[1] - addr], GENMASK(4, 0));
+}
+
+static void rtw8922d_vco_trim(struct rtw89_dev *rtwdev)
+{
+	struct rtw89_power_trim_info *info = &rtwdev->pwr_trim;
+
+	if (!info->pg_vco_trim)
+		return;
+
+	rtw89_write_rf(rtwdev, RF_PATH_A, RR_VCO, RR_VCO_VAL, info->vco_trim[0]);
+	rtw89_write_rf(rtwdev, RF_PATH_B, RR_VCO, RR_VCO_VAL, info->vco_trim[1]);
+}
+
+#define THM_TRIM_POSITIVE_MASK BIT(6)
+#define THM_TRIM_MAGNITUDE_MASK GENMASK(5, 0)
+#define THM_TRIM_MAX (15)
+#define THM_TRIM_MIN (-15)
+
+static void rtw8922d_phycap_parsing_thermal_trim(struct rtw89_dev *rtwdev,
+						 u8 *phycap_map)
+{
+	static const u32 thm_trim_addr[RF_PATH_NUM_8922D] = {0x1706, 0x1732};
+	struct rtw89_power_trim_info *info = &rtwdev->pwr_trim;
+	u32 addr = rtwdev->chip->phycap_addr;
+	bool pg = true;
+	u8 pg_th;
+	s8 val;
+	u8 i;
+
+	for (i = 0; i < RF_PATH_NUM_8922D; i++) {
+		pg_th = phycap_map[thm_trim_addr[i] - addr];
+		if (pg_th == 0xff) {
+			memset(info->thermal_trim, 0, sizeof(info->thermal_trim));
+			pg = false;
+			goto out;
+		}
+
+		val = u8_get_bits(pg_th, THM_TRIM_MAGNITUDE_MASK);
+
+		if (!(pg_th & THM_TRIM_POSITIVE_MASK))
+			val *= -1;
+
+		if (val <= THM_TRIM_MIN || val >= THM_TRIM_MAX) {
+			val = 0;
+			info->thermal_trim[i] = 0;
+		} else {
+			info->thermal_trim[i] = pg_th;
+		}
+
+		rtw89_debug(rtwdev, RTW89_DBG_RFK,
+			    "[THERMAL][TRIM] path=%d thermal_trim=0x%x (%d)\n",
+			    i, pg_th, val);
+	}
+
+out:
+	info->pg_thermal_trim = pg;
+}
+
+static void rtw8922d_thermal_trim(struct rtw89_dev *rtwdev)
+{
+	struct rtw89_power_trim_info *info = &rtwdev->pwr_trim;
+	u8 thermal;
+	int i;
+
+	for (i = 0; i < RF_PATH_NUM_8922D; i++) {
+		thermal = info->pg_thermal_trim ? info->thermal_trim[i] : 0;
+		rtw89_write_rf(rtwdev, i, RR_TM, RR_TM_TRM, thermal & 0x7f);
+	}
+}
+
+static void rtw8922d_phycap_parsing_pa_bias_trim(struct rtw89_dev *rtwdev,
+						 u8 *phycap_map)
+{
+	static const u32 pabias_trim_addr[RF_PATH_NUM_8922D] = {0x1707, 0x1733};
+	static const u32 check_pa_pad_trim_addr = 0x1700;
+	struct rtw89_power_trim_info *info = &rtwdev->pwr_trim;
+	u32 addr = rtwdev->chip->phycap_addr;
+	bool pg = true;
+	u8 val;
+	u8 i;
+
+	val = phycap_map[check_pa_pad_trim_addr - addr];
+	if (val == 0xff) {
+		pg = false;
+		goto out;
+	}
+
+	for (i = 0; i < RF_PATH_NUM_8922D; i++) {
+		info->pa_bias_trim[i] = phycap_map[pabias_trim_addr[i] - addr];
+
+		rtw89_debug(rtwdev, RTW89_DBG_RFK,
+			    "[PA_BIAS][TRIM] path=%d pa_bias_trim=0x%x\n",
+			    i, info->pa_bias_trim[i]);
+	}
+
+out:
+	info->pg_pa_bias_trim = pg;
+}
+
+static void rtw8922d_pa_bias_trim(struct rtw89_dev *rtwdev)
+{
+	struct rtw89_power_trim_info *info = &rtwdev->pwr_trim;
+	u8 pabias_2g, pabias_5g;
+	u8 i;
+
+	if (!info->pg_pa_bias_trim) {
+		rtw89_debug(rtwdev, RTW89_DBG_RFK,
+			    "[PA_BIAS][TRIM] no PG, do nothing\n");
+
+		return;
+	}
+
+	for (i = 0; i < RF_PATH_NUM_8922D; i++) {
+		pabias_2g = FIELD_GET(GENMASK(3, 0), info->pa_bias_trim[i]);
+		pabias_5g = FIELD_GET(GENMASK(7, 4), info->pa_bias_trim[i]);
+
+		rtw89_debug(rtwdev, RTW89_DBG_RFK,
+			    "[PA_BIAS][TRIM] path=%d 2G=0x%x 5G=0x%x\n",
+			    i, pabias_2g, pabias_5g);
+
+		rtw89_write_rf(rtwdev, i, RR_BIASA, RR_BIASA_TXG_V1, pabias_2g);
+		rtw89_write_rf(rtwdev, i, RR_BIASA, RR_BIASA_TXA_V1, pabias_5g);
+	}
+}
+
+static void rtw8922d_phycap_parsing_pad_bias_trim(struct rtw89_dev *rtwdev,
+						  u8 *phycap_map)
+{
+	static const u32 pad_bias_trim_addr[RF_PATH_NUM_8922D] = {0x1708, 0x1734};
+	struct rtw89_power_trim_info *info = &rtwdev->pwr_trim;
+	u32 addr = rtwdev->chip->phycap_addr;
+	u8 i;
+
+	if (!info->pg_pa_bias_trim)
+		return;
+
+	for (i = 0; i < RF_PATH_NUM_8922D; i++) {
+		info->pad_bias_trim[i] = phycap_map[pad_bias_trim_addr[i] - addr];
+
+		rtw89_debug(rtwdev, RTW89_DBG_RFK,
+			    "[PAD_BIAS][TRIM] path=%d pad_bias_trim=0x%x\n",
+			    i, info->pad_bias_trim[i]);
+	}
+}
+
+static void rtw8922d_pad_bias_trim(struct rtw89_dev *rtwdev)
+{
+	struct rtw89_power_trim_info *info = &rtwdev->pwr_trim;
+	u8 pad_bias_2g, pad_bias_5g;
+	u8 i;
+
+	if (!info->pg_pa_bias_trim) {
+		rtw89_debug(rtwdev, RTW89_DBG_RFK,
+			    "[PAD_BIAS][TRIM] no PG, do nothing\n");
+		return;
+	}
+
+	for (i = 0; i < RF_PATH_NUM_8922D; i++) {
+		pad_bias_2g = u8_get_bits(info->pad_bias_trim[i], GENMASK(3, 0));
+		pad_bias_5g = u8_get_bits(info->pad_bias_trim[i], GENMASK(7, 4));
+
+		rtw89_debug(rtwdev, RTW89_DBG_RFK,
+			    "[PAD_BIAS][TRIM] path=%d 2G=0x%x 5G=0x%x\n",
+			    i, pad_bias_2g, pad_bias_5g);
+
+		rtw89_write_rf(rtwdev, i, RR_BIASA, RR_BIASD_TXG_V1, pad_bias_2g);
+		rtw89_write_rf(rtwdev, i, RR_BIASA, RR_BIASD_TXA_V1, pad_bias_5g);
+	}
+}
+
+static int rtw8922d_read_phycap(struct rtw89_dev *rtwdev, u8 *phycap_map)
+{
+	rtw8922d_phycap_parsing_vco_trim(rtwdev, phycap_map);
+	rtw8922d_phycap_parsing_thermal_trim(rtwdev, phycap_map);
+	rtw8922d_phycap_parsing_pa_bias_trim(rtwdev, phycap_map);
+	rtw8922d_phycap_parsing_pad_bias_trim(rtwdev, phycap_map);
+
+	return 0;
+}
+
+static void rtw8922d_power_trim(struct rtw89_dev *rtwdev)
+{
+	rtw8922d_vco_trim(rtwdev);
+	rtw8922d_thermal_trim(rtwdev);
+	rtw8922d_pa_bias_trim(rtwdev);
+	rtw8922d_pad_bias_trim(rtwdev);
+}
+
 MODULE_FIRMWARE(RTW8922D_MODULE_FIRMWARE);
 MODULE_FIRMWARE(RTW8922DS_MODULE_FIRMWARE);
 MODULE_AUTHOR("Realtek Corporation");
-- 
2.25.1


^ permalink raw reply related

* [PATCH rtw-next 3/7] wifi: rtw89: 8922d: define efuse map and read necessary fields
From: Ping-Ke Shih @ 2026-03-23  3:25 UTC (permalink / raw)
  To: linux-wireless
In-Reply-To: <20260323032556.19825-1-pkshih@realtek.com>

Define specific efuse map for RTL8922D, including TSSI, RX gain, MAC
address, RFE type and etc. The additional fields comparing to existing
chips are BT setting (define BT switch GPIO, antenna number and etc) and
gain offset2 (define more fields like existing RX gain offset).

Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
---
 drivers/net/wireless/realtek/rtw89/core.h     |   3 +
 drivers/net/wireless/realtek/rtw89/rtw8922d.c | 162 ++++++++++++++++++
 drivers/net/wireless/realtek/rtw89/rtw8922d.h |  70 ++++++++
 3 files changed, 235 insertions(+)

diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h
index 94e4faf70e12..d0ae3e15253b 100644
--- a/drivers/net/wireless/realtek/rtw89/core.h
+++ b/drivers/net/wireless/realtek/rtw89/core.h
@@ -3562,6 +3562,8 @@ struct rtw89_efuse {
 	u8 rfe_type;
 	char country_code[2];
 	u8 adc_td;
+	u8 bt_setting_2;
+	u8 bt_setting_3;
 };
 
 struct rtw89_phy_rate_pattern {
@@ -5926,6 +5928,7 @@ struct rtw89_phy_efuse_gain {
 	bool offset_valid;
 	bool comp_valid;
 	s8 offset[RF_PATH_MAX][RTW89_GAIN_OFFSET_NR]; /* S(8, 0) */
+	s8 offset2[RF_PATH_MAX][RTW89_GAIN_OFFSET_NR]; /* S(8, 0) */
 	s8 offset_base[RTW89_PHY_NUM]; /* S(8, 4) */
 	s8 rssi_base[RTW89_PHY_NUM]; /* S(8, 4) */
 	s8 comp[RF_PATH_MAX][RTW89_SUBBAND_NR]; /* S(8, 0) */
diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922d.c b/drivers/net/wireless/realtek/rtw89/rtw8922d.c
index 194e2901232b..0ae34a4f8d79 100644
--- a/drivers/net/wireless/realtek/rtw89/rtw8922d.c
+++ b/drivers/net/wireless/realtek/rtw89/rtw8922d.c
@@ -2,6 +2,7 @@
 /* Copyright(c) 2026  Realtek Corporation
  */
 
+#include "debug.h"
 #include "efuse.h"
 #include "mac.h"
 #include "reg.h"
@@ -468,6 +469,167 @@ static int rtw8922d_pwr_off_func(struct rtw89_dev *rtwdev)
 	return 0;
 }
 
+static void rtw8922d_efuse_parsing_tssi(struct rtw89_dev *rtwdev,
+					struct rtw8922d_efuse *map)
+{
+	const struct rtw8922d_tssi_offset_6g * const ofst_6g[] = {
+		&map->path_a_tssi_6g,
+		&map->path_b_tssi_6g,
+	};
+	const struct rtw8922d_tssi_offset * const ofst[] = {
+		&map->path_a_tssi,
+		&map->path_b_tssi,
+	};
+	struct rtw89_tssi_info *tssi = &rtwdev->tssi;
+	u8 i, j;
+
+	tssi->thermal[RF_PATH_A] = map->path_a_therm;
+	tssi->thermal[RF_PATH_B] = map->path_b_therm;
+
+	for (i = 0; i < RF_PATH_NUM_8922D; i++) {
+		memcpy(tssi->tssi_cck[i], ofst[i]->cck_tssi, TSSI_CCK_CH_GROUP_NUM);
+
+		for (j = 0; j < TSSI_CCK_CH_GROUP_NUM; j++)
+			rtw89_debug(rtwdev, RTW89_DBG_TSSI,
+				    "[TSSI][EFUSE] path=%d cck[%d]=0x%x\n",
+				    i, j, tssi->tssi_cck[i][j]);
+
+		memcpy(tssi->tssi_mcs[i], ofst[i]->bw40_tssi,
+		       TSSI_MCS_2G_CH_GROUP_NUM);
+		memcpy(tssi->tssi_mcs[i] + TSSI_MCS_2G_CH_GROUP_NUM,
+		       ofst[i]->bw40_1s_tssi_5g, TSSI_MCS_5G_CH_GROUP_NUM);
+		memcpy(tssi->tssi_6g_mcs[i], ofst_6g[i]->bw40_1s_tssi_6g,
+		       TSSI_MCS_6G_CH_GROUP_NUM);
+
+		for (j = 0; j < TSSI_MCS_CH_GROUP_NUM; j++)
+			rtw89_debug(rtwdev, RTW89_DBG_TSSI,
+				    "[TSSI][EFUSE] path=%d mcs[%d]=0x%x\n",
+				    i, j, tssi->tssi_mcs[i][j]);
+
+		for (j = 0; j < TSSI_MCS_6G_CH_GROUP_NUM; j++)
+			rtw89_debug(rtwdev, RTW89_DBG_TSSI,
+				    "[TSSI][EFUSE] path=%d mcs_6g[%d]=0x%x\n",
+				    i, j, tssi->tssi_6g_mcs[i][j]);
+	}
+}
+
+static void
+__rtw8922d_efuse_parsing_gain_offset(struct rtw89_dev *rtwdev,
+				     s8 offset[RTW89_GAIN_OFFSET_NR],
+				     const s8 *offset_default,
+				     const struct rtw8922d_rx_gain *rx_gain,
+				     const struct rtw8922d_rx_gain_6g *rx_gain_6g)
+{
+	int i;
+	u8 t;
+
+	offset[RTW89_GAIN_OFFSET_2G_CCK] = rx_gain->_2g_cck;
+	offset[RTW89_GAIN_OFFSET_2G_OFDM] = rx_gain->_2g_ofdm;
+	offset[RTW89_GAIN_OFFSET_5G_LOW] = rx_gain->_5g_low;
+	offset[RTW89_GAIN_OFFSET_5G_MID] = rx_gain->_5g_mid;
+	offset[RTW89_GAIN_OFFSET_5G_HIGH] = rx_gain->_5g_high;
+	offset[RTW89_GAIN_OFFSET_6G_L0] = rx_gain_6g->_6g_l0;
+	offset[RTW89_GAIN_OFFSET_6G_L1] = rx_gain_6g->_6g_l1;
+	offset[RTW89_GAIN_OFFSET_6G_M0] = rx_gain_6g->_6g_m0;
+	offset[RTW89_GAIN_OFFSET_6G_M1] = rx_gain_6g->_6g_m1;
+	offset[RTW89_GAIN_OFFSET_6G_H0] = rx_gain_6g->_6g_h0;
+	offset[RTW89_GAIN_OFFSET_6G_H1] = rx_gain_6g->_6g_h1;
+	offset[RTW89_GAIN_OFFSET_6G_UH0] = rx_gain_6g->_6g_uh0;
+	offset[RTW89_GAIN_OFFSET_6G_UH1] = rx_gain_6g->_6g_uh1;
+
+	for (i = 0; i < RTW89_GAIN_OFFSET_NR; i++) {
+		t = offset[i];
+		if (t == 0xff) {
+			if (offset_default) {
+				offset[i] = offset_default[i];
+				continue;
+			}
+			t = 0;
+		}
+
+		/* transform: sign-bit + U(7,2) to S(8,2) */
+		if (t & 0x80)
+			offset[i] = (t ^ 0x7f) + 1;
+		else
+			offset[i] = t;
+	}
+}
+
+static void rtw8922d_efuse_parsing_gain_offset(struct rtw89_dev *rtwdev,
+					       struct rtw8922d_efuse *map)
+{
+	struct rtw89_phy_efuse_gain *gain = &rtwdev->efuse_gain;
+
+	__rtw8922d_efuse_parsing_gain_offset(rtwdev, gain->offset[RF_PATH_A],
+					     NULL,
+					     &map->rx_gain_a, &map->rx_gain_6g_a);
+	__rtw8922d_efuse_parsing_gain_offset(rtwdev, gain->offset[RF_PATH_B],
+					     NULL,
+					     &map->rx_gain_b, &map->rx_gain_6g_b);
+
+	__rtw8922d_efuse_parsing_gain_offset(rtwdev, gain->offset2[RF_PATH_A],
+					     gain->offset[RF_PATH_A],
+					     &map->rx_gain_a_2, &map->rx_gain_6g_a_2);
+	__rtw8922d_efuse_parsing_gain_offset(rtwdev, gain->offset2[RF_PATH_B],
+					     gain->offset[RF_PATH_B],
+					     &map->rx_gain_b_2, &map->rx_gain_6g_b_2);
+
+	gain->offset_valid = true;
+}
+
+static int rtw8922d_read_efuse_pci_sdio(struct rtw89_dev *rtwdev, u8 *log_map)
+{
+	struct rtw89_efuse *efuse = &rtwdev->efuse;
+
+	if (rtwdev->hci.type == RTW89_HCI_TYPE_PCIE)
+		ether_addr_copy(efuse->addr, log_map + 0x4104);
+	else
+		ether_addr_copy(efuse->addr, log_map + 0x001A);
+
+	return 0;
+}
+
+static int rtw8922d_read_efuse_usb(struct rtw89_dev *rtwdev, u8 *log_map)
+{
+	struct rtw89_efuse *efuse = &rtwdev->efuse;
+
+	ether_addr_copy(efuse->addr, log_map + 0x0078);
+
+	return 0;
+}
+
+static int rtw8922d_read_efuse_rf(struct rtw89_dev *rtwdev, u8 *log_map)
+{
+	struct rtw8922d_efuse *map = (struct rtw8922d_efuse *)log_map;
+	struct rtw89_efuse *efuse = &rtwdev->efuse;
+
+	efuse->rfe_type = map->rfe_type;
+	efuse->xtal_cap = map->xtal_k;
+	efuse->country_code[0] = map->country_code[0];
+	efuse->country_code[1] = map->country_code[1];
+	efuse->bt_setting_2 = map->bt_setting_2;
+	efuse->bt_setting_3 = map->bt_setting_3;
+	rtw8922d_efuse_parsing_tssi(rtwdev, map);
+	rtw8922d_efuse_parsing_gain_offset(rtwdev, map);
+
+	return 0;
+}
+
+static int rtw8922d_read_efuse(struct rtw89_dev *rtwdev, u8 *log_map,
+			       enum rtw89_efuse_block block)
+{
+	switch (block) {
+	case RTW89_EFUSE_BLOCK_HCI_DIG_PCIE_SDIO:
+		return rtw8922d_read_efuse_pci_sdio(rtwdev, log_map);
+	case RTW89_EFUSE_BLOCK_HCI_DIG_USB:
+		return rtw8922d_read_efuse_usb(rtwdev, log_map);
+	case RTW89_EFUSE_BLOCK_RF:
+		return rtw8922d_read_efuse_rf(rtwdev, log_map);
+	default:
+		return 0;
+	}
+}
+
 MODULE_FIRMWARE(RTW8922D_MODULE_FIRMWARE);
 MODULE_FIRMWARE(RTW8922DS_MODULE_FIRMWARE);
 MODULE_AUTHOR("Realtek Corporation");
diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922d.h b/drivers/net/wireless/realtek/rtw89/rtw8922d.h
index 7ef3f263274e..a3b98ad6636c 100644
--- a/drivers/net/wireless/realtek/rtw89/rtw8922d.h
+++ b/drivers/net/wireless/realtek/rtw89/rtw8922d.h
@@ -7,4 +7,74 @@
 
 #include "core.h"
 
+#define RF_PATH_NUM_8922D 2
+#define BB_PATH_NUM_8922D 2
+
+struct rtw8922d_tssi_offset {
+	u8 cck_tssi[TSSI_CCK_CH_GROUP_NUM];
+	u8 bw40_tssi[TSSI_MCS_2G_CH_GROUP_NUM];
+	u8 rsvd[7];
+	u8 bw40_1s_tssi_5g[TSSI_MCS_5G_CH_GROUP_NUM];
+	u8 bw_diff_5g[10];
+} __packed;
+
+struct rtw8922d_tssi_offset_6g {
+	u8 bw40_1s_tssi_6g[TSSI_MCS_6G_CH_GROUP_NUM];
+	u8 rsvd[0xa];
+} __packed;
+
+struct rtw8922d_rx_gain {
+	u8 _2g_ofdm;
+	u8 _2g_cck;
+	u8 _5g_low;
+	u8 _5g_mid;
+	u8 _5g_high;
+} __packed;
+
+struct rtw8922d_rx_gain_6g {
+	u8 _6g_l0;
+	u8 _6g_l1;
+	u8 _6g_m0;
+	u8 _6g_m1;
+	u8 _6g_h0;
+	u8 _6g_h1;
+	u8 _6g_uh0;
+	u8 _6g_uh1;
+} __packed;
+
+struct rtw8922d_efuse {
+	u8 country_code[2];
+	u8 rsvd[0xe];
+	struct rtw8922d_tssi_offset path_a_tssi;
+	struct rtw8922d_tssi_offset path_b_tssi;
+	u8 rsvd1[0x54];
+	u8 channel_plan;
+	u8 xtal_k;
+	u8 rsvd2[0x7];
+	u8 board_info;
+	u8 rsvd3[0x8];
+	u8 rfe_type;
+	u8 rsvd4[2];
+	u8 bt_setting_2;
+	u8 bt_setting_3;
+	u8 rsvd4_2;
+	u8 path_a_therm;
+	u8 path_b_therm;
+	u8 rsvd5[0x2];
+	struct rtw8922d_rx_gain rx_gain_a;
+	struct rtw8922d_rx_gain rx_gain_b;
+	u8 rsvd6[0x18];
+	struct rtw8922d_rx_gain rx_gain_a_2;
+	struct rtw8922d_rx_gain rx_gain_b_2;
+	struct rtw8922d_tssi_offset_6g path_a_tssi_6g;
+	struct rtw8922d_tssi_offset_6g path_b_tssi_6g;
+	struct rtw8922d_tssi_offset_6g path_c_tssi_6g;
+	struct rtw8922d_tssi_offset_6g path_d_tssi_6g;
+	struct rtw8922d_rx_gain_6g rx_gain_6g_a;
+	struct rtw8922d_rx_gain_6g rx_gain_6g_b;
+	u8 rsvd7[0x5a];
+	struct rtw8922d_rx_gain_6g rx_gain_6g_a_2;
+	struct rtw8922d_rx_gain_6g rx_gain_6g_b_2;
+} __packed;
+
 #endif
-- 
2.25.1


^ permalink raw reply related

* [PATCH rtw-next 2/7] wifi: rtw89: 8922d: add power on/off functions
From: Ping-Ke Shih @ 2026-03-23  3:25 UTC (permalink / raw)
  To: linux-wireless
In-Reply-To: <20260323032556.19825-1-pkshih@realtek.com>

The power on function is the first entry to power on hardware including
all MAC/BB/RF circuits, and then it becomes possible to do high level
operations, such as WiFi scan, connection.

If connection becomes unavailable, device stays into idle mode, calling
power off function to cut power.

Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
---
 drivers/net/wireless/realtek/rtw89/reg.h      |  63 +++++
 drivers/net/wireless/realtek/rtw89/rtw8922d.c | 237 ++++++++++++++++++
 2 files changed, 300 insertions(+)

diff --git a/drivers/net/wireless/realtek/rtw89/reg.h b/drivers/net/wireless/realtek/rtw89/reg.h
index 2cb35458a822..b6fd7b434de9 100644
--- a/drivers/net/wireless/realtek/rtw89/reg.h
+++ b/drivers/net/wireless/realtek/rtw89/reg.h
@@ -4403,6 +4403,31 @@
 #define B_BE_FS_GPIO17_INT_EN BIT(1)
 #define B_BE_FS_GPIO16_INT_EN BIT(0)
 
+#define R_BE_FWS1ISR 0x019C
+#define B_BE_FS_WL_HW_RADIO_OFF_INT BIT(28)
+#define B_BE_SWRD_BOD_INT BIT(27)
+#define B_BE_HCIDBG_INT BIT(25)
+#define B_BE_FS_RPWM_INT_V1 BIT(24)
+#define B_BE_PCIE_HOTRST BIT(22)
+#define B_BE_PCIE_SER_TIMEOUT_INDIC BIT(21)
+#define B_BE_PCIE_RXI300_SLVTOUT_INDIC BIT(20)
+#define B_BE_AON_PCIE_FLR_INT BIT(19)
+#define B_BE_PCIE_ERR_INDIC BIT(18)
+#define B_BE_SDIO_ERR_INDIC BIT(17)
+#define B_BE_USB_ERR_INDIC BIT(16)
+#define B_BE_FS_GPIO27_INT BIT(11)
+#define B_BE_FS_GPIO26_INT BIT(10)
+#define B_BE_FS_GPIO25_INT BIT(9)
+#define B_BE_FS_GPIO24_INT BIT(8)
+#define B_BE_FS_GPIO23_INT BIT(7)
+#define B_BE_FS_GPIO22_INT BIT(6)
+#define B_BE_FS_GPIO21_INT BIT(5)
+#define B_BE_FS_GPIO20_INT BIT(4)
+#define B_BE_FS_GPIO19_INT BIT(3)
+#define B_BE_FS_GPIO18_INT BIT(2)
+#define B_BE_FS_GPIO17_INT BIT(1)
+#define B_BE_FS_GPIO16_INT BIT(0)
+
 #define R_BE_HIMR0 0x01A0
 #define B_BE_WDT_DATACPU_TIMEOUT_INT_EN BIT(25)
 #define B_BE_HALT_D2H_INT_EN BIT(24)
@@ -4503,6 +4528,44 @@
 #define R_BE_UDM2 0x01F8
 #define B_BE_UDM2_EPC_RA_MASK GENMASK(31, 0)
 
+#define R_BE_SPS_DIG_ON_CTRL1 0x0204
+#define B_BE_SN_N_L_MASK GENMASK(31, 28)
+#define B_BE_SP_N_L_MASK GENMASK(27, 24)
+#define B_BE_SN_P_L_MASK GENMASK(23, 20)
+#define B_BE_SP_P_L_MASK GENMASK(19, 16)
+#define B_BE_VO_DISCHG_PWM_H BIT(15)
+#define B_BE_REG_MODE_PREDRIVER BIT(14)
+#define B_BE_VREFOCP_MASK GENMASK(13, 10)
+#define B_BE_POWOCP_L1 BIT(9)
+#define B_BE_PWM_FORCE BIT(8)
+#define B_BE_PFM_PD_RST BIT(7)
+#define B_BE_VC_PFM_RSTB BIT(6)
+#define B_BE_PFM_IN_SEL BIT(5)
+#define B_BE_VC_RSTB BIT(4)
+#define B_BE_FPWMDELAY BIT(3)
+#define B_BE_ENFPWMDELAY_H BIT(2)
+#define B_BE_REG_MOS_HALF_L BIT(1)
+#define B_BE_CURRENT_SENSE_MOS BIT(0)
+
+#define R_BE_SPS_ANA_ON_CTRL1 0x0224
+#define B_BE_SN_N_L_ANA_MASK GENMASK(31, 28)
+#define B_BE_SP_N_L_ANA_MASK GENMASK(27, 24)
+#define B_BE_SN_P_L_ANA_MASK GENMASK(23, 20)
+#define B_BE_SP_P_L_ANA_MASK GENMASK(19, 16)
+#define B_BE_VO_DISCHG_PWM_H_ANA BIT(15)
+#define B_BE_REG_MODE_PREDRIVER_ANA BIT(14)
+#define B_BE_VREFOCP_ANA_MASK GENMASK(13, 10)
+#define B_BE_POWOCP_L1_ANA BIT(9)
+#define B_BE_PWM_FORCE_ANA BIT(8)
+#define B_BE_PFM_PD_RST_ANA BIT(7)
+#define B_BE_VC_PFM_RSTB_ANA BIT(6)
+#define B_BE_PFM_IN_SEL_ANA BIT(5)
+#define B_BE_VC_RSTB_ANA BIT(4)
+#define B_BE_FPWMDELAY_ANA BIT(3)
+#define B_BE_ENFPWMDELAY_H_ANA BIT(2)
+#define B_BE_REG_MOS_HALF_L_ANA BIT(1)
+#define B_BE_CURRENT_SENSE_MOS_ANA BIT(0)
+
 #define R_BE_AFE_ON_CTRL0 0x0240
 #define B_BE_REG_LPF_R3_3_0_MASK GENMASK(31, 29)
 #define B_BE_REG_LPF_R2_MASK GENMASK(28, 24)
diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922d.c b/drivers/net/wireless/realtek/rtw89/rtw8922d.c
index 6a90ded1b33e..194e2901232b 100644
--- a/drivers/net/wireless/realtek/rtw89/rtw8922d.c
+++ b/drivers/net/wireless/realtek/rtw89/rtw8922d.c
@@ -231,6 +231,243 @@ static const struct rtw89_efuse_block_cfg rtw8922d_efuse_blocks[] = {
 	[RTW89_EFUSE_BLOCK_ADIE]		= {.offset = 0x70000, .size = 0x10},
 };
 
+static int rtw8922d_pwr_on_func(struct rtw89_dev *rtwdev)
+{
+	struct rtw89_hal *hal = &rtwdev->hal;
+	u32 val32;
+	int ret;
+
+	if (hal->cid != RTL8922D_CID7025)
+		goto begin;
+
+	switch (hal->cv) {
+	case CHIP_CAV:
+	case CHIP_CBV:
+		rtw89_write32_set(rtwdev, R_BE_SPS_DIG_ON_CTRL1, B_BE_PWM_FORCE);
+		rtw89_write32_set(rtwdev, R_BE_SPS_ANA_ON_CTRL1, B_BE_PWM_FORCE_ANA);
+		break;
+	default:
+		break;
+	}
+
+begin:
+	rtw89_write32_clr(rtwdev, R_BE_SYS_PW_CTRL, B_BE_AFSM_WLSUS_EN |
+						    B_BE_AFSM_PCIE_SUS_EN);
+	rtw89_write32_set(rtwdev, R_BE_SYS_PW_CTRL, B_BE_DIS_WLBT_PDNSUSEN_SOPC);
+	rtw89_write32_set(rtwdev, R_BE_WLLPS_CTRL, B_BE_DIS_WLBT_LPSEN_LOPC);
+	if (hal->cid != RTL8922D_CID7090)
+		rtw89_write32_clr(rtwdev, R_BE_SYS_PW_CTRL, B_BE_APDM_HPDN);
+	rtw89_write32_clr(rtwdev, R_BE_FWS1ISR, B_BE_FS_WL_HW_RADIO_OFF_INT);
+	rtw89_write32_clr(rtwdev, R_BE_SYS_PW_CTRL, B_BE_APFM_SWLPS);
+
+	ret = read_poll_timeout(rtw89_read32, val32, val32 & B_BE_RDY_SYSPWR,
+				1000, 3000000, false, rtwdev, R_BE_SYS_PW_CTRL);
+	if (ret)
+		return ret;
+
+	rtw89_write32_set(rtwdev, R_BE_SYS_PW_CTRL, B_BE_EN_WLON);
+	rtw89_write32_set(rtwdev, R_BE_WLRESUME_CTRL, B_BE_LPSROP_CMAC0 |
+						      B_BE_LPSROP_CMAC1);
+	rtw89_write32_set(rtwdev, R_BE_SYS_PW_CTRL, B_BE_APFN_ONMAC);
+
+	ret = read_poll_timeout(rtw89_read32, val32, !(val32 & B_BE_APFN_ONMAC),
+				1000, 3000000, false, rtwdev, R_BE_SYS_PW_CTRL);
+	if (ret)
+		return ret;
+
+	rtw89_write8_set(rtwdev, R_BE_PLATFORM_ENABLE, B_BE_PLATFORM_EN);
+	rtw89_write32_set(rtwdev, R_BE_HCI_OPT_CTRL, B_BE_HAXIDMA_IO_EN);
+
+	ret = read_poll_timeout(rtw89_read32, val32, val32 & B_BE_HAXIDMA_IO_ST,
+				1000, 3000000, false, rtwdev, R_BE_HCI_OPT_CTRL);
+	if (ret)
+		return ret;
+
+	ret = read_poll_timeout(rtw89_read32, val32, !(val32 & B_BE_HAXIDMA_BACKUP_RESTORE_ST),
+				1000, 3000000, false, rtwdev, R_BE_HCI_OPT_CTRL);
+	if (ret)
+		return ret;
+
+	rtw89_write32_set(rtwdev, R_BE_HCI_OPT_CTRL, B_BE_HCI_WLAN_IO_EN);
+
+	ret = read_poll_timeout(rtw89_read32, val32, val32 & B_BE_HCI_WLAN_IO_ST,
+				1000, 3000000, false, rtwdev, R_BE_HCI_OPT_CTRL);
+	if (ret)
+		return ret;
+
+	rtw89_write32_clr(rtwdev, R_BE_SYS_SDIO_CTRL, B_BE_PCIE_FORCE_IBX_EN);
+
+	ret = rtw89_mac_write_xtal_si(rtwdev, XTAL_SI_NORMAL_WRITE, 0x10, 0x10);
+	if (ret)
+		return ret;
+
+	rtw89_write32_set(rtwdev, R_BE_SYS_ADIE_PAD_PWR_CTRL, B_BE_SYM_PADPDN_WL_RFC1_1P3);
+
+	ret = rtw89_mac_write_xtal_si(rtwdev, XTAL_SI_ANAPAR_WL, 0x40, 0x40);
+	if (ret)
+		return ret;
+
+	rtw89_write32_set(rtwdev, R_BE_SYS_ADIE_PAD_PWR_CTRL, B_BE_SYM_PADPDN_WL_RFC0_1P3);
+
+	ret = rtw89_mac_write_xtal_si(rtwdev, XTAL_SI_ANAPAR_WL, 0x20, 0x20);
+	if (ret)
+		return ret;
+	ret = rtw89_mac_write_xtal_si(rtwdev, XTAL_SI_ANAPAR_WL, 0x04, 0x04);
+	if (ret)
+		return ret;
+	ret = rtw89_mac_write_xtal_si(rtwdev, XTAL_SI_ANAPAR_WL, 0x08, 0x08);
+	if (ret)
+		return ret;
+	ret = rtw89_mac_write_xtal_si(rtwdev, XTAL_SI_ANAPAR_WL, 0, 0x10);
+	if (ret)
+		return ret;
+	ret = rtw89_mac_write_xtal_si(rtwdev, XTAL_SI_WL_RFC_S0, 0xEB, 0xFF);
+	if (ret)
+		return ret;
+	ret = rtw89_mac_write_xtal_si(rtwdev, XTAL_SI_WL_RFC_S1, 0xEB, 0xFF);
+	if (ret)
+		return ret;
+	ret = rtw89_mac_write_xtal_si(rtwdev, XTAL_SI_ANAPAR_WL, 0x01, 0x01);
+	if (ret)
+		return ret;
+	ret = rtw89_mac_write_xtal_si(rtwdev, XTAL_SI_ANAPAR_WL, 0x02, 0x02);
+	if (ret)
+		return ret;
+	ret = rtw89_mac_write_xtal_si(rtwdev, XTAL_SI_ANAPAR_WL, 0, 0x80);
+	if (ret)
+		return ret;
+	ret = rtw89_mac_write_xtal_si(rtwdev, XTAL_SI_XTAL_XMD_2, 0, 0x70);
+	if (ret)
+		return ret;
+	ret = rtw89_mac_write_xtal_si(rtwdev, XTAL_SI_SRAM_CTRL, 0, 0x02);
+	if (ret)
+		return ret;
+
+	rtw89_write32_set(rtwdev, R_BE_PMC_DBG_CTRL2, B_BE_SYSON_DIS_PMCR_BE_WRMSK);
+	rtw89_write32_set(rtwdev, R_BE_SYS_ISO_CTRL, B_BE_ISO_EB2CORE);
+	rtw89_write32_clr(rtwdev, R_BE_SYS_ISO_CTRL, B_BE_PWC_EV2EF_B);
+
+	mdelay(1);
+
+	rtw89_write32_clr(rtwdev, R_BE_SYS_ISO_CTRL, B_BE_PWC_EV2EF_S);
+	rtw89_write32_clr(rtwdev, R_BE_PMC_DBG_CTRL2, B_BE_SYSON_DIS_PMCR_BE_WRMSK);
+
+	rtw89_write32_set(rtwdev, R_BE_DMAC_FUNC_EN,
+			  B_BE_MAC_FUNC_EN | B_BE_DMAC_FUNC_EN |
+			  B_BE_MPDU_PROC_EN | B_BE_WD_RLS_EN |
+			  B_BE_DLE_WDE_EN | B_BE_TXPKT_CTRL_EN |
+			  B_BE_STA_SCH_EN | B_BE_DLE_PLE_EN |
+			  B_BE_PKT_BUF_EN | B_BE_DMAC_TBL_EN |
+			  B_BE_PKT_IN_EN | B_BE_DLE_CPUIO_EN |
+			  B_BE_DISPATCHER_EN | B_BE_BBRPT_EN |
+			  B_BE_MAC_SEC_EN | B_BE_H_AXIDMA_EN |
+			  B_BE_DMAC_MLO_EN | B_BE_PLRLS_EN |
+			  B_BE_P_AXIDMA_EN | B_BE_DLE_DATACPUIO_EN |
+			  B_BE_LTR_CTL_EN);
+
+	set_bit(RTW89_FLAG_DMAC_FUNC, rtwdev->flags);
+
+	rtw89_write32_set(rtwdev, R_BE_CMAC_SHARE_FUNC_EN,
+			  B_BE_CMAC_SHARE_EN | B_BE_RESPBA_EN |
+			  B_BE_ADDRSRCH_EN | B_BE_BTCOEX_EN);
+
+	rtw89_write32_set(rtwdev, R_BE_CMAC_FUNC_EN,
+			  B_BE_CMAC_EN | B_BE_CMAC_TXEN |
+			  B_BE_CMAC_RXEN | B_BE_SIGB_EN |
+			  B_BE_PHYINTF_EN | B_BE_CMAC_DMA_EN |
+			  B_BE_PTCLTOP_EN | B_BE_SCHEDULER_EN |
+			  B_BE_TMAC_EN | B_BE_RMAC_EN |
+			  B_BE_TXTIME_EN | B_BE_RESP_PKTCTL_EN);
+
+	set_bit(RTW89_FLAG_CMAC0_FUNC, rtwdev->flags);
+
+	rtw89_write32_set(rtwdev, R_BE_FEN_RST_ENABLE,
+			  B_BE_FEN_BB_IP_RSTN | B_BE_FEN_BBPLAT_RSTB);
+
+	return 0;
+}
+
+static int rtw8922d_pwr_off_func(struct rtw89_dev *rtwdev)
+{
+	u32 val32;
+	int ret;
+
+	ret = rtw89_mac_write_xtal_si(rtwdev, XTAL_SI_ANAPAR_WL, 0x10, 0x10);
+	if (ret)
+		return ret;
+	ret = rtw89_mac_write_xtal_si(rtwdev, XTAL_SI_ANAPAR_WL, 0, 0x08);
+	if (ret)
+		return ret;
+	ret = rtw89_mac_write_xtal_si(rtwdev, XTAL_SI_ANAPAR_WL, 0, 0x04);
+	if (ret)
+		return ret;
+	ret = rtw89_mac_write_xtal_si(rtwdev, XTAL_SI_WL_RFC_S0, 0, 0x01);
+	if (ret)
+		return ret;
+	ret = rtw89_mac_write_xtal_si(rtwdev, XTAL_SI_WL_RFC_S1, 0, 0x01);
+	if (ret)
+		return ret;
+	ret = rtw89_mac_write_xtal_si(rtwdev, XTAL_SI_ANAPAR_WL, 0x80, 0x80);
+	if (ret)
+		return ret;
+	ret = rtw89_mac_write_xtal_si(rtwdev, XTAL_SI_ANAPAR_WL, 0, 0x02);
+	if (ret)
+		return ret;
+	ret = rtw89_mac_write_xtal_si(rtwdev, XTAL_SI_ANAPAR_WL, 0, 0x01);
+	if (ret)
+		return ret;
+
+	rtw89_write32_set(rtwdev, R_BE_SYS_PW_CTRL, B_BE_EN_WLON);
+	rtw89_write8_clr(rtwdev, R_BE_FEN_RST_ENABLE, B_BE_FEN_BB_IP_RSTN |
+						      B_BE_FEN_BBPLAT_RSTB);
+	rtw89_write32_clr(rtwdev, R_BE_SYS_ADIE_PAD_PWR_CTRL,
+			  B_BE_SYM_PADPDN_WL_RFC0_1P3);
+
+	ret = rtw89_mac_write_xtal_si(rtwdev, XTAL_SI_ANAPAR_WL, 0, 0x20);
+	if (ret)
+		return ret;
+
+	rtw89_write32_clr(rtwdev, R_BE_SYS_ADIE_PAD_PWR_CTRL,
+			  B_BE_SYM_PADPDN_WL_RFC1_1P3);
+
+	ret = rtw89_mac_write_xtal_si(rtwdev, XTAL_SI_ANAPAR_WL, 0, 0x40);
+	if (ret)
+		return ret;
+
+	rtw89_write32_clr(rtwdev, R_BE_HCI_OPT_CTRL, B_BE_HAXIDMA_IO_EN);
+
+	ret = read_poll_timeout(rtw89_read32, val32, !(val32 & B_BE_HAXIDMA_IO_ST),
+				1000, 3000000, false, rtwdev, R_BE_HCI_OPT_CTRL);
+	if (ret)
+		return ret;
+	ret = read_poll_timeout(rtw89_read32, val32,
+				!(val32 & B_BE_HAXIDMA_BACKUP_RESTORE_ST),
+				1000, 3000000, false, rtwdev, R_BE_HCI_OPT_CTRL);
+	if (ret)
+		return ret;
+
+	rtw89_write32_clr(rtwdev, R_BE_HCI_OPT_CTRL, B_BE_HCI_WLAN_IO_EN);
+
+	ret = read_poll_timeout(rtw89_read32, val32, !(val32 & B_BE_HCI_WLAN_IO_ST),
+				1000, 3000000, false, rtwdev, R_BE_HCI_OPT_CTRL);
+	if (ret)
+		return ret;
+
+	rtw89_write32_set(rtwdev, R_BE_SYS_PW_CTRL, B_BE_APFM_OFFMAC);
+
+	ret = read_poll_timeout(rtw89_read32, val32, !(val32 & B_BE_APFM_OFFMAC),
+				1000, 3000000, false, rtwdev, R_BE_SYS_PW_CTRL);
+	if (ret)
+		return ret;
+
+	rtw89_write32(rtwdev, R_BE_WLLPS_CTRL, 0x00015002);
+	rtw89_write32_clr(rtwdev, R_BE_SYS_PW_CTRL, B_BE_XTAL_OFF_A_DIE);
+	rtw89_write32_set(rtwdev, R_BE_SYS_PW_CTRL, B_BE_APFM_SWLPS);
+	rtw89_write32(rtwdev, R_BE_UDM1, 0);
+
+	return 0;
+}
+
 MODULE_FIRMWARE(RTW8922D_MODULE_FIRMWARE);
 MODULE_FIRMWARE(RTW8922DS_MODULE_FIRMWARE);
 MODULE_AUTHOR("Realtek Corporation");
-- 
2.25.1


^ permalink raw reply related

* [PATCH rtw-next 1/7] wifi: rtw89: 8922d: add definition of quota, registers and efuse block
From: Ping-Ke Shih @ 2026-03-23  3:25 UTC (permalink / raw)
  To: linux-wireless
In-Reply-To: <20260323032556.19825-1-pkshih@realtek.com>

The quota is used to configure memory size for TX/RX, and the definition
of registers includes H2C command, C2H event, WoWLAN reason, IMR of CMAC
and DMAC, ACK rate selector, RF kill GPIO, and BB functions of dynamic
initial gain and EDCCA. The layout of efuse block is to define logic
map of efuse, such as MAC address and RF calibration values.

Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
---
 drivers/net/wireless/realtek/rtw89/reg.h      |  30 +++
 drivers/net/wireless/realtek/rtw89/rtw8922d.c | 238 ++++++++++++++++++
 drivers/net/wireless/realtek/rtw89/rtw8922d.h |  10 +
 3 files changed, 278 insertions(+)
 create mode 100644 drivers/net/wireless/realtek/rtw89/rtw8922d.c
 create mode 100644 drivers/net/wireless/realtek/rtw89/rtw8922d.h

diff --git a/drivers/net/wireless/realtek/rtw89/reg.h b/drivers/net/wireless/realtek/rtw89/reg.h
index 9b605617c3f0..2cb35458a822 100644
--- a/drivers/net/wireless/realtek/rtw89/reg.h
+++ b/drivers/net/wireless/realtek/rtw89/reg.h
@@ -8737,6 +8737,7 @@
 #define R_PHY_STS_BITMAP_EHT 0x0788
 #define R_PHY_STS_BITMAP_EHT_BE4 0x20788
 #define R_EDCCA_RPTREG_SEL_BE 0x078C
+#define R_EDCCA_RPTREG_SEL_BE4 0x2078C
 #define B_EDCCA_RPTREG_SEL_BE_MSK GENMASK(22, 20)
 #define R_PMAC_GNT 0x0980
 #define B_PMAC_GNT_TXEN BIT(0)
@@ -8850,6 +8851,7 @@
 #define R_UDP_COEEF 0x0CBC
 #define B_UDP_COEEF BIT(19)
 #define R_TX_COLLISION_T2R_ST_BE 0x0CC8
+#define R_TX_COLLISION_T2R_ST_BE4 0x20CC8
 #define B_TX_COLLISION_T2R_ST_BE_M GENMASK(13, 8)
 #define R_RXHT_MCS_LIMIT 0x0D18
 #define B_RXHT_MCS_LIMIT GENMASK(9, 8)
@@ -9078,7 +9080,11 @@
 #define R_P1_EN_SOUND_WO_NDP 0x2D7C
 #define B_P1_EN_SOUND_WO_NDP BIT(1)
 #define R_EDCCA_RPT_A_BE 0x2E38
+#define R_EDCCA_RPT_A_BE4 0x2EE30
+#define R_EDCCA_RPT_A_BE4_C1 0x2FE30
 #define R_EDCCA_RPT_B_BE 0x2E3C
+#define R_EDCCA_RPT_B_BE4 0x2EE34
+#define R_EDCCA_RPT_B_BE4_C1 0x2FE34
 #define R_EDCCA_RPT_P1_A_BE 0x2E40
 #define R_EDCCA_RPT_P1_B_BE 0x2E44
 #define R_S1_HW_SI_DIS 0x3200
@@ -9262,11 +9268,13 @@
 #define R_PATH0_P20_FOLLOW_BY_PAGCUGC_V1 0x4C24
 #define R_PATH0_P20_FOLLOW_BY_PAGCUGC_V2 0x46E8
 #define R_PATH0_P20_FOLLOW_BY_PAGCUGC_V3 0x41C8
+#define R_PATH0_P20_FOLLOW_BY_PAGCUGC_BE4 0x241C8
 #define B_PATH0_P20_FOLLOW_BY_PAGCUGC_EN_MSK BIT(5)
 #define R_PATH0_S20_FOLLOW_BY_PAGCUGC 0x46A4
 #define R_PATH0_S20_FOLLOW_BY_PAGCUGC_V1 0x4C28
 #define R_PATH0_S20_FOLLOW_BY_PAGCUGC_V2 0x46EC
 #define R_PATH0_S20_FOLLOW_BY_PAGCUGC_V3 0x41CC
+#define R_PATH0_S20_FOLLOW_BY_PAGCUGC_BE4 0x241CC
 #define B_PATH0_S20_FOLLOW_BY_PAGCUGC_EN_MSK BIT(5)
 #define R_PATH0_RXB_INIT_V1 0x46A8
 #define B_PATH0_RXB_INIT_IDX_MSK_V1 GENMASK(14, 10)
@@ -9313,11 +9321,13 @@
 #define R_PATH1_P20_FOLLOW_BY_PAGCUGC_V1 0x4CE8
 #define R_PATH1_P20_FOLLOW_BY_PAGCUGC_V2 0x47A8
 #define R_PATH1_P20_FOLLOW_BY_PAGCUGC_V3 0x45C8
+#define R_PATH1_P20_FOLLOW_BY_PAGCUGC_BE4 0x245C8
 #define B_PATH1_P20_FOLLOW_BY_PAGCUGC_EN_MSK BIT(5)
 #define R_PATH1_S20_FOLLOW_BY_PAGCUGC 0x4778
 #define R_PATH1_S20_FOLLOW_BY_PAGCUGC_V1 0x4CEC
 #define R_PATH1_S20_FOLLOW_BY_PAGCUGC_V2 0x47AC
 #define R_PATH1_S20_FOLLOW_BY_PAGCUGC_V3 0x45CC
+#define R_PATH1_S20_FOLLOW_BY_PAGCUGC_BE4 0x245CC
 #define B_PATH1_S20_FOLLOW_BY_PAGCUGC_EN_MSK BIT(5)
 #define R_PATH1_G_TIA0_LNA6_OP1DB_V1 0x4778
 #define B_PATH1_G_TIA0_LNA6_OP1DB_V1 GENMASK(7, 0)
@@ -9338,6 +9348,7 @@
 #define R_SEG0R_PD 0x481C
 #define R_SEG0R_PD_V1 0x4860
 #define R_SEG0R_PD_V2 0x6A74
+#define R_SEG0R_PD_BE4 0x26210
 #define R_SEG0R_EDCCA_LVL 0x4840
 #define R_SEG0R_EDCCA_LVL_V1 0x4884
 #define B_EDCCA_LVL_MSK3 GENMASK(31, 24)
@@ -9476,9 +9487,11 @@
 #define B_DCFO_COMP_S0_V1_MSK GENMASK(13, 0)
 #define R_BMODE_PDTH_V1 0x4B64
 #define R_BMODE_PDTH_V2 0x6708
+#define R_BMODE_PDTH_BE4 0x26040
 #define B_BMODE_PDTH_LOWER_BOUND_MSK_V1 GENMASK(31, 24)
 #define R_BMODE_PDTH_EN_V1 0x4B74
 #define R_BMODE_PDTH_EN_V2 0x6718
+#define R_BMODE_PDTH_EN_BE4 0x26050
 #define B_BMODE_PDTH_LIMIT_EN_MSK_V1 BIT(30)
 #define R_BSS_CLR_VLD_V2 0x4EBC
 #define B_BSS_CLR_VLD0_V2 BIT(2)
@@ -9653,7 +9666,9 @@
 #define R_CCK_FC0INV 0x675c
 #define B_CCK_FC0INV GENMASK(18, 0)
 #define R_SEG0R_EDCCA_LVL_BE 0x69EC
+#define R_SEG0R_EDCCA_LVL_BE4 0x2623C
 #define R_SEG0R_PPDU_LVL_BE 0x69F0
+#define R_SEG0R_PPDU_LVL_BE4 0x26240
 #define R_SEGSND 0x6A14
 #define B_SEGSND_EN BIT(31)
 #define R_DBCC 0x6B48
@@ -10380,6 +10395,9 @@
 #define B_SW_SI_DATA_DAT_BE4 GENMASK(19, 0)
 #define R_SW_SI_READ_ADDR_BE4 0x20378
 #define B_SW_SI_READ_ADDR_BE4 GENMASK(10, 0)
+#define R_EDCCA_RPT_SEL_BE4 0x20780
+#define R_EDCCA_RPT_SEL_BE4_C1 0x21780
+#define B_EDCCA_RPT_SEL_BE4_MSK 0xE0000
 #define R_IFS_T1_AVG_BE4 0x20EDC
 #define B_IFS_T1_AVG_BE4 GENMASK(15, 0)
 #define B_IFS_T2_AVG_BE4 GENMASK(31, 16)
@@ -10402,6 +10420,18 @@
 #define B_IFS_T3_HIS_BE4 GENMASK(15, 0)
 #define B_IFS_T4_HIS_BE4 GENMASK(31, 16)
 
+#define R_PATH0_RXIDX_INIT_BE4 0x24108
+#define B_PATH0_RXIDX_INIT_BE4 GENMASK(29, 25)
+#define R_PATH0_LNA_INIT_BE4 0x24158
+#define B_PATH0_LNA_INIT_IDX_BE4 GENMASK(14, 12)
+#define R_PATH0_TIA_INIT_BE4 0x24168
+#define B_PATH0_TIA_INIT_IDX_BE4 BIT(18)
+#define R_PATH1_RXIDX_INIT_BE4 0x24508
+#define B_PATH1_RXIDX_INIT_BE4 GENMASK(29, 25)
+#define R_PATH1_LNA_INIT_BE4 0x24558
+#define B_PATH1_LNA_INIT_IDX_BE4 GENMASK(14, 12)
+#define R_PATH1_TIA_INIT_BE4 0x24568
+#define B_PATH1_TIA_INIT_IDX_BE4 BIT(18)
 #define R_TX_CFR_MANUAL_EN_BE4 0x2483C
 #define B_TX_CFR_MANUAL_EN_BE4_M BIT(30)
 
diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922d.c b/drivers/net/wireless/realtek/rtw89/rtw8922d.c
new file mode 100644
index 000000000000..6a90ded1b33e
--- /dev/null
+++ b/drivers/net/wireless/realtek/rtw89/rtw8922d.c
@@ -0,0 +1,238 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+/* Copyright(c) 2026  Realtek Corporation
+ */
+
+#include "efuse.h"
+#include "mac.h"
+#include "reg.h"
+#include "rtw8922d.h"
+
+#define RTW8922D_FW_FORMAT_MAX 0
+#define RTW8922D_FW_BASENAME "rtw89/rtw8922d_fw"
+#define RTW8922D_MODULE_FIRMWARE \
+	RTW89_GEN_MODULE_FWNAME(RTW8922D_FW_BASENAME, RTW8922D_FW_FORMAT_MAX)
+
+#define RTW8922DS_FW_FORMAT_MAX 0
+#define RTW8922DS_FW_BASENAME "rtw89/rtw8922ds_fw"
+#define RTW8922DS_MODULE_FIRMWARE \
+	RTW89_GEN_MODULE_FWNAME(RTW8922DS_FW_BASENAME, RTW8922DS_FW_FORMAT_MAX)
+
+static const struct rtw89_hfc_ch_cfg rtw8922d_hfc_chcfg_pcie[] = {
+	{2, 603, 0}, /* ACH 0 */
+	{0, 601, 0}, /* ACH 1 */
+	{2, 603, 0}, /* ACH 2 */
+	{0, 601, 0}, /* ACH 3 */
+	{2, 603, 0}, /* ACH 4 */
+	{0, 601, 0}, /* ACH 5 */
+	{2, 603, 0}, /* ACH 6 */
+	{0, 601, 0}, /* ACH 7 */
+	{2, 603, 0}, /* B0MGQ */
+	{0, 601, 0}, /* B0HIQ */
+	{2, 603, 0}, /* B1MGQ */
+	{0, 601, 0}, /* B1HIQ */
+	{0, 0, 0}, /* FWCMDQ */
+	{0, 0, 0}, /* BMC */
+	{0, 0, 0}, /* H2D */
+};
+
+static const struct rtw89_hfc_pub_cfg rtw8922d_hfc_pubcfg_pcie = {
+	613, /* Group 0 */
+	0, /* Group 1 */
+	613, /* Public Max */
+	0, /* WP threshold */
+};
+
+static const struct rtw89_hfc_param_ini rtw8922d_hfc_param_ini_pcie[] = {
+	[RTW89_QTA_SCC] = {rtw8922d_hfc_chcfg_pcie, &rtw8922d_hfc_pubcfg_pcie,
+			   &rtw89_mac_size.hfc_prec_cfg_c0, RTW89_HCIFC_POH},
+	[RTW89_QTA_DBCC] = {rtw8922d_hfc_chcfg_pcie, &rtw8922d_hfc_pubcfg_pcie,
+			   &rtw89_mac_size.hfc_prec_cfg_c0, RTW89_HCIFC_POH},
+	[RTW89_QTA_DLFW] = {NULL, NULL, &rtw89_mac_size.hfc_prec_cfg_c2,
+			    RTW89_HCIFC_POH},
+	[RTW89_QTA_INVALID] = {NULL},
+};
+
+static const struct rtw89_dle_mem rtw8922d_dle_mem_pcie[] = {
+	[RTW89_QTA_SCC] = {RTW89_QTA_SCC, &rtw89_mac_size.wde_size16_v1,
+			   &rtw89_mac_size.ple_size20_v1, &rtw89_mac_size.wde_qt19_v1,
+			   &rtw89_mac_size.wde_qt19_v1, &rtw89_mac_size.ple_qt42_v2,
+			   &rtw89_mac_size.ple_qt43_v2, &rtw89_mac_size.ple_rsvd_qt9,
+			   &rtw89_mac_size.rsvd0_size6, &rtw89_mac_size.rsvd1_size2,
+			   &rtw89_mac_size.dle_input18},
+	[RTW89_QTA_DBCC] = {RTW89_QTA_DBCC, &rtw89_mac_size.wde_size16_v1,
+			   &rtw89_mac_size.ple_size20_v1, &rtw89_mac_size.wde_qt19_v1,
+			   &rtw89_mac_size.wde_qt19_v1, &rtw89_mac_size.ple_qt42_v2,
+			   &rtw89_mac_size.ple_qt43_v2, &rtw89_mac_size.ple_rsvd_qt9,
+			   &rtw89_mac_size.rsvd0_size6, &rtw89_mac_size.rsvd1_size2,
+			   &rtw89_mac_size.dle_input18},
+	[RTW89_QTA_DLFW] = {RTW89_QTA_DLFW, &rtw89_mac_size.wde_size18_v1,
+			    &rtw89_mac_size.ple_size22_v1, &rtw89_mac_size.wde_qt3,
+			    &rtw89_mac_size.wde_qt3, &rtw89_mac_size.ple_qt5_v2,
+			    &rtw89_mac_size.ple_qt5_v2, &rtw89_mac_size.ple_rsvd_qt1,
+			    &rtw89_mac_size.rsvd0_size6, &rtw89_mac_size.rsvd1_size2,
+			    &rtw89_mac_size.dle_input3},
+	[RTW89_QTA_INVALID] = {RTW89_QTA_INVALID, NULL, NULL, NULL, NULL, NULL,
+			       NULL},
+};
+
+static const u32 rtw8922d_h2c_regs[RTW89_H2CREG_MAX] = {
+	R_BE_H2CREG_DATA0, R_BE_H2CREG_DATA1, R_BE_H2CREG_DATA2,
+	R_BE_H2CREG_DATA3
+};
+
+static const u32 rtw8922d_c2h_regs[RTW89_H2CREG_MAX] = {
+	R_BE_C2HREG_DATA0, R_BE_C2HREG_DATA1, R_BE_C2HREG_DATA2,
+	R_BE_C2HREG_DATA3
+};
+
+static const u32 rtw8922d_wow_wakeup_regs[RTW89_WOW_REASON_NUM] = {
+	R_BE_DBG_WOW, R_BE_DBG_WOW,
+};
+
+static const struct rtw89_page_regs rtw8922d_page_regs = {
+	.hci_fc_ctrl	= R_BE_HCI_FC_CTRL,
+	.ch_page_ctrl	= R_BE_CH_PAGE_CTRL,
+	.ach_page_ctrl	= R_BE_CH0_PAGE_CTRL,
+	.ach_page_info	= R_BE_CH0_PAGE_INFO,
+	.pub_page_info3	= R_BE_PUB_PAGE_INFO3,
+	.pub_page_ctrl1	= R_BE_PUB_PAGE_CTRL1,
+	.pub_page_ctrl2	= R_BE_PUB_PAGE_CTRL2,
+	.pub_page_info1	= R_BE_PUB_PAGE_INFO1,
+	.pub_page_info2 = R_BE_PUB_PAGE_INFO2,
+	.wp_page_ctrl1	= R_BE_WP_PAGE_CTRL1,
+	.wp_page_ctrl2	= R_BE_WP_PAGE_CTRL2,
+	.wp_page_info1	= R_BE_WP_PAGE_INFO1,
+};
+
+static const struct rtw89_reg_imr rtw8922d_imr_dmac_regs[] = {
+	{R_BE_HCI_BUF_IMR, B_BE_HCI_BUF_IMR_CLR, B_BE_HCI_BUF_IMR_SET},
+	{R_BE_DISP_HOST_IMR, B_BE_DISP_HOST_IMR_CLR_V1, B_BE_DISP_HOST_IMR_SET_V1},
+	{R_BE_DISP_CPU_IMR, B_BE_DISP_CPU_IMR_CLR_V1, B_BE_DISP_CPU_IMR_SET_V1},
+	{R_BE_DISP_OTHER_IMR, B_BE_DISP_OTHER_IMR_CLR_V1, B_BE_DISP_OTHER_IMR_SET_V1},
+	{R_BE_PKTIN_ERR_IMR, B_BE_PKTIN_ERR_IMR_CLR, B_BE_PKTIN_ERR_IMR_SET},
+	{R_BE_MLO_ERR_IDCT_IMR, B_BE_MLO_ERR_IDCT_IMR_CLR, B_BE_MLO_ERR_IDCT_IMR_SET},
+	{R_BE_MPDU_TX_ERR_IMR, B_BE_MPDU_TX_ERR_IMR_CLR, B_BE_MPDU_TX_ERR_IMR_SET},
+	{R_BE_MPDU_RX_ERR_IMR, B_BE_MPDU_RX_ERR_IMR_CLR, B_BE_MPDU_RX_ERR_IMR_SET},
+	{R_BE_SEC_ERROR_IMR, B_BE_SEC_ERROR_IMR_CLR, B_BE_SEC_ERROR_IMR_SET},
+	{R_BE_CPUIO_ERR_IMR, B_BE_CPUIO_ERR_IMR_CLR, B_BE_CPUIO_ERR_IMR_SET},
+	{R_BE_WDE_ERR_IMR, B_BE_WDE_ERR_IMR_CLR, B_BE_WDE_ERR_IMR_SET},
+	{R_BE_PLE_ERR_IMR, B_BE_PLE_ERR_IMR_CLR, B_BE_PLE_ERR_IMR_SET},
+	{R_BE_WDRLS_ERR_IMR, B_BE_WDRLS_ERR_IMR_CLR, B_BE_WDRLS_ERR_IMR_SET},
+	{R_BE_TXPKTCTL_B0_ERRFLAG_IMR, B_BE_TXPKTCTL_B0_ERRFLAG_IMR_CLR,
+	 B_BE_TXPKTCTL_B0_ERRFLAG_IMR_SET},
+	{R_BE_TXPKTCTL_B1_ERRFLAG_IMR, B_BE_TXPKTCTL_B1_ERRFLAG_IMR_CLR,
+	 B_BE_TXPKTCTL_B1_ERRFLAG_IMR_SET},
+	{R_BE_BBRPT_COM_ERR_IMR, B_BE_BBRPT_COM_ERR_IMR_CLR, B_BE_BBRPT_COM_ERR_IMR_SET},
+	{R_BE_BBRPT_CHINFO_ERR_IMR, B_BE_BBRPT_CHINFO_ERR_IMR_CLR,
+	 B_BE_BBRPT_CHINFO_ERR_IMR_SET},
+	{R_BE_BBRPT_DFS_ERR_IMR, B_BE_BBRPT_DFS_ERR_IMR_CLR, B_BE_BBRPT_DFS_ERR_IMR_SET},
+	{R_BE_LA_ERRFLAG_IMR, B_BE_LA_ERRFLAG_IMR_CLR, B_BE_LA_ERRFLAG_IMR_SET},
+	{R_BE_CH_INFO_DBGFLAG_IMR, B_BE_CH_INFO_DBGFLAG_IMR_CLR, B_BE_CH_INFO_DBGFLAG_IMR_SET},
+	{R_BE_PLRLS_ERR_IMR_V1, B_BE_PLRLS_ERR_IMR_V1_CLR, B_BE_PLRLS_ERR_IMR_V1_SET},
+	{R_BE_HAXI_IDCT_MSK, B_BE_HAXI_IDCT_MSK_CLR, B_BE_HAXI_IDCT_MSK_SET},
+};
+
+static const struct rtw89_imr_table rtw8922d_imr_dmac_table = {
+	.regs = rtw8922d_imr_dmac_regs,
+	.n_regs = ARRAY_SIZE(rtw8922d_imr_dmac_regs),
+};
+
+static const struct rtw89_reg_imr rtw8922d_imr_cmac_regs[] = {
+	{R_BE_RESP_IMR, B_BE_RESP_IMR_CLR_V1, B_BE_RESP_IMR_SET_V1},
+	{R_BE_RESP_IMR1, B_BE_RESP_IMR1_CLR, B_BE_RESP_IMR1_SET},
+	{R_BE_RX_ERROR_FLAG_IMR, B_BE_RX_ERROR_FLAG_IMR_CLR_V1, B_BE_RX_ERROR_FLAG_IMR_SET_V1},
+	{R_BE_TX_ERROR_FLAG_IMR, B_BE_TX_ERROR_FLAG_IMR_CLR, B_BE_TX_ERROR_FLAG_IMR_SET},
+	{R_BE_RX_ERROR_FLAG_IMR_1, B_BE_TX_ERROR_FLAG_IMR_1_CLR, B_BE_TX_ERROR_FLAG_IMR_1_SET},
+	{R_BE_PTCL_IMR1, B_BE_PTCL_IMR1_CLR, B_BE_PTCL_IMR1_SET},
+	{R_BE_PTCL_IMR0, B_BE_PTCL_IMR0_CLR, B_BE_PTCL_IMR0_SET},
+	{R_BE_PTCL_IMR_2, B_BE_PTCL_IMR_2_CLR, B_BE_PTCL_IMR_2_SET},
+	{R_BE_SCHEDULE_ERR_IMR, B_BE_SCHEDULE_ERR_IMR_CLR, B_BE_SCHEDULE_ERR_IMR_SET},
+	{R_BE_C0_TXPWR_IMR, B_BE_C0_TXPWR_IMR_CLR, B_BE_C0_TXPWR_IMR_SET},
+	{R_BE_TRXPTCL_ERROR_INDICA_MASK, B_BE_TRXPTCL_ERROR_INDICA_MASK_CLR,
+	 B_BE_TRXPTCL_ERROR_INDICA_MASK_SET},
+	{R_BE_RX_ERR_IMR, B_BE_RX_ERR_IMR_CLR, B_BE_RX_ERR_IMR_SET},
+	{R_BE_PHYINFO_ERR_IMR_V1, B_BE_PHYINFO_ERR_IMR_V1_CLR, B_BE_PHYINFO_ERR_IMR_V1_SET},
+};
+
+static const struct rtw89_imr_table rtw8922d_imr_cmac_table = {
+	.regs = rtw8922d_imr_cmac_regs,
+	.n_regs = ARRAY_SIZE(rtw8922d_imr_cmac_regs),
+};
+
+static const struct rtw89_rrsr_cfgs rtw8922d_rrsr_cfgs = {
+	.ref_rate = {R_BE_TRXPTCL_RESP_1, B_BE_WMAC_RESP_REF_RATE_SEL, 0},
+	.rsc = {R_BE_PTCL_RRSR1, B_BE_RSC_MASK, 2},
+};
+
+static const struct rtw89_rfkill_regs rtw8922d_rfkill_regs = {
+	.pinmux = {R_BE_GPIO8_15_FUNC_SEL,
+		   B_BE_PINMUX_GPIO9_FUNC_SEL_MASK,
+		   0xf},
+	.mode = {R_BE_GPIO_EXT_CTRL + 2,
+		 (B_BE_GPIO_MOD_9 | B_BE_GPIO_IO_SEL_9) >> 16,
+		 0x0},
+};
+
+static const struct rtw89_dig_regs rtw8922d_dig_regs = {
+	.seg0_pd_reg = R_SEG0R_PD_BE4,
+	.pd_lower_bound_mask = B_SEG0R_PD_LOWER_BOUND_MSK,
+	.pd_spatial_reuse_en = B_SEG0R_PD_SPATIAL_REUSE_EN_MSK_V1,
+	.bmode_pd_reg = R_BMODE_PDTH_EN_BE4,
+	.bmode_cca_rssi_limit_en = B_BMODE_PDTH_LIMIT_EN_MSK_V1,
+	.bmode_pd_lower_bound_reg = R_BMODE_PDTH_BE4,
+	.bmode_rssi_nocca_low_th_mask = B_BMODE_PDTH_LOWER_BOUND_MSK_V1,
+	.p0_lna_init = {R_PATH0_LNA_INIT_BE4, B_PATH0_LNA_INIT_IDX_BE4},
+	.p1_lna_init = {R_PATH1_LNA_INIT_BE4, B_PATH1_LNA_INIT_IDX_BE4},
+	.p0_tia_init = {R_PATH0_TIA_INIT_BE4, B_PATH0_TIA_INIT_IDX_BE4},
+	.p1_tia_init = {R_PATH1_TIA_INIT_BE4, B_PATH1_TIA_INIT_IDX_BE4},
+	.p0_rxb_init = {R_PATH0_RXIDX_INIT_BE4, B_PATH0_RXIDX_INIT_BE4},
+	.p1_rxb_init = {R_PATH1_RXIDX_INIT_BE4, B_PATH1_RXIDX_INIT_BE4},
+	.p0_p20_pagcugc_en = {R_PATH0_P20_FOLLOW_BY_PAGCUGC_BE4,
+			      B_PATH0_P20_FOLLOW_BY_PAGCUGC_EN_MSK},
+	.p0_s20_pagcugc_en = {R_PATH0_S20_FOLLOW_BY_PAGCUGC_BE4,
+			      B_PATH0_S20_FOLLOW_BY_PAGCUGC_EN_MSK},
+	.p1_p20_pagcugc_en = {R_PATH1_P20_FOLLOW_BY_PAGCUGC_BE4,
+			      B_PATH1_P20_FOLLOW_BY_PAGCUGC_EN_MSK},
+	.p1_s20_pagcugc_en = {R_PATH1_S20_FOLLOW_BY_PAGCUGC_BE4,
+			      B_PATH1_S20_FOLLOW_BY_PAGCUGC_EN_MSK},
+};
+
+static const struct rtw89_edcca_regs rtw8922d_edcca_regs = {
+	.edcca_level			= R_SEG0R_EDCCA_LVL_BE4,
+	.edcca_mask			= B_EDCCA_LVL_MSK0,
+	.edcca_p_mask			= B_EDCCA_LVL_MSK1,
+	.ppdu_level			= R_SEG0R_PPDU_LVL_BE4,
+	.ppdu_mask			= B_EDCCA_LVL_MSK1,
+	.p = {{
+		.rpt_a			= R_EDCCA_RPT_A_BE4,
+		.rpt_b			= R_EDCCA_RPT_B_BE4,
+		.rpt_sel		= R_EDCCA_RPT_SEL_BE4,
+		.rpt_sel_mask		= B_EDCCA_RPT_SEL_BE4_MSK,
+	}, {
+		.rpt_a			= R_EDCCA_RPT_A_BE4_C1,
+		.rpt_b			= R_EDCCA_RPT_A_BE4_C1,
+		.rpt_sel		= R_EDCCA_RPT_SEL_BE4_C1,
+		.rpt_sel_mask		= B_EDCCA_RPT_SEL_BE4_MSK,
+	}},
+	.rpt_sel_be			= R_EDCCA_RPTREG_SEL_BE4,
+	.rpt_sel_be_mask		= B_EDCCA_RPTREG_SEL_BE_MSK,
+	.tx_collision_t2r_st		= R_TX_COLLISION_T2R_ST_BE4,
+	.tx_collision_t2r_st_mask	= B_TX_COLLISION_T2R_ST_BE_M,
+};
+
+static const struct rtw89_efuse_block_cfg rtw8922d_efuse_blocks[] = {
+	[RTW89_EFUSE_BLOCK_SYS]			= {.offset = 0x00000, .size = 0x310},
+	[RTW89_EFUSE_BLOCK_RF]			= {.offset = 0x10000, .size = 0x240},
+	[RTW89_EFUSE_BLOCK_HCI_DIG_PCIE_SDIO]	= {.offset = 0x20000, .size = 0x4800},
+	[RTW89_EFUSE_BLOCK_HCI_DIG_USB]		= {.offset = 0x30000, .size = 0x890},
+	[RTW89_EFUSE_BLOCK_HCI_PHY_PCIE]	= {.offset = 0x40000, .size = 0x400},
+	[RTW89_EFUSE_BLOCK_HCI_PHY_USB3]	= {.offset = 0x50000, .size = 0x80},
+	[RTW89_EFUSE_BLOCK_HCI_PHY_USB2]	= {.offset = 0x60000, .size = 0x50},
+	[RTW89_EFUSE_BLOCK_ADIE]		= {.offset = 0x70000, .size = 0x10},
+};
+
+MODULE_FIRMWARE(RTW8922D_MODULE_FIRMWARE);
+MODULE_FIRMWARE(RTW8922DS_MODULE_FIRMWARE);
+MODULE_AUTHOR("Realtek Corporation");
+MODULE_DESCRIPTION("Realtek 802.11be wireless 8922D driver");
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/drivers/net/wireless/realtek/rtw89/rtw8922d.h b/drivers/net/wireless/realtek/rtw89/rtw8922d.h
new file mode 100644
index 000000000000..7ef3f263274e
--- /dev/null
+++ b/drivers/net/wireless/realtek/rtw89/rtw8922d.h
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
+/* Copyright(c) 2026  Realtek Corporation
+ */
+
+#ifndef __RTW89_8922D_H__
+#define __RTW89_8922D_H__
+
+#include "core.h"
+
+#endif
-- 
2.25.1


^ permalink raw reply related

* [PATCH rtw-next 0/7] wifi: rtw89: 8922d: add RTL8922D common routine part 1
From: Ping-Ke Shih @ 2026-03-23  3:25 UTC (permalink / raw)
  To: linux-wireless

This patchset is to add RTL8922D common routines. As the code is not small,
I split it into two parts, and this is the first part.

At this first part, add memory arrangement called quota, power on/off
functions, functions related to calibration data in efuse, and the
main set channel function.

Ping-Ke Shih (7):
  wifi: rtw89: 8922d: add definition of quota, registers and efuse block
  wifi: rtw89: 8922d: add power on/off functions
  wifi: rtw89: 8922d: define efuse map and read necessary fields
  wifi: rtw89: 8922d: read and configure RF by calibration data from
    efuse physical map
  wifi: rtw89: 8922d: add set channel of MAC part
  wifi: rtw89: 8922d: add set channel of BB part
  wifi: rtw89: 8922d: add set channel of RF part

 drivers/net/wireless/realtek/rtw89/core.h     |    7 +
 drivers/net/wireless/realtek/rtw89/reg.h      |  160 +-
 drivers/net/wireless/realtek/rtw89/rtw8922d.c | 1755 +++++++++++++++++
 drivers/net/wireless/realtek/rtw89/rtw8922d.h |   80 +
 .../net/wireless/realtek/rtw89/rtw8922d_rfk.c |   33 +
 .../net/wireless/realtek/rtw89/rtw8922d_rfk.h |   14 +
 6 files changed, 2048 insertions(+), 1 deletion(-)
 create mode 100644 drivers/net/wireless/realtek/rtw89/rtw8922d.c
 create mode 100644 drivers/net/wireless/realtek/rtw89/rtw8922d.h
 create mode 100644 drivers/net/wireless/realtek/rtw89/rtw8922d_rfk.c
 create mode 100644 drivers/net/wireless/realtek/rtw89/rtw8922d_rfk.h


base-commit: eef6d4449e8a540fde792968a26d8aa514af8089
-- 
2.25.1


^ permalink raw reply

* RE: [BUG] wifi: rtw88: Hard system freeze on RTL8821CE when power_save is enabled (LPS/ASPM conflict)
From: Ping-Ke Shih @ 2026-03-23  2:01 UTC (permalink / raw)
  To: LB F; +Cc: linux-wireless@vger.kernel.org, linux-kernel@vger.kernel.org
In-Reply-To: <CALdGYqRPcDRctCpNSJFatXvqMKLFiiRGXZoQa3KJwfwutHJEwA@mail.gmail.com>

LB F <goainwo@gmail.com> wrote:
> Ping-Ke Shih <pkshih@realtek.com> wrote:
> > I'll send formal patch (Cc you) for the invalid VHT NSS=0, but not
> > to handle "unused phy status page". Please give me Tested-by tag on
> > the patch after I send it.
> 
> Hi Ping-Ke,
> 
> Just a quick update to keep you informed -- no rush on anything.
> 
> My kernel updated from 6.19.7 to 6.19.9, which wiped the previously
> installed out-of-tree modules. I rebuilt and reinstalled both patches:
> 
>   1. The v2 DMI quirk (main.h + pci.c) disabling ASPM and LPS Deep
>      Mode for the HP P3S95EA#ACB SKU.
>   2. The rate validation patch (rx.c) clamping out-of-bounds rate
>      values before they reach mac80211.
> 
> Both patches apply cleanly and the system remains fully stable on
> 6.19.9. The DMI quirk is confirmed active via sysfs (disable_aspm=Y,
> disable_lps_deep=Y) with no manual modprobe overrides.
> 
> I am looking forward to your formal patch for the VHT NSS=0 issue and
> will provide a Tested-by tag as soon as it arrives. Thank you again
> for all your work and patience throughout this process.

I sent the VHT NSS=0 patch [1]. Please help to give it a test.
Thanks.

[1] https://lore.kernel.org/linux-wireless/20260323015849.9424-1-pkshih@realtek.com/T/#u

Ping-Ke



^ permalink raw reply

* [PATCH rtw-next] wifi: rtw88: validate RX rate to prevent out-of-bound
From: Ping-Ke Shih @ 2026-03-23  1:58 UTC (permalink / raw)
  To: linux-wireless; +Cc: goainwo

The reported RX rate might be unexpected, causing kernel warns:

  Rate marked as a VHT rate but data is invalid: MCS: 0, NSS: 0
  WARNING: net/mac80211/rx.c:5491 at ieee80211_rx_list+0x183/0x1020 [mac80211]

As the RX rate can be index of an array under certain conditions, validate
it to prevent accessing array out-of-bound potentially.

Reported-by: Oleksandr Havrylov <goainwo@gmail.com>
Closes: https://lore.kernel.org/linux-wireless/CALdGYqSMUPnPfW-_q1RgYr0_SjoXUejAaJJr-o+jpwCk1S7ndQ@mail.gmail.com/
Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
---
 drivers/net/wireless/realtek/rtw88/rx.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/drivers/net/wireless/realtek/rtw88/rx.c b/drivers/net/wireless/realtek/rtw88/rx.c
index 8b0afaaffaa0..41f6557d8d78 100644
--- a/drivers/net/wireless/realtek/rtw88/rx.c
+++ b/drivers/net/wireless/realtek/rtw88/rx.c
@@ -295,6 +295,14 @@ void rtw_rx_query_rx_desc(struct rtw_dev *rtwdev, void *rx_desc8,
 
 	pkt_stat->tsf_low = le32_get_bits(rx_desc->w5, RTW_RX_DESC_W5_TSFL);
 
+	if (pkt_stat->rate >= DESC_RATE_MAX) {
+		rtw_dbg(rtwdev, RTW_DBG_UNEXP,
+			"unexpected RX rate=0x%x\n", pkt_stat->rate);
+
+		pkt_stat->rate = DESC_RATE1M;
+		pkt_stat->bw = RTW_CHANNEL_WIDTH_20;
+	}
+
 	/* drv_info_sz is in unit of 8-bytes */
 	pkt_stat->drv_info_sz *= 8;
 

base-commit: eef6d4449e8a540fde792968a26d8aa514af8089
-- 
2.25.1


^ permalink raw reply related

* [RFC PATCH 0/1] wifi: iwlmvm: introduce iwl_mvm_has_gcmp_support() helper
From: Jonathan Brown @ 2026-03-22 21:52 UTC (permalink / raw)
  To: miriam.rachel.korenblit; +Cc: linux-wireless, linux-kernel

 From a9fa945246619b35e1e1302aa05f9106d7207dc7 Mon Sep 17 00:00:00 2001
From: Jonathan Brown <jonny@borderelliptic.com>
Date: Sun, 22 Mar 2026 17:09:24 -0400
Subject: [RFC PATCH 0/1] wifi: iwlmvm: introduce 
iwl_mvm_has_gcmp_support() helper

Jonathan Brown (1):
   wifi: iwlmvm: introduce iwl_mvm_has_gcmp_support() helper

  drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c |  4 ++--
  drivers/net/wireless/intel/iwlwifi/mvm/mvm.h      | 11 +++++++++++
  2 files changed, 13 insertions(+), 2 deletions(-)

---
This patch introduces a named capability helper for GCMP cipher support
in the iwlmvm driver.

Background
----------
During review of cipher suite registration in iwl_mvm_mac_setup_register()
(mac80211.c), GCMP-128, GCMP-256, BIP-GMAC-128, and BIP-GMAC-256
registration is gated on iwl_mvm_has_new_rx_api(). That function tests for
IWL_UCODE_TLV_CAPA_MULTI_QUEUE_RX_SUPPORT (capability bit 68).

The relationship between multi-queue RX support and GCMP hardware offload
is architectural: GCMP requires per-MSDU PN checking, which depends on the
extended descriptor format introduced with the MQ RX path. On current
hardware this coupling is correct.

Problem
-------
Using iwl_mvm_has_new_rx_api() to gate GCMP registration conflates two
distinct capabilities under one function name. A reader of mac80211.c has
no indication from the call site that the condition is GCMP-specific.
If future hardware supports GCMP offload without the full MQ RX path,
cipher registration would be silently denied with no obvious point of
correction.

This patch
----------
Introduces iwl_mvm_has_gcmp_support() as a named wrapper around
iwl_mvm_has_new_rx_api() and uses it at both cipher registration sites
in iwl_mvm_mac_setup_register(). No behaviour change is intended or
introduced for any current hardware. The wrapper allows the underlying
condition to be updated independently of the MQ RX check if future
hardware warrants it.

Testing
-------
The patch was compiled against the wireless-next tree using Ubuntu
6.17.0-19-generic kernel headers. Compilation of mac80211.c succeeded
past both modified sites (lines 449 and 470) before failing at an
unrelated kzalloc_flex() call at line 4351 -- a symbol present in
wireless-next but absent from the installed headers. The patch changes
themselves compiled without error or warning.

Full module build requires a matching kernel build tree. A complete
compile-tested version can be provided if the approach is accepted.

The patch passes scripts/checkpatch.pl --strict with zero errors,
warnings, or checks.

RFC rationale
-------------
First submission from this contributor. Submitting as RFC to invite
feedback on the abstraction approach before requesting merge.


base-commit: 9ac76f3d0bb2940db3a9684d596b9c8f301ef315
-- 
2.43.0




^ permalink raw reply

* [PATCH] iw: fix station dump beacon RX indent
From: Alex Gavin @ 2026-03-22 18:29 UTC (permalink / raw)
  To: johannes; +Cc: linux-wireless, Alex Gavin

Signed-off-by: Alex Gavin <a_gavin@icloud.com>
---
 station.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/station.c b/station.c
index 5adf6bf..0f992d5 100644
--- a/station.c
+++ b/station.c
@@ -354,7 +354,7 @@ static void print_nested_sta_handler(struct nlattr *link_sinfo[NL80211_STA_INFO_
 		printf("%sbeacon loss:\t%u", indent,
 		       nla_get_u32(link_sinfo[NL80211_STA_INFO_BEACON_LOSS]));
 	if (link_sinfo[NL80211_STA_INFO_BEACON_RX])
-		printf("\n\t\tbeacon rx:\t%llu",
+		printf("%sbeacon rx:\t%llu", indent,
 		       (unsigned long long)nla_get_u64(link_sinfo[NL80211_STA_INFO_BEACON_RX]));
 	if (link_sinfo[NL80211_STA_INFO_RX_DROP_MISC])
 		printf("%srx drop misc:\t%llu", indent,
-- 
2.51.2


^ permalink raw reply related

* [PATCH 2/2] ath10k: force passive scan on 5GHz for WCN3990
From: Malte Schababerle @ 2026-03-22 12:48 UTC (permalink / raw)
  To: Jeff Johnson; +Cc: linux-wireless, ath10k, Malte Schababerle
In-Reply-To: <20260322124822.230492-1-m.schababerle@gmail.com>

WCN3990 firmware (WLAN.HL.3.2) has a bug where active scan does not
tune the radio on 5GHz non-DFS channels. The radio reports identical
rx_clear_count values across channels, indicating no RF tuning occurs.
As a result, no 5GHz networks are discovered during active scan.

Passive scan works correctly on all 5GHz channels: the radio tunes
properly and rx_clear_count varies as expected.

Force passive scan mode for all 5GHz channels on WCN3990 using
QCA_REV_WCN3990(). DFS channels are already passive, so the effective
change is for non-DFS 5GHz channels only. This follows the pattern
established for WCN3990 firmware workarounds in thermal.c.

Tested on OnePlus 7T (SM8150/WCN3990) with WLAN.HL.3.2.0.c2-00006
and WLAN.HL.3.2.0.c2-00011:
- 5GHz passive scan discovers APs reliably (e.g. ch116/5580 MHz)
- Association to 5GHz networks succeeds
- No change to 2.4GHz or DFS channel behavior

Signed-off-by: Malte Schababerle <m.schababerle@gmail.com>
---
 drivers/net/wireless/ath/ath10k/mac.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/drivers/net/wireless/ath/ath10k/mac.c b/drivers/net/wireless/ath/ath10k/mac.c
index 24dd794e31ea2..f6829232f6b15 100644
--- a/drivers/net/wireless/ath/ath10k/mac.c
+++ b/drivers/net/wireless/ath/ath10k/mac.c
@@ -3441,6 +3441,14 @@ static int ath10k_update_channel_list(struct ath10k *ar)
 			passive = channel->flags & IEEE80211_CHAN_NO_IR;
 			ch->passive = passive;
 
+			/* Force passive scan on 5GHz to work around WCN3990
+			 * firmware bug where active scan doesn't tune the
+			 * radio on 5GHz non-DFS channels.
+			 */
+			if (QCA_REV_WCN3990(ar) &&
+			    band == NL80211_BAND_5GHZ)
+				ch->passive = true;
+
 			/* the firmware is ignoring the "radar" flag of the
 			 * channel and is scanning actively using Probe Requests
 			 * on "Radar detection"/DFS channels which are not
-- 
2.47.3


^ permalink raw reply related

* [PATCH 1/2] ath10k: skip quiet mode for WCN3990 to prevent firmware crash
From: Malte Schababerle @ 2026-03-22 12:48 UTC (permalink / raw)
  To: Jeff Johnson; +Cc: linux-wireless, ath10k, Malte Schababerle
In-Reply-To: <20260322124822.230492-1-m.schababerle@gmail.com>

WCN3990 firmware (WLAN.HL.3.2) crashes deterministically when the
quiet mode WMI command is sent during ath10k_start(). The crash occurs
at PC=0xb0008e20 in wlanmdsp.mbn, ~17ms after the subsequent
vdev_create command, and cascades into a full modem crash.

Commit 53884577fbcef ("ath10k: skip sending quiet mode cmd for
WCN3990") addressed this for HL2.0 firmware by gating quiet mode on
WMI_SERVICE_THERM_THROT. HL2.0 did not advertise the service bit, so
the guard was effective. However, newer WCN3990 firmware (HL3.2)
erroneously advertises WMI_SERVICE_THERM_THROT via its TLV service
map despite still being unable to handle the quiet mode command.

Skip quiet mode unconditionally for WCN3990 using QCA_REV_WCN3990()
instead of relying on the service bit.

Tested on OnePlus 7T (SM8150/WCN3990) with WLAN.HL.3.2.0.c2-00006
and WLAN.HL.3.2.0.c2-00011 (both crash without patch, both work with):
- wlan0 comes up without crash
- WiFi scanning works
- NetworkManager recognizes the interface

Fixes: 53884577fbcef ("ath10k: skip sending quiet mode cmd for WCN3990")
Signed-off-by: Malte Schababerle <m.schababerle@gmail.com>
---
 drivers/net/wireless/ath/ath10k/thermal.c | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/drivers/net/wireless/ath/ath10k/thermal.c b/drivers/net/wireless/ath/ath10k/thermal.c
index 8b15ec07b1071..33f299f414710 100644
--- a/drivers/net/wireless/ath/ath10k/thermal.c
+++ b/drivers/net/wireless/ath/ath10k/thermal.c
@@ -136,6 +136,16 @@ void ath10k_thermal_set_throttling(struct ath10k *ar)
 	if (!ar->wmi.ops->gen_pdev_set_quiet_mode)
 		return;
 
+	/* WCN3990 firmware crashes on quiet mode despite advertising support.
+	 * See also commit 53884577fbcef ("ath10k: skip sending quiet mode
+	 * cmd for WCN3990").
+	 */
+	if (QCA_REV_WCN3990(ar)) {
+		ath10k_dbg(ar, ATH10K_DBG_BOOT,
+			   "skip quiet mode for WCN3990 (known crash trigger)\n");
+		return;
+	}
+
 	if (ar->state != ATH10K_STATE_ON)
 		return;
 
-- 
2.47.3


^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox