Annotation of nono/vm/mk48t02.cpp, revision 1.1.1.11

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: }

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.