From: Kuo-Jung Su <dant...@faraday-tech.com> 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.
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. Signed-off-by: Kuo-Jung Su <dant...@faraday-tech.com> --- hw/arm/Makefile.objs | 1 + hw/arm/faraday_a369.c | 10 ++ hw/arm/ftrtc011.c | 406 +++++++++++++++++++++++++++++++++++++++++++++++++ hw/arm/ftrtc011.h | 32 ++++ 4 files changed, 449 insertions(+) create mode 100644 hw/arm/ftrtc011.c create mode 100644 hw/arm/ftrtc011.h diff --git a/hw/arm/Makefile.objs b/hw/arm/Makefile.objs index 04904e7..8e106ff 100644 --- a/hw/arm/Makefile.objs +++ b/hw/arm/Makefile.objs @@ -41,3 +41,4 @@ obj-y += ftintc020.o obj-y += fttmr010.o obj-y += ftpwmtmr010.o obj-y += ftwdt010.o +obj-y += ftrtc011.o diff --git a/hw/arm/faraday_a369.c b/hw/arm/faraday_a369.c index 89911e3..f8471b4 100644 --- a/hw/arm/faraday_a369.c +++ b/hw/arm/faraday_a369.c @@ -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 diff --git a/hw/arm/ftrtc011.c b/hw/arm/ftrtc011.c new file mode 100644 index 0000000..e6d13a7 --- /dev/null +++ b/hw/arm/ftrtc011.c @@ -0,0 +1,406 @@ +/* + * QEMU model of the FTRTC011 RTC Timer + * + * Copyright (C) 2012 Faraday Technology + * Written by Dante Su <dant...@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) diff --git a/hw/arm/ftrtc011.h b/hw/arm/ftrtc011.h new file mode 100644 index 0000000..273602f --- /dev/null +++ b/hw/arm/ftrtc011.h @@ -0,0 +1,32 @@ +/* + * QEMU model of the FTRTC011 RTC Timer + * + * Copyright (C) 2012 Faraday Technology + * Written by Dante Su <dant...@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 -- 1.7.9.5