Skip to contentSkip to conversation

Bluetooth: hci_bcm4377: resume after ACPI S3 and ASPM

PATCH New Andre Eikmeyer 1 participant 4 messages Nov 11

Subsystems (2)
lore
Share & tools
lore
#0 lore raw

v2 cover Bluetooth: hci_bcm4377: resume after ACPI S3 and ASPM

After ACPI S3 the Bluetooth function of the BCM4377 comes back without its firmware. Patch 2 probes it again after such a resume.

When the previous controller is still being released, the re-probe registers the new one under another index, which runs into the probe return check fixed in patch 1. Patch 1 is a fix on its own and goes first.

Patch 3 is new in v2. Because of a hardware erratum the driver keeps ASPM disabled on the whole link to the Wi-Fi and Bluetooth chip for as long as it is bound, which holds the CPU package at PC3. Patch 3 disables ASPM only while the firmware boots and restores it once the firmware runs, also after the re-probe from patch 2.

The Wi-Fi function of the same chip needs the same after S3 and goes to linux-wireless in "wifi: brcmfmac: pcie: S3 suspend and resume on the T2 Mac chips".

Successfully ested on MacBookAir9,1 and MacBookPro15,4.

v1 [1] has not received any feedback yet.

Changes in v2:
- Add patch 3, keep ASPM off only while the firmware boots
- Patches 1 and 2 are unchanged

[1] https://lore.kernel.org/all/[email protected]/

Andre Eikmeyer (3):
Bluetooth: hci_bcm4377: treat only negative hci_register_dev() returns
as errors
Bluetooth: hci_bcm4377: probe again after a resume through firmware
Bluetooth: hci_bcm4377: keep ASPM off only while the firmware boots

 drivers/bluetooth/hci_bcm4377.c | 93 +++++++++++++++++++++++++++++----
 1 file changed, 83 insertions(+), 10 deletions(-)

base-commit: 3f425f95ba3923d605f6a8b382e8fcdc59d479db

#1 lore raw

PATCH v2 1/3 Bluetooth: hci_bcm4377: treat only negative hci_register_dev() returns as errors

If another controller already holds hci0, a USB Bluetooth dongle for example, the BCM4377 is registered as hci1 and probe returns 1. The PCI core takes that as success, but the devm action that unregisters the controller is never added, so it stays registered after unbind while its driver data is freed.

hci_register_dev() returns the index of the new controller on success. Check for a negative return, as the other drivers do.

Fixes 8a06127602de ("Bluetooth: hci_bcm4377: Add new driver for BCM4377 PCIe boards")
---
 drivers/bluetooth/hci_bcm4377.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/bluetooth/hci_bcm4377.c +1 -1
index 66d49b47154..da0a48c2be3 100644
@@ -2410,7 +2410,7 @@ static int bcm4377_probe(struct pci_dev *pdev, const struct pci_device_id *id)
 		return ret;
 
 	ret = hci_register_dev(hdev);
-	if (ret)
+	if (ret < 0)
 		return ret;
 	return devm_add_action_or_reset(&pdev->dev, bcm4377_hci_unregister_dev,
 					hdev);
#2 lore raw

PATCH v2 2/3 Bluetooth: hci_bcm4377: probe again after a resume through firmware

In reply to Andre Eikmeyer

The BCM4377 is powered off during ACPI S3 and loses its firmware. On resume the driver only unquiesces it, so every command times out.

When the platform firmware handled the resume, probe the controller again from a work item and skip the ring teardown messages to the lost firmware. Suspend to idle keeps the quiesce path.

---
 drivers/bluetooth/hci_bcm4377.c | 59 +++++++++++++++++++++++++++++++++
 1 file changed, 59 insertions(+)
drivers/bluetooth/hci_bcm4377.c +59 -0
index da0a48c2be3..4e3630774c0 100644
@@ -16,6 +16,9 @@
 #include <linux/of.h>
 #include <linux/pci.h>
 #include <linux/printk.h>
