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 46baea6dbeb4a7f95275a812bcc230262555258e
Author: Jorge Guzman <[email protected]>
AuthorDate: Sun Oct 25 18:40:49 2026 -0300

    arch/risc-v/gd32vw55x: 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.  Otherwise
      the core waits for an interrupt, as the common up_idle() does.
    - pm: riscv_pminitialize() keeps the debugger connection in deep-sleep
      (DBG_CTL0 DSLP_HOLD).  gd32vw55x_pmstop() sleeps until the next alarm
      of the system timer, at most CONFIG_GD32VW55X_PM_WAKEUP_MS, waits on
      WFI with the SLEEPVALUE CSR set, sets the PLL up again and advances
      the system timer, stopped in deep-sleep, by the time spent asleep.
    - rtc: the wake-up timer (RTC clock / 16, EXTI line 21) ends the sleep.
      The time asleep is measured on the calendar counters, bypassing the
      shadow registers, which are not updated in deep-sleep; both readings
      are taken at the start of a sub-second unit, since the wake-up comes
      at a fixed point of a unit.  The asynchronous prescaler is lowered to
      2, for units of about 60 us.
    - serial: received characters are reported as PM activity.
    - boards/gd32vw553k-start: new pm configuration, NSH with the power
      management (activity governor), the RTC and the PM procfs entries,
      and its documentation.
    
    Tested on the GD32VW553K-START: 78 to 93 percent of the idle time in
    deep-sleep, NSH answers the first key, "sleep 3" lasts 3.00 s, and the
    system time follows the IRC32K while the chip sleeps: 600 ppm off over
    2 minutes of standby.
    
    Assisted-by: Claude Code:claude-opus-5-5
    Signed-off-by: Jorge Guzman <[email protected]>
---
 .../gd32vw55x/boards/gd32vw553k-start/index.rst    |  32 +++
 arch/risc-v/Kconfig                                |   1 +
 arch/risc-v/src/gd32vw55x/Kconfig                  |  26 ++
 arch/risc-v/src/gd32vw55x/Make.defs                |   5 +
 arch/risc-v/src/gd32vw55x/gd32vw55x_idle.c         | 155 ++++++++++++
 arch/risc-v/src/gd32vw55x/gd32vw55x_pm.c           | 269 +++++++++++++++++++++
 arch/risc-v/src/gd32vw55x/gd32vw55x_pm.h           |  88 +++++++
 arch/risc-v/src/gd32vw55x/gd32vw55x_rtc.c          | 195 ++++++++++++++-
 arch/risc-v/src/gd32vw55x/gd32vw55x_serial.c       |  13 +
 .../gd32vw553k-start/configs/pm/defconfig          |  58 +++++
 10 files changed, 838 insertions(+), 4 deletions(-)

diff --git 
a/Documentation/platforms/risc-v/gd32vw55x/boards/gd32vw553k-start/index.rst 
b/Documentation/platforms/risc-v/gd32vw55x/boards/gd32vw553k-start/index.rst
index 8d8a4397eeb..8208ea29cf2 100644
--- a/Documentation/platforms/risc-v/gd32vw55x/boards/gd32vw553k-start/index.rst
+++ b/Documentation/platforms/risc-v/gd32vw55x/boards/gd32vw553k-start/index.rst
@@ -424,6 +424,38 @@ the next boot format it again::
    Erase only the progmem region.  The NVDS lives immediately above it, at
    0x083fb000, and holds the BLE bonding data.
 
+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 GD32VW55x: the core and
+bus clocks stop, the SRAM and the registers keep their contents.  The RTC
+wake-up timer ends the deep-sleep at the next system timer event, at most
+``CONFIG_GD32VW55X_PM_WAKEUP_MS`` (1 s) later, and so does any EXTI
+interrupt.  On the way out the PLL is set up again and the system timer is
+advanced by the time spent asleep, measured on the RTC calendar.  The
+system time then follows the IRC32K while the board sleeps (within about
+0.1 percent here, the board has no 32.768 kHz crystal), and the timers keep
+their time.  A character received while the board sleeps is lost: press
+Enter until NSH answers, then the console activity keeps the board awake.
+The radios are not supported in deep-sleep.
+
+::
+
+    nsh> cat /proc/pm/state0
+    DOMAIN0                   WAKE           SLEEP          TOTAL
+    normal                   18s  10%        9s   5%       27s  15%
+    idle                      9s   5%        0s   0%        9s   5%
+    standby                 134s  78%        0s   0%      134s  78%
+    sleep                     0s   0%        0s   0%        0s   0%
+
+.. note::
+   The debug module does not answer while the core is in deep-sleep, so
+   OpenOCD fails to examine a sleeping board.  Keep the board awake by
+   pressing Enter on the console (or by sending characters in a loop) while
+   it is programmed, or press RESET right before.
+
 sta_softap
 ----------
 
