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 */