OXYGENHUB
◆ main openmsx / src / serial / I8251.cc
10.0 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
e8555c89 Andy McCall 1
#include "I8251.hh"
2
3
#include "serialize.hh"
4
5
#include "unreachable.hh"
6
7
#include <cassert>
8
9
namespace openmsx {
10
11
static constexpr uint8_t STAT_TXRDY   = 0x01;
12
static constexpr uint8_t STAT_RXRDY   = 0x02;
13
static constexpr uint8_t STAT_TXEMPTY = 0x04;
14
static constexpr uint8_t STAT_PE      = 0x08;
15
static constexpr uint8_t STAT_OE      = 0x10;
16
static constexpr uint8_t STAT_FE      = 0x20;
17
static constexpr uint8_t STAT_SYN_BRK = 0x40;
18
static constexpr uint8_t STAT_DSR     = 0x80;
19
20
static constexpr uint8_t MODE_BAUDRATE    = 0x03;
21
static constexpr uint8_t MODE_SYNCHRONOUS = 0x00;
22
static constexpr uint8_t MODE_RATE1       = 0x01;
23
static constexpr uint8_t MODE_RATE16      = 0x02;
24
static constexpr uint8_t MODE_RATE64      = 0x03;
25
static constexpr uint8_t MODE_WORD_LENGTH  = 0x0C;
26
static constexpr uint8_t MODE_5BIT        = 0x00;
27
static constexpr uint8_t MODE_6BIT        = 0x04;
28
static constexpr uint8_t MODE_7BIT        = 0x08;
29
static constexpr uint8_t MODE_8BIT        = 0x0C;
30
static constexpr uint8_t MODE_PARITY_EVEN = 0x10;
31
static constexpr uint8_t MODE_PARITY_ODD  = 0x00;
32
static constexpr uint8_t MODE_PARITEVEN   = 0x20;
33
static constexpr uint8_t MODE_STOP_BITS   = 0xC0;
34
static constexpr uint8_t MODE_STOP_INV    = 0x00;
35
static constexpr uint8_t MODE_STOP_1      = 0x40;
36
static constexpr uint8_t MODE_STOP_15     = 0x80;
37
static constexpr uint8_t MODE_STOP_2      = 0xC0;
38
static constexpr uint8_t MODE_SINGLE_SYNC = 0x80;
39
40
static constexpr uint8_t CMD_TXEN    = 0x01;
41
static constexpr uint8_t CMD_DTR     = 0x02;
42
static constexpr uint8_t CMD_RXE     = 0x04;
43
static constexpr uint8_t CMD_SBRK    = 0x08;
44
static constexpr uint8_t CMD_RST_ERR = 0x10;
45
static constexpr uint8_t CMD_RTS     = 0x20;
46
static constexpr uint8_t CMD_RESET   = 0x40;
47
static constexpr uint8_t CMD_HUNT    = 0x80;
48
49
50
I8251::I8251(Scheduler& scheduler, I8251Interface& interface_, EmuTime time)
51
	: syncRecv (scheduler)
52
	, syncTrans(scheduler)
53
	, interface(interface_), clock(scheduler)
54
{
55
	reset(time);
56
}
57
58
void I8251::reset(EmuTime time)
59
{
60
	// initialize these to avoid UMR on savestate
61
	//   TODO investigate correct initial state after reset
62
	charLength = 0;
63
	recvDataBits  = SerialDataInterface::DataBits::D8;
64
	recvStopBits  = SerialDataInterface::StopBits::S1;
65
	recvParityBit = SerialDataInterface::Parity::EVEN;
66
	recvParityEnabled = false;
67
	recvBuf = 0;
68
	recvReady = false;
69
	sendByte = 0;
70
	sendBuffer = 0;
71
	mode = 0;
72
	sync1 = sync2 = 0;
73
74
	status = STAT_TXRDY | STAT_TXEMPTY;
75
	command = 0xFF; // make sure all bits change
76
	writeCommand(0, time);
77
	cmdPhase = CmdPhase::MODE;
78
}
79
80
uint8_t I8251::readIO(uint16_t port, EmuTime time)
81
{
82
	switch (port & 1) {
83
		case 0:  return readTrans(time);
84
		case 1:  return readStatus(time);
85
		default: UNREACHABLE;
86
	}
87
}
88
89
uint8_t I8251::peekIO(uint16_t port, EmuTime /*time*/) const
90
{
91
	switch (port & 1) {
92
		case 0:  return recvBuf;
93
		case 1:  return status; // TODO peekStatus()
94
		default: UNREACHABLE;
95
	}
96
}
97
98
99
void I8251::writeIO(uint16_t port, uint8_t value, EmuTime time)
100
{
101
	switch (port & 1) {
102
	case 0:
103
		writeTrans(value, time);
104
		break;
105
	case 1:
106
		switch (cmdPhase) {
107
		using enum CmdPhase;
108
		case MODE:
109
			setMode(value);
110
			if ((mode & MODE_BAUDRATE) == MODE_SYNCHRONOUS) {
111
				cmdPhase = SYNC1;
112
			} else {
113
				cmdPhase = CMD;
114
			}
115
			break;
116
		case SYNC1:
117
			sync1 = value;
118
			if (mode & MODE_SINGLE_SYNC) {
119
				cmdPhase = CMD;
120
			} else {
121
				cmdPhase = SYNC2;
122
			}
123
			break;
124
		case SYNC2:
125
			sync2 = value;
126
			cmdPhase = CMD;
127
			break;
128
		case CMD:
129
			if (value & CMD_RESET) {
130
				cmdPhase = MODE;
131
			} else {
132
				writeCommand(value, time);
133
			}
134
			break;
135
		default:
136
			UNREACHABLE;
137
		}
138
		break;
139
	default:
140
		UNREACHABLE;
141
	}
142
}
143
144
void I8251::setMode(uint8_t newMode)
145
{
146
	mode = newMode;
147
148
	auto dataBits = [&] {
149
		switch (mode & MODE_WORD_LENGTH) {
150
		using enum SerialDataInterface::DataBits;
151
		case MODE_5BIT: return D5;
152
		case MODE_6BIT: return D6;
153
		case MODE_7BIT: return D7;
154
		case MODE_8BIT: return D8;
155
		default: UNREACHABLE;
156
		}
157
	}();
158
	interface.setDataBits(dataBits);
159
160
	auto stopBits = [&] {
161
		switch(mode & MODE_STOP_BITS) {
162
		using enum SerialDataInterface::StopBits;
163
		case MODE_STOP_INV: return INV;
164
		case MODE_STOP_1:   return S1;
165
		case MODE_STOP_15:  return S1_5;
166
		case MODE_STOP_2:   return S2;
167
		default: UNREACHABLE;
168
		}
169
	}();
170
	interface.setStopBits(stopBits);
171
172
	bool parityEnable = (mode & MODE_PARITY_EVEN) != 0;
173
	SerialDataInterface::Parity parity = (mode & MODE_PARITEVEN) ?
174
		SerialDataInterface::Parity::EVEN : SerialDataInterface::Parity::ODD;
175
	interface.setParityBit(parityEnable, parity);
176
177
	unsigned baudrate = [&] {
178
		switch (mode & MODE_BAUDRATE) {
179
		case MODE_SYNCHRONOUS: return 1;
180
		case MODE_RATE1:       return 1;
181
		case MODE_RATE16:      return 16;
182
		case MODE_RATE64:      return 64;
183
		default: UNREACHABLE;
184
		}
185
	}();
186
187
	charLength = (((2 * (1 + unsigned(dataBits) + (parityEnable ? 1 : 0))) +
188
	               unsigned(stopBits)) * baudrate) / 2;
189
}
190
191
void I8251::writeCommand(uint8_t value, EmuTime time)
192
{
193
	uint8_t oldCommand = command;
194
	command = value;
195
196
	// CMD_RESET, CMD_TXEN, CMD_RXE  handled in other routines
197
198
	interface.setRTS((command & CMD_RTS) != 0, time);
199
	interface.setDTR((command & CMD_DTR) != 0, time);
200
201
	if (!(command & CMD_TXEN)) {
202
		// disable transmitter
203
		syncTrans.removeSyncPoint();
204
		status |= STAT_TXRDY | STAT_TXEMPTY;
205
	}
206
	if (command & CMD_RST_ERR) {
207
		status &= ~(STAT_PE | STAT_OE | STAT_FE);
208
	}
209
	if (command & CMD_SBRK) {
210
		// TODO
211
	}
212
	if (command & CMD_HUNT) {
213
		// TODO
214
	}
215
216
	if ((command ^ oldCommand) & CMD_RXE) {
217
		if (command & CMD_RXE) {
218
			// enable receiver
219
			status &= ~(STAT_PE | STAT_OE | STAT_FE); // TODO
220
			recvReady = true;
221
		} else {
222
			// disable receiver
223
			syncRecv.removeSyncPoint();
224
			status &= ~(STAT_PE | STAT_OE | STAT_FE); // TODO
225
			status &= ~STAT_RXRDY;
226
		}
227
		interface.signal(time);
228
	}
229
}
230
231
uint8_t I8251::readStatus(EmuTime time)
232
{
233
	uint8_t result = status;
234
	if (interface.getDSR(time)) {
235
		result |= STAT_DSR;
236
	}
237
	return result;
238
}
239
240
uint8_t I8251::readTrans(EmuTime time)
241
{
242
	status &= ~STAT_RXRDY;
243
	interface.setRxRDY(false, time);
244
	return recvBuf;
245
}
246
247
void I8251::writeTrans(uint8_t value, EmuTime time)
248
{
249
	if (!(command & CMD_TXEN)) {
250
		return;
251
	}
252
	if (status & STAT_TXEMPTY) {
253
		// not sending
254
		send(value, time);
255
	} else {
256
		sendBuffer = value;
257
		status &= ~STAT_TXRDY;
258
	}
259
}
260
261
void I8251::setParityBit(bool enable, Parity parity)
262
{
263
	recvParityEnabled = enable;
264
	recvParityBit = parity;
265
}
266
267
void I8251::recvByte(uint8_t value, EmuTime time)
268
{
269
	// TODO STAT_PE / STAT_FE / STAT_SYN_BRK
270
	assert(recvReady && (command & CMD_RXE));
271
	if (status & STAT_RXRDY) {
272
		status |= STAT_OE;
273
	} else {
274
		recvBuf = value;
275
		status |= STAT_RXRDY;
276
		interface.setRxRDY(true, time);
277
	}
278
	recvReady = false;
279
	if (clock.isPeriodic()) {
280
		EmuTime next = time + (clock.getTotalDuration() * charLength);
281
		syncRecv.setSyncPoint(next);
282
	}
283
}
284
285
bool I8251::isRecvEnabled() const
286
{
287
	return (command & CMD_RXE) != 0;
288
}
289
290
bool I8251::isRecvFull() const
291
{
292
	return (status & STAT_RXRDY) != 0;
293
}
294
295
unsigned I8251::getBaudRateFactor() const
296
{
297
	switch (mode & MODE_BAUDRATE) {
298
	case MODE_SYNCHRONOUS: return 1;
299
	case MODE_RATE1:       return 1;
300
	case MODE_RATE16:      return 16;
301
	case MODE_RATE64:      return 64;
302
	default: UNREACHABLE;
303
	}
304
}
305
306
void I8251::send(uint8_t value, EmuTime time)
307
{
308
	status &= ~STAT_TXEMPTY;
309
	sendByte = value;
310
	if (clock.isPeriodic()) {
311
		EmuTime next = time + (clock.getTotalDuration() * charLength);
312
		syncTrans.setSyncPoint(next);
313
	}
314
}
315
316
void I8251::execRecv(EmuTime time)
317
{
318
	assert(command & CMD_RXE);
319
	recvReady = true;
320
	interface.signal(time);
321
}
322
323
void I8251::execTrans(EmuTime time)
324
{
325
	assert(!(status & STAT_TXEMPTY) && (command & CMD_TXEN));
326
327
	interface.recvByte(sendByte, time);
328
	if (status & STAT_TXRDY) {
329
		status |= STAT_TXEMPTY;
330
	} else {
331
		status |= STAT_TXRDY;
332
		send(sendBuffer, time);
333
	}
334
}
335
336
337
static constexpr auto dataBitsInfo = std::to_array<enum_string<SerialDataInterface::DataBits>>({
338
	{ "5", SerialDataInterface::DataBits::D5 },
339
	{ "6", SerialDataInterface::DataBits::D6 },
340
	{ "7", SerialDataInterface::DataBits::D7 },
341
	{ "8", SerialDataInterface::DataBits::D8 },
342
});
343
SERIALIZE_ENUM(SerialDataInterface::DataBits, dataBitsInfo);
344
345
static constexpr auto stopBitsInfo = std::to_array<enum_string<SerialDataInterface::StopBits>>({
346
	{ "INVALID", SerialDataInterface::StopBits::INV },
347
	{ "1",       SerialDataInterface::StopBits::S1   },
348
	{ "1.5",     SerialDataInterface::StopBits::S1_5  },
349
	{ "2",       SerialDataInterface::StopBits::S2   },
350
});
351
SERIALIZE_ENUM(SerialDataInterface::StopBits, stopBitsInfo);
352
353
static constexpr auto parityBitInfo = std::to_array<enum_string<SerialDataInterface::Parity>>({
354
	{ "EVEN", SerialDataInterface::Parity::EVEN },
355
	{ "ODD",  SerialDataInterface::Parity::ODD  },
356
});
357
SERIALIZE_ENUM(SerialDataInterface::Parity, parityBitInfo);
358
359
static constexpr auto cmdFazeInfo = std::to_array<enum_string<I8251::CmdPhase>>({
360
	{ "MODE",  I8251::CmdPhase::MODE  },
361
	{ "SYNC1", I8251::CmdPhase::SYNC1 },
362
	{ "SYNC2", I8251::CmdPhase::SYNC2 },
363
	{ "CMD",   I8251::CmdPhase::CMD   },
364
});
365
SERIALIZE_ENUM(I8251::CmdPhase, cmdFazeInfo);
366
367
// version 1: initial version
368
// version 2: removed 'userData' from Schedulable
369
template<typename Archive>
370
void I8251::serialize(Archive& ar, unsigned version)
371
{
372
	if (ar.versionAtLeast(version, 2)) {
373
		ar.serialize("syncRecv",  syncRecv,
374
		             "syncTrans", syncTrans);
375
	} else {
376
		Schedulable::restoreOld(ar, {&syncRecv, &syncTrans});
377
	}
378
	ar.serialize("clock",             clock,
379
	             "charLength",        charLength,
380
	             "recvDataBits",      recvDataBits,
381
	             "recvStopBits",      recvStopBits,
382
	             "recvParityBit",     recvParityBit,
383
	             "recvParityEnabled", recvParityEnabled,
384
	             "recvBuf",           recvBuf,
385
	             "recvReady",         recvReady,
386
	             "sendByte",          sendByte,
387
	             "sendBuffer",        sendBuffer,
388
	             "status",            status,
389
	             "command",           command,
390
	             "mode",              mode,
391
	             "sync1",             sync1,
392
	             "sync2",             sync2,
393
	             "cmdFaze",           cmdPhase); // TODO fix spelling error if we ever need to upgrade this savestate format
394
}
395
INSTANTIATE_SERIALIZE_METHODS(I8251);
396
397
} // namespace openmsx