[PATCH 2/5] hw/rtc: add K230 RTC.
Leo Cheng <[email protected]>
| Newsgroups | gmane.comp.emulators.qemu |
|---|---|
| Message-ID | <[email protected]> |
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