diff --git a/arch/risc-v/Kconfig b/arch/risc-v/Kconfig
index 07d04614435..a511292d7f9 100644
--- a/arch/risc-v/Kconfig
+++ b/arch/risc-v/Kconfig
@@ -65,6 +65,7 @@ config ARCH_CHIP_GD32VW55X
        select ONESHOT_COUNT
        select ONESHOT_FAST_DIVISION
        select ALARM_ARCH
+       select ARCH_IDLE_CUSTOM
        ---help---
                GigaDevice GD32VW55x Wi-Fi/BLE SoC (Nuclei N307 rv32imafc,
                ECLIC interrupt controller, 160 MHz, 320 KB SRAM).
diff --git a/arch/risc-v/src/gd32vw55x/Kconfig 
b/arch/risc-v/src/gd32vw55x/Kconfig
index dc6e55aa3d6..59f18ac4f02 100644
--- a/arch/risc-v/src/gd32vw55x/Kconfig
+++ b/arch/risc-v/src/gd32vw55x/Kconfig
@@ -356,6 +356,32 @@ config GD32VW55X_PROGMEM_SIZE
 
 endmenu # Program memory Configuration
 
+menu "Power Management Configuration"
+       depends on PM
+
+config GD32VW55X_PM_WAKEUP_MS
+       int "Longest deep-sleep (ms)"
+       default 1000
+       range 2 29000
+       depends on RTC
+       ---help---
+               In the PM_STANDBY state the chip is in deep-sleep, where the
+               system timer stops.  The RTC wake-up timer ends the deep-sleep
+               at the next timer event, or after this period if it comes
+               later, and the time spent asleep is added to the system timer.
+               Besides, any EXTI interrupt wakes the chip up.  Without the RTC
+               the core only waits for an interrupt.  The radios are not
+               supported in deep-sleep.
+
+config GD32VW55X_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
+
 menuconfig GD32VW55X_WIFI
        bool "Wi-Fi 802.11ax support"
        default n
diff --git a/arch/risc-v/src/gd32vw55x/Make.defs 
b/arch/risc-v/src/gd32vw55x/Make.defs
index e8b7f5849f7..e9759b90a95 100644
--- a/arch/risc-v/src/gd32vw55x/Make.defs
+++ b/arch/risc-v/src/gd32vw55x/Make.defs
@@ -33,6 +33,11 @@ CHIP_CSRCS  = gd32vw55x_allocateheap.c 
gd32vw55x_clockconfig.c
 CHIP_CSRCS += gd32vw55x_irq.c gd32vw55x_irq_dispatch.c
 CHIP_CSRCS += gd32vw55x_lowputc.c gd32vw55x_serial.c
 CHIP_CSRCS += gd32vw55x_start.c gd32vw55x_timerisr.c
+CHIP_CSRCS += gd32vw55x_idle.c
+
+ifeq ($(CONFIG_PM),y)
+CHIP_CSRCS += gd32vw55x_pm.c
+endif
 
 ifeq ($(CONFIG_RTC),y)
 CHIP_CSRCS += gd32vw55x_rtc.c
