arch/arm/rp23xx: Add the PM standby and dormant states.

Map the NuttX PM states onto the RP2350 low-power modes:

- PM_STANDBY is the RP2350 SLEEP state: a WFI with the clocks of the
  blocks that have no driver in the configuration gated (SLEEP_EN0/1).
- PM_SLEEP is the DORMANT state: clk_sys moves to the crystal
  oscillator, the PLLs and then the oscillator stop, and the clock tree
  is restored after the wake, as pico-extras does.  Only the GPIO
  dormant-wake detector can wake the chip, so PM_SLEEP gives the
  standby state when no wake GPIO is configured.  The wake GPIO also
  raises a one-shot interrupt, so that the core leaves its WFI and
  restores the PLLs at once.  The time of day is taken back from the
  always-on timer after the wake.

The dormant state stops the UARTs.  A PM_STANDBY wakelock
(pm_staytimeout()) is held for RP23XX_PM_WAKE_HOLD_MS after a dormant
wake and after each character a UART receives.  The serial driver
refuses PM_SLEEP in its prepare callback while a UART transmits.  A
dormant chip does not answer SWD, so the pm configurations use
PM_GOVERNOR_EXPLICIT_RELAX to stay out of it for a time after boot.

With RP23XX_PM_QUIESCE_PADS, arm_pminitialize() also isolates the
unused pads and holds the blocks with no driver in reset.  A floating
bank 0 input settles near 2.2V (erratum RP2350-E9) and its input
buffer then draws a static current in every state.

Also select ARCH_HAVE_PM, and fix the LED PM callbacks of three boards,
which used BOARD_LED where the boards define BOARD_LED1.  They did not
build with CONFIG_PM.

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 17:59:18 +02:00 • committed by Alan C. Assis
parent 5e7ea1ada6
commit b685c69f25
12 changed files with 1178 additions and 6 deletions

View file

@ -414,6 +414,7 @@ config ARCH_CHIP_RP23XX
select ARM_HAVE_DSP
select ARCH_HAVE_FPU
select ARCH_HAVE_TICKLESS
select ARCH_HAVE_PM
select ARCH_BOARD_COMMON
select ARCH_USBDEV_STALLQUEUE if USBDEV
---help---

View file

@ -115,6 +115,14 @@ if(CONFIG_RP23XX_RTC)
endif()
endif()
if(CONFIG_PM)
list(APPEND SRCS rp23xx_pminitialize.c)
endif()
if(CONFIG_RP23XX_PM)
list(APPEND SRCS rp23xx_pm.c)
endif()
if(CONFIG_RP23XX_OTP)
list(APPEND SRCS rp23xx_otp.c)
endif()

View file

@ -881,6 +881,73 @@ config RP23XX_RTC
The always-on timer also has one alarm comparator. Select
RTC_ALARM to expose it through /dev/rtc0.
#####################################################################
# Power Management
#####################################################################
menuconfig RP23XX_PM
bool "Power management (SLEEP and DORMANT states)"
default n
depends on PM
---help---
Map the NuttX power management states onto the RP2350 low-power
modes. PM_STANDBY gates the clocks of the blocks that have no
driver in the configuration during a WFI. PM_SLEEP is the dormant
state: the PLLs and the crystal oscillator stop, and only a GPIO
can wake the chip.
if RP23XX_PM
config RP23XX_PM_WAKEUP_GPIO
int "Wake-up GPIO for the dormant state (-1 to disable)"
default -1
range -1 47
---help---
GPIO that ends the dormant state. With -1 the dormant state is
never entered, and PM_SLEEP gives the standby state.
The pin keeps its function, so the console UART receive pin can
be used: a keypress then wakes the board, but its first
character is lost.
config RP23XX_PM_WAKEUP_GPIO_HIGH
bool "Wake on a high level or rising edge"
default n
depends on RP23XX_PM_WAKEUP_GPIO >= 0
---help---
The default is a low level or falling edge, which suits a button
to ground and a UART receive line.
config RP23XX_PM_WAKEUP_GPIO_EDGE
bool "Wake on an edge rather than a level"
default y
depends on RP23XX_PM_WAKEUP_GPIO >= 0
---help---
A level that stays active keeps the chip out of the dormant state.
config RP23XX_PM_WAKE_HOLD_MS
int "Stay out of the dormant state after a wake or UART input (ms)"
default 3000
---help---
The dormant state stops the UARTs, so the characters that arrive
then, and in the few milliseconds the wake takes, are lost. After
a dormant wake, and after each character a UART receives, a
PM_STANDBY wakelock (pm_staytimeout()) is held for this time.
A dormant chip does not answer SWD. To keep a debugger able to
attach after boot, set PM_GOVERNOR_EXPLICIT_RELAX.
config RP23XX_PM_QUIESCE_PADS
bool "Isolate unused pads and reset unused blocks"
default y
---help---
At start-up, isolate every GPIO pad that nothing uses and hold the
blocks that have no driver in reset. A floating input settles
near 2.2V (erratum RP2350-E9) and its input buffer then draws a
static current in every state.
endif # RP23XX_PM
config RP23XX_ADC
bool "Enable ADC Support"
default n

View file

@ -119,6 +119,14 @@ CHIP_CSRCS += rp23xx_rtc_lowerhalf.c
endif
endif
ifeq ($(CONFIG_PM),y)
CHIP_CSRCS += rp23xx_pminitialize.c
endif
ifeq ($(CONFIG_RP23XX_PM),y)
CHIP_CSRCS += rp23xx_pm.c
endif
ifeq ($(CONFIG_RP23XX_OTP),y)
CHIP_CSRCS += rp23xx_otp.c
endif

View file

