Free-running 64-bit counter with compare match interrupt.
TIM0..TIM6 give shifted views of the counter, CMP0 fires
when the masked counter matches the programmed value.

Originally-by: Christoph Seitz <[email protected]>
Signed-off-by: Parthiban Nallathambi <[email protected]>
---
 hw/timer/Kconfig               |   4 +
 hw/timer/meson.build           |   2 +
 hw/timer/tricore_stm.c         | 328 +++++++++++++++++++++++++++++++++++++++++
 include/hw/timer/tricore_stm.h |  76 ++++++++++
 4 files changed, 410 insertions(+)

diff --git a/hw/timer/Kconfig b/hw/timer/Kconfig
index b3d823ce2c..250452b3fa 100644
--- a/hw/timer/Kconfig
+++ b/hw/timer/Kconfig
@@ -65,3 +65,7 @@ config STELLARIS_GPTM
 
 config AVR_TIMER16
     bool
+
+config TRICORE_STM
+    bool
+    select TRICORE_IRBUS
diff --git a/hw/timer/meson.build b/hw/timer/meson.build
index 201b5d8316..78d67a7e69 100644
--- a/hw/timer/meson.build
+++ b/hw/timer/meson.build
@@ -34,3 +34,5 @@ specific_ss.add(when: 'CONFIG_IBEX', if_true: 
files('ibex_timer.c'))
 system_ss.add(when: 'CONFIG_SIFIVE_PWM', if_true: files('sifive_pwm.c'))
 
 system_ss.add(when: 'CONFIG_AVR_TIMER16', if_true: files('avr_timer16.c'))
