qemu-arm
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH] hw: timer: Add i.MX sysctr timer implementation


From: Daniel Baluta
Subject: [PATCH] hw: timer: Add i.MX sysctr timer implementation
Date: Wed, 7 Jul 2021 14:39:37 +0300

From: Viorica Mancas <vioricamancas@yahoo.com>

The System Counter (SYS_CTR) is a programmable system counter, which provides a
shared time base to multiple processors. It is intended for applications where 
the counter
is always powered on, and supports multiple unrelated clocks.

This system counter can be found on NXP i.MX8MN.

Signed-off-by: Viorica Mancas <vioricamancas@yahoo.com>
Signed-off-by: Daniel Baluta <daniel.baluta@nxp.com>
---
 hw/timer/imx_sysctr_timer.c         | 374 ++++++++++++++++++++++++++++
 hw/timer/meson.build                |   1 +
 include/hw/timer/imx_sysctr_timer.h |  84 +++++++
 3 files changed, 459 insertions(+)
 create mode 100644 hw/timer/imx_sysctr_timer.c
 create mode 100644 include/hw/timer/imx_sysctr_timer.h

diff --git a/hw/timer/imx_sysctr_timer.c b/hw/timer/imx_sysctr_timer.c
new file mode 100644
index 0000000000..728cde9c79
--- /dev/null
+++ b/hw/timer/imx_sysctr_timer.c
@@ -0,0 +1,374 @@
+/*
+ * QEMU NXP Sysctr Timer
+ *
+ * Author: Viorica Mancas <vioricamancas@yahoo.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to 
deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/sysbus.h"
+#include "hw/irq.h"
+#include "hw/ptimer.h"
+#include "hw/qdev-properties.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qom/object.h"
+#include "hw/timer/imx_sysctr_timer.h"
+
+#ifndef DEBUG_IMX_SYSCTR
+#define DEBUG_IMX_SYSCTR 0
+#endif
+
+#define DPRINTF(fmt, args...) \
+    do { \
+        if (DEBUG_IMX_SYSCTR) { \
+            fprintf(stderr, "[%s]%s: " fmt , TYPE_IMX_SYSCTR_TIMER, \
+                                             __func__, ##args); \
+        } \
+    } while (0)
+
+#define low(x) (x & 0xFFFFFFFFLL)
+#define high(x) (x >> 32)
+
+#define CLEAR_LOW_MASK (0xFFFFFFFFUL << 32)
+#define CLEAR_HIGH_MASK (0xFFFFFFFF)
+
+static const char *imx_sysctr_timer_reg_name(uint32_t reg);
+
+/* The system counter counts up and the ptimer counts down */
+static uint64_t imx_sysctr_update_count(IMXSysctrState *s)
+{
+    s->cnt = s->next_timeout - ptimer_get_count(s->timer);
+
+    return s->cnt;
+}
+
+static void imx_sysctr_update_int(IMXSysctrState *s)
+{
+    if (!(s->cmpcr & SYSCTR_CMPCR_IMASK) && (s->cmpcr & SYSCTR_CMPCR_EN)) {
+        qemu_irq_raise(s->irq);
+    } else {
+        qemu_irq_lower(s->irq);
+    }
+}
+
+/* Must be called from within ptimer_transaction_begin/commit block */
+static void imx_sysctr_compute_next_timeout(IMXSysctrState *s, bool event)
+{
+    uint64_t timeout = SYSCTR_TIMER_MAX;
+    uint64_t count;
+    uint64_t cmp_reg;
+    long long limit;
+
+    if (!(s->cmpcr & SYSCTR_CMPCR_EN)) {
+        /* if not enabled just return */
+        return;
+    }
+
+    /* update the count */
+    count = imx_sysctr_update_count(s);
+
+    if (event) {
+        /*
+         * This is an event (the ptimer reached 0 and stopped), and the
+         * timer counter is now equal to s->next_timeout.
+         * 
+         * The clock counts to overflow unless it is reset.
+         */
+        if (count == SYSCTR_TIMER_MAX) {
+            /* We reached SYSCTR_TIMER_MAX so we need to rollover */
+            count = s->cnt = s->next_timeout = 0;
+        }
+    }
+
+    /* now, find the next timeout related to count */
+    cmp_reg = s->cmpcv;
+    if ((count < cmp_reg) && (timeout > cmp_reg)) {
+        timeout = cmp_reg;
+    }
+
+    /* the new range to count down from */
+    limit = timeout - imx_sysctr_update_count(s);
+
+    if (limit < 0) {
+        /*
+         * if we reach here, then QEMU is running too slow and we pass the
+         * timeout limit while computing it. Let's deliver the interrupt
+         * and compute a new limit.
+         */
+        imx_sysctr_compute_next_timeout(s, true);
+
+        s->cmpcr |= SYSCTR_CMPCR_ISTAT;
+        /* send the irq */
+        imx_sysctr_update_int(s);
+    } else {
+        /* New timeout value */
+        s->next_timeout = timeout;
+
+        /* reset the limit to the computed range */
+        ptimer_set_limit(s->timer, limit, 1);
+    }
+}
+
+static void sysctr_compare_value_update(IMXSysctrState *s) {
+    ptimer_transaction_begin(s->timer);
+
+    /* compute the new timeout */
+    imx_sysctr_compute_next_timeout(s, false);
+    ptimer_transaction_commit(s->timer);
+}
+
+static uint64_t
+timer_read(void *opaque, hwaddr offset, unsigned int size)
+{
+    IMXSysctrState *s = IMX_SYSCTR_TIMER(opaque);
+    uint32_t reg_value = 0;
+
+    switch (offset) {
+    case CNTCV_LO: /* Counter Count Value Low Register */
+        imx_sysctr_update_count(s);
+        reg_value = low(s->cnt);
+        break;
+
+    case CNTCV_HI: /* Counter Count Value High Register */
+        imx_sysctr_update_count(s);
+        reg_value = high(s->cnt);
+        break;
+
+    case CMPCR: /* Compare Control Register 0*/
+        reg_value = s->cmpcr;
+        break;
+
+    case CMPCV_LO: /* Compare Count Value Low Register 0 */
+        reg_value = low(s->cmpcv);
+        break;
+
+    case CMPCV_HI: /* Compare Count Value High Register 0 */
+        reg_value = high(s->cmpcv);
+        break;
+    
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                  "Guest read from unimplemented/invalid register\n");
+        break;
+    }
+
+    DPRINTF("(%s) = 0x%08x\n", imx_sysctr_timer_reg_name(offset), reg_value);
+
+    return reg_value;
+}
+
+static void handle_control_register_update(IMXSysctrState *s, uint32_t oldreg)
+{
+    ptimer_transaction_begin(s->timer);
+
+    if (s->cmpcr & SYSCTR_CMPCR_EN) {
+        /* if enabled we want the timer to count until overflow */
+        s->next_timeout = SYSCTR_TIMER_MAX;
+        ptimer_set_count(s->timer, SYSCTR_TIMER_MAX);
+        /* update the cnt value */
+        imx_sysctr_compute_next_timeout(s, false);
+        ptimer_run(s->timer, 1);
+    } else {
+        /* Clearing the enable bit (EN=0) will clear the status bit
+        * (ISTAT=0) and will negate the interrupt output signal.
+        */
+        s->cmpcr &= ~SYSCTR_CMPCR_ISTAT;
+        qemu_irq_lower(s->irq);
+        /* stop timer */
+        ptimer_stop(s->timer);
+    }
+    ptimer_transaction_commit(s->timer);
+}
+
+static void
+timer_write(void *opaque, hwaddr offset,
+            uint64_t val64, unsigned int size)
+{
+    IMXSysctrState *s = IMX_SYSCTR_TIMER(opaque);
+    uint32_t value = val64, oldreg;
+  
+    switch (offset) {
+    case CNTCV_LO: /* Counter Count Value Low Register */
+        qemu_log_mask(LOG_GUEST_ERROR,
+                  "Guest write to read only register\n");
+        break;
+
+    case CNTCV_HI: /* Counter Count Value High Register */
+        qemu_log_mask(LOG_GUEST_ERROR,
+                  "Guest write to read only register\n");
+        break;
+
+    case CMPCR: /* Compare Control Register 0*/
+        oldreg = s->cmpcr;
+        /* the CMPCR_ISTAT bit is read only */
+        s->cmpcr &= ~CMPCR_WRITE;
+        s->cmpcr |= value & CMPCR_WRITE;
+
+        DPRINTF("the value of cmpcr is now: 0x%"PRIx32"\n", s->cmpcr);
+
+        handle_control_register_update(s, oldreg);
+        break;
+
+    case CMPCV_LO: /* Compare Count Value Low Register 0 */
+        s->cmpcv &= CLEAR_LOW_MASK;
+        s->cmpcv |= value;
+
+        DPRINTF("the value of cmpcv is now: 0x%"PRIx64"\n", s->cmpcv);
+
+        sysctr_compare_value_update(s);
+        break;
+
+    case CMPCV_HI: /* Compare Count Value High Register 0 */
+        s->cmpcv &= CLEAR_HIGH_MASK;
+        value &= 0x00FFFFFF;
+        s->cmpcv |= ((uint64_t) value) << 32;
+
+        DPRINTF("the value of cmpcv is now: 0x%"PRIx64"\n", s->cmpcv);
+
+        sysctr_compare_value_update(s);
+        break;
+    
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                  "Guest write to invalid register\n");
+        break;
+    }
+}
+
+static void imx_sysctr_timer_init(Object *obj)
+{
+    IMXSysctrState *t = IMX_SYSCTR_TIMER(obj);
+
+    sysbus_init_irq(SYS_BUS_DEVICE(obj), &t->irq);
+}
+
+static const MemoryRegionOps timer_ops = {
+    .read = timer_read,
+    .write = timer_write,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4
+    }
+};
+
+
+static void imx_sysctr_timeout(void *opaque)
+{
+    IMXSysctrState *s = IMX_SYSCTR_TIMER(opaque);
+
+    /* continue counting up */
+    imx_sysctr_compute_next_timeout(s, true);
+
+    s->cmpcr |= SYSCTR_CMPCR_ISTAT;
+    /* send the irq */
+    imx_sysctr_update_int(s);
+
+    if ((s->cmpcr & SYSCTR_CMPCR_EN)) {
+        ptimer_run(s->timer, 1);
+    }
+}
+
+static void imx_sysctr_timer_realize(DeviceState *dev, Error **errp)
+{
+    IMXSysctrState *s = IMX_SYSCTR_TIMER(dev);
+
+    memory_region_init_io(&s->iomem,
+                            OBJECT(s),
+                            &timer_ops,
+                            s,
+                            TYPE_IMX_SYSCTR_TIMER,
+                            0x20000);
+    sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->iomem);
+
+    s->timer = ptimer_init(imx_sysctr_timeout, s, PTIMER_POLICY_DEFAULT);
+
+    /* the default freq is 24Mhz and divided by 3*/
+    ptimer_transaction_begin(s->timer);
+    ptimer_set_freq(s->timer, 24000000 / 3);
+    ptimer_transaction_commit(s->timer);
+}
+
+static void imx_sysctr_timer_reset(DeviceState *dev)
+{
+    IMXSysctrState *s = IMX_SYSCTR_TIMER(dev);
+
+    ptimer_transaction_begin(s->timer);
+    /* stop timer */
+    ptimer_stop(s->timer);
+    s->cmpcr = 0;
+    s->cmpcv = 0;
+    s->cnt = 0;
+
+    s->next_timeout = SYSCTR_TIMER_MAX;
+
+    ptimer_set_limit(s->timer, SYSCTR_TIMER_MAX, 1);
+
+    /* if the timer is still enabled, restart it */
+    if ((s->cmpcr & SYSCTR_CMPCR_EN)) {
+        ptimer_run(s->timer, 1);
+    }
+    ptimer_transaction_commit(s->timer);
+
+    DPRINTF("System counter was reset\n");
+
+}
+
+static void imx_sysctr_timer_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    dc->realize = imx_sysctr_timer_realize;
+    dc->reset = imx_sysctr_timer_reset;
+}
+
+static const TypeInfo imx_sysctr_timer_info = {
+    .name          = TYPE_IMX_SYSCTR_TIMER,
+    .parent        = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(IMXSysctrState),
+    .instance_init = imx_sysctr_timer_init,
+    .class_init    = imx_sysctr_timer_class_init,
+};
+
+static void imx_sysctr_timer_register_types(void)
+{
+    type_register_static(&imx_sysctr_timer_info);
+}
+
+type_init(imx_sysctr_timer_register_types)
+
+static const char *imx_sysctr_timer_reg_name(uint32_t reg)
+{
+    switch (reg) {
+    case CMPCR:
+        return "CMPCR";
+    case CMPCV_LO:
+        return "CMPCV_LO";
+    case CMPCV_HI:
+        return "CMPCV_HI";
+    case CNTCV_HI:
+        return "CNTCV_HI";
+    case CNTCV_LO:
+        return "CNTCV_LO";
+    default:
+        return "[?]";
+    }
+}
\ No newline at end of file
diff --git a/hw/timer/meson.build b/hw/timer/meson.build
index 598d058506..b6cd52a0b1 100644
--- a/hw/timer/meson.build
+++ b/hw/timer/meson.build
@@ -19,6 +19,7 @@ softmmu_ss.add(when: 'CONFIG_HPET', if_true: files('hpet.c'))
 softmmu_ss.add(when: 'CONFIG_I8254', if_true: files('i8254_common.c', 
'i8254.c'))
 softmmu_ss.add(when: 'CONFIG_IMX', if_true: files('imx_epit.c'))
 softmmu_ss.add(when: 'CONFIG_IMX', if_true: files('imx_gpt.c'))
