[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: [Qemu-arm] [PATCH 3/4] hw/timer: Add Epson RX8900 RTC support
From: |
Cédric Le Goater |
Subject: |
Re: [Qemu-arm] [PATCH 3/4] hw/timer: Add Epson RX8900 RTC support |
Date: |
Thu, 17 Nov 2016 09:29:45 +0100 |
User-agent: |
Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 Thunderbird/45.4.0 |
On 11/17/2016 05:36 AM, Alastair D'Silva wrote:
> From: Alastair D'Silva <address@hidden>
>
> This patch adds support for the Epson RX8900 RTC chip.
It would be nice to have a short list of the features this
chip has and also the main point of the design. I see you
are using a BH.
> Signed-off-by: Alastair D'Silva <address@hidden>
> ---
> default-configs/arm-softmmu.mak | 1 +
> hw/timer/Makefile.objs | 2 +
> hw/timer/rx8900.c | 891
> ++++++++++++++++++++++++++++++++++++++++
> hw/timer/rx8900_regs.h | 125 ++++++
> tests/Makefile.include | 2 +
> tests/rx8900-test.c | 800 ++++++++++++++++++++++++++++++++++++
Nice test ! But Why aren't you using the aspeed machine in
qtest ?
The reason I am asking is because the I2C controller model
is a little too simplistic in the way it handles the irq
status and we would need a test for it.
Also I would put the test case in another patch, after the
model and after patch 2 also which introduces named
interrupts for qtest.
> 6 files changed, 1821 insertions(+)
> create mode 100644 hw/timer/rx8900.c
> create mode 100644 hw/timer/rx8900_regs.h
> create mode 100644 tests/rx8900-test.c
>
> diff --git a/default-configs/arm-softmmu.mak b/default-configs/arm-softmmu.mak
> index 6de3e16..adb600e 100644
> --- a/default-configs/arm-softmmu.mak
> +++ b/default-configs/arm-softmmu.mak
> @@ -29,6 +29,7 @@ CONFIG_SMC91C111=y
> CONFIG_ALLWINNER_EMAC=y
> CONFIG_IMX_FEC=y
> CONFIG_DS1338=y
> +CONFIG_RX8900=y
> CONFIG_PFLASH_CFI01=y
> CONFIG_PFLASH_CFI02=y
> CONFIG_MICRODRIVE=y
> diff --git a/hw/timer/Makefile.objs b/hw/timer/Makefile.objs
> index 7ba8c23..fa028ac 100644
> --- a/hw/timer/Makefile.objs
> +++ b/hw/timer/Makefile.objs
> @@ -3,6 +3,7 @@ common-obj-$(CONFIG_ARM_MPTIMER) += arm_mptimer.o
> common-obj-$(CONFIG_A9_GTIMER) += a9gtimer.o
> common-obj-$(CONFIG_CADENCE) += cadence_ttc.o
> common-obj-$(CONFIG_DS1338) += ds1338.o
> +common-obj-$(CONFIG_RX8900) += rx8900.o
> common-obj-$(CONFIG_HPET) += hpet.o
> common-obj-$(CONFIG_I8254) += i8254_common.o i8254.o
> common-obj-$(CONFIG_M48T59) += m48t59.o
> @@ -17,6 +18,7 @@ common-obj-$(CONFIG_IMX) += imx_epit.o
> common-obj-$(CONFIG_IMX) += imx_gpt.o
> common-obj-$(CONFIG_LM32) += lm32_timer.o
> common-obj-$(CONFIG_MILKYMIST) += milkymist-sysctl.o
> +common-obj-$(CONFIG_RX8900) += rx8900.o
>
> obj-$(CONFIG_EXYNOS4) += exynos4210_mct.o
> obj-$(CONFIG_EXYNOS4) += exynos4210_pwm.o
> diff --git a/hw/timer/rx8900.c b/hw/timer/rx8900.c
> new file mode 100644
> index 0000000..208a31b
> --- /dev/null
> +++ b/hw/timer/rx8900.c
> @@ -0,0 +1,891 @@
> +/*
> + * Epson RX8900SA/CE Realtime Clock Module
> + *
> + * Copyright (c) 2016 IBM Corporation
> + * Authors:
> + * Alastair D'Silva <address@hidden>
> + *
> + * This code is licensed under the GPL version 2 or later. See
> + * the COPYING file in the top-level directory.
> + *
> + * Datasheet available at:
> + * https://support.epson.biz/td/api/doc_check.php?dl=app_RX8900CE&lang=en
> + *
> + * Not implemented:
> + * Implement Timer Counters
> + * Implement i2c timeout
> + */
> +
> +#include "qemu/osdep.h"
> +#include "qemu-common.h"
> +#include "hw/i2c/i2c.h"
> +#include "hw/timer/rx8900_regs.h"
> +#include "hw/ptimer.h"
> +#include "qemu/main-loop.h"
> +#include "qemu/bcd.h"
> +#include "qemu/error-report.h"
> +#include "qemu/log.h"
> +#include "qapi/error.h"
> +#include "qapi/visitor.h"
> +
> + #include <sys/time.h>
> +
> + #include <execinfo.h>
> +
> +#define TYPE_RX8900 "rx8900"
> +#define RX8900(obj) OBJECT_CHECK(RX8900State, (obj), TYPE_RX8900)
> +
> +static bool log;
> +
> +typedef struct RX8900State {
> + I2CSlave parent_obj;
> +
> + ptimer_state *sec_timer; /* triggered once per second */
> + ptimer_state *fout_timer;
> + ptimer_state *countdown_timer;
> + bool fout;
> + int64_t offset;
> + uint8_t weekday; /* Saved for deferred offset calculation, 0-6 */
> + uint8_t wday_offset;
> + uint8_t nvram[RX8900_NVRAM_SIZE];
> + int32_t ptr; /* Wrapped to stay within RX8900_NVRAM_SIZE */
> + bool addr_byte;
> + uint8_t last_interrupt_seconds;
> + uint8_t last_update_interrupt_minutes;
> + qemu_irq interrupt_pin;
> + qemu_irq fout_pin;
> +} RX8900State;
> +
> +static const VMStateDescription vmstate_rx8900 = {
> + .name = "rx8900",
> + .version_id = 2,
> + .minimum_version_id = 1,
> + .fields = (VMStateField[]) {
> + VMSTATE_I2C_SLAVE(parent_obj, RX8900State),
> + VMSTATE_PTIMER(sec_timer, RX8900State),
> + VMSTATE_PTIMER(fout_timer, RX8900State),
> + VMSTATE_PTIMER(countdown_timer, RX8900State),
> + VMSTATE_BOOL(fout, RX8900State),
> + VMSTATE_INT64(offset, RX8900State),
> + VMSTATE_UINT8_V(weekday, RX8900State, 2),
> + VMSTATE_UINT8_V(wday_offset, RX8900State, 2),
> + VMSTATE_UINT8_ARRAY(nvram, RX8900State, RX8900_NVRAM_SIZE),
> + VMSTATE_INT32(ptr, RX8900State),
> + VMSTATE_BOOL(addr_byte, RX8900State),
> + VMSTATE_UINT8_V(last_interrupt_seconds, RX8900State, 2),
> + VMSTATE_UINT8_V(last_update_interrupt_minutes, RX8900State, 2),
> + VMSTATE_END_OF_LIST()
> + }
> +};
> +
> +static void rx8900_reset(DeviceState *dev);
> +static void disable_countdown_timer(RX8900State *s);
> +static void enable_countdown_timer(RX8900State *s);
> +static void disable_timer(RX8900State *s);
> +static void enable_timer(RX8900State *s);
> +
> +#ifdef RX8900_TRACE
> +#define RX8900_TRACE_BUF_SIZE 256
> +/**
> + * Emit a trace message
> + * @param file the source filename
> + * @param line the line number the message was emitted from
> + * @param dev the RX8900 device
> + * @param fmt a printf style format
> + */
> +static void trace(const char *file, int line, const char *func,
> + I2CSlave *dev, const char *fmt, ...)
> +{
> + va_list ap;
> + char buf[RX8900_TRACE_BUF_SIZE];
> + char timestamp[32];
> + int len;
> + struct timeval now;
> + struct tm *now2;
> +
> + gettimeofday(&now, NULL);
> + now2 = localtime(&now.tv_sec);
> +
> + strftime(timestamp, sizeof(timestamp), "%F %T", now2);
> +
> + len = snprintf(buf, sizeof(buf), "\n\t%s.%03ld %s:%s:%d: RX8900 %s
> address@hidden: %s",
> + timestamp, now.tv_usec / 1000,
> + file, func, line, dev->qdev.id, dev->qdev.parent_bus->name,
> + dev->address, fmt);
> + if (len >= RX8900_TRACE_BUF_SIZE) {
> + error_report("%s:%d: Trace buffer overflow", file, line);
> + }
> +
> + va_start(ap, fmt);
> + error_vreport(buf, ap);
> + va_end(ap);
> +}
> +
> +/**
> + * Emit a trace message
> + * @param dev the RX8900 device
> + * @param fmt a printf format
> + */
> +#define TRACE(dev, fmt, ...) \
> + do { \
> + if (log) { \
> + trace(__FILE__, __LINE__, __func__, &dev, fmt, ## __VA_ARGS__); \
> + } \
> + } while (0)
> +#else
> +#define TRACE(dev, fmt, ...)
> +#endif
Although this is very pratical and nicely written, I don't
think you can keep these TRACE calls in the code. You should
use the qemu trace subsystem for it.
> +static void capture_current_time(RX8900State *s)
> +{
> + /* Capture the current time into the secondary registers
> + * which will be actually read by the data transfer operation.
> + */
> + struct tm now;
> + qemu_get_timedate(&now, s->offset);
> + s->nvram[SECONDS] = to_bcd(now.tm_sec);
> + s->nvram[MINUTES] = to_bcd(now.tm_min);
> + s->nvram[HOURS] = to_bcd(now.tm_hour);
> +
> + s->nvram[WEEKDAY] = 0x01 << ((now.tm_wday + s->wday_offset) % 7);
> + s->nvram[DAY] = to_bcd(now.tm_mday);
> + s->nvram[MONTH] = to_bcd(now.tm_mon + 1);
> + s->nvram[YEAR] = to_bcd(now.tm_year % 100);
> +
> + s->nvram[EXT_SECONDS] = s->nvram[SECONDS];
> + s->nvram[EXT_MINUTES] = s->nvram[MINUTES];
> + s->nvram[EXT_HOURS] = s->nvram[HOURS];
> + s->nvram[EXT_WEEKDAY] = s->nvram[WEEKDAY];
> + s->nvram[EXT_DAY] = s->nvram[DAY];
> + s->nvram[EXT_MONTH] = s->nvram[MONTH];
> + s->nvram[EXT_YEAR] = s->nvram[YEAR];
> +
> + TRACE(s->parent_obj, "Update current time to %02d:%02d:%02d %d %d/%d/%d "
> + "(0x%02x%02x%02x%02x%02x%02x%02x)",
> + now.tm_hour, now.tm_min, now.tm_sec,
> + (now.tm_wday + s->wday_offset) % 7,
> + now.tm_mday, now.tm_mon, now.tm_year + 1900,
> + s->nvram[HOURS], s->nvram[MINUTES], s->nvram[SECONDS],
> + s->nvram[WEEKDAY],
> + s->nvram[DAY], s->nvram[MONTH], s->nvram[YEAR]);
> +}
> +
> +/**
> + * Increment the internal register pointer, dealing with wrapping
> + * @param s the RTC to operate on
> + */
> +static void inc_regptr(RX8900State *s)
> +{
> + /* The register pointer wraps around after 0x1F
> + */
> + s->ptr = (s->ptr + 1) & (RX8900_NVRAM_SIZE - 1);
> + TRACE(s->parent_obj, "Operating on register 0x%02x", s->ptr);
> +
> + if (s->ptr == 0x00) {
> + TRACE(s->parent_obj, "Register pointer has overflowed, wrapping to
> 0");
> + capture_current_time(s);
> + }
> +}
> +
> +/**
> + * Receive an I2C Event
> + * @param i2c the i2c device instance
> + * @param event the event to handle
> + */
> +static void rx8900_event(I2CSlave *i2c, enum i2c_event event)
> +{
> + RX8900State *s = RX8900(i2c);
> +
> + switch (event) {
> + case I2C_START_RECV:
> + /* In h/w, time capture happens on any START condition, not just a
> + * START_RECV. For the emulation, it doesn't actually matter,
> + * since a START_RECV has to occur before the data can be read.
> + */
> + capture_current_time(s);
> + break;
> + case I2C_START_SEND:
> + s->addr_byte = true;
> + break;
> + case I2C_FINISH:
> + if (s->weekday < 7) {
> + /* We defer the weekday calculation as it is handed to us before
> + * the date has been updated. If we calculate the weekday offset
> + * when it is passed to us, we will incorrectly determine it
> + * based on the current emulated date, rather than the date that
> + * has been written.
> + */
> + struct tm now;
> + qemu_get_timedate(&now, s->offset);
> +
> + s->wday_offset = (s->weekday - now.tm_wday + 7) % 7;
> +
> + TRACE(s->parent_obj, "Set weekday to %d (0x%02x),
> wday_offset=%d",
> + s->weekday, BIT(s->weekday), s->wday_offset);
> +
> + s->weekday = 7;
> + }
> + break;
> +
> + default:
> + break;
> + }
> +}
> +
> +/**
> + * Perform an i2c receive action
> + * @param i2c the i2c device instance
> + * @return the value of the current register
> + * @post the internal register pointer is incremented
> + */
> +static int rx8900_recv(I2CSlave *i2c)
> +{
> + RX8900State *s = RX8900(i2c);
> + uint8_t res = s->nvram[s->ptr];
> + TRACE(s->parent_obj, "Read register 0x%x = 0x%x", s->ptr, res);
> + inc_regptr(s);
> + return res;
> +}
> +
> +/**
> + * Validate the extension register and perform actions based on the bits
> + * @param s the RTC to operate on
> + * @param data the new data for the extension register
> + */
> +static void update_extension_register(RX8900State *s, uint8_t data)
> +{
> + if (data & EXT_MASK_TEST) {
> + error_report("WARNING: RX8900 - "
> + "Test bit is enabled but is forbidden by the manufacturer");
may be use instead :
qemu_log_mask(LOG_GUEST_ERROR,
> + }
> +
> + if ((data ^ s->nvram[EXTENSION_REGISTER]) &
> + (EXT_MASK_FSEL0 | EXT_MASK_FSEL1)) {
> + uint8_t fsel = (data & (EXT_MASK_FSEL0 | EXT_MASK_FSEL1))
> + >> EXT_REG_FSEL0;
> + /* FSELx has changed */
> + switch (fsel) {
> + case 0x01:
> + TRACE(s->parent_obj, "Setting fout to 1024Hz");
> + ptimer_set_limit(s->fout_timer, 32, 1);
> + break;
> + case 0x02:
> + TRACE(s->parent_obj, "Setting fout to 1Hz");
> + ptimer_set_limit(s->fout_timer, 32768, 1);
> + break;
> + default:
> + TRACE(s->parent_obj, "Setting fout to 32768Hz");
> + ptimer_set_limit(s->fout_timer, 1, 1);
> + break;
> + }
> + }
> +
> + if ((data ^ s->nvram[EXTENSION_REGISTER]) &
> + (EXT_MASK_TSEL0 | EXT_MASK_TSEL1)) {
> + uint8_t tsel = (data & (EXT_MASK_TSEL0 | EXT_MASK_TSEL1))
> + >> EXT_REG_TSEL0;
> + /* TSELx has changed */
> + switch (tsel) {
> + case 0x00:
> + TRACE(s->parent_obj, "Setting countdown timer to 64 Hz");
> + ptimer_set_limit(s->countdown_timer, 4096 / 64, 1);
> + break;
> + case 0x01:
> + TRACE(s->parent_obj, "Setting countdown timer to 1 Hz");
> + ptimer_set_limit(s->countdown_timer, 4096, 1);
> + break;
> + case 0x02:
> + TRACE(s->parent_obj,
> + "Setting countdown timer to per minute updates");
> + ptimer_set_limit(s->countdown_timer, 4069 * 60, 1);
> + break;
> + case 0x03:
> + TRACE(s->parent_obj, "Setting countdown timer to 4096Hz");
> + ptimer_set_limit(s->countdown_timer, 1, 1);
> + break;
> + }
> + }
> +
> + if (data & EXT_MASK_TE) {
> + enable_countdown_timer(s);
> + }
> +
> + s->nvram[EXTENSION_REGISTER] = data;
> + s->nvram[EXT_EXTENSION_REGISTER] = data;
> +
> +}
> +/**
> + * Validate the control register and perform actions based on the bits
> + * @param s the RTC to operate on
> + * @param data the new value for the control register
> + */
> +
> +static void update_control_register(RX8900State *s, uint8_t data)
> +{
> + uint8_t diffmask = ~s->nvram[CONTROL_REGISTER] & data;
> +
> + if (diffmask & CTRL_MASK_WP0) {
> + data &= ~CTRL_MASK_WP0;
> + error_report("WARNING: RX8900 - "
> + "Attempt to write to write protected bit %d in control register",
> + CTRL_REG_WP0);
may be use instead :
qemu_log_mask(LOG_GUEST_ERROR,
> + }
> +
> + if (diffmask & CTRL_MASK_WP1) {
> + data &= ~CTRL_MASK_WP1;
> + error_report("WARNING: RX8900 - "
> + "Attempt to write to write protected bit %d in control register",
> + CTRL_REG_WP1);
ditto for all in fact.
> + }
> +
> + if (data & CTRL_MASK_RESET) {
> + data &= ~CTRL_MASK_RESET;
> + rx8900_reset(DEVICE(s));
> + }
> +
> + if (diffmask & CTRL_MASK_UIE) {
> + /* Update interrupts were off and are now on */
> + struct tm now;
> +
> + TRACE(s->parent_obj, "Enabling update timer");
> +
> + qemu_get_timedate(&now, s->offset);
> +
> + s->last_update_interrupt_minutes = now.tm_min;
> + s->last_interrupt_seconds = now.tm_sec;
> + enable_timer(s);
> + }
> +
> + if (diffmask & CTRL_MASK_AIE) {
> + /* Alarm interrupts were off and are now on */
> + struct tm now;
> +
> + TRACE(s->parent_obj, "Enabling alarm");
> +
> + qemu_get_timedate(&now, s->offset);
> +
> + s->last_interrupt_seconds = now.tm_sec;
> + enable_timer(s);
> + }
> +
> + if (!(data & (CTRL_MASK_UIE | CTRL_MASK_AIE))) {
> + disable_timer(s);
> + }
> +
> + if (data & CTRL_MASK_TIE) {
> + enable_countdown_timer(s);
> + }
> +
> + s->nvram[CONTROL_REGISTER] = data;
> + s->nvram[EXT_CONTROL_REGISTER] = data;
> +}
> +
> +/**
> + * Validate the flag register
> + * @param s the RTC to operate on
> + * @param data the new value for the flag register
> + */
> +static void validate_flag_register(RX8900State *s, uint8_t *data)
> +{
> + uint8_t diffmask = ~s->nvram[FLAG_REGISTER] & *data;
> +
> + if (diffmask & FLAG_MASK_VDET) {
> + *data &= ~FLAG_MASK_VDET;
> + error_report("WARNING: RX8900 - "
> + "Only 0 can be written to VDET bit %d in the flag register",
> + FLAG_REG_VDET);
> + }
> +
> + if (diffmask & FLAG_MASK_VLF) {
> + *data &= ~FLAG_MASK_VLF;
> + error_report("WARNING: RX8900 - "
> + "Only 0 can be written to VLF bit %d in the flag register",
> + FLAG_REG_VLF);
> + }
> +
> + if (diffmask & FLAG_MASK_UNUSED_2) {
> + *data &= ~FLAG_MASK_UNUSED_2;
> + error_report("WARNING: RX8900 - "
> + "Only 0 can be written to unused bit %d in the flag register",
> + FLAG_REG_UNUSED_2);
> + }
> +
> + if (diffmask & FLAG_MASK_UNUSED_6) {
> + *data &= ~FLAG_MASK_UNUSED_6;
> + error_report("WARNING: RX8900 - "
> + "Only 0 can be written to unused bit %d in the flag register",
> + FLAG_REG_UNUSED_6);
> + }
> +
> + if (diffmask & FLAG_MASK_UNUSED_7) {
> + *data &= ~FLAG_MASK_UNUSED_7;
> + error_report("WARNING: RX8900 - "
> + "Only 0 can be written to unused bit %d in the flag register",
> + FLAG_REG_UNUSED_7);
> + }
> +}
> +
> +/**
> + * Tick the per second timer (can be called more frequently as it early exits
> + * if the wall clock has not progressed)
> + * @param opaque the RTC to tick
> + */
> +static void rx8900_timer_tick(void *opaque)
> +{
> + RX8900State *s = (RX8900State *)opaque;
> + struct tm now;
> + bool fire_interrupt = false;
> +
> + qemu_get_timedate(&now, s->offset);
> +
> + if (now.tm_sec == s->last_interrupt_seconds) {
> + return;
> + }
> +
> + s->last_interrupt_seconds = now.tm_sec;
> +
> + TRACE(s->parent_obj, "Tick");
> +
> + /* Update timer interrupt */
> + if (s->nvram[CONTROL_REGISTER] & CTRL_MASK_UIE) {
> + if ((s->nvram[EXTENSION_REGISTER] & EXT_MASK_USEL) &&
> + now.tm_min != s->last_update_interrupt_minutes) {
> + s->last_update_interrupt_minutes = now.tm_min;
> + s->nvram[FLAG_REGISTER] |= FLAG_MASK_UF;
> + fire_interrupt = true;
> + } else if (!(s->nvram[EXTENSION_REGISTER] & EXT_MASK_USEL)) {
> + /* per second update interrupt */
> + s->nvram[FLAG_REGISTER] |= FLAG_MASK_UF;
> + fire_interrupt = true;
> + }
> + }
> +
> + /* Alarm interrupt */
> + if ((s->nvram[CONTROL_REGISTER] & CTRL_MASK_AIE) && now.tm_sec == 0) {
> + if (s->nvram[ALARM_MINUTE] == to_bcd(now.tm_min) &&
> + s->nvram[ALARM_HOUR] == to_bcd(now.tm_hour) &&
> + s->nvram[ALARM_WEEK_DAY] ==
> + ((s->nvram[EXTENSION_REGISTER] & EXT_MASK_WADA) ?
> + to_bcd(now.tm_mday) :
> + 0x01 << ((now.tm_wday + s->wday_offset) %
> 7))) {
that's a nice if condition :) May be we could use a temp variable for
the last one.
> + TRACE(s->parent_obj, "Triggering alarm");
> + s->nvram[FLAG_REGISTER] |= FLAG_MASK_AF;
> + fire_interrupt = true;
> + }
> + }
> +
> + if (fire_interrupt) {
> + TRACE(s->parent_obj, "Pulsing interrupt");
> + qemu_irq_pulse(s->interrupt_pin);
> + }
> +}
> +
> +/**
> + * Disable the per second timer
> + * @param s the RTC to operate on
> + */
> +static void disable_timer(RX8900State *s)
> +{
> + TRACE(s->parent_obj, "Disabling timer");
> + ptimer_stop(s->sec_timer);
> +}
> +
> +/**
> + * Enable the per second timer
> + * @param s the RTC to operate on
> + */
> +static void enable_timer(RX8900State *s)
> +{
> + TRACE(s->parent_obj, "Enabling timer");
> + ptimer_run(s->sec_timer, 0);
> +}
> +
> +/**
> + * Handle FOUT_ENABLE (FOE) line
> + * Enables/disables the FOUT line
> + * @param opaque the device instance
> + * @param n the IRQ number
> + * @param level true if the line has been raised
> + */
> +static void rx8900_fout_enable_handler(void *opaque, int n, int level)
> +{
> + RX8900State *s = RX8900(opaque);
> +
> + if (level) {
> + TRACE(s->parent_obj, "Enabling fout");
> + ptimer_run(s->fout_timer, 0);
> + } else {
> + /* disable fout */
> + TRACE(s->parent_obj, "Disabling fout");
> + ptimer_stop(s->fout_timer);
> + }
> +}
> +
> +/**
> + * Tick the FOUT timer
> + * @param opaque the device instance
> + */
> +static void rx8900_fout_tick(void *opaque)
> +{
> + RX8900State *s = (RX8900State *)opaque;
> +
> + TRACE(s->parent_obj, "fout toggle");
> + s->fout = !s->fout;
> +
> + if (s->fout) {
> + qemu_irq_raise(s->fout_pin);
> + } else {
> + qemu_irq_lower(s->fout_pin);
> + }
> +}
> +
> +
> +/**
> + * Disable the countdown timer
> + * @param s the RTC to operate on
> + */
> +static void disable_countdown_timer(RX8900State *s)
> +{
> + TRACE(s->parent_obj, "Disabling countdown timer");
> + ptimer_stop(s->countdown_timer);
> +}
> +
> +/**
> + * Enable the per second timer
> + * @param s the RTC to operate on
> + */
> +static void enable_countdown_timer(RX8900State *s)
> +{
> + TRACE(s->parent_obj, "Enabling countdown timer");
> + ptimer_run(s->countdown_timer, 0);
> +}
These helpers don't add much I think.
> +/**
> + * Tick the countdown timer
> + * @param opaque the device instance
> + */
> +static void rx8900_countdown_tick(void *opaque)
> +{
> + RX8900State *s = (RX8900State *)opaque;
> +
> + uint16_t count = s->nvram[TIMER_COUNTER_0] +
> + ((s->nvram[TIMER_COUNTER_1] & 0x0F) << 8);
> + TRACE(s->parent_obj, "countdown tick, count=%d", count);
> + count--;
> +
> + s->nvram[TIMER_COUNTER_0] = (uint8_t)(count & 0x00ff);
> + s->nvram[TIMER_COUNTER_1] = (uint8_t)((count & 0x0f00) >> 8);
> +
> + if (count == 0) {
> + TRACE(s->parent_obj, "Countdown has elapsed, pulsing interrupt");
> +
> + disable_countdown_timer(s);
> +
> + s->nvram[FLAG_REGISTER] |= FLAG_MASK_TF;
> + qemu_irq_pulse(s->interrupt_pin);
> + }
> +}
> +
> +
> +/**
> + * Receive a byte of data from i2c
> + * @param i2c the i2c device that is receiving data
> + * @param data the data that was received
> + */
> +static int rx8900_send(I2CSlave *i2c, uint8_t data)
> +{
> + RX8900State *s = RX8900(i2c);
> + struct tm now;
> +
> + TRACE(s->parent_obj, "Received I2C data 0x%02x", data);
> +
> + if (s->addr_byte) {
> + s->ptr = data & (RX8900_NVRAM_SIZE - 1);
> + TRACE(s->parent_obj, "Operating on register 0x%02x", s->ptr);
> + s->addr_byte = false;
> + return 0;
> + }
> +
> + TRACE(s->parent_obj, "Set data 0x%02x=0x%02x", s->ptr, data);
> +
> + qemu_get_timedate(&now, s->offset);
> + switch (s->ptr) {
> + case SECONDS:
> + case EXT_SECONDS:
> + now.tm_sec = from_bcd(data & 0x7f);
> + s->offset = qemu_timedate_diff(&now);
> + break;
> +
> + case MINUTES:
> + case EXT_MINUTES:
> + now.tm_min = from_bcd(data & 0x7f);
> + s->offset = qemu_timedate_diff(&now);
> + break;
> +
> + case HOURS:
> + case EXT_HOURS:
> + now.tm_hour = from_bcd(data & 0x3f);
> + s->offset = qemu_timedate_diff(&now);
> + break;
> +
> + case WEEKDAY:
> + case EXT_WEEKDAY: {
> + int user_wday = ctz32(data);
> + /* The day field is supposed to contain a value in
> + * the range 0-6. Otherwise behavior is undefined.
> + */
> + switch (data) {
> + case 0x01:
> + case 0x02:
> + case 0x04:
> + case 0x08:
> + case 0x10:
> + case 0x20:
> + case 0x40:
> + break;
> + default:
> + error_report("WARNING: RX8900 - weekday data '%x' is out of
> range,"
> + " undefined behavior will result", data);
> + break;
> + }
> + s->weekday = user_wday;
> + break;
> + }
> +
> + case DAY:
> + case EXT_DAY:
> + now.tm_mday = from_bcd(data & 0x3f);
> + s->offset = qemu_timedate_diff(&now);
> + break;
> +
> + case MONTH:
> + case EXT_MONTH:
> + now.tm_mon = from_bcd(data & 0x1f) - 1;
> + s->offset = qemu_timedate_diff(&now);
> + break;
> +
> + case YEAR:
> + case EXT_YEAR:
> + now.tm_year = from_bcd(data) + 100;
> + s->offset = qemu_timedate_diff(&now);
> + break;
> +
> + case EXTENSION_REGISTER:
> + case EXT_EXTENSION_REGISTER:
> + update_extension_register(s, data);
> + break;
> +
> + case FLAG_REGISTER:
> + case EXT_FLAG_REGISTER:
> + validate_flag_register(s, &data);
> +
> + s->nvram[FLAG_REGISTER] = data;
> + s->nvram[EXT_FLAG_REGISTER] = data;
> + break;
> +
> + case CONTROL_REGISTER:
> + case EXT_CONTROL_REGISTER:
> + update_control_register(s, data);
> + break;
> +
> + default:
> + s->nvram[s->ptr] = data;
> + }
> +
> + inc_regptr(s);
> + return 0;
> +}
> +
> +/**
> + * Get the device temperature in Celcius as a property
> + * @param obj the device
> + * @param v
> + * @param name the property name
> + * @param opaque
> + * @param errp an error object to populate on failure
> + */
> +static void rx8900_get_temperature(Object *obj, Visitor *v, const char *name,
> + void *opaque, Error **errp)
> +{
> + RX8900State *s = RX8900(obj);
> + double value = (s->nvram[TEMPERATURE] * 2.0f - 187.1f) / 3.218f;
> +
> + TRACE(s->parent_obj, "Read temperature property, 0x%x = %f°C",
> + s->nvram[TEMPERATURE], value);
> +
> + visit_type_number(v, name, &value, errp);
> +}
> +
> +/**
> + * Set the device temperature in Celcius as a property
> + * @param obj the device
> + * @param v
> + * @param name the property name
> + * @param opaque
> + * @param errp an error object to populate on failure
> + */
> +static void rx8900_set_temperature(Object *obj, Visitor *v, const char *name,
> + void *opaque, Error **errp)
> +{
> + RX8900State *s = RX8900(obj);
> + Error *local_err = NULL;
> + double temp; /* degrees Celcius */
> + visit_type_number(v, name, &temp, &local_err);
> + if (local_err) {
> + error_propagate(errp, local_err);
> + return;
> + }
> + if (temp >= 100 || temp < -58) {
> + error_setg(errp, "value %f°C is out of range", temp);
> + return;
> + }
> +
> + s->nvram[TEMPERATURE] = (uint8_t) ((temp * 3.218f + 187.19f) / 2);
> +
> + TRACE(s->parent_obj, "Set temperature property, 0x%x = %f°C",
> + s->nvram[TEMPERATURE], temp);
> +}
> +
> +
> +/**
> + * Initialize the device
> + * @param i2c the i2c device instance
> + */
> +static int rx8900_init(I2CSlave *i2c)
> +{
> + TRACE(*i2c, "Initialized");
> +
> + return 0;
> +}
you can remove this routine.
> +/**
> + * Configure device properties
> + * @param obj the device
> + */
> +static void rx8900_initfn(Object *obj)
> +{
> + object_property_add(obj, "temperature", "number",
> + rx8900_get_temperature,
> + rx8900_set_temperature, NULL, NULL, NULL);
> +}
> +
> +/**
> + * Reset the device
> + * @param dev the RX8900 device to reset
> + */
> +static void rx8900_reset(DeviceState *dev)
> +{
> + RX8900State *s = RX8900(dev);
> +
> + TRACE(s->parent_obj, "Reset");
> +
> + /* The clock is running and synchronized with the host */
> + s->offset = 0;
> + s->weekday = 7; /* Set to an invalid value */
> +
> + /* Temperature formulation from the datasheet
> + * ( TEMP[ 7:0 ] * 2 - 187.19) / 3.218
> + *
> + * Set the initial state to 25 degrees Celcius
> + */
> + s->nvram[TEMPERATURE] = 135; /* (25 * 3.218 + 187.19) / 2 */
> +
> + s->nvram[EXTENSION_REGISTER] = EXT_MASK_TSEL1;
> + s->nvram[CONTROL_REGISTER] = CTRL_MASK_CSEL0;
> + s->nvram[FLAG_REGISTER] = FLAG_MASK_VLF | FLAG_MASK_VDET;
Just asking : why not fully memset(0) the nvram and then set
the values ?
Thanks,
C.
> + s->ptr = 0;
> + TRACE(s->parent_obj, "Operating on register 0x%02x", s->ptr);
> +
> + s->addr_byte = false;
> +}
> +
> +/**
> + * Realize an RX8900 device instance
> + * Set up timers
> + * Configure GPIO lines
> + * @param dev the device instance to realize
> + * @param errp an error object to populate on error
> + */
> +static void rx8900_realize(DeviceState *dev, Error **errp)
> +{
> + RX8900State *s = RX8900(dev);
> + I2CSlave *i2c = I2C_SLAVE(dev);
> + QEMUBH *bh;
> + char name[64];
> +
> + s->fout = false;
> +
> + memset(s->nvram, 0, RX8900_NVRAM_SIZE);
> +
> + bh = qemu_bh_new(rx8900_timer_tick, s);
> + s->sec_timer = ptimer_init(bh, PTIMER_POLICY_DEFAULT);
> + /* we trigger the timer at 10Hz and check for rollover, as the qemu
> + * clock does not advance in realtime in the test environment,
> + * leading to unstable test results
> + */
> + ptimer_set_freq(s->sec_timer, 10);
> + ptimer_set_limit(s->sec_timer, 1, 1);
> +
> + bh = qemu_bh_new(rx8900_fout_tick, s);
> + s->fout_timer = ptimer_init(bh, PTIMER_POLICY_DEFAULT);
> + /* frequency doubled to generate 50% duty cycle square wave */
> + ptimer_set_freq(s->fout_timer, 32768 * 2);
> + ptimer_set_limit(s->fout_timer, 1, 1);
> +
> + bh = qemu_bh_new(rx8900_countdown_tick, s);
> + s->countdown_timer = ptimer_init(bh, PTIMER_POLICY_DEFAULT);
> + ptimer_set_freq(s->countdown_timer, 4096);
> + ptimer_set_limit(s->countdown_timer, 4096, 1);
> +
> +
> + snprintf(name, sizeof(name), "rx8900-interrupt-out");
> + qdev_init_gpio_out_named(&i2c->qdev, &s->interrupt_pin, name, 1);
> + TRACE(s->parent_obj, "Interrupt pin is '%s'", name);
> +
> + snprintf(name, sizeof(name), "rx8900-fout-enable");
> + qdev_init_gpio_in_named(&i2c->qdev, rx8900_fout_enable_handler, name, 1);
> + TRACE(s->parent_obj, "Fout-enable pin is '%s'", name);
> +
> + snprintf(name, sizeof(name), "rx8900-fout");
> + qdev_init_gpio_out_named(&i2c->qdev, &s->fout_pin, name, 1);
> + TRACE(s->parent_obj, "Fout pin is '%s'", name);
> +}
> +
> +/**
> + * Set up the device callbacks
> + * @param klass the device class
> + * @param data
> + */
> +static void rx8900_class_init(ObjectClass *klass, void *data)
> +{
> + DeviceClass *dc = DEVICE_CLASS(klass);
> + I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
> +
> + k->init = rx8900_init;
> + k->event = rx8900_event;
> + k->recv = rx8900_recv;
> + k->send = rx8900_send;
> + dc->realize = rx8900_realize;
> + dc->reset = rx8900_reset;
> + dc->vmsd = &vmstate_rx8900;
> +}
> +
> +static const TypeInfo rx8900_info = {
> + .name = TYPE_RX8900,
> + .parent = TYPE_I2C_SLAVE,
> + .instance_size = sizeof(RX8900State),
> + .instance_init = rx8900_initfn,
> + .class_init = rx8900_class_init,
> +};
> +
> +/**
> + * Register the device with QEMU
> + */
> +static void rx8900_register_types(void)
> +{
> + log = getenv("RX8900_TRACE") != NULL;
> + type_register_static(&rx8900_info);
> +}
> +
> +type_init(rx8900_register_types)
> diff --git a/hw/timer/rx8900_regs.h b/hw/timer/rx8900_regs.h
> new file mode 100644
> index 0000000..5261c76
> --- /dev/null
> +++ b/hw/timer/rx8900_regs.h
> @@ -0,0 +1,125 @@
> +/*
> + * Epson RX8900SA/CE Realtime Clock Module
> + *
> + * Copyright (c) 2016 IBM Corporation
> + * Authors:
> + * Alastair D'Silva <address@hidden>
> + *
> + * This code is licensed under the GPL version 2 or later. See
> + * the COPYING file in the top-level directory.
> + *
> + * Datasheet available at:
> + * https://support.epson.biz/td/api/doc_check.php?dl=app_RX8900CE&lang=en
> + *
> + */
> +
> +#ifndef RX8900_REGS_H
> +#define RX8900_REGS_H
> +
> +#include "qemu/bitops.h"
> +
> +#define RX8900_NVRAM_SIZE 0x20
> +
> +typedef enum RX8900Addresses {
> + SECONDS = 0x00,
> + MINUTES = 0x01,
> + HOURS = 0x02,
> + WEEKDAY = 0x03,
> + DAY = 0x04,
> + MONTH = 0x05,
> + YEAR = 0x06,
> + RAM = 0x07,
> + ALARM_MINUTE = 0x08,
> + ALARM_HOUR = 0x09,
> + ALARM_WEEK_DAY = 0x0A,
> + TIMER_COUNTER_0 = 0x0B,
> + TIMER_COUNTER_1 = 0x0C,
> + EXTENSION_REGISTER = 0x0D,
> + FLAG_REGISTER = 0X0E,
> + CONTROL_REGISTER = 0X0F,
> + EXT_SECONDS = 0x010, /* Alias of SECONDS */
> + EXT_MINUTES = 0x11, /* Alias of MINUTES */
> + EXT_HOURS = 0x12, /* Alias of HOURS */
> + EXT_WEEKDAY = 0x13, /* Alias of WEEKDAY */
> + EXT_DAY = 0x14, /* Alias of DAY */
> + EXT_MONTH = 0x15, /* Alias of MONTH */
> + EXT_YEAR = 0x16, /* Alias of YEAR */
> + TEMPERATURE = 0x17,
> + BACKUP_FUNCTION = 0x18,
> + NO_USE_1 = 0x19,
> + NO_USE_2 = 0x1A,
> + EXT_TIMER_COUNTER_0 = 0x1B, /* Alias of TIMER_COUNTER_0 */
> + EXT_TIMER_COUNTER_1 = 0x1C, /* Alias of TIMER_COUNTER_1 */
> + EXT_EXTENSION_REGISTER = 0x1D, /* Alias of EXTENSION_REGISTER */
> + EXT_FLAG_REGISTER = 0X1E, /* Alias of FLAG_REGISTER */
> + EXT_CONTROL_REGISTER = 0X1F /* Alias of CONTROL_REGISTER */
> +} RX8900Addresses;
> +
> +typedef enum ExtRegBits {
> + EXT_REG_TSEL0 = 0,
> + EXT_REG_TSEL1 = 1,
> + EXT_REG_FSEL0 = 2,
> + EXT_REG_FSEL1 = 3,
> + EXT_REG_TE = 4,
> + EXT_REG_USEL = 5,
> + EXT_REG_WADA = 6,
> + EXT_REG_TEST = 7
> +} ExtRegBits;
> +
> +typedef enum ExtRegMasks {
> + EXT_MASK_TSEL0 = BIT(0),
> + EXT_MASK_TSEL1 = BIT(1),
> + EXT_MASK_FSEL0 = BIT(2),
> + EXT_MASK_FSEL1 = BIT(3),
> + EXT_MASK_TE = BIT(4),
> + EXT_MASK_USEL = BIT(5),
> + EXT_MASK_WADA = BIT(6),
> + EXT_MASK_TEST = BIT(7)
> +} ExtRegMasks;
> +
> +typedef enum CtrlRegBits {
> + CTRL_REG_RESET = 0,
> + CTRL_REG_WP0 = 1,
> + CTRL_REG_WP1 = 2,
> + CTRL_REG_AIE = 3,
> + CTRL_REG_TIE = 4,
> + CTRL_REG_UIE = 5,
> + CTRL_REG_CSEL0 = 6,
> + CTRL_REG_CSEL1 = 7
> +} CtrlRegBits;
> +
> +typedef enum CtrlRegMask {
> + CTRL_MASK_RESET = BIT(0),
> + CTRL_MASK_WP0 = BIT(1),
> + CTRL_MASK_WP1 = BIT(2),
> + CTRL_MASK_AIE = BIT(3),
> + CTRL_MASK_TIE = BIT(4),
> + CTRL_MASK_UIE = BIT(5),
> + CTRL_MASK_CSEL0 = BIT(6),
> + CTRL_MASK_CSEL1 = BIT(7)
> +} CtrlRegMask;
> +
> +typedef enum FlagRegBits {
> + FLAG_REG_VDET = 0,
> + FLAG_REG_VLF = 1,
> + FLAG_REG_UNUSED_2 = 2,
> + FLAG_REG_AF = 3,
> + FLAG_REG_TF = 4,
> + FLAG_REG_UF = 5,
> + FLAG_REG_UNUSED_6 = 6,
> + FLAG_REG_UNUSED_7 = 7
> +} FlagRegBits;
> +
> +#define RX8900_INTERRUPT_SOURCES 6
> +typedef enum FlagRegMask {
> + FLAG_MASK_VDET = BIT(0),
> + FLAG_MASK_VLF = BIT(1),
> + FLAG_MASK_UNUSED_2 = BIT(2),
> + FLAG_MASK_AF = BIT(3),
> + FLAG_MASK_TF = BIT(4),
> + FLAG_MASK_UF = BIT(5),
> + FLAG_MASK_UNUSED_6 = BIT(6),
> + FLAG_MASK_UNUSED_7 = BIT(7)
> +} FlagRegMask;
> +
> +#endif
> diff --git a/tests/Makefile.include b/tests/Makefile.include
> index e98d3b6..e52e355 100644
> --- a/tests/Makefile.include
> +++ b/tests/Makefile.include
> @@ -300,6 +300,7 @@ check-qtest-sparc64-y = tests/endianness-test$(EXESUF)
>
> check-qtest-arm-y = tests/tmp105-test$(EXESUF)
> check-qtest-arm-y += tests/ds1338-test$(EXESUF)
> +check-qtest-arm-y += tests/rx8900-test$(EXESUF)
> check-qtest-arm-y += tests/m25p80-test$(EXESUF)
> gcov-files-arm-y += hw/misc/tmp105.c
> check-qtest-arm-y += tests/virtio-blk-test$(EXESUF)
> @@ -637,6 +638,7 @@ tests/bios-tables-test$(EXESUF): tests/bios-tables-test.o
> \
> tests/pxe-test$(EXESUF): tests/pxe-test.o tests/boot-sector.o $(libqos-obj-y)
> tests/tmp105-test$(EXESUF): tests/tmp105-test.o $(libqos-omap-obj-y)
> tests/ds1338-test$(EXESUF): tests/ds1338-test.o $(libqos-imx-obj-y)
> +tests/rx8900-test$(EXESUF): tests/rx8900-test.o $(libqos-imx-obj-y)
> tests/m25p80-test$(EXESUF): tests/m25p80-test.o
> tests/i440fx-test$(EXESUF): tests/i440fx-test.o $(libqos-pc-obj-y)
> tests/q35-test$(EXESUF): tests/q35-test.o $(libqos-pc-obj-y)
> diff --git a/tests/rx8900-test.c b/tests/rx8900-test.c
> new file mode 100644
> index 0000000..a56426b
> --- /dev/null
> +++ b/tests/rx8900-test.c
> @@ -0,0 +1,800 @@
> +/*
> + * QTest testcase for the Enpes RX8900SA/CE RTC
> + *
> + * Copyright (c) 2016 IBM Corporation
> + * Authors:
> + * Alastair D'Silva <address@hidden>
> + *
> + * This code is licensed under the GPL version 2 or later. See
> + * the COPYING file in the top-level directory.
> + */
> +
> +#include "qemu/osdep.h"
> +#include "hw/timer/rx8900_regs.h"
> +#include "libqtest.h"
> +#include "libqos/i2c.h"
> +#include "qemu/timer.h"
> +
> +#define IMX25_I2C_0_BASE 0x43F80000
> +#define RX8900_TEST_ID "rx8900-test"
> +#define RX8900_ADDR 0x32
> +#define RX8900_INTERRUPT_OUT "rx8900-interrupt-out"
> +#define RX8900_FOUT_ENABLE "rx8900-fout-enable"
> +#define RX8900_FOUT "rx8900-fout"
>
> +static I2CAdapter *i2c;
> +static uint8_t addr;
> +
> +static inline uint8_t bcd2bin(uint8_t x)
> +{
> + return (x & 0x0f) + (x >> 4) * 10;
> +}
> +
> +static inline uint8_t bin2bcd(uint8_t x)
> +{
> + return (x / 10 << 4) | (x % 10);
> +}
> +
> +static void qmp_rx8900_set_temperature(const char *id, double value)
> +{
> + QDict *response;
> +
> + response = qmp("{ 'execute': 'qom-set', 'arguments': { 'path': %s, "
> + "'property': 'temperature', 'value': %f } }", id, value);
> + g_assert(qdict_haskey(response, "return"));
> + QDECREF(response);
> +}
> +
> +/**
> + * Read an RX8900 register
> + * @param reg the address of the register
> + * @return the value of the register
> + */
> +static uint8_t read_register(RX8900Addresses reg)
> +{
> + uint8_t val;
> + uint8_t reg_address = (uint8_t)reg;
> +
> + i2c_send(i2c, addr, ®_address, 1);
> + i2c_recv(i2c, addr, &val, 1);
> +
> + return val;
> +}
> +
> +/**
> + * Write to an RX8900 register
> + * @param reg the address of the register
> + * @param val the value to write
> + */
> +static uint8_t write_register(RX8900Addresses reg, uint8_t val)
> +{
> + uint8_t buf[2];
> +
> + buf[0] = reg;
> + buf[1] = val;
> +
> + i2c_send(i2c, addr, buf, 2);
> +
> + return val;
> +}
> +
> +/**
> + * Set bits in a register
> + * @param reg the address of the register
> + * @param mask a mask of the bits to set
> + */
> +static void set_bits_in_register(RX8900Addresses reg, uint8_t mask)
> +{
> + uint8_t value = read_register(reg);
> + value |= mask;
> + write_register(reg, value);
> +}
> +
> +/**
> + * Clear bits in a register
> + * @param reg the address of the register
> + * @param mask a mask of the bits to set
> + */
> +static void clear_bits_in_register(RX8900Addresses reg, uint8_t mask)
> +{
> + uint8_t value = read_register(reg);
> + value &= ~mask;
> + write_register(reg, value);
> +}
> +
> +/**
> + * Read a number of sequential RX8900 registers
> + * @param reg the address of the first register
> + * @param buf (out) an output buffer to stash the register values
> + * @param count the number of registers to read
> + */
> +static void read_registers(RX8900Addresses reg, uint8_t *buf, uint8_t count)
> +{
> + uint8_t reg_address = (uint8_t)reg;
> +
> + i2c_send(i2c, addr, ®_address, 1);
> + i2c_recv(i2c, addr, buf, count);
> +}
> +
> +/**
> + * Write to a sequential number of RX8900 registers
> + * @param reg the address of the first register
> + * @param buffer a buffer of values to write
> + * @param count the sumber of registers to write
> + */
> +static void write_registers(RX8900Addresses reg, uint8_t *buffer, uint8_t
> count)
> +{
> + uint8_t buf[RX8900_NVRAM_SIZE + 1];
> +
> + buf[0] = (uint8_t)reg;
> + memcpy(buf + 1, buffer, count);
> +
> + i2c_send(i2c, addr, buf, count + 1);
> +}
> +
> +/**
> + * Set the time on the RX8900
> + * @param secs the seconds to set
> + * @param mins the minutes to set
> + * @param hours the hours to set
> + * @param weekday the day of the week to set (0 = Sunday)
> + * @param day the day of the month to set
> + * @param month the month to set
> + * @param year the year to set
> + */
> +static void set_time(uint8_t secs, uint8_t mins, uint8_t hours,
> + uint8_t weekday, uint8_t day, uint8_t month, uint8_t year)
> +{
> + uint8_t buf[7];
> +
> + buf[0] = bin2bcd(secs);
> + buf[1] = bin2bcd(mins);
> + buf[2] = bin2bcd(hours);
> + buf[3] = BIT(weekday);
> + buf[4] = bin2bcd(day);
> + buf[5] = bin2bcd(month);
> + buf[6] = bin2bcd(year);
> +
> + write_registers(SECONDS, buf, 7);
> +}
> +
> +
> +/**
> + * Check basic communication
> + */
> +static void send_and_receive(void)
> +{
> + uint8_t buf[7];
> + time_t now = time(NULL);
> + struct tm *tm_ptr;
> +
> + /* retrieve the date */
> + read_registers(SECONDS, buf, 7);
> +
> + tm_ptr = gmtime(&now);
> +
> + /* check retrieved time against local time */
> + g_assert_cmpuint(bcd2bin(buf[0]), == , tm_ptr->tm_sec);
> + g_assert_cmpuint(bcd2bin(buf[1]), == , tm_ptr->tm_min);
> + g_assert_cmpuint(bcd2bin(buf[2]), == , tm_ptr->tm_hour);
> + g_assert_cmpuint(bcd2bin(buf[4]), == , tm_ptr->tm_mday);
> + g_assert_cmpuint(bcd2bin(buf[5]), == , 1 + tm_ptr->tm_mon);
> + g_assert_cmpuint(2000 + bcd2bin(buf[6]), == , 1900 + tm_ptr->tm_year);
> +}
> +
> +/**
> + * Check that the temperature can be altered via properties
> + */
> +static void check_temperature(void)
> +{
> + /* Check the initial temperature is 25C */
> + uint8_t temperature;
> +
> + temperature = read_register(TEMPERATURE);
> + g_assert_cmpuint(temperature, == , 135);
> +
> + /* Set the temperature to 40C and check the temperature again */
> + qmp_rx8900_set_temperature(RX8900_TEST_ID, 40.0f);
> + temperature = read_register(TEMPERATURE);
> + g_assert_cmpuint(temperature, == , 157);
> +}
> +
> +/**
> + * Check that the time rolls over correctly
> + */
> +static void check_rollover(void)
> +{
> + uint8_t buf[7];
> +
> +
> + set_time(59, 59, 23, 1, 29, 2, 16);
> +
> + /* Wait for the clock to rollover */
> + sleep(2);
> +
> + memset(buf, 0, sizeof(buf));
> +
> + /* Check that the clock rolled over */
> + /* Read from registers starting at 0x00 */
> + buf[0] = 0x00;
> +
> + read_registers(SECONDS, buf, 7);
> +
> + /* Ignore seconds as there may be some noise,
> + * we expect 00:00:xx Tuesday 1/3/2016
> + */
> + g_assert_cmpuint(bcd2bin(buf[1]), == , 0);
> + g_assert_cmpuint(bcd2bin(buf[2]), == , 0);
> + g_assert_cmpuint(bcd2bin(buf[3]), == , 0x04);
> + g_assert_cmpuint(bcd2bin(buf[4]), == , 1);
> + g_assert_cmpuint(bcd2bin(buf[5]), == , 3);
> + g_assert_cmpuint(bcd2bin(buf[6]), == , 16);
> +}
> +
> +uint32_t interrupt_counts[RX8900_INTERRUPT_SOURCES];
> +
> +/**
> + * Reset the interrupt counts
> + */
> +static void count_reset(void)
> +{
> + for (int source = 0; source < RX8900_INTERRUPT_SOURCES; source++) {
> + interrupt_counts[source] = 0;
> + }
> +}
> +
> +/**
> + * Handle an RX8900 interrupt (update the counts for that interrupt type)
> + */
> +static void handle_interrupt(void *opaque, const char *name, int irq,
> + bool level)
> +{
> + if (!level) {
> + return;
> + }
> +
> + uint8_t flags = read_register(FLAG_REGISTER);
> +
> + for (int flag = 0; flag < 8; flag++) {
> + if (flags & BIT(flag)) {
> + interrupt_counts[flag]++;
> + }
> + }
> +
> + write_register(FLAG_REGISTER, 0x00);
> +}
> +
> +uint32_t fout_counts;
> +
> +/**
> + * Handle an Fout state change
> + */
> +static void handle_fout(void *opaque, const char *name, int irq, bool level)
> +{
> + if (!level) {
> + return;
> + }
> +
> + fout_counts++;
> +}
> +
> +/**
> + * Reset the fout count
> + */
> +static void fout_count_reset(void)
> +{
> + fout_counts = 0;
> +}
> +
> +
> +/**
> + * Sleep for some real time while counting interrupts
> + * @param delay the delay in microseconds
> + * @param loop the loop time in microseconds
> + */
> +static void wait_for(uint64_t delay, uint64_t loop)
> +{
> + struct timeval end, now;
> +
> + gettimeofday(&end, NULL);
> + delay += end.tv_usec;
> + end.tv_sec += delay / 1000000;
> + end.tv_usec = delay % 1000000;
> +
> + while (gettimeofday(&now, NULL),
> + now.tv_sec < end.tv_sec || now.tv_usec < end.tv_usec) {
> + clock_step(loop * 1000);
> + usleep(loop);
> + }
> +}
> +
> +/**
> + * Sleep for some emulated time while counting interrupts
> + * @param delay the delay in nanoseconds
> + * @param loop the loop time in nanoseconds
> + */
> +static void wait_cycles(uint64_t delay, uint64_t loop)
> +{
> + uint64_t counter;
> +
> + for (counter = 0; counter < delay; counter += loop) {
> + clock_step(loop);
> + }
> +}
> +
> +
> +/**
> + * Check that when the update timer interrupt is disabled, that no interrupts
> + * occur
> + */
> +static void check_update_interrupt_disabled(void)
> +{
> + /* Disable the update interrupt */
> + clear_bits_in_register(CONTROL_REGISTER, CTRL_MASK_UIE);
> +
> + /* Wait for the clock to rollover, this will cover both seconds & minutes
> + */
> + set_time(59, 59, 23, 1, 29, 2, 16);
> +
> + count_reset();
> + wait_for(2 * 1000000, 1000);
> +
> + g_assert_cmpuint(interrupt_counts[FLAG_REG_UF], ==, 0);
> + g_assert_cmpuint(interrupt_counts[FLAG_REG_AF], ==, 0);
> +}
> +
> +
> +/**
> + * Check that when the update timer interrupt is enabled and configured for
> + * per second updates, that we get the appropriate number of interrupts
> + */
> +static void check_update_interrupt_seconds(void)
> +{
> + set_time(59, 59, 23, 1, 29, 2, 16);
> +
> + /* Enable the update interrupt for per second updates */
> + clear_bits_in_register(EXTENSION_REGISTER, EXT_MASK_USEL);
> + set_bits_in_register(CONTROL_REGISTER, CTRL_MASK_UIE);
> +
> + count_reset();
> + wait_for(5.1f * 1000000ULL, 1000);
> +
> + /* Disable the update interrupt */
> + clear_bits_in_register(CONTROL_REGISTER, CTRL_MASK_UIE);
> +
> + g_assert_cmpuint(interrupt_counts[FLAG_REG_UF], ==, 5);
> + g_assert_cmpuint(interrupt_counts[FLAG_REG_AF], ==, 0);
> +}
> +
> +/**
> + * Check that when the update timer interrupt is enabled and configured for
> + * per minute updates, that we get the appropriate number of interrupts
> + */
> +static void check_update_interrupt_minutes(void)
> +{
> + set_time(59, 59, 23, 1, 29, 2, 16);
> +
> + /* Enable the update interrupt for per minute updates */
> + set_bits_in_register(EXTENSION_REGISTER, EXT_MASK_USEL);
> + set_bits_in_register(CONTROL_REGISTER, CTRL_MASK_UIE);
> +
> + count_reset();
> + wait_for(5 * 1000000ULL, 1000);
> +
> + /* Disable the update interrupt */
> + clear_bits_in_register(CONTROL_REGISTER, CTRL_MASK_UIE);
> +
> + g_assert_cmpuint(interrupt_counts[FLAG_REG_UF], ==, 1);
> + g_assert_cmpuint(interrupt_counts[FLAG_REG_AF], ==, 0);
> +}
> +
> +
> +/**
> + * Check that when the alarm timer interrupt is disabled, that no interrupts
> + * occur
> + */
> +static void check_alarm_interrupt_disabled(void)
> +{
> + /* Disable the alarm interrupt */
> + clear_bits_in_register(CONTROL_REGISTER, CTRL_MASK_AIE);
> +
> + /* Set an alarm for midnight */
> + uint8_t buf[3];
> +
> + buf[0] = bin2bcd(0); /* minutes */
> + buf[1] = bin2bcd(0); /* hours */
> + buf[2] = bin2bcd(1); /* day */
> +
> + write_registers(ALARM_MINUTE, buf, 3);
> +
> + /* Wait for the clock to rollover */
> + set_time(59, 59, 23, 1, 29, 2, 16);
> +
> + count_reset();
> + wait_for(2 * 1000000, 1000);
> +
> + g_assert_cmpuint(interrupt_counts[FLAG_REG_UF], ==, 0);
> + g_assert_cmpuint(interrupt_counts[FLAG_REG_AF], ==, 0);
> +}
> +
> +/**
> + * Check that when the alarm timer interrupt is enabled, that an interrupt
> + * occurs
> + */
> +static void check_alarm_interrupt_day_of_month(void)
> +{
> +
> + /* Set an alarm for midnight */
> + uint8_t buf[3];
> +
> + buf[0] = bin2bcd(0); /* minutes */
> + buf[1] = bin2bcd(0); /* hours */
> + buf[2] = bin2bcd(1); /* day */
> +
> + write_registers(ALARM_MINUTE, buf, 3);
> +
> + /* Set alarm to day of month mode */
> + set_bits_in_register(EXTENSION_REGISTER, EXT_MASK_WADA);
> +
> + /* Enable the alarm interrupt */
> + set_bits_in_register(CONTROL_REGISTER, CTRL_MASK_AIE);
> +
> + /* Wait for the clock to rollover */
> + set_time(59, 59, 23, 1, 29, 2, 16);
> +
> + count_reset();
> + wait_for(2 * 1000000, 1000);
> +
> + /* Disable the alarm interrupt */
> + clear_bits_in_register(CONTROL_REGISTER, CTRL_MASK_AIE);
> +
> + g_assert_cmpuint(interrupt_counts[FLAG_REG_UF], ==, 0);
> + g_assert_cmpuint(interrupt_counts[FLAG_REG_AF], ==, 1);
> +}
> +
> +/**
> + * Check that when the alarm timer interrupt is enabled, that an interrupt
> + * does not occur
> + */
> +static void check_alarm_interrupt_day_of_month_negative(void)
> +{
> +
> + /* Set an alarm for midnight */
> + uint8_t buf[3];
> +
> + buf[0] = bin2bcd(0); /* minutes */
> + buf[1] = bin2bcd(0); /* hours */
> + buf[2] = bin2bcd(2); /* day */
> +
> + write_registers(ALARM_MINUTE, buf, 3);
> +
> + /* Set alarm to day of month mode */
> + set_bits_in_register(EXTENSION_REGISTER, EXT_MASK_WADA);
> +
> + /* Enable the alarm interrupt */
> + set_bits_in_register(CONTROL_REGISTER, CTRL_MASK_AIE);
> +
> + /* Wait for the clock to rollover */
> + set_time(59, 59, 23, 1, 29, 2, 16);
> +
> + count_reset();
> + wait_for(2 * 1000000, 1000);
> +
> + /* Disable the alarm interrupt */
> + clear_bits_in_register(CONTROL_REGISTER, CTRL_MASK_AIE);
> +
> + g_assert_cmpuint(interrupt_counts[FLAG_REG_UF], ==, 0);
> + g_assert_cmpuint(interrupt_counts[FLAG_REG_AF], ==, 0);
> +}
> +
> +/**
> + * Check that when the alarm timer interrupt is enabled, that an interrupt
> + * occurs
> + */
> +static void check_alarm_interrupt_day_of_week(void)
> +{
> +
> + /* Set an alarm for midnight */
> + uint8_t buf[3];
> +
> + buf[0] = bin2bcd(0); /* minutes */
> + buf[1] = bin2bcd(0); /* hours */
> + buf[2] = 0x01 << 2; /* day */
> +
> + write_registers(ALARM_MINUTE, buf, 3);
> +
> + /* Set alarm to day of week mode */
> + clear_bits_in_register(EXTENSION_REGISTER, EXT_MASK_WADA);
> +
> + /* Enable the alarm interrupt */
> + set_bits_in_register(CONTROL_REGISTER, CTRL_MASK_AIE);
> +
> + /* Wait for the clock to rollover */
> + set_time(59, 59, 23, 1, 29, 2, 16);
> +
> + count_reset();
> + wait_for(2 * 1000000, 1000);
> +
> + /* Disable the alarm interrupt */
> + clear_bits_in_register(CONTROL_REGISTER, CTRL_MASK_AIE);
> +
> + g_assert_cmpuint(interrupt_counts[FLAG_REG_UF], ==, 0);
> + g_assert_cmpuint(interrupt_counts[FLAG_REG_AF], ==, 1);
> +}
> +
> +/**
> + * Check that when the alarm timer interrupt is enabled, that an interrupt
> + * does not occur
> + */
> +static void check_alarm_interrupt_day_of_week_negative(void)
> +{
> +
> + /* Set an alarm for midnight */
> + uint8_t buf[3];
> +
> + buf[0] = bin2bcd(0); /* minutes */
> + buf[1] = bin2bcd(0); /* hours */
> + buf[2] = 0x01 << 2; /* day */
> +
> + write_registers(ALARM_MINUTE, buf, 3);
> +
> + /* Set alarm to day of week mode */
> + clear_bits_in_register(EXTENSION_REGISTER, EXT_MASK_WADA);
> +
> + /* Enable the alarm interrupt */
> + set_bits_in_register(CONTROL_REGISTER, CTRL_MASK_AIE);
> +
> + /* Wait for the clock to rollover */
> + set_time(59, 59, 23, 3, 29, 2, 16);
> +
> + count_reset();
> + wait_for(2 * 1000000, 1000);
> +
> + /* Disable the alarm interrupt */
> + clear_bits_in_register(CONTROL_REGISTER, CTRL_MASK_AIE);
> +
> + g_assert_cmpuint(interrupt_counts[FLAG_REG_UF], ==, 0);
> + g_assert_cmpuint(interrupt_counts[FLAG_REG_AF], ==, 0);
> +}
> +
> +/**
> + * Check that the reset function
> + */
> +static void check_reset(void)
> +{
> + set_bits_in_register(FLAG_REGISTER, FLAG_MASK_UF);
> + set_bits_in_register(CONTROL_REGISTER, CTRL_MASK_RESET);
> +
> + g_assert_cmpuint(read_register(FLAG_REGISTER), ==,
> + FLAG_MASK_VLF | FLAG_MASK_VDET);
> +}
> +
> +/**
> + * Check that Fout operates at 1Hz
> + */
> +static void check_fout_1hz(void)
> +{
> + uint8_t ext_reg = read_register(EXTENSION_REGISTER);
> + ext_reg |= EXT_MASK_FSEL1;
> + ext_reg &= ~EXT_MASK_FSEL0;
> + write_register(EXTENSION_REGISTER, ext_reg);
> +
> + /* Enable Fout */
> + irq_set(RX8900_TEST_ID, RX8900_FOUT_ENABLE, 0, true);
> +
> + fout_count_reset();
> + wait_cycles(2 * 1000000000ULL, 1000000);
> +
> + /* disable Fout */
> + irq_set(RX8900_TEST_ID, RX8900_FOUT_ENABLE, 0, false);
> +
> + g_assert_cmpuint(fout_counts, ==, 2);
> +}
> +
> +/**
> + * Check that Fout operates at 1024Hz
> + */
> +static void check_fout_1024hz(void)
> +{
> + uint8_t ext_reg = read_register(EXTENSION_REGISTER);
> + ext_reg |= EXT_MASK_FSEL0;
> + ext_reg &= ~EXT_MASK_FSEL1;
> + write_register(EXTENSION_REGISTER, ext_reg);
> +
> + /* Enable Fout */
> + irq_set(RX8900_TEST_ID, RX8900_FOUT_ENABLE, 0, true);
> +
> + fout_count_reset();
> + wait_cycles(2 * 1000000000ULL, 100000);
> +
> + /* disable Fout */
> + irq_set(RX8900_TEST_ID, RX8900_FOUT_ENABLE, 0, false);
> +
> + g_assert_cmpuint(fout_counts, ==, 1024 * 2);
> +}
> +
> +/**
> + * Check that Fout operates at 32768Hz
> + */
> +static void check_fout_32768hz(void)
> +{
> + uint8_t ext_reg = read_register(EXTENSION_REGISTER);
> + ext_reg &= ~EXT_MASK_FSEL0;
> + ext_reg &= ~EXT_MASK_FSEL1;
> + write_register(EXTENSION_REGISTER, ext_reg);
> +
> + /* Enable Fout */
> + irq_set(RX8900_TEST_ID, RX8900_FOUT_ENABLE, 0, true);
> +
> + fout_count_reset();
> + wait_cycles(2 * 1000000000ULL, 15000);
> +
> + /* disable Fout */
> + irq_set(RX8900_TEST_ID, RX8900_FOUT_ENABLE, 0, false);
> +
> + /* There appears to be some rounding errors in the timer,
> + * we'll tolerate it for now
> + */
> + g_assert_cmpuint(fout_counts, >=, 32768 * 2);
> + g_assert_cmpuint(fout_counts, <=, 65540);
> +}
> +
> +/**
> + * Check the countdown timer operates at 1 Hz
> + */
> +static void check_countdown_1hz(void)
> +{
> + uint8_t ext_reg;
> +
> + write_register(TIMER_COUNTER_0, 5);
> + write_register(TIMER_COUNTER_1, 0);
> +
> + ext_reg = read_register(EXTENSION_REGISTER);
> + ext_reg &= ~EXT_MASK_TSEL1;
> + ext_reg |= EXT_MASK_TSEL0;
> + ext_reg |= EXT_MASK_TE;
> + write_register(EXTENSION_REGISTER, ext_reg);
> +
> + count_reset();
> + wait_cycles(5 * 1000000000ULL, 1000000);
> +
> + g_assert_cmpuint(interrupt_counts[FLAG_REG_TF], ==, 0);
> +
> + wait_cycles(1 * 1000000000ULL, 1000000);
> +
> + g_assert_cmpuint(interrupt_counts[FLAG_REG_TF], ==, 1);
> +}
> +
> +/**
> + * Check the countdown timer operates at 64 Hz
> + */
> +static void check_countdown_64hz(void)
> +{
> + uint8_t ext_reg;
> +
> + write_register(TIMER_COUNTER_0, 0x40);
> + write_register(TIMER_COUNTER_1, 0x01); /* 5 * 64 */
> +
> + ext_reg = read_register(EXTENSION_REGISTER);
> + ext_reg &= ~EXT_MASK_TSEL0;
> + ext_reg &= ~EXT_MASK_TSEL1;
> + ext_reg |= EXT_MASK_TE;
> + write_register(EXTENSION_REGISTER, ext_reg);
> +
> + count_reset();
> + wait_cycles(5 * 1000000000ULL, 1000000);
> +
> + g_assert_cmpuint(interrupt_counts[FLAG_REG_TF], ==, 0);
> +
> + wait_cycles(1 * 1000000000ULL, 1000000);
> +
> + g_assert_cmpuint(interrupt_counts[FLAG_REG_TF], ==, 1);
> +}
> +
> +/**
> + * Check the countdown timer operates at 4096 Hz
> + */
> +static void check_countdown_4096hz(void)
> +{
> + uint8_t ext_reg;
> +
> + write_register(TIMER_COUNTER_0, 0xFF);
> + write_register(TIMER_COUNTER_1, 0x0F); /* 4095 */
> + ext_reg = read_register(EXTENSION_REGISTER);
> + ext_reg |= EXT_MASK_TSEL0;
> + ext_reg |= EXT_MASK_TSEL1;
> + ext_reg |= EXT_MASK_TE;
> + write_register(EXTENSION_REGISTER, ext_reg);
> +
> + count_reset();
> + wait_cycles(999755859ULL, 10000);
> +
> + g_assert_cmpuint(interrupt_counts[FLAG_REG_TF], ==, 0);
> +
> + wait_cycles(244141ULL, 10000);
> +
> + g_assert_cmpuint(interrupt_counts[FLAG_REG_TF], ==, 1);
> +}
> +
> +/**
> + * Check the countdown timer operates at 1 minute
> + */
> +static void check_countdown_1m(void)
> +{
> + uint8_t ext_reg;
> +
> + write_register(TIMER_COUNTER_0, 0x01);
> + write_register(TIMER_COUNTER_1, 0x00);
> + ext_reg = read_register(EXTENSION_REGISTER);
> + ext_reg &= ~EXT_MASK_TSEL0;
> + ext_reg |= EXT_MASK_TSEL1;
> + ext_reg |= EXT_MASK_TE;
> + write_register(EXTENSION_REGISTER, ext_reg);
> +
> + count_reset();
> + wait_cycles(59 * 1000000000ULL, 100000);
> +
> + g_assert_cmpuint(interrupt_counts[FLAG_REG_TF], ==, 0);
> +
> + wait_cycles(1000000000LL, 100000);
> +
> + g_assert_cmpuint(interrupt_counts[FLAG_REG_TF], ==, 1);
> +}
> +
> +
> +int main(int argc, char **argv)
> +{
> + QTestState *s = NULL;
> + int ret;
> + char args[255];
> + snprintf(args, sizeof(args), "-display none -machine imx25-pdk "
> + "-device rx8900,bus=i2c.0,address=0x%x,id=%s",
> + RX8900_ADDR, RX8900_TEST_ID);
> +
> + g_test_init(&argc, &argv, NULL);
> +
> + s = qtest_start(args);
> + i2c = imx_i2c_create(IMX25_I2C_0_BASE);
> + addr = RX8900_ADDR;
> +
> + irq_intercept_out(RX8900_TEST_ID);
> + irq_attach(RX8900_INTERRUPT_OUT, 0, handle_interrupt, NULL);
> + irq_attach(RX8900_FOUT, 0, handle_fout, NULL);
> +
> + qtest_add_func("/rx8900/reset", check_reset);
> + qtest_add_func("/rx8900/tx-rx", send_and_receive);
> + qtest_add_func("/rx8900/temperature", check_temperature);
> + qtest_add_func("/rx8900/rollover", check_rollover);
> + qtest_add_func("/rx8900/update-interrupt-disabled",
> + check_update_interrupt_disabled);
> + qtest_add_func("/rx8900/update-interrupt-seconds",
> + check_update_interrupt_seconds);
> + qtest_add_func("/rx8900/update-interrupt-minutes",
> + check_update_interrupt_minutes);
> + qtest_add_func("/rx8900/alarm-interrupt-disabled",
> + check_alarm_interrupt_disabled);
> + qtest_add_func("/rx8900/alarm-interrupt-month",
> + check_alarm_interrupt_day_of_month);
> + qtest_add_func("/rx8900/alarm-interrupt-month-negative",
> + check_alarm_interrupt_day_of_month_negative);
> + qtest_add_func("/rx8900/alarm-interrupt-week",
> + check_alarm_interrupt_day_of_week);
> + qtest_add_func("/rx8900/alarm-interrupt-week-negative",
> + check_alarm_interrupt_day_of_week_negative);
> + qtest_add_func("/rx8900/fout_1hz", check_fout_1hz);
> + qtest_add_func("/rx8900/fout_1024hz", check_fout_1024hz);
> + qtest_add_func("/rx8900/fout_32768hz", check_fout_32768hz);
> + qtest_add_func("/rx8900/countdown_1hz", check_countdown_1hz);
> + qtest_add_func("/rx8900/countdown_64hz", check_countdown_64hz);
> + qtest_add_func("/rx8900/countdown_4096hz", check_countdown_4096hz);
> + qtest_add_func("/rx8900/countdown_1m", check_countdown_1m);
> +
> + ret = g_test_run();
> +
> + if (s) {
> + qtest_quit(s);
> + }
> + g_free(i2c);
> +
> + return ret;
> +}
>
- [Qemu-arm] [PATCH 2/4] qtest: Support named interrupts, (continued)
- [Qemu-arm] [PATCH 2/4] qtest: Support named interrupts, Alastair D'Silva, 2016/11/17
- Re: [Qemu-arm] [PATCH 2/4] qtest: Support named interrupts, Cédric Le Goater, 2016/11/22
- Re: [Qemu-arm] [PATCH 2/4] qtest: Support named interrupts, Paolo Bonzini, 2016/11/22
- Re: [Qemu-arm] [PATCH 2/4] qtest: Support named interrupts, Alastair D'Silva, 2016/11/22
- Re: [Qemu-arm] [PATCH 2/4] qtest: Support named interrupts, Paolo Bonzini, 2016/11/22
- Re: [Qemu-arm] [PATCH 2/4] qtest: Support named interrupts, Alastair D'Silva, 2016/11/22
- Re: [Qemu-arm] [PATCH 2/4] qtest: Support named interrupts, Paolo Bonzini, 2016/11/23
- Re: [Qemu-arm] [PATCH 2/4] qtest: Support named interrupts, Edgar E. Iglesias, 2016/11/23
- Re: [Qemu-arm] [PATCH 2/4] qtest: Support named interrupts, Paolo Bonzini, 2016/11/23
[Qemu-arm] [PATCH 3/4] hw/timer: Add Epson RX8900 RTC support, Alastair D'Silva, 2016/11/17
- Re: [Qemu-arm] [PATCH 3/4] hw/timer: Add Epson RX8900 RTC support,
Cédric Le Goater <=
Re: [Qemu-arm] [Qemu-devel] [PATCH 0/4] Add support for the Epson RX8900 RTC to the aspeed board, no-reply, 2016/11/17