OXYGENHUB
◆ main openmsx / src / memory / ReproCartridgeV1.cc
6.9 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
e8555c89 Andy McCall 1
#include "ReproCartridgeV1.hh"
2
3
#include "DummyAY8910Periphery.hh"
4
#include "MSXCPUInterface.hh"
5
#include "serialize.hh"
6
7
#include "narrow.hh"
8
9
#include <array>
10
11
12
/******************************************************************************
13
 * DOCUMENTATION AS PROVIDED BY MANUEL PAZOS, WHO DEVELOPED THE CARTRIDGE     *
14
 ******************************************************************************
15
16
   Repro Cartridge version 1 is similar to Konami Ultimate Collection. It
17
   uses the same flashROM, SCC/SCC+. But it only supports Konami SCC mapper.
18
19
   Released were cartridges with the following content (at least): only Metal
20
   Gear, only Metal Gear 2
21
22
[REGISTER (#7FFF)]
23
If it contains value 0x50, the flash is writable and the mapper is disabled.
24
Otherwise, the mapper is enabled and the flash is readonly.
25
26
- Mapper supports 4 different ROMs of 2MB each, with the KonamiSCC mapper
27
- Cartridge has a PSG at 0x10, write only
28
- On I/O port 0x13 the 2MB block can be selected (default 0, so up to 3)
29
30
******************************************************************************/
31
32
namespace openmsx {
33
34
ReproCartridgeV1::ReproCartridgeV1(
35
		DeviceConfig& config, Rom&& rom_)
36
	: MSXRom(config, std::move(rom_))
37
	, flash(rom, AmdFlashChip::M29W640GB, {}, config)
38
	, scc("ReproCartV1 SCC", config, getCurrentTime(), SCC::Mode::Compatible)
39
	, psg("ReproCartV1 PSG", DummyAY8910Periphery::instance(), config,
40
	      getCurrentTime())
41
{
42
	// adjust PSG volume, see details in https://github.com/openMSX/openMSX/issues/1934
43
	// note: this is a theoretical value. The actual relative volume should be measured!
44
	psg.setSoftwareVolume(21000.0f/9000.0f, getCurrentTime());
45
46
	powerUp(getCurrentTime());
47
	auto& cpuInterface = getCPUInterface();
48
	for (auto port : {0x10, 0x11, 0x13}) {
49
		cpuInterface.register_IO_Out(narrow_cast<byte>(port), this);
50
	}
51
}
52
53
ReproCartridgeV1::~ReproCartridgeV1()
54
{
55
	auto& cpuInterface = getCPUInterface();
56
	for (auto port : {0x10, 0x11, 0x13}) {
57
		cpuInterface.unregister_IO_Out(narrow_cast<byte>(port), this);
58
	}
59
}
60
61
void ReproCartridgeV1::powerUp(EmuTime time)
62
{
63
	scc.powerUp(time);
64
	reset(time);
65
}
66
67
void ReproCartridgeV1::reset(EmuTime time)
68
{
69
	flashRomWriteEnabled = false;
70
	mainBankReg = 0;
71
	sccMode = 0;
72
	ranges::iota(bankRegs, byte(0));
73
74
	scc.reset(time);
75
	psgLatch = 0;
76
	psg.reset(time);
77
78
	flash.reset();
79
80
	invalidateDeviceRCache(); // flush all to be sure
81
}
82
83
unsigned ReproCartridgeV1::getFlashAddr(unsigned addr) const
84
{
85
	unsigned page8kB = (addr >> 13) - 2;
86
	if (page8kB >= 4) return unsigned(-1); // outside [0x4000, 0xBFFF]
87
88
	byte bank = bankRegs[page8kB]; // 2MB max
89
	return (mainBankReg << 21) | (bank << 13) | (addr & 0x1FFF);
90
}
91
92
// Note: implementation (mostly) copied from KUC
93
bool ReproCartridgeV1::isSCCAccess(uint16_t addr) const
94
{
95
	if (sccMode & 0x10) return false;
96
97
	if (addr & 0x0100) {
98
		// Address bit 8 must be zero, this is different from a real
99
		// SCC/SCC+. According to Manuel Pazos this is a leftover from
100
		// an earlier version that had 2 SCCs: the SCC on the left or
101
		// right channel reacts when address bit 8 is respectively 0/1.
102
		return false;
103
	}
104
105
	if (sccMode & 0x20) {
106
		// SCC+   range: 0xB800..0xBFFF,  excluding 0xBFFE-0xBFFF
107
		return  (bankRegs[3] & 0x80)          && (0xB800 <= addr) && (addr < 0xBFFE);
108
	} else {
109
		// SCC    range: 0x9800..0x9FFF,  excluding 0x9FFE-0x9FFF
110
		return ((bankRegs[2] & 0x3F) == 0x3F) && (0x9800 <= addr) && (addr < 0x9FFE);
111
	}
112
}
113
114
byte ReproCartridgeV1::readMem(uint16_t addr, EmuTime time)
115
{
116
	if (isSCCAccess(addr)) {
117
		return scc.readMem(narrow_cast<uint8_t>(addr & 0xFF), time);
118
	}
119
120
	unsigned flashAddr = getFlashAddr(addr);
121
	return (flashAddr != unsigned(-1))
122
		? flash.read(flashAddr, time)
123
		: 0xFF; // unmapped read
124
}
125
126
byte ReproCartridgeV1::peekMem(uint16_t addr, EmuTime time) const
127
{
128
	if (isSCCAccess(addr)) {
129
		return scc.peekMem(narrow_cast<uint8_t>(addr & 0xFF), time);
130
	}
131
132
	unsigned flashAddr = getFlashAddr(addr);
133
	return (flashAddr != unsigned(-1))
134
		? flash.peek(flashAddr, time)
135
		: 0xFF; // unmapped read
136
}
137
138
const byte* ReproCartridgeV1::getReadCacheLine(uint16_t addr) const
139
{
140
	if (isSCCAccess(addr)) return nullptr;
141
142
	unsigned flashAddr = getFlashAddr(addr);
143
	return (flashAddr != unsigned(-1))
144
		? flash.getReadCacheLine(flashAddr)
145
		: unmappedRead.data();
146
}
147
148
void ReproCartridgeV1::writeMem(uint16_t addr, byte value, EmuTime time)
149
{
150
	unsigned page8kB = (addr >> 13) - 2;
151
	if (page8kB >= 4) return; // outside [0x4000, 0xBFFF]
152
153
	// There are several overlapping functional regions in the address
154
	// space. A single write can trigger behaviour in multiple regions. In
155
	// other words there's no priority amongst the regions where a higher
156
	// priority region blocks the write from the lower priority regions.
157
	// This only goes for places where the flash is 'seen', so not for the
158
	// SCC registers
159
160
	if (isSCCAccess(addr)) {
161
		scc.writeMem(narrow_cast<uint8_t>(addr & 0xFF), value, time);
162
		return; // write to SCC blocks write to other functions
163
	}
164
165
	// address is calculated before writes to other regions take effect
166
	unsigned flashAddr = getFlashAddr(addr);
167
168
	// Main mapper register
169
	if (addr == 0x7FFF) {
170
		flashRomWriteEnabled = (value == 0x50);
171
		invalidateDeviceRCache(); // flush all to be sure
172
	}
173
174
	if (!flashRomWriteEnabled) {
175
		// Konami-SCC
176
		if ((addr & 0x1800) == 0x1000) {
177
			// [0x5000,0x57FF] [0x7000,0x77FF]
178
			// [0x9000,0x97FF] [0xB000,0xB7FF]
179
			bankRegs[page8kB] = value;
180
			invalidateDeviceRCache(0x4000 + 0x2000 * page8kB, 0x2000);
181
		}
182
183
		// SCC mode register
184
		if ((addr & 0xFFFE) == 0xBFFE) {
185
			sccMode = value;
186
			scc.setMode((value & 0x20) ? SCC::Mode::Plus
187
			                           : SCC::Mode::Compatible);
188
			invalidateDeviceRCache(0x9800, 0x800);
189
			invalidateDeviceRCache(0xB800, 0x800);
190
		}
191
	} else {
192
		if (flashAddr != unsigned(-1)) {
193
			flash.write(flashAddr, value, time);
194
		}
195
	}
196
}
197
198
byte* ReproCartridgeV1::getWriteCacheLine(uint16_t addr)
199
{
200
	return ((0x4000 <= addr) && (addr < 0xC000))
201
	       ? nullptr        // [0x4000,0xBFFF] isn't cacheable
202
	       : unmappedWrite.data();
203
}
204
205
void ReproCartridgeV1::writeIO(uint16_t port, byte value, EmuTime time)
206
{
207
	switch (port & 0xFF)
208
	{
209
		case 0x10:
210
			psgLatch = value & 0x0F;
211
			break;
212
		case 0x11:
213
			psg.writeRegister(psgLatch, value, time);
214
			break;
215
		case 0x13:
216
			mainBankReg = value & 3;
217
			invalidateDeviceRCache(); // flush all to be sure
218
			break;
219
		default: UNREACHABLE;
220
	}
221
}
222
223
template<typename Archive>
224
void ReproCartridgeV1::serialize(Archive& ar, unsigned /*version*/)
225
{
226
	// skip MSXRom base class
227
	ar.template serializeBase<MSXDevice>(*this);
228
229
	ar.serialize("flash",       flash,
230
	             "scc",         scc,
231
	             "psg",         psg,
232
	             "psgLatch",    psgLatch,
233
	             "flashRomWriteEnabled", flashRomWriteEnabled,
234
	             "mainBankReg", mainBankReg,
235
	             "sccMode",     sccMode,
236
	             "bankRegs",    bankRegs);
237
}
238
INSTANTIATE_SERIALIZE_METHODS(ReproCartridgeV1);
239
REGISTER_MSXDEVICE(ReproCartridgeV1, "ReproCartridgeV1");
240
241
} // namespace openmsx