* [PATCH wireless-next v3 01/25] wifi: inffmac: add a new driver directory for infineon WLAN vendor
2026-09-02 16:50 [PATCH wireless-next v3 00/25] wifi: inffmac: introducing a driver for Infineon's new generation chipsets Gokul Sivakumar
@ 2026-09-02 16:50 ` Gokul Sivakumar
2026-09-02 16:50 ` [PATCH wireless-next v3 02/25] wifi: inffmac: add firmware.c/h Gokul Sivakumar
` (23 subsequent siblings)
24 siblings, 0 replies; 26+ messages in thread
From: Gokul Sivakumar @ 2026-09-02 16:50 UTC (permalink / raw)
To: linux-wireless, johannes
Cc: arend.vanspriel, wlan-kernel-dev-list, gokulkumar.sivakumar
Update the wireless driver common Kconfig and Makefile to include the
new WLAN vendor directory for Infineon(Cypress).
Signed-off-by: Gokul Sivakumar <gokulkumar.sivakumar@infineon.com>
---
drivers/net/wireless/Kconfig | 1 +
drivers/net/wireless/Makefile | 1 +
drivers/net/wireless/infineon/Kconfig | 25 +++++++++++++++++++++++++
drivers/net/wireless/infineon/Makefile | 12 ++++++++++++
4 files changed, 39 insertions(+)
create mode 100644 drivers/net/wireless/infineon/Kconfig
create mode 100644 drivers/net/wireless/infineon/Makefile
diff --git a/drivers/net/wireless/Kconfig b/drivers/net/wireless/Kconfig
index f4f969fdbc02..0e3feb3de3e0 100644
--- a/drivers/net/wireless/Kconfig
+++ b/drivers/net/wireless/Kconfig
@@ -22,6 +22,7 @@ source "drivers/net/wireless/admtek/Kconfig"
source "drivers/net/wireless/ath/Kconfig"
source "drivers/net/wireless/atmel/Kconfig"
source "drivers/net/wireless/broadcom/Kconfig"
+source "drivers/net/wireless/infineon/Kconfig"
source "drivers/net/wireless/intel/Kconfig"
source "drivers/net/wireless/intersil/Kconfig"
source "drivers/net/wireless/marvell/Kconfig"
diff --git a/drivers/net/wireless/Makefile b/drivers/net/wireless/Makefile
index fa3100ff3bf7..796ee7d658e4 100644
--- a/drivers/net/wireless/Makefile
+++ b/drivers/net/wireless/Makefile
@@ -7,6 +7,7 @@ obj-$(CONFIG_WLAN_VENDOR_ADMTEK) += admtek/
obj-$(CONFIG_WLAN_VENDOR_ATH) += ath/
obj-$(CONFIG_WLAN_VENDOR_ATMEL) += atmel/
obj-$(CONFIG_WLAN_VENDOR_BROADCOM) += broadcom/
+obj-$(CONFIG_WLAN_VENDOR_INFINEON) += infineon/
obj-$(CONFIG_WLAN_VENDOR_INTEL) += intel/
obj-$(CONFIG_WLAN_VENDOR_INTERSIL) += intersil/
obj-$(CONFIG_WLAN_VENDOR_MARVELL) += marvell/
diff --git a/drivers/net/wireless/infineon/Kconfig b/drivers/net/wireless/infineon/Kconfig
new file mode 100644
index 000000000000..7eeeb5935361
--- /dev/null
+++ b/drivers/net/wireless/infineon/Kconfig
@@ -0,0 +1,25 @@
+# SPDX-License-Identifier: ISC
+
+config WLAN_VENDOR_INFINEON
+ bool "Infineon devices"
+ default y
+ help
+ If you have a wireless card belonging to this class, say Y.
+
+ Note that the answer to this question doesn't directly affect the
+ kernel: saying N will just cause the configurator to skip all the
+ questions about these cards. If you say Y, you will be asked for
+ your specific card in the following questions.
+
+if WLAN_VENDOR_INFINEON
+
+source "drivers/net/wireless/infineon/inffmac/Kconfig"
+
+config INF_DEBUG
+ bool "Infineon driver debug functions"
+ depends on INFFMAC
+ select WANT_DEV_COREDUMP
+ help
+ Selecting this enables additional code for debug purposes.
+
+endif # WLAN_VENDOR_INFINEON
diff --git a/drivers/net/wireless/infineon/Makefile b/drivers/net/wireless/infineon/Makefile
new file mode 100644
index 000000000000..25c8d9b701b0
--- /dev/null
+++ b/drivers/net/wireless/infineon/Makefile
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: ISC
+#
+# Copyright (c) 2025-2026, Infineon Technologies AG, or an affiliate of Infineon Technologies AG.
+# All rights reserved.
+#
+# Makefile fragment for Infineon 802.11 Networking Device Driver
+#
+
+# common flags
+subdir-ccflags-$(CONFIG_INF_DEBUG) += -DDEBUG
+
+obj-$(CONFIG_INFFMAC) += inffmac/
--
2.43.0
^ permalink raw reply related [flat|nested] 26+ messages in thread* [PATCH wireless-next v3 02/25] wifi: inffmac: add firmware.c/h
2026-09-02 16:50 [PATCH wireless-next v3 00/25] wifi: inffmac: introducing a driver for Infineon's new generation chipsets Gokul Sivakumar
2026-09-02 16:50 ` [PATCH wireless-next v3 01/25] wifi: inffmac: add a new driver directory for infineon WLAN vendor Gokul Sivakumar
@ 2026-09-02 16:50 ` Gokul Sivakumar
2026-09-02 16:50 ` [PATCH wireless-next v3 03/25] wifi: inffmac: add dfu.c/h Gokul Sivakumar
` (22 subsequent siblings)
24 siblings, 0 replies; 26+ messages in thread
From: Gokul Sivakumar @ 2026-09-02 16:50 UTC (permalink / raw)
To: linux-wireless, johannes
Cc: arend.vanspriel, wlan-kernel-dev-list, gokulkumar.sivakumar
Driver implementation for handling various types of Device firmware files
like binaries, NVRAM, CLM BLOB.
Signed-off-by: Gokul Sivakumar <gokulkumar.sivakumar@infineon.com>
---
.../net/wireless/infineon/inffmac/firmware.c | 968 ++++++++++++++++++
.../net/wireless/infineon/inffmac/firmware.h | 100 ++
2 files changed, 1068 insertions(+)
create mode 100644 drivers/net/wireless/infineon/inffmac/firmware.c
create mode 100644 drivers/net/wireless/infineon/inffmac/firmware.h
diff --git a/drivers/net/wireless/infineon/inffmac/firmware.c b/drivers/net/wireless/infineon/inffmac/firmware.c
new file mode 100644
index 000000000000..01c9efe2ae21
--- /dev/null
+++ b/drivers/net/wireless/infineon/inffmac/firmware.c
@@ -0,0 +1,968 @@
+// SPDX-License-Identifier: ISC
+/*
+ * Copyright (c) 2013 Broadcom Corporation
+ *
+ * Copyright (c) 2025-2026, Infineon Technologies AG, or an affiliate of Infineon Technologies AG.
+ * All rights reserved.
+ */
+
+#include <linux/efi.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/firmware.h>
+#include <linux/module.h>
+
+#include "main.h"
+#include "debug.h"
+#include "firmware.h"
+#include "chip.h"
+
+#define INFF_FW_MAX_NVRAM_SIZE 64000
+#define INFF_FW_NVRAM_DEVPATH_LEN 19 /* devpath0=pcie/1/4/ */
+#define INFF_FW_NVRAM_PCIEDEV_LEN 20 /* pcie/1/4/ + \0 */
+#define INFF_FW_DEFAULT_BOARDREV "boardrev=0xff"
+#define INFF_FW_MACADDR_FMT "macaddr=%pM"
+#define INFF_FW_MACADDR_LEN (7 + ETH_ALEN * 3)
+
+enum nvram_parser_state {
+ ST_IDLE,
+ ST_KEY,
+ ST_VALUE,
+ ST_COMMENT,
+ ST_END
+};
+
+/**
+ * struct nvram_parser - internal info for parser.
+ *
+ * @state: current parser state.
+ * @data: input buffer being parsed.
+ * @nvram: output buffer with parse result.
+ * @nvram_len: length of parse result.
+ * @line: current line.
+ * @column: current column in line.
+ * @pos: byte offset in input buffer.
+ * @entry: start position of key,value entry.
+ * @multi_dev_v1: detect pcie multi device v1 (compressed).
+ * @multi_dev_v2: detect pcie multi device v2.
+ * @boardrev_found: nvram contains boardrev information.
+ * @strip_mac: strip the MAC address.
+ */
+struct nvram_parser {
+ enum nvram_parser_state state;
+ const u8 *data;
+ u8 *nvram;
+ u32 nvram_len;
+ u32 line;
+ u32 column;
+ u32 pos;
+ u32 entry;
+ bool multi_dev_v1;
+ bool multi_dev_v2;
+ bool boardrev_found;
+ bool strip_mac;
+};
+
+/*
+ * is_nvram_char() - check if char is a valid one for NVRAM entry
+ *
+ * It accepts all printable ASCII chars except for '#' which opens a comment.
+ * Please note that ' ' (space) while accepted is not a valid key name char.
+ */
+static bool is_nvram_char(char c)
+{
+ /* comment marker excluded */
+ if (c == '#')
+ return false;
+
+ /* key and value may have any other readable character */
+ return (c >= 0x20 && c < 0x7f);
+}
+
+static bool is_whitespace(char c)
+{
+ return (c == ' ' || c == '\r' || c == '\n' || c == '\t');
+}
+
+static enum nvram_parser_state inff_nvram_handle_idle(struct nvram_parser *nvp)
+{
+ char c;
+
+ c = nvp->data[nvp->pos];
+ if (c == '\n')
+ return ST_COMMENT;
+ if (is_whitespace(c) || c == '\0')
+ goto proceed;
+ if (c == '#')
+ return ST_COMMENT;
+ if (is_nvram_char(c)) {
+ nvp->entry = nvp->pos;
+ return ST_KEY;
+ }
+ inff_dbg(INFO, "warning: ln=%d:col=%d: ignoring invalid character\n",
+ nvp->line, nvp->column);
+proceed:
+ nvp->column++;
+ nvp->pos++;
+ return ST_IDLE;
+}
+
+static enum nvram_parser_state inff_nvram_handle_key(struct nvram_parser *nvp)
+{
+ enum nvram_parser_state st = nvp->state;
+ char c;
+
+ c = nvp->data[nvp->pos];
+ if (c == '=') {
+ /* ignore RAW1 by treating as comment */
+ if (strncmp(&nvp->data[nvp->entry], "RAW1", 4) == 0)
+ st = ST_COMMENT;
+ else
+ st = ST_VALUE;
+ if (strncmp(&nvp->data[nvp->entry], "devpath", 7) == 0)
+ nvp->multi_dev_v1 = true;
+ if (strncmp(&nvp->data[nvp->entry], "pcie/", 5) == 0)
+ nvp->multi_dev_v2 = true;
+ if (strncmp(&nvp->data[nvp->entry], "boardrev", 8) == 0)
+ nvp->boardrev_found = true;
+ /* strip macaddr if platform MAC overrides */
+ if (nvp->strip_mac &&
+ strncmp(&nvp->data[nvp->entry], "macaddr", 7) == 0)
+ st = ST_COMMENT;
+ } else if (!is_nvram_char(c) || c == ' ') {
+ inff_dbg(INFO, "warning: ln=%d:col=%d: '=' expected, skip invalid key entry\n",
+ nvp->line, nvp->column);
+ return ST_COMMENT;
+ }
+
+ nvp->column++;
+ nvp->pos++;
+ return st;
+}
+
+static enum nvram_parser_state
+inff_nvram_handle_value(struct nvram_parser *nvp)
+{
+ char c;
+ char *skv;
+ char *ekv;
+ u32 cplen;
+
+ c = nvp->data[nvp->pos];
+ if (!is_nvram_char(c)) {
+ /* key,value pair complete */
+ ekv = (u8 *)&nvp->data[nvp->pos];
+ skv = (u8 *)&nvp->data[nvp->entry];
+ cplen = ekv - skv;
+ if (nvp->nvram_len + cplen + 1 >= INFF_FW_MAX_NVRAM_SIZE)
+ return ST_END;
+ /* copy to output buffer */
+ memcpy(&nvp->nvram[nvp->nvram_len], skv, cplen);
+ nvp->nvram_len += cplen;
+ nvp->nvram[nvp->nvram_len] = '\0';
+ nvp->nvram_len++;
+ return ST_IDLE;
+ }
+ nvp->pos++;
+ nvp->column++;
+ return ST_VALUE;
+}
+
+static enum nvram_parser_state
+inff_nvram_handle_comment(struct nvram_parser *nvp)
+{
+ char *eoc, *sol;
+
+ sol = (char *)&nvp->data[nvp->pos];
+ eoc = strchr(sol, '\n');
+ if (!eoc) {
+ eoc = strchr(sol, '\0');
+ if (!eoc)
+ return ST_END;
+ }
+
+ /* eat all moving to next line */
+ nvp->line++;
+ nvp->column = 1;
+ nvp->pos += (eoc - sol) + 1;
+ return ST_IDLE;
+}
+
+static enum nvram_parser_state inff_nvram_handle_end(struct nvram_parser *nvp)
+{
+ /* final state */
+ return ST_END;
+}
+
+static enum nvram_parser_state
+(*nv_parser_states[])(struct nvram_parser *nvp) = {
+ inff_nvram_handle_idle,
+ inff_nvram_handle_key,
+ inff_nvram_handle_value,
+ inff_nvram_handle_comment,
+ inff_nvram_handle_end
+};
+
+static int inff_init_nvram_parser(struct nvram_parser *nvp,
+ const u8 *data, size_t data_len)
+{
+ size_t size;
+
+ memset(nvp, 0, sizeof(*nvp));
+ nvp->data = data;
+ /* Limit size to MAX_NVRAM_SIZE, some files contain lot of comment */
+ if (data_len > INFF_FW_MAX_NVRAM_SIZE)
+ size = INFF_FW_MAX_NVRAM_SIZE;
+ else
+ size = data_len;
+ /* Add space for properties we may add */
+ size += strlen(INFF_FW_DEFAULT_BOARDREV) + 1;
+ size += INFF_FW_MACADDR_LEN + 1;
+ /* Alloc for extra 0 byte + roundup by 4 + length field */
+ size += 1 + 3 + sizeof(u32);
+ nvp->nvram = kzalloc(size, GFP_KERNEL);
+ if (!nvp->nvram)
+ return -ENOMEM;
+
+ nvp->line = 1;
+ nvp->column = 1;
+ return 0;
+}
+
+/* inff_fw_strip_multi_v1 :Some nvram files contain settings for multiple
+ * devices. Strip it down for one device, use domain_nr/bus_nr to determine
+ * which data is to be returned. v1 is the version where nvram is stored
+ * compressed and "devpath" maps to index for valid entries.
+ */
+static void inff_fw_strip_multi_v1(struct nvram_parser *nvp, u16 domain_nr,
+ u16 bus_nr)
+{
+ /* Device path with a leading '=' key-value separator */
+ char pci_path[20];
+ size_t pci_len;
+ char pcie_path[20];
+ size_t pcie_len;
+
+ u32 i, j;
+ bool found;
+ u8 *nvram;
+ u8 id;
+
+ nvram = kzalloc(nvp->nvram_len + 1 + 3 + sizeof(u32), GFP_KERNEL);
+ if (!nvram)
+ goto fail;
+
+ /* min length: devpath0=pcie/1/4/ + 0:x=y */
+ if (nvp->nvram_len < INFF_FW_NVRAM_DEVPATH_LEN + 6)
+ goto fail;
+
+ /* First search for the devpathX and see if it is the configuration
+ * for domain_nr/bus_nr. Search complete nvp
+ */
+ snprintf(pci_path, sizeof(pci_path), "=pci/%d/%d", domain_nr,
+ bus_nr);
+ pci_len = strlen(pci_path);
+ snprintf(pcie_path, sizeof(pcie_path), "=pcie/%d/%d", domain_nr,
+ bus_nr);
+ pcie_len = strlen(pcie_path);
+ found = false;
+ i = 0;
+ while (i < nvp->nvram_len - INFF_FW_NVRAM_DEVPATH_LEN) {
+ /* Format: devpathX=pcie/Y/Z/
+ * Y = domain_nr, Z = bus_nr, X = virtual ID
+ */
+ if (strncmp(&nvp->nvram[i], "devpath", 7) == 0 &&
+ (!strncmp(&nvp->nvram[i + 8], pci_path, pci_len) ||
+ !strncmp(&nvp->nvram[i + 8], pcie_path, pcie_len))) {
+ id = nvp->nvram[i + 7] - '0';
+ found = true;
+ break;
+ }
+ while (nvp->nvram[i] != 0)
+ i++;
+ i++;
+ }
+ if (!found)
+ goto fail;
+
+ /* Now copy all valid entries, release old nvram and assign new one */
+ i = 0;
+ j = 0;
+ while (i < nvp->nvram_len) {
+ if ((nvp->nvram[i] - '0' == id) && (nvp->nvram[i + 1] == ':')) {
+ i += 2;
+ if (strncmp(&nvp->nvram[i], "boardrev", 8) == 0)
+ nvp->boardrev_found = true;
+ while (nvp->nvram[i] != 0) {
+ nvram[j] = nvp->nvram[i];
+ i++;
+ j++;
+ }
+ nvram[j] = 0;
+ j++;
+ }
+ while (nvp->nvram[i] != 0)
+ i++;
+ i++;
+ }
+ kfree(nvp->nvram);
+ nvp->nvram = nvram;
+ nvp->nvram_len = j;
+ return;
+
+fail:
+ kfree(nvram);
+ nvp->nvram_len = 0;
+}
+
+/* inff_fw_strip_multi_v2 :Some nvram files contain settings for multiple
+ * devices. Strip it down for one device, use domain_nr/bus_nr to determine
+ * which data is to be returned. v2 is the version where nvram is stored
+ * uncompressed, all relevant valid entries are identified by
+ * pcie/domain_nr/bus_nr:
+ */
+static void inff_fw_strip_multi_v2(struct nvram_parser *nvp, u16 domain_nr,
+ u16 bus_nr)
+{
+ char prefix[INFF_FW_NVRAM_PCIEDEV_LEN];
+ size_t len;
+ u32 i, j;
+ u8 *nvram;
+
+ nvram = kzalloc(nvp->nvram_len + 1 + 3 + sizeof(u32), GFP_KERNEL);
+ if (!nvram) {
+ nvp->nvram_len = 0;
+ return;
+ }
+
+ /* Copy all valid entries, release old nvram and assign new one.
+ * Valid entries are of type pcie/X/Y/ where X = domain_nr and
+ * Y = bus_nr.
+ */
+ snprintf(prefix, sizeof(prefix), "pcie/%d/%d/", domain_nr, bus_nr);
+ len = strlen(prefix);
+ i = 0;
+ j = 0;
+ while (i < nvp->nvram_len - len) {
+ if (strncmp(&nvp->nvram[i], prefix, len) == 0) {
+ i += len;
+ if (strncmp(&nvp->nvram[i], "boardrev", 8) == 0)
+ nvp->boardrev_found = true;
+ while (nvp->nvram[i] != 0) {
+ nvram[j] = nvp->nvram[i];
+ i++;
+ j++;
+ }
+ nvram[j] = 0;
+ j++;
+ }
+ while (nvp->nvram[i] != 0)
+ i++;
+ i++;
+ }
+ kfree(nvp->nvram);
+ nvp->nvram = nvram;
+ nvp->nvram_len = j;
+}
+
+static void inff_fw_add_defaults(struct nvram_parser *nvp)
+{
+ if (nvp->boardrev_found)
+ return;
+
+ memcpy(&nvp->nvram[nvp->nvram_len], &INFF_FW_DEFAULT_BOARDREV,
+ strlen(INFF_FW_DEFAULT_BOARDREV));
+ nvp->nvram_len += strlen(INFF_FW_DEFAULT_BOARDREV);
+ nvp->nvram[nvp->nvram_len] = '\0';
+ nvp->nvram_len++;
+}
+
+static void inff_fw_add_macaddr(struct nvram_parser *nvp, u8 *mac)
+{
+ int len;
+
+ len = scnprintf(&nvp->nvram[nvp->nvram_len], INFF_FW_MACADDR_LEN + 1,
+ INFF_FW_MACADDR_FMT, mac);
+ WARN_ON(len != INFF_FW_MACADDR_LEN);
+ nvp->nvram_len += len + 1;
+}
+
+/* inff_nvram_strip :Takes a buffer of "<var>=<value>\n" lines read from a fil
+ * and ending in a NUL. Removes carriage returns, empty lines, comment lines,
+ * and converts newlines to NULs. Shortens buffer as needed and pads with NULs.
+ * End of buffer is completed with token identifying length of buffer.
+ */
+static void *inff_fw_nvram_strip(const u8 *data, size_t data_len,
+ u32 *new_length, u16 domain_nr, u16 bus_nr,
+ struct device *dev)
+{
+ struct nvram_parser nvp;
+ u32 pad;
+ u32 token;
+ __le32 token_le;
+ u8 mac[ETH_ALEN];
+
+ if (inff_init_nvram_parser(&nvp, data, data_len) < 0)
+ return NULL;
+
+ if (eth_platform_get_mac_address(dev, mac) == 0)
+ nvp.strip_mac = true;
+
+ while (nvp.pos < data_len) {
+ nvp.state = nv_parser_states[nvp.state](&nvp);
+ if (nvp.state == ST_END)
+ break;
+ }
+ if (nvp.multi_dev_v1) {
+ nvp.boardrev_found = false;
+ inff_fw_strip_multi_v1(&nvp, domain_nr, bus_nr);
+ } else if (nvp.multi_dev_v2) {
+ nvp.boardrev_found = false;
+ inff_fw_strip_multi_v2(&nvp, domain_nr, bus_nr);
+ }
+
+ if (nvp.nvram_len == 0) {
+ kfree(nvp.nvram);
+ return NULL;
+ }
+
+ inff_fw_add_defaults(&nvp);
+
+ if (nvp.strip_mac)
+ inff_fw_add_macaddr(&nvp, mac);
+
+ pad = nvp.nvram_len;
+ *new_length = roundup(nvp.nvram_len + 1, 4);
+ while (pad != *new_length) {
+ nvp.nvram[pad] = 0;
+ pad++;
+ }
+
+ token = *new_length / 4;
+ token = (~token << 16) | (token & 0x0000FFFF);
+ token_le = cpu_to_le32(token);
+
+ memcpy(&nvp.nvram[*new_length], &token_le, sizeof(token_le));
+ *new_length += sizeof(token_le);
+
+ return nvp.nvram;
+}
+
+void inff_fw_nvram_free(void *nvram)
+{
+ kfree(nvram);
+}
+
+struct inff_fw {
+ struct device *dev;
+ struct inff_fw_request *req;
+ u32 curpos;
+ unsigned int board_index;
+ void (*done)(struct device *dev, int err, struct inff_fw_request *req);
+};
+
+#ifdef CONFIG_EFI
+/* In some cases the EFI-var stored nvram contains "ccode=ALL" or "ccode=XV"
+ * to specify "worldwide" compatible settings, but these 2 ccode-s do not work
+ * properly. "ccode=ALL" causes channels 12 and 13 to not be available,
+ * "ccode=XV" causes all 5GHz channels to not be available. So we replace both
+ * with "ccode=X2" which allows channels 12+13 and 5Ghz channels in
+ * no-Initiate-Radiation mode. This means that we will never send on these
+ * channels without first having received valid wifi traffic on the channel.
+ */
+static void inff_fw_fix_efi_nvram_ccode(char *data, unsigned long data_len)
+{
+ char *ccode;
+
+ ccode = strnstr((char *)data, "ccode=ALL", data_len);
+ if (!ccode)
+ ccode = strnstr((char *)data, "ccode=XV\r", data_len);
+ if (!ccode)
+ return;
+
+ ccode[6] = 'X';
+ ccode[7] = '2';
+ ccode[8] = '\r';
+}
+
+static u8 *inff_fw_nvram_from_efi(size_t *data_len_ret)
+{
+ efi_guid_t guid = EFI_GUID(0x74b00bd9, 0x805a, 0x4d61, 0xb5, 0x1f,
+ 0x43, 0x26, 0x81, 0x23, 0xd1, 0x13);
+ unsigned long data_len = 0;
+ efi_status_t status;
+ u8 *data = NULL;
+
+ if (!efi_rt_services_supported(EFI_RT_SUPPORTED_GET_VARIABLE))
+ return NULL;
+
+ status = efi.get_variable(L"nvram", &guid, NULL, &data_len, NULL);
+ if (status != EFI_BUFFER_TOO_SMALL)
+ goto fail;
+
+ data = kmalloc(data_len, GFP_KERNEL);
+ if (!data)
+ goto fail;
+
+ status = efi.get_variable(L"nvram", &guid, NULL, &data_len, data);
+ if (status != EFI_SUCCESS)
+ goto fail;
+
+ inff_fw_fix_efi_nvram_ccode(data, data_len);
+ inff_info("Using nvram EFI variable\n");
+
+ *data_len_ret = data_len;
+ return data;
+fail:
+ kfree(data);
+ return NULL;
+}
+#else
+static inline u8 *inff_fw_nvram_from_efi(size_t *data_len) { return NULL; }
+#endif
+
+static void inff_fw_free_request(struct inff_fw_request *req)
+{
+ struct inff_fw_item *item;
+ int i;
+
+ for (i = 0, item = &req->items[0]; i < req->n_items; i++, item++) {
+ if (item->type == INFF_FW_TYPE_BINARY || item->type == INFF_FW_TYPE_TRXS ||
+ item->type == INFF_FW_TYPE_TRXSE || item->type == INFF_FW_TYPE_HEX)
+ release_firmware(item->binary);
+ else if (item->type == INFF_FW_TYPE_NVRAM)
+ inff_fw_nvram_free(item->nv_data.data);
+ }
+ kfree(req);
+}
+
+static int inff_fw_request_nvram_done(const struct firmware *fw, void *ctx)
+{
+ struct inff_fw *fwctx = ctx;
+ struct inff_fw_item *cur;
+ bool kfree_nvram = false;
+ u32 nvram_length = 0;
+ void *nvram = NULL;
+ u8 *data = NULL;
+ size_t data_len;
+
+ inff_dbg(TRACE, "dev=%s\n", dev_name(fwctx->dev));
+
+ cur = &fwctx->req->items[fwctx->curpos];
+
+ if (fw && fw->data) {
+ data = (u8 *)fw->data;
+ data_len = fw->size;
+ } else {
+ data = inff_fw_nvram_from_efi(&data_len);
+ if (data)
+ kfree_nvram = true;
+ else if (!(cur->flags & INFF_FW_REQF_OPTIONAL))
+ goto fail;
+ }
+
+ if (data)
+ nvram = inff_fw_nvram_strip(data, data_len, &nvram_length,
+ fwctx->req->domain_nr,
+ fwctx->req->bus_nr,
+ fwctx->dev);
+
+ if (kfree_nvram)
+ kfree(data);
+
+ release_firmware(fw);
+ if (!nvram && !(cur->flags & INFF_FW_REQF_OPTIONAL))
+ goto fail;
+
+ inff_dbg(TRACE, "nvram %p len %d\n", nvram, nvram_length);
+ cur->nv_data.data = nvram;
+ cur->nv_data.len = nvram_length;
+ return 0;
+
+fail:
+ return -ENOENT;
+}
+
+static int inff_fw_complete_request(const struct firmware *fw,
+ struct inff_fw *fwctx)
+{
+ struct inff_fw_item *cur = &fwctx->req->items[fwctx->curpos];
+ int ret = 0;
+
+ inff_dbg(TRACE, "firmware %s %sfound\n", cur->path, fw ? "" : "not ");
+
+ switch (cur->type) {
+ case INFF_FW_TYPE_NVRAM:
+ ret = inff_fw_request_nvram_done(fw, fwctx);
+ break;
+ case INFF_FW_TYPE_BINARY:
+ case INFF_FW_TYPE_TRXSE:
+ case INFF_FW_TYPE_TRXS:
+ case INFF_FW_TYPE_CLM:
+ case INFF_FW_TYPE_HEX:
+ if (fw)
+ cur->binary = fw;
+ else
+ ret = -ENOENT;
+ break;
+ default:
+ /* something fishy here so bail out early */
+ inff_err("unknown fw type: %d\n", cur->type);
+ release_firmware(fw);
+ ret = -EINVAL;
+ }
+
+ return (cur->flags & INFF_FW_REQF_OPTIONAL) ? 0 : ret;
+}
+
+static char *inff_alt_fw_path(const char *path, const char *board_type)
+{
+ char base[INFF_FW_NAME_LEN];
+ const char *suffix;
+ char *ret;
+
+ if (!board_type)
+ return NULL;
+
+ suffix = strrchr(path, '.');
+ if (!suffix || suffix == path)
+ return NULL;
+
+ /* strip extension at the end */
+ strscpy(base, path, INFF_FW_NAME_LEN);
+ base[suffix - path] = 0;
+
+ ret = kasprintf(GFP_KERNEL, "%s.%s%s", base, board_type, suffix);
+ if (!ret)
+ inff_err("out of memory allocating firmware path for '%s'\n",
+ path);
+
+ inff_dbg(TRACE, "FW alt path: %s\n", ret);
+
+ return ret;
+}
+
+static int inff_fw_request_firmware(const struct firmware **fw,
+ struct inff_fw *fwctx)
+{
+ struct inff_fw_item *cur = &fwctx->req->items[fwctx->curpos];
+ unsigned int i;
+ int ret;
+
+ /* Files can be board-specific, first try board-specific paths */
+ for (i = 0; i < ARRAY_SIZE(fwctx->req->board_types); i++) {
+ char *alt_path;
+
+ if (!fwctx->req->board_types[i])
+ goto fallback;
+ alt_path = inff_alt_fw_path(cur->path,
+ fwctx->req->board_types[i]);
+ if (!alt_path)
+ goto fallback;
+
+ ret = request_firmware_direct(fw, alt_path, fwctx->dev);
+ kfree(alt_path);
+ if (ret)
+ inff_info("no board-specific nvram available (ret=%d), device will use %s\n",
+ ret, cur->path);
+ else
+ return ret;
+ }
+
+fallback:
+ return request_firmware(fw, cur->path, fwctx->dev);
+}
+
+static void inff_fw_request_done(const struct firmware *fw, void *ctx)
+{
+ struct inff_fw *fwctx = ctx;
+ struct inff_fw_item *cur = &fwctx->req->items[fwctx->curpos];
+ char alt_path[INFF_FW_NAME_LEN];
+ int ret;
+
+ if (!fw && cur->type == INFF_FW_TYPE_TRXS) {
+ strscpy(alt_path, cur->path, INFF_FW_NAME_LEN);
+ /* strip 'se' from .trxse at the end */
+ //alt_path[strlen(alt_path) - ] = 0;
+ ret = request_firmware(&fw, alt_path, fwctx->dev);
+ if (!ret)
+ cur->path = alt_path;
+ }
+ if (!fw && cur->type == INFF_FW_TYPE_TRXSE) {
+ strscpy(alt_path, cur->path, INFF_FW_NAME_LEN);
+ /* strip 'se' from .trxse at the end */
+ alt_path[strlen(alt_path) - 2] = 0;
+ ret = request_firmware(&fw, alt_path, fwctx->dev);
+ if (!ret)
+ cur->path = alt_path;
+ }
+
+ ret = inff_fw_complete_request(fw, fwctx);
+
+ while (ret == 0 && ++fwctx->curpos < fwctx->req->n_items) {
+ inff_fw_request_firmware(&fw, fwctx);
+ ret = inff_fw_complete_request(fw, ctx);
+ }
+
+ if (ret) {
+ inff_fw_free_request(fwctx->req);
+ fwctx->req = NULL;
+ }
+ fwctx->done(fwctx->dev, ret, fwctx->req);
+ kfree(fwctx);
+}
+
+static void inff_fw_request_done_alt_path(const struct firmware *fw, void *ctx)
+{
+ struct inff_fw *fwctx = ctx;
+ struct inff_fw_item *first = &fwctx->req->items[0];
+ const char *board_type, *alt_path;
+ int ret = 0;
+
+ if (fw) {
+ inff_fw_request_done(fw, ctx);
+ return;
+ }
+
+ /* Try next board firmware */
+ if (fwctx->board_index < ARRAY_SIZE(fwctx->req->board_types)) {
+ board_type = fwctx->req->board_types[fwctx->board_index++];
+ if (!board_type)
+ goto fallback;
+ alt_path = inff_alt_fw_path(first->path, board_type);
+ if (!alt_path)
+ goto fallback;
+
+ if (!fw) {
+ ret = request_firmware_nowait(THIS_MODULE, true, alt_path,
+ fwctx->dev, GFP_KERNEL, fwctx,
+ inff_fw_request_done_alt_path);
+ }
+ kfree(alt_path);
+
+ if (ret < 0)
+ inff_fw_request_done(fw, ctx);
+ return;
+ }
+
+fallback:
+ /* Fall back to canonical path if board firmware not found */
+ ret = request_firmware_nowait(THIS_MODULE, true, first->path,
+ fwctx->dev, GFP_KERNEL, fwctx,
+ inff_fw_request_done);
+
+ if (ret < 0)
+ inff_fw_request_done(fw, ctx);
+}
+
+static bool inff_fw_request_is_valid(struct inff_fw_request *req)
+{
+ struct inff_fw_item *item;
+ int i;
+
+ if (!req->n_items)
+ return false;
+
+ for (i = 0, item = &req->items[0]; i < req->n_items; i++, item++) {
+ if (!item->path)
+ return false;
+ }
+ return true;
+}
+
+int inff_fw_get_firmwares(struct device *dev, struct inff_fw_request *req,
+ void (*fw_cb)(struct device *dev, int err,
+ struct inff_fw_request *req))
+{
+ struct inff_fw_item *first = &req->items[0];
+ struct inff_fw *fwctx;
+ char *alt_path = NULL;
+ int ret;
+
+ inff_dbg(TRACE, "dev=%s\n", dev_name(dev));
+ if (!fw_cb)
+ return -EINVAL;
+
+ if (!inff_fw_request_is_valid(req))
+ return -EINVAL;
+
+ fwctx = kzalloc_obj(*fwctx);
+ if (!fwctx)
+ return -ENOMEM;
+
+ fwctx->dev = dev;
+ fwctx->req = req;
+ fwctx->done = fw_cb;
+
+ /* First try alternative board-specific path if any */
+ if (fwctx->req->board_types[0])
+ alt_path = inff_alt_fw_path(first->path,
+ fwctx->req->board_types[0]);
+ if (alt_path) {
+ fwctx->board_index++;
+ ret = request_firmware_nowait(THIS_MODULE, true, alt_path,
+ fwctx->dev, GFP_KERNEL, fwctx,
+ inff_fw_request_done_alt_path);
+ kfree(alt_path);
+ } else {
+ ret = request_firmware_nowait(THIS_MODULE, true, first->path,
+ fwctx->dev, GFP_KERNEL, fwctx,
+ inff_fw_request_done);
+ }
+ if (ret < 0)
+ inff_fw_request_done(NULL, fwctx);
+
+ return 0;
+}
+
+struct inff_fw_request *
+inff_fw_alloc_request(char mp_path[], u32 chip, u32 chiprev,
+ const struct inff_firmware_mapping mapping_table[],
+ u32 table_size, struct inff_fw_name *fwnames,
+ u32 n_fwnames)
+{
+ struct inff_fw_request *fwreq;
+ char chipname[12];
+ size_t mp_path_len;
+ u32 i, j;
+ char end = '\0';
+ bool boot_fw_req = 0;
+
+ if (chiprev >= BITS_PER_TYPE(u32)) {
+ inff_err("Invalid chip revision %u\n", chiprev);
+ return NULL;
+ }
+
+ for (i = 0; i < table_size; i++) {
+ if (mapping_table[i].chipid == chip &&
+ mapping_table[i].revmask & BIT(chiprev))
+ break;
+ }
+
+ inff_chip_name(chip, chiprev, chipname, sizeof(chipname));
+
+ if (i == table_size) {
+ inff_err("Unknown chip %s\n", chipname);
+ return NULL;
+ }
+
+ fwreq = kzalloc_flex(*fwreq, items, n_fwnames);
+ if (!fwreq)
+ return NULL;
+
+ inff_info("using %s for chip %s\n",
+ mapping_table[i].fw_base, chipname);
+
+ mp_path_len = strnlen(mp_path, INFF_FW_ALTPATH_LEN);
+ if (mp_path_len)
+ end = mp_path[mp_path_len - 1];
+
+ fwreq->n_items = n_fwnames;
+
+ for (j = 0; j < n_fwnames; j++) {
+ fwreq->items[j].path = fwnames[j].path;
+ fwnames[j].path[0] = '\0';
+ /* check if firmware path is provided by module parameter */
+ if (mp_path[0] != '\0') {
+ strscpy(fwnames[j].path, mp_path,
+ INFF_FW_NAME_LEN);
+
+ if (end != '/') {
+ strlcat(fwnames[j].path, "/",
+ INFF_FW_NAME_LEN);
+ }
+ }
+
+ if (boot_fw_req) {
+ char bootfw_name[INFF_FW_NAME_LEN];
+
+ snprintf(bootfw_name, INFF_FW_NAME_LEN, "%s-bootfw",
+ mapping_table[i].fw_base);
+ strlcat(fwnames[j].path, bootfw_name, INFF_FW_NAME_LEN);
+ } else {
+ strlcat(fwnames[j].path, mapping_table[i].fw_base,
+ INFF_FW_NAME_LEN);
+ }
+ strlcat(fwnames[j].path, fwnames[j].extension,
+ INFF_FW_NAME_LEN);
+ fwreq->items[j].path = fwnames[j].path;
+
+ if (!strncmp(fwnames[j].extension, ".trxse", 6)) {
+ fwreq->items[j].type = INFF_FW_TYPE_TRXSE;
+ } else if (!strncmp(fwnames[j].extension, ".trxs", 5)) {
+ fwreq->items[j].type = INFF_FW_TYPE_TRXS;
+ } else if (!strncmp(fwnames[j].extension, ".txt", 4)) {
+ fwreq->items[j].type = INFF_FW_TYPE_NVRAM;
+ fwreq->items[j].flags = INFF_FW_REQF_OPTIONAL;
+ } else if (!strncmp(fwnames[j].extension, ".clm_blob", 9)) {
+ fwreq->items[j].type = INFF_FW_TYPE_CLM;
+ fwreq->items[j].flags = INFF_FW_REQF_OPTIONAL;
+ } else if (!strncmp(fwnames[j].extension, ".hex", 4)) {
+ fwreq->items[j].type = INFF_FW_TYPE_HEX;
+ if (chip == INF_CC_CYW5591X_CHIP_ID)
+ boot_fw_req = 1;
+ }
+ }
+
+ return fwreq;
+}
+
+struct inff_fw_request *
+inff_prepare_fw_request(char mp_path[], struct inff_chip *ci,
+ const struct inff_firmware_mapping *name_map,
+ int map_size, const char *board_type)
+{
+ struct inff_fw_request *fwreq;
+ struct inff_chip_specific *chip_spec = &ci->chip_spec;
+ struct inff_fw_dataset *fw_data = &chip_spec->fwdata[0];
+ u32 num_fwnames = 0;
+ struct inff_fw_name fwnames[INFF_FW_BIN_MAX_TYPE + INFF_FW_CP_BIN_MAX_TYPE];
+
+ if (ci->chip == INF_CC_CYW5591X_CHIP_ID) {
+ if (fw_data[INFF_FW_CP].fwnames.extension) {
+ fwnames[INFF_FW_CP].extension = fw_data[INFF_FW_CP].fwnames.extension;
+ fwnames[INFF_FW_CP].path = fw_data[INFF_FW_CP].fwnames.path;
+ num_fwnames++;
+ } else {
+ inff_err("chip 0x%x uninitialized with fw code file extension\n",
+ ci->chip);
+ return NULL;
+ }
+
+ if (fw_data[INFF_FW_BOOT].fwnames.extension) {
+ fwnames[INFF_FW_BOOT].extension = fw_data[INFF_FW_BOOT].fwnames.extension;
+ fwnames[INFF_FW_BOOT].path = fw_data[INFF_FW_BOOT].fwnames.path;
+ num_fwnames++;
+ }
+ } else {
+ if (fw_data[INFF_FW_CODE].fwnames.extension) {
+ fwnames[INFF_FW_CODE].extension = fw_data[INFF_FW_CODE].fwnames.extension;
+ fwnames[INFF_FW_CODE].path = fw_data[INFF_FW_CODE].fwnames.path;
+ num_fwnames++;
+ } else {
+ inff_err("chip 0x%x uninitialized with fw code file extension\n",
+ ci->chip);
+ return NULL;
+ }
+ if (fw_data[INFF_FW_NVRAM].fwnames.extension) {
+ fwnames[INFF_FW_NVRAM].extension = fw_data[INFF_FW_NVRAM].fwnames.extension;
+ fwnames[INFF_FW_NVRAM].path = fw_data[INFF_FW_NVRAM].fwnames.path;
+ num_fwnames++;
+ }
+ if (fw_data[INFF_FW_CLM].fwnames.extension) {
+ fwnames[INFF_FW_CLM].extension = fw_data[INFF_FW_CLM].fwnames.extension;
+ fwnames[INFF_FW_CLM].path = fw_data[INFF_FW_CLM].fwnames.path;
+ num_fwnames++;
+ }
+ }
+
+ fwreq = inff_fw_alloc_request(mp_path, ci->chip, ci->chiprev, name_map,
+ map_size, fwnames, num_fwnames);
+ if (!fwreq)
+ return NULL;
+
+ fwreq->board_types[0] = board_type;
+
+ return fwreq;
+}
diff --git a/drivers/net/wireless/infineon/inffmac/firmware.h b/drivers/net/wireless/infineon/inffmac/firmware.h
new file mode 100644
index 000000000000..0d36e5d6eb2d
--- /dev/null
+++ b/drivers/net/wireless/infineon/inffmac/firmware.h
@@ -0,0 +1,100 @@
+/* SPDX-License-Identifier: ISC */
+/*
+ * Copyright (c) 2013 Broadcom Corporation
+ *
+ * Copyright (c) 2025-2026, Infineon Technologies AG, or an affiliate of Infineon Technologies AG.
+ * All rights reserved.
+ */
+
+#ifndef INFF_FIRMWARE_H
+#define INFF_FIRMWARE_H
+
+#define INFF_FW_REQF_OPTIONAL 0x0001
+
+#define INFF_FW_NAME_LEN 320
+
+#define INFF_FW_MAX_BOARD_TYPES 8
+
+#define INFF_FW_DEFAULT_PATH "infineon/"
+
+/* forward declaration */
+struct inff_chip;
+
+/**
+ * struct inff_firmware_mapping - Used to map chipid/revmask to firmware
+ * filename and nvram filename. Each bus type implementation should create
+ * a table of firmware mappings (using the macros defined below).
+ *
+ * @chipid: ID of chip.
+ * @revmask: bitmask of revisions, e.g. 0x10 means rev 4 only, 0xf means rev 0-3
+ * @fw: name of the firmware file.
+ * @nvram: name of nvram file.
+ */
+struct inff_firmware_mapping {
+ u32 chipid;
+ u32 revmask;
+ const char *fw_base;
+};
+
+/* Firmware and Country Local Matrix files */
+#define INFF_FW_ENTRY(chipid, mask, name) \
+ { chipid, mask, INFF_ ## name ## _FIRMWARE_BASENAME }
+
+void inff_fw_nvram_free(void *nvram);
+
+enum inff_fw_type {
+ INFF_FW_TYPE_BINARY,
+ INFF_FW_TYPE_NVRAM,
+ INFF_FW_TYPE_TRXSE,
+ INFF_FW_TYPE_TRXS,
+ INFF_FW_TYPE_CLM,
+ INFF_FW_TYPE_HEX,
+};
+
+struct inff_fw_item {
+ const char *path;
+ enum inff_fw_type type;
+ u16 flags;
+ union {
+ const struct firmware *binary;
+ struct {
+ void *data;
+ u32 len;
+ } nv_data;
+ };
+};
+
+struct inff_fw_request {
+ u16 domain_nr;
+ u16 bus_nr;
+ u32 n_items;
+ const char *board_types[INFF_FW_MAX_BOARD_TYPES];
+ struct inff_fw_item items[] __counted_by(n_items);
+};
+
+struct inff_fw_name {
+ const char *extension;
+ char *path;
+};
+
+struct inff_fw_request *
+inff_fw_alloc_request(char mp_path[], u32 chip, u32 chiprev,
+ const struct inff_firmware_mapping mapping_table[],
+ u32 table_size, struct inff_fw_name *fwnames,
+ u32 n_fwnames);
+
+/*
+ * Request firmware(s) asynchronously. When the asynchronous request
+ * fails it will not use the callback, but call device_release_driver()
+ * instead which will call the driver .remove() callback.
+ */
+int inff_fw_get_firmwares(struct device *dev, struct inff_fw_request *req,
+ void (*fw_cb)(struct device *dev, int err,
+ struct inff_fw_request *req));
+
+struct inff_fw_request *
+inff_prepare_fw_request(char mp_path[], struct inff_chip *ci,
+ const struct inff_firmware_mapping *name_map,
+ int map_size, const char *board_type);
+
+#endif /* INFF_FIRMWARE_H */
--
2.43.0
^ permalink raw reply related [flat|nested] 26+ messages in thread* [PATCH wireless-next v3 03/25] wifi: inffmac: add dfu.c/h
2026-09-02 16:50 [PATCH wireless-next v3 00/25] wifi: inffmac: introducing a driver for Infineon's new generation chipsets Gokul Sivakumar
2026-09-02 16:50 ` [PATCH wireless-next v3 01/25] wifi: inffmac: add a new driver directory for infineon WLAN vendor Gokul Sivakumar
2026-09-02 16:50 ` [PATCH wireless-next v3 02/25] wifi: inffmac: add firmware.c/h Gokul Sivakumar
@ 2026-09-02 16:50 ` Gokul Sivakumar
2026-09-02 16:50 ` [PATCH wireless-next v3 04/25] wifi: inffmac: add trxhdr.h Gokul Sivakumar
` (21 subsequent siblings)
24 siblings, 0 replies; 26+ messages in thread
From: Gokul Sivakumar @ 2026-09-02 16:50 UTC (permalink / raw)
To: linux-wireless, johannes
Cc: arend.vanspriel, wlan-kernel-dev-list, gokulkumar.sivakumar
Driver implementation for doing Device Firmware Updates (DFU) on the WLAN
Device. Like for example Connecivity Processor (CP) chipsets have multiple
offloaded functionalities, which can be upgraded using this DFU support.
Signed-off-by: Gokul Sivakumar <gokulkumar.sivakumar@infineon.com>
---
drivers/net/wireless/infineon/inffmac/dfu.c | 1084 +++++++++++++++++++
drivers/net/wireless/infineon/inffmac/dfu.h | 124 +++
2 files changed, 1208 insertions(+)
create mode 100644 drivers/net/wireless/infineon/inffmac/dfu.c
create mode 100644 drivers/net/wireless/infineon/inffmac/dfu.h
diff --git a/drivers/net/wireless/infineon/inffmac/dfu.c b/drivers/net/wireless/infineon/inffmac/dfu.c
new file mode 100644
index 000000000000..fecd00a6efca
--- /dev/null
+++ b/drivers/net/wireless/infineon/inffmac/dfu.c
@@ -0,0 +1,1084 @@
+// SPDX-License-Identifier: ISC
+/*
+ * Copyright (c) 2025-2026, Infineon Technologies AG, or an affiliate of Infineon Technologies AG.
+ * All rights reserved.
+ */
+
+/*
+ * New IOT specific chips supports new FW download flow to NON-Volatile and Volatile memory.
+ * This is different from the regular Combo chip flow which downloads to RAM/SRAM.
+ * The update will be done to RRAM/FLASH/PSRAM portion of this category of chips.
+ * This would be done in
+ * 1. first time update to the product
+ * 2. dynamic update only to WRLS_Core
+ * 3. complete Product gets updated
+ */
+
+#include <linux/types.h>
+#include <linux/fs.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/atomic.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/mmc/sdio_func.h>
+#include <linux/mmc/card.h>
+#include <linux/mmc/sdio_ids.h>
+#include <linux/mmc/host.h>
+
+#include "main.h"
+#include "sdio.h"
+#include "chip.h"
+#include "dev_cmd.h"
+#include "feature.h"
+#include "bus_proto.h"
+#include "dfu.h"
+#include "firmware.h"
+
+struct inff_dfu_hex_data {
+ int addr_mode; /* mode of destination addr */
+ u16 hi_addr; /* high byte of destination addr */
+ u32 dest_addr; /* destination addr */
+ u8 *ds; /* allocate memory for hex file data */
+};
+
+static char *inff_dfu_reg_addr2str(u32 addr)
+{
+ switch (addr) {
+ case SDIOD_D2H_MSG_0:
+ return "SDIOD_D2H_MSG_0";
+ case SDIOD_D2H_MSG_1:
+ return "SDIOD_D2H_MSG_1";
+ case SDIOD_D2H_MSG_4:
+ return "SDIOD_D2H_MSG_4";
+ case SDIOD_H2D_MSG_0:
+ return "SDIOD_H2D_MSG_0";
+ case SDIOD_H2D_MSG_4:
+ return "SDIOD_H2D_MSG_4";
+ default:
+ return "Non DEVICE_TO_HOST address";
+ }
+}
+
+static int inff_dfu_msg_readl(struct inff_sdio_dev *sdiodev, u32 addr, u32 *msg)
+{
+ u8 readmsg;
+ int err = 0;
+ *msg = 0;
+
+ /* Security handshake registers are 1 byte registers in SDIO clock domain */
+ readmsg = inff_sdiod_func_rb_ext(sdiodev->func3, addr, &err);
+ if (err) {
+ inff_err("%s err %d", inff_dfu_reg_addr2str(addr), err);
+ return -EFAULT;
+ }
+ *msg |= readmsg;
+
+ readmsg = inff_sdiod_func_rb_ext(sdiodev->func3, addr + 1, &err);
+ if (err) {
+ inff_err("%s err %d", inff_dfu_reg_addr2str(addr + 1), err);
+ return -EFAULT;
+ }
+ *msg |= (readmsg << 8);
+
+ readmsg = inff_sdiod_func_rb_ext(sdiodev->func3, addr + 2, &err);
+ if (err) {
+ inff_err("%s err %d", inff_dfu_reg_addr2str(addr + 2), err);
+ return -EFAULT;
+ }
+ *msg |= (readmsg << 16);
+
+ readmsg = inff_sdiod_func_rb_ext(sdiodev->func3, addr + 3, &err);
+ if (err) {
+ inff_err("%s err %d", inff_dfu_reg_addr2str(addr + 3), err);
+ return -EFAULT;
+ }
+ *msg |= (readmsg << 24);
+
+ return err;
+}
+
+static int inff_dfu_msg_writeb(struct inff_sdio_dev *sdiodev, u8 msg)
+{
+ int err = 0;
+
+ inff_sdiod_func_wb(sdiodev, sdiodev->func3, SDIOD_H2D_MSG_0, msg, &err);
+ if (err) {
+ inff_err("err %d", err);
+ return -EFAULT;
+ }
+ return 0;
+}
+
+static int inff_dfu_msg_writel(struct inff_sdio_dev *sdiodev, u32 msg)
+{
+ int err = 0;
+
+ /* Security handshake registers are 1 byte registers in SDIO clock domain */
+ inff_sdiod_func_wb(sdiodev, sdiodev->func3, SDIOD_H2D_MSG_4, ((msg >> 0) & 0xFF), &err);
+ if (err) {
+ inff_err("%s err %d", inff_dfu_reg_addr2str(SDIOD_H2D_MSG_4), err);
+ return -EFAULT;
+ }
+ inff_sdiod_func_wb(sdiodev, sdiodev->func3, (SDIOD_H2D_MSG_4 + 1),
+ ((msg >> 8) & 0xFF), &err);
+ if (err) {
+ inff_err("%s err %d", inff_dfu_reg_addr2str(SDIOD_H2D_MSG_4 + 1), err);
+ return -EFAULT;
+ }
+ inff_sdiod_func_wb(sdiodev, sdiodev->func3, (SDIOD_H2D_MSG_4 + 2),
+ ((msg >> 16) & 0xFF), &err);
+ if (err) {
+ inff_err("%s err %d", inff_dfu_reg_addr2str(SDIOD_H2D_MSG_4 + 2), err);
+ return -EFAULT;
+ }
+ inff_sdiod_func_wb(sdiodev, sdiodev->func3, (SDIOD_H2D_MSG_4 + 3),
+ ((msg >> 24) & 0xFF), &err);
+ if (err) {
+ inff_err("%s err %d", inff_dfu_reg_addr2str(SDIOD_H2D_MSG_4 + 3), err);
+ return -EFAULT;
+ }
+
+ return 0;
+}
+
+static int inff_dfu_wait_msg(struct inff_sdio_dev *sdiodev, u32 addr, u32 msg)
+{
+ u32 readmsg = 0;
+ u32 readycnt = 0;
+ int err = 0;
+
+ while (readmsg != msg) {
+ usleep_range(INFF_DFU_CP_ROM_BOOT_DELAY, INFF_DFU_CP_ROM_BOOT_DELAY + 10);
+
+ /* Read the REG to check if desired message is ready */
+ readmsg = inff_sdiod_func_rb(sdiodev, sdiodev->func3, addr, &err);
+ if (err) {
+ inff_err("msg read failed %d", err);
+ return -EFAULT;
+ }
+
+ readycnt++;
+ if (readycnt == INFF_DFU_CP_IORDY_CNT) {
+ inff_err("msg = 0x%x expected = 0x%x\n", readmsg, msg);
+ return -EFAULT;
+ }
+ }
+
+ inff_dbg(SDIO, "msg = 0x%x cnt = %d", msg, readycnt);
+ return 0;
+}
+
+static u16 inff_dfu_extract_fw_hex_field(const char *fw_data, u16 sp, u16 cn)
+{
+ char field[8];
+ u16 v;
+
+ strscpy(field, fw_data + sp, cn + 1);
+ field[cn] = '\0';
+
+ if (!kstrtou16(field, 16, &v))
+ return v;
+
+ return 0;
+}
+
+static int inff_dfu_get_next_line_len(const u8 *fw_data)
+{
+ u32 str_len = 0;
+ u8 *line_end = NULL;
+
+ line_end = strchr(fw_data, '\n');
+ if (*fw_data != ':' || !line_end)
+ return 0;
+
+ str_len = (u32)(line_end - fw_data);
+ inff_dbg(SDIOEXT, "next line len = %d", str_len);
+
+ return str_len;
+}
+
+static int inff_dfu_sdio_transfer_membytes(struct inff_sdio_dev *sdiodev, u32 address,
+ u8 *data, u32 size)
+{
+ int err = 0;
+ u32 block1_offset = 0;
+ u32 block2_addr = 0;
+ u16 block1_size = 0;
+ u16 block2_size = 0;
+ u8 *block2_data = NULL;
+
+ do {
+ /* To avoid SDIO access crosses AXI 4k address boundaries crossing */
+ if (((address & SDIOD_ADDR_BOUND_MASK) + size) > SDIOD_ADDR_BOUND) {
+ inff_dbg(SDIO, "data cross 4K boundary\n");
+ /* The 1st 4k packet */
+ block1_offset = address & SDIOD_ADDR_BOUND_MASK;
+ block1_size = (SDIOD_ADDR_BOUND - block1_offset);
+
+ err = inff_sdiod_ramrw(sdiodev, true, address,
+ data, block1_size);
+ if (err)
+ break;
+
+ /* The 2nd 4k packet */
+ block2_addr = address + block1_size;
+ block2_size = size - block1_size;
+ block2_data = data + block1_size;
+ err = inff_sdiod_ramrw(sdiodev, true, block2_addr,
+ block2_data, block2_size);
+ } else {
+ err = inff_sdiod_ramrw(sdiodev, true, address, data, size);
+ }
+ } while (false);
+
+ return err;
+}
+
+static u32 inff_dfu_get_hfd_from_fw_data(const u8 **fw_data,
+ struct inff_dfu_hex_data *hfd)
+{
+ int str_len;
+ u16 num_bytes, addr, data_pos, type, w, i;
+ u32 abs_base_addr32 = 0;
+ u32 nbytes = 0;
+
+ if (!hfd->ds) {
+ inff_err("String or data string NULL");
+ return nbytes;
+ }
+
+ while (nbytes == 0) {
+ str_len = inff_dfu_get_next_line_len(*fw_data);
+
+ if (str_len == 0) {
+ break;
+ } else if (str_len > INFF_DFU_CP_HEX_DATA_OFFSET) {
+ num_bytes = inff_dfu_extract_fw_hex_field(*fw_data,
+ INFF_DFU_CP_HEX_NUMB_OFFSET,
+ INFF_DFU_CP_HEX_NUMB_LEN);
+ addr = inff_dfu_extract_fw_hex_field(*fw_data,
+ INFF_DFU_CP_HEX_ADDR_OFFSET,
+ INFF_DFU_CP_HEX_ADDR_LEN);
+ type = inff_dfu_extract_fw_hex_field(*fw_data,
+ INFF_DFU_CP_HEX_TYPE_OFFSET,
+ INFF_DFU_CP_HEX_TYPE_LEN);
+ data_pos = INFF_DFU_CP_HEX_DATA_OFFSET;
+
+ for (i = 0; i < num_bytes; i++) {
+ w = inff_dfu_extract_fw_hex_field(*fw_data, data_pos,
+ INFF_DFU_CP_HEX_NUMB_LEN);
+ hfd->ds[i] = (u8)(w & 0x00FF);
+ data_pos += INFF_DFU_CP_HEX_NUMB_LEN;
+ }
+ switch (type) {
+ case INFF_DFU_CP_HEX_LINE_TYPE_EXT_ADDR:
+ hfd->hi_addr = (hfd->ds[0] << 8) | hfd->ds[1];
+ hfd->addr_mode = INFF_DFU_CP_ADDR_MODE_EXTENDED;
+ inff_dbg(SDIO, "hfd->hi_addr0x%x ", hfd->hi_addr);
+ break;
+ case INFF_DFU_CP_HEX_LINE_TYPE_EXT_SEGMENT_ADDR:
+ hfd->hi_addr = (hfd->ds[0] << 8) | hfd->ds[1];
+ hfd->addr_mode = INFF_DFU_CP_ADDR_MODE_SEGMENT;
+ break;
+ case INFF_DFU_CP_HEX_LINE_TYPE_ABS_32BIT_ADDR:
+ abs_base_addr32 = (hfd->ds[0] << 24) | (hfd->ds[1] << 16) |
+ (hfd->ds[2] << 8) | hfd->ds[3];
+ hfd->addr_mode = INFF_DFU_CP_ADDR_MODE_LINEAR32;
+ break;
+ case INFF_DFU_CP_HEX_LINE_TYPE_DATA:
+ hfd->dest_addr = addr;
+ if (hfd->addr_mode == INFF_DFU_CP_ADDR_MODE_EXTENDED)
+ hfd->dest_addr += (hfd->hi_addr << 16);
+ else if (hfd->addr_mode == INFF_DFU_CP_ADDR_MODE_SEGMENT)
+ hfd->dest_addr += (hfd->hi_addr << 4);
+ else if (hfd->addr_mode == INFF_DFU_CP_ADDR_MODE_LINEAR32)
+ hfd->dest_addr += abs_base_addr32;
+ nbytes = num_bytes;
+ break;
+ }
+ }
+
+ /* End of file reached, exit loop */
+ if (type == INFF_DFU_CP_HEX_LINE_TYPE_END_OF_DATA) {
+ inff_dbg(SDIO, "End of data\n");
+ break;
+ }
+
+ /* move to next line */
+ *fw_data += str_len + 1;
+ }
+
+ inff_dbg(SDIO, "nbytes [%d]\n", nbytes);
+ return nbytes;
+}
+
+static int inff_dfu_cp_fw_img_download(struct inff_sdio_dev *sdiodev, const struct firmware *fw)
+{
+ struct inff_dfu_hex_data hfd = {INFF_DFU_CP_ADDR_MODE_EXTENDED, 0, 0, NULL};
+ const u8 *app_data = NULL;
+ u8 *mem_blk = NULL;
+ u8 *mem_ptr = NULL;
+ u32 rd;
+ int ret = 0;
+ u8 sn = 1, nesn = 0;
+ u32 addrf = 0;
+ u32 readmsg = 0;
+ u16 bootfw_rsp_cnt = 0;
+
+ app_data = fw->data;
+ mem_blk = kmalloc(INFF_DFU_CP_BLK_SIZE + INFF_DFU_CP_SD_ALIGN, GFP_KERNEL);
+ if (!mem_blk) {
+ inff_err("Fail to allocate memory for FWDL block");
+ ret = -ENOMEM;
+ goto err;
+ }
+ mem_ptr = mem_blk;
+
+ if ((u32)(uintptr_t)mem_ptr % INFF_DFU_CP_SD_ALIGN)
+ mem_ptr += (INFF_DFU_CP_SD_ALIGN - ((u32)(uintptr_t)mem_blk %
+ INFF_DFU_CP_SD_ALIGN));
+
+ hfd.ds = kmalloc(INFF_DFU_CP_MAX_STR_LEN, GFP_KERNEL);
+ if (!hfd.ds) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ /* Wait for boot fw ready message from Device */
+ ret = inff_dfu_wait_msg(sdiodev, SDIOD_D2H_MSG_1, INFF_DFU_CP_D2H_MSG_BOOTFW_READY);
+ if (ret)
+ goto err;
+
+ while ((rd = inff_dfu_get_hfd_from_fw_data(&app_data, &hfd)) > 0) {
+ u32 start_addr, start_data, end_addr, end_data, i, wbc, pad;
+
+ inff_dbg(SDIO, "SN = %d, NESN = %d, addr = 0x%x", sn, nesn, addrf);
+ inff_dbg(SDIOEXT, "\tread %d bytes at address %08x", rd, hfd.dest_addr);
+
+ start_addr = INFF_DFU_CP_MEM_OFFSET | (CYW5591X_FWDWNLD_RAM_WR_ADDR &
+ INFF_DFU_CP_START_ADDR_MASK);
+ wbc = 0;
+
+ /**
+ * Make sure the start address is 4 byte aligned to avoid alignment issues
+ * with SD host controllers
+ */
+ if (!ISALIGNED(start_addr, INFF_DFU_CP_HEX_ALIGN_LEN)) {
+ pad = start_addr % INFF_DFU_CP_HEX_ALIGN_LEN;
+ start_addr = ROUNDDN(start_addr, INFF_DFU_CP_HEX_ALIGN_LEN);
+ inff_dbg(SDIO, "Padding start address : %d bytes\n", pad);
+ start_data = inff_sdiod_readl(sdiodev, start_addr, &ret);
+ if (ret) {
+ inff_err("start_addr:0x%x inff_sdiod_readl error:%d",
+ start_addr, ret);
+ goto err;
+ }
+ for (i = 0; i < pad; i++, wbc++)
+ mem_ptr[wbc] = (u8)((u8 *)&start_data)[i];
+ }
+
+ /* length */
+ mem_ptr[0] = rd;
+ mem_ptr[1] = rd >> 8;
+ wbc += 2;
+
+ /* address */
+ mem_ptr[2] = hfd.dest_addr;
+ mem_ptr[3] = hfd.dest_addr >> 8;
+ mem_ptr[4] = hfd.dest_addr >> 16;
+ mem_ptr[5] = hfd.dest_addr >> 24;
+
+ wbc += 4;
+
+ inff_dbg(SDIO, "addr :0x%x\n", hfd.dest_addr);
+
+ memcpy(&mem_ptr[wbc], hfd.ds, rd);
+ wbc += rd;
+
+ inff_dbg(SDIO, "data copied %d bytes\n", rd);
+ /* Make sure the length is multiple of 4bytes to avoid alignment issues
+ * with SD host controllers
+ */
+ end_addr = start_addr + wbc;
+
+ if (!ISALIGNED(end_addr, 4)) {
+ end_data = inff_sdiod_readl(sdiodev, ROUNDDN(end_addr, 4), &ret);
+ if (ret) {
+ inff_err("end_addr:0x%x inff_sdiod_readl error:%d", end_addr,
+ ret);
+ goto err;
+ }
+ for (i = (end_addr % 4); i < 4; i++, wbc++)
+ mem_ptr[wbc] = (u8)((u8 *)&end_data)[i];
+ }
+
+ inff_dbg(SDIO, "SDIO PKT size %d bytes\n", wbc);
+
+ if (((start_addr & 0xFFF) + wbc) <= 0x1000) {
+ ret = inff_dfu_sdio_transfer_membytes(sdiodev, start_addr,
+ mem_ptr, wbc);
+ if (ret) {
+ inff_err("error %d on writing %d membytes at 0x%08x",
+ ret, rd, start_addr);
+ goto err;
+ }
+ } else {
+ u32 wb = 0x1000 - (start_addr & 0xFFF);
+
+ ret = inff_dfu_sdio_transfer_membytes(sdiodev, start_addr,
+ mem_ptr, wb);
+ if (ret) {
+ inff_err("error %d on writing %d membytes at 0x%08x",
+ ret, rd, start_addr);
+ goto err;
+ }
+
+ usleep_range(INFF_DFU_CP_SDIORB_NEXT_DELAY, INFF_DFU_CP_SDIORB_NEXT_DELAY);
+
+ ret = inff_dfu_sdio_transfer_membytes(sdiodev, (start_addr + wb),
+ (mem_ptr + wb), (wbc - wb));
+ if (ret) {
+ inff_err("error %d on writing %d membytes at 0x%08x",
+ ret, rd, start_addr);
+ goto err;
+ }
+ }
+
+ ret = inff_dfu_msg_writel(sdiodev, CYW5591X_FWDWNLD_RAM_WR_ADDR | nesn);
+ if (ret)
+ goto err;
+
+ ret = inff_dfu_msg_writeb(sdiodev, INFF_DFU_CP_H2D_MSG_BOOTFW_DATA_LOADED);
+ if (ret)
+ goto err;
+
+ /* Wait for CP boot fw to process current chunk of data */
+ sn = nesn;
+ nesn = (nesn) ? 0 : 1;
+ addrf = hfd.dest_addr;
+ bootfw_rsp_cnt = 0;
+ /*Check if seq no is changed, Mindriver might not have processed the prev pkt */
+ do {
+ usleep_range(INFF_BOOTFW_RSP_DELAY, INFF_BOOTFW_RSP_DELAY + 10);
+ ret = inff_dfu_msg_readl(sdiodev, SDIOD_D2H_MSG_4, &readmsg);
+ if (ret)
+ goto err;
+ inff_dbg(SDIO, "expected sn =%d, received sn = %d\n", nesn,
+ (readmsg & INFF_DFU_CP_SEQ_MASK));
+ bootfw_rsp_cnt++;
+ if (bootfw_rsp_cnt == INFF_BOOTFW_RSP_MAX_CNT) {
+ inff_err("Boot FW response timeout cnt = %d\n",
+ INFF_BOOTFW_RSP_MAX_CNT);
+ ret = -EFAULT;
+ goto err;
+ }
+ } while ((readmsg & INFF_DFU_CP_SEQ_MASK) != nesn);
+
+ /* Wait for boot fw ready message from Device before next chunk */
+ ret = inff_dfu_wait_msg(sdiodev, SDIOD_D2H_MSG_1,
+ INFF_DFU_CP_D2H_MSG_BOOTFW_READY);
+ if (ret)
+ goto err;
+
+ /* clear H2D message to prevent boot fw start to process chunk of data */
+ ret = inff_dfu_msg_writeb(sdiodev, 0);
+ if (ret)
+ goto err;
+ }
+
+ ret = inff_dfu_msg_writel(sdiodev, CYW5591X_IMAGE_LAUNCH_ADDR);
+ if (ret)
+ goto err;
+
+ ret = inff_dfu_msg_writeb(sdiodev, INFF_DFU_CP_H2D_MSG_BOOTFW_DATA_LOADED);
+ if (ret)
+ goto err;
+
+ ret = inff_dfu_wait_msg(sdiodev, SDIOD_D2H_MSG_0, INFF_DFU_CP_D2H_MSG_FW_VALIDAT_START);
+ if (ret)
+ goto err;
+err:
+ release_firmware(fw);
+ kfree(mem_blk);
+ kfree(hfd.ds);
+ return ret;
+}
+
+static int inff_dfu_cp_bootfw_download(struct inff_sdio_dev *sdiodev, const struct firmware *fw)
+{
+ struct inff_dfu_hex_data hfd = {INFF_DFU_CP_ADDR_MODE_EXTENDED, 0, 0, NULL};
+ const u8 *bootfw_data = NULL;
+ u8 *mem_blk = NULL;
+ u8 *mem_ptr = NULL;
+ u32 rd;
+ int ret = 0;
+
+ bootfw_data = fw->data;
+ mem_blk = kmalloc(INFF_DFU_CP_BLK_SIZE + INFF_DFU_CP_SD_ALIGN, GFP_KERNEL);
+ mem_ptr = mem_blk;
+ if (!mem_ptr) {
+ inff_err("Fail to allocate memory for FWDL block");
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ if ((u32)(uintptr_t)mem_ptr % INFF_DFU_CP_SD_ALIGN)
+ mem_ptr += (INFF_DFU_CP_SD_ALIGN - ((u32)(uintptr_t)mem_blk %
+ INFF_DFU_CP_SD_ALIGN));
+
+ hfd.ds = kmalloc(INFF_DFU_CP_MAX_STR_LEN, GFP_KERNEL);
+ if (!hfd.ds) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ while ((rd = inff_dfu_get_hfd_from_fw_data(&bootfw_data, &hfd)) > 0) {
+ u32 start_addr, start_data, end_addr, end_data, i, wbc, pad;
+
+ inff_dbg(SDIO, "\tread %d bytes at address %08x", rd, hfd.dest_addr);
+
+ start_addr = INFF_DFU_CP_MEM_OFFSET | (hfd.dest_addr & INFF_DFU_CP_START_ADDR_MASK);
+ wbc = 0;
+
+ /**
+ * Make sure the start address is 4 byte aligned to avoid alignment issues
+ * with SD host controllers
+ */
+ if (!ISALIGNED(start_addr, INFF_DFU_CP_HEX_ALIGN_LEN)) {
+ pad = start_addr % INFF_DFU_CP_HEX_ALIGN_LEN;
+ start_addr = ROUNDDN(start_addr, INFF_DFU_CP_HEX_ALIGN_LEN);
+ start_data = inff_sdiod_readl(sdiodev, start_addr, &ret);
+ if (ret) {
+ inff_err("start_addr:0x%x inf_sdiod_readl error:%d",
+ start_addr, ret);
+ goto err;
+ }
+ for (i = 0; i < pad; i++, wbc++)
+ mem_ptr[wbc] = (u8)((u8 *)&start_data)[i];
+ }
+ memcpy(&mem_ptr[wbc], hfd.ds, rd);
+ wbc += rd;
+ /**
+ * Make sure the length is multiple of 4bytes to avoid alignment issues
+ * with SD host controllers
+ */
+ end_addr = start_addr + wbc;
+ if (!ISALIGNED(end_addr, 4)) {
+ end_data = inff_sdiod_readl(sdiodev, ROUNDDN(end_addr, 4), &ret);
+ if (ret) {
+ inff_err("end_addr:0x%x inf_sdiod_readl error:%d", end_addr,
+ ret);
+ goto err;
+ }
+ for (i = (end_addr % 4); i < 4; i++, wbc++)
+ mem_ptr[wbc] = (u8)((u8 *)&end_data)[i];
+ }
+
+ if (((start_addr & 0xFFF) + wbc) <= 0x1000) {
+ ret = inff_dfu_sdio_transfer_membytes(sdiodev, start_addr,
+ mem_ptr, wbc);
+ if (ret) {
+ inff_err("error %d on writing %d membytes at 0x%08x",
+ ret, rd, start_addr);
+ goto err;
+ }
+ } else {
+ u32 wb = 0x1000 - (start_addr & 0xFFF);
+
+ ret = inff_dfu_sdio_transfer_membytes(sdiodev, start_addr,
+ mem_ptr, wb);
+ if (ret) {
+ inff_err("error %d on writing %d membytes at 0x%08x",
+ ret, rd, start_addr);
+ goto err;
+ }
+ usleep_range(INFF_DFU_CP_SDIORB_NEXT_DELAY,
+ INFF_DFU_CP_SDIORB_NEXT_DELAY + 10);
+ ret = inff_dfu_sdio_transfer_membytes(sdiodev, (start_addr + wb),
+ (mem_ptr + wb), (wbc - wb));
+ if (ret) {
+ inff_err("error %d on writing %d membytes at 0x%08x",
+ ret, rd, start_addr);
+ goto err;
+ }
+ }
+ }
+
+err:
+ release_firmware(fw);
+ kfree(mem_blk);
+ kfree(hfd.ds);
+ return ret;
+}
+
+static int inff_dfu_cp_img_dwnld_start(struct inff_sdio_dev *sdiodev,
+ const struct firmware *boot_fw,
+ const struct firmware *cp_fw)
+{
+ int err = 0;
+
+ err = inff_dfu_wait_msg(sdiodev, SDIOD_D2H_MSG_0, INFF_DFU_CP_D2H_MSG_BL_READY);
+ if (err)
+ return -EFAULT;
+
+ err = inff_dfu_msg_writel(sdiodev, CYW5591X_BOOTFW_RAM_LD_ADDR);
+ if (err)
+ return -EFAULT;
+
+ err = inff_dfu_msg_writeb(sdiodev, INFF_DFU_CP_H2D_MSG_BOOT_FWLOAD_START);
+ if (err)
+ return -EFAULT;
+
+ err = inff_dfu_cp_bootfw_download(sdiodev, boot_fw);
+ if (err) {
+ inff_err("CP boot fw download failed[ret %d]", err);
+ return -EFAULT;
+ }
+ inff_dbg(INFO, "CP boot fw download done!!!\n");
+
+ err = inff_dfu_msg_writeb(sdiodev, INFF_DFU_CP_H2D_MSG_BOOT_FWLOAD_DONE);
+ if (err)
+ return -EFAULT;
+
+ err = inff_dfu_cp_fw_img_download(sdiodev, cp_fw);
+ if (err)
+ inff_err("Connected Processor image download failed [ret %d]", err);
+ else
+ inff_dbg(INFO, "Connected Processor image download done!!!\n");
+
+ return err;
+}
+
+int inff_dfu_cp_img_dwnld_init(struct inff_sdio_dev *sdiodev, const struct firmware *boot_fw,
+ const struct firmware *cp_fw)
+{
+ int ret = 0;
+ u8 fw_ready = 0;
+
+ ret = sdio_enable_func(sdiodev->func3);
+ if (ret) {
+ inff_err("enable func3 err %d", ret);
+ /* Disable F3 again */
+ sdio_disable_func(sdiodev->func3);
+ goto fail;
+ }
+
+ fw_ready = inff_sdiod_func_rb_ext(sdiodev->func3, SDIOD_D2H_MSG_0, &ret);
+ if (ret) {
+ inff_err("SDIOD_F3_D2H_MSG_0 err %d", ret);
+ ret = -EFAULT;
+ goto fail;
+ }
+
+ inff_dbg(SDIO, "FW Ready Flags 0x%x\n", fw_ready);
+ ret = inff_dfu_cp_img_dwnld_start(sdiodev, boot_fw, cp_fw);
+ if (ret)
+ goto fail;
+
+ return 0;
+fail:
+ return ret;
+}
+
+static int inff_dfu_cp_send_opcode_data(struct inff_if *ifp, u16 op_code, const u8 *data,
+ u16 data_len)
+{
+ static u8 pkt[INFF_DFU_CP_PKT_LEN_MAX] = { INFF_DFU_CP_OTA_CMD_ID };
+ *(u16 *)&pkt[INFF_DFU_CP_OPC_OFFSET] = op_code;
+ int ret = 0;
+
+ switch (op_code) {
+ case INFF_CONTROL_CP_DFU_PRE_DOWNLOAD:
+ ret = inff_cpcmd_data_set(ifp, INFF_CP_C_DFU,
+ &pkt, INFF_DFU_CP_DAT_OFFSET);
+ break;
+ case INFF_CONTROL_CP_DFU_DOWNLOAD:
+ *(u16 *)&pkt[INFF_DFU_CP_LEN_OFFSET] = data_len;
+ memcpy(&pkt[INFF_DFU_CP_DAT_OFFSET], data, data_len);
+ ret = inff_cpcmd_data_set(ifp, INFF_CP_C_DFU,
+ &pkt, (data_len + INFF_DFU_CP_DAT_OFFSET));
+ break;
+ case INFF_CONTROL_CP_DFU_DATA:
+ *(u16 *)&pkt[INFF_DFU_CP_LEN_OFFSET] = data_len;
+ if (!data)
+ return -1;
+ memcpy(&pkt[INFF_DFU_CP_DAT_OFFSET], data, data_len);
+ ret = inff_cpcmd_data_set(ifp, INFF_CP_C_DFU,
+ &pkt, (data_len + INFF_DFU_CP_DAT_OFFSET));
+ break;
+ case INFF_CONTROL_CP_DFU_VERIFY:
+ ret = inff_cpcmd_data_set(ifp, INFF_CP_C_DFU,
+ &pkt, INFF_DFU_CP_DAT_OFFSET);
+ break;
+ case INFF_CONTROL_CP_DFU_ABORT:
+ ret = inff_cpcmd_data_set(ifp, INFF_CP_C_DFU,
+ &pkt, INFF_DFU_CP_DAT_OFFSET);
+ break;
+ default:
+ break;
+ }
+
+ if (ret) {
+ inff_err("DFU data send failed, ret=%d\n", ret);
+ return -1;
+ }
+
+ return 0;
+}
+
+static enum fw_upload_err inff_dfu_cp_prepare(struct fw_upload *fw_ul,
+ const u8 *data, u32 data_size)
+{
+ struct inff_dfu *dfu;
+ struct inff_pub *drvr;
+ struct inff_if *ifp;
+ int ret = 0;
+
+ if (!fw_ul->dd_handle) {
+ inff_err("DFU not attached\n");
+ return FW_UPLOAD_ERR_CANCELED;
+ }
+
+ dfu = fw_ul->dd_handle;
+ drvr = dfu->drvr;
+ ifp = inff_get_ifp(drvr, 0);
+
+ dfu->cancel_request = false;
+
+ ret = inff_dfu_cp_send_opcode_data(ifp, INFF_CONTROL_CP_DFU_PRE_DOWNLOAD, NULL, 0);
+
+ if (ret) {
+ inff_err("pre download failed, ret=%d\n", ret);
+ return FW_UPLOAD_ERR_BUSY;
+ }
+
+ ret = inff_dfu_cp_send_opcode_data(ifp, INFF_CONTROL_CP_DFU_DOWNLOAD,
+ (const u8 *)&data_size, sizeof(data_size));
+ if (ret) {
+ inff_err("pre download set size failed, ret=%d\n", ret);
+ return FW_UPLOAD_ERR_INVALID_SIZE;
+ }
+
+ return FW_UPLOAD_ERR_NONE;
+}
+
+static enum fw_upload_err inff_dfu_cp_write(struct fw_upload *fw_ul, const u8 *data,
+ u32 offset, u32 size, u32 *written)
+{
+ struct inff_dfu *dfu = fw_ul->dd_handle;
+ struct inff_pub *drvr = dfu->drvr;
+ struct inff_if *ifp = inff_get_ifp(drvr, 0);
+ int ret = 0;
+ u32 remains = size;
+ u32 offset_size = 0;
+
+ if (dfu->cancel_request)
+ return FW_UPLOAD_ERR_CANCELED;
+
+ inff_info("DFU image download in progress...\n");
+
+ while (remains > 0) {
+ u16 len = (remains > INFF_DFU_CP_PKT_CHUNK_LEN) ? INFF_DFU_CP_PKT_CHUNK_LEN :
+ remains;
+
+ ret = inff_dfu_cp_send_opcode_data(ifp, INFF_CONTROL_CP_DFU_DATA,
+ data + offset_size, len);
+ if (ret) {
+ inff_err("DFU write data failed, ret=%d\n", ret);
+ return FW_UPLOAD_ERR_INVALID_SIZE;
+ }
+ remains -= len;
+ offset_size += len;
+ inff_info(".");
+ }
+
+ inff_dbg(INFO, "\nDFU image download done, verification will start\n");
+ *written = offset_size;
+
+ return FW_UPLOAD_ERR_NONE;
+}
+
+static enum fw_upload_err inff_dfu_cp_poll_complete(struct fw_upload *fw_ul)
+{
+ struct inff_dfu *dfu = fw_ul->dd_handle;
+ struct inff_pub *drvr = dfu->drvr;
+ struct inff_if *ifp = inff_get_ifp(drvr, 0);
+ int ret = 0;
+
+ if (dfu->cancel_request)
+ return FW_UPLOAD_ERR_CANCELED;
+
+ ret = inff_dfu_cp_send_opcode_data(ifp, INFF_CONTROL_CP_DFU_VERIFY, NULL, 0);
+ if (ret) {
+ inff_err("DFU image verify failed, ret=%d\n", ret);
+ return FW_UPLOAD_ERR_FW_INVALID;
+ }
+
+ inff_info("\nDFU image download and validation done!!\n");
+
+ return FW_UPLOAD_ERR_NONE;
+}
+
+static void inff_dfu_cp_cancel(struct fw_upload *fw_ul)
+{
+ struct inff_dfu *dfu = fw_ul->dd_handle;
+ struct inff_pub *drvr = dfu->drvr;
+ struct inff_if *ifp = inff_get_ifp(drvr, 0);
+ int ret = 0;
+
+ dfu->cancel_request = true;
+
+ ret = inff_dfu_cp_send_opcode_data(ifp, INFF_CONTROL_CP_DFU_ABORT, NULL, 0);
+ if (ret)
+ inff_err("DFU cancel failed, ret=%d\n", ret);
+}
+
+static const struct fw_upload_ops inff_dfu_cp_ops = {
+ .prepare = inff_dfu_cp_prepare,
+ .write = inff_dfu_cp_write,
+ .poll_complete = inff_dfu_cp_poll_complete,
+ .cancel = inff_dfu_cp_cancel,
+};
+
+struct inff_dfu_update_context *dfu_upd_ctx;
+
+void inff_dfu_ai_free_ctx(struct inff_dfu_update_context **ctx)
+{
+ if (!ctx || !(*ctx))
+ return;
+
+ if ((*ctx)->bin_logic) {
+ kfree((*ctx)->bin_logic->data);
+ kfree((*ctx)->bin_logic);
+ (*ctx)->bin_logic = NULL;
+ }
+
+ if ((*ctx)->bin_payload) {
+ kfree((*ctx)->bin_payload->data);
+ kfree((*ctx)->bin_payload);
+ (*ctx)->bin_payload = NULL;
+ }
+
+ kfree(*ctx);
+ *ctx = NULL;
+}
+
+static enum fw_upload_err inff_dfu_ai_prepare(struct fw_upload *fw_ul,
+ const u8 *data, u32 data_size)
+{
+ struct inff_dfu *dfu = fw_ul->dd_handle;
+
+ inff_dbg(INFO, "requested size %d\n", data_size);
+
+ if (data_size >= INFF_DFU_MAX_SIZE) {
+ inff_err("data_size exceeds max size %d!\n", data_size);
+ return FW_UPLOAD_ERR_INVALID_SIZE;
+ }
+
+ kfree(dfu->data);
+ dfu->data = NULL;
+ dfu->size = 0;
+ dfu->data = kzalloc(data_size, GFP_KERNEL);
+ if (!dfu->data) {
+ inff_err("alloc fail!\n");
+ return FW_UPLOAD_ERR_RW_ERROR;
+ }
+
+ dfu->size = data_size;
+
+ return FW_UPLOAD_ERR_NONE;
+}
+
+static enum fw_upload_err inff_dfu_ai_write(struct fw_upload *fw_ul, const u8 *data,
+ u32 offset, u32 size, u32 *written)
+{
+ struct inff_dfu *dfu = fw_ul->dd_handle;
+
+ if (offset > dfu->size || size > dfu->size - offset) {
+ inff_err("offset %d size %d exceeds limitation!\n", offset, size);
+ return FW_UPLOAD_ERR_INVALID_SIZE;
+ }
+
+ memcpy(dfu->data + offset, data, size);
+ *written = size;
+
+ return FW_UPLOAD_ERR_NONE;
+}
+
+static enum fw_upload_err inff_dfu_ai_poll_complete(struct fw_upload *fw_ul)
+{
+ struct inff_dfu *dfu = fw_ul->dd_handle;
+ u8 *cur = dfu->data;
+ u32 bin_type = 0;
+ u32 bin_size = 0;
+ u32 remaining = 0;
+ u8 *bin_data;
+ struct firmware *binary;
+ struct inff_dfu_update_context **ctx = &dfu_upd_ctx;
+ enum fw_upload_err ret = FW_UPLOAD_ERR_NONE;
+
+ if (!dfu->drvr->bus_reset.func) {
+ inff_err("no reset function!\n");
+ ret = FW_UPLOAD_ERR_RW_ERROR;
+ goto exit;
+ }
+
+ inff_dfu_ai_free_ctx(ctx);
+ *ctx = kzalloc(sizeof(**ctx), GFP_KERNEL);
+ if (!*ctx) {
+ inff_err("alloc context failed!\n");
+ ret = FW_UPLOAD_ERR_RW_ERROR;
+ goto exit;
+ }
+
+ /* parse the binary type/size and alloc space to save the data */
+ while ((cur - dfu->data) < dfu->size) {
+ remaining = dfu->size - (cur - dfu->data);
+
+ if (remaining < INFF_DFU_FIELD_LEN * 2) {
+ inff_err("truncated TLV header!\n");
+ inff_dfu_ai_free_ctx(ctx);
+ ret = FW_UPLOAD_ERR_FW_INVALID;
+ goto exit;
+ }
+
+ bin_type = *((u32 *)cur);
+ bin_size = *((u32 *)cur + 1);
+
+ if (bin_size > remaining - INFF_DFU_FIELD_LEN * 2) {
+ inff_err("TLV data size exceeds buffer boundary!\n");
+ inff_dfu_ai_free_ctx(ctx);
+ ret = FW_UPLOAD_ERR_FW_INVALID;
+ goto exit;
+ }
+
+ bin_data = kmemdup((u8 *)((u32 *)cur + 2), bin_size, GFP_KERNEL);
+ if (!bin_data) {
+ inff_dfu_ai_free_ctx(ctx);
+ ret = FW_UPLOAD_ERR_RW_ERROR;
+ goto exit;
+ }
+
+ binary = kzalloc(sizeof(*binary), GFP_KERNEL);
+ if (!binary) {
+ kfree(bin_data);
+ inff_dfu_ai_free_ctx(ctx);
+ ret = FW_UPLOAD_ERR_RW_ERROR;
+ goto exit;
+ }
+
+ binary->size = bin_size;
+ binary->data = bin_data;
+
+ switch (bin_type) {
+ case INFF_DFU_TYPE_RAMAPP_LOGIC:
+ if ((*ctx)->bin_logic) {
+ kfree((*ctx)->bin_logic->data);
+ kfree((*ctx)->bin_logic);
+ }
+ (*ctx)->bin_logic = binary;
+ break;
+ case INFF_DFU_TYPE_RAMAPP_PAYLOAD:
+ if ((*ctx)->bin_payload) {
+ kfree((*ctx)->bin_payload->data);
+ kfree((*ctx)->bin_payload);
+ }
+ (*ctx)->bin_payload = binary;
+ break;
+ default:
+ kfree(binary->data);
+ kfree(binary);
+ inff_dfu_ai_free_ctx(ctx);
+ inff_dbg(INFO, "wrong file\n");
+ ret = FW_UPLOAD_ERR_FW_INVALID;
+ goto exit;
+ }
+
+ cur += (INFF_DFU_FIELD_LEN * 2);
+ cur += bin_size;
+ }
+
+ /* trigger the bus reset */
+ inff_dbg(INFO, "Process bus reset!\n");
+ schedule_work(&dfu->drvr->bus_reset);
+
+exit:
+ kfree(dfu->data);
+ dfu->data = NULL;
+ dfu->size = 0;
+ return ret;
+}
+
+static void inff_dfu_ai_cancel(struct fw_upload *fw_ul)
+{
+ /* A bus reset is triggered immediately after the firmware binary is
+ * collected, causing the DFU interface to be removed. As a result,
+ * there is no opportunity for the user to cancel the download once
+ * it has started. This function is intentionally left empty.
+ */
+}
+
+static const struct fw_upload_ops inff_dfu_ai_ops = {
+ .prepare = inff_dfu_ai_prepare,
+ .write = inff_dfu_ai_write,
+ .poll_complete = inff_dfu_ai_poll_complete,
+ .cancel = inff_dfu_ai_cancel,
+};
+
+int inff_dfu_attach(struct inff_pub *drvr)
+{
+ struct device *dev = drvr->bus_if->dev;
+ struct inff_dfu *dfu = NULL;
+ const struct fw_upload_ops *dfu_ops = NULL;
+ long fw_ul_err;
+
+ switch (drvr->bus_if->chip_pub->socitype) {
+ case INFF_SOCI_AI:
+ if (drvr->bus_if->chip_pub->chip != INF_CC_ACW74XX_CHIP_ID)
+ return 0;
+ dfu_ops = &inff_dfu_ai_ops;
+ break;
+ case INFF_SOCI_CP:
+ dfu_ops = &inff_dfu_cp_ops;
+ break;
+ default:
+ inff_err("chip type %u is not supported\n",
+ drvr->bus_if->chip_pub->socitype);
+ return -ENODEV;
+ }
+
+ /* allocate memory for DFU struct */
+ dfu = kzalloc(sizeof(*dfu), GFP_KERNEL);
+ if (!dfu)
+ return -ENOMEM;
+
+ dfu->drvr = drvr;
+ dfu->cancel_request = false;
+
+ /* register firmware upload feature */
+ dfu->fw_ul = firmware_upload_register(NULL, dev, dev_name(dev),
+ dfu_ops, dfu);
+
+ if (IS_ERR(dfu->fw_ul)) {
+ fw_ul_err = PTR_ERR(dfu->fw_ul);
+ kfree(dfu);
+ inff_err("firmware_upload_register failed %ld\n", fw_ul_err);
+ return (int)fw_ul_err;
+ }
+ drvr->dfu = dfu;
+ return 0;
+}
+
+void inff_dfu_detach(struct inff_pub *drvr)
+{
+ if (!drvr)
+ return;
+
+ if (!drvr->dfu)
+ return;
+
+ firmware_upload_unregister(drvr->dfu->fw_ul);
+ kfree(drvr->dfu);
+ drvr->dfu = NULL;
+}
diff --git a/drivers/net/wireless/infineon/inffmac/dfu.h b/drivers/net/wireless/infineon/inffmac/dfu.h
new file mode 100644
index 000000000000..6dd4e7ed9b3a
--- /dev/null
+++ b/drivers/net/wireless/infineon/inffmac/dfu.h
@@ -0,0 +1,124 @@
+/* SPDX-License-Identifier: ISC */
+/*
+ * Copyright (c) 2025-2026, Infineon Technologies AG, or an affiliate of Infineon Technologies AG.
+ * All rights reserved.
+ */
+
+#include "dev_cmd.h"
+#include "chip_cyw5591x.h"
+#include "sdio.h"
+
+#ifndef INFF_DFU_H
+#define INFF_DFU_H
+
+#define SDIOD_ADDR_BOUND 0x1000
+#define SDIOD_ADDR_BOUND_MASK 0xFFF
+
+/* Host to Device message */
+/* Below H2D Messages needs to align with the Minidirver used */
+#define SDIOD_H2D_MSG_0 0x10031
+#define INFF_DFU_CP_H2D_MSG_BOOT_FWLOAD_START BIT(0)
+#define INFF_DFU_CP_H2D_MSG_BOOT_FWLOAD_DONE BIT(1)
+#define INFF_DFU_CP_H2D_MSG_BOOTFW_DATA_LOADED BIT(4)
+
+/* Device to Host message */
+#define SDIOD_D2H_MSG_0 0x10039
+#define INFF_DFU_CP_D2H_MSG_BL_READY BIT(1)
+#define INFF_DFU_CP_D2H_MSG_FW_VALIDAT_START BIT(4)
+#define INFF_DFU_CP_D2H_MSG_FW_VALIDAT_DONE BIT(6)
+
+#define SDIOD_D2H_MSG_1 0x1003A
+#define INFF_DFU_CP_D2H_MSG_BOOTFW_READY BIT(0)
+
+#define SDIOD_H2D_MSG_4 0x10035
+
+#define SDIOD_D2H_MSG_4 0x1003D
+
+#define INFF_DFU_CP_ROM_BOOT_DELAY 100 /* in usecs */
+
+/* wait for 5000 write/read cycles till F3 is enabled */
+#define INFF_DFU_CP_IORDY_CNT 5000
+
+#define INFF_BOOTFW_RSP_MAX_CNT 2000 /* wait for 2000*100 uS = 200 mS */
+#define INFF_BOOTFW_RSP_DELAY 100 /* 100 uS */
+
+#define INFF_DFU_CP_MEM_OFFSET 0x19000000
+
+#define INFF_DFU_CP_OTA_CMD_ID (0x19)
+#define INFF_DFU_CP_OPC_OFFSET (1)
+#define INFF_DFU_CP_LEN_OFFSET (3)
+#define INFF_DFU_CP_DAT_OFFSET (5)
+
+#define INFF_DFU_CP_MAX_STR_LEN (600)
+#define INFF_DFU_CP_BLK_SIZE (INFF_DFU_CP_MAX_STR_LEN / 2 + 8)
+#define INFF_DFU_CP_SD_ALIGN (32)
+
+#define INFF_DFU_CP_PKT_LEN_MAX (600)
+#define INFF_DFU_CP_PKT_CHUNK_LEN (256)
+
+#define INFF_CONTROL_CP_GROUP_DFU (0xC0)
+#define INFF_CONTROL_CP_DFU_PRE_DOWNLOAD ((INFF_CONTROL_CP_GROUP_DFU << 8) | 0x88)
+#define INFF_CONTROL_CP_DFU_DOWNLOAD ((INFF_CONTROL_CP_GROUP_DFU << 8) | 0x89)
+#define INFF_CONTROL_CP_DFU_DATA ((INFF_CONTROL_CP_GROUP_DFU << 8) | 0x8a)
+#define INFF_CONTROL_CP_DFU_VERIFY ((INFF_CONTROL_CP_GROUP_DFU << 8) | 0x90)
+#define INFF_CONTROL_CP_DFU_ABORT ((INFF_CONTROL_CP_GROUP_DFU << 8) | 0x92)
+
+#define INFF_DFU_CP_SDIORB_DELAY 10
+#define INFF_DFU_CP_SDIORB_NEXT_DELAY 1000
+
+#define INFF_DFU_CP_MEM_OFFSET 0x19000000
+#define INFF_DFU_CP_START_ADDR_MASK 0x00FFFFFF
+
+#define INFF_DFU_CP_ADDR_MODE_UNKNOWN 0
+#define INFF_DFU_CP_ADDR_MODE_EXTENDED 1
+#define INFF_DFU_CP_ADDR_MODE_SEGMENT 2
+#define INFF_DFU_CP_ADDR_MODE_LINEAR32 3
+
+#define INFF_DFU_CP_HEX_LINE_TYPE_DATA 0
+#define INFF_DFU_CP_HEX_LINE_TYPE_END_OF_DATA 1
+#define INFF_DFU_CP_HEX_LINE_TYPE_EXT_SEGMENT_ADDR 2
+#define INFF_DFU_CP_HEX_LINE_TYPE_EXT_ADDR 4
+#define INFF_DFU_CP_HEX_LINE_TYPE_ABS_32BIT_ADDR 5
+
+#define INFF_DFU_CP_SEQ_MASK 0x1
+
+#define INFF_DFU_CP_HEX_DATA_OFFSET (9)
+#define INFF_DFU_CP_HEX_NUMB_OFFSET (1)
+#define INFF_DFU_CP_HEX_ADDR_OFFSET (3)
+#define INFF_DFU_CP_HEX_TYPE_OFFSET (7)
+
+#define INFF_DFU_CP_HEX_NUMB_LEN (2)
+#define INFF_DFU_CP_HEX_ADDR_LEN (4)
+#define INFF_DFU_CP_HEX_TYPE_LEN (2)
+#define INFF_DFU_CP_HEX_ALIGN_LEN (4)
+
+#define INFF_DFU_MAX_SIZE (5 * 1024 * 1024)
+#define INFF_DFU_FIELD_LEN 4
+#define INFF_DFU_TYPE_RAMAPP_LOGIC 0x11
+#define INFF_DFU_TYPE_RAMAPP_PAYLOAD 0x22
+
+#define ROUNDDN(p, align) ((p) & ~((align) - 1))
+#define ISALIGNED(a, x) (((uintptr_t)(a) & ((x) - 1)) == 0)
+
+struct inff_dfu {
+ struct inff_pub *drvr;
+ struct fw_upload *fw_ul;
+ u8 *data;
+ u32 size;
+ bool cancel_request;
+};
+
+struct inff_dfu_update_context {
+ struct firmware *bin_logic;
+ struct firmware *bin_payload;
+};
+
+int inff_dfu_attach(struct inff_pub *drvr);
+void inff_dfu_detach(struct inff_pub *drvr);
+int inff_dfu_cp_img_dwnld_init(struct inff_sdio_dev *sdiodev, const struct firmware *boot_fw,
+ const struct firmware *cp_fw);
+void inff_dfu_ai_free_ctx(struct inff_dfu_update_context **ctx);
+
+extern struct inff_dfu_update_context *dfu_upd_ctx;
+
+#endif /* INFF_DFU_H */
--
2.43.0
^ permalink raw reply related [flat|nested] 26+ messages in thread* [PATCH wireless-next v3 04/25] wifi: inffmac: add trxhdr.h
2026-09-02 16:50 [PATCH wireless-next v3 00/25] wifi: inffmac: introducing a driver for Infineon's new generation chipsets Gokul Sivakumar
` (2 preceding siblings ...)
2026-09-02 16:50 ` [PATCH wireless-next v3 03/25] wifi: inffmac: add dfu.c/h Gokul Sivakumar
@ 2026-09-02 16:50 ` Gokul Sivakumar
2026-09-02 16:50 ` [PATCH wireless-next v3 05/25] wifi: inffmac: add chip.c/h Gokul Sivakumar
` (20 subsequent siblings)
24 siblings, 0 replies; 26+ messages in thread
From: Gokul Sivakumar @ 2026-09-02 16:50 UTC (permalink / raw)
To: linux-wireless, johannes
Cc: arend.vanspriel, wlan-kernel-dev-list, gokulkumar.sivakumar
Definitions of the Metadata in the Device Firmware binary files.
Signed-off-by: Gokul Sivakumar <gokulkumar.sivakumar@infineon.com>
---
.../net/wireless/infineon/inffmac/trxhdr.h | 54 +++++++++++++++++++
1 file changed, 54 insertions(+)
create mode 100644 drivers/net/wireless/infineon/inffmac/trxhdr.h
diff --git a/drivers/net/wireless/infineon/inffmac/trxhdr.h b/drivers/net/wireless/infineon/inffmac/trxhdr.h
new file mode 100644
index 000000000000..edb2a2ee0443
--- /dev/null
+++ b/drivers/net/wireless/infineon/inffmac/trxhdr.h
@@ -0,0 +1,54 @@
+/* SPDX-License-Identifier: ISC */
+/*
+ * Copyright (c) 2020-2026, Infineon Technologies AG, or an affiliate of Infineon Technologies AG.
+ * All rights reserved.
+ */
+
+#ifndef INFF_TRXHDR_H
+#define INFF_TRXHDR_H
+
+/* Bootloader makes special use of trx header "offsets" array */
+enum {
+ TRX_OFFSET_SIGN_INFO_IDX = 0,
+ TRX_OFFSET_DATA_FOR_SIGN1_IDX = 1,
+ TRX_OFFSET_DATA_FOR_SIGN2_IDX = 2,
+ TRX_OFFSET_ROOT_MODULUS_IDX = 3,
+ TRX_OFFSET_ROOT_EXPONENT_IDX = 67,
+ TRX_OFFSET_CONT_MODULUS_IDX = 68,
+ TRX_OFFSET_CONT_EXPONENT_IDX = 132,
+ TRX_OFFSET_HASH_FW_IDX = 133,
+ TRX_OFFSET_FW_LEN_IDX = 149,
+ TRX_OFFSET_TR_RST_IDX = 150,
+ TRX_OFFSET_FW_VER_FOR_ANTIROOLBACK_IDX = 151,
+ TRX_OFFSET_IV_IDX = 152,
+ TRX_OFFSET_NONCE_IDX = 160,
+ TRX_OFFSET_SIGN_INFO2_IDX = 168,
+ TRX_OFFSET_MAX_IDX
+};
+
+#define TRX_MAGIC 0x30524448 /* "HDR0" */
+#define TRX_VERSION 4 /* Version 4 */
+#define TRX_MAX_OFFSET TRX_OFFSET_MAX_IDX /* Max number of file offsets */
+
+struct trx_header_le {
+ __le32 magic; /* "HDR0" */
+ __le32 len; /* Length of file including header */
+ __le32 crc32; /* CRC from flag_version to end of file */
+ __le32 flag_version; /* 0:15 flags, 16:31 version */
+ __le32 offsets[TRX_MAX_OFFSET]; /* Offsets of partitions */
+};
+
+#define TRX_VERSION5 5 /* Version 5 */
+
+struct trxv5_header_le {
+ __le32 magic; /* "HDR0" */
+ __le32 len; /* Length of file including header */
+ __le32 crc32; /* 32-bit CRC from flag_version to end of file */
+ __le32 flag_version; /* 0:15 flags, 16:31 version */
+ __le32 root_cert_start_offset; /* Start Offset IDX for Root Certificate */
+ __le32 content_cert_start_offset; /* Start Offset IDX for Content Certificate */
+ __le32 fw_entry; /* Firmware Entry Point for CM mode */
+ __le32 reserved;
+};
+
+#endif /* INFF_TRXHDR_H */
--
2.43.0
^ permalink raw reply related [flat|nested] 26+ messages in thread* [PATCH wireless-next v3 05/25] wifi: inffmac: add chip.c/h
2026-09-02 16:50 [PATCH wireless-next v3 00/25] wifi: inffmac: introducing a driver for Infineon's new generation chipsets Gokul Sivakumar
` (3 preceding siblings ...)
2026-09-02 16:50 ` [PATCH wireless-next v3 04/25] wifi: inffmac: add trxhdr.h Gokul Sivakumar
@ 2026-09-02 16:50 ` Gokul Sivakumar
2026-09-02 16:50 ` [PATCH wireless-next v3 06/25] wifi: inffmac: add chip_{cyw5591x/acw74xx}.c/h Gokul Sivakumar
` (19 subsequent siblings)
24 siblings, 0 replies; 26+ messages in thread
From: Gokul Sivakumar @ 2026-09-02 16:50 UTC (permalink / raw)
To: linux-wireless, johannes
Cc: arend.vanspriel, wlan-kernel-dev-list, gokulkumar.sivakumar
Driver implementation for managing the chip and also do the chip operations
needed for the Infineon-proprietary secured firmware architecture. This new
secure firmware architecture demands a unique vendor-specific handshake
between the host machine and new Infineon chipsets for both bring up and
normal operation, because of firmware encryption and firmware signature
validation.
Signed-off-by: Gokul Sivakumar <gokulkumar.sivakumar@infineon.com>
---
drivers/net/wireless/infineon/inffmac/chip.c | 1483 ++++++++++++++++++
drivers/net/wireless/infineon/inffmac/chip.h | 644 ++++++++
2 files changed, 2127 insertions(+)
create mode 100644 drivers/net/wireless/infineon/inffmac/chip.c
create mode 100644 drivers/net/wireless/infineon/inffmac/chip.h
diff --git a/drivers/net/wireless/infineon/inffmac/chip.c b/drivers/net/wireless/infineon/inffmac/chip.c
new file mode 100644
index 000000000000..2a2861ddbdc2
--- /dev/null
+++ b/drivers/net/wireless/infineon/inffmac/chip.c
@@ -0,0 +1,1483 @@
+// SPDX-License-Identifier: ISC
+/*
+ * Copyright (c) 2014 Broadcom Corporation
+ *
+ * Copyright (c) 2025-2026, Infineon Technologies AG, or an affiliate of Infineon Technologies AG.
+ * All rights reserved.
+ */
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/list.h>
+#include <linux/mmc/sdio_ids.h>
+#include <linux/ssb/ssb_regs.h>
+
+#include "main.h"
+#include "utils.h"
+#include "debug.h"
+#include "chip.h"
+#include "chip_cyw5591x.h"
+#include "chip_acw74xx.h"
+
+#define INFF_SI_ENUM_BASE_DEFAULT 0x18000000
+#define INFF_SI_ENUM_BASE_ACW74XX_DEFAULT 0x5cc00000
+
+/* Chipcommon Core CHIPID */
+#define CID_ID_MASK 0x0000ffff /* Chip Id mask */
+#define CID_REV_MASK 0x000f0000 /* Chip Revision mask */
+#define CID_REV_SHIFT 16 /* Chip Revision shift */
+#define CID_PKG_MASK 0x00f00000 /* Package Option mask */
+#define CID_PKG_SHIFT 20 /* Package Option shift */
+#define CID_CC_MASK 0x0f000000 /* CoreCount (corerev >= 4) */
+#define CID_CC_SHIFT 24
+#define CID_TYPE_MASK 0xf0000000 /* Chip Type */
+#define CID_TYPE_SHIFT 28
+
+/* Chipcommon capabilities */
+#define CC_CAP_UARTS_MASK 0x00000003 /* Number of UARTs */
+#define CC_CAP_MIPSEB 0x00000004 /* MIPS is in big-endian mode */
+#define CC_CAP_UCLKSEL 0x00000018 /* UARTs clock select */
+/* UARTs are driven by internal divided clock */
+#define CC_CAP_UINTCLK 0x00000008
+#define CC_CAP_UARTGPIO 0x00000020 /* UARTs own GPIOs 15:12 */
+#define CC_CAP_EXTBUS_MASK 0x000000c0 /* External bus mask */
+#define CC_CAP_EXTBUS_NONE 0x00000000 /* No ExtBus present */
+#define CC_CAP_EXTBUS_FULL 0x00000040 /* ExtBus: PCMCIA, IDE & Prog */
+#define CC_CAP_EXTBUS_PROG 0x00000080 /* ExtBus: ProgIf only */
+#define CC_CAP_FLASH_MASK 0x00000700 /* Type of flash */
+#define CC_CAP_PLL_MASK 0x00038000 /* Type of PLL */
+#define CC_CAP_PWR_CTL 0x00040000 /* Power control */
+#define CC_CAP_OTPSIZE 0x00380000 /* OTP Size (0 = none) */
+#define CC_CAP_OTPSIZE_SHIFT 19 /* OTP Size shift */
+#define CC_CAP_OTPSIZE_BASE 5 /* OTP Size base */
+#define CC_CAP_JTAGP 0x00400000 /* JTAG Master Present */
+#define CC_CAP_ROM 0x00800000 /* Internal boot rom active */
+#define CC_CAP_BKPLN64 0x08000000 /* 64-bit backplane */
+#define CC_CAP_PMU 0x10000000 /* PMU Present, rev >= 20 */
+#define CC_CAP_SROM 0x40000000 /* Srom Present, rev >= 32 */
+/* Nand flash present, rev >= 35 */
+#define CC_CAP_NFLASH 0x80000000
+
+#define CC_CAP2_SECI 0x00000001 /* SECI Present, rev >= 36 */
+/* GSIO (spi/i2c) present, rev >= 37 */
+#define CC_CAP2_GSIO 0x00000002
+
+/* Chip Common registers */
+/* PMU registers (rev >= 20) */
+#define INF_CC_PMU_CTL 0x0600 /* PMU control */
+#define INF_CC_PMU_CTL_ILP_DIV 0xFFFF0000 /* ILP div mask */
+#define INF_CC_PMU_CTL_ILP_DIV_SHIFT 16
+#define INF_CC_PMU_CTL_RES 0x00006000 /* reset control mask */
+#define INF_CC_PMU_CTL_RES_SHIFT 13
+#define INF_CC_PMU_CTL_RES_RELOAD 0x2 /* reload POR values */
+#define INF_CC_PMU_CTL_PLL_UPD 0x00000400
+#define INF_CC_PMU_CTL_NOILPONW 0x00000200 /* No ILP on wait */
+#define INF_CC_PMU_CTL_HTREQEN 0x00000100 /* HT req enable */
+#define INF_CC_PMU_CTL_ALPREQEN 0x00000080 /* ALP req enable */
+#define INF_CC_PMU_CTL_XTALFREQ 0x0000007C /* Crystal freq */
+#define INF_CC_PMU_CTL_XTALFREQ_SHIFT 2
+#define INF_CC_PMU_CTL_ILPDIVEN 0x00000002 /* ILP div enable */
+#define INF_CC_PMU_CTL_LPOSEL 0x00000001 /* LPO sel */
+
+#define INF_CC_CAP_EXT 0x00AC /* Capabilities */
+#define INF_CC_CAP_EXT_SECI_PRESENT 0x00000001
+#define INF_CC_CAP_EXT_GSIO_PRESENT 0x00000002
+#define INF_CC_CAP_EXT_GCI_PRESENT 0x00000004
+#define INF_CC_CAP_EXT_SECI_PUART_PRESENT 0x00000008 /* UART present */
+#define INF_CC_CAP_EXT_SRENG_REG_PRESENT 0x00000010
+#define INF_CC_CAP_EXT_ASCU_PRESENT 0x00000020
+#define INF_CC_CAP_EXT_AOB_PRESENT 0x00000040
+
+#define INF_CC_SROM_CONTROL 0x0190
+#define INF_CC_SROM_CONTROL_START 0x80000000
+#define INF_CC_SROM_CONTROL_BUSY 0x80000000
+#define INF_CC_SROM_CONTROL_OPCODE 0x60000000
+#define INF_CC_SROM_CONTROL_OP_READ 0x00000000
+#define INF_CC_SROM_CONTROL_OP_WRITE 0x20000000
+#define INF_CC_SROM_CONTROL_OP_WRDIS 0x40000000
+#define INF_CC_SROM_CONTROL_OP_WREN 0x60000000
+#define INF_CC_SROM_CONTROL_OTPSEL 0x00000010
+#define INF_CC_SROM_CONTROL_OTP_PRESENT 0x00000020
+#define INF_CC_SROM_CONTROL_LOCK 0x00000008
+#define INF_CC_SROM_CONTROL_SIZE_MASK 0x00000006
+#define INF_CC_SROM_CONTROL_SIZE_1K 0x00000000
+#define INF_CC_SROM_CONTROL_SIZE_4K 0x00000002
+#define INF_CC_SROM_CONTROL_SIZE_16K 0x00000004
+#define INF_CC_SROM_CONTROL_SIZE_SHIFT 1
+#define INF_CC_SROM_CONTROL_PRESENT 0x00000001
+
+/* PMU CORE */
+/* pmucapabilities */
+#define PCAP_REV_MASK 0x000000ff
+#define PCAP_RC_MASK 0x00001f00
+#define PCAP_RC_SHIFT 8
+#define PCAP_TC_MASK 0x0001e000
+#define PCAP_TC_SHIFT 13
+#define PCAP_PC_MASK 0x001e0000
+#define PCAP_PC_SHIFT 17
+#define PCAP_VC_MASK 0x01e00000
+#define PCAP_VC_SHIFT 21
+#define PCAP_CC_MASK 0x1e000000
+#define PCAP_CC_SHIFT 25
+#define PCAP5_PC_MASK 0x003e0000 /* PMU corerev >= 5 */
+#define PCAP5_PC_SHIFT 17
+#define PCAP5_VC_MASK 0x07c00000
+#define PCAP5_VC_SHIFT 22
+#define PCAP5_CC_MASK 0xf8000000
+#define PCAP5_CC_SHIFT 27
+/* pmucapabilites_ext PMU rev >= 15 */
+#define PCAPEXT_SR_SUPPORTED_MASK BIT(1)
+/* retention_ctl PMU rev >= 15 */
+#define PMU_RCTL_MACPHY_DISABLE_MASK BIT(26)
+#define PMU_RCTL_LOGIC_DISABLE_MASK BIT(27)
+
+/*
+ * Maximum delay for the PMU state transition in us.
+ * This is an upper bound intended for spinwaits etc.
+ */
+#define PMU_MAX_TRANSITION_DLY 15000
+
+/* SR CORE */
+/* sr_control0, rev >= 48 */
+#define CC_SR_CTL0_ENABLE_MASK BIT(0)
+#define CC_SR_CTL0_ENABLE_SHIFT 0
+#define CC_SR_CTL0_EN_SR_ENG_CLK_SHIFT 1 /* sr_clk to sr_memory enable */
+#define CC_SR_CTL0_RSRC_TRIGGER_SHIFT 2 /* Rising edge resource trigger 0 to
+ * sr_engine
+ */
+#define CC_SR_CTL0_MIN_DIV_SHIFT 6 /* Min division value for fast clk
+ * in sr_engine
+ */
+#define CC_SR_CTL0_EN_SBC_STBY_SHIFT 16
+#define CC_SR_CTL0_EN_SR_ALP_CLK_MASK_SHIFT 18
+#define CC_SR_CTL0_EN_SR_HT_CLK_SHIFT 19
+#define CC_SR_CTL0_ALLOW_PIC_SHIFT 20 /* Allow pic to separate power
+ * domains
+ */
+#define CC_SR_CTL0_MAX_SR_LQ_CLK_CNT_SHIFT 25
+#define CC_SR_CTL0_EN_MEM_DISABLE_FOR_SLEEP 30
+
+#define INFF_IOCTL 0x0408 /* IO control */
+#define INFF_IOCTL_CLK 0x0001
+#define INFF_IOCTL_FGC 0x0002
+#define INFF_RESET_CTL 0x0800
+#define INFF_RESET_CTL_RESET 0x0001
+
+/* PL-368 DMP definitions */
+#define DMP_DESC_TYPE_MSK 0x0000000F
+#define DMP_DESC_EMPTY 0x00000000
+#define DMP_DESC_VALID 0x00000001
+#define DMP_DESC_COMPONENT 0x00000001
+#define DMP_DESC_MASTER_PORT 0x00000003
+#define DMP_DESC_ADDRESS 0x00000005
+#define DMP_DESC_ADDRSIZE_GT32 0x00000008
+#define DMP_DESC_EOT 0x0000000F
+
+#define DMP_COMP_DESIGNER 0xFFF00000
+#define DMP_COMP_DESIGNER_S 20
+#define DMP_COMP_PARTNUM 0x000FFF00
+#define DMP_COMP_PARTNUM_S 8
+#define DMP_COMP_CLASS 0x000000F0
+#define DMP_COMP_CLASS_S 4
+#define DMP_COMP_REVISION 0xFF000000
+#define DMP_COMP_REVISION_S 24
+#define DMP_COMP_NUM_SWRAP 0x00F80000
+#define DMP_COMP_NUM_SWRAP_S 19
+#define DMP_COMP_NUM_MWRAP 0x0007C000
+#define DMP_COMP_NUM_MWRAP_S 14
+#define DMP_COMP_NUM_SPORT 0x00003E00
+#define DMP_COMP_NUM_SPORT_S 9
+#define DMP_COMP_NUM_MPORT 0x000001F0
+#define DMP_COMP_NUM_MPORT_S 4
+
+#define DMP_MASTER_PORT_UID 0x0000FF00
+#define DMP_MASTER_PORT_UID_S 8
+#define DMP_MASTER_PORT_NUM 0x000000F0
+#define DMP_MASTER_PORT_NUM_S 4
+
+#define DMP_SLAVE_ADDR_BASE 0xFFFFF000
+#define DMP_SLAVE_ADDR_BASE_S 12
+#define DMP_SLAVE_PORT_NUM 0x00000F00
+#define DMP_SLAVE_PORT_NUM_S 8
+#define DMP_SLAVE_TYPE 0x000000C0
+#define DMP_SLAVE_TYPE_S 6
+#define DMP_SLAVE_TYPE_SLAVE 0
+#define DMP_SLAVE_TYPE_BRIDGE 1
+#define DMP_SLAVE_TYPE_SWRAP 2
+#define DMP_SLAVE_TYPE_MWRAP 3
+#define DMP_SLAVE_SIZE_TYPE 0x00000030
+#define DMP_SLAVE_SIZE_TYPE_S 4
+#define DMP_SLAVE_SIZE_4K 0
+#define DMP_SLAVE_SIZE_8K 1
+#define DMP_SLAVE_SIZE_16K 2
+#define DMP_SLAVE_SIZE_DESC 3
+
+/* ARM CR4 core specific control flag bits */
+#define ARMCR4_INFF_IOCTL_CPUHALT 0x0020
+
+/* D11 core specific control flag bits */
+#define D11_INFF_IOCTL_PHYCLOCKEN 0x0004
+#define D11_INFF_IOCTL_PHYRESET 0x0008
+
+/* chip core base & ramsize */
+
+/* Max possibly supported memory size (limited by IO mapped memory) */
+#define INFF_CHIP_MAX_MEMSIZE (4 * 1024 * 1024)
+
+#define INVALID_RAMBASE ((u32)(~0))
+
+/* bankidx and bankinfo reg defines corerev >= 8 */
+#define SOCRAM_BANKINFO_RETNTRAM_MASK 0x00010000
+#define SOCRAM_BANKINFO_SZMASK 0x0000007f
+#define SOCRAM_BANKIDX_ROM_MASK 0x00000100
+
+#define SOCRAM_BANKIDX_MEMTYPE_SHIFT 8
+/* socram bankinfo memtype */
+#define SOCRAM_MEMTYPE_RAM 0
+#define SOCRAM_MEMTYPE_R0M 1
+#define SOCRAM_MEMTYPE_DEVRAM 2
+
+#define SOCRAM_BANKINFO_SZBASE 8192
+#define SRCI_SRNB_MASK 0xf0
+#define SRCI_SRNB_MASK_EXT 0x100
+#define SRCI_SRNB_SHIFT 4
+
+struct inff_sbsocramregs {
+ u32 coreinfo;
+ u32 bwalloc;
+ u32 extracoreinfo;
+ u32 biststat;
+ u32 bankidx;
+ u32 standbyctrl;
+
+ u32 errlogstatus; /* rev 6 */
+ u32 errlogaddr; /* rev 6 */
+ /* used for patching rev 3 & 5 */
+ u32 cambankidx;
+ u32 cambankstandbyctrl;
+ u32 cambankpatchctrl;
+ u32 cambankpatchtblbaseaddr;
+ u32 cambankcmdreg;
+ u32 cambankdatareg;
+ u32 cambankmaskreg;
+ u32 pad0[1];
+ u32 bankinfo; /* corev 8 */
+ u32 bankpda;
+ u32 pad1[14];
+ u32 extmemconfig;
+ u32 extmemparitycsr;
+ u32 extmemparityerrdata;
+ u32 extmemparityerrcnt;
+ u32 extmemwrctrlandsize;
+ u32 pad2[84];
+ u32 workaround;
+ u32 pwrctl; /* corerev >= 2 */
+ u32 pad3[133];
+ u32 sr_control; /* corerev >= 15 */
+ u32 sr_status; /* corerev >= 15 */
+ u32 sr_address; /* corerev >= 15 */
+ u32 sr_data; /* corerev >= 15 */
+};
+
+#define SOCRAMREGOFFS(_f) offsetof(struct inff_sbsocramregs, _f)
+#define SYSMEMREGOFFS(_f) offsetof(struct inff_sbsocramregs, _f)
+
+#define ARMCR4_CAP (0x04)
+#define ARMCR4_BANKIDX (0x40)
+#define ARMCR4_BANKINFO (0x44)
+#define ARMCR4_BANKPDA (0x4C)
+
+#define ARMCR4_TCBBNB_MASK 0xf0
+#define ARMCR4_TCBBNB_SHIFT 4
+#define ARMCR4_TCBANB_MASK 0xf
+#define ARMCR4_TCBANB_SHIFT 0
+
+#define ARMCR4_BSZ_MASK 0x7f
+#define ARMCR4_BSZ_MULT 8192
+#define ARMCR4_BLK_1K_MASK 0x200
+
+#define INFF_BLHS_POLL_INTERVAL 10 /* msec */
+#define INFF_BLHS_D2H_READY_TIMEOUT 5000 /* msec */
+#define INFF_BLHS_D2H_TRXHDR_PARSE_DONE_TIMEOUT 50 /* msec */
+
+#define INFF_BLHS_D2H_VALDN_DONE_TIMEOUT 1000 /* msec */
+#define INFF_BLHS_D2H_MV_NVRAM_DONE_TIMEOUT (100) /* msec */
+#define INFF_BLHS_D2H_BP_CLK_DISABLE_REQ_TIMEOUT (5 * 1000) /* msec */
+
+/* Bootloader handshake flags - dongle to host */
+#define INFF_BLHS_D2H_START BIT(0)
+#define INFF_BLHS_D2H_READY BIT(1)
+#define INFF_BLHS_D2H_STEADY BIT(2)
+#define INFF_BLHS_D2H_TRXHDR_PARSE_DONE BIT(3)
+#define INFF_BLHS_D2H_VALDN_START BIT(4)
+#define INFF_BLHS_D2H_VALDN_RESULT BIT(5)
+#define INFF_BLHS_D2H_VALDN_DONE BIT(6)
+#define INFF_BLHS_D2H_MV_NVRAM_DONE BIT(7)
+#define INFF_BLHS_D2H_BP_CLK_DISABLE_REQ BIT(8)
+#define INFF_BLHS_D2H_FW_OEM_HDR_PARSE_DONE_ACW74XX BIT(9)
+#define INFF_BLHS_D2H_FW_OEM_SIGN_VALDN_START_ACW74XX BIT(10)
+#define INFF_BLHS_D2H_FW_OEM_SIGN_VALDN_RESULT_ACW74XX BIT(11)
+#define INFF_BLHS_D2H_FW_OEM_SIGN_VALDN_DONE_ACW74XX BIT(12)
+#define INFF_BLHS_D2H_NVRAM_OEM_HDR_PARSE_DONE_ACW74XX BIT(13)
+#define INFF_BLHS_D2H_NVRAM_OEM_HDR_SIGN_VALDN_START_ACW74XX BIT(14)
+#define INFF_BLHS_D2H_NVRAM_OEM_HDR_SIGN_VALDN_RESULT_ACW74XX BIT(15)
+#define INFF_BLHS_D2H_NVRAM_OEM_HDR_SIGN_VALDN_DONE_ACW74XX BIT(16)
+#define INFF_BLHS_D2H_MAINLINE_FW_READY_ACW74XX BIT(17)
+
+#define INFF_BLHS_D2H_ACW74XX_READY BIT(24)
+#define INFF_BLHS_D2H_WLAN_FAILED_ACW74XX BIT(25)
+#define INFF_BLHS_D2H_ACW74XX_FW_DWLD_READY (INFF_BLHS_D2H_READY)
+
+/* Bootloader handshake flags - host to dongle */
+#define INFF_BLHS_H2D_DL_FW_START BIT(0)
+#define INFF_BLHS_H2D_DL_FW_DONE BIT(1)
+#define INFF_BLHS_H2D_DL_NVRAM_DONE BIT(2)
+#define INFF_BLHS_H2D_BL_RESET_ON_ERROR BIT(3)
+#define INFF_BLHS_H2D_DL_NVRAM_START BIT(4)
+#define INFF_BLHS_H2D_BP_CLK_DISABLE_ACK BIT(5)
+
+char *inff_chip_name(u32 id, u32 rev, char *buf, uint len)
+{
+ const char *fmt;
+
+ if (id == INF_CC_ACW74XX_CHIP_ID)
+ fmt = "ACW74xx";
+ else
+ fmt = ((id > 0xa000) || (id < 0x4000)) ? "CYW%d/%u" : "CYW%x/%u";
+
+ snprintf(buf, len, fmt, id, rev);
+ return buf;
+}
+
+static struct inff_core *inff_chip_add_core(struct inff_chip_priv *ci,
+ u16 coreid, u32 base,
+ u32 wrapbase)
+{
+ struct inff_core_priv *core;
+
+ core = kzalloc_obj(*core);
+ if (!core)
+ return ERR_PTR(-ENOMEM);
+
+ core->pub.id = coreid;
+ core->pub.base = base;
+ core->chip = ci;
+ core->wrapbase = wrapbase;
+
+ list_add_tail(&core->list, &ci->cores);
+ return &core->pub;
+}
+
+/* safety check for chipinfo */
+static int inff_chip_cores_check(struct inff_chip_priv *ci)
+{
+ struct inff_core_priv *core;
+ bool need_socram = false;
+ bool has_socram = false;
+ bool cpu_found = false;
+ int idx = 1;
+
+ list_for_each_entry(core, &ci->cores, list) {
+ inff_dbg(INFO, " [%-2d] core 0x%x:%-3d base 0x%08x wrap 0x%08x\n",
+ idx++, core->pub.id, core->pub.rev, core->pub.base,
+ core->wrapbase);
+
+ switch (core->pub.id) {
+ case INF_CORE_ARM_CM3:
+ cpu_found = true;
+ need_socram = true;
+ break;
+ case INF_CORE_INTERNAL_MEM:
+ has_socram = true;
+ break;
+ case INF_CORE_ARM_CR4:
+ cpu_found = true;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (!cpu_found) {
+ inff_err("CPU core not detected\n");
+ return -ENXIO;
+ }
+ /* check RAM core presence for ARM CM3 core */
+ if (need_socram && !has_socram) {
+ inff_err("RAM core not provided with ARM CM3 core\n");
+ return -ENODEV;
+ }
+ return 0;
+}
+
+static u32 inff_chip_core_read32(struct inff_core_priv *core, u16 reg)
+{
+ return core->chip->ops->read32(core->chip->ctx, core->pub.base + reg);
+}
+
+static void inff_chip_core_write32(struct inff_core_priv *core,
+ u16 reg, u32 val)
+{
+ core->chip->ops->write32(core->chip->ctx, core->pub.base + reg, val);
+}
+
+static bool inff_chip_socram_banksize(struct inff_core_priv *core, u8 idx,
+ u32 *banksize)
+{
+ u32 bankinfo;
+ u32 bankidx = (SOCRAM_MEMTYPE_RAM << SOCRAM_BANKIDX_MEMTYPE_SHIFT);
+
+ bankidx |= idx;
+ inff_chip_core_write32(core, SOCRAMREGOFFS(bankidx), bankidx);
+ bankinfo = inff_chip_core_read32(core, SOCRAMREGOFFS(bankinfo));
+ *banksize = (bankinfo & SOCRAM_BANKINFO_SZMASK) + 1;
+ *banksize *= SOCRAM_BANKINFO_SZBASE;
+ return !!(bankinfo & SOCRAM_BANKINFO_RETNTRAM_MASK);
+}
+
+static void inff_chip_socram_ramsize(struct inff_core_priv *sr, u32 *ramsize,
+ u32 *srsize)
+{
+ u32 coreinfo;
+ uint nb, banksize;
+ bool retent;
+ int i;
+
+ *ramsize = 0;
+ *srsize = 0;
+
+ /* chip has its own specific pre-assigned ramsize. */
+ if (sr->chip->pub.chip_spec.hw_chip_ramsize) {
+ *ramsize = sr->chip->pub.chip_spec.hw_chip_ramsize;
+ return;
+ }
+
+ if (WARN_ON(sr->pub.rev < 4))
+ return;
+
+ if (!inff_chip_iscoreup(&sr->pub))
+ inff_chip_resetcore(&sr->pub, 0, 0, 0);
+
+ /* Get info for determining size */
+ coreinfo = inff_chip_core_read32(sr, SOCRAMREGOFFS(coreinfo));
+
+ /* length of SRAM Banks increased for corerev greater than 23 */
+ nb = (coreinfo & (SRCI_SRNB_MASK | SRCI_SRNB_MASK_EXT)) >> SRCI_SRNB_SHIFT;
+ for (i = 0; i < nb; i++) {
+ retent = inff_chip_socram_banksize(sr, i, &banksize);
+ *ramsize += banksize;
+ if (retent)
+ *srsize += banksize;
+ }
+}
+
+/** Return the SYS MEM size */
+static u32 inff_chip_sysmem_ramsize(struct inff_core_priv *sysmem)
+{
+ u32 memsize = 0;
+ u32 coreinfo;
+ u32 idx;
+ u32 nb;
+ u32 banksize;
+
+ if (!inff_chip_iscoreup(&sysmem->pub))
+ inff_chip_resetcore(&sysmem->pub, 0, 0, 0);
+
+ coreinfo = inff_chip_core_read32(sysmem, SYSMEMREGOFFS(coreinfo));
+ nb = (coreinfo & SRCI_SRNB_MASK) >> SRCI_SRNB_SHIFT;
+
+ for (idx = 0; idx < nb; idx++) {
+ inff_chip_socram_banksize(sysmem, idx, &banksize);
+ memsize += banksize;
+ }
+
+ return memsize;
+}
+
+/** Return the TCM-RAM size of the ARMCR4 core. */
+static u32 inff_chip_tcm_ramsize(struct inff_core_priv *cr4)
+{
+ u32 corecap;
+ u32 memsize = 0;
+ u32 nab;
+ u32 nbb;
+ u32 totb;
+ u32 bxinfo;
+ u32 blksize;
+ u32 idx;
+
+ corecap = inff_chip_core_read32(cr4, ARMCR4_CAP);
+
+ nab = (corecap & ARMCR4_TCBANB_MASK) >> ARMCR4_TCBANB_SHIFT;
+ nbb = (corecap & ARMCR4_TCBBNB_MASK) >> ARMCR4_TCBBNB_SHIFT;
+ totb = nab + nbb;
+
+ for (idx = 0; idx < totb; idx++) {
+ inff_chip_core_write32(cr4, ARMCR4_BANKIDX, idx);
+ bxinfo = inff_chip_core_read32(cr4, ARMCR4_BANKINFO);
+ blksize = ARMCR4_BSZ_MULT;
+ if (bxinfo & ARMCR4_BLK_1K_MASK)
+ blksize >>= 3;
+
+ memsize += ((bxinfo & ARMCR4_BSZ_MASK) + 1) * blksize;
+ }
+
+ return memsize;
+}
+
+static u32 inff_chip_tcm_rambase(struct inff_chip_priv *ci)
+{
+ if (ci) {
+ if (ci->pub.chip_spec.hw_chip_rambase)
+ return ci->pub.chip_spec.hw_chip_rambase;
+
+ inff_err("chip: %s hasn't assigned rambase.\n", ci->pub.name);
+ }
+ return INVALID_RAMBASE;
+}
+
+int inff_chip_get_raminfo(struct inff_chip *pub)
+{
+ struct inff_chip_priv *ci = container_of(pub, struct inff_chip_priv,
+ pub);
+ struct inff_core_priv *mem_core;
+ struct inff_core *mem;
+
+ mem = inff_chip_get_core(&ci->pub, INF_CORE_ARM_CR4);
+ if (mem) {
+ mem_core = container_of(mem, struct inff_core_priv, pub);
+ ci->pub.ramsize = inff_chip_tcm_ramsize(mem_core);
+
+ ci->pub.ramsize -= (pub->chip_spec.hw_chip_tcam_size +
+ pub->chip_spec.hw_chip_trxhdr_size);
+
+ ci->pub.rambase = inff_chip_tcm_rambase(ci);
+ if (ci->pub.rambase == INVALID_RAMBASE) {
+ inff_err("RAM base not provided with ARM CR4 core\n");
+ return -EINVAL;
+ }
+ } else {
+ mem = inff_chip_get_core(&ci->pub, INF_CORE_SYS_MEM);
+ if (mem) {
+ mem_core = container_of(mem, struct inff_core_priv,
+ pub);
+ ci->pub.ramsize = inff_chip_sysmem_ramsize(mem_core);
+ ci->pub.rambase = inff_chip_tcm_rambase(ci);
+ if (ci->pub.rambase == INVALID_RAMBASE) {
+ inff_err("RAM base not provided with ARM CA7 core\n");
+ return -EINVAL;
+ }
+ } else {
+ mem = inff_chip_get_core(&ci->pub,
+ INF_CORE_INTERNAL_MEM);
+ if (mem) {
+ mem_core = container_of(mem, struct inff_core_priv,
+ pub);
+ inff_chip_socram_ramsize(mem_core, &ci->pub.ramsize,
+ &ci->pub.srsize);
+ } else if (pub->chip == INF_CC_ACW74XX_CHIP_ID) {
+ ci->pub.rambase = inff_chip_tcm_rambase(ci);
+ ci->pub.ramsize = ci->pub.chip_spec.hw_chip_ramsize;
+ ci->pub.cpuss_rambase = ci->pub.chip_spec.hw_chip_cpuss_rambase;
+ ci->pub.cpuss_ramsize = ci->pub.chip_spec.hw_chip_cpuss_ramsize;
+ } else {
+ inff_err("No memory cores found\n");
+ return -ENOMEM;
+ }
+ }
+ }
+ inff_dbg(INFO, "RAM: base=0x%x size=%d (0x%x) sr=%d (0x%x)\n",
+ ci->pub.rambase, ci->pub.ramsize, ci->pub.ramsize,
+ ci->pub.srsize, ci->pub.srsize);
+
+ if (!ci->pub.ramsize) {
+ inff_err("RAM size is undetermined\n");
+ return -ENOMEM;
+ }
+
+ if (ci->pub.ramsize > INFF_CHIP_MAX_MEMSIZE) {
+ inff_err("RAM size is incorrect\n");
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static u32 inff_chip_dmp_get_desc(struct inff_chip_priv *ci, u32 *eromaddr,
+ u8 *type)
+{
+ u32 val;
+
+ /* read next descriptor */
+ val = ci->ops->read32(ci->ctx, *eromaddr);
+ *eromaddr += 4;
+
+ if (!type)
+ return val;
+
+ /* determine descriptor type */
+ *type = (val & DMP_DESC_TYPE_MSK);
+ if ((*type & ~DMP_DESC_ADDRSIZE_GT32) == DMP_DESC_ADDRESS)
+ *type = DMP_DESC_ADDRESS;
+
+ return val;
+}
+
+static int inff_chip_dmp_get_regaddr(struct inff_chip_priv *ci, u32 *eromaddr,
+ u32 *regbase, u32 *wrapbase)
+{
+ u8 desc;
+ u32 val, szdesc;
+ u8 stype, sztype, wraptype;
+
+ *regbase = 0;
+ *wrapbase = 0;
+
+ val = inff_chip_dmp_get_desc(ci, eromaddr, &desc);
+ if (desc == DMP_DESC_MASTER_PORT) {
+ wraptype = DMP_SLAVE_TYPE_MWRAP;
+ } else if (desc == DMP_DESC_ADDRESS) {
+ /* revert erom address */
+ *eromaddr -= 4;
+ wraptype = DMP_SLAVE_TYPE_SWRAP;
+ } else {
+ *eromaddr -= 4;
+ return -EILSEQ;
+ }
+
+ do {
+ /* locate address descriptor */
+ do {
+ val = inff_chip_dmp_get_desc(ci, eromaddr, &desc);
+ /* unexpected table end */
+ if (desc == DMP_DESC_EOT) {
+ *eromaddr -= 4;
+ return -EFAULT;
+ }
+ } while (desc != DMP_DESC_ADDRESS &&
+ desc != DMP_DESC_COMPONENT);
+
+ /* stop if we crossed current component border */
+ if (desc == DMP_DESC_COMPONENT) {
+ *eromaddr -= 4;
+ return 0;
+ }
+
+ /* skip upper 32-bit address descriptor */
+ if (val & DMP_DESC_ADDRSIZE_GT32)
+ inff_chip_dmp_get_desc(ci, eromaddr, NULL);
+
+ sztype = (val & DMP_SLAVE_SIZE_TYPE) >> DMP_SLAVE_SIZE_TYPE_S;
+
+ /* next size descriptor can be skipped */
+ if (sztype == DMP_SLAVE_SIZE_DESC) {
+ szdesc = inff_chip_dmp_get_desc(ci, eromaddr, NULL);
+ /* skip upper size descriptor if present */
+ if (szdesc & DMP_DESC_ADDRSIZE_GT32)
+ inff_chip_dmp_get_desc(ci, eromaddr, NULL);
+ }
+
+ /* look for 4K or 8K register regions */
+ if (sztype != DMP_SLAVE_SIZE_4K &&
+ sztype != DMP_SLAVE_SIZE_8K)
+ continue;
+
+ stype = (val & DMP_SLAVE_TYPE) >> DMP_SLAVE_TYPE_S;
+
+ /* only regular slave and wrapper */
+ if (*regbase == 0 && stype == DMP_SLAVE_TYPE_SLAVE)
+ *regbase = val & DMP_SLAVE_ADDR_BASE;
+ if (*wrapbase == 0 && stype == wraptype)
+ *wrapbase = val & DMP_SLAVE_ADDR_BASE;
+ } while (*regbase == 0 || *wrapbase == 0);
+
+ return 0;
+}
+
+static
+int inff_chip_dmp_erom_scan(struct inff_chip_priv *ci)
+{
+ struct inff_core *core;
+ u32 eromaddr;
+ u8 desc_type = 0;
+ u32 val;
+ u16 id;
+ u8 nmw, nsw, rev;
+ u32 base, wrap;
+ int err;
+
+ if (ci->pub.ccsec)
+ eromaddr = ci->pub.ccsec->erombase;
+ else
+ eromaddr = ci->ops->read32(ci->ctx,
+ CORE_CC_REG(ci->pub.enum_base, eromptr));
+
+ while (desc_type != DMP_DESC_EOT) {
+ val = inff_chip_dmp_get_desc(ci, &eromaddr, &desc_type);
+ if (!(val & DMP_DESC_VALID))
+ continue;
+
+ if (desc_type == DMP_DESC_EMPTY)
+ continue;
+
+ /* need a component descriptor */
+ if (desc_type != DMP_DESC_COMPONENT)
+ continue;
+
+ id = (val & DMP_COMP_PARTNUM) >> DMP_COMP_PARTNUM_S;
+
+ /* next descriptor must be component as well */
+ val = inff_chip_dmp_get_desc(ci, &eromaddr, &desc_type);
+ if (WARN_ON((val & DMP_DESC_TYPE_MSK) != DMP_DESC_COMPONENT))
+ return -EFAULT;
+
+ /* only look at cores with master port(s) */
+ nmw = (val & DMP_COMP_NUM_MWRAP) >> DMP_COMP_NUM_MWRAP_S;
+ nsw = (val & DMP_COMP_NUM_SWRAP) >> DMP_COMP_NUM_SWRAP_S;
+ rev = (val & DMP_COMP_REVISION) >> DMP_COMP_REVISION_S;
+
+ /* need core with ports */
+ if (nmw + nsw == 0 &&
+ id != INF_CORE_PMU &&
+ id != INF_CORE_GCI &&
+ id != INF_CORE_SR)
+ continue;
+
+ /* try to obtain register address info */
+ err = inff_chip_dmp_get_regaddr(ci, &eromaddr, &base, &wrap);
+ if (err)
+ continue;
+
+ /* finally a core to be added */
+ core = inff_chip_add_core(ci, id, base, wrap);
+ if (IS_ERR(core))
+ return PTR_ERR(core);
+
+ core->rev = rev;
+ }
+
+ return 0;
+}
+
+inline
+u32 inff_chip_enum_base(u16 devid)
+{
+ if (devid == SDIO_DEVICE_ID_CYPRESS_ACW7413)
+ return INFF_SI_ENUM_BASE_ACW74XX_DEFAULT;
+ else
+ return INFF_SI_ENUM_BASE_DEFAULT;
+}
+
+inline
+u32 inff_chip_get_hw_caps_replaycnts(struct inff_chip *pub)
+{
+ return pub->chip_spec.hw_caps_replaycnts;
+}
+
+static void inff_blhs_init(struct inff_chip *pub)
+{
+ struct inff_chip_priv *chip;
+ u32 addr;
+
+ chip = container_of(pub, struct inff_chip_priv, pub);
+ addr = pub->blhs->h2d;
+ inff_dbg(TRACE,
+ "h2d value before initing: 0x%08x (addr 0x%08x)\n",
+ pub->blhs->read(chip->ctx, addr),
+ addr);
+ pub->blhs->write(chip->ctx, addr, 0);
+}
+
+static int inff_blhs_is_bootloader_ready(struct inff_chip_priv *chip)
+{
+ u32 regdata = 0, addr = 0;
+ u32 mask = 0x0000FFFF, boot_status = 0, dlm_status = 0, dlm_shift = 16;
+
+ if (chip->pub.chip == INF_CC_ACW74XX_CHIP_ID) {
+ addr = chip->pub.blhs->d2h;
+ SPINWAIT_MS((chip->pub.blhs->readl(chip->ctx, addr) &
+ INFF_BLHS_D2H_ACW74XX_READY) == 0,
+ INFF_BLHS_D2H_READY_TIMEOUT,
+ INFF_BLHS_POLL_INTERVAL);
+
+ regdata = chip->pub.blhs->readl(chip->ctx, addr);
+ if (!(regdata & INFF_BLHS_D2H_ACW74XX_READY))
+ inff_err(" Bootloader_ready timeout ");
+ else
+ inff_dbg(INFO, " Bootloader is ready 0x%x", regdata);
+
+ /* Read the last BOOT/DLM status */
+ addr = chip->pub.blhs->d2h_2;
+ regdata = chip->pub.blhs->readl(chip->ctx, addr);
+ inff_err(" regdata = 0x%x ", regdata);
+
+ dlm_status = regdata >> dlm_shift;
+ boot_status = regdata & mask;
+ if (boot_status)
+ inff_err(" boot status error code = 0x%x\n", boot_status);
+
+ if (dlm_status)
+ inff_err(" dlm status error code = 0x%x\n", dlm_status);
+
+ } else {
+ addr = chip->pub.blhs->d2h;
+ SPINWAIT_MS((chip->pub.blhs->read(chip->ctx, addr) &
+ INFF_BLHS_D2H_READY) == 0,
+ INFF_BLHS_D2H_READY_TIMEOUT,
+ INFF_BLHS_POLL_INTERVAL);
+
+ regdata = chip->pub.blhs->read(chip->ctx, addr);
+ if (!(regdata & INFF_BLHS_D2H_READY)) {
+ inff_err("Timeout waiting for bootloader ready, waittime %d ms addr 0x%x\n",
+ INFF_BLHS_D2H_READY_TIMEOUT,
+ addr);
+ return -EPERM;
+ }
+ }
+
+ inff_dbg(TRACE, "bootloader is ready\n");
+ return 0;
+}
+
+static int inff_blhs_is_fw_download_ready(struct inff_chip_priv *chip)
+{
+ u32 addr, regdata;
+
+ chip->pub.blhs->send_cpuss_cmd(chip->ctx, INFF_CPUSS_CMD_WLAN_ON);
+
+ addr = chip->pub.blhs->d2h;
+ SPINWAIT_MS((chip->pub.blhs->readl(chip->ctx, addr) &
+ INFF_BLHS_D2H_ACW74XX_FW_DWLD_READY) == 0,
+ INFF_BLHS_D2H_READY_TIMEOUT,
+ INFF_BLHS_POLL_INTERVAL);
+
+ regdata = chip->pub.blhs->readl(chip->ctx, addr);
+
+ if (!(regdata & INFF_BLHS_D2H_ACW74XX_FW_DWLD_READY)) {
+ inff_err("Timeout (%d ms) for firmware download ready, regdata 0x%x\n",
+ INFF_BLHS_D2H_READY_TIMEOUT, regdata);
+
+ SPINWAIT_MS((chip->pub.blhs->readl(chip->ctx, addr) &
+ INFF_BLHS_D2H_WLAN_FAILED_ACW74XX) == 0,
+ INFF_BLHS_D2H_READY_TIMEOUT,
+ INFF_BLHS_POLL_INTERVAL);
+ regdata = chip->pub.blhs->readl(chip->ctx, addr);
+
+ if (regdata & INFF_BLHS_D2H_WLAN_FAILED_ACW74XX) {
+ inff_err("INFF_BLHS_D2H_WLAN_FAILED_ACW74XX, regdata 0x%x\n",
+ regdata);
+ }
+
+ return -EPERM;
+ }
+ inff_dbg(TRACE, "Firmware downloader is ready\n");
+
+ return 0;
+}
+
+static int inff_blhs_pre_nvram_download(struct inff_chip *pub)
+{
+ struct inff_chip_priv *chip = container_of(pub, struct inff_chip_priv, pub);
+ u32 addr = pub->blhs->h2d;
+
+ /* Host notification about NVRAM download start */
+ pub->blhs->write(chip->ctx, addr, INFF_BLHS_H2D_DL_NVRAM_START);
+
+ return 0;
+}
+
+static int inff_blhs_prep_fw_download(struct inff_chip *pub)
+{
+ struct inff_chip_priv *chip;
+ u32 addr;
+ int err;
+
+ chip = container_of(pub, struct inff_chip_priv, pub);
+
+ /* Host indication for bootloader to start the init */
+ inff_blhs_init(pub);
+
+ chip = container_of(pub, struct inff_chip_priv, pub);
+ err = inff_blhs_is_bootloader_ready(chip);
+ if (err)
+ return err;
+
+ if (pub->chip == INF_CC_ACW74XX_CHIP_ID) {
+ err = inff_blhs_is_fw_download_ready(chip);
+ if (err)
+ return err;
+ }
+
+ /* Host notification about FW download start */
+ addr = pub->blhs->h2d;
+ pub->blhs->write(chip->ctx, addr, INFF_BLHS_H2D_DL_FW_START);
+
+ return 0;
+}
+
+static int inff_blhs_post_fw_download(struct inff_chip *pub)
+{
+ struct inff_chip_priv *chip;
+ u32 addr;
+ u32 regdata;
+ int err = 0;
+
+ chip = container_of(pub, struct inff_chip_priv, pub);
+ addr = pub->blhs->h2d;
+ pub->blhs->write(chip->ctx, addr, INFF_BLHS_H2D_DL_FW_DONE);
+
+ if (pub->chip == INF_CC_ACW74XX_CHIP_ID) {
+ addr = pub->blhs->d2h;
+ SPINWAIT_MS((chip->pub.blhs->readl(chip->ctx, addr) &
+ INFF_BLHS_D2H_FW_OEM_HDR_PARSE_DONE_ACW74XX) == 0,
+ INFF_BLHS_D2H_READY_TIMEOUT,
+ INFF_BLHS_POLL_INTERVAL);
+
+ regdata = chip->pub.blhs->readl(chip->ctx, addr);
+ if (!(regdata & INFF_BLHS_D2H_FW_OEM_HDR_PARSE_DONE_ACW74XX)) {
+ inff_err("Timeout (%d ms) for FW OEM HDR parsing completion, regdata 0x%x\n",
+ INFF_BLHS_D2H_READY_TIMEOUT, regdata);
+ return -EPERM;
+ }
+ inff_dbg(INFO, "FW OEM HDR parsing is successful\n");
+ } else {
+ addr = pub->blhs->d2h;
+ SPINWAIT_MS((pub->blhs->read(chip->ctx, addr) &
+ INFF_BLHS_D2H_TRXHDR_PARSE_DONE) == 0,
+ INFF_BLHS_D2H_TRXHDR_PARSE_DONE_TIMEOUT,
+ INFF_BLHS_POLL_INTERVAL);
+
+ regdata = pub->blhs->read(chip->ctx, addr);
+ if (!(regdata & INFF_BLHS_D2H_TRXHDR_PARSE_DONE)) {
+ inff_err("TRX header parsing failed\n");
+
+ /* Host indication for bootloader to get reset on error */
+ addr = pub->blhs->h2d;
+ regdata = pub->blhs->read(chip->ctx, addr);
+ regdata |= INFF_BLHS_H2D_BL_RESET_ON_ERROR;
+ pub->blhs->write(chip->ctx, addr, regdata);
+
+ return -EPERM;
+ }
+ }
+
+ return err;
+}
+
+static int inff_nvram_validation_acw74xx(struct inff_chip *pub)
+{
+ struct inff_chip_priv *chip;
+ u32 addr, regdata;
+
+ chip = container_of(pub, struct inff_chip_priv, pub);
+ addr = pub->blhs->d2h;
+
+ SPINWAIT_MS((chip->pub.blhs->readl(chip->ctx, addr) &
+ INFF_BLHS_D2H_NVRAM_OEM_HDR_PARSE_DONE_ACW74XX) == 0,
+ INFF_BLHS_D2H_READY_TIMEOUT,
+ INFF_BLHS_POLL_INTERVAL);
+ regdata = chip->pub.blhs->readl(chip->ctx, addr);
+
+ if (!(regdata & INFF_BLHS_D2H_NVRAM_OEM_HDR_PARSE_DONE_ACW74XX)) {
+ inff_err("Timeout (%d ms) for NVRAM HDR parsing completion, regdata 0x%x\n",
+ INFF_BLHS_D2H_READY_TIMEOUT, regdata);
+ return -EPERM;
+ }
+ inff_dbg(INFO, "NVRAM HDR parsing is successful\n");
+
+ SPINWAIT_MS((chip->pub.blhs->readl(chip->ctx, addr) &
+ INFF_BLHS_D2H_NVRAM_OEM_HDR_SIGN_VALDN_DONE_ACW74XX) == 0,
+ INFF_BLHS_D2H_READY_TIMEOUT,
+ INFF_BLHS_POLL_INTERVAL);
+ regdata = chip->pub.blhs->readl(chip->ctx, addr);
+
+ if (!(regdata & INFF_BLHS_D2H_NVRAM_OEM_HDR_SIGN_VALDN_DONE_ACW74XX)) {
+ inff_err("Timeout (%d ms) for NVRAM OEM HDR Signature validation, regdata 0x%x\n",
+ INFF_BLHS_D2H_READY_TIMEOUT, regdata);
+ return -EPERM;
+ } else if (!(regdata & INFF_BLHS_D2H_NVRAM_OEM_HDR_SIGN_VALDN_RESULT_ACW74XX)) {
+ inff_err("NVRAM OEM Signature validation failed, regdata 0x%x\n",
+ regdata);
+ return -EPERM;
+ }
+ inff_dbg(INFO, "NVRAM OEM Signature validation is successful\n");
+
+ SPINWAIT_MS((chip->pub.blhs->readl(chip->ctx, addr) &
+ INFF_BLHS_D2H_MAINLINE_FW_READY_ACW74XX) == 0,
+ INFF_BLHS_D2H_READY_TIMEOUT,
+ INFF_BLHS_POLL_INTERVAL);
+ regdata = chip->pub.blhs->readl(chip->ctx, addr);
+
+ if (!(regdata & INFF_BLHS_D2H_MAINLINE_FW_READY_ACW74XX)) {
+ inff_err("Mainline firmware ready failed, regdata 0x%x\n",
+ regdata);
+
+ SPINWAIT_MS((chip->pub.blhs->readl(chip->ctx, addr) &
+ INFF_BLHS_D2H_WLAN_FAILED_ACW74XX) == 0,
+ INFF_BLHS_D2H_READY_TIMEOUT,
+ INFF_BLHS_POLL_INTERVAL);
+ regdata = chip->pub.blhs->readl(chip->ctx, addr);
+
+ if (regdata & INFF_BLHS_D2H_WLAN_FAILED_ACW74XX)
+ inff_err("INFF_BLHS_D2H_WLAN_FAILED_ACW74XX, regdata 0x%x\n", regdata);
+
+ return -EPERM;
+ }
+ inff_dbg(INFO, "BL jumped to mainline FW\n");
+
+ return 0;
+}
+
+static int inff_blhs_post_nvram_download(struct inff_chip *pub)
+{
+ struct inff_chip_priv *chip;
+ u32 addr;
+ u32 regdata;
+
+ chip = container_of(pub, struct inff_chip_priv, pub);
+ addr = pub->blhs->h2d;
+
+ regdata = pub->blhs->read(chip->ctx, addr);
+ regdata |= INFF_BLHS_H2D_DL_NVRAM_DONE;
+ pub->blhs->write(chip->ctx, addr,
+ INFF_BLHS_H2D_DL_NVRAM_DONE | INFF_BLHS_H2D_DL_NVRAM_START);
+
+ if (pub->chip == INF_CC_ACW74XX_CHIP_ID)
+ return inff_nvram_validation_acw74xx(pub);
+
+ return 0;
+}
+
+static int inff_blhs_chk_validation(struct inff_chip *pub)
+{
+ struct inff_chip_priv *chip;
+ u32 addr;
+ u32 regdata;
+
+ chip = container_of(pub, struct inff_chip_priv, pub);
+ addr = pub->blhs->d2h;
+
+ if (pub->chip == INF_CC_ACW74XX_CHIP_ID) {
+ SPINWAIT_MS((chip->pub.blhs->readl(chip->ctx, addr) &
+ INFF_BLHS_D2H_FW_OEM_SIGN_VALDN_DONE_ACW74XX) == 0,
+ INFF_BLHS_D2H_READY_TIMEOUT,
+ INFF_BLHS_POLL_INTERVAL);
+ regdata = chip->pub.blhs->readl(chip->ctx, addr);
+
+ if (!(regdata & INFF_BLHS_D2H_FW_OEM_SIGN_VALDN_DONE_ACW74XX)) {
+ inff_err("Timeout (%d ms) for FW OEM Signature validation, regdata 0x%x\n",
+ INFF_BLHS_D2H_READY_TIMEOUT, regdata);
+ return -EPERM;
+ } else if (!(regdata & INFF_BLHS_D2H_FW_OEM_SIGN_VALDN_RESULT_ACW74XX)) {
+ inff_err("FW OEM Signature validation failed, regdata 0x%x\n",
+ regdata);
+ return -EPERM;
+ }
+ inff_dbg(INFO, "FW OEM Signature validation is successful\n");
+
+ SPINWAIT_MS((pub->blhs->read(chip->ctx, addr) &
+ INFF_BLHS_D2H_TRXHDR_PARSE_DONE) == 0,
+ INFF_BLHS_D2H_TRXHDR_PARSE_DONE_TIMEOUT,
+ INFF_BLHS_POLL_INTERVAL);
+
+ regdata = pub->blhs->read(chip->ctx, addr);
+
+ if (!(regdata & INFF_BLHS_D2H_TRXHDR_PARSE_DONE)) {
+ inff_err("Timeout (%d ms) for TRX HDR parsing completion, regdata 0x%x\n",
+ INFF_BLHS_D2H_TRXHDR_PARSE_DONE_TIMEOUT, regdata);
+ return -EPERM;
+ }
+ inff_dbg(INFO, "TRX HDR parsing is successful\n");
+ }
+
+ SPINWAIT_MS((pub->blhs->read(chip->ctx, addr) &
+ INFF_BLHS_D2H_VALDN_DONE) == 0,
+ INFF_BLHS_D2H_VALDN_DONE_TIMEOUT,
+ INFF_BLHS_POLL_INTERVAL);
+
+ regdata = pub->blhs->read(chip->ctx, addr);
+ if (!(regdata & INFF_BLHS_D2H_VALDN_DONE) ||
+ !(regdata & INFF_BLHS_D2H_VALDN_RESULT)) {
+ inff_err("TRX image validation check failed, timeout %d\n",
+ INFF_BLHS_D2H_VALDN_DONE_TIMEOUT);
+
+ /* Host notification for bootloader to get reset on error */
+ if (pub->chip != INF_CC_ACW74XX_CHIP_ID) {
+ addr = pub->blhs->h2d;
+ regdata = pub->blhs->read(chip->ctx, addr);
+ regdata |= INFF_BLHS_H2D_BL_RESET_ON_ERROR;
+ pub->blhs->write(chip->ctx, addr, regdata);
+ }
+
+ return -EPERM;
+ }
+
+ inff_dbg(INFO, "TRX Image validation check completed successfully\n");
+ return 0;
+}
+
+static int inff_blhs_post_watchdog_reset(struct inff_chip *pub)
+{
+ struct inff_chip_priv *chip;
+ int err;
+
+ /* Host indication for bootloader to start the init */
+ inff_blhs_init(pub);
+
+ chip = container_of(pub, struct inff_chip_priv, pub);
+ err = inff_blhs_is_bootloader_ready(chip);
+
+ return err;
+}
+
+static int inff_chip_recognition(struct inff_chip_priv *ci)
+{
+ u32 regdata;
+ int ret;
+ const u32 READ_FAILED = 0xFFFFFFFF;
+
+ /* Get CC core rev
+ * Chipid is in bus core if CC space is protected or
+ * it is assume to be at offset 0 from SI_ENUM_BASE
+ * For different chiptypes or old sdio hosts w/o chipcommon,
+ * other ways of recognition should be added here.
+ */
+ if (ci->pub.ccsec)
+ regdata = ci->pub.ccsec->chipid;
+ else
+ regdata = ci->ops->read32(ci->ctx,
+ CORE_CC_REG(ci->pub.enum_base, chipid));
+ if (regdata == READ_FAILED) {
+ inff_err("MMIO read failed: 0x%08x\n", regdata);
+ return -ENODEV;
+ }
+
+ ci->pub.chip = regdata & CID_ID_MASK;
+ ci->pub.chiprev = (regdata & CID_REV_MASK) >> CID_REV_SHIFT;
+ ci->pub.socitype = (regdata & CID_TYPE_MASK) >> CID_TYPE_SHIFT;
+ if (ci->pub.socitype == 0xf)
+ ci->pub.socitype = INFF_SOCI_AI;
+
+ inff_chip_name(ci->pub.chip, ci->pub.chiprev,
+ ci->pub.name, sizeof(ci->pub.name));
+
+ switch (ci->pub.socitype) {
+ case INFF_SOCI_AI:
+ inff_dbg(INFO, "found SOCI AXI (0x%x) chip: %s\n", ci->pub.socitype, ci->pub.name);
+ break;
+ case INFF_SOCI_CP:
+ inff_dbg(INFO, "found SOCI CP (0x%x) chip: %s\n", ci->pub.socitype, ci->pub.name);
+ break;
+ default:
+ inff_err("found unsupported backplane type N/A (0x%x) chip: %s\n",
+ ci->pub.socitype, ci->pub.name);
+ return -ENODEV;
+ }
+
+ switch (ci->pub.chip) {
+ case INF_CC_CYW5591X_CHIP_ID:
+ inff_chip_cyw5591x_init(&ci->pub);
+
+ inff_chip_dmp_erom_scan(ci);
+
+ break;
+ case INF_CC_ACW74XX_CHIP_ID:
+ inff_chip_acw74xx_init(&ci->pub);
+
+ inff_chip_add_core(ci, INF_CORE_ACW74XX, ci->pub.ccsec->bus_corebase, 0);
+
+ break;
+ default:
+ inff_err("chip %u is not supported\n", ci->pub.chip);
+ return -ENODEV;
+ }
+
+ ret = inff_chip_cores_check(ci);
+ if (ret)
+ return ret;
+
+ if (ci->pub.socitype == INFF_SOCI_CP) {
+ if (ci->ops->reset)
+ ci->ops->reset(ci->ctx, &ci->pub);
+ return 0;
+ }
+
+ return inff_chip_get_raminfo(&ci->pub);
+}
+
+static int inff_chip_setup(struct inff_chip_priv *chip)
+{
+ struct inff_chip *pub;
+ struct inff_core_priv *cc;
+ struct inff_core *pmu;
+ u32 base;
+ u32 val;
+ int ret = 0;
+
+ if (chip->pub.chip == INF_CC_ACW74XX_CHIP_ID)
+ return ret;
+
+ pub = &chip->pub;
+ cc = list_first_entry(&chip->cores, struct inff_core_priv, list);
+ base = cc->pub.base;
+
+ /* get chipcommon capabilities */
+ pub->cc_caps = chip->ops->read32(chip->ctx,
+ CORE_CC_REG(base, capabilities));
+ pub->cc_caps_ext = chip->ops->read32(chip->ctx,
+ CORE_CC_REG(base,
+ capabilities_ext));
+
+ /* get pmu caps & rev */
+ pmu = inff_chip_get_pmu(pub); /* after reading cc_caps_ext */
+ if (pub->cc_caps & CC_CAP_PMU) {
+ val = chip->ops->read32(chip->ctx,
+ CORE_CC_REG(pmu->base, pmucapabilities));
+ pub->pmurev = val & PCAP_REV_MASK;
+ pub->pmucaps = val;
+ }
+
+ inff_dbg(INFO, "ccrev=%d, pmurev=%d, pmucaps=0x%x\n",
+ cc->pub.rev, pub->pmurev, pub->pmucaps);
+
+ /* execute bus core specific setup */
+ if (chip->ops->setup)
+ ret = chip->ops->setup(chip->ctx, pub);
+
+ return ret;
+}
+
+struct inff_chip *inff_chip_attach(void *ctx, u16 devid,
+ const struct inff_buscore_ops *ops)
+{
+ struct inff_chip_priv *chip;
+ struct inff_blhs *blhs;
+ struct inff_ccsec *ccsec;
+ int err = 0;
+
+ if (WARN_ON(!ops->read32))
+ err = -EINVAL;
+ if (WARN_ON(!ops->write32))
+ err = -EINVAL;
+ if (WARN_ON(!ops->prepare))
+ err = -EINVAL;
+ if (WARN_ON(!ops->activate))
+ err = -EINVAL;
+ if (err < 0)
+ return ERR_PTR(-EINVAL);
+
+ chip = kzalloc_obj(*chip);
+ if (!chip)
+ return ERR_PTR(-ENOMEM);
+
+ INIT_LIST_HEAD(&chip->cores);
+ chip->num_cores = 0;
+ chip->ops = ops;
+ chip->ctx = ctx;
+
+ switch (devid) {
+ case SDIO_DEVICE_ID_CYPRESS_CYW55900:
+ if (chip->ops->sec_attach) {
+ ccsec = NULL;
+ err = chip->ops->sec_attach(chip->ctx, NULL, &ccsec,
+ 0, 0, 0);
+ if (err < 0)
+ goto fail;
+
+ chip->pub.ccsec = ccsec;
+ }
+ break;
+ case SDIO_DEVICE_ID_CYPRESS_ACW7413:
+ chip->pub.enum_base = inff_chip_enum_base(devid);
+
+ err = ops->prepare(ctx);
+ if (err < 0)
+ goto fail;
+
+ blhs = NULL;
+ ccsec = NULL;
+ if (chip->ops->sec_attach) {
+ err = chip->ops->sec_attach(chip->ctx, &blhs, &ccsec,
+ INFF_BLHS_D2H_READY,
+ INFF_BLHS_D2H_READY_TIMEOUT,
+ INFF_BLHS_POLL_INTERVAL);
+ if (err < 0)
+ goto fail;
+
+ chip->pub.blhs = blhs;
+ chip->pub.ccsec = ccsec;
+ if (blhs) {
+ blhs->init = inff_blhs_init;
+ blhs->pre_nvramdl = inff_blhs_pre_nvram_download;
+ blhs->prep_fwdl = inff_blhs_prep_fw_download;
+ blhs->post_fwdl = inff_blhs_post_fw_download;
+ blhs->post_nvramdl = inff_blhs_post_nvram_download;
+ blhs->chk_validation = inff_blhs_chk_validation;
+ blhs->post_wdreset = inff_blhs_post_watchdog_reset;
+ } else {
+ err = -EPERM;
+ inff_err("chip must support blhs!\n");
+ goto fail;
+ }
+ } else {
+ err = -EPERM;
+ inff_err("Dongle didn't support Secure Attach!\n");
+ goto fail;
+ }
+ break;
+ default:
+ err = -EPERM;
+ inff_err("Unsupported Chip for Attach!\n");
+ goto fail;
+ }
+
+ err = inff_chip_recognition(chip);
+ if (err < 0)
+ goto fail;
+
+ err = inff_chip_setup(chip);
+ if (err < 0)
+ goto fail;
+
+ return &chip->pub;
+
+fail:
+ inff_chip_detach(&chip->pub);
+ return ERR_PTR(err);
+}
+
+void inff_chip_detach(struct inff_chip *pub)
+{
+ struct inff_chip_priv *chip;
+ struct inff_core_priv *core;
+ struct inff_core_priv *tmp;
+
+ chip = container_of(pub, struct inff_chip_priv, pub);
+ list_for_each_entry_safe(core, tmp, &chip->cores, list) {
+ list_del(&core->list);
+ kfree(core);
+ }
+
+ kfree(pub->blhs);
+ kfree(pub->ccsec);
+ kfree(chip);
+}
+
+struct inff_core *inff_chip_get_d11core(struct inff_chip *pub, u8 unit)
+{
+ struct inff_chip_priv *chip;
+ struct inff_core_priv *core;
+
+ chip = container_of(pub, struct inff_chip_priv, pub);
+ list_for_each_entry(core, &chip->cores, list) {
+ if (core->pub.id == INF_CORE_80211) {
+ if (unit-- == 0)
+ return &core->pub;
+ }
+ }
+ return NULL;
+}
+
+struct inff_core *inff_chip_get_core(struct inff_chip *pub, u16 coreid)
+{
+ struct inff_chip_priv *chip;
+ struct inff_core_priv *core;
+
+ chip = container_of(pub, struct inff_chip_priv, pub);
+ list_for_each_entry(core, &chip->cores, list)
+ if (core->pub.id == coreid)
+ return &core->pub;
+
+ return NULL;
+}
+
+struct inff_core *inff_chip_get_chipcommon(struct inff_chip *pub)
+{
+ struct inff_chip_priv *chip;
+ struct inff_core_priv *cc;
+
+ chip = container_of(pub, struct inff_chip_priv, pub);
+ cc = list_first_entry(&chip->cores, struct inff_core_priv, list);
+ if (WARN_ON(!cc || cc->pub.id != INF_CORE_CHIPCOMMON))
+ return inff_chip_get_core(pub, INF_CORE_CHIPCOMMON);
+ return &cc->pub;
+}
+
+struct inff_core *inff_chip_get_pmu(struct inff_chip *pub)
+{
+ struct inff_core *cc = inff_chip_get_chipcommon(pub);
+ struct inff_core *pmu;
+
+ /* See if there is separated PMU core available */
+ if (cc->rev >= 35 &&
+ pub->cc_caps_ext & INF_CC_CAP_EXT_AOB_PRESENT) {
+ pmu = inff_chip_get_core(pub, INF_CORE_PMU);
+ if (pmu)
+ return pmu;
+ }
+
+ /* Fallback to ChipCommon core for older hardware */
+ return cc;
+}
+
+bool inff_chip_iscoreup(struct inff_core *pub)
+{
+ struct inff_core_priv *core;
+
+ core = container_of(pub, struct inff_core_priv, pub);
+ return core->chip->iscoreup(core);
+}
+
+void inff_chip_coredisable(struct inff_core *pub, u32 prereset, u32 reset)
+{
+ struct inff_core_priv *core;
+
+ core = container_of(pub, struct inff_core_priv, pub);
+ core->chip->coredisable(core, prereset, reset);
+}
+
+void inff_chip_resetcore(struct inff_core *pub, u32 prereset, u32 reset,
+ u32 postreset)
+{
+ struct inff_core_priv *core;
+
+ core = container_of(pub, struct inff_core_priv, pub);
+ core->chip->resetcore(core, prereset, reset, postreset);
+}
+
+static inline void
+inff_chip_cr4_set_passive(struct inff_chip_priv *chip)
+{
+ int i;
+ struct inff_core *core;
+
+ /* Disable the cores only and let the firmware enable them. */
+ for (i = 0; (core = inff_chip_get_d11core(&chip->pub, i)); i++)
+ inff_chip_coredisable(core, D11_INFF_IOCTL_PHYRESET |
+ D11_INFF_IOCTL_PHYCLOCKEN,
+ D11_INFF_IOCTL_PHYCLOCKEN);
+}
+
+void inff_chip_set_passive(struct inff_chip *pub)
+{
+ struct inff_chip_priv *chip;
+ struct inff_core *arm;
+
+ chip = container_of(pub, struct inff_chip_priv, pub);
+ arm = inff_chip_get_core(pub, INF_CORE_ARM_CR4);
+ if (arm) {
+ inff_chip_cr4_set_passive(chip);
+ return;
+ }
+}
+
+bool inff_chip_sr_capable(struct inff_chip *pub)
+{
+ switch (pub->chip) {
+ case INF_CC_ACW74XX_CHIP_ID:
+ return true;
+ case INF_CC_CYW5591X_CHIP_ID:
+ default:
+ inff_err("unsupported chip:0x%x\n", pub->chip);
+ }
+ return false;
+}
diff --git a/drivers/net/wireless/infineon/inffmac/chip.h b/drivers/net/wireless/infineon/inffmac/chip.h
new file mode 100644
index 000000000000..c37c7473129e
--- /dev/null
+++ b/drivers/net/wireless/infineon/inffmac/chip.h
@@ -0,0 +1,644 @@
+/* SPDX-License-Identifier: ISC */
+/*
+ * Copyright (c) 2014 Broadcom Corporation
+ *
+ * Copyright (c) 2025-2026, Infineon Technologies AG, or an affiliate of Infineon Technologies AG.
+ * All rights reserved.
+ */
+
+#ifndef INFF_CHIP_H
+#define INFF_CHIP_H
+
+#include <linux/types.h>
+#include <linux/firmware.h>
+
+#include "firmware.h"
+
+/* Chip Core ID values. */
+#define INF_CORE_CHIPCOMMON 0x800
+#define INF_CORE_INTERNAL_MEM 0x80E
+#define INF_CORE_80211 0x812
+#define INF_CORE_PMU 0x827
+#define INF_CORE_SDIO_DEV 0x829
+#define INF_CORE_ARM_CM3 0x82A
+#define INF_CORE_ARM_CR4 0x83E
+#define INF_CORE_GCI 0x840
+#define INF_CORE_SR 0x841
+#define INF_CORE_SYS_MEM 0x849
+#define INF_CORE_DEFAULT 0xFFF
+/* Erom enumeration is not applied & only one core in ACW74XX.
+ * Give default id to conform inff_core mechanism
+ */
+#define INF_CORE_ACW74XX 0xFFF
+
+#define INF_MAX_NR_CORES 16
+#define INF_CORE_SIZE 0x1000
+
+#define INFF_CPUSS_CMD_WLAN_ON 1
+#define INFF_CPUSS_CMD_WLAN_OFF 2
+#define INFF_CPUSS_CMD_DONGLE_CLEAN_UP 3
+
+/* CHIPCOMMON CORE */
+struct inff_chipcregs {
+ u32 chipid; /* 0x0 */
+ u32 capabilities;
+ u32 corecontrol; /* corerev >= 1 */
+ u32 bist;
+
+ /* OTP */
+ u32 otpstatus; /* 0x10, corerev >= 10 */
+ u32 otpcontrol;
+ u32 otpprog;
+ u32 otplayout; /* corerev >= 23 */
+
+ /* Interrupt control */
+ u32 intstatus; /* 0x20 */
+ u32 intmask;
+
+ /* Chip specific regs */
+ u32 chipcontrol; /* 0x28, rev >= 11 */
+ u32 chipstatus; /* 0x2c, rev >= 11 */
+
+ /* Jtag Master */
+ u32 jtagcmd; /* 0x30, rev >= 10 */
+ u32 jtagir;
+ u32 jtagdr;
+ u32 jtagctrl;
+
+ /* serial flash interface registers */
+ u32 flashcontrol; /* 0x40 */
+ u32 flashaddress;
+ u32 flashdata;
+ u32 pad0[1];
+
+ /* Silicon backplane configuration broadcast control */
+ u32 broadcastaddress; /* 0x50 */
+ u32 broadcastdata;
+
+ /* gpio - cleared only by power-on-reset */
+ u32 gpiopullup; /* 0x58, corerev >= 20 */
+ u32 gpiopulldown; /* 0x5c, corerev >= 20 */
+ u32 gpioin; /* 0x60 */
+ u32 gpioout; /* 0x64 */
+ u32 gpioouten; /* 0x68 */
+ u32 gpiocontrol; /* 0x6C */
+ u32 gpiointpolarity; /* 0x70 */
+ u32 gpiointmask; /* 0x74 */
+
+ /* GPIO events corerev >= 11 */
+ u32 gpioevent;
+ u32 gpioeventintmask;
+
+ /* Watchdog timer */
+ u32 watchdog; /* 0x80 */
+
+ /* GPIO events corerev >= 11 */
+ u32 gpioeventintpolarity;
+
+ /* GPIO based LED powersave registers corerev >= 16 */
+ u32 gpiotimerval; /* 0x88 */
+ u32 gpiotimeroutmask;
+
+ /* clock control */
+ u32 clockcontrol_n; /* 0x90 */
+ u32 clockcontrol_sb; /* aka m0 */
+ u32 clockcontrol_pci; /* aka m1 */
+ u32 clockcontrol_m2; /* mii/uart/mipsref */
+ u32 clockcontrol_m3; /* cpu */
+ u32 clkdiv; /* corerev >= 3 */
+ u32 gpiodebugsel; /* corerev >= 28 */
+ u32 capabilities_ext; /* 0xac */
+
+ /* pll delay registers (corerev >= 4) */
+ u32 pll_on_delay; /* 0xb0 */
+ u32 fref_sel_delay;
+ u32 slow_clk_ctl; /* 5 < corerev < 10 */
+ u32 pad1;
+
+ /* Instaclock registers (corerev >= 10) */
+ u32 system_clk_ctl; /* 0xc0 */
+ u32 clkstatestretch;
+ u32 pad2[2];
+
+ /* Indirect backplane access (corerev >= 22) */
+ u32 bp_addrlow; /* 0xd0 */
+ u32 bp_addrhigh;
+ u32 bp_data;
+ u32 pad3;
+ u32 bp_indaccess;
+ u32 pad4[3];
+
+ /* More clock dividers (corerev >= 32) */
+ u32 clkdiv2;
+ u32 pad5[2];
+
+ /* In AI chips, pointer to erom */
+ u32 eromptr; /* 0xfc */
+
+ /* ExtBus control registers (corerev >= 3) */
+ u32 pcmcia_config; /* 0x100 */
+ u32 pcmcia_memwait;
+ u32 pcmcia_attrwait;
+ u32 pcmcia_iowait;
+ u32 ide_config;
+ u32 ide_memwait;
+ u32 ide_attrwait;
+ u32 ide_iowait;
+ u32 prog_config;
+ u32 prog_waitcount;
+ u32 flash_config;
+ u32 flash_waitcount;
+ u32 SECI_config; /* 0x130 SECI configuration */
+ u32 pad6[3];
+
+ /* Enhanced Coexistence Interface (ECI) registers (corerev >= 21) */
+ u32 eci_output; /* 0x140 */
+ u32 eci_control;
+ u32 eci_inputlo;
+ u32 eci_inputmi;
+ u32 eci_inputhi;
+ u32 eci_inputintpolaritylo;
+ u32 eci_inputintpolaritymi;
+ u32 eci_inputintpolarityhi;
+ u32 eci_intmasklo;
+ u32 eci_intmaskmi;
+ u32 eci_intmaskhi;
+ u32 eci_eventlo;
+ u32 eci_eventmi;
+ u32 eci_eventhi;
+ u32 eci_eventmasklo;
+ u32 eci_eventmaskmi;
+ u32 eci_eventmaskhi;
+ u32 pad7[3];
+
+ /* SROM interface (corerev >= 32) */
+ u32 sromcontrol; /* 0x190 */
+ u32 sromaddress;
+ u32 sromdata;
+ u32 pad8[17];
+
+ /* Clock control and hardware workarounds (corerev >= 20) */
+ u32 clk_ctl_st; /* 0x1e0 */
+ u32 hw_war;
+ u32 pad9[70];
+
+ /* UARTs */
+ u8 uart0data; /* 0x300 */
+ u8 uart0imr;
+ u8 uart0fcr;
+ u8 uart0lcr;
+ u8 uart0mcr;
+ u8 uart0lsr;
+ u8 uart0msr;
+ u8 uart0scratch;
+ u8 pad10[248]; /* corerev >= 1 */
+
+ u8 uart1data; /* 0x400 */
+ u8 uart1imr;
+ u8 uart1fcr;
+ u8 uart1lcr;
+ u8 uart1mcr;
+ u8 uart1lsr;
+ u8 uart1msr;
+ u8 uart1scratch;
+ u32 pad11[62];
+
+ /* save/restore, corerev >= 48 */
+ u32 sr_capability; /* 0x500 */
+ u32 sr_control0; /* 0x504 */
+ u32 sr_control1; /* 0x508 */
+ u32 gpio_control; /* 0x50C */
+ u32 pad12[60];
+
+ /* PMU registers (corerev >= 20) */
+ u32 pmucontrol; /* 0x600 */
+ u32 pmucapabilities;
+ u32 pmustatus;
+ u32 res_state;
+ u32 res_pending;
+ u32 pmutimer;
+ u32 min_res_mask;
+ u32 max_res_mask;
+ u32 res_table_sel;
+ u32 res_dep_mask;
+ u32 res_updn_timer;
+ u32 res_timer;
+ u32 clkstretch;
+ u32 pmuwatchdog;
+ u32 gpiosel; /* 0x638, rev >= 1 */
+ u32 gpioenable; /* 0x63c, rev >= 1 */
+ u32 res_req_timer_sel;
+ u32 res_req_timer;
+ u32 res_req_mask;
+ u32 pmucapabilities_ext; /* 0x64c, pmurev >=15 */
+ u32 chipcontrol_addr; /* 0x650 */
+ u32 chipcontrol_data; /* 0x654 */
+ u32 regcontrol_addr;
+ u32 regcontrol_data;
+ u32 pllcontrol_addr;
+ u32 pllcontrol_data;
+ u32 pmustrapopt; /* 0x668, corerev >= 28 */
+ u32 pmu_xtalfreq; /* 0x66C, pmurev >= 10 */
+ u32 retention_ctl; /* 0x670, pmurev >= 15 */
+ u32 pad13[3];
+ u32 retention_grpidx; /* 0x680 */
+ u32 retention_grpctl; /* 0x684 */
+ u32 mac_res_req_timer; /* 0x688 */
+ u32 mac_res_req_mask; /* 0x68c */
+ u32 pad14[18];
+ u32 pmucontrol_ext; /* 0x6d8 */
+ u32 slowclkperiod; /* 0x6dc */
+ u32 pad15[8];
+ u32 pmuintmask0; /* 0x700 */
+ u32 pmuintmask1; /* 0x704 */
+ u32 pad16[14];
+ u32 pmuintstatus; /* 0x740 */
+ u32 extwakeupstatus; /* 0x744 */
+ u32 watchdog_res_mask; /* 0x748 */
+ u32 swscratch; /* 0x750 */
+ u32 pad17[3];
+ u32 extwakemask[2]; /* 0x760-0x764 */
+ u32 pad18[2];
+ u32 extwakereqmask[2]; /* 0x770-0x774 */
+ u32 pad19[2];
+ u32 pmuintctrl0; /* 0x780 */
+ u32 pmuintctrl1; /* 0x784 */
+ u32 pad20[2];
+ u32 extwakectrl[2]; /* 0x790 */
+};
+
+#define CHIPCREGOFFS(field) offsetof(struct inff_chipcregs, field)
+#define CORE_CC_REG(base, field) \
+ ((base) + CHIPCREGOFFS(field))
+
+#define INF_CC_CYW5591X_CHIP_ID 0xDA5C
+#define INF_CC_ACW74XX_CHIP_ID 0x1122
+
+/* GCI CORE */
+struct inff_chipgciregs {
+ u32 gci_corecaps0; /* 0x000 */
+ u32 gci_corecaps1; /* 0x004 */
+ u32 gci_corecaps2; /* 0x008 */
+ u32 gci_corectrl; /* 0x00c */
+ u32 gci_corestat; /* 0x010 */
+ u32 gci_intstat; /* 0x014 */
+ u32 gci_intmask; /* 0x018 */
+ u32 gci_wakemask; /* 0x01c */
+ u32 gci_levelintstat; /* 0x020 */
+ u32 gci_eventintstat; /* 0x024 */
+ u32 gci_wakelevelintstat; /* 0x028 */
+ u32 gci_wakeeventintstat; /* 0x02c */
+ u32 semaphoreintstatus; /* 0x030 */
+ u32 semaphoreintmask; /* 0x034 */
+ u32 semaphorerequest; /* 0x038 */
+ u32 semaphorereserve; /* 0x03c */
+ u32 gci_indirect_addr; /* 0x040 */
+ u32 gci_gpioctl; /* 0x044 */
+ u32 gci_gpiostatus; /* 0x048 */
+ u32 gci_gpiomask; /* 0x04c */
+ u32 eventsummary; /* 0x050 */
+ u32 gci_miscctl; /* 0x054 */
+ u32 gci_gpiointmask; /* 0x058 */
+ u32 gci_gpiowakemask; /* 0x05c */
+ u32 gci_input[32]; /* 0x060 */
+ u32 gci_event[32]; /* 0x0e0 */
+ u32 gci_output[4]; /* 0x160 */
+ u32 gci_control_0; /* 0x170 */
+ u32 gci_control_1; /* 0x174 */
+ u32 gci_intpolreg; /* 0x178 */
+ u32 gci_levelintmask; /* 0x17c */
+ u32 gci_eventintmask; /* 0x180 */
+ u32 wakelevelintmask; /* 0x184 */
+ u32 wakeeventintmask; /* 0x188 */
+ u32 hwmask; /* 0x18c */
+ u32 pad0;
+ u32 gci_inbandeventintmask; /* 0x194 */
+ u32 pad1;
+ u32 gci_inbandeventstatus; /* 0x19c */
+ u32 gci_seciauxtx; /* 0x1a0 */
+ u32 gci_seciauxrx; /* 0x1a4 */
+ u32 gci_secitx_datatag; /* 0x1a8 */
+ u32 gci_secirx_datatag; /* 0x1ac */
+ u32 gci_secitx_datamask; /* 0x1b0 */
+ u32 gci_seciusef0tx_reg; /* 0x1b4 */
+ u32 gci_secif0tx_offset; /* 0x1b8 */
+ u32 gci_secif0rx_offset; /* 0x1bc */
+ u32 gci_secif1tx_offset; /* 0x1c0 */
+ u32 gci_rxfifo_common_ctrl; /* 0x1c4 */
+ u32 gci_rxfifoctrl; /* 0x1c8 */
+ u32 gci_hw_sema_status; /* 0x1cc */
+ u32 gci_seciuartescval; /* 0x1d0 */
+ u32 gic_seciuartautobaudctr; /* 0x1d4 */
+ u32 gci_secififolevel; /* 0x1d8 */
+ u32 gci_seciuartdata; /* 0x1dc */
+ u32 gci_secibauddiv; /* 0x1e0 */
+ u32 gci_secifcr; /* 0x1e4 */
+ u32 gci_secilcr; /* 0x1e8 */
+ u32 gci_secimcr; /* 0x1ec */
+ u32 gci_secilsr; /* 0x1f0 */
+ u32 gci_secimsr; /* 0x1f4 */
+ u32 gci_baudadj; /* 0x1f8 */
+ u32 gci_inbandintmask; /* 0x1fc */
+ u32 gci_chipctrl; /* 0x200 */
+ u32 gci_chipsts; /* 0x204 */
+ u32 gci_gpioout; /* 0x208 */
+ u32 gci_gpioout_read; /* 0x20C */
+ u32 gci_mpwaketx; /* 0x210 */
+ u32 gci_mpwakedetect; /* 0x214 */
+ u32 gci_seciin_ctrl; /* 0x218 */
+ u32 gci_seciout_ctrl; /* 0x21C */
+ u32 gci_seciin_auxfifo_en; /* 0x220 */
+ u32 gci_seciout_txen_txbr; /* 0x224 */
+ u32 gci_seciin_rxbrstatus; /* 0x228 */
+ u32 gci_seciin_rxerrstatus; /* 0x22C */
+ u32 gci_seciin_fcstatus; /* 0x230 */
+ u32 gci_seciout_txstatus; /* 0x234 */
+ u32 gci_seciout_txbrstatus; /* 0x238 */
+ u32 wlan_mem_info; /* 0x23C */
+ u32 wlan_bankxinfo; /* 0x240 */
+ u32 bt_smem_select; /* 0x244 */
+ u32 bt_smem_stby; /* 0x248 */
+ u32 bt_smem_status; /* 0x24C */
+ u32 wlan_bankxactivepda; /* 0x250 */
+ u32 wlan_bankxsleeppda; /* 0x254 */
+ u32 wlan_bankxkill; /* 0x258 */
+ u32 pad2[41];
+ u32 gci_chipid; /* 0x300 */
+ u32 pad3[3];
+ u32 otpstatus; /* 0x310 */
+ u32 otpcontrol; /* 0x314 */
+ u32 otpprog; /* 0x318 */
+ u32 otplayout; /* 0x31c */
+ u32 otplayoutextension; /* 0x320 */
+ u32 otpcontrol1; /* 0x324 */
+ u32 otpprogdata; /* 0x328 */
+ u32 pad4[52];
+ u32 otp_ecc_status; /* 0x3FC */
+ u32 pad5[512];
+ u32 lhl_core_capab_adr; /* 0xC00 */
+ u32 lhl_main_ctl_adr; /* 0xC04 */
+ u32 lhl_pmu_ctl_adr; /* 0xC08 */
+ u32 lhl_extlpo_ctl_adr; /* 0xC0C */
+ u32 lpo_ctl_adr; /* 0xC10 */
+ u32 lhl_lpo2_ctl_adr; /* 0xC14 */
+ u32 lhl_osc32k_ctl_adr; /* 0xC18 */
+ u32 lhl_clk_status_adr; /* 0xC1C */
+ u32 lhl_clk_det_ctl_adr; /* 0xC20 */
+ u32 lhl_clk_sel_adr; /* 0xC24 */
+ u32 hidoff_cnt_adr[2]; /* 0xC28-0xC2C */
+ u32 lhl_autoclk_ctl_adr; /* 0xC30 */
+ u32 pad6;
+ u32 lhl_hibtim_adr; /* 0xC38 */
+ u32 lhl_wl_ilp_val_adr; /* 0xC3C */
+ u32 lhl_wl_armtim0_intrp_adr; /* 0xC40 */
+ u32 lhl_wl_armtim0_st_adr; /* 0xC44 */
+ u32 lhl_wl_armtim0_adr; /* 0xC48 */
+ u32 pad7[9];
+ u32 lhl_wl_mactim0_intrp_adr; /* 0xC70 */
+ u32 lhl_wl_mactim0_st_adr; /* 0xC74 */
+ u32 lhl_wl_mactim_int0_adr; /* 0xC78 */
+ u32 lhl_wl_mactim_frac0_adr; /* 0xC7C */
+ u32 lhl_wl_mactim1_intrp_adr; /* 0xC80 */
+ u32 lhl_wl_mactim1_st_adr; /* 0xC84 */
+ u32 lhl_wl_mactim_int1_adr; /* 0xC88 */
+ u32 lhl_wl_mactim_frac1_adr; /* 0xC8C */
+ u32 pad8[8];
+ u32 gpio_int_en_port_adr[4]; /* 0xCB0-0xCBC */
+ u32 gpio_int_st_port_adr[4]; /* 0xCC0-0xCCC */
+ u32 gpio_ctrl_iocfg_p_adr[64]; /* 0xCD0-0xDCC */
+ u32 gpio_gctrl_iocfg_p0_p39_adr; /* 0xDD0 */
+ u32 gpio_gdsctrl_iocfg_p0_p25_p30_p39_adr; /* 0xDD4 */
+ u32 gpio_gdsctrl_iocfg_p26_p29_adr; /* 0xDD8 */
+ u32 pad9[8];
+ u32 lhl_gpio_din0_adr; /* 0xDFC */
+ u32 lhl_gpio_din1_adr; /* 0xE00 */
+ u32 lhl_wkup_status_adr; /* 0xE04 */
+ u32 lhl_ctl_adr; /* 0xE08 */
+ u32 lhl_adc_ctl_adr; /* 0xE0C */
+ u32 lhl_qdxyz_in_dly_adr; /* 0xE10 */
+ u32 lhl_optctl_adr; /* 0xE14 */
+ u32 lhl_optct2_adr; /* 0xE18 */
+ u32 lhl_scanp_cntr_init_val_adr; /* 0xE1C */
+ u32 lhl_opt_togg_val_adr[6]; /* 0xE20-0xE34 */
+ u32 lhl_optx_smp_val_adr; /* 0xE38 */
+ u32 lhl_opty_smp_val_adr; /* 0xE3C */
+ u32 lhl_optz_smp_val_adr; /* 0xE40 */
+ u32 lhl_hidoff_keepstate_adr[3]; /* 0xE44-0xE4C */
+ u32 lhl_bt_slmboot_ctl0_adr[4]; /* 0xE50-0xE5C */
+ u32 lhl_wl_fw_ctl; /* 0xE60 */
+ u32 lhl_wl_hw_ctl_adr[2]; /* 0xE64-0xE68 */
+ u32 lhl_bt_hw_ctl_adr; /* 0xE6C */
+ u32 lhl_top_pwrseq_en_adr; /* 0xE70 */
+ u32 lhl_top_pwrdn_ctl_adr; /* 0xE74 */
+ u32 lhl_top_pwrup_ctl_adr; /* 0xE78 */
+ u32 lhl_top_pwrseq_ctl_adr; /* 0xE7C */
+ u32 lhl_top_pwrdn2_ctl_adr; /* 0xE80 */
+ u32 lhl_top_pwrup2_ctl_adr; /* 0xE84 */
+ u32 wpt_regon_intrp_cfg_adr; /* 0xE88 */
+ u32 bt_regon_intrp_cfg_adr; /* 0xE8C */
+ u32 wl_regon_intrp_cfg_adr; /* 0xE90 */
+ u32 regon_intrp_st_adr; /* 0xE94 */
+ u32 regon_intrp_en_adr; /* 0xE98 */
+
+};
+
+#define CHIPGCIREGOFFS(field) offsetof(struct inff_chipgciregs, field)
+#define CORE_GCI_REG(base, field) \
+ ((base) + CHIPGCIREGOFFS(field))
+
+#define INFF_CHIP_COMMON_HW_CAPS_REPLAYCNTS 16
+
+/* SOC Interconnect types (aka chip types) */
+#define INFF_SOCI_UNSPEC 0
+#define INFF_SOCI_AI 1
+#define INFF_SOCI_CP 5
+
+/**
+ * struct inff_blhs - bootloader handshake handle related information.
+ *
+ * @d2h: offset of dongle to host register for the handshake.
+ * @h2d: offset of host to dongle register for the handshake.
+ * @init: bootloader handshake initialization.
+ * @prep_fwdl: handshake before firmware download.
+ * @post_fwdl: handshake after firmware download.
+ * @post_nvramdl: handshake after nvram download.
+ * @chk_validation: handshake for firmware validation check.
+ * @post_wdreset: handshake after watchdog reset.
+ * @read: read value with register offset for the handshake.
+ * @write: write value with register offset for the handshake.
+ */
+struct inff_blhs {
+ u32 d2h;
+ u32 d2h_2;
+ u32 h2d;
+ void (*init)(struct inff_chip *pub);
+ int (*pre_nvramdl)(struct inff_chip *pub);
+ int (*prep_fwdl)(struct inff_chip *pub);
+ int (*post_fwdl)(struct inff_chip *pub);
+ int (*post_nvramdl)(struct inff_chip *pub);
+ int (*chk_validation)(struct inff_chip *pub);
+ int (*post_wdreset)(struct inff_chip *pub);
+ u32 (*read)(void *ctx, u32 addr);
+ void (*write)(void *ctx, u32 addr, u32 value);
+ u32 (*readl)(void *ctx, u32 addr);
+ void (*send_cpuss_cmd)(void *ctx, u8 cmd);
+};
+
+struct inff_ccsec {
+ u32 bus_corebase;
+ u32 erombase;
+ u32 chipid;
+};
+
+enum inff_fw_file_types {
+ INFF_FW_CODE = 0,
+ INFF_FW_NVRAM,
+ INFF_FW_CLM,
+ INFF_FW_BIN_MAX_TYPE,
+};
+
+enum inff_cp_fw_file_types {
+ INFF_FW_CP = 0,
+ INFF_FW_BOOT,
+ INFF_FW_CP_BIN_MAX_TYPE,
+};
+
+struct inff_fw_dataset {
+ enum inff_fw_type type;
+ struct inff_fw_name fwnames;
+};
+
+struct inff_chip_specific {
+ u32 hw_caps_replaycnts;
+ u32 hw_reg_pmu_status_msk;
+ u32 hw_reg_pmu_ctrl_ext_msk;
+ u32 hw_chip_ramsize;
+ u32 hw_chip_rambase;
+ u32 hw_chip_cpuss_ramsize;
+ u32 hw_chip_cpuss_rambase;
+ u32 hw_chip_tcam_size;
+ u32 hw_chip_trxhdr_size;
+
+ struct inff_fw_dataset fwdata[INFF_FW_BIN_MAX_TYPE];
+ char fw_name[INFF_FW_NAME_LEN];
+ char boot_fw_name[INFF_FW_NAME_LEN];
+ char nvram_name[INFF_FW_NAME_LEN];
+ char clm_name[INFF_FW_NAME_LEN];
+ const struct firmware *clm_fw;
+};
+
+/**
+ * struct inff_chip - chip level information.
+ *
+ * @chip: chip identifier.
+ * @chiprev: chip revision.
+ * @enum_base: base address of core enumeration space.
+ * @cc_caps: chipcommon core capabilities.
+ * @cc_caps_ext: chipcommon core extended capabilities.
+ * @pmucaps: PMU capabilities.
+ * @pmurev: PMU revision.
+ * @rambase: RAM base address (only applicable for ARM CR4 chips).
+ * @ramsize: amount of RAM on chip including retention.
+ * @cpuss_rambase: base address of cpuss.
+ * @cpuss_ramsize: memory size of cpuss.
+ * @srsize: amount of retention RAM on chip.
+ * @socitype: SOC interconnect type.
+ * @name: string representation of the chip identifier.
+ * @blhs: bootlooder handshake handle.
+ */
+struct inff_chip {
+ u32 chip;
+ u32 chiprev;
+ u32 enum_base;
+ u32 cc_caps;
+ u32 cc_caps_ext;
+ u32 pmucaps;
+ u32 pmurev;
+ u32 rambase;
+ u32 ramsize;
+ u32 cpuss_rambase;
+ u32 cpuss_ramsize;
+ u32 srsize;
+ u32 socitype;
+ char name[12];
+ struct inff_blhs *blhs;
+ struct inff_ccsec *ccsec;
+ struct inff_chip_specific chip_spec;
+};
+
+/**
+ * struct inff_core - core related information.
+ *
+ * @id: core identifier.
+ * @rev: core revision.
+ * @base: base address of core register space.
+ */
+struct inff_core {
+ u16 id;
+ u16 rev;
+ u32 base;
+};
+
+struct inff_core_priv {
+ struct inff_core pub;
+ u32 wrapbase;
+ struct list_head list;
+ struct inff_chip_priv *chip;
+};
+
+struct inff_chip_priv {
+ struct inff_chip pub;
+ const struct inff_buscore_ops *ops;
+ void *ctx;
+ /* assured first core is chipcommon, second core is buscore */
+ struct list_head cores;
+ u16 num_cores;
+
+ bool (*iscoreup)(struct inff_core_priv *core);
+ void (*coredisable)(struct inff_core_priv *core, u32 prereset,
+ u32 reset);
+ void (*resetcore)(struct inff_core_priv *core, u32 prereset, u32 reset,
+ u32 postreset);
+};
+
+/**
+ * struct inff_buscore_ops - buscore specific callbacks.
+ *
+ * @read32: read 32-bit value over bus.
+ * @write32: write 32-bit value over bus.
+ * @prepare: prepare bus for core configuration.
+ * @setup: bus-specific core setup.
+ * @active: chip becomes active.
+ * The callback should use the provided @rstvec when non-zero.
+ * @blhs_attach: attach bootloader handshake handle
+ */
+struct inff_buscore_ops {
+ u32 (*read32)(void *ctx, u32 addr);
+ void (*write32)(void *ctx, u32 addr, u32 value);
+ int (*prepare)(void *ctx);
+ int (*reset)(void *ctx, struct inff_chip *chip);
+ int (*setup)(void *ctx, struct inff_chip *chip);
+ void (*activate)(void *ctx, struct inff_chip *chip, u32 rstvec);
+ int (*sec_attach)(void *ctx, struct inff_blhs **blhs, struct inff_ccsec **ccsec,
+ u32 flag, uint timeout, uint interval);
+ int (*get_intr_pend)(void *ctx);
+};
+
+int inff_chip_get_raminfo(struct inff_chip *pub);
+struct inff_chip *inff_chip_attach(void *ctx, u16 devid,
+ const struct inff_buscore_ops *ops);
+void inff_chip_detach(struct inff_chip *chip);
+struct inff_core *inff_chip_get_core(struct inff_chip *chip, u16 coreid);
+struct inff_core *inff_chip_get_d11core(struct inff_chip *pub, u8 unit);
+struct inff_core *inff_chip_get_chipcommon(struct inff_chip *chip);
+struct inff_core *inff_chip_get_pmu(struct inff_chip *pub);
+bool inff_chip_iscoreup(struct inff_core *core);
+void inff_chip_coredisable(struct inff_core *core, u32 prereset, u32 reset);
+void inff_chip_resetcore(struct inff_core *core, u32 prereset, u32 reset,
+ u32 postreset);
+void inff_chip_set_passive(struct inff_chip *ci);
+bool inff_chip_sr_capable(struct inff_chip *pub);
+char *inff_chip_name(u32 chipid, u32 chiprev, char *buf, uint len);
+u32 inff_chip_enum_base(u16 devid);
+u32 inff_chip_get_hw_caps_replaycnts(struct inff_chip *pub);
+#endif /* INFF_CHIP_H */
--
2.43.0
^ permalink raw reply related [flat|nested] 26+ messages in thread* [PATCH wireless-next v3 06/25] wifi: inffmac: add chip_{cyw5591x/acw74xx}.c/h
2026-09-02 16:50 [PATCH wireless-next v3 00/25] wifi: inffmac: introducing a driver for Infineon's new generation chipsets Gokul Sivakumar
` (4 preceding siblings ...)
2026-09-02 16:50 ` [PATCH wireless-next v3 05/25] wifi: inffmac: add chip.c/h Gokul Sivakumar
@ 2026-09-02 16:50 ` Gokul Sivakumar
2026-09-02 16:50 ` [PATCH wireless-next v3 07/25] wifi: inffmac: add icdc.c/h Gokul Sivakumar
` (18 subsequent siblings)
24 siblings, 0 replies; 26+ messages in thread
From: Gokul Sivakumar @ 2026-09-02 16:50 UTC (permalink / raw)
To: linux-wireless, johannes
Cc: arend.vanspriel, wlan-kernel-dev-list, gokulkumar.sivakumar
Chip family specific initialization and definitions for Infineon's new
generation secure chipsets CYW5591x and ACW74xx.
Signed-off-by: Gokul Sivakumar <gokulkumar.sivakumar@infineon.com>
---
.../wireless/infineon/inffmac/chip_acw74xx.c | 35 ++++++++++++++++++
.../wireless/infineon/inffmac/chip_acw74xx.h | 19 ++++++++++
.../wireless/infineon/inffmac/chip_cyw5591x.c | 33 +++++++++++++++++
.../wireless/infineon/inffmac/chip_cyw5591x.h | 37 +++++++++++++++++++
4 files changed, 124 insertions(+)
create mode 100644 drivers/net/wireless/infineon/inffmac/chip_acw74xx.c
create mode 100644 drivers/net/wireless/infineon/inffmac/chip_acw74xx.h
create mode 100644 drivers/net/wireless/infineon/inffmac/chip_cyw5591x.c
create mode 100644 drivers/net/wireless/infineon/inffmac/chip_cyw5591x.h
diff --git a/drivers/net/wireless/infineon/inffmac/chip_acw74xx.c b/drivers/net/wireless/infineon/inffmac/chip_acw74xx.c
new file mode 100644
index 000000000000..fcd64481616d
--- /dev/null
+++ b/drivers/net/wireless/infineon/inffmac/chip_acw74xx.c
@@ -0,0 +1,35 @@
+// SPDX-License-Identifier: ISC
+/*
+ * Copyright (c) 2024-2026, Infineon Technologies AG, or an affiliate of Infineon Technologies AG.
+ * All rights reserved.
+ */
+
+#include "chip_acw74xx.h"
+
+#define ACW74XX_CR4_RAM_BASE 0x5e3b8000
+#define ACW74XX_CR4_RAM_SIZE 0x88000 /* 544 KBytes - TCM-A+TCM-B */
+#define ACW74XX_CM33_RAM_BASE 0x34002000
+#define ACW74XX_CM33_RAM_SIZE 0x25000 /* 148 KBytes */
+
+void inff_chip_acw74xx_init(struct inff_chip *chip)
+{
+ struct inff_chip_specific *chip_spec = &chip->chip_spec;
+ struct inff_fw_dataset *fw_data = &chip_spec->fwdata[0];
+
+ chip_spec->hw_chip_rambase = ACW74XX_CR4_RAM_BASE;
+ chip_spec->hw_chip_ramsize = ACW74XX_CR4_RAM_SIZE;
+ chip_spec->hw_chip_cpuss_rambase = ACW74XX_CM33_RAM_BASE;
+ chip_spec->hw_chip_cpuss_ramsize = ACW74XX_CM33_RAM_SIZE;
+
+ fw_data[INFF_FW_CODE].fwnames.extension = ".trxse";
+ fw_data[INFF_FW_CODE].fwnames.path = chip_spec->fw_name;
+ fw_data[INFF_FW_CODE].type = INFF_FW_TYPE_TRXSE;
+
+ fw_data[INFF_FW_NVRAM].fwnames.extension = ".txt";
+ fw_data[INFF_FW_NVRAM].fwnames.path = chip_spec->nvram_name;
+ fw_data[INFF_FW_NVRAM].type = INFF_FW_TYPE_NVRAM;
+
+ fw_data[INFF_FW_CLM].fwnames.extension = ".clm_blob";
+ fw_data[INFF_FW_CLM].fwnames.path = chip_spec->clm_name;
+ fw_data[INFF_FW_CLM].type = INFF_FW_TYPE_CLM;
+}
diff --git a/drivers/net/wireless/infineon/inffmac/chip_acw74xx.h b/drivers/net/wireless/infineon/inffmac/chip_acw74xx.h
new file mode 100644
index 000000000000..e3cdcaa968c6
--- /dev/null
+++ b/drivers/net/wireless/infineon/inffmac/chip_acw74xx.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: ISC */
+/*
+ * Copyright (c) 2024-2026, Infineon Technologies AG, or an affiliate of Infineon Technologies AG.
+ * All rights reserved.
+ */
+
+#ifndef INFF_CHIP_ACW74XX_H
+#define INFF_CHIP_ACW74XX_H
+
+#include "chip.h"
+
+/* INFACW74XX watermark expressed in number of words */
+#define INFACW74XX_F2_WATERMARK 0x40
+#define INFACW74XX_MES_WATERMARK 0x40
+#define INFACW74XX_F1_MESBUSYCTRL (INFACW74XX_MES_WATERMARK | \
+ SBSDIO_MESBUSYCTRL_ENAB)
+
+void inff_chip_acw74xx_init(struct inff_chip *chip);
+#endif /* INFF_CHIP_ACW74XX_H */
diff --git a/drivers/net/wireless/infineon/inffmac/chip_cyw5591x.c b/drivers/net/wireless/infineon/inffmac/chip_cyw5591x.c
new file mode 100644
index 000000000000..754996cbab4a
--- /dev/null
+++ b/drivers/net/wireless/infineon/inffmac/chip_cyw5591x.c
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: ISC
+/*
+ * Copyright (c) 2024-2026, Infineon Technologies AG, or an affiliate of Infineon Technologies AG.
+ * All rights reserved.
+ */
+
+#include "chip_cyw5591x.h"
+
+void inff_chip_cyw5591x_init(struct inff_chip *chip)
+{
+ struct inff_chip_specific *chip_spec = &chip->chip_spec;
+ struct inff_fw_dataset *fw_data = &chip_spec->fwdata[0];
+
+ chip_spec->hw_caps_replaycnts = INFF_CHIP_COMMON_HW_CAPS_REPLAYCNTS;
+
+ if (chip->chiprev == 0) {
+ chip_spec->hw_chip_tcam_size = INF55900_TCAM_SIZE;
+ chip_spec->hw_chip_trxhdr_size = INF55900_TRXHDR_SIZE;
+ chip_spec->hw_chip_rambase = INF55900_RAM_BASE;
+ } else {
+ chip_spec->hw_chip_tcam_size = INF55900_A1_TCAM_SIZE;
+ chip_spec->hw_chip_trxhdr_size = INF55900_A1_TRXHDR_SIZE;
+ chip_spec->hw_chip_rambase = INF55900_A1_RAM_BASE;
+ }
+
+ fw_data[INFF_FW_CP].fwnames.extension = ".hex";
+ fw_data[INFF_FW_CP].fwnames.path = chip_spec->fw_name;
+ fw_data[INFF_FW_CP].type = INFF_FW_TYPE_HEX;
+
+ fw_data[INFF_FW_BOOT].fwnames.extension = ".hex";
+ fw_data[INFF_FW_BOOT].fwnames.path = chip_spec->boot_fw_name;
+ fw_data[INFF_FW_BOOT].type = INFF_FW_TYPE_HEX;
+}
diff --git a/drivers/net/wireless/infineon/inffmac/chip_cyw5591x.h b/drivers/net/wireless/infineon/inffmac/chip_cyw5591x.h
new file mode 100644
index 000000000000..3c0810f3e601
--- /dev/null
+++ b/drivers/net/wireless/infineon/inffmac/chip_cyw5591x.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: ISC */
+/*
+ * Copyright (c) 2024-2026, Infineon Technologies AG, or an affiliate of Infineon Technologies AG.
+ * All rights reserved.
+ */
+
+#include "chip.h"
+
+/* 55900-A0, Dedicated sapce for TCAM_PATCH and TRX HDR area at RAMSTART */
+#define INF55900_RAM_START (0x3a0000)
+#define INF55900_TCAM_SIZE (0x800)
+#define INF55900_TRXHDR_SIZE (0x2b4)
+
+#define INF55900_RAM_BASE (INF55900_RAM_START + INF55900_TCAM_SIZE + \
+ INF55900_TRXHDR_SIZE)
+
+/* 55900-A1, Dedicated sapce for TCAM_PATCH and TRX HDR area at RAMSTART */
+#define INF55900_A1_TCAM_SIZE (0x1000)
+#define INF55900_A1_TRXHDR_SIZE (0x20)
+
+#define INF55900_A1_RAM_BASE (INF55900_RAM_START + INF55900_A1_TCAM_SIZE + \
+ INF55900_A1_TRXHDR_SIZE)
+
+/* INF55900 SDIO func2 block size */
+#define SDIO_INF55900_FUNC2_BLOCKSIZE 256
+
+/* INF55900 watermark expressed in number of words */
+#define INF55900_F2_WATERMARK 0x40
+#define INF55900_MES_WATERMARK 0x40
+#define INF55900_F1_MESBUSYCTRL (INF55900_MES_WATERMARK | \
+ SBSDIO_MESBUSYCTRL_ENAB)
+
+#define CYW5591X_BOOTFW_RAM_LD_ADDR 0x80420000
+#define CYW5591X_FWDWNLD_RAM_WR_ADDR 0x80470000
+#define CYW5591X_IMAGE_LAUNCH_ADDR 0xffffffff
+
+void inff_chip_cyw5591x_init(struct inff_chip *chip);
--
2.43.0
^ permalink raw reply related [flat|nested] 26+ messages in thread* [PATCH wireless-next v3 07/25] wifi: inffmac: add icdc.c/h
2026-09-02 16:50 [PATCH wireless-next v3 00/25] wifi: inffmac: introducing a driver for Infineon's new generation chipsets Gokul Sivakumar
` (5 preceding siblings ...)
2026-09-02 16:50 ` [PATCH wireless-next v3 06/25] wifi: inffmac: add chip_{cyw5591x/acw74xx}.c/h Gokul Sivakumar
@ 2026-09-02 16:50 ` Gokul Sivakumar
2026-09-02 16:50 ` [PATCH wireless-next v3 08/25] wifi: inffmac: add main.c/h Gokul Sivakumar
` (17 subsequent siblings)
24 siblings, 0 replies; 26+ messages in thread
From: Gokul Sivakumar @ 2026-09-02 16:50 UTC (permalink / raw)
To: linux-wireless, johannes
Cc: arend.vanspriel, wlan-kernel-dev-list, gokulkumar.sivakumar
Driver implementation of the ICDC protocol used for the Control and Data
communication with Infineon's Chipsets that have the Connectivity processor
(CP) architecture like the CYW5591x sub-family of chipsets, using SDIO.
Signed-off-by: Gokul Sivakumar <gokulkumar.sivakumar@infineon.com>
---
drivers/net/wireless/infineon/inffmac/icdc.c | 346 +++++++++++++++++++
drivers/net/wireless/infineon/inffmac/icdc.h | 29 ++
2 files changed, 375 insertions(+)
create mode 100644 drivers/net/wireless/infineon/inffmac/icdc.c
create mode 100644 drivers/net/wireless/infineon/inffmac/icdc.h
diff --git a/drivers/net/wireless/infineon/inffmac/icdc.c b/drivers/net/wireless/infineon/inffmac/icdc.c
new file mode 100644
index 000000000000..eb4eec880146
--- /dev/null
+++ b/drivers/net/wireless/infineon/inffmac/icdc.c
@@ -0,0 +1,346 @@
+// SPDX-License-Identifier: ISC
+/*
+ * Copyright (c) 2010 Broadcom Corporation
+ *
+ * Copyright (c) 2025-2026, Infineon Technologies AG, or an affiliate of Infineon Technologies AG.
+ * All rights reserved.
+ */
+
+/*******************************************************************************
+ * Communicates with the dongle by using dcmd (CP and FW) codes.
+ * For certain dcmd codes, the dongle interprets string data from the host.
+ ******************************************************************************/
+
+#include <linux/types.h>
+#include <linux/netdevice.h>
+
+#include "main.h"
+#include "chip_cyw5591x.h"
+#include "net.h"
+#include "utils.h"
+#include "fwsignal.h"
+#include "debug.h"
+#include "bus_proto.h"
+#include "icdc.h"
+
+struct inff_proto_icdc_dcmd {
+ __le32 cmd; /* dongle command value */
+ __le32 len; /* lower 16: output buflen;
+ * upper 16: input buflen (excludes header)
+ */
+ __le32 flags; /* flag defns given below */
+ __le32 status; /* status code returned from the device */
+ unsigned char ioc_key[ICDC_GCM_KEY_SIZE_BYTES]; /* key used for ioc enc */
+ unsigned char ioc_iv[ICDC_GCM_IV_SIZE]; /* enc field */
+ unsigned char padding[4]; /* pad to match CP firmware dcmd header layout */
+};
+
+/* ICDC flag definitions */
+#define ICDC_DCMD_ERROR 0x01 /* 1=cmd failed */
+#define ICDC_DCMD_SET 0x02 /* 0=get, 1=set cmd */
+#define ICDC_DCMD_IF_MASK 0xF000 /* I/F index */
+#define ICDC_DCMD_IF_SHIFT 12
+#define ICDC_DCMD_ID_MASK 0xFFFF0000 /* id an cmd pairing */
+#define ICDC_DCMD_ID_SHIFT 16 /* ID Mask shift bits */
+#define ICDC_DCMD_ID(flags) \
+ (((flags) & ICDC_DCMD_ID_MASK) >> ICDC_DCMD_ID_SHIFT)
+
+/*
+ * ICDC header - Infineon specific extension of CDC.
+ * Used on data packets to convey priority.
+ */
+#define ICDC_HEADER_LEN 4
+#define ICDC_HEADER_LEN_RSV 8
+#define ICDC_PROTO_VER 2 /* Protocol version */
+#define ICDC_FLAG_VER_SHIFT 4 /* Protocol version shift */
+
+#define RETRIES 2 /* # of retries to retrieve matching dcmd response */
+#define BUS_HEADER_LEN (16 + 64) /* Must be at least SDPCM_RESERVE
+ * (amount of header tha might be added)
+ * plus any space that might be needed
+ * for bus alignment padding.
+ */
+#define ROUND_UP_MARGIN 2048
+
+struct inff_icdc {
+ u16 reqid;
+ u8 bus_header[BUS_HEADER_LEN];
+ struct inff_proto_icdc_dcmd msg;
+ unsigned char buf[INFF_DCMD_MAXLEN];
+};
+
+static int
+inff_proto_icdc_msg(struct inff_pub *drvr, int ifidx, uint cmd, void *buf,
+ uint len, bool set)
+{
+ struct inff_icdc *icdc = (struct inff_icdc *)drvr->proto->pd;
+ struct inff_proto_icdc_dcmd *msg = &icdc->msg;
+ u32 flags;
+
+ memset(msg, 0, sizeof(struct inff_proto_icdc_dcmd));
+
+ msg->cmd = cpu_to_le32(cmd);
+ msg->len = cpu_to_le32(len);
+ flags = (++icdc->reqid << ICDC_DCMD_ID_SHIFT);
+
+ flags = (ICDC_PROTO_VER << ICDC_FLAG_VER_SHIFT);
+ flags |= (++icdc->reqid << ICDC_DCMD_ID_SHIFT);
+
+ if (set)
+ flags |= ICDC_DCMD_SET;
+ flags = (flags & ~ICDC_DCMD_IF_MASK) |
+ (ifidx << ICDC_DCMD_IF_SHIFT);
+ msg->flags = cpu_to_le32(flags);
+
+ if (buf)
+ memcpy(icdc->buf, buf, len);
+
+ len += sizeof(*msg);
+ if (len > INFF_TX_IOCTL_MAX_MSG_SIZE)
+ len = INFF_TX_IOCTL_MAX_MSG_SIZE;
+
+ /* wait for 50ms when the payload spans more than one SDIO block. */
+ if ((len / SDIO_INF55900_FUNC2_BLOCKSIZE) > 1)
+ msleep(50);
+
+ /* Send request */
+ return inff_bus_txctl(drvr->bus_if, (unsigned char *)&icdc->msg, len);
+}
+
+static int
+inff_proto_icdc_cmplt(struct inff_pub *drvr, u32 id, u32 len)
+{
+ int ret;
+ struct inff_icdc *icdc = (struct inff_icdc *)drvr->proto->pd;
+
+ len += sizeof(struct inff_proto_icdc_dcmd);
+ do {
+ ret = inff_bus_rxctl(drvr->bus_if, (unsigned char *)&icdc->msg,
+ len);
+ if (ret < 0)
+ break;
+ } while (ICDC_DCMD_ID(le32_to_cpu(icdc->msg.flags)) != id);
+
+ return ret;
+}
+
+static int
+inff_proto_icdc_query_dcmd(struct inff_pub *drvr, int ifidx, uint cmd,
+ void *buf, uint len, int *err)
+{
+ struct inff_icdc *icdc = (struct inff_icdc *)drvr->proto->pd;
+ struct inff_proto_icdc_dcmd *msg = &icdc->msg;
+ void *info;
+ int ret = 0, retries = 0;
+ u32 id, flags;
+
+ *err = 0;
+ ret = inff_proto_icdc_msg(drvr, ifidx, cmd, buf, len, false);
+ if (ret < 0) {
+ iphy_err(drvr, "inff_proto_icdc_msg failed w/status %d\n",
+ ret);
+ goto done;
+ }
+
+retry:
+ /* wait for interrupt and get first fragment */
+ ret = inff_proto_icdc_cmplt(drvr, icdc->reqid, len);
+ if (ret < 0)
+ goto done;
+
+ flags = le32_to_cpu(msg->flags);
+ id = (flags & ICDC_DCMD_ID_MASK) >> ICDC_DCMD_ID_SHIFT;
+
+ if (id < icdc->reqid && (++retries < RETRIES))
+ goto retry;
+ if (id != icdc->reqid) {
+ iphy_err(drvr, "%s: unexpected request id %d (expected %d)\n",
+ inff_ifname(inff_get_ifp(drvr, ifidx)), id,
+ icdc->reqid);
+ ret = -EINVAL;
+ goto done;
+ }
+
+ /* Check info buffer */
+ info = (void *)&icdc->buf[0];
+
+ /* Copy info buffer */
+ if (buf) {
+ if (ret < (int)len)
+ len = ret;
+ memcpy(buf, info, len);
+ }
+
+ ret = 0;
+
+ /* Check the ERROR flag */
+ if (flags & ICDC_DCMD_ERROR)
+ *err = le32_to_cpu(msg->status);
+done:
+ return ret;
+}
+
+static int
+inff_proto_icdc_query_cpcmd(struct inff_pub *drvr, int ifidx, uint cmd,
+ void *buf, uint len, int *cperr)
+{
+ inff_dbg(ICDC, "cmd %d len %d\n", cmd, len);
+
+ cmd = (CMD_TYPE_CP << CMD_TYPE_SHIFT) | cmd;
+
+ return inff_proto_icdc_query_dcmd(drvr, ifidx, cmd,
+ buf, len, cperr);
+}
+
+static int
+inff_proto_icdc_query_fwcmd(struct inff_pub *drvr, int ifidx, uint cmd,
+ void *buf, uint len, int *fwerr)
+{
+ inff_dbg(ICDC, "cmd %d len %d\n", cmd, len);
+
+ cmd = (CMD_TYPE_WL << CMD_TYPE_SHIFT) | cmd;
+
+ return inff_proto_icdc_query_dcmd(drvr, ifidx, cmd,
+ buf, len, fwerr);
+}
+
+static int
+inff_proto_icdc_set_dcmd(struct inff_pub *drvr, int ifidx, uint cmd,
+ void *buf, uint len, int *err)
+{
+ struct inff_icdc *icdc = (struct inff_icdc *)drvr->proto->pd;
+ struct inff_proto_icdc_dcmd *msg = &icdc->msg;
+ int ret;
+ u32 flags, id;
+
+ inff_dbg(ICDC, "cmd %d len %d\n", cmd, len);
+
+ *err = 0;
+ ret = inff_proto_icdc_msg(drvr, ifidx, cmd, buf, len, true);
+ if (ret < 0)
+ goto done;
+
+ ret = inff_proto_icdc_cmplt(drvr, icdc->reqid, len);
+ if (ret < 0)
+ goto done;
+
+ flags = le32_to_cpu(msg->flags);
+ id = (flags & ICDC_DCMD_ID_MASK) >> ICDC_DCMD_ID_SHIFT;
+
+ if (id != icdc->reqid) {
+ iphy_err(drvr, "%s: unexpected request id %d (expected %d)\n",
+ inff_ifname(inff_get_ifp(drvr, ifidx)), id,
+ icdc->reqid);
+ ret = -EINVAL;
+ goto done;
+ }
+
+ ret = 0;
+
+ /* Check the ERROR flag */
+ if (flags & ICDC_DCMD_ERROR)
+ *err = le32_to_cpu(msg->status);
+
+done:
+ return ret;
+}
+
+static int
+inff_proto_icdc_set_cpcmd(struct inff_pub *drvr, int ifidx, uint cmd,
+ void *buf, uint len, int *cperr)
+{
+ inff_dbg(ICDC, "cmd %d len %d\n", cmd, len);
+
+ cmd = (CMD_TYPE_CP << CMD_TYPE_SHIFT) | cmd;
+
+ return inff_proto_icdc_set_dcmd(drvr, ifidx, cmd,
+ buf, len, cperr);
+}
+
+static int
+inff_proto_icdc_set_fwcmd(struct inff_pub *drvr, int ifidx, uint cmd,
+ void *buf, uint len, int *fwerr)
+{
+ inff_dbg(ICDC, "cmd %d len %d\n", cmd, len);
+
+ cmd = (CMD_TYPE_WL << CMD_TYPE_SHIFT) | cmd;
+
+ return inff_proto_icdc_set_dcmd(drvr, ifidx, cmd,
+ buf, len, fwerr);
+}
+
+static int
+inff_proto_icdc_tx_queue_data(struct inff_pub *drvr, int ifidx,
+ struct sk_buff *skb)
+{
+ return inff_proto_txdata(drvr, ifidx, 0, skb);
+}
+
+static int
+inff_proto_icdc_txdata(struct inff_pub *drvr, int ifidx, u8 offset,
+ struct sk_buff *pktbuf)
+{
+ return inff_bus_txdata(drvr->bus_if, pktbuf);
+}
+
+static void
+inff_proto_icdc_configure_addr_mode(struct inff_pub *drvr, int ifidx,
+ enum proto_addr_mode addr_mode)
+{
+}
+
+static void
+inff_proto_icdc_delete_peer(struct inff_pub *drvr, int ifidx,
+ u8 peer[ETH_ALEN])
+{
+}
+
+static void
+inff_proto_icdc_debugfs_create(struct inff_pub *drvr)
+{
+}
+
+int
+inff_proto_icdc_attach(struct inff_pub *drvr)
+{
+ struct inff_icdc *icdc;
+
+ icdc = kzalloc_obj(*icdc, GFP_ATOMIC);
+ if (!icdc)
+ goto fail;
+
+ /* ensure that the msg buf directly follows the cdc msg struct */
+ if ((unsigned long)(&icdc->msg + 1) != (unsigned long)icdc->buf) {
+ iphy_err(drvr, "struct inff_proto_icdc is not correctly defined\n");
+ goto fail;
+ }
+
+ drvr->proto->query_cpcmd = inff_proto_icdc_query_cpcmd;
+ drvr->proto->set_cpcmd = inff_proto_icdc_set_cpcmd;
+ drvr->proto->query_fwcmd = inff_proto_icdc_query_fwcmd;
+ drvr->proto->set_fwcmd = inff_proto_icdc_set_fwcmd;
+ drvr->proto->tx_queue_data = inff_proto_icdc_tx_queue_data;
+ drvr->proto->txdata = inff_proto_icdc_txdata;
+ drvr->proto->configure_addr_mode = inff_proto_icdc_configure_addr_mode;
+ drvr->proto->delete_peer = inff_proto_icdc_delete_peer;
+ drvr->proto->debugfs_create = inff_proto_icdc_debugfs_create;
+ drvr->proto->pd = icdc;
+
+ drvr->hdrlen += ICDC_HEADER_LEN + ICDC_HEADER_LEN_RSV;
+ drvr->bus_if->maxctl = INFF_DCMD_MAXLEN +
+ sizeof(struct inff_proto_icdc_dcmd) + ROUND_UP_MARGIN;
+ return 0;
+
+fail:
+ kfree(icdc);
+ return -ENOMEM;
+}
+
+void
+inff_proto_icdc_detach(struct inff_pub *drvr)
+{
+ struct inff_icdc *icdc = drvr->proto->pd;
+
+ drvr->proto->pd = NULL;
+ kfree(icdc);
+}
diff --git a/drivers/net/wireless/infineon/inffmac/icdc.h b/drivers/net/wireless/infineon/inffmac/icdc.h
new file mode 100644
index 000000000000..df870e52a99b
--- /dev/null
+++ b/drivers/net/wireless/infineon/inffmac/icdc.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: ISC */
+/*
+ * Copyright (c) 2013 Broadcom Corporation
+ *
+ * Copyright (c) 2025-2026, Infineon Technologies AG, or an affiliate of Infineon Technologies AG.
+ * All rights reserved.
+ */
+
+#ifndef INFF_ICDC_H
+#define INFF_ICDC_H
+
+#define ICDC_PROTO_ENC (0x01)
+#define ICDC_IOC_ENC_MASK (0x200)
+#define ICDC_IOC_ENC_SHIFT (0x09)
+
+#define ICDC_DAT_ENC_MASK (0x20)
+#define ICDC_DAT_ENC_SHIFT (0x05)
+
+#define ICDC_GCM_KEY_SIZE_BYTES (16)
+#define ICDC_GCM_IV_SIZE (12)
+
+int inff_proto_icdc_attach(struct inff_pub *drvr);
+void inff_proto_icdc_detach(struct inff_pub *drvr);
+void inff_proto_icdc_txflowblock(struct device *dev, bool state);
+void inff_icdc_process_event(struct device *dev, struct sk_buff *skb);
+struct sk_buff *inff_icdc_rx(struct device *dev, struct sk_buff *skb,
+ bool handle_event, bool inirq);
+
+#endif /* INFF_ICDC_H */
--
2.43.0
^ permalink raw reply related [flat|nested] 26+ messages in thread* [PATCH wireless-next v3 08/25] wifi: inffmac: add main.c/h
2026-09-02 16:50 [PATCH wireless-next v3 00/25] wifi: inffmac: introducing a driver for Infineon's new generation chipsets Gokul Sivakumar
` (6 preceding siblings ...)
2026-09-02 16:50 ` [PATCH wireless-next v3 07/25] wifi: inffmac: add icdc.c/h Gokul Sivakumar
@ 2026-09-02 16:50 ` Gokul Sivakumar
2026-09-02 16:50 ` [PATCH wireless-next v3 09/25] wifi: inffmac: add dev_cmd.c/h Gokul Sivakumar
` (16 subsequent siblings)
24 siblings, 0 replies; 26+ messages in thread
From: Gokul Sivakumar @ 2026-09-02 16:50 UTC (permalink / raw)
To: linux-wireless, johannes
Cc: arend.vanspriel, wlan-kernel-dev-list, gokulkumar.sivakumar
Driver main file to initialize and De-initialize the Kernel module in the
kernel. Also maintains some of the core driver structures, functions and
common module param registrations.
Signed-off-by: Gokul Sivakumar <gokulkumar.sivakumar@infineon.com>
---
drivers/net/wireless/infineon/inffmac/main.c | 821 +++++++++++++++++++
drivers/net/wireless/infineon/inffmac/main.h | 314 +++++++
2 files changed, 1135 insertions(+)
create mode 100644 drivers/net/wireless/infineon/inffmac/main.c
create mode 100644 drivers/net/wireless/infineon/inffmac/main.h
diff --git a/drivers/net/wireless/infineon/inffmac/main.c b/drivers/net/wireless/infineon/inffmac/main.c
new file mode 100644
index 000000000000..9b9d0c4f1409
--- /dev/null
+++ b/drivers/net/wireless/infineon/inffmac/main.c
@@ -0,0 +1,821 @@
+// SPDX-License-Identifier: ISC
+/*
+ * Copyright (c) 2010 Broadcom Corporation
+ *
+ * Copyright (c) 2025-2026, Infineon Technologies AG, or an affiliate of Infineon Technologies AG.
+ * All rights reserved.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/etherdevice.h>
+#include <linux/inetdevice.h>
+#include <linux/reboot.h>
+
+#include "main.h"
+#include "net.h"
+#include "cfg80211.h"
+#include "utils.h"
+#include "chan.h"
+#include "debug.h"
+#include "bus_proto.h"
+#include "sdio.h"
+#include "chip.h"
+#include "firmware.h"
+#include "dev_cmd.h"
+#include "dev_evt.h"
+#include "feature.h"
+#include "interface.h"
+
+#define INFF_DEFAULT_SCAN_CHANNEL_TIME 40
+#define INFF_DEFAULT_SCAN_UNASSOC_TIME 40
+
+/* default boost value for RSSI_DELTA in preferred join selection */
+#define INFF_JOIN_PREF_RSSI_BOOST 8
+
+/* Debug level configuration. See debug.h for bits, sysfs modifiable */
+int inff_msg_level;
+module_param_named(debug, inff_msg_level, int, 0600);
+MODULE_PARM_DESC(debug, "Level of debug output");
+
+static char inff_firmware_path[INFF_FW_ALTPATH_LEN];
+module_param_string(alternative_fw_path, inff_firmware_path,
+ INFF_FW_ALTPATH_LEN, 0400);
+MODULE_PARM_DESC(alternative_fw_path, "Alternative firmware path");
+
+static int inff_fcmode = 2;
+module_param_named(fcmode, inff_fcmode, int, 0);
+MODULE_PARM_DESC(fcmode, "Mode of firmware signalled flow control");
+
+static int inff_afq_enable = 1;
+module_param_named(afq_enable, inff_afq_enable, int, 0);
+MODULE_PARM_DESC(afq_enable, "Enable AFQ txstatus mechanism");
+
+static int inff_reboot_callback(struct notifier_block *this, unsigned long code, void *unused);
+static struct notifier_block inff_reboot_notifier = {
+ .notifier_call = inff_reboot_callback,
+ .priority = 1,
+};
+
+#define MAX_WAIT_FOR_8021X_TX msecs_to_jiffies(950)
+
+bool inff_socitype_is_ai(struct inff_bus *bus)
+{
+ if (bus->chip_pub)
+ return (bus->chip_pub->socitype == INFF_SOCI_AI);
+
+ return false;
+}
+
+bool inff_socitype_is_cp(struct inff_bus *bus)
+{
+ if (bus->chip_pub)
+ return (bus->chip_pub->socitype == INFF_SOCI_CP);
+
+ return false;
+}
+
+char *inff_ifname(struct inff_if *ifp)
+{
+ if (!ifp)
+ return "<if_null>";
+
+ if (ifp->ndev)
+ return ifp->ndev->name;
+
+ return "<if_none>";
+}
+
+void __inff_err(struct device *dev, const char *func, const char *fmt, ...)
+{
+ struct va_format vaf = {
+ .fmt = fmt,
+ };
+ va_list args;
+
+ va_start(args, fmt);
+ vaf.va = &args;
+ if (dev)
+ dev_err(dev, "%s: %pV", func, &vaf);
+ else
+ pr_err("%s: %pV", func, &vaf);
+ va_end(args);
+}
+
+struct inff_if *inff_get_ifp(struct inff_pub *drvr, int ifidx)
+{
+ struct inff_if *ifp;
+ s32 bsscfgidx;
+
+ if (ifidx < 0 || ifidx >= INFF_MAX_IFS) {
+ iphy_err(drvr, "ifidx %d out of range\n", ifidx);
+ return NULL;
+ }
+
+ ifp = NULL;
+ bsscfgidx = drvr->if2bss[ifidx];
+ if (bsscfgidx >= 0)
+ ifp = drvr->iflist[bsscfgidx];
+
+ return ifp;
+}
+
+int inff_rx_hdrpull(struct inff_pub *drvr, struct sk_buff *skb,
+ struct inff_if **ifp)
+{
+ int ret;
+
+ /* process and remove protocol-specific header */
+ ret = inff_proto_hdrpull(drvr, true, skb, ifp);
+
+ if (ret || !(*ifp) || !(*ifp)->ndev) {
+ if (ret != -ENODATA && *ifp && (*ifp)->ndev)
+ (*ifp)->ndev->stats.rx_errors++;
+ inff_pkt_buf_free_skb(skb);
+ return -ENODATA;
+ }
+
+ skb->protocol = eth_type_trans(skb, (*ifp)->ndev);
+ inff_dbg(DATA, "protocol: 0x%04X\n", skb->protocol);
+
+ return 0;
+}
+
+void inff_txfinalize(struct inff_if *ifp, struct sk_buff *txp, bool success)
+{
+ if (!txp) {
+ if (!success && ifp && ifp->ndev)
+ ifp->ndev->stats.tx_errors++;
+ return;
+ }
+
+ if (!ifp) {
+ inff_pkt_buf_free_skb(txp);
+ return;
+ }
+
+ if (txp->data) {
+ struct ethhdr *eh = (struct ethhdr *)(txp->data);
+ u16 type = ntohs(eh->h_proto);
+
+ if (type == ETH_P_PAE) {
+ atomic_dec(&ifp->pend_8021x_cnt);
+ /* Adding comment to avoid WARNING */
+ if (waitqueue_active(&ifp->pend_8021x_wait))
+ wake_up(&ifp->pend_8021x_wait);
+ }
+ }
+
+ if (!success && ifp->ndev)
+ ifp->ndev->stats.tx_errors++;
+
+ inff_pkt_buf_free_skb(txp);
+}
+
+void inff_fw_crashed(struct device *dev)
+{
+ struct inff_bus *bus_if = dev_get_drvdata(dev);
+ struct inff_pub *drvr = bus_if->drvr;
+
+ iphy_err(drvr, "Firmware has halted or crashed\n");
+
+ if (drvr->bus_reset.func)
+ schedule_work(&drvr->bus_reset);
+}
+
+static int inff_get_pend_8021x_cnt(struct inff_if *ifp)
+{
+ return atomic_read(&ifp->pend_8021x_cnt);
+}
+
+int inff_netdev_wait_pend8021x(struct inff_if *ifp)
+{
+ struct inff_pub *drvr = ifp->drvr;
+ int err;
+
+ err = wait_event_timeout(ifp->pend_8021x_wait,
+ !inff_get_pend_8021x_cnt(ifp),
+ MAX_WAIT_FOR_8021X_TX);
+
+ if (!err) {
+ iphy_err(drvr, "Timed out waiting for no pending 802.1x packets\n");
+ atomic_set(&ifp->pend_8021x_cnt, 0);
+ }
+
+ return !err;
+}
+
+static int
+inff_psm_watchdog_notify(struct inff_if *ifp,
+ const struct inff_event_msg *evtmsg,
+ void *data)
+{
+ struct inff_pub *drvr = ifp->drvr;
+
+ iphy_err(drvr, "PSM's watchdog has fired bsscfgidx=%d !\n",
+ ifp->bsscfgidx);
+
+ return 0;
+}
+
+void
+inff_set_joinpref_default(struct inff_if *ifp)
+{
+ struct inff_pub *drvr = ifp->drvr;
+ struct inff_join_pref_params join_pref_params[2];
+ int err;
+
+ /* Setup join_pref to select target by RSSI (boost on 5GHz) */
+ join_pref_params[0].type = INFF_JOIN_PREF_RSSI_DELTA;
+ join_pref_params[0].len = 2;
+ join_pref_params[0].rssi_gain = INFF_JOIN_PREF_RSSI_BOOST;
+ join_pref_params[0].band = WLC_BAND_5G;
+
+ join_pref_params[1].type = INFF_JOIN_PREF_RSSI;
+ join_pref_params[1].len = 2;
+ join_pref_params[1].rssi_gain = 0;
+ join_pref_params[1].band = 0;
+ err = inff_fwcmd_iovar_data_set(ifp, "join_pref", join_pref_params,
+ sizeof(join_pref_params));
+ if (err)
+ iphy_err(drvr, "Set join_pref error (%d)\n", err);
+}
+
+static int
+inff_download_clm_blob(struct inff_if *ifp, u16 flag,
+ struct inff_dload_data_le *dload_buf,
+ u32 len)
+{
+ s32 err;
+
+ flag |= (DLOAD_HANDLER_VER << DLOAD_FLAG_VER_SHIFT);
+ dload_buf->flag = cpu_to_le16(flag);
+ dload_buf->dload_type = cpu_to_le16(DL_TYPE_CLM);
+ dload_buf->len = cpu_to_le32(len);
+ dload_buf->crc = cpu_to_le32(0);
+
+ err = inff_fwcmd_iovar_data_set(ifp, "clmload", dload_buf,
+ struct_size(dload_buf, data, len));
+
+ return err;
+}
+
+static int
+inff_process_clm_blob(struct inff_if *ifp)
+{
+ struct inff_pub *drvr = ifp->drvr;
+ struct inff_bus *bus = drvr->bus_if;
+ struct inff_dload_data_le *chunk_buf;
+ const struct firmware *clm = NULL;
+ u32 chunk_len;
+ u32 datalen;
+ u32 cumulative_len;
+ u16 dl_flag = DL_BEGIN;
+ u32 status = 0;
+ s32 err;
+
+ err = inff_bus_get_blob(bus, &clm, INFF_BLOB_CLM);
+ if (err || !clm) {
+ inff_info("no clm_blob available (err=%d), device may have limited channels available\n",
+ err);
+ return 0;
+ }
+
+ chunk_buf = kzalloc_flex(*chunk_buf, data, MAX_CHUNK_LEN);
+ if (!chunk_buf) {
+ err = -ENOMEM;
+ goto done;
+ }
+
+ datalen = clm->size;
+ cumulative_len = 0;
+ do {
+ if (datalen > MAX_CHUNK_LEN) {
+ chunk_len = MAX_CHUNK_LEN;
+ } else {
+ chunk_len = datalen;
+ dl_flag |= DL_END;
+ }
+ memcpy(chunk_buf->data, clm->data + cumulative_len, chunk_len);
+
+ err = inff_download_clm_blob(ifp, dl_flag, chunk_buf, chunk_len);
+
+ dl_flag &= ~DL_BEGIN;
+
+ cumulative_len += chunk_len;
+ datalen -= chunk_len;
+ } while ((datalen > 0) && (err == 0));
+
+ if (err) {
+ iphy_err(drvr, "clmload (%zu byte file) failed (%d)\n",
+ clm->size, err);
+ /* Retrieve clmload_status and print */
+ err = inff_fwcmd_iovar_int_get(ifp, "clmload_status", &status);
+ if (err)
+ iphy_err(drvr, "get clmload_status failed (%d)\n", err);
+ else
+ inff_dbg(INFO, "clmload_status=%d\n", status);
+ err = -EIO;
+ }
+
+ kfree(chunk_buf);
+done:
+ release_firmware(clm);
+ return err;
+}
+
+int
+inff_set_cur_etheraddr(struct inff_if *ifp, const u8 *addr)
+{
+ s32 err;
+
+ err = inff_fwcmd_iovar_data_set(ifp, "cur_etheraddr", addr, ETH_ALEN);
+ if (err < 0)
+ iphy_err(ifp->drvr, "Setting cur_etheraddr failed, %d\n", err);
+
+ return err;
+}
+
+/* On some boards there is no eeprom to hold the nvram, in this case instead
+ * a board specific nvram is loaded from /lib/firmware. On most boards the
+ * macaddr setting in the /lib/firmware nvram file is ignored because the
+ * wifibt chip has a unique MAC programmed into the chip itself.
+ * But in some cases the actual MAC from the /lib/firmware nvram file gets
+ * used, leading to MAC conflicts.
+ * The MAC addresses in the troublesome nvram files seem to all come from
+ * the same nvram file template, so we only need to check for 1 known
+ * address to detect this.
+ */
+static const u8 inff_default_mac_address[ETH_ALEN] = {
+ 0x00, 0x90, 0x4c, 0xc5, 0x12, 0x38
+};
+
+static int
+inff_set_preinit_dcmds(struct inff_if *ifp)
+{
+ struct inff_pub *drvr = ifp->drvr;
+ s8 eventmask[INFF_EVENTING_MASK_LEN];
+ u8 buf[INFF_DCMD_SMLEN];
+ struct inff_bus *bus;
+ struct inff_rev_info_le revinfo;
+ struct inff_rev_info *ri;
+ struct inff_wlc_version_le wlc_ver;
+ char *clmver;
+ char *ptr;
+ s32 err;
+
+ if (is_valid_ether_addr(ifp->mac_addr)) {
+ /* set mac address */
+ err = inff_set_cur_etheraddr(ifp, ifp->mac_addr);
+ if (err < 0)
+ goto done;
+ } else {
+ /* retrieve mac address */
+ err = inff_fwcmd_iovar_data_get(ifp, "cur_etheraddr", ifp->mac_addr,
+ sizeof(ifp->mac_addr));
+ if (err < 0) {
+ iphy_err(drvr, "Retrieving cur_etheraddr failed, %d\n", err);
+ goto done;
+ }
+
+ if (ether_addr_equal_unaligned(ifp->mac_addr, inff_default_mac_address)) {
+ iphy_err(drvr, "Default MAC is used, replacing with random MAC to avoid conflicts\n");
+ eth_random_addr(ifp->mac_addr);
+ ifp->ndev->addr_assign_type = NET_ADDR_RANDOM;
+ err = inff_set_cur_etheraddr(ifp, ifp->mac_addr);
+ if (err < 0)
+ goto done;
+ }
+ }
+
+ memcpy(ifp->drvr->mac, ifp->mac_addr, sizeof(ifp->drvr->mac));
+ memcpy(ifp->drvr->wiphy->perm_addr, ifp->drvr->mac, ETH_ALEN);
+
+ bus = ifp->drvr->bus_if;
+ ri = &ifp->drvr->revinfo;
+
+ err = inff_fwcmd_data_get(ifp, INFF_C_GET_REVINFO,
+ &revinfo, sizeof(revinfo));
+ if (err < 0) {
+ iphy_err(drvr, "retrieving revision info failed, %d\n", err);
+ strscpy(ri->chipname, "UNKNOWN", sizeof(ri->chipname));
+ } else {
+ ri->vendorid = le32_to_cpu(revinfo.vendorid);
+ ri->deviceid = le32_to_cpu(revinfo.deviceid);
+ ri->radiorev = le32_to_cpu(revinfo.radiorev);
+ ri->corerev = le32_to_cpu(revinfo.corerev);
+ ri->boardid = le32_to_cpu(revinfo.boardid);
+ ri->boardvendor = le32_to_cpu(revinfo.boardvendor);
+ ri->boardrev = le32_to_cpu(revinfo.boardrev);
+ ri->driverrev = le32_to_cpu(revinfo.driverrev);
+ ri->ucoderev = le32_to_cpu(revinfo.ucoderev);
+ ri->bus = le32_to_cpu(revinfo.bus);
+ ri->phytype = le32_to_cpu(revinfo.phytype);
+ ri->phyrev = le32_to_cpu(revinfo.phyrev);
+ ri->anarev = le32_to_cpu(revinfo.anarev);
+ ri->chippkg = le32_to_cpu(revinfo.chippkg);
+ ri->nvramrev = le32_to_cpu(revinfo.nvramrev);
+
+ /* use revinfo if not known yet */
+ if (!bus->chip) {
+ bus->chip = le32_to_cpu(revinfo.chipnum);
+ bus->chiprev = le32_to_cpu(revinfo.chiprev);
+ }
+ }
+ ri->result = err;
+
+ if (bus->chip)
+ inff_chip_name(bus->chip, bus->chiprev,
+ ri->chipname, sizeof(ri->chipname));
+
+ if (inff_socitype_is_ai(drvr->bus_if)) {
+ /* Download CLM BLOB */
+ err = inff_process_clm_blob(ifp);
+ if (err < 0) {
+ iphy_err(drvr, "download CLM blob file failed, %d\n", err);
+ goto done;
+ }
+ }
+
+ /* query for 'ver' to get version info from firmware */
+ memset(buf, 0, sizeof(buf));
+ err = inff_fwcmd_iovar_data_get(ifp, "ver", buf, sizeof(buf));
+ if (err < 0) {
+ iphy_err(drvr, "Retrieving version information failed, %d\n",
+ err);
+ goto done;
+ }
+ buf[sizeof(buf) - 1] = '\0';
+ ptr = (char *)buf;
+ strsep(&ptr, "\n");
+
+ /* Print fw version info */
+ inff_info("Firmware: %s %s\n", ri->chipname, buf);
+
+ /* locate firmware version number for ethtool */
+ ptr = strrchr(buf, ' ');
+ if (!ptr) {
+ iphy_err(drvr, "Retrieving version number failed");
+ goto done;
+ }
+ strscpy(ifp->drvr->fwver, ptr + 1, sizeof(ifp->drvr->fwver));
+
+ /* Get wlc interface version, set to 0 for legacy chip
+ * that is not supporting wlc_ver iovar
+ */
+ err = inff_fwcmd_iovar_data_get(ifp, "wlc_ver", &wlc_ver, sizeof(wlc_ver));
+ if (err < 0) {
+ ifp->drvr->wlc_ver.wlc_ver_major = 0;
+ ifp->drvr->wlc_ver.wlc_ver_minor = 0;
+ } else {
+ ifp->drvr->wlc_ver.wlc_ver_major = le16_to_cpu(wlc_ver.wlc_ver_major);
+ ifp->drvr->wlc_ver.wlc_ver_minor = le16_to_cpu(wlc_ver.wlc_ver_minor);
+ }
+ inff_dbg(TRACE, "wlc interface version, major=%d, minor=%d\n",
+ ifp->drvr->wlc_ver.wlc_ver_major,
+ ifp->drvr->wlc_ver.wlc_ver_minor);
+
+ /* Query for 'clmver' to get CLM version info from firmware */
+ if (inff_socitype_is_ai(drvr->bus_if)) {
+ memset(buf, 0, sizeof(buf));
+ err = inff_fwcmd_iovar_data_get(ifp, "clmver", buf, sizeof(buf));
+ if (err) {
+ inff_dbg(TRACE, "retrieving clmver failed, %d\n", err);
+ } else {
+ buf[sizeof(buf) - 1] = '\0';
+ clmver = (char *)buf;
+
+ /* Replace all newline/linefeed characters with space
+ * character
+ */
+ strreplace(clmver, '\n', ' ');
+
+ /* store CLM version for adding it to revinfo debugfs file */
+ memcpy(ifp->drvr->clmver, clmver, sizeof(ifp->drvr->clmver));
+
+ inff_dbg(INFO, "CLM version = %s\n", clmver);
+ }
+ }
+
+ /* set apsta */
+ err = inff_fwcmd_iovar_int_set(ifp, "apsta", 1);
+ if (err)
+ inff_info("failed setting apsta, %d\n", err);
+
+ /* set mpc */
+ err = inff_fwcmd_iovar_int_set(ifp, "mpc", 1);
+ if (err) {
+ iphy_err(drvr, "failed setting mpc\n");
+ goto done;
+ }
+
+ inff_set_joinpref_default(ifp);
+
+ /* Setup event_msgs, enable E_IF */
+ err = inff_fwcmd_iovar_data_get(ifp, "event_msgs", eventmask,
+ INFF_EVENTING_MASK_LEN);
+ if (err) {
+ iphy_err(drvr, "Get event_msgs error (%d)\n", err);
+ goto done;
+ }
+ setbit(eventmask, INFF_E_IF);
+ err = inff_fwcmd_iovar_data_set(ifp, "event_msgs", eventmask,
+ INFF_EVENTING_MASK_LEN);
+ if (err) {
+ iphy_err(drvr, "Set event_msgs error (%d)\n", err);
+ goto done;
+ }
+
+ if (inff_socitype_is_ai(drvr->bus_if)) {
+ /* Setup default scan channel time */
+ err = inff_fwcmd_int_set(ifp, INFF_C_SET_SCAN_CHANNEL_TIME,
+ INFF_DEFAULT_SCAN_CHANNEL_TIME);
+ if (err) {
+ iphy_err(drvr, "INFF_C_SET_SCAN_CHANNEL_TIME error (%d)\n",
+ err);
+ goto done;
+ }
+
+ /* Setup default scan unassoc time */
+ err = inff_fwcmd_int_set(ifp, INFF_C_SET_SCAN_UNASSOC_TIME,
+ INFF_DEFAULT_SCAN_UNASSOC_TIME);
+ if (err) {
+ iphy_err(drvr, "INFF_C_SET_SCAN_UNASSOC_TIME error (%d)\n",
+ err);
+ goto done;
+ }
+ }
+ /* Enable tx beamforming, errors can be ignored (not supported) */
+ (void)inff_fwcmd_iovar_int_set(ifp, "txbf", 1);
+ err = inff_fwcmd_iovar_int_set(ifp, "chanspec", 0x1001);
+ if (err < 0) {
+ iphy_err(drvr, "Initial Channel failed %d\n", err);
+ goto done;
+ }
+
+done:
+ return err;
+}
+
+static int
+inff_revinfo_read(struct seq_file *s, void *data)
+{
+ struct inff_bus *bus_if = dev_get_drvdata(s->private);
+ struct inff_rev_info *ri = &bus_if->drvr->revinfo;
+ char drev[INFF_DOTREV_LEN];
+ char brev[INFF_BOARDREV_LEN];
+
+ seq_printf(s, "vendorid: 0x%04x\n", ri->vendorid);
+ seq_printf(s, "deviceid: 0x%04x\n", ri->deviceid);
+ seq_printf(s, "radiorev: %s\n", inff_dotrev_str(ri->radiorev, drev));
+ seq_printf(s, "chip: %s\n", ri->chipname);
+ seq_printf(s, "chippkg: %u\n", ri->chippkg);
+ seq_printf(s, "corerev: %u\n", ri->corerev);
+ seq_printf(s, "boardid: 0x%04x\n", ri->boardid);
+ seq_printf(s, "boardvendor: 0x%04x\n", ri->boardvendor);
+ seq_printf(s, "boardrev: %s\n", inff_boardrev_str(ri->boardrev, brev));
+ seq_printf(s, "driverrev: %s\n", inff_dotrev_str(ri->driverrev, drev));
+ seq_printf(s, "ucoderev: %u\n", ri->ucoderev);
+ seq_printf(s, "bus: %u\n", ri->bus);
+ seq_printf(s, "phytype: %u\n", ri->phytype);
+ seq_printf(s, "phyrev: %u\n", ri->phyrev);
+ seq_printf(s, "anarev: %u\n", ri->anarev);
+ seq_printf(s, "nvramrev: %08x\n", ri->nvramrev);
+
+ seq_printf(s, "clmver: %s\n", bus_if->drvr->clmver);
+
+ return 0;
+}
+
+int
+inff_bus_attach(struct device *dev)
+{
+ struct inff_bus *bus_if = dev_get_drvdata(dev);
+ struct inff_pub *drvr = bus_if->drvr;
+ struct inff_if *pri_ifp;
+ struct inff_if *ifp_ap = NULL;
+ int ret = 0;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(drvr->if2bss); i++)
+ drvr->if2bss[i] = INFF_BSSIDX_INVALID;
+
+ mutex_init(&drvr->proto_block);
+
+ /* Link to bus module */
+ drvr->hdrlen = 0;
+
+ drvr->req_mpc = 1;
+ /* Attach and link in the protocol */
+ ret = inff_bus_proto_attach(drvr);
+ if (ret < 0) {
+ iphy_err(drvr, "inff_prot_attach failed\n");
+ goto fail;
+ }
+
+ if (inff_socitype_is_ai(bus_if)) {
+ /* Attach to events important for core code */
+ inff_fwevt_register(drvr, INFF_E_PSM_WATCHDOG,
+ inff_psm_watchdog_notify);
+ /* attach firmware event handler */
+ inff_fwevt_attach(drvr);
+ } else if (inff_socitype_is_cp(bus_if)) {
+ ret = inff_cpevt_attach(drvr);
+ if (ret != 0) {
+ iphy_err(drvr, "Event attach failed.!\n");
+ goto fail;
+ }
+ }
+
+ if (is_valid_ether_addr(user_mac_addr))
+ memcpy(drvr->settings->mac, user_mac_addr, ETH_ALEN);
+ /* add primary netdev in station mode */
+ pri_ifp = inff_net_add_if(drvr, INFF_INTERFACE_PRI_BSSCFGIDX,
+ INFF_INTERFACE_PRI_IFIDX, "wlan%d",
+ is_valid_ether_addr(drvr->settings->mac) ?
+ drvr->settings->mac : NULL,
+ NL80211_IFTYPE_STATION);
+ if (IS_ERR(pri_ifp)) {
+ ret = PTR_ERR(pri_ifp);
+ goto fail;
+ }
+
+ if (inff_socitype_is_cp(bus_if)) {
+ /* add netdev for SoftAP mode */
+ ifp_ap = inff_net_add_if(drvr, INFF_INTERFACE_SEC_BSSCFGIDX,
+ INFF_INTERFACE_SEC_IFIDX, "wlan%d",
+ NULL, NL80211_IFTYPE_AP);
+ if (IS_ERR(ifp_ap)) {
+ ret = PTR_ERR(ifp_ap);
+ goto fail;
+ }
+ }
+
+ if (inff_socitype_is_ai(bus_if)) {
+ /* signal bus ready */
+ inff_bus_change_state(bus_if, INFF_BUS_UP);
+
+ /* do bus specific preinit here */
+ ret = inff_bus_preinit(bus_if);
+ if (ret < 0)
+ goto fail;
+
+ ret = inff_bus_set_fcmode(bus_if);
+ /* Set fcmode = 0 for PCIe */
+ if (ret < 0)
+ drvr->settings->fcmode = 0;
+
+ ret = inff_proto_init_done(drvr);
+ if (ret < 0)
+ goto fail;
+
+ inff_proto_add_if(drvr, pri_ifp);
+ }
+
+ inff_feat_attach(drvr);
+
+ /* Bus is ready, do any initialization */
+ ret = inff_set_preinit_dcmds(pri_ifp);
+ if (ret < 0)
+ goto fail;
+
+ drvr->config = inff_cfg80211_attach(drvr, drvr->ops);
+ if (!drvr->config) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ ret = inff_net_attach(pri_ifp, false);
+ if (ret)
+ goto fail;
+
+ if (inff_socitype_is_cp(bus_if)) {
+ ret = inff_net_attach(ifp_ap, false);
+ if (ret)
+ goto fail;
+ }
+
+ /* populate debugfs */
+ inff_debugfs_add_entry(drvr, "revinfo", inff_revinfo_read);
+ inff_debugfs_add_entry(drvr, "parameter", inff_debugfs_param_read);
+ inff_feat_debugfs_create(drvr);
+ inff_bus_proto_debugfs_create(drvr);
+
+ goto exit;
+fail:
+ iphy_err(drvr, "dongle is not responding: err=%d\n",
+ ret);
+ inff_bus_detach(dev);
+exit:
+ return ret;
+}
+
+void
+inff_bus_detach(struct device *dev)
+{
+ s32 i;
+ struct inff_bus *bus_if = dev_get_drvdata(dev);
+ struct inff_pub *drvr = bus_if->drvr;
+
+ if (!drvr)
+ return;
+
+ if (inff_socitype_is_ai(bus_if))
+ inff_fwevt_detach(drvr);
+ else if (inff_socitype_is_cp(bus_if))
+ inff_cpevt_detach(drvr);
+
+ inff_bus_stop(bus_if);
+
+ inff_bus_change_state(bus_if, INFF_BUS_DOWN);
+
+ /* make sure primary interface removed last */
+ for (i = INFF_MAX_IFS - 1; i > -1; i--) {
+ if (drvr->iflist[i])
+ inff_net_del_if(drvr, drvr->iflist[i], false);
+ }
+
+ inff_bus_proto_detach(drvr);
+
+ if (drvr->config) {
+ inff_cfg80211_detach(drvr->config);
+ drvr->config = NULL;
+ }
+}
+
+int inff_debugfs_param_read(struct seq_file *s, void *data)
+{
+ struct inff_bus *bus_if = dev_get_drvdata(s->private);
+
+ seq_printf(s, "%-20s: %s\n", "Name", "Value");
+ seq_printf(s, "%-20s: 0x%x\n", "debug", inff_msg_level);
+ seq_printf(s, "%-20s: %s\n", "alternative_fw_path", inff_firmware_path);
+ seq_printf(s, "%-20s: %d\n", "fcmode", bus_if->drvr->settings->fcmode);
+ seq_printf(s, "%-20s: %d\n", "afq_enable", !!inff_afq_enable);
+
+ return 0;
+}
+
+struct inff_mp_device *inff_get_module_param(struct device *dev)
+{
+ struct inff_mp_device *settings;
+
+ settings = kzalloc_obj(*settings, GFP_ATOMIC);
+ if (!settings)
+ return NULL;
+
+ /* start by using the module parameters */
+ inff_dbg(INFO, "debug: 0x%x\n", inff_msg_level);
+ strscpy(settings->firmware_path, inff_firmware_path,
+ INFF_FW_ALTPATH_LEN);
+ inff_dbg(INFO, "alternative_fw_path: %s\n", settings->firmware_path);
+
+ settings->fcmode = inff_fcmode;
+ inff_dbg(INFO, "fcmode: %d\n", settings->fcmode);
+ settings->afq_enable = !!inff_afq_enable;
+ inff_dbg(INFO, "afq_enable: %d\n", settings->afq_enable);
+
+ return settings;
+}
+
+void inff_release_module_param(struct inff_mp_device *module_param)
+{
+ kfree(module_param);
+}
+
+static int
+inff_reboot_callback(struct notifier_block *this, unsigned long code, void *unused)
+{
+ inff_dbg(INFO, "code = %ld\n", code);
+ if (code == SYS_RESTART)
+ inff_sdio_exit();
+
+ return NOTIFY_DONE;
+}
+
+static int __init inff_module_init(void)
+{
+ int err;
+
+ err = inff_sdio_register();
+ if (err)
+ return err;
+
+ register_reboot_notifier(&inff_reboot_notifier);
+
+ return 0;
+}
+
+static void __exit inff_module_exit(void)
+{
+ inff_sdio_exit();
+
+ unregister_reboot_notifier(&inff_reboot_notifier);
+}
+
+module_init(inff_module_init);
+module_exit(inff_module_exit);
+
+MODULE_AUTHOR("Infineon Technologies AG");
+MODULE_DESCRIPTION("Infineon 802.11 wireless LAN fullmac driver.");
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/drivers/net/wireless/infineon/inffmac/main.h b/drivers/net/wireless/infineon/inffmac/main.h
new file mode 100644
index 000000000000..6f68838612ba
--- /dev/null
+++ b/drivers/net/wireless/infineon/inffmac/main.h
@@ -0,0 +1,314 @@
+/* SPDX-License-Identifier: ISC */
+/*
+ * Copyright (c) 2010 Broadcom Corporation
+ *
+ * Copyright (c) 2025-2026, Infineon Technologies AG, or an affiliate of Infineon Technologies AG.
+ * All rights reserved.
+ */
+
+#ifndef INFF_MAIN_H
+#define INFF_MAIN_H
+
+#include <linux/types.h>
+#include <linux/firmware.h>
+#include <net/cfg80211.h>
+
+#include "dev_evt.h"
+#include "dev_cmd.h"
+#include "feature.h"
+
+/* The interval to poll console */
+#define INFF_CONSOLE_INTERVAL 500
+#define INFF_CPUSS_CONSOLE_INTERVAL 100
+
+/* The maximum console interval value (5 mins) */
+#define MAX_CONSOLE_INTERVAL (5 * 60 * 1000)
+
+/*
+ * Priority definitions according 802.1D
+ */
+#define PRIO_8021D_NONE 2
+#define PRIO_8021D_BK 1
+#define PRIO_8021D_BE 0
+#define PRIO_8021D_EE 3
+#define PRIO_8021D_CL 4
+#define PRIO_8021D_VI 5
+#define PRIO_8021D_VO 6
+#define PRIO_8021D_NC 7
+
+#define MAXPRIO 7
+#define NUMPRIO (MAXPRIO + 1)
+
+/* Bit masks for radio disabled status - returned by WL_GET_RADIO */
+
+#define WL_RADIO_SW_DISABLE BIT(0)
+#define WL_RADIO_HW_DISABLE BIT(1)
+/* some countries don't support any channel */
+#define WL_RADIO_COUNTRY_DISABLE BIT(3)
+
+/* Override bit for SET_TXPWR. if set, ignore other level limits */
+#define WL_TXPWR_OVERRIDE BIT(31)
+
+/*
+ * Sonics Configuration Space Registers.
+ */
+
+/* core sbconfig regs are top 256bytes of regs */
+#define SBCONFIGOFF 0xf00
+
+#define TOE_TX_CSUM_OL 0x00000001
+#define TOE_RX_CSUM_OL 0x00000002
+
+/* For supporting multiple interfaces */
+#define INFF_MAX_IFS 16
+
+/* Small, medium and maximum buffer size for dcmd
+ */
+#define INFF_DCMD_SMLEN 256
+#define INFF_DCMD_MEDLEN 1536
+#define INFF_DCMD_MAXLEN 8192
+
+/* IOCTL from host to device are limited in length. A device can only handle
+ * ethernet frame size. This limitation is to be applied by protocol layer.
+ */
+#define INFF_TX_IOCTL_MAX_MSG_SIZE (ETH_FRAME_LEN + ETH_FCS_LEN)
+
+#define INFF_AMPDU_RX_REORDER_MAXFLOWS 256
+
+/* Length of firmware version string stored for
+ * ethtool driver info which uses 32 bytes as well.
+ */
+#define INFF_DRIVER_FIRMWARE_VERSION_LEN 32
+
+#define NDOL_MAX_ENTRIES 8
+
+#define INFF_FW_ALTPATH_LEN 256
+
+#define INFFMAC_DISABLE 0
+#define INFFMAC_ENABLE 1
+#define INFFMAC_AUTO 2
+
+#define INFF_DEFAULT_SDIO_IDLE_CONFIG 2
+/* Keeping these macro definition here because these are defined in mmc drivers.
+ * So for 3rd party mmc, fmac build should not fail due to build error.
+ */
+
+/* SDIO IDLECLOCK Support - reusing pm_caps */
+#ifndef SDIO_IDLECLOCK_DIS
+#define SDIO_IDLECLOCK_DIS BIT(2) /* Start SDClock */
+#define SDIO_IDLECLOCK_EN BIT(3) /* Stop SDClock */
+#define SDIO_SDMODE_1BIT BIT(4) /* Set 1-bit Bus mode */
+#define SDIO_SDMODE_4BIT BIT(5) /* Set 4-bit Bus mode */
+#endif /* !SDIO_IDLECLOCK_DIS */
+
+extern u8 user_mac_addr[ETH_ALEN];
+
+struct inff_sdio_platform_data {
+ bool oob_irq_supported;
+ unsigned int oob_irq_nr;
+ unsigned long oob_irq_flags;
+ bool broken_sg_support;
+ unsigned short sd_head_align;
+ unsigned short sd_sgentry_align;
+};
+
+/**
+ * struct inff_mp_device - Device module parameters.
+ *
+ * @fcmode: FWS flow control.
+ * @afq_enable: Enable AFQ txstatus mechanism.
+ * @country_codes: If available, pointer to struct for translating country codes
+ * @bus: Bus specific platform data. Only SDIO at the mmoment.
+ */
+#define INFF_MAX_FEATURE_BYTES DIV_ROUND_UP(INFF_FEAT_LAST, 8)
+struct inff_mp_device {
+ char firmware_path[INFF_FW_ALTPATH_LEN];
+ int fcmode;
+ bool afq_enable;
+ const char *board_type;
+ unsigned char mac[ETH_ALEN];
+ union {
+ struct inff_sdio_platform_data sdio;
+ } bus;
+};
+
+/**
+ * struct inff_ampdu_rx_reorder - AMPDU receive reorder info
+ *
+ * @flow_id: AMPDU flow identifier.
+ * @cur_idx: last AMPDU index from firmware.
+ * @exp_idx: expected next AMPDU index.
+ * @max_idx: maximum amount of packets per AMPDU.
+ * @pend_pkts: number of packets currently in @pktslots.
+ * @pktslots: array for ordering AMPDU packets.
+ */
+struct inff_ampdu_rx_reorder {
+ u8 flow_id;
+ u8 cur_idx;
+ u8 exp_idx;
+ u8 max_idx;
+ u8 pend_pkts;
+ struct sk_buff *pktslots[];
+};
+
+/* Forward decls for struct inff_pub (see below) */
+struct inff_proto; /* device communication protocol info */
+struct inff_fws_info; /* firmware signalling info */
+struct inff_mp_device; /* module parameters, device specific */
+
+/*
+ * struct inff_rev_info
+ *
+ * The result field stores the error code of the
+ * revision info request from firmware. For the
+ * other fields see struct inff_rev_info_le in
+ * fwil_types.h
+ */
+struct inff_rev_info {
+ int result;
+ u32 vendorid;
+ u32 deviceid;
+ u32 radiorev;
+ u32 corerev;
+ u32 boardid;
+ u32 boardvendor;
+ u32 boardrev;
+ u32 driverrev;
+ u32 ucoderev;
+ u32 bus;
+ char chipname[12];
+ u32 phytype;
+ u32 phyrev;
+ u32 anarev;
+ u32 chippkg;
+ u32 nvramrev;
+};
+
+/** wlc interface version */
+struct inff_wlc_version {
+ /* wlc interface version numbers */
+ u16 wlc_ver_major; /**< wlc interface major version number */
+ u16 wlc_ver_minor; /**< wlc interface minor version number */
+};
+
+/* Common structure for module and instance linkage */
+struct inff_pub {
+ /* Linkage ponters */
+ struct inff_bus *bus_if;
+ struct inff_proto *proto;
+ struct wiphy *wiphy;
+ struct cfg80211_ops *ops;
+ struct inff_cfg80211_info *config;
+
+ /* Internal inff items */
+ uint hdrlen; /* Total INFF header length (proto + bus) */
+
+ /* Dongle media info */
+ char fwver[INFF_DRIVER_FIRMWARE_VERSION_LEN];
+ u8 mac[ETH_ALEN]; /* MAC address obtained from dongle */
+
+ struct mac_address addresses[INFF_MAX_IFS];
+
+ struct inff_if *iflist[INFF_MAX_IFS];
+ s32 if2bss[INFF_MAX_IFS];
+
+ struct mutex proto_block; /* used to protect proto resource */
+ unsigned char proto_buf[INFF_DCMD_MAXLEN];
+
+ struct inff_fwevt_info fwevt;
+
+ struct inff_ampdu_rx_reorder
+ *reorder_flows[INFF_AMPDU_RX_REORDER_MAXFLOWS];
+
+ u8 feat_flags[DIV_ROUND_UP(INFF_FEAT_LAST, 8)];
+ u32 chip_quirks;
+ int req_mpc;
+
+ struct inff_rev_info revinfo;
+ struct inff_wlc_version wlc_ver;
+#ifdef DEBUG
+ struct dentry *dbgfs_dir;
+#endif
+
+ struct inff_mp_device *settings;
+
+ struct work_struct bus_reset;
+
+ u8 clmver[INFF_DCMD_SMLEN];
+ u8 sta_mac_idx;
+
+ struct inff_dfu *dfu;
+ struct inff_cpevt_info evi;
+};
+
+/* forward declarations */
+struct inff_cfg80211_vif;
+struct inff_fws_mac_descriptor;
+
+#define INFF_BSSIDX_INVALID -1
+
+/**
+ * struct inff_if - interface control information.
+ *
+ * @drvr: points to device related information.
+ * @vif: points to cfg80211 specific interface information.
+ * @ndev: associated network device.
+ * @multicast_work: wiphy work object for multicast provisioning.
+ * @fws_desc: interface specific firmware-signalling descriptor.
+ * @ifidx: interface index in device firmware.
+ * @bsscfgidx: index of bss associated with this interface.
+ * @mac_addr: assigned mac address.
+ * @netif_stop: bitmap indicates reason why netif queues are stopped.
+ * @netif_stop_lock: spinlock for update netif_stop from multiple sources.
+ * @pend_8021x_cnt: tracks outstanding number of 802.1x frames.
+ * @pend_8021x_wait: used for signalling change in count.
+ * @fwil_fwerr: flag indicating fwil layer should return firmware error codes.
+ * @fmac_pkt_fwd_en: Driver packet forward enabled for AP Mode.
+ * @napi: NAPI structure for handling the RX packets received on this interface.
+ * @napi_gro: NAPI GRO Receive enabled flag.
+ */
+struct inff_if {
+ struct inff_pub *drvr;
+ struct inff_cfg80211_vif *vif;
+ struct net_device *ndev;
+ struct wiphy_work multicast_work;
+ struct inff_fws_mac_descriptor *fws_desc;
+ int ifidx;
+ s32 bsscfgidx;
+ bool isap;
+ u8 mac_addr[ETH_ALEN];
+ u8 netif_stop;
+ spinlock_t netif_stop_lock; /* used to protect netif_stop resource */
+ atomic_t pend_8021x_cnt;
+ wait_queue_head_t pend_8021x_wait;
+ bool fwil_fwerr;
+ bool fmac_pkt_fwd_en;
+ struct napi_struct napi;
+ bool napi_gro;
+ bool connected;
+};
+
+bool inff_socitype_is_ai(struct inff_bus *bus);
+bool inff_socitype_is_cp(struct inff_bus *bus);
+/* Return pointer to interface name */
+char *inff_ifname(struct inff_if *ifp);
+struct inff_if *inff_get_ifp(struct inff_pub *drvr, int ifidx);
+int inff_rx_hdrpull(struct inff_pub *drvr, struct sk_buff *skb,
+ struct inff_if **ifp);
+void inff_txfinalize(struct inff_if *ifp, struct sk_buff *txp, bool success);
+/* Indication that firmware has halted or crashed */
+void inff_fw_crashed(struct device *dev);
+int inff_netdev_wait_pend8021x(struct inff_if *ifp);
+
+void inff_set_joinpref_default(struct inff_if *ifp);
+int inff_set_cur_etheraddr(struct inff_if *ifp, const u8 *addr);
+
+/* Indication from bus module regarding presence/insertion of dongle. */
+int inff_bus_attach(struct device *dev);
+/* Indication from bus module regarding removal/absence of dongle */
+void inff_bus_detach(struct device *dev);
+
+int inff_debugfs_param_read(struct seq_file *s, void *data);
+struct inff_mp_device *inff_get_module_param(struct device *dev);
+void inff_release_module_param(struct inff_mp_device *module_param);
+#endif /* INFF_MAIN_H */
--
2.43.0
^ permalink raw reply related [flat|nested] 26+ messages in thread* [PATCH wireless-next v3 09/25] wifi: inffmac: add dev_cmd.c/h
2026-09-02 16:50 [PATCH wireless-next v3 00/25] wifi: inffmac: introducing a driver for Infineon's new generation chipsets Gokul Sivakumar
` (7 preceding siblings ...)
2026-09-02 16:50 ` [PATCH wireless-next v3 08/25] wifi: inffmac: add main.c/h Gokul Sivakumar
@ 2026-09-02 16:50 ` Gokul Sivakumar
2026-09-02 16:50 ` [PATCH wireless-next v3 10/25] wifi: inffmac: add dev_evt.c/h Gokul Sivakumar
` (15 subsequent siblings)
24 siblings, 0 replies; 26+ messages in thread
From: Gokul Sivakumar @ 2026-09-02 16:50 UTC (permalink / raw)
To: linux-wireless, johannes
Cc: arend.vanspriel, wlan-kernel-dev-list, gokulkumar.sivakumar
Driver implementation of the Control path communication to the CP and WLAN
Firmware of the Device.
Signed-off-by: Gokul Sivakumar <gokulkumar.sivakumar@infineon.com>
---
.../net/wireless/infineon/inffmac/dev_cmd.c | 653 ++++++++++++
.../net/wireless/infineon/inffmac/dev_cmd.h | 969 ++++++++++++++++++
2 files changed, 1622 insertions(+)
create mode 100644 drivers/net/wireless/infineon/inffmac/dev_cmd.c
create mode 100644 drivers/net/wireless/infineon/inffmac/dev_cmd.h
diff --git a/drivers/net/wireless/infineon/inffmac/dev_cmd.c b/drivers/net/wireless/infineon/inffmac/dev_cmd.c
new file mode 100644
index 000000000000..1ea01f66239e
--- /dev/null
+++ b/drivers/net/wireless/infineon/inffmac/dev_cmd.c
@@ -0,0 +1,653 @@
+// SPDX-License-Identifier: ISC
+/*
+ * Copyright (c) 2012 Broadcom Corporation
+ *
+ * Copyright (c) 2025-2026, Infineon Technologies AG, or an affiliate of Infineon Technologies AG.
+ * All rights reserved.
+ */
+
+/**
+ * Device/Dongle Control Interface. In this module the support functions
+ * are located to set and get variables to and from the Device.
+ */
+
+#include <linux/kernel.h>
+#include <linux/netdevice.h>
+
+#include "main.h"
+#include "utils.h"
+#include "debug.h"
+#include "dev_cmd.h"
+#include "bus_proto.h"
+
+#define MAX_HEX_DUMP_LEN 64
+#define MAX_CMD_RESEND 3
+
+#ifdef DEBUG
+static const char * const inff_fwcmd_errstr[] = {
+ "INFE_OK",
+ "INFE_ERROR",
+ "INFE_BADARG",
+ "INFE_BADOPTION",
+ "INFE_NOTUP",
+ "INFE_NOTDOWN",
+ "INFE_NOTAP",
+ "INFE_NOTSTA",
+ "INFE_BADKEYIDX",
+ "INFE_RADIOOFF",
+ "INFE_NOTBANDLOCKED",
+ "INFE_NOCLK",
+ "INFE_BADRATESET",
+ "INFE_BADBAND",
+ "INFE_BUFTOOSHORT",
+ "INFE_BUFTOOLONG",
+ "INFE_BUSY",
+ "INFE_NOTASSOCIATED",
+ "INFE_BADSSIDLEN",
+ "INFE_OUTOFRANGECHAN",
+ "INFE_BADCHAN",
+ "INFE_BADADDR",
+ "INFE_NORESOURCE",
+ "INFE_UNSUPPORTED",
+ "INFE_BADLEN",
+ "INFE_NOTREADY",
+ "INFE_EPERM",
+ "INFE_NOMEM",
+ "INFE_ASSOCIATED",
+ "INFE_RANGE",
+ "INFE_NOTFOUND",
+ "INFE_WME_NOT_ENABLED",
+ "INFE_TSPEC_NOTFOUND",
+ "INFE_ACM_NOTSUPPORTED",
+ "INFE_NOT_WME_ASSOCIATION",
+ "INFE_SDIO_ERROR",
+ "INFE_DONGLE_DOWN",
+ "INFE_VERSION",
+ "INFE_TXFAIL",
+ "INFE_RXFAIL",
+ "INFE_NODEVICE",
+ "INFE_NMODE_DISABLED",
+ "INFE_NONRESIDENT",
+ "INFE_SCANREJECT",
+ "INFE_USAGE_ERROR",
+ "INFE_IOCTL_ERROR",
+ "INFE_SERIAL_PORT_ERR",
+ "INFE_DISABLED",
+ "INFE_DECERR",
+ "INFE_ENCERR",
+ "INFE_MICERR",
+ "INFE_REPLAY",
+ "INFE_IE_NOTFOUND",
+};
+
+static const char *inff_fwcmd_get_errstr(u32 err)
+{
+ if (err >= ARRAY_SIZE(inff_fwcmd_errstr))
+ return "(unknown)";
+
+ return inff_fwcmd_errstr[err];
+}
+#else
+static const char *inff_fwcmd_get_errstr(u32 err)
+{
+ return "";
+}
+#endif /* DEBUG */
+
+static s32
+inff_cpcmd_data(struct inff_if *ifp, u32 cmd, void *data, u32 len, bool set)
+{
+ struct inff_pub *drvr = ifp->drvr;
+ s32 err, cperr;
+ u8 resend_cnt = 1;
+
+ if (drvr->bus_if->state != INFF_BUS_UP) {
+ iphy_err(drvr, "bus is down. we have nothing to do.\n");
+ return -EIO;
+ }
+
+ if (data)
+ len = min_t(uint, len, INFF_DCMD_MAXLEN);
+
+ do {
+ if (set)
+ err = inff_proto_set_cpcmd(drvr, ifp->ifidx, cmd,
+ data, len, &cperr);
+ else
+ err = inff_proto_query_cpcmd(drvr, ifp->ifidx, cmd,
+ data, len, &cperr);
+ if (!err || resend_cnt > MAX_CMD_RESEND)
+ break;
+
+ inff_err("cmd error %d, resend cmd count %d\n", err, resend_cnt++);
+ } while (true);
+
+ if (err) {
+ inff_dbg(DEVCMD, "Failed: error=%d\n", err);
+ } else if (cperr < 0) {
+ inff_dbg(DEVCMD, "CP Command error: %s (%d)\n",
+ inff_fwcmd_get_errstr((u32)(-cperr)), cperr);
+ err = -EBADE;
+ }
+ if (ifp->fwil_fwerr)
+ return cperr;
+
+ return err;
+}
+
+s32
+inff_cpcmd_data_set(struct inff_if *ifp, u32 cmd, void *data, u32 len)
+{
+ s32 err;
+
+ mutex_lock(&ifp->drvr->proto_block);
+
+ inff_dbg(DEVCMD, "ifidx=%d, cmd=%d, len=%d\n", ifp->ifidx, cmd, len);
+ inff_dbg_hex_dump(INFF_MSGLVL_ON(DEVCMD), data,
+ min_t(uint, len, MAX_HEX_DUMP_LEN), "data\n");
+
+ err = inff_cpcmd_data(ifp, cmd, data, len, true);
+ mutex_unlock(&ifp->drvr->proto_block);
+
+ return err;
+}
+
+s32
+inff_cpcmd_data_get(struct inff_if *ifp, u32 cmd, void *data, u32 len)
+{
+ s32 err;
+
+ mutex_lock(&ifp->drvr->proto_block);
+ err = inff_cpcmd_data(ifp, cmd, data, len, false);
+
+ inff_dbg(DEVCMD, "ifidx=%d, cmd=%d, len=%d, err=%d\n", ifp->ifidx, cmd,
+ len, err);
+ inff_dbg_hex_dump(INFF_MSGLVL_ON(DEVCMD), data,
+ min_t(uint, len, MAX_HEX_DUMP_LEN), "data\n");
+
+ mutex_unlock(&ifp->drvr->proto_block);
+
+ return err;
+}
+
+static s32
+inff_fwcmd_data(struct inff_if *ifp, u32 cmd, void *data, u32 len, bool set)
+{
+ struct inff_pub *drvr = ifp->drvr;
+ s32 err, fwerr;
+ u8 resend_cnt = 1;
+
+ if (drvr->bus_if->state != INFF_BUS_UP) {
+ iphy_err(drvr, "bus is down. we have nothing to do.\n");
+ return -EIO;
+ }
+
+ if (data)
+ len = min_t(uint, len, INFF_DCMD_MAXLEN);
+
+ do {
+ if (set)
+ err = inff_proto_set_fwcmd(drvr, ifp->ifidx, cmd,
+ data, len, &fwerr);
+ else
+ err = inff_proto_query_fwcmd(drvr, ifp->ifidx, cmd,
+ data, len, &fwerr);
+ if (!err || resend_cnt > MAX_CMD_RESEND)
+ break;
+
+ inff_err("cmd error %d, resend cmd count %d\n", err, resend_cnt++);
+ } while (true);
+
+ if (err) {
+ inff_dbg(DEVCMD, "Failed: error=%d\n", err);
+ } else if (fwerr < 0) {
+ inff_dbg(DEVCMD, "Firmware error: %s (%d)\n",
+ inff_fwcmd_get_errstr((u32)(-fwerr)), fwerr);
+ err = -EBADE;
+ }
+ if (ifp->fwil_fwerr)
+ return fwerr;
+
+ if (err && resend_cnt == MAX_CMD_RESEND) {
+ if (drvr->bus_reset.func)
+ schedule_work(&drvr->bus_reset);
+ }
+ return err;
+}
+
+s32
+inff_fwcmd_data_set(struct inff_if *ifp, u32 cmd, void *data, u32 len)
+{
+ s32 err;
+
+ mutex_lock(&ifp->drvr->proto_block);
+
+ inff_dbg(DEVCMD, "ifidx=%d, cmd=%d, len=%d\n", ifp->ifidx, cmd, len);
+ inff_dbg_hex_dump(INFF_MSGLVL_ON(DEVCMD), data,
+ min_t(uint, len, MAX_HEX_DUMP_LEN), "data\n");
+
+ err = inff_fwcmd_data(ifp, cmd, data, len, true);
+
+ mutex_unlock(&ifp->drvr->proto_block);
+
+ return err;
+}
+
+s32
+inff_fwcmd_data_get(struct inff_if *ifp, u32 cmd, void *data, u32 len)
+{
+ s32 err;
+
+ mutex_lock(&ifp->drvr->proto_block);
+
+ inff_dbg(DEVCMD, "ifidx=%d, cmd=%d, len=%d\n", ifp->ifidx, cmd,
+ len);
+
+ err = inff_fwcmd_data(ifp, cmd, data, len, false);
+
+ inff_dbg_hex_dump(INFF_MSGLVL_ON(DEVCMD), data,
+ min_t(uint, len, MAX_HEX_DUMP_LEN), "data\n");
+
+ mutex_unlock(&ifp->drvr->proto_block);
+
+ return err;
+}
+
+s32
+inff_fwcmd_int_set(struct inff_if *ifp, u32 cmd, u32 data)
+{
+ s32 err;
+ __le32 data_le = cpu_to_le32(data);
+
+ mutex_lock(&ifp->drvr->proto_block);
+ inff_dbg(DEVCMD, "ifidx=%d, cmd=%d, value=%d\n", ifp->ifidx, cmd, data);
+ err = inff_fwcmd_data(ifp, cmd, &data_le, sizeof(data_le), true);
+ mutex_unlock(&ifp->drvr->proto_block);
+
+ return err;
+}
+
+static u32
+inff_create_iovar(const char *name, const char *data, u32 datalen,
+ char *buf, u32 buflen)
+{
+ u32 len;
+
+ len = strlen(name) + 1;
+
+ if ((len + datalen) > buflen)
+ return 0;
+
+ memcpy(buf, name, len);
+
+ /* append data onto the end of the name string */
+ if (data && datalen)
+ memcpy(&buf[len], data, datalen);
+
+ return len + datalen;
+}
+
+s32
+inff_fwcmd_iovar_data_set(struct inff_if *ifp, const char *name,
+ const void *data, u32 len)
+{
+ struct inff_pub *drvr = ifp->drvr;
+ s32 err;
+ u32 buflen;
+
+ mutex_lock(&drvr->proto_block);
+
+ inff_dbg(DEVCMD, "ifidx=%d, name=%s, len=%d\n", ifp->ifidx, name, len);
+ inff_dbg_hex_dump(INFF_MSGLVL_ON(DEVCMD), data,
+ min_t(uint, len, MAX_HEX_DUMP_LEN), "data\n");
+
+ buflen = inff_create_iovar(name, data, len, drvr->proto_buf,
+ sizeof(drvr->proto_buf));
+ if (buflen) {
+ err = inff_fwcmd_data(ifp, INFF_C_SET_VAR,
+ drvr->proto_buf, buflen, true);
+ } else {
+ err = -EPERM;
+ iphy_err(drvr, "Creating iovar failed\n");
+ }
+
+ mutex_unlock(&drvr->proto_block);
+ return err;
+}
+
+s32
+inff_fwcmd_iovar_data_get(struct inff_if *ifp, const char *name,
+ void *data, u32 len)
+{
+ struct inff_pub *drvr = ifp->drvr;
+ s32 err;
+ u32 buflen;
+
+ mutex_lock(&drvr->proto_block);
+
+ inff_dbg(DEVCMD, "ifidx=%d, name=%s, len=%d\n", ifp->ifidx, name, len);
+
+ buflen = inff_create_iovar(name, data, len, drvr->proto_buf,
+ sizeof(drvr->proto_buf));
+ if (buflen) {
+ err = inff_fwcmd_data(ifp, INFF_C_GET_VAR,
+ drvr->proto_buf, buflen, false);
+ if (err == 0)
+ memcpy(data, drvr->proto_buf, len);
+ } else {
+ err = -EPERM;
+ iphy_err(drvr, "Creating iovar failed\n");
+ }
+
+ inff_dbg_hex_dump(INFF_MSGLVL_ON(DEVCMD), data,
+ min_t(uint, len, MAX_HEX_DUMP_LEN), "data\n");
+
+ mutex_unlock(&drvr->proto_block);
+ return err;
+}
+
+s32
+inff_fwcmd_iovar_int_set(struct inff_if *ifp, const char *name, u32 data)
+{
+ __le32 data_le = cpu_to_le32(data);
+
+ return inff_fwcmd_iovar_data_set(ifp, name, &data_le, sizeof(data_le));
+}
+
+s32
+inff_fwcmd_iovar_int_get(struct inff_if *ifp, const char *name, u32 *data)
+{
+ __le32 data_le = cpu_to_le32(*data);
+ s32 err;
+
+ err = inff_fwcmd_iovar_data_get(ifp, name, &data_le, sizeof(data_le));
+ if (err == 0)
+ *data = le32_to_cpu(data_le);
+ return err;
+}
+
+static u32
+inff_create_bsscfg(s32 bsscfgidx, const char *name, char *data, u32 datalen,
+ char *buf, u32 buflen)
+{
+ const s8 *prefix = "bsscfg:";
+ s8 *p;
+ u32 prefixlen;
+ u32 namelen;
+ u32 iolen;
+ __le32 bsscfgidx_le;
+
+ if (bsscfgidx == 0)
+ return inff_create_iovar(name, data, datalen, buf, buflen);
+
+ prefixlen = strlen(prefix);
+ namelen = strlen(name) + 1; /* length of iovar name + null */
+ iolen = prefixlen + namelen + sizeof(bsscfgidx_le) + datalen;
+
+ if (buflen < iolen) {
+ inff_err("buffer is too short\n");
+ return 0;
+ }
+
+ p = buf;
+
+ /* copy prefix, no null */
+ memcpy(p, prefix, prefixlen);
+ p += prefixlen;
+
+ /* copy iovar name including null */
+ memcpy(p, name, namelen);
+ p += namelen;
+
+ /* bss config index as first data */
+ bsscfgidx_le = cpu_to_le32(bsscfgidx);
+ memcpy(p, &bsscfgidx_le, sizeof(bsscfgidx_le));
+ p += sizeof(bsscfgidx_le);
+
+ /* parameter buffer follows */
+ if (datalen)
+ memcpy(p, data, datalen);
+
+ return iolen;
+}
+
+s32
+inff_fwcmd_bsscfg_data_set(struct inff_if *ifp, const char *name,
+ void *data, u32 len)
+{
+ struct inff_pub *drvr = ifp->drvr;
+ s32 err;
+ u32 buflen;
+
+ mutex_lock(&drvr->proto_block);
+
+ inff_dbg(DEVCMD, "ifidx=%d, bsscfgidx=%d, name=%s, len=%d\n", ifp->ifidx,
+ ifp->bsscfgidx, name, len);
+ inff_dbg_hex_dump(INFF_MSGLVL_ON(DEVCMD), data,
+ min_t(uint, len, MAX_HEX_DUMP_LEN), "data\n");
+
+ buflen = inff_create_bsscfg(ifp->bsscfgidx, name, data, len,
+ drvr->proto_buf, sizeof(drvr->proto_buf));
+ if (buflen) {
+ err = inff_fwcmd_data(ifp, INFF_C_SET_VAR,
+ drvr->proto_buf, buflen, true);
+ } else {
+ err = -EPERM;
+ iphy_err(drvr, "Creating bsscfg failed\n");
+ }
+
+ mutex_unlock(&drvr->proto_block);
+ return err;
+}
+
+s32
+inff_fwcmd_bsscfg_data_get(struct inff_if *ifp, const char *name,
+ void *data, u32 len)
+{
+ struct inff_pub *drvr = ifp->drvr;
+ s32 err;
+ u32 buflen;
+
+ mutex_lock(&drvr->proto_block);
+
+ buflen = inff_create_bsscfg(ifp->bsscfgidx, name, data, len,
+ drvr->proto_buf, sizeof(drvr->proto_buf));
+ if (buflen) {
+ err = inff_fwcmd_data(ifp, INFF_C_GET_VAR,
+ drvr->proto_buf, buflen, false);
+ if (err == 0)
+ memcpy(data, drvr->proto_buf, len);
+ } else {
+ err = -EPERM;
+ iphy_err(drvr, "Creating bsscfg failed\n");
+ }
+ inff_dbg(DEVCMD, "ifidx=%d, bsscfgidx=%d, name=%s, len=%d, err=%d\n",
+ ifp->ifidx, ifp->bsscfgidx, name, len, err);
+ inff_dbg_hex_dump(INFF_MSGLVL_ON(DEVCMD), data,
+ min_t(uint, len, MAX_HEX_DUMP_LEN), "data\n");
+
+ mutex_unlock(&drvr->proto_block);
+ return err;
+}
+
+s32
+inff_fwcmd_bsscfg_int_get(struct inff_if *ifp, const char *name, u32 *data)
+{
+ __le32 data_le = cpu_to_le32(*data);
+ s32 err;
+
+ err = inff_fwcmd_bsscfg_data_get(ifp, name, &data_le,
+ sizeof(data_le));
+ if (err == 0)
+ *data = le32_to_cpu(data_le);
+ return err;
+}
+
+static int inff_fwcmd_xtlv_header_size(u16 opts)
+{
+ int len = (int)offsetof(struct inff_xtlv, data);
+
+ if (opts & INFF_XTLV_OPTION_IDU8)
+ --len;
+ if (opts & INFF_XTLV_OPTION_LENU8)
+ --len;
+
+ return len;
+}
+
+static int inff_fwcmd_xtlv_data_size(int dlen, u16 opts)
+{
+ int hsz;
+
+ hsz = inff_fwcmd_xtlv_header_size(opts);
+ if (opts & INFF_XTLV_OPTION_ALIGN32)
+ return roundup(dlen + hsz, 4);
+
+ return dlen + hsz;
+}
+
+static void inff_fwcmd_xtlv_pack_header(struct inff_xtlv *xtlv, u16 id, u16 len,
+ const u8 *data, u16 opts)
+{
+ u8 *data_buf;
+ u16 mask = INFF_XTLV_OPTION_IDU8 | INFF_XTLV_OPTION_LENU8;
+
+ if (!(opts & mask)) {
+ u8 *idp = (u8 *)xtlv;
+ u8 *lenp = idp + sizeof(xtlv->id);
+
+ put_unaligned_le16(id, idp);
+ put_unaligned_le16(len, lenp);
+ data_buf = lenp + sizeof(u16);
+ } else if ((opts & mask) == mask) { /* u8 id and u8 len */
+ u8 *idp = (u8 *)xtlv;
+ u8 *lenp = idp + 1;
+
+ *idp = (u8)id;
+ *lenp = (u8)len;
+ data_buf = lenp + sizeof(u8);
+ } else if (opts & INFF_XTLV_OPTION_IDU8) { /* u8 id, u16 len */
+ u8 *idp = (u8 *)xtlv;
+ u8 *lenp = idp + 1;
+
+ *idp = (u8)id;
+ put_unaligned_le16(len, lenp);
+ data_buf = lenp + sizeof(u16);
+ } else if (opts & INFF_XTLV_OPTION_LENU8) { /* u16 id, u8 len */
+ u8 *idp = (u8 *)xtlv;
+ u8 *lenp = idp + sizeof(u16);
+
+ put_unaligned_le16(id, idp);
+ *lenp = (u8)len;
+ data_buf = lenp + sizeof(u8);
+ } else {
+ WARN(true, "Unexpected xtlv option");
+ return;
+ }
+
+ if (opts & INFF_XTLV_OPTION_LENU8) {
+ WARN_ON(len > 0x00ff);
+ len &= 0xff;
+ }
+
+ if (data)
+ memcpy(data_buf, data, len);
+}
+
+u32 inff_fwcmd_pack_xtlv(u16 id, char *data, u32 len,
+ char **buf, u16 *buflen)
+{
+ u32 iolen;
+
+ iolen = inff_fwcmd_xtlv_data_size(len, INFF_XTLV_OPTION_ALIGN32);
+
+ if (iolen > *buflen) {
+ WARN(true, "xtlv buffer is too short");
+ return 0;
+ }
+
+ inff_fwcmd_xtlv_pack_header((void *)*buf, id, len, data,
+ INFF_XTLV_OPTION_ALIGN32);
+
+ *buf = *buf + iolen;
+ *buflen -= iolen;
+ return iolen;
+}
+
+static u32 inff_create_xtlv(const char *name, u16 id, char *data, u32 len,
+ char *buf, u32 buflen)
+{
+ u32 iolen;
+ u32 nmlen;
+
+ nmlen = strlen(name) + 1;
+ iolen = nmlen + inff_fwcmd_xtlv_data_size(len, INFF_XTLV_OPTION_ALIGN32);
+
+ if (iolen > buflen) {
+ inff_err("buffer is too short\n");
+ return 0;
+ }
+
+ memcpy(buf, name, nmlen);
+ inff_fwcmd_xtlv_pack_header((void *)(buf + nmlen), id, len, data,
+ INFF_XTLV_OPTION_ALIGN32);
+
+ return iolen;
+}
+
+s32 inff_fwcmd_xtlv_data_set(struct inff_if *ifp, const char *name, u16 id,
+ void *data, u32 len)
+{
+ struct inff_pub *drvr = ifp->drvr;
+ s32 err;
+ u32 buflen;
+
+ mutex_lock(&drvr->proto_block);
+
+ inff_dbg(DEVCMD, "ifidx=%d, name=%s, id=%u, len=%u\n", ifp->ifidx, name,
+ id, len);
+ inff_dbg_hex_dump(INFF_MSGLVL_ON(DEVCMD), data,
+ min_t(uint, len, MAX_HEX_DUMP_LEN), "data\n");
+
+ buflen = inff_create_xtlv(name, id, data, len,
+ drvr->proto_buf, sizeof(drvr->proto_buf));
+ if (buflen) {
+ err = inff_fwcmd_data(ifp, INFF_C_SET_VAR,
+ drvr->proto_buf, buflen, true);
+ } else {
+ err = -EPERM;
+ iphy_err(drvr, "Creating xtlv failed\n");
+ }
+
+ mutex_unlock(&drvr->proto_block);
+ return err;
+}
+
+s32 inff_fwcmd_xtlv_data_get(struct inff_if *ifp, const char *name, u16 id,
+ void *data, u32 len)
+{
+ struct inff_pub *drvr = ifp->drvr;
+ s32 err;
+ u32 buflen;
+
+ mutex_lock(&drvr->proto_block);
+
+ buflen = inff_create_xtlv(name, id, data, len,
+ drvr->proto_buf, sizeof(drvr->proto_buf));
+ if (buflen) {
+ err = inff_fwcmd_data(ifp, INFF_C_GET_VAR,
+ drvr->proto_buf, buflen, false);
+ if (err == 0)
+ memcpy(data, drvr->proto_buf, len);
+ } else {
+ err = -EPERM;
+ iphy_err(drvr, "Creating bsscfg failed\n");
+ }
+ inff_dbg(DEVCMD, "ifidx=%d, name=%s, id=%u, len=%u, err=%d\n",
+ ifp->ifidx, name, id, len, err);
+ inff_dbg_hex_dump(INFF_MSGLVL_ON(DEVCMD), data,
+ min_t(uint, len, MAX_HEX_DUMP_LEN), "data\n");
+
+ mutex_unlock(&drvr->proto_block);
+ return err;
+}
diff --git a/drivers/net/wireless/infineon/inffmac/dev_cmd.h b/drivers/net/wireless/infineon/inffmac/dev_cmd.h
new file mode 100644
index 000000000000..22bf99c824e6
--- /dev/null
+++ b/drivers/net/wireless/infineon/inffmac/dev_cmd.h
@@ -0,0 +1,969 @@
+/* SPDX-License-Identifier: ISC */
+/*
+ * Copyright (c) 2012 Broadcom Corporation
+ *
+ * Copyright (c) 2025-2026, Infineon Technologies AG, or an affiliate of Infineon Technologies AG.
+ * All rights reserved.
+ */
+
+#ifndef INFF_DEV_CMD_H
+#define INFF_DEV_CMD_H
+
+#include <linux/if_ether.h>
+#include <linux/byteorder/generic.h>
+#include "debug.h"
+
+/*******************************************************************************
+ * Dongle command codes that are interpreted by firmware
+ ******************************************************************************/
+#define INFF_C_GET_VERSION 1
+#define INFF_C_UP 2
+#define INFF_C_DOWN 3
+#define INFF_C_SET_PROMISC 10
+#define INFF_C_GET_RATE 12
+#define INFF_C_GET_INFRA 19
+#define INFF_C_SET_INFRA 20
+#define INFF_C_GET_AUTH 21
+#define INFF_C_SET_AUTH 22
+#define INFF_C_GET_BSSID 23
+#define INFF_C_GET_SSID 25
+#define INFF_C_SET_SSID 26
+#define INFF_C_TERMINATED 28
+#define INFF_C_GET_CHANNEL 29
+#define INFF_C_SET_CHANNEL 30
+#define INFF_C_GET_SRL 31
+#define INFF_C_SET_SRL 32
+#define INFF_C_GET_LRL 33
+#define INFF_C_SET_LRL 34
+#define INFF_C_GET_RADIO 37
+#define INFF_C_SET_RADIO 38
+#define INFF_C_GET_PHYTYPE 39
+#define INFF_C_SET_KEY 45
+#define INFF_C_GET_REGULATORY 46
+#define INFF_C_SET_REGULATORY 47
+#define INFF_C_SET_PASSIVE_SCAN 49
+#define INFF_C_SCAN 50
+#define INFF_C_SCAN_RESULTS 51
+#define INFF_C_DISASSOC 52
+#define INFF_C_REASSOC 53
+#define INFF_C_SET_ROAM_TRIGGER 55
+#define INFF_C_SET_ROAM_DELTA 57
+#define INFF_C_GET_BCNPRD 75
+#define INFF_C_SET_BCNPRD 76
+#define INFF_C_GET_DTIMPRD 77
+#define INFF_C_SET_DTIMPRD 78
+#define INFF_C_SET_COUNTRY 84
+#define INFF_C_GET_PM 85
+#define INFF_C_SET_PM 86
+#define INFF_C_GET_REVINFO 98
+#define INFF_C_GET_CURR_RATESET 114
+#define INFF_C_GET_AP 117
+#define INFF_C_SET_AP 118
+#define INFF_C_SET_SCB_AUTHORIZE 121
+#define INFF_C_SET_SCB_DEAUTHORIZE 122
+#define INFF_C_GET_RSSI 127
+#define INFF_C_GET_WSEC 133
+#define INFF_C_SET_WSEC 134
+#define INFF_C_GET_PHY_NOISE 135
+#define INFF_C_GET_BSS_INFO 136
+#define INFF_C_GET_GET_PKTCNTS 137
+#define INFF_C_GET_BANDLIST 140
+#define INFF_C_SET_SCB_TIMEOUT 158
+#define INFF_C_GET_ASSOCLIST 159
+#define INFF_C_GET_UP 162
+#define INFF_C_GET_PHYLIST 180
+#define INFF_C_SET_SCAN_CHANNEL_TIME 185
+#define INFF_C_SET_SCAN_UNASSOC_TIME 187
+#define INFF_C_SCB_DEAUTHENTICATE_FOR_REASON 201
+#define INFF_C_SET_ASSOC_PREFER 205
+#define INFF_C_GET_VALID_CHANNELS 217
+#define INFF_C_GET_FAKEFRAG 218
+#define INFF_C_SET_FAKEFRAG 219
+#define INFF_C_GET_KEY_PRIMARY 235
+#define INFF_C_SET_KEY_PRIMARY 236
+#define INFF_C_SET_SCAN_PASSIVE_TIME 258
+#define INFF_C_GET_VAR 262
+#define INFF_C_SET_VAR 263
+#define INFF_C_SET_WSEC_PMK 268
+
+#define INFF_FW_BADARG 2
+#define INFF_FW_UNSUPPORTED 23
+
+/* Special OHD IOCTL definitions
+ * These ioctls carrying SDPCM_OHD_CHANNEL in swheader
+ * and will not be passed on to the firmware and is not
+ * exposed to WL_TOOL. It will be consumed by OHD.
+ */
+#define NON_WLC_ACW74XX_OHD_IOCTL_BASE 1000
+#define ACW74XX_OHD_IOCTL_GET_SHARED_INFO (NON_WLC_ACW74XX_OHD_IOCTL_BASE + 1)
+#define ACW74XX_OHD_IOCTL_GET_CREDIT_INFO (NON_WLC_ACW74XX_OHD_IOCTL_BASE + 2)
+/* end of Special OHD IOCTL */
+
+#define INFF_FIL_ACTION_FRAME_SIZE 1800
+
+#define INFF_AF_PARAM_V2_FW_MAJOR 13
+#define INFF_AF_PARAM_V2_FW_MINOR 2
+
+#define INFF_AUTH_STATUS_V2_FW_MAJOR 13
+#define INFF_AUTH_STATUS_V2_FW_MINOR 3
+
+#define INFF_BSS_INFO_VER_109 109 /* curr ver of inff_bss_info_le struct */
+#define INFF_BSS_INFO_VER_110 110
+
+#define INFF_BSS_RSSI_ON_CHANNEL 0x0004
+
+#define INFF_STA_INF 0x00000001 /* Running a Infineon driver */
+#define INFF_STA_WME 0x00000002 /* WMM association */
+#define INFF_STA_NONERP 0x00000004 /* No ERP */
+#define INFF_STA_AUTHE 0x00000008 /* Authenticated */
+#define INFF_STA_ASSOC 0x00000010 /* Associated */
+#define INFF_STA_AUTHO 0x00000020 /* Authorized */
+#define INFF_STA_WDS 0x00000040 /* Wireless Distribution System */
+#define INFF_STA_WDS_LINKUP 0x00000080 /* WDS traffic/probes flowing properly */
+#define INFF_STA_PS 0x00000100 /* STA is in power save mode from AP's viewpoint */
+#define INFF_STA_APSD_BE 0x00000200 /* APSD delv/trigger for AC_BE is default enabled */
+#define INFF_STA_APSD_BK 0x00000400 /* APSD delv/trigger for AC_BK is default enabled */
+#define INFF_STA_APSD_VI 0x00000800 /* APSD delv/trigger for AC_VI is default enabled */
+#define INFF_STA_APSD_VO 0x00001000 /* APSD delv/trigger for AC_VO is default enabled */
+#define INFF_STA_N_CAP 0x00002000 /* STA 802.11n capable */
+#define INFF_STA_SCBSTATS 0x00004000 /* Per STA debug stats */
+#define INFF_STA_AMPDU_CAP 0x00008000 /* STA AMPDU capable */
+#define INFF_STA_AMSDU_CAP 0x00010000 /* STA AMSDU capable */
+#define INFF_STA_MIMO_PS 0x00020000 /* mimo ps mode is enabled */
+#define INFF_STA_MIMO_RTS 0x00040000 /* send rts in mimo ps mode */
+#define INFF_STA_RIFS_CAP 0x00080000 /* rifs enabled */
+#define INFF_STA_VHT_CAP 0x00100000 /* STA VHT(11ac) capable */
+#define INFF_STA_WPS 0x00200000 /* WPS state */
+#define INFF_STA_DWDS_CAP 0x01000000 /* DWDS CAP */
+#define INFF_STA_DWDS 0x02000000 /* DWDS active */
+
+/* size of inff_scan_params not including variable length array */
+#define INFF_SCAN_PARAMS_FIXED_SIZE 64
+#define INFF_SCAN_PARAMS_V2_FIXED_SIZE 72
+
+/* version of inff_scan_params structure */
+#define INFF_SCAN_PARAMS_VERSION_V2 2
+
+/* masks for channel and ssid count */
+#define INFF_SCAN_PARAMS_COUNT_MASK 0x0000ffff
+#define INFF_SCAN_PARAMS_NSSID_SHIFT 16
+
+/* scan type definitions */
+#define INFF_SCANTYPE_DEFAULT 0xFF
+#define INFF_SCANTYPE_ACTIVE 0
+#define INFF_SCANTYPE_PASSIVE 1
+
+#define INFF_WSEC_MAX_PSK_LEN 32
+#define INFF_WSEC_PMK_LEN_SUITEB_192 48
+#define INFF_WSEC_MAX_PMK_LEN 64 /* SUITE-B-192's PMK is 48 bytes */
+#define INFF_WSEC_PASSPHRASE BIT(0)
+
+#define INFF_WSEC_MAX_SAE_PASSWORD_LEN 128
+
+/* primary (ie tx) key */
+#define INFF_PRIMARY_KEY BIT(1)
+#define DOT11_BSSTYPE_ANY 2
+#define INFF_ESCAN_REQ_VERSION 1
+#define INFF_ESCAN_REQ_VERSION_V2 2
+
+#define INFF_MAXRATES_IN_SET 16 /* max # of rates in rateset */
+
+/* OBSS Coex Auto/On/Off */
+#define INFF_OBSS_COEX_AUTO (-1)
+#define INFF_OBSS_COEX_OFF 0
+#define INFF_OBSS_COEX_ON 1
+
+#define CMD_MASK 0x00FFFFFF
+#define CMD_TYPE_MASK 0xFF000000
+#define CMD_TYPE_SHIFT 24
+
+#define CMD_TYPE_CP 0
+#define CMD_TYPE_WL 1
+
+enum {
+ INFF_CP_C_MAC = 1, /* Get MAC address, Set not ready */
+ INFF_CP_C_IPv4 = 2, /* Get IPv4 address, Set not ready */
+ INFF_CP_C_GATEWAY_IP = 3, /* Get gateway address, Set not ready */
+ INFF_CP_C_NETMASK = 4, /* Get netmask address, Set not ready */
+ INFF_CP_C_AP_INFO = 5, /* Get AP SSID/BSSID/channel/RSSI, Set not ready */
+ INFF_CP_C_IP_INFO = 6, /* Get IPv4/gateway/netmask address, Set not ready */
+ INFF_CP_C_SCAN = 7, /* Set to trigger Wi-Fi scan, Get not support */
+ INFF_CP_C_CONNECT = 8, /* Set to trigger Wi-Fi connect, Get not support */
+ INFF_CP_C_DISCONNECT = 9, /* Set to trigger Wi-Fi disconnect, Get not support */
+ INFF_CP_C_PING = 10, /* Set to trigger ICMP ping, Get not support */
+ INFF_CP_C_AT_CMD = 11, /* Set to send AT command to chip, Get not support */
+ INFF_CP_C_MSG_LEVEL = 12, /* Set to msg level to chip, Get not support */
+ INFF_CP_C_DFU = 13, /* Set to download cp image through SDIO, Get not support */
+ INFF_CP_C_IPv6 = 14, /* Get IPv6 link local address, Set not support */
+ INFF_CP_C_SAP_MAC = 15, /* Get MAC address of SoftAP, Set not ready */
+ INFF_CP_C_START_SAP = 16, /* Set Soft-AP Start, Get not ready */
+ INFF_CP_C_STOP_SAP = 17, /* Set Soft-AP Stop, Get not ready */
+
+ /* Driver Get/Set */
+ INFF_CP_C_REG_APP = 1001, /* Set to register task, Get not support */
+ INFF_CP_C_DEREG_APP = 1002, /* Set to deregister task, Get not support */
+ INFF_CP_C_EVENT = 1003, /* Get event, Set not support */
+ INFF_CP_C_REG_MONITOR = 1004, /* Set to register monitor task, Get not support */
+ INFF_CP_C_DEREG_MONITOR = 1005, /* Set to deregister monitor task, Get not support */
+ INFF_CP_C_MONITOR_EVENT = 1006, /* Get monitor event, Set not support */
+};
+
+#define INFF_COUNTRY_BUF_SZ 4
+#define INFF_ANT_MAX 4
+
+#define INFF_MAX_ASSOCLIST 128
+
+#define INFF_TXBF_SU_BFE_CAP BIT(0)
+#define INFF_TXBF_MU_BFE_CAP BIT(1)
+#define INFF_TXBF_SU_BFR_CAP BIT(0)
+#define INFF_TXBF_MU_BFR_CAP BIT(1)
+
+#define INFF_MAXPMKID 16 /* max # PMKID cache entries */
+#define INFF_NUMCHANNELS 64
+
+#define INFF_MCSSET_LEN 16
+
+#define INFF_RSN_KCK_LENGTH 16
+#define INFF_RSN_KEK_LENGTH 16
+#define INFF_RSN_REPLAY_LEN 8
+
+#define INFF_MFP_NONE 0
+#define INFF_MFP_CAPABLE 1
+#define INFF_MFP_REQUIRED 2
+
+#define INFF_VHT_CAP_MCS_MAP_NSS_MAX 8
+
+#define INFF_HE_CAP_MCS_MAP_NSS_MAX 8
+
+#define INFF_PMKSA_VER_2 2
+#define INFF_PMKSA_VER_3 3
+#define INFF_PMKSA_NO_EXPIRY 0xffffffff
+
+#define INFF_EXTAUTH_START 1
+#define INFF_EXTAUTH_ABORT 2
+#define INFF_EXTAUTH_FAIL 3
+#define INFF_EXTAUTH_SUCCESS 4
+
+/* mchan configuration (ap timeslot : sta timeslot)*/
+#define INFF_MCHAN_CONF_DEFAULT 0 /* mchan_algo=2 (25ms:25ms) */
+#define INFF_MCHAN_CONF_VEDIO 1 /* mchan_algo=5 (29ms:21ms) */
+#define INFF_MCHAN_CONF_AUDIO 2 /* mchan_algo=1, mchan_bw=32 (68ms:32ms) */
+/* mchan algo in dongle */
+#define INFF_MCHAN_DEFAULT_ALGO 0
+#define INFF_MCHAN_BANDWIDTH_ALGO 1
+#define INFF_MCHAN_SI_ALGO 2
+#define INFF_MCHAN_DYNAMIC_BW_ALGO 3
+#define INFF_MCHAN_ALTERNATE_SWITCHING 4
+#define INFF_MCHAN_ASYMMETRIC_SI_ALGO 5
+#define INFF_MCHAN_BANDWIDTH_VAL 32
+
+/* Values for PM */
+#define INFF_PM_OFF 0
+#define INFF_PM_MAX 1
+#define INFF_PM_FAST 2
+
+/* MAX_CHUNK_LEN is the maximum length for data passing to firmware in each
+ * ioctl. It is relatively small because firmware has small maximum size input
+ * playload restriction for ioctls.
+ */
+#define MAX_CHUNK_LEN 1400
+
+#define DLOAD_HANDLER_VER 1 /* Downloader version */
+#define DLOAD_FLAG_VER_MASK 0xf000 /* Downloader version mask */
+#define DLOAD_FLAG_VER_SHIFT 12 /* Downloader version shift */
+
+#define DL_BEGIN 0x0002
+#define DL_END 0x0004
+
+#define DL_TYPE_CLM 2
+
+/* inf type(id), length, value with w/16 bit id/len. The structure below
+ * is nominal, and is used to support variable length id and type. See
+ * xtlv options below.
+ */
+struct inff_xtlv {
+ __le16 id;
+ __le16 len;
+ u8 data[];
+};
+
+#define INFF_WL_CNT_XTLV_SLICE_IDX 256
+#define INFF_IOVAR_XTLV_BEGIN 4
+#define INFF_XTLV_TYPE_SIZE 2
+#define INFF_XTLV_TYPE_LEN_SIZE 4
+
+/* Macro to calculate packing factor with scalar 4 in a xTLV */
+#define INFF_XTLV_PACKING_FACTOR(args) ({ \
+ typeof(args) _args = (args); \
+ ((_args) % 4 == 0 ? 0 : (4 - ((_args) % 4))); \
+ })
+
+enum inff_xtlv_option {
+ INFF_XTLV_OPTION_ALIGN32 = BIT(0),
+ INFF_XTLV_OPTION_IDU8 = BIT(1),
+ INFF_XTLV_OPTION_LENU8 = BIT(2),
+};
+
+/* join preference types for join_pref iovar */
+enum inff_join_pref_types {
+ INFF_JOIN_PREF_RSSI = 1,
+ INFF_JOIN_PREF_WPA,
+ INFF_JOIN_PREF_BAND,
+ INFF_JOIN_PREF_RSSI_DELTA,
+};
+
+struct inff_fwcmd_chan_info_le {
+ __le32 hw_channel;
+ __le32 target_channel;
+ __le32 scan_channel;
+};
+
+struct inff_fwcmd_action_frame_le {
+ u8 da[ETH_ALEN];
+ __le16 len;
+ __le32 packet_id;
+ u8 data[INFF_FIL_ACTION_FRAME_SIZE];
+};
+
+struct inff_fwcmd_af_params_le {
+ __le32 channel;
+ __le32 dwell_time;
+ u8 bssid[ETH_ALEN];
+ u8 pad[2];
+ struct inff_fwcmd_action_frame_le action_frame;
+};
+
+struct inff_fwcmd_af_params_v2_le {
+ __le16 version;
+ __le16 length;
+ __le32 channel;
+ __le32 dwell_time;
+ u8 bssid[ETH_ALEN];
+ u8 band;
+ u8 pad[1];
+ struct inff_fwcmd_action_frame_le action_frame;
+};
+
+struct inff_fwcmd_bss_enable_le {
+ __le32 bsscfgidx;
+ __le32 enable;
+};
+
+struct inff_fwcmd_bwcap_le {
+ __le32 band;
+ __le32 bw_cap;
+};
+
+/* BSS info structure
+ * Applications MUST CHECK ie_offset field and length field to access IEs and
+ * next bss_info structure in a vector (in struct inff_scan_results)
+ */
+struct inff_bss_info_le {
+ __le32 version; /* version field */
+ __le32 length; /* byte length of data in this record,
+ * starting at version and including IEs
+ */
+ u8 BSSID[ETH_ALEN];
+ __le16 beacon_period; /* units are Kusec */
+ __le16 capability; /* Capability information */
+ u8 SSID_len;
+ u8 SSID[32];
+ u8 bcnflags; /* additional flags w.r.t. beacon */
+ struct {
+ __le32 count; /* # rates in this set */
+ u8 rates[16]; /* rates in 500kbps units w/hi bit set if basic */
+ } rateset; /* supported rates */
+ __le16 chanspec; /* chanspec for bss */
+ __le16 atim_window; /* units are Kusec */
+ u8 dtim_period; /* DTIM period */
+ u8 accessnet; /* from beacon interwork IE (if bcnflags) */
+ __le16 RSSI; /* receive signal strength (in dBm) */
+ s8 phy_noise; /* noise (in dBm) */
+
+ u8 n_cap; /* BSS is 802.11N Capable */
+
+ u8 he_cap; /* BSS is he capable */
+ u8 padding1;
+ __le32 nbss_cap; /* 802.11N BSS Capabilities (based on HT_CAP_*): */
+
+ u8 ctl_ch; /* 802.11N BSS control channel number */
+ u8 padding2[3];
+ __le16 vht_rxmcsmap; /* VHT rx mcs map (802.11ac IE, VHT_CAP_MCS_MAP_*) */
+ __le16 vht_txmcsmap; /* VHT tx mcs map (802.11ac IE, VHT_CAP_MCS_MAP_*) */
+
+ u8 flags; /* flags */
+ u8 vht_cap; /* BSS is vht capable */
+ u8 padding3[2]; /* Reserved for expansion of BSS properties */
+ u8 basic_mcs[INFF_MCSSET_LEN]; /* 802.11N BSS required MCS set */
+
+ __le16 ie_offset; /* offset at which IEs start, from beginning */
+ u8 padding4[2];
+ __le32 ie_length; /* byte length of Information Elements */
+ __le16 SNR; /* average SNR of during frame reception */
+
+ __le16 vht_mcsmap; /* STA's Associated vhtmcsmap */
+ __le16 vht_mcsmap_prop; /* STA's Associated prop vhtmcsmap */
+ __le16 vht_txmcsmap_prop; /* prop VHT tx mcs prop */
+ __le32 he_mcsmap; /* STA's Associated hemcsmap */
+ __le32 he_rxmcsmap; /* HE rx mcs map */
+ __le32 he_txmcsmap; /* HE tx mcs map */
+ __le32 timestamp[2]; /* Beacon Timestamp for FAKEAP req */
+
+ /* v110 */
+ /* Eht parameters in bss info
+ */
+ u8 eht_cap; /* BSS is eht capable */
+ u8 eht_20monly; /* BSS is 20M only capable */
+ u8 ml_cap; /* BSS is ML capable */
+ u8 linkid; /* Link id(if ML capable) */
+ __le32 eht_txrxnssmap; /* EHT txrx nss map */
+ __le32 eht_mcsnssmap; /* EHT device nss map */
+ u8 mld_mac_address[ETH_ALEN]; /* MLD mac address(if ML capable) */
+
+ /* Add new fields here */
+ /* variable length Information Elements */
+};
+
+struct inff_rateset_le {
+ /* # rates in this set */
+ __le32 count;
+ /* rates in 500kbps units w/hi bit set if basic */
+ u8 rates[INFF_MAXRATES_IN_SET];
+};
+
+struct inff_ssid_le {
+ __le32 SSID_len;
+ unsigned char SSID[IEEE80211_MAX_SSID_LEN];
+};
+
+struct inff_scan_params_le {
+ struct inff_ssid_le ssid_le; /* default: {0, ""} */
+ u8 bssid[ETH_ALEN]; /* default: bcast */
+ s8 bss_type; /* default: any,
+ * DOT11_BSSTYPE_ANY/INFRASTRUCTURE/INDEPENDENT
+ */
+ u8 scan_type; /* flags, 0 use default */
+ __le32 nprobes; /* -1 use default, number of probes per channel */
+ __le32 active_time; /* -1 use default, dwell time per channel for
+ * active scanning
+ */
+ __le32 passive_time; /* -1 use default, dwell time per channel
+ * for passive scanning
+ */
+ __le32 home_time; /* -1 use default, dwell time for the
+ * home channel between channel scans
+ */
+ __le32 channel_num; /* count of channels and ssids that follow
+ *
+ * low half is count of channels in
+ * channel_list, 0 means default (use all
+ * available channels)
+ *
+ * high half is entries in struct inff_ssid
+ * array that follows channel_list, aligned for
+ * s32 (4 bytes) meaning an odd channel count
+ * implies a 2-byte pad between end of
+ * channel_list and first ssid
+ *
+ * if ssid count is zero, single ssid in the
+ * fixed parameter portion is assumed, otherwise
+ * ssid in the fixed portion is ignored
+ */
+ union {
+ __le16 padding; /* Reserve space for at least 1 entry for abort
+ * which uses an on stack inff_scan_params_le
+ */
+ DECLARE_FLEX_ARRAY(__le16, channel_list); /* chanspecs */
+ };
+};
+
+struct inff_scan_params_v2_le {
+ __le16 version; /* structure version */
+ __le16 length; /* structure length */
+ struct inff_ssid_le ssid_le; /* default: {0, ""} */
+ u8 bssid[ETH_ALEN]; /* default: bcast */
+ s8 bss_type; /* default: any,
+ * DOT11_BSSTYPE_ANY/INFRASTRUCTURE/INDEPENDENT
+ */
+ u8 pad;
+ __le32 scan_type; /* flags, 0 use default */
+ __le32 nprobes; /* -1 use default, number of probes per channel */
+ __le32 active_time; /* -1 use default, dwell time per channel for
+ * active scanning
+ */
+ __le32 passive_time; /* -1 use default, dwell time per channel
+ * for passive scanning
+ */
+ __le32 home_time; /* -1 use default, dwell time for the
+ * home channel between channel scans
+ */
+ __le32 channel_num; /* count of channels and ssids that follow
+ *
+ * low half is count of channels in
+ * channel_list, 0 means default (use all
+ * available channels)
+ *
+ * high half is entries in struct inff_ssid
+ * array that follows channel_list, aligned for
+ * s32 (4 bytes) meaning an odd channel count
+ * implies a 2-byte pad between end of
+ * channel_list and first ssid
+ *
+ * if ssid count is zero, single ssid in the
+ * fixed parameter portion is assumed, otherwise
+ * ssid in the fixed portion is ignored
+ */
+ union {
+ __le16 padding; /* Reserve space for at least 1 entry for abort
+ * which uses an on stack inff_scan_params_v2_le
+ */
+ DECLARE_FLEX_ARRAY(__le16, channel_list); /* chanspecs */
+ };
+};
+
+struct inff_scan_results {
+ u32 buflen;
+ u32 version;
+ u32 count;
+ struct inff_bss_info_le bss_info_le[];
+};
+
+struct inff_escan_params_le {
+ __le32 version;
+ __le16 action;
+ __le16 sync_id;
+ union {
+ struct inff_scan_params_le params_le;
+ struct inff_scan_params_v2_le params_v2_le;
+ };
+};
+
+struct inff_escan_result_le {
+ __le32 buflen;
+ __le32 version;
+ __le16 sync_id;
+ __le16 bss_count;
+ struct inff_bss_info_le bss_info_le;
+};
+
+#define WL_ESCAN_RESULTS_FIXED_SIZE (sizeof(struct inff_escan_result_le) - \
+ sizeof(struct inff_bss_info_le))
+
+/* used for association with a specific BSSID and chanspec list */
+struct inff_assoc_params_le {
+ /* 00:00:00:00:00:00: broadcast scan */
+ u8 bssid[ETH_ALEN];
+ /* 0: all available channels, otherwise count of chanspecs in
+ * chanspec_list
+ */
+ __le32 chanspec_num;
+ /*
+ * list of chanspecs. Currently, driver only uses 1 chanspec here.
+ * Can convert this into a flexible array to support multiple
+ * chanspecs.
+ */
+ __le16 chanspec_list;
+};
+
+/**
+ * struct join_pref params - parameters for preferred join selection.
+ *
+ * @type: preference type (see enum inff_join_pref_types).
+ * @len: length of bytes following (currently always 2).
+ * @rssi_gain: signal gain for selection (only when @type is RSSI_DELTA).
+ * @band: band to which selection preference applies.
+ * This is used if @type is BAND or RSSI_DELTA.
+ */
+struct inff_join_pref_params {
+ u8 type;
+ u8 len;
+ u8 rssi_gain;
+ u8 band;
+};
+
+/* used for join with or without a specific bssid and channel list */
+struct inff_join_params {
+ struct inff_ssid_le ssid_le;
+ struct inff_assoc_params_le params_le;
+};
+
+/*
+ * wifi connect structure
+ */
+struct inff_cp_connect_le {
+ u8 ssid_length; /* SSID length */
+ char ssid[IEEE80211_MAX_SSID_LEN + 1]; /* SSID of the access point */
+ __le32 security_type; /* WiFi security type */
+ char password[PMK_MAX_LEN - 1]; /* Length of WIFI password */
+ u8 macaddr[ETH_ALEN]; /* MAC Address of the access point */
+ u8 band; /* WiFi band of access point */
+ __le32 ip_address; /* static IP address */
+ __le32 gateway; /* static gateway */
+ __le32 netmask; /* static netmask */
+ __le32 option; /* join option */
+ __le32 channel; /* WiFi channel number */
+} __packed;
+
+/* scan params for extended join */
+struct inff_join_scan_params_le {
+ u8 scan_type; /* 0 use default, active or passive scan */
+ __le32 nprobes; /* -1 use default, nr of probes per channel */
+ __le32 active_time; /* -1 use default, dwell time per channel for
+ * active scanning
+ */
+ __le32 passive_time; /* -1 use default, dwell time per channel
+ * for passive scanning
+ */
+ __le32 home_time; /* -1 use default, dwell time for the home
+ * channel between channel scans
+ */
+};
+
+/* extended join params */
+struct inff_ext_join_params_le {
+ struct inff_ssid_le ssid_le; /* {0, ""}: wildcard scan */
+ struct inff_join_scan_params_le scan_le;
+ struct inff_assoc_params_le assoc_le;
+};
+
+/**
+ * struct inff_auth_req_status_le - external auth request and status update
+ *
+ * @flags: flags for external auth status
+ * @peer_mac: peer MAC address
+ * @ssid_len: length of ssid
+ * @ssid: ssid characters
+ * @pmkid: PMKSA identifier
+ */
+struct inff_auth_req_status_le {
+ __le16 flags;
+ u8 peer_mac[ETH_ALEN];
+ __le32 ssid_len;
+ u8 ssid[IEEE80211_MAX_SSID_LEN];
+ u8 pmkid[WLAN_PMKID_LEN];
+};
+
+struct inff_auth_req_status_info_le_v2 {
+ __le16 version;
+ __le16 len;
+ __le16 flags;
+ u8 peer_mac[ETH_ALEN];/* peer mac address */
+ __le32 ssid_len;
+ u8 ssid[IEEE80211_MAX_SSID_LEN];
+ u8 pmkid[WLAN_PMKID_LEN];
+ struct inff_bss_info_le bss_info_le[];
+};
+
+/**
+ * struct inff_mf_params_le - management frame parameters for mgmt_frame iovar
+ *
+ * @version: version of the iovar
+ * @dwell_time: dwell duration in ms
+ * @len: length of frame data
+ * @frame_control: frame control
+ * @channel: channel
+ * @da: peer MAC address
+ * @bssid: BSS network identifier
+ * @packet_id: packet identifier
+ * @data: frame data
+ */
+struct inff_mf_params_le {
+ __le32 version;
+ __le32 dwell_time;
+ __le16 len;
+ __le16 frame_control;
+ __le16 channel;
+ u8 da[ETH_ALEN];
+ u8 bssid[ETH_ALEN];
+ __le32 packet_id;
+ u8 data[] __counted_by_le(len);
+};
+
+/* Used to get specific STA parameters */
+struct inff_scb_val_le {
+ __le32 val;
+ u8 ea[ETH_ALEN];
+};
+
+/* channel encoding */
+struct inff_channel_info_le {
+ __le32 hw_channel;
+ __le32 target_channel;
+ __le32 scan_channel;
+};
+
+struct inff_sta_info_le {
+ __le16 ver; /* version of this struct */
+ __le16 len; /* length in bytes of this structure */
+ __le16 cap; /* sta's advertised capabilities */
+ __le32 flags; /* flags defined below */
+ __le32 idle; /* time since data pkt rx'd from sta */
+ u8 ea[ETH_ALEN]; /* Station address */
+ __le32 count; /* # rates in this set */
+ u8 rates[INFF_MAXRATES_IN_SET]; /* rates in 500kbps units */
+ /* w/hi bit set if basic */
+ __le32 in; /* seconds elapsed since associated */
+ __le32 listen_interval_inms; /* Min Listen interval in ms for STA */
+
+ /* Fields valid for ver >= 3 */
+ __le32 tx_pkts; /* # of packets transmitted */
+ __le32 tx_failures; /* # of packets failed */
+ __le32 rx_ucast_pkts; /* # of unicast packets received */
+ __le32 rx_mcast_pkts; /* # of multicast packets received */
+ __le32 tx_rate; /* Rate of last successful tx frame */
+ __le32 rx_rate; /* Rate of last successful rx frame */
+ __le32 rx_decrypt_succeeds; /* # of packet decrypted successfully */
+ __le32 rx_decrypt_failures; /* # of packet decrypted failed */
+
+ /* Fields valid for ver >= 4 */
+ __le32 tx_tot_pkts; /* # of tx pkts (ucast + mcast) */
+ __le32 rx_tot_pkts; /* # of data packets recvd (uni + mcast) */
+ __le32 tx_mcast_pkts; /* # of mcast pkts txed */
+ __le64 tx_tot_bytes; /* data bytes txed (ucast + mcast) */
+ __le64 rx_tot_bytes; /* data bytes recvd (ucast + mcast) */
+ __le64 tx_ucast_bytes; /* data bytes txed (ucast) */
+ __le64 tx_mcast_bytes; /* # data bytes txed (mcast) */
+ __le64 rx_ucast_bytes; /* data bytes recvd (ucast) */
+ __le64 rx_mcast_bytes; /* data bytes recvd (mcast) */
+ s8 rssi[INFF_ANT_MAX]; /* per antenna rssi */
+ s8 nf[INFF_ANT_MAX]; /* per antenna noise floor */
+ __le16 aid; /* association ID */
+ __le16 ht_capabilities; /* advertised ht caps */
+ __le16 vht_flags; /* converted vht flags */
+ __le32 tx_pkts_retry_cnt; /* # of frames where a retry was
+ * exhausted.
+ */
+ __le32 tx_pkts_retry_exhausted; /* # of user frames where a retry
+ * was exhausted
+ */
+ s8 rx_lastpkt_rssi[INFF_ANT_MAX]; /* Per antenna RSSI of last
+ * received data frame.
+ */
+ /* TX WLAN retry/failure statistics:
+ * Separated for host requested frames and locally generated frames.
+ * Include unicast frame only where the retries/failures can be counted.
+ */
+ __le32 tx_pkts_total; /* # user frames sent successfully */
+ __le32 tx_pkts_retries; /* # user frames retries */
+ __le32 tx_pkts_fw_total; /* # FW generated sent successfully */
+ __le32 tx_pkts_fw_retries; /* # retries for FW generated frames */
+ __le32 tx_pkts_fw_retry_exhausted; /* # FW generated where a retry
+ * was exhausted
+ */
+ __le32 rx_pkts_retried; /* # rx with retry bit set */
+ __le32 tx_rate_fallback; /* lowest fallback TX rate */
+
+ union {
+ struct {
+ struct {
+ /* # rates in this set */
+ __le32 count;
+ /* rates in 500kbps units w/hi bit set if basic */
+ u8 rates[INFF_MAXRATES_IN_SET];
+ /* supported mcs index bit map */
+ u8 mcs[INFF_MCSSET_LEN];
+ /* supported mcs index bit map per nss */
+ __le16 vht_mcs[INFF_VHT_CAP_MCS_MAP_NSS_MAX];
+ } rateset_adv;
+ } v5;
+
+ struct {
+ __le32 rx_dur_total; /* total user RX duration (estimated) */
+ __le16 chanspec; /** chanspec this sta is on */
+ __le16 pad_1;
+ struct {
+ __le16 version; /* version */
+ __le16 len; /* length */
+ /* # rates in this set */
+ __le32 count;
+ /* rates in 500kbps units w/hi bit set if basic */
+ u8 rates[INFF_MAXRATES_IN_SET];
+ /* supported mcs index bit map */
+ u8 mcs[INFF_MCSSET_LEN];
+ /* supported mcs index bit map per nss */
+ __le16 vht_mcs[INFF_VHT_CAP_MCS_MAP_NSS_MAX];
+ /* supported he mcs index bit map per nss */
+ __le16 he_mcs[INFF_HE_CAP_MCS_MAP_NSS_MAX];
+ } rateset_adv; /* rateset along with mcs index bitmap */
+ __le16 wpauth; /* authentication type */
+ u8 algo; /* crypto algorithm */
+ u8 pad_2;
+ __le32 tx_rspec; /* Rate of last successful tx frame */
+ __le32 rx_rspec; /* Rate of last successful rx frame */
+ __le32 wnm_cap; /* wnm capabilities */
+ } v7;
+ };
+};
+
+struct inff_chanspec_list {
+ __le32 count; /* # of entries */
+ __le32 element[]; /* variable length uint32 list */
+};
+
+/*
+ * INFF_E_PROBRESP_MSG
+ * INFF_E_ACTION_FRAME_RX_COMPLETE
+ */
+struct inff_rx_mgmt_data {
+ __be16 version;
+ __be16 chanspec;
+ __be32 rssi;
+ __be32 mactime;
+ __be32 rate;
+};
+
+/**
+ * struct inff_fwcmd_country_le - country configuration structure.
+ *
+ * @country_abbrev: null-terminated country code used in the country IE.
+ * @rev: revision specifier for ccode. on set, -1 indicates unspecified.
+ * @ccode: null-terminated built-in country code.
+ */
+struct inff_fwcmd_country_le {
+ char country_abbrev[INFF_COUNTRY_BUF_SZ];
+ __le32 rev;
+ char ccode[INFF_COUNTRY_BUF_SZ];
+};
+
+/**
+ * struct inff_rev_info_le - device revision info.
+ *
+ * @vendorid: PCI vendor id.
+ * @deviceid: device id of chip.
+ * @radiorev: radio revision.
+ * @chiprev: chip revision.
+ * @corerev: core revision.
+ * @boardid: board identifier (usu. PCI sub-device id).
+ * @boardvendor: board vendor (usu. PCI sub-vendor id).
+ * @boardrev: board revision.
+ * @driverrev: driver version.
+ * @ucoderev: microcode version.
+ * @bus: bus type.
+ * @chipnum: chip number.
+ * @phytype: phy type.
+ * @phyrev: phy revision.
+ * @anarev: anacore rev.
+ * @chippkg: chip package info.
+ * @nvramrev: nvram revision number.
+ */
+struct inff_rev_info_le {
+ __le32 vendorid;
+ __le32 deviceid;
+ __le32 radiorev;
+ __le32 chiprev;
+ __le32 corerev;
+ __le32 boardid;
+ __le32 boardvendor;
+ __le32 boardrev;
+ __le32 driverrev;
+ __le32 ucoderev;
+ __le32 bus;
+ __le32 chipnum;
+ __le32 phytype;
+ __le32 phyrev;
+ __le32 anarev;
+ __le32 chippkg;
+ __le32 nvramrev;
+};
+
+/**
+ * struct inff_wlc_version_le - firmware revision info.
+ *
+ * @version: structure version.
+ * @length: structure length.
+ * @epi_ver_major: EPI major version
+ * @epi_ver_minor: EPI minor version
+ * @epi_ver_rc: EPI rc version
+ * @epi_ver_incr: EPI increment version
+ * @wlc_ver_major: WLC major version
+ * @wlc_ver_minor: WLC minor version
+ */
+struct inff_wlc_version_le {
+ __le16 version;
+ __le16 length;
+
+ __le16 epi_ver_major;
+ __le16 epi_ver_minor;
+ __le16 epi_ver_rc;
+ __le16 epi_ver_incr;
+
+ __le16 wlc_ver_major;
+ __le16 wlc_ver_minor;
+};
+
+/**
+ * struct inff_dload_data_le - data passing to firmware for downloading
+ * @flag: flags related to download data.
+ * @dload_type: type of download data.
+ * @len: length in bytes of download data.
+ * @crc: crc of download data.
+ * @data: download data.
+ */
+struct inff_dload_data_le {
+ __le16 flag;
+ __le16 dload_type;
+ __le32 len;
+ __le32 crc;
+ u8 data[];
+};
+
+/**
+ * struct inff_pktcnt_le - packet counters.
+ *
+ * @rx_good_pkt: packets (MSDUs & MMPDUs) received from this station
+ * @rx_bad_pkt: failed rx packets
+ * @tx_good_pkt: packets (MSDUs & MMPDUs) transmitted to this station
+ * @tx_bad_pkt: failed tx packets
+ * @rx_ocast_good_pkt: unicast packets destined for others
+ */
+struct inff_pktcnt_le {
+ __le32 rx_good_pkt;
+ __le32 rx_bad_pkt;
+ __le32 tx_good_pkt;
+ __le32 tx_bad_pkt;
+ __le32 rx_ocast_good_pkt;
+};
+
+/**
+ * struct inff_gtk_keyinfo_le - GTP rekey data
+ *
+ * @kck: key confirmation key.
+ * @kek: key encryption key.
+ * @replay_counter: replay counter.
+ */
+struct inff_gtk_keyinfo_le {
+ u8 kck[INFF_RSN_KCK_LENGTH];
+ u8 kek[INFF_RSN_KEK_LENGTH];
+ u8 replay_counter[INFF_RSN_REPLAY_LEN];
+};
+
+s32 inff_cpcmd_data_set(struct inff_if *ifp, u32 cmd, void *data, u32 len);
+s32 inff_cpcmd_data_get(struct inff_if *ifp, u32 cmd, void *data, u32 len);
+
+s32 inff_fwcmd_data_set(struct inff_if *ifp, u32 cmd, void *data, u32 len);
+s32 inff_fwcmd_data_get(struct inff_if *ifp, u32 cmd, void *data, u32 len);
+s32 inff_fwcmd_int_set(struct inff_if *ifp, u32 cmd, u32 data);
+
+s32 inff_fwcmd_iovar_data_set(struct inff_if *ifp, const char *name,
+ const void *data, u32 len);
+s32 inff_fwcmd_iovar_data_get(struct inff_if *ifp, const char *name,
+ void *data, u32 len);
+s32 inff_fwcmd_iovar_int_set(struct inff_if *ifp, const char *name, u32 data);
+s32 inff_fwcmd_iovar_int_get(struct inff_if *ifp, const char *name, u32 *data);
+
+s32 inff_fwcmd_bsscfg_data_set(struct inff_if *ifp, const char *name,
+ void *data, u32 len);
+s32 inff_fwcmd_bsscfg_data_get(struct inff_if *ifp, const char *name,
+ void *data, u32 len);
+s32 inff_fwcmd_bsscfg_int_get(struct inff_if *ifp, const char *name, u32 *data);
+
+s32 inff_fwcmd_xtlv_data_get(struct inff_if *ifp, const char *name, u16 id,
+ void *data, u32 len);
+u32 inff_fwcmd_pack_xtlv(u16 id, char *data, u32 len,
+ char **buf, u16 *buflen);
+s32 inff_fwcmd_xtlv_data_set(struct inff_if *ifp, const char *name, u16 id,
+ void *data, u32 len);
+s32 inff_fwcmd_xtlv_data_get(struct inff_if *ifp, const char *name, u16 id,
+ void *data, u32 len);
+
+#endif /* INFF_DEV_CMD_H */
--
2.43.0
^ permalink raw reply related [flat|nested] 26+ messages in thread* [PATCH wireless-next v3 10/25] wifi: inffmac: add dev_evt.c/h
2026-09-02 16:50 [PATCH wireless-next v3 00/25] wifi: inffmac: introducing a driver for Infineon's new generation chipsets Gokul Sivakumar
` (8 preceding siblings ...)
2026-09-02 16:50 ` [PATCH wireless-next v3 09/25] wifi: inffmac: add dev_cmd.c/h Gokul Sivakumar
@ 2026-09-02 16:50 ` Gokul Sivakumar
2026-09-02 16:50 ` [PATCH wireless-next v3 11/25] wifi: inffmac: add net.c/h Gokul Sivakumar
` (14 subsequent siblings)
24 siblings, 0 replies; 26+ messages in thread
From: Gokul Sivakumar @ 2026-09-02 16:50 UTC (permalink / raw)
To: linux-wireless, johannes
Cc: arend.vanspriel, wlan-kernel-dev-list, gokulkumar.sivakumar
Driver implementation for registering and handling various types of
asynchronous events generated by the CP or the WLAN firmware in the
Device.
Signed-off-by: Gokul Sivakumar <gokulkumar.sivakumar@infineon.com>
---
.../net/wireless/infineon/inffmac/dev_evt.c | 847 ++++++++++++++++++
.../net/wireless/infineon/inffmac/dev_evt.h | 447 +++++++++
2 files changed, 1294 insertions(+)
create mode 100644 drivers/net/wireless/infineon/inffmac/dev_evt.c
create mode 100644 drivers/net/wireless/infineon/inffmac/dev_evt.h
diff --git a/drivers/net/wireless/infineon/inffmac/dev_evt.c b/drivers/net/wireless/infineon/inffmac/dev_evt.c
new file mode 100644
index 000000000000..6d63ad4afab1
--- /dev/null
+++ b/drivers/net/wireless/infineon/inffmac/dev_evt.c
@@ -0,0 +1,847 @@
+// SPDX-License-Identifier: ISC
+/*
+ * Copyright (c) 2012 Broadcom Corporation
+ *
+ * Copyright (c) 2025-2026, Infineon Technologies AG, or an affiliate of Infineon Technologies AG.
+ * All rights reserved.
+ */
+#include <linux/netdevice.h>
+
+#include "main.h"
+#include "chan.h"
+#include "utils.h"
+#include "cfg80211.h"
+#include "debug.h"
+#include "dev_evt.h"
+#include "dev_cmd.h"
+#include "interface.h"
+#include "bus_proto.h"
+#include "net.h"
+
+/**
+ * struct inff_fwevt_queue_item - event item on event queue.
+ *
+ * @q: list element for queuing.
+ * @code: event code.
+ * @ifidx: interface index related to this event.
+ * @ifaddr: ethernet address for interface.
+ * @emsg: common parameters of the firmware event message.
+ * @datalen: length of the data array
+ * @data: event specific data part of the firmware event.
+ */
+struct inff_fwevt_queue_item {
+ struct list_head q;
+ enum inff_fwevt_code code;
+ u8 ifidx;
+ u8 ifaddr[ETH_ALEN];
+ struct inff_event_msg_be emsg;
+ u32 datalen;
+ u8 data[] __counted_by(datalen);
+};
+
+/*
+ * struct inff_fwevt_code_name - code, name mapping entry.
+ */
+struct inff_fwevt_code_name {
+ enum inff_fwevt_code code;
+ const char *name;
+};
+
+#ifdef DEBUG
+/* array for mapping code to event name */
+static struct inff_fwevt_code_name
+fwevt_names[] = {
+ {INFF_E_SET_SSID, "SET_SSID"},
+ {INFF_E_JOIN, "JOIN"},
+ {INFF_E_START, "START"},
+ {INFF_E_AUTH, "AUTH"},
+ {INFF_E_AUTH_IND, "AUTH_IND"},
+ {INFF_E_DEAUTH, "DEAUTH"},
+ {INFF_E_DEAUTH_IND, "DEAUTH_IND"},
+ {INFF_E_ASSOC, "ASSOC"},
+ {INFF_E_ASSOC_IND, "ASSOC_IND"},
+ {INFF_E_REASSOC, "REASSOC"},
+ {INFF_E_REASSOC_IND, "REASSOC_IND"},
+ {INFF_E_DISASSOC, "DISASSOC"},
+ {INFF_E_DISASSOC_IND, "DISASSOC_IND"},
+ {INFF_E_QUIET_START, "QUIET_START"},
+ {INFF_E_QUIET_END, "QUIET_END"},
+ {INFF_E_BEACON_RX, "BEACON_RX"},
+ {INFF_E_LINK, "LINK"},
+ {INFF_E_MIC_ERROR, "MIC_ERROR"},
+ {INFF_E_NDIS_LINK, "NDIS_LINK"},
+ {INFF_E_ROAM, "ROAM"},
+ {INFF_E_TXFAIL, "TXFAIL"},
+ {INFF_E_PMKID_CACHE, "PMKID_CACHE"},
+ {INFF_E_RETROGRADE_TSF, "RETROGRADE_TSF"},
+ {INFF_E_PRUNE, "PRUNE"},
+ {INFF_E_AUTOAUTH, "AUTOAUTH"},
+ {INFF_E_EAPOL_MSG, "EAPOL_MSG"},
+ {INFF_E_SCAN_COMPLETE, "SCAN_COMPLETE"},
+ {INFF_E_ADDTS_IND, "ADDTS_IND"},
+ {INFF_E_DELTS_IND, "DELTS_IND"},
+ {INFF_E_BCNSENT_IND, "BCNSENT_IND"},
+ {INFF_E_BCNRX_MSG, "BCNRX_MSG"},
+ {INFF_E_BCNLOST_MSG, "BCNLOST_MSG"},
+ {INFF_E_ROAM_PREP, "ROAM_PREP"},
+ {INFF_E_RESET_COMPLETE, "RESET_COMPLETE"},
+ {INFF_E_JOIN_START, "JOIN_START"},
+ {INFF_E_ROAM_START, "ROAM_START"},
+ {INFF_E_ASSOC_START, "ASSOC_START"},
+ {INFF_E_IBSS_ASSOC, "IBSS_ASSOC"},
+ {INFF_E_RADIO, "RADIO"},
+ {INFF_E_PSM_WATCHDOG, "PSM_WATCHDOG"},
+ {INFF_E_PROBREQ_MSG, "PROBREQ_MSG"},
+ {INFF_E_SCAN_CONFIRM_IND, "SCAN_CONFIRM_IND"},
+ {INFF_E_PSK_SUP, "PSK_SUP"},
+ {INFF_E_COUNTRY_CODE_CHANGED, "COUNTRY_CODE_CHANGED"},
+ {INFF_E_EXCEEDED_MEDIUM_TIME, "EXCEEDED_MEDIUM_TIME"},
+ {INFF_E_ICV_ERROR, "ICV_ERROR"},
+ {INFF_E_UNICAST_DECODE_ERROR, "UNICAST_DECODE_ERROR"},
+ {INFF_E_MULTICAST_DECODE_ERROR, "MULTICAST_DECODE_ERROR"},
+ {INFF_E_TRACE, "TRACE"},
+ {INFF_E_IF, "IF"},
+ {INFF_E_RSSI, "RSSI"},
+ {INFF_E_EXTLOG_MSG, "EXTLOG_MSG"},
+ {INFF_E_ACTION_FRAME, "ACTION_FRAME"},
+ {INFF_E_ACTION_FRAME_TX_COMPLETE, "ACTION_FRAME_TX_COMPLETE"},
+ {INFF_E_PRE_ASSOC_IND, "PRE_ASSOC_IND"},
+ {INFF_E_PRE_REASSOC_IND, "PRE_REASSOC_IND"},
+ {INFF_E_CHANNEL_ADOPTED, "CHANNEL_ADOPTED"},
+ {INFF_E_AP_STARTED, "AP_STARTED"},
+ {INFF_E_DFS_AP_STOP, "DFS_AP_STOP"},
+ {INFF_E_DFS_AP_RESUME, "DFS_AP_RESUME"},
+ {INFF_E_ESCAN_RESULT, "ESCAN_RESULT"},
+ {INFF_E_ACTION_FRAME_OFF_CHAN_COMPLETE, "ACTION_FRAME_OFF_CHAN_COMPLETE"},
+ {INFF_E_PROBERESP_MSG, "PROBERESP_MSG"},
+ {INFF_E_PROBEREQ_MSG, "PROBEREQ_MSG"},
+ {INFF_E_DCS_REQUEST, "DCS_REQUEST"},
+ {INFF_E_FIFO_CREDIT_MAP, "FIFO_CREDIT_MAP"},
+ {INFF_E_ACTION_FRAME_RX_COMPLETE, "ACTION_FRAME_RX_COMPLETE"},
+ {INFF_E_SA_COMPLETE_IND, "SA_COMPLETE_IND"},
+ {INFF_E_ASSOC_REQ_IE, "ASSOC_REQ_IE"},
+ {INFF_E_ASSOC_RESP_IE, "ASSOC_RESP_IE"},
+ {INFF_E_BCMC_CREDIT_SUPPORT, "BCMC_CREDIT_SUPPORT"},
+ {INFF_E_EXT_AUTH_REQ, "EXT_AUTH_REQ"},
+ {INFF_E_EXT_AUTH_FRAME_RX, "EXT_AUTH_FRAME_RX"},
+ {INFF_E_MGMT_FRAME_TXSTATUS, "MGMT_FRAME_TXSTATUS"},
+ {INFF_E_MGMT_FRAME_OFF_CHAN_COMPLETE, "MGMT_FRAME_OFF_CHAN_COMPLETE"},
+ {INFF_E_EXT_ASSOC_FRAME_RX, "EXT_ASSOC_FRAME_RX"},
+};
+
+/**
+ * inff_fwevt_name() - returns name for given event code.
+ *
+ * @code: code to lookup.
+ */
+const char *
+inff_fwevt_name(enum inff_fwevt_code code)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(fwevt_names); i++) {
+ if (fwevt_names[i].code == code)
+ return fwevt_names[i].name;
+ }
+ return "unknown";
+}
+#else
+const char *
+inff_fwevt_name(enum inff_fwevt_code code)
+{
+ return "nodebug";
+}
+#endif
+
+/**
+ * inff_fwevt_queue_event() - create and queue event.
+ *
+ * @fwevt: firmware event handling info.
+ * @event: event queue entry.
+ */
+static void
+inff_fwevt_queue_event(struct inff_fwevt_info *fwevt,
+ struct inff_fwevt_queue_item *event)
+{
+ ulong flags;
+
+ spin_lock_irqsave(&fwevt->evt_q_lock, flags);
+ list_add_tail(&event->q, &fwevt->event_q);
+ spin_unlock_irqrestore(&fwevt->evt_q_lock, flags);
+ schedule_work(&fwevt->event_work);
+}
+
+/**
+ * inff_fwevt_dequeue_event() - get event from the queue.
+ *
+ * @fwevt: firmware event handling info.
+ */
+static struct inff_fwevt_queue_item *
+inff_fwevt_dequeue_event(struct inff_fwevt_info *fwevt)
+{
+ struct inff_fwevt_queue_item *event = NULL;
+ ulong flags;
+
+ spin_lock_irqsave(&fwevt->evt_q_lock, flags);
+ if (!list_empty(&fwevt->event_q)) {
+ event = list_first_entry(&fwevt->event_q,
+ struct inff_fwevt_queue_item, q);
+ list_del(&event->q);
+ }
+ spin_unlock_irqrestore(&fwevt->evt_q_lock, flags);
+
+ return event;
+}
+
+/**
+ * inff_fwevt_event_worker() - firmware event worker.
+ *
+ * @work: worker object.
+ */
+static void
+inff_fwevt_event_worker(struct work_struct *work)
+{
+ struct inff_pub *drvr;
+ struct inff_if *ifp;
+ struct inff_fwevt_info *fwevt;
+ struct inff_fwevt_queue_item *event;
+ struct inff_event_msg_be *emsg_be;
+ struct inff_event_msg emsg;
+
+ fwevt = container_of(work, struct inff_fwevt_info, event_work);
+ drvr = container_of(fwevt, struct inff_pub, fwevt);
+
+ while ((event = inff_fwevt_dequeue_event(fwevt))) {
+ inff_dbg(DEVEVT, "event %s (%u) ifidx %u bsscfg %u addr %pM\n",
+ inff_fwevt_name(event->code), event->code,
+ event->emsg.ifidx, event->emsg.bsscfgidx,
+ event->emsg.addr);
+ if (event->emsg.bsscfgidx >= INFF_MAX_IFS) {
+ iphy_err(drvr, "invalid bsscfg index: %u\n", event->emsg.bsscfgidx);
+ goto event_free;
+ }
+
+ /* convert event message */
+ emsg_be = &event->emsg;
+ emsg.version = be16_to_cpu(emsg_be->version);
+ emsg.flags = be16_to_cpu(emsg_be->flags);
+ emsg.event_code = event->code;
+ emsg.status = be32_to_cpu(emsg_be->status);
+ emsg.reason = be32_to_cpu(emsg_be->reason);
+ emsg.auth_type = be32_to_cpu(emsg_be->auth_type);
+ emsg.datalen = be32_to_cpu(emsg_be->datalen);
+ memcpy(emsg.addr, emsg_be->addr, ETH_ALEN);
+ memcpy(emsg.ifname, emsg_be->ifname, sizeof(emsg.ifname));
+ emsg.ifidx = emsg_be->ifidx;
+ emsg.bsscfgidx = emsg_be->bsscfgidx;
+
+ inff_dbg(DEVEVT, " version %u flags %u status %u reason %u\n",
+ emsg.version, emsg.flags, emsg.status, emsg.reason);
+ inff_dbg_hex_dump(INFF_MSGLVL_ON(DEVEVT), event->data,
+ min_t(u32, emsg.datalen, 64),
+ "event payload, len=%d\n", emsg.datalen);
+
+ ifp = drvr->iflist[emsg.bsscfgidx];
+ if (!ifp)
+ ifp = inff_get_ifp(drvr, 0);
+
+ /* Call the registered event handler */
+ if (fwevt->evt_handler[event->code]) {
+ if (fwevt->evt_handler[event->code](ifp, &emsg, event->data))
+ iphy_err(drvr, "event handler failed (%d)\n",
+ event->code);
+ } else {
+ iphy_err(drvr, "unhandled event %d ignored\n", event->code);
+ }
+event_free:
+ kfree(event);
+ }
+}
+
+/**
+ * inff_fwevt_attach() - initialize firmware event handling.
+ *
+ * @drvr: driver information object.
+ */
+void
+inff_fwevt_attach(struct inff_pub *drvr)
+{
+ struct inff_fwevt_info *fwevt = &drvr->fwevt;
+
+ INIT_WORK(&fwevt->event_work, inff_fwevt_event_worker);
+ spin_lock_init(&fwevt->evt_q_lock);
+ INIT_LIST_HEAD(&fwevt->event_q);
+}
+
+/**
+ * inff_fwevt_detach() - cleanup firmware event handling.
+ *
+ * @drvr: driver information object.
+ */
+void
+inff_fwevt_detach(struct inff_pub *drvr)
+{
+ struct inff_fwevt_info *fwevt = &drvr->fwevt;
+
+ /* cancel the worker if initialized */
+ if (fwevt->event_work.func) {
+ cancel_work_sync(&fwevt->event_work);
+ WARN_ON(!list_empty(&fwevt->event_q));
+ memset(fwevt->evt_handler, 0, sizeof(fwevt->evt_handler));
+ }
+}
+
+/**
+ * inff_fwevt_register() - register handler for given event code.
+ *
+ * @drvr: driver information object.
+ * @code: event code.
+ * @handler: handler for the given event code.
+ */
+int
+inff_fwevt_register(struct inff_pub *drvr, enum inff_fwevt_code code,
+ inff_fwevt_handler_t handler)
+{
+ if (drvr->fwevt.evt_handler[code]) {
+ iphy_err(drvr, "event code %d already registered\n", code);
+ return -ENOSPC;
+ }
+ drvr->fwevt.evt_handler[code] = handler;
+ inff_dbg(TRACE, "event handler registered for %s\n",
+ inff_fwevt_name(code));
+ return 0;
+}
+
+/**
+ * inff_fwevt_unregister() - remove handler for given code.
+ *
+ * @drvr: driver information object.
+ * @code: event code.
+ */
+void
+inff_fwevt_unregister(struct inff_pub *drvr,
+ enum inff_fwevt_code code)
+{
+ inff_dbg(TRACE, "event handler cleared for %s\n",
+ inff_fwevt_name(code));
+ drvr->fwevt.evt_handler[code] = NULL;
+}
+
+/**
+ * inff_fwevt_activate_events() - enables firmware events registered.
+ *
+ * @ifp: primary interface object.
+ */
+int
+inff_fwevt_activate_events(struct inff_if *ifp)
+{
+ struct inff_pub *drvr = ifp->drvr;
+ int i, err;
+ struct eventmsgs_ext *eventmask_msg;
+ u32 msglen;
+
+ msglen = EVENTMSGS_EXT_STRUCT_SIZE + INFF_EVENTING_MASK_LEN;
+ eventmask_msg = kzalloc(msglen, GFP_KERNEL);
+ if (!eventmask_msg)
+ return -ENOMEM;
+
+ for (i = 0; i < INFF_E_LAST; i++) {
+ if (ifp->drvr->fwevt.evt_handler[i]) {
+ inff_dbg(DEVEVT, "enable event %s\n",
+ inff_fwevt_name(i));
+ setbit(eventmask_msg->mask, i);
+ }
+ }
+
+ /* want to handle IF event as well */
+ inff_dbg(DEVEVT, "enable event IF\n");
+ setbit(eventmask_msg->mask, INFF_E_IF);
+
+ eventmask_msg->ver = EVENTMSGS_VER;
+ eventmask_msg->command = EVENTMSGS_SET_MASK;
+ eventmask_msg->len = INFF_EVENTING_MASK_LEN;
+
+ err = inff_fwcmd_iovar_data_set(ifp, "event_msgs_ext", eventmask_msg,
+ msglen);
+ if (!err)
+ goto end;
+
+ err = inff_fwcmd_iovar_data_set(ifp, "event_msgs", eventmask_msg->mask,
+ INFF_EVENTING_MASK_LEN);
+ if (err)
+ iphy_err(drvr, "Set event_msgs error (%d)\n", err);
+
+end:
+ kfree(eventmask_msg);
+ return err;
+}
+
+/**
+ * inff_fwevt_process_event() - process skb as firmware event.
+ *
+ * @drvr: driver information object.
+ * @event_packet: event packet to process.
+ * @packet_len: length of the packet
+ * @gfp: memory allocation flags.
+ *
+ * If the packet buffer contains a firmware event message it will
+ * dispatch the event to a registered handler (using worker).
+ */
+void
+inff_fwevt_process_event(struct inff_pub *drvr,
+ struct inff_event *event_packet,
+ u32 packet_len, gfp_t gfp)
+{
+ enum inff_fwevt_code code;
+ struct inff_fwevt_info *fwevt = &drvr->fwevt;
+ struct inff_fwevt_queue_item *event;
+ void *data;
+ u32 datalen;
+
+ /* get event info */
+ code = get_unaligned_be32(&event_packet->msg.event_type);
+ datalen = get_unaligned_be32(&event_packet->msg.datalen);
+ data = &event_packet[1];
+
+ if (code >= INFF_E_LAST)
+ return;
+
+ if (code != INFF_E_IF && !fwevt->evt_handler[code])
+ return;
+
+ if (datalen > INFF_DCMD_MAXLEN ||
+ datalen + sizeof(*event_packet) > packet_len)
+ return;
+
+ event = kzalloc_flex(*event, data, datalen, gfp);
+ if (!event)
+ return;
+
+ event->datalen = datalen;
+ event->code = code;
+ event->ifidx = event_packet->msg.ifidx;
+
+ /* use memcpy to get aligned event message */
+ memcpy(&event->emsg, &event_packet->msg, sizeof(event->emsg));
+ memcpy(event->data, data, datalen);
+ memcpy(event->ifaddr, event_packet->eth.h_dest, ETH_ALEN);
+
+ inff_fwevt_queue_event(fwevt, event);
+}
+
+void
+inff_fwevt_process_skb(struct inff_pub *drvr, struct sk_buff *skb, u16 stype,
+ gfp_t gfp)
+{
+ struct inff_event *event_packet;
+ u16 subtype, usr_stype;
+
+ /* only process events when protocol matches */
+ if (skb->protocol != cpu_to_be16(ETH_P_LINK_CTL))
+ return;
+
+ if ((skb->len + ETH_HLEN) < sizeof(*event_packet))
+ return;
+
+ event_packet = (struct inff_event *)skb_mac_header(skb);
+
+ /* check subtype if needed */
+ if (unlikely(stype)) {
+ subtype = get_unaligned_be16(&event_packet->hdr.subtype);
+ if (subtype != stype)
+ return;
+ }
+
+ if (memcmp(INF_OUI, &event_packet->hdr.oui[0],
+ sizeof(event_packet->hdr.oui)))
+ return;
+
+ /* final match on usr_subtype */
+ usr_stype = get_unaligned_be16(&event_packet->hdr.usr_subtype);
+ if (usr_stype != INFILCP_INF_SUBTYPE_EVENT)
+ return;
+
+ inff_fwevt_process_event(drvr, event_packet, skb->len + ETH_HLEN, gfp);
+}
+
+static s32
+inff_notify_vif_event(struct inff_if *ifp, const struct inff_event_msg *e, void *data)
+{
+ struct inff_pub *drvr = ifp->drvr;
+ struct inff_cfg80211_info *cfg = drvr->config;
+ struct inff_if_event *ifevent = (struct inff_if_event *)data;
+ struct inff_cfg80211_vif_event *event = &cfg->vif_event;
+ struct inff_cfg80211_vif *vif;
+ enum nl80211_iftype iftype = NL80211_IFTYPE_UNSPECIFIED;
+ int err = 0;
+
+ inff_dbg(DEVEVT, "%s (%d) action: %u ifidx: %u bsscfgidx: %u flags: %u role: %u\n",
+ inff_fwevt_name(e->event_code), e->event_code,
+ ifevent->action, ifevent->ifidx, ifevent->bsscfgidx,
+ ifevent->flags, ifevent->role);
+
+ if (ifevent->ifidx >= INFF_MAX_IFS) {
+ iphy_err(drvr, "Invalid interface index: %u\n", ifevent->ifidx);
+ return -EINVAL;
+ }
+
+ spin_lock(&event->vif_event_lock);
+ event->action = ifevent->action;
+ vif = event->vif;
+
+ switch (ifevent->action) {
+ case INFF_E_IF_ADD:
+ inff_dbg(DEVEVT, "adding %s (%pM) interface\n", e->ifname,
+ e->addr);
+
+ switch (ifevent->role) {
+ case INFF_E_IF_ROLE_STA:
+ iftype = NL80211_IFTYPE_STATION;
+ break;
+ case INFF_E_IF_ROLE_AP:
+ iftype = NL80211_IFTYPE_AP;
+ break;
+ default:
+ iphy_err(drvr, "Invalid interface role: %d\n", ifevent->role);
+ spin_unlock(&event->vif_event_lock);
+ return -EINVAL;
+ }
+
+ if (ifevent->bsscfgidx >= INFF_MAX_IFS) {
+ iphy_err(drvr, "invalid bsscfg index: %u\n",
+ ifevent->bsscfgidx);
+ spin_unlock(&event->vif_event_lock);
+ return -EINVAL;
+ }
+
+ ifp = drvr->iflist[ifevent->bsscfgidx];
+ if (ifp) {
+ if (ifevent->bsscfgidx == 0) {
+ inff_dbg(INFO,
+ "ignore IF ADD event for existing primary interface %s\n",
+ ifp->ndev->name);
+ spin_unlock(&event->vif_event_lock);
+ return 0;
+ }
+
+ /*
+ * Remove the corresponding existing interface, to add it again,
+ * incase if its INFF_E_IF_DEL event was missed earlier.
+ */
+ iphy_err(drvr,
+ "netdev:%s already exists, removing to add it again\n",
+ ifp->ndev->name);
+ inff_net_del_if(drvr, ifp, false);
+ }
+
+ ifp = inff_net_add_if(drvr, ifevent->bsscfgidx, ifevent->ifidx,
+ e->ifname, e->addr, iftype);
+ if (IS_ERR_OR_NULL(ifp)) {
+ iphy_err(drvr, "interface (role: %d) addition error (%ld)\n",
+ ifevent->role, PTR_ERR(ifp));
+ spin_unlock(&event->vif_event_lock);
+ return PTR_ERR(ifp);
+ }
+
+ if (!vif) {
+ spin_unlock(&event->vif_event_lock);
+ /* Interface Add event may not be upon user request */
+ vif = inff_alloc_vif(cfg, iftype);
+ if (IS_ERR(vif)) {
+ inff_net_del_if(drvr, ifp, false);
+ return PTR_ERR(vif);
+ }
+ }
+
+ ifp->vif = vif;
+ vif->ifp = ifp;
+
+ if (ifp->ndev) {
+ vif->wdev.netdev = ifp->ndev;
+ ifp->ndev->ieee80211_ptr = &vif->wdev;
+ SET_NETDEV_DEV(ifp->ndev, wiphy_dev(cfg->wiphy));
+ }
+
+ inff_proto_add_if(drvr, ifp);
+
+ spin_unlock(&event->vif_event_lock);
+ if (inff_vif_event_armed(cfg)) {
+ wake_up(&event->vif_wq);
+ break;
+ }
+
+ if (ifp->ndev) {
+ /* Interface Add event may not be upon user request */
+ err = inff_net_attach(ifp, false);
+ if (err) {
+ inff_err("netdevice register failed with err:%d\n",
+ err);
+ inff_net_del_if(drvr, ifp, false);
+ }
+ }
+
+ break;
+ case INFF_E_IF_DEL:
+ inff_dbg(DEVEVT, "deleting %s (%pM) interface\n", e->ifname,
+ e->addr);
+
+ spin_unlock(&event->vif_event_lock);
+ if (inff_vif_event_armed(cfg)) {
+ wake_up(&event->vif_wq);
+ break;
+ }
+
+ /* Interface Delete event may not be upon user request */
+ if (!ifp || ifp->bsscfgidx != ifevent->bsscfgidx)
+ break;
+
+ inff_net_del_if(drvr, ifp, false);
+
+ break;
+ case INFF_E_IF_CHANGE:
+ inff_dbg(DEVEVT, "changing %s (%pM) interface\n", e->ifname,
+ e->addr);
+
+ inff_proto_reset_if(drvr, ifp);
+
+ spin_unlock(&event->vif_event_lock);
+ if (inff_vif_event_armed(cfg))
+ wake_up(&event->vif_wq);
+
+ break;
+ default:
+ err = -EINVAL;
+ spin_unlock(&event->vif_event_lock);
+ break;
+ }
+
+ return err;
+}
+
+void inff_fwevt_register_handlers(struct inff_cfg80211_info *cfg)
+{
+ inff_fwevt_register(cfg->pub, INFF_E_IF,
+ inff_notify_vif_event);
+}
+
+/*
+ * inff_cpevt_dequeue_event() - get event from the queue.
+ *
+ * @evi: event handling info.
+ * @event_q: event queue.
+ */
+static struct inff_cpevt_queue_item *
+inff_cpevt_dequeue_event(struct inff_cpevt_info *evi, struct list_head *event_q)
+{
+ struct inff_cpevt_queue_item *event = NULL;
+ ulong flags;
+
+ spin_lock_irqsave(&evi->lock, flags);
+ if (!list_empty(event_q)) {
+ event = list_first_entry(event_q,
+ struct inff_cpevt_queue_item, list);
+ list_del(&event->list);
+ }
+ spin_unlock_irqrestore(&evi->lock, flags);
+
+ return event;
+}
+
+/*
+ * inff_cpevt_queue_event() - queue event.
+ * @evi: event handling info.
+ * @event: event item.
+ * @event_q: event queue.
+ */
+static void
+inff_cpevt_queue_event(struct inff_cpevt_info *evi,
+ struct inff_cpevt_queue_item *event,
+ struct list_head *event_q)
+{
+ ulong flags;
+
+ spin_lock_irqsave(&evi->lock, flags);
+ list_add_tail(&event->list, event_q);
+ spin_unlock_irqrestore(&evi->lock, flags);
+}
+
+static int
+inff_cpevt_alloc_buf(struct inff_cpevt_info *evi)
+{
+ struct inff_cpevt_queue_item *event_item;
+ u8 i;
+
+ INIT_LIST_HEAD(&evi->idle_q);
+ INIT_LIST_HEAD(&evi->wait_q);
+ INIT_LIST_HEAD(&evi->event_q);
+ INIT_LIST_HEAD(&evi->monitor_q);
+ spin_lock_init(&evi->lock);
+
+ for (i = 0; i < EVENT_MAX_RX_BUF; i++) {
+ event_item = kzalloc_obj(*event_item);
+ if (!event_item)
+ return -ENODATA;
+
+ INIT_LIST_HEAD(&event_item->list);
+ memset(&event_item->event, 0, sizeof(event_item->event));
+
+ spin_lock(&evi->lock);
+ list_add_tail(&event_item->list, &evi->idle_q);
+ spin_unlock(&evi->lock);
+ }
+
+ return 0;
+}
+
+static void
+inff_cpevt_free_buf(struct list_head *q, const char name[])
+{
+ struct inff_cpevt_queue_item *event_item;
+ struct inff_cpevt_queue_item *next = NULL;
+ u32 count = 0;
+
+ list_for_each_entry_safe(event_item, next, q, list) {
+ list_del(&event_item->list);
+ kfree(event_item);
+ count++;
+ }
+ inff_dbg(INFO, "%s Q: free pkt count %d\n", name, count);
+}
+
+static int
+inff_notify_cpevt_scan(struct inff_pub *drvr, struct inff_cpevt *event)
+{
+ struct inff_cpevt_scan *scan_ev = NULL;
+
+ if (event->version != INFF_CP_SCAN_EVENT_VER) {
+ inff_err("version (%d) != %d\n", event->version, INFF_CP_SCAN_EVENT_VER);
+ return -ENODATA;
+ }
+
+ if (event->len != sizeof(*scan_ev)) {
+ inff_err("len %d != scan_event %ld\n", event->len, sizeof(*scan_ev));
+ return -ENODATA;
+ }
+
+ scan_ev = (struct inff_cpevt_scan *)event->data;
+ return inff_cp_scan_event_handler(drvr, scan_ev);
+}
+
+static int
+inff_cpevt_handler(struct inff_pub *drvr, struct inff_cpevt *e)
+{
+ int ret = 0;
+
+ switch (e->type) {
+ case INFF_CP_E_SCAN:
+ ret = inff_notify_cpevt_scan(drvr, e);
+ break;
+ default:
+ inff_err("unsupported event: %d\n", e->type);
+ /* driver bypass this event, keep forward to user */
+ break;
+ }
+
+ if (ret)
+ inff_err("handle event code (%d) failed\n", e->type);
+ return ret;
+}
+
+/*
+ * inff_cpevt_worker() - event worker.
+ *
+ * @work: worker object.
+ */
+static void
+inff_cpevt_worker(struct work_struct *work)
+{
+ struct inff_pub *drvr;
+ struct inff_cpevt_info *evi;
+ struct inff_cpevt_queue_item *event_item = NULL;
+
+ evi = container_of(work, struct inff_cpevt_info, event_work);
+ drvr = container_of(evi, struct inff_pub, evi);
+
+ while ((event_item = inff_cpevt_dequeue_event(evi, &evi->wait_q))) {
+ inff_cpevt_handler(drvr, &event_item->event);
+ inff_cpevt_queue_event(evi, event_item, &evi->idle_q);
+ }
+}
+
+/*
+ * inff_cpevt_attach() - initialize event handling.
+ * @drvr: driver information object.
+ */
+int
+inff_cpevt_attach(struct inff_pub *drvr)
+{
+ struct inff_cpevt_info *evi = &drvr->evi;
+
+ if (inff_cpevt_alloc_buf(evi)) {
+ inff_err("Failed to alloc event buffer\n");
+ return -ENOMEM;
+ }
+
+ INIT_WORK(&evi->event_work, inff_cpevt_worker);
+
+ return 0;
+}
+
+/*
+ * inff_cpevt_detach() - cleanup event handling.
+ * @drvr: driver information object.
+ */
+void
+inff_cpevt_detach(struct inff_pub *drvr)
+{
+ struct inff_cpevt_info *evi = &drvr->evi;
+
+ /* cancel the worker if initialized */
+ if (evi->event_work.func)
+ cancel_work_sync(&evi->event_work);
+
+ spin_lock(&evi->lock);
+
+ inff_cpevt_free_buf(&evi->idle_q, "idle");
+ inff_cpevt_free_buf(&evi->wait_q, "wait");
+ inff_cpevt_free_buf(&evi->event_q, "event");
+ inff_cpevt_free_buf(&evi->monitor_q, "monitor");
+
+ spin_unlock(&evi->lock);
+}
+
+/*
+ * inff_cpevt_process_skb() - process skb as event.
+ * @drvr: driver information object.
+ * @pkt: event packet to process.
+ * @inirq: in irq.
+ * Dispatch the event packet to worker.
+ */
+void
+inff_cpevt_process_skb(struct inff_pub *drvr, struct sk_buff *pkt)
+{
+ struct inff_cpevt_info *evi = &drvr->evi;
+ struct inff_cpevt_queue_item *event_item;
+
+ if (pkt->len > sizeof(struct inff_cpevt)) {
+ inff_err("event len (%d) > %ld\n", pkt->len, sizeof(struct inff_cpevt));
+ return;
+ }
+
+ event_item = inff_cpevt_dequeue_event(evi, &evi->idle_q);
+ if (!event_item) {
+ inff_err("no buffer in idle q\n");
+ return;
+ }
+
+ memset(&event_item->event, 0, sizeof(event_item->event));
+ memcpy(&event_item->event, pkt->data, pkt->len);
+
+ inff_dbg_hex_dump(INFF_MSGLVL_ON(DEVEVT), pkt->data,
+ min_t(u32, pkt->len, 64),
+ "event payload, len=%d\n", pkt->len);
+
+ inff_cpevt_queue_event(evi, event_item, &evi->wait_q);
+
+ schedule_work(&evi->event_work);
+}
diff --git a/drivers/net/wireless/infineon/inffmac/dev_evt.h b/drivers/net/wireless/infineon/inffmac/dev_evt.h
new file mode 100644
index 000000000000..cc9d4384b1b8
--- /dev/null
+++ b/drivers/net/wireless/infineon/inffmac/dev_evt.h
@@ -0,0 +1,447 @@
+/* SPDX-License-Identifier: ISC */
+/*
+ * Copyright (c) 2012 Broadcom Corporation
+ *
+ * Copyright (c) 2025-2026, Infineon Technologies AG, or an affiliate of Infineon Technologies AG.
+ * All rights reserved.
+ */
+
+#ifndef INFF_DEV_EVT_H
+#define INFF_DEV_EVT_H
+
+#include <linux/unaligned.h>
+#include <linux/skbuff.h>
+#include <linux/if_ether.h>
+#include <linux/if.h>
+
+#define INF_OUI "\x00\x03\x19"
+
+/* formward declarations */
+struct inff_pub;
+struct inff_if;
+struct inff_cfg80211_info;
+
+/* list of firmware events */
+/* firmware event codes sent by the dongle */
+enum inff_fwevt_code {
+ INFF_E_SET_SSID = 0,
+ INFF_E_JOIN = 1,
+ INFF_E_START = 2,
+ INFF_E_AUTH = 3,
+ INFF_E_AUTH_IND = 4,
+ INFF_E_DEAUTH = 5,
+ INFF_E_DEAUTH_IND = 6,
+ INFF_E_ASSOC = 7,
+ INFF_E_ASSOC_IND = 8,
+ INFF_E_REASSOC = 9,
+ INFF_E_REASSOC_IND = 10,
+ INFF_E_DISASSOC = 11,
+ INFF_E_DISASSOC_IND = 12,
+ INFF_E_QUIET_START = 13,
+ INFF_E_QUIET_END = 14,
+ INFF_E_BEACON_RX = 15,
+ INFF_E_LINK = 16,
+ INFF_E_MIC_ERROR = 17,
+ INFF_E_NDIS_LINK = 18,
+ INFF_E_ROAM = 19,
+ INFF_E_TXFAIL = 20,
+ INFF_E_PMKID_CACHE = 21,
+ INFF_E_RETROGRADE_TSF = 22,
+ INFF_E_PRUNE = 23,
+ INFF_E_AUTOAUTH = 24,
+ INFF_E_EAPOL_MSG = 25,
+ INFF_E_SCAN_COMPLETE = 26,
+ INFF_E_ADDTS_IND = 27,
+ INFF_E_DELTS_IND = 28,
+ INFF_E_BCNSENT_IND = 29,
+ INFF_E_BCNRX_MSG = 30,
+ INFF_E_BCNLOST_MSG = 31,
+ INFF_E_ROAM_PREP = 32,
+ INFF_E_RESET_COMPLETE = 35,
+ INFF_E_JOIN_START = 36,
+ INFF_E_ROAM_START = 37,
+ INFF_E_ASSOC_START = 38,
+ INFF_E_IBSS_ASSOC = 39,
+ INFF_E_RADIO = 40,
+ INFF_E_PSM_WATCHDOG = 41,
+ INFF_E_PROBREQ_MSG = 44,
+ INFF_E_SCAN_CONFIRM_IND = 45,
+ INFF_E_PSK_SUP = 46,
+ INFF_E_COUNTRY_CODE_CHANGED = 47,
+ INFF_E_EXCEEDED_MEDIUM_TIME = 48,
+ INFF_E_ICV_ERROR = 49,
+ INFF_E_UNICAST_DECODE_ERROR = 50,
+ INFF_E_MULTICAST_DECODE_ERROR = 51,
+ INFF_E_TRACE = 52,
+ INFF_E_IF = 54,
+ INFF_E_RSSI = 56,
+ INFF_E_EXTLOG_MSG = 58,
+ INFF_E_ACTION_FRAME = 59,
+ INFF_E_ACTION_FRAME_TX_COMPLETE = 60,
+ INFF_E_PRE_ASSOC_IND = 61,
+ INFF_E_PRE_REASSOC_IND = 62,
+ INFF_E_CHANNEL_ADOPTED = 63,
+ INFF_E_AP_STARTED = 64,
+ INFF_E_DFS_AP_STOP = 65,
+ INFF_E_DFS_AP_RESUME = 66,
+ INFF_E_ESCAN_RESULT = 69,
+ INFF_E_ACTION_FRAME_OFF_CHAN_COMPLETE = 70,
+ INFF_E_PROBERESP_MSG = 71,
+ INFF_E_PROBEREQ_MSG = 72,
+ INFF_E_DCS_REQUEST = 73,
+ INFF_E_FIFO_CREDIT_MAP = 74,
+ INFF_E_ACTION_FRAME_RX_COMPLETE = 75,
+ INFF_E_SA_COMPLETE_IND = 80,
+ INFF_E_ASSOC_REQ_IE = 87,
+ INFF_E_ASSOC_RESP_IE = 88,
+ INFF_E_BCMC_CREDIT_SUPPORT = 127,
+ INFF_E_EXT_AUTH_REQ = 187,
+ INFF_E_EXT_AUTH_FRAME_RX = 188,
+ INFF_E_MGMT_FRAME_TXSTATUS = 189,
+ INFF_E_MGMT_FRAME_OFF_CHAN_COMPLETE = 190,
+ INFF_E_EXT_ASSOC_FRAME_RX = 196,
+ /* this determines event mask length which must match
+ * minimum length check in device firmware so it is
+ * hard-coded here.
+ */
+ INFF_E_LAST = 203
+};
+
+/* list of CP events */
+/* CP event codes sent by the dongle */
+enum inff_cpevt_code {
+ INFF_CP_E_NW = 0,
+ INFF_CP_E_SCAN = 1
+};
+
+#define INFF_EVENTING_MASK_LEN DIV_ROUND_UP(INFF_E_LAST, 8)
+
+/* flags field values in struct inff_event_msg */
+#define INFF_EVENT_MSG_LINK 0x01
+#define INFF_EVENT_MSG_FLUSHTXQ 0x02
+#define INFF_EVENT_MSG_GROUP 0x04
+
+/* status field values in struct inff_event_msg */
+#define INFF_E_STATUS_SUCCESS 0
+#define INFF_E_STATUS_FAIL 1
+#define INFF_E_STATUS_TIMEOUT 2
+#define INFF_E_STATUS_NO_NETWORKS 3
+#define INFF_E_STATUS_ABORT 4
+#define INFF_E_STATUS_NO_ACK 5
+#define INFF_E_STATUS_UNSOLICITED 6
+#define INFF_E_STATUS_ATTEMPT 7
+#define INFF_E_STATUS_PARTIAL 8
+#define INFF_E_STATUS_NEWSCAN 9
+#define INFF_E_STATUS_NEWASSOC 10
+#define INFF_E_STATUS_11HQUIET 11
+#define INFF_E_STATUS_SUPPRESS 12
+#define INFF_E_STATUS_NOCHANS 13
+#define INFF_E_STATUS_CS_ABORT 15
+#define INFF_E_STATUS_ERROR 16
+
+/* status field values for PSK_SUP event */
+#define INFF_E_STATUS_FWSUP_WAIT_M1 4
+#define INFF_E_STATUS_FWSUP_PREP_M2 5
+#define INFF_E_STATUS_FWSUP_COMPLETED 6
+#define INFF_E_STATUS_FWSUP_TIMEOUT 7
+#define INFF_E_STATUS_FWSUP_WAIT_M3 8
+#define INFF_E_STATUS_FWSUP_PREP_M4 9
+#define INFF_E_STATUS_FWSUP_WAIT_G1 10
+#define INFF_E_STATUS_FWSUP_PREP_G2 11
+
+/* reason field values in struct inff_event_msg */
+/* roam reason codes */
+#define INFF_ROAM_E_REASON_INITIAL_ASSOC 0 /* initial assoc */
+#define INFF_ROAM_E_REASON_LOW_RSSI 1 /* roamed due to low RSSI */
+#define INFF_ROAM_E_REASON_DEAUTH 2 /* roamed due to DEAUTH indication */
+#define INFF_ROAM_E_REASON_DISASSOC 3 /* roamed due to DISASSOC indication */
+#define INFF_ROAM_E_REASON_BCNS_LOST 4 /* roamed due to lost beacons */
+#define INFF_ROAM_E_REASON_FAST_ROAM_FAILED 5 /* roamed due to fast roam failure */
+#define INFF_ROAM_E_REASON_DIRECTED_ROAM 6 /* roamed due to request by AP */
+#define INFF_ROAM_E_REASON_TSPEC_REJECTED 7 /* roamed due to TSPEC rejection */
+#define INFF_ROAM_E_REASON_BETTER_AP 8 /* roamed due to finding better AP */
+#define INFF_ROAM_E_REASON_MINTXRATE 9 /* roamed because at mintxrate for too long */
+#define INFF_ROAM_E_REASON_TXFAIL 10 /* We can hear AP, but AP can't hear us */
+#define INFF_ROAM_E_REASON_BSSTRANS_REQ 11 /* roamed due to BSS Transition request by AP */
+#define INFF_ROAM_E_REASON_LOW_RSSI_CU 12 /* roamed due to low RSSI and Channel Usage */
+#define INFF_ROAM_E_REASON_RADAR_DETECTED 13 /* roamed due to radar detection by STA */
+#define INFF_ROAM_E_REASON_CSA 14 /* roamed due to CSA from AP */
+#define INFF_ROAM_E_REASON_ESTM_LOW 15 /* roamed due to ESTM low tput */
+#define INFF_ROAM_E_REASON_LAST 16
+
+#define INFF_E_REASON_LINK_BSSCFG_DIS 4
+#define INFF_E_REASON_FAST_ROAM_FAILED 5
+#define INFF_E_REASON_DIRECTED_ROAM 6
+#define INFF_E_REASON_TSPEC_REJECTED 7
+#define INFF_E_REASON_BETTER_AP 8
+
+/* reason field values for PSK_SUP event */
+#define INFF_E_REASON_FWSUP_OTHER 0
+#define INFF_E_REASON_FWSUP_DECRYPT_KEY_DATA 1
+#define INFF_E_REASON_FWSUP_BAD_UCAST_WEP128 2
+#define INFF_E_REASON_FWSUP_BAD_UCAST_WEP40 3
+#define INFF_E_REASON_FWSUP_UNSUP_KEY_LEN 4
+#define INFF_E_REASON_FWSUP_PW_KEY_CIPHER 5
+#define INFF_E_REASON_FWSUP_MSG3_TOO_MANY_IE 6
+#define INFF_E_REASON_FWSUP_MSG3_IE_MISMATCH 7
+#define INFF_E_REASON_FWSUP_NO_INSTALL_FLAG 8
+#define INFF_E_REASON_FWSUP_MSG3_NO_GTK 9
+#define INFF_E_REASON_FWSUP_GRP_KEY_CIPHER 10
+#define INFF_E_REASON_FWSUP_GRP_MSG1_NO_GTK 11
+#define INFF_E_REASON_FWSUP_GTK_DECRYPT_FAIL 12
+#define INFF_E_REASON_FWSUP_SEND_FAIL 13
+#define INFF_E_REASON_FWSUP_DEAUTH 14
+#define INFF_E_REASON_FWSUP_WPA_PSK_TMO 15
+#define INFF_E_REASON_FWSUP_WPA_PSK_M1_TMO 16
+#define INFF_E_REASON_FWSUP_WPA_PSK_M3_TMO 17
+
+/* action field values for inff_ifevent */
+#define INFF_E_IF_ADD 1
+#define INFF_E_IF_DEL 2
+#define INFF_E_IF_CHANGE 3
+
+/* flag field values for inff_ifevent */
+#define INFF_E_IF_FLAG_NOIF 1
+
+/* role field values for inff_ifevent */
+#define INFF_E_IF_ROLE_STA 0
+#define INFF_E_IF_ROLE_AP 1
+
+/**
+ * definitions for event packet validation.
+ */
+#define INFILCP_INF_SUBTYPE_EVENT 1
+#define INFILCP_SUBTYPE_VENDOR_LONG 32769
+
+/**
+ * struct inff_ethhdr - Infineon specific ether header.
+ *
+ * @subtype: subtype for this packet.
+ * @length: length of appended data.
+ * @version: version indication.
+ * @oui: OUI of this packet.
+ * @usr_subtype: subtype for this OUI.
+ */
+struct inff_ethhdr {
+ __be16 subtype;
+ __be16 length;
+ u8 version;
+ u8 oui[3];
+ __be16 usr_subtype;
+} __packed;
+
+struct inff_event_msg_be {
+ __be16 version;
+ __be16 flags;
+ __be32 event_type;
+ __be32 status;
+ __be32 reason;
+ __be32 auth_type;
+ __be32 datalen;
+ u8 addr[ETH_ALEN];
+ char ifname[IFNAMSIZ];
+ u8 ifidx;
+ u8 bsscfgidx;
+} __packed;
+
+/**
+ * struct inff_event - contents of Infineon event packet.
+ *
+ * @eth: standard ether header.
+ * @hdr: Infineon specific ether header.
+ * @msg: common part of the actual event message.
+ */
+struct inff_event {
+ struct ethhdr eth;
+ struct inff_ethhdr hdr;
+ struct inff_event_msg_be msg;
+} __packed;
+
+#define EVENT_MAX_DATA_LEN 1024
+#define EVENT_MAX_RX_BUF 10
+
+struct inff_cpevt {
+ u16 type;
+ u16 len;
+ u8 version;
+ u8 data[EVENT_MAX_DATA_LEN];
+};
+
+/**
+ * struct inff_event_msg - firmware event message.
+ *
+ * @version: version information.
+ * @flags: event flags.
+ * @event_code: firmware event code.
+ * @status: status information.
+ * @reason: reason code.
+ * @auth_type: authentication type.
+ * @datalen: length of event data buffer.
+ * @addr: ether address.
+ * @ifname: interface name.
+ * @ifidx: interface index.
+ * @bsscfgidx: bsscfg index.
+ */
+struct inff_event_msg {
+ u16 version;
+ u16 flags;
+ u32 event_code;
+ u32 status;
+ u32 reason;
+ s32 auth_type;
+ u32 datalen;
+ u8 addr[ETH_ALEN];
+ char ifname[IFNAMSIZ];
+ u8 ifidx;
+ u8 bsscfgidx;
+};
+
+struct inff_if_event {
+ u8 ifidx;
+ u8 action;
+ u8 flags;
+ u8 bsscfgidx;
+ u8 role;
+};
+
+enum event_msgs_ext_command {
+ EVENTMSGS_NONE = 0,
+ EVENTMSGS_SET_BIT = 1,
+ EVENTMSGS_RESET_BIT = 2,
+ EVENTMSGS_SET_MASK = 3
+};
+
+#define EVENTMSGS_VER 1
+#define EVENTMSGS_EXT_STRUCT_SIZE offsetof(struct eventmsgs_ext, mask[0])
+
+/* len- for SET it would be mask size from the application to the firmware */
+/* for GET it would be actual firmware mask size */
+/* maxgetsize - is only used for GET. indicate max mask size that the */
+/* application can read from the firmware */
+struct eventmsgs_ext {
+ u8 ver;
+ u8 command;
+ u8 len;
+ u8 maxgetsize;
+ u8 mask[];
+};
+
+struct inff_fwevt_roam_reason_name {
+ u32 reason;
+ char *reason_name;
+};
+
+typedef int (*inff_fwevt_handler_t)(struct inff_if *ifp,
+ const struct inff_event_msg *evtmsg,
+ void *data);
+
+/**
+ * struct inff_fwevt_info - firmware event handling information.
+ *
+ * @event_work: event worker.
+ * @evt_q_lock: lock for event queue protection.
+ * @event_q: event queue.
+ * @evt_handler: registered event handlers.
+ */
+struct inff_fwevt_info {
+ struct work_struct event_work;
+ spinlock_t evt_q_lock; /* lock for event queue protection */
+ struct list_head event_q;
+
+ int (*evt_handler[INFF_E_LAST])(struct inff_if *ifp,
+ const struct inff_event_msg *evtmsg,
+ void *data);
+};
+
+/*
+ * struct inff_cpevt_queue_item - event item on event queue.
+ *
+ * @list: list element for queuing.
+ * @event: event specific data part of the event.
+ */
+struct inff_cpevt_queue_item {
+ struct list_head list;
+ struct inff_cpevt event;
+};
+
+/*
+ * struct inff_cpevt_info - event handling information.
+ *
+ * @event_work: event worker.
+ * @event_q_lock: lock for event queue protection.
+ * @event_q: event queue.
+ */
+struct inff_cpevt_info {
+ struct work_struct event_work;
+ /* lock for event queue protection */
+ spinlock_t lock;
+ struct list_head idle_q;
+ struct list_head wait_q;
+ struct list_head event_q;
+ struct list_head monitor_q;
+};
+
+#define INFF_CP_NW_EVENT_VER 2
+
+/*
+ * struct inff_cpevt_nw - contents of CP network event.
+ */
+struct inff_cpevt_nw {
+ u8 type;
+ u8 if_idx;
+ union {
+ u8 mac[ETH_ALEN];
+ u32 ipv4; /* IPv4 address in network byte order */
+ u32 reason;
+ u32 pad[2];
+ } u;
+ u32 ipv6[4];
+} __packed;
+
+/* Scan Event */
+#define INFF_CP_SCAN_EVENT_VER 2
+#define INFF_CP_SCAN_IE_LEN_MAX 896
+
+/*
+ * struct inff_cpevt_scan - contents of CP scan event.
+ */
+struct inff_cpevt_scan {
+ u8 ssid_length; /* SSID length */
+ char ssid[IEEE80211_MAX_SSID_LEN + 1]; /* SSID of the access point */
+ u32 security_type; /* WiFi security type */
+ u8 macaddr[ETH_ALEN]; /* MAC Address of the access point */
+ s16 signal_strength; /* signal strength */
+ u8 channel; /* channel number */
+ u8 band; /* WiFi band of access point */
+ bool scan_complete; /* WiFi scan complete/incomplete */
+ u16 beacon_period; /* Interval between two consecutive beacon frames. Unit: Kusec */
+ u16 capability; /* Capability information */
+ u32 ie_len; /* Length of the IE. */
+
+ /* Pointer to the received Beacon/Probe Response IE (Information Element). */
+ u8 ie[INFF_CP_SCAN_IE_LEN_MAX];
+} __packed;
+
+const char *inff_fwevt_name(enum inff_fwevt_code code);
+
+void inff_fwevt_attach(struct inff_pub *drvr);
+void inff_fwevt_detach(struct inff_pub *drvr);
+int inff_fwevt_register(struct inff_pub *drvr, enum inff_fwevt_code code,
+ int (*handler)(struct inff_if *ifp,
+ const struct inff_event_msg *evtmsg,
+ void *data));
+void inff_fwevt_unregister(struct inff_pub *drvr,
+ enum inff_fwevt_code code);
+int inff_fwevt_activate_events(struct inff_if *ifp);
+void inff_fwevt_process_event(struct inff_pub *drvr,
+ struct inff_event *event_packet,
+ u32 packet_len, gfp_t gfp);
+void inff_fwevt_process_skb(struct inff_pub *drvr, struct sk_buff *skb, u16 stype,
+ gfp_t gfp);
+void inff_fwevt_register_handlers(struct inff_cfg80211_info *cfg);
+
+int inff_cpevt_attach(struct inff_pub *drvr);
+void inff_cpevt_detach(struct inff_pub *drvr);
+void inff_cpevt_process_skb(struct inff_pub *drvr, struct sk_buff *pkt);
+
+#endif /* INFF_DEV_EVT_H */
--
2.43.0
^ permalink raw reply related [flat|nested] 26+ messages in thread* [PATCH wireless-next v3 11/25] wifi: inffmac: add net.c/h
2026-09-02 16:50 [PATCH wireless-next v3 00/25] wifi: inffmac: introducing a driver for Infineon's new generation chipsets Gokul Sivakumar
` (9 preceding siblings ...)
2026-09-02 16:50 ` [PATCH wireless-next v3 10/25] wifi: inffmac: add dev_evt.c/h Gokul Sivakumar
@ 2026-09-02 16:50 ` Gokul Sivakumar
2026-09-02 16:50 ` [PATCH wireless-next v3 12/25] wifi: inffmac: add cfg80211.c/h Gokul Sivakumar
` (13 subsequent siblings)
24 siblings, 0 replies; 26+ messages in thread
From: Gokul Sivakumar @ 2026-09-02 16:50 UTC (permalink / raw)
To: linux-wireless, johannes
Cc: arend.vanspriel, wlan-kernel-dev-list, gokulkumar.sivakumar
Integrate the driver with kernel network stack for enabling
datapath communication using a netdev for WLAN interfaces.
Signed-off-by: Gokul Sivakumar <gokulkumar.sivakumar@infineon.com>
---
drivers/net/wireless/infineon/inffmac/net.c | 500 ++++++++++++++++++++
drivers/net/wireless/infineon/inffmac/net.h | 43 ++
2 files changed, 543 insertions(+)
create mode 100644 drivers/net/wireless/infineon/inffmac/net.c
create mode 100644 drivers/net/wireless/infineon/inffmac/net.h
diff --git a/drivers/net/wireless/infineon/inffmac/net.c b/drivers/net/wireless/infineon/inffmac/net.c
new file mode 100644
index 000000000000..036bf7ebad8b
--- /dev/null
+++ b/drivers/net/wireless/infineon/inffmac/net.c
@@ -0,0 +1,500 @@
+// SPDX-License-Identifier: ISC
+/*
+ * Copyright (c) 2010 Broadcom Corporation
+ *
+ * Copyright (c) 2025-2026, Infineon Technologies AG, or an affiliate of Infineon Technologies AG.
+ * All rights reserved.
+ */
+
+#include <linux/netdevice.h>
+#include <net/rtnetlink.h>
+#include <net/sock.h>
+
+#include "main.h"
+#include "net.h"
+#include "cfg80211.h"
+#include "interface.h"
+#include "utils.h"
+#include "bus_proto.h"
+#include "dev_cmd.h"
+
+/* Store the mac that user set from cfg80211 api.
+ * If dongle gets reset, the mac will be restored to dongle.
+ */
+u8 user_mac_addr[ETH_ALEN] = {0};
+
+void inff_net_txflowblock(struct inff_if *ifp,
+ enum inff_netif_stop_reason reason, bool state)
+{
+ unsigned long flags;
+
+ if (!ifp || !ifp->ndev)
+ return;
+
+ inff_dbg(TRACE, "bsscfgidx=%d stop=0x%X reason=%d state=%d\n",
+ ifp->bsscfgidx, ifp->netif_stop, reason, state);
+
+ spin_lock_irqsave(&ifp->netif_stop_lock, flags);
+ if (state) {
+ if (!ifp->netif_stop)
+ netif_stop_queue(ifp->ndev);
+ ifp->netif_stop |= reason;
+ } else {
+ ifp->netif_stop &= ~reason;
+ if (!ifp->netif_stop)
+ netif_wake_queue(ifp->ndev);
+ }
+ spin_unlock_irqrestore(&ifp->netif_stop_lock, flags);
+}
+
+void inff_net_setcarrier(struct inff_if *ifp, bool on)
+{
+ struct net_device *ndev;
+
+ ndev = ifp->ndev;
+ inff_net_txflowblock(ifp, INFF_NETIF_STOP_REASON_DISCONNECTED, !on);
+ if (on) {
+ if (!netif_carrier_ok(ndev))
+ netif_carrier_on(ndev);
+
+ } else {
+ if (netif_carrier_ok(ndev))
+ netif_carrier_off(ndev);
+ }
+}
+
+bool inff_net_rx_preprocess(struct inff_if *ifp, struct sk_buff *skb)
+{
+ if (skb->pkt_type == PACKET_MULTICAST)
+ ifp->ndev->stats.multicast++;
+
+ if (!(ifp->ndev->flags & IFF_UP)) {
+ inff_pkt_buf_free_skb(skb);
+ return false;
+ }
+
+ ifp->ndev->stats.rx_bytes += skb->len;
+ ifp->ndev->stats.rx_packets++;
+
+ return true;
+}
+
+void inff_net_rx(struct inff_if *ifp, struct sk_buff *skb, bool inirq)
+{
+ if (!inff_net_rx_preprocess(ifp, skb))
+ return;
+
+ inff_dbg(DATA, "rx proto=0x%X\n", ntohs(skb->protocol));
+ if (ifp->napi.poll && ifp->napi_gro)
+ napi_gro_receive(&ifp->napi, skb);
+ else
+ netif_rx(skb);
+}
+
+static int inff_netdev_open(struct net_device *ndev)
+{
+ struct inff_if *ifp = netdev_priv(ndev);
+ struct inff_pub *drvr = ifp->drvr;
+ struct inff_bus *bus_if = drvr->bus_if;
+ u32 toe_ol = 0;
+ int ret;
+
+ /* If bus is not ready, can't continue */
+ if (bus_if->state != INFF_BUS_UP) {
+ iphy_err(drvr, "failed bus is not ready\n");
+ return -EAGAIN;
+ }
+
+ atomic_set(&ifp->pend_8021x_cnt, 0);
+
+ /* Get current TOE mode from dongle */
+ if (inff_socitype_is_ai(bus_if)) {
+ if (inff_fwcmd_iovar_int_get(ifp, "toe_ol", &toe_ol) >= 0 &&
+ (toe_ol & TOE_TX_CSUM_OL) != 0)
+ ndev->features |= NETIF_F_IP_CSUM;
+ else
+ ndev->features &= ~NETIF_F_IP_CSUM;
+ }
+
+ wiphy_lock(drvr->wiphy);
+ ret = inff_cfg80211_up(ndev);
+ wiphy_unlock(drvr->wiphy);
+
+ if (ret) {
+ iphy_err(drvr, "failed to bring up cfg80211\n");
+ return -EIO;
+ }
+
+ /* Clear, carrier, set when connected or AP mode. */
+ netif_carrier_off(ndev);
+
+ return 0;
+}
+
+static int inff_netdev_stop(struct net_device *ndev)
+{
+ struct inff_if *ifp = netdev_priv(ndev);
+ struct inff_pub *drvr = ifp->drvr;
+
+ wiphy_lock(drvr->wiphy);
+
+ if (inff_socitype_is_ai(drvr->bus_if)) {
+ inff_cfg80211_down(ndev);
+ } else if (inff_socitype_is_cp(drvr->bus_if)) {
+ inff_cp_cfg80211_link_down(ifp);
+ clear_bit(INFF_VIF_STATUS_READY, &ifp->vif->sme_state);
+ }
+
+ wiphy_unlock(drvr->wiphy);
+
+ inff_net_setcarrier(ifp, false);
+
+ return 0;
+}
+
+static netdev_tx_t inff_netdev_start_xmit(struct sk_buff *skb,
+ struct net_device *ndev)
+{
+ int ret;
+ struct inff_if *ifp = netdev_priv(ndev);
+ struct inff_pub *drvr = ifp->drvr;
+ struct ethhdr *eh;
+ int head_delta;
+ unsigned int tx_bytes = skb->len;
+
+ /* Can the device send data? */
+ if (drvr->bus_if->state != INFF_BUS_UP) {
+ iphy_err(drvr, "xmit rejected state=%d\n", drvr->bus_if->state);
+ netif_stop_queue(ndev);
+ dev_kfree_skb(skb);
+ ret = -ENODEV;
+ goto done;
+ }
+
+ /* Make sure there's enough writeable headroom */
+ if (skb_headroom(skb) < drvr->hdrlen || skb_header_cloned(skb)) {
+ head_delta = max_t(int, drvr->hdrlen - skb_headroom(skb), 0);
+
+ inff_dbg(INFO, "%s: %s headroom\n", inff_ifname(ifp),
+ head_delta ? "insufficient" : "unmodifiable");
+ atomic_inc(&drvr->bus_if->stats.pktcowed);
+ ret = pskb_expand_head(skb, ALIGN(head_delta, NET_SKB_PAD), 0,
+ GFP_ATOMIC);
+ if (ret < 0) {
+ iphy_err(drvr, "%s: failed to expand headroom\n",
+ inff_ifname(ifp));
+ atomic_inc(&drvr->bus_if->stats.pktcow_failed);
+ dev_kfree_skb(skb);
+ goto done;
+ }
+ }
+
+ /* validate length for ether packet */
+ if (skb->len < sizeof(*eh)) {
+ ret = -EINVAL;
+ dev_kfree_skb(skb);
+ goto done;
+ }
+
+ eh = (struct ethhdr *)(skb->data);
+
+ if (eh->h_proto == htons(ETH_P_PAE))
+ atomic_inc(&ifp->pend_8021x_cnt);
+
+ /* determine the priority */
+ if (skb->priority == 0 || skb->priority > 7)
+ skb->priority = cfg80211_classify8021d(skb, NULL);
+
+ /* set pacing shift for packet aggregation */
+ sk_pacing_shift_update(skb->sk, 8);
+
+ ret = inff_proto_tx_queue_data(drvr, ifp->ifidx, skb);
+ if (ret < 0)
+ inff_txfinalize(ifp, skb, false);
+
+done:
+ if (ret) {
+ ndev->stats.tx_dropped++;
+ } else {
+ ndev->stats.tx_packets++;
+ ndev->stats.tx_bytes += tx_bytes;
+ }
+
+ /* Return ok: we always eat the packet */
+ return NETDEV_TX_OK;
+}
+
+static int inff_netdev_set_mac_address(struct net_device *ndev, void *addr)
+{
+ struct inff_if *ifp = netdev_priv(ndev);
+ struct sockaddr *sa = (struct sockaddr *)addr;
+ int err = 0;
+
+ if (inff_socitype_is_ai(ifp->drvr->bus_if)) {
+ err = inff_set_cur_etheraddr(ifp, sa->sa_data);
+ if (err >= 0) {
+ inff_dbg(TRACE, "MAC Address updated to %pM\n", sa->sa_data);
+ memcpy(ifp->mac_addr, sa->sa_data, ETH_ALEN);
+ memcpy(user_mac_addr, sa->sa_data, ETH_ALEN);
+ eth_hw_addr_set(ifp->ndev, ifp->mac_addr);
+ }
+ }
+
+ return err;
+}
+
+static void inff_net_set_multicast_list(struct wiphy *wiphy,
+ struct wiphy_work *work)
+{
+ struct inff_if *ifp = container_of(work, struct inff_if,
+ multicast_work);
+ struct inff_pub *drvr = ifp->drvr;
+ struct net_device *ndev = ifp->ndev;
+ struct netdev_hw_addr *ha;
+ u32 cmd_value, cnt;
+ __le32 cnt_le;
+ char *buf, *bufp;
+ u32 buflen;
+ s32 err;
+
+ if (inff_socitype_is_cp(ifp->drvr->bus_if))
+ return;
+
+ /* Determine initial value of allmulti flag */
+ cmd_value = (ndev->flags & IFF_ALLMULTI) ? true : false;
+
+ /* Send down the multicast list first. */
+ cnt = netdev_mc_count(ndev);
+ buflen = sizeof(cnt) + (cnt * ETH_ALEN);
+ buf = kmalloc(buflen, GFP_KERNEL);
+ if (!buf)
+ return;
+ bufp = buf;
+
+ cnt_le = cpu_to_le32(cnt);
+ memcpy(bufp, &cnt_le, sizeof(cnt_le));
+ bufp += sizeof(cnt_le);
+
+ netdev_for_each_mc_addr(ha, ndev) {
+ if (!cnt)
+ break;
+ memcpy(bufp, ha->addr, ETH_ALEN);
+ bufp += ETH_ALEN;
+ cnt--;
+ }
+
+ err = inff_fwcmd_iovar_data_set(ifp, "mcast_list", buf, buflen);
+ if (err < 0) {
+ iphy_err(drvr, "Setting mcast_list failed, %d\n", err);
+ cmd_value = cnt ? true : cmd_value;
+ }
+
+ kfree(buf);
+
+ /*
+ * Now send the allmulti setting. This is based on the setting in the
+ * net_device flags, but might be modified above to be turned on if we
+ * were trying to set some addresses and dongle rejected it...
+ */
+ err = inff_fwcmd_iovar_int_set(ifp, "allmulti", cmd_value);
+ if (err < 0)
+ iphy_err(drvr, "Setting allmulti failed, %d\n", err);
+
+ /*Finally, pick up the PROMISC flag */
+ cmd_value = (ndev->flags & IFF_PROMISC) ? true : false;
+ err = inff_fwcmd_int_set(ifp, INFF_C_SET_PROMISC, cmd_value);
+ if (err < 0) {
+ /* PROMISC unsupported by firmware of older chips */
+ if (err == -EBADE)
+ iphy_info_once(drvr, "INFF_C_SET_PROMISC unsupported\n");
+ else
+ iphy_err(drvr, "Setting INFF_C_SET_PROMISC failed, err=%d\n",
+ err);
+ }
+}
+
+static void inff_netdev_set_multicast_list(struct net_device *ndev)
+{
+ struct inff_if *ifp = netdev_priv(ndev);
+
+ if (inff_socitype_is_ai(ifp->drvr->bus_if))
+ wiphy_work_queue(ifp->drvr->wiphy, &ifp->multicast_work);
+}
+
+static const struct net_device_ops inff_netdev_ops_pri = {
+ .ndo_open = inff_netdev_open,
+ .ndo_stop = inff_netdev_stop,
+ .ndo_start_xmit = inff_netdev_start_xmit,
+ .ndo_set_mac_address = inff_netdev_set_mac_address,
+ .ndo_set_rx_mode = inff_netdev_set_multicast_list,
+};
+
+int inff_net_attach(struct inff_if *ifp, bool locked)
+{
+ struct inff_pub *drvr = ifp->drvr;
+ struct net_device *ndev = ifp->ndev;
+ s32 err;
+
+ if (!ndev)
+ return -EINVAL;
+
+ /* set appropriate operations */
+ ndev->netdev_ops = &inff_netdev_ops_pri;
+
+ ndev->needed_headroom += drvr->hdrlen;
+
+ /* set the mac address & netns */
+ eth_hw_addr_set(ndev, ifp->mac_addr);
+ dev_net_set(ndev, wiphy_net(drvr->config->wiphy));
+
+ wiphy_work_init(&ifp->multicast_work, inff_net_set_multicast_list);
+
+ if (locked)
+ err = cfg80211_register_netdevice(ndev);
+ else
+ err = register_netdev(ndev);
+ if (err != 0) {
+ iphy_err(drvr, "couldn't register the net device\n");
+ goto fail;
+ }
+
+ netif_carrier_off(ndev);
+
+ ndev->priv_destructor = inff_cfg80211_free_netdev_priv;
+ inff_dbg(INFO, "%s: Infineon Dongle Host Driver\n", ndev->name);
+ return 0;
+
+fail:
+ drvr->iflist[ifp->bsscfgidx] = NULL;
+ ndev->netdev_ops = NULL;
+ return -EBADE;
+}
+
+void inff_net_detach(struct net_device *ndev, bool locked)
+{
+ if (ndev->reg_state == NETREG_REGISTERED) {
+ if (locked)
+ cfg80211_unregister_netdevice(ndev);
+ else
+ unregister_netdev(ndev);
+ } else {
+ inff_cfg80211_free_netdev_priv(ndev);
+ free_netdev(ndev);
+ }
+}
+
+struct inff_if *inff_net_add_if(struct inff_pub *drvr, s32 bsscfgidx, s32 ifidx,
+ const char *name, const u8 *mac_addr,
+ enum nl80211_iftype iftype)
+{
+ struct inff_if *ifp;
+ struct net_device *ndev;
+ int err;
+
+ inff_dbg(TRACE, "bsscfgidx=%d, ifidx=%d\n", bsscfgidx, ifidx);
+
+ switch (iftype) {
+ case NL80211_IFTYPE_STATION:
+ fallthrough;
+ case NL80211_IFTYPE_AP:
+ inff_dbg(INFO, "adding netdev interface\n");
+ /* Allocate netdev, including space for private structure */
+ ndev = alloc_netdev(sizeof(*ifp), name,
+ NET_NAME_UNKNOWN, ether_setup);
+ if (!ndev)
+ return ERR_PTR(-ENOMEM);
+
+ ndev->needs_free_netdev = true;
+ ifp = netdev_priv(ndev);
+ ifp->ndev = ndev;
+ /* store mapping ifidx to bsscfgidx */
+ if (drvr->if2bss[ifidx] == INFF_BSSIDX_INVALID)
+ drvr->if2bss[ifidx] = bsscfgidx;
+ break;
+ default:
+ return ERR_PTR(-EINVAL);
+ }
+
+ ifp->drvr = drvr;
+ drvr->iflist[bsscfgidx] = ifp;
+ ifp->ifidx = ifidx;
+ ifp->bsscfgidx = bsscfgidx;
+
+ if (inff_socitype_is_ai(drvr->bus_if)) {
+ init_waitqueue_head(&ifp->pend_8021x_wait);
+ spin_lock_init(&ifp->netif_stop_lock);
+ } else if (inff_socitype_is_cp(drvr->bus_if)) {
+ if (drvr->bus_if->state != INFF_BUS_UP)
+ inff_bus_change_state(drvr->bus_if, INFF_BUS_UP);
+
+ if (iftype == NL80211_IFTYPE_STATION) {
+ err = inff_cpcmd_data_get(ifp, INFF_CP_C_MAC, ifp->mac_addr, ETH_ALEN);
+ if (err) {
+ inff_err("Could not get MAC address (%d)\n", err);
+ return ERR_PTR(-EINVAL);
+ }
+ } else if (iftype == NL80211_IFTYPE_AP) {
+ err = inff_cpcmd_data_get(ifp, INFF_CP_C_SAP_MAC, ifp->mac_addr, ETH_ALEN);
+ if (err) {
+ inff_err("Could not get SoftAP MAC address (%d)\n", err);
+ return ERR_PTR(-EINVAL);
+ }
+ }
+ }
+
+ if (mac_addr)
+ memcpy(ifp->mac_addr, mac_addr, ETH_ALEN);
+
+ inff_dbg(TRACE, "%s (%pM) created by pid:%x\n",
+ name, ifp->mac_addr, current->pid);
+
+ return ifp;
+}
+
+void inff_net_del_if(struct inff_pub *drvr, struct inff_if *ifp, bool locked)
+{
+ int ifidx;
+ s32 bsscfgidx;
+ enum nl80211_iftype iftype = NL80211_IFTYPE_UNSPECIFIED;
+
+ if (!drvr || !ifp) {
+ inff_err("NULL interface cannot be removed\n");
+ return;
+ }
+
+ ifidx = ifp->ifidx;
+ bsscfgidx = ifp->bsscfgidx;
+
+ if (ifp->vif)
+ iftype = ifp->vif->wdev.iftype;
+
+ inff_dbg(TRACE, "bsscfgidx=%d, ifidx=%d\n", bsscfgidx,
+ ifp->ifidx);
+
+ inff_proto_del_if(drvr, ifp);
+
+ switch (iftype) {
+ case NL80211_IFTYPE_STATION:
+ fallthrough;
+ case NL80211_IFTYPE_AP:
+ inff_dbg(INFO, "deleting netdev interface\n");
+ if (bsscfgidx == 0) {
+ rtnl_lock();
+ inff_netdev_stop(ifp->ndev);
+ rtnl_unlock();
+ } else {
+ netif_stop_queue(ifp->ndev);
+ }
+
+ wiphy_work_cancel(drvr->wiphy, &ifp->multicast_work);
+ inff_net_detach(ifp->ndev, locked);
+ break;
+ default:
+ inff_dbg(TRACE, "Unknown wdev iface type");
+ break;
+ }
+
+ drvr->iflist[bsscfgidx] = NULL;
+ if (drvr->if2bss[ifidx] == bsscfgidx)
+ drvr->if2bss[ifidx] = INFF_BSSIDX_INVALID;
+}
diff --git a/drivers/net/wireless/infineon/inffmac/net.h b/drivers/net/wireless/infineon/inffmac/net.h
new file mode 100644
index 000000000000..ac16fbd63235
--- /dev/null
+++ b/drivers/net/wireless/infineon/inffmac/net.h
@@ -0,0 +1,43 @@
+/* SPDX-License-Identifier: ISC */
+/*
+ * Copyright (c) 2010 Broadcom Corporation
+ *
+ * Copyright (c) 2025-2026, Infineon Technologies AG, or an affiliate of Infineon Technologies AG.
+ * All rights reserved.
+ */
+
+#ifndef INFF_NET_H
+#define INFF_NET_H
+
+#include <linux/types.h>
+#include <linux/unaligned.h>
+
+/**
+ * enum inff_netif_stop_reason - reason for stopping netif queue.
+ *
+ * @INFF_NETIF_STOP_REASON_FWS_FC:
+ * netif stopped due to firmware signalling flow control.
+ * @INFF_NETIF_STOP_REASON_FLOW:
+ * netif stopped due to flowring full.
+ * @INFF_NETIF_STOP_REASON_DISCONNECTED:
+ * netif stopped due to not being connected (STA mode).
+ */
+enum inff_netif_stop_reason {
+ INFF_NETIF_STOP_REASON_FWS_FC = BIT(0),
+ INFF_NETIF_STOP_REASON_FLOW = BIT(1),
+ INFF_NETIF_STOP_REASON_DISCONNECTED = BIT(2)
+};
+
+void inff_net_txflowblock(struct inff_if *ifp,
+ enum inff_netif_stop_reason reason, bool state);
+void inff_net_setcarrier(struct inff_if *ifp, bool on);
+bool inff_net_rx_preprocess(struct inff_if *ifp, struct sk_buff *skb);
+void inff_net_rx(struct inff_if *ifp, struct sk_buff *skb, bool inirq);
+int inff_net_attach(struct inff_if *ifp, bool locked);
+void inff_net_detach(struct net_device *ndev, bool locked);
+struct inff_if *inff_net_add_if(struct inff_pub *drvr, s32 bsscfgidx, s32 ifidx,
+ const char *name, const u8 *mac_addr,
+ enum nl80211_iftype iftype);
+void inff_net_del_if(struct inff_pub *drvr, struct inff_if *ifp, bool locked);
+
+#endif /* INFF_NET_H */
--
2.43.0
^ permalink raw reply related [flat|nested] 26+ messages in thread* [PATCH wireless-next v3 12/25] wifi: inffmac: add cfg80211.c/h
2026-09-02 16:50 [PATCH wireless-next v3 00/25] wifi: inffmac: introducing a driver for Infineon's new generation chipsets Gokul Sivakumar
` (10 preceding siblings ...)
2026-09-02 16:50 ` [PATCH wireless-next v3 11/25] wifi: inffmac: add net.c/h Gokul Sivakumar
@ 2026-09-02 16:50 ` Gokul Sivakumar
2026-09-02 16:50 ` [PATCH wireless-next v3 13/25] wifi: inffmac: add interface.c/h Gokul Sivakumar
` (12 subsequent siblings)
24 siblings, 0 replies; 26+ messages in thread
From: Gokul Sivakumar @ 2026-09-02 16:50 UTC (permalink / raw)
To: linux-wireless, johannes
Cc: arend.vanspriel, wlan-kernel-dev-list, gokulkumar.sivakumar
Integrate the driver with cfg80211 subsystem. Also define the cfg80211 ops
and driver functions that utilize the cfg80211 callbacks for notifications.
Signed-off-by: Gokul Sivakumar <gokulkumar.sivakumar@infineon.com>
---
.../net/wireless/infineon/inffmac/cfg80211.c | 1418 +++++++++++++++++
.../net/wireless/infineon/inffmac/cfg80211.h | 309 ++++
2 files changed, 1727 insertions(+)
create mode 100644 drivers/net/wireless/infineon/inffmac/cfg80211.c
create mode 100644 drivers/net/wireless/infineon/inffmac/cfg80211.h
diff --git a/drivers/net/wireless/infineon/inffmac/cfg80211.c b/drivers/net/wireless/infineon/inffmac/cfg80211.c
new file mode 100644
index 000000000000..9ded62905970
--- /dev/null
+++ b/drivers/net/wireless/infineon/inffmac/cfg80211.c
@@ -0,0 +1,1418 @@
+// SPDX-License-Identifier: ISC
+/*
+ * Copyright (c) 2010 Broadcom Corporation
+ *
+ * Copyright (c) 2025-2026, Infineon Technologies AG, or an affiliate of Infineon Technologies AG.
+ * All rights reserved.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/etherdevice.h>
+#include <linux/vmalloc.h>
+#include <linux/string.h>
+#include <net/cfg80211.h>
+#include <net/netlink.h>
+
+#include "main.h"
+#include "chip.h"
+#include "net.h"
+#include "cfg80211.h"
+#include "utils.h"
+#include "debug.h"
+#include "bus_proto.h"
+#include "dev_cmd.h"
+#include "interface.h"
+#include "fwsignal.h"
+#include "feature.h"
+#include "he.h"
+
+#define INFF_ASSOC_PARAMS_FIXED_SIZE \
+ (sizeof(struct inff_assoc_params_le) - sizeof(u16))
+
+/* This is to override regulatory domains defined in cfg80211 module (reg.c)
+ * By default world regulatory domain defined in reg.c puts the flags
+ * NL80211_RRF_NO_IR for 5GHz channels (for * 36..48 and 149..165).
+ * With respect to these flags, All the changes in world regulatory
+ * domain are to be done here.
+ */
+static const struct ieee80211_regdomain inff_regdom = {
+ .n_reg_rules = 5,
+ .alpha2 = "99",
+ .reg_rules = {
+ /* IEEE 802.11b/g, channels 1..11 */
+ REG_RULE(2412 - 10, 2472 + 10, 40, 6, 20, 0),
+ /* If any */
+ /* IEEE 802.11 channel 14 - Only JP enables
+ * this and for 802.11b only
+ */
+ REG_RULE(2484 - 10, 2484 + 10, 20, 6, 20, 0),
+ /* IEEE 802.11a, channel 36..64 */
+ REG_RULE(5150 - 10, 5350 + 10, 160, 6, 20, 0),
+ /* IEEE 802.11a, channel 100..165 */
+ REG_RULE(5470 - 10, 5850 + 10, 160, 6, 20, 0),
+ /* IEEE 802.11ax, 6E */
+ REG_RULE(5935 - 10, 7115 + 10, 160, 6, 20, 0),
+ }
+};
+
+s32
+inff_inform_single_bss(struct inff_cfg80211_info *cfg,
+ struct inff_bss_info_le *bi)
+{
+ struct wiphy *wiphy = cfg->wiphy;
+ struct inff_pub *drvr = cfg->pub;
+ struct cfg80211_bss *bss;
+ enum nl80211_band band;
+ struct inff_chan ch;
+ u16 channel;
+ u32 freq;
+ u16 notify_capability;
+ u16 notify_interval;
+ u8 *notify_ie;
+ size_t notify_ielen;
+ struct cfg80211_inform_bss bss_data = {};
+ const struct inff_tlv *ssid = NULL;
+
+ if (le32_to_cpu(bi->length) > WL_BSS_INFO_MAX) {
+ iphy_err(drvr, "Bss info is larger than buffer. Discarding\n");
+ return -EINVAL;
+ }
+
+ ch.chspec = le16_to_cpu(bi->chanspec);
+ inff_chan_decchspec(&ch);
+
+ if (!bi->ctl_ch)
+ bi->ctl_ch = ch.control_ch_num;
+
+ channel = bi->ctl_ch;
+ band = inff_chan_band_to_nl80211(ch.band);
+
+ freq = ieee80211_channel_to_frequency(channel, band);
+ if (!freq)
+ return -EINVAL;
+
+ bss_data.chan = ieee80211_get_channel(wiphy, freq);
+ if (!bss_data.chan)
+ return -EINVAL;
+
+ bss_data.boottime_ns = ktime_to_ns(ktime_get_boottime());
+
+ notify_capability = le16_to_cpu(bi->capability);
+ notify_interval = le16_to_cpu(bi->beacon_period);
+ notify_ie = (u8 *)bi + le16_to_cpu(bi->ie_offset);
+ notify_ielen = le32_to_cpu(bi->ie_length);
+ bss_data.signal = (s16)le16_to_cpu(bi->RSSI) * 100;
+
+ ssid = inff_parse_tlvs(notify_ie, notify_ielen, WLAN_EID_SSID);
+ if (ssid && ssid->data[0] == '\0' && ssid->len == bi->SSID_len) {
+ /* Update SSID for hidden AP */
+ memcpy((u8 *)ssid->data, bi->SSID, bi->SSID_len);
+ }
+
+ inff_dbg(CONN, "bssid: %pM\n", bi->BSSID);
+ inff_dbg(CONN, "Channel: %d(%d)\n", channel, freq);
+ inff_dbg(CONN, "Capability: %X\n", notify_capability);
+ inff_dbg(CONN, "Beacon interval: %d\n", notify_interval);
+ inff_dbg(CONN, "Signal: %d\n", bss_data.signal);
+
+ bss = cfg80211_inform_bss_data(wiphy, &bss_data,
+ CFG80211_BSS_FTYPE_PRESP,
+ (const u8 *)bi->BSSID,
+ 0, notify_capability,
+ notify_interval, notify_ie,
+ notify_ielen, GFP_KERNEL);
+
+ if (!bss)
+ return -ENOMEM;
+
+ cfg80211_put_bss(wiphy, bss);
+
+ return 0;
+}
+
+static struct inff_bss_info_le *
+next_bss_le(struct inff_scan_results *list, struct inff_bss_info_le *bss)
+{
+ if (!bss)
+ return list->bss_info_le;
+ return (struct inff_bss_info_le *)((unsigned long)bss +
+ le32_to_cpu(bss->length));
+}
+
+s32
+inff_inform_bss(struct inff_cfg80211_info *cfg)
+{
+ struct inff_pub *drvr = cfg->pub;
+ struct inff_scan_results *bss_list;
+ struct inff_bss_info_le *bi = NULL; /* must be initialized */
+ s32 err = 0;
+ int i;
+
+ bss_list = (struct inff_scan_results *)cfg->escan_info.escan_buf;
+ if (bss_list->count != 0 &&
+ (bss_list->version != INFF_BSS_INFO_VER_109 &&
+ bss_list->version != INFF_BSS_INFO_VER_110)) {
+ iphy_err(drvr, "invalid version %d\n",
+ bss_list->version);
+ return -EOPNOTSUPP;
+ }
+
+ inff_dbg(SCAN, "scanned AP count (%d)\n", bss_list->count);
+ for (i = 0; i < bss_list->count; i++) {
+ bi = next_bss_le(bss_list, bi);
+ err = inff_inform_single_bss(cfg, bi);
+ if (err)
+ break;
+ }
+ return err;
+}
+
+s32
+inff_compare_update_same_bss(struct inff_cfg80211_info *cfg,
+ struct inff_bss_info_le *bss,
+ struct inff_bss_info_le *bss_info_le)
+{
+ struct inff_chan ch_bss, ch_bss_info_le;
+
+ ch_bss.chspec = le16_to_cpu(bss->chanspec);
+ inff_chan_decchspec(&ch_bss);
+ ch_bss_info_le.chspec = le16_to_cpu(bss_info_le->chanspec);
+ inff_chan_decchspec(&ch_bss_info_le);
+
+ if (!memcmp(&bss_info_le->BSSID, &bss->BSSID, ETH_ALEN) &&
+ ch_bss.band == ch_bss_info_le.band &&
+ bss_info_le->SSID_len == bss->SSID_len &&
+ !memcmp(bss_info_le->SSID, bss->SSID, bss_info_le->SSID_len)) {
+ if ((bss->flags & INFF_BSS_RSSI_ON_CHANNEL) ==
+ (bss_info_le->flags & INFF_BSS_RSSI_ON_CHANNEL)) {
+ s16 bss_rssi = le16_to_cpu(bss->RSSI);
+ s16 bss_info_rssi = le16_to_cpu(bss_info_le->RSSI);
+
+ /* preserve max RSSI if the measurements are
+ * both on-channel or both off-channel
+ */
+ if (bss_info_rssi > bss_rssi)
+ bss->RSSI = bss_info_le->RSSI;
+ } else if ((bss->flags & INFF_BSS_RSSI_ON_CHANNEL) &&
+ (bss_info_le->flags & INFF_BSS_RSSI_ON_CHANNEL) == 0) {
+ /* preserve the on-channel rssi measurement
+ * if the new measurement is off channel
+ */
+ bss->RSSI = bss_info_le->RSSI;
+ bss->flags |= INFF_BSS_RSSI_ON_CHANNEL;
+ }
+ return 1;
+ }
+ return 0;
+}
+
+void
+inff_set_mpc(struct inff_if *ifp, int mpc)
+{
+ struct inff_pub *drvr = ifp->drvr;
+ s32 err = 0;
+
+ ifp->drvr->req_mpc = mpc;
+ if (inff_check_vif_up(ifp->vif)) {
+ err = inff_fwcmd_iovar_int_set(ifp, "mpc",
+ ifp->drvr->req_mpc);
+ if (err) {
+ iphy_err(drvr, "fail to set mpc\n");
+ return;
+ }
+ inff_dbg(INFO, "MPC : %d\n", mpc);
+ }
+}
+
+static void
+inff_ht_update_wiphy_cap(struct inff_if *ifp,
+ u32 bw_cap[2], u32 nchain)
+{
+ struct inff_pub *drvr = ifp->drvr;
+ struct wiphy *wiphy = drvr->wiphy;
+ struct ieee80211_supported_band *band;
+ u32 nmode = 0, sgi_rx = 1;
+ int i = 0;
+
+ /* HT mode */
+ if (inff_fwcmd_iovar_int_get(ifp, "nmode", &nmode))
+ iphy_err(drvr, "nmode error\n");
+
+ if (!nmode)
+ return;
+
+ inff_dbg(INFO, "HT Enabled\n");
+
+ if (inff_fwcmd_iovar_int_get(ifp, "sgi_rx", &sgi_rx))
+ iphy_err(drvr, "sgi_rx error\n");
+
+ /* Update HT Capab for each Band */
+ for (i = 0; i < ARRAY_SIZE(wiphy->bands); i++) {
+ band = wiphy->bands[i];
+ if (!band)
+ continue;
+
+ switch (band->band) {
+ case NL80211_BAND_6GHZ:
+ break;
+
+ case NL80211_BAND_5GHZ:
+ /* Band 5GHz supports HT, so */
+ fallthrough;
+
+ case NL80211_BAND_2GHZ:
+ /* Band 2GHz supports HT */
+ band->ht_cap.ht_supported = true;
+
+ /* Bit 0 represents HT SGI 20MHz */
+ if ((sgi_rx & BIT(0)) &&
+ (bw_cap[band->band] & WLC_BW_20MHZ_BIT))
+ band->ht_cap.cap |= IEEE80211_HT_CAP_SGI_20;
+
+ /* Bit 1 represents HT SGI 40MHz */
+ if ((sgi_rx & BIT(1)) &&
+ (bw_cap[band->band] & WLC_BW_40MHZ_BIT)) {
+ band->ht_cap.cap |= IEEE80211_HT_CAP_SGI_40;
+ band->ht_cap.cap |= IEEE80211_HT_CAP_SUP_WIDTH_20_40;
+ }
+
+ band->ht_cap.cap |= IEEE80211_HT_CAP_DSSSCCK40;
+ band->ht_cap.ampdu_factor = IEEE80211_HT_MAX_AMPDU_64K;
+ band->ht_cap.ampdu_density = IEEE80211_HT_MPDU_DENSITY_16;
+ memset(band->ht_cap.mcs.rx_mask, 0xff, nchain);
+ band->ht_cap.mcs.tx_params = IEEE80211_HT_MCS_TX_DEFINED;
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+static __le16
+inff_vht_get_mcs_map(u32 nchain, enum ieee80211_vht_mcs_support supp)
+{
+ u16 mcs_map;
+ int i;
+
+ for (i = 0, mcs_map = 0xFFFF; i < nchain; i++)
+ mcs_map = (mcs_map << 2) | supp;
+
+ return cpu_to_le16(mcs_map);
+}
+
+static void
+inff_vht_update_wiphy_cap(struct inff_if *ifp,
+ u32 bw_cap[2], u32 nchain)
+{
+ struct inff_pub *drvr = ifp->drvr;
+ struct wiphy *wiphy = drvr->wiphy;
+ struct ieee80211_supported_band *band;
+ u32 vhtmode = 0;
+ u32 txstreams = 0;
+ u32 txbf_bfe_cap = 0;
+ u32 txbf_bfr_cap = 0;
+ __le16 mcs_map;
+ int i = 0;
+
+ /* VHT mode */
+ if (inff_fwcmd_iovar_int_get(ifp, "vhtmode", &vhtmode))
+ iphy_err(drvr, "vhtmode error\n");
+
+ if (!vhtmode)
+ return;
+
+ inff_dbg(INFO, "VHT Enabled\n");
+
+ /* Create a MAP with MCS for each Stream */
+ mcs_map = inff_vht_get_mcs_map(nchain, IEEE80211_VHT_MCS_SUPPORT_0_9);
+
+ if (inff_fwcmd_iovar_int_get(ifp, "txstreams", &txstreams))
+ iphy_err(drvr, "txstreams error\n");
+
+ /* Beamforming support information */
+ if (inff_fwcmd_iovar_int_get(ifp, "txbf_bfe_cap", &txbf_bfe_cap))
+ iphy_err(drvr, "txbf_bfe_cap error\n");
+ if (inff_fwcmd_iovar_int_get(ifp, "txbf_bfr_cap", &txbf_bfr_cap))
+ iphy_err(drvr, "txbf_bfw_cap error\n");
+
+ /* Update VHT Capab for each Band */
+ for (i = 0; i < ARRAY_SIZE(wiphy->bands); i++) {
+ band = wiphy->bands[i];
+ if (!band)
+ continue;
+
+ switch (band->band) {
+ case NL80211_BAND_6GHZ:
+ break;
+
+ case NL80211_BAND_5GHZ:
+ /* Band 5GHz supports VHT */
+ band->vht_cap.vht_supported = true;
+ band->vht_cap.vht_mcs.rx_mcs_map = mcs_map;
+ band->vht_cap.vht_mcs.tx_mcs_map = mcs_map;
+
+ if (bw_cap[band->band] & WLC_BW_80MHZ_BIT)
+ band->vht_cap.cap |= IEEE80211_VHT_CAP_SHORT_GI_80;
+
+ if (txbf_bfe_cap & INFF_TXBF_SU_BFE_CAP)
+ band->vht_cap.cap |= IEEE80211_VHT_CAP_SU_BEAMFORMEE_CAPABLE;
+ if (txbf_bfe_cap & INFF_TXBF_MU_BFE_CAP)
+ band->vht_cap.cap |= IEEE80211_VHT_CAP_MU_BEAMFORMEE_CAPABLE;
+ if (txbf_bfr_cap & INFF_TXBF_SU_BFR_CAP)
+ band->vht_cap.cap |= IEEE80211_VHT_CAP_SU_BEAMFORMER_CAPABLE;
+ if (txbf_bfr_cap & INFF_TXBF_MU_BFR_CAP)
+ band->vht_cap.cap |= IEEE80211_VHT_CAP_MU_BEAMFORMER_CAPABLE;
+
+ if ((txbf_bfe_cap || txbf_bfr_cap) && txstreams > 1) {
+ band->vht_cap.cap |=
+ (2 << IEEE80211_VHT_CAP_BEAMFORMEE_STS_SHIFT);
+ band->vht_cap.cap |= ((txstreams - 1) <<
+ IEEE80211_VHT_CAP_SOUNDING_DIMENSIONS_SHIFT);
+ band->vht_cap.cap |=
+ IEEE80211_VHT_CAP_VHT_LINK_ADAPTATION_VHT_MRQ_MFB;
+ }
+ break;
+
+ case NL80211_BAND_2GHZ:
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+static int
+inff_setup_wiphybands(struct inff_cfg80211_info *cfg)
+{
+ struct inff_pub *drvr = cfg->pub;
+ struct inff_if *ifp = inff_get_ifp(drvr, 0);
+ u32 rxchain = 0, nchain = 1;
+ u32 bw_cap[4] = { WLC_BW_20MHZ_BIT, /* 2GHz */
+ WLC_BW_20MHZ_BIT, /* 5GHz */
+ 0, /* 60GHz */
+ 0 }; /* 6GHz */
+ int err;
+
+ inff_nl80211_chan_get_bwcap(ifp, bw_cap);
+ inff_dbg(INFO, "bw_cap=[2G(%d), 5G(%d), 6G(%d)]\n",
+ bw_cap[NL80211_BAND_2GHZ], bw_cap[NL80211_BAND_5GHZ],
+ bw_cap[NL80211_BAND_6GHZ]);
+
+ err = inff_fwcmd_iovar_int_get(ifp, "rxchain", &rxchain);
+ if (err)
+ iphy_err(drvr, "rxchain error (%d)\n", err);
+ else
+ for (nchain = 0; rxchain; nchain++)
+ rxchain = rxchain & (rxchain - 1);
+ inff_dbg(INFO, "nchain=%d\n", nchain);
+
+ err = inff_nl80211_chan_populate(cfg, bw_cap);
+ if (err) {
+ iphy_err(drvr, "inff_construct_chaninfo failed (%d)\n", err);
+ return err;
+ }
+
+ /* HT Capability Registration */
+ inff_ht_update_wiphy_cap(ifp, bw_cap, nchain);
+
+ /* VHT Capability Registration */
+ inff_vht_update_wiphy_cap(ifp, bw_cap, nchain);
+
+ /* HE Capability Registration */
+ inff_he_update_wiphy_cap(ifp);
+
+ return 0;
+}
+
+void
+inff_cfg80211_update_proto_addr_mode(struct wireless_dev *wdev)
+{
+ struct inff_cfg80211_vif *vif;
+ struct inff_if *ifp;
+
+ vif = container_of(wdev, struct inff_cfg80211_vif, wdev);
+ ifp = vif->ifp;
+
+ if (wdev->iftype == NL80211_IFTYPE_AP)
+ inff_proto_configure_addr_mode(ifp->drvr, ifp->ifidx,
+ ADDR_DIRECT);
+ else
+ inff_proto_configure_addr_mode(ifp->drvr, ifp->ifidx,
+ ADDR_INDIRECT);
+}
+
+/**
+ * inff_ieee80211_is_dpp_pub_action() - true if dpp public type frame.
+ *
+ * @frame: action frame data.
+ * @frame_len: length of action frame data.
+ *
+ * Determine if action frame is dpp public action type
+ */
+bool
+inff_ieee80211_is_dpp_pub_action(void *frame, u32 frame_len)
+{
+ struct inff_pub_act_frame *pact_frm;
+
+ if (!frame)
+ return false;
+
+ pact_frm = (struct inff_pub_act_frame *)frame;
+ if (frame_len < sizeof(struct inff_pub_act_frame) - 1)
+ return false;
+
+ if (pact_frm->category == WLAN_CATEGORY_PUBLIC &&
+ pact_frm->action == WLAN_PUB_ACTION_VENDOR_SPECIFIC &&
+ pact_frm->oui_type == WLAN_OUI_TYPE_WFA_DPP &&
+ memcmp(pact_frm->oui, WFA_OUI, TLV_OUI_LEN) == 0)
+ return true;
+
+ return false;
+}
+
+/**
+ * inff_ieee80211_is_gas_pub_action() - true if gas public type frame.
+ *
+ * @frame: action frame data.
+ * @frame_len: length of action frame data.
+ *
+ * Determine if action frame is gas public action type
+ */
+bool
+inff_ieee80211_is_gas_pub_action(void *frame, u32 frame_len)
+{
+ struct inff_gas_pub_act_frame *act_frm;
+
+ if (!frame)
+ return false;
+
+ act_frm = (struct inff_gas_pub_act_frame *)frame;
+ if (frame_len < sizeof(*act_frm))
+ return false;
+
+ if (act_frm->category != WLAN_CATEGORY_PUBLIC)
+ return false;
+
+ if (act_frm->action == WLAN_PUB_ACTION_GAS_INITIAL_REQ ||
+ act_frm->action == WLAN_PUB_ACTION_GAS_INITIAL_RESP ||
+ act_frm->action == WLAN_PUB_ACTION_GAS_COMEBACK_REQ ||
+ act_frm->action == WLAN_PUB_ACTION_GAS_COMEBACK_RESP)
+ return true;
+
+ return false;
+}
+
+/**
+ * inff_print_action_frame() - WLAN Action frame type print function.
+ *
+ * @tx: Received or to be transmitted
+ * @frame: action frame data.
+ * @frame_len: length of action frame data.
+ *
+ * Print information about the action frame
+ */
+void inff_print_action_frame(bool tx, void *frame, u32 frame_len)
+{
+ struct inff_gas_pub_act_frame *gas_pub_act_frm;
+
+ if (!frame || frame_len <= 2)
+ return;
+
+ if (inff_ieee80211_is_gas_pub_action(frame, frame_len)) {
+ gas_pub_act_frm = (struct inff_gas_pub_act_frame *)frame;
+ switch (gas_pub_act_frm->action) {
+ case WLAN_PUB_ACTION_GAS_INITIAL_REQ:
+ inff_dbg(TRACE, "%s Public Action GAS Initial Request\n",
+ (tx) ? "TX" : "RX");
+ break;
+ case WLAN_PUB_ACTION_GAS_INITIAL_RESP:
+ inff_dbg(TRACE, "%s Public Action GAS Initial Response\n",
+ (tx) ? "TX" : "RX");
+ break;
+ case WLAN_PUB_ACTION_GAS_COMEBACK_REQ:
+ inff_dbg(TRACE, "%s Public Action GAS Comeback Request\n",
+ (tx) ? "TX" : "RX");
+ break;
+ case WLAN_PUB_ACTION_GAS_COMEBACK_RESP:
+ inff_dbg(TRACE, "%s Public Action GAS Comeback Response\n",
+ (tx) ? "TX" : "RX");
+ break;
+ default:
+ inff_dbg(TRACE, "%s Unknown Public Action GAS Frame\n",
+ (tx) ? "TX" : "RX");
+ break;
+ }
+ } else if (inff_ieee80211_is_dpp_pub_action(frame, frame_len)) {
+ inff_dbg(TRACE, "%s Public Action DPP Frame\n",
+ (tx) ? "TX" : "RX");
+ } else {
+ inff_dbg(TRACE, "%s Unknown Action Frame\n",
+ (tx) ? "TX" : "RX");
+ }
+}
+
+static s32
+inff_cfg80211_change_iface(struct wiphy *wiphy, struct net_device *ndev,
+ enum nl80211_iftype type,
+ struct vif_params *params)
+{
+ struct inff_cfg80211_info *cfg = wiphy_to_cfg(wiphy);
+ struct inff_if *ifp = netdev_priv(ndev);
+ struct inff_cfg80211_vif *vif = ifp->vif;
+ struct inff_pub *drvr = cfg->pub;
+ s32 infra = 0;
+ s32 ap = 0;
+ s32 err = 0;
+
+ inff_dbg(TRACE, "bsscfgidx=%d, type=%d\n", ifp->bsscfgidx,
+ type);
+
+ err = inff_vif_change_validate(wiphy_to_cfg(wiphy), vif, type);
+ if (err) {
+ iphy_err(drvr, "iface validation failed: err=%d\n", err);
+ return err;
+ }
+ switch (type) {
+ case NL80211_IFTYPE_STATION:
+ infra = 1;
+ break;
+ case NL80211_IFTYPE_AP:
+ ap = 1;
+ break;
+ default:
+ err = -EINVAL;
+ goto done;
+ }
+
+ if (!ap) {
+ err = inff_fwcmd_int_set(ifp, INFF_C_SET_INFRA, infra);
+ if (err) {
+ iphy_err(drvr, "WLC_SET_INFRA error (%d)\n", err);
+ err = -EAGAIN;
+ goto done;
+ }
+ inff_dbg(INFO, "IF Type = Infra\n");
+ }
+ ndev->ieee80211_ptr->iftype = type;
+
+ inff_cfg80211_update_proto_addr_mode(&vif->wdev);
+ inff_setup_wiphybands(cfg);
+done:
+ return err;
+}
+
+int
+inff_cfg80211_get_channel(struct wiphy *wiphy,
+ struct wireless_dev *wdev,
+ unsigned int link_id,
+ struct cfg80211_chan_def *chandef)
+{
+ struct inff_cfg80211_info *cfg = wiphy_to_cfg(wiphy);
+ struct inff_cfg80211_vif *vif;
+ struct net_device *ndev = wdev->netdev;
+ struct inff_pub *drvr = cfg->pub;
+ struct inff_chan ch;
+ enum nl80211_band band = 0;
+ enum nl80211_chan_width width = 0;
+ u32 chanspec = 0;
+ int freq, err;
+
+ if (!ndev || drvr->bus_if->state != INFF_BUS_UP)
+ return -ENODEV;
+
+ vif = container_of(wdev, struct inff_cfg80211_vif, wdev);
+ if (!inff_check_vif_up(vif))
+ return -EIO;
+
+ err = inff_fwcmd_iovar_int_get(netdev_priv(ndev), "chanspec", &chanspec);
+ if (err) {
+ iphy_err(drvr, "chanspec failed (%d)\n", err);
+ return err;
+ }
+
+ ch.chspec = chanspec;
+ inff_chan_decchspec(&ch);
+ band = inff_chan_band_to_nl80211(ch.band);
+
+ switch (ch.bw) {
+ case INFF_CHAN_BW_80:
+ width = NL80211_CHAN_WIDTH_80;
+ break;
+ case INFF_CHAN_BW_40:
+ width = NL80211_CHAN_WIDTH_40;
+ break;
+ case INFF_CHAN_BW_20:
+ width = NL80211_CHAN_WIDTH_20;
+ break;
+ case INFF_CHAN_BW_80P80:
+ width = NL80211_CHAN_WIDTH_80P80;
+ break;
+ case INFF_CHAN_BW_160:
+ width = NL80211_CHAN_WIDTH_160;
+ break;
+ }
+
+ freq = ieee80211_channel_to_frequency(ch.control_ch_num, band);
+ if (!freq)
+ return -EINVAL;
+ chandef->chan = ieee80211_get_channel(wiphy, freq);
+ if (!chandef->chan)
+ return -EINVAL;
+ chandef->width = width;
+ chandef->center_freq1 = ieee80211_channel_to_frequency(ch.chnum, band);
+ chandef->center_freq2 = 0;
+
+ return 0;
+}
+
+static struct cfg80211_ops inff_cfg80211_ops = {
+ .scan = inff_cfg80211_scan,
+ .get_channel = inff_cfg80211_get_channel,
+};
+
+struct cfg80211_ops *
+inff_cfg80211_get_ops(struct inff_mp_device *settings)
+{
+ struct cfg80211_ops *ops;
+
+ ops = kmemdup(&inff_cfg80211_ops, sizeof(inff_cfg80211_ops),
+ GFP_KERNEL);
+
+ return ops;
+}
+
+void
+inff_cfg80211_free_netdev_priv(struct net_device *ndev)
+{
+ struct inff_cfg80211_vif *vif;
+ struct inff_if *ifp;
+
+ ifp = netdev_priv(ndev);
+ vif = ifp->vif;
+
+ if (vif) {
+ inff_free_vif(vif);
+ ifp->vif = NULL;
+ }
+}
+
+u8
+inff_map_prio_to_prec(void *config, u8 prio)
+{
+ struct inff_cfg80211_info *cfg = (struct inff_cfg80211_info *)config;
+
+ if (!cfg)
+ return (prio == PRIO_8021D_NONE || prio == PRIO_8021D_BE) ?
+ (prio ^ 2) : prio;
+
+ /* For those AC(s) with ACM flag set to 1, convert its 4-level priority
+ * to an 8-level precedence which is the same as BE's
+ */
+ if (prio > PRIO_8021D_EE &&
+ cfg->ac_priority[prio] == cfg->ac_priority[PRIO_8021D_BE])
+ return cfg->ac_priority[prio] * 2;
+
+ /* Conversion of 4-level priority to 8-level precedence */
+ if (prio == PRIO_8021D_BE || prio == PRIO_8021D_BK ||
+ prio == PRIO_8021D_CL || prio == PRIO_8021D_VO)
+ return cfg->ac_priority[prio] * 2;
+ else
+ return cfg->ac_priority[prio] * 2 + 1;
+}
+
+u8
+inff_map_prio_to_aci(void *config, u8 prio)
+{
+ /* Prio here refers to the 802.1d priority in range of 0 to 7.
+ * ACI here refers to the WLAN AC Index in range of 0 to 3.
+ * This function will return ACI corresponding to input prio.
+ */
+ struct inff_cfg80211_info *cfg = (struct inff_cfg80211_info *)config;
+
+ if (cfg)
+ return cfg->ac_priority[prio];
+
+ return prio;
+}
+
+static void
+inff_init_wmm_prio(u8 *priority)
+{
+ /* Initialize AC priority array to default
+ * 802.1d priority as per following table:
+ * 802.1d prio 0,3 maps to BE
+ * 802.1d prio 1,2 maps to BK
+ * 802.1d prio 4,5 maps to VI
+ * 802.1d prio 6,7 maps to VO
+ */
+ priority[0] = INFF_FWS_FIFO_AC_BE;
+ priority[3] = INFF_FWS_FIFO_AC_BE;
+ priority[1] = INFF_FWS_FIFO_AC_BK;
+ priority[2] = INFF_FWS_FIFO_AC_BK;
+ priority[4] = INFF_FWS_FIFO_AC_VI;
+ priority[5] = INFF_FWS_FIFO_AC_VI;
+ priority[6] = INFF_FWS_FIFO_AC_VO;
+ priority[7] = INFF_FWS_FIFO_AC_VO;
+}
+
+void
+inff_wifi_prioritize_acparams(const struct inff_cfg80211_edcf_acparam *acp,
+ u8 *priority)
+{
+ u8 aci;
+ u8 aifsn;
+ u8 ecwmin;
+ u8 ecwmax;
+ u8 acm;
+ u8 ranking_basis[EDCF_AC_COUNT];
+ u8 aci_prio[EDCF_AC_COUNT]; /* AC_BE, AC_BK, AC_VI, AC_VO */
+ u8 index;
+
+ for (aci = 0; aci < EDCF_AC_COUNT; aci++, acp++) {
+ aifsn = acp->ACI & EDCF_AIFSN_MASK;
+ acm = (acp->ACI & EDCF_ACM_MASK) ? 1 : 0;
+ ecwmin = acp->ECW & EDCF_ECWMIN_MASK;
+ ecwmax = (acp->ECW & EDCF_ECWMAX_MASK) >> EDCF_ECWMAX_SHIFT;
+ inff_dbg(CONN, "ACI %d aifsn %d acm %d ecwmin %d ecwmax %d\n",
+ aci, aifsn, acm, ecwmin, ecwmax);
+ /* Default AC_VO will be the lowest ranking value */
+ ranking_basis[aci] = aifsn + ecwmin + ecwmax;
+ /* Initialise priority starting at 0 (AC_BE) */
+ aci_prio[aci] = 0;
+
+ /* If ACM is set, STA can't use this AC as per 802.11.
+ * Change the ranking to BE
+ */
+ if (aci != AC_BE && aci != AC_BK && acm == 1)
+ ranking_basis[aci] = ranking_basis[AC_BE];
+ }
+
+ /* Ranking method which works for AC priority
+ * swapping when values for cwmin, cwmax and aifsn are varied
+ * Compare each aci_prio against each other aci_prio
+ */
+ for (aci = 0; aci < EDCF_AC_COUNT; aci++) {
+ for (index = 0; index < EDCF_AC_COUNT; index++) {
+ if (index != aci) {
+ /* Smaller ranking value has higher priority,
+ * so increment priority for each ACI which has
+ * a higher ranking value
+ */
+ if (ranking_basis[aci] < ranking_basis[index])
+ aci_prio[aci]++;
+ }
+ }
+ }
+
+ /* By now, aci_prio[] will be in range of 0 to 3.
+ * Use ACI prio to get the new priority value for
+ * each 802.1d traffic type, in this range.
+ */
+ if (!(aci_prio[AC_BE] == aci_prio[AC_BK] &&
+ aci_prio[AC_BK] == aci_prio[AC_VI] &&
+ aci_prio[AC_VI] == aci_prio[AC_VO])) {
+ /* 802.1d 0,3 maps to BE */
+ priority[0] = aci_prio[AC_BE];
+ priority[3] = aci_prio[AC_BE];
+
+ /* 802.1d 1,2 maps to BK */
+ priority[1] = aci_prio[AC_BK];
+ priority[2] = aci_prio[AC_BK];
+
+ /* 802.1d 4,5 maps to VO */
+ priority[4] = aci_prio[AC_VI];
+ priority[5] = aci_prio[AC_VI];
+
+ /* 802.1d 6,7 maps to VO */
+ priority[6] = aci_prio[AC_VO];
+ priority[7] = aci_prio[AC_VO];
+ } else {
+ /* Initialize to default priority */
+ inff_init_wmm_prio(priority);
+ }
+
+ inff_dbg(CONN, "Adj prio BE 0->%d, BK 1->%d, BK 2->%d, BE 3->%d\n",
+ priority[0], priority[1], priority[2], priority[3]);
+
+ inff_dbg(CONN, "Adj prio VI 4->%d, VI 5->%d, VO 6->%d, VO 7->%d\n",
+ priority[4], priority[5], priority[6], priority[7]);
+}
+
+static s32
+inff_dongle_roam(struct inff_if *ifp)
+{
+ struct inff_pub *drvr = ifp->drvr;
+ s32 err;
+ __le32 roamtrigger[2];
+ __le32 roam_delta[2];
+ __le32 bandlist[4];
+ u32 n_bands;
+ int i;
+
+ /* Use the firmware roaming engine with a conservative beacon timeout */
+ err = inff_fwcmd_iovar_int_set(ifp, "bcn_timeout",
+ INFF_DEFAULT_BCN_TIMEOUT_ROAM_ON);
+ if (err) {
+ iphy_err(drvr, "bcn_timeout error (%d)\n", err);
+ goto roam_setup_done;
+ }
+
+ err = inff_fwcmd_iovar_int_set(ifp, "roam_off", 0);
+ if (err) {
+ iphy_err(drvr, "roam_off error (%d)\n", err);
+ goto roam_setup_done;
+ }
+
+ err = inff_fwcmd_data_get(ifp, INFF_C_GET_BANDLIST, &bandlist,
+ sizeof(bandlist));
+ if (err) {
+ iphy_err(drvr, "could not obtain band info: err=%d\n", err);
+ goto roam_setup_done;
+ }
+ /* To enhance compatibility set each band's roam properties instead of
+ * using all band. BAND_5G is 1, BAND_2G is 2 and BAND_6G is 3.
+ */
+ n_bands = le32_to_cpu(bandlist[0]);
+ for (i = 1; i <= n_bands; i++) {
+ roamtrigger[0] = cpu_to_le32(WL_ROAM_TRIGGER_LEVEL);
+ roamtrigger[1] = bandlist[i];
+ err = inff_fwcmd_data_set(ifp, INFF_C_SET_ROAM_TRIGGER,
+ (void *)roamtrigger, sizeof(roamtrigger));
+ if (err)
+ iphy_err(drvr, "WLC_SET_ROAM_TRIGGER error (%d), band %d\n",
+ err, bandlist[i]);
+
+ roam_delta[0] = cpu_to_le32(WL_ROAM_DELTA);
+ roam_delta[1] = bandlist[i];
+ err = inff_fwcmd_data_set(ifp, INFF_C_SET_ROAM_DELTA,
+ (void *)roam_delta, sizeof(roam_delta));
+ if (err)
+ iphy_err(drvr, "WLC_SET_ROAM_DELTA error (%d), band %d\n",
+ err, bandlist[i]);
+ }
+
+ return 0;
+
+roam_setup_done:
+ return err;
+}
+
+s32
+inff_cfg80211_up(struct net_device *ndev)
+{
+ struct inff_if *pri_ifp, *ifp = netdev_priv(ndev);
+ struct inff_cfg80211_info *cfg = ifp->drvr->config;
+ struct inff_pub *drvr = cfg->pub;
+ struct net_device *pri_ndev;
+ struct wireless_dev *pri_wdev;
+ s32 power_mode;
+ s32 err = 0;
+
+ lockdep_assert_held(cfg->wiphy);
+
+ set_bit(INFF_VIF_STATUS_READY, &ifp->vif->sme_state);
+
+ if (cfg->dongle_up ||
+ inff_socitype_is_cp(drvr->bus_if))
+ goto exit;
+
+ pri_ndev = cfg_to_ndev(cfg);
+ pri_ifp = netdev_priv(pri_ndev);
+
+ /* make sure RF is ready for work */
+ inff_fwcmd_int_set(pri_ifp, INFF_C_UP, 0);
+
+ inff_dongle_scantime(pri_ifp);
+
+ power_mode = cfg->pwr_save ? INFF_PM_FAST : INFF_PM_OFF;
+ err = inff_fwcmd_int_set(pri_ifp, INFF_C_SET_PM, power_mode);
+ if (err)
+ goto exit;
+ inff_dbg(INFO, "power save set to %s\n",
+ (power_mode ? "enabled" : "disabled"));
+
+ err = inff_dongle_roam(pri_ifp);
+ if (err)
+ goto exit;
+
+ pri_wdev = pri_ndev->ieee80211_ptr;
+ err = inff_cfg80211_change_iface(pri_wdev->wiphy, pri_ndev,
+ pri_wdev->iftype, NULL);
+ if (err)
+ goto exit;
+
+ err = inff_fwcmd_int_set(pri_ifp, INFF_C_SET_FAKEFRAG, 1);
+ if (err) {
+ iphy_err(drvr, "failed to set frameburst mode\n");
+ goto exit;
+ }
+
+ cfg->dongle_up = true;
+exit:
+ return err;
+}
+
+s32
+inff_cfg80211_down(struct net_device *ndev)
+{
+ struct inff_if *ifp = netdev_priv(ndev);
+ struct inff_cfg80211_info *cfg = ifp->drvr->config;
+
+ lockdep_assert_held(cfg->wiphy);
+
+ /*
+ * While going down, if associated with AP disassociate
+ * from AP to save power
+ */
+ if (inff_check_vif_up(ifp->vif)) {
+ inff_link_down(ifp->vif, WLAN_REASON_UNSPECIFIED, true);
+
+ /* Make sure WPA_Supplicant receives all the event
+ * generated due to DISASSOC call to the fw to keep
+ * the state fw and WPA_Supplicant state consistent
+ */
+ inff_delay(500);
+ cfg->dongle_up = false;
+ }
+
+ inff_abort_scanning(cfg);
+ clear_bit(INFF_VIF_STATUS_READY, &ifp->vif->sme_state);
+
+ return 0;
+}
+
+static const struct ieee80211_txrx_stypes
+inff_txrx_stypes[NUM_NL80211_IFTYPES] = {
+ [NL80211_IFTYPE_STATION] = {
+ .tx = 0xffff,
+ .rx = BIT(IEEE80211_STYPE_ACTION >> 4) |
+ BIT(IEEE80211_STYPE_AUTH >> 4) |
+ BIT(IEEE80211_STYPE_PROBE_REQ >> 4)
+ },
+ [NL80211_IFTYPE_AP] = {
+ .tx = 0xffff,
+ .rx = BIT(IEEE80211_STYPE_ASSOC_REQ >> 4) |
+ BIT(IEEE80211_STYPE_REASSOC_REQ >> 4) |
+ BIT(IEEE80211_STYPE_PROBE_REQ >> 4) |
+ BIT(IEEE80211_STYPE_DISASSOC >> 4) |
+ BIT(IEEE80211_STYPE_AUTH >> 4) |
+ BIT(IEEE80211_STYPE_DEAUTH >> 4) |
+ BIT(IEEE80211_STYPE_ACTION >> 4)
+ }
+};
+
+static int
+inff_setup_wiphy_params(struct wiphy *wiphy, struct inff_if *ifp)
+{
+ struct inff_pub *drvr = ifp->drvr;
+ const struct ieee80211_iface_combination *combo;
+ u16 max_interfaces = 0;
+ int err, i;
+ enum nl80211_band band;
+
+ wiphy->max_scan_ssids = WL_NUM_SCAN_MAX;
+ wiphy->max_scan_ie_len = INFF_SCAN_IE_LEN_MAX;
+ wiphy->max_num_pmkids = INFF_MAXPMKID;
+
+ err = inff_setup_ifmodes(wiphy, ifp);
+ if (err)
+ return err;
+
+ for (i = 0, combo = wiphy->iface_combinations;
+ i < wiphy->n_iface_combinations; i++, combo++) {
+ max_interfaces = max(max_interfaces, combo->max_interfaces);
+ }
+
+ for (i = 0; i < max_interfaces && i < ARRAY_SIZE(drvr->addresses);
+ i++) {
+ u8 *addr = drvr->addresses[i].addr;
+
+ memcpy(addr, drvr->mac, ETH_ALEN);
+ if (i) {
+ addr[0] |= BIT(1);
+ addr[ETH_ALEN - 1] ^= i;
+ }
+ }
+ wiphy->addresses = drvr->addresses;
+ wiphy->n_addresses = i;
+
+ wiphy->signal_type = CFG80211_SIGNAL_TYPE_MBM;
+
+ wiphy->bss_select_support = BIT(NL80211_BSS_SELECT_ATTR_RSSI) |
+ BIT(NL80211_BSS_SELECT_ATTR_BAND_PREF) |
+ BIT(NL80211_BSS_SELECT_ATTR_RSSI_ADJUST);
+
+ wiphy->flags |= WIPHY_FLAG_NETNS_OK |
+ WIPHY_FLAG_PS_ON_BY_DEFAULT |
+ WIPHY_FLAG_OFFCHAN_TX |
+ WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL |
+ WIPHY_FLAG_SPLIT_SCAN_6GHZ;
+
+ wiphy->mgmt_stypes = inff_txrx_stypes;
+ wiphy->max_remain_on_channel_duration = 5000;
+
+ /* first entry in bandlist is number of bands */
+ for (band = 0; band < NUM_NL80211_BANDS; band++)
+ wiphy->bands[band] = NULL;
+
+ wiphy_read_of_freq_limits(wiphy);
+ return 0;
+}
+
+static void
+inff_free_wiphy_params(struct wiphy *wiphy)
+{
+ int i;
+
+ if (!wiphy)
+ return;
+
+ if (wiphy->iface_combinations) {
+ for (i = 0; i < wiphy->n_iface_combinations; i++)
+ kfree(wiphy->iface_combinations[i].limits);
+ }
+ kfree(wiphy->iface_combinations);
+ if (wiphy->bands[NL80211_BAND_2GHZ]) {
+ kfree(wiphy->bands[NL80211_BAND_2GHZ]->channels);
+ kfree(wiphy->bands[NL80211_BAND_2GHZ]);
+ }
+ if (wiphy->bands[NL80211_BAND_5GHZ]) {
+ kfree(wiphy->bands[NL80211_BAND_5GHZ]->channels);
+ kfree(wiphy->bands[NL80211_BAND_5GHZ]);
+ }
+ if (wiphy->bands[NL80211_BAND_6GHZ]) {
+ kfree(wiphy->bands[NL80211_BAND_6GHZ]->channels);
+ kfree(wiphy->bands[NL80211_BAND_6GHZ]);
+ }
+}
+
+int
+inff_alloc_wiphy(struct device *dev, struct inff_mp_device *settings)
+{
+ struct wiphy *wiphy;
+ struct cfg80211_ops *ops;
+ struct inff_pub *drvr = NULL;
+
+ ops = inff_cfg80211_get_ops(settings);
+ if (!ops)
+ return -ENOMEM;
+
+ wiphy = wiphy_new(ops, sizeof(*drvr));
+ if (!wiphy) {
+ kfree(ops);
+ return -ENOMEM;
+ }
+
+ set_wiphy_dev(wiphy, dev);
+ drvr = wiphy_priv(wiphy);
+ drvr->wiphy = wiphy;
+ drvr->ops = ops;
+ drvr->bus_if = dev_get_drvdata(dev);
+ drvr->bus_if->drvr = drvr;
+ drvr->settings = settings;
+
+ return 0;
+}
+
+void
+inff_free_wiphy(struct device *dev)
+{
+ struct inff_bus *bus_if = dev_get_drvdata(dev);
+ struct inff_pub *drvr = bus_if->drvr;
+
+ if (!drvr)
+ return;
+
+ bus_if->drvr = NULL;
+
+ kfree(drvr->ops);
+
+ wiphy_free(drvr->wiphy);
+}
+
+static void
+inff_cfg80211_reg_notifier(struct wiphy *wiphy,
+ struct regulatory_request *req)
+{
+ struct inff_cfg80211_info *cfg = wiphy_to_cfg(wiphy);
+ struct inff_if *ifp = inff_get_ifp(cfg->pub, 0);
+ struct inff_pub *drvr = cfg->pub;
+ struct inff_fwcmd_country_le ccreq;
+ s32 err;
+ int i;
+
+ /* The country code gets set to "00" by default at boot, ignore */
+ if (req->alpha2[0] == '0' && req->alpha2[1] == '0')
+ return;
+
+ /* ignore non-ISO3166 country codes */
+ for (i = 0; i < 2; i++)
+ if (req->alpha2[i] < 'A' || req->alpha2[i] > 'Z') {
+ iphy_err(drvr, "not an ISO3166 code (0x%02x 0x%02x)\n",
+ req->alpha2[0], req->alpha2[1]);
+ return;
+ }
+
+ inff_dbg(INFO, "initiator=%d, alpha=%c%c\n", req->initiator,
+ req->alpha2[0], req->alpha2[1]);
+
+ err = inff_fwcmd_iovar_data_get(ifp, "country", &ccreq, sizeof(ccreq));
+ if (err) {
+ iphy_err(drvr, "Country code iovar returned err = %d\n", err);
+ return;
+ }
+
+ if (req->alpha2[0] == ccreq.country_abbrev[0] &&
+ req->alpha2[1] == ccreq.country_abbrev[1]) {
+ inff_dbg(INFO, "Country code already set\n");
+ return;
+ }
+
+ inff_dbg(INFO, "For Country code, using ISO3166 code and 0 rev\n");
+ memset(&ccreq, 0, sizeof(ccreq));
+ ccreq.country_abbrev[0] = req->alpha2[0];
+ ccreq.country_abbrev[1] = req->alpha2[1];
+ ccreq.ccode[0] = req->alpha2[0];
+ ccreq.ccode[1] = req->alpha2[1];
+
+ /* Abort on-going scan before changing ccode */
+ inff_abort_scanning(cfg);
+
+ err = inff_fwcmd_iovar_data_set(ifp, "country", &ccreq, sizeof(ccreq));
+ if (err) {
+ iphy_err(drvr, "Firmware rejected country setting\n");
+ return;
+ }
+
+ drvr->config->bands_reset_required = true;
+ inff_dbg(TRACE, "Marking bands for reset due to country change\n");
+ err = inff_setup_wiphybands(cfg);
+ if (err)
+ drvr->config->bands_reset_required = false;
+}
+
+struct inff_cfg80211_info *
+inff_cfg80211_attach(struct inff_pub *drvr,
+ struct cfg80211_ops *ops)
+{
+ struct wiphy *wiphy = drvr->wiphy;
+ struct inff_cfg80211_info *cfg;
+ struct net_device *ndev = NULL;
+ struct inff_cfg80211_vif *vif = NULL;
+ struct inff_if *ifp;
+ struct inff_cfg80211_vif *vif_ap = NULL;
+ struct inff_if *ifp_ap;
+ struct net_device *ndev_ap = NULL;
+ s32 err = 0;
+ u16 *cap = NULL;
+
+ ndev = inff_get_ifp(drvr, INFF_INTERFACE_PRI_IFIDX)->ndev;
+
+ if (!ndev) {
+ iphy_err(drvr, "ndev is invalid\n");
+ return NULL;
+ }
+
+ cfg = kzalloc_obj(*cfg);
+ if (!cfg)
+ return NULL;
+
+ cfg->wiphy = wiphy;
+ cfg->pub = drvr;
+ cfg->pm_state = INFF_CFG80211_PM_STATE_RESUMED;
+ cfg->num_softap = 0;
+ cfg->mchan_conf = INFF_MCHAN_CONF_DEFAULT;
+ cfg->pwr_save = true;
+ cfg->dongle_up = false; /* dongle is not up yet */
+ cfg->conf.frag_threshold = (u32)-1;
+ cfg->conf.rts_threshold = (u32)-1;
+ cfg->conf.retry_short = (u32)-1;
+ cfg->conf.retry_long = (u32)-1;
+
+ init_waitqueue_head(&cfg->vif_event.vif_wq);
+ spin_lock_init(&cfg->vif_event.vif_event_lock);
+ INIT_LIST_HEAD(&cfg->vif_list);
+
+ cfg->extra_buf = kzalloc(WL_EXTRA_BUF_MAX, GFP_KERNEL);
+ if (!cfg->extra_buf)
+ goto cfg_out;
+
+ /* allocate primary vif */
+ vif = inff_alloc_vif(cfg, NL80211_IFTYPE_STATION);
+ if (IS_ERR(vif))
+ goto cfg_extra_buf_out;
+
+ ifp = netdev_priv(ndev);
+ ifp->vif = vif;
+ vif->ifp = ifp;
+ vif->wdev.netdev = ndev;
+ ndev->ieee80211_ptr = &vif->wdev;
+ SET_NETDEV_DEV(ndev, wiphy_dev(cfg->wiphy));
+ init_completion(&cfg->vif_disabled);
+
+ if (inff_socitype_is_cp(drvr->bus_if)) {
+ /* allocate vif for SoftAP */
+ vif_ap = inff_alloc_vif(cfg, NL80211_IFTYPE_AP);
+ if (IS_ERR(vif_ap))
+ goto vif_out;
+
+ ndev_ap = inff_get_ifp(drvr, INFF_INTERFACE_SEC_IFIDX)->ndev;
+ ifp_ap = netdev_priv(ndev_ap);
+ ifp_ap->vif = vif_ap;
+ vif_ap->ifp = ifp_ap;
+ vif_ap->wdev.netdev = ndev_ap;
+ ndev_ap->ieee80211_ptr = &vif_ap->wdev;
+ SET_NETDEV_DEV(ndev_ap, wiphy_dev(cfg->wiphy));
+
+ err = inff_cp_scan_init(cfg);
+ if (err) {
+ inff_err("Failed to init the priv (%d)\n", err);
+ kfree(cfg->scan.bss_buf);
+ cfg->scan.bss_buf = NULL;
+ goto vif_ap_out;
+ }
+ } else if (inff_socitype_is_ai(drvr->bus_if)) {
+ inff_fwevt_register_handlers(cfg);
+
+ err = inff_init_escan(cfg);
+ if (err)
+ goto vif_out;
+
+ inff_init_wmm_prio(cfg->ac_priority);
+ }
+
+ /* regulatory notifier below needs access to cfg so
+ * assign it now.
+ */
+ drvr->config = cfg;
+
+ err = inff_setup_wiphy_params(wiphy, ifp);
+ if (err < 0)
+ goto scan_out;
+
+ err = inff_setup_wiphybands(cfg);
+ if (err) {
+ iphy_err(drvr, "Setting wiphy bands failed (%d)\n", err);
+ goto wiphy_params_out;
+ }
+
+ inff_dbg(INFO, "Registering custom regulatory\n");
+ wiphy->reg_notifier = inff_cfg80211_reg_notifier;
+ wiphy->regulatory_flags |= REGULATORY_CUSTOM_REG;
+ wiphy->regulatory_flags |= REGULATORY_COUNTRY_IE_IGNORE;
+ wiphy_apply_custom_regulatory(wiphy, &inff_regdom);
+
+ if (inff_socitype_is_ai(drvr->bus_if)) {
+ /* firmware defaults to 40MHz disabled in 2G band. We signal
+ * cfg80211 here that we do and have it decide we can enable
+ * it. But first check if device does support 2G operation.
+ */
+ if (wiphy->bands[NL80211_BAND_2GHZ]) {
+ cap = &wiphy->bands[NL80211_BAND_2GHZ]->ht_cap.cap;
+ *cap |= IEEE80211_HT_CAP_SUP_WIDTH_20_40;
+ }
+ }
+
+ err = wiphy_register(wiphy);
+ if (err < 0) {
+ iphy_err(drvr, "Could not register wiphy device (%d)\n", err);
+ goto wiphy_params_out;
+ }
+
+ if (inff_socitype_is_cp(drvr->bus_if))
+ return cfg;
+
+ /* If cfg80211 didn't disable 40MHz HT CAP in wiphy_register(),
+ * setup 40MHz in 2GHz band and enable OBSS scanning.
+ */
+ if (cap && (*cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40)) {
+ err = inff_chan_enable_bw40_2g(cfg);
+ if (!err) {
+ err = inff_fwcmd_iovar_int_set(ifp, "obss_coex",
+ INFF_OBSS_COEX_AUTO);
+ if (err)
+ iphy_err(drvr,
+ "Failed to configure OBSS coex: (%d)\n",
+ err);
+ } else {
+ *cap &= ~IEEE80211_HT_CAP_SUP_WIDTH_20_40;
+ }
+ }
+
+ /* activate FWEH event handling */
+ err = inff_fwevt_activate_events(ifp);
+ if (err) {
+ iphy_err(drvr, "FWEH activation failed (%d)\n", err);
+ goto wiphy_unreg_out;
+ }
+
+ /* (re-) activate FWEH event handling */
+ err = inff_fwevt_activate_events(ifp);
+ if (err) {
+ iphy_err(drvr, "FWEH activation failed (%d)\n", err);
+ goto wiphy_unreg_out;
+ }
+
+ /* Fill in some of the advertised nl80211 supported features */
+ if (inff_feat_is_enabled(ifp, INFF_FEAT_SCAN_RANDOM_MAC))
+ wiphy->features |= NL80211_FEATURE_SCHED_SCAN_RANDOM_MAC_ADDR;
+
+ return cfg;
+
+wiphy_unreg_out:
+ wiphy_unregister(cfg->wiphy);
+wiphy_params_out:
+ inff_free_wiphy_params(wiphy);
+scan_out:
+ if (inff_socitype_is_ai(drvr->bus_if)) {
+ inff_deinit_escan(cfg);
+ kfree(cfg->scan.bss_buf);
+ cfg->scan.bss_buf = NULL;
+ }
+vif_ap_out:
+ /* Free AP vif if it was allocated */
+ if (inff_socitype_is_cp(drvr->bus_if)) {
+ inff_free_vif(vif_ap);
+ if (ifp_ap)
+ ifp_ap->vif = NULL;
+ }
+vif_out:
+ /* Always free primary vif */
+ inff_free_vif(vif);
+ if (ifp)
+ ifp->vif = NULL;
+cfg_extra_buf_out:
+ kfree(cfg->extra_buf);
+ cfg->extra_buf = NULL;
+cfg_out:
+ kfree(cfg);
+ return NULL;
+}
+
+void
+inff_cfg80211_detach(struct inff_cfg80211_info *cfg)
+{
+ if (!cfg)
+ return;
+
+ if (inff_socitype_is_ai(cfg->pub->bus_if))
+ inff_deinit_escan(cfg);
+ else if (inff_socitype_is_cp(cfg->pub->bus_if))
+ inff_cp_scan_abort(cfg->pub->bus_if->drvr);
+
+ wiphy_unregister(cfg->wiphy);
+ cfg->dongle_up = false; /* dongle down */
+
+ kfree(cfg->scan.bss_buf);
+ cfg->scan.bss_buf = NULL;
+ kfree(cfg->extra_buf);
+ cfg->extra_buf = NULL;
+
+ inff_clear_assoc_ies(cfg);
+ inff_free_wiphy_params(cfg->wiphy);
+
+ kfree(cfg);
+}
diff --git a/drivers/net/wireless/infineon/inffmac/cfg80211.h b/drivers/net/wireless/infineon/inffmac/cfg80211.h
new file mode 100644
index 000000000000..1df540c28cee
--- /dev/null
+++ b/drivers/net/wireless/infineon/inffmac/cfg80211.h
@@ -0,0 +1,309 @@
+/* SPDX-License-Identifier: ISC */
+/*
+ * Copyright (c) 2010 Broadcom Corporation
+ *
+ * Copyright (c) 2025-2026, Infineon Technologies AG, or an affiliate of Infineon Technologies AG.
+ * All rights reserved.
+ */
+
+#ifndef INFF_CFG80211_H
+#define INFF_CFG80211_H
+
+#include "main.h"
+#include "dev_cmd.h"
+#include "scan.h"
+#include "ie.h"
+#include "chan.h"
+
+#define WL_NUM_SCAN_MAX 10
+#define WL_TLV_INFO_MAX 1024
+#define WL_BSS_INFO_MAX 2048
+#define WL_ASSOC_INFO_MAX 512 /* assoc related fil max buf */
+#define WL_EXTRA_BUF_MAX 2048
+#define WL_ROAM_TRIGGER_LEVEL -75
+#define WL_ROAM_DELTA 20
+
+/* WME Access Category Indices (ACIs) */
+#define AC_BE 0 /* Best Effort */
+#define AC_BK 1 /* Background */
+#define AC_VI 2 /* Video */
+#define AC_VO 3 /* Voice */
+
+#define EDCF_AC_COUNT 4
+#define MAX_8021D_PRIO 8
+
+#define EDCF_ACI_MASK 0x60
+#define EDCF_ACI_SHIFT 5
+#define EDCF_ACM_MASK 0x10
+#define EDCF_ECWMIN_MASK 0x0f
+#define EDCF_ECWMAX_SHIFT 4
+#define EDCF_AIFSN_MASK 0x0f
+#define EDCF_AIFSN_MAX 15
+#define EDCF_ECWMAX_MASK 0xf0
+
+/* beacon loss timeout defaults */
+#define INFF_DEFAULT_BCN_TIMEOUT_ROAM_ON 2
+
+#define INFF_PM_WAIT_MAXRETRY 100
+
+#define WFA_OUI "\x50\x6F\x9A" /* WFA OUI */
+
+/* dongle configuration */
+struct inff_cfg80211_conf {
+ u32 frag_threshold;
+ u32 rts_threshold;
+ u32 retry_short;
+ u32 retry_long;
+};
+
+/* security information with currently associated ap */
+struct inff_cfg80211_security {
+ u32 wpa_versions;
+ u32 auth_type;
+ u32 cipher_pairwise;
+ u32 cipher_group;
+};
+
+enum inff_profile_fwsup {
+ INFF_PROFILE_FWSUP_NONE,
+ INFF_PROFILE_FWSUP_PSK,
+ INFF_PROFILE_FWSUP_1X,
+ INFF_PROFILE_FWSUP_SAE,
+ INFF_PROFILE_FWSUP_ROAM,
+ INFF_PROFILE_FWSUP_CP
+};
+
+/**
+ * enum inff_profile_fwauth - firmware authenticator profile
+ *
+ * @INFF_PROFILE_FWAUTH_NONE: no firmware authenticator
+ * @INFF_PROFILE_FWAUTH_PSK: authenticator for WPA/WPA2-PSK
+ * @INFF_PROFILE_FWAUTH_SAE: authenticator for SAE
+ */
+enum inff_profile_fwauth {
+ INFF_PROFILE_FWAUTH_NONE,
+ INFF_PROFILE_FWAUTH_PSK,
+ INFF_PROFILE_FWAUTH_SAE
+};
+
+enum inff_cfg80211_pm_state {
+ INFF_CFG80211_PM_STATE_RESUMED,
+ INFF_CFG80211_PM_STATE_RESUMING,
+ INFF_CFG80211_PM_STATE_SUSPENDED,
+ INFF_CFG80211_PM_STATE_SUSPENDING,
+};
+
+/**
+ * enum inff_mgmt_tx_status - mgmt frame tx status
+ *
+ * @INFF_MGMT_TX_ACK: mgmt frame acked
+ * @INFF_MGMT_TX_NOACK: mgmt frame not acked
+ * @INFF_MGMT_TX_OFF_CHAN_COMPLETED: off-channel complete
+ * @INFF_MGMT_TX_SEND_FRAME: mgmt frame tx is in progress
+ */
+enum inff_mgmt_tx_status {
+ INFF_MGMT_TX_ACK,
+ INFF_MGMT_TX_NOACK,
+ INFF_MGMT_TX_OFF_CHAN_COMPLETED,
+ INFF_MGMT_TX_SEND_FRAME
+};
+
+/* association inform */
+struct inff_cfg80211_connect_info {
+ u8 *req_ie;
+ s32 req_ie_len;
+ u8 *resp_ie;
+ s32 resp_ie_len;
+};
+
+/* assoc ie length */
+struct inff_cfg80211_assoc_ielen_le {
+ __le32 req_len;
+ __le32 resp_len;
+ __le32 flags;
+};
+
+/* Action Frame Constants */
+#define DOT11_ACTION_HDR_LEN 2 /* action frame category + action */
+#define DOT11_ACTION_CAT_OFF 0 /* category offset */
+#define DOT11_ACTION_ACT_OFF 1 /* action offset */
+
+#define IEEE80211_AF_DWELL_TIME 200
+#define IEEE80211_AF_MIN_DWELL_TIME 100
+#define IEEE80211_AF_MED_DWELL_TIME 400
+#define IEEE80211_AF_LONG_DWELL_TIME 1000
+#define IEEE80211_AF_TX_MAX_RETRY 5
+#define IEEE80211_AF_MAX_WAIT_TIME msecs_to_jiffies(2000)
+
+/**
+ * struct inff_pub_act_frame - WLAN MGMT Public Action Frame
+ *
+ * @category: Category Code
+ * @action: Action Code
+ * @oui: Action frame OUI
+ * @oui_type: Action frame OUI type
+ * @subtype: Action frame subtype
+ * @dialog_token: nonzero, identifies req/rsp transaction
+ * @elts: Variable length information elements
+ */
+struct inff_pub_act_frame {
+ u8 category;
+ u8 action;
+ u8 oui[3];
+ u8 oui_type;
+ u8 subtype;
+ u8 dialog_token;
+ u8 elts[];
+};
+
+/**
+ * struct inff_gas_pub_act_frame - WLAN MGMT GAS Public Action Frame
+ *
+ * @category: 0x04 Public Action Frame
+ * @action: 0x6c Advertisement Protocol
+ * @dialog_token: nonzero, identifies req/rsp transaction
+ * @query_data: Query Data. SD gas ireq SD gas iresp
+ */
+struct inff_gas_pub_act_frame {
+ u8 category;
+ u8 action;
+ u8 dialog_token;
+ u8 query_data[];
+};
+
+struct inff_cfg80211_edcf_acparam {
+ u8 ACI;
+ u8 ECW;
+ u16 TXOP; /* stored in network order (ls octet first) */
+};
+
+/**
+ * struct inff_cfg80211_vif_event - virtual interface event information.
+ *
+ * @vif_wq: waitqueue awaiting interface event from firmware.
+ * @vif_event_lock: protects other members in this structure.
+ * @action: either add, change, or delete.
+ * @vif: virtual interface object related to the event.
+ */
+struct inff_cfg80211_vif_event {
+ wait_queue_head_t vif_wq;
+ spinlock_t vif_event_lock; /* protects other members in this structure */
+ u8 action;
+ struct inff_cfg80211_vif *vif;
+};
+
+/**
+ * struct inff_cfg80211_info - dongle private data of cfg80211 interface
+ *
+ * @wiphy: wiphy object for cfg80211 interface.
+ * @conf: dongle configuration.
+ * @scan_request: cfg80211 scan request object.
+ * @conn_info: association info.
+ * @scan_status: scan activity on the dongle.
+ * @pub: common driver information.
+ * @channel: current channel.
+ * @int_escan_map: bucket map for which internal e-scan is done.
+ * @pwr_save: indicate whether dongle to support power save mode.
+ * @dongle_up: indicate whether dongle up or not.
+ * @roam_on: on/off switch for dongle self-roaming.
+ * @scan_tried: indicates if first scan attempted.
+ * @dcmd_buf: dcmd buffer.
+ * @extra_buf: mainly to grab assoc information.
+ * @debugfsdir: debugfs folder for this device.
+ * @escan_info: escan information.
+ * @escan_timeout_work: scan timeout delayed work.
+ * @vif_list: linked list of vif instances.
+ * @vif_cnt: number of vif instances.
+ * @vif_event: vif event signalling.
+ */
+struct inff_cfg80211_info {
+ struct wiphy *wiphy;
+ struct inff_cfg80211_conf conf;
+ struct cfg80211_scan_request *scan_request;
+ struct inff_cfg80211_connect_info conn_info;
+ unsigned long scan_status;
+ struct inff_pub *pub;
+ u32 channel;
+ u32 int_escan_map;
+ bool pwr_save;
+ bool dongle_up;
+ bool scan_tried;
+ u8 *dcmd_buf;
+ u8 *extra_buf;
+ struct dentry *debugfsdir;
+ struct escan_info escan_info;
+ struct wiphy_delayed_work escan_timeout_work;
+ struct list_head vif_list;
+ struct inff_cfg80211_vif_event vif_event;
+ struct completion vif_disabled;
+ u8 ac_priority[MAX_8021D_PRIO];
+ u8 pm_state;
+ u8 num_softap;
+ u8 mchan_conf;
+ struct inff_scan_info scan;
+ bool bands_reset_required;
+};
+
+static inline struct inff_cfg80211_info *
+wiphy_to_cfg(struct wiphy *wiphy)
+{
+ struct inff_pub *drvr = wiphy_priv(wiphy);
+
+ return drvr->config;
+}
+
+static inline struct inff_cfg80211_info *
+wdev_to_cfg(struct wireless_dev *wdev)
+{
+ return wiphy_to_cfg(wdev->wiphy);
+}
+
+static inline struct net_device *
+cfg_to_ndev(struct inff_cfg80211_info *cfg)
+{
+ return inff_get_ifp(cfg->pub, 0)->ndev;
+}
+
+static inline struct inff_cfg80211_info *
+ndev_to_cfg(struct net_device *ndev)
+{
+ return wdev_to_cfg(ndev->ieee80211_ptr);
+}
+
+static inline struct inff_cfg80211_vif *
+ndev_to_vif(struct net_device *ndev)
+{
+ struct inff_if *ifp = netdev_priv(ndev);
+
+ return ifp->vif;
+}
+
+s32 inff_inform_single_bss(struct inff_cfg80211_info *cfg,
+ struct inff_bss_info_le *bi);
+s32 inff_inform_bss(struct inff_cfg80211_info *cfg);
+s32 inff_compare_update_same_bss(struct inff_cfg80211_info *cfg,
+ struct inff_bss_info_le *bss,
+ struct inff_bss_info_le *bss_info_le);
+void inff_set_mpc(struct inff_if *ndev, int mpc);
+void inff_cfg80211_update_proto_addr_mode(struct wireless_dev *wdev);
+bool inff_ieee80211_is_dpp_pub_action(void *frame, u32 frame_len);
+bool inff_ieee80211_is_gas_pub_action(void *frame, u32 frame_len);
+void inff_print_action_frame(bool tx, void *frame, u32 frame_len);
+int inff_cfg80211_get_channel(struct wiphy *wiphy,
+ struct wireless_dev *wdev,
+ unsigned int link_id,
+ struct cfg80211_chan_def *chandef);
+struct cfg80211_ops *inff_cfg80211_get_ops(struct inff_mp_device *settings);
+void inff_cfg80211_free_netdev_priv(struct net_device *ndev);
+u8 inff_map_prio_to_prec(void *config, u8 prio);
+u8 inff_map_prio_to_aci(void *config, u8 prio);
+void inff_wifi_prioritize_acparams(const struct inff_cfg80211_edcf_acparam *acp,
+ u8 *priority);
+s32 inff_cfg80211_up(struct net_device *ndev);
+s32 inff_cfg80211_down(struct net_device *ndev);
+int inff_alloc_wiphy(struct device *dev, struct inff_mp_device *settings);
+void inff_free_wiphy(struct device *dev);
+struct inff_cfg80211_info *inff_cfg80211_attach(struct inff_pub *drvr,
+ struct cfg80211_ops *ops);
+void inff_cfg80211_detach(struct inff_cfg80211_info *cfg);
+#endif /* INFF_CFG80211_H */
--
2.43.0
^ permalink raw reply related [flat|nested] 26+ messages in thread* [PATCH wireless-next v3 13/25] wifi: inffmac: add interface.c/h
2026-09-02 16:50 [PATCH wireless-next v3 00/25] wifi: inffmac: introducing a driver for Infineon's new generation chipsets Gokul Sivakumar
` (11 preceding siblings ...)
2026-09-02 16:50 ` [PATCH wireless-next v3 12/25] wifi: inffmac: add cfg80211.c/h Gokul Sivakumar
@ 2026-09-02 16:50 ` Gokul Sivakumar
2026-09-02 16:50 ` [PATCH wireless-next v3 14/25] wifi: inffmac: add he.c/h Gokul Sivakumar
` (11 subsequent siblings)
24 siblings, 0 replies; 26+ messages in thread
From: Gokul Sivakumar @ 2026-09-02 16:50 UTC (permalink / raw)
To: linux-wireless, johannes
Cc: arend.vanspriel, wlan-kernel-dev-list, gokulkumar.sivakumar
Implementions the functions for creating, mananging and deleting various
type of WLAN interfaces.
Signed-off-by: Gokul Sivakumar <gokulkumar.sivakumar@infineon.com>
---
.../net/wireless/infineon/inffmac/interface.c | 496 ++++++++++++++++++
.../net/wireless/infineon/inffmac/interface.h | 155 ++++++
2 files changed, 651 insertions(+)
create mode 100644 drivers/net/wireless/infineon/inffmac/interface.c
create mode 100644 drivers/net/wireless/infineon/inffmac/interface.h
diff --git a/drivers/net/wireless/infineon/inffmac/interface.c b/drivers/net/wireless/infineon/inffmac/interface.c
new file mode 100644
index 000000000000..25cb28d08ebc
--- /dev/null
+++ b/drivers/net/wireless/infineon/inffmac/interface.c
@@ -0,0 +1,496 @@
+// SPDX-License-Identifier: ISC
+/*
+ * Copyright (c) 2010 Broadcom Corporation
+ *
+ * Copyright (c) 2025-2026, Infineon Technologies AG, or an affiliate of Infineon Technologies AG.
+ * All rights reserved.
+ */
+
+#include <linux/kernel.h>
+#include <linux/etherdevice.h>
+#include <linux/module.h>
+#include <linux/vmalloc.h>
+#include <linux/string.h>
+#include <net/cfg80211.h>
+#include <net/netlink.h>
+
+#include "main.h"
+#include "utils.h"
+#include "chan.h"
+#include "debug.h"
+#include "bus_proto.h"
+#include "fwsignal.h"
+#include "cfg80211.h"
+#include "interface.h"
+#include "feature.h"
+#include "dev_cmd.h"
+#include "dev_evt.h"
+#include "net.h"
+#include "chip.h"
+
+void
+inff_cp_cfg80211_link_down(struct inff_if *ifp)
+{
+ struct net_device *ndev = ifp->ndev;
+
+ if (!test_and_clear_bit(INFF_VIF_STATUS_CONNECTED, &ifp->vif->sme_state))
+ return;
+
+ cfg80211_disconnected(ndev, 0, NULL, 0, true, GFP_KERNEL);
+}
+
+void
+inff_link_down(struct inff_cfg80211_vif *vif, u16 reason,
+ bool locally_generated)
+{
+ struct inff_cfg80211_info *cfg = wiphy_to_cfg(vif->wdev.wiphy);
+ struct inff_pub *drvr = cfg->pub;
+ bool bus_up = drvr->bus_if->state == INFF_BUS_UP;
+ s32 err = 0;
+
+ if (test_and_clear_bit(INFF_VIF_STATUS_CONNECTED, &vif->sme_state)) {
+ if (bus_up) {
+ inff_dbg(INFO, "Call WLC_DISASSOC to stop excess roaming\n");
+ err = inff_fwcmd_data_set(vif->ifp,
+ INFF_C_DISASSOC, NULL, 0);
+ if (err)
+ iphy_err(drvr, "WLC_DISASSOC failed (%d)\n", err);
+ }
+
+ if (vif->wdev.iftype == NL80211_IFTYPE_STATION)
+ cfg80211_disconnected(vif->wdev.netdev, reason, NULL, 0,
+ locally_generated, GFP_KERNEL);
+ }
+ clear_bit(INFF_VIF_STATUS_CONNECTING, &vif->sme_state);
+ clear_bit(INFF_VIF_STATUS_EAP_SUCCESS, &vif->sme_state);
+ clear_bit(INFF_VIF_STATUS_ASSOC_SUCCESS, &vif->sme_state);
+ clear_bit(INFF_SCAN_STATUS_SUPPRESS, &cfg->scan_status);
+}
+
+static bool
+inff_vif_event_equals(struct inff_cfg80211_vif_event *event,
+ u8 action)
+{
+ u8 evt_action;
+
+ spin_lock(&event->vif_event_lock);
+ evt_action = event->action;
+ spin_unlock(&event->vif_event_lock);
+ return evt_action == action;
+}
+
+void
+inff_arm_vif_event(struct inff_cfg80211_info *cfg,
+ struct inff_cfg80211_vif *vif)
+{
+ struct inff_cfg80211_vif_event *event = &cfg->vif_event;
+
+ spin_lock(&event->vif_event_lock);
+ event->vif = vif;
+ event->action = 0;
+ spin_unlock(&event->vif_event_lock);
+}
+
+void
+inff_disarm_vif_event(struct inff_cfg80211_info *cfg)
+{
+ struct inff_cfg80211_vif_event *event = &cfg->vif_event;
+
+ spin_lock(&event->vif_event_lock);
+ event->vif = NULL;
+ event->action = 0;
+ spin_unlock(&event->vif_event_lock);
+}
+
+bool
+inff_vif_event_armed(struct inff_cfg80211_info *cfg)
+{
+ struct inff_cfg80211_vif_event *event = &cfg->vif_event;
+ bool armed;
+
+ spin_lock(&event->vif_event_lock);
+ armed = (event->vif) ? true : false;
+ spin_unlock(&event->vif_event_lock);
+
+ return armed;
+}
+
+int
+inff_wait_vif_event(struct inff_cfg80211_info *cfg,
+ u8 action, ulong timeout)
+{
+ struct inff_cfg80211_vif_event *event = &cfg->vif_event;
+
+ return wait_event_timeout(event->vif_wq,
+ inff_vif_event_equals(event, action),
+ timeout);
+}
+
+int
+inff_vif_change_validate(struct inff_cfg80211_info *cfg,
+ struct inff_cfg80211_vif *vif,
+ enum nl80211_iftype new_type)
+{
+ struct inff_cfg80211_vif *pos;
+ bool check_combos = false;
+ int ret = 0;
+ struct iface_combination_params params = {
+ .num_different_channels = 1,
+ };
+
+ list_for_each_entry(pos, &cfg->vif_list, list)
+ if (pos == vif) {
+ params.iftype_num[new_type]++;
+ } else {
+ /* concurrent interfaces so need check combinations */
+ check_combos = true;
+ params.iftype_num[pos->wdev.iftype]++;
+ }
+
+ if (check_combos)
+ ret = cfg80211_check_combinations(cfg->wiphy, ¶ms);
+
+ return ret;
+}
+
+bool
+inff_check_vif_up(struct inff_cfg80211_vif *vif)
+{
+ if (!test_bit(INFF_VIF_STATUS_READY, &vif->sme_state)) {
+ inff_dbg(INFO, "device is not ready : status (%lu)\n",
+ vif->sme_state);
+ return false;
+ }
+ return true;
+}
+
+static void
+inff_set_vif_macaddr(struct inff_if *ifp, u8 *mac_addr)
+{
+ u8 mac_idx = ifp->drvr->sta_mac_idx;
+
+ /* set difference MAC address with locally administered bit */
+ memcpy(mac_addr, ifp->mac_addr, ETH_ALEN);
+ mac_addr[0] |= 0x02;
+ mac_addr[3] ^= mac_idx ? 0xC0 : 0xA0;
+ mac_idx++;
+ mac_idx = mac_idx % 2;
+ ifp->drvr->sta_mac_idx = mac_idx;
+}
+
+static int
+inff_vif_req_create(struct inff_if *ifp, enum nl80211_iftype type,
+ u8 *vif_macaddr)
+{
+ struct inff_pub *drvr = ifp->drvr;
+ struct inff_interface_create_v3 iface_v3;
+ u8 macaddr[ETH_ALEN];
+ u32 iface_create_ver = 0;
+ u8 iftype;
+ int err;
+
+ switch (type) {
+ case NL80211_IFTYPE_STATION:
+ iftype = INFF_INTERFACE_CREATE_STA;
+ if (!is_zero_ether_addr(vif_macaddr))
+ memcpy(macaddr, vif_macaddr, ETH_ALEN);
+ else
+ inff_set_vif_macaddr(ifp, macaddr);
+ break;
+ case NL80211_IFTYPE_AP:
+ iftype = INFF_INTERFACE_CREATE_AP;
+ inff_set_vif_macaddr(ifp, macaddr);
+ break;
+ default:
+ err = -EOPNOTSUPP;
+ goto fail;
+ }
+
+ switch (drvr->bus_if->chip) {
+ case INF_CC_ACW74XX_CHIP_ID:
+ fallthrough;
+ case INF_CC_CYW5591X_CHIP_ID:
+ /* interface_create version 3+ */
+ /* get supported version from firmware side */
+ err = inff_fwcmd_bsscfg_int_get(ifp, "interface_create",
+ &iface_create_ver);
+ if (err) {
+ iphy_err(drvr,
+ "failed to get interface create ver, err=%d\n",
+ err);
+ err = -EOPNOTSUPP;
+ break;
+ }
+
+ switch (iface_create_ver) {
+ case INFF_INTERFACE_CREATE_VER_3:
+ memset(&iface_v3, 0, sizeof(iface_v3));
+ iface_v3.ver = INFF_INTERFACE_CREATE_VER_3;
+ iface_v3.flags = INFF_INTERFACE_MAC_USE;
+ iface_v3.iftype = iftype;
+ memcpy(iface_v3.mac_addr, macaddr, ETH_ALEN);
+
+ err = inff_fwcmd_iovar_data_get(ifp, "interface_create",
+ &iface_v3, sizeof(iface_v3));
+ break;
+ default:
+ inff_err("unsupported interface create ver (v%d)\n",
+ iface_create_ver);
+ err = -EOPNOTSUPP;
+ break;
+ }
+ break;
+ default:
+ err = -EOPNOTSUPP;
+ break;
+ }
+fail:
+ if (err) {
+ iphy_err(drvr,
+ "failed to request interface creation, err=%d\n",
+ err);
+ return -EIO;
+ }
+
+ inff_dbg(INFO, "requested interface creation\n");
+
+ return 0;
+}
+
+/**
+ * inff_apsta_add_vif() - create a new AP or STA virtual interface
+ *
+ * @wiphy: wiphy device of new interface.
+ * @name: name of the new interface.
+ * @params: contains mac address for AP or STA device.
+ * @type: interface type.
+ *
+ * Return: pointer to new vif on success, ERR_PTR(-errno) if not
+ */
+struct wireless_dev *
+inff_apsta_add_vif(struct wiphy *wiphy, const char *name,
+ struct vif_params *params,
+ enum nl80211_iftype type)
+{
+ struct inff_cfg80211_info *cfg = wiphy_to_cfg(wiphy);
+ struct inff_if *ifp = netdev_priv(cfg_to_ndev(cfg));
+ struct inff_pub *drvr = cfg->pub;
+ struct inff_cfg80211_vif *vif;
+ int err;
+
+ if (type != NL80211_IFTYPE_STATION && type != NL80211_IFTYPE_AP)
+ return ERR_PTR(-EINVAL);
+
+ if (inff_vif_event_armed(cfg))
+ return ERR_PTR(-EBUSY);
+
+ inff_dbg(INFO, "Adding vif \"%s\"\n", name);
+
+ vif = inff_alloc_vif(cfg, type);
+ if (IS_ERR(vif))
+ return (struct wireless_dev *)vif;
+
+ inff_arm_vif_event(cfg, vif);
+
+ err = inff_vif_req_create(ifp, type, params->macaddr);
+ if (err)
+ goto fail;
+
+ /* wait for firmware event */
+ err = inff_wait_vif_event(cfg, INFF_E_IF_ADD,
+ INFF_VIF_EVENT_TIMEOUT);
+ if (!err) {
+ iphy_err(drvr, "vif event timeout occurred\n");
+ err = -EIO;
+ goto fail;
+ }
+
+ /* interface created in firmware */
+ ifp = vif->ifp;
+ if (!ifp) {
+ iphy_err(drvr, "no if pointer provided\n");
+ err = -ENOENT;
+ goto fail;
+ }
+
+ strscpy(ifp->ndev->name, name, sizeof(ifp->ndev->name));
+ err = inff_net_attach(ifp, true);
+ if (err) {
+ iphy_err(drvr, "Registering netdevice failed\n");
+ free_netdev(ifp->ndev);
+ goto fail;
+ }
+ inff_disarm_vif_event(cfg);
+
+ return &ifp->vif->wdev;
+
+fail:
+ inff_disarm_vif_event(cfg);
+ inff_free_vif(vif);
+ return ERR_PTR(err);
+}
+
+/**
+ * inff_setup_ifmodes() - determine interface modes and combinations.
+ *
+ * @wiphy: wiphy object.
+ * @ifp: interface object needed for feat module api.
+ *
+ * The interface modes and combinations are determined dynamically here
+ * based on firmware functionality.
+ *
+ * no mbss:
+ *
+ * #STA <= 1, #AP <= 1, channels = 1, 2 total
+ *
+ * mbss:
+ *
+ * #STA <= 1, #AP <= 1, channels = 1, 2 total
+ * #AP <= 4, matching BI, channels = 1, 4 total
+ *
+ * rsdb:
+ * #STA <= 1, #AP <= 2, channels = 2, 4 total
+ *
+ * Return: 0 on success, negative errno on failure
+ */
+int
+inff_setup_ifmodes(struct wiphy *wiphy, struct inff_if *ifp)
+{
+ struct ieee80211_iface_combination *combo = NULL;
+ struct ieee80211_iface_limit *c0_limits = NULL;
+ struct ieee80211_iface_limit *mbss_limits = NULL;
+ bool mbss, rsdb;
+ int i, c, n_combos, n_limits;
+
+ if (inff_socitype_is_cp(ifp->drvr->bus_if)) {
+ n_combos = 1;
+ combo = kcalloc(n_combos, sizeof(*combo), GFP_KERNEL);
+ if (!combo)
+ goto err;
+
+ wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) |
+ BIT(NL80211_IFTYPE_AP);
+
+ c = 0;
+ i = 0;
+ n_limits = 2;
+ c0_limits = kcalloc(n_limits, sizeof(*c0_limits), GFP_KERNEL);
+ if (!c0_limits)
+ goto err;
+
+ combo[c].num_different_channels = 1;
+ c0_limits[i].max = 1;
+ c0_limits[i++].types = BIT(NL80211_IFTYPE_STATION);
+
+ c0_limits[i].max = 1;
+ c0_limits[i++].types = BIT(NL80211_IFTYPE_AP);
+
+ combo[c].max_interfaces = i;
+
+ combo[c].n_limits = i;
+ combo[c].limits = c0_limits;
+
+ } else {
+ mbss = inff_feat_is_enabled(ifp, INFF_FEAT_MBSS);
+ rsdb = inff_feat_is_enabled(ifp, INFF_FEAT_RSDB);
+
+ n_combos = 1 + !!mbss;
+ combo = kzalloc_objs(*combo, n_combos);
+ if (!combo)
+ goto err;
+
+ wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) |
+ BIT(NL80211_IFTYPE_AP);
+
+ c = 0;
+ i = 0;
+ n_limits = 2;
+
+ c0_limits = kzalloc_objs(*c0_limits, n_limits);
+ if (!c0_limits)
+ goto err;
+
+ combo[c].num_different_channels = 1 + rsdb;
+ c0_limits[i].max = 1;
+ c0_limits[i++].types = BIT(NL80211_IFTYPE_STATION);
+
+ if (rsdb) {
+ c0_limits[i].max = 2;
+ c0_limits[i++].types = BIT(NL80211_IFTYPE_AP);
+ combo[c].max_interfaces = 3;
+ } else {
+ c0_limits[i].max = 1;
+ c0_limits[i++].types = BIT(NL80211_IFTYPE_AP);
+ combo[c].max_interfaces = i;
+ }
+ combo[c].n_limits = i;
+ combo[c].limits = c0_limits;
+
+ if (mbss) {
+ c++;
+ i = 0;
+ n_limits = 1;
+ mbss_limits = kzalloc_objs(*mbss_limits, n_limits);
+ if (!mbss_limits)
+ goto err;
+ mbss_limits[i].max = 4;
+ mbss_limits[i++].types = BIT(NL80211_IFTYPE_AP);
+ combo[c].beacon_int_infra_match = true;
+ combo[c].num_different_channels = 1;
+ combo[c].max_interfaces = 4;
+ combo[c].n_limits = i;
+ combo[c].limits = mbss_limits;
+ }
+ }
+ wiphy->n_iface_combinations = n_combos;
+ wiphy->iface_combinations = combo;
+ return 0;
+
+err:
+ kfree(c0_limits);
+ kfree(mbss_limits);
+ kfree(combo);
+ return -ENOMEM;
+}
+
+struct inff_cfg80211_vif *
+inff_alloc_vif(struct inff_cfg80211_info *cfg,
+ enum nl80211_iftype type)
+{
+ struct inff_cfg80211_vif *vif_walk;
+ struct inff_cfg80211_vif *vif;
+ bool mbss;
+ struct inff_if *ifp = inff_get_ifp(cfg->pub, 0);
+
+ inff_dbg(TRACE, "allocating virtual interface (size=%zu)\n",
+ sizeof(*vif));
+ vif = kzalloc_obj(*vif);
+ if (!vif)
+ return ERR_PTR(-ENOMEM);
+
+ vif->wdev.wiphy = cfg->wiphy;
+ vif->wdev.iftype = type;
+
+ if (type == NL80211_IFTYPE_AP &&
+ inff_feat_is_enabled(ifp, INFF_FEAT_MBSS)) {
+ mbss = false;
+ list_for_each_entry(vif_walk, &cfg->vif_list, list) {
+ if (vif_walk->wdev.iftype == NL80211_IFTYPE_AP) {
+ mbss = true;
+ break;
+ }
+ }
+ vif->mbss = mbss;
+ }
+
+ init_completion(&vif->mgmt_tx);
+ list_add_tail(&vif->list, &cfg->vif_list);
+ return vif;
+}
+
+void
+inff_free_vif(struct inff_cfg80211_vif *vif)
+{
+ list_del(&vif->list);
+ kfree(vif);
+}
diff --git a/drivers/net/wireless/infineon/inffmac/interface.h b/drivers/net/wireless/infineon/inffmac/interface.h
new file mode 100644
index 000000000000..ab0ad5f1802d
--- /dev/null
+++ b/drivers/net/wireless/infineon/inffmac/interface.h
@@ -0,0 +1,155 @@
+/* SPDX-License-Identifier: ISC */
+/*
+ * Copyright (c) 2010 Broadcom Corporation
+ *
+ * Copyright (c) 2025-2026, Infineon Technologies AG, or an affiliate of Infineon Technologies AG.
+ * All rights reserved.
+ */
+
+#ifndef INFF_INTERFACE_H
+#define INFF_INTERFACE_H
+
+#define INFF_VIF_EVENT_TIMEOUT msecs_to_jiffies(1500)
+
+#define INFF_INTERFACE_CREATE_VER_2 2
+#define INFF_INTERFACE_CREATE_VER_3 3
+
+#define INFF_INTERFACE_MAC_DONT_USE 0x0
+#define INFF_INTERFACE_MAC_USE 0x2
+
+#define INFF_INTERFACE_CREATE_STA 0x0
+#define INFF_INTERFACE_CREATE_AP 0x1
+
+#define INFF_INTERFACE_PRI_BSSCFGIDX (0)
+#define INFF_INTERFACE_PRI_IFIDX (0)
+
+#define INFF_INTERFACE_SEC_BSSCFGIDX (1)
+#define INFF_INTERFACE_SEC_IFIDX (1)
+
+struct inff_interface_create_v2 {
+ u16 ver; /* structure version */
+ u8 pad1[2];
+ u32 flags; /* flags for operation */
+ u8 mac_addr[ETH_ALEN]; /* MAC address */
+ u8 iftype; /* type of interface created */
+ u8 pad2;
+ u32 wlc_index; /* optional for wlc index */
+};
+
+struct inff_interface_create_v3 {
+ u16 ver; /* structure version */
+ u16 len; /* length of structure + data */
+ u16 fixed_len; /* length of structure */
+ u8 iftype; /* type of interface created */
+ u8 wlc_index; /* optional for wlc index */
+ u32 flags; /* flags for operation */
+ u8 mac_addr[ETH_ALEN]; /* MAC address */
+ u8 bssid[ETH_ALEN]; /* optional for BSSID */
+ u8 if_index; /* interface index request */
+ u8 pad[3];
+ u8 data[]; /* Optional for specific data */
+};
+
+#define WL_IOV_OP_BSSCFG_DISABLE 0
+#define WL_IOV_OP_BSSCFG_ENABLE 1
+#define WL_IOV_OP_MANUAL_STA_BSSCFG_CREATE 2
+#define WL_IOV_OP_MANUAL_AP_BSSCFG_CREATE 3
+
+/**
+ * enum inff_vif_status - bit indices for vif status.
+ *
+ * @INFF_VIF_STATUS_READY: ready for operation.
+ * @INFF_VIF_STATUS_CONNECTING: connect/join in progress.
+ * @INFF_VIF_STATUS_CONNECTED: connected/joined successfully.
+ * @INFF_VIF_STATUS_DISCONNECTING: disconnect/disable in progress.
+ * @INFF_VIF_STATUS_AP_CREATED: AP operation started.
+ * @INFF_VIF_STATUS_EAP_SUCCUSS: EAPOL handshake successful.
+ * @INFF_VIF_STATUS_ASSOC_SUCCESS: successful SET_SSID received.
+ */
+enum inff_vif_status {
+ INFF_VIF_STATUS_READY,
+ INFF_VIF_STATUS_CONNECTING,
+ INFF_VIF_STATUS_CONNECTED,
+ INFF_VIF_STATUS_DISCONNECTING,
+ INFF_VIF_STATUS_AP_CREATED,
+ INFF_VIF_STATUS_EAP_SUCCESS,
+ INFF_VIF_STATUS_ASSOC_SUCCESS,
+};
+
+/**
+ * struct inff_vif_saved_ie - holds saved IEs for a virtual interface.
+ *
+ * @probe_req_ie: IE info for probe request.
+ * @probe_res_ie: IE info for probe response.
+ * @beacon_ie: IE info for beacon frame.
+ * @assoc_req_ie: IE info for association request frame.
+ * @assoc_res_ie: IE info for association response frame.
+ * @probe_req_ie_len: IE info length for probe request.
+ * @probe_res_ie_len: IE info length for probe response.
+ * @beacon_ie_len: IE info length for beacon frame.
+ * @assoc_req_ie_len: IE info length for association request frame.
+ * @assoc_res_ie_len: IE info length for association response frame.
+ */
+struct inff_vif_saved_ie {
+ u8 probe_req_ie[IE_MAX_LEN];
+ u8 probe_res_ie[IE_MAX_LEN];
+ u8 beacon_ie[IE_MAX_LEN];
+ u8 assoc_req_ie[IE_MAX_LEN];
+ u8 assoc_res_ie[IE_MAX_LEN];
+ u32 probe_req_ie_len;
+ u32 probe_res_ie_len;
+ u32 beacon_ie_len;
+ u32 assoc_req_ie_len;
+ u32 assoc_res_ie_len;
+};
+
+/**
+ * struct inff_cfg80211_vif - virtual interface specific information.
+ *
+ * @ifp: lower layer interface pointer
+ * @wdev: wireless device.
+ * @sme_state: SME state using enum inff_vif_status bits.
+ * @saved_id: saved IE info for a vif.
+ * @list: linked list.
+ * @mgmt_rx_reg: registered rx mgmt frame types.
+ * @mbss: Multiple BSS type, set if not first AP
+ * @is_11d: beacon contains country IE, enable regulatory 802.11d support
+ */
+struct inff_cfg80211_vif {
+ struct inff_if *ifp;
+ struct wireless_dev wdev;
+ unsigned long sme_state;
+ struct inff_vif_saved_ie saved_ie;
+ struct list_head list;
+ struct completion mgmt_tx;
+ unsigned long mgmt_tx_status;
+ u32 mgmt_tx_id;
+ u16 mgmt_rx_reg;
+ bool mbss;
+ int is_11d;
+};
+
+void inff_link_down(struct inff_cfg80211_vif *vif, u16 reason,
+ bool locally_generated);
+void inff_arm_vif_event(struct inff_cfg80211_info *cfg,
+ struct inff_cfg80211_vif *vif);
+void inff_disarm_vif_event(struct inff_cfg80211_info *cfg);
+bool inff_vif_event_armed(struct inff_cfg80211_info *cfg);
+int inff_wait_vif_event(struct inff_cfg80211_info *cfg,
+ u8 action, ulong timeout);
+int inff_vif_change_validate(struct inff_cfg80211_info *cfg,
+ struct inff_cfg80211_vif *vif,
+ enum nl80211_iftype new_type);
+bool inff_check_vif_up(struct inff_cfg80211_vif *vif);
+struct wireless_dev *
+inff_apsta_add_vif(struct wiphy *wiphy, const char *name,
+ struct vif_params *params,
+ enum nl80211_iftype type);
+int inff_setup_ifmodes(struct wiphy *wiphy, struct inff_if *ifp);
+struct inff_cfg80211_vif *
+inff_alloc_vif(struct inff_cfg80211_info *cfg, enum nl80211_iftype type);
+void inff_free_vif(struct inff_cfg80211_vif *vif);
+
+void inff_cp_cfg80211_link_down(struct inff_if *ifp);
+
+#endif /* INFF_INTERFACE_H */
--
2.43.0
^ permalink raw reply related [flat|nested] 26+ messages in thread* [PATCH wireless-next v3 14/25] wifi: inffmac: add he.c/h
2026-09-02 16:50 [PATCH wireless-next v3 00/25] wifi: inffmac: introducing a driver for Infineon's new generation chipsets Gokul Sivakumar
` (12 preceding siblings ...)
2026-09-02 16:50 ` [PATCH wireless-next v3 13/25] wifi: inffmac: add interface.c/h Gokul Sivakumar
@ 2026-09-02 16:50 ` Gokul Sivakumar
2026-09-02 16:50 ` [PATCH wireless-next v3 15/25] wifi: inffmac: add feature.c/h Gokul Sivakumar
` (10 subsequent siblings)
24 siblings, 0 replies; 26+ messages in thread
From: Gokul Sivakumar @ 2026-09-02 16:50 UTC (permalink / raw)
To: linux-wireless, johannes
Cc: arend.vanspriel, wlan-kernel-dev-list, gokulkumar.sivakumar
Collect the 802.11ax HE capabilities of the Device and register it as part
of the wiphy capabilities in the cfg80211 driver.
Signed-off-by: Gokul Sivakumar <gokulkumar.sivakumar@infineon.com>
---
drivers/net/wireless/infineon/inffmac/he.c | 112 +++++++++++++++++++++
drivers/net/wireless/infineon/inffmac/he.h | 27 +++++
2 files changed, 139 insertions(+)
create mode 100644 drivers/net/wireless/infineon/inffmac/he.c
create mode 100644 drivers/net/wireless/infineon/inffmac/he.h
diff --git a/drivers/net/wireless/infineon/inffmac/he.c b/drivers/net/wireless/infineon/inffmac/he.c
new file mode 100644
index 000000000000..334c12f5a49c
--- /dev/null
+++ b/drivers/net/wireless/infineon/inffmac/he.c
@@ -0,0 +1,112 @@
+// SPDX-License-Identifier: ISC
+/*
+ * Copyright (c) 2025-2026, Infineon Technologies AG, or an affiliate of Infineon Technologies AG.
+ * All rights reserved.
+ */
+
+#include <linux/bitfield.h>
+#include "he.h"
+#include "dev_cmd.h"
+#include "cfg80211.h"
+#include "debug.h"
+
+static struct ieee80211_sband_iftype_data sdata[NUM_NL80211_BANDS];
+
+int
+inff_he_get_enable(struct inff_if *ifp, u8 *param, int param_len)
+{
+ s32 ret = 0;
+
+ ret = inff_fwcmd_xtlv_data_get(ifp, "he", INFF_HE_CMD_ENAB, param, param_len);
+ if (unlikely(ret))
+ iphy_err(ifp->drvr, "failed to check if HE is enabled");
+
+ return ret;
+}
+
+void
+inff_he_update_wiphy_cap(struct inff_if *ifp)
+{
+ struct inff_pub *drvr = ifp->drvr;
+ struct wiphy *wiphy = drvr->wiphy;
+ struct ieee80211_supported_band *band;
+ struct ieee80211_sband_iftype_data *data;
+ struct ieee80211_sta_he_cap *he_cap;
+ struct ieee80211_he_cap_elem *he_cap_elem;
+ struct ieee80211_he_mcs_nss_supp *he_mcs;
+ u8 mac_cap_info[HE_MAC_CAP_INFO_SIZE] = { 0 };
+ u8 phy_cap_info[HE_PHY_CAP_INFO_SIZE] = { 0 };
+ u16 capa = 0;
+ u8 hemode = 0;
+ int idx = 1, i = 0, j = 0;
+
+ /* HE mode */
+ inff_he_get_enable(ifp, &hemode, sizeof(hemode));
+ if (!hemode)
+ return;
+
+ inff_dbg(INFO, "HE Enabled\n");
+
+ /* HE MAC Capabilities Information */
+ if (inff_fwcmd_xtlv_data_get(ifp, "he", INFF_HE_CMD_MACCAP, mac_cap_info,
+ HE_MAC_CAP_INFO_SIZE))
+ iphy_err(drvr, "HE MACCAP error\n");
+
+ /* HE PHY Capabilities Information */
+ if (inff_fwcmd_xtlv_data_get(ifp, "he", INFF_HE_CMD_PHYCAP, phy_cap_info,
+ HE_PHY_CAP_INFO_SIZE))
+ iphy_err(drvr, "HE PHYCAP error\n");
+
+ /* Update HE Capab for each Band */
+ for (i = 0; i < ARRAY_SIZE(wiphy->bands); i++) {
+ band = wiphy->bands[i];
+ if (!band)
+ continue;
+
+ data = &sdata[band->band];
+ he_cap = &data->he_cap;
+ he_cap_elem = &he_cap->he_cap_elem;
+ he_mcs = &he_cap->he_mcs_nss_supp;
+
+ switch (band->band) {
+ case NL80211_BAND_6GHZ:
+ /* HE 6 GHz band capabilities */
+ capa = (FIELD_PREP(IEEE80211_HE_6GHZ_CAP_MIN_MPDU_START,
+ IEEE80211_HT_MPDU_DENSITY_8) |
+ FIELD_PREP(IEEE80211_HE_6GHZ_CAP_MAX_AMPDU_LEN_EXP,
+ IEEE80211_VHT_MAX_AMPDU_1024K) |
+ FIELD_PREP(IEEE80211_HE_6GHZ_CAP_MAX_MPDU_LEN,
+ IEEE80211_VHT_CAP_MAX_MPDU_LENGTH_11454));
+ data->he_6ghz_capa.capa = cpu_to_le16(capa);
+
+ /* Band 6GHz supports HE, so */
+ fallthrough;
+
+ case NL80211_BAND_5GHZ:
+ /* Band 5GHz supports HE, so */
+ fallthrough;
+
+ case NL80211_BAND_2GHZ:
+ /* Band 2GHz supports HE */
+ data->types_mask = BIT(NL80211_IFTYPE_STATION) | BIT(NL80211_IFTYPE_AP);
+ data->he_cap.has_he = true;
+
+ for (j = 0; j < HE_MAC_CAP_INFO_SIZE; j++)
+ he_cap_elem->mac_cap_info[j] = mac_cap_info[j];
+
+ for (j = 0; j < HE_PHY_CAP_INFO_SIZE; j++)
+ he_cap_elem->phy_cap_info[j] = phy_cap_info[j];
+
+ /* HE Supported MCS and NSS Set */
+ he_mcs->rx_mcs_80 = cpu_to_le16(0xfffa);
+ he_mcs->tx_mcs_80 = cpu_to_le16(0xfffa);
+
+ _ieee80211_set_sband_iftype_data(band, data, idx);
+
+ break;
+
+ default:
+ break;
+ }
+ }
+}
diff --git a/drivers/net/wireless/infineon/inffmac/he.h b/drivers/net/wireless/infineon/inffmac/he.h
new file mode 100644
index 000000000000..643814a1bc42
--- /dev/null
+++ b/drivers/net/wireless/infineon/inffmac/he.h
@@ -0,0 +1,27 @@
+/* SPDX-License-Identifier: ISC */
+/*
+ * Copyright (c) 2025-2026, Infineon Technologies AG, or an affiliate of Infineon Technologies AG.
+ * All rights reserved.
+ */
+
+#ifndef INFF_HE_H
+#define INFF_HE_H
+
+#include "main.h"
+
+#define HE_MAC_CAP_INFO_SIZE 6
+#define HE_PHY_CAP_INFO_SIZE 11
+
+/**
+ * enum inff_he_cmd - HE iovar subcmds handled by firmware HE module
+ */
+enum inff_he_cmd {
+ INFF_HE_CMD_ENAB = 0,
+ INFF_HE_CMD_MACCAP = 16,
+ INFF_HE_CMD_PHYCAP = 17,
+};
+
+int inff_he_get_enable(struct inff_if *ifp, u8 *param, int param_len);
+void inff_he_update_wiphy_cap(struct inff_if *ifp);
+
+#endif /* INFF_HE_H */
--
2.43.0
^ permalink raw reply related [flat|nested] 26+ messages in thread* [PATCH wireless-next v3 15/25] wifi: inffmac: add feature.c/h
2026-09-02 16:50 [PATCH wireless-next v3 00/25] wifi: inffmac: introducing a driver for Infineon's new generation chipsets Gokul Sivakumar
` (13 preceding siblings ...)
2026-09-02 16:50 ` [PATCH wireless-next v3 14/25] wifi: inffmac: add he.c/h Gokul Sivakumar
@ 2026-09-02 16:50 ` Gokul Sivakumar
2026-09-02 16:50 ` [PATCH wireless-next v3 16/25] wifi: inffmac: add bus_proto.c/h Gokul Sivakumar
` (9 subsequent siblings)
24 siblings, 0 replies; 26+ messages in thread
From: Gokul Sivakumar @ 2026-09-02 16:50 UTC (permalink / raw)
To: linux-wireless, johannes
Cc: arend.vanspriel, wlan-kernel-dev-list, gokulkumar.sivakumar
Driver implementation to detect and maintain the feature capabilities of
the currently loaded Device firmware. All other driver source files
utilizes these firmware feature flags for finding if the firmware is
supporting a specific operation or functionality.
Signed-off-by: Gokul Sivakumar <gokulkumar.sivakumar@infineon.com>
---
.../net/wireless/infineon/inffmac/feature.c | 264 ++++++++++++++++++
.../net/wireless/infineon/inffmac/feature.h | 121 ++++++++
2 files changed, 385 insertions(+)
create mode 100644 drivers/net/wireless/infineon/inffmac/feature.c
create mode 100644 drivers/net/wireless/infineon/inffmac/feature.h
diff --git a/drivers/net/wireless/infineon/inffmac/feature.c b/drivers/net/wireless/infineon/inffmac/feature.c
new file mode 100644
index 000000000000..e748ef02b558
--- /dev/null
+++ b/drivers/net/wireless/infineon/inffmac/feature.c
@@ -0,0 +1,264 @@
+// SPDX-License-Identifier: ISC
+/*
+ * Copyright (c) 2014 Broadcom Corporation
+ *
+ * Copyright (c) 2025-2026, Infineon Technologies AG, or an affiliate of Infineon Technologies AG.
+ * All rights reserved.
+ */
+
+#include <linux/netdevice.h>
+#include <linux/module.h>
+
+#include "main.h"
+#include "debug.h"
+#include "dev_cmd.h"
+#include "feature.h"
+#include "bus_proto.h"
+
+static const char * const inff_feat_names[] = {
+ "MBSS",
+ "MCHAN",
+ "RSV2",
+ "RSV3",
+ "RSV4",
+ "RSDB",
+ "RSV6",
+ "SCAN_RANDOM_MAC",
+ "RSV8",
+ "RSV9",
+ "RSV10",
+ "MFP",
+ "GSCAN",
+ "FWSUP",
+ "RSV14",
+ "RSV15",
+ "RSV16",
+ "RSV17",
+ "DOT11H",
+ "SAE",
+ "FWAUTH",
+ "RSV21",
+ "SCAN_V2",
+ "RSV23",
+ "RSV24",
+ "RSV25",
+ "SAE_EXT",
+ "FBT",
+ "OKC",
+ "GCMP",
+ "RSV30",
+ "RSV31",
+ "RSV32",
+ "PROPTXSTATUS",
+ "OWE",
+ "RSV35",
+ "RSV36",
+ "GTKO",
+ "MCHAN_CONFIG",
+ "RSV39",
+ "LAST"
+};
+
+struct inff_feat_fwcap {
+ enum inff_feat_id feature;
+ const char * const fwcap_id;
+};
+
+static const struct inff_feat_fwcap inff_fwcap_map[] = {
+ { INFF_FEAT_MBSS, "mbss" },
+ { INFF_FEAT_MCHAN, "mchan" },
+ { INFF_FEAT_DOT11H, "802.11h" },
+ { INFF_FEAT_SAE, "sae " },
+ { INFF_FEAT_FWAUTH, "idauth" },
+ { INFF_FEAT_SAE_EXT, "sae_ext " },
+ { INFF_FEAT_FBT, "fbt " },
+ { INFF_FEAT_OKC, "okc" },
+ { INFF_FEAT_GCMP, "gcmp" },
+ { INFF_FEAT_PROPTXSTATUS, "proptxstatus" },
+ { INFF_FEAT_OWE, "owe" },
+ { INFF_FEAT_GTKO, "gtko" },
+ { INFF_FEAT_MCHAN_CONFIG, "mchan_config" },
+};
+
+#ifdef DEBUG
+static const char * const inff_quirk_names[] = {
+ "QUIRK_AUTO_AUTH",
+ "QUIRK_NEED_MPC",
+ "QUIRK_LAST"
+};
+
+/**
+ * inff_feat_debugfs_read() - expose feature info to debugfs.
+ *
+ * @seq: sequence for debugfs entry.
+ * @data: raw data pointer.
+ */
+static int inff_feat_debugfs_read(struct seq_file *seq, void *data)
+{
+ struct inff_bus *bus_if = dev_get_drvdata(seq->private);
+ u8 feats[DIV_ROUND_UP(INFF_FEAT_LAST, 8)] = {0};
+ u32 quirks = bus_if->drvr->chip_quirks;
+ int id, i;
+ u8 size = INFF_FEAT_LAST / 8;
+
+ memcpy(feats, bus_if->drvr->feat_flags, sizeof(feats));
+
+ seq_puts(seq, "Features: ");
+ for (i = 0; i < size; i++)
+ seq_printf(seq, "%02x", feats[i]);
+ seq_puts(seq, "\n");
+
+ for (id = 0; id < INFF_FEAT_LAST; id++)
+ if (feats[id / 8] & BIT(id % 8))
+ seq_printf(seq, "\t%s\n", inff_feat_names[id]);
+
+ seq_printf(seq, "\nQuirks: %08x\n", quirks);
+ for (id = 0; id < INFF_FEAT_QUIRK_LAST; id++)
+ if (quirks & BIT(id))
+ seq_printf(seq, "\t%s\n", inff_quirk_names[id]);
+ return 0;
+}
+#else
+static int inff_feat_debugfs_read(struct seq_file *seq, void *data)
+{
+ return 0;
+}
+#endif /* DEBUG */
+
+static void inff_feat_wlc_version_overrides(struct inff_pub *drv)
+{
+ struct inff_if *ifp = inff_get_ifp(drv, 0);
+ struct inff_wlc_version_le ver;
+ int err, major, minor;
+
+ err = inff_fwcmd_iovar_data_get(ifp, "wlc_ver", &ver, sizeof(ver));
+ if (err)
+ return;
+
+ major = le16_to_cpu(ver.wlc_ver_major);
+ minor = le16_to_cpu(ver.wlc_ver_minor);
+
+ inff_dbg(INFO, "WLC version: %d.%d\n", major, minor);
+}
+
+/**
+ * inff_feat_iovar_int_get() - determine feature through iovar query.
+ *
+ * @ifp: interface to query.
+ * @id: feature id.
+ * @name: iovar name.
+ */
+static void inff_feat_iovar_int_get(struct inff_if *ifp,
+ enum inff_feat_id id, char *name)
+{
+ u32 data = 0;
+ int err;
+
+ /* we need to know firmware error */
+ ifp->fwil_fwerr = true;
+
+ err = inff_fwcmd_iovar_int_get(ifp, name, &data);
+ if (err != -INFF_FW_UNSUPPORTED) {
+ inff_dbg(INFO, "enabling feature: %s\n", inff_feat_names[id]);
+ ifp->drvr->feat_flags[id / 8] |= BIT(id % 8);
+ } else {
+ inff_dbg(TRACE, "%s feature check failed: %d\n",
+ inff_feat_names[id], err);
+ }
+
+ ifp->fwil_fwerr = false;
+}
+
+#define MAX_CAPS_BUFFER_SIZE 768
+static void inff_feat_firmware_capabilities(struct inff_if *ifp)
+{
+ struct inff_pub *drvr = ifp->drvr;
+ char caps[MAX_CAPS_BUFFER_SIZE];
+ enum inff_feat_id id;
+ int i, err;
+
+ memset(caps, 0, MAX_CAPS_BUFFER_SIZE);
+ err = inff_fwcmd_iovar_data_get(ifp, "cap", caps, sizeof(caps));
+ if (err) {
+ iphy_err(drvr, "could not get firmware cap (%d)\n", err);
+ return;
+ }
+
+ inff_dbg(INFO, "[ %s]\n", caps);
+
+ for (i = 0; i < ARRAY_SIZE(inff_fwcap_map); i++) {
+ if (strnstr(caps, inff_fwcap_map[i].fwcap_id, sizeof(caps))) {
+ id = inff_fwcap_map[i].feature;
+ inff_dbg(INFO, "enabling feature: %s\n",
+ inff_feat_names[id]);
+ ifp->drvr->feat_flags[id / 8] |= BIT(id % 8);
+ }
+ }
+}
+
+/**
+ * inff_feat_fwcap_debugfs_read() - expose firmware capabilities to debugfs.
+ *
+ * @seq: sequence for debugfs entry.
+ * @data: raw data pointer.
+ */
+static int inff_feat_fwcap_debugfs_read(struct seq_file *seq, void *data)
+{
+ struct inff_bus *bus_if = dev_get_drvdata(seq->private);
+ struct inff_pub *drvr = bus_if->drvr;
+ struct inff_if *ifp = inff_get_ifp(drvr, 0);
+ char caps[MAX_CAPS_BUFFER_SIZE + 1] = { };
+ char *tmp;
+ int err;
+
+ err = inff_fwcmd_iovar_data_get(ifp, "cap", caps, sizeof(caps));
+ if (err) {
+ iphy_err(drvr, "could not get firmware cap (%d)\n", err);
+ return err;
+ }
+
+ /* Put every capability in a new line */
+ for (tmp = caps; *tmp; tmp++) {
+ if (*tmp == ' ')
+ *tmp = '\n';
+ }
+
+ /* Usually there is a space at the end of capabilities string */
+ seq_printf(seq, "%s", caps);
+ /* So make sure we don't print two line breaks */
+ if (tmp > caps && *(tmp - 1) != '\n')
+ seq_puts(seq, "\n");
+
+ return 0;
+}
+
+void inff_feat_attach(struct inff_pub *drvr)
+{
+ struct inff_if *ifp = inff_get_ifp(drvr, 0);
+
+ inff_feat_firmware_capabilities(ifp);
+
+ inff_feat_iovar_int_get(ifp, INFF_FEAT_RSDB, "rsdb_mode");
+ inff_feat_iovar_int_get(ifp, INFF_FEAT_MFP, "mfp");
+ inff_feat_iovar_int_get(ifp, INFF_FEAT_FWSUP, "sup_wpa");
+ inff_feat_iovar_int_get(ifp, INFF_FEAT_SCAN_V2, "scan_ver");
+
+ inff_feat_wlc_version_overrides(drvr);
+}
+
+void inff_feat_debugfs_create(struct inff_pub *drvr)
+{
+ inff_debugfs_add_entry(drvr, "features", inff_feat_debugfs_read);
+ inff_debugfs_add_entry(drvr, "fwcap", inff_feat_fwcap_debugfs_read);
+}
+
+bool inff_feat_is_enabled(struct inff_if *ifp, enum inff_feat_id id)
+{
+ return (ifp->drvr->feat_flags[id / 8] & BIT(id % 8));
+}
+
+bool inff_feat_is_quirk_enabled(struct inff_if *ifp,
+ enum inff_feat_quirk quirk)
+{
+ return (ifp->drvr->chip_quirks & BIT(quirk));
+}
diff --git a/drivers/net/wireless/infineon/inffmac/feature.h b/drivers/net/wireless/infineon/inffmac/feature.h
new file mode 100644
index 000000000000..56ac69a0c940
--- /dev/null
+++ b/drivers/net/wireless/infineon/inffmac/feature.h
@@ -0,0 +1,121 @@
+/* SPDX-License-Identifier: ISC */
+/*
+ * Copyright (c) 2014 Broadcom Corporation
+ *
+ * Copyright (c) 2025-2026, Infineon Technologies AG, or an affiliate of Infineon Technologies AG.
+ * All rights reserved.
+ */
+
+#ifndef INFF_FEATURE_H
+#define INFF_FEATURE_H
+
+/*
+ * Features:
+ *
+ * MBSS: multiple BSSID support (eg. guest network in AP mode).
+ * MCHAN: multi-channel for concurrent interface
+ * RSDB: Real Simultaneous Dual Band
+ * SCAN_RANDOM_MAC: Random MAC during (net detect) scheduled scan.
+ * MFP: 802.11w Management Frame Protection.
+ * GSCAN: enhanced scan offload feature.
+ * FWSUP: Firmware supplicant.
+ * DOT11H: firmware supports 802.11h
+ * SAE: simultaneous authentication of equals
+ * FWAUTH: Firmware authenticator
+ * SCAN_V2: Version 2 scan params
+ * SAE_EXT: SAE be handled by userspace supplicant
+ * GCMP: firmware has defined GCMP or not.
+ * GTKO: GTK rekey offload.
+ */
+enum inff_feat_id {
+ INFF_FEAT_MBSS,
+ INFF_FEAT_MCHAN,
+ INFF_FEAT_RSV2,
+ INFF_FEAT_RSV3,
+ INFF_FEAT_RSV4,
+ INFF_FEAT_RSDB,
+ INFF_FEAT_RSV6,
+ INFF_FEAT_SCAN_RANDOM_MAC,
+ INFF_FEAT_RSV8,
+ INFF_FEAT_RSV9,
+ INFF_FEAT_RSV10,
+ INFF_FEAT_MFP,
+ INFF_FEAT_GSCAN,
+ INFF_FEAT_FWSUP,
+ INFF_FEAT_RSV14,
+ INFF_FEAT_RSV15,
+ INFF_FEAT_RSV16,
+ INFF_FEAT_RSV17,
+ INFF_FEAT_DOT11H,
+ INFF_FEAT_SAE,
+ INFF_FEAT_FWAUTH,
+ INFF_FEAT_RSV21,
+ INFF_FEAT_SCAN_V2,
+ INFF_FEAT_RSV23,
+ INFF_FEAT_RSV24,
+ INFF_FEAT_RSV25,
+ INFF_FEAT_SAE_EXT,
+ INFF_FEAT_FBT,
+ INFF_FEAT_OKC,
+ INFF_FEAT_GCMP,
+ INFF_FEAT_RSV30,
+ INFF_FEAT_RSV31,
+ INFF_FEAT_RSV32,
+ INFF_FEAT_PROPTXSTATUS,
+ INFF_FEAT_OWE,
+ INFF_FEAT_RSV35,
+ INFF_FEAT_RSV36,
+ INFF_FEAT_GTKO,
+ INFF_FEAT_MCHAN_CONFIG,
+ INFF_FEAT_RSV39,
+ INFF_FEAT_LAST
+};
+
+/*
+ * Quirks:
+ *
+ * AUTO_AUTH: workaround needed for automatic authentication type.
+ * NEED_MPC: driver needs to disable MPC during scanning operation.
+ */
+enum inff_feat_quirk {
+ INFF_FEAT_QUIRK_AUTO_AUTH,
+ INFF_FEAT_QUIRK_NEED_MPC,
+ INFF_FEAT_QUIRK_LAST
+};
+
+/**
+ * inff_feat_attach() - determine features and quirks.
+ *
+ * @drvr: driver instance.
+ */
+void inff_feat_attach(struct inff_pub *drvr);
+
+/**
+ * inff_feat_debugfs_create() - create debugfs entries.
+ *
+ * @drvr: driver instance.
+ */
+void inff_feat_debugfs_create(struct inff_pub *drvr);
+
+/**
+ * inff_feat_is_enabled() - query feature.
+ *
+ * @ifp: interface instance.
+ * @id: feature id to check.
+ *
+ * Return: true is feature is enabled; otherwise false.
+ */
+bool inff_feat_is_enabled(struct inff_if *ifp, enum inff_feat_id id);
+
+/**
+ * inff_feat_is_quirk_enabled() - query chip quirk.
+ *
+ * @ifp: interface instance.
+ * @quirk: quirk id to check.
+ *
+ * Return: true is quirk is enabled; otherwise false.
+ */
+bool inff_feat_is_quirk_enabled(struct inff_if *ifp,
+ enum inff_feat_quirk quirk);
+
+#endif /* INFF_FEATURE_H */
--
2.43.0
^ permalink raw reply related [flat|nested] 26+ messages in thread* [PATCH wireless-next v3 16/25] wifi: inffmac: add bus_proto.c/h
2026-09-02 16:50 [PATCH wireless-next v3 00/25] wifi: inffmac: introducing a driver for Infineon's new generation chipsets Gokul Sivakumar
` (14 preceding siblings ...)
2026-09-02 16:50 ` [PATCH wireless-next v3 15/25] wifi: inffmac: add feature.c/h Gokul Sivakumar
@ 2026-09-02 16:50 ` Gokul Sivakumar
2026-09-02 16:50 ` [PATCH wireless-next v3 17/25] wifi: inffmac: add sdio.c/h Gokul Sivakumar
` (8 subsequent siblings)
24 siblings, 0 replies; 26+ messages in thread
From: Gokul Sivakumar @ 2026-09-02 16:50 UTC (permalink / raw)
To: linux-wireless, johannes
Cc: arend.vanspriel, wlan-kernel-dev-list, gokulkumar.sivakumar
Provides the generic BUS protocol abstraction layer that is used for
the Control and Data communication with the Infineon's WLAN Device
over the SDIO BUS types.
Signed-off-by: Gokul Sivakumar <gokulkumar.sivakumar@infineon.com>
---
.../net/wireless/infineon/inffmac/bus_proto.c | 162 +++++++
.../net/wireless/infineon/inffmac/bus_proto.h | 436 ++++++++++++++++++
2 files changed, 598 insertions(+)
create mode 100644 drivers/net/wireless/infineon/inffmac/bus_proto.c
create mode 100644 drivers/net/wireless/infineon/inffmac/bus_proto.h
diff --git a/drivers/net/wireless/infineon/inffmac/bus_proto.c b/drivers/net/wireless/infineon/inffmac/bus_proto.c
new file mode 100644
index 000000000000..8ea899e2e037
--- /dev/null
+++ b/drivers/net/wireless/infineon/inffmac/bus_proto.c
@@ -0,0 +1,162 @@
+// SPDX-License-Identifier: ISC
+/*
+ * Copyright (c) 2013 Broadcom Corporation
+ *
+ * Copyright (c) 2025-2026, Infineon Technologies AG, or an affiliate of Infineon Technologies AG.
+ * All rights reserved.
+ */
+
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/netdevice.h>
+
+#include "main.h"
+#include "cfg80211.h"
+#include "debug.h"
+#include "bus_proto.h"
+#include "bcdc.h"
+#include "icdc.h"
+
+static void inff_core_bus_reset(struct work_struct *work)
+{
+ struct inff_pub *drvr = container_of(work, struct inff_pub,
+ bus_reset);
+
+ /* WLAN REG TOGGLE */
+ inff_bus_wlan_regon_gpio_toggle(0);
+ msleep(100);
+ inff_bus_wlan_regon_gpio_toggle(1);
+
+ inff_bus_reset(drvr->bus_if);
+}
+
+static ssize_t
+inff_bus_reset_write(struct file *file, const char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ struct inff_pub *drvr = file->private_data;
+ u8 value;
+
+ if (kstrtou8_from_user(user_buf, count, 0, &value))
+ return -EINVAL;
+
+ if (value != 1)
+ return -EINVAL;
+
+ schedule_work(&drvr->bus_reset);
+
+ return count;
+}
+
+static const struct file_operations bus_reset_fops = {
+ .open = simple_open,
+ .write = inff_bus_reset_write,
+};
+
+void inff_bus_proto_debugfs_create(struct inff_pub *drvr)
+{
+ struct inff_bus *bus = drvr->bus_if;
+
+ debugfs_create_file("reset", 0600, inff_debugfs_get_devdir(drvr),
+ drvr, &bus_reset_fops);
+
+ if (bus->ops->debugfs_create)
+ bus->ops->debugfs_create(bus->dev);
+
+ if (drvr->proto->debugfs_create)
+ drvr->proto->debugfs_create(drvr);
+}
+
+void inff_bus_change_state(struct inff_bus *bus, enum inff_bus_state state)
+{
+ struct inff_pub *drvr = bus->drvr;
+ struct net_device *ndev;
+ int ifidx;
+
+ inff_dbg(TRACE, "%d -> %d\n", bus->state, state);
+
+ if (!drvr) {
+ inff_dbg(INFO, "ignoring transition, bus not attached yet\n");
+ return;
+ }
+
+ bus->state = state;
+
+ if (state == INFF_BUS_UP) {
+ for (ifidx = 0; ifidx < INFF_MAX_IFS; ifidx++) {
+ if (drvr->iflist[ifidx] &&
+ drvr->iflist[ifidx]->ndev) {
+ ndev = drvr->iflist[ifidx]->ndev;
+ if (netif_queue_stopped(ndev))
+ netif_wake_queue(ndev);
+ }
+ }
+ }
+}
+
+void inff_bus_add_txhdrlen(struct device *dev, uint len)
+{
+ struct inff_bus *bus_if = dev_get_drvdata(dev);
+ struct inff_pub *drvr = bus_if->drvr;
+
+ if (drvr)
+ drvr->hdrlen += len;
+}
+
+void inff_bus_wlan_regon_gpio_toggle(u8 gpio_on)
+{
+ /* WLAN REG ON/OFF Toggle code to be added */
+}
+
+int inff_bus_proto_attach(struct inff_pub *drvr)
+{
+ struct inff_proto *proto;
+
+ proto = kzalloc_obj(*proto, GFP_ATOMIC);
+ if (!proto)
+ goto fail;
+
+ drvr->proto = proto;
+
+ if (drvr->bus_if->proto_type == INFF_PROTO_ICDC) {
+ if (inff_proto_icdc_attach(drvr))
+ goto fail;
+ } else if (drvr->bus_if->proto_type == INFF_PROTO_BCDC) {
+ if (inff_proto_bcdc_attach(drvr))
+ goto fail;
+ } else {
+ iphy_err(drvr, "Unsupported proto type %d\n",
+ drvr->bus_if->proto_type);
+ goto fail;
+ }
+ if (!proto->tx_queue_data ||
+ !proto->query_fwcmd || !proto->set_fwcmd ||
+ !proto->configure_addr_mode ||
+ !proto->delete_peer ||
+ !proto->debugfs_create) {
+ iphy_err(drvr, "Not all proto handlers have been installed\n");
+ goto fail;
+ }
+
+ INIT_WORK(&drvr->bus_reset, inff_core_bus_reset);
+
+ return 0;
+
+fail:
+ kfree(proto);
+ drvr->proto = NULL;
+ return -ENOMEM;
+}
+
+void inff_bus_proto_detach(struct inff_pub *drvr)
+{
+ if (drvr->proto) {
+ if (drvr->bus_if->proto_type == INFF_PROTO_ICDC)
+ inff_proto_icdc_detach(drvr);
+ else if (drvr->bus_if->proto_type == INFF_PROTO_BCDC)
+ inff_proto_bcdc_detach(drvr);
+
+ kfree(drvr->proto);
+ drvr->proto = NULL;
+ }
+}
diff --git a/drivers/net/wireless/infineon/inffmac/bus_proto.h b/drivers/net/wireless/infineon/inffmac/bus_proto.h
new file mode 100644
index 000000000000..fffcc4f2db25
--- /dev/null
+++ b/drivers/net/wireless/infineon/inffmac/bus_proto.h
@@ -0,0 +1,436 @@
+/* SPDX-License-Identifier: ISC */
+/*
+ * Copyright (c) 2013 Broadcom Corporation
+ *
+ * Copyright (c) 2025-2026, Infineon Technologies AG, or an affiliate of Infineon Technologies AG.
+ * All rights reserved.
+ */
+
+#ifndef INFF_BUS_PROTO_H
+#define INFF_BUS_PROTO_H
+
+/* State of bus for communication with the dongle */
+enum inff_bus_state {
+ INFF_BUS_DOWN, /* Not ready for frame transfers */
+ INFF_BUS_UP /* Ready for frame transfers */
+};
+
+/* Protocol of bus communication with the dongle */
+enum inff_bus_protocol_type {
+ INFF_PROTO_ICDC,
+ INFF_PROTO_BCDC
+};
+
+struct inff_bus_dcmd {
+ char *name;
+ char *param;
+ int param_len;
+ struct list_head list;
+};
+
+/* Firmware blobs that may be available */
+enum inff_blob_type {
+ INFF_BLOB_CLM,
+};
+
+/**
+ * struct inff_bus_ops - bus callback operations.
+ *
+ * @preinit: execute bus/device specific dongle init commands (optional).
+ * @init: prepare for communication with dongle.
+ * @stop: clear pending frames, disable data flow.
+ * @txdata: send a data frame to the dongle. When the data
+ * has been transferred, the common driver must be
+ * notified using inff_txcomplete(). The common
+ * driver calls this function with interrupts
+ * disabled.
+ * @txctl: transmit a control request message to dongle.
+ * @rxctl: receive a control response message from dongle.
+ * @gettxq: obtain a reference of bus transmit queue (optional).
+ * @get_rambase: obtain base address of wifiss.
+ * @get_ramsize: obtain memory size of wifiss.
+ * @get_cpuss_rambase: obtain base address of cpuss.
+ * @get_cpuss_ramsize: obtain memory size of cpuss.
+ * @get_blob: obtain a firmware blob.
+ * @remove: initiate unbind of the device.
+ * @interrupt_enable: enable the interrupts from the device.
+ * @interrupt_disable: disable the interrupts from the device.
+ *
+ * This structure provides an abstract interface towards the
+ * bus specific driver. For control messages to common driver
+ * will assure there is only one active transaction. Unless
+ * indicated otherwise these callbacks are mandatory.
+ */
+struct inff_bus_ops {
+ int (*preinit)(struct device *dev);
+ void (*stop)(struct device *dev);
+ int (*txdata)(struct device *dev, struct sk_buff *skb);
+ int (*txctl)(struct device *dev, unsigned char *msg, uint len);
+ int (*rxctl)(struct device *dev, unsigned char *msg, uint len);
+ struct pktq * (*gettxq)(struct device *dev);
+ size_t (*get_rambase)(struct device *dev);
+ size_t (*get_ramsize)(struct device *dev);
+ size_t (*get_cpuss_rambase)(struct device *dev);
+ size_t (*get_cpuss_ramsize)(struct device *dev);
+ int (*get_blob)(struct device *dev, const struct firmware **fw,
+ enum inff_blob_type type);
+ void (*debugfs_create)(struct device *dev);
+ int (*reset)(struct device *dev);
+ void (*remove)(struct device *dev);
+ int (*set_fcmode)(struct device *dev);
+ int (*napi_poll)(struct napi_struct *napi, int budget);
+ void (*interrupt_enable)(struct device *dev);
+ void (*interrupt_disable)(struct device *dev);
+};
+
+/**
+ * struct inff_bus_stats - bus statistic counters.
+ *
+ * @pktcowed: packets cowed for extra headroom/unorphan.
+ * @pktcow_failed: packets dropped due to failed cow-ing.
+ */
+struct inff_bus_stats {
+ atomic_t pktcowed;
+ atomic_t pktcow_failed;
+};
+
+/**
+ * struct inff_bus - interface structure between common and bus layer
+ *
+ * @bus_priv: pointer to private bus device.
+ * @proto_type: protocol type, icdc or bcdc
+ * @dev: device pointer of bus device.
+ * @drvr: public driver information.
+ * @state: operational state of the bus interface.
+ * @stats: statistics shared between common and bus layer.
+ * @maxctl: maximum size for rxctl request message.
+ * @chip: device identifier of the dongle chip.
+ * @always_use_fws_queue: bus wants use queue also when fwsignal is inactive.
+ * @chiprev: revision of the dongle chip.
+ */
+struct inff_bus {
+ union {
+ struct inff_sdio_dev *sdio;
+ } bus_priv;
+ enum inff_bus_protocol_type proto_type;
+ struct device *dev;
+ struct inff_pub *drvr;
+ enum inff_bus_state state;
+ struct inff_bus_stats stats;
+ uint maxctl;
+ u32 chip;
+ u32 chiprev;
+ bool always_use_fws_queue;
+
+ const struct inff_bus_ops *ops;
+ struct inff_chip *chip_pub;
+};
+
+enum proto_addr_mode {
+ ADDR_INDIRECT = 0,
+ ADDR_DIRECT
+};
+
+struct inff_skb_reorder_data {
+ u8 *reorder;
+};
+
+struct inff_proto {
+ int (*query_cpcmd)(struct inff_pub *drvr, int ifidx, uint cmd,
+ void *buf, uint len, int *fwerr);
+ int (*set_cpcmd)(struct inff_pub *drvr, int ifidx, uint cmd, void *buf,
+ uint len, int *fwerr);
+ int (*query_fwcmd)(struct inff_pub *drvr, int ifidx, uint cmd,
+ void *buf, uint len, int *fwerr);
+ int (*set_fwcmd)(struct inff_pub *drvr, int ifidx, uint cmd, void *buf,
+ uint len, int *fwerr);
+ int (*hdrpull)(struct inff_pub *drvr, bool do_fws,
+ struct sk_buff *skb, struct inff_if **ifp);
+ int (*tx_queue_data)(struct inff_pub *drvr, int ifidx,
+ struct sk_buff *skb);
+ int (*txdata)(struct inff_pub *drvr, int ifidx, u8 offset,
+ struct sk_buff *skb);
+ void (*configure_addr_mode)(struct inff_pub *drvr, int ifidx,
+ enum proto_addr_mode addr_mode);
+ void (*delete_peer)(struct inff_pub *drvr, int ifidx,
+ u8 peer[ETH_ALEN]);
+ void (*rxreorder)(struct inff_if *ifp, struct sk_buff *skb, bool inirq);
+ void (*add_if)(struct inff_if *ifp);
+ void (*del_if)(struct inff_if *ifp);
+ void (*reset_if)(struct inff_if *ifp);
+ void (*cleanup_if)(struct inff_if *ifp);
+ int (*init_done)(struct inff_pub *drvr);
+ void (*debugfs_create)(struct inff_pub *drvr);
+ void *pd;
+};
+
+/*
+ * callback wrappers
+ */
+static inline int
+inff_bus_preinit(struct inff_bus *bus)
+{
+ if (!bus->ops->preinit)
+ return 0;
+ return bus->ops->preinit(bus->dev);
+}
+
+static inline void
+inff_bus_stop(struct inff_bus *bus)
+{
+ bus->ops->stop(bus->dev);
+}
+
+static inline int
+inff_bus_txdata(struct inff_bus *bus, struct sk_buff *skb)
+{
+ return bus->ops->txdata(bus->dev, skb);
+}
+
+static inline int
+inff_bus_txctl(struct inff_bus *bus, unsigned char *msg, uint len)
+{
+ return bus->ops->txctl(bus->dev, msg, len);
+}
+
+static inline int
+inff_bus_rxctl(struct inff_bus *bus, unsigned char *msg, uint len)
+{
+ return bus->ops->rxctl(bus->dev, msg, len);
+}
+
+static inline struct pktq *
+inff_bus_gettxq(struct inff_bus *bus)
+{
+ if (!bus->ops->gettxq)
+ return ERR_PTR(-ENOENT);
+
+ return bus->ops->gettxq(bus->dev);
+}
+
+static inline size_t
+inff_bus_get_rambase(struct inff_bus *bus)
+{
+ if (!bus->ops->get_rambase)
+ return 0;
+
+ return bus->ops->get_rambase(bus->dev);
+}
+
+static inline size_t
+inff_bus_get_ramsize(struct inff_bus *bus)
+{
+ if (!bus->ops->get_ramsize)
+ return 0;
+
+ return bus->ops->get_ramsize(bus->dev);
+}
+
+static inline size_t
+inff_bus_get_cpuss_rambase(struct inff_bus *bus)
+{
+ if (!bus->ops->get_cpuss_rambase)
+ return 0;
+
+ return bus->ops->get_cpuss_rambase(bus->dev);
+}
+
+static inline size_t
+inff_bus_get_cpuss_ramsize(struct inff_bus *bus)
+{
+ if (!bus->ops->get_cpuss_ramsize)
+ return 0;
+
+ return bus->ops->get_cpuss_ramsize(bus->dev);
+}
+
+static inline int
+inff_bus_get_blob(struct inff_bus *bus, const struct firmware **fw,
+ enum inff_blob_type type)
+{
+ return bus->ops->get_blob(bus->dev, fw, type);
+}
+
+static inline int
+inff_bus_reset(struct inff_bus *bus)
+{
+ if (!bus->ops->reset)
+ return -EOPNOTSUPP;
+
+ return bus->ops->reset(bus->dev);
+}
+
+static inline void
+inff_bus_remove(struct inff_bus *bus)
+{
+ if (!bus->ops->remove) {
+ device_release_driver(bus->dev);
+ return;
+ }
+
+ bus->ops->remove(bus->dev);
+}
+
+static inline int
+inff_bus_set_fcmode(struct inff_bus *bus)
+{
+ if (!bus->ops->set_fcmode)
+ return -EOPNOTSUPP;
+
+ return bus->ops->set_fcmode(bus->dev);
+}
+
+static inline void
+inff_bus_interrupt_enable(struct inff_bus *bus)
+{
+ if (!bus->ops->interrupt_enable)
+ return;
+
+ bus->ops->interrupt_enable(bus->dev);
+}
+
+static inline void
+inff_bus_interrupt_disable(struct inff_bus *bus)
+{
+ if (!bus->ops->interrupt_disable)
+ return;
+
+ bus->ops->interrupt_disable(bus->dev);
+}
+
+static inline int
+inff_proto_query_cpcmd(struct inff_pub *drvr, int ifidx,
+ uint cmd, void *buf, uint len,
+ int *cperr)
+{
+ return drvr->proto->query_cpcmd(drvr, ifidx, cmd, buf, len, cperr);
+}
+
+static inline int
+inff_proto_set_cpcmd(struct inff_pub *drvr, int ifidx,
+ uint cmd, void *buf, uint len, int *cperr)
+{
+ return drvr->proto->set_cpcmd(drvr, ifidx, cmd, buf, len, cperr);
+}
+
+static inline int
+inff_proto_query_fwcmd(struct inff_pub *drvr, int ifidx,
+ uint cmd, void *buf, uint len,
+ int *fwerr)
+{
+ return drvr->proto->query_fwcmd(drvr, ifidx, cmd, buf, len, fwerr);
+}
+
+static inline int
+inff_proto_set_fwcmd(struct inff_pub *drvr, int ifidx,
+ uint cmd, void *buf, uint len, int *fwerr)
+{
+ return drvr->proto->set_fwcmd(drvr, ifidx, cmd, buf, len, fwerr);
+}
+
+static inline int
+inff_proto_hdrpull(struct inff_pub *drvr, bool do_fws,
+ struct sk_buff *skb,
+ struct inff_if **ifp)
+{
+ struct inff_if *tmp = NULL;
+
+ /* assure protocol is always called with
+ * non-null initialized pointer.
+ */
+ if (ifp)
+ *ifp = NULL;
+ else
+ ifp = &tmp;
+ return drvr->proto->hdrpull(drvr, do_fws, skb, ifp);
+}
+
+static inline int
+inff_proto_tx_queue_data(struct inff_pub *drvr, int ifidx,
+ struct sk_buff *skb)
+{
+ return drvr->proto->tx_queue_data(drvr, ifidx, skb);
+}
+
+static inline int
+inff_proto_txdata(struct inff_pub *drvr, int ifidx,
+ u8 offset, struct sk_buff *skb)
+{
+ return drvr->proto->txdata(drvr, ifidx, offset, skb);
+}
+
+static inline void
+inff_proto_configure_addr_mode(struct inff_pub *drvr, int ifidx,
+ enum proto_addr_mode addr_mode)
+{
+ drvr->proto->configure_addr_mode(drvr, ifidx, addr_mode);
+}
+
+static inline void
+inff_proto_delete_peer(struct inff_pub *drvr, int ifidx, u8 peer[ETH_ALEN])
+{
+ drvr->proto->delete_peer(drvr, ifidx, peer);
+}
+
+static inline bool inff_proto_is_reorder_skb(struct sk_buff *skb)
+{
+ struct inff_skb_reorder_data *rd;
+
+ rd = (struct inff_skb_reorder_data *)skb->cb;
+ return !!rd->reorder;
+}
+
+static inline void
+inff_proto_rxreorder(struct inff_if *ifp, struct sk_buff *skb, bool inirq)
+{
+ ifp->drvr->proto->rxreorder(ifp, skb, inirq);
+}
+
+static inline void
+inff_proto_add_if(struct inff_pub *drvr, struct inff_if *ifp)
+{
+ if (!drvr->proto->add_if)
+ return;
+ drvr->proto->add_if(ifp);
+}
+
+static inline void
+inff_proto_del_if(struct inff_pub *drvr, struct inff_if *ifp)
+{
+ if (!drvr->proto->del_if)
+ return;
+ drvr->proto->del_if(ifp);
+}
+
+static inline void
+inff_proto_reset_if(struct inff_pub *drvr, struct inff_if *ifp)
+{
+ if (!drvr->proto->reset_if)
+ return;
+ drvr->proto->reset_if(ifp);
+}
+
+static inline void
+inff_proto_cleanup_if(struct inff_pub *drvr, struct inff_if *ifp)
+{
+ if (!drvr->proto->cleanup_if)
+ return;
+ drvr->proto->cleanup_if(ifp);
+}
+
+static inline int
+inff_proto_init_done(struct inff_pub *drvr)
+{
+ if (!drvr->proto->init_done)
+ return 0;
+ return drvr->proto->init_done(drvr);
+}
+
+void inff_bus_proto_debugfs_create(struct inff_pub *drvr);
+void inff_bus_change_state(struct inff_bus *bus, enum inff_bus_state state);
+void inff_bus_add_txhdrlen(struct device *dev, uint len);
+void inff_bus_wlan_regon_gpio_toggle(u8 gpio_on);
+int inff_bus_proto_attach(struct inff_pub *drvr);
+void inff_bus_proto_detach(struct inff_pub *drvr);
+
+#endif /* INFF_BUS_PROTO_H */
--
2.43.0
^ permalink raw reply related [flat|nested] 26+ messages in thread* [PATCH wireless-next v3 17/25] wifi: inffmac: add sdio.c/h
2026-09-02 16:50 [PATCH wireless-next v3 00/25] wifi: inffmac: introducing a driver for Infineon's new generation chipsets Gokul Sivakumar
` (15 preceding siblings ...)
2026-09-02 16:50 ` [PATCH wireless-next v3 16/25] wifi: inffmac: add bus_proto.c/h Gokul Sivakumar
@ 2026-09-02 16:50 ` Gokul Sivakumar
2026-09-02 16:50 ` [PATCH wireless-next v3 18/25] wifi: inffmac: add fwsignal.c/h Gokul Sivakumar
` (7 subsequent siblings)
24 siblings, 0 replies; 26+ messages in thread
From: Gokul Sivakumar @ 2026-09-02 16:50 UTC (permalink / raw)
To: linux-wireless, johannes
Cc: arend.vanspriel, wlan-kernel-dev-list, gokulkumar.sivakumar
Implements the specific bus logic for Infineon devices connected to the
linux machine via an SDIO interface.
Signed-off-by: Gokul Sivakumar <gokulkumar.sivakumar@infineon.com>
---
drivers/net/wireless/infineon/inffmac/sdio.c | 7585 ++++++++++++++++++
drivers/net/wireless/infineon/inffmac/sdio.h | 234 +
include/linux/mmc/sdio_ids.h | 2 +
3 files changed, 7821 insertions(+)
create mode 100644 drivers/net/wireless/infineon/inffmac/sdio.c
create mode 100644 drivers/net/wireless/infineon/inffmac/sdio.h
diff --git a/drivers/net/wireless/infineon/inffmac/sdio.c b/drivers/net/wireless/infineon/inffmac/sdio.c
new file mode 100644
index 000000000000..afe4ac407bf0
--- /dev/null
+++ b/drivers/net/wireless/infineon/inffmac/sdio.c
@@ -0,0 +1,7585 @@
+// SPDX-License-Identifier: ISC
+/*
+ * Copyright (c) 2010 Broadcom Corporation
+ *
+ * Copyright (c) 2025-2026, Infineon Technologies AG, or an affiliate of Infineon Technologies AG.
+ * All rights reserved.
+ */
+
+#include <linux/types.h>
+#include <linux/atomic.h>
+#include <linux/kernel.h>
+#include <linux/kthread.h>
+#include <linux/printk.h>
+#include <linux/netdevice.h>
+#include <linux/interrupt.h>
+#include <linux/sched.h>
+#include <linux/sched/signal.h>
+#include <linux/mmc/sdio.h>
+#include <linux/mmc/sdio_ids.h>
+#include <linux/mmc/sdio_func.h>
+#include <linux/mmc/card.h>
+#include <linux/mmc/core.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/sdio_ids.h>
+#include <linux/completion.h>
+#include <linux/firmware.h>
+#include <linux/module.h>
+#include <linux/debugfs.h>
+#include <linux/vmalloc.h>
+#include <linux/net_tstamp.h>
+#include <linux/unaligned.h>
+#include <linux/scatterlist.h>
+#include <linux/pm_runtime.h>
+#include <linux/suspend.h>
+#include <linux/errno.h>
+#include <linux/module.h>
+#include <linux/acpi.h>
+#include <net/cfg80211.h>
+
+#include "main.h"
+#include "utils.h"
+#include "bus_proto.h"
+#include "sdio.h"
+#include "firmware.h"
+#include "bcdc.h"
+#include "fwsignal.h"
+#include "dev_cmd.h"
+#include "trxhdr.h"
+#include "feature.h"
+#include "chip_cyw5591x.h"
+#include "chip_acw74xx.h"
+#include "debug.h"
+#include "cfg80211.h"
+#include "chip.h"
+#include "dfu.h"
+
+/* Definitions related to SDIOD DMA WAR in Diamond */
+#define SD_DMA_REG_XMTCONTROL_OFFSET 0x200
+#define SD_DMA_REG_GPIOOUTREG_OFFSET 0x338
+#define SD_DMA_REG_XMTCTRL_SUSP_EN 0x803
+#define SD_DMA_REG_XMTCTRL_SUSP_DIS 0x801
+#define SD_DMA_RX_FIFO_DBG_CNT_MSK 0xFE
+
+#define DCMD_RESP_TIMEOUT msecs_to_jiffies(2500)
+#define CTL_DONE_TIMEOUT msecs_to_jiffies(60000)
+
+#define INFF_BLHS_D2H_READY_TIMEOUT 5000 /* msec */
+
+/* Last 32-bit word of the RAM_APP staging area in the CPUSS SRAM
+ * 0x34002000 + 0x14000 - 4 = 0x34015FFC
+ */
+#define INFF_CPUSS_CONSOLE_SHARED_ADDR 0x34015FFC
+#define INFF_CPUSS_CONSOLE_ADDR_RANGE_LOW 0x34000000
+#define INFF_CPUSS_CONSOLE_ADDR_RANGE_HIGH 0x3402C000
+
+/* watermark expressed in number of words */
+#define DEFAULT_F2_WATERMARK 0x8
+
+#ifdef DEBUG
+
+#define INFF_TRAP_INFO_SIZE 80
+
+#define CBUF_LEN (128)
+
+/* Device console log buffer state */
+#define CONSOLE_BUFFER_MAX 2024
+
+struct rte_log_le {
+ __le32 buf; /* Can't be pointer on (64-bit) hosts */
+ __le32 buf_size;
+ __le32 idx;
+ char *_buf_compat; /* Redundant pointer for backward compat. */
+};
+
+struct cpu_log_le {
+ __le32 magic_word;
+ __le32 buf; /* Can't be pointer on (64-bit) hosts */
+ __le32 buf_size;
+ __le32 idx;
+};
+
+struct rte_console {
+ /* Virtual UART
+ * When there is no UART (e.g. Quickturn),
+ * the host should write a complete
+ * input line directly into cbuf and then write
+ * the length into vcons_in.
+ * This may also be used when there is a real UART
+ * (at risk of conflicting with
+ * the real UART). vcons_out is currently unused.
+ */
+ uint vcons_in;
+ uint vcons_out;
+
+ /* Output (logging) buffer
+ * Console output is written to a ring buffer log_buf at index log_idx.
+ * The host may read the output when it sees log_idx advance.
+ * Output will be lost if the output wraps around faster than the host
+ * polls.
+ */
+ struct rte_log_le log_le;
+
+ /* Console input line buffer
+ * Characters are read one at a time into cbuf
+ * until <CR> is received, then
+ * the buffer is processed as a command line.
+ * Also used for virtual UART.
+ */
+ uint cbuf_idx;
+ char cbuf[CBUF_LEN];
+};
+
+#endif /* DEBUG */
+
+#define TXQLEN 2048 /* bulk tx queue length */
+#define TXHI (TXQLEN - 256) /* turn on flow control above TXHI */
+#define TXLOW (TXHI - 256) /* turn off flow control below TXLOW */
+#define PRIOMASK 7
+
+#define TXRETRIES 2 /* # of retries for tx frames */
+
+#define INFF_RXBOUND 50 /* Default for max rx frames in
+ * one scheduling
+ */
+
+#define INFF_TXBOUND 20 /* Default for max tx frames in
+ * one scheduling
+ */
+
+#define INFF_TXMINMAX 1 /* Max tx frames if rx still pending */
+
+#define MEMBLOCK 2048 /* Block size used for downloading
+ * of dongle image
+ */
+
+#define INFF_FIRSTREAD BIT(6)
+
+#define MAX_INIT_RETRY_CNT 3
+static unsigned char global_init_retry;
+
+/* SBSDIO_DEVICE_CTL */
+
+/* 1: device will assert busy signal when receiving CMD53 */
+#define SBSDIO_DEVCTL_SETBUSY 0x01
+/* 1: assertion of sdio interrupt is synchronous to the sdio clock */
+#define SBSDIO_DEVCTL_SPI_INTR_SYNC 0x02
+/* 1: mask all interrupts to host except the chipActive (rev 8) */
+#define SBSDIO_DEVCTL_CA_INT_ONLY 0x04
+/* 1: isolate internal sdio signals, put external pads in tri-state; requires
+ * sdio bus power cycle to clear (rev 9)
+ */
+#define SBSDIO_DEVCTL_PADS_ISO 0x08
+/* 1: enable F2 Watermark */
+#define SBSDIO_DEVCTL_F2WM_ENAB 0x10
+/* Force SD->SB reset mapping (rev 11) */
+#define SBSDIO_DEVCTL_SB_RST_CTL 0x30
+/* Determined by CoreControl bit */
+#define SBSDIO_DEVCTL_RST_CORECTL 0x00
+/* Force backplane reset */
+#define SBSDIO_DEVCTL_RST_BPRESET 0x10
+/* Force no backplane reset */
+#define SBSDIO_DEVCTL_RST_NOBPRESET 0x20
+/* Reset SB Address to default value */
+#define SBSDIO_DEVCTL_ADDR_RESET 0x40
+
+/* direct(mapped) cis space */
+
+/* SDIO function 1 register CHIPCLKCSR */
+/* Force ALP request to backplane */
+#define SBSDIO_FORCE_ALP 0x01
+/* Force HT request to backplane */
+#define SBSDIO_FORCE_HT 0x02
+/* Force ILP request to backplane */
+#define SBSDIO_FORCE_ILP 0x04
+/* Make ALP ready (power up xtal) */
+#define SBSDIO_ALP_AVAIL_REQ 0x08
+/* Make HT ready (power up PLL) */
+#define SBSDIO_HT_AVAIL_REQ 0x10
+/* Squelch clock requests from HW */
+#define SBSDIO_FORCE_HW_CLKREQ_OFF 0x20
+/* Status: ALP is ready */
+#define SBSDIO_ALP_AVAIL 0x40
+/* Status: HT is ready */
+#define SBSDIO_HT_AVAIL 0x80
+#define SBSDIO_CSR_MASK 0x1F
+#define SBSDIO_AVBITS (SBSDIO_HT_AVAIL | SBSDIO_ALP_AVAIL)
+#define SBSDIO_ALPAV(regval) ((regval) & SBSDIO_AVBITS)
+#define SBSDIO_HTAV(regval) (((regval) & SBSDIO_AVBITS) == SBSDIO_AVBITS)
+#define SBSDIO_ALPONLY(regval) ({ \
+ typeof(regval) _regval = (regval); \
+ (SBSDIO_ALPAV(_regval) && !SBSDIO_HTAV(_regval)); \
+ })
+#define SBSDIO_CLKAV(regval, alponly) ({ \
+ typeof(regval) _regval = (regval); \
+ (SBSDIO_ALPAV(_regval) && (alponly ? 1 : SBSDIO_HTAV(_regval))); \
+ })
+#define ALP_WAIT_MIN 65
+#define ALP_WAIT_MAX 85
+
+/* intstatus */
+#define I_SMB_SW0 BIT(0) /* To SB Mail S/W interrupt 0 */
+#define I_SMB_SW1 BIT(1) /* To SB Mail S/W interrupt 1 */
+#define I_SMB_SW2 BIT(2) /* To SB Mail S/W interrupt 2 */
+#define I_SMB_SW3 BIT(3) /* To SB Mail S/W interrupt 3 */
+#define I_SMB_SW_MASK 0x0000000f /* To SB Mail S/W interrupts mask */
+#define I_SMB_SW_SHIFT 0 /* To SB Mail S/W interrupts shift */
+#define I_HMB_SW0 BIT(4) /* To Host Mail S/W interrupt 0 */
+#define I_HMB_SW1 BIT(5) /* To Host Mail S/W interrupt 1 */
+#define I_HMB_SW2 BIT(6) /* To Host Mail S/W interrupt 2 */
+#define I_HMB_SW3 BIT(7) /* To Host Mail S/W interrupt 3 */
+#define I_HMB_SW_MASK 0x000000f0 /* To Host Mail S/W interrupts mask */
+#define I_HMB_SW_SHIFT 4 /* To Host Mail S/W interrupts shift */
+#define I_WR_OOSYNC BIT(8) /* Write Frame Out Of Sync */
+#define I_RD_OOSYNC BIT(9) /* Read Frame Out Of Sync */
+#define I_PC BIT(10) /* descriptor error */
+#define I_PD BIT(11) /* data error */
+#define I_DE BIT(12) /* Descriptor protocol Error */
+#define I_RU BIT(13) /* Receive descriptor Underflow */
+#define I_RO BIT(14) /* Receive fifo Overflow */
+#define I_XU BIT(15) /* Transmit fifo Underflow */
+#define I_RI BIT(16) /* Receive Interrupt */
+#define I_BUSPWR BIT(17) /* SDIO Bus Power Change (rev 9) */
+#define I_XMTDATA_AVAIL BIT(23) /* bits in fifo */
+#define I_XI BIT(24) /* Transmit Interrupt */
+#define I_RF_TERM BIT(25) /* Read Frame Terminate */
+#define I_WF_TERM BIT(26) /* Write Frame Terminate */
+#define I_PCMCIA_XU BIT(27) /* PCMCIA Transmit FIFO Underflow */
+#define I_SBINT BIT(28) /* sbintstatus Interrupt */
+#define I_CHIPACTIVE BIT(29) /* chip from doze to active state */
+#define I_SRESET BIT(30) /* CCCR RES interrupt */
+#define I_IOE2 BIT(31) /* CCCR IOE2 Bit Changed */
+#define I_ERRORS (I_PC | I_PD | I_DE | I_RU | I_RO | I_XU)
+#define I_DMA (I_RI | I_XI | I_ERRORS)
+
+/* SDA_FRAMECTRL */
+#define SFC_RF_TERM BIT(0) /* Read Frame Terminate */
+#define SFC_WF_TERM BIT(1) /* Write Frame Terminate */
+#define SFC_CRC4WOOS BIT(2) /* CRC error for write out of sync */
+#define SFC_ABORTALL BIT(3) /* Abort all in-progress frames */
+
+/*
+ * Software allocation of To SB Mailbox resources
+ */
+
+/* tosbmailbox bits corresponding to intstatus bits */
+#define SMB_NAK BIT(0) /* Frame NAK */
+#define SMB_INT_ACK BIT(1) /* Host Interrupt ACK */
+#define SMB_USE_OOB BIT(2) /* Use OOB Wakeup */
+#define SMB_DEV_INT BIT(3) /* Miscellaneous Interrupt */
+
+/* tosbmailboxdata */
+#define SMB_DATA_VERSION_SHIFT 16 /* host protocol version */
+#define SMB_DATA_WLAN_ON BIT(0) /* Host sends WLAN_ON interrupt to device */
+#define SMB_DATA_WLAN_OFF BIT(1) /* Host sends WLAN_OFF interrupt to device */
+#define SMB_DATA_DONGLE_CLEAN_UP BIT(7) /* Host indicates cleanup to dongle */
+
+/*
+ * Software allocation of To Host Mailbox resources
+ */
+
+/* intstatus bits */
+#define I_HMB_FC_STATE I_HMB_SW0 /* Flow Control State */
+#define I_HMB_FC_CHANGE I_HMB_SW1 /* Flow Control State Changed */
+#define I_HMB_FRAME_IND I_HMB_SW2 /* Frame Indication */
+#define I_HMB_HOST_INT I_HMB_SW3 /* Miscellaneous Interrupt */
+
+/* tohostmailboxdata */
+#define HMB_DATA_NAKHANDLED 0x0001 /* retransmit NAK'd frame */
+#define HMB_DATA_DEVREADY 0x0002 /* talk to host after enable */
+#define HMB_DATA_FC 0x0004 /* per prio flowcontrol update flag */
+#define HMB_DATA_FWREADY 0x0008 /* fw ready for protocol activity */
+#define HMB_DATA_FWHALT 0x0010 /* firmware halted */
+#define HMB_DATA_CREDITMAP 0x0020 /* Device sent interrupt to collect credit map */
+#define HMB_DATA_CPUSS_FWHALT 0x8000 /* cpuss firmware halted */
+
+#define HMB_DATA_FCDATA_MASK 0xff000000
+#define HMB_DATA_FCDATA_SHIFT 24
+
+#define HMB_DATA_VERSION_MASK 0x00ff0000
+#define HMB_DATA_VERSION_SHIFT 16
+
+/*
+ * Software-defined protocol header
+ */
+
+/* Current protocol version */
+#define SDPCM_PROT_VERSION 4
+
+/*
+ * Shared structure between dongle and the host.
+ * The structure contains pointers to trap or assert information.
+ */
+#define SDPCM_SHARED_VERSION 0x0003
+#define SDPCM_SHARED_VERSION_MASK 0x00FF
+#define SDPCM_SHARED_ASSERT_BUILT 0x0100
+#define SDPCM_SHARED_ASSERT 0x0200
+#define SDPCM_SHARED_TRAP 0x0400
+
+/* Space for header read, limit for data packets */
+#define MAX_HDR_READ BIT(6)
+#define MAX_RX_DATASZ 2048
+
+/* Bump up limit on waiting for HT to account for first startup;
+ * if the image is doing a CRC calculation before programming the PMU
+ * for HT availability, it could take a couple hundred ms more, so
+ * max out at a 1 second (1000000us).
+ */
+#undef PMU_MAX_TRANSITION_DLY
+#define PMU_MAX_TRANSITION_DLY 1000000
+
+/* Value for ChipClockCSR during initial setup */
+#define INFF_INIT_CLKCTL1 (SBSDIO_FORCE_HW_CLKREQ_OFF | \
+ SBSDIO_ALP_AVAIL_REQ)
+
+#define INFF_IDLE_ACTIVE 0 /* Do not request any SD clock change
+ * when idle
+ */
+#define INFF_IDLE_STOP (-1) /* Request SD clock be stopped */
+#define INFF_IDLE_INTERVAL 1
+
+#define KSO_WAIT_US 50
+#define KSO_MAX_SEQ_TIME_NS (1000000 * 10) /* Ideal time for kso sequence 10ms in ns*/
+#define MAX_KSO_ATTEMPTS (PMU_MAX_TRANSITION_DLY / KSO_WAIT_US)
+
+/* io_en */
+#define SDIO_FUNC_ENABLE_1 0x02
+#define SDIO_FUNC_ENABLE_2 0x04
+
+/* intr_status */
+#define INTR_STATUS_FUNC1 0x2
+#define INTR_STATUS_FUNC2 0x4
+
+#define INFF_SDIO_REG_DAR_H2D_MSG_0 0x10030
+#define INFF_SDIO_REG_DAR_D2H_MSG_0 0x10038
+#define INFF_SDIO_REG_DAR_D2H_MSG_1 0x1003c
+#define INFF_SDIO_REG_DAR_D2H_MSG_2 0x10046
+
+#define INFF_SDIO_REG_D2H_MSG_0 0x1800204C
+#define INFF_SDIO_REG_H2D_MSG_0 0x18002048
+
+#define INFF_SDIO_D2H_ACW74XX_READY BIT(24)
+
+#define CM3_SOCRAM_WRITE_END_LOCATION 0x80000
+
+/* Sdio rev 27 only */
+/* To read secure-mode bit */
+#define SBSDIO_FUNC1_SECURE_MODE 0x10001
+
+/* function 0 vendor specific CCCR registers */
+
+#define SDIO_CCCR_INTR_PND 0x05
+#define SDIO_CCCR_IO_ABORT 0x06
+#define SDIO_CCCR_IO_ABORT_RES BIT(3)
+#define SDIO_CCCR_INFF_CARDCAP 0xf0
+#define SDIO_CCCR_INFF_CARDCAP_CMD14_SUPPORT BIT(1)
+#define SDIO_CCCR_INFF_CARDCAP_CMD14_EXT BIT(2)
+#define SDIO_CCCR_INFF_CARDCAP_CMD_NODEC BIT(3)
+#define SDIO_CCCR_INFF_CARDCAP_CHIPID_PRESENT BIT(6)
+#define SDIO_CCCR_INFF_CARDCAP_SECURE_MODE BIT(7)
+
+/* Interrupt enable bits for each function */
+#define SDIO_CCCR_IEN_FUNC0 BIT(0)
+#define SDIO_CCCR_IEN_FUNC1 BIT(1)
+#define SDIO_CCCR_IEN_FUNC2 BIT(2)
+#define SDIO_CCCR_IEN_FUNC3 BIT(3)
+
+#define SDIO_CCCR_INFF_CARDCTRL 0xf1
+#define SDIO_CCCR_INFF_CARDCTRL_WLANRESET BIT(1)
+#define SDIO_CCCR_INFF_CARDCTRL_BTRESET BIT(2)
+
+#define SDIO_CCCR_INFF_SEPINT 0xf2
+#define SDIO_CCCR_INFF_SEPINT_MASK BIT(0)
+#define SDIO_CCCR_INFF_SEPINT_OE BIT(1)
+#define SDIO_CCCR_INFF_SEPINT_ACT_HI BIT(2)
+
+/* function 1 miscellaneous registers */
+
+/* sprom command and status */
+#define SBSDIO_SPROM_CS 0x10000
+/* sprom info register */
+#define SBSDIO_SPROM_INFO 0x10001
+/* sprom indirect access data byte 0 */
+#define SBSDIO_SPROM_DATA_LOW 0x10002
+/* sprom indirect access data byte 1 */
+#define SBSDIO_SPROM_DATA_HIGH 0x10003
+/* sprom indirect access addr byte 0 */
+#define SBSDIO_SPROM_ADDR_LOW 0x10004
+/* gpio select */
+#define SBSDIO_GPIO_SELECT 0x10005
+/* gpio output */
+#define SBSDIO_GPIO_OUT 0x10006
+/* gpio enable */
+#define SBSDIO_GPIO_EN 0x10007
+/* rev < 7, watermark for sdio device TX path */
+#define SBSDIO_WATERMARK 0x10008
+/* control busy signal generation */
+#define SBSDIO_DEVICE_CTL 0x10009
+
+/* SB Address Window Low (b15) */
+#define SBSDIO_FUNC1_SBADDRLOW 0x1000A
+/* SB Address Window Mid (b23:b16) */
+#define SBSDIO_FUNC1_SBADDRMID 0x1000B
+/* SB Address Window High (b31:b24) */
+#define SBSDIO_FUNC1_SBADDRHIGH 0x1000C
+/* Frame Control (frame term/abort) */
+#define SBSDIO_FUNC1_FRAMECTRL 0x1000D
+/* ChipClockCSR (ALP/HT ctl/status) */
+#define SBSDIO_FUNC1_CHIPCLKCSR 0x1000E
+/* SdioPullUp (on cmd, d0-d2) */
+#define SBSDIO_FUNC1_SDIOPULLUP 0x1000F
+/* Write Frame Byte Count Low */
+#define SBSDIO_FUNC1_WFRAMEBCLO 0x10019
+/* Write Frame Byte Count High */
+#define SBSDIO_FUNC1_WFRAMEBCHI 0x1001A
+/* Read Frame Byte Count Low */
+#define SBSDIO_FUNC1_RFRAMEBCLO 0x1001B
+/* Read Frame Byte Count High */
+#define SBSDIO_FUNC1_RFRAMEBCHI 0x1001C
+/* MesBusyCtl (rev 11) */
+#define SBSDIO_FUNC1_MESBUSYCTRL 0x1001D
+/* Watermark for sdio device RX path */
+#define SBSDIO_MESBUSY_RXFIFO_WM_MASK 0x7F
+#define SBSDIO_MESBUSY_RXFIFO_WM_SHIFT 0
+/* Enable busy capability for MES access */
+#define SBSDIO_MESBUSYCTRL_ENAB 0x80
+#define SBSDIO_MESBUSYCTRL_ENAB_SHIFT 7
+
+/* Sdio Core Rev 12 */
+#define SBSDIO_FUNC1_WAKEUPCTRL 0x1001E
+#define SBSDIO_FUNC1_WCTRL_ALPWAIT_MASK 0x1
+#define SBSDIO_FUNC1_WCTRL_ALPWAIT_SHIFT 0
+#define SBSDIO_FUNC1_WCTRL_HTWAIT_MASK 0x2
+#define SBSDIO_FUNC1_WCTRL_HTWAIT_SHIFT 1
+#define SBSDIO_FUNC1_SLEEPCSR 0x1001F
+#define SBSDIO_FUNC1_SLEEPCSR_KSO_MASK 0x1
+#define SBSDIO_FUNC1_SLEEPCSR_KSO_SHIFT 0
+#define SBSDIO_FUNC1_SLEEPCSR_KSO_EN 1
+#define SBSDIO_FUNC1_SLEEPCSR_DEVON_MASK 0x2
+#define SBSDIO_FUNC1_SLEEPCSR_DEVON_SHIFT 1
+#define SBSDIO_FUNC1_SLEEPCSR_BT_KSO_MASK 0x4
+#define SBSDIO_FUNC1_SLEEPCSR_BT_KSO_SHIFT 2
+#define SBSDIO_FUNC1_SLEEPCSR_BT_DEVON_MASK 0x8
+
+#define SBSDIO_FUNC1_MISC_REG_START 0x10000 /* f1 misc register start */
+#define SBSDIO_FUNC1_MISC_REG_LIMIT 0x1001F /* f1 misc register end */
+
+/* Sdio Core Rev 31 */
+/* Hard Reset SDIO core, output soft reset signal which should cause backplane reset */
+#define SDIO_IO_CARD_RESET 0x08
+
+/* function 1 OCP space */
+
+/* sb offset addr is <= 15 bits, 32k */
+#define SBSDIO_SB_OFT_ADDR_MASK 0x07FFF
+#define SBSDIO_SB_OFT_ADDR_LIMIT 0x08000
+/* with b15, maps to 32-bit SB access */
+#define SBSDIO_SB_ACCESS_2_4B_FLAG 0x08000
+
+/* Address bits from SBADDR regs */
+#define SBSDIO_SBWINDOW_MASK 0xffff8000
+
+#define INFF_CP_TX_WAIT_TIME 10
+
+/* watchdog polling interval */
+#define INFF_WD_POLL msecs_to_jiffies(10)
+
+/* SDIO function number definition */
+#define SDIO_FUNC_0 0
+#define SDIO_FUNC_1 1
+#define SDIO_FUNC_2 2
+#define SDIO_FUNC_3 3
+#define SDIO_FUNC_4 4
+#define SDIO_FUNC_5 5
+#define SDIO_FUNC_6 6
+#define SDIO_FUNC_7 7
+
+#define ACW74XX_WIFISS_ADDR_2_CPUSS_ADDR_OFFSET 0x5e000000
+
+/* RAM_APP */
+#define SMB_DATA_RAM_APP_START 0x10000000
+#define SMB_DATA_RAM_APP_END 0x20000000
+
+#define CM33_CPUSS_SRAM_START 0x34002000
+#define SRT_RAM_APP_TYPE 1
+#define CPUSS_RAM_APP_TYPE 2
+
+#define RAM_APP_TYPE_MASK 0xF
+#define RAM_APP_TYPE_SHIFT 24
+#define RAM_APP_SIZE_MASK 0xFFFFFF
+
+/* RAM_APP toHostmailboxData over DEVICE2HOSTREG1 */
+enum {
+ HMB_DATA_WIFISS_SRAM_STAGING = 1,
+ HMB_DATA_CPUSS_SRAM_STAGING = 2,
+};
+
+#define INFF_SDIO_D2H_READY_TIMEOUT 5000 /* msec */
+
+/* sdio core registers */
+struct sdpcmd_regs {
+ u32 corecontrol; /* 0x00, rev8 */
+ u32 corestatus; /* rev8 */
+ u32 pad0[1];
+ u32 biststatus; /* rev8 */
+
+ /* PCMCIA access */
+ u16 pcmciamesportaladdr; /* 0x010, rev8 */
+ u16 pad1[1];
+ u16 pcmciamesportalmask; /* rev8 */
+ u16 pad2[1];
+ u16 pcmciawrframebc; /* rev8 */
+ u16 pad3[1];
+ u16 pcmciaunderflowtimer; /* rev8 */
+ u16 pad4[1];
+
+ /* interrupt */
+ u32 intstatus; /* 0x020, rev8 */
+ u32 hostintmask; /* rev8 */
+ u32 intmask; /* rev8 */
+ u32 sbintstatus; /* rev8 */
+ u32 sbintmask; /* rev8 */
+ u32 funcintmask; /* rev4 */
+ u32 pad5[2];
+ u32 tosbmailbox; /* 0x040, rev8 */
+ u32 tohostmailbox; /* rev8 */
+ u32 tosbmailboxdata; /* rev8 */
+ u32 tohostmailboxdata; /* rev8 */
+
+ /* synchronized access to registers in SDIO clock domain */
+ u32 sdioaccess; /* 0x050, rev8 */
+ u32 pad6[3];
+
+ /* PCMCIA frame control */
+ u8 pcmciaframectrl; /* 0x060, rev8 */
+ u8 pad7[3];
+ u8 pcmciawatermark; /* rev8 */
+ u8 pad8[155];
+
+ /* interrupt batching control */
+ u32 intrcvlazy; /* 0x100, rev8 */
+ u32 pad9[3];
+
+ /* counters */
+ u32 cmd52rd; /* 0x110, rev8 */
+ u32 cmd52wr; /* rev8 */
+ u32 cmd53rd; /* rev8 */
+ u32 cmd53wr; /* rev8 */
+ u32 abort; /* rev8 */
+ u32 datacrcerror; /* rev8 */
+ u32 rdoutofsync; /* rev8 */
+ u32 wroutofsync; /* rev8 */
+ u32 writebusy; /* rev8 */
+ u32 readwait; /* rev8 */
+ u32 readterm; /* rev8 */
+ u32 writeterm; /* rev8 */
+ u32 pad10[40];
+ u32 clockctlstatus; /* rev8 */
+ u32 pad11[7];
+
+ u32 pad12[76]; /* DMA engines */
+
+ u32 chipid; /* SDIO ChipID Register, 0x330, rev31 */
+ u32 eromptr; /* SDIO EromPtrOffset Register, 0x334, rev31 */
+ u32 pad13[50];
+
+ /* SDIO/PCMCIA CIS region */
+ char cis[512]; /* 0x400-0x5ff, rev6 */
+
+ /* PCMCIA function control registers */
+ char pcmciafcr[256]; /* 0x600-6ff, rev6 */
+ u16 pad14[55];
+
+ /* PCMCIA backplane access */
+ u16 backplanecsr; /* 0x76E, rev6 */
+ u16 backplaneaddr0; /* rev6 */
+ u16 backplaneaddr1; /* rev6 */
+ u16 backplaneaddr2; /* rev6 */
+ u16 backplaneaddr3; /* rev6 */
+ u16 backplanedata0; /* rev6 */
+ u16 backplanedata1; /* rev6 */
+ u16 backplanedata2; /* rev6 */
+ u16 backplanedata3; /* rev6 */
+ u16 pad15[31];
+
+ /* sprom "size" & "blank" info */
+ u16 spromstatus; /* 0x7BE, rev2 */
+ u32 pad16[464];
+
+ u16 pad17[0x80];
+};
+
+#define SD_REG(field) \
+ (offsetof(struct sdpcmd_regs, field))
+
+static void inff_sdio_setup(struct device *dev, int err,
+ struct inff_fw_request *fwreq);
+static int inff_sdio_f2_ready(struct inff_sdio *bus);
+static int inff_sdio_bus_reset(struct device *dev);
+
+#ifdef DEBUG
+/* Device console log buffer state */
+struct inff_console {
+ uint count; /* Poll interval msec counter */
+ uint log_addr; /* Log struct address (fixed) */
+ struct rte_log_le log_le; /* Log struct (host copy) */
+ uint bufsize; /* Size of log buffer */
+ u8 *buf; /* Log buffer (host copy) */
+ uint last; /* Last buffer read index */
+};
+
+struct inff_trap_info {
+ __le32 type;
+ __le32 epc;
+ __le32 cpsr;
+ __le32 spsr;
+ __le32 r0; /* a1 */
+ __le32 r1; /* a2 */
+ __le32 r2; /* a3 */
+ __le32 r3; /* a4 */
+ __le32 r4; /* v1 */
+ __le32 r5; /* v2 */
+ __le32 r6; /* v3 */
+ __le32 r7; /* v4 */
+ __le32 r8; /* v5 */
+ __le32 r9; /* sb/v6 */
+ __le32 r10; /* sl/v7 */
+ __le32 r11; /* fp/v8 */
+ __le32 r12; /* ip */
+ __le32 r13; /* sp */
+ __le32 r14; /* lr */
+ __le32 pc; /* r15 */
+};
+#endif /* DEBUG */
+
+struct sdpcm_shared {
+ u32 flags;
+ u32 trap_addr;
+ u32 assert_exp_addr;
+ u32 assert_file_addr;
+ u32 assert_line;
+ u32 console_addr; /* Address of struct rte_console */
+ u32 msgtrace_addr;
+ u8 tag[32];
+ u32 brpt_addr;
+};
+
+struct sdpcm_shared_le {
+ __le32 flags;
+ __le32 trap_addr;
+ __le32 assert_exp_addr;
+ __le32 assert_file_addr;
+ __le32 assert_line;
+ __le32 console_addr; /* Address of struct rte_console */
+ __le32 msgtrace_addr;
+ u8 tag[32];
+ __le32 brpt_addr;
+};
+
+/* dongle SDIO bus specific header info */
+struct inff_sdio_hdrinfo {
+ u8 seq_num;
+ u8 channel_and_flags;
+ u16 len;
+ u16 len_left;
+ u16 len_nxtfrm;
+ u8 dat_offset;
+ bool lastfrm;
+ u16 tail_pad;
+};
+
+struct task_ctl {
+ struct task_struct *p_task;
+ struct completion comp;
+};
+
+/*
+ * hold counter variables
+ */
+struct inff_sdio_count {
+ uint intrcount; /* Count of device interrupt callbacks */
+ uint lastintrs; /* Count as of last watchdog timer */
+ uint pollcnt; /* Count of active polls */
+ uint regfails; /* Count of R_REG failures */
+ uint tx_sderrs; /* Count of tx attempts with sd errors */
+ uint fcqueued; /* Tx packets that got queued */
+ uint rxrtx; /* Count of rtx requests (NAK to dongle) */
+ uint rx_toolong; /* Receive frames too long to receive */
+ uint rxc_errors; /* SDIO errors when reading control frames */
+ uint rx_hdrfail; /* SDIO errors on header reads */
+ uint rx_badhdr; /* Bad received headers (roosync?) */
+ uint rx_badseq; /* Mismatched rx sequence number */
+ uint fc_rcvd; /* Number of flow-control events received */
+ uint fc_xoff; /* Number which turned on flow-control */
+ uint fc_xon; /* Number which turned off flow-control */
+ uint rxglomfail; /* Failed deglom attempts */
+ uint rxglomframes; /* Number of glom frames (superframes) */
+ uint rxglompkts; /* Number of packets from glom frames */
+ uint f2rxhdrs; /* Number of header reads */
+ uint f2rxdata; /* Number of frame data reads */
+ uint f2txdata; /* Number of f2 frame writes */
+ uint f1regdata; /* Number of f1 register accesses */
+ uint tickcnt; /* Number of watchdog been schedule */
+ ulong tx_ctlerrs; /* Err of sending ctrl frames */
+ ulong tx_ctlpkts; /* Ctrl frames sent to dongle */
+ ulong rx_ctlerrs; /* Err of processing rx ctrl frames */
+ ulong rx_ctlpkts; /* Ctrl frames processed from dongle */
+ ulong rx_readahead_cnt; /* packets where header read-ahead was used */
+};
+
+/* misc chip info needed by some of the routines */
+/* Private data for SDIO bus interaction */
+struct inff_sdio {
+ struct inff_sdio_dev *sdiodev; /* sdio device handler */
+ struct inff_chip *ci; /* Chip info struct */
+ struct inff_core *sdio_core; /* sdio core info struct */
+
+ u32 hostintmask; /* Copy of Host Interrupt Mask */
+ atomic_t intstatus; /* Intstatus bits (events) pending */
+ atomic_t fcstate; /* State of dongle flow-control */
+
+ uint blocksize; /* Block size of SDIO transfers */
+ uint roundup; /* Max roundup limit */
+
+ struct pktq txq; /* Queue length used for flow-control */
+ u8 flowcontrol; /* per prio flow control bitmask */
+ u8 tx_seq; /* Transmit sequence number (next) */
+ u8 tx_max; /* Maximum transmit sequence allowed */
+
+ u8 *hdrbuf; /* buffer for handling rx frame */
+ u8 *rxhdr; /* Header of current rx frame (in hdrbuf) */
+ u8 rx_seq; /* Receive sequence number (expected) */
+ struct inff_sdio_hdrinfo cur_read;
+ /* info of current read frame */
+ bool rxskip; /* Skip receive (awaiting NAK ACK) */
+ bool rxpending; /* Data frame pending in dongle */
+
+ uint rxbound; /* Rx frames to read before resched */
+ uint txbound; /* Tx frames to send before resched */
+ uint txminmax;
+
+ struct sk_buff *glomd; /* Packet containing glomming descriptor */
+ struct sk_buff_head glom; /* Packet list for glommed superframe */
+
+ u8 *rxbuf; /* Buffer for receiving control packets */
+ uint rxblen; /* Allocated length of rxbuf */
+ u8 *rxctl; /* Aligned pointer into rxbuf */
+ u8 *rxctl_orig; /* pointer for freeing rxctl */
+ uint rxlen; /* Length of valid data in buffer */
+ spinlock_t rxctl_lock; /* protection lock for ctrl frame resources */
+
+ u8 sdpcm_ver; /* Bus protocol reported by dongle */
+
+ atomic_t ipend; /* Device interrupt is pending */
+ uint spurious; /* Count of spurious interrupts */
+
+#ifdef DEBUG
+ uint console_interval;
+ uint cpuss_console_interval;
+ struct inff_console console; /* Console output polling support */
+ struct inff_console cpuss_console;
+ uint console_addr; /* Console address from shared struct */
+ uint cpuss_console_addr;
+#endif /* DEBUG */
+
+ uint clkstate; /* State of sd and backplane clock(s) */
+ s32 idletime; /* Control for activity timeout */
+ s32 idlecount; /* Activity timeout counter */
+ s32 idleclock; /* How to set bus driver when idle */
+ bool rxflow_mode; /* Rx flow control mode */
+ bool rxflow; /* Is rx flow control on */
+ bool alp_only; /* Don't use HT clock (ALP only) */
+
+ u8 *ctrl_frame_buf;
+ u16 ctrl_frame_len;
+ bool ctrl_frame_stat;
+ int ctrl_frame_err;
+
+ spinlock_t txq_lock; /* protect bus->txq */
+ wait_queue_head_t ctrl_wait;
+ wait_queue_head_t dcmd_resp_wait;
+
+ struct timer_list timer;
+ struct completion watchdog_wait;
+ struct task_struct *watchdog_tsk;
+ bool wd_active;
+
+ struct workqueue_struct *inff_wq;
+ struct work_struct datawork;
+ bool dpc_triggered;
+ bool dpc_running;
+
+ bool txoff; /* Transmit flow-controlled */
+ struct inff_sdio_count sdcnt;
+ bool sr_enabled; /* SaveRestore enabled */
+ bool sleeping;
+
+ u8 tx_hdrlen; /* sdio bus header length for tx packet */
+ bool txglom; /* host tx glomming enable flag */
+ u16 head_align; /* buffer pointer alignment */
+ u16 sgentry_align; /* scatter-gather buffer alignment */
+ struct mutex sdsem;
+ bool chipid_preset;
+ bool ddr50_mode; /* DDR50 Mode enabled*/
+ bool acw74xx_ramapp; /* indicate it's running rampap download */
+ bool acw74xx_f2_txdma_suspended; /* Tracks whether the TXDMA has been suspended */
+ bool credit_map_capable;
+};
+
+/* clkstate */
+#define CLK_NONE 0
+#define CLK_SDONLY 1
+#define CLK_PENDING 2
+#define CLK_AVAIL 3
+
+#ifdef DEBUG
+static int qcount[NUMPRIO];
+#endif /* DEBUG */
+
+#define RETRYCHAN(chan) ((chan) == SDPCM_EVENT_CHANNEL)
+
+/* Limit on rounding up frames */
+static const uint max_roundup = 512;
+
+#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT
+#define ALIGNMENT 8
+#else
+#define ALIGNMENT 4
+#endif
+
+enum inff_sdio_frmtype {
+ INFF_SDIO_FT_NORMAL,
+ INFF_SDIO_FT_SUPER,
+ INFF_SDIO_FT_SUB,
+};
+
+/* SDIO Pad drive strength to select value mappings */
+struct sdiod_drive_str {
+ u8 strength; /* Pad Drive Strength in mA */
+ u8 sel; /* Chip-specific select value */
+};
+
+/* SDIO Drive Strength to sel value table for PMU Rev 11 (1.8V) */
+static const struct sdiod_drive_str sdiod_drvstr_tab1_1v8[] = {
+ {32, 0x6},
+ {26, 0x7},
+ {22, 0x4},
+ {16, 0x5},
+ {12, 0x2},
+ {8, 0x3},
+ {4, 0x0},
+ {0, 0x1}
+};
+
+/* SDIO Drive Strength to sel value table for PMU Rev 13 (1.8v) */
+static const struct sdiod_drive_str sdiod_drive_strength_tab5_1v8[] = {
+ {6, 0x7},
+ {5, 0x6},
+ {4, 0x5},
+ {3, 0x4},
+ {2, 0x2},
+ {1, 0x1},
+ {0, 0x0}
+};
+
+/* SDIO Drive Strength to sel value table for PMU Rev 17 (1.8v) */
+static const struct sdiod_drive_str sdiod_drvstr_tab6_1v8[] = {
+ {3, 0x3},
+ {2, 0x2},
+ {1, 0x1},
+ {0, 0x0} };
+
+/* SDIO Drive Strength to sel value table for 43143 PMU Rev 17 (3.3V) */
+static const struct sdiod_drive_str sdiod_drvstr_tab2_3v3[] = {
+ {16, 0x7},
+ {12, 0x5},
+ {8, 0x3},
+ {4, 0x1}
+};
+
+/* per-board firmware binaries */
+#define INFF_CYW55900_FIRMWARE_BASENAME INFF_FW_DEFAULT_PATH "inffmac55900-sdio"
+#define INFF_ACW74XX_FIRMWARE_BASENAME INFF_FW_DEFAULT_PATH "inffmac7413-sdio"
+
+MODULE_FIRMWARE(INFF_CYW55900_FIRMWARE_BASENAME ".hex");
+MODULE_FIRMWARE(INFF_ACW74XX_FIRMWARE_BASENAME ".trx");
+
+static const struct inff_firmware_mapping inff_sdio_fwnames[] = {
+ INFF_FW_ENTRY(INF_CC_ACW74XX_CHIP_ID, 0xFFFFFFFF, ACW74XX),
+};
+
+static const struct inff_firmware_mapping inff_cp_fwnames[] = {
+ INFF_FW_ENTRY(INF_CC_CYW5591X_CHIP_ID, 0xFFFFFFFF, CYW55900),
+};
+
+#define TXCTL_CREDITS 2
+
+u8 inff_sdiod_func0_rb(struct inff_sdio_dev *sdiodev, u32 addr, int *ret)
+{
+ inff_dbg(SDIOEXT, "addr 0x%x\n", addr);
+
+ if (!sdiodev->bus->sleeping || sdiodev->ignore_bus_error)
+ return inff_sdiod_func0_rb_ext(sdiodev, addr, ret);
+
+ inff_err(" Error Access Not allowed\n");
+ if (ret)
+ *ret = -EPERM;
+ return 0xFF;
+}
+
+void inff_sdiod_func0_wb(struct inff_sdio_dev *sdiodev, u32 addr, u32 data,
+ int *ret)
+{
+ inff_dbg(SDIOEXT, "addr 0x%x val 0x%x\n", addr, data);
+
+ if (!sdiodev->bus->sleeping || sdiodev->ignore_bus_error) {
+ inff_sdiod_func0_wb_ext(sdiodev, addr, data, ret);
+ } else {
+ inff_err(" Error Access Not allowed\n");
+ if (ret)
+ *ret = -EPERM;
+ }
+}
+
+u8 inff_sdiod_readb(struct inff_sdio_dev *sdiodev, u32 addr, int *ret)
+{
+ inff_dbg(SDIOEXT, "addr 0x%x\n", addr);
+
+ if (!sdiodev->bus->sleeping || addr == SBSDIO_FUNC1_SLEEPCSR)
+ return inff_sdiod_readb_ext(sdiodev, addr, ret);
+
+ inff_err(" Error Access Not allowed\n");
+ if (ret)
+ *ret = -EPERM;
+ return 0xFF;
+}
+
+void inff_sdiod_writeb(struct inff_sdio_dev *sdiodev, u32 addr, u32 data,
+ int *ret)
+{
+ inff_dbg(SDIOEXT, "addr 0x%x val 0x%x\n", addr, data);
+
+ if (!sdiodev->bus->sleeping || addr == SBSDIO_FUNC1_SLEEPCSR) {
+ inff_sdiod_writeb_ext(sdiodev, addr, data, ret);
+ } else {
+ inff_err(" Error Access Not allowed\n");
+ if (ret)
+ *ret = -EPERM;
+ }
+}
+
+u8 inff_sdiod_func_rb(struct inff_sdio_dev *sdiodev, struct sdio_func *func, u32 addr, int *ret)
+{
+ inff_dbg(SDIOEXT, "addr 0x%x\n", addr);
+
+ if (!sdiodev->bus->sleeping)
+ return inff_sdiod_func_rb_ext(func, addr, ret);
+
+ inff_err(" Error Access Not allowed\n");
+ if (ret)
+ *ret = -EPERM;
+ return 0xFF;
+}
+
+void inff_sdiod_func_wb(struct inff_sdio_dev *sdiodev, struct sdio_func *func, u32 addr,
+ u32 data, int *ret)
+{
+ inff_dbg(SDIOEXT, "addr 0x%x val 0x%x\n", addr, data);
+
+ if (!sdiodev->bus->sleeping) {
+ inff_sdiod_func_wb_ext(func, addr, data, ret);
+ } else {
+ inff_err(" Error Access Not allowed\n");
+ if (ret)
+ *ret = -EPERM;
+ }
+}
+
+static void pkt_align(struct sk_buff *p, int len, int align)
+{
+ uint datalign;
+
+ datalign = (unsigned long)(p->data);
+ datalign = roundup(datalign, (align)) - datalign;
+ if (datalign)
+ skb_pull(p, datalign);
+ __skb_trim(p, len);
+}
+
+/* To check if there's window offered */
+static bool data_ok(struct inff_sdio *bus)
+{
+ return (u8)(bus->tx_max - bus->tx_seq) > TXCTL_CREDITS &&
+ ((u8)(bus->tx_max - bus->tx_seq) & 0x80) == 0;
+}
+
+/* To check if there's window offered */
+static bool txctl_ok(struct inff_sdio *bus)
+{
+ return (bus->tx_max - bus->tx_seq) != 0 &&
+ ((bus->tx_max - bus->tx_seq) & 0x80) == 0;
+}
+
+static int
+inff_sdio_kso_control(struct inff_sdio *bus, bool on)
+{
+ u8 wr_val = 0, rd_val, cmp_val, bmask;
+ int err = 0;
+ int err_cnt = 0;
+ int try_cnt = 0;
+ unsigned long kso_loop_time = 0;
+ struct timespec64 ts_start, ts_end, ts_delta;
+ struct inff_sdio_dev *sdiod = bus->sdiodev;
+
+ inff_dbg(SDIO, "KSO on=%d\n", on);
+
+ sdio_retune_crc_disable(bus->sdiodev->func1);
+
+ /* Cannot re-tune if device is asleep; defer till we're awake */
+ if (on)
+ sdio_retune_hold_now(bus->sdiodev->func1);
+
+ switch (bus->ci->socitype) {
+ case INFF_SOCI_AI:
+ wr_val = (on << SBSDIO_FUNC1_SLEEPCSR_KSO_SHIFT);
+
+ /* Start time of kso_sequence */
+ ktime_get_ts64(&ts_start);
+
+ /* Change bus width to 1-bit mode before kso 0 */
+ if (!on && bus->idleclock == INFF_IDLE_STOP)
+ inff_sdio_set_sdbus_clk_width(bus, SDIO_SDMODE_1BIT);
+
+ /* 1st KSO write goes to AOS wake up core if device is asleep */
+ inff_sdiod_writeb(bus->sdiodev, SBSDIO_FUNC1_SLEEPCSR, wr_val, &err);
+
+ /* The chip could go down immediately after
+ * KSO bit is cleared. So the further reads of KSO register could
+ * fail. Thereby just bailing out immediately after clearing KSO
+ * bit, to avoid polling of KSO bit.
+ */
+ if (!on) {
+ bus->sdiodev->sbwad_valid = 0;
+ return err;
+ }
+
+ /* device WAKEUP through KSO:
+ * write bit 0 & read back until
+ * both bits 0 (kso bit) & 1 (dev on status) are set
+ */
+ cmp_val = SBSDIO_FUNC1_SLEEPCSR_KSO_MASK |
+ SBSDIO_FUNC1_SLEEPCSR_DEVON_MASK;
+ bmask = cmp_val;
+ break;
+ case INFF_SOCI_CP:
+ wr_val = (on << SBSDIO_FUNC1_SLEEPCSR_BT_KSO_SHIFT);
+
+ /* Start time of kso_sequence */
+ ktime_get_ts64(&ts_start);
+
+ /* Change bus width to 1-bit mode before kso 0 */
+ if (!on && bus->idleclock == INFF_IDLE_STOP)
+ inff_sdio_set_sdbus_clk_width(bus, SDIO_SDMODE_1BIT);
+ else
+ /* Set Flag to ignore SDIO Bus access error during KSO */
+ bus->sdiodev->ignore_bus_error = true;
+
+ /* 1st KSO write goes to AOS wake up core if device is asleep */
+ inff_sdiod_writeb(bus->sdiodev, SBSDIO_FUNC1_SLEEPCSR, wr_val, &err);
+
+ /* The chip could go down immediately after
+ * KSO bit is cleared. So the further reads of KSO register could
+ * fail. Thereby just bailing out immediately after clearing KSO
+ * bit, to avoid polling of KSO bit.
+ */
+ if (!on) {
+ bus->sdiodev->sbwad_valid = 0;
+ return err;
+ }
+
+ /* device WAKEUP through KSO:
+ * write bit 2 & read back until
+ * both bits 2 (kso bit) & 3 (dev on status) are set
+ */
+ cmp_val = SBSDIO_FUNC1_SLEEPCSR_BT_KSO_MASK |
+ SBSDIO_FUNC1_SLEEPCSR_BT_DEVON_MASK;
+ bmask = cmp_val;
+ break;
+ }
+
+ do {
+ /* reliable KSO bit set/clr:
+ * the sdiod sleep write access is synced to PMU 32khz clk
+ * just one write attempt may fail,
+ * read it back until it matches written value
+ */
+ rd_val = inff_sdiod_readb(bus->sdiodev, SBSDIO_FUNC1_SLEEPCSR,
+ &err);
+ if (!err) {
+ if ((rd_val & bmask) == cmp_val)
+ break;
+ } else {
+ err_cnt++;
+ }
+
+ /* Do one KSO write-read-check without any delay in between the steps,
+ * if Device is already up KSO sequence will complete immediately
+ * without any delay for Host
+ */
+ if (try_cnt == 0) {
+ /* If Device is already up then it will not reach here,
+ * if the control reaches here it means device is in sleep
+ * so delay for some time to let Device wake up before
+ * starting subsequent KSO wr-rd-check sequence
+ * Delay should be less than the time device takes to wakeup
+ * in normal case, because giving more delay than what device needs
+ * will lead to host being unnecessarily blocked here while device is
+ * already up and ready, leading to more power consumptions of both
+ * host and device, as well as overall increased response delays
+ */
+ usleep_range(2500, 3000);
+ } else {
+ /* Initial delay is done, now do continuous KSO wr-rd-check
+ * sequence with some small delay
+ */
+ usleep_range(KSO_WAIT_US / 2, KSO_WAIT_US);
+ }
+
+ inff_sdiod_writeb(bus->sdiodev, SBSDIO_FUNC1_SLEEPCSR, wr_val,
+ &err);
+
+ } while (try_cnt++ < MAX_KSO_ATTEMPTS);
+
+ ktime_get_ts64(&ts_end);
+ ts_delta = timespec64_sub(ts_end, ts_start);
+ kso_loop_time = timespec64_to_ns(&ts_delta);
+
+ if (try_cnt > MAX_KSO_ATTEMPTS)
+ inff_dbg(SDIO, "KSO=%d sequence failed after max tries=%d and err_cnt=%d\n"
+ "kso_seq_time=%luns rd_val=0x%x err=%d\n",
+ on, try_cnt, err_cnt, kso_loop_time, rd_val, err);
+
+ if (bus->idleclock == INFF_IDLE_STOP) {
+ /* Change the bus width to 4-bit mode on kso 1 */
+ sdiod->ignore_bus_error = true;
+ inff_sdio_set_sdbus_clk_width(bus, SDIO_SDMODE_4BIT);
+ sdiod->ignore_bus_error = false;
+ }
+
+ /* New KSO Sequence for H1 DDR50 Mode*/
+ if (bus->ddr50_mode) {
+ u32 ret, chipid;
+
+ /* Set Flag to ignore SDIO Bus access error during KSO */
+ sdiod->ignore_bus_error = true;
+ chipid = inff_sdiod_readl(sdiod,
+ bus->ci->ccsec->bus_corebase + SD_REG(chipid),
+ &ret);
+ /* Clear Flag to ignore SDIO Bus access error during KSO */
+ sdiod->ignore_bus_error = false;
+ inff_dbg(SDIO, "chipid: 0x%x ret = 0x%x\n", chipid, ret);
+ }
+
+ sdio_retune_release(bus->sdiodev->func1);
+
+ if (kso_loop_time > KSO_MAX_SEQ_TIME_NS)
+ inff_err("ERR: KSO=%d sequence took %luns > expected %uns try_cnt=%d\n"
+ "err_cnt=%d rd_val=0x%x err=%d\n",
+ on, kso_loop_time, KSO_MAX_SEQ_TIME_NS, try_cnt, err_cnt, rd_val, err);
+
+ inff_dbg(SDIO, "INFO: KSO=%d try_cnt=%d err_cnt=%d kso_seq_time=%luns\n"
+ "rd_val=0x%x err=%d\n", on, try_cnt, err_cnt, kso_loop_time, rd_val, err);
+
+ sdio_retune_crc_enable(bus->sdiodev->func1);
+
+ return err;
+}
+
+#define HOSTINTMASK (I_HMB_SW_MASK | I_CHIPACTIVE)
+
+/* Turn backplane clock on or off */
+static int inff_sdio_htclk(struct inff_sdio *bus, bool on, bool pendok)
+{
+ int err;
+ u8 clkctl, clkreq, devctl;
+ unsigned long timeout;
+
+ clkctl = 0;
+
+ if (bus->sr_enabled) {
+ bus->clkstate = (on ? CLK_AVAIL : CLK_SDONLY);
+ return 0;
+ }
+
+ if (on) {
+ /* Request HT Avail */
+ clkreq =
+ bus->alp_only ? SBSDIO_ALP_AVAIL_REQ : SBSDIO_HT_AVAIL_REQ;
+
+ inff_sdiod_writeb(bus->sdiodev, SBSDIO_FUNC1_CHIPCLKCSR,
+ clkreq, &err);
+ if (err) {
+ inff_err("HT Avail request error: %d\n", err);
+ return -EBADE;
+ }
+
+ /* Check current status */
+ clkctl = inff_sdiod_readb(bus->sdiodev,
+ SBSDIO_FUNC1_CHIPCLKCSR, &err);
+ if (err) {
+ inff_err("HT Avail read error: %d\n", err);
+ return -EBADE;
+ }
+
+ /* Go to pending and await interrupt if appropriate */
+ if (!SBSDIO_CLKAV(clkctl, bus->alp_only) && pendok) {
+ /* Allow only clock-available interrupt */
+ devctl = inff_sdiod_readb(bus->sdiodev,
+ SBSDIO_DEVICE_CTL, &err);
+ if (err) {
+ inff_err("Devctl error setting CA: %d\n", err);
+ return -EBADE;
+ }
+
+ devctl |= SBSDIO_DEVCTL_CA_INT_ONLY;
+ inff_sdiod_writeb(bus->sdiodev, SBSDIO_DEVICE_CTL,
+ devctl, &err);
+ inff_dbg(SDIO, "CLKCTL: set PENDING\n");
+ bus->clkstate = CLK_PENDING;
+
+ return 0;
+ } else if (bus->clkstate == CLK_PENDING) {
+ /* Cancel CA-only interrupt filter */
+ devctl = inff_sdiod_readb(bus->sdiodev,
+ SBSDIO_DEVICE_CTL, &err);
+ devctl &= ~SBSDIO_DEVCTL_CA_INT_ONLY;
+ inff_sdiod_writeb(bus->sdiodev, SBSDIO_DEVICE_CTL,
+ devctl, &err);
+ }
+
+ /* Otherwise, wait here (polling) for HT Avail */
+ timeout = jiffies +
+ msecs_to_jiffies(PMU_MAX_TRANSITION_DLY / 1000);
+ while (!SBSDIO_CLKAV(clkctl, bus->alp_only)) {
+ clkctl = inff_sdiod_readb(bus->sdiodev,
+ SBSDIO_FUNC1_CHIPCLKCSR,
+ &err);
+ if (time_after(jiffies, timeout))
+ break;
+
+ usleep_range(5000, 10000);
+ }
+ if (err) {
+ inff_err("HT Avail request error: %d\n", err);
+ return -EBADE;
+ }
+ if (!SBSDIO_CLKAV(clkctl, bus->alp_only)) {
+ inff_err("HT Avail timeout (%d): clkctl 0x%02x\n",
+ PMU_MAX_TRANSITION_DLY, clkctl);
+ return -EBADE;
+ }
+
+ /* Mark clock available */
+ bus->clkstate = CLK_AVAIL;
+ inff_dbg(SDIO, "CLKCTL: turned ON\n");
+
+#if defined(DEBUG)
+ if (!bus->alp_only) {
+ if (SBSDIO_ALPONLY(clkctl))
+ inff_err("HT Clock should be on\n");
+ }
+#endif /* defined (DEBUG) */
+
+ } else {
+ clkreq = 0;
+
+ if (bus->clkstate == CLK_PENDING) {
+ /* Cancel CA-only interrupt filter */
+ devctl = inff_sdiod_readb(bus->sdiodev,
+ SBSDIO_DEVICE_CTL, &err);
+ devctl &= ~SBSDIO_DEVCTL_CA_INT_ONLY;
+ inff_sdiod_writeb(bus->sdiodev, SBSDIO_DEVICE_CTL,
+ devctl, &err);
+ }
+
+ bus->clkstate = CLK_SDONLY;
+ inff_sdiod_writeb(bus->sdiodev, SBSDIO_FUNC1_CHIPCLKCSR,
+ clkreq, &err);
+ inff_dbg(SDIO, "CLKCTL: turned OFF\n");
+ if (err) {
+ inff_err("Failed access turning clock off: %d\n",
+ err);
+ return -EBADE;
+ }
+ }
+ return 0;
+}
+
+/**
+ * inff_sdio_set_sdbus_clk_width - set SD clock enable/disable and sd_mode
+ * @func: SDIO function attached to host
+ * @flags: reusing existing mmc->pm_flags to pass idle clk disable/enable or
+ * change sdbus width through mmc.
+ */
+int inff_sdio_set_sdbus_clk_width(struct inff_sdio *bus, unsigned int flags)
+{
+ struct mmc_host *host;
+ u8 ctrl;
+ int ret = 0;
+
+ if (WARN_ON(!bus))
+ return -EINVAL;
+
+ host = bus->sdiodev->func1->card->host;
+
+ if (flags == SDIO_IDLECLOCK_DIS || flags == SDIO_IDLECLOCK_EN) {
+ /* Switch OFF/ON SD CLOCK in sdio Host Controller */
+ host->pm_caps |= flags;
+ /* Call SDHCI interface function from ops */
+ host->ops->set_ios(host, &host->ios);
+ } else if (flags == SDIO_SDMODE_1BIT || flags == SDIO_SDMODE_4BIT) {
+ ctrl = inff_sdiod_func0_rb(bus->sdiodev, SDIO_CCCR_IF, &ret);
+ /* Check for Error */
+ if (ret)
+ return ret;
+
+ /* Clear first two bits
+ * 00 - 1 bit wide
+ * 10 - 4 bit wide
+ */
+ ctrl &= ~SDIO_BUS_WIDTH_MASK;
+ /* set as 4-bit bus width */
+ if (flags == SDIO_SDMODE_4BIT)
+ ctrl |= SDIO_BUS_WIDTH_4BIT;
+
+ inff_sdiod_func0_wb(bus->sdiodev, SDIO_CCCR_IF, ctrl, &ret);
+ /* Update HOST CTRL register with 1 bit or 4 bit mode */
+ host->pm_caps |= flags;
+ /* Call SDHCI interface function from ops */
+ host->ops->set_ios(host, &host->ios);
+ }
+ return ret;
+}
+
+/* Change idle/active SD state */
+static int inff_sdio_sdclk(struct inff_sdio *bus, bool on)
+{
+ if (bus->idleclock == INFF_IDLE_STOP)
+ inff_sdio_set_sdbus_clk_width(bus,
+ on ? SDIO_IDLECLOCK_DIS :
+ SDIO_IDLECLOCK_EN);
+
+ if (on)
+ bus->clkstate = CLK_SDONLY;
+ else
+ bus->clkstate = CLK_NONE;
+
+ return 0;
+}
+
+/* Transition SD and backplane clock readiness */
+int inff_sdio_clkctl(struct inff_sdio *bus, uint target, bool pendok)
+{
+#ifdef DEBUG
+ uint oldstate = bus->clkstate;
+#endif /* DEBUG */
+
+ /* Early exit if we're already there */
+ if (bus->clkstate == target)
+ return 0;
+
+ switch (target) {
+ case CLK_AVAIL:
+ /* Make sure SD clock is available */
+ if (bus->clkstate == CLK_NONE)
+ inff_sdio_sdclk(bus, true);
+ /* Now request HT Avail on the backplane */
+ inff_sdio_htclk(bus, true, pendok);
+ break;
+
+ case CLK_SDONLY:
+ /* Remove HT request, or bring up SD clock */
+ if (bus->clkstate == CLK_NONE)
+ inff_sdio_sdclk(bus, true);
+ else if (bus->clkstate == CLK_AVAIL)
+ inff_sdio_htclk(bus, false, false);
+ else
+ inff_err("request for %d -> %d\n",
+ bus->clkstate, target);
+ break;
+
+ case CLK_NONE:
+ /* Make sure to remove HT request */
+ if (bus->clkstate == CLK_AVAIL)
+ inff_sdio_htclk(bus, false, false);
+ /* Now remove the SD clock */
+ inff_sdio_sdclk(bus, false);
+ break;
+ }
+#ifdef DEBUG
+ inff_dbg(SDIO, "%d -> %d\n", oldstate, bus->clkstate);
+#endif /* DEBUG */
+
+ return 0;
+}
+
+static int
+inff_sdio_bus_sleep(struct inff_sdio *bus, bool sleep, bool pendok)
+{
+ int err = 0;
+ u8 clkcsr;
+
+ inff_dbg(SDIO, "BUS state request %s, currently %s\n",
+ (sleep ? "SLEEP" : "WAKE"),
+ (bus->sleeping ? "SLEEP" : "WAKE"));
+
+ /* Done if we're already in the requested state */
+ if (sleep == bus->sleeping)
+ goto end;
+
+ switch (bus->ci->socitype) {
+ case INFF_SOCI_CP:
+ /* Going to sleep */
+ if (sleep) {
+ err = inff_sdio_kso_control(bus, false);
+ if (bus->idleclock == INFF_IDLE_STOP)
+ inff_sdio_sdclk(bus, false);
+ } else {
+ if (bus->idleclock == INFF_IDLE_STOP && bus->clkstate == CLK_NONE)
+ inff_sdio_sdclk(bus, true);
+ err = inff_sdio_kso_control(bus, true);
+ }
+ if (err) {
+ inff_err("error while changing bus sleep state %d\n", err);
+ goto done;
+ }
+ break;
+ case INFF_SOCI_AI:
+ /* If SR is enabled control bus state with KSO */
+ if (bus->sr_enabled) {
+ /* Going to sleep */
+ if (sleep) {
+ clkcsr = inff_sdiod_readb(bus->sdiodev,
+ SBSDIO_FUNC1_CHIPCLKCSR,
+ &err);
+ if ((clkcsr & SBSDIO_CSR_MASK) == 0) {
+ inff_dbg(SDIO, "no clock, set ALP\n");
+ inff_sdiod_writeb(bus->sdiodev,
+ SBSDIO_FUNC1_CHIPCLKCSR,
+ SBSDIO_ALP_AVAIL_REQ, &err);
+ }
+
+ err = inff_sdio_kso_control(bus, false);
+
+ if (bus->idleclock == INFF_IDLE_STOP)
+ inff_sdio_sdclk(bus, false);
+ } else {
+ if (bus->idleclock == INFF_IDLE_STOP && bus->clkstate == CLK_NONE)
+ inff_sdio_clkctl(bus, CLK_SDONLY, false);
+
+ err = inff_sdio_kso_control(bus, true);
+ }
+ if (err) {
+ inff_err("error while changing bus sleep state %d\n",
+ err);
+ goto done;
+ }
+ }
+ break;
+ }
+
+end:
+ /* control clocks */
+ if (sleep) {
+ if (!bus->sr_enabled)
+ inff_sdio_clkctl(bus, CLK_NONE, pendok);
+ } else {
+ if (inff_socitype_is_ai(bus->sdiodev->bus_if))
+ inff_sdio_clkctl(bus, CLK_AVAIL, pendok);
+
+ inff_sdio_wd_timer(bus, true);
+ }
+ bus->sleeping = sleep;
+ inff_dbg(SDIO, "BUS state changed to %s\n",
+ (sleep ? "SLEEP" : "WAKE"));
+done:
+ inff_dbg(SDIO, "BUS state request err=%d\n", err);
+ return err;
+}
+
+#ifdef DEBUG
+static inline bool inff_sdio_valid_shared_address(u32 addr)
+{
+ return !(addr == 0 || ((~addr >> 16) & 0xffff) == (addr & 0xffff));
+}
+
+static int inff_sdio_readshared(struct inff_sdio *bus,
+ struct sdpcm_shared *sh)
+{
+ u32 addr = 0;
+ int rv;
+ u32 shaddr = 0;
+ struct sdpcm_shared_le sh_le;
+ __le32 addr_le;
+ u32 offset = 0;
+ u32 tmp_addr = 0;
+
+ sdio_claim_host(bus->sdiodev->func1);
+ inff_sdio_bus_sleep(bus, false, false);
+
+ /** Read last word in socram to determine
+ * address of sdpcm_shared structure
+ */
+ if (bus->ci->chip == INF_CC_ACW74XX_CHIP_ID) {
+ shaddr = bus->ci->rambase + bus->ci->ramsize - 4;
+ offset = ACW74XX_WIFISS_ADDR_2_CPUSS_ADDR_OFFSET;
+
+ bus->cpuss_console_addr = 0;
+ rv = inff_sdiod_ramrw(bus->sdiodev, false, INFF_CPUSS_CONSOLE_SHARED_ADDR,
+ (u8 *)&addr_le, 4);
+ if (rv < 0)
+ return rv;
+ tmp_addr = le32_to_cpu(addr_le);
+ if (tmp_addr > INFF_CPUSS_CONSOLE_ADDR_RANGE_LOW &&
+ tmp_addr < INFF_CPUSS_CONSOLE_ADDR_RANGE_HIGH)
+ bus->cpuss_console_addr = le32_to_cpu(addr_le);
+
+ inff_dbg(INFO, " CPUSS console address 0x%x\n", bus->cpuss_console_addr);
+ } else {
+ shaddr = bus->ci->rambase + bus->ci->ramsize - 4;
+ if (!bus->ci->rambase && inff_chip_sr_capable(bus->ci))
+ shaddr -= bus->ci->srsize;
+ }
+
+ rv = inff_sdiod_ramrw(bus->sdiodev, false, shaddr,
+ (u8 *)&addr_le, 4);
+ if (rv < 0)
+ goto fail;
+
+ /*
+ * Check if addr is valid.
+ * NVRAM length at the end of memory should have been overwritten.
+ */
+ addr = le32_to_cpu(addr_le);
+ if (!inff_sdio_valid_shared_address(addr)) {
+ inff_err("invalid sdpcm_shared address 0x%08X\n", addr);
+ rv = -EINVAL;
+ goto fail;
+ }
+
+ addr += offset;
+
+ inff_dbg(INFO, "sdpcm_shared address 0x%08X\n", addr);
+
+ /* Read hndrte_shared structure */
+ rv = inff_sdiod_ramrw(bus->sdiodev, false, addr, (u8 *)&sh_le,
+ sizeof(struct sdpcm_shared_le));
+ if (rv < 0)
+ goto fail;
+
+ sdio_release_host(bus->sdiodev->func1);
+
+ /* Endianness */
+ sh->flags = le32_to_cpu(sh_le.flags);
+ sh->trap_addr = le32_to_cpu(sh_le.trap_addr) + offset;
+ sh->assert_exp_addr = le32_to_cpu(sh_le.assert_exp_addr) + offset;
+ sh->assert_file_addr = le32_to_cpu(sh_le.assert_file_addr) + offset;
+ sh->assert_line = le32_to_cpu(sh_le.assert_line) + offset;
+ sh->console_addr = le32_to_cpu(sh_le.console_addr) + offset;
+ sh->msgtrace_addr = le32_to_cpu(sh_le.msgtrace_addr) + offset;
+
+ inff_dbg(INFO, "rte_console address is is 0x%08x\n", sh->console_addr);
+
+ if ((sh->flags & SDPCM_SHARED_VERSION_MASK) > SDPCM_SHARED_VERSION) {
+ inff_err("sdpcm_shared version %d unsupported, drivers expects version <= %d\n",
+ sh->flags & SDPCM_SHARED_VERSION_MASK,
+ SDPCM_SHARED_VERSION);
+ return -EPROTO;
+ }
+ return 0;
+
+fail:
+ inff_err("unable to obtain sdpcm_shared info: rv=%d (addr=0x%x)\n",
+ rv, addr);
+ sdio_release_host(bus->sdiodev->func1);
+ return rv;
+}
+
+static void inff_sdio_get_console_addr(struct inff_sdio *bus)
+{
+ struct sdpcm_shared sh;
+
+ if (inff_sdio_readshared(bus, &sh) == 0) {
+ /* reset the last read count when buffer address is updated */
+ bus->console.last = 0;
+ bus->console_addr = sh.console_addr;
+ }
+}
+#else
+static void inff_sdio_get_console_addr(struct inff_sdio *bus)
+{
+}
+#endif /* DEBUG */
+
+static u32 inff_sdio_hostmail(struct inff_sdio *bus, u32 *hmbd)
+{
+ struct inff_sdio_dev *sdiod = bus->sdiodev;
+ struct inff_core *core = bus->sdio_core;
+ struct inff_fws_info *fws;
+ u32 intstatus = 0;
+ u32 hmb_data;
+ bool fw_halt = false;
+ u8 fcbits;
+ int ret;
+
+ /* Read mailbox data and ack that we did so */
+ hmb_data = inff_sdiod_readl(sdiod,
+ core->base + SD_REG(tohostmailboxdata),
+ &ret);
+ /* skip generating SMB_INT_ACK if there is no MB data */
+ if (!ret && hmb_data)
+ inff_sdiod_writel(sdiod, core->base + SD_REG(tosbmailbox),
+ SMB_INT_ACK, &ret);
+
+ bus->sdcnt.f1regdata += 2;
+
+ /* dongle indicates the firmware has halted/crashed */
+ if (hmb_data & HMB_DATA_FWHALT) {
+ inff_dbg(SDIO, "mailbox indicates firmware halted\n");
+ fw_halt = true;
+ }
+ if (hmb_data & HMB_DATA_CPUSS_FWHALT) {
+ inff_dbg(SDIO, "mailbox indicates cpuss firmware halted\n");
+ fw_halt = true;
+ }
+ if (fw_halt)
+ inff_fw_crashed(&sdiod->func1->dev);
+
+ /* Dongle recomposed rx frames, accept them again */
+ if (hmb_data & HMB_DATA_NAKHANDLED) {
+ inff_dbg(SDIO, "Dongle reports NAK handled, expect rtx of %d\n",
+ bus->rx_seq);
+ if (!bus->rxskip)
+ inff_err("unexpected NAKHANDLED!\n");
+
+ bus->rxskip = false;
+ intstatus |= I_HMB_FRAME_IND;
+ }
+
+ /*
+ * DEVREADY does not occur with gSPI.
+ */
+ if (hmb_data & (HMB_DATA_DEVREADY | HMB_DATA_FWREADY)) {
+ bus->sdpcm_ver =
+ (hmb_data & HMB_DATA_VERSION_MASK) >>
+ HMB_DATA_VERSION_SHIFT;
+ if (bus->sdpcm_ver != SDPCM_PROT_VERSION)
+ inff_err("ver mismatch, %d, expecting %d, hmb_data 0x%x\n",
+ bus->sdpcm_ver, SDPCM_PROT_VERSION, hmb_data);
+ else
+ inff_dbg(SDIO, "Dongle ready, protocol version %d\n",
+ bus->sdpcm_ver);
+
+ /*
+ * Retrieve console state address now that firmware should have
+ * updated it.
+ */
+ inff_sdio_get_console_addr(bus);
+ }
+
+ /*
+ * Flow Control has been moved into the RX headers and this out of band
+ * method isn't used any more.
+ * remaining backward compatible with older dongles.
+ */
+ if (hmb_data & HMB_DATA_FC) {
+ fcbits = (hmb_data & HMB_DATA_FCDATA_MASK) >>
+ HMB_DATA_FCDATA_SHIFT;
+
+ if (fcbits & ~bus->flowcontrol)
+ bus->sdcnt.fc_xoff++;
+
+ if (bus->flowcontrol & ~fcbits)
+ bus->sdcnt.fc_xon++;
+
+ bus->sdcnt.fc_rcvd++;
+ bus->flowcontrol = fcbits;
+ }
+
+ /* query FC credit map info for ACW74XX */
+ if (hmb_data & HMB_DATA_CREDITMAP) {
+ bus->credit_map_capable = true;
+ if (bus->ci->chip == INF_CC_ACW74XX_CHIP_ID) {
+ fws = drvr_to_fws(sdiod->bus_if->drvr);
+ inff_creditmap_query(fws);
+ }
+ }
+
+ /* Shouldn't be any others */
+ if (hmb_data & ~(HMB_DATA_DEVREADY |
+ HMB_DATA_NAKHANDLED |
+ HMB_DATA_FC |
+ HMB_DATA_FWREADY |
+ HMB_DATA_FWHALT |
+ HMB_DATA_CREDITMAP |
+ HMB_DATA_CPUSS_FWHALT |
+ HMB_DATA_FCDATA_MASK | HMB_DATA_VERSION_MASK))
+ inff_err("Unknown mailbox data content: 0x%02x\n",
+ hmb_data);
+ /* Populate hmb_data if argument is passed for DS1 check later */
+ if (hmbd)
+ *hmbd = hmb_data;
+
+ return intstatus;
+}
+
+static void inff_sdio_rxfail(struct inff_sdio *bus, bool abort, bool rtx)
+{
+ struct inff_sdio_dev *sdiod = bus->sdiodev;
+ struct inff_core *core = bus->sdio_core;
+ uint retries = 0;
+ u16 lastrbc;
+ u8 hi, lo;
+ int err;
+
+ inff_err("%sterminate frame%s\n",
+ abort ? "abort command, " : "",
+ rtx ? ", send NAK" : "");
+
+ if (abort)
+ inff_sdiod_abort(bus->sdiodev, bus->sdiodev->func2);
+
+ inff_sdiod_writeb(bus->sdiodev, SBSDIO_FUNC1_FRAMECTRL, SFC_RF_TERM,
+ &err);
+ bus->sdcnt.f1regdata++;
+
+ /* Wait until the packet has been flushed (device/FIFO stable) */
+ for (lastrbc = retries = 0xffff; retries > 0; retries--) {
+ hi = inff_sdiod_readb(bus->sdiodev, SBSDIO_FUNC1_RFRAMEBCHI,
+ &err);
+ lo = inff_sdiod_readb(bus->sdiodev, SBSDIO_FUNC1_RFRAMEBCLO,
+ &err);
+ bus->sdcnt.f1regdata += 2;
+
+ if (hi == 0 && lo == 0)
+ break;
+
+ if ((hi > (lastrbc >> 8)) && (lo > (lastrbc & 0x00ff))) {
+ inff_err("count growing: last 0x%04x now 0x%04x\n",
+ lastrbc, (hi << 8) + lo);
+ }
+ lastrbc = (hi << 8) + lo;
+ }
+
+ if (!retries)
+ inff_err("count never zeroed: last 0x%04x\n", lastrbc);
+ else
+ inff_dbg(SDIO, "flush took %d iterations\n", 0xffff - retries);
+
+ if (rtx) {
+ bus->sdcnt.rxrtx++;
+ inff_sdiod_writel(sdiod, core->base + SD_REG(tosbmailbox),
+ SMB_NAK, &err);
+
+ bus->sdcnt.f1regdata++;
+ if (err == 0)
+ bus->rxskip = true;
+ }
+
+ /* Clear partial in any case */
+ bus->cur_read.len = 0;
+}
+
+static void inff_sdio_txfail(struct inff_sdio *bus)
+{
+ struct inff_sdio_dev *sdiodev = bus->sdiodev;
+ u8 i, hi, lo;
+
+ /* On failure, abort the command and terminate the frame */
+ inff_err("sdio error, abort command and terminate frame\n");
+ bus->sdcnt.tx_sderrs++;
+
+ if (inff_socitype_is_cp(bus->sdiodev->bus_if))
+ return;
+
+ inff_sdiod_abort(sdiodev, sdiodev->func2);
+ inff_sdiod_writeb(sdiodev, SBSDIO_FUNC1_FRAMECTRL, SFC_WF_TERM, NULL);
+ bus->sdcnt.f1regdata++;
+
+ for (i = 0; i < 3; i++) {
+ hi = inff_sdiod_readb(sdiodev, SBSDIO_FUNC1_WFRAMEBCHI, NULL);
+ lo = inff_sdiod_readb(sdiodev, SBSDIO_FUNC1_WFRAMEBCLO, NULL);
+ bus->sdcnt.f1regdata += 2;
+ if (hi == 0 && lo == 0)
+ break;
+ }
+}
+
+static int inff_cp_sdio_wait_dev_ready(struct inff_sdio_dev *sdiodev, bool polling)
+{
+ struct inff_sdio *bus = sdiodev->bus;
+ struct inff_core *core = bus->sdio_core;
+ u32 addr = core->base + SD_REG(tohostmailboxdata);
+ static u32 prev_data_seq = 1;
+ u32 current_seq;
+ int ret = -EBUSY, err;
+
+ /* WAR: wait for NCP lib(in device) to receive previous data */
+ do {
+ current_seq = inff_sdiod_readl(bus->sdiodev, addr, &err);
+ if (err) {
+ inff_err("failed to read host mailbox data: %d\n", err);
+ break;
+ }
+ if (current_seq != prev_data_seq) {
+ prev_data_seq = current_seq;
+ ret = 0;
+ break;
+ }
+
+ if (polling)
+ usleep_range(INFF_CP_TX_WAIT_TIME / 2, INFF_CP_TX_WAIT_TIME);
+
+ } while (polling);
+
+ return ret;
+}
+
+/* return total length of buffer chain */
+static uint inff_sdio_glom_len(struct inff_sdio *bus)
+{
+ struct sk_buff *p;
+ uint total;
+
+ total = 0;
+ skb_queue_walk(&bus->glom, p)
+ total += p->len;
+ return total;
+}
+
+static void inff_sdio_free_glom(struct inff_sdio *bus)
+{
+ struct sk_buff *cur, *next;
+
+ skb_queue_walk_safe(&bus->glom, cur, next) {
+ skb_unlink(cur, &bus->glom);
+ inff_pkt_buf_free_skb(cur);
+ }
+}
+
+/*
+ * inffmac sdio bus specific header
+ * This is the lowest layer header wrapped on the packets transmitted between
+ * host and WiFi dongle which contains information needed for SDIO core and
+ * firmware
+ *
+ * It consists of 3 parts: hardware header, hardware extension header and
+ * software header
+ * hardware header (frame tag) - 4 bytes
+ * Byte 0~1: Frame length
+ * Byte 2~3: Checksum, bit-wise inverse of frame length
+ * hardware extension header - 8 bytes
+ * Tx glom mode only, N/A for Rx or normal Tx
+ * Byte 0~1: Packet length excluding hw frame tag
+ * Byte 2: Reserved
+ * Byte 3: Frame flags, bit 0: last frame indication
+ * Byte 4~5: Reserved
+ * Byte 6~7: Tail padding length
+ * software header - 8 bytes
+ * Byte 0: Rx/Tx sequence number
+ * Byte 1: 4 MSB Channel number, 4 LSB arbitrary flag
+ * Byte 2: Length of next data frame, reserved for Tx
+ * Byte 3: Data offset
+ * Byte 4: Flow control bits, reserved for Tx
+ * Byte 5: Maximum Sequence number allowed by firmware for Tx, N/A for Tx packet
+ * Byte 6~7: Reserved
+ */
+#define SDPCM_HWHDR_LEN 4
+#define SDPCM_HWEXT_LEN 8
+#define SDPCM_SWHDR_LEN 8
+#define SDPCM_HDRLEN (SDPCM_HWHDR_LEN + SDPCM_SWHDR_LEN)
+/* software header */
+#define SDPCM_SEQ_MASK 0x000000ff
+#define SDPCM_SEQ_WRAP 256
+#define SDPCM_CHANNEL_MASK 0x00000f00
+#define SDPCM_CHANNEL_SHIFT 8
+#define SDPCM_FLAGS_MASK 0x0000f000
+#define SDPCM_FLAGS_SHIFT 12
+#define SDPCM_CONTROL_CHANNEL 0 /* Control */
+#define SDPCM_EVENT_CHANNEL 1 /* Asyc Event Indication */
+#define SDPCM_DATA_CHANNEL 2 /* Data Xmit/Recv */
+#define SDPCM_GLOM_CHANNEL 3 /* Coalesced packets */
+#define SDPCM_BT_CHANNEL 4 /* For BT packets */
+#define SDPCM_OHD_CHANNEL 5 /* For packet to be consumed by OHD */
+#define SDPCM_TEST_CHANNEL 15 /* Test/debug packets */
+#define SDPCM_GLOMDESC(p) (((u8 *)p)[1] & 0x80)
+#define SDPCM_NEXTLEN_MASK 0x00ff0000
+#define SDPCM_NEXTLEN_SHIFT 16
+#define SDPCM_DOFFSET_MASK 0xff000000
+#define SDPCM_DOFFSET_SHIFT 24
+#define SDPCM_FCMASK_MASK 0x000000ff
+#define SDPCM_WINDOW_MASK 0x0000ff00
+#define SDPCM_WINDOW_SHIFT 8
+
+static inline u8 inff_sdio_getdatoffset(u8 *swheader)
+{
+ u32 hdrvalue;
+
+ hdrvalue = le32_to_cpu(*(__le32 *)swheader);
+ return (u8)((hdrvalue & SDPCM_DOFFSET_MASK) >> SDPCM_DOFFSET_SHIFT);
+}
+
+static inline bool inff_sdio_fromevntchan(u8 *swheader)
+{
+ u32 hdrvalue;
+ u8 ret;
+
+ hdrvalue = le32_to_cpu(*(__le32 *)swheader);
+ ret = (u8)((hdrvalue & SDPCM_CHANNEL_MASK) >> SDPCM_CHANNEL_SHIFT);
+
+ return (ret == SDPCM_EVENT_CHANNEL);
+}
+
+static int inff_sdio_hdparse(struct inff_sdio *bus, u8 *header,
+ struct inff_sdio_hdrinfo *rd,
+ enum inff_sdio_frmtype type)
+{
+ u16 len, checksum;
+ u8 rx_seq, fc, tx_seq_max;
+ u32 swheader;
+
+ /* hw header */
+ len = get_unaligned_le16(header);
+ checksum = get_unaligned_le16(header + sizeof(u16));
+ /* All zero means no more to read */
+ if (!(len | checksum)) {
+ bus->rxpending = false;
+ return -ENODATA;
+ }
+ if ((u16)(~(len ^ checksum))) {
+ inff_err("HW header checksum error\n");
+ bus->sdcnt.rx_badhdr++;
+ inff_sdio_rxfail(bus, false, false);
+ return -EIO;
+ }
+ if (len < SDPCM_HDRLEN) {
+ inff_err("HW header length error\n");
+ return -EPROTO;
+ }
+ if (type == INFF_SDIO_FT_SUPER &&
+ (roundup(len, bus->blocksize) != rd->len)) {
+ inff_err("HW superframe header length error\n");
+ return -EPROTO;
+ }
+ if (type == INFF_SDIO_FT_SUB && len > rd->len) {
+ inff_err("HW subframe header length error\n");
+ return -EPROTO;
+ }
+ rd->len = len;
+
+ /* software header */
+ header += SDPCM_HWHDR_LEN;
+ swheader = le32_to_cpu(*(__le32 *)header);
+ if (type == INFF_SDIO_FT_SUPER && SDPCM_GLOMDESC(header)) {
+ inff_err("Glom descriptor found in superframe head\n");
+ rd->len = 0;
+ return -EINVAL;
+ }
+ rx_seq = (u8)(swheader & SDPCM_SEQ_MASK);
+ rd->channel_and_flags = (swheader & SDPCM_CHANNEL_MASK) >> SDPCM_CHANNEL_SHIFT;
+ if (len > MAX_RX_DATASZ && rd->channel_and_flags != SDPCM_CONTROL_CHANNEL &&
+ type != INFF_SDIO_FT_SUPER) {
+ inff_err("HW header length too long\n");
+ bus->sdcnt.rx_toolong++;
+ inff_sdio_rxfail(bus, false, false);
+ rd->len = 0;
+ return -EPROTO;
+ }
+ if (type == INFF_SDIO_FT_SUPER && rd->channel_and_flags != SDPCM_GLOM_CHANNEL) {
+ inff_err("Wrong channel for superframe\n");
+ rd->len = 0;
+ return -EINVAL;
+ }
+ if (type == INFF_SDIO_FT_SUB && rd->channel_and_flags != SDPCM_DATA_CHANNEL &&
+ rd->channel_and_flags != SDPCM_EVENT_CHANNEL) {
+ inff_err("Wrong channel for subframe\n");
+ rd->len = 0;
+ return -EINVAL;
+ }
+ rd->dat_offset = inff_sdio_getdatoffset(header);
+ if (rd->dat_offset < SDPCM_HDRLEN || rd->dat_offset > rd->len) {
+ inff_err("seq %d: bad data offset\n", rx_seq);
+ bus->sdcnt.rx_badhdr++;
+ inff_sdio_rxfail(bus, false, false);
+ rd->len = 0;
+ return -ENXIO;
+ }
+ if (rd->seq_num != rx_seq) {
+ inff_dbg(SDIO, "seq %d, expected %d\n", rx_seq, rd->seq_num);
+ bus->sdcnt.rx_badseq++;
+ rd->seq_num = rx_seq;
+ }
+ /* no need to check the reset for subframe */
+ if (type == INFF_SDIO_FT_SUB)
+ return 0;
+ rd->len_nxtfrm = (swheader & SDPCM_NEXTLEN_MASK) >> SDPCM_NEXTLEN_SHIFT;
+ if (rd->len_nxtfrm << 4 > MAX_RX_DATASZ) {
+ /* only warm for NON glom packet */
+ if (rd->channel_and_flags != SDPCM_GLOM_CHANNEL)
+ inff_err("seq %d: next length error\n", rx_seq);
+ rd->len_nxtfrm = 0;
+ }
+ swheader = le32_to_cpu(*(__le32 *)(header + 4));
+ fc = swheader & SDPCM_FCMASK_MASK;
+ if (bus->flowcontrol != fc) {
+ if (~bus->flowcontrol & fc)
+ bus->sdcnt.fc_xoff++;
+ if (bus->flowcontrol & ~fc)
+ bus->sdcnt.fc_xon++;
+ bus->sdcnt.fc_rcvd++;
+ bus->flowcontrol = fc;
+ }
+ tx_seq_max = (swheader & SDPCM_WINDOW_MASK) >> SDPCM_WINDOW_SHIFT;
+ if ((u8)(tx_seq_max - bus->tx_seq) > 0x40) {
+ inff_err("seq %d: max tx seq number error\n", rx_seq);
+ tx_seq_max = bus->tx_seq + 2;
+ }
+ bus->tx_max = tx_seq_max;
+
+ return 0;
+}
+
+static inline void inff_sdio_update_hwhdr(u8 *header, u16 frm_length)
+{
+ *(__le16 *)header = cpu_to_le16(frm_length);
+ *(((__le16 *)header) + 1) = cpu_to_le16(~frm_length);
+}
+
+static void inff_sdio_hdpack(struct inff_sdio *bus, u8 *header,
+ struct inff_sdio_hdrinfo *hd_info)
+{
+ u32 hdrval;
+ u8 hdr_offset;
+
+ inff_sdio_update_hwhdr(header, hd_info->len);
+ hdr_offset = SDPCM_HWHDR_LEN;
+
+ if (bus->txglom) {
+ hdrval = (hd_info->len - hdr_offset) | (hd_info->lastfrm << 24);
+ *((__le32 *)(header + hdr_offset)) = cpu_to_le32(hdrval);
+ hdrval = (u16)hd_info->tail_pad << 16;
+ *(((__le32 *)(header + hdr_offset)) + 1) = cpu_to_le32(hdrval);
+ hdr_offset += SDPCM_HWEXT_LEN;
+ }
+
+ hdrval = hd_info->seq_num;
+ hdrval |= (hd_info->channel_and_flags << SDPCM_CHANNEL_SHIFT) &
+ SDPCM_CHANNEL_MASK;
+ hdrval |= ((hd_info->channel_and_flags & 0xf0) << 8) &
+ SDPCM_FLAGS_MASK;
+ hdrval |= (hd_info->dat_offset << SDPCM_DOFFSET_SHIFT) &
+ SDPCM_DOFFSET_MASK;
+ *((__le32 *)(header + hdr_offset)) = cpu_to_le32(hdrval);
+ *(((__le32 *)(header + hdr_offset)) + 1) = 0;
+}
+
+static u8 inff_sdio_rxglom(struct inff_sdio *bus, u8 rxseq)
+{
+ u16 dlen, totlen;
+ u8 *dptr, num = 0;
+ u16 sublen;
+ struct sk_buff *pfirst, *pnext;
+ int errcode;
+ u8 doff;
+ struct inff_sdio_hdrinfo rd_new;
+
+ /* If packets, issue read(s) and send up packet chain */
+ /* Return sequence numbers consumed? */
+
+ inff_dbg(SDIO, "start: glomd %p glom %p\n",
+ bus->glomd, skb_peek(&bus->glom));
+
+ /* If there's a descriptor, generate the packet chain */
+ if (bus->glomd) {
+ pfirst = NULL;
+ pnext = NULL;
+ /* it is a u32 len to u16 dlen, should have a sanity check here. */
+ if (bus->glomd->len <= 0xFFFF) {
+ dlen = (u16)(bus->glomd->len);
+ if (!dlen || (dlen & 1)) {
+ inff_err("bad glomd len(%d), ignore descriptor\n",
+ dlen);
+ dlen = 0;
+ }
+ } else {
+ inff_err("overflowed glomd len(%d), ignore descriptor\n",
+ bus->glomd->len);
+ dlen = 0;
+ }
+ dptr = bus->glomd->data;
+
+ for (totlen = num = 0; dlen; num++) {
+ /* Get (and move past) next length */
+ sublen = get_unaligned_le16(dptr);
+ dlen -= sizeof(u16);
+ dptr += sizeof(u16);
+ if (sublen < SDPCM_HDRLEN ||
+ (num == 0 && (sublen < (2 * SDPCM_HDRLEN)))) {
+ inff_err("descriptor len %d bad: %d\n",
+ num, sublen);
+ pnext = NULL;
+ break;
+ }
+ if (sublen % bus->sgentry_align) {
+ inff_err("sublen %d not multiple of %d\n",
+ sublen, bus->sgentry_align);
+ }
+ totlen += sublen;
+
+ /* For last frame, adjust read len so total
+ * is a block multiple
+ */
+ if (!dlen) {
+ sublen +=
+ (roundup(totlen, bus->blocksize) - totlen);
+ totlen = roundup(totlen, bus->blocksize);
+ }
+
+ /* Allocate/chain packet for next subframe */
+ pnext = inff_pkt_buf_get_skb(sublen + bus->sgentry_align);
+ if (!pnext) {
+ inff_err("inff_pkt_buf_get_skb failed, num %d len %d\n",
+ num, sublen);
+ break;
+ }
+ skb_queue_tail(&bus->glom, pnext);
+
+ /* Adhere to start alignment requirements */
+ pkt_align(pnext, sublen, bus->sgentry_align);
+ }
+
+ /* If all allocations succeeded, save packet chain
+ * in bus structure
+ */
+ if (pnext) {
+ inff_dbg(GLOM, "allocated %d-byte packet chain for %d subframes\n",
+ totlen, num);
+ if (INFF_MSGLVL_ON(GLOM) && bus->cur_read.len &&
+ totlen != bus->cur_read.len) {
+ inff_dbg(GLOM, "glomdesc mismatch: nextlen %d glomdesc %d rxseq %d\n",
+ bus->cur_read.len, totlen, rxseq);
+ }
+ pfirst = NULL;
+ pnext = NULL;
+ } else {
+ inff_sdio_free_glom(bus);
+ num = 0;
+ }
+
+ /* Done with descriptor packet */
+ inff_pkt_buf_free_skb(bus->glomd);
+ bus->glomd = NULL;
+ bus->cur_read.len = 0;
+ }
+
+ /* Ok -- either we just generated a packet chain,
+ * or had one from before
+ */
+ if (!skb_queue_empty(&bus->glom)) {
+ u32 len_glom = 0;
+
+ if (INFF_MSGLVL_ON(GLOM)) {
+ inff_dbg(GLOM, "try superframe read, packet chain:\n");
+ skb_queue_walk(&bus->glom, pnext) {
+ inff_dbg(GLOM, " %p: %p len 0x%04x (%d)\n",
+ pnext, (u8 *)(pnext->data),
+ pnext->len, pnext->len);
+ }
+ }
+
+ pfirst = skb_peek(&bus->glom);
+ len_glom = inff_sdio_glom_len(bus);
+ if (len_glom > 0xFFFF) {
+ inff_err("glom_len is %d bytes, overflowed\n",
+ len_glom);
+ goto frame_error_handle;
+ } else {
+ dlen = (u16)len_glom;
+ }
+
+ /* Do an SDIO read for the superframe. Configurable iovar to
+ * read directly into the chained packet, or allocate a large
+ * packet and copy into the chain.
+ */
+ sdio_claim_host(bus->sdiodev->func1);
+ errcode = inff_sdiod_recv_chain(bus->sdiodev,
+ &bus->glom, dlen);
+ sdio_release_host(bus->sdiodev->func1);
+ bus->sdcnt.f2rxdata++;
+
+ /* On failure, kill the superframe */
+ if (errcode < 0) {
+ inff_err("glom read of %d bytes failed: %d\n",
+ dlen, errcode);
+ goto frame_error_handle;
+ }
+
+ inff_dbg_hex_dump(INFF_MSGLVL_ON(GLOM),
+ pfirst->data, min_t(int, pfirst->len, 48),
+ "SUPERFRAME:\n");
+
+ rd_new.seq_num = rxseq;
+ rd_new.len = dlen;
+ sdio_claim_host(bus->sdiodev->func1);
+ errcode = inff_sdio_hdparse(bus, pfirst->data, &rd_new,
+ INFF_SDIO_FT_SUPER);
+ sdio_release_host(bus->sdiodev->func1);
+ bus->cur_read.len = rd_new.len_nxtfrm << 4;
+
+ /* Remove superframe header, remember offset */
+ skb_pull(pfirst, rd_new.dat_offset);
+ num = 0;
+
+ /* Validate all the subframe headers */
+ skb_queue_walk(&bus->glom, pnext) {
+ /* leave when invalid subframe is found */
+ if (errcode)
+ break;
+
+ rd_new.len = pnext->len;
+ rd_new.seq_num = rxseq++;
+ sdio_claim_host(bus->sdiodev->func1);
+ errcode = inff_sdio_hdparse(bus, pnext->data, &rd_new,
+ INFF_SDIO_FT_SUB);
+ sdio_release_host(bus->sdiodev->func1);
+ inff_dbg_hex_dump(INFF_MSGLVL_ON(GLOM),
+ pnext->data, 32, "subframe:\n");
+
+ num++;
+ }
+
+ /* Terminate frame on error */
+ if (errcode)
+ goto frame_error_handle;
+
+ /* Basic SD framing looks ok - process each packet (header) */
+
+ skb_queue_walk_safe(&bus->glom, pfirst, pnext) {
+ dptr = (u8 *)(pfirst->data);
+ sublen = get_unaligned_le16(dptr);
+ doff = inff_sdio_getdatoffset(&dptr[SDPCM_HWHDR_LEN]);
+
+ inff_dbg_hex_dump(INFF_MSGLVL_ON(BYTES) && INFF_MSGLVL_ON(DATA),
+ dptr, pfirst->len,
+ "Rx Subframe Data:\n");
+
+ __skb_trim(pfirst, sublen);
+ skb_pull(pfirst, doff);
+
+ if (pfirst->len == 0) {
+ skb_unlink(pfirst, &bus->glom);
+ inff_pkt_buf_free_skb(pfirst);
+ continue;
+ }
+
+ inff_dbg_hex_dump(INFF_MSGLVL_ON(GLOM),
+ pfirst->data,
+ min_t(int, pfirst->len, 32),
+ "subframe %d to stack, %p (%p/%d) nxt/lnk %p/%p\n",
+ bus->glom.qlen, pfirst, pfirst->data,
+ pfirst->len, pfirst->next,
+ pfirst->prev);
+ skb_unlink(pfirst, &bus->glom);
+ if (inff_sdio_fromevntchan(&dptr[SDPCM_HWHDR_LEN])) {
+ inff_bcdc_process_event(bus->sdiodev->dev, pfirst);
+ } else {
+ inff_bcdc_rx(bus->sdiodev->dev, pfirst,
+ false, false);
+ }
+
+ bus->sdcnt.rxglompkts++;
+ }
+
+ bus->sdcnt.rxglomframes++;
+ }
+
+ return num;
+
+frame_error_handle:
+ sdio_claim_host(bus->sdiodev->func1);
+ inff_sdio_rxfail(bus, true, false);
+ bus->sdcnt.rxglomfail++;
+ inff_sdio_free_glom(bus);
+ sdio_release_host(bus->sdiodev->func1);
+ bus->cur_read.len = 0;
+
+ return 0;
+}
+
+static int inff_sdio_dcmd_resp_wait(struct inff_sdio *bus, uint *condition,
+ bool *pending)
+{
+ DECLARE_WAITQUEUE(wait, current);
+ int timeout = DCMD_RESP_TIMEOUT;
+
+ /* Wait until control frame is available */
+ add_wait_queue(&bus->dcmd_resp_wait, &wait);
+ set_current_state(TASK_INTERRUPTIBLE);
+
+ while (!(*condition) && (!signal_pending(current) && timeout))
+ timeout = schedule_timeout(timeout);
+
+ if (signal_pending(current))
+ *pending = true;
+
+ set_current_state(TASK_RUNNING);
+ remove_wait_queue(&bus->dcmd_resp_wait, &wait);
+
+ return timeout;
+}
+
+static int inff_sdio_dcmd_resp_wake(struct inff_sdio *bus)
+{
+ wake_up_interruptible(&bus->dcmd_resp_wait);
+
+ return 0;
+}
+
+static void
+inff_cp_sdio_rx_proc_data(struct inff_sdio_dev *sdiodev,
+ struct sk_buff *pkt, u8 ifidx)
+{
+ struct inff_pub *drvr = sdiodev->bus_if->drvr;
+ struct inff_if *ifp;
+
+ ifp = drvr->iflist[ifidx];
+
+ if (!ifp) {
+ inff_err("ifp is NULL\n");
+ return;
+ }
+
+ pkt->protocol = eth_type_trans(pkt, ifp->ndev);
+
+ netif_rx(pkt);
+}
+
+static void
+inff_cp_sdio_read_control(struct inff_sdio *bus, struct sk_buff *pkt)
+{
+ u8 *buf = NULL;
+
+ buf = vzalloc(pkt->len);
+ if (!buf)
+ goto done;
+
+ memcpy(buf, pkt->data, pkt->len);
+
+ inff_dbg_hex_dump(INFF_MSGLVL_ON(SDIO), pkt->data,
+ pkt->len, "Rx Control:\n");
+
+ spin_lock_bh(&bus->rxctl_lock);
+ if (bus->rxctl) {
+ inff_err("last control frame is being processed.\n");
+ spin_unlock_bh(&bus->rxctl_lock);
+ goto done;
+ }
+ bus->rxctl = buf;
+ bus->rxlen = pkt->len;
+ spin_unlock_bh(&bus->rxctl_lock);
+ buf = NULL;
+
+done:
+ /* Awake any waiters */
+ inff_sdio_dcmd_resp_wake(bus);
+ vfree(buf);
+}
+
+static bool
+inff_cp_sdio_rx_proc(struct inff_sdio *bus, struct sk_buff *pkt, u16 len)
+{
+ struct inff_sdio_dev *sdiodev = bus->sdiodev;
+ struct inff_pub *drvr = sdiodev->bus_if->drvr;
+ struct inff_sdio_hdrinfo *sw_hdr = (struct inff_sdio_hdrinfo *)pkt->data;
+ bool free_skb = true;
+ u8 channel, ifidx;
+
+ if (sw_hdr->seq_num != bus->rx_seq) {
+ inff_err("seq %d, expected %d\n", sw_hdr->seq_num, bus->rx_seq);
+ bus->rx_seq = sw_hdr->seq_num + 1;
+ } else {
+ bus->rx_seq++;
+ }
+
+ skb_pull(pkt, SDPCM_SWHDR_LEN);
+
+ inff_dbg_hex_dump(INFF_MSGLVL_ON(BYTES) && INFF_MSGLVL_ON(DATA),
+ pkt->data, sw_hdr->len, "Rx Data:\n");
+
+ inff_dbg(SDIO, "seq_num: %d, channel_and_flags: %d\n",
+ sw_hdr->seq_num, sw_hdr->channel_and_flags);
+
+ channel = (sw_hdr->channel_and_flags & 0x0f);
+ ifidx = (sw_hdr->channel_and_flags & 0xf0) >> 4;
+
+ switch (channel) {
+ case SDPCM_CONTROL_CHANNEL:
+ inff_cp_sdio_read_control(bus, pkt);
+ break;
+ case SDPCM_EVENT_CHANNEL:
+ inff_cpevt_process_skb(drvr, pkt);
+ break;
+ case SDPCM_DATA_CHANNEL:
+ inff_cp_sdio_rx_proc_data(sdiodev, pkt, ifidx);
+ free_skb = false;
+ break;
+ default:
+ inff_err("unsupported SDPCM channel_and_flags: %d\n",
+ sw_hdr->channel_and_flags);
+ break;
+ }
+
+ return free_skb;
+}
+
+static void
+inff_sdio_read_control(struct inff_sdio *bus, u8 *hdr, uint len, uint doff)
+{
+ uint rdlen, pad;
+ u8 *buf = NULL, *rbuf;
+ int sdret;
+
+ if (bus->rxblen)
+ buf = vzalloc(bus->rxblen);
+ if (!buf)
+ goto done;
+
+ rbuf = bus->rxbuf;
+ pad = ((unsigned long)rbuf % bus->head_align);
+ if (pad)
+ rbuf += (bus->head_align - pad);
+
+ /* Copy the already-read portion over */
+ memcpy(buf, hdr, INFF_FIRSTREAD);
+ if (len <= INFF_FIRSTREAD)
+ goto gotpkt;
+
+ /* Raise rdlen to next SDIO block to avoid tail command */
+ rdlen = len - INFF_FIRSTREAD;
+ if (bus->roundup && bus->blocksize && rdlen > bus->blocksize) {
+ pad = bus->blocksize - (rdlen % bus->blocksize);
+ if (pad <= bus->roundup && pad < bus->blocksize &&
+ ((len + pad) < bus->sdiodev->bus_if->maxctl))
+ rdlen += pad;
+ } else if (rdlen % bus->head_align) {
+ rdlen += bus->head_align - (rdlen % bus->head_align);
+ }
+
+ /* Drop if the read is too big or it exceeds our maximum */
+ if ((rdlen + INFF_FIRSTREAD) > bus->sdiodev->bus_if->maxctl) {
+ inff_err("%d-byte control read exceeds %d-byte buffer\n",
+ rdlen, bus->sdiodev->bus_if->maxctl);
+ inff_sdio_rxfail(bus, false, false);
+ goto done;
+ }
+
+ if ((len - doff) > bus->sdiodev->bus_if->maxctl) {
+ inff_err("%d-byte ctl frame (%d-byte ctl data) exceeds %d-byte limit\n",
+ len, len - doff, bus->sdiodev->bus_if->maxctl);
+ bus->sdcnt.rx_toolong++;
+ inff_sdio_rxfail(bus, false, false);
+ goto done;
+ }
+
+ /* Read remain of frame body */
+ sdret = inff_sdiod_recv_buf(bus->sdiodev, SDIO_FUNC_2, rbuf, rdlen);
+ bus->sdcnt.f2rxdata++;
+
+ /* Control frame failures need retransmission */
+ if (sdret < 0) {
+ inff_err("read %d control bytes failed: %d\n",
+ rdlen, sdret);
+ bus->sdcnt.rxc_errors++;
+ inff_sdio_rxfail(bus, true, true);
+ goto done;
+ } else {
+ memcpy(buf + INFF_FIRSTREAD, rbuf, rdlen);
+ }
+
+gotpkt:
+
+ inff_dbg_hex_dump(INFF_MSGLVL_ON(BYTES) && INFF_MSGLVL_ON(CTL),
+ buf, len, "RxCtrl:\n");
+
+ /* Point to valid data and indicate its length */
+ spin_lock_bh(&bus->rxctl_lock);
+ if (bus->rxctl) {
+ inff_err("last control frame is being processed.\n");
+ spin_unlock_bh(&bus->rxctl_lock);
+ vfree(buf);
+ goto done;
+ }
+ bus->rxctl = buf + doff;
+ bus->rxctl_orig = buf;
+ bus->rxlen = len - doff;
+ spin_unlock_bh(&bus->rxctl_lock);
+
+done:
+ /* Awake any waiters */
+ inff_sdio_dcmd_resp_wake(bus);
+}
+
+static int
+inff_cp_sdio_hdparse(struct inff_sdio *bus, u8 *header,
+ struct inff_sdio_hdrinfo *rd)
+{
+ u16 len, checksum;
+
+ /* hw header */
+ len = get_unaligned_le16(header);
+ checksum = get_unaligned_le16(header + sizeof(u16));
+
+ /* All zero means no more to read */
+ if (!(len | checksum)) {
+ inff_dbg(SDIO, "no more data to recv");
+ return -ENODATA;
+ }
+
+ inff_dbg(SDIO, "len: 0x%x, checksum: 0x%x\n", len, checksum);
+
+ if ((u16)(~(len ^ checksum))) {
+ inff_err("HW header checksum error\n");
+ return -EIO;
+ }
+
+ if (len < SDPCM_HDRLEN) {
+ inff_err("HW header length error\n");
+ return -EPROTO;
+ }
+
+ len -= SDPCM_HWHDR_LEN;
+
+ rd->len = len;
+
+ return 0;
+}
+
+static int
+inff_cp_sdio_readframes(struct inff_sdio *bus)
+{
+ struct inff_sdio_dev *sdiodev = bus->sdiodev;
+ struct inff_sdio_hdrinfo rd;
+ struct sk_buff *pkt;
+ int err;
+
+ sdio_claim_host(sdiodev->func1);
+
+ do {
+ /* read HW header */
+ err = inff_sdiod_recv_buf(sdiodev, SDIO_FUNC_2,
+ bus->rxhdr, SDPCM_HWHDR_LEN);
+ if (err) {
+ inff_err("RXHEADER FAILED\n");
+ err = -ENODATA;
+ break;
+ }
+
+ inff_dbg_hex_dump(INFF_MSGLVL_ON(BYTES) || INFF_MSGLVL_ON(HDRS),
+ bus->rxhdr, SDPCM_HWHDR_LEN,
+ "RxHdr:\n");
+
+ if (inff_cp_sdio_hdparse(bus, bus->rxhdr, &rd))
+ break;
+
+ /* read payload */
+ pkt = inff_pkt_buf_get_skb(rd.len);
+ if (!pkt) {
+ inff_err("infu_pkt_buf_get_skb %d failed\n", rd.len);
+ err = -ENODATA;
+ break;
+ }
+
+ err = inff_sdiod_recv_pkt(sdiodev, SDIO_FUNC_2, pkt);
+ if (err) {
+ inff_err("recv packet error %d\n", err);
+ break;
+ }
+
+ if (inff_cp_sdio_rx_proc(bus, pkt, rd.len))
+ inff_pkt_buf_free_skb(pkt);
+
+ } while (true);
+
+ sdio_release_host(sdiodev->func1);
+
+ return err;
+}
+
+/* Pad read to blocksize for efficiency */
+static void inff_sdio_pad(struct inff_sdio *bus, u16 *pad, u16 *rdlen)
+{
+ if (bus->roundup && bus->blocksize && *rdlen > bus->blocksize) {
+ *pad = bus->blocksize - (*rdlen % bus->blocksize);
+ if (*pad <= bus->roundup && *pad < bus->blocksize &&
+ *rdlen + *pad + INFF_FIRSTREAD < MAX_RX_DATASZ)
+ *rdlen += *pad;
+ } else if (*rdlen % bus->head_align) {
+ *rdlen += bus->head_align - (*rdlen % bus->head_align);
+ }
+}
+
+static inline void inff_sdio_acw74xx_resume_txdma(struct inff_sdio *bus)
+{
+ u32 val = SD_DMA_RX_FIFO_DBG_CNT_MSK;
+ u32 loop = 0;
+ u32 gpio_addr = (bus->sdio_core->base + SD_DMA_REG_GPIOOUTREG_OFFSET);
+ u32 xmtctl_addr = (bus->sdio_core->base + SD_DMA_REG_XMTCONTROL_OFFSET);
+
+ if (!bus->acw74xx_f2_txdma_suspended)
+ return;
+
+ /* Read back the SDIO GpioOutput Register (Offset 0x338) */
+ while (val & SD_DMA_RX_FIFO_DBG_CNT_MSK) {
+ loop++;
+ val = inff_sdiod_readl(bus->sdiodev, gpio_addr, NULL);
+ };
+ val = SD_DMA_REG_XMTCTRL_SUSP_DIS;
+ inff_sdiod_writel(bus->sdiodev, xmtctl_addr, val, NULL);
+ bus->acw74xx_f2_txdma_suspended = false;
+}
+
+static inline void inff_sdio_acw74xx_suspend_txdma(struct inff_sdio *bus)
+{
+ u32 xmtctl = SD_DMA_REG_XMTCTRL_SUSP_EN;
+ u32 addr = (bus->sdio_core->base + SD_DMA_REG_XMTCONTROL_OFFSET);
+
+ if (bus->acw74xx_f2_txdma_suspended)
+ return;
+
+ inff_sdiod_writel(bus->sdiodev, addr, xmtctl, NULL);
+ bus->acw74xx_f2_txdma_suspended = true;
+}
+
+static uint inff_sdio_readframes(struct inff_sdio *bus, uint maxframes)
+{
+ struct sk_buff *pkt; /* Packet for event or data frames */
+ u16 pad; /* Number of pad bytes to read */
+ uint rxleft = 0; /* Remaining number of frames allowed */
+ int ret; /* Return code from calls */
+ uint rxcount = 0; /* Total frames read */
+ struct inff_sdio_hdrinfo *rd = &bus->cur_read, rd_new;
+ u8 head_read = 0;
+
+ if (bus->acw74xx_f2_txdma_suspended && bus->ci->chip == INF_CC_ACW74XX_CHIP_ID) {
+ sdio_claim_host(bus->sdiodev->func1);
+ inff_sdio_acw74xx_resume_txdma(bus);
+ sdio_release_host(bus->sdiodev->func1);
+ }
+
+ /* Not finished unless we encounter no more frames indication */
+ bus->rxpending = true;
+
+ for (rd->seq_num = bus->rx_seq, rxleft = maxframes;
+ !bus->rxskip && rxleft && bus->sdiodev->state == INFF_SDIOD_DATA;
+ rd->seq_num++, rxleft--) {
+ /* Handle glomming separately */
+ if (bus->glomd || !skb_queue_empty(&bus->glom)) {
+ u8 cnt;
+
+ inff_dbg(GLOM, "calling rxglom: glomd %p, glom %p\n",
+ bus->glomd, skb_peek(&bus->glom));
+ cnt = inff_sdio_rxglom(bus, rd->seq_num);
+ inff_dbg(GLOM, "rxglom returned %d\n", cnt);
+ rd->seq_num += cnt - 1;
+ rxleft = (rxleft > cnt) ? (rxleft - cnt) : 1;
+ continue;
+ }
+
+ sdio_claim_host(bus->sdiodev->func1);
+
+ rd->len_left = rd->len;
+ /* read header first for unknown frame length */
+ if (!rd->len) {
+ ret = inff_sdiod_recv_buf(bus->sdiodev, SDIO_FUNC_2,
+ bus->rxhdr, INFF_FIRSTREAD);
+ bus->sdcnt.f2rxhdrs++;
+ if (ret < 0) {
+ inff_err("RXHEADER FAILED: %d\n",
+ ret);
+ bus->sdcnt.rx_hdrfail++;
+ inff_sdio_rxfail(bus, true, true);
+ sdio_release_host(bus->sdiodev->func1);
+ continue;
+ }
+
+ inff_dbg_hex_dump(INFF_MSGLVL_ON(BYTES) || INFF_MSGLVL_ON(HDRS),
+ bus->rxhdr, SDPCM_HDRLEN,
+ "RxHdr:\n");
+
+ if (inff_sdio_hdparse(bus, bus->rxhdr, rd,
+ INFF_SDIO_FT_NORMAL)) {
+ sdio_release_host(bus->sdiodev->func1);
+ if (!bus->rxpending)
+ break;
+
+ continue;
+ }
+
+ if (rd->channel_and_flags == SDPCM_CONTROL_CHANNEL) {
+ inff_sdio_read_control(bus, bus->rxhdr,
+ rd->len,
+ rd->dat_offset);
+ /* prepare the descriptor for the next read */
+ rd->len = rd->len_nxtfrm << 4;
+ rd->len_nxtfrm = 0;
+ /* treat all packet as event if we don't know */
+ rd->channel_and_flags = SDPCM_EVENT_CHANNEL;
+ sdio_release_host(bus->sdiodev->func1);
+ continue;
+ }
+
+ if (bus->ci->chip == INF_CC_ACW74XX_CHIP_ID) {
+ if (rd->channel_and_flags == SDPCM_OHD_CHANNEL) {
+ inff_err(" SDPCM_OHD_CHANNEL event received");
+ inff_sdio_read_control(bus, bus->rxhdr,
+ rd->len,
+ rd->dat_offset);
+ /* prepare the descriptor for the next read */
+ rd->len = rd->len_nxtfrm << 4;
+ rd->len_nxtfrm = 0;
+ /* treat all packet as event if we don't know */
+ rd->channel_and_flags = SDPCM_EVENT_CHANNEL;
+ sdio_release_host(bus->sdiodev->func1);
+ continue;
+ }
+ }
+
+ rd->len_left = rd->len > INFF_FIRSTREAD ?
+ rd->len - INFF_FIRSTREAD : 0;
+ head_read = INFF_FIRSTREAD;
+ }
+
+ inff_sdio_pad(bus, &pad, &rd->len_left);
+
+ pkt = inff_pkt_buf_get_skb(rd->len_left + head_read +
+ bus->head_align);
+ if (!pkt) {
+ /* Give up on data, request rtx of events */
+ inff_err("inff_pkt_buf_get_skb failed\n");
+ inff_sdio_rxfail(bus, false,
+ RETRYCHAN(rd->channel_and_flags));
+ sdio_release_host(bus->sdiodev->func1);
+ continue;
+ }
+ skb_pull(pkt, head_read);
+ pkt_align(pkt, rd->len_left, bus->head_align);
+
+ ret = inff_sdiod_recv_pkt(bus->sdiodev, SDIO_FUNC_2, pkt);
+ bus->sdcnt.f2rxdata++;
+ sdio_release_host(bus->sdiodev->func1);
+
+ if (ret < 0) {
+ inff_err("read %d bytes from channel %d failed: %d\n",
+ rd->len, rd->channel_and_flags, ret);
+ inff_pkt_buf_free_skb(pkt);
+ sdio_claim_host(bus->sdiodev->func1);
+ inff_sdio_rxfail(bus, true,
+ RETRYCHAN(rd->channel_and_flags));
+ sdio_release_host(bus->sdiodev->func1);
+ continue;
+ }
+
+ if (head_read) {
+ skb_push(pkt, head_read);
+ memcpy(pkt->data, bus->rxhdr, head_read);
+ head_read = 0;
+ } else {
+ memcpy(bus->rxhdr, pkt->data, SDPCM_HDRLEN);
+ rd_new.seq_num = rd->seq_num;
+ sdio_claim_host(bus->sdiodev->func1);
+ if (inff_sdio_hdparse(bus, bus->rxhdr, &rd_new,
+ INFF_SDIO_FT_NORMAL)) {
+ rd->len = 0;
+ inff_sdio_rxfail(bus, true, true);
+ sdio_release_host(bus->sdiodev->func1);
+ inff_pkt_buf_free_skb(pkt);
+ continue;
+ }
+ bus->sdcnt.rx_readahead_cnt++;
+ if (rd->len != roundup(rd_new.len, 16)) {
+ inff_err("frame length mismatch:read %d, should be %d\n",
+ rd->len,
+ roundup(rd_new.len, 16) >> 4);
+ rd->len = 0;
+ inff_sdio_rxfail(bus, true, true);
+ sdio_release_host(bus->sdiodev->func1);
+ inff_pkt_buf_free_skb(pkt);
+ continue;
+ }
+ sdio_release_host(bus->sdiodev->func1);
+ rd->len_nxtfrm = rd_new.len_nxtfrm;
+ rd->channel_and_flags = rd_new.channel_and_flags;
+ rd->dat_offset = rd_new.dat_offset;
+
+ inff_dbg_hex_dump(!(INFF_MSGLVL_ON(BYTES) &&
+ INFF_MSGLVL_ON(DATA)) &&
+ INFF_MSGLVL_ON(HDRS),
+ bus->rxhdr, SDPCM_HDRLEN,
+ "RxHdr:\n");
+
+ if (rd_new.channel_and_flags == SDPCM_CONTROL_CHANNEL) {
+ inff_err("readahead on control packet %d?\n",
+ rd_new.seq_num);
+ /* Force retry w/normal header read */
+ rd->len = 0;
+ sdio_claim_host(bus->sdiodev->func1);
+ inff_sdio_rxfail(bus, false, true);
+ sdio_release_host(bus->sdiodev->func1);
+ inff_pkt_buf_free_skb(pkt);
+ continue;
+ }
+ }
+
+ inff_dbg_hex_dump(INFF_MSGLVL_ON(BYTES) && INFF_MSGLVL_ON(DATA),
+ pkt->data, rd->len, "Rx Data:\n");
+
+ /* Save superframe descriptor and allocate packet frame */
+ if (rd->channel_and_flags == SDPCM_GLOM_CHANNEL) {
+ if (SDPCM_GLOMDESC(&bus->rxhdr[SDPCM_HWHDR_LEN])) {
+ inff_dbg(GLOM, "glom descriptor, %d bytes:\n",
+ rd->len);
+ inff_dbg_hex_dump(INFF_MSGLVL_ON(GLOM),
+ pkt->data, rd->len,
+ "Glom Data:\n");
+ __skb_trim(pkt, rd->len);
+ skb_pull(pkt, SDPCM_HDRLEN);
+ bus->glomd = pkt;
+ } else {
+ inff_err("%s: glom superframe w/o descriptor!\n", __func__);
+ sdio_claim_host(bus->sdiodev->func1);
+ inff_sdio_rxfail(bus, false, false);
+ sdio_release_host(bus->sdiodev->func1);
+ }
+ /* prepare the descriptor for the next read */
+ rd->len = rd->len_nxtfrm << 4;
+ rd->len_nxtfrm = 0;
+ /* treat all packet as event if we don't know */
+ rd->channel_and_flags = SDPCM_EVENT_CHANNEL;
+ continue;
+ }
+
+ /* Fill in packet len and prio, deliver upward */
+ __skb_trim(pkt, rd->len);
+ skb_pull(pkt, rd->dat_offset);
+
+ if (pkt->len == 0) {
+ inff_pkt_buf_free_skb(pkt);
+ } else if (rd->channel_and_flags == SDPCM_EVENT_CHANNEL) {
+ inff_bcdc_process_event(bus->sdiodev->dev, pkt);
+ } else {
+ inff_bcdc_rx(bus->sdiodev->dev, pkt,
+ false, false);
+ }
+
+ /* prepare the descriptor for the next read */
+ rd->len = rd->len_nxtfrm << 4;
+ rd->len_nxtfrm = 0;
+ /* treat all packet as event if we don't know */
+ rd->channel_and_flags = SDPCM_EVENT_CHANNEL;
+ }
+
+ rxcount = maxframes - rxleft;
+ /* Message if we hit the limit */
+ if (!rxleft)
+ inff_dbg(DATA, "hit rx limit of %d frames\n", maxframes);
+ else
+ inff_dbg(DATA, "processed %d frames\n", rxcount);
+ /* Back off rxseq if awaiting rtx, update rx_seq */
+ if (bus->rxskip)
+ rd->seq_num--;
+ bus->rx_seq = rd->seq_num;
+
+ return rxcount;
+}
+
+static void
+inff_sdio_wait_event_wakeup(struct inff_sdio *bus)
+{
+ wake_up(&bus->ctrl_wait);
+}
+
+static int inff_sdio_txpkt_hdalign(struct inff_sdio *bus, struct sk_buff *pkt)
+{
+ struct inff_bus_stats *stats;
+ u16 head_pad;
+ u8 *dat_buf;
+
+ dat_buf = (u8 *)(pkt->data);
+
+ /* Check head padding */
+ head_pad = ((unsigned long)dat_buf % bus->head_align);
+ if (head_pad) {
+ if (skb_headroom(pkt) < head_pad) {
+ stats = &bus->sdiodev->bus_if->stats;
+ atomic_inc(&stats->pktcowed);
+ if (skb_cow_head(pkt, head_pad)) {
+ atomic_inc(&stats->pktcow_failed);
+ return -ENOMEM;
+ }
+ head_pad = 0;
+ }
+ skb_push(pkt, head_pad);
+ dat_buf = (u8 *)(pkt->data);
+ }
+ memset(dat_buf, 0, head_pad + bus->tx_hdrlen);
+ return head_pad;
+}
+
+/*
+ * struct inff_skbuff_cb reserves first two bytes in sk_buff::cb for
+ * bus layer usage.
+ */
+/* flag marking a dummy skb added for DMA alignment requirement */
+#define ALIGN_SKB_FLAG 0x8000
+/* bit mask of data length chopped from the previous packet */
+#define ALIGN_SKB_CHOP_LEN_MASK 0x7fff
+
+static int inff_sdio_txpkt_prep_sg(struct inff_sdio *bus,
+ struct sk_buff_head *pktq,
+ struct sk_buff *pkt, u16 total_len)
+{
+ struct inff_sdio_dev *sdiodev;
+ struct sk_buff *pkt_pad;
+ u16 tail_pad, tail_chop, chain_pad;
+ unsigned int blksize;
+ bool lastfrm;
+ int ntail, ret;
+
+ sdiodev = bus->sdiodev;
+ blksize = sdiodev->func2->cur_blksize;
+ /* sg entry alignment should be a divisor of block size */
+ WARN_ON(blksize % bus->sgentry_align);
+
+ /* Check tail padding */
+ lastfrm = skb_queue_is_last(pktq, pkt);
+ tail_pad = 0;
+ tail_chop = pkt->len % bus->sgentry_align;
+ if (tail_chop)
+ tail_pad = bus->sgentry_align - tail_chop;
+ chain_pad = (total_len + tail_pad) % blksize;
+ if (lastfrm && chain_pad)
+ tail_pad += blksize - chain_pad;
+ if (skb_tailroom(pkt) < tail_pad && pkt->len > blksize) {
+ pkt_pad = inff_pkt_buf_get_skb(tail_pad + tail_chop +
+ bus->head_align);
+ if (!pkt_pad)
+ return -ENOMEM;
+ ret = inff_sdio_txpkt_hdalign(bus, pkt_pad);
+ if (unlikely(ret < 0)) {
+ kfree_skb(pkt_pad);
+ return ret;
+ }
+ memcpy(pkt_pad->data,
+ pkt->data + pkt->len - tail_chop,
+ tail_chop);
+ *(u16 *)(pkt_pad->cb) = ALIGN_SKB_FLAG + tail_chop;
+ skb_trim(pkt, pkt->len - tail_chop);
+ skb_trim(pkt_pad, tail_pad + tail_chop);
+ __skb_queue_after(pktq, pkt, pkt_pad);
+ } else {
+ ntail = pkt->data_len + tail_pad -
+ (pkt->end - pkt->tail);
+ if (skb_cloned(pkt) || ntail > 0)
+ if (pskb_expand_head(pkt, 0, ntail, GFP_ATOMIC))
+ return -ENOMEM;
+ if (skb_linearize(pkt))
+ return -ENOMEM;
+ __skb_put(pkt, tail_pad);
+ }
+
+ return tail_pad;
+}
+
+/**
+ * inff_sdio_txpkt_prep - packet preparation for transmit
+ * @bus: inff_sdio structure pointer
+ * @pktq: packet list pointer
+ * @chan: virtual channel to transmit the packet
+ *
+ * Processes to be applied to the packet
+ * - Align data buffer pointer
+ * - Align data buffer length
+ * - Prepare header
+ * Return: negative value if there is error
+ */
+static int
+inff_sdio_txpkt_prep(struct inff_sdio *bus,
+ struct sk_buff_head *pktq, uint chan)
+{
+ u16 head_pad, total_len, tail_pad = 0, payload_len;
+ struct sk_buff *pkt_next;
+ u8 txseq;
+ int ret;
+ struct inff_sdio_hdrinfo hd_info = {0};
+
+ txseq = bus->tx_seq;
+ total_len = 0;
+ skb_queue_walk(pktq, pkt_next) {
+ /* alignment packet inserted in previous
+ * loop cycle can be skipped as it is
+ * already properly aligned and does not
+ * need an sdpcm header.
+ */
+ if (*(u16 *)pkt_next->cb & ALIGN_SKB_FLAG)
+ continue;
+
+ /* align packet data pointer */
+ ret = inff_sdio_txpkt_hdalign(bus, pkt_next);
+ if (ret < 0)
+ return ret;
+ head_pad = (u16)ret;
+ if (head_pad)
+ memset(pkt_next->data + bus->tx_hdrlen, 0, head_pad);
+
+ total_len += pkt_next->len;
+
+ if (bus->ci->chip == INF_CC_ACW74XX_CHIP_ID) {
+ tail_pad = 0;
+ if (pkt_next->len % bus->head_align)
+ tail_pad = bus->head_align - (pkt_next->len % bus->head_align);
+
+ payload_len = pkt_next->len;
+ pkt_next->len += tail_pad;
+ hd_info.len = (payload_len + tail_pad);
+ } else {
+ hd_info.len = pkt_next->len;
+ }
+
+ hd_info.lastfrm = skb_queue_is_last(pktq, pkt_next);
+ if (bus->txglom && pktq->qlen > 1) {
+ ret = inff_sdio_txpkt_prep_sg(bus, pktq,
+ pkt_next, total_len);
+ if (ret < 0)
+ return ret;
+ hd_info.tail_pad = (u16)ret;
+ total_len += (u16)ret;
+ }
+
+ hd_info.channel_and_flags = ((chan & 0x0f));
+ hd_info.dat_offset = head_pad + bus->tx_hdrlen;
+ hd_info.seq_num = txseq++;
+
+ /* Now fill the header */
+ inff_sdio_hdpack(bus, pkt_next->data, &hd_info);
+
+ if (bus->ci->chip == INF_CC_ACW74XX_CHIP_ID) {
+ /* update reserve bytes (B10 and B11) with actual payload length */
+ *((__le16 *)(pkt_next->data + 10)) = cpu_to_le16(payload_len);
+ }
+
+ if (INFF_MSGLVL_ON(BYTES) &&
+ ((INFF_MSGLVL_ON(CTL) && chan == SDPCM_CONTROL_CHANNEL) ||
+ (INFF_MSGLVL_ON(DATA) && chan != SDPCM_CONTROL_CHANNEL)))
+ inff_dbg_hex_dump(INFF_MSGLVL_ON(SDIO), pkt_next->data,
+ hd_info.len, "Tx Frame:\n");
+ else if (INFF_MSGLVL_ON(HDRS))
+ inff_dbg_hex_dump(INFF_MSGLVL_ON(SDIO), pkt_next->data,
+ head_pad + bus->tx_hdrlen,
+ "Tx Header:\n");
+ }
+ /* Hardware length tag of the first packet should be total
+ * length of the chain (including padding)
+ */
+ if (bus->txglom)
+ inff_sdio_update_hwhdr(__skb_peek(pktq)->data, total_len);
+ return 0;
+}
+
+/**
+ * inff_sdio_cp_txpkt_prep - packet preparation for transmit
+ * @bus: inff_sdio structure pointer
+ * @pktq: packet list pointer
+ * @chan: virtual channel to transmit the packet
+ *
+ * Processes to be applied to the packet
+ * - Align data buffer pointer
+ * - Align data buffer length
+ * - Prepare header
+ * Return: negative value if there is error
+ */
+static int
+inff_sdio_cp_txpkt_prep(struct inff_sdio *bus, struct inff_if *ifp,
+ struct sk_buff_head *pktq, uint chan)
+{
+ u16 head_pad, total_len;
+ struct sk_buff *pkt_next;
+ u8 txseq = 0;
+ int ret;
+ struct inff_sdio_hdrinfo hd_info = {0};
+
+ txseq = bus->tx_seq;
+ total_len = 0;
+ skb_queue_walk(pktq, pkt_next) {
+ /* alignment packet inserted in previous
+ * loop cycle can be skipped as it is
+ * already properly aligned and does not
+ * need an sdpcm header.
+ */
+ if (*(u16 *)pkt_next->cb & ALIGN_SKB_FLAG)
+ continue;
+
+ /* align packet data pointer */
+ ret = inff_sdio_txpkt_hdalign(bus, pkt_next);
+ if (ret < 0)
+ return ret;
+ head_pad = (u16)ret;
+ if (head_pad)
+ memset(pkt_next->data + bus->tx_hdrlen, 0, head_pad);
+
+ total_len += pkt_next->len;
+
+ hd_info.len = pkt_next->len;
+ if (bus->sdiodev->sg_support && (bus->txglom && pktq->qlen > 1)) {
+ ret = inff_sdio_txpkt_prep_sg(bus, pktq,
+ pkt_next, total_len);
+ if (ret < 0)
+ return ret;
+ hd_info.tail_pad = (u16)ret;
+ total_len += (u16)ret;
+ }
+
+ hd_info.lastfrm = skb_queue_is_last(pktq, pkt_next);
+ hd_info.channel_and_flags = ((chan & 0x0f) | ((ifp->ifidx << 4) & 0xf0));
+ hd_info.dat_offset = head_pad + bus->tx_hdrlen;
+ hd_info.seq_num = txseq++;
+
+ /* Now fill the header */
+ inff_sdio_hdpack(bus, pkt_next->data, &hd_info);
+
+ if (INFF_MSGLVL_ON(BYTES) &&
+ ((INFF_MSGLVL_ON(CTL) && chan == SDPCM_CONTROL_CHANNEL) ||
+ (INFF_MSGLVL_ON(DATA) && chan != SDPCM_CONTROL_CHANNEL)))
+ inff_dbg_hex_dump(INFF_MSGLVL_ON(SDIO), pkt_next->data,
+ hd_info.len, "Tx Frame:\n");
+ else if (INFF_MSGLVL_ON(HDRS))
+ inff_dbg_hex_dump(INFF_MSGLVL_ON(SDIO), pkt_next->data,
+ head_pad + bus->tx_hdrlen,
+ "Tx Header:\n");
+ }
+ return 0;
+}
+
+/**
+ * inff_sdio_txpkt_postp - packet post processing for transmit
+ * @bus: inff_sdio structure pointer
+ * @pktq: packet list pointer
+ *
+ * Processes to be applied to the packet
+ * - Remove head padding
+ * - Remove tail padding
+ */
+static void
+inff_sdio_txpkt_postp(struct inff_sdio *bus, struct sk_buff_head *pktq)
+{
+ u8 *hdr;
+ u32 dat_offset;
+ u16 tail_pad;
+ u16 dummy_flags, chop_len;
+ struct sk_buff *pkt_next, *tmp, *pkt_prev;
+
+ skb_queue_walk_safe(pktq, pkt_next, tmp) {
+ dummy_flags = *(u16 *)(pkt_next->cb);
+ if (dummy_flags & ALIGN_SKB_FLAG) {
+ chop_len = dummy_flags & ALIGN_SKB_CHOP_LEN_MASK;
+ if (chop_len) {
+ pkt_prev = pkt_next->prev;
+ skb_put(pkt_prev, chop_len);
+ }
+ __skb_unlink(pkt_next, pktq);
+ inff_pkt_buf_free_skb(pkt_next);
+ } else {
+ hdr = pkt_next->data + bus->tx_hdrlen - SDPCM_SWHDR_LEN;
+ dat_offset = le32_to_cpu(*(__le32 *)hdr);
+ dat_offset = (dat_offset & SDPCM_DOFFSET_MASK) >>
+ SDPCM_DOFFSET_SHIFT;
+ skb_pull(pkt_next, dat_offset);
+ if (bus->txglom) {
+ tail_pad = le16_to_cpu(*(__le16 *)(hdr - 2));
+ if (tail_pad)
+ skb_trim(pkt_next, pkt_next->len - tail_pad);
+ }
+ }
+ }
+}
+
+/* Writes a HW/SW header into the packet and sends it. */
+static int inff_cp_sdio_txpkt(struct inff_sdio *bus, struct inff_if *ifp,
+ struct sk_buff_head *pktq, uint chan)
+{
+ struct inff_sdio_dev *sdiodev = bus->sdiodev;
+ struct sk_buff *pkt_next, *tmp;
+ bool success = false;
+ int ret;
+
+ if (!ifp) {
+ inff_err("Error: inff_sdio_txpkt, ifp NULL\n");
+ return -1;
+ }
+
+ ret = inff_sdio_cp_txpkt_prep(bus, ifp, pktq, chan);
+ if (ret)
+ goto done;
+
+ sdio_claim_host(sdiodev->func1);
+ ret = inff_sdiod_send_pkt(sdiodev, pktq);
+ if (ret < 0) {
+ inff_sdio_txfail(bus);
+ } else {
+ bus->tx_seq = (bus->tx_seq + pktq->qlen) % SDPCM_SEQ_WRAP;
+ success = true;
+ }
+ sdio_release_host(sdiodev->func1);
+
+done:
+ inff_sdio_txpkt_postp(bus, pktq);
+ skb_queue_walk_safe(pktq, pkt_next, tmp) {
+ __skb_unlink(pkt_next, pktq);
+ inff_txfinalize(ifp, pkt_next, success);
+ }
+
+ return ret;
+}
+
+/* Writes a HW/SW header into the packet and sends it. */
+/* Assumes: (a) header space already there, (b) caller holds lock */
+static int inff_sdio_txpkt(struct inff_sdio *bus,
+ struct sk_buff_head *pktq, uint chan)
+{
+ int ret;
+ struct sk_buff *pkt_next, *tmp;
+
+ ret = inff_sdio_txpkt_prep(bus, pktq, chan);
+ if (ret)
+ goto done;
+
+ sdio_claim_host(bus->sdiodev->func1);
+
+ if (!bus->acw74xx_f2_txdma_suspended && bus->ci->chip == INF_CC_ACW74XX_CHIP_ID)
+ inff_sdio_acw74xx_suspend_txdma(bus);
+ ret = inff_sdiod_send_pkt(bus->sdiodev, pktq);
+ bus->sdcnt.f2txdata++;
+
+ if (ret < 0)
+ inff_sdio_txfail(bus);
+
+ sdio_release_host(bus->sdiodev->func1);
+
+done:
+ inff_sdio_txpkt_postp(bus, pktq);
+ if (ret == 0)
+ bus->tx_seq = (bus->tx_seq + pktq->qlen) % SDPCM_SEQ_WRAP;
+ skb_queue_walk_safe(pktq, pkt_next, tmp) {
+ __skb_unlink(pkt_next, pktq);
+ inff_proto_bcdc_txcomplete(bus->sdiodev->dev, pkt_next,
+ ret == 0);
+ }
+ return ret;
+}
+
+static u16 inff_cp_sdio_sendfromq(struct inff_sdio *bus)
+{
+ struct inff_sdio_dev *sdiodev = bus->sdiodev;
+ struct inff_if *ifp = NULL;
+ struct sk_buff *pkt;
+ struct sk_buff_head pktq;
+ u16 pkt_num;
+ u8 tx_prec_map;
+ int ret = 0, prec_out;
+
+ tx_prec_map = ~bus->flowcontrol;
+
+ do {
+ pkt_num = pktq_len(&bus->txq);
+ if (pkt_num == 0)
+ break;
+
+ inff_dbg(DATA, "pkt_num: %d\n", pkt_num);
+
+ /* check dev whether prepare to receive next data */
+ sdio_claim_host(sdiodev->func1);
+ ret = inff_cp_sdio_wait_dev_ready(sdiodev, false);
+ sdio_release_host(sdiodev->func1);
+ if (ret < 0)
+ break;
+
+ /* dequeue and transmit skb */
+ __skb_queue_head_init(&pktq);
+ spin_lock_bh(&bus->txq_lock);
+
+ /* Find highest priority packet */
+ pkt = inff_pktq_mdeq(&bus->txq, tx_prec_map, &prec_out);
+ if (!pkt) {
+ spin_unlock_bh(&bus->txq_lock);
+ break;
+ }
+
+ skb_orphan(pkt);
+ __skb_queue_tail(&pktq, pkt);
+
+ spin_unlock_bh(&bus->txq_lock);
+
+ ifp = netdev_priv(pkt->dev);
+
+ ret = inff_cp_sdio_txpkt(bus, ifp, &pktq, SDPCM_DATA_CHANNEL);
+ } while (true);
+
+ return pkt_num;
+}
+
+static uint inff_sdio_sendfromq(struct inff_sdio *bus, uint maxframes)
+{
+ struct sk_buff *pkt;
+ struct sk_buff_head pktq;
+ int ret = 0, prec_out, i;
+ uint cnt = 0;
+ u8 tx_prec_map, pkt_num, que_cnt;
+
+ tx_prec_map = ~bus->flowcontrol;
+
+ /* Send frames until the limit or some other event */
+ for (cnt = 0; (cnt < maxframes) && data_ok(bus);) {
+ pkt_num = 1;
+ que_cnt = bus->tx_max - bus->tx_seq;
+ if (bus->txglom) {
+ pkt_num = min_t(u8, que_cnt - TXCTL_CREDITS,
+ bus->sdiodev->txglomsz);
+ }
+
+ pkt_num = min_t(u32, pkt_num,
+ inff_pktq_mlen(&bus->txq, ~bus->flowcontrol));
+ __skb_queue_head_init(&pktq);
+ spin_lock_bh(&bus->txq_lock);
+ for (i = 0; i < pkt_num; i++) {
+ pkt = inff_pktq_mdeq(&bus->txq, tx_prec_map,
+ &prec_out);
+ if (!pkt)
+ break;
+ __skb_queue_tail(&pktq, pkt);
+ }
+ spin_unlock_bh(&bus->txq_lock);
+ if (i == 0)
+ break;
+
+ ret = inff_sdio_txpkt(bus, &pktq, SDPCM_DATA_CHANNEL);
+
+ cnt += i;
+ }
+
+ /* Deflow-control stack if needed */
+ if (bus->sdiodev->state == INFF_SDIOD_DATA &&
+ bus->txoff && (pktq_len(&bus->txq) < TXLOW)) {
+ bus->txoff = false;
+ inff_proto_bcdc_txflowblock(bus->sdiodev->dev, false);
+ }
+
+ return cnt;
+}
+
+static int inff_sdio_tx_ctrlframe(struct inff_sdio *bus, u8 *frame, u16 len)
+{
+ u8 doff = 0;
+ u16 pad = 0, payload_len = 0;
+ uint retries = 0;
+ struct inff_sdio_hdrinfo hd_info = {0};
+ int ret;
+ struct inff_proto_bcdc_dcmd *ioctl_ptr = (struct inff_proto_bcdc_dcmd *)frame;
+
+ /* Back the pointer to make room for bus header */
+ frame -= bus->tx_hdrlen;
+ len += bus->tx_hdrlen;
+
+ /* Add alignment padding (optional for ctl frames) */
+ doff = ((unsigned long)frame % bus->head_align);
+ if (doff) {
+ frame -= doff;
+ len += doff;
+ memset(frame + bus->tx_hdrlen, 0, doff);
+ }
+
+ /* Round send length to next SDIO block */
+ if (bus->roundup && bus->blocksize && len > bus->blocksize) {
+ pad = bus->blocksize - (len % bus->blocksize);
+ if (pad > bus->roundup || pad >= bus->blocksize)
+ pad = 0;
+ } else if (len % bus->head_align) {
+ pad = bus->head_align - (len % bus->head_align);
+ }
+ len += pad;
+
+ if (bus->ci->chip == INF_CC_ACW74XX_CHIP_ID) {
+ payload_len = len - pad;
+ hd_info.len = len;
+ } else {
+ hd_info.len = len - pad;
+ }
+
+ hd_info.channel_and_flags = SDPCM_CONTROL_CHANNEL;
+
+ hd_info.dat_offset = doff + bus->tx_hdrlen;
+ hd_info.seq_num = bus->tx_seq;
+ hd_info.lastfrm = true;
+ hd_info.tail_pad = pad;
+
+ if (bus->ci->chip == INF_CC_ACW74XX_CHIP_ID) {
+ if (ioctl_ptr->cmd > NON_WLC_ACW74XX_OHD_IOCTL_BASE) {
+ hd_info.channel_and_flags = SDPCM_OHD_CHANNEL;
+ ioctl_ptr->cmd -= NON_WLC_ACW74XX_OHD_IOCTL_BASE;
+ }
+ }
+
+ inff_sdio_hdpack(bus, frame, &hd_info);
+
+ if (bus->ci->chip == INF_CC_ACW74XX_CHIP_ID) {
+ //update reserve bytes (B10 and B11) with actual payload length
+ *((__le16 *)(frame + 10)) = cpu_to_le16(payload_len);
+ }
+
+ if (bus->txglom)
+ inff_sdio_update_hwhdr(frame, len);
+
+ inff_dbg_hex_dump(INFF_MSGLVL_ON(BYTES) && INFF_MSGLVL_ON(CTL),
+ frame, len, "Tx Frame:\n");
+ inff_dbg_hex_dump(!(INFF_MSGLVL_ON(BYTES) && INFF_MSGLVL_ON(CTL)) &&
+ INFF_MSGLVL_ON(HDRS),
+ frame, min_t(u16, len, 16), "TxHdr:\n");
+
+ if (!bus->acw74xx_f2_txdma_suspended && bus->ci->chip == INF_CC_ACW74XX_CHIP_ID)
+ inff_sdio_acw74xx_suspend_txdma(bus);
+
+ do {
+ ret = inff_sdiod_send_buf(bus->sdiodev, SDIO_FUNC_2, frame, len);
+
+ if (ret < 0)
+ inff_sdio_txfail(bus);
+ else
+ bus->tx_seq = (bus->tx_seq + 1) % SDPCM_SEQ_WRAP;
+ } while (ret < 0 && retries++ < TXRETRIES);
+
+ return ret;
+}
+
+static void inff_sdio_bus_stop(struct device *dev)
+{
+ struct inff_bus *bus_if = dev_get_drvdata(dev);
+ struct inff_sdio_dev *sdiodev = bus_if->bus_priv.sdio;
+ struct inff_sdio *bus = sdiodev->bus;
+ struct inff_core *core = bus->sdio_core;
+ u32 local_hostintmask;
+ u8 saveclk, bpreq;
+ int err;
+
+ if (bus->watchdog_tsk) {
+ get_task_struct(bus->watchdog_tsk);
+ send_sig(SIGTERM, bus->watchdog_tsk, 1);
+ kthread_stop_put(bus->watchdog_tsk);
+ bus->watchdog_tsk = NULL;
+ }
+
+ if (sdiodev->state != INFF_SDIOD_NOMEDIUM) {
+ sdio_claim_host(sdiodev->func1);
+
+ /* Enable clock for device interrupts */
+ inff_sdio_bus_sleep(bus, false, false);
+
+ /* Disable and clear interrupts at the chip level also */
+ inff_sdiod_writel(sdiodev, core->base + SD_REG(hostintmask),
+ 0, NULL);
+
+ local_hostintmask = bus->hostintmask;
+ bus->hostintmask = 0;
+
+ if (inff_socitype_is_ai(bus_if)) {
+ /* Force backplane clocks to assure F2 interrupt propagates */
+ saveclk = inff_sdiod_readb(sdiodev, SBSDIO_FUNC1_CHIPCLKCSR,
+ &err);
+ if (!err) {
+ bpreq = saveclk;
+ bpreq |= SBSDIO_FORCE_HT;
+ inff_sdiod_writeb(sdiodev,
+ SBSDIO_FUNC1_CHIPCLKCSR,
+ bpreq, &err);
+ }
+ if (err)
+ inff_err("Failed to force clock for F2: err %d\n",
+ err);
+ }
+
+ /* Turn off the bus (F2), free any pending packets */
+ inff_dbg(INTR, "disable SDIO interrupts\n");
+ sdio_disable_func(sdiodev->func2);
+
+ /* Clear any pending interrupts now that F2 is disabled */
+ inff_sdiod_writel(sdiodev, core->base + SD_REG(intstatus),
+ local_hostintmask, NULL);
+
+ sdio_release_host(sdiodev->func1);
+ }
+ /* Clear the data packet queues */
+ inff_pktq_flush(&bus->txq, true, NULL, NULL);
+
+ /* Clear any held glomming stuff */
+ inff_pkt_buf_free_skb(bus->glomd);
+ inff_sdio_free_glom(bus);
+
+ /* Clear rx control and wake any waiters */
+ spin_lock_bh(&bus->rxctl_lock);
+ bus->rxlen = 0;
+ spin_unlock_bh(&bus->rxctl_lock);
+ inff_sdio_dcmd_resp_wake(bus);
+
+ /* Reset some F2 state stuff */
+ bus->rxskip = false;
+ bus->tx_seq = 0;
+ bus->rx_seq = 0;
+}
+
+static inline void inff_sdio_clrintr(struct inff_sdio *bus)
+{
+ struct inff_sdio_dev *sdiodev;
+ unsigned long flags;
+
+ sdiodev = bus->sdiodev;
+ if (sdiodev->oob_irq_requested) {
+ spin_lock_irqsave(&sdiodev->irq_en_lock, flags);
+ if (!sdiodev->irq_en && !atomic_read(&bus->ipend)) {
+ enable_irq(sdiodev->settings->bus.sdio.oob_irq_nr);
+ sdiodev->irq_en = true;
+ }
+ spin_unlock_irqrestore(&sdiodev->irq_en_lock, flags);
+ }
+}
+
+static int inff_sdio_intr_rstatus(struct inff_sdio *bus)
+{
+ struct inff_core *core = bus->sdio_core;
+ u32 addr;
+ unsigned long val;
+ int ret;
+
+ addr = core->base + SD_REG(intstatus);
+
+ val = inff_sdiod_readl(bus->sdiodev, addr, &ret);
+ bus->sdcnt.f1regdata++;
+ if (ret != 0)
+ return ret;
+
+ val &= bus->hostintmask;
+ atomic_set(&bus->fcstate, !!(val & I_HMB_FC_STATE));
+
+ /* Clear interrupts */
+ if (val) {
+ inff_sdiod_writel(bus->sdiodev, addr, val, &ret);
+ bus->sdcnt.f1regdata++;
+ atomic_or(val, &bus->intstatus);
+ }
+
+ return ret;
+}
+
+static void inff_sdio_dpc(struct inff_sdio *bus)
+{
+ struct inff_sdio_dev *sdiod = bus->sdiodev;
+ u32 newstatus = 0;
+ u32 intstat_addr = bus->sdio_core->base + SD_REG(intstatus);
+ unsigned long intstatus;
+ uint txlimit = bus->txbound; /* Tx frames to send before resched */
+ uint framecnt; /* Temporary counter of tx/rx frames */
+ int err = 0;
+ u16 txq_num = 0;
+
+ sdio_claim_host(bus->sdiodev->func1);
+
+ if (inff_socitype_is_ai(bus->sdiodev->bus_if)) {
+ /* If waiting for HTAVAIL, check status */
+ if (!bus->sr_enabled && bus->clkstate == CLK_PENDING) {
+ u8 clkctl, devctl = 0;
+
+#ifdef DEBUG
+ /* Check for inconsistent device control */
+ devctl = inff_sdiod_readb(bus->sdiodev, SBSDIO_DEVICE_CTL,
+ &err);
+#endif /* DEBUG */
+
+ /* Read CSR, if clock on switch to AVAIL, else ignore */
+ clkctl = inff_sdiod_readb(bus->sdiodev,
+ SBSDIO_FUNC1_CHIPCLKCSR, &err);
+
+ inff_dbg(SDIO, "DPC: PENDING, devctl 0x%02x clkctl 0x%02x\n",
+ devctl, clkctl);
+
+ if (SBSDIO_HTAV(clkctl)) {
+ devctl = inff_sdiod_readb(bus->sdiodev,
+ SBSDIO_DEVICE_CTL, &err);
+ devctl &= ~SBSDIO_DEVCTL_CA_INT_ONLY;
+ inff_sdiod_writeb(bus->sdiodev,
+ SBSDIO_DEVICE_CTL, devctl, &err);
+ bus->clkstate = CLK_AVAIL;
+ }
+ }
+ }
+ /* Make sure backplane clock is on */
+ inff_sdio_bus_sleep(bus, false, true);
+
+ /* Pending interrupt indicates new device status */
+ if (atomic_read(&bus->ipend) > 0) {
+ atomic_set(&bus->ipend, 0);
+ err = inff_sdio_intr_rstatus(bus);
+ }
+
+ /* Start with leftover status bits */
+ intstatus = atomic_xchg(&bus->intstatus, 0);
+
+ /* Handle flow-control change: read new state in case our ack
+ * crossed another change interrupt. If change still set, assume
+ * FC ON for safety, let next loop through do the debounce.
+ */
+ if (intstatus & I_HMB_FC_CHANGE) {
+ intstatus &= ~I_HMB_FC_CHANGE;
+ inff_sdiod_writel(sdiod, intstat_addr, I_HMB_FC_CHANGE, &err);
+
+ newstatus = inff_sdiod_readl(sdiod, intstat_addr, &err);
+
+ bus->sdcnt.f1regdata += 2;
+ atomic_set(&bus->fcstate,
+ !!(newstatus & (I_HMB_FC_STATE | I_HMB_FC_CHANGE)));
+ intstatus |= (newstatus & bus->hostintmask);
+ }
+
+ /* Handle host mailbox indication */
+ if (intstatus & I_HMB_HOST_INT) {
+ u32 hmb_data = 0;
+
+ intstatus &= ~I_HMB_HOST_INT;
+ intstatus |= inff_sdio_hostmail(bus, &hmb_data);
+ }
+
+ sdio_release_host(bus->sdiodev->func1);
+
+ /* Generally don't ask for these, can get CRC errors... */
+ if (intstatus & I_WR_OOSYNC) {
+ inff_err("Dongle reports WR_OOSYNC\n");
+ intstatus &= ~I_WR_OOSYNC;
+ }
+
+ if (intstatus & I_RD_OOSYNC) {
+ inff_err("Dongle reports RD_OOSYNC\n");
+ intstatus &= ~I_RD_OOSYNC;
+ }
+
+ if (intstatus & I_SBINT) {
+ inff_err("Dongle reports SBINT\n");
+ intstatus &= ~I_SBINT;
+ }
+
+ /* Would be active due to wake-wlan in gSPI */
+ if (intstatus & I_CHIPACTIVE) {
+ inff_dbg(SDIO, "Dongle reports CHIPACTIVE\n");
+ intstatus &= ~I_CHIPACTIVE;
+ }
+
+ if (intstatus & I_HMB_FC_STATE) {
+ inff_dbg(INFO, "Dongle reports HMB_FC_STATE\n");
+ intstatus &= ~I_HMB_FC_STATE;
+ }
+
+ /* Ignore frame indications if rxskip is set */
+ if (bus->rxskip)
+ intstatus &= ~I_HMB_FRAME_IND;
+
+ /* On frame indication, read available frames */
+ if (intstatus & I_HMB_FRAME_IND) {
+ if (inff_socitype_is_cp(bus->sdiodev->bus_if)) {
+ inff_cp_sdio_readframes(bus);
+ } else if (inff_socitype_is_ai(bus->sdiodev->bus_if)) {
+ if (bus->clkstate == CLK_AVAIL && !sdiod->redownload_fw)
+ inff_sdio_readframes(bus, bus->rxbound);
+ }
+ if (!bus->rxpending)
+ intstatus &= ~I_HMB_FRAME_IND;
+ }
+
+ /* Keep still-pending events for next scheduling */
+ if (intstatus)
+ atomic_or(intstatus, &bus->intstatus);
+
+ inff_sdio_clrintr(bus);
+
+ switch (bus->ci->socitype) {
+ case INFF_SOCI_CP:
+ if (bus->ctrl_frame_stat) {
+ sdio_claim_host(bus->sdiodev->func1);
+ err = inff_cp_sdio_wait_dev_ready(bus->sdiodev, false);
+
+ err |= inff_sdio_tx_ctrlframe(bus, bus->ctrl_frame_buf,
+ bus->ctrl_frame_len);
+ bus->ctrl_frame_err = err;
+ wmb(); /*Ensure tx ctrlframe cache line entry is flushed*/
+ bus->ctrl_frame_stat = false;
+ if (err)
+ inff_err("sdio ctrlframe tx failed err=%d\n", err);
+ sdio_release_host(bus->sdiodev->func1);
+ inff_sdio_wait_event_wakeup(bus);
+ }
+
+ txq_num = inff_cp_sdio_sendfromq(bus);
+ if (txq_num)
+ inff_sdio_trigger_dpc(bus);
+ break;
+ case INFF_SOCI_AI:
+ if (bus->clkstate == CLK_AVAIL && inff_sdio_f2_ready(bus)) {
+ if (bus->ctrl_frame_stat && txctl_ok(bus)) {
+ sdio_claim_host(bus->sdiodev->func1);
+ if (bus->ctrl_frame_stat) {
+ err = inff_sdio_tx_ctrlframe(bus, bus->ctrl_frame_buf,
+ bus->ctrl_frame_len);
+ bus->ctrl_frame_err = err;
+ wmb(); /*Ensure tx ctrlframe cache line entry is flushed*/
+ bus->ctrl_frame_stat = false;
+ if (err)
+ inff_err("sdio ctrlframe tx failed err=%d\n", err);
+ }
+ sdio_release_host(bus->sdiodev->func1);
+ inff_sdio_wait_event_wakeup(bus);
+ }
+ /* Send queued frames (limit 1 if rx may still be pending) */
+ if (!atomic_read(&bus->fcstate) && data_ok(bus) &&
+ inff_pktq_mlen(&bus->txq, ~bus->flowcontrol) && txlimit) {
+ framecnt = bus->rxpending ? min(txlimit, bus->txminmax) :
+ txlimit;
+ inff_sdio_sendfromq(bus, framecnt);
+ }
+ }
+
+ if (bus->sdiodev->state != INFF_SDIOD_DATA || err != 0) {
+ inff_err("failed backplane access over SDIO, halting operation\n");
+ atomic_set(&bus->intstatus, 0);
+ if (bus->ctrl_frame_stat) {
+ sdio_claim_host(bus->sdiodev->func1);
+ if (bus->ctrl_frame_stat) {
+ bus->ctrl_frame_err = -ENODEV;
+ /* Ensure err is written to variable before wakeup */
+ wmb();
+ bus->ctrl_frame_stat = false;
+ inff_sdio_wait_event_wakeup(bus);
+ }
+ sdio_release_host(bus->sdiodev->func1);
+ }
+ } else if (atomic_read(&bus->intstatus) ||
+ atomic_read(&bus->ipend) > 0 ||
+ (!atomic_read(&bus->fcstate) &&
+ inff_pktq_mlen(&bus->txq, ~bus->flowcontrol) &&
+ data_ok(bus))) {
+ bus->dpc_triggered = true;
+ }
+ break;
+ }
+}
+
+static struct pktq *inff_sdio_bus_gettxq(struct device *dev)
+{
+ struct inff_bus *bus_if = dev_get_drvdata(dev);
+ struct inff_sdio_dev *sdiodev = bus_if->bus_priv.sdio;
+ struct inff_sdio *bus = sdiodev->bus;
+
+ return &bus->txq;
+}
+
+static bool inff_sdio_prec_enq(struct pktq *q, struct sk_buff *pkt, int prec)
+{
+ struct sk_buff *p;
+ int eprec = -1; /* precedence to evict from */
+
+ /* Fast case, precedence queue is not full and we are also not
+ * exceeding total queue length
+ */
+ if (!pktq_pfull(q, prec) && !pktq_full(q)) {
+ inff_pktq_penq(q, prec, pkt);
+ return true;
+ }
+
+ /* Determine precedence from which to evict packet, if any */
+ if (pktq_pfull(q, prec)) {
+ eprec = prec;
+ } else if (pktq_full(q)) {
+ p = inff_pktq_peek_tail(q, &eprec);
+ if (!p)
+ inff_err("inff_pktq_peek_tail() failed\n");
+ if (eprec > prec)
+ return false;
+ }
+
+ /* Evict if needed */
+ if (eprec >= 0) {
+ /* Detect queueing to unconfigured precedence */
+ if (eprec == prec)
+ return false; /* refuse newer (incoming) packet */
+ /* Evict packet according to discard policy */
+ p = inff_pktq_pdeq_tail(q, eprec);
+ if (!p)
+ inff_err("inff_pktq_pdeq_tail() failed\n");
+ inff_pkt_buf_free_skb(p);
+ }
+
+ /* Enqueue */
+ p = inff_pktq_penq(q, prec, pkt);
+ if (!p)
+ inff_err("inff_pktq_penq() failed\n");
+
+ return p ? true : false;
+}
+
+static int inff_sdio_bus_txdata(struct device *dev, struct sk_buff *pkt)
+{
+ int ret = -EBADE;
+ uint prec;
+ struct inff_bus *bus_if = dev_get_drvdata(dev);
+ struct inff_sdio_dev *sdiodev = bus_if->bus_priv.sdio;
+ struct inff_sdio *bus = sdiodev->bus;
+
+ if (sdiodev->state != INFF_SDIOD_DATA)
+ return -EIO;
+
+ /* Add space for the header */
+ skb_push(pkt, bus->tx_hdrlen);
+ /* precondition: IS_ALIGNED((unsigned long)(pkt->data), 2) */
+
+ /* In WLAN, priority is always set by the AP using WMM parameters
+ * and this need not always follow the standard 802.1d priority.
+ * Based on AP WMM config, map from 802.1d priority to corresponding
+ * precedence level.
+ */
+ prec = inff_map_prio_to_prec(bus_if->drvr->config,
+ (pkt->priority & PRIOMASK));
+
+ /* Check for existing queue, current flow-control,
+ * pending event, or pending clock
+ */
+ bus->sdcnt.fcqueued++;
+
+ skb_tx_timestamp(pkt);
+
+ /* Priority based enq */
+ spin_lock_bh(&bus->txq_lock);
+ /* reset bus_flags in packet cb */
+ *(u16 *)(pkt->cb) = 0;
+ if (!inff_sdio_prec_enq(&bus->txq, pkt, prec)) {
+ skb_pull(pkt, bus->tx_hdrlen);
+ inff_err("out of bus->txq !!!\n");
+ ret = -ENOSR;
+ } else {
+ ret = 0;
+ }
+
+ if (inff_socitype_is_ai(sdiodev->bus_if)) {
+ if (pktq_len(&bus->txq) >= TXHI) {
+ bus->txoff = true;
+ inff_proto_bcdc_txflowblock(dev, true);
+ }
+ }
+ spin_unlock_bh(&bus->txq_lock);
+
+#ifdef DEBUG
+ if (pktq_plen(&bus->txq, prec) > qcount[prec])
+ qcount[prec] = pktq_plen(&bus->txq, prec);
+#endif
+
+ inff_sdio_trigger_dpc(bus);
+ return ret;
+}
+
+#ifdef DEBUG
+#define CONSOLE_LINE_MAX 192
+#define CPUSS_CONS_MAGIC_WORD 0xAABB1122
+
+static int inff_sdio_read_cpuss_console(struct inff_sdio *bus)
+{
+ u32 addr;
+ int rv, idx;
+ u32 n;
+ u8 ch;
+ struct cpu_log_le log_le;
+ struct inff_console *c = &bus->cpuss_console;
+ u8 line[CONSOLE_LINE_MAX];
+ u32 magic_word = 0;
+
+ if (!bus->cpuss_console_addr)
+ return 0;
+ rv = inff_sdiod_ramrw(bus->sdiodev, false, bus->cpuss_console_addr, (u8 *)&log_le,
+ sizeof(log_le));
+
+ if (rv < 0)
+ return rv;
+
+ /* Allocate console buffer (one time only) */
+ if (!c->buf) {
+ c->bufsize = le32_to_cpu(log_le.buf_size);
+ c->buf = kmalloc(c->bufsize, GFP_ATOMIC);
+ if (!c->buf)
+ return -ENOMEM;
+ }
+
+ magic_word = le32_to_cpu(log_le.magic_word);
+ if (magic_word != CPUSS_CONS_MAGIC_WORD) {
+ inff_err("cpuss log buffer not initialized!");
+ return 0;
+ }
+
+ idx = le32_to_cpu(log_le.idx);
+
+ /* Protect against corrupt value */
+ if (idx > c->bufsize)
+ return -EBADE;
+
+ /*
+ * Skip reading the console buffer if the index pointer
+ * has not moved
+ */
+ if (idx == c->last)
+ return 0;
+
+ /* Read the console buffer */
+ addr = le32_to_cpu(log_le.buf);
+
+ if (addr == 0)
+ return 0;
+
+ rv = inff_sdiod_ramrw(bus->sdiodev, false, addr, c->buf, c->bufsize);
+ if (rv < 0)
+ return rv;
+
+ while (c->last != idx) {
+ for (n = 0; n < CONSOLE_LINE_MAX - 2; n++) {
+ if (c->last == idx) {
+ /* This would output a partial line.
+ * Instead, back up
+ * the buffer pointer and output this
+ * line next time around.
+ */
+ if (c->last >= n)
+ c->last -= n;
+ else
+ c->last = c->bufsize - n;
+ goto break2;
+ }
+ ch = c->buf[c->last];
+ c->last = (c->last + 1) % c->bufsize;
+ if (ch == '\n')
+ break;
+ line[n] = ch;
+ }
+
+ if (n > 0) {
+ if (line[n - 1] == '\r')
+ n--;
+ line[n] = 0;
+ pr_debug("CPUSS Console : %s\n", line);
+ }
+ }
+break2:
+
+ return 0;
+}
+
+static int inff_sdio_readconsole(struct inff_sdio *bus)
+{
+ struct inff_console *c = &bus->console;
+ u8 line[CONSOLE_LINE_MAX], ch;
+ u32 n, idx, addr;
+ int rv;
+
+ /* Don't do anything until FWREADY updates console address */
+ if (bus->console_addr == 0)
+ return 0;
+
+ /* Read console log struct */
+ addr = bus->console_addr + offsetof(struct rte_console, log_le);
+ rv = inff_sdiod_ramrw(bus->sdiodev, false, addr, (u8 *)&c->log_le,
+ sizeof(c->log_le));
+ if (rv < 0)
+ return rv;
+
+ /* Allocate console buffer (one time only) */
+ if (!c->buf) {
+ c->bufsize = le32_to_cpu(c->log_le.buf_size);
+ c->buf = kmalloc(c->bufsize, GFP_ATOMIC);
+ if (!c->buf)
+ return -ENOMEM;
+ }
+
+ idx = le32_to_cpu(c->log_le.idx);
+
+ /* Protect against corrupt value */
+ if (idx > c->bufsize)
+ return -EBADE;
+
+ /* Skip reading the console buffer if the index pointer
+ * has not moved
+ */
+ if (idx == c->last)
+ return 0;
+
+ /* Read the console buffer */
+ addr = le32_to_cpu(c->log_le.buf);
+
+ if (bus->ci->chip == INF_CC_ACW74XX_CHIP_ID)
+ addr += ACW74XX_WIFISS_ADDR_2_CPUSS_ADDR_OFFSET;
+
+ /* During FW Control Switch from Bootloader to Ram
+ * Console address read will return all 0's which is not a valid.
+ * when we try to access 0 ram address we are getting SDIO error.
+ */
+ if (addr == 0)
+ return 0;
+
+ rv = inff_sdiod_ramrw(bus->sdiodev, false, addr, c->buf, c->bufsize);
+ if (rv < 0)
+ return rv;
+
+ while (c->last != idx) {
+ for (n = 0; n < CONSOLE_LINE_MAX - 2; n++) {
+ if (c->last == idx) {
+ /* This would output a partial line.
+ * Instead, back up
+ * the buffer pointer and output this
+ * line next time around.
+ */
+ if (c->last >= n)
+ c->last -= n;
+ else
+ c->last = c->bufsize - n;
+ goto break2;
+ }
+ ch = c->buf[c->last];
+ c->last = (c->last + 1) % c->bufsize;
+ if (ch == '\n')
+ break;
+ line[n] = ch;
+ }
+
+ if (n > 0) {
+ if (line[n - 1] == '\r')
+ n--;
+ line[n] = 0;
+ pr_debug("CONSOLE: %s\n", line);
+ }
+ }
+break2:
+
+ return 0;
+}
+#endif /* DEBUG */
+
+static int
+inff_sdio_bus_txctl(struct device *dev, unsigned char *msg, uint msglen)
+{
+ struct inff_bus *bus_if = dev_get_drvdata(dev);
+ struct inff_sdio_dev *sdiodev = bus_if->bus_priv.sdio;
+ struct inff_sdio *bus = sdiodev->bus;
+ int ret;
+
+ if (sdiodev->state != INFF_SDIOD_DATA)
+ return -EIO;
+
+ /* Send from dpc */
+ bus->ctrl_frame_buf = msg;
+ bus->ctrl_frame_len = msglen;
+ /* Ensure msg is written to variable before trigger dpc */
+ wmb();
+ bus->ctrl_frame_stat = true;
+
+ inff_sdio_trigger_dpc(bus);
+ wait_event_timeout(bus->ctrl_wait, !bus->ctrl_frame_stat,
+ CTL_DONE_TIMEOUT);
+
+ ret = 0;
+ if (bus->ctrl_frame_stat) {
+ sdio_claim_host(bus->sdiodev->func1);
+ if (bus->ctrl_frame_stat) {
+ inff_dbg(SDIO, "ctrl_frame timeout\n");
+ bus->ctrl_frame_stat = false;
+ ret = -ETIMEDOUT;
+ }
+ sdio_release_host(bus->sdiodev->func1);
+ }
+ if (!ret) {
+ inff_dbg(SDIO, "ctrl_frame complete, err=%d\n",
+ bus->ctrl_frame_err);
+ /* Ensure read completed */
+ rmb();
+ ret = bus->ctrl_frame_err;
+ }
+
+ if (ret)
+ bus->sdcnt.tx_ctlerrs++;
+ else
+ bus->sdcnt.tx_ctlpkts++;
+
+ return ret;
+}
+
+#ifdef DEBUG
+static int inff_sdio_dump_console(struct seq_file *seq, struct inff_sdio *bus,
+ struct sdpcm_shared *sh)
+{
+ u32 addr, console_ptr, console_size, console_index;
+ char *conbuf = NULL;
+ __le32 sh_val;
+ int rv;
+
+ /* obtain console information from device memory */
+ addr = sh->console_addr + offsetof(struct rte_console, log_le);
+ rv = inff_sdiod_ramrw(bus->sdiodev, false, addr,
+ (u8 *)&sh_val, sizeof(u32));
+ if (rv < 0)
+ return rv;
+ console_ptr = le32_to_cpu(sh_val);
+
+ addr = sh->console_addr + offsetof(struct rte_console, log_le.buf_size);
+ rv = inff_sdiod_ramrw(bus->sdiodev, false, addr,
+ (u8 *)&sh_val, sizeof(u32));
+ if (rv < 0)
+ return rv;
+ console_size = le32_to_cpu(sh_val);
+
+ addr = sh->console_addr + offsetof(struct rte_console, log_le.idx);
+ rv = inff_sdiod_ramrw(bus->sdiodev, false, addr,
+ (u8 *)&sh_val, sizeof(u32));
+ if (rv < 0)
+ return rv;
+ console_index = le32_to_cpu(sh_val);
+
+ /* allocate buffer for console data */
+ if (console_size <= CONSOLE_BUFFER_MAX)
+ conbuf = vzalloc(console_size + 1);
+
+ if (!conbuf)
+ return -ENOMEM;
+
+ /* obtain the console data from device */
+ conbuf[console_size] = '\0';
+ rv = inff_sdiod_ramrw(bus->sdiodev, false, console_ptr, (u8 *)conbuf,
+ console_size);
+ if (rv < 0)
+ goto done;
+
+ rv = seq_write(seq, conbuf + console_index,
+ console_size - console_index);
+ if (rv < 0)
+ goto done;
+
+ if (console_index > 0)
+ rv = seq_write(seq, conbuf, console_index - 1);
+
+done:
+ vfree(conbuf);
+ return rv;
+}
+
+static int inff_sdio_trap_info(struct seq_file *seq, struct inff_sdio *bus,
+ struct sdpcm_shared *sh)
+{
+ int error;
+ struct inff_trap_info tr;
+
+ if ((sh->flags & SDPCM_SHARED_TRAP) == 0) {
+ inff_dbg(INFO, "no trap in firmware\n");
+ return 0;
+ }
+
+ error = inff_sdiod_ramrw(bus->sdiodev, false, sh->trap_addr, (u8 *)&tr,
+ sizeof(struct inff_trap_info));
+ if (error < 0)
+ return error;
+
+ if (seq)
+ seq_printf(seq,
+ "dongle trap info: type 0x%x @ epc 0x%08x\n"
+ " cpsr 0x%08x spsr 0x%08x sp 0x%08x\n"
+ " lr 0x%08x pc 0x%08x offset 0x%x\n"
+ " r0 0x%08x r1 0x%08x r2 0x%08x r3 0x%08x\n"
+ " r4 0x%08x r5 0x%08x r6 0x%08x r7 0x%08x\n",
+ le32_to_cpu(tr.type), le32_to_cpu(tr.epc),
+ le32_to_cpu(tr.cpsr), le32_to_cpu(tr.spsr),
+ le32_to_cpu(tr.r13), le32_to_cpu(tr.r14),
+ le32_to_cpu(tr.pc), sh->trap_addr,
+ le32_to_cpu(tr.r0), le32_to_cpu(tr.r1),
+ le32_to_cpu(tr.r2), le32_to_cpu(tr.r3),
+ le32_to_cpu(tr.r4), le32_to_cpu(tr.r5),
+ le32_to_cpu(tr.r6), le32_to_cpu(tr.r7));
+ else
+ pr_debug("dongle trap info: type 0x%x @ epc 0x%08x\n"
+ " cpsr 0x%08x spsr 0x%08x sp 0x%08x\n"
+ " lr 0x%08x pc 0x%08x offset 0x%x\n"
+ " r0 0x%08x r1 0x%08x r2 0x%08x r3 0x%08x\n"
+ " r4 0x%08x r5 0x%08x r6 0x%08x r7 0x%08x\n",
+ le32_to_cpu(tr.type), le32_to_cpu(tr.epc),
+ le32_to_cpu(tr.cpsr), le32_to_cpu(tr.spsr),
+ le32_to_cpu(tr.r13), le32_to_cpu(tr.r14),
+ le32_to_cpu(tr.pc), sh->trap_addr,
+ le32_to_cpu(tr.r0), le32_to_cpu(tr.r1),
+ le32_to_cpu(tr.r2), le32_to_cpu(tr.r3),
+ le32_to_cpu(tr.r4), le32_to_cpu(tr.r5),
+ le32_to_cpu(tr.r6), le32_to_cpu(tr.r7));
+ return 0;
+}
+
+static int inff_sdio_assert_info(struct seq_file *seq, struct inff_sdio *bus,
+ struct sdpcm_shared *sh)
+{
+ int error = 0;
+ char file[80] = "?";
+ char expr[80] = "<???>";
+
+ if ((sh->flags & SDPCM_SHARED_ASSERT_BUILT) == 0) {
+ inff_dbg(INFO, "firmware not built with -assert\n");
+ return 0;
+ } else if ((sh->flags & SDPCM_SHARED_ASSERT) == 0) {
+ inff_dbg(INFO, "no assert in dongle\n");
+ return 0;
+ }
+
+ sdio_claim_host(bus->sdiodev->func1);
+ if (sh->assert_file_addr != 0) {
+ error = inff_sdiod_ramrw(bus->sdiodev, false,
+ sh->assert_file_addr, (u8 *)file, 80);
+ if (error < 0)
+ return error;
+ }
+ if (sh->assert_exp_addr != 0) {
+ error = inff_sdiod_ramrw(bus->sdiodev, false,
+ sh->assert_exp_addr, (u8 *)expr, 80);
+ if (error < 0)
+ return error;
+ }
+ sdio_release_host(bus->sdiodev->func1);
+
+ seq_printf(seq, "dongle assert: %s:%d: assert(%s)\n",
+ file, sh->assert_line, expr);
+ return 0;
+}
+
+static int inff_sdio_checkdied(struct inff_sdio *bus)
+{
+ int error;
+ struct sdpcm_shared sh;
+
+ if (inff_socitype_is_cp(bus->sdiodev->bus_if)) {
+ inff_dbg(INFO, "FW trap support not enabled for CP.\n");
+ return 0;
+ }
+
+ error = inff_sdio_readshared(bus, &sh);
+
+ if (error < 0)
+ return error;
+
+ if ((sh.flags & SDPCM_SHARED_ASSERT_BUILT) == 0)
+ inff_dbg(INFO, "firmware not built with -assert\n");
+ else if (sh.flags & SDPCM_SHARED_ASSERT)
+ inff_err("assertion in dongle\n");
+
+ if (sh.flags & SDPCM_SHARED_TRAP) {
+ inff_err("firmware trap in dongle\n");
+ inff_sdio_trap_info(NULL, bus, &sh);
+ }
+
+ return 0;
+}
+
+static int inff_sdio_died_dump(struct seq_file *seq, struct inff_sdio *bus)
+{
+ int error = 0;
+ struct sdpcm_shared sh;
+
+ error = inff_sdio_readshared(bus, &sh);
+ if (error < 0)
+ goto done;
+
+ error = inff_sdio_assert_info(seq, bus, &sh);
+ if (error < 0)
+ goto done;
+
+ error = inff_sdio_trap_info(seq, bus, &sh);
+ if (error < 0)
+ goto done;
+
+ error = inff_sdio_dump_console(seq, bus, &sh);
+
+done:
+ return error;
+}
+
+static int inff_sdio_forensic_read(struct seq_file *seq, void *data)
+{
+ struct inff_bus *bus_if = dev_get_drvdata(seq->private);
+ struct inff_sdio *bus = bus_if->bus_priv.sdio->bus;
+
+ return inff_sdio_died_dump(seq, bus);
+}
+
+static int inff_debugfs_sdio_count_read(struct seq_file *seq, void *data)
+{
+ struct inff_bus *bus_if = dev_get_drvdata(seq->private);
+ struct inff_sdio_dev *sdiodev = bus_if->bus_priv.sdio;
+ struct inff_sdio_count *sdcnt = &sdiodev->bus->sdcnt;
+
+ seq_printf(seq,
+ "intrcount: %u\nlastintrs: %u\n"
+ "pollcnt: %u\nregfails: %u\n"
+ "tx_sderrs: %u\nfcqueued: %u\n"
+ "rxrtx: %u\nrx_toolong: %u\n"
+ "rxc_errors: %u\nrx_hdrfail: %u\n"
+ "rx_badhdr: %u\nrx_badseq: %u\n"
+ "fc_rcvd: %u\nfc_xoff: %u\n"
+ "fc_xon: %u\nrxglomfail: %u\n"
+ "rxglomframes: %u\nrxglompkts: %u\n"
+ "f2rxhdrs: %u\nf2rxdata: %u\n"
+ "f2txdata: %u\nf1regdata: %u\n"
+ "tickcnt: %u\ntx_ctlerrs: %lu\n"
+ "tx_ctlpkts: %lu\nrx_ctlerrs: %lu\n"
+ "rx_ctlpkts: %lu\nrx_readahead: %lu\n",
+ sdcnt->intrcount, sdcnt->lastintrs,
+ sdcnt->pollcnt, sdcnt->regfails,
+ sdcnt->tx_sderrs, sdcnt->fcqueued,
+ sdcnt->rxrtx, sdcnt->rx_toolong,
+ sdcnt->rxc_errors, sdcnt->rx_hdrfail,
+ sdcnt->rx_badhdr, sdcnt->rx_badseq,
+ sdcnt->fc_rcvd, sdcnt->fc_xoff,
+ sdcnt->fc_xon, sdcnt->rxglomfail,
+ sdcnt->rxglomframes, sdcnt->rxglompkts,
+ sdcnt->f2rxhdrs, sdcnt->f2rxdata,
+ sdcnt->f2txdata, sdcnt->f1regdata,
+ sdcnt->tickcnt, sdcnt->tx_ctlerrs,
+ sdcnt->tx_ctlpkts, sdcnt->rx_ctlerrs,
+ sdcnt->rx_ctlpkts, sdcnt->rx_readahead_cnt);
+
+ return 0;
+}
+
+static void inff_sdio_debugfs_create(struct device *dev)
+{
+ struct inff_bus *bus_if = dev_get_drvdata(dev);
+ struct inff_pub *drvr = bus_if->drvr;
+ struct inff_sdio_dev *sdiodev = bus_if->bus_priv.sdio;
+ struct inff_sdio *bus = sdiodev->bus;
+ struct dentry *dentry = inff_debugfs_get_devdir(drvr);
+
+ if (IS_ERR_OR_NULL(dentry))
+ return;
+
+ bus->console_interval = INFF_CONSOLE_INTERVAL;
+ bus->cpuss_console_interval = INFF_CPUSS_CONSOLE_INTERVAL;
+
+ inff_debugfs_add_entry(drvr, "forensics", inff_sdio_forensic_read);
+ inff_debugfs_add_entry(drvr, "counters",
+ inff_debugfs_sdio_count_read);
+ debugfs_create_u32("console_interval", 0644, dentry,
+ &bus->console_interval);
+ debugfs_create_u32("cpuss_console_interval", 0644, dentry,
+ &bus->cpuss_console_interval);
+}
+#else
+static int inff_sdio_checkdied(struct inff_sdio *bus)
+{
+ return 0;
+}
+
+static void inff_sdio_debugfs_create(struct device *dev)
+{
+}
+#endif /* DEBUG */
+
+static int
+inff_sdio_bus_rxctl(struct device *dev, unsigned char *msg, uint msglen)
+{
+ int timeleft;
+ uint rxlen = 0;
+ bool pending;
+ u8 *buf;
+ struct inff_bus *bus_if = dev_get_drvdata(dev);
+ struct inff_sdio_dev *sdiodev = bus_if->bus_priv.sdio;
+ struct inff_sdio *bus = sdiodev->bus;
+
+ if (sdiodev->state != INFF_SDIOD_DATA)
+ return -EIO;
+
+ /* Wait until control frame is available */
+ timeleft = inff_sdio_dcmd_resp_wait(bus, &bus->rxlen, &pending);
+
+ spin_lock_bh(&bus->rxctl_lock);
+ rxlen = bus->rxlen;
+ memcpy(msg, bus->rxctl, min(msglen, rxlen));
+ bus->rxctl = NULL;
+ buf = bus->rxctl_orig;
+ bus->rxctl_orig = NULL;
+ bus->rxlen = 0;
+ spin_unlock_bh(&bus->rxctl_lock);
+ vfree(buf);
+
+ if (rxlen) {
+ inff_dbg(CTL, "resumed on rxctl frame, got %d expected %d\n",
+ rxlen, msglen);
+ } else if (timeleft == 0) {
+ inff_err("resumed on timeout\n");
+ inff_sdio_checkdied(bus);
+ } else if (!pending) {
+ inff_dbg(CTL, "resumed for unknown reason?\n");
+ inff_sdio_checkdied(bus);
+ } else {
+ inff_dbg(CTL, "cancelled\n");
+ return -ERESTARTSYS;
+ }
+
+ if (rxlen)
+ bus->sdcnt.rx_ctlpkts++;
+ else
+ bus->sdcnt.rx_ctlerrs++;
+
+ return rxlen ? (int)rxlen : -ETIMEDOUT;
+}
+
+#ifdef DEBUG
+static bool
+inff_sdio_verifymemory(struct inff_sdio_dev *sdiodev, u32 ram_addr,
+ u8 *ram_data, uint ram_sz)
+{
+ char *ram_cmp;
+ int err;
+ bool ret = true;
+ int address;
+ int offset;
+ int len;
+
+ /* read back and verify */
+ inff_dbg(INFO, "Compare RAM dl & ul at 0x%08x; size=%d\n", ram_addr,
+ ram_sz);
+ ram_cmp = kmalloc(MEMBLOCK, GFP_KERNEL);
+ /* do not proceed while no memory but */
+ if (!ram_cmp)
+ return true;
+
+ address = ram_addr;
+ offset = 0;
+ while (offset < ram_sz) {
+ len = ((offset + MEMBLOCK) < ram_sz) ? MEMBLOCK :
+ ram_sz - offset;
+ err = inff_sdiod_ramrw(sdiodev, false, address, ram_cmp, len);
+ if (err) {
+ inff_err("error %d on reading %d membytes at 0x%08x\n",
+ err, len, address);
+ ret = false;
+ break;
+ } else if (memcmp(ram_cmp, &ram_data[offset], len)) {
+ inff_err("Downloaded RAM image is corrupted, block offset is %d, len is %d\n",
+ offset, len);
+ ret = false;
+ break;
+ }
+ offset += len;
+ address += len;
+ }
+
+ kfree(ram_cmp);
+
+ return ret;
+}
+#else /* DEBUG */
+static bool
+inff_sdio_verifymemory(struct inff_sdio_dev *sdiodev, u32 ram_addr,
+ u8 *ram_data, uint ram_sz)
+{
+ return true;
+}
+#endif /* DEBUG */
+
+static int inff_sdio_download_code_file(struct inff_sdio *bus,
+ const struct firmware *fw)
+{
+ struct trx_header_le *trx = (struct trx_header_le *)fw->data;
+ u32 fw_size;
+ u32 address;
+ int err;
+
+ address = bus->ci->rambase;
+ fw_size = fw->size;
+
+ if (bus->ci->chip == INF_CC_ACW74XX_CHIP_ID) {
+ /* The SRAM FW size should be lesser 544k.
+ * Otherwise NVRAM cannot be downloaded
+ */
+ if (fw_size > bus->ci->chip_spec.hw_chip_ramsize) {
+ inff_err("fw size exceeds!\n");
+ return -EINVAL;
+ }
+
+ err = inff_sdiod_ramrw(bus->sdiodev, true, address,
+ (u8 *)fw->data, fw_size);
+
+ if (err)
+ inff_err("error %d on writing %d membytes at 0x%08x\n",
+ err, (int)fw_size, address);
+ else if (!inff_sdio_verifymemory(bus->sdiodev, address,
+ (u8 *)fw->data, fw_size))
+ err = -EIO;
+ } else {
+ if (trx->magic == cpu_to_le32(TRX_MAGIC)) {
+ if ((le32_to_cpu(trx->flag_version) >> 16) == TRX_VERSION5)
+ address -= sizeof(struct trxv5_header_le);
+ else
+ address -= sizeof(struct trx_header_le);
+ fw_size = le32_to_cpu(trx->len);
+ }
+
+ err = inff_sdiod_ramrw(bus->sdiodev, true, address,
+ (u8 *)fw->data, fw_size);
+
+ if (err)
+ inff_err("error %d on writing %d membytes at 0x%08x\n",
+ err, (int)fw_size, address);
+ else if (!inff_sdio_verifymemory(bus->sdiodev, address,
+ (u8 *)fw->data, fw_size))
+ err = -EIO;
+ }
+
+ return err;
+}
+
+static int inff_sdio_download_nvram(struct inff_sdio *bus,
+ void *vars, u32 varsz)
+{
+ int address = bus->ci->ramsize - varsz + bus->ci->rambase;
+ int err;
+
+ err = inff_sdiod_ramrw(bus->sdiodev, true, address, vars, varsz);
+ if (err)
+ inff_err("error %d on writing %d nvram bytes at 0x%08x\n",
+ err, varsz, address);
+ else if (!inff_sdio_verifymemory(bus->sdiodev, address, vars, varsz))
+ err = -EIO;
+
+ return err;
+}
+
+static int inff_sdio_acw74xx_ram_app_staging(struct inff_sdio_dev *sdiodev, u8 staging_val)
+{
+ u32 regdata;
+
+ SPINWAIT_MS(((u32)inff_sdiod_readb(sdiodev, INFF_SDIO_REG_DAR_D2H_MSG_1, NULL) &
+ staging_val) == 0,
+ INFF_SDIO_D2H_READY_TIMEOUT, 10);
+
+ regdata = (u32)inff_sdiod_readb(sdiodev, INFF_SDIO_REG_DAR_D2H_MSG_1, NULL);
+ if (!(regdata & staging_val)) {
+ inff_err("staging_val 0x%x fail, addr 0x%x regdata 0x%x timeout %dms\n",
+ staging_val,
+ INFF_SDIO_REG_DAR_D2H_MSG_1,
+ regdata,
+ INFF_SDIO_D2H_READY_TIMEOUT);
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
+static int inff_sdio_acw74xx_ram_app_download(struct inff_sdio_dev *sdiod,
+ const struct firmware *logic,
+ const struct firmware *payload)
+{
+ struct inff_core *core = sdiod->bus->sdio_core;
+ u8 ram_app_type = SRT_RAM_APP_TYPE;
+ size_t ram_app_sz = 0;
+ u32 smbdata;
+ int err;
+
+ ram_app_sz = logic->size;
+ if (payload) {
+ ram_app_type = CPUSS_RAM_APP_TYPE;
+ ram_app_sz = payload->size;
+ }
+
+ smbdata = (SMB_DATA_RAM_APP_START | ((ram_app_type & RAM_APP_TYPE_MASK)
+ << RAM_APP_TYPE_SHIFT)) | (ram_app_sz & RAM_APP_SIZE_MASK);
+ inff_sdiod_writel(sdiod, core->base + SD_REG(tosbmailboxdata), smbdata, NULL);
+ inff_sdiod_writel(sdiod, core->base + SD_REG(tosbmailbox), SMB_DEV_INT, NULL);
+
+ if (ram_app_type == SRT_RAM_APP_TYPE) {
+ err = inff_sdio_acw74xx_ram_app_staging(sdiod, HMB_DATA_CPUSS_SRAM_STAGING);
+ if (err)
+ return err;
+ } else if (ram_app_type == CPUSS_RAM_APP_TYPE) {
+ err = inff_sdio_acw74xx_ram_app_staging(sdiod, HMB_DATA_WIFISS_SRAM_STAGING);
+ if (err)
+ return err;
+ }
+
+ /* Download RAM APP logic */
+ inff_dbg(INFO, "Download RAMAPP logic\n");
+ err = inff_sdiod_ramrw(sdiod, true, CM33_CPUSS_SRAM_START,
+ (u8 *)logic->data, logic->size);
+ if (err) {
+ inff_err("error %d on writing %zu membytes at 0x%08x\n",
+ err, logic->size, CM33_CPUSS_SRAM_START);
+ return err;
+ }
+
+ if (payload) {
+ /* Download RAM APP payload */
+ inff_dbg(INFO, "Download RAMAPP payload\n");
+ err = inff_sdiod_ramrw(sdiod, true, sdiod->bus->ci->rambase,
+ (u8 *)payload->data, payload->size);
+ if (err) {
+ inff_err("error %d on writing %zu membytes at 0x%08x\n",
+ err, payload->size, sdiod->bus->ci->rambase);
+ return err;
+ }
+ }
+
+ smbdata = (SMB_DATA_RAM_APP_END | ((ram_app_type & RAM_APP_TYPE_MASK)
+ << RAM_APP_TYPE_SHIFT)) | (ram_app_sz & RAM_APP_SIZE_MASK);
+ inff_sdiod_writel(sdiod, core->base + SD_REG(tosbmailboxdata), smbdata, NULL);
+ inff_sdiod_writel(sdiod, core->base + SD_REG(tosbmailbox), SMB_DEV_INT, NULL);
+
+ /* wait 5s for device to complete the process */
+ inff_dbg(INFO, "wait 5s for dongle reset completion\n");
+ msleep(5000);
+ return 0;
+}
+
+static int inff_sdio_download_firmware_acw74xx(struct inff_sdio *bus,
+ const struct firmware *fw,
+ void *nvram, u32 nvlen)
+{
+ struct inff_dfu_update_context **ctx = &dfu_upd_ctx;
+ int infferr;
+ u32 rstvec;
+
+ sdio_claim_host(bus->sdiodev->func1);
+ inff_sdio_clkctl(bus, CLK_AVAIL, false);
+
+ bus->acw74xx_ramapp = false;
+ bus->acw74xx_f2_txdma_suspended = false;
+ if (*ctx) {
+ inff_dbg(INFO, "Process RAMAPP download!\n");
+ if (inff_sdio_acw74xx_ram_app_download(bus->sdiodev,
+ (*ctx)->bin_logic,
+ (*ctx)->bin_payload))
+ inff_dbg(INFO, "Process RAMAPP download FAIL!\n");
+ inff_dfu_ai_free_ctx(ctx);
+ release_firmware(fw);
+ inff_fw_nvram_free(nvram);
+
+ bus->acw74xx_ramapp = true;
+ inff_sdio_bus_reset(bus->sdiodev->dev);
+ infferr = -EBUSY;
+ goto err;
+ }
+
+ rstvec = get_unaligned_le32(fw->data);
+ inff_dbg(SDIO, "firmware rstvec: %x\n", rstvec);
+
+ infferr = bus->ci->blhs->prep_fwdl(bus->ci);
+ if (infferr) {
+ inff_err("FW download preparation failed\n");
+ release_firmware(fw);
+ inff_fw_nvram_free(nvram);
+ goto err;
+ }
+
+ infferr = inff_sdio_download_code_file(bus, fw);
+ release_firmware(fw);
+ if (infferr) {
+ inff_err("dongle image file download failed\n");
+ inff_fw_nvram_free(nvram);
+ goto err;
+ }
+
+ infferr = bus->ci->blhs->post_fwdl(bus->ci);
+ if (infferr) {
+ inff_err("FW download failed, err=%d\n", infferr);
+ inff_fw_nvram_free(nvram);
+ goto err;
+ }
+
+ infferr = bus->ci->blhs->chk_validation(bus->ci);
+ if (infferr) {
+ inff_err("FW validation failed, err=%d\n", infferr);
+ inff_fw_nvram_free(nvram);
+ goto err;
+ }
+
+ inff_err(" FW download and validation is successful\n");
+
+ infferr = bus->ci->blhs->pre_nvramdl(bus->ci);
+ if (infferr) {
+ inff_err("NVRAM download preparation failed\n");
+ inff_fw_nvram_free(nvram);
+ goto err;
+ }
+
+ infferr = inff_sdio_download_nvram(bus, nvram, nvlen);
+ inff_fw_nvram_free(nvram);
+ if (infferr) {
+ inff_err("dongle nvram file download failed\n");
+ goto err;
+ }
+
+ infferr = bus->ci->blhs->post_nvramdl(bus->ci);
+ if (infferr) {
+ inff_err(" nvram file validation failed\n");
+ goto err;
+ }
+
+ inff_err(" FW and NVram download successful\n");
+err:
+ inff_sdio_clkctl(bus, CLK_SDONLY, false);
+ sdio_release_host(bus->sdiodev->func1);
+ return infferr;
+}
+
+static int inff_sdio_download_firmware(struct inff_sdio *bus,
+ const struct firmware *fw,
+ void *nvram, u32 nvlen)
+{
+ int inferror;
+ u32 rstvec;
+
+ sdio_claim_host(bus->sdiodev->func1);
+ inff_sdio_clkctl(bus, CLK_AVAIL, false);
+
+ rstvec = get_unaligned_le32(fw->data);
+ inff_dbg(SDIO, "firmware rstvec: %x\n", rstvec);
+
+ inferror = bus->ci->blhs->prep_fwdl(bus->ci);
+ if (inferror) {
+ inff_err("FW download preparation failed\n");
+ release_firmware(fw);
+ inff_fw_nvram_free(nvram);
+ goto err;
+ }
+
+ inferror = inff_sdio_download_code_file(bus, fw);
+ release_firmware(fw);
+ if (inferror) {
+ inff_err("dongle image file download failed\n");
+ inff_fw_nvram_free(nvram);
+ goto err;
+ }
+
+ inferror = bus->ci->blhs->post_fwdl(bus->ci);
+ if (inferror) {
+ inff_err("FW download failed, err=%d\n", inferror);
+ inff_fw_nvram_free(nvram);
+ goto err;
+ }
+
+ inferror = bus->ci->blhs->chk_validation(bus->ci);
+ if (inferror) {
+ inff_err("FW valication failed, err=%d\n", inferror);
+ inff_fw_nvram_free(nvram);
+ goto err;
+ }
+
+ inferror = inff_sdio_download_nvram(bus, nvram, nvlen);
+ inff_fw_nvram_free(nvram);
+ if (inferror) {
+ inff_err("dongle nvram file download failed\n");
+ goto err;
+ }
+
+ bus->ci->blhs->post_nvramdl(bus->ci);
+
+err:
+ inff_sdio_clkctl(bus, CLK_SDONLY, false);
+ sdio_release_host(bus->sdiodev->func1);
+ return inferror;
+}
+
+static void inff_sdio_sr_init(struct inff_sdio *bus)
+{
+ int err = 0;
+ u8 val;
+ u8 wakeupctrl;
+ u8 cardcap;
+ u8 chipclkcsr;
+
+ wakeupctrl = SBSDIO_FUNC1_WCTRL_HTWAIT_SHIFT;
+ chipclkcsr = SBSDIO_FORCE_HT;
+
+ cardcap = (SDIO_CCCR_INFF_CARDCAP_CMD14_SUPPORT |
+ SDIO_CCCR_INFF_CARDCAP_CMD14_EXT);
+
+ val = inff_sdiod_readb(bus->sdiodev, SBSDIO_FUNC1_WAKEUPCTRL, &err);
+ if (err) {
+ inff_err("error reading SBSDIO_FUNC1_WAKEUPCTRL\n");
+ return;
+ }
+ val |= 1 << wakeupctrl;
+ inff_sdiod_writeb(bus->sdiodev, SBSDIO_FUNC1_WAKEUPCTRL, val, &err);
+ if (err) {
+ inff_err("error writing SBSDIO_FUNC1_WAKEUPCTRL\n");
+ return;
+ }
+
+ /* Add CMD14 Support */
+ inff_sdiod_func0_wb(bus->sdiodev, SDIO_CCCR_INFF_CARDCAP,
+ cardcap,
+ &err);
+ if (err) {
+ inff_err("error writing SDIO_CCCR_INFF_CARDCAP\n");
+ return;
+ }
+
+ inff_sdiod_writeb(bus->sdiodev, SBSDIO_FUNC1_CHIPCLKCSR,
+ chipclkcsr, &err);
+ if (err) {
+ inff_err("error writing SBSDIO_FUNC1_CHIPCLKCSR\n");
+ return;
+ }
+
+ /* set flag */
+ bus->sr_enabled = true;
+ inff_dbg(INFO, "SR enabled\n");
+}
+
+/* enable KSO bit */
+static int inff_sdio_kso_init(struct inff_sdio *bus)
+{
+ struct inff_core *core = bus->sdio_core;
+ u8 kso_mask, kso_shift;
+ u8 val;
+ int err = 0;
+
+ /* KSO bit added in SDIO core rev 12 */
+ if (core->rev < 12)
+ return 0;
+
+ val = inff_sdiod_readb(bus->sdiodev, SBSDIO_FUNC1_SLEEPCSR, &err);
+ if (err) {
+ inff_err("error reading SBSDIO_FUNC1_SLEEPCSR\n");
+ return err;
+ }
+
+ switch (bus->ci->socitype) {
+ case INFF_SOCI_AI:
+ kso_mask = SBSDIO_FUNC1_SLEEPCSR_KSO_MASK;
+ kso_shift = SBSDIO_FUNC1_SLEEPCSR_KSO_SHIFT;
+ break;
+ case INFF_SOCI_CP:
+ kso_mask = SBSDIO_FUNC1_SLEEPCSR_BT_KSO_MASK;
+ kso_shift = SBSDIO_FUNC1_SLEEPCSR_BT_KSO_SHIFT;
+ break;
+ default:
+ inff_err("chip type %u is not supported\n",
+ bus->ci->socitype);
+ return -ENODEV;
+ }
+
+ if (!(val & SBSDIO_FUNC1_SLEEPCSR_KSO_MASK)) {
+ val |= (SBSDIO_FUNC1_SLEEPCSR_KSO_EN <<
+ SBSDIO_FUNC1_SLEEPCSR_KSO_SHIFT);
+ inff_sdiod_writeb(bus->sdiodev, SBSDIO_FUNC1_SLEEPCSR,
+ val, &err);
+ if (err) {
+ inff_err("error writing SBSDIO_FUNC1_SLEEPCSR\n");
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+static int inff_sdio_bus_preinit(struct device *dev)
+{
+ struct inff_bus *bus_if = dev_get_drvdata(dev);
+ struct inff_sdio_dev *sdiodev = bus_if->bus_priv.sdio;
+ struct inff_sdio *bus = sdiodev->bus;
+ struct inff_core *core = bus->sdio_core;
+ struct inff_if *ifp = bus_if->drvr->iflist[0];
+ u32 value;
+ __le32 iovar;
+ int err = 0;
+
+ /* maxctl provided by common layer */
+ if (WARN_ON(!bus_if->maxctl))
+ return -EINVAL;
+
+ /* Allocate control receive buffer */
+ bus_if->maxctl += bus->roundup;
+ value = roundup((bus_if->maxctl + SDPCM_HDRLEN), ALIGNMENT);
+ value += bus->head_align;
+ bus->rxbuf = kmalloc(value, GFP_ATOMIC);
+ if (bus->rxbuf)
+ bus->rxblen = value;
+
+ if (bus->ci->chip == INF_CC_ACW74XX_CHIP_ID) {
+ bus->tx_hdrlen = SDPCM_HWHDR_LEN + SDPCM_SWHDR_LEN;
+ inff_bus_add_txhdrlen(bus->sdiodev->dev, bus->tx_hdrlen);
+ goto done;
+ }
+
+ /* the commands below use the terms tx and rx from
+ * a device perspective, ie. bus:txglom affects the
+ * bus transfers from device to host.
+ */
+ if (core->rev < 12) {
+ /* for sdio core rev < 12, disable txgloming */
+ iovar = 0;
+ err = inff_fwcmd_iovar_data_set(ifp, "bus:txglom", &iovar,
+ sizeof(iovar));
+ } else {
+ /* otherwise, set txglomalign */
+ value = sdiodev->settings->bus.sdio.sd_sgentry_align;
+ /* SDIO ADMA requires at least 32 bit alignment */
+ iovar = cpu_to_le32(max_t(u32, value, ALIGNMENT));
+ err = inff_fwcmd_iovar_data_set(ifp, "bus:txglomalign", &iovar,
+ sizeof(iovar));
+ }
+
+ if (err < 0)
+ goto done;
+
+ bus->tx_hdrlen = SDPCM_HWHDR_LEN + SDPCM_SWHDR_LEN;
+ if (sdiodev->sg_support) {
+ bus->txglom = false;
+ iovar = cpu_to_le32(1);
+ err = inff_fwcmd_iovar_data_set(ifp, "bus:rxglom",
+ &iovar, sizeof(iovar));
+ if (err < 0) {
+ /* bus:rxglom is allowed to fail */
+ err = 0;
+ } else {
+ bus->txglom = true;
+ bus->tx_hdrlen += SDPCM_HWEXT_LEN;
+ }
+ }
+ inff_bus_add_txhdrlen(bus->sdiodev->dev, bus->tx_hdrlen);
+done:
+ return err;
+}
+
+static size_t inff_sdio_bus_get_ramsize(struct device *dev)
+{
+ struct inff_bus *bus_if = dev_get_drvdata(dev);
+ struct inff_sdio_dev *sdiodev = bus_if->bus_priv.sdio;
+ struct inff_sdio *bus = sdiodev->bus;
+
+ return bus->ci->ramsize - bus->ci->srsize;
+}
+
+static size_t inff_sdio_bus_get_rambase(struct device *dev)
+{
+ struct inff_bus *bus_if = dev_get_drvdata(dev);
+ struct inff_sdio_dev *sdiodev = bus_if->bus_priv.sdio;
+ struct inff_sdio *bus = sdiodev->bus;
+
+ return bus->ci->rambase;
+}
+
+static size_t inff_sdio_bus_get_cpuss_rambase(struct device *dev)
+{
+ struct inff_bus *bus_if = dev_get_drvdata(dev);
+ struct inff_sdio_dev *sdiodev = bus_if->bus_priv.sdio;
+ struct inff_sdio *bus = sdiodev->bus;
+
+ return bus->ci->cpuss_rambase;
+}
+
+static size_t inff_sdio_bus_get_cpuss_ramsize(struct device *dev)
+{
+ struct inff_bus *bus_if = dev_get_drvdata(dev);
+ struct inff_sdio_dev *sdiodev = bus_if->bus_priv.sdio;
+ struct inff_sdio *bus = sdiodev->bus;
+
+ return bus->ci->cpuss_ramsize;
+}
+
+void inff_sdio_trigger_dpc(struct inff_sdio *bus)
+{
+ bus->dpc_triggered = true;
+ queue_work(bus->inff_wq, &bus->datawork);
+}
+
+void inff_sdio_isr(struct inff_sdio *bus, bool in_isr)
+{
+ struct inff_sdio_dev *sdiod;
+ struct inff_pub *drvr;
+
+ if (!bus) {
+ inff_err("bus is null pointer, exiting\n");
+ return;
+ }
+
+ sdiod = bus->sdiodev;
+ drvr = sdiod->bus_if->drvr;
+
+ /* Count the interrupt call */
+ bus->sdcnt.intrcount++;
+ if (in_isr) {
+ atomic_set(&bus->ipend, 1);
+ } else {
+ /* Wake up the bus if in sleep */
+ if (bus->sleeping)
+ inff_sdio_bus_sleep(bus, false, false);
+
+ if (inff_sdio_intr_rstatus(bus))
+ inff_err("failed backplane access\n");
+ }
+
+ bus->dpc_triggered = true;
+ queue_work(bus->inff_wq, &bus->datawork);
+}
+
+static void inff_sdio_bus_watchdog(struct inff_sdio *bus)
+{
+ bool clk_check = false;
+#ifdef DEBUG
+ /* Poll for console output periodically */
+ if (bus->sdiodev->state == INFF_SDIOD_DATA && INFF_MSGLVL_ON(FWCON) &&
+ bus->console_interval != 0) {
+ bus->console.count += jiffies_to_msecs(INFF_WD_POLL);
+ if (bus->console.count >= bus->console_interval) {
+ bus->console.count -= bus->console_interval;
+ sdio_claim_host(bus->sdiodev->func1);
+ /* Make sure backplane clock is on */
+ inff_sdio_bus_sleep(bus, false, false);
+ if (inff_sdio_readconsole(bus) < 0)
+ /* stop on error */
+ bus->console_interval = 0;
+ sdio_release_host(bus->sdiodev->func1);
+ }
+ }
+
+ if (bus->ci->chip == INF_CC_ACW74XX_CHIP_ID) {
+ if (bus->sdiodev->state == INFF_SDIOD_DATA && INFF_MSGLVL_ON(CPUSS_ON) &&
+ bus->cpuss_console_interval != 0) {
+ bus->cpuss_console.count += jiffies_to_msecs(INFF_WD_POLL);
+ if (bus->cpuss_console.count >= bus->cpuss_console_interval) {
+ bus->cpuss_console.count -= bus->cpuss_console_interval;
+ sdio_claim_host(bus->sdiodev->func1);
+ inff_sdio_bus_sleep(bus, false, false);
+ if (inff_sdio_read_cpuss_console(bus) < 0)
+ bus->cpuss_console_interval = 0;
+ sdio_release_host(bus->sdiodev->func1);
+ }
+ }
+ }
+#endif /* DEBUG */
+
+ if (inff_socitype_is_ai(bus->sdiodev->bus_if))
+ clk_check = (bus->clkstate == CLK_AVAIL);
+ else if (inff_socitype_is_cp(bus->sdiodev->bus_if))
+ clk_check = (bus->clkstate == CLK_SDONLY);
+
+ /* On idle timeout clear activity flag and/or turn off clock */
+ if (!bus->dpc_triggered) {
+ /* Ensure read completed */
+ rmb();
+ if (!bus->dpc_running && bus->idletime > 0 &&
+ (clk_check)) {
+ bus->idlecount++;
+ if (bus->idlecount >= bus->idletime) {
+ inff_dbg(SDIO, "idle\n");
+ sdio_claim_host(bus->sdiodev->func1);
+#ifdef DEBUG
+ if (!INFF_MSGLVL_ON(FWCON) ||
+ bus->console_interval == 0)
+#endif
+ inff_sdio_wd_timer(bus, false);
+ bus->idlecount = 0;
+
+ if (!bus->dpc_triggered && !bus->dpc_running)
+ inff_sdio_bus_sleep(bus, true, false);
+ else
+ inff_err("DPC active Skip sleep");
+
+ sdio_release_host(bus->sdiodev->func1);
+ }
+ } else {
+ bus->idlecount = 0;
+ }
+ } else {
+ bus->idlecount = 0;
+ }
+}
+
+static void inff_sdio_dataworker(struct work_struct *work)
+{
+ struct inff_sdio *bus = container_of(work, struct inff_sdio,
+ datawork);
+
+ bus->dpc_running = true;
+
+ /* make sure dpc_running is true */
+ wmb();
+ while (READ_ONCE(bus->dpc_triggered)) {
+ bus->dpc_triggered = false;
+ inff_sdio_dpc(bus);
+ bus->idlecount = 0;
+ }
+ bus->dpc_running = false;
+ if (inff_sdiod_freezing(bus->sdiodev)) {
+ inff_sdiod_change_state(bus->sdiodev, INFF_SDIOD_DOWN);
+ inff_sdiod_try_freeze(bus->sdiodev);
+ inff_sdiod_change_state(bus->sdiodev, INFF_SDIOD_DATA);
+ }
+}
+
+static u32
+inff_sdio_ccsec_get_buscorebase(struct inff_sdio_dev *sdiodev)
+{
+ u8 devctl = 0;
+ u32 addr = 0;
+ int err = 0;
+
+ devctl = inff_sdiod_readb(sdiodev, SBSDIO_DEVICE_CTL, NULL);
+ inff_sdiod_writeb(sdiodev, SBSDIO_DEVICE_CTL, devctl | SBSDIO_DEVCTL_ADDR_RESET, &err);
+ if (err)
+ goto exit;
+
+ addr |= (inff_sdiod_readb(sdiodev, SBSDIO_FUNC1_SBADDRLOW, NULL) << 8) |
+ (inff_sdiod_readb(sdiodev, SBSDIO_FUNC1_SBADDRMID, NULL) << 16) |
+ (inff_sdiod_readb(sdiodev, SBSDIO_FUNC1_SBADDRHIGH, NULL) << 24);
+
+ inff_dbg(INFO, "sdiod core address is 0x%x\n", addr);
+exit:
+ if (err) {
+ inff_err("Get SDIO core base address failed, err=%d", err);
+ addr = 0;
+ }
+ inff_sdiod_writeb(sdiodev, SBSDIO_DEVICE_CTL, devctl, &err);
+
+ return addr;
+}
+
+static u32 inff_sdio_buscore_blhs_read(void *ctx, u32 reg_offset)
+{
+ struct inff_sdio_dev *sdiodev = (struct inff_sdio_dev *)ctx;
+
+ return (u32)inff_sdiod_readb(sdiodev, reg_offset, NULL);
+}
+
+static void inff_sdio_buscore_blhs_write(void *ctx, u32 reg_offset, u32 value)
+{
+ struct inff_sdio_dev *sdiodev = (struct inff_sdio_dev *)ctx;
+
+ inff_sdiod_writeb(sdiodev, reg_offset, (u8)value, NULL);
+}
+
+static u32 inff_sdio_buscore_blhs_readl(void *ctx, u32 addr)
+{
+ struct inff_sdio_dev *sdiodev = (struct inff_sdio_dev *)ctx;
+ u8 offset = 0;
+ u32 buffer = 0, reg = 0;
+
+ for (offset = 0; offset < 4 ; offset++) {
+ reg = inff_sdiod_readb(sdiodev, addr + offset, NULL);
+ buffer |= (reg << (8 * offset));
+ }
+
+ return buffer;
+}
+
+static void inff_sdio_acw74xx_send_cpuss_cmd(void *ctx, u8 wlan_cmd)
+{
+ struct inff_sdio_dev *sdiodev = (struct inff_sdio_dev *)ctx;
+ struct inff_core *core = sdiodev->bus->sdio_core;
+ u8 tosbmailbox_data = 0;
+
+ switch (wlan_cmd) {
+ case INFF_CPUSS_CMD_WLAN_ON:
+ tosbmailbox_data = SMB_DATA_WLAN_ON;
+ break;
+ case INFF_CPUSS_CMD_WLAN_OFF:
+ tosbmailbox_data = SMB_DATA_WLAN_OFF;
+ break;
+ case INFF_CPUSS_CMD_DONGLE_CLEAN_UP:
+ tosbmailbox_data = SMB_DATA_DONGLE_CLEAN_UP;
+ break;
+ default:
+ return;
+ }
+
+ inff_sdiod_writel(sdiodev, core->base + SD_REG(tosbmailboxdata),
+ tosbmailbox_data, NULL);
+
+ inff_sdiod_writel(sdiodev, core->base + SD_REG(tosbmailbox),
+ SMB_DEV_INT, NULL);
+}
+
+static int inff_sdio_buscoreprep(void *ctx)
+{
+ struct inff_sdio_dev *sdiodev = ctx;
+ int err = 0;
+ u8 clkval, clkset;
+
+ /* Try forcing SDIO core to do ALPAvail request only */
+ clkset = SBSDIO_FORCE_HW_CLKREQ_OFF | SBSDIO_ALP_AVAIL_REQ;
+ inff_sdiod_writeb(sdiodev, SBSDIO_FUNC1_CHIPCLKCSR, clkset, &err);
+ if (err) {
+ inff_err("error writing for HT off\n");
+ return err;
+ }
+
+ /* If register supported, wait for ALPAvail and then force ALP */
+ /* This may take up to 15 milliseconds */
+ clkval = inff_sdiod_readb(sdiodev, SBSDIO_FUNC1_CHIPCLKCSR, NULL);
+
+ if ((clkval & ~SBSDIO_AVBITS) != clkset) {
+ inff_err("ChipClkCSR access: wrote 0x%02x read 0x%02x\n",
+ clkset, clkval);
+ return -EACCES;
+ }
+
+ SPINWAIT(((clkval = inff_sdiod_readb(sdiodev, SBSDIO_FUNC1_CHIPCLKCSR,
+ NULL)),
+ !SBSDIO_ALPAV(clkval)),
+ PMU_MAX_TRANSITION_DLY);
+
+ if (!SBSDIO_ALPAV(clkval)) {
+ inff_err("timeout on ALPAV wait, clkval 0x%02x\n",
+ clkval);
+ return -EBUSY;
+ }
+
+ clkset = SBSDIO_FORCE_HW_CLKREQ_OFF | SBSDIO_FORCE_ALP;
+ inff_sdiod_writeb(sdiodev, SBSDIO_FUNC1_CHIPCLKCSR, clkset, &err);
+ usleep_range(ALP_WAIT_MIN, ALP_WAIT_MAX);
+
+ /* Also, disable the extra SDIO pull-ups */
+ inff_sdiod_writeb(sdiodev, SBSDIO_FUNC1_SDIOPULLUP, 0, NULL);
+
+ return 0;
+}
+
+static void inff_sdio_buscore_activate(void *ctx, struct inff_chip *chip,
+ u32 rstvec)
+{
+ struct inff_sdio_dev *sdiodev = ctx;
+ struct inff_core *core = sdiodev->bus->sdio_core;
+ u32 reg_addr;
+ int err;
+
+ /* clear all interrupts */
+ reg_addr = core->base + SD_REG(intstatus);
+ inff_sdiod_writel(sdiodev, reg_addr, 0xFFFFFFFF, NULL);
+
+ if (rstvec) {
+ /* Write reset vector to address 0 */
+ err = inff_sdiod_ramrw(sdiodev, true, 0, (void *)&rstvec,
+ sizeof(rstvec));
+ if (err)
+ inff_err("error %d on reading %d membytes\n",
+ err, (int)sizeof(rstvec));
+ }
+}
+
+static u32 inff_sdio_buscore_read32(void *ctx, u32 addr)
+{
+ struct inff_sdio_dev *sdiodev = ctx;
+ u32 val;
+
+ val = inff_sdiod_readl(sdiodev, addr, NULL);
+
+ return val;
+}
+
+static void inff_sdio_buscore_write32(void *ctx, u32 addr, u32 val)
+{
+ struct inff_sdio_dev *sdiodev = ctx;
+
+ inff_sdiod_writel(sdiodev, addr, val, NULL);
+}
+
+static int
+inff_sdio_buscore_sec_attach(void *ctx, struct inff_blhs **blhs, struct inff_ccsec **ccsec,
+ u32 flag, uint timeout, uint interval)
+{
+ struct inff_sdio_dev *sdiodev = (struct inff_sdio_dev *)ctx;
+ struct inff_blhs *blhsh = NULL;
+ struct inff_ccsec *ccsech = NULL;
+ u32 reg_addr;
+ u32 regdata;
+ u8 cardcap;
+
+ if (sdiodev->func1->vendor != SDIO_VENDOR_ID_CYPRESS)
+ return 0;
+
+ cardcap = inff_sdiod_func0_rb(sdiodev, SDIO_CCCR_INFF_CARDCAP, NULL);
+
+ switch (sdiodev->func2->device) {
+ case SDIO_DEVICE_ID_CYPRESS_ACW7413:
+ if (cardcap & SDIO_CCCR_INFF_CARDCAP_SECURE_MODE) {
+ blhsh = kzalloc_obj(*blhsh);
+ if (!blhsh)
+ return -ENOMEM;
+
+ blhsh->d2h = INFF_SDIO_REG_DAR_D2H_MSG_0;
+ blhsh->h2d = INFF_SDIO_REG_DAR_H2D_MSG_0;
+ blhsh->read = inff_sdio_buscore_blhs_read;
+ blhsh->write = inff_sdio_buscore_blhs_write;
+ blhsh->readl = inff_sdio_buscore_blhs_readl;
+ blhsh->write(ctx, blhsh->h2d, 0);
+
+ blhsh->d2h_2 = INFF_SDIO_REG_DAR_D2H_MSG_2;
+ blhsh->send_cpuss_cmd = inff_sdio_acw74xx_send_cpuss_cmd;
+
+ SPINWAIT_MS((blhsh->readl(ctx, blhsh->d2h)
+ & INFF_SDIO_D2H_ACW74XX_READY) == 0, timeout, interval);
+
+ regdata = blhsh->readl(ctx, blhsh->d2h);
+ if (!(regdata & INFF_SDIO_D2H_ACW74XX_READY)) {
+ inff_err("Timeout waiting for bootloader ready\n");
+ kfree(blhsh);
+ return -EPERM;
+ }
+ *blhs = blhsh;
+ }
+ break;
+ case SDIO_DEVICE_ID_CYPRESS_CYW55900:
+ default:
+ break;
+ }
+
+ if (cardcap & SDIO_CCCR_INFF_CARDCAP_CHIPID_PRESENT) {
+ ccsech = kzalloc_obj(*ccsech);
+ if (!ccsech) {
+ kfree(blhsh);
+ return -ENOMEM;
+ }
+ ccsech->bus_corebase = inff_sdio_ccsec_get_buscorebase(sdiodev);
+ reg_addr = ccsech->bus_corebase + SD_REG(eromptr);
+ ccsech->erombase = inff_sdio_buscore_read32(ctx, reg_addr);
+ reg_addr = ccsech->bus_corebase + SD_REG(chipid);
+ ccsech->chipid = inff_sdio_buscore_read32(ctx, reg_addr);
+ *ccsec = ccsech;
+
+ inff_dbg(TRACE, " chip id present 0x%x\n", ccsech->chipid);
+
+ /* Add backward compatibility for CPUSS that may advertise the
+ * older/temporary chip id (0x1104da0d)
+ */
+ if (ccsech->chipid == 0x1104da0d) {
+ ccsech->chipid = 0xf0891122;
+ inff_dbg(TRACE, " chip id updated to 0x%x\n", ccsech->chipid);
+ }
+ }
+
+ if (cardcap & SDIO_CCCR_INFF_CARDCAP_CHIPID_PRESENT) {
+ u32 reg_val;
+ u32 err;
+
+ /* Get SDIO Bus Mode*/
+ reg_val = inff_sdiod_func0_rb(sdiodev, SDIO_CCCR_SPEED, &err);
+ if (err) {
+ inff_err("error getting sdio bus speed\n");
+ } else {
+ if (reg_val & SDIO_SPEED_DDR50)
+ sdiodev->bus->ddr50_mode = true;
+ }
+ }
+
+ return 0;
+}
+
+#define LOOP_TO_CHECK_FOR_BP_ENABLE 50000 /* Wait for 500msec */
+#define INTR_F1_WAIT_MIN 10
+#define INTR_F1_WAIT_MAX 20
+
+static int inff_get_intr_pending_data(void *ctx)
+{
+ struct inff_sdio_dev *sdiodev = (struct inff_sdio_dev *)ctx;
+ int loop = 0, status = 0, err = 0;
+ u32 reg_val = 0;
+
+ for (loop = 0; loop < LOOP_TO_CHECK_FOR_BP_ENABLE; loop++) {
+ sdio_claim_host(sdiodev->func1);
+ reg_val = inff_sdiod_func0_rb(sdiodev, SDIO_CCCR_INTx, &err);
+ sdio_release_host(sdiodev->func1);
+ status = reg_val & INTR_STATUS_FUNC1;
+
+ if (status) {
+ inff_err("[%d]: Backplane enabled.\n", loop);
+ break;
+ }
+ usleep_range(INTR_F1_WAIT_MIN, INTR_F1_WAIT_MAX);
+ }
+
+ /* Bootloader hung after backplane disable */
+ if (loop == LOOP_TO_CHECK_FOR_BP_ENABLE) {
+ err = -EBUSY;
+ inff_err("Device hung, return failure. time out %d ms\n",
+ (LOOP_TO_CHECK_FOR_BP_ENABLE * 10) / 1000);
+ }
+
+ return 0;
+}
+
+static const struct inff_buscore_ops inff_sdio_buscore_ops = {
+ .prepare = inff_sdio_buscoreprep,
+ .activate = inff_sdio_buscore_activate,
+ .read32 = inff_sdio_buscore_read32,
+ .write32 = inff_sdio_buscore_write32,
+ .sec_attach = inff_sdio_buscore_sec_attach,
+ .get_intr_pend = inff_get_intr_pending_data,
+};
+
+static int
+inff_sdio_probe_attach(struct inff_sdio *bus)
+{
+ struct inff_sdio_dev *sdiodev;
+ u8 clkctl = 0;
+ int err = 0;
+ u32 enum_base;
+ int ret = -EBADE;
+
+ sdiodev = bus->sdiodev;
+ sdio_claim_host(sdiodev->func1);
+
+ enum_base = inff_chip_enum_base(sdiodev->func1->device);
+
+ /*
+ * Force PLL off until inff_chip_attach()
+ * programs PLL control regs
+ */
+
+ inff_sdiod_writeb(sdiodev, SBSDIO_FUNC1_CHIPCLKCSR, INFF_INIT_CLKCTL1,
+ &err);
+ if (!err)
+ clkctl = inff_sdiod_readb(sdiodev, SBSDIO_FUNC1_CHIPCLKCSR,
+ &err);
+
+ if (err || ((clkctl & ~SBSDIO_AVBITS) != INFF_INIT_CLKCTL1)) {
+ inff_err("ChipClkCSR access: err %d wrote 0x%02x read 0x%02x\n",
+ err, INFF_INIT_CLKCTL1, clkctl);
+ goto fail;
+ }
+
+ bus->ci = inff_chip_attach(sdiodev, sdiodev->func1->device,
+ &inff_sdio_buscore_ops);
+ if (IS_ERR(bus->ci)) {
+ inff_err("inff_chip_attach failed!\n");
+ bus->ci = NULL;
+ goto fail;
+ }
+
+ if (!bus->ci->ccsec)
+ pr_debug("F1 signature read @0x18000000=0x%4x\n",
+ inff_sdiod_readl(sdiodev, enum_base, NULL));
+
+ if (bus->ci->chip == INF_CC_ACW74XX_CHIP_ID) {
+ bus->sdio_core = inff_chip_get_core(bus->ci, INF_CORE_ACW74XX);
+ sdiodev->cc_core = inff_chip_get_core(bus->ci, INF_CORE_ACW74XX);
+ } else {
+ /* Pick up the SDIO core info struct from chip.c */
+ bus->sdio_core = inff_chip_get_core(bus->ci, INF_CORE_SDIO_DEV);
+ if (!bus->sdio_core)
+ goto fail;
+
+ /* Pick up the CHIPCOMMON core info struct, for bulk IO in infsdh.c */
+ sdiodev->cc_core = inff_chip_get_core(bus->ci, INF_CORE_CHIPCOMMON);
+ if (!sdiodev->cc_core)
+ goto fail;
+ }
+
+ sdiodev->settings = inff_get_module_param(sdiodev->dev);
+ if (IS_ERR_OR_NULL(sdiodev->settings)) {
+ inff_err("Failed to get device parameters\n");
+ ret = PTR_ERR_OR_ZERO(sdiodev->settings);
+ goto fail;
+ }
+
+ /* platform specific configuration:
+ * alignments must be at least 4 bytes for ADMA
+ */
+ bus->head_align = ALIGNMENT;
+ bus->sgentry_align = ALIGNMENT;
+ if (sdiodev->settings->bus.sdio.sd_head_align > ALIGNMENT)
+ bus->head_align = sdiodev->settings->bus.sdio.sd_head_align;
+ if (sdiodev->settings->bus.sdio.sd_sgentry_align > ALIGNMENT)
+ bus->sgentry_align =
+ sdiodev->settings->bus.sdio.sd_sgentry_align;
+
+ /* allocate scatter-gather table. sg support
+ * will be disabled upon allocation failure.
+ */
+ inff_sdiod_sgtable_alloc(sdiodev);
+
+ if (inff_sdio_kso_init(bus)) {
+ inff_err("error enabling KSO\n");
+ goto fail;
+ }
+
+ sdio_release_host(sdiodev->func1);
+
+ inff_pktq_init(&bus->txq, (PRIOMASK + 1), TXQLEN);
+
+ /* allocate header buffer */
+ bus->hdrbuf = kzalloc(MAX_HDR_READ + bus->head_align, GFP_KERNEL);
+ if (!bus->hdrbuf)
+ return -ENOMEM;
+ /* Locate an appropriately-aligned portion of hdrbuf */
+ bus->rxhdr = (u8 *)roundup((unsigned long)&bus->hdrbuf[0],
+ bus->head_align);
+
+ sdiodev->bus_if->chip_pub = bus->ci;
+
+ return 0;
+
+fail:
+ sdio_release_host(sdiodev->func1);
+ return ret;
+}
+
+static int
+inff_sdio_watchdog_thread(void *data)
+{
+ struct inff_sdio *bus = (struct inff_sdio *)data;
+ int wait;
+
+ allow_signal(SIGTERM);
+ /* Run until signal received */
+ inff_sdiod_freezer_count(bus->sdiodev);
+ while (1) {
+ if (kthread_should_stop())
+ break;
+ inff_sdiod_freezer_uncount(bus->sdiodev);
+ wait = wait_for_completion_interruptible(&bus->watchdog_wait);
+ inff_sdiod_freezer_count(bus->sdiodev);
+ inff_sdiod_try_freeze(bus->sdiodev);
+ if (!wait) {
+ inff_sdio_bus_watchdog(bus);
+ /* Count the tick for reference */
+ bus->sdcnt.tickcnt++;
+ reinit_completion(&bus->watchdog_wait);
+ } else {
+ break;
+ }
+ }
+ return 0;
+}
+
+static void
+inff_sdio_watchdog(struct timer_list *t)
+{
+ struct inff_sdio *bus = timer_container_of(bus, t, timer);
+
+ if (bus->watchdog_tsk) {
+ complete(&bus->watchdog_wait);
+ /* Reschedule the watchdog */
+ if (bus->wd_active)
+ mod_timer(&bus->timer,
+ jiffies + INFF_WD_POLL);
+ }
+}
+
+static int inff_sdio_get_blob(struct device *dev, const struct firmware **fw,
+ enum inff_blob_type type)
+{
+ struct inff_bus *bus_if = dev_get_drvdata(dev);
+ struct inff_sdio_dev *sdiodev = bus_if->bus_priv.sdio;
+ struct inff_sdio *bus = sdiodev->bus;
+ struct inff_chip_specific *chip_spec = &bus->ci->chip_spec;
+
+ switch (type) {
+ case INFF_BLOB_CLM:
+ *fw = chip_spec->clm_fw;
+ chip_spec->clm_fw = NULL;
+ break;
+ default:
+ return -ENOENT;
+ }
+
+ if (!*fw)
+ return -ENOENT;
+
+ return 0;
+}
+
+static int inff_sdio_bus_reset(struct device *dev)
+{
+ struct inff_bus *bus_if = dev_get_drvdata(dev);
+ struct inff_sdio_dev *sdiodev = bus_if->bus_priv.sdio;
+
+ /* start by unregistering irqs */
+ inff_sdiod_intr_unregister(sdiodev);
+
+ inff_sdiod_remove(sdiodev);
+
+ /* reset the adapter */
+ sdio_claim_host(sdiodev->func1);
+ mmc_hw_reset(sdiodev->func1->card);
+ sdio_release_host(sdiodev->func1);
+
+ inff_bus_change_state(sdiodev->bus_if, INFF_BUS_DOWN);
+ return 0;
+}
+
+static void inff_sdio_bus_remove(struct device *dev)
+{
+ struct inff_bus *bus_if = dev_get_drvdata(dev);
+ struct inff_sdio_dev *sdiod = bus_if->bus_priv.sdio;
+
+ device_release_driver(&sdiod->func2->dev);
+ device_release_driver(&sdiod->func1->dev);
+}
+
+static int inff_sdio_bus_set_fcmode(struct device *dev)
+{
+ struct inff_bus *bus_if = dev_get_drvdata(dev);
+ struct inff_sdio_dev *sdiodev = bus_if->bus_priv.sdio;
+
+ if (!inff_feat_is_enabled(bus_if->drvr->iflist[0], INFF_FEAT_PROPTXSTATUS)) {
+ sdiodev->settings->fcmode = bus_if->drvr->settings->fcmode;
+ inff_dbg(INFO, "Set fcmode = %d\n", sdiodev->settings->fcmode);
+ }
+
+ return sdiodev->settings->fcmode;
+}
+
+static const struct inff_bus_ops inff_sdio_bus_ops = {
+ .stop = inff_sdio_bus_stop,
+ .preinit = inff_sdio_bus_preinit,
+ .txdata = inff_sdio_bus_txdata,
+ .txctl = inff_sdio_bus_txctl,
+ .rxctl = inff_sdio_bus_rxctl,
+ .gettxq = inff_sdio_bus_gettxq,
+ .get_rambase = inff_sdio_bus_get_rambase,
+ .get_ramsize = inff_sdio_bus_get_ramsize,
+ .get_cpuss_rambase = inff_sdio_bus_get_cpuss_rambase,
+ .get_cpuss_ramsize = inff_sdio_bus_get_cpuss_ramsize,
+ .get_blob = inff_sdio_get_blob,
+ .debugfs_create = inff_sdio_debugfs_create,
+ .reset = inff_sdio_bus_reset,
+ .remove = inff_sdio_bus_remove,
+ .set_fcmode = inff_sdio_bus_set_fcmode
+};
+
+static void inff_sdio_reset_cp(struct inff_sdio *bus)
+{
+ struct inff_sdio_dev *sdiodev = bus->sdiodev;
+ u32 reg_val = 0;
+ int err = 0;
+
+ inff_sdio_kso_control(bus, false);
+
+ /* Configure registers to trigger BT reset on
+ * "SDIO Soft Reset", and set RES bit to trigger
+ * SDIO as well as BT reset
+ */
+
+ /* Set card control so an SDIO card reset
+ * does a BT backplane reset
+ */
+ reg_val = inff_sdiod_func0_rb(sdiodev,
+ SDIO_CCCR_INFF_CARDCTRL,
+ &err);
+ if (err) {
+ inff_err("Failed to read SDIO_CCCR_INFF_CARDCTRL: %d\n", err);
+ return;
+ }
+
+ reg_val |= SDIO_CCCR_INFF_CARDCTRL_BTRESET;
+
+ inff_dbg(INFO, "Write CARDCTRL = 0x%x\n", reg_val);
+
+ inff_sdiod_func0_wb(sdiodev, SDIO_CCCR_INFF_CARDCTRL,
+ reg_val, &err);
+ if (err) {
+ inff_err("Failed to write SDIO_CCCR_INFF_CARDCTRL: %d\n", err);
+ return;
+ }
+
+ inff_sdiod_func0_wb(sdiodev, SDIO_CCCR_ABORT,
+ sdiodev->func1->num | SDIO_IO_CARD_RESET,
+ &err);
+ if (err) {
+ inff_err("Failed to write SDIO_IO_CARD_RESET: %d\n", err);
+ return;
+ }
+}
+
+static void inff_sdio_reset(struct inff_sdio *bus)
+{
+ switch (bus->ci->socitype) {
+ case INFF_SOCI_CP:
+ switch (bus->ci->chip) {
+ case INF_CC_CYW5591X_CHIP_ID:
+ inff_sdio_reset_cp(bus);
+ break;
+ }
+ break;
+ case INFF_SOCI_AI:
+ switch (bus->ci->chip) {
+ case INF_CC_ACW74XX_CHIP_ID:
+ if (!bus->acw74xx_ramapp) {
+ sdio_claim_host(bus->sdiodev->func1);
+ inff_sdio_bus_sleep(bus, false, false);
+ inff_sdio_acw74xx_send_cpuss_cmd(bus->sdiodev,
+ INFF_CPUSS_CMD_DONGLE_CLEAN_UP);
+ sdio_release_host(bus->sdiodev->func1);
+ /* wait 1s for device to complete the RMMOD operation */
+ msleep(1000);
+ }
+ break;
+ }
+ }
+}
+
+#define INFF_SDIO_FW_CP 0
+#define INFF_SDIO_FW_BOOT 1
+
+#define INFF_SDIO_FW_CODE 0
+#define INFF_SDIO_FW_NVRAM 1
+#define INFF_SDIO_FW_CLM 2
+
+static int inff_sdio_enable_func2(struct inff_bus *bus_if)
+{
+ struct inff_sdio_dev *sdiod = bus_if->bus_priv.sdio;
+ struct inff_sdio *bus = sdiod->bus;
+ struct inff_core *core = bus->sdio_core;
+ u8 saveclk, bpreq;
+ u8 devctl;
+ int err = 0;
+
+ sdio_claim_host(sdiod->func1);
+
+ if (inff_socitype_is_ai(bus_if)) {
+ /* Make sure backplane clock is on, needed to generate F2 interrupt */
+ inff_sdio_clkctl(bus, CLK_AVAIL, false);
+ if (bus->clkstate != CLK_AVAIL)
+ goto release;
+
+ /* Force clocks on backplane to be sure F2 interrupt propagates */
+ saveclk = inff_sdiod_readb(sdiod, SBSDIO_FUNC1_CHIPCLKCSR, &err);
+ if (!err) {
+ bpreq = saveclk;
+ bpreq |= SBSDIO_FORCE_HT;
+ inff_sdiod_writeb(sdiod, SBSDIO_FUNC1_CHIPCLKCSR,
+ bpreq, &err);
+ }
+ if (err) {
+ inff_err("Failed to force clock for F2: err %d\n", err);
+ goto release;
+ }
+
+ /* Enable function 2 (frame transfers) */
+ inff_sdiod_writel(sdiod, core->base + SD_REG(tosbmailboxdata),
+ SDPCM_PROT_VERSION << SMB_DATA_VERSION_SHIFT, NULL);
+ }
+
+ err = sdio_enable_func(sdiod->func2);
+
+ inff_dbg(INFO, "enable F2: err=%d\n", err);
+
+ /* If F2 successfully enabled, set core and enable interrupts */
+ if (!err) {
+ /* Set up the interrupt mask and enable interrupts */
+ bus->hostintmask = HOSTINTMASK;
+ inff_sdiod_writel(sdiod, core->base + SD_REG(hostintmask),
+ bus->hostintmask, NULL);
+
+ switch (sdiod->func1->device) {
+ case SDIO_DEVICE_ID_CYPRESS_ACW7413:
+ inff_dbg(INFO, "set F2 watermark to 0x%x*4 bytes\n",
+ INFACW74XX_F2_WATERMARK);
+ inff_sdiod_writeb(sdiod, SBSDIO_WATERMARK,
+ INFACW74XX_F2_WATERMARK, &err);
+ devctl = inff_sdiod_readb(sdiod, SBSDIO_DEVICE_CTL,
+ &err);
+ devctl |= SBSDIO_DEVCTL_F2WM_ENAB;
+ inff_sdiod_writeb(sdiod, SBSDIO_DEVICE_CTL, devctl,
+ &err);
+ inff_sdiod_writeb(sdiod, SBSDIO_FUNC1_MESBUSYCTRL,
+ INFACW74XX_F1_MESBUSYCTRL, &err);
+ break;
+ case SDIO_DEVICE_ID_CYPRESS_CYW55900:
+ inff_dbg(INFO, "set F2 watermark to 0x%x*4 bytes\n",
+ INF55900_F2_WATERMARK);
+ inff_sdiod_writeb(sdiod, SBSDIO_WATERMARK,
+ INF55900_F2_WATERMARK, &err);
+ devctl = inff_sdiod_readb(sdiod, SBSDIO_DEVICE_CTL,
+ &err);
+ devctl |= SBSDIO_DEVCTL_F2WM_ENAB;
+ inff_sdiod_writeb(sdiod, SBSDIO_DEVICE_CTL, devctl,
+ &err);
+ inff_sdiod_writeb(sdiod, SBSDIO_FUNC1_MESBUSYCTRL,
+ INF55900_F1_MESBUSYCTRL, &err);
+ break;
+ default:
+ inff_sdiod_writeb(sdiod, SBSDIO_WATERMARK,
+ DEFAULT_F2_WATERMARK, &err);
+ break;
+ }
+ } else {
+ /* Disable F2 again */
+ sdio_disable_func(sdiod->func2);
+ goto checkdied;
+ }
+
+ if (inff_socitype_is_ai(bus->sdiodev->bus_if)) {
+ if (inff_chip_sr_capable(bus->ci)) {
+ inff_sdio_sr_init(bus);
+ } else {
+ /* Restore previous clock setting */
+ inff_sdiod_writeb(sdiod, SBSDIO_FUNC1_CHIPCLKCSR,
+ saveclk, &err);
+ }
+ }
+
+ if (err == 0) {
+ /* Assign bus interface call back */
+ sdiod->bus_if->dev = sdiod->dev;
+ sdiod->bus_if->ops = &inff_sdio_bus_ops;
+ sdiod->bus_if->chip = bus->ci->chip;
+ sdiod->bus_if->chiprev = bus->ci->chiprev;
+
+ /* Allow full data communication using DPC from now on. */
+ inff_sdiod_change_state(bus->sdiodev, INFF_SDIOD_DATA);
+
+ err = inff_sdiod_intr_register(sdiod);
+ if (err != 0)
+ inff_err("intr register failed:%d\n", err);
+ bus->sdiodev->redownload_fw = false;
+ }
+
+ /* If we didn't come up, turn off backplane clock */
+ if (err != 0) {
+ if (inff_socitype_is_ai(bus->sdiodev->bus_if))
+ inff_sdio_clkctl(bus, CLK_NONE, false);
+ goto checkdied;
+ }
+
+ /* Start the watchdog timer */
+ bus->sdcnt.tickcnt = 0;
+ inff_sdio_wd_timer(bus, true);
+ sdio_release_host(sdiod->func1);
+
+ err = inff_alloc_wiphy(sdiod->dev, sdiod->settings);
+ if (err) {
+ inff_err("inff_alloc failed\n");
+ goto claim;
+ }
+
+ /* Attach to the common layer, reserve hdr space */
+ err = inff_bus_attach(sdiod->dev);
+ if (err != 0) {
+ inff_err("inff_bus_attach failed\n");
+ goto free;
+ }
+
+ return 0;
+free:
+ if (global_init_retry < MAX_INIT_RETRY_CNT) {
+ global_init_retry++;
+ inff_err("global_init_retry:%d\n", global_init_retry);
+
+ /* start by unregistering irqs */
+ inff_sdiod_intr_unregister(bus->sdiodev);
+
+ inff_sdiod_remove(bus->sdiodev);
+
+ /* reset the adapter */
+ sdio_claim_host(bus->sdiodev->func1);
+ mmc_hw_reset(bus->sdiodev->func1->card);
+ sdio_release_host(bus->sdiodev->func1);
+
+ inff_bus_change_state(bus->sdiodev->bus_if, INFF_BUS_DOWN);
+ return err;
+ }
+
+ inff_free_wiphy(sdiod->dev);
+claim:
+ sdio_claim_host(sdiod->func1);
+checkdied:
+ inff_sdio_checkdied(bus);
+release:
+ sdio_release_host(sdiod->func1);
+ return err;
+}
+
+static int inff_sdio_cp_img_download(struct inff_bus *bus_if, const struct firmware *boot_fw,
+ const struct firmware *cp_fw)
+{
+ struct inff_sdio_dev *sdiod = bus_if->bus_priv.sdio;
+ u8 val = 0;
+ int err = 0;
+ u8 f2_retry_nct = 0;
+
+ /* Enable SDIO function 3 */
+ inff_dbg(SDIO, "Enable Func3\n");
+
+ sdio_claim_host(sdiod->func1);
+ val = inff_sdiod_func0_rb(sdiod, SDIO_CCCR_IOEx, &err);
+ if (err) {
+ inff_err("Failed to read SDIO_CCCR_IOEx: err %d\n", err);
+ goto release;
+ }
+
+ val |= SDIO_CCCR_IEN_FUNC3;
+ inff_sdiod_func0_wb(sdiod, SDIO_CCCR_IOEx, val, &err);
+ if (err) {
+ inff_err("Failed to write SDIO_CCCR_IOEx: err %d\n", err);
+ goto release;
+ }
+ sdio_release_host(sdiod->func1);
+ inff_dbg(SDIO, "Enabled F3 successfully\n");
+ /* Enable SDIO function 3 Done */
+
+ do {
+ inff_info("Checking if CP image is already running or not...\n");
+ msleep(6000);
+ err = inff_sdio_enable_func2(bus_if);
+ f2_retry_nct++;
+ } while ((err != 0) && (f2_retry_nct < 2));
+
+ if (err) {
+ inff_info("No image running in CP, Image download required\n");
+ sdio_claim_host(sdiod->func1);
+ err = inff_dfu_cp_img_dwnld_init(sdiod, boot_fw, cp_fw);
+ if (err) {
+ inff_err("CP first image download failed: %d\n", err);
+ goto release;
+ }
+ inff_info("CP first image download Done!\n");
+ sdio_release_host(sdiod->func1);
+ mmc_hw_reset(sdiod->func1->card);
+ return 0;
+ }
+
+ inff_info("CP image already booted and F2 already enabled!\n");
+
+ /* Initialize DFU */
+ err = inff_dfu_attach(bus_if->drvr);
+ if (err != 0)
+ inff_err("CP DFU init failed, run time image update not possible\n");
+
+ return 0;
+
+release:
+ sdio_release_host(sdiod->func1);
+ return err;
+}
+
+static void inff_sdio_setup(struct device *dev, int err,
+ struct inff_fw_request *fwreq)
+{
+ struct inff_bus *bus_if = dev_get_drvdata(dev);
+ struct inff_sdio_dev *sdiod = bus_if->bus_priv.sdio;
+ struct inff_sdio *bus = sdiod->bus;
+ struct inff_chip_specific *chip_spec = &bus->ci->chip_spec;
+ const struct firmware *code;
+ void *nvram;
+ u32 nvram_len;
+
+ inff_dbg(SDIO, "dev=%s, err=%d\n", dev_name(dev), err);
+
+ if (err)
+ goto fail;
+
+ code = fwreq->items[INFF_SDIO_FW_CODE].binary;
+ nvram = fwreq->items[INFF_SDIO_FW_NVRAM].nv_data.data;
+ nvram_len = fwreq->items[INFF_SDIO_FW_NVRAM].nv_data.len;
+ chip_spec->clm_fw = fwreq->items[INFF_SDIO_FW_CLM].binary;
+ kfree(fwreq);
+
+ /* try to download image and nvram to the dongle */
+ bus->alp_only = true;
+ if (bus->ci->chip == INF_CC_ACW74XX_CHIP_ID)
+ err = inff_sdio_download_firmware_acw74xx(bus, code, nvram, nvram_len);
+ else
+ err = inff_sdio_download_firmware(bus, code, nvram, nvram_len);
+
+ if (err)
+ goto fail;
+ bus->alp_only = false;
+
+ err = inff_sdio_enable_func2(bus_if);
+ if (err)
+ goto fail;
+
+ /* Initialize DFU */
+ err = inff_dfu_attach(bus_if->drvr);
+ if (err != 0)
+ goto fail;
+
+ /* ready */
+ return;
+
+fail:
+ inff_dbg(TRACE, "failed: dev=%s, err=%d\n", dev_name(dev), err);
+ device_release_driver(&sdiod->func2->dev);
+ device_release_driver(dev);
+}
+
+static void inff_cp_firmware_callback(struct device *dev, int err,
+ struct inff_fw_request *fwreq)
+{
+ struct inff_bus *bus_if = dev_get_drvdata(dev);
+ struct inff_sdio_dev *sdiod = bus_if->bus_priv.sdio;
+ struct inff_sdio *bus = sdiod->bus;
+ const struct firmware *boot_fw;
+ const struct firmware *cp_fw;
+
+ inff_dbg(SDIO, "dev=%s, err=%d\n", dev_name(dev), err);
+
+ if (err)
+ goto fail;
+
+ cp_fw = fwreq->items[INFF_SDIO_FW_CP].binary;
+ boot_fw = fwreq->items[INFF_SDIO_FW_BOOT].binary;
+
+ kfree(fwreq);
+
+ /* try to download image to the dongle */
+ if (bus->ci->chip == INF_CC_CYW5591X_CHIP_ID)
+ err = inff_sdio_cp_img_download(bus_if, boot_fw, cp_fw);
+
+ if (err)
+ goto fail;
+
+ /* ready */
+ return;
+fail:
+ inff_dbg(TRACE, "failed: dev=%s, err=%d\n", dev_name(dev), err);
+ device_release_driver(&sdiod->func2->dev);
+ device_release_driver(dev);
+}
+
+int inff_sdio_probe(struct inff_sdio_dev *sdiodev)
+{
+ int ret;
+ struct inff_sdio *bus;
+ struct workqueue_struct *wq;
+ struct inff_fw_request *fwreq;
+
+ /* Allocate private bus interface state */
+ bus = kzalloc_obj(*bus, GFP_ATOMIC);
+ if (!bus) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ bus->sdiodev = sdiodev;
+ sdiodev->bus = bus;
+ skb_queue_head_init(&bus->glom);
+ INIT_WORK(&bus->datawork, inff_sdio_dataworker);
+ bus->txbound = INFF_TXBOUND;
+ bus->rxbound = INFF_RXBOUND;
+ bus->txminmax = INFF_TXMINMAX;
+ bus->tx_seq = SDPCM_SEQ_WRAP - 1;
+
+ /* single-threaded workqueue */
+ wq = alloc_ordered_workqueue("inff_wq/%s", WQ_MEM_RECLAIM | WQ_HIGHPRI,
+ dev_name(&sdiodev->func1->dev));
+ if (!wq) {
+ inff_err("insufficient memory to create txworkqueue\n");
+ ret = -ENOMEM;
+ goto fail;
+ }
+ inff_sdiod_freezer_count(sdiodev);
+ bus->inff_wq = wq;
+
+ /* attempt to attach to the dongle */
+ ret = inff_sdio_probe_attach(bus);
+ if (ret < 0) {
+ inff_err("inff_sdio_probe_attach failed\n");
+ goto fail;
+ }
+
+ spin_lock_init(&bus->rxctl_lock);
+ spin_lock_init(&bus->txq_lock);
+ init_waitqueue_head(&bus->ctrl_wait);
+ init_waitqueue_head(&bus->dcmd_resp_wait);
+ /* Initialize thread based operation and lock */
+ mutex_init(&bus->sdsem);
+
+ /* Set up the watchdog timer */
+ timer_setup(&bus->timer, inff_sdio_watchdog, 0);
+ /* Initialize watchdog thread */
+ init_completion(&bus->watchdog_wait);
+ bus->watchdog_tsk = kthread_run(inff_sdio_watchdog_thread,
+ bus, "inff_wdog/%s",
+ dev_name(&sdiodev->func1->dev));
+ if (IS_ERR(bus->watchdog_tsk)) {
+ pr_warn("inff_watchdog thread failed to start\n");
+ bus->watchdog_tsk = NULL;
+ }
+ /* Initialize DPC thread */
+ bus->dpc_triggered = false;
+ bus->dpc_running = false;
+
+ /* default sdio bus header length for tx packet */
+ bus->tx_hdrlen = SDPCM_HWHDR_LEN + SDPCM_SWHDR_LEN;
+
+ /* Query the F2 block size, set roundup accordingly */
+ bus->blocksize = bus->sdiodev->func2->cur_blksize;
+ bus->roundup = min(max_roundup, bus->blocksize);
+
+ if (inff_socitype_is_ai(bus->sdiodev->bus_if)) {
+ sdio_claim_host(bus->sdiodev->func1);
+
+ /* Disable F2 to clear any intermediate frame state on the dongle */
+ sdio_disable_func(bus->sdiodev->func2);
+
+ bus->rxflow = false;
+
+ /* Done with backplane-dependent accesses, can drop clock... */
+ inff_sdiod_writeb(bus->sdiodev, SBSDIO_FUNC1_CHIPCLKCSR, 0, NULL);
+
+ sdio_release_host(bus->sdiodev->func1);
+ /* ...and initialize clock/power states */
+ bus->clkstate = CLK_SDONLY;
+ } else if (inff_socitype_is_cp(bus->sdiodev->bus_if)) {
+ bus->clkstate = CLK_SDONLY;
+ bus->tx_seq = 0;
+ }
+
+ bus->idletime = INFF_DEFAULT_SDIO_IDLE_CONFIG;
+
+ switch (sdiodev->func1->device) {
+ case SDIO_DEVICE_ID_CYPRESS_CYW55900:
+ bus->idleclock = INFF_IDLE_STOP;
+ break;
+ default:
+ bus->idleclock = INFF_IDLE_ACTIVE;
+ break;
+ }
+
+ /* SR state */
+ bus->sr_enabled = false;
+
+ inff_dbg(INFO, "completed!!\n");
+
+ switch (bus->ci->socitype) {
+ case INFF_SOCI_AI:
+ fwreq = inff_prepare_fw_request(sdiodev->settings->firmware_path,
+ bus->ci, inff_sdio_fwnames,
+ ARRAY_SIZE(inff_sdio_fwnames),
+ bus->sdiodev->settings->board_type);
+ if (!fwreq) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ ret = inff_fw_get_firmwares(sdiodev->dev, fwreq,
+ inff_sdio_setup);
+ if (ret != 0) {
+ inff_err("async firmware request failed: %d\n", ret);
+ kfree(fwreq);
+ goto fail;
+ }
+ break;
+ case INFF_SOCI_CP:
+ fwreq = inff_prepare_fw_request(sdiodev->settings->firmware_path,
+ bus->ci, inff_cp_fwnames,
+ ARRAY_SIZE(inff_cp_fwnames),
+ bus->sdiodev->settings->board_type);
+ if (!fwreq) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ ret = inff_fw_get_firmwares(sdiodev->dev, fwreq,
+ inff_cp_firmware_callback);
+ if (ret != 0) {
+ inff_err("async firmware request failed: %d\n", ret);
+ kfree(fwreq);
+ goto fail;
+ }
+ break;
+ default:
+ inff_err("chip type %u is not supported\n",
+ bus->ci->socitype);
+ goto fail;
+ }
+
+ return 0;
+
+fail:
+ inff_sdio_remove(bus);
+ sdiodev->bus = NULL;
+ return ret;
+}
+
+/* Detach and free everything */
+void inff_sdio_remove(struct inff_sdio *bus)
+{
+ struct inff_chip_specific *chip_spec;
+
+ if (!bus) {
+ inff_err("bus is null pointer, exiting\n");
+ return;
+ }
+
+ chip_spec = &bus->ci->chip_spec;
+
+ /* Stop watchdog task */
+ if (bus->watchdog_tsk) {
+ get_task_struct(bus->watchdog_tsk);
+ send_sig(SIGTERM, bus->watchdog_tsk, 1);
+ kthread_stop_put(bus->watchdog_tsk);
+ bus->watchdog_tsk = NULL;
+ }
+
+ /* De-register interrupt handler */
+ inff_sdiod_intr_unregister(bus->sdiodev);
+
+ inff_bus_detach(bus->sdiodev->dev);
+ inff_dfu_detach(bus->sdiodev->bus_if->drvr);
+ inff_free_wiphy(bus->sdiodev->dev);
+
+ cancel_work_sync(&bus->datawork);
+ if (bus->inff_wq)
+ destroy_workqueue(bus->inff_wq);
+
+ if (bus->ci) {
+ if (bus->sdiodev->state != INFF_SDIOD_NOMEDIUM) {
+ sdio_claim_host(bus->sdiodev->func1);
+ inff_sdio_wd_timer(bus, false);
+
+ if (inff_socitype_is_ai(bus->sdiodev->bus_if)) {
+ inff_sdio_clkctl(bus, CLK_AVAIL, false);
+
+ /* Leave the device in state where it is
+ * 'passive'. This is done by resetting all
+ * necessary cores.
+ */
+ msleep(20);
+ inff_chip_set_passive(bus->ci);
+ }
+
+ inff_sdio_reset(bus);
+
+ inff_sdio_clkctl(bus, CLK_NONE, false);
+ sdio_release_host(bus->sdiodev->func1);
+ }
+ inff_chip_detach(bus->ci);
+ }
+ if (bus->sdiodev->settings)
+ inff_release_module_param(bus->sdiodev->settings);
+
+ if (inff_socitype_is_ai(bus->sdiodev->bus_if)) {
+ release_firmware(chip_spec->clm_fw);
+ chip_spec->clm_fw = NULL;
+#if IS_BUILTIN(CONFIG_MMC)
+ sdio_claim_host(bus->sdiodev->func1);
+ mmc_hw_reset(bus->sdiodev->func1->card);
+ sdio_release_host(bus->sdiodev->func1);
+#endif
+ }
+
+ kfree(bus->rxbuf);
+ kfree(bus->hdrbuf);
+ kfree(bus);
+
+ inff_dbg(TRACE, "Disconnected\n");
+}
+
+void inff_sdio_wd_timer(struct inff_sdio *bus, bool active)
+{
+ /* Totally stop the timer */
+ if (!active && bus->wd_active) {
+ timer_delete_sync(&bus->timer);
+ bus->wd_active = false;
+ return;
+ }
+
+ /* don't start the wd until fw is loaded */
+ if (bus->sdiodev->state != INFF_SDIOD_DATA)
+ return;
+
+ if (active) {
+ if (!bus->wd_active) {
+ /* Create timer again when watchdog period is
+ * dynamically changed or in the first instance
+ */
+ bus->timer.expires = jiffies + INFF_WD_POLL;
+ add_timer(&bus->timer);
+ bus->wd_active = true;
+ } else {
+ /* Re arm the timer, at last watchdog period */
+ mod_timer(&bus->timer, jiffies + INFF_WD_POLL);
+ }
+ }
+}
+
+int inff_sdio_sleep(struct inff_sdio *bus, bool sleep)
+{
+ int ret;
+
+ sdio_claim_host(bus->sdiodev->func1);
+ ret = inff_sdio_bus_sleep(bus, sleep, false);
+ sdio_release_host(bus->sdiodev->func1);
+
+ return ret;
+}
+
+/* Check F2 Ready bit before sending data to Firmware */
+static int
+inff_sdio_f2_ready(struct inff_sdio *bus)
+{
+ int ret = -1;
+ int iordy_status = 0;
+
+ sdio_claim_host(bus->sdiodev->func1);
+ /* Read the status of IOR2 */
+ iordy_status = inff_sdiod_func0_rb(bus->sdiodev, SDIO_CCCR_IORx, NULL);
+
+ sdio_release_host(bus->sdiodev->func1);
+ ret = iordy_status & SDIO_FUNC_ENABLE_2;
+ return ret;
+}
+
+bool inff_sdio_credit_map_update(struct inff_bus *bus_if)
+{
+ struct inff_sdio_dev *sdiodev = bus_if->bus_priv.sdio;
+
+ return sdiodev->bus->credit_map_capable;
+}
+
+#define SDIO_FUNC1_BLOCKSIZE 64
+#define SDIO_FUNC2_BLOCKSIZE 512
+#define SDIO_ACW74XX_FUNC2_BLOCKSIZE 256
+
+/* Maximum milliseconds to wait for F2 to come up */
+#define SDIO_WAIT_F2RDY 3000
+
+static irqreturn_t inff_sdiod_oob_irqhandler(int irq, void *dev_id)
+{
+ struct inff_bus *bus_if = dev_get_drvdata(dev_id);
+ struct inff_sdio_dev *sdiodev = bus_if->bus_priv.sdio;
+
+ inff_dbg(INTR, "OOB intr triggered\n");
+
+ /* out-of-band interrupt is level-triggered which won't
+ * be cleared until dpc
+ */
+ if (sdiodev->irq_en) {
+ disable_irq_nosync(irq);
+ sdiodev->irq_en = false;
+ }
+
+ inff_sdio_isr(sdiodev->bus, true);
+
+ return IRQ_HANDLED;
+}
+
+/* interrupt handler for SDIO function 1 interrupt */
+static void inff_sdiod_ib_irqhandler(struct sdio_func *func)
+{
+ struct inff_bus *bus_if = dev_get_drvdata(&func->dev);
+ struct inff_sdio_dev *sdiodev = bus_if->bus_priv.sdio;
+
+ inff_dbg(INTR, "F%d IB intr triggered\n", func->num);
+
+ inff_sdio_isr(sdiodev->bus, false);
+}
+
+/* dummy handler for SDIO function 2 interrupt */
+static void inff_sdiod_dummy_irqhandler(struct sdio_func *func)
+{
+}
+
+int inff_sdiod_intr_register(struct inff_sdio_dev *sdiodev)
+{
+ struct inff_sdio_platform_data *pdata;
+ int ret = 0;
+ u8 data;
+
+ pdata = &sdiodev->settings->bus.sdio;
+ if (pdata->oob_irq_supported) {
+ inff_dbg(SDIO, "SDIO register OOB IRQ %d\n",
+ pdata->oob_irq_nr);
+ if (!sdiodev->oob_irq_requested) {
+ spin_lock_init(&sdiodev->irq_en_lock);
+ sdiodev->irq_en = true;
+
+ ret = request_irq(pdata->oob_irq_nr,
+ inff_sdiod_oob_irqhandler,
+ pdata->oob_irq_flags, "inff_oob_intr",
+ &sdiodev->func1->dev);
+ if (ret != 0) {
+ inff_err("request_irq failed %d\n", ret);
+ return ret;
+ }
+ sdiodev->oob_irq_requested = true;
+
+ ret = enable_irq_wake(pdata->oob_irq_nr);
+ if (ret != 0) {
+ inff_err("enable_irq_wake failed %d\n", ret);
+ return ret;
+ }
+ disable_irq_wake(pdata->oob_irq_nr);
+ }
+ sdio_claim_host(sdiodev->func1);
+
+ /* must configure SDIO_CCCR_IENx to enable irq */
+ data = inff_sdiod_func0_rb(sdiodev, SDIO_CCCR_IENx, &ret);
+ data |= SDIO_CCCR_IEN_FUNC1 | SDIO_CCCR_IEN_FUNC2 |
+ SDIO_CCCR_IEN_FUNC0;
+ inff_sdiod_func0_wb(sdiodev, SDIO_CCCR_IENx, data, &ret);
+
+ /* redirect, configure and enable io for interrupt signal */
+ data = SDIO_CCCR_INFF_SEPINT_MASK | SDIO_CCCR_INFF_SEPINT_OE;
+ if (pdata->oob_irq_flags & IRQF_TRIGGER_HIGH)
+ data |= SDIO_CCCR_INFF_SEPINT_ACT_HI;
+ inff_sdiod_func0_wb(sdiodev, SDIO_CCCR_INFF_SEPINT,
+ data, &ret);
+ sdio_release_host(sdiodev->func1);
+ } else {
+ sdio_claim_host(sdiodev->func1);
+ sdio_claim_irq(sdiodev->func1, inff_sdiod_ib_irqhandler);
+ sdio_claim_irq(sdiodev->func2, inff_sdiod_dummy_irqhandler);
+ sdio_release_host(sdiodev->func1);
+ sdiodev->sd_irq_requested = true;
+ }
+
+ return 0;
+}
+
+void inff_sdiod_intr_unregister(struct inff_sdio_dev *sdiodev)
+{
+ inff_dbg(SDIO, "SDIO unregister oob=%d sd=%d\n",
+ sdiodev->oob_irq_requested,
+ sdiodev->sd_irq_requested);
+
+ if (sdiodev->oob_irq_requested) {
+ struct inff_sdio_platform_data *pdata;
+
+ pdata = &sdiodev->settings->bus.sdio;
+ sdio_claim_host(sdiodev->func1);
+ inff_sdiod_func0_wb(sdiodev, SDIO_CCCR_INFF_SEPINT, 0, NULL);
+ inff_sdiod_func0_wb(sdiodev, SDIO_CCCR_IENx, 0, NULL);
+ sdio_release_host(sdiodev->func1);
+
+ sdiodev->oob_irq_requested = false;
+ free_irq(pdata->oob_irq_nr, &sdiodev->func1->dev);
+ sdiodev->irq_en = false;
+ sdiodev->oob_irq_requested = false;
+ }
+
+ if (sdiodev->sd_irq_requested) {
+ sdio_claim_host(sdiodev->func1);
+ sdio_release_irq(sdiodev->func2);
+ sdio_release_irq(sdiodev->func1);
+ sdio_release_host(sdiodev->func1);
+ sdiodev->sd_irq_requested = false;
+ }
+}
+
+void inff_sdiod_change_state(struct inff_sdio_dev *sdiodev,
+ enum inff_sdiod_state state)
+{
+ if (sdiodev->state == INFF_SDIOD_NOMEDIUM ||
+ state == sdiodev->state)
+ return;
+
+ inff_dbg(TRACE, "%d -> %d\n", sdiodev->state, state);
+ switch (sdiodev->state) {
+ case INFF_SDIOD_DATA:
+ /* any other state means bus interface is down */
+ inff_bus_change_state(sdiodev->bus_if, INFF_BUS_DOWN);
+ break;
+ case INFF_SDIOD_DOWN:
+ /* transition from DOWN to DATA means bus interface is up */
+ if (state == INFF_SDIOD_DATA)
+ inff_bus_change_state(sdiodev->bus_if, INFF_BUS_UP);
+ break;
+ default:
+ break;
+ }
+ sdiodev->state = state;
+}
+
+static int inff_sdiod_set_backplane_window(struct inff_sdio_dev *sdiodev,
+ u32 addr)
+{
+ u32 v, bar0 = addr & SBSDIO_SBWINDOW_MASK;
+ int err = 0, i;
+
+ if (sdiodev->bus->sleeping) {
+ if (!sdiodev->ignore_bus_error)
+ inff_err("ERROR: Write operation when bus is in sleep state\n");
+ return -EPERM;
+ }
+
+ if (sdiodev->sbwad_valid && bar0 == sdiodev->sbwad)
+ return 0;
+
+ v = bar0 >> 8;
+
+ for (i = 0 ; i < 3 && !err ; i++, v >>= 8)
+ inff_sdiod_writeb(sdiodev, SBSDIO_FUNC1_SBADDRLOW + i,
+ v & 0xff, &err);
+
+ if (!err) {
+ sdiodev->sbwad_valid = 1;
+ sdiodev->sbwad = bar0;
+ }
+
+ return err;
+}
+
+u32 inff_sdiod_readl(struct inff_sdio_dev *sdiodev, u32 addr, int *ret)
+{
+ u32 data = 0;
+ int retval;
+
+ inff_dbg(SDIOEXT, "addr 0x%x\n", addr);
+
+ if (sdiodev->bus->sleeping) {
+ if (!sdiodev->ignore_bus_error)
+ inff_err("ERROR: Read operation when bus is in sleep state\n");
+ if (ret)
+ *ret = -EPERM;
+ return data;
+ }
+
+ retval = inff_sdiod_set_backplane_window(sdiodev, addr);
+ if (retval)
+ goto out;
+
+ inff_dbg(SDIO, "reading from addr 0x%x bar0 0x%08x ", addr, sdiodev->sbwad);
+
+ addr &= SBSDIO_SB_OFT_ADDR_MASK;
+ addr |= SBSDIO_SB_ACCESS_2_4B_FLAG;
+
+ data = sdio_readl(sdiodev->func1, addr, &retval);
+
+ if (retval)
+ data = 0;
+
+ inff_dbg(SDIO, "data 0x%08x\n", data);
+out:
+ if (ret)
+ *ret = retval;
+
+ return data;
+}
+
+void inff_sdiod_writel(struct inff_sdio_dev *sdiodev, u32 addr,
+ u32 data, int *ret)
+{
+ int retval;
+
+ inff_dbg(SDIOEXT, "addr 0x%x val 0x%x\n", addr, data);
+
+ if (sdiodev->bus->sleeping) {
+ if (!sdiodev->ignore_bus_error)
+ inff_err("ERROR: Write operation when bus is in sleep state\n");
+ if (ret)
+ *ret = -EPERM;
+ return;
+ }
+
+ retval = inff_sdiod_set_backplane_window(sdiodev, addr);
+ if (retval)
+ goto out;
+
+ inff_dbg(SDIO, "writing 0x%08x to addr 0x%x bar0 0x%08x\n", data, addr, sdiodev->sbwad);
+
+ addr &= SBSDIO_SB_OFT_ADDR_MASK;
+ addr |= SBSDIO_SB_ACCESS_2_4B_FLAG;
+
+ sdio_writel(sdiodev->func1, data, addr, &retval);
+
+out:
+ if (ret)
+ *ret = retval;
+}
+
+static int inff_sdiod_skbuff_read(struct inff_sdio_dev *sdiodev,
+ struct sdio_func *func, u32 addr,
+ struct sk_buff *skb)
+{
+ unsigned int req_sz;
+ int err;
+
+ if (sdiodev->bus->sleeping) {
+ if (!sdiodev->ignore_bus_error)
+ inff_err("ERROR: Read operation when bus is in sleep state\n");
+ return -EPERM;
+ }
+
+ /* Single skb use the standard mmc interface */
+ req_sz = skb->len + 3;
+ req_sz &= (uint)~3;
+
+ switch (func->num) {
+ case SDIO_FUNC_1:
+ err = sdio_memcpy_fromio(func, ((u8 *)(skb->data)), addr,
+ req_sz);
+ break;
+ case SDIO_FUNC_2:
+ case SDIO_FUNC_3:
+ err = sdio_readsb(func, ((u8 *)(skb->data)), addr, req_sz);
+ break;
+ default:
+ /* bail out as things are really fishy here */
+ WARN(1, "invalid sdio function number: %d\n", func->num);
+ err = -ENOMEDIUM;
+ }
+
+ if (err == -ENOMEDIUM)
+ inff_sdiod_change_state(sdiodev, INFF_SDIOD_NOMEDIUM);
+
+ return err;
+}
+
+static int inff_sdiod_skbuff_write(struct inff_sdio_dev *sdiodev,
+ struct sdio_func *func, u32 addr,
+ struct sk_buff *skb)
+{
+ unsigned int req_sz;
+ int err;
+
+ if (sdiodev->bus->sleeping) {
+ if (!sdiodev->ignore_bus_error)
+ inff_err("ERROR: Write operation when bus is in sleep state\n");
+ return -EPERM;
+ }
+
+ /* Single skb use the standard mmc interface */
+ req_sz = skb->len + 3;
+ req_sz &= (uint)~3;
+
+ if (func->num == SDIO_FUNC_1 || func->num == SDIO_FUNC_2)
+ err = sdio_memcpy_toio(func, addr, ((u8 *)(skb->data)), req_sz);
+ else if (func->num == SDIO_FUNC_3)
+ err = sdio_writesb(func, addr, ((u8 *)(skb->data)), req_sz);
+ else
+ return -EINVAL;
+
+ if (err == -ENOMEDIUM)
+ inff_sdiod_change_state(sdiodev, INFF_SDIOD_NOMEDIUM);
+
+ return err;
+}
+
+static int mmc_submit_one(struct mmc_data *md, struct mmc_request *mr,
+ struct mmc_command *mc, int sg_cnt, int req_sz,
+ int func_blk_sz, u32 *addr,
+ struct inff_sdio_dev *sdiodev,
+ struct sdio_func *func, int write)
+{
+ int ret;
+
+ if (sdiodev->bus->sleeping) {
+ if (!sdiodev->ignore_bus_error)
+ inff_err("ERROR: %s operation when bus is in sleep state\n",
+ write ? "Write" : "Read");
+ return -EPERM;
+ }
+
+ md->sg_len = sg_cnt;
+ md->blocks = req_sz / func_blk_sz;
+ mc->arg |= (*addr & 0x1FFFF) << 9; /* address */
+ mc->arg |= md->blocks & 0x1FF; /* block count */
+ /* incrementing addr for function 1 */
+ if (func->num == SDIO_FUNC_1)
+ *addr += req_sz;
+
+ mmc_set_data_timeout(md, func->card);
+ mmc_wait_for_req(func->card->host, mr);
+
+ ret = mc->error ? mc->error : md->error;
+ if (ret == -ENOMEDIUM) {
+ inff_sdiod_change_state(sdiodev, INFF_SDIOD_NOMEDIUM);
+ } else if (ret != 0) {
+ inff_err("CMD53 sg block %s failed %d\n",
+ write ? "write" : "read", ret);
+ ret = -EIO;
+ }
+
+ return ret;
+}
+
+/**
+ * inff_sdiod_sglist_rw - SDIO interface function for block data access
+ * @sdiodev: inffmac sdio device
+ * @func: SDIO function
+ * @write: direction flag
+ * @addr: dongle memory address as source/destination
+ * @pktlist: skb buffer head pointer
+ *
+ * This function takes the respbonsibility as the interface function to MMC
+ * stack for block data access. It assumes that the skb passed down by the
+ * caller has already been padded and aligned.
+ */
+static int inff_sdiod_sglist_rw(struct inff_sdio_dev *sdiodev,
+ struct sdio_func *func,
+ bool write, u32 addr,
+ struct sk_buff_head *pktlist)
+{
+ unsigned int req_sz, func_blk_sz, sg_cnt, sg_data_sz, pkt_offset;
+ unsigned int max_req_sz, src_offset, dst_offset;
+ unsigned char *pkt_data, *orig_data, *dst_data;
+ struct sk_buff_head local_list, *target_list;
+ struct sk_buff *pkt_next = NULL, *src;
+ unsigned short max_seg_cnt;
+ struct mmc_request mmc_req;
+ struct mmc_command mmc_cmd;
+ struct mmc_data mmc_dat;
+ struct scatterlist *sgl;
+ int ret = 0;
+
+ if (!pktlist->qlen)
+ return -EINVAL;
+
+ target_list = pktlist;
+ /* for host with broken sg support, prepare a page aligned list */
+ __skb_queue_head_init(&local_list);
+ if (!write && sdiodev->settings->bus.sdio.broken_sg_support) {
+ req_sz = 0;
+ skb_queue_walk(pktlist, pkt_next)
+ req_sz += pkt_next->len;
+ req_sz = ALIGN(req_sz, func->cur_blksize);
+ while (req_sz > PAGE_SIZE) {
+ pkt_next = inff_pkt_buf_get_skb(PAGE_SIZE);
+ if (!pkt_next) {
+ ret = -ENOMEM;
+ goto exit;
+ }
+ __skb_queue_tail(&local_list, pkt_next);
+ req_sz -= PAGE_SIZE;
+ }
+ pkt_next = inff_pkt_buf_get_skb(req_sz);
+ if (!pkt_next) {
+ ret = -ENOMEM;
+ goto exit;
+ }
+ __skb_queue_tail(&local_list, pkt_next);
+ target_list = &local_list;
+ }
+
+ func_blk_sz = func->cur_blksize;
+ max_req_sz = sdiodev->max_request_size;
+ max_seg_cnt = min_t(unsigned short, sdiodev->max_segment_count,
+ target_list->qlen);
+
+ memset(&mmc_req, 0, sizeof(struct mmc_request));
+ memset(&mmc_cmd, 0, sizeof(struct mmc_command));
+ memset(&mmc_dat, 0, sizeof(struct mmc_data));
+
+ mmc_dat.sg = sdiodev->sgtable.sgl;
+ mmc_dat.blksz = func_blk_sz;
+ mmc_dat.flags = write ? MMC_DATA_WRITE : MMC_DATA_READ;
+ mmc_cmd.opcode = SD_IO_RW_EXTENDED;
+ mmc_cmd.arg = write ? 1 << 31 : 0; /* write flag */
+ mmc_cmd.arg |= (func->num & 0x7) << 28; /* SDIO func num */
+ mmc_cmd.arg |= 1 << 27; /* block mode */
+ /* for function 1 the addr will be incremented */
+ mmc_cmd.arg |= (func->num == SDIO_FUNC_1) ? 1 << 26 : 0;
+ mmc_cmd.flags = MMC_RSP_SPI_R5 | MMC_RSP_R5 | MMC_CMD_ADTC;
+ mmc_req.cmd = &mmc_cmd;
+ mmc_req.data = &mmc_dat;
+
+ req_sz = 0;
+ sg_cnt = 0;
+ sgl = sdiodev->sgtable.sgl;
+ skb_queue_walk(target_list, pkt_next) {
+ pkt_offset = 0;
+ while (pkt_offset < pkt_next->len) {
+ pkt_data = pkt_next->data + pkt_offset;
+ sg_data_sz = pkt_next->len - pkt_offset;
+ if (sg_data_sz > sdiodev->max_segment_size)
+ sg_data_sz = sdiodev->max_segment_size;
+ if (sg_data_sz > max_req_sz - req_sz)
+ sg_data_sz = max_req_sz - req_sz;
+
+ if (!sgl) {
+ /* out of (pre-allocated) scatterlist entries */
+ ret = -ENOMEM;
+ goto exit;
+ }
+ sg_set_buf(sgl, pkt_data, sg_data_sz);
+ sg_cnt++;
+
+ sgl = sg_next(sgl);
+ req_sz += sg_data_sz;
+ pkt_offset += sg_data_sz;
+ if (req_sz >= max_req_sz || sg_cnt >= max_seg_cnt) {
+ ret = mmc_submit_one(&mmc_dat, &mmc_req, &mmc_cmd,
+ sg_cnt, req_sz, func_blk_sz,
+ &addr, sdiodev, func, write);
+ if (ret)
+ goto exit_queue_walk;
+ req_sz = 0;
+ sg_cnt = 0;
+ sgl = sdiodev->sgtable.sgl;
+ }
+ }
+ }
+ if (sg_cnt)
+ ret = mmc_submit_one(&mmc_dat, &mmc_req, &mmc_cmd,
+ sg_cnt, req_sz, func_blk_sz,
+ &addr, sdiodev, func, write);
+exit_queue_walk:
+ if (!write && sdiodev->settings->bus.sdio.broken_sg_support) {
+ src = __skb_peek(&local_list);
+ src_offset = 0;
+ skb_queue_walk(pktlist, pkt_next) {
+ dst_offset = 0;
+
+ /* This is safe because we must have enough SKB data
+ * in the local list to cover everything in pktlist.
+ */
+ while (1) {
+ req_sz = pkt_next->len - dst_offset;
+ if (req_sz > src->len - src_offset)
+ req_sz = src->len - src_offset;
+
+ orig_data = src->data + src_offset;
+ dst_data = pkt_next->data + dst_offset;
+ memcpy(dst_data, orig_data, req_sz);
+
+ src_offset += req_sz;
+ if (src_offset == src->len) {
+ src_offset = 0;
+ src = skb_peek_next(src, &local_list);
+ }
+ dst_offset += req_sz;
+ if (dst_offset == pkt_next->len)
+ break;
+ }
+ }
+ }
+
+exit:
+ sg_init_table(sdiodev->sgtable.sgl, sdiodev->sgtable.orig_nents);
+ while ((pkt_next = __skb_dequeue(&local_list)) != NULL)
+ inff_pkt_buf_free_skb(pkt_next);
+
+ return ret;
+}
+
+int inff_sdiod_recv_buf(struct inff_sdio_dev *sdiodev, u8 fn,
+ u8 *buf, uint nbytes)
+{
+ struct sk_buff *mypkt = NULL;
+ int err = 0;
+
+ mypkt = inff_pkt_buf_get_skb(nbytes);
+ if (!mypkt) {
+ inff_err("inff_pkt_buf_get_skb failed: len %d\n",
+ nbytes);
+ return -EIO;
+ }
+
+ err = inff_sdiod_recv_pkt(sdiodev, fn, mypkt);
+ if (!err)
+ memcpy(buf, mypkt->data, nbytes);
+
+ inff_pkt_buf_free_skb(mypkt);
+ return err;
+}
+
+int inff_sdiod_recv_pkt(struct inff_sdio_dev *sdiodev, u8 fn,
+ struct sk_buff *pkt)
+{
+ struct sdio_func *func = NULL;
+ u32 base_addr = 0;
+ u32 recv_addr = 0;
+ int err = 0;
+
+ if (fn == SDIO_FUNC_2) {
+ /* F2 is only DMA. HW ignore the address field in the cmd53 /cmd52. */
+ base_addr = sdiodev->cc_core->base;
+ recv_addr = base_addr & SBSDIO_SB_OFT_ADDR_MASK;
+ recv_addr |= SBSDIO_SB_ACCESS_2_4B_FLAG;
+ func = sdiodev->func2;
+ } else if (fn == SDIO_FUNC_3) {
+ /* F3 has registers and DMA. A DMA access is identified using the
+ * address value 0x0. If the address field has any other value, it
+ * won't be considered as F3 packet transfer. If the address corresponds
+ * to a valid F3 register address, driver will get proper response,
+ * otherwise driver will get error response.
+ */
+ base_addr = 0;
+ recv_addr = 0;
+ func = sdiodev->func3;
+ } else {
+ inff_err("invalid function number: %d\n", fn);
+ return -EINVAL;
+ }
+
+ err = inff_sdiod_set_backplane_window(sdiodev, base_addr);
+ if (err)
+ goto done;
+
+ err = inff_sdiod_skbuff_read(sdiodev, func, recv_addr, pkt);
+
+ inff_dbg(DATA, "F%d, base addr: 0x%x, recv addr: 0x%x, size: %d, err: %d\n",
+ fn, base_addr, recv_addr, pkt->len, err);
+done:
+ return err;
+}
+
+int inff_sdiod_recv_chain(struct inff_sdio_dev *sdiodev,
+ struct sk_buff_head *pktq, uint totlen)
+{
+ struct sk_buff *glom_skb = NULL;
+ struct sk_buff *skb;
+ u32 addr = sdiodev->cc_core->base;
+ int err = 0;
+
+ inff_dbg(SDIO, "addr = 0x%x, size = %d\n",
+ addr, pktq->qlen);
+
+ err = inff_sdiod_set_backplane_window(sdiodev, addr);
+ if (err)
+ goto done;
+
+ addr &= SBSDIO_SB_OFT_ADDR_MASK;
+ addr |= SBSDIO_SB_ACCESS_2_4B_FLAG;
+
+ if (pktq->qlen == 1) {
+ err = inff_sdiod_skbuff_read(sdiodev, sdiodev->func2, addr,
+ __skb_peek(pktq));
+ } else if (!sdiodev->sg_support) {
+ glom_skb = inff_pkt_buf_get_skb(totlen);
+ if (!glom_skb)
+ return -ENOMEM;
+ err = inff_sdiod_skbuff_read(sdiodev, sdiodev->func2, addr,
+ glom_skb);
+ if (err)
+ goto done;
+
+ skb_queue_walk(pktq, skb) {
+ memcpy(skb->data, glom_skb->data, skb->len);
+ skb_pull(glom_skb, skb->len);
+ }
+ } else {
+ err = inff_sdiod_sglist_rw(sdiodev, sdiodev->func2, false,
+ addr, pktq);
+ }
+
+done:
+ inff_pkt_buf_free_skb(glom_skb);
+ return err;
+}
+
+int inff_sdiod_send_buf(struct inff_sdio_dev *sdiodev, u8 fn,
+ u8 *buf, uint nbytes)
+{
+ struct sk_buff *mypkt = NULL;
+ struct sdio_func *func = NULL;
+ u32 base_addr = 0;
+ u32 send_addr = 0;
+ int err = 0;
+
+ if (fn == 2) {
+ /* F2 is only DMA. HW ignore the address field in the cmd53 /cmd52. */
+ base_addr = sdiodev->cc_core->base;
+ send_addr = base_addr & SBSDIO_SB_OFT_ADDR_MASK;
+ send_addr |= SBSDIO_SB_ACCESS_2_4B_FLAG;
+ func = sdiodev->func2;
+ } else if (fn == 3) {
+ /* F3 has registers and DMA. A DMA access is identified using the
+ * address value 0x0. If the address field has any other value, it
+ * won't be considered as F3 packet transfer. If the address corresponds
+ * to a valid F3 register address, driver will get proper response,
+ * otherwise driver will get error response.
+ */
+ base_addr = 0;
+ send_addr = 0;
+ func = sdiodev->func3;
+ } else {
+ inff_err("invalid function number: %d\n", fn);
+ return -EINVAL;
+ }
+
+ mypkt = inff_pkt_buf_get_skb(nbytes);
+
+ if (!mypkt) {
+ inff_err("inff_pkt_buf_get_skb failed: len %d\n",
+ nbytes);
+ return -EIO;
+ }
+
+ memcpy(mypkt->data, buf, nbytes);
+
+ err = inff_sdiod_set_backplane_window(sdiodev, base_addr);
+ if (err)
+ goto out;
+
+ err = inff_sdiod_skbuff_write(sdiodev, func, send_addr, mypkt);
+
+ inff_dbg(DATA, "F%d, base addr: 0x%x, send addr: 0x%x, size: %d, err: %d\n",
+ fn, base_addr, send_addr, mypkt->len, err);
+out:
+ inff_pkt_buf_free_skb(mypkt);
+
+ return err;
+}
+
+int inff_sdiod_send_pkt(struct inff_sdio_dev *sdiodev,
+ struct sk_buff_head *pktq)
+{
+ struct sk_buff *skb;
+ u32 addr = sdiodev->cc_core->base;
+ int err;
+
+ inff_dbg(SDIO, "addr = 0x%x, size = %d\n", addr, pktq->qlen);
+
+ err = inff_sdiod_set_backplane_window(sdiodev, addr);
+ if (err)
+ return err;
+
+ addr &= SBSDIO_SB_OFT_ADDR_MASK;
+ addr |= SBSDIO_SB_ACCESS_2_4B_FLAG;
+
+ if (pktq->qlen == 1 || !sdiodev->sg_support) {
+ skb_queue_walk(pktq, skb) {
+ err = inff_sdiod_skbuff_write(sdiodev, sdiodev->func2,
+ addr, skb);
+ if (err)
+ break;
+ }
+ } else {
+ err = inff_sdiod_sglist_rw(sdiodev, sdiodev->func2, true,
+ addr, pktq);
+ }
+
+ return err;
+}
+
+int
+inff_sdiod_ramrw(struct inff_sdio_dev *sdiodev, bool write, u32 address,
+ u8 *data, uint size)
+{
+ int err = 0;
+ struct sk_buff *pkt;
+ u32 sdaddr;
+ uint dsize;
+
+ dsize = min_t(uint, SBSDIO_SB_OFT_ADDR_LIMIT, size);
+ pkt = __dev_alloc_skb(dsize, GFP_KERNEL);
+ if (!pkt) {
+ inff_err("dev_alloc_skb failed: len %d\n", dsize);
+ return -EIO;
+ }
+ pkt->priority = 0;
+
+ /* Determine initial transfer parameters */
+ sdaddr = address & SBSDIO_SB_OFT_ADDR_MASK;
+ if ((sdaddr + size) & SBSDIO_SBWINDOW_MASK)
+ dsize = (SBSDIO_SB_OFT_ADDR_LIMIT - sdaddr);
+ else
+ dsize = size;
+
+ sdio_claim_host(sdiodev->func1);
+
+ /* Do the transfer(s) */
+ while (size) {
+ /* Set the backplane window to include the start address */
+ err = inff_sdiod_set_backplane_window(sdiodev, address);
+ if (err)
+ break;
+
+ inff_dbg(SDIO, "%s %d bytes at offset 0x%08x in window 0x%08x\n",
+ write ? "write" : "read", dsize,
+ sdaddr, address & SBSDIO_SBWINDOW_MASK);
+
+ sdaddr &= SBSDIO_SB_OFT_ADDR_MASK;
+ sdaddr |= SBSDIO_SB_ACCESS_2_4B_FLAG;
+
+ skb_put(pkt, dsize);
+
+ if (write) {
+ memcpy(pkt->data, data, dsize);
+ err = inff_sdiod_skbuff_write(sdiodev, sdiodev->func1,
+ sdaddr, pkt);
+ } else {
+ err = inff_sdiod_skbuff_read(sdiodev, sdiodev->func1,
+ sdaddr, pkt);
+ }
+
+ if (err) {
+ inff_err("membytes transfer failed write=%d err=%d\n", write, err);
+ break;
+ }
+ if (!write)
+ memcpy(data, pkt->data, dsize);
+ skb_trim(pkt, 0);
+
+ /* Adjust for next transfer (if any) */
+ size -= dsize;
+ if (size) {
+ data += dsize;
+ address += dsize;
+ sdaddr = 0;
+ dsize = min_t(uint, SBSDIO_SB_OFT_ADDR_LIMIT, size);
+ }
+ }
+
+ dev_kfree_skb(pkt);
+
+ sdio_release_host(sdiodev->func1);
+
+ return err;
+}
+
+int inff_sdiod_abort(struct inff_sdio_dev *sdiodev, struct sdio_func *func)
+{
+ /* Issue abort cmd52 command through F0 */
+ inff_sdiod_func0_wb(sdiodev, SDIO_CCCR_ABORT, func->num, NULL);
+ return 0;
+}
+
+#define INFF_SDIO_DEFAULT_GLOM_SIZE 32 /* max tx frames in glom chain */
+
+void inff_sdiod_sgtable_alloc(struct inff_sdio_dev *sdiodev)
+{
+ struct sdio_func *func;
+ struct mmc_host *host;
+ uint max_blocks;
+ uint nents;
+ int err;
+
+ func = sdiodev->func2;
+ host = func->card->host;
+ sdiodev->sg_support = host->max_segs > 1;
+ max_blocks = min_t(uint, host->max_blk_count, 511u);
+ sdiodev->max_request_size = min_t(uint, host->max_req_size,
+ max_blocks * func->cur_blksize);
+ sdiodev->max_segment_count = min_t(uint, host->max_segs,
+ SG_MAX_SINGLE_ALLOC);
+ sdiodev->max_segment_size = host->max_seg_size;
+
+ if (!sdiodev->sg_support)
+ return;
+
+ nents = INFF_SDIO_DEFAULT_GLOM_SIZE * 2;
+
+ WARN_ON(nents > sdiodev->max_segment_count);
+
+ inff_dbg(TRACE, "nents=%d\n", nents);
+ err = sg_alloc_table(&sdiodev->sgtable, nents, GFP_KERNEL);
+ if (err < 0) {
+ inff_err("allocation failed: disable scatter-gather");
+ sdiodev->sg_support = false;
+ }
+
+ sdiodev->txglomsz = INFF_SDIO_DEFAULT_GLOM_SIZE;
+}
+
+static int inff_sdiod_freezer_attach(struct inff_sdio_dev *sdiodev)
+{
+ if (!IS_ENABLED(CONFIG_PM_SLEEP))
+ return 0;
+
+ sdiodev->freezer = kzalloc_obj(*sdiodev->freezer);
+ if (!sdiodev->freezer)
+ return -ENOMEM;
+ atomic_set(&sdiodev->freezer->thread_count, 0);
+ atomic_set(&sdiodev->freezer->freezing, 0);
+ init_waitqueue_head(&sdiodev->freezer->thread_freeze);
+ init_completion(&sdiodev->freezer->resumed);
+ return 0;
+}
+
+static void inff_sdiod_freezer_detach(struct inff_sdio_dev *sdiodev)
+{
+ if (sdiodev->freezer) {
+ WARN_ON(atomic_read(&sdiodev->freezer->freezing));
+ kfree(sdiodev->freezer);
+ sdiodev->freezer = NULL;
+ }
+}
+
+static int inff_sdiod_freezer_on(struct inff_sdio_dev *sdiodev)
+{
+ atomic_t *expect = &sdiodev->freezer->thread_count;
+ int res = 0;
+
+ sdiodev->freezer->frozen_count = 0;
+ reinit_completion(&sdiodev->freezer->resumed);
+ atomic_set(&sdiodev->freezer->freezing, 1);
+ inff_sdio_trigger_dpc(sdiodev->bus);
+ wait_event(sdiodev->freezer->thread_freeze,
+ atomic_read(expect) == sdiodev->freezer->frozen_count);
+ sdio_claim_host(sdiodev->func1);
+ res = inff_sdio_sleep(sdiodev->bus, true);
+ sdio_release_host(sdiodev->func1);
+ return res;
+}
+
+static void inff_sdiod_freezer_off(struct inff_sdio_dev *sdiodev)
+{
+ sdio_claim_host(sdiodev->func1);
+ inff_sdio_sleep(sdiodev->bus, false);
+ sdio_release_host(sdiodev->func1);
+ atomic_set(&sdiodev->freezer->freezing, 0);
+ complete_all(&sdiodev->freezer->resumed);
+}
+
+bool inff_sdiod_freezing(struct inff_sdio_dev *sdiodev)
+{
+ return IS_ENABLED(CONFIG_PM_SLEEP) &&
+ atomic_read(&sdiodev->freezer->freezing);
+}
+
+void inff_sdiod_try_freeze(struct inff_sdio_dev *sdiodev)
+{
+ if (!inff_sdiod_freezing(sdiodev))
+ return;
+ sdiodev->freezer->frozen_count++;
+ wake_up(&sdiodev->freezer->thread_freeze);
+ wait_for_completion(&sdiodev->freezer->resumed);
+}
+
+void inff_sdiod_freezer_count(struct inff_sdio_dev *sdiodev)
+{
+ if (IS_ENABLED(CONFIG_PM_SLEEP))
+ atomic_inc(&sdiodev->freezer->thread_count);
+}
+
+void inff_sdiod_freezer_uncount(struct inff_sdio_dev *sdiodev)
+{
+ if (IS_ENABLED(CONFIG_PM_SLEEP))
+ atomic_dec(&sdiodev->freezer->thread_count);
+}
+
+int inff_sdiod_remove(struct inff_sdio_dev *sdiodev)
+{
+ sdiodev->state = INFF_SDIOD_DOWN;
+ if (sdiodev->bus) {
+ inff_sdio_remove(sdiodev->bus);
+ sdiodev->bus = NULL;
+ }
+
+ inff_sdiod_freezer_detach(sdiodev);
+
+ /* Disable functions 2 then 1. */
+ sdio_claim_host(sdiodev->func1);
+ sdio_disable_func(sdiodev->func2);
+ sdio_disable_func(sdiodev->func1);
+ sdio_release_host(sdiodev->func1);
+
+ sg_free_table(&sdiodev->sgtable);
+ sdiodev->sbwad = 0;
+ sdiodev->sbwad_valid = 0;
+
+ pm_runtime_allow(sdiodev->func1->card->host->parent);
+ return 0;
+}
+
+static void inff_sdiod_host_fixup(struct mmc_host *host)
+{
+ /* runtime-pm powers off the device */
+ pm_runtime_forbid(host->parent);
+ /* avoid removal detection upon resume */
+ host->caps |= MMC_CAP_NONREMOVABLE;
+}
+
+int inff_sdiod_probe(struct inff_sdio_dev *sdiodev)
+{
+ int ret = 0;
+ unsigned int f2_blksz = SDIO_FUNC2_BLOCKSIZE;
+
+ sdio_claim_host(sdiodev->func1);
+
+ ret = sdio_set_block_size(sdiodev->func1, SDIO_FUNC1_BLOCKSIZE);
+ if (ret) {
+ inff_err("Failed to set F1 blocksize\n");
+ sdio_release_host(sdiodev->func1);
+ return ret;
+ }
+ switch (sdiodev->func2->device) {
+ case SDIO_DEVICE_ID_CYPRESS_CYW55900:
+ f2_blksz = SDIO_INF55900_FUNC2_BLOCKSIZE;
+ break;
+ case SDIO_DEVICE_ID_CYPRESS_ACW7413:
+ f2_blksz = SDIO_ACW74XX_FUNC2_BLOCKSIZE;
+ break;
+ default:
+ break;
+ }
+
+ ret = sdio_set_block_size(sdiodev->func2, f2_blksz);
+ if (ret) {
+ inff_err("Failed to set F2 blocksize\n");
+ sdio_release_host(sdiodev->func1);
+ return ret;
+ }
+ inff_dbg(SDIO, "set F2 blocksize to %d\n", f2_blksz);
+
+ /* increase F2 timeout */
+ sdiodev->func2->enable_timeout = SDIO_WAIT_F2RDY;
+
+ /* Enable Function 1 */
+ ret = sdio_enable_func(sdiodev->func1);
+ sdio_release_host(sdiodev->func1);
+ if (ret) {
+ inff_err("Failed to enable F1: err=%d\n", ret);
+ goto out;
+ }
+
+ ret = inff_sdiod_freezer_attach(sdiodev);
+ if (ret)
+ goto out;
+
+ /* try to attach to the target device */
+ ret = inff_sdio_probe(sdiodev);
+ if (ret)
+ goto out;
+
+ inff_sdiod_host_fixup(sdiodev->func2->card->host);
+out:
+ if (ret)
+ inff_sdiod_remove(sdiodev);
+
+ return ret;
+}
+
+#define INFF_SDIO_DEVICE(dev_id) \
+ {SDIO_DEVICE(SDIO_VENDOR_ID_CYPRESS, dev_id)}
+
+/* devices we support, null terminated */
+static const struct sdio_device_id inff_sdmmc_ids[] = {
+ INFF_SDIO_DEVICE(SDIO_DEVICE_ID_CYPRESS_CYW55900),
+ INFF_SDIO_DEVICE(SDIO_DEVICE_ID_CYPRESS_ACW7413),
+ { /* end: all zeroes */ }
+};
+MODULE_DEVICE_TABLE(sdio, inff_sdmmc_ids);
+
+static int inff_ops_sdio_probe(struct sdio_func *func,
+ const struct sdio_device_id *id)
+{
+ int err;
+ struct inff_sdio_dev *sdiodev;
+ struct inff_bus *bus_if;
+
+ if (!id) {
+ dev_err(&func->dev, "Error no sdio_device_id passed for %x:%x\n",
+ func->vendor, func->device);
+ return -ENODEV;
+ }
+
+ inff_dbg(SDIO, "Class=%x\n", func->class);
+ inff_dbg(SDIO, "sdio vendor ID: 0x%04x\n", func->vendor);
+ inff_dbg(SDIO, "sdio device ID: 0x%04x\n", func->device);
+ inff_dbg(SDIO, "Function#: %d\n", func->num);
+ /* Consume func num 1 but dont do anything with it. */
+ if (func->num == SDIO_FUNC_1 || func->num == SDIO_FUNC_3)
+ return 0;
+
+ /* Set MMC_QUIRK_LENIENT_FN0 for this card */
+ func->card->quirks |= MMC_QUIRK_LENIENT_FN0;
+
+ /* Set MMC_QUIRK_BLKSZ_FOR_BYTE_MODE for this card
+ * Use func->cur_blksize by default
+ */
+ func->card->quirks |= MMC_QUIRK_BLKSZ_FOR_BYTE_MODE;
+
+ /* Ignore anything but func 2 */
+ if (func->num != SDIO_FUNC_2)
+ return -ENODEV;
+
+ bus_if = kzalloc_obj(*bus_if);
+ if (!bus_if)
+ return -ENOMEM;
+ sdiodev = kzalloc_obj(*sdiodev);
+ if (!sdiodev) {
+ kfree(bus_if);
+ return -ENOMEM;
+ }
+
+ /* store refs to functions used. mmc_card does
+ * not hold the F0 function pointer.
+ */
+ sdiodev->func1 = func->card->sdio_func[0];
+ sdiodev->func2 = func;
+ sdiodev->func3 = func->card->sdio_func[2];
+
+ sdiodev->bus_if = bus_if;
+ bus_if->bus_priv.sdio = sdiodev;
+
+ switch (sdiodev->func2->device) {
+ case SDIO_DEVICE_ID_CYPRESS_CYW55900:
+ bus_if->proto_type = INFF_PROTO_ICDC;
+ break;
+ case SDIO_DEVICE_ID_CYPRESS_ACW7413:
+ bus_if->proto_type = INFF_PROTO_BCDC;
+ break;
+ default:
+ inff_err("Unsupported SDIO Device %d\n",
+ sdiodev->func2->device);
+ goto fail;
+ }
+
+ dev_set_drvdata(&func->dev, bus_if);
+ dev_set_drvdata(&sdiodev->func1->dev, bus_if);
+ sdiodev->dev = &sdiodev->func1->dev;
+ dev_set_drvdata(&sdiodev->func2->dev, bus_if);
+ if (sdiodev->func3) {
+ inff_dbg(SDIO, "Set F3 dev\n");
+ dev_set_drvdata(&sdiodev->func3->dev, bus_if);
+ }
+
+ inff_sdiod_change_state(sdiodev, INFF_SDIOD_DOWN);
+
+ inff_dbg(SDIO, "F2 found, calling inff_sdiod_probe...\n");
+ err = inff_sdiod_probe(sdiodev);
+ if (err) {
+ inff_err("F2 error, probe failed %d...\n", err);
+ goto fail;
+ }
+
+ inff_dbg(SDIO, "SDIO probing completed...\n");
+ return 0;
+
+fail:
+ dev_set_drvdata(&func->dev, NULL);
+ dev_set_drvdata(&sdiodev->func1->dev, NULL);
+ dev_set_drvdata(&sdiodev->func2->dev, NULL);
+ kfree(sdiodev);
+ kfree(bus_if);
+ return err;
+}
+
+static void inff_ops_sdio_remove(struct sdio_func *func)
+{
+ struct inff_bus *bus_if;
+ struct inff_sdio_dev *sdiodev;
+
+ inff_dbg(SDIO, "sdio vendor ID: 0x%04x\n", func->vendor);
+ inff_dbg(SDIO, "sdio device ID: 0x%04x\n", func->device);
+ inff_dbg(SDIO, "Function: %d\n", func->num);
+
+ bus_if = dev_get_drvdata(&func->dev);
+ if (bus_if) {
+ sdiodev = bus_if->bus_priv.sdio;
+
+ /* start by unregistering irqs */
+ inff_sdiod_intr_unregister(sdiodev);
+
+ if (func->num != SDIO_FUNC_1)
+ return;
+
+ /* only proceed with rest of cleanup if func 1 */
+ inff_sdiod_remove(sdiodev);
+
+ dev_set_drvdata(&sdiodev->func1->dev, NULL);
+ dev_set_drvdata(&sdiodev->func2->dev, NULL);
+ if (sdiodev->func3) {
+ inff_dbg(SDIO, "Remove F3 dev\n");
+ dev_set_drvdata(&sdiodev->func3->dev, NULL);
+ }
+
+ kfree(bus_if);
+ kfree(sdiodev);
+ }
+}
+
+static int inff_ops_sdio_suspend(struct device *dev)
+{
+ struct sdio_func *func;
+ struct inff_bus *bus_if;
+ struct inff_sdio_dev *sdiodev;
+ mmc_pm_flag_t sdio_flags;
+ struct inff_cfg80211_info *config;
+ int retry = INFF_PM_WAIT_MAXRETRY;
+ bool cap_power_off;
+ int ret = 0;
+
+ func = container_of(dev, struct sdio_func, dev);
+
+ cap_power_off = !!(func->card->host->caps & MMC_CAP_POWER_OFF_CARD);
+
+ bus_if = dev_get_drvdata(dev);
+ config = bus_if->drvr->config;
+
+ inff_dbg(SDIO, "SDIO suspend: F%d\n", func->num);
+
+ while (retry &&
+ config->pm_state == INFF_CFG80211_PM_STATE_SUSPENDING) {
+ usleep_range(10000, 20000);
+ retry--;
+ }
+ if (!retry && config->pm_state == INFF_CFG80211_PM_STATE_SUSPENDING)
+ inff_err("timed out wait for cfg80211 suspended\n");
+
+ if (func->num != SDIO_FUNC_1)
+ return 0;
+
+ sdiodev = bus_if->bus_priv.sdio;
+
+ if (!cap_power_off) {
+ inff_sdiod_freezer_on(sdiodev);
+ inff_sdio_wd_timer(sdiodev->bus, 0);
+
+ sdio_flags = MMC_PM_KEEP_POWER;
+ if (sdio_set_host_pm_flags(sdiodev->func1, sdio_flags))
+ inff_err("Failed to set pm_flags %x\n", sdio_flags);
+
+ } else {
+ /* power will be cut so remove device, probe again in resume */
+ inff_sdiod_intr_unregister(sdiodev);
+ ret = inff_sdiod_remove(sdiodev);
+ if (ret)
+ inff_err("Failed to remove device on suspend\n");
+ }
+
+ return ret;
+}
+
+static int inff_ops_sdio_resume(struct device *dev)
+{
+ struct inff_bus *bus_if = dev_get_drvdata(dev);
+ struct inff_sdio_dev *sdiodev = bus_if->bus_priv.sdio;
+ struct sdio_func *func = container_of(dev, struct sdio_func, dev);
+ bool cap_power_off = !!(func->card->host->caps & MMC_CAP_POWER_OFF_CARD);
+ int ret = 0;
+
+ inff_dbg(SDIO, "SDIO resume: F%d\n", func->num);
+
+ if (func->num != SDIO_FUNC_2)
+ return 0;
+
+ if (cap_power_off) {
+ /* bus was powered off and device removed, probe again */
+ ret = inff_sdiod_probe(sdiodev);
+ if (ret)
+ inff_err("Failed to probe device on resume\n");
+ } else {
+ inff_sdiod_freezer_off(sdiodev);
+ }
+
+ return ret;
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(inff_sdio_pm_ops,
+ inff_ops_sdio_suspend,
+ inff_ops_sdio_resume);
+
+static struct sdio_driver inff_sdmmc_driver = {
+ .probe = inff_ops_sdio_probe,
+ .remove = inff_ops_sdio_remove,
+ .name = KBUILD_MODNAME,
+ .id_table = inff_sdmmc_ids,
+ .drv = {
+ .pm = pm_sleep_ptr(&inff_sdio_pm_ops),
+ },
+};
+
+int inff_sdio_register(void)
+{
+ return sdio_register_driver(&inff_sdmmc_driver);
+}
+
+void inff_sdio_exit(void)
+{
+ sdio_unregister_driver(&inff_sdmmc_driver);
+}
diff --git a/drivers/net/wireless/infineon/inffmac/sdio.h b/drivers/net/wireless/infineon/inffmac/sdio.h
new file mode 100644
index 000000000000..86a0da406f50
--- /dev/null
+++ b/drivers/net/wireless/infineon/inffmac/sdio.h
@@ -0,0 +1,234 @@
+/* SPDX-License-Identifier: ISC */
+/*
+ * Copyright (c) 2010 Broadcom Corporation
+ *
+ * Copyright (c) 2025-2026, Infineon Technologies AG, or an affiliate of Infineon Technologies AG.
+ * All rights reserved.
+ */
+
+#ifndef INFF_SDIO_H
+#define INFF_SDIO_H
+
+#include <linux/skbuff.h>
+#include <linux/firmware.h>
+#include "firmware.h"
+
+/**
+ * enum inff_sdiod_state - the state of the bus.
+ *
+ * @INFF_SDIOD_DOWN: Device can be accessed, no DPC.
+ * @INFF_SDIOD_DATA: Ready for data transfers, DPC enabled.
+ * @INFF_SDIOD_NOMEDIUM: No medium access to dongle possible.
+ */
+enum inff_sdiod_state {
+ INFF_SDIOD_DOWN,
+ INFF_SDIOD_DATA,
+ INFF_SDIOD_NOMEDIUM
+};
+
+struct inff_sdiod_freezer {
+ atomic_t freezing;
+ atomic_t thread_count;
+ u32 frozen_count;
+ wait_queue_head_t thread_freeze;
+ struct completion resumed;
+};
+
+struct inff_sdio_dev {
+ struct sdio_func *func1;
+ struct sdio_func *func2;
+ struct sdio_func *func3;
+ u32 sbwad; /* Save backplane window address */
+ bool sbwad_valid; /* Save backplane window address */
+ struct inff_core *cc_core; /* chipcommon core info struct */
+ struct inff_sdio *bus;
+ struct device *dev;
+ struct inff_bus *bus_if;
+ struct inff_mp_device *settings;
+ bool oob_irq_requested;
+ bool sd_irq_requested;
+ bool irq_en; /* irq enable flags */
+ spinlock_t irq_en_lock;
+ bool sg_support;
+ uint max_request_size;
+ ushort max_segment_count;
+ uint max_segment_size;
+ uint txglomsz;
+ struct sg_table sgtable;
+ enum inff_sdiod_state state;
+ struct inff_sdiod_freezer *freezer;
+ bool redownload_fw;
+ bool ignore_bus_error; /* Ignore SDIO Bus access error*/
+};
+
+/* Register/deregister interrupt handler. */
+int inff_sdiod_intr_register(struct inff_sdio_dev *sdiodev);
+void inff_sdiod_intr_unregister(struct inff_sdio_dev *sdiodev);
+
+/* SDIO device register access interface */
+/* Accessors for SDIO Function 0 */
+#define inff_sdiod_func0_rb_ext(sdiodev, addr, r) \
+ sdio_f0_readb((sdiodev)->func1, (addr), (r))
+
+#define inff_sdiod_func0_wb_ext(sdiodev, addr, v, ret) \
+ sdio_f0_writeb((sdiodev)->func1, (v), (addr), (ret))
+
+/* Accessors for SDIO Function 1 */
+#define inff_sdiod_readb_ext(sdiodev, addr, r) \
+ sdio_readb((sdiodev)->func1, (addr), (r))
+
+#define inff_sdiod_writeb_ext(sdiodev, addr, v, ret) \
+ sdio_writeb((sdiodev)->func1, (v), (addr), (ret))
+
+/* Accessors for SDIO specific function number */
+#define inff_sdiod_func_rb_ext(func, addr, r) \
+ sdio_readb((func), (addr), (r))
+
+#define inff_sdiod_func_wb_ext(func, addr, v, ret) \
+ sdio_writeb((func), (v), (addr), (ret))
+
+u8 inff_sdiod_func0_rb(struct inff_sdio_dev *sdiodev, u32 addr, int *ret);
+void inff_sdiod_func0_wb(struct inff_sdio_dev *sdiodev, u32 addr, u32 data,
+ int *ret);
+u8 inff_sdiod_readb(struct inff_sdio_dev *sdiodev, u32 addr, int *ret);
+void inff_sdiod_writeb(struct inff_sdio_dev *sdiodev, u32 addr, u32 data,
+ int *ret);
+u8 inff_sdiod_func_rb(struct inff_sdio_dev *sdiodev, struct sdio_func *func, u32 addr, int *ret);
+void inff_sdiod_func_wb(struct inff_sdio_dev *sdiodev, struct sdio_func *func, u32 addr,
+ u32 data, int *ret);
+
+u32 inff_sdiod_readl(struct inff_sdio_dev *sdiodev, u32 addr, int *ret);
+void inff_sdiod_writel(struct inff_sdio_dev *sdiodev, u32 addr, u32 data,
+ int *ret);
+
+/* Buffer transfer to/from device (client) core via cmd53.
+ * fn: function number
+ * flags: backplane width, address increment, sync/async
+ * buf: pointer to memory data buffer
+ * nbytes: number of bytes to transfer to/from buf
+ * pkt: pointer to packet associated with buf (if any)
+ * complete: callback function for command completion (async only)
+ * handle: handle for completion callback (first arg in callback)
+ * Returns 0 or error code.
+ * NOTE: Async operation is not currently supported.
+ */
+int inff_sdiod_send_pkt(struct inff_sdio_dev *sdiodev,
+ struct sk_buff_head *pktq);
+int inff_sdiod_send_buf(struct inff_sdio_dev *sdiodev, u8 fn,
+ u8 *buf, uint nbytes);
+
+int inff_sdiod_recv_pkt(struct inff_sdio_dev *sdiodev, u8 fn,
+ struct sk_buff *pkt);
+int inff_sdiod_recv_buf(struct inff_sdio_dev *sdiodev, u8 fn,
+ u8 *buf, uint nbytes);
+int inff_sdiod_recv_chain(struct inff_sdio_dev *sdiodev,
+ struct sk_buff_head *pktq, uint totlen);
+
+/* Read/write to memory block (F1, no FIFO) via CMD53 (sync only).
+ * rw: read or write (0/1)
+ * addr: direct SDIO address
+ * buf: pointer to memory data buffer
+ * nbytes: number of bytes to transfer to/from buf
+ * Returns 0 or error code.
+ */
+int inff_sdiod_ramrw(struct inff_sdio_dev *sdiodev, bool write, u32 address,
+ u8 *data, uint size);
+
+/* Issue an abort to the specified function */
+int inff_sdiod_abort(struct inff_sdio_dev *sdiodev, struct sdio_func *func);
+
+void inff_sdiod_sgtable_alloc(struct inff_sdio_dev *sdiodev);
+void inff_sdiod_change_state(struct inff_sdio_dev *sdiodev,
+ enum inff_sdiod_state state);
+bool inff_sdiod_freezing(struct inff_sdio_dev *sdiodev);
+void inff_sdiod_try_freeze(struct inff_sdio_dev *sdiodev);
+void inff_sdiod_freezer_count(struct inff_sdio_dev *sdiodev);
+void inff_sdiod_freezer_uncount(struct inff_sdio_dev *sdiodev);
+
+int inff_sdiod_probe(struct inff_sdio_dev *sdiodev);
+int inff_sdiod_remove(struct inff_sdio_dev *sdiodev);
+
+int inff_sdio_probe(struct inff_sdio_dev *sdiodev);
+void inff_sdio_remove(struct inff_sdio *bus);
+void inff_sdio_isr(struct inff_sdio *bus, bool in_isr);
+
+void inff_sdio_wd_timer(struct inff_sdio *bus, bool active);
+int inff_sdio_sleep(struct inff_sdio *bus, bool sleep);
+int inff_sdio_set_sdbus_clk_width(struct inff_sdio *bus, unsigned int flags);
+int inff_sdio_clkctl(struct inff_sdio *bus, uint target, bool pendok);
+void inff_sdio_trigger_dpc(struct inff_sdio *bus);
+bool inff_sdio_credit_map_update(struct inff_bus *bus_if);
+
+#define D11_BASE_ADDR 0x18001000
+#define D11_AXI_BASE_ADDR 0xE8000000
+#define D11_SHM_BASE_ADDR (D11_AXI_BASE_ADDR + 0x4000)
+
+#define D11REG_ADDR(offset) (D11_BASE_ADDR + (offset))
+#define D11IHR_ADDR(offset) (D11_AXI_BASE_ADDR + 0x400 + (2 * (offset)))
+#define D11SHM_ADDR(offset) (D11_SHM_BASE_ADDR + (offset))
+
+/* MacControl register */
+#define D11_MACCONTROL_REG D11REG_ADDR(0x120)
+#define D11_MACCONTROL_REG_WAKE 0x4000000
+
+/* HUDI Sequence SHM bits */
+#define C_DS1_CTRL_SDIO_DS1_SLEEP 0x1
+#define C_DS1_CTRL_SDIO_MAC_ON 0x2
+#define C_DS1_CTRL_SDIO_RADIO_PHY_ON 0x4
+#define C_DS1_CTRL_SDIO_DS1_EXIT 0x8
+#define C_DS1_CTRL_PROC_DONE 0x100
+#define C_DS1_CTRL_REQ_VALID 0x200
+
+#define PHYTX_ERR_BLK_SIZE 18
+#define D11SHM_FIRST2BYTE_MASK 0xFFFF0000
+#define D11SHM_SECOND2BYTE_MASK 0x0000FFFF
+#define D11SHM_2BYTE_SHIFT 16
+
+#define D11SHM_RD(sdh, offset, ret) \
+ inff_sdiod_readl(sdh, D11SHM_ADDR(offset), ret)
+
+/* SHM Read is motified based on SHM 4 byte alignment as SHM size is 2 bytes and
+ * 2 byte is currently not working on FMAC
+ * If SHM address is not 4 byte aligned, then right shift by 16
+ * otherwise, mask the first two MSB bytes
+ * Suppose data in address 7260 is 0x440002 and it is 4 byte aligned
+ * Correct SHM value is 0x2 for this SHM offset and next SHM value is 0x44
+ */
+#define D11SHM_RDW(sdh, offset, ret) ({ \
+ typeof(sdh) _sdh = (sdh); \
+ typeof(offset) _offset = (offset); \
+ typeof(ret) _ret = (ret); \
+ ((_offset % 4) ? \
+ (inff_sdiod_readl(_sdh, D11SHM_ADDR(_offset), _ret) \
+ >> D11SHM_2BYTE_SHIFT) : \
+ (inff_sdiod_readl(_sdh, D11SHM_ADDR(_offset), _ret) \
+ & D11SHM_SECOND2BYTE_MASK)); \
+ })
+
+/* SHM is of size 2 bytes, 4 bytes write will overwrite other SHM's
+ * First read 4 bytes and then clear the required two bytes based on
+ * 4 byte alignment, then update the required value and write the
+ * 4 byte value now
+ */
+#define D11SHM_WR(sdh, offset, val, mask, ret) \
+ do { \
+ typeof(offset) _offet = (offset); \
+ typeof(val) _val = (val); \
+ typeof(mask) _mask = (mask); \
+ if ((_offet) % 4) \
+ _val = (_val & D11SHM_SECOND2BYTE_MASK) | \
+ ((_mask) << D11SHM_2BYTE_SHIFT); \
+ else \
+ _val = (_mask) | (_val & D11SHM_FIRST2BYTE_MASK); \
+ inff_sdiod_writel(sdh, D11SHM_ADDR(_offet), _val, ret); \
+ } while (0)
+#define D11REG_WR(sdh, addr, val, ret) \
+ inff_sdiod_writel(sdh, addr, val, ret)
+
+#define D11REG_RD(sdh, addr, ret) \
+ inff_sdiod_readl(sdh, addr, ret)
+
+void inff_sdio_exit(void);
+int inff_sdio_register(void);
+
+#endif /* INFF_SDIO_H */
diff --git a/include/linux/mmc/sdio_ids.h b/include/linux/mmc/sdio_ids.h
index afae93f6f245..655fad820dc4 100644
--- a/include/linux/mmc/sdio_ids.h
+++ b/include/linux/mmc/sdio_ids.h
@@ -81,6 +81,8 @@
#define SDIO_VENDOR_ID_CYPRESS 0x04b4
#define SDIO_DEVICE_ID_BROADCOM_CYPRESS_43439 0xbd3d
+#define SDIO_DEVICE_ID_CYPRESS_CYW55900 0xbd40
+#define SDIO_DEVICE_ID_CYPRESS_ACW7413 0xbd42
#define SDIO_VENDOR_ID_MARVELL 0x02df
#define SDIO_DEVICE_ID_MARVELL_LIBERTAS 0x9103
--
2.43.0
^ permalink raw reply related [flat|nested] 26+ messages in thread* [PATCH wireless-next v3 18/25] wifi: inffmac: add fwsignal.c/h
2026-09-02 16:50 [PATCH wireless-next v3 00/25] wifi: inffmac: introducing a driver for Infineon's new generation chipsets Gokul Sivakumar
` (16 preceding siblings ...)
2026-09-02 16:50 ` [PATCH wireless-next v3 17/25] wifi: inffmac: add sdio.c/h Gokul Sivakumar
@ 2026-09-02 16:50 ` Gokul Sivakumar
2026-09-02 16:50 ` [PATCH wireless-next v3 19/25] wifi: inffmac: add bcdc.c/h Gokul Sivakumar
` (6 subsequent siblings)
24 siblings, 0 replies; 26+ messages in thread
From: Gokul Sivakumar @ 2026-09-02 16:50 UTC (permalink / raw)
To: linux-wireless, johannes
Cc: arend.vanspriel, wlan-kernel-dev-list, gokulkumar.sivakumar
Implementation of the firmware signalling used for flow control during
Data transmission between Host Driver and Device firmware via the SDIO bus.
Signed-off-by: Gokul Sivakumar <gokulkumar.sivakumar@infineon.com>
---
.../net/wireless/infineon/inffmac/fwsignal.c | 3017 +++++++++++++++++
.../net/wireless/infineon/inffmac/fwsignal.h | 53 +
2 files changed, 3070 insertions(+)
create mode 100644 drivers/net/wireless/infineon/inffmac/fwsignal.c
create mode 100644 drivers/net/wireless/infineon/inffmac/fwsignal.h
diff --git a/drivers/net/wireless/infineon/inffmac/fwsignal.c b/drivers/net/wireless/infineon/inffmac/fwsignal.c
new file mode 100644
index 000000000000..637b50bd6499
--- /dev/null
+++ b/drivers/net/wireless/infineon/inffmac/fwsignal.c
@@ -0,0 +1,3017 @@
+// SPDX-License-Identifier: ISC
+/*
+ * Copyright (c) 2010 Broadcom Corporation
+ *
+ * Copyright (c) 2025-2026, Infineon Technologies AG, or an affiliate of Infineon Technologies AG.
+ * All rights reserved.
+ */
+#include <linux/types.h>
+#include <linux/module.h>
+#include <linux/if_ether.h>
+#include <linux/spinlock.h>
+#include <linux/skbuff.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/err.h>
+#include <linux/jiffies.h>
+#include <net/cfg80211.h>
+
+#include "main.h"
+#include "net.h"
+#include "utils.h"
+#include "chan.h"
+#include "debug.h"
+#include "dev_cmd.h"
+#include "dev_evt.h"
+#include "fwsignal.h"
+#include "cfg80211.h"
+#include "bus_proto.h"
+#include "bcdc.h"
+#include "chip.h"
+#include "sdio.h"
+
+/**
+ * DOC: Firmware Signalling
+ *
+ * Firmware can send signals to host and vice versa, which are passed in the
+ * data packets using TLV based header. This signalling layer is on top of the
+ * BDC bus protocol layer.
+ */
+/*
+ * enum inff_fws_tlv_type - definition of tlv identifiers.
+ */
+enum inff_fws_tlv_type {
+ INFF_FWS_TYPE_MAC_OPEN = 1,
+ INFF_FWS_TYPE_MAC_CLOSE = 2,
+ INFF_FWS_TYPE_MAC_REQUEST_CREDIT = 3,
+ INFF_FWS_TYPE_TXSTATUS = 4,
+ INFF_FWS_TYPE_PKTTAG = 5,
+ INFF_FWS_TYPE_MACDESC_ADD = 6,
+ INFF_FWS_TYPE_MACDESC_DEL = 7,
+ INFF_FWS_TYPE_RSSI = 8,
+ INFF_FWS_TYPE_INTERFACE_OPEN = 9,
+ INFF_FWS_TYPE_INTERFACE_CLOSE = 10,
+ INFF_FWS_TYPE_FIFO_CREDITBACK = 11,
+ INFF_FWS_TYPE_PENDING_TRAFFIC_BMP = 12,
+ INFF_FWS_TYPE_MAC_REQUEST_PACKET = 13,
+ INFF_FWS_TYPE_HOST_REORDER_RXPKTS = 14,
+ INFF_FWS_TYPE_TRANS_ID = 18,
+ INFF_FWS_TYPE_COMP_TXSTATUS = 19,
+ INFF_FWS_TYPE_FILLER = 255,
+ INFF_FWS_TYPE_INVALID
+};
+
+#define INFF_FWS_TYPE_FIFO_CREDITBACK_V2_LEN 12
+#define INFF_FIFO_CREDITBACK_TX_OFFSET 6
+
+/*
+ * enum inff_fws_tlv_len - definition of tlv lengths.
+ */
+enum inff_fws_tlv_len {
+ INFF_FWS_TYPE_MAC_OPEN_LEN = 1,
+ INFF_FWS_TYPE_MAC_CLOSE_LEN = 1,
+ INFF_FWS_TYPE_MAC_REQUEST_CREDIT_LEN = 2,
+ INFF_FWS_TYPE_TXSTATUS_LEN = 4,
+ INFF_FWS_TYPE_PKTTAG_LEN = 4,
+ INFF_FWS_TYPE_MACDESC_ADD_LEN = 8,
+ INFF_FWS_TYPE_MACDESC_DEL_LEN = 8,
+ INFF_FWS_TYPE_RSSI_LEN = 1,
+ INFF_FWS_TYPE_INTERFACE_OPEN_LEN = 1,
+ INFF_FWS_TYPE_INTERFACE_CLOSE_LEN = 1,
+ INFF_FWS_TYPE_FIFO_CREDITBACK_LEN = 6,
+ INFF_FWS_TYPE_PENDING_TRAFFIC_BMP_LEN = 2,
+ INFF_FWS_TYPE_MAC_REQUEST_PACKET_LEN = 3,
+ INFF_FWS_TYPE_HOST_REORDER_RXPKTS_LEN = 10,
+ INFF_FWS_TYPE_TRANS_ID_LEN = 6,
+ INFF_FWS_TYPE_COMP_TXSTATUS_LEN = 1,
+ INFF_FWS_TYPE_FILLER_LEN = 0
+};
+
+/* AMPDU rx reordering definitions */
+#define INFF_RXREORDER_FLOWID_OFFSET 0
+#define INFF_RXREORDER_MAXIDX_OFFSET 2
+#define INFF_RXREORDER_FLAGS_OFFSET 4
+#define INFF_RXREORDER_CURIDX_OFFSET 6
+#define INFF_RXREORDER_EXPIDX_OFFSET 8
+
+#define INFF_RXREORDER_DEL_FLOW 0x01
+#define INFF_RXREORDER_FLUSH_ALL 0x02
+#define INFF_RXREORDER_CURIDX_VALID 0x04
+#define INFF_RXREORDER_EXPIDX_VALID 0x08
+#define INFF_RXREORDER_NEW_HOLE 0x10
+
+#ifdef DEBUG
+/*
+ * inff_fws_tlv_names - array of tlv names.
+ */
+static struct {
+ enum inff_fws_tlv_type id;
+ const char *name;
+} inff_fws_tlv_names[] = {
+ {1, "MAC_OPEN"},
+ {2, "MAC_CLOSE"},
+ {3, "MAC_REQUEST_CREDIT"},
+ {4, "TXSTATUS"},
+ {5, "PKTTAG"},
+ {6, "MACDESC_ADD"},
+ {7, "MACDESC_DEL"},
+ {8, "RSSI"},
+ {9, "INTERFACE_OPEN"},
+ {10, "INTERFACE_CLOSE"},
+ {11, "FIFO_CREDITBACK"},
+ {12, "PENDING_TRAFFIC_BMP"},
+ {13, "MAC_REQUEST_PACKET"},
+ {14, "HOST_REORDER_RXPKTS"},
+ {18, "TRANS_ID"},
+ {19, "COMP_TXSTATUS"},
+ {255, "FILLER"}
+};
+
+static const char *inff_fws_get_tlv_name(enum inff_fws_tlv_type id)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(inff_fws_tlv_names); i++)
+ if (inff_fws_tlv_names[i].id == id)
+ return inff_fws_tlv_names[i].name;
+
+ return "INVALID";
+}
+#else
+static const char *inff_fws_get_tlv_name(enum inff_fws_tlv_type id)
+{
+ return "NODEBUG";
+}
+#endif /* DEBUG */
+
+/*
+ * The PKTTAG tlv has additional bytes when firmware-signalling
+ * mode has REUSESEQ flag set.
+ */
+#define INFF_FWS_TYPE_SEQ_LEN 2
+
+/*
+ * flags used to enable tlv signalling from firmware.
+ */
+#define INFF_FWS_FLAGS_RSSI_SIGNALS 0x0001
+#define INFF_FWS_FLAGS_XONXOFF_SIGNALS 0x0002
+#define INFF_FWS_FLAGS_CREDIT_STATUS_SIGNALS 0x0004
+#define INFF_FWS_FLAGS_HOST_PROPTXSTATUS_ACTIVE 0x0008
+#define INFF_FWS_FLAGS_PSQ_GENERATIONFSM_ENABLE 0x0010
+#define INFF_FWS_FLAGS_PSQ_ZERO_BUFFER_ENABLE 0x0020
+#define INFF_FWS_FLAGS_HOST_RXREORDER_ACTIVE 0x0040
+
+#define INFF_FWS_MAC_DESC_TABLE_SIZE 32
+#define INFF_FWS_MAC_DESC_ID_INVALID 0xff
+
+#define INFF_FWS_HOSTIF_FLOWSTATE_OFF 0
+#define INFF_FWS_HOSTIF_FLOWSTATE_ON 1
+#define INFF_FWS_FLOWCONTROL_HIWATER ((256 * 8) - 256)
+#define INFF_FWS_FLOWCONTROL_LOWATER 256
+
+#define INFF_FWS_PSQ_PREC_COUNT ((INFF_FWS_FIFO_COUNT + 1) * 2)
+#define INFF_FWS_PSQ_LEN (256 * 8)
+#define INFF_FWS_AFQ_PREC_COUNT (INFF_FWS_FIFO_COUNT + 1)
+#define INFF_FWS_AFQ_LEN (4096 * 8)
+
+#define INFF_FWS_HTOD_FLAG_PKTFROMHOST 0x01
+#define INFF_FWS_HTOD_FLAG_PKT_REQUESTED 0x02
+
+#define INFF_FWS_RET_OK_NOSCHEDULE 0
+#define INFF_FWS_RET_OK_SCHEDULE 1
+
+#define INFF_FWS_MODE_HANGER 1 /* use hanger */
+#define INFF_FWS_MODE_AFQ 2 /* use afq */
+#define INFF_FWS_MODE_IS_OLD_DEF(x) (((x) & 3U) != 0)
+
+#define INFF_FWS_MODE_AFQ_SHIFT 2
+#define INFF_FWS_MODE_GET_AFQ(x) (((x) >> INFF_FWS_MODE_AFQ_SHIFT) & 1)
+
+#define INFF_FWS_MODE_REUSESEQ_SHIFT 3 /* seq reuse */
+#define INFF_FWS_MODE_SET_REUSESEQ(x, val) { \
+ typeof(x) _x = (x); \
+ ((_x) = \
+ ((_x) & ~(1 << INFF_FWS_MODE_REUSESEQ_SHIFT)) | \
+ (((val) & 1) << INFF_FWS_MODE_REUSESEQ_SHIFT)); \
+ }
+#define INFF_FWS_MODE_GET_REUSESEQ(x) \
+ (((x) >> INFF_FWS_MODE_REUSESEQ_SHIFT) & 1)
+
+#define INFF_FWS_MODE_REORDERSUPP_SHIFT 4
+#define INFF_FWS_MODE_GET_REORDERSUPP(x) (((x) >> INFF_FWS_MODE_REORDERSUPP_SHIFT) & 1)
+
+static inline u32 inff_fws_mode_set_afq(u32 mode, u8 val)
+{
+ return (mode & ~(1 << INFF_FWS_MODE_AFQ_SHIFT)) |
+ ((val & 1) << INFF_FWS_MODE_AFQ_SHIFT);
+}
+
+static inline u32 inff_fws_mode_set_reordersupp(u32 mode, u8 val)
+{
+ return (mode & ~(1 << INFF_FWS_MODE_REORDERSUPP_SHIFT)) |
+ ((val & 1) << INFF_FWS_MODE_REORDERSUPP_SHIFT);
+}
+
+/**
+ * enum inff_fws_skb_state - indicates processing state of skb.
+ *
+ * @INFF_FWS_SKBSTATE_NEW: sk_buff is newly arrived in the driver.
+ * @INFF_FWS_SKBSTATE_DELAYED: sk_buff had to wait on queue.
+ * @INFF_FWS_SKBSTATE_SUPPRESSED: sk_buff has been suppressed by firmware.
+ * @INFF_FWS_SKBSTATE_TIM: allocated for TIM update info.
+ */
+enum inff_fws_skb_state {
+ INFF_FWS_SKBSTATE_NEW,
+ INFF_FWS_SKBSTATE_DELAYED,
+ INFF_FWS_SKBSTATE_SUPPRESSED,
+ INFF_FWS_SKBSTATE_TIM
+};
+
+/**
+ * struct inff_skbuff_cb - control buffer associated with skbuff.
+ *
+ * @bus_flags: 2 bytes reserved for bus specific parameters
+ * @if_flags: holds interface index and packet related flags.
+ * @htod: host to device packet identifier (used in PKTTAG tlv).
+ * @htod_seq: this 16-bit is original seq number for every suppress packet.
+ * @state: transmit state of the packet.
+ * @mac: descriptor related to destination for this packet.
+ *
+ * This information is stored in control buffer struct sk_buff::cb, which
+ * provides 48 bytes of storage so this structure should not exceed that.
+ */
+struct inff_skbuff_cb {
+ u16 bus_flags;
+ u16 if_flags;
+ u32 htod;
+ u16 htod_seq;
+ enum inff_fws_skb_state state;
+ struct inff_fws_mac_descriptor *mac;
+};
+
+/*
+ * macro casting skbuff control buffer to struct inff_skbuff_cb.
+ */
+#define inff_skbcb(skb) ((struct inff_skbuff_cb *)((skb)->cb))
+
+/*
+ * sk_buff control if flags
+ *
+ * b[11] - packet sent upon firmware request.
+ * b[10] - packet only contains signalling data.
+ * b[9] - packet is a tx packet.
+ * b[8] - packet used requested credit
+ * b[7] - interface in AP mode.
+ * b[3:0] - interface index.
+ */
+#define INFF_SKB_IF_FLAGS_REQUESTED_MASK 0x0800
+#define INFF_SKB_IF_FLAGS_REQUESTED_SHIFT 11
+#define INFF_SKB_IF_FLAGS_SIGNAL_ONLY_MASK 0x0400
+#define INFF_SKB_IF_FLAGS_SIGNAL_ONLY_SHIFT 10
+#define INFF_SKB_IF_FLAGS_TRANSMIT_MASK 0x0200
+#define INFF_SKB_IF_FLAGS_TRANSMIT_SHIFT 9
+#define INFF_SKB_IF_FLAGS_REQ_CREDIT_MASK 0x0100
+#define INFF_SKB_IF_FLAGS_REQ_CREDIT_SHIFT 8
+#define INFF_SKB_IF_FLAGS_IF_AP_MASK 0x0080
+#define INFF_SKB_IF_FLAGS_IF_AP_SHIFT 7
+#define INFF_SKB_IF_FLAGS_INDEX_MASK 0x000f
+#define INFF_SKB_IF_FLAGS_INDEX_SHIFT 0
+
+#define inff_skb_if_flags_set_field(skb, field, value) \
+ inff_maskset16(&(inff_skbcb(skb)->if_flags), \
+ INFF_SKB_IF_FLAGS_ ## field ## _MASK, \
+ INFF_SKB_IF_FLAGS_ ## field ## _SHIFT, (value))
+#define inff_skb_if_flags_get_field(skb, field) \
+ inff_maskget16(inff_skbcb(skb)->if_flags, \
+ INFF_SKB_IF_FLAGS_ ## field ## _MASK, \
+ INFF_SKB_IF_FLAGS_ ## field ## _SHIFT)
+
+/*
+ * sk_buff control packet identifier
+ *
+ * 32-bit packet identifier used in PKTTAG tlv from host to dongle.
+ *
+ * - Generated at the host driver
+ * - Seen as a generic sequence number by firmware except for the flags field.
+ *
+ * Generation : b[31] => generation number for this packet [host->fw]
+ * OR, current generation number [fw->host]
+ * Flags : b[30:27] => command, status flags
+ * FIFO-AC : b[26:24] => AC-FIFO id
+ * h-slot : b[23:8] => hanger-slot
+ * freerun : b[7:0] => A free running counter
+ */
+#define INFF_SKB_HTOD_TAG_GENERATION_MASK 0x80000000
+#define INFF_SKB_HTOD_TAG_GENERATION_SHIFT 31
+#define INFF_SKB_HTOD_TAG_FLAGS_MASK 0x78000000
+#define INFF_SKB_HTOD_TAG_FLAGS_SHIFT 27
+#define INFF_SKB_HTOD_TAG_FIFO_MASK 0x07000000
+#define INFF_SKB_HTOD_TAG_FIFO_SHIFT 24
+#define INFF_SKB_HTOD_TAG_HSLOT_MASK 0x00ffff00
+#define INFF_SKB_HTOD_TAG_HSLOT_SHIFT 8
+#define INFF_SKB_HTOD_TAG_FREERUN_MASK 0x000000ff
+#define INFF_SKB_HTOD_TAG_FREERUN_SHIFT 0
+
+#define inff_skb_htod_tag_set_field(skb, field, value) \
+ inff_maskset32(&(inff_skbcb(skb)->htod), \
+ INFF_SKB_HTOD_TAG_ ## field ## _MASK, \
+ INFF_SKB_HTOD_TAG_ ## field ## _SHIFT, (value))
+#define inff_skb_htod_tag_get_field(skb, field) \
+ inff_maskget32(inff_skbcb(skb)->htod, \
+ INFF_SKB_HTOD_TAG_ ## field ## _MASK, \
+ INFF_SKB_HTOD_TAG_ ## field ## _SHIFT)
+
+#define INFF_SKB_HTOD_SEQ_FROMFW_MASK 0x2000
+#define INFF_SKB_HTOD_SEQ_FROMFW_SHIFT 13
+#define INFF_SKB_HTOD_SEQ_FROMDRV_MASK 0x1000
+#define INFF_SKB_HTOD_SEQ_FROMDRV_SHIFT 12
+#define INFF_SKB_HTOD_SEQ_NR_MASK 0x0fff
+#define INFF_SKB_HTOD_SEQ_NR_SHIFT 0
+
+#define inff_skb_htod_seq_set_field(skb, field, value) \
+ inff_maskset16(&(inff_skbcb(skb)->htod_seq), \
+ INFF_SKB_HTOD_SEQ_ ## field ## _MASK, \
+ INFF_SKB_HTOD_SEQ_ ## field ## _SHIFT, (value))
+#define inff_skb_htod_seq_get_field(skb, field) \
+ inff_maskget16(inff_skbcb(skb)->htod_seq, \
+ INFF_SKB_HTOD_SEQ_ ## field ## _MASK, \
+ INFF_SKB_HTOD_SEQ_ ## field ## _SHIFT)
+
+#define INFF_FWS_TXSTAT_GENERATION_MASK 0x80000000
+#define INFF_FWS_TXSTAT_GENERATION_SHIFT 31
+#define INFF_FWS_TXSTAT_FLAGS_MASK 0x78000000
+#define INFF_FWS_TXSTAT_FLAGS_SHIFT 27
+#define INFF_FWS_TXSTAT_FIFO_MASK 0x07000000
+#define INFF_FWS_TXSTAT_FIFO_SHIFT 24
+#define INFF_FWS_TXSTAT_HSLOT_MASK 0x00FFFF00
+#define INFF_FWS_TXSTAT_HSLOT_SHIFT 8
+#define INFF_FWS_TXSTAT_FREERUN_MASK 0x000000FF
+#define INFF_FWS_TXSTAT_FREERUN_SHIFT 0
+
+#define inff_txstatus_get_field(txs, field) \
+ inff_maskget32(txs, INFF_FWS_TXSTAT_ ## field ## _MASK, \
+ INFF_FWS_TXSTAT_ ## field ## _SHIFT)
+
+/* How long to defer borrowing in jiffies */
+#define INFF_FWS_BORROW_DEFER_PERIOD (HZ / 10)
+
+/**
+ * enum inff_fws_txstatus - txstatus flag values.
+ *
+ * @INFF_FWS_TXSTATUS_DISCARD:
+ * host is free to discard the packet.
+ * @INFF_FWS_TXSTATUS_CORE_SUPPRESS:
+ * 802.11 core suppressed the packet.
+ * @INFF_FWS_TXSTATUS_FW_PS_SUPPRESS:
+ * firmware suppress the packet as device is already in PS mode.
+ * @INFF_FWS_TXSTATUS_FW_TOSSED:
+ * firmware tossed the packet.
+ * @INFF_FWS_TXSTATUS_FW_DISCARD_NOACK:
+ * firmware tossed the packet after retries.
+ * @INFF_FWS_TXSTATUS_FW_SUPPRESS_ACKED:
+ * firmware wrongly reported suppressed previously, now fixing to acked.
+ * @INFF_FWS_TXSTATUS_FW_EXPIRED:
+ * firmware expired the packet.
+ * @INFF_FWS_TXSTATUS_FW_DROPPED:
+ * firmware dropped the packet.
+ * @INFF_FWS_TXSTATUS_FW_MKTFREE:
+ * firmware marked the packet free.
+ */
+enum inff_fws_txstatus {
+ INFF_FWS_TXSTATUS_DISCARD = 0,
+ INFF_FWS_TXSTATUS_CORE_SUPPRESS = 1,
+ INFF_FWS_TXSTATUS_FW_PS_SUPPRESS = 2,
+ INFF_FWS_TXSTATUS_FW_TOSSED = 3,
+ INFF_FWS_TXSTATUS_FW_DISCARD_NOACK = 4,
+ INFF_FWS_TXSTATUS_FW_SUPPRESS_ACKED = 5,
+ INFF_FWS_TXSTATUS_FW_EXPIRED = 6,
+ INFF_FWS_TXSTATUS_FW_DROPPED = 7,
+ INFF_FWS_TXSTATUS_FW_MKTFREE = 8
+};
+
+enum inff_fws_fcmode {
+ INFF_FWS_FCMODE_NONE,
+ INFF_FWS_FCMODE_IMPLIED_CREDIT,
+ INFF_FWS_FCMODE_EXPLICIT_CREDIT
+};
+
+enum inff_fws_mac_desc_state {
+ INFF_FWS_STATE_OPEN = 1,
+ INFF_FWS_STATE_CLOSE
+};
+
+/**
+ * struct inff_fws_mac_descriptor - firmware signalling data per node/interface
+ *
+ * @name: name of the descriptor.
+ * @occupied: slot is in use.
+ * @mac_handle: handle for mac entry determined by firmware.
+ * @interface_id: interface index.
+ * @state: current state.
+ * @suppressed: mac entry is suppressed.
+ * @generation: generation bit.
+ * @ac_bitmap: ac queue bitmap.
+ * @requested_credit: credits requested by firmware.
+ * @requested_packet: packet requested by firmware.
+ * @ea: ethernet address.
+ * @seq: per-node free-running sequence.
+ * @psq: power-save queue.
+ * @transit_count: packet in transit to firmware.
+ * @suppr_transit_count: suppressed packet in transit to firmware.
+ * @send_tim_signal: if set tim signal will be sent.
+ * @traffic_pending_bmp: traffic pending bitmap.
+ * @traffic_lastreported_bmp: traffic last reported bitmap.
+ */
+struct inff_fws_mac_descriptor {
+ char name[16];
+ u8 occupied;
+ u8 mac_handle;
+ u8 interface_id;
+ u8 state;
+ bool suppressed;
+ u8 generation;
+ u8 ac_bitmap;
+ u8 requested_credit;
+ u8 requested_packet;
+ u8 ea[ETH_ALEN];
+ u8 seq[INFF_FWS_FIFO_COUNT];
+ struct pktq psq;
+ int transit_count;
+ int suppr_transit_count;
+ bool send_tim_signal;
+ u8 traffic_pending_bmp;
+ u8 traffic_lastreported_bmp;
+ struct pktq afq;
+};
+
+#define INFF_FWS_HANGER_MAXITEMS 3072
+#define INFF_BORROW_RATIO 3
+
+/**
+ * enum inff_fws_hanger_item_state - state of hanger item.
+ *
+ * @INFF_FWS_HANGER_ITEM_STATE_FREE: item is free for use.
+ * @INFF_FWS_HANGER_ITEM_STATE_INUSE: item is in use.
+ * @INFF_FWS_HANGER_ITEM_STATE_INUSE_SUPPRESSED: item was suppressed.
+ */
+enum inff_fws_hanger_item_state {
+ INFF_FWS_HANGER_ITEM_STATE_FREE = 1,
+ INFF_FWS_HANGER_ITEM_STATE_INUSE,
+ INFF_FWS_HANGER_ITEM_STATE_INUSE_SUPPRESSED
+};
+
+/**
+ * struct inff_fws_hanger_item - single entry for tx pending packet.
+ *
+ * @state: entry is either free or occupied.
+ * @pkt: packet itself.
+ */
+struct inff_fws_hanger_item {
+ enum inff_fws_hanger_item_state state;
+ struct sk_buff *pkt;
+};
+
+/**
+ * struct inff_fws_hanger - holds packets awaiting firmware txstatus.
+ *
+ * @pushed: packets pushed to await txstatus.
+ * @popped: packets popped upon handling txstatus.
+ * @failed_to_push: packets that could not be pushed.
+ * @failed_to_pop: packets that could not be popped.
+ * @failed_slotfind: packets for which failed to find an entry.
+ * @slot_pos: last returned item index for a free entry.
+ * @items: array of hanger items.
+ */
+struct inff_fws_hanger {
+ u32 pushed;
+ u32 popped;
+ u32 failed_to_push;
+ u32 failed_to_pop;
+ u32 failed_slotfind;
+ u32 slot_pos;
+ struct inff_fws_hanger_item items[INFF_FWS_HANGER_MAXITEMS];
+};
+
+struct inff_fws_macdesc_table {
+ struct inff_fws_mac_descriptor nodes[INFF_FWS_MAC_DESC_TABLE_SIZE];
+ struct inff_fws_mac_descriptor iface[INFF_MAX_IFS];
+ struct inff_fws_mac_descriptor other;
+};
+
+struct inff_ohd_creditmap {
+ u8 num_acs;
+ u8 fifo_credit[INFF_FWS_FIFO_COUNT];
+};
+
+struct inff_fws_stats {
+ u32 tlv_parse_failed;
+ u32 tlv_invalid_type;
+ u32 header_only_pkt;
+ u32 header_pulls;
+ u32 pkt2bus;
+ u32 send_pkts[5];
+ u32 requested_sent[5];
+ u32 generic_error;
+ u32 mac_update_failed;
+ u32 mac_ps_update_failed;
+ u32 if_update_failed;
+ u32 packet_request_failed;
+ u32 credit_request_failed;
+ u32 rollback_success;
+ u32 rollback_failed;
+ u32 delayq_full_error;
+ u32 supprq_full_error;
+ u32 txs_indicate;
+ u32 txs_discard;
+ u32 txs_supp_core;
+ u32 txs_supp_ps;
+ u32 txs_tossed;
+ u32 txs_expired;
+ u32 txs_dropped;
+ u32 txs_mktfree;
+ u32 txs_unknown;
+ u32 txs_host_tossed;
+ u32 bus_flow_block;
+ u32 fws_flow_block;
+ u32 cnt_recv_err;
+ u32 cnt_cleanup_if;
+};
+
+struct inff_fws_info {
+ struct inff_pub *drvr;
+ spinlock_t spinlock; /* spinlock for fw signal resource protection */
+ ulong flags;
+ struct inff_fws_stats stats;
+ struct inff_fws_hanger hanger;
+ enum inff_fws_fcmode fcmode;
+ bool fw_signals;
+ bool bcmc_credit_check;
+ struct inff_fws_macdesc_table desc;
+ struct workqueue_struct *fws_wq;
+ struct work_struct fws_dequeue_work;
+ u32 fifo_enqpkt[INFF_FWS_FIFO_COUNT];
+ int fifo_credit[INFF_FWS_FIFO_COUNT];
+ int init_fifo_credit[INFF_FWS_FIFO_COUNT];
+ int credits_borrowed[INFF_FWS_FIFO_AC_VO + 1]
+ [INFF_FWS_FIFO_AC_VO + 1];
+ int deq_node_pos[INFF_FWS_FIFO_COUNT];
+ u32 fifo_credit_map;
+ u32 fifo_delay_map;
+ unsigned long borrow_defer_timestamp;
+ bool bus_flow_blocked;
+ bool creditmap_received;
+ bool credit_recover;
+ bool sdio_recv_error;
+ u8 mode;
+ bool avoid_queueing;
+ int fws_psq_len;
+ int fws_psq_hi_water;
+ int fws_psq_low_water;
+ struct work_struct creditmap_query_work;
+ int wlfc_mode;
+};
+
+static int inff_fws_deque_afq(struct inff_fws_info *fws, u16 hslot, u8 hcnt,
+ u8 prec, struct sk_buff **pktout);
+static int inff_fws_enque_afq(struct inff_fws_info *fws,
+ struct sk_buff *skb);
+
+/**
+ * inff_fws_get_tlv_len() - returns defined length for given tlv id.
+ *
+ * @fws: firmware-signalling information.
+ * @id: identifier of the TLV.
+ *
+ * Return: the specified length for the given TLV; Otherwise -EINVAL.
+ */
+static int inff_fws_get_tlv_len(struct inff_fws_info *fws,
+ enum inff_fws_tlv_type id)
+{
+ switch (id) {
+ case INFF_FWS_TYPE_MAC_OPEN:
+ return INFF_FWS_TYPE_MAC_OPEN_LEN;
+ case INFF_FWS_TYPE_MAC_CLOSE:
+ return INFF_FWS_TYPE_MAC_CLOSE_LEN;
+ case INFF_FWS_TYPE_MAC_REQUEST_CREDIT:
+ return INFF_FWS_TYPE_MAC_REQUEST_CREDIT_LEN;
+ case INFF_FWS_TYPE_TXSTATUS:
+ return INFF_FWS_TYPE_TXSTATUS_LEN;
+ case INFF_FWS_TYPE_PKTTAG:
+ return INFF_FWS_TYPE_PKTTAG_LEN;
+ case INFF_FWS_TYPE_MACDESC_ADD:
+ return INFF_FWS_TYPE_MACDESC_ADD_LEN;
+ case INFF_FWS_TYPE_MACDESC_DEL:
+ return INFF_FWS_TYPE_MACDESC_DEL_LEN;
+ case INFF_FWS_TYPE_RSSI:
+ return INFF_FWS_TYPE_RSSI_LEN;
+ case INFF_FWS_TYPE_INTERFACE_OPEN:
+ return INFF_FWS_TYPE_INTERFACE_OPEN_LEN;
+ case INFF_FWS_TYPE_INTERFACE_CLOSE:
+ return INFF_FWS_TYPE_INTERFACE_CLOSE_LEN;
+ case INFF_FWS_TYPE_FIFO_CREDITBACK:
+ return INFF_FWS_TYPE_FIFO_CREDITBACK_LEN;
+ case INFF_FWS_TYPE_PENDING_TRAFFIC_BMP:
+ return INFF_FWS_TYPE_PENDING_TRAFFIC_BMP_LEN;
+ case INFF_FWS_TYPE_MAC_REQUEST_PACKET:
+ return INFF_FWS_TYPE_MAC_REQUEST_PACKET_LEN;
+ case INFF_FWS_TYPE_HOST_REORDER_RXPKTS:
+ return INFF_FWS_TYPE_HOST_REORDER_RXPKTS_LEN;
+ case INFF_FWS_TYPE_TRANS_ID:
+ return INFF_FWS_TYPE_TRANS_ID_LEN;
+ case INFF_FWS_TYPE_COMP_TXSTATUS:
+ return INFF_FWS_TYPE_COMP_TXSTATUS_LEN;
+ case INFF_FWS_TYPE_FILLER:
+ return INFF_FWS_TYPE_FILLER_LEN;
+ default:
+ fws->stats.tlv_invalid_type++;
+ break;
+ }
+ return -EINVAL;
+}
+
+static void inff_fws_lock(struct inff_fws_info *fws)
+ __acquires(&fws->spinlock)
+{
+ spin_lock_irqsave(&fws->spinlock, fws->flags);
+}
+
+static void inff_fws_unlock(struct inff_fws_info *fws)
+ __releases(&fws->spinlock)
+{
+ spin_unlock_irqrestore(&fws->spinlock, fws->flags);
+}
+
+static bool inff_fws_ifidx_match(struct sk_buff *skb, void *arg)
+{
+ u32 ifidx = inff_skb_if_flags_get_field(skb, INDEX);
+ return ifidx == *(int *)arg;
+}
+
+static void inff_fws_hanger_init(struct inff_fws_hanger *hanger)
+{
+ int i;
+
+ memset(hanger, 0, sizeof(*hanger));
+ for (i = 0; i < ARRAY_SIZE(hanger->items); i++)
+ hanger->items[i].state = INFF_FWS_HANGER_ITEM_STATE_FREE;
+}
+
+static u32 inff_fws_hanger_get_free_slot(struct inff_fws_hanger *h)
+{
+ u32 i;
+
+ i = (h->slot_pos + 1) % INFF_FWS_HANGER_MAXITEMS;
+
+ while (i != h->slot_pos) {
+ if (h->items[i].state == INFF_FWS_HANGER_ITEM_STATE_FREE) {
+ h->slot_pos = i;
+ goto done;
+ }
+ i++;
+ if (i == INFF_FWS_HANGER_MAXITEMS)
+ i = 0;
+ }
+ inff_err("all slots occupied\n");
+ h->failed_slotfind++;
+ i = INFF_FWS_HANGER_MAXITEMS;
+done:
+ return i;
+}
+
+static int inff_fws_hanger_pushpkt(struct inff_fws_hanger *h,
+ struct sk_buff *pkt, u32 slot_id)
+{
+ if (slot_id >= INFF_FWS_HANGER_MAXITEMS)
+ return -ENOENT;
+
+ if (h->items[slot_id].state != INFF_FWS_HANGER_ITEM_STATE_FREE) {
+ inff_err("slot is not free\n");
+ h->failed_to_push++;
+ return -EINVAL;
+ }
+
+ h->items[slot_id].state = INFF_FWS_HANGER_ITEM_STATE_INUSE;
+ h->items[slot_id].pkt = pkt;
+ h->pushed++;
+ return 0;
+}
+
+static inline int inff_fws_hanger_poppkt(struct inff_fws_hanger *h,
+ u32 slot_id, struct sk_buff **pktout,
+ bool remove_item)
+{
+ if (slot_id >= INFF_FWS_HANGER_MAXITEMS)
+ return -ENOENT;
+
+ if (h->items[slot_id].state == INFF_FWS_HANGER_ITEM_STATE_FREE) {
+ inff_err("entry not in use\n");
+ h->failed_to_pop++;
+ return -EINVAL;
+ }
+
+ *pktout = h->items[slot_id].pkt;
+ if (remove_item) {
+ h->items[slot_id].state = INFF_FWS_HANGER_ITEM_STATE_FREE;
+ h->items[slot_id].pkt = NULL;
+ h->popped++;
+ }
+ return 0;
+}
+
+static void
+inff_fws_flow_control_check(struct inff_fws_info *fws, struct pktq *pq,
+ u8 if_id)
+{
+ struct inff_if *ifp = inff_get_ifp(fws->drvr, if_id);
+
+ if (WARN_ON(!ifp))
+ return;
+
+ if ((ifp->netif_stop & INFF_NETIF_STOP_REASON_FWS_FC) &&
+ pq->len <= fws->fws_psq_low_water)
+ inff_net_txflowblock(ifp, INFF_NETIF_STOP_REASON_FWS_FC,
+ false);
+ if (!(ifp->netif_stop & INFF_NETIF_STOP_REASON_FWS_FC) &&
+ pq->len >= fws->fws_psq_hi_water) {
+ fws->stats.fws_flow_block++;
+ inff_net_txflowblock(ifp, INFF_NETIF_STOP_REASON_FWS_FC,
+ true);
+ }
+}
+
+static void inff_fws_psq_flush(struct inff_fws_info *fws, struct pktq *q,
+ int ifidx)
+{
+ struct inff_fws_hanger_item *hi;
+ bool (*matchfn)(struct sk_buff *, void *) = NULL;
+ struct sk_buff *skb;
+ int prec;
+ u32 hslot;
+ int skbidx;
+
+ if (ifidx != -1)
+ matchfn = inff_fws_ifidx_match;
+ for (prec = 0; prec < q->num_prec; prec++) {
+ skb = inff_pktq_pdeq_match(q, prec, matchfn, &ifidx);
+ while (skb) {
+ hslot = inff_skb_htod_tag_get_field(skb, HSLOT);
+ if (!INFF_FWS_MODE_GET_AFQ(fws->wlfc_mode)) {
+ hi = &fws->hanger.items[hslot];
+ WARN_ON(skb != hi->pkt);
+ hi->state = INFF_FWS_HANGER_ITEM_STATE_FREE;
+ inff_fws_hanger_poppkt(&fws->hanger, hslot,
+ &skb, true);
+ }
+ skbidx = inff_skb_if_flags_get_field(skb, INDEX);
+ inff_fws_flow_control_check(fws, q, skbidx);
+ inff_pkt_buf_free_skb(skb);
+ skb = inff_pktq_pdeq_match(q, prec, matchfn, &ifidx);
+ }
+ }
+}
+
+static int inff_fws_hanger_mark_suppressed(struct inff_fws_hanger *h,
+ u32 slot_id)
+{
+ if (slot_id >= INFF_FWS_HANGER_MAXITEMS)
+ return -ENOENT;
+
+ if (h->items[slot_id].state == INFF_FWS_HANGER_ITEM_STATE_FREE) {
+ inff_err("entry not in use\n");
+ return -EINVAL;
+ }
+
+ h->items[slot_id].state = INFF_FWS_HANGER_ITEM_STATE_INUSE_SUPPRESSED;
+ return 0;
+}
+
+static void inff_fws_hanger_cleanup(struct inff_fws_info *fws,
+ bool (*fn)(struct sk_buff *, void *),
+ int ifidx)
+{
+ struct inff_fws_hanger *h = &fws->hanger;
+ struct sk_buff *skb;
+ int i;
+ enum inff_fws_hanger_item_state s;
+
+ for (i = 0; i < ARRAY_SIZE(h->items); i++) {
+ s = h->items[i].state;
+ if (s == INFF_FWS_HANGER_ITEM_STATE_INUSE ||
+ s == INFF_FWS_HANGER_ITEM_STATE_INUSE_SUPPRESSED) {
+ skb = h->items[i].pkt;
+ if (!fn || fn(skb, &ifidx)) {
+ /* suppress packets freed from psq */
+ if (s == INFF_FWS_HANGER_ITEM_STATE_INUSE)
+ inff_pkt_buf_free_skb(skb);
+ h->items[i].state =
+ INFF_FWS_HANGER_ITEM_STATE_FREE;
+ }
+ }
+ }
+}
+
+static void inff_fws_macdesc_set_name(struct inff_fws_info *fws,
+ struct inff_fws_mac_descriptor *desc)
+{
+ if (desc == &fws->desc.other)
+ strscpy(desc->name, "MAC-OTHER", sizeof(desc->name));
+ else if (desc->mac_handle)
+ scnprintf(desc->name, sizeof(desc->name), "MAC-%d:%d",
+ desc->mac_handle, desc->interface_id);
+ else
+ scnprintf(desc->name, sizeof(desc->name), "MACIF:%d",
+ desc->interface_id);
+}
+
+static void inff_fws_macdesc_init(struct inff_fws_mac_descriptor *desc,
+ u8 *addr, u8 ifidx)
+{
+ inff_dbg(TRACE,
+ "desc %p ea=%pM, ifidx=%u\n", desc, addr, ifidx);
+ desc->occupied = 1;
+ desc->state = INFF_FWS_STATE_OPEN;
+ desc->requested_credit = 0;
+ desc->requested_packet = 0;
+ /* depending on use may need ifp->bsscfgidx instead */
+ desc->interface_id = ifidx;
+ desc->ac_bitmap = 0xff; /* update this when handling APSD */
+ if (addr)
+ memcpy(&desc->ea[0], addr, ETH_ALEN);
+}
+
+static void inff_fws_macdesc_reset(struct inff_fws_mac_descriptor *entry)
+{
+ int i;
+
+ inff_fws_macdesc_init(entry, entry->ea, entry->interface_id);
+ entry->mac_handle = 0;
+ entry->suppressed = 0;
+ entry->transit_count = 0;
+ entry->suppr_transit_count = 0;
+ entry->generation = 0;
+
+ for (i = 0; i < INFF_FWS_FIFO_COUNT; i++)
+ entry->seq[i] = 0;
+
+ entry->send_tim_signal = 0;
+ entry->traffic_pending_bmp = 0;
+ entry->traffic_lastreported_bmp = 0;
+}
+
+static
+void inff_fws_macdesc_deinit(struct inff_fws_mac_descriptor *desc)
+{
+ inff_dbg(TRACE,
+ "ea=%pM, ifidx=%u\n", desc->ea, desc->interface_id);
+ desc->occupied = 0;
+ desc->state = INFF_FWS_STATE_CLOSE;
+ desc->requested_credit = 0;
+ desc->requested_packet = 0;
+}
+
+static struct inff_fws_mac_descriptor *
+inff_fws_macdesc_lookup(struct inff_fws_info *fws, u8 *ea)
+{
+ struct inff_fws_mac_descriptor *entry;
+ int i;
+
+ if (!ea)
+ return ERR_PTR(-EINVAL);
+
+ entry = &fws->desc.nodes[0];
+ for (i = 0; i < ARRAY_SIZE(fws->desc.nodes); i++) {
+ if (entry->occupied && !memcmp(entry->ea, ea, ETH_ALEN))
+ return entry;
+ entry++;
+ }
+
+ return ERR_PTR(-ENOENT);
+}
+
+static struct inff_fws_mac_descriptor*
+inff_fws_macdesc_find(struct inff_fws_info *fws, struct inff_if *ifp, u8 *da)
+{
+ struct inff_fws_mac_descriptor *entry;
+ bool multicast;
+
+ multicast = is_multicast_ether_addr(da);
+
+ /* Multicast destination, STA clients get the interface entry */
+ if (multicast && ifp->fws_desc) {
+ entry = ifp->fws_desc;
+ goto done;
+ }
+
+ entry = inff_fws_macdesc_lookup(fws, da);
+ if (IS_ERR(entry))
+ entry = ifp->fws_desc;
+
+done:
+ return entry;
+}
+
+static bool inff_fws_macdesc_closed(struct inff_fws_info *fws,
+ struct inff_fws_mac_descriptor *entry,
+ int fifo)
+{
+ struct inff_fws_mac_descriptor *if_entry;
+ bool closed;
+
+ /* for unique destination entries the related interface
+ * may be closed.
+ */
+ if (entry->mac_handle) {
+ if_entry = &fws->desc.iface[entry->interface_id];
+ if (if_entry->state == INFF_FWS_STATE_CLOSE)
+ return true;
+ }
+ /* an entry is closed when the state is closed and
+ * the firmware did not request anything.
+ */
+ closed = entry->state == INFF_FWS_STATE_CLOSE &&
+ !entry->requested_credit && !entry->requested_packet;
+
+ /* Or firmware does not allow traffic for given fifo */
+ return closed || !(entry->ac_bitmap & BIT(fifo));
+}
+
+static void inff_fws_macdesc_cleanup(struct inff_fws_info *fws,
+ struct inff_fws_mac_descriptor *entry,
+ int ifidx)
+{
+ if (entry->occupied && (ifidx == -1 || ifidx == entry->interface_id)) {
+ inff_fws_psq_flush(fws, &entry->psq, ifidx);
+ if (INFF_FWS_MODE_GET_AFQ(fws->wlfc_mode))
+ inff_pktq_flush(&entry->afq, true, NULL, NULL);
+ entry->occupied = !!(entry->psq.len);
+ }
+}
+
+static void inff_fws_bus_txq_cleanup(struct inff_fws_info *fws,
+ bool (*fn)(struct sk_buff *, void *),
+ int ifidx)
+{
+ struct inff_fws_hanger_item *hi;
+ struct pktq *txq;
+ struct sk_buff *skb;
+ int prec;
+ u32 hslot;
+
+ txq = inff_bus_gettxq(fws->drvr->bus_if);
+ if (IS_ERR(txq)) {
+ inff_dbg(TRACE, "no txq to clean up\n");
+ return;
+ }
+
+ for (prec = 0; prec < txq->num_prec; prec++) {
+ skb = inff_pktq_pdeq_match(txq, prec, fn, &ifidx);
+ while (skb) {
+ if (!INFF_FWS_MODE_GET_AFQ(fws->wlfc_mode)) {
+ hslot = inff_skb_htod_tag_get_field(skb, HSLOT);
+ hi = &fws->hanger.items[hslot];
+ WARN_ON(skb != hi->pkt);
+ hi->state = INFF_FWS_HANGER_ITEM_STATE_FREE;
+ }
+ inff_pkt_buf_free_skb(skb);
+ skb = inff_pktq_pdeq_match(txq, prec, fn, &ifidx);
+ }
+ }
+}
+
+static void inff_fws_cleanup(struct inff_fws_info *fws, int ifidx)
+{
+ int i;
+ struct inff_fws_mac_descriptor *table;
+ bool (*matchfn)(struct sk_buff *, void *) = NULL;
+
+ if (!fws)
+ return;
+
+ if (ifidx != -1)
+ matchfn = inff_fws_ifidx_match;
+
+ /* cleanup individual nodes */
+ table = &fws->desc.nodes[0];
+ for (i = 0; i < ARRAY_SIZE(fws->desc.nodes); i++)
+ inff_fws_macdesc_cleanup(fws, &table[i], ifidx);
+
+ inff_fws_macdesc_cleanup(fws, &fws->desc.other, ifidx);
+ inff_fws_bus_txq_cleanup(fws, matchfn, ifidx);
+ if (!INFF_FWS_MODE_GET_AFQ(fws->wlfc_mode))
+ inff_fws_hanger_cleanup(fws, matchfn, ifidx);
+}
+
+static u8 inff_fws_hdrpush(struct inff_fws_info *fws, struct sk_buff *skb)
+{
+ struct inff_fws_mac_descriptor *entry = inff_skbcb(skb)->mac;
+ u8 *wlh;
+ u16 data_offset = 0;
+ u8 fillers;
+ __le32 pkttag = cpu_to_le32(inff_skbcb(skb)->htod);
+ __le16 pktseq = cpu_to_le16(inff_skbcb(skb)->htod_seq);
+
+ inff_dbg(TRACE, "%s, idx=%d hslot=%d htod %X seq %X\n",
+ entry->name, inff_skb_if_flags_get_field(skb, INDEX),
+ (le32_to_cpu(pkttag) >> 8) & 0xffff,
+ inff_skbcb(skb)->htod, inff_skbcb(skb)->htod_seq);
+ if (entry->send_tim_signal)
+ data_offset += 2 + INFF_FWS_TYPE_PENDING_TRAFFIC_BMP_LEN;
+ if (INFF_FWS_MODE_GET_REUSESEQ(fws->mode))
+ data_offset += INFF_FWS_TYPE_SEQ_LEN;
+ /* +2 is for Type[1] and Len[1] in TLV, plus TIM signal */
+ data_offset += 2 + INFF_FWS_TYPE_PKTTAG_LEN;
+ fillers = round_up(data_offset, 4) - data_offset;
+ data_offset += fillers;
+
+ skb_push(skb, data_offset);
+ wlh = skb->data;
+
+ wlh[0] = INFF_FWS_TYPE_PKTTAG;
+ wlh[1] = INFF_FWS_TYPE_PKTTAG_LEN;
+ memcpy(&wlh[2], &pkttag, sizeof(pkttag));
+ if (INFF_FWS_MODE_GET_REUSESEQ(fws->mode)) {
+ wlh[1] += INFF_FWS_TYPE_SEQ_LEN;
+ memcpy(&wlh[2 + INFF_FWS_TYPE_PKTTAG_LEN], &pktseq,
+ sizeof(pktseq));
+ }
+ wlh += wlh[1] + 2;
+
+ if (entry->send_tim_signal) {
+ entry->send_tim_signal = false;
+ wlh[0] = INFF_FWS_TYPE_PENDING_TRAFFIC_BMP;
+ wlh[1] = INFF_FWS_TYPE_PENDING_TRAFFIC_BMP_LEN;
+ wlh[2] = entry->mac_handle;
+ wlh[3] = entry->traffic_pending_bmp;
+ inff_dbg(TRACE, "adding TIM info: handle %d bmp 0x%X\n",
+ entry->mac_handle, entry->traffic_pending_bmp);
+ wlh += INFF_FWS_TYPE_PENDING_TRAFFIC_BMP_LEN + 2;
+ entry->traffic_lastreported_bmp = entry->traffic_pending_bmp;
+ }
+ if (fillers)
+ memset(wlh, INFF_FWS_TYPE_FILLER, fillers);
+
+ return (u8)(data_offset >> 2);
+}
+
+static bool inff_fws_tim_update(struct inff_fws_info *fws,
+ struct inff_fws_mac_descriptor *entry,
+ int fifo, bool send_immediately)
+{
+ struct sk_buff *skb;
+ struct inff_skbuff_cb *skcb;
+ s32 err;
+ u32 len;
+ u8 data_offset;
+ int ifidx;
+
+ /* check delayedQ and suppressQ in one call using bitmap */
+ if (inff_pktq_mlen(&entry->psq, 3 << (fifo * 2)) == 0)
+ entry->traffic_pending_bmp &= ~NBITVAL(fifo);
+ else
+ entry->traffic_pending_bmp |= NBITVAL(fifo);
+
+ entry->send_tim_signal = false;
+ if (entry->traffic_lastreported_bmp != entry->traffic_pending_bmp)
+ entry->send_tim_signal = true;
+ if (send_immediately && entry->send_tim_signal &&
+ entry->state == INFF_FWS_STATE_CLOSE) {
+ /* create a dummy packet and sent that. The traffic */
+ /* bitmap info will automatically be attached to that packet */
+ len = INFF_FWS_TYPE_PKTTAG_LEN + 2 +
+ INFF_FWS_TYPE_SEQ_LEN +
+ INFF_FWS_TYPE_PENDING_TRAFFIC_BMP_LEN + 2 +
+ 4 + fws->drvr->hdrlen;
+ skb = inff_pkt_buf_get_skb(len);
+ if (!skb)
+ return false;
+ skb_pull(skb, len);
+ skcb = inff_skbcb(skb);
+ skcb->mac = entry;
+ skcb->state = INFF_FWS_SKBSTATE_TIM;
+ skcb->htod = 0;
+ skcb->htod_seq = 0;
+ data_offset = inff_fws_hdrpush(fws, skb);
+ ifidx = inff_skb_if_flags_get_field(skb, INDEX);
+ inff_fws_unlock(fws);
+ err = inff_proto_txdata(fws->drvr, ifidx, data_offset, skb);
+ inff_fws_lock(fws);
+ if (err)
+ inff_pkt_buf_free_skb(skb);
+ return true;
+ }
+ return false;
+}
+
+static int inff_fws_rssi_indicate(struct inff_fws_info *fws, s8 rssi)
+{
+ inff_dbg(CTL, "rssi %d\n", rssi);
+ return 0;
+}
+
+static
+int inff_fws_macdesc_indicate(struct inff_fws_info *fws, u8 type, u8 *data)
+{
+ struct inff_fws_mac_descriptor *entry, *existing;
+ u8 mac_handle;
+ u8 ifidx;
+ u8 *addr;
+
+ mac_handle = *data++;
+ ifidx = *data++;
+ addr = data;
+
+ entry = &fws->desc.nodes[mac_handle & 0x1F];
+ if (type == INFF_FWS_TYPE_MACDESC_DEL) {
+ if (entry->occupied) {
+ inff_dbg(TRACE, "deleting %s mac %pM\n",
+ entry->name, addr);
+ inff_fws_lock(fws);
+ inff_fws_macdesc_cleanup(fws, entry, -1);
+ inff_fws_macdesc_deinit(entry);
+ inff_fws_unlock(fws);
+ } else {
+ fws->stats.mac_update_failed++;
+ }
+ return 0;
+ }
+
+ existing = inff_fws_macdesc_lookup(fws, addr);
+ if (IS_ERR(existing)) {
+ if (!entry->occupied) {
+ inff_fws_lock(fws);
+ entry->mac_handle = mac_handle;
+ inff_fws_macdesc_init(entry, addr, ifidx);
+ inff_fws_macdesc_set_name(fws, entry);
+ inff_pktq_init(&entry->psq,
+ INFF_FWS_PSQ_PREC_COUNT,
+ fws->fws_psq_len);
+ if (INFF_FWS_MODE_GET_AFQ(fws->wlfc_mode))
+ inff_pktq_init(&entry->afq,
+ INFF_FWS_AFQ_PREC_COUNT,
+ INFF_FWS_AFQ_LEN);
+ inff_fws_unlock(fws);
+ inff_dbg(TRACE, "add %s mac %pM\n", entry->name, addr);
+ } else {
+ fws->stats.mac_update_failed++;
+ }
+ } else {
+ if (entry != existing) {
+ inff_dbg(TRACE, "copy mac %s\n", existing->name);
+ inff_fws_lock(fws);
+ memcpy(entry, existing,
+ offsetof(struct inff_fws_mac_descriptor, psq));
+ entry->mac_handle = mac_handle;
+ inff_fws_macdesc_deinit(existing);
+ inff_fws_macdesc_set_name(fws, entry);
+ inff_fws_unlock(fws);
+ inff_dbg(TRACE, "relocate %s mac %pM\n", entry->name,
+ addr);
+ } else {
+ inff_dbg(TRACE, "use existing\n");
+ WARN_ON(entry->mac_handle != mac_handle);
+ }
+ }
+ return 0;
+}
+
+static int inff_fws_macdesc_state_indicate(struct inff_fws_info *fws,
+ u8 type, u8 *data)
+{
+ struct inff_fws_mac_descriptor *entry;
+ u8 mac_handle;
+ int ret;
+
+ mac_handle = data[0];
+ entry = &fws->desc.nodes[mac_handle & 0x1F];
+ if (!entry->occupied) {
+ fws->stats.mac_ps_update_failed++;
+ return -ESRCH;
+ }
+ inff_fws_lock(fws);
+ /* a state update should wipe old credits */
+ entry->requested_credit = 0;
+ entry->requested_packet = 0;
+ if (type == INFF_FWS_TYPE_MAC_OPEN) {
+ entry->state = INFF_FWS_STATE_OPEN;
+ ret = INFF_FWS_RET_OK_SCHEDULE;
+ } else {
+ entry->state = INFF_FWS_STATE_CLOSE;
+ inff_fws_tim_update(fws, entry, INFF_FWS_FIFO_AC_BK, false);
+ inff_fws_tim_update(fws, entry, INFF_FWS_FIFO_AC_BE, false);
+ inff_fws_tim_update(fws, entry, INFF_FWS_FIFO_AC_VI, false);
+ inff_fws_tim_update(fws, entry, INFF_FWS_FIFO_AC_VO, true);
+ ret = INFF_FWS_RET_OK_NOSCHEDULE;
+ }
+ inff_fws_unlock(fws);
+ return ret;
+}
+
+static int inff_fws_interface_state_indicate(struct inff_fws_info *fws,
+ u8 type, u8 *data)
+{
+ struct inff_fws_mac_descriptor *entry;
+ u8 ifidx;
+ int ret;
+
+ ifidx = data[0];
+
+ if (ifidx >= INFF_MAX_IFS) {
+ ret = -ERANGE;
+ goto fail;
+ }
+
+ entry = &fws->desc.iface[ifidx];
+ if (!entry->occupied) {
+ ret = -ESRCH;
+ goto fail;
+ }
+
+ inff_dbg(TRACE, "%s (%d): %s\n", inff_fws_get_tlv_name(type), type,
+ entry->name);
+ inff_fws_lock(fws);
+ switch (type) {
+ case INFF_FWS_TYPE_INTERFACE_OPEN:
+ entry->state = INFF_FWS_STATE_OPEN;
+ ret = INFF_FWS_RET_OK_SCHEDULE;
+ break;
+ case INFF_FWS_TYPE_INTERFACE_CLOSE:
+ entry->state = INFF_FWS_STATE_CLOSE;
+ ret = INFF_FWS_RET_OK_NOSCHEDULE;
+ break;
+ default:
+ ret = -EINVAL;
+ inff_fws_unlock(fws);
+ goto fail;
+ }
+ inff_fws_unlock(fws);
+ return ret;
+
+fail:
+ fws->stats.if_update_failed++;
+ return ret;
+}
+
+static int inff_fws_request_indicate(struct inff_fws_info *fws, u8 type,
+ u8 *data)
+{
+ struct inff_fws_mac_descriptor *entry;
+
+ entry = &fws->desc.nodes[data[1] & 0x1F];
+ if (!entry->occupied) {
+ if (type == INFF_FWS_TYPE_MAC_REQUEST_CREDIT)
+ fws->stats.credit_request_failed++;
+ else
+ fws->stats.packet_request_failed++;
+ return -ESRCH;
+ }
+
+ inff_dbg(TRACE, "%s (%d): %s cnt %d bmp %d\n",
+ inff_fws_get_tlv_name(type), type, entry->name,
+ data[0], data[2]);
+ inff_fws_lock(fws);
+ if (type == INFF_FWS_TYPE_MAC_REQUEST_CREDIT)
+ entry->requested_credit = data[0];
+ else
+ entry->requested_packet = data[0];
+
+ entry->ac_bitmap = data[2];
+ inff_fws_unlock(fws);
+ return INFF_FWS_RET_OK_SCHEDULE;
+}
+
+static void
+inff_fws_macdesc_use_req_credit(struct inff_fws_mac_descriptor *entry,
+ struct sk_buff *skb)
+{
+ if (entry->requested_credit > 0) {
+ entry->requested_credit--;
+ inff_skb_if_flags_set_field(skb, REQUESTED, 1);
+ inff_skb_if_flags_set_field(skb, REQ_CREDIT, 1);
+ if (entry->state != INFF_FWS_STATE_CLOSE)
+ inff_err("requested credit set while mac not closed!\n");
+ } else if (entry->requested_packet > 0) {
+ entry->requested_packet--;
+ inff_skb_if_flags_set_field(skb, REQUESTED, 1);
+ inff_skb_if_flags_set_field(skb, REQ_CREDIT, 0);
+ if (entry->state != INFF_FWS_STATE_CLOSE)
+ inff_err("requested packet set while mac not closed!\n");
+ } else {
+ inff_skb_if_flags_set_field(skb, REQUESTED, 0);
+ inff_skb_if_flags_set_field(skb, REQ_CREDIT, 0);
+ }
+}
+
+static void inff_fws_macdesc_return_req_credit(struct sk_buff *skb)
+{
+ struct inff_fws_mac_descriptor *entry = inff_skbcb(skb)->mac;
+
+ if ((inff_skb_if_flags_get_field(skb, REQ_CREDIT)) &&
+ entry->state == INFF_FWS_STATE_CLOSE)
+ entry->requested_credit++;
+}
+
+static void inff_fws_return_credits(struct inff_fws_info *fws,
+ u8 fifo, u8 credits)
+{
+ int lender_ac;
+ int *borrowed;
+ int *fifo_credit;
+
+ if (!credits)
+ return;
+
+ fws->fifo_credit_map |= 1 << fifo;
+
+ if (fifo >= INFF_FWS_FIFO_AC_BK &&
+ fifo <= INFF_FWS_FIFO_AC_VO)
+ for (lender_ac = INFF_FWS_FIFO_AC_VO; lender_ac >= 0;
+ lender_ac--) {
+ borrowed = &fws->credits_borrowed[fifo][lender_ac];
+ if (*borrowed) {
+ fws->fifo_credit_map |= (1 << lender_ac);
+ fifo_credit = &fws->fifo_credit[lender_ac];
+ if (*borrowed >= credits) {
+ *borrowed -= credits;
+ *fifo_credit += credits;
+ return;
+ }
+
+ credits -= *borrowed;
+ *fifo_credit += *borrowed;
+ *borrowed = 0;
+ }
+ }
+
+ if (credits)
+ fws->fifo_credit[fifo] += credits;
+
+ if (fws->fifo_credit[fifo] > fws->init_fifo_credit[fifo])
+ fws->fifo_credit[fifo] = fws->init_fifo_credit[fifo];
+}
+
+static void inff_fws_schedule_deq(struct inff_fws_info *fws)
+{
+ /* only schedule dequeue when there are credits for delayed traffic */
+ if ((fws->fifo_credit_map & fws->fifo_delay_map) ||
+ (!inff_fws_fc_active(fws) && fws->fifo_delay_map))
+ queue_work(fws->fws_wq, &fws->fws_dequeue_work);
+}
+
+static int inff_fws_enq(struct inff_fws_info *fws,
+ enum inff_fws_skb_state state, int fifo,
+ struct sk_buff *p)
+{
+ struct inff_pub *drvr = fws->drvr;
+ int prec = 2 * fifo;
+ u32 *qfull_stat = &fws->stats.delayq_full_error;
+ struct inff_fws_mac_descriptor *entry;
+ struct pktq *pq;
+ struct sk_buff_head *queue;
+ struct sk_buff *p_head;
+ struct sk_buff *p_tail;
+ u32 fr_new;
+ u32 fr_compare;
+
+ entry = inff_skbcb(p)->mac;
+ if (!entry) {
+ iphy_err(drvr, "no mac descriptor found for skb %p\n", p);
+ return -ENOENT;
+ }
+
+ inff_dbg(DATA, "fifo %d skb %p\n", fifo, p);
+ if (state == INFF_FWS_SKBSTATE_SUPPRESSED) {
+ prec += 1;
+ qfull_stat = &fws->stats.supprq_full_error;
+
+ /* Fix out of order delivery of frames. Dont assume frame */
+ /* can be inserted at the end, but look for correct position */
+ pq = &entry->psq;
+ if (pktq_full(pq) || pktq_pfull(pq, prec)) {
+ *qfull_stat += 1;
+ return -ENFILE;
+ }
+ queue = &pq->q[prec].skblist;
+
+ p_head = skb_peek(queue);
+ p_tail = skb_peek_tail(queue);
+ fr_new = inff_skb_htod_tag_get_field(p, FREERUN);
+
+ while (p_head != p_tail) {
+ fr_compare = inff_skb_htod_tag_get_field(p_tail,
+ FREERUN);
+ /* be sure to handle wrap of 256 */
+ if ((fr_new > fr_compare &&
+ ((fr_new - fr_compare) < 128)) ||
+ (fr_new < fr_compare &&
+ ((fr_compare - fr_new) > 128)))
+ break;
+ p_tail = skb_queue_prev(queue, p_tail);
+ }
+ /* Position found. Determine what to do */
+ if (!p_tail) {
+ /* empty list */
+ __skb_queue_tail(queue, p);
+ } else {
+ fr_compare = inff_skb_htod_tag_get_field(p_tail,
+ FREERUN);
+ if ((fr_new > fr_compare &&
+ ((fr_new - fr_compare) < 128)) ||
+ (fr_new < fr_compare &&
+ ((fr_compare - fr_new) > 128))) {
+ /* After tail */
+ __skb_queue_after(queue, p_tail, p);
+ } else {
+ /* Before tail */
+ __skb_insert(p, p_tail->prev, p_tail, queue);
+ }
+ }
+
+ /* Complete the counters and statistics */
+ pq->len++;
+ if (pq->hi_prec < prec)
+ pq->hi_prec = (u8)prec;
+ } else if (!inff_pktq_penq(&entry->psq, prec, p)) {
+ *qfull_stat += 1;
+ return -ENFILE;
+ }
+
+ /* increment total enqueued packet count */
+ fws->fifo_delay_map |= 1 << fifo;
+ fws->fifo_enqpkt[fifo]++;
+
+ /* update the sk_buff state */
+ inff_skbcb(p)->state = state;
+
+ /*
+ * A packet has been pushed so update traffic
+ * availability bitmap, if applicable
+ */
+ inff_fws_tim_update(fws, entry, fifo, true);
+ inff_fws_flow_control_check(fws, &entry->psq,
+ inff_skb_if_flags_get_field(p, INDEX));
+ return 0;
+}
+
+static struct sk_buff *inff_fws_deq(struct inff_fws_info *fws, int fifo)
+{
+ struct inff_fws_mac_descriptor *table;
+ struct inff_fws_mac_descriptor *entry;
+ struct sk_buff *p;
+ int num_nodes;
+ int node_pos;
+ int prec_out;
+ int pmsk;
+ int i;
+
+ table = (struct inff_fws_mac_descriptor *)&fws->desc;
+ num_nodes = sizeof(fws->desc) / sizeof(struct inff_fws_mac_descriptor);
+ node_pos = fws->deq_node_pos[fifo];
+
+ for (i = 0; i < num_nodes; i++) {
+ entry = &table[(node_pos + i) % num_nodes];
+ if (!entry->occupied ||
+ inff_fws_macdesc_closed(fws, entry, fifo))
+ continue;
+
+ if (entry->suppressed)
+ pmsk = 2;
+ else
+ pmsk = 3;
+ p = inff_pktq_mdeq(&entry->psq, pmsk << (fifo * 2), &prec_out);
+ if (!p) {
+ if (entry->suppressed) {
+ if (entry->suppr_transit_count)
+ continue;
+ entry->suppressed = false;
+ p = inff_pktq_mdeq(&entry->psq,
+ 1 << (fifo * 2), &prec_out);
+ }
+ }
+ if (!p)
+ continue;
+
+ inff_fws_macdesc_use_req_credit(entry, p);
+
+ /* move dequeue position to ensure fair round-robin */
+ fws->deq_node_pos[fifo] = (node_pos + i + 1) % num_nodes;
+ inff_fws_flow_control_check(fws, &entry->psq,
+ inff_skb_if_flags_get_field(p,
+ INDEX));
+ /*
+ * A packet has been picked up, update traffic
+ * availability bitmap, if applicable
+ */
+ inff_fws_tim_update(fws, entry, fifo, false);
+
+ /*
+ * decrement total enqueued fifo packets and
+ * clear delay bitmap if done.
+ */
+ fws->fifo_enqpkt[fifo]--;
+ if (fws->fifo_enqpkt[fifo] == 0)
+ fws->fifo_delay_map &= ~(1 << fifo);
+ goto done;
+ }
+ p = NULL;
+done:
+ inff_dbg(DATA, "fifo %d skb %p\n", fifo, p);
+ return p;
+}
+
+static int inff_fws_txstatus_suppressed(struct inff_fws_info *fws, int fifo,
+ struct sk_buff *skb,
+ u32 genbit, u16 seq)
+{
+ struct inff_fws_mac_descriptor *entry = inff_skbcb(skb)->mac;
+ u32 hslot;
+ int ret;
+
+ hslot = inff_skb_htod_tag_get_field(skb, HSLOT);
+
+ /* this packet was suppressed */
+ if (!entry->suppressed) {
+ entry->suppressed = true;
+ entry->suppr_transit_count = entry->transit_count;
+ inff_dbg(DATA, "suppress %s: transit %d\n",
+ entry->name, entry->transit_count);
+ }
+
+ entry->generation = genbit;
+
+ inff_skb_htod_tag_set_field(skb, GENERATION, genbit);
+ inff_skbcb(skb)->htod_seq = seq;
+ if (inff_skb_htod_seq_get_field(skb, FROMFW)) {
+ inff_skb_htod_seq_set_field(skb, FROMDRV, 1);
+ inff_skb_htod_seq_set_field(skb, FROMFW, 0);
+ } else {
+ inff_skb_htod_seq_set_field(skb, FROMDRV, 0);
+ }
+ ret = inff_fws_enq(fws, INFF_FWS_SKBSTATE_SUPPRESSED, fifo, skb);
+
+ if (ret != 0) {
+ /* suppress q is full drop this packet */
+ if (!INFF_FWS_MODE_GET_AFQ(fws->wlfc_mode))
+ inff_fws_hanger_poppkt(&fws->hanger, hslot, &skb,
+ true);
+ } else {
+ /* Mark suppressed to avoid a double free during wlfc cleanup */
+ if (!INFF_FWS_MODE_GET_AFQ(fws->wlfc_mode))
+ inff_fws_hanger_mark_suppressed(&fws->hanger, hslot);
+ }
+
+ return ret;
+}
+
+static int
+inff_fws_txs_process(struct inff_fws_info *fws, u8 flags, u32 hslot,
+ u32 genbit, u16 seq, u8 compcnt, u8 hcnt, u8 fifo_id)
+{
+ struct inff_pub *drvr = fws->drvr;
+ u32 fifo;
+ u8 cnt = 0;
+ int ret = 0;
+ bool remove_from_hanger = true;
+ struct sk_buff *skb = NULL;
+ struct inff_skbuff_cb *skcb;
+ struct inff_fws_mac_descriptor *entry = NULL;
+ struct inff_if *ifp;
+
+ inff_dbg(DATA, "flags %d\n", flags);
+
+ if (flags == INFF_FWS_TXSTATUS_DISCARD) {
+ fws->stats.txs_discard += compcnt;
+ } else if (flags == INFF_FWS_TXSTATUS_CORE_SUPPRESS) {
+ fws->stats.txs_supp_core += compcnt;
+ remove_from_hanger = false;
+ } else if (flags == INFF_FWS_TXSTATUS_FW_PS_SUPPRESS) {
+ fws->stats.txs_supp_ps += compcnt;
+ remove_from_hanger = false;
+ } else if (flags == INFF_FWS_TXSTATUS_FW_TOSSED) {
+ fws->stats.txs_tossed += compcnt;
+ } else if (flags == INFF_FWS_TXSTATUS_FW_DISCARD_NOACK) {
+ fws->stats.txs_discard += compcnt;
+ } else if (flags == INFF_FWS_TXSTATUS_FW_SUPPRESS_ACKED) {
+ fws->stats.txs_discard += compcnt;
+ } else if (flags == INFF_FWS_TXSTATUS_FW_EXPIRED) {
+ fws->stats.txs_expired += compcnt;
+ } else if (flags == INFF_FWS_TXSTATUS_FW_DROPPED) {
+ fws->stats.txs_dropped += compcnt;
+ } else if (flags == INFF_FWS_TXSTATUS_FW_MKTFREE) {
+ fws->stats.txs_mktfree += compcnt;
+ } else {
+ fws->stats.txs_unknown += compcnt;
+ iphy_err(drvr, "unexpected txstatus %u\n", flags);
+ }
+
+ while (cnt < compcnt) {
+ if (INFF_FWS_MODE_GET_AFQ(fws->wlfc_mode)) {
+ ret = inff_fws_deque_afq(fws, hslot, hcnt,
+ fifo_id, &skb);
+ } else {
+ ret = inff_fws_hanger_poppkt(&fws->hanger, hslot, &skb,
+ remove_from_hanger);
+ }
+ if (ret != 0 || !skb) {
+ iphy_err(drvr, "no packet in hanger slot: hslot=%d\n",
+ hslot);
+ goto cont;
+ }
+
+ skcb = inff_skbcb(skb);
+ entry = skcb->mac;
+ if (WARN_ON(!entry)) {
+ inff_pkt_buf_free_skb(skb);
+ goto cont;
+ }
+ entry->transit_count--;
+ if (entry->suppressed && entry->suppr_transit_count)
+ entry->suppr_transit_count--;
+
+ inff_dbg(DATA, "%s flags %d htod %X seq %X\n", entry->name,
+ flags, skcb->htod, seq);
+
+ /* pick up the implicit credit from this packet */
+ fifo = inff_skb_htod_tag_get_field(skb, FIFO);
+ if (fws->fcmode == INFF_FWS_FCMODE_IMPLIED_CREDIT ||
+ inff_skb_if_flags_get_field(skb, REQ_CREDIT)) {
+ inff_fws_return_credits(fws, fifo, 1);
+ inff_fws_schedule_deq(fws);
+ }
+ inff_fws_macdesc_return_req_credit(skb);
+
+ ret = inff_proto_hdrpull(fws->drvr, false, skb, &ifp);
+ if (ret) {
+ inff_pkt_buf_free_skb(skb);
+ goto cont;
+ }
+ if (!remove_from_hanger)
+ ret = inff_fws_txstatus_suppressed(fws, fifo, skb,
+ genbit, seq);
+ if (remove_from_hanger || ret)
+ inff_txfinalize(ifp, skb, true);
+
+cont:
+ hcnt = (hcnt + 1) & INFF_FWS_TXSTAT_FREERUN_MASK;
+ if (!INFF_FWS_MODE_GET_AFQ(fws->wlfc_mode))
+ hslot = (hslot + 1) & (INFF_FWS_TXSTAT_HSLOT_MASK >>
+ INFF_FWS_TXSTAT_HSLOT_SHIFT);
+ if (INFF_FWS_MODE_GET_REUSESEQ(fws->mode))
+ seq = (seq + 1) & INFF_SKB_HTOD_SEQ_NR_MASK;
+
+ cnt++;
+ }
+
+ return 0;
+}
+
+static void inff_fws_txs_process_host_tossed(struct inff_fws_info *fws,
+ struct sk_buff *skb,
+ bool header_pulled,
+ bool in_transit)
+{
+ struct inff_pub *drvr = fws->drvr;
+ struct inff_fws_mac_descriptor *entry;
+ struct inff_if *ifp = NULL;
+ struct sk_buff *pktout;
+ u32 hslot;
+ u32 fifo;
+ int ret;
+
+ fws->stats.txs_host_tossed++;
+
+ if (!INFF_FWS_MODE_GET_AFQ(fws->wlfc_mode)) {
+ hslot = inff_skb_htod_tag_get_field(skb, HSLOT);
+ ret = inff_fws_hanger_poppkt(&fws->hanger, hslot, &pktout,
+ true);
+ if (ret || !pktout)
+ iphy_err(drvr, "no packet in hanger slot: hslot=%d\n",
+ hslot);
+ else
+ skb = pktout;
+ }
+
+ entry = inff_skbcb(skb)->mac;
+ if (WARN_ON(!entry || IS_ERR(entry))) {
+ inff_pkt_buf_free_skb(skb);
+ return;
+ }
+
+ if (in_transit) {
+ entry->transit_count--;
+ if (entry->suppressed && entry->suppr_transit_count)
+ entry->suppr_transit_count--;
+ }
+
+ fifo = inff_skb_htod_tag_get_field(skb, FIFO);
+ inff_fws_return_credits(fws, fifo, 1);
+ inff_fws_schedule_deq(fws);
+ inff_fws_macdesc_return_req_credit(skb);
+
+ if (header_pulled) {
+ ifp = inff_get_ifp(drvr,
+ inff_skb_if_flags_get_field(skb, INDEX));
+ } else {
+ ret = inff_proto_hdrpull(drvr, false, skb, &ifp);
+ if (ret) {
+ inff_pkt_buf_free_skb(skb);
+ return;
+ }
+ }
+
+ inff_txfinalize(ifp, skb, false);
+}
+
+static void inff_fws_credit_auto_recover(struct inff_fws_info *fws, u8 *data)
+{
+ int fifo, i;
+ u8 *fw_tx = data + INFF_FIFO_CREDITBACK_TX_OFFSET;
+ u8 *fw_back = data;
+ int borrowed = 0;
+ int loan = 0;
+ int missing = 0;
+ int host_record_credit;
+ int in_fw_credit;
+
+ inff_dbg(SDIO, "tx %pM back %pM\n", fw_tx, fw_back);
+ inff_dbg(SDIO, "credit [BK]:%d [BE]:%d [VI]:%d [VO]:%d [BCMC]:%d\n",
+ fws->fifo_credit[0], fws->fifo_credit[1], fws->fifo_credit[2],
+ fws->fifo_credit[3], fws->fifo_credit[4]);
+
+ /* must check from highest priority FIFO */
+ for (fifo = INFF_FWS_FIFO_COUNT - 1; fifo >= INFF_FWS_FIFO_AC_BK; fifo--) {
+ /* if no credit lost, continue to check next FIFO */
+ if (fws->init_fifo_credit[fifo] == fws->fifo_credit[fifo])
+ continue;
+
+ inff_dbg(SDIO, "FIFO %d init: %d current: %d\n",
+ fifo, fws->init_fifo_credit[fifo], fws->fifo_credit[fifo]);
+
+ if (fifo <= INFF_FWS_FIFO_AC_VO) {
+ /* how many credit are borrowed from other FIFO */
+ for (i = 0; i <= INFF_FWS_FIFO_AC_VO; i++)
+ borrowed += fws->credits_borrowed[fifo][i];
+
+ /* how many credit are lend to other FIFO */
+ for (i = 0; i <= INFF_FWS_FIFO_AC_VO; i++)
+ loan += fws->credits_borrowed[i][fifo];
+
+ inff_dbg(SDIO, "borrowed: %d loan: %d\n", borrowed, loan);
+ }
+
+ /* calculate missed credit */
+ host_record_credit = fws->init_fifo_credit[fifo] -
+ fws->fifo_credit[fifo] + borrowed - loan;
+ in_fw_credit = fw_tx[fifo] - fw_back[fifo];
+ missing = host_record_credit - in_fw_credit;
+ inff_dbg(SDIO, "host %d fw %d missing: %d\n",
+ host_record_credit, in_fw_credit, missing);
+
+ if (missing > 0)
+ inff_fws_return_credits(fws, fifo, missing);
+ }
+
+ inff_dbg(SDIO, "Leave: credit [BK]:%d [BE]:%d [VI]:%d [VO]:%d [BCMC]:%d\n",
+ fws->fifo_credit[0], fws->fifo_credit[1], fws->fifo_credit[2],
+ fws->fifo_credit[3], fws->fifo_credit[4]);
+}
+
+static int inff_fws_fifocreditback_indicate(struct inff_fws_info *fws,
+ s16 len, u8 *data)
+{
+ int i;
+
+ if (fws->fcmode != INFF_FWS_FCMODE_EXPLICIT_CREDIT) {
+ inff_dbg(INFO, "ignored\n");
+ return INFF_FWS_RET_OK_NOSCHEDULE;
+ }
+
+ inff_fws_lock(fws);
+ for (i = 0; i < INFF_FWS_FIFO_COUNT; i++)
+ inff_fws_return_credits(fws, i, data[i]);
+
+ inff_dbg(DATA, "map: credit %x delay %x\n", fws->fifo_credit_map,
+ fws->fifo_delay_map);
+
+ /* when bus error happened, try to recover lost credit */
+ if (len == INFF_FWS_TYPE_FIFO_CREDITBACK_V2_LEN && fws->credit_recover) {
+ inff_err("Trigger credit recover\n");
+ inff_fws_credit_auto_recover(fws, data);
+ fws->credit_recover = false;
+ }
+ inff_fws_unlock(fws);
+ return INFF_FWS_RET_OK_SCHEDULE;
+}
+
+static int inff_fws_txstatus_indicate(struct inff_fws_info *fws, u8 type,
+ u8 *data)
+{
+ __le32 status_le;
+ __le16 seq_le;
+ u32 status;
+ u32 hslot;
+ u32 genbit;
+ u8 flags, hcnt, fifo_id;
+ u16 seq;
+ u8 compcnt;
+ u8 compcnt_offset = INFF_FWS_TYPE_TXSTATUS_LEN;
+
+ memcpy(&status_le, data, sizeof(status_le));
+ status = le32_to_cpu(status_le);
+ flags = inff_txstatus_get_field(status, FLAGS);
+ hslot = inff_txstatus_get_field(status, HSLOT);
+ genbit = inff_txstatus_get_field(status, GENERATION);
+ hcnt = inff_txstatus_get_field(status, FREERUN);
+ fifo_id = inff_txstatus_get_field(status, FIFO);
+ if (INFF_FWS_MODE_GET_REUSESEQ(fws->mode)) {
+ memcpy(&seq_le, &data[INFF_FWS_TYPE_TXSTATUS_LEN],
+ sizeof(seq_le));
+ seq = le16_to_cpu(seq_le);
+ compcnt_offset += INFF_FWS_TYPE_SEQ_LEN;
+ } else {
+ seq = 0;
+ }
+
+ if (type == INFF_FWS_TYPE_COMP_TXSTATUS)
+ compcnt = data[compcnt_offset];
+ else
+ compcnt = 1;
+ fws->stats.txs_indicate += compcnt;
+
+ inff_fws_lock(fws);
+ inff_fws_txs_process(fws, flags, hslot, genbit, seq, compcnt, hcnt,
+ fifo_id);
+ inff_fws_unlock(fws);
+ return INFF_FWS_RET_OK_NOSCHEDULE;
+}
+
+static int inff_fws_dbg_seqnum_check(struct inff_fws_info *fws, u8 *data)
+{
+ __le32 timestamp;
+
+ memcpy(×tamp, &data[2], sizeof(timestamp));
+ inff_dbg(CTL, "received: seq %d, timestamp %d\n", data[1],
+ le32_to_cpu(timestamp));
+ return 0;
+}
+
+static int inff_fws_notify_credit_map(struct inff_if *ifp,
+ const struct inff_event_msg *e,
+ void *data)
+{
+ struct inff_pub *drvr = ifp->drvr;
+ struct inff_fws_info *fws = drvr_to_fws(drvr);
+ int i;
+ u8 *credits = data;
+
+ if (e->datalen < INFF_FWS_FIFO_COUNT) {
+ iphy_err(drvr, "event payload too small (%d)\n", e->datalen);
+ return -EINVAL;
+ }
+
+ fws->creditmap_received = true;
+
+ inff_dbg(TRACE, "Event FIFO_CREDIT_MAP (%d) credit map for %pM\n",
+ e->event_code, credits);
+ inff_fws_lock(fws);
+ for (i = 0; i < ARRAY_SIZE(fws->fifo_credit); i++) {
+ fws->fifo_credit[i] += credits[i] - fws->init_fifo_credit[i];
+ fws->init_fifo_credit[i] = credits[i];
+ if (fws->fifo_credit[i] > 0)
+ fws->fifo_credit_map |= 1 << i;
+ else
+ fws->fifo_credit_map &= ~(1 << i);
+ WARN_ONCE(fws->fifo_credit[i] < 0,
+ "fifo_credit[%d] is negative(%d)\n", i,
+ fws->fifo_credit[i]);
+ }
+ inff_fws_schedule_deq(fws);
+ inff_fws_unlock(fws);
+ return 0;
+}
+
+static void inff_creditmap_query_worker(struct work_struct *work)
+{
+ struct inff_fws_info *fws = container_of(work, struct inff_fws_info, creditmap_query_work);
+ struct inff_if *ifp = inff_get_ifp(fws->drvr, 0);
+ struct inff_ohd_creditmap credit_buff;
+ int ret, i = 0;
+ u8 credits[INFF_FWS_FIFO_COUNT];
+ struct inff_event_msg msg;
+
+ if (fws->creditmap_received)
+ return;
+
+ memset(&credit_buff, 0, sizeof(credit_buff));
+ ret = inff_fwcmd_data_get(ifp, ACW74XX_OHD_IOCTL_GET_CREDIT_INFO,
+ &credit_buff, sizeof(credit_buff));
+
+ if (ret < 0) {
+ inff_err("ACW74XX_OHD_IOCTL_GET_CREDIT_INFO failed\n");
+ } else {
+ for (i = 0; i < credit_buff.num_acs; i++) {
+ inff_dbg(TRACE, " FIFO %d Credits %d\n", i, credit_buff.fifo_credit[i]);
+ credits[i] = credit_buff.fifo_credit[i];
+ }
+ msg.datalen = INFF_FWS_FIFO_COUNT;
+ inff_fws_notify_credit_map(ifp, &msg, credits);
+ }
+}
+
+void inff_creditmap_query(struct inff_fws_info *fws)
+{
+ if (fws && fws->creditmap_query_work.func) {
+ if (!work_busy(&fws->creditmap_query_work))
+ schedule_work(&fws->creditmap_query_work);
+ }
+}
+
+static int inff_fws_notify_bcmc_credit_support(struct inff_if *ifp,
+ const struct inff_event_msg *e,
+ void *data)
+{
+ struct inff_fws_info *fws = drvr_to_fws(ifp->drvr);
+
+ if (fws) {
+ inff_fws_lock(fws);
+ fws->bcmc_credit_check = true;
+ inff_fws_unlock(fws);
+ }
+ return 0;
+}
+
+static void inff_rxreorder_get_skb_list(struct inff_ampdu_rx_reorder *rfi,
+ u8 start, u8 end,
+ struct sk_buff_head *skb_list)
+{
+ /* initialize return list */
+ __skb_queue_head_init(skb_list);
+
+ if (rfi->pend_pkts == 0) {
+ inff_dbg(INFO, "no packets in reorder queue\n");
+ return;
+ }
+
+ do {
+ if (rfi->pktslots[start]) {
+ __skb_queue_tail(skb_list, rfi->pktslots[start]);
+ rfi->pktslots[start] = NULL;
+ }
+ start++;
+ if (start > rfi->max_idx)
+ start = 0;
+ } while (start != end);
+ rfi->pend_pkts -= skb_queue_len(skb_list);
+}
+
+void inff_fws_rxreorder(struct inff_if *ifp, struct sk_buff *pkt, bool inirq)
+{
+ struct inff_pub *drvr = ifp->drvr;
+ u8 *reorder_data;
+ u8 flow_id, max_idx, cur_idx, exp_idx, end_idx;
+ struct inff_ampdu_rx_reorder *rfi;
+ struct sk_buff_head reorder_list;
+ struct sk_buff *pnext;
+ u8 flags;
+
+ reorder_data = ((struct inff_skb_reorder_data *)pkt->cb)->reorder;
+ flow_id = reorder_data[INFF_RXREORDER_FLOWID_OFFSET];
+ flags = reorder_data[INFF_RXREORDER_FLAGS_OFFSET];
+
+ /* validate flags and flow id */
+ if (flags == 0xFF) {
+ iphy_err(drvr, "invalid flags...so ignore this packet\n");
+ inff_net_rx(ifp, pkt, inirq);
+ return;
+ }
+
+ rfi = ifp->drvr->reorder_flows[flow_id];
+ if (flags & INFF_RXREORDER_DEL_FLOW) {
+ inff_dbg(INFO, "flow-%d: delete\n",
+ flow_id);
+
+ if (!rfi) {
+ inff_dbg(INFO, "received flags to cleanup, but no flow (%d) yet\n",
+ flow_id);
+ inff_net_rx(ifp, pkt, inirq);
+ return;
+ }
+
+ inff_rxreorder_get_skb_list(rfi, rfi->exp_idx, rfi->exp_idx,
+ &reorder_list);
+ /* add the last packet */
+ __skb_queue_tail(&reorder_list, pkt);
+ kfree(rfi);
+ ifp->drvr->reorder_flows[flow_id] = NULL;
+ goto netif_rx;
+ }
+ /* from here on we need a flow reorder instance */
+ if (!rfi) {
+ max_idx = reorder_data[INFF_RXREORDER_MAXIDX_OFFSET];
+
+ /* allocate space for flow reorder info */
+ inff_dbg(INFO, "flow-%d: start, maxidx %d\n",
+ flow_id, max_idx);
+ rfi = kzalloc_flex(*rfi, pktslots, max_idx + 1,
+ GFP_ATOMIC);
+ if (!rfi) {
+ inff_net_rx(ifp, pkt, inirq);
+ return;
+ }
+
+ ifp->drvr->reorder_flows[flow_id] = rfi;
+ rfi->max_idx = max_idx;
+ }
+ if (flags & INFF_RXREORDER_NEW_HOLE) {
+ if (rfi->pend_pkts) {
+ inff_rxreorder_get_skb_list(rfi, rfi->exp_idx,
+ rfi->exp_idx,
+ &reorder_list);
+ WARN_ON(rfi->pend_pkts);
+ } else {
+ __skb_queue_head_init(&reorder_list);
+ }
+ rfi->cur_idx = reorder_data[INFF_RXREORDER_CURIDX_OFFSET];
+ rfi->exp_idx = reorder_data[INFF_RXREORDER_EXPIDX_OFFSET];
+ rfi->max_idx = reorder_data[INFF_RXREORDER_MAXIDX_OFFSET];
+ rfi->pktslots[rfi->cur_idx] = pkt;
+ rfi->pend_pkts++;
+ inff_dbg(DATA, "flow-%d: new hole %d (%d), pending %d\n",
+ flow_id, rfi->cur_idx, rfi->exp_idx, rfi->pend_pkts);
+ } else if (flags & INFF_RXREORDER_CURIDX_VALID) {
+ cur_idx = reorder_data[INFF_RXREORDER_CURIDX_OFFSET];
+ exp_idx = reorder_data[INFF_RXREORDER_EXPIDX_OFFSET];
+
+ if (exp_idx == rfi->exp_idx && cur_idx != rfi->exp_idx) {
+ /* still in the current hole */
+ /* enqueue the current on the buffer chain */
+ if (rfi->pktslots[cur_idx]) {
+ inff_dbg(INFO, "HOLE: ERROR buffer pending..free it\n");
+ inff_pkt_buf_free_skb(rfi->pktslots[cur_idx]);
+ rfi->pktslots[cur_idx] = NULL;
+ rfi->pend_pkts--;
+ }
+ rfi->pktslots[cur_idx] = pkt;
+ rfi->pend_pkts++;
+ rfi->cur_idx = cur_idx;
+ inff_dbg(DATA, "flow-%d: store pkt %d (%d), pending %d\n",
+ flow_id, cur_idx, exp_idx, rfi->pend_pkts);
+
+ /* can return now as there is no reorder
+ * list to process.
+ */
+ return;
+ }
+ if (rfi->exp_idx == cur_idx) {
+ if (rfi->pktslots[cur_idx]) {
+ inff_dbg(INFO, "error buffer pending..free it\n");
+ inff_pkt_buf_free_skb(rfi->pktslots[cur_idx]);
+ rfi->pktslots[cur_idx] = NULL;
+ rfi->pend_pkts--;
+ }
+ rfi->pktslots[cur_idx] = pkt;
+ rfi->pend_pkts++;
+
+ /* got the expected one. flush from current to expected
+ * and update expected
+ */
+ inff_dbg(DATA, "flow-%d: expected %d (%d), pending %d\n",
+ flow_id, cur_idx, exp_idx, rfi->pend_pkts);
+
+ rfi->cur_idx = cur_idx;
+ rfi->exp_idx = exp_idx;
+
+ inff_rxreorder_get_skb_list(rfi, cur_idx, exp_idx,
+ &reorder_list);
+ inff_dbg(DATA, "flow-%d: freeing buffers %d, pending %d\n",
+ flow_id, skb_queue_len(&reorder_list),
+ rfi->pend_pkts);
+ } else {
+ u8 end_idx;
+
+ inff_dbg(DATA, "flow-%d (0x%x): both moved, old %d/%d, new %d/%d\n",
+ flow_id, flags, rfi->cur_idx, rfi->exp_idx,
+ cur_idx, exp_idx);
+ if (flags & INFF_RXREORDER_FLUSH_ALL)
+ end_idx = rfi->exp_idx;
+ else
+ end_idx = exp_idx;
+
+ /* flush pkts first */
+ inff_rxreorder_get_skb_list(rfi, rfi->exp_idx, end_idx,
+ &reorder_list);
+
+ if (exp_idx == ((cur_idx + 1) % (rfi->max_idx + 1))) {
+ __skb_queue_tail(&reorder_list, pkt);
+ } else {
+ rfi->pktslots[cur_idx] = pkt;
+ rfi->pend_pkts++;
+ }
+ rfi->exp_idx = exp_idx;
+ rfi->cur_idx = cur_idx;
+ }
+ } else {
+ /* explicitly window move updating the expected index */
+ exp_idx = reorder_data[INFF_RXREORDER_EXPIDX_OFFSET];
+
+ inff_dbg(DATA, "flow-%d (0x%x): change expected: %d -> %d\n",
+ flow_id, flags, rfi->exp_idx, exp_idx);
+ if (flags & INFF_RXREORDER_FLUSH_ALL)
+ end_idx = rfi->exp_idx;
+ else
+ end_idx = exp_idx;
+
+ inff_rxreorder_get_skb_list(rfi, rfi->exp_idx, end_idx,
+ &reorder_list);
+ __skb_queue_tail(&reorder_list, pkt);
+ /* set the new expected idx */
+ rfi->exp_idx = exp_idx;
+ }
+netif_rx:
+ skb_queue_walk_safe(&reorder_list, pkt, pnext) {
+ __skb_unlink(pkt, &reorder_list);
+ inff_net_rx(ifp, pkt, inirq);
+ }
+}
+
+void inff_fws_hdrpull(struct inff_if *ifp, s16 siglen, struct sk_buff *skb)
+{
+ struct inff_skb_reorder_data *rd;
+ struct inff_fws_info *fws = drvr_to_fws(ifp->drvr);
+ u8 *signal_data;
+ s16 data_len;
+ u8 type;
+ s16 len;
+ u8 *data;
+ s32 status;
+ s32 err;
+
+ inff_dbg(HDRS, "ifidx %d, skblen %u, sig %d\n",
+ ifp->ifidx, skb->len, siglen);
+
+ WARN_ON(siglen > skb->len);
+
+ if (siglen > skb->len)
+ siglen = skb->len;
+
+ if (!siglen)
+ return;
+ /* if flow control disabled, skip to packet data and leave */
+ if (!fws || !fws->fw_signals) {
+ skb_pull(skb, siglen);
+ return;
+ }
+
+ fws->stats.header_pulls++;
+ data_len = siglen;
+ signal_data = skb->data;
+
+ status = INFF_FWS_RET_OK_NOSCHEDULE;
+ while (data_len > 0) {
+ /* extract tlv info */
+ type = signal_data[0];
+
+ /* FILLER type is actually not a TLV, but
+ * a single byte that can be skipped.
+ */
+ if (type == INFF_FWS_TYPE_FILLER) {
+ signal_data += 1;
+ data_len -= 1;
+ continue;
+ }
+ len = signal_data[1];
+ data = signal_data + 2;
+
+ inff_dbg(HDRS, "tlv type=%s (%d), len=%d (%d)\n",
+ inff_fws_get_tlv_name(type), type, len,
+ inff_fws_get_tlv_len(fws, type));
+
+ /* abort parsing when length invalid */
+ if (data_len < len + 2)
+ break;
+
+ if (len < inff_fws_get_tlv_len(fws, type))
+ break;
+
+ err = INFF_FWS_RET_OK_NOSCHEDULE;
+ switch (type) {
+ case INFF_FWS_TYPE_HOST_REORDER_RXPKTS:
+ rd = (struct inff_skb_reorder_data *)skb->cb;
+ rd->reorder = data;
+ break;
+ case INFF_FWS_TYPE_MACDESC_ADD:
+ case INFF_FWS_TYPE_MACDESC_DEL:
+ inff_fws_macdesc_indicate(fws, type, data);
+ break;
+ case INFF_FWS_TYPE_MAC_OPEN:
+ case INFF_FWS_TYPE_MAC_CLOSE:
+ err = inff_fws_macdesc_state_indicate(fws, type, data);
+ break;
+ case INFF_FWS_TYPE_INTERFACE_OPEN:
+ case INFF_FWS_TYPE_INTERFACE_CLOSE:
+ err = inff_fws_interface_state_indicate(fws, type,
+ data);
+ break;
+ case INFF_FWS_TYPE_MAC_REQUEST_CREDIT:
+ case INFF_FWS_TYPE_MAC_REQUEST_PACKET:
+ err = inff_fws_request_indicate(fws, type, data);
+ break;
+ case INFF_FWS_TYPE_TXSTATUS:
+ case INFF_FWS_TYPE_COMP_TXSTATUS:
+ inff_fws_txstatus_indicate(fws, type, data);
+ break;
+ case INFF_FWS_TYPE_FIFO_CREDITBACK:
+ err = inff_fws_fifocreditback_indicate(fws, len, data);
+ break;
+ case INFF_FWS_TYPE_RSSI:
+ inff_fws_rssi_indicate(fws, *data);
+ break;
+ case INFF_FWS_TYPE_TRANS_ID:
+ inff_fws_dbg_seqnum_check(fws, data);
+ break;
+ case INFF_FWS_TYPE_PKTTAG:
+ case INFF_FWS_TYPE_PENDING_TRAFFIC_BMP:
+ default:
+ fws->stats.tlv_invalid_type++;
+ break;
+ }
+ if (err == INFF_FWS_RET_OK_SCHEDULE)
+ status = INFF_FWS_RET_OK_SCHEDULE;
+ signal_data += len + 2;
+ data_len -= len + 2;
+ }
+
+ if (data_len != 0)
+ fws->stats.tlv_parse_failed++;
+
+ if (status == INFF_FWS_RET_OK_SCHEDULE)
+ inff_fws_schedule_deq(fws);
+
+ /* signalling processing result does
+ * not affect the actual ethernet packet.
+ */
+ skb_pull(skb, siglen);
+
+ /* this may be a signal-only packet
+ */
+ if (skb->len == 0)
+ fws->stats.header_only_pkt++;
+}
+
+static u8 inff_fws_precommit_skb(struct inff_fws_info *fws, int fifo,
+ struct sk_buff *p)
+{
+ struct inff_skbuff_cb *skcb = inff_skbcb(p);
+ struct inff_fws_mac_descriptor *entry = skcb->mac;
+ u8 flags;
+
+ if (skcb->state != INFF_FWS_SKBSTATE_SUPPRESSED)
+ inff_skb_htod_tag_set_field(p, GENERATION, entry->generation);
+ flags = INFF_FWS_HTOD_FLAG_PKTFROMHOST;
+ if (inff_skb_if_flags_get_field(p, REQUESTED)) {
+ /*
+ * Indicate that this packet is being sent in response to an
+ * explicit request from the firmware side.
+ */
+ flags |= INFF_FWS_HTOD_FLAG_PKT_REQUESTED;
+ }
+ inff_skb_htod_tag_set_field(p, FLAGS, flags);
+ return inff_fws_hdrpush(fws, p);
+}
+
+static void inff_fws_rollback_toq(struct inff_fws_info *fws,
+ struct sk_buff *skb, int fifo)
+{
+ struct inff_pub *drvr = fws->drvr;
+ struct inff_fws_mac_descriptor *entry;
+ struct sk_buff *pktout;
+ int qidx;
+ int rc = 0;
+
+ entry = inff_skbcb(skb)->mac;
+ if (entry->occupied) {
+ qidx = 2 * fifo;
+ if (inff_skbcb(skb)->state == INFF_FWS_SKBSTATE_SUPPRESSED)
+ qidx++;
+
+ pktout = inff_pktq_penq_head(&entry->psq, qidx, skb);
+ if (!pktout) {
+ iphy_err(drvr, "%s queue %d full\n", entry->name, qidx);
+ rc = -ENOSPC;
+ }
+ } else {
+ iphy_err(drvr, "%s entry removed\n", entry->name);
+ rc = -ENOENT;
+ }
+
+ if (rc) {
+ fws->stats.rollback_failed++;
+ inff_fws_txs_process_host_tossed(fws, skb, true, false);
+ } else {
+ fws->stats.rollback_success++;
+ inff_fws_return_credits(fws, fifo, 1);
+ inff_fws_macdesc_return_req_credit(skb);
+ }
+}
+
+static int inff_fws_borrow_credit(struct inff_fws_info *fws,
+ int highest_lender_ac, int borrower_ac,
+ bool borrow_all)
+{
+ int lender_ac, borrow_limit = 0;
+
+ for (lender_ac = 0; lender_ac <= highest_lender_ac; lender_ac++) {
+ if (!borrow_all)
+ borrow_limit =
+ fws->init_fifo_credit[lender_ac] / INFF_BORROW_RATIO;
+ else
+ borrow_limit = 0;
+
+ if (fws->fifo_credit[lender_ac] > borrow_limit) {
+ fws->credits_borrowed[borrower_ac][lender_ac]++;
+ fws->fifo_credit[lender_ac]--;
+ if (fws->fifo_credit[lender_ac] == 0)
+ fws->fifo_credit_map &= ~(1 << lender_ac);
+ fws->fifo_credit_map |= (1 << borrower_ac);
+ inff_dbg(DATA, "borrow credit from: %d\n", lender_ac);
+ return 0;
+ }
+ }
+ fws->fifo_credit_map &= ~(1 << borrower_ac);
+ return -ENAVAIL;
+}
+
+static int inff_fws_commit_skb(struct inff_fws_info *fws, int fifo,
+ struct sk_buff *skb)
+{
+ struct inff_skbuff_cb *skcb = inff_skbcb(skb);
+ struct inff_fws_mac_descriptor *entry;
+ int rc;
+ u8 ifidx;
+ u8 data_offset;
+
+ entry = skcb->mac;
+ if (IS_ERR(entry))
+ return PTR_ERR(entry);
+
+ data_offset = inff_fws_precommit_skb(fws, fifo, skb);
+ entry->transit_count++;
+ if (entry->suppressed)
+ entry->suppr_transit_count++;
+ ifidx = inff_skb_if_flags_get_field(skb, INDEX);
+ inff_fws_unlock(fws);
+ rc = inff_proto_txdata(fws->drvr, ifidx, data_offset, skb);
+ inff_fws_lock(fws);
+ inff_dbg(DATA, "%s flags %X htod %X bus_tx %d\n", entry->name,
+ skcb->if_flags, skcb->htod, rc);
+ if (rc < 0) {
+ entry->transit_count--;
+ if (entry->suppressed)
+ entry->suppr_transit_count--;
+ (void)inff_proto_hdrpull(fws->drvr, false, skb, NULL);
+ goto rollback;
+ }
+
+ fws->stats.pkt2bus++;
+ fws->stats.send_pkts[fifo]++;
+ if (inff_skb_if_flags_get_field(skb, REQUESTED))
+ fws->stats.requested_sent[fifo]++;
+
+ return rc;
+
+rollback:
+ inff_fws_rollback_toq(fws, skb, fifo);
+ return rc;
+}
+
+static int inff_fws_assign_htod(struct inff_fws_info *fws, struct sk_buff *p,
+ int fifo)
+{
+ struct inff_skbuff_cb *skcb = inff_skbcb(p);
+ int rc = 0, hslot;
+
+ skcb->htod = 0;
+ skcb->htod_seq = 0;
+ if (INFF_FWS_MODE_GET_AFQ(fws->wlfc_mode))
+ hslot = (uint)(skcb->mac - &fws->desc.nodes[0]);
+ else
+ hslot = inff_fws_hanger_get_free_slot(&fws->hanger);
+ inff_skb_htod_tag_set_field(p, HSLOT, hslot);
+ inff_skb_htod_tag_set_field(p, FREERUN, skcb->mac->seq[fifo]);
+ inff_skb_htod_tag_set_field(p, FIFO, fifo);
+ if (!INFF_FWS_MODE_GET_AFQ(fws->wlfc_mode))
+ rc = inff_fws_hanger_pushpkt(&fws->hanger, p, hslot);
+ if (!rc)
+ skcb->mac->seq[fifo]++;
+ else
+ fws->stats.generic_error++;
+ return rc;
+}
+
+int inff_fws_process_skb(struct inff_if *ifp, struct sk_buff *skb)
+{
+ struct inff_pub *drvr = ifp->drvr;
+ struct inff_fws_info *fws = drvr_to_fws(drvr);
+ struct inff_skbuff_cb *skcb = inff_skbcb(skb);
+ struct ethhdr *eh = (struct ethhdr *)(skb->data);
+ int fifo = INFF_FWS_FIFO_BCMC;
+ bool multicast = is_multicast_ether_addr(eh->h_dest);
+ int rc = 0;
+
+ inff_dbg(DATA, "tx proto=0x%X\n", ntohs(eh->h_proto));
+
+ /* set control buffer information */
+ skcb->if_flags = 0;
+ skcb->state = INFF_FWS_SKBSTATE_NEW;
+ inff_skb_if_flags_set_field(skb, INDEX, ifp->ifidx);
+
+ /* mapping from 802.1d priority to firmware fifo index */
+ if (!multicast)
+ fifo = inff_map_prio_to_aci(drvr->config, skb->priority);
+
+ inff_fws_lock(fws);
+ if (fifo != INFF_FWS_FIFO_AC_BE && fifo < INFF_FWS_FIFO_BCMC)
+ fws->borrow_defer_timestamp = jiffies +
+ INFF_FWS_BORROW_DEFER_PERIOD;
+
+ skcb->mac = inff_fws_macdesc_find(fws, ifp, eh->h_dest);
+ inff_dbg(DATA, "%s mac %pM multi %d fifo %d\n", skcb->mac->name,
+ eh->h_dest, multicast, fifo);
+ if (!inff_fws_assign_htod(fws, skb, fifo)) {
+ inff_fws_enq(fws, INFF_FWS_SKBSTATE_DELAYED, fifo, skb);
+ inff_fws_schedule_deq(fws);
+ } else {
+ iphy_err(drvr, "no hanger slot available\n");
+ rc = -ENOMEM;
+ }
+ inff_fws_unlock(fws);
+
+ return rc;
+}
+
+void inff_fws_reset_interface(struct inff_if *ifp)
+{
+ struct inff_fws_mac_descriptor *entry = ifp->fws_desc;
+
+ if (entry)
+ inff_fws_macdesc_init(entry, ifp->mac_addr, ifp->ifidx);
+}
+
+void inff_fws_add_interface(struct inff_if *ifp)
+{
+ struct inff_fws_info *fws = drvr_to_fws(ifp->drvr);
+ struct inff_fws_mac_descriptor *entry;
+
+ if (!ifp->ndev || !inff_fws_queue_skbs(fws))
+ return;
+
+ entry = &fws->desc.iface[ifp->ifidx];
+ ifp->fws_desc = entry;
+ inff_fws_macdesc_init(entry, ifp->mac_addr, ifp->ifidx);
+ inff_fws_macdesc_set_name(fws, entry);
+ inff_pktq_init(&entry->psq,
+ INFF_FWS_PSQ_PREC_COUNT,
+ fws->fws_psq_len);
+ if (INFF_FWS_MODE_GET_AFQ(fws->wlfc_mode))
+ inff_pktq_init(&entry->afq, INFF_FWS_AFQ_PREC_COUNT,
+ INFF_FWS_AFQ_LEN);
+ inff_dbg(TRACE, "added %s\n", entry->name);
+}
+
+void inff_fws_del_interface(struct inff_if *ifp)
+{
+ struct inff_fws_mac_descriptor *entry = ifp->fws_desc;
+ struct inff_fws_info *fws = drvr_to_fws(ifp->drvr);
+
+ if (!entry)
+ return;
+
+ inff_fws_lock(fws);
+ ifp->fws_desc = NULL;
+ inff_dbg(TRACE, "deleting %s\n", entry->name);
+ inff_fws_macdesc_cleanup(fws, &fws->desc.iface[ifp->ifidx],
+ ifp->ifidx);
+ inff_fws_macdesc_deinit(entry);
+ inff_fws_cleanup(fws, ifp->ifidx);
+ inff_fws_unlock(fws);
+}
+
+static void inff_fws_dequeue_worker(struct work_struct *worker)
+{
+ struct inff_fws_info *fws;
+ struct inff_pub *drvr;
+ struct sk_buff *skb;
+ int fifo;
+ u32 hslot;
+ u32 ifidx;
+ int ret;
+ u32 highest_lender = 0;
+
+ fws = container_of(worker, struct inff_fws_info, fws_dequeue_work);
+ drvr = fws->drvr;
+
+ inff_fws_lock(fws);
+ for (fifo = INFF_FWS_FIFO_BCMC; fifo >= 0 && !fws->bus_flow_blocked;
+ fifo--) {
+ if (!inff_fws_fc_active(fws)) {
+ while ((skb = inff_fws_deq(fws, fifo)) != NULL) {
+ hslot = inff_skb_htod_tag_get_field(skb,
+ HSLOT);
+ if (!INFF_FWS_MODE_GET_AFQ(fws->wlfc_mode))
+ inff_fws_hanger_poppkt(&fws->hanger,
+ hslot, &skb,
+ true);
+ ifidx = inff_skb_if_flags_get_field(skb,
+ INDEX);
+ /* Use proto layer to send data frame */
+ inff_fws_unlock(fws);
+ ret = inff_proto_txdata(drvr, ifidx, 0, skb);
+ inff_fws_lock(fws);
+ if (ret < 0)
+ inff_txfinalize(inff_get_ifp(drvr,
+ ifidx),
+ skb, false);
+ if (fws->bus_flow_blocked)
+ break;
+ }
+ continue;
+ }
+
+ while ((fws->fifo_credit[fifo]) ||
+ ((!fws->bcmc_credit_check) &&
+ (fifo == INFF_FWS_FIFO_BCMC))) {
+ skb = inff_fws_deq(fws, fifo);
+ if (!skb)
+ break;
+ fws->fifo_credit[fifo]--;
+ if (inff_fws_commit_skb(fws, fifo, skb))
+ break;
+ if (fws->bus_flow_blocked)
+ break;
+ }
+
+ if (fifo >= INFF_FWS_FIFO_AC_BK &&
+ fifo <= INFF_FWS_FIFO_AC_VO &&
+ fws->fifo_credit[fifo] == 0 &&
+ !fws->bus_flow_blocked) {
+ highest_lender = fifo - 1;
+
+ /* Borrow Credit for BK access category from Higer AC queues */
+ if (fifo == INFF_FWS_FIFO_AC_BK)
+ highest_lender = INFF_FWS_FIFO_AC_BE;
+
+ while (inff_fws_borrow_credit(fws, highest_lender,
+ fifo, true) == 0) {
+ skb = inff_fws_deq(fws, fifo);
+ if (!skb) {
+ inff_fws_return_credits(fws, fifo, 1);
+ break;
+ }
+ if (inff_fws_commit_skb(fws, fifo, skb))
+ break;
+ if (fws->bus_flow_blocked)
+ break;
+ }
+ }
+ }
+ inff_fws_unlock(fws);
+}
+
+#ifdef DEBUG
+static int inff_debugfs_fws_stats_read(struct seq_file *seq, void *data)
+{
+ struct inff_bus *bus_if = dev_get_drvdata(seq->private);
+ struct inff_fws_info *fws = drvr_to_fws(bus_if->drvr);
+ struct inff_fws_stats *fwstats = &fws->stats;
+
+ seq_printf(seq,
+ "header_pulls: %8u\t"
+ "header_only_pkt: %8u\n"
+ "tlv_parse_failed:%8u\t"
+ "tlv_invalid_type:%8u\n"
+ "mac_update_fails:%8u\t"
+ "ps_update_fails: %8u\t"
+ "if_update_fails: %8u\n"
+ "pkt2bus: %8u\t"
+ "generic_error: %8u\n"
+ "rollback_success:%8u\t"
+ "rollback_failed: %8u\n"
+ "delayq_full: %8u\t"
+ "supprq_full: %8u\n"
+ "txs_indicate: %8u\t"
+ "txs_discard: %8u\n"
+ "txs_suppr_core: %8u\t"
+ "txs_suppr_ps: %8u\n"
+ "txs_tossed: %8u\t"
+ "txs_expired: %8u\n"
+ "txs_dropped: %8u\t"
+ "txs_mktfree: %8u\n"
+ "txs_unknown: %8u\t"
+ "txs_host_tossed: %8u\n"
+ "bus_flow_block: %8u\t"
+ "fws_flow_block: %8u\n",
+ fwstats->header_pulls,
+ fwstats->header_only_pkt,
+ fwstats->tlv_parse_failed,
+ fwstats->tlv_invalid_type,
+ fwstats->mac_update_failed,
+ fwstats->mac_ps_update_failed,
+ fwstats->if_update_failed,
+ fwstats->pkt2bus,
+ fwstats->generic_error,
+ fwstats->rollback_success,
+ fwstats->rollback_failed,
+ fwstats->delayq_full_error,
+ fwstats->supprq_full_error,
+ fwstats->txs_indicate,
+ fwstats->txs_discard,
+ fwstats->txs_supp_core,
+ fwstats->txs_supp_ps,
+ fwstats->txs_tossed,
+ fwstats->txs_expired,
+ fwstats->txs_dropped,
+ fwstats->txs_mktfree,
+ fwstats->txs_unknown,
+ fwstats->txs_host_tossed,
+ fwstats->bus_flow_block,
+ fwstats->fws_flow_block);
+
+ seq_printf(seq,
+ "receive error: %8u\t"
+ "cleanup if: %8u\n\n",
+ fwstats->cnt_recv_err,
+ fwstats->cnt_cleanup_if);
+
+ seq_printf(seq,
+ "send_pkts: BK:%u BE:%u VO:%u VI:%u BCMC:%u\n"
+ "requested_sent: BK:%u BE:%u VO:%u VI:%u BCMC:%u\n\n",
+ fwstats->send_pkts[0], fwstats->send_pkts[1],
+ fwstats->send_pkts[2], fwstats->send_pkts[3],
+ fwstats->send_pkts[4],
+ fwstats->requested_sent[0],
+ fwstats->requested_sent[1],
+ fwstats->requested_sent[2],
+ fwstats->requested_sent[3],
+ fwstats->requested_sent[4]);
+
+ return 0;
+}
+#else
+static int inff_debugfs_fws_stats_read(struct seq_file *seq, void *data)
+{
+ return 0;
+}
+#endif
+
+struct inff_fws_info *inff_fws_attach(struct inff_pub *drvr)
+{
+ struct inff_chip *ci = drvr->bus_if->chip_pub;
+ struct inff_fws_info *fws;
+ struct inff_if *ifp;
+ u32 tlv = INFF_FWS_FLAGS_RSSI_SIGNALS;
+ int rc;
+ u32 mode = 0, fw_caps;
+
+ fws = kzalloc_obj(*fws);
+ if (!fws) {
+ rc = -ENOMEM;
+ goto fail;
+ }
+
+ spin_lock_init(&fws->spinlock);
+
+ /* store drvr reference */
+ fws->drvr = drvr;
+ fws->fcmode = drvr->settings->fcmode;
+ fws->fws_psq_len = INFF_FWS_PSQ_LEN;
+ fws->fws_psq_hi_water = INFF_FWS_FLOWCONTROL_HIWATER;
+ fws->fws_psq_low_water = INFF_FWS_FLOWCONTROL_LOWATER;
+
+ if (!drvr->bus_if->always_use_fws_queue &&
+ fws->fcmode == INFF_FWS_FCMODE_NONE) {
+ fws->avoid_queueing = true;
+ inff_dbg(INFO, "FWS queueing will be avoided\n");
+ return fws;
+ }
+
+ fws->fws_wq = create_singlethread_workqueue("inff_fws_wq");
+ if (!fws->fws_wq) {
+ iphy_err(drvr, "workqueue creation failed\n");
+ rc = -EBADF;
+ goto fail;
+ }
+ INIT_WORK(&fws->fws_dequeue_work, inff_fws_dequeue_worker);
+
+ /* enable firmware signalling if fcmode active */
+ if (fws->fcmode != INFF_FWS_FCMODE_NONE)
+ tlv |= INFF_FWS_FLAGS_XONXOFF_SIGNALS |
+ INFF_FWS_FLAGS_CREDIT_STATUS_SIGNALS |
+ INFF_FWS_FLAGS_HOST_PROPTXSTATUS_ACTIVE |
+ INFF_FWS_FLAGS_HOST_RXREORDER_ACTIVE;
+
+ if (ci->chip == INF_CC_ACW74XX_CHIP_ID) {
+ INIT_WORK(&fws->creditmap_query_work, inff_creditmap_query_worker);
+ } else {
+ rc = inff_fwevt_register(drvr, INFF_E_FIFO_CREDIT_MAP,
+ inff_fws_notify_credit_map);
+ if (rc < 0) {
+ iphy_err(drvr, "register credit map handler failed\n");
+ goto fail;
+ }
+ rc = inff_fwevt_register(drvr, INFF_E_BCMC_CREDIT_SUPPORT,
+ inff_fws_notify_bcmc_credit_support);
+ if (rc < 0) {
+ iphy_err(drvr, "register bcmc credit handler failed\n");
+ inff_fwevt_unregister(drvr, INFF_E_FIFO_CREDIT_MAP);
+ goto fail;
+ }
+ }
+
+ /* Setting the iovar may fail if feature is unsupported
+ * so leave the rc as is so driver initialization can
+ * continue. Set mode back to none indicating not enabled.
+ */
+ fws->fw_signals = true;
+ ifp = inff_get_ifp(drvr, 0);
+ if (inff_fwcmd_iovar_int_set(ifp, "tlv", tlv)) {
+ iphy_err(drvr, "failed to set bdcv2 tlv signaling\n");
+ fws->fcmode = INFF_FWS_FCMODE_NONE;
+ fws->fw_signals = false;
+ }
+
+ if (inff_fwcmd_iovar_int_set(ifp, "ampdu_hostreorder", 1))
+ inff_dbg(INFO, "enabling AMPDU host-reorder failed\n");
+
+ /* Enable seq number reuse, if supported */
+ rc = 0;
+ if (inff_fwcmd_iovar_int_get(ifp, "wlfc_mode", &fw_caps) == 0) {
+ inff_dbg(INFO, "wlfc_mode fw_caps=0x%x\n", fw_caps);
+
+ if (INFF_FWS_MODE_IS_OLD_DEF(fw_caps)) {
+ mode = INFF_FWS_MODE_AFQ;
+ } else {
+ u8 afq = INFF_FWS_MODE_GET_AFQ(fw_caps);
+ u8 reordersupp = INFF_FWS_MODE_GET_REORDERSUPP(fw_caps);
+
+ mode = inff_fws_mode_set_afq(mode, afq);
+ mode = inff_fws_mode_set_reordersupp(mode, reordersupp);
+ }
+
+ if (!drvr->settings->afq_enable) {
+ if (INFF_FWS_MODE_IS_OLD_DEF(fw_caps))
+ mode = INFF_FWS_MODE_HANGER;
+ else
+ mode = inff_fws_mode_set_afq(mode, 0);
+ }
+
+ rc = inff_fwcmd_iovar_int_set(ifp, "wlfc_mode", mode);
+ if (INFF_FWS_MODE_GET_REUSESEQ(fw_caps)) {
+ INFF_FWS_MODE_SET_REUSESEQ(fws->mode, 1);
+ if (rc >= 0)
+ INFF_FWS_MODE_SET_REUSESEQ(mode, 1);
+ }
+ }
+
+ fws->wlfc_mode = 0;
+ if (rc >= 0) {
+ if (INFF_FWS_MODE_IS_OLD_DEF(mode))
+ fws->wlfc_mode = inff_fws_mode_set_afq(fws->wlfc_mode,
+ mode == INFF_FWS_MODE_AFQ);
+ else
+ fws->wlfc_mode = mode;
+ } else {
+ iphy_err(drvr, "set wlfc_mode failed, rc=%d\n", rc);
+ goto fail;
+ }
+
+ if (!INFF_FWS_MODE_GET_AFQ(fws->wlfc_mode))
+ inff_fws_hanger_init(&fws->hanger);
+ inff_fws_macdesc_init(&fws->desc.other, NULL, 0);
+ inff_fws_macdesc_set_name(fws, &fws->desc.other);
+ inff_dbg(INFO, "added %s\n", fws->desc.other.name);
+ inff_pktq_init(&fws->desc.other.psq,
+ INFF_FWS_PSQ_PREC_COUNT,
+ fws->fws_psq_len);
+ if (INFF_FWS_MODE_GET_AFQ(fws->wlfc_mode))
+ inff_pktq_init(&fws->desc.other.afq, INFF_FWS_AFQ_PREC_COUNT,
+ INFF_FWS_AFQ_LEN);
+
+ inff_dbg(INFO, "%s bdcv2 tlv signaling [%x]\n",
+ fws->fw_signals ? "enabled" : "disabled", tlv);
+
+ if (ci->chip == INF_CC_ACW74XX_CHIP_ID) {
+ if (inff_sdio_credit_map_update(drvr->bus_if)) {
+ /* process the pending query */
+ inff_creditmap_query(fws);
+ }
+ }
+
+ return fws;
+
+fail:
+ inff_fws_detach(fws);
+ return ERR_PTR(rc);
+}
+
+void inff_fws_detach(struct inff_fws_info *fws)
+{
+ if (!fws)
+ return;
+
+ /* Cancel creditmap_query_work which uses system_wq (not fws_wq).
+ * Must cancel before kfree(fws) to prevent use-after-free.
+ */
+ /* Only cancel if work was initialized (ACW74XX chip only) */
+ if (fws->creditmap_query_work.func)
+ cancel_work_sync(&fws->creditmap_query_work);
+
+ if (fws->fws_wq)
+ destroy_workqueue(fws->fws_wq);
+
+ /* cleanup */
+ inff_fws_lock(fws);
+ inff_fws_cleanup(fws, -1);
+ inff_fws_unlock(fws);
+
+ /* free top structure */
+ kfree(fws);
+}
+
+void inff_fws_debugfs_create(struct inff_pub *drvr)
+{
+ /* create debugfs file for statistics */
+ inff_debugfs_add_entry(drvr, "fws_stats",
+ inff_debugfs_fws_stats_read);
+}
+
+bool inff_fws_queue_skbs(struct inff_fws_info *fws)
+{
+ return !fws->avoid_queueing;
+}
+
+bool inff_fws_fc_active(struct inff_fws_info *fws)
+{
+ if (!fws->creditmap_received)
+ return false;
+
+ return fws->fcmode != INFF_FWS_FCMODE_NONE;
+}
+
+void inff_fws_bustxcomplete(struct inff_fws_info *fws, struct sk_buff *skb,
+ bool success)
+{
+ if (inff_skbcb(skb)->state == INFF_FWS_SKBSTATE_TIM) {
+ inff_pkt_buf_free_skb(skb);
+ return;
+ }
+
+ if (!success) {
+ inff_fws_lock(fws);
+ inff_fws_txs_process_host_tossed(fws, skb, false, true);
+ inff_fws_unlock(fws);
+ } else {
+ inff_fws_lock(fws);
+ if (INFF_FWS_MODE_GET_AFQ(fws->wlfc_mode))
+ inff_fws_enque_afq(fws, skb);
+ inff_fws_unlock(fws);
+ }
+}
+
+/** afq = At Firmware Queue, queue containing packets pending in the dongle */
+static int inff_fws_deque_afq(struct inff_fws_info *fws, u16 hslot, u8 hcnt,
+ u8 prec, struct sk_buff **pktout)
+{
+ struct inff_fws_mac_descriptor *entry;
+ struct pktq *pq;
+ struct sk_buff_head *q;
+ struct sk_buff *p = NULL, *b = NULL;
+
+ if (!fws)
+ return -ENAVAIL;
+
+ if (pktout)
+ *pktout = NULL;
+
+ WARN_ON(hslot >= INFF_FWS_MAC_DESC_TABLE_SIZE + INFF_MAX_IFS + 1);
+
+ if (hslot < INFF_FWS_MAC_DESC_TABLE_SIZE)
+ entry = &fws->desc.nodes[hslot];
+ else if (hslot < INFF_FWS_MAC_DESC_TABLE_SIZE + INFF_MAX_IFS)
+ entry = &fws->desc.iface[hslot - INFF_FWS_MAC_DESC_TABLE_SIZE];
+ else
+ entry = &fws->desc.other;
+
+ pq = &entry->afq;
+ if (WARN_ON(prec >= pq->num_prec))
+ return -EINVAL;
+
+ q = &pq->q[prec].skblist;
+ skb_queue_walk(q, p) {
+ if (p && hcnt != inff_skb_htod_tag_get_field(p, FREERUN))
+ b = p;
+ else
+ break;
+ }
+
+ if (p == (struct sk_buff *)q) {
+ if (b)
+ inff_dbg(SDIO, "can't find matching seq(%d)\n", hcnt);
+ else
+ inff_dbg(SDIO, "queue is empty\n");
+ return 0;
+ }
+
+ if (!b) {
+ p = inff_pktq_pdeq(pq, prec);
+ } else {
+ inff_dbg(SDIO, "out of order, seq(%d), head_seq(%d)\n", hcnt,
+ inff_skb_htod_tag_get_field(skb_peek(q), FREERUN));
+ skb_unlink(p, q);
+ pq->len--;
+ }
+
+ if (pktout)
+ *pktout = p;
+
+ return 0;
+}
+
+static int inff_fws_enque_afq(struct inff_fws_info *fws, struct sk_buff *skb)
+{
+ struct inff_fws_mac_descriptor *entry;
+ u32 hslot = inff_skb_htod_tag_get_field(skb, HSLOT);
+ u32 fifo = inff_skb_htod_tag_get_field(skb, FIFO);
+
+ if (hslot < INFF_FWS_MAC_DESC_TABLE_SIZE)
+ entry = &fws->desc.nodes[hslot];
+ else if (hslot < INFF_FWS_MAC_DESC_TABLE_SIZE + INFF_MAX_IFS)
+ entry = &fws->desc.iface[hslot - INFF_FWS_MAC_DESC_TABLE_SIZE];
+ else
+ entry = &fws->desc.other;
+
+ inff_pktq_penq(&entry->afq, fifo, skb);
+
+ return 0;
+}
+
+void inff_fws_bus_blocked(struct inff_pub *drvr, bool flow_blocked)
+{
+ struct inff_fws_info *fws = drvr_to_fws(drvr);
+ struct inff_if *ifp;
+ int i;
+
+ if (fws->avoid_queueing) {
+ for (i = 0; i < INFF_MAX_IFS; i++) {
+ ifp = drvr->iflist[i];
+ if (!ifp || !ifp->ndev)
+ continue;
+ inff_net_txflowblock(ifp, INFF_NETIF_STOP_REASON_FLOW,
+ flow_blocked);
+ }
+ } else {
+ fws->bus_flow_blocked = flow_blocked;
+ if (!flow_blocked)
+ inff_fws_schedule_deq(fws);
+ else
+ fws->stats.bus_flow_block++;
+ }
+}
+
+void inff_fws_cleanup_interface(struct inff_if *ifp)
+{
+ struct inff_fws_info *fws = drvr_to_fws(ifp->drvr);
+ struct inff_fws_mac_descriptor *entry;
+ struct inff_fws_mac_descriptor *table;
+ bool (*matchfn)(struct sk_buff *, void *) = inff_fws_ifidx_match;
+ int ifidx = ifp->ifidx;
+ int i;
+
+ if (!fws->sdio_recv_error)
+ return;
+
+ inff_fws_lock(fws);
+
+ fws->stats.cnt_cleanup_if++;
+ fws->sdio_recv_error = false;
+
+ entry = &fws->desc.iface[ifidx];
+ inff_dbg(SDIO, "iface[%d] mac %pM if %d psq len %d\n",
+ ifidx, entry->ea, entry->interface_id, entry->psq.len);
+
+ /* cleanup interface */
+ inff_fws_psq_flush(fws, &entry->psq, ifidx);
+ if (INFF_FWS_MODE_GET_AFQ(fws->wlfc_mode))
+ inff_pktq_flush(&entry->afq, true, NULL, NULL);
+ inff_fws_macdesc_reset(entry);
+
+ /* cleanup individual nodes */
+ table = &fws->desc.nodes[0];
+ for (i = 0; i < ARRAY_SIZE(fws->desc.nodes); i++)
+ inff_fws_macdesc_cleanup(fws, &table[i], ifidx);
+
+ /* cleanup txq and hanger */
+ inff_fws_bus_txq_cleanup(fws, matchfn, ifidx);
+ if (!INFF_FWS_MODE_GET_AFQ(fws->wlfc_mode))
+ inff_fws_hanger_cleanup(fws, matchfn, ifidx);
+
+ inff_fws_unlock(fws);
+}
diff --git a/drivers/net/wireless/infineon/inffmac/fwsignal.h b/drivers/net/wireless/infineon/inffmac/fwsignal.h
new file mode 100644
index 000000000000..24ef391f531f
--- /dev/null
+++ b/drivers/net/wireless/infineon/inffmac/fwsignal.h
@@ -0,0 +1,53 @@
+/* SPDX-License-Identifier: ISC */
+/*
+ * Copyright (c) 2012 Broadcom Corporation
+ *
+ * Copyright (c) 2025-2026, Infineon Technologies AG, or an affiliate of Infineon Technologies AG.
+ * All rights reserved.
+ */
+
+#ifndef INFF_FWSIGNAL_H
+#define INFF_FWSIGNAL_H
+
+/**
+ * enum inff_fws_fifo - fifo indices used by dongle firmware.
+ *
+ * @INFF_FWS_FIFO_FIRST: first fifo, ie. background.
+ * @INFF_FWS_FIFO_AC_BK: fifo for background traffic.
+ * @INFF_FWS_FIFO_AC_BE: fifo for best-effort traffic.
+ * @INFF_FWS_FIFO_AC_VI: fifo for video traffic.
+ * @INFF_FWS_FIFO_AC_VO: fifo for voice traffic.
+ * @INFF_FWS_FIFO_BCMC: fifo for broadcast/multicast (AP only).
+ * @INFF_FWS_FIFO_ATIM: fifo for ATIM (AP only).
+ * @INFF_FWS_FIFO_COUNT: number of fifos.
+ */
+enum inff_fws_fifo {
+ INFF_FWS_FIFO_FIRST,
+ INFF_FWS_FIFO_AC_BK = INFF_FWS_FIFO_FIRST,
+ INFF_FWS_FIFO_AC_BE,
+ INFF_FWS_FIFO_AC_VI,
+ INFF_FWS_FIFO_AC_VO,
+ INFF_FWS_FIFO_BCMC,
+ INFF_FWS_FIFO_ATIM,
+ INFF_FWS_FIFO_COUNT
+};
+
+struct inff_fws_info *inff_fws_attach(struct inff_pub *drvr);
+void inff_fws_detach(struct inff_fws_info *fws);
+void inff_fws_debugfs_create(struct inff_pub *drvr);
+bool inff_fws_queue_skbs(struct inff_fws_info *fws);
+bool inff_fws_fc_active(struct inff_fws_info *fws);
+void inff_fws_hdrpull(struct inff_if *ifp, s16 siglen, struct sk_buff *skb);
+int inff_fws_process_skb(struct inff_if *ifp, struct sk_buff *skb);
+
+void inff_fws_reset_interface(struct inff_if *ifp);
+void inff_fws_add_interface(struct inff_if *ifp);
+void inff_fws_del_interface(struct inff_if *ifp);
+void inff_fws_bustxcomplete(struct inff_fws_info *fws, struct sk_buff *skb,
+ bool success);
+void inff_fws_bus_blocked(struct inff_pub *drvr, bool flow_blocked);
+void inff_fws_rxreorder(struct inff_if *ifp, struct sk_buff *skb, bool inirq);
+void inff_fws_cleanup_interface(struct inff_if *ifp);
+void inff_creditmap_query(struct inff_fws_info *fws);
+
+#endif /* INFF_FWSIGNAL_H */
--
2.43.0
^ permalink raw reply related [flat|nested] 26+ messages in thread* [PATCH wireless-next v3 19/25] wifi: inffmac: add bcdc.c/h
2026-09-02 16:50 [PATCH wireless-next v3 00/25] wifi: inffmac: introducing a driver for Infineon's new generation chipsets Gokul Sivakumar
` (17 preceding siblings ...)
2026-09-02 16:50 ` [PATCH wireless-next v3 18/25] wifi: inffmac: add fwsignal.c/h Gokul Sivakumar
@ 2026-09-02 16:50 ` Gokul Sivakumar
2026-09-02 16:50 ` [PATCH wireless-next v3 20/25] wifi: inffmac: add ie.c/h Gokul Sivakumar
` (5 subsequent siblings)
24 siblings, 0 replies; 26+ messages in thread
From: Gokul Sivakumar @ 2026-09-02 16:50 UTC (permalink / raw)
To: linux-wireless, johannes
Cc: arend.vanspriel, wlan-kernel-dev-list, gokulkumar.sivakumar
Driver implementation of the BCDC protocol used for the Control and Data
communication with Infineon's WLAN Device over the SDIO BUS.
Signed-off-by: Gokul Sivakumar <gokulkumar.sivakumar@infineon.com>
---
drivers/net/wireless/infineon/inffmac/bcdc.c | 516 +++++++++++++++++++
drivers/net/wireless/infineon/inffmac/bcdc.h | 29 ++
2 files changed, 545 insertions(+)
create mode 100644 drivers/net/wireless/infineon/inffmac/bcdc.c
create mode 100644 drivers/net/wireless/infineon/inffmac/bcdc.h
diff --git a/drivers/net/wireless/infineon/inffmac/bcdc.c b/drivers/net/wireless/infineon/inffmac/bcdc.c
new file mode 100644
index 000000000000..8c61c9bcc146
--- /dev/null
+++ b/drivers/net/wireless/infineon/inffmac/bcdc.c
@@ -0,0 +1,516 @@
+// SPDX-License-Identifier: ISC
+/*
+ * Copyright (c) 2010 Broadcom Corporation
+ *
+ * Copyright (c) 2025-2026, Infineon Technologies AG, or an affiliate of Infineon Technologies AG.
+ * All rights reserved.
+ */
+
+/*******************************************************************************
+ * Communicates with the dongle by using dcmd codes.
+ * For certain dcmd codes, the dongle interprets string data from the host.
+ ******************************************************************************/
+
+#include <linux/types.h>
+#include <linux/netdevice.h>
+
+#include "main.h"
+#include "net.h"
+#include "utils.h"
+#include "fwsignal.h"
+#include "debug.h"
+#include "bus_proto.h"
+#include "bcdc.h"
+
+/* BCDC flag definitions */
+#define BCDC_DCMD_ERROR 0x01 /* 1=cmd failed */
+#define BCDC_DCMD_SET 0x02 /* 0=get, 1=set cmd */
+#define BCDC_DCMD_IF_MASK 0xF000 /* I/F index */
+#define BCDC_DCMD_IF_SHIFT 12
+#define BCDC_DCMD_ID_MASK 0xFFFF0000 /* id an cmd pairing */
+#define BCDC_DCMD_ID_SHIFT 16 /* ID Mask shift bits */
+#define BCDC_DCMD_ID(flags) \
+ (((flags) & BCDC_DCMD_ID_MASK) >> BCDC_DCMD_ID_SHIFT)
+
+/*
+ * BCDC header - Infineon specific extension of CDC.
+ * Used on data packets to convey priority.
+ */
+#define BCDC_HEADER_LEN 4
+#define BCDC_PROTO_VER 2 /* Protocol version */
+#define BCDC_FLAG_VER_MASK 0xf0 /* Protocol version mask */
+#define BCDC_FLAG_VER_SHIFT 4 /* Protocol version shift */
+#define BCDC_FLAG_SUM_GOOD 0x04 /* Good RX checksums */
+#define BCDC_FLAG_SUM_NEEDED 0x08 /* Dongle needs to do TX checksums */
+#define BCDC_PRIORITY_MASK 0x7
+#define BCDC_FLAG2_IF_MASK 0x0f /* packet rx interface in APSTA */
+#define BCDC_FLAG2_IF_SHIFT 0
+
+#define BCDC_GET_IF_IDX(hdr) \
+ ((int)((((hdr)->flags2) & BCDC_FLAG2_IF_MASK) >> BCDC_FLAG2_IF_SHIFT))
+#define BCDC_SET_IF_IDX(hdr, idx) {\
+ typeof(hdr) _hdr = (hdr); \
+ ((_hdr)->flags2 = (((_hdr)->flags2 & ~BCDC_FLAG2_IF_MASK) | \
+ ((idx) << BCDC_FLAG2_IF_SHIFT))); \
+ }
+
+/**
+ * struct inff_proto_bcdc_header - BCDC header format
+ *
+ * @flags: flags contain protocol and checksum info.
+ * @priority: 802.1d priority.
+ * @flags2: additional flags containing dongle interface index.
+ * @data_offset: start of packet data. header is following by firmware signals.
+ */
+struct inff_proto_bcdc_header {
+ u8 flags;
+ u8 priority;
+ u8 flags2;
+ u8 data_offset;
+};
+
+/*
+ * maximum length of firmware signal data between
+ * the BCDC header and packet data in the tx path.
+ */
+#define INFF_PROT_FW_SIGNAL_MAX_TXBYTES 12
+
+#define RETRIES 2 /* # of retries to retrieve matching dcmd response */
+#define BUS_HEADER_LEN (16 + 64) /* Must be at least SDPCM_RESERVE
+ * (amount of header tha might be added)
+ * plus any space that might be needed
+ * for bus alignment padding.
+ */
+#define ROUND_UP_MARGIN 2048
+
+struct inff_bcdc {
+ u16 reqid;
+ u8 bus_header[BUS_HEADER_LEN];
+ struct inff_proto_bcdc_dcmd msg;
+ unsigned char buf[INFF_DCMD_MAXLEN];
+ struct inff_fws_info *fws;
+};
+
+struct inff_fws_info *drvr_to_fws(struct inff_pub *drvr)
+{
+ struct inff_bcdc *bcdc = drvr->proto->pd;
+
+ return bcdc->fws;
+}
+
+static int
+inff_proto_bcdc_msg(struct inff_pub *drvr, int ifidx, uint cmd, void *buf,
+ uint len, bool set)
+{
+ struct inff_bcdc *bcdc = (struct inff_bcdc *)drvr->proto->pd;
+ struct inff_proto_bcdc_dcmd *msg = &bcdc->msg;
+ u32 flags;
+
+ memset(msg, 0, sizeof(struct inff_proto_bcdc_dcmd));
+
+ msg->cmd = cpu_to_le32(cmd);
+ msg->len = cpu_to_le32(len);
+ flags = (++bcdc->reqid << BCDC_DCMD_ID_SHIFT);
+
+ if (set)
+ flags |= BCDC_DCMD_SET;
+ flags = (flags & ~BCDC_DCMD_IF_MASK) |
+ (ifidx << BCDC_DCMD_IF_SHIFT);
+ msg->flags = cpu_to_le32(flags);
+
+ if (buf)
+ memcpy(bcdc->buf, buf, len);
+
+ len += sizeof(*msg);
+ if (len > INFF_TX_IOCTL_MAX_MSG_SIZE)
+ len = INFF_TX_IOCTL_MAX_MSG_SIZE;
+
+ /* Send request */
+ return inff_bus_txctl(drvr->bus_if, (unsigned char *)&bcdc->msg, len);
+}
+
+static int inff_proto_bcdc_cmplt(struct inff_pub *drvr, u32 id, u32 len)
+{
+ int ret;
+ struct inff_bcdc *bcdc = (struct inff_bcdc *)drvr->proto->pd;
+
+ len += sizeof(struct inff_proto_bcdc_dcmd);
+ do {
+ ret = inff_bus_rxctl(drvr->bus_if, (unsigned char *)&bcdc->msg,
+ len);
+ if (ret < 0)
+ break;
+ } while (BCDC_DCMD_ID(le32_to_cpu(bcdc->msg.flags)) != id);
+
+ return ret;
+}
+
+static int
+inff_proto_bcdc_query_fwcmd(struct inff_pub *drvr, int ifidx, uint cmd,
+ void *buf, uint len, int *fwerr)
+{
+ struct inff_bcdc *bcdc = (struct inff_bcdc *)drvr->proto->pd;
+ struct inff_proto_bcdc_dcmd *msg = &bcdc->msg;
+ void *info;
+ int ret = 0, retries = 0;
+ u32 id, flags;
+
+ inff_dbg(BCDC, "cmd %d len %d\n", cmd, len);
+
+ *fwerr = 0;
+ ret = inff_proto_bcdc_msg(drvr, ifidx, cmd, buf, len, false);
+ if (ret < 0) {
+ iphy_err(drvr, "inff_proto_bcdc_msg failed w/status %d\n",
+ ret);
+ goto done;
+ }
+
+retry:
+ /* wait for interrupt and get first fragment */
+ ret = inff_proto_bcdc_cmplt(drvr, bcdc->reqid, len);
+ if (ret < 0)
+ goto done;
+
+ flags = le32_to_cpu(msg->flags);
+ id = (flags & BCDC_DCMD_ID_MASK) >> BCDC_DCMD_ID_SHIFT;
+
+ if (id < bcdc->reqid && (++retries < RETRIES))
+ goto retry;
+ if (id != bcdc->reqid) {
+ iphy_err(drvr, "%s: unexpected request id %d (expected %d)\n",
+ inff_ifname(inff_get_ifp(drvr, ifidx)), id,
+ bcdc->reqid);
+ ret = -EINVAL;
+ goto done;
+ }
+
+ /* Check info buffer */
+ info = (void *)&bcdc->buf[0];
+
+ /* Copy info buffer */
+ if (buf) {
+ if (ret < (int)len)
+ len = ret;
+ memcpy(buf, info, len);
+ }
+
+ ret = 0;
+
+ /* Check the ERROR flag */
+ if (flags & BCDC_DCMD_ERROR)
+ *fwerr = le32_to_cpu(msg->status);
+done:
+ return ret;
+}
+
+static int
+inff_proto_bcdc_set_fwcmd(struct inff_pub *drvr, int ifidx, uint cmd,
+ void *buf, uint len, int *fwerr)
+{
+ struct inff_bcdc *bcdc = (struct inff_bcdc *)drvr->proto->pd;
+ struct inff_proto_bcdc_dcmd *msg = &bcdc->msg;
+ int ret;
+ u32 flags, id;
+
+ inff_dbg(BCDC, "cmd %d len %d\n", cmd, len);
+
+ *fwerr = 0;
+ ret = inff_proto_bcdc_msg(drvr, ifidx, cmd, buf, len, true);
+ if (ret < 0)
+ goto done;
+
+ ret = inff_proto_bcdc_cmplt(drvr, bcdc->reqid, len);
+ if (ret < 0)
+ goto done;
+
+ flags = le32_to_cpu(msg->flags);
+ id = (flags & BCDC_DCMD_ID_MASK) >> BCDC_DCMD_ID_SHIFT;
+
+ if (id != bcdc->reqid) {
+ iphy_err(drvr, "%s: unexpected request id %d (expected %d)\n",
+ inff_ifname(inff_get_ifp(drvr, ifidx)), id,
+ bcdc->reqid);
+ ret = -EINVAL;
+ goto done;
+ }
+
+ ret = 0;
+
+ /* Check the ERROR flag */
+ if (flags & BCDC_DCMD_ERROR)
+ *fwerr = le32_to_cpu(msg->status);
+
+done:
+ return ret;
+}
+
+static void
+inff_proto_bcdc_hdrpush(struct inff_pub *drvr, int ifidx, u8 offset,
+ struct sk_buff *pktbuf)
+{
+ struct inff_proto_bcdc_header *h;
+
+ /* Push BDC header used to convey priority for buses that don't */
+ skb_push(pktbuf, BCDC_HEADER_LEN);
+
+ h = (struct inff_proto_bcdc_header *)(pktbuf->data);
+
+ h->flags = (BCDC_PROTO_VER << BCDC_FLAG_VER_SHIFT);
+ if (pktbuf->ip_summed == CHECKSUM_PARTIAL)
+ h->flags |= BCDC_FLAG_SUM_NEEDED;
+
+ h->priority = (pktbuf->priority & BCDC_PRIORITY_MASK);
+ h->flags2 = 0;
+ h->data_offset = offset;
+ BCDC_SET_IF_IDX(h, ifidx);
+}
+
+static int
+inff_proto_bcdc_hdrpull(struct inff_pub *drvr, bool do_fws,
+ struct sk_buff *pktbuf, struct inff_if **ifp)
+{
+ struct inff_proto_bcdc_header *h;
+ struct inff_if *tmp_if;
+
+ /* Pop BCDC header used to convey priority for buses that don't */
+ if (pktbuf->len <= BCDC_HEADER_LEN) {
+ inff_dbg(INFO, "rx data too short (%d <= %d)\n",
+ pktbuf->len, BCDC_HEADER_LEN);
+ return -EBADE;
+ }
+
+ h = (struct inff_proto_bcdc_header *)(pktbuf->data);
+
+ tmp_if = inff_get_ifp(drvr, BCDC_GET_IF_IDX(h));
+ if (!tmp_if) {
+ inff_dbg(INFO, "no matching ifp found for ifidx: %d\n",
+ BCDC_GET_IF_IDX(h));
+ return -EBADE;
+ }
+ if (((h->flags & BCDC_FLAG_VER_MASK) >> BCDC_FLAG_VER_SHIFT) !=
+ BCDC_PROTO_VER) {
+ iphy_err(drvr, "%s: non-BCDC packet received, flags 0x%x\n",
+ inff_ifname(tmp_if), h->flags);
+ return -EBADE;
+ }
+
+ if (h->flags & BCDC_FLAG_SUM_GOOD) {
+ inff_dbg(BCDC, "%s: BDC rcv, good checksum, flags 0x%x\n",
+ inff_ifname(tmp_if), h->flags);
+ pktbuf->ip_summed = CHECKSUM_UNNECESSARY;
+ }
+
+ pktbuf->priority = h->priority & BCDC_PRIORITY_MASK;
+
+ skb_pull(pktbuf, BCDC_HEADER_LEN);
+ if (do_fws)
+ inff_fws_hdrpull(tmp_if, h->data_offset << 2, pktbuf);
+ else
+ skb_pull(pktbuf, h->data_offset << 2);
+
+ if (pktbuf->len == 0)
+ return -ENODATA;
+
+ if (ifp)
+ *ifp = tmp_if;
+ return 0;
+}
+
+static int inff_proto_bcdc_tx_queue_data(struct inff_pub *drvr, int ifidx,
+ struct sk_buff *skb)
+{
+ struct inff_if *ifp = inff_get_ifp(drvr, ifidx);
+ struct inff_bcdc *bcdc = drvr->proto->pd;
+
+ if (!inff_fws_queue_skbs(bcdc->fws))
+ return inff_proto_txdata(drvr, ifidx, 0, skb);
+
+ return inff_fws_process_skb(ifp, skb);
+}
+
+static int
+inff_proto_bcdc_txdata(struct inff_pub *drvr, int ifidx, u8 offset,
+ struct sk_buff *pktbuf)
+{
+ inff_proto_bcdc_hdrpush(drvr, ifidx, offset, pktbuf);
+ return inff_bus_txdata(drvr->bus_if, pktbuf);
+}
+
+void inff_proto_bcdc_txflowblock(struct device *dev, bool state)
+{
+ struct inff_bus *bus_if = dev_get_drvdata(dev);
+ struct inff_pub *drvr = bus_if->drvr;
+
+ inff_fws_bus_blocked(drvr, state);
+}
+
+void
+inff_proto_bcdc_txcomplete(struct device *dev, struct sk_buff *txp,
+ bool success)
+{
+ struct inff_bus *bus_if = dev_get_drvdata(dev);
+ struct inff_bcdc *bcdc = bus_if->drvr->proto->pd;
+ struct inff_if *ifp;
+
+ /* await txstatus signal for firmware if active */
+ if (inff_fws_fc_active(bcdc->fws)) {
+ inff_fws_bustxcomplete(bcdc->fws, txp, success);
+ } else {
+ if (inff_proto_bcdc_hdrpull(bus_if->drvr, false, txp, &ifp))
+ inff_pkt_buf_free_skb(txp);
+ else
+ inff_txfinalize(ifp, txp, success);
+ }
+}
+
+static void
+inff_proto_bcdc_configure_addr_mode(struct inff_pub *drvr, int ifidx,
+ enum proto_addr_mode addr_mode)
+{
+}
+
+static void
+inff_proto_bcdc_delete_peer(struct inff_pub *drvr, int ifidx,
+ u8 peer[ETH_ALEN])
+{
+}
+
+static void inff_proto_bcdc_rxreorder(struct inff_if *ifp,
+ struct sk_buff *skb, bool inirq)
+{
+ inff_fws_rxreorder(ifp, skb, inirq);
+}
+
+static void
+inff_proto_bcdc_add_if(struct inff_if *ifp)
+{
+ inff_fws_add_interface(ifp);
+}
+
+static void
+inff_proto_bcdc_del_if(struct inff_if *ifp)
+{
+ inff_fws_del_interface(ifp);
+}
+
+static void
+inff_proto_bcdc_reset_if(struct inff_if *ifp)
+{
+ inff_fws_reset_interface(ifp);
+}
+
+static void
+inff_proto_bcdc_cleanup_if(struct inff_if *ifp)
+{
+ inff_fws_cleanup_interface(ifp);
+}
+
+static int
+inff_proto_bcdc_init_done(struct inff_pub *drvr)
+{
+ struct inff_bcdc *bcdc = drvr->proto->pd;
+ struct inff_fws_info *fws;
+
+ fws = inff_fws_attach(drvr);
+ if (IS_ERR(fws))
+ return PTR_ERR(fws);
+
+ bcdc->fws = fws;
+ return 0;
+}
+
+static void inff_proto_bcdc_debugfs_create(struct inff_pub *drvr)
+{
+ inff_fws_debugfs_create(drvr);
+}
+
+void inff_bcdc_process_event(struct device *dev, struct sk_buff *skb)
+{
+ struct inff_if *ifp;
+ struct inff_bus *bus_if = dev_get_drvdata(dev);
+ struct inff_pub *drvr = bus_if->drvr;
+
+ inff_dbg(DEVEVT, "%s: rxp=%p\n", dev_name(dev), skb);
+
+ if (inff_rx_hdrpull(drvr, skb, &ifp))
+ return;
+
+ inff_fwevt_process_skb(ifp->drvr, skb, 0, GFP_KERNEL);
+ inff_pkt_buf_free_skb(skb);
+}
+
+void inff_bcdc_rx(struct device *dev, struct sk_buff *skb, bool handle_event,
+ bool inirq)
+{
+ struct inff_if *ifp;
+ struct inff_bus *bus_if = dev_get_drvdata(dev);
+ struct inff_pub *drvr = bus_if->drvr;
+
+ inff_dbg(DATA, "%s: rxp=%p\n", dev_name(dev), skb);
+
+ if (inff_rx_hdrpull(drvr, skb, &ifp))
+ return;
+
+ if (inff_proto_is_reorder_skb(skb)) {
+ inff_proto_rxreorder(ifp, skb, inirq);
+ } else {
+ /* Process special event packets */
+ if (handle_event) {
+ gfp_t gfp = inirq ? GFP_ATOMIC : GFP_KERNEL;
+
+ inff_fwevt_process_skb(ifp->drvr, skb,
+ INFILCP_SUBTYPE_VENDOR_LONG, gfp);
+ }
+
+ inff_net_rx(ifp, skb, inirq);
+ }
+}
+
+int inff_proto_bcdc_attach(struct inff_pub *drvr)
+{
+ struct inff_bcdc *bcdc;
+
+ bcdc = kzalloc_obj(*bcdc, GFP_ATOMIC);
+ if (!bcdc)
+ goto fail;
+
+ /* ensure that the msg buf directly follows the cdc msg struct */
+ if ((unsigned long)(&bcdc->msg + 1) != (unsigned long)bcdc->buf) {
+ iphy_err(drvr, "struct inff_proto_bcdc is not correctly defined\n");
+ goto fail;
+ }
+
+ drvr->proto->query_fwcmd = inff_proto_bcdc_query_fwcmd;
+ drvr->proto->set_fwcmd = inff_proto_bcdc_set_fwcmd;
+ drvr->proto->hdrpull = inff_proto_bcdc_hdrpull;
+ drvr->proto->tx_queue_data = inff_proto_bcdc_tx_queue_data;
+ drvr->proto->txdata = inff_proto_bcdc_txdata;
+ drvr->proto->configure_addr_mode = inff_proto_bcdc_configure_addr_mode;
+ drvr->proto->delete_peer = inff_proto_bcdc_delete_peer;
+ drvr->proto->rxreorder = inff_proto_bcdc_rxreorder;
+ drvr->proto->add_if = inff_proto_bcdc_add_if;
+ drvr->proto->del_if = inff_proto_bcdc_del_if;
+ drvr->proto->reset_if = inff_proto_bcdc_reset_if;
+ drvr->proto->cleanup_if = inff_proto_bcdc_cleanup_if;
+ drvr->proto->init_done = inff_proto_bcdc_init_done;
+ drvr->proto->debugfs_create = inff_proto_bcdc_debugfs_create;
+ drvr->proto->pd = bcdc;
+
+ drvr->hdrlen += BCDC_HEADER_LEN + INFF_PROT_FW_SIGNAL_MAX_TXBYTES;
+ drvr->bus_if->maxctl = INFF_DCMD_MAXLEN +
+ sizeof(struct inff_proto_bcdc_dcmd) + ROUND_UP_MARGIN;
+ return 0;
+
+fail:
+ kfree(bcdc);
+ return -ENOMEM;
+}
+
+void inff_proto_bcdc_detach(struct inff_pub *drvr)
+{
+ struct inff_bcdc *bcdc = drvr->proto->pd;
+
+ drvr->proto->pd = NULL;
+ inff_fws_detach(bcdc->fws);
+ kfree(bcdc);
+}
diff --git a/drivers/net/wireless/infineon/inffmac/bcdc.h b/drivers/net/wireless/infineon/inffmac/bcdc.h
new file mode 100644
index 000000000000..c0b451d74c32
--- /dev/null
+++ b/drivers/net/wireless/infineon/inffmac/bcdc.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: ISC */
+/*
+ * Copyright (c) 2013 Broadcom Corporation
+ *
+ * Copyright (c) 2025-2026, Infineon Technologies AG, or an affiliate of Infineon Technologies AG.
+ * All rights reserved.
+ */
+
+#ifndef INFF_BCDC_H
+#define INFF_BCDC_H
+
+struct inff_proto_bcdc_dcmd {
+ __le32 cmd; /* dongle command value */
+ __le32 len; /* lower 16: output buflen, upper 16:input buflen (excludes header) */
+ __le32 flags; /* flag defns given below */
+ __le32 status; /* status code returned from the device */
+};
+
+int inff_proto_bcdc_attach(struct inff_pub *drvr);
+void inff_proto_bcdc_detach(struct inff_pub *drvr);
+void inff_proto_bcdc_txflowblock(struct device *dev, bool state);
+void inff_proto_bcdc_txcomplete(struct device *dev, struct sk_buff *txp,
+ bool success);
+struct inff_fws_info *drvr_to_fws(struct inff_pub *drvr);
+void inff_bcdc_process_event(struct device *dev, struct sk_buff *skb);
+void inff_bcdc_rx(struct device *dev, struct sk_buff *skb,
+ bool handle_event, bool inirq);
+
+#endif /* INFF_BCDC_H */
--
2.43.0
^ permalink raw reply related [flat|nested] 26+ messages in thread* [PATCH wireless-next v3 20/25] wifi: inffmac: add ie.c/h
2026-09-02 16:50 [PATCH wireless-next v3 00/25] wifi: inffmac: introducing a driver for Infineon's new generation chipsets Gokul Sivakumar
` (18 preceding siblings ...)
2026-09-02 16:50 ` [PATCH wireless-next v3 19/25] wifi: inffmac: add bcdc.c/h Gokul Sivakumar
@ 2026-09-02 16:50 ` Gokul Sivakumar
2026-09-02 16:50 ` [PATCH wireless-next v3 21/25] wifi: inffmac: add scan.c/h Gokul Sivakumar
` (4 subsequent siblings)
24 siblings, 0 replies; 26+ messages in thread
From: Gokul Sivakumar @ 2026-09-02 16:50 UTC (permalink / raw)
To: linux-wireless, johannes
Cc: arend.vanspriel, wlan-kernel-dev-list, gokulkumar.sivakumar
Driver imeplementation to configure, handle and parse Information elements
of the Wi-Fi frames.
Signed-off-by: Gokul Sivakumar <gokulkumar.sivakumar@infineon.com>
---
drivers/net/wireless/infineon/inffmac/ie.c | 656 +++++++++++++++++++++
drivers/net/wireless/infineon/inffmac/ie.h | 170 ++++++
2 files changed, 826 insertions(+)
create mode 100644 drivers/net/wireless/infineon/inffmac/ie.c
create mode 100644 drivers/net/wireless/infineon/inffmac/ie.h
diff --git a/drivers/net/wireless/infineon/inffmac/ie.c b/drivers/net/wireless/infineon/inffmac/ie.c
new file mode 100644
index 000000000000..35251b354b50
--- /dev/null
+++ b/drivers/net/wireless/infineon/inffmac/ie.c
@@ -0,0 +1,656 @@
+// SPDX-License-Identifier: ISC
+/*
+ * Copyright (c) 2010 Broadcom Corporation
+ *
+ * Copyright (c) 2025-2026, Infineon Technologies AG, or an affiliate of Infineon Technologies AG.
+ * All rights reserved.
+ */
+
+#include <linux/kernel.h>
+#include <linux/etherdevice.h>
+#include <linux/module.h>
+#include <linux/vmalloc.h>
+#include <linux/string.h>
+#include <net/cfg80211.h>
+#include <net/netlink.h>
+
+#include "main.h"
+#include "utils.h"
+#include "chan.h"
+#include "debug.h"
+#include "dev_cmd.h"
+#include "fwsignal.h"
+#include "cfg80211.h"
+#include "interface.h"
+#include "feature.h"
+#include "dev_cmd.h"
+
+void inff_clear_assoc_req_ie(struct inff_cfg80211_info *cfg)
+{
+ struct inff_cfg80211_connect_info *conn_info = &cfg->conn_info;
+
+ kfree(conn_info->req_ie);
+ conn_info->req_ie = NULL;
+ conn_info->req_ie_len = 0;
+}
+
+void inff_clear_assoc_resp_ie(struct inff_cfg80211_info *cfg)
+{
+ struct inff_cfg80211_connect_info *conn_info = &cfg->conn_info;
+
+ kfree(conn_info->resp_ie);
+ conn_info->resp_ie = NULL;
+ conn_info->resp_ie_len = 0;
+}
+
+/* Traverse a string of 1-byte tag/1-byte length/variable-length value
+ * triples, returning a pointer to the substring whose first element
+ * matches tag
+ */
+const struct inff_tlv *
+inff_parse_tlvs(const void *buf, int buflen, uint key)
+{
+ const struct inff_tlv *elt = buf;
+ int totlen = buflen;
+
+ /* find tagged parameter */
+ while (totlen >= TLV_HDR_LEN) {
+ int len = elt->len;
+
+ /* validate remaining totlen */
+ if (elt->id == key && (totlen >= (len + TLV_HDR_LEN)))
+ return elt;
+
+ elt = (struct inff_tlv *)((u8 *)elt + (len + TLV_HDR_LEN));
+ totlen -= (len + TLV_HDR_LEN);
+ }
+
+ return NULL;
+}
+
+struct inff_tlv *
+inff_find_iwie(const u8 *parse, u32 len)
+{
+ const struct inff_tlv *ie = NULL;
+
+ /* unfortunately it's too much work to dispose the const cast - inff_parse_tlvs
+ * is used everywhere and changing its prototype to take const qualifier needs
+ * a massive change to all its callers...
+ */
+
+ ie = inff_parse_tlvs(parse, len, WLAN_EID_INTERWORKING);
+ if (ie)
+ return (struct inff_tlv *)ie;
+ return NULL;
+}
+
+s32
+inff_clear_iwie(struct inff_cfg80211_info *cfg, struct inff_if *ifp)
+{
+ struct ie_set_buffer ie_setbuf = {0};
+
+ inff_dbg(TRACE, "clear interworking IE\n");
+
+ memset(&ie_setbuf, 0, sizeof(struct ie_set_buffer));
+
+ ie_setbuf.ie_buffer.iecount = cpu_to_le32(1);
+ ie_setbuf.ie_buffer.ie_list[0].ie_data.id = WLAN_EID_INTERWORKING;
+ ie_setbuf.ie_buffer.ie_list[0].ie_data.len = 0;
+
+ return inff_fwcmd_iovar_data_set(ifp, "ie", &ie_setbuf, sizeof(ie_setbuf));
+}
+
+s32
+inff_add_iwie(struct inff_cfg80211_info *cfg, struct inff_if *ifp, s32 pktflag,
+ u8 ie_id, u8 *data, u8 data_len)
+{
+ int err = 0;
+ u32 buf_len;
+ struct ie_set_buffer *ie_setbuf;
+
+ if (ie_id != WLAN_EID_INTERWORKING) {
+ inff_err("unsupported (id=%d)\n", ie_id);
+ return -EINVAL;
+ }
+
+ /* access network options (1 octet) is the mandatory field */
+ if (!data || data_len == 0 || data_len > INFF_IW_IES_MAX_BUF_LEN) {
+ inff_err("wrong interworking IE (len=%d)\n", data_len);
+ return -EINVAL;
+ }
+
+ /* Validate the pktflag parameter */
+ if (pktflag & ~(INFF_VNDR_IE_CUSTOM_FLAG)) {
+ inff_err("invalid packet flag 0x%x\n", pktflag);
+ return -EINVAL;
+ }
+
+ buf_len = sizeof(struct ie_set_buffer) + data_len - 1;
+
+ /* if already set with previous values, delete it first */
+ err = inff_clear_iwie(cfg, ifp);
+ if (err)
+ return err;
+
+ ie_setbuf = kmalloc(buf_len, GFP_KERNEL);
+ if (!ie_setbuf)
+ return -ENOMEM;
+
+ strscpy(ie_setbuf->cmd, "add", sizeof(ie_setbuf->cmd));
+
+ /* Buffer contains only 1 IE */
+ ie_setbuf->ie_buffer.iecount = cpu_to_le32(1);
+ /* use VNDR_IE_CUSTOM_FLAG flags for none vendor IE . currently fixed value */
+ ie_setbuf->ie_buffer.ie_list[0].pktflag = cpu_to_le32(pktflag);
+
+ /* Now, add the IE to the buffer */
+ ie_setbuf->ie_buffer.ie_list[0].ie_data.id = WLAN_EID_INTERWORKING;
+ ie_setbuf->ie_buffer.ie_list[0].ie_data.len = data_len;
+ /* Returning void here as max data_len can be 8 */
+ (void)memcpy((u8 *)&ie_setbuf->ie_buffer.ie_list[0].ie_data.data[0],
+ data, data_len);
+
+ err = inff_fwcmd_iovar_data_set(ifp, "ie", ie_setbuf, buf_len);
+ if (err)
+ inff_err("Failed to add interworking IE\n");
+
+ kfree(ie_setbuf);
+
+ return err;
+}
+
+s32
+inff_parse_vndr_ies(const u8 *vndr_ie_buf, u32 vndr_ie_len,
+ struct parsed_vndr_ies *vndr_ies)
+{
+ struct inff_vs_tlv *vndrie;
+ struct inff_tlv *ie;
+ struct parsed_vndr_ie_info *parsed_info;
+ s32 remaining_len;
+
+ remaining_len = (s32)vndr_ie_len;
+ memset(vndr_ies, 0, sizeof(*vndr_ies));
+
+ ie = (struct inff_tlv *)vndr_ie_buf;
+ while (ie) {
+ if (ie->id != WLAN_EID_VENDOR_SPECIFIC)
+ goto next;
+ vndrie = (struct inff_vs_tlv *)ie;
+ /* len should be bigger than OUI length + one */
+ if (vndrie->len < (VS_IE_FIXED_HDR_LEN - TLV_HDR_LEN + 1)) {
+ inff_err("invalid vndr ie. length is too small %d\n",
+ vndrie->len);
+ goto next;
+ }
+ /* if wpa or wme ie, do not add ie */
+ if (!memcmp(vndrie->oui, (u8 *)WPA_OUI, TLV_OUI_LEN) &&
+ (vndrie->oui_type == WPA_OUI_TYPE ||
+ vndrie->oui_type == WME_OUI_TYPE)) {
+ inff_dbg(TRACE, "Found WPA/WME oui. Do not add it\n");
+ goto next;
+ }
+
+ parsed_info = &vndr_ies->ie_info[vndr_ies->count];
+
+ /* save vndr ie information */
+ parsed_info->ie_ptr = (char *)vndrie;
+ parsed_info->ie_len = vndrie->len + TLV_HDR_LEN;
+ memcpy(&parsed_info->vndrie, vndrie, sizeof(*vndrie));
+
+ vndr_ies->count++;
+
+ inff_dbg(TRACE, "** OUI %3ph, type 0x%02x\n",
+ parsed_info->vndrie.oui,
+ parsed_info->vndrie.oui_type);
+
+ if (vndr_ies->count >= VNDR_IE_PARSE_LIMIT)
+ break;
+next:
+ remaining_len -= (ie->len + TLV_HDR_LEN);
+ if (remaining_len <= TLV_HDR_LEN)
+ ie = NULL;
+ else
+ ie = (struct inff_tlv *)(((u8 *)ie) + ie->len +
+ TLV_HDR_LEN);
+ }
+ return 0;
+}
+
+u32
+inff_vndr_ie(u8 *iebuf, s32 pktflag, u8 *ie_ptr, u32 ie_len, s8 *add_del_cmd)
+{
+ strscpy(iebuf, add_del_cmd, VNDR_IE_CMD_LEN);
+
+ put_unaligned_le32(1, &iebuf[VNDR_IE_COUNT_OFFSET]);
+
+ put_unaligned_le32(pktflag, &iebuf[VNDR_IE_PKTFLAG_OFFSET]);
+
+ memcpy(&iebuf[VNDR_IE_VSIE_OFFSET], ie_ptr, ie_len);
+
+ return ie_len + VNDR_IE_HDR_SIZE;
+}
+
+s32
+inff_parse_extension_ies(const u8 *extension_ie_buf, u32 extension_ie_len,
+ struct parsed_extension_ies *extension_ies)
+{
+ struct inff_ext_tlv *ext_ie;
+ struct inff_tlv *ie;
+ struct parsed_ext_ie_info *parsed_info;
+ s32 remaining_len;
+
+ remaining_len = (s32)extension_ie_len;
+ memset(extension_ies, 0, sizeof(*extension_ies));
+
+ ie = (struct inff_tlv *)extension_ie_buf;
+ while (ie) {
+ if (ie->id != WLAN_EID_EXTENSION)
+ goto next;
+ ext_ie = (struct inff_ext_tlv *)ie;
+
+ /* len should be bigger than ext_id + one data */
+ if (ext_ie->len < 2) {
+ inff_err("invalid ext_ie ie. length is too small %d\n",
+ ext_ie->len);
+ goto next;
+ }
+
+ /* skip parsing the HE capab, HE_6G_capa & oper IE from upper layer
+ * to avoid sending it to the FW, as these IEs will be
+ * added by the FW based on the MAC & PHY capab if HE
+ * is enabled.
+ */
+ if (ext_ie->ext_id == WLAN_EID_EXT_HE_CAPABILITY ||
+ ext_ie->ext_id == WLAN_EID_EXT_HE_OPERATION ||
+ ext_ie->ext_id == WLAN_EID_EXT_HE_6GHZ_CAPA)
+ goto next;
+
+ parsed_info = &extension_ies->ie_info[extension_ies->count];
+
+ parsed_info->ie_ptr = (char *)ext_ie;
+ parsed_info->ie_len = ext_ie->len + TLV_HDR_LEN;
+ memcpy(&parsed_info->ie_data, ext_ie, sizeof(*ext_ie));
+
+ extension_ies->count++;
+
+ inff_dbg(TRACE, "** EXT_IE %d, len 0x%02x EXT_ID: %d\n",
+ parsed_info->ie_data.id,
+ parsed_info->ie_data.len,
+ parsed_info->ie_data.ext_id);
+
+ /* temperory parsing at most 5 EXT_ID, will review it.*/
+ if (extension_ies->count >= VNDR_IE_PARSE_LIMIT)
+ break;
+next:
+ remaining_len -= (ie->len + TLV_HDR_LEN);
+ if (remaining_len <= TLV_HDR_LEN)
+ ie = NULL;
+ else
+ ie = (struct inff_tlv *)(((u8 *)ie) + ie->len +
+ TLV_HDR_LEN);
+ }
+ return 0;
+}
+
+s32 inff_get_assoc_ies(struct inff_cfg80211_info *cfg, struct inff_if *ifp)
+{
+ struct inff_pub *drvr = cfg->pub;
+ struct inff_cfg80211_assoc_ielen_le *assoc_info;
+ struct inff_cfg80211_connect_info *conn_info = &cfg->conn_info;
+ struct inff_cfg80211_edcf_acparam edcf_acparam_info[EDCF_AC_COUNT];
+ u32 req_len;
+ u32 resp_len;
+ u32 flags;
+ s32 err = 0;
+
+ inff_dbg(CONN, "req: %p, req len (%d) resp: %p resp len (%d)\n", conn_info->req_ie,
+ conn_info->req_ie_len, conn_info->resp_ie, conn_info->resp_ie_len);
+
+ if (conn_info->req_ie_len && conn_info->resp_ie_len &&
+ conn_info->req_ie && conn_info->resp_ie)
+ return 0;
+
+ inff_clear_assoc_ies(cfg);
+
+ memset(cfg->extra_buf, '\0', WL_EXTRA_BUF_MAX);
+ err = inff_fwcmd_iovar_data_get(ifp, "assoc_info",
+ cfg->extra_buf, WL_ASSOC_INFO_MAX);
+ if (err) {
+ iphy_err(drvr, "could not get assoc info (%d)\n", err);
+ return err;
+ }
+ assoc_info =
+ (struct inff_cfg80211_assoc_ielen_le *)cfg->extra_buf;
+ req_len = le32_to_cpu(assoc_info->req_len);
+ resp_len = le32_to_cpu(assoc_info->resp_len);
+ flags = le32_to_cpu(assoc_info->flags);
+ if (req_len > WL_EXTRA_BUF_MAX || resp_len > WL_EXTRA_BUF_MAX) {
+ iphy_err(drvr, "invalid lengths in assoc info: req %u resp %u\n",
+ req_len, resp_len);
+ return -EINVAL;
+ }
+ if (req_len) {
+ memset(cfg->extra_buf, '\0', WL_EXTRA_BUF_MAX);
+ err = inff_fwcmd_iovar_data_get(ifp, "assoc_req_ies",
+ cfg->extra_buf,
+ WL_ASSOC_INFO_MAX);
+ if (err) {
+ iphy_err(drvr, "could not get assoc req (%d)\n", err);
+ return err;
+ }
+
+ if (flags & INFF_ASSOC_REQ_IS_REASSOC)
+ conn_info->req_ie_len = req_len - sizeof(struct dot11_reassoc_req);
+ else
+ conn_info->req_ie_len = req_len - sizeof(struct dot11_assoc_req);
+
+ conn_info->req_ie =
+ kmemdup(cfg->extra_buf, conn_info->req_ie_len,
+ GFP_KERNEL);
+ if (!conn_info->req_ie)
+ conn_info->req_ie_len = 0;
+ } else {
+ conn_info->req_ie_len = 0;
+ conn_info->req_ie = NULL;
+ }
+
+ /* resp_len is the total length of assoc resp
+ * which includes 6 bytes of aid/status code/capabilities.
+ * the assoc_resp_ie length should minus the 6 bytes which starts from rate_ie.
+ */
+ if (resp_len) {
+ memset(cfg->extra_buf, '\0', WL_EXTRA_BUF_MAX);
+ err = inff_fwcmd_iovar_data_get(ifp, "assoc_resp_ies",
+ cfg->extra_buf,
+ WL_ASSOC_INFO_MAX);
+ if (err) {
+ iphy_err(drvr, "could not get assoc resp (%d)\n", err);
+ return err;
+ }
+ conn_info->resp_ie_len = resp_len - sizeof(struct dot11_assoc_resp);
+ conn_info->resp_ie =
+ kmemdup(cfg->extra_buf, conn_info->resp_ie_len,
+ GFP_KERNEL);
+ if (!conn_info->resp_ie)
+ conn_info->resp_ie_len = 0;
+
+ err = inff_fwcmd_iovar_data_get(ifp, "wme_ac_sta",
+ edcf_acparam_info,
+ sizeof(edcf_acparam_info));
+ if (err) {
+ inff_err("could not get wme_ac_sta (%d)\n", err);
+ return err;
+ }
+
+ inff_wifi_prioritize_acparams(edcf_acparam_info,
+ cfg->ac_priority);
+ } else {
+ conn_info->resp_ie_len = 0;
+ conn_info->resp_ie = NULL;
+ }
+ inff_dbg(CONN, "req len (%d) resp len (%d)\n",
+ conn_info->req_ie_len, conn_info->resp_ie_len);
+
+ return err;
+}
+
+void inff_clear_assoc_ies(struct inff_cfg80211_info *cfg)
+{
+ struct inff_cfg80211_connect_info *conn_info = &cfg->conn_info;
+
+ kfree(conn_info->req_ie);
+ conn_info->req_ie = NULL;
+ conn_info->req_ie_len = 0;
+ kfree(conn_info->resp_ie);
+ conn_info->resp_ie = NULL;
+ conn_info->resp_ie_len = 0;
+}
+
+s32 inff_vif_set_mgmt_ie(struct inff_cfg80211_vif *vif, s32 pktflag,
+ const u8 *vndr_ie_buf, u32 vndr_ie_len)
+{
+ struct inff_pub *drvr;
+ struct inff_if *ifp;
+ struct inff_vif_saved_ie *saved_ie;
+ s32 err = 0;
+ u8 *iovar_ie_buf;
+ u8 *curr_ie_buf;
+ u8 *mgmt_ie_buf = NULL;
+ int mgmt_ie_buf_len;
+ u32 *mgmt_ie_len;
+ u32 del_add_ie_buf_len = 0;
+ u32 total_ie_buf_len = 0;
+ u32 parsed_ie_buf_len = 0;
+ struct parsed_vndr_ies old_vndr_ies;
+ struct parsed_vndr_ies new_vndr_ies;
+ struct parsed_vndr_ie_info *vndrie_info;
+ s32 i;
+ u8 *ptr;
+ int remained_buf_len;
+ struct parsed_extension_ies new_ext_ies;
+ struct parsed_extension_ies old_ext_ies;
+ struct parsed_ext_ie_info *extie_info;
+
+ if (!vif)
+ return -ENODEV;
+ ifp = vif->ifp;
+ drvr = ifp->drvr;
+ saved_ie = &vif->saved_ie;
+
+ inff_dbg(TRACE, "bsscfgidx %d, pktflag : 0x%02X\n", ifp->bsscfgidx,
+ pktflag);
+ iovar_ie_buf = kzalloc(WL_EXTRA_BUF_MAX, GFP_KERNEL);
+ if (!iovar_ie_buf)
+ return -ENOMEM;
+ curr_ie_buf = iovar_ie_buf;
+ switch (pktflag) {
+ case INFF_VNDR_IE_PRBREQ_FLAG:
+ mgmt_ie_buf = saved_ie->probe_req_ie;
+ mgmt_ie_len = &saved_ie->probe_req_ie_len;
+ mgmt_ie_buf_len = sizeof(saved_ie->probe_req_ie);
+ break;
+ case INFF_VNDR_IE_PRBRSP_FLAG:
+ mgmt_ie_buf = saved_ie->probe_res_ie;
+ mgmt_ie_len = &saved_ie->probe_res_ie_len;
+ mgmt_ie_buf_len = sizeof(saved_ie->probe_res_ie);
+ break;
+ case INFF_VNDR_IE_BEACON_FLAG:
+ mgmt_ie_buf = saved_ie->beacon_ie;
+ mgmt_ie_len = &saved_ie->beacon_ie_len;
+ mgmt_ie_buf_len = sizeof(saved_ie->beacon_ie);
+ break;
+ case INFF_VNDR_IE_ASSOCREQ_FLAG:
+ mgmt_ie_buf = saved_ie->assoc_req_ie;
+ mgmt_ie_len = &saved_ie->assoc_req_ie_len;
+ mgmt_ie_buf_len = sizeof(saved_ie->assoc_req_ie);
+ break;
+ case INFF_VNDR_IE_ASSOCRSP_FLAG:
+ mgmt_ie_buf = saved_ie->assoc_res_ie;
+ mgmt_ie_len = &saved_ie->assoc_res_ie_len;
+ mgmt_ie_buf_len = sizeof(saved_ie->assoc_res_ie);
+ break;
+ default:
+ err = -EPERM;
+ iphy_err(drvr, "not suitable type\n");
+ goto exit;
+ }
+
+ if (vndr_ie_len > mgmt_ie_buf_len) {
+ err = -ENOMEM;
+ iphy_err(drvr, "extra IE size too big\n");
+ goto exit;
+ }
+
+ /* parse and save new vndr_ie in curr_ie_buff before comparing it */
+ if (vndr_ie_buf && vndr_ie_len && curr_ie_buf) {
+ ptr = curr_ie_buf;
+ inff_parse_vndr_ies(vndr_ie_buf, vndr_ie_len, &new_vndr_ies);
+ for (i = 0; i < new_vndr_ies.count; i++) {
+ vndrie_info = &new_vndr_ies.ie_info[i];
+ memcpy(ptr + parsed_ie_buf_len, vndrie_info->ie_ptr,
+ vndrie_info->ie_len);
+ parsed_ie_buf_len += vndrie_info->ie_len;
+ }
+ inff_parse_extension_ies(vndr_ie_buf, vndr_ie_len, &new_ext_ies);
+ for (i = 0; i < new_ext_ies.count; i++) {
+ extie_info = &new_ext_ies.ie_info[i];
+ memcpy(ptr + parsed_ie_buf_len, extie_info->ie_ptr,
+ extie_info->ie_len);
+ parsed_ie_buf_len += extie_info->ie_len;
+ }
+ }
+
+ if (mgmt_ie_buf && *mgmt_ie_len) {
+ if (parsed_ie_buf_len && parsed_ie_buf_len == *mgmt_ie_len &&
+ (memcmp(mgmt_ie_buf, curr_ie_buf,
+ parsed_ie_buf_len) == 0)) {
+ inff_dbg(TRACE, "Previous mgmt IE equals to current IE\n");
+ goto exit;
+ }
+
+ /* parse old vndr_ie */
+ inff_parse_vndr_ies(mgmt_ie_buf, *mgmt_ie_len, &old_vndr_ies);
+ /* parse old ext_ie */
+ inff_parse_extension_ies(mgmt_ie_buf, *mgmt_ie_len, &old_ext_ies);
+
+ /* make a command to delete old ie */
+ for (i = 0; i < old_vndr_ies.count; i++) {
+ vndrie_info = &old_vndr_ies.ie_info[i];
+
+ inff_dbg(TRACE, "DEL ID : %d, Len: %d , OUI:%3ph\n",
+ vndrie_info->vndrie.id,
+ vndrie_info->vndrie.len,
+ vndrie_info->vndrie.oui);
+
+ del_add_ie_buf_len = inff_vndr_ie(curr_ie_buf, pktflag,
+ vndrie_info->ie_ptr,
+ vndrie_info->ie_len,
+ "del");
+ curr_ie_buf += del_add_ie_buf_len;
+ total_ie_buf_len += del_add_ie_buf_len;
+ }
+ /* make a command to delete old extension ie */
+ for (i = 0; i < old_ext_ies.count; i++) {
+ extie_info = &old_ext_ies.ie_info[i];
+
+ inff_dbg(TRACE, "DEL EXT_IE : %d, Len: %d , ext_id:%d\n",
+ extie_info->ie_data.id,
+ extie_info->ie_data.len,
+ extie_info->ie_data.ext_id);
+
+ del_add_ie_buf_len = inff_vndr_ie(curr_ie_buf,
+ pktflag | INFF_VNDR_IE_CUSTOM_FLAG,
+ extie_info->ie_ptr,
+ extie_info->ie_len,
+ "del");
+ curr_ie_buf += del_add_ie_buf_len;
+ total_ie_buf_len += del_add_ie_buf_len;
+ }
+ }
+
+ *mgmt_ie_len = 0;
+ /* Add if there is any extra IE */
+ if (mgmt_ie_buf && parsed_ie_buf_len) {
+ ptr = mgmt_ie_buf;
+
+ remained_buf_len = mgmt_ie_buf_len;
+
+ /* make a command to add new ie */
+ for (i = 0; i < new_vndr_ies.count; i++) {
+ vndrie_info = &new_vndr_ies.ie_info[i];
+
+ /* verify remained buf size before copy data */
+ if (remained_buf_len < (vndrie_info->vndrie.len +
+ VNDR_IE_VSIE_OFFSET)) {
+ iphy_err(drvr, "no space in mgmt_ie_buf: len left %d",
+ remained_buf_len);
+ break;
+ }
+ remained_buf_len -= (vndrie_info->ie_len +
+ VNDR_IE_VSIE_OFFSET);
+
+ inff_dbg(TRACE, "ADDED ID : %d, Len: %d, OUI:%3ph\n",
+ vndrie_info->vndrie.id,
+ vndrie_info->vndrie.len,
+ vndrie_info->vndrie.oui);
+
+ del_add_ie_buf_len = inff_vndr_ie(curr_ie_buf, pktflag,
+ vndrie_info->ie_ptr,
+ vndrie_info->ie_len,
+ "add");
+
+ /* save the parsed IE in wl struct */
+ memcpy(ptr + (*mgmt_ie_len), vndrie_info->ie_ptr,
+ vndrie_info->ie_len);
+ *mgmt_ie_len += vndrie_info->ie_len;
+
+ curr_ie_buf += del_add_ie_buf_len;
+ total_ie_buf_len += del_add_ie_buf_len;
+ }
+ /* make a command to add new EXT ie */
+ for (i = 0; i < new_ext_ies.count; i++) {
+ extie_info = &new_ext_ies.ie_info[i];
+
+ /* verify remained buf size before copy data */
+ if (remained_buf_len < (extie_info->ie_data.len +
+ VNDR_IE_VSIE_OFFSET)) {
+ iphy_err(drvr, "no space in mgmt_ie_buf: len left %d",
+ remained_buf_len);
+ break;
+ }
+ remained_buf_len -= (extie_info->ie_len +
+ VNDR_IE_VSIE_OFFSET);
+
+ inff_dbg(TRACE, "ADDED EXT ID : %d, Len: %d, OUI:%d\n",
+ extie_info->ie_data.id,
+ extie_info->ie_data.len,
+ extie_info->ie_data.ext_id);
+
+ del_add_ie_buf_len = inff_vndr_ie(curr_ie_buf,
+ pktflag | INFF_VNDR_IE_CUSTOM_FLAG,
+ extie_info->ie_ptr,
+ extie_info->ie_len,
+ "add");
+
+ /* save the parsed IE in wl struct */
+ memcpy(ptr + (*mgmt_ie_len), extie_info->ie_ptr,
+ extie_info->ie_len);
+ *mgmt_ie_len += extie_info->ie_len;
+
+ curr_ie_buf += del_add_ie_buf_len;
+ total_ie_buf_len += del_add_ie_buf_len;
+ }
+ }
+ if (total_ie_buf_len) {
+ err = inff_fwcmd_bsscfg_data_set(ifp, "vndr_ie", iovar_ie_buf,
+ total_ie_buf_len);
+ if (err)
+ iphy_err(drvr, "vndr ie set error : %d\n", err);
+ }
+
+exit:
+ kfree(iovar_ie_buf);
+ return err;
+}
+
+s32 inff_vif_clear_mgmt_ies(struct inff_cfg80211_vif *vif)
+{
+ static const s32 pktflags[] = {
+ INFF_VNDR_IE_PRBRSP_FLAG,
+ INFF_VNDR_IE_BEACON_FLAG,
+ INFF_VNDR_IE_ASSOCRSP_FLAG
+ };
+ int i;
+
+ if (vif->wdev.iftype == NL80211_IFTYPE_AP)
+ inff_vif_set_mgmt_ie(vif, INFF_VNDR_IE_ASSOCRSP_FLAG, NULL, 0);
+ else
+ inff_vif_set_mgmt_ie(vif, INFF_VNDR_IE_PRBREQ_FLAG, NULL, 0);
+
+ for (i = 0; i < ARRAY_SIZE(pktflags); i++)
+ inff_vif_set_mgmt_ie(vif, pktflags[i], NULL, 0);
+
+ memset(&vif->saved_ie, 0, sizeof(vif->saved_ie));
+ return 0;
+}
+
diff --git a/drivers/net/wireless/infineon/inffmac/ie.h b/drivers/net/wireless/infineon/inffmac/ie.h
new file mode 100644
index 000000000000..df2e6a3d10a8
--- /dev/null
+++ b/drivers/net/wireless/infineon/inffmac/ie.h
@@ -0,0 +1,170 @@
+/* SPDX-License-Identifier: ISC */
+/*
+ * Copyright (c) 2010 Broadcom Corporation
+ *
+ * Copyright (c) 2025-2026, Infineon Technologies AG, or an affiliate of Infineon Technologies AG.
+ * All rights reserved.
+ */
+
+#ifndef INFF_IE_H
+#define INFF_IE_H
+
+#define DOT11_MGMT_HDR_LEN 24 /* d11 management header len */
+#define DOT11_BCN_PRB_FIXED_LEN 12 /* beacon/probe fixed length */
+
+#define WPA_OUI "\x00\x50\xF2" /* WPA OUI */
+#define WPA_OUI_TYPE 1
+#define RSN_OUI "\x00\x0F\xAC" /* RSN OUI */
+#define WME_OUI_TYPE 2
+#define WPS_OUI_TYPE 4
+#define WFA_OUI_TYPE_MBO_OCE 0x16
+
+#define VS_IE_FIXED_HDR_LEN 6
+#define WPA_IE_VERSION_LEN 2
+#define WPA_IE_MIN_OUI_LEN 4
+#define WPA_IE_SUITE_COUNT_LEN 2
+
+#define VNDR_IE_CMD_LEN 4 /* length of the set command
+ * string :"add", "del" (+ NUL)
+ */
+#define VNDR_IE_COUNT_OFFSET 4
+#define VNDR_IE_PKTFLAG_OFFSET 8
+#define VNDR_IE_VSIE_OFFSET 12
+#define VNDR_IE_HDR_SIZE 12
+#define VNDR_IE_PARSE_LIMIT 5
+
+#define IE_MAX_LEN 512
+
+/* IE TLV processing */
+#define TLV_LEN_OFF 1 /* length offset */
+#define TLV_HDR_LEN 2 /* header length */
+#define TLV_BODY_OFF 2 /* body offset */
+#define TLV_OUI_LEN 3 /* oui id length */
+
+/* 802.11 Mgmt Packet flags */
+#define INFF_VNDR_IE_BEACON_FLAG 0x1
+#define INFF_VNDR_IE_PRBRSP_FLAG 0x2
+#define INFF_VNDR_IE_ASSOCRSP_FLAG 0x4
+#define INFF_VNDR_IE_AUTHRSP_FLAG 0x8
+#define INFF_VNDR_IE_PRBREQ_FLAG 0x10
+#define INFF_VNDR_IE_ASSOCREQ_FLAG 0x20
+/* vendor IE in IW advertisement protocol ID field */
+#define INFF_VNDR_IE_IWAPID_FLAG 0x40
+/* allow custom IE id */
+#define INFF_VNDR_IE_CUSTOM_FLAG 0x100
+
+/* flags */
+#define INFF_ASSOC_REQ_IS_REASSOC 0x01 /* assoc req was actually a reassoc */
+
+/* Max length of Interworking element */
+#define INFF_IW_IES_MAX_BUF_LEN 8
+
+struct dot11_assoc_req {
+ u16 capability; /* capability information */
+ u16 listen; /* listen interval */
+};
+
+struct dot11_reassoc_req {
+ u16 capability; /* capability information */
+ u16 listen; /* listen interval */
+ u8 ap[ETH_ALEN]; /* Current AP address */
+};
+
+struct dot11_assoc_resp {
+ u16 capability; /* capability information */
+ u16 status; /* status code */
+ u16 aid; /* association ID */
+};
+
+/**
+ * struct inff_tlv - tag_ID/length/value_buffer tuple.
+ *
+ * @id: tag identifier.
+ * @len: number of bytes in value buffer.
+ * @data: value buffer.
+ */
+struct inff_tlv {
+ u8 id;
+ u8 len;
+ u8 data[];
+};
+
+/* Vendor specific ie. id = 221, oui and type defines exact ie */
+struct inff_vs_tlv {
+ u8 id;
+ u8 len;
+ u8 oui[3];
+ u8 oui_type;
+};
+
+struct parsed_vndr_ie_info {
+ u8 *ie_ptr;
+ u32 ie_len; /* total length including id & length field */
+ struct inff_vs_tlv vndrie;
+};
+
+struct parsed_vndr_ies {
+ u32 count;
+ struct parsed_vndr_ie_info ie_info[VNDR_IE_PARSE_LIMIT];
+};
+
+struct inff_ext_tlv {
+ u8 id;
+ u8 len;
+ u8 ext_id;
+};
+
+struct parsed_ext_ie_info {
+ u8 *ie_ptr;
+ u32 ie_len; /* total length including id & length field */
+ struct inff_ext_tlv ie_data;
+};
+
+struct parsed_extension_ies {
+ u32 count;
+ struct parsed_ext_ie_info ie_info[VNDR_IE_PARSE_LIMIT];
+};
+
+struct ie_info {
+ __le32 pktflag; /* bitmask indicating which packet(s) contain this IE */
+ struct inff_tlv ie_data; /* IE data */
+} __packed;
+
+struct ie_buf {
+ __le32 iecount; /* number of entries in the ie_list[] array */
+ struct ie_info ie_list[1]; /* variable size list of ie_info_t structs */
+} __packed;
+
+struct ie_set_buffer {
+ char cmd[VNDR_IE_CMD_LEN]; /* ie IOVar set command : "add" + NUL */
+ struct ie_buf ie_buffer; /* buffer containing IE list information */
+} __packed;
+
+const struct inff_tlv *inff_parse_tlvs(const void *buf, int buflen, uint key);
+
+struct inff_tlv *inff_find_iwie(const u8 *parse, u32 len);
+
+s32 inff_clear_iwie(struct inff_cfg80211_info *cfg, struct inff_if *ifp);
+
+s32 inff_add_iwie(struct inff_cfg80211_info *cfg, struct inff_if *ifp,
+ s32 pktflag, u8 ie_id, u8 *data, u8 data_len);
+
+s32 inff_parse_vndr_ies(const u8 *vndr_ie_buf, u32 vndr_ie_len, struct parsed_vndr_ies *vndr_ies);
+
+u32 inff_vndr_ie(u8 *iebuf, s32 pktflag, u8 *ie_ptr, u32 ie_len, s8 *add_del_cmd);
+
+s32 inff_parse_extension_ies(const u8 *extension_ie_buf, u32 extension_ie_len,
+ struct parsed_extension_ies *extension_ies);
+
+s32 inff_get_assoc_ies(struct inff_cfg80211_info *cfg, struct inff_if *ifp);
+
+void inff_clear_assoc_ies(struct inff_cfg80211_info *cfg);
+
+s32 inff_vif_set_mgmt_ie(struct inff_cfg80211_vif *vif, s32 pktflag,
+ const u8 *vndr_ie_buf, u32 vndr_ie_len);
+s32 inff_vif_clear_mgmt_ies(struct inff_cfg80211_vif *vif);
+
+void inff_clear_assoc_req_ie(struct inff_cfg80211_info *cfg);
+void inff_clear_assoc_resp_ie(struct inff_cfg80211_info *cfg);
+
+#endif /* INFF_IE_H */
--
2.43.0
^ permalink raw reply related [flat|nested] 26+ messages in thread* [PATCH wireless-next v3 21/25] wifi: inffmac: add scan.c/h
2026-09-02 16:50 [PATCH wireless-next v3 00/25] wifi: inffmac: introducing a driver for Infineon's new generation chipsets Gokul Sivakumar
` (19 preceding siblings ...)
2026-09-02 16:50 ` [PATCH wireless-next v3 20/25] wifi: inffmac: add ie.c/h Gokul Sivakumar
@ 2026-09-02 16:50 ` Gokul Sivakumar
2026-09-02 16:50 ` [PATCH wireless-next v3 22/25] wifi: inffmac: add chan.c/h Gokul Sivakumar
` (3 subsequent siblings)
24 siblings, 0 replies; 26+ messages in thread
From: Gokul Sivakumar @ 2026-09-02 16:50 UTC (permalink / raw)
To: linux-wireless, johannes
Cc: arend.vanspriel, wlan-kernel-dev-list, gokulkumar.sivakumar
Driver imeplementation of the SCAN functionality to discover all active BSS
in the medium. Also support collecting the channel survery dump.
Signed-off-by: Gokul Sivakumar <gokulkumar.sivakumar@infineon.com>
---
drivers/net/wireless/infineon/inffmac/scan.c | 800 +++++++++++++++++++
drivers/net/wireless/infineon/inffmac/scan.h | 136 ++++
2 files changed, 936 insertions(+)
create mode 100644 drivers/net/wireless/infineon/inffmac/scan.c
create mode 100644 drivers/net/wireless/infineon/inffmac/scan.h
diff --git a/drivers/net/wireless/infineon/inffmac/scan.c b/drivers/net/wireless/infineon/inffmac/scan.c
new file mode 100644
index 000000000000..f71dcc60386a
--- /dev/null
+++ b/drivers/net/wireless/infineon/inffmac/scan.c
@@ -0,0 +1,800 @@
+// SPDX-License-Identifier: ISC
+/*
+ * Copyright (c) 2010 Broadcom Corporation
+ *
+ * Copyright (c) 2025-2026, Infineon Technologies AG, or an affiliate of Infineon Technologies AG.
+ * All rights reserved.
+ */
+
+#include <linux/kernel.h>
+#include <linux/etherdevice.h>
+#include <linux/module.h>
+#include <linux/vmalloc.h>
+#include <linux/string.h>
+#include <net/cfg80211.h>
+#include <net/netlink.h>
+
+#include "main.h"
+#include "utils.h"
+#include "chan.h"
+#include "debug.h"
+#include "fwsignal.h"
+#include "cfg80211.h"
+#include "interface.h"
+#include "feature.h"
+#include "dev_cmd.h"
+
+s32
+inff_dongle_scantime(struct inff_if *ifp)
+{
+ struct inff_pub *drvr = ifp->drvr;
+ s32 err = 0;
+
+ err = inff_fwcmd_int_set(ifp, INFF_C_SET_SCAN_CHANNEL_TIME,
+ INFF_SCAN_CHANNEL_TIME);
+ if (err) {
+ iphy_err(drvr, "Scan assoc time error (%d)\n", err);
+ goto dongle_scantime_out;
+ }
+ err = inff_fwcmd_int_set(ifp, INFF_C_SET_SCAN_UNASSOC_TIME,
+ INFF_SCAN_UNASSOC_TIME);
+ if (err) {
+ iphy_err(drvr, "Scan unassoc time error (%d)\n", err);
+ goto dongle_scantime_out;
+ }
+
+ err = inff_fwcmd_int_set(ifp, INFF_C_SET_SCAN_PASSIVE_TIME,
+ INFF_SCAN_PASSIVE_TIME);
+ if (err) {
+ iphy_err(drvr, "Scan passive time error (%d)\n", err);
+ goto dongle_scantime_out;
+ }
+
+dongle_scantime_out:
+ return err;
+}
+
+static void
+inff_cp_scan_reset(struct inff_scan_info *scan)
+{
+ scan->request = NULL;
+ scan->bss_count = 0;
+ scan->used_buf_len = 0;
+
+ if (scan->bss_buf)
+ memset(scan->bss_buf, 0, INFF_SCAN_BUF_SIZE);
+}
+
+static void
+inff_cp_scan_complete(struct inff_pub *drvr, bool aborted)
+{
+ struct inff_cfg80211_info *cfg = drvr->config;
+ struct inff_scan_info *scan = &drvr->config->scan;
+ struct cfg80211_scan_info info = {
+ .aborted = aborted,
+ };
+
+ if (aborted)
+ set_bit(INFF_SCAN_STATUS_ABORT, &cfg->scan_status);
+
+ if (!scan || !scan->request)
+ return;
+
+ inff_dbg(SCAN, "%s\n", aborted ? "aborted" : "done");
+ cfg80211_scan_done(scan->request, &info);
+
+ if (!test_and_clear_bit(INFF_SCAN_STATUS_BUSY, &cfg->scan_status))
+ inff_dbg(SCAN, "Scan complete complete/abort\n");
+
+ inff_cp_scan_reset(scan);
+
+ if (aborted)
+ clear_bit(INFF_SCAN_STATUS_ABORT, &cfg->scan_status);
+}
+
+void
+inff_cp_scan_abort(struct inff_pub *drvr)
+{
+ inff_cp_scan_complete(drvr, true);
+}
+
+static s32
+inff_cp_scan_inform_single_bss(struct inff_scan_info *scan,
+ struct inff_cp_bss_info *bss)
+{
+ struct wiphy *wiphy = scan->wiphy;
+ struct cfg80211_bss *cfg80211_bss;
+ struct cfg80211_inform_bss bss_data = {};
+ enum nl80211_band band;
+ u16 channel;
+ u32 freq;
+ u8 *ie;
+
+ inff_dbg(SCAN, "SSID: %s\n", bss->ssid);
+ inff_dbg(SCAN, "BSSID: %pM\n", bss->macaddr);
+
+ channel = bss->channel;
+ band = inff_cp_chan_band_to_nl80211(bss->band);
+
+ freq = ieee80211_channel_to_frequency(channel, band);
+ if (!freq) {
+ inff_err("failed to get frequency from channel %d and band %d\n",
+ channel, band);
+ return -EINVAL;
+ }
+
+ bss_data.chan = ieee80211_get_channel(wiphy, freq);
+ if (!bss_data.chan) {
+ inff_err("failed to get channel from freq %d\n", freq);
+ return -EINVAL;
+ }
+
+ bss_data.boottime_ns = ktime_to_ns(ktime_get_boottime());
+ bss_data.signal = bss->signal_strength * 100;
+
+ inff_dbg(SCAN, "Channel: %d(%d)\n", channel, freq);
+ inff_dbg(SCAN, "Capability: %X\n", bss->capability);
+ inff_dbg(SCAN, "Beacon interval: %d\n", bss->beacon_period);
+ inff_dbg(SCAN, "Signal: %d\n", bss->signal_strength);
+
+ ie = (unsigned char *)bss + sizeof(*bss);
+
+ cfg80211_bss = cfg80211_inform_bss_data(wiphy, &bss_data,
+ CFG80211_BSS_FTYPE_UNKNOWN,
+ (const u8 *)bss->macaddr,
+ 0, bss->capability,
+ bss->beacon_period, ie,
+ bss->ie_len, GFP_KERNEL);
+ if (!cfg80211_bss)
+ return -ENOMEM;
+
+ cfg80211_put_bss(wiphy, cfg80211_bss);
+
+ return 0;
+}
+
+static void
+inff_cp_scan_inform_bss(struct inff_scan_info *scan)
+{
+ struct inff_cp_bss_info *bss = (struct inff_cp_bss_info *)scan->bss_buf;
+ s32 err = 0;
+ int i;
+
+ inff_dbg(SCAN, "scanned AP count (%d)\n", scan->bss_count);
+ for (i = 0; i < scan->bss_count; i++) {
+ err = inff_cp_scan_inform_single_bss(scan, bss);
+ if (err == -ENOMEM)
+ break;
+
+ bss = (struct inff_cp_bss_info *)
+ ((unsigned char *)bss + bss->length);
+ }
+}
+
+static void
+inff_cp_scan_save_bss(struct inff_scan_info *scan, struct inff_cpevt_scan *new_bss)
+{
+ struct inff_cp_bss_info *bss = (struct inff_cp_bss_info *)scan->bss_buf;
+ u32 new_bss_len;
+ u8 i;
+
+ new_bss_len = sizeof(*bss) + new_bss->ie_len;
+
+ for (i = 0; i < scan->bss_count; i++) {
+ if (!memcmp(&bss->macaddr, &new_bss->macaddr, ETH_ALEN))
+ return;
+
+ bss = (struct inff_cp_bss_info *)((unsigned char *)bss + bss->length);
+ }
+
+ if (scan->used_buf_len + new_bss_len > INFF_SCAN_BUF_SIZE) {
+ inff_err("scan buffer is not enough\n");
+ return;
+ }
+
+ bss->length = new_bss_len;
+ bss->ssid_length = new_bss->ssid_length;
+ memcpy(bss->ssid, new_bss->ssid, new_bss->ssid_length);
+ memcpy(bss->macaddr, new_bss->macaddr, ETH_ALEN);
+ bss->signal_strength = new_bss->signal_strength;
+ bss->channel = new_bss->channel;
+ bss->band = new_bss->band;
+ bss->beacon_period = new_bss->beacon_period;
+ bss->capability = new_bss->capability;
+ bss->ie_len = new_bss->ie_len;
+
+ memcpy((u8 *)bss + sizeof(*bss), new_bss->ie, new_bss->ie_len);
+
+ inff_dbg(SCAN, "BSS Idx %d\n", scan->bss_count);
+ inff_dbg(SCAN, "SSID: %s\n", bss->ssid);
+ inff_dbg(SCAN, "BSSID: %pM\n", bss->macaddr);
+
+ scan->bss_count++;
+ scan->used_buf_len += new_bss_len;
+ inff_dbg(SCAN, "used_buf_len: %d\n", scan->used_buf_len);
+}
+
+/*
+ * inff_cp_scan_event_handler() - handle scan event.
+ *
+ * @scan: scan info context.
+ * @event: scan event.
+ */
+int
+inff_cp_scan_event_handler(struct inff_pub *drvr,
+ struct inff_cpevt_scan *scan_event)
+{
+ int ret = 0;
+ struct inff_scan_info *scan = &drvr->config->scan;
+
+ if (!scan->request)
+ return 0;
+
+ if (scan_event->scan_complete) {
+ inff_cp_scan_inform_bss(scan);
+ inff_cp_scan_complete(drvr, false);
+ } else {
+ inff_cp_scan_save_bss(scan, scan_event);
+ }
+
+ return ret;
+}
+
+int
+inff_cp_scan_init(struct inff_cfg80211_info *cfg)
+{
+ struct inff_scan_info *scan = &cfg->scan;
+
+ scan->wiphy = cfg->wiphy;
+
+ scan->bss_buf = kzalloc(INFF_SCAN_BUF_SIZE, GFP_KERNEL);
+ if (!scan->bss_buf)
+ return -ENOMEM;
+
+ inff_cp_scan_reset(scan);
+
+ return 0;
+}
+
+int
+inff_cp_scan_start(struct inff_cfg80211_info *cfg, struct cfg80211_scan_request *request)
+{
+ struct inff_cfg80211_vif *vif = NULL;
+ struct inff_scan_info *scan = &cfg->scan;
+ int err;
+
+ if (test_bit(INFF_SCAN_STATUS_BUSY, &cfg->scan_status)) {
+ inff_err("Scan in-progress\n");
+ return -EAGAIN;
+ }
+
+ vif = container_of(request->wdev, struct inff_cfg80211_vif, wdev);
+
+ scan->request = request;
+
+ err = inff_cpcmd_data_set(vif->ifp, INFF_CP_C_SCAN, NULL, 0);
+ if (err) {
+ inff_err("scan failed %d\n", err);
+ clear_bit(INFF_SCAN_STATUS_BUSY, &cfg->scan_status);
+ return -EINVAL;
+ }
+
+ set_bit(INFF_SCAN_STATUS_BUSY, &cfg->scan_status);
+ return 0;
+}
+
+s32
+inff_run_escan(struct inff_cfg80211_info *cfg, struct inff_if *ifp,
+ struct cfg80211_scan_request *request)
+{
+ s32 params_size = INFF_SCAN_PARAMS_V2_FIXED_SIZE +
+ offsetof(struct inff_escan_params_le, params_v2_le);
+ struct inff_escan_params_le *params;
+ s32 err = 0;
+
+ inff_dbg(SCAN, "E-SCAN START\n");
+
+ if (request) {
+ /* Allocate space for populating ssids in struct */
+ params_size += sizeof(u32) * ((request->n_channels + 1) / 2);
+
+ /* Allocate space for populating ssids in struct */
+ params_size += sizeof(struct inff_ssid_le) * request->n_ssids;
+ }
+
+ params = kzalloc(params_size, GFP_KERNEL);
+ if (!params) {
+ err = -ENOMEM;
+ goto exit;
+ }
+ WARN_ON_ONCE(params_size + sizeof("escan") >= INFF_DCMD_MEDLEN);
+ inff_escan_prep(cfg, ¶ms->params_v2_le, request);
+
+ params->version = cpu_to_le32(INFF_ESCAN_REQ_VERSION_V2);
+
+ if (!inff_feat_is_enabled(ifp, INFF_FEAT_SCAN_V2)) {
+ struct inff_escan_params_le *params_v1;
+
+ params_size -= INFF_SCAN_PARAMS_V2_FIXED_SIZE;
+ params_size += INFF_SCAN_PARAMS_FIXED_SIZE;
+ params_v1 = kzalloc(params_size, GFP_KERNEL);
+ if (!params_v1) {
+ err = -ENOMEM;
+ goto exit_params;
+ }
+ params_v1->version = cpu_to_le32(INFF_ESCAN_REQ_VERSION);
+ inff_scan_params_v2_to_v1(¶ms->params_v2_le, ¶ms_v1->params_le);
+ kfree(params);
+ params = params_v1;
+ }
+
+ params->action = cpu_to_le16(WL_ESCAN_ACTION_START);
+ params->sync_id = cpu_to_le16(0x1234);
+
+ err = inff_fwcmd_iovar_data_set(ifp, "escan", params, params_size);
+ if (err) {
+ if (err == -EBUSY)
+ inff_dbg(INFO, "system busy : escan canceled\n");
+ else
+ iphy_err(ifp->drvr, "error (%d)\n", err);
+ }
+
+exit_params:
+ kfree(params);
+exit:
+ return err;
+}
+
+static s32
+inff_do_escan(struct inff_if *ifp, struct cfg80211_scan_request *request)
+{
+ struct inff_cfg80211_info *cfg = ifp->drvr->config;
+ s32 err;
+ struct inff_scan_results *results;
+ struct escan_info *escan = &cfg->escan_info;
+
+ escan->ifp = ifp;
+ escan->wiphy = cfg->wiphy;
+ escan->escan_state = WL_ESCAN_STATE_SCANNING;
+
+ inff_scan_config_mpc(ifp, 0);
+ results = (struct inff_scan_results *)cfg->escan_info.escan_buf;
+ results->version = 0;
+ results->count = 0;
+ results->buflen = WL_ESCAN_RESULTS_FIXED_SIZE;
+
+ err = escan->run(cfg, ifp, request);
+ if (err)
+ inff_scan_config_mpc(ifp, 1);
+ return err;
+}
+
+void inff_abort_scanning(struct inff_cfg80211_info *cfg)
+{
+ struct escan_info *escan = &cfg->escan_info;
+
+ set_bit(INFF_SCAN_STATUS_ABORT, &cfg->scan_status);
+ if (cfg->int_escan_map || cfg->scan_request) {
+ escan->escan_state = WL_ESCAN_STATE_IDLE;
+ inff_notify_escan_complete(cfg, escan->ifp, true, true);
+ }
+ clear_bit(INFF_SCAN_STATUS_BUSY, &cfg->scan_status);
+ clear_bit(INFF_SCAN_STATUS_ABORT, &cfg->scan_status);
+}
+
+void inff_escan_prep(struct inff_cfg80211_info *cfg,
+ struct inff_scan_params_v2_le *params_le,
+ struct cfg80211_scan_request *request)
+{
+ u32 n_ssids;
+ u32 n_channels;
+ s32 i;
+ s32 offset;
+ u16 chanspec;
+ char *ptr;
+ int length;
+ struct inff_ssid_le ssid_le;
+
+ eth_broadcast_addr(params_le->bssid);
+
+ length = INFF_SCAN_PARAMS_V2_FIXED_SIZE;
+
+ params_le->version = cpu_to_le16(INFF_SCAN_PARAMS_VERSION_V2);
+ params_le->bss_type = DOT11_BSSTYPE_ANY;
+ params_le->scan_type = cpu_to_le32(INFF_SCANTYPE_ACTIVE);
+ params_le->channel_num = 0;
+ params_le->nprobes = cpu_to_le32(-1);
+ params_le->active_time = cpu_to_le32(-1);
+ params_le->passive_time = cpu_to_le32(-1);
+ params_le->home_time = cpu_to_le32(-1);
+ memset(¶ms_le->ssid_le, 0, sizeof(params_le->ssid_le));
+
+ /* Scan abort */
+ if (!request) {
+ length += sizeof(u16);
+ params_le->channel_num = cpu_to_le32(1);
+ params_le->channel_list[0] = cpu_to_le16(-1);
+ params_le->length = cpu_to_le16(length);
+ return;
+ }
+
+ n_ssids = request->n_ssids;
+ n_channels = request->n_channels;
+
+ /* Copy channel array if applicable */
+ inff_dbg(SCAN, "### List of channelspecs to scan ### %d\n",
+ n_channels);
+ if (n_channels > 0) {
+ length += roundup(sizeof(u16) * n_channels, sizeof(u32));
+ for (i = 0; i < n_channels; i++) {
+ chanspec = inff_nl80211_chan_to_chanspec(request->channels[i]);
+ inff_dbg(SCAN, "Chan : %d, Channel spec: %x\n",
+ request->channels[i]->hw_value, chanspec);
+ params_le->channel_list[i] = cpu_to_le16(chanspec);
+ }
+ } else {
+ inff_dbg(SCAN, "Scanning all channels\n");
+ }
+
+ /* Copy ssid array if applicable */
+ inff_dbg(SCAN, "### List of SSIDs to scan ### %d\n", n_ssids);
+ if (n_ssids > 0) {
+ offset = offsetof(struct inff_scan_params_v2_le, channel_list) +
+ n_channels * sizeof(u16);
+ offset = roundup(offset, sizeof(u32));
+ length += sizeof(ssid_le) * n_ssids;
+ ptr = (char *)params_le + offset;
+ for (i = 0; i < n_ssids; i++) {
+ memset(&ssid_le, 0, sizeof(ssid_le));
+ ssid_le.SSID_len =
+ cpu_to_le32(request->ssids[i].ssid_len);
+ memcpy(ssid_le.SSID, request->ssids[i].ssid,
+ request->ssids[i].ssid_len);
+ if (!ssid_le.SSID_len)
+ inff_dbg(SCAN, "%d: Broadcast scan\n", i);
+ else
+ inff_dbg(SCAN, "%d: scan for %.32s size=%d\n",
+ i, ssid_le.SSID, ssid_le.SSID_len);
+ memcpy(ptr, &ssid_le, sizeof(ssid_le));
+ ptr += sizeof(ssid_le);
+ }
+ } else {
+ inff_dbg(SCAN, "Performing passive scan\n");
+ params_le->scan_type = cpu_to_le32(INFF_SCANTYPE_PASSIVE);
+ }
+ params_le->length = cpu_to_le16(length);
+ /* Adding mask to channel numbers */
+ params_le->channel_num =
+ cpu_to_le32((n_ssids << INFF_SCAN_PARAMS_NSSID_SHIFT) |
+ (n_channels & INFF_SCAN_PARAMS_COUNT_MASK));
+}
+
+s32
+inff_cfg80211_scan(struct wiphy *wiphy, struct cfg80211_scan_request *request)
+{
+ struct inff_cfg80211_info *cfg = wiphy_to_cfg(wiphy);
+ struct inff_pub *drvr = cfg->pub;
+ struct inff_cfg80211_vif *vif;
+ s32 err = 0;
+ struct inff_tlv *interworking_ie = NULL;
+
+ vif = container_of(request->wdev, struct inff_cfg80211_vif, wdev);
+ if (!inff_check_vif_up(vif))
+ return -EIO;
+
+ if (inff_socitype_is_cp(drvr->bus_if)) {
+ err = inff_cp_scan_start(cfg, request);
+ if (err)
+ goto scan_out;
+ else
+ return 0;
+ }
+
+ if (test_bit(INFF_SCAN_STATUS_BUSY, &cfg->scan_status)) {
+ iphy_err(drvr, "Scanning already: status (%lu)\n",
+ cfg->scan_status);
+ return -EAGAIN;
+ }
+ if (test_bit(INFF_SCAN_STATUS_ABORT, &cfg->scan_status)) {
+ iphy_err(drvr, "Scanning being aborted: status (%lu)\n",
+ cfg->scan_status);
+ return -EAGAIN;
+ }
+ if (test_bit(INFF_SCAN_STATUS_SUPPRESS, &cfg->scan_status)) {
+ iphy_err(drvr, "Scanning suppressed: status (%lu)\n",
+ cfg->scan_status);
+ return -EAGAIN;
+ }
+ if (test_bit(INFF_VIF_STATUS_CONNECTING, &vif->sme_state)) {
+ iphy_err(drvr, "Connecting: status (%lu)\n", vif->sme_state);
+ return -EAGAIN;
+ }
+
+ inff_dbg(SCAN, "START ESCAN\n");
+
+ cfg->scan_request = request;
+ set_bit(INFF_SCAN_STATUS_BUSY, &cfg->scan_status);
+
+ interworking_ie = inff_find_iwie(request->ie, request->ie_len);
+ if (interworking_ie) {
+ err = inff_add_iwie(cfg, vif->ifp,
+ INFF_VNDR_IE_CUSTOM_FLAG,
+ interworking_ie->id,
+ interworking_ie->data,
+ interworking_ie->len);
+ if (err)
+ inff_err("Failed to add interworking IE");
+ } else {
+ /* we have to clear IW IE */
+ inff_clear_iwie(cfg, vif->ifp);
+ }
+
+ cfg->escan_info.run = inff_run_escan;
+
+ err = inff_do_escan(vif->ifp, request);
+ if (err)
+ goto scan_out;
+
+ /* Arm scan timeout timer */
+ wiphy_delayed_work_queue(cfg->wiphy, &cfg->escan_timeout_work,
+ msecs_to_jiffies(INFF_ESCAN_TIMER_INTERVAL_MS));
+
+ return 0;
+
+scan_out:
+ iphy_err(drvr, "scan error (%d)\n", err);
+ clear_bit(INFF_SCAN_STATUS_BUSY, &cfg->scan_status);
+ cfg->scan_request = NULL;
+ return err;
+}
+
+static s32
+inff_cfg80211_escan_handler(struct inff_if *ifp,
+ const struct inff_event_msg *e, void *data)
+{
+ struct inff_pub *drvr = ifp->drvr;
+ struct inff_cfg80211_info *cfg = drvr->config;
+ s32 status;
+ struct inff_escan_result_le *escan_result_le;
+ u32 escan_buflen;
+ struct inff_bss_info_le *bss_info_le;
+ struct inff_bss_info_le *bss = NULL;
+ u32 bi_length;
+ struct inff_scan_results *list;
+ u32 i;
+ bool aborted;
+
+ status = e->status;
+
+ if (status == INFF_E_STATUS_ABORT)
+ goto exit;
+
+ if (!test_bit(INFF_SCAN_STATUS_BUSY, &cfg->scan_status)) {
+ iphy_err(drvr, "scan not ready, bsscfgidx=%d\n",
+ ifp->bsscfgidx);
+ return -EPERM;
+ }
+
+ if (status == INFF_E_STATUS_PARTIAL) {
+ inff_dbg(SCAN, "ESCAN Partial result\n");
+ if (e->datalen < sizeof(*escan_result_le)) {
+ iphy_err(drvr, "invalid event data length\n");
+ goto exit;
+ }
+ escan_result_le = (struct inff_escan_result_le *)data;
+ if (!escan_result_le) {
+ iphy_err(drvr, "Invalid escan result (NULL pointer)\n");
+ goto exit;
+ }
+ escan_buflen = le32_to_cpu(escan_result_le->buflen);
+ if (escan_buflen > INFF_ESCAN_BUF_SIZE ||
+ escan_buflen > e->datalen ||
+ escan_buflen < sizeof(*escan_result_le)) {
+ iphy_err(drvr, "Invalid escan buffer length: %d\n",
+ escan_buflen);
+ goto exit;
+ }
+ if (le16_to_cpu(escan_result_le->bss_count) != 1) {
+ iphy_err(drvr, "Invalid bss_count %d: ignoring\n",
+ escan_result_le->bss_count);
+ goto exit;
+ }
+ bss_info_le = &escan_result_le->bss_info_le;
+
+ if (!cfg->int_escan_map && !cfg->scan_request) {
+ inff_dbg(SCAN, "result without cfg80211 request\n");
+ goto exit;
+ }
+
+ bi_length = le32_to_cpu(bss_info_le->length);
+ if (bi_length != escan_buflen - WL_ESCAN_RESULTS_FIXED_SIZE) {
+ iphy_err(drvr, "Ignoring invalid bss_info length: %d\n",
+ bi_length);
+ goto exit;
+ }
+
+ if (!(cfg->wiphy->interface_modes &
+ BIT(NL80211_IFTYPE_ADHOC))) {
+ if (le16_to_cpu(bss_info_le->capability) &
+ WLAN_CAPABILITY_IBSS) {
+ iphy_err(drvr, "Ignoring IBSS result\n");
+ goto exit;
+ }
+ }
+
+ list = (struct inff_scan_results *)
+ cfg->escan_info.escan_buf;
+
+ if (bi_length > INFF_ESCAN_BUF_SIZE - list->buflen) {
+ iphy_err(drvr, "Buffer is too small: ignoring\n");
+ goto exit;
+ }
+
+ for (i = 0; i < list->count; i++) {
+ bss = bss ? (struct inff_bss_info_le *)
+ ((unsigned char *)bss +
+ le32_to_cpu(bss->length)) : list->bss_info_le;
+ if (inff_compare_update_same_bss(cfg, bss,
+ bss_info_le))
+ goto exit;
+ }
+ memcpy(&cfg->escan_info.escan_buf[list->buflen], bss_info_le,
+ bi_length);
+ list->version = le32_to_cpu(bss_info_le->version);
+ list->buflen += bi_length;
+ list->count++;
+ } else {
+ cfg->escan_info.escan_state = WL_ESCAN_STATE_IDLE;
+ if (cfg->int_escan_map || cfg->scan_request) {
+ inff_inform_bss(cfg);
+ aborted = status != INFF_E_STATUS_SUCCESS;
+ inff_notify_escan_complete(cfg, ifp, aborted, false);
+ } else {
+ inff_dbg(SCAN, "Ignored scan complete result 0x%x\n",
+ status);
+ }
+ }
+exit: return 0;
+}
+
+static void inff_cfg80211_escan_timeout_worker(struct wiphy *wiphy,
+ struct wiphy_work *work)
+{
+ struct inff_cfg80211_info *cfg =
+ container_of(work, struct inff_cfg80211_info,
+ escan_timeout_work.work);
+
+ lockdep_assert_wiphy(wiphy);
+
+ if (cfg->int_escan_map || cfg->scan_request) {
+ iphy_err(cfg->pub, "ESCAN Request timed out\n");
+ inff_inform_bss(cfg);
+ inff_notify_escan_complete(cfg, cfg->escan_info.ifp, true, true);
+ }
+}
+
+int inff_init_escan(struct inff_cfg80211_info *cfg)
+{
+ cfg->scan_request = NULL;
+
+ cfg->escan_info.escan_buf = kzalloc(INFF_ESCAN_BUF_SIZE, GFP_KERNEL);
+ if (!cfg->escan_info.escan_buf)
+ return -ENOMEM;
+
+ inff_fwevt_register(cfg->pub, INFF_E_ESCAN_RESULT,
+ inff_cfg80211_escan_handler);
+ cfg->escan_info.escan_state = WL_ESCAN_STATE_IDLE;
+
+ /* Init scan timeout delayed work */
+ wiphy_delayed_work_init(&cfg->escan_timeout_work,
+ inff_cfg80211_escan_timeout_worker);
+
+ return 0;
+}
+
+void inff_deinit_escan(struct inff_cfg80211_info *cfg)
+{
+ kfree(cfg->escan_info.escan_buf);
+ cfg->escan_info.escan_buf = NULL;
+
+ inff_abort_scanning(cfg);
+
+ wiphy_delayed_work_cancel(cfg->wiphy, &cfg->escan_timeout_work);
+}
+
+void inff_scan_params_v2_to_v1(struct inff_scan_params_v2_le *params_v2_le,
+ struct inff_scan_params_le *params_le)
+{
+ size_t params_size;
+ u32 ch;
+ int n_channels, n_ssids;
+
+ memcpy(¶ms_le->ssid_le, ¶ms_v2_le->ssid_le,
+ sizeof(params_le->ssid_le));
+ memcpy(¶ms_le->bssid, ¶ms_v2_le->bssid,
+ sizeof(params_le->bssid));
+
+ params_le->bss_type = params_v2_le->bss_type;
+ params_le->scan_type = le32_to_cpu(params_v2_le->scan_type);
+ params_le->nprobes = params_v2_le->nprobes;
+ params_le->active_time = params_v2_le->active_time;
+ params_le->passive_time = params_v2_le->passive_time;
+ params_le->home_time = params_v2_le->home_time;
+ params_le->channel_num = params_v2_le->channel_num;
+
+ ch = le32_to_cpu(params_v2_le->channel_num);
+ n_channels = ch & INFF_SCAN_PARAMS_COUNT_MASK;
+ n_ssids = ch >> INFF_SCAN_PARAMS_NSSID_SHIFT;
+
+ params_size = sizeof(u16) * n_channels;
+ if (n_ssids > 0) {
+ params_size = roundup(params_size, sizeof(u32));
+ params_size += sizeof(struct inff_ssid_le) * n_ssids;
+ }
+
+ memcpy(¶ms_le->channel_list[0],
+ ¶ms_v2_le->channel_list[0], params_size);
+}
+
+void inff_scan_config_mpc(struct inff_if *ifp, int mpc)
+{
+ if (inff_feat_is_quirk_enabled(ifp, INFF_FEAT_QUIRK_NEED_MPC))
+ inff_set_mpc(ifp, mpc);
+}
+
+s32 inff_notify_escan_complete(struct inff_cfg80211_info *cfg,
+ struct inff_if *ifp, bool aborted,
+ bool fw_abort)
+{
+ struct inff_pub *drvr = cfg->pub;
+ struct inff_scan_params_v2_le params_v2_le;
+ struct cfg80211_scan_request *scan_request;
+ s32 err = 0;
+
+ /* clear scan request, because the FW abort can cause a second call */
+ /* to this functon and might cause a double cfg80211_scan_done */
+ scan_request = cfg->scan_request;
+ cfg->scan_request = NULL;
+
+ wiphy_delayed_work_cancel(cfg->wiphy, &cfg->escan_timeout_work);
+
+ if (fw_abort) {
+ /* Do a scan abort to stop the driver's scan engine */
+ inff_dbg(SCAN, "ABORT scan in firmware\n");
+
+ inff_escan_prep(cfg, ¶ms_v2_le, NULL);
+
+ /* E-Scan (or anyother type) can be aborted by SCAN */
+ if (inff_feat_is_enabled(ifp, INFF_FEAT_SCAN_V2)) {
+ err = inff_fwcmd_data_set(ifp, INFF_C_SCAN,
+ ¶ms_v2_le,
+ sizeof(params_v2_le));
+ } else {
+ struct inff_scan_params_le params_le;
+
+ inff_scan_params_v2_to_v1(¶ms_v2_le, ¶ms_le);
+ err = inff_fwcmd_data_set(ifp, INFF_C_SCAN,
+ ¶ms_le,
+ sizeof(params_le));
+ }
+
+ if (err)
+ iphy_err(drvr, "Scan abort failed\n");
+ }
+
+ inff_scan_config_mpc(ifp, 1);
+
+ if (scan_request) {
+ struct cfg80211_scan_info info = {
+ .aborted = aborted,
+ };
+
+ inff_dbg(SCAN, "ESCAN Completed scan: %s\n",
+ aborted ? "Aborted" : "Done");
+ cfg80211_scan_done(scan_request, &info);
+ }
+ if (!test_and_clear_bit(INFF_SCAN_STATUS_BUSY, &cfg->scan_status))
+ inff_dbg(SCAN, "Scan complete\n");
+
+ return err;
+}
diff --git a/drivers/net/wireless/infineon/inffmac/scan.h b/drivers/net/wireless/infineon/inffmac/scan.h
new file mode 100644
index 000000000000..2303a959ea1c
--- /dev/null
+++ b/drivers/net/wireless/infineon/inffmac/scan.h
@@ -0,0 +1,136 @@
+/* SPDX-License-Identifier: ISC */
+/*
+ * Copyright (c) 2010 Broadcom Corporation
+ *
+ * Copyright (c) 2025-2026, Infineon Technologies AG, or an affiliate of Infineon Technologies AG.
+ * All rights reserved.
+ */
+
+#ifndef INFF_SCAN_H
+#define INFF_SCAN_H
+
+#define INFF_SCAN_IE_LEN_MAX 2048
+
+#define INFF_SCAN_JOIN_ACTIVE_DWELL_TIME_MS 320
+#define INFF_SCAN_JOIN_PASSIVE_DWELL_TIME_MS 400
+#define INFF_SCAN_JOIN_ACTIVE_DWELL_TIME_MS_6E 80
+#define INFF_SCAN_JOIN_PASSIVE_DWELL_TIME_MS_6E 130
+#define INFF_SCAN_JOIN_PROBE_INTERVAL_MS 20
+
+#define INFF_SCAN_CHANNEL_TIME 40
+#define INFF_SCAN_UNASSOC_TIME 40
+#define INFF_SCAN_PASSIVE_TIME 120
+
+/* Keep SCAN_BUF_SIZE below 64K (65536). Allocing over 64K can be
+ * problematic on some systems and should be avoided.
+ */
+#define INFF_SCAN_BUF_SIZE 65000
+#define INFF_ESCAN_BUF_SIZE 65000
+#define INFF_ESCAN_TIMER_INTERVAL_MS 10000 /* E-Scan timeout */
+
+#define WL_ESCAN_ACTION_START 1
+#define WL_ESCAN_ACTION_CONTINUE 2
+#define WL_ESCAN_ACTION_ABORT 3
+
+/**
+ * enum inff_scan_status - scan engine status
+ *
+ * @INFF_SCAN_STATUS_BUSY: scanning in progress on dongle.
+ * @INFF_SCAN_STATUS_ABORT: scan being aborted on dongle.
+ * @INFF_SCAN_STATUS_SUPPRESS: scanning is suppressed in driver.
+ */
+enum inff_scan_status {
+ INFF_SCAN_STATUS_BUSY,
+ INFF_SCAN_STATUS_ABORT,
+ INFF_SCAN_STATUS_SUPPRESS,
+};
+
+/* dongle escan state */
+enum wl_escan_state {
+ WL_ESCAN_STATE_IDLE,
+ WL_ESCAN_STATE_SCANNING
+};
+
+/**
+ * BSS info structure
+ * Applications MUST CHECK ie_offset field and length field to access IEs and
+ * next bss_info structure in a vector (in struct inff_scan_results)
+ */
+struct inff_cp_bss_info {
+ /* byte length of data in this record, starting at version and including IEs */
+ u32 length;
+ /* SSID length */
+ u8 ssid_length;
+ /* SSID of the access point */
+ char ssid[IEEE80211_MAX_SSID_LEN + 1];
+ /* MAC Address of the access point */
+ u8 macaddr[ETH_ALEN];
+ /* signal strength */
+ s16 signal_strength;
+ /* channel number */
+ u8 channel;
+ /* WiFi band of access point */
+ u8 band;
+ /* Interval b/w two consecutive beacon frames. Units are Kusec */
+ u16 beacon_period;
+ /* Capability information */
+ u16 capability;
+ /* Length of the IE. */
+ u32 ie_len;
+};
+
+struct inff_scan_info {
+ struct wiphy *wiphy;
+ struct cfg80211_scan_request *request;
+ u32 bss_count;
+ u8 *bss_buf;
+ u32 used_buf_len;
+};
+
+struct escan_info {
+ u32 escan_state;
+ u8 *escan_buf;
+ struct wiphy *wiphy;
+ struct inff_if *ifp;
+ s32 (*run)(struct inff_cfg80211_info *cfg, struct inff_if *ifp,
+ struct cfg80211_scan_request *request);
+};
+
+s32 inff_dongle_scantime(struct inff_if *ifp);
+
+s32 inff_run_escan(struct inff_cfg80211_info *cfg, struct inff_if *ifp,
+ struct cfg80211_scan_request *request);
+
+s32 inff_cfg80211_scan(struct wiphy *wiphy, struct cfg80211_scan_request *request);
+
+void inff_escan_prep(struct inff_cfg80211_info *cfg,
+ struct inff_scan_params_v2_le *params_le,
+ struct cfg80211_scan_request *request);
+
+int inff_init_escan(struct inff_cfg80211_info *cfg);
+void inff_deinit_escan(struct inff_cfg80211_info *cfg);
+
+int inff_cfg80211_sched_scan_stop(struct wiphy *wiphy,
+ struct net_device *ndev, u64 reqid);
+
+int inff_cfg80211_sched_scan_start(struct wiphy *wiphy,
+ struct net_device *ndev,
+ struct cfg80211_sched_scan_request *req);
+
+void inff_scan_params_v2_to_v1(struct inff_scan_params_v2_le *params_v2_le,
+ struct inff_scan_params_le *params_le);
+
+s32 inff_notify_escan_complete(struct inff_cfg80211_info *cfg,
+ struct inff_if *ifp, bool aborted,
+ bool fw_abort);
+void inff_scan_config_mpc(struct inff_if *ifp, int mpc);
+void inff_abort_scanning(struct inff_cfg80211_info *cfg);
+
+int
+inff_cp_scan_init(struct inff_cfg80211_info *cfg);
+int
+inff_cp_scan_start(struct inff_cfg80211_info *cfg, struct cfg80211_scan_request *request);
+int
+inff_cp_scan_event_handler(struct inff_pub *drvr, struct inff_cpevt_scan *result);
+void inff_cp_scan_abort(struct inff_pub *drvr);
+#endif /* INFF_SCAN_H */
--
2.43.0
^ permalink raw reply related [flat|nested] 26+ messages in thread* [PATCH wireless-next v3 22/25] wifi: inffmac: add chan.c/h
2026-09-02 16:50 [PATCH wireless-next v3 00/25] wifi: inffmac: introducing a driver for Infineon's new generation chipsets Gokul Sivakumar
` (20 preceding siblings ...)
2026-09-02 16:50 ` [PATCH wireless-next v3 21/25] wifi: inffmac: add scan.c/h Gokul Sivakumar
@ 2026-09-02 16:50 ` Gokul Sivakumar
2026-09-02 16:50 ` [PATCH wireless-next v3 23/25] wifi: inffmac: add debug.c/h Gokul Sivakumar
` (2 subsequent siblings)
24 siblings, 0 replies; 26+ messages in thread
From: Gokul Sivakumar @ 2026-09-02 16:50 UTC (permalink / raw)
To: linux-wireless, johannes
Cc: arend.vanspriel, wlan-kernel-dev-list, gokulkumar.sivakumar
Common definitions of the wifi channel and chanspec specific structures,
MACROs and helper functions.
Signed-off-by: Gokul Sivakumar <gokulkumar.sivakumar@infineon.com>
---
drivers/net/wireless/infineon/inffmac/chan.c | 795 +++++++++++++++++++
drivers/net/wireless/infineon/inffmac/chan.h | 365 +++++++++
2 files changed, 1160 insertions(+)
create mode 100644 drivers/net/wireless/infineon/inffmac/chan.c
create mode 100644 drivers/net/wireless/infineon/inffmac/chan.h
diff --git a/drivers/net/wireless/infineon/inffmac/chan.c b/drivers/net/wireless/infineon/inffmac/chan.c
new file mode 100644
index 000000000000..6d81d6d2f92d
--- /dev/null
+++ b/drivers/net/wireless/infineon/inffmac/chan.c
@@ -0,0 +1,795 @@
+// SPDX-License-Identifier: ISC
+/*
+ * Copyright (c) 2013 Broadcom Corporation
+ *
+ * Copyright (c) 2025-2026, Infineon Technologies AG, or an affiliate of Infineon Technologies AG.
+ * All rights reserved.
+ */
+
+#include <linux/module.h>
+#include <net/cfg80211.h>
+
+#include "cfg80211.h"
+#include "interface.h"
+#include "chan.h"
+#include "utils.h"
+#include "debug.h"
+#include "dev_cmd.h"
+#include "feature.h"
+
+#define INFF_MAX_CHANSPEC_LIST \
+ (INFF_DCMD_MEDLEN / sizeof(__le32) - 1)
+
+#define RATE_TO_BASE100KBPS(rate) (((rate) * 10) / 2)
+#define RATETAB_ENT(_bitrate, _rateid, _flags) \
+ { \
+ .bitrate = (_bitrate), \
+ .hw_value = (_rateid), \
+ .flags = (_flags), \
+ }
+
+static struct ieee80211_rate __wl_rates[] = {
+ RATETAB_ENT(RATE_TO_BASE100KBPS(INF_RATE_1M), INF_RATE_1M, 0),
+ RATETAB_ENT(RATE_TO_BASE100KBPS(INF_RATE_2M), INF_RATE_2M, IEEE80211_RATE_SHORT_PREAMBLE),
+ RATETAB_ENT(RATE_TO_BASE100KBPS(INF_RATE_5M5), INF_RATE_5M5, IEEE80211_RATE_SHORT_PREAMBLE),
+ RATETAB_ENT(RATE_TO_BASE100KBPS(INF_RATE_11M), INF_RATE_11M, IEEE80211_RATE_SHORT_PREAMBLE),
+ RATETAB_ENT(RATE_TO_BASE100KBPS(INF_RATE_6M), INF_RATE_6M, 0),
+ RATETAB_ENT(RATE_TO_BASE100KBPS(INF_RATE_9M), INF_RATE_9M, 0),
+ RATETAB_ENT(RATE_TO_BASE100KBPS(INF_RATE_12M), INF_RATE_12M, 0),
+ RATETAB_ENT(RATE_TO_BASE100KBPS(INF_RATE_18M), INF_RATE_18M, 0),
+ RATETAB_ENT(RATE_TO_BASE100KBPS(INF_RATE_24M), INF_RATE_24M, 0),
+ RATETAB_ENT(RATE_TO_BASE100KBPS(INF_RATE_36M), INF_RATE_36M, 0),
+ RATETAB_ENT(RATE_TO_BASE100KBPS(INF_RATE_48M), INF_RATE_48M, 0),
+ RATETAB_ENT(RATE_TO_BASE100KBPS(INF_RATE_54M), INF_RATE_54M, 0),
+};
+
+#define wl_g_rates (__wl_rates + 0)
+#define wl_g_rates_size ARRAY_SIZE(__wl_rates)
+#define wl_a_rates (__wl_rates + 4)
+#define wl_a_rates_size (wl_g_rates_size - 4)
+
+#define CHAN2G(_channel, _freq) { \
+ .band = NL80211_BAND_2GHZ, \
+ .center_freq = (_freq), \
+ .hw_value = (_channel), \
+ .max_antenna_gain = 0, \
+ .max_power = 30, \
+}
+
+#define CH5G_FREQ(_channel) (5000 + (5 * (_channel)))
+#define CHAN5G(_channel, _freq) { \
+ .band = NL80211_BAND_5GHZ, \
+ .center_freq = (_freq), \
+ .hw_value = (_channel), \
+ .max_antenna_gain = 0, \
+ .max_power = 30, \
+}
+
+#define CH6G_FREQ(_channel) (5950 + (5 * (_channel)))
+#define CHAN6G(_channel, _freq) { \
+ .band = NL80211_BAND_6GHZ, \
+ .center_freq = (_freq), \
+ .hw_value = (_channel), \
+ .max_antenna_gain = 0, \
+ .max_power = 30, \
+}
+
+static struct ieee80211_channel __wl_2ghz_channels[] = {
+ CHAN2G(1, 2412), CHAN2G(2, 2417), CHAN2G(3, 2422), CHAN2G(4, 2427),
+ CHAN2G(5, 2432), CHAN2G(6, 2437), CHAN2G(7, 2442), CHAN2G(8, 2447),
+ CHAN2G(9, 2452), CHAN2G(10, 2457), CHAN2G(11, 2462), CHAN2G(12, 2467),
+ CHAN2G(13, 2472), CHAN2G(14, 2484)
+};
+
+static struct ieee80211_channel __wl_5ghz_channels[] = {
+ CHAN5G(34, CH5G_FREQ(34)), CHAN5G(36, CH5G_FREQ(36)), CHAN5G(38, CH5G_FREQ(38)),
+ CHAN5G(40, CH5G_FREQ(40)), CHAN5G(42, CH5G_FREQ(42)), CHAN5G(44, CH5G_FREQ(44)),
+ CHAN5G(46, CH5G_FREQ(46)), CHAN5G(48, CH5G_FREQ(48)), CHAN5G(52, CH5G_FREQ(52)),
+ CHAN5G(56, CH5G_FREQ(56)), CHAN5G(60, CH5G_FREQ(60)), CHAN5G(64, CH5G_FREQ(64)),
+ CHAN5G(100, CH5G_FREQ(100)), CHAN5G(104, CH5G_FREQ(104)), CHAN5G(108, CH5G_FREQ(108)),
+ CHAN5G(112, CH5G_FREQ(112)), CHAN5G(116, CH5G_FREQ(116)), CHAN5G(120, CH5G_FREQ(120)),
+ CHAN5G(124, CH5G_FREQ(124)), CHAN5G(128, CH5G_FREQ(128)), CHAN5G(132, CH5G_FREQ(132)),
+ CHAN5G(136, CH5G_FREQ(136)), CHAN5G(140, CH5G_FREQ(140)), CHAN5G(144, CH5G_FREQ(144)),
+ CHAN5G(149, CH5G_FREQ(149)), CHAN5G(153, CH5G_FREQ(153)), CHAN5G(157, CH5G_FREQ(157)),
+ CHAN5G(161, CH5G_FREQ(161)), CHAN5G(165, CH5G_FREQ(165))
+};
+
+static struct ieee80211_channel __wl_6ghz_channels[] = {
+ CHAN6G(1, CH6G_FREQ(1)), CHAN6G(5, CH6G_FREQ(5)), CHAN6G(9, CH6G_FREQ(9)),
+ CHAN6G(13, CH6G_FREQ(13)), CHAN6G(17, CH6G_FREQ(17)),
+ CHAN6G(21, CH6G_FREQ(21)), CHAN6G(25, CH6G_FREQ(25)), CHAN6G(29, CH6G_FREQ(29)),
+ CHAN6G(33, CH6G_FREQ(33)), CHAN6G(37, CH6G_FREQ(37)),
+ CHAN6G(41, CH6G_FREQ(41)), CHAN6G(45, CH6G_FREQ(45)), CHAN6G(49, CH6G_FREQ(49)),
+ CHAN6G(53, CH6G_FREQ(53)), CHAN6G(57, CH6G_FREQ(57)),
+ CHAN6G(61, CH6G_FREQ(61)), CHAN6G(65, CH6G_FREQ(65)), CHAN6G(69, CH6G_FREQ(69)),
+ CHAN6G(73, CH6G_FREQ(73)), CHAN6G(77, CH6G_FREQ(77)),
+ CHAN6G(81, CH6G_FREQ(81)), CHAN6G(85, CH6G_FREQ(85)), CHAN6G(89, CH6G_FREQ(89)),
+ CHAN6G(93, CH6G_FREQ(93)), CHAN6G(97, CH6G_FREQ(97)),
+ CHAN6G(101, CH6G_FREQ(101)), CHAN6G(105, CH6G_FREQ(105)), CHAN6G(109, CH6G_FREQ(109)),
+ CHAN6G(113, CH6G_FREQ(113)), CHAN6G(117, CH6G_FREQ(117)),
+ CHAN6G(121, CH6G_FREQ(121)), CHAN6G(125, CH6G_FREQ(125)), CHAN6G(129, CH6G_FREQ(129)),
+ CHAN6G(133, CH6G_FREQ(133)), CHAN6G(137, CH6G_FREQ(137)),
+ CHAN6G(141, CH6G_FREQ(141)), CHAN6G(145, CH6G_FREQ(145)), CHAN6G(149, CH6G_FREQ(149)),
+ CHAN6G(153, CH6G_FREQ(153)), CHAN6G(157, CH6G_FREQ(157)),
+ CHAN6G(161, CH6G_FREQ(161)), CHAN6G(165, CH6G_FREQ(165)), CHAN6G(169, CH6G_FREQ(169)),
+ CHAN6G(173, CH6G_FREQ(173)), CHAN6G(177, CH6G_FREQ(177)),
+ CHAN6G(181, CH6G_FREQ(181)), CHAN6G(185, CH6G_FREQ(185)), CHAN6G(189, CH6G_FREQ(189)),
+ CHAN6G(193, CH6G_FREQ(193)), CHAN6G(197, CH6G_FREQ(197)),
+ CHAN6G(201, CH6G_FREQ(201)), CHAN6G(205, CH6G_FREQ(205)), CHAN6G(209, CH6G_FREQ(209)),
+ CHAN6G(213, CH6G_FREQ(213)), CHAN6G(217, CH6G_FREQ(217)),
+ CHAN6G(221, CH6G_FREQ(221)), CHAN6G(225, CH6G_FREQ(225)), CHAN6G(229, CH6G_FREQ(229)),
+ CHAN6G(233, CH6G_FREQ(233))
+};
+
+/* Band templates duplicated per wiphy. The channel info
+ * above is added to the band during setup.
+ */
+static const struct ieee80211_supported_band __wl_band_2ghz = {
+ .band = NL80211_BAND_2GHZ,
+ .bitrates = wl_g_rates,
+ .n_bitrates = wl_g_rates_size,
+};
+
+static const struct ieee80211_supported_band __wl_band_5ghz = {
+ .band = NL80211_BAND_5GHZ,
+ .bitrates = wl_a_rates,
+ .n_bitrates = wl_a_rates_size,
+};
+
+static struct ieee80211_supported_band __wl_band_6ghz = {
+ .band = NL80211_BAND_6GHZ,
+ .bitrates = wl_a_rates,
+ .n_bitrates = wl_a_rates_size,
+};
+
+enum nl80211_band
+inff_chan_band_to_nl80211(u8 band)
+{
+ if (band == INFF_CHAN_BAND_2G)
+ return NL80211_BAND_2GHZ;
+ else if (band == INFF_CHAN_BAND_5G)
+ return NL80211_BAND_5GHZ;
+ else
+ return NL80211_BAND_6GHZ;
+}
+
+static u16
+inff_chan_bw_to_chspec_bw(enum inff_chan_bw bw)
+{
+ switch (bw) {
+ case INFF_CHAN_BW_20:
+ return INFF_CHSPEC_BW_20;
+ case INFF_CHAN_BW_40:
+ return INFF_CHSPEC_BW_40;
+ case INFF_CHAN_BW_80:
+ return INFF_CHSPEC_BW_80;
+ case INFF_CHAN_BW_160:
+ return INFF_CHSPEC_BW_160;
+ default:
+ WARN_ON(1);
+ }
+ return 0;
+}
+
+/*
+ * inff_chan_encchspec: encodes channel info into a chanspec, requires center channel
+ * number, ignores control one
+ */
+void
+inff_chan_encchspec(struct inff_chan *ch)
+{
+ if (ch->bw == INFF_CHAN_BW_20 || ch->sb == INFF_CHAN_SB_NONE)
+ ch->sb = INFF_CHAN_SB_L;
+
+ inff_maskset16(&ch->chspec, INFF_CHSPEC_CH_MASK,
+ INFF_CHSPEC_CH_SHIFT, ch->chnum);
+ inff_maskset16(&ch->chspec, INFF_CHSPEC_SB_MASK,
+ INFF_CHSPEC_SB_SHIFT, ch->sb);
+ inff_maskset16(&ch->chspec, INFF_CHSPEC_BW_MASK,
+ 0, inff_chan_bw_to_chspec_bw(ch->bw));
+
+ ch->chspec &= ~INFF_CHSPEC_BND_MASK;
+ switch (ch->band) {
+ case INFF_CHAN_BAND_6G:
+ ch->chspec |= INFF_CHSPEC_BND_6G;
+ break;
+ case INFF_CHAN_BAND_5G:
+ ch->chspec |= INFF_CHSPEC_BND_5G;
+ break;
+ case INFF_CHAN_BAND_2G:
+ ch->chspec |= INFF_CHSPEC_BND_2G;
+ break;
+ default:
+ WARN_ONCE(1, "Invalid band 0x%04x\n", ch->band);
+ break;
+ }
+}
+
+/*
+ * inff_chan_decchspec: decodes chanspec into generic info
+ */
+void
+inff_chan_decchspec(struct inff_chan *ch)
+{
+ u16 val;
+
+ ch->chnum = (u8)(ch->chspec & INFF_CHSPEC_CH_MASK);
+ ch->control_ch_num = ch->chnum;
+
+ switch (ch->chspec & INFF_CHSPEC_BW_MASK) {
+ case INFF_CHSPEC_BW_20:
+ ch->bw = INFF_CHAN_BW_20;
+ ch->sb = INFF_CHAN_SB_NONE;
+ break;
+ case INFF_CHSPEC_BW_40:
+ ch->bw = INFF_CHAN_BW_40;
+ val = ch->chspec & INFF_CHSPEC_SB_MASK;
+ if (val == INFF_CHSPEC_SB_L) {
+ ch->sb = INFF_CHAN_SB_L;
+ ch->control_ch_num -= CH_10MHZ_APART;
+ } else if (val == INFF_CHSPEC_SB_U) {
+ ch->sb = INFF_CHAN_SB_U;
+ ch->control_ch_num += CH_10MHZ_APART;
+ } else {
+ WARN_ONCE(1, "Invalid chanspec 0x%04x\n", ch->chspec);
+ }
+ break;
+ case INFF_CHSPEC_BW_80:
+ ch->bw = INFF_CHAN_BW_80;
+ ch->sb = inff_maskget16(ch->chspec, INFF_CHSPEC_SB_MASK,
+ INFF_CHSPEC_SB_SHIFT);
+ switch (ch->sb) {
+ case INFF_CHAN_SB_LL:
+ ch->control_ch_num -= CH_30MHZ_APART;
+ break;
+ case INFF_CHAN_SB_LU:
+ ch->control_ch_num -= CH_10MHZ_APART;
+ break;
+ case INFF_CHAN_SB_UL:
+ ch->control_ch_num += CH_10MHZ_APART;
+ break;
+ case INFF_CHAN_SB_UU:
+ ch->control_ch_num += CH_30MHZ_APART;
+ break;
+ default:
+ WARN_ONCE(1, "Invalid chanspec 0x%04x\n", ch->chspec);
+ break;
+ }
+ break;
+ case INFF_CHSPEC_BW_160:
+ ch->bw = INFF_CHAN_BW_160;
+ ch->sb = inff_maskget16(ch->chspec, INFF_CHSPEC_SB_MASK,
+ INFF_CHSPEC_SB_SHIFT);
+ switch (ch->sb) {
+ case INFF_CHAN_SB_LLL:
+ ch->control_ch_num -= CH_70MHZ_APART;
+ break;
+ case INFF_CHAN_SB_LLU:
+ ch->control_ch_num -= CH_50MHZ_APART;
+ break;
+ case INFF_CHAN_SB_LUL:
+ ch->control_ch_num -= CH_30MHZ_APART;
+ break;
+ case INFF_CHAN_SB_LUU:
+ ch->control_ch_num -= CH_10MHZ_APART;
+ break;
+ case INFF_CHAN_SB_ULL:
+ ch->control_ch_num += CH_10MHZ_APART;
+ break;
+ case INFF_CHAN_SB_ULU:
+ ch->control_ch_num += CH_30MHZ_APART;
+ break;
+ case INFF_CHAN_SB_UUL:
+ ch->control_ch_num += CH_50MHZ_APART;
+ break;
+ case INFF_CHAN_SB_UUU:
+ ch->control_ch_num += CH_70MHZ_APART;
+ break;
+ default:
+ WARN_ONCE(1, "Invalid chanspec 0x%04x\n", ch->chspec);
+ break;
+ }
+ break;
+ case INFF_CHSPEC_BW_8080:
+ default:
+ WARN_ONCE(1, "Invalid chanspec 0x%04x\n", ch->chspec);
+ break;
+ }
+
+ switch (ch->chspec & INFF_CHSPEC_BND_MASK) {
+ case INFF_CHSPEC_BND_6G:
+ ch->band = INFF_CHAN_BAND_6G;
+ break;
+ case INFF_CHSPEC_BND_5G:
+ ch->band = INFF_CHAN_BAND_5G;
+ break;
+ case INFF_CHSPEC_BND_2G:
+ ch->band = INFF_CHAN_BAND_2G;
+ break;
+ default:
+ WARN_ONCE(1, "Invalid chanspec 0x%04x\n", ch->chspec);
+ break;
+ }
+}
+
+void
+inff_chan_update_bw40_channel_flag(struct ieee80211_channel *channel,
+ struct inff_chan *ch)
+{
+ u32 ht40_flag;
+
+ ht40_flag = channel->flags & IEEE80211_CHAN_NO_HT40;
+ if (ch->sb == INFF_CHAN_SB_U) {
+ if (ht40_flag == IEEE80211_CHAN_NO_HT40)
+ channel->flags &= ~IEEE80211_CHAN_NO_HT40;
+ channel->flags |= IEEE80211_CHAN_NO_HT40PLUS;
+ } else {
+ /* It should be one of
+ * IEEE80211_CHAN_NO_HT40 or
+ * IEEE80211_CHAN_NO_HT40PLUS
+ */
+ channel->flags &= ~IEEE80211_CHAN_NO_HT40;
+ if (ht40_flag == IEEE80211_CHAN_NO_HT40)
+ channel->flags |= IEEE80211_CHAN_NO_HT40MINUS;
+ }
+}
+
+int
+inff_chan_enable_bw40_2g(struct inff_cfg80211_info *cfg)
+{
+ struct inff_pub *drvr = cfg->pub;
+ struct inff_if *ifp = inff_get_ifp(drvr, 0);
+ struct ieee80211_supported_band *band;
+ struct inff_fwcmd_bwcap_le band_bwcap;
+ struct inff_chanspec_list *list;
+ u8 *pbuf;
+ u32 val;
+ int err;
+ struct inff_chan ch;
+ u32 num_chan;
+ int i, j;
+
+ /* verify support for bw_cap command */
+ val = WLC_BAND_5G;
+ err = inff_fwcmd_iovar_int_get(ifp, "bw_cap", &val);
+
+ if (!err) {
+ /* only set 2G bandwidth using bw_cap command */
+ band_bwcap.band = cpu_to_le32(WLC_BAND_2G);
+ band_bwcap.bw_cap = cpu_to_le32(WLC_BW_CAP_40MHZ);
+ err = inff_fwcmd_iovar_data_set(ifp, "bw_cap", &band_bwcap,
+ sizeof(band_bwcap));
+ } else {
+ inff_dbg(INFO, "fallback to mimo_bw_cap\n");
+ val = WLC_N_BW_40ALL;
+ err = inff_fwcmd_iovar_int_set(ifp, "mimo_bw_cap", val);
+ }
+
+ if (!err) {
+ /* update channel info in 2G band */
+ pbuf = kzalloc(INFF_DCMD_MEDLEN, GFP_KERNEL);
+
+ if (!pbuf)
+ return -ENOMEM;
+
+ ch.band = INFF_CHAN_BAND_2G;
+ ch.bw = INFF_CHAN_BW_40;
+ ch.sb = INFF_CHAN_SB_NONE;
+ ch.chnum = 0;
+ inff_chan_encchspec(&ch);
+
+ /* pass encoded chanspec in query */
+ *(__le16 *)pbuf = cpu_to_le16(ch.chspec);
+
+ err = inff_fwcmd_iovar_data_get(ifp, "chanspecs", pbuf,
+ INFF_DCMD_MEDLEN);
+ if (err) {
+ iphy_err(drvr, "get chanspecs error (%d)\n", err);
+ kfree(pbuf);
+ return err;
+ }
+
+ band = cfg->wiphy->bands[NL80211_BAND_2GHZ];
+ list = (struct inff_chanspec_list *)pbuf;
+ num_chan = le32_to_cpu(list->count);
+ if (num_chan > INFF_MAX_CHANSPEC_LIST) {
+ iphy_err(drvr, "Invalid count of channel Spec. (%u)\n",
+ num_chan);
+ kfree(pbuf);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < num_chan; i++) {
+ ch.chspec = (u16)le32_to_cpu(list->element[i]);
+ inff_chan_decchspec(&ch);
+ if (WARN_ON(ch.band != INFF_CHAN_BAND_2G))
+ continue;
+ if (WARN_ON(ch.bw != INFF_CHAN_BW_40))
+ continue;
+ for (j = 0; j < band->n_channels; j++) {
+ if (band->channels[j].hw_value == ch.control_ch_num)
+ break;
+ }
+ if (WARN_ON(j == band->n_channels))
+ continue;
+
+ inff_chan_update_bw40_channel_flag(&band->channels[j], &ch);
+ }
+ kfree(pbuf);
+ }
+ return err;
+}
+
+u16
+inff_nl80211_chan_to_chanspec(struct ieee80211_channel *ch)
+{
+ struct inff_chan ch_inf = {0};
+
+ switch (ch->band) {
+ case NL80211_BAND_2GHZ:
+ ch_inf.band = INFF_CHAN_BAND_2G;
+ break;
+ case NL80211_BAND_5GHZ:
+ ch_inf.band = INFF_CHAN_BAND_5G;
+ break;
+ case NL80211_BAND_6GHZ:
+ ch_inf.band = INFF_CHAN_BAND_6G;
+ break;
+ case NL80211_BAND_60GHZ:
+ default:
+ WARN_ON_ONCE(1);
+ }
+ ch_inf.chnum = ieee80211_frequency_to_channel(ch->center_freq);
+ ch_inf.bw = INFF_CHAN_BW_20;
+ inff_chan_encchspec(&ch_inf);
+
+ return ch_inf.chspec;
+}
+
+void
+inff_nl80211_chan_get_bwcap(struct inff_if *ifp, u32 bw_cap[])
+{
+ struct inff_pub *drvr = ifp->drvr;
+ u32 band, mimo_bwcap;
+ int err;
+
+ band = WLC_BAND_2G;
+ err = inff_fwcmd_iovar_int_get(ifp, "bw_cap", &band);
+ if (!err) {
+ bw_cap[NL80211_BAND_2GHZ] = band;
+ band = WLC_BAND_5G;
+ err = inff_fwcmd_iovar_int_get(ifp, "bw_cap", &band);
+ if (!err) {
+ bw_cap[NL80211_BAND_5GHZ] = band;
+
+ band = WLC_BAND_6G;
+ err = inff_fwcmd_iovar_int_get(ifp, "bw_cap", &band);
+ if (!err) {
+ bw_cap[NL80211_BAND_6GHZ] = band;
+ return;
+ }
+ return;
+ }
+ WARN_ON(1);
+ return;
+ }
+ inff_dbg(INFO, "fallback to mimo_bw_cap info\n");
+ mimo_bwcap = 0;
+ err = inff_fwcmd_iovar_int_get(ifp, "mimo_bw_cap", &mimo_bwcap);
+ if (err)
+ /* assume 20MHz if firmware does not give a clue */
+ mimo_bwcap = WLC_N_BW_20ALL;
+
+ switch (mimo_bwcap) {
+ case WLC_N_BW_40ALL:
+ bw_cap[NL80211_BAND_2GHZ] |= WLC_BW_40MHZ_BIT;
+ fallthrough;
+ case WLC_N_BW_20IN2G_40IN5G:
+ bw_cap[NL80211_BAND_5GHZ] |= WLC_BW_40MHZ_BIT;
+ fallthrough;
+ case WLC_N_BW_20ALL:
+ bw_cap[NL80211_BAND_2GHZ] |= WLC_BW_20MHZ_BIT;
+ bw_cap[NL80211_BAND_5GHZ] |= WLC_BW_20MHZ_BIT;
+ break;
+ default:
+ iphy_err(drvr, "invalid mimo_bw_cap value\n");
+ }
+}
+
+static void
+inff_wiphy_reset_band_and_channel(struct wiphy *wiphy)
+{
+ enum nl80211_band band;
+ struct ieee80211_supported_band *wiphy_band = NULL;
+
+ for (band = 0; band < NUM_NL80211_BANDS; band++) {
+ wiphy_band = wiphy->bands[band];
+ if (!wiphy_band)
+ continue;
+
+ kfree(wiphy_band->channels);
+ wiphy_band->channels = NULL;
+ kfree(wiphy_band);
+ wiphy->bands[band] = NULL;
+ }
+}
+
+int
+inff_fill_band_with_default_chanlist(struct wiphy *wiphy,
+ struct inff_if *ifp)
+{
+ struct inff_pub *drvr = ifp->drvr;
+ struct ieee80211_supported_band *band;
+ struct inff_cfg80211_info *cfg = drvr->config;
+ int err, i;
+ __le32 bandlist[4];
+ u32 n_bands;
+
+ err = inff_fwcmd_data_get(ifp, INFF_C_GET_BANDLIST, &bandlist,
+ sizeof(bandlist));
+ if (err) {
+ iphy_err(drvr, "could not obtain band info: err=%d\n", err);
+ return err;
+ }
+
+ /* Reset bands if needed */
+ if (cfg->bands_reset_required) {
+ inff_dbg(TRACE, "Performing band reset due to country change\n");
+ inff_wiphy_reset_band_and_channel(wiphy);
+ cfg->bands_reset_required = false;
+ }
+
+ n_bands = le32_to_cpu(bandlist[0]);
+ for (i = 1; i <= n_bands && i < ARRAY_SIZE(bandlist); i++) {
+ if (bandlist[i] == cpu_to_le32(WLC_BAND_2G) &&
+ !wiphy->bands[NL80211_BAND_2GHZ]) {
+ band = kmemdup(&__wl_band_2ghz, sizeof(__wl_band_2ghz),
+ GFP_KERNEL);
+ if (!band)
+ goto mem_err;
+ band->channels = kmemdup(&__wl_2ghz_channels,
+ sizeof(__wl_2ghz_channels),
+ GFP_KERNEL);
+ if (!band->channels)
+ goto mem_err;
+
+ /* restore 2G channels info */
+ band->n_channels = ARRAY_SIZE(__wl_2ghz_channels);
+ wiphy->bands[NL80211_BAND_2GHZ] = band;
+ } else if (bandlist[i] == cpu_to_le32(WLC_BAND_5G) &&
+ !wiphy->bands[NL80211_BAND_5GHZ]) {
+ band = kmemdup(&__wl_band_5ghz, sizeof(__wl_band_5ghz),
+ GFP_KERNEL);
+ if (!band)
+ goto mem_err;
+ band->channels = kmemdup(&__wl_5ghz_channels,
+ sizeof(__wl_5ghz_channels),
+ GFP_KERNEL);
+ if (!band->channels)
+ goto mem_err;
+
+ /* restore 5G channels info */
+ band->n_channels = ARRAY_SIZE(__wl_5ghz_channels);
+ wiphy->bands[NL80211_BAND_5GHZ] = band;
+ } else if (bandlist[i] == cpu_to_le32(WLC_BAND_6G) &&
+ !wiphy->bands[NL80211_BAND_6GHZ]) {
+ band = kmemdup(&__wl_band_6ghz, sizeof(__wl_band_6ghz),
+ GFP_KERNEL);
+ if (!band)
+ goto mem_err;
+ band->channels = kmemdup(&__wl_6ghz_channels,
+ sizeof(__wl_6ghz_channels),
+ GFP_KERNEL);
+ if (!band->channels)
+ goto mem_err;
+
+ /* restore 6G channels info */
+ band->n_channels = ARRAY_SIZE(__wl_6ghz_channels);
+ wiphy->bands[NL80211_BAND_6GHZ] = band;
+ }
+ }
+
+ if (wiphy->bands[NL80211_BAND_5GHZ] &&
+ inff_feat_is_enabled(ifp, INFF_FEAT_DOT11H))
+ wiphy_ext_feature_set(wiphy,
+ NL80211_EXT_FEATURE_DFS_OFFLOAD);
+
+ return 0;
+
+mem_err:
+ inff_wiphy_reset_band_and_channel(wiphy);
+ return -ENOMEM;
+}
+
+static void
+inff_wiphy_rm_disabled_band_and_channel(struct wiphy *wiphy)
+{
+ enum nl80211_band band;
+ struct ieee80211_supported_band *wiphy_band = NULL;
+ struct ieee80211_channel *cur = NULL;
+ struct ieee80211_channel *next = NULL;
+ u32 n_ch = 0;
+ u32 i, j;
+
+ for (band = 0; band < NUM_NL80211_BANDS; band++) {
+ wiphy_band = wiphy->bands[band];
+ if (!wiphy_band)
+ continue;
+
+ n_ch = wiphy_band->n_channels;
+ for (i = 0; i < n_ch;) {
+ cur = &wiphy_band->channels[i];
+ if (cur->flags == IEEE80211_CHAN_DISABLED) {
+ for (j = i; j < n_ch - 1; j++) {
+ cur = &wiphy_band->channels[j];
+ next = &wiphy_band->channels[j + 1];
+ memcpy(cur, next, sizeof(*cur));
+ }
+ n_ch--;
+ } else {
+ i++;
+ }
+ }
+
+ wiphy_band->n_channels = n_ch;
+ if (!n_ch) {
+ kfree(wiphy_band->channels);
+ wiphy_band->channels = NULL;
+ kfree(wiphy_band);
+ wiphy->bands[band] = NULL;
+ }
+ }
+}
+
+int
+inff_nl80211_chan_populate(struct inff_cfg80211_info *cfg,
+ u32 bw_cap[])
+{
+ struct wiphy *wiphy = cfg->wiphy;
+ struct inff_pub *drvr = cfg->pub;
+ struct inff_if *ifp = inff_get_ifp(drvr, 0);
+ struct ieee80211_supported_band *band;
+ struct ieee80211_channel *channel;
+ struct inff_chanspec_list *list;
+ struct inff_chan ch;
+ int err;
+ u8 *pbuf;
+ u32 i, j;
+ u32 total;
+ u32 chaninfo;
+
+ err = inff_fill_band_with_default_chanlist(wiphy, ifp);
+ if (err) {
+ iphy_err(drvr, "could not retrore band and channels: err=%d\n", err);
+ goto fail_pbuf;
+ }
+
+ band = wiphy->bands[NL80211_BAND_2GHZ];
+ if (band) {
+ for (i = 0; i < band->n_channels; i++)
+ band->channels[i].flags = IEEE80211_CHAN_DISABLED;
+ }
+ band = wiphy->bands[NL80211_BAND_5GHZ];
+ if (band) {
+ for (i = 0; i < band->n_channels; i++)
+ band->channels[i].flags = IEEE80211_CHAN_DISABLED;
+ }
+ band = wiphy->bands[NL80211_BAND_6GHZ];
+ if (band) {
+ for (i = 0; i < band->n_channels; i++)
+ band->channels[i].flags = IEEE80211_CHAN_DISABLED;
+ }
+
+ pbuf = kzalloc(INFF_DCMD_MEDLEN, GFP_KERNEL);
+ if (!pbuf)
+ return -ENOMEM;
+
+ err = inff_fwcmd_iovar_data_get(ifp, "chanspecs", pbuf,
+ INFF_DCMD_MEDLEN);
+ if (err) {
+ iphy_err(drvr, "get chanspecs error (%d)\n", err);
+ goto fail_pbuf;
+ }
+
+ list = (struct inff_chanspec_list *)pbuf;
+ total = le32_to_cpu(list->count);
+ if (total > INFF_MAX_CHANSPEC_LIST) {
+ iphy_err(drvr, "Invalid count of channel Spec. (%u)\n",
+ total);
+ err = -EINVAL;
+ goto fail_pbuf;
+ }
+
+ for (i = 0; i < total; i++) {
+ ch.chspec = (u16)le32_to_cpu(list->element[i]);
+ inff_chan_decchspec(&ch);
+
+ if (ch.band == INFF_CHAN_BAND_2G) {
+ band = wiphy->bands[NL80211_BAND_2GHZ];
+ } else if (ch.band == INFF_CHAN_BAND_5G) {
+ band = wiphy->bands[NL80211_BAND_5GHZ];
+ } else if (ch.band == INFF_CHAN_BAND_6G) {
+ band = wiphy->bands[NL80211_BAND_6GHZ];
+ } else {
+ iphy_err(drvr, "Invalid channel Spec. 0x%x.\n",
+ ch.chspec);
+ continue;
+ }
+ if (!band)
+ continue;
+ if (!(bw_cap[band->band] & WLC_BW_40MHZ_BIT) &&
+ ch.bw == INFF_CHAN_BW_40)
+ continue;
+ if (!(bw_cap[band->band] & WLC_BW_80MHZ_BIT) &&
+ ch.bw == INFF_CHAN_BW_80)
+ continue;
+
+ channel = NULL;
+ for (j = 0; j < band->n_channels; j++) {
+ if (band->channels[j].hw_value == ch.control_ch_num) {
+ channel = &band->channels[j];
+ break;
+ }
+ }
+ if (!channel) {
+ /* It seems firmware supports some channel we never
+ * considered. Something new in IEEE standard?
+ */
+ iphy_err(drvr, "Ignoring unexpected firmware channel %d\n",
+ ch.control_ch_num);
+ continue;
+ }
+
+ if (channel->orig_flags & IEEE80211_CHAN_DISABLED)
+ continue;
+
+ /* assuming the chanspecs order is HT20,
+ * HT40 upper, HT40 lower, and VHT80.
+ */
+ switch (ch.bw) {
+ case INFF_CHAN_BW_160:
+ channel->flags &= ~IEEE80211_CHAN_NO_160MHZ;
+ break;
+ case INFF_CHAN_BW_80:
+ channel->flags &= ~IEEE80211_CHAN_NO_80MHZ;
+ break;
+ case INFF_CHAN_BW_40:
+ inff_chan_update_bw40_channel_flag(channel, &ch);
+ break;
+ default:
+ wiphy_warn(wiphy, "Firmware reported unsupported bandwidth %d\n",
+ ch.bw);
+ fallthrough;
+ case INFF_CHAN_BW_20:
+ /* enable the channel and disable other bandwidths
+ * for now as mentioned order assure they are enabled
+ * for subsequent chanspecs.
+ */
+ channel->flags = IEEE80211_CHAN_NO_HT40 |
+ IEEE80211_CHAN_NO_80MHZ |
+ IEEE80211_CHAN_NO_160MHZ;
+ ch.bw = INFF_CHAN_BW_20;
+ inff_chan_encchspec(&ch);
+ chaninfo = ch.chspec;
+ err = inff_fwcmd_bsscfg_int_get(ifp, "per_chan_info",
+ &chaninfo);
+ if (!err) {
+ if (chaninfo & WL_CHAN_RADAR)
+ channel->flags |=
+ (IEEE80211_CHAN_RADAR |
+ IEEE80211_CHAN_NO_IR);
+ if (chaninfo & WL_CHAN_PASSIVE)
+ channel->flags |=
+ IEEE80211_CHAN_NO_IR;
+ }
+ }
+ }
+
+ /* Remove disabled channels and band to avoid unexpected restore. */
+ inff_wiphy_rm_disabled_band_and_channel(wiphy);
+
+fail_pbuf:
+ kfree(pbuf);
+ return err;
+}
+
diff --git a/drivers/net/wireless/infineon/inffmac/chan.h b/drivers/net/wireless/infineon/inffmac/chan.h
new file mode 100644
index 000000000000..47ba1879bf21
--- /dev/null
+++ b/drivers/net/wireless/infineon/inffmac/chan.h
@@ -0,0 +1,365 @@
+/* SPDX-License-Identifier: ISC */
+/*
+ * Copyright (c) 2010 Broadcom Corporation
+ *
+ * Copyright (c) 2025-2026, Infineon Technologies AG, or an affiliate of Infineon Technologies AG.
+ * All rights reserved.
+ */
+
+#ifndef INFF_CHAN_H
+#define INFF_CHAN_H
+
+#include <linux/if_ether.h>
+#include <linux/ieee80211.h>
+#include <linux/nl80211.h>
+
+/*
+ * A chanspec (u16) holds the channel number, band, bandwidth and control
+ * sideband
+ */
+
+/* channel defines */
+#define CH_UPPER_SB 0x01
+#define CH_LOWER_SB 0x02
+#define CH_EWA_VALID 0x04
+#define CH_70MHZ_APART 14
+#define CH_50MHZ_APART 10
+#define CH_30MHZ_APART 6
+#define CH_20MHZ_APART 4
+#define CH_10MHZ_APART 2
+#define CH_5MHZ_APART 1 /* 2G band channels are 5 Mhz apart */
+#define CH_MIN_2G_CHANNEL 1
+#define CH_MAX_2G_CHANNEL 14 /* Max channel in 2G band */
+#define CH_MIN_5G_CHANNEL 34
+
+/* bandstate array indices */
+#define BAND_2G_INDEX 0 /* wlc->bandstate[x] index */
+#define BAND_5G_INDEX 1 /* wlc->bandstate[x] index */
+
+/*
+ * max # supported channels. The max channel no is 216, this is that + 1
+ * rounded up to a multiple of NBBY (8). DO NOT MAKE it > 255: channels are
+ * u8's all over
+ */
+#define MAXCHANNEL 224
+
+#define WL_CHANSPEC_CHAN_MASK 0x00ff
+#define WL_CHANSPEC_CHAN_SHIFT 0
+
+#define WL_CHANSPEC_CTL_SB_MASK 0x0300
+#define WL_CHANSPEC_CTL_SB_SHIFT 8
+#define WL_CHANSPEC_CTL_SB_LOWER 0x0100
+#define WL_CHANSPEC_CTL_SB_UPPER 0x0200
+#define WL_CHANSPEC_CTL_SB_NONE 0x0300
+
+#define WL_CHANSPEC_BW_MASK 0x0C00
+#define WL_CHANSPEC_BW_SHIFT 10
+#define WL_CHANSPEC_BW_10 0x0400
+#define WL_CHANSPEC_BW_20 0x0800
+#define WL_CHANSPEC_BW_40 0x0C00
+#define WL_CHANSPEC_BW_80 0x2000
+
+#define WL_CHANSPEC_BAND_MASK 0xf000
+#define WL_CHANSPEC_BAND_SHIFT 12
+#define WL_CHANSPEC_BAND_5G 0x1000
+#define WL_CHANSPEC_BAND_2G 0x2000
+#define INVCHANSPEC 255
+
+#define WL_CHAN_VALID_HW BIT(0) /* valid with current HW */
+#define WL_CHAN_VALID_SW BIT(1) /* valid with country sett. */
+#define WL_CHAN_BAND_5G BIT(2) /* 5GHz-band channel */
+#define WL_CHAN_RADAR BIT(3) /* radar sensitive channel */
+#define WL_CHAN_INACTIVE BIT(4) /* inactive due to radar */
+#define WL_CHAN_PASSIVE BIT(5) /* channel in passive mode */
+#define WL_CHAN_RESTRICTED BIT(6) /* restricted use channel */
+
+/* values for band specific 40MHz capabilities */
+#define WLC_N_BW_20ALL 0
+#define WLC_N_BW_40ALL 1
+#define WLC_N_BW_20IN2G_40IN5G 2
+
+#define WLC_BW_20MHZ_BIT BIT(0)
+#define WLC_BW_40MHZ_BIT BIT(1)
+#define WLC_BW_80MHZ_BIT BIT(2)
+#define WLC_BW_160MHZ_BIT BIT(3)
+
+/* Bandwidth capabilities */
+#define WLC_BW_CAP_20MHZ (WLC_BW_20MHZ_BIT)
+#define WLC_BW_CAP_40MHZ (WLC_BW_40MHZ_BIT | WLC_BW_20MHZ_BIT)
+#define WLC_BW_CAP_80MHZ (WLC_BW_80MHZ_BIT | WLC_BW_40MHZ_BIT | \
+ WLC_BW_20MHZ_BIT)
+#define WLC_BW_CAP_160MHZ (WLC_BW_160MHZ_BIT | WLC_BW_80MHZ_BIT | \
+ WLC_BW_40MHZ_BIT | WLC_BW_20MHZ_BIT)
+#define WLC_BW_CAP_UNRESTRICTED 0xFF
+
+/* band types */
+#define WLC_BAND_AUTO 0 /* auto-select */
+#define WLC_BAND_5G 1 /* 5 Ghz */
+#define WLC_BAND_2G 2 /* 2.4 Ghz */
+#define WLC_BAND_6G 3 /* 6 Ghz */
+#define WLC_BAND_ALL 4 /* all bands */
+
+#define CHSPEC_CHANNEL(chspec) ((u8)((chspec) & WL_CHANSPEC_CHAN_MASK))
+#define CHSPEC_BAND(chspec) ((chspec) & WL_CHANSPEC_BAND_MASK)
+
+#define CHSPEC_CTL_SB(chspec) ((chspec) & WL_CHANSPEC_CTL_SB_MASK)
+#define CHSPEC_BW(chspec) ((chspec) & WL_CHANSPEC_BW_MASK)
+
+#define CHSPEC_IS10(chspec) \
+ (((chspec) & WL_CHANSPEC_BW_MASK) == WL_CHANSPEC_BW_10)
+
+#define CHSPEC_IS20(chspec) \
+ (((chspec) & WL_CHANSPEC_BW_MASK) == WL_CHANSPEC_BW_20)
+
+#define CHSPEC_IS40(chspec) \
+ (((chspec) & WL_CHANSPEC_BW_MASK) == WL_CHANSPEC_BW_40)
+
+#define CHSPEC_IS80(chspec) \
+ (((chspec) & WL_CHANSPEC_BW_MASK) == WL_CHANSPEC_BW_80)
+
+#define CHSPEC_IS5G(chspec) \
+ (((chspec) & WL_CHANSPEC_BAND_MASK) == WL_CHANSPEC_BAND_5G)
+
+#define CHSPEC_IS2G(chspec) \
+ (((chspec) & WL_CHANSPEC_BAND_MASK) == WL_CHANSPEC_BAND_2G)
+
+#define CHSPEC_SB_NONE(chspec) \
+ (((chspec) & WL_CHANSPEC_CTL_SB_MASK) == WL_CHANSPEC_CTL_SB_NONE)
+
+#define CHSPEC_SB_UPPER(chspec) \
+ (((chspec) & WL_CHANSPEC_CTL_SB_MASK) == WL_CHANSPEC_CTL_SB_UPPER)
+
+#define CHSPEC_SB_LOWER(chspec) \
+ (((chspec) & WL_CHANSPEC_CTL_SB_MASK) == WL_CHANSPEC_CTL_SB_LOWER)
+
+#define CHSPEC_CTL_CHAN(chspec) {\
+ typeof(i) _chspec = (chspec); \
+ ((CHSPEC_SB_LOWER(_chspec)) ? \
+ (lower_20_sb((_chspec & WL_CHANSPEC_CHAN_MASK))) : \
+ (upper_20_sb((_chspec & WL_CHANSPEC_CHAN_MASK)))); \
+ }
+
+/* band types */
+#define INF_BAND_AUTO 0 /* auto-select */
+#define INF_BAND_5G 1 /* 5 Ghz */
+#define INF_BAND_2G 2 /* 2.4 Ghz */
+#define INF_BAND_ALL 3 /* all bands */
+
+#define CHSPEC2BAND(chspec) (CHSPEC_IS5G(chspec) ? INF_BAND_5G : INF_BAND_2G)
+
+#define CHANSPEC_STR_LEN 8
+
+/*
+ * Enumeration of 802.11 radio bands
+ */
+enum cp_channel_band {
+ INFF_CP_WIFI_BAND_ANY = 0, /**< Platform will choose an available band. */
+ INFF_CP_WIFI_BAND_5GHZ, /**< 5-GHz radio band. */
+ INFF_CP_WIFI_BAND_2_4GHZ, /**< 2.4-GHz radio band. */
+ INFF_CP_WIFI_BAND_6GHZ, /**< 6-GHz radio band. */
+};
+
+static inline enum nl80211_band inff_cp_chan_band_to_nl80211(int band)
+{
+ switch (band) {
+ case INFF_CP_WIFI_BAND_2_4GHZ:
+ return NL80211_BAND_2GHZ;
+ case INFF_CP_WIFI_BAND_5GHZ:
+ return NL80211_BAND_5GHZ;
+ default:
+ return NL80211_BAND_6GHZ;
+ }
+}
+
+static inline enum cp_channel_band inff_cp_nl80211_to_chan_band(int band)
+{
+ switch (band) {
+ case NL80211_BAND_2GHZ:
+ return INFF_CP_WIFI_BAND_2_4GHZ;
+ case NL80211_BAND_5GHZ:
+ return INFF_CP_WIFI_BAND_5GHZ;
+ case NL80211_BAND_6GHZ:
+ return INFF_CP_WIFI_BAND_6GHZ;
+ default:
+ return INFF_CP_WIFI_BAND_ANY;
+ }
+}
+
+static inline int lower_20_sb(int channel)
+{
+ return channel > CH_10MHZ_APART ? (channel - CH_10MHZ_APART) : 0;
+}
+
+static inline int upper_20_sb(int channel)
+{
+ return (channel < (MAXCHANNEL - CH_10MHZ_APART)) ?
+ channel + CH_10MHZ_APART : 0;
+}
+
+static inline int chspec_bandunit(u16 chspec)
+{
+ return CHSPEC_IS5G(chspec) ? BAND_5G_INDEX : BAND_2G_INDEX;
+}
+
+static inline u16 ch20mhz_chspec(int channel)
+{
+ u16 rc = channel <= CH_MAX_2G_CHANNEL ?
+ WL_CHANSPEC_BAND_2G : WL_CHANSPEC_BAND_5G;
+
+ return (u16)((u16)channel | WL_CHANSPEC_BW_20 |
+ WL_CHANSPEC_CTL_SB_NONE | rc);
+}
+
+static inline int next_20mhz_chan(int channel)
+{
+ return channel < (MAXCHANNEL - CH_20MHZ_APART) ?
+ channel + CH_20MHZ_APART : 0;
+}
+
+/* defined rate in 500kbps */
+#define INF_MAXRATE 108 /* in 500kbps units */
+#define INF_RATE_1M 2 /* in 500kbps units */
+#define INF_RATE_2M 4 /* in 500kbps units */
+#define INF_RATE_5M5 11 /* in 500kbps units */
+#define INF_RATE_11M 22 /* in 500kbps units */
+#define INF_RATE_6M 12 /* in 500kbps units */
+#define INF_RATE_9M 18 /* in 500kbps units */
+#define INF_RATE_12M 24 /* in 500kbps units */
+#define INF_RATE_18M 36 /* in 500kbps units */
+#define INF_RATE_24M 48 /* in 500kbps units */
+#define INF_RATE_36M 72 /* in 500kbps units */
+#define INF_RATE_48M 96 /* in 500kbps units */
+#define INF_RATE_54M 108 /* in 500kbps units */
+
+#define INF_2G_25MHZ_OFFSET 5 /* 2.4GHz band channel offset */
+
+#define MCSSET_LEN 16
+
+static inline bool ac_bitmap_tst(u8 bitmap, int prec)
+{
+ return (bitmap & (1 << (prec))) != 0;
+}
+
+/* A chanspec (channel specification) holds the channel number, band,
+ * bandwidth and control sideband
+ */
+
+/* chanspec binary format */
+
+#define INFF_CHSPEC_INVALID 255
+/* bit 0~7 channel number
+ * for 80+80 channels: bit 0~3 low channel id, bit 4~7 high channel id
+ */
+#define INFF_CHSPEC_CH_MASK 0x00ff
+#define INFF_CHSPEC_CH_SHIFT 0
+#define INFF_CHSPEC_CHL_MASK 0x000f
+#define INFF_CHSPEC_CHL_SHIFT 0
+#define INFF_CHSPEC_CHH_MASK 0x00f0
+#define INFF_CHSPEC_CHH_SHIFT 4
+
+/* bit 8~16 for Chanspec
+ * bit 8~10 sideband
+ * bit 11~13 bandwidth
+ * bit 14~15 spectral band
+ */
+#define INFF_CHSPEC_SB_MASK 0x0700
+#define INFF_CHSPEC_SB_SHIFT 8
+#define INFF_CHSPEC_SB_LLL 0x0000
+#define INFF_CHSPEC_SB_LLU 0x0100
+#define INFF_CHSPEC_SB_LUL 0x0200
+#define INFF_CHSPEC_SB_LUU 0x0300
+#define INFF_CHSPEC_SB_ULL 0x0400
+#define INFF_CHSPEC_SB_ULU 0x0500
+#define INFF_CHSPEC_SB_UUL 0x0600
+#define INFF_CHSPEC_SB_UUU 0x0700
+#define INFF_CHSPEC_SB_LL INFF_CHSPEC_SB_LLL
+#define INFF_CHSPEC_SB_LU INFF_CHSPEC_SB_LLU
+#define INFF_CHSPEC_SB_UL INFF_CHSPEC_SB_LUL
+#define INFF_CHSPEC_SB_UU INFF_CHSPEC_SB_LUU
+#define INFF_CHSPEC_SB_L INFF_CHSPEC_SB_LLL
+#define INFF_CHSPEC_SB_U INFF_CHSPEC_SB_LLU
+#define INFF_CHSPEC_BW_MASK 0x3800
+#define INFF_CHSPEC_BW_SHIFT 11
+#define INFF_CHSPEC_BW_5 0x0000
+#define INFF_CHSPEC_BW_10 0x0800
+#define INFF_CHSPEC_BW_20 0x1000
+#define INFF_CHSPEC_BW_40 0x1800
+#define INFF_CHSPEC_BW_80 0x2000
+#define INFF_CHSPEC_BW_160 0x2800
+#define INFF_CHSPEC_BW_8080 0x3000
+#define INFF_CHSPEC_BND_MASK 0xc000
+#define INFF_CHSPEC_BND_SHIFT 14
+#define INFF_CHSPEC_BND_2G 0x0000
+#define INFF_CHSPEC_BND_3G 0x4000
+#define INFF_CHSPEC_BND_6G 0x8000
+#define INFF_CHSPEC_BND_5G 0xc000
+#define INFF_CHSPEC_IS5G(chspec) \
+ (((chspec) & INFF_CHSPEC_BND_MASK) == INFF_CHSPEC_BND_5G)
+#define INFF_CHSPEC_IS6G(chspec) \
+ (((chspec) & INFF_CHSPEC_BND_MASK) == INFF_CHSPEC_BND_6G)
+#define INFF_CHAN_BAND_2G 1
+#define INFF_CHAN_BAND_5G 2
+#define INFF_CHAN_BAND_6G 3
+
+enum inff_chan_bw {
+ INFF_CHAN_BW_20,
+ INFF_CHAN_BW_40,
+ INFF_CHAN_BW_80,
+ INFF_CHAN_BW_80P80,
+ INFF_CHAN_BW_160,
+};
+
+enum inff_chan_sb {
+ INFF_CHAN_SB_NONE = -1,
+ INFF_CHAN_SB_LLL,
+ INFF_CHAN_SB_LLU,
+ INFF_CHAN_SB_LUL,
+ INFF_CHAN_SB_LUU,
+ INFF_CHAN_SB_ULL,
+ INFF_CHAN_SB_ULU,
+ INFF_CHAN_SB_UUL,
+ INFF_CHAN_SB_UUU,
+ INFF_CHAN_SB_L = INFF_CHAN_SB_LLL,
+ INFF_CHAN_SB_U = INFF_CHAN_SB_LLU,
+ INFF_CHAN_SB_LL = INFF_CHAN_SB_LLL,
+ INFF_CHAN_SB_LU = INFF_CHAN_SB_LLU,
+ INFF_CHAN_SB_UL = INFF_CHAN_SB_LUL,
+ INFF_CHAN_SB_UU = INFF_CHAN_SB_LUU,
+};
+
+/**
+ * struct inff_chan - stores channel formats
+ *
+ * This structure can be used with functions translating chanspec into generic
+ * channel info and the other way.
+ *
+ * @chspec: firmware specific format
+ * @chnum: center channel number
+ * @control_ch_num: control channel number
+ * @band: frequency band
+ * @bw: channel width
+ * @sb: control sideband (location of control channel against the center one)
+ */
+struct inff_chan {
+ u16 chspec;
+ u8 chnum;
+ u8 control_ch_num;
+ u8 band;
+ enum inff_chan_bw bw;
+ enum inff_chan_sb sb;
+};
+
+enum nl80211_band inff_chan_band_to_nl80211(u8 band);
+void inff_chan_encchspec(struct inff_chan *ch);
+void inff_chan_decchspec(struct inff_chan *ch);
+void inff_chan_update_bw40_channel_flag(struct ieee80211_channel *channel,
+ struct inff_chan *ch);
+int inff_chan_enable_bw40_2g(struct inff_cfg80211_info *cfg);
+u16 inff_nl80211_chan_to_chanspec(struct ieee80211_channel *ch);
+void inff_nl80211_chan_get_bwcap(struct inff_if *ifp, u32 bw_cap[]);
+int inff_fill_band_with_default_chanlist(struct wiphy *wiphy,
+ struct inff_if *ifp);
+int inff_nl80211_chan_populate(struct inff_cfg80211_info *cfg,
+ u32 bw_cap[]);
+
+#endif /* INFF_CHAN_H */
--
2.43.0
^ permalink raw reply related [flat|nested] 26+ messages in thread* [PATCH wireless-next v3 23/25] wifi: inffmac: add debug.c/h
2026-09-02 16:50 [PATCH wireless-next v3 00/25] wifi: inffmac: introducing a driver for Infineon's new generation chipsets Gokul Sivakumar
` (21 preceding siblings ...)
2026-09-02 16:50 ` [PATCH wireless-next v3 22/25] wifi: inffmac: add chan.c/h Gokul Sivakumar
@ 2026-09-02 16:50 ` Gokul Sivakumar
2026-09-02 16:50 ` [PATCH wireless-next v3 24/25] wifi: inffmac: add utils.c/h Gokul Sivakumar
2026-09-02 16:50 ` [PATCH wireless-next v3 25/25] wifi: inffmac: add Kconfig, Makefile Gokul Sivakumar
24 siblings, 0 replies; 26+ messages in thread
From: Gokul Sivakumar @ 2026-09-02 16:50 UTC (permalink / raw)
To: linux-wireless, johannes
Cc: arend.vanspriel, wlan-kernel-dev-list, gokulkumar.sivakumar
Implements the driver debugging infrastructure for sending debug prints to
the user (based on the configured debug level) with the help of helper
functions. These functions are utilized by source files in the driver.
Signed-off-by: Gokul Sivakumar <gokulkumar.sivakumar@infineon.com>
---
drivers/net/wireless/infineon/inffmac/debug.c | 59 +++++++
drivers/net/wireless/infineon/inffmac/debug.h | 161 ++++++++++++++++++
2 files changed, 220 insertions(+)
create mode 100644 drivers/net/wireless/infineon/inffmac/debug.c
create mode 100644 drivers/net/wireless/infineon/inffmac/debug.h
diff --git a/drivers/net/wireless/infineon/inffmac/debug.c b/drivers/net/wireless/infineon/inffmac/debug.c
new file mode 100644
index 000000000000..2c199beb014c
--- /dev/null
+++ b/drivers/net/wireless/infineon/inffmac/debug.c
@@ -0,0 +1,59 @@
+// SPDX-License-Identifier: ISC
+/*
+ * Copyright (c) 2012 Broadcom Corporation
+ *
+ * Copyright (c) 2025-2026, Infineon Technologies AG, or an affiliate of Infineon Technologies AG.
+ * All rights reserved.
+ */
+#include <linux/debugfs.h>
+#include <linux/netdevice.h>
+#include <linux/module.h>
+
+#include "main.h"
+#include "bus_proto.h"
+#include "debug.h"
+
+void __inff_dbg(u32 level, const char *func, const char *fmt, ...)
+{
+ struct va_format vaf = {
+ .fmt = fmt,
+ };
+ va_list args;
+
+ va_start(args, fmt);
+ vaf.va = &args;
+ if (inff_msg_level & level)
+ pr_debug("%s %pV", func, &vaf);
+ va_end(args);
+}
+
+void __inff_dbg_hex_dump(const void *data, size_t size,
+ const char *fmt, ...)
+{
+ struct va_format vaf;
+ va_list args;
+
+ va_start(args, fmt);
+
+ vaf.fmt = fmt;
+ vaf.va = &args;
+
+ pr_debug("%pV", &vaf);
+
+ va_end(args);
+
+ print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, data, size);
+}
+
+struct dentry *inff_debugfs_get_devdir(struct inff_pub *drvr)
+{
+ return drvr->wiphy->debugfsdir;
+}
+
+void inff_debugfs_add_entry(struct inff_pub *drvr, const char *fn,
+ int (*read_fn)(struct seq_file *seq, void *data))
+{
+ WARN(!drvr->wiphy->debugfsdir, "wiphy not (yet) registered\n");
+ debugfs_create_devm_seqfile(drvr->bus_if->dev, fn,
+ drvr->wiphy->debugfsdir, read_fn);
+}
diff --git a/drivers/net/wireless/infineon/inffmac/debug.h b/drivers/net/wireless/infineon/inffmac/debug.h
new file mode 100644
index 000000000000..3e4ca31fd8be
--- /dev/null
+++ b/drivers/net/wireless/infineon/inffmac/debug.h
@@ -0,0 +1,161 @@
+/* SPDX-License-Identifier: ISC */
+/*
+ * Copyright (c) 2010 Broadcom Corporation
+ *
+ * Copyright (c) 2025-2026, Infineon Technologies AG, or an affiliate of Infineon Technologies AG.
+ * All rights reserved.
+ */
+
+#ifndef INFF_DEBUG_H
+#define INFF_DEBUG_H
+
+#include <linux/net.h> /* net_ratelimit() */
+#include <linux/device.h>
+
+/* message levels */
+enum inff_msglvl {
+ INFF_MSGLVL_TRACE = BIT(1),
+ INFF_MSGLVL_INFO = BIT(2),
+ INFF_MSGLVL_DATA = BIT(3),
+ INFF_MSGLVL_CTL = BIT(4),
+ INFF_MSGLVL_TIMER = BIT(5),
+ INFF_MSGLVL_HDRS = BIT(6),
+ INFF_MSGLVL_BYTES = BIT(7),
+ INFF_MSGLVL_INTR = BIT(8),
+ INFF_MSGLVL_GLOM = BIT(9),
+ INFF_MSGLVL_DEVEVT = BIT(10),
+ INFF_MSGLVL_BTA = BIT(11),
+ INFF_MSGLVL_DEVCMD = BIT(12),
+ INFF_MSGLVL_USB = BIT(13),
+ INFF_MSGLVL_SCAN = BIT(14),
+ INFF_MSGLVL_CONN = BIT(15),
+ INFF_MSGLVL_BCDC = BIT(16),
+ INFF_MSGLVL_SDIO = BIT(17),
+ INFF_MSGLVL_RSV18 = BIT(18),
+ INFF_MSGLVL_RSV19 = BIT(19),
+ INFF_MSGLVL_FWCON = BIT(20),
+ INFF_MSGLVL_RSV21 = BIT(21),
+ INFF_MSGLVL_RSV22 = BIT(22),
+ INFF_MSGLVL_SDIOEXT = BIT(24),
+ INFF_MSGLVL_ICDC = BIT(25),
+ INFF_MSGLVL_CPUSS_ON = BIT(26),
+};
+
+#ifdef DEBUG
+#define INFF_MSGLVL_ON(level) (inff_msg_level & INFF_MSGLVL_##level)
+#else /* DEBUG */
+#define INFF_MSGLVL_ON(LVL) 0
+#endif /* DEBUG */
+
+/* set default print format */
+#undef pr_fmt
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+struct inff_bus;
+
+__printf(3, 4)
+void __inff_err(struct device *dev, const char *func, const char *fmt, ...);
+/* Macro for error messages. When debugging / tracing the driver all error
+ * messages are important to us.
+ */
+#define inff_err(fmt, ...) \
+ do { \
+ if (IS_ENABLED(CONFIG_INF_DEBUG) || \
+ net_ratelimit()) \
+ __inff_err(NULL, __func__, fmt, ##__VA_ARGS__); \
+ } while (0)
+
+#define iphy_err(drvr, fmt, ...) \
+ do { \
+ if (IS_ENABLED(CONFIG_INF_DEBUG) || \
+ net_ratelimit()) \
+ wiphy_err((drvr)->wiphy, "%s: " fmt, __func__, \
+ ##__VA_ARGS__); \
+ } while (0)
+
+#define iphy_info_once(drvr, fmt, ...) \
+ wiphy_info_once((drvr)->wiphy, "%s: " fmt, __func__, \
+ ##__VA_ARGS__)
+
+#ifdef DEBUG
+
+/* For debug purposes treat info messages as errors */
+#define inff_info inff_err
+
+__printf(3, 4)
+void __inff_dbg(u32 level, const char *func, const char *fmt, ...);
+#define inff_dbg(level, fmt, ...) \
+do { \
+ if (IS_ENABLED(CONFIG_INF_DEBUG)) \
+ __inff_dbg(INFF_MSGLVL_##level, __func__, \
+ fmt, ##__VA_ARGS__); \
+} while (0)
+
+__printf(3, 4)
+void __inff_dbg_hex_dump(const void *data, size_t size,
+ const char *fmt, ...);
+#define inff_dbg_hex_dump(test, data, len, fmt, ...) \
+do { \
+ if (test) \
+ __inff_dbg_hex_dump(data, len, fmt, ##__VA_ARGS__); \
+} while (0)
+
+#else /* DEBUG */
+
+#define inff_info(fmt, ...) \
+ pr_info("%s: " fmt, __func__, ##__VA_ARGS__)
+
+#define inff_dbg(level, fmt, ...) \
+ do { \
+ (void)(INFF_MSGLVL_ ## level); \
+ no_printk(fmt, ##__VA_ARGS__); \
+ } while (0)
+
+#define inff_dbg_hex_dump(test, data, len, fmt, ...) \
+ do { \
+ (void)(test); \
+ (void)(data); \
+ (void)(len); \
+ no_printk(fmt, ##__VA_ARGS__); \
+ } while (0)
+
+#endif /* DEBUG */
+
+extern int inff_msg_level;
+struct inff_pub;
+
+#ifdef DEBUG
+struct dentry *inff_debugfs_get_devdir(struct inff_pub *drvr);
+void inff_debugfs_add_entry(struct inff_pub *drvr, const char *fn,
+ int (*read_fn)(struct seq_file *seq, void *data));
+#else
+static inline struct dentry *inff_debugfs_get_devdir(struct inff_pub *drvr)
+{
+ return ERR_PTR(-ENOENT);
+}
+
+static inline
+void inff_debugfs_add_entry(struct inff_pub *drvr, const char *fn,
+ int (*read_fn)(struct seq_file *seq, void *data))
+{ }
+#endif /* DEBUG */
+
+#define MSGTRACE_VERSION 1
+#define MSGTRACE_HDR_TYPE_MSG 0
+#define MSGTRACE_HDR_TYPE_LOG 1
+
+/* Message trace header */
+struct msgtrace_hdr {
+ u8 version;
+ u8 trace_type;
+ u16 len; /* Len of the trace */
+ u32 seqnum; /* Sequence number of message */
+ /* Number of discarded bytes because of trace overflow */
+ u32 discarded_bytes;
+ /* Number of discarded printf because of trace overflow */
+ u32 discarded_printf;
+};
+
+#define MSGTRACE_HDRLEN sizeof(struct msgtrace_hdr)
+
+#endif /* INFF_DEBUG_H */
--
2.43.0
^ permalink raw reply related [flat|nested] 26+ messages in thread* [PATCH wireless-next v3 24/25] wifi: inffmac: add utils.c/h
2026-09-02 16:50 [PATCH wireless-next v3 00/25] wifi: inffmac: introducing a driver for Infineon's new generation chipsets Gokul Sivakumar
` (22 preceding siblings ...)
2026-09-02 16:50 ` [PATCH wireless-next v3 23/25] wifi: inffmac: add debug.c/h Gokul Sivakumar
@ 2026-09-02 16:50 ` Gokul Sivakumar
2026-09-02 16:50 ` [PATCH wireless-next v3 25/25] wifi: inffmac: add Kconfig, Makefile Gokul Sivakumar
24 siblings, 0 replies; 26+ messages in thread
From: Gokul Sivakumar @ 2026-09-02 16:50 UTC (permalink / raw)
To: linux-wireless, johannes
Cc: arend.vanspriel, wlan-kernel-dev-list, gokulkumar.sivakumar
Imeplemenation of common utility functions and MACRO definitions which can
be used by other driver source files.
Signed-off-by: Gokul Sivakumar <gokulkumar.sivakumar@infineon.com>
---
drivers/net/wireless/infineon/inffmac/utils.c | 291 ++++++++++++++++++
drivers/net/wireless/infineon/inffmac/utils.h | 233 ++++++++++++++
2 files changed, 524 insertions(+)
create mode 100644 drivers/net/wireless/infineon/inffmac/utils.c
create mode 100644 drivers/net/wireless/infineon/inffmac/utils.h
diff --git a/drivers/net/wireless/infineon/inffmac/utils.c b/drivers/net/wireless/infineon/inffmac/utils.c
new file mode 100644
index 000000000000..5d6468808d79
--- /dev/null
+++ b/drivers/net/wireless/infineon/inffmac/utils.c
@@ -0,0 +1,291 @@
+// SPDX-License-Identifier: ISC
+
+/*
+ * Copyright (c) 2010 Broadcom Corporation
+ *
+ * Copyright (c) 2025-2026, Infineon Technologies AG, or an affiliate of Infineon Technologies AG.
+ * All rights reserved.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/netdevice.h>
+#include <linux/module.h>
+
+#include "utils.h"
+
+struct sk_buff *inff_pkt_buf_get_skb(uint len)
+{
+ struct sk_buff *skb;
+
+ skb = dev_alloc_skb(len);
+ if (skb) {
+ skb_put(skb, len);
+ skb->priority = 0;
+ }
+
+ return skb;
+}
+
+/* Free the driver packet. Free the tag if present */
+void inff_pkt_buf_free_skb(struct sk_buff *skb)
+{
+ if (!skb)
+ return;
+
+ WARN_ON(skb->next);
+ dev_kfree_skb_any(skb);
+}
+
+/*
+ * osl multiple-precedence packet queue
+ * hi_prec is always >= the number of the highest non-empty precedence
+ */
+struct sk_buff *inff_pktq_penq(struct pktq *pq, int prec,
+ struct sk_buff *p)
+{
+ struct sk_buff_head *q;
+
+ if (pktq_full(pq) || pktq_pfull(pq, prec))
+ return NULL;
+
+ q = &pq->q[prec].skblist;
+ skb_queue_tail(q, p);
+ pq->len++;
+
+ if (pq->hi_prec < prec)
+ pq->hi_prec = (u8)prec;
+
+ return p;
+}
+
+struct sk_buff *inff_pktq_penq_head(struct pktq *pq, int prec,
+ struct sk_buff *p)
+{
+ struct sk_buff_head *q;
+
+ if (pktq_full(pq) || pktq_pfull(pq, prec))
+ return NULL;
+
+ q = &pq->q[prec].skblist;
+ skb_queue_head(q, p);
+ pq->len++;
+
+ if (pq->hi_prec < prec)
+ pq->hi_prec = (u8)prec;
+
+ return p;
+}
+
+struct sk_buff *inff_pktq_pdeq(struct pktq *pq, int prec)
+{
+ struct sk_buff_head *q;
+ struct sk_buff *p;
+
+ q = &pq->q[prec].skblist;
+ p = skb_dequeue(q);
+ if (!p)
+ return NULL;
+
+ pq->len--;
+ return p;
+}
+
+/*
+ * precedence based dequeue with match function. Passing a NULL pointer
+ * for the match function parameter is considered to be a wildcard so
+ * any packet on the queue is returned. In that case it is no different
+ * from inff_pktq_pdeq() above.
+ */
+struct sk_buff *inff_pktq_pdeq_match(struct pktq *pq, int prec,
+ bool (*match_fn)(struct sk_buff *skb,
+ void *arg), void *arg)
+{
+ struct sk_buff_head *q;
+ struct sk_buff *p, *next;
+
+ q = &pq->q[prec].skblist;
+ skb_queue_walk_safe(q, p, next) {
+ if (!match_fn || match_fn(p, arg)) {
+ skb_unlink(p, q);
+ pq->len--;
+ return p;
+ }
+ }
+ return NULL;
+}
+
+struct sk_buff *inff_pktq_pdeq_tail(struct pktq *pq, int prec)
+{
+ struct sk_buff_head *q;
+ struct sk_buff *p;
+
+ q = &pq->q[prec].skblist;
+ p = skb_dequeue_tail(q);
+ if (!p)
+ return NULL;
+
+ pq->len--;
+ return p;
+}
+
+void
+inff_pktq_pflush(struct pktq *pq, int prec, bool dir,
+ bool (*fn)(struct sk_buff *, void *), void *arg)
+{
+ struct sk_buff_head *q;
+ struct sk_buff *p, *next;
+
+ q = &pq->q[prec].skblist;
+ skb_queue_walk_safe(q, p, next) {
+ if (!fn || (*fn) (p, arg)) {
+ skb_unlink(p, q);
+ inff_pkt_buf_free_skb(p);
+ pq->len--;
+ }
+ }
+}
+
+void inff_pktq_flush(struct pktq *pq, bool dir,
+ bool (*fn)(struct sk_buff *, void *), void *arg)
+{
+ int prec;
+
+ for (prec = 0; prec < pq->num_prec; prec++)
+ inff_pktq_pflush(pq, prec, dir, fn, arg);
+}
+
+void inff_pktq_init(struct pktq *pq, int num_prec, int max_len)
+{
+ int prec;
+
+ /* pq is variable size; only zero out what's requested */
+ memset(pq, 0,
+ offsetof(struct pktq, q) + (sizeof(struct pktq_prec) * num_prec));
+
+ pq->num_prec = (u16)num_prec;
+
+ pq->max = (u16)max_len;
+
+ for (prec = 0; prec < num_prec; prec++) {
+ pq->q[prec].max = pq->max;
+ skb_queue_head_init(&pq->q[prec].skblist);
+ }
+}
+
+struct sk_buff *inff_pktq_peek_tail(struct pktq *pq, int *prec_out)
+{
+ int prec;
+
+ if (pktq_empty(pq))
+ return NULL;
+
+ for (prec = 0; prec < pq->hi_prec; prec++)
+ if (!skb_queue_empty(&pq->q[prec].skblist))
+ break;
+
+ if (prec_out)
+ *prec_out = prec;
+
+ return skb_peek_tail(&pq->q[prec].skblist);
+}
+
+/* Return sum of lengths of a specific set of precedences */
+int inff_pktq_mlen(struct pktq *pq, uint prec_bmp)
+{
+ int prec, len;
+
+ len = 0;
+
+ for (prec = 0; prec <= pq->hi_prec; prec++)
+ if (prec_bmp & (1 << prec))
+ len += pq->q[prec].skblist.qlen;
+
+ return len;
+}
+
+/* Priority dequeue from a specific set of precedences */
+struct sk_buff *inff_pktq_mdeq(struct pktq *pq, uint prec_bmp,
+ int *prec_out)
+{
+ struct sk_buff_head *q;
+ struct sk_buff *p;
+ int prec;
+
+ if (pktq_empty(pq))
+ return NULL;
+
+ while ((prec = pq->hi_prec) > 0 &&
+ skb_queue_empty(&pq->q[prec].skblist))
+ pq->hi_prec--;
+
+ while ((prec_bmp & (1 << prec)) == 0 ||
+ skb_queue_empty(&pq->q[prec].skblist))
+ if (prec-- == 0)
+ return NULL;
+
+ q = &pq->q[prec].skblist;
+ p = skb_dequeue(q);
+ if (!p)
+ return NULL;
+
+ pq->len--;
+
+ if (prec_out)
+ *prec_out = prec;
+
+ return p;
+}
+
+/* Produce a human-readable string for boardrev */
+char *inff_boardrev_str(u32 brev, char *buf)
+{
+ char c;
+
+ if (brev < 0x100) {
+ snprintf(buf, INFF_BOARDREV_LEN, "%d.%d",
+ (brev & 0xf0) >> 4, brev & 0xf);
+ } else {
+ c = (brev & 0xf000) == 0x1000 ? 'P' : 'A';
+ snprintf(buf, INFF_BOARDREV_LEN, "%c%03x", c, brev & 0xfff);
+ }
+ return buf;
+}
+
+char *inff_dotrev_str(u32 dotrev, char *buf)
+{
+ u8 dotval[4];
+
+ if (!dotrev) {
+ snprintf(buf, INFF_DOTREV_LEN, "unknown");
+ return buf;
+ }
+ dotval[0] = (dotrev >> 24) & 0xFF;
+ dotval[1] = (dotrev >> 16) & 0xFF;
+ dotval[2] = (dotrev >> 8) & 0xFF;
+ dotval[3] = dotrev & 0xFF;
+
+ if (dotval[3])
+ snprintf(buf, INFF_DOTREV_LEN, "%d.%d.%d.%d", dotval[0],
+ dotval[1], dotval[2], dotval[3]);
+ else if (dotval[2])
+ snprintf(buf, INFF_DOTREV_LEN, "%d.%d.%d", dotval[0],
+ dotval[1], dotval[2]);
+ else
+ snprintf(buf, INFF_DOTREV_LEN, "%d.%d", dotval[0],
+ dotval[1]);
+
+ return buf;
+}
+
+struct sk_buff *__inff_pkt_buf_get_skb(uint len, gfp_t gfp_mask)
+{
+ struct sk_buff *skb;
+
+ skb = __netdev_alloc_skb(NULL, len, gfp_mask);
+ if (skb) {
+ skb_put(skb, len);
+ skb->priority = 0;
+ }
+ return skb;
+}
diff --git a/drivers/net/wireless/infineon/inffmac/utils.h b/drivers/net/wireless/infineon/inffmac/utils.h
new file mode 100644
index 000000000000..79a100647eab
--- /dev/null
+++ b/drivers/net/wireless/infineon/inffmac/utils.h
@@ -0,0 +1,233 @@
+/* SPDX-License-Identifier: ISC */
+/*
+ * Copyright (c) 2010 Broadcom Corporation
+ *
+ * Copyright (c) 2025-2026, Infineon Technologies AG, or an affiliate of Infineon Technologies AG.
+ * All rights reserved.
+ */
+
+#ifndef INFF_UTILS_H
+#define INFF_UTILS_H
+
+#include <linux/skbuff.h>
+
+/*
+ * Spin at most 'us' microseconds while 'exp' is true.
+ * Caller should explicitly test 'exp' when this completes
+ * and take appropriate error action if 'exp' is still true.
+ */
+#define SPINWAIT(exp, us) { \
+ uint countdown = (us) + 9; \
+ while ((exp) && (countdown >= 10)) {\
+ usleep_range(10, 20); \
+ countdown -= 10; \
+ } \
+}
+
+/* Spin at most 'ms' milliseconds with polling interval 'interval' milliseconds
+ * while 'exp' is true. Caller should explicitly test 'exp' when this completes
+ * and take appropriate error action if 'exp' is still true.
+ */
+#define SPINWAIT_MS(exp, ms, interval) { \
+ typeof(interval) interval_ = (interval); \
+ uint countdown = (ms) + (interval_ - 1U); \
+ while ((exp) && (countdown >= interval_)) { \
+ msleep(interval_); \
+ countdown -= interval_; \
+ } \
+}
+
+/* osl multi-precedence packet queue */
+#define PKTQ_LEN_DEFAULT 128 /* Max 128 packets */
+#define PKTQ_MAX_PREC 16 /* Maximum precedence levels */
+
+/* the largest reasonable packet buffer driver uses for ethernet MTU in bytes */
+#define PKTBUFSZ 2048
+
+#ifndef setbit
+#ifndef NBBY /* the BSD family defines NBBY */
+#define NBBY 8 /* 8 bits per byte */
+#endif /* #ifndef NBBY */
+#define setbit(a, i) { \
+ typeof(i) _i = (i); \
+ (((u8 *)a)[(_i) / NBBY] |= 1 << ((_i) % NBBY)); \
+ }
+#define clrbit(a, i) { \
+ typeof(i) _i = (i); \
+ (((u8 *)a)[(_i) / NBBY] &= ~(1 << ((_i) % NBBY))); \
+ }
+#define isset(a, i) { \
+ typeof(i) _i = (i); \
+ (((const u8 *)a)[(_i) / NBBY] & (1 << ((_i) % NBBY))); \
+ }
+#define isclr(a, i) { \
+ typeof(i) _i = (i); \
+ ((((const u8 *)a)[(_i) / NBBY] & (1 << ((_i) % NBBY))) == 0); \
+ }
+#endif /* setbit */
+
+#define NBITS(type) (sizeof(type) * 8)
+#define NBITVAL(nbits) (1 << (nbits))
+#define MAXBITVAL(nbits) ((1 << (nbits)) - 1)
+#define NBITMASK(nbits) MAXBITVAL(nbits)
+#define MAXNBVAL(nbyte) MAXBITVAL((nbyte) * 8)
+
+/* crc defines */
+#define CRC16_INIT_VALUE 0xffff /* Initial CRC16 checksum value */
+#define CRC16_GOOD_VALUE 0xf0b8 /* Good final CRC16 checksum value */
+
+/* 18-bytes of Ethernet address buffer length */
+#define ETHER_ADDR_STR_LEN 18
+
+struct pktq_prec {
+ struct sk_buff_head skblist;
+ u16 max; /* maximum number of queued packets */
+};
+
+/* multi-priority pkt queue */
+struct pktq {
+ u16 num_prec; /* number of precedences in use */
+ u16 hi_prec; /* rapid dequeue hint (>= highest non-empty prec) */
+ u16 max; /* total max packets */
+ u16 len; /* total number of packets */
+ /*
+ * q array must be last since # of elements can be either
+ * PKTQ_MAX_PREC or 1
+ */
+ struct pktq_prec q[PKTQ_MAX_PREC];
+};
+
+/* operations on a specific precedence in packet queue */
+
+static inline int pktq_plen(struct pktq *pq, int prec)
+{
+ return pq->q[prec].skblist.qlen;
+}
+
+static inline int pktq_pavail(struct pktq *pq, int prec)
+{
+ return pq->q[prec].max - pq->q[prec].skblist.qlen;
+}
+
+static inline bool pktq_pfull(struct pktq *pq, int prec)
+{
+ return pq->q[prec].skblist.qlen >= pq->q[prec].max;
+}
+
+static inline bool pktq_pempty(struct pktq *pq, int prec)
+{
+ return skb_queue_empty(&pq->q[prec].skblist);
+}
+
+static inline struct sk_buff *pktq_ppeek(struct pktq *pq, int prec)
+{
+ return skb_peek(&pq->q[prec].skblist);
+}
+
+static inline struct sk_buff *pktq_ppeek_tail(struct pktq *pq, int prec)
+{
+ return skb_peek_tail(&pq->q[prec].skblist);
+}
+
+struct sk_buff *inff_pktq_penq(struct pktq *pq, int prec, struct sk_buff *p);
+struct sk_buff *inff_pktq_penq_head(struct pktq *pq, int prec,
+ struct sk_buff *p);
+struct sk_buff *inff_pktq_pdeq(struct pktq *pq, int prec);
+struct sk_buff *inff_pktq_pdeq_tail(struct pktq *pq, int prec);
+struct sk_buff *inff_pktq_pdeq_match(struct pktq *pq, int prec,
+ bool (*match_fn)(struct sk_buff *p,
+ void *arg),
+ void *arg);
+
+/* packet primitives */
+struct sk_buff *inff_pkt_buf_get_skb(uint len);
+void inff_pkt_buf_free_skb(struct sk_buff *skb);
+struct sk_buff *__inff_pkt_buf_get_skb(uint len, gfp_t gfp_mask);
+
+/* Empty the queue at particular precedence level */
+/* callback function fn(pkt, arg) returns true if pkt belongs to if */
+void inff_pktq_pflush(struct pktq *pq, int prec, bool dir,
+ bool (*fn)(struct sk_buff *, void *), void *arg);
+
+/* operations on a set of precedences in packet queue */
+
+int inff_pktq_mlen(struct pktq *pq, uint prec_bmp);
+struct sk_buff *inff_pktq_mdeq(struct pktq *pq, uint prec_bmp, int *prec_out);
+
+/* operations on packet queue as a whole */
+
+static inline int pktq_len(struct pktq *pq)
+{
+ return (int)pq->len;
+}
+
+static inline int pktq_max(struct pktq *pq)
+{
+ return (int)pq->max;
+}
+
+static inline int pktq_avail(struct pktq *pq)
+{
+ return (int)(pq->max - pq->len);
+}
+
+static inline bool pktq_full(struct pktq *pq)
+{
+ return pq->len >= pq->max;
+}
+
+static inline bool pktq_empty(struct pktq *pq)
+{
+ return pq->len == 0;
+}
+
+void inff_pktq_init(struct pktq *pq, int num_prec, int max_len);
+/* prec_out may be NULL if caller is not interested in return value */
+struct sk_buff *inff_pktq_peek_tail(struct pktq *pq, int *prec_out);
+void inff_pktq_flush(struct pktq *pq, bool dir,
+ bool (*fn)(struct sk_buff *, void *), void *arg);
+
+/*
+ * bitfield macros using masking and shift
+ *
+ * remark: the mask parameter should be a shifted mask.
+ */
+static inline void inff_maskset32(u32 *var, u32 mask, u8 shift, u32 value)
+{
+ value = (value << shift) & mask;
+ *var = (*var & ~mask) | value;
+}
+
+static inline u32 inff_maskget32(u32 var, u32 mask, u8 shift)
+{
+ return (var & mask) >> shift;
+}
+
+static inline void inff_maskset16(u16 *var, u16 mask, u8 shift, u16 value)
+{
+ value = (value << shift) & mask;
+ *var = (*var & ~mask) | value;
+}
+
+static inline u16 inff_maskget16(u16 var, u16 mask, u8 shift)
+{
+ return (var & mask) >> shift;
+}
+
+static __always_inline void inff_delay(u32 ms)
+{
+ if (ms < 1000 / HZ) {
+ cond_resched();
+ mdelay(ms);
+ } else {
+ msleep(ms);
+ }
+}
+
+#define INFF_BOARDREV_LEN 8
+#define INFF_DOTREV_LEN 16
+
+char *inff_boardrev_str(u32 brev, char *buf);
+char *inff_dotrev_str(u32 dotrev, char *buf);
+
+#endif /* INFF_UTILS_H */
--
2.43.0
^ permalink raw reply related [flat|nested] 26+ messages in thread* [PATCH wireless-next v3 25/25] wifi: inffmac: add Kconfig, Makefile
2026-09-02 16:50 [PATCH wireless-next v3 00/25] wifi: inffmac: introducing a driver for Infineon's new generation chipsets Gokul Sivakumar
` (23 preceding siblings ...)
2026-09-02 16:50 ` [PATCH wireless-next v3 24/25] wifi: inffmac: add utils.c/h Gokul Sivakumar
@ 2026-09-02 16:50 ` Gokul Sivakumar
24 siblings, 0 replies; 26+ messages in thread
From: Gokul Sivakumar @ 2026-09-02 16:50 UTC (permalink / raw)
To: linux-wireless, johannes
Cc: arend.vanspriel, wlan-kernel-dev-list, gokulkumar.sivakumar
Introduce a new Kconfig and Makefile for the inffmac driver sub-directory.
Signed-off-by: Gokul Sivakumar <gokulkumar.sivakumar@infineon.com>
---
drivers/net/wireless/infineon/inffmac/Kconfig | 13 +++++++
.../net/wireless/infineon/inffmac/Makefile | 36 +++++++++++++++++++
2 files changed, 49 insertions(+)
create mode 100644 drivers/net/wireless/infineon/inffmac/Kconfig
create mode 100644 drivers/net/wireless/infineon/inffmac/Makefile
diff --git a/drivers/net/wireless/infineon/inffmac/Kconfig b/drivers/net/wireless/infineon/inffmac/Kconfig
new file mode 100644
index 000000000000..036ef2421f32
--- /dev/null
+++ b/drivers/net/wireless/infineon/inffmac/Kconfig
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: ISC
+
+config INFFMAC
+ tristate "Infineon FullMAC WLAN driver"
+ depends on CFG80211 && MMC
+ select FW_LOADER
+ select FW_UPLOAD
+ default y
+ help
+ This module adds support for wireless adapters based on Infineon
+ FullMAC chipsets. It has to work with at least one of the bus
+ interface support. If you choose to build a module, it'll be called
+ inffmac.ko.
diff --git a/drivers/net/wireless/infineon/inffmac/Makefile b/drivers/net/wireless/infineon/inffmac/Makefile
new file mode 100644
index 000000000000..1e13c2b2e5ec
--- /dev/null
+++ b/drivers/net/wireless/infineon/inffmac/Makefile
@@ -0,0 +1,36 @@
+# SPDX-License-Identifier: ISC
+#
+# Copyright (c) 2025-2026, Infineon Technologies AG, or an affiliate of Infineon Technologies AG.
+# All rights reserved.
+#
+# Makefile fragment for Infineon 802.11 Networking Device Driver
+#
+
+ccflags-y += -I $(src)
+
+obj-$(CONFIG_INFFMAC) += inffmac.o
+inffmac-objs += \
+ bcdc.o \
+ bus_proto.o \
+ cfg80211.o \
+ chan.o \
+ chip.o \
+ chip_acw74xx.o \
+ chip_cyw5591x.o \
+ dev_cmd.o \
+ dev_evt.o \
+ dfu.o \
+ feature.o \
+ firmware.o \
+ fwsignal.o \
+ he.o \
+ icdc.o \
+ ie.o \
+ interface.o \
+ main.o \
+ net.o \
+ scan.o \
+ sdio.o \
+ utils.o
+inffmac-$(CONFIG_INF_DEBUG) += \
+ debug.o
--
2.43.0
^ permalink raw reply related [flat|nested] 26+ messages in thread