|
|
1.1 root 1: //
2: // nono
1.1.1.3 root 3: // Copyright (C) 2020 nono project
4: // Licensed under nono-license.txt
1.1 root 5: //
6:
1.1.1.10 root 7: //
8: // RTC (RP5C15)
9: //
1.1 root 10:
11: // アラームは未実装
12:
1.1.1.10 root 13: #include "rp5c15.h"
1.1.1.11! root 14: #include "mfp.h"
1.1.1.10 root 15: #include "mpu.h"
1.1.1.11! root 16: #include "power.h"
1.1 root 17:
1.1.1.10 root 18: // コンストラクタ
1.1 root 19: RP5C15Device::RP5C15Device()
1.1.1.8 root 20: : inherited("RTC")
1.1 root 21: {
22: devaddr = 0xe8a000;
23: devlen = 0x2000;
1.1.1.8 root 24:
1.1.1.10 root 25: // X68k の RTC epoch は 1980年固定で変更の必要はないので、設定も見ない。
1.1.1.9 root 26: year_epoch = 1980;
1.1.1.10 root 27: // X68k は通常ローカルタイム (というか JST) で使用する前提
1.1.1.9 root 28: use_localtime = true;
29:
1.1.1.10 root 30: monitor.func = ToMonitorCallback(&RP5C15Device::MonitorUpdate);
1.1.1.9 root 31: monitor.SetSize(41, 22);
1.1.1.8 root 32: monitor.Regist(ID_MONITOR_RTC);
1.1 root 33: }
34:
1.1.1.10 root 35: // デストラクタ
1.1 root 36: RP5C15Device::~RP5C15Device()
37: {
38: }
39:
1.1.1.7 root 40: // 初期化
1.1 root 41: bool
1.1.1.7 root 42: RP5C15Device::Init()
1.1 root 43: {
1.1.1.9 root 44: // デフォルトで 24 時間計にしておく
45: reg.sel24 = 0x01;
46: // デフォルトで 1HZ_ON, 16HZ_ON をネゲート(1)しておく
47: reg.reset = RP5C15::RESET_1Hz | RP5C15::RESET_16Hz;
1.1 root 48:
1.1.1.9 root 49: return inherited::Init();
1.1 root 50: }
51:
1.1.1.2 root 52: uint64
1.1.1.6 root 53: RP5C15Device::Read(uint32 offset)
1.1.1.2 root 54: {
55: uint8 data;
56: int n;
57:
1.1.1.5 root 58: gMPU->AddCycle(29); // InsideOut p.135
59:
1.1.1.3 root 60: // バンクを展開する
1.1.1.9 root 61: int bank = (reg.mode & RP5C15::MODE_BANK);
1.1.1.6 root 62: n = bank * 0x10 + offset;
1.1 root 63:
64: // XXX 14, 15 は write only らしいが何が読めるか
65:
66: switch (n) {
67: case 0x1d: // MODE (Bank0 と同じ)
68: case 0x1e: // TEST (Bank0 と同じ)
69: case 0x1f: // RESET (Bank0 と同じ)
70: n -= 0x10;
71: FALLTHROUGH;
72:
73: case 0x00 ... 0x1c:
74: data = reg.r[n];
75: break;
76: default:
1.1.1.3 root 77: __unreachable();
1.1 root 78: }
79:
80: // 下位4ビット(RP5C15) の中の未実装ビットは %0 だが
81: // 上位4ビットは %1 になる。
82: data |= 0xf0;
1.1.1.6 root 83: putlog(2, "RTC Bank%d $%x -> $%02x", bank, offset, data);
1.1 root 84:
85: return data;
86: }
87:
88: uint64
1.1.1.6 root 89: RP5C15Device::Write(uint32 offset, uint32 data)
1.1 root 90: {
91: int n;
92:
1.1.1.5 root 93: gMPU->AddCycle(38); // InsideOut p.135
94:
1.1.1.3 root 95: // バンクを展開する
1.1.1.9 root 96: int bank = (reg.mode & RP5C15::MODE_BANK);
1.1.1.6 root 97: n = bank * 0x10 + offset;
1.1 root 98:
1.1.1.6 root 99: putlog(2, "RTC Bank%d $%x <- $%x", bank, offset, (data & 0x0f));
1.1 root 100:
101: switch (n) {
102: case RP5C15::SEC1:
103: case RP5C15::SEC10:
104: case RP5C15::MIN1:
105: case RP5C15::MIN10:
1.1.1.9 root 106: case RP5C15::HOUR1:
107: case RP5C15::HOUR10:
108: case RP5C15::WDAY: // 曜日カウンタは日付などとは一切連動していない
1.1 root 109: case RP5C15::DAY1:
110: case RP5C15::DAY10:
111: case RP5C15::MON1:
112: case RP5C15::MON10:
113: case RP5C15::YEAR1:
114: case RP5C15::YEAR10:
1.1.1.9 root 115: case RP5C15::SEL24:
116: case RP5C15::LEAP:
117: reg.r[n] = data & reg.mask[n];
1.1 root 118: break;
119:
120: case RP5C15::CLKSEL:
1.1.1.9 root 121: reg.clksel = data & reg.mask[n];
122: switch (reg.clksel) {
123: case 0: // ハイインピーダンス
124: case 1: // 16384Hz
125: case 2: // 1024Hz
126: case 3: // 128Hz
127: // 128Hz 点滅とか点灯みたいなもんだろ(適当)
128: clkout = true;
129: break;
130:
131: // (?) たぶん内部ではずっとカウンタ部分は動いていて
132: // 出力位置を切り替えているだけのはずなので、
133: // CLKSEL を変えた時の初期状態は内部カウンタの状態に依存するはず。
134: case 4: // 16Hz
135: clkout = ((cnt & 1) == 0);
136: break;
137: case 5: // 1Hz
138: clkout = ((cnt & 16) == 0);
139: break;
140: case 6: // 1/60Hz
141: clkout = (cnt_60sec < 30);
142: break;
143: case 7:
144: clkout = false;
145: break;
146: }
147: break;
148:
149: // アラーム関係は未実装
150: case RP5C15::ALARM_MIN1 ... RP5C15::ALARM_DAY10:
151: case RP5C15::notused1:
152: case RP5C15::notused2:
1.1 root 153: // マスクして書き込む
154: reg.r[n] = data & reg.mask[n];
155: break;
156:
157: case RP5C15::ADJUST:
158: {
159: // %0 なら何もしない?
160: if ((data & 1) == 0) {
161: break;
162: }
163: // %1 ならアジャスト。
164:
165: // 現在の秒が 0..29秒なら 0秒に切り下げ。
166: // 30..59秒なら +1分0秒に繰り上げ。
167: // いずれにしても 1秒と10秒の位は 0 にする。
1.1.1.9 root 168: // XXX 秒以下のカウンタもクリアするのでは?
169: if (GetSec() >= 30) {
1.1 root 170: // 1分繰り上げ
1.1.1.9 root 171: CountUpMin();
1.1 root 172: }
1.1.1.9 root 173: // マニュアルを読む限り秒以下をクリア(?)
174: reg.sec1 = 0;
175: reg.sec10 = 0;
176: cnt = 0;
177: clock_hold = false;
1.1 root 178: break;
179: }
180:
181: case RP5C15::MODE:
182: case RP5C15::MODE_BANK1:
183: reg.mode = data & reg.mask[RP5C15::MODE];
1.1.1.9 root 184:
185: if ((reg.mode & RP5C15::MODE_TIMER_EN)) {
186: if (clock_hold) {
187: // ホールドされていた秒カウントアップを実行
188: CountUpSec();
189: clock_hold = false;
190: }
191: }
1.1 root 192: break;
193:
194: case RP5C15::TEST:
195: case RP5C15::TEST_BANK1:
196: data &= reg.mask[RP5C15::TEST];
197: if (data == 0) {
198: // 通常 0 を書き込むこと、となっているので 0 は許可
199: } else {
1.1.1.11! root 200: putlog(0, "TEST <- $%1x (NOT IMPLEMENTED)", data);
1.1 root 201: }
202: reg.test = data;
203: break;
204:
205: case RP5C15::RESET:
206: case RP5C15::RESET_BANK1:
207: reg.reset = data & reg.mask[RP5C15::RESET];
208: if ((reg.reset & RP5C15::RESET_TIMER)) {
209: // %1 を書き込むと、秒未満の内部カウンタをリセット
1.1.1.9 root 210: // 繰り上がり分の保持も消す。
211: cnt = 0;
212: clock_hold = false;
213: }
214: if ((reg.reset & RP5C15::RESET_16Hz)) {
215: alarm_16Hz = false;
216: }
217: if ((reg.reset & RP5C15::RESET_1Hz)) {
218: alarm_1Hz = false;
1.1 root 219: }
1.1.1.11! root 220: ChangeAlarmOut();
1.1 root 221: break;
222:
223: default:
224: break;
225: }
226: return 0;
227: }
228:
229: uint64
1.1.1.6 root 230: RP5C15Device::Peek(uint32 offset)
1.1 root 231: {
232: int n;
233:
1.1.1.3 root 234: // バンクを展開する
1.1.1.9 root 235: int bank = (reg.mode & RP5C15::MODE_BANK);
1.1.1.6 root 236: n = bank * 0x10 + offset;
1.1 root 237:
238: switch (n) {
239: case RP5C15::MODE_BANK1:
240: case RP5C15::TEST_BANK1:
241: case RP5C15::RESET_BANK1:
242: // Bank0 と同じものが見える
243: n -= 0x10;
244: FALLTHROUGH;
245:
246: case 0x00 ... 0x1c:
247: return reg.r[n] | 0xf0;
248:
249: default:
1.1.1.3 root 250: __unreachable();
1.1 root 251: }
252: }
253:
1.1.1.4 root 254: void
1.1.1.8 root 255: RP5C15Device::MonitorUpdate(Monitor *, TextScreen& screen)
1.1 root 256: {
1.1.1.9 root 257: int x = 18;
258: int y = 0;
1.1 root 259:
1.1.1.8 root 260: screen.Clear();
1.1 root 261:
1.1.1.9 root 262: int year = reg.year10 * 10 + reg.year1 + year_epoch;
263: int hour10;
264: const char *ampm;
1.1 root 265: if (reg.sel24) {
1.1.1.9 root 266: ampm = "";
267: hour10 = reg.hour10;
1.1 root 268: } else {
1.1.1.9 root 269: ampm = (reg.hour10 & 2) == 0 ? " AM": " PM";
270: hour10 = reg.hour10 & 1;
1.1 root 271: }
1.1.1.9 root 272: screen.Print(0, y++, "%04d[%x%x]/%x%x/%x%x(%s) %x%x:%x%x:%x%x.%02x%s",
273: year, reg.year10, reg.year1,
274: reg.mon10, reg.mon1, reg.day10, reg.day1, wdays[reg.wday],
275: hour10, reg.hour1, reg.min10, reg.min1, reg.sec10, reg.sec1,
276: ((uint32)cnt & 0x1f), ampm);
277:
278: screen.Print(0, y, "TimeZone: %s", (use_localtime) ? "Local" : "UTC");
279: screen.Print(x, y, "Epoch: %4d", year_epoch);
280:
281: // バンク0
282: y = 3;
283: screen.Print(0, y++, "Bank0");
284: static const char * const b0names[] = {
285: " 1sec",
286: "10sec",
287: " 1min",
288: "10min",
289: " 1hour",
290: "10hour",
291: "wday",
292: " 1day",
293: "10day",
294: " 1mon",
295: "10mon",
296: " 1year",
297: "10year",
298: };
299: for (int i = 0; i < countof(b0names); i++) {
300: screen.Print(0, y, "$%x(%s)", i, b0names[i]);
301: screen.Print(10, y, ": %x", reg.r[i]);
302: y++;
303: }
304:
305: // バンク1
306: y = 3;
307: screen.Print(x, y++, "Bank1");
308: static const char * const b1names[] = {
309: "CLKSEL",
310: "ADJ",
311: "AL 1m",
312: "AL10m",
313: "AL 1h",
314: "AL10h",
315: "ALwday",
316: "AL 1d",
317: "AL10d",
318: "",
319: "12/24H",
320: "LEAP",
321: "",
322: };
323: for (int i = 0; i < countof(b1names); i++) {
324: if (b1names[i][0] != '\0') {
325: screen.Print(x, y, "$%x(%s)", i, b1names[i]);
326: screen.Print(x + 10, y, ": %x", reg.r[16 + i]);
327: } else {
328: // $9 と $c は欠番
329: screen.Print(x, y, "$%x - ", i);
330: }
331: y++;
332: }
333:
334: // CLKSEL
335: static const char * const clksel_s[] = {
336: "HiZ",
337: "16384Hz",
338: "1024Hz",
339: "128Hz",
340: "16Hz",
341: "1Hz",
342: "1/60Hz",
343: "L",
344: };
345: screen.Print(x + 14, 4, "(%s)", clksel_s[reg.clksel]);
346:
347: // 12/24H
348: screen.Print(x + 14, 14, "(%dH)", reg.sel24 ? 24 : 12);
349:
350: // MODE
351: y = 17;
352: screen.Print(0, y, "$d(MODE): %x", reg.mode);
353: screen.Puts(13, y, TA::OnOff(reg.mode & RP5C15::MODE_TIMER_EN), "TIMER_EN");
354: screen.Puts(22, y, TA::OnOff(reg.mode & RP5C15::MODE_ALARM_EN), "ALARM_EN");
355: screen.Print(31, y, "BANK=%d", reg.mode & RP5C15::MODE_BANK);
356:
357: // TEST
1.1 root 358: y++;
1.1.1.9 root 359: screen.Print(0, y, "$e(TEST): %x", reg.test);
1.1 root 360:
1.1.1.9 root 361: // RESET
1.1 root 362: y++;
1.1.1.9 root 363: screen.Print(0, y, "$f(RESET): %x", reg.reset);
364: screen.Print(13, y, "1Hz=%s",
365: (reg.reset & RP5C15::RESET_1Hz) ? "OFF" : "ON");
366: screen.Print(21, y, "16Hz=%s",
367: (reg.reset & RP5C15::RESET_16Hz) ? "OFF" : "ON");
368: screen.Puts(30, y, TA::OnOff(reg.reset & RP5C15::RESET_TIMER), "TIMER");
369: screen.Puts(36, y, TA::OnOff(reg.reset & RP5C15::RESET_ALARM), "ALARM");
1.1 root 370:
1.1.1.9 root 371: // PIN
1.1 root 372: y++;
1.1.1.9 root 373: y++;
374: // ハードウェアの ~ALARM ピンは負論理なのでアクティブ L だが、
375: // ここでは分かりやすさ優先で正論理としている。
376: screen.Print(0, y, "Pin: CLKOUT=%c ALARM=%c",
377: clkout ? 'H' : 'L',
1.1.1.11! root 378: alarm_out ? 'H' : 'L');
1.1.1.9 root 379: }
1.1 root 380:
1.1.1.9 root 381: // 秒を取得
382: uint
383: RP5C15Device::GetSec() const
384: {
385: return reg.sec10 * 10 + reg.sec1;
1.1 root 386: }
387:
1.1.1.9 root 388: // 秒を設定
389: void
390: RP5C15Device::SetSec(uint v)
391: {
392: reg.sec1 = v % 10;
393: reg.sec10 = v / 10;
394: }
1.1 root 395:
1.1.1.9 root 396: // 分を取得
397: uint
398: RP5C15Device::GetMin() const
399: {
400: return reg.min10 * 10 + reg.min1;
401: }
402:
403: // 分を設定
404: void
405: RP5C15Device::SetMin(uint v)
406: {
407: reg.min1 = v % 10;
408: reg.min10 = v / 10;
409: }
410:
411: // 時を取得
412: uint
413: RP5C15Device::GetHour() const
414: {
415: if (reg.sel24) {
416: return reg.hour10 * 10 + reg.hour1;
417: } else {
418: int h10;
419: h10 = (reg.hour10 & 0x1) * 10 + ((reg.hour10 & 0x2) >> 1) * 12;
420: return h10 + reg.hour1;
421: }
422: }
1.1 root 423:
1.1.1.9 root 424: // 時を設定
425: void
426: RP5C15Device::SetHour(uint v)
427: {
428: if (reg.sel24) {
429: reg.hour1 = v % 10;
430: reg.hour10 = v / 10;
431: } else {
432: int pm = (v < 12) ? 0x0 : 0x2;
433: v %= 12;
434: reg.hour1 = v % 10;
435: reg.hour10 = v / 10 + pm;
436: }
437: }
438:
439: // 曜日を取得
440: uint
441: RP5C15Device::GetWday() const
442: {
443: return reg.wday;
444: }
445:
446: // 曜日を設定
447: void
448: RP5C15Device::SetWday(uint v)
449: {
450: reg.wday = v;
451: }
452:
453: // 日を取得
454: uint
455: RP5C15Device::GetMday() const
456: {
457: return reg.day10 * 10 + reg.day1;
458: }
459:
460: // 日を設定
461: void
462: RP5C15Device::SetMday(uint v)
463: {
464: reg.day1 = v % 10;
465: reg.day10 = v / 10;
466: }
467:
468: // 月を取得
469: uint
470: RP5C15Device::GetMon() const
471: {
472: return reg.mon10 * 10 + reg.mon1;
473: }
474:
475: // 月を設定
476: void
477: RP5C15Device::SetMon(uint v)
478: {
479: reg.mon1 = v % 10;
480: reg.mon10 = v / 10;
481: }
482:
483: // 年を取得
484: uint
485: RP5C15Device::GetYear() const
486: {
487: return reg.year10 * 10 + reg.year1 + year_epoch;
488: }
489:
490: // 年を設定
491: void
492: RP5C15Device::SetYear(uint v)
493: {
494: v -= year_epoch;
495: reg.year1 = v % 10;
496: reg.year10 = v / 10;
497: }
498:
499: uint
500: RP5C15Device::GetLeap() const
501: {
502: return reg.leap;
503: }
504:
505: void
506: RP5C15Device::SetLeap(uint v)
507: {
508: reg.leap = v & 3;
509: }
510:
1.1.1.11! root 511: // ALARM OUT 信号
! 512: void
! 513: RP5C15Device::ChangeAlarmOut()
! 514: {
! 515: bool old = alarm_out;
! 516:
! 517: // アラーム出力は内部のアラーム用 16Hz, 1Hz と
! 518: // アラーム時刻一致の alarm_on との OR 論理となっている。
! 519: alarm_out = alarm_16Hz | alarm_1Hz | alarm_on;
! 520:
! 521: if (old != alarm_out) {
! 522: gPower->SetAlarmOut(alarm_out);
! 523: gMFP->SetAlarmOut(alarm_out);
! 524: }
! 525: }
! 526:
1.1.1.9 root 527: // 32Hz 仮想 RTC からのコール
528: void
529: RP5C15Device::Tick32Hz()
530: {
531: if (reg.clksel == 4) {
1.1.1.10 root 532: // CLKOUT 16Hz は duty50% なのでオン/オフ駆動は 32Hz になる
1.1.1.9 root 533: clkout = ((cnt & 1) == 0);
534: }
535:
536: // ALARM 端子の 16Hz パルスも CLKOUT に同期するとは書いてないけど、
537: // 面倒なのでとりあえず同じところで処理してしまう。
538: // データシートの書き方的には同じ波形になると思われる。
539:
540: // アラーム端子 16Hz パルス出力は %0 ならオン
541: if ((reg.reset & RP5C15::RESET_16Hz) == 0) {
542: alarm_16Hz = !alarm_16Hz;
1.1.1.11! root 543: ChangeAlarmOut();
1.1.1.9 root 544: }
545: }
546:
547: // 2Hz 仮想 RTC からのコール
548: // 分周段 0/32, 16/32 秒でコールされる。
549: void
550: RP5C15Device::Tick2Hz()
551: {
552: if (reg.clksel == 5) {
1.1.1.10 root 553: // CLKOUT 1Hz は duty50% なのでオン/オフ駆動は 2Hz になる。
1.1 root 554: // CLKOUT 端子は秒の繰り上がりに同期している。
555: // TIMER_EN (秒への繰り上がり) が無効でも反転自体は起きる気が
556: // するので、ここでは UPDATE_SEC ではなく cnt で判定する。
1.1.1.10 root 557: // CLKOUT は 1秒の繰り上がりが起きる時に 'H' になる。
1.1 root 558:
1.1.1.9 root 559: clkout = ((cnt & 15) == 0);
560: }
1.1 root 561:
1.1.1.9 root 562: // ALARM 端子の 1Hz パルスも CLKOUT に同期するとは書いてないけど、
563: // 面倒なのでとりあえず同じところで処理してしまう。
564: // データシートの書き方的には同じ波形になると思われる。
1.1 root 565:
1.1.1.9 root 566: // アラーム端子 16Hz パルス出力は %0 ならオン
567: if ((reg.reset & RP5C15::RESET_1Hz) == 0) {
568: alarm_1Hz = !alarm_1Hz;
1.1.1.11! root 569: ChangeAlarmOut();
1.1 root 570: }
571: }
572:
1.1.1.9 root 573: // 1Hz 仮想 RTC からのコール
574: // 分周段 0/32 秒でコールされる。
1.1 root 575: void
1.1.1.9 root 576: RP5C15Device::Tick1Hz()
1.1 root 577: {
1.1.1.9 root 578: if ((reg.mode & RP5C15::MODE_TIMER_EN)) {
579: // 秒カウンタ更新
580: CountUpSec();
581: } else {
582: // 秒カウンタは更新させない。内部のクロックホールドをアサート。
583: clock_hold = true;
584: }
585:
586: // CLKOUT 1/60 Hz については
587: // 「秒のカウントアップのタイミングでアップエッジ」
588: // としか書かれておらず、00 or 30 秒で動作するとは書かれていない。
589: // データシートの図からはカレンダとは独立しているように読める。
590:
1.1.1.10 root 591: // 1/60Hz CLKOUT のための 60sec カウンタ。
1.1.1.9 root 592: // このカウンタは clksel によらず常にカウントする。
593: cnt_60sec++;
594: if (cnt_60sec == 60) {
595: cnt_60sec = 0;
1.1 root 596: }
597:
1.1.1.9 root 598: if (reg.clksel == 6) {
1.1.1.10 root 599: // CLKOUT 1/60Hz は duty50% なのでオン/オフ駆動は 30sec になる
1.1.1.9 root 600: clkout = (cnt_60sec < 30);
1.1 root 601: }
602: }
603:
604: /*static*/ const uint8
605: RP5C15::mask[] = {
606: 0x0f, 0x07, 0x0f, 0x07, // 1秒 10秒 1分 10分
607: 0x0f, 0x03, 0x07, 0x0f, // 1時 10時 曜日 1日
608: 0x01, 0x0f, 0x01, 0x0f, // 10日 1月 10月 1年
609: 0x0f, 0x0d, 0x0f, 0x0f, // 10年 MODE TEST RESET
610:
611: 0x07, 0x01, 0x0f, 0x07, // CLKO Adjust AL1分 AL10分
612: 0x0f, 0x03, 0x07, 0x0f, // AL1時 AL10時 AL曜日 AL1日
613: 0x03, 0x00, 0x01, 0x03, // AL10日 --- 12/24 閏年
614: 0x00 // ---
615: };
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.