diff --git a/arch/risc-v/src/gd32vw55x/gd32vw55x_idle.c 
b/arch/risc-v/src/gd32vw55x/gd32vw55x_idle.c
new file mode 100644
index 00000000000..ccc95a0cedf
--- /dev/null
+++ b/arch/risc-v/src/gd32vw55x/gd32vw55x_idle.c
@@ -0,0 +1,155 @@
+/****************************************************************************
+ * arch/risc-v/src/gd32vw55x/gd32vw55x_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 <nuttx/arch.h>
+#include <nuttx/board.h>
+#include <nuttx/irq.h>
+#include <nuttx/power/pm.h>
+#include <arch/board/board.h>
+
+#include "riscv_internal.h"
+#include "gd32vw55x_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 GD32VW55x.  Return false when the core still has
+ *   to wait for an interrupt.
+ *
+ ****************************************************************************/
+
+#ifdef CONFIG_PM
+static bool up_idlepm(void)
+{
+  static enum pm_state_e oldstate = PM_NORMAL;
+  enum pm_state_e newstate;
+  irqstate_t flags;
+  bool slept = false;
+  int ret;
+
+  /* Decide which power saving level can be obtained */
+
+  newstate = pm_checkstate(PM_IDLE_DOMAIN);
+
+  if (newstate == oldstate)
+    {
+      return false;
+    }
+
+  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 the next timer event or an interrupt, then tell
+           * the drivers that the system is running again.
+           */
+
+          gd32vw55x_pmstop();
+          slept = true;
+
+          if (pm_changestate(PM_IDLE_DOMAIN, PM_NORMAL) >= 0)
+            {
+              oldstate = PM_NORMAL;
+            }
+        }
+    }
+
+  leave_critical_section(flags);
+  return slept;
+}
+#else
+#  define up_idlepm() false
+#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 */
+
+  if (up_idlepm())
+    {
+      return;
+    }
+
+#ifdef CONFIG_ARCH_LEDS_CPU_ACTIVITY
+  board_autoled_off(LED_CPU);
+#endif
+
+  /* Sleep until an interrupt occurs.  With frame pointers, clobbering "ra"
+   * makes the compiler keep it in the frame chain that sched_backtrace()
+   * follows, as in the common riscv_idle.c.
+   */
+
+#if defined(CONFIG_FRAME_POINTER) && defined(CONFIG_SCHED_BACKTRACE)
+  asm volatile ("wfi" : : : "ra");
+#else
+  asm volatile ("wfi");
+#endif
+
+#endif
+}
diff --git a/arch/risc-v/src/gd32vw55x/gd32vw55x_pm.c 
b/arch/risc-v/src/gd32vw55x/gd32vw55x_pm.c
new file mode 100644
index 00000000000..8ec71f2c96f
--- /dev/null
+++ b/arch/risc-v/src/gd32vw55x/gd32vw55x_pm.c
@@ -0,0 +1,269 @@
+/****************************************************************************
+ * arch/risc-v/src/gd32vw55x/gd32vw55x_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 <sys/param.h>
+
+#include <nuttx/arch.h>
+#include <nuttx/clock.h>
+#include <nuttx/power/pm.h>
+
+#include "riscv_internal.h"
+#include "riscv_mtimer.h"
+#include "chip.h"
+#include "gd32vw55x_clockconfig.h"
+#include "gd32vw55x_pm.h"
+#include "hardware/gd32vw55x_rcu.h"
+#include "hardware/gd32vw55x_uart.h"
+
+#ifdef CONFIG_PM
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* PMU_CTL0: LDO and low-driver modes during deep-sleep */
+
+#define GD32VW55X_PMU_CTL0      (GD32VW55X_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 */
+
+/* Nuclei SLEEPVALUE CSR: WFI enters the deep-sleep mode when set */
+
+#define CSR_SLEEPVALUE          0x811
+#define SLEEPVALUE_SHALLOW      0
+#define SLEEPVALUE_DEEP         1
+
+/* DBG_CTL0: keep the debugger connection in deep-sleep, so that a sleeping
+ * board can still be programmed.
+ */
+
+#define GD32VW55X_DBG_CTL0      0xe0044004
+#define DBG_CTL0_DSLP_HOLD      (1 << 1)
+
+/* System timer (mtime), stopped in deep-sleep */
+
+#define GD32VW55X_PM_MTIME      (GD32VW55X_SYSTIMER_BASE + 0x0000)
+#define GD32VW55X_PM_MTIMECMP   (GD32VW55X_SYSTIMER_BASE + 0x0008)
+
+/* Shorter waits are not worth the deep-sleep: the wake-up timer counts
+ * 0.5 ms periods, and the PLL takes time to lock again.
+ */
+
+#define GD32VW55X_PM_MIN_SLEEP  2000  /* us */
+
+/* The console UART, emptied before deep-sleep stops its clock */
+
+#if defined(CONFIG_USART0_SERIAL_CONSOLE)
+#  define CONSOLE_BASE          GD32VW55X_USART0_BASE
+#elif defined(CONFIG_UART1_SERIAL_CONSOLE)
+#  define CONSOLE_BASE          GD32VW55X_UART1_BASE
+#elif defined(CONFIG_UART2_SERIAL_CONSOLE)
+#  define CONSOLE_BASE          GD32VW55X_UART2_BASE
+#endif
+
+#define CONSOLE_FLUSH_TIMEOUT   1000000
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: gd32vw55x_pmwait
+ *
+ * Description:
+ *   Wait for an interrupt, in the sleep or the deep-sleep mode.  With the
+ *   interrupts disabled, a pending interrupt still ends WFI; it is taken
+ *   once they are enabled again.
+ *
+ ****************************************************************************/
+
+static void gd32vw55x_pmwait(bool deep)
+{
+  if (deep)
+    {
+      WRITE_CSR(CSR_SLEEPVALUE, SLEEPVALUE_DEEP);
+    }
+
+  asm volatile ("wfi");
+  WRITE_CSR(CSR_SLEEPVALUE, SLEEPVALUE_SHALLOW);
+}
+
+#ifdef CONFIG_RTC
+/****************************************************************************
+ * Name: gd32vw55x_mtime_advance
+ *
+ * Description:
+ *   Add 'cycles' to the running system timer.  The low word is cleared
+ *   first, so that the counter never goes past the new value and fires an
+ *   alarm early.
+ *
+ ****************************************************************************/
+
+static void gd32vw55x_mtime_advance(uint64_t cycles)
+{
+  uint64_t value = riscv_mtimer_get(GD32VW55X_PM_MTIME) + cycles;
+
+  putreg32(0, GD32VW55X_PM_MTIME);
+  putreg32(value >> 32, GD32VW55X_PM_MTIME + 4);
+  putreg32(value, GD32VW55X_PM_MTIME);
+}
+#endif
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: riscv_pminitialize
+ *
+ * Description:
+ *   Initialize the power management, early in up_initialize().
+ *
+ ****************************************************************************/
+
+void riscv_pminitialize(void)
+{
+  modifyreg32(GD32VW55X_RCU_APB1EN, 0, RCU_APB1EN_PMUEN);
+  modifyreg32(GD32VW55X_DBG_CTL0, 0, DBG_CTL0_DSLP_HOLD);
+  pm_initialize();
+}
+
+/****************************************************************************
+ * Name: gd32vw55x_pmstop
+ *
+ * Description:
+ *   Enter the deep-sleep mode until the next timer event: the core and bus
+ *   clocks stop, SRAM and registers keep their contents.  The RTC wake-up
+ *   timer ends it at the next system timer alarm, at most
+ *   CONFIG_GD32VW55X_PM_WAKEUP_MS milliseconds later, and so does any EXTI
+ *   interrupt.  On return the clock tree is set up again and the system
+ *   timer is advanced by the time spent asleep.  Without the RTC, or when
+ *   the next alarm is too close, the core only waits for an interrupt.
+ *
+ *   Called with the interrupts disabled.
+ *
+ ****************************************************************************/
+
+void gd32vw55x_pmstop(void)
+{
+#ifdef CONFIG_RTC
+  uint64_t now = riscv_mtimer_get(GD32VW55X_PM_MTIME);
+  uint64_t alarm = riscv_mtimer_get(GD32VW55X_PM_MTIMECMP);
+  uint64_t elapsed;
+  uint64_t awake;
+  uint64_t usec;
+  uint32_t before;
+  uint32_t after;
+  uint32_t hz;
+#ifdef CONSOLE_BASE
+  int timeout;
+#endif
+
+  /* Sleep until the next alarm of the system timer */
+
+  usec = alarm > now ? (alarm - now) / (GD32VW55X_MTIME_FREQ / USEC_PER_SEC)
+                     : 0;
+  usec = MIN(usec, (uint64_t)CONFIG_GD32VW55X_PM_WAKEUP_MS * USEC_PER_MSEC);
+
+  if (usec < GD32VW55X_PM_MIN_SLEEP)
+    {
+      gd32vw55x_pmwait(false);
+      return;
+    }
+
+#ifdef CONSOLE_BASE
+  /* Let the console finish the character on the wire */
+
+  for (timeout = CONSOLE_FLUSH_TIMEOUT; timeout > 0; timeout--)
+    {
+      if ((getreg32(CONSOLE_BASE + GD32VW55X_UART_STAT_OFFSET) &
+           UART_STAT_TC) != 0)
+        {
+          break;
+        }
+    }
+#endif
+
+  /* Take the system timer and the calendar at the same instant, when a
+   * sub-second unit starts: the wake-up comes at a fixed point of a unit,
+   * so reading the calendar anywhere in a unit would leave a bias of up to
+   * one unit for every sleep.
+   */
+
+  before = gd32vw55x_rtc_timeofday(&hz);
+  now    = riscv_mtimer_get(GD32VW55X_PM_MTIME);
+
+  if (gd32vw55x_rtc_setwakeup(usec) < 0)
+    {
+      gd32vw55x_pmwait(false);
+      return;
+    }
+
+  /* LDO in low power mode and low-driver mode during deep-sleep */
+
+  modifyreg32(GD32VW55X_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);
+
+  gd32vw55x_pmwait(true);
+
+  /* The chip wakes up on the IRC16M: bring the PLL back */
+
+  gd32vw55x_clockconfig();
+  gd32vw55x_rtc_setwakeup(0);
+
+  /* The system timer was stopped: add the time spent asleep, measured on
+   * the calendar, less the time it counted.
+   */
+
+  after = gd32vw55x_rtc_timeofday(&hz);
+  if (after < before)
+    {
+      after += SEC_PER_DAY * hz;
+    }
+
+  elapsed = ((uint64_t)(after - before) * GD32VW55X_MTIME_FREQ) / hz;
+  awake   = riscv_mtimer_get(GD32VW55X_PM_MTIME) - now;
+
+  if (elapsed > awake)
+    {
+      gd32vw55x_mtime_advance(elapsed - awake);
+    }
+#else
+  /* Nothing would wake the chip up at the next alarm */
+
+  gd32vw55x_pmwait(false);
+#endif
+}
+
+#endif /* CONFIG_PM */
diff --git a/arch/risc-v/src/gd32vw55x/gd32vw55x_pm.h 
b/arch/risc-v/src/gd32vw55x/gd32vw55x_pm.h
new file mode 100644
index 00000000000..82bbbc67c0d
--- /dev/null
+++ b/arch/risc-v/src/gd32vw55x/gd32vw55x_pm.h
@@ -0,0 +1,88 @@
+/****************************************************************************
+ * arch/risc-v/src/gd32vw55x/gd32vw55x_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_RISCV_SRC_GD32VW55X_GD32VW55X_PM_H
+#define __ARCH_RISCV_SRC_GD32VW55X_GD32VW55X_PM_H
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <stdint.h>
+
+/****************************************************************************
+ * Public Function Prototypes
+ ****************************************************************************/
+
+#ifdef CONFIG_PM
+
+/****************************************************************************
+ * Name: gd32vw55x_pmstop
+ *
+ * Description:
+ *   Enter the deep-sleep mode until the next timer event: the core and bus
+ *   clocks stop, SRAM and registers keep their contents.  The RTC wake-up
+ *   timer ends it at the next system timer alarm, at most
+ *   CONFIG_GD32VW55X_PM_WAKEUP_MS milliseconds later, and so does any EXTI
+ *   interrupt.  On return the clock tree is set up again and the system
+ *   timer is advanced by the time spent asleep.  Without the RTC, or when
+ *   the next alarm is too close, the core only waits for an interrupt.
+ *
+ *   Called with the interrupts disabled.
+ *
+ ****************************************************************************/
+
+void gd32vw55x_pmstop(void);
+
+#endif /* CONFIG_PM */
+
+#ifdef CONFIG_RTC
+
+/****************************************************************************
+ * Name: gd32vw55x_rtc_setwakeup
+ *
+ * Description:
+ *   Start the RTC wake-up timer to raise its interrupt (EXTI line 21) in
+ *   'usec' microseconds, at most about 30 s, or stop it if 'usec' is 0.
+ *
+ ****************************************************************************/
+
+int gd32vw55x_rtc_setwakeup(uint32_t usec);
+
+/****************************************************************************
+ * Name: gd32vw55x_rtc_timeofday
+ *
+ * Description:
+ *   Wait for the start of the next sub-second unit of the calendar, and
+ *   return the time of day in these units, and their frequency in 'hz'.
+ *   The time is right after deep-sleep, without waiting for the shadow
+ *   registers.
+ *
+ ****************************************************************************/
+
+uint32_t gd32vw55x_rtc_timeofday(uint32_t *hz);
+
+#endif /* CONFIG_RTC */
+
+#endif /* __ARCH_RISCV_SRC_GD32VW55X_GD32VW55X_PM_H */
diff --git a/arch/risc-v/src/gd32vw55x/gd32vw55x_rtc.c 
b/arch/risc-v/src/gd32vw55x/gd32vw55x_rtc.c
index b87339a66ce..035497f8bb1 100644
--- a/arch/risc-v/src/gd32vw55x/gd32vw55x_rtc.c
+++ b/arch/risc-v/src/gd32vw55x/gd32vw55x_rtc.c
@@ -34,9 +34,13 @@
 #include <debug.h>
 
 #include <nuttx/arch.h>
