From eb92b74395d62e2c8b7600c50bff9fa95c778ffd Mon Sep 17 00:00:00 2001 From: Peter Bailey Date: Tue, 21 Jul 2026 15:55:04 +0100 Subject: [PATCH 01/25] arm64: configs: bcm2712: Enable CONFIG_SUSPEND Add support for suspend to RAM on bcm2712 Signed-off-by: Peter Bailey --- arch/arm64/configs/bcm2712_defconfig | 3 --- 1 file changed, 3 deletions(-) diff --git a/arch/arm64/configs/bcm2712_defconfig b/arch/arm64/configs/bcm2712_defconfig index c5573cf1bd11c0..5f88ef0f963812 100644 --- a/arch/arm64/configs/bcm2712_defconfig +++ b/arch/arm64/configs/bcm2712_defconfig @@ -50,7 +50,6 @@ CONFIG_ARM64_ERRATUM_2441009=y CONFIG_ARM64_16K_PAGES=y CONFIG_ARM64_VA_BITS_47=y CONFIG_NR_CPUS=4 -CONFIG_HOTPLUG_CPU=y CONFIG_NUMA=y CONFIG_COMPAT=y CONFIG_ARMV8_DEPRECATED=y @@ -59,8 +58,6 @@ 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_PM=y CONFIG_PM_DEBUG=y CONFIG_CPU_IDLE=y CONFIG_CPU_FREQ=y From d2be4e2561f3ff4ba48b43b0b34798a57ef546b7 Mon Sep 17 00:00:00 2001 From: Peter Bailey Date: Tue, 21 Jul 2026 15:56:52 +0100 Subject: [PATCH 02/25] watchdog: bcm2835: Stop watchdog during suspend/resume Stop the watchdog during suspend to that it does not reset while suspended 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 8df5a5b73e62151248dd2cd9bb091d821f77ff76 Mon Sep 17 00:00:00 2001 From: Peter Bailey Date: Tue, 21 Jul 2026 16:14:14 +0100 Subject: [PATCH 03/25] arm64: dts: bcm2712: wifi: Power off wifi during suspend/resume 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 --- 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 dfe038e6d8aaf89872173de1a4f542fae374aab8 Mon Sep 17 00:00:00 2001 From: Peter Bailey Date: Thu, 23 Jul 2026 16:04:24 +0100 Subject: [PATCH 04/25] 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 f8692b158a85d6b6fdfd2984653ea497dfa07e0e Mon Sep 17 00:00:00 2001 From: Peter Bailey Date: Wed, 12 Aug 2026 15:49:49 +0100 Subject: [PATCH 05/25] iommu: bcm2712-iommu: Add suspend/resume support Disable the iommu on suspend and reinitialise it on resume. Only allocate top_table and default_page on first initialisation. nmapped_pages is already 0 from kzalloc on the first initialisation. Prevent it from being reset to 0 on subsequent initialisations. 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 97a4845d9885a0564371028cda4d6e2ba8016c22 Mon Sep 17 00:00:00 2001 From: Peter Bailey Date: Wed, 12 Aug 2026 15:56:37 +0100 Subject: [PATCH 06/25] drm/vc4: hdmi: Add suspend/resume support Force a runtime suspend during system suspend. This prevents ccasional warnings about packet RAM being off. 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 fe0f6a4ab9d288fa7ce64e7498da9c781eec00a4 Mon Sep 17 00:00:00 2001 From: Peter Bailey Date: Wed, 12 Aug 2026 15:57:58 +0100 Subject: [PATCH 07/25] drm/vc4: Add suspend/resume support Use the drm_mode_config_helper_ functions on suspend and resume. 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 1cea7043329ae89b98898fe85f2bcea16d692bdf Mon Sep 17 00:00:00 2001 From: Peter Bailey Date: Wed, 12 Aug 2026 15:58:29 +0100 Subject: [PATCH 08/25] drm/vc4: hvs: Add suspend/resume support 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 --- 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 3bb6caeae407a5bf9a150c5633fa611b77a330c8 Mon Sep 17 00:00:00 2001 From: Peter Bailey Date: Fri, 14 Aug 2026 14:22:55 +0100 Subject: [PATCH 09/25] gpio-brcmstb: Enable hibernation support Use the suspend/resume methods for hibernation. Signed-off-by: Peter Bailey --- drivers/gpio/gpio-brcmstb.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpio/gpio-brcmstb.c b/drivers/gpio/gpio-brcmstb.c index 7e2e3f3829efec..e004c4b1cde38a 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,7 @@ 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, + SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(brcmstb_gpio_suspend, brcmstb_gpio_resume) }; static int brcmstb_gpio_probe(struct platform_device *pdev) From 45a2c428295d81ba1cff68946624b4d4894e9137 Mon Sep 17 00:00:00 2001 From: Tim Gover Date: Mon, 17 Aug 2026 08:42:25 +0100 Subject: [PATCH 10/25] 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, }; From 467598860ddd520375385781585fa7171b8dde6c Mon Sep 17 00:00:00 2001 From: Peter Bailey Date: Wed, 2 Sep 2026 09:51:15 +0100 Subject: [PATCH 11/25] arm64: configs: bcm2712: Enable DPM watchdog Enable the watchdog for device power management. Use a short timeout before warning. Signed-off-by: Peter Bailey --- arch/arm64/configs/bcm2712_defconfig | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arch/arm64/configs/bcm2712_defconfig b/arch/arm64/configs/bcm2712_defconfig index 5f88ef0f963812..8f7457461d9b1d 100644 --- a/arch/arm64/configs/bcm2712_defconfig +++ b/arch/arm64/configs/bcm2712_defconfig @@ -59,6 +59,9 @@ CONFIG_SETEND_EMULATION=y CONFIG_RANDOMIZE_BASE=y CONFIG_CMDLINE="console=ttyAMA0,115200 kgdboc=ttyAMA0,115200 root=/dev/mmcblk0p2 rootfstype=ext4 rootwait" CONFIG_PM_DEBUG=y +CONFIG_DPM_WATCHDOG=y +CONFIG_DPM_WATCHDOG_TIMEOUT=30 +CONFIG_DPM_WATCHDOG_WARNING_TIMEOUT=5 CONFIG_CPU_IDLE=y CONFIG_CPU_FREQ=y CONFIG_CPU_FREQ_STAT=y From a742d4b684e9b22350c7ad421a04cdafeb7d132b Mon Sep 17 00:00:00 2001 From: Peter Bailey Date: Wed, 2 Sep 2026 09:55:15 +0100 Subject: [PATCH 12/25] arm64: configs: bcm2712: Enable hibernation Add support for hibernation on bcm2712 Signed-off-by: Peter Bailey --- arch/arm64/configs/bcm2712_defconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm64/configs/bcm2712_defconfig b/arch/arm64/configs/bcm2712_defconfig index 8f7457461d9b1d..1a29136456b354 100644 --- a/arch/arm64/configs/bcm2712_defconfig +++ b/arch/arm64/configs/bcm2712_defconfig @@ -58,6 +58,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_HIBERNATION=y CONFIG_PM_DEBUG=y CONFIG_DPM_WATCHDOG=y CONFIG_DPM_WATCHDOG_TIMEOUT=30 From 6ed825c13c60b438bf4dae6b1dc77ba1c268a72a Mon Sep 17 00:00:00 2001 From: Peter Bailey Date: Mon, 17 Aug 2026 17:37:30 +0100 Subject: [PATCH 13/25] pinctrl: pinctrl-brcmstb: Save pin mux and pad during suspend 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 --- drivers/pinctrl/bcm/pinctrl-brcmstb-bcm2712.c | 6 +++ drivers/pinctrl/bcm/pinctrl-brcmstb.c | 49 +++++++++++++++++++ drivers/pinctrl/bcm/pinctrl-brcmstb.h | 2 + 3 files changed, 57 insertions(+) diff --git a/drivers/pinctrl/bcm/pinctrl-brcmstb-bcm2712.c b/drivers/pinctrl/bcm/pinctrl-brcmstb-bcm2712.c index 752b78e2c0d8ce..befd6680cd69b9 100644 --- a/drivers/pinctrl/bcm/pinctrl-brcmstb-bcm2712.c +++ b/drivers/pinctrl/bcm/pinctrl-brcmstb-bcm2712.c @@ -13,6 +13,7 @@ #include #include +#include #include "pinctrl-brcmstb.h" #define BRCMSTB_FSEL_COUNT 8 @@ -729,12 +730,17 @@ static const struct of_device_id bcm2712_pinctrl_match[] = { }; MODULE_DEVICE_TABLE(of, bcm2712_pinctrl_match); +static const struct dev_pm_ops bcm2712_pinctrl_pm_ops = { + SET_LATE_SYSTEM_SLEEP_PM_OPS(brcmstb_pinctrl_suspend, brcmstb_pinctrl_resume) +}; + static struct platform_driver bcm2712_pinctrl_driver = { .probe = bcm2712_pinctrl_probe, .driver = { .name = "pinctrl-bcm2712", .of_match_table = bcm2712_pinctrl_match, .suppress_bind_attrs = true, + .pm = &bcm2712_pinctrl_pm_ops, }, }; module_platform_driver(bcm2712_pinctrl_driver); diff --git a/drivers/pinctrl/bcm/pinctrl-brcmstb.c b/drivers/pinctrl/bcm/pinctrl-brcmstb.c index f46b27155c3c40..b32822558bbc4d 100644 --- a/drivers/pinctrl/bcm/pinctrl-brcmstb.c +++ b/drivers/pinctrl/bcm/pinctrl-brcmstb.c @@ -49,6 +49,8 @@ struct brcmstb_pinctrl { struct pinctrl_gpio_range gpio_range; /* Protect FSEL registers */ spinlock_t fsel_lock; + u32 *saved_mux; + u32 *saved_pad; }; static unsigned int brcmstb_pinctrl_fsel_get(struct brcmstb_pinctrl *pc, @@ -422,6 +424,13 @@ int brcmstb_pinctrl_probe(struct platform_device *pdev) pc->func_count = pdata->func_count; pc->func_names = pdata->func_names; + pc->saved_mux = devm_kcalloc(dev, num_pins, sizeof(*pc->saved_mux), + GFP_KERNEL); + pc->saved_pad = devm_kcalloc(dev, num_pins, sizeof(*pc->saved_pad), + GFP_KERNEL); + if (!pc->saved_mux || !pc->saved_pad) + return -ENOMEM; + pc->pctl_dev = devm_pinctrl_register(dev, &pc->pctl_desc, pc); if (IS_ERR(pc->pctl_dev)) return dev_err_probe(&pdev->dev, PTR_ERR(pc->pctl_dev), @@ -434,6 +443,46 @@ int brcmstb_pinctrl_probe(struct platform_device *pdev) } EXPORT_SYMBOL(brcmstb_pinctrl_probe); +int brcmstb_pinctrl_suspend(struct device *dev) +{ + struct brcmstb_pinctrl *pc = dev_get_drvdata(dev); + unsigned int i, num_pins = pc->pctl_desc.npins; + u32 bit; + + for (i = 0; i < num_pins; i++) { + bit = pc->pin_regs[i].mux_bit; + if (bit) + pc->saved_mux[i] = readl(pc->base + BIT_TO_REG(bit & ~MUX_BIT_VALID)); + + bit = pc->pin_regs[i].pad_bit; + if (bit != PAD_BIT_INVALID) + pc->saved_pad[i] = readl(pc->base + BIT_TO_REG(bit)); + } + + return 0; +} +EXPORT_SYMBOL(brcmstb_pinctrl_suspend); + +int brcmstb_pinctrl_resume(struct device *dev) +{ + struct brcmstb_pinctrl *pc = dev_get_drvdata(dev); + unsigned int i, num_pins = pc->pctl_desc.npins; + u32 bit; + + for (i = 0; i < num_pins; i++) { + bit = pc->pin_regs[i].mux_bit; + if (bit) + writel(pc->saved_mux[i], pc->base + BIT_TO_REG(bit & ~MUX_BIT_VALID)); + + bit = pc->pin_regs[i].pad_bit; + if (bit != PAD_BIT_INVALID) + writel(pc->saved_pad[i], pc->base + BIT_TO_REG(bit)); + } + + return 0; +} +EXPORT_SYMBOL(brcmstb_pinctrl_resume); + MODULE_AUTHOR("Phil Elwell"); MODULE_AUTHOR("Jonathan Bell"); MODULE_AUTHOR("Ivan T. Ivanov"); diff --git a/drivers/pinctrl/bcm/pinctrl-brcmstb.h b/drivers/pinctrl/bcm/pinctrl-brcmstb.h index c3459103e05639..e5f8049eb0e517 100644 --- a/drivers/pinctrl/bcm/pinctrl-brcmstb.h +++ b/drivers/pinctrl/bcm/pinctrl-brcmstb.h @@ -89,5 +89,7 @@ struct brcmstb_pdata { }; int brcmstb_pinctrl_probe(struct platform_device *pdev); +int brcmstb_pinctrl_suspend(struct device *dev); +int brcmstb_pinctrl_resume(struct device *dev); #endif From 181495d51c5916023e87ddee0de2f37e4548b55b Mon Sep 17 00:00:00 2001 From: Peter Bailey Date: Tue, 18 Aug 2026 15:22:44 +0100 Subject: [PATCH 14/25] serial: amba-pl011: Add start_rx to re-enable interrupts 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 --- drivers/tty/serial/amba-pl011.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/drivers/tty/serial/amba-pl011.c b/drivers/tty/serial/amba-pl011.c index 7068245dcbfd94..4757c1c2251536 100644 --- a/drivers/tty/serial/amba-pl011.c +++ b/drivers/tty/serial/amba-pl011.c @@ -1457,6 +1457,24 @@ static void pl011_stop_rx(struct uart_port *port) pl011_dma_rx_stop(uap); } +static void pl011_start_rx(struct uart_port *port) +{ + struct uart_amba_port *uap = + container_of(port, struct uart_amba_port, port); + + uap->im |= UART011_RTIM; + if (!pl011_dma_rx_running(uap)) + uap->im |= UART011_RXIM; + pl011_write(uap->im, uap, REG_IMSC); + +#ifdef CONFIG_DMA_ENGINE + if (uap->using_rx_dma) { + uap->dmacr |= UART011_RXDMAE; + pl011_write(uap->dmacr, uap, REG_DMACR); + } +#endif +} + static void pl011_throttle_rx(struct uart_port *port) { unsigned long flags; @@ -2334,6 +2352,7 @@ static const struct uart_ops amba_pl011_pops = { .stop_tx = pl011_stop_tx, .start_tx = pl011_start_tx, .stop_rx = pl011_stop_rx, + .start_rx = pl011_start_rx, .throttle = pl011_throttle_rx, .unthrottle = pl011_unthrottle_rx, .enable_ms = pl011_enable_ms, @@ -2368,6 +2387,7 @@ static const struct uart_ops sbsa_uart_pops = { .stop_tx = pl011_stop_tx, .start_tx = pl011_start_tx, .stop_rx = pl011_stop_rx, + .start_rx = pl011_start_rx, .startup = sbsa_uart_startup, .shutdown = sbsa_uart_shutdown, .set_termios = sbsa_uart_set_termios, From bf45fa3f4adede43608e46680c3339f10e309763 Mon Sep 17 00:00:00 2001 From: Peter Bailey Date: Tue, 18 Aug 2026 17:19:59 +0100 Subject: [PATCH 15/25] drm/vc4: hdmi: Ignore the hsm clock on resume The firmware reports that the HSM clock is 0 after resume. The HDMI runtime resume will fail early if it is 0 to prevent stalling on memory accesses. Ignore the check for now. Signed-off-by: Peter Bailey --- drivers/gpu/drm/vc4/vc4_hdmi.c | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index 899940f98f3410..3479b46b0eda70 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -3091,19 +3091,13 @@ static int vc4_hdmi_runtime_resume(struct device *dev) return ret; /* - * Whenever the RaspberryPi boots without an HDMI monitor - * plugged in, the firmware won't have initialized the HSM clock - * rate and it will be reported as 0. - * - * If we try to access a register of the controller in such a - * case, it will lead to a silent CPU stall. Let's make sure we - * prevent such a case. + * The clock rate may be reported wrong after suspend to RAM. + * This may cause a cpu stall. */ rate = clk_get_rate(vc4_hdmi->hsm_clock); - if (!rate) { - ret = -EINVAL; - goto err_disable_clk; - } + if (!rate) + dev_warn(&vc4_hdmi->pdev->dev, + "hsm_clock rate is 0, proceeding anyway\n"); ret = clk_prepare_enable(vc4_hdmi->audio_clock); if (ret) From fd1ceb6faf4315036a786ab3c55c2be73a8c9a20 Mon Sep 17 00:00:00 2001 From: Peter Bailey Date: Tue, 18 Aug 2026 18:00:47 +0100 Subject: [PATCH 16/25] irqchip: bcm2712-mip: Restore registers after suspend The registers get reset during suspend. Restore them on resume. Signed-off-by: Peter Bailey --- drivers/irqchip/irq-bcm2712-mip.c | 40 +++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/drivers/irqchip/irq-bcm2712-mip.c b/drivers/irqchip/irq-bcm2712-mip.c index 4761974ad650a9..a2b593f1e5cc16 100644 --- a/drivers/irqchip/irq-bcm2712-mip.c +++ b/drivers/irqchip/irq-bcm2712-mip.c @@ -10,6 +10,7 @@ #include #include #include +#include #include @@ -170,6 +171,19 @@ static const struct msi_parent_ops mip_msi_parent_ops = { .init_dev_msi_info = msi_lib_init_dev_msi_info, }; +static void mip_hw_init(struct mip_priv *mip) +{ + /* + * All MSI-X unmasked for the host, masked for the VPU, and edge-triggered. + */ + writel(0, mip->base + MIP_INT_MASKL_HOST); + writel(0, mip->base + MIP_INT_MASKH_HOST); + writel(~0, mip->base + MIP_INT_MASKL_VPU); + writel(~0, mip->base + MIP_INT_MASKH_VPU); + writel(~0, mip->base + MIP_INT_CFGL_HOST); + writel(~0, mip->base + MIP_INT_CFGH_HOST); +} + static int mip_init_domains(struct mip_priv *mip, struct device_node *np) { struct irq_domain_info info = { @@ -184,15 +198,7 @@ static int mip_init_domains(struct mip_priv *mip, struct device_node *np) if (!msi_create_parent_irq_domain(&info, &mip_msi_parent_ops)) return -ENOMEM; - /* - * All MSI-X unmasked for the host, masked for the VPU, and edge-triggered. - */ - writel(0, mip->base + MIP_INT_MASKL_HOST); - writel(0, mip->base + MIP_INT_MASKH_HOST); - writel(~0, mip->base + MIP_INT_MASKL_VPU); - writel(~0, mip->base + MIP_INT_MASKH_VPU); - writel(~0, mip->base + MIP_INT_CFGL_HOST); - writel(~0, mip->base + MIP_INT_CFGH_HOST); + mip_hw_init(mip); return 0; } @@ -244,6 +250,7 @@ static int mip_msi_probe(struct platform_device *pdev, struct device_node *paren spin_lock_init(&mip->lock); mip->dev = &pdev->dev; + platform_set_drvdata(pdev, mip); ret = mip_parse_dt(mip, node); if (ret) @@ -279,9 +286,22 @@ static int mip_msi_probe(struct platform_device *pdev, struct device_node *paren return ret; } +static int mip_resume(struct device *dev) +{ + struct mip_priv *mip = dev_get_drvdata(dev); + + mip_hw_init(mip); + + return 0; +} + +static const struct dev_pm_ops mip_pm_ops = { + SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(NULL, mip_resume) +}; + IRQCHIP_PLATFORM_DRIVER_BEGIN(mip_msi) IRQCHIP_MATCH("brcm,bcm2712-mip", mip_msi_probe) -IRQCHIP_PLATFORM_DRIVER_END(mip_msi) +IRQCHIP_PLATFORM_DRIVER_END(mip_msi, .pm = &mip_pm_ops) MODULE_DESCRIPTION("Broadcom BCM2712 MSI-X interrupt controller"); MODULE_AUTHOR("Phil Elwell "); MODULE_AUTHOR("Stanimir Varbanov "); From 58ecfd7cec6d872066360ddcca6d3ad0e4feb471 Mon Sep 17 00:00:00 2001 From: Jonathan Bell Date: Wed, 19 Aug 2026 10:57:39 +0100 Subject: [PATCH 17/25] mailbox: rp1: check received event bits more carefully 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 --- drivers/mailbox/rp1-mailbox.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/mailbox/rp1-mailbox.c b/drivers/mailbox/rp1-mailbox.c index a9a86a9ec89b93..b5044fe0f2e761 100644 --- a/drivers/mailbox/rp1-mailbox.c +++ b/drivers/mailbox/rp1-mailbox.c @@ -66,8 +66,11 @@ static irqreturn_t rp1_mbox_irq(int irq, void *dev_id) while (evs) { doorbell = __ffs(evs); + if (doorbell >= MAX_CHANS) + break; chan = &mbox->controller.chans[doorbell]; - mbox_chan_received_data(chan, NULL); + if (chan) + mbox_chan_received_data(chan, NULL); evs &= ~(1 << doorbell); } return IRQ_HANDLED; From 2acdebad2468a0c6d24892e1d631d91bb36f6ab7 Mon Sep 17 00:00:00 2001 From: Peter Bailey Date: Wed, 19 Aug 2026 15:06:54 +0100 Subject: [PATCH 18/25] mmc: sdhci-brcmstb: Reconfigure on resume Rerun the sdhci initialisation on resume. Signed-off-by: Peter Bailey --- drivers/mmc/host/sdhci-brcmstb.c | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/drivers/mmc/host/sdhci-brcmstb.c b/drivers/mmc/host/sdhci-brcmstb.c index 0b1823ec06c115..2406601093f493 100644 --- a/drivers/mmc/host/sdhci-brcmstb.c +++ b/drivers/mmc/host/sdhci-brcmstb.c @@ -50,8 +50,16 @@ #define SDIO_CFG_MAX_50MHZ_MODE_STRAP_OVERRIDE BIT(31) #define SDIO_CFG_MAX_50MHZ_MODE_ENABLE BIT(0) +struct brcmstb_match_priv { + void (*cfginit)(struct sdhci_host *host); + void (*hs400es)(struct mmc_host *mmc, struct mmc_ios *ios); + struct sdhci_ops *ops; + const unsigned int flags; +}; + struct sdhci_brcmstb_priv { void __iomem *cfg_regs; + const struct brcmstb_match_priv *match_priv; unsigned int flags; struct clk *base_clk; u32 base_freq_hz; @@ -64,13 +72,6 @@ struct sdhci_brcmstb_priv { struct pinctrl_state *pins_sdex; }; -struct brcmstb_match_priv { - void (*cfginit)(struct sdhci_host *host); - void (*hs400es)(struct mmc_host *mmc, struct mmc_ios *ios); - struct sdhci_ops *ops; - const unsigned int flags; -}; - static inline void enable_clock_gating(struct sdhci_host *host) { struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); @@ -600,6 +601,7 @@ static int sdhci_brcmstb_probe(struct platform_device *pdev) pltfm_host->clk = clk; priv = sdhci_pltfm_priv(pltfm_host); + priv->match_priv = match_priv; cqe = 0; device_property_read_u32(&pdev->dev, "supports-cqe", &cqe); if (cqe > 0) { @@ -772,6 +774,9 @@ static int sdhci_brcmstb_resume(struct device *dev) struct sdhci_brcmstb_priv *priv = sdhci_pltfm_priv(pltfm_host); int ret; + if (priv->match_priv->cfginit) + priv->match_priv->cfginit(host); + ret = sdhci_pltfm_resume(dev); if (!ret && priv->base_freq_hz) { ret = clk_prepare_enable(priv->base_clk); From 6edd879d45447805783bc3922edefac4964f5543 Mon Sep 17 00:00:00 2001 From: Peter Bailey Date: Fri, 21 Aug 2026 16:26:43 +0100 Subject: [PATCH 19/25] media: pisp_be: Add suspend/resume support 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 --- .../platform/raspberrypi/pisp_be/pisp_be.c | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/drivers/media/platform/raspberrypi/pisp_be/pisp_be.c b/drivers/media/platform/raspberrypi/pisp_be/pisp_be.c index c6c73097d04022..5ad96500ad5dab 100644 --- a/drivers/media/platform/raspberrypi/pisp_be/pisp_be.c +++ b/drivers/media/platform/raspberrypi/pisp_be/pisp_be.c @@ -1826,8 +1826,29 @@ static void pispbe_remove(struct platform_device *pdev) pm_runtime_disable(pispbe->dev); } +static int pispbe_suspend(struct device *dev) { + struct pispbe_dev *pispbe = dev_get_drvdata(dev); + bool busy; + + scoped_guard(spinlock_irqsave, &pispbe->hw_lock) { + busy = pispbe->hw_busy; + } + + if (busy) + return -EBUSY; + + return 0; +} + +static int pispbe_resume(struct device *dev) +{ + struct pispbe_dev *pispbe = dev_get_drvdata(dev); + return pispbe_hw_init(pispbe); +} + static const struct dev_pm_ops pispbe_pm_ops = { SET_RUNTIME_PM_OPS(pispbe_runtime_suspend, pispbe_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(pispbe_suspend, pispbe_resume) }; static const struct of_device_id pispbe_of_match[] = { From 2c119f07a6f43d8d3dc4f5893bb23bc5e334cf61 Mon Sep 17 00:00:00 2001 From: Peter Bailey Date: Mon, 24 Aug 2026 10:56:24 +0100 Subject: [PATCH 20/25] drm/rp1: dsi: Add suspend/resume support Use the modeset helper functions and disable the clock in suspend. Signed-off-by: Peter Bailey --- drivers/gpu/drm/rp1/rp1-dsi/rp1_dsi.c | 35 +++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/drivers/gpu/drm/rp1/rp1-dsi/rp1_dsi.c b/drivers/gpu/drm/rp1/rp1-dsi/rp1_dsi.c index d452b0b1804e62..3a8d2438f77203 100644 --- a/drivers/gpu/drm/rp1/rp1-dsi/rp1_dsi.c +++ b/drivers/gpu/drm/rp1/rp1-dsi/rp1_dsi.c @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -522,6 +523,39 @@ static void rp1dsi_platform_shutdown(struct platform_device *pdev) rp1dsi_stopall(drm); } +static int rp1dsi_platform_suspend(struct device *dev) +{ + struct drm_device *drm = dev_get_drvdata(dev); + struct rp1_dsi *dsi = drm->dev_private; + int ret; + + ret = drm_mode_config_helper_suspend(drm); + if (ret) + return ret; + + if (dsi->clocks[RP1DSI_CLOCK_CFG]) + clk_disable_unprepare(dsi->clocks[RP1DSI_CLOCK_CFG]); + + return 0; +} + +static int rp1dsi_platform_resume(struct device *dev) +{ + struct drm_device *drm = dev_get_drvdata(dev); + struct rp1_dsi *dsi = drm->dev_private; + int ret; + + if (dsi->clocks[RP1DSI_CLOCK_CFG]) + clk_prepare_enable(dsi->clocks[RP1DSI_CLOCK_CFG]); + + ret = drm_mode_config_helper_resume(drm); + + return ret; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(rp1dsi_pm_ops, rp1dsi_platform_suspend, + rp1dsi_platform_resume); + static const struct of_device_id rp1dsi_of_match[] = { { .compatible = "raspberrypi,rp1dsi", @@ -539,6 +573,7 @@ static struct platform_driver rp1dsi_platform_driver = { .name = DRIVER_NAME, .owner = THIS_MODULE, .of_match_table = rp1dsi_of_match, + .pm = pm_sleep_ptr(&rp1dsi_pm_ops), }, }; From 1f67cabecf97c49663933e43bc2a077a989bc53e Mon Sep 17 00:00:00 2001 From: Peter Bailey Date: Tue, 25 Aug 2026 09:59:16 +0100 Subject: [PATCH 21/25] bluetooth: hci_bcm: Add support for powering off the device in suspend Unregister the device when suspending and then register it again on resume Signed-off-by: Peter Bailey --- .../net/bluetooth/brcm,bluetooth.yaml | 6 ++ drivers/bluetooth/hci_bcm.c | 80 +++++++++++++++++-- 2 files changed, 79 insertions(+), 7 deletions(-) diff --git a/Documentation/devicetree/bindings/net/bluetooth/brcm,bluetooth.yaml b/Documentation/devicetree/bindings/net/bluetooth/brcm,bluetooth.yaml index 3c410cadff2304..fd1f398d914b22 100644 --- a/Documentation/devicetree/bindings/net/bluetooth/brcm,bluetooth.yaml +++ b/Documentation/devicetree/bindings/net/bluetooth/brcm,bluetooth.yaml @@ -109,6 +109,12 @@ properties: if the device's initial baud rate in normal mode is not supported by the host or if the device requires autobaud mode startup before loading FW. + brcm,power-off-in-suspend: + type: boolean + description: + Set this property if the controller is powered off during a system + suspend. The driver will need to reprobe the device on resume. + interrupts: items: - description: Handle to the line HOST_WAKE used to wake diff --git a/drivers/bluetooth/hci_bcm.c b/drivers/bluetooth/hci_bcm.c index fff845ed44e35f..0a4f841bb82d3f 100644 --- a/drivers/bluetooth/hci_bcm.c +++ b/drivers/bluetooth/hci_bcm.c @@ -107,6 +107,9 @@ struct bcm_device_data { * @no_uart_clock_set: UART clock set command for >3Mbps mode is unavailable * @pcm_int_params: keep the initial PCM configuration * @use_autobaud_mode: start Bluetooth device in autobaud mode + * @power_off_in_suspend: controller loses power during a system suspend and + * has to be rebuilt on resume + * @hci_registered: whether the hci_uart currently has a registered hci_dev * @max_autobaud_speed: max baudrate supported by device in autobaud mode */ struct bcm_device { @@ -147,6 +150,8 @@ struct bcm_device { bool drive_rts_on_open; bool no_uart_clock_set; bool use_autobaud_mode; + bool power_off_in_suspend; + bool hci_registered; u8 pcm_int_params[5]; u32 max_autobaud_speed; }; @@ -159,6 +164,8 @@ struct bcm_data { struct bcm_device *dev; }; +static const struct hci_uart_proto bcm_proto; + /* List of BCM BT UART devices */ static DEFINE_MUTEX(bcm_device_lock); static LIST_HEAD(bcm_device_list); @@ -485,6 +492,14 @@ static int bcm_open(struct hci_uart *hu) out: if (bcm->dev) { + /* + * Since bcm_resume() reprobes the device, the suspend handling + * done by the hci_suspend_notifier is not necessary. + */ + if (IS_ENABLED(CONFIG_PM_SLEEP) && hu->serdev && + bcm->dev->power_off_in_suspend) + set_bit(HCI_UART_NO_SUSPEND_NOTIFIER, &hu->flags); + if (bcm->dev->use_autobaud_mode) hci_uart_set_flow_control(hu, false); /* Assert BT_UART_CTS_N */ else if (bcm->dev->drive_rts_on_open) @@ -825,6 +840,21 @@ static int bcm_suspend(struct device *dev) bt_dev_dbg(bdev, "suspend: is_suspended %d", bdev->is_suspended); + /* + * Unregister the device before suspend since it will lose power. + * Flush power_on so that bcm_setup is not running. This avoids a + * timeout when the device is being unregistered during bcm_setup + */ + if (bdev->power_off_in_suspend && bdev->hu && bdev->hci_registered) { + struct hci_dev *hdev = bdev->hu->hdev; + + if (hdev) + disable_work_sync(&hdev->power_on); + + hci_uart_unregister_device(bdev->hu); + bdev->hci_registered = false; + } + /* * When used with a device instantiated as platform_device, bcm_suspend * can be called at any time as long as the platform device is bound, @@ -836,10 +866,11 @@ static int bcm_suspend(struct device *dev) if (!bdev->hu) goto unlock; - if (pm_runtime_active(dev)) + if (!bdev->power_off_in_suspend && pm_runtime_active(dev)) bcm_suspend_device(dev); - if (device_may_wakeup(dev) && bdev->irq > 0) { + if (!bdev->power_off_in_suspend && device_may_wakeup(dev) && + bdev->irq > 0) { error = enable_irq_wake(bdev->irq); if (!error) bt_dev_dbg(bdev, "BCM irq: enabled"); @@ -855,6 +886,7 @@ static int bcm_suspend(struct device *dev) static int bcm_resume(struct device *dev) { struct bcm_device *bdev = dev_get_drvdata(dev); + struct hci_uart *hu; int err = 0; bt_dev_dbg(bdev, "resume: is_suspended %d", bdev->is_suspended); @@ -867,15 +899,18 @@ static int bcm_resume(struct device *dev) */ mutex_lock(&bcm_device_lock); - if (!bdev->hu) + hu = bdev->hu; + if (!hu) goto unlock; - if (device_may_wakeup(dev) && bdev->irq > 0) { + if (!bdev->power_off_in_suspend && device_may_wakeup(dev) && + bdev->irq > 0) { disable_irq_wake(bdev->irq); bt_dev_dbg(bdev, "BCM irq: disabled"); } - err = bcm_resume_device(dev); + if (!bdev->power_off_in_suspend) + err = bcm_resume_device(dev); unlock: mutex_unlock(&bcm_device_lock); @@ -886,6 +921,26 @@ static int bcm_resume(struct device *dev) pm_runtime_enable(dev); } + /* + * On boards flagged with brcm,power-off-in-suspend the controller is + * powered down entirely across a system suspend. + * + * Register the HCI device again. + * + * This has to be outside bcm_device_lock, which bcm_open() takes. + */ + if (hu && hu->serdev && bdev->power_off_in_suspend && + !bdev->hci_registered) { + int rerr; + + rerr = hci_uart_register_device(hu, &bcm_proto); + if (rerr) + dev_err(dev, "Failed to register HCI device: %d\n", + rerr); + else + bdev->hci_registered = true; + } + return 0; } #endif @@ -1229,6 +1284,8 @@ static int bcm_of_probe(struct bcm_device *bdev) { bdev->use_autobaud_mode = device_property_read_bool(bdev->dev, "brcm,requires-autobaud-mode"); + bdev->power_off_in_suspend = device_property_read_bool(bdev->dev, + "brcm,power-off-in-suspend"); device_property_read_u32(bdev->dev, "max-speed", &bdev->oper_speed); device_property_read_u8_array(bdev->dev, "brcm,bt-pcm-int-params", bdev->pcm_int_params, 5); @@ -1557,14 +1614,23 @@ static int bcm_serdev_probe(struct serdev_device *serdev) bcmdev->oper_speed = data->max_speed; } - return hci_uart_register_device(&bcmdev->serdev_hu, &bcm_proto); + err = hci_uart_register_device(&bcmdev->serdev_hu, &bcm_proto); + if (err) + return err; + + bcmdev->hci_registered = true; + + return 0; } static void bcm_serdev_remove(struct serdev_device *serdev) { struct bcm_device *bcmdev = serdev_device_get_drvdata(serdev); - hci_uart_unregister_device(&bcmdev->serdev_hu); + if (bcmdev->hci_registered) { + hci_uart_unregister_device(&bcmdev->serdev_hu); + bcmdev->hci_registered = false; + } } #ifdef CONFIG_OF From 38a83df133955d90ea901f5db1ef36cc91d1ad25 Mon Sep 17 00:00:00 2001 From: Peter Bailey Date: Tue, 25 Aug 2026 10:04:54 +0100 Subject: [PATCH 22/25] arm64: dts: broadcom: bcm2712: Power off bluetooth in suspend Add a device tree property so that bluetooth is handled correctly after suspend. It is powered off so the device must be reprobed on resume. Signed-off-by: Peter Bailey --- .../devicetree/bindings/net/bluetooth/brcm,bluetooth.yaml | 2 +- arch/arm64/boot/dts/broadcom/bcm2712-rpi-5-b.dts | 1 + arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5.dtsi | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/net/bluetooth/brcm,bluetooth.yaml b/Documentation/devicetree/bindings/net/bluetooth/brcm,bluetooth.yaml index fd1f398d914b22..d3cdb95bab7649 100644 --- a/Documentation/devicetree/bindings/net/bluetooth/brcm,bluetooth.yaml +++ b/Documentation/devicetree/bindings/net/bluetooth/brcm,bluetooth.yaml @@ -113,7 +113,7 @@ properties: type: boolean description: Set this property if the controller is powered off during a system - suspend. The driver will need to reprobe the device on resume. + suspend. interrupts: items: 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 c6ff03ba495a8d..c549fd8057e081 100644 --- a/arch/arm64/boot/dts/broadcom/bcm2712-rpi-5-b.dts +++ b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-5-b.dts @@ -374,6 +374,7 @@ dpi_16bit_gpio2: &rp1_dpi_16bit_gpio2 { }; compatible = "brcm,bcm43438-bt"; max-speed = <3000000>; shutdown-gpios = <&gio 29 GPIO_ACTIVE_HIGH>; + brcm,power-off-in-suspend; local-bd-address = [ 00 00 00 00 00 00 ]; }; }; diff --git a/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5.dtsi b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5.dtsi index 76ecc3d2e35826..a3fba8181113b5 100644 --- a/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5.dtsi +++ b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5.dtsi @@ -359,6 +359,7 @@ dpi_16bit_gpio2: &rp1_dpi_16bit_gpio2 { }; compatible = "brcm,bcm43438-bt"; max-speed = <3000000>; shutdown-gpios = <&gio 29 GPIO_ACTIVE_HIGH>; + brcm,power-off-in-suspend; local-bd-address = [ 00 00 00 00 00 00 ]; }; }; From 6e5fcf3318a2be5b29ac82e97d4d80b7cf7f7256 Mon Sep 17 00:00:00 2001 From: Peter Bailey Date: Tue, 25 Aug 2026 15:25:13 +0100 Subject: [PATCH 23/25] arm64: dts: bcm2712: Add a regulator for pcie 3v3 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 --- arch/arm/boot/dts/overlays/README | 2 ++ .../overlays/pciex1-compat-pi5-overlay.dts | 12 +++++++ .../boot/dts/broadcom/bcm2712-rpi-5-b.dts | 29 ++++++++++++++++ .../boot/dts/broadcom/bcm2712-rpi-cm5io.dtsi | 33 +++++++++++++++++++ 4 files changed, 76 insertions(+) diff --git a/arch/arm/boot/dts/overlays/README b/arch/arm/boot/dts/overlays/README index 3ce83a441bfacf..abae5a1469d3c4 100644 --- a/arch/arm/boot/dts/overlays/README +++ b/arch/arm/boot/dts/overlays/README @@ -3992,6 +3992,8 @@ Params: l1ss Enable RC ASPM L1 sub-state support. Requires requires DMA and MSI addresses to be 32bit. mmio-hi Move the start of outbound 32bit addresses to 2GB and expand 64bit outbound space to 14GB. + no-pwr-ctrl Disable the regulator on the PCIe 3v3 supply. + This leaves it always enabled. [ The pcf2127-rtc overlay has been deleted. See i2c-rtc. ] diff --git a/arch/arm/boot/dts/overlays/pciex1-compat-pi5-overlay.dts b/arch/arm/boot/dts/overlays/pciex1-compat-pi5-overlay.dts index 1f5adfc4add10e..e7ca5bf1a08a37 100644 --- a/arch/arm/boot/dts/overlays/pciex1-compat-pi5-overlay.dts +++ b/arch/arm/boot/dts/overlays/pciex1-compat-pi5-overlay.dts @@ -51,10 +51,22 @@ }; }; + /* + * Disable the regulator on the PCIe 3v3 supply. + * Its is enabled by default by the firmware. + */ + fragment@4 { + target = <&pcie1_3v3>; + __dormant__ { + status = "disabled"; + }; + }; + __overrides__ { l1ss = <0>, "+0"; no-l0s = <0>, "+1"; no-mip = <0>, "+2"; mmio-hi = <0>, "+3"; + no-pwr-ctrl = <0>, "+4"; }; }; 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 c549fd8057e081..aec70c4e780887 100644 --- a/arch/arm64/boot/dts/broadcom/bcm2712-rpi-5-b.dts +++ b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-5-b.dts @@ -101,6 +101,17 @@ gpio = <&rp1_gpio 46 0>; // CD1_IO0_MICCLK, to MIPI 1 connector }; + pcie1_3v3: pcie1_3v3 { + compatible = "regulator-fixed"; + regulator-name = "pcie1-3v3"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-boot-on; + enable-active-high; + gpio = <&rp1_gpio 28 GPIO_ACTIVE_HIGH>; // PCIE_RP1_WAKE + startup-delay-us = <20000>; + }; + cam_dummy_reg: cam_dummy_reg { compatible = "regulator-fixed"; regulator-name = "cam-dummy-reg"; @@ -125,6 +136,24 @@ rp1_target: &pcie2 { &pcie1 { brcm,fifo-qos-map = /bits/ 8 <3 3 3 3>; + + /* + * Hack to get linux to probe this pcie after pcie2. + * The pcie-brcmstb driver does not defer probe if + * the regulator is not found yet. This forces pcie1 + * to be probed after pcie2 so that the regulator + * is found. + */ + vpcie3v3-supply = <&pcie1_3v3>; + + pci@0,0 { + device_type = "pci"; + reg = <0 0 0 0 0>; + #address-cells = <3>; + #size-cells = <2>; + ranges; + vpcie3v3-supply = <&pcie1_3v3>; + }; }; // The system SPI for the bootloader EEPROM diff --git a/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5io.dtsi b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5io.dtsi index 4c616a21dc76b7..6eb15ef06bfe08 100644 --- a/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5io.dtsi +++ b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5io.dtsi @@ -1,5 +1,38 @@ // SPDX-License-Identifier: GPL-2.0 +/ { + pcie1_3v3: pcie1_3v3 { + compatible = "regulator-fixed"; + regulator-name = "pcie1-3v3"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-boot-on; + enable-active-high; + gpio = <&rp1_gpio 28 GPIO_ACTIVE_HIGH>; // PCIE_PWR_EN + startup-delay-us = <20000>; + }; +}; + +&pcie1 { + /* + * Hack to get linux to probe this pcie after pcie2. + * The pcie-brcmstb driver does not defer probe if + * the regulator is not found yet. This forces pcie1 + * to be probed after pcie2 so that the regulator + * is found. + */ + vpcie3v3-supply = <&pcie1_3v3>; + + pci@0,0 { + device_type = "pci"; + reg = <0 0 0 0 0>; + #address-cells = <3>; + #size-cells = <2>; + ranges; + vpcie3v3-supply = <&pcie1_3v3>; + }; +}; + i2c_csi_dsi1: &i2c0 { // Note: This is for CAM/DISP 1 connector symlink = "i2c-11"; }; From 6893992cc1462d27d80d1d8126c86a47e44a87cf Mon Sep 17 00:00:00 2001 From: Peter Bailey Date: Tue, 25 Aug 2026 16:13:32 +0100 Subject: [PATCH 24/25] drivers: rtc-rpi: Clear a pending alarm on resume and when setting another 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 --- drivers/rtc/rtc-rpi.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/drivers/rtc/rtc-rpi.c b/drivers/rtc/rtc-rpi.c index 006012333e7891..2d0b6773d916b7 100644 --- a/drivers/rtc/rtc-rpi.c +++ b/drivers/rtc/rtc-rpi.c @@ -108,6 +108,8 @@ static int rpi_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm) u32 data[2] = {RTC_ALARM, rtc_tm_to_time64(&alarm->time)}; int err; + rpi_rtc_alarm_clear_pending(dev); + err = rpi_firmware_property(vrtc->fw, RPI_FIRMWARE_SET_RTC_REG, &data, sizeof(data)); @@ -257,6 +259,19 @@ static int rpi_rtc_probe(struct platform_device *pdev) return devm_rtc_register_device(vrtc->rtc); } +static int rpi_rtc_resume(struct device *dev) +{ + struct rpi_rtc_data *vrtc = dev_get_drvdata(dev); + + rpi_rtc_alarm_clear_pending(dev); + + rtc_update_irq(vrtc->rtc, 1, RTC_AF); + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(rpi_rtc_pm_ops, NULL, rpi_rtc_resume); + static const struct of_device_id rpi_rtc_dt_match[] = { { .compatible = "raspberrypi,rpi-rtc"}, {}, @@ -268,6 +283,7 @@ static struct platform_driver rpi_rtc_driver = { .driver = { .name = "rpi-rtc", .of_match_table = rpi_rtc_dt_match, + .pm = pm_sleep_ptr(&rpi_rtc_pm_ops), }, }; From 17a72542f15c333da288a41c444416d117fc6724 Mon Sep 17 00:00:00 2001 From: Peter Bailey Date: Fri, 28 Aug 2026 11:42:10 +0100 Subject: [PATCH 25/25] media: imx708: Stop streaming during system suspend. This prevents the camera from sometimes freezing when using rpicam-hello -t 0 Signed-off-by: Peter Bailey --- drivers/media/i2c/imx500.c | 50 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/drivers/media/i2c/imx500.c b/drivers/media/i2c/imx500.c index 5fc593340e9588..1e44c54b477783 100644 --- a/drivers/media/i2c/imx500.c +++ b/drivers/media/i2c/imx500.c @@ -3250,6 +3250,50 @@ static int imx500_set_stream(struct v4l2_subdev *sd, int enable) return ret; } +static int __maybe_unused imx500_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx500 *imx500 = to_imx500(sd); + + mutex_lock(&imx500->mutex); + + if (imx500->streaming) + imx500_stop_streaming(imx500); + + mutex_unlock(&imx500->mutex); + + return 0; +} + +static int __maybe_unused imx500_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx500 *imx500 = to_imx500(sd); + int ret; + + mutex_lock(&imx500->mutex); + + if (imx500->streaming) { + ret = imx500_start_streaming(imx500); + if (ret) + goto error; + } + + mutex_unlock(&imx500->mutex); + + return 0; + +error: + imx500_stop_streaming(imx500); + imx500->streaming = false; + + mutex_unlock(&imx500->mutex); + + return ret; +} + static int imx500_get_regulators(struct imx500 *imx500) { struct i2c_client *client = v4l2_get_subdevdata(&imx500->sd); @@ -3734,8 +3778,10 @@ static const struct of_device_id imx500_dt_ids[] = { MODULE_DEVICE_TABLE(of, imx500_dt_ids); -static const struct dev_pm_ops imx500_pm_ops = { SET_RUNTIME_PM_OPS( - imx500_power_off, imx500_power_on, NULL) }; +static const struct dev_pm_ops imx500_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(imx500_suspend, imx500_resume) + SET_RUNTIME_PM_OPS(imx500_power_off, imx500_power_on, NULL) +}; static struct i2c_driver imx500_i2c_driver = { .driver = {