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