Signed-off-by: gilles grimaud <[email protected]>
---
 hw/arm/rp2040.c                 |  10 +-
 hw/misc/meson.build             |   1 +
 hw/misc/rp2040_timer.c          | 352 ++++++++++++++++++++++++++++++++
 include/hw/arm/rp2040.h         |   2 +
 include/hw/misc/rp2040_timer.h  |  39 ++++
 tests/qtest/meson.build         |   1 +
 tests/qtest/rp2040-timer-test.c | 149 ++++++++++++++
 7 files changed, 553 insertions(+), 1 deletion(-)
 create mode 100644 hw/misc/rp2040_timer.c
 create mode 100644 include/hw/misc/rp2040_timer.h
 create mode 100644 tests/qtest/rp2040-timer-test.c

diff --git a/hw/arm/rp2040.c b/hw/arm/rp2040.c
index d0107f98a6..86bf259439 100644
--- a/hw/arm/rp2040.c
+++ b/hw/arm/rp2040.c
@@ -74,7 +74,6 @@ static const struct {
     { "rp2040.i2c1",     0x40048000, 0x4000 },
     { "rp2040.adc",      0x4004c000, 0x4000 },
     { "rp2040.pwm",      0x40050000, 0x4000 },
-    { "rp2040.timer",    0x40054000, 0x4000 },
     { "rp2040.rtc",      0x4005c000, 0x4000 },
     { "rp2040.dma",      0x50000000, 0x1000 },
     { "rp2040.usbctrl_dpram", 0x50100000, 0x10000 },
@@ -281,6 +280,7 @@ static void rp2040_soc_init(Object *obj)
     object_initialize_child(obj, "rosc", &s->rosc, TYPE_RP2040_ROSC);
     object_initialize_child(obj, "sio", &s->sio, TYPE_RP2040_SIO);
     object_initialize_child(obj, "tbman", &s->tbman, TYPE_RP2040_TBMAN);
+    object_initialize_child(obj, "timer", &s->timer, TYPE_RP2040_TIMER);
     object_initialize_child(obj, "vreg", &s->vreg, TYPE_RP2040_VREG);
     object_initialize_child(obj, "watchdog", &s->watchdog,
                             TYPE_RP2040_WATCHDOG);
@@ -496,6 +496,14 @@ static void rp2040_soc_realize(DeviceState *dev, Error 
**errp)
     }
     sysbus_mmio_map(SYS_BUS_DEVICE(&s->tbman), 0, RP2040_TBMAN_BASE);
 
+    if (!sysbus_realize(SYS_BUS_DEVICE(&s->timer), errp)) {
+        return;
+    }
+    sysbus_mmio_map(SYS_BUS_DEVICE(&s->timer), 0, RP2040_TIMER_BASE);
+    for (i = 0; i < RP2040_TIMER_NUM_ALARMS; i++) {
+        sysbus_connect_irq(SYS_BUS_DEVICE(&s->timer), i, s->irq[i]);
+    }
+
     if (!sysbus_realize(SYS_BUS_DEVICE(&s->vreg), errp)) {
         return;
     }
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index db0776227d..47e53fc49e 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -111,6 +111,7 @@ system_ss.add(when: 'CONFIG_RP2040', if_true: 
files('rp2040_syscfg.c'))
 system_ss.add(when: 'CONFIG_RP2040',
               if_true: files('rp2040_sysinfo.c'))
 system_ss.add(when: 'CONFIG_RP2040', if_true: files('rp2040_tbman.c'))
+system_ss.add(when: 'CONFIG_RP2040', if_true: files('rp2040_timer.c'))
 system_ss.add(when: 'CONFIG_RP2040', if_true: files('rp2040_vreg.c'))
 system_ss.add(when: 'CONFIG_RP2040',
               if_true: files('rp2040_watchdog.c'))
