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