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