From a6ddfcd360e42c096973a1f90a62d693e2ff59ec Mon Sep 17 00:00:00 2001 From: Marco Casaroli Date: Wed, 7 Oct 2026 18:05:24 +0200 Subject: [PATCH] arch/arm/rp23xx: Add suspend to RAM in the POWMAN P1.0 state. P1.0 powers the switched core off and keeps the XIP cache and SRAM. Every peripheral loses its registers, so the chip comes back through the bootrom and the ordinary boot, not from the WFI. The idle governor never selects it: an application asks for it with the new BOARDIOC_RP23XX_SUSPEND boardctl() command (arch/chip/pm.h, handled by the common rp23xx board_ioctl()). rp23xx_pm_suspend(): - saves the NVIC, writes a marker to POWMAN SCRATCH0, and arms the wake: the RP23XX_PM_WAKEUP_GPIO pin in a POWMAN power-up detector, and the always-on timer alarm for a timed wake. An armed RTC alarm that comes first powers the chip up itself, so an application can set the wake with RTC_SET_ALARM. Otherwise the RTC alarm is saved with the new rp23xx_rtc_savealarm() and given back after the wake with rp23xx_rtc_restorealarm(). - cleans the XIP cache, with the RP2350-E11 workaround, because the resume discards it and PSRAM can hold task stacks. - requests P1.0 and waits in WFI. On the next boot, __start asks rp23xx_pm_resume_pending() (the marker and CHIP_RESET.HAD_SWCORE_PD) before .bss and .data are touched. For a resume it moves to its own stack, because the idle thread still runs on the idle stack, sets up the hardware with the cold boot code (now rp23xx_hwinit()), and longjmps back to the suspended thread. The UARTs are set up from the driver state in RAM, the PSRAM format is applied again without detection (the part is still in quad mode), the dormant- wake GPIOs are armed again (the IO bank lost them, and the next dormant period could then never end), and the time of day is taken from the always-on timer. Assisted-by: Claude Code:claude-opus-5-5 Signed-off-by: Marco Casaroli --- arch/arm/include/rp23xx/pm.h | 56 ++ arch/arm/src/rp23xx/CMakeLists.txt | 4 + arch/arm/src/rp23xx/Kconfig | 12 + arch/arm/src/rp23xx/Make.defs | 4 + arch/arm/src/rp23xx/rp23xx_pm.c | 35 ++ arch/arm/src/rp23xx/rp23xx_pm.h | 51 ++ arch/arm/src/rp23xx/rp23xx_pm_suspend.c | 565 ++++++++++++++++++ arch/arm/src/rp23xx/rp23xx_psram.c | 16 +- arch/arm/src/rp23xx/rp23xx_rtc.c | 59 ++ arch/arm/src/rp23xx/rp23xx_rtc.h | 13 + arch/arm/src/rp23xx/rp23xx_serial.c | 40 ++ arch/arm/src/rp23xx/rp23xx_serial.h | 13 + arch/arm/src/rp23xx/rp23xx_start.c | 203 +++++-- boards/arm/rp23xx/common/src/CMakeLists.txt | 4 + boards/arm/rp23xx/common/src/Make.defs | 4 + .../arm/rp23xx/common/src/rp23xx_boardctl.c | 77 +++ 16 files changed, 1102 insertions(+), 54 deletions(-) create mode 100644 arch/arm/include/rp23xx/pm.h create mode 100644 arch/arm/src/rp23xx/rp23xx_pm_suspend.c create mode 100644 boards/arm/rp23xx/common/src/rp23xx_boardctl.c diff --git a/arch/arm/include/rp23xx/pm.h b/arch/arm/include/rp23xx/pm.h new file mode 100644 index 00000000000..af49b0fbf98 --- /dev/null +++ b/arch/arm/include/rp23xx/pm.h @@ -0,0 +1,56 @@ +/**************************************************************************** + * arch/arm/include/rp23xx/pm.h + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __ARCH_ARM_INCLUDE_RP23XX_PM_H +#define __ARCH_ARM_INCLUDE_RP23XX_PM_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* boardctl() command: suspend to RAM (POWMAN P1.0). The argument is a + * pointer to struct rp23xx_suspend_s. Needs CONFIG_RP23XX_PM_SUSPEND and + * CONFIG_BOARDCTL_IOCTL. An armed RTC alarm also ends the suspend. + */ + +#define BOARDIOC_RP23XX_SUSPEND (BOARDIOC_USER + 0x0001) + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +struct rp23xx_suspend_s +{ + uint32_t wake_ms; /* In: timed wake in milliseconds, 0 for none */ + uint32_t wake_source; /* Out: POWMAN LAST_SWCORE_PWRUP after the wake */ +}; + +#endif /* __ARCH_ARM_INCLUDE_RP23XX_PM_H */ diff --git a/arch/arm/src/rp23xx/CMakeLists.txt b/arch/arm/src/rp23xx/CMakeLists.txt index 00bd52110f1..304c1cfe7d8 100644 --- a/arch/arm/src/rp23xx/CMakeLists.txt +++ b/arch/arm/src/rp23xx/CMakeLists.txt @@ -123,6 +123,10 @@ if(CONFIG_RP23XX_PM) list(APPEND SRCS rp23xx_pm.c) endif() +if(CONFIG_RP23XX_PM_SUSPEND) + list(APPEND SRCS rp23xx_pm_suspend.c) +endif() + if(CONFIG_RP23XX_OTP) list(APPEND SRCS rp23xx_otp.c) endif() diff --git a/arch/arm/src/rp23xx/Kconfig b/arch/arm/src/rp23xx/Kconfig index 577b370f4fe..32ce2d7afab 100644 --- a/arch/arm/src/rp23xx/Kconfig +++ b/arch/arm/src/rp23xx/Kconfig @@ -946,6 +946,18 @@ config RP23XX_PM_QUIESCE_PADS near 2.2V (erratum RP2350-E9) and its input buffer then draws a static current in every state. +config RP23XX_PM_SUSPEND + bool "Suspend to RAM (POWMAN P1.0)" + default n + select ARCH_SETJMP_H + ---help--- + Power the switched core off, and keep the XIP cache and SRAM. Every + peripheral loses its registers, and the chip comes back through + the boot path, so the idle governor never selects this state. An + application asks for it with the BOARDIOC_RP23XX_SUSPEND boardctl() + command (arch/chip/pm.h, needs BOARDCTL_IOCTL). The wake is a GPIO + (RP23XX_PM_WAKEUP_GPIO) or the always-on timer alarm. + endif # RP23XX_PM config RP23XX_ADC diff --git a/arch/arm/src/rp23xx/Make.defs b/arch/arm/src/rp23xx/Make.defs index ed1b1ae1516..2850c2f6d95 100644 --- a/arch/arm/src/rp23xx/Make.defs +++ b/arch/arm/src/rp23xx/Make.defs @@ -127,6 +127,10 @@ ifeq ($(CONFIG_RP23XX_PM),y) CHIP_CSRCS += rp23xx_pm.c endif +ifeq ($(CONFIG_RP23XX_PM_SUSPEND),y) +CHIP_CSRCS += rp23xx_pm_suspend.c +endif + ifeq ($(CONFIG_RP23XX_OTP),y) CHIP_CSRCS += rp23xx_otp.c endif diff --git a/arch/arm/src/rp23xx/rp23xx_pm.c b/arch/arm/src/rp23xx/rp23xx_pm.c index c5b6b80a4a5..2b2c97658ea 100644 --- a/arch/arm/src/rp23xx/rp23xx_pm.c +++ b/arch/arm/src/rp23xx/rp23xx_pm.c @@ -348,6 +348,13 @@ static uint64_t g_pm_wakeup_gpios; static volatile bool g_pm_woken; +#ifdef CONFIG_RP23XX_PM_SUSPEND +/* Their trigger, to arm them again after a suspend to RAM */ + +static uint64_t g_pm_wakeup_edge; +static uint64_t g_pm_wakeup_high; +#endif + /**************************************************************************** * Private Functions ****************************************************************************/ @@ -720,6 +727,14 @@ int rp23xx_pm_gpio_wakeup(int gpio, bool edge, bool high) rp23xx_pm_wake_irq, NULL); g_pm_wakeup_gpios |= 1ull << gpio; + +#ifdef CONFIG_RP23XX_PM_SUSPEND + g_pm_wakeup_edge = edge ? g_pm_wakeup_edge | (1ull << gpio) : + g_pm_wakeup_edge & ~(1ull << gpio); + g_pm_wakeup_high = high ? g_pm_wakeup_high | (1ull << gpio) : + g_pm_wakeup_high & ~(1ull << gpio); +#endif + return OK; } @@ -740,3 +755,23 @@ int rp23xx_pm_gpio_wakeup_disable(int gpio) g_pm_wakeup_gpios &= ~(1ull << gpio); return OK; } + +#ifdef CONFIG_RP23XX_PM_SUSPEND +/**************************************************************************** + * Name: rp23xx_pm_gpio_wakeup_restore + ****************************************************************************/ + +void rp23xx_pm_gpio_wakeup_restore(void) +{ + int gpio; + + for (gpio = 0; gpio < RP23XX_GPIO_NUM; gpio++) + { + if ((g_pm_wakeup_gpios & (1ull << gpio)) != 0) + { + rp23xx_pm_gpio_wakeup(gpio, (g_pm_wakeup_edge >> gpio) & 1, + (g_pm_wakeup_high >> gpio) & 1); + } + } +} +#endif diff --git a/arch/arm/src/rp23xx/rp23xx_pm.h b/arch/arm/src/rp23xx/rp23xx_pm.h index c7e79307a0d..ca37bc60464 100644 --- a/arch/arm/src/rp23xx/rp23xx_pm.h +++ b/arch/arm/src/rp23xx/rp23xx_pm.h @@ -113,6 +113,57 @@ int rp23xx_pm_gpio_wakeup_disable(int gpio); void rp23xx_pm_pads_quiesce(void); #endif +/**************************************************************************** + * Name: g_pm_resume_stack + * + * Description: + * The stack a resume boots on. The idle stack still holds the frames of + * the idle thread, which the resumed system returns to. + * + ****************************************************************************/ + +#ifdef CONFIG_RP23XX_PM_SUSPEND +# define RP23XX_PM_RESUME_STACK_WORDS (CONFIG_IDLETHREAD_STACKSIZE / 4) + +EXTERN uint32_t g_pm_resume_stack[RP23XX_PM_RESUME_STACK_WORDS]; +#endif + +/**************************************************************************** + * Name: rp23xx_pm_suspend + * + * Description: + * Power the switched core down (POWMAN P1.0) and return after the wake. + * Memory is kept, but every peripheral comes back reset. + * + * Input Parameters: + * wake_ms - Timed wake in milliseconds, or 0 for none. An RTC alarm + * that comes first also wakes the chip. + * + * Returned Value: + * Zero after the resume; a negated errno if the request was refused. + * + * Name: rp23xx_pm_gpio_wakeup_restore + * + * Description: + * Arm the dormant-wake GPIOs again after a resume. + * + * Name: rp23xx_pm_resume_pending / rp23xx_pm_resume + * + * Description: + * Called from __start only. rp23xx_pm_resume_pending() tells if this + * boot is a resume, before .bss and .data are touched. + * rp23xx_pm_resume() then replaces nx_start() and does not return. + * + ****************************************************************************/ + +#ifdef CONFIG_RP23XX_PM_SUSPEND +int rp23xx_pm_suspend(uint32_t wake_ms); +void rp23xx_pm_gpio_wakeup_restore(void); +uint32_t rp23xx_pm_wake_source(void); +bool rp23xx_pm_resume_pending(void); +void rp23xx_pm_resume(void); +#endif + #undef EXTERN #if defined(__cplusplus) } diff --git a/arch/arm/src/rp23xx/rp23xx_pm_suspend.c b/arch/arm/src/rp23xx/rp23xx_pm_suspend.c new file mode 100644 index 00000000000..f037b9eac19 --- /dev/null +++ b/arch/arm/src/rp23xx/rp23xx_pm_suspend.c @@ -0,0 +1,565 @@ +/**************************************************************************** + * arch/arm/src/rp23xx/rp23xx_pm_suspend.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Suspend to RAM in the POWMAN P1.0 state: the switched core is powered off, + * the XIP cache and SRAM keep their contents. Every peripheral loses its + * registers, so the chip comes back through the ordinary boot, and __start + * hands control back to the suspended thread (datasheet 6.2). + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "arm_internal.h" +#include "nvic.h" + +#include "rp23xx_pm.h" +#include "rp23xx_gpio.h" + +#ifdef CONFIG_RTC_ALARM +# include "rp23xx_rtc.h" +#endif + +#include "hardware/rp23xx_powman.h" +#include "hardware/rp23xx_pads_bank0.h" +#include "hardware/rp23xx_memorymap.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* POWMAN ignores a write without this password in the top 16 bits. The + * registers above offset 0xac (SCRATCH, BOOT, interrupts) take none. + */ + +#define POWMAN_PASSWORD 0x5afe0000 +#define POWMAN_SET_ALIAS 0x2000 +#define POWMAN_CLR_ALIAS 0x3000 + +/* STATE.REQ is bits 7:4, and a set bit powers a domain down: bit 3 is + * SWCORE. So P1.0 (switched core down) is 0x8. + */ + +#define POWMAN_STATE_P1_0 (0x8 << 4) + +/* XIP cache clean by set/way, through the top of the maintenance window */ + +#define XIP_CACHE_SIZE (16 * 1024) +#define XIP_CACHE_LINE_SIZE 8 +#define XIP_CACHE_CLEAN_BASE (0x18000000 + 0x04000000 - XIP_CACHE_SIZE + 1) + +/* Left in SCRATCH0 (always-on domain) to tell the next boot it is a resume */ + +#define POWMAN_SUSPEND_MAGIC 0x50575231 /* 'PWR1' */ + +/* The NVIC registers saved over a suspend: 52 interrupts */ + +#define RP23XX_PM_NVIC_ENABLE_REGS 2 +#define RP23XX_PM_NVIC_PRIO_REGS 13 + +/* The trigger of the wake GPIO */ + +#ifdef CONFIG_RP23XX_PM_WAKEUP_GPIO_EDGE +# define RP23XX_PM_SUSPEND_WAKE_EDGE true +#else +# define RP23XX_PM_SUSPEND_WAKE_EDGE false +#endif + +#ifdef CONFIG_RP23XX_PM_WAKEUP_GPIO_HIGH +# define RP23XX_PM_SUSPEND_WAKE_HIGH true +#else +# define RP23XX_PM_SUSPEND_WAKE_HIGH false +#endif + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct rp23xx_pm_nvic_s +{ + uint32_t enable[RP23XX_PM_NVIC_ENABLE_REGS]; + uint32_t prio[RP23XX_PM_NVIC_PRIO_REGS]; + uint32_t systick_ctrl; + uint32_t systick_reload; + uint32_t shpr2; + uint32_t shpr3; + uint32_t vectab; +}; + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +uint32_t g_pm_resume_stack[RP23XX_PM_RESUME_STACK_WORDS] + __attribute__((aligned(8))); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/* Where the resume returns to. .bss is kept over P1.0. */ + +static jmp_buf g_suspend_ctx; + +/* What woke the chip, for the caller */ + +static volatile uint32_t g_suspend_wake_source; + +/* True while the timed wake uses the alarm comparator. The application + * alarm it replaced is saved here and given back after the resume. + */ + +static bool g_suspend_wake_armed; + +#ifdef CONFIG_RTC_ALARM +static struct rp23xx_alarm_state_s g_suspend_alarm; + +/* True while the application alarm itself is the timed wake */ + +static bool g_suspend_alarm_wake; +#endif + +/* The NVIC is in the switched core and comes back cleared */ + +static struct rp23xx_pm_nvic_s g_suspend_nvic; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static inline void powman_write(uint32_t reg, uint32_t value) +{ + putreg32(POWMAN_PASSWORD | (value & 0xffff), reg); +} + +static inline void powman_setbits(uint32_t reg, uint32_t bits) +{ + putreg32(POWMAN_PASSWORD | bits, reg + POWMAN_SET_ALIAS); +} + +static inline void powman_clrbits(uint32_t reg, uint32_t bits) +{ + putreg32(POWMAN_PASSWORD | bits, reg + POWMAN_CLR_ALIAS); +} + +/**************************************************************************** + * Name: rp23xx_pm_xip_clean + * + * Description: + * Write dirty XIP cache lines back to PSRAM, by set/way through the top + * of the maintenance window to avoid erratum RP2350-E11. The resume boot + * discards the cache, and PSRAM can hold task stacks. + * + ****************************************************************************/ + +static void rp23xx_pm_xip_clean(void) +{ + uintptr_t addr; + + for (addr = XIP_CACHE_CLEAN_BASE; + addr < XIP_CACHE_CLEAN_BASE + XIP_CACHE_SIZE; + addr += XIP_CACHE_LINE_SIZE) + { + putreg8(0, addr); + } + + UP_DSB(); + UP_ISB(); +} + +/**************************************************************************** + * Name: rp23xx_pm_nvic_save / rp23xx_pm_nvic_restore + * + * Description: + * Save and restore the NVIC. up_irqinitialize() cannot be used: it would + * also reset the handler table, which RAM still holds. + * + ****************************************************************************/ + +static void rp23xx_pm_nvic_save(void) +{ + int i; + + for (i = 0; i < RP23XX_PM_NVIC_ENABLE_REGS; i++) + { + g_suspend_nvic.enable[i] = getreg32(NVIC_IRQ_ENABLE(i * 32)); + } + + for (i = 0; i < RP23XX_PM_NVIC_PRIO_REGS; i++) + { + g_suspend_nvic.prio[i] = getreg32(NVIC_IRQ0_3_PRIORITY + i * 4); + } + + g_suspend_nvic.systick_ctrl = getreg32(NVIC_SYSTICK_CTRL); + g_suspend_nvic.systick_reload = getreg32(NVIC_SYSTICK_RELOAD); + g_suspend_nvic.shpr2 = getreg32(NVIC_SYSH8_11_PRIORITY); + g_suspend_nvic.shpr3 = getreg32(NVIC_SYSH12_15_PRIORITY); + g_suspend_nvic.vectab = getreg32(NVIC_VECTAB); +} + +static void rp23xx_pm_nvic_restore(void) +{ + int i; + + putreg32(g_suspend_nvic.vectab, NVIC_VECTAB); + + for (i = 0; i < RP23XX_PM_NVIC_PRIO_REGS; i++) + { + putreg32(g_suspend_nvic.prio[i], NVIC_IRQ0_3_PRIORITY + i * 4); + } + + putreg32(g_suspend_nvic.shpr2, NVIC_SYSH8_11_PRIORITY); + putreg32(g_suspend_nvic.shpr3, NVIC_SYSH12_15_PRIORITY); + + putreg32(g_suspend_nvic.systick_reload, NVIC_SYSTICK_RELOAD); + putreg32(0, NVIC_SYSTICK_CURRENT); + putreg32(g_suspend_nvic.systick_ctrl, NVIC_SYSTICK_CTRL); + + /* Enables last, so nothing fires before its priority is back */ + + for (i = 0; i < RP23XX_PM_NVIC_ENABLE_REGS; i++) + { + putreg32(g_suspend_nvic.enable[i], NVIC_IRQ_ENABLE(i * 32)); + } +} + +/**************************************************************************** + * Name: rp23xx_pm_wake_release + * + * Description: + * Disarm the timed wake and give the alarm comparator back to the + * application alarm, if one was armed. + * + ****************************************************************************/ + +static void rp23xx_pm_wake_release(void) +{ +#ifdef CONFIG_RTC_ALARM + if (g_suspend_alarm_wake) + { + g_suspend_alarm_wake = false; + powman_clrbits(RP23XX_POWMAN_TIMER, + RP23XX_POWMAN_TIMER_PWRUP_ON_ALARM); + } +#endif + + if (!g_suspend_wake_armed) + { + return; + } + + g_suspend_wake_armed = false; + + powman_clrbits(RP23XX_POWMAN_TIMER, RP23XX_POWMAN_TIMER_PWRUP_ON_ALARM | + RP23XX_POWMAN_TIMER_ALARM_ENAB); + powman_clrbits(RP23XX_POWMAN_TIMER, RP23XX_POWMAN_TIMER_ALARM); + +#ifdef CONFIG_RTC_ALARM + rp23xx_rtc_restorealarm(&g_suspend_alarm); +#endif +} + +/**************************************************************************** + * Name: rp23xx_pm_pwrup_gpio + * + * Description: + * Arm a GPIO in a POWMAN power-up detector, which, unlike the dormant-wake + * detector of the IO bank, keeps working with the switched core off. + * + * Input Parameters: + * gpio - Pin to watch. + * edge - True for a transition, false for a level. + * high - True for rising/high, false for falling/low. + * + ****************************************************************************/ + +static void rp23xx_pm_pwrup_gpio(int gpio, bool edge, bool high) +{ + uint32_t regval; + + /* The detector reads the pad: enable the input, remove the isolation */ + + modifyreg32(RP23XX_PADS_BANK0_GPIO(gpio), RP23XX_PADS_BANK0_GPIO_ISO, + RP23XX_PADS_BANK0_GPIO_IE); + + /* Pull away from the wake level. The reset pull-down on an idle-high + * UART receive line costs 66 uA while suspended. A resume does not run + * arm_pminitialize(), so do it here. + */ + + if (high) + { + modifyreg32(RP23XX_PADS_BANK0_GPIO(gpio), RP23XX_PADS_BANK0_GPIO_PUE, + RP23XX_PADS_BANK0_GPIO_PDE); + } + else + { + modifyreg32(RP23XX_PADS_BANK0_GPIO(gpio), RP23XX_PADS_BANK0_GPIO_PDE, + RP23XX_PADS_BANK0_GPIO_PUE); + } + + /* Disable the detector while its source changes */ + + powman_clrbits(RP23XX_POWMAN_PWRUP0, RP23XX_POWMAN_PWRUP0_ENABLE); + + regval = (uint32_t)gpio & RP23XX_POWMAN_PWRUP0_SOURCE_MASK; + + if (edge) + { + regval |= RP23XX_POWMAN_PWRUP0_MODE; + } + + if (high) + { + regval |= RP23XX_POWMAN_PWRUP0_DIRECTION; + } + + putreg32(POWMAN_PASSWORD | regval, RP23XX_POWMAN_PWRUP0); + + /* Clear an old edge before enabling, or it ends the suspend at once */ + + powman_clrbits(RP23XX_POWMAN_PWRUP0, RP23XX_POWMAN_PWRUP0_STATUS); + powman_setbits(RP23XX_POWMAN_PWRUP0, RP23XX_POWMAN_PWRUP0_ENABLE); +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: rp23xx_pm_resume_pending + * + * Description: + * Tell if this boot is a resume. Called from __start before .bss is + * cleared, so it reads only always-on registers. The marker survives a + * reset too, so CHIP_RESET must also say that the switched core was + * powered down. The marker is consumed, so a failed resume boots cold. + * + ****************************************************************************/ + +bool rp23xx_pm_resume_pending(void) +{ + uint32_t marker; + uint32_t cause; + + marker = getreg32(RP23XX_POWMAN_SCRATCH0); + cause = getreg32(RP23XX_POWMAN_CHIP_RESET); + + putreg32(0, RP23XX_POWMAN_SCRATCH0); + + return marker == POWMAN_SUSPEND_MAGIC && + (cause & RP23XX_POWMAN_CHIP_RESET_HAD_SWCORE_PD) != 0; +} + +/**************************************************************************** + * Name: rp23xx_pm_resume + * + * Description: + * Return to the suspended thread in place of nx_start(). The boot has + * set up the hardware again; restore the NVIC, which only RAM knew. + * Does not return. + * + ****************************************************************************/ + +void rp23xx_pm_resume(void) +{ + g_suspend_wake_source = getreg32(RP23XX_POWMAN_LAST_SWCORE_PWRUP); + + /* No interrupt until the longjmp: this stack belongs to no thread */ + + up_irq_save(); + + rp23xx_pm_nvic_restore(); + + longjmp(g_suspend_ctx, 1); + + for (; ; ); +} + +/**************************************************************************** + * Name: rp23xx_pm_suspend + * + * Description: + * Power the switched core down (P1.0) and return after the wake. Every + * peripheral comes back reset. + * + * Input Parameters: + * wake_ms - Wake after this many milliseconds with the always-on timer + * alarm, or 0 for no timed wake. An RTC alarm that comes + * first also wakes the chip. + * + * Returned Value: + * Zero after the resume. A negated errno if the request was refused. + * + ****************************************************************************/ + +int rp23xx_pm_suspend(uint32_t wake_ms) +{ + irqstate_t flags; + uint32_t state; + uint64_t now; +#ifdef CONFIG_RTC_ALARM + uint64_t alarm; +#endif + + /* A debugger power-up request otherwise blocks the state change */ + + powman_setbits(RP23XX_POWMAN_DBG_PWRCFG, RP23XX_POWMAN_DBG_PWRCFG_IGNORE); + + flags = enter_critical_section(); + + /* Non-zero when rp23xx_pm_resume() returns here */ + + if (setjmp(g_suspend_ctx) != 0) + { + leave_critical_section(flags); + rp23xx_pm_wake_release(); + +#ifdef CONFIG_RTC + /* The system tick stopped; take the time of day from the always-on + * timer. CLOCK_MONOTONIC excludes the suspend, as it should. + */ + + clock_synchronize(NULL); +#endif + return OK; + } + + rp23xx_pm_nvic_save(); + + /* No bootrom resume vector: the resume uses the ordinary boot path */ + + putreg32(0, RP23XX_POWMAN_BOOT0); + putreg32(POWMAN_SUSPEND_MAGIC, RP23XX_POWMAN_SCRATCH0); + +#if CONFIG_RP23XX_PM_WAKEUP_GPIO >= 0 + rp23xx_pm_pwrup_gpio(CONFIG_RP23XX_PM_WAKEUP_GPIO, + RP23XX_PM_SUSPEND_WAKE_EDGE, + RP23XX_PM_SUSPEND_WAKE_HIGH); +#endif + + now = ((uint64_t)getreg32(RP23XX_POWMAN_READ_TIME_UPPER) << 32) | + getreg32(RP23XX_POWMAN_READ_TIME_LOWER); + +#ifdef CONFIG_RTC_ALARM + if (rp23xx_rtc_rdalarm(&alarm) == OK && + (wake_ms == 0 || alarm < now + wake_ms)) + { + /* The application alarm comes first: let it power the chip up */ + + g_suspend_alarm_wake = true; + powman_setbits(RP23XX_POWMAN_TIMER, + RP23XX_POWMAN_TIMER_PWRUP_ON_ALARM); + } + else +#endif + if (wake_ms > 0) + { + uint64_t when = now + wake_ms; + +#ifdef CONFIG_RTC_ALARM + rp23xx_rtc_savealarm(&g_suspend_alarm); +#endif + + g_suspend_wake_armed = true; + + powman_clrbits(RP23XX_POWMAN_TIMER, RP23XX_POWMAN_TIMER_ALARM_ENAB); + + powman_write(RP23XX_POWMAN_ALARM_TIME_15TO0, (uint32_t)(when)); + powman_write(RP23XX_POWMAN_ALARM_TIME_31TO16, (uint32_t)(when >> 16)); + powman_write(RP23XX_POWMAN_ALARM_TIME_47TO32, (uint32_t)(when >> 32)); + powman_write(RP23XX_POWMAN_ALARM_TIME_63TO48, (uint32_t)(when >> 48)); + + powman_clrbits(RP23XX_POWMAN_TIMER, RP23XX_POWMAN_TIMER_ALARM); + powman_setbits(RP23XX_POWMAN_TIMER, + RP23XX_POWMAN_TIMER_PWRUP_ON_ALARM | + RP23XX_POWMAN_TIMER_ALARM_ENAB); + } + + /* Write dirty XIP cache lines back to PSRAM. The resume boot discards + * the cache, and PSRAM can hold task stacks. + */ + + rp23xx_pm_xip_clean(); + + /* Request P1.0. Clear REQ_IGNORED first, so that it reports on this + * request only. + */ + + powman_clrbits(RP23XX_POWMAN_STATE, RP23XX_POWMAN_STATE_REQ_IGNORED); + powman_write(RP23XX_POWMAN_STATE, POWMAN_STATE_P1_0); + + state = getreg32(RP23XX_POWMAN_STATE); + + if ((state & RP23XX_POWMAN_STATE_REQ_IGNORED) != 0) + { + putreg32(0, RP23XX_POWMAN_SCRATCH0); + leave_critical_section(flags); + rp23xx_pm_wake_release(); + return -EBUSY; + } + + if ((state & RP23XX_POWMAN_STATE_BAD_SW_REQ) != 0) + { + putreg32(0, RP23XX_POWMAN_SCRATCH0); + leave_critical_section(flags); + rp23xx_pm_wake_release(); + return -EINVAL; + } + + /* The transition starts when the processors halt. Power goes away in + * the WFI, and execution continues in rp23xx_pm_resume(). + */ + + for (; ; ) + { + __asm__ __volatile__ ("dsb" ::: "memory"); + __asm__ __volatile__ ("wfi"); + } +} + +/**************************************************************************** + * Name: rp23xx_pm_wake_source + * + * Description: + * What caused the last resume, as the raw LAST_SWCORE_PWRUP value. + * + ****************************************************************************/ + +uint32_t rp23xx_pm_wake_source(void) +{ + return g_suspend_wake_source; +} diff --git a/arch/arm/src/rp23xx/rp23xx_psram.c b/arch/arm/src/rp23xx/rp23xx_psram.c index 82b83762907..cb9ea49703c 100644 --- a/arch/arm/src/rp23xx/rp23xx_psram.c +++ b/arch/arm/src/rp23xx/rp23xx_psram.c @@ -352,10 +352,22 @@ rp23xx_psramconfig(void) flags = up_irq_save(); - size = rp23xx_psram_detect(); - if (size != 0) + if (g_psram_size != 0) { + /* A resume from suspend to RAM: the PSRAM kept its power and is + * still in quad mode, which detection cannot handle. + */ + rp23xx_psram_apply_format(); + size = g_psram_size; + } + else + { + size = rp23xx_psram_detect(); + if (size != 0) + { + rp23xx_psram_apply_format(); + } } up_irq_restore(flags); diff --git a/arch/arm/src/rp23xx/rp23xx_rtc.c b/arch/arm/src/rp23xx/rp23xx_rtc.c index 88c5d051992..57ed91389b5 100644 --- a/arch/arm/src/rp23xx/rp23xx_rtc.c +++ b/arch/arm/src/rp23xx/rp23xx_rtc.c @@ -304,6 +304,65 @@ int rp23xx_rtc_cancelalarm(void) return OK; } +/**************************************************************************** + * Name: rp23xx_rtc_savealarm + ****************************************************************************/ + +void rp23xx_rtc_savealarm(FAR struct rp23xx_alarm_state_s *state) +{ + irqstate_t flags; + + flags = enter_critical_section(); + + *state = g_alarm; + + powman_alarm_disable(); + + g_alarm.active = false; + g_alarm.cb = NULL; + g_alarm.arg = NULL; + + leave_critical_section(flags); +} + +/**************************************************************************** + * Name: rp23xx_rtc_restorealarm + ****************************************************************************/ + +void rp23xx_rtc_restorealarm(FAR const struct rp23xx_alarm_state_s *state) +{ + rp23xx_alarm_callback_t cb; + FAR void *cbarg; + irqstate_t flags; + + if (!state->active) + { + return; + } + + flags = enter_critical_section(); + + if (state->time > powman_get_ms()) + { + g_alarm = *state; + powman_set_alarm_ms(state->time); + leave_critical_section(flags); + return; + } + + /* The time passed while the alarm was saved: report it now */ + + cb = state->cb; + cbarg = state->arg; + + leave_critical_section(flags); + + if (cb != NULL) + { + cb(cbarg); + } +} + /**************************************************************************** * Name: rp23xx_rtc_rdalarm ****************************************************************************/ diff --git a/arch/arm/src/rp23xx/rp23xx_rtc.h b/arch/arm/src/rp23xx/rp23xx_rtc.h index 3a94c8240ad..2798283dfed 100644 --- a/arch/arm/src/rp23xx/rp23xx_rtc.h +++ b/arch/arm/src/rp23xx/rp23xx_rtc.h @@ -135,6 +135,19 @@ int rp23xx_rtc_cancelalarm(void); int rp23xx_rtc_rdalarm(FAR uint64_t *time); +/**************************************************************************** + * Name: rp23xx_rtc_savealarm / rp23xx_rtc_restorealarm + * + * Description: + * Save the alarm and disarm it, so that the power management can use the + * comparator; then arm it again. An alarm whose time passed meanwhile + * fires at once. + * + ****************************************************************************/ + +void rp23xx_rtc_savealarm(FAR struct rp23xx_alarm_state_s *state); +void rp23xx_rtc_restorealarm(FAR const struct rp23xx_alarm_state_s *state); + #endif /* CONFIG_RTC_ALARM */ /**************************************************************************** diff --git a/arch/arm/src/rp23xx/rp23xx_serial.c b/arch/arm/src/rp23xx/rp23xx_serial.c index 9dab27de35f..42267154725 100644 --- a/arch/arm/src/rp23xx/rp23xx_serial.c +++ b/arch/arm/src/rp23xx/rp23xx_serial.c @@ -1047,6 +1047,46 @@ void arm_earlyserialinit(void) } #endif +/**************************************************************************** + * Name: rp23xx_serial_resume + * + * Description: + * Set up the open UARTs again after a suspend to RAM. up_setup() reads + * the interrupt mask back from the reset hardware, so restore the mask + * that the driver kept in RAM. + * + ****************************************************************************/ + +#ifdef CONFIG_RP23XX_PM_SUSPEND +static void up_resume_one(struct uart_dev_s *dev) +{ + struct up_dev_s *priv = (struct up_dev_s *)dev->priv; + uint32_t ier; + + if (dev->open_count == 0 && !dev->isconsole) + { + return; + } + + ier = priv->ier; + + up_setup(dev); + + priv->ier = ier; + up_serialout(priv, RP23XX_UART_UARTIMSC_OFFSET, ier); +} + +void rp23xx_serial_resume(void) +{ +#ifdef TTYS0_DEV + up_resume_one(&TTYS0_DEV); +#endif +#ifdef TTYS1_DEV + up_resume_one(&TTYS1_DEV); +#endif +} +#endif + /**************************************************************************** * Name: arm_serialinit * diff --git a/arch/arm/src/rp23xx/rp23xx_serial.h b/arch/arm/src/rp23xx/rp23xx_serial.h index 9b0ad6faf94..5907a7be845 100644 --- a/arch/arm/src/rp23xx/rp23xx_serial.h +++ b/arch/arm/src/rp23xx/rp23xx_serial.h @@ -50,4 +50,17 @@ * Public Functions Prototypes ****************************************************************************/ +/**************************************************************************** + * Name: rp23xx_serial_resume + * + * Description: + * Set up the UARTs again after a suspend to RAM, in place of + * arm_earlyserialinit(). + * + ****************************************************************************/ + +#ifdef CONFIG_RP23XX_PM_SUSPEND +void rp23xx_serial_resume(void); +#endif + #endif /* __ARCH_ARM_SRC_RP23XX_RP23XX_SERIAL_H */ diff --git a/arch/arm/src/rp23xx/rp23xx_start.c b/arch/arm/src/rp23xx/rp23xx_start.c index b0d38f0c61c..6ec67f9abff 100644 --- a/arch/arm/src/rp23xx/rp23xx_start.c +++ b/arch/arm/src/rp23xx/rp23xx_start.c @@ -27,6 +27,7 @@ #include #include +#include #include #include #include @@ -40,6 +41,11 @@ #include "rp23xx_uart.h" #include "hardware/rp23xx_sio.h" +#ifdef CONFIG_RP23XX_PM_SUSPEND +# include "rp23xx_pm.h" +# include "rp23xx_serial.h" +#endif + /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ @@ -71,24 +77,23 @@ const uintptr_t g_idle_topstack = IDLE_STACK; #endif /**************************************************************************** - * Public Functions - ****************************************************************************/ - -/**************************************************************************** - * Name: __start + * Name: rp23xx_hwinit * * Description: - * This is the reset entry point. + * Set up the hardware of the switched core: clocks, pins, spinlocks, FPU, + * console and board. A resume from suspend to RAM does this too, but not + * the memory initialisation of __start. + * + * Input Parameters: + * resume - True when this is a resume rather than a cold boot. * ****************************************************************************/ -void __start(void) +static void rp23xx_hwinit(bool resume) { -#ifdef CONFIG_BOOT_RUNFROMFLASH - const uint32_t *src; -#endif - uint32_t *dest; - size_t i; + size_t i; + + UNUSED(resume); /* Errata RP2350-E2 SIO SPINLOCK writes are mirrored at +0x80 offset * Use only safe SPINLOCKS @@ -103,42 +108,6 @@ void __start(void) 27, 28, 29, 30, 31 }; - /* Set MSP to the top of the IDLE stack */ - - __asm__ __volatile__ ("\tmsr msp, %0\n" :: "r" (g_idle_topstack)); - - if (this_cpu() != 0) - { - while (1) - { - __asm__ volatile ("wfe"); - } - } - - /* Clear .bss. We'll do this inline (vs. calling memset) just to be - * certain that there are no issues with the state of global variables. - */ - - for (dest = (uint32_t *)_sbss; dest < (uint32_t *)_ebss; ) - { - *dest++ = 0; - } - - /* Move the initialized data section from its temporary holding spot in - * FLASH into the correct place in SRAM. The correct place in SRAM is - * give by _sdata and _edata. The temporary location is in FLASH at the - * end of all of the other read-only data (.text, .rodata) at _eronly. - */ - -#ifdef CONFIG_BOOT_RUNFROMFLASH - for (src = (const uint32_t *)_eronly, - dest = (uint32_t *)_sdata; dest < (uint32_t *)_edata; - ) - { - *dest++ = *src++; - } -#endif - /* Set up clock */ rp23xx_clockconfig(); @@ -165,10 +134,22 @@ void __start(void) rp23xx_lowsetup(); showprogress('A'); - /* Perform early serial initialization */ + /* Perform early serial initialization. On a resume, the UARTs are reset + * but the driver state is not, so restore the hardware from it. + */ #ifdef USE_EARLYSERIALINIT - arm_earlyserialinit(); +# ifdef CONFIG_RP23XX_PM_SUSPEND + if (resume) + { + rp23xx_serial_resume(); + } + else +# endif + { + arm_earlyserialinit(); + } + #endif showprogress('B'); @@ -179,14 +160,132 @@ void __start(void) */ #ifdef CONFIG_BUILD_PROTECTED - rp23xx_userspace(); - showprogress('C'); + /* Not on a resume: the user data and bss hold the resumed state */ + + if (!resume) + { + rp23xx_userspace(); + showprogress('C'); + } #endif /* Initialize onboard resources */ rp23xx_boardinitialize(); showprogress('D'); +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +#ifdef CONFIG_RP23XX_PM_SUSPEND + +/**************************************************************************** + * Name: rp23xx_resume_boot + * + * Description: + * The rest of a boot that is a resume from suspend to RAM. __start + * branches here on its own stack. Does not return. + * + ****************************************************************************/ + +void rp23xx_resume_boot(void) +{ + rp23xx_hwinit(true); + + /* The IO bank lost its dormant-wake enables */ + + rp23xx_pm_gpio_wakeup_restore(); + +#ifdef CONFIG_RP23XX_PM_QUIESCE_PADS + /* The pads came back reset, and a resume does not run + * arm_pminitialize() + */ + + rp23xx_pm_pads_quiesce(); +#endif + + showprogress('\r'); + showprogress('\n'); + + rp23xx_pm_resume(); + + for (; ; ); +} +#endif + +/**************************************************************************** + * Name: __start + * + * Description: + * This is the reset entry point. + * + ****************************************************************************/ + +void __start(void) +{ +#ifdef CONFIG_BOOT_RUNFROMFLASH + const uint32_t *src; +#endif + uint32_t *dest; + + /* Set MSP to the top of the IDLE stack */ + + __asm__ __volatile__ ("\tmsr msp, %0\n" :: "r" (g_idle_topstack)); + + if (this_cpu() != 0) + { + while (1) + { + __asm__ volatile ("wfe"); + } + } + +#ifdef CONFIG_RP23XX_PM_SUSPEND + /* A resume comes through the bootrom like a cold boot. Check before + * .bss and .data are touched, and leave the idle stack at once: the idle + * thread still runs on it. + */ + + if (rp23xx_pm_resume_pending()) + { + __asm__ __volatile__ + ( + "msr msp, %0\n" + "b rp23xx_resume_boot\n" + : + : "r" (&g_pm_resume_stack[RP23XX_PM_RESUME_STACK_WORDS]) + : "memory" + ); + } +#endif + + /* Clear .bss. We'll do this inline (vs. calling memset) just to be + * certain that there are no issues with the state of global variables. + */ + + for (dest = (uint32_t *)_sbss; dest < (uint32_t *)_ebss; ) + { + *dest++ = 0; + } + + /* Move the initialized data section from its temporary holding spot in + * FLASH into the correct place in SRAM. The correct place in SRAM is + * give by _sdata and _edata. The temporary location is in FLASH at the + * end of all of the other read-only data (.text, .rodata) at _eronly. + */ + +#ifdef CONFIG_BOOT_RUNFROMFLASH + for (src = (const uint32_t *)_eronly, + dest = (uint32_t *)_sdata; dest < (uint32_t *)_edata; + ) + { + *dest++ = *src++; + } +#endif + + rp23xx_hwinit(false); /* Then start NuttX */ diff --git a/boards/arm/rp23xx/common/src/CMakeLists.txt b/boards/arm/rp23xx/common/src/CMakeLists.txt index 02fc89626e4..3a728d97b63 100644 --- a/boards/arm/rp23xx/common/src/CMakeLists.txt +++ b/boards/arm/rp23xx/common/src/CMakeLists.txt @@ -30,6 +30,10 @@ if(CONFIG_ARCH_BOARD_COMMON) list(APPEND SRCS rp23xx_reset.c) endif() + if(CONFIG_BOARDCTL_IOCTL) + list(APPEND SRCS rp23xx_boardctl.c) + endif() + if(CONFIG_SPI) list(APPEND SRCS rp23xx_spi.c) endif() diff --git a/boards/arm/rp23xx/common/src/Make.defs b/boards/arm/rp23xx/common/src/Make.defs index d400a49268c..7a174bcfaef 100644 --- a/boards/arm/rp23xx/common/src/Make.defs +++ b/boards/arm/rp23xx/common/src/Make.defs @@ -33,6 +33,10 @@ ifeq ($(CONFIG_BOARDCTL_RESET),y) CSRCS += rp23xx_reset.c endif +ifeq ($(CONFIG_BOARDCTL_IOCTL),y) +CSRCS += rp23xx_boardctl.c +endif + ifeq ($(CONFIG_SPI),y) CSRCS += rp23xx_spi.c endif diff --git a/boards/arm/rp23xx/common/src/rp23xx_boardctl.c b/boards/arm/rp23xx/common/src/rp23xx_boardctl.c new file mode 100644 index 00000000000..f17b8f422ae --- /dev/null +++ b/boards/arm/rp23xx/common/src/rp23xx_boardctl.c @@ -0,0 +1,77 @@ +/**************************************************************************** + * boards/arm/rp23xx/common/src/rp23xx_boardctl.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include + +#include +#include + +#include "rp23xx_pm.h" + +#ifdef CONFIG_BOARDCTL_IOCTL + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: board_ioctl + * + * Description: + * Handle the rp23xx boardctl() commands of arch/chip/pm.h. + * + ****************************************************************************/ + +int board_ioctl(unsigned int cmd, uintptr_t arg) +{ + switch (cmd) + { +#ifdef CONFIG_RP23XX_PM_SUSPEND + case BOARDIOC_RP23XX_SUSPEND: + { + FAR struct rp23xx_suspend_s *suspend = + (FAR struct rp23xx_suspend_s *)arg; + int ret; + + if (suspend == NULL) + { + return -EINVAL; + } + + ret = rp23xx_pm_suspend(suspend->wake_ms); + suspend->wake_source = rp23xx_pm_wake_source(); + return ret; + } +#endif + + default: + return -ENOTTY; + } +} + +#endif /* CONFIG_BOARDCTL_IOCTL */