Add DORMANT_CLOCK_SOURCE_RTC option on RP2040 (#3048)

* Add DORMANT_CLOCK_SOURCE_RTC option on RP2040

This runs RTC from XOSC, which allows going dormant without an external timer, at the expense of higher power consumption

* Switch DORMANT_CLOCK_SOURCE_DEFAULT on RP2040 to DORMANT_CLOCK_SOURCE_RTC

Also change power consumption figures and re-jig tests to match this
diff --git a/src/rp2_common/pico_low_power/include/pico/low_power.h b/src/rp2_common/pico_low_power/include/pico/low_power.h
index 3855a6b..18de824 100644
--- a/src/rp2_common/pico_low_power/include/pico/low_power.h
+++ b/src/rp2_common/pico_low_power/include/pico/low_power.h
@@ -60,13 +60,13 @@
  * Mode                  | Pico (VSYS)    | Pico 2 (VSYS)   | Pico (3V3)     | Pico 2 (3V3)
  * ----------------------|----------------|-----------------|----------------|----------------
  * Sleep                 | 7.3mA (37.9mW) | 5.9mA (30.7mW)  | 8.7mA (28.5mW) | 6.9mA (22.7mW)
- * Dormant               | 0.76mA (4.0mW) | 3.3mA (17.0mW)  | 0.75mA (2.5mW) | 3.7mA (12.0mW)
+ * Dormant               | 0.95mA (5.0mW) | 3.3mA (17.0mW)  | 1.2mA (4.0mW)  | 3.7mA (12.0mW)
  * Pstate (SRAM0 On)     | N/A            | 0.25mA (1.32mW) | N/A            | 0.14mA (0.47mW)
  * Pstate (XIP SRAM On)  | N/A            | 0.22mA (1.21mW) | N/A            | 0.10mA (0.44mW)
  * Pstate (All SRAM Off) | N/A            | 0.18mA (1.10mW) | N/A            | 0.08mA (0.40mW)
  * 
  * NOTE: The RP2350 dormant values are higher than the RP2040 ones because RP2350 continues running clk_ref from the LPOSC to run the timer,
- * whereas RP2040 requires an external clock input.
+ * whereas RP2040 only runs clk_rtc from the XOSC.
  */
 
 // PICO_CONFIG: PARAM_ASSERTIONS_ENABLED_PICO_LOW_POWER, Enable/disable assertions in the pico_low_power module, type=bool, default=0, group=pico_low_power
@@ -108,14 +108,16 @@
 typedef enum {
     DORMANT_CLOCK_SOURCE_XOSC,
     DORMANT_CLOCK_SOURCE_ROSC,
-#if !PICO_RP2040
+#if PICO_RP2040
+    DORMANT_CLOCK_SOURCE_RTC,
+#else
     DORMANT_CLOCK_SOURCE_LPOSC,
 #endif
     NUM_DORMANT_CLOCK_SOURCES
 } dormant_clock_source_t;
 
 #if PICO_RP2040
-#define DORMANT_CLOCK_SOURCE_DEFAULT DORMANT_CLOCK_SOURCE_XOSC
+#define DORMANT_CLOCK_SOURCE_DEFAULT DORMANT_CLOCK_SOURCE_RTC
 #else
 #define DORMANT_CLOCK_SOURCE_DEFAULT DORMANT_CLOCK_SOURCE_LPOSC
 #endif
@@ -222,13 +224,18 @@
  * The clocks specified in keep_enabled will be kept enabled during dormant, but XOSC and ROSC will be stopped.
  *
  * \if rp2040_specific
- * This requires an external clock source to be set using \ref low_power_set_external_clock_source before calling this function.
- * If the external clock source is not set, or it is not running, this will return PICO_ERROR_PRECONDITION_NOT_MET.
+ * If the clock source is set to DORMANT_CLOCK_SOURCE_RTC, all clocks will be switched to the ROSC while dormant so
+ * they can be stopped, except clk_rtc which will be run from the XOSC so that it continues running for the timer.
+ * In this case the XOSC will not be stopped.
+ *
+ * Otherwise, this requires an external clock source to be set using \ref low_power_set_external_clock_source before
+ * calling this function. If the external clock source is not set, or it is not running, this will return
+ * PICO_ERROR_PRECONDITION_NOT_MET.
  * \endif
  *
  * \if (!rp2040_specific || combined_docs)
- * If the clock source is set to DORMANT_CLOCK_SOURCE_LPOSC, clk_sys will be switched to the ROSC while dormant so
- * it can be stopped, while clk_ref will be run from the LPOSC so that it continues running for the timer.
+ * The clock source must be set to DORMANT_CLOCK_SOURCE_LPOSC, which means clk_sys will be switched to the ROSC while
+ * dormant so it can be stopped, while clk_ref will be run from the LPOSC so that it continues running for the timer.
  * \endif
  *
  * \param until The time to go dormant until.
@@ -243,6 +250,13 @@
  *
  * Go dormant until the given GPIO pin changes state.
  * The clocks specified in keep_enabled will be kept enabled during dormant, but XOSC and ROSC will be stopped.
+ * 
+ * \if rp2040_specific
+ * If the clock source is set to DORMANT_CLOCK_SOURCE_RTC, all clocks will be switched to the ROSC while dormant so
+ * they can be stopped, except clk_rtc which will be run from the XOSC. In this case the XOSC will not be stopped.
+ * For the lowest power consumption, you should use DORMANT_CLOCK_SOURCE_ROSC instead, as the GPIO interrupt does
+ * not require a clock.
+ * \endif
  *
  * \if (!rp2040_specific || combined_docs)
  * If the clock source is set to DORMANT_CLOCK_SOURCE_LPOSC, clk_sys will be run from the ROSC while dormant so
diff --git a/src/rp2_common/pico_low_power/low_power.c b/src/rp2_common/pico_low_power/low_power.c
index 2559e56..5b4d67e 100644
--- a/src/rp2_common/pico_low_power/low_power.c
+++ b/src/rp2_common/pico_low_power/low_power.c
@@ -424,7 +424,15 @@
             clk_sys_src = CLOCKS_CLK_SYS_CTRL_SRC_VALUE_CLK_REF;
             clk_sys_aux_src = 0;
             break;
-#if !PICO_RP2040
+#if PICO_RP2040
+        case DORMANT_CLOCK_SOURCE_RTC:
+            clk_ref_src_hz = rosc_measure_freq_khz() * KHZ;
+            clk_ref_src = CLOCKS_CLK_REF_CTRL_SRC_VALUE_ROSC_CLKSRC_PH;
+            clk_sys_src_hz = clk_ref_src_hz;
+            clk_sys_src = CLOCKS_CLK_SYS_CTRL_SRC_VALUE_CLK_REF;
+            clk_sys_aux_src = 0;
+            break;
+#else
         case DORMANT_CLOCK_SOURCE_LPOSC:
             clk_ref_src_hz = 32 * KHZ;
             clk_ref_src = CLOCKS_CLK_REF_CTRL_SRC_VALUE_LPOSC_CLKSRC;
@@ -475,6 +483,26 @@
     if (dormant_source == DORMANT_CLOCK_SOURCE_XOSC) {
         // Safe to disable rosc
         rosc_disable();
+#if PICO_RP2040
+    } else if (dormant_source == DORMANT_CLOCK_SOURCE_RTC) {
+        // Run RTC directly from XOSC
+    #if (XOSC_HZ % RTC_CLOCK_FREQ_HZ == 0)
+        // this doesn't pull in 64 bit arithmetic
+        clock_configure_int_divider(clk_rtc,
+                        0, // No GLMUX
+                        CLOCKS_CLK_RTC_CTRL_AUXSRC_VALUE_XOSC_CLKSRC,
+                        XOSC_HZ,
+                        XOSC_HZ / RTC_CLOCK_FREQ_HZ);
+
+    #else
+        clock_configure(clk_rtc,
+                        0, // No GLMUX
+                        CLOCKS_CLK_RTC_CTRL_AUXSRC_VALUE_XOSC_CLKSRC,
+                        XOSC_HZ,
+                        RTC_CLOCK_FREQ_HZ);
+
+    #endif
+#endif
     } else {
         // Safe to disable xosc
         xosc_disable();
@@ -496,7 +524,9 @@
 static void low_power_go_dormant(dormant_clock_source_t dormant_clock_source) {
     valid_params_if(PICO_LOW_POWER,
         dormant_clock_source == DORMANT_CLOCK_SOURCE_XOSC || dormant_clock_source == DORMANT_CLOCK_SOURCE_ROSC
-    #if !PICO_RP2040
+    #if PICO_RP2040
+        || dormant_clock_source == DORMANT_CLOCK_SOURCE_RTC
+    #else
         || dormant_clock_source == DORMANT_CLOCK_SOURCE_LPOSC
     #endif
     );
@@ -529,12 +559,14 @@
     replace_null_enable_values(keep_enabled, &local_keep_enabled);
 
 #if PICO_RP2040
-    if (dormant_rtc_src_hz == 0) {
-        return PICO_ERROR_PRECONDITION_NOT_MET;
-    }
-    // The RTC must be run from an external source, since the dormant source will be inactive
-    if (!rtc_run_from_external_source(dormant_rtc_src_hz, dormant_rtc_gpio_pin)) {
-        return PICO_ERROR_PRECONDITION_NOT_MET;
+    if (dormant_clock_source != DORMANT_CLOCK_SOURCE_RTC) {
+        if (dormant_rtc_src_hz == 0) {
+            return PICO_ERROR_PRECONDITION_NOT_MET;
+        }
+        // The RTC must be run from an external source, since the dormant source will be inactive
+        if (!rtc_run_from_external_source(dormant_rtc_src_hz, dormant_rtc_gpio_pin)) {
+            return PICO_ERROR_PRECONDITION_NOT_MET;
+        }
     }
     clock_dest_bitset_add(&local_keep_enabled, CLK_DEST_RTC_RTC);
 #elif PICO_RP2350
diff --git a/test/pico_low_power_test/low_power_test_simple.c b/test/pico_low_power_test/low_power_test_simple.c
index d830163..1769cbd 100644
--- a/test/pico_low_power_test/low_power_test_simple.c
+++ b/test/pico_low_power_test/low_power_test_simple.c
@@ -65,6 +65,16 @@
         } else {
             printf("ERROR: Woken up from Pstate\n");
         }
+#elif PICO_RP2040
+        printf("Going dormant from the XOSC for %dms\n", SLEEP_TIME_MS);
+        gpio_put(SLEEP_MONITOR_PIN, 0);
+        ret = low_power_dormant_for_ms(SLEEP_TIME_MS, DORMANT_CLOCK_SOURCE_XOSC, NULL);
+        gpio_put(SLEEP_MONITOR_PIN, 1);
+        if (ret != PICO_OK) {
+            printf("ERROR: low_power_dormant_for_ms returned %d\n", ret);
+        } else {
+            printf("Woken up\n");
+        }
 #endif
     }
 
diff --git a/test/pico_low_power_test/low_power_test_timers.c b/test/pico_low_power_test/low_power_test_timers.c
index b828043..c53504f 100644
--- a/test/pico_low_power_test/low_power_test_timers.c
+++ b/test/pico_low_power_test/low_power_test_timers.c
@@ -129,6 +129,7 @@
 
     absolute_time_t start_time;
     static absolute_time_t __persistent_data(wakeup_time);
+    absolute_time_t system_time_before;
     int64_t diff;
     int ret;
 
@@ -243,21 +244,16 @@
 
 
 
-    // dormant
+    // dormant using default clock source
     printf("Going DORMANT for %d seconds via AON TIMER\n", SLEEP_TIME_S);
 
     gpio_put(SLEEP_MONITOR_PIN, 0);
     start_time = aon_timer_get_absolute_time();
-    absolute_time_t system_time_before = get_absolute_time();
+    system_time_before = get_absolute_time();
     wakeup_time = delayed_by_ms(start_time, SLEEP_TIME_MS);
     ret = low_power_dormant_until_aon_timer(wakeup_time, DORMANT_CLOCK_SOURCE_DEFAULT, NULL);
     if (ret != PICO_OK) {
         printf("ERROR: %d returned by low_power_dormant_until_aon_timer\n", ret);
-    #if PICO_RP2040
-        if (ret == PICO_ERROR_PRECONDITION_NOT_MET) {
-            printf("ERROR: RTC clock source is not running - connect a device running external_sleep_timer to GPIO %d\n", RTC_GPIO_IN);
-        }
-    #endif
         EXIT_TEST;
     }
     gpio_put(SLEEP_MONITOR_PIN, 1);
@@ -390,6 +386,42 @@
         printf("ERROR: number of POWMAN reboots was %d not 2\n", powman_hw->scratch[3]);
         EXIT_TEST;
     }
+#else
+    // dormant using XOSC clock source
+    printf("Going DORMANT from the XOSC for %d seconds via AON TIMER\n", SLEEP_TIME_S);
+
+    gpio_put(SLEEP_MONITOR_PIN, 0);
+    start_time = aon_timer_get_absolute_time();
+    system_time_before = get_absolute_time();
+    wakeup_time = delayed_by_ms(start_time, SLEEP_TIME_MS);
+    ret = low_power_dormant_until_aon_timer(wakeup_time, DORMANT_CLOCK_SOURCE_XOSC, NULL);
+    if (ret != PICO_OK) {
+        printf("ERROR: %d returned by low_power_dormant_until_aon_timer\n", ret);
+    #if PICO_RP2040
+        if (ret == PICO_ERROR_PRECONDITION_NOT_MET) {
+            printf("ERROR: RTC clock source is not running - connect a device running external_sleep_timer to GPIO %d\n", RTC_GPIO_IN);
+        }
+    #endif
+        EXIT_TEST;
+    }
+    gpio_put(SLEEP_MONITOR_PIN, 1);
+    // check the system timer was stopped while dormant
+    diff = absolute_time_diff_us(system_time_before, get_absolute_time());
+    if (diff > 200 * 1000 // 200ms
+        #ifdef PICO_STDIO_USB_CONNECT_WAIT_TIMEOUT_MS
+        + (PICO_STDIO_USB_CONNECT_WAIT_TIMEOUT_MS * 1000)
+        #endif
+    ) {
+        printf("ERROR: doesn't seem like timer was stopped: diff %lldus\n", diff);
+        return - 1;
+    }
+    diff = absolute_time_diff_us(wakeup_time, aon_timer_get_absolute_time());
+    printf("Woken up now @%dus since target\n", (int)diff);
+    if (diff < 0) {
+        printf("WARNING: Woke up too soon - is this within the resolution of the aon timer?\n");
+    }
+    printf("Doing %d second pause to prove timer running\n", SLEEP_TIME_S);
+    busy_wait_ms(SLEEP_TIME_MS);
 #endif
 
     printf("PASSED\n");