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