Patchwork [v2,15/20] arm: add Faraday FTWDT010 watchdog timer support

login
register
mail settings
Submitter Kuo-Jung Su
Date Jan. 25, 2013, 8:19 a.m.
Message ID <1359101996-11667-16-git-send-email-dantesu@gmail.com>
Download mbox | patch
Permalink /patch/215593/
State New
Headers show

Comments

Kuo-Jung Su - Jan. 25, 2013, 8:19 a.m.
From: Kuo-Jung Su <dantesu@faraday-tech.com>

The FTWDT010 is used to prevent system from infinite loop
while software gets trapped in the deadlock.

Under the normal operation, users should restart FTWDT010
at the regular intervals before counter counts down to 0.

If the counter does reach 0, FTWDT010 will try to reset
the system by generating one or a combination of signals,
system reset, system interrupt, and external interrupt.

Signed-off-by: Kuo-Jung Su <dantesu@faraday-tech.com>
---
 hw/ftwdt010.c |  213 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 213 insertions(+)
 create mode 100644 hw/ftwdt010.c

Patch

diff --git a/hw/ftwdt010.c b/hw/ftwdt010.c
new file mode 100644
index 0000000..3620f61
--- /dev/null
+++ b/hw/ftwdt010.c
@@ -0,0 +1,213 @@ 
+/*
+ * QEMU model of the FTWDT010 WatchDog Timer
+ *
+ * Copyright (C) 2012 Faraday Technology
+ * Written by Dante Su <dantesu@faraday-tech.com>
+ *
+ * This file is licensed under GNU GPL v2.
+ */
+
+#include "sysbus.h"
+#include "sysemu/sysemu.h"
+#include "qemu/timer.h"
+
+/* Hardware registers */
+#define REG_COUNTER     0x00    /* counter register */
+#define REG_LOAD        0x04    /* (re)load register */
+#define REG_RESTART     0x08    /* restart register */
+#define REG_CR          0x0C    /* control register */
+#define REG_SR          0x10    /* status register */
+#define REG_SCR         0x14    /* status clear register */
+#define REG_INTR_LEN    0x18    /* interrupt length register */
+#define REG_REV         0x1C
+
+#define TYPE_FTWDT010   "ftwdt010"
+
+typedef struct Ftwdt010State {
+    SysBusDevice busdev;
+    MemoryRegion mmio;
+
+    qemu_irq irq;
+
+    QEMUTimer *qtimer;
+
+    uint64_t timeout;
+    uint32_t load;
+    uint32_t cr;
+    uint32_t sr;
+
+    uint32_t freq;        /* desired source clock */
+    uint32_t step;        /* get_ticks_per_sec() / freq */
+    int running;
+} ftwdt010_state;
+
+#define FTWDT010(obj) \
+    OBJECT_CHECK(ftwdt010_state, obj, TYPE_FTWDT010)
+
+static uint64_t ftwdt010_mem_read(void *opaque, hwaddr addr, unsigned size)
+{
+    ftwdt010_state *s = FTWDT010(opaque);
+    uint32_t rc = 0;
+
+    switch (addr) {
+    case REG_COUNTER:
+        if (s->cr & 0x01) {
+            return (s->timeout - qemu_get_clock_ns(vm_clock)) / s->step;
+        } else {
+            return s->load;
+        }
+    case REG_LOAD:
+        return s->load;
+    case REG_CR:
+        return s->cr;
+    case REG_SR:
+        return s->sr;
+    case REG_REV:
+        return 0x00010601;
+    default:
+        break;
+    }
+
+    return rc;
+}
+
+static void ftwdt010_mem_write(void    *opaque,
+                               hwaddr   addr,
+                               uint64_t val,
+                               unsigned size)
+{
+    ftwdt010_state *s = FTWDT010(opaque);
+
+    switch (addr) {
+    case REG_LOAD:
+        s->load = (uint32_t)val;
+        break;
+    case REG_RESTART:
+        if ((s->cr & 0x01) && (val == 0x5ab9)) {
+            s->timeout = (uint64_t)s->step * (uint64_t)s->load
+                            + qemu_get_clock_ns(vm_clock);
+            qemu_mod_timer(s->qtimer, s->timeout);
+        }
+        break;
+    case REG_CR:
+        s->cr = (uint32_t)val;
+        if (s->cr & 0x01) {
+            if (!s->running) {
+                s->running = 1;
+                s->timeout = (uint64_t)s->step * (uint64_t)s->load
+                                + qemu_get_clock_ns(vm_clock);
+                qemu_mod_timer(s->qtimer, s->timeout);
+            }
+        } else {
+            s->running = 0;
+            qemu_del_timer(s->qtimer);
+        }
+        break;
+    case REG_SCR:
+        s->sr &= ~(uint32_t)(val & 0x01);
+        break;
+    default:
+        break;
+    }
+}
+
+static const MemoryRegionOps ftwdt010_ops = {
+    .read  = ftwdt010_mem_read,
+    .write = ftwdt010_mem_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4
+    }
+};
+
+static void ftwdt010_timer_tick(void *opaque)
+{
+    ftwdt010_state *s = FTWDT010(opaque);
+
+    s->sr = 1;
+
+    /* send interrupt signal */
+    qemu_set_irq(s->irq, (s->cr & (1 << 2)) ? 1 : 0);
+
+    /* send system reset */
+    if (s->cr & (1 << 1)) {
+        qemu_system_reset_request();
+    }
+}
+
+static void ftwdt010_reset(DeviceState *ds)
+{
+    SysBusDevice *busdev = SYS_BUS_DEVICE(ds);
+    ftwdt010_state *s = FTWDT010(FROM_SYSBUS(ftwdt010_state, busdev));
+
+    s->cr      = 0;
+    s->sr      = 0;
+    s->load    = 0x3ef1480;
+    s->timeout = 0;
+}
+
+static int ftwdt010_init(SysBusDevice *dev)
+{
+    ftwdt010_state *s = FTWDT010(FROM_SYSBUS(ftwdt010_state, dev));
+
+    s->step = (uint64_t)get_ticks_per_sec() / (uint64_t)s->freq;
+    s->qtimer = qemu_new_timer_ns(vm_clock, ftwdt010_timer_tick, s);
+
+    memory_region_init_io(&s->mmio,
+                          &ftwdt010_ops,
+                          s,
+                          TYPE_FTWDT010,
+                          0x1000);
+    sysbus_init_mmio(dev, &s->mmio);
+    sysbus_init_irq(dev, &s->irq);
+
+    return 0;
+}
+
+static const VMStateDescription vmstate_ftwdt010 = {
+    .name = TYPE_FTWDT010,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .minimum_version_id_old = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT64(timeout, ftwdt010_state),
+        VMSTATE_UINT32(freq, ftwdt010_state),
+        VMSTATE_UINT32(step, ftwdt010_state),
+        VMSTATE_UINT32(load, ftwdt010_state),
+        VMSTATE_UINT32(cr, ftwdt010_state),
+        VMSTATE_UINT32(sr, ftwdt010_state),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static Property ftwdt010_properties[] = {
+    DEFINE_PROP_UINT32("freq", ftwdt010_state, freq, 66000000),
+    DEFINE_PROP_END_OF_LIST(),
+};
+
+static void ftwdt010_class_init(ObjectClass *klass, void *data)
+{
+    SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    k->init     = ftwdt010_init;
+    dc->vmsd    = &vmstate_ftwdt010;
+    dc->props   = ftwdt010_properties;
+    dc->reset   = ftwdt010_reset;
+    dc->no_user = 1;
+}
+
+static const TypeInfo ftwdt010_info = {
+    .name           = TYPE_FTWDT010,
+    .parent         = TYPE_SYS_BUS_DEVICE,
+    .instance_size  = sizeof(ftwdt010_state),
+    .class_init     = ftwdt010_class_init,
+};
+
+static void ftwdt010_register_types(void)
+{
+    type_register_static(&ftwdt010_info);
+}
+
+type_init(ftwdt010_register_types)