+#include <linux/slab.h>
+#include <linux/suspend.h>
+#include <linux/workqueue.h>
 
 #include <linux/unaligned.h>
 
@@ -566,6 +569,9 @@ struct bcm4377_data {
 	struct pci_dev *pdev;
 	struct hci_dev *hdev;
 
+	/* set when the controller lost its firmware during system sleep */
+	bool fw_lost;
+
 	void __iomem *bar0;
 	void __iomem *bar2;
 
@@ -1017,6 +1023,11 @@ static int bcm4377_destroy_completion_ring(struct bcm4377_data *bcm4377,
 	struct bcm4377_destroy_completion_ring_msg msg;
 	int ret;
 
+	if (bcm4377->fw_lost) {
+		ring->enabled = false;
+		return 0;
+	}
+
 	memset(&msg, 0, sizeof(msg));
 	msg.msg_type = BCM4377_CONTROL_MSG_DESTROY_COMPLETION_RING;
 	msg.ring_id = cpu_to_le16(ring->ring_id);
@@ -1107,6 +1118,11 @@ static int bcm4377_destroy_transfer_ring(struct bcm4377_data *bcm4377,
 	struct bcm4377_destroy_transfer_ring_msg msg;
 	int ret;
 
+	if (bcm4377->fw_lost) {
+		ring->enabled = false;
+		return 0;
+	}
+
 	memset(&msg, 0, sizeof(msg));
 	msg.msg_type = BCM4377_CONTROL_MSG_DESTROY_XFER_RING;
 	msg.ring_id = cpu_to_le16(ring->ring_id);
@@ -2432,11 +2448,54 @@ static int bcm4377_suspend(struct device *dev)
 	return 0;
 }
 
+struct bcm4377_reprobe {
+	struct work_struct work;
+	struct pci_dev *pdev;
+};
+
+static void bcm4377_reprobe_work(struct work_struct *work)
+{
+	struct bcm4377_reprobe *reprobe =
+		container_of(work, struct bcm4377_reprobe, work);
+	int ret;
+
+	/*
+	 * Probing stays blocked until the system resume completes, the driver
+	 * core then retries the deferred probe.
+	 */
+	ret = device_reprobe(&reprobe->pdev->dev);
+	if (ret && ret != -EPROBE_DEFER)
+		dev_err(&reprobe->pdev->dev, "re-probe after resume failed (%d)\n",
+			ret);
+
+	pci_dev_put(reprobe->pdev);
+	kfree(reprobe);
+}
+
 static int bcm4377_resume(struct device *dev)
 {
 	struct pci_dev *pdev = to_pci_dev(dev);
 	struct bcm4377_data *bcm4377 = pci_get_drvdata(pdev);
 
+	/*
+	 * The controller is powered off when the platform firmware handles
+	 * the system sleep, as with ACPI S3 on x86 Macs, and comes back
+	 * without its firmware. Probe it again.
+	 */
+	if (pm_resume_via_firmware()) {
+		struct bcm4377_reprobe *reprobe;
+
+		reprobe = kzalloc_obj(*reprobe);
+		if (!reprobe)
+			return -ENOMEM;
+
+		bcm4377->fw_lost = true;
+		reprobe->pdev = pci_dev_get(pdev);
+		INIT_WORK(&reprobe->work, bcm4377_reprobe_work);
+		schedule_work(&reprobe->work);
+		return 0;
+	}
+
 	iowrite32(BCM4377_BAR0_SLEEP_CONTROL_UNQUIESCE,
 		  bcm4377->bar0 + BCM4377_BAR0_SLEEP_CONTROL);
 
#3 lore raw

PATCH v2 3/3 Bluetooth: hci_bcm4377: keep ASPM off only while the firmware boots

In reply to Andre Eikmeyer

The BCM4377 needs ASPM disabled because of a hardware erratum, and the driver disables it on the whole link for as long as the device is bound. On the MacBookAir9,1 this keeps the link to the Wi-Fi and Bluetooth chip in L0, and the package does not go deeper than PC3.

Disable ASPM only in this function's Link Control register before the function level reset and the firmware boot, and restore the saved value once the firmware runs. A non-ARI Multi-Function Device enables only the ASPM states enabled in all of its functions, so the link stays in L0 until then.

Tested on a MacBookAir9,1: with ASPM L1 enabled by the PCI core the link returns to L1 after probe and after the re-probe that follows ACPI S3, Bluetooth and Wi-Fi keep working, and the package reaches PC8 instead of PC3 at idle.

---
 drivers/bluetooth/hci_bcm4377.c | 32 +++++++++++++++++++++++---------
 1 file changed, 23 insertions(+), 9 deletions(-)
drivers/bluetooth/hci_bcm4377.c +23 -9
index 4e3630774c0..0d2103cfd9b 100644
@@ -497,7 +497,8 @@ struct bcm4377_data;
  * clear_pciecfg_subsystem_ctrl_bit19: Set to true if bit 19 in the
  *                                     vendor-specific subsystem control
  *                                     register has to be cleared
- * disable_aspm: Set to true if ASPM must be disabled due to hardware errata
+ * disable_aspm: Set to true if ASPM must be disabled while the firmware is
+ *               loaded and booted due to hardware errata
  * broken_ext_scan: Set to true if the chip erroneously claims to support
  *                  extended scanning
  * broken_mws_transport_config: Set to true if the chip erroneously claims to
@@ -572,6 +573,9 @@ struct bcm4377_data {
 	/* set when the controller lost its firmware during system sleep */
 	bool fw_lost;
 
+	/* Link Control value saved while ASPM is disabled for the boot */
+	u16 link_ctl;
+
 	void __iomem *bar0;
 	void __iomem *bar2;
 
@@ -2260,20 +2264,27 @@ static int bcm4377_probe_of(struct bcm4377_data *bcm4377)
 	return 0;
 }
 
+/*
+ * ASPM is disabled in this function's Link Control register only. A
+ * non-ARI Multi-Function Device enables only the ASPM states enabled in
+ * all of its functions, so the link stays in L0 until it is restored.
+ */
 static void bcm4377_disable_aspm(struct bcm4377_data *bcm4377)
 {
-	pci_disable_link_state(bcm4377->pdev,
-			       PCIE_LINK_STATE_L0S | PCIE_LINK_STATE_L1);
-
-	/*
-	 * pci_disable_link_state can fail if either CONFIG_PCIEASPM is disabled
-	 * or if the BIOS hasn't handed over control to us. We must *always*
-	 * disable ASPM for this device due to hardware errata though.
-	 */
+	pcie_capability_read_word(bcm4377->pdev, PCI_EXP_LNKCTL,
+				  &bcm4377->link_ctl);
 	pcie_capability_clear_word(bcm4377->pdev, PCI_EXP_LNKCTL,
 				   PCI_EXP_LNKCTL_ASPMC);
 }
 
+static void bcm4377_restore_aspm(struct bcm4377_data *bcm4377)
+{
+	pcie_capability_clear_and_set_word(bcm4377->pdev, PCI_EXP_LNKCTL,
+					   PCI_EXP_LNKCTL_ASPMC,
+					   bcm4377->link_ctl &
+					   PCI_EXP_LNKCTL_ASPMC);
+}
+
 static void bcm4377_pci_free_irq_vectors(void *data)
 {
 	pci_free_irq_vectors(data);
@@ -2425,6 +2436,9 @@ static int bcm4377_probe(struct pci_dev *pdev, const struct pci_device_id *id)
 	if (ret)
 		return ret;
 
+	if (bcm4377->hw->disable_aspm)
+		bcm4377_restore_aspm(bcm4377);
+
 	ret = hci_register_dev(hdev);
 	if (ret < 0)
 		return ret;