]>
Commit | Line | Data |
---|---|---|
83d290c5 | 1 | // SPDX-License-Identifier: GPL-2.0+ |
dd18e5d8 SG |
2 | /* |
3 | * Simulate an I2C real time clock | |
4 | * | |
5 | * Copyright (c) 2015 Google, Inc | |
6 | * Written by Simon Glass <[email protected]> | |
dd18e5d8 SG |
7 | */ |
8 | ||
9 | /* | |
10 | * This is a test driver. It starts off with the current time of the machine, | |
11 | * but also supports setting the time, using an offset from the current | |
12 | * clock. This driver is only intended for testing, not accurate | |
13 | * time-keeping. It does not change the system time. | |
14 | */ | |
15 | ||
dd18e5d8 | 16 | #include <dm.h> |
dd18e5d8 | 17 | #include <i2c.h> |
f7ae49fc | 18 | #include <log.h> |
dd18e5d8 SG |
19 | #include <os.h> |
20 | #include <rtc.h> | |
21 | #include <asm/rtc.h> | |
22 | #include <asm/test.h> | |
23 | ||
24 | #ifdef DEBUG | |
25 | #define debug_buffer print_buffer | |
26 | #else | |
27 | #define debug_buffer(x, ...) | |
28 | #endif | |
29 | ||
dd18e5d8 SG |
30 | long sandbox_i2c_rtc_set_offset(struct udevice *dev, bool use_system_time, |
31 | int offset) | |
32 | { | |
c69cda25 | 33 | struct sandbox_i2c_rtc_plat_data *plat = dev_get_plat(dev); |
dd18e5d8 SG |
34 | long old_offset; |
35 | ||
36 | old_offset = plat->offset; | |
37 | plat->use_system_time = use_system_time; | |
38 | if (offset != -1) | |
39 | plat->offset = offset; | |
43db0750 | 40 | os_set_time_offset(plat->offset); |
dd18e5d8 SG |
41 | |
42 | return old_offset; | |
43 | } | |
44 | ||
45 | long sandbox_i2c_rtc_get_set_base_time(struct udevice *dev, long base_time) | |
46 | { | |
c69cda25 | 47 | struct sandbox_i2c_rtc_plat_data *plat = dev_get_plat(dev); |
dd18e5d8 SG |
48 | long old_base_time; |
49 | ||
50 | old_base_time = plat->base_time; | |
51 | if (base_time != -1) | |
52 | plat->base_time = base_time; | |
53 | ||
54 | return old_base_time; | |
55 | } | |
56 | ||
57 | static void reset_time(struct udevice *dev) | |
58 | { | |
c69cda25 | 59 | struct sandbox_i2c_rtc_plat_data *plat = dev_get_plat(dev); |
dd18e5d8 SG |
60 | struct rtc_time now; |
61 | ||
62 | os_localtime(&now); | |
63 | plat->base_time = rtc_mktime(&now); | |
43db0750 | 64 | plat->offset = os_get_time_offset(); |
dd18e5d8 SG |
65 | plat->use_system_time = true; |
66 | } | |
67 | ||
68 | static int sandbox_i2c_rtc_get(struct udevice *dev, struct rtc_time *time) | |
69 | { | |
c69cda25 | 70 | struct sandbox_i2c_rtc_plat_data *plat = dev_get_plat(dev); |
dd18e5d8 SG |
71 | struct rtc_time tm_now; |
72 | long now; | |
73 | ||
74 | if (plat->use_system_time) { | |
75 | os_localtime(&tm_now); | |
76 | now = rtc_mktime(&tm_now); | |
77 | } else { | |
78 | now = plat->base_time; | |
79 | } | |
80 | ||
992c1db4 HS |
81 | rtc_to_tm(now + plat->offset, time); |
82 | ||
83 | return 0; | |
dd18e5d8 SG |
84 | } |
85 | ||
86 | static int sandbox_i2c_rtc_set(struct udevice *dev, const struct rtc_time *time) | |
87 | { | |
c69cda25 | 88 | struct sandbox_i2c_rtc_plat_data *plat = dev_get_plat(dev); |
dd18e5d8 SG |
89 | struct rtc_time tm_now; |
90 | long now; | |
91 | ||
92 | if (plat->use_system_time) { | |
93 | os_localtime(&tm_now); | |
94 | now = rtc_mktime(&tm_now); | |
95 | } else { | |
96 | now = plat->base_time; | |
97 | } | |
98 | plat->offset = rtc_mktime(time) - now; | |
43db0750 | 99 | os_set_time_offset(plat->offset); |
dd18e5d8 SG |
100 | |
101 | return 0; | |
102 | } | |
103 | ||
104 | /* Update the current time in the registers */ | |
105 | static int sandbox_i2c_rtc_prepare_read(struct udevice *emul) | |
106 | { | |
c69cda25 | 107 | struct sandbox_i2c_rtc_plat_data *plat = dev_get_plat(emul); |
dd18e5d8 SG |
108 | struct rtc_time time; |
109 | int ret; | |
110 | ||
111 | ret = sandbox_i2c_rtc_get(emul, &time); | |
112 | if (ret) | |
113 | return ret; | |
114 | ||
115 | plat->reg[REG_SEC] = time.tm_sec; | |
116 | plat->reg[REG_MIN] = time.tm_min; | |
117 | plat->reg[REG_HOUR] = time.tm_hour; | |
118 | plat->reg[REG_MDAY] = time.tm_mday; | |
119 | plat->reg[REG_MON] = time.tm_mon; | |
120 | plat->reg[REG_YEAR] = time.tm_year - 1900; | |
121 | plat->reg[REG_WDAY] = time.tm_wday; | |
122 | ||
123 | return 0; | |
124 | } | |
125 | ||
126 | static int sandbox_i2c_rtc_complete_write(struct udevice *emul) | |
127 | { | |
c69cda25 | 128 | struct sandbox_i2c_rtc_plat_data *plat = dev_get_plat(emul); |
dd18e5d8 SG |
129 | struct rtc_time time; |
130 | int ret; | |
131 | ||
132 | time.tm_sec = plat->reg[REG_SEC]; | |
133 | time.tm_min = plat->reg[REG_MIN]; | |
134 | time.tm_hour = plat->reg[REG_HOUR]; | |
135 | time.tm_mday = plat->reg[REG_MDAY]; | |
136 | time.tm_mon = plat->reg[REG_MON]; | |
137 | time.tm_year = plat->reg[REG_YEAR] + 1900; | |
138 | time.tm_wday = plat->reg[REG_WDAY]; | |
139 | ||
140 | ret = sandbox_i2c_rtc_set(emul, &time); | |
141 | if (ret) | |
142 | return ret; | |
143 | ||
144 | return 0; | |
145 | } | |
146 | ||
147 | static int sandbox_i2c_rtc_xfer(struct udevice *emul, struct i2c_msg *msg, | |
148 | int nmsgs) | |
149 | { | |
c69cda25 | 150 | struct sandbox_i2c_rtc_plat_data *plat = dev_get_plat(emul); |
dd18e5d8 SG |
151 | uint offset = 0; |
152 | int ret; | |
153 | ||
154 | debug("\n%s\n", __func__); | |
155 | ret = sandbox_i2c_rtc_prepare_read(emul); | |
156 | if (ret) | |
157 | return ret; | |
158 | for (; nmsgs > 0; nmsgs--, msg++) { | |
159 | int len; | |
160 | u8 *ptr; | |
161 | ||
162 | len = msg->len; | |
163 | debug(" %s: msg->len=%d", | |
164 | msg->flags & I2C_M_RD ? "read" : "write", | |
165 | msg->len); | |
166 | if (msg->flags & I2C_M_RD) { | |
167 | debug(", offset %x, len %x: ", offset, len); | |
168 | ||
169 | /* Read the register */ | |
170 | memcpy(msg->buf, plat->reg + offset, len); | |
171 | memset(msg->buf + len, '\xff', msg->len - len); | |
172 | debug_buffer(0, msg->buf, 1, msg->len, 0); | |
173 | } else if (len >= 1) { | |
174 | ptr = msg->buf; | |
175 | offset = *ptr++ & (REG_COUNT - 1); | |
176 | len--; | |
177 | debug(", set offset %x: ", offset); | |
178 | debug_buffer(0, msg->buf, 1, msg->len, 0); | |
179 | ||
180 | /* Write the register */ | |
181 | memcpy(plat->reg + offset, ptr, len); | |
a3e36525 RV |
182 | /* If the reset register was written to, do reset. */ |
183 | if (offset <= REG_RESET && REG_RESET < offset + len) | |
dd18e5d8 SG |
184 | reset_time(emul); |
185 | } | |
186 | } | |
187 | ret = sandbox_i2c_rtc_complete_write(emul); | |
188 | if (ret) | |
189 | return ret; | |
190 | ||
191 | return 0; | |
192 | } | |
193 | ||
194 | struct dm_i2c_ops sandbox_i2c_rtc_emul_ops = { | |
195 | .xfer = sandbox_i2c_rtc_xfer, | |
196 | }; | |
197 | ||
198 | static int sandbox_i2c_rtc_bind(struct udevice *dev) | |
199 | { | |
200 | reset_time(dev); | |
201 | ||
202 | return 0; | |
203 | } | |
204 | ||
d3f72878 SA |
205 | static int sandbox_i2c_rtc_probe(struct udevice *dev) |
206 | { | |
207 | const u8 mac[] = { 0x02, 0x00, 0x11, 0x22, 0x33, 0x48 }; | |
208 | struct sandbox_i2c_rtc_plat_data *plat = dev_get_plat(dev); | |
209 | ||
210 | memcpy(&plat->reg[0x40], mac, sizeof(mac)); | |
211 | return 0; | |
212 | } | |
213 | ||
dd18e5d8 | 214 | static const struct udevice_id sandbox_i2c_rtc_ids[] = { |
c4085d73 | 215 | { .compatible = "sandbox,i2c-rtc-emul" }, |
dd18e5d8 SG |
216 | { } |
217 | }; | |
218 | ||
219 | U_BOOT_DRIVER(sandbox_i2c_rtc_emul) = { | |
220 | .name = "sandbox_i2c_rtc_emul", | |
221 | .id = UCLASS_I2C_EMUL, | |
222 | .of_match = sandbox_i2c_rtc_ids, | |
223 | .bind = sandbox_i2c_rtc_bind, | |
d3f72878 | 224 | .probe = sandbox_i2c_rtc_probe, |
41575d8e | 225 | .priv_auto = sizeof(struct sandbox_i2c_rtc), |
caa4daa2 | 226 | .plat_auto = sizeof(struct sandbox_i2c_rtc_plat_data), |
dd18e5d8 SG |
227 | .ops = &sandbox_i2c_rtc_emul_ops, |
228 | }; |