+#include <nuttx/clock.h>
+#include <nuttx/irq.h>
 
 #include "riscv_internal.h"
 #include "gd32vw55x_clockconfig.h"
+#include "gd32vw55x_pm.h"
+#include "hardware/gd32vw55x_exti.h"
 #include "hardware/gd32vw55x_rcu.h"
 #include "chip.h"
 
@@ -53,14 +57,25 @@
 #define GD32VW55X_RTC_CTL       (GD32VW55X_RTC_BASE + 0x0008)
 #define GD32VW55X_RTC_STAT      (GD32VW55X_RTC_BASE + 0x000c)
 #define GD32VW55X_RTC_PSC       (GD32VW55X_RTC_BASE + 0x0010)
+#define GD32VW55X_RTC_WUT       (GD32VW55X_RTC_BASE + 0x0014)
 #define GD32VW55X_RTC_WPK       (GD32VW55X_RTC_BASE + 0x0024)
 #define GD32VW55X_RTC_SS        (GD32VW55X_RTC_BASE + 0x0028)
 
+/* 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_BPSHAD          (1 << 5)   /* Bypass the shadow registers */
+#define RTC_CTL_WTEN            (1 << 10)  /* Wake-up timer enable */
+#define RTC_CTL_WTIE            (1 << 14)  /* Wake-up timer interrupt */
+
 /* STAT bits */
 
