From e88338e5fcfa6eb04524ae053e5224260d406479 Mon Sep 17 00:00:00 2001 From: Peter Bailey Date: Tue, 21 Jul 2026 15:55:04 +0100 Subject: [PATCH 01/10] arm64: configs: Enable CONFIG_SUSPEND Signed-off-by: Peter Bailey --- arch/arm64/configs/bcm2712_defconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/configs/bcm2712_defconfig b/arch/arm64/configs/bcm2712_defconfig index c5573cf1bd11c0..b72788f16212d3 100644 --- a/arch/arm64/configs/bcm2712_defconfig +++ b/arch/arm64/configs/bcm2712_defconfig @@ -59,7 +59,7 @@ CONFIG_CP15_BARRIER_EMULATION=y CONFIG_SETEND_EMULATION=y CONFIG_RANDOMIZE_BASE=y CONFIG_CMDLINE="console=ttyAMA0,115200 kgdboc=ttyAMA0,115200 root=/dev/mmcblk0p2 rootfstype=ext4 rootwait" -# CONFIG_SUSPEND is not set +CONFIG_SUSPEND=y CONFIG_PM=y CONFIG_PM_DEBUG=y CONFIG_CPU_IDLE=y From f06b1e5584e2d982e5937d957cde22fb21592363 Mon Sep 17 00:00:00 2001 From: Peter Bailey Date: Tue, 21 Jul 2026 15:56:52 +0100 Subject: [PATCH 02/10] watchdog: bcm2835: Stop watchdog during suspend/resume Signed-off-by: Peter Bailey --- drivers/watchdog/bcm2835_wdt.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/drivers/watchdog/bcm2835_wdt.c b/drivers/watchdog/bcm2835_wdt.c index ed4ae9fdf9d222..2e7a7ce6297e57 100644 --- a/drivers/watchdog/bcm2835_wdt.c +++ b/drivers/watchdog/bcm2835_wdt.c @@ -232,11 +232,31 @@ static void bcm2835_wdt_remove(struct platform_device *pdev) pm_power_off = NULL; } +static int bcm2835_wdt_suspend(struct device *dev) +{ + if (watchdog_active(&bcm2835_wdt_wdd) || watchdog_hw_running(&bcm2835_wdt_wdd)) + bcm2835_wdt_stop(&bcm2835_wdt_wdd); + + return 0; +} + +static int bcm2835_wdt_resume(struct device *dev) +{ + if (watchdog_active(&bcm2835_wdt_wdd) || watchdog_hw_running(&bcm2835_wdt_wdd)) + bcm2835_wdt_start(&bcm2835_wdt_wdd); + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(bcm2835_wdt_pm_ops, + bcm2835_wdt_suspend, bcm2835_wdt_resume); + static struct platform_driver bcm2835_wdt_driver = { .probe = bcm2835_wdt_probe, .remove = bcm2835_wdt_remove, .driver = { .name = "bcm2835-wdt", + .pm = pm_sleep_ptr(&bcm2835_wdt_pm_ops), }, }; module_platform_driver(bcm2835_wdt_driver); From b07e653f559b0a686414abd64129f9f97943187a Mon Sep 17 00:00:00 2001 From: Peter Bailey Date: Tue, 21 Jul 2026 16:14:14 +0100 Subject: [PATCH 03/10] arm64: dts: broadcom: bcm2712: Power off wireless during suspend/resume Signed-off-by: Peter Bailey --- arch/arm64/boot/dts/broadcom/bcm2712-rpi-5-b.dts | 1 + arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5.dtsi | 1 + 2 files changed, 2 insertions(+) diff --git a/arch/arm64/boot/dts/broadcom/bcm2712-rpi-5-b.dts b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-5-b.dts index c8908f6b3143b5..c6ff03ba495a8d 100644 --- a/arch/arm64/boot/dts/broadcom/bcm2712-rpi-5-b.dts +++ b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-5-b.dts @@ -426,6 +426,7 @@ dpi_16bit_gpio2: &rp1_dpi_16bit_gpio2 { }; vmmc-supply = <&wl_on_reg>; sd-uhs-ddr50; non-removable; + cap-power-off-card; status = "okay"; #address-cells = <1>; #size-cells = <0>; diff --git a/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5.dtsi b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5.dtsi index dfad85a666a8a6..76ecc3d2e35826 100644 --- a/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5.dtsi +++ b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5.dtsi @@ -411,6 +411,7 @@ dpi_16bit_gpio2: &rp1_dpi_16bit_gpio2 { }; vmmc-supply = <&wl_on_reg>; sd-uhs-ddr50; non-removable; + cap-power-off-card; status = "okay"; #address-cells = <1>; #size-cells = <0>; From bd60a513c3cb187b2f0480d7e2bdf24549cc6309 Mon Sep 17 00:00:00 2001 From: Peter Bailey Date: Thu, 23 Jul 2026 16:04:24 +0100 Subject: [PATCH 04/10] ACPI: PCI: bcm2712: Set acpi_storage_d3 to true on bcm2712 This makes the NVMe driver reset drives during suspend/resume. Signed-off-by: Peter Bailey --- include/linux/acpi.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/linux/acpi.h b/include/linux/acpi.h index 5ff5d99f6ead50..d038dc2996e0e4 100644 --- a/include/linux/acpi.h +++ b/include/linux/acpi.h @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -1182,7 +1183,7 @@ static inline int acpi_dev_pm_attach(struct device *dev, bool power_on) } static inline bool acpi_storage_d3(struct device *dev) { - return false; + return of_machine_is_compatible("brcm,bcm2712"); } static inline bool acpi_dev_state_d0(struct device *dev) { From 9b3870277e86d8cee519c42e493651cf2c1eabd8 Mon Sep 17 00:00:00 2001 From: Peter Bailey Date: Wed, 12 Aug 2026 15:49:49 +0100 Subject: [PATCH 05/10] iommu: bcm2712-iommu: Add suspend/resume support Signed-off-by: Peter Bailey --- drivers/iommu/bcm2712-iommu.c | 47 ++++++++++++++++++++++++++++------- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/drivers/iommu/bcm2712-iommu.c b/drivers/iommu/bcm2712-iommu.c index 4721a830d2aecf..33f4104e7a7e1d 100644 --- a/drivers/iommu/bcm2712-iommu.c +++ b/drivers/iommu/bcm2712-iommu.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #define MMU_WR(off, val) writel(val, mmu->reg_base + (off)) @@ -207,18 +208,25 @@ static int bcm2712_iommu_init(struct bcm2712_iommu *mmu) * the aperture does not start from zero), and of the default page. * For simplicity, both these regions are whole Linux pages. */ - u = bcm2712_iommu_get_page(mmu, &mmu->top_table); - if (!u) - return -ENOMEM; + if (mmu->top_table) { + u = (u32)(virt_to_phys(mmu->top_table) >> IOMMU_PAGE_SHIFT); + } else { + u = bcm2712_iommu_get_page(mmu, &mmu->top_table); + if (!u) + return -ENOMEM; + } MMU_WR(MMMU_PT_PA_BASE_OFFSET, u - ((mmu->aperture_base - mmu->dma_iova_offset) >> L1_AP_BASE_SHIFT)); - u = bcm2712_iommu_get_page(mmu, &mmu->default_page); - if (!u) { - bcm2712_iommu_free_page(mmu, mmu->top_table); - return -ENOMEM; + if (mmu->default_page) { + u = (u32)(virt_to_phys(mmu->default_page) >> IOMMU_PAGE_SHIFT); + } else { + u = bcm2712_iommu_get_page(mmu, &mmu->default_page); + if (!u) { + bcm2712_iommu_free_page(mmu, mmu->top_table); + return -ENOMEM; + } } MMU_WR(MMMU_ILLEGAL_ADR_OFFSET, MMMU_ILLEGAL_ADR_ENABLE + u); - mmu->nmapped_pages = 0; /* Flush (and enable) the shared TLB cache; enable this MMU. */ if (mmu->cache) @@ -744,6 +752,26 @@ static void bcm2712_iommu_remove(struct platform_device *pdev) MMU_WR(MMMU_CTRL_OFFSET, 0); /* disable the MMU */ } +static int bcm2712_iommu_suspend(struct device *dev) +{ + struct bcm2712_iommu *mmu = dev_get_drvdata(dev); + + if (mmu->reg_base) + MMU_WR(MMMU_CTRL_OFFSET, 0); /* disable the MMU */ + + return 0; +} + +static int bcm2712_iommu_resume(struct device *dev) +{ + struct bcm2712_iommu *mmu = dev_get_drvdata(dev); + + return bcm2712_iommu_init(mmu); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(bcm2712_iommu_pm_ops, bcm2712_iommu_suspend, + bcm2712_iommu_resume); + static const struct of_device_id bcm2712_iommu_of_match[] = { { . compatible = "brcm,bcm2712-iommu" @@ -756,7 +784,8 @@ static struct platform_driver bcm2712_iommu_driver = { .remove = bcm2712_iommu_remove, .driver = { .name = "bcm2712-iommu", - .of_match_table = bcm2712_iommu_of_match + .of_match_table = bcm2712_iommu_of_match, + .pm = pm_sleep_ptr(&bcm2712_iommu_pm_ops), }, }; From 964750bd679b99915860f4a7168083bbdf5cadda Mon Sep 17 00:00:00 2001 From: Peter Bailey Date: Wed, 12 Aug 2026 15:56:37 +0100 Subject: [PATCH 06/10] drm/vc4: hdmi: Add suspend/resume support Signed-Off-By: Peter Bailey --- drivers/gpu/drm/vc4/vc4_hdmi.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index c111b61175f044..899940f98f3410 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -3441,6 +3441,8 @@ static const struct dev_pm_ops vc4_hdmi_pm_ops = { SET_RUNTIME_PM_OPS(vc4_hdmi_runtime_suspend, vc4_hdmi_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) }; struct platform_driver vc4_hdmi_driver = { From 3a8d259f2ceca6d1fe1d93155d20156762565651 Mon Sep 17 00:00:00 2001 From: Peter Bailey Date: Wed, 12 Aug 2026 15:57:58 +0100 Subject: [PATCH 07/10] drm/vc4: Add suspend/resume support Signed-off-by: Peter Bailey --- drivers/gpu/drm/vc4/vc4_drv.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/drivers/gpu/drm/vc4/vc4_drv.c b/drivers/gpu/drm/vc4/vc4_drv.c index 038e551654ea87..3a72b47f7a7ff1 100644 --- a/drivers/gpu/drm/vc4/vc4_drv.c +++ b/drivers/gpu/drm/vc4/vc4_drv.c @@ -494,6 +494,22 @@ static void vc4_platform_drm_shutdown(struct platform_device *pdev) drm_atomic_helper_shutdown(platform_get_drvdata(pdev)); } +static int vc4_drm_suspend(struct device *dev) +{ + struct drm_device *drm = dev_get_drvdata(dev); + + return drm_mode_config_helper_suspend(drm); +} + +static int vc4_drm_resume(struct device *dev) +{ + struct drm_device *drm = dev_get_drvdata(dev); + + return drm_mode_config_helper_resume(drm); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(vc4_drm_pm_ops, vc4_drm_suspend, vc4_drm_resume); + static const struct of_device_id vc4_of_match[] = { { .compatible = "brcm,bcm2711-vc5", .data = (void *)VC4_GEN_5 }, /* NB GEN_6_C will be corrected on D0 hw to GEN_6_D via vc4_hvs_bind */ @@ -511,6 +527,7 @@ static struct platform_driver vc4_platform_driver = { .driver = { .name = "vc4-drm", .of_match_table = vc4_of_match, + .pm = pm_sleep_ptr(&vc4_drm_pm_ops), }, }; From 50238c7704712b3930a0257295c769a52f7e2450 Mon Sep 17 00:00:00 2001 From: Peter Bailey Date: Wed, 12 Aug 2026 15:58:29 +0100 Subject: [PATCH 08/10] drm/vc4: hvs: Add suspend/resume support Signed-off-by: Peter Bailey --- drivers/gpu/drm/vc4/vc4_hvs.c | 64 ++++++++++++++++++++++++++++------- 1 file changed, 52 insertions(+), 12 deletions(-) diff --git a/drivers/gpu/drm/vc4/vc4_hvs.c b/drivers/gpu/drm/vc4/vc4_hvs.c index 4ae905855bb466..ada90642ea0227 100644 --- a/drivers/gpu/drm/vc4/vc4_hvs.c +++ b/drivers/gpu/drm/vc4/vc4_hvs.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -437,12 +438,28 @@ static const u32 nearest_neighbour_kernel[] = VC4_LINEAR_PHASE_KERNEL(0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 255, 255, 255, 255); +static void vc4_hvs_write_linear_kernel(struct vc4_hvs *hvs, + struct drm_mm_node *space, + const u32 *kernel) +{ + u32 __iomem *dst_kernel = hvs->dlist + space->start; + unsigned int i; + + for (i = 0; i < VC4_KERNEL_DWORDS; i++) { + if (i < VC4_LINEAR_PHASE_KERNEL_DWORDS) + writel(kernel[i], &dst_kernel[i]); + else { + writel(kernel[VC4_KERNEL_DWORDS - i - 1], + &dst_kernel[i]); + } + } +} + static int vc4_hvs_upload_linear_kernel(struct vc4_hvs *hvs, struct drm_mm_node *space, const u32 *kernel) { - int ret, i; - u32 __iomem *dst_kernel; + int ret; /* * NOTE: We don't need a call to drm_dev_enter()/drm_dev_exit() @@ -456,16 +473,7 @@ static int vc4_hvs_upload_linear_kernel(struct vc4_hvs *hvs, return ret; } - dst_kernel = hvs->dlist + space->start; - - for (i = 0; i < VC4_KERNEL_DWORDS; i++) { - if (i < VC4_LINEAR_PHASE_KERNEL_DWORDS) - writel(kernel[i], &dst_kernel[i]); - else { - writel(kernel[VC4_KERNEL_DWORDS - i - 1], - &dst_kernel[i]); - } - } + vc4_hvs_write_linear_kernel(hvs, space, kernel); return 0; } @@ -2109,6 +2117,8 @@ static int vc4_hvs_bind(struct device *dev, struct device *master, void *data) if (IS_ERR(hvs)) return PTR_ERR(hvs); + platform_set_drvdata(pdev, hvs); + hvs->regset.base = hvs->regs; if (vc4->gen == VC4_GEN_6_C) { @@ -2291,6 +2301,35 @@ static void vc4_hvs_dev_remove(struct platform_device *pdev) component_del(&pdev->dev, &vc4_hvs_ops); } +static int vc4_hvs_resume_early(struct device *dev) +{ + struct vc4_hvs *hvs = platform_get_drvdata(to_platform_device(dev)); + struct vc4_dev *vc4; + int ret; + + if (!hvs) + return 0; + + vc4 = hvs->vc4; + if (vc4->gen >= VC4_GEN_6_C) + ret = vc6_hvs_hw_init(hvs); + else + ret = vc4_hvs_hw_init(hvs); + if (ret) + return ret; + + vc4_hvs_write_linear_kernel(hvs, &hvs->mitchell_netravali_filter, + mitchell_netravali_1_3_1_3_kernel); + vc4_hvs_write_linear_kernel(hvs, &hvs->nearest_neighbour_filter, + nearest_neighbour_kernel); + + return vc4_hvs_cob_init(hvs); +} + +static const struct dev_pm_ops vc4_hvs_pm_ops = { + SET_LATE_SYSTEM_SLEEP_PM_OPS(NULL, vc4_hvs_resume_early) +}; + static const struct of_device_id vc4_hvs_dt_match[] = { { .compatible = "brcm,bcm2711-hvs" }, { .compatible = "brcm,bcm2712-hvs" }, @@ -2304,5 +2343,6 @@ struct platform_driver vc4_hvs_driver = { .driver = { .name = "vc4_hvs", .of_match_table = vc4_hvs_dt_match, + .pm = pm_sleep_ptr(&vc4_hvs_pm_ops), }, }; From 0981b46472b468312d8f64a44cf17db13861fceb Mon Sep 17 00:00:00 2001 From: Peter Bailey Date: Fri, 14 Aug 2026 14:22:55 +0100 Subject: [PATCH 09/10] gpio-brcmstb: Enable hibernation support Signed-off-by: Peter Bailey --- drivers/gpio/gpio-brcmstb.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/drivers/gpio/gpio-brcmstb.c b/drivers/gpio/gpio-brcmstb.c index 7e2e3f3829efec..5cdaf2c85f8555 100644 --- a/drivers/gpio/gpio-brcmstb.c +++ b/drivers/gpio/gpio-brcmstb.c @@ -10,6 +10,7 @@ #include #include #include +#include #include enum gio_reg_index { @@ -576,8 +577,15 @@ static int brcmstb_gpio_resume(struct device *dev) #endif /* CONFIG_PM_SLEEP */ static const struct dev_pm_ops brcmstb_gpio_pm_ops = { - .suspend_noirq = brcmstb_gpio_suspend, - .resume_noirq = brcmstb_gpio_resume, + /* + * A hibernate power cycle wipes this GPIO bank's registers, same + * as a suspend-to-RAM transition. Only wiring .suspend_noirq/ + * .resume_noirq left the save/restore logic below unreachable on + * the freeze/thaw and poweroff/restore phases hibernate actually + * uses, so an output pin's level (and anything else save/restore + * covers) was silently lost across hibernate specifically. + */ + SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(brcmstb_gpio_suspend, brcmstb_gpio_resume) }; static int brcmstb_gpio_probe(struct platform_device *pdev) From 6b593a736c5622b7e6b2c3a4e06174bd98a2ede3 Mon Sep 17 00:00:00 2001 From: Tim Gover Date: Mon, 17 Aug 2026 08:42:25 +0100 Subject: [PATCH 10/10] bcm2385: mailbox: Add resumer hander for S3 wakeup on Pi5 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. --- drivers/mailbox/bcm2835-mailbox.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/drivers/mailbox/bcm2835-mailbox.c b/drivers/mailbox/bcm2835-mailbox.c index 8c54980b4b7e12..f458d477bc8aab 100644 --- a/drivers/mailbox/bcm2835-mailbox.c +++ b/drivers/mailbox/bcm2835-mailbox.c @@ -183,6 +183,25 @@ static int bcm2835_mbox_probe(struct platform_device *pdev) return ret; } +static int bcm2835_mbox_resume_noirq(struct device *dev) +{ + struct bcm2835_mbox *mbox = dev_get_drvdata(dev); + + /* + * MAIL0_CNF is reset while the SoC is powered down in suspend-to-RAM. + * Re-enable the receive interrupt before any driver resumes, otherwise + * the replies are never signalled and every firmware transaction times + * out. + */ + writel(ARM_MC_IHAVEDATAIRQEN, mbox->regs + MAIL0_CNF); + + return 0; +} + +static const struct dev_pm_ops bcm2835_mbox_pm_ops = { + NOIRQ_SYSTEM_SLEEP_PM_OPS(NULL, bcm2835_mbox_resume_noirq) +}; + static const struct of_device_id bcm2835_mbox_of_match[] = { { .compatible = "brcm,bcm2835-mbox", }, {}, @@ -193,6 +212,7 @@ static struct platform_driver bcm2835_mbox_driver = { .driver = { .name = "bcm2835-mbox", .of_match_table = bcm2835_mbox_of_match, + .pm = pm_sleep_ptr(&bcm2835_mbox_pm_ops), }, .probe = bcm2835_mbox_probe, };