mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v2 0/5] wifi: brcmfmac: pcie: S3 and deep sleep on the T2 Mac chips
@ 2026-11-11 12:07 Andre Eikmeyer
  2026-11-11 12:07 ` [PATCH v2 1/5] wifi: brcmfmac: pcie: set the state before D0_INFORM on resume Andre Eikmeyer
                   ` (4 more replies)
  0 siblings, 5 replies; 6+ messages in thread
From: Andre Eikmeyer @ 2026-11-11 12:07 UTC (permalink / raw)
  To: arend.vanspriel
  Cc: linux-wireless, brcm80211, brcm80211-dev-list.pdl, linux-kernel,
	michael.reeves077, marcan, j, asahi, sebastian.reichel,
	Andre Eikmeyer

This goes on top of Michael Reeves' BCM4388 series [1], which also
changes how brcmfmac talks to the BCM4364 and BCM4377 of the T2 Macs.
These patches are follow-ups to it.

Michael's Patch 5 moves the mailbox to the control ring when the
firmware does not set BRCMF_PCIE_SHARED_USE_MAILBOX. On the BCM4377
of the T2 Macs that makes D3 work at all, since this firmware doesn't
read the TCM mailbox the driver used before. But that caused some
other issues as an aftermath.

Patches 1 and 2 fix two things in that path. D0_INFORM on resume fails
while the PCIe state is still DOWN, which I reported on patch 5 [2].
And with host ready as a capability, the host ready doorbell at init
halts the BCM4364 firmware.

Patch 3 probes again after a resume through the platform firmware,
since the chip loses its firmware in S3. Patch 4 stands on its own and
drops a tx status wait on flowring delete that the BCM4377 firmware
doesn't satisfy.

Patch 5 is new in v2. The BCM4364 and BCM4377 firmware support inband
deep sleep, but the driver only advertises that it has no out-of-band
device wake, which leaves deep sleep disabled. The firmware then stays
awake and its latency tolerance holds the package out of PC10. With
patch 5 the firmware enters deep sleep at idle and the package reaches
PC10 on both machines. It also relies on the mailbox over the control
ring from Michael's patch 5.

The Bluetooth function of the BCM4377 gets the same resume handling in
"Bluetooth: hci_bcm4377: resume after ACPI S3" on linux-bluetooth.

Successfully tested on the BCM4377 (MacBookAir9,1), BCM4364B2
(MacBookPro15,1), and on the BCM4364B3 (MacBookPro16,1).

v1 [3] has not received any feedback yet. Without these patches the
T2 Mac chips do not survive S3 on top of Michael's series.

Changes in v2:
- Add patch 5, inband device wake and deep sleep
- Patches 1 to 4 are unchanged

[1] https://lore.kernel.org/all/20260925-brcm4388-submit-v1-0-b5cbc2cde2fb@gmail.com/
[2] https://lore.kernel.org/all/20261106153612.2aadef2a@kait2en-mbp15-1/
[3] https://lore.kernel.org/all/20261107154810.89780-1-dev@deq.rocks/


Andre Eikmeyer (5):
  wifi: brcmfmac: pcie: set the state before D0_INFORM on resume
  wifi: brcmfmac: pcie: skip the host ready doorbell at init once it is
    a capability
  wifi: brcmfmac: pcie: probe again after a resume through firmware
  wifi: brcmfmac: msgbuf: send the flowring delete without waiting for
    tx status
  wifi: brcmfmac: pcie: support inband device wake and deep sleep

 .../broadcom/brcm80211/brcmfmac/commonring.c  |  14 +
 .../broadcom/brcm80211/brcmfmac/commonring.h  |   5 +
 .../broadcom/brcm80211/brcmfmac/msgbuf.c      |  23 +-
 .../broadcom/brcm80211/brcmfmac/pcie.c        | 259 +++++++++++++++++-
 4 files changed, 282 insertions(+), 19 deletions(-)


base-commit: 991a73f4909f1348ffb4b38765cdb522a775e4c2
prerequisite-patch-id: e3427132df0f9740b79d9b3f84bc14c25f787514
prerequisite-patch-id: 1a0aae8b057854813ca6d1f20d3bf153a7b761ec
prerequisite-patch-id: 3453364de54d0d786a6126fca792567f13aa3679
prerequisite-patch-id: d40a127ef83ad7d898ca7c3d643b45b829051477
prerequisite-patch-id: 5bd50905bb42d138eb6fda7d5ad795615348ecdd

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

* [PATCH v2 1/5] wifi: brcmfmac: pcie: set the state before D0_INFORM on resume
  2026-11-11 12:07 [PATCH v2 0/5] wifi: brcmfmac: pcie: S3 and deep sleep on the T2 Mac chips Andre Eikmeyer
