Patchwork hw/ds1338.c: implement clock enable/disable (CH bit)

login
register
mail settings
Submitter Antoine Mathys
Date Feb. 16, 2013, 4:03 p.m.
Message ID <1361030617-6849-1-git-send-email-barsamin@gmail.com>
Download mbox | patch
Permalink /patch/220970/
State New
Headers show

Comments

Antoine Mathys - Feb. 16, 2013, 4:03 p.m.
Signed-off-by: Antoine Mathys <barsamin@gmail.com>
---
 hw/ds1338.c |  156 ++++++++++++++++++++++++++++++++++++-----------------------
 1 file changed, 95 insertions(+), 61 deletions(-)

Patch

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;
 }