+softmmu_ss.add(when: 'CONFIG_IMX', if_true: files('imx_sysctr_timer.c'))
 softmmu_ss.add(when: 'CONFIG_LM32_DEVICES', if_true: files('lm32_timer.c'))
 softmmu_ss.add(when: 'CONFIG_MILKYMIST', if_true: files('milkymist-sysctl.c'))
 softmmu_ss.add(when: 'CONFIG_MIPS_CPS', if_true: files('mips_gictimer.c'))
diff --git a/include/hw/timer/imx_sysctr_timer.h 
b/include/hw/timer/imx_sysctr_timer.h
new file mode 100644
index 0000000000..c36ae9b393
--- /dev/null
+++ b/include/hw/timer/imx_sysctr_timer.h
@@ -0,0 +1,84 @@
+/*
+ * QEMU NXP Sysctr Timer
+ *
+ * Author: Viorica Mancas <vioricamancas@yahoo.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to 
deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#ifndef IMX_SYSCTR_H
+#define IMX_SYSCTR_H
+
+#include "hw/sysbus.h"
+#include "hw/ptimer.h"
+#include "hw/misc/imx_ccm.h"
+#include "qom/object.h"
+
+/*
+ * sysctr : System counter
+ *
+ * The System Counter inputs two counter clock sources and outputs a counter
+ * value and interrupt signals (one per compare frame) to the platform’s
+ * interrupt controller.
+ */
+
+/* The counter in the timer is 56 bits (first 8 are 0) */
+#define SYSCTR_TIMER_MAX  0X00FFFFFFFFFFFFFFUL
+
+/* addresses */
+#define CMP_OFFSET     0x10000
+
+#define CNTCV_LO       0x8
+#define CNTCV_HI       0xc
+#define CMPCV_LO       (CMP_OFFSET + 0x20)
+#define CMPCV_HI       (CMP_OFFSET + 0x24)
+#define CMPCR          (CMP_OFFSET + 0x2c)
+
+/* Control register.  Not all of these bits have any effect (yet) */
+#define SYSCTR_CMPCR_EN        (1 << 0)  /*  Enable */
+#define SYSCTR_CMPCR_IMASK     (1 << 1)  /*  Enable */
+#define SYSCTR_CMPCR_ISTAT     (1 << 2)  /*  Compare (interrupt) status (read 
only) */
+/* interupt condition: ISTAT = (CNTCV >= CMPCV) */
+
+#define CMPCR_WRITE (SYSCTR_CMPCR_IMASK | SYSCTR_CMPCR_EN)
+
+#define TYPE_IMX_SYSCTR_TIMER "imx.sysctr-timer"
+
+typedef struct IMXSysctrState IMXSysctrState;
+DECLARE_INSTANCE_CHECKER(IMXSysctrState, IMX_SYSCTR_TIMER,
+                         TYPE_IMX_SYSCTR_TIMER)
+
+struct IMXSysctrState {
+    /*< private >*/
+    SysBusDevice parent_obj;
+
+    /*< public >*/
+    ptimer_state *timer;
+    MemoryRegion  iomem;
+    
+    qemu_irq irq;
+
+    uint32_t cmpcr; /* Compare Control Register */
+    uint64_t cnt;   /* Counter on 56 bits */
+    uint64_t cmpcv; /* Compare Count Value */
+
+    uint64_t next_timeout;
+};
+
+#endif /* IMX_SYSCTR_H */
-- 
2.20.1




reply via email to

[Prev in Thread] Current Thread [Next in Thread]