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