From patchwork Mon Mar 25 12:09:44 2013 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kuo-Jung Su X-Patchwork-Id: 230679 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [208.118.235.17]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (Client did not present a certificate) by ozlabs.org (Postfix) with ESMTPS id E2EA32C00B0 for ; Mon, 25 Mar 2013 23:20:21 +1100 (EST) Received: from localhost ([::1]:50391 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1UK6Nw-0001Xm-2G for incoming@patchwork.ozlabs.org; Mon, 25 Mar 2013 08:20:20 -0400 Received: from eggs.gnu.org ([208.118.235.92]:39920) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1UK6Kx-0005LV-4J for qemu-devel@nongnu.org; Mon, 25 Mar 2013 08:17:19 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1UK6Ek-0006ce-2Q for qemu-devel@nongnu.org; Mon, 25 Mar 2013 08:11:02 -0400 Received: from mail-pa0-f48.google.com ([209.85.220.48]:51204) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1UK6Ej-0006cD-1k for qemu-devel@nongnu.org; Mon, 25 Mar 2013 08:10:49 -0400 Received: by mail-pa0-f48.google.com with SMTP id hz10so996244pad.21 for ; Mon, 25 Mar 2013 05:10:48 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=x-received:from:to:cc:subject:date:message-id:x-mailer:in-reply-to :references; bh=39Xw4/Pg0Zb7Ofh1y47oYP7/F5HKKrhz19wxakAWJm8=; b=WA4BfrHhCRXERUT/YcQnveSHUdwawWPnNA3aTR2dThcvpY1vcvJzItR6WrAozBGMWo 8tBq3vML1Zkbd0FwQalfi3HPjbwZkKlrhNQ1g5pA3MoDPc9+t3yGPUmOwrTXMV6dzVCV GOaBquVhliCwqSV/f20/ujOW0ZOodtLlaTG1CcT4jRJz9myCrfHlw2XdVLBHdxWbbCCy X3KpYRzPWGo7MBCe2aRQPu1JtC8qCFvl4m8XM6DJSV97gIcR/cZWdg9osPY7z7qahouF 4yZX92PWrfZLI5q59uxMZT6H18jIB+ttERV4sWBqalBP+XZS8WftNkUi4b+ZfKSGb4HM GmtQ== X-Received: by 10.68.8.69 with SMTP id p5mr16874621pba.212.1364213448276; Mon, 25 Mar 2013 05:10:48 -0700 (PDT) Received: from localhost.localdomain ([220.132.37.35]) by mx.google.com with ESMTPS id wi7sm14669199pac.9.2013.03.25.05.10.45 (version=TLSv1 cipher=DES-CBC3-SHA bits=168/168); Mon, 25 Mar 2013 05:10:47 -0700 (PDT) From: Kuo-Jung Su To: qemu-devel@nongnu.org Date: Mon, 25 Mar 2013 20:09:44 +0800 Message-Id: <1364213400-10266-9-git-send-email-dantesu@gmail.com> X-Mailer: git-send-email 1.7.9.5 In-Reply-To: <1364213400-10266-1-git-send-email-dantesu@gmail.com> References: <1364213400-10266-1-git-send-email-dantesu@gmail.com> X-detected-operating-system: by eggs.gnu.org: GNU/Linux 3.x [fuzzy] X-Received-From: 209.85.220.48 Cc: Peter Maydell , i.mitsyanko@samsung.com, Wei-Ren Chen , Blue Swirl , Paul Brook , Kuo-Jung Su , Paolo Bonzini , Andreas , fred.konrad@greensocs.com Subject: [Qemu-devel] [PATCH v9 08/24] hw/arm: add FTRTC011 RTC timer support X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org From: Kuo-Jung Su 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 --- hw/arm/Makefile.objs | 3 +- hw/arm/ftplat_a369soc.c | 18 +++ hw/ftrtc011.c | 387 +++++++++++++++++++++++++++++++++++++++++++++++ hw/ftrtc011.h | 53 +++++++ 4 files changed, 460 insertions(+), 1 deletion(-) create mode 100644 hw/ftrtc011.c create mode 100644 hw/ftrtc011.h diff --git a/hw/arm/Makefile.objs b/hw/arm/Makefile.objs index 22f0c64..6a41b21 100644 --- a/hw/arm/Makefile.objs +++ b/hw/arm/Makefile.objs @@ -24,7 +24,8 @@ obj-y += framebuffer.o obj-y += strongarm.o obj-y += imx_serial.o imx_ccm.o imx_timer.o imx_avic.o obj-$(CONFIG_KVM) += kvm/arm_gic.o -obj-y += ftintc020.o ftahbc020.o ftddrii030.o ftpwmtmr010.o ftwdt010.o +obj-y += ftintc020.o ftahbc020.o ftddrii030.o ftpwmtmr010.o ftwdt010.o \ + ftrtc011.o obj-y := $(addprefix ../,$(obj-y)) diff --git a/hw/arm/ftplat_a369soc.c b/hw/arm/ftplat_a369soc.c index 56f0920..bd696c4 100644 --- a/hw/arm/ftplat_a369soc.c +++ b/hw/arm/ftplat_a369soc.c @@ -150,6 +150,24 @@ static void a369soc_chip_init(FaradaySoCState *s) /* ftwdt010 */ sysbus_create_simple("ftwdt010", 0x92200000, s->pic[46]); + + /* 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 void a369soc_realize(DeviceState *dev, Error **errp) diff --git a/hw/ftrtc011.c b/hw/ftrtc011.c new file mode 100644 index 0000000..79b9021 --- /dev/null +++ b/hw/ftrtc011.c @@ -0,0 +1,387 @@ +/* + * QEMU model of the FTRTC011 RTC Timer + * + * Copyright (C) 2012 Faraday Technology + * Written by Dante Su + * + * This file is licensed under GNU GPL v2+. + */ + +#include "hw/sysbus.h" +#include "qemu/timer.h" +#include "sysemu/sysemu.h" + +#include "hw/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 { + /*< private >*/ + SysBusDevice parent; + + /*< public >*/ + 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)); + + 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; + } + + /* + * 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 void ftrtc011_realize(DeviceState *dev, Error **errp) +{ + Ftrtc011State *s = FTRTC011(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(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(sbd, &s->mmio); + sysbus_init_irq(sbd, &s->irq[IRQ_ALARM_LEVEL]); + sysbus_init_irq(sbd, &s->irq[IRQ_ALARM_EDGE]); + sysbus_init_irq(sbd, &s->irq[IRQ_SEC]); + sysbus_init_irq(sbd, &s->irq[IRQ_MIN]); + sysbus_init_irq(sbd, &s->irq[IRQ_HOUR]); + sysbus_init_irq(sbd, &s->irq[IRQ_DAY]); + + register_savevm(&sbd->qdev, TYPE_FTRTC011, -1, 0, + ftrtc011_save, ftrtc011_load, s); +} + +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) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->vmsd = &vmstate_ftrtc011; + dc->reset = ftrtc011_reset; + dc->realize = ftrtc011_realize; + 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/ftrtc011.h b/hw/ftrtc011.h new file mode 100644 index 0000000..6e23d5e --- /dev/null +++ b/hw/ftrtc011.h @@ -0,0 +1,53 @@ +/* + * QEMU model of the FTRTC011 RTC Timer + * + * Copyright (C) 2012 Faraday Technology + * Written by Dante Su + * + * 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