Skip to content

Enable suspend/resume on Pi 5 - #7514

Draft
PineappleBeech wants to merge 28 commits into
raspberrypi:rpi-6.18.yfrom
PineappleBeech:suspend
Draft

Enable suspend/resume on Pi 5#7514
PineappleBeech wants to merge 28 commits into
raspberrypi:rpi-6.18.yfrom
PineappleBeech:suspend

Conversation

@PineappleBeech

Copy link
Copy Markdown

This enables suspend/resume support for Pi 5.

The watchdog should be stopped when suspending.

The wireless can be turned off with cap-power-off-card. It could be left on with keep-power-in-suspend but that does not have any benefits currently.

NVME drives currently do not work. The NVME driver puts the drive in a low power state and expects the link to be left on. The PCIe driver then turns off the link. This stops the NVME driver waking up the drive. Adding NVME_QUIRK_SIMPLE_SUSPEND to a drive fixes it.

@popcornmix

Copy link
Copy Markdown
Collaborator

Can you describe your use case?

@PineappleBeech

Copy link
Copy Markdown
Author

This is just for testing how the drivers handle suspend/resume. The firmware does not support suspend yet. It currently stops the arm and then starts it again after one second.

The first commit should only be merged if the firmware supports suspend. The other commits should do nothing if suspend is not enabled so they could be merged before suspend works.

@popcornmix

popcornmix commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

The commits look plausible to me. Claude had some comments (that may not be an issue):

Commit 2 — watchdog: bcm2835: stop watchdog over suspend/resume. This is the solid one. It's the standard pattern used by many upstream watchdog drivers, and the implementation is idiomatic (DEFINE_SIMPLE_DEV_PM_OPS + pm_sleep_ptr, so it compiles away without CONFIG_PM_SLEEP). It still applies cleanly to our tree — the driver has no PM ops today. One edge case worth raising: it only checks watchdog_active(), which covers the userspace-opened case (WDOG_ACTIVE) but not WDOG_HW_RUNNING, which the probe sets when the bootloader left the watchdog running (bcm2835_wdt.c:207). In that state the watchdog core's keepalive worker pings the hardware, but that worker freezes during suspend, so a bootloader-armed watchdog would reset the board mid-suspend. Rare in practice on Pi, but cheap to handle.

Commit 3 — cap-power-off-card on the Pi 5 / CM5 wifi SDIO node. The rationale is sound: without keep-power-in-suspend, the MMC core needs permission to power-cycle the card across suspend, otherwise the SDIO suspend path aborts. Coverage is complete (Pi 500 includes the 5-b dts, the CM5L variants include cm5.dtsi). However, the author's claim that this commit "does nothing if suspend is not enabled" is not quite right: MMC_CAP_POWER_OFF_CARD also enables SDIO runtime PM, which changes card power behaviour at probe and whenever brcmfmac is unbound — today, on every user's board. It's probably fine, but it needs testing on the current kernel (wifi bring-up, unbind/rebind, rfkill) before merging, independent of any suspend work.

@PineappleBeech

Copy link
Copy Markdown
Author

I've updated the watchdog driver with the extra check.

The wireless driver prevents runtime PM while it is bound

I've added a temporary fix for NVMe drives. It works by overriding acpi_storage_d3 which has the correct effect of telling the NVMe driver that PCIe will be powered down. However, the Pi 5 does not use ACPI.

@timg236

timg236 commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

The firmware is now getting into the kernel after wakeup from S3 (lots to do!).

I think our TFA was assuming that GIC distributor was preserved in S3, it isn't. It's not in the AON power island
https://github.com/timg236/trusted-firmware-a/pull/new/pi5-s2ram-bringup

Mailboxes writes weren't making it to VPU after wakeup from S3, here's my very lightly tested patch for this on top of your patchset
#7559

@timg236

timg236 commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Firmware for kernel debug here - pieeprom-2026-08-17 (probably quite broken)
https://github.com/timg236/rpi-eeprom/tree/pi5-s3-s2ram/firmware-2712/latest

