@@ -42,3 +42,4 @@ obj-y += ftahbc020.o
obj-y += ftddrii030.o
obj-y += ftpwmtmr010.o
obj-y += ftwdt010.o
+obj-y += ftrtc011.o
@@ -191,6 +191,24 @@ a369soc_device_init(FaradaySoCState *s)
/* ftwdt010 */
sysbus_create_simple("ftwdt010", 0x92200000, s->pic[46]);
qemu_register_reset(a369soc_system_reset, s);
+
+ /* ftrtc011 */
+ ds = qdev_create(NULL, "ftrtc011");
+ /* Setup QOM path for QTest */
+ object_property_add_child(OBJECT(s),
+ "ftrtc011",
+ OBJECT(ds),
+ NULL);
+ qdev_init_nofail(ds);
+ sysbus_mmio_map(SYS_BUS_DEVICE(ds), 0, 0x92100000);
+ /* Alarm (Edge) */
+ sysbus_connect_irq(SYS_BUS_DEVICE(ds), 1, s->pic[42]);
+ /* Second (Edge) */
+ sysbus_connect_irq(SYS_BUS_DEVICE(ds), 2, s->pic[43]);
+ /* Minute (Edge) */
+ sysbus_connect_irq(SYS_BUS_DEVICE(ds), 3, s->pic[44]);
+ /* Hour (Edge) */
+ sysbus_connect_irq(SYS_BUS_DEVICE(ds), 4, s->pic[45]);
}
static int a369soc_init(SysBusDevice *dev)
new file mode 100644
@@ -0,0 +1,392 @@
+/*
+ * 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 "faraday.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"
+
+#define CFG_REGSIZE (0x3c / 4)
+
+typedef struct Ftrtc011State {
+ SysBusDevice busdev;
+ MemoryRegion mmio;
+
+ qemu_irq irq[6];
+
+ QEMUTimer *qtimer;
+ int64_t rtc_base;
+ int64_t rtc_start;
+
+ /* HW register caches */
+ uint32_t regs[CFG_REGSIZE];
+} Ftrtc011State;
+
+#define FTRTC011(obj) \
+ OBJECT_CHECK(Ftrtc011State, obj, TYPE_FTRTC011)
+
+#define RTC_REG32(s, off) \
+ ((s)->regs[(off) / 4])
+
+/* Update interrupts. */
+static void ftrtc011_update_irq(Ftrtc011State *s)
+{
+ uint32_t mask = extract32(RTC_REG32(s, REG_CR), 1, 5)
+ & RTC_REG32(s, REG_ISR);
+
+ qemu_set_irq(s->irq[IRQ_ALARM_LEVEL], !!(mask & ISR_ALARM));
+
+ if (mask) {
+ if (mask & ISR_SEC) {
+ qemu_irq_pulse(s->irq[IRQ_SEC]);
+ }
+ if (mask & ISR_MIN) {
+ qemu_irq_pulse(s->irq[IRQ_MIN]);
+ }
+ if (mask & ISR_HOUR) {
+ qemu_irq_pulse(s->irq[IRQ_HOUR]);
+ }
+ if (mask & ISR_DAY) {
+ qemu_irq_pulse(s->irq[IRQ_DAY]);
+ }
+ if (mask & ISR_ALARM) {
+ qemu_irq_pulse(s->irq[IRQ_ALARM_EDGE]);
+ }
+ }
+}
+
+static void ftrtc011_timer_rebase(Ftrtc011State *s)
+{
+ int64_t ticks = get_ticks_per_sec();
+ int64_t elapsed = RTC_REG32(s, REG_SEC)
+ + (60LL * RTC_REG32(s, REG_MIN))
+ + (3600LL * RTC_REG32(s, REG_HOUR))
+ + (86400LL * RTC_REG32(s, REG_DAY));
+
+ DPRINTF("rebase: now=%lld\n",
+ qemu_get_clock_ns(rtc_clock) / 1000000000LL);
+
+ s->rtc_base = elapsed;
+ s->rtc_start = qemu_get_clock_ns(rtc_clock);
+ /* adjust to the beginning of the current second */
+ s->rtc_start = s->rtc_start - (s->rtc_start % ticks);
+}
+
+static void ftrtc011_timer_update(Ftrtc011State *s)
+{
+ int64_t elapsed;
+ uint8_t sec, min, hr;
+ uint32_t day;
+
+ /* check if RTC is enabled */
+ if (!(RTC_REG32(s, REG_CR) & CR_EN)) {
+ return;
+ }
+
+ DPRINTF("update: now=%lld\n",
+ qemu_get_clock_ns(rtc_clock) / 1000000000LL);
+ /*
+ * 2013.03.11 Kuo-Jung Su
+ * Under QTest, in the very beginning of test,
+ * qemu_get_clock_ns(rtc_clock) would somehow jumps
+ * from 0 sec into 436474 sec.
+ * So I have to apply this simple work-aroud to make it work.
+ */
+ if (!s->rtc_start) {
+ ftrtc011_timer_rebase(s);
+ }
+
+ /*
+ * Although the timer is supposed to tick per second,
+ * there is no guarantee that the tick interval is exactly
+ * 1 and only 1 second.
+ */
+ elapsed = s->rtc_base
+ + (qemu_get_clock_ns(rtc_clock) - s->rtc_start) / 1000000000LL;
+ day = (uint32_t)(elapsed / 86400LL);
+ elapsed %= 86400LL;
+ hr = (uint8_t)(elapsed / 3600LL);
+ elapsed %= 3600LL;
+ min = (uint8_t)(elapsed / 60LL);
+ elapsed %= 60LL;
+ sec = elapsed;
+
+ /* sec interrupt */
+ if ((RTC_REG32(s, REG_SEC) != sec)
+ && (RTC_REG32(s, REG_CR) & CR_INTR_SEC)) {
+ RTC_REG32(s, REG_ISR) |= ISR_SEC;
+ }
+ /* min interrupt */
+ if ((RTC_REG32(s, REG_MIN) != min)
+ && (RTC_REG32(s, REG_CR) & CR_INTR_MIN)) {
+ RTC_REG32(s, REG_ISR) |= ISR_MIN;
+ }
+ /* hr interrupt */
+ if ((RTC_REG32(s, REG_HOUR) != hr)
+ && (RTC_REG32(s, REG_CR) & CR_INTR_HOUR)) {
+ RTC_REG32(s, REG_ISR) |= ISR_HOUR;
+ }
+ /* day interrupt */
+ if ((RTC_REG32(s, REG_DAY) != day)
+ && (RTC_REG32(s, REG_CR) & CR_INTR_DAY)) {
+ RTC_REG32(s, REG_ISR) |= ISR_DAY;
+ }
+
+ /* update RTC registers */
+ RTC_REG32(s, REG_SEC) = (uint8_t)sec;
+ RTC_REG32(s, REG_MIN) = (uint8_t)min;
+ RTC_REG32(s, REG_HOUR) = (uint8_t)hr;
+ RTC_REG32(s, REG_DAY) = (uint16_t)day;
+
+ /* alarm interrupt */
+ if (RTC_REG32(s, REG_CR) & CR_INTR_ALARM) {
+ if ((RTC_REG32(s, REG_SEC)
+ | (RTC_REG32(s, REG_MIN) << 8)
+ | (RTC_REG32(s, REG_HOUR) << 16)) ==
+ (RTC_REG32(s, REG_ALARM_SEC)
+ | (RTC_REG32(s, REG_ALARM_MIN) << 8)
+ | (RTC_REG32(s, REG_ALARM_HOUR) << 16))) {
+ RTC_REG32(s, REG_ISR) |= ISR_ALARM;
+ }
+ }
+
+ /* set interrupt signal */
+ ftrtc011_update_irq(s);
+}
+
+static void ftrtc011_timer_resche(Ftrtc011State *s)
+{
+ uint32_t cr = RTC_REG32(s, REG_CR);
+ int64_t ticks = get_ticks_per_sec();
+ int64_t timeout = 0;
+
+ if ((cr & CR_EN) && (cr & CR_INTR_MASK)) {
+ timeout = qemu_get_clock_ns(rtc_clock) + ticks;
+ }
+
+ if (timeout > 0) {
+ qemu_mod_timer(s->qtimer, timeout);
+ }
+}
+
+static uint64_t
+ftrtc011_mem_read(void *opaque, hwaddr addr, unsigned size)
+{
+ Ftrtc011State *s = FTRTC011(opaque);
+ uint32_t ret = 0;
+
+ switch (addr) {
+ case REG_SEC ... REG_DAY:
+ ftrtc011_timer_update(s);
+ /* fall-through */
+ case REG_ALARM_SEC ... REG_ISR:
+ ret = s->regs[addr / 4];
+ break;
+ case REG_REVR:
+ ret = 0x00010000; /* rev. 1.0.0 */
+ break;
+ case REG_CURRENT:
+ ftrtc011_timer_update(s);
+ ret |= RTC_REG32(s, REG_DAY) << 17;
+ ret |= RTC_REG32(s, REG_HOUR) << 12;
+ ret |= RTC_REG32(s, REG_MIN) << 6;
+ ret |= RTC_REG32(s, REG_SEC);
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ftrtc011: undefined memory access@%#" HWADDR_PRIx "\n", addr);
+ break;
+ }
+
+ return ret;
+}
+
+static void
+ftrtc011_mem_write(void *opaque, hwaddr addr, uint64_t val, unsigned size)
+{
+ Ftrtc011State *s = FTRTC011(opaque);
+
+ switch (addr) {
+ case REG_ALARM_SEC ... REG_ALARM_HOUR:
+ /* fall-through */
+ case REG_WSEC ... REG_WHOUR:
+ s->regs[addr / 4] = (uint32_t)val & 0xff;
+ break;
+ case REG_WDAY:
+ s->regs[addr / 4] = (uint32_t)val & 0xffff;
+ break;
+ case REG_CR:
+ /* check if RTC is enabled */
+ if (val & CR_EN) {
+ /* update the RTC counter with the user supplied values */
+ if (val & CR_LOAD) {
+ RTC_REG32(s, REG_SEC) = RTC_REG32(s, REG_WSEC);
+ RTC_REG32(s, REG_MIN) = RTC_REG32(s, REG_WMIN);
+ RTC_REG32(s, REG_HOUR) = RTC_REG32(s, REG_WHOUR);
+ RTC_REG32(s, REG_DAY) = RTC_REG32(s, REG_WDAY);
+ val &= ~CR_LOAD;
+ ftrtc011_timer_rebase(s);
+ } else if (!(RTC_REG32(s, REG_CR) & CR_EN)) {
+ ftrtc011_timer_rebase(s);
+ }
+ } else {
+ qemu_del_timer(s->qtimer);
+ }
+ RTC_REG32(s, REG_CR) = (uint32_t)val;
+ /* reschedule a RTC timer update */
+ ftrtc011_timer_resche(s);
+ break;
+ case REG_ISR:
+ RTC_REG32(s, REG_ISR) &= ~((uint32_t)val);
+ ftrtc011_update_irq(s);
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ftrtc011: undefined memory access@%#" HWADDR_PRIx "\n", addr);
+ break;
+ }
+
+ if (RTC_REG32(s, REG_ALARM_SEC) > 59
+ || RTC_REG32(s, REG_ALARM_MIN) > 59
+ || RTC_REG32(s, REG_ALARM_HOUR) > 23
+ || RTC_REG32(s, REG_WSEC) > 59
+ || RTC_REG32(s, REG_WMIN) > 59
+ || RTC_REG32(s, REG_WHOUR) > 23) {
+ goto werr;
+ }
+
+ return;
+
+werr:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ftrtc011: %u is an invalid value to reg@0x%02x.\n",
+ (uint32_t)val, (uint32_t)addr);
+}
+
+static const MemoryRegionOps mmio_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);
+ ftrtc011_timer_resche(s);
+}
+
+static void ftrtc011_reset(DeviceState *ds)
+{
+ Ftrtc011State *s = FTRTC011(SYS_BUS_DEVICE(ds));
+
+ qemu_del_timer(s->qtimer);
+ memset(s->regs, 0, sizeof(s->regs));
+ ftrtc011_update_irq(s);
+}
+
+static void ftrtc011_save(QEMUFile *f, void *opaque)
+{
+ int i;
+ Ftrtc011State *s = FTRTC011(opaque);
+
+ for (i = 0; i < sizeof(s->regs) / 4; ++i) {
+ qemu_put_be32(f, s->regs[i]);
+ }
+}
+
+static int ftrtc011_load(QEMUFile *f, void *opaque, int version_id)
+{
+ int i;
+ Ftrtc011State *s = FTRTC011(opaque);
+
+ for (i = 0; i < sizeof(s->regs) / 4; ++i) {
+ s->regs[i] = qemu_get_be32(f);
+ }
+
+ return 0;
+}
+
+static int ftrtc011_init(SysBusDevice *dev)
+{
+ Ftrtc011State *s = FTRTC011(dev);
+
+ s->qtimer = qemu_new_timer_ns(rtc_clock, ftrtc011_timer_tick, s);
+
+ memory_region_init_io(&s->mmio,
+ &mmio_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_UINT32_ARRAY(regs, Ftrtc011State, CFG_REGSIZE),
+ 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,53 @@
+/*
+ * 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
+
+#include "qemu/bitops.h"
+
+/* Hardware registers */
+#define REG_SEC 0x00
+#define REG_MIN 0x04
+#define REG_HOUR 0x08
+#define REG_DAY 0x0C
+
+#define REG_ALARM_SEC 0x10 /* Alarm: sec */
+#define REG_ALARM_MIN 0x14 /* Alarm: min */
+#define REG_ALARM_HOUR 0x18 /* Alarm: hour */
+
+#define REG_CR 0x20 /* Control Register */
+#define REG_WSEC 0x24 /* Write SEC Register */
+#define REG_WMIN 0x28 /* Write MIN Register */
+#define REG_WHOUR 0x2C /* Write HOUR Register */
+#define REG_WDAY 0x30 /* Write DAY Register */
+#define REG_ISR 0x34 /* Interrupt Status Register */
+
+#define REG_REVR 0x3C /* Revision Register */
+#define REG_CURRENT 0x44 /* Group-up day/hour/min/sec as a register */
+
+#define CR_LOAD BIT(6) /* Update counters by Wxxx registers */
+#define CR_INTR_ALARM BIT(5) /* Alarm interrupt enabled */
+#define CR_INTR_DAY BIT(4) /* DDAY interrupt enabled */
+#define CR_INTR_HOUR BIT(3) /* HOUR interrupt enabled */
+#define CR_INTR_MIN BIT(2) /* MIN interrupt enabled */
+#define CR_INTR_SEC BIT(1) /* SEC interrupt enabled */
+#define CR_INTR_MASK (CR_INTR_SEC | CR_INTR_MIN \
+ | CR_INTR_HOUR | CR_INTR_DAY | CR_INTR_ALARM)
+#define CR_EN BIT(0) /* RTC enabled */
+
+#define ISR_LOAD BIT(5) /* CR_LOAD finished (no interrupt occurs) */
+#define ISR_ALARM BIT(4)
+#define ISR_DAY BIT(3)
+#define ISR_HOUR BIT(2)
+#define ISR_MIN BIT(1)
+#define ISR_SEC BIT(0)
+#define ISR_MASK (ISR_SEC | ISR_MIN \
+ | ISR_HOUR | ISR_DAY | ISR_ALARM | ISR_LOAD)
+
+#endif