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);
     }
 

Reply via email to