Linux wireless drivers development
 help / color / mirror / Atom feed
* [RFC PATCH wireless-next 00/17] wifi: add a driver for Broadcom BCM4323x USB adapters
@ 2026-10-26  5:36 Ragul Balaji
  2026-10-26  5:36 ` [RFC PATCH wireless-next 01/17] wifi: brcmsmac: make d11.h self-contained Ragul Balaji
                   ` (16 more replies)
  0 siblings, 17 replies; 18+ messages in thread
From: Ragul Balaji @ 2026-10-26  5:36 UTC (permalink / raw)
  To: linux-wireless; +Cc: Arend van Spriel, Johannes Berg, brcm80211, Ragul Balaji

This series adds support for the following 802.11n USB adapters:
- Broadcom BCM43231 (Netgear WNA3100, Dynex DX-NUSB)
- BCM4323 (Netgear WNDA3100v2, Belkin F9L1101 and others)


The protocol was inferred from USB captures of the vendor's
Windows driver, cross referenced with code from brcmsmac, brcmfmac and b43.
The code which are copied almost as-is from the referenced drivers
are indicated in the comments of the code

The driver supports:

- station, P2P client and monitor interfaces
- HT20 and HT40, A-MPDU and A-MSDU transmission
- 2.4 GHz channels 1-14 and, on the BCM4323, 5 GHz channels 36-165
- hardware CCMP with management frame protection
- the LEDs and the WPS button

Power save, AP, P2P GO, IBSS and mesh interfaces and hardware
TKIP/WEP can be added in later patches

The series
----------

Patches 1-10 prepare brcm80211, so that the driver shares brcmsmac's
and brcmfmac's code instead of copying it:

- 1-2 make d11.h self-contained and move it, with the transmit frame
  ID masks, to the shared include directory;
- 3-4 prepare brcmsmac's rate tables and rateset helpers for brcmutil;
- 5 fixes brcmsmac's L-SIG length of short-GI frames, which could be
  one symbol shorter than the frame;
- 6-7 make the PLCP and frame timing helpers take the band instead
  of brcmsmac's state, and move them to rate.c;
- 8 moves rate.c, with all of these tables and helpers, to brcmutil;
- 9 moves brcmfmac's USB bootloader definitions to a shared header, as
  these adapters boot through the same bootloader.
- 10 exports ieee80211_get_bssid() from mac80211 again, reverting
  391234eb4831 ("wifi: mac80211: unexport ieee80211_get_bssid()")
Apart from patch 5, these patches change no behaviour; patch 6 only
drops the device name from the helpers' error messages. The brcmsmac
and brcmfmac changes are compile-tested only.

Patches 11-17 add the driver, with the Kconfig, Makefile and MAINTAINERS
entry coming last.

Firmware
--------

The driver requires brcm/bcm4323x.bin. As captured from the 5.100.68.46
version of the Windows driver.
sha256 cc286a529396434a38e59382dcd46fa92a57f1cd1a38eea20d2b24dec028cf29
This firmware needs to be in linux-firmware.

Testing
-------

On a Belkin F9L1101 associated to a 5 GHz HT40 WPA2 access point,
single-stream TCP ran at 107-128 Mbit/s in both directions.

AI USE
------
Reverse engineering is done with assistance from LLMs.
Code has been reviewed us manually.

Ragul Balaji (17):
  wifi: brcmsmac: make d11.h self-contained
  wifi: brcm80211: move d11.h to the shared include directory
  wifi: brcmsmac: look up OFDM rates in rate.c
  wifi: brcmsmac: give the rate tables and helpers a brcmu_ prefix
  wifi: brcmsmac: round short-GI L-SIG lengths up to whole symbols
  wifi: brcmsmac: pass the band to the PLCP and frame timing helpers
  wifi: brcmsmac: move the PLCP and frame timing helpers to rate.c
  wifi: brcm80211: move the rate helpers of brcmsmac to brcmutil
  wifi: brcm80211: move the USB bootloader definitions to a shared
    header
  wifi: mac80211: export ieee80211_get_bssid() again
  wifi: bcm4323x: add the shared definitions
  wifi: bcm4323x: add the USB transport and firmware setup
  wifi: bcm4323x: add the receive path
  wifi: bcm4323x: add the transmit path
  wifi: bcm4323x: add the mac80211 operations
  wifi: bcm4323x: add LED and WPS button support
  wifi: bcm4323x: add Kconfig, Makefile and MAINTAINERS entry

 MAINTAINERS                                   |    7 +
 .../net/wireless/broadcom/brcm80211/Kconfig   |    1 +
 .../net/wireless/broadcom/brcm80211/Makefile  |    1 +
 .../broadcom/brcm80211/bcm4323x/Kconfig       |   12 +
 .../broadcom/brcm80211/bcm4323x/Makefile      |    6 +
 .../broadcom/brcm80211/bcm4323x/bcm4323x.h    |  258 +++
 .../brcm80211/bcm4323x/bcm4323x_core.c        |  545 ++++++
 .../brcm80211/bcm4323x/bcm4323x_led.c         |  221 +++
 .../brcm80211/bcm4323x/bcm4323x_mac.c         | 1495 +++++++++++++++++
 .../broadcom/brcm80211/bcm4323x/bcm4323x_rx.c |  317 ++++
 .../broadcom/brcm80211/bcm4323x/bcm4323x_tx.c | 1386 +++++++++++++++
 .../broadcom/brcm80211/brcmfmac/usb.c         |   68 +-
 .../broadcom/brcm80211/brcmsmac/Makefile      |    1 -
 .../broadcom/brcm80211/brcmsmac/ampdu.c       |   34 +-
 .../broadcom/brcm80211/brcmsmac/dma.h         |   27 -
 .../broadcom/brcm80211/brcmsmac/mac80211_if.c |    2 +-
 .../broadcom/brcm80211/brcmsmac/main.c        |  687 ++------
 .../broadcom/brcm80211/brcmsmac/main.h        |   26 +-
 .../broadcom/brcm80211/brcmsmac/phy/phy_cmn.c |   17 -
 .../broadcom/brcm80211/brcmsmac/phy/phy_hal.h |    3 +-
 .../broadcom/brcm80211/brcmsmac/pub.h         |   35 +-
 .../broadcom/brcm80211/brcmsmac/rate.c        |  514 ------
 .../broadcom/brcm80211/brcmsmac/stf.c         |    4 +-
 .../broadcom/brcm80211/brcmutil/Makefile      |    2 +-
 .../broadcom/brcm80211/brcmutil/rate.c        | 1027 +++++++++++
 .../broadcom/brcm80211/include/brcm_usb_rdl.h |   77 +
 .../{brcmsmac/rate.h => include/brcmu_rate.h} |  160 +-
 .../brcm80211/{brcmsmac => include}/d11.h     |   43 +-
 include/net/mac80211.h                        |   12 +
 net/mac80211/ieee80211_i.h                    |    2 -
 net/mac80211/util.c                           |    1 +
 31 files changed, 5649 insertions(+), 1342 deletions(-)
 create mode 100644 drivers/net/wireless/broadcom/brcm80211/bcm4323x/Kconfig
 create mode 100644 drivers/net/wireless/broadcom/brcm80211/bcm4323x/Makefile
 create mode 100644 drivers/net/wireless/broadcom/brcm80211/bcm4323x/bcm4323x.h
 create mode 100644 drivers/net/wireless/broadcom/brcm80211/bcm4323x/bcm4323x_core.c
 create mode 100644 drivers/net/wireless/broadcom/brcm80211/bcm4323x/bcm4323x_led.c
 create mode 100644 drivers/net/wireless/broadcom/brcm80211/bcm4323x/bcm4323x_mac.c
 create mode 100644 drivers/net/wireless/broadcom/brcm80211/bcm4323x/bcm4323x_rx.c
 create mode 100644 drivers/net/wireless/broadcom/brcm80211/bcm4323x/bcm4323x_tx.c
 delete mode 100644 drivers/net/wireless/broadcom/brcm80211/brcmsmac/rate.c
 create mode 100644 drivers/net/wireless/broadcom/brcm80211/brcmutil/rate.c
 create mode 100644 drivers/net/wireless/broadcom/brcm80211/include/brcm_usb_rdl.h
 rename drivers/net/wireless/broadcom/brcm80211/{brcmsmac/rate.h => include/brcmu_rate.h} (53%)
 rename drivers/net/wireless/broadcom/brcm80211/{brcmsmac => include}/d11.h (97%)


base-commit: 734bef548a96e8774d6eef5dfa2026c6edc4e80b
-- 
2.53.0


^ permalink raw reply	[flat|nested] 18+ messages in thread

* [RFC PATCH wireless-next 01/17] wifi: brcmsmac: make d11.h self-contained
  2026-10-26  5:36 [RFC PATCH wireless-next 00/17] wifi: add a driver for Broadcom BCM4323x USB adapters Ragul Balaji
@ 2026-10-26  5:36 ` Ragul Balaji
  2026-10-26  5:36 ` [RFC PATCH wireless-next 02/17] wifi: brcm80211: move d11.h to the shared include directory Ragul Balaji
                   ` (15 subsequent siblings)
  16 siblings, 0 replies; 18+ messages in thread
From: Ragul Balaji @ 2026-10-26  5:36 UTC (permalink / raw)
  To: linux-wireless
  Cc: Arend van Spriel, Johannes Berg, brcm80211, Ragul Balaji,
	Ho Jie Feng, brcm80211-dev-list.pdl, linux-kernel

d11.h includes pub.h and dma.h, but it needs only the two DMA register
layouts from dma.h for struct d11regs and nothing from pub.h. Move
struct dma32diag and struct dma64regs into d11.h next to the register
map that embeds them, and drop both includes.

The TXFID_* masks describe the TxFrameID field of struct d11txh.
Define them in d11.h next to the header that carries the field,
so that it can be shared with other code.

pub.h and d11.h both define the PHY types of the PhyVersion register,
with the same values. Add the ones that only pub.h has, PHY_TYPE_A,
PHY_TYPE_G, PHY_TYPE_LP and PHY_TYPE_HT, to d11.h, and drop the list
in pub.h.

This prepares d11.h for use outside brcmsmac. No functional change.

Signed-off-by: Ragul Balaji <ragulbaby@gmail.com>
Signed-off-by: Ho Jie Feng <hjf3108@gmail.com>
---
 .../broadcom/brcm80211/brcmsmac/ampdu.c       |  1 +
 .../broadcom/brcm80211/brcmsmac/d11.h         | 43 ++++++++++++++++++-
 .../broadcom/brcm80211/brcmsmac/dma.h         | 27 ------------
 .../broadcom/brcm80211/brcmsmac/main.h        | 11 +----
 .../broadcom/brcm80211/brcmsmac/phy/phy_hal.h |  1 +
 .../broadcom/brcm80211/brcmsmac/pub.h         | 10 -----
 .../broadcom/brcm80211/brcmsmac/rate.h        |  1 +
 7 files changed, 45 insertions(+), 49 deletions(-)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/ampdu.c b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/ampdu.c
index fc7a5dd2e..b42e43c88 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/ampdu.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/ampdu.c
@@ -19,6 +19,7 @@
 #include "scb.h"
 #include "phy/phy_hal.h"
 #include "antsel.h"
+#include "dma.h"
 #include "main.h"
 #include "ampdu.h"
 #include "debug.h"
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/d11.h b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/d11.h
index 0c685eeae..78cbfceab 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/d11.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/d11.h
@@ -20,8 +20,33 @@
 #include <linux/ieee80211.h>
 
 #include <defs.h>
-#include "pub.h"
-#include "dma.h"
+
+/* DMA structure:
+ *  support two DMA engines: 32 bits address or 64 bit addressing
+ *  basic DMA register set is per channel(transmit or receive)
+ *  a pair of channels is defined for convenience
+ */
+
+/* 32 bits addressing */
+
+struct dma32diag {	/* diag access */
+	u32 fifoaddr;	/* diag address */
+	u32 fifodatalow;	/* low 32bits of data */
+	u32 fifodatahigh;	/* high 32bits of data */
+	u32 pad;		/* reserved */
+};
+
+/* 64 bits addressing */
+
+/* dma registers per channel(xmt or rcv) */
+struct dma64regs {
+	u32 control;	/* enable, et al */
+	u32 ptr;	/* last descriptor posted to chip */
+	u32 addrlow;	/* desc ring base address low 32-bits (8K aligned) */
+	u32 addrhigh;	/* desc ring base address bits 63:32 (8K aligned) */
+	u32 status0;	/* current descriptor, xmt state */
+	u32 status1;	/* active descriptor, xmt error */
+};
 
 /* RX FIFO numbers */
 #define	RX_FIFO			0	/* data and ctl frames */
@@ -658,8 +683,12 @@ struct d11regs {
 #define	PHY_TYPE(v)		((v & PV_PT_MASK) >> PV_PT_SHIFT)
 
 /*== phy types (PhyVersion:PhyType field) ==*/
+#define	PHY_TYPE_A		0	/* A-Phy value */
+#define	PHY_TYPE_G		2	/* G-Phy value */
 #define	PHY_TYPE_N		4	/* N-Phy value */
+#define	PHY_TYPE_LP		5	/* LP-Phy value */
 #define	PHY_TYPE_SSN		6	/* SSLPN-Phy value */
+#define	PHY_TYPE_HT		7	/* HT-Phy value */
 #define	PHY_TYPE_LCN		8	/* LCN-Phy value */
 #define	PHY_TYPE_LCNXN		9	/* LCNXN-Phy value */
 #define	PHY_TYPE_NULL		0xf	/* Invalid Phy value */
@@ -787,6 +816,16 @@ struct d11txh {
 
 #define	D11_TXH_LEN		112	/* bytes */
 
+/* TxFrameID */
+/* seq and frag bits: SEQNUM_SHIFT, FRAGNUM_MASK (802.11.h) */
+/* rate epoch bits: TXFID_RATE_SHIFT, TXFID_RATE_MASK ((wlc_rate.c) */
+#define TXFID_QUEUE_MASK	0x0007	/* Bits 0-2 */
+#define TXFID_SEQ_MASK		0x7FE0	/* Bits 5-15 */
+#define TXFID_SEQ_SHIFT		5	/* Number of bit shifts */
+#define	TXFID_RATE_PROBE_MASK	0x8000	/* Bit 15 for rate probe */
+#define TXFID_RATE_MASK		0x0018	/* Mask for bits 3 and 4 */
+#define TXFID_RATE_SHIFT	3	/* Shift 3 bits for rate mask */
+
 /* Frame Types */
 #define FT_CCK	0
 #define FT_OFDM	1
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/dma.h b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/dma.h
index 7905fd081..d2f00d699 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/dma.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/dma.h
@@ -25,33 +25,6 @@
 #define	DMA_TX	1		/* TX direction for DMA */
 #define	DMA_RX	2		/* RX direction for DMA */
 
-/* DMA structure:
- *  support two DMA engines: 32 bits address or 64 bit addressing
- *  basic DMA register set is per channel(transmit or receive)
- *  a pair of channels is defined for convenience
- */
-
-/* 32 bits addressing */
-
-struct dma32diag {	/* diag access */
-	u32 fifoaddr;	/* diag address */
-	u32 fifodatalow;	/* low 32bits of data */
-	u32 fifodatahigh;	/* high 32bits of data */
-	u32 pad;		/* reserved */
-};
-
-/* 64 bits addressing */
-
-/* dma registers per channel(xmt or rcv) */
-struct dma64regs {
-	u32 control;	/* enable, et al */
-	u32 ptr;	/* last descriptor posted to chip */
-	u32 addrlow;	/* desc ring base address low 32-bits (8K aligned) */
-	u32 addrhigh;	/* desc ring base address bits 63:32 (8K aligned) */
-	u32 status0;	/* current descriptor, xmt state */
-	u32 status1;	/* active descriptor, xmt error */
-};
-
 /* range param for dma_getnexttxp() and dma_txreclaim */
 enum txd_range {
 	DMA_RANGE_ALL = 1,
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/main.h b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/main.h
index 2d133264f..9cb09c8a1 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/main.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/main.h
@@ -22,6 +22,7 @@
 #include <brcmu_utils.h>
 #include "types.h"
 #include "d11.h"
+#include "pub.h"
 #include "scb.h"
 
 #define	INVCHANNEL		255	/* invalid channel */
@@ -85,16 +86,6 @@
 #define BRCMS_IS_MIMO_PREAMBLE(_pre) (((_pre) == BRCMS_GF_PREAMBLE) || \
 				      ((_pre) == BRCMS_MM_PREAMBLE))
 
-/* TxFrameID */
-/* seq and frag bits: SEQNUM_SHIFT, FRAGNUM_MASK (802.11.h) */
-/* rate epoch bits: TXFID_RATE_SHIFT, TXFID_RATE_MASK ((wlc_rate.c) */
-#define TXFID_QUEUE_MASK	0x0007	/* Bits 0-2 */
-#define TXFID_SEQ_MASK		0x7FE0	/* Bits 5-15 */
-#define TXFID_SEQ_SHIFT		5	/* Number of bit shifts */
-#define	TXFID_RATE_PROBE_MASK	0x8000	/* Bit 15 for rate probe */
-#define TXFID_RATE_MASK		0x0018	/* Mask for bits 3 and 4 */
-#define TXFID_RATE_SHIFT	3	/* Shift 3 bits for rate mask */
-
 /* promote boardrev */
 #define BOARDREV_PROMOTABLE	0xFF	/* from */
 #define BOARDREV_PROMOTED	1	/* to */
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/phy/phy_hal.h b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/phy/phy_hal.h
index cab08f066..7ffff7f51 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/phy/phy_hal.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/phy/phy_hal.h
@@ -10,6 +10,7 @@
 #ifndef _BRCM_PHY_HAL_H_
 #define _BRCM_PHY_HAL_H_
 
+#include <linux/bcma/bcma.h>
 #include <brcmu_utils.h>
 #include <brcmu_wifi.h>
 #include <phy_shim.h>
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/pub.h b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/pub.h
index bfc63b2f0..c366a7571 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/pub.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/pub.h
@@ -24,16 +24,6 @@
 
 #define	BRCMS_NUMRATES	16	/* max # of rates in a rateset */
 
-/* phy types */
-#define	PHY_TYPE_A	0	/* Phy type A */
-#define	PHY_TYPE_G	2	/* Phy type G */
-#define	PHY_TYPE_N	4	/* Phy type N */
-#define	PHY_TYPE_LP	5	/* Phy type Low Power A/B/G */
-#define	PHY_TYPE_SSN	6	/* Phy type Single Stream N */
-#define	PHY_TYPE_LCN	8	/* Phy type Single Stream N */
-#define	PHY_TYPE_LCNXN	9	/* Phy type 2-stream N */
-#define	PHY_TYPE_HT	7	/* Phy type 3-Stream N */
-
 /* bw */
 #define BRCMS_10_MHZ	10	/* 10Mhz nphy channel bandwidth */
 #define BRCMS_20_MHZ	20	/* 20Mhz nphy channel bandwidth */
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/rate.h b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/rate.h
index 5bb88b78e..2b10fd542 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/rate.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/rate.h
@@ -19,6 +19,7 @@
 
 #include "types.h"
 #include "d11.h"
+#include "pub.h"
 #include "phy_hal.h"
 
 extern const u8 rate_info[];
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [RFC PATCH wireless-next 02/17] wifi: brcm80211: move d11.h to the shared include directory
  2026-10-26  5:36 [RFC PATCH wireless-next 00/17] wifi: add a driver for Broadcom BCM4323x USB adapters Ragul Balaji
  2026-10-26  5:36 ` [RFC PATCH wireless-next 01/17] wifi: brcmsmac: make d11.h self-contained Ragul Balaji
@ 2026-10-26  5:36 ` Ragul Balaji
  2026-10-26  5:36 ` [RFC PATCH wireless-next 03/17] wifi: brcmsmac: look up OFDM rates in rate.c Ragul Balaji
                   ` (14 subsequent siblings)
  16 siblings, 0 replies; 18+ messages in thread
From: Ragul Balaji @ 2026-10-26  5:36 UTC (permalink / raw)
  To: linux-wireless
  Cc: Arend van Spriel, Johannes Berg, brcm80211, Ragul Balaji,
	Ho Jie Feng, brcm80211-dev-list.pdl, linux-kernel

Move the header to brcm80211/include, where the other hardware
definitions shared between the brcm80211 drivers live, and include it
as <d11.h> in brcmsmac.

Signed-off-by: Ragul Balaji <ragulbaby@gmail.com>
Signed-off-by: Ho Jie Feng <hjf3108@gmail.com>
---
 drivers/net/wireless/broadcom/brcm80211/brcmsmac/mac80211_if.c  | 2 +-
 drivers/net/wireless/broadcom/brcm80211/brcmsmac/main.h         | 2 +-
 drivers/net/wireless/broadcom/brcm80211/brcmsmac/rate.c         | 2 +-
 drivers/net/wireless/broadcom/brcm80211/brcmsmac/rate.h         | 2 +-
 drivers/net/wireless/broadcom/brcm80211/brcmsmac/stf.c          | 2 +-
 .../net/wireless/broadcom/brcm80211/{brcmsmac => include}/d11.h | 0
 6 files changed, 5 insertions(+), 5 deletions(-)
 rename drivers/net/wireless/broadcom/brcm80211/{brcmsmac => include}/d11.h (100%)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/mac80211_if.c b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/mac80211_if.c
index c1a2318d7..eeae85cae 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/mac80211_if.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/mac80211_if.c
@@ -28,7 +28,7 @@
 #include <net/mac80211.h>
 #include <defs.h>
 #include "phy/phy_int.h"
-#include "d11.h"
+#include <d11.h>
 #include "channel.h"
 #include "scb.h"
 #include "pub.h"
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/main.h b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/main.h
index 9cb09c8a1..d81de701a 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/main.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/main.h
@@ -21,7 +21,7 @@
 
 #include <brcmu_utils.h>
 #include "types.h"
-#include "d11.h"
+#include <d11.h>
 #include "pub.h"
 #include "scb.h"
 
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/rate.c b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/rate.c
index 0a0c0ad4f..6841bc005 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/rate.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/rate.c
@@ -17,7 +17,7 @@
 #include <brcmu_wifi.h>
 #include <brcmu_utils.h>
 
-#include "d11.h"
+#include <d11.h>
 #include "pub.h"
 #include "rate.h"
 
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/rate.h b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/rate.h
index 2b10fd542..e178b1a0e 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/rate.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/rate.h
@@ -18,7 +18,7 @@
 #define _BRCM_RATE_H_
 
 #include "types.h"
-#include "d11.h"
+#include <d11.h>
 #include "pub.h"
 #include "phy_hal.h"
 
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/stf.c b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/stf.c
index 79d4a7a4d..6e92dc4c9 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/stf.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/stf.c
@@ -17,7 +17,7 @@
 #include <net/mac80211.h>
 
 #include "types.h"
-#include "d11.h"
+#include <d11.h>
 #include "rate.h"
 #include "phy/phy_hal.h"
 #include "channel.h"
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/d11.h b/drivers/net/wireless/broadcom/brcm80211/include/d11.h
similarity index 100%
rename from drivers/net/wireless/broadcom/brcm80211/brcmsmac/d11.h
rename to drivers/net/wireless/broadcom/brcm80211/include/d11.h
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [RFC PATCH wireless-next 03/17] wifi: brcmsmac: look up OFDM rates in rate.c
  2026-10-26  5:36 [RFC PATCH wireless-next 00/17] wifi: add a driver for Broadcom BCM4323x USB adapters Ragul Balaji
  2026-10-26  5:36 ` [RFC PATCH wireless-next 01/17] wifi: brcmsmac: make d11.h self-contained Ragul Balaji
  2026-10-26  5:36 ` [RFC PATCH wireless-next 02/17] wifi: brcm80211: move d11.h to the shared include directory Ragul Balaji
@ 2026-10-26  5:36 ` Ragul Balaji
  2026-10-26  5:36 ` [RFC PATCH wireless-next 04/17] wifi: brcmsmac: give the rate tables and helpers a brcmu_ prefix Ragul Balaji
                   ` (13 subsequent siblings)
  16 siblings, 0 replies; 18+ messages in thread
From: Ragul Balaji @ 2026-10-26  5:36 UTC (permalink / raw)
  To: linux-wireless
  Cc: Arend van Spriel, Johannes Berg, brcm80211, Ragul Balaji,
	Ho Jie Feng, brcm80211-dev-list.pdl, linux-kernel

Move the OFDM rate table next to its only user, ofdm_phy2mac_rate(),
which leaves rate.c independent of the PHY code. No functional change.

Signed-off-by: Ragul Balaji <ragulbaby@gmail.com>
Signed-off-by: Ho Jie Feng <hjf3108@gmail.com>
---
 .../broadcom/brcm80211/brcmsmac/phy/phy_cmn.c | 17 -----------------
 .../broadcom/brcm80211/brcmsmac/phy/phy_hal.h |  2 --
 .../broadcom/brcm80211/brcmsmac/rate.c        | 19 +++++++++++++++++++
 .../broadcom/brcm80211/brcmsmac/rate.h        |  7 -------
 4 files changed, 19 insertions(+), 26 deletions(-)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/phy/phy_cmn.c b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/phy/phy_cmn.c
index 7eae73ef7..d519e950c 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/phy/phy_cmn.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/phy/phy_cmn.c
@@ -101,18 +101,6 @@ static const struct chan_info_basic chan_info_all[] = {
 	{216, 5080}
 };
 
-static const u8 ofdm_rate_lookup[] = {
-
-	BRCM_RATE_48M,
-	BRCM_RATE_24M,
-	BRCM_RATE_12M,
-	BRCM_RATE_6M,
-	BRCM_RATE_54M,
-	BRCM_RATE_36M,
-	BRCM_RATE_18M,
-	BRCM_RATE_9M
-};
-
 #define PHY_WREG_LIMIT  24
 
 void wlc_phyreg_enter(struct brcms_phy_pub *pih)
@@ -2486,11 +2474,6 @@ u8 wlc_phy_stf_chain_active_get(struct brcms_phy_pub *pih)
 	return active_bitmap;
 }
 
-const u8 *wlc_phy_get_ofdm_rate_lookup(void)
-{
-	return ofdm_rate_lookup;
-}
-
 void wlc_phy_ldpc_override_set(struct brcms_phy_pub *ppi, bool ldpc)
 {
 	return;
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/phy/phy_hal.h b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/phy/phy_hal.h
index 7ffff7f51..c5aaaec43 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/phy/phy_hal.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/phy/phy_hal.h
@@ -234,8 +234,6 @@ void wlc_phy_ofdm_rateset_war(struct brcms_phy_pub *pih, bool war);
 void wlc_phy_bf_preempt_enable(struct brcms_phy_pub *pih, bool bf_preempt);
 void wlc_phy_machwcap_set(struct brcms_phy_pub *ppi, u32 machwcap);
 
-const u8 *wlc_phy_get_ofdm_rate_lookup(void);
-
 s8 wlc_phy_get_tx_power_offset_by_mcs(struct brcms_phy_pub *ppi,
 				      u8 mcs_offset);
 s8 wlc_phy_get_tx_power_offset(struct brcms_phy_pub *ppi, u8 tbl_offset);
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/rate.c b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/rate.c
index 6841bc005..ac577c8e8 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/rate.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/rate.c
@@ -266,6 +266,25 @@ const struct brcms_c_rateset cck_rates = {
 	  0x00, 0x00, 0x00, 0x00, 0x00}
 };
 
+static const u8 ofdm_rate_lookup[] = {
+
+	BRCM_RATE_48M,
+	BRCM_RATE_24M,
+	BRCM_RATE_12M,
+	BRCM_RATE_6M,
+	BRCM_RATE_54M,
+	BRCM_RATE_36M,
+	BRCM_RATE_18M,
+	BRCM_RATE_9M
+};
+
+/* Convert encoded rate value in plcp header to numerical rates in 500 KHz
+ * increments */
+static inline u8 ofdm_phy2mac_rate(u8 rlpt)
+{
+	return ofdm_rate_lookup[rlpt & 0x7];
+}
+
 /* check if rateset is valid.
  * if check_brate is true, rateset without a basic rate is considered NOT valid.
  */
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/rate.h b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/rate.h
index e178b1a0e..7c1d43323 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/rate.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/rate.h
@@ -198,13 +198,6 @@ static inline u8 cck_rspec(u8 cck)
 	return cck & RSPEC_RATE_MASK;
 }
 
-/* Convert encoded rate value in plcp header to numerical rates in 500 KHz
- * increments */
-static inline u8 ofdm_phy2mac_rate(u8 rlpt)
-{
-	return wlc_phy_get_ofdm_rate_lookup()[rlpt & 0x7];
-}
-
 static inline u8 cck_phy2mac_rate(u8 signal)
 {
 	return signal/5;
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [RFC PATCH wireless-next 04/17] wifi: brcmsmac: give the rate tables and helpers a brcmu_ prefix
  2026-10-26  5:36 [RFC PATCH wireless-next 00/17] wifi: add a driver for Broadcom BCM4323x USB adapters Ragul Balaji
                   ` (2 preceding siblings ...)
  2026-10-26  5:36 ` [RFC PATCH wireless-next 03/17] wifi: brcmsmac: look up OFDM rates in rate.c Ragul Balaji
@ 2026-10-26  5:36 ` Ragul Balaji
  2026-10-26  5:36 ` [RFC PATCH wireless-next 05/17] wifi: brcmsmac: round short-GI L-SIG lengths up to whole symbols Ragul Balaji
                   ` (12 subsequent siblings)
  16 siblings, 0 replies; 18+ messages in thread
From: Ragul Balaji @ 2026-10-26  5:36 UTC (permalink / raw)
  To: linux-wireless
  Cc: Arend van Spriel, Johannes Berg, brcm80211, Ragul Balaji,
	Ho Jie Feng, brcm80211-dev-list.pdl, linux-kernel

Prepare rate.c for move to brcmutil, which exports them.
Rename brcms_c_ to brcmu_.

Signed-off-by: Ragul Balaji <ragulbaby@gmail.com>
Signed-off-by: Ho Jie Feng <hjf3108@gmail.com>
---
 .../broadcom/brcm80211/brcmsmac/main.c        | 87 +++++++++---------
 .../broadcom/brcm80211/brcmsmac/rate.c        | 88 +++++++++----------
 .../broadcom/brcm80211/brcmsmac/rate.h        | 68 +++++++-------
 3 files changed, 121 insertions(+), 122 deletions(-)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/main.c b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/main.c
index ec3aeaa7b..3a383b0d1 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/main.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/main.c
@@ -352,7 +352,7 @@ static u8 brcms_fifo_to_ac(u8 fifo)
 static u8 brcms_basic_rate(struct brcms_c_info *wlc, u32 rspec)
 {
 	if (is_mcs_rate(rspec))
-		return wlc->band->basic_rate[mcs_table[rspec & RSPEC_RATE_MASK]
+		return wlc->band->basic_rate[brcmu_mcs_table[rspec & RSPEC_RATE_MASK]
 		       .leg_ofdm];
 	return wlc->band->basic_rate[rspec & RSPEC_RATE_MASK];
 }
@@ -3361,11 +3361,11 @@ static void brcms_c_set_phy_chanspec(struct brcms_c_info *wlc,
 static void
 brcms_default_rateset(struct brcms_c_info *wlc, struct brcms_c_rateset *rs)
 {
-	brcms_c_rateset_default(rs, NULL, wlc->band->phytype,
-		wlc->band->bandtype, false, BRCMS_RATE_MASK_FULL,
-		(bool) (wlc->pub->_n_enab & SUPPORT_11N),
-		brcms_chspec_bw(wlc->default_bss->chanspec),
-		wlc->stf->txstreams);
+	brcmu_rateset_default(rs, NULL, wlc->band->phytype,
+			      wlc->band->bandtype, false, BRCMS_RATE_MASK_FULL,
+			      (bool)(wlc->pub->_n_enab & SUPPORT_11N),
+			      brcms_chspec_bw(wlc->default_bss->chanspec),
+			      wlc->stf->txstreams);
 }
 
 /* derive wlc->band->basic_rate[] table from 'rateset' */
@@ -3496,9 +3496,9 @@ static void brcms_c_bandinit_ordered(struct brcms_c_info *wlc,
 		brcms_default_rateset(wlc, &default_rateset);
 
 		/* fill in hw_rate */
-		brcms_c_rateset_filter(&default_rateset, &wlc->band->hw_rateset,
-				   false, BRCMS_RATES_CCK_OFDM, BRCMS_RATE_MASK,
-				   (bool) (wlc->pub->_n_enab & SUPPORT_11N));
+		brcmu_rateset_filter(&default_rateset, &wlc->band->hw_rateset,
+				     false, BRCMS_RATES_CCK_OFDM, BRCMS_RATE_MASK,
+				     (bool)(wlc->pub->_n_enab & SUPPORT_11N));
 
 		/* init basic rate lookup */
 		brcms_c_rate_lookup_init(wlc, &default_rateset);
@@ -3583,8 +3583,8 @@ static void brcms_c_write_rate_shm(struct brcms_c_info *wlc, u8 rate,
 	 * for a given rate, the LS-nibble of the PLCP SIGNAL field is
 	 * the index into the rate table.
 	 */
-	phy_rate = rate_info[rate] & BRCMS_RATE_MASK;
-	basic_phy_rate = rate_info[basic_rate] & BRCMS_RATE_MASK;
+	phy_rate = brcmu_rate_info[rate] & BRCMS_RATE_MASK;
+	basic_phy_rate = brcmu_rate_info[basic_rate] & BRCMS_RATE_MASK;
 	index = phy_rate & 0xf;
 	basic_index = basic_phy_rate & 0xf;
 
@@ -3606,13 +3606,13 @@ brcms_c_rateset_get_hwrs(struct brcms_c_info *wlc)
 
 	if (BRCMS_PHY_11N_CAP(wlc->band)) {
 		if (wlc->band->bandtype == BRCM_BAND_5G)
-			rs_dflt = &ofdm_mimo_rates;
+			rs_dflt = &brcmu_ofdm_mimo_rates;
 		else
-			rs_dflt = &cck_ofdm_mimo_rates;
+			rs_dflt = &brcmu_cck_ofdm_mimo_rates;
 	} else if (wlc->band->gmode)
-		rs_dflt = &cck_ofdm_rates;
+		rs_dflt = &brcmu_cck_ofdm_rates;
 	else
-		rs_dflt = &cck_rates;
+		rs_dflt = &brcmu_cck_rates;
 
 	return rs_dflt;
 }
@@ -3626,8 +3626,8 @@ static void brcms_c_set_ratetable(struct brcms_c_info *wlc)
 
 	rs_dflt = brcms_c_rateset_get_hwrs(wlc);
 
-	brcms_c_rateset_copy(rs_dflt, &rs);
-	brcms_c_rateset_mcs_upd(&rs, wlc->stf->txstreams);
+	brcmu_rateset_copy(rs_dflt, &rs);
+	brcmu_rateset_mcs_upd(&rs, wlc->stf->txstreams);
 
 	/* walk the phy rate table and update SHM basic rate lookup table */
 	for (i = 0; i < rs.count; i++) {
@@ -3894,8 +3894,9 @@ static void brcms_c_set_chanspec(struct brcms_c_info *wlc, u16 chanspec)
 		/* Fix the hardware rateset based on bw.
 		 * Mainly add MCS32 for 40Mhz, remove MCS 32 for 20Mhz
 		 */
-		brcms_c_rateset_bw_mcs_filter(&wlc->band->hw_rateset,
-			wlc->band->mimo_cap_40 ? brcms_chspec_bw(chanspec) : 0);
+		brcmu_rateset_bw_mcs_filter(&wlc->band->hw_rateset,
+					    wlc->band->mimo_cap_40 ?
+					    brcms_chspec_bw(chanspec) : 0);
 	}
 
 	/* update some mac configuration since chanspec changed */
@@ -4712,10 +4713,10 @@ static void brcms_c_bss_default_init(struct brcms_c_info *wlc)
 		band = wlc->bandstate[OTHERBANDUNIT(wlc)];
 
 	/* init bss rates to the band specific default rate set */
-	brcms_c_rateset_default(&bi->rateset, NULL, band->phytype,
-		band->bandtype, false, BRCMS_RATE_MASK_FULL,
-		(bool) (wlc->pub->_n_enab & SUPPORT_11N),
-		brcms_chspec_bw(chanspec), wlc->stf->txstreams);
+	brcmu_rateset_default(&bi->rateset, NULL, band->phytype,
+			      band->bandtype, false, BRCMS_RATE_MASK_FULL,
+			      (bool)(wlc->pub->_n_enab & SUPPORT_11N),
+			      brcms_chspec_bw(chanspec), wlc->stf->txstreams);
 
 	if (wlc->pub->_n_enab & SUPPORT_11N)
 		bi->flags |= BRCMS_BSS_HT;
@@ -5207,7 +5208,7 @@ int brcms_c_set_gmode(struct brcms_c_info *wlc, u8 gmode, bool config)
 	switch (gmode) {
 	case GMODE_LEGACY_B:
 		shortslot = BRCMS_SHORTSLOT_OFF;
-		brcms_c_rateset_copy(&gphy_legacy_rates, &rs);
+		brcmu_rateset_copy(&brcmu_gphy_legacy_rates, &rs);
 
 		break;
 
@@ -5240,7 +5241,7 @@ int brcms_c_set_gmode(struct brcms_c_info *wlc, u8 gmode, bool config)
 
 	/* Use the default 11g rateset */
 	if (!rs.count)
-		brcms_c_rateset_copy(&cck_ofdm_rates, &rs);
+		brcmu_rateset_copy(&brcmu_cck_ofdm_rates, &rs);
 
 	if (ofdm_basic) {
 		for (i = 0; i < rs.count; i++) {
@@ -5277,8 +5278,8 @@ int brcms_c_set_nmode(struct brcms_c_info *wlc)
 		wlc->pub->_n_enab = SUPPORT_11N;
 	wlc->default_bss->flags |= BRCMS_BSS_HT;
 	/* add the mcs rates to the default and hw ratesets */
-	brcms_c_rateset_mcs_build(&wlc->default_bss->rateset,
-			      wlc->stf->txstreams);
+	brcmu_rateset_mcs_build(&wlc->default_bss->rateset,
+				wlc->stf->txstreams);
 	for (i = 0; i < wlc->pub->_nbands; i++)
 		memcpy(wlc->bandstate[i]->hw_rateset.mcs,
 		       wlc->default_bss->rateset.mcs, MCSSET_LEN);
@@ -5302,7 +5303,7 @@ brcms_c_set_internal_rateset(struct brcms_c_info *wlc,
 	/* try the current band */
 	bandunit = wlc->band->bandunit;
 	memcpy(&new, &rs, sizeof(struct brcms_c_rateset));
-	if (brcms_c_rate_hwrs_filter_sort_validate
+	if (brcmu_rate_hwrs_filter_sort_validate
 	    (&new, &wlc->bandstate[bandunit]->hw_rateset, true,
 	     wlc->stf->txstreams))
 		goto good;
@@ -5311,11 +5312,9 @@ brcms_c_set_internal_rateset(struct brcms_c_info *wlc,
 	if (brcms_is_mband_unlocked(wlc)) {
 		bandunit = OTHERBANDUNIT(wlc);
 		memcpy(&new, &rs, sizeof(struct brcms_c_rateset));
-		if (brcms_c_rate_hwrs_filter_sort_validate(&new,
-						       &wlc->
-						       bandstate[bandunit]->
-						       hw_rateset, true,
-						       wlc->stf->txstreams))
+		if (brcmu_rate_hwrs_filter_sort_validate(&new,
+							 &wlc->bandstate[bandunit]->hw_rateset,
+							 true, wlc->stf->txstreams))
 			goto good;
 	}
 
@@ -5610,7 +5609,7 @@ u16 brcms_b_rate_shm_offset(struct brcms_hardware *wlc_hw, u8 rate)
 	/* for a given rate, the LS-nibble of the PLCP SIGNAL field is
 	 * the index into the rate table.
 	 */
-	phy_rate = rate_info[rate] & BRCMS_RATE_MASK;
+	phy_rate = brcmu_rate_info[rate] & BRCMS_RATE_MASK;
 	index = phy_rate & 0xf;
 
 	/* Find the SHM pointer to the rate table entry by looking in the
@@ -5984,7 +5983,7 @@ brcms_c_compute_ofdm_plcp(u32 rspec, u32 length, u8 *plcp)
 	 * encode rate per 802.11a-1999 sec 17.3.4.1, with lsb
 	 * transmitted first
 	 */
-	rate_signal = rate_info[rate] & BRCMS_RATE_MASK;
+	rate_signal = brcmu_rate_info[rate] & BRCMS_RATE_MASK;
 	memset(plcp, 0, D11_PHY_HDR_LEN);
 	D11A_PHY_HDR_SRATE((struct ofdm_phy_hdr *) plcp, rate_signal);
 
@@ -6084,7 +6083,7 @@ static u16 brcms_c_phytxctl1_calc(struct brcms_c_info *wlc, u32 rspec)
 		/* bw, stf, coding-type is part of rspec_phytxbyte2 returns */
 		phyctl1 = rspec_phytxbyte2(rspec);
 		/* set the upper byte of phyctl1 */
-		phyctl1 |= (mcs_table[mcs].tx_phy_ctl3 << 8);
+		phyctl1 |= (brcmu_mcs_table[mcs].tx_phy_ctl3 << 8);
 	} else if (is_cck_rate(rspec) && !BRCMS_ISLCNPHY(wlc->band)
 		   && !BRCMS_ISSSLPNPHY(wlc->band)) {
 		/*
@@ -6097,7 +6096,7 @@ static u16 brcms_c_phytxctl1_calc(struct brcms_c_info *wlc, u32 rspec)
 	} else {		/* legacy OFDM/CCK */
 		s16 phycfg;
 		/* get the phyctl byte from rate phycfg table */
-		phycfg = brcms_c_rate_legacy_phyctl(rspec2rate(rspec));
+		phycfg = brcmu_rate_legacy_phyctl(rspec2rate(rspec));
 		if (phycfg == -1) {
 			brcms_err(wlc->hw->d11core, "phytxctl1_calc: wrong "
 				  "legacy OFDM/CCK rate\n");
@@ -6988,7 +6987,7 @@ prep_mac80211_status(struct brcms_c_info *wlc, struct d11rxhdr *rxh,
 
 	plcp = p->data;
 
-	rspec = brcms_c_compute_rspec(rxh, plcp);
+	rspec = brcmu_compute_rspec(rxh, plcp);
 	if (is_mcs_rate(rspec)) {
 		rx_status->rate_idx = rspec & RSPEC_RATE_MASK;
 		rx_status->encoding = RX_ENC_HT;
@@ -7162,8 +7161,8 @@ brcms_c_mod_prb_rsp_rate_table(struct brcms_c_info *wlc, uint frame_len)
 
 	rs_dflt = brcms_c_rateset_get_hwrs(wlc);
 
-	brcms_c_rateset_copy(rs_dflt, &rs);
-	brcms_c_rateset_mcs_upd(&rs, wlc->stf->txstreams);
+	brcmu_rateset_copy(rs_dflt, &rs);
+	brcmu_rateset_mcs_upd(&rs, wlc->stf->txstreams);
 
 	/*
 	 * walk the phy rate table and update MAC core SHM
@@ -7958,10 +7957,10 @@ brcms_c_attach(struct brcms_info *wl, struct bcma_device *core, uint unit,
 		brcms_default_rateset(wlc, &wlc->band->defrateset);
 
 		/* fill in hw_rateset */
-		brcms_c_rateset_filter(&wlc->band->defrateset,
-				   &wlc->band->hw_rateset, false,
-				   BRCMS_RATES_CCK_OFDM, BRCMS_RATE_MASK,
-				   (bool) (wlc->pub->_n_enab & SUPPORT_11N));
+		brcmu_rateset_filter(&wlc->band->defrateset,
+				     &wlc->band->hw_rateset, false,
+				     BRCMS_RATES_CCK_OFDM, BRCMS_RATE_MASK,
+				     (bool)(wlc->pub->_n_enab & SUPPORT_11N));
 	}
 
 	/*
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/rate.c b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/rate.c
index ac577c8e8..95ccc1177 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/rate.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/rate.c
@@ -25,7 +25,7 @@
  * Rate info per rate: It tells whether a rate is ofdm or not and its phy_rate
  * value
  */
-const u8 rate_info[BRCM_MAXRATE + 1] = {
+const u8 brcmu_rate_info[BRCM_MAXRATE + 1] = {
 	/*  0     1     2     3     4     5     6     7     8     9 */
 /*   0 */ 0x00, 0x00, 0x0a, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00,
 /*  10 */ 0x00, 0x37, 0x8b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8f, 0x00,
@@ -41,7 +41,7 @@ const u8 rate_info[BRCM_MAXRATE + 1] = {
 };
 
 /* rates are in units of Kbps */
-const struct brcms_mcs_info mcs_table[MCS_TABLE_SIZE] = {
+const struct brcms_mcs_info brcmu_mcs_table[MCS_TABLE_SIZE] = {
 	/* MCS  0: SS 1, MOD: BPSK,  CR 1/2 */
 	{6500, 13500, CEIL(6500 * 10, 9), CEIL(13500 * 10, 9), 0x00,
 	 BRCM_RATE_6M},
@@ -187,7 +187,7 @@ legacy_phycfg legacy_phycfg_table[LEGACY_PHYCFG_TABLE_SIZE] = {
 
 /* Hardware rates (also encodes default basic rates) */
 
-const struct brcms_c_rateset cck_ofdm_mimo_rates = {
+const struct brcms_c_rateset brcmu_cck_ofdm_mimo_rates = {
 	12,
 	/*  1b,   2b,   5.5b, 6,    9,    11b,  12,   18,   24,   36,   48, */
 	{ 0x82, 0x84, 0x8b, 0x0c, 0x12, 0x96, 0x18, 0x24, 0x30, 0x48, 0x60,
@@ -198,7 +198,7 @@ const struct brcms_c_rateset cck_ofdm_mimo_rates = {
 	  0x00, 0x00, 0x00, 0x00, 0x00}
 };
 
-const struct brcms_c_rateset ofdm_mimo_rates = {
+const struct brcms_c_rateset brcmu_ofdm_mimo_rates = {
 	8,
 	/*  6b,   9,    12b,  18,   24b,  36,   48,   54 Mbps */
 	{ 0x8c, 0x12, 0x98, 0x24, 0xb0, 0x48, 0x60, 0x6c},
@@ -228,7 +228,7 @@ static const struct brcms_c_rateset ofdm_40bw_mimo_rates = {
 	  0x00, 0x00, 0x00, 0x00, 0x00}
 };
 
-const struct brcms_c_rateset cck_ofdm_rates = {
+const struct brcms_c_rateset brcmu_cck_ofdm_rates = {
 	12,
 	/*  1b,   2b, 5.5b, 6,    9,    11b,  12,   18,   24,   36,   48,*/
 	{ 0x82, 0x84, 0x8b, 0x0c, 0x12, 0x96, 0x18, 0x24, 0x30, 0x48, 0x60,
@@ -239,7 +239,7 @@ const struct brcms_c_rateset cck_ofdm_rates = {
 	  0x00, 0x00, 0x00, 0x00, 0x00}
 };
 
-const struct brcms_c_rateset gphy_legacy_rates = {
+const struct brcms_c_rateset brcmu_gphy_legacy_rates = {
 	4,
 	/*  1b,   2b,   5.5b, 11b Mbps */
 	{ 0x82, 0x84, 0x8b, 0x96},
@@ -248,7 +248,7 @@ const struct brcms_c_rateset gphy_legacy_rates = {
 	  0x00, 0x00, 0x00, 0x00, 0x00}
 };
 
-const struct brcms_c_rateset ofdm_rates = {
+const struct brcms_c_rateset brcmu_ofdm_rates = {
 	8,
 	/*  6b,   9,    12b,  18,   24b,  36,   48,   54 Mbps */
 	{ 0x8c, 0x12, 0x98, 0x24, 0xb0, 0x48, 0x60, 0x6c},
@@ -257,7 +257,7 @@ const struct brcms_c_rateset ofdm_rates = {
 	  0x00, 0x00, 0x00, 0x00, 0x00}
 };
 
-const struct brcms_c_rateset cck_rates = {
+const struct brcms_c_rateset brcmu_cck_rates = {
 	4,
 	/*  1b,   2b,   5.5,  11 Mbps */
 	{ 0x82, 0x84, 0x0b, 0x16},
@@ -288,7 +288,7 @@ static inline u8 ofdm_phy2mac_rate(u8 rlpt)
 /* check if rateset is valid.
  * if check_brate is true, rateset without a basic rate is considered NOT valid.
  */
-static bool brcms_c_rateset_valid(struct brcms_c_rateset *rs, bool check_brate)
+static bool brcmu_rateset_valid(struct brcms_c_rateset *rs, bool check_brate)
 {
 	uint idx;
 
@@ -306,7 +306,7 @@ static bool brcms_c_rateset_valid(struct brcms_c_rateset *rs, bool check_brate)
 	return false;
 }
 
-void brcms_c_rateset_mcs_upd(struct brcms_c_rateset *rs, u8 txstreams)
+void brcmu_rateset_mcs_upd(struct brcms_c_rateset *rs, u8 txstreams)
 {
 	int i;
 	for (i = txstreams; i < MAX_STREAMS_SUPPORTED; i++)
@@ -318,9 +318,9 @@ void brcms_c_rateset_mcs_upd(struct brcms_c_rateset *rs, u8 txstreams)
  * bit(s) preserved, and check if resulting rateset is valid.
 */
 bool
-brcms_c_rate_hwrs_filter_sort_validate(struct brcms_c_rateset *rs,
-				   const struct brcms_c_rateset *hw_rs,
-				   bool check_brate, u8 txstreams)
+brcmu_rate_hwrs_filter_sort_validate(struct brcms_c_rateset *rs,
+				     const struct brcms_c_rateset *hw_rs,
+				     bool check_brate, u8 txstreams)
 {
 	u8 rateset[BRCM_MAXRATE + 1];
 	u8 r;
@@ -333,7 +333,7 @@ brcms_c_rate_hwrs_filter_sort_validate(struct brcms_c_rateset *rs,
 	for (i = 0; i < count; i++) {
 		/* mask off "basic rate" bit, BRCMS_RATE_FLAG */
 		r = (int)rs->rates[i] & BRCMS_RATE_MASK;
-		if ((r > BRCM_MAXRATE) || (rate_info[r] == 0))
+		if (r > BRCM_MAXRATE || brcmu_rate_info[r] == 0)
 			continue;
 		rateset[r] = rs->rates[i];	/* preserve basic bit! */
 	}
@@ -352,14 +352,14 @@ brcms_c_rate_hwrs_filter_sort_validate(struct brcms_c_rateset *rs,
 	for (i = 0; i < MCSSET_LEN; i++)
 		rs->mcs[i] = (rs->mcs[i] & hw_rs->mcs[i]);
 
-	if (brcms_c_rateset_valid(rs, check_brate))
+	if (brcmu_rateset_valid(rs, check_brate))
 		return true;
 	else
 		return false;
 }
 
 /* calculate the rate of a rx'd frame and return it as a ratespec */
-u32 brcms_c_compute_rspec(struct d11rxhdr *rxh, u8 *plcp)
+u32 brcmu_compute_rspec(struct d11rxhdr *rxh, u8 *plcp)
 {
 	int phy_type;
 	u32 rspec = PHY_TXC1_BW_20MHZ << RSPEC_BW_SHIFT;
@@ -408,8 +408,8 @@ u32 brcms_c_compute_rspec(struct d11rxhdr *rxh, u8 *plcp)
 }
 
 /* copy rateset src to dst as-is (no masking or sorting) */
-void brcms_c_rateset_copy(const struct brcms_c_rateset *src,
-			  struct brcms_c_rateset *dst)
+void brcmu_rateset_copy(const struct brcms_c_rateset *src,
+			struct brcms_c_rateset *dst)
 {
 	memcpy(dst, src, sizeof(struct brcms_c_rateset));
 }
@@ -424,8 +424,8 @@ void brcms_c_rateset_copy(const struct brcms_c_rateset *src,
  * 'xmask' is the copy mask (typically 0x7f or 0xff).
  */
 void
-brcms_c_rateset_filter(struct brcms_c_rateset *src, struct brcms_c_rateset *dst,
-		       bool basic_only, u8 rates, uint xmask, bool mcsallow)
+brcmu_rateset_filter(struct brcms_c_rateset *src, struct brcms_c_rateset *dst,
+		     bool basic_only, u8 rates, uint xmask, bool mcsallow)
 {
 	uint i;
 	uint r;
@@ -450,17 +450,17 @@ brcms_c_rateset_filter(struct brcms_c_rateset *src, struct brcms_c_rateset *dst,
 	if (mcsallow && rates != BRCMS_RATES_CCK)
 		memcpy(&dst->mcs[0], &src->mcs[0], MCSSET_LEN);
 	else
-		brcms_c_rateset_mcs_clear(dst);
+		brcmu_rateset_mcs_clear(dst);
 }
 
 /* select rateset for a given phy_type and bandtype and filter it, sort it
  * and fill rs_tgt with result
  */
 void
-brcms_c_rateset_default(struct brcms_c_rateset *rs_tgt,
-			const struct brcms_c_rateset *rs_hw,
-			uint phy_type, int bandtype, bool cck_only,
-			uint rate_mask, bool mcsallow, u8 bw, u8 txstreams)
+brcmu_rateset_default(struct brcms_c_rateset *rs_tgt,
+		      const struct brcms_c_rateset *rs_hw,
+		      uint phy_type, int bandtype, bool cck_only,
+		      uint rate_mask, bool mcsallow, u8 bw, u8 txstreams)
 {
 	const struct brcms_c_rateset *rs_dflt;
 	struct brcms_c_rateset rs_sel;
@@ -470,37 +470,37 @@ brcms_c_rateset_default(struct brcms_c_rateset *rs_tgt,
 	    (PHYTYPE_IS(phy_type, PHY_TYPE_SSN))) {
 		if (bandtype == BRCM_BAND_5G)
 			rs_dflt = (bw == BRCMS_20_MHZ ?
-				   &ofdm_mimo_rates : &ofdm_40bw_mimo_rates);
+				   &brcmu_ofdm_mimo_rates : &ofdm_40bw_mimo_rates);
 		else
 			rs_dflt = (bw == BRCMS_20_MHZ ?
-				   &cck_ofdm_mimo_rates :
+				   &brcmu_cck_ofdm_mimo_rates :
 				   &cck_ofdm_40bw_mimo_rates);
 	} else if (PHYTYPE_IS(phy_type, PHY_TYPE_LP)) {
 		rs_dflt = (bandtype == BRCM_BAND_5G) ?
-			  &ofdm_rates : &cck_ofdm_rates;
+			  &brcmu_ofdm_rates : &brcmu_cck_ofdm_rates;
 	} else if (PHYTYPE_IS(phy_type, PHY_TYPE_A)) {
-		rs_dflt = &ofdm_rates;
+		rs_dflt = &brcmu_ofdm_rates;
 	} else if (PHYTYPE_IS(phy_type, PHY_TYPE_G)) {
-		rs_dflt = &cck_ofdm_rates;
+		rs_dflt = &brcmu_cck_ofdm_rates;
 	} else {
 		/* should not happen, error condition */
-		rs_dflt = &cck_rates;	/* force cck */
+		rs_dflt = &brcmu_cck_rates;	/* force cck */
 	}
 
 	/* if hw rateset is not supplied, assign selected rateset to it */
 	if (!rs_hw)
 		rs_hw = rs_dflt;
 
-	brcms_c_rateset_copy(rs_dflt, &rs_sel);
-	brcms_c_rateset_mcs_upd(&rs_sel, txstreams);
-	brcms_c_rateset_filter(&rs_sel, rs_tgt, false,
-			   cck_only ? BRCMS_RATES_CCK : BRCMS_RATES_CCK_OFDM,
-			   rate_mask, mcsallow);
-	brcms_c_rate_hwrs_filter_sort_validate(rs_tgt, rs_hw, false,
-					   mcsallow ? txstreams : 1);
+	brcmu_rateset_copy(rs_dflt, &rs_sel);
+	brcmu_rateset_mcs_upd(&rs_sel, txstreams);
+	brcmu_rateset_filter(&rs_sel, rs_tgt, false,
+			     cck_only ? BRCMS_RATES_CCK : BRCMS_RATES_CCK_OFDM,
+			     rate_mask, mcsallow);
+	brcmu_rate_hwrs_filter_sort_validate(rs_tgt, rs_hw, false,
+					     mcsallow ? txstreams : 1);
 }
 
-s16 brcms_c_rate_legacy_phyctl(uint rate)
+s16 brcmu_rate_legacy_phyctl(uint rate)
 {
 	uint i;
 	for (i = 0; i < LEGACY_PHYCFG_TABLE_SIZE; i++)
@@ -510,21 +510,21 @@ s16 brcms_c_rate_legacy_phyctl(uint rate)
 	return -1;
 }
 
-void brcms_c_rateset_mcs_clear(struct brcms_c_rateset *rateset)
+void brcmu_rateset_mcs_clear(struct brcms_c_rateset *rateset)
 {
 	uint i;
 	for (i = 0; i < MCSSET_LEN; i++)
 		rateset->mcs[i] = 0;
 }
 
-void brcms_c_rateset_mcs_build(struct brcms_c_rateset *rateset, u8 txstreams)
+void brcmu_rateset_mcs_build(struct brcms_c_rateset *rateset, u8 txstreams)
 {
-	memcpy(&rateset->mcs[0], &cck_ofdm_mimo_rates.mcs[0], MCSSET_LEN);
-	brcms_c_rateset_mcs_upd(rateset, txstreams);
+	memcpy(&rateset->mcs[0], &brcmu_cck_ofdm_mimo_rates.mcs[0], MCSSET_LEN);
+	brcmu_rateset_mcs_upd(rateset, txstreams);
 }
 
 /* Based on bandwidth passed, allow/disallow MCS 32 in the rateset */
-void brcms_c_rateset_bw_mcs_filter(struct brcms_c_rateset *rateset, u8 bw)
+void brcmu_rateset_bw_mcs_filter(struct brcms_c_rateset *rateset, u8 bw)
 {
 	if (bw == BRCMS_40_MHZ)
 		setbit(rateset->mcs, 32);
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/rate.h b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/rate.h
index 7c1d43323..e20913bdf 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/rate.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/rate.h
@@ -22,13 +22,13 @@
 #include "pub.h"
 #include "phy_hal.h"
 
-extern const u8 rate_info[];
-extern const struct brcms_c_rateset cck_ofdm_mimo_rates;
-extern const struct brcms_c_rateset ofdm_mimo_rates;
-extern const struct brcms_c_rateset cck_ofdm_rates;
-extern const struct brcms_c_rateset ofdm_rates;
-extern const struct brcms_c_rateset cck_rates;
-extern const struct brcms_c_rateset gphy_legacy_rates;
+extern const u8 brcmu_rate_info[];
+extern const struct brcms_c_rateset brcmu_cck_ofdm_mimo_rates;
+extern const struct brcms_c_rateset brcmu_ofdm_mimo_rates;
+extern const struct brcms_c_rateset brcmu_cck_ofdm_rates;
+extern const struct brcms_c_rateset brcmu_ofdm_rates;
+extern const struct brcms_c_rateset brcmu_cck_rates;
+extern const struct brcms_c_rateset brcmu_gphy_legacy_rates;
 extern const struct brcms_c_rateset rate_limit_1_2;
 
 struct brcms_mcs_info {
@@ -48,7 +48,7 @@ struct brcms_mcs_info {
 
 #define BRCMS_MAXMCS	32	/* max valid mcs index */
 #define MCS_TABLE_SIZE	33	/* Number of mcs entries in the table */
-extern const struct brcms_mcs_info mcs_table[];
+extern const struct brcms_mcs_info brcmu_mcs_table[];
 
 #define MCS_TXS_MASK	0xc0	/* num tx streams - 1 bit mask */
 #define MCS_TXS_SHIFT	6	/* num tx streams - 1 bit shift */
@@ -56,23 +56,23 @@ extern const struct brcms_mcs_info mcs_table[];
 /* returns num tx streams - 1 */
 static inline u8 mcs_2_txstreams(u8 mcs)
 {
-	return (mcs_table[mcs].tx_phy_ctl3 & MCS_TXS_MASK) >> MCS_TXS_SHIFT;
+	return (brcmu_mcs_table[mcs].tx_phy_ctl3 & MCS_TXS_MASK) >> MCS_TXS_SHIFT;
 }
 
 static inline uint mcs_2_rate(u8 mcs, bool is40, bool sgi)
 {
 	if (sgi) {
 		if (is40)
-			return mcs_table[mcs].phy_rate_40_sgi;
-		return mcs_table[mcs].phy_rate_20_sgi;
+			return brcmu_mcs_table[mcs].phy_rate_40_sgi;
+		return brcmu_mcs_table[mcs].phy_rate_20_sgi;
 	}
 	if (is40)
-		return mcs_table[mcs].phy_rate_40;
+		return brcmu_mcs_table[mcs].phy_rate_40;
 
-	return mcs_table[mcs].phy_rate_20;
+	return brcmu_mcs_table[mcs].phy_rate_20;
 }
 
-/* Macro to use the rate_info table */
+/* Macro to use the brcmu_rate_info table */
 #define	BRCMS_RATE_MASK_FULL 0xff /* Rate value mask with basic rate flag */
 
 /*
@@ -176,7 +176,7 @@ static inline bool is_mcs_rate(u32 ratespec)
 static inline bool is_ofdm_rate(u32 ratespec)
 {
 	return !is_mcs_rate(ratespec) &&
-	       (rate_info[ratespec & RSPEC_RATE_MASK] & BRCMS_RATE_FLAG);
+	       (brcmu_rate_info[ratespec & RSPEC_RATE_MASK] & BRCMS_RATE_FLAG);
 }
 
 static inline bool is_cck_rate(u32 ratespec)
@@ -203,37 +203,37 @@ static inline u8 cck_phy2mac_rate(u8 signal)
 	return signal/5;
 }
 
-/* Rates specified in brcms_c_rateset_filter() */
+/* Rates specified in brcmu_rateset_filter() */
 #define BRCMS_RATES_CCK_OFDM	0
 #define BRCMS_RATES_CCK		1
 #define BRCMS_RATES_OFDM		2
 
 /* sanitize, and sort a rateset with the basic bit(s) preserved, validate
  * rateset */
-bool brcms_c_rate_hwrs_filter_sort_validate(struct brcms_c_rateset *rs,
-					    const struct brcms_c_rateset *hw_rs,
-					    bool check_brate, u8 txstreams);
+bool brcmu_rate_hwrs_filter_sort_validate(struct brcms_c_rateset *rs,
+					  const struct brcms_c_rateset *hw_rs,
+					  bool check_brate, u8 txstreams);
 /* copy rateset src to dst as-is (no masking or sorting) */
-void brcms_c_rateset_copy(const struct brcms_c_rateset *src,
-			  struct brcms_c_rateset *dst);
+void brcmu_rateset_copy(const struct brcms_c_rateset *src,
+			struct brcms_c_rateset *dst);
 
 /* would be nice to have these documented ... */
-u32 brcms_c_compute_rspec(struct d11rxhdr *rxh, u8 *plcp);
+u32 brcmu_compute_rspec(struct d11rxhdr *rxh, u8 *plcp);
 
-void brcms_c_rateset_filter(struct brcms_c_rateset *src,
-			    struct brcms_c_rateset *dst, bool basic_only,
-			    u8 rates, uint xmask, bool mcsallow);
+void brcmu_rateset_filter(struct brcms_c_rateset *src,
+			  struct brcms_c_rateset *dst, bool basic_only,
+			  u8 rates, uint xmask, bool mcsallow);
 
-void brcms_c_rateset_default(struct brcms_c_rateset *rs_tgt,
-			     const struct brcms_c_rateset *rs_hw, uint phy_type,
-			     int bandtype, bool cck_only, uint rate_mask,
-			     bool mcsallow, u8 bw, u8 txstreams);
+void brcmu_rateset_default(struct brcms_c_rateset *rs_tgt,
+			   const struct brcms_c_rateset *rs_hw, uint phy_type,
+			   int bandtype, bool cck_only, uint rate_mask,
+			   bool mcsallow, u8 bw, u8 txstreams);
 
-s16 brcms_c_rate_legacy_phyctl(uint rate);
+s16 brcmu_rate_legacy_phyctl(uint rate);
 
-void brcms_c_rateset_mcs_upd(struct brcms_c_rateset *rs, u8 txstreams);
-void brcms_c_rateset_mcs_clear(struct brcms_c_rateset *rateset);
-void brcms_c_rateset_mcs_build(struct brcms_c_rateset *rateset, u8 txstreams);
-void brcms_c_rateset_bw_mcs_filter(struct brcms_c_rateset *rateset, u8 bw);
+void brcmu_rateset_mcs_upd(struct brcms_c_rateset *rs, u8 txstreams);
+void brcmu_rateset_mcs_clear(struct brcms_c_rateset *rateset);
+void brcmu_rateset_mcs_build(struct brcms_c_rateset *rateset, u8 txstreams);
+void brcmu_rateset_bw_mcs_filter(struct brcms_c_rateset *rateset, u8 bw);
 
 #endif				/* _BRCM_RATE_H_ */
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [RFC PATCH wireless-next 05/17] wifi: brcmsmac: round short-GI L-SIG lengths up to whole symbols
  2026-10-26  5:36 [RFC PATCH wireless-next 00/17] wifi: add a driver for Broadcom BCM4323x USB adapters Ragul Balaji
                   ` (3 preceding siblings ...)
  2026-10-26  5:36 ` [RFC PATCH wireless-next 04/17] wifi: brcmsmac: give the rate tables and helpers a brcmu_ prefix Ragul Balaji
@ 2026-10-26  5:36 ` Ragul Balaji
  2026-10-26  5:36 ` [RFC PATCH wireless-next 06/17] wifi: brcmsmac: pass the band to the PLCP and frame timing helpers Ragul Balaji
                   ` (11 subsequent siblings)
  16 siblings, 0 replies; 18+ messages in thread
From: Ragul Balaji @ 2026-10-26  5:36 UTC (permalink / raw)
  To: linux-wireless
  Cc: Arend van Spriel, Johannes Berg, brcm80211, Ragul Balaji,
	Ho Jie Feng, brcm80211-dev-list.pdl, linux-kernel

The L-SIG length of a mixed-mode frame covers its HT part in 4 us
legacy symbols.  For a short guard interval, the HT PHY's TXTIME counts
the data symbols at the guard-independent bits per symbol, then rounds
their 3.6 us each up to 4 us units.

brcms_c_calc_lsig_len() instead divides the bits by the short-GI rate,
which rounds the symbols down before scaling them and uses the rounded-
up rates of the MCS table, so it can count one symbol less than the
frame takes.  Count the symbols at the long-GI rate and scale them.

Signed-off-by: Ragul Balaji <ragulbaby@gmail.com>
Signed-off-by: Ho Jie Feng <hjf3108@gmail.com>
---
 drivers/net/wireless/broadcom/brcm80211/brcmsmac/main.c | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/main.c b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/main.c
index 3a383b0d1..c9a5877c1 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/main.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/main.c
@@ -7119,9 +7119,8 @@ brcms_c_calc_lsig_len(struct brcms_c_info *wlc, u32 ratespec,
 		 * the payload duration calculation matches that
 		 * of regular ofdm
 		 */
-		/* 1000Ndbps = kbps * 4 */
-		kNdps = mcs_2_rate(mcs, rspec_is40mhz(ratespec),
-				   rspec_issgi(ratespec)) * 4;
+		/* 1000Ndbps = kbps * 4, at the long-GI rate */
+		kNdps = mcs_2_rate(mcs, rspec_is40mhz(ratespec), false) * 4;
 
 		if (rspec_stc(ratespec) == 0)
 			nsyms =
@@ -7134,6 +7133,9 @@ brcms_c_calc_lsig_len(struct brcms_c_info *wlc, u32 ratespec,
 			    CEIL((APHY_SERVICE_NBITS + 8 * mac_len +
 				  APHY_TAIL_NBITS) * 1000, 2 * kNdps);
 
+		/* short-GI symbols (3.6 us) in 4 us units, rounded up */
+		if (rspec_issgi(ratespec))
+			nsyms = CEIL(nsyms * 9, 10);
 		/* (+3) account for HT-SIG(2) and HT-STF(1) */
 		nsyms += (tot_streams + 3);
 		/*
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [RFC PATCH wireless-next 06/17] wifi: brcmsmac: pass the band to the PLCP and frame timing helpers
  2026-10-26  5:36 [RFC PATCH wireless-next 00/17] wifi: add a driver for Broadcom BCM4323x USB adapters Ragul Balaji
                   ` (4 preceding siblings ...)
  2026-10-26  5:36 ` [RFC PATCH wireless-next 05/17] wifi: brcmsmac: round short-GI L-SIG lengths up to whole symbols Ragul Balaji
@ 2026-10-26  5:36 ` Ragul Balaji
  2026-10-26  5:36 ` [RFC PATCH wireless-next 07/17] wifi: brcmsmac: move the PLCP and frame timing helpers to rate.c Ragul Balaji
                   ` (10 subsequent siblings)
  16 siblings, 0 replies; 18+ messages in thread
From: Ragul Balaji @ 2026-10-26  5:36 UTC (permalink / raw)
  To: linux-wireless
  Cc: Arend van Spriel, Johannes Berg, brcm80211, Ragul Balaji,
	Ho Jie Feng, brcm80211-dev-list.pdl, linux-kernel

The functions computing the PLCP headers, frame and response times,
RTS/CTS durations, L-SIG lengths and PHY control words take the driver
state, but only use the band type, PHY type and basic rate table of the
current band from it.  Pass those instead and give the functions the
brcmu_ prefix, so that they can move to brcmutil for other drivers of
the d11 core.

Their error messages lose the device name, and BRCMS_ISLCNPHY() and
BRCMS_ISSSLPNPHY() become plain PHY type tests, which they are for the
PHY types brcmsmac supports.

Signed-off-by: Ragul Balaji <ragulbaby@gmail.com>
Signed-off-by: Ho Jie Feng <hjf3108@gmail.com>
---
 .../broadcom/brcm80211/brcmsmac/ampdu.c       |  31 ++-
 .../broadcom/brcm80211/brcmsmac/main.c        | 249 +++++++++---------
 .../broadcom/brcm80211/brcmsmac/main.h        |  10 +-
 3 files changed, 150 insertions(+), 140 deletions(-)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/ampdu.c b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/ampdu.c
index b42e43c88..19ffa72e2 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/ampdu.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/ampdu.c
@@ -720,14 +720,13 @@ void brcms_c_ampdu_finalize(struct brcms_ampdu_session *session)
 
 	/* reset the mixed mode header durations */
 	if (txh->MModeLen) {
-		u16 mmodelen = brcms_c_calc_lsig_len(wlc, rspec,
-						     session->ampdu_len);
+		u16 mmodelen = brcmu_calc_lsig_len(rspec, session->ampdu_len);
 		txh->MModeLen = cpu_to_le16(mmodelen);
 		preamble_type = BRCMS_MM_PREAMBLE;
 	}
 	if (txh->MModeFbrLen) {
-		u16 mmfbrlen = brcms_c_calc_lsig_len(wlc, rspec_fallback,
-						     session->ampdu_len);
+		u16 mmfbrlen = brcmu_calc_lsig_len(rspec_fallback,
+						   session->ampdu_len);
 		txh->MModeFbrLen = cpu_to_le16(mmfbrlen);
 		fbr_preamble_type = BRCMS_MM_PREAMBLE;
 	}
@@ -753,17 +752,21 @@ void brcms_c_ampdu_finalize(struct brcms_ampdu_session *session)
 		     TXC_PREAMBLE_RTS_FB_SHORT)
 			rts_fbr_preamble_type = BRCMS_SHORT_PREAMBLE;
 
-		durid = brcms_c_compute_rtscts_dur(wlc, use_cts, rts_rspec,
-						   rspec, rts_preamble_type,
-						   preamble_type,
-						   session->ampdu_len, true);
+		durid = brcmu_compute_rtscts_dur(wlc->band->bandtype,
+						 wlc->band->basic_rate,
+						 use_cts, rts_rspec,
+						 rspec, rts_preamble_type,
+						 preamble_type,
+						 session->ampdu_len, true);
 		rts->duration = cpu_to_le16(durid);
-		durid = brcms_c_compute_rtscts_dur(wlc, use_cts,
-						   rts_rspec_fallback,
-						   rspec_fallback,
-						   rts_fbr_preamble_type,
-						   fbr_preamble_type,
-						   session->ampdu_len, true);
+		durid = brcmu_compute_rtscts_dur(wlc->band->bandtype,
+						 wlc->band->basic_rate,
+						 use_cts,
+						 rts_rspec_fallback,
+						 rspec_fallback,
+						 rts_fbr_preamble_type,
+						 fbr_preamble_type,
+						 session->ampdu_len, true);
 		txh->RTSDurFallback = cpu_to_le16(durid);
 		/* set TxFesTimeNormal */
 		txh->TxFesTimeNormal = rts->duration;
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/main.c b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/main.c
index c9a5877c1..1725f0634 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/main.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/main.c
@@ -349,12 +349,12 @@ static u8 brcms_fifo_to_ac(u8 fifo)
 }
 
 /* Find basic rate for a given rate */
-static u8 brcms_basic_rate(struct brcms_c_info *wlc, u32 rspec)
+static u8 brcmu_basic_rate(const u8 *basic_rate, u32 rspec)
 {
 	if (is_mcs_rate(rspec))
-		return wlc->band->basic_rate[brcmu_mcs_table[rspec & RSPEC_RATE_MASK]
+		return basic_rate[brcmu_mcs_table[rspec & RSPEC_RATE_MASK]
 		       .leg_ofdm];
-	return wlc->band->basic_rate[rspec & RSPEC_RATE_MASK];
+	return basic_rate[rspec & RSPEC_RATE_MASK];
 }
 
 static u16 frametype(u32 rspec, u8 mimoframe)
@@ -365,10 +365,9 @@ static u16 frametype(u32 rspec, u8 mimoframe)
 }
 
 /* currently the best mechanism for determining SIFS is the band in use */
-static u16 get_sifs(struct brcms_band *band)
+static u16 brcmu_get_sifs(int bandtype)
 {
-	return band->bandtype == BRCM_BAND_5G ? APHY_SIFS_TIME :
-				 BPHY_SIFS_TIME;
+	return bandtype == BRCM_BAND_5G ? APHY_SIFS_TIME : BPHY_SIFS_TIME;
 }
 
 /*
@@ -576,15 +575,14 @@ static void brcms_b_update_slot_timing(struct brcms_hardware *wlc_hw,
  * calculate frame duration of a given rate and length, return
  * time in usec unit
  */
-static uint brcms_c_calc_frame_time(struct brcms_c_info *wlc, u32 ratespec,
-				    u8 preamble_type, uint mac_len)
+static uint brcmu_calc_frame_time(int bandtype, u32 ratespec,
+				  u8 preamble_type, uint mac_len)
 {
 	uint nsyms, dur = 0, Ndps, kNdps;
 	uint rate = rspec2rate(ratespec);
 
 	if (rate == 0) {
-		brcms_err(wlc->hw->d11core, "wl%d: WAR: using rate of 1 mbps\n",
-			  wlc->pub->unit);
+		pr_err("WAR: using rate of 1 mbps\n");
 		rate = BRCM_RATE_1M;
 	}
 
@@ -611,7 +609,7 @@ static uint brcms_c_calc_frame_time(struct brcms_c_info *wlc, u32 ratespec,
 				  APHY_TAIL_NBITS) * 1000, 2 * kNdps);
 
 		dur += APHY_SYMBOL_TIME * nsyms;
-		if (wlc->band->bandtype == BRCM_BAND_2G)
+		if (bandtype == BRCM_BAND_2G)
 			dur += DOT11_OFDM_SIGNAL_EXTENSION;
 	} else if (is_ofdm_rate(rate)) {
 		dur = APHY_PREAMBLE_TIME;
@@ -623,7 +621,7 @@ static uint brcms_c_calc_frame_time(struct brcms_c_info *wlc, u32 ratespec,
 		    CEIL((APHY_SERVICE_NBITS + 8 * mac_len + APHY_TAIL_NBITS),
 			 Ndps);
 		dur += APHY_SYMBOL_TIME * nsyms;
-		if (wlc->band->bandtype == BRCM_BAND_2G)
+		if (bandtype == BRCM_BAND_2G)
 			dur += DOT11_OFDM_SIGNAL_EXTENSION;
 	} else {
 		/*
@@ -3368,15 +3366,15 @@ brcms_default_rateset(struct brcms_c_info *wlc, struct brcms_c_rateset *rs)
 			      wlc->stf->txstreams);
 }
 
-/* derive wlc->band->basic_rate[] table from 'rateset' */
-static void brcms_c_rate_lookup_init(struct brcms_c_info *wlc,
-			      struct brcms_c_rateset *rateset)
+/* derive basic_rate[] table 'br' from 'rateset' */
+static void brcmu_rate_lookup_init(u8 *br,
+				   const struct brcms_c_rateset *hw_rateset,
+				   struct brcms_c_rateset *rateset)
 {
 	u8 rate;
 	u8 mandatory;
 	u8 cck_basic = 0;
 	u8 ofdm_basic = 0;
-	u8 *br = wlc->band->basic_rate;
 	uint i;
 
 	/* incoming rates are in 500kbps units as in 802.11 Supported Rates */
@@ -3394,9 +3392,8 @@ static void brcms_c_rate_lookup_init(struct brcms_c_info *wlc,
 		rate = (rateset->rates[i] & BRCMS_RATE_MASK);
 
 		if (rate > BRCM_MAXRATE) {
-			brcms_err(wlc->hw->d11core, "brcms_c_rate_lookup_init: "
-				  "invalid rate 0x%X in rate set\n",
-				  rateset->rates[i]);
+			pr_err("%s: invalid rate 0x%X in rate set\n", __func__,
+			       rateset->rates[i]);
 			continue;
 		}
 
@@ -3417,8 +3414,8 @@ static void brcms_c_rate_lookup_init(struct brcms_c_info *wlc,
 	 * to high, filling in holes in the lookup table
 	 */
 
-	for (i = 0; i < wlc->band->hw_rateset.count; i++) {
-		rate = wlc->band->hw_rateset.rates[i];
+	for (i = 0; i < hw_rateset->count; i++) {
+		rate = hw_rateset->rates[i];
 
 		if (br[rate] != 0) {
 			/* This rate is a basic rate.
@@ -3501,7 +3498,9 @@ static void brcms_c_bandinit_ordered(struct brcms_c_info *wlc,
 				     (bool)(wlc->pub->_n_enab & SUPPORT_11N));
 
 		/* init basic rate lookup */
-		brcms_c_rate_lookup_init(wlc, &default_rateset);
+		brcmu_rate_lookup_init(wlc->band->basic_rate,
+				       &wlc->band->hw_rateset,
+				       &default_rateset);
 	}
 
 	/* sync up phy/radio chanspec */
@@ -3633,10 +3632,10 @@ static void brcms_c_set_ratetable(struct brcms_c_info *wlc)
 	for (i = 0; i < rs.count; i++) {
 		rate = rs.rates[i] & BRCMS_RATE_MASK;
 
-		/* for a given rate brcms_basic_rate returns the rate at
+		/* for a given rate brcmu_basic_rate returns the rate at
 		 * which a response ACK/CTS should be sent.
 		 */
-		basic_rate = brcms_basic_rate(wlc, rate);
+		basic_rate = brcmu_basic_rate(wlc->band->basic_rate, rate);
 		if (basic_rate == 0)
 			/* This should only happen if we are using a
 			 * restricted rateset.
@@ -5640,8 +5639,8 @@ bcmc_fid_generate(struct brcms_c_info *wlc, struct brcms_bss_cfg *bsscfg,
 }
 
 static uint
-brcms_c_calc_ack_time(struct brcms_c_info *wlc, u32 rspec,
-		      u8 preamble_type)
+brcmu_calc_ack_time(int bandtype, const u8 *basic_rate, u32 rspec,
+		    u8 preamble_type)
 {
 	uint dur = 0;
 
@@ -5650,35 +5649,35 @@ brcms_c_calc_ack_time(struct brcms_c_info *wlc, u32 rspec,
 	 * is less than or equal to the rate of the immediately previous
 	 * frame in the FES
 	 */
-	rspec = brcms_basic_rate(wlc, rspec);
+	rspec = brcmu_basic_rate(basic_rate, rspec);
 	/* ACK frame len == 14 == 2(fc) + 2(dur) + 6(ra) + 4(fcs) */
 	dur =
-	    brcms_c_calc_frame_time(wlc, rspec, preamble_type,
-				(DOT11_ACK_LEN + FCS_LEN));
+	    brcmu_calc_frame_time(bandtype, rspec, preamble_type,
+				  (DOT11_ACK_LEN + FCS_LEN));
 	return dur;
 }
 
 static uint
-brcms_c_calc_cts_time(struct brcms_c_info *wlc, u32 rspec,
-		      u8 preamble_type)
+brcmu_calc_cts_time(int bandtype, const u8 *basic_rate, u32 rspec,
+		    u8 preamble_type)
 {
-	return brcms_c_calc_ack_time(wlc, rspec, preamble_type);
+	return brcmu_calc_ack_time(bandtype, basic_rate, rspec, preamble_type);
 }
 
 static uint
-brcms_c_calc_ba_time(struct brcms_c_info *wlc, u32 rspec,
-		     u8 preamble_type)
+brcmu_calc_ba_time(int bandtype, const u8 *basic_rate, u32 rspec,
+		   u8 preamble_type)
 {
 	/*
 	 * Spec 9.6: ack rate is the highest rate in BSSBasicRateSet that
 	 * is less than or equal to the rate of the immediately previous
 	 * frame in the FES
 	 */
-	rspec = brcms_basic_rate(wlc, rspec);
+	rspec = brcmu_basic_rate(basic_rate, rspec);
 	/* BA len == 32 == 16(ctl hdr) + 4(ba len) + 8(bitmap) + 4(fcs) */
-	return brcms_c_calc_frame_time(wlc, rspec, preamble_type,
-				   (DOT11_BA_LEN + DOT11_BA_BITMAP_LEN +
-				    FCS_LEN));
+	return brcmu_calc_frame_time(bandtype, rspec, preamble_type,
+				     (DOT11_BA_LEN + DOT11_BA_BITMAP_LEN +
+				      FCS_LEN));
 }
 
 /* brcms_c_compute_frame_dur()
@@ -5697,10 +5696,12 @@ brcms_c_compute_frame_dur(struct brcms_c_info *wlc, u32 rate,
 {
 	u16 dur, sifs;
 
-	sifs = get_sifs(wlc->band);
+	sifs = brcmu_get_sifs(wlc->band->bandtype);
 
 	dur = sifs;
-	dur += (u16) brcms_c_calc_ack_time(wlc, rate, preamble_type);
+	dur += (u16)brcmu_calc_ack_time(wlc->band->bandtype,
+					wlc->band->basic_rate, rate,
+					preamble_type);
 
 	if (next_frag_len) {
 		/* Double the current DUR to get 2 SIFS + 2 ACKs */
@@ -5708,13 +5709,13 @@ brcms_c_compute_frame_dur(struct brcms_c_info *wlc, u32 rate,
 		/* add another SIFS and the frag time */
 		dur += sifs;
 		dur +=
-		    (u16) brcms_c_calc_frame_time(wlc, rate, preamble_type,
-						 next_frag_len);
+		    (u16)brcmu_calc_frame_time(wlc->band->bandtype, rate,
+					       preamble_type, next_frag_len);
 	}
 	return dur;
 }
 
-/* The opposite of brcms_c_calc_frame_time */
+/* The opposite of brcmu_calc_frame_time */
 static uint
 brcms_c_calc_frame_len(struct brcms_c_info *wlc, u32 ratespec,
 		   u8 preamble_type, uint dur)
@@ -5910,8 +5911,7 @@ mac80211_wlc_set_nrate(struct brcms_c_info *wlc, struct brcms_band *cur_band,
  * Broken out for PRQ.
  */
 
-static void brcms_c_cck_plcp_set(struct brcms_c_info *wlc, int rate_500,
-			     uint length, u8 *plcp)
+static void brcmu_cck_plcp_set(int rate_500, uint length, u8 *plcp)
 {
 	u16 usec = 0;
 	u8 le = 0;
@@ -5938,9 +5938,7 @@ static void brcms_c_cck_plcp_set(struct brcms_c_info *wlc, int rate_500,
 		break;
 
 	default:
-		brcms_err(wlc->hw->d11core,
-			  "brcms_c_cck_plcp_set: unsupported rate %d\n",
-			  rate_500);
+		pr_err("%s: unsupported rate %d\n", __func__, rate_500);
 		rate_500 = BRCM_RATE_1M;
 		usec = length << 3;
 		break;
@@ -5958,7 +5956,7 @@ static void brcms_c_cck_plcp_set(struct brcms_c_info *wlc, int rate_500,
 }
 
 /* Rate: 802.11 rate code, length: PSDU length in octets */
-static void brcms_c_compute_mimo_plcp(u32 rspec, uint length, u8 *plcp)
+static void brcmu_compute_mimo_plcp(u32 rspec, uint length, u8 *plcp)
 {
 	u8 mcs = (u8) (rspec & RSPEC_RATE_MASK);
 	plcp[0] = mcs;
@@ -5973,7 +5971,7 @@ static void brcms_c_compute_mimo_plcp(u32 rspec, uint length, u8 *plcp)
 
 /* Rate: 802.11 rate code, length: PSDU length in octets */
 static void
-brcms_c_compute_ofdm_plcp(u32 rspec, u32 length, u8 *plcp)
+brcmu_compute_ofdm_plcp(u32 rspec, u32 length, u8 *plcp)
 {
 	u8 rate_signal;
 	u32 tmp = 0;
@@ -5994,27 +5992,25 @@ brcms_c_compute_ofdm_plcp(u32 rspec, u32 length, u8 *plcp)
 }
 
 /* Rate: 802.11 rate code, length: PSDU length in octets */
-static void brcms_c_compute_cck_plcp(struct brcms_c_info *wlc, u32 rspec,
-				 uint length, u8 *plcp)
+static void brcmu_compute_cck_plcp(u32 rspec, uint length, u8 *plcp)
 {
 	int rate = rspec2rate(rspec);
 
-	brcms_c_cck_plcp_set(wlc, rate, length, plcp);
+	brcmu_cck_plcp_set(rate, length, plcp);
 }
 
 static void
-brcms_c_compute_plcp(struct brcms_c_info *wlc, u32 rspec,
-		     uint length, u8 *plcp)
+brcmu_compute_plcp(u32 rspec, uint length, u8 *plcp)
 {
 	if (is_mcs_rate(rspec))
-		brcms_c_compute_mimo_plcp(rspec, length, plcp);
+		brcmu_compute_mimo_plcp(rspec, length, plcp);
 	else if (is_ofdm_rate(rspec))
-		brcms_c_compute_ofdm_plcp(rspec, length, plcp);
+		brcmu_compute_ofdm_plcp(rspec, length, plcp);
 	else
-		brcms_c_compute_cck_plcp(wlc, rspec, length, plcp);
+		brcmu_compute_cck_plcp(rspec, length, plcp);
 }
 
-/* brcms_c_compute_rtscts_dur()
+/* brcmu_compute_rtscts_dur()
  *
  * Calculate the 802.11 MAC header DUR field for an RTS or CTS frame
  * DUR for normal RTS/CTS w/ frame = 3 SIFS + 1 CTS + next frame time + 1 ACK
@@ -6026,53 +6022,53 @@ brcms_c_compute_plcp(struct brcms_c_info *wlc, u32 rspec,
  * frame_len		next MPDU frame length in bytes
  */
 u16
-brcms_c_compute_rtscts_dur(struct brcms_c_info *wlc, bool cts_only,
-			   u32 rts_rate,
-			   u32 frame_rate, u8 rts_preamble_type,
-			   u8 frame_preamble_type, uint frame_len, bool ba)
+brcmu_compute_rtscts_dur(int bandtype, const u8 *basic_rate, bool cts_only,
+			 u32 rts_rate,
+			 u32 frame_rate, u8 rts_preamble_type,
+			 u8 frame_preamble_type, uint frame_len, bool ba)
 {
 	u16 dur, sifs;
 
-	sifs = get_sifs(wlc->band);
+	sifs = brcmu_get_sifs(bandtype);
 
 	if (!cts_only) {
 		/* RTS/CTS */
 		dur = 3 * sifs;
 		dur +=
-		    (u16) brcms_c_calc_cts_time(wlc, rts_rate,
-					       rts_preamble_type);
+		    (u16)brcmu_calc_cts_time(bandtype, basic_rate, rts_rate,
+					     rts_preamble_type);
 	} else {
 		/* CTS-TO-SELF */
 		dur = 2 * sifs;
 	}
 
 	dur +=
-	    (u16) brcms_c_calc_frame_time(wlc, frame_rate, frame_preamble_type,
-					 frame_len);
+	    (u16)brcmu_calc_frame_time(bandtype, frame_rate,
+				       frame_preamble_type, frame_len);
 	if (ba)
 		dur +=
-		    (u16) brcms_c_calc_ba_time(wlc, frame_rate,
-					      BRCMS_SHORT_PREAMBLE);
+		    (u16)brcmu_calc_ba_time(bandtype, basic_rate, frame_rate,
+					    BRCMS_SHORT_PREAMBLE);
 	else
 		dur +=
-		    (u16) brcms_c_calc_ack_time(wlc, frame_rate,
-					       frame_preamble_type);
+		    (u16)brcmu_calc_ack_time(bandtype, basic_rate, frame_rate,
+					     frame_preamble_type);
 	return dur;
 }
 
-static u16 brcms_c_phytxctl1_calc(struct brcms_c_info *wlc, u32 rspec)
+static u16 brcmu_phytxctl1_calc(u16 phytype, u32 rspec)
 {
 	u16 phyctl1 = 0;
 	u16 bw;
 
-	if (BRCMS_ISLCNPHY(wlc->band)) {
+	if (phytype == PHY_TYPE_LCN) {
 		bw = PHY_TXC1_BW_20MHZ;
 	} else {
 		bw = rspec_get_bw(rspec);
 		/* 10Mhz is not supported yet */
 		if (bw < PHY_TXC1_BW_20MHZ) {
-			brcms_err(wlc->hw->d11core, "phytxctl1_calc: bw %d is "
-				  "not supported yet, set to 20L\n", bw);
+			pr_err("%s: bw %d is not supported yet, set to 20L\n",
+			       __func__, bw);
 			bw = PHY_TXC1_BW_20MHZ;
 		}
 	}
@@ -6084,8 +6080,8 @@ static u16 brcms_c_phytxctl1_calc(struct brcms_c_info *wlc, u32 rspec)
 		phyctl1 = rspec_phytxbyte2(rspec);
 		/* set the upper byte of phyctl1 */
 		phyctl1 |= (brcmu_mcs_table[mcs].tx_phy_ctl3 << 8);
-	} else if (is_cck_rate(rspec) && !BRCMS_ISLCNPHY(wlc->band)
-		   && !BRCMS_ISSSLPNPHY(wlc->band)) {
+	} else if (is_cck_rate(rspec) && phytype != PHY_TYPE_LCN &&
+		   phytype != PHY_TYPE_SSN) {
 		/*
 		 * In CCK mode LPPHY overloads OFDM Modulation bits with CCK
 		 * Data Rate. Eventually MIMOPHY would also be converted to
@@ -6098,8 +6094,7 @@ static u16 brcms_c_phytxctl1_calc(struct brcms_c_info *wlc, u32 rspec)
 		/* get the phyctl byte from rate phycfg table */
 		phycfg = brcmu_rate_legacy_phyctl(rspec2rate(rspec));
 		if (phycfg == -1) {
-			brcms_err(wlc->hw->d11core, "phytxctl1_calc: wrong "
-				  "legacy OFDM/CCK rate\n");
+			pr_err("%s: wrong legacy OFDM/CCK rate\n", __func__);
 			phycfg = 0;
 		}
 		/* set the upper byte of phyctl1 */
@@ -6395,8 +6390,8 @@ brcms_c_d11hdrs_mac80211(struct brcms_c_info *wlc, struct ieee80211_hw *hw,
 
 	/* (3) PLCP: determine PLCP header and MAC duration,
 	 * fill struct d11txh */
-	brcms_c_compute_plcp(wlc, rspec[0], phylen, plcp);
-	brcms_c_compute_plcp(wlc, rspec[1], phylen, plcp_fallback);
+	brcmu_compute_plcp(rspec[0], phylen, plcp);
+	brcmu_compute_plcp(rspec[1], phylen, plcp_fallback);
 	memcpy(&txh->FragPLCPFallback,
 	       plcp_fallback, sizeof(txh->FragPLCPFallback));
 
@@ -6421,9 +6416,9 @@ brcms_c_d11hdrs_mac80211(struct brcms_c_info *wlc, struct ieee80211_hw *hw,
 	} else if (use_rifs) {
 		/* NAV protect to end of next max packet size */
 		durid =
-		    (u16) brcms_c_calc_frame_time(wlc, rspec[0],
-						 preamble_type[0],
-						 DOT11_MAX_FRAG_LEN);
+		    (u16)brcmu_calc_frame_time(wlc->band->bandtype, rspec[0],
+					       preamble_type[0],
+					       DOT11_MAX_FRAG_LEN);
 		durid += RIFS_11N_TIME;
 		h->duration_id = cpu_to_le16(durid);
 	}
@@ -6537,26 +6532,30 @@ brcms_c_d11hdrs_mac80211(struct brcms_c_info *wlc, struct ieee80211_hw *hw,
 		else
 			rts_phylen = DOT11_RTS_LEN + FCS_LEN;
 
-		brcms_c_compute_plcp(wlc, rts_rspec[0], rts_phylen, rts_plcp);
+		brcmu_compute_plcp(rts_rspec[0], rts_phylen, rts_plcp);
 
 		/* fallback rate version of RTS PLCP header */
-		brcms_c_compute_plcp(wlc, rts_rspec[1], rts_phylen,
-				 rts_plcp_fallback);
+		brcmu_compute_plcp(rts_rspec[1], rts_phylen, rts_plcp_fallback);
 		memcpy(&txh->RTSPLCPFallback, rts_plcp_fallback,
 		       sizeof(txh->RTSPLCPFallback));
 
 		/* RTS frame fields... */
 		rts = (struct ieee80211_rts *)&txh->rts_frame;
 
-		durid = brcms_c_compute_rtscts_dur(wlc, use_cts, rts_rspec[0],
-					       rspec[0], rts_preamble_type[0],
-					       preamble_type[0], phylen, false);
+		durid = brcmu_compute_rtscts_dur(wlc->band->bandtype,
+						 wlc->band->basic_rate,
+						 use_cts, rts_rspec[0],
+						 rspec[0], rts_preamble_type[0],
+						 preamble_type[0], phylen,
+						 false);
 		rts->duration = cpu_to_le16(durid);
 		/* fallback rate version of RTS DUR field */
-		durid = brcms_c_compute_rtscts_dur(wlc, use_cts,
-					       rts_rspec[1], rspec[1],
-					       rts_preamble_type[1],
-					       preamble_type[1], phylen, false);
+		durid = brcmu_compute_rtscts_dur(wlc->band->bandtype,
+						 wlc->band->basic_rate,
+						 use_cts, rts_rspec[1],
+						 rspec[1], rts_preamble_type[1],
+						 preamble_type[1], phylen,
+						 false);
 		txh->RTSDurFallback = cpu_to_le16(durid);
 
 		if (use_cts) {
@@ -6631,15 +6630,17 @@ brcms_c_d11hdrs_mac80211(struct brcms_c_info *wlc, struct ieee80211_hw *hw,
 	if (BRCMS_PHY_11N_CAP(wlc->band)) {
 		u16 phyctl1 = 0;
 
-		phyctl1 = brcms_c_phytxctl1_calc(wlc, rspec[0]);
+		phyctl1 = brcmu_phytxctl1_calc(wlc->band->phytype, rspec[0]);
 		txh->PhyTxControlWord_1 = cpu_to_le16(phyctl1);
-		phyctl1 = brcms_c_phytxctl1_calc(wlc, rspec[1]);
+		phyctl1 = brcmu_phytxctl1_calc(wlc->band->phytype, rspec[1]);
 		txh->PhyTxControlWord_1_Fbr = cpu_to_le16(phyctl1);
 
 		if (use_rts || use_cts) {
-			phyctl1 = brcms_c_phytxctl1_calc(wlc, rts_rspec[0]);
+			phyctl1 = brcmu_phytxctl1_calc(wlc->band->phytype,
+						       rts_rspec[0]);
 			txh->PhyTxControlWord_1_Rts = cpu_to_le16(phyctl1);
-			phyctl1 = brcms_c_phytxctl1_calc(wlc, rts_rspec[1]);
+			phyctl1 = brcmu_phytxctl1_calc(wlc->band->phytype,
+						       rts_rspec[1]);
 			txh->PhyTxControlWord_1_FbrRts = cpu_to_le16(phyctl1);
 		}
 
@@ -6651,14 +6652,14 @@ brcms_c_d11hdrs_mac80211(struct brcms_c_info *wlc, struct ieee80211_hw *hw,
 		if (is_mcs_rate(rspec[0]) &&
 		    (preamble_type[0] == BRCMS_MM_PREAMBLE)) {
 			u16 mmodelen =
-			    brcms_c_calc_lsig_len(wlc, rspec[0], phylen);
+			    brcmu_calc_lsig_len(rspec[0], phylen);
 			txh->MModeLen = cpu_to_le16(mmodelen);
 		}
 
 		if (is_mcs_rate(rspec[1]) &&
 		    (preamble_type[1] == BRCMS_MM_PREAMBLE)) {
 			u16 mmodefbrlen =
-			    brcms_c_calc_lsig_len(wlc, rspec[1], phylen);
+			    brcmu_calc_lsig_len(rspec[1], phylen);
 			txh->MModeFbrLen = cpu_to_le16(mmodefbrlen);
 		}
 	}
@@ -6670,17 +6671,21 @@ brcms_c_d11hdrs_mac80211(struct brcms_c_info *wlc, struct ieee80211_hw *hw,
 		/* WME: Update TXOP threshold */
 		if (!(tx_info->flags & IEEE80211_TX_CTL_AMPDU) && frag == 0) {
 			frag_dur =
-			    brcms_c_calc_frame_time(wlc, rspec[0],
-					preamble_type[0], phylen);
+			    brcmu_calc_frame_time(wlc->band->bandtype, rspec[0],
+						  preamble_type[0], phylen);
 
 			if (rts) {
 				/* 1 RTS or CTS-to-self frame */
 				dur =
-				    brcms_c_calc_cts_time(wlc, rts_rspec[0],
-						      rts_preamble_type[0]);
+				    brcmu_calc_cts_time(wlc->band->bandtype,
+							wlc->band->basic_rate,
+							rts_rspec[0],
+							rts_preamble_type[0]);
 				dur_fallback =
-				    brcms_c_calc_cts_time(wlc, rts_rspec[1],
-						      rts_preamble_type[1]);
+				    brcmu_calc_cts_time(wlc->band->bandtype,
+							wlc->band->basic_rate,
+							rts_rspec[1],
+							rts_preamble_type[1]);
 				/* (SIFS + CTS) + SIFS + frame + SIFS + ACK */
 				dur += le16_to_cpu(rts->duration);
 				dur_fallback +=
@@ -6696,9 +6701,10 @@ brcms_c_d11hdrs_mac80211(struct brcms_c_info *wlc, struct ieee80211_hw *hw,
 							  preamble_type[0], 0);
 
 				dur_fallback =
-				    brcms_c_calc_frame_time(wlc, rspec[1],
-							preamble_type[1],
-							phylen);
+				    brcmu_calc_frame_time(wlc->band->bandtype,
+							  rspec[1],
+							  preamble_type[1],
+							  phylen);
 				dur_fallback +=
 				    brcms_c_compute_frame_dur(wlc, rspec[1],
 							  preamble_type[1], 0);
@@ -6862,19 +6868,20 @@ brcms_c_rspec_to_rts_rspec(struct brcms_c_info *wlc, u32 rspec,
 		rts_rspec = rspec;
 	else if (wlc->band->gmode && wlc->protection->_g && !is_cck_rate(rspec))
 		/* Use 11Mbps as the g protection RTS target rate and fallback.
-		 * Use the brcms_basic_rate() lookup to find the best basic rate
+		 * Use the brcmu_basic_rate() lookup to find the best basic rate
 		 * under the target in case 11 Mbps is not Basic.
 		 * 6 and 9 Mbps are not usually selected by rate selection, but
 		 * even if the OFDM rate we are protecting is 6 or 9 Mbps, 11
 		 * is more robust.
 		 */
-		rts_rspec = brcms_basic_rate(wlc, BRCM_RATE_11M);
+		rts_rspec = brcmu_basic_rate(wlc->band->basic_rate,
+					     BRCM_RATE_11M);
 	else
 		/* calculate RTS rate and fallback rate based on the frame rate
 		 * RTS must be sent at a basic rate since it is a
 		 * control frame, sec 9.6 of 802.11 spec
 		 */
-		rts_rspec = brcms_basic_rate(wlc, rspec);
+		rts_rspec = brcmu_basic_rate(wlc->band->basic_rate, rspec);
 
 	if (BRCMS_PHY_11N_CAP(wlc->band)) {
 		/* set rts txbw to correct side band */
@@ -7105,8 +7112,7 @@ brcms_c_recvctl(struct brcms_c_info *wlc, struct d11rxhdr *rxh,
  *   len = 3(nsyms + nstream + 3) - 3
  */
 u16
-brcms_c_calc_lsig_len(struct brcms_c_info *wlc, u32 ratespec,
-		      uint mac_len)
+brcmu_calc_lsig_len(u32 ratespec, uint mac_len)
 {
 	uint nsyms, len = 0, kNdps;
 
@@ -7159,7 +7165,7 @@ brcms_c_mod_prb_rsp_rate_table(struct brcms_c_info *wlc, uint frame_len)
 	u16 dur, sifs;
 	uint i;
 
-	sifs = get_sifs(wlc->band);
+	sifs = brcmu_get_sifs(wlc->band->bandtype);
 
 	rs_dflt = brcms_c_rateset_get_hwrs(wlc);
 
@@ -7176,14 +7182,15 @@ brcms_c_mod_prb_rsp_rate_table(struct brcms_c_info *wlc, uint frame_len)
 		entry_ptr = brcms_b_rate_shm_offset(wlc->hw, rate);
 
 		/* Calculate the Probe Response PLCP for the given rate */
-		brcms_c_compute_plcp(wlc, rate, frame_len, plcp);
+		brcmu_compute_plcp(rate, frame_len, plcp);
 
 		/*
 		 * Calculate the duration of the Probe Response
 		 * frame plus SIFS for the MAC
 		 */
-		dur = (u16) brcms_c_calc_frame_time(wlc, rate,
-						BRCMS_LONG_PREAMBLE, frame_len);
+		dur = (u16)brcmu_calc_frame_time(wlc->band->bandtype, rate,
+						 BRCMS_LONG_PREAMBLE,
+						 frame_len);
 		dur += sifs;
 
 		/* Update the SHM Rate Table entry Probe Response values */
@@ -7215,8 +7222,8 @@ static void brcms_c_beacon_write(struct brcms_c_info *wlc,
 	len = min_t(size_t, beacon->len, BCN_TMPL_LEN);
 	wlc->bcn_rspec = ieee80211_get_tx_rate(ieee_hw, tx_info)->hw_value;
 
-	brcms_c_compute_plcp(wlc, wlc->bcn_rspec,
-			     len + FCS_LEN - D11_PHY_HDR_LEN, beacon->data);
+	brcmu_compute_plcp(wlc->bcn_rspec,
+			   len + FCS_LEN - D11_PHY_HDR_LEN, beacon->data);
 
 	/* "Regular" and 16 MBSS but not for 4 MBSS */
 	/* Update the phytxctl for the beacon based on the rspec */
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/main.h b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/main.h
index d81de701a..347d9a4a8 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/main.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/main.h
@@ -614,13 +614,13 @@ int brcms_b_xmtfifo_sz_get(struct brcms_hardware *wlc_hw, uint fifo,
 
 int brcms_c_set_gmode(struct brcms_c_info *wlc, u8 gmode, bool config);
 void brcms_c_mac_promisc(struct brcms_c_info *wlc, uint filter_flags);
-u16 brcms_c_calc_lsig_len(struct brcms_c_info *wlc, u32 ratespec, uint mac_len);
+u16 brcmu_calc_lsig_len(u32 ratespec, uint mac_len);
 u32 brcms_c_rspec_to_rts_rspec(struct brcms_c_info *wlc, u32 rspec,
 			       bool use_rspec, u16 mimo_ctlchbw);
-u16 brcms_c_compute_rtscts_dur(struct brcms_c_info *wlc, bool cts_only,
-			       u32 rts_rate, u32 frame_rate,
-			       u8 rts_preamble_type, u8 frame_preamble_type,
-			       uint frame_len, bool ba);
+u16 brcmu_compute_rtscts_dur(int bandtype, const u8 *basic_rate, bool cts_only,
+			     u32 rts_rate, u32 frame_rate,
+			     u8 rts_preamble_type, u8 frame_preamble_type,
+			     uint frame_len, bool ba);
 void brcms_c_inval_dma_pkts(struct brcms_hardware *hw,
 			    struct ieee80211_sta *sta, void (*dma_callback_fn));
 void brcms_c_update_probe_resp(struct brcms_c_info *wlc, bool suspend);
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [RFC PATCH wireless-next 07/17] wifi: brcmsmac: move the PLCP and frame timing helpers to rate.c
  2026-10-26  5:36 [RFC PATCH wireless-next 00/17] wifi: add a driver for Broadcom BCM4323x USB adapters Ragul Balaji
                   ` (5 preceding siblings ...)
  2026-10-26  5:36 ` [RFC PATCH wireless-next 06/17] wifi: brcmsmac: pass the band to the PLCP and frame timing helpers Ragul Balaji
@ 2026-10-26  5:36 ` Ragul Balaji
  2026-10-26  5:36 ` [RFC PATCH wireless-next 08/17] wifi: brcm80211: move the rate helpers of brcmsmac to brcmutil Ragul Balaji
                   ` (9 subsequent siblings)
  16 siblings, 0 replies; 18+ messages in thread
From: Ragul Balaji @ 2026-10-26  5:36 UTC (permalink / raw)
  To: linux-wireless
  Cc: Arend van Spriel, Johannes Berg, brcm80211, Ragul Balaji,
	Ho Jie Feng, brcm80211-dev-list.pdl, linux-kernel

Move the functions computing PLCP headers, frame and response times,
RTS/CTS durations, L-SIG lengths and PHY control words, with the basic
rate lookup they use, from main.c to rate.c, so that they move to
brcmutil with the rate helpers. brcmu_rate_legacy_phyctl() is then only
used in rate.c and becomes static.

Signed-off-by: Ragul Balaji <ragulbaby@gmail.com>
Signed-off-by: Ho Jie Feng <hjf3108@gmail.com>
---
 .../broadcom/brcm80211/brcmsmac/main.c        | 487 ------------------
 .../broadcom/brcm80211/brcmsmac/main.h        |  13 -
 .../broadcom/brcm80211/brcmsmac/rate.c        | 471 ++++++++++++++++-
 .../broadcom/brcm80211/brcmsmac/rate.h        |  50 +-
 4 files changed, 518 insertions(+), 503 deletions(-)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/main.c b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/main.c
index 1725f0634..f7d9480cc 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/main.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/main.c
@@ -81,31 +81,13 @@
 #define EDCF_TXOP2USEC(txop)		((txop) << 5)
 #define EDCF_ECW2CW(exp)		((1 << (exp)) - 1)
 
-#define APHY_SYMBOL_TIME		4
-#define APHY_PREAMBLE_TIME		16
-#define APHY_SIGNAL_TIME		4
-#define APHY_SIFS_TIME			16
-#define APHY_SERVICE_NBITS		16
-#define APHY_TAIL_NBITS			6
-#define BPHY_SIFS_TIME			10
-#define BPHY_PLCP_SHORT_TIME		96
-
-#define PREN_PREAMBLE			24
-#define PREN_MM_EXT			12
-#define PREN_PREAMBLE_EXT		4
-
 #define DOT11_MAC_HDR_LEN		24
-#define DOT11_ACK_LEN			10
-#define DOT11_BA_LEN			4
-#define DOT11_OFDM_SIGNAL_EXTENSION	6
 #define DOT11_MIN_FRAG_LEN		256
 #define DOT11_RTS_LEN			16
 #define DOT11_CTS_LEN			10
-#define DOT11_BA_BITMAP_LEN		128
 #define DOT11_MAXNUMFRAGS		16
 #define DOT11_MAX_FRAG_LEN		2346
 
-#define BPHY_PLCP_TIME			192
 #define RIFS_11N_TIME			2
 
 /* length of the BCN template area */
@@ -348,15 +330,6 @@ static u8 brcms_fifo_to_ac(u8 fifo)
 	return fifo_to_ac_mapping[fifo];
 }
 
-/* Find basic rate for a given rate */
-static u8 brcmu_basic_rate(const u8 *basic_rate, u32 rspec)
-{
-	if (is_mcs_rate(rspec))
-		return basic_rate[brcmu_mcs_table[rspec & RSPEC_RATE_MASK]
-		       .leg_ofdm];
-	return basic_rate[rspec & RSPEC_RATE_MASK];
-}
-
 static u16 frametype(u32 rspec, u8 mimoframe)
 {
 	if (is_mcs_rate(rspec))
@@ -364,12 +337,6 @@ static u16 frametype(u32 rspec, u8 mimoframe)
 	return is_cck_rate(rspec) ? FT_CCK : FT_OFDM;
 }
 
-/* currently the best mechanism for determining SIFS is the band in use */
-static u16 brcmu_get_sifs(int bandtype)
-{
-	return bandtype == BRCM_BAND_5G ? APHY_SIFS_TIME : BPHY_SIFS_TIME;
-}
-
 /*
  * Detect Card removed.
  * Even checking an sbconfig register read will not false trigger when the core
@@ -571,74 +538,6 @@ static void brcms_b_update_slot_timing(struct brcms_hardware *wlc_hw,
 	}
 }
 
-/*
- * calculate frame duration of a given rate and length, return
- * time in usec unit
- */
-static uint brcmu_calc_frame_time(int bandtype, u32 ratespec,
-				  u8 preamble_type, uint mac_len)
-{
-	uint nsyms, dur = 0, Ndps, kNdps;
-	uint rate = rspec2rate(ratespec);
-
-	if (rate == 0) {
-		pr_err("WAR: using rate of 1 mbps\n");
-		rate = BRCM_RATE_1M;
-	}
-
-	if (is_mcs_rate(ratespec)) {
-		uint mcs = ratespec & RSPEC_RATE_MASK;
-		int tot_streams = mcs_2_txstreams(mcs) + rspec_stc(ratespec);
-
-		dur = PREN_PREAMBLE + (tot_streams * PREN_PREAMBLE_EXT);
-		if (preamble_type == BRCMS_MM_PREAMBLE)
-			dur += PREN_MM_EXT;
-		/* 1000Ndbps = kbps * 4 */
-		kNdps = mcs_2_rate(mcs, rspec_is40mhz(ratespec),
-				   rspec_issgi(ratespec)) * 4;
-
-		if (rspec_stc(ratespec) == 0)
-			nsyms =
-			    CEIL((APHY_SERVICE_NBITS + 8 * mac_len +
-				  APHY_TAIL_NBITS) * 1000, kNdps);
-		else
-			/* STBC needs to have even number of symbols */
-			nsyms =
-			    2 *
-			    CEIL((APHY_SERVICE_NBITS + 8 * mac_len +
-				  APHY_TAIL_NBITS) * 1000, 2 * kNdps);
-
-		dur += APHY_SYMBOL_TIME * nsyms;
-		if (bandtype == BRCM_BAND_2G)
-			dur += DOT11_OFDM_SIGNAL_EXTENSION;
-	} else if (is_ofdm_rate(rate)) {
-		dur = APHY_PREAMBLE_TIME;
-		dur += APHY_SIGNAL_TIME;
-		/* Ndbps = Mbps * 4 = rate(500Kbps) * 2 */
-		Ndps = rate * 2;
-		/* NSyms = CEILING((SERVICE + 8*NBytes + TAIL) / Ndbps) */
-		nsyms =
-		    CEIL((APHY_SERVICE_NBITS + 8 * mac_len + APHY_TAIL_NBITS),
-			 Ndps);
-		dur += APHY_SYMBOL_TIME * nsyms;
-		if (bandtype == BRCM_BAND_2G)
-			dur += DOT11_OFDM_SIGNAL_EXTENSION;
-	} else {
-		/*
-		 * calc # bits * 2 so factor of 2 in rate (1/2 mbps)
-		 * will divide out
-		 */
-		mac_len = mac_len * 8 * 2;
-		/* calc ceiling of bits/rate = microseconds of air time */
-		dur = (mac_len + rate - 1) / rate;
-		if (preamble_type & BRCMS_SHORT_PREAMBLE)
-			dur += BPHY_PLCP_SHORT_TIME;
-		else
-			dur += BPHY_PLCP_TIME;
-	}
-	return dur;
-}
-
 static void brcms_c_write_inits(struct brcms_hardware *wlc_hw,
 				const struct d11init *inits)
 {
@@ -3366,100 +3265,6 @@ brcms_default_rateset(struct brcms_c_info *wlc, struct brcms_c_rateset *rs)
 			      wlc->stf->txstreams);
 }
 
-/* derive basic_rate[] table 'br' from 'rateset' */
-static void brcmu_rate_lookup_init(u8 *br,
-				   const struct brcms_c_rateset *hw_rateset,
-				   struct brcms_c_rateset *rateset)
-{
-	u8 rate;
-	u8 mandatory;
-	u8 cck_basic = 0;
-	u8 ofdm_basic = 0;
-	uint i;
-
-	/* incoming rates are in 500kbps units as in 802.11 Supported Rates */
-	memset(br, 0, BRCM_MAXRATE + 1);
-
-	/* For each basic rate in the rates list, make an entry in the
-	 * best basic lookup.
-	 */
-	for (i = 0; i < rateset->count; i++) {
-		/* only make an entry for a basic rate */
-		if (!(rateset->rates[i] & BRCMS_RATE_FLAG))
-			continue;
-
-		/* mask off basic bit */
-		rate = (rateset->rates[i] & BRCMS_RATE_MASK);
-
-		if (rate > BRCM_MAXRATE) {
-			pr_err("%s: invalid rate 0x%X in rate set\n", __func__,
-			       rateset->rates[i]);
-			continue;
-		}
-
-		br[rate] = rate;
-	}
-
-	/* The rate lookup table now has non-zero entries for each
-	 * basic rate, equal to the basic rate: br[basicN] = basicN
-	 *
-	 * To look up the best basic rate corresponding to any
-	 * particular rate, code can use the basic_rate table
-	 * like this
-	 *
-	 * basic_rate = wlc->band->basic_rate[tx_rate]
-	 *
-	 * Make sure there is a best basic rate entry for
-	 * every rate by walking up the table from low rates
-	 * to high, filling in holes in the lookup table
-	 */
-
-	for (i = 0; i < hw_rateset->count; i++) {
-		rate = hw_rateset->rates[i];
-
-		if (br[rate] != 0) {
-			/* This rate is a basic rate.
-			 * Keep track of the best basic rate so far by
-			 * modulation type.
-			 */
-			if (is_ofdm_rate(rate))
-				ofdm_basic = rate;
-			else
-				cck_basic = rate;
-
-			continue;
-		}
-
-		/* This rate is not a basic rate so figure out the
-		 * best basic rate less than this rate and fill in
-		 * the hole in the table
-		 */
-
-		br[rate] = is_ofdm_rate(rate) ? ofdm_basic : cck_basic;
-
-		if (br[rate] != 0)
-			continue;
-
-		if (is_ofdm_rate(rate)) {
-			/*
-			 * In 11g and 11a, the OFDM mandatory rates
-			 * are 6, 12, and 24 Mbps
-			 */
-			if (rate >= BRCM_RATE_24M)
-				mandatory = BRCM_RATE_24M;
-			else if (rate >= BRCM_RATE_12M)
-				mandatory = BRCM_RATE_12M;
-			else
-				mandatory = BRCM_RATE_6M;
-		} else {
-			/* In 11b, all CCK rates are mandatory 1 - 11 Mbps */
-			mandatory = rate;
-		}
-
-		br[rate] = mandatory;
-	}
-}
-
 static void brcms_c_bandinit_ordered(struct brcms_c_info *wlc,
 				     u16 chanspec)
 {
@@ -5638,48 +5443,6 @@ bcmc_fid_generate(struct brcms_c_info *wlc, struct brcms_bss_cfg *bsscfg,
 	return frameid;
 }
 
-static uint
-brcmu_calc_ack_time(int bandtype, const u8 *basic_rate, u32 rspec,
-		    u8 preamble_type)
-{
-	uint dur = 0;
-
-	/*
-	 * Spec 9.6: ack rate is the highest rate in BSSBasicRateSet that
-	 * is less than or equal to the rate of the immediately previous
-	 * frame in the FES
-	 */
-	rspec = brcmu_basic_rate(basic_rate, rspec);
-	/* ACK frame len == 14 == 2(fc) + 2(dur) + 6(ra) + 4(fcs) */
-	dur =
-	    brcmu_calc_frame_time(bandtype, rspec, preamble_type,
-				  (DOT11_ACK_LEN + FCS_LEN));
-	return dur;
-}
-
-static uint
-brcmu_calc_cts_time(int bandtype, const u8 *basic_rate, u32 rspec,
-		    u8 preamble_type)
-{
-	return brcmu_calc_ack_time(bandtype, basic_rate, rspec, preamble_type);
-}
-
-static uint
-brcmu_calc_ba_time(int bandtype, const u8 *basic_rate, u32 rspec,
-		   u8 preamble_type)
-{
-	/*
-	 * Spec 9.6: ack rate is the highest rate in BSSBasicRateSet that
-	 * is less than or equal to the rate of the immediately previous
-	 * frame in the FES
-	 */
-	rspec = brcmu_basic_rate(basic_rate, rspec);
-	/* BA len == 32 == 16(ctl hdr) + 4(ba len) + 8(bitmap) + 4(fcs) */
-	return brcmu_calc_frame_time(bandtype, rspec, preamble_type,
-				     (DOT11_BA_LEN + DOT11_BA_BITMAP_LEN +
-				      FCS_LEN));
-}
-
 /* brcms_c_compute_frame_dur()
  *
  * Calculate the 802.11 MAC header DUR field for MPDU
@@ -5904,207 +5667,6 @@ mac80211_wlc_set_nrate(struct brcms_c_info *wlc, struct brcms_band *cur_band,
 	return rate;
 }
 
-/*
- * Compute PLCP, but only requires actual rate and length of pkt.
- * Rate is given in the driver standard multiple of 500 kbps.
- * le is set for 11 Mbps rate if necessary.
- * Broken out for PRQ.
- */
-
-static void brcmu_cck_plcp_set(int rate_500, uint length, u8 *plcp)
-{
-	u16 usec = 0;
-	u8 le = 0;
-
-	switch (rate_500) {
-	case BRCM_RATE_1M:
-		usec = length << 3;
-		break;
-	case BRCM_RATE_2M:
-		usec = length << 2;
-		break;
-	case BRCM_RATE_5M5:
-		usec = (length << 4) / 11;
-		if ((length << 4) - (usec * 11) > 0)
-			usec++;
-		break;
-	case BRCM_RATE_11M:
-		usec = (length << 3) / 11;
-		if ((length << 3) - (usec * 11) > 0) {
-			usec++;
-			if ((usec * 11) - (length << 3) >= 8)
-				le = D11B_PLCP_SIGNAL_LE;
-		}
-		break;
-
-	default:
-		pr_err("%s: unsupported rate %d\n", __func__, rate_500);
-		rate_500 = BRCM_RATE_1M;
-		usec = length << 3;
-		break;
-	}
-	/* PLCP signal byte */
-	plcp[0] = rate_500 * 5;	/* r (500kbps) * 5 == r (100kbps) */
-	/* PLCP service byte */
-	plcp[1] = (u8) (le | D11B_PLCP_SIGNAL_LOCKED);
-	/* PLCP length u16, little endian */
-	plcp[2] = usec & 0xff;
-	plcp[3] = (usec >> 8) & 0xff;
-	/* PLCP CRC16 */
-	plcp[4] = 0;
-	plcp[5] = 0;
-}
-
-/* Rate: 802.11 rate code, length: PSDU length in octets */
-static void brcmu_compute_mimo_plcp(u32 rspec, uint length, u8 *plcp)
-{
-	u8 mcs = (u8) (rspec & RSPEC_RATE_MASK);
-	plcp[0] = mcs;
-	if (rspec_is40mhz(rspec) || (mcs == 32))
-		plcp[0] |= MIMO_PLCP_40MHZ;
-	BRCMS_SET_MIMO_PLCP_LEN(plcp, length);
-	plcp[3] = rspec_mimoplcp3(rspec); /* rspec already holds this byte */
-	plcp[3] |= 0x7; /* set smoothing, not sounding ppdu & reserved */
-	plcp[4] = 0; /* number of extension spatial streams bit 0 & 1 */
-	plcp[5] = 0;
-}
-
-/* Rate: 802.11 rate code, length: PSDU length in octets */
-static void
-brcmu_compute_ofdm_plcp(u32 rspec, u32 length, u8 *plcp)
-{
-	u8 rate_signal;
-	u32 tmp = 0;
-	int rate = rspec2rate(rspec);
-
-	/*
-	 * encode rate per 802.11a-1999 sec 17.3.4.1, with lsb
-	 * transmitted first
-	 */
-	rate_signal = brcmu_rate_info[rate] & BRCMS_RATE_MASK;
-	memset(plcp, 0, D11_PHY_HDR_LEN);
-	D11A_PHY_HDR_SRATE((struct ofdm_phy_hdr *) plcp, rate_signal);
-
-	tmp = (length & 0xfff) << 5;
-	plcp[2] |= (tmp >> 16) & 0xff;
-	plcp[1] |= (tmp >> 8) & 0xff;
-	plcp[0] |= tmp & 0xff;
-}
-
-/* Rate: 802.11 rate code, length: PSDU length in octets */
-static void brcmu_compute_cck_plcp(u32 rspec, uint length, u8 *plcp)
-{
-	int rate = rspec2rate(rspec);
-
-	brcmu_cck_plcp_set(rate, length, plcp);
-}
-
-static void
-brcmu_compute_plcp(u32 rspec, uint length, u8 *plcp)
-{
-	if (is_mcs_rate(rspec))
-		brcmu_compute_mimo_plcp(rspec, length, plcp);
-	else if (is_ofdm_rate(rspec))
-		brcmu_compute_ofdm_plcp(rspec, length, plcp);
-	else
-		brcmu_compute_cck_plcp(rspec, length, plcp);
-}
-
-/* brcmu_compute_rtscts_dur()
- *
- * Calculate the 802.11 MAC header DUR field for an RTS or CTS frame
- * DUR for normal RTS/CTS w/ frame = 3 SIFS + 1 CTS + next frame time + 1 ACK
- * DUR for CTS-TO-SELF w/ frame    = 2 SIFS         + next frame time + 1 ACK
- *
- * cts			cts-to-self or rts/cts
- * rts_rate		rts or cts rate in unit of 500kbps
- * rate			next MPDU rate in unit of 500kbps
- * frame_len		next MPDU frame length in bytes
- */
-u16
-brcmu_compute_rtscts_dur(int bandtype, const u8 *basic_rate, bool cts_only,
-			 u32 rts_rate,
-			 u32 frame_rate, u8 rts_preamble_type,
-			 u8 frame_preamble_type, uint frame_len, bool ba)
-{
-	u16 dur, sifs;
-
-	sifs = brcmu_get_sifs(bandtype);
-
-	if (!cts_only) {
-		/* RTS/CTS */
-		dur = 3 * sifs;
-		dur +=
-		    (u16)brcmu_calc_cts_time(bandtype, basic_rate, rts_rate,
-					     rts_preamble_type);
-	} else {
-		/* CTS-TO-SELF */
-		dur = 2 * sifs;
-	}
-
-	dur +=
-	    (u16)brcmu_calc_frame_time(bandtype, frame_rate,
-				       frame_preamble_type, frame_len);
-	if (ba)
-		dur +=
-		    (u16)brcmu_calc_ba_time(bandtype, basic_rate, frame_rate,
-					    BRCMS_SHORT_PREAMBLE);
-	else
-		dur +=
-		    (u16)brcmu_calc_ack_time(bandtype, basic_rate, frame_rate,
-					     frame_preamble_type);
-	return dur;
-}
-
-static u16 brcmu_phytxctl1_calc(u16 phytype, u32 rspec)
-{
-	u16 phyctl1 = 0;
-	u16 bw;
-
-	if (phytype == PHY_TYPE_LCN) {
-		bw = PHY_TXC1_BW_20MHZ;
-	} else {
-		bw = rspec_get_bw(rspec);
-		/* 10Mhz is not supported yet */
-		if (bw < PHY_TXC1_BW_20MHZ) {
-			pr_err("%s: bw %d is not supported yet, set to 20L\n",
-			       __func__, bw);
-			bw = PHY_TXC1_BW_20MHZ;
-		}
-	}
-
-	if (is_mcs_rate(rspec)) {
-		uint mcs = rspec & RSPEC_RATE_MASK;
-
-		/* bw, stf, coding-type is part of rspec_phytxbyte2 returns */
-		phyctl1 = rspec_phytxbyte2(rspec);
-		/* set the upper byte of phyctl1 */
-		phyctl1 |= (brcmu_mcs_table[mcs].tx_phy_ctl3 << 8);
-	} else if (is_cck_rate(rspec) && phytype != PHY_TYPE_LCN &&
-		   phytype != PHY_TYPE_SSN) {
-		/*
-		 * In CCK mode LPPHY overloads OFDM Modulation bits with CCK
-		 * Data Rate. Eventually MIMOPHY would also be converted to
-		 * this format
-		 */
-		/* 0 = 1Mbps; 1 = 2Mbps; 2 = 5.5Mbps; 3 = 11Mbps */
-		phyctl1 = (bw | (rspec_stf(rspec) << PHY_TXC1_MODE_SHIFT));
-	} else {		/* legacy OFDM/CCK */
-		s16 phycfg;
-		/* get the phyctl byte from rate phycfg table */
-		phycfg = brcmu_rate_legacy_phyctl(rspec2rate(rspec));
-		if (phycfg == -1) {
-			pr_err("%s: wrong legacy OFDM/CCK rate\n", __func__);
-			phycfg = 0;
-		}
-		/* set the upper byte of phyctl1 */
-		phyctl1 =
-		    (bw | (phycfg << 8) |
-		     (rspec_stf(rspec) << PHY_TXC1_MODE_SHIFT));
-	}
-	return phyctl1;
-}
-
 /*
  * Add struct d11txh, struct cck_phy_hdr.
  *
@@ -7105,55 +6667,6 @@ brcms_c_recvctl(struct brcms_c_info *wlc, struct d11rxhdr *rxh,
 	ieee80211_rx_irqsafe(wlc->pub->ieee_hw, p);
 }
 
-/* calculate frame duration for Mixed-mode L-SIG spoofing, return
- * number of bytes goes in the length field
- *
- * Formula given by HT PHY Spec v 1.13
- *   len = 3(nsyms + nstream + 3) - 3
- */
-u16
-brcmu_calc_lsig_len(u32 ratespec, uint mac_len)
-{
-	uint nsyms, len = 0, kNdps;
-
-	if (is_mcs_rate(ratespec)) {
-		uint mcs = ratespec & RSPEC_RATE_MASK;
-		int tot_streams = (mcs_2_txstreams(mcs) + 1) +
-				  rspec_stc(ratespec);
-
-		/*
-		 * the payload duration calculation matches that
-		 * of regular ofdm
-		 */
-		/* 1000Ndbps = kbps * 4, at the long-GI rate */
-		kNdps = mcs_2_rate(mcs, rspec_is40mhz(ratespec), false) * 4;
-
-		if (rspec_stc(ratespec) == 0)
-			nsyms =
-			    CEIL((APHY_SERVICE_NBITS + 8 * mac_len +
-				  APHY_TAIL_NBITS) * 1000, kNdps);
-		else
-			/* STBC needs to have even number of symbols */
-			nsyms =
-			    2 *
-			    CEIL((APHY_SERVICE_NBITS + 8 * mac_len +
-				  APHY_TAIL_NBITS) * 1000, 2 * kNdps);
-
-		/* short-GI symbols (3.6 us) in 4 us units, rounded up */
-		if (rspec_issgi(ratespec))
-			nsyms = CEIL(nsyms * 9, 10);
-		/* (+3) account for HT-SIG(2) and HT-STF(1) */
-		nsyms += (tot_streams + 3);
-		/*
-		 * 3 bytes/symbol @ legacy 6Mbps rate
-		 * (-3) excluding service bits and tail bits
-		 */
-		len = (3 * nsyms) - 3;
-	}
-
-	return (u16) len;
-}
-
 static void
 brcms_c_mod_prb_rsp_rate_table(struct brcms_c_info *wlc, uint frame_len)
 {
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/main.h b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/main.h
index 347d9a4a8..c0ab15762 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/main.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/main.h
@@ -78,14 +78,6 @@
 #define BRCMS_SHORTSLOT_OFF	0  /* Turn off short slot */
 #define BRCMS_SHORTSLOT_ON	1  /* Turn on short slot */
 
-/* value for short/long and mixmode/greenfield preamble */
-#define BRCMS_LONG_PREAMBLE	(0)
-#define BRCMS_SHORT_PREAMBLE	(1 << 0)
-#define BRCMS_GF_PREAMBLE		(1 << 1)
-#define BRCMS_MM_PREAMBLE		(1 << 2)
-#define BRCMS_IS_MIMO_PREAMBLE(_pre) (((_pre) == BRCMS_GF_PREAMBLE) || \
-				      ((_pre) == BRCMS_MM_PREAMBLE))
-
 /* promote boardrev */
 #define BOARDREV_PROMOTABLE	0xFF	/* from */
 #define BOARDREV_PROMOTED	1	/* to */
@@ -614,13 +606,8 @@ int brcms_b_xmtfifo_sz_get(struct brcms_hardware *wlc_hw, uint fifo,
 
 int brcms_c_set_gmode(struct brcms_c_info *wlc, u8 gmode, bool config);
 void brcms_c_mac_promisc(struct brcms_c_info *wlc, uint filter_flags);
-u16 brcmu_calc_lsig_len(u32 ratespec, uint mac_len);
 u32 brcms_c_rspec_to_rts_rspec(struct brcms_c_info *wlc, u32 rspec,
 			       bool use_rspec, u16 mimo_ctlchbw);
-u16 brcmu_compute_rtscts_dur(int bandtype, const u8 *basic_rate, bool cts_only,
-			     u32 rts_rate, u32 frame_rate,
-			     u8 rts_preamble_type, u8 frame_preamble_type,
-			     uint frame_len, bool ba);
 void brcms_c_inval_dma_pkts(struct brcms_hardware *hw,
 			    struct ieee80211_sta *sta, void (*dma_callback_fn));
 void brcms_c_update_probe_resp(struct brcms_c_info *wlc, bool suspend);
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/rate.c b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/rate.c
index 95ccc1177..1c0259139 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/rate.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/rate.c
@@ -14,6 +14,8 @@
  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
 #include <brcmu_wifi.h>
 #include <brcmu_utils.h>
 
@@ -21,6 +23,10 @@
 #include "pub.h"
 #include "rate.h"
 
+#define DOT11_ACK_LEN			10
+#define DOT11_BA_LEN			4
+#define DOT11_BA_BITMAP_LEN		128
+
 /*
  * Rate info per rate: It tells whether a rate is ofdm or not and its phy_rate
  * value
@@ -500,7 +506,7 @@ brcmu_rateset_default(struct brcms_c_rateset *rs_tgt,
 					     mcsallow ? txstreams : 1);
 }
 
-s16 brcmu_rate_legacy_phyctl(uint rate)
+static s16 brcmu_rate_legacy_phyctl(uint rate)
 {
 	uint i;
 	for (i = 0; i < LEGACY_PHYCFG_TABLE_SIZE; i++)
@@ -531,3 +537,466 @@ void brcmu_rateset_bw_mcs_filter(struct brcms_c_rateset *rateset, u8 bw)
 	else
 		clrbit(rateset->mcs, 32);
 }
+
+/* Find basic rate for a given rate */
+u8 brcmu_basic_rate(const u8 *basic_rate, u32 rspec)
+{
+	if (is_mcs_rate(rspec))
+		return basic_rate[brcmu_mcs_table[rspec & RSPEC_RATE_MASK]
+		       .leg_ofdm];
+	return basic_rate[rspec & RSPEC_RATE_MASK];
+}
+
+/*
+ * calculate frame duration of a given rate and length, return
+ * time in usec unit
+ */
+uint brcmu_calc_frame_time(int bandtype, u32 ratespec,
+			   u8 preamble_type, uint mac_len)
+{
+	uint nsyms, dur = 0, Ndps, kNdps;
+	uint rate = rspec2rate(ratespec);
+
+	if (rate == 0) {
+		pr_err("WAR: using rate of 1 mbps\n");
+		rate = BRCM_RATE_1M;
+	}
+
+	if (is_mcs_rate(ratespec)) {
+		uint mcs = ratespec & RSPEC_RATE_MASK;
+		int tot_streams = mcs_2_txstreams(mcs) + rspec_stc(ratespec);
+
+		dur = PREN_PREAMBLE + (tot_streams * PREN_PREAMBLE_EXT);
+		if (preamble_type == BRCMS_MM_PREAMBLE)
+			dur += PREN_MM_EXT;
+		/* 1000Ndbps = kbps * 4 */
+		kNdps = mcs_2_rate(mcs, rspec_is40mhz(ratespec),
+				   rspec_issgi(ratespec)) * 4;
+
+		if (rspec_stc(ratespec) == 0)
+			nsyms =
+			    CEIL((APHY_SERVICE_NBITS + 8 * mac_len +
+				  APHY_TAIL_NBITS) * 1000, kNdps);
+		else
+			/* STBC needs to have even number of symbols */
+			nsyms =
+			    2 *
+			    CEIL((APHY_SERVICE_NBITS + 8 * mac_len +
+				  APHY_TAIL_NBITS) * 1000, 2 * kNdps);
+
+		dur += APHY_SYMBOL_TIME * nsyms;
+		if (bandtype == BRCM_BAND_2G)
+			dur += DOT11_OFDM_SIGNAL_EXTENSION;
+	} else if (is_ofdm_rate(rate)) {
+		dur = APHY_PREAMBLE_TIME;
+		dur += APHY_SIGNAL_TIME;
+		/* Ndbps = Mbps * 4 = rate(500Kbps) * 2 */
+		Ndps = rate * 2;
+		/* NSyms = CEILING((SERVICE + 8*NBytes + TAIL) / Ndbps) */
+		nsyms =
+		    CEIL((APHY_SERVICE_NBITS + 8 * mac_len + APHY_TAIL_NBITS),
+			 Ndps);
+		dur += APHY_SYMBOL_TIME * nsyms;
+		if (bandtype == BRCM_BAND_2G)
+			dur += DOT11_OFDM_SIGNAL_EXTENSION;
+	} else {
+		/*
+		 * calc # bits * 2 so factor of 2 in rate (1/2 mbps)
+		 * will divide out
+		 */
+		mac_len = mac_len * 8 * 2;
+		/* calc ceiling of bits/rate = microseconds of air time */
+		dur = (mac_len + rate - 1) / rate;
+		if (preamble_type & BRCMS_SHORT_PREAMBLE)
+			dur += BPHY_PLCP_SHORT_TIME;
+		else
+			dur += BPHY_PLCP_TIME;
+	}
+	return dur;
+}
+
+/* derive basic_rate[] table 'br' from 'rateset' */
+void brcmu_rate_lookup_init(u8 *br,
+			    const struct brcms_c_rateset *hw_rateset,
+			    struct brcms_c_rateset *rateset)
+{
+	u8 rate;
+	u8 mandatory;
+	u8 cck_basic = 0;
+	u8 ofdm_basic = 0;
+	uint i;
+
+	/* incoming rates are in 500kbps units as in 802.11 Supported Rates */
+	memset(br, 0, BRCM_MAXRATE + 1);
+
+	/* For each basic rate in the rates list, make an entry in the
+	 * best basic lookup.
+	 */
+	for (i = 0; i < rateset->count; i++) {
+		/* only make an entry for a basic rate */
+		if (!(rateset->rates[i] & BRCMS_RATE_FLAG))
+			continue;
+
+		/* mask off basic bit */
+		rate = (rateset->rates[i] & BRCMS_RATE_MASK);
+
+		if (rate > BRCM_MAXRATE) {
+			pr_err("%s: invalid rate 0x%X in rate set\n", __func__,
+			       rateset->rates[i]);
+			continue;
+		}
+
+		br[rate] = rate;
+	}
+
+	/* The rate lookup table now has non-zero entries for each
+	 * basic rate, equal to the basic rate: br[basicN] = basicN
+	 *
+	 * To look up the best basic rate corresponding to any
+	 * particular rate, code can use the basic_rate table
+	 * like this
+	 *
+	 * basic_rate = br[tx_rate]
+	 *
+	 * Make sure there is a best basic rate entry for
+	 * every rate by walking up the table from low rates
+	 * to high, filling in holes in the lookup table
+	 */
+
+	for (i = 0; i < hw_rateset->count; i++) {
+		rate = hw_rateset->rates[i];
+
+		if (br[rate] != 0) {
+			/* This rate is a basic rate.
+			 * Keep track of the best basic rate so far by
+			 * modulation type.
+			 */
+			if (is_ofdm_rate(rate))
+				ofdm_basic = rate;
+			else
+				cck_basic = rate;
+
+			continue;
+		}
+
+		/* This rate is not a basic rate so figure out the
+		 * best basic rate less than this rate and fill in
+		 * the hole in the table
+		 */
+
+		br[rate] = is_ofdm_rate(rate) ? ofdm_basic : cck_basic;
+
+		if (br[rate] != 0)
+			continue;
+
+		if (is_ofdm_rate(rate)) {
+			/*
+			 * In 11g and 11a, the OFDM mandatory rates
+			 * are 6, 12, and 24 Mbps
+			 */
+			if (rate >= BRCM_RATE_24M)
+				mandatory = BRCM_RATE_24M;
+			else if (rate >= BRCM_RATE_12M)
+				mandatory = BRCM_RATE_12M;
+			else
+				mandatory = BRCM_RATE_6M;
+		} else {
+			/* In 11b, all CCK rates are mandatory 1 - 11 Mbps */
+			mandatory = rate;
+		}
+
+		br[rate] = mandatory;
+	}
+}
+
+uint
+brcmu_calc_ack_time(int bandtype, const u8 *basic_rate, u32 rspec,
+		    u8 preamble_type)
+{
+	uint dur = 0;
+
+	/*
+	 * Spec 9.6: ack rate is the highest rate in BSSBasicRateSet that
+	 * is less than or equal to the rate of the immediately previous
+	 * frame in the FES
+	 */
+	rspec = brcmu_basic_rate(basic_rate, rspec);
+	/* ACK frame len == 14 == 2(fc) + 2(dur) + 6(ra) + 4(fcs) */
+	dur =
+	    brcmu_calc_frame_time(bandtype, rspec, preamble_type,
+				  (DOT11_ACK_LEN + FCS_LEN));
+	return dur;
+}
+
+uint
+brcmu_calc_cts_time(int bandtype, const u8 *basic_rate, u32 rspec,
+		    u8 preamble_type)
+{
+	return brcmu_calc_ack_time(bandtype, basic_rate, rspec, preamble_type);
+}
+
+static uint
+brcmu_calc_ba_time(int bandtype, const u8 *basic_rate, u32 rspec,
+		   u8 preamble_type)
+{
+	/*
+	 * Spec 9.6: ack rate is the highest rate in BSSBasicRateSet that
+	 * is less than or equal to the rate of the immediately previous
+	 * frame in the FES
+	 */
+	rspec = brcmu_basic_rate(basic_rate, rspec);
+	/* BA len == 32 == 16(ctl hdr) + 4(ba len) + 8(bitmap) + 4(fcs) */
+	return brcmu_calc_frame_time(bandtype, rspec, preamble_type,
+				     (DOT11_BA_LEN + DOT11_BA_BITMAP_LEN +
+				      FCS_LEN));
+}
+
+/*
+ * Compute PLCP, but only requires actual rate and length of pkt.
+ * Rate is given in the driver standard multiple of 500 kbps.
+ * le is set for 11 Mbps rate if necessary.
+ * Broken out for PRQ.
+ */
+
+static void brcmu_cck_plcp_set(int rate_500, uint length, u8 *plcp)
+{
+	u16 usec = 0;
+	u8 le = 0;
+
+	switch (rate_500) {
+	case BRCM_RATE_1M:
+		usec = length << 3;
+		break;
+	case BRCM_RATE_2M:
+		usec = length << 2;
+		break;
+	case BRCM_RATE_5M5:
+		usec = (length << 4) / 11;
+		if ((length << 4) - (usec * 11) > 0)
+			usec++;
+		break;
+	case BRCM_RATE_11M:
+		usec = (length << 3) / 11;
+		if ((length << 3) - (usec * 11) > 0) {
+			usec++;
+			if ((usec * 11) - (length << 3) >= 8)
+				le = D11B_PLCP_SIGNAL_LE;
+		}
+		break;
+
+	default:
+		pr_err("%s: unsupported rate %d\n", __func__, rate_500);
+		rate_500 = BRCM_RATE_1M;
+		usec = length << 3;
+		break;
+	}
+	/* PLCP signal byte */
+	plcp[0] = rate_500 * 5;	/* r (500kbps) * 5 == r (100kbps) */
+	/* PLCP service byte */
+	plcp[1] = (u8) (le | D11B_PLCP_SIGNAL_LOCKED);
+	/* PLCP length u16, little endian */
+	plcp[2] = usec & 0xff;
+	plcp[3] = (usec >> 8) & 0xff;
+	/* PLCP CRC16 */
+	plcp[4] = 0;
+	plcp[5] = 0;
+}
+
+/* Rate: 802.11 rate code, length: PSDU length in octets */
+static void brcmu_compute_mimo_plcp(u32 rspec, uint length, u8 *plcp)
+{
+	u8 mcs = (u8) (rspec & RSPEC_RATE_MASK);
+	plcp[0] = mcs;
+	if (rspec_is40mhz(rspec) || (mcs == 32))
+		plcp[0] |= MIMO_PLCP_40MHZ;
+	BRCMS_SET_MIMO_PLCP_LEN(plcp, length);
+	plcp[3] = rspec_mimoplcp3(rspec); /* rspec already holds this byte */
+	plcp[3] |= 0x7; /* set smoothing, not sounding ppdu & reserved */
+	plcp[4] = 0; /* number of extension spatial streams bit 0 & 1 */
+	plcp[5] = 0;
+}
+
+/* Rate: 802.11 rate code, length: PSDU length in octets */
+static void
+brcmu_compute_ofdm_plcp(u32 rspec, u32 length, u8 *plcp)
+{
+	u8 rate_signal;
+	u32 tmp = 0;
+	int rate = rspec2rate(rspec);
+
+	/*
+	 * encode rate per 802.11a-1999 sec 17.3.4.1, with lsb
+	 * transmitted first
+	 */
+	rate_signal = brcmu_rate_info[rate] & BRCMS_RATE_MASK;
+	memset(plcp, 0, D11_PHY_HDR_LEN);
+	D11A_PHY_HDR_SRATE((struct ofdm_phy_hdr *) plcp, rate_signal);
+
+	tmp = (length & 0xfff) << 5;
+	plcp[2] |= (tmp >> 16) & 0xff;
+	plcp[1] |= (tmp >> 8) & 0xff;
+	plcp[0] |= tmp & 0xff;
+}
+
+/* Rate: 802.11 rate code, length: PSDU length in octets */
+static void brcmu_compute_cck_plcp(u32 rspec, uint length, u8 *plcp)
+{
+	int rate = rspec2rate(rspec);
+
+	brcmu_cck_plcp_set(rate, length, plcp);
+}
+
+void
+brcmu_compute_plcp(u32 rspec, uint length, u8 *plcp)
+{
+	if (is_mcs_rate(rspec))
+		brcmu_compute_mimo_plcp(rspec, length, plcp);
+	else if (is_ofdm_rate(rspec))
+		brcmu_compute_ofdm_plcp(rspec, length, plcp);
+	else
+		brcmu_compute_cck_plcp(rspec, length, plcp);
+}
+
+/* brcmu_compute_rtscts_dur()
+ *
+ * Calculate the 802.11 MAC header DUR field for an RTS or CTS frame
+ * DUR for normal RTS/CTS w/ frame = 3 SIFS + 1 CTS + next frame time + 1 ACK
+ * DUR for CTS-TO-SELF w/ frame    = 2 SIFS         + next frame time + 1 ACK
+ *
+ * cts			cts-to-self or rts/cts
+ * rts_rate		rts or cts rate in unit of 500kbps
+ * rate			next MPDU rate in unit of 500kbps
+ * frame_len		next MPDU frame length in bytes
+ */
+u16
+brcmu_compute_rtscts_dur(int bandtype, const u8 *basic_rate, bool cts_only,
+			 u32 rts_rate,
+			 u32 frame_rate, u8 rts_preamble_type,
+			 u8 frame_preamble_type, uint frame_len, bool ba)
+{
+	u16 dur, sifs;
+
+	sifs = brcmu_get_sifs(bandtype);
+
+	if (!cts_only) {
+		/* RTS/CTS */
+		dur = 3 * sifs;
+		dur +=
+		    (u16)brcmu_calc_cts_time(bandtype, basic_rate, rts_rate,
+					     rts_preamble_type);
+	} else {
+		/* CTS-TO-SELF */
+		dur = 2 * sifs;
+	}
+
+	dur +=
+	    (u16)brcmu_calc_frame_time(bandtype, frame_rate,
+				       frame_preamble_type, frame_len);
+	if (ba)
+		dur +=
+		    (u16)brcmu_calc_ba_time(bandtype, basic_rate, frame_rate,
+					    BRCMS_SHORT_PREAMBLE);
+	else
+		dur +=
+		    (u16)brcmu_calc_ack_time(bandtype, basic_rate, frame_rate,
+					     frame_preamble_type);
+	return dur;
+}
+
+u16 brcmu_phytxctl1_calc(u16 phytype, u32 rspec)
+{
+	u16 phyctl1 = 0;
+	u16 bw;
+
+	if (phytype == PHY_TYPE_LCN) {
+		bw = PHY_TXC1_BW_20MHZ;
+	} else {
+		bw = rspec_get_bw(rspec);
+		/* 10Mhz is not supported yet */
+		if (bw < PHY_TXC1_BW_20MHZ) {
+			pr_err("%s: bw %d is not supported yet, set to 20L\n",
+			       __func__, bw);
+			bw = PHY_TXC1_BW_20MHZ;
+		}
+	}
+
+	if (is_mcs_rate(rspec)) {
+		uint mcs = rspec & RSPEC_RATE_MASK;
+
+		/* bw, stf, coding-type is part of rspec_phytxbyte2 returns */
+		phyctl1 = rspec_phytxbyte2(rspec);
+		/* set the upper byte of phyctl1 */
+		phyctl1 |= (brcmu_mcs_table[mcs].tx_phy_ctl3 << 8);
+	} else if (is_cck_rate(rspec) && phytype != PHY_TYPE_LCN &&
+		   phytype != PHY_TYPE_SSN) {
+		/*
+		 * In CCK mode LPPHY overloads OFDM Modulation bits with CCK
+		 * Data Rate. Eventually MIMOPHY would also be converted to
+		 * this format
+		 */
+		/* 0 = 1Mbps; 1 = 2Mbps; 2 = 5.5Mbps; 3 = 11Mbps */
+		phyctl1 = (bw | (rspec_stf(rspec) << PHY_TXC1_MODE_SHIFT));
+	} else {		/* legacy OFDM/CCK */
+		s16 phycfg;
+		/* get the phyctl byte from rate phycfg table */
+		phycfg = brcmu_rate_legacy_phyctl(rspec2rate(rspec));
+		if (phycfg == -1) {
+			pr_err("%s: wrong legacy OFDM/CCK rate\n", __func__);
+			phycfg = 0;
+		}
+		/* set the upper byte of phyctl1 */
+		phyctl1 =
+		    (bw | (phycfg << 8) |
+		     (rspec_stf(rspec) << PHY_TXC1_MODE_SHIFT));
+	}
+	return phyctl1;
+}
+
+/* calculate frame duration for Mixed-mode L-SIG spoofing, return
+ * number of bytes goes in the length field
+ *
+ * Formula given by HT PHY Spec v 1.13
+ *   len = 3(nsyms + nstream + 3) - 3
+ */
+u16
+brcmu_calc_lsig_len(u32 ratespec, uint mac_len)
+{
+	uint nsyms, len = 0, kNdps;
+
+	if (is_mcs_rate(ratespec)) {
+		uint mcs = ratespec & RSPEC_RATE_MASK;
+		int tot_streams = (mcs_2_txstreams(mcs) + 1) +
+				  rspec_stc(ratespec);
+
+		/*
+		 * the payload duration calculation matches that
+		 * of regular ofdm
+		 */
+		/* 1000Ndbps = kbps * 4, at the long-GI rate */
+		kNdps = mcs_2_rate(mcs, rspec_is40mhz(ratespec), false) * 4;
+
+		if (rspec_stc(ratespec) == 0)
+			nsyms =
+			    CEIL((APHY_SERVICE_NBITS + 8 * mac_len +
+				  APHY_TAIL_NBITS) * 1000, kNdps);
+		else
+			/* STBC needs to have even number of symbols */
+			nsyms =
+			    2 *
+			    CEIL((APHY_SERVICE_NBITS + 8 * mac_len +
+				  APHY_TAIL_NBITS) * 1000, 2 * kNdps);
+
+		/* short-GI symbols (3.6 us) in 4 us units, rounded up */
+		if (rspec_issgi(ratespec))
+			nsyms = CEIL(nsyms * 9, 10);
+		/* (+3) account for HT-SIG(2) and HT-STF(1) */
+		nsyms += (tot_streams + 3);
+		/*
+		 * 3 bytes/symbol @ legacy 6Mbps rate
+		 * (-3) excluding service bits and tail bits
+		 */
+		len = (3 * nsyms) - 3;
+	}
+
+	return (u16) len;
+}
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/rate.h b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/rate.h
index e20913bdf..401fed693 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/rate.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/rate.h
@@ -22,6 +22,30 @@
 #include "pub.h"
 #include "phy_hal.h"
 
+/* value for short/long and mixmode/greenfield preamble */
+#define BRCMS_LONG_PREAMBLE	(0)
+#define BRCMS_SHORT_PREAMBLE	(1 << 0)
+#define BRCMS_GF_PREAMBLE		(1 << 1)
+#define BRCMS_MM_PREAMBLE		(1 << 2)
+#define BRCMS_IS_MIMO_PREAMBLE(_pre) (((_pre) == BRCMS_GF_PREAMBLE) || \
+				      ((_pre) == BRCMS_MM_PREAMBLE))
+
+#define APHY_SYMBOL_TIME		4
+#define APHY_PREAMBLE_TIME		16
+#define APHY_SIGNAL_TIME		4
+#define APHY_SIFS_TIME			16
+#define APHY_SERVICE_NBITS		16
+#define APHY_TAIL_NBITS			6
+#define BPHY_SIFS_TIME			10
+#define BPHY_PLCP_SHORT_TIME		96
+#define BPHY_PLCP_TIME			192
+
+#define PREN_PREAMBLE			24
+#define PREN_MM_EXT			12
+#define PREN_PREAMBLE_EXT		4
+
+#define DOT11_OFDM_SIGNAL_EXTENSION	6
+
 extern const u8 brcmu_rate_info[];
 extern const struct brcms_c_rateset brcmu_cck_ofdm_mimo_rates;
 extern const struct brcms_c_rateset brcmu_ofdm_mimo_rates;
@@ -229,11 +253,33 @@ void brcmu_rateset_default(struct brcms_c_rateset *rs_tgt,
 			   int bandtype, bool cck_only, uint rate_mask,
 			   bool mcsallow, u8 bw, u8 txstreams);
 
-s16 brcmu_rate_legacy_phyctl(uint rate);
-
 void brcmu_rateset_mcs_upd(struct brcms_c_rateset *rs, u8 txstreams);
 void brcmu_rateset_mcs_clear(struct brcms_c_rateset *rateset);
 void brcmu_rateset_mcs_build(struct brcms_c_rateset *rateset, u8 txstreams);
 void brcmu_rateset_bw_mcs_filter(struct brcms_c_rateset *rateset, u8 bw);
 
+/* currently the best mechanism for determining SIFS is the band in use */
+static inline u16 brcmu_get_sifs(int bandtype)
+{
+	return bandtype == BRCM_BAND_5G ? APHY_SIFS_TIME : BPHY_SIFS_TIME;
+}
+
+u8 brcmu_basic_rate(const u8 *basic_rate, u32 rspec);
+uint brcmu_calc_frame_time(int bandtype, u32 ratespec,
+			   u8 preamble_type, uint mac_len);
+void brcmu_rate_lookup_init(u8 *br,
+			    const struct brcms_c_rateset *hw_rateset,
+			    struct brcms_c_rateset *rateset);
+uint brcmu_calc_ack_time(int bandtype, const u8 *basic_rate, u32 rspec,
+			 u8 preamble_type);
+uint brcmu_calc_cts_time(int bandtype, const u8 *basic_rate, u32 rspec,
+			 u8 preamble_type);
+void brcmu_compute_plcp(u32 rspec, uint length, u8 *plcp);
+u16 brcmu_compute_rtscts_dur(int bandtype, const u8 *basic_rate, bool cts_only,
+			     u32 rts_rate, u32 frame_rate,
+			     u8 rts_preamble_type, u8 frame_preamble_type,
+			     uint frame_len, bool ba);
+u16 brcmu_phytxctl1_calc(u16 phytype, u32 rspec);
+u16 brcmu_calc_lsig_len(u32 ratespec, uint mac_len);
+
 #endif				/* _BRCM_RATE_H_ */
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [RFC PATCH wireless-next 08/17] wifi: brcm80211: move the rate helpers of brcmsmac to brcmutil
  2026-10-26  5:36 [RFC PATCH wireless-next 00/17] wifi: add a driver for Broadcom BCM4323x USB adapters Ragul Balaji
                   ` (6 preceding siblings ...)
  2026-10-26  5:36 ` [RFC PATCH wireless-next 07/17] wifi: brcmsmac: move the PLCP and frame timing helpers to rate.c Ragul Balaji
@ 2026-10-26  5:36 ` Ragul Balaji
  2026-10-26  5:36 ` [RFC PATCH wireless-next 09/17] wifi: brcm80211: move the USB bootloader definitions to a shared header Ragul Balaji
                   ` (8 subsequent siblings)
  16 siblings, 0 replies; 18+ messages in thread
From: Ragul Balaji @ 2026-10-26  5:36 UTC (permalink / raw)
  To: linux-wireless
  Cc: Arend van Spriel, Johannes Berg, brcm80211, Ragul Balaji,
	Ho Jie Feng, brcm80211-dev-list.pdl, linux-kernel

Move rate.c to brcmutil in preparation for the new driver.

PHYTYPE_IS() depends on the PHY configuration of brcmsmac, so
brcmu_rateset_default() compares the PHY types directly, which is the
same for the N and LCN PHYs that brcmsmac attaches. brcmu_ofdm_rates
is only used in rate.c and becomes static.

Signed-off-by: Ragul Balaji <ragulbaby@gmail.com>
Signed-off-by: Ho Jie Feng <hjf3108@gmail.com>
---
 .../broadcom/brcm80211/brcmsmac/Makefile      |   1 -
 .../broadcom/brcm80211/brcmsmac/ampdu.c       |   2 +-
 .../broadcom/brcm80211/brcmsmac/main.c        |   2 +-
 .../broadcom/brcm80211/brcmsmac/pub.h         |  25 +---
 .../broadcom/brcm80211/brcmsmac/stf.c         |   2 +-
 .../broadcom/brcm80211/brcmutil/Makefile      |   2 +-
 .../brcm80211/{brcmsmac => brcmutil}/rate.c   | 141 +++++++++++-------
 .../{brcmsmac/rate.h => include/brcmu_rate.h} |  38 +++--
 8 files changed, 117 insertions(+), 96 deletions(-)
 rename drivers/net/wireless/broadcom/brcm80211/{brcmsmac => brcmutil}/rate.c (85%)
 rename drivers/net/wireless/broadcom/brcm80211/{brcmsmac/rate.h => include/brcmu_rate.h} (89%)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/Makefile b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/Makefile
index ffdd17eab..9cd7ef1bc 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/Makefile
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/Makefile
@@ -29,7 +29,6 @@ brcmsmac-y := \
 	main.o \
 	phy_shim.o \
 	pmu.o \
-	rate.o \
 	stf.o \
 	aiutils.o \
 	phy/phy_cmn.o \
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/ampdu.c b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/ampdu.c
index 19ffa72e2..d707c6e01 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/ampdu.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/ampdu.c
@@ -15,7 +15,7 @@
  */
 #include <net/mac80211.h>
 
-#include "rate.h"
+#include <brcmu_rate.h>
 #include "scb.h"
 #include "phy/phy_hal.h"
 #include "antsel.h"
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/main.c b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/main.c
index f7d9480cc..d923bc224 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/main.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/main.c
@@ -24,7 +24,7 @@
 #include <brcm_hw_ids.h>
 #include <aiutils.h>
 #include <chipcommon.h>
-#include "rate.h"
+#include <brcmu_rate.h>
 #include "scb.h"
 #include "phy/phy_hal.h"
 #include "channel.h"
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/pub.h b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/pub.h
index c366a7571..ed6c6888d 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/pub.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/pub.h
@@ -19,16 +19,10 @@
 
 #include <linux/bcma/bcma.h>
 #include <brcmu_wifi.h>
+#include <brcmu_rate.h>
 #include "types.h"
 #include "defs.h"
 
-#define	BRCMS_NUMRATES	16	/* max # of rates in a rateset */
-
-/* bw */
-#define BRCMS_10_MHZ	10	/* 10Mhz nphy channel bandwidth */
-#define BRCMS_20_MHZ	20	/* 20Mhz nphy channel bandwidth */
-#define BRCMS_40_MHZ	40	/* 40Mhz nphy channel bandwidth */
-
 #define	BRCMS_RSSI_MINVAL	-200	/* Low value, e.g. for forcing roam */
 #define	BRCMS_RSSI_NO_SIGNAL	-91	/* NDIS RSSI link quality cutoffs */
 #define	BRCMS_RSSI_VERY_LOW	-80	/* Very low quality cutoffs */
@@ -42,10 +36,6 @@
  */
 #define BRCMS_TXPWR_MAX		(127)	/* ~32 dBm = 1,500 mW */
 
-/* rate related definitions */
-#define	BRCMS_RATE_FLAG	0x80	/* Flag to indicate it is a basic rate */
-#define	BRCMS_RATE_MASK	0x7f	/* Rate value mask w/o basic rate flag */
-
 /* legacy rx Antenna diversity for SISO rates */
 #define	ANT_RX_DIV_FORCE_0	0	/* Use antenna 0 */
 #define	ANT_RX_DIV_FORCE_1	1	/* Use antenna 1 */
@@ -89,8 +79,6 @@
 
 #define RXBUFSZ		PKTBUFSZ
 
-#define MAX_STREAMS_SUPPORTED	4	/* max number of streams supported */
-
 struct brcm_rateset {
 	/* # rates in this set */
 	u32 count;
@@ -98,14 +86,6 @@ struct brcm_rateset {
 	u8 rates[WL_NUMRATES];
 };
 
-struct brcms_c_rateset {
-	uint count;		/* number of rates in rates[] */
-	 /* rates in 500kbps units w/hi bit set if basic */
-	u8 rates[BRCMS_NUMRATES];
-	u8 htphy_membership;	/* HT PHY Membership */
-	u8 mcs[MCSSET_LEN];	/* supported mcs index bit map */
-};
-
 /* All the HT-specific default advertised capabilities (including AMPDU)
  * should be grouped here at one place
  */
@@ -242,9 +222,6 @@ enum wlc_par_id {
 #define GMODE_LRS		5
 #define GMODE_MAX		6
 
-/* MCS values greater than this enable multiple streams */
-#define HIGHEST_SINGLE_STREAM_MCS	7
-
 #define	MAXBANDS		2	/* Maximum #of bands */
 
 /* max number of antenna configurations */
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/stf.c b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/stf.c
index 6e92dc4c9..67571db2b 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/stf.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/stf.c
@@ -18,7 +18,7 @@
 
 #include "types.h"
 #include <d11.h>
-#include "rate.h"
+#include <brcmu_rate.h>
 #include "phy/phy_hal.h"
 #include "channel.h"
 #include "main.h"
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmutil/Makefile b/drivers/net/wireless/broadcom/brcm80211/brcmutil/Makefile
index f9b40cab7..5d8b7c26a 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmutil/Makefile
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmutil/Makefile
@@ -7,4 +7,4 @@
 ccflags-y := -I $(src)/../include
 
 obj-$(CONFIG_BRCMUTIL)	+= brcmutil.o
-brcmutil-objs	= utils.o d11.o
+brcmutil-objs	= utils.o d11.o rate.o
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/rate.c b/drivers/net/wireless/broadcom/brcm80211/brcmutil/rate.c
similarity index 85%
rename from drivers/net/wireless/broadcom/brcm80211/brcmsmac/rate.c
rename to drivers/net/wireless/broadcom/brcm80211/brcmutil/rate.c
index 1c0259139..4d74145c7 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/rate.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmutil/rate.c
@@ -16,17 +16,18 @@
 
 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
 
-#include <brcmu_wifi.h>
-#include <brcmu_utils.h>
+#include <linux/math.h>
+#include <linux/module.h>
 
-#include <d11.h>
-#include "pub.h"
-#include "rate.h"
+#include <brcmu_rate.h>
+#include <brcmu_utils.h>
 
 #define DOT11_ACK_LEN			10
 #define DOT11_BA_LEN			4
 #define DOT11_BA_BITMAP_LEN		128
 
+static void brcmu_rateset_mcs_clear(struct brcms_c_rateset *rateset);
+
 /*
  * Rate info per rate: It tells whether a rate is ofdm or not and its phy_rate
  * value
@@ -45,108 +46,110 @@ const u8 brcmu_rate_info[BRCM_MAXRATE + 1] = {
 /*  90 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00,
 /* 100 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8c
 };
+EXPORT_SYMBOL(brcmu_rate_info);
 
 /* rates are in units of Kbps */
 const struct brcms_mcs_info brcmu_mcs_table[MCS_TABLE_SIZE] = {
 	/* MCS  0: SS 1, MOD: BPSK,  CR 1/2 */
-	{6500, 13500, CEIL(6500 * 10, 9), CEIL(13500 * 10, 9), 0x00,
+	{6500, 13500, DIV_ROUND_UP(6500 * 10, 9), DIV_ROUND_UP(13500 * 10, 9), 0x00,
 	 BRCM_RATE_6M},
 	/* MCS  1: SS 1, MOD: QPSK,  CR 1/2 */
-	{13000, 27000, CEIL(13000 * 10, 9), CEIL(27000 * 10, 9), 0x08,
+	{13000, 27000, DIV_ROUND_UP(13000 * 10, 9), DIV_ROUND_UP(27000 * 10, 9), 0x08,
 	 BRCM_RATE_12M},
 	/* MCS  2: SS 1, MOD: QPSK,  CR 3/4 */
-	{19500, 40500, CEIL(19500 * 10, 9), CEIL(40500 * 10, 9), 0x0A,
+	{19500, 40500, DIV_ROUND_UP(19500 * 10, 9), DIV_ROUND_UP(40500 * 10, 9), 0x0A,
 	 BRCM_RATE_18M},
 	/* MCS  3: SS 1, MOD: 16QAM, CR 1/2 */
-	{26000, 54000, CEIL(26000 * 10, 9), CEIL(54000 * 10, 9), 0x10,
+	{26000, 54000, DIV_ROUND_UP(26000 * 10, 9), DIV_ROUND_UP(54000 * 10, 9), 0x10,
 	 BRCM_RATE_24M},
 	/* MCS  4: SS 1, MOD: 16QAM, CR 3/4 */
-	{39000, 81000, CEIL(39000 * 10, 9), CEIL(81000 * 10, 9), 0x12,
+	{39000, 81000, DIV_ROUND_UP(39000 * 10, 9), DIV_ROUND_UP(81000 * 10, 9), 0x12,
 	 BRCM_RATE_36M},
 	/* MCS  5: SS 1, MOD: 64QAM, CR 2/3 */
-	{52000, 108000, CEIL(52000 * 10, 9), CEIL(108000 * 10, 9), 0x19,
+	{52000, 108000, DIV_ROUND_UP(52000 * 10, 9), DIV_ROUND_UP(108000 * 10, 9), 0x19,
 	 BRCM_RATE_48M},
 	/* MCS  6: SS 1, MOD: 64QAM, CR 3/4 */
-	{58500, 121500, CEIL(58500 * 10, 9), CEIL(121500 * 10, 9), 0x1A,
+	{58500, 121500, DIV_ROUND_UP(58500 * 10, 9), DIV_ROUND_UP(121500 * 10, 9), 0x1A,
 	 BRCM_RATE_54M},
 	/* MCS  7: SS 1, MOD: 64QAM, CR 5/6 */
-	{65000, 135000, CEIL(65000 * 10, 9), CEIL(135000 * 10, 9), 0x1C,
+	{65000, 135000, DIV_ROUND_UP(65000 * 10, 9), DIV_ROUND_UP(135000 * 10, 9), 0x1C,
 	 BRCM_RATE_54M},
 	/* MCS  8: SS 2, MOD: BPSK,  CR 1/2 */
-	{13000, 27000, CEIL(13000 * 10, 9), CEIL(27000 * 10, 9), 0x40,
+	{13000, 27000, DIV_ROUND_UP(13000 * 10, 9), DIV_ROUND_UP(27000 * 10, 9), 0x40,
 	 BRCM_RATE_6M},
 	/* MCS  9: SS 2, MOD: QPSK,  CR 1/2 */
-	{26000, 54000, CEIL(26000 * 10, 9), CEIL(54000 * 10, 9), 0x48,
+	{26000, 54000, DIV_ROUND_UP(26000 * 10, 9), DIV_ROUND_UP(54000 * 10, 9), 0x48,
 	 BRCM_RATE_12M},
 	/* MCS 10: SS 2, MOD: QPSK,  CR 3/4 */
-	{39000, 81000, CEIL(39000 * 10, 9), CEIL(81000 * 10, 9), 0x4A,
+	{39000, 81000, DIV_ROUND_UP(39000 * 10, 9), DIV_ROUND_UP(81000 * 10, 9), 0x4A,
 	 BRCM_RATE_18M},
 	/* MCS 11: SS 2, MOD: 16QAM, CR 1/2 */
-	{52000, 108000, CEIL(52000 * 10, 9), CEIL(108000 * 10, 9), 0x50,
+	{52000, 108000, DIV_ROUND_UP(52000 * 10, 9), DIV_ROUND_UP(108000 * 10, 9), 0x50,
 	 BRCM_RATE_24M},
 	/* MCS 12: SS 2, MOD: 16QAM, CR 3/4 */
-	{78000, 162000, CEIL(78000 * 10, 9), CEIL(162000 * 10, 9), 0x52,
+	{78000, 162000, DIV_ROUND_UP(78000 * 10, 9), DIV_ROUND_UP(162000 * 10, 9), 0x52,
 	 BRCM_RATE_36M},
 	/* MCS 13: SS 2, MOD: 64QAM, CR 2/3 */
-	{104000, 216000, CEIL(104000 * 10, 9), CEIL(216000 * 10, 9), 0x59,
+	{104000, 216000, DIV_ROUND_UP(104000 * 10, 9), DIV_ROUND_UP(216000 * 10, 9), 0x59,
 	 BRCM_RATE_48M},
 	/* MCS 14: SS 2, MOD: 64QAM, CR 3/4 */
-	{117000, 243000, CEIL(117000 * 10, 9), CEIL(243000 * 10, 9), 0x5A,
+	{117000, 243000, DIV_ROUND_UP(117000 * 10, 9), DIV_ROUND_UP(243000 * 10, 9), 0x5A,
 	 BRCM_RATE_54M},
 	/* MCS 15: SS 2, MOD: 64QAM, CR 5/6 */
-	{130000, 270000, CEIL(130000 * 10, 9), CEIL(270000 * 10, 9), 0x5C,
+	{130000, 270000, DIV_ROUND_UP(130000 * 10, 9), DIV_ROUND_UP(270000 * 10, 9), 0x5C,
 	 BRCM_RATE_54M},
 	/* MCS 16: SS 3, MOD: BPSK,  CR 1/2 */
-	{19500, 40500, CEIL(19500 * 10, 9), CEIL(40500 * 10, 9), 0x80,
+	{19500, 40500, DIV_ROUND_UP(19500 * 10, 9), DIV_ROUND_UP(40500 * 10, 9), 0x80,
 	 BRCM_RATE_6M},
 	/* MCS 17: SS 3, MOD: QPSK,  CR 1/2 */
-	{39000, 81000, CEIL(39000 * 10, 9), CEIL(81000 * 10, 9), 0x88,
+	{39000, 81000, DIV_ROUND_UP(39000 * 10, 9), DIV_ROUND_UP(81000 * 10, 9), 0x88,
 	 BRCM_RATE_12M},
 	/* MCS 18: SS 3, MOD: QPSK,  CR 3/4 */
-	{58500, 121500, CEIL(58500 * 10, 9), CEIL(121500 * 10, 9), 0x8A,
+	{58500, 121500, DIV_ROUND_UP(58500 * 10, 9), DIV_ROUND_UP(121500 * 10, 9), 0x8A,
 	 BRCM_RATE_18M},
 	/* MCS 19: SS 3, MOD: 16QAM, CR 1/2 */
-	{78000, 162000, CEIL(78000 * 10, 9), CEIL(162000 * 10, 9), 0x90,
+	{78000, 162000, DIV_ROUND_UP(78000 * 10, 9), DIV_ROUND_UP(162000 * 10, 9), 0x90,
 	 BRCM_RATE_24M},
 	/* MCS 20: SS 3, MOD: 16QAM, CR 3/4 */
-	{117000, 243000, CEIL(117000 * 10, 9), CEIL(243000 * 10, 9), 0x92,
+	{117000, 243000, DIV_ROUND_UP(117000 * 10, 9), DIV_ROUND_UP(243000 * 10, 9), 0x92,
 	 BRCM_RATE_36M},
 	/* MCS 21: SS 3, MOD: 64QAM, CR 2/3 */
-	{156000, 324000, CEIL(156000 * 10, 9), CEIL(324000 * 10, 9), 0x99,
+	{156000, 324000, DIV_ROUND_UP(156000 * 10, 9), DIV_ROUND_UP(324000 * 10, 9), 0x99,
 	 BRCM_RATE_48M},
 	/* MCS 22: SS 3, MOD: 64QAM, CR 3/4 */
-	{175500, 364500, CEIL(175500 * 10, 9), CEIL(364500 * 10, 9), 0x9A,
+	{175500, 364500, DIV_ROUND_UP(175500 * 10, 9), DIV_ROUND_UP(364500 * 10, 9), 0x9A,
 	 BRCM_RATE_54M},
 	/* MCS 23: SS 3, MOD: 64QAM, CR 5/6 */
-	{195000, 405000, CEIL(195000 * 10, 9), CEIL(405000 * 10, 9), 0x9B,
+	{195000, 405000, DIV_ROUND_UP(195000 * 10, 9), DIV_ROUND_UP(405000 * 10, 9), 0x9B,
 	 BRCM_RATE_54M},
 	/* MCS 24: SS 4, MOD: BPSK,  CR 1/2 */
-	{26000, 54000, CEIL(26000 * 10, 9), CEIL(54000 * 10, 9), 0xC0,
+	{26000, 54000, DIV_ROUND_UP(26000 * 10, 9), DIV_ROUND_UP(54000 * 10, 9), 0xC0,
 	 BRCM_RATE_6M},
 	/* MCS 25: SS 4, MOD: QPSK,  CR 1/2 */
-	{52000, 108000, CEIL(52000 * 10, 9), CEIL(108000 * 10, 9), 0xC8,
+	{52000, 108000, DIV_ROUND_UP(52000 * 10, 9), DIV_ROUND_UP(108000 * 10, 9), 0xC8,
 	 BRCM_RATE_12M},
 	/* MCS 26: SS 4, MOD: QPSK,  CR 3/4 */
-	{78000, 162000, CEIL(78000 * 10, 9), CEIL(162000 * 10, 9), 0xCA,
+	{78000, 162000, DIV_ROUND_UP(78000 * 10, 9), DIV_ROUND_UP(162000 * 10, 9), 0xCA,
 	 BRCM_RATE_18M},
 	/* MCS 27: SS 4, MOD: 16QAM, CR 1/2 */
-	{104000, 216000, CEIL(104000 * 10, 9), CEIL(216000 * 10, 9), 0xD0,
+	{104000, 216000, DIV_ROUND_UP(104000 * 10, 9), DIV_ROUND_UP(216000 * 10, 9), 0xD0,
 	 BRCM_RATE_24M},
 	/* MCS 28: SS 4, MOD: 16QAM, CR 3/4 */
-	{156000, 324000, CEIL(156000 * 10, 9), CEIL(324000 * 10, 9), 0xD2,
+	{156000, 324000, DIV_ROUND_UP(156000 * 10, 9), DIV_ROUND_UP(324000 * 10, 9), 0xD2,
 	 BRCM_RATE_36M},
 	/* MCS 29: SS 4, MOD: 64QAM, CR 2/3 */
-	{208000, 432000, CEIL(208000 * 10, 9), CEIL(432000 * 10, 9), 0xD9,
+	{208000, 432000, DIV_ROUND_UP(208000 * 10, 9), DIV_ROUND_UP(432000 * 10, 9), 0xD9,
 	 BRCM_RATE_48M},
 	/* MCS 30: SS 4, MOD: 64QAM, CR 3/4 */
-	{234000, 486000, CEIL(234000 * 10, 9), CEIL(486000 * 10, 9), 0xDA,
+	{234000, 486000, DIV_ROUND_UP(234000 * 10, 9), DIV_ROUND_UP(486000 * 10, 9), 0xDA,
 	 BRCM_RATE_54M},
 	/* MCS 31: SS 4, MOD: 64QAM, CR 5/6 */
-	{260000, 540000, CEIL(260000 * 10, 9), CEIL(540000 * 10, 9), 0xDB,
+	{260000, 540000, DIV_ROUND_UP(260000 * 10, 9), DIV_ROUND_UP(540000 * 10, 9), 0xDB,
 	 BRCM_RATE_54M},
 	/* MCS 32: SS 1, MOD: BPSK,  CR 1/2 */
-	{0, 6000, 0, CEIL(6000 * 10, 9), 0x00, BRCM_RATE_6M},
+	{0, 6000, 0, DIV_ROUND_UP(6000 * 10, 9), 0x00, BRCM_RATE_6M},
 };
+EXPORT_SYMBOL(brcmu_mcs_table);
 
 /*
  * phycfg for legacy OFDM frames: code rate, modulation scheme, spatial streams
@@ -203,6 +206,7 @@ const struct brcms_c_rateset brcmu_cck_ofdm_mimo_rates = {
 	{ 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 	  0x00, 0x00, 0x00, 0x00, 0x00}
 };
+EXPORT_SYMBOL(brcmu_cck_ofdm_mimo_rates);
 
 const struct brcms_c_rateset brcmu_ofdm_mimo_rates = {
 	8,
@@ -212,6 +216,7 @@ const struct brcms_c_rateset brcmu_ofdm_mimo_rates = {
 	{ 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 	  0x00, 0x00, 0x00, 0x00, 0x00}
 };
+EXPORT_SYMBOL(brcmu_ofdm_mimo_rates);
 
 /* Default ratesets that include MCS32 for 40BW channels */
 static const struct brcms_c_rateset cck_ofdm_40bw_mimo_rates = {
@@ -244,6 +249,7 @@ const struct brcms_c_rateset brcmu_cck_ofdm_rates = {
 	{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 	  0x00, 0x00, 0x00, 0x00, 0x00}
 };
+EXPORT_SYMBOL(brcmu_cck_ofdm_rates);
 
 const struct brcms_c_rateset brcmu_gphy_legacy_rates = {
 	4,
@@ -253,8 +259,9 @@ const struct brcms_c_rateset brcmu_gphy_legacy_rates = {
 	{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 	  0x00, 0x00, 0x00, 0x00, 0x00}
 };
+EXPORT_SYMBOL(brcmu_gphy_legacy_rates);
 
-const struct brcms_c_rateset brcmu_ofdm_rates = {
+static const struct brcms_c_rateset brcmu_ofdm_rates = {
 	8,
 	/*  6b,   9,    12b,  18,   24b,  36,   48,   54 Mbps */
 	{ 0x8c, 0x12, 0x98, 0x24, 0xb0, 0x48, 0x60, 0x6c},
@@ -271,6 +278,7 @@ const struct brcms_c_rateset brcmu_cck_rates = {
 	{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 	  0x00, 0x00, 0x00, 0x00, 0x00}
 };
+EXPORT_SYMBOL(brcmu_cck_rates);
 
 static const u8 ofdm_rate_lookup[] = {
 
@@ -318,6 +326,7 @@ void brcmu_rateset_mcs_upd(struct brcms_c_rateset *rs, u8 txstreams)
 	for (i = txstreams; i < MAX_STREAMS_SUPPORTED; i++)
 		rs->mcs[i] = 0;
 }
+EXPORT_SYMBOL(brcmu_rateset_mcs_upd);
 
 /*
  * filter based on hardware rateset, and sort filtered rateset with basic
@@ -363,6 +372,7 @@ brcmu_rate_hwrs_filter_sort_validate(struct brcms_c_rateset *rs,
 	else
 		return false;
 }
+EXPORT_SYMBOL(brcmu_rate_hwrs_filter_sort_validate);
 
 /* calculate the rate of a rx'd frame and return it as a ratespec */
 u32 brcmu_compute_rspec(struct d11rxhdr *rxh, u8 *plcp)
@@ -412,6 +422,7 @@ u32 brcmu_compute_rspec(struct d11rxhdr *rxh, u8 *plcp)
 
 	return rspec;
 }
+EXPORT_SYMBOL(brcmu_compute_rspec);
 
 /* copy rateset src to dst as-is (no masking or sorting) */
 void brcmu_rateset_copy(const struct brcms_c_rateset *src,
@@ -419,6 +430,7 @@ void brcmu_rateset_copy(const struct brcms_c_rateset *src,
 {
 	memcpy(dst, src, sizeof(struct brcms_c_rateset));
 }
+EXPORT_SYMBOL(brcmu_rateset_copy);
 
 /*
  * Copy and selectively filter one rateset to another.
@@ -458,6 +470,7 @@ brcmu_rateset_filter(struct brcms_c_rateset *src, struct brcms_c_rateset *dst,
 	else
 		brcmu_rateset_mcs_clear(dst);
 }
+EXPORT_SYMBOL(brcmu_rateset_filter);
 
 /* select rateset for a given phy_type and bandtype and filter it, sort it
  * and fill rs_tgt with result
@@ -470,10 +483,10 @@ brcmu_rateset_default(struct brcms_c_rateset *rs_tgt,
 {
 	const struct brcms_c_rateset *rs_dflt;
 	struct brcms_c_rateset rs_sel;
-	if ((PHYTYPE_IS(phy_type, PHY_TYPE_HT)) ||
-	    (PHYTYPE_IS(phy_type, PHY_TYPE_N)) ||
-	    (PHYTYPE_IS(phy_type, PHY_TYPE_LCN)) ||
-	    (PHYTYPE_IS(phy_type, PHY_TYPE_SSN))) {
+	if ((phy_type == PHY_TYPE_HT) ||
+	    (phy_type == PHY_TYPE_N) ||
+	    (phy_type == PHY_TYPE_LCN) ||
+	    (phy_type == PHY_TYPE_SSN)) {
 		if (bandtype == BRCM_BAND_5G)
 			rs_dflt = (bw == BRCMS_20_MHZ ?
 				   &brcmu_ofdm_mimo_rates : &ofdm_40bw_mimo_rates);
@@ -481,12 +494,12 @@ brcmu_rateset_default(struct brcms_c_rateset *rs_tgt,
 			rs_dflt = (bw == BRCMS_20_MHZ ?
 				   &brcmu_cck_ofdm_mimo_rates :
 				   &cck_ofdm_40bw_mimo_rates);
-	} else if (PHYTYPE_IS(phy_type, PHY_TYPE_LP)) {
+	} else if (phy_type == PHY_TYPE_LP) {
 		rs_dflt = (bandtype == BRCM_BAND_5G) ?
 			  &brcmu_ofdm_rates : &brcmu_cck_ofdm_rates;
-	} else if (PHYTYPE_IS(phy_type, PHY_TYPE_A)) {
+	} else if (phy_type == PHY_TYPE_A) {
 		rs_dflt = &brcmu_ofdm_rates;
-	} else if (PHYTYPE_IS(phy_type, PHY_TYPE_G)) {
+	} else if (phy_type == PHY_TYPE_G) {
 		rs_dflt = &brcmu_cck_ofdm_rates;
 	} else {
 		/* should not happen, error condition */
@@ -505,6 +518,7 @@ brcmu_rateset_default(struct brcms_c_rateset *rs_tgt,
 	brcmu_rate_hwrs_filter_sort_validate(rs_tgt, rs_hw, false,
 					     mcsallow ? txstreams : 1);
 }
+EXPORT_SYMBOL(brcmu_rateset_default);
 
 static s16 brcmu_rate_legacy_phyctl(uint rate)
 {
@@ -516,7 +530,7 @@ static s16 brcmu_rate_legacy_phyctl(uint rate)
 	return -1;
 }
 
-void brcmu_rateset_mcs_clear(struct brcms_c_rateset *rateset)
+static void brcmu_rateset_mcs_clear(struct brcms_c_rateset *rateset)
 {
 	uint i;
 	for (i = 0; i < MCSSET_LEN; i++)
@@ -528,6 +542,7 @@ void brcmu_rateset_mcs_build(struct brcms_c_rateset *rateset, u8 txstreams)
 	memcpy(&rateset->mcs[0], &brcmu_cck_ofdm_mimo_rates.mcs[0], MCSSET_LEN);
 	brcmu_rateset_mcs_upd(rateset, txstreams);
 }
+EXPORT_SYMBOL(brcmu_rateset_mcs_build);
 
 /* Based on bandwidth passed, allow/disallow MCS 32 in the rateset */
 void brcmu_rateset_bw_mcs_filter(struct brcms_c_rateset *rateset, u8 bw)
@@ -537,6 +552,7 @@ void brcmu_rateset_bw_mcs_filter(struct brcms_c_rateset *rateset, u8 bw)
 	else
 		clrbit(rateset->mcs, 32);
 }
+EXPORT_SYMBOL(brcmu_rateset_bw_mcs_filter);
 
 /* Find basic rate for a given rate */
 u8 brcmu_basic_rate(const u8 *basic_rate, u32 rspec)
@@ -546,6 +562,7 @@ u8 brcmu_basic_rate(const u8 *basic_rate, u32 rspec)
 		       .leg_ofdm];
 	return basic_rate[rspec & RSPEC_RATE_MASK];
 }
+EXPORT_SYMBOL(brcmu_basic_rate);
 
 /*
  * calculate frame duration of a given rate and length, return
@@ -575,14 +592,14 @@ uint brcmu_calc_frame_time(int bandtype, u32 ratespec,
 
 		if (rspec_stc(ratespec) == 0)
 			nsyms =
-			    CEIL((APHY_SERVICE_NBITS + 8 * mac_len +
-				  APHY_TAIL_NBITS) * 1000, kNdps);
+			    DIV_ROUND_UP((APHY_SERVICE_NBITS + 8 * mac_len +
+					  APHY_TAIL_NBITS) * 1000, kNdps);
 		else
 			/* STBC needs to have even number of symbols */
 			nsyms =
 			    2 *
-			    CEIL((APHY_SERVICE_NBITS + 8 * mac_len +
-				  APHY_TAIL_NBITS) * 1000, 2 * kNdps);
+			    DIV_ROUND_UP((APHY_SERVICE_NBITS + 8 * mac_len +
+					  APHY_TAIL_NBITS) * 1000, 2 * kNdps);
 
 		dur += APHY_SYMBOL_TIME * nsyms;
 		if (bandtype == BRCM_BAND_2G)
@@ -594,8 +611,8 @@ uint brcmu_calc_frame_time(int bandtype, u32 ratespec,
 		Ndps = rate * 2;
 		/* NSyms = CEILING((SERVICE + 8*NBytes + TAIL) / Ndbps) */
 		nsyms =
-		    CEIL((APHY_SERVICE_NBITS + 8 * mac_len + APHY_TAIL_NBITS),
-			 Ndps);
+		    DIV_ROUND_UP((APHY_SERVICE_NBITS + 8 * mac_len +
+				  APHY_TAIL_NBITS), Ndps);
 		dur += APHY_SYMBOL_TIME * nsyms;
 		if (bandtype == BRCM_BAND_2G)
 			dur += DOT11_OFDM_SIGNAL_EXTENSION;
@@ -614,6 +631,7 @@ uint brcmu_calc_frame_time(int bandtype, u32 ratespec,
 	}
 	return dur;
 }
+EXPORT_SYMBOL(brcmu_calc_frame_time);
 
 /* derive basic_rate[] table 'br' from 'rateset' */
 void brcmu_rate_lookup_init(u8 *br,
@@ -708,6 +726,7 @@ void brcmu_rate_lookup_init(u8 *br,
 		br[rate] = mandatory;
 	}
 }
+EXPORT_SYMBOL(brcmu_rate_lookup_init);
 
 uint
 brcmu_calc_ack_time(int bandtype, const u8 *basic_rate, u32 rspec,
@@ -727,6 +746,7 @@ brcmu_calc_ack_time(int bandtype, const u8 *basic_rate, u32 rspec,
 				  (DOT11_ACK_LEN + FCS_LEN));
 	return dur;
 }
+EXPORT_SYMBOL(brcmu_calc_ack_time);
 
 uint
 brcmu_calc_cts_time(int bandtype, const u8 *basic_rate, u32 rspec,
@@ -734,6 +754,7 @@ brcmu_calc_cts_time(int bandtype, const u8 *basic_rate, u32 rspec,
 {
 	return brcmu_calc_ack_time(bandtype, basic_rate, rspec, preamble_type);
 }
+EXPORT_SYMBOL(brcmu_calc_cts_time);
 
 static uint
 brcmu_calc_ba_time(int bandtype, const u8 *basic_rate, u32 rspec,
@@ -856,6 +877,7 @@ brcmu_compute_plcp(u32 rspec, uint length, u8 *plcp)
 	else
 		brcmu_compute_cck_plcp(rspec, length, plcp);
 }
+EXPORT_SYMBOL(brcmu_compute_plcp);
 
 /* brcmu_compute_rtscts_dur()
  *
@@ -902,6 +924,7 @@ brcmu_compute_rtscts_dur(int bandtype, const u8 *basic_rate, bool cts_only,
 					     frame_preamble_type);
 	return dur;
 }
+EXPORT_SYMBOL(brcmu_compute_rtscts_dur);
 
 u16 brcmu_phytxctl1_calc(u16 phytype, u32 rspec)
 {
@@ -951,6 +974,7 @@ u16 brcmu_phytxctl1_calc(u16 phytype, u32 rspec)
 	}
 	return phyctl1;
 }
+EXPORT_SYMBOL(brcmu_phytxctl1_calc);
 
 /* calculate frame duration for Mixed-mode L-SIG spoofing, return
  * number of bytes goes in the length field
@@ -977,18 +1001,18 @@ brcmu_calc_lsig_len(u32 ratespec, uint mac_len)
 
 		if (rspec_stc(ratespec) == 0)
 			nsyms =
-			    CEIL((APHY_SERVICE_NBITS + 8 * mac_len +
-				  APHY_TAIL_NBITS) * 1000, kNdps);
+			    DIV_ROUND_UP((APHY_SERVICE_NBITS + 8 * mac_len +
+					  APHY_TAIL_NBITS) * 1000, kNdps);
 		else
 			/* STBC needs to have even number of symbols */
 			nsyms =
 			    2 *
-			    CEIL((APHY_SERVICE_NBITS + 8 * mac_len +
-				  APHY_TAIL_NBITS) * 1000, 2 * kNdps);
+			    DIV_ROUND_UP((APHY_SERVICE_NBITS + 8 * mac_len +
+					  APHY_TAIL_NBITS) * 1000, 2 * kNdps);
 
 		/* short-GI symbols (3.6 us) in 4 us units, rounded up */
 		if (rspec_issgi(ratespec))
-			nsyms = CEIL(nsyms * 9, 10);
+			nsyms = DIV_ROUND_UP(nsyms * 9, 10);
 		/* (+3) account for HT-SIG(2) and HT-STF(1) */
 		nsyms += (tot_streams + 3);
 		/*
@@ -1000,3 +1024,4 @@ brcmu_calc_lsig_len(u32 ratespec, uint mac_len)
 
 	return (u16) len;
 }
+EXPORT_SYMBOL(brcmu_calc_lsig_len);
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/rate.h b/drivers/net/wireless/broadcom/brcm80211/include/brcmu_rate.h
similarity index 89%
rename from drivers/net/wireless/broadcom/brcm80211/brcmsmac/rate.h
rename to drivers/net/wireless/broadcom/brcm80211/include/brcmu_rate.h
index 401fed693..8f61f85c5 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/rate.h
+++ b/drivers/net/wireless/broadcom/brcm80211/include/brcmu_rate.h
@@ -14,13 +14,36 @@
  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-#ifndef _BRCM_RATE_H_
-#define _BRCM_RATE_H_
+#ifndef _BRCMU_RATE_H_
+#define _BRCMU_RATE_H_
 
-#include "types.h"
+#include <linux/types.h>
+#include <brcmu_wifi.h>
 #include <d11.h>
-#include "pub.h"
-#include "phy_hal.h"
+
+#define	BRCMS_NUMRATES	16	/* max # of rates in a rateset */
+
+/* bw */
+#define BRCMS_10_MHZ	10	/* 10Mhz nphy channel bandwidth */
+#define BRCMS_20_MHZ	20	/* 20Mhz nphy channel bandwidth */
+#define BRCMS_40_MHZ	40	/* 40Mhz nphy channel bandwidth */
+
+/* rate related definitions */
+#define	BRCMS_RATE_FLAG	0x80	/* Flag to indicate it is a basic rate */
+#define	BRCMS_RATE_MASK	0x7f	/* Rate value mask w/o basic rate flag */
+
+#define MAX_STREAMS_SUPPORTED	4	/* max number of streams supported */
+
+struct brcms_c_rateset {
+	uint count;		/* number of rates in rates[] */
+	 /* rates in 500kbps units w/hi bit set if basic */
+	u8 rates[BRCMS_NUMRATES];
+	u8 htphy_membership;	/* HT PHY Membership */
+	u8 mcs[MCSSET_LEN];	/* supported mcs index bit map */
+};
+
+/* MCS values greater than this enable multiple streams */
+#define HIGHEST_SINGLE_STREAM_MCS	7
 
 /* value for short/long and mixmode/greenfield preamble */
 #define BRCMS_LONG_PREAMBLE	(0)
@@ -50,10 +73,8 @@ extern const u8 brcmu_rate_info[];
 extern const struct brcms_c_rateset brcmu_cck_ofdm_mimo_rates;
 extern const struct brcms_c_rateset brcmu_ofdm_mimo_rates;
 extern const struct brcms_c_rateset brcmu_cck_ofdm_rates;
-extern const struct brcms_c_rateset brcmu_ofdm_rates;
 extern const struct brcms_c_rateset brcmu_cck_rates;
 extern const struct brcms_c_rateset brcmu_gphy_legacy_rates;
-extern const struct brcms_c_rateset rate_limit_1_2;
 
 struct brcms_mcs_info {
 	/* phy rate in kbps [20Mhz] */
@@ -254,7 +275,6 @@ void brcmu_rateset_default(struct brcms_c_rateset *rs_tgt,
 			   bool mcsallow, u8 bw, u8 txstreams);
 
 void brcmu_rateset_mcs_upd(struct brcms_c_rateset *rs, u8 txstreams);
-void brcmu_rateset_mcs_clear(struct brcms_c_rateset *rateset);
 void brcmu_rateset_mcs_build(struct brcms_c_rateset *rateset, u8 txstreams);
 void brcmu_rateset_bw_mcs_filter(struct brcms_c_rateset *rateset, u8 bw);
 
@@ -282,4 +302,4 @@ u16 brcmu_compute_rtscts_dur(int bandtype, const u8 *basic_rate, bool cts_only,
 u16 brcmu_phytxctl1_calc(u16 phytype, u32 rspec);
 u16 brcmu_calc_lsig_len(u32 ratespec, uint mac_len);
 
-#endif				/* _BRCM_RATE_H_ */
+#endif				/* _BRCMU_RATE_H_ */
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [RFC PATCH wireless-next 09/17] wifi: brcm80211: move the USB bootloader definitions to a shared header
  2026-10-26  5:36 [RFC PATCH wireless-next 00/17] wifi: add a driver for Broadcom BCM4323x USB adapters Ragul Balaji
                   ` (7 preceding siblings ...)
  2026-10-26  5:36 ` [RFC PATCH wireless-next 08/17] wifi: brcm80211: move the rate helpers of brcmsmac to brcmutil Ragul Balaji
@ 2026-10-26  5:36 ` Ragul Balaji
  2026-10-26  5:36 ` [RFC PATCH wireless-next 10/17] wifi: mac80211: export ieee80211_get_bssid() again Ragul Balaji
                   ` (7 subsequent siblings)
  16 siblings, 0 replies; 18+ messages in thread
From: Ragul Balaji @ 2026-10-26  5:36 UTC (permalink / raw)
  To: linux-wireless
  Cc: Arend van Spriel, Johannes Berg, brcm80211, Ragul Balaji,
	Ho Jie Feng, linux-kernel, brcm80211-dev-list.pdl

The Broadcom USB bootloader downloads firmware the same way for other
USB chips than the FullMAC ones of brcmfmac.  Move its commands, states,
firmware header and response layouts from brcmfmac's usb.c to a header
in the shared include directory, so that other drivers can use them.

Signed-off-by: Ragul Balaji <ragulbaby@gmail.com>
Signed-off-by: Ho Jie Feng <hjf3108@gmail.com>
---
 .../broadcom/brcm80211/brcmfmac/usb.c         | 68 +---------------
 .../broadcom/brcm80211/include/brcm_usb_rdl.h | 77 +++++++++++++++++++
 2 files changed, 78 insertions(+), 67 deletions(-)
 create mode 100644 drivers/net/wireless/broadcom/brcm80211/include/brcm_usb_rdl.h

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/usb.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/usb.c
index b41949a9b..e48f06468 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/usb.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/usb.c
@@ -12,6 +12,7 @@
 #include <brcmu_utils.h>
 #include <brcm_hw_ids.h>
 #include <brcmu_wifi.h>
+#include <brcm_usb_rdl.h>
 #include "bus.h"
 #include "debug.h"
 #include "firmware.h"
@@ -26,8 +27,6 @@
 #define BRCMF_USB_RESET_GETVER_SPINWAIT	100	/* in unit of ms */
 #define BRCMF_USB_RESET_GETVER_LOOP_CNT	10
 
-#define BRCMF_POSTBOOT_ID		0xA123  /* ID to detect if dongle
-						   has boot up */
 #define BRCMF_USB_NRXQ			50
 #define BRCMF_USB_NTXQ			50
 
@@ -52,71 +51,6 @@ static const struct brcmf_firmware_mapping brcmf_usb_fwnames[] = {
 	BRCMF_FW_ENTRY(CY_CC_4373_CHIP_ID, 0xFFFFFFFF, 4373)
 };
 
-#define TRX_MAGIC		0x30524448	/* "HDR0" */
-#define TRX_MAX_OFFSET		3		/* Max number of file offsets */
-#define TRX_UNCOMP_IMAGE	0x20		/* Trx holds uncompressed img */
-#define TRX_RDL_CHUNK		1500		/* size of each dl transfer */
-#define TRX_OFFSETS_DLFWLEN_IDX	0
-
-/* Control messages: bRequest values */
-#define DL_GETSTATE	0	/* returns the rdl_state_t struct */
-#define DL_CHECK_CRC	1	/* currently unused */
-#define DL_GO		2	/* execute downloaded image */
-#define DL_START	3	/* initialize dl state */
-#define DL_REBOOT	4	/* reboot the device in 2 seconds */
-#define DL_GETVER	5	/* returns the bootrom_id_t struct */
-#define DL_GO_PROTECTED	6	/* execute the downloaded code and set reset
-				 * event to occur in 2 seconds.  It is the
-				 * responsibility of the downloaded code to
-				 * clear this event
-				 */
-#define DL_EXEC		7	/* jump to a supplied address */
-#define DL_RESETCFG	8	/* To support single enum on dongle
-				 * - Not used by bootloader
-				 */
-#define DL_DEFER_RESP_OK 9	/* Potentially defer the response to setup
-				 * if resp unavailable
-				 */
-
-/* states */
-#define DL_WAITING	0	/* waiting to rx first pkt */
-#define DL_READY	1	/* hdr was good, waiting for more of the
-				 * compressed image
-				 */
-#define DL_BAD_HDR	2	/* hdr was corrupted */
-#define DL_BAD_CRC	3	/* compressed image was corrupted */
-#define DL_RUNNABLE	4	/* download was successful,waiting for go cmd */
-#define DL_START_FAIL	5	/* failed to initialize correctly */
-#define DL_NVRAM_TOOBIG	6	/* host specified nvram data exceeds DL_NVRAM
-				 * value
-				 */
-#define DL_IMAGE_TOOBIG	7	/* firmware image too big */
-
-
-struct trx_header_le {
-	__le32 magic;		/* "HDR0" */
-	__le32 len;		/* Length of file including header */
-	__le32 crc32;		/* CRC from flag_version to end of file */
-	__le32 flag_version;	/* 0:15 flags, 16:31 version */
-	__le32 offsets[TRX_MAX_OFFSET];	/* Offsets of partitions from start of
-					 * header
-					 */
-};
-
-struct rdl_state_le {
-	__le32 state;
-	__le32 bytes;
-};
-
-struct bootrom_id_le {
-	__le32 chip;		/* Chip id */
-	__le32 chiprev;		/* Chip rev */
-	__le32 ramsize;		/* Size of  RAM */
-	__le32 remapbase;	/* Current remap base address */
-	__le32 boardtype;	/* Type of board */
-	__le32 boardrev;	/* Board revision */
-};
-
 struct brcmf_usbdev_info {
 	struct brcmf_usbdev bus_pub; /* MUST BE FIRST */
 	spinlock_t qlock;
diff --git a/drivers/net/wireless/broadcom/brcm80211/include/brcm_usb_rdl.h b/drivers/net/wireless/broadcom/brcm80211/include/brcm_usb_rdl.h
new file mode 100644
index 000000000..cf8492a55
--- /dev/null
+++ b/drivers/net/wireless/broadcom/brcm80211/include/brcm_usb_rdl.h
@@ -0,0 +1,77 @@
+/* SPDX-License-Identifier: ISC */
+/*
+ * Copyright (c) 2011 Broadcom Corporation
+ */
+
+#ifndef	_BRCM_USB_RDL_H_
+#define	_BRCM_USB_RDL_H_
+
+#include <linux/types.h>
+
+#define BRCMF_POSTBOOT_ID	0xA123  /* ID to detect if dongle has boot up */
+
+#define TRX_MAGIC		0x30524448	/* "HDR0" */
+#define TRX_MAX_OFFSET		3		/* Max number of file offsets */
+#define TRX_UNCOMP_IMAGE	0x20		/* Trx holds uncompressed img */
+#define TRX_RDL_CHUNK		1500		/* size of each dl transfer */
+#define TRX_OFFSETS_DLFWLEN_IDX	0
+
+/* Control messages: bRequest values */
+#define DL_GETSTATE	0	/* returns the rdl_state_t struct */
+#define DL_CHECK_CRC	1	/* currently unused */
+#define DL_GO		2	/* execute downloaded image */
+#define DL_START	3	/* initialize dl state */
+#define DL_REBOOT	4	/* reboot the device in 2 seconds */
+#define DL_GETVER	5	/* returns the bootrom_id_t struct */
+#define DL_GO_PROTECTED	6	/* execute the downloaded code and set reset
+				 * event to occur in 2 seconds.  It is the
+				 * responsibility of the downloaded code to
+				 * clear this event
+				 */
+#define DL_EXEC		7	/* jump to a supplied address */
+#define DL_RESETCFG	8	/* To support single enum on dongle
+				 * - Not used by bootloader
+				 */
+#define DL_DEFER_RESP_OK 9	/* Potentially defer the response to setup
+				 * if resp unavailable
+				 */
+
+/* states */
+#define DL_WAITING	0	/* waiting to rx first pkt */
+#define DL_READY	1	/* hdr was good, waiting for more of the
+				 * compressed image
+				 */
+#define DL_BAD_HDR	2	/* hdr was corrupted */
+#define DL_BAD_CRC	3	/* compressed image was corrupted */
+#define DL_RUNNABLE	4	/* download was successful,waiting for go cmd */
+#define DL_START_FAIL	5	/* failed to initialize correctly */
+#define DL_NVRAM_TOOBIG	6	/* host specified nvram data exceeds DL_NVRAM
+				 * value
+				 */
+#define DL_IMAGE_TOOBIG	7	/* firmware image too big */
+
+struct trx_header_le {
+	__le32 magic;		/* "HDR0" */
+	__le32 len;		/* Length of file including header */
+	__le32 crc32;		/* CRC from flag_version to end of file */
+	__le32 flag_version;	/* 0:15 flags, 16:31 version */
+	__le32 offsets[TRX_MAX_OFFSET];	/* Offsets of partitions from start of
+					 * header
+					 */
+};
+
+struct rdl_state_le {
+	__le32 state;
+	__le32 bytes;
+};
+
+struct bootrom_id_le {
+	__le32 chip;		/* Chip id */
+	__le32 chiprev;		/* Chip rev */
+	__le32 ramsize;		/* Size of  RAM */
+	__le32 remapbase;	/* Current remap base address */
+	__le32 boardtype;	/* Type of board */
+	__le32 boardrev;	/* Board revision */
+};
+
+#endif /* _BRCM_USB_RDL_H_ */
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [RFC PATCH wireless-next 10/17] wifi: mac80211: export ieee80211_get_bssid() again
  2026-10-26  5:36 [RFC PATCH wireless-next 00/17] wifi: add a driver for Broadcom BCM4323x USB adapters Ragul Balaji
                   ` (8 preceding siblings ...)
  2026-10-26  5:36 ` [RFC PATCH wireless-next 09/17] wifi: brcm80211: move the USB bootloader definitions to a shared header Ragul Balaji
@ 2026-10-26  5:36 ` Ragul Balaji
  2026-10-26  5:36 ` [RFC PATCH wireless-next 11/17] wifi: bcm4323x: add the shared definitions Ragul Balaji
                   ` (6 subsequent siblings)
  16 siblings, 0 replies; 18+ messages in thread
From: Ragul Balaji @ 2026-10-26  5:36 UTC (permalink / raw)
  To: linux-wireless
  Cc: Arend van Spriel, Johannes Berg, brcm80211, Ragul Balaji,
	Ho Jie Feng, linux-kernel

Export ieee80211_get_bssid() again in preparation for the
new driver.

Signed-off-by: Ragul Balaji <ragulbaby@gmail.com>
Signed-off-by: Ho Jie Feng <hjf3108@gmail.com>
---
 include/net/mac80211.h     | 12 ++++++++++++
 net/mac80211/ieee80211_i.h |  2 --
 net/mac80211/util.c        |  1 +
 3 files changed, 13 insertions(+), 2 deletions(-)

diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index a1a6409b4..607369e32 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -6166,6 +6166,18 @@ __le16 ieee80211_generic_frame_duration(struct ieee80211_hw *hw,
 					size_t frame_len,
 					struct ieee80211_rate *rate);
 
+/**
+ * ieee80211_get_bssid - get the BSSID of a frame
+ * @hdr: the 802.11 header of the frame
+ * @len: the length of the frame
+ * @type: the type of the interface the frame belongs to
+ *
+ * Return: A pointer to the BSSID within @hdr, or %NULL if the frame
+ * carries none or is too short.
+ */
+u8 *ieee80211_get_bssid(struct ieee80211_hdr *hdr, size_t len,
+			enum nl80211_iftype type);
+
 /**
  * ieee80211_get_buffered_bc - accessing buffered broadcast and multicast frames
  * @hw: pointer as obtained from ieee80211_alloc_hw().
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 1a6cbbb5a..ee3a49321 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -2968,8 +2968,6 @@ ieee80211_process_tdls_channel_switch(struct ieee80211_sub_if_data *sdata,
 
 const char *ieee80211_get_reason_code_string(u16 reason_code);
 u16 ieee80211_encode_usf(int val);
-u8 *ieee80211_get_bssid(struct ieee80211_hdr *hdr, size_t len,
-			enum nl80211_iftype type);
 
 extern const struct ethtool_ops ieee80211_ethtool_ops;
 
diff --git a/net/mac80211/util.c b/net/mac80211/util.c
index 44feb8027..182b41db0 100644
--- a/net/mac80211/util.c
+++ b/net/mac80211/util.c
@@ -104,6 +104,7 @@ u8 *ieee80211_get_bssid(struct ieee80211_hdr *hdr, size_t len,
 
 	return NULL;
 }
+EXPORT_SYMBOL(ieee80211_get_bssid);
 
 void ieee80211_tx_set_protected(struct ieee80211_tx_data *tx)
 {
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [RFC PATCH wireless-next 11/17] wifi: bcm4323x: add the shared definitions
  2026-10-26  5:36 [RFC PATCH wireless-next 00/17] wifi: add a driver for Broadcom BCM4323x USB adapters Ragul Balaji
                   ` (9 preceding siblings ...)
  2026-10-26  5:36 ` [RFC PATCH wireless-next 10/17] wifi: mac80211: export ieee80211_get_bssid() again Ragul Balaji
@ 2026-10-26  5:36 ` Ragul Balaji
  2026-10-26  5:36 ` [RFC PATCH wireless-next 12/17] wifi: bcm4323x: add the USB transport and firmware setup Ragul Balaji
                   ` (5 subsequent siblings)
  16 siblings, 0 replies; 18+ messages in thread
From: Ragul Balaji @ 2026-10-26  5:36 UTC (permalink / raw)
  To: linux-wireless
  Cc: Arend van Spriel, Johannes Berg, brcm80211, Ragul Balaji,
	Ho Jie Feng, linux-kernel, brcm80211-dev-list.pdl

Add the definitions for the BCM4323x devices.

Signed-off-by: Ragul Balaji <ragulbaby@gmail.com>
Signed-off-by: Ho Jie Feng <hjf3108@gmail.com>
---
 .../broadcom/brcm80211/bcm4323x/bcm4323x.h    | 258 ++++++++++++++++++
 1 file changed, 258 insertions(+)
 create mode 100644 drivers/net/wireless/broadcom/brcm80211/bcm4323x/bcm4323x.h

diff --git a/drivers/net/wireless/broadcom/brcm80211/bcm4323x/bcm4323x.h b/drivers/net/wireless/broadcom/brcm80211/bcm4323x/bcm4323x.h
new file mode 100644
index 000000000..cd1c83dd4
--- /dev/null
+++ b/drivers/net/wireless/broadcom/brcm80211/bcm4323x/bcm4323x.h
@@ -0,0 +1,258 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#ifndef BCM4323X_H
+#define BCM4323X_H
+
+#include <linux/atomic.h>
+#include <linux/bitfield.h>
+#include <linux/completion.h>
+#include <linux/etherdevice.h>
+#include <linux/ieee80211.h>
+#include <linux/input.h>
+#include <linux/kfifo.h>
+#include <linux/led-class-multicolor.h>
+#include <linux/leds.h>
+#include <linux/mutex.h>
+#include <linux/skbuff.h>
+#include <linux/types.h>
+#include <linux/usb.h>
+#include <linux/wait.h>
+#include <linux/workqueue.h>
+#include <net/mac80211.h>
+
+#include <brcmu_rate.h>
+#include <brcmu_utils.h>
+#include <brcmu_wifi.h>
+#include <d11.h>
+
+#define BCM4323X_RX_SIZE 16384
+#define BCM4323X_RX_URBS 8
+#define BCM4323X_RPC_CLASS_MASK GENMASK(31, 24)
+#define BCM4323X_RPC_SESSION_MASK GENMASK(23, 16)
+#define BCM4323X_RPC_XID_MASK GENMASK(15, 0)
+#define BCM4323X_RPC_CLASS_DATA 1
+#define BCM4323X_RPC_CLASS_CONTROL 2
+#define BCM4323X_RPC_CLASS_TRANSPORT 3
+#define BCM4323X_RPC_SESSION 0x69
+#define BCM4323X_RPC_PROC_VERSION 1
+#define BCM4323X_RPC_PROC_CHIP 2
+#define BCM4323X_RPC_PROC_CHIP_ACK 3
+#define BCM4323X_RPC_VERSION 0x0564442e
+/* brcmsmac's bcma_read16() or bcma_read32() of a d11 register: {offset, size} */
+#define BCM4323X_RPC_PROC_REG_READ 0x01
+#define BCM4323X_RPC_PROC_LED_BLINK 0x76
+#define BCM4323X_RPC_PROC_GPIO_OUT 0x77
+/* brcmsmac's brcms_c_watchdog(), which the firmware answers while it runs */
+#define BCM4323X_RPC_PROC_WATCHDOG 0x99
+#define BCM4323X_NUM_2GHZ_CHANNELS 14
+#define BCM4323X_NUM_5GHZ_CHANNELS 24
+#define BCM4323X_NUM_CHANNELS (BCM4323X_NUM_2GHZ_CHANNELS + BCM4323X_NUM_5GHZ_CHANNELS)
+#define BCM4323X_ANTENNAS (BIT(0) | BIT(1))
+
+#define BCM4323X_HW_PAIRWISE_BASE 4
+#define BCM4323X_HW_KEYS M_SECKINDXALGO_BLK_SZ
+
+#define BCM4323X_NUM_CCK_RATES 4
+#define BCM4323X_NUM_OFDM_RATES 8
+#define BCM4323X_NUM_RATES (BCM4323X_NUM_CCK_RATES + BCM4323X_NUM_OFDM_RATES)
+#define BCM4323X_NUM_MCS 16
+#define BCM4323X_TX_STATUSES 8
+/* The index of the staged batch in tx_batches[], after the committed ones */
+#define BCM4323X_TX_STAGED 2
+
+struct bcm4323x_rpc_header {
+	__le32 length;
+	__le32 xid_class;
+	__le32 procedure;
+} __packed;
+
+/* The length word counts the bytes that follow it. */
+static inline __le32 bcm4323x_rpc_length(size_t body_length)
+{
+	return cpu_to_le32(sizeof(struct bcm4323x_rpc_header) - sizeof(__le32) + body_length);
+}
+
+static inline __le32 bcm4323x_rpc_xid_class(u8 rpc_class, u16 xid)
+{
+	return cpu_to_le32(FIELD_PREP(BCM4323X_RPC_CLASS_MASK, rpc_class) |
+			   FIELD_PREP(BCM4323X_RPC_SESSION_MASK, BCM4323X_RPC_SESSION) |
+			   FIELD_PREP(BCM4323X_RPC_XID_MASK, xid));
+}
+
+/* Procedure 0x52 */
+struct bcm4323x_tx_status {
+	/* from brcmsmac: struct tx_status, in little endian and in another order */
+	__le16 reserved0;
+	__le16 frameid;
+	__le16 status;
+	__le16 sequence;
+	__le32 lasttxtime;
+	__le16 phyerr;
+	__le16 ackphyrxsh;
+	__le32 reserved1[2];
+	/* The second status package of an aggregate, as in brcmsmac */
+	__le32 s1;
+	__le32 s2;
+} __packed;
+
+struct bcm4323x_txq {
+	struct sk_buff_head frames;
+	u8 ampdu_window;
+};
+
+/* Board/chip specific data*/
+struct bcm4323x_chip {
+	bool has_5ghz;
+	u8 channel_data_2ghz[3];
+	/* Hardcoded to green on GPIO 2 and red on GPIO 3 for now*/
+	bool has_wps_led;
+	/* Hardcoded to GPIO 10 for now */
+	bool has_wps_button;
+};
+
+struct bcm4323x {
+	struct ieee80211_hw *hw;
+	struct ieee80211_supported_band bands[2];
+	struct ieee80211_channel channels[BCM4323X_NUM_CHANNELS];
+	const struct bcm4323x_chip *chip;
+	struct usb_device *udev;
+	struct usb_interface *intf;
+	bool running;
+	/* Set when firmware hangs, different from running */
+	unsigned int dead;
+
+	/* Serializes the I/O buffer, transaction IDs and control responses. */
+	struct mutex io_mutex;
+
+	/* Shared USB transfer buffer */
+	u8 *io_buffer;
+	/* USB packet serial number */
+	u16 rpc_xid;
+	struct completion handshake_done;
+	__le32 handshake_chip;
+
+	struct delayed_work watchdog_work;
+	unsigned int watchdog_failures;
+	unsigned long phy_cal_expires;
+
+	struct urb *rx_urbs[BCM4323X_RX_URBS];
+	/* Only the first sub-frame in an A-MPDU have PLCP, save it for later frames */
+	u8 rx_ht_plcp[4];
+
+	u16 chanspec;
+	bool band_switched;
+	u16 ss_algo_channel;
+	int tx_power;
+	bool scanning;
+	s8 noise[BCM4323X_NUM_CHANNELS];
+	u32 filter_flags;
+
+	unsigned int tx_pipe;
+	struct usb_anchor tx_anchor;
+	wait_queue_head_t tx_wait;
+	struct sk_buff_head tx_queue;
+	struct work_struct tx_work;
+	DECLARE_KFIFO(tx_statuses, struct bcm4323x_tx_status, BCM4323X_TX_STATUSES);
+
+	/* Serializes the TX work with pausing, and protects the fields below. */
+	struct mutex tx_mutex;
+	u16 tx_sequence;
+
+	struct sk_buff_head tx_batches[BCM4323X_TX_STAGED + 1];
+	struct urb *tx_commit;
+	unsigned int tx_owned;
+	unsigned long tx_expires;
+	bool tx_paused;
+
+	struct ieee80211_vif __rcu *active_vif;
+
+	DECLARE_BITMAP(keys, BCM4323X_HW_KEYS);
+	u8 key_peers[BCM4323X_HW_KEYS][ETH_ALEN];
+
+	struct led_classdev led;
+	char led_name[32];
+	struct led_classdev_mc wps_led;
+	struct mc_subled wps_subleds[2];
+	char wps_led_name[40];
+	struct input_dev *wps_button;
+	char wps_button_phys[32];
+};
+
+/* Whether mac80211 started the device and the firmware still works */
+static inline bool bcm4323x_active(struct bcm4323x *dev)
+{
+	return READ_ONCE(dev->running) && !READ_ONCE(dev->dead);
+}
+
+int bcm4323x_rpc_send(struct bcm4323x *dev, u32 procedure, const void *body, size_t body_length);
+int bcm4323x_rpc_call(struct bcm4323x *dev, u32 procedure, const void *body, size_t body_length,
+		      void *response, size_t response_length);
+
+/* Sends a data procedure whose body is the arguments, as little-endian words */
+#define bcm4323x_rpc_send_u32(dev, procedure, ...) ({				\
+	u32 __body[] = { __VA_ARGS__ };						\
+										\
+	cpu_to_le32_array(__body, ARRAY_SIZE(__body));				\
+	bcm4323x_rpc_send(dev, procedure, __body, sizeof(__body));		\
+})
+
+/* Calls a control procedure the same way, with a word of response if any */
+#define bcm4323x_rpc_call_u32(dev, procedure, response, ...) ({			\
+	u32 __body[] = { __VA_ARGS__ };						\
+										\
+	cpu_to_le32_array(__body, ARRAY_SIZE(__body));				\
+	bcm4323x_rpc_call(dev, procedure, __body, sizeof(__body), response,	\
+			  sizeof(__le32));					\
+})
+
+void bcm4323x_fatal_error(struct bcm4323x *dev);
+int bcm4323x_start_rx_urbs(struct bcm4323x *dev);
+void bcm4323x_kill_rx_urbs(struct bcm4323x *dev);
+
+#ifdef CONFIG_MAC80211_LEDS
+void bcm4323x_led_init(struct bcm4323x *dev);
+void bcm4323x_led_register(struct bcm4323x *dev);
+void bcm4323x_led_unregister(struct bcm4323x *dev);
+#else
+static inline void bcm4323x_led_init(struct bcm4323x *dev) {}
+static inline void bcm4323x_led_register(struct bcm4323x *dev) {}
+static inline void bcm4323x_led_unregister(struct bcm4323x *dev) {}
+#endif
+
+extern struct ieee80211_rate bcm4323x_rates[BCM4323X_NUM_RATES];
+
+static inline unsigned int bcm4323x_first_rate(enum nl80211_band band)
+{
+	return band == NL80211_BAND_5GHZ ? BCM4323X_NUM_CCK_RATES : 0;
+}
+
+extern const struct ieee80211_ops bcm4323x_ops;
+int bcm4323x_register_hw(struct bcm4323x *dev);
+int bcm4323x_write_shm(struct bcm4323x *dev, u32 offset, u16 value);
+int bcm4323x_read_shm(struct bcm4323x *dev, u32 offset, u16 *value);
+int bcm4323x_init_mac(struct bcm4323x *dev);
+
+void bcm4323x_rx_complete(struct urb *urb);
+
+void bcm4323x_init_tx(struct bcm4323x *dev);
+void bcm4323x_init_basic_rate(u8 *basic_rate, enum nl80211_band band, u32 basic_rates);
+u8 bcm4323x_stf_mode(u16 ss_algo_channel);
+u8 bcm4323x_ac_to_fifo(u8 ac);
+void bcm4323x_tx_status(struct bcm4323x *dev, const u8 *body, size_t body_length);
+void bcm4323x_check_tx(struct bcm4323x *dev);
+void bcm4323x_set_tx_paused(struct bcm4323x *dev, bool paused);
+bool bcm4323x_quiesce_tx(struct bcm4323x *dev);
+void bcm4323x_flush_tx(struct bcm4323x *dev);
+void bcm4323x_retire_key(struct bcm4323x *dev, u8 key_index, struct ieee80211_sta *sta);
+void bcm4323x_init_txq(struct ieee80211_txq *txq);
+void bcm4323x_tx(struct ieee80211_hw *hw, struct ieee80211_tx_control *control,
+		 struct sk_buff *skb);
+void bcm4323x_wake_tx_queue(struct ieee80211_hw *hw, struct ieee80211_txq *txq);
+int bcm4323x_sta_state(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+		       struct ieee80211_sta *sta, enum ieee80211_sta_state old_state,
+		       enum ieee80211_sta_state new_state);
+void bcm4323x_flush(struct ieee80211_hw *hw, struct ieee80211_vif *vif, u32 queues, bool drop);
+int bcm4323x_ampdu_action(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+			  struct ieee80211_ampdu_params *params);
+
+#endif /* BCM4323X_H */
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [RFC PATCH wireless-next 12/17] wifi: bcm4323x: add the USB transport and firmware setup
  2026-10-26  5:36 [RFC PATCH wireless-next 00/17] wifi: add a driver for Broadcom BCM4323x USB adapters Ragul Balaji
                   ` (10 preceding siblings ...)
  2026-10-26  5:36 ` [RFC PATCH wireless-next 11/17] wifi: bcm4323x: add the shared definitions Ragul Balaji
@ 2026-10-26  5:36 ` Ragul Balaji
  2026-10-26  5:36 ` [RFC PATCH wireless-next 13/17] wifi: bcm4323x: add the receive path Ragul Balaji
                   ` (4 subsequent siblings)
  16 siblings, 0 replies; 18+ messages in thread
From: Ragul Balaji @ 2026-10-26  5:36 UTC (permalink / raw)
  To: linux-wireless
  Cc: Arend van Spriel, Johannes Berg, brcm80211, Ragul Balaji,
	Ho Jie Feng, linux-kernel, brcm80211-dev-list.pdl

Adds the usb side of the driver, enumeration, tx/rx and
firmware. Requires brcm/bcm4323x.bin.

The ID table covers the BCM43231 and BCM4323 adapters of
Belkin, Dynex and Netgear.

Signed-off-by: Ragul Balaji <ragulbaby@gmail.com>
Signed-off-by: Ho Jie Feng <hjf3108@gmail.com>
---
 .../brcm80211/bcm4323x/bcm4323x_core.c        | 545 ++++++++++++++++++
 1 file changed, 545 insertions(+)
 create mode 100644 drivers/net/wireless/broadcom/brcm80211/bcm4323x/bcm4323x_core.c

diff --git a/drivers/net/wireless/broadcom/brcm80211/bcm4323x/bcm4323x_core.c b/drivers/net/wireless/broadcom/brcm80211/bcm4323x/bcm4323x_core.c
new file mode 100644
index 000000000..e1a3cdf3b
--- /dev/null
+++ b/drivers/net/wireless/broadcom/brcm80211/bcm4323x/bcm4323x_core.c
@@ -0,0 +1,545 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/device.h>
+#include <linux/firmware.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+
+#include <brcm_usb_rdl.h>
+
+#include "bcm4323x.h"
+
+#define BCM4323X_FW_NAME "brcm/bcm4323x.bin"
+
+#define BCM4323X_REQ_RPC_RESPONSE 9
+#define BCM4323X_REQ_TYPE (USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_INTERFACE)
+#define BCM4323X_DL_VALUE 1
+#define BCM4323X_TIMEOUT_MS 2000
+#define BCM4323X_BOOT_CHIP_ID 0x4322
+
+#define BCM4323X_BOOT_POLL_US 40000
+#define BCM4323X_BOOT_TIMEOUT_US 1200000
+#define BCM4323X_CONTROL_MAX 1928
+#define BCM4323X_IO_SIZE 4096
+#define BCM4323X_RPC_READS 16
+
+union bcm4323x_dl_response {
+	struct rdl_state_le rdl;
+	struct {
+		__le32 chip;
+		__le32 chiprev;
+	} id;
+};
+
+struct bcm4323x_handshake {
+	__le32 version;
+	__le32 chip;
+} __packed;
+
+static int bcm4323x_bulk_transfer(struct bcm4323x *dev, void *buffer, int length, bool *sent)
+{
+	int actual = 0;
+	int ret;
+
+	ret = usb_bulk_msg(dev->udev, dev->tx_pipe, buffer, length, &actual, BCM4323X_TIMEOUT_MS);
+	if (sent)
+		*sent = actual > 0;
+	if (ret)
+		return ret;
+
+	if (!(length % usb_maxpacket(dev->udev, dev->tx_pipe)))
+		return usb_bulk_msg(dev->udev, dev->tx_pipe, NULL, 0, &actual, BCM4323X_TIMEOUT_MS);
+
+	return 0;
+}
+
+/* from brcmfmac: check_file */
+static int bcm4323x_check_file(const struct firmware *fw)
+{
+	const struct trx_header_le *trx = (const void *)fw->data;
+
+	/* Unlike brcmfmac's images, this one is not flagged TRX_UNCOMP_IMAGE. */
+	if (fw->size < sizeof(*trx) || trx->magic != cpu_to_le32(TRX_MAGIC) ||
+	    le32_to_cpu(trx->len) != fw->size)
+		return -EINVAL;
+	return 0;
+}
+
+/* from brcmfmac: brcmf_usb_dl_cmd */
+static int bcm4323x_dl_cmd(struct bcm4323x *dev, u8 cmd, union bcm4323x_dl_response *buffer)
+{
+	return usb_control_msg_recv(dev->udev, 0, cmd, BCM4323X_REQ_TYPE, BCM4323X_DL_VALUE,
+				    dev->intf->cur_altsetting->desc.bInterfaceNumber, buffer,
+				    sizeof(*buffer), BCM4323X_TIMEOUT_MS, GFP_KERNEL);
+}
+
+/* from brcmfmac: brcmf_usb_resetcfg */
+static int bcm4323x_wait_postboot(struct bcm4323x *dev)
+{
+	union bcm4323x_dl_response id;
+	int err;
+
+	/* The bootloader stalls or answers short while the runtime starts. */
+	return poll_timeout_us(err = bcm4323x_dl_cmd(dev, DL_GETVER, &id),
+			       err ? err != -EPIPE && err != -EREMOTEIO :
+				     id.id.chip == cpu_to_le32(BRCMF_POSTBOOT_ID),
+			       BCM4323X_BOOT_POLL_US, BCM4323X_BOOT_TIMEOUT_US, true) ?: err;
+}
+
+/* from brcmfmac: brcmf_usb_dl_writeimage */
+static int bcm4323x_dl_writeimage(struct bcm4323x *dev, const u8 *fw, u32 fwlen)
+{
+	union bcm4323x_dl_response state;
+	u32 rdlstate;
+	u32 sent = 0;
+	int err;
+
+	err = bcm4323x_dl_cmd(dev, DL_START, &state);
+	if (err)
+		return err;
+	if (le32_to_cpu(state.rdl.state) != DL_WAITING)
+		return -EINVAL;
+
+	while (sent < fwlen) {
+		u32 sendlen = min_t(u32, fwlen - sent, TRX_RDL_CHUNK);
+
+		memcpy(dev->io_buffer, fw + sent, sendlen);
+		err = bcm4323x_bulk_transfer(dev, dev->io_buffer, sendlen, NULL);
+		if (err)
+			return err;
+		sent += sendlen;
+
+		err = bcm4323x_dl_cmd(dev, DL_GETSTATE, &state);
+		if (err)
+			return err;
+		rdlstate = le32_to_cpu(state.rdl.state);
+		/*
+		 * Unlike brcmfmac, expect the bytes to be accepted at once, as the
+		 * reference driver does.
+		 */
+		if (rdlstate == DL_BAD_HDR || rdlstate == DL_BAD_CRC ||
+		    le32_to_cpu(state.rdl.bytes) != sent)
+			return -EINVAL;
+	}
+	return 0;
+}
+
+/* from brcmfmac: brcmf_usb_dlrun */
+static int bcm4323x_dlrun(struct bcm4323x *dev)
+{
+	union bcm4323x_dl_response state;
+	int err;
+
+	err = bcm4323x_dl_cmd(dev, DL_GETSTATE, &state);
+	if (err)
+		return err;
+	if (state.rdl.state != cpu_to_le32(DL_RUNNABLE))
+		return -EINVAL;
+
+	err = bcm4323x_dl_cmd(dev, DL_GO, &state);
+	if (err)
+		return err;
+	return bcm4323x_wait_postboot(dev);
+}
+
+/* from brcmfmac: brcmf_usb_fw_download, brcmf_usb_dlneeded */
+static int bcm4323x_fw_download(struct bcm4323x *dev, const struct firmware *fw)
+{
+	union bcm4323x_dl_response id;
+	int err;
+
+	err = bcm4323x_check_file(fw);
+	if (err)
+		return err;
+
+	err = bcm4323x_dl_cmd(dev, DL_GETVER, &id);
+	if (err)
+		return err;
+	if (id.id.chip != cpu_to_le32(BRCMF_POSTBOOT_ID)) {
+		if (id.id.chip != cpu_to_le32(BCM4323X_BOOT_CHIP_ID))
+			return -ENODEV;
+		err = bcm4323x_dl_writeimage(dev, fw->data, fw->size);
+		if (!err)
+			err = bcm4323x_dlrun(dev);
+		if (err)
+			return err;
+	}
+	bcm4323x_dl_cmd(dev, DL_RESETCFG, &id);
+	return 0;
+}
+
+/*
+ * Reads the response to a control procedure, skipping the responses to other
+ * procedures that the dongle kept, and copies the start of its body, if any.
+ */
+static int bcm4323x_rpc_response_locked(struct bcm4323x *dev, u32 procedure, void *response,
+					size_t response_length)
+{
+	const struct bcm4323x_rpc_header *rpc = (void *)dev->io_buffer;
+	u8 interface = dev->intf->cur_altsetting->desc.bInterfaceNumber;
+	unsigned int reads;
+	size_t length;
+	int ret;
+
+	lockdep_assert_held(&dev->io_mutex);
+	for (reads = 0; reads < BCM4323X_RPC_READS; reads++) {
+		ret = usb_control_msg(dev->udev, usb_rcvctrlpipe(dev->udev, 0),
+				      BCM4323X_REQ_RPC_RESPONSE, BCM4323X_REQ_TYPE, 0, interface,
+				      dev->io_buffer, BCM4323X_CONTROL_MAX,
+				      BCM4323X_TIMEOUT_MS);
+		if (ret < 0)
+			return ret;
+		if (ret < sizeof(*rpc))
+			return -EPROTO;
+
+		length = le32_to_cpu(rpc->length) + sizeof(rpc->length);
+		if (length < sizeof(*rpc) || length > ret ||
+		    FIELD_GET(BCM4323X_RPC_CLASS_MASK, le32_to_cpu(rpc->xid_class)) !=
+			    BCM4323X_RPC_CLASS_CONTROL)
+			return -EPROTO;
+		if (le32_to_cpu(rpc->procedure) != procedure) {
+			dev_dbg(&dev->intf->dev, "skipped response to RPC 0x%x\n",
+				le32_to_cpu(rpc->procedure));
+			continue;
+		}
+		if (!response)
+			return 0;
+		if (length - sizeof(*rpc) < response_length)
+			return -EPROTO;
+		memcpy(response, rpc + 1, response_length);
+		return 0;
+	}
+	return -EPROTO;
+}
+
+static int bcm4323x_rpc_transact(struct bcm4323x *dev, u8 rpc_class, u32 procedure,
+				 const void *body, size_t body_length, void *response,
+				 size_t response_length)
+{
+	struct bcm4323x_rpc_header *rpc;
+	bool sent;
+	int ret;
+
+	if (body_length > BCM4323X_IO_SIZE - sizeof(*rpc))
+		return -EMSGSIZE;
+	if (READ_ONCE(dev->dead))
+		return -ENODEV;
+
+	mutex_lock(&dev->io_mutex);
+	rpc = (void *)dev->io_buffer;
+	rpc->length = bcm4323x_rpc_length(body_length);
+	rpc->xid_class = bcm4323x_rpc_xid_class(rpc_class, dev->rpc_xid);
+	rpc->procedure = cpu_to_le32(procedure);
+	memcpy(rpc + 1, body, body_length);
+	ret = bcm4323x_bulk_transfer(dev, dev->io_buffer, sizeof(*rpc) + body_length, &sent);
+	if (sent)
+		dev->rpc_xid++;
+	if (!ret && rpc_class == BCM4323X_RPC_CLASS_CONTROL)
+		ret = bcm4323x_rpc_response_locked(dev, procedure, response, response_length);
+	mutex_unlock(&dev->io_mutex);
+	return ret;
+}
+
+int bcm4323x_rpc_send(struct bcm4323x *dev, u32 procedure, const void *body, size_t body_length)
+{
+	return bcm4323x_rpc_transact(dev, BCM4323X_RPC_CLASS_DATA, procedure, body, body_length,
+				     NULL, 0);
+}
+
+int bcm4323x_rpc_call(struct bcm4323x *dev, u32 procedure, const void *body, size_t body_length,
+		      void *response, size_t response_length)
+{
+	return bcm4323x_rpc_transact(dev, BCM4323X_RPC_CLASS_CONTROL, procedure, body,
+				     body_length, response, response_length);
+}
+
+static int bcm4323x_handshake_step(struct bcm4323x *dev, u32 procedure, __le32 chip)
+{
+	struct bcm4323x_handshake body = {
+		.version = cpu_to_le32(BCM4323X_RPC_VERSION),
+		.chip = chip,
+	};
+	int ret;
+
+	reinit_completion(&dev->handshake_done);
+	ret = bcm4323x_rpc_transact(dev, BCM4323X_RPC_CLASS_TRANSPORT, procedure, &body,
+				    sizeof(body), NULL, 0);
+	if (ret)
+		return ret;
+	if (!wait_for_completion_timeout(&dev->handshake_done,
+					 msecs_to_jiffies(BCM4323X_TIMEOUT_MS)))
+		return -ETIMEDOUT;
+	/* VERSION sends no chip ID, and CHIP the one that the VERSION reply carried. */
+	if (!dev->handshake_chip || (chip && dev->handshake_chip != chip))
+		return -EPROTO;
+	return 0;
+}
+
+/* Reset the runtime firmware to its initialized state. */
+static int bcm4323x_init_firmware(struct bcm4323x *dev)
+{
+	int ret;
+
+	ret = bcm4323x_start_rx_urbs(dev);
+	if (ret)
+		return ret;
+	ret = bcm4323x_handshake_step(dev, BCM4323X_RPC_PROC_VERSION, 0);
+	if (!ret)
+		ret = bcm4323x_handshake_step(dev, BCM4323X_RPC_PROC_CHIP, dev->handshake_chip);
+
+	dev->rpc_xid = 0;
+	if (!ret)
+		ret = bcm4323x_init_mac(dev);
+	bcm4323x_kill_rx_urbs(dev);
+	if (!ret && READ_ONCE(dev->dead))
+		ret = -EIO;
+	return ret;
+}
+
+void bcm4323x_kill_rx_urbs(struct bcm4323x *dev)
+{
+	unsigned int index;
+
+	for (index = 0; index < BCM4323X_RX_URBS; index++)
+		usb_kill_urb(dev->rx_urbs[index]);
+}
+
+int bcm4323x_start_rx_urbs(struct bcm4323x *dev)
+{
+	unsigned int index;
+	int ret;
+
+	for (index = 0; index < BCM4323X_RX_URBS; index++) {
+		ret = usb_submit_urb(dev->rx_urbs[index], GFP_KERNEL);
+		if (ret) {
+			bcm4323x_kill_rx_urbs(dev);
+			return ret;
+		}
+	}
+	return 0;
+}
+
+static int bcm4323x_alloc_transport(struct bcm4323x *dev)
+{
+	struct usb_endpoint_descriptor *bulk_in;
+	struct usb_endpoint_descriptor *bulk_out;
+	unsigned int rx_pipe;
+	unsigned int index;
+
+	if (usb_find_common_endpoints_reverse(dev->intf->cur_altsetting, &bulk_in, &bulk_out,
+					      NULL, NULL))
+		return -ENODEV;
+	dev->tx_pipe = usb_sndbulkpipe(dev->udev, bulk_out->bEndpointAddress);
+	dev->io_buffer = devm_kmalloc(&dev->intf->dev, BCM4323X_IO_SIZE, GFP_KERNEL);
+	if (!dev->io_buffer)
+		return -ENOMEM;
+
+	rx_pipe = usb_rcvbulkpipe(dev->udev, bulk_in->bEndpointAddress);
+	for (index = 0; index < BCM4323X_RX_URBS; index++) {
+		struct urb *urb = usb_alloc_urb(0, GFP_KERNEL);
+		void *buffer;
+
+		if (!urb)
+			return -ENOMEM;
+		dev->rx_urbs[index] = urb;
+		buffer = kmalloc(BCM4323X_RX_SIZE, GFP_KERNEL);
+		if (!buffer)
+			return -ENOMEM;
+		usb_fill_bulk_urb(urb, dev->udev, rx_pipe, buffer, BCM4323X_RX_SIZE,
+				  bcm4323x_rx_complete, dev);
+		urb->transfer_flags = URB_FREE_BUFFER;
+	}
+	return 0;
+}
+
+static void bcm4323x_free_transport(struct bcm4323x *dev)
+{
+	unsigned int index;
+
+	for (index = 0; index < BCM4323X_RX_URBS; index++)
+		usb_free_urb(dev->rx_urbs[index]);
+}
+
+struct bcm4323x_reprobe {
+	struct work_struct work;
+	struct device *dev;
+};
+
+static void bcm4323x_reprobe_work(struct work_struct *work)
+{
+	struct bcm4323x_reprobe *reprobe = container_of(work, struct bcm4323x_reprobe, work);
+	int ret;
+
+	ret = device_reprobe(reprobe->dev);
+	if (ret)
+		dev_err(reprobe->dev, "reprobe failed: %d\n", ret);
+	put_device(reprobe->dev);
+	kfree(reprobe);
+	module_put(THIS_MODULE);
+}
+
+/* Reprobe the device in case the firmware stalls */
+void bcm4323x_fatal_error(struct bcm4323x *dev)
+{
+	struct bcm4323x_reprobe *reprobe;
+
+	if (xchg(&dev->dead, true))
+		return;
+
+	wake_up_all(&dev->tx_wait);
+	if (!READ_ONCE(dev->running))
+		return;
+	ieee80211_stop_queues(dev->hw);
+
+	if (!try_module_get(THIS_MODULE))
+		return;
+	reprobe = kzalloc_obj(*reprobe, GFP_ATOMIC);
+	if (!reprobe) {
+		module_put(THIS_MODULE);
+		return;
+	}
+	INIT_WORK(&reprobe->work, bcm4323x_reprobe_work);
+	reprobe->dev = get_device(&dev->intf->dev);
+	queue_work(system_long_wq, &reprobe->work);
+}
+
+static int bcm4323x_probe(struct usb_interface *intf, const struct usb_device_id *id)
+{
+	const struct bcm4323x_chip *chip = (const void *)id->driver_info;
+	struct ieee80211_hw *hw;
+	struct bcm4323x *dev;
+	const char *stage;
+	const struct firmware *firmware = NULL;
+	int ret;
+
+	hw = ieee80211_alloc_hw(sizeof(*dev), &bcm4323x_ops);
+	if (!hw)
+		return -ENOMEM;
+	dev = hw->priv;
+	dev->hw = hw;
+	dev->chip = chip;
+	dev->udev = interface_to_usbdev(intf);
+	dev->intf = intf;
+	mutex_init(&dev->io_mutex);
+	init_completion(&dev->handshake_done);
+	bcm4323x_init_tx(dev);
+	usb_set_intfdata(intf, hw);
+
+	stage = "transport allocation";
+	ret = bcm4323x_alloc_transport(dev);
+	if (ret)
+		goto error;
+	stage = "firmware request";
+	ret = request_firmware(&firmware, BCM4323X_FW_NAME, &intf->dev);
+	if (ret)
+		goto error;
+	stage = "firmware activation";
+	ret = bcm4323x_fw_download(dev, firmware);
+	if (ret)
+		goto error;
+	stage = "runtime initialization";
+	ret = bcm4323x_init_firmware(dev);
+	if (ret)
+		goto error;
+
+	stage = "mac80211 registration";
+	ret = bcm4323x_register_hw(dev);
+	if (ret)
+		goto error;
+	bcm4323x_led_register(dev);
+
+	dev_info(&intf->dev, "BCM4323x RPC mac80211 device ready, MAC %pM\n", hw->wiphy->perm_addr);
+	release_firmware(firmware);
+	return 0;
+
+error:
+	release_firmware(firmware);
+	bcm4323x_free_transport(dev);
+	ieee80211_free_hw(hw);
+	return dev_err_probe(&intf->dev, ret, "%s failed\n", stage);
+}
+
+static void bcm4323x_disconnect(struct usb_interface *intf)
+{
+	struct ieee80211_hw *hw = usb_get_intfdata(intf);
+	struct bcm4323x *dev = hw->priv;
+
+	bcm4323x_led_unregister(dev);
+	ieee80211_unregister_hw(hw);
+	bcm4323x_free_transport(dev);
+	ieee80211_free_hw(hw);
+}
+
+static int bcm4323x_suspend(struct usb_interface *intf, pm_message_t message)
+{
+	return 0;
+}
+
+static int bcm4323x_resume(struct usb_interface *intf)
+{
+	struct ieee80211_hw *hw = usb_get_intfdata(intf);
+	struct bcm4323x *dev = hw->priv;
+	int ret;
+
+	if (READ_ONCE(dev->dead))
+		return 0;
+	ret = bcm4323x_rpc_call(dev, BCM4323X_RPC_PROC_WATCHDOG, NULL, 0, NULL, 0);
+	if (ret) {
+		dev_err(&intf->dev, "firmware lost during suspend: %d\n", ret);
+		WRITE_ONCE(dev->dead, true);
+		intf->needs_binding = 1;
+	}
+	return 0;
+}
+
+static const struct bcm4323x_chip bcm43231_chip = { };
+
+static const struct bcm4323x_chip wna3100_chip = {
+	.has_wps_button = true,
+};
+
+static const struct bcm4323x_chip bcm4323_chip = {
+	.has_5ghz = true,
+};
+
+static const struct bcm4323x_chip f9l1101_chip = {
+	.has_5ghz = true,
+	.channel_data_2ghz = { 0x58, 0x44, 0x3c },
+	.has_wps_led = true,
+	.has_wps_button = true,
+};
+
+static const struct usb_device_id bcm4323x_id_table[] = {
+	/* Belkin Double N+ Wireless USB Adapter */
+	{ USB_DEVICE(0x050d, 0x6050), .driver_info = (kernel_ulong_t)&bcm4323_chip },
+	/* Belkin F7D4101/F9L1101v1 */
+	{ USB_DEVICE(0x050d, 0x615a), .driver_info = (kernel_ulong_t)&f9l1101_chip },
+	/* Belkin N+ Wireless USB Adapter */
+	{ USB_DEVICE(0x050d, 0x825c), .driver_info = (kernel_ulong_t)&bcm4323_chip },
+	/* Dynex DX-NUSB */
+	{ USB_DEVICE(0x050d, 0xd321), .driver_info = (kernel_ulong_t)&bcm43231_chip },
+	/* Netgear WNDA3100v2 */
+	{ USB_DEVICE(0x0846, 0x9011), .driver_info = (kernel_ulong_t)&bcm4323_chip },
+	/* Netgear WNA3100 */
+	{ USB_DEVICE(0x0846, 0x9020), .driver_info = (kernel_ulong_t)&wna3100_chip },
+	{ }
+};
+MODULE_DEVICE_TABLE(usb, bcm4323x_id_table);
+
+static struct usb_driver bcm4323x_driver = {
+	.name = KBUILD_MODNAME,
+	.probe = bcm4323x_probe,
+	.disconnect = bcm4323x_disconnect,
+	.suspend = bcm4323x_suspend,
+	.resume = bcm4323x_resume,
+	.id_table = bcm4323x_id_table,
+	.soft_unbind = 1,
+};
+module_usb_driver(bcm4323x_driver);
+
+MODULE_AUTHOR("Ragul Balaji <ragulbaby@gmail.com>");
+MODULE_AUTHOR("Ho Jie Feng <hjf3108@gmail.com>");
+MODULE_DESCRIPTION("Broadcom BCM4323x USB mac80211 driver");
+MODULE_LICENSE("GPL");
+MODULE_FIRMWARE(BCM4323X_FW_NAME);
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [RFC PATCH wireless-next 13/17] wifi: bcm4323x: add the receive path
  2026-10-26  5:36 [RFC PATCH wireless-next 00/17] wifi: add a driver for Broadcom BCM4323x USB adapters Ragul Balaji
                   ` (11 preceding siblings ...)
  2026-10-26  5:36 ` [RFC PATCH wireless-next 12/17] wifi: bcm4323x: add the USB transport and firmware setup Ragul Balaji
@ 2026-10-26  5:36 ` Ragul Balaji
  2026-10-26  5:36 ` [RFC PATCH wireless-next 14/17] wifi: bcm4323x: add the transmit path Ragul Balaji
                   ` (3 subsequent siblings)
  16 siblings, 0 replies; 18+ messages in thread
From: Ragul Balaji @ 2026-10-26  5:36 UTC (permalink / raw)
  To: linux-wireless
  Cc: Arend van Spriel, Johannes Berg, brcm80211, Ragul Balaji,
	Ho Jie Feng, linux-kernel, brcm80211-dev-list.pdl

Add the RX path of the device. RX supports RX aggregation.

Signed-off-by: Ragul Balaji <ragulbaby@gmail.com>
Signed-off-by: Ho Jie Feng <hjf3108@gmail.com>
---
 .../broadcom/brcm80211/bcm4323x/bcm4323x_rx.c | 317 ++++++++++++++++++
 1 file changed, 317 insertions(+)
 create mode 100644 drivers/net/wireless/broadcom/brcm80211/bcm4323x/bcm4323x_rx.c

diff --git a/drivers/net/wireless/broadcom/brcm80211/bcm4323x/bcm4323x_rx.c b/drivers/net/wireless/broadcom/brcm80211/bcm4323x/bcm4323x_rx.c
new file mode 100644
index 000000000..8f5b3e01f
--- /dev/null
+++ b/drivers/net/wireless/broadcom/brcm80211/bcm4323x/bcm4323x_rx.c
@@ -0,0 +1,317 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include "bcm4323x.h"
+
+#define BCM4323X_RPC_PROC_RX 0x51
+#define BCM4323X_RPC_PROC_TX_STATUS 0x52
+#define BCM4323X_RPC_PROC_NOISE_RESULT 0x66
+
+struct bcm4323x_handshake_reply {
+	__le32 version;
+	__le32 state;
+	__le32 chip;
+} __packed;
+
+/* Procedure 0x51 */
+struct bcm4323x_rxhdr {
+	/* The length of the body up to the end of the frame, without its FCS */
+	__le32 length;
+	/* from brcmfmac: struct wlc_d11rxhdr */
+	struct d11rxhdr_le rxhdr;
+	__le32 tsf_l;
+	s8 rssi;
+	u8 reserved0[3];
+	s8 rxpwr0;
+	s8 rxpwr1;
+	u8 reserved1[4];
+} __packed;
+
+/* Procedure 0x66 */
+struct bcm4323x_noise {
+	__le32 channel;
+	__le32 dbm;
+} __packed;
+
+#define BCM4323X_RX_FRAME_OFFSET (sizeof(struct bcm4323x_rxhdr) + D11_PHY_HDR_LEN)
+
+/* Verify the receive frame key slot matches */
+static bool bcm4323x_rx_key_match(struct bcm4323x *dev, struct sk_buff *skb, unsigned int index)
+{
+	const struct ieee80211_hdr *hdr = (void *)skb->data;
+	bool multicast;
+	bool group_slot;
+	const u8 *frame_bssid;
+	struct ieee80211_vif *vif;
+	const u8 *bssid;
+	bool addressed;
+
+	if (ieee80211_get_hdrlen_from_skb(skb) < sizeof(struct ieee80211_hdr_3addr) ||
+	    !ieee80211_has_protected(hdr->frame_control) || index >= BCM4323X_HW_KEYS)
+		return false;
+	/* A group key takes the slot of its key ID, below the pairwise keys. */
+	multicast = is_multicast_ether_addr(hdr->addr1);
+	group_slot = index < BCM4323X_HW_PAIRWISE_BASE;
+	if (multicast != group_slot)
+		return false;
+
+	rcu_read_lock();
+	vif = rcu_dereference(dev->active_vif);
+	if (!vif) {
+		addressed = false;
+	} else if (multicast) {
+		/* An interface type change can repoint it. */
+		bssid = READ_ONCE(vif->bss_conf.bssid);
+		frame_bssid = ieee80211_get_bssid((void *)skb->data, skb->len, vif->type);
+		addressed = frame_bssid && bssid && ether_addr_equal(frame_bssid, bssid);
+	} else {
+		addressed = ether_addr_equal(hdr->addr1, vif->addr);
+	}
+	rcu_read_unlock();
+	if (!addressed)
+		return false;
+
+	/* bcm4323x_set_key() sets the peer before it puts the slot in use. */
+	if (!test_bit_acquire(index, dev->keys))
+		return false;
+	return multicast || ether_addr_equal(hdr->addr2, dev->key_peers[index]);
+}
+
+/* from brcmsmac: prep_mac80211_status */
+static void bcm4323x_set_rx_rate(struct bcm4323x *dev, struct ieee80211_rx_status *status,
+				 const u8 *body, unsigned int frame_offset)
+{
+	const struct bcm4323x_rxhdr *hdr = (const void *)body;
+	const struct ieee80211_supported_band *sband = &dev->bands[status->band];
+	struct d11rxhdr rxh = {
+		.PhyRxStatus_0 = le16_to_cpu(hdr->rxhdr.PhyRxStatus_0),
+		.RxChan = le16_to_cpu(hdr->rxhdr.RxChan),
+	};
+	u8 *plcp = (u8 *)body + frame_offset - D11_PHY_HDR_LEN;
+	unsigned int i;
+	u32 rspec;
+
+	if ((rxh.PhyRxStatus_0 & PRXS0_FT_MASK) == PRXS0_PREN) {
+		if (plcp[0] || plcp[1] || plcp[2])
+			memcpy(dev->rx_ht_plcp, plcp, sizeof(dev->rx_ht_plcp));
+		plcp = dev->rx_ht_plcp;
+	}
+	rspec = brcmu_compute_rspec(&rxh, plcp);
+	if (is_mcs_rate(rspec)) {
+		status->encoding = RX_ENC_HT;
+		status->rate_idx = rspec & RSPEC_RATE_MASK;
+		if (status->rate_idx >= BCM4323X_NUM_MCS)
+			status->rate_idx = 0;
+		if (rspec_is40mhz(rspec))
+			status->bw = RATE_INFO_BW_40;
+		if (rspec_issgi(rspec))
+			status->enc_flags |= RX_ENC_FLAG_SHORT_GI;
+		/* The HT-SIG byte has the STBC field where plcp3_issgi() finds SGI. */
+		status->enc_flags |= rspec_stc(plcp[3] << 16) << RX_ENC_FLAG_STBC_SHIFT;
+		return;
+	}
+	for (i = 0; i < sband->n_bitrates; i++)
+		if (sband->bitrates[i].hw_value == rspec2rate(rspec)) {
+			status->rate_idx = i;
+			break;
+		}
+	if (is_cck_rate(rspec) && (rxh.PhyRxStatus_0 & PRXS0_SHORTH))
+		status->enc_flags |= RX_ENC_FLAG_SHORTPRE;
+}
+
+static unsigned int bcm4323x_rx_frame_offset(const struct bcm4323x_rxhdr *hdr)
+{
+	if (le16_to_cpu(hdr->rxhdr.RxStatus1) & RXS_PBPRES)
+		return BCM4323X_RX_FRAME_OFFSET + sizeof(__le16);
+	return BCM4323X_RX_FRAME_OFFSET;
+}
+
+/* Whether a procedure 0x51 body holds a whole frame, on a channel of the device */
+static bool bcm4323x_rx_valid(struct bcm4323x *dev, const u8 *body, size_t body_length)
+{
+	const struct bcm4323x_rxhdr *hdr = (const void *)body;
+	unsigned int frame_size;
+	unsigned int channel;
+
+	if (body_length < BCM4323X_RX_FRAME_OFFSET + sizeof(__le32))
+		return false;
+	frame_size = le32_to_cpu(hdr->length);
+	if (ALIGN(frame_size + sizeof(__le32), sizeof(__le32)) > body_length ||
+	    frame_size <= bcm4323x_rx_frame_offset(hdr))
+		return false;
+	channel = le16_get_bits(hdr->rxhdr.RxChan, RXS_CHAN_ID_MASK);
+	if (le16_to_cpu(hdr->rxhdr.RxChan) & RXS_CHAN_5G)
+		return channel && dev->chip->has_5ghz && channel <= 196;
+	return channel && channel <= BCM4323X_NUM_2GHZ_CHANNELS;
+}
+
+/*
+ * from brcmsmac: wlc_phy_chanspec_set, which gives the microcode the center of
+ * a 40 MHz channel, while mac80211 expects the control channel
+ *
+ * Returns 0 for a frame received before a switch.
+ */
+static unsigned int bcm4323x_rx_channel(struct bcm4323x *dev, const struct d11rxhdr_le *rxh)
+{
+	unsigned int channel = le16_get_bits(rxh->RxChan, RXS_CHAN_ID_MASK);
+	u16 chanspec;
+
+	if (!(le16_to_cpu(rxh->RxChan) & RXS_CHAN_40))
+		return channel;
+	chanspec = READ_ONCE(dev->chanspec);
+	if (!CHSPEC_IS40(chanspec) || CHSPEC_CHANNEL(chanspec) != channel)
+		return 0;
+	return CHSPEC_CTL_CHAN(chanspec);
+}
+
+/* from brcmsmac: brcms_c_recv, prep_mac80211_status */
+static void bcm4323x_rx_80211(struct bcm4323x *dev, const u8 *body, size_t body_length)
+{
+	const struct bcm4323x_rxhdr *hdr = (const void *)body;
+	const struct d11rxhdr_le *rxh = &hdr->rxhdr;
+	unsigned int frame_offset;
+	unsigned int frame_length;
+	unsigned int rx_status1;
+	unsigned int channel;
+	struct sk_buff *skb;
+	struct ieee80211_rx_status *status;
+
+	if (!READ_ONCE(dev->running))
+		return;
+	if (!bcm4323x_rx_valid(dev, body, body_length)) {
+		dev_dbg_ratelimited(&dev->intf->dev, "malformed procedure 0x51 body (%zu bytes)\n",
+				    body_length);
+		return;
+	}
+	channel = bcm4323x_rx_channel(dev, rxh);
+	if (!channel)
+		return;
+
+	frame_offset = bcm4323x_rx_frame_offset(hdr);
+	frame_length = le32_to_cpu(hdr->length) - frame_offset;
+	/* Aligns the IP header of QoS data, which mac80211 would otherwise move. */
+	skb = dev_alloc_skb(frame_length + 2);
+	if (!skb)
+		return;
+	skb_reserve(skb, 2);
+	skb_put_data(skb, body + frame_offset, frame_length);
+
+	status = IEEE80211_SKB_RXCB(skb);
+	status->band = le16_to_cpu(rxh->RxChan) & RXS_CHAN_5G ? NL80211_BAND_5GHZ :
+								NL80211_BAND_2GHZ;
+	status->freq = ieee80211_channel_to_frequency(channel, status->band);
+	bcm4323x_set_rx_rate(dev, status, body, frame_offset);
+	/* PHY status is valid only on the last subframe of an A-MPDU. */
+	if ((le16_to_cpu(rxh->RxStatus2) & RXS_PHYRXST_VALID) && hdr->rssi < 0) {
+		status->signal = hdr->rssi;
+		status->chains = BCM4323X_ANTENNAS;
+		status->chain_signal[0] = hdr->rxpwr0;
+		status->chain_signal[1] = hdr->rxpwr1;
+	} else {
+		status->flag |= RX_FLAG_NO_SIGNAL_VAL;
+	}
+	rx_status1 = le16_to_cpu(rxh->RxStatus1);
+	if (rx_status1 & RXS_FCSERR)
+		status->flag |= RX_FLAG_FAILED_FCS_CRC;
+
+	/* Only accept hw decrypted frame if it isn't corrupted */
+	if ((rx_status1 & RXS_DECATMPT) && !(rx_status1 & RXS_DECERR) &&
+	    bcm4323x_rx_key_match(dev, skb, FIELD_GET(RXS_SECKINDX_MASK, rx_status1)))
+		status->flag |= RX_FLAG_DECRYPTED;
+	ieee80211_rx_irqsafe(dev->hw, skb);
+}
+
+static void bcm4323x_handle_handshake_reply(struct bcm4323x *dev, const u8 *body,
+					    size_t body_length)
+{
+	const struct bcm4323x_handshake_reply *reply = (const void *)body;
+
+	dev->handshake_chip = 0;
+	if (body_length == sizeof(*reply) && le32_to_cpu(reply->version) == BCM4323X_RPC_VERSION)
+		dev->handshake_chip = reply->chip;
+	complete(&dev->handshake_done);
+}
+
+static void bcm4323x_rx_noise(struct bcm4323x *dev, const u8 *body, size_t body_length)
+{
+	const struct bcm4323x_noise *noise = (const void *)body;
+	u32 channel;
+	s32 dbm;
+	unsigned int i;
+
+	if (body_length < sizeof(*noise))
+		return;
+	/* Channels above 0x7F are presented as sign-extended */
+	channel = le32_to_cpu(noise->channel) & 0xff;
+	dbm = le32_to_cpu(noise->dbm);
+	if (dbm >= 0 || dbm < S8_MIN)
+		return;
+	for (i = 0; i < BCM4323X_NUM_CHANNELS; i++)
+		if (dev->channels[i].hw_value == channel) {
+			WRITE_ONCE(dev->noise[i], dbm);
+			break;
+		}
+}
+
+static void bcm4323x_handle_runtime_frame(struct bcm4323x *dev, const u8 *frame, size_t length)
+{
+	const struct bcm4323x_rpc_header *rpc = (const void *)frame;
+	u8 rpc_class = FIELD_GET(BCM4323X_RPC_CLASS_MASK, le32_to_cpu(rpc->xid_class));
+	u32 procedure = le32_to_cpu(rpc->procedure);
+	const u8 *body = frame + sizeof(*rpc);
+	size_t body_length = length - sizeof(*rpc);
+
+	if (rpc_class == BCM4323X_RPC_CLASS_TRANSPORT) {
+		if (procedure == BCM4323X_RPC_PROC_VERSION ||
+		    procedure == BCM4323X_RPC_PROC_CHIP_ACK)
+			bcm4323x_handle_handshake_reply(dev, body, body_length);
+	} else if (rpc_class == BCM4323X_RPC_CLASS_DATA) {
+		switch (procedure) {
+		case BCM4323X_RPC_PROC_RX:
+			bcm4323x_rx_80211(dev, body, body_length);
+			break;
+		case BCM4323X_RPC_PROC_TX_STATUS:
+			bcm4323x_tx_status(dev, body, body_length);
+			break;
+		case BCM4323X_RPC_PROC_NOISE_RESULT:
+			bcm4323x_rx_noise(dev, body, body_length);
+			break;
+		}
+	}
+}
+
+void bcm4323x_rx_complete(struct urb *urb)
+{
+	struct bcm4323x *dev = urb->context;
+	u8 *cursor = urb->transfer_buffer;
+	size_t remaining = urb->actual_length;
+	int ret = 0;
+
+	/* Errors on explicit stops don't need an error print */
+	if (urb->status == -ENOENT || urb->status == -ECONNRESET || urb->status == -ESHUTDOWN)
+		return;
+	if (urb->status) {
+		dev_err_ratelimited(&dev->intf->dev, "RX URB failed: %d\n", urb->status);
+		bcm4323x_fatal_error(dev);
+		return;
+	}
+
+	/* RPC frames don't split across transfers */
+	while (remaining) {
+		const struct bcm4323x_rpc_header *rpc = (const void *)cursor;
+		u32 length = remaining < sizeof(*rpc) ? 0 :
+			     le32_to_cpu(rpc->length) + sizeof(rpc->length);
+
+		if (length < sizeof(*rpc) || length > remaining) {
+			dev_warn_ratelimited(&dev->intf->dev, "invalid RX RPC length %u of %zu\n",
+					     length, remaining);
+			break;
+		}
+		bcm4323x_handle_runtime_frame(dev, cursor, length);
+		cursor += length;
+		remaining -= length;
+	}
+	if (!READ_ONCE(dev->dead))
+		ret = usb_submit_urb(urb, GFP_ATOMIC);
+	if (ret && ret != -EPERM)
+		dev_warn_ratelimited(&dev->intf->dev, "RX URB left idle: %d\n", ret);
+}
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [RFC PATCH wireless-next 14/17] wifi: bcm4323x: add the transmit path
  2026-10-26  5:36 [RFC PATCH wireless-next 00/17] wifi: add a driver for Broadcom BCM4323x USB adapters Ragul Balaji
                   ` (12 preceding siblings ...)
  2026-10-26  5:36 ` [RFC PATCH wireless-next 13/17] wifi: bcm4323x: add the receive path Ragul Balaji
@ 2026-10-26  5:36 ` Ragul Balaji
  2026-10-26  5:36 ` [RFC PATCH wireless-next 15/17] wifi: bcm4323x: add the mac80211 operations Ragul Balaji
                   ` (2 subsequent siblings)
  16 siblings, 0 replies; 18+ messages in thread
From: Ragul Balaji @ 2026-10-26  5:36 UTC (permalink / raw)
  To: linux-wireless
  Cc: Arend van Spriel, Johannes Berg, brcm80211, Ragul Balaji,
	Ho Jie Feng, linux-kernel, brcm80211-dev-list.pdl

Adds the TX part of the device. Supports TX aggregation.

Signed-off-by: Ragul Balaji <ragulbaby@gmail.com>
Signed-off-by: Ho Jie Feng <hjf3108@gmail.com>
---
 .../broadcom/brcm80211/bcm4323x/bcm4323x_tx.c | 1386 +++++++++++++++++
 1 file changed, 1386 insertions(+)
 create mode 100644 drivers/net/wireless/broadcom/brcm80211/bcm4323x/bcm4323x_tx.c

diff --git a/drivers/net/wireless/broadcom/brcm80211/bcm4323x/bcm4323x_tx.c b/drivers/net/wireless/broadcom/brcm80211/bcm4323x/bcm4323x_tx.c
new file mode 100644
index 000000000..3b3bc8ee3
--- /dev/null
+++ b/drivers/net/wireless/broadcom/brcm80211/bcm4323x/bcm4323x_tx.c
@@ -0,0 +1,1386 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/bitfield.h>
+#include <linux/jiffies.h>
+#include <linux/kfifo.h>
+#include <linux/netdevice.h>
+#include <linux/slab.h>
+
+#include "bcm4323x.h"
+
+#define BCM4323X_RPC_PROC_TX 0x3d
+
+#define BCM4323X_TX_BATCH_FRAMES 16
+#define BCM4323X_TX_BATCH_BYTES 26624
+#define BCM4323X_AMPDU_PRIMARY_ATTEMPTS 3
+#define BCM4323X_TX_TIMEOUT (2 * HZ)
+
+#define BCM4323X_TX_WAIT (BCM4323X_TX_TIMEOUT + 2 * HZ)
+#define BCM4323X_TX_DRAIN_MS 1000
+
+/* SIFS plus the acknowledgment */
+/* 24 Mbit/s OFDM */
+#define BCM4323X_OFDM_ACK_DURATION_US 44
+/* 1 Mbit/s CCK */
+#define BCM4323X_CCK_ACK_DURATION_US 314
+/* 6 Mbit/s OFDM on 5 GHz */
+#define BCM4323X_OFDM6_ACK_DURATION_US 60
+#define BCM4323X_MAX_DURATION_US 32767
+
+/* from brcmsmac */
+#define	AMPDU_MAX_DUR			5
+#define AMPDU_DEF_RETRY_LIMIT		5
+#define AMPDU_DELIMITER_LEN	4
+
+#define BCM4323X_TX_META_KEY BIT(0)
+#define BCM4323X_TX_META_AMPDU BIT(1)
+
+/* From driver captures */
+#define BCM4323X_TX_ARG2 0xffff
+#define BCM4323X_TX_WEIGHT 1
+#define BCM4323X_TX_WEIGHT_AMPDU 2
+#define BCM4323X_TX_FLAGS_MGMT 0
+#define BCM4323X_TX_FLAGS_DATA 1
+
+/* from brcmsmac: struct d11txh and PLCP header */
+struct bcm4323x_txh {
+	struct d11txh d11;
+	u8 plcp[D11_PHY_HDR_LEN];
+} __packed __aligned(2);
+
+static_assert(sizeof(struct bcm4323x_txh) == D11_TXH_LEN + D11_PHY_HDR_LEN);
+
+/* Procedure 0x3d */
+struct bcm4323x_tx_call {
+	__le32 fifo;
+	/* Set once per aggregate, on its last MPDU, and on unaggregated frames */
+	__le32 commit;
+	__le32 arg2;
+	/* Firmware credit charged on commit */
+	__le32 weight;
+	__le32 flags;
+	__le32 arg5;
+	/* Length of the TX header and the frame */
+	__le32 packet_length;
+	struct bcm4323x_txh txh;
+	u8 frame[];
+} __packed __aligned(2);
+
+struct bcm4323x_tx_meta {
+	u8 flags;
+	u16 frame_id;
+	u8 ampdu_retries;
+	/* Set when the aggregate is built */
+	u8 null_delimiters;
+	u8 key_index;
+	u8 iv_length;
+	u8 icv_length;
+};
+
+static struct bcm4323x_tx_meta *bcm4323x_tx_meta(struct sk_buff *skb)
+{
+	struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
+
+	BUILD_BUG_ON(sizeof(struct bcm4323x_tx_meta) > IEEE80211_TX_INFO_RATE_DRIVER_DATA_SIZE);
+	return (void *)info->rate_driver_data;
+}
+
+static struct bcm4323x_txq *to_bcm4323x_txq(struct ieee80211_txq *txq)
+{
+	return (void *)txq->drv_priv;
+}
+
+/*
+ * Moves an aggregated MPDU to the fallback rate after the primary attempts that
+ * rate control asks for, at most BCM4323X_AMPDU_PRIMARY_ATTEMPTS, as multi-rate
+ * retry hardware would.
+ */
+static void bcm4323x_tx_fallback(struct sk_buff *skb)
+{
+	struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
+	struct ieee80211_tx_rate *rates = info->control.rates;
+
+	if (bcm4323x_tx_meta(skb)->ampdu_retries !=
+	    clamp_t(u8, rates[0].count, 1, BCM4323X_AMPDU_PRIMARY_ATTEMPTS) ||
+	    !(rates[1].flags & IEEE80211_TX_RC_MCS) || rates[1].idx < 0 || !rates[1].count)
+		return;
+	/* Protection stays as rate control chose it for the primary rate. */
+	rates[0].idx = rates[1].idx;
+	rates[0].flags ^= (rates[0].flags ^ rates[1].flags) & IEEE80211_TX_RC_SHORT_GI;
+	/* The remaining attempts no longer sample the primary rate. */
+	info->flags &= ~IEEE80211_TX_CTL_RATE_CTRL_PROBE;
+}
+
+static void bcm4323x_schedule_tx_work(struct bcm4323x *dev)
+{
+	if (bcm4323x_active(dev))
+		queue_work(system_long_wq, &dev->tx_work);
+}
+
+/*
+ * mac80211 counts a lost aggregated MPDU only when its status carries an
+ * aggregate transmission, in @ampdu.
+ */
+static void bcm4323x_report_tx(struct bcm4323x *dev, struct sk_buff *skb, bool transmitted,
+			       bool acked, const struct ieee80211_tx_info *ampdu)
+{
+	struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
+	struct bcm4323x_tx_meta *metadata = bcm4323x_tx_meta(skb);
+	u8 attempts = metadata->ampdu_retries + 1;
+	bool no_ack = info->flags & IEEE80211_TX_CTL_NO_ACK;
+
+	/*
+	 * Clear the driver data and report only the last rate, as rate control
+	 * ignores aggregated MPDUs without an aggregate.
+	 */
+	ieee80211_tx_info_clear_status(info);
+	if (ampdu) {
+		info->flags |= IEEE80211_TX_STAT_AMPDU;
+		info->status = ampdu->status;
+	} else {
+		info->status.rates[0].count = attempts;
+		info->status.rates[1].idx = -1;
+	}
+	/*
+	 * Let mac80211 advance the peer's BA window after a lost MPDU, also one
+	 * sent unaggregated, as at a CCK rate.
+	 */
+	if ((info->flags & IEEE80211_TX_CTL_AMPDU) && transmitted && !acked)
+		info->flags |= IEEE80211_TX_STAT_AMPDU_NO_BACK;
+	if (acked)
+		info->flags |= IEEE80211_TX_STAT_ACK;
+	else if (transmitted && no_ack)
+		info->flags |= IEEE80211_TX_STAT_NOACK_TRANSMITTED;
+	ieee80211_tx_status_ni(dev->hw, skb);
+}
+
+/*
+ * A-MSDUs at CCK rates went unacknowledged at 1 Mbit/s, and at 5.5 Mbit/s were
+ * lost though acknowledged, so they use their MCS fallback.
+ */
+static void bcm4323x_amsdu_rate(struct sk_buff *skb)
+{
+	struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
+	struct ieee80211_tx_rate *rates = info->control.rates;
+	struct ieee80211_hdr *hdr = (void *)skb->data;
+
+	if (!(rates[0].flags & IEEE80211_TX_RC_MCS) && (rates[1].flags & IEEE80211_TX_RC_MCS) &&
+	    rates[1].idx >= 0 && ieee80211_is_data_qos(hdr->frame_control) &&
+	    (*ieee80211_get_qos_ctl(hdr) & IEEE80211_QOS_CTL_A_MSDU_PRESENT)) {
+		rates[0].idx = rates[1].idx;
+		rates[0].flags = rates[1].flags;
+		info->flags &= ~IEEE80211_TX_CTL_RATE_CTRL_PROBE;
+	}
+}
+
+/* Returns false, after dropping the frame, if its key was removed. */
+static bool bcm4323x_prepare_tx(struct bcm4323x *dev, struct sk_buff *skb)
+{
+	struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
+	struct ieee80211_key_conf *key = info->control.hw_key;
+	struct bcm4323x_tx_meta metadata = {};
+
+	bcm4323x_amsdu_rate(skb);
+	/*
+	 * mac80211 marks the frames of an operational aggregation session as it
+	 * dequeues them, and unacknowledged MPDUs return to the TX queue.
+	 */
+	if ((info->flags & IEEE80211_TX_CTL_AMPDU) &&
+	    (info->control.rates[0].flags & IEEE80211_TX_RC_MCS))
+		metadata.flags |= BCM4323X_TX_META_AMPDU;
+
+	if (key) {
+		if (!test_bit(key->hw_key_idx, dev->keys)) {
+			ieee80211_free_txskb(dev->hw, skb);
+			return false;
+		}
+		metadata.flags |= BCM4323X_TX_META_KEY;
+		metadata.key_index = key->hw_key_idx;
+		metadata.iv_length = key->iv_len;
+		metadata.icv_length = key->icv_len;
+	}
+	/* The metadata overlays control.vif and control.hw_key. */
+	memcpy(bcm4323x_tx_meta(skb), &metadata, sizeof(metadata));
+	return true;
+}
+
+/*
+ * Once the device stops, the frames are dropped, as mac80211 cancelled the work
+ * that a failed management frame would schedule.
+ */
+static void bcm4323x_fail_batch(struct bcm4323x *dev, struct sk_buff_head *batch)
+{
+	struct sk_buff *skb;
+
+	while ((skb = __skb_dequeue(batch))) {
+		if (READ_ONCE(dev->running))
+			bcm4323x_report_tx(dev, skb, false, false, NULL);
+		else
+			ieee80211_free_txskb(dev->hw, skb);
+	}
+}
+
+/* Fails the frames that firmware owns. */
+static void bcm4323x_fail_pending(struct bcm4323x *dev)
+{
+	unsigned int i;
+
+	usb_free_urb(dev->tx_commit);
+	dev->tx_commit = NULL;
+	for (i = 0; i < ARRAY_SIZE(dev->tx_batches); i++)
+		bcm4323x_fail_batch(dev, &dev->tx_batches[i]);
+	WRITE_ONCE(dev->tx_owned, 0);
+}
+
+/* The status arrives with the receive stream, and the TX work processes it. */
+void bcm4323x_tx_status(struct bcm4323x *dev, const u8 *body, size_t body_length)
+{
+	if (body_length < sizeof(struct bcm4323x_tx_status))
+		return;
+	if (!kfifo_put(&dev->tx_statuses, *(const struct bcm4323x_tx_status *)body))
+		dev_warn_ratelimited(&dev->intf->dev, "TX status dropped\n");
+	bcm4323x_schedule_tx_work(dev);
+}
+
+/*
+ * Moves the committed batch holding a frame to @batch, as batches in different
+ * FIFOs complete in either order.
+ */
+static bool bcm4323x_take_committed(struct bcm4323x *dev, u16 frame_id,
+				    struct sk_buff_head *batch)
+{
+	struct sk_buff_head *committed = dev->tx_batches;
+	struct sk_buff *skb;
+	unsigned int i;
+
+	for (i = 0; i < BCM4323X_TX_STAGED; i++)
+		skb_queue_walk(&committed[i], skb)
+			if (bcm4323x_tx_meta(skb)->frame_id == frame_id)
+				goto found;
+	return false;
+
+found:
+	skb_queue_splice_init(&committed[i], batch);
+	/* A batch still committed becomes the older one. */
+	skb_queue_splice_init(&committed[1], &committed[0]);
+	WRITE_ONCE(dev->tx_owned, dev->tx_owned - skb_queue_len(batch));
+	if (!skb_queue_empty(&committed[0]))
+		dev->tx_expires = jiffies + BCM4323X_TX_TIMEOUT;
+	return true;
+}
+
+/* from brcmsmac: brcms_c_ampdu_dotxstatus_complete, the block acknowledgment bitmap */
+static u64 bcm4323x_ba_bitmap(const struct bcm4323x_tx_status *txs, u16 status)
+{
+	u32 s1 = le32_to_cpu(txs->s1);
+
+	if (!(status & TX_STATUS_ACK_RCV) || !(status & TX_STATUS_AMPDU))
+		return 0;
+	return FIELD_GET(TX_STATUS_BA_BMAP03_MASK, status) |
+	       (s1 & TX_STATUS_BA_BMAP47_MASK) << TX_STATUS_BA_BMAP47_SHIFT |
+	       (s1 & GENMASK(31, 8)) | (u64)le32_to_cpu(txs->s2) << 32;
+}
+
+/* What rate control learns of an aggregate, whose MPDUs share its rate */
+static void bcm4323x_ampdu_rate_info(struct ieee80211_tx_info *rate_info,
+				     const struct ieee80211_tx_info *info)
+{
+	rate_info->band = info->band;
+	rate_info->flags = IEEE80211_TX_STAT_AMPDU |
+			   (info->flags & IEEE80211_TX_CTL_RATE_CTRL_PROBE);
+	rate_info->status.rates[0] = info->control.rates[0];
+	rate_info->status.rates[0].count = 1;
+	rate_info->status.rates[1].idx = -1;
+}
+
+/*
+ * from brcmsmac: brcms_c_ampdu_dotxstatus_complete
+ *
+ * One status completes a committed batch, returning unacknowledged MPDUs to
+ * their TX queue ahead of newer frames, and tells rate control of the aggregate
+ * transmission.
+ */
+static void bcm4323x_complete_tx(struct bcm4323x *dev, const struct bcm4323x_tx_status *txs)
+{
+	u16 status = le16_to_cpu(txs->status);
+	u16 start_seq = IEEE80211_SEQ_TO_SN(le16_to_cpu(txs->sequence));
+	bool suppressed = status & TX_STATUS_SUPR_MASK;
+	struct sk_buff_head batch;
+	struct sk_buff_head retries;
+	struct sk_buff *skb;
+	struct sk_buff *lost = NULL;
+	struct bcm4323x_tx_meta *metadata;
+	struct ieee80211_hdr *hdr;
+	u64 bitmap;
+	struct ieee80211_tx_info rate_info = {};
+	struct ieee80211_sta *sta;
+	struct ieee80211_txq *txq = NULL;
+
+	__skb_queue_head_init(&batch);
+	if (!bcm4323x_take_committed(dev, le16_to_cpu(txs->frameid), &batch))
+		return;
+	skb = skb_peek(&batch);
+	if (!(bcm4323x_tx_meta(skb)->flags & BCM4323X_TX_META_AMPDU)) {
+		/* Only aggregates have more than one frame. */
+		bcm4323x_report_tx(dev, __skb_dequeue(&batch), !suppressed,
+				   status & TX_STATUS_ACK_RCV, NULL);
+		return;
+	}
+	bitmap = bcm4323x_ba_bitmap(txs, status);
+
+	/* All MPDUs of an aggregate share its rate, receiver and TID. */
+	bcm4323x_ampdu_rate_info(&rate_info, IEEE80211_SKB_CB(skb));
+	hdr = (void *)skb->data;
+	/* A station that is no longer found does not get its TX queues back. */
+	rcu_read_lock();
+	sta = ieee80211_find_sta_by_ifaddr(dev->hw, hdr->addr1, NULL);
+	if (sta && !dev->tx_paused && !suppressed)
+		txq = sta->txq[ieee80211_get_tid(hdr)];
+
+	__skb_queue_head_init(&retries);
+	while ((skb = __skb_dequeue(&batch))) {
+		u16 bindex;
+		bool acked;
+
+		hdr = (void *)skb->data;
+		metadata = bcm4323x_tx_meta(skb);
+		bindex = ieee80211_sn_sub(ieee80211_get_sn(hdr), start_seq);
+		acked = bindex < IEEE80211_MAX_AMPDU_BUF_HT && (bitmap & BIT_ULL(bindex));
+		rate_info.status.ampdu_len++;
+		rate_info.status.ampdu_ack_len += acked;
+		if (acked) {
+			bcm4323x_report_tx(dev, skb, true, true, NULL);
+		} else if (txq && metadata->ampdu_retries < AMPDU_DEF_RETRY_LIMIT) {
+			metadata->ampdu_retries++;
+			bcm4323x_tx_fallback(skb);
+			hdr->frame_control |= cpu_to_le16(IEEE80211_FCTL_RETRY);
+			__skb_queue_tail(&retries, skb);
+		} else {
+			/*
+			 * Only the last MPDU given up is reported as
+			 * transmitted, so that a single block-ack request moves
+			 * the recipient's window past all of them.
+			 */
+			if (lost)
+				bcm4323x_report_tx(dev, lost, false, false, NULL);
+			lost = skb;
+		}
+	}
+	if (!skb_queue_empty(&retries)) {
+		skb_queue_splice(&retries, &to_bcm4323x_txq(txq)->frames);
+		ieee80211_schedule_txq(dev->hw, txq);
+	}
+
+	if (lost)
+		bcm4323x_report_tx(dev, lost, true, false, suppressed ? NULL : &rate_info);
+	else if (sta && !suppressed)
+		ieee80211_tx_rate_update(dev->hw, sta, &rate_info);
+	rcu_read_unlock();
+}
+
+/* Mapping of ieee80211 AC numbers to tx fifos */
+/* from brcmsmac: ac_to_fifo_mapping */
+static const u8 ac_to_fifo_mapping[IEEE80211_NUM_ACS] = {
+	[IEEE80211_AC_VO]	= TX_AC_VO_FIFO,
+	[IEEE80211_AC_VI]	= TX_AC_VI_FIFO,
+	[IEEE80211_AC_BE]	= TX_AC_BE_FIFO,
+	[IEEE80211_AC_BK]	= TX_AC_BK_FIFO,
+};
+
+/* from brcmsmac: brcms_ac_to_fifo */
+u8 bcm4323x_ac_to_fifo(u8 ac)
+{
+	if (ac >= ARRAY_SIZE(ac_to_fifo_mapping))
+		return TX_AC_BE_FIFO;
+	return ac_to_fifo_mapping[ac];
+}
+
+/* from brcmsmac: frametype */
+static u16 bcm4323x_frametype(u32 rspec, u8 mimoframe)
+{
+	if (is_mcs_rate(rspec))
+		return mimoframe;
+	return is_cck_rate(rspec) ? FT_CCK : FT_OFDM;
+}
+
+/* from: brcmsmac */
+static int bcm4323x_bandtype(enum nl80211_band band)
+{
+	return band == NL80211_BAND_5GHZ ? BRCM_BAND_5G : BRCM_BAND_2G;
+}
+
+/* Fills brcmsmac's basic_rate[] table of a band, for the basic rates of a BSS. */
+void bcm4323x_init_basic_rate(u8 *basic_rate, enum nl80211_band band, u32 basic_rates)
+{
+	unsigned int first = bcm4323x_first_rate(band);
+	struct brcms_c_rateset hw_rateset = {};
+	struct brcms_c_rateset rateset = {};
+	unsigned int i;
+
+	for (i = first; i < BCM4323X_NUM_RATES; i++) {
+		u8 rate = bcm4323x_rates[i].hw_value;
+
+		hw_rateset.rates[hw_rateset.count++] = rate;
+		rateset.rates[rateset.count++] = rate | (basic_rates & BIT(i - first) ?
+							 BRCMS_RATE_FLAG : 0);
+	}
+	brcmu_rate_lookup_init(basic_rate, &hw_rateset, &rateset);
+}
+
+static unsigned int bcm4323x_tx_kbps(struct sk_buff *skb)
+{
+	const struct ieee80211_tx_rate *rate = &IEEE80211_SKB_CB(skb)->control.rates[0];
+
+	return mcs_2_rate(rate->idx, rate->flags & IEEE80211_TX_RC_40_MHZ_WIDTH,
+			  rate->flags & IEEE80211_TX_RC_SHORT_GI);
+}
+
+/* from brcmsmac: brcms_c_d11hdrs_mac80211 */
+static u16 bcm4323x_ctlchbw(u16 chanspec)
+{
+	return CHSPEC_SB_UPPER(chanspec) ? PHY_TXC1_BW_20MHZ_UP : PHY_TXC1_BW_20MHZ;
+}
+
+/* from brcmsmac: brcms_c_stf_ss_update */
+u8 bcm4323x_stf_mode(u16 ss_algo_channel)
+{
+	return isset(&ss_algo_channel, PHY_TXC1_MODE_SISO) ? PHY_TXC1_MODE_SISO :
+							      PHY_TXC1_MODE_CDD;
+}
+
+/*
+ * Completes a ratespec as brcms_c_d11hdrs_mac80211() does: MCS frames to 40 MHz
+ * peers (@ht40) take the whole of a 40 MHz channel and other frames its control
+ * sideband, with the single-stream mode for OFDM, and STBC or that mode for
+ * single-stream MCS.
+ */
+static u32 bcm4323x_rspec(u32 rspec, u8 stf_mode, bool stbc, u16 chanspec, bool ht40)
+{
+	u16 bw = bcm4323x_ctlchbw(chanspec);
+
+	if (is_mcs_rate(rspec) && ht40 && CHSPEC_IS40(chanspec))
+		bw = PHY_TXC1_BW_40MHZ;
+	if (is_cck_rate(rspec)) {
+		stf_mode = PHY_TXC1_MODE_SISO;
+	} else if (is_mcs_rate(rspec) && !is_single_stream(rspec & RSPEC_RATE_MASK)) {
+		stf_mode = PHY_TXC1_MODE_SDM;
+	} else if (is_mcs_rate(rspec) && stbc) {
+		stf_mode = PHY_TXC1_MODE_STBC;
+		/* Nss for single stream is always 1 */
+		rspec |= 1 << RSPEC_STC_SHIFT;
+	}
+	return rspec | (bw << RSPEC_BW_SHIFT) | (stf_mode << RSPEC_STF_SHIFT);
+}
+
+static unsigned int bcm4323x_tx_phy_length(struct sk_buff *skb)
+{
+	/* from b43: b43_generate_txhdr, the hardware appends the ICV */
+	return skb->len + FCS_LEN + bcm4323x_tx_meta(skb)->icv_length;
+}
+
+/* What protection needs of the BSS, read as each frame is built */
+struct bcm4323x_protection {
+	/* brcmsmac's band type, and basic_rate[] table for the basic rates of the BSS */
+	int bandtype;
+	u8 basic_rate[BRCM_MAXRATE + 1];
+	bool short_preamble;
+	/* the RTS or CTS-to-self of a frame or aggregate, both short if any */
+	bool cts_only;
+	u32 rts_rspec[2];
+};
+
+/*
+ * from brcmsmac: brcms_c_d11hdrs_mac80211
+ *
+ * The RTS or CTS-to-self of a frame, or of an aggregate of @frame_len bytes
+ * (@ampdu), at the rates of prot->rts_rspec
+ */
+static void bcm4323x_build_rts(struct d11txh *txh, const struct ieee80211_hdr *h,
+			       const struct bcm4323x_protection *prot, const u32 *rspec,
+			       const u8 *preamble_type, uint frame_len, bool ampdu)
+{
+	const u32 *rts_rspec = prot->rts_rspec;
+	struct ieee80211_rts *rts = &txh->rts_frame;
+	u16 mcl = le16_to_cpu(txh->MacTxControlLow);
+	u16 mch = le16_to_cpu(txh->MacTxControlHigh);
+	u16 xfts = le16_to_cpu(txh->XtraFrameTypes);
+	uint rts_phylen;
+	u16 durid[2];
+	unsigned int k;
+
+	/* RTS/CTS additions to MacTxControlLow */
+	if (prot->cts_only)
+		mcl |= TXC_SENDCTS;
+	else
+		mcl |= TXC_SENDRTS | TXC_LONGFRAME;
+
+	/* RTS PLCP header */
+	rts_phylen = (prot->cts_only ? sizeof(struct ieee80211_cts) : sizeof(*rts)) + FCS_LEN;
+	brcmu_compute_plcp(rts_rspec[0], rts_phylen, txh->RTSPhyHeader);
+	/* fallback rate version of RTS PLCP header */
+	brcmu_compute_plcp(rts_rspec[1], rts_phylen, txh->RTSPLCPFallback);
+
+	/* RTS frame fields... */
+	for (k = 0; k < 2; k++) {
+		u8 rts_preamble_type = BRCMS_LONG_PREAMBLE;
+		uint dur;
+
+		/* Unlike brcmsmac, short only if the BSS allows it */
+		if (is_cck_rate(rts_rspec[k]) && prot->short_preamble &&
+		    rspec2rate(rts_rspec[k]) != BRCM_RATE_1M) {
+			rts_preamble_type = BRCMS_SHORT_PREAMBLE;
+			mch |= k ? TXC_PREAMBLE_RTS_FB_SHORT : TXC_PREAMBLE_RTS_MAIN_SHORT;
+		}
+		dur = brcmu_compute_rtscts_dur(prot->bandtype, prot->basic_rate, prot->cts_only,
+					       rts_rspec[k], rspec[k], rts_preamble_type,
+					       preamble_type[k], frame_len, ampdu);
+		durid[k] = min_t(uint, dur, BCM4323X_MAX_DURATION_US);
+	}
+	rts->duration = cpu_to_le16(durid[0]);
+	/* fallback rate version of RTS DUR field */
+	txh->RTSDurFallback = cpu_to_le16(durid[1]);
+	if (prot->cts_only) {
+		rts->frame_control = cpu_to_le16(IEEE80211_FTYPE_CTL | IEEE80211_STYPE_CTS);
+		ether_addr_copy(rts->ra, h->addr2);
+	} else {
+		rts->frame_control = cpu_to_le16(IEEE80211_FTYPE_CTL | IEEE80211_STYPE_RTS);
+		ether_addr_copy(rts->ra, h->addr1);
+		ether_addr_copy(rts->ta, h->addr2);
+	}
+	/*
+	 * Unlike brcmsmac, also without a TXOP limit, as the reference driver
+	 * does for aggregates
+	 */
+	if (ampdu) {
+		txh->TxFesTimeNormal = cpu_to_le16(durid[0]);
+		txh->TxFesTimeFallback = cpu_to_le16(durid[1]);
+	}
+
+	/*
+	 * mainrate
+	 *    low 8 bits: main frag rate/mcs,
+	 *    high 8 bits: rts/cts rate/mcs
+	 */
+	txh->MainRates |= cpu_to_le16((is_ofdm_rate(rts_rspec[0]) ?
+				       D11A_PHY_HDR_GRATE((struct ofdm_phy_hdr *)
+							  txh->RTSPhyHeader) :
+				       txh->RTSPhyHeader[0]) << 8);
+	xfts &= ~((3 << XFTS_RTS_FT_SHIFT) | (3 << XFTS_FBRRTS_FT_SHIFT));
+	xfts |= bcm4323x_frametype(rts_rspec[0], FT_HT) << XFTS_RTS_FT_SHIFT;
+	xfts |= bcm4323x_frametype(rts_rspec[1], FT_HT) << XFTS_FBRRTS_FT_SHIFT;
+	txh->XtraFrameTypes = cpu_to_le16(xfts);
+	txh->PhyTxControlWord_1_Rts = cpu_to_le16(brcmu_phytxctl1_calc(PHY_TYPE_N, rts_rspec[0]));
+	txh->PhyTxControlWord_1_FbrRts =
+		cpu_to_le16(brcmu_phytxctl1_calc(PHY_TYPE_N, rts_rspec[1]));
+	txh->MacTxControlLow = cpu_to_le16(mcl);
+	txh->MacTxControlHigh = cpu_to_le16(mch);
+}
+
+/* from brcmsmac: brcms_c_ampdu_finalize */
+static void bcm4323x_ampdu_finalize(struct d11txh *txh, u8 *plcp, const u32 *rspec,
+				    uint ampdu_len)
+{
+	u16 mcl = le16_to_cpu(txh->MacTxControlLow);
+
+	mcl |= TXC_STARTMSDU;
+	if (is_mcs_rate(rspec[0])) {
+		BRCMS_SET_MIMO_PLCP_LEN(plcp, ampdu_len);
+		/* mark plcp to indicate ampdu */
+		BRCMS_SET_MIMO_PLCP_AMPDU(plcp);
+	}
+
+	/* reset the mixed mode header durations */
+	if (txh->MModeLen)
+		txh->MModeLen = cpu_to_le16(brcmu_calc_lsig_len(rspec[0], ampdu_len));
+	if (txh->MModeFbrLen)
+		txh->MModeFbrLen = cpu_to_le16(brcmu_calc_lsig_len(rspec[1], ampdu_len));
+	txh->MacTxControlLow = cpu_to_le16(mcl);
+}
+
+/* from brcmsmac: brcms_ops_bss_info_changed */
+static bool bcm4323x_n_protection(const struct ieee80211_bss_conf *bss_conf)
+{
+	switch (bss_conf->ht_operation_mode & IEEE80211_HT_OP_MODE_PROTECTION) {
+	case IEEE80211_HT_OP_MODE_PROTECTION_NONMEMBER:
+	case IEEE80211_HT_OP_MODE_PROTECTION_NONHT_MIXED:
+		return true;
+	default:
+		return false;
+	}
+}
+
+/*
+ * Whether rate control or HT protection asks to protect the frame, and how, an
+ * aggregate through its first MPDU
+ */
+static bool bcm4323x_protection(struct bcm4323x *dev, const struct ieee80211_tx_info *tx_info,
+				const struct ieee80211_hdr *h, const u32 *rspec, u8 stf_mode,
+				u16 chanspec, struct bcm4323x_protection *prot)
+{
+	u32 flags = tx_info->control.rates[0].flags;
+	struct ieee80211_vif *vif;
+	u32 basic_rates = 0;
+	bool g_protection = false;
+	bool n_protection = false;
+	unsigned int k;
+
+	rcu_read_lock();
+	vif = rcu_dereference(dev->active_vif);
+	if (vif) {
+		basic_rates = vif->bss_conf.basic_rates;
+		prot->short_preamble = vif->bss_conf.use_short_preamble;
+		g_protection = vif->bss_conf.use_cts_prot && tx_info->band == NL80211_BAND_2GHZ;
+		n_protection = (flags & IEEE80211_TX_RC_MCS) &&
+			       bcm4323x_n_protection(&vif->bss_conf);
+	}
+	rcu_read_unlock();
+	if (!(flags & (IEEE80211_TX_RC_USE_RTS_CTS | IEEE80211_TX_RC_USE_CTS_PROTECT)) &&
+	    !n_protection)
+		return false;
+	/* Nothing would answer the RTS of a group-addressed frame. */
+	prot->cts_only = !(flags & IEEE80211_TX_RC_USE_RTS_CTS) ||
+			 is_multicast_ether_addr(h->addr1) ||
+			 (tx_info->flags & IEEE80211_TX_CTL_NO_ACK);
+	prot->bandtype = bcm4323x_bandtype(tx_info->band);
+	bcm4323x_init_basic_rate(prot->basic_rate, tx_info->band, basic_rates);
+	/* from brcmsmac: brcms_c_rspec_to_rts_rspec */
+	for (k = 0; k < 2; k++) {
+		u32 rts_rspec;
+
+		if (g_protection && !is_cck_rate(rspec[k]))
+			rts_rspec = brcmu_basic_rate(prot->basic_rate, BRCM_RATE_11M);
+		else
+			rts_rspec = brcmu_basic_rate(prot->basic_rate, rspec[k]);
+		rts_rspec = bcm4323x_rspec(rts_rspec, stf_mode, false, chanspec, false);
+		if (rspec_is40mhz(rspec[k]) && !is_cck_rate(rts_rspec))
+			rts_rspec = (rts_rspec & ~RSPEC_BW_MASK) |
+				    (PHY_TXC1_BW_40MHZ_DUP << RSPEC_BW_SHIFT);
+		prot->rts_rspec[k] = rts_rspec;
+	}
+	return true;
+}
+
+/* from brcmsmac: brcms_c_d11hdrs_mac80211 */
+static void bcm4323x_tx_rspecs(const struct ieee80211_tx_info *tx_info, bool ampdu,
+			       u16 chanspec, u16 ss_algo_channel, u32 *rspec, u8 *preamble_type)
+{
+	const struct ieee80211_tx_rate *rate = &tx_info->control.rates[0];
+	bool band_5ghz = tx_info->band == NL80211_BAND_5GHZ;
+	u8 stf_mode = bcm4323x_stf_mode(ss_algo_channel);
+	bool stbc;
+	unsigned int k;
+
+	if (rate->flags & IEEE80211_TX_RC_MCS)
+		rspec[0] = RSPEC_MIMORATE | rate->idx |
+			   (rate->flags & IEEE80211_TX_RC_SHORT_GI ? RSPEC_SHORT_GI : 0);
+	else
+		rspec[0] = bcm4323x_rates[bcm4323x_first_rate(tx_info->band) + rate->idx].hw_value;
+	/*
+	 * Aggregated MPDUs, and unlike brcmsmac MCS frames on 5 GHz as in the
+	 * vendor driver, fall back to one spatial stream or MCS 0, and others
+	 * to 1 Mbit/s or to 6 Mbit/s on 5 GHz, as legacy OFDM fallbacks were
+	 * reported acknowledged yet never received.
+	 */
+	if (ampdu || (band_5ghz && (rate->flags & IEEE80211_TX_RC_MCS)))
+		rspec[1] = RSPEC_MIMORATE | (is_single_stream(rate->idx) ? 0 :
+					     rate->idx - (HIGHEST_SINGLE_STREAM_MCS + 1));
+	else
+		rspec[1] = band_5ghz ? BRCM_RATE_6M : BRCM_RATE_1M;
+
+	/* from brcmsmac: BRCMS_STF_SS_STBC_TX */
+	stbc = (tx_info->flags & IEEE80211_TX_CTL_STBC) &&
+	       isset(&ss_algo_channel, PHY_TXC1_MODE_STBC);
+	for (k = 0; k < 2; k++) {
+		rspec[k] = bcm4323x_rspec(rspec[k], stf_mode, stbc, chanspec,
+					  rate->flags & IEEE80211_TX_RC_40_MHZ_WIDTH);
+		preamble_type[k] = is_mcs_rate(rspec[k]) ? BRCMS_MM_PREAMBLE : BRCMS_LONG_PREAMBLE;
+	}
+	if (is_cck_rate(rspec[0]) && (rate->flags & IEEE80211_TX_RC_USE_SHORT_PREAMBLE) &&
+	    rspec2rate(rspec[0]) != BRCM_RATE_1M)
+		preamble_type[0] = BRCMS_SHORT_PREAMBLE;
+}
+
+/* from brcmsmac: brcms_c_d11hdrs_mac80211, MacTxControlLow */
+static u16 bcm4323x_tx_mcl(const struct ieee80211_tx_info *tx_info, struct ieee80211_hdr *h,
+			   struct d11txh *txh, const u32 *rspec, bool ampdu, u8 ampdu_position,
+			   u16 chanspec)
+{
+	bool band_5ghz = tx_info->band == NL80211_BAND_5GHZ;
+	u16 mcl = TXC_STARTMSDU;
+
+	if (band_5ghz)
+		mcl |= TXC_FREQBAND_5G;
+	if (CHSPEC_IS40(chanspec))
+		mcl |= TXC_BW_40;
+	if (!is_multicast_ether_addr(h->addr1) && !(tx_info->flags & IEEE80211_TX_CTL_NO_ACK))
+		mcl |= TXC_IMMEDACK;
+	if (tx_info->flags & IEEE80211_TX_CTL_ASSIGN_SEQ)
+		mcl |= TXC_HWSEQ;
+	if (ampdu) {
+		mcl &= ~TXC_STARTMSDU;
+		mcl |= (ampdu_position << TXC_AMPDU_SHIFT) | TXC_LONGFRAME;
+		h->duration_id = cpu_to_le16(BCM4323X_OFDM_ACK_DURATION_US);
+		txh->FragDurFallback = cpu_to_le16(BCM4323X_OFDM_ACK_DURATION_US);
+	} else if (mcl & TXC_IMMEDACK) {
+		/* mac80211 leaves the duration of MCS and fast-xmit frames to the driver. */
+		if (ieee80211_is_data(h->frame_control) && !h->duration_id)
+			h->duration_id = cpu_to_le16(is_cck_rate(rspec[0]) ?
+						     BCM4323X_CCK_ACK_DURATION_US :
+						     BCM4323X_OFDM_ACK_DURATION_US);
+		if (is_mcs_rate(rspec[1]))
+			txh->FragDurFallback = cpu_to_le16(BCM4323X_OFDM_ACK_DURATION_US);
+		else
+			txh->FragDurFallback = cpu_to_le16(band_5ghz ?
+							   BCM4323X_OFDM6_ACK_DURATION_US :
+							   BCM4323X_CCK_ACK_DURATION_US);
+	}
+	return mcl;
+}
+
+/*
+ * from brcmsmac: brcms_c_d11hdrs_mac80211
+ *
+ * @ampdu_len is the length of the aggregate on its first MPDU, and 0 otherwise.
+ */
+static void bcm4323x_build_txh(struct bcm4323x *dev, struct sk_buff *skb,
+			       struct bcm4323x_txh *tx_header, u8 ampdu_position,
+			       unsigned int ampdu_len)
+{
+	struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(skb);
+	struct bcm4323x_tx_meta *metadata = bcm4323x_tx_meta(skb);
+	bool ampdu = metadata->flags & BCM4323X_TX_META_AMPDU;
+	bool hardware_key = metadata->flags & BCM4323X_TX_META_KEY;
+	struct ieee80211_hdr *h = (void *)skb->data;
+	unsigned int phylen = bcm4323x_tx_phy_length(skb);
+	u16 chanspec = READ_ONCE(dev->chanspec);
+	u16 ss_algo_channel = READ_ONCE(dev->ss_algo_channel);
+	struct d11txh *txh = &tx_header->d11;
+	u8 *plcp = tx_header->plcp;
+	struct bcm4323x_protection prot = {};
+	bool protect = false;
+	u32 rspec[2];
+	u8 preamble_type[2];
+	u16 mch = 0;
+	u16 phyctl = PHY_TXC_ANT_0_1;
+	u16 mainrates;
+	u16 xfts;
+
+	memset(tx_header, 0, sizeof(*tx_header));
+	bcm4323x_tx_rspecs(tx_info, ampdu, chanspec, ss_algo_channel, rspec, preamble_type);
+	if (!ampdu || ampdu_len)
+		protect = bcm4323x_protection(dev, tx_info, h, rspec,
+					      bcm4323x_stf_mode(ss_algo_channel), chanspec, &prot);
+	txh->MacTxControlLow = cpu_to_le16(bcm4323x_tx_mcl(tx_info, h, txh, rspec, ampdu,
+							   ampdu_position, chanspec));
+	if (hardware_key)
+		mch = FIELD_PREP(TXC_SECKEY_MASK, metadata->key_index) |
+		      FIELD_PREP(TXC_SECTYPE_MASK, WSEC_ALGO_AES);
+
+	brcmu_compute_plcp(rspec[0], phylen, plcp);
+	brcmu_compute_plcp(rspec[1], phylen, txh->FragPLCPFallback);
+	/* Length field now put in CCK FBR CRC field */
+	if (is_cck_rate(rspec[1])) {
+		txh->FragPLCPFallback[4] = phylen & 0xff;
+		txh->FragPLCPFallback[5] = (phylen & 0xff00) >> 8;
+	}
+	mainrates = is_ofdm_rate(rspec[0]) ?
+		    D11A_PHY_HDR_GRATE((struct ofdm_phy_hdr *)plcp) : plcp[0];
+	xfts = bcm4323x_frametype(rspec[1], FT_HT) | (FT_OFDM << XFTS_RTS_FT_SHIFT) |
+	       (FT_OFDM << XFTS_FBRRTS_FT_SHIFT) |
+	       (CHSPEC_CHANNEL(chanspec) << XFTS_CHANNEL_SHIFT);
+	phyctl |= bcm4323x_frametype(rspec[0], FT_HT);
+	if (preamble_type[0] == BRCMS_SHORT_PREAMBLE)
+		phyctl |= PHY_TXC_SHORT_HDR;
+	if (is_mcs_rate(rspec[0]))
+		txh->MModeLen = cpu_to_le16(brcmu_calc_lsig_len(rspec[0], phylen));
+	if (is_mcs_rate(rspec[1]))
+		txh->MModeFbrLen = cpu_to_le16(brcmu_calc_lsig_len(rspec[1], phylen));
+
+	txh->MacTxControlHigh = cpu_to_le16(mch);
+	txh->MacFrameControl = h->frame_control;
+	txh->PhyTxControlWord = cpu_to_le16(phyctl);
+	txh->PhyTxControlWord_1 = cpu_to_le16(brcmu_phytxctl1_calc(PHY_TYPE_N, rspec[0]));
+	txh->PhyTxControlWord_1_Fbr = cpu_to_le16(brcmu_phytxctl1_calc(PHY_TYPE_N, rspec[1]));
+	txh->MainRates = cpu_to_le16(mainrates);
+	txh->XtraFrameTypes = cpu_to_le16(xfts);
+	if (hardware_key && ieee80211_has_protected(h->frame_control))
+		memcpy(txh->IV, skb->data + ieee80211_hdrlen(h->frame_control),
+		       min_t(size_t, metadata->iv_length, sizeof(txh->IV)));
+	ether_addr_copy(txh->TxFrameRA, h->addr1);
+	txh->TxFrameID = cpu_to_le16(metadata->frame_id);
+	if (ampdu_len)
+		bcm4323x_ampdu_finalize(txh, plcp, rspec, ampdu_len);
+	if (protect)
+		bcm4323x_build_rts(txh, h, &prot, rspec, preamble_type, ampdu_len ?: phylen,
+				   ampdu_len);
+	/* add null delimiter count, none after the last MPDU */
+	if (ampdu)
+		txh->RTSPLCPFallback[AMPDU_FBR_NULL_DELIM] =
+			ampdu_position == TXC_AMPDU_LAST ? 0 : metadata->null_delimiters;
+}
+
+static void bcm4323x_tx_complete(struct urb *urb)
+{
+	struct bcm4323x *dev = urb->context;
+
+	if (urb->status == -ENOENT || urb->status == -ECONNRESET || urb->status == -ESHUTDOWN)
+		return;
+
+	if (urb->status) {
+		dev_err_ratelimited(&dev->intf->dev, "TX URB failed: %d\n", urb->status);
+		bcm4323x_fatal_error(dev);
+	}
+}
+
+/* @density is the minimum MPDU start spacing of the recipient. */
+static u8 bcm4323x_ampdu_null_delimiters(struct sk_buff *skb, u8 density)
+{
+	u64 spacing_ns;
+	unsigned int minimum_bytes;
+	unsigned int occupied_bytes;
+
+	if (!density)
+		return 0;
+	spacing_ns = 250ULL << (density - 1);
+	minimum_bytes = DIV_ROUND_UP_ULL((u64)bcm4323x_tx_kbps(skb) * spacing_ns, 8000000);
+	occupied_bytes = AMPDU_DELIMITER_LEN + ALIGN(bcm4323x_tx_phy_length(skb), 4);
+	if (minimum_bytes <= occupied_bytes)
+		return 0;
+	return min_t(unsigned int, DIV_ROUND_UP(minimum_bytes - occupied_bytes, 4), U8_MAX);
+}
+
+static bool bcm4323x_ampdu_compatible(struct sk_buff *first, struct sk_buff *next)
+{
+	struct ieee80211_tx_info *first_info = IEEE80211_SKB_CB(first);
+	struct ieee80211_tx_info *next_info = IEEE80211_SKB_CB(next);
+	struct bcm4323x_tx_meta *first_meta = bcm4323x_tx_meta(first);
+	struct bcm4323x_tx_meta *next_meta = bcm4323x_tx_meta(next);
+
+	return first_meta->flags == next_meta->flags &&
+	       first_meta->key_index == next_meta->key_index &&
+	       !((first_info->flags ^ next_info->flags) & IEEE80211_TX_CTL_RATE_CTRL_PROBE) &&
+	       first_info->control.rates[0].idx == next_info->control.rates[0].idx &&
+	       first_info->control.rates[0].flags == next_info->control.rates[0].flags;
+}
+
+static struct urb *bcm4323x_tx_urb(struct bcm4323x *dev, struct sk_buff *skb, unsigned int index,
+				   unsigned int count, unsigned int aggregate_length)
+{
+	struct ieee80211_hdr *hdr = (void *)skb->data;
+	struct bcm4323x_tx_meta *metadata = bcm4323x_tx_meta(skb);
+	bool ampdu = metadata->flags & BCM4323X_TX_META_AMPDU;
+	struct bcm4323x_rpc_header *rpc;
+	struct bcm4323x_tx_call *call;
+	size_t body_length;
+	size_t total_length;
+	struct urb *urb;
+	u32 fifo;
+	u8 ampdu_position = TXC_AMPDU_NONE;
+
+	body_length = ALIGN(struct_size(call, frame, skb->len), sizeof(__le32));
+	total_length = sizeof(*rpc) + body_length;
+	urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!urb)
+		return NULL;
+	urb->transfer_flags |= URB_FREE_BUFFER | URB_ZERO_PACKET;
+	urb->transfer_buffer = kzalloc(total_length, GFP_KERNEL);
+	if (!urb->transfer_buffer) {
+		usb_free_urb(urb);
+		return NULL;
+	}
+
+	fifo = bcm4323x_ac_to_fifo(skb_get_queue_mapping(skb));
+	dev->tx_sequence++;
+	metadata->frame_id = ((dev->tx_sequence << TXFID_SEQ_SHIFT) & TXFID_SEQ_MASK) |
+			     (fifo & TXFID_QUEUE_MASK);
+
+	if (aggregate_length && index == count - 1)
+		ampdu_position = TXC_AMPDU_LAST;
+	else if (aggregate_length)
+		ampdu_position = index ? TXC_AMPDU_MIDDLE : TXC_AMPDU_FIRST;
+
+	rpc = urb->transfer_buffer;
+	call = (void *)(rpc + 1);
+	call->fifo = cpu_to_le32(fifo);
+	/* Last one of the aggregate */
+	call->commit = cpu_to_le32(!aggregate_length || index == count - 1);
+	call->arg2 = cpu_to_le32(BCM4323X_TX_ARG2);
+	call->weight = cpu_to_le32(ampdu ? BCM4323X_TX_WEIGHT_AMPDU : BCM4323X_TX_WEIGHT);
+	call->flags = cpu_to_le32(ieee80211_is_data(hdr->frame_control) ? BCM4323X_TX_FLAGS_DATA :
+								   BCM4323X_TX_FLAGS_MGMT);
+	call->packet_length = cpu_to_le32(sizeof(call->txh) + skb->len);
+	bcm4323x_build_txh(dev, skb, &call->txh, ampdu_position, index ? 0 : aggregate_length);
+	/* A-MSDU subframes after the first are chained on the frag list. */
+	skb_copy_bits(skb, 0, call->frame, skb->len);
+	rpc->length = bcm4323x_rpc_length(body_length);
+	rpc->procedure = cpu_to_le32(BCM4323X_RPC_PROC_TX);
+	usb_fill_bulk_urb(urb, dev->udev, dev->tx_pipe,
+			  urb->transfer_buffer, total_length, bcm4323x_tx_complete, dev);
+	return urb;
+}
+
+static int bcm4323x_submit_tx_urb(struct bcm4323x *dev, struct urb *urb)
+{
+	struct bcm4323x_rpc_header *rpc = urb->transfer_buffer;
+	int ret;
+
+	lockdep_assert_held(&dev->io_mutex);
+	rpc->xid_class = bcm4323x_rpc_xid_class(BCM4323X_RPC_CLASS_DATA, dev->rpc_xid);
+	usb_anchor_urb(urb, &dev->tx_anchor);
+	ret = usb_submit_urb(urb, GFP_KERNEL);
+	if (ret)
+		usb_unanchor_urb(urb);
+	else
+		dev->rpc_xid++;
+	return ret;
+}
+
+/*
+ * The firmware MAC stalls with two multi-frame batches committed, so
+ * avoid committing two batches.
+ */
+static bool bcm4323x_may_commit(struct bcm4323x *dev, unsigned int count)
+{
+	unsigned int older = skb_queue_len(&dev->tx_batches[0]);
+
+	return !older || (skb_queue_empty(&dev->tx_batches[1]) && (count == 1 || older == 1));
+}
+
+static void bcm4323x_add_committed(struct bcm4323x *dev, struct sk_buff_head *batch)
+{
+	unsigned int slot = !skb_queue_empty(&dev->tx_batches[0]);
+
+	if (!slot)
+		dev->tx_expires = jiffies + BCM4323X_TX_TIMEOUT;
+	skb_queue_splice_tail_init(batch, &dev->tx_batches[slot]);
+}
+
+static int bcm4323x_tx_urbs(struct bcm4323x *dev, struct sk_buff_head *batch,
+			    unsigned int aggregate_length, struct urb **urbs)
+{
+	unsigned int count = skb_queue_len(batch);
+	unsigned int index = 0;
+	struct sk_buff *skb;
+
+	skb_queue_walk(batch, skb) {
+		urbs[index] = bcm4323x_tx_urb(dev, skb, index, count, aggregate_length);
+		if (!urbs[index])
+			return -ENOMEM;
+		index++;
+	}
+	return 0;
+}
+
+/*
+ * Since the firmware will crash when committing two batches, we commit the
+ * first one, put the data of the second one in the device, then wait
+ * for the first one to complete before completing the second one. This allows
+ * overlapping transmit and usb tx, making things faster
+ */
+static void bcm4323x_tx_batch(struct bcm4323x *dev, struct sk_buff_head *batch,
+			      unsigned int aggregate_length)
+{
+	unsigned int count = skb_queue_len(batch);
+	bool staged = !bcm4323x_may_commit(dev, count);
+	/* A staged batch keeps back its last call. */
+	unsigned int to_submit = staged ? count - 1 : count;
+	struct urb *urbs[BCM4323X_TX_BATCH_FRAMES] = {};
+	unsigned int submitted = 0;
+	unsigned int index;
+	int ret;
+
+	ret = bcm4323x_tx_urbs(dev, batch, aggregate_length, urbs);
+	if (ret)
+		goto out;
+	mutex_lock(&dev->io_mutex);
+	while (submitted < to_submit) {
+		ret = bcm4323x_submit_tx_urb(dev, urbs[submitted]);
+		if (ret)
+			break;
+		submitted++;
+	}
+	mutex_unlock(&dev->io_mutex);
+	if (!ret && staged)
+		swap(dev->tx_commit, urbs[count - 1]);
+
+out:
+	for (index = 0; index < count; index++)
+		usb_free_urb(urbs[index]);
+	if (!ret) {
+		WRITE_ONCE(dev->tx_owned, dev->tx_owned + count);
+		if (staged)
+			skb_queue_splice_tail_init(batch, &dev->tx_batches[BCM4323X_TX_STAGED]);
+		else
+			bcm4323x_add_committed(dev, batch);
+		return;
+	}
+	/* Firmware holds back later RPCs until it receives a missing ID. */
+	if (submitted) {
+		dev_err_ratelimited(&dev->intf->dev, "incomplete A-MPDU submission\n");
+		bcm4323x_fatal_error(dev);
+	}
+	bcm4323x_fail_batch(dev, batch);
+}
+
+/* Submits the last call of the staged batch, which commits it. */
+static void bcm4323x_commit_staged(struct bcm4323x *dev)
+{
+	struct urb *urb = dev->tx_commit;
+	int ret;
+
+	dev->tx_commit = NULL;
+	mutex_lock(&dev->io_mutex);
+	ret = bcm4323x_submit_tx_urb(dev, urb);
+	mutex_unlock(&dev->io_mutex);
+	usb_free_urb(urb);
+	bcm4323x_add_committed(dev, &dev->tx_batches[BCM4323X_TX_STAGED]);
+	if (ret) {
+		dev_err_ratelimited(&dev->intf->dev, "TX commit failed: %d\n", ret);
+		bcm4323x_fatal_error(dev);
+		bcm4323x_fail_pending(dev);
+	}
+}
+
+static struct sk_buff *bcm4323x_txq_dequeue(struct bcm4323x *dev, struct ieee80211_txq *txq)
+{
+	struct sk_buff *skb = __skb_dequeue(&to_bcm4323x_txq(txq)->frames);
+
+	if (skb)
+		return skb;
+	while ((skb = ieee80211_tx_dequeue_ni(dev->hw, txq)))
+		if (bcm4323x_prepare_tx(dev, skb))
+			break;
+	return skb;
+}
+
+static u16 bcm4323x_window_start(struct bcm4323x *dev, struct sk_buff *first)
+{
+	struct ieee80211_hdr *hdr = (void *)first->data;
+	u16 start = ieee80211_get_sn(hdr);
+	struct sk_buff *skb;
+	unsigned int i;
+
+	for (i = 0; i < BCM4323X_TX_STAGED; i++) {
+		skb_queue_walk(&dev->tx_batches[i], skb) {
+			struct ieee80211_hdr *owned_hdr = (void *)skb->data;
+
+			if ((bcm4323x_tx_meta(skb)->flags & BCM4323X_TX_META_AMPDU) &&
+			    ether_addr_equal(owned_hdr->addr1, hdr->addr1) &&
+			    ieee80211_get_tid(owned_hdr) == ieee80211_get_tid(hdr) &&
+			    ieee80211_sn_less(ieee80211_get_sn(owned_hdr), start))
+				start = ieee80211_get_sn(owned_hdr);
+		}
+	}
+	return start;
+}
+
+static unsigned int bcm4323x_ampdu_grow(struct sk_buff *last, struct sk_buff *next,
+					unsigned int length, u8 density)
+{
+	unsigned int next_length = AMPDU_DELIMITER_LEN + bcm4323x_tx_phy_length(next);
+	struct bcm4323x_tx_meta *metadata;
+
+	if (!last)
+		return next_length;
+	metadata = bcm4323x_tx_meta(last);
+	metadata->null_delimiters = bcm4323x_ampdu_null_delimiters(last, density);
+	return roundup(length, 4) + metadata->null_delimiters * AMPDU_DELIMITER_LEN + next_length;
+}
+
+/* from brcmsmac: brcms_c_ampdu_add_frame, brcms_c_scb_ampdu_update_max_txlen */
+static unsigned int bcm4323x_txq_batch(struct bcm4323x *dev, struct ieee80211_txq *txq,
+				       struct sk_buff_head *batch)
+{
+	struct bcm4323x_txq *txq_priv = to_bcm4323x_txq(txq);
+	struct sk_buff *first = bcm4323x_txq_dequeue(dev, txq);
+	struct ieee80211_sta_ht_cap *ht_cap;
+	u8 window;
+	u16 start;
+	unsigned int max_bytes;
+	unsigned int max_frames;
+	struct sk_buff *next = first;
+	struct sk_buff *last = NULL;
+	unsigned int aggregate_length = 0;
+
+	if (!first)
+		return 0;
+	if (!(bcm4323x_tx_meta(first)->flags & BCM4323X_TX_META_AMPDU)) {
+		__skb_queue_tail(batch, first);
+		return 0;
+	}
+
+	ht_cap = &txq->sta->deflink.ht_cap;
+	window = READ_ONCE(txq_priv->ampdu_window) ?: 1;
+	start = bcm4323x_window_start(dev, first);
+	max_bytes = min3((1U << (IEEE80211_HT_MAX_AMPDU_FACTOR + ht_cap->ampdu_factor)) - 1,
+			 bcm4323x_tx_kbps(first) * AMPDU_MAX_DUR / 8,
+			 BCM4323X_TX_BATCH_BYTES);
+	max_frames = min(window, BCM4323X_TX_BATCH_FRAMES);
+	do {
+		unsigned int length = bcm4323x_ampdu_grow(last, next, aggregate_length,
+							  ht_cap->ampdu_density);
+
+		if (ieee80211_sn_sub(ieee80211_get_sn((void *)next->data), start) >= window ||
+		    (last && (!bcm4323x_ampdu_compatible(first, next) || length > max_bytes))) {
+			__skb_queue_head(&txq_priv->frames, next);
+			break;
+		}
+		__skb_queue_tail(batch, next);
+		aggregate_length = length;
+		last = next;
+	} while (skb_queue_len(batch) < max_frames && (next = bcm4323x_txq_dequeue(dev, txq)));
+	return aggregate_length;
+}
+
+static unsigned int bcm4323x_next_batch(struct bcm4323x *dev, struct sk_buff_head *batch)
+{
+	struct ieee80211_hw *hw = dev->hw;
+	unsigned int aggregate_length = 0;
+	struct ieee80211_txq *txq;
+	struct sk_buff *skb;
+	u8 ac;
+
+	skb = skb_dequeue(&dev->tx_queue);
+	if (skb) {
+		__skb_queue_tail(batch, skb);
+		return 0;
+	}
+	/* RCU protects pointers in a dequeued frame until bcm4323x_prepare_tx(). */
+	rcu_read_lock();
+	for (ac = IEEE80211_AC_VO; ac < IEEE80211_NUM_ACS && skb_queue_empty(batch); ac++) {
+		ieee80211_txq_schedule_start(hw, ac);
+		while (skb_queue_empty(batch) && (txq = ieee80211_next_txq(hw, ac))) {
+			struct bcm4323x_txq *txq_priv = to_bcm4323x_txq(txq);
+
+			aggregate_length = bcm4323x_txq_batch(dev, txq, batch);
+			ieee80211_return_txq(hw, txq, !skb_queue_empty(&txq_priv->frames));
+		}
+		ieee80211_txq_schedule_end(hw, ac);
+	}
+	rcu_read_unlock();
+	return aggregate_length;
+}
+
+static void bcm4323x_tx_worker(struct work_struct *work)
+{
+	struct bcm4323x *dev = container_of(work, struct bcm4323x, tx_work);
+	struct sk_buff_head *staged = &dev->tx_batches[BCM4323X_TX_STAGED];
+	struct bcm4323x_tx_status txs;
+	unsigned int aggregate_length;
+	struct sk_buff_head batch;
+
+	__skb_queue_head_init(&batch);
+	mutex_lock(&dev->tx_mutex);
+	while (kfifo_get(&dev->tx_statuses, &txs))
+		bcm4323x_complete_tx(dev, &txs);
+	while (bcm4323x_active(dev)) {
+		if (!skb_queue_empty(staged) && bcm4323x_may_commit(dev, skb_queue_len(staged)))
+			bcm4323x_commit_staged(dev);
+		if (dev->tx_paused || !skb_queue_empty(staged))
+			break;
+		aggregate_length = bcm4323x_next_batch(dev, &batch);
+		if (skb_queue_empty(&batch))
+			break;
+		bcm4323x_tx_batch(dev, &batch, aggregate_length);
+	}
+	mutex_unlock(&dev->tx_mutex);
+	wake_up_all(&dev->tx_wait);
+}
+
+void bcm4323x_tx(struct ieee80211_hw *hw, struct ieee80211_tx_control *control, struct sk_buff *skb)
+{
+	struct bcm4323x *dev = hw->priv;
+
+	if (!bcm4323x_prepare_tx(dev, skb))
+		return;
+	skb_queue_tail(&dev->tx_queue, skb);
+	bcm4323x_schedule_tx_work(dev);
+}
+
+void bcm4323x_wake_tx_queue(struct ieee80211_hw *hw, struct ieee80211_txq *txq)
+{
+	bcm4323x_schedule_tx_work(hw->priv);
+}
+
+void bcm4323x_check_tx(struct bcm4323x *dev)
+{
+	mutex_lock(&dev->tx_mutex);
+	if (kfifo_is_empty(&dev->tx_statuses) && !skb_queue_empty(&dev->tx_batches[0]) &&
+	    time_after(jiffies, dev->tx_expires)) {
+		dev_err(&dev->intf->dev, "firmware TX status timeout\n");
+		bcm4323x_fatal_error(dev);
+		bcm4323x_fail_pending(dev);
+	}
+	mutex_unlock(&dev->tx_mutex);
+	wake_up_all(&dev->tx_wait);
+}
+
+/* Drops the frames of a queue that use a key slot. */
+static void bcm4323x_drop_tx(struct bcm4323x *dev, struct sk_buff_head *queue, u8 key_index)
+{
+	struct sk_buff_head dropped;
+	struct sk_buff *skb;
+	struct sk_buff *tmp;
+	unsigned long flags;
+
+	__skb_queue_head_init(&dropped);
+	spin_lock_irqsave(&queue->lock, flags);
+	skb_queue_walk_safe(queue, skb, tmp) {
+		struct bcm4323x_tx_meta *metadata = bcm4323x_tx_meta(skb);
+
+		if ((metadata->flags & BCM4323X_TX_META_KEY) && metadata->key_index == key_index) {
+			__skb_unlink(skb, queue);
+			__skb_queue_tail(&dropped, skb);
+		}
+	}
+	spin_unlock_irqrestore(&queue->lock, flags);
+	ieee80211_purge_tx_queue(dev->hw, &dropped);
+}
+
+void bcm4323x_flush_tx(struct bcm4323x *dev)
+{
+	cancel_work_sync(&dev->tx_work);
+	/* Flush the firmware buffers */
+	if (dev->tx_commit && !READ_ONCE(dev->dead))
+		bcm4323x_commit_staged(dev);
+	/* A killed URB can leave its transaction ID unsent; let submissions finish. */
+	usb_wait_anchor_empty_timeout(&dev->tx_anchor, BCM4323X_TX_DRAIN_MS);
+	usb_kill_anchored_urbs(&dev->tx_anchor);
+	kfifo_reset(&dev->tx_statuses);
+	ieee80211_purge_tx_queue(dev->hw, &dev->tx_queue);
+	bcm4323x_fail_pending(dev);
+}
+
+void bcm4323x_set_tx_paused(struct bcm4323x *dev, bool paused)
+{
+	mutex_lock(&dev->tx_mutex);
+	dev->tx_paused = paused;
+	mutex_unlock(&dev->tx_mutex);
+	if (!paused)
+		bcm4323x_schedule_tx_work(dev);
+}
+
+bool bcm4323x_quiesce_tx(struct bcm4323x *dev)
+{
+	bcm4323x_set_tx_paused(dev, true);
+	return wait_event_timeout(dev->tx_wait, !READ_ONCE(dev->tx_owned) || READ_ONCE(dev->dead),
+				  BCM4323X_TX_WAIT);
+}
+
+/* Drain queued frames which use the key slot that is currently requested to be dropped */
+void bcm4323x_retire_key(struct bcm4323x *dev, u8 key_index, struct ieee80211_sta *sta)
+{
+	unsigned int i;
+
+	clear_bit(key_index, dev->keys);
+	/* Frames prepared from now on are dropped. */
+	synchronize_net();
+	/* Frames that are being buffered must not go out without their key. */
+	if (!bcm4323x_quiesce_tx(dev))
+		bcm4323x_fatal_error(dev);
+	bcm4323x_drop_tx(dev, &dev->tx_queue, key_index);
+	for (i = 0; sta && i < IEEE80211_NUM_TIDS; i++)
+		bcm4323x_drop_tx(dev, &to_bcm4323x_txq(sta->txq[i])->frames, key_index);
+	bcm4323x_set_tx_paused(dev, false);
+}
+
+static bool bcm4323x_tx_idle(struct bcm4323x *dev)
+{
+	if (!bcm4323x_active(dev))
+		return true;
+
+	return skb_queue_empty_lockless(&dev->tx_queue) && !READ_ONCE(dev->tx_owned) &&
+	       !mutex_is_locked(&dev->tx_mutex);
+}
+
+void bcm4323x_flush(struct ieee80211_hw *hw, struct ieee80211_vif *vif, u32 queues, bool drop)
+{
+	struct bcm4323x *dev = hw->priv;
+	struct sk_buff *skb;
+	bool idle;
+
+	if (drop) {
+		idle = bcm4323x_quiesce_tx(dev);
+		/* bcm4323x_tx() may still be queueing a frame. */
+		while ((skb = skb_dequeue(&dev->tx_queue)))
+			ieee80211_free_txskb(hw, skb);
+		bcm4323x_set_tx_paused(dev, false);
+	} else {
+		idle = wait_event_timeout(dev->tx_wait, bcm4323x_tx_idle(dev), BCM4323X_TX_WAIT);
+	}
+	if (!idle)
+		dev_warn(&dev->intf->dev, "TX flush timed out\n");
+}
+
+/* from brcmsmac: brcms_ops_ampdu_action */
+int bcm4323x_ampdu_action(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+			  struct ieee80211_ampdu_params *params)
+{
+	struct bcm4323x *dev = hw->priv;
+	struct ieee80211_txq *txq = params->sta->txq[params->tid];
+	struct sk_buff_head *frames = &to_bcm4323x_txq(txq)->frames;
+	struct sk_buff *skb;
+
+	switch (params->action) {
+	case IEEE80211_AMPDU_RX_START:
+	case IEEE80211_AMPDU_RX_STOP:
+	case IEEE80211_AMPDU_TX_STOP_FLUSH:
+	case IEEE80211_AMPDU_TX_STOP_FLUSH_CONT:
+		return 0;
+	case IEEE80211_AMPDU_TX_START:
+		return IEEE80211_AMPDU_TX_START_IMMEDIATE;
+	case IEEE80211_AMPDU_TX_OPERATIONAL:
+		WRITE_ONCE(to_bcm4323x_txq(txq)->ampdu_window, params->buf_size);
+		return 0;
+	case IEEE80211_AMPDU_TX_STOP_CONT:
+		bcm4323x_quiesce_tx(dev);
+		/* While TX is paused, the TX work leaves the TX queues alone. */
+		skb_queue_walk(frames, skb)
+			bcm4323x_tx_meta(skb)->flags &= ~BCM4323X_TX_META_AMPDU;
+		bcm4323x_set_tx_paused(dev, false);
+		ieee80211_stop_tx_ba_cb_irqsafe(vif, params->sta->addr, params->tid);
+		return 0;
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+void bcm4323x_init_txq(struct ieee80211_txq *txq)
+{
+	skb_queue_head_init(&to_bcm4323x_txq(txq)->frames);
+}
+
+int bcm4323x_sta_state(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+		       struct ieee80211_sta *sta, enum ieee80211_sta_state old_state,
+		       enum ieee80211_sta_state new_state)
+{
+	struct bcm4323x *dev = hw->priv;
+	unsigned int i;
+
+	if (old_state == IEEE80211_STA_NOTEXIST) {
+		for (i = 0; i < IEEE80211_NUM_TIDS; i++)
+			bcm4323x_init_txq(sta->txq[i]);
+	} else if (new_state == IEEE80211_STA_NOTEXIST) {
+		mutex_lock(&dev->tx_mutex);
+		for (i = 0; i < IEEE80211_NUM_TIDS; i++)
+			ieee80211_purge_tx_queue(hw, &to_bcm4323x_txq(sta->txq[i])->frames);
+		mutex_unlock(&dev->tx_mutex);
+	}
+	return 0;
+}
+
+void bcm4323x_init_tx(struct bcm4323x *dev)
+{
+	unsigned int i;
+
+	init_usb_anchor(&dev->tx_anchor);
+	init_waitqueue_head(&dev->tx_wait);
+	skb_queue_head_init(&dev->tx_queue);
+	INIT_WORK(&dev->tx_work, bcm4323x_tx_worker);
+	INIT_KFIFO(dev->tx_statuses);
+	mutex_init(&dev->tx_mutex);
+	for (i = 0; i < ARRAY_SIZE(dev->tx_batches); i++)
+		__skb_queue_head_init(&dev->tx_batches[i]);
+}
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [RFC PATCH wireless-next 15/17] wifi: bcm4323x: add the mac80211 operations
  2026-10-26  5:36 [RFC PATCH wireless-next 00/17] wifi: add a driver for Broadcom BCM4323x USB adapters Ragul Balaji
                   ` (13 preceding siblings ...)
  2026-10-26  5:36 ` [RFC PATCH wireless-next 14/17] wifi: bcm4323x: add the transmit path Ragul Balaji
@ 2026-10-26  5:36 ` Ragul Balaji
  2026-10-26  5:36 ` [RFC PATCH wireless-next 16/17] wifi: bcm4323x: add LED and WPS button support Ragul Balaji
  2026-10-26  5:36 ` [RFC PATCH wireless-next 17/17] wifi: bcm4323x: add Kconfig, Makefile and MAINTAINERS entry Ragul Balaji
  16 siblings, 0 replies; 18+ messages in thread
From: Ragul Balaji @ 2026-10-26  5:36 UTC (permalink / raw)
  To: linux-wireless
  Cc: Arend van Spriel, Johannes Berg, brcm80211, Ragul Balaji,
	Ho Jie Feng, linux-kernel, brcm80211-dev-list.pdl

Add the mac80211 side of the device driver.

Signed-off-by: Ragul Balaji <ragulbaby@gmail.com>
Signed-off-by: Ho Jie Feng <hjf3108@gmail.com>
---
 .../brcm80211/bcm4323x/bcm4323x_mac.c         | 1495 +++++++++++++++++
 1 file changed, 1495 insertions(+)
 create mode 100644 drivers/net/wireless/broadcom/brcm80211/bcm4323x/bcm4323x_mac.c

diff --git a/drivers/net/wireless/broadcom/brcm80211/bcm4323x/bcm4323x_mac.c b/drivers/net/wireless/broadcom/brcm80211/bcm4323x/bcm4323x_mac.c
new file mode 100644
index 000000000..4864c5d12
--- /dev/null
+++ b/drivers/net/wireless/broadcom/brcm80211/bcm4323x/bcm4323x_mac.c
@@ -0,0 +1,1495 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/args.h>
+#include <linux/device.h>
+#include <linux/jiffies.h>
+#include <linux/random.h>
+#include <linux/slab.h>
+
+#include "bcm4323x.h"
+
+/* from brcmsmac: phy_hal.h */
+#define BRCMS_TXPWR_DB_FACTOR	4
+#define BRCMS_TXPWR_MAX		(127)	/* ~32 dBm = 1,500 mW */
+#define PHY_PERICAL_JOIN_BSS	4
+
+/* from brcmsmac: phy_shim.h */
+#define WL_TX_POWER_MCS20_SISO_FIRST   20
+#define WL_TX_POWER_MCS20_CDD_FIRST    28
+#define WL_TX_POWER_MCS20_STBC_FIRST   36
+#define WL_TX_POWER_MCS40_SISO_FIRST   68
+#define WL_TX_POWER_MCS40_CDD_FIRST    76
+#define WL_TX_POWER_MCS40_STBC_FIRST   84
+
+#define BCM4323X_RPC_PROC_REG_WRITE 0x02
+#define BCM4323X_RPC_PROC_HOST_FLAGS 0x03
+#define BCM4323X_RPC_PROC_NOISE 0x1f
+#define BCM4323X_RPC_PROC_PHY_CAL 0x20
+#define BCM4323X_RPC_PROC_TX_POWER 0x22
+#define BCM4323X_RPC_PROC_SHM_COPY 0x28
+#define BCM4323X_RPC_PROC_RADIO_SYNC 0x29
+#define BCM4323X_RPC_PROC_MAC_CONTROL 0x2a
+#define BCM4323X_RPC_PROC_SHM_READ 0x2c
+#define BCM4323X_RPC_PROC_RX_ADDRESS 0x2e
+#define BCM4323X_RPC_PROC_KEY_PEER 0x30
+#define BCM4323X_RPC_PROC_SHM_FILL 0x31
+#define BCM4323X_RPC_PROC_CHANNEL_PREP 0x32
+#define BCM4323X_RPC_PROC_SHM_WRITE 0x33
+#define BCM4323X_RPC_PROC_TEMPLATE 0x34
+#define BCM4323X_RPC_PROC_MAC 0x38
+#define BCM4323X_RPC_PROC_CHANNEL_DATA 0x3a
+#define BCM4323X_RPC_PROC_SHORT_SLOT 0x41
+#define BCM4323X_RPC_PROC_TX_POWER_REPORT 0x43
+#define BCM4323X_RPC_PROC_STF_MODE 0x47
+
+#define BCM4323X_KEY_TABLE_BASE 0x0ea4
+#define BCM4323X_MHF1_DEFKEYS 0x4000
+
+#define BCM4323X_SHM_TUNE 0x0076
+#define BCM4323X_SHM_TUNE_VALUE 0x36
+
+#define BCM4323X_TX_POWER_RATES 153
+#define BCM4323X_WATCHDOG_INTERVAL HZ
+#define BCM4323X_WATCHDOG_FAILURES 3
+#define BCM4323X_PHY_CAL_INTERVAL (60 * HZ)
+
+/* from brcmsmac */
+#define EDCF_AIFSN_MASK 0x0f
+#define EDCF_AIFSN_MAX 15
+#define EDCF_TXOP2USEC(txop) ((txop) << 5)
+#define MAXFRAMEBURST_TXOP 10000
+#define RFDISABLE_DEFAULT 10000000
+
+/* The fields of the M_AC_TXLMT_ADDR() words */
+#define EDCF_SHORT GENMASK(3, 0)
+#define EDCF_SFB GENMASK(7, 4)
+#define EDCF_LONG GENMASK(11, 8)
+#define EDCF_LFB GENMASK(15, 12)
+#define RETRY_SHORT_FB 3
+#define RETRY_LONG_FB 2
+
+/* from brcmsmac */
+#define MAC_FILTERS (FIF_ALLMULTI | \
+	FIF_FCSFAIL | \
+	FIF_CONTROL | \
+	FIF_OTHER_BSS | \
+	FIF_BCN_PRBRESP_PROMISC | \
+	FIF_PSPOLL)
+
+/* The bits of maccontrol that bcm4323x_set_maccontrol() sets or clears */
+#define BCM4323X_MCTL_RECEIVE (MCTL_DISCARD_PMQ | MCTL_AP | MCTL_INFRA | MCTL_PROMISC | \
+			       MCTL_BCNS_PROMISC | MCTL_KEEPCONTROL | MCTL_KEEPBADFCS | \
+			       MCTL_HPS | MCTL_WAKE)
+
+/* Procedure 0x28 */
+struct bcm4323x_shm_copy {
+	__le32 offset;
+	__le32 length;
+	/* The data, at most the parameters of an EDCF queue, then the memory selector */
+	u8 data[M_EDCF_QLEN + sizeof(__le32)];
+} __packed;
+
+static_assert(sizeof(struct shm_acparams) <= M_EDCF_QLEN);
+
+/* Procedures 0x2e and 0x30 */
+struct bcm4323x_slot_address {
+	__le32 slot;
+	u8 addr[ETH_ALEN];
+	__le16 pad;
+} __packed;
+
+/* Procedure 0x34: brcms_b_write_template_ram() */
+struct bcm4323x_ba_template {
+	__le32 offset;
+	__le32 length;
+	u8 data[T_RAM_ACCESS_SZ * 2];
+} __packed;
+
+/* Procedure 0x3a */
+struct bcm4323x_channel_data {
+	__le32 chanspec;
+	__le32 reserved;
+	u8 values[156];
+} __packed;
+
+/* from brcmsmac: struct tx_power */
+struct bcm4323x_tx_power_report {
+	__le32 flags;
+	__le16 chanspec;
+	__le16 local_chanspec;
+	u8 local_max;
+	u8 local_constraint;
+	s8 antgain[2];
+	u8 rf_cores;
+	u8 est_Pout[4];
+	u8 est_Pout_act[4];
+	u8 est_Pout_cck;
+	u8 tx_power_max[4];
+	u8 tx_power_max_rate_ind[4];
+	u8 user_limit[BCM4323X_TX_POWER_RATES];
+	u8 reg_limit[BCM4323X_TX_POWER_RATES];
+	u8 board_limit[BCM4323X_TX_POWER_RATES];
+	u8 target[BCM4323X_TX_POWER_RATES];
+	u8 pad[2];
+} __packed;
+
+static_assert(sizeof(struct bcm4323x_tx_power_report) == 644);
+
+/* Procedure 0x43 */
+struct bcm4323x_tx_power_query {
+	__le32 channel;
+	struct bcm4323x_tx_power_report power;
+} __packed;
+
+/* from brcmsmac */
+#define RATE(rate100m, _flags) { \
+	.bitrate = (rate100m), \
+	.flags = (_flags), \
+	.hw_value = (rate100m / 5), \
+}
+
+/* from brcmsmac: legacy_ratetable  */
+struct ieee80211_rate bcm4323x_rates[BCM4323X_NUM_RATES] = {
+	RATE(10, 0),
+	RATE(20, IEEE80211_RATE_SHORT_PREAMBLE),
+	RATE(55, IEEE80211_RATE_SHORT_PREAMBLE),
+	RATE(110, IEEE80211_RATE_SHORT_PREAMBLE),
+	RATE(60, 0),
+	RATE(90, 0),
+	RATE(120, 0),
+	RATE(180, 0),
+	RATE(240, 0),
+	RATE(360, 0),
+	RATE(480, 0),
+	RATE(540, 0),
+};
+
+/* from brcmsmac, leaving the maximum power to the regulatory domain */
+#define CHAN2GHZ(channel, frequency, chflags)  { \
+	.band = NL80211_BAND_2GHZ, \
+	.center_freq = (frequency), \
+	.hw_value = (channel), \
+	.flags = chflags, \
+}
+
+#define CHAN5GHZ(channel, chflags)  { \
+	.band = NL80211_BAND_5GHZ, \
+	.center_freq = 5000 + 5 * (channel), \
+	.hw_value = (channel), \
+	.flags = chflags, \
+}
+
+/* from brcmsmac: brcms_2ghz_chantable */
+static const struct ieee80211_channel bcm4323x_2ghz_channels[BCM4323X_NUM_2GHZ_CHANNELS] = {
+	CHAN2GHZ(1, 2412, IEEE80211_CHAN_NO_HT40MINUS),
+	CHAN2GHZ(2, 2417, IEEE80211_CHAN_NO_HT40MINUS),
+	CHAN2GHZ(3, 2422, IEEE80211_CHAN_NO_HT40MINUS),
+	CHAN2GHZ(4, 2427, IEEE80211_CHAN_NO_HT40MINUS),
+	CHAN2GHZ(5, 2432, 0),
+	CHAN2GHZ(6, 2437, 0),
+	CHAN2GHZ(7, 2442, 0),
+	CHAN2GHZ(8, 2447, IEEE80211_CHAN_NO_HT40PLUS),
+	CHAN2GHZ(9, 2452, IEEE80211_CHAN_NO_HT40PLUS),
+	CHAN2GHZ(10, 2457, IEEE80211_CHAN_NO_HT40PLUS),
+	CHAN2GHZ(11, 2462, IEEE80211_CHAN_NO_HT40PLUS),
+	CHAN2GHZ(12, 2467,
+		 IEEE80211_CHAN_NO_IR |
+		 IEEE80211_CHAN_NO_HT40PLUS),
+	CHAN2GHZ(13, 2472,
+		 IEEE80211_CHAN_NO_IR |
+		 IEEE80211_CHAN_NO_HT40PLUS),
+	CHAN2GHZ(14, 2484,
+		 IEEE80211_CHAN_NO_IR |
+		 IEEE80211_CHAN_NO_HT40PLUS | IEEE80211_CHAN_NO_HT40MINUS |
+		 IEEE80211_CHAN_NO_OFDM)
+};
+
+/* from brcmsmac: brcms_5ghz_nphy_chantable */
+static const struct ieee80211_channel bcm4323x_5ghz_channels[BCM4323X_NUM_5GHZ_CHANNELS] = {
+	/* UNII-1 */
+	CHAN5GHZ(36, IEEE80211_CHAN_NO_HT40MINUS),
+	CHAN5GHZ(40, IEEE80211_CHAN_NO_HT40PLUS),
+	CHAN5GHZ(44, IEEE80211_CHAN_NO_HT40MINUS),
+	CHAN5GHZ(48, IEEE80211_CHAN_NO_HT40PLUS),
+	/* UNII-2 */
+	CHAN5GHZ(52,
+		 IEEE80211_CHAN_RADAR |
+		 IEEE80211_CHAN_NO_IR | IEEE80211_CHAN_NO_HT40MINUS),
+	CHAN5GHZ(56,
+		 IEEE80211_CHAN_RADAR |
+		 IEEE80211_CHAN_NO_IR | IEEE80211_CHAN_NO_HT40PLUS),
+	CHAN5GHZ(60,
+		 IEEE80211_CHAN_RADAR |
+		 IEEE80211_CHAN_NO_IR | IEEE80211_CHAN_NO_HT40MINUS),
+	CHAN5GHZ(64,
+		 IEEE80211_CHAN_RADAR |
+		 IEEE80211_CHAN_NO_IR | IEEE80211_CHAN_NO_HT40PLUS),
+	/* MID */
+	CHAN5GHZ(100,
+		 IEEE80211_CHAN_RADAR |
+		 IEEE80211_CHAN_NO_IR | IEEE80211_CHAN_NO_HT40MINUS),
+	CHAN5GHZ(104,
+		 IEEE80211_CHAN_RADAR |
+		 IEEE80211_CHAN_NO_IR | IEEE80211_CHAN_NO_HT40PLUS),
+	CHAN5GHZ(108,
+		 IEEE80211_CHAN_RADAR |
+		 IEEE80211_CHAN_NO_IR | IEEE80211_CHAN_NO_HT40MINUS),
+	CHAN5GHZ(112,
+		 IEEE80211_CHAN_RADAR |
+		 IEEE80211_CHAN_NO_IR | IEEE80211_CHAN_NO_HT40PLUS),
+	CHAN5GHZ(116,
+		 IEEE80211_CHAN_RADAR |
+		 IEEE80211_CHAN_NO_IR | IEEE80211_CHAN_NO_HT40MINUS),
+	CHAN5GHZ(120,
+		 IEEE80211_CHAN_RADAR |
+		 IEEE80211_CHAN_NO_IR | IEEE80211_CHAN_NO_HT40PLUS),
+	CHAN5GHZ(124,
+		 IEEE80211_CHAN_RADAR |
+		 IEEE80211_CHAN_NO_IR | IEEE80211_CHAN_NO_HT40MINUS),
+	CHAN5GHZ(128,
+		 IEEE80211_CHAN_RADAR |
+		 IEEE80211_CHAN_NO_IR | IEEE80211_CHAN_NO_HT40PLUS),
+	CHAN5GHZ(132,
+		 IEEE80211_CHAN_RADAR |
+		 IEEE80211_CHAN_NO_IR | IEEE80211_CHAN_NO_HT40MINUS),
+	CHAN5GHZ(136,
+		 IEEE80211_CHAN_RADAR |
+		 IEEE80211_CHAN_NO_IR | IEEE80211_CHAN_NO_HT40PLUS),
+	CHAN5GHZ(140,
+		 IEEE80211_CHAN_RADAR |
+		 IEEE80211_CHAN_NO_IR | IEEE80211_CHAN_NO_HT40PLUS |
+		 IEEE80211_CHAN_NO_HT40MINUS),
+	/* UNII-3 */
+	CHAN5GHZ(149, IEEE80211_CHAN_NO_HT40MINUS),
+	CHAN5GHZ(153, IEEE80211_CHAN_NO_HT40PLUS),
+	CHAN5GHZ(157, IEEE80211_CHAN_NO_HT40MINUS),
+	CHAN5GHZ(161, IEEE80211_CHAN_NO_HT40PLUS),
+	CHAN5GHZ(165, IEEE80211_CHAN_NO_HT40PLUS | IEEE80211_CHAN_NO_HT40MINUS)
+};
+
+/* From USB captures of the windows driver */
+static void bcm4323x_channel_data(const struct bcm4323x_chip *chip,
+				  struct bcm4323x_channel_data *data, u16 chanspec)
+{
+	unsigned int channel = CHSPEC_CHANNEL(chanspec);
+	u8 *values = data->values;
+	u8 value;
+	u8 last;
+
+	memset(data, 0, sizeof(*data));
+	data->chanspec = cpu_to_le32(chanspec);
+	if (channel > 14) {
+		memset(values + 4, channel < 149 ? 0x38 : 0x44, 24);
+		memset(values + 28, channel < 149 ? 0x28 : 0x3c, 24);
+		memset(values + 52, channel < 149 ? 0x30 : 0x3c, 49);
+		return;
+	}
+	if (chip->channel_data_2ghz[0]) {
+		memset(values, chip->channel_data_2ghz[0], 28);
+		memset(values + 28, chip->channel_data_2ghz[1], 24);
+		memset(values + 52, chip->channel_data_2ghz[2], 49);
+		return;
+	}
+	memset(values, 0x4c, 28);
+
+	switch (channel) {
+	case 12:
+	case 13:
+	case 14:
+	case 11:
+		memset(values + 4, 0x42, 24);
+		fallthrough;
+	case 1:
+		memset(values + 28, 0x38, 24);
+		return;
+	case 2:
+	case 10:
+		memset(values + 28, 0x40, 24);
+		return;
+	case 3:
+		value = 0x34;
+		break;
+	case 9:
+		value = 0x30;
+		break;
+	default:
+		value = 0x38;
+		break;
+	}
+	last = value + (channel == 8 ? 6 : 10);
+	memset(values + 28, 0x4c, 24);
+	memset(values + 52, last, 8);
+	memset(values + 60, value, 6);
+	memset(values + 66, value + 4, 2);
+	memset(values + 68, last, 8);
+	memset(values + 76, value, 5);
+	memset(values + 81, value + 4, 3);
+	memset(values + 84, value, 5);
+	memset(values + 89, value + 4, 7);
+	memset(values + 96, value + 6, 4);
+	values[100] = last;
+}
+
+/* from brcmsmac: brcms_c_stf_ss_algo_channel_get */
+static int bcm4323x_stf_ss_algo_channel_get(struct bcm4323x *dev, u16 *ss_algo_channel,
+					    u16 chanspec)
+{
+	struct bcm4323x_tx_power_query *query;
+	struct bcm4323x_tx_power_report *power;
+	u8 siso_mcs_id, cdd_mcs_id, stbc_mcs_id;
+	int ret;
+
+	/* Clear previous settings */
+	*ss_algo_channel = 0;
+
+	/* The response overwrites the query only after it is sent. */
+	query = kzalloc_obj(*query);
+	if (!query)
+		return -ENOMEM;
+	query->channel = cpu_to_le32(CHSPEC_CHANNEL(chanspec));
+	power = &query->power;
+	ret = bcm4323x_rpc_call(dev, BCM4323X_RPC_PROC_TX_POWER_REPORT, query, sizeof(*query),
+				power, sizeof(*power));
+	if (ret)
+		goto out;
+
+	siso_mcs_id = (CHSPEC_IS40(chanspec)) ?
+	    WL_TX_POWER_MCS40_SISO_FIRST : WL_TX_POWER_MCS20_SISO_FIRST;
+	cdd_mcs_id = (CHSPEC_IS40(chanspec)) ?
+	    WL_TX_POWER_MCS40_CDD_FIRST : WL_TX_POWER_MCS20_CDD_FIRST;
+	stbc_mcs_id = (CHSPEC_IS40(chanspec)) ?
+	    WL_TX_POWER_MCS40_STBC_FIRST : WL_TX_POWER_MCS20_STBC_FIRST;
+
+	/* criteria to choose stf mode */
+
+	/*
+	 * the "+3dbm (12 0.25db units)" is to account for the fact that with
+	 * CDD, tx occurs on both chains
+	 */
+	if (power->target[siso_mcs_id] > (power->target[cdd_mcs_id] + 12))
+		setbit(ss_algo_channel, PHY_TXC1_MODE_SISO);
+	else
+		setbit(ss_algo_channel, PHY_TXC1_MODE_CDD);
+
+	/*
+	 * STBC is ORed into to algo channel as STBC requires per-packet SCB
+	 * capability check so cannot be default mode of operation. One of
+	 * SISO, CDD have to be set
+	 */
+	if (power->target[siso_mcs_id] <= (power->target[stbc_mcs_id] + 12))
+		setbit(ss_algo_channel, PHY_TXC1_MODE_STBC);
+out:
+	kfree(query);
+	return ret;
+}
+
+/* from brcmsmac: brcms_c_stf_ss_update */
+static int bcm4323x_stf_ss_update(struct bcm4323x *dev, u16 ss_algo_channel)
+{
+	u8 upd_stf_ss = bcm4323x_stf_mode(ss_algo_channel);
+	int ret = 0;
+
+	/* Only the BCM4323 trace sets a mode, so the first selection always does. */
+	if (!dev->ss_algo_channel || bcm4323x_stf_mode(dev->ss_algo_channel) != upd_stf_ss)
+		ret = bcm4323x_rpc_send_u32(dev, BCM4323X_RPC_PROC_STF_MODE, upd_stf_ss);
+	if (!ret)
+		WRITE_ONCE(dev->ss_algo_channel, ss_algo_channel);
+	return ret;
+}
+
+/* Procedure 0x33: brcms_b_write_shm() */
+int bcm4323x_write_shm(struct bcm4323x *dev, u32 offset, u16 value)
+{
+	return bcm4323x_rpc_send_u32(dev, BCM4323X_RPC_PROC_SHM_WRITE, offset, value);
+}
+
+/* Procedure 0x2c: brcms_b_read_shm() */
+int bcm4323x_read_shm(struct bcm4323x *dev, u32 offset, u16 *value)
+{
+	__le32 response;
+	int ret;
+
+	ret = bcm4323x_rpc_call_u32(dev, BCM4323X_RPC_PROC_SHM_READ, &response, offset);
+	if (!ret)
+		*value = le32_to_cpu(response);
+	return ret;
+}
+
+/* Procedure 0x2a: brcms_b_mctrl() */
+static int bcm4323x_set_mac_control(struct bcm4323x *dev, u32 mask, u32 value)
+{
+	return bcm4323x_rpc_send_u32(dev, BCM4323X_RPC_PROC_MAC_CONTROL, mask, value);
+}
+
+static int bcm4323x_read_reg(struct bcm4323x *dev, u32 offset, u32 size, u32 *value)
+{
+	__le32 response;
+	int ret;
+
+	ret = bcm4323x_rpc_call_u32(dev, BCM4323X_RPC_PROC_REG_READ, &response, offset, size);
+	if (!ret)
+		*value = le32_to_cpu(response);
+	return ret;
+}
+
+/* Procedure 0x02: bcma_write16() or bcma_write32() by @size */
+static int bcm4323x_write_reg(struct bcm4323x *dev, u32 offset, u32 value, u32 size)
+{
+	return bcm4323x_rpc_send_u32(dev, BCM4323X_RPC_PROC_REG_WRITE, offset, value, size);
+}
+
+/* Procedure 0x03: brcms_b_mhf() */
+static int bcm4323x_mhf(struct bcm4323x *dev, u8 idx, u16 mask, u16 val, int bands)
+{
+	return bcm4323x_rpc_send_u32(dev, BCM4323X_RPC_PROC_HOST_FLAGS, idx, mask, val, bands);
+}
+
+static void bcm4323x_warn(struct bcm4323x *dev, int ret, const char *what)
+{
+	if (ret && ret != -ENODEV)
+		dev_warn(&dev->intf->dev, "failed to %s: %d\n", what, ret);
+}
+
+static int bcm4323x_tune(struct bcm4323x *dev, u16 chanspec)
+{
+	struct bcm4323x_channel_data body;
+	u16 ss_algo_channel;
+	int ret;
+
+	ret = bcm4323x_rpc_send(dev, BCM4323X_RPC_PROC_CHANNEL_PREP, NULL, 0);
+	if (ret)
+		return ret;
+	bcm4323x_channel_data(dev->chip, &body, chanspec);
+	ret = bcm4323x_rpc_send(dev, BCM4323X_RPC_PROC_CHANNEL_DATA, &body, sizeof(body));
+	if (ret)
+		return ret;
+	ret = bcm4323x_stf_ss_algo_channel_get(dev, &ss_algo_channel, chanspec);
+	if (ret)
+		return ret;
+	ret = bcm4323x_stf_ss_update(dev, ss_algo_channel);
+	if (ret)
+		return ret;
+	return bcm4323x_write_shm(dev, BCM4323X_SHM_TUNE, BCM4323X_SHM_TUNE_VALUE);
+}
+
+/* from brcmsmac: brcms_c_set_channel */
+static int bcm4323x_set_channel(struct bcm4323x *dev, u16 chanspec)
+{
+	int enable_ret;
+	int ret;
+
+	if (chanspec == dev->chanspec)
+		return 0;
+	/* A chanspec of 0 has no band, so the first switch also counts. */
+	if (CHSPEC_BAND(chanspec) != CHSPEC_BAND(dev->chanspec))
+		dev->band_switched = true;
+	WRITE_ONCE(dev->chanspec, 0);
+
+	ret = bcm4323x_set_mac_control(dev, MCTL_EN_MAC, 0);
+	if (ret)
+		return ret;
+	ret = bcm4323x_tune(dev, chanspec);
+	enable_ret = bcm4323x_set_mac_control(dev, MCTL_EN_MAC, MCTL_EN_MAC);
+	ret = ret ?: enable_ret;
+	if (!ret)
+		WRITE_ONCE(dev->chanspec, chanspec);
+	return ret;
+}
+
+/* @offset is in bytes: brcmsmac's brcms_b_write_objmem() selects word @offset / 4. */
+static int bcm4323x_copy_to_objmem(struct bcm4323x *dev, u32 offset, const void *buf, u32 length,
+				   u32 objsel)
+{
+	struct bcm4323x_shm_copy body = {
+		.offset = cpu_to_le32(offset),
+		.length = cpu_to_le32(length),
+	};
+	__le32 sel = cpu_to_le32(objsel);
+
+	memcpy(body.data, buf, length);
+	memcpy(body.data + length, &sel, sizeof(sel));
+	return bcm4323x_rpc_send(dev, BCM4323X_RPC_PROC_SHM_COPY, &body,
+				 offsetof(struct bcm4323x_shm_copy, data) + length + sizeof(sel));
+}
+
+static int bcm4323x_copy_to_shm(struct bcm4323x *dev, u32 offset, const void *buf, u32 length)
+{
+	return bcm4323x_copy_to_objmem(dev, offset, buf, length, OBJADDR_SHM_SEL);
+}
+
+/*
+ * from brcmsmac: brcms_c_set_rate_limit, brcms_b_retrylimit_upd and
+ * brcms_c_wme_retries_write
+ */
+static int bcm4323x_set_retry_limits(struct bcm4323x *dev, u8 srl, u8 lrl)
+{
+	__le16 limit;
+	u16 txlmt;
+	int ac;
+	int ret;
+
+	srl = min_t(u8, srl, FIELD_MAX(EDCF_SHORT));
+	lrl = min_t(u8, lrl, FIELD_MAX(EDCF_LONG));
+	limit = cpu_to_le16(srl);
+	ret = bcm4323x_copy_to_objmem(dev, S_DOT11_SRC_LMT * sizeof(u32), &limit, sizeof(limit),
+				      OBJADDR_SCR_SEL);
+	if (ret)
+		return ret;
+	limit = cpu_to_le16(lrl);
+	ret = bcm4323x_copy_to_objmem(dev, S_DOT11_LRC_LMT * sizeof(u32), &limit, sizeof(limit),
+				      OBJADDR_SCR_SEL);
+	if (ret)
+		return ret;
+	txlmt = FIELD_PREP(EDCF_SHORT, srl) | FIELD_PREP(EDCF_SFB, RETRY_SHORT_FB) |
+		FIELD_PREP(EDCF_LONG, lrl) | FIELD_PREP(EDCF_LFB, RETRY_LONG_FB);
+	for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) {
+		ret = bcm4323x_write_shm(dev, M_AC_TXLMT_ADDR(ac), txlmt);
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+
+static int bcm4323x_set_slot_address(struct bcm4323x *dev, u32 procedure, u32 slot,
+				     const u8 *addr)
+{
+	struct bcm4323x_slot_address body = { .slot = cpu_to_le32(slot) };
+
+	if (addr)
+		ether_addr_copy(body.addr, addr);
+	return bcm4323x_rpc_send(dev, procedure, &body, sizeof(body));
+}
+
+/* from brcmsmac: brcms_c_ampdu_macaddr_upd */
+static int bcm4323x_ampdu_macaddr_upd(struct bcm4323x *dev, const u8 *addr)
+{
+	/* the transmitter address is at offset 16 */
+	struct bcm4323x_ba_template body = {
+		.offset = cpu_to_le32(T_BA_TPL_BASE + 16),
+		.length = cpu_to_le32(sizeof(body.data)),
+	};
+
+	ether_addr_copy(body.data, addr);
+	return bcm4323x_rpc_send(dev, BCM4323X_RPC_PROC_TEMPLATE, &body, sizeof(body));
+}
+
+static int bcm4323x_radio_sync(struct bcm4323x *dev)
+{
+	return bcm4323x_rpc_send(dev, BCM4323X_RPC_PROC_RADIO_SYNC, NULL, 0);
+}
+
+/* Procedure 0x41: brcms_b_set_shortslot() */
+static int bcm4323x_set_shortslot(struct bcm4323x *dev, bool shortslot)
+{
+	return bcm4323x_rpc_send_u32(dev, BCM4323X_RPC_PROC_SHORT_SLOT, shortslot);
+}
+
+/* from brcmsmac: brcms_c_write_rate_shm */
+static int bcm4323x_write_rate_shm(struct bcm4323x *dev, u8 rate, u8 basic_rate)
+{
+	u8 phy_rate, index;
+	u8 basic_phy_rate, basic_index;
+	u16 dir_table, basic_table;
+	u16 basic_ptr;
+	int ret;
+
+	/* Shared memory address for the table we are reading */
+	dir_table = is_ofdm_rate(basic_rate) ? M_RT_DIRMAP_A : M_RT_DIRMAP_B;
+
+	/* Shared memory address for the table we are writing */
+	basic_table = is_ofdm_rate(rate) ? M_RT_BBRSMAP_A : M_RT_BBRSMAP_B;
+
+	/*
+	 * for a given rate, the LS-nibble of the PLCP SIGNAL field is
+	 * the index into the rate table.
+	 */
+	phy_rate = brcmu_rate_info[rate] & BRCMS_RATE_MASK;
+	basic_phy_rate = brcmu_rate_info[basic_rate] & BRCMS_RATE_MASK;
+	index = phy_rate & 0xf;
+	basic_index = basic_phy_rate & 0xf;
+
+	/* Find the SHM pointer to the ACK rate entry by looking in the
+	 * Direct-map Table
+	 */
+	ret = bcm4323x_read_shm(dev, dir_table + basic_index * 2, &basic_ptr);
+	if (ret)
+		return ret;
+
+	/* Update the SHM BSS-basic-rate-set mapping table with the pointer
+	 * to the correct basic rate for the given incoming rate
+	 */
+	return bcm4323x_write_shm(dev, basic_table + index * 2, basic_ptr);
+}
+
+/* from brcmsmac: brcms_c_bsinit and brcms_c_set_ratetable */
+static int bcm4323x_set_ratetable(struct bcm4323x *dev, enum nl80211_band band, u32 basic_rates)
+{
+	u8 br[BRCM_MAXRATE + 1];
+	u8 rate, basic_rate;
+	uint i;
+	int ret = 0;
+
+	bcm4323x_init_basic_rate(br, band, basic_rates);
+	/* walk the phy rate table and update SHM basic rate lookup table */
+	for (i = bcm4323x_first_rate(band); i < BCM4323X_NUM_RATES && !ret; i++) {
+		rate = bcm4323x_rates[i].hw_value;
+		basic_rate = brcmu_basic_rate(br, rate);
+		ret = bcm4323x_write_rate_shm(dev, rate, basic_rate);
+	}
+	return ret;
+}
+
+/* from brcmsmac: brcms_c_bsinit */
+static int bcm4323x_bsinit(struct bcm4323x *dev)
+{
+	struct ieee80211_vif *vif = wiphy_dereference(dev->hw->wiphy, dev->active_vif);
+	const struct ieee80211_channel *bss_chan = vif ? vif->bss_conf.chanreq.oper.chan : NULL;
+	enum nl80211_band band = dev->hw->conf.chandef.chan->band;
+	int ret;
+
+	if (!dev->band_switched || dev->scanning)
+		return 0;
+	dev->band_switched = false;
+	if (!bss_chan || bss_chan->band != band || !vif->cfg.assoc)
+		return 0;
+	ret = bcm4323x_set_ratetable(dev, band, vif->bss_conf.basic_rates);
+	if (ret)
+		return ret;
+	return bcm4323x_set_shortslot(dev, band == NL80211_BAND_5GHZ ||
+					   vif->bss_conf.use_short_slot);
+}
+
+/* Procedure 0x22, wlc_phy_txpower_set() */
+static int bcm4323x_write_tx_power(struct bcm4323x *dev, int qdbm)
+{
+	int ret;
+
+	ret = bcm4323x_radio_sync(dev);
+	if (ret)
+		return ret;
+	ret = bcm4323x_rpc_call_u32(dev, BCM4323X_RPC_PROC_TX_POWER, NULL, qdbm, 0);
+	if (!ret)
+		dev->tx_power = qdbm;
+	return ret;
+}
+
+static int bcm4323x_qdbm(int dbm)
+{
+	return clamp(dbm * BRCMS_TXPWR_DB_FACTOR, 0, BRCMS_TXPWR_MAX);
+}
+
+/* from brcmsmac: brcms_c_set_tx_power */
+static int bcm4323x_set_tx_power(struct bcm4323x *dev, int qdbm)
+{
+	return qdbm == dev->tx_power ? 0 : bcm4323x_write_tx_power(dev, qdbm);
+}
+
+/* from brcmsmac: brcms_c_mac_promisc */
+static u32 bcm4323x_mac_promisc(uint filter_flags)
+{
+	u32 promisc_bits = 0;
+
+	if (filter_flags & FIF_OTHER_BSS)
+		promisc_bits |= MCTL_PROMISC;
+
+	if (filter_flags & FIF_BCN_PRBRESP_PROMISC)
+		promisc_bits |= MCTL_BCNS_PROMISC;
+
+	if (filter_flags & FIF_FCSFAIL)
+		promisc_bits |= MCTL_KEEPBADFCS;
+
+	if (filter_flags & (FIF_CONTROL | FIF_PSPOLL))
+		promisc_bits |= MCTL_KEEPCONTROL;
+
+	return promisc_bits;
+}
+
+static int bcm4323x_set_maccontrol(struct bcm4323x *dev)
+{
+	u32 maccontrol = MCTL_DISCARD_PMQ | MCTL_INFRA | MCTL_WAKE |
+			 bcm4323x_mac_promisc(dev->filter_flags);
+
+	return bcm4323x_set_mac_control(dev, BCM4323X_MCTL_RECEIVE, maccontrol);
+}
+
+static int bcm4323x_set_receive(struct bcm4323x *dev)
+{
+	struct ieee80211_vif *vif = wiphy_dereference(dev->hw->wiphy, dev->active_vif);
+	const u8 *addr;
+	int ret;
+
+	ret = bcm4323x_set_slot_address(dev, BCM4323X_RPC_PROC_RX_ADDRESS, RCM_BSSID_OFFSET,
+					vif && !dev->scanning ? vif->bss_conf.bssid : NULL);
+	if (ret)
+		return ret;
+	addr = vif && vif->addr_valid ? vif->addr : NULL;
+	ret = bcm4323x_set_slot_address(dev, BCM4323X_RPC_PROC_RX_ADDRESS, RCM_MAC_OFFSET, addr);
+	if (!ret && addr)
+		ret = bcm4323x_ampdu_macaddr_upd(dev, addr);
+	if (ret)
+		return ret;
+	ret = bcm4323x_set_maccontrol(dev);
+	if (ret)
+		return ret;
+	return bcm4323x_radio_sync(dev);
+}
+
+/* The device is lost once firmware misses several watchdog calls in a row. */
+static void bcm4323x_watchdog_rpc(struct bcm4323x *dev)
+{
+	int ret = bcm4323x_rpc_call(dev, BCM4323X_RPC_PROC_WATCHDOG, NULL, 0, NULL, 0);
+
+	if (!ret) {
+		dev->watchdog_failures = 0;
+	} else if (ret != -ENODEV && ++dev->watchdog_failures >= BCM4323X_WATCHDOG_FAILURES) {
+		dev_err(&dev->intf->dev, "firmware stopped answering: %d\n", ret);
+		bcm4323x_fatal_error(dev);
+	} else {
+		dev_dbg_ratelimited(&dev->intf->dev, "watchdog failed: %d\n", ret);
+	}
+}
+
+/*
+ * Procedure 0x20: wlc_phy_cal_perical(). The firmware calibrates on its own
+ * about every 123s, and sending aggregates during the calibration will crash
+ * it. Avoid this by explicitly calibrating and stopping TX while doing so.
+ */
+static bool bcm4323x_phy_cal(struct bcm4323x *dev)
+{
+	dev->phy_cal_expires = jiffies + BCM4323X_PHY_CAL_INTERVAL;
+	if (!bcm4323x_quiesce_tx(dev))
+		return false;
+	bcm4323x_rpc_send_u32(dev, BCM4323X_RPC_PROC_PHY_CAL, PHY_PERICAL_JOIN_BSS);
+	return true;
+}
+
+static void bcm4323x_watchdog_worker(struct work_struct *work)
+{
+	struct bcm4323x *dev = container_of(to_delayed_work(work), struct bcm4323x, watchdog_work);
+	bool paused;
+
+	if (READ_ONCE(dev->dead))
+		return;
+	bcm4323x_check_tx(dev);
+	if (!READ_ONCE(dev->scanning)) {
+		paused = time_after_eq(jiffies, dev->phy_cal_expires);
+		if (paused && !bcm4323x_phy_cal(dev))
+			dev_warn(&dev->intf->dev, "TX not drained for the PHY calibration\n");
+		/* answered after the calibration */
+		bcm4323x_watchdog_rpc(dev);
+		if (paused)
+			bcm4323x_set_tx_paused(dev, false);
+	}
+	queue_delayed_work(system_dfl_wq, &dev->watchdog_work, BCM4323X_WATCHDOG_INTERVAL);
+}
+
+static int bcm4323x_start(struct ieee80211_hw *hw)
+{
+	struct bcm4323x *dev = hw->priv;
+	int ret;
+
+	ret = bcm4323x_set_receive(dev);
+	if (ret)
+		return ret;
+	ret = bcm4323x_set_retry_limits(dev, hw->conf.short_frame_max_tx_count,
+					hw->conf.long_frame_max_tx_count);
+	if (ret)
+		return ret;
+	WRITE_ONCE(dev->running, true);
+	ret = bcm4323x_start_rx_urbs(dev);
+	if (ret) {
+		WRITE_ONCE(dev->running, false);
+		return ret;
+	}
+	dev->watchdog_failures = 0;
+	dev->phy_cal_expires = jiffies;
+	queue_delayed_work(system_dfl_wq, &dev->watchdog_work, BCM4323X_WATCHDOG_INTERVAL);
+	return 0;
+}
+
+static void bcm4323x_stop(struct ieee80211_hw *hw, bool suspend)
+{
+	struct bcm4323x *dev = hw->priv;
+
+	WRITE_ONCE(dev->running, false);
+	cancel_delayed_work_sync(&dev->watchdog_work);
+	bcm4323x_kill_rx_urbs(dev);
+	bcm4323x_flush_tx(dev);
+}
+
+static int bcm4323x_add_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
+{
+	struct bcm4323x *dev = hw->priv;
+	int ret;
+
+	if (rcu_access_pointer(dev->active_vif))
+		return -EBUSY;
+	if (vif->txq)
+		bcm4323x_init_txq(vif->txq);
+	rcu_assign_pointer(dev->active_vif, vif);
+	ret = bcm4323x_set_receive(dev);
+	if (ret)
+		RCU_INIT_POINTER(dev->active_vif, NULL);
+	return ret;
+}
+
+static void bcm4323x_remove_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
+{
+	struct bcm4323x *dev = hw->priv;
+
+	RCU_INIT_POINTER(dev->active_vif, NULL);
+}
+
+static u16 bcm4323x_chanspec(const struct cfg80211_chan_def *chandef)
+{
+	unsigned int channel = chandef->chan->hw_value;
+	u16 chanspec = ch20mhz_chspec(channel);
+	unsigned int center;
+
+	if (chandef->width != NL80211_CHAN_WIDTH_40)
+		return chanspec;
+	center = ieee80211_frequency_to_channel(chandef->center_freq1);
+	return CHSPEC_BAND(chanspec) | WL_CHANSPEC_BW_40 | center |
+	       (channel < center ? WL_CHANSPEC_CTL_SB_LOWER : WL_CHANSPEC_CTL_SB_UPPER);
+}
+
+/* Switches to the configured channel, with the TX power @qdbm. */
+static int bcm4323x_config_channel(struct bcm4323x *dev, int qdbm)
+{
+	const struct cfg80211_chan_def *chandef = &dev->hw->conf.chandef;
+	int ret;
+
+	ret = bcm4323x_set_channel(dev, bcm4323x_chanspec(chandef));
+	if (ret)
+		return ret;
+	ret = bcm4323x_set_receive(dev);
+	if (ret)
+		return ret;
+	ret = bcm4323x_bsinit(dev);
+	if (ret)
+		return ret;
+	/*
+	 * After a switch, the receiver stays deaf until the device transmits or
+	 * the power is set again
+	 */
+	ret = qdbm < 0 ? bcm4323x_radio_sync(dev) : bcm4323x_write_tx_power(dev, qdbm);
+	if (ret || !dev->scanning)
+		return ret;
+
+	return bcm4323x_rpc_send(dev, BCM4323X_RPC_PROC_NOISE, NULL, 0);
+}
+
+static int bcm4323x_config(struct ieee80211_hw *hw, int radio_idx, u32 changed)
+{
+	struct bcm4323x *dev = hw->priv;
+	int qdbm = dev->tx_power;
+	int ret;
+
+	if (changed & IEEE80211_CONF_CHANGE_RETRY_LIMITS) {
+		ret = bcm4323x_set_retry_limits(dev, hw->conf.short_frame_max_tx_count,
+						hw->conf.long_frame_max_tx_count);
+		if (ret)
+			return ret;
+	}
+	if (changed & IEEE80211_CONF_CHANGE_POWER)
+		qdbm = bcm4323x_qdbm(hw->conf.power_level);
+	if (changed & IEEE80211_CONF_CHANGE_CHANNEL)
+		return bcm4323x_config_channel(dev, qdbm);
+	if (changed & IEEE80211_CONF_CHANGE_POWER)
+		return bcm4323x_set_tx_power(dev, qdbm);
+	return 0;
+}
+
+/* from brcmsmac: brcms_c_get_tx_power */
+static int bcm4323x_get_txpower(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+				unsigned int link_id, int *dbm)
+{
+	struct bcm4323x *dev = hw->priv;
+
+	if (dev->tx_power < 0)
+		return -EINVAL;
+	*dbm = dev->tx_power / BRCMS_TXPWR_DB_FACTOR;
+	return 0;
+}
+
+static void bcm4323x_sw_scan_start(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+				   const u8 *mac_addr)
+{
+	struct bcm4323x *dev = hw->priv;
+
+	WRITE_ONCE(dev->scanning, true);
+}
+
+static void bcm4323x_sw_scan_complete(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
+{
+	struct bcm4323x *dev = hw->priv;
+	int ret;
+
+	WRITE_ONCE(dev->scanning, false);
+	ret = bcm4323x_set_receive(dev);
+	if (!ret)
+		ret = bcm4323x_bsinit(dev);
+	bcm4323x_warn(dev, ret, "leave scan mode cleanly");
+}
+
+static void bcm4323x_bss_info_changed(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+				      struct ieee80211_bss_conf *info, u64 changed)
+{
+	struct bcm4323x *dev = hw->priv;
+	const struct ieee80211_channel *chan = info->chanreq.oper.chan ?: hw->conf.chandef.chan;
+
+	if (changed & BSS_CHANGED_BSSID)
+		bcm4323x_warn(dev, bcm4323x_set_receive(dev), "program receive addresses");
+	if (changed & BSS_CHANGED_BASIC_RATES)
+		bcm4323x_warn(dev, bcm4323x_set_ratetable(dev, chan->band, info->basic_rates),
+			      "set the response rates");
+	if (changed & BSS_CHANGED_ERP_SLOT)
+		bcm4323x_warn(dev, bcm4323x_set_shortslot(dev, info->use_short_slot),
+			      "set the slot time");
+	if ((changed & BSS_CHANGED_TXPOWER) && info->txpower != INT_MIN)
+		bcm4323x_warn(dev, bcm4323x_set_tx_power(dev, bcm4323x_qdbm(info->txpower)),
+			      "set the TX power");
+}
+
+/* from brcmsmac: brcms_c_wme_setparams */
+static int bcm4323x_wme_setparams(struct bcm4323x *dev, u16 aci,
+				  const struct ieee80211_tx_queue_params *params)
+{
+	u32 base = M_EDCF_QINFO + bcm4323x_ac_to_fifo(aci) * M_EDCF_QLEN;
+	struct shm_acparams acp_shm = {};
+	__le16 words[sizeof(acp_shm) / sizeof(u16)];
+	u16 *shm_entry;
+	unsigned int i;
+
+	/* convert from units of 32us to us for ucode */
+	acp_shm.txop = min_t(u32, EDCF_TXOP2USEC(params->txop), U16_MAX);
+	acp_shm.aifs = (params->aifs & EDCF_AIFSN_MASK);
+
+	if (aci == IEEE80211_AC_VI && acp_shm.txop == 0 &&
+	    acp_shm.aifs < EDCF_AIFSN_MAX)
+		acp_shm.aifs++;
+
+	acp_shm.cwmin = params->cw_min;
+	acp_shm.cwmax = params->cw_max;
+	acp_shm.cwcur = acp_shm.cwmin;
+
+	/* Unlike brcmsmac, take the random backoff from the kernel, not from tsf_random. */
+	acp_shm.bslots = get_random_u16() & acp_shm.cwcur;
+	acp_shm.reggap = acp_shm.bslots + acp_shm.aifs;
+	/*
+	 * Indicate the new params to the ucode, without reading the status word
+	 * first as brcmsmac does: it holds nothing else.
+	 */
+	acp_shm.status = WME_STATUS_NEWAC;
+
+	/* Fill in shm acparam table */
+	shm_entry = (u16 *)&acp_shm;
+	for (i = 0; i < ARRAY_SIZE(words); i++)
+		words[i] = cpu_to_le16(shm_entry[i]);
+	return bcm4323x_copy_to_shm(dev, base, words, sizeof(words));
+}
+
+static int bcm4323x_conf_tx(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+			    unsigned int link_id, u16 aci,
+			    const struct ieee80211_tx_queue_params *params)
+{
+	return bcm4323x_wme_setparams(hw->priv, aci, params);
+}
+
+/* from brcmsmac: brcms_ops_configure_filter */
+static void bcm4323x_configure_filter(struct ieee80211_hw *hw, unsigned int changed_flags,
+				      unsigned int *total_flags, u64 multicast)
+{
+	struct bcm4323x *dev = hw->priv;
+
+	*total_flags &= MAC_FILTERS;
+	dev->filter_flags = *total_flags;
+	if (changed_flags & MAC_FILTERS)
+		bcm4323x_warn(dev, bcm4323x_set_maccontrol(dev), "configure receive filter");
+}
+
+static int bcm4323x_program_key(struct bcm4323x *dev, u8 index, const u8 *peer,
+				const struct ieee80211_key_conf *key)
+{
+	u8 material[WLAN_KEY_LEN_CCMP] = {};
+	bool pairwise = index >= BCM4323X_HW_PAIRWISE_BASE;
+	u8 slot = index - BCM4323X_HW_PAIRWISE_BASE;
+	int ret = 0;
+
+	if (key)
+		memcpy(material, key->key, sizeof(material));
+	if (pairwise)
+		ret = bcm4323x_set_slot_address(dev, BCM4323X_RPC_PROC_KEY_PEER, slot, NULL);
+	if (!ret)
+		ret = bcm4323x_write_shm(dev, M_SECKINDXALGO_BLK + index * sizeof(u16),
+					 key ? FIELD_PREP(SKL_INDEX_MASK, index) |
+					       FIELD_PREP(SKL_ALGO_MASK, WSEC_ALGO_AES) : 0);
+	if (!ret)
+		ret = bcm4323x_copy_to_shm(dev, BCM4323X_KEY_TABLE_BASE + index * sizeof(material),
+					   material, sizeof(material));
+	if (!ret && pairwise && key)
+		ret = bcm4323x_set_slot_address(dev, BCM4323X_RPC_PROC_KEY_PEER, slot, peer);
+	return ret;
+}
+
+static int bcm4323x_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd,
+			    struct ieee80211_vif *vif, struct ieee80211_sta *sta,
+			    struct ieee80211_key_conf *key)
+{
+	struct bcm4323x *dev = hw->priv;
+	bool pairwise = key->flags & IEEE80211_KEY_FLAG_PAIRWISE;
+	unsigned int index;
+	int ret;
+
+	/* TODO: WEP and TKIP */
+	if (key->cipher != WLAN_CIPHER_SUITE_CCMP)
+		return -EOPNOTSUPP;
+	if (!pairwise && key->keyidx >= BCM4323X_HW_PAIRWISE_BASE)
+		return -ENOSPC;
+
+	switch (cmd) {
+	case SET_KEY:
+		if (pairwise) {
+			/* The wiphy mutex serializes the calls, so a free slot stays free. */
+			index = find_next_zero_bit(dev->keys, BCM4323X_HW_KEYS,
+						   BCM4323X_HW_PAIRWISE_BASE);
+			if (index == BCM4323X_HW_KEYS)
+				return -ENOSPC;
+			ether_addr_copy(dev->key_peers[index], sta->addr);
+		} else {
+			index = key->keyidx;
+		}
+		ret = bcm4323x_program_key(dev, index, pairwise ? sta->addr : NULL, key);
+		if (ret) {
+			bcm4323x_program_key(dev, index, NULL, NULL);
+			return ret;
+		}
+
+		key->hw_key_idx = index;
+		key->flags |= IEEE80211_KEY_FLAG_GENERATE_IV;
+		key->flags |= IEEE80211_KEY_FLAG_SW_MGMT_TX;
+		/* Pairs with test_bit_acquire() in bcm4323x_rx_key_match(). */
+		smp_mb__before_atomic();
+		set_bit(index, dev->keys);
+		return 0;
+	case DISABLE_KEY:
+		bcm4323x_retire_key(dev, key->hw_key_idx, sta);
+		bcm4323x_program_key(dev, key->hw_key_idx, NULL, NULL);
+		return 0;
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+/* Only the channels scanned so far have a noise measurement. */
+static int bcm4323x_get_survey(struct ieee80211_hw *hw, int idx, struct survey_info *survey)
+{
+	struct bcm4323x *dev = hw->priv;
+	s8 noise;
+
+	if (idx >= BCM4323X_NUM_2GHZ_CHANNELS + dev->bands[NL80211_BAND_5GHZ].n_channels)
+		return -ENOENT;
+
+	survey->channel = &dev->channels[idx];
+	survey->filled = 0;
+
+	if (survey->channel == hw->conf.chandef.chan)
+		survey->filled |= SURVEY_INFO_IN_USE;
+
+	noise = READ_ONCE(dev->noise[idx]);
+	if (noise) {
+		survey->noise = noise;
+		survey->filled |= SURVEY_INFO_NOISE_DBM;
+	}
+	return 0;
+}
+
+const struct ieee80211_ops bcm4323x_ops = {
+	.tx = bcm4323x_tx,
+	.start = bcm4323x_start,
+	.stop = bcm4323x_stop,
+	.add_interface = bcm4323x_add_interface,
+	.remove_interface = bcm4323x_remove_interface,
+	.config = bcm4323x_config,
+	.bss_info_changed = bcm4323x_bss_info_changed,
+	.configure_filter = bcm4323x_configure_filter,
+	.set_key = bcm4323x_set_key,
+	.sw_scan_start = bcm4323x_sw_scan_start,
+	.sw_scan_complete = bcm4323x_sw_scan_complete,
+	.sta_state = bcm4323x_sta_state,
+	.conf_tx = bcm4323x_conf_tx,
+	.ampdu_action = bcm4323x_ampdu_action,
+	.get_survey = bcm4323x_get_survey,
+	.flush = bcm4323x_flush,
+	.add_chanctx = ieee80211_emulate_add_chanctx,
+	.remove_chanctx = ieee80211_emulate_remove_chanctx,
+	.change_chanctx = ieee80211_emulate_change_chanctx,
+	.switch_vif_chanctx = ieee80211_emulate_switch_vif_chanctx,
+	.get_txpower = bcm4323x_get_txpower,
+	.wake_tx_queue = bcm4323x_wake_tx_queue,
+};
+
+enum bcm4323x_init_chip {
+	BCM4323X_INIT_BOTH,
+	BCM4323X_INIT_BCM43231,
+	BCM4323X_INIT_BCM4323,
+};
+
+struct bcm4323x_init_call {
+	u8 procedure;
+	/* Whether to wait for the response */
+	bool control;
+	u8 chip;
+	u8 words;
+	u32 body[5];
+};
+
+#define BCM4323X_INIT_DATA(_procedure, ...) {					\
+	.procedure = (_procedure),						\
+	.words = COUNT_ARGS(__VA_ARGS__),					\
+	.body = { __VA_ARGS__ },						\
+}
+
+#define BCM4323X_INIT_CONTROL(_procedure, ...) {				\
+	.procedure = (_procedure),						\
+	.control = true,							\
+	.words = COUNT_ARGS(__VA_ARGS__),					\
+	.body = { __VA_ARGS__ },						\
+}
+
+/* The reference driver initializes the firmware on channel 11. */
+#define BCM4323X_INIT_CHANNEL 11
+#define BCM4323X_INIT_CHANSPEC (WL_CHANSPEC_BAND_2G | WL_CHANSPEC_BW_20 | \
+				WL_CHANSPEC_CTL_SB_NONE | BCM4323X_INIT_CHANNEL)
+
+/* The reference driver's calls first part */
+static const struct bcm4323x_init_call bcm4323x_attach_calls[] = {
+	BCM4323X_INIT_CONTROL(0x4b),
+	BCM4323X_INIT_CONTROL(0x4c),
+	BCM4323X_INIT_CONTROL(0x4d, 0),
+	BCM4323X_INIT_CONTROL(0x4d, 1),
+	BCM4323X_INIT_CONTROL(0x4d, 2),
+	BCM4323X_INIT_CONTROL(0x4d, 3),
+	BCM4323X_INIT_CONTROL(0x4d, 4),
+	BCM4323X_INIT_CONTROL(0x4d, 5),
+	/* the NVRAM text */
+	BCM4323X_INIT_CONTROL(0x5b),
+	BCM4323X_INIT_DATA(0x3b, 0xc0),
+	BCM4323X_INIT_DATA(0x57, 3, 3),
+	BCM4323X_INIT_CONTROL(0xa1),
+};
+
+/*
+ * Captured from the reference driver. Similar to what
+ * brcmsmac's brcms_c_init() does
+ */
+static const struct bcm4323x_init_call bcm4323x_up_calls[] = {
+	BCM4323X_INIT_DATA(0x3b, 0xc0),
+	BCM4323X_INIT_DATA(0x3c, 0),
+	BCM4323X_INIT_CONTROL(0x44),
+	{
+		.procedure = BCM4323X_RPC_PROC_STF_MODE,
+		.chip = BCM4323X_INIT_BCM4323,
+		.words = 1,
+		.body = { PHY_TXC1_MODE_CDD },
+	},
+	BCM4323X_INIT_DATA(BCM4323X_RPC_PROC_HOST_FLAGS, MHF4, 0x40, 0x40, BRCM_BAND_AUTO),
+	BCM4323X_INIT_DATA(BCM4323X_RPC_PROC_HOST_FLAGS, MHF4, 0x40, 0x40, BRCM_BAND_AUTO),
+	{ .procedure = 0x8b, .chip = BCM4323X_INIT_BCM4323, .words = 1, .body = { 1 } },
+	{ .procedure = 0x4a, .control = true, .chip = BCM4323X_INIT_BCM4323, .words = 1,
+	  .body = { 1 } },
+	BCM4323X_INIT_CONTROL(0x4a, 2),
+	BCM4323X_INIT_DATA(0x55, 0),
+	BCM4323X_INIT_DATA(0x9a, 0),
+	BCM4323X_INIT_DATA(0x0e, 1),
+	BCM4323X_INIT_CONTROL(0x5d, 8),
+	BCM4323X_INIT_CONTROL(0x6e, 3, 4, 0, 4, 4),
+	BCM4323X_INIT_DATA(0x86, 0x64),
+	BCM4323X_INIT_DATA(0x6d, 0),
+	{ .procedure = 0x68, .chip = BCM4323X_INIT_BCM43231, .words = 1, .body = { 0x7 } },
+	{ .procedure = 0x68, .chip = BCM4323X_INIT_BCM4323, .words = 1, .body = { 0xf } },
+	BCM4323X_INIT_DATA(0x85),
+	BCM4323X_INIT_DATA(BCM4323X_RPC_PROC_HOST_FLAGS, MHF5, 0x80, 0x80, BRCM_BAND_ALL),
+	BCM4323X_INIT_CONTROL(0x05),
+	/* from brcmsmac: brcms_c_up */
+	BCM4323X_INIT_DATA(BCM4323X_RPC_PROC_HOST_FLAGS, MHF1, MHF1_EDCF, MHF1_EDCF, BRCM_BAND_ALL),
+	BCM4323X_INIT_DATA(0x0a),
+	BCM4323X_INIT_DATA(0x3b, 0xc0),
+	BCM4323X_INIT_DATA(0x1a, 3),
+	BCM4323X_INIT_DATA(0x12, BCM4323X_INIT_CHANSPEC, 0),
+	BCM4323X_INIT_CONTROL(0x5e),
+	BCM4323X_INIT_CONTROL(0x60),
+};
+
+/* The reference driver's last calls, some for the LEDs */
+static const struct bcm4323x_init_call bcm4323x_finish_calls[] = {
+	BCM4323X_INIT_DATA(0x3b, 0xc0),
+	BCM4323X_INIT_DATA(0x9d, 0),
+	BCM4323X_INIT_DATA(BCM4323X_RPC_PROC_GPIO_OUT, BIT(0), 0, 1),
+	BCM4323X_INIT_DATA(BCM4323X_RPC_PROC_LED_BLINK, 1, 500, 500),
+	BCM4323X_INIT_DATA(0x88, 2),
+	{ .procedure = BCM4323X_RPC_PROC_GPIO_OUT, .chip = BCM4323X_INIT_BCM43231, .words = 3,
+	  .body = { BIT(2), BIT(2), 1 } },
+	{ .procedure = BCM4323X_RPC_PROC_GPIO_OUT, .chip = BCM4323X_INIT_BCM4323, .words = 3,
+	  .body = { BIT(2), 0, 1 } },
+	{ .procedure = BCM4323X_RPC_PROC_GPIO_OUT, .chip = BCM4323X_INIT_BCM4323, .words = 3,
+	  .body = { BIT(3), 0, 1 } },
+	BCM4323X_INIT_DATA(0x74, 1),
+};
+
+/* from brcmsmac: brcms_c_edcf_setparams */
+static const struct ieee80211_tx_queue_params bcm4323x_edcf_defaults[IEEE80211_NUM_ACS] = {
+	[IEEE80211_AC_VO] = { .txop = 0x2f, .cw_min = 3, .cw_max = 7, .aifs = 2 },
+	[IEEE80211_AC_VI] = { .txop = 0x5e, .cw_min = 7, .cw_max = 15, .aifs = 2 },
+	[IEEE80211_AC_BE] = { .cw_min = 15, .cw_max = 1023, .aifs = 3 },
+	[IEEE80211_AC_BK] = { .cw_min = 15, .cw_max = 1023, .aifs = 7 },
+};
+
+static int bcm4323x_send_init_calls(struct bcm4323x *dev, const struct bcm4323x_init_call *calls,
+				    size_t count)
+{
+	u8 chip = dev->chip->has_5ghz ? BCM4323X_INIT_BCM4323 : BCM4323X_INIT_BCM43231;
+	const struct bcm4323x_init_call *call;
+
+	for (call = calls; call < calls + count; call++) {
+		u32 body[ARRAY_SIZE(call->body)];
+		size_t length = call->words * sizeof(*body);
+		int ret;
+
+		if (call->chip && call->chip != chip)
+			continue;
+		memcpy(body, call->body, length);
+		cpu_to_le32_array(body, call->words);
+		if (call->control)
+			ret = bcm4323x_rpc_call(dev, call->procedure, body, length, NULL, 0);
+		else
+			ret = bcm4323x_rpc_send(dev, call->procedure, body, length);
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+
+/* from b43: b43_security_init and b43_clear_keys */
+static int bcm4323x_clear_keys(struct bcm4323x *dev)
+{
+	unsigned int i;
+	int ret;
+
+	/* Procedure 0x31 fills shared memory. */
+	ret = bcm4323x_rpc_send_u32(dev, BCM4323X_RPC_PROC_SHM_FILL, BCM4323X_KEY_TABLE_BASE, 0,
+				    BCM4323X_HW_KEYS * WLAN_KEY_LEN_CCMP);
+	for (i = 0; i < BCM4323X_HW_KEYS && !ret; i++)
+		ret = bcm4323x_write_shm(dev, M_SECKINDXALGO_BLK + i * sizeof(u16), 0);
+	for (i = 0; i < BCM4323X_HW_KEYS - BCM4323X_HW_PAIRWISE_BASE && !ret; i++)
+		ret = bcm4323x_set_slot_address(dev, BCM4323X_RPC_PROC_KEY_PEER, i, NULL);
+	if (!ret)
+		ret = bcm4323x_write_reg(dev, D11REGOFFS(rcmta_size),
+					 BCM4323X_HW_KEYS - BCM4323X_HW_PAIRWISE_BASE, sizeof(u16));
+	if (!ret)
+		ret = bcm4323x_mhf(dev, MHF1, BCM4323X_MHF1_DEFKEYS, 0, BRCM_BAND_ALL);
+	return ret;
+}
+
+/*
+ * from brcmsmac: brcms_c_bandinit_ordered and brcms_c_set_phy_chanspec, without
+ * the power report
+ */
+static int bcm4323x_init_chanspec(struct bcm4323x *dev)
+{
+	struct bcm4323x_channel_data data;
+	int ret;
+
+	/* the reference driver's, with the channel first in 0x8c */
+	ret = bcm4323x_rpc_send_u32(dev, 0x8d, 1);
+	if (!ret)
+		ret = bcm4323x_rpc_send_u32(dev, 0x8c, BCM4323X_INIT_CHANNEL, 0, 1, 0);
+	bcm4323x_channel_data(dev->chip, &data, BCM4323X_INIT_CHANSPEC);
+	if (!ret)
+		ret = bcm4323x_rpc_send(dev, BCM4323X_RPC_PROC_CHANNEL_DATA, &data, sizeof(data));
+	/* The reference driver starts the BCM4323 in SISO; the first selection updates it. */
+	if (!ret && dev->chip->has_5ghz)
+		ret = bcm4323x_rpc_send_u32(dev, BCM4323X_RPC_PROC_STF_MODE, PHY_TXC1_MODE_SISO);
+	if (!ret)
+		ret = bcm4323x_mhf(dev, MHF4, 0x40, 0x40, BRCM_BAND_AUTO);
+	return ret;
+}
+
+/*
+ * from brcmsmac: brcms_c_up and brcms_c_init, amid the reference driver's
+ * calls, which leave the firmware on channel 11
+ */
+int bcm4323x_init_mac(struct bcm4323x *dev)
+{
+	u8 *addr = dev->hw->wiphy->perm_addr;
+	u16 major, minor;
+	u32 ifs_ctl;
+	int ac;
+	int ret;
+
+	ret = bcm4323x_send_init_calls(dev, bcm4323x_attach_calls,
+				       ARRAY_SIZE(bcm4323x_attach_calls));
+	if (!ret)
+		ret = bcm4323x_rpc_call(dev, BCM4323X_RPC_PROC_MAC, NULL, 0, addr, ETH_ALEN);
+	if (ret)
+		return ret;
+	if (!is_valid_ether_addr(addr))
+		return -ENODEV;
+	ret = bcm4323x_send_init_calls(dev, bcm4323x_up_calls, ARRAY_SIZE(bcm4323x_up_calls));
+	/* from brcmsmac: brcms_c_bcn_li_upd, waking up every DTIM */
+	if (!ret)
+		ret = bcm4323x_write_shm(dev, M_BCN_LI, 0);
+	if (!ret)
+		ret = bcm4323x_clear_keys(dev);
+	/* from brcmsmac: brcms_c_set_mac and brcms_c_set_bssid */
+	if (!ret)
+		ret = bcm4323x_set_slot_address(dev, BCM4323X_RPC_PROC_RX_ADDRESS, RCM_MAC_OFFSET,
+						addr);
+	if (!ret)
+		ret = bcm4323x_ampdu_macaddr_upd(dev, addr);
+	if (!ret)
+		ret = bcm4323x_set_slot_address(dev, BCM4323X_RPC_PROC_RX_ADDRESS,
+						RCM_BSSID_OFFSET, NULL);
+	if (!ret)
+		ret = bcm4323x_init_chanspec(dev);
+	/* init probe response timeout: disabled */
+	if (!ret)
+		ret = bcm4323x_write_shm(dev, M_PRS_MAXTIME, 0);
+	/* init max burst txop (framebursting) */
+	if (!ret)
+		ret = bcm4323x_write_shm(dev, M_MBURST_TXOP, MAXFRAMEBURST_TXOP);
+	/* the reference driver's */
+	if (!ret)
+		ret = bcm4323x_rpc_send_u32(dev, 0x5a, 0);
+	/* from brcmsmac: brcms_c_duty_cycle_set, without a duty cycle limit */
+	if (!ret)
+		ret = bcm4323x_write_shm(dev, M_TX_IDLE_BUSY_RATIO_X_16_OFDM, 0);
+	if (!ret)
+		ret = bcm4323x_write_shm(dev, M_TX_IDLE_BUSY_RATIO_X_16_CCK, 0);
+	/* from brcmsmac: brcms_c_ampdu_shm_upd */
+	if (!ret)
+		ret = bcm4323x_write_shm(dev, M_MIMO_MAXSYM, MIMO_MAXSYM_MAX);
+	if (!ret)
+		ret = bcm4323x_write_shm(dev, M_WATCHDOG_8TU, WATCHDOG_8TU_MAX);
+	/* from brcmsmac: brcms_c_bsinit, with the basic rates of the default rate set */
+	if (!ret)
+		ret = bcm4323x_set_ratetable(dev, NL80211_BAND_2GHZ,
+					     GENMASK(BCM4323X_NUM_CCK_RATES - 1, 0));
+	/* Enable EDCF mode (while the MAC is suspended) */
+	if (!ret)
+		ret = bcm4323x_read_reg(dev, D11REGOFFS(ifs_ctl), sizeof(u16), &ifs_ctl);
+	if (!ret)
+		ret = bcm4323x_write_reg(dev, D11REGOFFS(ifs_ctl), ifs_ctl | IFS_USEEDCF,
+					 sizeof(u16));
+	/* from brcmsmac: brcms_c_edcf_setparams, for monitor interfaces, which get no conf_tx() */
+	for (ac = 0; ac < IEEE80211_NUM_ACS && !ret; ac++)
+		ret = bcm4323x_wme_setparams(dev, ac, &bcm4323x_edcf_defaults[ac]);
+	/* read the ucode version */
+	if (!ret)
+		ret = bcm4323x_read_shm(dev, M_BOM_REV_MAJOR, &major);
+	if (!ret)
+		ret = bcm4323x_read_shm(dev, M_BOM_REV_MINOR, &minor);
+	if (ret)
+		return ret;
+	snprintf(dev->hw->wiphy->fw_version, sizeof(dev->hw->wiphy->fw_version), "%u.%u", major,
+		 minor);
+	/* from brcmsmac: brcms_c_enable_mac */
+	ret = bcm4323x_radio_sync(dev);
+	if (!ret)
+		ret = bcm4323x_set_mac_control(dev, MCTL_WAKE, 0);
+	/* enable the RF Disable Delay timer */
+	if (!ret)
+		ret = bcm4323x_write_reg(dev, D11REGOFFS(rfdisabledly), RFDISABLE_DEFAULT,
+					 sizeof(u32));
+	/* the reference driver's */
+	if (!ret)
+		ret = bcm4323x_rpc_call(dev, BCM4323X_RPC_PROC_WATCHDOG, NULL, 0, NULL, 0);
+	if (!ret)
+		ret = bcm4323x_rpc_send(dev, BCM4323X_RPC_PROC_CHANNEL_PREP, NULL, 0);
+	if (!ret)
+		ret = bcm4323x_write_shm(dev, M_AGING_THRSH, 0x320);
+	if (!ret)
+		ret = bcm4323x_mhf(dev, MHF4, 0x20, 0x20, BRCM_BAND_ALL);
+	if (!ret)
+		ret = bcm4323x_radio_sync(dev);
+	if (!ret)
+		ret = bcm4323x_rpc_call(dev, 0x06, NULL, 0, NULL, 0);
+	if (!ret)
+		ret = bcm4323x_send_init_calls(dev, bcm4323x_finish_calls,
+					       ARRAY_SIZE(bcm4323x_finish_calls));
+	return ret;
+}
+
+int bcm4323x_register_hw(struct bcm4323x *dev)
+{
+	struct ieee80211_hw *hw = dev->hw;
+
+	INIT_DELAYED_WORK(&dev->watchdog_work, bcm4323x_watchdog_worker);
+	dev->tx_power = -1;
+	memcpy(dev->channels, bcm4323x_2ghz_channels, sizeof(bcm4323x_2ghz_channels));
+	memcpy(dev->channels + BCM4323X_NUM_2GHZ_CHANNELS, bcm4323x_5ghz_channels,
+	       sizeof(bcm4323x_5ghz_channels));
+	dev->bands[NL80211_BAND_2GHZ] = (struct ieee80211_supported_band) {
+		.channels = dev->channels,
+		.n_channels = BCM4323X_NUM_2GHZ_CHANNELS,
+		.bitrates = bcm4323x_rates,
+		.n_bitrates = BCM4323X_NUM_RATES,
+		.ht_cap = {
+			.ht_supported = true,
+			.cap = IEEE80211_HT_CAP_SUP_WIDTH_20_40 | IEEE80211_HT_CAP_SGI_20 |
+			       IEEE80211_HT_CAP_SGI_40 | IEEE80211_HT_CAP_TX_STBC |
+			       IEEE80211_HT_CAP_MAX_AMSDU | IEEE80211_HT_CAP_DSSSCCK40,
+			.ampdu_factor = IEEE80211_HT_MAX_AMPDU_64K,
+			.ampdu_density = IEEE80211_HT_MPDU_DENSITY_8,
+			.mcs = {
+				.rx_mask = { 0xff, 0xff },
+				.rx_highest = cpu_to_le16(300),
+				.tx_params = IEEE80211_HT_MCS_TX_DEFINED,
+			},
+		},
+	};
+	hw->wiphy->bands[NL80211_BAND_2GHZ] = &dev->bands[NL80211_BAND_2GHZ];
+	if (dev->chip->has_5ghz) {
+		dev->bands[NL80211_BAND_5GHZ] = (struct ieee80211_supported_band) {
+			.channels = dev->channels + BCM4323X_NUM_2GHZ_CHANNELS,
+			.n_channels = BCM4323X_NUM_5GHZ_CHANNELS,
+			.bitrates = bcm4323x_rates + BCM4323X_NUM_CCK_RATES,
+			.n_bitrates = BCM4323X_NUM_OFDM_RATES,
+			.ht_cap = dev->bands[NL80211_BAND_2GHZ].ht_cap,
+		};
+		hw->wiphy->bands[NL80211_BAND_5GHZ] = &dev->bands[NL80211_BAND_5GHZ];
+	}
+
+	hw->wiphy->available_antennas_tx = BCM4323X_ANTENNAS;
+	hw->wiphy->available_antennas_rx = BCM4323X_ANTENNAS;
+	hw->wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) | BIT(NL80211_IFTYPE_P2P_CLIENT) |
+				     BIT(NL80211_IFTYPE_MONITOR);
+	hw->wiphy->flags |= WIPHY_FLAG_SUPPORTS_TDLS;
+
+	wiphy_ext_feature_set(hw->wiphy, NL80211_EXT_FEATURE_CQM_RSSI_LIST);
+	wiphy_ext_feature_set(hw->wiphy, NL80211_EXT_FEATURE_CAN_REPLACE_PTK0);
+	wiphy_ext_feature_set(hw->wiphy, NL80211_EXT_FEATURE_BEACON_PROTECTION_CLIENT);
+
+	hw->queues = IEEE80211_NUM_ACS;
+	hw->max_rates = 2;
+	hw->max_rate_tries = 1;
+	hw->txq_data_size = sizeof(struct bcm4323x_txq);
+	hw->max_listen_interval = 10;
+	hw->radiotap_mcs_details |= IEEE80211_RADIOTAP_MCS_HAVE_STBC;
+
+	ieee80211_hw_set(hw, SIGNAL_DBM);
+	ieee80211_hw_set(hw, REPORTS_TX_ACK_STATUS);
+	ieee80211_hw_set(hw, SUPPORT_FAST_XMIT);
+	ieee80211_hw_set(hw, TX_AMSDU);
+	ieee80211_hw_set(hw, TX_FRAG_LIST);
+	ieee80211_hw_set(hw, SUPPORTS_CLONED_SKBS);
+	ieee80211_hw_set(hw, AMPDU_AGGREGATION);
+	ieee80211_hw_set(hw, SUPPORTS_HT_CCK_RATES);
+	ieee80211_hw_set(hw, MFP_CAPABLE);
+	ieee80211_hw_set(hw, SPECTRUM_MGMT);
+	ieee80211_hw_set(hw, NO_VIRTUAL_MONITOR);
+
+	SET_IEEE80211_DEV(hw, &dev->intf->dev);
+	bcm4323x_led_init(dev);
+	return ieee80211_register_hw(hw);
+}
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [RFC PATCH wireless-next 16/17] wifi: bcm4323x: add LED and WPS button support
  2026-10-26  5:36 [RFC PATCH wireless-next 00/17] wifi: add a driver for Broadcom BCM4323x USB adapters Ragul Balaji
                   ` (14 preceding siblings ...)
  2026-10-26  5:36 ` [RFC PATCH wireless-next 15/17] wifi: bcm4323x: add the mac80211 operations Ragul Balaji
@ 2026-10-26  5:36 ` Ragul Balaji
  2026-10-26  5:36 ` [RFC PATCH wireless-next 17/17] wifi: bcm4323x: add Kconfig, Makefile and MAINTAINERS entry Ragul Balaji
  16 siblings, 0 replies; 18+ messages in thread
From: Ragul Balaji @ 2026-10-26  5:36 UTC (permalink / raw)
  To: linux-wireless
  Cc: Arend van Spriel, Johannes Berg, brcm80211, Ragul Balaji,
	Ho Jie Feng, linux-kernel, brcm80211-dev-list.pdl

Add the LED and WPS button to the device driver.
Currently the GPIOs are hardcoded to values in specific devices.

Signed-off-by: Ragul Balaji <ragulbaby@gmail.com>
Signed-off-by: Ho Jie Feng <hjf3108@gmail.com>
---
 .../brcm80211/bcm4323x/bcm4323x_led.c         | 221 ++++++++++++++++++
 1 file changed, 221 insertions(+)
 create mode 100644 drivers/net/wireless/broadcom/brcm80211/bcm4323x/bcm4323x_led.c

diff --git a/drivers/net/wireless/broadcom/brcm80211/bcm4323x/bcm4323x_led.c b/drivers/net/wireless/broadcom/brcm80211/bcm4323x/bcm4323x_led.c
new file mode 100644
index 000000000..8d2aa459a
--- /dev/null
+++ b/drivers/net/wireless/broadcom/brcm80211/bcm4323x/bcm4323x_led.c
@@ -0,0 +1,221 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/bits.h>
+#include <linux/input.h>
+#include <linux/led-class-multicolor.h>
+#include <linux/leds.h>
+#include <linux/usb/input.h>
+
+#include "bcm4323x.h"
+
+/* Power/Activity LED */
+#define BCM4323X_LED_GPIO 1
+#define BCM4323X_LED_BLINK_MS 500
+/* TODO: This is Belkin F9L1101 specific*/
+#define BCM4323X_WPS_GREEN_GPIO 2
+#define BCM4323X_WPS_RED_GPIO 3
+/* TODO: this is Belkin F9L1101 and WNA3100 specific */
+#define BCM4323X_WPS_BUTTON_GPIO 10
+#define BCM4323X_WPS_BUTTON_POLL_MS 200
+
+static const struct ieee80211_tpt_blink bcm4323x_tpt_blink[] = {
+	{ .throughput = 0 * 1024, .blink_time = 334 },
+	{ .throughput = 1 * 1024, .blink_time = 260 },
+	{ .throughput = 5 * 1024, .blink_time = 220 },
+	{ .throughput = 10 * 1024, .blink_time = 190 },
+	{ .throughput = 20 * 1024, .blink_time = 170 },
+	{ .throughput = 50 * 1024, .blink_time = 150 },
+	{ .throughput = 70 * 1024, .blink_time = 130 },
+	{ .throughput = 100 * 1024, .blink_time = 110 },
+	{ .throughput = 200 * 1024, .blink_time = 80 },
+	{ .throughput = 300 * 1024, .blink_time = 50 },
+};
+
+static int bcm4323x_led_blink(struct bcm4323x *dev, unsigned long on_ms, unsigned long off_ms)
+{
+	int ret = bcm4323x_rpc_send_u32(dev, BCM4323X_RPC_PROC_LED_BLINK, BCM4323X_LED_GPIO, on_ms,
+					off_ms);
+
+	return ret == -ENODEV ? 0 : ret;
+}
+
+static int bcm4323x_led_gpio_out(struct bcm4323x *dev, u32 mask, u32 value)
+{
+	int ret = bcm4323x_rpc_send_u32(dev, BCM4323X_RPC_PROC_GPIO_OUT, mask, value, 1);
+
+	return ret == -ENODEV ? 0 : ret;
+}
+
+static int bcm4323x_led_set(struct bcm4323x *dev, bool on)
+{
+	int ret;
+
+	ret = bcm4323x_led_blink(dev, 0, 0);
+	if (ret)
+		return ret;
+	return bcm4323x_led_gpio_out(dev, BIT(BCM4323X_LED_GPIO),
+				     on ? BIT(BCM4323X_LED_GPIO) : 0);
+}
+
+static int bcm4323x_led_brightness_set(struct led_classdev *led, enum led_brightness brightness)
+{
+	return bcm4323x_led_set(container_of(led, struct bcm4323x, led), brightness);
+}
+
+static int bcm4323x_led_blink_set(struct led_classdev *led, unsigned long *delay_on,
+				  unsigned long *delay_off)
+{
+	struct bcm4323x *dev = container_of(led, struct bcm4323x, led);
+
+	if (!*delay_on && !*delay_off)
+		*delay_on = *delay_off = BCM4323X_LED_BLINK_MS;
+	if (!*delay_on || !*delay_off)
+		return bcm4323x_led_set(dev, *delay_on);
+	return bcm4323x_led_blink(dev, *delay_on, *delay_off);
+}
+
+#if IS_REACHABLE(CONFIG_LEDS_CLASS_MULTICOLOR)
+static int bcm4323x_wps_led_brightness_set(struct led_classdev *led,
+					   enum led_brightness brightness)
+{
+	struct led_classdev_mc *mc = lcdev_to_mccdev(led);
+	struct bcm4323x *dev = container_of(mc, struct bcm4323x, wps_led);
+	u32 value = 0;
+	unsigned int i;
+
+	led_mc_calc_color_components(mc, brightness);
+	for (i = 0; i < mc->num_colors; i++) {
+		if (mc->subled_info[i].brightness)
+			value |= BIT(mc->subled_info[i].channel);
+	}
+	return bcm4323x_led_gpio_out(dev, BIT(BCM4323X_WPS_GREEN_GPIO) | BIT(BCM4323X_WPS_RED_GPIO),
+				     value);
+}
+
+/* F9L1101 v1 specific */
+static void bcm4323x_wps_led_register(struct bcm4323x *dev)
+{
+	struct led_classdev *led = &dev->wps_led.led_cdev;
+	int ret;
+
+	if (!dev->chip->has_wps_led)
+		return;
+	dev->wps_subleds[0] = (struct mc_subled) {
+		.color_index = LED_COLOR_ID_GREEN,
+		.intensity = 1,
+		.max_intensity = 1,
+		.channel = BCM4323X_WPS_GREEN_GPIO,
+	};
+	dev->wps_subleds[1] = (struct mc_subled) {
+		.color_index = LED_COLOR_ID_RED,
+		.intensity = 1,
+		.max_intensity = 1,
+		.channel = BCM4323X_WPS_RED_GPIO,
+	};
+	dev->wps_led.subled_info = dev->wps_subleds;
+	dev->wps_led.num_colors = ARRAY_SIZE(dev->wps_subleds);
+	snprintf(dev->wps_led_name, sizeof(dev->wps_led_name), "bcm4323x-%s:multicolor:wps",
+		 wiphy_name(dev->hw->wiphy));
+	led->name = dev->wps_led_name;
+	led->max_brightness = 1;
+	led->brightness_set_blocking = bcm4323x_wps_led_brightness_set;
+	ret = bcm4323x_wps_led_brightness_set(led, LED_OFF);
+	if (!ret)
+		ret = led_classdev_multicolor_register(&dev->intf->dev, &dev->wps_led);
+	if (ret) {
+		led->dev = NULL;
+		wiphy_warn(dev->hw->wiphy, "cannot register the WPS LED: %d\n", ret);
+	}
+}
+
+static void bcm4323x_wps_led_unregister(struct bcm4323x *dev)
+{
+	if (dev->wps_led.led_cdev.dev)
+		led_classdev_multicolor_unregister(&dev->wps_led);
+}
+#else
+static void bcm4323x_wps_led_register(struct bcm4323x *dev) {}
+static void bcm4323x_wps_led_unregister(struct bcm4323x *dev) {}
+#endif
+
+static void bcm4323x_wps_button_poll(struct input_dev *input)
+{
+	struct bcm4323x *dev = input_get_drvdata(input);
+	__le32 value;
+
+	if (bcm4323x_rpc_call_u32(dev, BCM4323X_RPC_PROC_REG_READ, &value,
+				  D11REGOFFS(psm_gpio_in), sizeof(u16)))
+		return;
+	input_report_key(input, KEY_WPS_BUTTON,
+			 !(le32_to_cpu(value) & BIT(BCM4323X_WPS_BUTTON_GPIO)));
+	input_sync(input);
+}
+
+static void bcm4323x_wps_button_register(struct bcm4323x *dev)
+{
+	struct input_dev *input;
+	int ret;
+
+	if (!dev->chip->has_wps_button)
+		return;
+	input = input_allocate_device();
+	if (!input)
+		return;
+	input->name = "BCM4323x WPS button";
+	snprintf(dev->wps_button_phys, sizeof(dev->wps_button_phys), "ieee80211/%s/input0",
+		 wiphy_name(dev->hw->wiphy));
+	input->phys = dev->wps_button_phys;
+	usb_to_input_id(dev->udev, &input->id);
+	input->dev.parent = &dev->hw->wiphy->dev;
+	input_set_capability(input, EV_KEY, KEY_WPS_BUTTON);
+	input_set_drvdata(input, dev);
+	ret = input_setup_polling(input, bcm4323x_wps_button_poll);
+	if (!ret) {
+		input_set_poll_interval(input, BCM4323X_WPS_BUTTON_POLL_MS);
+		ret = input_register_device(input);
+	}
+	if (ret) {
+		input_free_device(input);
+		wiphy_warn(dev->hw->wiphy, "cannot register the WPS button: %d\n", ret);
+		return;
+	}
+	dev->wps_button = input;
+}
+
+void bcm4323x_led_init(struct bcm4323x *dev)
+{
+	dev->led.default_trigger =
+		ieee80211_create_tpt_led_trigger(dev->hw, IEEE80211_TPT_LEDTRIG_FL_RADIO,
+						 bcm4323x_tpt_blink,
+						 ARRAY_SIZE(bcm4323x_tpt_blink));
+}
+
+void bcm4323x_led_register(struct bcm4323x *dev)
+{
+	struct wiphy *wiphy = dev->hw->wiphy;
+	int ret;
+
+	snprintf(dev->led_name, sizeof(dev->led_name), "bcm4323x-%s:radio", wiphy_name(wiphy));
+	dev->led.name = dev->led_name;
+	dev->led.max_brightness = 1;
+	dev->led.brightness_set_blocking = bcm4323x_led_brightness_set;
+	dev->led.blink_set = bcm4323x_led_blink_set;
+	ret = bcm4323x_led_set(dev, false);
+	if (!ret)
+		ret = led_classdev_register(&dev->intf->dev, &dev->led);
+	if (ret) {
+		dev->led.dev = NULL;
+		wiphy_warn(wiphy, "cannot register the LED: %d\n", ret);
+	}
+	bcm4323x_wps_led_register(dev);
+	bcm4323x_wps_button_register(dev);
+}
+
+void bcm4323x_led_unregister(struct bcm4323x *dev)
+{
+	if (dev->wps_button)
+		input_unregister_device(dev->wps_button);
+	bcm4323x_wps_led_unregister(dev);
+	if (dev->led.dev)
+		led_classdev_unregister(&dev->led);
+}
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [RFC PATCH wireless-next 17/17] wifi: bcm4323x: add Kconfig, Makefile and MAINTAINERS entry
  2026-10-26  5:36 [RFC PATCH wireless-next 00/17] wifi: add a driver for Broadcom BCM4323x USB adapters Ragul Balaji
                   ` (15 preceding siblings ...)
  2026-10-26  5:36 ` [RFC PATCH wireless-next 16/17] wifi: bcm4323x: add LED and WPS button support Ragul Balaji
@ 2026-10-26  5:36 ` Ragul Balaji
  16 siblings, 0 replies; 18+ messages in thread
From: Ragul Balaji @ 2026-10-26  5:36 UTC (permalink / raw)
  To: linux-wireless
  Cc: Arend van Spriel, Johannes Berg, brcm80211, Ragul Balaji,
	Ho Jie Feng, linux-kernel, brcm80211-dev-list.pdl

Wire up the driver so that it can be built

Signed-off-by: Ragul Balaji <ragulbaby@gmail.com>
Signed-off-by: Ho Jie Feng <hjf3108@gmail.com>
---
 MAINTAINERS                                          |  7 +++++++
 drivers/net/wireless/broadcom/brcm80211/Kconfig      |  1 +
 drivers/net/wireless/broadcom/brcm80211/Makefile     |  1 +
 .../net/wireless/broadcom/brcm80211/bcm4323x/Kconfig | 12 ++++++++++++
 .../wireless/broadcom/brcm80211/bcm4323x/Makefile    |  6 ++++++
 5 files changed, 27 insertions(+)
 create mode 100644 drivers/net/wireless/broadcom/brcm80211/bcm4323x/Kconfig
 create mode 100644 drivers/net/wireless/broadcom/brcm80211/bcm4323x/Makefile

diff --git a/MAINTAINERS b/MAINTAINERS
index 733660095..91f4eef9e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5210,6 +5210,13 @@ S:	Maintained
 F:	Documentation/devicetree/bindings/media/brcm,bcm2835-unicam.yaml
 F:	drivers/media/platform/broadcom/bcm2835-unicam*
 
+BROADCOM BCM4323X USB WIRELESS DRIVER
+M:	Ragul Balaji <ragulbaby@gmail.com>
+R:	Ho Jie Feng <hjf3108@gmail.com>
+L:	linux-wireless@vger.kernel.org
+S:	Maintained
+F:	drivers/net/wireless/broadcom/brcm80211/bcm4323x/
+
 BROADCOM BCM47XX MIPS ARCHITECTURE
 M:	Hauke Mehrtens <hauke@hauke-m.de>
 M:	Rafał Miłecki <zajec5@gmail.com>
diff --git a/drivers/net/wireless/broadcom/brcm80211/Kconfig b/drivers/net/wireless/broadcom/brcm80211/Kconfig
index 19d0c003f..e363ebdd5 100644
--- a/drivers/net/wireless/broadcom/brcm80211/Kconfig
+++ b/drivers/net/wireless/broadcom/brcm80211/Kconfig
@@ -23,6 +23,7 @@ config BRCMSMAC_LEDS
 	  is enabled and reachable from the driver module.
 
 source "drivers/net/wireless/broadcom/brcm80211/brcmfmac/Kconfig"
+source "drivers/net/wireless/broadcom/brcm80211/bcm4323x/Kconfig"
 
 config BRCM_TRACING
 	bool "Broadcom device tracing"
diff --git a/drivers/net/wireless/broadcom/brcm80211/Makefile b/drivers/net/wireless/broadcom/brcm80211/Makefile
index 88115d072..4d0dc67e6 100644
--- a/drivers/net/wireless/broadcom/brcm80211/Makefile
+++ b/drivers/net/wireless/broadcom/brcm80211/Makefile
@@ -11,3 +11,4 @@ subdir-ccflags-$(CONFIG_BRCMDBG)	+= -DDEBUG
 obj-$(CONFIG_BRCMUTIL)	+= brcmutil/
 obj-$(CONFIG_BRCMFMAC)	+= brcmfmac/
 obj-$(CONFIG_BRCMSMAC)	+= brcmsmac/
+obj-$(CONFIG_BCM4323X)	+= bcm4323x/
diff --git a/drivers/net/wireless/broadcom/brcm80211/bcm4323x/Kconfig b/drivers/net/wireless/broadcom/brcm80211/bcm4323x/Kconfig
new file mode 100644
index 000000000..0015893f9
--- /dev/null
+++ b/drivers/net/wireless/broadcom/brcm80211/bcm4323x/Kconfig
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config BCM4323X
+	tristate "Broadcom BCM4323x USB support"
+	depends on MAC80211 && USB && INPUT
+	select BRCMUTIL
+	select FW_LOADER
+	help
+	  Support for the Broadcom BCM43231 and BCM4323 USB wireless adapters
+	  used in the Netgear WNA3100 and WNDA3100v2 and the Belkin F9L1101.
+
+	  To compile this driver as a module, choose M here. The module will
+	  be called bcm4323x.
diff --git a/drivers/net/wireless/broadcom/brcm80211/bcm4323x/Makefile b/drivers/net/wireless/broadcom/brcm80211/bcm4323x/Makefile
new file mode 100644
index 000000000..406cc8fad
--- /dev/null
+++ b/drivers/net/wireless/broadcom/brcm80211/bcm4323x/Makefile
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0-only
+ccflags-y += -I $(src)/../include
+
+obj-$(CONFIG_BCM4323X) += bcm4323x.o
+bcm4323x-y := bcm4323x_core.o bcm4323x_mac.o bcm4323x_tx.o bcm4323x_rx.o
+bcm4323x-$(CONFIG_MAC80211_LEDS) += bcm4323x_led.o
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 18+ messages in thread

end of thread, other threads:[~2026-10-26  5:37 UTC | newest]

Thread overview: 18+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-26  5:36 [RFC PATCH wireless-next 00/17] wifi: add a driver for Broadcom BCM4323x USB adapters Ragul Balaji
2026-10-26  5:36 ` [RFC PATCH wireless-next 01/17] wifi: brcmsmac: make d11.h self-contained Ragul Balaji
2026-10-26  5:36 ` [RFC PATCH wireless-next 02/17] wifi: brcm80211: move d11.h to the shared include directory Ragul Balaji
2026-10-26  5:36 ` [RFC PATCH wireless-next 03/17] wifi: brcmsmac: look up OFDM rates in rate.c Ragul Balaji
2026-10-26  5:36 ` [RFC PATCH wireless-next 04/17] wifi: brcmsmac: give the rate tables and helpers a brcmu_ prefix Ragul Balaji
2026-10-26  5:36 ` [RFC PATCH wireless-next 05/17] wifi: brcmsmac: round short-GI L-SIG lengths up to whole symbols Ragul Balaji
2026-10-26  5:36 ` [RFC PATCH wireless-next 06/17] wifi: brcmsmac: pass the band to the PLCP and frame timing helpers Ragul Balaji
2026-10-26  5:36 ` [RFC PATCH wireless-next 07/17] wifi: brcmsmac: move the PLCP and frame timing helpers to rate.c Ragul Balaji
2026-10-26  5:36 ` [RFC PATCH wireless-next 08/17] wifi: brcm80211: move the rate helpers of brcmsmac to brcmutil Ragul Balaji
2026-10-26  5:36 ` [RFC PATCH wireless-next 09/17] wifi: brcm80211: move the USB bootloader definitions to a shared header Ragul Balaji
2026-10-26  5:36 ` [RFC PATCH wireless-next 10/17] wifi: mac80211: export ieee80211_get_bssid() again Ragul Balaji
2026-10-26  5:36 ` [RFC PATCH wireless-next 11/17] wifi: bcm4323x: add the shared definitions Ragul Balaji
2026-10-26  5:36 ` [RFC PATCH wireless-next 12/17] wifi: bcm4323x: add the USB transport and firmware setup Ragul Balaji
2026-10-26  5:36 ` [RFC PATCH wireless-next 13/17] wifi: bcm4323x: add the receive path Ragul Balaji
2026-10-26  5:36 ` [RFC PATCH wireless-next 14/17] wifi: bcm4323x: add the transmit path Ragul Balaji
2026-10-26  5:36 ` [RFC PATCH wireless-next 15/17] wifi: bcm4323x: add the mac80211 operations Ragul Balaji
2026-10-26  5:36 ` [RFC PATCH wireless-next 16/17] wifi: bcm4323x: add LED and WPS button support Ragul Balaji
2026-10-26  5:36 ` [RFC PATCH wireless-next 17/17] wifi: bcm4323x: add Kconfig, Makefile and MAINTAINERS entry Ragul Balaji

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