Should look something like this, WiFi + NETDEV timeouts

  0.65 BUCK1_VOUT e6 - e6
  0.65 BUCK2_VOUT be - be
  0.65 BUCK3_VOUT 28 - 28
  0.65 BUCK4_VOUT a0 - a0
  0.66 BUCK5_VOUT 3c - 3c
  0.66 BUCK6_VOUT a0 - a0
  0.66 BUCK7_VOUT 64 - 64
  0.66 BUCK8_VOUT 64 - 64
  0.67 USBPD_STATUS: 00,00,00,00,00

  0.67 RPi: BOOTSYS release VERSION:7397d9bd DATE: 2026/08/17 TIME: 08:11:26
  0.68 MFG_VER: 1
  0.68 BOOTMODE: 0x06 partition 0 build-ts BUILD_TIMESTAMP=1786954286 serial 1760d33e boardrev b04170 stc 684236
  0.69 AON_RESET: 00000010 PM_RSTS 00001000
  0.69 POWER_OFF_ON_HALT: 0 WAIT_FOR_POWER_BUTTON 0 power-on-reset 1
  0.70 EEPROM ID 0xef4015
  0.70 SFDP v1.5 Param v1.5
  0.70 boot_eeprom_config.active_standby 0
  0.71 part 00000000 reset_info 00000000
  0.71 PMIC reset-event 00000000 rtc 6a82c5ad alarm 00000000 enabled 0
  0.72 uSD voltage 3.3V
  0.72 S2RAM RESUME: sdram_config 00000006
  0.73 Initialising SDRAM rank 1 total-size: 16Gbit part: 0 (0x06 0x06) odt: 0
  0.73 memsys_init: 0 16Gbit MCB 0x80015ff0: 7880
  0.74 DDR 4267 Mbps dual-rank:0 byte-mode:0 size-gbit:16 part-config:0 odt:0
  0.77 Resume DDR PHYs
  0.89 SREF exit SD_CS 00018203
  0.91 OTP boardrev b04170 bootrom a a
  0.91 Customer key hash 0000000000000000000000000000000000000000000000000000000000000000
  0.92 VC-JTAG unlocked
  0.94 MESS:00:00:00.944130  0.96 MESS:00:00:00.962439:0: 00000040: -> 00000480
  0.96 MESS:00:00:00.964305:0: 00000030: -> 00100080
  0.96 MESS:00:00:00.969019:0: 00000034: -> 00100080
  0.97 MESS:00:00:00.973731:0: 00000038: -> 00100080
  0.97 MESS:00:00:00.978443:0: 0000003c: -> 00100080

INFO:    rpi5_pwr_domain_suspend_finish
INFO:    gic reinitialised
INFO:    returning to kernel
[  361.384292] Calling cpu_pm_resume+0x0/0x68
[  361.384292] Calling kvm_resume+0x0/0x40
[  361.384292] Calling irq_gc_resume+0x0/0x90
[  361.384292] Calling irq_pm_syscore_resume+0x0/0x30
[  361.384292] Calling timekeeping_resume+0x0/0x150
[  361.384292] Calling sched_clock_resume+0x0/0x68
[  361.384294] Calling ledtrig_cpu_syscore_resume+0x0/0x30
[  361.389632] Enabling non-boot CPUs ...
[  361.393553] Detected PIPT I-cache on CPU1
[  361.397605] CPU1: Booted secondary processor 0x0000000100 [0x414fd0b1]
[  361.404466] CPU1 is up
[  361.406939] Detected PIPT I-cache on CPU2
[  361.410981] CPU2: Booted secondary processor 0x0000000200 [0x414fd0b1]
[  361.417794] CPU2 is up
[  361.420272] Detected PIPT I-cache on CPU3
[  361.424312] CPU3: Booted secondary processor 0x0000000300 [0x414fd0b1]
[  361.431130] CPU3 is up
[  361.537120] brcm-pcie 1000120000.pcie: clkreq-mode set to default
[  361.543244] brcm-pcie 1000120000.pcie: link up, 5.0 GT/s PCIe x4 (!SSC)
[  361.550254] PM: noirq resume of devices complete after 116.765 msecs
[  361.556980] PM: early resume of devices complete after 0.291 msecs
[  361.565972] Timekeeping suspended for 5.421 seconds

@timg236

timg236 commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

WiFi no longer hangs for me with the pinctrl changes - tested with rebased s2ram pieeprom-2026-08-17-v3.bin that includes other firmware clock changes.

@PineappleBeech

Copy link
Copy Markdown
Author

Using the regulator driver for PCIE_PWR_EN means that it will also be left unpowered when not in use.

Comment thread arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5.dtsi Outdated
items:
enum: [1, 4, 8, 16, 32, 64, 128, 256]

snps,power-off-in-suspend:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs a better name - USB suspend is the name given to a runtime power management choice, but collides with the concept of system suspend (which may or may not power off the system). The SNPS driver instantiates an xhci platform driver, so we don't get the luxury of pretending that PCI power states apply.

I'd suggest s2ram as this maps to the state in admin-guide/pm/sleep-states.rst

Comment thread drivers/iommu/bcm2712-iommu.c Outdated
u = bcm2712_iommu_get_page(mmu, &mmu->top_table);
if (!u)
return -ENOMEM;
if (mmu->top_table) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@njhollinghurst - Does this seem sensible?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it looks plausible.

When called from probe() the structure is kzalloc'd so it will allocate memory for top-level tables and default/trap page. When called from resume() it will merely re-program their addresses into H/W registers.

(Tangentially, there is some pre-existing conflation of "physical address" (where the tables are in SDRAM) and "dma_addr_t" (how the IOMMU can reach them) but we already assume they are identical and there is a WARN_ON about it; virt_to_phys is fine here.)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see Claude has made an almost identical comment!

@popcornmix

Copy link
Copy Markdown
Collaborator

Claude review:

Review: #7514 — "Enable suspend/resume on Pi 5"

Base rpi-6.18.y, head a8e9787922e1a, 29 commits. Reviewed the full diff plus
surrounding code at the PR head.

Overall: the shape of the series is right and most of the driver work is
idiomatic (pm_sleep_ptr(), DEFINE_SIMPLE_DEV_PM_OPS, the
drm_mode_config_helper_* pattern). There are, however, four commits I'd call
wrong as written, one DT commit that is a real hazard for people who never
suspend, and a handful of hygiene problems.


1. Regression risk for users who do not use suspend