@ -29,10 +29,12 @@
#include <nuttx/arch.h>
#include <nuttx/board.h>
#include <nuttx/power/pm.h>
#include <nuttx/irq.h>
#include "arm_internal.h"
#include "rp23xx_pm.h"
/****************************************************************************
* Pre-processor Definitions
@ -58,6 +60,75 @@
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: rp23xx_idlepm
*
* Description:
* Perform IDLE state power management. Without CONFIG_RP23XX_PM every
* state is a plain WFI.
*
****************************************************************************/
#ifdef CONFIG_PM
static void rp23xx_idlepm(void)
{
static enum pm_state_e oldstate = PM_NORMAL;
enum pm_state_e newstate;
irqstate_t flags;
int ret;
/* Decide which power saving level can be obtained */
newstate = pm_checkstate(PM_IDLE_DOMAIN);
/* Check for state changes */
if (newstate != oldstate)
{
flags = enter_critical_section();
ret = pm_changestate(PM_IDLE_DOMAIN, newstate);
if (ret < 0)
{
/* The new state change failed, revert to the preceding state */
pm_changestate(PM_IDLE_DOMAIN, oldstate);
}
else
{
/* Save the new state */
oldstate = newstate;
}
leave_critical_section(flags);
}
/* Enter the state on every pass, not only on a change. Not in a
* critical section: a masked interrupt does not end a WFI.
*/
switch (oldstate)
{
#ifdef CONFIG_RP23XX_PM
case PM_STANDBY:
rp23xx_pm_standby();
break;
case PM_SLEEP:
rp23xx_pm_sleep();
break;
#endif
case PM_NORMAL:
case PM_IDLE:
default:
asm("WFI");
break;
}
}
#endif
/****************************************************************************
* Public Functions
****************************************************************************/
@ -88,7 +159,11 @@ void up_idle(void)
/* Sleep until an interrupt occurs to save power */
BEGIN_IDLE();
#ifdef CONFIG_PM
rp23xx_idlepm();
#else
asm("WFI");
#endif
END_IDLE();
#endif
}

View file

