Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions MAINTAINERS
Original file line number Diff line number Diff line change
Expand Up @@ -2769,6 +2769,7 @@ F: Documentation/hwmon/macsmc-hwmon.rst
F: Documentation/power/apple-s2idle.rst
F: arch/arm64/boot/dts/apple/
F: drivers/bluetooth/hci_bcm4377.c
F: tools/asahi/bcm4377-sleep-config-tests.*
F: drivers/clk/clk-apple-nco.c
F: drivers/cpufreq/apple-soc-cpufreq.c
F: drivers/dma/apple-admac.c
Expand Down
75 changes: 67 additions & 8 deletions drivers/bluetooth/hci_bcm4377.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@
#include <linux/of.h>
#include <linux/wait.h>
#include <linux/pci.h>
#include <linux/soc/apple/radio-sleep.h>
#include <linux/printk.h>
#include <linux/property.h>
#include <linux/suspend.h>
#include <linux/workqueue.h>

#include <linux/unaligned.h>
Expand Down Expand Up @@ -3098,6 +3100,15 @@ static int bcm4377_probe(struct pci_dev *pdev, const struct pci_device_id *id)
hdev);
}

/* Final release runs after every device resume, including reversible unwind. */
static void bcm4377_sleep_finish(struct device *dev)
{
struct bcm4377_data *bcm4377 = pci_get_drvdata(to_pci_dev(dev));

WRITE_ONCE(bcm4377->setup_retry_blocked, false);
bcm4377_queue_setup_retry(bcm4377);
}

static int bcm4377_resume(struct device *dev);

static int bcm4377_suspend(struct device *dev)
Expand All @@ -3106,6 +3117,12 @@ static int bcm4377_suspend(struct device *dev)
struct bcm4377_data *bcm4377 = pci_get_drvdata(pdev);
int ret;

ret = apple_radio_sleep_endpoint_prepare(dev, bcm4377_sleep_finish);
if (ret)
return ret;
if (apple_radio_sleep_owned(dev) && device_may_wakeup(dev))
return -EBUSY;

bcm4377_resume_recovery_stop(bcm4377);
WRITE_ONCE(bcm4377->setup_retry_blocked, true);
cancel_delayed_work_sync(&bcm4377->setup_retry_work);
Expand All @@ -3116,8 +3133,10 @@ static int bcm4377_suspend(struct device *dev)
ret = hci_suspend_dev(bcm4377->hdev);
if (ret) {
bcm4377->resume_powered = false;
WRITE_ONCE(bcm4377->setup_retry_blocked, false);
bcm4377_queue_setup_retry(bcm4377);
if (!apple_radio_sleep_owned(dev)) {
WRITE_ONCE(bcm4377->setup_retry_blocked, false);
bcm4377_queue_setup_retry(bcm4377);
}
return ret;
}

Expand Down Expand Up @@ -3152,8 +3171,11 @@ static int bcm4377_suspend(struct device *dev)
/* Flush the posted write before the system goes to sleep. */
ioread32(bcm4377->bar0 + BCM4377_BAR0_SLEEP_CONTROL);
bcm4377->quiesced = true;

return 0;
/* This records the command/readflush, not a firmware idle acknowledgment. */
ret = apple_radio_sleep_endpoint_suspended(dev);
if (ret)
bcm4377_resume(dev);
return ret;
}

static int bcm4377_resume(struct device *dev)
Expand All @@ -3163,6 +3185,10 @@ static int bcm4377_resume(struct device *dev)
unsigned long flags;
int ret;

ret = apple_radio_sleep_endpoint_resume(dev);
if (ret)
return ret;

if (bcm4377->wake_armed) {
disable_irq_wake(pdev->irq);
bcm4377->wake_armed = false;
Expand All @@ -3186,13 +3212,46 @@ static int bcm4377_resume(struct device *dev)
bcm4377->resume_powered = false;

ret = hci_resume_dev(bcm4377->hdev);
WRITE_ONCE(bcm4377->setup_retry_blocked, false);
if (!ret)
bcm4377_queue_setup_retry(bcm4377);
apple_radio_sleep_endpoint_resumed(dev, ret);
if (!apple_radio_sleep_owned(dev)) {
WRITE_ONCE(bcm4377->setup_retry_blocked, false);
if (!ret)
bcm4377_queue_setup_retry(bcm4377);
}
return ret;
}

static DEFINE_SIMPLE_DEV_PM_OPS(bcm4377_ops, bcm4377_suspend, bcm4377_resume);
static int bcm4377_resume_noirq(struct device *dev)
{
struct bcm4377_data *bcm4377 = pci_get_drvdata(to_pci_dev(dev));
u32 before, after;
int ret;

if (!apple_radio_sleep_owned(dev) || !pm_resume_via_firmware())
return 0;

/*
* PERST resets the private BAR windows, which PCI's standard saved state
* does not cover. Restore them before the IRQ handler or UNQUIESCE can
* access the retained controller through those windows.
*/
ret = pci_read_config_dword(bcm4377->pdev, BCM4377_PCIECFG_BAR0_WINDOW1, &before);
if (!ret)
ret = bcm4377_init_cfg(bcm4377);
if (!ret)
ret = pci_read_config_dword(bcm4377->pdev, BCM4377_PCIECFG_BAR0_WINDOW1,
&after);
if (ret || after != bcm4377->hw->bar0_window1)
panic("J713 Bluetooth PCI window restore failed: %d", ret);
dev_info(dev, "private PCI windows restored after sleep (%#x -> %#x)\n",
before, after);
return 0;
}

