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 cc05cc1de7c735218b7b0e7f1aac289e00c38526 Author: Jorge Guzman <[email protected]> AuthorDate: Sun Oct 25 01:37:57 2026 -0300 arch/risc-v/gd32vw55x: calibrate the IRC32K of the RTC The RTC divided the IRC32K as if it ran at 32 kHz. On the GD32VW553K-START it runs at about 35.2 kHz: the calendar gained about 10 %, 17 s in 3 minutes. At every boot, measure the IRC32K against the system clock, which comes from the crystal: count the SysTimer ticks over about 100 ms of sub-second ticks of the RTC, and program the synchronous prescaler from it. The IRC32K drifts with the temperature, and the calendar keeps its value in the initialization mode. Reading the sub-second counter locks the TIME and DATE shadow registers until DATE is read, and leaving the initialization mode loads the calendar from the shadows, which hold their reset values after a system reset until the first synchronization. So the measure starts after a synchronization and releases the shadows when done, or the calendar could be reset at boot. Measured on the board: prescaler 1100 instead of 1000. Over 180 s and a reboot, the calendar is now within the 1 s resolution of the host; the date survives the reboots. Assisted-by: Claude Code:claude-opus-5-5 Signed-off-by: Jorge Guzman <[email protected]> --- arch/risc-v/src/gd32vw55x/gd32vw55x_rtc.c | 135 +++++++++++++++++++++++++++++- 1 file changed, 133 insertions(+), 2 deletions(-) diff --git a/arch/risc-v/src/gd32vw55x/gd32vw55x_rtc.c b/arch/risc-v/src/gd32vw55x/gd32vw55x_rtc.c index 1c6115002b9..b87339a66ce 100644 --- a/arch/risc-v/src/gd32vw55x/gd32vw55x_rtc.c +++ b/arch/risc-v/src/gd32vw55x/gd32vw55x_rtc.c @@ -26,14 +26,17 @@ #include <nuttx/config.h> +#include <inttypes.h> #include <stdint.h> #include <time.h> #include <errno.h> +#include <sys/param.h> #include <debug.h> #include <nuttx/arch.h> #include "riscv_internal.h" +#include "gd32vw55x_clockconfig.h" #include "hardware/gd32vw55x_rcu.h" #include "chip.h" @@ -51,6 +54,7 @@ #define GD32VW55X_RTC_STAT (GD32VW55X_RTC_BASE + 0x000c) #define GD32VW55X_RTC_PSC (GD32VW55X_RTC_BASE + 0x0010) #define GD32VW55X_RTC_WPK (GD32VW55X_RTC_BASE + 0x0024) +#define GD32VW55X_RTC_SS (GD32VW55X_RTC_BASE + 0x0028) /* STAT bits */ @@ -70,6 +74,25 @@ #define RTC_PSC_FACTOR_A 0x1f #define RTC_PSC_FACTOR_S 0x3e7 +#define RTC_PSC_FACTOR_A_SHIFT 16 +#define RTC_PSC_FACTOR_A_MASK 0x7f +#define RTC_SS_MASK 0xffff + +/* The IRC32K is off by a few percent. Its frequency is measured against + * the core clock, which comes from the crystal: the CPU cycles are counted + * over about 100 ms of sub-second ticks of the RTC (RTC_CALIB_CLOCKS RTC + * clocks), each tick bounded by RTC_CALIB_TIMEOUT cycles (20 ms). The + * result is used if it is within 25 percent of the nominal frequency (the + * datasheets give no tolerance). + */ + +#define RTC_IRC32K_FREQUENCY 32000 +#define RTC_CALIB_CLOCKS 3200 +#define RTC_CALIB_TIMEOUT (GD32VW55X_MTIME_FREQ / 50) + +/* Low word of the SysTimer counter, which runs from the system clock */ + +#define GD32VW55X_RTC_MTIME (GD32VW55X_SYSTIMER_BASE + 0x0000) /* RCU_RSTSCK register (IRC32K control) */ @@ -140,6 +163,107 @@ static int rtc_synchwait(void) return timeout > 0 ? OK : -ETIMEDOUT; } +/**************************************************************************** + * Name: rtc_ss_edge + * + * Description: + * Wait for the next change of the sub-second counter, at most + * RTC_CALIB_TIMEOUT core cycles. + * + ****************************************************************************/ + +static int rtc_ss_edge(void) +{ + uint32_t ss = getreg32(GD32VW55X_RTC_SS) & RTC_SS_MASK; + uint32_t start = getreg32(GD32VW55X_RTC_MTIME); + + while ((getreg32(GD32VW55X_RTC_SS) & RTC_SS_MASK) == ss) + { + if (getreg32(GD32VW55X_RTC_MTIME) - start > RTC_CALIB_TIMEOUT) + { + return -ETIMEDOUT; + } + } + + return OK; +} + +/**************************************************************************** + * Name: rtc_sync_prescaler + * + * Description: + * Measure the IRC32K against the core clock and return the synchronous + * prescaler that gives a 1 Hz calendar with RTC_PSC_FACTOR_A, or + * RTC_PSC_FACTOR_S if the measure fails. + * + ****************************************************************************/ + +static uint32_t rtc_sync_prescaler(void) +{ + uint32_t diva; + uint32_t ticks; + uint32_t start; + uint32_t cycles; + uint32_t freq; + uint32_t i; + + /* Read the sub-second counter only once the shadow registers hold the + * calendar: reading it locks the TIME and DATE shadows until DATE is + * read, and leaving the initialization mode loads the calendar from the + * shadows. After a system reset, they hold their reset values until the + * first synchronization. + */ + + if (rtc_synchwait() < 0) + { + return RTC_PSC_FACTOR_S; + } + + /* The RTC counts with the prescalers it has now */ + + diva = ((getreg32(GD32VW55X_RTC_PSC) >> RTC_PSC_FACTOR_A_SHIFT) & + RTC_PSC_FACTOR_A_MASK) + 1; + ticks = MAX(RTC_CALIB_CLOCKS / diva, 1); + + if (rtc_ss_edge() < 0) + { + getreg32(GD32VW55X_RTC_TIME); + getreg32(GD32VW55X_RTC_DATE); + return RTC_PSC_FACTOR_S; + } + + start = getreg32(GD32VW55X_RTC_MTIME); + + for (i = 0; i < ticks; i++) + { + if (rtc_ss_edge() < 0) + { + break; + } + } + + cycles = getreg32(GD32VW55X_RTC_MTIME) - start; + + /* Release the TIME and DATE shadows */ + + getreg32(GD32VW55X_RTC_TIME); + getreg32(GD32VW55X_RTC_DATE); + + freq = (uint32_t)(((uint64_t)ticks * diva * GD32VW55X_MTIME_FREQ) / + cycles); + + if (i < ticks || + freq < RTC_IRC32K_FREQUENCY - RTC_IRC32K_FREQUENCY / 4 || + freq > RTC_IRC32K_FREQUENCY + RTC_IRC32K_FREQUENCY / 4) + { + return RTC_PSC_FACTOR_S; + } + + rtcinfo("IRC32K: %" PRIu32 " Hz\n", freq); + + return (freq + (RTC_PSC_FACTOR_A + 1) / 2) / (RTC_PSC_FACTOR_A + 1) - 1; +} + /**************************************************************************** * Public Functions ****************************************************************************/ @@ -156,6 +280,7 @@ static int rtc_synchwait(void) int up_rtc_initialize(void) { + uint32_t sync; int timeout; /* PMU clock + backup-domain write enable (without this every write to @@ -195,7 +320,13 @@ int up_rtc_initialize(void) modifyreg32(GD32VW55X_RCU_BDCTL, 0, RCU_BDCTL_RTCEN); - /* Program the prescalers (init mode required) */ + /* Measure the IRC32K, then program the prescalers (init mode required; + * the calendar keeps its value). This is done at every boot, since the + * IRC32K drifts with the temperature. + */ + + sync = rtc_sync_prescaler(); + rtc_synchwait(); rtc_unlock(); modifyreg32(GD32VW55X_RTC_STAT, 0, RTC_STAT_INITM); @@ -210,7 +341,7 @@ int up_rtc_initialize(void) if (timeout > 0) { - putreg32((RTC_PSC_FACTOR_A << 16) | RTC_PSC_FACTOR_S, + putreg32((RTC_PSC_FACTOR_A << RTC_PSC_FACTOR_A_SHIFT) | sync, GD32VW55X_RTC_PSC); }
