On 11/17/2016 05:36 AM, Alastair D'Silva wrote: > From: Alastair D'Silva <alast...@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. > Signed-off-by: Alastair D'Silva <alast...@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. 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 <alast...@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. > +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 <alast...@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 <alast...@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, ®_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; > +} >