--- nono/vm/rp5c15.cpp 2026/04/29 17:04:59 1.1.1.9 +++ nono/vm/rp5c15.cpp 2026/04/29 17:05:29 1.1.1.15 @@ -4,29 +4,31 @@ // Licensed under nono-license.txt // -#include "rp5c15.h" -#include "mpu.h" -#include +// +// RTC (RP5C15) +// // アラームは未実装 -// XXX RTC の読み書きはロックしたほうがいいような気がする +#include "rp5c15.h" +#include "mfp.h" +#include "power.h" +// コンストラクタ RP5C15Device::RP5C15Device() - : inherited("RTC") + : inherited() { - devaddr = 0xe8a000; - devlen = 0x2000; - - // X68k は 1980 年の時、年カウンタに 0 をセット + // X68k の RTC epoch は 1980年固定で変更の必要はないので、設定も見ない。 year_epoch = 1980; + // X68k は通常ローカルタイム (というか JST) で使用する前提 use_localtime = true; - monitor.func = (MonitorCallback_t)&RP5C15Device::MonitorUpdate; - monitor.SetSize(41, 22); + monitor.func = ToMonitorCallback(&RP5C15Device::MonitorUpdate); + monitor.SetSize(38, 22); monitor.Regist(ID_MONITOR_RTC); } +// デストラクタ RP5C15Device::~RP5C15Device() { } @@ -35,25 +37,29 @@ RP5C15Device::~RP5C15Device() bool RP5C15Device::Init() { + if (inherited::Init() == false) { + return false; + } + + mfp = GetMFPDevice(); + power = dynamic_cast(GetPowerDevice()); + // デフォルトで 24 時間計にしておく reg.sel24 = 0x01; // デフォルトで 1HZ_ON, 16HZ_ON をネゲート(1)しておく reg.reset = RP5C15::RESET_1Hz | RP5C15::RESET_16Hz; - return inherited::Init(); + return true; } -uint64 -RP5C15Device::Read(uint32 offset) +busdata +RP5C15Device::ReadPort(uint32 offset) { - uint8 data; - int n; - - gMPU->AddCycle(29); // InsideOut p.135 + busdata data; // バンクを展開する - int bank = (reg.mode & RP5C15::MODE_BANK); - n = bank * 0x10 + offset; + uint bank = (reg.mode & RP5C15::MODE_BANK); + uint n = bank * 0x10 + offset; // XXX 14, 15 は write only らしいが何が読めるか @@ -68,29 +74,30 @@ RP5C15Device::Read(uint32 offset) data = reg.r[n]; break; default: - __unreachable(); + VMPANIC("corrupted n=$%x", n); } // 下位4ビット(RP5C15) の中の未実装ビットは %0 だが // 上位4ビットは %1 になる。 data |= 0xf0; - putlog(2, "RTC Bank%d $%x -> $%02x", bank, offset, data); + putlog(2, "Bank%u $%x -> $%02x", bank, offset, data.Data()); + // InsideOut p.135 + const busdata read_wait = busdata::Wait(29 * 40_nsec); + data |= read_wait; + + data |= BusData::Size1; return data; } -uint64 -RP5C15Device::Write(uint32 offset, uint32 data) +busdata +RP5C15Device::WritePort(uint32 offset, uint32 data) { - int n; - - gMPU->AddCycle(38); // InsideOut p.135 - // バンクを展開する - int bank = (reg.mode & RP5C15::MODE_BANK); - n = bank * 0x10 + offset; + uint bank = (reg.mode & RP5C15::MODE_BANK); + uint n = bank * 0x10 + offset; - putlog(2, "RTC Bank%d $%x <- $%x", bank, offset, (data & 0x0f)); + putlog(2, "Bank%u $%x <- $%x", bank, offset, (data & 0x0f)); switch (n) { case RP5C15::SEC1: @@ -191,7 +198,7 @@ RP5C15Device::Write(uint32 offset, uint3 if (data == 0) { // 通常 0 を書き込むこと、となっているので 0 は許可 } else { - putlog(0, "TEST <- $%1x 未実装書き込み", data); + putlog(0, "TEST <- $%1x (NOT IMPLEMENTED)", data); } reg.test = data; break; @@ -211,16 +218,23 @@ RP5C15Device::Write(uint32 offset, uint3 if ((reg.reset & RP5C15::RESET_1Hz)) { alarm_1Hz = false; } + ChangeAlarmOut(); break; default: break; } - return 0; + + // InsideOut p.135 + const busdata write_wait = busdata::Wait(38 * 40_nsec); + + busdata r = write_wait; + r |= BusData::Size1; + return r; } -uint64 -RP5C15Device::Peek(uint32 offset) +busdata +RP5C15Device::PeekPort(uint32 offset) { int n; @@ -240,10 +254,16 @@ RP5C15Device::Peek(uint32 offset) return reg.r[n] | 0xf0; default: - __unreachable(); + return 0xff; } } +bool +RP5C15Device::PokePort(uint32 offset, uint32 data) +{ + return false; +} + void RP5C15Device::MonitorUpdate(Monitor *, TextScreen& screen) { @@ -252,8 +272,8 @@ RP5C15Device::MonitorUpdate(Monitor *, T screen.Clear(); - int year = reg.year10 * 10 + reg.year1 + year_epoch; - int hour10; + uint year = reg.year10 * 10 + reg.year1 + year_epoch; + uint hour10; const char *ampm; if (reg.sel24) { ampm = ""; @@ -262,14 +282,14 @@ RP5C15Device::MonitorUpdate(Monitor *, T ampm = (reg.hour10 & 2) == 0 ? " AM": " PM"; hour10 = reg.hour10 & 1; } - screen.Print(0, y++, "%04d[%x%x]/%x%x/%x%x(%s) %x%x:%x%x:%x%x.%02x%s", + screen.Print(0, y++, "%04u[%x%x]/%x%x/%x%x(%s) %x%x:%x%x:%x%x.%02x%s", year, reg.year10, reg.year1, reg.mon10, reg.mon1, reg.day10, reg.day1, wdays[reg.wday], hour10, reg.hour1, reg.min10, reg.min1, reg.sec10, reg.sec1, ((uint32)cnt & 0x1f), ampm); screen.Print(0, y, "TimeZone: %s", (use_localtime) ? "Local" : "UTC"); - screen.Print(x, y, "Epoch: %4d", year_epoch); + screen.Print(x, y, "Epoch: %4u", year_epoch); // バンク0 y = 3; @@ -338,28 +358,26 @@ RP5C15Device::MonitorUpdate(Monitor *, T screen.Print(x + 14, 4, "(%s)", clksel_s[reg.clksel]); // 12/24H - screen.Print(x + 14, 14, "(%dH)", reg.sel24 ? 24 : 12); + screen.Print(x + 14, 14, "(%uH)", reg.sel24 ? 24 : 12); // MODE y = 17; - screen.Print(0, y, "$d(MODE): %x", reg.mode); - screen.Puts(13, y, TA::OnOff(reg.mode & RP5C15::MODE_TIMER_EN), "TIMER_EN"); - screen.Puts(22, y, TA::OnOff(reg.mode & RP5C15::MODE_ALARM_EN), "ALARM_EN"); - screen.Print(31, y, "BANK=%d", reg.mode & RP5C15::MODE_BANK); + screen.Print(0, y, "$d(MODE): %x", reg.mode); + screen.Puts(14, y, TA::OnOff(reg.mode & RP5C15::MODE_TIMER_EN), "TIMER_EN"); + screen.Puts(23, y, TA::OnOff(reg.mode & RP5C15::MODE_ALARM_EN), "ALARM_EN"); + screen.Print(32, y, "BANK=%u", reg.mode & RP5C15::MODE_BANK); // TEST y++; - screen.Print(0, y, "$e(TEST): %x", reg.test); + screen.Print(0, y, "$e(TEST): %x", reg.test); // RESET y++; - screen.Print(0, y, "$f(RESET): %x", reg.reset); - screen.Print(13, y, "1Hz=%s", - (reg.reset & RP5C15::RESET_1Hz) ? "OFF" : "ON"); - screen.Print(21, y, "16Hz=%s", - (reg.reset & RP5C15::RESET_16Hz) ? "OFF" : "ON"); - screen.Puts(30, y, TA::OnOff(reg.reset & RP5C15::RESET_TIMER), "TIMER"); - screen.Puts(36, y, TA::OnOff(reg.reset & RP5C15::RESET_ALARM), "ALARM"); + screen.Print(0, y, "$f(RESET): %x", reg.reset); + screen.Print(14, y, TA::OnOff(reg.reset & RP5C15::RESET_1Hz), "!1Hz"); + screen.Print(19, y, TA::OnOff(reg.reset & RP5C15::RESET_16Hz), "!16Hz"); + screen.Puts(25, y, TA::OnOff(reg.reset & RP5C15::RESET_TIMER), "TIMER"); + screen.Puts(31, y, TA::OnOff(reg.reset & RP5C15::RESET_ALARM), "ALARM"); // PIN y++; @@ -368,7 +386,7 @@ RP5C15Device::MonitorUpdate(Monitor *, T // ここでは分かりやすさ優先で正論理としている。 screen.Print(0, y, "Pin: CLKOUT=%c ALARM=%c", clkout ? 'H' : 'L', - AlarmOut() ? 'H' : 'L'); + alarm_out ? 'H' : 'L'); } // 秒を取得 @@ -408,7 +426,7 @@ RP5C15Device::GetHour() const if (reg.sel24) { return reg.hour10 * 10 + reg.hour1; } else { - int h10; + uint h10; h10 = (reg.hour10 & 0x1) * 10 + ((reg.hour10 & 0x2) >> 1) * 12; return h10 + reg.hour1; } @@ -422,7 +440,7 @@ RP5C15Device::SetHour(uint v) reg.hour1 = v % 10; reg.hour10 = v / 10; } else { - int pm = (v < 12) ? 0x0 : 0x2; + uint pm = (v < 12) ? 0x0 : 0x2; v %= 12; reg.hour1 = v % 10; reg.hour10 = v / 10 + pm; @@ -501,12 +519,28 @@ RP5C15Device::SetLeap(uint v) reg.leap = v & 3; } +// ALARM OUT 信号 +void +RP5C15Device::ChangeAlarmOut() +{ + bool old = alarm_out; + + // アラーム出力は内部のアラーム用 16Hz, 1Hz と + // アラーム時刻一致の alarm_on との OR 論理となっている。 + alarm_out = alarm_16Hz | alarm_1Hz | alarm_on; + + if (old != alarm_out) { + power->SetAlarmOut(alarm_out); + mfp->SetAlarmOut(alarm_out); + } +} + // 32Hz 仮想 RTC からのコール void RP5C15Device::Tick32Hz() { if (reg.clksel == 4) { - // CLKOUT 16Hz は duty50% なので ON/OFF 駆動は 32Hz になる + // CLKOUT 16Hz は duty50% なのでオン/オフ駆動は 32Hz になる clkout = ((cnt & 1) == 0); } @@ -517,6 +551,7 @@ RP5C15Device::Tick32Hz() // アラーム端子 16Hz パルス出力は %0 ならオン if ((reg.reset & RP5C15::RESET_16Hz) == 0) { alarm_16Hz = !alarm_16Hz; + ChangeAlarmOut(); } } @@ -526,11 +561,11 @@ void RP5C15Device::Tick2Hz() { if (reg.clksel == 5) { - // CLKOUT 1Hz は duty50% なので ON/OFF 駆動は 2Hz になる + // CLKOUT 1Hz は duty50% なのでオン/オフ駆動は 2Hz になる。 // CLKOUT 端子は秒の繰り上がりに同期している。 // TIMER_EN (秒への繰り上がり) が無効でも反転自体は起きる気が // するので、ここでは UPDATE_SEC ではなく cnt で判定する。 - // CLKOUT は 1秒の繰り上がりが起きる時に 'H' になる + // CLKOUT は 1秒の繰り上がりが起きる時に 'H' になる。 clkout = ((cnt & 15) == 0); } @@ -542,6 +577,7 @@ RP5C15Device::Tick2Hz() // アラーム端子 16Hz パルス出力は %0 ならオン if ((reg.reset & RP5C15::RESET_1Hz) == 0) { alarm_1Hz = !alarm_1Hz; + ChangeAlarmOut(); } } @@ -563,7 +599,7 @@ RP5C15Device::Tick1Hz() // としか書かれておらず、00 or 30 秒で動作するとは書かれていない。 // データシートの図からはカレンダとは独立しているように読める。 - // 1/60Hz CLKOUT のための 60sec カウンタ + // 1/60Hz CLKOUT のための 60sec カウンタ。 // このカウンタは clksel によらず常にカウントする。 cnt_60sec++; if (cnt_60sec == 60) { @@ -571,7 +607,7 @@ RP5C15Device::Tick1Hz() } if (reg.clksel == 6) { - // CLKOUT 1/60Hz は duty50% なので ON/OFF 駆動は 30sec になる + // CLKOUT 1/60Hz は duty50% なのでオン/オフ駆動は 30sec になる clkout = (cnt_60sec < 30); } }