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
