Patchwork [v2,17/20] arm: add Faraday FTPWMTMR010 timer support

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

Comments

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

The FTPWMTMR010 is an APB device which provides up to 8 independent timers.
Each timer can use either the internal system clock (The PCLK of APB) or
external clock.

These timers can be used to generate internal interrupts to the CPU.
They can also be used to trigger DMA transfers. In addition, each timer
supports PWM (Pulse Width Modulation) function, which can generate PWM
signals for motor control or power level control.

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

Patch

diff --git a/hw/ftpwmtmr010.c b/hw/ftpwmtmr010.c
new file mode 100644
index 0000000..0857523
--- /dev/null
+++ b/hw/ftpwmtmr010.c
@@ -0,0 +1,244 @@ 
+/*
+ * Faraday FTPWMTMR010 Timer.
+ *
+ * Copyright (c) 2012 Faraday Technology
+ * Written by Dante Su <dantesu@faraday-tech.com>
+ *
+ * This code is licensed under GPL v2.
+ */
+
+#include "hw.h"
+#include "qdev.h"
+#include "qemu/timer.h"
+#include "sysemu/sysemu.h"
+#include "sysbus.h"
+
+#define TYPE_FTPWMTMR010        "ftpwmtmr010"
+#define TYPE_FTPWMTMR010_TIMER  "ftpwmtmr010_timer"
+
+typedef struct Ftpwmtmr010State ftpwmtmr010_state;
+
+typedef struct Ftpwmtmr010Timer {
+    uint32_t ctrl;
+    uint32_t cntb;
+    int id;
+    uint64_t timeout;
+    uint64_t countdown;
+    qemu_irq irq;
+    QEMUTimer *qtimer;
+    ftpwmtmr010_state *chip;
+} ftpwmtmr010_timer;
+
+struct Ftpwmtmr010State {
+    SysBusDevice busdev;
+
+    MemoryRegion iomem;
+    ftpwmtmr010_timer timer[8];
+    uint32_t freq;        /* desired source clock */
+    uint32_t step;        /* get_ticks_per_sec() / freq */
+    uint32_t stat;
+};
+
+#define FTPWMTMR010(obj) \
+    OBJECT_CHECK(ftpwmtmr010_state, obj, TYPE_FTPWMTMR010)
+
+static uint64_t ftpwmtmr010_mem_read(void *opaque, hwaddr addr, unsigned size)
+{
+    ftpwmtmr010_state *s = FTPWMTMR010(opaque);
+    ftpwmtmr010_timer *t;
+    uint64_t now = qemu_get_clock_ns(vm_clock);
+    uint64_t ret = 0;
+
+    if (addr == 0x00) {
+        ret = s->stat;
+    } else if (addr >= 0x10 && addr < 0x90) {
+        t = s->timer + (addr >> 4) - 1;
+        switch (addr & 0x0f) {
+        case 0x00:
+            return t->ctrl;
+        case 0x04:
+            return t->cntb;
+        case 0x0c:
+            if ((t->ctrl & 0x02) && (t->timeout > now)) {
+                ret = (t->timeout - now) / s->step;
+            }
+            break;
+        }
+    }
+
+    return ret;
+}
+
+static void ftpwmtmr010_mem_write(void    *opaque,
+                                  hwaddr   addr,
+                                  uint64_t val,
+                                  unsigned size)
+{
+    ftpwmtmr010_state *s = FTPWMTMR010(opaque);
+    ftpwmtmr010_timer *t;
+
+    if (addr == 0x00) {
+        int i;
+        s->stat &= ~((uint32_t)val & 0xffffffff);
+        for (i = 0; i < 8; ++i) {
+            if (val & (1 << i)) {
+                qemu_irq_lower(s->timer[i].irq);
+            }
+        }
+    } else if (addr >= 0x10 && addr < 0x90) {
+        t = s->timer + (addr >> 4) - 1;
+        switch (addr & 0x0f) {
+        case 0x00:
+            t->ctrl = (uint32_t)val;
+            if (t->ctrl & (1 << 2)) {
+                t->countdown = (uint64_t)t->cntb * (uint64_t)s->step;
+            }
+            if (t->ctrl & (1 << 1)) {
+                t->timeout = t->countdown + qemu_get_clock_ns(vm_clock);
+                qemu_mod_timer(t->qtimer, t->timeout);
+            }
+            break;
+        case 0x04:
+            t->cntb = (uint32_t)val;
+            break;
+        }
+    }
+}
+
+static const MemoryRegionOps ftpwmtmr010_ops = {
+    .read  = ftpwmtmr010_mem_read,
+    .write = ftpwmtmr010_mem_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void ftpwmtmr010_timer_tick(void *opaque)
+{
+    ftpwmtmr010_timer *t = opaque;
+    ftpwmtmr010_state *s = t->chip;
+
+    /* if the timer has been enabled/started */
+    if (!(t->ctrl & (1 << 1))) {
+        return;
+    }
+
+    /* if the interrupt enabled */
+    if (t->ctrl & (1 << 5)) {
+        s->stat |= 1 << t->id;
+        if (t->ctrl & (1 << 6)) {
+            qemu_irq_pulse(t->irq);
+        } else {
+            qemu_irq_raise(t->irq);
+        }
+    }
+
+    /* if auto-reload is enabled */
+    if (t->ctrl & (1 << 4)) {
+        t->timeout = t->countdown + qemu_get_clock_ns(vm_clock);
+        qemu_mod_timer(t->qtimer, t->timeout);
+    } else {
+        t->ctrl &= ~(1 << 1);
+    }
+}
+
+static void ftpwmtmr010_reset(DeviceState *ds)
+{
+    SysBusDevice *busdev = SYS_BUS_DEVICE(ds);
+    ftpwmtmr010_state *s = FTPWMTMR010(FROM_SYSBUS(ftpwmtmr010_state, busdev));
+    int i;
+
+    s->stat = 0;
+
+    for (i = 0; i < 8; ++i) {
+        s->timer[i].ctrl = 0;
+        s->timer[i].cntb = 0;
+        s->timer[i].timeout = 0;
+        qemu_irq_lower(s->timer[i].irq);
+        qemu_del_timer(s->timer[i].qtimer);
+    }
+}
+
+static int ftpwmtmr010_init(SysBusDevice *busdev)
+{
+    ftpwmtmr010_state *s = FTPWMTMR010(FROM_SYSBUS(ftpwmtmr010_state, busdev));
+    int i;
+
+    s->step = (uint64_t)get_ticks_per_sec() / (uint64_t)s->freq;
+
+    printf("qemu: ftpwmtmr010 freq=%d\n", s->freq);
+
+    memory_region_init_io(&s->iomem,
+                          &ftpwmtmr010_ops,
+                          s,
+                          TYPE_FTPWMTMR010,
+                          0x1000);
+    sysbus_init_mmio(busdev, &s->iomem);
+    for (i = 0; i < 8; ++i) {
+        s->timer[i].id = i;
+        s->timer[i].chip = s;
+        s->timer[i].qtimer = qemu_new_timer_ns(
+                                vm_clock,
+                                ftpwmtmr010_timer_tick,
+                                &s->timer[i]);
+        sysbus_init_irq(busdev, &s->timer[i].irq);
+    }
+
+    return 0;
+}
+
+static const VMStateDescription vmstate_ftpwmtmr010_timer = {
+    .name = TYPE_FTPWMTMR010_TIMER,
+    .version_id = 2,
+    .minimum_version_id = 2,
+    .minimum_version_id_old = 2,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT32(ctrl, ftpwmtmr010_timer),
+        VMSTATE_UINT32(cntb, ftpwmtmr010_timer),
+        VMSTATE_END_OF_LIST(),
+    },
+};
+
+static const VMStateDescription vmstate_ftpwmtmr010 = {
+    .name = TYPE_FTPWMTMR010,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .minimum_version_id_old = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT32(stat, ftpwmtmr010_state),
+        VMSTATE_UINT32(freq, ftpwmtmr010_state),
+        VMSTATE_UINT32(step, ftpwmtmr010_state),
+        VMSTATE_STRUCT_ARRAY(timer, ftpwmtmr010_state, 8, 1,
+                        vmstate_ftpwmtmr010_timer, ftpwmtmr010_timer),
+        VMSTATE_END_OF_LIST(),
+    }
+};
+
+static Property ftpwmtmr010_properties[] = {
+    DEFINE_PROP_UINT32("freq", ftpwmtmr010_state, freq, 66000000),
+    DEFINE_PROP_END_OF_LIST(),
+};
+
+static void ftpwmtmr010_class_init(ObjectClass *klass, void *data)
+{
+    SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    k->init     = ftpwmtmr010_init;
+    dc->vmsd    = &vmstate_ftpwmtmr010;
+    dc->props   = ftpwmtmr010_properties;
+    dc->reset   = ftpwmtmr010_reset;
+    dc->no_user = 1;
+}
+
+static const TypeInfo ftpwmtmr010_info = {
+    .name          = TYPE_FTPWMTMR010,
+    .parent        = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(ftpwmtmr010_state),
+    .class_init    = ftpwmtmr010_class_init,
+};
+
+static void ftpwmtmr010_register_types(void)
+{
+    type_register_static(&ftpwmtmr010_info);
+}
+
+type_init(ftpwmtmr010_register_types)