+
+system_ss.add(when: 'CONFIG_TRICORE_STM', if_true: files('tricore_stm.c'))
diff --git a/hw/timer/tricore_stm.c b/hw/timer/tricore_stm.c
new file mode 100644
index 0000000000..376e680bc7
--- /dev/null
+++ b/hw/timer/tricore_stm.c
@@ -0,0 +1,328 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * QEMU TriCore System Timer Module (STM)
+ *
+ * Copyright (c) 2024 Infineon Technologies AG
+ */
+
+#include "qemu/osdep.h"
+#include "hw/core/clock.h"
+#include "hw/core/irq.h"
+#include "hw/core/qdev-clock.h"
+#include "hw/timer/tricore_stm.h"
+#include "qemu/bitops.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qemu/timer.h"
+
+enum {
+    R_CLC,
+    R_RESERVED1,
+    R_ID,
+    R_RESERVED2,
+    R_TIM0,
+    R_TIM1,
+    R_TIM2,
+    R_TIM3,
+    R_TIM4,
+    R_TIM5,
+    R_TIM6,
+    R_CAP,
+    R_CMP0,
+    R_CMP1,
+    R_CMCON,
+    R_ICR,
+    R_ISCR,
+    R_RESERVED3,
+    R_TIM0SV  = 0x50 / 4,
+    R_CAPSV,
+    R_RESERVED4,
+    R_OCS     = 0xE8 / 4,
+    R_KRSTCLR,
+    R_KRST1,
+    R_KRST0,
+    R_ACCEN1,
+    R_ACCEN0,
+};
+
+static void tricore_stm_tim_update(TriCoreSTMState *s)
+{
+    int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+
+    s->tim_counter += clock_ns_to_ticks(s->fstm, now - s->tim_base_ns);
+    s->tim_base_ns = now;
+}
+
+static void tricore_stm_timer_update(TriCoreSTMState *s)
+{
+    uint32_t mstart = (s->regs[R_CMCON] & MASK_CMCON_MSTART0) >> 8;
+    uint32_t msize = s->regs[R_CMCON] & MASK_CMCON_MSIZE0;
+    uint32_t nbits = msize + 1;
+    uint64_t tim_target = s->tim_counter;
+
+    if (!(s->regs[R_ICR] & MASK_ICR_CMP0EN) &&
+        (s->regs[R_ICR] & MASK_ICR_CMP0IR)) {
+        return;
+    }
+
+    /* Calculate the target TIM value */
+    if (mstart) {
+        tim_target = deposit64(tim_target, 0, mstart, 0);
+    }
+    tim_target = deposit64(tim_target, mstart, nbits,
+                           (uint64_t)s->regs[R_CMP0]);
+
+    /* Wrap around if needed */
+    if (tim_target <= s->tim_counter) {
+        tim_target += (1ULL << (mstart + nbits));
+    }
+
+    timer_mod(s->timer,
+              s->tim_base_ns +
+              clock_ticks_to_ns(s->fstm, tim_target - s->tim_counter));
+}
+
+static void tricore_stm_clock_update(void *opaque, enum ClockEvent event)
+{
+    TriCoreSTMState *s = TRICORE_STM(opaque);
+
+    if (event == ClockPreUpdate) {
+        tricore_stm_tim_update(s);
+    } else {
+        tricore_stm_timer_update(s);
+    }
+}
+
+static void tricore_stm_write(void *opaque, hwaddr offset,
+                              uint64_t value, unsigned size)
+{
+    TriCoreSTMState *s = TRICORE_STM(opaque);
+    hwaddr reg_addr = offset >> 2;
+
+    switch (reg_addr) {
+    case R_CLC:
+    case R_ID:
+    case R_TIM0:
+    case R_TIM1:
+    case R_TIM2:
+    case R_TIM3:
+    case R_TIM4:
+    case R_TIM5:
+    case R_TIM6:
+    case R_CAP:
+        s->regs[reg_addr] = value;
+        break;
+    case R_CMP0:
+        s->regs[reg_addr] = value;
+        tricore_stm_tim_update(s);
+        tricore_stm_timer_update(s);
+        break;
+    case R_CMP1:
+    case R_ICR:
+        s->regs[reg_addr] = value;
+        tricore_stm_timer_update(s);
+        break;
+    case R_CMCON:
+        s->regs[reg_addr] = value;
+        tricore_stm_tim_update(s);
+        tricore_stm_timer_update(s);
+        break;
+    case R_ISCR:
+        if (value & MASK_ISCR_CMP0IRR) {
+            qatomic_and(&s->regs[R_ICR], ~MASK_ICR_CMP0IR);
+        }
+        if (value & MASK_ISCR_CMP1IRR) {
+            qatomic_and(&s->regs[R_ICR], ~MASK_ICR_CMP1IR);
+        }
+        if (value & MASK_ISCR_CMP0IRS) {
+            qatomic_or(&s->regs[R_ICR], MASK_ICR_CMP0IR);
+        }
+        if (value & MASK_ISCR_CMP1IRS) {
+            qatomic_or(&s->regs[R_ICR], MASK_ICR_CMP1IR);
+        }
+        tricore_stm_timer_update(s);
+        break;
+    case R_TIM0SV:
+    case R_CAPSV:
+    case R_OCS:
+    case R_KRSTCLR:
+    case R_KRST1:
+    case R_KRST0:
+    case R_ACCEN1:
+    case R_ACCEN0:
+        s->regs[reg_addr] = value;
+        break;
+    default:
+        qemu_log_mask(LOG_UNIMP,
+                      "tricore_stm: write to unknown register 0x%"
+                      HWADDR_PRIx "\n", offset);
+        break;
+    }
+}
+
+static uint64_t tricore_stm_read(void *opaque, hwaddr offset,
+                                 unsigned size)
+{
+    TriCoreSTMState *s = TRICORE_STM(opaque);
+    uint64_t r = 0;
+    hwaddr reg_addr = offset >> 2;
+
+    if ((reg_addr >= R_TIM0 && reg_addr <= R_TIM6) ||
+        reg_addr == R_TIM0SV) {
+        tricore_stm_tim_update(s);
+        s->regs[R_CAP] = (uint32_t)(s->tim_counter >> 32);
+    }
+
+    switch (reg_addr) {
+    case R_CLC:
+    case R_ID:
+        r = s->regs[reg_addr];
+        break;
+    case R_TIM0:
+    case R_TIM1:
+    case R_TIM2:
+    case R_TIM3:
+    case R_TIM4:
+    case R_TIM5:
+    case R_TIM6:
+        r = s->tim_counter << (reg_addr - R_TIM0) * 4;
+        break;
+    case R_CAP:
+        r = s->regs[R_CAP];
+        break;
+    case R_ISCR:
+        r = 0;
+        break;
+    case R_CMP0:
+    case R_CMP1:
+    case R_CMCON:
+    case R_ICR:
+        r = s->regs[reg_addr];
+        break;
+    case R_TIM0SV:
+        r = s->tim_counter;
+        break;
+    case R_CAPSV:
+        r = s->regs[R_CAP];
+        break;
+    case R_OCS:
+    case R_KRSTCLR:
+    case R_KRST1:
+    case R_KRST0:
+    case R_ACCEN1:
+    case R_ACCEN0:
+        r = s->regs[reg_addr];
+        break;
+    default:
+        qemu_log_mask(LOG_UNIMP,
+                      "tricore_stm: read from unknown register 0x%"
+                      HWADDR_PRIx "\n", offset);
+        r = 0;
+        break;
+    }
+
+    return r;
+}
+
+static void tricore_stm_reset(DeviceState *dev)
+{
+    TriCoreSTMState *s = TRICORE_STM(dev);
+
+    s->regs[R_CLC] = RESET_TRICORE_STM_CLC;
+    s->regs[R_ID] = RESET_TRICORE_STM_ID;
+    s->regs[R_TIM0] = RESET_TRICORE_STM_TIM0;
+    s->regs[R_TIM1] = RESET_TRICORE_STM_TIM1;
+    s->regs[R_TIM2] = RESET_TRICORE_STM_TIM2;
+    s->regs[R_TIM3] = RESET_TRICORE_STM_TIM3;
+    s->regs[R_TIM4] = RESET_TRICORE_STM_TIM4;
+    s->regs[R_TIM5] = RESET_TRICORE_STM_TIM5;
+    s->regs[R_TIM6] = RESET_TRICORE_STM_TIM6;
+    s->regs[R_CAP] = RESET_TRICORE_STM_CAP;
+    s->regs[R_CMP0] = RESET_TRICORE_STM_CMP0;
+    s->regs[R_CMP1] = RESET_TRICORE_STM_CMP1;
+    s->regs[R_CMCON] = RESET_TRICORE_STM_CMCON;
+    s->regs[R_ICR] = RESET_TRICORE_STM_ICR;
+    s->regs[R_ISCR] = RESET_TRICORE_STM_ISCR;
+    s->regs[R_TIM0SV] = RESET_TRICORE_STM_TIM0SV;
+    s->regs[R_CAPSV] = RESET_TRICORE_STM_CAPSV;
+    s->regs[R_OCS] = RESET_TRICORE_STM_OCS;
+    s->regs[R_KRSTCLR] = RESET_TRICORE_STM_KRSTCLR;
+    s->regs[R_KRST1] = RESET_TRICORE_STM_KRST1;
+    s->regs[R_KRST0] = RESET_TRICORE_STM_KRST0;
+    s->regs[R_ACCEN1] = RESET_TRICORE_STM_ACCEN1;
+    s->regs[R_ACCEN0] = RESET_TRICORE_STM_ACCEN0;
+
+    s->tim_counter = 0;
+    s->tim_base_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+}
+
+static const MemoryRegionOps tricore_stm_ops = {
+    .read = tricore_stm_read,
+    .write = tricore_stm_write,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+    },
+    .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void tricore_stm_timer_hit(void *opaque)
+{
+    TriCoreSTMState *s = TRICORE_STM(opaque);
+
+    qatomic_or(&s->regs[R_ICR], MASK_ICR_CMP0IR);
+
+    if (s->regs[R_ICR] & MASK_ICR_CMP0EN) {
+        qemu_irq_pulse(s->irq);
+    }
+
+    tricore_stm_tim_update(s);
+    tricore_stm_timer_update(s);
+}
+
+static void tricore_stm_realize(DeviceState *dev, Error **errp)
+{
+    TriCoreSTMState *s = TRICORE_STM(dev);
+    SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+
+    s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, tricore_stm_timer_hit, s);
+    s->tim_base_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+    s->tim_counter = 0;
+
+    sysbus_init_mmio(sbd, &s->iomem);
+    sysbus_init_irq(sbd, &s->irq);
+}
+
+static void tricore_stm_init(Object *obj)
+{
+    TriCoreSTMState *s = TRICORE_STM(obj);
+
+    memory_region_init_io(&s->iomem, OBJECT(s), &tricore_stm_ops, s,
+                          "tricore_stm", 0x100);
+    s->fstm = qdev_init_clock_in(DEVICE(s), "fstm",
+                                 tricore_stm_clock_update, s,
+                                 ClockPreUpdate | ClockUpdate);
+}
+
+static void tricore_stm_class_init(ObjectClass *klass, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    dc->legacy_reset = tricore_stm_reset;
+    dc->realize = tricore_stm_realize;
+}
+
+static const TypeInfo tricore_stm_info = {
+    .name = TYPE_TRICORE_STM,
+    .parent = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(TriCoreSTMState),
+    .instance_init = tricore_stm_init,
+    .class_init = tricore_stm_class_init,
+};
+
+static void tricore_stm_register_types(void)
+{
+    type_register_static(&tricore_stm_info);
+}
+
+type_init(tricore_stm_register_types)
diff --git a/include/hw/timer/tricore_stm.h b/include/hw/timer/tricore_stm.h
new file mode 100644
index 0000000000..48345f5181
--- /dev/null
+++ b/include/hw/timer/tricore_stm.h
@@ -0,0 +1,76 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * QEMU TriCore System Timer Module (STM)
+ *
+ * Copyright (c) 2024 Infineon Technologies AG
+ */
+
+#ifndef HW_TRICORE_STM_H
+#define HW_TRICORE_STM_H
+
+#include "hw/core/clock.h"
+#include "hw/core/sysbus.h"
+#include "qom/object.h"
+#include "qemu/timer.h"
+
+#define TYPE_TRICORE_STM "tricore_stm"
+OBJECT_DECLARE_SIMPLE_TYPE(TriCoreSTMState, TRICORE_STM)
+
+#define MASK_ICR_CMP0EN     0x01
+#define MASK_ICR_CMP0IR     0x02
+#define MASK_ICR_CMP1EN     0x10
+#define MASK_ICR_CMP1IR     0x20
+
+#define MASK_ISCR_CMP0IRR   0x1
+#define MASK_ISCR_CMP0IRS   0x2
+#define MASK_ISCR_CMP1IRR   0x4
+#define MASK_ISCR_CMP1IRS   0x8
+
+#define MASK_CMCON_MSIZE0   0x1F
+#define MASK_CMCON_MSTART0  0x1F00
+#define MASK_CMCON_MSIZE1   0x1F0000
+#define MASK_CMCON_MSTART1  0x1F000000
+
+#define STM_R_MAX           (0x100 / 4)
+
+/* Reset values */
+#define RESET_TRICORE_STM_CLC       0x0
+#define RESET_TRICORE_STM_ID        0x0000C000
+#define RESET_TRICORE_STM_TIM0      0x0
+#define RESET_TRICORE_STM_TIM1      0x0
+#define RESET_TRICORE_STM_TIM2      0x0
+#define RESET_TRICORE_STM_TIM3      0x0
+#define RESET_TRICORE_STM_TIM4      0x0
+#define RESET_TRICORE_STM_TIM5      0x0
+#define RESET_TRICORE_STM_TIM6      0x0
+#define RESET_TRICORE_STM_CAP       0x0
+#define RESET_TRICORE_STM_CMP0      0x0
+#define RESET_TRICORE_STM_CMP1      0x0
+#define RESET_TRICORE_STM_CMCON     0x0
+#define RESET_TRICORE_STM_ICR       0x0
+#define RESET_TRICORE_STM_ISCR      0x0
+#define RESET_TRICORE_STM_TIM0SV    0x0
+#define RESET_TRICORE_STM_CAPSV     0x0
+#define RESET_TRICORE_STM_OCS       0x0
+#define RESET_TRICORE_STM_KRSTCLR   0x0
+#define RESET_TRICORE_STM_KRST1     0x0
+#define RESET_TRICORE_STM_KRST0     0x0
+#define RESET_TRICORE_STM_ACCEN1    0x0
+#define RESET_TRICORE_STM_ACCEN0    0xFFFFFFFF
+
+struct TriCoreSTMState {
+    /* <private> */
+    SysBusDevice parent_obj;
+
+    /* <public> */
+    MemoryRegion iomem;
+    QEMUTimer *timer;
+    Clock *fstm;
+    qemu_irq irq;
+
+    uint32_t regs[STM_R_MAX];
+    uint64_t tim_counter;
+    int64_t tim_base_ns;
+};
+
+#endif /* HW_TRICORE_STM_H */

-- 
2.47.3


Reply via email to