@ -0,0 +1,742 @@
/****************************************************************************
* arch/arm/src/rp23xx/rp23xx_pm.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 <sys/types.h>
#include <stdint.h>
#include <stdbool.h>
#include <errno.h>
#include <arch/board/board.h>
#include <nuttx/arch.h>
#include <nuttx/irq.h>
#include <nuttx/clock.h>
#include <nuttx/power/pm.h>
#include "arm_internal.h"
#include "nvic.h"
#include "rp23xx_pm.h"
#include "rp23xx_gpio.h"
#include "rp23xx_pll.h"
#include "hardware/rp23xx_clocks.h"
#include "hardware/rp23xx_powman.h"
#include "hardware/rp23xx_xosc.h"
#include "hardware/rp23xx_pll.h"
#include "hardware/rp23xx_pads_bank0.h"
#include "hardware/rp23xx_io_bank0.h"
#include "hardware/rp23xx_memorymap.h"
#include "hardware/rp23xx_resets.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* No flash fetch works while the crystal oscillator is stopped, so the
* dormant sequence runs from RAM (.time_critical).
*/
#define RP23XX_PM_RAMFUNC \
__attribute__((section(".time_critical.rp23xx_pm"), noinline))
/* POWMAN ignores a write without this password in the top 16 bits */
#define POWMAN_PASSWORD 0x5afe0000
/* Atomic set/clear aliases of a bus register (RP2350 memory map). */
#define POWMAN_SET_ALIAS 0x2000
#define POWMAN_CLR_ALIAS 0x3000
/* Clocks that keep running through a PM_STANDBY WFI. SLEEP_EN0/1 use the
* bits of WAKE_EN0/1. Only blocks with no driver in the configuration are
* gated: the rp23xx drivers have no PM callbacks to say they are idle.
*/
#define RP23XX_PM_SLEEP_EN0_BASE \
(RP23XX_CLOCKS_WAKE_EN0_CLK_SYS_CLOCKS | \
RP23XX_CLOCKS_WAKE_EN0_CLK_SYS_ACCESSCTRL | \
RP23XX_CLOCKS_WAKE_EN0_CLK_SYS_BOOTRAM | \
RP23XX_CLOCKS_WAKE_EN0_CLK_SYS_BUSCTRL | \
RP23XX_CLOCKS_WAKE_EN0_CLK_SYS_BUSFABRIC | \
RP23XX_CLOCKS_WAKE_EN0_CLK_SYS_IO | \
RP23XX_CLOCKS_WAKE_EN0_CLK_SYS_PADS | \
RP23XX_CLOCKS_WAKE_EN0_CLK_SYS_PLL_SYS | \
RP23XX_CLOCKS_WAKE_EN0_CLK_REF_POWMAN | \
RP23XX_CLOCKS_WAKE_EN0_CLK_SYS_POWMAN | \
RP23XX_CLOCKS_WAKE_EN0_CLK_SYS_PSM | \
RP23XX_CLOCKS_WAKE_EN0_CLK_SYS_RESETS | \
RP23XX_CLOCKS_WAKE_EN0_CLK_SYS_ROM | \
RP23XX_CLOCKS_WAKE_EN0_CLK_SYS_ROSC | \
RP23XX_CLOCKS_WAKE_EN0_CLK_SYS_SIO)
#define RP23XX_PM_SLEEP_EN1_BASE \
(RP23XX_CLOCKS_WAKE_EN1_CLK_SYS_SRAM0 | \
RP23XX_CLOCKS_WAKE_EN1_CLK_SYS_SRAM1 | \
RP23XX_CLOCKS_WAKE_EN1_CLK_SYS_SRAM2 | \
RP23XX_CLOCKS_WAKE_EN1_CLK_SYS_SRAM3 | \
RP23XX_CLOCKS_WAKE_EN1_CLK_SYS_SRAM4 | \
RP23XX_CLOCKS_WAKE_EN1_CLK_SYS_SRAM5 | \
RP23XX_CLOCKS_WAKE_EN1_CLK_SYS_SRAM6 | \
RP23XX_CLOCKS_WAKE_EN1_CLK_SYS_SRAM7 | \
RP23XX_CLOCKS_WAKE_EN1_CLK_SYS_SRAM8 | \
RP23XX_CLOCKS_WAKE_EN1_CLK_SYS_SRAM9 | \
RP23XX_CLOCKS_WAKE_EN1_CLK_SYS_SYSCFG | \
RP23XX_CLOCKS_WAKE_EN1_CLK_SYS_SYSINFO | \
RP23XX_CLOCKS_WAKE_EN1_CLK_SYS_TBMAN | \
RP23XX_CLOCKS_WAKE_EN1_CLK_REF_TICKS | \
RP23XX_CLOCKS_WAKE_EN1_CLK_SYS_TICKS | \
RP23XX_CLOCKS_WAKE_EN1_CLK_SYS_WATCHDOG | \
RP23XX_CLOCKS_WAKE_EN1_CLK_SYS_XIP | \
RP23XX_CLOCKS_WAKE_EN1_CLK_SYS_XOSC)
/* Optional blocks, kept only when their driver is configured. */
#ifdef CONFIG_RP23XX_ADC
# define RP23XX_PM_EN0_ADC (RP23XX_CLOCKS_WAKE_EN0_CLK_SYS_ADC | \
RP23XX_CLOCKS_WAKE_EN0_CLK_ADC_ADC)
#else
# define RP23XX_PM_EN0_ADC 0
#endif
#ifdef CONFIG_RP23XX_DMAC
# define RP23XX_PM_EN0_DMA RP23XX_CLOCKS_WAKE_EN0_CLK_SYS_DMA
#else
# define RP23XX_PM_EN0_DMA 0
#endif
#ifdef CONFIG_RP23XX_I2C0
# define RP23XX_PM_EN0_I2C0 RP23XX_CLOCKS_WAKE_EN0_CLK_SYS_I2C0
#else
# define RP23XX_PM_EN0_I2C0 0
#endif
#ifdef CONFIG_RP23XX_I2C1
# define RP23XX_PM_EN0_I2C1 RP23XX_CLOCKS_WAKE_EN0_CLK_SYS_I2C1
#else
# define RP23XX_PM_EN0_I2C1 0
#endif
#ifdef CONFIG_RP23XX_PWM
# define RP23XX_PM_EN0_PWM RP23XX_CLOCKS_WAKE_EN0_CLK_SYS_PWM
#else
# define RP23XX_PM_EN0_PWM 0
#endif
#ifdef CONFIG_RP23XX_OTP
# define RP23XX_PM_EN0_OTP (RP23XX_CLOCKS_WAKE_EN0_CLK_SYS_OTP | \
RP23XX_CLOCKS_WAKE_EN0_CLK_REF_OTP)
#else
# define RP23XX_PM_EN0_OTP 0
#endif
#ifdef CONFIG_CRYPTO_CRYPTODEV_HARDWARE
# define RP23XX_PM_EN0_SHA RP23XX_CLOCKS_WAKE_EN0_CLK_SYS_SHA256
#else
# define RP23XX_PM_EN0_SHA 0
#endif
/* The PIO blocks are shared by several drivers */
#if defined(CONFIG_RP23XX_I2S) || defined(CONFIG_WS2812) || \
defined(CONFIG_IEEE80211_INFINEON_CYW43439)
# define RP23XX_PM_EN0_PIO (RP23XX_CLOCKS_WAKE_EN0_CLK_SYS_PIO0 | \
RP23XX_CLOCKS_WAKE_EN0_CLK_SYS_PIO1 | \
RP23XX_CLOCKS_WAKE_EN0_CLK_SYS_PIO2)
#else
# define RP23XX_PM_EN0_PIO 0
#endif
#ifdef CONFIG_USBDEV
# define RP23XX_PM_EN0_USB RP23XX_CLOCKS_WAKE_EN0_CLK_SYS_PLL_USB
# define RP23XX_PM_EN1_USB (RP23XX_CLOCKS_WAKE_EN1_CLK_USB | \
RP23XX_CLOCKS_WAKE_EN1_CLK_SYS_USBCTRL)
#else
# define RP23XX_PM_EN0_USB 0
# define RP23XX_PM_EN1_USB 0
#endif
#ifdef CONFIG_RP23XX_UART0
# define RP23XX_PM_EN1_UART0 (RP23XX_CLOCKS_WAKE_EN1_CLK_SYS_UART0 | \
RP23XX_CLOCKS_WAKE_EN1_CLK_PERI_UART0)
#else
# define RP23XX_PM_EN1_UART0 0
#endif
#ifdef CONFIG_RP23XX_UART1
# define RP23XX_PM_EN1_UART1 (RP23XX_CLOCKS_WAKE_EN1_CLK_SYS_UART1 | \
RP23XX_CLOCKS_WAKE_EN1_CLK_PERI_UART1)
#else
# define RP23XX_PM_EN1_UART1 0
#endif
#ifdef CONFIG_RP23XX_SPI0
# define RP23XX_PM_EN1_SPI0 (RP23XX_CLOCKS_WAKE_EN1_CLK_SYS_SPI0 | \
RP23XX_CLOCKS_WAKE_EN1_CLK_PERI_SPI0)
#else
# define RP23XX_PM_EN1_SPI0 0
#endif
#ifdef CONFIG_RP23XX_SPI1
# define RP23XX_PM_EN1_SPI1 (RP23XX_CLOCKS_WAKE_EN1_CLK_SYS_SPI1 | \
RP23XX_CLOCKS_WAKE_EN1_CLK_PERI_SPI1)
#else
# define RP23XX_PM_EN1_SPI1 0
#endif
#ifdef CONFIG_RP23XX_RNG
# define RP23XX_PM_EN1_TRNG RP23XX_CLOCKS_WAKE_EN1_CLK_SYS_TRNG
#else
# define RP23XX_PM_EN1_TRNG 0
#endif
#ifdef CONFIG_RP23XX_TIMER0
# define RP23XX_PM_EN1_TIMER0 RP23XX_CLOCKS_WAKE_EN1_CLK_SYS_TIMER0
#else
# define RP23XX_PM_EN1_TIMER0 0
#endif
#ifdef CONFIG_RP23XX_TIMER1
# define RP23XX_PM_EN1_TIMER1 RP23XX_CLOCKS_WAKE_EN1_CLK_SYS_TIMER1
#else
# define RP23XX_PM_EN1_TIMER1 0
#endif
/* The timer block that the tickless scheduler uses */
#if defined(CONFIG_RP23XX_SYSTIMER_TICKLESS_TIMER0)
# define RP23XX_PM_EN1_SYSTIMER RP23XX_CLOCKS_WAKE_EN1_CLK_SYS_TIMER0
#elif defined(CONFIG_RP23XX_SYSTIMER_TICKLESS_TIMER1)
# define RP23XX_PM_EN1_SYSTIMER RP23XX_CLOCKS_WAKE_EN1_CLK_SYS_TIMER1
#else
# define RP23XX_PM_EN1_SYSTIMER 0
#endif
#define RP23XX_PM_SLEEP_EN0 \
(RP23XX_PM_SLEEP_EN0_BASE | RP23XX_PM_EN0_ADC | RP23XX_PM_EN0_DMA | \
RP23XX_PM_EN0_I2C0 | RP23XX_PM_EN0_I2C1 | RP23XX_PM_EN0_PWM | \
RP23XX_PM_EN0_OTP | RP23XX_PM_EN0_SHA | RP23XX_PM_EN0_PIO | \
RP23XX_PM_EN0_USB)
#define RP23XX_PM_SLEEP_EN1 \
(RP23XX_PM_SLEEP_EN1_BASE | RP23XX_PM_EN1_USB | \
RP23XX_PM_EN1_UART0 | RP23XX_PM_EN1_UART1 | \
RP23XX_PM_EN1_SPI0 | RP23XX_PM_EN1_SPI1 | \
RP23XX_PM_EN1_TRNG | RP23XX_PM_EN1_TIMER0 | \
RP23XX_PM_EN1_TIMER1 | RP23XX_PM_EN1_SYSTIMER)
#ifdef CONFIG_RP23XX_PM_QUIESCE_PADS
/* Blocks with no driver are held in reset. A gated clock does not stop
* the bias current of an analogue block such as the USB PHY.
*/
#ifndef CONFIG_USBDEV
# define RP23XX_PM_RST_USB RP23XX_RESETS_RESET_USBCTRL
#else
# define RP23XX_PM_RST_USB 0
#endif
#ifndef CONFIG_RP23XX_ADC
# define RP23XX_PM_RST_ADC RP23XX_RESETS_RESET_ADC
#else
# define RP23XX_PM_RST_ADC 0
#endif
#ifndef CONFIG_RP23XX_RNG
# define RP23XX_PM_RST_TRNG RP23XX_RESETS_RESET_TRNG
#else
# define RP23XX_PM_RST_TRNG 0
#endif
#ifndef CONFIG_CRYPTO_CRYPTODEV_HARDWARE
# define RP23XX_PM_RST_SHA RP23XX_RESETS_RESET_SHA256
#else
# define RP23XX_PM_RST_SHA 0
#endif
#ifndef CONFIG_RP23XX_PWM
# define RP23XX_PM_RST_PWM RP23XX_RESETS_RESET_PWM
#else
# define RP23XX_PM_RST_PWM 0
#endif
#if !defined(CONFIG_RP23XX_I2S) && !defined(CONFIG_WS2812) && \
!defined(CONFIG_IEEE80211_INFINEON_CYW43439)
# define RP23XX_PM_RST_PIO (RP23XX_RESETS_RESET_PIO0 | \
RP23XX_RESETS_RESET_PIO1 | \
RP23XX_RESETS_RESET_PIO2)
#else
# define RP23XX_PM_RST_PIO 0
#endif
#ifndef CONFIG_RP23XX_SPI0
# define RP23XX_PM_RST_SPI0 RP23XX_RESETS_RESET_SPI0
#else
# define RP23XX_PM_RST_SPI0 0
#endif
#ifndef CONFIG_RP23XX_SPI1
# define RP23XX_PM_RST_SPI1 RP23XX_RESETS_RESET_SPI1
#else
# define RP23XX_PM_RST_SPI1 0
#endif
#ifndef CONFIG_RP23XX_I2C0
# define RP23XX_PM_RST_I2C0 RP23XX_RESETS_RESET_I2C0
#else
# define RP23XX_PM_RST_I2C0 0
#endif
#ifndef CONFIG_RP23XX_I2C1
# define RP23XX_PM_RST_I2C1 RP23XX_RESETS_RESET_I2C1
#else
# define RP23XX_PM_RST_I2C1 0
#endif
#ifndef CONFIG_RP23XX_UART1
# define RP23XX_PM_RST_UART1 RP23XX_RESETS_RESET_UART1
#else
# define RP23XX_PM_RST_UART1 0
#endif
/* NuttX has no HSTX driver */
#define RP23XX_PM_RST_HSTX RP23XX_RESETS_RESET_HSTX
#define RP23XX_PM_RESET_UNUSED \
(RP23XX_PM_RST_USB | RP23XX_PM_RST_ADC | RP23XX_PM_RST_TRNG | \
RP23XX_PM_RST_SHA | RP23XX_PM_RST_PWM | RP23XX_PM_RST_PIO | \
RP23XX_PM_RST_SPI0 | RP23XX_PM_RST_SPI1 | RP23XX_PM_RST_I2C0 | \
RP23XX_PM_RST_I2C1 | RP23XX_PM_RST_UART1 | RP23XX_PM_RST_HSTX)
#endif
/****************************************************************************
* Private Data
****************************************************************************/
/* GPIOs armed in the dormant-wake detector. With none, no dormant state */
static uint64_t g_pm_wakeup_gpios;
/* Set by the wake interrupt */
static volatile bool g_pm_woken;
/****************************************************************************
* Private Functions
****************************************************************************/
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_wake_irq
*
* Description:
* One-shot interrupt of a wake GPIO. The dormant-wake edge stays in the
* INTR latch, so this interrupt ends the WFI of the dormant state as soon
* as the crystal oscillator runs again.
*
****************************************************************************/
static int rp23xx_pm_wake_irq(int irq, FAR void *context, FAR void *arg)
{
rp23xx_gpio_disable_irq(irq);
g_pm_woken = true;
return OK;
}
/****************************************************************************
* Name: rp23xx_pm_stayed
*
* Description:
* True while a wakelock holds a state above PM_SLEEP. A driver can take
* one from an interrupt after the governor chose PM_SLEEP.
*
****************************************************************************/
static bool rp23xx_pm_stayed(void)
{
int state;
for (state = PM_NORMAL; state < PM_SLEEP; state++)
{
if (pm_staycount(PM_IDLE_DOMAIN, state) > 0)
{
return true;
}
}
return false;
}
#ifdef CONFIG_RP23XX_PM_QUIESCE_PADS
/****************************************************************************
* Name: rp23xx_pm_pads_quiesce
*
* Description:
* Isolate every unused pad and turn off its input buffer. A floating
* bank 0 input settles near 2.2V (erratum RP2350-E9) and its buffer then
* draws a static current in every state.
*
****************************************************************************/
void rp23xx_pm_pads_quiesce(void)
{
int gpio;
/* Reset the unused blocks first, so that they stop driving their pads */
setbits_reg32(RP23XX_PM_RESET_UNUSED, RP23XX_RESETS_RESET);
for (gpio = 0; gpio < RP23XX_GPIO_NUM; gpio++)
{
/* A wake source must keep watching its pad */
if ((g_pm_wakeup_gpios & (1ull << gpio)) != 0)
{
continue;
}
#ifdef CONFIG_RP23XX_UART0
if (gpio == CONFIG_RP23XX_UART0_TX_GPIO ||
gpio == CONFIG_RP23XX_UART0_RX_GPIO)
{
continue;
}
#endif
#ifdef CONFIG_RP23XX_UART1
if (gpio == CONFIG_RP23XX_UART1_TX_GPIO ||
gpio == CONFIG_RP23XX_UART1_RX_GPIO)
{
continue;
}
#endif
#ifdef CONFIG_RP23XX_PSRAM
/* An isolated PSRAM chip select never asserts, and every PSRAM read
* then returns the same pattern.
*/
if (gpio == CONFIG_RP23XX_PSRAM_CS1_GPIO)
{
continue;
}
#endif
modbits_reg32(RP23XX_PADS_BANK0_GPIO_ISO,
RP23XX_PADS_BANK0_GPIO_IE |
RP23XX_PADS_BANK0_GPIO_ISO,
RP23XX_PADS_BANK0_GPIO(gpio));
}
}
#endif /* CONFIG_RP23XX_PM_QUIESCE_PADS */
/****************************************************************************
* Name: rp23xx_pm_dormant
*
* Description:
* Run from the crystal oscillator, stop it, and restore the clock tree
* after the wake. As pico-extras sleep_goto_dormant_until_pin() does.
*
****************************************************************************/
static void RP23XX_PM_RAMFUNC rp23xx_pm_dormant(void)
{
uint32_t sys_ctrl;
uint32_t sys_div;
uint32_t ref_ctrl;
uint32_t ref_div;
/* Save the clk_sys and clk_ref setup, to restore it exactly */
sys_ctrl = getreg32(RP23XX_CLOCKS_CLK_SYS_CTRL);
sys_div = getreg32(RP23XX_CLOCKS_CLK_SYS_DIV);
ref_ctrl = getreg32(RP23XX_CLOCKS_CLK_REF_CTRL);
ref_div = getreg32(RP23XX_CLOCKS_CLK_REF_DIV);
/* Move clk_sys from the system PLL to clk_ref (glitchless). Leave the
* divisors: they are 16.16, so writing 1 divides by 65536.
*/
clrbits_reg32(RP23XX_CLOCKS_CLK_SYS_CTRL_SRC, RP23XX_CLOCKS_CLK_SYS_CTRL);
while (getreg32(RP23XX_CLOCKS_CLK_SYS_SELECTED) != 1)
{
}
/* Run clk_ref from the crystal oscillator, which the wake restarts */
modbits_reg32(RP23XX_CLOCKS_CLK_REF_CTRL_SRC_XOSC_CLKSRC,
RP23XX_CLOCKS_CLK_REF_CTRL_SRC_MASK,
RP23XX_CLOCKS_CLK_REF_CTRL);
while (!(getreg32(RP23XX_CLOCKS_CLK_REF_SELECTED) &
(1u << RP23XX_CLOCKS_CLK_REF_CTRL_SRC_XOSC_CLKSRC)))
{
}
/* Nothing uses the PLLs now, so power them down */
putreg32(RP23XX_PLL_PWR_PD | RP23XX_PLL_PWR_VCOPD |
RP23XX_PLL_PWR_POSTDIVPD,
RP23XX_PLL_SYS_BASE + RP23XX_PLL_PWR_OFFSET);
putreg32(RP23XX_PLL_PWR_PD | RP23XX_PLL_PWR_VCOPD |
RP23XX_PLL_PWR_POSTDIVPD,
RP23XX_PLL_USB_BASE + RP23XX_PLL_PWR_OFFSET);
/* Arm the dormant request. The oscillator stops only when the core
* stops requesting a clock, so enter deep sleep.
*/
putreg32(RP23XX_XOSC_DORMANT_DORMANT, RP23XX_XOSC_DORMANT);
putreg32(getreg32(NVIC_SYSCON) | NVIC_SYSCON_SLEEPDEEP, NVIC_SYSCON);
__asm__ __volatile__ ("dsb" ::: "memory");
__asm__ __volatile__ ("wfi");
__asm__ __volatile__ ("isb" ::: "memory");
putreg32(getreg32(NVIC_SYSCON) & ~NVIC_SYSCON_SLEEPDEEP, NVIC_SYSCON);
/* Woken: wait for the oscillator to be stable */
while (!(getreg32(RP23XX_XOSC_STATUS) & RP23XX_XOSC_STATUS_STABLE))
{
}
/* Restart the PLLs with the values of clocks_init() */
rp23xx_pll_init(RP23XX_PLL_SYS_BASE, 1, 1500 * MHZ, 5, 2);
rp23xx_pll_init(RP23XX_PLL_USB_BASE, 1, 1200 * MHZ, 5, 5);
/* Restore clk_ref first, because clk_sys still runs from it */
modbits_reg32(ref_ctrl, RP23XX_CLOCKS_CLK_REF_CTRL_SRC_MASK,
RP23XX_CLOCKS_CLK_REF_CTRL);
while (!(getreg32(RP23XX_CLOCKS_CLK_REF_SELECTED) &
(1u << (ref_ctrl & RP23XX_CLOCKS_CLK_REF_CTRL_SRC_MASK))))
{
}
putreg32(ref_div, RP23XX_CLOCKS_CLK_REF_DIV);
putreg32(sys_ctrl, RP23XX_CLOCKS_CLK_SYS_CTRL);
while (!(getreg32(RP23XX_CLOCKS_CLK_SYS_SELECTED) &
(1u << (sys_ctrl & RP23XX_CLOCKS_CLK_SYS_CTRL_SRC))))
{
}
putreg32(sys_div, RP23XX_CLOCKS_CLK_SYS_DIV);
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: rp23xx_pm_standby
****************************************************************************/
void rp23xx_pm_standby(void)
{
uint32_t saved_en0;
uint32_t saved_en1;
/* SLEEP_EN0/1 take effect only while the processors sleep */
saved_en0 = getreg32(RP23XX_CLOCKS_SLEEP_EN0);
saved_en1 = getreg32(RP23XX_CLOCKS_SLEEP_EN1);
putreg32(RP23XX_PM_SLEEP_EN0, RP23XX_CLOCKS_SLEEP_EN0);
putreg32(RP23XX_PM_SLEEP_EN1, RP23XX_CLOCKS_SLEEP_EN1);
/* A plain WFI: any enabled interrupt wakes the core */
putreg32(getreg32(NVIC_SYSCON) & ~NVIC_SYSCON_SLEEPDEEP, NVIC_SYSCON);
__asm__ __volatile__ ("dsb" ::: "memory");
__asm__ __volatile__ ("wfi");
__asm__ __volatile__ ("isb" ::: "memory");
putreg32(saved_en0, RP23XX_CLOCKS_SLEEP_EN0);
putreg32(saved_en1, RP23XX_CLOCKS_SLEEP_EN1);
}
/****************************************************************************
* Name: rp23xx_pm_sleep
****************************************************************************/
void rp23xx_pm_sleep(void)
{
int gpio;
if (rp23xx_pm_stayed())
{
rp23xx_pm_standby();
return;
}
/* Only the GPIO dormant-wake detector can restart the oscillator; the
* always-on timer alarm cannot. With no GPIO armed, use standby.
*/
if (g_pm_wakeup_gpios == 0)
{
rp23xx_pm_standby();
return;
}
/* Clear the edges latched by the last wake, or it ends this one at once,
* and enable the wake interrupts.
*/
g_pm_woken = false;
for (gpio = 0; gpio < RP23XX_GPIO_NUM; gpio++)
{
if ((g_pm_wakeup_gpios & (1ull << gpio)) != 0)
{
setbits_reg32(0xfu << ((gpio % 8) * 4),
RP23XX_IO_BANK0_INTR(gpio));
rp23xx_gpio_enable_irq(gpio);
}
}
/* An edge came in meanwhile: the interrupt is already gone */
if (!g_pm_woken)
{
rp23xx_pm_dormant();
}
for (gpio = 0; gpio < RP23XX_GPIO_NUM; gpio++)
{
if ((g_pm_wakeup_gpios & (1ull << gpio)) != 0)
{
rp23xx_gpio_disable_irq(gpio);
}
}
/* Let the input that woke the chip follow */
pm_staytimeout(PM_IDLE_DOMAIN, PM_STANDBY, CONFIG_RP23XX_PM_WAKE_HOLD_MS);
#ifdef CONFIG_RTC
/* The system tick stopped while dormant. The always-on timer did not,
* so take the time of day back from it.
*/
clock_synchronize(NULL);
#endif
}
/****************************************************************************
* Name: rp23xx_pm_gpio_wakeup
****************************************************************************/
int rp23xx_pm_gpio_wakeup(int gpio, bool edge, bool high)
{
uint32_t bit;
if (gpio < 0 || gpio >= RP23XX_GPIO_NUM)
{
return -EINVAL;
}
/* Enable the input and clear the isolation that pads have from reset */
modbits_reg32(RP23XX_PADS_BANK0_GPIO_IE,
RP23XX_PADS_BANK0_GPIO_ISO |
RP23XX_PADS_BANK0_GPIO_IE |
RP23XX_PADS_BANK0_GPIO_OD,
RP23XX_PADS_BANK0_GPIO(gpio));
/* Pull the pad away from the wake level. A floating input settles
* near 2.2V (erratum RP2350-E9) and would wake the chip at once.
*/
rp23xx_gpio_set_pulls(gpio, !high, high);
if (edge)
{
bit = high ? RP23XX_IO_BANK0_INTR_GPIO_EDGE_HIGH(gpio)
: RP23XX_IO_BANK0_INTR_GPIO_EDGE_LOW(gpio);
}
else
{
bit = high ? RP23XX_IO_BANK0_INTR_GPIO_LEVEL_HIGH(gpio)
: RP23XX_IO_BANK0_INTR_GPIO_LEVEL_LOW(gpio);
}
/* Clear old latched edges */
setbits_reg32(0xfu << ((gpio % 8) * 4), RP23XX_IO_BANK0_INTR(gpio));
setbits_reg32(bit, RP23XX_IO_BANK0_DORMANT_WAKE_INTE(gpio));
/* The same trigger as an interrupt, enabled only around the dormant
* state. It replaces any other handler of this GPIO.
*/
rp23xx_gpio_irq_attach(gpio, edge ? (high ? RP23XX_GPIO_INTR_EDGE_HIGH :
RP23XX_GPIO_INTR_EDGE_LOW) :
(high ? RP23XX_GPIO_INTR_LEVEL_HIGH :
RP23XX_GPIO_INTR_LEVEL_LOW),
rp23xx_pm_wake_irq, NULL);
g_pm_wakeup_gpios |= 1ull << gpio;
return OK;
}
/****************************************************************************
* Name: rp23xx_pm_gpio_wakeup_disable
****************************************************************************/
int rp23xx_pm_gpio_wakeup_disable(int gpio)
{
if (gpio < 0 || gpio >= RP23XX_GPIO_NUM)
{
return -EINVAL;
}
clrbits_reg32(0xfu << ((gpio % 8) * 4),
RP23XX_IO_BANK0_DORMANT_WAKE_INTE(gpio));
g_pm_wakeup_gpios &= ~(1ull << gpio);
return OK;
}

View file

@ -0,0 +1,122 @@
/****************************************************************************
* arch/arm/src/rp23xx/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_SRC_RP23XX_RP23XX_PM_H
#define __ARCH_ARM_SRC_RP23XX_RP23XX_PM_H
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <stdint.h>
#include <stdbool.h>
#ifdef CONFIG_RP23XX_PM
#ifndef __ASSEMBLY__
#undef EXTERN
#if defined(__cplusplus)
#define EXTERN extern "C"
extern "C"
{
#else
#define EXTERN extern
#endif
/****************************************************************************
* Public Function Prototypes
****************************************************************************/
/****************************************************************************
* Name: rp23xx_pm_standby
*
* Description:
* Enter the RP2350 SLEEP state: WFI with the clocks of blocks that have
* no driver gated. Any enabled interrupt wakes the core.
*
****************************************************************************/
void rp23xx_pm_standby(void);
/****************************************************************************
* Name: rp23xx_pm_sleep
*
* Description:
* Enter the RP2350 DORMANT state: stop the PLLs and the crystal
* oscillator. Only a GPIO armed with rp23xx_pm_gpio_wakeup() can wake
* the chip, so with none armed this enters the standby state instead.
*
****************************************************************************/
void rp23xx_pm_sleep(void);
/****************************************************************************
* Name: rp23xx_pm_gpio_wakeup
*
* Description:
* Arm a GPIO in the dormant-wake detector. The detector watches the pad,
* so the pin keeps its function (a UART receive pin can be armed).
*
* Input Parameters:
* gpio - GPIO number to watch.
* edge - True to trigger on a transition, false on a level.
* high - True for rising edge / high level, false for falling / low.
*
* Returned Value:
* Zero on success; a negated errno on failure.
*
****************************************************************************/
int rp23xx_pm_gpio_wakeup(int gpio, bool edge, bool high);
/****************************************************************************
* Name: rp23xx_pm_gpio_wakeup_disable
*
* Description:
* Disarm a GPIO previously passed to rp23xx_pm_gpio_wakeup().
*
****************************************************************************/
int rp23xx_pm_gpio_wakeup_disable(int gpio);
/****************************************************************************
* Name: rp23xx_pm_pads_quiesce
*
* Description:
* Isolate the unused pads and hold the unused blocks in reset.
*
****************************************************************************/
#ifdef CONFIG_RP23XX_PM_QUIESCE_PADS
void rp23xx_pm_pads_quiesce(void);
#endif
#undef EXTERN
#if defined(__cplusplus)
}
#endif
#endif /* __ASSEMBLY__ */
#endif /* CONFIG_RP23XX_PM */
#endif /* __ARCH_ARM_SRC_RP23XX_RP23XX_PM_H */

