123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275 |
- #include "RP5C01.hh"
- #include "SRAM.hh"
- #include "serialize.hh"
- #include <cassert>
- #include <ctime>
- namespace openmsx {
- // TODO ALARM is not implemented (not connected on MSX)
- // TODO 1Hz 16Hz output not implemented (not connected on MSX)
- static const nibble MODE_REG = 13;
- static const nibble TEST_REG = 14;
- static const nibble RESET_REG = 15;
- static const nibble TIME_BLOCK = 0;
- static const nibble ALARM_BLOCK = 1;
- static const nibble MODE_BLOKSELECT = 0x3;
- static const nibble MODE_ALARMENABLE = 0x4;
- static const nibble MODE_TIMERENABLE = 0x8;
- static const nibble TEST_SECONDS = 0x1;
- static const nibble TEST_MINUTES = 0x2;
- static const nibble TEST_DAYS = 0x4;
- static const nibble TEST_YEARS = 0x8;
- static const nibble RESET_ALARM = 0x1;
- static const nibble RESET_FRACTION = 0x2;
- // 0-bits are ignored on writing and return 0 on reading
- static const nibble mask[4][13] = {
- { 0xf, 0x7, 0xf, 0x7, 0xf, 0x3, 0x7, 0xf, 0x3, 0xf, 0x1, 0xf, 0xf},
- { 0x0, 0x0, 0xf, 0x7, 0xf, 0x3, 0x7, 0xf, 0x3, 0x0, 0x1, 0x3, 0x0},
- { 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf},
- { 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf}
- };
- RP5C01::RP5C01(CommandController& commandController, SRAM& regs_,
- EmuTime::param time, const std::string& name)
- : regs(regs_)
- , modeSetting(
- commandController,
- ((name == "Real time clock") ? "rtcmode" // bw-compat
- : (name + " mode")),
- "Real Time Clock mode", RP5C01::EMUTIME,
- EnumSetting<RP5C01::RTCMode>::Map{
- {"EmuTime", RP5C01::EMUTIME},
- {"RealTime", RP5C01::REALTIME}})
- , reference(time)
- {
- initializeTime();
- reset(time);
- }
- void RP5C01::reset(EmuTime::param time)
- {
- modeReg = MODE_TIMERENABLE;
- testReg = 0;
- resetReg = 0;
- updateTimeRegs(time);
- }
- nibble RP5C01::readPort(nibble port, EmuTime::param time)
- {
- switch (port) {
- case MODE_REG:
- case TEST_REG:
- case RESET_REG:
- // nothing
- break;
- default:
- unsigned block = modeReg & MODE_BLOKSELECT;
- if (block == TIME_BLOCK) {
- updateTimeRegs(time);
- }
- }
- return peekPort(port);
- }
- nibble RP5C01::peekPort(nibble port) const
- {
- assert(port <= 0x0f);
- switch (port) {
- case MODE_REG:
- return modeReg;
- case TEST_REG:
- case RESET_REG:
- // write only
- return 0x0f; // TODO check this
- default:
- unsigned block = modeReg & MODE_BLOKSELECT;
- nibble tmp = regs[block * 13 + port];
- return tmp & mask[block][port];
- }
- }
- void RP5C01::writePort(nibble port, nibble value, EmuTime::param time)
- {
- assert (port<=0x0f);
- switch (port) {
- case MODE_REG:
- updateTimeRegs(time);
- modeReg = value;
- break;
- case TEST_REG:
- updateTimeRegs(time);
- testReg = value;
- break;
- case RESET_REG:
- resetReg = value;
- if (value & RESET_ALARM) {
- resetAlarm();
- }
- if (value & RESET_FRACTION) {
- fraction = 0;
- }
- break;
- default:
- unsigned block = modeReg & MODE_BLOKSELECT;
- if (block == TIME_BLOCK) {
- updateTimeRegs(time);
- }
- regs.write(block * 13 + port, value & mask[block][port]);
- if (block == TIME_BLOCK) {
- regs2Time();
- }
- }
- }
- void RP5C01::initializeTime()
- {
- time_t t = time(nullptr);
- struct tm *tm = localtime(&t);
- fraction = 0; // fractions of a second
- seconds = tm->tm_sec; // 0-59
- minutes = tm->tm_min; // 0-59
- hours = tm->tm_hour; // 0-23
- dayWeek = tm->tm_wday; // 0-6 0=sunday
- days = tm->tm_mday-1; // 0-30
- months = tm->tm_mon; // 0-11
- years = tm->tm_year - 80; // 0-99 0=1980
- leapYear = tm->tm_year % 4; // 0-3 0=leap year
- time2Regs();
- }
- void RP5C01::regs2Time()
- {
- seconds = regs[TIME_BLOCK * 13 + 0] + 10 * regs[TIME_BLOCK * 13 + 1];
- minutes = regs[TIME_BLOCK * 13 + 2] + 10 * regs[TIME_BLOCK * 13 + 3];
- hours = regs[TIME_BLOCK * 13 + 4] + 10 * regs[TIME_BLOCK * 13 + 5];
- dayWeek = regs[TIME_BLOCK * 13 + 6];
- days = regs[TIME_BLOCK * 13 + 7] + 10 * regs[TIME_BLOCK * 13 + 8] - 1;
- months = regs[TIME_BLOCK * 13 + 9] + 10 * regs[TIME_BLOCK * 13 +10] - 1;
- years = regs[TIME_BLOCK * 13 +11] + 10 * regs[TIME_BLOCK * 13 +12];
- leapYear = regs[ALARM_BLOCK * 13 +11];
- if (!regs[ALARM_BLOCK * 13 + 10]) {
- // 12 hours mode
- if (hours >= 20) hours = (hours - 20) + 12;
- }
- }
- void RP5C01::time2Regs()
- {
- unsigned hours_ = hours;
- if (!regs[ALARM_BLOCK * 13 + 10]) {
- // 12 hours mode
- if (hours >= 12) hours_ = (hours - 12) + 20;
- }
- regs.write(TIME_BLOCK * 13 + 0, seconds % 10);
- regs.write(TIME_BLOCK * 13 + 1, seconds / 10);
- regs.write(TIME_BLOCK * 13 + 2, minutes % 10);
- regs.write(TIME_BLOCK * 13 + 3, minutes / 10);
- regs.write(TIME_BLOCK * 13 + 4, hours_ % 10);
- regs.write(TIME_BLOCK * 13 + 5, hours_ / 10);
- regs.write(TIME_BLOCK * 13 + 6, dayWeek);
- regs.write(TIME_BLOCK * 13 + 7, (days+1) % 10); // 0-30 -> 1-31
- regs.write(TIME_BLOCK * 13 + 8, (days+1) / 10); // 0-11 -> 1-12
- regs.write(TIME_BLOCK * 13 + 9, (months+1) % 10);
- regs.write(TIME_BLOCK * 13 + 10, (months+1) / 10);
- regs.write(TIME_BLOCK * 13 + 11, years % 10);
- regs.write(TIME_BLOCK * 13 + 12, years / 10);
- regs.write(ALARM_BLOCK * 13 + 11, leapYear);
- }
- static int daysInMonth(int month, unsigned leapYear)
- {
- const unsigned daysInMonths[12] = {
- 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
- };
- month %= 12;
- return ((month == 1) && (leapYear == 0)) ? 29 : daysInMonths[month];
- }
- void RP5C01::updateTimeRegs(EmuTime::param time)
- {
- if (modeSetting.getEnum() == EMUTIME) {
- // sync with EmuTime, perfect emulation
- auto elapsed = unsigned(reference.getTicksTill(time));
- reference.advance(time);
- // in test mode increase sec/min/.. at a rate of 16384Hz
- fraction += (modeReg & MODE_TIMERENABLE) ? elapsed : 0;
- unsigned carrySeconds = (testReg & TEST_SECONDS)
- ? elapsed : fraction / FREQ;
- seconds += carrySeconds;
- unsigned carryMinutes = (testReg & TEST_MINUTES)
- ? elapsed : seconds / 60;
- minutes += carryMinutes;
- hours += minutes / 60;
- unsigned carryDays = (testReg & TEST_DAYS)
- ? elapsed : hours / 24;
- days += carryDays;
- dayWeek += carryDays;
- while (days >= daysInMonth(months, leapYear)) {
- // TODO not correct because leapYear is not updated
- // is only triggered when we update several months
- // at a time (but might happen in TEST_DAY mode)
- days -= daysInMonth(months, leapYear);
- months++;
- }
- unsigned carryYears = (testReg & TEST_YEARS)
- ? elapsed : unsigned(months / 12);
- years += carryYears;
- leapYear += carryYears;
- fraction %= FREQ;
- seconds %= 60;
- minutes %= 60;
- hours %= 24;
- dayWeek %= 7;
- months %= 12;
- years %= 100;
- leapYear %= 4;
- time2Regs();
- } else {
- // sync with host clock
- // writes to time, test and reset registers have no effect
- initializeTime();
- }
- }
- void RP5C01::resetAlarm()
- {
- for (unsigned i = 2; i <= 8; ++i) {
- regs.write(ALARM_BLOCK * 13 + i, 0);
- }
- }
- template<typename Archive>
- void RP5C01::serialize(Archive& ar, unsigned /*version*/)
- {
- ar.serialize("reference", reference);
- ar.serialize("fraction", fraction);
- ar.serialize("seconds", seconds);
- ar.serialize("minutes", minutes);
- ar.serialize("hours", hours);
- ar.serialize("dayWeek", dayWeek);
- ar.serialize("years", years);
- ar.serialize("leapYear", leapYear);
- ar.serialize("days", days);
- ar.serialize("months", months);
- ar.serialize("modeReg", modeReg);
- ar.serialize("testReg", testReg);
- ar.serialize("resetReg", resetReg);
- }
- INSTANTIATE_SERIALIZE_METHODS(RP5C01);
- } // namespace openmsx
|