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
T getEnum() const noexcept
Definition: EnumSetting.hh:92
void serialize(Archive &ar, unsigned version)
Definition: RP5C01.cc:256
constexpr nibble TEST_MINUTES
Definition: RP5C01.cc:24
constexpr nibble RESET_REG
Definition: RP5C01.cc:14
void write(unsigned addr, byte value)
Definition: SRAM.cc:67
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
constexpr nibble RESET_FRACTION
Definition: RP5C01.cc:29
constexpr nibble MODE_TIMERENABLE
Definition: RP5C01.cc:21
nibble peekPort(nibble port) const
Definition: RP5C01.cc:82
const char *const days[7]
Definition: Date.cc:7
RP5C01(CommandController &commandController, SRAM &regs, EmuTime::param time, const std::string &name)
Definition: RP5C01.cc:40
const char *const months[12]
Definition: Date.cc:11
constexpr nibble ALARM_BLOCK
Definition: RP5C01.cc:17
void writePort(nibble port, nibble value, EmuTime::param time)
Definition: RP5C01.cc:99
void reset(EmuTime::param time)
Definition: RP5C01.cc:57
nibble readPort(nibble port, EmuTime::param time)
Definition: RP5C01.cc:65
constexpr nibble TEST_SECONDS
Definition: RP5C01.cc:23
constexpr nibble TEST_DAYS
Definition: RP5C01.cc:25
Thanks to enen for testing this on a real cartridge:
Definition: Autofire.cc:5
constexpr unsigned getTicksTill(EmuTime::param e) const
Calculate the number of ticks for this clock until the given time.
Definition: Clock.hh:58
constexpr nibble MODE_REG
Definition: RP5C01.cc:12
uint8_t nibble
4 bit integer
Definition: openmsx.hh:23
constexpr nibble RESET_ALARM
Definition: RP5C01.cc:28
constexpr nibble TEST_REG
Definition: RP5C01.cc:13
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
Definition: serialize.hh:981
constexpr nibble mask[4][13]
Definition: RP5C01.cc:33
constexpr nibble MODE_ALARMENABLE
Definition: RP5C01.cc:20
constexpr nibble TEST_YEARS
Definition: RP5C01.cc:26
constexpr nibble MODE_BLOKSELECT
Definition: RP5C01.cc:19
TclObject t
constexpr nibble TIME_BLOCK
Definition: RP5C01.cc:16