Good news first: every pm_sleep_ptr() / SET_*_SLEEP_PM_OPS change in
this series is completely inert outside bcm2712. I checked all the RPi
defconfigs at the PR head:

defconfig CONFIG_PM_SLEEP
arm64/bcm2712_defconfig y (this PR)
arm64/bcm2711_defconfig n (# CONFIG_SUSPEND is not set)
arm/bcm2709_defconfig n
arm/bcm2835_defconfig n
arm/bcmrpi_defconfig n

So the shared drivers this series touches — amba-pl011, bcm2835-mailbox,
bcm2835_wdt, vc4_drv/vc4_hdmi/vc4_hvs, gpio-brcmstb — gain no new
behaviour at all on Pi 4 and earlier. That removes most of the cross-platform
worry.

What is left unconditional, ranked by risk:

1a. arm64: dts: bcm2712: Add a regulator for pcie 3v3 (1b9f707) — highest risk

This is the one commit in the series I'd hold back. It is not gated on
CONFIG_SUSPEND and it changes PCIe behaviour for every Pi 5 / CM5 user who
has dtparam=pciex1 or dtparam=nvme set — i.e. essentially the entire
NVMe-base and PCIe-HAT+ population.

Three specific problems:

  1. The Pi 5 GPIO looks wrong. The new node uses <&rp1_gpio 28> and the
    comment says // PCIE_RP1_WAKE. That matches the existing
    gpio-line-names in bcm2712-rpi-5-b.dts:693 and bcm2712-rpi-500.dts:115,
    which both call RP1 GPIO28 PCIE_RP1_WAKE. Only bcm2712-rpi-cm5.dtsi:665
    names it PCIE_PWR_EN — and the cm5io copy of the node comments it
    // PCIE_PWR_EN accordingly. So either the Pi 5 line name has been wrong all
    along, or this regulator drives the wake pin as an output on Pi 5. Please
    resolve against the schematic and make the line name, the node comment and
    the regulator agree. As it stands the two copies of the same node disagree
    with each other about what the pin is.

  2. Linux now owns the PCIe 3V3 rail. pcie-brcmstb disables
    vpcie3v3-supply on link-down and on unbind. Today the firmware brings it up
    and nothing turns it off. regulator-boot-on covers the boot case but not
    "slot enabled, nothing plugged in" or echo 1 > .../remove. Worth testing:
    no device present; device present; unbind/rebind; reboot; kexec.

  3. The probe-ordering hack makes pcie1 depend on RP1. The comment is candid
    about this — vpcie3v3-supply on &pcie1 exists purely so fw_devlink orders
    pcie1 after pcie2/rp1_gpio. The consequence is that anyone booting rootfs from
    the PCIe slot now has an unbootable machine if RP1 fails to come up, where
    previously PCIe1 was independent. If the ordering must be forced, please do it
    explicitly rather than via a supply phandle that also has a functional effect.

1b. pciex1-compat-pi5-overlay.dts fragment@4 — will break an existing overlay

fragment@4 {
        target = <&pcie1_3v3>;
        __dormant__ { status = "disabled"; };
};

&pcie1_3v3 only exists in bcm2712-rpi-5-b.dts and bcm2712-rpi-cm5io.dtsi.
Every other fragment in this overlay targets &pciex1, which exists on all
bcm2712. Dormant fragments still need their target phandle fixed up at apply
time, so on a Pi 500, or a CM5 on a non-CM5IO carrier,
dtoverlay=pciex1-compat-pi5 will now fail to apply outright — including for
users who only wanted mmio-hi or no-l0s and never touch suspend. Please
either add the regulator to the shared bcm2712-rpi-cm5.dtsi/500 DTs, or move
fragment@4 into a separate Pi-5-only overlay.

Separately: status = "disabled" on a regulator-fixed node means no provider
is registered, so regulator_bulk_get() for vpcie3v3-supply will most likely
return -EPROBE_DEFER forever rather than "don't control power" — i.e.
no-pwr-ctrl may hang pcie1 probe instead of doing what it says. Deleting the
gpio property, or adding regulator-always-on, expresses the intent safely.
(Also: "Its is enabled" → "It is".)

1c. ACPI: PCI: bcm2712: Set acpi_storage_d3 to true on bcm2712 (dfe038e)

Functionally this is benign for non-suspend users — I traced both callers:

  • nvme_probe() only uses the result to set NVME_QUIRK_SIMPLE_SUSPEND, and
    that quirk is read in exactly one place (nvme_suspend()).
  • libahci's use is inside ahci_port_suspend().

So the only visible effect without suspend is a new platform quirk: setting simple suspend line in dmesg for every NVMe on every Pi 5.

The implementation is the problem, and I don't think it can ship as-is:

  • It puts an RPi machine check into the !CONFIG_ACPI stub in
    include/linux/acpi.h, which every arch and thousands of translation units
    include. It'll never be accepted upstream, and it silently becomes a no-op if
    anyone ever builds bcm2712 with CONFIG_ACPI=y.
  • It adds #include <linux/of.h> to include/linux/acpi.h unconditionally. I
    found no include cycle at depth 1, but this needs at least a multi-arch
    allmodconfig before I'd trust it.

The PR description already identifies the real bug: "The NVMe driver puts the
drive in a low power state and expects the link to be left on. The PCIe driver
then turns off the link." Fixing that in pcie-brcmstb is the right change.
Failing that, put the override in drivers/nvme/host/pci.c behind
of_machine_is_compatible() and leave include/linux/acpi.h alone.

1d. Verified benign, but worth a line in the commit messages

  • cap-power-off-card (8df5a5b) — I checked this out because the cap
    also switches on SDIO runtime PM. sdio_bus_probe() takes a
    pm_runtime_get_sync() that is only released if the driver calls
    pm_runtime_put_noidle() in probe, and brcmfmac doesn't. So the reference is
    held for the driver's lifetime and there's no autosuspend — the commit
    message's claim is correct. brcmfmac also handles MMC_CAP_POWER_OFF_CARD
    explicitly in brcmf_ops_sdio_suspend() (remove-and-reprobe), and WoWLAN
    still keeps power via the wowl_enabled branch. This one is fine.
  • rpi_rtc_set_alarm() now calls rpi_rtc_alarm_clear_pending() on every
    RTC_ALM_SET/RTC_WKALM_SET, for all users. Low risk, but it's an extra
    firmware mailbox round-trip on a hot-ish path and its return value is
    discarded (unlike every other call in that driver).
  • hci_bcm: bdev->irq_acquired = false in bcm_close() is a genuine
    pre-existing bug fix (the flag was never cleared, so open/close/open got the
    devm_free_irq/pm_runtime_disable accounting wrong). It affects any
    hci_bcm platform with a host-wake IRQ, unrelated to suspend. Please split it
    into its own commit with a Fixes: tag — it's independently upstreamable.
  • CONFIG_HOTPLUG_CPU=y disappearing from bcm2712_defconfig is correct
    savedefconfig output (PM_SLEEP_SMP selects it). Just be aware that anyone
    who derives a config from this and switches suspend back off now silently
    loses CPU hotplug too.

2. Commits I'd call clearly correct

I checked these against the surrounding code and have no objection:

  • gpio-brcmstb: Enable hibernation support (0bdee42) — textbook. The
    #else #define ... NULL guard already existed, so SET_NOIRQ_SYSTEM_SLEEP_PM_OPS
    is a clean swap that adds the freeze/thaw/poweroff/restore variants. Save at
    suspend_noirq, restore at resume_noirq, and it nests correctly with
    pinctrl's suspend_late/resume_early.
  • irqchip: bcm2712-mip: Restore registers after suspend (79f317f) —
    platform_set_drvdata() is added, IRQCHIP_PLATFORM_DRIVER_END already takes
    __VA_ARGS__, and mip_hw_init() really is the complete hardware state:
    masking is delegated to the parent GIC via irq_chip_mask_parent, so there is
    no per-vector MIP state to restore. resume_noirq is after the GIC's syscore
    restore. Correct and complete.
  • bcm2385: mailbox: Add resume handler for S3 wakeup on Pi5 (09dda5c) —
    platform_set_drvdata() is already done in probe, the write is idempotent
    with bcm2835_startup(), and resume_noirq is the right phase given
    IRQF_NO_SUSPEND. (Subject/S-o-b problems below.)
  • serial: amba-pl011: Add start_rx to re-enable interrupts (8e1bb66) —
    I chased this one carefully because pl011 has a lot of users.
    ops->start_rx is reachable from exactly two places in serial_core.c, both
    gated on !console_suspend_enabled && uart_console(uport), and both hold the
    port lock. Providing it also makes uart_suspend_port() start calling
    stop_rx() — which is the correct pairing. The body is a copy of
    pl011_unthrottle_rx() minus the locking, and the FEIM/PEIM/BEIM/OEIM bits
    that stop_rx clears are never set anywhere in the driver, so not restoring
    them is fine. pl011_dma_rx_stop() only clears RXDMAE, so re-setting it is
    the exact inverse. This is upstreamable as-is.
  • drm/rp1: dsi (fe30ab6) and drm/rp1: vec (5be1dc0) — I
    checked the clock balance, which is the usual trap here. In both drivers the
    clock is clk_prepare_enable()d once in probe and only dropped in
    *_stopall() (remove/shutdown), not in the pipe disable path, so the
    suspend/resume enable counts balance. Correct.
    Minor: rp1vec_platform_resume() calls rp1vec_vidout_setup() and then
    drm_mode_config_helper_resume() calls it again via pipe_enable; and if the
    display was off across suspend, resume leaves the VEC output powered up. Not a
    bug, but if the explicit call is fixing something specific, say so in the
    message.
  • drm/vc4: hdmi (1190e36) — devm_pm_runtime_enable() is
    unconditional in bind and the driver uses plain pm_runtime_put_sync(), so
    pm_runtime_force_suspend/resume is the right idiom. See §4 for a CEC
    question that isn't this commit's fault.
  • mmc: sdhci-brcmstb: Reconfigure on resume (de37b53) — the struct
    reorder is mechanical, and priv->match_priv can't be NULL (every
    of_match entry has .data, and probe already dereferences it). One ordering
    question in §4.
  • iommu: bcm2712-iommu (f8692b1) — dropping nmapped_pages = 0 is
    safe: bcm2712_iommu_init() has exactly two callers (probe, where mmu is
    kzalloc'd, and the new resume). Using virt_to_phys() instead of re-mapping
    is consistent with the driver's own documented assumption
    (bcm2712_iommu_get_page() WARN_ONs if dma != virt_to_phys(*ptr)).
  • arm64: dts: bcm2712: wifi: Power off wifi during suspend (8df5a5b) —
    see §1d; verified correct.
  • watchdog: bcm2835 (d2be4e2) — bcm2835_wdt_wdd is a file-scope
    static, so the global reference compiles and is consistent with the rest of
    the driver; bcm2835_wdt_stop() doesn't touch WDOG_HW_RUNNING, so the
    resume condition still matches. One design note in §4.
  • usb: dwc3 / dt-bindings: snps,dwc3 / arm64: dts: rp1
    (054202a, 2b40a13, a8e9787) — plumbing verified:
    xhci_plat_priv.power_lost exists, xhci_plat_probe() copies priv_match
    wholesale, and xhci_plat_resume_common() ORs it in. Copying
    dwc3_xhci_plat_quirk onto the stack to patch one field is fine. Also
    upstreamable, though upstream may prefer a glue quirk over a DT property.

3. Commits I'd call wrong as written

3a. media: pisp_be: Add suspend/resume support (0ed80cf) — will poke a gated block

static int pispbe_resume(struct device *dev)
{
        struct pispbe_dev *pispbe = dev_get_drvdata(dev);
        return pispbe_hw_init(pispbe);
}

pispbe uses runtime PM with a 200 ms autosuspend
(pm_runtime_put_autosuspend() after each job), and pispbe_runtime_suspend()
does clk_disable_unprepare(pispbe->clk). device_prepare() takes a
pm_runtime_get_noresume() — it does not runtime-resume the device — so a
device that was runtime-suspended before the system suspend is still
runtime-suspended, clock off, when pispbe_resume() runs. pispbe_hw_init() then does pispbe_rd(pispbe, PISP_BE_VERSION_REG) with the clock gated. Best
case that returns garbage and you get -ENODEV ("resume failed"); worst case
it's a bus stall.

Since pispbe_runtime_resume() already enables the clock, the fix is the
standard one:

SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume)

