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 static const nibble MODE_REG = 13;
13 static const nibble TEST_REG = 14;
14 static const nibble RESET_REG = 15;
15 
16 static const nibble TIME_BLOCK = 0;
17 static const nibble ALARM_BLOCK = 1;
18 
19 static const nibble MODE_BLOKSELECT = 0x3;
20 static const nibble MODE_ALARMENABLE = 0x4;
21 static const nibble MODE_TIMERENABLE = 0x8;
22 
23 static const nibble TEST_SECONDS = 0x1;
24 static const nibble TEST_MINUTES = 0x2;
25 static const nibble TEST_DAYS = 0x4;
26 static const nibble TEST_YEARS = 0x8;
27 
28 static const nibble RESET_ALARM = 0x1;
29 static const nibble RESET_FRACTION = 0x2;
30 
31 
32 // 0-bits are ignored on writing and return 0 on reading
33 static const 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  ar.serialize("fraction", fraction);
260  ar.serialize("seconds", seconds);
261  ar.serialize("minutes", minutes);
262  ar.serialize("hours", hours);
263  ar.serialize("dayWeek", dayWeek);
264  ar.serialize("years", years);
265  ar.serialize("leapYear", leapYear);
266  ar.serialize("days", days);
267  ar.serialize("months", months);
268  ar.serialize("modeReg", modeReg);
269  ar.serialize("testReg", testReg);
270  ar.serialize("resetReg", resetReg);
271 }
273 
274 } // namespace openmsx
unsigned getTicksTill(EmuTime::param e) const
Calculate the number of ticks for this clock until the given time.
Definition: Clock.hh:58
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
void serialize(Archive &ar, unsigned version)
Definition: RP5C01.cc:256
void write(unsigned addr, byte value)
Definition: SRAM.cc:67
nibble peekPort(nibble port) const
Definition: RP5C01.cc:82
const char *const days[7]
Definition: Date.cc:9
RP5C01(CommandController &commandController, SRAM &regs, EmuTime::param time, const std::string &name)
Definition: RP5C01.cc:40
const char *const months[12]
Definition: Date.cc:13
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
Thanks to enen for testing this on a real cartridge:
Definition: Autofire.cc:5
uint8_t nibble
4 bit integer
Definition: openmsx.hh:23
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
Definition: serialize.hh:840
TclObject t