Implements the RTC timer for nRF51. Implementation is based
on existing nrf51_timer.
Event signaling through PPI peripheral is not implemented.
Tests added in microbit board.

Signed-off-by: Kaido Kert <kaidok...@gmail.com>
---
 hw/arm/nrf51_soc.c          |  13 ++
 hw/rtc/Kconfig              |   3 +
 hw/rtc/meson.build          |   1 +
 hw/rtc/nrf51_rtc.c          | 373 ++++++++++++++++++++++++++++++++++++
 hw/rtc/trace-events         |   4 +
 include/hw/arm/nrf51.h      |   1 +
 include/hw/arm/nrf51_soc.h  |   2 +
 include/hw/rtc/nrf51_rtc.h  |  68 +++++++
 tests/qtest/microbit-test.c | 249 ++++++++++++++++++++++++
 9 files changed, 714 insertions(+)
 create mode 100644 hw/rtc/nrf51_rtc.c
 create mode 100644 include/hw/rtc/nrf51_rtc.h

diff --git a/hw/arm/nrf51_soc.c b/hw/arm/nrf51_soc.c
index d8cc3214ed..f1e3aadf19 100644
--- a/hw/arm/nrf51_soc.c
+++ b/hw/arm/nrf51_soc.c
@@ -118,6 +118,17 @@ static void nrf51_soc_realize(DeviceState *dev_soc, Error 
**errp)
                        qdev_get_gpio_in(DEVICE(&s->armv7m),
                        BASE_TO_IRQ(NRF51_RNG_BASE)));
 
