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 * CMDHD
90 * RAMLINK
91 */
92
93 /* ---------------------------------------------------------------------------------------------------- */
94
rtc72421_init(char * device)95 rtc_72421_t *rtc72421_init(char *device)
96 {
97 rtc_72421_t *retval = lib_calloc(1, sizeof(rtc_72421_t));
98 int loaded = rtc_load_context(device, 0, 0);
99
100 if (loaded) {
101 retval->offset = rtc_get_loaded_offset();
102 } else {
103 retval->offset = 0;
104 }
105 retval->old_offset = retval->offset;
106
107 retval->hour24 = 0;
108 retval->device = lib_strdup(device);
109
110 return retval;
111 }
112
rtc72421_destroy(rtc_72421_t * context,int save)113 void rtc72421_destroy(rtc_72421_t *context, int save)
114 {
115 if (save) {
116 if (context->old_offset != context->offset) {
117 rtc_save_context(NULL, 0, NULL, 0, context->device, context->offset);
118 }
119 }
120 lib_free(context->device);
121 lib_free(context);
122 }
123
124 /* ---------------------------------------------------------------------------------------------------- */
125
rtc72421_read(rtc_72421_t * context,uint8_t address)126 uint8_t rtc72421_read(rtc_72421_t *context, uint8_t address)
127 {
128 uint8_t retval = 0;
129 time_t latch = (context->stop) ? context->latch : rtc_get_latch(context->offset);
130
131 switch (address & 0xf) {
132 case RTC72421_REGISTER_SECONDS:
133 retval = rtc_get_second(latch, 0);
134 retval %= 10;
135 break;
136 case RTC72421_REGISTER_10SECONDS:
137 retval = rtc_get_second(latch, 0);
138 retval /= 10;
139 break;
140 case RTC72421_REGISTER_MINUTES:
141 retval = rtc_get_minute(latch, 0);
142 retval %= 10;
143 break;
144 case RTC72421_REGISTER_10MINUTES:
145 retval = rtc_get_minute(latch, 0);
146 retval /= 10;
147 break;
148 case RTC72421_REGISTER_HOURS:
149 if (context->hour24) {
150 retval = rtc_get_hour(latch, 0);
151 } else {
152 retval = rtc_get_hour_am_pm(latch, 0);
153 retval &= 0x1f;
154 }
155 retval %= 10;
156 break;
157 case RTC72421_REGISTER_10HOURS:
158 if (context->hour24) {
159 retval = rtc_get_hour(latch, 0);
160 retval /= 10;
161 retval |= 8;
162 } else {
163 retval = rtc_get_hour_am_pm(latch, 0);
164 if (retval > 23) {
165 retval = (retval - 32) / 10;
166 retval |= 4;
167 } else {
168 retval /= 10;
169 }
170 }
171 break;
172 case RTC72421_REGISTER_WEEKDAYS:
173 retval = rtc_get_weekday(latch);
174 if (retval > 6) {
175 retval = 6;
176 }
177 break;
178 case RTC72421_REGISTER_MONTHDAYS:
179 retval = rtc_get_day_of_month(latch, 0);
180 retval %= 10;
181 break;
182 case RTC72421_REGISTER_10MONTHDAYS:
183 retval = rtc_get_day_of_month(latch, 0);
184 retval /= 10;
185 break;
186 case RTC72421_REGISTER_MONTHS:
187 retval = rtc_get_month(latch, 0);
188 retval %= 10;
189 break;
190 case RTC72421_REGISTER_10MONTHS:
191 retval = rtc_get_month(latch, 0);
192 retval /= 10;
193 break;
194 case RTC72421_REGISTER_YEARS:
195 retval = rtc_get_year(latch, 0);
196 retval %= 10;
197 break;
198 case RTC72421_REGISTER_10YEARS:
199 retval = rtc_get_year(latch, 0);
200 retval /= 10;
201 break;
202 case RTC72421_REGISTER_CTRL1:
203 /* RAMLINK writes/reads data to this register to detect the
204 presence of the rtc */
205 retval = context->control[1];
206 break;
207 case RTC72421_REGISTER_CTRL2:
208 retval = context->hour24 ? 2 : 0;
209 retval |= context->stop ? 1 : 0;
210 break;
211 }
212 return retval;
213 }
214
215
216 #define LIMIT_9(x) (x > 9) ? 9 : x
217
rtc72421_write(rtc_72421_t * context,uint8_t address,uint8_t data)218 void rtc72421_write(rtc_72421_t *context, uint8_t address, uint8_t data)
219 {
220 time_t latch = (context->stop) ? context->latch : rtc_get_latch(context->offset);
221 uint8_t real_data = data & 0xf;
222 uint8_t new_data;
223
224 switch (address & 0xf) {
225 case RTC72421_REGISTER_SECONDS:
226 new_data = rtc_get_second(latch, 0);
227 new_data /= 10;
228 new_data *= 10;
229 new_data += LIMIT_9(real_data);
230 if (context->stop) {
231 context->latch = rtc_set_latched_second(new_data, latch, 0);
232 } else {
233 context->offset = rtc_set_second(new_data, context->offset, 0);
234 }
235 break;
236 case RTC72421_REGISTER_10SECONDS:
237 new_data = rtc_get_second(latch, 0);
238 new_data %= 10;
239 new_data += ((real_data & 7) * 10);
240 if (context->stop) {
241 context->latch = rtc_set_latched_second(new_data, latch, 0);
242 } else {
243 context->offset = rtc_set_second(new_data, context->offset, 0);
244 }
245 break;
246 case RTC72421_REGISTER_MINUTES:
247 new_data = rtc_get_minute(latch, 0);
248 new_data /= 10;
249 new_data *= 10;
250 new_data += LIMIT_9(real_data);
251 if (context->stop) {
252 context->latch = rtc_set_latched_minute(new_data, latch, 0);
253 } else {
254 context->offset = rtc_set_minute(new_data, context->offset, 0);
255 }
256 break;
257 case RTC72421_REGISTER_10MINUTES:
258 new_data = rtc_get_minute(latch, 0);
259 new_data %= 10;
260 new_data += ((real_data & 7) * 10);
261 if (context->stop) {
262 context->latch = rtc_set_latched_minute(new_data, latch, 0);
263 } else {
264 context->offset = rtc_set_minute(new_data, context->offset, 0);
265 }
266 break;
267 case RTC72421_REGISTER_HOURS:
268 if (context->hour24) {
269 new_data = rtc_get_hour(latch, 0);
270 new_data /= 10;
271 new_data *= 10;
272 new_data += LIMIT_9(real_data);
273 if (context->stop) {
274 context->latch = rtc_set_latched_hour(new_data, latch, 0);
275 } else {
276 context->offset = rtc_set_hour(new_data, context->offset, 0);
277 }
278 } else {
279 new_data = rtc_get_hour_am_pm(latch, 0);
280 if (new_data >= 32) {
281 new_data -= 32;
282 new_data /= 10;
283 new_data *= 10;
284 new_data += (LIMIT_9(real_data) + 32);
285 } else {
286 new_data /= 10;
287 new_data *= 10;
288 new_data += LIMIT_9(real_data);
289 }
290 if (context->stop) {
291 context->latch = rtc_set_latched_hour_am_pm(new_data, latch, 0);
292 } else {
293 context->offset = rtc_set_hour_am_pm(new_data, context->offset, 0);
294 }
295 }
296 break;
297 case RTC72421_REGISTER_10HOURS:
298 if (real_data & 8) {
299 new_data = rtc_get_hour(latch, 0);
300 new_data %= 10;
301 new_data += ((real_data & 3) * 10);
302 context->hour24 = 1;
303 if (context->stop) {
304 context->latch = rtc_set_latched_hour(new_data, latch, 0);
305 } else {
306 context->offset = rtc_set_hour(new_data, context->offset, 0);
307 }
308 } else {
309 real_data &= 7;
310 new_data = rtc_get_hour_am_pm(latch, 0);
311 if (new_data >= 32) {
312 new_data -= 32;
313 }
314 new_data %= 10;
315 new_data += ((real_data & 3) * 10);
316 if (real_data & 4) {
317 new_data += 32;
318 }
319 context->hour24 = 0;
320 if (context->stop) {
321 context->latch = rtc_set_latched_hour_am_pm(new_data, latch, 0);
322 } else {
323 context->offset = rtc_set_hour_am_pm(new_data, context->offset, 0);
324 }
325 }
326 break;
327 case RTC72421_REGISTER_WEEKDAYS:
328 if (context->stop) {
329 context->latch = rtc_set_latched_weekday(((real_data + 1) & 7), latch);
330 } else {
331 context->offset = rtc_set_weekday(((real_data + 1) & 7), context->offset);
332 }
333 break;
334 case RTC72421_REGISTER_MONTHDAYS:
335 new_data = rtc_get_day_of_month(latch, 0);
336 new_data /= 10;
337 new_data *= 10;
338 new_data += LIMIT_9(real_data);
339 if (context->stop) {
340 context->latch = rtc_set_latched_day_of_month(new_data, latch, 0);
341 } else {
342 context->offset = rtc_set_day_of_month(new_data, context->offset, 0);
343 }
344 break;
345 case RTC72421_REGISTER_10MONTHDAYS:
346 new_data = rtc_get_day_of_month(latch, 0);
347 new_data %= 10;
348 new_data += ((real_data & 3) * 10);
349 if (context->stop) {
350 context->latch = rtc_set_latched_day_of_month(new_data, latch, 0);
351 } else {
352 context->offset = rtc_set_day_of_month(new_data, context->offset, 0);
353 }
354 break;
355 case RTC72421_REGISTER_MONTHS:
356 new_data = rtc_get_month(latch, 0);
357 new_data /= 10;
358 new_data *= 10;
359 new_data += LIMIT_9(real_data);
360 if (context->stop) {
361 context->latch = rtc_set_latched_month(new_data, latch, 0);
362 } else {
363 context->offset = rtc_set_month(new_data, context->offset, 0);
364 }
365 break;
366 case RTC72421_REGISTER_10MONTHS:
367 new_data = rtc_get_month(latch, 0);
368 new_data %= 10;
369 new_data += ((real_data & 1) * 10);
370 if (context->stop) {
371 context->latch = rtc_set_latched_month(new_data, latch, 0);
372 } else {
373 context->offset = rtc_set_month(new_data, context->offset, 0);
374 }
375 break;
376 case RTC72421_REGISTER_YEARS:
377 new_data = rtc_get_year(latch, 0);
378 new_data /= 10;
379 new_data *= 10;
380 new_data += LIMIT_9(real_data);
381 if (context->stop) {
382 context->latch = rtc_set_latched_year(new_data, latch, 0);
383 } else {
384 context->offset = rtc_set_year(new_data, context->offset, 0);
385 }
386 break;
387 case RTC72421_REGISTER_10YEARS:
388 new_data = rtc_get_year(latch, 0);
389 new_data %= 10;
390 new_data += (LIMIT_9(real_data) * 10);
391 if (context->stop) {
392 context->latch = rtc_set_latched_year(new_data, latch, 0);
393 } else {
394 context->offset = rtc_set_year(new_data, context->offset, 0);
395 }
396 break;
397 case RTC72421_REGISTER_CTRL0:
398 context->control[0] = real_data;
399 break;
400 case RTC72421_REGISTER_CTRL1:
401 /* RAMLINK writes/reads data to this register to detect the
402 presence of the rtc */
403 context->control[1] = real_data;
404 break;
405 case RTC72421_REGISTER_CTRL2:
406 context->control[2] = real_data;
407 context->hour24 = (real_data & 4) ? 1: 0;
408 if (real_data & 2) {
409 context->stop = 1;
410 context->latch = rtc_get_latch(context->offset);
411 } else {
412 /* problem here, we have to do this only if we were previously
413 stopped, otherwise we mess up the counter */
414 if (context->stop) {
415 context->stop = 0;
416 context->offset = context->offset -
417 (rtc_get_latch(0) - (context->latch - context->offset));
418 }
419 }
420 break;
421 }
422 }
423
424 /* ---------------------------------------------------------------------------------------------------- */
425
426 /* RTC_72421 snapshot module format:
427
428 type | name | description
429 --------------------------------
430 BYTE | stop | stop flag
431 BYTE | 24 hours | 24 hours flag
432 DWORD | latch hi | high DWORD of latch offset
433 DWORD | latch lo | low DWORD of latch offset
434 DWORD | offset hi | high DWORD of RTC offset
435 DWORD | offset lo | low DWORD of RTC offset
436 DWORD | old offset hi | high DWORD of old RTC offset
437 DWORD | old offset lo | low DWORD of old RTC offset
438 STRING | device | device name string
439 */
440
441 static const char snap_module_name[] = "RTC_72421";
442 #define SNAP_MAJOR 0
443 #define SNAP_MINOR 0
444
rtc72421_write_snapshot(rtc_72421_t * context,snapshot_t * s)445 int rtc72421_write_snapshot(rtc_72421_t *context, snapshot_t *s)
446 {
447 uint32_t latch_lo = 0;
448 uint32_t latch_hi = 0;
449 uint32_t offset_lo = 0;
450 uint32_t offset_hi = 0;
451 uint32_t old_offset_lo = 0;
452 uint32_t old_offset_hi = 0;
453 snapshot_module_t *m;
454
455 /* time_t can be either 32bit or 64bit, so we save as 64bit */
456 #if (SIZE_OF_TIME_T == 8)
457 latch_hi = (uint32_t)(context->latch >> 32);
458 latch_lo = (uint32_t)(context->latch & 0xffffffff);
459 offset_hi = (uint32_t)(context->offset >> 32);
460 offset_lo = (uint32_t)(context->offset & 0xffffffff);
461 old_offset_hi = (uint32_t)(context->old_offset >> 32);
462 old_offset_lo = (uint32_t)(context->old_offset & 0xffffffff);
463 #else
464 latch_lo = (uint32_t)context->latch;
465 offset_lo = (uint32_t)context->offset;
466 old_offset_lo = (uint32_t)context->old_offset;
467 #endif
468
469 m = snapshot_module_create(s, snap_module_name, SNAP_MAJOR, SNAP_MINOR);
470
471 if (m == NULL) {
472 return -1;
473 }
474
475 if (0
476 || SMW_B(m, (uint8_t)context->stop) < 0
477 || SMW_B(m, (uint8_t)context->hour24) < 0
478 || SMW_DW(m, latch_hi) < 0
479 || SMW_DW(m, latch_lo) < 0
480 || SMW_DW(m, offset_hi) < 0
481 || SMW_DW(m, offset_lo) < 0
482 || SMW_DW(m, old_offset_hi) < 0
483 || SMW_DW(m, old_offset_lo) < 0
484 || SMW_STR(m, context->device) < 0) {
485 snapshot_module_close(m);
486 return -1;
487 }
488 return snapshot_module_close(m);
489 }
490
rtc72421_read_snapshot(rtc_72421_t * context,snapshot_t * s)491 int rtc72421_read_snapshot(rtc_72421_t *context, snapshot_t *s)
492 {
493 uint32_t latch_lo = 0;
494 uint32_t latch_hi = 0;
495 uint32_t offset_lo = 0;
496 uint32_t offset_hi = 0;
497 uint32_t old_offset_lo = 0;
498 uint32_t old_offset_hi = 0;
499 uint8_t vmajor, vminor;
500 snapshot_module_t *m;
501
502 m = snapshot_module_open(s, snap_module_name, &vmajor, &vminor);
503
504 if (m == NULL) {
505 return -1;
506 }
507
508 /* Do not accept versions higher than current */
509 if (snapshot_version_is_bigger(vmajor, vminor, SNAP_MAJOR, SNAP_MINOR)) {
510 snapshot_set_error(SNAPSHOT_MODULE_HIGHER_VERSION);
511 goto fail;
512 }
513
514 if (0
515 || SMR_B_INT(m, &context->stop) < 0
516 || SMR_B_INT(m, &context->hour24) < 0
517 || SMR_DW(m, &latch_hi) < 0
518 || SMR_DW(m, &latch_lo) < 0
519 || SMR_DW(m, &offset_hi) < 0
520 || SMR_DW(m, &offset_lo) < 0
521 || SMR_DW(m, &old_offset_hi) < 0
522 || SMR_DW(m, &old_offset_lo) < 0
523 || SMR_STR(m, &context->device) < 0) {
524 goto fail;
525 }
526
527 #if (SIZE_OF_TIME_T == 8)
528 context->latch = (time_t)(latch_hi) << 32;
529 context->latch |= latch_lo;
530 context->offset = (time_t)(offset_hi) << 32;
531 context->offset |= offset_lo;
532 context->old_offset = (time_t)(old_offset_hi) << 32;
533 context->old_offset |= old_offset_lo;
534 #else
535 context->latch = latch_lo;
536 context->offset = offset_lo;
537 context->old_offset = old_offset_lo;
538 #endif
539
540 return snapshot_module_close(m);
541
542 fail:
543 snapshot_module_close(m);
544 return -1;
545 }
546