and move the pispbe_hw_init() call into pispbe_runtime_resume(). That also
removes the need for the hw_busy check, which as written aborts the entire
system suspend with -EBUSY and is racy anyway (the flag is read under the
spinlock and then dropped before returning).

3b. media: imx708: Stop streaming during system suspend. (aec21fa) — three separate problems

First, the subject says imx708 but the commit only touches
drivers/media/i2c/imx500.c. Either the subject or the file is wrong.

Second, same runtime-PM problem as pisp_be, with a worse failure mode.
imx500_power_off() (the runtime-suspend callback) is what clears
common_regs_written, loader_and_main_written, fsm_state and the firmware
network. With SET_SYSTEM_SLEEP_PM_OPS(imx500_suspend, imx500_resume) the
runtime state is never cycled, so after an S3 where the camera rail /
RP1 actually lost power, the driver still believes the sensor firmware is
loaded and imx500_start_streaming() will skip re-uploading it.

Third, a refcount underflow on the resume error path:

        if (imx500->streaming) {
                ret = imx500_start_streaming(imx500);
                if (ret)
                        goto error;
        }
        ...
error:
        imx500_stop_streaming(imx500);

imx500_start_streaming() already does pm_runtime_put_autosuspend() on its
err_runtime_put: path, and imx500_stop_streaming() ends with another
pm_runtime_put_autosuspend(). So a failed resume double-puts the usage count.