@ 2026-11-11 12:07 ` Andre Eikmeyer
  2026-11-11 12:07 ` [PATCH v2 2/5] wifi: brcmfmac: pcie: skip the host ready doorbell at init once it is a capability Andre Eikmeyer
                   ` (3 subsequent siblings)
  4 siblings, 0 replies; 6+ messages in thread
From: Andre Eikmeyer @ 2026-11-11 12:07 UTC (permalink / raw)
  To: arend.vanspriel
  Cc: linux-wireless, brcm80211, brcm80211-dev-list.pdl, linux-kernel,
	michael.reeves077, marcan, j, asahi, sebastian.reichel,
	Andre Eikmeyer

With the mailbox on the control ring, D0_INFORM goes through the ring
write path, which refuses to write while devinfo->state is DOWN.
brcmf_pcie_pm_leave_D3() sets the state only after D0_INFORM, so every
hot resume fails and falls back to remove and re-probe:

  brcmf_pcie_send_mb_data: Failed to send H2D mailbox data (-5)

Set the state before D0_INFORM and back to DOWN when the hot resume
fails.

Signed-off-by: Andre Eikmeyer <dev@deq.rocks>
---
 drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
index 4de157d18ff..231a2fb89f0 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
@@ -2838,10 +2838,10 @@ static int brcmf_pcie_pm_leave_D3(struct device *dev)
 	/* Check if device is still up and running, if so we are ready */
 	if (brcmf_pcie_read_pcie32(devinfo, devinfo->reginfo->intmask) != 0) {
 		brcmf_dbg(PCIE, "Try to wakeup device....\n");
+		devinfo->state = BRCMFMAC_PCIE_STATE_UP;
 		if (brcmf_pcie_send_mb_data(devinfo, BRCMF_H2D_HOST_D0_INFORM))
 			goto cleanup;
 		brcmf_dbg(PCIE, "Hot resume, continue....\n");
-		devinfo->state = BRCMFMAC_PCIE_STATE_UP;
 		brcmf_pcie_select_core(devinfo, BCMA_CORE_PCIE2);
 		brcmf_bus_change_state(bus, BRCMF_BUS_UP);
 		brcmf_pcie_intr_enable(devinfo);
@@ -2851,6 +2851,7 @@ static int brcmf_pcie_pm_leave_D3(struct device *dev)
 	}
 
 cleanup:
+	devinfo->state = BRCMFMAC_PCIE_STATE_DOWN;
 	brcmf_chip_detach(devinfo->ci);
 	devinfo->ci = NULL;
 	pdev = devinfo->pdev;

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

* [PATCH v2 2/5] wifi: brcmfmac: pcie: skip the host ready doorbell at init once it is a capability
  2026-11-11 12:07 [PATCH v2 0/5] wifi: brcmfmac: pcie: S3 and deep sleep on the T2 Mac chips Andre Eikmeyer
  2026-11-11 12:07 ` [PATCH v2 1/5] wifi: brcmfmac: pcie: set the state before D0_INFORM on resume Andre Eikmeyer
@ 2026-11-11 12:07 ` Andre Eikmeyer
  2026-11-11 12:07 ` [PATCH v2 3/5] wifi: brcmfmac: pcie: probe again after a resume through firmware Andre Eikmeyer
                   ` (2 subsequent siblings)
  4 siblings, 0 replies; 6+ messages in thread
From: Andre Eikmeyer @ 2026-11-11 12:07 UTC (permalink / raw)
  To: arend.vanspriel
  Cc: linux-wireless, brcm80211, brcm80211-dev-list.pdl, linux-kernel,
	michael.reeves077, marcan, j, asahi, sebastian.reichel,
	Andre Eikmeyer

With host ready in the host capabilities, the BCM4364 firmware halts on
the doorbell that brcmf_pcie_preinit() rings, before it answers the
first ioctl. The B3 of the MacBookPro16,1 does so on every load, the B2
of the MacBookPro15,1 on some:

  brcmf_pcie_handle_mb_data D2H_MB_DATA: FW HALT
  brcmf_attach: dongle is not responding: err=-5

The BCM4377 stays silent without the capability, so it stays. Once host
ready is a capability the firmware takes the doorbell as the exit from
D3, where Broadcom's DHD driver rings it, after D0_INFORM. Ring it at
init only when host ready is not a capability.

Signed-off-by: Andre Eikmeyer <dev@deq.rocks>
---
 .../net/wireless/broadcom/brcm80211/brcmfmac/pcie.c | 13 +++++++++++--
 1 file changed, 11 insertions(+), 2 deletions(-)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
index 231a2fb89f0..fc0f23477eb 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
@@ -325,6 +325,7 @@ struct brcmf_pcie_shared_info {
 	dma_addr_t ringupd_dmahandle;
 	u8 version;
 	bool mb_via_ctl;
+	bool hostrdy;
 };
 
 #define BRCMF_OTP_MAX_PARAM_LEN 16
@@ -1599,7 +1600,13 @@ static int brcmf_pcie_preinit(struct device *dev)
 	brcmf_dbg(PCIE, "Enter\n");
 
 	brcmf_pcie_intr_enable(buspub->devinfo);
-	brcmf_pcie_hostready(buspub->devinfo);
+	/*
+	 * Once the host capabilities enable host ready, the firmware takes the
+	 * doorbell as the exit from D3 only. The BCM4364 firmware halts on it
+	 * here.
+	 */
+	if (!buspub->devinfo->shared.hostrdy)
+		brcmf_pcie_hostready(buspub->devinfo);
 
 	return 0;
 }
@@ -1806,8 +1813,10 @@ brcmf_pcie_init_share_ram_info(struct brcmf_pciedev_info *devinfo,
 			!(shared->flags & BRCMF_PCIE_SHARED_USE_MAILBOX);
 
 		host_cap = shared->version;
-		if (shared->flags & BRCMF_PCIE_SHARED_HOSTRDY_DB1)
+		if (shared->flags & BRCMF_PCIE_SHARED_HOSTRDY_DB1) {
 			host_cap |= BRCMF_HOSTCAP_H2D_ENABLE_HOSTRDY;
+			shared->hostrdy = true;
+		}
 		if (shared->flags & BRCMF_PCIE_SHARED_DAR)
 			host_cap |= BRCMF_HOSTCAP_H2D_DAR;
 		host_cap |= BRCMF_HOSTCAP_DS_NO_OOB_DW;

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

* [PATCH v2 3/5] wifi: brcmfmac: pcie: probe again after a resume through firmware
  2026-11-11 12:07 [PATCH v2 0/5] wifi: brcmfmac: pcie: S3 and deep sleep on the T2 Mac chips Andre Eikmeyer
  2026-11-11 12:07 ` [PATCH v2 1/5] wifi: brcmfmac: pcie: set the state before D0_INFORM on resume Andre Eikmeyer
  2026-11-11 12:07 ` [PATCH v2 2/5] wifi: brcmfmac: pcie: skip the host ready doorbell at init once it is a capability Andre Eikmeyer
@ 2026-11-11 12:07 ` Andre Eikmeyer
  2026-11-11 12:07 ` [PATCH v2 4/5] wifi: brcmfmac: msgbuf: send the flowring delete without waiting for tx status Andre Eikmeyer
  2026-11-11 12:07 ` [PATCH v2 5/5] wifi: brcmfmac: pcie: support inband device wake and deep sleep Andre Eikmeyer
  4 siblings, 0 replies; 6+ messages in thread
