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