Il 06/03/2013 08:27, Kuo-Jung Su ha scritto: > 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...@gmail.com> > --- > hw/arm/Makefile.objs | 1 + > hw/arm/faraday_a369_soc.c | 10 ++ > hw/arm/ftrtc011.c | 346 > +++++++++++++++++++++++++++++++++++++++++++++ > hw/arm/ftrtc011.h | 49 +++++++ > 4 files changed, 406 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 bc8e2de..6dbac2f 100644 > --- a/hw/arm/Makefile.objs > +++ b/hw/arm/Makefile.objs > @@ -42,3 +42,4 @@ obj-y += ftahbc020.o > obj-y += ftddrii030.o > obj-y += ftpwmtmr010.o > obj-y += ftwdt010.o > +obj-y += ftrtc011.o > diff --git a/hw/arm/faraday_a369_soc.c b/hw/arm/faraday_a369_soc.c > index 1bf64d4..4371a3a 100644 > --- a/hw/arm/faraday_a369_soc.c > +++ b/hw/arm/faraday_a369_soc.c > @@ -189,6 +189,16 @@ a369soc_device_init(FaradaySoCState *s) > /* ftwdt010 */ > sysbus_create_simple("ftwdt010", 0x92200000, s->pic[46]); > qemu_register_reset(a369soc_system_reset, s); > + > + /* ftrtc011 */ > + sysbus_create_varargs("ftrtc011", > + 0x92100000, > + s->pic[0], /* Alarm (Level): NC in A369 */ > + s->pic[42], /* Alarm (Edge) */ > + s->pic[43], /* Second (Edge) */ > + s->pic[44], /* Minute (Edge) */ > + s->pic[45], /* Hour (Edge) */ > + NULL); > } > > static int a369soc_init(SysBusDevice *dev) > diff --git a/hw/arm/ftrtc011.c b/hw/arm/ftrtc011.c > new file mode 100644 > index 0000000..2e8776e > --- /dev/null > +++ b/hw/arm/ftrtc011.c > @@ -0,0 +1,346 @@ > +/* > + * 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 "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_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) ? 1 : 0); > + > + 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_resync(Ftrtc011State *s) > +{ > + int64_t elapsed = RTC_REG32(s, REG_SEC) > + + (60 * RTC_REG32(s, REG_MIN)) > + + (3600 * RTC_REG32(s, REG_HOUR)) > + + (86400 * RTC_REG32(s, REG_DAY)); > + s->rtc_start = get_clock_realtime() - elapsed * 1000000000LL;
Please use instead qemu_get_clock_ns(rtc_clock). In general, use rtc_clock instead of rt_clock. This way a) you can control the clock with -clock; b) you can write testcases. Please add a testcase for the RTC clock, since we have other examples of tested devices like this one. Paolo > +} > + > +static void ftrtc011_timer_update(Ftrtc011State *s) > +{ > + int64_t elapsed; > + uint8_t sec, min, hr; > + uint32_t day; > + > + if (!(RTC_REG32(s, REG_CR) & CR_EN)) { > + return; > + } > + > + /* > + * 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 ((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; > + } > + /* 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; > + } > + } > + > + /* send interrupt signal */ > + ftrtc011_update_irq(s); > + > + /* 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; > +} > + > +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); > + 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: > + case REG_WSEC ... REG_WHOUR: > + s->regs[addr / 4] = (uint32_t)val & 0x3f; > + break; > + case REG_WDAY: > + s->regs[addr / 4] = (uint32_t)val & 0xffff; > + break; > + case REG_CR: > + /* 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_resync(s); > + } > + /* check if RTC enabled */ > + if (val & CR_EN) { > + if (!(RTC_REG32(s, REG_CR) & CR_EN)) { > + ftrtc011_timer_resync(s); > + } > + qemu_mod_timer(s->qtimer, qemu_get_clock_ms(rt_clock) + 1000); > + } else { > + qemu_del_timer(s->qtimer); > + } > + RTC_REG32(s, REG_CR) = (uint32_t)val; > + 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: > + fprintf(stderr, "ftrtc011: %u is an invalid value.\n", (uint32_t)val); > + exit(1); > +} > + > +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); > + qemu_mod_timer(s->qtimer, qemu_get_clock_ms(rt_clock) + 1000); > +} > + > +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_ms(rt_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) > diff --git a/hw/arm/ftrtc011.h b/hw/arm/ftrtc011.h > new file mode 100644 > index 0000000..feb8aab > --- /dev/null > +++ b/hw/arm/ftrtc011.h > @@ -0,0 +1,49 @@ > +/* > + * 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 > + > +#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_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) > + > +#endif >