diff --git a/src/device/i2c_gpio.c b/src/device/i2c_gpio.c
index 08096123d..2b8cb9762 100644
--- a/src/device/i2c_gpio.c
+++ b/src/device/i2c_gpio.c
@@ -6,14 +6,12 @@
*
* This file is part of the 86Box distribution.
*
- * Emulation of a GPIO-based I2C device.
+ * Emulation of a GPIO-based I2C host controller.
*
*
*
- * Authors: Sarah Walker,
- * RichardG,
+ * Authors: RichardG,
*
- * Copyright 2008-2020 Sarah Walker.
* Copyright 2020 RichardG.
*/
#include
@@ -27,36 +25,11 @@
#include <86box/i2c.h>
-enum {
- TRANSMITTER_SLAVE = 1,
- TRANSMITTER_MASTER = 2
-};
-
-enum {
- I2C_IDLE = 0,
- I2C_RECEIVE,
- I2C_RECEIVE_WAIT,
- I2C_TRANSMIT_START,
- I2C_TRANSMIT,
- I2C_ACKNOWLEDGE,
- I2C_NOTACKNOWLEDGE,
- I2C_TRANSACKNOWLEDGE,
- I2C_TRANSMIT_WAIT
-};
-
-enum {
- SLAVE_IDLE = 0,
- SLAVE_RECEIVEADDR,
- SLAVE_RECEIVEDATA,
- SLAVE_SENDDATA,
- SLAVE_INVALID
-};
-
typedef struct {
char *bus_name;
void *i2c;
- uint8_t scl, sda, receive_wait_sda, state, slave_state, slave_addr,
- slave_read, last_sda, pos, transmit, byte;
+ uint8_t prev_scl, prev_sda, slave_sda, started,
+ slave_addr_received, slave_addr, slave_read, pos, byte;
} i2c_gpio_t;
@@ -90,7 +63,7 @@ i2c_gpio_init(char *bus_name)
dev->bus_name = bus_name;
dev->i2c = i2c_addbus(dev->bus_name);
- dev->scl = dev->sda = 1;
+ dev->prev_scl = dev->prev_sda = dev->slave_sda = 1;
dev->slave_addr = 0xff;
return dev;
@@ -110,206 +83,77 @@ i2c_gpio_close(void *dev_handle)
}
-void
-i2c_gpio_next_byte(i2c_gpio_t *dev)
-{
- dev->byte = i2c_read(dev->i2c, dev->slave_addr);
- i2c_gpio_log(1, "I2C GPIO %s: Transmitting data %02X\n", dev->bus_name, dev->byte);
-}
-
-
-uint8_t
-i2c_gpio_write(i2c_gpio_t *dev)
-{
- uint8_t i;
-
- switch (dev->slave_state) {
- case SLAVE_IDLE:
- i = dev->slave_addr;
- dev->slave_addr = dev->byte >> 1;
- dev->slave_read = dev->byte & 1;
-
- i2c_gpio_log(1, "I2C GPIO %s: Initiating %s address %02X\n", dev->bus_name, dev->slave_read ? "read from" : "write to", dev->slave_addr);
-
- if (!i2c_has_device(dev->i2c, dev->slave_addr) ||
- ((i == 0xff) && !i2c_start(dev->i2c, dev->slave_addr, dev->slave_read))) { /* start only once per transfer */
- dev->slave_state = SLAVE_INVALID;
- dev->slave_addr = 0xff;
- return I2C_NOTACKNOWLEDGE;
- }
-
- if (dev->slave_read) {
- dev->slave_state = SLAVE_SENDDATA;
- dev->transmit = TRANSMITTER_SLAVE;
- dev->byte = i2c_read(dev->i2c, dev->slave_addr);
- } else {
- dev->slave_state = SLAVE_RECEIVEADDR;
- dev->transmit = TRANSMITTER_MASTER;
- }
- break;
-
- case SLAVE_RECEIVEADDR:
- i2c_gpio_log(1, "I2C GPIO %s: Receiving address %02X\n", dev->bus_name, dev->byte);
- dev->slave_state = dev->slave_read ? SLAVE_SENDDATA : SLAVE_RECEIVEDATA;
- if (!i2c_write(dev->i2c, dev->slave_addr, dev->byte))
- return I2C_NOTACKNOWLEDGE;
- break;
-
- case SLAVE_RECEIVEDATA:
- i2c_gpio_log(1, "I2C GPIO %s: Receiving data %02X\n", dev->bus_name, dev->byte);
- if (!i2c_write(dev->i2c, dev->slave_addr, dev->byte))
- return I2C_NOTACKNOWLEDGE;
- break;
-
- case SLAVE_INVALID:
- return I2C_NOTACKNOWLEDGE;
- }
-
- return I2C_ACKNOWLEDGE;
-}
-
-
-void
-i2c_gpio_stop(i2c_gpio_t *dev)
-{
- i2c_gpio_log(1, "I2C GPIO %s: Stopping transfer\n", dev->bus_name);
-
- if (dev->slave_addr != 0xff) /* don't stop if no transfer was in progress */
- i2c_stop(dev->i2c, dev->slave_addr);
-
- dev->slave_addr = 0xff;
- dev->slave_state = SLAVE_IDLE;
- dev->transmit = TRANSMITTER_MASTER;
-}
-
-
void
i2c_gpio_set(void *dev_handle, uint8_t scl, uint8_t sda)
{
i2c_gpio_t *dev = (i2c_gpio_t *) dev_handle;
- i2c_gpio_log(3, "I2C GPIO %s: scl=%d->%d sda=%d->%d last_valid_sda=%d state=%d\n", dev->bus_name, dev->scl, scl, dev->last_sda, sda, dev->sda, dev->state);
+ i2c_gpio_log(3, "I2C GPIO %s: write scl=%d->%d sda=%d->%d read=%d\n", dev->bus_name, dev->prev_scl, scl, dev->prev_sda, sda, dev->slave_read);
- switch (dev->state) {
- case I2C_IDLE:
- if (scl && dev->last_sda && !sda) { /* start condition; dev->scl check breaks NCR SDMS */
- i2c_gpio_log(2, "I2C GPIO %s: Start condition received (from IDLE)\n", dev->bus_name);
- dev->state = I2C_RECEIVE;
- dev->pos = 0;
- }
- break;
+ if (dev->prev_scl && scl) {
+ if (dev->prev_sda && !sda) {
+ i2c_gpio_log(2, "I2C GPIO %s: Start condition\n", dev->bus_name);
+ dev->started = 1;
+ dev->pos = 0;
+ dev->slave_read = 2; /* start with address transfer */
+ dev->slave_sda = 1;
+ } else if (!dev->prev_sda && sda) {
+ i2c_gpio_log(2, "I2C GPIO %s: Stop condition\n", dev->bus_name);
+ dev->started = 0;
+ dev->slave_sda = 1;
+ }
+ } else if (!dev->prev_scl && scl && dev->started) {
+ if (dev->pos++ < 8) {
+ if (dev->slave_read == 1) {
+ dev->slave_sda = !!(dev->byte & 0x80);
+ dev->byte <<= 1;
+ } else {
+ dev->byte <<= 1;
+ dev->byte |= sda;
+ }
- case I2C_RECEIVE_WAIT:
- if (!dev->scl && scl)
- dev->state = I2C_RECEIVE;
- else if (!dev->scl && !scl && dev->last_sda && sda) /* workaround for repeated start condition on Windows XP DDC */
- dev->receive_wait_sda = 1;
- /* fall-through */
+ i2c_gpio_log(2, "I2C GPIO %s: Bit %d = %d\n", dev->bus_name, 8 - dev->pos, (dev->slave_read == 1) ? dev->slave_sda : sda);
+ }
- case I2C_RECEIVE:
- if (!dev->scl && scl) {
- dev->byte <<= 1;
- if (sda)
- dev->byte |= 1;
- else
- dev->byte &= 0xfe;
- if (++dev->pos == 8)
- dev->state = i2c_gpio_write(dev);
- } else if (dev->scl && scl) {
- if (sda && !dev->last_sda) { /* stop condition */
- i2c_gpio_log(2, "I2C GPIO %s: Stop condition received (from RECEIVE)\n", dev->bus_name);
- dev->state = I2C_IDLE;
- i2c_gpio_stop(dev);
- } else if (!sda && dev->last_sda) { /* start condition */
- i2c_gpio_log(2, "I2C GPIO %s: Start condition received (from RECEIVE)\n", dev->bus_name);
- dev->pos = 0;
- dev->slave_state = SLAVE_IDLE;
- }
- }
- break;
+ if (dev->pos == 8) {
+ i2c_gpio_log(2, "I2C GPIO %s: Byte = %02X\n", dev->bus_name, dev->byte);
- case I2C_ACKNOWLEDGE:
- if (!dev->scl && scl) {
- i2c_gpio_log(2, "I2C GPIO %s: Acknowledging transfer to %02X\n", dev->bus_name, dev->slave_addr);
- sda = 0;
- dev->receive_wait_sda = 0; /* ack */
- dev->pos = 0;
- dev->state = (dev->transmit == TRANSMITTER_MASTER) ? I2C_RECEIVE_WAIT : I2C_TRANSMIT;
- }
- break;
+ /* (N)ACKing here instead of at the 9th bit may sound odd, but is required by the Matrox Mystique Windows drivers. */
+ switch (dev->slave_read) {
+ case 2: /* address transfer */
+ dev->slave_addr = dev->byte >> 1;
+ dev->slave_read = (dev->byte & 1);
- case I2C_NOTACKNOWLEDGE:
- if (!dev->scl && scl) {
- i2c_gpio_log(2, "I2C GPIO %s: Not acknowledging transfer\n", dev->bus_name);
- sda = 1;
- dev->pos = 0;
- dev->state = I2C_IDLE;
- dev->slave_state = SLAVE_IDLE;
- }
- break;
+ /* slave ACKs? */
+ dev->slave_sda = !(i2c_has_device(dev->i2c, dev->slave_addr) && i2c_start(dev->i2c, dev->slave_addr, dev->slave_read));
+ i2c_gpio_log(2, "I2C GPIO %s: Slave %02X %s %sACK\n", dev->bus_name, dev->slave_addr, dev->slave_read ? "read" : "write", dev->slave_sda ? "N" : "");
- case I2C_TRANSACKNOWLEDGE:
- if (!dev->scl && scl) {
- if (sda) { /* not acknowledged; must be end of transfer */
- i2c_gpio_log(2, "I2C GPIO %s: End of transfer\n", dev->bus_name);
- dev->state = I2C_IDLE;
- i2c_gpio_stop(dev);
- } else { /* next byte to transfer */
- dev->state = I2C_TRANSMIT_START;
- i2c_gpio_next_byte(dev);
- dev->pos = 0;
- i2c_gpio_log(2, "I2C GPIO %s: Next byte = %02X\n", dev->bus_name, dev->byte);
- }
- }
- break;
+ if (!dev->slave_sda && dev->slave_read) /* read first byte on an ACKed read transfer */
+ dev->byte = i2c_read(dev->i2c, dev->slave_addr);
- case I2C_TRANSMIT_WAIT:
- if (dev->scl && scl) {
- if (dev->last_sda && !sda) { /* start condition */
- i2c_gpio_next_byte(dev);
- dev->pos = 0;
- i2c_gpio_log(2, "I2C GPIO %s: Next byte = %02X\n", dev->bus_name, dev->byte);
- }
- if (!dev->last_sda && sda) { /* stop condition */
- i2c_gpio_log(2, "I2C GPIO %s: Stop condition received (from TRANSMIT_WAIT)\n", dev->bus_name);
- dev->state = I2C_IDLE;
- i2c_gpio_stop(dev);
- }
- }
- break;
+ dev->slave_read |= 0x80; /* slave_read was overwritten; stop the master ACK read logic from running at the 9th bit if we're reading */
+ break;
- case I2C_TRANSMIT_START:
- if (!dev->scl && scl)
- dev->state = I2C_TRANSMIT;
- if (dev->scl && scl && !dev->last_sda && sda) { /* stop condition */
- i2c_gpio_log(2, "I2C GPIO %s: Stop condition received (from TRANSMIT_START)\n", dev->bus_name);
- dev->state = I2C_IDLE;
- i2c_gpio_stop(dev);
- }
- /* fall-through */
-
- case I2C_TRANSMIT:
- if (!dev->scl && scl) {
- dev->scl = scl;
- if (!dev->pos)
- i2c_gpio_log(2, "I2C GPIO %s: Transmit byte %02X\n", dev->bus_name, dev->byte);
- dev->sda = sda = dev->byte & 0x80;
- i2c_gpio_log(2, "I2C GPIO %s: Transmit bit %02X %d\n", dev->bus_name, dev->byte, dev->pos);
- dev->byte <<= 1;
- dev->pos++;
- return;
- }
- if (dev->scl && !scl && (dev->pos == 8)) {
- dev->state = I2C_TRANSACKNOWLEDGE;
- i2c_gpio_log(2, "I2C GPIO %s: Acknowledge mode\n", dev->bus_name);
- }
- break;
+ case 0: /* write transfer */
+ dev->slave_sda = !i2c_write(dev->i2c, dev->slave_addr, dev->byte);
+ i2c_gpio_log(2, "I2C GPIO %s: Write %02X %sACK\n", dev->bus_name, dev->byte, dev->slave_sda ? "N" : "");
+ break;
+ }
+ } else if (dev->pos == 9) {
+ if (dev->slave_read == 1) { /* read transfer (unless we're in an address transfer) */
+ if (!sda) /* master ACKs? */
+ dev->byte = i2c_read(dev->i2c, dev->slave_addr);
+ i2c_gpio_log(2, "I2C GPIO %s: Read %02X %sACK\n", dev->bus_name, dev->byte, sda ? "N" : "");
+ } else
+ dev->slave_read &= 1; /* if we're in an address transfer, clear it */
+ dev->pos = 0; /* start over */
+ }
+ } else if (dev->prev_scl && !scl && (dev->pos != 8)) { /* keep (N)ACK computed at the 8th bit when transitioning to the 9th bit */
+ dev->slave_sda = 1;
}
- if (!dev->scl && scl)
- dev->sda = sda;
- dev->last_sda = sda;
- dev->scl = scl;
+ dev->prev_scl = scl;
+ dev->prev_sda = sda;
}
@@ -317,7 +161,7 @@ uint8_t
i2c_gpio_get_scl(void *dev_handle)
{
i2c_gpio_t *dev = (i2c_gpio_t *) dev_handle;
- return dev->scl;
+ return dev->prev_scl;
}
@@ -325,17 +169,8 @@ uint8_t
i2c_gpio_get_sda(void *dev_handle)
{
i2c_gpio_t *dev = (i2c_gpio_t *) dev_handle;
- switch (dev->state) {
- case I2C_TRANSMIT:
- case I2C_ACKNOWLEDGE:
- return dev->sda;
-
- case I2C_RECEIVE_WAIT:
- return dev->receive_wait_sda;
-
- default:
- return 1;
- }
+ i2c_gpio_log(3, "I2C GPIO %s: read myscl=%d mysda=%d slavesda=%d\n", dev->bus_name, dev->prev_scl, dev->prev_sda, dev->slave_sda);
+ return dev->prev_sda && dev->slave_sda;
}