Signed-off-by: Antoine Mathys <barsa...@gmail.com> --- hw/ds1338.c | 156 ++++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 95 insertions(+), 61 deletions(-)
diff --git a/hw/ds1338.c b/hw/ds1338.c index 1da0f96..5a93fb6 100644 --- a/hw/ds1338.c +++ b/hw/ds1338.c @@ -48,17 +48,32 @@ static const VMStateDescription vmstate_ds1338 = { } }; -static void capture_current_time(DS1338State *s) +/* This mask is used to clear the read as zero bits in the RTC registers */ +static const uint8_t nvram_mask[8] = { + 0xff, 0x7f, 0x7f, 0x7, 0x3f, 0x1f, 0xff, 0xb3 +}; + + +static int compute_wday(int y, int m, int d) { - /* 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[0] = to_bcd(now.tm_sec); - s->nvram[1] = to_bcd(now.tm_min); + static int t[12] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4}; + + if (m < 2) { + y--; + } + return (y + y/4 - y/100 + y/400 + t[m] + d) % 7; +} + +/* Write TM to the RTC registers. */ +static void write_time(DS1338State *s, const struct tm *tm) +{ + /* Preserve the CH flag. */ + s->nvram[0] &= SECONDS_CH; + s->nvram[0] |= to_bcd(tm->tm_sec); + + s->nvram[1] = to_bcd(tm->tm_min); if (s->nvram[2] & HOURS_12) { - int tmp = now.tm_hour; + int tmp = tm->tm_hour; if (tmp % 12 == 0) { tmp += 12; } @@ -68,12 +83,50 @@ static void capture_current_time(DS1338State *s) s->nvram[2] = HOURS_12 | HOURS_PM | to_bcd(tmp - 12); } } else { - s->nvram[2] = to_bcd(now.tm_hour); + s->nvram[2] = to_bcd(tm->tm_hour); + } + s->nvram[3] = (tm->tm_wday + s->wday_offset) % 7 + 1; + s->nvram[4] = to_bcd(tm->tm_mday); + s->nvram[5] = to_bcd(tm->tm_mon + 1); + s->nvram[6] = to_bcd(tm->tm_year - 100); +} + +/* Read TM from the RTC registers. */ +static void read_time(DS1338State *s, struct tm *tm) +{ + tm->tm_sec = from_bcd(s->nvram[0] & 0x7f); + tm->tm_min = from_bcd(s->nvram[1] & 0x7f); + if (s->nvram[2] & HOURS_12) { + int tmp = from_bcd(s->nvram[2] & (HOURS_PM - 1)); + if (s->nvram[2] & HOURS_PM) { + tmp += 12; + } + if (tmp % 12 == 0) { + tmp -= 12; + } + tm->tm_hour = tmp; + } else { + tm->tm_hour = from_bcd(s->nvram[2] & (HOURS_12 - 1)); + } + tm->tm_mday = from_bcd(s->nvram[4] & 0x3f); + tm->tm_mon = from_bcd(s->nvram[5] & 0x1f) - 1; + tm->tm_year = from_bcd(s->nvram[6]) + 100; + tm->tm_wday = compute_wday(tm->tm_year + 1900, tm->tm_mon, tm->tm_mday); +} + +static bool clock_running(DS1338State *s) +{ + return !(s->nvram[0] & SECONDS_CH); +} + +static void capture_current_time(DS1338State *s) +{ + if (clock_running(s)) { + /* Write current time. */ + struct tm tmp; + qemu_get_timedate(&tmp, s->offset); + write_time(s, &tmp); } - s->nvram[3] = (now.tm_wday + s->wday_offset) % 7 + 1; - s->nvram[4] = to_bcd(now.tm_mday); - s->nvram[5] = to_bcd(now.tm_mon + 1); - s->nvram[6] = to_bcd(now.tm_year - 100); } static void inc_regptr(DS1338State *s) @@ -129,65 +182,46 @@ static int ds1338_send(I2CSlave *i2c, uint8_t data) } if (s->ptr < 7) { /* Time register. */ - struct tm now; - qemu_get_timedate(&now, s->offset); - switch(s->ptr) { - case 0: - /* TODO: Implement CH (stop) bit. */ - now.tm_sec = from_bcd(data & 0x7f); - break; - case 1: - now.tm_min = from_bcd(data & 0x7f); - break; - case 2: - if (data & HOURS_12) { - int tmp = from_bcd(data & (HOURS_PM - 1)); - if (data & HOURS_PM) { - tmp += 12; - } - if (tmp % 12 == 0) { - tmp -= 12; - } - now.tm_hour = tmp; - } else { - now.tm_hour = from_bcd(data & (HOURS_12 - 1)); - } - break; - case 3: - { - /* The day field is supposed to contain a value in - the range 1-7. Otherwise behavior is undefined. - */ - int user_wday = (data & 7) - 1; - s->wday_offset = (user_wday - now.tm_wday + 7) % 7; + bool was_running = clock_running(s); + + capture_current_time(s); + + s->nvram[s->ptr] = data & nvram_mask[s->ptr]; + + if (clock_running(s)) { + /* Read the new time */ + struct tm tmp; + int user_wday; + + read_time(s, &tmp); + s->offset = qemu_timedate_diff(&tmp); + + /* The day field is supposed to contain a value in + the range 1-7. Otherwise behavior is undefined. + */ + user_wday = (s->nvram[3] & 7) - 1; + s->wday_offset = (user_wday - tmp.tm_wday + 7) % 7; + } else { + /* If the clock is transitioning from on to off, set the OSF + flag. */ + if (was_running) { + s->nvram[7] |= CTRL_OSF; } - break; - case 4: - now.tm_mday = from_bcd(data & 0x3f); - break; - case 5: - now.tm_mon = from_bcd(data & 0x1f) - 1; - break; - case 6: - now.tm_year = from_bcd(data) + 100; - break; } - s->offset = qemu_timedate_diff(&now); } else if (s->ptr == 7) { /* Control register. */ - /* Ensure bits 2, 3 and 6 will read back as zero. */ - data &= 0xB3; - /* Attempting to write the OSF flag to logic 1 leaves the value unchanged. */ data = (data & ~CTRL_OSF) | (data & s->nvram[s->ptr] & CTRL_OSF); - s->nvram[s->ptr] = data; + s->nvram[s->ptr] = data & nvram_mask[s->ptr]; } else { s->nvram[s->ptr] = data; } - inc_regptr(s); + /* Note: we don't need to reload the rtc registers on wraparound + when writing */ + s->ptr = (s->ptr + 1) & (NVRAM_SIZE - 1); return 0; } -- 1.7.10.4