summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/bus/sms_ctrl/mouse.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/bus/sms_ctrl/mouse.cpp')
-rw-r--r--src/devices/bus/sms_ctrl/mouse.cpp264
1 files changed, 264 insertions, 0 deletions
diff --git a/src/devices/bus/sms_ctrl/mouse.cpp b/src/devices/bus/sms_ctrl/mouse.cpp
new file mode 100644
index 00000000000..bf34dc8f5fd
--- /dev/null
+++ b/src/devices/bus/sms_ctrl/mouse.cpp
@@ -0,0 +1,264 @@
+// license:BSD-3-Clause
+// copyright-holders:Vas Crabb
+/**********************************************************************
+
+ Sega Mega Mouse emulation
+
+ The Sega Mouse sold in Japan and Europe has only two buttons but
+ is otherwise compatible.
+
+**********************************************************************/
+
+#include "emu.h"
+#include "mouse.h"
+
+#include <algorithm>
+
+//#define VERBOSE 1
+//#define LOG_OUTPUT_FUNC osd_printf_info
+#include "logmacro.h"
+
+
+namespace {
+
+INPUT_PORTS_START( sms_megamouse )
+ PORT_START("BUTTONS")
+ PORT_BIT(0x01, IP_ACTIVE_HIGH, IPT_BUTTON1) PORT_NAME("%p A (Left Button)")
+ PORT_BIT(0x02, IP_ACTIVE_HIGH, IPT_BUTTON2) PORT_NAME("%p C (Right Button)")
+ PORT_BIT(0x04, IP_ACTIVE_HIGH, IPT_BUTTON3) PORT_NAME("%p B (Middle Button)")
+ PORT_BIT(0x08, IP_ACTIVE_HIGH, IPT_START)
+
+ PORT_START("X")
+ PORT_BIT( 0x1fff, 0x0000, IPT_MOUSE_X ) PORT_SENSITIVITY(50) PORT_KEYDELTA(1)
+
+ PORT_START("Y")
+ PORT_BIT( 0x1fff, 0x0000, IPT_MOUSE_Y ) PORT_SENSITIVITY(50) PORT_KEYDELTA(1) PORT_REVERSE
+INPUT_PORTS_END
+
+
+INPUT_PORTS_START( sms_segamouse )
+ PORT_INCLUDE(sms_megamouse)
+
+ PORT_MODIFY("BUTTONS")
+ PORT_BIT(0x01, IP_ACTIVE_HIGH, IPT_BUTTON1) PORT_NAME("%p Left Button")
+ PORT_BIT(0x02, IP_ACTIVE_HIGH, IPT_BUTTON2) PORT_NAME("%p Right Button")
+ PORT_BIT(0x0c, IP_ACTIVE_HIGH, IPT_UNUSED)
+INPUT_PORTS_END
+
+
+
+class sms_megamouse_device : public device_t, public device_sms_control_interface
+{
+public:
+ sms_megamouse_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock);
+
+ virtual u8 in_r() override { return m_out; }
+ virtual void out_w(u8 data, u8 mem_mask) override;
+
+protected:
+ sms_megamouse_device(machine_config const &mconfig, device_type type, char const *tag, device_t *owner, u32 clock);
+
+ virtual ioport_constructor device_input_ports() const override { return INPUT_PORTS_NAME(sms_megamouse); }
+ virtual void device_start() override ATTR_COLD;
+
+private:
+ TIMER_CALLBACK_MEMBER(next_step);
+
+ required_ioport m_buttons;
+ required_ioport_array<2> m_axes;
+
+ emu_timer *m_busy_timer;
+
+ u16 m_base[2];
+ s16 m_count[2];
+ u8 m_th_in;
+ u8 m_tr_in;
+ u8 m_out;
+ u8 m_phase;
+};
+
+
+sms_megamouse_device::sms_megamouse_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock) :
+ sms_megamouse_device(mconfig, SMS_MEGAMOUSE, tag, owner, clock)
+{
+}
+
+
+sms_megamouse_device::sms_megamouse_device(machine_config const &mconfig, device_type type, char const *tag, device_t *owner, u32 clock) :
+ device_t(mconfig, type, tag, owner, clock),
+ device_sms_control_interface(mconfig, *this),
+ m_buttons(*this, "BUTTONS"),
+ m_axes(*this, "%c", 'X'),
+ m_busy_timer(nullptr),
+ m_th_in(1),
+ m_tr_in(1),
+ m_out(0x30),
+ m_phase(0)
+{
+}
+
+
+void sms_megamouse_device::out_w(u8 data, u8 mem_mask)
+{
+ u8 const th = BIT(data, 6);
+ u8 const tr = BIT(data, 5);
+ if (th != m_th_in)
+ {
+ // seems to respond to TH pretty quickly
+ if (th)
+ {
+ LOG("%s: TH rising, deselected\n", machine().describe_context());
+ m_busy_timer->enable(false);
+ m_out = 0x30;
+ m_phase = 0;
+ }
+ else
+ {
+ LOG("%s: TH falling, selected\n", machine().describe_context());
+ m_out = 0x3b;
+
+ if (!tr)
+ {
+ LOG("%s: TR low, wait for data update\n", machine().describe_context());
+ m_busy_timer->adjust(attotime::from_nsec(7200), 0);
+ }
+ }
+ m_th_in = th;
+ m_tr_in = tr;
+ }
+ else if (!m_th_in && (tr != m_tr_in))
+ {
+ if (m_busy_timer->enabled())
+ {
+ LOG(
+ "%s: busy, deferring response to TH %s\n",
+ machine().describe_context(),
+ th ? "rising" : "falling");
+ }
+ else
+ {
+ LOG(
+ "%s: TR %s, wait for data update\n",
+ machine().describe_context(),
+ tr ? "rising" : "falling");
+ m_busy_timer->adjust(attotime::from_nsec(7200), tr);
+ }
+ m_tr_in = tr;
+ }
+}
+
+
+void sms_megamouse_device::device_start()
+{
+ m_busy_timer = timer_alloc(FUNC(sms_megamouse_device::next_step), this);
+
+ std::fill(std::begin(m_base), std::end(m_base), 0);
+ std::fill(std::begin(m_count), std::end(m_count), 0);
+ m_out = 0x30;
+ m_phase = 0;
+
+ save_item(NAME(m_base));
+ save_item(NAME(m_count));
+ save_item(NAME(m_th_in));
+ save_item(NAME(m_tr_in));
+ save_item(NAME(m_out));
+ save_item(NAME(m_phase));
+}
+
+
+TIMER_CALLBACK_MEMBER(sms_megamouse_device::next_step)
+{
+ if (BIT(param, 1))
+ {
+ u8 const tr = BIT(param, 0);
+ LOG("update TL = %u\n", tr);
+ if (tr)
+ m_out |= 0x10;
+ else
+ m_out &= ~0x10;
+
+ assert(!m_th_in);
+ if (tr != m_tr_in)
+ {
+ LOG("TR %s, wait for data update\n", tr ? "high" : "low");
+ m_busy_timer->adjust(attotime::from_nsec(7200), tr);
+ }
+ }
+ else
+ {
+ switch (m_phase)
+ {
+ case 0:
+ if (!BIT(param, 0))
+ {
+ for (unsigned i = 0; m_axes.size() > i; ++i)
+ {
+ u16 const current = m_axes[i]->read();
+ m_count[i] = s16(current) - s16(m_base[i]);
+ m_base[i] = current;
+ if (0x1000 < m_count[i])
+ m_count[i] -= 0x2000;
+ else if (-0x1000 > m_count[i])
+ m_count[i] += 0x2000;
+ }
+ LOG("movement (%d %d)\n", m_count[0], m_count[1]);
+ }
+ m_out = (m_out & 0x30) | 0x0f;
+ LOG("update data unused = 0x%X\n", m_out & 0x0f);
+ break;
+ case 1:
+ if (BIT(param, 0))
+ {
+ m_out = (m_out & 0x30) | (m_buttons->read() & 0x0f);
+ LOG("update data buttons = 0x%X\n", m_out & 0x0f);
+ }
+ else
+ {
+ m_out =
+ (m_out & 0x30) |
+ ((util::sext(m_count[1], 9) != m_count[1]) ? 0x80 : 0x00) |
+ ((util::sext(m_count[0], 9) != m_count[0]) ? 0x40 : 0x00) |
+ (BIT(m_count[1], 8) << 1) |
+ (BIT(m_count[0], 8) << 0);
+ LOG("update data flags = 0x%X\n", m_out & 0x0f);
+ }
+ break;
+ case 2:
+ case 3:
+ m_out = (m_out & 0x30) | BIT(m_count[BIT(m_phase, 0)], BIT(param, 0) ? 0 : 4, 4);
+ LOG(
+ "update data %c %s = 0x%X\n",
+ 'X' + BIT(m_phase, 0),
+ BIT(param, 0) ? "low" : "high",
+ m_out & 0x0f);
+ break;
+ }
+ if (BIT(param, 0))
+ m_phase = (m_phase + 1) & 0x03;
+ m_busy_timer->adjust(attotime::from_nsec(4800), param | 0x02);
+ }
+}
+
+
+
+class sms_segamouse_device : public sms_megamouse_device
+{
+public:
+ sms_segamouse_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock);
+
+protected:
+ virtual ioport_constructor device_input_ports() const override { return INPUT_PORTS_NAME(sms_segamouse); }
+};
+
+
+sms_segamouse_device::sms_segamouse_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock) :
+ sms_megamouse_device(mconfig, SMS_SEGAMOUSE, tag, owner, clock)
+{
+}
+
+} // anonymous namespace
+
+
+
+DEFINE_DEVICE_TYPE_PRIVATE(SMS_MEGAMOUSE, device_sms_control_interface, sms_megamouse_device, "sms_megamouse", "Sega Mega Mouse (US)")
+DEFINE_DEVICE_TYPE_PRIVATE(SMS_SEGAMOUSE, device_sms_control_interface, sms_segamouse_device, "sms_segamouse", "Sega Mega Drive Mouse (World)")