diff mbox

[3/4] hw/timer: Add Epson RX8900 RTC support

Message ID 1479357400-17441-4-git-send-email-alastair@au1.ibm.com
State New
Headers show

Commit Message

Alastair D'Silva Nov. 17, 2016, 4:36 a.m. UTC
From: Alastair D'Silva <alastair@d-silva.org>

This patch adds support for the Epson RX8900 RTC chip.

Signed-off-by: Alastair D'Silva <alastair@d-silva.org>
---
 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 ++++++++++++++++++++++++++++++++++++
 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

Comments

Alastair D'Silva Nov. 18, 2016, 12:19 a.m. UTC | #1
On Thu, 2016-11-17 at 09:29 +0100, Cédric Le Goater wrote:
On 11/17/2016 05:36 AM, Alastair D'Silva wrote:
> > 

> > From: Alastair D'Silva <alastair@d-silva.org>

> > 

> > 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.

> 


Ok

> 

> > 

> > Signed-off-by: Alastair D'Silva <alastair@d-silva.org>

> > ---

> >  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.

> 


The aspeed machine is missing a bunch of I2C infrastructure
(imx_i2c_create & friends) required to access it from the test harness,
and I don't have the knowledge required to implement it. I am working
on the premise that the RX8900 is an independent device, and so can be
tested independently of the aspeed model.

> 

> Also I would put the test case in another patch, after the 

> model and after patch 2 also which introduces named 

> interrupts for qtest.

> 


Ok

> 

> > 

> >  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 <alastair@d-silva.org>

> > + *

> > + * 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 %s@0x%x: %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.

> 


Ok. Would it be worth expanding this to include the file & line, or do
you expect resistance?

> 

> > 

> > +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,

> 


OK

> > 

> > +    }

> > +

> > +    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.


Ok, it was more readable with longer lines :)

> 

> > 

> > +            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.


They are called in multiple places and add tracing.

> > 

> > +/**

> > + * 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.

> 


I don't think I can, core.c:i2c_slave_qdev_init() calls it without a
guard.

> > 

> > +/**

> > + * 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 ?


I also use this function for a soft-reset, which does not clear the
nvram.

Actually, I should move the temperature out of there...


Cheers,

-- 
Alastair D'Silva
Open Source Developer
Linux Technology Centre, IBM Australia
mob: 0423 762 819
Cédric Le Goater Nov. 18, 2016, 8:52 a.m. UTC | #2
>>> +/**
>>> + * 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.
>>
> 
> I don't think I can, core.c:i2c_slave_qdev_init() calls it without a
> guard.

ah yes. So may be, we should add a test in i2c_slave_qdev_init() and
kill these :

	ds1338_init
	pxa2xx_i2c_slave_init
	aer915_init
	tosa_dac_init

it looks like a good cleanup. 

Thanks,

C.
diff mbox

Patch

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 <alastair@d-silva.org>
+ *
+ * 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 %s@0x%x: %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
+
+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");
+    }
+
+    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);
+    }
+
+    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);
+    }
+
+    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))) {
+            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);
+}
+
+/**
+ * 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;
+}
+
+/**
+ * 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;
+
+    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 <alastair@d-silva.org>
+ *
+ * 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 <alastair@d-silva.org>
+ *
+ * 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, &reg_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, &reg_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;
+}