openMSX
MSXRS232.cc
Go to the documentation of this file.
1 #include "MSXRS232.hh"
2 #include "RS232Device.hh"
3 #include "CacheLine.hh"
4 #include "I8254.hh"
5 #include "I8251.hh"
6 #include "Ram.hh"
7 #include "Rom.hh"
8 #include "BooleanSetting.hh"
9 #include "serialize.hh"
10 #include "unreachable.hh"
11 #include "memory.hh"
12 #include <cassert>
13 
14 namespace openmsx {
15 
16 const unsigned RAM_OFFSET = 0x2000;
17 const unsigned RAM_SIZE = 0x800;
18 
19 
20 class Counter0 : public ClockPinListener
21 {
22 public:
23  explicit Counter0(MSXRS232& rs232);
24  virtual ~Counter0();
25  virtual void signal(ClockPin& pin, EmuTime::param time);
26  virtual void signalPosEdge(ClockPin& pin, EmuTime::param time);
27 private:
28  MSXRS232& rs232;
29 };
30 
31 class Counter1 : public ClockPinListener
32 {
33 public:
34  explicit Counter1(MSXRS232& rs232);
35  virtual ~Counter1();
36  virtual void signal(ClockPin& pin, EmuTime::param time);
37  virtual void signalPosEdge(ClockPin& pin, EmuTime::param time);
38 private:
39  MSXRS232& rs232;
40 };
41 
43 {
44 public:
45  explicit I8251Interf(MSXRS232& rs232);
46  virtual ~I8251Interf();
47  virtual void setRxRDY(bool status, EmuTime::param time);
48  virtual void setDTR(bool status, EmuTime::param time);
49  virtual void setRTS(bool status, EmuTime::param time);
50  virtual bool getDSR(EmuTime::param time);
51  virtual bool getCTS(EmuTime::param time);
52  virtual void setDataBits(DataBits bits);
53  virtual void setStopBits(StopBits bits);
54  virtual void setParityBit(bool enable, ParityBit parity);
55  virtual void recvByte(byte value, EmuTime::param time);
56  virtual void signal(EmuTime::param time);
57 private:
58  MSXRS232& rs232;
59 };
60 
61 
63  : MSXDevice(config)
64  , RS232Connector(MSXDevice::getPluggingController(), "msx-rs232")
65  , cntr0(make_unique<Counter0>(*this))
66  , cntr1(make_unique<Counter1>(*this))
67  , i8254(make_unique<I8254>(
68  getScheduler(), cntr0.get(), cntr1.get(), nullptr,
69  getCurrentTime()))
70  , interf(make_unique<I8251Interf>(*this))
71  , i8251(make_unique<I8251>(getScheduler(), *interf, getCurrentTime()))
72  , rom(config.findChild("rom")
73  ? make_unique<Rom>(
74  MSXDevice::getName() + " ROM", "rom", config)
75  : nullptr) // when the ROM is already mapped, you don't want to specify it again here
76  , ram(config.getChildDataAsBool("ram", false)
77  ? make_unique<Ram>(
78  config, MSXDevice::getName() + " RAM",
79  "RS232 RAM", RAM_SIZE)
80  : nullptr)
81  , rxrdyIRQ(getMotherBoard(), MSXDevice::getName() + ".IRQrxrdy")
82  , rxrdyIRQlatch(false)
83  , rxrdyIRQenabled(false)
84  , hasMemoryBasedIo(config.getChildDataAsBool("memorybasedio", false))
85  , ioAccessEnabled(!hasMemoryBasedIo)
86  , switchSetting(config.getChildDataAsBool("toshiba_rs232c_switch",
87  false) ? make_unique<BooleanSetting>(getCommandController(),
88  "toshiba_rs232c_switch", "status of the RS-232C enable switch",
89  true) : nullptr)
90 {
91  EmuDuration total(1.0 / 1.8432e6); // 1.8432MHz
92  EmuDuration hi (1.0 / 3.6864e6); // half clock period
94  i8254->getClockPin(0).setPeriodicState(total, hi, time);
95  i8254->getClockPin(1).setPeriodicState(total, hi, time);
96  i8254->getClockPin(2).setPeriodicState(total, hi, time);
97 
98  powerUp(time);
99 }
100 
102 {
103 }
104 
106 {
107  if(ram.get()) {
108  ram->clear();
109  }
110  reset(time);
111 }
112 
114 {
115  rxrdyIRQlatch = false;
116  rxrdyIRQenabled = false;
117  rxrdyIRQ.reset();
118 
119  ioAccessEnabled = !hasMemoryBasedIo;
120 
121  if (ram.get()) {
122  ram->clear();
123  }
124 }
125 
127 {
128  if (hasMemoryBasedIo && (0xBFF8 <= address) && (address <= 0xBFFF)) {
129  return readIOImpl(address & 0x07, time);
130  }
131  word addr = address & 0x3FFF;
132  if (ram.get() && ((RAM_OFFSET <= addr) && (addr < (RAM_OFFSET + RAM_SIZE)))) {
133  return (*ram)[addr - RAM_OFFSET];
134  } else if (rom.get() && (0x4000 <= address) && (address < 0x8000)) {
135  return (*rom)[addr];
136  } else {
137  return 0xFF;
138  }
139 }
140 
142 {
143  if (hasMemoryBasedIo && (start == (0xBFF8 & CacheLine::HIGH))) {
144  return nullptr;
145  }
146  word addr = start & 0x3FFF;
147  if (ram.get() && ((RAM_OFFSET <= addr) && (addr < (RAM_OFFSET + RAM_SIZE)))) {
148  return &(*ram)[addr - RAM_OFFSET];
149  } else if (rom.get() && (0x4000 <= start) && (start < 0x8000)) {
150  return &(*rom)[addr];
151  } else {
152  return unmappedRead;
153  }
154 }
155 
156 void MSXRS232::writeMem(word address, byte value, EmuTime::param time)
157 {
158 
159  if (hasMemoryBasedIo && (0xBFF8 <= address) && (address <= 0xBFFF)) {
160  // when the interface has memory based I/O, the I/O port
161  // based I/O is disabled, but it can be enabled by writing
162  // bit 4 to 0xBFFA. It is disabled again at reset.
163  // Source: Sony HB-G900P and Sony HB-G900AP service manuals.
164  // We assume here you can also disable it by writing 0 to it.
165  if (address == 0xBFFA) {
166  ioAccessEnabled = (value & (1 << 4))!=0;
167  }
168  return writeIOImpl(address & 0x07, value, time);
169  }
170  word addr = address & 0x3FFF;
171  if (ram.get() && ((RAM_OFFSET <= addr) && (addr < (RAM_OFFSET + RAM_SIZE)))) {
172  (*ram)[addr - RAM_OFFSET] = value;
173  }
174 }
175 
177 {
178  if (hasMemoryBasedIo && (start == (0xBFF8 & CacheLine::HIGH))) {
179  return nullptr;
180  }
181  word addr = start & 0x3FFF;
182  if (ram.get() && ((RAM_OFFSET <= addr) && (addr < (RAM_OFFSET + RAM_SIZE)))) {
183  return &(*ram)[addr - RAM_OFFSET];
184  } else {
185  return unmappedWrite;
186  }
187 }
188 
190 {
191  if (ioAccessEnabled) {
192  return readIOImpl(port & 0x07, time);
193  }
194  return 0xFF;
195 }
196 
197 byte MSXRS232::readIOImpl(word port, EmuTime::param time)
198 {
199  byte result;
200  switch (port) {
201  case 0: // UART data register
202  case 1: // UART status register
203  result = i8251->readIO(port, time);
204  break;
205  case 2: // Status sense port
206  result = readStatus(time);
207  break;
208  case 3: // no function
209  result = 0xFF;
210  break;
211  case 4: // counter 0 data port
212  case 5: // counter 1 data port
213  case 6: // counter 2 data port
214  case 7: // timer command register
215  result = i8254->readIO(port - 4, time);
216  break;
217  default:
218  UNREACHABLE; return 0;
219  }
220  return result;
221 }
222 
224 {
225  if (hasMemoryBasedIo && !ioAccessEnabled) return 0xFF;
226  byte result;
227  port &= 0x07;
228  switch (port) {
229  case 0: // UART data register
230  case 1: // UART status register
231  result = i8251->peekIO(port, time);
232  break;
233  case 2: // Status sense port
234  result = 0; // TODO not implemented
235  break;
236  case 3: // no function
237  result = 0xFF;
238  break;
239  case 4: // counter 0 data port
240  case 5: // counter 1 data port
241  case 6: // counter 2 data port
242  case 7: // timer command register
243  result = i8254->peekIO(port - 4, time);
244  break;
245  default:
246  UNREACHABLE; return 0;
247  }
248  return result;
249 }
250 
251 void MSXRS232::writeIO(word port, byte value, EmuTime::param time)
252 {
253  if (ioAccessEnabled) writeIOImpl(port & 0x07, value, time);
254 }
255 
256 void MSXRS232::writeIOImpl(word port, byte value, EmuTime::param time)
257 {
258  switch (port) {
259  case 0: // UART data register
260  case 1: // UART command register
261  i8251->writeIO(port, value, time);
262  break;
263  case 2: // interrupt mask register
264  setIRQMask(value);
265  break;
266  case 3: // no function
267  break;
268  case 4: // counter 0 data port
269  case 5: // counter 1 data port
270  case 6: // counter 2 data port
271  case 7: // timer command register
272  i8254->writeIO(port - 4, value, time);
273  break;
274  }
275 }
276 
277 byte MSXRS232::readStatus(EmuTime::param time)
278 {
279 
280  // Info from http://nocash.emubase.de/portar.htm
281  //
282  // Bit Name Expl.
283  // 0 CD Carrier Detect (0=Active, 1=Not active)
284  // 1 RI Ring Indicator (0=Active, 1=Not active) (N/C in MSX)
285  // 6 Timer Output from i8253 Counter 2
286  // 7 CTS Clear to Send (0=Active, 1=Not active)
287  //
288  // On Toshiba HX-22, see
289  // http://www.msx.org/forum/msx-talk/hardware/toshiba-hx-22?page=3
290  // RetroTechie's post of 20-09-2012, 08:08
291  // ... The "RS-232 interrupt disable" bit can be read back via bit 3
292  // on this I/O port, if CN1 is open. If CN1 is closed, it always
293  // reads back as "0". ...
294 
295  byte result = 0; // TODO check unused bits
296 
297  // TODO bit 0: carrier detect
298 
299  if (!rxrdyIRQenabled && switchSetting.get() &&
300  switchSetting->getValue()) {
301  result |= 0x08;
302  }
303 
304  if (!interf->getCTS(time)) {
305  result |= 0x80;
306  }
307  if (i8254->getOutputPin(2).getState(time)) {
308  result |= 0x40;
309  }
310  return result;
311 }
312 
313 void MSXRS232::setIRQMask(byte value)
314 {
315  enableRxRDYIRQ(!(value & 1));
316 }
317 
318 void MSXRS232::setRxRDYIRQ(bool status)
319 {
320  if (rxrdyIRQlatch != status) {
321  rxrdyIRQlatch = status;
322  if (rxrdyIRQenabled) {
323  if (rxrdyIRQlatch) {
324  rxrdyIRQ.set();
325  } else {
326  rxrdyIRQ.reset();
327  }
328  }
329  }
330 }
331 
332 void MSXRS232::enableRxRDYIRQ(bool enabled)
333 {
334  if (rxrdyIRQenabled != enabled) {
335  rxrdyIRQenabled = enabled;
336  if (!rxrdyIRQenabled && rxrdyIRQlatch) {
337  rxrdyIRQ.reset();
338  }
339  }
340 }
341 
342 
343 // I8251Interface (pass calls from I8251 to outConnector)
344 
346  : rs232(rs232_)
347 {
348 }
349 
351 {
352 }
353 
354 void I8251Interf::setRxRDY(bool status, EmuTime::param /*time*/)
355 {
356  rs232.setRxRDYIRQ(status);
357 }
358 
359 void I8251Interf::setDTR(bool status, EmuTime::param time)
360 {
361  rs232.getPluggedRS232Dev().setDTR(status, time);
362 }
363 
364 void I8251Interf::setRTS(bool status, EmuTime::param time)
365 {
366  rs232.getPluggedRS232Dev().setRTS(status, time);
367 }
368 
370 {
371  return rs232.getPluggedRS232Dev().getDSR(time);
372 }
373 
375 {
376  return rs232.getPluggedRS232Dev().getCTS(time);
377 }
378 
380 {
381  rs232.getPluggedRS232Dev().setDataBits(bits);
382 }
383 
385 {
386  rs232.getPluggedRS232Dev().setStopBits(bits);
387 }
388 
389 void I8251Interf::setParityBit(bool enable, ParityBit parity)
390 {
391  rs232.getPluggedRS232Dev().setParityBit(enable, parity);
392 }
393 
395 {
396  rs232.getPluggedRS232Dev().recvByte(value, time);
397 }
398 
400 {
401  rs232.getPluggedRS232Dev().signal(time); // for input
402 }
403 
404 
405 // Counter 0 output
406 
408  : rs232(rs232_)
409 {
410 }
411 
413 {
414 }
415 
417 {
418  ClockPin& clk = rs232.i8251->getClockPin();
419  if (pin.isPeriodic()) {
421  pin.getHighDuration(), time);
422  } else {
423  clk.setState(pin.getState(time), time);
424  }
425 }
426 
428 {
429  UNREACHABLE;
430 }
431 
432 
433 // Counter 1 output // TODO split rx tx
434 
436  : rs232(rs232_)
437 {
438 }
439 
441 {
442 }
443 
445 {
446  ClockPin& clk = rs232.i8251->getClockPin();
447  if (pin.isPeriodic()) {
449  pin.getHighDuration(), time);
450  } else {
451  clk.setState(pin.getState(time), time);
452  }
453 }
454 
456 {
457  UNREACHABLE;
458 }
459 
460 
461 // RS232Connector input
462 
464 {
465  return i8251->isRecvReady();
466 }
467 
469 {
470  return i8251->isRecvEnabled();
471 }
472 
474 {
475  i8251->setDataBits(bits);
476 }
477 
479 {
480  i8251->setStopBits(bits);
481 }
482 
483 void MSXRS232::setParityBit(bool enable, ParityBit parity)
484 {
485  i8251->setParityBit(enable, parity);
486 }
487 
489 {
490  i8251->recvByte(value, time);
491 }
492 
493 // version 1: initial version
494 // version 2: added ioAccessEnabled
495 // TODO: serialize switch status?
496 template<typename Archive>
497 void MSXRS232::serialize(Archive& ar, unsigned version)
498 {
499  ar.template serializeBase<MSXDevice>(*this);
500  ar.template serializeBase<RS232Connector>(*this);
501 
502  ar.serialize("I8254", *i8254);
503  ar.serialize("I8251", *i8251);
504  if (ram.get()) {
505  ar.serialize("ram", *ram);
506  }
507  ar.serialize("rxrdyIRQ", rxrdyIRQ);
508  ar.serialize("rxrdyIRQlatch", rxrdyIRQlatch);
509  ar.serialize("rxrdyIRQenabled", rxrdyIRQenabled);
510  if (ar.versionAtLeast(version, 2)) {
511  ar.serialize("ioAccessEnabled", ioAccessEnabled);
512  } else {
513  assert(ar.isLoader());
514  ioAccessEnabled = !hasMemoryBasedIo; // we can't know the
515  // actual value, but this is probably
516  // safest
517  }
518 
519  // don't serialize cntr0, cntr1, interf
520 }
522 REGISTER_MSXDEVICE(MSXRS232, "RS232");
523 
524 } // namespace openmsx