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