Model the K230 RTC: date/time keeping with leap-year carry, write protection, a 
masked comparator alarm, a TICK_EN periodic tick interrupt, and a live 
sub-second COUNT derived from virtual time. The alarm is re-evaluated on 
TIME/ALARM/INT_CTRL writes and the year comparison ignores the DATE leap flag. 
Register layout from the K230 TRM v0.3.1. Covered by tests/qtest/k230-rtc-test.

Signed-off-by: Leo Cheng <[email protected]>
---
 hw/rtc/Kconfig              |   3 +
 hw/rtc/k230_rtc.c           | 306 ++++++++++++++++++++++++++++++++++++
 hw/rtc/meson.build          |   1 +
 hw/rtc/trace-events         |   5 +
 include/hw/rtc/k230_rtc.h   |  61 +++++++
 tests/qtest/k230-rtc-test.c | 188 ++++++++++++++++++++++
 6 files changed, 564 insertions(+)
 create mode 100644 hw/rtc/k230_rtc.c
 create mode 100644 include/hw/rtc/k230_rtc.h
 create mode 100644 tests/qtest/k230-rtc-test.c

diff --git a/hw/rtc/Kconfig b/hw/rtc/Kconfig
index 315b0e4..d764807 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 K230_RTC
+    bool
diff --git a/hw/rtc/k230_rtc.c b/hw/rtc/k230_rtc.c
new file mode 100644
index 0000000..c1de83d
--- /dev/null
+++ b/hw/rtc/k230_rtc.c
@@ -0,0 +1,306 @@
+/*
+ * K230 RTC compatible with Kendryte K230 SDK
+ *
+ * Copyright (c) 2026 Leo Cheng <[email protected]>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * K230 Technical Reference Manual V0.3.1 (2024-11-18):
+ * 
https://github.com/revyos/external-docs/blob/master/K230/en-us/K230_Technical_Reference_Manual_V0.3.1_20241118.pdf
+ *
+ * For more information, see <https://www.kendryte.com/en/proDetail/230>
+ */
+#include "qemu/osdep.h"
+#include "qemu/bitops.h"
+#include "qemu/module.h"
+#include "qemu/timer.h"
+#include "hw/core/irq.h"
+#include "migration/vmstate.h"
+#include "hw/rtc/k230_rtc.h"
+#include "trace.h"
+
+static bool k230_rtc_is_leap(uint32_t year)
+{
+    return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
+}
+
+static void k230_rtc_advance(K230RtcState *s)
+{
+    uint32_t sec = extract32(s->time, 0, 6);
+    uint32_t min = extract32(s->time, 8, 6);
+    uint32_t hour = extract32(s->time, 16, 5);
+    uint32_t week = extract32(s->time, 24, 3);
+    uint32_t day = extract32(s->date, 0, 5);
+    uint32_t month = extract32(s->date, 8, 4);
+    uint32_t year_l = extract32(s->date, 16, 7);
+    uint32_t year_h = extract32(s->date, 24, 7);
+    static const uint8_t dim[12] = {31, 28, 31, 30, 31, 30,
+                                    31, 31, 30, 31, 30, 31};
+
+    if (++sec >= 60) {
+        sec = 0;
+        if (++min >= 60) {
+            min = 0;
+            if (++hour >= 24) {
+                uint32_t year = year_h * 100 + year_l;
+                uint32_t maxd = month >= 1 && month <= 12 ? dim[month - 1] : 
31;
+
+                hour = 0;
+                week = (week + 1) % 7;
+                if (month == 2 && k230_rtc_is_leap(year)) {
+                    maxd = 29;
+                }
+                if (++day > maxd) {
+                    day = 1;
+                    if (++month > 12) {
+                        month = 1;
+                        if (++year_l >= 100) {
+                            year_l = 0;
+                            year_h++;
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    s->time = deposit32(deposit32(deposit32(deposit32(0, 0, 6, sec),
+                        8, 6, min), 16, 5, hour), 24, 3, week);
+    s->date = deposit32(deposit32(deposit32(deposit32(deposit32(0, 0, 5, day),
+                        8, 4, month), 16, 7, year_l), 24, 7, year_h),
+                        23, 1, k230_rtc_is_leap(year_h * 100 + year_l));
+}
+
+static void k230_rtc_update_irq(K230RtcState *s)
+{
+    qemu_set_irq(s->irq, s->alarm_pending || s->tick_pending);
+}
+
+static void k230_rtc_check_alarm(K230RtcState *s)
+{
+    uint32_t cmp = extract32(s->int_ctrl, K230_RTC_CMP_SHIFT, 7);
+    bool match = true;
+
+    if (!(s->int_ctrl & K230_RTC_ALARM_EN) || cmp == 0) {
+        return;
+    }
+
+    if (cmp & BIT(0)) { /* second */
+        match &= extract32(s->time, 0, 6) == extract32(s->alarm_time, 0, 6);
+    }
+    if (cmp & BIT(1)) { /* minute */
+        match &= extract32(s->time, 8, 6) == extract32(s->alarm_time, 8, 6);
+    }
+    if (cmp & BIT(2)) { /* hour */
+        match &= extract32(s->time, 16, 5) == extract32(s->alarm_time, 16, 5);
+    }
+    if (cmp & BIT(3)) { /* weekday */
+        match &= extract32(s->time, 24, 3) == extract32(s->alarm_time, 24, 3);
+    }
+    if (cmp & BIT(4)) { /* day */
+        match &= extract32(s->date, 0, 5) == extract32(s->alarm_date, 0, 5);
+    }
+    if (cmp & BIT(5)) { /* month */
+        match &= extract32(s->date, 8, 4) == extract32(s->alarm_date, 8, 4);
+    }
+    if (cmp & BIT(6)) { /* year: skip leap flag at bit 23 */
+        match &= extract32(s->date, 16, 7) == extract32(s->alarm_date, 16, 7) 
&&
+                 extract32(s->date, 24, 7) == extract32(s->alarm_date, 24, 7);
+    }
+
+    if (match) {
+        s->alarm_pending = true;
+        k230_rtc_update_irq(s);
+        trace_k230_rtc_alarm();
+    }
+}
+
+static void k230_rtc_tick(void *opaque)
+{
+    K230RtcState *s = K230_RTC(opaque);
+
+    s->last_tick_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+    k230_rtc_advance(s);
+    if (s->int_ctrl & K230_RTC_TICK_EN) {
+        s->tick_pending = true;
+    }
+    k230_rtc_check_alarm(s);
+    k230_rtc_update_irq(s);
+    timer_mod(s->timer, s->last_tick_ns + NANOSECONDS_PER_SECOND);
+}
+
+static uint64_t k230_rtc_read(void *opaque, hwaddr addr, unsigned int size)
+{
+    K230RtcState *s = K230_RTC(opaque);
+    uint32_t value = 0;
+
+    switch (addr) {
+    case K230_RTC_DATE:
+        value = s->date;
+        break;
+    case K230_RTC_TIME:
+        value = s->time;
+        break;
+    case K230_RTC_ALARM_DATE:
+        value = s->alarm_date;
+        break;
+    case K230_RTC_ALARM_TIME:
+        value = s->alarm_time;
+        break;
+    case K230_RTC_COUNT: {
+        uint32_t sum = extract32(s->count, 16, 16);
+        int64_t elapsed = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) -
+                          s->last_tick_ns;
+        uint64_t curr = 0;
+
+        if (elapsed > 0) {
+            curr = (uint64_t)elapsed * (sum + 1) / NANOSECONDS_PER_SECOND;
+        }
+        if (curr > sum) {
+            curr = sum;
+        }
+        value = (sum << 16) | (uint32_t)curr;
+        break;
+    }
+    case K230_RTC_INT_CTRL:
+        value = s->int_ctrl;
+        break;
+    default:
+        break;
+    }
+
+    trace_k230_rtc_read(addr, value);
+    return value;
+}
+
+static void k230_rtc_write(void *opaque, hwaddr addr,
+                           uint64_t value, unsigned int size)
+{
+    K230RtcState *s = K230_RTC(opaque);
+    bool w_en = s->int_ctrl & K230_RTC_TIMER_W_EN;
+
+    trace_k230_rtc_write(addr, value);
+
+    switch (addr) {
+    case K230_RTC_DATE:
+        if (w_en) {
+            s->date = value;
+            k230_rtc_check_alarm(s);
+        }
+        break;
+    case K230_RTC_TIME:
+        if (w_en) {
+            s->time = value;
+            k230_rtc_check_alarm(s);
+        }
+        break;
+    case K230_RTC_ALARM_DATE:
+        s->alarm_date = value;
+        k230_rtc_check_alarm(s);
+        break;
+    case K230_RTC_ALARM_TIME:
+        s->alarm_time = value;
+        k230_rtc_check_alarm(s);
+        break;
+    case K230_RTC_COUNT:
+        if (w_en) {
+            s->count = value;
+            s->last_tick_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+        }
+        break;
+    case K230_RTC_INT_CTRL:
+        if (value & K230_RTC_ALARM_CLR) {
+            s->alarm_pending = false;
+            s->tick_pending = false;
+            value &= ~K230_RTC_ALARM_CLR;
+        }
+        s->int_ctrl = value;
+        k230_rtc_check_alarm(s);
+        k230_rtc_update_irq(s);
+        break;
+    default:
+        break;
+    }
+}
+
+static const MemoryRegionOps k230_rtc_ops = {
+    .read = k230_rtc_read,
+    .write = k230_rtc_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .impl = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+        .unaligned = false,
+    },
+};
+
+static void k230_rtc_reset(DeviceState *dev)
+{
+    K230RtcState *s = K230_RTC(dev);
+
+    s->date = 0;
+    s->time = 0;
+    s->alarm_date = 0;
+    s->alarm_time = 0;
+    s->count = 32767u << 16;
+    s->int_ctrl = 0;
+    s->alarm_pending = false;
+    s->tick_pending = false;
+    s->last_tick_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+    qemu_set_irq(s->irq, 0);
+
+    timer_mod(s->timer, s->last_tick_ns + NANOSECONDS_PER_SECOND);
+}
+
+static const VMStateDescription vmstate_k230_rtc = {
+    .name = "k230.rtc",
+    .fields = (const VMStateField[]) {
+        VMSTATE_TIMER_PTR(timer, K230RtcState),
+        VMSTATE_UINT32(date, K230RtcState),
+        VMSTATE_UINT32(time, K230RtcState),
+        VMSTATE_UINT32(alarm_date, K230RtcState),
+        VMSTATE_UINT32(alarm_time, K230RtcState),
+        VMSTATE_UINT32(count, K230RtcState),
+        VMSTATE_UINT32(int_ctrl, K230RtcState),
+        VMSTATE_INT64(last_tick_ns, K230RtcState),
+        VMSTATE_BOOL(alarm_pending, K230RtcState),
+        VMSTATE_BOOL(tick_pending, K230RtcState),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static void k230_rtc_realize(DeviceState *dev, Error **errp)
+{
+    K230RtcState *s = K230_RTC(dev);
+    SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+
+    memory_region_init_io(&s->mmio, OBJECT(dev), &k230_rtc_ops, s,
+                          TYPE_K230_RTC, K230_RTC_MMIO_SIZE);
+    sysbus_init_mmio(sbd, &s->mmio);
+    sysbus_init_irq(sbd, &s->irq);
+
+    s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, k230_rtc_tick, s);
+}
+
+static void k230_rtc_class_init(ObjectClass *klass, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    dc->realize = k230_rtc_realize;
+    device_class_set_legacy_reset(dc, k230_rtc_reset);
+    dc->vmsd = &vmstate_k230_rtc;
+    dc->desc = "K230 RTC";
+}
+
+static const TypeInfo k230_rtc_info = {
+    .name          = TYPE_K230_RTC,
+    .parent        = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(K230RtcState),
+    .class_init    = k230_rtc_class_init,
+};
+
+static void k230_rtc_register_type(void)
+{
+    type_register_static(&k230_rtc_info);
+}
+type_init(k230_rtc_register_type)
diff --git a/hw/rtc/meson.build b/hw/rtc/meson.build
index 6c87864..3f3529c 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_K230_RTC', if_true: files('k230_rtc.c'))
diff --git a/hw/rtc/trace-events b/hw/rtc/trace-events
index d2f3621..beee3b1 100644
--- a/hw/rtc/trace-events
+++ b/hw/rtc/trace-events
@@ -43,3 +43,8 @@ goldfish_rtc_write(uint64_t addr, uint64_t value) "addr 
0x%02" PRIx64 " value 0x
 # 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
+
+# k230_rtc.c
+k230_rtc_read(uint64_t addr, uint32_t data) "K230 RTC read: [0x%" PRIx64 "] -> 
0x%" PRIx32
+k230_rtc_write(uint64_t addr, uint64_t data) "K230 RTC write: [0x%" PRIx64 "] 
<- 0x%" PRIx64
+k230_rtc_alarm(void) "K230 RTC alarm"
diff --git a/include/hw/rtc/k230_rtc.h b/include/hw/rtc/k230_rtc.h
new file mode 100644
index 0000000..678ee37
--- /dev/null
+++ b/include/hw/rtc/k230_rtc.h
@@ -0,0 +1,61 @@
+/*
+ * K230 RTC compatible with Kendryte K230 SDK
+ *
+ * Copyright (c) 2026 Leo Cheng <[email protected]>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * K230 Technical Reference Manual V0.3.1 (2024-11-18):
+ * 
https://github.com/revyos/external-docs/blob/master/K230/en-us/K230_Technical_Reference_Manual_V0.3.1_20241118.pdf
+ *
+ * For more information, see <https://www.kendryte.com/en/proDetail/230>
+ */
+#ifndef HW_RTC_K230_RTC_H
+#define HW_RTC_K230_RTC_H
+
+#include "hw/core/sysbus.h"
+#include "qemu/timer.h"
+#include "qom/object.h"
+
+#define TYPE_K230_RTC "riscv.k230.rtc"
+OBJECT_DECLARE_SIMPLE_TYPE(K230RtcState, K230_RTC)
+
+#define K230_RTC_MMIO_SIZE 0x400
+
+enum {
+    K230_RTC_DATE       = 0x00,
+    K230_RTC_TIME       = 0x04,
+    K230_RTC_ALARM_DATE = 0x08,
+    K230_RTC_ALARM_TIME = 0x0c,
+    K230_RTC_COUNT      = 0x10,
+    K230_RTC_INT_CTRL   = 0x14,
+};
+
+/* INT_CTRL bits */
+#define K230_RTC_TIMER_W_EN (1u << 0)
+#define K230_RTC_TIMER_R_EN (1u << 1)
+#define K230_RTC_TICK_EN    (1u << 8)
+#define K230_RTC_ALARM_EN   (1u << 16)
+#define K230_RTC_ALARM_CLR  (1u << 17)
+#define K230_RTC_CMP_SHIFT  24
+#define K230_RTC_CMP_SECOND (1u << 24)
+
+struct K230RtcState {
+    SysBusDevice parent_obj;
+
+    MemoryRegion mmio;
+    qemu_irq irq;
+    QEMUTimer *timer;
+
+    uint32_t date;
+    uint32_t time;
+    uint32_t alarm_date;
+    uint32_t alarm_time;
+    uint32_t count;
+    uint32_t int_ctrl;
+    int64_t last_tick_ns;
+    bool alarm_pending;
+    bool tick_pending;
+};
+
+#endif
diff --git a/tests/qtest/k230-rtc-test.c b/tests/qtest/k230-rtc-test.c
new file mode 100644
index 0000000..081f21d
--- /dev/null
+++ b/tests/qtest/k230-rtc-test.c
@@ -0,0 +1,188 @@
+/*
+ * QTest testcase for K230 RTC
+ *
+ * Copyright (c) 2026 Leo Cheng <[email protected]>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+#include "qemu/osdep.h"
+#include "qemu/timer.h"
+#include "libqtest.h"
+#include "hw/rtc/k230_rtc.h"
+
+#define RTC_BASE 0x91000C00
+
+#define PLIC_BASE    0xF00000000ULL
+#define PLIC_PENDING (PLIC_BASE + 0x1000)
+#define RTC_IRQ      175
+
+static bool plic_pending(QTestState *qts, int irq)
+{
+    uint32_t word = qtest_readl(qts, PLIC_PENDING + (irq / 32) * 4);
+    return (word >> (irq % 32)) & 1;
+}
+
+static void test_reset_values(void)
+{
+    QTestState *qts = qtest_init("-machine k230");
+
+    g_assert_cmphex(qtest_readl(qts, RTC_BASE + K230_RTC_INT_CTRL), ==, 0);
+
+    qtest_quit(qts);
+}
+
+/* Write-protect: date/time writes need timer_w_en */
+static void test_write_protect(void)
+{
+    QTestState *qts = qtest_init("-machine k230");
+
+    /* Protected: with w_en clear, writes are ignored */
+    qtest_writel(qts, RTC_BASE + K230_RTC_INT_CTRL, 0);
+    qtest_writel(qts, RTC_BASE + K230_RTC_TIME, 0x0A);
+    g_assert_cmphex(qtest_readl(qts, RTC_BASE + K230_RTC_TIME), ==, 0);
+
+    /* Unlocked: enable then write date + time, read back */
+    qtest_writel(qts, RTC_BASE + K230_RTC_INT_CTRL, K230_RTC_TIMER_W_EN);
+    qtest_writel(qts, RTC_BASE + K230_RTC_DATE, (6 << 8) | 15);  /* Jun 15 */
+    qtest_writel(qts, RTC_BASE + K230_RTC_TIME,
+                 (12 << 16) | (30 << 8) | 45);  /* 12:30:45 */
+    g_assert_cmphex(qtest_readl(qts, RTC_BASE + K230_RTC_DATE), ==,
+                    (6 << 8) | 15);
+    g_assert_cmphex(qtest_readl(qts, RTC_BASE + K230_RTC_TIME), ==,
+                    (12 << 16) | (30 << 8) | 45);
+
+    qtest_quit(qts);
+}
+
+/* One virtual second advances TIME.second */
+static void test_tick(void)
+{
+    QTestState *qts = qtest_init("-machine k230");
+
+    qtest_writel(qts, RTC_BASE + K230_RTC_INT_CTRL, K230_RTC_TIMER_W_EN);
+    qtest_writel(qts, RTC_BASE + K230_RTC_TIME, 10);  /* second = 10 */
+
+    qtest_clock_step(qts, NANOSECONDS_PER_SECOND);
+    g_assert_cmpuint(qtest_readl(qts, RTC_BASE + K230_RTC_TIME) & 0x3F, ==, 
11);
+
+    qtest_quit(qts);
+}
+
+/* Masked second alarm raises the RTC interrupt; cleared by alarm_clr */
+static void test_alarm(void)
+{
+    QTestState *qts = qtest_init("-machine k230");
+
+    qtest_writel(qts, RTC_BASE + K230_RTC_INT_CTRL, K230_RTC_TIMER_W_EN);
+    qtest_writel(qts, RTC_BASE + K230_RTC_TIME, 10);
+    qtest_writel(qts, RTC_BASE + K230_RTC_ALARM_TIME, 11);
+    qtest_writel(qts, RTC_BASE + K230_RTC_INT_CTRL,
+                 K230_RTC_ALARM_EN | K230_RTC_CMP_SECOND);
+
+    qtest_clock_step(qts, NANOSECONDS_PER_SECOND);
+    g_assert_true(plic_pending(qts, RTC_IRQ));
+
+    /*
+     * alarm_clr is write-1 self-clearing and lowers the RTC IRQ line; the
+     * PLIC pending bit is sticky until claimed, so verify the ack bit here.
+     */
+    qtest_writel(qts, RTC_BASE + K230_RTC_INT_CTRL,
+                 K230_RTC_ALARM_EN | K230_RTC_CMP_SECOND | K230_RTC_ALARM_CLR);
+    g_assert_cmphex(qtest_readl(qts, RTC_BASE + K230_RTC_INT_CTRL), ==,
+                    K230_RTC_ALARM_EN | K230_RTC_CMP_SECOND);
+
+    qtest_quit(qts);
+}
+
+/* COUNT sub-second field advances with virtual time within the second */
+static void test_count_live(void)
+{
+    QTestState *qts = qtest_init("-machine k230");
+    uint32_t count;
+
+    /* half a second in -> curr = (sum+1)/2, sum field unchanged */
+    qtest_clock_step(qts, NANOSECONDS_PER_SECOND / 2);
+    count = qtest_readl(qts, RTC_BASE + K230_RTC_COUNT);
+    g_assert_cmpuint(count >> 16, ==, 32767);
+    g_assert_cmpuint(count & 0xFFFF, ==, 16384);
+
+    qtest_quit(qts);
+}
+
+/* TICK_EN turns the 1 Hz advance into a periodic interrupt; gated by TICK_EN 
*/
+static void test_tick_irq(void)
+{
+    QTestState *qts = qtest_init("-machine k230");
+
+    /* No TICK_EN: a tick advances time but raises no interrupt */
+    qtest_writel(qts, RTC_BASE + K230_RTC_INT_CTRL, K230_RTC_TIMER_W_EN);
+    qtest_clock_step(qts, NANOSECONDS_PER_SECOND);
+    g_assert_false(plic_pending(qts, RTC_IRQ));
+
+    /* TICK_EN set: the next tick raises the shared RTC interrupt */
+    qtest_writel(qts, RTC_BASE + K230_RTC_INT_CTRL,
+                 K230_RTC_TIMER_W_EN | K230_RTC_TICK_EN);
+    qtest_clock_step(qts, NANOSECONDS_PER_SECOND);
+    g_assert_true(plic_pending(qts, RTC_IRQ));
+
+    qtest_quit(qts);
+}
+
+/* Enabling an alarm whose time already matches fires without waiting a tick */
+static void test_alarm_immediate(void)
+{
+    QTestState *qts = qtest_init("-machine k230");
+
+    qtest_writel(qts, RTC_BASE + K230_RTC_INT_CTRL, K230_RTC_TIMER_W_EN);
+    qtest_writel(qts, RTC_BASE + K230_RTC_TIME, 10);
+    qtest_writel(qts, RTC_BASE + K230_RTC_ALARM_TIME, 10);
+
+    /* enable while already matching -> immediate, no clock step */
+    qtest_writel(qts, RTC_BASE + K230_RTC_INT_CTRL,
+                 K230_RTC_TIMER_W_EN | K230_RTC_ALARM_EN | 
K230_RTC_CMP_SECOND);
+    g_assert_true(plic_pending(qts, RTC_IRQ));
+
+    qtest_quit(qts);
+}
+
+/*
+ * Year alarm must match in a leap year: advance() sets a leap flag at DATE
+ * bit 23, which must not contaminate the year comparison.
+ */
+static void test_alarm_year_leap(void)
+{
+    QTestState *qts = qtest_init("-machine k230");
+    uint32_t date2024 = (20u << 24) | (24u << 16) | (6u << 8) | 15u; /* 2024 */
+
+    qtest_writel(qts, RTC_BASE + K230_RTC_INT_CTRL, K230_RTC_TIMER_W_EN);
+    qtest_writel(qts, RTC_BASE + K230_RTC_DATE, date2024);
+    /* one tick makes advance() set the leap flag (bit 23) in DATE */
+    qtest_clock_step(qts, NANOSECONDS_PER_SECOND);
+
+    qtest_writel(qts, RTC_BASE + K230_RTC_INT_CTRL,
+                 K230_RTC_TIMER_W_EN | K230_RTC_ALARM_EN |
+                 ((1u << 6) << K230_RTC_CMP_SHIFT));  /* year compare */
+    g_assert_false(plic_pending(qts, RTC_IRQ));       /* alarm_date still 0 */
+
+    qtest_writel(qts, RTC_BASE + K230_RTC_ALARM_DATE,
+                 (20u << 24) | (24u << 16));
+    g_assert_true(plic_pending(qts, RTC_IRQ));  /* matches despite leap flag */
+
+    qtest_quit(qts);
+}
+
+int main(int argc, char **argv)
+{
+    g_test_init(&argc, &argv, NULL);
+
+    qtest_add_func("/k230-rtc/reset_values", test_reset_values);
+    qtest_add_func("/k230-rtc/write_protect", test_write_protect);
+    qtest_add_func("/k230-rtc/tick", test_tick);
+    qtest_add_func("/k230-rtc/alarm", test_alarm);
+    qtest_add_func("/k230-rtc/count_live", test_count_live);
+    qtest_add_func("/k230-rtc/tick_irq", test_tick_irq);
+    qtest_add_func("/k230-rtc/alarm_immediate", test_alarm_immediate);
+    qtest_add_func("/k230-rtc/alarm_year_leap", test_alarm_year_leap);
+
+    return g_test_run();
+}
-- 
2.43.0


Reply via email to