# Base git commit: e8f897f4afef
# (Linux 6.8)
# 
# Author:    Russell King (Oracle) (Thu 18 Apr 09:53:32 BST 2024)
# Committer: Russell King (Oracle) (Tue  7 May 10:41:04 BST 2024)
# 
#   net: wlcore: debugfs: export hlid mac address/allocated packets
# 
#   Signed-off-by: Russell King (Oracle)
# 
#   4a69c0a515be4bd7408b27308524d95b41209adf
#    drivers/net/wireless/ti/wlcore/debugfs.c | 12 ++++++++++++
#    1 file changed, 12 insertions(+)
# 
# Author:    Russell King (Oracle) (Thu 11 May 17:09:50 BST 2023)
# Committer: Russell King (Oracle) (Tue  7 May 10:41:03 BST 2024)
# 
#   net: wlcore: debugfs: export tx_total
# 
#   Signed-off-by: Russell King (Oracle)
# 
#   71f3da4f7f64ac76a4e5b6e238e1a35e2233939d
#    drivers/net/wireless/ti/wlcore/debugfs.c | 1 +
#    drivers/net/wireless/ti/wlcore/main.c    | 1 +
#    drivers/net/wireless/ti/wlcore/wlcore.h  | 1 +
#    3 files changed, 3 insertions(+)
# 
# Author:    Johannes Berg (Sat 27 Apr 14:10:54 BST 2024)
# Committer: Russell King (Oracle) (Tue  7 May 10:41:02 BST 2024)
# 
#   net: wlcore: fix wlcore AP mode
# 
#   Using wl183x devices in AP mode with various firmwares is not stable.
# 
#   The driver currently adds a station to firmware with basic rates when it
#   is first known to the stack using the CMD_ADD_PEER command. Once the
#   station has finished authorising, another CMD_ADD_PEER command is issued
#   to update the firmware with the rates the station can use.
# 
#   However, after a random amount of time, the firmware ignores the power
#   management nullfunc frames from the station, and tries to send packets
#   while the station is asleep, resulting in lots of retries dropping down
#   in rate due to no response. This restricts the available bandwidth.
# 
#   With this happening with several stations, the user visible effect is
#   the latency of interactive connections increases significantly, packets
#   get dropped, and in general the WiFi connections become unreliable and
#   unstable.
# 
#   Eventually, the firmware transmit queue appears to get stuck - with
#   packets and blocks allocated that never clear.
# 
#   TI have a couple of patches that address this, but they touch the
#   mac80211 core to disable NL80211_FEATURE_FULL_AP_CLIENT_STATE for *all*
#   wireless drivers, which has the effect of not adding the station to the
#   stack until later when the rates are known. This is a sledge hammer
#   approach to solving the problem.
# 
#   The solution implemented here has the same effect, but without
#   impacting all drivers.
# 
#   We delay adding the station to firmware until it has been authorised
#   in the driver, and correspondingly remove the station when unwinding
#   from authorised state. Adding the station to firmware allocates a hlid,
#   which will now happen later than the driver expects. Therefore, we need
#   to track when this happens so that we transmit using the correct hlid.
# 
#   Reported-by: Russell King (Oracle)
#   Co-developed-by: Russell King (Oracle)
#   Tested-by: Russell King (Oracle)
#   Signed-off-by: Johannes Berg"
#   Signed-off-by: Russell King (Oracle)
# 
#   7f4473749b2071ce8c0ffe03592366055325cd80
#    drivers/net/wireless/ti/wlcore/cmd.c      |  7 -------
#    drivers/net/wireless/ti/wlcore/main.c     | 17 ++++++++---------
#    drivers/net/wireless/ti/wlcore/tx.c       |  7 ++-----
#    drivers/net/wireless/ti/wlcore/wlcore_i.h |  6 ++++++
#    4 files changed, 16 insertions(+), 21 deletions(-)
# 
# Author:    Russell King (Oracle) (Sat 20 Apr 22:02:29 BST 2024)
# Committer: Russell King (Oracle) (Tue  7 May 10:41:01 BST 2024)
# 
#   net: wlcore: add support for AES-CMAC
# 
#   Add support for the AES-CMAC cipher to TI Wireless. Based on TI's
#   patch:
# 
#   https://git.ti.com/cgit/wilink8-wlan/build-utilites/tree/patches/kernel_patches/4.19.38/0007-Adding-support-to-IGTK-key-AES-CMAC128-in-the-wlcore.patch?h=r8.9&id=a2ee50aa5190ed3b334373d6cd09b1bff56ffcf7
# 
#   Signed-off-by: Russell King (Oracle)
# 
#   2ee7156d4e28d60ddd7a3f8d55d64b3fb94eef1f
#    drivers/net/wireless/ti/wlcore/cmd.h  | 1 +
#    drivers/net/wireless/ti/wlcore/main.c | 4 ++++
#    2 files changed, 5 insertions(+)
# 
# Author:    Russell King (Oracle) (Sun 14 Apr 18:11:54 BST 2024)
# Committer: Russell King (Oracle) (Tue  7 May 10:40:59 BST 2024)
# 
#   net: wl18xx: allow firmwares > 8.9.0.x.58
# 
#   wlcore firmware versions are structured thusly:
# 
#   	chip.if-type.major.sub-type.minor
#   e.g.	  8    9       0       0     58
# 
#   With WL18xx ignoring the major firmware version, looking for a
#   firmware version that conforms to:
# 
#   	chip >= 8
#   	if-type >= 9
#   	major (don't care)
#   	sub-type (don't care)
#   	minor >= 58
# 
#   Each test is satisfied if the value read from the firmware is greater
#   than the minimum, but if it is equal (or we don't care about the
#   field), then the next field is checked.
# 
#   Thus it doesn't recognise 8.9.1.x.0 as being newer than 8.9.0.x.58
#   since the major and sub-type numbers are "don't care" and the minor
#   needs to be greater or equal to 58.
# 
#   We need to change the major version from "ignore" to "0" for this later
#   firmware to be correctly detected, and allow the dual-firmware version
#   support to work.
# 
#   Signed-off-by: Russell King (Oracle)
# 
#   8486e64f778ac6494385759e1afd63ae8c4214a0
#    drivers/net/wireless/ti/wl18xx/wl18xx.h | 2 +-
#    1 file changed, 1 insertion(+), 1 deletion(-)
# 
# Author:    Russell King (Oracle) (Sun 14 Apr 16:57:06 BST 2024)
# Committer: Russell King (Oracle) (Tue  7 May 10:40:58 BST 2024)
# 
#   net: wl18xx: add support for reading 8.9.1 fw status
# 
#   Add the necessary code to read the 8.9.1 firmware status into the
#   driver private status structure, augmenting the 8.9.0 firmware
#   status code. New structure layout taken from:
# 
#   https://git.ti.com/cgit/wilink8-wlan/build-utilites/tree/patches/kernel_patches/4.19.38/0023-wlcore-Fixing-PN-drift-on-encrypted-link-after-recov.patch?h=r8.9&id=a2ee50aa5190ed3b334373d6cd09b1bff56ffcf7
# 
#   Signed-off-by: Russell King (Oracle)
# 
#   a8b6447671adfeee9d5de9ee615eeac1b8fd9a8e
#    drivers/net/wireless/ti/wl18xx/main.c   | 71 ++++++++++++++++++++++++++++++++-
#    drivers/net/wireless/ti/wl18xx/wl18xx.h | 60 ++++++++++++++++++++++++++++
#    2 files changed, 129 insertions(+), 2 deletions(-)
# 
# Author:    Russell King (Oracle) (Thu 11 May 13:23:37 BST 2023)
# Committer: Russell King (Oracle) (Tue  7 May 10:40:57 BST 2024)
# 
#   net: wlcore: add pn16 support
# 
#   TI Wl18xx firmware adds a "pn16" field for AES and TKIP keys as per
#   their patch:
# 
#   https://git.ti.com/cgit/wilink8-wlan/build-utilites/tree/patches/kernel_patches/4.19.38/0023-wlcore-Fixing-PN-drift-on-encrypted-link-after-recov.patch?h=r8.9&id=a2ee50aa5190ed3b334373d6cd09b1bff56ffcf7
# 
#   Add support for this, but rather than requiring the field to be
#   present (which would break existing firmwares), make it optional.
# 
#   Signed-off-by: Russell King (Oracle)
# 
#   cd818e060e99961f740346aaf0e00b1e0653ad61
#    drivers/net/wireless/ti/wlcore/cmd.c      |  9 ++++
#    drivers/net/wireless/ti/wlcore/main.c     | 89 +++++++++++++++++++++++++++++--
#    drivers/net/wireless/ti/wlcore/wlcore_i.h |  4 ++
#    3 files changed, 97 insertions(+), 5 deletions(-)
# 
# Author:    Russell King (Oracle) (Thu 11 May 13:25:44 BST 2023)
# Committer: Russell King (Oracle) (Tue  7 May 10:40:56 BST 2024)
# 
#   net: wlcore: store AP encryption key type
# 
#   Updates for WL18xx firmware 8.9.1.x.x need to know the AP encryption
#   key type. Store this when a new key is set. Patch extracted from:
# 
#   https://git.ti.com/cgit/wilink8-wlan/build-utilites/tree/patches/kernel_patches/4.19.38/0023-wlcore-Fixing-PN-drift-on-encrypted-link-after-recov.patch?h=r8.9&id=a2ee50aa5190ed3b334373d6cd09b1bff56ffcf7
# 
#   Signed-off-by: Russell King (Oracle)
# 
#   01f5ff8012b67157f67224377639c75fd080fafc
#    drivers/net/wireless/ti/wlcore/main.c | 4 ++++
#    1 file changed, 4 insertions(+)
# 
# Author:    Russell King (Oracle) (Sun  5 May 12:05:37 BST 2024)
# Committer: Russell King (Oracle) (Tue  7 May 10:40:54 BST 2024)
# 
#   net: wlcore: pass "status" to wlcore_hw_convert_fw_status()
# 
#   wlcore_fw_status() is passed a pointer to the struct wl_fw_status to
#   decode the status into, which is always wl->fw_status. Rather than
#   referencing wl->fw_status within wlcore_fw_status(), use the supplied
#   argument so that we access this member in a consistent manner.
# 
#   Signed-off-by: Russell King (Oracle)
# 
#   4151317aa8d9db0858e5fa6a511debfc84d6d8e3
#    drivers/net/wireless/ti/wlcore/main.c | 2 +-
#    1 file changed, 1 insertion(+), 1 deletion(-)
# 
# Author:    Russell King (Oracle) (Thu 11 May 15:15:14 BST 2023)
# Committer: Russell King (Oracle) (Mon 15 Apr 11:54:03 BST 2024)
# 
#   net: wlcore: improve code in wlcore_fw_status()
# 
#   Referring to status->counters.tx_lnk_free_pkts[i] multiple times leads
#   to less efficient code. Cache this value in a local variable. This
#   also makes the code clearer.
# 
#   Signed-off-by: Russell King (Oracle)
# 
#   7081e7645698bdc0e9b02a496f4cc11912a54e7f
#    drivers/net/wireless/ti/wlcore/main.c | 8 ++++----
#    1 file changed, 4 insertions(+), 4 deletions(-)
# 
# Author:    Russell King (Oracle) (Thu 11 May 17:07:26 BST 2023)
# Committer: Russell King (Oracle) (Mon 15 Apr 11:54:03 BST 2024)
# 
#   net: wl18xx: make wl18xx_tx_immediate_complete() more efficient
# 
#   wl18xx_tx_immediate_complete() iterates through the completed transmit
#   descriptors in a circular fashion, and in doing so uses a modulus
#   operation that is not a power of two. This leads to inefficient code
#   generation, which can be easily solved by providing a helper to
#   increment to the next descriptor. Use this more efficient solution.
# 
#   Signed-off-by: Russell King (Oracle)
# 
#   d892cc8041c0ce5cea2a62ae5d9832aa3a1bb269
#    drivers/net/wireless/ti/wl18xx/tx.c | 13 ++++++++++---
#    1 file changed, 10 insertions(+), 3 deletions(-)
# 
# Author:    Russell King (Oracle) (Fri 12 May 15:07:10 BST 2023)
# Committer: Russell King (Oracle) (Mon 15 Apr 11:54:02 BST 2024)
# 
#   net: wlcore: correctness fix fwlog reading
# 
#   Fix the calculation of clear_offset, which may overflow the end of
#   the buffer. However, this is harmless if it does because in that case
#   it will be recalculated when we copy the chunk of messages at the
#   start of the buffer.
# 
#   Signed-off-by: Russell King (Oracle)
# 
#   8e84c4317565652b41fefc9e685330fb5baabc6e
#    drivers/net/wireless/ti/wlcore/event.c | 2 +-
#    1 file changed, 1 insertion(+), 1 deletion(-)
# 
diff --git a/drivers/net/wireless/ti/wl18xx/main.c b/drivers/net/wireless/ti/wl18xx/main.c
index 2ccac1cdec01..39d8eebb9b6e 100644
--- a/drivers/net/wireless/ti/wl18xx/main.c
+++ b/drivers/net/wireless/ti/wl18xx/main.c
@@ -1177,8 +1177,49 @@ static int wl18xx_hw_init(struct wl1271 *wl)
 	return ret;
 }
 
