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 <marco.casaroli@gmail.com>
This commit is contained in:
Marco Casaroli 2026-10-07 18:05:24 +02:00 • committed by Alan C. Assis
parent b685c69f25
commit a6ddfcd360
16 changed files with 1102 additions and 54 deletions

View file

@ -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 <nuttx/config.h>
#include <stdint.h>
#include <sys/boardctl.h>
/****************************************************************************
* 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 */

View file

@ -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()

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)
}

View file

@ -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 <nuttx/config.h>
#include <sys/types.h>
#include <stdint.h>
#include <stdbool.h>
#include <errno.h>
#include <setjmp.h>
#include <nuttx/arch.h>
#include <nuttx/irq.h>
#include <nuttx/clock.h>
#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;
}

View file

@ -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);

View file

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

View file

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

View file

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

View file

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

View file

@ -27,6 +27,7 @@
#include <nuttx/config.h>
#include <stdint.h>
#include <stdbool.h>
#include <assert.h>
#include <nuttx/debug.h>
#include <sys/param.h>
@ -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 */

View file

@ -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()

View file

@ -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

View file

@ -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 <nuttx/config.h>
#include <errno.h>
#include <nuttx/board.h>
#include <arch/chip/pm.h>
#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 */