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
8namespace openmsx {
9
10// TODO ALARM is not implemented (not connected on MSX)
11// TODO 1Hz 16Hz output not implemented (not connected on MSX)
12
13constexpr nibble MODE_REG = 13;
14constexpr nibble TEST_REG = 14;
15constexpr nibble RESET_REG = 15;
16
17constexpr nibble TIME_BLOCK = 0;
18constexpr nibble ALARM_BLOCK = 1;
19
20constexpr nibble MODE_BLOKSELECT = 0x3;
21constexpr nibble MODE_ALARMENABLE = 0x4;
22constexpr nibble MODE_TIMERENABLE = 0x8;
23
24constexpr nibble TEST_SECONDS = 0x1;
25constexpr nibble TEST_MINUTES = 0x2;
26constexpr nibble TEST_DAYS = 0x4;
27constexpr nibble TEST_YEARS = 0x8;
28
29constexpr nibble RESET_ALARM = 0x1;
30constexpr nibble RESET_FRACTION = 0x2;
31
32
33// 0-bits are ignored on writing and return 0 on reading
34constexpr 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
41RP5C01::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,
50 {"EmuTime", RP5C01::EMUTIME},
51 {"RealTime", RP5C01::REALTIME}})
52 , reference(time)
53{
54 initializeTime();
55 reset(time);
56}
57
58void RP5C01::reset(EmuTime::param time)
59{
60 modeReg = MODE_TIMERENABLE;
61 testReg = 0;
62 resetReg = 0;
63 updateTimeRegs(time);
64}
65
66nibble 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
83nibble 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
100void 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
133void 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
149void 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
166void 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
190static 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
200void 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
261void RP5C01::resetAlarm()
262{
263 for (auto i : xrange(2, 9)) {
264 regs.write(ALARM_BLOCK * 13 + i, 0);
265 }
266}
267
268template<typename Archive>
269void 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:12
const char *const days[7]
Definition: Date.cc:8
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
STL namespace.
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
Definition: serialize.hh:1009
TemporaryString tmpStrCat(Ts &&... ts)
Definition: strCat.hh:617
constexpr auto xrange(T e)
Definition: xrange.hh:133