+    /* RTC */
+    if (!sysbus_realize(SYS_BUS_DEVICE(&s->rtc), errp)) {
+        return;
+    }
+    mr = sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->rtc), 0);
+    memory_region_add_subregion_overlap(&s->container, NRF51_RTC0_BASE, mr, 0);
+    sysbus_connect_irq(SYS_BUS_DEVICE(&s->rtc), 0,
+                       qdev_get_gpio_in(DEVICE(&s->armv7m),
+                       BASE_TO_IRQ(NRF51_RTC0_BASE)
+                       ));
+
     /* UICR, FICR, NVMC, FLASH */
     if (!object_property_set_uint(OBJECT(&s->nvm), "flash-size",
                                   s->flash_size, errp)) {
@@ -195,6 +206,8 @@ static void nrf51_soc_init(Object *obj)
 
     object_initialize_child(obj, "rng", &s->rng, TYPE_NRF51_RNG);
 
+    object_initialize_child(obj, "rtc", &s->rtc, TYPE_NRF51_RTC);
+
     object_initialize_child(obj, "nvm", &s->nvm, TYPE_NRF51_NVM);
 
     object_initialize_child(obj, "gpio", &s->gpio, TYPE_NRF51_GPIO);
diff --git a/hw/rtc/Kconfig b/hw/rtc/Kconfig
index 315b0e4ecc..18306c5db2 100644
--- a/hw/rtc/Kconfig
+++ b/hw/rtc/Kconfig
@@ -31,3 +31,6 @@ config RS5C372_RTC
     bool
     depends on I2C
     default y if I2C_DEVICES
+
+config NRF51_RTC
+    bool
diff --git a/hw/rtc/meson.build b/hw/rtc/meson.build
index 6c87864dc0..b959c9efe3 100644
--- a/hw/rtc/meson.build
+++ b/hw/rtc/meson.build
@@ -14,3 +14,4 @@ system_ss.add(when: 'CONFIG_LS7A_RTC', if_true: 
files('ls7a_rtc.c'))
 system_ss.add(when: 'CONFIG_ALLWINNER_H3', if_true: files('allwinner-rtc.c'))
 system_ss.add(when: 'CONFIG_MC146818RTC', if_true: files('mc146818rtc.c'))
 system_ss.add(when: 'CONFIG_RS5C372_RTC', if_true: files('rs5c372.c'))
+system_ss.add(when: 'CONFIG_NRF51_SOC', if_true: files('nrf51_rtc.c'))
diff --git a/hw/rtc/nrf51_rtc.c b/hw/rtc/nrf51_rtc.c
new file mode 100644
index 0000000000..5ffb269133
--- /dev/null
+++ b/hw/rtc/nrf51_rtc.c
@@ -0,0 +1,373 @@
+/*
+ * nRF51 SoC RTC emulation
+ *
+ * Copyright 2025 Kaido Kert <kaidok...@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/rtc/nrf51_rtc.h"
+#include "hw/irq.h"
+#include "hw/registerfields.h"
+#include "hw/sysbus.h"
+#include "migration/vmstate.h"
+#include "qemu/log.h"
+#include "qemu/timer.h"
+#include "qemu/units.h"
+#include "system/rtc.h"
+#include "system/system.h"
+#include "trace.h"
+
+#define NRF51_RTC_SIZE 0x1000
+#define NRF51_RTC_TICK_HZ 32768 /* 32.768 kHz LFCLK */
+#define NRF51_RTC_TICK_NS (NANOSECONDS_PER_SECOND / NRF51_RTC_TICK_HZ)
+
+/* Register field definitions */
+FIELD(NRF51_RTC_INTEN, TICK, 0, 1)
+FIELD(NRF51_RTC_INTEN, OVRFLW, 1, 1)
+FIELD(NRF51_RTC_INTEN, COMPARE0, 16, 1)
+FIELD(NRF51_RTC_INTEN, COMPARE1, 17, 1)
+FIELD(NRF51_RTC_INTEN, COMPARE2, 18, 1)
+FIELD(NRF51_RTC_INTEN, COMPARE3, 19, 1)
+
+/* 24-bit counter mask */
+#define BIT24_MASK 0xFFFFFF
+
+/* Map event address to s->events index */
+static int nrf51_rtc_event_index(hwaddr addr)
+{
+    switch (addr) {
+    case NRF51_RTC_EVENTS_TICK:
+        return 0;
+    case NRF51_RTC_EVENTS_OVRFLW:
+        return 1;
+    case NRF51_RTC_EVENTS_COMPARE0:
+        return 2;
+    case NRF51_RTC_EVENTS_COMPARE1:
+        return 3;
+    case NRF51_RTC_EVENTS_COMPARE2:
+        return 4;
+    case NRF51_RTC_EVENTS_COMPARE3:
+        return 5;
+    default:
+        return -1; /* Invalid */
+    }
+}
+
+static void nrf51_rtc_update_irq(NRF51RTCState *s)
+{
+    bool irq_pending = false;
+
+    irq_pending |= s->events[NRF51_RTC_EVENT_TICK] &&
+        FIELD_EX32(s->inten, NRF51_RTC_INTEN, TICK);
+    irq_pending |= s->events[NRF51_RTC_EVENT_OVRFLW] &&
+        FIELD_EX32(s->inten, NRF51_RTC_INTEN, OVRFLW);
+    for (int i = 0; i < 4; i++) {
+        irq_pending |= s->events[NRF51_RTC_EVENT_COMPARE + i]
+        && (s->inten & BIT(16 + i));
+    }
+
+    s->irq_pending = irq_pending;
+    qemu_set_irq(s->irq, s->irq_pending);
+}
+
+static void nrf51_rtc_tick(void *opaque)
+{
+    NRF51RTCState *s = opaque;
+    uint64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+    uint64_t elapsed_ns = now - s->last_update;
+    uint64_t tick_period_ns = NRF51_RTC_TICK_NS * (s->prescaler + 1);
+    uint32_t num_ticks = elapsed_ns / tick_period_ns;
+
+    /* Schedule next tick before any early returns */
+    timer_mod_ns(s->timer, now + tick_period_ns);
+
+    if (!s->powered || s->last_update == 0) {
+        s->last_update = now;
+        /*  Skip increment on first tick */
+        return;
+    }
+
+    for (uint32_t tick = 0; tick < num_ticks; tick++) {
+        s->tick_count = (s->tick_count + 1) & BIT24_MASK; /* 24-bit counter */
+        /*
+         * Check for tick event. Note: Events are always generated
+         * regardless of EVTEN register settings. EVTEN only controls
+         * PPI routing of events.
+         */
+        s->events[NRF51_RTC_EVENT_TICK] = 1;
+
+        /* Check for overflow (24-bit counter) */
+        if (s->tick_count == 0) {
+            s->events[NRF51_RTC_EVENT_OVRFLW] = 1;
+        }
+
+        /* Check compare registers */
+        for (int i = 0; i < 4; i++) {
+            if (s->tick_count == s->cc[i]) {
+                s->events[NRF51_RTC_EVENT_COMPARE + i] = 1;
+            }
+        }
+    }
+
+    s->last_update += num_ticks * tick_period_ns;
+    nrf51_rtc_update_irq(s);
+}
+
+static uint64_t nrf51_rtc_read(void *opaque, hwaddr addr, unsigned size)
+{
+    NRF51RTCState *s = opaque;
+    uint64_t value = 0;
+
+    if (!s->powered) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: read at 0x%" HWADDR_PRIx " when powered off\n",
+                      __func__,
+                      addr);
+        return 0;
+    }
+
+    switch (addr) {
+    case NRF51_RTC_EVENTS_TICK:
+    case NRF51_RTC_EVENTS_OVRFLW:
+    case NRF51_RTC_EVENTS_COMPARE0:
+    case NRF51_RTC_EVENTS_COMPARE1:
+    case NRF51_RTC_EVENTS_COMPARE2:
+    case NRF51_RTC_EVENTS_COMPARE3: {
+        int index = nrf51_rtc_event_index(addr);
+        if (index >= 0) {
+            value = s->events[index];
+        }
+        break;
+    }
+    case NRF51_RTC_INTENSET:
+    case NRF51_RTC_INTENCLR:
+        value = s->inten;
+        break;
+    case NRF51_RTC_EVTEN:
+    case NRF51_RTC_EVTENSET:
+    case NRF51_RTC_EVTENCLR:
+        value = s->evten;
+        break;
+    case NRF51_RTC_COUNTER:
+        value = s->tick_count & BIT24_MASK; /* 24-bit counter */
+        break;
+    case NRF51_RTC_PRESCALER:
+        value = s->prescaler & 0xFFF; /* 12-bit prescaler */
+        break;
+    case NRF51_RTC_CC0:
+    case NRF51_RTC_CC1:
+    case NRF51_RTC_CC2:
+    case NRF51_RTC_CC3:
+        value = s->cc[(addr - NRF51_RTC_CC0) / 4];
+        break;
+    case NRF51_RTC_POWER:
+        value = s->powered ? 1 : 0;
+        break;
+    default:
+        qemu_log_mask(LOG_UNIMP,
+                      "%s: unimplemented read at 0x%" HWADDR_PRIx "\n",
+                      __func__,
+                      addr);
+        break;
+    }
+
+    trace_nrf51_rtc_read(addr, value);
+    return value;
+}
+
+static void nrf51_rtc_write(void *opaque,
+                            hwaddr addr,
+                            uint64_t value,
+                            unsigned size) {
+    NRF51RTCState *s = opaque;
+
+    if (!s->powered && addr != NRF51_RTC_POWER) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: write at 0x%" HWADDR_PRIx " when powered off\n",
+                      __func__,
+                      addr);
+        return;
+    }
+
+    switch (addr) {
+    case NRF51_RTC_TASKS_START:
+        if (value == 1) {
+            s->running = true;
+            s->last_update = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+            timer_mod_ns(s->timer,
+                         s->last_update +
+                             NRF51_RTC_TICK_NS * (s->prescaler + 1));
+        }
+        break;
+    case NRF51_RTC_TASKS_STOP:
+        if (value == 1) {
+            s->running = false;
+            timer_del(s->timer);
+        }
+        break;
+    case NRF51_RTC_TASKS_CLEAR:
+        if (value == 1) {
+            s->tick_count = 0;
+            memset(s->events, 0, sizeof(s->events)); /* Clear all events */
+            s->last_update = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+            nrf51_rtc_update_irq(s);
+        }
+        break;
+    case NRF51_RTC_TASKS_TRIGOVRFLW:
+        if (value == 1) {
+            s->tick_count = 0xFFFFFE; /* Trigger overflow on next tick */
+        }
+        break;
+    case NRF51_RTC_EVENTS_TICK:
+    case NRF51_RTC_EVENTS_OVRFLW:
+    case NRF51_RTC_EVENTS_COMPARE0:
+    case NRF51_RTC_EVENTS_COMPARE1:
+    case NRF51_RTC_EVENTS_COMPARE2:
+    case NRF51_RTC_EVENTS_COMPARE3: {
+        int index = nrf51_rtc_event_index(addr);
+        if (index >= 0) {
+            s->events[index] = value & 1;
+            nrf51_rtc_update_irq(s);
+        }
+        break;
+    }
+    case NRF51_RTC_INTENSET:
+        s->inten |= value;
+        nrf51_rtc_update_irq(s);
+        break;
+    case NRF51_RTC_INTENCLR:
+        s->inten &= ~value;
+        nrf51_rtc_update_irq(s);
+        break;
+    case NRF51_RTC_EVTENSET:
+        s->evten |= value;
+        break;
+    case NRF51_RTC_EVTENCLR:
+        s->evten &= ~value;
+        break;
+    case NRF51_RTC_PRESCALER:
+        if (s->running) {
+            qemu_log_mask(LOG_GUEST_ERROR,
+                          "%s: PRESCALER write while RTC running\n",
+                          __func__);
+        } else {
+            s->prescaler = value & 0xFFF; /* 12-bit prescaler */
+        }
+        break;
+    case NRF51_RTC_CC0:
+    case NRF51_RTC_CC1:
+    case NRF51_RTC_CC2:
+    case NRF51_RTC_CC3: {
+        int index = (addr - NRF51_RTC_CC0) / 4;
+        s->cc[index] = value & BIT24_MASK;
+         /* Writing to a CC register clears its associated COMPARE event */
+        s->events[NRF51_RTC_EVENT_COMPARE + index] = 0;
+        nrf51_rtc_update_irq(s);
+        break;
+    }
+    case NRF51_RTC_POWER:
+        s->powered = (value & 1) != 0;
+        if (!s->powered) {
+            s->running = false;
+            timer_del(s->timer);
+            s->tick_count = 0;
+            s->last_update = 0;
+        }
+        break;
+    default:
+        qemu_log_mask(LOG_UNIMP,
+                      "%s: unimplemented write at 0x%" HWADDR_PRIx "\n",
+                      __func__,
+                      addr);
+        break;
+    }
+
+    trace_nrf51_rtc_write(addr, value);
+}
+
+static const MemoryRegionOps nrf51_rtc_ops = {
+    .read = nrf51_rtc_read,
+    .write = nrf51_rtc_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+    },
+};
+
+static void nrf51_rtc_reset(DeviceState *dev)
+{
+    NRF51RTCState *s = NRF51_RTC(dev);
+
+    timer_del(s->timer);
+    s->tick_count = 0;
+    s->prescaler = 0;
+    s->running = false;
+    s->powered = false; /* Reset to disabled per POWER register */
+    s->irq_pending = false;
+    s->inten = 0;
+    s->evten = 0;
+    s->last_update = 0;
+    memset(s->cc, 0, sizeof(s->cc));
+    memset(s->events, 0, sizeof(s->events));
+}
+
+static void nrf51_rtc_realize(DeviceState *dev, Error **errp)
+{
+    NRF51RTCState *s = NRF51_RTC(dev);
+    SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+
+    memory_region_init_io(&s->iomem,
+                          OBJECT(s),
+                          &nrf51_rtc_ops,
+                          s,
+                          TYPE_NRF51_RTC,
+                          NRF51_RTC_SIZE);
+    sysbus_init_mmio(sbd, &s->iomem);
+    sysbus_init_irq(sbd, &s->irq);
+
+    s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, nrf51_rtc_tick, s);
+}
+
+static const VMStateDescription nrf51_rtc_vmstate = {
+    .name = TYPE_NRF51_RTC,
+    .version_id = 1,
+    .fields = (const VMStateField[]){VMSTATE_UINT32(tick_count, NRF51RTCState),
+                                     VMSTATE_UINT64(last_update, 
NRF51RTCState),
+                                     VMSTATE_UINT32(prescaler, NRF51RTCState),
+                                     VMSTATE_BOOL(running, NRF51RTCState),
+                                     VMSTATE_BOOL(powered, NRF51RTCState),
+                                     VMSTATE_BOOL(irq_pending, NRF51RTCState),
+                                     VMSTATE_UINT32(inten, NRF51RTCState),
+                                     VMSTATE_UINT32(evten, NRF51RTCState),
+                                     VMSTATE_UINT32_ARRAY(cc, NRF51RTCState, 
4),
+                                     VMSTATE_UINT32_ARRAY(events,
+                                                          NRF51RTCState,
+                                                          6),
+                                     VMSTATE_END_OF_LIST()},
+};
+
+static void nrf51_rtc_class_init(ObjectClass *klass, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    dc->realize = nrf51_rtc_realize;
+    device_class_set_legacy_reset(dc, nrf51_rtc_reset);
+    dc->vmsd = &nrf51_rtc_vmstate;
+}
+
+static const TypeInfo nrf51_rtc_info = {
+    .name = TYPE_NRF51_RTC,
+    .parent = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(NRF51RTCState),
+    .class_init = nrf51_rtc_class_init,
+};
+
+static void nrf51_rtc_register_types(void)
+{
+    type_register_static(&nrf51_rtc_info);
+}
+
+type_init(nrf51_rtc_register_types)
diff --git a/hw/rtc/trace-events b/hw/rtc/trace-events
index b9f2852d35..b9dd52e6d0 100644
--- a/hw/rtc/trace-events
+++ b/hw/rtc/trace-events
@@ -36,6 +36,10 @@ m48txx_nvram_mem_write(uint32_t addr, uint32_t value) "mem 
write addr:0x%04x val
 goldfish_rtc_read(uint64_t addr, uint64_t value) "addr 0x%02" PRIx64 " value 