Again pm_runtime_force_suspend/force_resume (plus keeping the
stop/start-streaming around it) is the shape you want.

3c. drivers: rtc-rpi: Clear a pending alarm on resume ... (bc20083) — spurious alarm on every resume

static int rpi_rtc_resume(struct device *dev)
{
        rpi_rtc_alarm_clear_pending(dev);
        rtc_update_irq(vrtc->rtc, 1, RTC_AF);
        return 0;
}

rtc_update_irq() is called unconditionally, so every resume reports an
alarm to the RTC core whether or not the RTC caused the wake. Anything
poll()ing or read()ing /dev/rtc0 (or using RTC_AIE) will see a phantom
alarm after a lid-close/keypress wake. It's also odd for a device that sets
RTC_FEATURE_ALARM_WAKEUP_ONLY.

Worse, the driver clears the pending flag before reporting, so it destroys the
one piece of information it needs. It should GET RTC_ALARM_PENDING first and
only clear-and-report when the flag was actually set. (A rpi_rtc_alarm_is_pending()
helper next to the existing rpi_rtc_alarm_irq_is_enabled() would be the
natural shape.)

3d. mailbox: rp1: check received event bits more carefully (9000659) — doesn't fix the reported crash

        if (doorbell >= MAX_CHANS)
                break;
        chan = &mbox->controller.chans[doorbell];
        if (chan)
                mbox_chan_received_data(chan, NULL);