+#define RTC_STAT_WTWF           (1 << 2)   /* Wake-up timer writable */
 #define RTC_STAT_RSYNF          (1 << 5)   /* Registers synchronized */
 #define RTC_STAT_INITF          (1 << 6)   /* In initialization state */
 #define RTC_STAT_INITM          (1 << 7)   /* Enter initialization mode */
+#define RTC_STAT_WTF            (1 << 10)  /* Wake-up timer flag */
 
 /* Write-protection key sequence */
 
@@ -68,14 +83,16 @@
 #define RTC_UNLOCK_KEY2         0x53
 #define RTC_LOCK_KEY            0xff
 
-/* IRC32K (32 kHz): async 32, sync 1000 -> 1 Hz calendar clock.
- * Matches the vendor SDK rtc_pre_config().
+/* IRC32K (32 kHz): async 2, sync 16000 -> 1 Hz calendar clock.  The
+ * small asynchronous prescaler gives sub-second units of about 60 us, which
+ * measure the time spent in deep-sleep.
  */
 
-#define RTC_PSC_FACTOR_A        0x1f
-#define RTC_PSC_FACTOR_S        0x3e7
+#define RTC_PSC_FACTOR_A        0x01
+#define RTC_PSC_FACTOR_S        0x3e7f
 #define RTC_PSC_FACTOR_A_SHIFT  16
 #define RTC_PSC_FACTOR_A_MASK   0x7f