View file

@ -0,0 +1,85 @@
/****************************************************************************
* arch/arm/src/rp23xx/rp23xx_pminitialize.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 <nuttx/arch.h>
#include <nuttx/power/pm.h>
#include "arm_internal.h"
#include "rp23xx_pm.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#ifdef CONFIG_RP23XX_PM_WAKEUP_GPIO_HIGH
# define RP23XX_PM_WAKEUP_ACTIVE_HIGH true
#else
# define RP23XX_PM_WAKEUP_ACTIVE_HIGH false
#endif
#ifdef CONFIG_RP23XX_PM_WAKEUP_GPIO_EDGE
# define RP23XX_PM_WAKEUP_ON_EDGE true
#else
# define RP23XX_PM_WAKEUP_ON_EDGE false
#endif
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: arm_pminitialize
*
* Description:
* This function is called by MCU-specific logic at power-on reset in
* order to provide one-time initialization the power management
* subsystem. This function must be called *very* early in the
* initialization sequence *before* any other device drivers are
* initialized (since they may attempt to register with the power
* management subsystem).
*
****************************************************************************/
void arm_pminitialize(void)
{
/* Initialize the NuttX power management subsystem proper */
pm_initialize();
#if defined(CONFIG_RP23XX_PM) && CONFIG_RP23XX_PM_WAKEUP_GPIO >= 0
/* Arm the wake pin, without which PM_SLEEP is never dormant */
rp23xx_pm_gpio_wakeup(CONFIG_RP23XX_PM_WAKEUP_GPIO,
RP23XX_PM_WAKEUP_ON_EDGE,
RP23XX_PM_WAKEUP_ACTIVE_HIGH);
#endif
#ifdef CONFIG_RP23XX_PM_QUIESCE_PADS
rp23xx_pm_pads_quiesce();
#endif
}