static const struct dev_pm_ops bcm4377_ops = {
SYSTEM_SLEEP_PM_OPS(bcm4377_suspend, bcm4377_resume)
.resume_noirq = bcm4377_resume_noirq,
};

static const struct dmi_system_id bcm4377_dmi_board_table[] = {
{
Expand Down
120 changes: 120 additions & 0 deletions tools/asahi/bcm4377-sleep-config-tests.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <setjmp.h>
#include <stdio.h>
#include <string.h>
typedef uint32_t u32;
#define BIT(n) (1U << (n))
#define dev_info(...) ((void)0)
struct device {
int unused;
};
struct pci_dev {
int unused;
};
struct hardware {
u32 bar0_window1, bar0_window2, bar0_core2_window2;
bool has_bar0_core2_window2, clear_pciecfg_subsystem_ctrl_bit19;
};
struct bcm4377_data {
struct pci_dev *pdev;
struct hardware *hw;
};
static struct pci_dev pci;
static struct hardware hw = { 0x18002000, 0x18109000, 0x18106000, true, false };
static struct bcm4377_data data = { &pci, &hw };
static bool owned, physical, ignore_window;
static unsigned int ops, writes, fail;
static u32 cfg[64];
static jmp_buf fatal;
static struct pci_dev *to_pci_dev(struct device *d)
{
(void)d;
return &pci;
}
static struct bcm4377_data *pci_get_drvdata(struct pci_dev *d)
{
(void)d;
return &data;
}
static bool apple_radio_sleep_owned(struct device *d)
{
(void)d;
return owned;
}
static bool pm_resume_via_firmware(void)
{
return physical;
}
static int pci_read_config_dword(struct pci_dev *d, unsigned int off, u32 *v)
{
(void)d;
if (++ops == fail)
return 0x87;
*v = cfg[off / 4];
return 0;
}
static int pci_write_config_dword(struct pci_dev *d, unsigned int off, u32 v)
{
(void)d;
writes++;
if (++ops == fail)
return 0x87;
if (!(ignore_window && off == 0x80))
cfg[off / 4] = v;
return 0;
}
#define panic(...) longjmp(fatal, 1)
/* ACTUAL */
static void fresh(void)
{
owned = physical = true;
ignore_window = false;
ops = writes = fail = 0;
memset(cfg, 0, sizeof(cfg));
cfg[0x88 / 4] = BIT(19) | 0x80;
}
int main(void)
{
struct device dev = {};

fresh();

assert(!bcm4377_resume_noirq(&dev));
assert(cfg[0x80 / 4] == hw.bar0_window1);
assert(cfg[0x70 / 4] == hw.bar0_window2);
assert(cfg[0x74 / 4] == 0x18011000);
assert(cfg[0x78 / 4] == hw.bar0_core2_window2);
assert(cfg[0x84 / 4] == 0x19000000);
assert(cfg[0x88 / 4] == (BIT(19) | BIT(16) | 0x80));
unsigned int count = ops;

fresh();

owned = false;
assert(!bcm4377_resume_noirq(&dev));
assert(!ops);
fresh();
physical = false;
assert(!bcm4377_resume_noirq(&dev));
assert(!ops);
for (unsigned int i = 1; i <= count; i++) {
fresh();
fail = i;
if (!setjmp(fatal)) {
bcm4377_resume_noirq(&dev);
assert(0);
}
assert(ops == i);
}
fresh();
ignore_window = true;
if (!setjmp(fatal)) {
bcm4377_resume_noirq(&dev);
assert(0);
}
puts("PASS: private PCI windows, retained-path skip and restore failures");
return 0;
}
36 changes: 36 additions & 0 deletions tools/asahi/bcm4377-sleep-config-tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0-only OR MIT
"""Exercise private PCI-window restoration before Bluetooth resume IRQs."""

from pathlib import Path
import re
import subprocess
import tempfile

root = Path(__file__).resolve().parents[2]
source = (root / "drivers/bluetooth/hci_bcm4377.c").read_text()
functions = []
for name in ("bcm4377_init_cfg", "bcm4377_resume_noirq"):
match = re.search(
r"^static int " + name + r"\([^;]+?\{.*?\n\}", source, re.M | re.S
)
assert match, name
functions.append(match.group())
constants = "\n".join(
line for line in source.splitlines() if line.startswith("#define BCM4377_PCIECFG_")
)
code = (
Path(__file__)
.with_suffix(".c")
.read_text()
.replace("/* ACTUAL */", constants + "\n" + "\n".join(functions))
)
with tempfile.TemporaryDirectory() as tmp:
c = Path(tmp) / "test.c"
c.write_text(code)
exe = Path(tmp) / "test"
subprocess.run(
["cc", "-std=gnu11", "-Wall", "-Wextra", "-Werror", str(c), "-o", str(exe)],
check=True,
)
subprocess.run([str(exe)], check=True)