+#define RTC_PSC_FACTOR_S_MASK   0x7fff
 #define RTC_SS_MASK             0xffff
 
 /* The IRC32K is off by a few percent.  Its frequency is measured against
@@ -116,6 +133,14 @@
 #define GD32VW55X_PMU_CTL0      (GD32VW55X_PMU_BASE + 0x0000)
 #define PMU_CTL0_BKPWEN         (1 << 8)
 
+/* The wake-up timer counts the RTC clock divided by 16, with a 16-bit
+ * reload value.  Its interrupt goes through EXTI line 21.
+ */
+
+#define RTC_WUT_DIV             16
+#define RTC_WUT_MAX             0xffff
+#define RTC_WKUP_EXTI_LINE      (1 << 21)
+
 /* BCD helpers */
 
 #define BCD2DEC(x)              ((((x) >> 4) * 10) + ((x) & 0x0f))
@@ -264,6 +289,23 @@ static uint32_t rtc_sync_prescaler(void)
   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)
+{
+  modifyreg32(GD32VW55X_RTC_STAT, RTC_STAT_WTF, 0);
+  putreg32(RTC_WKUP_EXTI_LINE, GD32VW55X_EXTI_PD);
+  return OK;
+}
+#endif
+
 /****************************************************************************
  * Public Functions
  ****************************************************************************/
@@ -448,4 +490,149 @@ int up_rtc_settime(const struct timespec *tp)
   return rtc_synchwait();
 }
 
