OXYGENHUB
◆ main openmsx / src / serial / MC6850.cc
9.5 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#include "MC6850.hh"
2
3
#include "MidiInDevice.hh"
4
5
#include "EmuTime.hh"
6
#include "MSXMotherBoard.hh"
7
#include "serialize.hh"
8
9
#include <array>
10
11
namespace openmsx {
12
13
// control register bits
14
static constexpr unsigned CR_CDS1 = 0x01; // Counter Divide Select 1
15
static constexpr unsigned CR_CDS2 = 0x02; // Counter Divide Select 2
16
static constexpr unsigned CR_CDS  = CR_CDS1 | CR_CDS2;
17
static constexpr unsigned CR_MR   = CR_CDS1 | CR_CDS2; // Master Reset
18
// CDS2 CDS1
19
// 0    0     divide by 1
20
// 0    1     divide by 16
21
// 1    0     divide by 64
22
// 1    1     master reset (!)
23
24
static constexpr unsigned CR_WS1  = 0x04; // Word Select 1 (mostly parity)
25
static constexpr unsigned CR_WS2  = 0x08; // Word Select 2 (mostly nof stop bits)
26
static constexpr unsigned CR_WS3  = 0x10; // Word Select 3: 7/8 bits
27
static constexpr unsigned CR_WS   = CR_WS1 | CR_WS2 | CR_WS3; // Word Select
28
// WS3 WS2 WS1
29
// 0   0   0   7 bits - 2 stop bits - Even parity
30
// 0   0   1   7 bits - 2 stop bits - Odd  parity
31
// 0   1   0   7 bits - 1 stop bit  - Even parity
32
// 0   1   1   7 bits - 1 stop bit  - Odd  Parity
33
// 1   0   0   8 bits - 2 stop bits - No   Parity
34
// 1   0   1   8 bits - 1 stop bit  - No   Parity
35
// 1   1   0   8 bits - 1 stop bit  - Even parity
36
// 1   1   1   8 bits - 1 stop bit  - Odd  parity
37
38
static constexpr unsigned CR_TC1  = 0x20; // Transmit Control 1
39
static constexpr unsigned CR_TC2  = 0x40; // Transmit Control 2
40
static constexpr unsigned CR_TC   = CR_TC1 | CR_TC2; // Transmit Control
41
// TC2 TC1
42
// 0   0   /RTS low,  Transmitting Interrupt disabled
43
// 0   1   /RTS low,  Transmitting Interrupt enabled
44
// 1   0   /RTS high, Transmitting Interrupt disabled
45
// 1   1   /RTS low,  Transmits a Break level on the Transmit Data Output.
46
//                                 Interrupt disabled
47
48
static constexpr unsigned CR_RIE  = 0x80; // Receive Interrupt Enable: interrupt
49
// at Receive Data Register Full, Overrun, low-to-high transition on the Data
50
// Carrier Detect (/DCD) signal line
51
52
// status register bits
53
static constexpr unsigned STAT_RDRF = 0x01; // Receive Data Register Full
54
static constexpr unsigned STAT_TDRE = 0x02; // Transmit Data Register Empty
55
static constexpr unsigned STAT_DCD  = 0x04; // Data Carrier Detect (/DCD)
56
static constexpr unsigned STAT_CTS  = 0x08; // Clear-to-Send (/CTS)
57
static constexpr unsigned STAT_FE   = 0x10; // Framing Error
58
static constexpr unsigned STAT_OVRN = 0x20; // Receiver Overrun
59
static constexpr unsigned STAT_PE   = 0x40; // Parity Error
60
static constexpr unsigned STAT_IRQ  = 0x80; // Interrupt Request (/IRQ)
61
62
MC6850::MC6850(const std::string& name_, MSXMotherBoard& motherBoard, unsigned clockFreq_)
63
	: MidiInConnector(motherBoard.getPluggingController(), name_ + "-in")
64
	, syncRecv (motherBoard.getScheduler())
65
	, syncTrans(motherBoard.getScheduler())
66
	, clockFreq(clockFreq_)
67
	, rxIRQ(motherBoard, name_ + "-rx-IRQ")
68
	, txIRQ(motherBoard, name_ + "-tx-IRQ")
69
	, outConnector(motherBoard.getPluggingController(), name_ + "-out")
70
{
71
	reset(EmuTime::zero());
72
	setDataFormat();
73
}
74
75
// (Re-)initialize chip to default values (Tx and Rx disabled)
76
void MC6850::reset(EmuTime time)
77
{
78
	syncRecv .removeSyncPoint();
79
	syncTrans.removeSyncPoint();
80
	txClock.reset(time);
81
	txClock.setFreq(clockFreq);
82
	rxIRQ.reset();
83
	txIRQ.reset();
84
	rxReady = false;
85
	txShiftRegValid = false;
86
	pendingOVRN = false;
87
	controlReg = CR_MR;
88
	statusReg = 0;
89
	rxDataReg = 0;
90
	setDataFormat();
91
}
92
93
uint8_t MC6850::readStatusReg() const
94
{
95
	return peekStatusReg();
96
}
97
98
uint8_t MC6850::peekStatusReg() const
99
{
100
	uint8_t result = statusReg;
101
	if (rxIRQ.getState() || txIRQ.getState()) result |= STAT_IRQ;
102
	return result;
103
}
104
105
uint8_t MC6850::readDataReg()
106
{
107
	uint8_t result = peekDataReg();
108
	statusReg &= uint8_t(~(STAT_RDRF | STAT_OVRN));
109
	if (pendingOVRN) {
110
		pendingOVRN = false;
111
		statusReg |= STAT_OVRN;
112
	}
113
	rxIRQ.reset();
114
	return result;
115
}
116
117
uint8_t MC6850::peekDataReg() const
118
{
119
	return rxDataReg;
120
}
121
122
void MC6850::writeControlReg(uint8_t value, EmuTime time)
123
{
124
	uint8_t diff = value ^ controlReg;
125
	if (diff & CR_CDS) {
126
		if ((value & CR_CDS) == CR_MR) {
127
			reset(time);
128
		} else {
129
			// we got out of MR state
130
			rxReady = true;
131
			statusReg |= STAT_TDRE;
132
133
			txClock.reset(time);
134
			switch (value & CR_CDS) {
135
			case 0: txClock.setFreq(clockFreq,  1); break;
136
			case 1: txClock.setFreq(clockFreq, 16); break;
137
			case 2: txClock.setFreq(clockFreq, 64); break;
138
			}
139
		}
140
	}
141
142
	controlReg = value;
143
	if (diff & CR_WS) setDataFormat();
144
145
	// update IRQ status
146
	rxIRQ.set(( value & CR_RIE) && (statusReg & STAT_RDRF));
147
	txIRQ.set(((value & CR_TC) == 0x20) && (statusReg & STAT_TDRE));
148
}
149
150
// Sync data-format related parameters with the current value of controlReg
151
void MC6850::setDataFormat()
152
{
153
	outConnector.setDataBits(controlReg & CR_WS3 ? DataBits::D8 : DataBits::D7);
154
155
	static constexpr std::array<StopBits, 8> stopBits = {
156
		StopBits::S2, StopBits::S2, StopBits::S1, StopBits::S1,
157
		StopBits::S2, StopBits::S1, StopBits::S1, StopBits::S1,
158
	};
159
	outConnector.setStopBits(stopBits[(controlReg & CR_WS) >> 2]);
160
161
	outConnector.setParityBit(
162
		(controlReg & (CR_WS3 | CR_WS2)) != 0x10, // enable
163
		(controlReg & CR_WS1) ? Parity::ODD : Parity::EVEN);
164
165
	// start-bits, data-bits, parity-bits, stop-bits
166
	static constexpr std::array<uint8_t, 8> len = {
167
		1 + 7 + 1 + 2,
168
		1 + 7 + 1 + 2,
169
		1 + 7 + 1 + 1,
170
		1 + 7 + 1 + 1,
171
		1 + 8 + 0 + 2,
172
		1 + 8 + 0 + 1,
173
		1 + 8 + 1 + 1,
174
		1 + 8 + 1 + 1,
175
	};
176
	charLen = len[(controlReg & CR_WS) >> 2];
177
}
178
179
void MC6850::writeDataReg(uint8_t value, EmuTime time)
180
{
181
	if ((controlReg & CR_CDS) == CR_MR) return;
182
183
	txDataReg = value;
184
	statusReg &= uint8_t(~STAT_TDRE);
185
	txIRQ.reset();
186
187
	if (syncTrans.isPending()) {
188
		// We're still sending the previous character, only
189
		// buffer this one. Don't accept any further characters
190
	} else {
191
		// We were not yet sending. Start sending at the next txClock.
192
		// Important: till that time TDRE should go low
193
		//  (MC6850 detection routine in Synthesix depends on this)
194
		txClock.advance(time); // clock edge before or at 'time'
195
		txClock += 1; // clock edge strictly after 'time'
196
		syncTrans.setSyncPoint(txClock.getTime());
197
	}
198
}
199
200
// Triggered between transmitted characters, including before the first and
201
// after the last character.
202
void MC6850::execTrans(EmuTime time)
203
{
204
	assert(txClock.getTime() == time);
205
	assert((controlReg & CR_CDS) != CR_MR);
206
207
	if (txShiftRegValid) {
208
		txShiftRegValid = false;
209
		outConnector.recvByte(txShiftReg, time);
210
	}
211
212
	if (statusReg & STAT_TDRE) {
213
		// No next character to send, we're done.
214
	} else {
215
		// There already is a next character, start sending that now
216
		// and accept a next one.
217
		statusReg |= STAT_TDRE;
218
		if ((controlReg & CR_TC) == 0x20) txIRQ.set();
219
220
		txShiftReg = txDataReg;
221
		txShiftRegValid = true;
222
223
		txClock += charLen;
224
		syncTrans.setSyncPoint(txClock.getTime());
225
	}
226
}
227
228
// MidiInConnector sends a new character.
229
void MC6850::recvByte(uint8_t value, EmuTime time)
230
{
231
	assert(acceptsData() && ready());
232
233
	if (statusReg & STAT_RDRF) {
234
		// So, there is a byte that has to be read by the MSX still!
235
		// This happens when the MSX program doesn't
236
		// respond fast enough to an earlier received byte.
237
		// The STAT_OVRN flag only becomes active once the prior valid
238
		// character has been read from the data register.
239
		pendingOVRN = true;
240
	} else {
241
		rxDataReg = value;
242
		statusReg |= STAT_RDRF;
243
	}
244
	// both for OVRN and RDRF an IRQ is raised
245
	if (controlReg & CR_RIE) rxIRQ.set();
246
247
	// Not ready now, but we will be in a while
248
	rxReady = false;
249
250
	// The MC6850 has separate TxCLK and RxCLK inputs, but both share a
251
	// common divider. This implementation hard-codes an input frequency
252
	// for both. Below we want the receive clock period, but it's OK
253
	// to calculate that as 'txClock.getPeriod()'.
254
	syncRecv.setSyncPoint(time + txClock.getPeriod() * charLen);
255
}
256
257
// Triggered when we're ready to receive the next character.
258
void MC6850::execRecv(EmuTime time)
259
{
260
	assert(acceptsData());
261
	assert(!rxReady);
262
	rxReady = true;
263
	getPluggedMidiInDev().signal(time); // trigger (possible) send of next char
264
}
265
266
// MidiInDevice queries whether it can send a new character 'now'.
267
bool MC6850::ready()
268
{
269
	return rxReady;
270
}
271
272
// MidiInDevice queries whether it can send characters at all.
273
bool MC6850::acceptsData()
274
{
275
	return (controlReg & CR_CDS) != CR_MR;
276
}
277
278
// MidiInDevice informs us about the format of the data it will send
279
// (MIDI is always 1 start-bit, 8 data-bits, 1 stop-bit, no parity-bits).
280
void  MC6850::setDataBits(DataBits /*bits*/)
281
{
282
	// ignore
283
}
284
void MC6850::setStopBits(StopBits /*bits*/)
285
{
286
	// ignore
287
}
288
void MC6850::setParityBit(bool /*enable*/, Parity /*parity*/)
289
{
290
	// ignore
291
}
292
293
// version 1: initial version
294
// version 2: added control
295
// version 3: actually working MC6850 with many more member variables
296
template<typename Archive>
297
void MC6850::serialize(Archive& ar, unsigned version)
298
{
299
	if (ar.versionAtLeast(version, 3)) {
300
		ar.template serializeBase<MidiInConnector>(*this);
301
		ar.serialize("outConnector", outConnector,
302
303
		             "syncRecv",  syncRecv,
304
		             "syncTrans", syncTrans,
305
306
		             "txClock", txClock,
307
		             "rxIRQ", rxIRQ,
308
		             "txIRQ", txIRQ,
309
310
		             "rxReady",         rxReady,
311
		             "txShiftRegValid", txShiftRegValid,
312
		             "pendingOVRN",     pendingOVRN,
313
314
		             "rxDataReg",  rxDataReg,
315
		             "txDataReg",  txDataReg,
316
		             "txShiftReg", txShiftReg,
317
		             "controlReg", controlReg,
318
		             "statusReg",  statusReg);
319
	} else if (ar.versionAtLeast(version, 2)) {
320
		ar.serialize("control", controlReg);
321
	} else {
322
		controlReg = 3;
323
	}
324
325
	if constexpr (Archive::IS_LOADER) {
326
		setDataFormat();
327
	}
328
}
329
INSTANTIATE_SERIALIZE_METHODS(MC6850);
330
331
} // namespace openmsx