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
12constexpr unsigned STAT_TXRDY = 0x01; // Transmitter ready: no MIDI-out send is in progress
13constexpr unsigned STAT_RXRDY = 0x02; // Receiver ready: a MIDI-in byte is available for the MSX
14constexpr unsigned STAT_OE = 0x10; // Overrun error (incoming data)
15constexpr unsigned STAT_FE = 0x20; // Framing error (incoming data)
16
17// command register bits
18constexpr unsigned CMD_TXEN = 0x01; // Transmit enable
19constexpr unsigned CMD_TXIE = 0x02; // TxRDY interrupt enable
20constexpr unsigned CMD_RXEN = 0x04; // Receive enable
21constexpr unsigned CMD_RXIE = 0x08; // RxRDY interrupt enable
22constexpr unsigned CMD_ER = 0x10; // Error Reset
23constexpr 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
28constexpr auto BIT_DURATION = EmuDuration::hz(31250);
29constexpr 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 , 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.
56void 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.
78void 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 queries whether it can send a new character 'now'.
87bool YM2148::ready()
88{
89 return rxReady;
90}
91
92// MidiInDevice queries whether it can send characters at all.
93bool 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).
100void YM2148::setDataBits(DataBits /*bits*/)
101{
102 // ignore
103}
104void YM2148::setStopBits(StopBits /*bits*/)
105{
106 // ignore
107}
108void YM2148::setParityBit(bool /*enable*/, ParityBit /*parity*/)
109{
110 // ignore
111}
112
113// MSX program reads the status register.
114byte YM2148::readStatus(EmuTime::param /*time*/) const
115{
116 return status;
117}
118byte YM2148::peekStatus(EmuTime::param /*time*/) const
119{
120 return status;
121}
122
123// MSX programs reads the data register.
124byte YM2148::readData(EmuTime::param /*time*/)
125{
126 status &= ~STAT_RXRDY;
127 rxIRQ.reset(); // no need to check CMD_RXIE
128 return rxBuffer;
129}
130byte YM2148::peekData(EmuTime::param /*time*/) const
131{
132 return rxBuffer;
133}
134
135// MSX program writes the command register.
136void 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.
179void 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.
197void 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.
204void 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?
223{
224 return rxIRQ.getState() || txIRQ.getState();
225}
226
227template<typename Archive>
228void 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
static constexpr EmuDuration hz(unsigned x)
Definition: EmuDuration.hh:46
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:43
byte readData(EmuTime::param time)
Definition: YM2148.cc:124
void writeCommand(byte value)
Definition: YM2148.cc:136
YM2148(const std::string &name, MSXMotherBoard &motherBoard)
Definition: YM2148.cc:31
void serialize(Archive &ar, unsigned version)
Definition: YM2148.cc:228
byte peekData(EmuTime::param time) const
Definition: YM2148.cc:130
byte readStatus(EmuTime::param time) const
Definition: YM2148.cc:114
byte peekStatus(EmuTime::param time) const
Definition: YM2148.cc:118
bool pendingIRQ() const
Definition: YM2148.cc:222
void writeData(byte value, EmuTime::param time)
Definition: YM2148.cc:179
This file implemented 3 utility functions:
Definition: Autofire.cc:9
constexpr byte STAT_OE
Definition: I8251.cc:12
constexpr unsigned CMD_RXEN
Definition: YM2148.cc:20
constexpr byte STAT_FE
Definition: I8251.cc:13
constexpr auto BIT_DURATION
Definition: YM2148.cc:28
constexpr unsigned CMD_ER
Definition: YM2148.cc:22
constexpr byte CMD_TXEN
Definition: I8251.cc:37
constexpr unsigned CMD_TXIE
Definition: YM2148.cc:19
constexpr unsigned CMD_IR
Definition: YM2148.cc:23
constexpr unsigned CMD_RXIE
Definition: YM2148.cc:21
constexpr byte STAT_RXRDY
Definition: I8251.cc:9
constexpr auto CHAR_DURATION
Definition: YM2148.cc:29
constexpr byte STAT_TXRDY
Definition: I8251.cc:8
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
Definition: serialize.hh:1009