View file

@ -51,6 +51,10 @@
#include "rp23xx_config.h"
#include "rp23xx_serial.h"
#ifdef CONFIG_RP23XX_PM
# include <nuttx/power/pm.h>
#endif
/****************************************************************************
* Pre-processor definitions
****************************************************************************/
@ -109,6 +113,10 @@ static void up_send(struct uart_dev_s *dev, int ch);
static void up_txint(struct uart_dev_s *dev, bool enable);
static bool up_txready(struct uart_dev_s *dev);
static bool up_txempty(struct uart_dev_s *dev);
#ifdef CONFIG_RP23XX_PM
static int up_pm_prepare(struct pm_callback_s *cb, int domain,
enum pm_state_e pmstate);
#endif
/****************************************************************************
* Private Data
@ -133,6 +141,13 @@ static const struct uart_ops_s g_uart_ops =
.txempty = up_txempty,
};
#ifdef CONFIG_RP23XX_PM
static struct pm_callback_s g_serial_pmcb =
{
.prepare = up_pm_prepare,
};
#endif
/* I/O buffers */
#ifdef CONFIG_RP23XX_UART0
@ -609,6 +624,15 @@ static int up_interrupt(int irq, void *context, void *arg)
if (status & (RP23XX_UART_UARTICR_RXIC | RP23XX_UART_UARTICR_RTIC))
{
#ifdef CONFIG_RP23XX_PM
/* The dormant state stops the UART: stay out of it while input
* arrives.
*/
pm_staytimeout(PM_IDLE_DOMAIN, PM_STANDBY,
CONFIG_RP23XX_PM_WAKE_HOLD_MS);
#endif
uart_recvchars(dev);
}
@ -958,6 +982,42 @@ static bool up_txempty(struct uart_dev_s *dev)
((rbr & RP23XX_UART_UARTFR_BUSY) == 0));
}
/****************************************************************************
* Name: up_pm_prepare
*
* Description:
* Refuse PM_SLEEP while a UART transmits. The dormant state stops
* clk_peri, which would hold the transmit until the next wake.
*
****************************************************************************/
#ifdef CONFIG_RP23XX_PM
static int up_pm_prepare(struct pm_callback_s *cb, int domain,
enum pm_state_e pmstate)
{
if (domain != PM_IDLE_DOMAIN || pmstate < PM_SLEEP)
{
return OK;
}
#ifdef CONFIG_RP23XX_UART0
if ((getreg32(RP23XX_UART0_UARTFR) & RP23XX_UART_UARTFR_BUSY) != 0)
{
return -EBUSY;
}
#endif
#ifdef CONFIG_RP23XX_UART1
if ((getreg32(RP23XX_UART1_UARTFR) & RP23XX_UART_UARTFR_BUSY) != 0)
{
return -EBUSY;
}
#endif
return OK;
}
#endif
/****************************************************************************
* Public Functions
****************************************************************************/
@ -1007,6 +1067,10 @@ void arm_serialinit(void)
#ifdef TTYS1_DEV
uart_register("/dev/ttyS1", &TTYS1_DEV);
#endif
#ifdef CONFIG_RP23XX_PM
pm_register(&g_serial_pmcb);
#endif
}
/****************************************************************************

View file

@ -98,7 +98,7 @@ static void led_pm_notify(struct pm_callback_s *cb, int domain,
{
/* Restore normal LEDs operation */
board_userled(BOARD_LED, true);
board_userled(BOARD_LED1, true);
}
break;
@ -106,7 +106,7 @@ static void led_pm_notify(struct pm_callback_s *cb, int domain,
{
/* Entering IDLE mode - Turn leds off */
board_userled(BOARD_LED, false);
board_userled(BOARD_LED1, false);
}
break;

View file

@ -98,7 +98,7 @@ static void led_pm_notify(struct pm_callback_s *cb, int domain,
{
/* Restore normal LEDs operation */
board_userled(BOARD_LED, true);
board_userled(BOARD_LED1, true);
}
break;
@ -106,7 +106,7 @@ static void led_pm_notify(struct pm_callback_s *cb, int domain,
{
/* Entering IDLE mode - Turn leds off */
board_userled(BOARD_LED, false);
board_userled(BOARD_LED1, false);
}
break;

View file

@ -98,7 +98,7 @@ static void led_pm_notify(struct pm_callback_s *cb, int domain,
{
/* Restore normal LEDs operation */
board_userled(BOARD_LED, true);
board_userled(BOARD_LED1, true);
}
break;
@ -106,7 +106,7 @@ static void led_pm_notify(struct pm_callback_s *cb, int domain,
{
/* Entering IDLE mode - Turn leds off */
board_userled(BOARD_LED, false);
board_userled(BOARD_LED1, false);
}
break;