From patchwork Fri Jan 18 06:32:47 2013 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: [16/18] hw: add QEMU model for Faraday RTC timer Date: Thu, 17 Jan 2013 20:32:47 -0000 From: Dante X-Patchwork-Id: 213490 Message-Id: <1358490767-18723-1-git-send-email-dantesu@faraday-tech.com> To: Cc: peter.maydell@linaro.org, paul@codesourcery.com, dantesu@faraday-tech.com Signed-off-by: Kuo-Jung Su --- hw/ftrtc011.c | 308 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 308 insertions(+) create mode 100644 hw/ftrtc011.c diff --git a/hw/ftrtc011.c b/hw/ftrtc011.c new file mode 100644 index 0000000..466cbb6 --- /dev/null +++ b/hw/ftrtc011.c @@ -0,0 +1,308 @@ +/* + * QEMU model of the FTRTC011 RTC Timer + * + * Copyright (C) 2012 Faraday Technology + * Copyright (C) 2012 Dante Su + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#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, +}; + +typedef struct { + 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; + +/* Update interrupts. */ +static inline 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 int size) +{ + ftrtc011_state *s = 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 int size) +{ + ftrtc011_state *s = 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 bus_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_state *)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 *d) +{ + ftrtc011_state *s = DO_UPCAST(ftrtc011_state, busdev.qdev, d); + + 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 = FROM_SYSBUS(typeof(*s), dev); + + s->qtimer = qemu_new_timer_ns(vm_clock, ftrtc011_timer_tick, s); + + memory_region_init_io(&s->mmio, &bus_ops, s, "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 = "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 *sdc = SYS_BUS_DEVICE_CLASS(klass); + DeviceClass *k = DEVICE_CLASS(klass); + + sdc->init = ftrtc011_init; + k->vmsd = &vmstate_ftrtc011; + k->reset = ftrtc011_reset; + k->no_user = 1; +} + +static TypeInfo ftrtc011_info = { + .name = "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)