]>
Commit | Line | Data |
---|---|---|
a9ad9e73 NL |
1 | /* |
2 | * Allwinner Real Time Clock emulation | |
3 | * | |
4 | * Copyright (C) 2019 Niek Linnenbank <[email protected]> | |
5 | * | |
6 | * This program is free software: you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License as published by | |
8 | * the Free Software Foundation, either version 2 of the License, or | |
9 | * (at your option) any later version. | |
10 | * | |
11 | * This program is distributed in the hope that it will be useful, | |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | * GNU General Public License for more details. | |
15 | * | |
16 | * You should have received a copy of the GNU General Public License | |
17 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
18 | */ | |
19 | ||
20 | #include "qemu/osdep.h" | |
21 | #include "qemu/units.h" | |
22 | #include "hw/sysbus.h" | |
23 | #include "migration/vmstate.h" | |
24 | #include "qemu/log.h" | |
25 | #include "qemu/module.h" | |
26 | #include "qemu-common.h" | |
27 | #include "hw/qdev-properties.h" | |
28 | #include "hw/rtc/allwinner-rtc.h" | |
29 | #include "trace.h" | |
30 | ||
31 | /* RTC registers */ | |
32 | enum { | |
33 | REG_LOSC = 1, /* Low Oscillator Control */ | |
34 | REG_YYMMDD, /* RTC Year-Month-Day */ | |
35 | REG_HHMMSS, /* RTC Hour-Minute-Second */ | |
36 | REG_ALARM1_WKHHMMSS, /* Alarm1 Week Hour-Minute-Second */ | |
37 | REG_ALARM1_EN, /* Alarm1 Enable */ | |
38 | REG_ALARM1_IRQ_EN, /* Alarm1 IRQ Enable */ | |
39 | REG_ALARM1_IRQ_STA, /* Alarm1 IRQ Status */ | |
40 | REG_GP0, /* General Purpose Register 0 */ | |
41 | REG_GP1, /* General Purpose Register 1 */ | |
42 | REG_GP2, /* General Purpose Register 2 */ | |
43 | REG_GP3, /* General Purpose Register 3 */ | |
44 | ||
45 | /* sun4i registers */ | |
46 | REG_ALARM1_DDHHMMSS, /* Alarm1 Day Hour-Minute-Second */ | |
47 | REG_CPUCFG, /* CPU Configuration Register */ | |
48 | ||
49 | /* sun6i registers */ | |
50 | REG_LOSC_AUTOSTA, /* LOSC Auto Switch Status */ | |
51 | REG_INT_OSC_PRE, /* Internal OSC Clock Prescaler */ | |
52 | REG_ALARM0_COUNTER, /* Alarm0 Counter */ | |
53 | REG_ALARM0_CUR_VLU, /* Alarm0 Counter Current Value */ | |
54 | REG_ALARM0_ENABLE, /* Alarm0 Enable */ | |
55 | REG_ALARM0_IRQ_EN, /* Alarm0 IRQ Enable */ | |
56 | REG_ALARM0_IRQ_STA, /* Alarm0 IRQ Status */ | |
57 | REG_ALARM_CONFIG, /* Alarm Config */ | |
58 | REG_LOSC_OUT_GATING, /* LOSC Output Gating Register */ | |
59 | REG_GP4, /* General Purpose Register 4 */ | |
60 | REG_GP5, /* General Purpose Register 5 */ | |
61 | REG_GP6, /* General Purpose Register 6 */ | |
62 | REG_GP7, /* General Purpose Register 7 */ | |
63 | REG_RTC_DBG, /* RTC Debug Register */ | |
64 | REG_GPL_HOLD_OUT, /* GPL Hold Output Register */ | |
65 | REG_VDD_RTC, /* VDD RTC Regulate Register */ | |
66 | REG_IC_CHARA, /* IC Characteristics Register */ | |
67 | }; | |
68 | ||
69 | /* RTC register flags */ | |
70 | enum { | |
71 | REG_LOSC_YMD = (1 << 7), | |
72 | REG_LOSC_HMS = (1 << 8), | |
73 | }; | |
74 | ||
75 | /* RTC sun4i register map (offset to name) */ | |
76 | const uint8_t allwinner_rtc_sun4i_regmap[] = { | |
77 | [0x0000] = REG_LOSC, | |
78 | [0x0004] = REG_YYMMDD, | |
79 | [0x0008] = REG_HHMMSS, | |
80 | [0x000C] = REG_ALARM1_DDHHMMSS, | |
81 | [0x0010] = REG_ALARM1_WKHHMMSS, | |
82 | [0x0014] = REG_ALARM1_EN, | |
83 | [0x0018] = REG_ALARM1_IRQ_EN, | |
84 | [0x001C] = REG_ALARM1_IRQ_STA, | |
85 | [0x0020] = REG_GP0, | |
86 | [0x0024] = REG_GP1, | |
87 | [0x0028] = REG_GP2, | |
88 | [0x002C] = REG_GP3, | |
89 | [0x003C] = REG_CPUCFG, | |
90 | }; | |
91 | ||
92 | /* RTC sun6i register map (offset to name) */ | |
93 | const uint8_t allwinner_rtc_sun6i_regmap[] = { | |
94 | [0x0000] = REG_LOSC, | |
95 | [0x0004] = REG_LOSC_AUTOSTA, | |
96 | [0x0008] = REG_INT_OSC_PRE, | |
97 | [0x0010] = REG_YYMMDD, | |
98 | [0x0014] = REG_HHMMSS, | |
99 | [0x0020] = REG_ALARM0_COUNTER, | |
100 | [0x0024] = REG_ALARM0_CUR_VLU, | |
101 | [0x0028] = REG_ALARM0_ENABLE, | |
102 | [0x002C] = REG_ALARM0_IRQ_EN, | |
103 | [0x0030] = REG_ALARM0_IRQ_STA, | |
104 | [0x0040] = REG_ALARM1_WKHHMMSS, | |
105 | [0x0044] = REG_ALARM1_EN, | |
106 | [0x0048] = REG_ALARM1_IRQ_EN, | |
107 | [0x004C] = REG_ALARM1_IRQ_STA, | |
108 | [0x0050] = REG_ALARM_CONFIG, | |
109 | [0x0060] = REG_LOSC_OUT_GATING, | |
110 | [0x0100] = REG_GP0, | |
111 | [0x0104] = REG_GP1, | |
112 | [0x0108] = REG_GP2, | |
113 | [0x010C] = REG_GP3, | |
114 | [0x0110] = REG_GP4, | |
115 | [0x0114] = REG_GP5, | |
116 | [0x0118] = REG_GP6, | |
117 | [0x011C] = REG_GP7, | |
118 | [0x0170] = REG_RTC_DBG, | |
119 | [0x0180] = REG_GPL_HOLD_OUT, | |
120 | [0x0190] = REG_VDD_RTC, | |
121 | [0x01F0] = REG_IC_CHARA, | |
122 | }; | |
123 | ||
124 | static bool allwinner_rtc_sun4i_read(AwRtcState *s, uint32_t offset) | |
125 | { | |
126 | /* no sun4i specific registers currently implemented */ | |
127 | return false; | |
128 | } | |
129 | ||
130 | static bool allwinner_rtc_sun4i_write(AwRtcState *s, uint32_t offset, | |
131 | uint32_t data) | |
132 | { | |
133 | /* no sun4i specific registers currently implemented */ | |
134 | return false; | |
135 | } | |
136 | ||
137 | static bool allwinner_rtc_sun6i_read(AwRtcState *s, uint32_t offset) | |
138 | { | |
139 | const AwRtcClass *c = AW_RTC_GET_CLASS(s); | |
140 | ||
141 | switch (c->regmap[offset]) { | |
142 | case REG_GP4: /* General Purpose Register 4 */ | |
143 | case REG_GP5: /* General Purpose Register 5 */ | |
144 | case REG_GP6: /* General Purpose Register 6 */ | |
145 | case REG_GP7: /* General Purpose Register 7 */ | |
146 | return true; | |
147 | default: | |
148 | break; | |
149 | } | |
150 | return false; | |
151 | } | |
152 | ||
153 | static bool allwinner_rtc_sun6i_write(AwRtcState *s, uint32_t offset, | |
154 | uint32_t data) | |
155 | { | |
156 | const AwRtcClass *c = AW_RTC_GET_CLASS(s); | |
157 | ||
158 | switch (c->regmap[offset]) { | |
159 | case REG_GP4: /* General Purpose Register 4 */ | |
160 | case REG_GP5: /* General Purpose Register 5 */ | |
161 | case REG_GP6: /* General Purpose Register 6 */ | |
162 | case REG_GP7: /* General Purpose Register 7 */ | |
163 | return true; | |
164 | default: | |
165 | break; | |
166 | } | |
167 | return false; | |
168 | } | |
169 | ||
170 | static uint64_t allwinner_rtc_read(void *opaque, hwaddr offset, | |
171 | unsigned size) | |
172 | { | |
173 | AwRtcState *s = AW_RTC(opaque); | |
174 | const AwRtcClass *c = AW_RTC_GET_CLASS(s); | |
175 | uint64_t val = 0; | |
176 | ||
177 | if (offset >= c->regmap_size) { | |
178 | qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n", | |
179 | __func__, (uint32_t)offset); | |
180 | return 0; | |
181 | } | |
182 | ||
183 | if (!c->regmap[offset]) { | |
184 | qemu_log_mask(LOG_GUEST_ERROR, "%s: invalid register 0x%04x\n", | |
185 | __func__, (uint32_t)offset); | |
186 | return 0; | |
187 | } | |
188 | ||
189 | switch (c->regmap[offset]) { | |
190 | case REG_LOSC: /* Low Oscillator Control */ | |
191 | val = s->regs[REG_LOSC]; | |
192 | s->regs[REG_LOSC] &= ~(REG_LOSC_YMD | REG_LOSC_HMS); | |
193 | break; | |
194 | case REG_YYMMDD: /* RTC Year-Month-Day */ | |
195 | case REG_HHMMSS: /* RTC Hour-Minute-Second */ | |
196 | case REG_GP0: /* General Purpose Register 0 */ | |
197 | case REG_GP1: /* General Purpose Register 1 */ | |
198 | case REG_GP2: /* General Purpose Register 2 */ | |
199 | case REG_GP3: /* General Purpose Register 3 */ | |
200 | val = s->regs[c->regmap[offset]]; | |
201 | break; | |
202 | default: | |
203 | if (!c->read(s, offset)) { | |
204 | qemu_log_mask(LOG_UNIMP, "%s: unimplemented register 0x%04x\n", | |
205 | __func__, (uint32_t)offset); | |
206 | } | |
207 | val = s->regs[c->regmap[offset]]; | |
208 | break; | |
209 | } | |
210 | ||
211 | trace_allwinner_rtc_read(offset, val); | |
212 | return val; | |
213 | } | |
214 | ||
215 | static void allwinner_rtc_write(void *opaque, hwaddr offset, | |
216 | uint64_t val, unsigned size) | |
217 | { | |
218 | AwRtcState *s = AW_RTC(opaque); | |
219 | const AwRtcClass *c = AW_RTC_GET_CLASS(s); | |
220 | ||
221 | if (offset >= c->regmap_size) { | |
222 | qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n", | |
223 | __func__, (uint32_t)offset); | |
224 | return; | |
225 | } | |
226 | ||
227 | if (!c->regmap[offset]) { | |
228 | qemu_log_mask(LOG_GUEST_ERROR, "%s: invalid register 0x%04x\n", | |
229 | __func__, (uint32_t)offset); | |
230 | return; | |
231 | } | |
232 | ||
233 | trace_allwinner_rtc_write(offset, val); | |
234 | ||
235 | switch (c->regmap[offset]) { | |
236 | case REG_YYMMDD: /* RTC Year-Month-Day */ | |
237 | s->regs[REG_YYMMDD] = val; | |
238 | s->regs[REG_LOSC] |= REG_LOSC_YMD; | |
239 | break; | |
240 | case REG_HHMMSS: /* RTC Hour-Minute-Second */ | |
241 | s->regs[REG_HHMMSS] = val; | |
242 | s->regs[REG_LOSC] |= REG_LOSC_HMS; | |
243 | break; | |
244 | case REG_GP0: /* General Purpose Register 0 */ | |
245 | case REG_GP1: /* General Purpose Register 1 */ | |
246 | case REG_GP2: /* General Purpose Register 2 */ | |
247 | case REG_GP3: /* General Purpose Register 3 */ | |
248 | s->regs[c->regmap[offset]] = val; | |
249 | break; | |
250 | default: | |
251 | if (!c->write(s, offset, val)) { | |
252 | qemu_log_mask(LOG_UNIMP, "%s: unimplemented register 0x%04x\n", | |
253 | __func__, (uint32_t)offset); | |
254 | } | |
255 | break; | |
256 | } | |
257 | } | |
258 | ||
259 | static const MemoryRegionOps allwinner_rtc_ops = { | |
260 | .read = allwinner_rtc_read, | |
261 | .write = allwinner_rtc_write, | |
262 | .endianness = DEVICE_NATIVE_ENDIAN, | |
263 | .valid = { | |
264 | .min_access_size = 4, | |
265 | .max_access_size = 4, | |
266 | }, | |
267 | .impl.min_access_size = 4, | |
268 | }; | |
269 | ||
270 | static void allwinner_rtc_reset(DeviceState *dev) | |
271 | { | |
272 | AwRtcState *s = AW_RTC(dev); | |
273 | struct tm now; | |
274 | ||
275 | /* Clear registers */ | |
276 | memset(s->regs, 0, sizeof(s->regs)); | |
277 | ||
278 | /* Get current datetime */ | |
279 | qemu_get_timedate(&now, 0); | |
280 | ||
281 | /* Set RTC with current datetime */ | |
282 | if (s->base_year > 1900) { | |
283 | s->regs[REG_YYMMDD] = ((now.tm_year + 1900 - s->base_year) << 16) | | |
284 | ((now.tm_mon + 1) << 8) | | |
285 | now.tm_mday; | |
286 | s->regs[REG_HHMMSS] = (((now.tm_wday + 6) % 7) << 29) | | |
287 | (now.tm_hour << 16) | | |
288 | (now.tm_min << 8) | | |
289 | now.tm_sec; | |
290 | } | |
291 | } | |
292 | ||
293 | static void allwinner_rtc_init(Object *obj) | |
294 | { | |
295 | SysBusDevice *sbd = SYS_BUS_DEVICE(obj); | |
296 | AwRtcState *s = AW_RTC(obj); | |
297 | ||
298 | /* Memory mapping */ | |
299 | memory_region_init_io(&s->iomem, OBJECT(s), &allwinner_rtc_ops, s, | |
300 | TYPE_AW_RTC, 1 * KiB); | |
301 | sysbus_init_mmio(sbd, &s->iomem); | |
302 | } | |
303 | ||
304 | static const VMStateDescription allwinner_rtc_vmstate = { | |
305 | .name = "allwinner-rtc", | |
306 | .version_id = 1, | |
307 | .minimum_version_id = 1, | |
308 | .fields = (VMStateField[]) { | |
309 | VMSTATE_UINT32_ARRAY(regs, AwRtcState, AW_RTC_REGS_NUM), | |
310 | VMSTATE_END_OF_LIST() | |
311 | } | |
312 | }; | |
313 | ||
314 | static Property allwinner_rtc_properties[] = { | |
315 | DEFINE_PROP_INT32("base-year", AwRtcState, base_year, 0), | |
316 | DEFINE_PROP_END_OF_LIST(), | |
317 | }; | |
318 | ||
319 | static void allwinner_rtc_class_init(ObjectClass *klass, void *data) | |
320 | { | |
321 | DeviceClass *dc = DEVICE_CLASS(klass); | |
322 | ||
323 | dc->reset = allwinner_rtc_reset; | |
324 | dc->vmsd = &allwinner_rtc_vmstate; | |
325 | device_class_set_props(dc, allwinner_rtc_properties); | |
326 | } | |
327 | ||
328 | static void allwinner_rtc_sun4i_init(Object *obj) | |
329 | { | |
330 | AwRtcState *s = AW_RTC(obj); | |
331 | s->base_year = 2010; | |
332 | } | |
333 | ||
334 | static void allwinner_rtc_sun4i_class_init(ObjectClass *klass, void *data) | |
335 | { | |
336 | AwRtcClass *arc = AW_RTC_CLASS(klass); | |
337 | ||
338 | arc->regmap = allwinner_rtc_sun4i_regmap; | |
339 | arc->regmap_size = sizeof(allwinner_rtc_sun4i_regmap); | |
340 | arc->read = allwinner_rtc_sun4i_read; | |
341 | arc->write = allwinner_rtc_sun4i_write; | |
342 | } | |
343 | ||
344 | static void allwinner_rtc_sun6i_init(Object *obj) | |
345 | { | |
346 | AwRtcState *s = AW_RTC(obj); | |
347 | s->base_year = 1970; | |
348 | } | |
349 | ||
350 | static void allwinner_rtc_sun6i_class_init(ObjectClass *klass, void *data) | |
351 | { | |
352 | AwRtcClass *arc = AW_RTC_CLASS(klass); | |
353 | ||
354 | arc->regmap = allwinner_rtc_sun6i_regmap; | |
355 | arc->regmap_size = sizeof(allwinner_rtc_sun6i_regmap); | |
356 | arc->read = allwinner_rtc_sun6i_read; | |
357 | arc->write = allwinner_rtc_sun6i_write; | |
358 | } | |
359 | ||
360 | static void allwinner_rtc_sun7i_init(Object *obj) | |
361 | { | |
362 | AwRtcState *s = AW_RTC(obj); | |
363 | s->base_year = 1970; | |
364 | } | |
365 | ||
366 | static void allwinner_rtc_sun7i_class_init(ObjectClass *klass, void *data) | |
367 | { | |
368 | AwRtcClass *arc = AW_RTC_CLASS(klass); | |
369 | allwinner_rtc_sun4i_class_init(klass, arc); | |
370 | } | |
371 | ||
372 | static const TypeInfo allwinner_rtc_info = { | |
373 | .name = TYPE_AW_RTC, | |
374 | .parent = TYPE_SYS_BUS_DEVICE, | |
375 | .instance_init = allwinner_rtc_init, | |
376 | .instance_size = sizeof(AwRtcState), | |
377 | .class_init = allwinner_rtc_class_init, | |
378 | .class_size = sizeof(AwRtcClass), | |
379 | .abstract = true, | |
380 | }; | |
381 | ||
382 | static const TypeInfo allwinner_rtc_sun4i_info = { | |
383 | .name = TYPE_AW_RTC_SUN4I, | |
384 | .parent = TYPE_AW_RTC, | |
385 | .class_init = allwinner_rtc_sun4i_class_init, | |
386 | .instance_init = allwinner_rtc_sun4i_init, | |
387 | }; | |
388 | ||
389 | static const TypeInfo allwinner_rtc_sun6i_info = { | |
390 | .name = TYPE_AW_RTC_SUN6I, | |
391 | .parent = TYPE_AW_RTC, | |
392 | .class_init = allwinner_rtc_sun6i_class_init, | |
393 | .instance_init = allwinner_rtc_sun6i_init, | |
394 | }; | |
395 | ||
396 | static const TypeInfo allwinner_rtc_sun7i_info = { | |
397 | .name = TYPE_AW_RTC_SUN7I, | |
398 | .parent = TYPE_AW_RTC, | |
399 | .class_init = allwinner_rtc_sun7i_class_init, | |
400 | .instance_init = allwinner_rtc_sun7i_init, | |
401 | }; | |
402 | ||
403 | static void allwinner_rtc_register(void) | |
404 | { | |
405 | type_register_static(&allwinner_rtc_info); | |
406 | type_register_static(&allwinner_rtc_sun4i_info); | |
407 | type_register_static(&allwinner_rtc_sun6i_info); | |
408 | type_register_static(&allwinner_rtc_sun7i_info); | |
409 | } | |
410 | ||
411 | type_init(allwinner_rtc_register) |