This is an automated email from the ASF dual-hosted git repository.

acassis pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx.git

commit b685c69f25a699b932da87be50267793ea5f5ed8
Author: Marco Casaroli <[email protected]>
AuthorDate: Wed Oct 7 17:59:18 2026 +0200

    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 <[email protected]>
---
 arch/arm/Kconfig                                   |   1 +
 arch/arm/src/rp23xx/CMakeLists.txt                 |   8 +
 arch/arm/src/rp23xx/Kconfig                        |  67 ++
 arch/arm/src/rp23xx/Make.defs                      |   8 +
 arch/arm/src/rp23xx/rp23xx_idle.c                  |  75 +++
 arch/arm/src/rp23xx/rp23xx_pm.c                    | 742 +++++++++++++++++++++
 arch/arm/src/rp23xx/{rp23xx_idle.c => rp23xx_pm.h} | 108 +--
 .../{rp23xx_idle.c => rp23xx_pminitialize.c}       |  69 +-
 arch/arm/src/rp23xx/rp23xx_serial.c                |  64 ++
 .../pimoroni-pico-2-plus/src/rp23xx_userleds.c     |   4 +-
 .../raspberrypi-pico-2/src/rp23xx_userleds.c       |   4 +-
 .../arm/rp23xx/xiao-rp2350/src/rp23xx_userleds.c   |   4 +-
 12 files changed, 1069 insertions(+), 85 deletions(-)

diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
index b1b7a550b05..be209af3edd 100644
--- a/arch/arm/Kconfig
+++ b/arch/arm/Kconfig
@@ -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---
diff --git a/arch/arm/src/rp23xx/CMakeLists.txt 
b/arch/arm/src/rp23xx/CMakeLists.txt
index 4fd9afd4055..00bd52110f1 100644
--- a/arch/arm/src/rp23xx/CMakeLists.txt
+++ b/arch/arm/src/rp23xx/CMakeLists.txt
@@ -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()
diff --git a/arch/arm/src/rp23xx/Kconfig b/arch/arm/src/rp23xx/Kconfig
index 4cc45cd0c6b..577b370f4fe 100644
--- a/arch/arm/src/rp23xx/Kconfig
+++ b/arch/arm/src/rp23xx/Kconfig
@@ -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
diff --git a/arch/arm/src/rp23xx/Make.defs b/arch/arm/src/rp23xx/Make.defs
index a7371fd01ac..ed1b1ae1516 100644
--- a/arch/arm/src/rp23xx/Make.defs
+++ b/arch/arm/src/rp23xx/Make.defs
@@ -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
diff --git a/arch/arm/src/rp23xx/rp23xx_idle.c 
b/arch/arm/src/rp23xx/rp23xx_idle.c
index dc83257fe52..6a7c95be127 100644
--- a/arch/arm/src/rp23xx/rp23xx_idle.c
+++ b/arch/arm/src/rp23xx/rp23xx_idle.c
@@ -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
 }
diff --git a/arch/arm/src/rp23xx/rp23xx_pm.c b/arch/arm/src/rp23xx/rp23xx_pm.c
new file mode 100644
index 00000000000..c5b6b80a4a5
--- /dev/null
+++ b/arch/arm/src/rp23xx/rp23xx_pm.c
@@ -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;
+}
diff --git a/arch/arm/src/rp23xx/rp23xx_idle.c b/arch/arm/src/rp23xx/rp23xx_pm.h
similarity index 50%
copy from arch/arm/src/rp23xx/rp23xx_idle.c
copy to arch/arm/src/rp23xx/rp23xx_pm.h
index dc83257fe52..c7e79307a0d 100644
--- a/arch/arm/src/rp23xx/rp23xx_idle.c
+++ b/arch/arm/src/rp23xx/rp23xx_pm.h
@@ -1,5 +1,5 @@
 /****************************************************************************
- * arch/arm/src/rp23xx/rp23xx_idle.c
+ * arch/arm/src/rp23xx/rp23xx_pm.h
  *
  * SPDX-License-Identifier: Apache-2.0
  *
@@ -20,75 +20,103 @@
  *
  ****************************************************************************/
 
+#ifndef __ARCH_ARM_SRC_RP23XX_RP23XX_PM_H
+#define __ARCH_ARM_SRC_RP23XX_RP23XX_PM_H
+
 /****************************************************************************
  * Included Files
  ****************************************************************************/
 