+/****************************************************************************
+ * Name: gd32vw55x_rtc_setwakeup
+ *
+ * Description:
+ *   Start the RTC wake-up timer to raise its interrupt (EXTI line 21) in
+ *   'usec' microseconds, at most about 30 s, or stop it if 'usec' is 0.
+ *
+ ****************************************************************************/
+
+int gd32vw55x_rtc_setwakeup(uint32_t usec)
+{
+  uint32_t psc;
+  uint32_t hz;
+  uint64_t ticks;
+  int timeout;
+  int ret = OK;
+
+#ifdef CONFIG_PM
+  static bool attached;
+
+  if (!attached)
+    {
+      irq_attach(GD32VW55X_IRQ_RTC_WKUP, rtc_wakeup_interrupt, NULL);
+      up_enable_irq(GD32VW55X_IRQ_RTC_WKUP);
+      attached = true;
+    }
+#endif
+
+  rtc_unlock();
+
+  /* Stop the timer, then wait until its reload value can be written */
+
+  modifyreg32(GD32VW55X_RTC_CTL, RTC_CTL_WTEN | RTC_CTL_WTIE, 0);
+  modifyreg32(GD32VW55X_RTC_STAT, RTC_STAT_WTF, 0);
+  modifyreg32(GD32VW55X_EXTI_INTEN, RTC_WKUP_EXTI_LINE, 0);
+  putreg32(RTC_WKUP_EXTI_LINE, GD32VW55X_EXTI_PD);
+
+  if (usec > 0)
+    {
+      for (timeout = 1000000; timeout > 0; timeout--)
+        {
+          if ((getreg32(GD32VW55X_RTC_STAT) & RTC_STAT_WTWF) != 0)
+            {
+              break;
+            }
+        }
+
+      if (timeout <= 0)
+        {
+          ret = -ETIMEDOUT;
+        }
+      else
+        {
+          /* The RTC clock is the calendar clock times the prescalers, the
+           * timer counts the reload value plus one clock down to 0.
+           */
+
+          psc   = getreg32(GD32VW55X_RTC_PSC);
+          hz    = (((psc >> RTC_PSC_FACTOR_A_SHIFT) &
+                    RTC_PSC_FACTOR_A_MASK) + 1) *
+                  ((psc & RTC_PSC_FACTOR_S_MASK) + 1);
+          ticks = ((uint64_t)usec * (hz / RTC_WUT_DIV)) / USEC_PER_SEC;
+          ticks = MAX(MIN(ticks, RTC_WUT_MAX), 1);
+
+          putreg32((uint32_t)ticks - 1, GD32VW55X_RTC_WUT);
+          modifyreg32(GD32VW55X_RTC_CTL, RTC_CTL_WTCS_MASK,
+                      RTC_CTL_WTCS_DIV16);
+
+          modifyreg32(GD32VW55X_EXTI_RTEN, 0, RTC_WKUP_EXTI_LINE);
+          modifyreg32(GD32VW55X_EXTI_INTEN, 0, RTC_WKUP_EXTI_LINE);
+          modifyreg32(GD32VW55X_RTC_CTL, 0, RTC_CTL_WTIE | RTC_CTL_WTEN);
+        }
+    }
+
+  rtc_lock();
+  return ret;
+}
+
+/****************************************************************************
+ * Name: gd32vw55x_rtc_timeofday
+ *
+ * Description:
+ *   Wait for the start of the next sub-second unit of the calendar, and
+ *   return the time of day in these units, and their frequency in 'hz'.
+ *   The time is right after deep-sleep, without waiting for the shadow
+ *   registers.
+ *
+ ****************************************************************************/
+
+uint32_t gd32vw55x_rtc_timeofday(uint32_t *hz)
+{
+  uint32_t sync = getreg32(GD32VW55X_RTC_PSC) & RTC_PSC_FACTOR_S_MASK;
+  uint32_t start = getreg32(GD32VW55X_RTC_MTIME);
+  uint32_t prevss;
+  uint32_t prevtime;
+  uint32_t ss;
+  uint32_t time;
+  int i;
+
+  /* Read the calendar counters, not the shadow registers: these are only
+   * updated every two RTC clocks, and not in deep-sleep.
+   */
+
+  rtc_unlock();
+  modifyreg32(GD32VW55X_RTC_CTL, 0, RTC_CTL_BPSHAD);
+
+  /* Wait for the next sub-second unit */
+
+  prevss = getreg32(GD32VW55X_RTC_SS) & RTC_SS_MASK;
+  while ((getreg32(GD32VW55X_RTC_SS) & RTC_SS_MASK) == prevss &&
+         getreg32(GD32VW55X_RTC_MTIME) - start <= RTC_CALIB_TIMEOUT)
+    {
+    }
+
+  /* The counters may change between two reads: read them until the same
+   * values come twice.
+   */
+
+  ss   = getreg32(GD32VW55X_RTC_SS) & RTC_SS_MASK;
+  time = getreg32(GD32VW55X_RTC_TIME);
+
+  for (i = 0; i < 3; i++)
+    {
+      prevss   = ss;
+      prevtime = time;
+      ss       = getreg32(GD32VW55X_RTC_SS) & RTC_SS_MASK;
+      time     = getreg32(GD32VW55X_RTC_TIME);
+
+      if (ss == prevss && time == prevtime)
+        {
+          break;
+        }
+    }
+
+  modifyreg32(GD32VW55X_RTC_CTL, RTC_CTL_BPSHAD, 0);
+  rtc_lock();
+
+  ss   = MIN(ss, sync);
+  *hz  = sync + 1;
+
+  return (BCD2DEC((time >> 16) & 0x3f) * SEC_PER_HOUR +
+          BCD2DEC((time >> 8) & 0x7f) * SEC_PER_MIN +
+          BCD2DEC(time & 0x7f)) * (sync + 1) + (sync - ss);
+}
+
 #endif /* CONFIG_RTC */
