@@ -41,3 +41,4 @@ obj-y += ftintc020.o
obj-y += fttmr010.o
obj-y += ftpwmtmr010.o
obj-y += ftwdt010.o
+obj-y += ftrtc011.o
@@ -97,6 +97,16 @@ a369_device_init(A369State *s)
/* ftwdt010 */
sysbus_create_simple("ftwdt010", 0x92200000, pic[46]);
qemu_register_reset(a369_board_reset, s);
+
+ /* ftrtc011 */
+ sysbus_create_varargs("ftrtc011",
+ 0x92100000,
+ pic[0], /* Alarm (Level): NC in A369 */
+ pic[42], /* Alarm (Edge) */
+ pic[43], /* Second (Edge) */
+ pic[44], /* Minute (Edge) */
+ pic[45], /* Hour (Edge) */
+ NULL);
}
static void
new file mode 100644
@@ -0,0 +1,406 @@
+/*
+ * 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 <hw/sysbus.h>
+#include <qemu/timer.h>
+#include <sysemu/sysemu.h>
+
+#include "ftrtc011.h"
+
+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;
+ int64_t rtc_start;
+
+ /* HW register caches */
+
+ uint8_t sec;
+ uint8_t min;
+ uint8_t hr;
+ uint16_t day;
+ uint8_t wsec;
+ uint8_t wmin;
+ uint8_t whr;
+ uint16_t wday;
+
+ uint8_t alarm_sec;
+ uint8_t alarm_min;
+ uint8_t alarm_hr;
+
+ uint32_t cr;
+ uint32_t isr;
+} Ftrtc011State;
+
+#define FTRTC011(obj) \
+ OBJECT_CHECK(Ftrtc011State, obj, TYPE_FTRTC011)
+
+/* Update interrupts. */
+static void ftrtc011_update_irq(Ftrtc011State *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 void ftrtc011_timer_resync(Ftrtc011State *s)
+{
+ int64_t elapsed = s->sec
+ + (60 * s->min)
+ + (3600 * s->hr)
+ + (86400 * s->day);
+ s->rtc_start = get_clock_realtime() - elapsed * 1000000000LL;
+}
+
+static void ftrtc011_timer_update(Ftrtc011State *s)
+{
+ int64_t elapsed;
+ uint8_t sec, min, hr;
+ uint32_t day;
+
+ /*
+ * Although the timer is supposed to tick per second,
+ * there is no guarantee that the tick interval is
+ * exactly 1 second, and the system might even be
+ * suspend/resume which cause large time drift here.
+ */
+ elapsed = (get_clock_realtime() - s->rtc_start) / 1000000000LL;
+ sec = (uint8_t)(elapsed % 60LL);
+ min = (uint8_t)((elapsed / 60LL) % 60LL);
+ hr = (uint8_t)((elapsed / 3600LL) % 24LL);
+ day = (uint32_t)(elapsed / 86400LL);
+
+ /* sec interrupt */
+ if ((s->sec != sec) && (s->cr & (1 << 1))) {
+ s->isr |= (1 << 0);
+ }
+ /* min interrupt */
+ if ((s->min != min) && (s->cr & (1 << 2))) {
+ s->isr |= (1 << 1);
+ }
+ /* hr interrupt */
+ if ((s->hr != hr) && (s->cr & (1 << 3))) {
+ s->isr |= (1 << 2);
+ }
+ /* day interrupt */
+ if ((s->day != day) && (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);
+
+ /* update RTC registers */
+ s->sec = (uint8_t)sec;
+ s->min = (uint8_t)min;
+ s->hr = (uint8_t)hr;
+ s->day = (uint16_t)day;
+}
+
+static uint64_t ftrtc011_mem_read(void *opaque, hwaddr addr, unsigned size)
+{
+ Ftrtc011State *s = FTRTC011(opaque);
+ uint32_t rc = 0;
+
+ switch (addr) {
+ case REG_SEC:
+ ftrtc011_timer_update(s);
+ return s->sec;
+ case REG_MIN:
+ ftrtc011_timer_update(s);
+ return s->min;
+ case REG_HOUR:
+ ftrtc011_timer_update(s);
+ return s->hr;
+ case REG_DAY:
+ ftrtc011_timer_update(s);
+ 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:
+ ftrtc011_timer_update(s);
+ 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)
+{
+ Ftrtc011State *s = FTRTC011(opaque);
+
+ switch (addr) {
+ case REG_ALARM_SEC:
+ s->alarm_sec = (uint8_t)val;
+ break;
+ case REG_ALARM_MIN:
+ s->alarm_min = (uint8_t)val;
+ break;
+ case REG_ALARM_HOUR:
+ s->alarm_hr = (uint8_t)val;
+ break;
+ case REG_WSEC:
+ s->wsec = (uint8_t)val;
+ if (s->wsec > 59) {
+ hw_error("ftrtc011: %d is an invalid value for SEC\n", s->wsec);
+ exit(1);
+ }
+ break;
+ case REG_WMIN:
+ s->wmin = (uint8_t)val;
+ if (s->wmin > 59) {
+ hw_error("ftrtc011: %d is an invalid value for MIN\n", s->wmin);
+ exit(1);
+ }
+ break;
+ case REG_WHOUR:
+ s->whr = (uint8_t)val;
+ if (s->wmin > 23) {
+ hw_error("ftrtc011: %d is an invalid value for HOUR\n", s->whr);
+ exit(1);
+ }
+ break;
+ case REG_WDAY:
+ s->wday = (uint16_t)val;
+ break;
+ case REG_CR:
+ /* update the RTC counter with the user supplied values */
+ if (val & 0x40) {
+ s->sec = s->wsec;
+ s->min = s->wmin;
+ s->hr = s->whr;
+ s->day = s->wday;
+ val &= ~0x40;
+ ftrtc011_timer_resync(s);
+ }
+ /* check if RTC enabled */
+ if (val & 0x01) {
+ if (!(s->cr & 0x01)) {
+ ftrtc011_timer_resync(s);
+ }
+ qemu_mod_timer(s->qtimer, qemu_get_clock_ms(rt_clock) + 1000);
+ } else {
+ qemu_del_timer(s->qtimer);
+ }
+ s->cr = (uint32_t)val;
+ 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)
+{
+ Ftrtc011State *s = FTRTC011(opaque);
+ ftrtc011_timer_update(s);
+ qemu_mod_timer(s->qtimer, qemu_get_clock_ms(rt_clock) + 1000);
+}
+
+static void ftrtc011_reset(DeviceState *ds)
+{
+ SysBusDevice *busdev = SYS_BUS_DEVICE(ds);
+ Ftrtc011State *s = FTRTC011(FROM_SYSBUS(Ftrtc011State, busdev));
+
+ s->sec = 0;
+ s->min = 0;
+ s->hr = 0;
+ s->day = 0;
+ s->wsec = 0;
+ s->wmin = 0;
+ s->whr = 0;
+ s->wday = 0;
+ s->alarm_sec = 0;
+ s->alarm_min = 0;
+ s->alarm_hr = 0;
+
+ ftrtc011_update_irq(s);
+}
+
+static void ftrtc011_save(QEMUFile *f, void *opaque)
+{
+ Ftrtc011State *s = FTRTC011(opaque);
+
+ qemu_put_sbe64s(f, &s->rtc_start);
+ qemu_put_be32s(f, &s->cr);
+ qemu_put_be32s(f, &s->isr);
+ qemu_put_8s(f, &s->sec);
+ qemu_put_8s(f, &s->min);
+ qemu_put_8s(f, &s->hr);
+ qemu_put_be16s(f, &s->day);
+ qemu_put_8s(f, &s->wsec);
+ qemu_put_8s(f, &s->wmin);
+ qemu_put_8s(f, &s->whr);
+ qemu_put_be16s(f, &s->wday);
+ qemu_put_8s(f, &s->alarm_sec);
+ qemu_put_8s(f, &s->alarm_min);
+ qemu_put_8s(f, &s->alarm_hr);
+}
+
+static int ftrtc011_load(QEMUFile *f, void *opaque, int version_id)
+{
+ Ftrtc011State *s = FTRTC011(opaque);
+
+ qemu_get_sbe64s(f, &s->rtc_start);
+ qemu_get_be32s(f, &s->cr);
+ qemu_get_be32s(f, &s->isr);
+ qemu_get_8s(f, &s->sec);
+ qemu_get_8s(f, &s->min);
+ qemu_get_8s(f, &s->hr);
+ qemu_get_be16s(f, &s->day);
+ qemu_get_8s(f, &s->wsec);
+ qemu_get_8s(f, &s->wmin);
+ qemu_get_8s(f, &s->whr);
+ qemu_get_be16s(f, &s->wday);
+ qemu_get_8s(f, &s->alarm_sec);
+ qemu_get_8s(f, &s->alarm_min);
+ qemu_get_8s(f, &s->alarm_hr);
+
+ return 0;
+}
+
+static int ftrtc011_init(SysBusDevice *dev)
+{
+ Ftrtc011State *s = FTRTC011(FROM_SYSBUS(Ftrtc011State, dev));
+
+ s->qtimer = qemu_new_timer_ms(rt_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]);
+
+ register_savevm(&dev->qdev, TYPE_FTRTC011, -1, 0,
+ ftrtc011_save, ftrtc011_load, s);
+
+ 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, Ftrtc011State),
+ VMSTATE_UINT8(min, Ftrtc011State),
+ VMSTATE_UINT8(hr, Ftrtc011State),
+ VMSTATE_UINT16(day, Ftrtc011State),
+ VMSTATE_UINT8(wsec, Ftrtc011State),
+ VMSTATE_UINT8(wmin, Ftrtc011State),
+ VMSTATE_UINT8(whr, Ftrtc011State),
+ VMSTATE_UINT16(wday, Ftrtc011State),
+ VMSTATE_UINT8(alarm_sec, Ftrtc011State),
+ VMSTATE_UINT8(alarm_min, Ftrtc011State),
+ VMSTATE_UINT8(alarm_hr, Ftrtc011State),
+ VMSTATE_UINT32(cr, Ftrtc011State),
+ VMSTATE_UINT32(isr, Ftrtc011State),
+ 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(Ftrtc011State),
+ .class_init = ftrtc011_class_init,
+};
+
+static void ftrtc011_register_types(void)
+{
+ type_register_static(&ftrtc011_info);
+}
+
+type_init(ftrtc011_register_types)
new file mode 100644
@@ -0,0 +1,32 @@
+/*
+ * 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+.
+ */
+#ifndef HW_ARM_FTRTC011_H
+#define HW_ARM_FTRTC011_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
+
+#endif