0x%08" PRIx64
 goldfish_rtc_write(uint64_t addr, uint64_t value) "addr 0x%02" PRIx64 " value 
0x%08" PRIx64
 
+# nrf51_rtc.c
+nrf51_rtc_read(uint64_t addr, uint64_t value) "addr 0x%" PRIx64 " value 0x%" 
PRIx64
+nrf51_rtc_write(uint64_t addr, uint64_t value) "addr 0x%" PRIx64 " value 0x%" 
PRIx64
+
 # rs5c372.c
 rs5c372_recv(uint32_t addr, uint8_t value) "[0x%" PRIx32 "] -> 0x%02" PRIx8
 rs5c372_send(uint32_t addr, uint8_t value) "[0x%" PRIx32 "] <- 0x%02" PRIx8
diff --git a/include/hw/arm/nrf51.h b/include/hw/arm/nrf51.h
index de836beaa4..b20519bd0a 100644
--- a/include/hw/arm/nrf51.h
+++ b/include/hw/arm/nrf51.h
@@ -28,6 +28,7 @@
 #define NRF51_UART_BASE       0x40002000
 #define NRF51_TWI_BASE        0x40003000
 #define NRF51_TIMER_BASE      0x40008000
+#define NRF51_RTC0_BASE       0x4000B000
 #define NRF51_RNG_BASE        0x4000D000
 #define NRF51_NVMC_BASE       0x4001E000
 #define NRF51_GPIO_BASE       0x50000000