diff --git a/arch/risc-v/src/gd32vw55x/gd32vw55x_serial.c 
b/arch/risc-v/src/gd32vw55x/gd32vw55x_serial.c
index 005afe59775..b94f1b4c48c 100644
--- a/arch/risc-v/src/gd32vw55x/gd32vw55x_serial.c
+++ b/arch/risc-v/src/gd32vw55x/gd32vw55x_serial.c
@@ -36,6 +36,7 @@
 
 #include <nuttx/irq.h>
 #include <nuttx/arch.h>
+#include <nuttx/power/pm.h>
 #include <nuttx/serial/serial.h>
 
 #include <arch/board/board.h>
@@ -577,6 +578,12 @@ static int gd32vw55x_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_GD32VW55X_PM_SERIAL_ACTIVITY);
+#endif
+
       uart_recvchars(dev);
     }
 
@@ -590,6 +597,9 @@ static int gd32vw55x_interrupt(int irq, void *context, void 
*arg)
 
       if (priv->rxenable && gd32vw55x_dma_rxavailable(dev))
         {
+#ifdef CONFIG_PM
+          pm_activity(PM_IDLE_DOMAIN, CONFIG_GD32VW55X_PM_SERIAL_ACTIVITY);
+#endif
           uart_recvchars(dev);
         }
     }
@@ -799,6 +809,9 @@ static void gd32vw55x_dma_rxcallback(DMA_HANDLE handle, 
uint16_t status,
 
   if (priv->rxenable && gd32vw55x_dma_rxavailable(dev))
     {
+#ifdef CONFIG_PM
+      pm_activity(PM_IDLE_DOMAIN, CONFIG_GD32VW55X_PM_SERIAL_ACTIVITY);
+#endif
       uart_recvchars(dev);
     }
 }
diff --git a/boards/risc-v/gd32vw55x/gd32vw553k-start/configs/pm/defconfig 
b/boards/risc-v/gd32vw55x/gd32vw553k-start/configs/pm/defconfig
new file mode 100644
index 00000000000..1842132e0b0
--- /dev/null
+++ b/boards/risc-v/gd32vw55x/gd32vw553k-start/configs/pm/defconfig
@@ -0,0 +1,58 @@
+#
+# 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="risc-v"
+CONFIG_ARCH_BOARD="gd32vw553k-start"
+CONFIG_ARCH_BOARD_GD32VW553K_START=y
+CONFIG_ARCH_CHIP="gd32vw55x"
+CONFIG_ARCH_CHIP_GD32VW553KM=y
+CONFIG_ARCH_CHIP_GD32VW55X=y
+CONFIG_ARCH_INTERRUPTSTACK=2048
+CONFIG_ARCH_RISCV=y
+CONFIG_ARCH_STACKDUMP=y
+CONFIG_BOARDCTL_RESET=y
+CONFIG_BOARD_LOOPSPERMSEC=16000
+CONFIG_BUILTIN=y
+CONFIG_DEBUG_FEATURES=y
+CONFIG_DEBUG_FULLOPT=y
+CONFIG_DEBUG_SYMBOLS=y
+CONFIG_EXAMPLES_HELLO=y
+CONFIG_FS_PROCFS=y
+CONFIG_FS_PROCFS_REGISTER=y
+CONFIG_GD32VW55X_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=64
+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=294400
+CONFIG_RAM_START=0x20000200
+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=13
+CONFIG_START_MONTH=7
+CONFIG_START_YEAR=2026
+CONFIG_SYSTEM_NSH=y
+CONFIG_TASK_NAME_SIZE=12
+CONFIG_UART2_RXBUFSIZE=128
+CONFIG_UART2_SERIAL_CONSOLE=y
+CONFIG_UART2_TXBUFSIZE=128

Reply via email to