// license:BSD-3-Clause // copyright-holders:R. Belmont /*************************************************************************** Roland MPU-401 core This emulates the MPU-401 external box with the 6801, ASIC, and RAM in it. We do it this way to facilitate the various PC, Apple II, C64, and other possible hookups. 6801 GPIO port hookups (from the schematics) P10 / P11 / P12: drive the metronome and speaker P13 / P14 / P15: drive 3 pins on the SYNC OUT connector P16: to DSRD on gate array P17: to DRRD on gate array P20: to SYC OUT on gate array P21: to SYC IN on gate array, pulled up to Vcc via 4.7K resistor programmed as output of timer (OLVL) P22: to SRCK on gate array, inverted P23: MIDI IN serial data (SCI in) P24: MIDI OUT serial data (SCI out) ASIC addresses from the 6801: 0x20: (r) read pending byte from the PC (w) apparently nothing 0x21: (r) ASIC status, see STAT_xxx bits below (w) send new byte to PC data port Theory of operation: 6801's timer/counter is set up to drive a pulse stream out P21 to the ASIC's SYC IN pin. The ASIC in turn generates the MIDI baud rate (times 8) and returns that on pin P22. The 6801 is believed to run in mode 2, based on a combination of the schematics and the behavior (ie, internal RAM from 80-FF is clearly present from the program's behavior, and ports 3/4 are obviously external address/data buses) ***************************************************************************/ #include "emu.h" #include "machine/mpu401.h" #include "bus/midi/midi.h" #define M6801_TAG "mpu6801" #define ROM_TAG "mpurom" #define MIDIIN_TAG "mdin" #define MIDIOUT_TAG "mdout" #define P2_SYNC_OUT (0x01) #define P2_SYNC_IN (0x02) #define P2_SRCK_OUT (0x04) #define P2_MIDI_IN (0x08) #define P2_MIDI_OUT (0x10) #define STAT_CMD_PORT (0x01) // set if the new byte indicated by TX FULL was written to the command port, clear for data port #define STAT_TX_FULL (0x40) // indicates the PC has written a new byte we haven't read yet #define STAT_RX_EMPTY (0x80) // indicates we've written a new byte the PC hasn't read yet void mpu401_device::mpu401_map(address_map &map) { map(0x0000, 0x001f).rw(FUNC(mpu401_device::regs_mode2_r), FUNC(mpu401_device::regs_mode2_w)); map(0x0020, 0x0021).rw(FUNC(mpu401_device::asic_r), FUNC(mpu401_device::asic_w)); map(0x0080, 0x00ff).ram(); // on-chip RAM map(0x0800, 0x0fff).ram(); // external RAM map(0xf000, 0xffff).rom().region(ROM_TAG, 0); } void mpu401_device::mpu401_io_map(address_map &map) { map(M6801_PORT1, M6801_PORT1).rw(FUNC(mpu401_device::port1_r), FUNC(mpu401_device::port1_w)); map(M6801_PORT2, M6801_PORT2).rw(FUNC(mpu401_device::port2_r), FUNC(mpu401_device::port2_w)); } ROM_START( mpu401 ) ROM_REGION(0x1000, ROM_TAG, 0) ROM_LOAD( "roland__6801v0b55p__15179222.bin", 0x000000, 0x001000, CRC(65d3a151) SHA1(00efbfb96aeb997b69bb16981c6751d3c784bb87) ) /* Mask MCU; Label: "Roland // 6801V0B55P // 5A1 JAPAN // 15179222"; This is the final version (1.5A) of the mpu401 firmware; version is located at offsets 0x649 (0x15) and 0x64f (0x01) */ ROM_END //************************************************************************** // GLOBAL VARIABLES //************************************************************************** DEFINE_DEVICE_TYPE(MPU401, mpu401_device, "mpu401", "Roland MPU-401 I/O box") //------------------------------------------------- // device_add_mconfig - add device configuration //------------------------------------------------- MACHINE_CONFIG_START(mpu401_device::device_add_mconfig) MCFG_DEVICE_ADD(M6801_TAG, M6801, 4000000) /* 4 MHz as per schematics */ MCFG_DEVICE_PROGRAM_MAP(mpu401_map) MCFG_DEVICE_IO_MAP(mpu401_io_map) MCFG_M6801_SER_TX(WRITELINE(MIDIOUT_TAG, midi_port_device, write_txd)) MCFG_MIDI_PORT_ADD(MIDIIN_TAG, midiin_slot, "midiin") MCFG_MIDI_RX_HANDLER(WRITELINE(DEVICE_SELF, mpu401_device, midi_rx_w)) MCFG_MIDI_PORT_ADD(MIDIOUT_TAG, midiout_slot, "midiout") MACHINE_CONFIG_END //------------------------------------------------- // rom_region - device-specific ROM region //------------------------------------------------- const tiny_rom_entry *mpu401_device::device_rom_region() const { return ROM_NAME( mpu401 ); } //************************************************************************** // LIVE DEVICE //************************************************************************** //------------------------------------------------- // mpu401_device - constructor //------------------------------------------------- mpu401_device::mpu401_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : device_t(mconfig, MPU401, tag, owner, clock), m_ourcpu(*this, M6801_TAG), write_irq(*this) { } //------------------------------------------------- // device_start - device-specific startup //------------------------------------------------- void mpu401_device::device_start() { write_irq.resolve_safe(); m_timer = timer_alloc(0, nullptr); } //------------------------------------------------- // device_reset - device-specific reset //------------------------------------------------- void mpu401_device::device_reset() { m_port2 = 0xff & ~(P2_SRCK_OUT | P2_MIDI_IN); // prevent spurious reception m_command = 0; m_mpudata = 0; m_gatearrstat = 0; m_timer->adjust(attotime::zero, 0, attotime::from_hz(31250*8)); } //------------------------------------------------- // device_timer - called when our device timer expires //------------------------------------------------- void mpu401_device::device_timer(emu_timer &timer, device_timer_id tid, int param, void *ptr) { m_ourcpu->m6801_clock_serial(); } READ8_MEMBER(mpu401_device::regs_mode2_r) { switch (offset) { case 4: case 5: case 6: case 7: case 0xf: // logerror("MPU401: read @ unk %x %s\n", offset, machine().describe_context()); break; default: return m_ourcpu->m6801_io_r(space, offset); } return 0xff; } WRITE8_MEMBER(mpu401_device::regs_mode2_w) { switch (offset) { case 4: case 5: case 6: case 7: case 0xf: // logerror("MPU401: %02x @ unk %x %s\n", data, offset, machine().describe_context()); break; default: return m_ourcpu->m6801_io_w(space, offset, data); } } READ8_MEMBER(mpu401_device::port1_r) { return 0xff; } WRITE8_MEMBER(mpu401_device::port1_w) { // printf("port1_w: %02x met %x syncout %x DSRD %d DRRD %d\n", data, data & 3, (data>>3) & 3, (data>>6) & 1, (data>>7) & 1); } READ8_MEMBER(mpu401_device::port2_r) { // printf("%s Read P2\n", machine().describe_context().c_str()); return m_port2; } WRITE8_MEMBER(mpu401_device::port2_w) { // printf("port2_w: %02x SYCOUT %d SYCIN %d SRCK %d MIDI OUT %d\n", data, (data & 1), (data>>1) & 1, (data>>2) & 1, (data>>4) & 1); } READ8_MEMBER(mpu401_device::mpu_r) { // printf("mpu_r @ %d\n", offset); if (offset == 1) // status { return m_gatearrstat; } else // data { write_irq(CLEAR_LINE); m_gatearrstat |= STAT_RX_EMPTY; return m_mpudata; } } WRITE8_MEMBER(mpu401_device::mpu_w) { // printf("%02x to MPU-401 @ %d\n", data, offset); m_command = data; m_gatearrstat |= STAT_TX_FULL; if (offset == 1) { m_gatearrstat |= STAT_CMD_PORT; } else { m_gatearrstat &= ~STAT_CMD_PORT; } } READ8_MEMBER(mpu401_device::asic_r) { if (offset == 0) { m_gatearrstat &= ~STAT_TX_FULL; return m_command; } else if (offset == 1) { return m_gatearrstat; } return 0xff; } WRITE8_MEMBER(mpu401_device::asic_w) { // printf("MPU401: %02x to gate array @ %d\n", data, offset); if (offset == 1) { m_mpudata = data; m_gatearrstat &= ~STAT_RX_EMPTY; write_irq(ASSERT_LINE); } } // MIDI receive WRITE_LINE_MEMBER( mpu401_device::midi_rx_w ) { if (state == ASSERT_LINE) { m_port2 |= P2_MIDI_IN; } else { m_port2 &= ~P2_MIDI_IN; } }