From: Andre Eikmeyer @ 2026-11-11 12:07 UTC (permalink / raw)
  To: arend.vanspriel
  Cc: linux-wireless, brcm80211, brcm80211-dev-list.pdl, linux-kernel,
	michael.reeves077, marcan, j, asahi, sebastian.reichel,
	Andre Eikmeyer

After ACPI S3 the chip comes back without its firmware, but the
interrupt mask can read back nonzero, so brcmf_pcie_pm_leave_D3()
takes the hot resume path and every command times out afterwards.
Seen on the BCM4377 and BCM4364 of the T2 Macs.

When the platform firmware handled the resume and WoWL is off, go
straight to the existing remove and re-probe path.

Signed-off-by: Andre Eikmeyer <dev@deq.rocks>
---
 .../net/wireless/broadcom/brcm80211/brcmfmac/pcie.c    | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
index fc0f23477eb..faa1e16ed4f 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
@@ -7,6 +7,7 @@
 #include <linux/module.h>
 #include <linux/firmware.h>
 #include <linux/pci.h>
+#include <linux/suspend.h>
 #include <linux/vmalloc.h>
 #include <linux/delay.h>
 #include <linux/interrupt.h>
@@ -2844,6 +2845,15 @@ static int brcmf_pcie_pm_leave_D3(struct device *dev)
 	devinfo = bus->bus_priv.pcie->devinfo;
 	brcmf_dbg(PCIE, "Enter, dev=%p, bus=%p\n", dev, bus);
 
