|
|
1.1 root 1: //
2: // nono
1.1.1.4 root 3: // Copyright (C) 2020 nono project
4: // Licensed under nono-license.txt
1.1 root 5: //
6:
7: //
1.1.1.12 root 8: // NVRAM/RTC (MK48T02)
1.1 root 9: //
1.1.1.12 root 10:
1.1 root 11: // SRAM 部分はビッグエンディアンのままメモリに置かれるので、
12: // 必要ならアクセス時に都度変換すること。
13: //
14: // 仕様は 0x45000000 から 2KB だが、
1.1.1.13 root 15: // LUNA-I では 0x44000000 から 0x47ffffff まで 2KB 単位でミラーが見える。
16: // LUNA-88K では 0x44000000 から 0x45ffffff までが
17: // (こちらはロングワード配置なので) 8KB 単位のミラーに見える。
1.1 root 18:
1.1.1.12 root 19: #include "mk48t02.h"
20: #include "bitops.h"
21: #include "config.h"
22: #include "mainapp.h"
23: #include "mpu.h"
24: #include <fcntl.h>
25: #include <unistd.h>
26: #include <sys/stat.h>
27: #include <array>
28:
29: // コンストラクタ
1.1 root 30: MK48T02Device::MK48T02Device()
1.1.1.13 root 31: : inherited()
1.1 root 32: {
1.1.1.13 root 33: SetName("MK48T02");
1.1.1.9 root 34:
1.1.1.13 root 35: AddAlias("MK48T02");
36: AddAlias("NVRAM");
1.1.1.9 root 37:
1.1.1.12 root 38: // 今の所 UTC として使うケースだけのようだ。
1.1.1.10 root 39: use_localtime = false;
40:
1.1.1.12 root 41: monitor.func = ToMonitorCallback(&MK48T02Device::MonitorUpdate);
1.1.1.10 root 42: monitor.SetSize(40, 13);
1.1.1.9 root 43: monitor.Regist(ID_MONITOR_MK48T02);
1.1 root 44: }
45:
1.1.1.12 root 46: // デストラクタ
1.1 root 47: MK48T02Device::~MK48T02Device()
48: {
49: }
50:
1.1.1.13 root 51: // 初期化
1.1 root 52: bool
53: MK48T02Device::Init()
54: {
1.1.1.13 root 55: if (inherited::Init() == false) {
56: return false;
57: }
58:
1.1.1.12 root 59: struct stat st;
60:
61: // NetBSD/luna68k, OpenBSD/luna88k ともに epoch を 1970年としている
62: // (2021/07 現在) 一方、4.4BSD (luna68k 版?) は 1900年としている。
63: // MK48T02 本来定義は 0 というか 1900 というか。DecodeYear() も参照。
64: const ConfigItem& item = gConfig->Find("rtc-epoch-year");
65: int year_epoch_val;
66: if (item.TryInt(&year_epoch_val) == false) {
67: item.Err();
1.1.1.10 root 68: return false;
69: }
1.1.1.12 root 70: // 雑にチェック
71: if (year_epoch_val < 1900) {
1.1.1.13 root 72: item.Err("Must be >= 1900");
1.1.1.12 root 73: return false;
74: }
75: year_epoch = (uint)year_epoch_val;
1.1.1.10 root 76:
77: // MK48T02 のデータシートには年には西暦年の下位2桁を設定するように
1.1.1.12 root 78: // 指示されており、4.4BSD (luna68k) はこの通りになっている。
1.1.1.10 root 79: // しかし CMU Mach luna88k, NetBSD/luna68k, OpenBSD/luna88k はいずれも
80: // 西暦年から 1970 引いた値を設定するようになっている (2021/07 現在)。
81: // このため、うるう年が2年ずれた状態になる。
82: //
83: // NetBSD で MK48T02 を採用している luna68k 以外の port は
84: // ソースコード上の変数 year0 もしくは YEAR0 に
85: // newsmips, news68k: 1900 (データシート規定通り)
86: // sun3, sparc, mvme68k: 1968 (1968 はうるう年なので問題ない)
87: // を採用しており、バグっていない。
88: // この year0 は UNIX EPOCH とは直接関係がない RTC ハードウェアに
89: // 依存する値なのだが、LUNA では混同したのだろうか?
90: //
91: // たぶんこのバグはもう修正されることはないだろうから、こちらで補正する
92: // スイッチを用意した。
1.1.1.13 root 93: if (gMainApp.Has(VMCap::LUNA)) {
94: if (gConfig->Find("luna-adjust-misused-epoch").AsInt()) {
95: // エポック年のうるう年カウンタ相当を補正項として持つ
96: adjust_leap = year_epoch % 4;
97: } else {
98: adjust_leap = 0;
99: }
100: }
1.1.1.10 root 101:
1.1.1.13 root 102: // MSX-DOS モードなら NVRAM 不要なのでここでダミーのメモリだけ用意。
103: if (gMainApp.msxdos_mode) {
104: // XXX アプリケーション寿命まで解放してないけど、まあいいか
105: mem = (uint8 *)malloc(16 * 1024);
106: return true;
1.1.1.10 root 107: }
108:
1.1.1.12 root 109: // ファイルサイズを更新。
110: // ver 0.2.3 までの NVRAM.DAT は 2040 バイトだったが、ver 0.2.4 以降は
111: // 2048 バイトになるため、ここでファイルがあって 2040 バイトなら 2048
112: // バイトに拡張する。失敗したらここでは何もしない。
1.1.1.3 root 113: filename = gMainApp.GetVMDir() + "NVRAM.DAT";
1.1.1.12 root 114: std::string dispname = string_format("NVRAM \"%s\"", filename.c_str());
115: if (stat(filename.c_str(), &st) == 0 && st.st_size == 2040) {
116: int fd;
117: fd = open(filename.c_str(), O_WRONLY | O_APPEND);
118: if (fd >= 0) {
119: std::array<uint8, 8> zerobuf {};
120: write(fd, &zerobuf[0], zerobuf.size());
121: close(fd);
122: stat(filename.c_str(), &st);
123: warnx("%s extended to %jd bytes",
124: dispname.c_str(), (intmax_t)st.st_size);
125: }
126: }
127:
128: // ファイルのロード
1.1.1.4 root 129: file.SetFilename(filename);
1.1.1.12 root 130: file.SetDispname(dispname);
131: mem = file.OpenCreate(2048);
1.1.1.4 root 132: if (mem == NULL) {
1.1 root 133: return false;
134: }
135:
1.1.1.13 root 136: return true;
1.1.1.12 root 137: }
138:
139: void
140: MK48T02Device::StartTime()
141: {
142: inherited::StartTime();
143:
144: // 内部カレンダをメモリに反映
145: Store(reg.in);
1.1 root 146: }
147:
148: // バイナリ表現を BCD 表現に変換する
149: /*static*/ inline uint8
150: MK48T02Device::num2BCD(uint8 num)
151: {
152: return ((num / 10) << 4) + (num % 10);
153: }
154:
155: // BCD 表現をバイナリ表現に変換する
156: /*static*/ inline uint8
157: MK48T02Device::BCD2num(uint8 bcd)
158: {
159: return (bcd >> 4) * 10 + (bcd & 0x0f);
160: }
161:
1.1.1.14 root 162: busdata
1.1.1.15! root 163: MK48T02Device::ReadPort(uint32 offset)
1.1 root 164: {
1.1.1.14 root 165: busdata data;
1.1 root 166:
1.1.1.12 root 167: data = mem[offset];
1.1.1.14 root 168: putlog(3, "$%08x -> $%02x", mpu->GetPaddr(), data.Data());
1.1 root 169:
1.1.1.14 root 170: // XXX wait?
1.1.1.15! root 171: data |= BusData::Size1;
1.1 root 172: return data;
173: }
174:
1.1.1.14 root 175: busdata
1.1.1.15! root 176: MK48T02Device::WritePort(uint32 offset, uint32 data)
1.1 root 177: {
1.1.1.13 root 178: putlog(3, "$%08x <- $%02x", mpu->GetPaddr(), data);
1.1.1.10 root 179:
1.1.1.12 root 180: switch (offset - 2040) {
181: case 0: // コントロール
182: WriteCtrl(data);
183: break;
184: case 1: // 秒 (b7 は STOP BIT)
185: reg.stop = data & MK48T02Clock::STOP;
186: break;
187: case 2: // 分
188: break;
189: case 3: // 時 (b7 は KICK START)
190: reg.kickstart = data & MK48T02Clock::KICKSTART;
191: break;
192: case 4: // 曜日 (b6 は Frequency Test (未実装))
193: // データシートでは b7, b5, b4 は '0' とあるが実際には '1' が
194: // 書き込める。書き込んだ '1' が内部クロックの更新でどうなるか
195: // 未確認のため、とりあえず放置。
196: reg.freqtest = data & MK48T02Clock::FREQTEST;
197: break;
198: case 5: // 日
199: // データシートでは b7, b6 は '0' とあるが実際には b7 には '1' が
200: // 書き込める (b6 には書き込めない)。b7 に書き込んだ '1' が内部
201: // クロックの更新でどうなるか未確認のため、とりあえずは放置。
202: break;
203: case 6: // 月
204: break;
205: case 7: // 年
206: break;
207: default:
208: break;
1.1 root 209: }
1.1.1.12 root 210:
211: mem[offset] = data;
212:
1.1.1.14 root 213: // XXX wait?
1.1.1.15! root 214: busdata r = BusData::Size1;
! 215: return r;
1.1 root 216: }
217:
1.1.1.14 root 218: busdata
1.1.1.15! root 219: MK48T02Device::PeekPort(uint32 offset)
1.1 root 220: {
1.1.1.12 root 221: return mem[offset];
1.1 root 222: }
223:
1.1.1.14 root 224: bool
1.1.1.15! root 225: MK48T02Device::PokePort(uint32 offset, uint32 data)
1.1.1.13 root 226: {
227: // 時計部分にはコントロールレジスタもあるので書き込み不可。
228: if (offset >= 2040) {
1.1.1.14 root 229: return false;
1.1.1.13 root 230: } else {
231: if ((int32)data >= 0) {
232: mem[offset] = data;
233: }
1.1.1.14 root 234: return true;
1.1.1.13 root 235: }
236: }
237:
1.1.1.5 root 238: void
1.1.1.9 root 239: MK48T02Device::MonitorUpdate(Monitor *, TextScreen& screen)
1.1 root 240: {
1.1.1.10 root 241: uint32 addr;
242: uint32 v;
243: int y = 0;
244:
1.1.1.9 root 245: screen.Clear();
1.1 root 246:
1.1.1.10 root 247: // BCD なので %x で表示する
248: screen.Print(0, y++,
1.1.1.15! root 249: "Internal: %04u[%02x]/%02x/%02x(%s) %02x:%02x:%02x.%1x",
1.1.1.10 root 250: DecodeYear(reg.in.year), reg.in.year,
251: reg.in.mon, reg.in.mday, wdays[reg.in.wday],
252: reg.in.hour, reg.in.min, reg.in.sec, (uint32)((cnt >> 1) & 0xf));
1.1.1.12 root 253:
254: auto ex = Load();
1.1.1.10 root 255: screen.Print(0, y++,
1.1.1.15! root 256: "Register: %04u[%02x]/%02x/%02x(%s) %02x:%02x:%02x",
1.1.1.12 root 257: DecodeYear(ex.year), ex.year,
258: ex.mon, ex.mday, wdays[ex.wday],
259: ex.hour, ex.min, ex.sec);
260:
1.1.1.10 root 261: screen.Print(0, y++, "TimeZone: %s",
262: use_localtime ? "Local" : "UTC");
1.1.1.15! root 263: screen.Print(0, y++, "Epoch : %4u (Adjust Leap: %u)",
1.1.1.10 root 264: year_epoch, adjust_leap);
265: y++;
266:
267: addr = 0x800;
1.1.1.15! root 268: v = PeekPort(--addr);
1.1.1.10 root 269: screen.Print(0, y++, "$%3x: $%02x (year=%02x)", addr, v, v);
1.1.1.15! root 270: v = PeekPort(--addr);
1.1.1.10 root 271: screen.Print(0, y++, "$%3x: $%02x (mon =%02x)", addr, v, v);
1.1.1.15! root 272: v = PeekPort(--addr);
1.1.1.10 root 273: screen.Print(0, y++, "$%3x: $%02x (date=%02x)", addr, v, v);
1.1.1.15! root 274: v = PeekPort(--addr);
! 275: screen.Print(0, y++, "$%3x: $%02x (wday=%02x FT=%u)",
1.1.1.10 root 276: addr, v, (v & 0x07), (v & MK48T02Clock::FREQTEST) ? 1 : 0);
1.1.1.15! root 277: v = PeekPort(--addr);
! 278: screen.Print(0, y++, "$%3x: $%02x (hour=%02x KS=%u)",
1.1.1.10 root 279: addr, v, (v & 0x3f), (v & MK48T02Clock::KICKSTART) ? 1 : 0);
1.1.1.15! root 280: v = PeekPort(--addr);
1.1.1.10 root 281: screen.Print(0, y++, "$%3x: $%02x (min =%02x)", addr, v, (v & 0x7f));
1.1.1.15! root 282: v = PeekPort(--addr);
! 283: screen.Print(0, y++, "$%3x: $%02x (sec =%02x ST=%u)",
1.1.1.10 root 284: addr, v, (v & 0x7f), (v & MK48T02Clock::STOP) ? 1 : 0);
1.1.1.15! root 285: v = PeekPort(--addr);
! 286: screen.Print(0, y++, "$%3x: $%02x (W=%u R=%u)",
1.1.1.10 root 287: addr, v,
288: (v & MK48T02Clock::WRITE) ? 1 : 0,
289: (v & MK48T02Clock::READ) ? 1 : 0);
1.1 root 290: }
291:
292: // コントロールレジスタへの書き込み
293: void
294: MK48T02Device::WriteCtrl(uint32 data)
295: {
1.1.1.12 root 296: uint8 old = mem[0x7f8];
1.1.1.10 root 297:
1.1.1.12 root 298: bool write_fall = bit_falling(old, data, MK48T02Clock::WRITE);
1.1 root 299:
1.1.1.10 root 300: // ホールドしているかどうかは状態なのでエッジではない
1.1.1.12 root 301: hold = (data & (MK48T02Clock::READ | MK48T02Clock::WRITE));
1.1.1.10 root 302:
303: if (write_fall) {
1.1.1.12 root 304: // WRITE ビットの立ち下がりエッジでメモリから内部カウンタへロード
305: reg.in = Load();
306:
1.1.1.10 root 307: // 分周段は規定されていないが意味的にクリアするほうが良いだろう
308: cnt = 0;
1.1 root 309: }
310: }
311:
1.1.1.12 root 312: // NVRAM からカレンダをロード
313: MK48T02Clock::calendar
314: MK48T02Device::Load() const
315: {
316: MK48T02Clock::calendar cal;
317:
318: cal.sec = mem[0x7f9] & 0x7f;
319: cal.min = mem[0x7fa] & 0x7f;
320: cal.hour = mem[0x7fb] & 0x3f;
321: cal.wday = mem[0x7fc] & 0x07;
322: cal.mday = mem[0x7fd] & 0x3f;
323: cal.mon = mem[0x7fe] & 0x1f;
324: cal.year = mem[0x7ff];
325:
326: return cal;
327: }
328:
329: // カレンダを NVRAM にストア
330: void
331: MK48T02Device::Store(const MK48T02Clock::calendar& cal)
332: {
333: mem[0x7f9] = (mem[0x7f9] & ~0x7f) | cal.sec;
334: mem[0x7fa] = (mem[0x7fa] & ~0x7f) | cal.min;
335: mem[0x7fb] = (mem[0x7fb] & ~0x3f) | cal.hour;
336: mem[0x7fc] = (mem[0x7fc] & ~0x07) | cal.wday;
337: mem[0x7fd] = (mem[0x7fd] & ~0x3f) | cal.mday;
338: mem[0x7fe] = (mem[0x7fe] & ~0x1f) | cal.mon;
339: mem[0x7ff] = cal.year;
340: }
341:
1.1.1.10 root 342: uint
343: MK48T02Device::GetSec() const
344: {
345: return BCD2num(reg.in.sec);
346: }
347:
348: void
349: MK48T02Device::SetSec(uint v)
350: {
351: reg.in.sec = num2BCD(v);
352: }
353:
354: uint
355: MK48T02Device::GetMin() const
356: {
357: return BCD2num(reg.in.min);
358: }
359:
360: void
361: MK48T02Device::SetMin(uint v)
362: {
363: reg.in.min = num2BCD(v);
364: }
365:
366: uint
367: MK48T02Device::GetHour() const
368: {
369: return BCD2num(reg.in.hour);
370: }
371:
372: void
373: MK48T02Device::SetHour(uint v)
374: {
375: reg.in.hour = num2BCD(v);
376: }
377:
378: uint
379: MK48T02Device::GetWday() const
380: {
381: // データシートの正規の値範囲は 1..7 だが 0 も問題なく保持できて、
382: // NetBSD/luna68k, OpenBSD/luna88k は日付を日曜に変更する際には 0 を
383: // レジスタに書き込んでいる。そのためレジスタ値は 0 と 7 を日曜、
384: // 1を月曜…という運用ということにする。
385: // 戻り値は 0..6 (0が日曜)。
386:
1.1.1.15! root 387: uint v = reg.in.wday;
1.1.1.10 root 388: if (v == 7) {
389: v = 0;
390: }
391: return v;
392: }
393:
394: void
395: MK48T02Device::SetWday(uint v)
396: {
397: // 引数は 0..6 (0が日曜)、MK48T02 の正規の範囲は 1..7 で曜日との対応は
398: // 運用依存のようだ。NetBSD/luna68k, OpenBSD/luna88k は 0 を日曜、1 を
399: // 月曜…と扱っているが、MK48T02 の曜日レジスタは 7 の翌日 1 に繰り上がる
400: // 動作なので、ここでもレジスタには 1..7 (1が月曜、7が日曜) をセットする。
401: if (v == 0)
402: v = 7;
403: reg.in.wday = v;
404: }
405:
406: uint
407: MK48T02Device::GetMday() const
408: {
409: return BCD2num(reg.in.mday);
410: }
411:
412: void
413: MK48T02Device::SetMday(uint v)
414: {
415: reg.in.mday = num2BCD(v);
416: }
417:
418: uint
419: MK48T02Device::GetMon() const
420: {
421: return BCD2num(reg.in.mon);
422: }
423:
424: void
425: MK48T02Device::SetMon(uint v)
426: {
427: reg.in.mon = num2BCD(v);
428: }
429:
430: uint
431: MK48T02Device::DecodeYear(uint bcd_year) const
432: {
433: uint y = BCD2num(bcd_year);
434: y += year_epoch;
435:
436: // 1970年より前を 2000 年代とみなすのはシステム依存だが
437: // UNIX 系はみんなこう(?)
438: // XXX ここの 1970 と RTC エポックを一致させれば端折れるじゃん!と
439: // 思いついたのかしら?
440: if (y < 1970) {
441: y += 100;
442: }
443: return y;
444: }
445:
446: uint
447: MK48T02Device::GetYear() const
448: {
449: // see DecodeYear()
450: return DecodeYear(reg.in.year);
451: }
452:
453: void
454: MK48T02Device::SetYear(uint v)
455: {
456: // see DecodeYear()
457: v -= year_epoch;
458: reg.in.year = num2BCD(v % 100);
459: }
460:
461: uint
462: MK48T02Device::GetLeap() const
463: {
464: // 補正項込みで計算
465: return (BCD2num(reg.in.year) + adjust_leap) & 3;
466: }
467:
468: void
469: MK48T02Device::SetLeap(uint v)
470: {
471: // nop
472: // MK48T02 ではうるう年カウンタはなく、西暦年でうるう年を判断している。
473: }
474:
475: // 1Hz 仮想 RTC からのコール
1.1.1.12 root 476: // (分周段 0/32 秒でコールされる)
1.1.1.10 root 477: void
478: MK48T02Device::Tick1Hz()
479: {
480: if (reg.stop) {
481: // 実機は電力消費を抑えるため発振器ごと止めるが、
482: // 分周段が規定されていないので秒カウントを止めれば十分なはず
483: //nop
484: } else {
485: CountUpSec();
486: }
487:
488: if (hold == false) {
1.1.1.12 root 489: // 秒の更新でメモリにコピーする (Datasheet p2)
490: Store(reg.in);
1.1.1.10 root 491: }
1.1 root 492: }
1.1.1.3 root 493:
494: // 指定したアドレスからのゼロ終端文字列を返す
495: // (内蔵 ROM からのアクセス用)
496: std::string
497: MK48T02Device::PeekString(uint32 addr) const
498: {
499: std::string rv = "";
500:
1.1.1.7 root 501: addr &= 0x7ff;
502: for (; mem[addr]; addr++) {
503: rv += mem[addr];
1.1.1.3 root 504: }
505: return rv;
506: }
507:
508: // 指定したアドレスに文字列を書き込み、
509: // 16 バイトに満たないところはゼロでパディングする。
510: // 書き込めれば true を、書き込めなければ false を返す。
511: // (内蔵 ROM からの書き込み用)
512: bool
513: MK48T02Device::WriteString(uint32 addr, const std::string& data)
514: {
515: int i = 0;
516:
1.1.1.9 root 517: // ここの文字列は16バイト未満なら一見ゼロ終端のようだが、
518: // 16バイトならゼロ終端せず16文字とするフォーマット。
519:
520: if (data.length() > 16) {
1.1.1.3 root 521: return false;
522: }
523:
524: addr &= 0x7ff;
525: for (; i < data.length(); i++) {
1.1.1.9 root 526: mem[addr + i] = data[i];
1.1.1.3 root 527: }
528: for (; i < 16; i++) {
1.1.1.9 root 529: mem[addr + i] = 0;
1.1.1.3 root 530: }
531: return true;
532: }
1.1.1.9 root 533:
534: // 全域をゼロクリアする。
535: // クリアするだけなので、その後何か書いたりチェックサムを計算したりする
536: // 必要はある。
537: // (内蔵 ROM からの書き込み用)
538: void
539: MK48T02Device::ClearAll()
540: {
541: for (int i = 0, sz = file.GetMemSize(); i < sz; i++) {
542: mem[i] = 0;
543: }
544: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.