Two issues:

  1. chan = &array[i] is never NULL, so if (chan) is dead code.
  2. The NULL deref the commit message is chasing is in mbox_chan_received_data():
    void mbox_chan_received_data(struct mbox_chan *chan, void *mssg)
    {
            if (chan->cl->rx_callback)   /* <-- chan->cl */
    rp1_mbox_probe() always allocates MAX_CHANS (4) channels, and chan->cl
    stays NULL until a client binds via rp1_mbox_xlate(). The "link is down →
    all-1s" case the message describes sets bits 0..3 as well, so the handler
    still walks straight into chan->cl == NULL for any unclaimed channel.

The bounds check is right and worth keeping — it fixes a genuine
out-of-bounds read for bits 4..31 — but the guard needs to be if (chan->cl)
(or num_chans-based) to actually prevent the deref. break rather than
continue is correct given __ffs(), and the events are all cleared up front,
so nothing leaks.

This one is unconditional (no PM gating) and independently upstreamable — please
split it out with a Fixes: tag.

3e. Bluetooth: hci_bcm: Add support for powering off during suspend (bb127bd) — unlocked bdev->hu access

bcm_powers_off_in_suspend() dereferences bdev->hu and bdev->hu->serdev,
and the new fast path in bcm_suspend() then uses bdev->hu->hdev and
bdev->hu again — all outside bcm_device_lock. The comment immediately
below the new code explains why that lock exists:

bcm_suspend can be called at any time as long as the platform device is
bound, so it should use bcm_device_lock to protect access to hci_uart

bcm_close() sets bdev->hu = NULL under that mutex. Closing the tty
concurrently with a suspend gives you a NULL deref in
hci_uart_set_flow_control(bdev->hu, true). Please take the mutex for the new
path too (note bcm_gpio_set_power() and hci_uart_set_flow_control() are
already called under it elsewhere in the file, so this should be mechanical).

Also on this commit:

  • flush_work(&hdev->power_on) from .suspend can block for the whole HCI
    setup — firmware download and a baud-rate change. With
    CONFIG_DPM_WATCHDOG_TIMEOUT=30 from this same series, a wedged BT setup
    turns a suspend into a panic. Returning -EAGAIN/-EBUSY instead would be
    friendlier. (Also: "duriung".)
  • The device_reprobe() work item runs on system_long_wq asynchronously, so
    it can fire while dpm_resume is still walking other devices. Worth
    confirming the ordering is actually harmless rather than lucky.
  • HCI_UART_NO_SUSPEND_NOTIFIER is set correctly — p->open() runs before the
    flag is tested in hci_uart_register_device_priv(). Good.
  • bcm_setup() skipping bcm_request_irq() for these devices is fine on
    Pi 5/CM5 specifically, because their BT nodes have no interrupts/host-wake
    property, so bcm_request_irq() already returned -EOPNOTSUPP and
    bcm_setup_sleep() was already skipped. Worth a sentence in the commit
    message so nobody has to re-derive that.

4. Things I'd want answered before merge

  • DPM watchdog in a shipping defconfig (3cd3df6). DPM_WATCHDOG
    panics on timeout (per its Kconfig help), and the upstream default is 120 s;
    this sets 30. That turns any slow device callback on a user's machine into a
    panic instead of a slow resume. It's a great bring-up setting; I'd rather it
    wasn't in the config we ship. At minimum leave DPM_WATCHDOG_TIMEOUT at 120
    and keep only the 5 s warning. (PSTORE and EXPERT are both already set, so
    this does take effect.)
  • Hibernation (abd985f). This doubles the test matrix — every
    DEFINE_SIMPLE_DEV_PM_OPS in this series now also runs as
    .freeze/.thaw/.poweroff/.restore. Has S4 actually been tested
    end-to-end on a Pi 5? Two specifics:
    • Raspberry Pi OS uses a swapfile (dphys-swapfile), which needs
      resume=+resume_offset= — so /sys/power/disk will be exposed and
      non-functional for almost everyone.
    • bcm2712-iommu does explicit dma_sync_single_for_device() for its page
      tables. On restore the tables come back via the CPU with no sync, so the
      IOMMU may walk stale RAM. Worth a dma_sync in the resume path, or
      restricting the iommu ops to SYSTEM_SLEEP rather than the simple macro.
  • vc4 suspend ordering is implicit. vc4_drm_suspend() (which does the
    atomic disable) and vc4_hdmi's pm_runtime_force_suspend are both in the
    .suspend phase, so their relative order is just reverse-registration order.
    It happens to work today — in bcm2712.dtsi, hdmi0/hdmi1 are at lines
    365/394 and vc4: gpu at 441, so the HDMI devices suspend after vc4-drm,
    which is what you want (and is presumably why the "packet RAM is off" warnings
    went away). But that's a DT-ordering coincidence. Moving the component
    drivers' callbacks to .suspend_late, or adding device links, would make it
    robust.
  • CEC across resume. vc4_hdmi_cec_enable() holds a runtime PM reference for
    as long as the CEC adapter is enabled, so pm_runtime_force_suspend() really
    will call vc4_hdmi_runtime_suspend(). vc4_hdmi_runtime_resume() then resets
    HDMI_CEC_CNTRL_1 to logical address Unregistered, and nothing re-runs
    adap_log_addr. The physical address should come back via the connector
    re-detect that drm_mode_config_helper_resume() triggers, but I don't think
    the logical address does. Please test CEC (e.g. cec-ctl -S, or Kodi) after a
    suspend/resume cycle.
  • sdhci-brcmstb: cfginit() runs with the clocks off. sdhci_brcmstb_suspend()
    disables priv->base_clk and sdhci_pltfm_suspend() disables
    pltfm_host->clk; sdhci_pltfm_resume() is what re-enables the latter. The
    new cfginit() call is placed before that, so it does readl/writel on
    cfg_regs and clk_get_rate(pltfm_host->clk) with the main clock gated —
    the inverse of probe, where cfginit() runs after clk_prepare_enable(). If
    cfginit() genuinely has to precede sdhci_resume_host()'s reset (which I
    assume is the point, for the MAX_50MHZ_MODE strap override), then an
    explicit clk_prepare_enable(pltfm_host->clk) first would make that safe and
    obvious.
  • bcm2835_wdt stops at .suspend. That's the earliest phase, so a hang in
    suspend_late/suspend_noirq, in the platform sleep itself, or anywhere in
    resume has no hardware watchdog behind it — and DPM_WATCHDOG only covers
    per-device callbacks, not the noirq phase or the sleep. Would
    .suspend_noirq/.resume_noirq (or .suspend_late) narrow the window
    usefully?
  • vc4_hvs: clear drvdata on unbind. platform_set_drvdata(pdev, hvs) is
    right (I checked — nothing else reads the HVS pdev's drvdata), but hvs is
    drmm_kzalloc'd, so after vc4_hvs_unbind() the pointer dangles and
    vc4_hvs_resume_early()'s if (!hvs) won't catch it. A
    platform_set_drvdata(pdev, NULL) in unbind closes that.
  • bcm2712_iommu_resume() doesn't guard mmu->reg_base while
    bcm2712_iommu_suspend() and bcm2712_iommu_remove() both do, and
    bcm2712_iommu_init() starts with an MMU_RD(). I believe reg_base is
    always valid for a bound device (probe fails otherwise), so the existing
    checks are dead code — but the three sites should agree either way.
  • pinctrl-brcmstb: if (bit) for mux_bit works (only EMMC_REGS()
    produces 0) but the rest of the driver tests bit & MUX_BIT_VALID; worth
    matching. And bcm2712_pinctrl_pm_ops is assigned to .pm directly rather
    than via pm_sleep_ptr(), unlike everything else in the series.

5. Commit hygiene

  • 09dda5c has no Signed-off-by:. Also the subject reads
    bcm2385: mailbox: Add resumer hander for S3 wakeup on Pi5 — "bcm2385" →
    "bcm2835", "resumer hander" → "resume handler", and the prefix should be
    mailbox: bcm2835: to match the file's history.
  • aec21fa: subject says media: imx708: but the diff is
    drivers/media/i2c/imx500.c.
  • bc20083: the subject is 94 chars and got truncated by GitHub, so the
    body starts with the orphaned …other alarm. Please rewrap — subject ≤ 72,
    body starting on its own paragraph.
  • d2be4e2: "Stop the watchdog during suspend to that it does not
    reset" → "so that"; the body's second line has a stray leading space.
  • a8e9787: "oveer suspend" → "over suspend".
  • Four subjects exceed 72 characters (a8e9787, bc20083,
    bb127bd, 8df5a5b).
  • Split out the standalone fixes. bcm_close()'s irq_acquired reset and
    the rp1-mailbox bounds check are real bug fixes that stand on their own,
    affect non-suspend users, and want Fixes: tags. Several other commits are
    upstreamable as-is (pl011 start_rx, gpio-brcmstb macro swap, dwc3/xhci
    power_lost) and would be worth sending upstream rather than carrying.
  • The two pcie1_3v3 node definitions in bcm2712-rpi-5-b.dts and
    bcm2712-rpi-cm5io.dtsi are byte-identical apart from the pin-name comment.
    Worth factoring into bcm2712-rpi.dtsi once the GPIO question is settled.

@PineappleBeech
PineappleBeech force-pushed the suspend branch 2 times, most recently from 0f0f657 to e71c9a3 Compare September 9, 2026 16:38
Comment thread drivers/iommu/bcm2712-iommu.c Outdated
* Configure the addresses of the top-level table (offset because
* the aperture does not start from zero), and of the default page.
* For simplicity, both these regions are whole Linux pages.
*/

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's easier to understand now, but the comment above is now only partly relevant here (perhaps also the comment on line 723).

This function is small enough that it could perhaps be rolled into bcm2712_iommu_probe? OTOH that function is quite large already. Hmm.

@PineappleBeech

Copy link
Copy Markdown
Author

I've moved bcm2712_iommu_hw_init to be called in probe so now the top level and default page are allocated in their own function which just does that.

@PineappleBeech
PineappleBeech force-pushed the suspend branch 5 times, most recently from 1cf2806 to c924d6d Compare September 11, 2026 09:30
Add support for suspend to RAM on bcm2712.

Signed-off-by: Peter Bailey <peter.bailey@raspberrypi.com>
PineappleBeech and others added 27 commits September 11, 2026 11:24
Stop the watchdog during suspend to that it does not reset while
 suspended

Signed-off-by: Peter Bailey <peter.bailey@raspberrypi.com>
Add a device tree property so that wifi is powered off correctly
during suspend.

The wifi is powered off by the firmware during system suspend.

The wifi driver does not allow runtime suspension so this will only
affect system suspend.

Signed-off-by: Peter Bailey <peter.bailey@raspberrypi.com>
This makes the NVMe driver reset drives during suspend/resume.

Signed-off-by: Peter Bailey <peter.bailey@raspberrypi.com>
Disable the iommu on suspend and reinitialise it on resume.

Move allocating top_table and default_page into another function so that
they do not run again on resume.

Signed-off-by: Peter Bailey <peter.bailey@raspberrypi.com>
Force a runtime suspend during system suspend.

This prevents occasional warnings about packet RAM being off.

Signed-off-by: Peter Bailey <peter.bailey@raspberrypi.com>
Use the drm_mode_config_helper_ functions on suspend and resume.

Signed-off-by: Peter Bailey <peter.bailey@raspberrypi.com>
Reinitialise the hardware on resume.

Split vc4_hvs_upload_linear_kernel into two functions. On resume, the
kernels have already been allocated. Move writing to the hardware into
another function and call that on resume.

Signed-off-by: Peter Bailey <peter.bailey@raspberrypi.com>
Use the suspend/resume methods for hibernation.

Signed-off-by: Peter Bailey <peter.bailey@raspberrypi.com>
In S3 deep-sleep, the entire VPU, ARM, GIC infrastructure is
powered off. Re-initialise the mailbox hardware on resume
otherwise, the VPU won't see mailbox requests.

Signed-off-by: Tim Gover <tim.gover@raspberrypi.com>
Enable the watchdog for device power management.
Use a short timeout before warning.

Signed-off-by: Peter Bailey <peter.bailey@raspberrypi.com>
Save the pin mux and pad during suspend and restore it during resume.

Multiple pins are stored in each register. Store the entire register for
each pin. The values will not change between storing and restoring the
individual pins. This uses a few extra bytes but simplifies the code.

Signed-off-by: Peter Bailey <peter.bailey@raspberrypi.com>
Re-enable interrupts in start_rx.

This fixes the serial console ignoring input after a system suspend with
no_console_suspend set.

Signed-off-by: Peter Bailey <peter.bailey@raspberrypi.com>
The registers get reset during suspend. Restore them on resume.

Signed-off-by: Peter Bailey <peter.bailey@raspberrypi.com>
There are 32 individual event bits of which 4 correspond to mailbox
channels.

Limit the IRQ handler to signalling mailbox events on actual mailboxes,
to prevent all-1s completions (such as when the link is down) or RP1
firmware bugs from causing null pointer dereferences.

Signed-off-by: Jonathan Bell <jonathan@raspberrypi.com>
Rerun the sdhci initialisation on resume.

Signed-off-by: Peter Bailey <peter.bailey@raspberrypi.com>
On suspend, check if there is a current job being processed.
Suspending takes longer than a pisp job so assume it will be done
and cancel suspending if it is not.

On resume, restore hardware registers.

Signed-off-by: Peter Bailey <peter.bailey@raspberrypi.com>
Use the modeset helper functions and disable the clock in suspend.

Signed-off-by: Peter Bailey <peter.bailey@raspberrypi.com>
On a Raspberry Pi 5, the bluetooth is powered off in system suspend.

Add a property for this behaviour.

Signed-off-by: Peter Bailey <peter.bailey@raspberrypi.com>
Implement the power-off-in-suspend property so that bluetooth is
handled correctly after suspend. It is powered off so reprobe the
device again on resume.

Signed-off-by: Peter Bailey <peter.bailey@raspberrypi.com>
Add a device tree property so that bluetooth is handled correctly after
suspend.

It is powered off so the device is registered again on resume.

Signed-off-by: Peter Bailey <peter.bailey@raspberrypi.com>
This adds a regulator for pcie1 using a pin on the RP1.
It needs a hack to order pcie2 before pcie1 so that the
regulator is found by the pcie-brcmstb driver.

Signed-off-by: Peter Bailey <peter.bailey@raspberrypi.com>
…other alarm

This clears the pending alarm when resuming from system suspend
and when setting another alarm.

Previously, If the rtc was used to wake from suspend more than one
time in a row, It would fail.

If the alarm is set and the system is suspended, resumed and suspended
before the alarm occurs, the alarm will still wake the system.

Signed-off-by: Peter Bailey <peter.bailey@raspberrypi.com>
This prevents the camera from sometimes freezing
when using rpicam-hello -t 0

Signed-off-by: Peter Bailey <peter.bailey@raspberrypi.com>
Use the modeset helper functions and poweroff the DAC in suspend.

Signed-off-by: Peter Bailey <peter.bailey@raspberrypi.com>
Add a device tree property to indicate that the xHCI controller will be
reset over suspend.

Signed-off-by: Peter Bailey <peter.bailey@raspberrypi.com>
Use the snps,power-off-in-s2ram property to set power_lost in xhci-plat

Signed-off-by: Peter Bailey <peter.bailey@raspberrypi.com>
The RP1 will reset the USB controllers over suspend.

Add snps,power-off-in-suspend to its device tree.

Signed-off-by: Peter Bailey <peter.bailey@raspberrypi.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants