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