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