summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/machine/ym3802.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/machine/ym3802.cpp')
-rw-r--r--src/devices/machine/ym3802.cpp286
1 files changed, 286 insertions, 0 deletions
diff --git a/src/devices/machine/ym3802.cpp b/src/devices/machine/ym3802.cpp
new file mode 100644
index 00000000000..6afd4ea9a12
--- /dev/null
+++ b/src/devices/machine/ym3802.cpp
@@ -0,0 +1,286 @@
+// license:BSD-3-Clause
+// copyright-holders:Barry Rodewald
+/*
+ * ym3802.c - Yamaha MCS MIDI Communication and Service Controller
+ *
+ * TODO:
+ * - Receive serial data
+ * - Transmit Idle detection
+ * - IRx/ITx (used for MIDI system messages)
+ * - FSK modulation
+ * - Timers
+ * - Interrupts (except for Tx Buffer Empty)
+ */
+
+#include "emu.h"
+#include "ym3802.h"
+
+
+DEFINE_DEVICE_TYPE(YM3802, ym3802_device, "ym3802", "Yamaha YM3802 MCS MIDI Communication and Service Controller")
+
+ym3802_device::ym3802_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : device_t(mconfig, YM3802, tag, owner, clock)
+ , device_serial_interface(mconfig, *this)
+ , m_irq_handler(*this)
+ , m_txd_handler(*this)
+ , m_rxd_handler(*this)
+ , m_reg(REG_MAX)
+ , m_wdr(0)
+ , m_irq_status(0)
+ , m_vector(0)
+ , m_clkm_rate(500000) // TODO: make these configurable
+ , m_clkf_rate(614400)
+ {
+ }
+
+void ym3802_device::device_start()
+{
+ m_irq_handler.resolve_safe();
+ m_txd_handler.resolve_safe();
+ m_rxd_handler.resolve_safe(0xff);
+ m_clock_timer = timer_alloc(TIMER_SYSTEM_CLOCK);
+ m_midi_timer = timer_alloc(TIMER_MIDI_CLOCK);
+ save_item(NAME(m_reg));
+}
+
+void ym3802_device::device_reset()
+{
+ m_reg.clear();
+ reset_irq(0xff);
+ transmit_register_reset();
+ receive_register_reset();
+ reset_midi_timer();
+ set_comms_mode();
+}
+
+void ym3802_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
+{
+ // TODO: support clock and timers
+ switch(id)
+ {
+ case TIMER_MIDI_CLOCK:
+ transmit_clk();
+ break;
+ }
+}
+
+void ym3802_device::set_irq(uint8_t irq)
+{
+ uint8_t x;
+
+ m_irq_status |= (irq & m_reg[REG_IER]);
+ for(x=0;x<8;x++)
+ {
+ if(m_irq_status & (1 << x))
+ break;
+ }
+ m_vector = (m_reg[REG_IOR] & 0xe0) | (x << 1);
+ if(m_irq_status != 0)
+ m_irq_handler(ASSERT_LINE);
+}
+
+void ym3802_device::reset_irq(uint8_t irq)
+{
+ m_irq_status &= ~irq;
+ if(m_irq_status == 0)
+ m_irq_handler(CLEAR_LINE);
+}
+
+void ym3802_device::transmit_clk()
+{
+ if(m_reg[REG_TCR] & 0x01) // Tx Enable
+ {
+ if(!m_tx_fifo.empty())
+ {
+ if (is_transmit_register_empty())
+ {
+ transmit_register_setup(m_tx_fifo.front()); // start to send first byte in FIFO
+ m_tx_fifo.pop(); // and remove it from the FIFO
+ if(m_tx_fifo.empty())
+ set_irq(IRQ_FIFOTX_EMPTY);
+ }
+ }
+ /* if diserial has bits to send, make them so */
+ if (!is_transmit_register_empty())
+ {
+ uint8_t data = transmit_register_get_data_bit();
+ m_tx_busy = true;
+ m_txd_handler(data);
+ }
+ if (m_tx_fifo.empty() && is_transmit_register_empty())
+ m_tx_busy = false;
+ }
+}
+
+void ym3802_device::reset_midi_timer()
+{
+ uint64_t rate;
+ uint8_t divisor = m_reg[REG_RRR] & 0x1f;
+
+ if(!(divisor & 0x10))
+ {
+ if(divisor & 0x08)
+ rate = m_clkm_rate / 32;
+ else
+ rate = m_clkm_rate / 16;
+ }
+ else
+ {
+ if(!(divisor & 0x08))
+ rate = m_clkf_rate / 32;
+ else
+ {
+ switch(divisor & 0x07)
+ {
+ case 0:
+ rate = m_clkf_rate / 64;
+ break;
+ case 1:
+ rate = m_clkf_rate / 128;
+ break;
+ case 2:
+ rate = m_clkf_rate / 256;
+ break;
+ case 3:
+ rate = m_clkf_rate / 512;
+ break;
+ case 4:
+ rate = m_clkf_rate / 1024;
+ break;
+ case 5:
+ rate = m_clkf_rate / 2048;
+ break;
+ case 6:
+ rate = m_clkf_rate / 4096;
+ break;
+ case 7:
+ rate = m_clkf_rate / 8192;
+ break;
+ }
+ }
+ }
+
+ if(rate != m_prev_rate)
+ m_midi_timer->adjust(attotime::from_hz(rate),0,attotime::from_hz(rate));
+ m_prev_rate = rate;
+ logerror("MIDI Timer rate set to %iHz\n",rate);
+}
+
+void ym3802_device::set_comms_mode()
+{
+ uint8_t data_bits = (m_reg[REG_TMR] & 0x20) ? 7 : 8;
+ parity_t parity;
+ stop_bits_t stop_bits = (m_reg[REG_TMR] & 0x02) ? STOP_BITS_2 : STOP_BITS_1;
+
+ if(!(m_reg[REG_TMR] & 0x10)) // parity enable
+ parity = PARITY_NONE;
+ else
+ {
+ if(m_reg[REG_TMR] & 0x04)
+ parity = PARITY_ODD;
+ else
+ parity = PARITY_EVEN;
+ // TODO: 4-bit parity
+ }
+
+ set_data_frame(1, data_bits, parity, stop_bits);
+ logerror("MIDI comms set to 1 start bit, %i data bits, %s, parity = %i\n",data_bits, (stop_bits == STOP_BITS_2) ? "2 stop bits" : "1 stop bit", parity);
+}
+
+READ8_MEMBER(ym3802_device::read)
+{
+ if(offset < 4)
+ {
+ if(offset == 3)
+ return m_wdr;
+ if(offset == 2)
+ return m_irq_status;
+ if(offset == 0)
+ return m_vector;
+ return m_reg[offset];
+ }
+ else
+ {
+ uint8_t bank = m_reg[REG_RGR] & 0x0f;
+ uint8_t ret = 0;
+
+ if(bank > 9)
+ return m_wdr;
+
+ switch(offset + (bank * 10))
+ {
+ case REG_TSR:
+ if(m_tx_fifo.empty())
+ ret |= 0x80;
+ if(m_tx_fifo.size() < 16)
+ ret |= 0x40;
+ if(m_tx_busy)
+ ret |= 0x01;
+ break;
+ default:
+ ret = m_reg[offset + (bank * 10)];
+ }
+ return ret;
+ }
+}
+
+WRITE8_MEMBER(ym3802_device::write)
+{
+ m_wdr = data;
+ if(offset == 1)
+ {
+ m_reg[REG_RGR] = data & 0x0f;
+ if(data & 0x80)
+ device_reset();
+ logerror("MIDI: writing %02x to reg %i\n",data,offset);
+ }
+ if(offset == 3)
+ reset_irq(data);
+ if(offset > 4)
+ {
+ uint8_t bank = m_reg[REG_RGR] & 0x0f;
+
+ if(bank > 9)
+ return;
+
+ m_reg[offset + (bank * 10)] = data;
+ logerror("MIDI: writing %02x to reg %i\n",data,offset + (bank * 10));
+
+ switch(offset + (bank * 10))
+ {
+ case REG_IOR:
+ popmessage("IOR vector write %02\n",data);
+ break;
+ case REG_IER:
+ logerror("IER set to %02x\n",data);
+ break;
+ case REG_TMR:
+ set_comms_mode();
+ break;
+ case REG_TCR:
+ if(data & 0x01)
+ reset_midi_timer();
+ break;
+ case REG_TDR:
+ m_tx_fifo.push(data);
+ reset_irq(IRQ_FIFOTX_EMPTY);
+ break;
+ case REG_GTR_LOW:
+ m_general_counter = (m_general_counter & 0xff00) | data;
+ //popmessage("General counter set to %i\n",m_general_counter);
+ break;
+ case REG_GTR_HIGH:
+ m_general_counter = (m_general_counter & 0x00ff) | ((data & 0x3f) << 8);
+ //popmessage("General counter set to %i\n",m_general_counter);
+ break;
+ case REG_MTR_LOW:
+ m_midi_counter = (m_midi_counter & 0xff00) | data;
+ //popmessage("MIDI counter set to %i\n",m_midi_counter);
+ break;
+ case REG_MTR_HIGH:
+ m_midi_counter = (m_midi_counter & 0x00ff) | ((data & 0x3f) << 8);
+ //popmessage("MIDI counter set to %i\n",m_midi_counter);
+ break;
+ }
+ }
+}