diff --git a/include/hw/arm/nrf51_soc.h b/include/hw/arm/nrf51_soc.h
index f88ab1b7d3..633100d4d0 100644
--- a/include/hw/arm/nrf51_soc.h
+++ b/include/hw/arm/nrf51_soc.h
@@ -16,6 +16,7 @@
 #include "hw/misc/nrf51_rng.h"
 #include "hw/gpio/nrf51_gpio.h"
 #include "hw/nvram/nrf51_nvm.h"
+#include "hw/rtc/nrf51_rtc.h"
 #include "hw/timer/nrf51_timer.h"
 #include "hw/clock.h"
 #include "qom/object.h"
@@ -34,6 +35,7 @@ struct NRF51State {
 
     NRF51UARTState uart;
     NRF51RNGState rng;
+    NRF51RTCState rtc;
     NRF51NVMState nvm;
     NRF51GPIOState gpio;
     NRF51TimerState timer[NRF51_NUM_TIMERS];
diff --git a/include/hw/rtc/nrf51_rtc.h b/include/hw/rtc/nrf51_rtc.h
new file mode 100644
index 0000000000..08c8319fd5
--- /dev/null
+++ b/include/hw/rtc/nrf51_rtc.h
@@ -0,0 +1,68 @@
+/*
+ * nRF51 SoC RTC emulation
+ *
+ * Copyright 2025 Kaido Kert <kaidok...@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_RTC_NRF51_RTC_H
+#define HW_RTC_NRF51_RTC_H
+
+#include "hw/sysbus.h"
+#include "qom/object.h"
+
+#define TYPE_NRF51_RTC "nrf51_soc.rtc"
+OBJECT_DECLARE_SIMPLE_TYPE(NRF51RTCState, NRF51_RTC)
+
+/* Register offsets */
+#define NRF51_RTC_TASKS_START 0x000
+#define NRF51_RTC_TASKS_STOP 0x004
+#define NRF51_RTC_TASKS_CLEAR 0x008
+#define NRF51_RTC_TASKS_TRIGOVRFLW 0x00C
+#define NRF51_RTC_EVENTS_TICK 0x100
+#define NRF51_RTC_EVENTS_OVRFLW 0x104
+#define NRF51_RTC_EVENTS_COMPARE0 0x140
+#define NRF51_RTC_EVENTS_COMPARE1 0x144
+#define NRF51_RTC_EVENTS_COMPARE2 0x148
+#define NRF51_RTC_EVENTS_COMPARE3 0x14C
+#define NRF51_RTC_INTENSET 0x304
+#define NRF51_RTC_INTENCLR 0x308
+#define NRF51_RTC_EVTEN 0x340
+#define NRF51_RTC_EVTENSET 0x344
+#define NRF51_RTC_EVTENCLR 0x348
+#define NRF51_RTC_COUNTER 0x504
+#define NRF51_RTC_PRESCALER 0x508
+#define NRF51_RTC_CC0 0x540
+#define NRF51_RTC_CC1 0x544
+#define NRF51_RTC_CC2 0x548
+#define NRF51_RTC_CC3 0x54C
+#define NRF51_RTC_POWER 0xFFC
+
+#define NRF51_RTC_CC_REG_COUNT 4
+#define NRF51_RTC_EVENT_COUNT 6
+
+#define NRF51_RTC_EVENT_TICK 0
+#define NRF51_RTC_EVENT_OVRFLW 1
+#define NRF51_RTC_EVENT_COMPARE 2
+
+struct NRF51RTCState {
+    SysBusDevice parent_obj;
+
+    MemoryRegion iomem;
+    QEMUTimer *timer;
+    qemu_irq irq;
+
+    uint32_t tick_count;  /* Current counter value, 24-bit */
+    uint64_t last_update; /* Last tick update time (ns) */
+    uint32_t prescaler;   /* 12-bit prescaler */
+    bool running;         /* RTC running state */
+    bool powered;         /* RTC power state (POWER register) */
+    bool irq_pending;     /* Pending interrupts */
+    uint32_t inten;       /* Interrupt enable */
+    uint32_t evten;       /* Event enable */
+    uint32_t cc[NRF51_RTC_CC_REG_COUNT];    /* Compare registers CC[0-3] */
+    uint32_t events[NRF51_RTC_EVENT_COUNT]; /* TICK, OVRFLW, COMPARE[0-3] */
+};
+
+#endif /* HW_RTC_NRF51_RTC_H */
diff --git a/tests/qtest/microbit-test.c b/tests/qtest/microbit-test.c
index 505c831f13..8c0b81192f 100644
--- a/tests/qtest/microbit-test.c
+++ b/tests/qtest/microbit-test.c
@@ -22,6 +22,7 @@
 #include "hw/char/nrf51_uart.h"
 #include "hw/gpio/nrf51_gpio.h"
 #include "hw/nvram/nrf51_nvm.h"
+#include "hw/rtc/nrf51_rtc.h"
 #include "hw/timer/nrf51_timer.h"
 #include "hw/i2c/microbit_i2c.h"
 
@@ -538,6 +539,253 @@ static void test_nrf51_timer(void)
     qtest_quit(qts);
 }
 
