From: Alastair D'Silva <alast...@d-silva.org> This patch adds support for the Epson RX8900 RTC chip.
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 ++++++++++++++++++++++++++++++++++++ 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 + +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 <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; +} -- 2.7.4