OXYGENHUB
◆ main openmsx / src / memory / MusicalMemoryMapper.cc
5.9 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
e8555c89 Andy McCall 1
#include "MusicalMemoryMapper.hh"
2
3
#include "serialize.hh"
4
5
#include "enumerate.hh"
6
#include "narrow.hh"
7
#include "xrange.hh"
8
9
namespace openmsx {
10
11
static constexpr byte MEM_ACCESS_ENABLED = 1 << 7;
12
static constexpr byte SOUND_PORT_ENABLED = 1 << 6;
13
static constexpr byte PORT_ACCESS_DISABLED = 1 << 5;
14
static constexpr byte UNUSED = 1 << 4;
15
static constexpr byte WRITE_PROTECT = 0x0F;
16
17
MusicalMemoryMapper::MusicalMemoryMapper(const DeviceConfig& config)
18
	: MSXMemoryMapperBase(config)
19
	, sn76489(config)
20
{
21
	reset(getCurrentTime());
22
}
23
24
void MusicalMemoryMapper::reset(EmuTime time)
25
{
26
	controlReg = 0x00;
27
28
	// MMM inits the page registers to 3, 2, 1, 0 instead of zeroes, so we
29
	// don't call the superclass implementation.
30
	for (auto [page, reg] : enumerate(registers)) {
31
		reg = byte(3 - page);
32
	}
33
34
	// Note: The actual SN76489AN chip does not have a reset pin. I assume
35
	//       MMM either powers off the chip on reset or suppresses its audio
36
	//       output. We instead keep the chip in a silent state until it is
37
	//       activated.
38
	sn76489.reset(time);
39
}
40
41
byte MusicalMemoryMapper::readIO(uint16_t port, EmuTime time)
42
{
43
	if (controlReg & PORT_ACCESS_DISABLED) {
44
		return 0xFF;
45
	} else {
46
		return MSXMemoryMapperBase::readIO(port, time);
47
	}
48
}
49
50
byte MusicalMemoryMapper::peekIO(uint16_t port, EmuTime time) const
51
{
52
	if (controlReg & PORT_ACCESS_DISABLED) {
53
		return 0xFF;
54
	} else {
55
		return MSXMemoryMapperBase::peekIO(port, time);
56
	}
57
}
58
59
void MusicalMemoryMapper::writeIO(uint16_t port, byte value, EmuTime time)
60
{
61
	if ((port & 0xFC) == 0xFC) {
62
		// Mapper port.
63
		if (!(controlReg & PORT_ACCESS_DISABLED)) {
64
			MSXMemoryMapperBase::writeIOImpl(port, value, time);
65
			invalidateDeviceRWCache(0x4000 * (port & 0x03), 0x4000);
66
		}
67
	} else if (port & 1) {
68
		// Sound chip.
69
		if (controlReg & SOUND_PORT_ENABLED) {
70
			sn76489.write(value, time);
71
		}
72
	} else {
73
		// Control port.
74
		// Only bit 7 of the control register is accessible through this port.
75
		// The documentation explicitly states that the sound chip port is still
76
		// accessible when port access is disabled, but this control port must
77
		// also remain accessible or the ROM2MMM loader won't work.
78
		updateControlReg((value & MEM_ACCESS_ENABLED) |
79
		                 (controlReg & ~MEM_ACCESS_ENABLED));
80
	}
81
}
82
83
bool MusicalMemoryMapper::registerAccessAt(uint16_t address) const
84
{
85
	if (controlReg & MEM_ACCESS_ENABLED) {
86
		if (controlReg & PORT_ACCESS_DISABLED) {
87
			// Note: I'm assuming the mirroring is still active when the
88
			//       address range is restricted, but this is unclear in
89
			//       the documentation and not tested on real hardware.
90
			if (controlReg & 0x08) {
91
				return 0x4000 <= address && address < 0x8000;
92
			} else {
93
				return 0x8000 <= address && address < 0xC000;
94
			}
95
		} else {
96
			return 0x4000 <= address && address < 0xC000;
97
		}
98
	} else {
99
		return false;
100
	}
101
}
102
103
int MusicalMemoryMapper::readReg(uint16_t address) const
104
{
105
	if (registerAccessAt(address)) {
106
		switch (address & 0xFF) {
107
		case 0x3C:
108
			return controlReg;
109
		case 0xFC:
110
		case 0xFD:
111
		case 0xFE:
112
		case 0xFF:
113
			return 0xC0 | getSelectedSegment(address & 0x03);
114
		}
115
	}
116
	return -1;
117
}
118
119
void MusicalMemoryMapper::updateControlReg(byte value)
120
{
121
	// Note: Bit 4 is unused, but I don't know its value when read.
122
	//       For now, I'll force it to 0.
123
	value &= ~UNUSED;
124
125
	byte change = value ^ controlReg;
126
	if (change) {
127
		// Pages for which write protect is toggled must be invalidated.
128
		byte invalidate = change & WRITE_PROTECT;
129
130
		// Invalidate pages for which register access changes.
131
		byte regAccessBefore = 0;
132
		for (auto page : xrange(4)) {
133
			regAccessBefore |= byte(registerAccessAt(narrow_cast<uint16_t>(0x4000 * page)) << page);
134
		}
135
		controlReg = value;
136
		byte regAccessAfter = 0;
137
		for (auto page : xrange(4)) {
138
			regAccessAfter |= byte(registerAccessAt(narrow_cast<uint16_t>(0x4000 * page)) << page);
139
		}
140
		invalidate |= regAccessBefore ^ regAccessAfter;
141
142
		for (auto page : xrange(4)) {
143
			if ((invalidate >> page) & 1) {
144
				invalidateDeviceRWCache(0x4000 * page, 0x4000);
145
			}
146
		}
147
	}
148
}
149
150
byte MusicalMemoryMapper::peekMem(uint16_t address, EmuTime time) const
151
{
152
	int reg = readReg(address);
153
	return reg >= 0 ? narrow<byte>(reg) : MSXMemoryMapperBase::peekMem(address, time);
154
}
155
156
byte MusicalMemoryMapper::readMem(uint16_t address, EmuTime time)
157
{
158
	int reg = readReg(address);
159
	return reg >= 0 ? narrow<byte>(reg) : MSXMemoryMapperBase::readMem(address, time);
160
}
161
162
void MusicalMemoryMapper::writeMem(uint16_t address, byte value, EmuTime time)
163
{
164
	if (registerAccessAt(address)) {
165
		switch (address & 0xFF) {
166
		case 0x3C:
167
			// When port access is disabled, memory access is automatically
168
			// enabled, according to the manual.
169
			if (value & PORT_ACCESS_DISABLED) {
170
				value |= MEM_ACCESS_ENABLED;
171
			}
172
			updateControlReg(value);
173
			return;
174
		case 0xFC:
175
		case 0xFD:
176
		case 0xFE:
177
		case 0xFF:
178
			MSXMemoryMapperBase::writeIOImpl(address & 0xFF, value, time);
179
			invalidateDeviceRWCache(0x4000 * (address & 0x03), 0x4000);
180
			return;
181
		}
182
	}
183
	if (!writeProtected(address)) {
184
		MSXMemoryMapperBase::writeMem(address, value, time);
185
	}
186
}
187
188
const byte* MusicalMemoryMapper::getReadCacheLine(uint16_t start) const
189
{
190
	if (controlReg & MEM_ACCESS_ENABLED) {
191
		if (0x4000 <= start && start < 0xC000) {
192
			return nullptr;
193
		}
194
	}
195
	return MSXMemoryMapperBase::getReadCacheLine(start);
196
}
197
198
byte* MusicalMemoryMapper::getWriteCacheLine(uint16_t start)
199
{
200
	if (controlReg & MEM_ACCESS_ENABLED) {
201
		if (0x4000 <= start && start < 0xC000) {
202
			return nullptr;
203
		}
204
	}
205
	if (writeProtected(start)) {
206
		return unmappedWrite.data();
207
	} else {
208
		return MSXMemoryMapperBase::getWriteCacheLine(start);
209
	}
210
}
211
212
template<typename Archive>
213
void MusicalMemoryMapper::serialize(Archive& ar, unsigned /*version*/)
214
{
215
	ar.template serializeBase<MSXMemoryMapperBase>(*this);
216
	ar.serialize("ctrl",    controlReg,
217
	             "sn76489", sn76489);
218
}
219
INSTANTIATE_SERIALIZE_METHODS(MusicalMemoryMapper);
220
REGISTER_MSXDEVICE(MusicalMemoryMapper, "MusicalMemoryMapper");
221
222
} // namespace openmsx