Linux wireless drivers development
 help / color / mirror / Atom feed
From: Ragul Balaji <ragulbaby@gmail.com>
To: linux-wireless@vger.kernel.org
Cc: Arend van Spriel <arend.vanspriel@broadcom.com>,
	Johannes Berg <johannes@sipsolutions.net>,
	brcm80211@lists.linux.dev, Ragul Balaji <ragulbaby@gmail.com>,
	Ho Jie Feng <hjf3108@gmail.com>,
	linux-kernel@vger.kernel.org,
	brcm80211-dev-list.pdl@broadcom.com
Subject: [RFC PATCH wireless-next 15/17] wifi: bcm4323x: add the mac80211 operations
Date: Mon, 26 Oct 2026 13:36:21 +0800	[thread overview]
Message-ID: <20261026053633.361454-16-ragulbaby@gmail.com> (raw)
In-Reply-To: <20261026053633.361454-1-ragulbaby@gmail.com>

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


  parent reply	other threads:[~2026-10-26  5:37 UTC|newest]

Thread overview: 18+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
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 ` Ragul Balaji [this message]
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

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261026053633.361454-16-ragulbaby@gmail.com \
    --to=ragulbaby@gmail.com \
    --cc=arend.vanspriel@broadcom.com \
    --cc=brcm80211-dev-list.pdl@broadcom.com \
    --cc=brcm80211@lists.linux.dev \
    --cc=hjf3108@gmail.com \
    --cc=johannes@sipsolutions.net \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-wireless@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox