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

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

commit 275ab8e83f1be8abbf74fdb1fa49c162ab721d5c
Author: Jorge Guzman <[email protected]>
AuthorDate: Sun Oct 25 02:42:20 2026 -0300

    arch/arm/gd32w51x: add the deep-sleep power management
    
    With CONFIG_PM, the PM_STANDBY state of the IDLE domain is the PMU
    deep-sleep mode: the core and bus clocks stop, SRAM and registers keep
    their contents, the LDO runs in low power and low-driver mode.
    
    - idle: custom up_idle() that runs the PM state machine; PM_STANDBY
      enters deep-sleep and goes back to PM_NORMAL on wake-up.
    - pm: arm_pminitialize() keeps the debugger connection in deep-sleep
      (DBG_CTL0 DSLP_HOLD), so that a sleeping board can still be
      programmed.  gd32w51x_pmstop() waits for the console, sleeps, sets
      the PLL up again and corrects the system time with
      clock_resynchronize().  WFI only wakes up on an interrupt that could
      be taken, and the critical section raises BASEPRI: the wait masks the
      interrupts with PRIMASK instead.
    - rtc: the wake-up timer (RTC clock / 16, EXTI line 24) ends the sleep
      after CONFIG_GD32W51X_PM_WAKEUP_MS, so that the timers keep running;
      up_rtc_getdatetime_with_subseconds() gives the RTC time with its
      sub-second counter, for an accurate correction.
    - serial: received characters are reported as PM activity.
    - boards/gd-xd-w515-eval: new pm configuration, NSH with the power
      management (activity governor), its procfs and the RTC.
    
    Tested on the GD-xD-W515-EVAL: idle for 33 s, the board spent 22 s in
    deep-sleep, NSH answered within 1 s, and after a minute mostly asleep
    the system time still matched the host within the 1 s resolution of
    "date".
    
    Assisted-by: Claude Code:claude-opus-5-5
    Signed-off-by: Jorge Guzman <[email protected]>
---
 .../arm/gd32w51x/boards/gd-xd-w515-eval/index.rst  |  25 +++
 arch/arm/Kconfig                                   |   2 +
 arch/arm/src/gd32w51x/CMakeLists.txt               |   5 +
 arch/arm/src/gd32w51x/Kconfig                      |  22 +++
 arch/arm/src/gd32w51x/Make.defs                    |   6 +-
 arch/arm/src/gd32w51x/gd32w51x_idle.c              | 131 ++++++++++++++++
 arch/arm/src/gd32w51x/gd32w51x_pm.c                | 173 +++++++++++++++++++++
 arch/arm/src/gd32w51x/gd32w51x_pm.h                |  85 ++++++++++
 arch/arm/src/gd32w51x/gd32w51x_rtc.c               | 163 ++++++++++++++++++-
 arch/arm/src/gd32w51x/gd32w51x_serial.c            |   7 +
 .../gd32w51x/gd-xd-w515-eval/configs/pm/defconfig  |  54 +++++++
 11 files changed, 671 insertions(+), 2 deletions(-)

diff --git 
a/Documentation/platforms/arm/gd32w51x/boards/gd-xd-w515-eval/index.rst 
b/Documentation/platforms/arm/gd32w51x/boards/gd-xd-w515-eval/index.rst
index 338fd3bd06e..82d48b06b2f 100644
--- a/Documentation/platforms/arm/gd32w51x/boards/gd-xd-w515-eval/index.rst
+++ b/Documentation/platforms/arm/gd32w51x/boards/gd-xd-w515-eval/index.rst
@@ -262,3 +262,28 @@ reboot:
     nsh> echo hello > /flash/hello.txt
     nsh> cat /flash/hello.txt
     hello
+
+pm
+--
+
+NSH with the power management (``CONFIG_PM``, activity governor) and the
+RTC.  After a few seconds without activity the governor selects
+``PM_STANDBY``, which is the deep-sleep mode of the GD32W51x: the core and
+bus clocks stop, the SRAM and the registers keep their contents.  The chip
+wakes up on any EXTI interrupt and on the RTC wake-up timer every
+``CONFIG_GD32W51X_PM_WAKEUP_MS`` (1 s), sets the PLL up again and corrects
+the system time with the time spent asleep, read from the RTC with its
+sub-second counter.  Timers may run late by up to that period while the
+board sleeps, and a character received while the board sleeps is lost:
+press Enter until NSH answers, then the console activity keeps the board
+awake.  The debugger connection is kept in deep-sleep, so a sleeping board
+can still be programmed.
+
+::
+
+    nsh> cat /proc/pm/state0
+    DOMAIN0                   WAKE           SLEEP          TOTAL
+    normal                   20s  17%        1s   0%       21s  18%
+    idle                     15s  13%        0s   0%       15s  13%
+    standby                  76s  67%        0s   0%       76s  67%
+    sleep                     0s   0%        0s   0%        0s   0%
diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
index a121045fb85..9a7c40cbe6b 100644
--- a/arch/arm/Kconfig
+++ b/arch/arm/Kconfig
@@ -171,6 +171,8 @@ config ARCH_CHIP_GD32W51X
        select ARCH_HAVE_PROGMEM
        select ARCH_HAVE_PWM_MULTICHAN
        select ARCH_HAVE_RESET
+       select ARCH_HAVE_RTC_SUBSECONDS if RTC
+       select ARCH_IDLE_CUSTOM
        select ARM_HAVE_DSP
        select ARM_HAVE_MPU_UNIFIED
        select ARMV8M_HAVE_STACKCHECK
diff --git a/arch/arm/src/gd32w51x/CMakeLists.txt 
b/arch/arm/src/gd32w51x/CMakeLists.txt
index 7383450c949..d7026c7fdcb 100644
--- a/arch/arm/src/gd32w51x/CMakeLists.txt
+++ b/arch/arm/src/gd32w51x/CMakeLists.txt
@@ -28,8 +28,13 @@ set(SRCS
     gd32w51x_lowputc.c
     gd32w51x_serial.c
     gd32w51x_tzinit.c
+    gd32w51x_idle.c
     gd32w51x_gpio.c)
 
+if(CONFIG_PM)
+  list(APPEND SRCS gd32w51x_pm.c)
+endif()
+
 if(CONFIG_RTC)
   list(APPEND SRCS gd32w51x_rtc.c)
 endif()
diff --git a/arch/arm/src/gd32w51x/Kconfig b/arch/arm/src/gd32w51x/Kconfig
index 7f11e7707fc..7c0a457728c 100644
--- a/arch/arm/src/gd32w51x/Kconfig
+++ b/arch/arm/src/gd32w51x/Kconfig
@@ -373,3 +373,25 @@ config GD32W51X_PROGMEM_SIZE
 
 endmenu # Program memory Configuration
 
+menu "Power Management Configuration"
+       depends on PM
+
+config GD32W51X_PM_WAKEUP_MS
+       int "Wake-up period in standby (ms)"
+       default 1000
+       range 0 32000
+       depends on RTC
+       ---help---
+               In the PM_STANDBY state the chip is in deep-sleep, where the
+               system timer stops.  Besides any EXTI interrupt, the RTC wake-up
+               timer ends the deep-sleep after this period, so that the timers
+               still run, late by at most this period.  0 disables it.
+
+config GD32W51X_PM_SERIAL_ACTIVITY
+       int "PM serial activity"
+       default 10
+       ---help---
+               PM activity reported to the power management logic on every
+               received character.
+
+endmenu # Power Management Configuration
diff --git a/arch/arm/src/gd32w51x/Make.defs b/arch/arm/src/gd32w51x/Make.defs
index 10e97866801..d3b1f208eaa 100644
--- a/arch/arm/src/gd32w51x/Make.defs
+++ b/arch/arm/src/gd32w51x/Make.defs
@@ -24,7 +24,11 @@ include armv8-m/Make.defs
 
 CHIP_CSRCS  = gd32w51x_start.c gd32w51x_clockconfig.c gd32w51x_irq.c
 CHIP_CSRCS += gd32w51x_timerisr.c gd32w51x_lowputc.c gd32w51x_serial.c
-CHIP_CSRCS += gd32w51x_tzinit.c
+CHIP_CSRCS += gd32w51x_tzinit.c gd32w51x_idle.c
+
+ifeq ($(CONFIG_PM),y)
+CHIP_CSRCS += gd32w51x_pm.c
+endif
 
 ifeq ($(CONFIG_RTC),y)
 CHIP_CSRCS += gd32w51x_rtc.c
diff --git a/arch/arm/src/gd32w51x/gd32w51x_idle.c 
b/arch/arm/src/gd32w51x/gd32w51x_idle.c
new file mode 100644
index 00000000000..83ef924267c
--- /dev/null
+++ b/arch/arm/src/gd32w51x/gd32w51x_idle.c
@@ -0,0 +1,131 @@
+/****************************************************************************
+ * arch/arm/src/gd32w51x/gd32w51x_idle.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 <debug.h>
+
+#include <nuttx/arch.h>
+#include <nuttx/irq.h>
+#include <nuttx/power/pm.h>
+
+#include "arm_internal.h"
+#include "gd32w51x_pm.h"
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: up_idlepm
+ *
+ * Description:
+ *   Perform the IDLE state power management: the PM_STANDBY state is the
+ *   deep-sleep mode of the GD32W51x.
+ *
+ ****************************************************************************/
+
+#ifdef CONFIG_PM
+static void up_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);
+
+  if (newstate == oldstate)
+    {
+      return;
+    }
+
+  flags = enter_critical_section();
+
+  /* Force the global state change */
+
+  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
+    {
+      oldstate = newstate;
+
+      if (newstate == PM_STANDBY)
+        {
+          /* Sleep until an interrupt, then tell the drivers that the
+           * system is running again.
+           */
+
+          gd32w51x_pmstop();
+
+          if (pm_changestate(PM_IDLE_DOMAIN, PM_NORMAL) >= 0)
+            {
+              oldstate = PM_NORMAL;
+            }
+        }
+    }
+
+  leave_critical_section(flags);
+}
+#else
+#  define up_idlepm()
+#endif
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: up_idle
+ *
+ * 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.
+ *
+ ****************************************************************************/
+
+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.
+   */
+
+  nxsched_process_timer();
+#else
+  /* Perform IDLE mode power management */
+
+  up_idlepm();
+#endif
+}
diff --git a/arch/arm/src/gd32w51x/gd32w51x_pm.c 
b/arch/arm/src/gd32w51x/gd32w51x_pm.c
new file mode 100644
index 00000000000..71067913a3d
--- /dev/null
+++ b/arch/arm/src/gd32w51x/gd32w51x_pm.c
@@ -0,0 +1,173 @@
+/****************************************************************************
+ * arch/arm/src/gd32w51x/gd32w51x_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 <stdint.h>
+
+#include <nuttx/arch.h>
+#include <nuttx/clock.h>
+#include <nuttx/power/pm.h>
+
+#include "arm_internal.h"
+#include "nvic.h"
+#include "chip.h"
+#include "gd32w51x_clockconfig.h"
+#include "gd32w51x_pm.h"
+#include "hardware/gd32w51x_uart.h"
+
+#ifdef CONFIG_PM
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* PMU_CTL0: LDO and low-driver modes during deep-sleep */
+
+#define GD32W51X_PMU_CTL0       (GD32W51X_PMU_BASE + 0x0000)
+#define PMU_CTL0_LDOLP          (1 << 0)   /* LDO low power mode */
+#define PMU_CTL0_STBMOD         (1 << 1)   /* Standby mode */
+#define PMU_CTL0_LDLP           (1 << 10)  /* Low-driver with low power LDO */
+#define PMU_CTL0_LDNP           (1 << 11)  /* Low-driver with normal LDO */
+#define PMU_CTL0_LDEN           (3 << 18)  /* Low-driver mode enable */
+
+/* DBG_CTL0: keep the debugger connection in deep-sleep, so that a sleeping
+ * board can still be programmed.
+ */
+
+#define GD32W51X_DBG_CTL0       0xe0044004
+#define DBG_CTL0_DSLP_HOLD      (1 << 1)
+
+/* The console UART, emptied before deep-sleep stops its clock */
+
+#if defined(CONFIG_USART0_SERIAL_CONSOLE)
+#  define CONSOLE_BASE          GD32W51X_USART0_BASE
+#elif defined(CONFIG_UART1_SERIAL_CONSOLE)
+#  define CONSOLE_BASE          GD32W51X_UART1_BASE
+#elif defined(CONFIG_UART2_SERIAL_CONSOLE)
+#  define CONSOLE_BASE          GD32W51X_UART2_BASE
+#endif
+
+#define CONSOLE_FLUSH_TIMEOUT   1000000
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: arm_pminitialize
+ *
+ * Description:
+ *   Initialize the power management, early in up_initialize().
+ *
+ ****************************************************************************/
+
+void arm_pminitialize(void)
+{
+  modifyreg32(GD32W51X_DBG_CTL0, 0, DBG_CTL0_DSLP_HOLD);
+  pm_initialize();
+}
+
+/****************************************************************************
+ * Name: gd32w51x_pmstop
+ *
+ * Description:
+ *   Enter the deep-sleep mode: the core and bus clocks stop, SRAM and
+ *   registers keep their contents.  Any EXTI interrupt wakes the chip up,
+ *   and so does the RTC wake-up timer after
+ *   CONFIG_GD32W51X_PM_WAKEUP_MS milliseconds (when it is not zero).  On
+ *   return the clock tree is set up again and the system time is
+ *   corrected with the time spent asleep.
+ *
+ *   Called with the interrupts disabled.
+ *
+ ****************************************************************************/
+
+void gd32w51x_pmstop(void)
+{
+#ifdef CONSOLE_BASE
+  int timeout;
+#endif
+  uint8_t basepri;
+
+#ifdef CONSOLE_BASE
+  /* Let the console finish the character on the wire */
+
+  for (timeout = CONSOLE_FLUSH_TIMEOUT; timeout > 0; timeout--)
+    {
+      if ((getreg32(CONSOLE_BASE + GD32W51X_UART_STAT_OFFSET) &
+           UART_STAT_TC) != 0)
+        {
+          break;
+        }
+    }
+#endif
+
+#if defined(CONFIG_RTC) && CONFIG_GD32W51X_PM_WAKEUP_MS > 0
+  gd32w51x_rtc_setwakeup(CONFIG_GD32W51X_PM_WAKEUP_MS);
+#endif
+
+  /* LDO in low power mode and low-driver mode during deep-sleep */
+
+  modifyreg32(GD32W51X_PMU_CTL0,
+              PMU_CTL0_STBMOD | PMU_CTL0_LDOLP | PMU_CTL0_LDEN |
+              PMU_CTL0_LDNP | PMU_CTL0_LDLP,
+              PMU_CTL0_LDOLP | PMU_CTL0_LDEN | PMU_CTL0_LDLP);
+
+  /* WFI only wakes up on an interrupt that could be taken: the critical
+   * section raises BASEPRI, which would mask the wake-up interrupts.  Mask
+   * them with PRIMASK instead while waiting, so that they wake the core up
+   * and are serviced once the critical section is left.
+   */
+
+  basepri = getbasepri();
+  cpsid();
+  setbasepri(0);
+
+  modifyreg32(NVIC_SYSCON, 0, NVIC_SYSCON_SLEEPDEEP);
+  UP_DSB();
+  asm volatile ("wfi");
+  UP_ISB();
+  modifyreg32(NVIC_SYSCON, NVIC_SYSCON_SLEEPDEEP, 0);
+
+  setbasepri(basepri);
+  cpsie();
+
+  /* The chip wakes up on the IRC16M: bring the PLL back */
+
+  gd32w51x_clockconfig();
+
+#ifdef CONFIG_RTC
+  gd32w51x_rtc_setwakeup(0);
+  gd32w51x_rtc_resync();
+
+  /* The system timer was stopped: add the time spent asleep */
+
+  clock_resynchronize(NULL);
+#endif
+}
+
+#endif /* CONFIG_PM */
diff --git a/arch/arm/src/gd32w51x/gd32w51x_pm.h 
b/arch/arm/src/gd32w51x/gd32w51x_pm.h
new file mode 100644
index 00000000000..e966c29eb86
--- /dev/null
+++ b/arch/arm/src/gd32w51x/gd32w51x_pm.h
@@ -0,0 +1,85 @@
+/****************************************************************************
+ * arch/arm/src/gd32w51x/gd32w51x_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_GD32W51X_GD32W51X_PM_H
+#define __ARCH_ARM_SRC_GD32W51X_GD32W51X_PM_H
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <stdint.h>
+
+/****************************************************************************
+ * Public Function Prototypes
+ ****************************************************************************/
+
+#ifdef CONFIG_PM
+
+/****************************************************************************
+ * Name: gd32w51x_pmstop
+ *
+ * Description:
+ *   Enter the deep-sleep mode: the core and bus clocks stop, SRAM and
+ *   registers keep their contents.  Any EXTI interrupt wakes the chip up,
+ *   and so does the RTC wake-up timer after
+ *   CONFIG_GD32W51X_PM_WAKEUP_MS milliseconds (when it is not zero).  On
+ *   return the clock tree is set up again and the system time is
+ *   corrected with the time spent asleep.
+ *
+ *   Called with the interrupts disabled.
+ *
+ ****************************************************************************/
+
+void gd32w51x_pmstop(void);
+
+#endif /* CONFIG_PM */
+
+#ifdef CONFIG_RTC
+
+/****************************************************************************
+ * Name: gd32w51x_rtc_setwakeup
+ *
+ * Description:
+ *   Start the RTC wake-up timer to raise its interrupt (EXTI line 24) in
+ *   'msec' milliseconds, at most about 32 s, or stop it if 'msec' is 0.
+ *
+ ****************************************************************************/
+
+int gd32w51x_rtc_setwakeup(uint32_t msec);
+
+/****************************************************************************
+ * Name: gd32w51x_rtc_resync
+ *
+ * Description:
+ *   Wait for the calendar shadow registers to be updated, which they are
+ *   not in deep-sleep.
+ *
+ ****************************************************************************/
+
+int gd32w51x_rtc_resync(void);
+
+#endif /* CONFIG_RTC */
+
+#endif /* __ARCH_ARM_SRC_GD32W51X_GD32W51X_PM_H */
diff --git a/arch/arm/src/gd32w51x/gd32w51x_rtc.c 
b/arch/arm/src/gd32w51x/gd32w51x_rtc.c
index 4298eeaff8b..89d80307f51 100644
--- a/arch/arm/src/gd32w51x/gd32w51x_rtc.c
+++ b/arch/arm/src/gd32w51x/gd32w51x_rtc.c
@@ -34,13 +34,17 @@
 #include <debug.h>
 
 #include <nuttx/arch.h>
+#include <nuttx/clock.h>
+#include <nuttx/irq.h>
 #include <arch/board/board.h>
 
 #include "arm_internal.h"
 #include "nvic.h"
 #include "dwt.h"
+#include "hardware/gd32w51x_exti.h"
 #include "hardware/gd32w51x_rcu.h"
 #include "chip.h"
+#include "gd32w51x_pm.h"
 
 #ifdef CONFIG_RTC
 
@@ -55,15 +59,36 @@
 #define GD32W51X_RTC_CTL       (GD32W51X_RTC_BASE + 0x0008)
 #define GD32W51X_RTC_ICS      (GD32W51X_RTC_BASE + 0x000c)
 #define GD32W51X_RTC_PSC       (GD32W51X_RTC_BASE + 0x0010)
+#define GD32W51X_RTC_WUT       (GD32W51X_RTC_BASE + 0x0014)
 #define GD32W51X_RTC_WPK       (GD32W51X_RTC_BASE + 0x0024)
 #define GD32W51X_RTC_SS        (GD32W51X_RTC_BASE + 0x0028)
+#define GD32W51X_RTC_STATC     (GD32W51X_RTC_BASE + 0x0064)
+
+/* CTL bits */
+
+#define RTC_CTL_WTCS_MASK      (7 << 0)   /* Wake-up timer clock */
+#define RTC_CTL_WTCS_DIV16     (0 << 0)   /* RTC clock / 16 */
+#define RTC_CTL_WTEN           (1 << 10)  /* Wake-up timer enable */
+#define RTC_CTL_WTIE           (1 << 14)  /* Wake-up timer interrupt */
 
 /* STAT bits */
 
+#define RTC_ICS_WTWF           (1 << 2)   /* Wake-up timer writable */
 #define RTC_ICS_RSYNF          (1 << 5)   /* Registers synchronized */
 #define RTC_ICS_INITF          (1 << 6)   /* In initialization state */
 #define RTC_ICS_INITM          (1 << 7)   /* Enter initialization mode */
 
+#define RTC_STATC_WTFC         (1 << 2)   /* Clear the wake-up timer flag */
+
+/* The wake-up timer counts the RTC clock divided by 16, on 16 bits; its
+ * interrupt goes through EXTI line 24 (the non-secure line, line 25 is the
+ * secure one).
+ */
+
+#define RTC_WUT_DIV            16
+#define RTC_WUT_MAX            0xffff
+#define RTC_WKUP_EXTI_LINE     (1 << 24)
+
 /* Write-protection key sequence */
 
 #define RTC_UNLOCK_KEY1         0xca
@@ -125,6 +150,14 @@
 
 volatile bool g_rtc_enabled = false;
 
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+/* RTC clock frequency, measured at boot */
+
+static uint32_t g_rtc_frequency = RTC_IRC32K_FREQUENCY;
+
 /****************************************************************************
  * Private Functions
  ****************************************************************************/
@@ -263,10 +296,28 @@ static uint32_t rtc_sync_prescaler(void)
     }
 
   rtcinfo("IRC32K: %" PRIu32 " Hz\n", freq);
+  g_rtc_frequency = freq;
 
   return (freq + (RTC_PSC_FACTOR_A + 1) / 2) / (RTC_PSC_FACTOR_A + 1) - 1;
 }
 
+/****************************************************************************
+ * Name: rtc_wakeup_interrupt
+ *
+ * Description:
+ *   The RTC wake-up timer interrupt: its only job is to end deep-sleep.
+ *
+ ****************************************************************************/
+
+#ifdef CONFIG_PM
+static int rtc_wakeup_interrupt(int irq, void *context, void *arg)
+{
+  putreg32(RTC_STATC_WTFC, GD32W51X_RTC_STATC);
+  putreg32(RTC_WKUP_EXTI_LINE, GD32W51X_EXTI_PD);
+  return OK;
+}
+#endif
+
 /****************************************************************************
  * Public Functions
  ****************************************************************************/
@@ -367,14 +418,39 @@ int up_rtc_initialize(void)
 
 int up_rtc_getdatetime(struct tm *tp)
 {
+  long nsec;
+
+  return up_rtc_getdatetime_with_subseconds(tp, &nsec);
+}
+
+/****************************************************************************
+ * Name: up_rtc_getdatetime_with_subseconds
+ *
+ * Description:
+ *   Get the current date and time from the hardware RTC, with the part of
+ *   the current second in nanoseconds.
+ *
+ ****************************************************************************/
+
+int up_rtc_getdatetime_with_subseconds(struct tm *tp, long *nsec)
+{
+  uint32_t sync;
+  uint32_t ss;
   uint32_t time;
   uint32_t date;
 
-  /* Reading TIME locks DATE until DATE is read, so this pair is atomic */
+  /* Reading SS locks TIME and DATE until DATE is read, so the three reads
+   * are a single sample.  SS counts down from the synchronous prescaler.
+   */
 
+  sync = getreg32(GD32W51X_RTC_PSC) & 0x7fff;
+  ss   = getreg32(GD32W51X_RTC_SS) & RTC_SS_MASK;
   time = getreg32(GD32W51X_RTC_TIME);
   date = getreg32(GD32W51X_RTC_DATE);
 
+  ss    = MIN(ss, sync);
+  *nsec = (long)(((uint64_t)(sync - ss) * NSEC_PER_SEC) / (sync + 1));
+
   tp->tm_sec  = BCD2DEC(time & 0x7f);
   tp->tm_min  = BCD2DEC((time >> 8) & 0x7f);
   tp->tm_hour = BCD2DEC((time >> 16) & 0x3f);
@@ -451,4 +527,89 @@ int up_rtc_settime(const struct timespec *tp)
   return rtc_synchwait();
 }
 
