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
next prev 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