summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/machine/micomxe1a.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/machine/micomxe1a.cpp')
-rw-r--r--src/devices/machine/micomxe1a.cpp372
1 files changed, 372 insertions, 0 deletions
diff --git a/src/devices/machine/micomxe1a.cpp b/src/devices/machine/micomxe1a.cpp
new file mode 100644
index 00000000000..d8e0dde9a8b
--- /dev/null
+++ b/src/devices/machine/micomxe1a.cpp
@@ -0,0 +1,372 @@
+// license:BSD-3-Clause
+// copyright-holders:Vas Crabb
+/**********************************************************************
+
+ Dempa Micom Soft Analog/Digital Controller emulation
+
+ PC pin Name MD pin Name Dir Signal
+ 1 Up 1 Up In D0
+ 2 Down 2 Down In D1
+ 3 Left 3 Left In D2
+ 4 Right 4 Right In D3
+ 6 TRIG1 6 TL In L/H
+ 7 TRIG2 9 TR In ACK
+ 8 STROBE 7 TH Out REQ
+
+ In analog mode, data is shifted out as twelve nybbles:
+
+ _ ________________________________________________________________
+ REQ \_________/
+ ____ __ __ __ __ __ __ __ __ __ __ __ __
+ ACK \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/
+ _____ _____ _____ _____ _____ _____
+ L/H _______/ \_____/ \_____/ \_____/ \_____/ \_____/ \__
+ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____
+ D XXXX_____X_____X_____X_____X_____X_____X_____X_____X_____X_____X_____X_____X
+
+ The falling edge on REQ causes data output to start. The host
+ can't control the speed, it just polls the L/H and ACK lines to
+ know when the data is ready to read.
+
+ Nybble D3 D2 D1 D0
+ 1 A/A' B/B' C D
+ 2 E1 E2 Start Select
+ 3 Y7 Y6 Y5 Y4
+ 4 X7 X6 X5 X4
+ 5 Z7 Z6 Z5 Z4
+ 6 RZ7 RZ6 RZ5 RZ4
+ 7 Y3 Y2 Y1 Y0
+ 8 X3 X2 X1 X0
+ 9 Z3 Z2 Z1 Z0
+ 10 RZ3 RZ2 RZ1 RZ0
+ 11 A B A' B'
+ 12 - - - -
+
+ In MD mode, each pair of nybbles is transmitted in reverse
+ order.
+
+ Sharp released assembly language source code for an X68000
+ driver. It uses the following algorithm:
+ 1. Generate falling edge on REQ
+ 2. Wait until L/H is low
+ 3. Wait until ACK is low
+ 4. Read a nybble
+ 5. Wait until L/H is high
+ 6. Wait until ACK is low
+ 7. Read a nybble
+ 8. If eight nybbles have been read, raise REQ.
+ 9. Loop to step 2 until twelve nybbles have been read
+
+ Mega Drive games use a similar approach, but raise REQ after
+ reading two nybbles. PC Engine games only generate a short low
+ pulse on REQ, but use the same algorithm to determine when to
+ read data.
+
+ CSK Research Institute games for FM Towns (including After
+ Burner III and Galaxy Force II) use a different algorithm:
+ 1. Generate falling edge on REQ
+ 2. Wait until L/H is high
+ 3. Wait until ACK is high
+ 4. Read a nybble
+ 5. Wait until L/H is low
+ 6. Wait until ACK is high
+ 7. Read a nybble
+ 8. Wait until L/H is high
+ 9. Wait until ACK is high
+ 10. Read a nybble
+ 11. Loop to step 5 until eleven nybbles have been read
+ 11. Raise REQ
+
+ From this it can be deduced that:
+ * A negative edge on REQ triggers a report.
+ * The exact time REQ is held low isn't important.
+ * Data is valid while ACK is low and for some time after ACK is
+ raised.
+ * L/H is low when idle and changes some time before data is
+ updated.
+
+ In digital mode, REQ is a simple multiplexer input:
+
+ REQ 0 1
+ D0 Up Throttle Up
+ D1 Down Throttle Down
+ D2 Left C
+ D3 Right D
+ L/H A/A' E1
+ ACK B/B' E2
+
+ Start appears as simultaneous Left/Right
+ Select appears as simultaneous Up/Down
+
+ This mode is almost compatible with a 6-button Towns Pad (on a
+ real 6-button Towns Pad, buttons A and B can be read in either
+ state, they bypass the multiplexer).
+
+ Digital MD mode emulates a 3-button Mega Drive pad:
+
+ REQ 0 1
+ D0 Up Up
+ D1 Down Down
+ D2 0 Left
+ D3 0 Right
+ L/H A B
+ ACK Start C
+
+ TODO:
+ * Dump MB88513 microcontroller from original controller.
+ * Measure timings.
+ - Timings currently fudged for CRI games in FM Towns.
+ * Latch data at beginning of packet.
+ * Confirm button mapping in digital mode.
+ * Estimate thresholds in digital modes.
+ * Implement trigger A/B rapid fire switches.
+ * Implement channel shift switch (Y->X, X->Z, Z->X).
+ * Does channel shift affect digital mode?
+ * Implement special modes (holding buttons on power-on):
+ - Double displacement modes:
+ + X/Y (hold SELECT + A')
+ + Z (hold SELECT + B')
+ + X/Y/Z (hold SELECT + A' + B')
+ - Up/down reverse mode (hold C)
+ * Implement desktop (XE-1AJ/CZ-8NJ2) version:
+ - Four analog channels
+ - E1/E2 on a rocker switch (can't press simultaneously)
+ - Hold mode for A and B triggers
+ - Variable rapid fire rate for A and B triggers
+ - Reset button
+ - Different special modes
+ - No Mega Drive mode
+ - Start and Select not reported in digital mode
+
+**********************************************************************/
+
+#include "emu.h"
+#include "micomxe1a.h"
+
+//#define VERBOSE 1
+//#define LOG_OUTPUT_FUNC osd_printf_info
+#include "logmacro.h"
+
+
+DEFINE_DEVICE_TYPE(MICOM_XE_1A, micom_xe_1a_device, "micom_xe_1a", "Dempa Micom Soft Analog/Digital Intelligent Controller")
+
+
+
+micom_xe_1a_device::micom_xe_1a_device(
+ machine_config const &mconfig,
+ char const *tag,
+ device_t *owner,
+ u32 clock):
+ device_t(mconfig, MICOM_XE_1A, tag, owner, clock),
+ m_buttons_callback(*this, 0xffff),
+ m_analog_callback(*this, 0x00),
+ m_output_timer(nullptr),
+ m_req(1),
+ m_mode(1),
+ m_interface(0),
+ m_out(0x2f)
+{
+}
+
+micom_xe_1a_device::~micom_xe_1a_device()
+{
+}
+
+
+u8 micom_xe_1a_device::out_r()
+{
+ if (m_mode)
+ {
+ LOG("%s: analog mode read data = %02X\n", machine().describe_context(), m_out);
+ return m_out;
+ }
+ else
+ {
+ u16 const buttons = m_buttons_callback();
+ if (m_interface)
+ {
+ u8 const y = m_analog_callback(0);
+ if (m_req)
+ {
+ u8 const x = m_analog_callback(1);
+ u8 const result =
+ ((0x40 <= y) ? 0x01 : 0x00) | // Up
+ ((0xc0 > y) ? 0x02 : 0x00) | // Down
+ ((0x40 <= x) ? 0x04 : 0x00) | // Left
+ ((0xc0 > x) ? 0x08 : 0x00) | // Right
+ ((BIT(buttons, 2) & BIT(buttons, 8)) << 4) | // B/B'
+ (BIT(buttons, 1) << 5); // C
+ LOG(
+ "%s: MD digital mode basic read = 0x%02X\n",
+ machine().describe_context(),
+ result);
+ return result;
+ }
+ else
+ {
+ u8 const result =
+ ((0x40 <= y) ? 0x01 : 0x00) | // Up
+ ((0xc0 > y) ? 0x02 : 0x00) | // Down
+ ((BIT(buttons, 3) & BIT(buttons, 9)) << 4) | // A/A'
+ (BIT(buttons, 5) << 5); // Start
+ LOG(
+ "%s: MD digital mode extended read = 0x%02X\n",
+ machine().describe_context(),
+ result);
+ return result;
+ }
+ }
+ else
+ {
+ if (m_req)
+ {
+ u8 const z = m_analog_callback(2);
+ u8 const result =
+ ((0x40 <= z) ? 0x01 : 0x00) | // Throttle Up
+ ((0xc0 > z) ? 0x02 : 0x00) | // Throttle Down
+ (BIT(buttons, 1) << 2) | // C
+ (BIT(buttons, 0) << 3) | // D
+ (BIT(buttons, 7) << 4) | // E1
+ (BIT(buttons, 6) << 5); // E2
+ LOG(
+ "%s: digital mode extended read = 0x%02X\n",
+ machine().describe_context(),
+ result);
+ return result;
+ }
+ else
+ {
+ u8 const y = m_analog_callback(0);
+ u8 const x = m_analog_callback(1);
+ u8 const result =
+ ((BIT(buttons, 4) && (0x40 <= y)) ? 0x01 : 0x00) | // Select/Up
+ ((BIT(buttons, 4) && (0xc0 > y)) ? 0x02 : 0x00) | // Select/Down
+ ((BIT(buttons, 5) && (0x40 <= x)) ? 0x04 : 0x00) | // Start/Left
+ ((BIT(buttons, 5) && (0xc0 > x)) ? 0x08 : 0x00) | // Start/Right
+ ((BIT(buttons, 3) & BIT(buttons, 9)) << 4) | // A/A'
+ ((BIT(buttons, 2) & BIT(buttons, 8)) << 5); // B/B'
+ LOG(
+ "%s: digital mode basic read = 0x%02X\n",
+ machine().describe_context(),
+ result);
+ return result;
+ }
+ }
+ }
+}
+
+
+void micom_xe_1a_device::req_w(int state)
+{
+ u8 const req = state ? 1 : 0;
+ if (req != m_req)
+ {
+ if (m_mode)
+ {
+ LOG("%s: /REQ = %u\n", machine().describe_context(), req);
+ if (!req)
+ {
+ // acquire data
+ u16 const buttons = m_buttons_callback();
+ u8 analog[4];
+ for (unsigned i = 0; std::size(analog) > i; ++i)
+ analog[i] = m_analog_callback(i);
+
+ // pack data
+ m_data[0] = BIT(buttons, 0, 8) & ((BIT(buttons, 8, 2) << 2) | 0xf3);
+ m_data[1] = BIT(analog[0], 4, 4) | (BIT(analog[1], 4, 4) << 4);
+ m_data[2] = BIT(analog[2], 4, 4) | (BIT(analog[3], 4, 4) << 4);
+ m_data[3] = BIT(analog[0], 0, 4) | (BIT(analog[1], 0, 4) << 4);
+ m_data[4] = BIT(analog[2], 0, 4) | (BIT(analog[3], 0, 4) << 4);
+ m_data[5] = BIT(buttons, 8, 8) & ((BIT(buttons, 2, 2) << 2) | 0xf3);
+
+ // takes a while to respond
+ m_output_timer->adjust(attotime::from_nsec(50'000), 0);
+ }
+ }
+ else
+ {
+ LOG("%s: /REQ = %u ignored in digital mode\n", machine().describe_context(), req);
+ }
+ m_req = req;
+ }
+}
+
+
+void micom_xe_1a_device::mode_w(int state)
+{
+ u8 const mode = state ? 1 : 0;
+ if (mode != m_mode)
+ {
+ if (mode)
+ {
+ LOG("Analog mode selected\n");
+ }
+ else
+ {
+ LOG("Digital mode selected\n");
+ m_output_timer->enable(false);
+ m_out = 0x2f;
+ }
+ m_mode = mode;
+ }
+}
+
+
+void micom_xe_1a_device::interface_w(int state)
+{
+ m_interface = state ? 1 : 0;
+}
+
+
+void micom_xe_1a_device::device_start()
+{
+ m_output_timer = timer_alloc(FUNC(micom_xe_1a_device::step_output), this);
+
+ std::fill(std::begin(m_data), std::end(m_data), 0x00);
+ m_out = 0x2f;
+
+ save_item(NAME(m_req));
+ save_item(NAME(m_mode));
+ save_item(NAME(m_interface));
+ save_item(NAME(m_data));
+ save_item(NAME(m_out));
+}
+
+
+TIMER_CALLBACK_MEMBER(micom_xe_1a_device::step_output)
+{
+ auto const step = param >> 1;
+ if (!BIT(param, 0))
+ {
+ m_out = (m_out & 0x0f) | (BIT(step, 0) ? 0x30 : 0x20);
+ LOG(
+ "Set nybble %u data = 0x%X, L/H = %u, /ACK = %u\n",
+ step,
+ BIT(m_out, 0, 4),
+ BIT(m_out, 4),
+ BIT(m_out, 5));
+ if ((std::size(m_data) * 2) > step)
+ {
+ m_output_timer->adjust(attotime::from_nsec(10'000), param + 1);
+ }
+ }
+ else
+ {
+ if ((std::size(m_data) * 2) > step)
+ {
+ auto const nybble = step ^ m_interface;
+ if ((std::size(m_data) * 2) > step)
+ m_out = BIT(m_data[nybble >> 1], BIT(nybble, 0) ? 4 : 0, 4) | (m_out & 0x10);
+ else
+ m_out = 0x0f | (m_out & 0x10);
+ LOG(
+ "Set nybble %u data = 0x%X, L/H = %u, /ACK = %u\n",
+ step,
+ BIT(m_out, 0, 4),
+ BIT(m_out, 4),
+ BIT(m_out, 5));
+ m_output_timer->adjust(attotime::from_nsec(10'000), param + 1);
+ }
+ }
+}