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