OXYGENHUB
◆ main openmsx / src / sound / DalSoRiR2.cc
7.7 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
e8555c89 Andy McCall 1
#include "DalSoRiR2.hh"
2
3
#include "MSXCPUInterface.hh"
4
#include "serialize.hh"
5
6
#include "ranges.hh"
7
8
namespace openmsx {
9
10
// bit(s) of the memory bank selection registers regBank[n]
11
static constexpr byte REG_BANK_SEGMENT_MASK = (1 << 5) - 1; // 5-bits
12
13
// bit(s) of the frame selection registers regFrame[n]
14
static constexpr byte DISABLE = 1 << 7;
15
static constexpr byte REG_FRAME_SEGMENT_MASK = (1 << 3) - 1; // 3-bits
16
17
// bits of the configuration register regCfg
18
static constexpr byte ENA_S0 = 1 << 5;
19
static constexpr byte ENA_FW = 1 << 4;
20
static constexpr byte ENA_C4 = 1 << 1;
21
static constexpr byte ENA_C0 = 1 << 0;
22
23
DalSoRiR2::DalSoRiR2(DeviceConfig& config)
24
	: MSXDevice(config)
25
	, ymf278b(getName(),
26
	          size_t(4096 * 1024), // 4MB RAM
27
	          config,
28
	          [this](bool mode0, std::span<const uint8_t> rom, std::span<const uint8_t> ram,
29
	                std::span<YMF278::Block128, 32> memPtrs) {
30
		     setupMemPtrs(mode0, rom, ram, memPtrs);
31
		  }, getCurrentTime())
32
	, sram(config, getName() + " RAM", "DalSoRi R2 RAM", 0x8000)
33
	, flash(getName() + " flash", AmdFlashChip::SST39SF010, {}, config, "flash")
34
	, dipSwitchBDIS(getCommandController(),
35
		getName() + " DIP switch BDIS",
36
		"Controls the BDIS DIP switch position. ON = Disable BIOS (flash memory access)", false)
37
	, dipSwitchMCFG(getCommandController(),
38
		getName() + " DIP switch MCFG",
39
		"Controls the MCFG DIP switch position. ON = Enable sample RAM instead of sample ROM on reset.", false)
40
	, dipSwitchIO_C0(getCommandController(),
41
		getName() + " DIP switch IO/C0",
42
		"Controls the IO/C0 DIP switch position. ON = Enable I/O addres C0H~C3H on reset. This is the MSX-AUDIO compatible I/O port range.", true)
43
	, dipSwitchIO_C4(getCommandController(),
44
		getName() + " DIP switch IO/C4",
45
		"Controls the IO/C4 DIP switch position. ON = Enable I/O addres C4H~C7H on reset. This is the MoonSound compatible I/O port range", true)
46
	, biosDisable(dipSwitchBDIS.getBoolean())
47
{
48
	dipSwitchBDIS.attach(*this);
49
	powerUp(getCurrentTime());
50
}
51
52
DalSoRiR2::~DalSoRiR2()
53
{
54
	setRegCfg(0); // unregister any FM I/O ports
55
	dipSwitchBDIS.detach(*this);
56
}
57
58
void DalSoRiR2::setupMemPtrs(
59
	bool mode0,
60
	std::span<const uint8_t> rom,
61
	std::span<const uint8_t> ram,
62
	std::span<YMF278::Block128, 32> memPtrs) const
63
{
64
	// /MCS0: connected to either a 2MB ROM or a 2MB RAM chip (dynamically
65
	//        selectable via software)
66
	// /MCS1: connected to a 2MB RAM chip
67
	// /MCS2../MCS9: unused
68
	static constexpr auto k128 = YMF278::k128;
69
70
	// first 2MB, either ROM or RAM, both mode0 and mode1
71
	bool enableRam = regCfg & ENA_S0;
72
	auto mem0 = enableRam ? std::span<const uint8_t>{ram} : std::span<const uint8_t>{rom};
73
	for (auto i : xrange(16)) {
74
		memPtrs[i] = subspan<k128>(mem0, i * k128);
75
	}
76
	// second 2MB
77
	if (mode0) [[likely]] {
78
		// mode 0: RAM
79
		for (auto i : xrange(16, 32)) {
80
			memPtrs[i] = subspan<k128>(ram, i * k128);
81
		}
82
	} else {
83
		// mode 1: unmapped (normally shouldn't be used on DalSoRiR2)
84
		// Note: in this mode accessing region [0x00'0000, 0x10'0000) activates
85
		// both /MCS0 and /MCS1, thus two chips (ROM+RAM or RAM+RAM) get activated
86
		// simultaneously. That might cause problems on the real hardware. Here we
87
		// emulate it by ignoring the problem, as-if only /MCS0 was active.
88
		for (auto i : xrange(16, 32)) {
89
			memPtrs[i] = YMF278::Block128{};
90
		}
91
	}
92
}
93
94
void DalSoRiR2::powerUp(EmuTime time)
95
{
96
	ymf278b.powerUp(time);
97
	reset(time);
98
}
99
100
void DalSoRiR2::reset(EmuTime time)
101
{
102
	ymf278b.reset(time);
103
	flash.reset();
104
105
	ranges::iota(regBank, byte(0));
106
	ranges::iota(regFrame, byte(0));
107
108
	setRegCfg((dipSwitchMCFG. getBoolean() ? ENA_S0 : 0) |
109
	          (dipSwitchIO_C0.getBoolean() ? ENA_C0 : 0) |
110
	          (dipSwitchIO_C4.getBoolean() ? ENA_C4 : 0));
111
}
112
113
void DalSoRiR2::setRegCfg(byte value)
114
{
115
	auto registerHelper = [this](bool enable, byte base) {
116
		if (enable) {
117
			getCPUInterface().  register_IO_InOut_range(base, 4, this);
118
		} else {
119
			getCPUInterface().unregister_IO_InOut_range(base, 4, this);
120
		}
121
	};
122
	if ((value ^ regCfg) & ENA_C0) { // enable C0 changed
123
		registerHelper((value & ENA_C0) != 0, 0xC0);
124
	}
125
	if ((value ^ regCfg) & ENA_C4) { // enable C4 changed
126
		registerHelper((value & ENA_C4) != 0, 0xC4);
127
	}
128
129
	if ((value ^ regCfg) & ENA_FW) {
130
		invalidateDeviceRWCache();
131
	}
132
133
	regCfg = value;
134
135
	ymf278b.setupMemoryPointers(); // ENA_S0 might have changed
136
}
137
138
byte DalSoRiR2::readIO(uint16_t port, EmuTime time)
139
{
140
	return ymf278b.readIO(port, time);
141
}
142
143
byte DalSoRiR2::peekIO(uint16_t port, EmuTime time) const
144
{
145
	return ymf278b.peekIO(port, time);
146
}
147
148
void DalSoRiR2::writeIO(uint16_t port, byte value, EmuTime time)
149
{
150
	ymf278b.writeIO(port, value, time);
151
}
152
153
std::optional<size_t> DalSoRiR2::getSramAddr(uint16_t addr) const
154
{
155
	auto get = [&](byte frame) -> size_t {
156
		return (addr & 0x0FFF) + (frame & REG_FRAME_SEGMENT_MASK) * 0x1000;
157
	};
158
159
	if ((0x3000 <= addr) && (addr < 0x4000)) {
160
		// SRAM frame 0
161
		return get(0);
162
	} else if ((0x7000 <= addr) && (addr < 0x8000) && ((regFrame[0] & DISABLE) == 0)) {
163
		// SRAM frame 1
164
		return get(regFrame[0]);
165
	} else if ((0xB000 <= addr) && (addr < 0xC000) && ((regFrame[1] & DISABLE) == 0)) {
166
		// SRAM frame 2
167
		return get(regFrame[1]);
168
	} else {
169
		return {};
170
	}
171
}
172
173
unsigned DalSoRiR2::getFlashAddr(uint16_t addr) const
174
{
175
	auto page = addr >> 14;
176
	auto bank = regBank[page] & REG_BANK_SEGMENT_MASK;
177
	auto offset = addr & 0x3FFF;
178
	return 0x4000 * bank + offset;
179
}
180
181
byte DalSoRiR2::readMem(uint16_t addr, EmuTime time)
182
{
183
	if (auto r = getSramAddr(addr)) {
184
		return sram[*r];
185
	} else if (!biosDisable) {
186
		return flash.read(getFlashAddr(addr), time);
187
	}
188
	return 0xFF;
189
}
190
191
byte DalSoRiR2::peekMem(uint16_t addr, EmuTime time) const
192
{
193
	if (auto r = getSramAddr(addr)) {
194
		return sram[*r];
195
	} else if (!biosDisable) {
196
		return flash.peek(getFlashAddr(addr), time);
197
	}
198
	return 0xFF;
199
}
200
201
const byte* DalSoRiR2::getReadCacheLine(uint16_t start) const
202
{
203
	if (auto r = getSramAddr(start)) {
204
		return &sram[*r];
205
	} else if (!biosDisable) {
206
		return flash.getReadCacheLine(getFlashAddr(start));
207
	}
208
	return unmappedRead.data();
209
}
210
211
void DalSoRiR2::writeMem(uint16_t addr, byte value, EmuTime time)
212
{
213
	if (auto r = getSramAddr(addr)) {
214
		sram[*r] = value;
215
	} else if ((0x6000 <= addr) && (addr < 0x6400)) {
216
		auto bank = (addr >> 8) & 3;
217
		regBank[bank] = value;
218
		invalidateDeviceRWCache(bank * 0x4000, 0x4000);
219
	} else if ((0x6500 <= addr) && (addr < 0x6700)) {
220
		auto frame = (addr >> 9) & 1; // [0x6500,0x65FF] -> 0, [0x6600,0x66FF] -> 1
221
		regFrame[frame] = value;
222
		invalidateDeviceRWCache(0x7000 + frame * 0x4000, 0x1000);
223
	} else if ((0x6700 <= addr) && (addr < 0x6800)) {
224
		setRegCfg(value);
225
	} else if (!biosDisable && ((regCfg & ENA_FW) != 0)) {
226
		flash.write(getFlashAddr(addr), value, time);
227
	}
228
}
229
230
byte* DalSoRiR2::getWriteCacheLine(uint16_t start)
231
{
232
	if (auto r = getSramAddr(start)) {
233
		return &sram[*r];
234
	} else {
235
		// bank/frame/cfg registers or flash, not cacheable
236
		return nullptr;
237
	}
238
}
239
240
void DalSoRiR2::update(const Setting& setting) noexcept
241
{
242
	assert(&setting == &dipSwitchBDIS); (void)setting;
243
	auto newBiosDisable = dipSwitchBDIS.getBoolean();
244
	if (biosDisable != newBiosDisable) {
245
		biosDisable = newBiosDisable;
246
		invalidateDeviceRWCache();
247
	}
248
}
249
250
// version 1: initial version
251
template<typename Archive>
252
void DalSoRiR2::serialize(Archive& ar, unsigned /*version*/)
253
{
254
	ar.template serializeBase<MSXDevice>(*this);
255
	auto backupRegCfg = regCfg;
256
	ar.serialize("ymf278b",   ymf278b,
257
	             "sram",      sram,
258
	             "regBank",   regBank,
259
	             "regFrame",  regFrame,
260
	             "regCfg",    backupRegCfg);
261
	if constexpr (Archive::IS_LOADER) {
262
		setRegCfg(backupRegCfg); // register ports and setupMemoryPointers()
263
	}
264
	// no need to serialize biosDisable
265
}
266
INSTANTIATE_SERIALIZE_METHODS(DalSoRiR2);
267
REGISTER_MSXDEVICE(DalSoRiR2, "DalSoRiR2");
268
269
} // namespace openmsx