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