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