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