diff --git a/hw/misc/rp2040_timer.c b/hw/misc/rp2040_timer.c
new file mode 100644
index 0000000000..024a3e091b
--- /dev/null
+++ b/hw/misc/rp2040_timer.c
@@ -0,0 +1,352 @@
+/*
+ * RP2040 timer emulation
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/core/irq.h"
+#include "hw/misc/rp2040_timer.h"
+#include "hw/misc/rp2040.h"
+#include "migration/vmstate.h"
+#include "qemu/bitops.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qemu/timer.h"
+
+#define TIMER_TIMEHW       0x00
+#define TIMER_TIMELW       0x04
+#define TIMER_TIMEHR       0x08
+#define TIMER_TIMELR       0x0c
+#define TIMER_ALARM0       0x10
+#define TIMER_ALARM3       0x1c
+#define TIMER_ARMED        0x20
+#define TIMER_TIMERAWH     0x24
+#define TIMER_TIMERAWL     0x28
+#define TIMER_DBGPAUSE     0x2c
+#define TIMER_PAUSE        0x30
+#define TIMER_INTR         0x34
+#define TIMER_INTE         0x38
+#define TIMER_INTF         0x3c
+#define TIMER_INTS         0x40
+
+#define TIMER_ALARM_MASK   0x0f
+#define TIMER_DBGPAUSE_MASK (BIT(2) | BIT(1))
+#define TIMER_PAUSE_MASK   BIT(0)
+
+static uint64_t rp2040_timer_now_us(RP2040TimerState *s)
+{
+    if (s->pause & TIMER_PAUSE_MASK) {
+        return s->paused_time_us;
+    }
+
+    return qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) / 1000 + s->time_offset_us;
+}
+
+static uint32_t rp2040_timer_ints(RP2040TimerState *s)
+{
+    uint32_t ints = (s->intr & s->inte) | s->intf;
+
+    return ints & TIMER_ALARM_MASK;
+}
+
+static void rp2040_timer_update_irq(RP2040TimerState *s)
+{
+    uint32_t ints = rp2040_timer_ints(s);
+    int i;
+
+    for (i = 0; i < RP2040_TIMER_NUM_ALARMS; i++) {
+        qemu_set_irq(s->irq[i], !!(ints & BIT(i)));
+    }
+}
+
+static void rp2040_timer_update_alarm(RP2040TimerState *s, unsigned alarm)
+{
+    uint64_t now_us;
+    uint32_t delta;
+
+    if (!(s->armed & BIT(alarm)) || (s->pause & TIMER_PAUSE_MASK)) {
+        timer_del(s->alarm_timer[alarm]);
+        return;
+    }
+
+    now_us = rp2040_timer_now_us(s);
+    delta = s->alarm[alarm] - (uint32_t)now_us;
+    if (delta == 0 || delta > INT32_MAX) {
+        timer_del(s->alarm_timer[alarm]);
+        s->armed &= ~BIT(alarm);
+        s->intr |= BIT(alarm);
+        rp2040_timer_update_irq(s);
+    } else {
+        timer_mod(s->alarm_timer[alarm],
+                  qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
+                  (int64_t)delta * 1000);
+    }
+}
+
+static void rp2040_timer_update_alarms(RP2040TimerState *s)
+{
+    int i;
+
+    for (i = 0; i < RP2040_TIMER_NUM_ALARMS; i++) {
+        rp2040_timer_update_alarm(s, i);
+    }
+}
+
+static bool rp2040_timer_alarm_offset(hwaddr offset, unsigned *alarm)
+{
+    if (offset < TIMER_ALARM0 || offset > TIMER_ALARM3 || (offset & 0x3)) {
+        return false;
+    }
+
+    *alarm = (offset - TIMER_ALARM0) / sizeof(uint32_t);
+    return true;
+}
+
+static uint64_t rp2040_timer_read(void *opaque, hwaddr addr, unsigned size)
+{
+    RP2040TimerState *s = opaque;
+    hwaddr offset = addr & 0xfff;
+    uint64_t now_us = rp2040_timer_now_us(s);
+    unsigned alarm;
+    uint64_t value;
+
+    switch (offset) {
+    case TIMER_TIMEHR:
+    case TIMER_TIMERAWH:
+        value = now_us >> 32;
+        break;
+    case TIMER_TIMELR:
+    case TIMER_TIMERAWL:
+        value = (uint32_t)now_us;
+        break;
+    case TIMER_ARMED:
+        value = s->armed;
+        break;
+    case TIMER_DBGPAUSE:
+        value = s->dbgpause;
+        break;
+    case TIMER_PAUSE:
+        value = s->pause;
+        break;
+    case TIMER_INTR:
+        value = s->intr;
+        break;
+    case TIMER_INTE:
+        value = s->inte;
+        break;
+    case TIMER_INTF:
+        value = s->intf;
+        break;
+    case TIMER_INTS:
+        value = rp2040_timer_ints(s);
+        break;
+    default:
+        if (rp2040_timer_alarm_offset(offset, &alarm)) {
+            value = s->alarm[alarm];
+        } else {
+            value = 0;
+            qemu_log_mask(LOG_UNIMP,
+                      "%s: unimplemented read at offset 0x%"
+                      HWADDR_PRIx "\n", __func__, addr & 0xfff);
+        }
+        break;
+    }
+
+    return value;
+}
+
+static void rp2040_timer_set_time(RP2040TimerState *s, uint64_t value)
+{
+    if (s->pause & TIMER_PAUSE_MASK) {
+        s->paused_time_us = value;
+    } else {
+        s->time_offset_us = value -
+                            qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) / 1000;
+    }
+    rp2040_timer_update_alarms(s);
+}
+
+static void rp2040_timer_write(void *opaque, hwaddr addr,
+                               uint64_t value64, unsigned size)
+{
+    RP2040TimerState *s = opaque;
+    hwaddr alias = addr & RP2040_ATOMIC_ALIAS_MASK;
+    hwaddr offset = addr & 0xfff;
+    uint32_t value = value64;
+    uint32_t old;
+    uint64_t now_us;
+    unsigned alarm;
+
+    switch (offset) {
+    case TIMER_TIMEHW:
+        now_us = rp2040_timer_now_us(s);
+        rp2040_timer_set_time(s, ((uint64_t)value << 32) |
+                              (uint32_t)now_us);
+        break;
+    case TIMER_TIMELW:
+        now_us = rp2040_timer_now_us(s);
+        rp2040_timer_set_time(s, (now_us & UINT64_C(0xffffffff00000000)) |
+                              value);
+        break;
+    case TIMER_ARMED:
+        s->armed &= ~(value & TIMER_ALARM_MASK);
+        rp2040_timer_update_alarms(s);
+        break;
+    case TIMER_DBGPAUSE:
+        s->dbgpause = rp2040_atomic_update(s->dbgpause, value, alias) &
+                      TIMER_DBGPAUSE_MASK;
+        break;
+    case TIMER_PAUSE:
+        old = s->pause;
+        now_us = rp2040_timer_now_us(s);
+        s->pause = rp2040_atomic_update(s->pause, value, alias) &
+                   TIMER_PAUSE_MASK;
+        if (!(old & TIMER_PAUSE_MASK) && (s->pause & TIMER_PAUSE_MASK)) {
+            s->paused_time_us = now_us;
+        } else if ((old & TIMER_PAUSE_MASK) &&
+                   !(s->pause & TIMER_PAUSE_MASK)) {
+            s->time_offset_us = s->paused_time_us -
+                                qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) / 1000;
+            rp2040_timer_update_alarms(s);
+        }
+        break;
+    case TIMER_INTR:
+        s->intr &= ~(value & TIMER_ALARM_MASK);
+        rp2040_timer_update_irq(s);
+        break;
+    case TIMER_INTE:
+        s->inte = rp2040_atomic_update(s->inte, value, alias) &
+                  TIMER_ALARM_MASK;
+        rp2040_timer_update_irq(s);
+        break;
+    case TIMER_INTF:
+        s->intf = rp2040_atomic_update(s->intf, value, alias) &
+                  TIMER_ALARM_MASK;
+        rp2040_timer_update_irq(s);
+        break;
+    default:
+        if (rp2040_timer_alarm_offset(offset, &alarm)) {
+            s->alarm[alarm] = value;
+            s->armed |= BIT(alarm);
+            rp2040_timer_update_alarm(s, alarm);
+        } else {
+            qemu_log_mask(LOG_UNIMP,
+                      "%s: unimplemented write at offset 0x%"
+                      HWADDR_PRIx "\n", __func__, addr & 0xfff);
+        }
+        break;
+    }
+}
+
+static const MemoryRegionOps rp2040_timer_ops = {
+    .read = rp2040_timer_read,
+    .write = rp2040_timer_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+    },
+};
+
+static void rp2040_timer_alarm_tick(void *opaque)
+{
+    uintptr_t packed = (uintptr_t)opaque;
+    RP2040TimerState *s = (RP2040TimerState *)(packed & ~(uintptr_t)0x3);
+    unsigned alarm = packed & 0x3;
+
+    s->armed &= ~BIT(alarm);
+    s->intr |= BIT(alarm);
+    rp2040_timer_update_irq(s);
+}
+
+static void rp2040_timer_reset(DeviceState *dev)
+{
+    RP2040TimerState *s = RP2040_TIMER(dev);
+    int i;
+
+    s->time_offset_us = -qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) / 1000;
+    s->paused_time_us = 0;
+    memset(s->alarm, 0, sizeof(s->alarm));
+    s->armed = 0;
+    s->dbgpause = TIMER_DBGPAUSE_MASK;
+    s->pause = 0;
+    s->intr = 0;
+    s->inte = 0;
+    s->intf = 0;
+
+    for (i = 0; i < RP2040_TIMER_NUM_ALARMS; i++) {
+        timer_del(s->alarm_timer[i]);
+        qemu_set_irq(s->irq[i], 0);
+    }
+}
+
+static void rp2040_timer_init(Object *obj)
+{
+    RP2040TimerState *s = RP2040_TIMER(obj);
+
+    memory_region_init_io(&s->iomem, obj, &rp2040_timer_ops, s,
+                          "rp2040.timer", RP2040_TIMER_SIZE);
+    sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
+    sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq[0]);
+    sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq[1]);
+    sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq[2]);
+    sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq[3]);
+}
+
+static void rp2040_timer_realize(DeviceState *dev, Error **errp)
+{
+    RP2040TimerState *s = RP2040_TIMER(dev);
+    int i;
+
+    for (i = 0; i < RP2040_TIMER_NUM_ALARMS; i++) {
+        s->alarm_timer[i] =
+            timer_new_ns(QEMU_CLOCK_VIRTUAL, rp2040_timer_alarm_tick,
+                         (void *)((uintptr_t)s | i));
+    }
+}
+
+static const VMStateDescription rp2040_timer_vmstate = {
+    .name = TYPE_RP2040_TIMER,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (const VMStateField[]) {
+        VMSTATE_TIMER_PTR_ARRAY(alarm_timer, RP2040TimerState,
+                                RP2040_TIMER_NUM_ALARMS),
+        VMSTATE_INT64(time_offset_us, RP2040TimerState),
+        VMSTATE_UINT64(paused_time_us, RP2040TimerState),
+        VMSTATE_UINT32_ARRAY(alarm, RP2040TimerState,
+                             RP2040_TIMER_NUM_ALARMS),
+        VMSTATE_UINT32(armed, RP2040TimerState),
+        VMSTATE_UINT32(dbgpause, RP2040TimerState),
+        VMSTATE_UINT32(pause, RP2040TimerState),
+        VMSTATE_UINT32(intr, RP2040TimerState),
+        VMSTATE_UINT32(inte, RP2040TimerState),
+        VMSTATE_UINT32(intf, RP2040TimerState),
+        VMSTATE_END_OF_LIST()
+    },
+};
+
+static void rp2040_timer_class_init(ObjectClass *klass, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    dc->realize = rp2040_timer_realize;
+    device_class_set_legacy_reset(dc, rp2040_timer_reset);
+    dc->vmsd = &rp2040_timer_vmstate;
+}
+
+static const TypeInfo rp2040_timer_info = {
+    .name          = TYPE_RP2040_TIMER,
+    .parent        = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(RP2040TimerState),
+    .instance_init = rp2040_timer_init,
+    .class_init    = rp2040_timer_class_init,
+};
+
+static void rp2040_timer_register_types(void)
+{
+    type_register_static(&rp2040_timer_info);
+}
+
+type_init(rp2040_timer_register_types)
diff --git a/include/hw/arm/rp2040.h b/include/hw/arm/rp2040.h
index 9fcc50eb3e..788b821d0e 100644
--- a/include/hw/arm/rp2040.h
+++ b/include/hw/arm/rp2040.h
@@ -25,6 +25,7 @@
 #include "hw/misc/rp2040_sysinfo.h"
 #include "hw/misc/rp2040_syscfg.h"
 #include "hw/misc/rp2040_tbman.h"
+#include "hw/misc/rp2040_timer.h"
 #include "hw/misc/rp2040_vreg.h"
 #include "hw/misc/rp2040_watchdog.h"
 #include "hw/misc/rp2040_xosc.h"
@@ -64,6 +65,7 @@ struct RP2040State {
     RP2040RoscState rosc;
     RP2040SioState sio;
     RP2040TbmanState tbman;
+    RP2040TimerState timer;
     RP2040VregState vreg;
     RP2040WatchdogState watchdog;
     RP2040XoscState xosc;
diff --git a/include/hw/misc/rp2040_timer.h b/include/hw/misc/rp2040_timer.h
new file mode 100644
index 0000000000..b14bf75d53
--- /dev/null
+++ b/include/hw/misc/rp2040_timer.h
@@ -0,0 +1,39 @@
+/*
+ * RP2040 timer emulation
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_MISC_RP2040_TIMER_H
+#define HW_MISC_RP2040_TIMER_H
+
+#include "hw/core/sysbus.h"
+#include "qemu/timer.h"
+#include "qom/object.h"
+
+#define TYPE_RP2040_TIMER "rp2040-timer"
+OBJECT_DECLARE_SIMPLE_TYPE(RP2040TimerState, RP2040_TIMER)
+
+#define RP2040_TIMER_BASE 0x40054000
+#define RP2040_TIMER_SIZE 0x4000
+#define RP2040_TIMER_NUM_ALARMS 4
+
+struct RP2040TimerState {
+    SysBusDevice parent_obj;
+
+    MemoryRegion iomem;
+    qemu_irq irq[RP2040_TIMER_NUM_ALARMS];
+    QEMUTimer *alarm_timer[RP2040_TIMER_NUM_ALARMS];
+
+    int64_t time_offset_us;
+    uint64_t paused_time_us;
+    uint32_t alarm[RP2040_TIMER_NUM_ALARMS];
+    uint32_t armed;
+    uint32_t dbgpause;
+    uint32_t pause;
+    uint32_t intr;
+    uint32_t inte;
+    uint32_t intf;
+};
+
+#endif
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index fca71c8bef..e2ffb99b1f 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -274,6 +274,7 @@ qtests_arm = \
     'rp2040-rosc-test',
     'rp2040-sio-test',
     'rp2040-tbman-test',
+    'rp2040-timer-test',
     'rp2040-uart-test',
     'rp2040-vreg-test',
     'rp2040-watchdog-test'] : []) + \
diff --git a/tests/qtest/rp2040-timer-test.c b/tests/qtest/rp2040-timer-test.c
new file mode 100644
index 0000000000..35185415b5
--- /dev/null
+++ b/tests/qtest/rp2040-timer-test.c
@@ -0,0 +1,149 @@
+/*
+ * QTest testcase for the RP2040 timer.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+#include "qemu/bitops.h"
+
+#define TIMER_BASE          0x40054000
+#define TIMER_TIMEHW        0x00
+#define TIMER_TIMELW        0x04
+#define TIMER_TIMEHR        0x08
+#define TIMER_TIMELR        0x0c
+#define TIMER_ALARM0        0x10
+#define TIMER_ARMED         0x20
+#define TIMER_TIMERAWH      0x24
+#define TIMER_TIMERAWL      0x28
+#define TIMER_DBGPAUSE      0x2c
+#define TIMER_PAUSE         0x30
+#define TIMER_INTR          0x34
+#define TIMER_INTE          0x38
+#define TIMER_INTF          0x3c
+#define TIMER_INTS          0x40
+
+#define TIMER_SET_ALIAS     0x2000
+#define TIMER_CLR_ALIAS     0x3000
+#define TIMER_ALARM_MASK    0x0f
+#define TIMER_DBGPAUSE_MASK (BIT(2) | BIT(1))
+#define TIMER_PAUSE_MASK    BIT(0)
+#define NS_PER_US           1000
+
+static QTestState *rp2040_timer_start(void)
+{
+    return qtest_init("-machine raspi-pico");
+}
+
+static void test_reset_values(void)
+{
+    QTestState *qts = rp2040_timer_start();
+
+    g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_ARMED), ==, 0);
+    g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_DBGPAUSE), ==,
+                    TIMER_DBGPAUSE_MASK);
+    g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_PAUSE), ==, 0);
+    g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_INTR), ==, 0);
+    g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_INTE), ==, 0);
+    g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_INTF), ==, 0);
+    g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_INTS), ==, 0);
+    g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_TIMEHR), ==, 0);
+    g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_TIMERAWH), ==, 0);
+
+    qtest_quit(qts);
+}
+
+static void test_counter_and_pause(void)
+{
+    QTestState *qts = rp2040_timer_start();
+    uint32_t before = qtest_readl(qts, TIMER_BASE + TIMER_TIMERAWL);
+    uint32_t paused;
+
+    qtest_clock_step(qts, 123 * NS_PER_US);
+    g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_TIMERAWL) - before,
+                    ==, 123);
+
+    qtest_writel(qts, TIMER_BASE + TIMER_PAUSE, TIMER_PAUSE_MASK);
+    paused = qtest_readl(qts, TIMER_BASE + TIMER_TIMERAWL);
+    qtest_clock_step(qts, 100 * NS_PER_US);
+    g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_TIMERAWL), ==,
+                    paused);
+
+    qtest_writel(qts, TIMER_BASE + TIMER_TIMEHW, 0);
+    qtest_writel(qts, TIMER_BASE + TIMER_TIMELW, 0x12345678);
+    g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_TIMERAWL), ==,
+                    0x12345678);
+
+    qtest_writel(qts, TIMER_BASE + TIMER_CLR_ALIAS + TIMER_PAUSE,
+                 TIMER_PAUSE_MASK);
+    qtest_clock_step(qts, 10 * NS_PER_US);
+    g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_TIMELR), ==,
+                    0x12345682);
+
+    qtest_quit(qts);
+}
+
+static void test_alarm(void)
+{
+    QTestState *qts = rp2040_timer_start();
+    uint32_t now = qtest_readl(qts, TIMER_BASE + TIMER_TIMERAWL);
+
+    qtest_writel(qts, TIMER_BASE + TIMER_INTE, BIT(0));
+    qtest_writel(qts, TIMER_BASE + TIMER_ALARM0, now + 100);
+    g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_ARMED), ==, BIT(0));
+
+    qtest_clock_step(qts, 99 * NS_PER_US);
+    g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_INTR), ==, 0);
+    qtest_clock_step(qts, NS_PER_US);
+    g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_ARMED), ==, 0);
+    g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_INTR), ==, BIT(0));
+    g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_INTS), ==, BIT(0));
+
+    qtest_writel(qts, TIMER_BASE + TIMER_INTR, BIT(0));
+    g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_INTR), ==, 0);
+    g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_INTS), ==, 0);
+
+    qtest_quit(qts);
+}
+
+static void test_interrupt_force_and_masks(void)
+{
+    QTestState *qts = rp2040_timer_start();
+
+    qtest_writel(qts, TIMER_BASE + TIMER_SET_ALIAS + TIMER_INTE,
+                 BIT(2) | BIT(8));
+    qtest_writel(qts, TIMER_BASE + TIMER_SET_ALIAS + TIMER_INTF,
+                 BIT(2) | BIT(9));
+    g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_INTE), ==, BIT(2));
+    g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_INTF), ==, BIT(2));
+    g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_INTS), ==, BIT(2));
+
+    qtest_writel(qts, TIMER_BASE + TIMER_CLR_ALIAS + TIMER_INTF, BIT(2));
+    g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_INTF), ==, 0);
+    g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_INTS), ==, 0);
+
+    qtest_writel(qts, TIMER_BASE + TIMER_ARMED, TIMER_ALARM_MASK);
+    qtest_writel(qts, TIMER_BASE + TIMER_DBGPAUSE, UINT32_MAX);
+    qtest_writel(qts, TIMER_BASE + TIMER_PAUSE, UINT32_MAX);
+    g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_DBGPAUSE), ==,
+                    TIMER_DBGPAUSE_MASK);
+    g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_PAUSE), ==,
+                    TIMER_PAUSE_MASK);
+
+    qtest_quit(qts);
+}
+
+int main(int argc, char **argv)
+{
+    g_test_init(&argc, &argv, NULL);
+
+    qtest_add_func("/rp2040-timer/reset-values", test_reset_values);
+    qtest_add_func("/rp2040-timer/counter-and-pause",
+                   test_counter_and_pause);
+    qtest_add_func("/rp2040-timer/alarm", test_alarm);
+    qtest_add_func("/rp2040-timer/interrupt-force-and-masks",
+                   test_interrupt_force_and_masks);
+
+    return g_test_run();
+}
-- 
2.55.0


Reply via email to