-static void wl18xx_convert_fw_status(struct wl1271 *wl, void *raw_fw_status,
-				     struct wl_fw_status *fw_status)
+static void wl18xx_convert_fw_status_8_9_1(struct wl1271 *wl,
+					   void *raw_fw_status,
+					   struct wl_fw_status *fw_status)
+{
+	struct wl18xx_fw_status_8_9_1 *int_fw_status = raw_fw_status;
+
+	fw_status->intr = le32_to_cpu(int_fw_status->intr);
+	fw_status->fw_rx_counter = int_fw_status->fw_rx_counter;
+	fw_status->drv_rx_counter = int_fw_status->drv_rx_counter;
+	fw_status->tx_results_counter = int_fw_status->tx_results_counter;
+	fw_status->rx_pkt_descs = int_fw_status->rx_pkt_descs;
+
+	fw_status->fw_localtime = le32_to_cpu(int_fw_status->fw_localtime);
+	fw_status->link_ps_bitmap = le32_to_cpu(int_fw_status->link_ps_bitmap);
+	fw_status->link_fast_bitmap =
+			le32_to_cpu(int_fw_status->link_fast_bitmap);
+	fw_status->total_released_blks =
+			le32_to_cpu(int_fw_status->total_released_blks);
+	fw_status->tx_total = le32_to_cpu(int_fw_status->tx_total);
+
+	fw_status->counters.tx_released_pkts =
+			int_fw_status->counters.tx_released_pkts;
+	fw_status->counters.tx_lnk_free_pkts =
+			int_fw_status->counters.tx_lnk_free_pkts;
+	fw_status->counters.tx_lnk_sec_pn16 =
+			int_fw_status->counters.tx_lnk_sec_pn16;
+	fw_status->counters.tx_voice_released_blks =
+			int_fw_status->counters.tx_voice_released_blks;
+	fw_status->counters.tx_last_rate =
+			int_fw_status->counters.tx_last_rate;
+	fw_status->counters.tx_last_rate_mbps =
+			int_fw_status->counters.tx_last_rate_mbps;
+	fw_status->counters.hlid =
+			int_fw_status->counters.hlid;
+
+	fw_status->log_start_addr = le32_to_cpu(int_fw_status->log_start_addr);
+
+	fw_status->priv = &int_fw_status->priv;
+}
+
+static void wl18xx_convert_fw_status_8_9_0(struct wl1271 *wl,
+					   void *raw_fw_status,
+					   struct wl_fw_status *fw_status)
 {
 	struct wl18xx_fw_status *int_fw_status = raw_fw_status;
 
@@ -1214,6 +1255,15 @@ static void wl18xx_convert_fw_status(struct wl1271 *wl, void *raw_fw_status,
 	fw_status->priv = &int_fw_status->priv;
 }
 
+static void wl18xx_convert_fw_status(struct wl1271 *wl, void *raw_fw_status,
+				     struct wl_fw_status *fw_status)
+{
+	if (wl->chip.fw_ver[FW_VER_MAJOR] == 0)
+		wl18xx_convert_fw_status_8_9_0(wl, raw_fw_status, fw_status);
+	else
+		wl18xx_convert_fw_status_8_9_1(wl, raw_fw_status, fw_status);
+}
+
 static void wl18xx_set_tx_desc_csum(struct wl1271 *wl,
 				    struct wl1271_tx_hw_descr *desc,
 				    struct sk_buff *skb)
@@ -1515,12 +1565,29 @@ static int wl18xx_handle_static_data(struct wl1271 *wl,
 {
 	struct wl18xx_static_data_priv *static_data_priv =
 		(struct wl18xx_static_data_priv *) static_data->priv;
+	size_t fw_status_len;
 
 	strscpy(wl->chip.phy_fw_ver_str, static_data_priv->phy_version,
 		sizeof(wl->chip.phy_fw_ver_str));
 
 	wl1271_info("PHY firmware version: %s", static_data_priv->phy_version);
 
+	/* Adjust the firmware status size according to the firmware version */
+	if (wl->chip.fw_ver[FW_VER_MAJOR] == 0)
+		fw_status_len = sizeof(struct wl18xx_fw_status);
+	else
+		fw_status_len = sizeof(struct wl18xx_fw_status_8_9_1);
+
+	if (wl->fw_status_len != fw_status_len) {
+		void *new_status = krealloc(wl->raw_fw_status, fw_status_len,
+					    GFP_KERNEL | __GFP_ZERO);
+		if (!new_status)
+			return -ENOMEM;
+
+		wl->raw_fw_status = new_status;
+		wl->fw_status_len = fw_status_len;
+	}
+
 	return 0;
 }
 
diff --git a/drivers/net/wireless/ti/wl18xx/tx.c b/drivers/net/wireless/ti/wl18xx/tx.c
index 55d9b0861c53..beef393853ef 100644
--- a/drivers/net/wireless/ti/wl18xx/tx.c
+++ b/drivers/net/wireless/ti/wl18xx/tx.c
@@ -129,6 +129,14 @@ static void wl18xx_tx_complete_packet(struct wl1271 *wl, u8 tx_stat_byte)
 	wl1271_free_tx_id(wl, id);
 }
 
+static u8 wl18xx_next_tx_idx(u8 idx)
+{
+	if (++idx >= WL18XX_FW_MAX_TX_STATUS_DESC)
+		idx = 0;
+
+	return idx;
+}
+
 void wl18xx_tx_immediate_complete(struct wl1271 *wl)
 {
 	struct wl18xx_fw_status_priv *status_priv =
@@ -161,9 +169,8 @@ void wl18xx_tx_immediate_complete(struct wl1271 *wl)
 		return;
 	}
 
-	for (i = priv->last_fw_rls_idx;
-	     i != status_priv->fw_release_idx;
-	     i = (i + 1) % WL18XX_FW_MAX_TX_STATUS_DESC) {
+	for (i = priv->last_fw_rls_idx; i != status_priv->fw_release_idx;
+	     i = wl18xx_next_tx_idx(i)) {
 		wl18xx_tx_complete_packet(wl,
 			status_priv->released_tx_desc[i]);
 
diff --git a/drivers/net/wireless/ti/wl18xx/wl18xx.h b/drivers/net/wireless/ti/wl18xx/wl18xx.h
index b642e0c437bb..9fc2972bcb4f 100644
--- a/drivers/net/wireless/ti/wl18xx/wl18xx.h
+++ b/drivers/net/wireless/ti/wl18xx/wl18xx.h
@@ -13,7 +13,7 @@
 /* minimum FW required for driver */
 #define WL18XX_CHIP_VER		8
 #define WL18XX_IFTYPE_VER	9
-#define WL18XX_MAJOR_VER	WLCORE_FW_VER_IGNORE
+#define WL18XX_MAJOR_VER	0
 #define WL18XX_SUBTYPE_VER	WLCORE_FW_VER_IGNORE
 #define WL18XX_MINOR_VER	58
 
@@ -155,6 +155,66 @@ struct wl18xx_fw_status {
 	struct wl18xx_fw_status_priv priv;
 } __packed;
 
+struct wl18xx_fw_packet_counters_8_9_1 {
+	/* Cumulative counter of released packets per AC */
+	u8 tx_released_pkts[NUM_TX_QUEUES];
+
+	/* Cumulative counter of freed packets per HLID */
+	u8 tx_lnk_free_pkts[WL18XX_MAX_LINKS];
+
+	/* PN16 of last TKIP/AES seq-num per HLID */
+	u16 tx_lnk_sec_pn16[WL18XX_MAX_LINKS];
+
+	/* Cumulative counter of released Voice memory blocks */
+	u8 tx_voice_released_blks;
+
+	/* Tx rate of the last transmitted packet */
+	u8 tx_last_rate;
+
+	/* Tx rate or Tx rate estimate pre-calculated by fw in mbps units */
+	u8 tx_last_rate_mbps;
+
+	/* hlid for which the rates were reported */
+	u8 hlid;
+} __packed;
+
+/* FW status registers */
+struct wl18xx_fw_status_8_9_1 {
+	__le32 intr;
+	u8  fw_rx_counter;
+	u8  drv_rx_counter;
+	u8  reserved;
+	u8  tx_results_counter;
+	__le32 rx_pkt_descs[WL18XX_NUM_RX_DESCRIPTORS];
+
+	__le32 fw_localtime;
+
+	/*
+	 * A bitmap (where each bit represents a single HLID)
+	 * to indicate if the station is in PS mode.
+	 */
+	__le32 link_ps_bitmap;
+
+	/*
+	 * A bitmap (where each bit represents a single HLID) to indicate
+	 * if the station is in Fast mode
+	 */
+	__le32 link_fast_bitmap;
+
+	/* Cumulative counter of total released mem blocks since FW-reset */
+	__le32 total_released_blks;
+
+	/* Size (in Memory Blocks) of TX pool */
+	__le32 tx_total;
+
+	struct wl18xx_fw_packet_counters_8_9_1 counters;
+
+	__le32 log_start_addr;
+
+	/* Private status to be used by the lower drivers */
+	struct wl18xx_fw_status_priv priv;
+} __packed;
+
 #define WL18XX_PHY_VERSION_MAX_LEN 20
 
 struct wl18xx_static_data_priv {
diff --git a/drivers/net/wireless/ti/wlcore/cmd.c b/drivers/net/wireless/ti/wlcore/cmd.c
index a939fd89a7f5..9d99d144a727 100644
--- a/drivers/net/wireless/ti/wlcore/cmd.c
+++ b/drivers/net/wireless/ti/wlcore/cmd.c
@@ -332,6 +332,14 @@ int wl12xx_allocate_link(struct wl1271 *wl, struct wl12xx_vif *wlvif, u8 *hlid)
 			wl->fw_status->counters.tx_lnk_free_pkts[link];
 	wl->links[link].wlvif = wlvif;
 
+	/*
+	 * Take the last sec_pn16 value from the current FW status. On recovery,
+	 * we might not have fw_status yet, and tx_lnk_sec_pn16[] will be NULL.
+	 */
+	if (wl->fw_status->counters.tx_lnk_sec_pn16)
+		wl->links[link].prev_sec_pn16 =
+			wl->fw_status->counters.tx_lnk_sec_pn16[link];
+
 	/*
 	 * Take saved value for total freed packets from wlvif, in case this is
 	 * recovery/resume
@@ -360,6 +368,7 @@ void wl12xx_free_link(struct wl1271 *wl, struct wl12xx_vif *wlvif, u8 *hlid)
 
 	wl->links[*hlid].allocated_pkts = 0;
 	wl->links[*hlid].prev_freed_pkts = 0;
+	wl->links[*hlid].prev_sec_pn16 = 0;
 	wl->links[*hlid].ba_bitmap = 0;
 	eth_zero_addr(wl->links[*hlid].addr);
 
@@ -1566,13 +1575,6 @@ int wl12xx_cmd_add_peer(struct wl1271 *wl, struct wl12xx_vif *wlvif,
 		cpu_to_le32(wl1271_tx_enabled_rates_get(wl, sta_rates,
 							wlvif->band));
 
-	if (!cmd->supported_rates) {
-		wl1271_debug(DEBUG_CMD,
-			     "peer has no supported rates yet, configuring basic rates: 0x%x",
-			     wlvif->basic_rate_set);
-		cmd->supported_rates = cpu_to_le32(wlvif->basic_rate_set);
-	}
-
 	wl1271_debug(DEBUG_CMD, "new peer rates=0x%x queues=0x%x",
 		     cmd->supported_rates, sta->uapsd_queues);
 
diff --git a/drivers/net/wireless/ti/wlcore/cmd.h b/drivers/net/wireless/ti/wlcore/cmd.h
index f2609d5b6bf7..1d59eb481dec 100644
--- a/drivers/net/wireless/ti/wlcore/cmd.h
+++ b/drivers/net/wireless/ti/wlcore/cmd.h
@@ -458,6 +458,7 @@ enum wl1271_cmd_key_type {
 	KEY_TKIP = 2,
 	KEY_AES  = 3,
 	KEY_GEM  = 4,
+	KEY_IGTK = 5,
 };
 
 struct wl1271_cmd_set_keys {
diff --git a/drivers/net/wireless/ti/wlcore/debugfs.c b/drivers/net/wireless/ti/wlcore/debugfs.c
index eb3d3f0e0b4d..27ac27364fe0 100644
--- a/drivers/net/wireless/ti/wlcore/debugfs.c
+++ b/drivers/net/wireless/ti/wlcore/debugfs.c
@@ -457,6 +457,7 @@ static ssize_t driver_state_read(struct file *file, char __user *user_buf,
 		DRIVER_STATE_PRINT_GENERIC(channel, "%d (%s)", wlvif->channel,
 					   wlvif->p2p ? "P2P-GO" : "AP");
 
+	DRIVER_STATE_PRINT_INT(tx_blocks_total);
 	DRIVER_STATE_PRINT_INT(tx_blocks_available);
 	DRIVER_STATE_PRINT_INT(tx_allocated_blocks);
 	DRIVER_STATE_PRINT_INT(tx_allocated_pkts[0]);
@@ -492,6 +493,18 @@ static ssize_t driver_state_read(struct file *file, char __user *user_buf,
 	DRIVER_STATE_PRINT_STR(chip.phy_fw_ver_str);
 	DRIVER_STATE_PRINT_INT(recovery_count);
 
+	wl12xx_for_each_wlvif_ap(wl, wlvif) {
+		int hlid;
+		for_each_set_bit(hlid, wlvif->ap.sta_hlid_map, wl->num_links) {
+			res += scnprintf(buf + res, DRIVER_STATE_BUF_LEN - res,
+					 "links[%d].allocated_pkts = %u\n",
+					 hlid, wl->links[hlid].allocated_pkts);
+			res += scnprintf(buf + res, DRIVER_STATE_BUF_LEN - res,
+					 "links[%d].addr = %pM\n",
+					 hlid, wl->links[hlid].addr);
+		}
+	}
+
 #undef DRIVER_STATE_PRINT_INT
 #undef DRIVER_STATE_PRINT_LONG
 #undef DRIVER_STATE_PRINT_HEX
diff --git a/drivers/net/wireless/ti/wlcore/event.c b/drivers/net/wireless/ti/wlcore/event.c
index 1e082d039b82..3bb6ff6e0853 100644
--- a/drivers/net/wireless/ti/wlcore/event.c
+++ b/drivers/net/wireless/ti/wlcore/event.c
@@ -83,7 +83,7 @@ int wlcore_event_fw_logger(struct wl1271 *wl)
 	/* Copy initial part up to the end of ring buffer */
 	len = min(actual_len, available_len);
 	wl12xx_copy_fwlog(wl, &buffer[start_loc], len);
-	clear_ptr = addr_ptr + start_loc + actual_len;
+	clear_ptr = addr_ptr + start_loc + len;
 	if (clear_ptr == buff_end_ptr)
 		clear_ptr = buff_start_ptr;
 
diff --git a/drivers/net/wireless/ti/wlcore/main.c b/drivers/net/wireless/ti/wlcore/main.c
index 5736acb4d206..4bfc1fc688fc 100644
--- a/drivers/net/wireless/ti/wlcore/main.c
+++ b/drivers/net/wireless/ti/wlcore/main.c
@@ -379,6 +379,8 @@ static void wl12xx_irq_update_links_status(struct wl1271 *wl,
 
 static int wlcore_fw_status(struct wl1271 *wl, struct wl_fw_status *status)
 {
+	struct wl12xx_vif *wlvifsta;
+	struct wl12xx_vif *wlvifap;
 	struct wl12xx_vif *wlvif;
 	u32 old_tx_blk_count = wl->tx_blocks_available;
 	int avail, freed_blocks;
@@ -392,7 +394,7 @@ static int wlcore_fw_status(struct wl1271 *wl, struct wl_fw_status *status)
 	if (ret < 0)
 		return ret;
 
-	wlcore_hw_convert_fw_status(wl, wl->raw_fw_status, wl->fw_status);
+	wlcore_hw_convert_fw_status(wl, wl->raw_fw_status, status);
 
 	wl1271_debug(DEBUG_IRQ, "intr: 0x%x (fw_rx_counter = %d, "
 		     "drv_rx_counter = %d, tx_results_counter = %d)",
@@ -410,23 +412,100 @@ static int wlcore_fw_status(struct wl1271 *wl, struct wl_fw_status *status)
 		wl->tx_pkts_freed[i] = status->counters.tx_released_pkts[i];
 	}
 
+	/* Find an authorized STA vif */
+	wlvifsta = NULL;
+	wl12xx_for_each_wlvif_sta(wl, wlvif) {
+		if (wlvif->sta.hlid != WL12XX_INVALID_LINK_ID &&
+		    test_bit(WLVIF_FLAG_STA_AUTHORIZED, &wlvif->flags)) {
+			wlvifsta = wlvif;
+			break;
+		}
+	}
+
+	/* Find a started AP vif */
+	wlvifap = NULL;
+	wl12xx_for_each_wlvif(wl, wlvif) {
+		if (wlvif->bss_type == BSS_TYPE_AP_BSS &&
+		    wlvif->inconn_count == 0 &&
+		    test_bit(WLVIF_FLAG_AP_STARTED, &wlvif->flags)) {
+			wlvifap = wlvif;
+			break;
+		}
+	}
 
 	for_each_set_bit(i, wl->links_map, wl->num_links) {
-		u8 diff;
+		u16 diff16, sec_pn16;
+		u8 diff, tx_lnk_free_pkts;
+
 		lnk = &wl->links[i];
 
 		/* prevent wrap-around in freed-packets counter */
-		diff = (status->counters.tx_lnk_free_pkts[i] -
-		       lnk->prev_freed_pkts) & 0xff;
+		tx_lnk_free_pkts = status->counters.tx_lnk_free_pkts[i];
+		diff = (tx_lnk_free_pkts - lnk->prev_freed_pkts) & 0xff;
 
-		if (diff == 0)
+		if (diff) {
+			lnk->allocated_pkts -= diff;
+			lnk->prev_freed_pkts = tx_lnk_free_pkts;
+		}
+
+		/* Get the current sec_pn16 value if present */
+		if (status->counters.tx_lnk_sec_pn16)
+			sec_pn16 = status->counters.tx_lnk_sec_pn16[i];
+		else
+			sec_pn16 = 0;
+		/* prevent wrap-around in pn16 counter */
+		diff16 = (sec_pn16 - lnk->prev_sec_pn16) & 0xffff;
+
+		/* FIXME: since free_pkts is a 8-bit counter of packets that
+		 * rolls over, it can become zero. If it is zero, then we
+		 * omit processing below. Is that really correct?
+		 */
+		if (tx_lnk_free_pkts <= 0)
 			continue;
 
-		lnk->allocated_pkts -= diff;
-		lnk->prev_freed_pkts = status->counters.tx_lnk_free_pkts[i];
+		/* For a station that has an authorized link: */
+		if (wlvifsta && wlvifsta->sta.hlid == i) {
+			if (wlvifsta->encryption_type == KEY_TKIP ||
+			    wlvifsta->encryption_type == KEY_AES) {
+				if (diff16) {
+					lnk->prev_sec_pn16 = sec_pn16;
+					/* accumulate the prev_freed_pkts
+					 * counter according to the PN from
+					 * firmware
+					 */
+					lnk->total_freed_pkts += diff16;
+				}
+			} else {
+				if (diff)
+					/* accumulate the prev_freed_pkts
+					 * counter according to the free packets
+					 * count from firmware
+					 */
+					lnk->total_freed_pkts += diff;
+			}
+		}
 
-		/* accumulate the prev_freed_pkts counter */
-		lnk->total_freed_pkts += diff;
+		/* For an AP that has been started */
+		if (wlvifap && test_bit(i, wlvifap->ap.sta_hlid_map)) {
+			if (wlvifap->encryption_type == KEY_TKIP ||
+			    wlvifap->encryption_type == KEY_AES) {
+				if (diff16) {
+					lnk->prev_sec_pn16 = sec_pn16;
+					/* accumulate the prev_freed_pkts
+					 * counter according to the PN from
+					 * firmware
+					 */
+					lnk->total_freed_pkts += diff16;
+				}
+			} else {
+				if (diff)
+					/* accumulate the prev_freed_pkts
+					 * counter according to the free packets
+					 * count from firmware
+					 */
+					lnk->total_freed_pkts += diff;
+			}
+		}
 	}
 
 	/* prevent wrap-around in total blocks counter */
@@ -453,6 +532,7 @@ static int wlcore_fw_status(struct wl1271 *wl, struct wl_fw_status *status)
 			cancel_delayed_work(&wl->tx_watchdog_work);
 	}
 
+	wl->tx_blocks_total = status->tx_total;
 	avail = status->tx_total - wl->tx_allocated_blocks;
 
 	/*
@@ -3518,6 +3598,9 @@ int wlcore_set_key(struct wl1271 *wl, enum set_key_cmd cmd,
 	case WL1271_CIPHER_SUITE_GEM:
 		key_type = KEY_GEM;
 		break;
+	case WLAN_CIPHER_SUITE_AES_CMAC:
+		key_type = KEY_IGTK;
+		break;
 	default:
 		wl1271_error("Unknown key algo 0x%x", key_conf->cipher);
 
@@ -3537,6 +3620,10 @@ int wlcore_set_key(struct wl1271 *wl, enum set_key_cmd cmd,
 			return ret;
 		}
 
+		/* Store AP encryption key type */
+		if (wlvif->bss_type == BSS_TYPE_AP_BSS)
+			wlvif->encryption_type = key_type;
+
 		/*
 		 * reconfiguring arp response if the unicast (or common)
 		 * encryption key type was changed
@@ -5039,6 +5126,8 @@ static int wl12xx_sta_add(struct wl1271 *wl,
 	ret = wl12xx_cmd_add_peer(wl, wlvif, sta, hlid);
 	if (ret < 0)
 		wl1271_free_sta(wl, wlvif, hlid);
+	else
+		wl_sta->fw_added = true;
 
 	return ret;
 }
@@ -5061,6 +5150,8 @@ static int wl12xx_sta_remove(struct wl1271 *wl,
 	if (ret < 0)
 		return ret;
 
+	wl_sta->fw_added = false;
+
 	wl1271_free_sta(wl, wlvif, wl_sta->hlid);
 	return ret;
 }
@@ -5139,8 +5230,8 @@ static int wl12xx_update_sta_state(struct wl1271 *wl,
 
 	/* Add station (AP mode) */
 	if (is_ap &&
-	    old_state == IEEE80211_STA_NOTEXIST &&
-	    new_state == IEEE80211_STA_NONE) {
+	    old_state == IEEE80211_STA_AUTH &&
+	    new_state == IEEE80211_STA_ASSOC) {
 		ret = wl12xx_sta_add(wl, wlvif, sta);
 		if (ret)
 			return ret;
@@ -5150,8 +5241,8 @@ static int wl12xx_update_sta_state(struct wl1271 *wl,
 
 	/* Remove station (AP mode) */
 	if (is_ap &&
-	    old_state == IEEE80211_STA_NONE &&
-	    new_state == IEEE80211_STA_NOTEXIST) {
+	    old_state == IEEE80211_STA_ASSOC &&
+	    new_state == IEEE80211_STA_AUTH) {
 		/* must not fail */
 		wl12xx_sta_remove(wl, wlvif, sta);
 
@@ -5165,11 +5256,6 @@ static int wl12xx_update_sta_state(struct wl1271 *wl,
 		if (ret < 0)
 			return ret;
 
-		/* reconfigure rates */
-		ret = wl12xx_cmd_add_peer(wl, wlvif, sta, wl_sta->hlid);
-		if (ret < 0)
-			return ret;
-
 		ret = wl1271_acx_set_ht_capabilities(wl, &sta->deflink.ht_cap,
 						     true,
 						     wl_sta->hlid);
@@ -6146,6 +6232,7 @@ static int wl1271_init_ieee80211(struct wl1271 *wl)
 		WLAN_CIPHER_SUITE_TKIP,
 		WLAN_CIPHER_SUITE_CCMP,
 		WL1271_CIPHER_SUITE_GEM,
+		WLAN_CIPHER_SUITE_AES_CMAC,
 	};
 
 	/* The tx descriptor buffer */
diff --git a/drivers/net/wireless/ti/wlcore/tx.c b/drivers/net/wireless/ti/wlcore/tx.c
index 7bd3ce2f0804..464587d16ab2 100644
--- a/drivers/net/wireless/ti/wlcore/tx.c
+++ b/drivers/net/wireless/ti/wlcore/tx.c
@@ -140,11 +140,8 @@ EXPORT_SYMBOL(wl12xx_is_dummy_packet);
 static u8 wl12xx_tx_get_hlid_ap(struct wl1271 *wl, struct wl12xx_vif *wlvif,
 				struct sk_buff *skb, struct ieee80211_sta *sta)
 {
-	if (sta) {
-		struct wl1271_station *wl_sta;
-
-		wl_sta = (struct wl1271_station *)sta->drv_priv;
-		return wl_sta->hlid;
+	if (sta && wl1271_station(sta)->fw_added) {
+		return wl1271_station(sta)->hlid;
 	} else {
 		struct ieee80211_hdr *hdr;
 
diff --git a/drivers/net/wireless/ti/wlcore/wlcore.h b/drivers/net/wireless/ti/wlcore/wlcore.h
index 1f8511bf9bb3..97ade837c343 100644
--- a/drivers/net/wireless/ti/wlcore/wlcore.h
+++ b/drivers/net/wireless/ti/wlcore/wlcore.h
@@ -242,6 +242,7 @@ struct wl1271 {
 	struct wl1271_acx_mem_map *target_mem_map;
 
 	/* Accounting for allocated / available TX blocks on HW */
+	u32 tx_blocks_total;
 	u32 tx_blocks_freed;
 	u32 tx_blocks_available;
 	u32 tx_allocated_blocks;
diff --git a/drivers/net/wireless/ti/wlcore/wlcore_i.h b/drivers/net/wireless/ti/wlcore/wlcore_i.h
index eefae3f867b9..a5688bb23ff1 100644
--- a/drivers/net/wireless/ti/wlcore/wlcore_i.h
+++ b/drivers/net/wireless/ti/wlcore/wlcore_i.h
@@ -151,6 +151,9 @@ struct wl_fw_status {
 		 */
 		u8 *tx_lnk_free_pkts;
 
+		/* PN16 of last TKIP/AES seq-num per HLID */
+		u16 *tx_lnk_sec_pn16;
+
 		/* Cumulative counter of released Voice memory blocks */
 		u8 tx_voice_released_blks;
 
@@ -259,6 +262,7 @@ struct wl1271_link {
 	/* accounting for allocated / freed packets in FW */
 	u8 allocated_pkts;
 	u8 prev_freed_pkts;
+	u16 prev_sec_pn16;
 
 	u8 addr[ETH_ALEN];
 
@@ -324,6 +328,7 @@ struct wl12xx_rx_filter {
 
 struct wl1271_station {
 	u8 hlid;
+	bool fw_added;
 	bool in_connection;
 
 	/*
@@ -335,6 +340,11 @@ struct wl1271_station {
 	u64 total_freed_pkts;
 };
 
+static inline struct wl1271_station *wl1271_station(struct ieee80211_sta *sta)
+{
+	return (struct wl1271_station *)sta->drv_priv;
+}
+
 struct wl12xx_vif {
 	struct wl1271 *wl;
 	struct list_head list;
