Patchwork [v2,14/20] arm: add Faraday FTRTC011 RTC timer support

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

Comments

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

The FTRTC011 is a flexible, small-area, and low-power Real Time Clock (RTC).
It accepts two clock sources: APB bus clock (PCLK) and 32.768 kHz clock.
When the system enters the sleep mode, the PCLK clock will be gated by the
system while RTC will keep on counting. This mechanism promises the lowest
power consumption when the system is asleep.

It provides separate second, minute, hour, and day counters. The second
counter is toggled each second, the minute counter is toggled each minute,
the hour counter is toggled each hour, and the day counter is toggled each day.
Because the second counter consumes the most power when the system is asleep,
the second toggled bits have to be separated. This counter separation mechanism
reduces the total power consumption when the system is in the sleep mode.

The counter separation mechanism also reduces the complexity of the software.
The software does not have to calculate the minute, hour, or day information.
It only needs to read the counter values and calculate the current time.

The FTRTC011 provides a programmable auto-alarm function. When the second
auto-alarm function is turned on, the RTC will automatically trigger an
interrupt each second. The automatic minute and hour alarms can be turned on
as well. This function is useful for implementing a clock.

Signed-off-by: Kuo-Jung Su <dantesu@faraday-tech.com>
---
 hw/ftrtc011.c |  315 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 315 insertions(+)
 create mode 100644 hw/ftrtc011.c
Paul Brook - Jan. 25, 2013, 11:49 p.m.
> +    qemu_mod_timer(s->qtimer,
> +            qemu_get_clock_ns(vm_clock) + get_ticks_per_sec());

This will not work reliably.  You can not rely on timers triggering promptly.  
Plus you're loosing the time taken to execute the callback every tick.

Additionally you can calculate values on demand, and only trigger a timer tick 
when an interrupt is actually enabled.  You don't need high precision, so use 
timer_ms rather than timer_ns.

Paul

Patch

