1 /*
2  * rtc-58321a.c - RTC-58321A 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-58321a.h"
30 #include "lib.h"
31 #include "rtc.h"
32 #include "snapshot.h"
33 
34 #include <string.h>
35 
36 /* The RTC-58321A is a 4bit multiplexed address/data line RTC module,
37  * the RTC registers are accessed by first setting the address and
38  * then reading/writing. The RTC has the following features:
39  * - Real-Time Clock Counts seconds, minutes, hours, date of the month,
40  *   months, and years
41  * - Clock can be stopped by asserting the stop line
42  * - All registers are decimal
43  */
44 
45 /* The RTC-58321A has the following clock registers:
46  *
47  * register 0 : bits 3-0 seconds
48  *
49  * register 1 : bit  3   0
50  *              bits 2-0 10 seconds
51  *
52  * register 2 : bits 3-0 minutes
53  *
54  * register 3 : bit  3   0
55  *              bits 2-0 10 minutes
56  *
57  * register 4 : bits 3-0 hours
58  *
59  * register 5 : bit  3   24/12 hour selection (0 = 12 hour, 1 = 24 hour)
60  *              bit  2   AM/PM indication bit (1 when in 12 hour mode, otherwise 0)
61  *              bits 1-0 10 hours
62  *
63  * register 6 : bit  3   0
64  *              bits 2-0 weekdays
65  *
66  * register 7 : bits 3-0 day of the month
67  *
68  * register 8 : bits 3-2 leapyear indicator
69  *              bits 1-0 10 day of the month
70  *
71  * register 9 : bits 3-0 months
72  *
73  * register A : bits 3-1 0
74  *              bit  0   10 months
75  *
76  * register B : bits 3-0 years
77  *
78  * register C : bits 3-0 10 years
79  *
80  * register D : bits 3-0 0 (reset register)
81  *
82  * register E : bits 3-0 0 (SS0 register)
83  *
84  * register F : bits 3-0 0 (SS1 register)
85  */
86 
87 /* This module is currently used in the following emulated hardware:
88    - userport RTC (58321a) device
89  */
90 
91 /* ---------------------------------------------------------------------------------------------------- */
92 
rtc58321a_init(char * device)93 rtc_58321a_t *rtc58321a_init(char *device)
94 {
95     rtc_58321a_t *retval = lib_calloc(1, sizeof(rtc_58321a_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 = 1;
106     retval->device = lib_strdup(device);
107 
108     return retval;
109 }
110 
rtc58321a_destroy(rtc_58321a_t * context,int save)111 void rtc58321a_destroy(rtc_58321a_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 
rtc58321a_read(rtc_58321a_t * context)124 uint8_t rtc58321a_read(rtc_58321a_t *context)
125 {
126     uint8_t retval = 0;
127     time_t latch = (context->stop) ? context->latch : rtc_get_latch(context->offset);
128 
129     switch (context->address) {
130         case RTC58321A_REGISTER_SECONDS:
131             retval = rtc_get_second(latch, 0);
132             retval %= 10;
133             break;
134         case RTC58321A_REGISTER_10SECONDS:
135             retval = rtc_get_second(latch, 0);
136             retval /= 10;
137             break;
138         case RTC58321A_REGISTER_MINUTES:
139             retval = rtc_get_minute(latch, 0);
140             retval %= 10;
141             break;
142         case RTC58321A_REGISTER_10MINUTES:
143             retval = rtc_get_minute(latch, 0);
144             retval /= 10;
145             break;
146         case RTC58321A_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 RTC58321A_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 RTC58321A_REGISTER_WEEKDAYS:
171             retval = rtc_get_weekday(latch) - 1;
172             if (retval > 6) {
173                 retval = 6;
174             }
175             break;
176         case RTC58321A_REGISTER_MONTHDAYS:
177             retval = rtc_get_day_of_month(latch, 0);
178             retval %= 10;
179             break;
180         case RTC58321A_REGISTER_10MONTHDAYS:
181             retval = rtc_get_day_of_month(latch, 0);
182             retval /= 10;
183 /* TODO: leapyear calculation */
184             break;
185         case RTC58321A_REGISTER_MONTHS:
186             retval = rtc_get_month(latch, 0);
187             retval %= 10;
188             break;
189         case RTC58321A_REGISTER_10MONTHS:
190             retval = rtc_get_month(latch, 0);
191             retval /= 10;
192             break;
193         case RTC58321A_REGISTER_YEARS:
194             retval = rtc_get_year(latch, 0);
195             retval %= 10;
196             break;
197         case RTC58321A_REGISTER_10YEARS:
198             retval = rtc_get_year(latch, 0);
199             retval /= 10;
200             break;
201     }
202     return retval;
203 }
204 
205 
rtc58321a_write_address(rtc_58321a_t * context,uint8_t address)206 void rtc58321a_write_address(rtc_58321a_t *context, uint8_t address)
207 {
208     context->address = address & 0xf;
209 }
210 
211 #define LIMIT_9(x) (x > 9) ? 9 : x
212 
rtc58321a_write_data(rtc_58321a_t * context,uint8_t data)213 void rtc58321a_write_data(rtc_58321a_t *context, uint8_t data)
214 {
215     time_t latch = (context->stop) ? context->latch : rtc_get_latch(context->offset);
216     uint8_t real_data = data & 0xf;
217     uint8_t new_data;
218 
219     switch (context->address) {
220         case RTC58321A_REGISTER_SECONDS:
221             new_data = rtc_get_second(latch, 0);
222             new_data /= 10;
223             new_data *= 10;
224             new_data += LIMIT_9(real_data);
225             if (context->stop) {
226                 context->latch = rtc_set_latched_second(new_data, latch, 0);
227             } else {
228                 context->offset = rtc_set_second(new_data, context->offset, 0);
229             }
230             break;
231         case RTC58321A_REGISTER_10SECONDS:
232             new_data = rtc_get_second(latch, 0);
233             new_data %= 10;
234             new_data += ((real_data & 7) * 10);
235             if (context->stop) {
236                 context->latch = rtc_set_latched_second(new_data, latch, 0);
237             } else {
238                 context->offset = rtc_set_second(new_data, context->offset, 0);
239             }
240             break;
241         case RTC58321A_REGISTER_MINUTES:
242             new_data = rtc_get_minute(latch, 0);
243             new_data /= 10;
244             new_data *= 10;
245             new_data += LIMIT_9(real_data);
246             if (context->stop) {
247                 context->latch = rtc_set_latched_minute(new_data, latch, 0);
248             } else {
249                 context->offset = rtc_set_minute(new_data, context->offset, 0);
250             }
251             break;
252         case RTC58321A_REGISTER_10MINUTES:
253             new_data = rtc_get_minute(latch, 0);
254             new_data %= 10;
255             new_data += ((real_data & 7) * 10);
256             if (context->stop) {
257                 context->latch = rtc_set_latched_minute(new_data, latch, 0);
258             } else {
259                 context->offset = rtc_set_minute(new_data, context->offset, 0);
260             }
261             break;
262         case RTC58321A_REGISTER_HOURS:
263             if (context->hour24) {
264                 new_data = rtc_get_hour(latch, 0);
265                 new_data /= 10;
266                 new_data *= 10;
267                 new_data += LIMIT_9(real_data);
268                 if (context->stop) {
269                     context->latch = rtc_set_latched_hour(new_data, latch, 0);
270                 } else {
271                     context->offset = rtc_set_hour(new_data, context->offset, 0);
272                 }
273             } else {
274                 new_data = rtc_get_hour_am_pm(latch, 0);
275                 if (new_data >= 32) {
276                     new_data -= 32;
277                     new_data /= 10;
278                     new_data *= 10;
279                     new_data += (LIMIT_9(real_data) + 32);
280                 } else {
281                     new_data /= 10;
282                     new_data *= 10;
283                     new_data += LIMIT_9(real_data);
284                 }
285                 if (context->stop) {
286                     context->latch = rtc_set_latched_hour_am_pm(new_data, latch, 0);
287                 } else {
288                     context->offset = rtc_set_hour_am_pm(new_data, context->offset, 0);
289                 }
290             }
291             break;
292         case RTC58321A_REGISTER_10HOURS:
293             if (real_data & 8) {
294                 new_data = rtc_get_hour(latch, 0);
295                 new_data %= 10;
296                 new_data += ((real_data & 3) * 10);
297                 context->hour24 = 1;
298                 if (context->stop) {
299                     context->latch = rtc_set_latched_hour(new_data, latch, 0);
300                 } else {
301                     context->offset = rtc_set_hour(new_data, context->offset, 0);
302                 }
303             } else {
304                 real_data &= 7;
305                 new_data = rtc_get_hour_am_pm(latch, 0);
306                 if (new_data >= 32) {
307                     new_data -= 32;
308                 }
309                 new_data %= 10;
310                 new_data += ((real_data & 3) * 10);
311                 if (real_data & 4) {
312                     new_data += 32;
313                 }
314                 context->hour24 = 0;
315                 if (context->stop) {
316                     context->latch = rtc_set_latched_hour_am_pm(new_data, latch, 0);
317                 } else {
318                     context->offset = rtc_set_hour_am_pm(new_data, context->offset, 0);
319                 }
320             }
321             break;
322         case RTC58321A_REGISTER_WEEKDAYS:
323             if (context->stop) {
324                 context->latch = rtc_set_latched_weekday(((real_data + 1) & 7), latch);
325             } else {
326                 context->offset = rtc_set_weekday(((real_data + 1) & 7), context->offset);
327             }
328             break;
329         case RTC58321A_REGISTER_MONTHDAYS:
330             new_data = rtc_get_day_of_month(latch, 0);
331             new_data /= 10;
332             new_data *= 10;
333             new_data += LIMIT_9(real_data);
334             if (context->stop) {
335                 context->latch = rtc_set_latched_day_of_month(new_data, latch, 0);
336             } else {
337                 context->offset = rtc_set_day_of_month(new_data, context->offset, 0);
338             }
339             break;
340         case RTC58321A_REGISTER_10MONTHDAYS:
341             new_data = rtc_get_day_of_month(latch, 0);
342             new_data %= 10;
343             new_data += ((real_data & 3) * 10);
344             if (context->stop) {
345                 context->latch = rtc_set_latched_day_of_month(new_data, latch, 0);
346             } else {
347                 context->offset = rtc_set_day_of_month(new_data, context->offset, 0);
348             }
349             break;
350         case RTC58321A_REGISTER_MONTHS:
351             new_data = rtc_get_month(latch, 0);
352             new_data /= 10;
353             new_data *= 10;
354             new_data += LIMIT_9(real_data);
355             if (context->stop) {
356                 context->latch = rtc_set_latched_month(new_data, latch, 0);
357             } else {
358                 context->offset = rtc_set_month(new_data, context->offset, 0);
359             }
360             break;
361         case RTC58321A_REGISTER_10MONTHS:
362             new_data = rtc_get_month(latch, 0);
363             new_data %= 10;
364             new_data += ((real_data & 1) * 10);
365             if (context->stop) {
366                 context->latch = rtc_set_latched_month(new_data, latch, 0);
367             } else {
368                 context->offset = rtc_set_month(new_data, context->offset, 0);
369             }
370             break;
371         case RTC58321A_REGISTER_YEARS:
372             new_data = rtc_get_year(latch, 0);
373             new_data /= 10;
374             new_data *= 10;
375             new_data += LIMIT_9(real_data);
376             if (context->stop) {
377                 context->latch = rtc_set_latched_year(new_data, latch, 0);
378             } else {
379                 context->offset = rtc_set_year(new_data, context->offset, 0);
380             }
381             break;
382         case RTC58321A_REGISTER_10YEARS:
383             new_data = rtc_get_year(latch, 0);
384             new_data %= 10;
385             new_data += (LIMIT_9(real_data) * 10);
386             if (context->stop) {
387                 context->latch = rtc_set_latched_year(new_data, latch, 0);
388             } else {
389                 context->offset = rtc_set_year(new_data, context->offset, 0);
390             }
391             break;
392     }
393 }
394 
395 
396 /* ---------------------------------------------------------------------------------------------------- */
397 
398 /* RTC_58321A snapshot module format:
399 
400    type   | name          | description
401    --------------------------------
402    BYTE   | stop          | stop flag
403    BYTE   | 24 hours      | 24 hours flag
404    BYTE   | address       | current address
405    DWORD  | latch hi      | high DWORD of latch offset
406    DWORD  | latch lo      | low DWORD of latch offset
407    DWORD  | offset hi     | high DWORD of RTC offset
408    DWORD  | offset lo     | low DWORD of RTC offset
409    DWORD  | old offset hi | high DWORD of old RTC offset
410    DWORD  | old offset lo | low DWORD of old RTC offset
411    STRING | device        | device name STRING
412  */
413 
414 static const char snap_module_name[] = "RTC_58321A";
415 #define SNAP_MAJOR   0
416 #define SNAP_MINOR   0
417 
rtc58321a_write_snapshot(rtc_58321a_t * context,snapshot_t * s)418 int rtc58321a_write_snapshot(rtc_58321a_t *context, snapshot_t *s)
419 {
420     uint32_t latch_lo = 0;
421     uint32_t latch_hi = 0;
422     uint32_t offset_lo = 0;
423     uint32_t offset_hi = 0;
424     uint32_t old_offset_lo = 0;
425     uint32_t old_offset_hi = 0;
426     snapshot_module_t *m;
427 
428     /* time_t can be either 32bit or 64bit, so we save as 64bit */
429 #if (SIZE_OF_TIME_T == 8)
430     latch_hi = (uint32_t)(context->latch >> 32);
431     latch_lo = (uint32_t)(context->latch & 0xffffffff);
432     offset_hi = (uint32_t)(context->offset >> 32);
433     offset_lo = (uint32_t)(context->offset & 0xffffffff);
434     old_offset_hi = (uint32_t)(context->old_offset >> 32);
435     old_offset_lo = (uint32_t)(context->old_offset & 0xffffffff);
436 #else
437     latch_lo = (uint32_t)context->latch;
438     offset_lo = (uint32_t)context->offset;
439     old_offset_lo = (uint32_t)context->old_offset;
440 #endif
441 
442     m = snapshot_module_create(s, snap_module_name, SNAP_MAJOR, SNAP_MINOR);
443 
444     if (m == NULL) {
445         return -1;
446     }
447 
448     if (0
449         || SMW_B(m, (uint8_t)context->stop) < 0
450         || SMW_B(m, (uint8_t)context->hour24) < 0
451         || SMW_B(m, context->address) < 0
452         || SMW_DW(m, latch_hi) < 0
453         || SMW_DW(m, latch_lo) < 0
454         || SMW_DW(m, offset_hi) < 0
455         || SMW_DW(m, offset_lo) < 0
456         || SMW_DW(m, old_offset_hi) < 0
457         || SMW_DW(m, old_offset_lo) < 0
458         || SMW_STR(m, context->device) < 0) {
459         snapshot_module_close(m);
460         return -1;
461     }
462     return snapshot_module_close(m);
463 }
464 
rtc58321a_read_snapshot(rtc_58321a_t * context,snapshot_t * s)465 int rtc58321a_read_snapshot(rtc_58321a_t *context, snapshot_t *s)
466 {
467     uint32_t latch_lo = 0;
468     uint32_t latch_hi = 0;
469     uint32_t offset_lo = 0;
470     uint32_t offset_hi = 0;
471     uint32_t old_offset_lo = 0;
472     uint32_t old_offset_hi = 0;
473     uint8_t vmajor, vminor;
474     snapshot_module_t *m;
475 
476     m = snapshot_module_open(s, snap_module_name, &vmajor, &vminor);
477 
478     if (m == NULL) {
479         return -1;
480     }
481 
482     /* Do not accept versions higher than current */
483     if (snapshot_version_is_bigger(vmajor, vminor, SNAP_MAJOR, SNAP_MINOR)) {
484         snapshot_set_error(SNAPSHOT_MODULE_HIGHER_VERSION);
485         goto fail;
486     }
487 
488     if (0
489         || SMR_B_INT(m, &context->stop) < 0
490         || SMR_B_INT(m, &context->hour24) < 0
491         || SMR_B(m, &context->address) < 0
492         || SMR_DW(m, &latch_hi) < 0
493         || SMR_DW(m, &latch_lo) < 0
494         || SMR_DW(m, &offset_hi) < 0
495         || SMR_DW(m, &offset_lo) < 0
496         || SMR_DW(m, &old_offset_hi) < 0
497         || SMR_DW(m, &old_offset_lo) < 0
498         || SMR_STR(m, &context->device) < 0) {
499         goto fail;
500     }
501 
502     snapshot_module_close(m);
503 
504 #if (SIZE_OF_TIME_T == 8)
505     context->latch = (time_t)(latch_hi) << 32;
506     context->latch |= latch_lo;
507     context->offset = (time_t)(offset_hi) << 32;
508     context->offset |= offset_lo;
509     context->old_offset = (time_t)(old_offset_hi) << 32;
510     context->old_offset |= old_offset_lo;
511 #else
512     context->latch = latch_lo;
513     context->offset = offset_lo;
514     context->old_offset = old_offset_lo;
515 #endif
516 
517     return 0;
518 
519 fail:
520     snapshot_module_close(m);
521     return -1;
522 }
523