+/****************************************************************************
+ * Name: gd32w51x_rtc_setwakeup
+ *
+ * Description:
+ *   Start the RTC wake-up timer to raise its interrupt (EXTI line 24) in
+ *   'msec' milliseconds, at most about 32 s, or stop it if 'msec' is 0.
+ *
+ ****************************************************************************/
+
+int gd32w51x_rtc_setwakeup(uint32_t msec)
+{
+  uint64_t ticks;
+  int timeout;
+  int ret = OK;
+
+#ifdef CONFIG_PM
+  static bool attached;
+
+  if (!attached)
+    {
+      irq_attach(GD32W51X_IRQ_RTC_WKUP, rtc_wakeup_interrupt, NULL);
+      up_enable_irq(GD32W51X_IRQ_RTC_WKUP);
+      attached = true;
+    }
+#endif
+
+  rtc_unlock();
+
+  /* Stop the timer, then wait until its reload value can be written */
+
+  modifyreg32(GD32W51X_RTC_CTL, RTC_CTL_WTEN | RTC_CTL_WTIE, 0);
+  putreg32(RTC_STATC_WTFC, GD32W51X_RTC_STATC);
+  modifyreg32(GD32W51X_EXTI_INTEN, RTC_WKUP_EXTI_LINE, 0);
+  putreg32(RTC_WKUP_EXTI_LINE, GD32W51X_EXTI_PD);
+
+  if (msec > 0)
+    {
+      for (timeout = 1000000; timeout > 0; timeout--)
+        {
+          if ((getreg32(GD32W51X_RTC_ICS) & RTC_ICS_WTWF) != 0)
+            {
+              break;
+            }
+        }
+
+      if (timeout <= 0)
+        {
+          ret = -ETIMEDOUT;
+        }
+      else
+        {
+          /* The timer counts the reload value plus one clock down to 0 */
+
+          ticks = ((uint64_t)msec * (g_rtc_frequency / RTC_WUT_DIV)) /
+                  MSEC_PER_SEC;
+          ticks = MAX(MIN(ticks, RTC_WUT_MAX), 1);
+
+          putreg32((uint32_t)ticks - 1, GD32W51X_RTC_WUT);
+          modifyreg32(GD32W51X_RTC_CTL, RTC_CTL_WTCS_MASK,
+                      RTC_CTL_WTCS_DIV16);
+
+          modifyreg32(GD32W51X_EXTI_RTEN, 0, RTC_WKUP_EXTI_LINE);
+          modifyreg32(GD32W51X_EXTI_INTEN, 0, RTC_WKUP_EXTI_LINE);
+          modifyreg32(GD32W51X_RTC_CTL, 0, RTC_CTL_WTIE | RTC_CTL_WTEN);
+        }
+    }
+
+  rtc_lock();
+  return ret;
+}
+
+/****************************************************************************
+ * Name: gd32w51x_rtc_resync
+ *
+ * Description:
+ *   Wait for the calendar shadow registers to be updated, which they are
+ *   not in deep-sleep.
+ *
+ ****************************************************************************/
+
+int gd32w51x_rtc_resync(void)
+{
+  return rtc_synchwait();
+}
+
 #endif /* CONFIG_RTC */
diff --git a/arch/arm/src/gd32w51x/gd32w51x_serial.c 
b/arch/arm/src/gd32w51x/gd32w51x_serial.c
index 253d7169125..6ebdb834586 100644
--- a/arch/arm/src/gd32w51x/gd32w51x_serial.c
+++ b/arch/arm/src/gd32w51x/gd32w51x_serial.c
@@ -37,6 +37,7 @@
 #include <nuttx/irq.h>
 #include <nuttx/arch.h>
 #include <nuttx/serial/serial.h>