+/* RTC Helper Functions */
+static void rtc_task(QTestState *qts, hwaddr task)
+{
+    qtest_writel(qts, NRF51_RTC0_BASE + task, NRF51_TRIGGER_TASK);
+}
+
+static void rtc_clear_event(QTestState *qts, hwaddr event)
+{
+    qtest_writel(qts, NRF51_RTC0_BASE + event, NRF51_EVENT_CLEAR);
+}
+
+static void rtc_set_prescaler(QTestState *qts, uint32_t prescaler)
+{
+    qtest_writel(qts, NRF51_RTC0_BASE + NRF51_RTC_PRESCALER, prescaler);
+}
+
+static void rtc_set_cc(QTestState *qts, size_t idx, uint32_t value)
+{
+    qtest_writel(qts, NRF51_RTC0_BASE + NRF51_RTC_CC0 + idx * 4, value);
+}
+
+static void rtc_assert_events(QTestState *qts,
+                              uint32_t tick,
+                              uint32_t ovrflw,
+                              uint32_t cmp0,
+                              uint32_t cmp1,
+                              uint32_t cmp2,
+                              uint32_t cmp3) {
+    g_assert_cmpuint(qtest_readl(qts, NRF51_RTC0_BASE + NRF51_RTC_EVENTS_TICK),
+                     ==,
+                     tick);
+    g_assert_cmpuint(qtest_readl(qts,
+                                 NRF51_RTC0_BASE + NRF51_RTC_EVENTS_OVRFLW),
+                     ==,
+                     ovrflw);
+    g_assert_cmpuint(qtest_readl(qts,
+                                 NRF51_RTC0_BASE + NRF51_RTC_EVENTS_COMPARE0),
+                     ==,
+                     cmp0);
+    g_assert_cmpuint(qtest_readl(qts,
+                                 NRF51_RTC0_BASE + NRF51_RTC_EVENTS_COMPARE1),
+                     ==,
+                     cmp1);
+    g_assert_cmpuint(qtest_readl(qts,
+                                 NRF51_RTC0_BASE + NRF51_RTC_EVENTS_COMPARE2),
+                     ==,
+                     cmp2);
+    g_assert_cmpuint(qtest_readl(qts,
+                                 NRF51_RTC0_BASE + NRF51_RTC_EVENTS_COMPARE3),
+                     ==,
+                     cmp3);
+}
+
+static void test_nrf51_rtc(void)
+{
+    QTestState *qts = qtest_init("-M microbit");
+    uint64_t tick_ns = 30517; /* 32.768 kHz = ~30.517 µs per tick */
+
+    /* Check reset state */
+    g_assert_cmpuint(qtest_readl(qts, NRF51_RTC0_BASE + NRF51_RTC_COUNTER),
+                     ==,
+                     0);
+    g_assert_cmpuint(qtest_readl(qts, NRF51_RTC0_BASE + NRF51_RTC_PRESCALER),
+                     ==,
+                     0);
+    g_assert_cmpuint(qtest_readl(qts, NRF51_RTC0_BASE + NRF51_RTC_INTENSET),
+                     ==,
+                     0);
+    g_assert_cmpuint(qtest_readl(qts, NRF51_RTC0_BASE + NRF51_RTC_EVTEN),
+                     ==,
+                     0);
+    g_assert_cmpuint(qtest_readl(qts, NRF51_RTC0_BASE + NRF51_RTC_POWER),
+                     ==,
+                     0);
+    rtc_assert_events(qts, 0, 0, 0, 0, 0, 0);
+
+    /* Test power control */
+    qtest_writel(qts, NRF51_RTC0_BASE + NRF51_RTC_POWER, 1); /* Enable power */
+    g_assert_cmpuint(qtest_readl(qts, NRF51_RTC0_BASE + NRF51_RTC_POWER),
+                     ==,
+                     1);
+    qtest_writel(qts,
+                 NRF51_RTC0_BASE + NRF51_RTC_TASKS_START,
+                 1); /* Start RTC */
+    /* Perform multiple smaller clock steps to ensure timer fires */
+    for (int i = 0; i < 10; i++) {
+        qtest_clock_step(qts, tick_ns * 2); /* Advance 2 ticks per step */
+    }
+    g_assert_cmpuint(qtest_readl(qts, NRF51_RTC0_BASE + NRF51_RTC_COUNTER),
+                     ==,
+                     19);
+    qtest_writel(qts, NRF51_RTC0_BASE + NRF51_RTC_POWER, 0); /* Disable power 
*/
+    g_assert_cmpuint(qtest_readl(qts, NRF51_RTC0_BASE + NRF51_RTC_POWER),
+                     ==,
+                     0);
+    qtest_clock_step(qts, tick_ns * 10);
+    g_assert_cmpuint(qtest_readl(qts, NRF51_RTC0_BASE + NRF51_RTC_COUNTER),
+                     ==,
+                     0); /* No increment */
+    qtest_writel(qts,
+                 NRF51_RTC0_BASE + NRF51_RTC_POWER,
+                 1); /* Re-enable power */
+
+    /* Test counter increment with prescaler = 0 */
+    rtc_task(qts, NRF51_RTC_TASKS_START);
+    qtest_clock_step(qts, tick_ns * 10); /* Advance 10 ticks */
+    g_assert_cmpuint(qtest_readl(qts, NRF51_RTC0_BASE + NRF51_RTC_COUNTER),
+                     ==,
+                     10);
+    rtc_task(qts, NRF51_RTC_TASKS_STOP);
+    qtest_clock_step(qts, tick_ns * 10); /* No increment when stopped */
+    g_assert_cmpuint(qtest_readl(qts, NRF51_RTC0_BASE + NRF51_RTC_COUNTER),
+                     ==,
+                     10);
+
+    /* Test PRESCALER write restriction */
+    rtc_task(qts, NRF51_RTC_TASKS_START);
+    qtest_writel(qts,
+                 NRF51_RTC0_BASE + NRF51_RTC_PRESCALER,
+                 1); /* Try to write while running */
+    g_assert_cmpuint(qtest_readl(qts, NRF51_RTC0_BASE + NRF51_RTC_PRESCALER),
+                     ==,
+                     0); /* No change */
+    rtc_task(qts, NRF51_RTC_TASKS_STOP);
+    qtest_writel(qts,
+                 NRF51_RTC0_BASE + NRF51_RTC_PRESCALER,
+                 1); /* Write when stopped */
+    g_assert_cmpuint(qtest_readl(qts, NRF51_RTC0_BASE + NRF51_RTC_PRESCALER),
+                     ==,
+                     1);
+
+    /* Test TICK and COMPARE events with IRQ */
+    qtest_irq_intercept_out_named(qts,
+                                  "/machine/nrf51/rtc",
+                                  "sysbus-irq"); /* Intercept RTC IRQ */
+    rtc_task(qts, NRF51_RTC_TASKS_CLEAR);
+    g_assert_cmpuint(qtest_readl(qts, NRF51_RTC0_BASE + NRF51_RTC_COUNTER),
+                     ==,
+                     0);
+    qtest_writel(qts,
+                 NRF51_RTC0_BASE + NRF51_RTC_EVTENSET,
+                 0x00010001); /* Enable TICK, COMPARE0 events */
+    qtest_writel(qts,
+                 NRF51_RTC0_BASE + NRF51_RTC_INTENSET,
+                 0x00010001); /* Enable TICK, COMPARE0 interrupts */
+    rtc_set_cc(qts, 0, 5);    /* COMPARE0 at counter = 5 */
+    g_assert_false(qtest_get_irq(qts, 0)); /* No IRQ yet */
+    rtc_task(qts, NRF51_RTC_TASKS_START);
+    qtest_clock_step(qts, tick_ns * 6); /* Advance 3 ticks */
+    rtc_assert_events(qts, 1, 0, 0, 0, 0, 0);
+    g_assert_true(qtest_get_irq(qts, 0)); /* TICK IRQ */
+    qtest_clock_step(qts, tick_ns * 6);   /* Advance to 6 ticks */
+    rtc_assert_events(qts, 1, 0, 1, 0, 0, 0);
+    g_assert_true(qtest_get_irq(qts, 0)); /* COMPARE0 IRQ */
+    g_assert_cmpuint(qtest_readl(qts, NRF51_RTC0_BASE + NRF51_RTC_COUNTER),
+                     ==,
+                     6);
+    rtc_clear_event(qts, NRF51_RTC_EVENTS_TICK);
+    rtc_clear_event(qts, NRF51_RTC_EVENTS_COMPARE0);
+    rtc_assert_events(qts, 0, 0, 0, 0, 0, 0);
+    g_assert_false(qtest_get_irq(qts, 0)); /* IRQ cleared */
+
+    /* Test prescaler */
+    rtc_task(qts, NRF51_RTC_TASKS_CLEAR);
+    rtc_set_prescaler(qts, 1); /* Prescaler = 1, tick period = 2 * 30.517 µs */
+    rtc_task(qts, NRF51_RTC_TASKS_START);
+    qtest_clock_step(qts, tick_ns * 4); /* 4 base ticks = 2 prescaled ticks */
+    g_assert_cmpuint(qtest_readl(qts, NRF51_RTC0_BASE + NRF51_RTC_COUNTER),
+                     ==,
+                     2);
+    rtc_task(qts, NRF51_RTC_TASKS_STOP);
+
+    /* Test interrupt enable */
+    rtc_task(qts, NRF51_RTC_TASKS_CLEAR);
+    qtest_writel(qts,
+                 NRF51_RTC0_BASE + NRF51_RTC_INTENSET,
+                 0x00010001); /* Enable TICK, COMPARE0 interrupts */
+    g_assert_cmpuint(qtest_readl(qts, NRF51_RTC0_BASE + NRF51_RTC_INTENSET),
+                     ==,
+                     0x00010001);
+    qtest_writel(qts, NRF51_RTC0_BASE + NRF51_RTC_INTENCLR, 0x00010001);
+    g_assert_cmpuint(qtest_readl(qts, NRF51_RTC0_BASE + NRF51_RTC_INTENSET),
+                     ==,
+                     0x00000000);
+
+    /* Test overflow */
+    rtc_task(qts, NRF51_RTC_TASKS_CLEAR);
+    qtest_writel(qts,
+                 NRF51_RTC0_BASE + NRF51_RTC_EVTENCLR,
+                 0x00000001); /* Disable TICK event */
+    rtc_task(qts, NRF51_RTC_TASKS_TRIGOVRFLW);
+    qtest_writel(qts,
+                 NRF51_RTC0_BASE + NRF51_RTC_EVTENSET,
+                 0x00000002); /* Enable OVRFLW event */
+    qtest_writel(qts,
+                 NRF51_RTC0_BASE + NRF51_RTC_INTENSET,
+                 0x00000002); /* Enable OVRFLW interrupt */
+    rtc_set_prescaler(qts, 0);
+    g_assert_false(qtest_get_irq(qts, 0)); /* No IRQ yet */
+    rtc_task(qts, NRF51_RTC_TASKS_START);
+    qtest_clock_step(qts, tick_ns * 3); /* Advance 3 ticks to overflow */
+    g_assert_cmpuint(qtest_readl(qts, NRF51_RTC0_BASE + NRF51_RTC_COUNTER),
+                     ==,
+                     1); /* Wrapped to 1 */
+    rtc_assert_events(qts, 1, 1, 0, 1, 1, 1);
+    g_assert_true(qtest_get_irq(qts, 0)); /* OVRFLW IRQ */
+    /* Clear overflow & tick */
+    rtc_clear_event(qts, NRF51_RTC_EVENTS_OVRFLW);
+    rtc_clear_event(qts, NRF51_RTC_EVENTS_TICK);
+    /* Clear the spurious compare events */
+    rtc_clear_event(qts, NRF51_RTC_EVENTS_COMPARE1);
+    rtc_clear_event(qts, NRF51_RTC_EVENTS_COMPARE2);
+    rtc_clear_event(qts, NRF51_RTC_EVENTS_COMPARE3);
+    rtc_assert_events(qts, 0, 0, 0, 0, 0, 0);
+    g_assert_false(qtest_get_irq(qts, 0)); /* IRQ cleared */
+
+    /* Test multiple COMPARE registers */
+    rtc_task(qts, NRF51_RTC_TASKS_CLEAR);
+    qtest_writel(qts,
+                 NRF51_RTC0_BASE + NRF51_RTC_EVTENSET,
+                 0x000F0000); /* Enable COMPARE[0-3] events */
+    qtest_writel(qts,
+                 NRF51_RTC0_BASE + NRF51_RTC_INTENSET,
+                 0x000F0000); /* Enable COMPARE[0-3] interrupts */
+    rtc_set_cc(qts, 0, 2);
+    rtc_set_cc(qts, 1, 4);
+    rtc_set_cc(qts, 2, 6);
+    rtc_set_cc(qts, 3, 8);
+    g_assert_false(qtest_get_irq(qts, 0)); /* No IRQ yet */
+    rtc_task(qts, NRF51_RTC_TASKS_START);
+    qtest_clock_step(qts, tick_ns * 3); /* Advance to 3 ticks */
+    rtc_assert_events(qts, 1, 0, 1, 0, 0, 0);
+    g_assert_true(qtest_get_irq(qts, 0)); /* COMPARE0 IRQ */
+    qtest_clock_step(qts, tick_ns * 2);   /* Advance to 5 ticks */
+    rtc_assert_events(qts, 1, 0, 1, 1, 0, 0);
+    g_assert_true(qtest_get_irq(qts, 0)); /* COMPARE1 IRQ */
+    qtest_clock_step(qts, tick_ns * 2);   /* Advance to 7 ticks */
+    rtc_assert_events(qts, 1, 0, 1, 1, 1, 0);
+    g_assert_true(qtest_get_irq(qts, 0)); /* COMPARE2 IRQ */
+    qtest_clock_step(qts, tick_ns * 2);   /* Advance to 9 ticks */
+    rtc_assert_events(qts, 1, 0, 1, 1, 1, 1);
+    g_assert_true(qtest_get_irq(qts, 0)); /* COMPARE3 IRQ */
+    rtc_task(qts, NRF51_RTC_TASKS_STOP);
+
+    qtest_quit(qts);
+}
+
 int main(int argc, char **argv)
 {
     g_test_init(&argc, &argv, NULL);
@@ -548,6 +796,7 @@ int main(int argc, char **argv)
     qtest_add_func("/microbit/nrf51/nvmc", test_nrf51_nvmc);
     qtest_add_func("/microbit/nrf51/timer", test_nrf51_timer);
     qtest_add_func("/microbit/microbit/i2c", test_microbit_i2c);
+    qtest_add_func("/microbit/nrf51/rtc", test_nrf51_rtc);
 
     return g_test_run();
 }
-- 
2.39.5 (Apple Git-154)


Reply via email to