openMSX
RP5C01.cc
Go to the documentation of this file.
1 #include "RP5C01.hh"
2 #include "SRAM.hh"
3 #include "serialize.hh"
4 #include <cassert>
5 #include <ctime>
6 
7 namespace openmsx {
8 
9 // TODO ALARM is not implemented (not connected on MSX)
10 // TODO 1Hz 16Hz output not implemented (not connected on MSX)
11 
12 constexpr nibble MODE_REG = 13;
13 constexpr nibble TEST_REG = 14;
14 constexpr nibble RESET_REG = 15;
15 
16 constexpr nibble TIME_BLOCK = 0;
17 constexpr nibble ALARM_BLOCK = 1;
18 
19 constexpr nibble MODE_BLOKSELECT = 0x3;
20 constexpr nibble MODE_ALARMENABLE = 0x4;
21 constexpr nibble MODE_TIMERENABLE = 0x8;
22 
23 constexpr nibble TEST_SECONDS = 0x1;
24 constexpr nibble TEST_MINUTES = 0x2;
25 constexpr nibble TEST_DAYS = 0x4;
26 constexpr nibble TEST_YEARS = 0x8;
27 
28 constexpr nibble RESET_ALARM = 0x1;
29 constexpr nibble RESET_FRACTION = 0x2;
30 
31 
32 // 0-bits are ignored on writing and return 0 on reading
33 constexpr nibble mask[4][13] = {
34  { 0xf, 0x7, 0xf, 0x7, 0xf, 0x3, 0x7, 0xf, 0x3, 0xf, 0x1, 0xf, 0xf},
35  { 0x0, 0x0, 0xf, 0x7, 0xf, 0x3, 0x7, 0xf, 0x3, 0x0, 0x1, 0x3, 0x0},
36  { 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf},
37  { 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf}
38 };
39 
40 RP5C01::RP5C01(CommandController& commandController, SRAM& regs_,
41  EmuTime::param time, const std::string& name)
42  : regs(regs_)
43  , modeSetting(
44  commandController,
45  ((name == "Real time clock") ? "rtcmode" // bw-compat
46  : (name + " mode")),
47  "Real Time Clock mode", RP5C01::EMUTIME,
48  EnumSetting<RP5C01::RTCMode>::Map{
49  {"EmuTime", RP5C01::EMUTIME},
50  {"RealTime", RP5C01::REALTIME}})
51  , reference(time)
52 {
53  initializeTime();
54  reset(time);
55 }
56 
57 void RP5C01::reset(EmuTime::param time)
58 {
59  modeReg = MODE_TIMERENABLE;
60  testReg = 0;
61  resetReg = 0;
62  updateTimeRegs(time);
63 }
64 
65 nibble RP5C01::readPort(nibble port, EmuTime::param time)
66 {
67  switch (port) {
68  case MODE_REG:
69  case TEST_REG:
70  case RESET_REG:
71  // nothing
72  break;
73  default:
74  unsigned block = modeReg & MODE_BLOKSELECT;
75  if (block == TIME_BLOCK) {
76  updateTimeRegs(time);
77  }
78  }
79  return peekPort(port);
80 }
81 
83 {
84  assert(port <= 0x0f);
85  switch (port) {
86  case MODE_REG:
87  return modeReg;
88  case TEST_REG:
89  case RESET_REG:
90  // write only
91  return 0x0f; // TODO check this
92  default:
93  unsigned block = modeReg & MODE_BLOKSELECT;
94  nibble tmp = regs[block * 13 + port];
95  return tmp & mask[block][port];
96  }
97 }
98 
99 void RP5C01::writePort(nibble port, nibble value, EmuTime::param time)
100 {
101  assert (port<=0x0f);
102  switch (port) {
103  case MODE_REG:
104  updateTimeRegs(time);
105  modeReg = value;
106  break;
107  case TEST_REG:
108  updateTimeRegs(time);
109  testReg = value;
110  break;
111  case RESET_REG:
112  resetReg = value;
113  if (value & RESET_ALARM) {
114  resetAlarm();
115  }
116  if (value & RESET_FRACTION) {
117  fraction = 0;
118  }
119  break;
120  default:
121  unsigned block = modeReg & MODE_BLOKSELECT;
122  if (block == TIME_BLOCK) {
123  updateTimeRegs(time);
124  }
125  regs.write(block * 13 + port, value & mask[block][port]);
126  if (block == TIME_BLOCK) {
127  regs2Time();
128  }
129  }
130 }
131 
132 void RP5C01::initializeTime()
133 {
134  time_t t = time(nullptr);
135  struct tm *tm = localtime(&t);
136  fraction = 0; // fractions of a second
137  seconds = tm->tm_sec; // 0-59
138  minutes = tm->tm_min; // 0-59
139  hours = tm->tm_hour; // 0-23
140  dayWeek = tm->tm_wday; // 0-6 0=sunday
141  days = tm->tm_mday-1; // 0-30
142  months = tm->tm_mon; // 0-11
143  years = tm->tm_year - 80; // 0-99 0=1980
144  leapYear = tm->tm_year % 4; // 0-3 0=leap year
145  time2Regs();
146 }
147 
148 void RP5C01::regs2Time()
149 {
150  seconds = regs[TIME_BLOCK * 13 + 0] + 10 * regs[TIME_BLOCK * 13 + 1];
151  minutes = regs[TIME_BLOCK * 13 + 2] + 10 * regs[TIME_BLOCK * 13 + 3];
152  hours = regs[TIME_BLOCK * 13 + 4] + 10 * regs[TIME_BLOCK * 13 + 5];
153  dayWeek = regs[TIME_BLOCK * 13 + 6];
154  days = regs[TIME_BLOCK * 13 + 7] + 10 * regs[TIME_BLOCK * 13 + 8] - 1;
155  months = regs[TIME_BLOCK * 13 + 9] + 10 * regs[TIME_BLOCK * 13 +10] - 1;
156  years = regs[TIME_BLOCK * 13 +11] + 10 * regs[TIME_BLOCK * 13 +12];
157  leapYear = regs[ALARM_BLOCK * 13 +11];
158 
159  if (!regs[ALARM_BLOCK * 13 + 10]) {
160  // 12 hours mode
161  if (hours >= 20) hours = (hours - 20) + 12;
162  }
163 }
164 
165 void RP5C01::time2Regs()
166 {
167  unsigned hours_ = hours;
168  if (!regs[ALARM_BLOCK * 13 + 10]) {
169  // 12 hours mode
170  if (hours >= 12) hours_ = (hours - 12) + 20;
171  }
172 
173  regs.write(TIME_BLOCK * 13 + 0, seconds % 10);
174  regs.write(TIME_BLOCK * 13 + 1, seconds / 10);
175  regs.write(TIME_BLOCK * 13 + 2, minutes % 10);
176  regs.write(TIME_BLOCK * 13 + 3, minutes / 10);
177  regs.write(TIME_BLOCK * 13 + 4, hours_ % 10);
178  regs.write(TIME_BLOCK * 13 + 5, hours_ / 10);
179  regs.write(TIME_BLOCK * 13 + 6, dayWeek);
180  regs.write(TIME_BLOCK * 13 + 7, (days+1) % 10); // 0-30 -> 1-31
181  regs.write(TIME_BLOCK * 13 + 8, (days+1) / 10); // 0-11 -> 1-12
182  regs.write(TIME_BLOCK * 13 + 9, (months+1) % 10);
183  regs.write(TIME_BLOCK * 13 + 10, (months+1) / 10);
184  regs.write(TIME_BLOCK * 13 + 11, years % 10);
185  regs.write(TIME_BLOCK * 13 + 12, years / 10);
186  regs.write(ALARM_BLOCK * 13 + 11, leapYear);
187 }
188 
189 static int daysInMonth(int month, unsigned leapYear)
190 {
191  const unsigned daysInMonths[12] = {
192  31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
193  };
194 
195  month %= 12;
196  return ((month == 1) && (leapYear == 0)) ? 29 : daysInMonths[month];
197 }
198 
199 void RP5C01::updateTimeRegs(EmuTime::param time)
200 {
201  if (modeSetting.getEnum() == EMUTIME) {
202  // sync with EmuTime, perfect emulation
203  auto elapsed = unsigned(reference.getTicksTill(time));
204  reference.advance(time);
205 
206  // in test mode increase sec/min/.. at a rate of 16384Hz
207  fraction += (modeReg & MODE_TIMERENABLE) ? elapsed : 0;
208  unsigned carrySeconds = (testReg & TEST_SECONDS)
209  ? elapsed : fraction / FREQ;
210  seconds += carrySeconds;
211  unsigned carryMinutes = (testReg & TEST_MINUTES)
212  ? elapsed : seconds / 60;
213  minutes += carryMinutes;
214  hours += minutes / 60;
215  unsigned carryDays = (testReg & TEST_DAYS)
216  ? elapsed : hours / 24;
217  days += carryDays;
218  dayWeek += carryDays;
219  while (days >= daysInMonth(months, leapYear)) {
220  // TODO not correct because leapYear is not updated
221  // is only triggered when we update several months
222  // at a time (but might happen in TEST_DAY mode)
223  days -= daysInMonth(months, leapYear);
224  months++;
225  }
226  unsigned carryYears = (testReg & TEST_YEARS)
227  ? elapsed : unsigned(months / 12);
228  years += carryYears;
229  leapYear += carryYears;
230 
231  fraction %= FREQ;
232  seconds %= 60;
233  minutes %= 60;
234  hours %= 24;
235  dayWeek %= 7;
236  months %= 12;
237  years %= 100;
238  leapYear %= 4;
239 
240  time2Regs();
241  } else {
242  // sync with host clock
243  // writes to time, test and reset registers have no effect
244  initializeTime();
245  }
246 }
247 
248 void RP5C01::resetAlarm()
249 {
250  for (unsigned i = 2; i <= 8; ++i) {
251  regs.write(ALARM_BLOCK * 13 + i, 0);
252  }
253 }
254 
255 template<typename Archive>
256 void RP5C01::serialize(Archive& ar, unsigned /*version*/)
257 {
258  ar.serialize("reference", reference,
259  "fraction", fraction,
260  "seconds", seconds,
261  "minutes", minutes,
262  "hours", hours,
263  "dayWeek", dayWeek,
264  "years", years,
265  "leapYear", leapYear,
266  "days", days,
267  "months", months,
268  "modeReg", modeReg,
269  "testReg", testReg,
270  "resetReg", resetReg);
271 }
273 
274 } // namespace openmsx
openmsx::EnumSetting
Definition: Reactor.hh:54
openmsx::Clock::getTicksTill
constexpr unsigned getTicksTill(EmuTime::param e) const
Calculate the number of ticks for this clock until the given time.
Definition: Clock.hh:58
openmsx::RP5C01::reset
void reset(EmuTime::param time)
Definition: RP5C01.cc:57
openmsx::MODE_BLOKSELECT
constexpr nibble MODE_BLOKSELECT
Definition: RP5C01.cc:19
openmsx::CommandController
Definition: CommandController.hh:17
serialize.hh
openmsx::RP5C01::RP5C01
RP5C01(CommandController &commandController, SRAM &regs, EmuTime::param time, const std::string &name)
Definition: RP5C01.cc:40
openmsx::RESET_REG
constexpr nibble RESET_REG
Definition: RP5C01.cc:14
openmsx::RP5C01::serialize
void serialize(Archive &ar, unsigned version)
Definition: RP5C01.cc:256
openmsx::MODE_TIMERENABLE
constexpr nibble MODE_TIMERENABLE
Definition: RP5C01.cc:21
openmsx::Date::days
const char *const days[7]
Definition: Date.cc:7
t
TclObject t
Definition: TclObject_test.cc:264
openmsx::EnumSetting::getEnum
T getEnum() const noexcept
Definition: EnumSetting.hh:92
openmsx::RP5C01
Definition: RP5C01.hh:19
openmsx::RESET_FRACTION
constexpr nibble RESET_FRACTION
Definition: RP5C01.cc:29
openmsx::SRAM
Definition: SRAM.hh:11
openmsx::RP5C01::REALTIME
Definition: RP5C01.hh:22
openmsx::TEST_SECONDS
constexpr nibble TEST_SECONDS
Definition: RP5C01.cc:23
RP5C01.hh
openmsx::RP5C01::EMUTIME
Definition: RP5C01.hh:22
openmsx::RP5C01::readPort
nibble readPort(nibble port, EmuTime::param time)
Definition: RP5C01.cc:65
openmsx::nibble
uint8_t nibble
4 bit integer
Definition: openmsx.hh:23
INSTANTIATE_SERIALIZE_METHODS
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
Definition: serialize.hh:981
openmsx::TEST_REG
constexpr nibble TEST_REG
Definition: RP5C01.cc:13
openmsx::SRAM::write
void write(unsigned addr, byte value)
Definition: SRAM.cc:67
openmsx::TEST_DAYS
constexpr nibble TEST_DAYS
Definition: RP5C01.cc:25
openmsx::ALARM_BLOCK
constexpr nibble ALARM_BLOCK
Definition: RP5C01.cc:17
SRAM.hh
openmsx::MODE_REG
constexpr nibble MODE_REG
Definition: RP5C01.cc:12
openmsx::RP5C01::peekPort
nibble peekPort(nibble port) const
Definition: RP5C01.cc:82
openmsx::Date::months
const char *const months[12]
Definition: Date.cc:11
openmsx::Clock::advance
constexpr void advance(EmuTime::param e)
Advance this clock in time until the last tick which is not past the given time.
Definition: Clock.hh:110
openmsx::RP5C01::RTCMode
RTCMode
Definition: RP5C01.hh:22
openmsx::RESET_ALARM
constexpr nibble RESET_ALARM
Definition: RP5C01.cc:28
openmsx::TIME_BLOCK
constexpr nibble TIME_BLOCK
Definition: RP5C01.cc:16
openmsx::mask
constexpr nibble mask[4][13]
Definition: RP5C01.cc:33
openmsx::RP5C01::writePort
void writePort(nibble port, nibble value, EmuTime::param time)
Definition: RP5C01.cc:99
openmsx::TEST_YEARS
constexpr nibble TEST_YEARS
Definition: RP5C01.cc:26
openmsx::MODE_ALARMENABLE
constexpr nibble MODE_ALARMENABLE
Definition: RP5C01.cc:20
openmsx
Thanks to enen for testing this on a real cartridge:
Definition: Autofire.cc:5
openmsx::TEST_MINUTES
constexpr nibble TEST_MINUTES
Definition: RP5C01.cc:24