]>
Commit | Line | Data |
---|---|---|
1dfe3943 PB |
1 | /* |
2 | * MAXIM DS1338 I2C RTC+NVRAM | |
3 | * | |
4 | * Copyright (c) 2009 CodeSourcery. | |
5 | * Written by Paul Brook | |
6 | * | |
8e31bf38 | 7 | * This code is licensed under the GNU GPL v2. |
1dfe3943 PB |
8 | */ |
9 | ||
10 | #include "i2c.h" | |
11 | ||
12 | typedef struct { | |
13 | i2c_slave i2c; | |
14 | time_t offset; | |
15 | struct tm now; | |
16 | uint8_t nvram[56]; | |
17 | int ptr; | |
18 | int addr_byte; | |
19 | } DS1338State; | |
20 | ||
21 | static void ds1338_event(i2c_slave *i2c, enum i2c_event event) | |
22 | { | |
23 | DS1338State *s = FROM_I2C_SLAVE(DS1338State, i2c); | |
24 | ||
25 | switch (event) { | |
26 | case I2C_START_RECV: | |
27 | qemu_get_timedate(&s->now, s->offset); | |
28 | s->nvram[0] = to_bcd(s->now.tm_sec); | |
29 | s->nvram[1] = to_bcd(s->now.tm_min); | |
30 | if (s->nvram[2] & 0x40) { | |
31 | s->nvram[2] = (to_bcd((s->now.tm_hour % 12)) + 1) | 0x40; | |
32 | if (s->now.tm_hour >= 12) { | |
33 | s->nvram[2] |= 0x20; | |
34 | } | |
35 | } else { | |
36 | s->nvram[2] = to_bcd(s->now.tm_hour); | |
37 | } | |
38 | s->nvram[3] = to_bcd(s->now.tm_wday) + 1; | |
39 | s->nvram[4] = to_bcd(s->now.tm_mday); | |
40 | s->nvram[5] = to_bcd(s->now.tm_mon) + 1; | |
41 | s->nvram[6] = to_bcd(s->now.tm_year - 100); | |
42 | break; | |
43 | case I2C_START_SEND: | |
44 | s->addr_byte = 1; | |
45 | break; | |
46 | default: | |
47 | break; | |
48 | } | |
49 | } | |
50 | ||
51 | static int ds1338_recv(i2c_slave *i2c) | |
52 | { | |
53 | DS1338State *s = FROM_I2C_SLAVE(DS1338State, i2c); | |
54 | uint8_t res; | |
55 | ||
56 | res = s->nvram[s->ptr]; | |
57 | s->ptr = (s->ptr + 1) & 0xff; | |
58 | return res; | |
59 | } | |
60 | ||
61 | static int ds1338_send(i2c_slave *i2c, uint8_t data) | |
62 | { | |
63 | DS1338State *s = FROM_I2C_SLAVE(DS1338State, i2c); | |
64 | if (s->addr_byte) { | |
65 | s->ptr = data; | |
66 | s->addr_byte = 0; | |
67 | return 0; | |
68 | } | |
69 | s->nvram[s->ptr - 8] = data; | |
70 | if (data < 8) { | |
71 | qemu_get_timedate(&s->now, s->offset); | |
72 | switch(data) { | |
73 | case 0: | |
74 | /* TODO: Implement CH (stop) bit. */ | |
75 | s->now.tm_sec = from_bcd(data & 0x7f); | |
76 | break; | |
77 | case 1: | |
78 | s->now.tm_min = from_bcd(data & 0x7f); | |
79 | break; | |
80 | case 2: | |
81 | if (data & 0x40) { | |
82 | if (data & 0x20) { | |
83 | data = from_bcd(data & 0x4f) + 11; | |
84 | } else { | |
85 | data = from_bcd(data & 0x1f) - 1; | |
86 | } | |
87 | } else { | |
88 | data = from_bcd(data); | |
89 | } | |
90 | s->now.tm_hour = data; | |
91 | break; | |
92 | case 3: | |
93 | s->now.tm_wday = from_bcd(data & 7) - 1; | |
94 | break; | |
95 | case 4: | |
96 | s->now.tm_mday = from_bcd(data & 0x3f); | |
97 | break; | |
98 | case 5: | |
99 | s->now.tm_mon = from_bcd(data & 0x1f) - 1; | |
100 | case 6: | |
101 | s->now.tm_year = from_bcd(data) + 100; | |
102 | break; | |
103 | case 7: | |
104 | /* Control register. Currently ignored. */ | |
105 | break; | |
106 | } | |
107 | s->offset = qemu_timedate_diff(&s->now); | |
108 | } | |
109 | s->ptr = (s->ptr + 1) & 0xff; | |
110 | return 0; | |
111 | } | |
112 | ||
113 | static int ds1338_init(i2c_slave *i2c) | |
114 | { | |
115 | return 0; | |
116 | } | |
117 | ||
118 | static I2CSlaveInfo ds1338_info = { | |
119 | .qdev.name = "ds1338", | |
120 | .qdev.size = sizeof(DS1338State), | |
121 | .init = ds1338_init, | |
122 | .event = ds1338_event, | |
123 | .recv = ds1338_recv, | |
124 | .send = ds1338_send, | |
125 | }; | |
126 | ||
127 | static void ds1338_register_devices(void) | |
128 | { | |
129 | i2c_register_slave(&ds1338_info); | |
130 | } | |
131 | ||
132 | device_init(ds1338_register_devices) |