1 /*
2 * rtc-72421.c - RTC-72421 RTC emulation.
3 *
4 * Written by
5 * Marco van den Heuvel <blackystardust68@yahoo.com>
6 *
7 * This file is part of VICE, the Versatile Commodore Emulator.
8 * See README for copyright notice.
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
23 * 02111-1307 USA.
24 *
25 */
26
27 #include "vice.h"
28
29 #include "rtc-72421.h"
30 #include "lib.h"
31 #include "rtc.h"
32 #include "snapshot.h"
33
34 #include <string.h>
35
36 /* The RTC-72421 is a 4bit address/data line RTC module,
37 * The RTC has the following features:
38 * - Real-Time Clock Counts seconds, minutes, hours, date of the month,
39 * months, and years
40 * - All registers are decimal
41 */
42
43 /* The RTC-72421 has the following clock registers:
44 *
45 * register 0 : bits 3-0 seconds
46 *
47 * register 1 : bit 3 0
48 * bits 2-0 10 seconds
49 *
50 * register 2 : bits 3-0 minutes
51 *
52 * register 3 : bit 3 0
53 * bits 2-0 10 minutes
54 *
55 * register 4 : bits 3-0 hours
56 *
57 * register 5 : bit 3 24/12 hour selection (0 = 12 hour, 1 = 24 hour)
58 * bit 2 AM/PM indication bit (1 when in 12 hour mode, otherwise 0)
59 * bits 1-0 10 hours
60 *
61 * register 6 : bits 3-0 day of the month
62 *
63 * register 7 : bits 3-2 leapyear indicator
64 * bits 1-0 10 day of the month
65 *
66 * register 8 : bits 3-0 months
67 *
68 * register 9 : bits 3-1 0
69 * bit 0 10 months
70 *
71 * register A : bits 3-0 years
72 *
73 * register B : bits 3-0 10 years
74 *
75 * register C : bit 3 0
76 * bits 2-0 weekdays
77 *
78 * register D : bits 3-0 0
79 *
80 * register E : bits 3-0 0
81 *
82 * register F : bit 3 0
83 * bit 2 AM/PM selector
84 * bit 1 Clock Halt
85 * bit 0 0
86 */
87
88 /* This module is currently used in the following emulated hardware:
89 */
90
91 /* ---------------------------------------------------------------------------------------------------- */
92
rtc72421_init(char * device)93 rtc_72421_t *rtc72421_init(char *device)
94 {
95 rtc_72421_t *retval = lib_calloc(1, sizeof(rtc_72421_t));
96 int loaded = rtc_load_context(device, 0, 0);
97
98 if (loaded) {
99 retval->offset = rtc_get_loaded_offset();
100 } else {
101 retval->offset = 0;
102 }
103 retval->old_offset = retval->offset;
104
105 retval->hour24 = 0;
106 retval->device = lib_stralloc(device);
107
108 return retval;
109 }
110
rtc72421_destroy(rtc_72421_t * context,int save)111 void rtc72421_destroy(rtc_72421_t *context, int save)
112 {
113 if (save) {
114 if (context->old_offset != context->offset) {
115 rtc_save_context(NULL, 0, NULL, 0, context->device, context->offset);
116 }
117 }
118 lib_free(context->device);
119 lib_free(context);
120 }
121
122 /* ---------------------------------------------------------------------------------------------------- */
123
rtc72421_read(rtc_72421_t * context,uint8_t address)124 uint8_t rtc72421_read(rtc_72421_t *context, uint8_t address)
125 {
126 uint8_t retval = 0;
127 time_t latch = (context->stop) ? context->latch : rtc_get_latch(context->offset);
128
129 switch (address & 0xf) {
130 case RTC72421_REGISTER_SECONDS:
131 retval = rtc_get_second(latch, 0);
132 retval %= 10;
133 break;
134 case RTC72421_REGISTER_10SECONDS:
135 retval = rtc_get_second(latch, 0);
136 retval /= 10;
137 break;
138 case RTC72421_REGISTER_MINUTES:
139 retval = rtc_get_minute(latch, 0);
140 retval %= 10;
141 break;
142 case RTC72421_REGISTER_10MINUTES:
143 retval = rtc_get_minute(latch, 0);
144 retval /= 10;
145 break;
146 case RTC72421_REGISTER_HOURS:
147 if (context->hour24) {
148 retval = rtc_get_hour(latch, 0);
149 } else {
150 retval = rtc_get_hour_am_pm(latch, 0);
151 retval &= 0x1f;
152 }
153 retval %= 10;
154 break;
155 case RTC72421_REGISTER_10HOURS:
156 if (context->hour24) {
157 retval = rtc_get_hour(latch, 0);
158 retval /= 10;
159 retval |= 8;
160 } else {
161 retval = rtc_get_hour_am_pm(latch, 0);
162 if (retval > 23) {
163 retval = (retval - 32) / 10;
164 retval |= 4;
165 } else {
166 retval /= 10;
167 }
168 }
169 break;
170 case RTC72421_REGISTER_WEEKDAYS:
171 retval = rtc_get_weekday(latch) - 1;
172 if (retval > 6) {
173 retval = 6;
174 }
175 break;
176 case RTC72421_REGISTER_MONTHDAYS:
177 retval = rtc_get_day_of_month(latch, 0);
178 retval %= 10;
179 break;
180 case RTC72421_REGISTER_10MONTHDAYS:
181 retval = rtc_get_day_of_month(latch, 0);
182 retval /= 10;
183 break;
184 case RTC72421_REGISTER_MONTHS:
185 retval = rtc_get_month(latch, 0);
186 retval %= 10;
187 break;
188 case RTC72421_REGISTER_10MONTHS:
189 retval = rtc_get_month(latch, 0);
190 retval /= 10;
191 break;
192 case RTC72421_REGISTER_YEARS:
193 retval = rtc_get_year(latch, 0);
194 retval %= 10;
195 break;
196 case RTC72421_REGISTER_10YEARS:
197 retval = rtc_get_year(latch, 0);
198 retval /= 10;
199 break;
200 case RTC72421_REGISTER_CTRL2:
201 retval = context->hour24 ? 2 : 0;
202 retval |= context->stop ? 1 : 0;
203 break;
204 }
205 return retval;
206 }
207
208
209 #define LIMIT_9(x) (x > 9) ? 9 : x
210
rtc72421_write(rtc_72421_t * context,uint8_t address,uint8_t data)211 void rtc72421_write(rtc_72421_t *context, uint8_t address, uint8_t data)
212 {
213 time_t latch = (context->stop) ? context->latch : rtc_get_latch(context->offset);
214 uint8_t real_data = data & 0xf;
215 uint8_t new_data;
216
217 switch (address & 0xf) {
218 case RTC72421_REGISTER_SECONDS:
219 new_data = rtc_get_second(latch, 0);
220 new_data /= 10;
221 new_data *= 10;
222 new_data += LIMIT_9(real_data);
223 if (context->stop) {
224 context->latch = rtc_set_latched_second(new_data, latch, 0);
225 } else {
226 context->offset = rtc_set_second(new_data, context->offset, 0);
227 }
228 break;
229 case RTC72421_REGISTER_10SECONDS:
230 new_data = rtc_get_second(latch, 0);
231 new_data %= 10;
232 new_data += ((real_data & 7) * 10);
233 if (context->stop) {
234 context->latch = rtc_set_latched_second(new_data, latch, 0);
235 } else {
236 context->offset = rtc_set_second(new_data, context->offset, 0);
237 }
238 break;
239 case RTC72421_REGISTER_MINUTES:
240 new_data = rtc_get_minute(latch, 0);
241 new_data /= 10;
242 new_data *= 10;
243 new_data += LIMIT_9(real_data);
244 if (context->stop) {
245 context->latch = rtc_set_latched_minute(new_data, latch, 0);
246 } else {
247 context->offset = rtc_set_minute(new_data, context->offset, 0);
248 }
249 break;
250 case RTC72421_REGISTER_10MINUTES:
251 new_data = rtc_get_minute(latch, 0);
252 new_data %= 10;
253 new_data += ((real_data & 7) * 10);
254 if (context->stop) {
255 context->latch = rtc_set_latched_minute(new_data, latch, 0);
256 } else {
257 context->offset = rtc_set_minute(new_data, context->offset, 0);
258 }
259 break;
260 case RTC72421_REGISTER_HOURS:
261 if (context->hour24) {
262 new_data = rtc_get_hour(latch, 0);
263 new_data /= 10;
264 new_data *= 10;
265 new_data += LIMIT_9(real_data);
266 if (context->stop) {
267 context->latch = rtc_set_latched_hour(new_data, latch, 0);
268 } else {
269 context->offset = rtc_set_hour(new_data, context->offset, 0);
270 }
271 } else {
272 new_data = rtc_get_hour_am_pm(latch, 0);
273 if (new_data >= 32) {
274 new_data -= 32;
275 new_data /= 10;
276 new_data *= 10;
277 new_data += (LIMIT_9(real_data) + 32);
278 } else {
279 new_data /= 10;
280 new_data *= 10;
281 new_data += LIMIT_9(real_data);
282 }
283 if (context->stop) {
284 context->latch = rtc_set_latched_hour_am_pm(new_data, latch, 0);
285 } else {
286 context->offset = rtc_set_hour_am_pm(new_data, context->offset, 0);
287 }
288 }
289 break;
290 case RTC72421_REGISTER_10HOURS:
291 if (real_data & 8) {
292 new_data = rtc_get_hour(latch, 0);
293 new_data %= 10;
294 new_data += ((real_data & 3) * 10);
295 context->hour24 = 1;
296 if (context->stop) {
297 context->latch = rtc_set_latched_hour(new_data, latch, 0);
298 } else {
299 context->offset = rtc_set_hour(new_data, context->offset, 0);
300 }
301 } else {
302 real_data &= 7;
303 new_data = rtc_get_hour_am_pm(latch, 0);
304 if (new_data >= 32) {
305 new_data -= 32;
306 }
307 new_data %= 10;
308 new_data += ((real_data & 3) * 10);
309 if (real_data & 4) {
310 new_data += 32;
311 }
312 context->hour24 = 0;
313 if (context->stop) {
314 context->latch = rtc_set_latched_hour_am_pm(new_data, latch, 0);
315 } else {
316 context->offset = rtc_set_hour_am_pm(new_data, context->offset, 0);
317 }
318 }
319 break;
320 case RTC72421_REGISTER_WEEKDAYS:
321 if (context->stop) {
322 context->latch = rtc_set_latched_weekday(((real_data + 1) & 7), latch);
323 } else {
324 context->offset = rtc_set_weekday(((real_data + 1) & 7), context->offset);
325 }
326 break;
327 case RTC72421_REGISTER_MONTHDAYS:
328 new_data = rtc_get_day_of_month(latch, 0);
329 new_data /= 10;
330 new_data *= 10;
331 new_data += LIMIT_9(real_data);
332 if (context->stop) {
333 context->latch = rtc_set_latched_day_of_month(new_data, latch, 0);
334 } else {
335 context->offset = rtc_set_day_of_month(new_data, context->offset, 0);
336 }
337 break;
338 case RTC72421_REGISTER_10MONTHDAYS:
339 new_data = rtc_get_day_of_month(latch, 0);
340 new_data %= 10;
341 new_data += ((real_data & 3) * 10);
342 if (context->stop) {
343 context->latch = rtc_set_latched_day_of_month(new_data, latch, 0);
344 } else {
345 context->offset = rtc_set_day_of_month(new_data, context->offset, 0);
346 }
347 break;
348 case RTC72421_REGISTER_MONTHS:
349 new_data = rtc_get_month(latch, 0);
350 new_data /= 10;
351 new_data *= 10;
352 new_data += LIMIT_9(real_data);
353 if (context->stop) {
354 context->latch = rtc_set_latched_month(new_data, latch, 0);
355 } else {
356 context->offset = rtc_set_month(new_data, context->offset, 0);
357 }
358 break;
359 case RTC72421_REGISTER_10MONTHS:
360 new_data = rtc_get_month(latch, 0);
361 new_data %= 10;
362 new_data += ((real_data & 1) * 10);
363 if (context->stop) {
364 context->latch = rtc_set_latched_month(new_data, latch, 0);
365 } else {
366 context->offset = rtc_set_month(new_data, context->offset, 0);
367 }
368 break;
369 case RTC72421_REGISTER_YEARS:
370 new_data = rtc_get_year(latch, 0);
371 new_data /= 10;
372 new_data *= 10;
373 new_data += LIMIT_9(real_data);
374 if (context->stop) {
375 context->latch = rtc_set_latched_year(new_data, latch, 0);
376 } else {
377 context->offset = rtc_set_year(new_data, context->offset, 0);
378 }
379 break;
380 case RTC72421_REGISTER_10YEARS:
381 new_data = rtc_get_year(latch, 0);
382 new_data %= 10;
383 new_data += (LIMIT_9(real_data) * 10);
384 if (context->stop) {
385 context->latch = rtc_set_latched_year(new_data, latch, 0);
386 } else {
387 context->offset = rtc_set_year(new_data, context->offset, 0);
388 }
389 break;
390 case RTC72421_REGISTER_CTRL2:
391 context->hour24 = (real_data & 4) ? 1: 0;
392 if (real_data & 2) {
393 context->stop = 1;
394 context->latch = rtc_get_latch(context->offset);
395 } else {
396 context->stop = 0;
397 context->offset = context->offset - (rtc_get_latch(0) - (context->latch - context->offset));
398 }
399 break;
400 }
401 }
402
403 /* ---------------------------------------------------------------------------------------------------- */
404
405 /* RTC_72421 snapshot module format:
406
407 type | name | description
408 --------------------------------
409 BYTE | stop | stop flag
410 BYTE | 24 hours | 24 hours flag
411 DWORD | latch hi | high DWORD of latch offset
412 DWORD | latch lo | low DWORD of latch offset
413 DWORD | offset hi | high DWORD of RTC offset
414 DWORD | offset lo | low DWORD of RTC offset
415 DWORD | old offset hi | high DWORD of old RTC offset
416 DWORD | old offset lo | low DWORD of old RTC offset
417 STRING | device | device name string
418 */
419
420 static char snap_module_name[] = "RTC_72421";
421 #define SNAP_MAJOR 0
422 #define SNAP_MINOR 0
423
rtc72421_write_snapshot(rtc_72421_t * context,snapshot_t * s)424 int rtc72421_write_snapshot(rtc_72421_t *context, snapshot_t *s)
425 {
426 uint32_t latch_lo = 0;
427 uint32_t latch_hi = 0;
428 uint32_t offset_lo = 0;
429 uint32_t offset_hi = 0;
430 uint32_t old_offset_lo = 0;
431 uint32_t old_offset_hi = 0;
432 snapshot_module_t *m;
433
434 /* time_t can be either 32bit or 64bit, so we save as 64bit */
435 #if (SIZE_OF_TIME_T == 8)
436 latch_hi = (uint32_t)(context->latch >> 32);
437 latch_lo = (uint32_t)(context->latch & 0xffffffff);
438 offset_hi = (uint32_t)(context->offset >> 32);
439 offset_lo = (uint32_t)(context->offset & 0xffffffff);
440 old_offset_hi = (uint32_t)(context->old_offset >> 32);
441 old_offset_lo = (uint32_t)(context->old_offset & 0xffffffff);
442 #else
443 latch_lo = (uint32_t)context->latch;
444 offset_lo = (uint32_t)context->offset;
445 old_offset_lo = (uint32_t)context->old_offset;
446 #endif
447
448 m = snapshot_module_create(s, snap_module_name, SNAP_MAJOR, SNAP_MINOR);
449
450 if (m == NULL) {
451 return -1;
452 }
453
454 if (0
455 || SMW_B(m, (uint8_t)context->stop) < 0
456 || SMW_B(m, (uint8_t)context->hour24) < 0
457 || SMW_DW(m, latch_hi) < 0
458 || SMW_DW(m, latch_lo) < 0
459 || SMW_DW(m, offset_hi) < 0
460 || SMW_DW(m, offset_lo) < 0
461 || SMW_DW(m, old_offset_hi) < 0
462 || SMW_DW(m, old_offset_lo) < 0
463 || SMW_STR(m, context->device) < 0) {
464 snapshot_module_close(m);
465 return -1;
466 }
467 return snapshot_module_close(m);
468 }
469
rtc72421_read_snapshot(rtc_72421_t * context,snapshot_t * s)470 int rtc72421_read_snapshot(rtc_72421_t *context, snapshot_t *s)
471 {
472 uint32_t latch_lo = 0;
473 uint32_t latch_hi = 0;
474 uint32_t offset_lo = 0;
475 uint32_t offset_hi = 0;
476 uint32_t old_offset_lo = 0;
477 uint32_t old_offset_hi = 0;
478 uint8_t vmajor, vminor;
479 snapshot_module_t *m;
480
481 m = snapshot_module_open(s, snap_module_name, &vmajor, &vminor);
482
483 if (m == NULL) {
484 return -1;
485 }
486
487 /* Do not accept versions higher than current */
488 if (vmajor > SNAP_MAJOR || vminor > SNAP_MINOR) {
489 snapshot_set_error(SNAPSHOT_MODULE_HIGHER_VERSION);
490 goto fail;
491 }
492
493 if (0
494 || SMR_B_INT(m, &context->stop) < 0
495 || SMR_B_INT(m, &context->hour24) < 0
496 || SMR_DW(m, &latch_hi) < 0
497 || SMR_DW(m, &latch_lo) < 0
498 || SMR_DW(m, &offset_hi) < 0
499 || SMR_DW(m, &offset_lo) < 0
500 || SMR_DW(m, &old_offset_hi) < 0
501 || SMR_DW(m, &old_offset_lo) < 0
502 || SMR_STR(m, &context->device) < 0) {
503 goto fail;
504 }
505
506 #if (SIZE_OF_TIME_T == 8)
507 context->latch = (time_t)(latch_hi) << 32;
508 context->latch |= latch_lo;
509 context->offset = (time_t)(offset_hi) << 32;
510 context->offset |= offset_lo;
511 context->old_offset = (time_t)(old_offset_hi) << 32;
512 context->old_offset |= old_offset_lo;
513 #else
514 context->latch = latch_lo;
515 context->offset = offset_lo;
516 context->old_offset = old_offset_lo;
517 #endif
518
519 return snapshot_module_close(m);
520
521 fail:
522 snapshot_module_close(m);
523 return -1;
524 }
525