+	/*
+	 * Without WoWL the device is powered off when the platform firmware
+	 * handles the system sleep, as with ACPI S3, and comes back without
+	 * its firmware. Its registers can still read back nonzero, so probe
+	 * it again.
+	 */
+	if (pm_resume_via_firmware() && !devinfo->wowl_enabled)
+		goto cleanup;
+
 	/* Check if device is still up and running, if so we are ready */
 	if (brcmf_pcie_read_pcie32(devinfo, devinfo->reginfo->intmask) != 0) {
 		brcmf_dbg(PCIE, "Try to wakeup device....\n");

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

* [PATCH v2 4/5] wifi: brcmfmac: msgbuf: send the flowring delete without waiting for tx status
  2026-11-11 12:07 [PATCH v2 0/5] wifi: brcmfmac: pcie: S3 and deep sleep on the T2 Mac chips Andre Eikmeyer
                   ` (2 preceding siblings ...)
  2026-11-11 12:07 ` [PATCH v2 3/5] wifi: brcmfmac: pcie: probe again after a resume through firmware Andre Eikmeyer
@ 2026-11-11 12:07 ` Andre Eikmeyer
  2026-11-11 12:07 ` [PATCH v2 5/5] wifi: brcmfmac: pcie: support inband device wake and deep sleep Andre Eikmeyer
  4 siblings, 0 replies; 6+ messages in thread
From: Andre Eikmeyer @ 2026-11-11 12:07 UTC (permalink / raw)
  To: arend.vanspriel
  Cc: linux-wireless, brcm80211, brcm80211-dev-list.pdl, linux-kernel,
	michael.reeves077, marcan, j, asahi, sebastian.reichel,
	Andre Eikmeyer

The BCM4377 firmware returns the tx status of queued packets only when
it gets the flowring delete request. brcmf_msgbuf_delete_flowring()
waits up to 100 ms for them before it sends the request, so every
disconnect logs a timeout, and the cleared counter goes below zero when
the status arrives.

The race the wait was added for in commit 0fa24196e425 ("wifi: brcmfmac:
fix continuous 802.1x tx pending timeout error") is covered by
RING_CLOSING since commit 09be7546a602 ("wifi: brcmfmac: fix scheduling
while atomic issue when deleting flowring"), and late tx status still
releases the packet. Send the delete right away and reset the counter
when the ring is created.

Signed-off-by: Andre Eikmeyer <dev@deq.rocks>
---
 .../broadcom/brcm80211/brcmfmac/msgbuf.c      | 23 ++++++++-----------
 1 file changed, 10 insertions(+), 13 deletions(-)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.c
index 8e5c697e7f1..a9ccf647f9a 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.c
@@ -73,7 +73,6 @@
 #define BRCMF_MSGBUF_TRICKLE_TXWORKER_THRS	32
 #define BRCMF_MSGBUF_UPDATE_RX_PTR_THRS		48
 
-#define BRCMF_MAX_TXSTATUS_WAIT_RETRIES		10
 
 struct msgbuf_common_hdr {
 	u8				msgtype;
@@ -645,6 +644,7 @@ brcmf_msgbuf_flowring_create_worker(struct brcmf_msgbuf *msgbuf,
 	brcmf_commonring_config(msgbuf->flowrings[flowid],
 				BRCMF_H2D_TXFLOWRING_MAX_ITEM,
 				BRCMF_H2D_TXFLOWRING_ITEMSIZE, dma_buf);
+	atomic_set(&msgbuf->flowrings[flowid]->outstanding_tx, 0);
 
 	commonring = msgbuf->commonrings[BRCMF_H2D_MSGRING_CONTROL_SUBMIT];
 	brcmf_commonring_lock(commonring);
@@ -1330,6 +1330,10 @@ brcmf_msgbuf_process_flow_ring_delete_response(struct brcmf_msgbuf *msgbuf,
 	}
 	brcmf_dbg(MSGBUF, "Flowring %d Delete response status %d\n", flowid,
 		  status);
+	if (atomic_read(&msgbuf->flowrings[flowid]->outstanding_tx))
+		brcmf_dbg(MSGBUF, "Flowring %d deleted with %d tx status outstanding\n",
+			  flowid,
+			  atomic_read(&msgbuf->flowrings[flowid]->outstanding_tx));
 
 	brcmf_msgbuf_remove_flowring(msgbuf, flowid);
 }
@@ -1471,23 +1475,16 @@ void brcmf_msgbuf_delete_flowring(struct brcmf_pub *drvr, u16 flowid)
 	void *ret_ptr;
 	u8 ifidx;
 	int err;
-	int retry = BRCMF_MAX_TXSTATUS_WAIT_RETRIES;
 
-	/* make sure it is not in txflow */
+	/*
+	 * Make sure txflow posts nothing more to the ring. The firmware returns
+	 * the tx status of the posted packets when it deletes the ring, before
+	 * it sends the delete response.
+	 */
 	brcmf_commonring_lock(commonring_del);
 	flow->rings[flowid]->status = RING_CLOSING;
 	brcmf_commonring_unlock(commonring_del);
 
-	/* wait for commonring txflow finished */
-	while (retry && atomic_read(&commonring_del->outstanding_tx)) {
-		usleep_range(5000, 10000);
-		retry--;
-	}
-	if (!retry) {
-		brcmf_err("timed out waiting for txstatus\n");
-		atomic_set(&commonring_del->outstanding_tx, 0);
-	}
-
 	/* no need to submit if firmware can not be reached */
 	if (drvr->bus_if->state != BRCMF_BUS_UP) {
 		brcmf_dbg(MSGBUF, "bus down, flowring will be removed\n");

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

* [PATCH v2 5/5] wifi: brcmfmac: pcie: support inband device wake and deep sleep
  2026-11-11 12:07 [PATCH v2 0/5] wifi: brcmfmac: pcie: S3 and deep sleep on the T2 Mac chips Andre Eikmeyer
                   ` (3 preceding siblings ...)
  2026-11-11 12:07 ` [PATCH v2 4/5] wifi: brcmfmac: msgbuf: send the flowring delete without waiting for tx status Andre Eikmeyer
@ 2026-11-11 12:07 ` Andre Eikmeyer
  4 siblings, 0 replies; 6+ messages in thread
From: Andre Eikmeyer @ 2026-11-11 12:07 UTC (permalink / raw)
  To: arend.vanspriel
  Cc: linux-wireless, brcm80211, brcm80211-dev-list.pdl, linux-kernel,
	michael.reeves077, marcan, j, asahi, sebastian.reichel,
	Andre Eikmeyer

The BCM4364 and BCM4377 firmware report inband deep sleep in the PCIe
shared flags. The driver only advertises that it has no out-of-band
device wake, which leaves deep sleep disabled. The firmware then stays
awake and keeps its latency tolerance short enough to hold the package
out of PC10.

Advertise inband device wake when the firmware supports it and follow
the handshake of the firmware. The device asks for deep sleep while
device wake is deasserted, the host acknowledges it once it has nothing
to send, asserts device wake before it uses the rings again and waits
for the exit note, and deasserts device wake after 200 ms without ring
writes. Hooks around the ring lock cover every host to device ring
writer. A system suspend asserts device wake before D3_INFORM and
acknowledges no deep sleep request until the resume.

Tested on a MacBookPro15,1 (BCM4364) and a MacBookAir9,1 (BCM4377). The
firmware enters and leaves deep sleep about 40 times a minute at idle
with Wi-Fi associated, traffic and downloads work, and the package
reaches PC10 without ignoring the latency tolerance of the Wi-Fi port.

Signed-off-by: Andre Eikmeyer <dev@deq.rocks>
---
 .../broadcom/brcm80211/brcmfmac/commonring.c  |  14 ++
 .../broadcom/brcm80211/brcmfmac/commonring.h  |   5 +
 .../broadcom/brcm80211/brcmfmac/pcie.c        | 233 +++++++++++++++++-
 3 files changed, 249 insertions(+), 3 deletions(-)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/commonring.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/commonring.c
index e44236cb210..881505b992c 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/commonring.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/commonring.c
@@ -28,6 +28,16 @@ void brcmf_commonring_register_cb(struct brcmf_commonring *commonring,
 }
 
 
+/* Both run outside the ring lock and may sleep */
+void brcmf_commonring_register_pm_cb(struct brcmf_commonring *commonring,
+				     void (*cr_wake)(void *ctx),
+				     void (*cr_idle)(void *ctx))
+{
+	commonring->cr_wake = cr_wake;
+	commonring->cr_idle = cr_idle;
+}
+
+
 void brcmf_commonring_config(struct brcmf_commonring *commonring, u16 depth,
 			     u16 item_len, void *buf_addr)
 {
@@ -53,6 +63,8 @@ void brcmf_commonring_lock(struct brcmf_commonring *commonring)
 {
 	unsigned long flags;
 
+	if (commonring->cr_wake)
+		commonring->cr_wake(commonring->cr_ctx);
 	spin_lock_irqsave(&commonring->lock, flags);
 	commonring->flags = flags;
 }
@@ -62,6 +74,8 @@ void brcmf_commonring_unlock(struct brcmf_commonring *commonring)
 		__releases(&commonring->lock)
 {
 	spin_unlock_irqrestore(&commonring->lock, commonring->flags);
+	if (commonring->cr_idle)
+		commonring->cr_idle(commonring->cr_ctx);
 }
 
 
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/commonring.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/commonring.h
index 7fb11f4823e..a27be6d7b17 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/commonring.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/commonring.h
@@ -20,6 +20,8 @@ struct brcmf_commonring {
 	int (*cr_update_wptr)(void *ctx);
 	int (*cr_write_rptr)(void *ctx);
 	int (*cr_write_wptr)(void *ctx);
+	void (*cr_wake)(void *ctx);
+	void (*cr_idle)(void *ctx);
 
 	void *cr_ctx;
 
@@ -38,6 +40,9 @@ void brcmf_commonring_register_cb(struct brcmf_commonring *commonring,
 				  int (*cr_update_wptr)(void *ctx),
 				  int (*cr_write_rptr)(void *ctx),
 				  int (*cr_write_wptr)(void *ctx), void *ctx);
+void brcmf_commonring_register_pm_cb(struct brcmf_commonring *commonring,
+				     void (*cr_wake)(void *ctx),
+				     void (*cr_idle)(void *ctx));
 void brcmf_commonring_config(struct brcmf_commonring *commonring, u16 depth,
 			     u16 item_len, void *buf_addr);
 void brcmf_commonring_lock(struct brcmf_commonring *commonring);
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
index faa1e16ed4f..f4a5ba9f9fe 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
@@ -222,10 +222,12 @@ static const struct brcmf_firmware_mapping brcmf_pcie_fwnames[] = {
 #define BRCMF_PCIE_SHARED_DMA_2B_IDX		0x100000
 #define BRCMF_PCIE_SHARED_USE_MAILBOX		0x2000000
 #define BRCMF_PCIE_SHARED_HOSTRDY_DB1		0x10000000
+#define BRCMF_PCIE_SHARED_INBAND_DS		0x40000000
 #define BRCMF_PCIE_SHARED_DAR			0x80000000
 
 #define BRCMF_HOSTCAP_H2D_ENABLE_HOSTRDY	0x400
 #define BRCMF_HOSTCAP_DS_NO_OOB_DW		0x1000
+#define BRCMF_HOSTCAP_DS_INBAND_DW		0x2000
 #define BRCMF_HOSTCAP_H2D_DAR			0x10000
 
 #define BRCMF_PCIE_FLAGS_HTOD_SPLIT		0x4000
@@ -274,6 +276,11 @@ static const struct brcmf_firmware_mapping brcmf_pcie_fwnames[] = {
 #define BRCMF_H2D_HOST_DS_ACK			0x00000002
 #define BRCMF_H2D_HOST_D0_INFORM_IN_USE		0x00000008
 #define BRCMF_H2D_HOST_D0_INFORM		0x00000010
+#define BRCMF_H2D_HOST_DW_DEASSERT		0x00000020
+#define BRCMF_H2D_HOST_DW_ASSERT		0x00000040
+
+#define BRCMF_PCIE_DS_IDLE_MS			200
+#define BRCMF_PCIE_DS_EXIT_TIMEOUT		msecs_to_jiffies(500)
 
 #define BRCMF_PCIE_MBDATA_TIMEOUT		msecs_to_jiffies(2000)
 
@@ -338,6 +345,15 @@ struct brcmf_otp_params {
 	bool valid;
 };
 
+enum brcmf_pcie_ds_state {
+	BRCMF_PCIE_DS_NONE,
+	BRCMF_PCIE_DS_ALLOWED,
+	BRCMF_PCIE_DS_SLEEP_PEND,
+	BRCMF_PCIE_DS_SLEEP,
+	BRCMF_PCIE_DS_EXIT_WAIT,
+	BRCMF_PCIE_DS_AWAKE,
+};
+
 struct brcmf_pciedev_info {
 	enum brcmf_pcie_state state;
 	bool in_irq;
@@ -371,6 +387,16 @@ struct brcmf_pciedev_info {
 	struct brcmf_mp_device *settings;
 	struct brcmf_otp_params otp;
 	bool fwseed;
+	/* protects ds_state, ds_active and ds_suspend */
+	spinlock_t ds_lock;
+	enum brcmf_pcie_ds_state ds_state;
+	int ds_active;
+	bool ds_suspend;
+	/* serializes the deep sleep mailbox messages */
+	struct mutex ds_mutex;
+	struct task_struct *ds_sender;
+	wait_queue_head_t ds_wait;
+	struct delayed_work ds_deassert_work;
 #ifdef DEBUG
 	u32 console_interval;
 	bool console_active;
@@ -892,17 +918,184 @@ brcmf_pcie_send_mb_data(struct brcmf_pciedev_info *devinfo, u32 htod_mb_data)
 }
 
 
+static void brcmf_pcie_ds_send(struct brcmf_pciedev_info *devinfo, u32 data)
+{
+	mutex_lock(&devinfo->ds_mutex);
+	WRITE_ONCE(devinfo->ds_sender, current);
+	brcmf_pcie_send_mb_data(devinfo, data);
+	WRITE_ONCE(devinfo->ds_sender, NULL);
+	mutex_unlock(&devinfo->ds_mutex);
+}
+
+/* The handshake messages themselves go through the hooked rings */
+static bool brcmf_pcie_ds_skip(struct brcmf_pciedev_info *devinfo)
+{
+	return READ_ONCE(devinfo->ds_state) == BRCMF_PCIE_DS_NONE ||
+	       READ_ONCE(devinfo->ds_sender) == current;
+}
+
+static bool brcmf_pcie_ds_idle_locked(struct brcmf_pciedev_info *devinfo)
+{
+	lockdep_assert_held(&devinfo->ds_lock);
+
+	if (devinfo->ds_active || devinfo->ds_suspend)
+		return false;
+
+	if (devinfo->ds_state == BRCMF_PCIE_DS_SLEEP_PEND) {
+		devinfo->ds_state = BRCMF_PCIE_DS_SLEEP;
+		return true;
+	}
+	if (devinfo->ds_state == BRCMF_PCIE_DS_AWAKE)
+		mod_delayed_work(system_dfl_wq, &devinfo->ds_deassert_work,
+				 msecs_to_jiffies(BRCMF_PCIE_DS_IDLE_MS));
+	return false;
+}
+
+static void brcmf_pcie_ds_wake(struct brcmf_pciedev_info *devinfo)
+{
+	bool assert = false, wait;
+
+	if (brcmf_pcie_ds_skip(devinfo))
+		return;
+
+	spin_lock(&devinfo->ds_lock);
+	devinfo->ds_active++;
+	if (devinfo->ds_state == BRCMF_PCIE_DS_SLEEP) {
+		devinfo->ds_state = BRCMF_PCIE_DS_EXIT_WAIT;
+		assert = true;
+	}
+	wait = devinfo->ds_state == BRCMF_PCIE_DS_EXIT_WAIT;
+	spin_unlock(&devinfo->ds_lock);
+
+	if (assert) {
+		brcmf_dbg(PCIE, "deep sleep: assert device wake\n");
+		brcmf_pcie_ds_send(devinfo, BRCMF_H2D_HOST_DW_ASSERT);
+	}
+	if (!wait)
+		return;
+
+	/* The exit note arrives through the interrupt thread itself */
+	if (devinfo->in_irq) {
+		usleep_range(1000, 1100);
+		return;
+	}
+	if (wait_event_timeout(devinfo->ds_wait,
+			       READ_ONCE(devinfo->ds_state) !=
+			       BRCMF_PCIE_DS_EXIT_WAIT,
+			       BRCMF_PCIE_DS_EXIT_TIMEOUT))
+		return;
+
+	brcmf_err(dev_get_drvdata(&devinfo->pdev->dev),
+		  "no deep sleep exit after device wake\n");
+	spin_lock(&devinfo->ds_lock);
+	if (devinfo->ds_state == BRCMF_PCIE_DS_EXIT_WAIT)
+		devinfo->ds_state = BRCMF_PCIE_DS_AWAKE;
+	spin_unlock(&devinfo->ds_lock);
+}
+
+static void brcmf_pcie_ds_idle(struct brcmf_pciedev_info *devinfo)
+{
+	bool ack;
+
+	if (brcmf_pcie_ds_skip(devinfo))
+		return;
+
+	spin_lock(&devinfo->ds_lock);
+	devinfo->ds_active--;
+	ack = brcmf_pcie_ds_idle_locked(devinfo);
+	spin_unlock(&devinfo->ds_lock);
+
+	if (ack) {
+		brcmf_dbg(PCIE, "deep sleep: ack pending request\n");
+		brcmf_pcie_ds_send(devinfo, BRCMF_H2D_HOST_DS_ACK);
+	}
+}
+
+static void brcmf_pcie_ds_deassert_worker(struct work_struct *work)
+{
+	struct brcmf_pciedev_info *devinfo =
+		container_of(work, struct brcmf_pciedev_info,
+			     ds_deassert_work.work);
+	bool deassert = false;
+
+	spin_lock(&devinfo->ds_lock);
+	if (devinfo->ds_state == BRCMF_PCIE_DS_AWAKE && !devinfo->ds_active &&
+	    !devinfo->ds_suspend) {
+		devinfo->ds_state = BRCMF_PCIE_DS_ALLOWED;
+		deassert = true;
+	}
+	spin_unlock(&devinfo->ds_lock);
+
+	if (deassert) {
+		brcmf_dbg(PCIE, "deep sleep: deassert device wake\n");
+		brcmf_pcie_ds_send(devinfo, BRCMF_H2D_HOST_DW_DEASSERT);
+	}
+}
+
+static void brcmf_pcie_ds_enter_req(struct brcmf_pciedev_info *devinfo)
+{
+	bool ack = false;
+
+	if (devinfo->ds_state == BRCMF_PCIE_DS_NONE) {
+		brcmf_pcie_send_mb_data(devinfo, BRCMF_H2D_HOST_DS_ACK);
+		return;
+	}
+
+	spin_lock(&devinfo->ds_lock);
+	if (devinfo->ds_state == BRCMF_PCIE_DS_ALLOWED) {
+		devinfo->ds_state = BRCMF_PCIE_DS_SLEEP_PEND;
+		ack = brcmf_pcie_ds_idle_locked(devinfo);
+	} else {
+		brcmf_dbg(PCIE, "deep sleep request in state %d\n",
+			  devinfo->ds_state);
+	}
+	spin_unlock(&devinfo->ds_lock);
+
+	if (ack)
+		brcmf_pcie_ds_send(devinfo, BRCMF_H2D_HOST_DS_ACK);
+}
+
+static void brcmf_pcie_ds_exit_note(struct brcmf_pciedev_info *devinfo)
+{
+	if (devinfo->ds_state == BRCMF_PCIE_DS_NONE)
+		return;
+
+	spin_lock(&devinfo->ds_lock);
+	if (devinfo->ds_state == BRCMF_PCIE_DS_EXIT_WAIT ||
+	    devinfo->ds_state == BRCMF_PCIE_DS_SLEEP) {
+		devinfo->ds_state = BRCMF_PCIE_DS_AWAKE;
+		brcmf_pcie_ds_idle_locked(devinfo);
+	}
+	spin_unlock(&devinfo->ds_lock);
+	wake_up(&devinfo->ds_wait);
+}
+
+static void brcmf_pcie_ring_ds_wake(void *ctx)
+{
+	struct brcmf_pcie_ringbuf *ring = ctx;
+
+	brcmf_pcie_ds_wake(ring->devinfo);
+}
+
+static void brcmf_pcie_ring_ds_idle(void *ctx)
+{
+	struct brcmf_pcie_ringbuf *ring = ctx;
+
+	brcmf_pcie_ds_idle(ring->devinfo);
+}
+
 static void brcmf_pcie_handle_mb_data(struct brcmf_pciedev_info *devinfo,
 				      u32 dtoh_mb_data)
 {
 	brcmf_dbg(PCIE, "D2H_MB_DATA: 0x%04x\n", dtoh_mb_data);
 	if (dtoh_mb_data & BRCMF_D2H_DEV_DS_ENTER_REQ)  {
 		brcmf_dbg(PCIE, "D2H_MB_DATA: DEEP SLEEP REQ\n");
-		brcmf_pcie_send_mb_data(devinfo, BRCMF_H2D_HOST_DS_ACK);
-		brcmf_dbg(PCIE, "D2H_MB_DATA: sent DEEP SLEEP ACK\n");
+		brcmf_pcie_ds_enter_req(devinfo);
 	}
-	if (dtoh_mb_data & BRCMF_D2H_DEV_DS_EXIT_NOTE)
+	if (dtoh_mb_data & BRCMF_D2H_DEV_DS_EXIT_NOTE) {
 		brcmf_dbg(PCIE, "D2H_MB_DATA: DEEP SLEEP EXIT\n");
+		brcmf_pcie_ds_exit_note(devinfo);
+	}
 	if (dtoh_mb_data & BRCMF_D2H_DEV_D3_ACK) {
 		brcmf_dbg(PCIE, "D2H_MB_DATA: D3 ACK\n");
 		devinfo->mbdata_completed = true;
@@ -1308,6 +1501,10 @@ brcmf_pcie_alloc_dma_and_ring(struct brcmf_pciedev_info *devinfo, u32 ring_id,
 				     brcmf_pcie_ring_mb_update_wptr,
 				     brcmf_pcie_ring_mb_write_rptr,
 				     brcmf_pcie_ring_mb_write_wptr, ring);
+	if (ring_id < BRCMF_NROF_H2D_COMMON_MSGRINGS)
+		brcmf_commonring_register_pm_cb(&ring->commonring,
+						brcmf_pcie_ring_ds_wake,
+						brcmf_pcie_ring_ds_idle);
 
 	return (ring);
 }
@@ -1501,6 +1698,9 @@ static int brcmf_pcie_init_ringbuffers(struct brcmf_pciedev_info *devinfo)
 					     brcmf_pcie_ring_mb_write_rptr,
 					     brcmf_pcie_ring_mb_write_wptr,
 					     ring);
+		brcmf_commonring_register_pm_cb(&ring->commonring,
+						brcmf_pcie_ring_ds_wake,
+						brcmf_pcie_ring_ds_idle);
 		ring->w_idx_addr = h2d_w_idx_ptr;
 		ring->r_idx_addr = h2d_r_idx_ptr;
 		h2d_w_idx_ptr += idx_offset;
@@ -1821,6 +2021,10 @@ brcmf_pcie_init_share_ram_info(struct brcmf_pciedev_info *devinfo,
 		if (shared->flags & BRCMF_PCIE_SHARED_DAR)
 			host_cap |= BRCMF_HOSTCAP_H2D_DAR;
 		host_cap |= BRCMF_HOSTCAP_DS_NO_OOB_DW;
+		if (shared->flags & BRCMF_PCIE_SHARED_INBAND_DS) {
+			host_cap |= BRCMF_HOSTCAP_DS_INBAND_DW;
+			devinfo->ds_state = BRCMF_PCIE_DS_ALLOWED;
+		}
 
 		brcmf_pcie_write_tcm32(devinfo, sharedram_addr +
 				       BRCMF_SHARED_HOST_CAP_OFFSET,
@@ -2630,6 +2834,11 @@ brcmf_pcie_probe(struct pci_dev *pdev, const struct pci_device_id *id)
 		return ret;
 
 	devinfo->pdev = pdev;
+	spin_lock_init(&devinfo->ds_lock);
+	mutex_init(&devinfo->ds_mutex);
+	init_waitqueue_head(&devinfo->ds_wait);
+	INIT_DELAYED_WORK(&devinfo->ds_deassert_work,
+			  brcmf_pcie_ds_deassert_worker);
 	pcie_bus_dev = NULL;
 	devinfo->ci = brcmf_chip_attach(devinfo, pdev->device,
 					&brcmf_pcie_buscore_ops);
@@ -2757,6 +2966,8 @@ brcmf_pcie_remove(struct pci_dev *pdev)
 		return;
 
 	devinfo = bus->bus_priv.pcie->devinfo;
+	WRITE_ONCE(devinfo->ds_state, BRCMF_PCIE_DS_NONE);
+	cancel_delayed_work_sync(&devinfo->ds_deassert_work);
 	brcmf_pcie_bus_console_read(devinfo, false);
 	brcmf_pcie_fwcon_timer(devinfo, false);
 
@@ -2811,6 +3022,15 @@ static int brcmf_pcie_pm_enter_D3(struct device *dev)
 	brcmf_pcie_fwcon_timer(devinfo, false);
 	brcmf_bus_change_state(bus, BRCMF_BUS_DOWN);
 
+	if (devinfo->ds_state != BRCMF_PCIE_DS_NONE) {
+		spin_lock(&devinfo->ds_lock);
+		devinfo->ds_suspend = true;
+		spin_unlock(&devinfo->ds_lock);
+		cancel_delayed_work_sync(&devinfo->ds_deassert_work);
+		brcmf_pcie_ds_wake(devinfo);
+		brcmf_pcie_ds_idle(devinfo);
+	}
+
 	devinfo->mbdata_completed = false;
 	brcmf_pcie_send_mb_data(devinfo, BRCMF_H2D_HOST_D3_INFORM);
 
@@ -2861,6 +3081,13 @@ static int brcmf_pcie_pm_leave_D3(struct device *dev)
 		if (brcmf_pcie_send_mb_data(devinfo, BRCMF_H2D_HOST_D0_INFORM))
 			goto cleanup;
 		brcmf_dbg(PCIE, "Hot resume, continue....\n");
+		if (devinfo->ds_state != BRCMF_PCIE_DS_NONE) {
+			spin_lock(&devinfo->ds_lock);
+			devinfo->ds_suspend = false;
+			devinfo->ds_state = BRCMF_PCIE_DS_AWAKE;
+			brcmf_pcie_ds_idle_locked(devinfo);
+			spin_unlock(&devinfo->ds_lock);
+		}
 		brcmf_pcie_select_core(devinfo, BCMA_CORE_PCIE2);
 		brcmf_bus_change_state(bus, BRCMF_BUS_UP);
 		brcmf_pcie_intr_enable(devinfo);

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

end of thread, other threads:[~2026-11-11 12:08 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-11 12:07 [PATCH v2 0/5] wifi: brcmfmac: pcie: S3 and deep sleep on the T2 Mac chips Andre Eikmeyer
2026-11-11 12:07 ` [PATCH v2 1/5] wifi: brcmfmac: pcie: set the state before D0_INFORM on resume Andre Eikmeyer
2026-11-11 12:07 ` [PATCH v2 2/5] wifi: brcmfmac: pcie: skip the host ready doorbell at init once it is a capability Andre Eikmeyer
2026-11-11 12:07 ` [PATCH v2 3/5] wifi: brcmfmac: pcie: probe again after a resume through firmware Andre Eikmeyer
2026-11-11 12:07 ` [PATCH v2 4/5] wifi: brcmfmac: msgbuf: send the flowring delete without waiting for tx status Andre Eikmeyer
2026-11-11 12:07 ` [PATCH v2 5/5] wifi: brcmfmac: pcie: support inband device wake and deep sleep Andre Eikmeyer

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

all inboxes | Powered by JetHome®