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