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