OXYGENHUB
◆ main openmsx / src / serial / YM2148.cc
6.6 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
e8555c89 Andy McCall 1
// implementation based on:
2
// http://map.grauw.nl/resources/midi/ym2148.php
3
4
#include "YM2148.hh"
5
6
#include "MidiInDevice.hh"
7
8
#include "MSXMotherBoard.hh"
9
#include "serialize.hh"
10
11
namespace openmsx {
12
13
// status register flags
14
static constexpr unsigned STAT_TXRDY = 0x01; // Transmitter ready: no MIDI-out send is in progress
15
static constexpr unsigned STAT_RXRDY = 0x02; // Receiver ready: a MIDI-in byte is available for the MSX
16
static constexpr unsigned STAT_OE    = 0x10; // Overrun error (incoming data)
17
static constexpr unsigned STAT_FE    = 0x20; // Framing error (incoming data)
18
19
// command register bits
20
static constexpr unsigned CMD_TXEN  = 0x01; // Transmit enable
21
static constexpr unsigned CMD_TXIE  = 0x02; // TxRDY interrupt enable
22
static constexpr unsigned CMD_RXEN  = 0x04; // Receive enable
23
static constexpr unsigned CMD_RXIE  = 0x08; // RxRDY interrupt enable
24
static constexpr unsigned CMD_ER    = 0x10; // Error Reset
25
static constexpr unsigned CMD_IR    = 0x80; // Internal Reset
26
// The meaning of bits 5 and 6 are unknown (they are used by the CX5M
27
// software). Some documentation *guesses* they are related to IM2
28
// IRQ handling.
29
30
static constexpr auto BIT_DURATION = EmuDuration::hz(31250);
31
static constexpr auto CHAR_DURATION = BIT_DURATION * 10; // 1 start-bit, 8 data-bits, 1 stop-bit
32
33
YM2148::YM2148(const std::string& name_, MSXMotherBoard& motherBoard)
34
	: MidiInConnector(motherBoard.getPluggingController(), name_ + "-MIDI-in")
35
	, syncRecv (motherBoard.getScheduler())
36
	, syncTrans(motherBoard.getScheduler())
37
	, rxIRQ(motherBoard, name_ + "-rx-IRQ")
38
	, txIRQ(motherBoard, name_ + "-tx-IRQ")
39
	, outConnector(motherBoard.getPluggingController(), name_ + "-MIDI-out")
40
{
41
	reset();
42
}
43
44
void YM2148::reset()
45
{
46
	syncRecv .removeSyncPoint();
47
	syncTrans.removeSyncPoint();
48
	rxIRQ.reset();
49
	txIRQ.reset();
50
	rxReady = false;
51
	rxBuffer = 0;
52
	status = 0;
53
	commandReg = 0;
54
}
55
56
// MidiInConnector sends a new character.
57
void YM2148::recvByte(uint8_t value, EmuTime time)
58
{
59
	assert(acceptsData() && ready());
60
61
	if (status & STAT_RXRDY) {
62
		// So, there is a byte that has to be read by the MSX still!
63
		// This happens when the MSX program doesn't
64
		// respond fast enough to an earlier received byte.
65
		status |= STAT_OE;
66
		// TODO investigate: overwrite rxBuffer in case of overrun?
67
	} else {
68
		rxBuffer = value;
69
		status |= STAT_RXRDY;
70
		if (commandReg & CMD_RXIE) rxIRQ.set();
71
	}
72
73
	// Not ready now, but we will be in a while
74
	rxReady = false;
75
	syncRecv.setSyncPoint(time + CHAR_DURATION);
76
}
77
78
// Triggered when we're ready to receive the next character.
79
void YM2148::execRecv(EmuTime time)
80
{
81
	assert(commandReg & CMD_RXEN);
82
	assert(!rxReady);
83
	rxReady = true;
84
	getPluggedMidiInDev().signal(time); // trigger (possible) send of next char
85
}
86
87
// MidiInDevice queries whether it can send a new character 'now'.
88
bool YM2148::ready()
89
{
90
	return rxReady;
91
}
92
93
// MidiInDevice queries whether it can send characters at all.
94
bool YM2148::acceptsData()
95
{
96
	return (commandReg & CMD_RXEN) != 0;
97
}
98
99
// MidiInDevice informs us about the format of the data it will send
100
// (MIDI is always 1 start-bit, 8 data-bits, 1 stop-bit, no parity-bits).
101
void  YM2148::setDataBits(DataBits /*bits*/)
102
{
103
	// ignore
104
}
105
void YM2148::setStopBits(StopBits /*bits*/)
106
{
107
	// ignore
108
}
109
void YM2148::setParityBit(bool /*enable*/, Parity /*parity*/)
110
{
111
	// ignore
112
}
113
114
// MSX program reads the status register.
115
uint8_t YM2148::readStatus(EmuTime /*time*/) const
116
{
117
	return status;
118
}
119
uint8_t YM2148::peekStatus(EmuTime /*time*/) const
120
{
121
	return status;
122
}
123
124
// MSX programs reads the data register.
125
uint8_t YM2148::readData(EmuTime /*time*/)
126
{
127
	status &= uint8_t(~STAT_RXRDY);
128
	rxIRQ.reset(); // no need to check CMD_RXIE
129
	return rxBuffer;
130
}
131
uint8_t YM2148::peekData(EmuTime /*time*/) const
132
{
133
	return rxBuffer;
134
}
135
136
// MSX program writes the command register.
137
void YM2148::writeCommand(uint8_t value)
138
{
139
	if (value & CMD_IR) {
140
		reset();
141
		return; // do not process any other commands
142
	}
143
	if (value & CMD_ER) {
144
		status &= uint8_t(~(STAT_OE | STAT_FE));
145
		return;
146
	}
147
148
	uint8_t diff = commandReg ^ value;
149
	commandReg = value;
150
151
	if (diff & CMD_RXEN) {
152
		if (commandReg & CMD_RXEN) {
153
			// disabled -> enabled
154
			rxReady = true;
155
		} else {
156
			// enabled -> disabled
157
			rxReady = false;
158
			syncRecv.removeSyncPoint();
159
			status &= uint8_t(~STAT_RXRDY); // IRQ is handled below
160
		}
161
	}
162
	if (diff & CMD_TXEN) {
163
		if (commandReg & CMD_TXEN) {
164
			// disabled -> enabled
165
			status |= STAT_TXRDY; // IRQ is handled below
166
			// TODO transmitter is ready at this point, does this immediately trigger an IRQ (when IRQs are enabled)?
167
		} else {
168
			// enabled -> disabled
169
			status &= uint8_t(~STAT_TXRDY); // IRQ handled below
170
			syncTrans.removeSyncPoint();
171
		}
172
	}
173
174
	// update IRQ status
175
	rxIRQ.set((value & CMD_RXIE) && (status & STAT_RXRDY));
176
	txIRQ.set((value & CMD_TXIE) && (status & STAT_TXRDY));
177
}
178
179
// MSX program writes the data register.
180
void YM2148::writeData(uint8_t value, EmuTime time)
181
{
182
	if (!(commandReg & CMD_TXEN)) return;
183
184
	if (syncTrans.isPending()) {
185
		// We're still sending the previous character, only buffer
186
		// this one. Don't accept any further characters.
187
		txBuffer2 = value;
188
		status &= uint8_t(~STAT_TXRDY);
189
		txIRQ.reset();
190
	} else {
191
		// Immediately start sending this character. We're still
192
		// ready to accept a next character.
193
		send(value, time);
194
	}
195
}
196
197
// Start sending a character. It takes a while before it's finished sending.
198
void YM2148::send(uint8_t value, EmuTime time)
199
{
200
	txBuffer1 = value;
201
	syncTrans.setSyncPoint(time + CHAR_DURATION);
202
}
203
204
// Triggered when a character has finished sending.
205
void YM2148::execTrans(EmuTime time)
206
{
207
	assert(commandReg & CMD_TXEN);
208
209
	outConnector.recvByte(txBuffer1, time);
210
211
	if (status & STAT_TXRDY) {
212
		// No next character to send.
213
	} else {
214
		// There already is a next character, start sending that now
215
		// and accept a next one.
216
		status |= STAT_TXRDY;
217
		if (commandReg & CMD_TXIE) txIRQ.set();
218
		send(txBuffer2, time);
219
	}
220
}
221
222
// Any pending IRQs?
223
bool YM2148::pendingIRQ() const
224
{
225
	return rxIRQ.getState() || txIRQ.getState();
226
}
227
228
template<typename Archive>
229
void YM2148::serialize(Archive& ar, unsigned version)
230
{
231
	if (ar.versionAtLeast(version, 2)) {
232
		ar.template serializeBase<MidiInConnector>(*this);
233
		ar.serialize("outConnector", outConnector,
234
235
		             "syncRecv",     syncRecv,
236
		             "syncTrans",    syncTrans,
237
238
		             "rxIRQ",        rxIRQ,
239
		             "txIRQ",        txIRQ,
240
241
		             "rxReady",      rxReady,
242
		             "rxBuffer",     rxBuffer,
243
		             "txBuffer1",    txBuffer1,
244
		             "txBuffer2",    txBuffer2,
245
		             "status",       status,
246
		             "commandReg",   commandReg);
247
	}
248
}
249
INSTANTIATE_SERIALIZE_METHODS(YM2148);
250
251
} // namespace openmsx