|
|
nono 1.7.0
//
// nono
// Copyright (C) 2020 nono project
// Licensed under nono-license.txt
//
//
// RTC (RP5C15)
//
// アラームは未実装
#include "rp5c15.h"
#include "event.h"
#include "mfp.h"
#include "monitor.h"
#include "power.h"
// コンストラクタ
RP5C15Device::RP5C15Device()
: inherited()
{
// X68k の RTC epoch は 1980年固定で変更の必要はないので、設定も見ない。
year_epoch = 1980;
// X68k は通常ローカルタイム (というか JST) で使用する前提
use_localtime = true;
monitor = gMonitorManager->Regist(ID_MONITOR_RTC, this);
monitor->SetCallback(&RP5C15Device::MonitorScreen);
monitor->SetSize(38, 22);
}
// デストラクタ
RP5C15Device::~RP5C15Device()
{
}
// 初期化
bool
RP5C15Device::Init()
{
if (inherited::Init() == false) {
return false;
}
mfp = GetMFPDevice();
power = dynamic_cast<X68030PowerDevice *>(GetPowerDevice());
// デフォルトで 24 時間計にしておく
reg.sel24 = 0x01;
// デフォルトで 1HZ_ON, 16HZ_ON をネゲート(1)しておく
reg.reset = RP5C15::RESET_1Hz | RP5C15::RESET_16Hz;
return true;
}
busdata
RP5C15Device::ReadPort(uint32 offset)
{
busdata data;
// バンクを展開する
uint bank = (reg.mode & RP5C15::MODE_BANK);
uint n = bank * 0x10 + offset;
// XXX 14, 15 は write only らしいが何が読めるか
switch (n) {
case 0x1d: // MODE (Bank0 と同じ)
case 0x1e: // TEST (Bank0 と同じ)
case 0x1f: // RESET (Bank0 と同じ)
n -= 0x10;
FALLTHROUGH;
case 0x00 ... 0x1c:
data = reg.r[n];
break;
default:
VMPANIC("corrupted n=$%x", n);
}
// 下位4ビット(RP5C15) の中の未実装ビットは %0 だが
// 上位4ビットは %1 になる。
data |= 0xf0;
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;
}
busdata
RP5C15Device::WritePort(uint32 offset, uint32 data)
{
// バンクを展開する
uint bank = (reg.mode & RP5C15::MODE_BANK);
uint n = bank * 0x10 + offset;
putlog(2, "Bank%u $%x <- $%x", bank, offset, (data & 0x0f));
switch (n) {
case RP5C15::SEC1:
case RP5C15::SEC10:
case RP5C15::MIN1:
case RP5C15::MIN10:
case RP5C15::HOUR1:
case RP5C15::HOUR10:
case RP5C15::WDAY: // 曜日カウンタは日付などとは一切連動していない
case RP5C15::DAY1:
case RP5C15::DAY10:
case RP5C15::MON1:
case RP5C15::MON10:
case RP5C15::YEAR1:
case RP5C15::YEAR10:
case RP5C15::SEL24:
case RP5C15::LEAP:
reg.r[n] = data & reg.mask[n];
break;
case RP5C15::CLKSEL:
reg.clksel = data & reg.mask[n];
switch (reg.clksel) {
case 0: // ハイインピーダンス
case 1: // 16384Hz
case 2: // 1024Hz
case 3: // 128Hz
// 128Hz 点滅とか点灯みたいなもんだろ(適当)
clkout = true;
break;
// (?) たぶん内部ではずっとカウンタ部分は動いていて
// 出力位置を切り替えているだけのはずなので、
// CLKSEL を変えた時の初期状態は内部カウンタの状態に依存するはず。
case 4: // 16Hz
clkout = ((cnt & 1) == 0);
break;
case 5: // 1Hz
clkout = ((cnt & 16) == 0);
break;
case 6: // 1/60Hz
clkout = (cnt_60sec < 30);
break;
case 7:
clkout = false;
break;
}
break;
// アラーム関係は未実装
case RP5C15::ALARM_MIN1 ... RP5C15::ALARM_DAY10:
case RP5C15::notused1:
case RP5C15::notused2:
// マスクして書き込む
reg.r[n] = data & reg.mask[n];
break;
case RP5C15::ADJUST:
{
// %0 なら何もしない?
if ((data & 1) == 0) {
break;
}
// %1 ならアジャスト。
// 現在の秒が 0..29秒なら 0秒に切り下げ。
// 30..59秒なら +1分0秒に繰り上げ。
// いずれにしても 1秒と10秒の位は 0 にする。
// XXX 秒以下のカウンタもクリアするのでは?
if (GetSec() >= 30) {
// 1分繰り上げ
CountUpMin();
}
// マニュアルを読む限り秒以下をクリア(?)
reg.sec1 = 0;
reg.sec10 = 0;
cnt = 0;
clock_hold = false;
break;
}
case RP5C15::MODE:
case RP5C15::MODE_BANK1:
reg.mode = data & reg.mask[RP5C15::MODE];
if ((reg.mode & RP5C15::MODE_TIMER_EN)) {
if (clock_hold) {
// ホールドされていた秒カウントアップを実行
CountUpSec();
clock_hold = false;
}
}
break;
case RP5C15::TEST:
case RP5C15::TEST_BANK1:
data &= reg.mask[RP5C15::TEST];
if (data == 0) {
// 通常 0 を書き込むこと、となっているので 0 は許可
} else {
putlog(0, "TEST <- $%1x (NOT IMPLEMENTED)", data);
}
reg.test = data;
break;
case RP5C15::RESET:
case RP5C15::RESET_BANK1:
reg.reset = data & reg.mask[RP5C15::RESET];
if ((reg.reset & RP5C15::RESET_TIMER)) {
// %1 を書き込むと、秒未満の内部カウンタをリセット
// 繰り上がり分の保持も消す。
cnt = 0;
clock_hold = false;
}
if ((reg.reset & RP5C15::RESET_16Hz)) {
alarm_16Hz = false;
}
if ((reg.reset & RP5C15::RESET_1Hz)) {
alarm_1Hz = false;
}
ChangeAlarmOut();
break;
default:
break;
}
// InsideOut p.135
const busdata write_wait = busdata::Wait(38 * 40_nsec);
busdata r = write_wait;
r |= BusData::Size1;
return r;
}
busdata
RP5C15Device::PeekPort(uint32 offset)
{
int n;
// バンクを展開する
int bank = (reg.mode & RP5C15::MODE_BANK);
n = bank * 0x10 + offset;
switch (n) {
case RP5C15::MODE_BANK1:
case RP5C15::TEST_BANK1:
case RP5C15::RESET_BANK1:
// Bank0 と同じものが見える
n -= 0x10;
FALLTHROUGH;
case 0x00 ... 0x1c:
return reg.r[n] | 0xf0;
default:
return 0xff;
}
}
bool
RP5C15Device::PokePort(uint32 offset, uint32 data)
{
return false;
}
void
RP5C15Device::MonitorScreen(Monitor *, TextScreen& screen)
{
int x = 18;
int y = 0;
screen.Clear();
uint year = reg.year10 * 10 + reg.year1 + year_epoch;
uint hour10;
const char *ampm;
if (reg.sel24) {
ampm = "";
hour10 = reg.hour10;
} else {
ampm = (reg.hour10 & 2) == 0 ? " AM": " PM";
hour10 = reg.hour10 & 1;
}
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: %4u", year_epoch);
// バンク0
y = 3;
screen.Print(0, y++, "Bank0");
static const char * const b0names[] = {
" 1sec",
"10sec",
" 1min",
"10min",
" 1hour",
"10hour",
"wday",
" 1day",
"10day",
" 1mon",
"10mon",
" 1year",
"10year",
};
for (int i = 0; i < countof(b0names); i++) {
screen.Print(0, y, "$%x(%s)", i, b0names[i]);
screen.Print(10, y, ": %x", reg.r[i]);
y++;
}
// バンク1
y = 3;
screen.Print(x, y++, "Bank1");
static const char * const b1names[] = {
"CLKSEL",
"ADJ",
"AL 1m",
"AL10m",
"AL 1h",
"AL10h",
"ALwday",
"AL 1d",
"AL10d",
"",
"12/24H",
"LEAP",
"",
};
for (int i = 0; i < countof(b1names); i++) {
if (b1names[i][0] != '\0') {
screen.Print(x, y, "$%x(%s)", i, b1names[i]);
screen.Print(x + 10, y, ": %x", reg.r[16 + i]);
} else {
// $9 と $c は欠番
screen.Print(x, y, "$%x - ", i);
}
y++;
}
// CLKSEL
static const char * const clksel_s[] = {
"HiZ",
"16384Hz",
"1024Hz",
"128Hz",
"16Hz",
"1Hz",
"1/60Hz",
"L",
};
screen.Print(x + 14, 4, "(%s)", clksel_s[reg.clksel]);
// 12/24H
screen.Print(x + 14, 14, "(%uH)", reg.sel24 ? 24 : 12);
// MODE
y = 17;
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);
// RESET
y++;
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++;
y++;
// ハードウェアの ~ALARM ピンは負論理なのでアクティブ L だが、
// ここでは分かりやすさ優先で正論理としている。
screen.Print(0, y, "Pin: CLKOUT=%c ALARM=%c",
clkout ? 'H' : 'L',
alarm_out ? 'H' : 'L');
}
// 秒を取得
uint
RP5C15Device::GetSec() const
{
return reg.sec10 * 10 + reg.sec1;
}
// 秒を設定
void
RP5C15Device::SetSec(uint v)
{
reg.sec1 = v % 10;
reg.sec10 = v / 10;
}
// 分を取得
uint
RP5C15Device::GetMin() const
{
return reg.min10 * 10 + reg.min1;
}
// 分を設定
void
RP5C15Device::SetMin(uint v)
{
reg.min1 = v % 10;
reg.min10 = v / 10;
}
// 時を取得
uint
RP5C15Device::GetHour() const
{
if (reg.sel24) {
return reg.hour10 * 10 + reg.hour1;
} else {
uint h10;
h10 = (reg.hour10 & 0x1) * 10 + ((reg.hour10 & 0x2) >> 1) * 12;
return h10 + reg.hour1;
}
}
// 時を設定
void
RP5C15Device::SetHour(uint v)
{
if (reg.sel24) {
reg.hour1 = v % 10;
reg.hour10 = v / 10;
} else {
uint pm = (v < 12) ? 0x0 : 0x2;
v %= 12;
reg.hour1 = v % 10;
reg.hour10 = v / 10 + pm;
}
}
// 曜日を取得
uint
RP5C15Device::GetWday() const
{
return reg.wday;
}
// 曜日を設定
void
RP5C15Device::SetWday(uint v)
{
reg.wday = v;
}
// 日を取得
uint
RP5C15Device::GetMday() const
{
return reg.day10 * 10 + reg.day1;
}
// 日を設定
void
RP5C15Device::SetMday(uint v)
{
reg.day1 = v % 10;
reg.day10 = v / 10;
}
// 月を取得
uint
RP5C15Device::GetMon() const
{
return reg.mon10 * 10 + reg.mon1;
}
// 月を設定
void
RP5C15Device::SetMon(uint v)
{
reg.mon1 = v % 10;
reg.mon10 = v / 10;
}
// 年を取得
uint
RP5C15Device::GetYear() const
{
return reg.year10 * 10 + reg.year1 + year_epoch;
}
// 年を設定
void
RP5C15Device::SetYear(uint v)
{
v -= year_epoch;
reg.year1 = v % 10;
reg.year10 = v / 10;
}
uint
RP5C15Device::GetLeap() const
{
return reg.leap;
}
void
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% なのでオン/オフ駆動は 32Hz になる
clkout = ((cnt & 1) == 0);
}
// ALARM 端子の 16Hz パルスも CLKOUT に同期するとは書いてないけど、
// 面倒なのでとりあえず同じところで処理してしまう。
// データシートの書き方的には同じ波形になると思われる。
// アラーム端子 16Hz パルス出力は %0 ならオン
if ((reg.reset & RP5C15::RESET_16Hz) == 0) {
alarm_16Hz = !alarm_16Hz;
ChangeAlarmOut();
}
}
// 2Hz 仮想 RTC からのコール
// 分周段 0/32, 16/32 秒でコールされる。
void
RP5C15Device::Tick2Hz()
{
if (reg.clksel == 5) {
// CLKOUT 1Hz は duty50% なのでオン/オフ駆動は 2Hz になる。
// CLKOUT 端子は秒の繰り上がりに同期している。
// TIMER_EN (秒への繰り上がり) が無効でも反転自体は起きる気が
// するので、ここでは UPDATE_SEC ではなく cnt で判定する。
// CLKOUT は 1秒の繰り上がりが起きる時に 'H' になる。
clkout = ((cnt & 15) == 0);
}
// ALARM 端子の 1Hz パルスも CLKOUT に同期するとは書いてないけど、
// 面倒なのでとりあえず同じところで処理してしまう。
// データシートの書き方的には同じ波形になると思われる。
// アラーム端子 16Hz パルス出力は %0 ならオン
if ((reg.reset & RP5C15::RESET_1Hz) == 0) {
alarm_1Hz = !alarm_1Hz;
ChangeAlarmOut();
}
}
// 1Hz 仮想 RTC からのコール
// 分周段 0/32 秒でコールされる。
void
RP5C15Device::Tick1Hz()
{
if ((reg.mode & RP5C15::MODE_TIMER_EN)) {
// 秒カウンタ更新
CountUpSec();
} else {
// 秒カウンタは更新させない。内部のクロックホールドをアサート。
clock_hold = true;
}
// CLKOUT 1/60 Hz については
// 「秒のカウントアップのタイミングでアップエッジ」
// としか書かれておらず、00 or 30 秒で動作するとは書かれていない。
// データシートの図からはカレンダとは独立しているように読める。
// 1/60Hz CLKOUT のための 60sec カウンタ。
// このカウンタは clksel によらず常にカウントする。
cnt_60sec++;
if (cnt_60sec == 60) {
cnt_60sec = 0;
}
if (reg.clksel == 6) {
// CLKOUT 1/60Hz は duty50% なのでオン/オフ駆動は 30sec になる
clkout = (cnt_60sec < 30);
}
}
/*static*/ const uint8
RP5C15::mask[] = {
0x0f, 0x07, 0x0f, 0x07, // 1秒 10秒 1分 10分
0x0f, 0x03, 0x07, 0x0f, // 1時 10時 曜日 1日
0x01, 0x0f, 0x01, 0x0f, // 10日 1月 10月 1年
0x0f, 0x0d, 0x0f, 0x0f, // 10年 MODE TEST RESET
0x07, 0x01, 0x0f, 0x07, // CLKO Adjust AL1分 AL10分
0x0f, 0x03, 0x07, 0x0f, // AL1時 AL10時 AL曜日 AL1日
0x03, 0x00, 0x01, 0x03, // AL10日 --- 12/24 閏年
0x00 // ---
};
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.