OXYGENHUB
◆ main openmsx / src / memory / Yamanooto.cc
9.4 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#include "Yamanooto.hh"
2
3
#include "DummyAY8910Periphery.hh"
4
#include "MSXCPUInterface.hh"
5
6
#include "narrow.hh"
7
#include "outer.hh"
8
#include "ranges.hh"
9
#include "unreachable.hh"
10
11
#include <cassert>
12
#include <ranges>
13
14
// TODO:
15
// * HOME/DEL boot keys.  How are these handled?
16
// * SCC stereo mode.
17
18
namespace openmsx {
19
20
static constexpr uint16_t ENAR = 0x7FFF;
21
static constexpr uint8_t  REGEN  = 0x01;
22
static constexpr uint8_t  WREN   = 0x10;
23
static constexpr uint16_t OFFR = 0x7FFE;
24
static constexpr uint16_t CFGR = 0x7FFD;
25
static constexpr uint8_t  MDIS   = 0x01;
26
static constexpr uint8_t  ECHO   = 0x02;
27
static constexpr uint8_t  ROMDIS = 0x04;
28
static constexpr uint8_t  K4     = 0x08;
29
static constexpr uint8_t  SUBOFF = 0x30;
30
// undocumented stuff
31
static constexpr uint8_t  FPGA_EN   = 0x40; // write: 1 -> enable communication with FPGA commands ???
32
static constexpr uint8_t  FPGA_WAIT = 0x80; // read: ready signal ??? (1 = ready)
33
static constexpr uint16_t FPGA_REG  = 0x7FFC; // bi-direction 8-bit communication channel
34
35
Yamanooto::Yamanooto(DeviceConfig& config, Rom&& rom_)
36
	: MSXRom(config, std::move(rom_))
37
	, romBlockDebug(*this)
38
	, flash(rom, AmdFlashChip::S29GL064N90TFI04, {}, config)
39
	, scc(getName() + " SCC", config, getCurrentTime(), SCC::Mode::Compatible)
40
	, psg(getName() + " PSG", DummyAY8910Periphery::instance(), config, getCurrentTime())
41
{
42
	// Tests show that the PSG has higher volume than the scc. See
43
	//   https://www.msx.org/forum/msx-talk/openmsx/openmsx-cartridge-type?page=2#comment-470014
44
	//   https://msx.pics/images/2024/12/23/yamanooto_volume_comparison.png
45
	psg.setSoftwareVolume(3.0f, getCurrentTime());
46
47
	scc.setBalance(0,  0.00f);
48
	scc.setBalance(1, -0.67f);
49
	scc.setBalance(2,  0.67f);
50
	scc.setBalance(3, -0.67f);
51
	scc.setBalance(4,  0.67f);
52
	scc.postSetBalance();
53
54
	getCPUInterface().register_IO_Out_range(0x10, 2, this);
55
	powerUp(getCurrentTime());
56
}
57
58
Yamanooto::~Yamanooto()
59
{
60
	writeConfigReg(0); // unregister A0-A2 ports
61
	getCPUInterface().unregister_IO_Out_range(0x10, 2, this);
62
}
63
64
void Yamanooto::powerUp(EmuTime time)
65
{
66
	scc.powerUp(time);
67
	reset(time);
68
}
69
70
void Yamanooto::reset(EmuTime time)
71
{
72
	// TODO is offsetReg changed by reset?
73
	// TODO are all bits in configReg set to zero?   (also bit 0,1)
74
	enableReg = 0;
75
	offsetReg = 0;
76
	writeConfigReg(0);
77
	fpgaFsm = 0;
78
79
	ranges::iota(bankRegs, uint16_t(0));
80
	ranges::iota(rawBanks, uint8_t(0));
81
	sccMode = 0;
82
	scc.reset(time);
83
84
	psgLatch = 0;
85
	psg.reset(time);
86
87
	flash.reset();
88
89
	invalidateDeviceRCache(); // flush all to be sure
90
}
91
92
void Yamanooto::writeConfigReg(byte value)
93
{
94
	if ((value ^ configReg) & ECHO) {
95
		if (value & ECHO) {
96
			getCPUInterface().register_IO_Out_range(0xA0, 2, this);
97
		} else {
98
			getCPUInterface().unregister_IO_Out_range(0xA0, 2, this);
99
		}
100
	}
101
	configReg = value;
102
}
103
104
bool Yamanooto::isSCCAccess(uint16_t address) const
105
{
106
	if (configReg & K4) return false; // Konami4 doesn't have SCC
107
108
	// This uses the raw bank registers (not adjusted with offset register),
109
	//   see: https://github.com/openMSX/openMSX/issues/1992
110
	if (sccMode & 0x20) {
111
		// SCC+   range: 0xB800..0xBFFF,  excluding 0xBFFE-0xBFFF
112
		return  (rawBanks[3] & 0x80)          && (0xB800 <= address) && (address < 0xBFFE);
113
	} else {
114
		// SCC    range: 0x9800..0x9FFF
115
		return ((rawBanks[2] & 0x3F) == 0x3F) && (0x9800 <= address) && (address < 0xA000);
116
	}
117
}
118
119
unsigned Yamanooto::getFlashAddr(unsigned addr) const
120
{
121
	unsigned page8kB = (addr >> 13) - 2;
122
	assert(page8kB < 4); // must be inside [0x4000, 0xBFFF]
123
	auto bank = bankRegs[page8kB] & 0x3ff; // max 8MB
124
	return (bank << 13) | (addr & 0x1FFF);
125
}
126
127
[[nodiscard]] static uint16_t mirror(uint16_t address)
128
{
129
	if (address < 0x4000 || 0xC000 <= address) {
130
		// mirror 0x4000 <-> 0xc000   /   0x8000 <-> 0x0000
131
		address ^= 0x8000; // real hw probably just ignores upper address bit
132
	}
133
	assert((0x4000 <= address) && (address < 0xC000));
134
	return address;
135
}
136
137
static constexpr std::array<byte, 4 + 1> FPGA_ID = {
138
	0xFF, // idle
139
	0x1F, 0x23, 0x00, 0x00, // TODO check last 2
140
};
141
byte Yamanooto::peekMem(uint16_t address, EmuTime time) const
142
{
143
	address = mirror(address);
144
145
	// 0x7ffc-0x7fff
146
	if (FPGA_REG <= address && address <= ENAR && (enableReg & REGEN)) {
147
		switch (address) {
148
		case FPGA_REG:
149
			if (!(configReg & FPGA_EN)) return 0xFF; // TODO check this
150
			assert(fpgaFsm < 5);
151
			return FPGA_ID[fpgaFsm];
152
		case CFGR: return configReg | FPGA_WAIT; // not-busy
153
		case OFFR: return offsetReg;
154
		case ENAR: return enableReg;
155
		default: UNREACHABLE;
156
		}
157
	}
158
159
	// 0x9800-0x9FFF or 0xB800-0xBFFD
160
	if (isSCCAccess(address)) {
161
		return scc.peekMem(narrow_cast<uint8_t>(address & 0xFF), time);
162
	}
163
	return ((configReg & ROMDIS) == 0) ? flash.peek(getFlashAddr(address), time)
164
	                                   : 0xFF; // access to flash ROM disabled
165
}
166
167
byte Yamanooto::readMem(uint16_t address, EmuTime time)
168
{
169
	// 0x7ffc-0x7fff  (NOT mirrored)
170
	if (FPGA_REG <= address && address <= ENAR && (enableReg & REGEN)) {
171
		return peekMem(address, time);
172
	}
173
	address = mirror(address);
174
175
	// 0x9800-0x9FFF or 0xB800-0xBFFD
176
	if (isSCCAccess(address)) {
177
		return scc.readMem(narrow_cast<uint8_t>(address & 0xFF), time);
178
	}
179
	return ((configReg & ROMDIS) == 0) ? flash.read(getFlashAddr(address), time)
180
	                                   : 0xFF; // access to flash ROM disabled
181
}
182
183
const byte* Yamanooto::getReadCacheLine(uint16_t address) const
184
{
185
	if (((address & CacheLine::HIGH) == (ENAR & CacheLine::HIGH)) && (enableReg & REGEN)) {
186
		return nullptr; // Yamanooto registers, non-cacheable
187
	}
188
	address = mirror(address);
189
	if (isSCCAccess(address)) return nullptr;
190
	return ((configReg & ROMDIS) == 0) ? flash.getReadCacheLine(getFlashAddr(address))
191
	                                   : unmappedRead.data(); // access to flash ROM disabled
192
}
193
194
void Yamanooto::writeMem(uint16_t address, byte value, EmuTime time)
195
{
196
	// 0x7ffc-0x7fff  (NOT mirrored)
197
	if (FPGA_REG <= address && address <= ENAR) {
198
		if (address == ENAR) {
199
			enableReg = value;
200
		} else if (enableReg & REGEN) {
201
			switch (address) {
202
			case FPGA_REG:
203
				if (!(configReg & FPGA_EN)) break;
204
				switch (fpgaFsm) {
205
				case 0:
206
					if (value == 0x9f) { // read ID command ??
207
						++fpgaFsm;
208
					}
209
					break;
210
				case 1: case 2: case 3:
211
					++fpgaFsm;
212
					break;
213
				case 4:
214
					fpgaFsm = 0;
215
					break;
216
				default:
217
					UNREACHABLE;
218
				}
219
				break;
220
			case CFGR:
221
				writeConfigReg(value);
222
				break;
223
			case OFFR:
224
				offsetReg = value; // does NOT immediately switch bankRegs
225
				break;
226
			default:
227
				UNREACHABLE;
228
			}
229
		}
230
		invalidateDeviceRCache();
231
	}
232
	address = mirror(address);
233
	unsigned page8kB = (address >> 13) - 2;
234
235
	if (enableReg & WREN) {
236
		// write to flash rom
237
		if (!(configReg & ROMDIS)) { // disabled?
238
			flash.write(getFlashAddr(address), value, time);
239
			invalidateDeviceRCache(); // needed ?
240
		}
241
	} else {
242
		auto invalidateCache = [&](unsigned start, unsigned size) {
243
			invalidateDeviceRCache(start, size);
244
			invalidateDeviceRCache(start ^ 0x8000, size); // mirror
245
		};
246
		auto offset = (offsetReg << 2) | ((configReg & SUBOFF) >> 4);
247
		if (configReg & K4) {
248
			// Konami-4
249
			if (((configReg & MDIS) == 0) && (0x6000 <= address)) {
250
				// bank 0 is NOT switchable, but it keeps the
251
				// value it had before activating K4 mode
252
				bankRegs[page8kB] = (value + offset) & 0x3FF;
253
				rawBanks[page8kB] = value;
254
				invalidateCache(0x4000 + 0x2000 * page8kB, 0x2000);
255
			}
256
		} else {
257
			// 0x9800-0x9FFF or 0xB800-0xBFFD
258
			if (isSCCAccess(address)) {
259
				scc.writeMem(narrow_cast<uint8_t>(address & 0xFF), value, time);
260
			}
261
262
			// Konami-SCC
263
			if (((address & 0x1800) == 0x1000) && ((configReg & MDIS) == 0)) {
264
				// [0x5000,0x57FF] [0x7000,0x77FF]
265
				// [0x9000,0x97FF] [0xB000,0xB7FF]
266
				bankRegs[page8kB] = (value + offset) & 0x3FF;
267
				rawBanks[page8kB] = value;
268
				invalidateCache(0x4000 + 0x2000 * page8kB, 0x2000);
269
			}
270
271
			// SCC mode register
272
			if ((address & 0xFFFE) == 0xBFFE) {
273
				sccMode = value;
274
				scc.setMode((value & 0x20) ? SCC::Mode::Plus
275
				                           : SCC::Mode::Compatible);
276
				invalidateCache(0x9800, 0x800);
277
				invalidateCache(0xB800, 0x800);
278
			}
279
		}
280
	}
281
}
282
283
byte* Yamanooto::getWriteCacheLine(uint16_t /*address*/)
284
{
285
	return nullptr;
286
}
287
288
byte Yamanooto::peekIO(uint16_t /*port*/, EmuTime /*time*/) const
289
{
290
	return 0xff;
291
}
292
293
byte Yamanooto::readIO(uint16_t /*port*/, EmuTime /*time*/)
294
{
295
	// PSG is not readable
296
	return 0xff; // should never be called
297
}
298
299
void Yamanooto::writeIO(uint16_t port, byte value, EmuTime time)
300
{
301
	if (port & 1) { // 0x11 or 0xA1
302
		psg.writeRegister(psgLatch, value, time);
303
	} else { // 0x10 or 0xA0
304
		psgLatch = value & 0x0F;
305
	}
306
}
307
308
// version 1: initial version
309
// version 2: added 'rawBanks'
310
template<typename Archive>
311
void Yamanooto::serialize(Archive& ar, unsigned version)
312
{
313
	ar.serialize(
314
		"flash", flash,
315
		"scc", scc,
316
		"psg", psg,
317
		"bankRegs", bankRegs,
318
		"enableReg", enableReg,
319
		"offsetReg", offsetReg,
320
		"configReg", configReg,
321
		"sccMode", sccMode,
322
		"psgLatch", psgLatch,
323
		"fpgaFsm", fpgaFsm
324
	);
325
	if (ar.versionAtLeast(version, 2)) {
326
		ar.serialize("rawBanks", rawBanks);
327
	} else {
328
		// Could be wrong, but the best we can do?
329
		auto offset = (offsetReg << 2) | ((configReg & SUBOFF) >> 4);
330
		for (auto [b, r] : std::views::zip(bankRegs, rawBanks)) {
331
			r = uint8_t(b - offset);
332
		}
333
	}
334
}
335
INSTANTIATE_SERIALIZE_METHODS(Yamanooto);
336
REGISTER_MSXDEVICE(Yamanooto, "Yamanooto");
337
338
339
unsigned Yamanooto::Blocks::readExt(unsigned address)
340
{
341
	const auto& dev = OUTER(Yamanooto, romBlockDebug);
342
	address = mirror(narrow<uint16_t>(address));
343
	unsigned page8kB = (address >> 13) - 2;
344
	return dev.bankRegs[page8kB];
345
}
346
347
} // namespace openmsx