diff --git a/hw/ftrtc011.c b/hw/ftrtc011.c
new file mode 100644
index 0000000..71dd421
--- /dev/null
+++ b/hw/ftrtc011.c
@@ -0,0 +1,315 @@ 
+/*
+ * QEMU model of the FTRTC011 RTC 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_SEC         0x00
+#define REG_MIN         0x04
+#define REG_HOUR        0x08
+#define REG_DAY         0x0C
+
+#define REG_ALARM_SEC   0x10
+#define REG_ALARM_MIN   0x14
+#define REG_ALARM_HOUR  0x18
+
+#define REG_CR          0x20
+#define REG_WSEC        0x24
+#define REG_WMIN        0x28
+#define REG_WHOUR       0x2C
+#define REG_WDAY        0x30
+#define REG_ISR         0x34
+
+#define REG_REV         0x3C
+#define REG_CURRENT     0x44
+
+enum ftrtc011_irqpin {
+    IRQ_ALARM_LEVEL = 0,
+    IRQ_ALARM_EDGE,
+    IRQ_SEC,
+    IRQ_MIN,
+    IRQ_HOUR,
+    IRQ_DAY,
+};
+
+#define TYPE_FTRTC011   "ftrtc011"
+
+typedef struct Ftrtc011State {
+    SysBusDevice busdev;
+    MemoryRegion mmio;
+
+    qemu_irq irq[6];
+
+    QEMUTimer *qtimer;
+
+    uint8_t sec;
+    uint8_t min;
+    uint8_t hr;
+    uint32_t day;
+
+    uint8_t alarm_sec;
+    uint8_t alarm_min;
+    uint8_t alarm_hr;
+
+    uint32_t cr;
+    uint32_t isr;
+
+} ftrtc011_state;
+
+#define FTRTC011(obj) \
+    OBJECT_CHECK(ftrtc011_state, obj, TYPE_FTRTC011)
+
+/* Update interrupts.  */
+static void ftrtc011_update_irq(ftrtc011_state *s)
+{
+    uint32_t mask = ((s->cr >> 1) & 0x1f) & s->isr;
+
+    qemu_set_irq(s->irq[IRQ_ALARM_LEVEL], (mask & 0x10) ? 1 : 0);
+
+    if (mask) {
+        if (mask & 0x01) {
+            qemu_irq_pulse(s->irq[IRQ_SEC]);
+        }
+        if (mask & 0x02) {
+            qemu_irq_pulse(s->irq[IRQ_MIN]);
+        }
+        if (mask & 0x04) {
+            qemu_irq_pulse(s->irq[IRQ_HOUR]);
+        }
+        if (mask & 0x08) {
+            qemu_irq_pulse(s->irq[IRQ_DAY]);
+        }
+        if (mask & 0x10) {
+            qemu_irq_pulse(s->irq[IRQ_ALARM_EDGE]);
+        }
+    }
+}
+
+static uint64_t ftrtc011_mem_read(void *opaque, hwaddr addr, unsigned size)
+{
+    ftrtc011_state *s = FTRTC011(opaque);
+    uint32_t rc = 0;
+
+    switch (addr) {
+    case REG_SEC:
+        return s->sec;
+    case REG_MIN:
+        return s->min;
+    case REG_HOUR:
+        return s->hr;
+    case REG_DAY:
+        return s->day;
+    case REG_ALARM_SEC:
+        return s->alarm_sec;
+    case REG_ALARM_MIN:
+        return s->alarm_min;
+    case REG_ALARM_HOUR:
+        return s->alarm_hr;
+    case REG_CR:
+        return s->cr;
+    case REG_ISR:
+        return s->isr;
+    case REG_REV:
+        return 0x00010000;
+    case REG_CURRENT:
+        return (s->day << 17) | (s->hr << 12) | (s->min << 6) | (s->sec);
+    default:
+        break;
+    }
+
+    return rc;
+}
+
+static void ftrtc011_mem_write(void    *opaque,
+                               hwaddr   addr,
+                               uint64_t val,
+                               unsigned size)
+{
+    ftrtc011_state *s = FTRTC011(opaque);
+
+    switch (addr) {
+    case REG_ALARM_SEC:
+        s->alarm_sec = (uint32_t)val;
+        break;
+    case REG_ALARM_MIN:
+        s->alarm_min = (uint32_t)val;
+        break;
+    case REG_ALARM_HOUR:
+        s->alarm_hr = (uint32_t)val;
+        break;
+    case REG_WSEC:
+        s->sec = (uint32_t)val;
+        break;
+    case REG_WMIN:
+        s->min = (uint32_t)val;
+        break;
+    case REG_WHOUR:
+        s->hr = (uint32_t)val;
+        break;
+    case REG_WDAY:
+        s->day = (uint32_t)val;
+        break;
+    case REG_CR:
+        s->cr = (uint32_t)val;
+        if (s->cr & 0x01) {
+            qemu_mod_timer(s->qtimer,
+                    qemu_get_clock_ns(vm_clock) + get_ticks_per_sec());
+        } else {
+            qemu_del_timer(s->qtimer);
+        }
+        break;
+    case REG_ISR:
+        s->isr &= ~((uint32_t)val);
+        ftrtc011_update_irq(s);
+        break;
+    default:
+        break;
+    }
+}
+
+static const MemoryRegionOps ftrtc011_ops = {
+    .read  = ftrtc011_mem_read,
+    .write = ftrtc011_mem_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4
+    }
+};
+
+static void ftrtc011_timer_tick(void *opaque)
+{
+    ftrtc011_state *s = FTRTC011(opaque);
+
+    s->sec += 1;
+    if (s->cr & (1 << 1)) {
+        s->isr |= (1 << 0);
+    }
+
+    if (s->sec >= 60) {
+        s->sec = 0;
+        s->min += 1;
+        if (s->cr & (1 << 2)) {
+            s->isr |= (1 << 1);
+        }
+
+        if (s->min >= 60) {
+            s->min = 0;
+            s->hr += 1;
+            if (s->cr & (1 << 3)) {
+                s->isr |= (1 << 2);
+            }
+            if (s->hr >= 24) {
+                s->hr = 0;
+                s->day += 1;
+                if (s->cr & (1 << 4)) {
+                    s->isr |= (1 << 3);
+                }
+            }
+        }
+    }
+
+    /* alarm interrupt */
+    if (s->cr & (1 << 5)) {
+        if ((s->sec | (s->min << 8) | (s->hr << 16)) ==
+            (s->alarm_sec | (s->alarm_min << 8) | (s->alarm_hr << 16))) {
+            s->isr |= (1 << 4);
+        }
+    }
+
+    /* send interrupt signal */
+    ftrtc011_update_irq(s);
+
+    qemu_mod_timer(s->qtimer,
+            qemu_get_clock_ns(vm_clock) + get_ticks_per_sec());
+}
+
+static void ftrtc011_reset(DeviceState *ds)
+{
+    SysBusDevice *busdev = SYS_BUS_DEVICE(ds);
+    ftrtc011_state *s = FTRTC011(FROM_SYSBUS(ftrtc011_state, busdev));
+
+    s->sec = 0;
+    s->min = 0;
+    s->hr  = 0;
+    s->day = 0;
+    s->alarm_sec = 0;
+    s->alarm_min = 0;
+    s->alarm_hr  = 0;
+
+    ftrtc011_update_irq(s);
+}
+
+static int ftrtc011_init(SysBusDevice *dev)
+{
+    ftrtc011_state *s = FTRTC011(FROM_SYSBUS(ftrtc011_state, dev));
+
+    s->qtimer = qemu_new_timer_ns(vm_clock, ftrtc011_timer_tick, s);
+
+    memory_region_init_io(&s->mmio,
+                          &ftrtc011_ops,
+                          s,
+                          TYPE_FTRTC011,
+                          0x1000);
+    sysbus_init_mmio(dev, &s->mmio);
+    sysbus_init_irq(dev, &s->irq[IRQ_ALARM_LEVEL]);
+    sysbus_init_irq(dev, &s->irq[IRQ_ALARM_EDGE]);
+    sysbus_init_irq(dev, &s->irq[IRQ_SEC]);
+    sysbus_init_irq(dev, &s->irq[IRQ_MIN]);
+    sysbus_init_irq(dev, &s->irq[IRQ_HOUR]);
+    sysbus_init_irq(dev, &s->irq[IRQ_DAY]);
+
+    return 0;
+}
+
+static const VMStateDescription vmstate_ftrtc011 = {
+    .name = TYPE_FTRTC011,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .minimum_version_id_old = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT8(sec, ftrtc011_state),
+        VMSTATE_UINT8(min, ftrtc011_state),
+        VMSTATE_UINT8(hr, ftrtc011_state),
+        VMSTATE_UINT32(day, ftrtc011_state),
+        VMSTATE_UINT8(alarm_sec, ftrtc011_state),
+        VMSTATE_UINT8(alarm_min, ftrtc011_state),
+        VMSTATE_UINT8(alarm_hr, ftrtc011_state),
+        VMSTATE_UINT32(cr, ftrtc011_state),
+        VMSTATE_UINT32(isr, ftrtc011_state),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static void ftrtc011_class_init(ObjectClass *klass, void *data)
+{
+    SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    k->init     = ftrtc011_init;
+    dc->vmsd    = &vmstate_ftrtc011;
+    dc->reset   = ftrtc011_reset;
+    dc->no_user = 1;
+}
+
+static const TypeInfo ftrtc011_info = {
+    .name           = TYPE_FTRTC011,
+    .parent         = TYPE_SYS_BUS_DEVICE,
+    .instance_size  = sizeof(ftrtc011_state),
+    .class_init     = ftrtc011_class_init,
+};
+
+static void ftrtc011_register_types(void)
+{
+    type_register_static(&ftrtc011_info);
+}
+
+type_init(ftrtc011_register_types)