summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/machine/i2chle.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/machine/i2chle.cpp')
-rw-r--r--src/devices/machine/i2chle.cpp276
1 files changed, 276 insertions, 0 deletions
diff --git a/src/devices/machine/i2chle.cpp b/src/devices/machine/i2chle.cpp
new file mode 100644
index 00000000000..93a54826c11
--- /dev/null
+++ b/src/devices/machine/i2chle.cpp
@@ -0,0 +1,276 @@
+// license:BSD-3-Clause
+// copyright-holders:R. Belmont
+/*
+ I2C HLE mix-in
+ by R. Belmont
+
+ A mix-in to allow devices to speak I2C without reimplementing
+ the protocol themselves.
+
+ If the device returns data over I2C, override read_data.
+ If the device is written data over I2C, override write_data.
+ If you want the mix-in's logging to show your device name, override
+ get_tag() with something that just returns your tag().
+
+ This mix-in will auto-increment the address on repeated reads/writes,
+ it's up to your class that consumes this mix-in
+*/
+
+#include "emu.h"
+#include "i2chle.h"
+
+#define LOG_DATAOUT (1U << 1)
+
+#define VERBOSE (0)
+
+#define LOG_OUTPUT_FUNC osd_printf_info
+
+#include "logmacro.h"
+
+constexpr int STATE_IDLE = 0;
+constexpr int STATE_GET_ADDRESS = 1;
+constexpr int STATE_GET_SUBADDRESS = 2;
+constexpr int STATE_READ_DATA = 3;
+constexpr int STATE_WAIT_ACK = 4;
+constexpr int STATE_GET_WRITE_DATA = 5;
+
+i2c_hle_interface::i2c_hle_interface(const machine_config &mconfig, device_t &device, u16 address) :
+ device_interface(device, "i2chle"),
+ write_sda(*this),
+ m_address(address)
+{
+ m_data_offset = 0;
+ m_sda = m_scl = 1;
+ m_state = m_state_next = STATE_IDLE;
+ m_last_address = 0;
+ m_just_acked = false;
+}
+
+i2c_hle_interface::~i2c_hle_interface()
+{
+}
+
+void i2c_hle_interface::interface_post_start()
+{
+ m_latch = m_bit = 0;
+ m_state = STATE_IDLE;
+ m_sda = m_scl = 1;
+ m_last_address = 0;
+ m_data_offset = 0;
+
+ device().save_item(NAME(m_latch));
+ device().save_item(NAME(m_bit));
+ device().save_item(NAME(m_state));
+ device().save_item(NAME(m_state_next));
+ device().save_item(NAME(m_data_offset));
+ device().save_item(NAME(m_just_acked));
+
+ write_sda(1);
+}
+
+void i2c_hle_interface::sda_write(int state)
+{
+ if (m_sda != state)
+ {
+ m_sda = state & 1;
+
+ if (m_scl)
+ {
+ if (m_sda)
+ {
+ LOG("%s: stop\n", get_tag());
+ m_state = STATE_IDLE;
+ m_last_address = 0;
+ m_just_acked = false;
+ m_data_offset = 0;
+ }
+ else
+ {
+ LOG("%s: start\n", get_tag());
+ m_state = STATE_GET_ADDRESS;
+ m_bit = 0;
+ m_latch = 0;
+ m_just_acked = false;
+ }
+ }
+ }
+}
+
+void i2c_hle_interface::scl_write(int state)
+{
+ if (m_scl != state)
+ {
+ m_scl = state & 1;
+
+ switch (m_state)
+ {
+ case STATE_IDLE:
+ // just ignore everything until a START
+ break;
+
+ case STATE_GET_ADDRESS:
+ case STATE_GET_SUBADDRESS:
+ case STATE_GET_WRITE_DATA:
+ if (m_bit < 8)
+ {
+ if (m_scl)
+ {
+ m_latch <<= 1;
+ m_latch |= m_sda;
+ m_bit++;
+ }
+ }
+ else
+ {
+ if (m_scl)
+ {
+ m_bit++;
+ }
+ else
+ {
+ if (m_bit == 8)
+ {
+ if (m_state == STATE_GET_ADDRESS)
+ {
+ LOG("%s: Got address %02x (ours is %02x r/w %d)\n", get_tag(), m_latch >> 1, m_address, m_latch & 1);
+ // check if reading
+ if (m_latch & 1)
+ {
+ if ((m_latch >> 1) == m_address)
+ {
+ LOG("%s: address matches, ACKing\n", get_tag());
+ write_sda(0);
+ m_bit = 0;
+ m_latch = 0;
+ m_state_next = STATE_READ_DATA;
+ m_state = STATE_WAIT_ACK;
+ }
+ else
+ {
+ LOG("%s: address doesn't match, ignoring\n", get_tag());
+ m_state = STATE_IDLE;
+ write_sda(1);
+ }
+ }
+ else
+ {
+ if ((m_latch >> 1) == m_address)
+ {
+ LOGMASKED(LOG_DATAOUT, "%s: write: getting subaddress\n", get_tag());
+ m_last_address = m_latch >> 1;
+ write_sda(0);
+ m_bit = 0;
+ m_latch = 0;
+ m_state_next = STATE_GET_SUBADDRESS;
+ m_state = STATE_WAIT_ACK;
+ }
+ else
+ {
+ LOG("%s: address doesn't match, ignoring\n", get_tag());
+ m_state = STATE_IDLE;
+ write_sda(1);
+ }
+ }
+ }
+ else if (m_state == STATE_GET_SUBADDRESS)
+ {
+ LOGMASKED(LOG_DATAOUT, "%s: subaddress is %02x\n", get_tag(), m_latch);
+ m_data_offset = m_latch;
+ write_sda(0);
+ m_bit = 0;
+ m_latch = 0;
+ m_state_next = STATE_GET_WRITE_DATA;
+ m_state = STATE_WAIT_ACK;
+ }
+ else if (m_state == STATE_GET_WRITE_DATA)
+ {
+ LOGMASKED(LOG_DATAOUT, "%s: got write data %02x for address %02x\n", get_tag(), m_latch, m_data_offset);
+ write_data(m_data_offset, m_latch);
+ m_data_offset++;
+ write_sda(0);
+ m_bit = 0;
+ m_latch = 0;
+ m_state_next = STATE_GET_WRITE_DATA;
+ m_state = STATE_WAIT_ACK;
+ }
+ }
+ }
+ }
+ break;
+
+ case STATE_WAIT_ACK:
+ if (!m_scl)
+ {
+ m_state = m_state_next;
+ write_sda(1);
+ }
+ break;
+
+ case STATE_READ_DATA:
+ if (m_bit < 8)
+ {
+ if (!m_scl)
+ {
+ m_bit++;
+ write_sda(1);
+ }
+ else
+ {
+ if (m_bit == 0)
+ {
+ m_latch = read_data(m_data_offset);
+ m_data_offset++;
+ m_data_offset &= 0xff;
+ LOGMASKED(LOG_DATAOUT, "%s: outputting byte %02x\n", get_tag(), m_latch);
+ }
+
+ write_sda(BIT(m_latch, 7));
+ m_latch <<= 1;
+ }
+ }
+ else
+ {
+ if (m_scl)
+ {
+ // did the master ACK or NACK?
+ if (m_sda)
+ {
+ LOGMASKED(LOG_DATAOUT, "%s: master NACK\n", get_tag());
+ m_state = STATE_IDLE;
+ write_sda(1);
+ }
+ else
+ {
+ LOGMASKED(LOG_DATAOUT, "%s: master ACK\n", get_tag());
+ m_just_acked = true;
+ }
+ }
+ else
+ {
+ write_sda(1);
+ if (m_just_acked)
+ {
+ m_bit = 0;
+ m_just_acked = false;
+ }
+ }
+ }
+ break;
+ }
+ }
+}
+
+u8 i2c_hle_interface::read_data(u16 offset)
+{
+ return 0xff;
+}
+
+void i2c_hle_interface::write_data(u16 offset, u8 data)
+{
+}
+
+static const char i2chle_name[] = "i2chle";
+const char *i2c_hle_interface::get_tag()
+{
+ return i2chle_name;
+}