+#include <nuttx/power/pm.h>
 
 #include <arch/board/board.h>
 
@@ -355,6 +356,12 @@ static int gd32w51x_interrupt(int irq, void *context, void 
*arg)
   if ((status & UART_STAT_RBNE) != 0 &&
       (priv->im & UART_CTL0_RBNEIE) != 0)
     {
+#ifdef CONFIG_PM
+      /* Keep the system awake while characters are received */
+
+      pm_activity(PM_IDLE_DOMAIN, CONFIG_GD32W51X_PM_SERIAL_ACTIVITY);
+#endif
+
       uart_recvchars(dev);
     }
 
diff --git a/boards/arm/gd32w51x/gd-xd-w515-eval/configs/pm/defconfig 
b/boards/arm/gd32w51x/gd-xd-w515-eval/configs/pm/defconfig
new file mode 100644
index 00000000000..efb76f82929
--- /dev/null
+++ b/boards/arm/gd32w51x/gd-xd-w515-eval/configs/pm/defconfig
@@ -0,0 +1,54 @@
+#
+# This file is autogenerated: PLEASE DO NOT EDIT IT.
+#
+# You can use "make menuconfig" to make any modifications to the installed 
.config file.
+# You can then do "make savedefconfig" to generate a new defconfig file that 
includes your
+# modifications.
+#
+# CONFIG_ARCH_LEDS is not set
+CONFIG_ARCH="arm"
+CONFIG_ARCH_BOARD="gd-xd-w515-eval"
+CONFIG_ARCH_BOARD_GD_XD_W515_EVAL=y
+CONFIG_ARCH_CHIP="gd32w51x"
+CONFIG_ARCH_CHIP_GD32W515PI=y
+CONFIG_ARCH_CHIP_GD32W51X=y
+CONFIG_ARCH_INTERRUPTSTACK=2048
+CONFIG_ARCH_STACKDUMP=y
+CONFIG_BOARDCTL_RESET=y
+CONFIG_BOARD_LOOPSPERMSEC=30000
+CONFIG_BUILTIN=y
+CONFIG_DEBUG_FULLOPT=y
+CONFIG_DEBUG_SYMBOLS=y
+CONFIG_EXAMPLES_HELLO=y
+CONFIG_FS_PROCFS=y
+CONFIG_FS_PROCFS_REGISTER=y
+CONFIG_GD32W51X_UART2=y
+CONFIG_IDLETHREAD_STACKSIZE=2048
+CONFIG_INIT_ENTRYPOINT="nsh_main"
+CONFIG_INIT_STACKSIZE=3072
+CONFIG_INTELHEX_BINARY=y
+CONFIG_LIBC_PERROR_STDOUT=y
+CONFIG_LIBC_STRERROR=y
+CONFIG_NFILE_DESCRIPTORS_PER_BLOCK=6
+CONFIG_NSH_BUILTIN_APPS=y
+CONFIG_NSH_FILEIOSIZE=512
+CONFIG_NSH_MAXARGUMENTS=12
+CONFIG_NSH_READLINE=y
+CONFIG_NSH_STRERROR=y
+CONFIG_PM=y
+CONFIG_PM_GOVERNOR_ACTIVITY=y
+CONFIG_PM_PROCFS=y
+CONFIG_RAM_SIZE=458752
+CONFIG_RAM_START=0x20000000
+CONFIG_RAW_BINARY=y
+CONFIG_RR_INTERVAL=200
+CONFIG_RTC=y
+CONFIG_RTC_DATETIME=y
+CONFIG_SCHED_WAITPID=y
+CONFIG_STACK_COLORATION=y
+CONFIG_START_DAY=18
+CONFIG_START_MONTH=10
+CONFIG_START_YEAR=2026
+CONFIG_SYSTEM_NSH=y
+CONFIG_TASK_NAME_SIZE=12
+CONFIG_UART2_SERIAL_CONSOLE=y

Reply via email to