-#include <arch/board/board.h>
 #include <nuttx/config.h>
 
-#include <nuttx/arch.h>
-#include <nuttx/board.h>
-
-#include <nuttx/irq.h>
+#include <stdint.h>
+#include <stdbool.h>
 
-#include "arm_internal.h"
-
-/****************************************************************************
- * Pre-processor Definitions
- ****************************************************************************/
+#ifdef CONFIG_RP23XX_PM
 
-/* Does the board support an IDLE LED to indicate that the board is in the
- * IDLE state?
- */
+#ifndef __ASSEMBLY__
 
-#if defined(CONFIG_ARCH_LEDS) && defined(LED_IDLE)
-#  define BEGIN_IDLE() board_autoled_on(LED_IDLE)
-#  define END_IDLE()   board_autoled_off(LED_IDLE)
+#undef EXTERN
+#if defined(__cplusplus)
+#define EXTERN extern "C"
+extern "C"
+{
 #else
-#  define BEGIN_IDLE()
-#  define END_IDLE()
+#define EXTERN extern
 #endif
 
 /****************************************************************************
- * Private Data
+ * Public Function Prototypes
  ****************************************************************************/
 
 /****************************************************************************
- * Private Functions
+ * 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);
+
 /****************************************************************************
- * Public Functions
+ * 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: up_idle
+ * Name: rp23xx_pm_gpio_wakeup
  *
  * Description:
- *   up_idle() is the logic that will be executed when there is no other
- *   ready-to-run task.  This is processor idle time and will continue until
- *   some interrupt occurs to cause a context switch from the idle task.
+ *   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.
  *
- *   Processing in this state may be processor-specific. e.g., this is where
- *   power management operations might be performed.
+ * Returned Value:
+ *   Zero on success; a negated errno on failure.
  *
  ****************************************************************************/
 
-void up_idle(void)
-{
-#if defined(CONFIG_SUPPRESS_INTERRUPTS) || defined(CONFIG_SUPPRESS_TIMER_INTS)
-  /* If the system is idle and there are no timer interrupts, then process
-   * "fake" timer interrupts. Hopefully, something will wake up.
-   */
+int rp23xx_pm_gpio_wakeup(int gpio, bool edge, bool high);
 
-  nxsched_process_timer();
-#else
+/****************************************************************************
+ * Name: rp23xx_pm_gpio_wakeup_disable
+ *
+ * Description:
+ *   Disarm a GPIO previously passed to rp23xx_pm_gpio_wakeup().
+ *
+ ****************************************************************************/
 
-  /* Sleep until an interrupt occurs to save power */
+int rp23xx_pm_gpio_wakeup_disable(int gpio);
 
-  BEGIN_IDLE();
-  asm("WFI");
-  END_IDLE();
+/****************************************************************************
+ * 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 */
diff --git a/arch/arm/src/rp23xx/rp23xx_idle.c 
b/arch/arm/src/rp23xx/rp23xx_pminitialize.c
similarity index 55%
copy from arch/arm/src/rp23xx/rp23xx_idle.c
copy to arch/arm/src/rp23xx/rp23xx_pminitialize.c
index dc83257fe52..f3912504866 100644
--- a/arch/arm/src/rp23xx/rp23xx_idle.c
+++ b/arch/arm/src/rp23xx/rp23xx_pminitialize.c
@@ -1,5 +1,5 @@
 /****************************************************************************
- * arch/arm/src/rp23xx/rp23xx_idle.c
+ * arch/arm/src/rp23xx/rp23xx_pminitialize.c
  *
  * SPDX-License-Identifier: Apache-2.0
  *
@@ -24,71 +24,62 @@
  * Included Files
  ****************************************************************************/
 
-#include <arch/board/board.h>
 #include <nuttx/config.h>
 
 #include <nuttx/arch.h>
-#include <nuttx/board.h>
-
-#include <nuttx/irq.h>
+#include <nuttx/power/pm.h>
 
 #include "arm_internal.h"
+#include "rp23xx_pm.h"
 
 /****************************************************************************
  * Pre-processor Definitions
  ****************************************************************************/
 
-/* Does the board support an IDLE LED to indicate that the board is in the
- * IDLE state?
- */
-
-#if defined(CONFIG_ARCH_LEDS) && defined(LED_IDLE)
-#  define BEGIN_IDLE() board_autoled_on(LED_IDLE)
-#  define END_IDLE()   board_autoled_off(LED_IDLE)
+#ifdef CONFIG_RP23XX_PM_WAKEUP_GPIO_HIGH
+#  define RP23XX_PM_WAKEUP_ACTIVE_HIGH true
 #else
-#  define BEGIN_IDLE()
-#  define END_IDLE()
+#  define RP23XX_PM_WAKEUP_ACTIVE_HIGH false
 #endif
 
-/****************************************************************************
- * Private Data
- ****************************************************************************/
-
-/****************************************************************************
- * Private Functions
- ****************************************************************************/
+#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: up_idle
+ * Name: arm_pminitialize
  *
  * Description:
- *   up_idle() is the logic that will be executed when there is no other
- *   ready-to-run task.  This is processor idle time and will continue until
- *   some interrupt occurs to cause a context switch from the idle task.
- *
- *   Processing in this state may be processor-specific. e.g., this is where
- *   power management operations might be performed.
+ *   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 up_idle(void)
+void arm_pminitialize(void)
 {
-#if defined(CONFIG_SUPPRESS_INTERRUPTS) || defined(CONFIG_SUPPRESS_TIMER_INTS)
-  /* If the system is idle and there are no timer interrupts, then process
-   * "fake" timer interrupts. Hopefully, something will wake up.
-   */
+  /* Initialize the NuttX power management subsystem proper */
 
-  nxsched_process_timer();
-#else
+  pm_initialize();
+
+#if defined(CONFIG_RP23XX_PM) && CONFIG_RP23XX_PM_WAKEUP_GPIO >= 0
+  /* Arm the wake pin, without which PM_SLEEP is never dormant */
 
-  /* Sleep until an interrupt occurs to save power */
+  rp23xx_pm_gpio_wakeup(CONFIG_RP23XX_PM_WAKEUP_GPIO,
+                        RP23XX_PM_WAKEUP_ON_EDGE,
+                        RP23XX_PM_WAKEUP_ACTIVE_HIGH);
+#endif
 
-  BEGIN_IDLE();
-  asm("WFI");
-  END_IDLE();
+#ifdef CONFIG_RP23XX_PM_QUIESCE_PADS
+  rp23xx_pm_pads_quiesce();
 #endif
 }
diff --git a/arch/arm/src/rp23xx/rp23xx_serial.c 
b/arch/arm/src/rp23xx/rp23xx_serial.c
index da71331dced..9dab27de35f 100644
--- a/arch/arm/src/rp23xx/rp23xx_serial.c
+++ b/arch/arm/src/rp23xx/rp23xx_serial.c
@@ -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
 }
 
 /****************************************************************************
diff --git a/boards/arm/rp23xx/pimoroni-pico-2-plus/src/rp23xx_userleds.c 
b/boards/arm/rp23xx/pimoroni-pico-2-plus/src/rp23xx_userleds.c
index 5b3dc423730..6c33924edf6 100644
--- a/boards/arm/rp23xx/pimoroni-pico-2-plus/src/rp23xx_userleds.c
+++ b/boards/arm/rp23xx/pimoroni-pico-2-plus/src/rp23xx_userleds.c
@@ -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;
 
diff --git a/boards/arm/rp23xx/raspberrypi-pico-2/src/rp23xx_userleds.c 
b/boards/arm/rp23xx/raspberrypi-pico-2/src/rp23xx_userleds.c
index a57886a40d1..3c39bfb85d0 100644
--- a/boards/arm/rp23xx/raspberrypi-pico-2/src/rp23xx_userleds.c
+++ b/boards/arm/rp23xx/raspberrypi-pico-2/src/rp23xx_userleds.c
@@ -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;
 
diff --git a/boards/arm/rp23xx/xiao-rp2350/src/rp23xx_userleds.c 
b/boards/arm/rp23xx/xiao-rp2350/src/rp23xx_userleds.c
index 0a0962b9048..1ddedf927ae 100644
--- a/boards/arm/rp23xx/xiao-rp2350/src/rp23xx_userleds.c
+++ b/boards/arm/rp23xx/xiao-rp2350/src/rp23xx_userleds.c
@@ -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;
 

Reply via email to