2020-11-20 01:22:04 -03:00
|
|
|
/*
|
|
|
|
|
* 86Box A hypervisor and IBM PC system emulator that specializes in
|
|
|
|
|
* running old operating systems and software designed for IBM
|
|
|
|
|
* PC systems and compatibles from 1981 through fairly recent
|
|
|
|
|
* system designs based on the PCI bus.
|
|
|
|
|
*
|
|
|
|
|
* This file is part of the 86Box distribution.
|
|
|
|
|
*
|
2020-11-20 19:23:14 -03:00
|
|
|
* Emulation of a GPIO-based I2C device.
|
2020-11-20 01:22:04 -03:00
|
|
|
*
|
|
|
|
|
*
|
|
|
|
|
*
|
|
|
|
|
* Authors: Sarah Walker, <http://pcem-emulator.co.uk/>
|
|
|
|
|
* RichardG, <richardg867@gmail.com>
|
|
|
|
|
*
|
|
|
|
|
* Copyright 2008-2020 Sarah Walker.
|
|
|
|
|
* Copyright 2020 RichardG.
|
|
|
|
|
*/
|
2020-11-20 19:23:14 -03:00
|
|
|
#include <stdarg.h>
|
2020-11-20 01:22:04 -03:00
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <stdint.h>
|
|
|
|
|
#include <stdlib.h>
|
2020-11-20 19:23:14 -03:00
|
|
|
#include <string.h>
|
|
|
|
|
#define HAVE_STDARG_H
|
2020-11-20 01:22:04 -03:00
|
|
|
#include <wchar.h>
|
|
|
|
|
#include <86box/86box.h>
|
|
|
|
|
#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,
|
2020-11-23 14:49:49 -03:00
|
|
|
I2C_NOTACKNOWLEDGE,
|
2020-11-20 01:22:04 -03:00
|
|
|
I2C_TRANSACKNOWLEDGE,
|
|
|
|
|
I2C_TRANSMIT_WAIT
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
enum {
|
|
|
|
|
SLAVE_IDLE = 0,
|
|
|
|
|
SLAVE_RECEIVEADDR,
|
|
|
|
|
SLAVE_RECEIVEDATA,
|
|
|
|
|
SLAVE_SENDDATA,
|
|
|
|
|
SLAVE_INVALID
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
typedef struct {
|
2020-11-21 01:36:33 -03:00
|
|
|
char *bus_name;
|
2020-11-20 01:22:04 -03:00
|
|
|
void *i2c;
|
2020-11-24 01:56:06 -03:00
|
|
|
uint8_t scl, sda, receive_wait_sda, state, slave_state, slave_addr,
|
2020-11-22 00:19:13 -03:00
|
|
|
slave_read, last_sda, pos, transmit, byte;
|
2020-11-20 01:22:04 -03:00
|
|
|
} i2c_gpio_t;
|
|
|
|
|
|
|
|
|
|
|
2020-11-21 01:36:33 -03:00
|
|
|
#ifdef ENABLE_I2C_GPIO_LOG
|
|
|
|
|
int i2c_gpio_do_log = ENABLE_I2C_GPIO_LOG;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
i2c_gpio_log(int level, const char *fmt, ...)
|
|
|
|
|
{
|
|
|
|
|
va_list ap;
|
|
|
|
|
|
|
|
|
|
if (i2c_gpio_do_log >= level) {
|
|
|
|
|
va_start(ap, fmt);
|
|
|
|
|
pclog_ex(fmt, ap);
|
|
|
|
|
va_end(ap);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#else
|
|
|
|
|
#define i2c_gpio_log(fmt, ...)
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
2020-11-20 01:22:04 -03:00
|
|
|
void *
|
|
|
|
|
i2c_gpio_init(char *bus_name)
|
|
|
|
|
{
|
|
|
|
|
i2c_gpio_t *dev = (i2c_gpio_t *) malloc(sizeof(i2c_gpio_t));
|
|
|
|
|
memset(dev, 0, sizeof(i2c_gpio_t));
|
|
|
|
|
|
2020-11-21 01:36:33 -03:00
|
|
|
i2c_gpio_log(1, "I2C GPIO %s: init()\n", bus_name);
|
|
|
|
|
|
|
|
|
|
dev->bus_name = bus_name;
|
|
|
|
|
dev->i2c = i2c_addbus(dev->bus_name);
|
2020-11-20 01:22:04 -03:00
|
|
|
dev->scl = dev->sda = 1;
|
2020-11-20 19:23:14 -03:00
|
|
|
dev->slave_addr = 0xff;
|
2020-11-20 01:22:04 -03:00
|
|
|
|
|
|
|
|
return dev;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
i2c_gpio_close(void *dev_handle)
|
|
|
|
|
{
|
|
|
|
|
i2c_gpio_t *dev = (i2c_gpio_t *) dev_handle;
|
|
|
|
|
|
2020-11-21 01:36:33 -03:00
|
|
|
i2c_gpio_log(1, "I2C GPIO %s: close()\n", dev->bus_name);
|
|
|
|
|
|
2020-11-20 01:22:04 -03:00
|
|
|
i2c_removebus(dev->i2c);
|
|
|
|
|
|
|
|
|
|
free(dev);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
i2c_gpio_next_byte(i2c_gpio_t *dev)
|
|
|
|
|
{
|
2020-11-20 19:23:14 -03:00
|
|
|
dev->byte = i2c_read(dev->i2c, dev->slave_addr);
|
2020-11-22 00:19:13 -03:00
|
|
|
i2c_gpio_log(1, "I2C GPIO %s: Transmitting data %02X\n", dev->bus_name, dev->byte);
|
2020-11-20 01:22:04 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2020-11-22 00:19:13 -03:00
|
|
|
uint8_t
|
2020-11-20 01:22:04 -03:00
|
|
|
i2c_gpio_write(i2c_gpio_t *dev)
|
|
|
|
|
{
|
2020-11-20 19:23:14 -03:00
|
|
|
uint8_t i;
|
|
|
|
|
|
2020-11-20 01:22:04 -03:00
|
|
|
switch (dev->slave_state) {
|
|
|
|
|
case SLAVE_IDLE:
|
2020-11-20 19:23:14 -03:00
|
|
|
i = dev->slave_addr;
|
2020-11-20 01:22:04 -03:00
|
|
|
dev->slave_addr = dev->byte >> 1;
|
2020-11-22 00:19:13 -03:00
|
|
|
dev->slave_read = dev->byte & 1;
|
2020-11-20 19:23:14 -03:00
|
|
|
|
2020-11-22 00:19:13 -03:00
|
|
|
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);
|
2020-11-21 01:36:33 -03:00
|
|
|
|
2020-11-23 14:49:49 -03:00
|
|
|
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 */
|
2020-11-20 01:22:04 -03:00
|
|
|
dev->slave_state = SLAVE_INVALID;
|
2020-11-22 00:19:13 -03:00
|
|
|
dev->slave_addr = 0xff;
|
2020-11-23 14:49:49 -03:00
|
|
|
return I2C_NOTACKNOWLEDGE;
|
2020-11-20 01:22:04 -03:00
|
|
|
}
|
2020-11-20 19:23:14 -03:00
|
|
|
|
2020-11-22 00:19:13 -03:00
|
|
|
if (dev->slave_read) {
|
2020-11-20 01:22:04 -03:00
|
|
|
dev->slave_state = SLAVE_SENDDATA;
|
|
|
|
|
dev->transmit = TRANSMITTER_SLAVE;
|
2020-11-20 19:23:14 -03:00
|
|
|
dev->byte = i2c_read(dev->i2c, dev->slave_addr);
|
2020-11-20 01:22:04 -03:00
|
|
|
} else {
|
|
|
|
|
dev->slave_state = SLAVE_RECEIVEADDR;
|
|
|
|
|
dev->transmit = TRANSMITTER_MASTER;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case SLAVE_RECEIVEADDR:
|
2020-11-21 01:36:33 -03:00
|
|
|
i2c_gpio_log(1, "I2C GPIO %s: Receiving address %02X\n", dev->bus_name, dev->byte);
|
2020-11-22 00:19:13 -03:00
|
|
|
dev->slave_state = dev->slave_read ? SLAVE_SENDDATA : SLAVE_RECEIVEDATA;
|
|
|
|
|
if (!i2c_write(dev->i2c, dev->slave_addr, dev->byte))
|
2020-11-23 14:49:49 -03:00
|
|
|
return I2C_NOTACKNOWLEDGE;
|
2020-11-20 01:22:04 -03:00
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case SLAVE_RECEIVEDATA:
|
2020-11-21 01:36:33 -03:00
|
|
|
i2c_gpio_log(1, "I2C GPIO %s: Receiving data %02X\n", dev->bus_name, dev->byte);
|
2020-11-22 00:19:13 -03:00
|
|
|
if (!i2c_write(dev->i2c, dev->slave_addr, dev->byte))
|
2020-11-23 14:49:49 -03:00
|
|
|
return I2C_NOTACKNOWLEDGE;
|
2020-11-20 01:22:04 -03:00
|
|
|
break;
|
2020-11-22 00:19:13 -03:00
|
|
|
|
|
|
|
|
case SLAVE_INVALID:
|
2020-11-23 14:49:49 -03:00
|
|
|
return I2C_NOTACKNOWLEDGE;
|
2020-11-20 01:22:04 -03:00
|
|
|
}
|
2020-11-22 00:19:13 -03:00
|
|
|
|
|
|
|
|
return I2C_ACKNOWLEDGE;
|
2020-11-20 01:22:04 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
i2c_gpio_stop(i2c_gpio_t *dev)
|
|
|
|
|
{
|
2020-11-21 01:36:33 -03:00
|
|
|
i2c_gpio_log(1, "I2C GPIO %s: Stopping transfer\n", dev->bus_name);
|
|
|
|
|
|
2020-11-22 00:19:13 -03:00
|
|
|
if (dev->slave_addr != 0xff) /* don't stop if no transfer was in progress */
|
2020-11-21 01:36:33 -03:00
|
|
|
i2c_stop(dev->i2c, dev->slave_addr);
|
2020-11-20 01:22:04 -03:00
|
|
|
|
2020-11-20 19:23:14 -03:00
|
|
|
dev->slave_addr = 0xff;
|
2020-11-20 01:22:04 -03:00
|
|
|
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;
|
|
|
|
|
|
2020-11-24 01:56:06 -03:00
|
|
|
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);
|
|
|
|
|
|
2020-11-20 01:22:04 -03:00
|
|
|
switch (dev->state) {
|
|
|
|
|
case I2C_IDLE:
|
2020-11-24 01:56:06 -03:00
|
|
|
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);
|
2020-11-20 01:22:04 -03:00
|
|
|
dev->state = I2C_RECEIVE;
|
|
|
|
|
dev->pos = 0;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case I2C_RECEIVE_WAIT:
|
|
|
|
|
if (!dev->scl && scl)
|
|
|
|
|
dev->state = I2C_RECEIVE;
|
2020-11-24 01:56:06 -03:00
|
|
|
else if (!dev->scl && !scl && dev->last_sda && sda) /* workaround for repeated start condition on Windows XP DDC */
|
|
|
|
|
dev->receive_wait_sda = 1;
|
2020-11-20 01:22:04 -03:00
|
|
|
/* fall-through */
|
|
|
|
|
|
|
|
|
|
case I2C_RECEIVE:
|
|
|
|
|
if (!dev->scl && scl) {
|
|
|
|
|
dev->byte <<= 1;
|
|
|
|
|
if (sda)
|
|
|
|
|
dev->byte |= 1;
|
|
|
|
|
else
|
|
|
|
|
dev->byte &= 0xfe;
|
2020-11-22 00:19:13 -03:00
|
|
|
if (++dev->pos == 8)
|
|
|
|
|
dev->state = i2c_gpio_write(dev);
|
2020-11-20 01:22:04 -03:00
|
|
|
} else if (dev->scl && scl) {
|
2020-11-24 01:56:06 -03:00
|
|
|
if (sda && !dev->last_sda) { /* stop condition */
|
|
|
|
|
i2c_gpio_log(2, "I2C GPIO %s: Stop condition received (from RECEIVE)\n", dev->bus_name);
|
2020-11-20 01:22:04 -03:00
|
|
|
dev->state = I2C_IDLE;
|
|
|
|
|
i2c_gpio_stop(dev);
|
2020-11-24 01:56:06 -03:00
|
|
|
} else if (!sda && dev->last_sda) { /* start condition */
|
|
|
|
|
i2c_gpio_log(2, "I2C GPIO %s: Start condition received (from RECEIVE)\n", dev->bus_name);
|
2020-11-20 01:22:04 -03:00
|
|
|
dev->pos = 0;
|
|
|
|
|
dev->slave_state = SLAVE_IDLE;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case I2C_ACKNOWLEDGE:
|
|
|
|
|
if (!dev->scl && scl) {
|
2020-11-22 00:19:13 -03:00
|
|
|
i2c_gpio_log(2, "I2C GPIO %s: Acknowledging transfer to %02X\n", dev->bus_name, dev->slave_addr);
|
2020-11-20 01:22:04 -03:00
|
|
|
sda = 0;
|
2020-11-24 01:56:06 -03:00
|
|
|
dev->receive_wait_sda = 0; /* ack */
|
2020-11-20 01:22:04 -03:00
|
|
|
dev->pos = 0;
|
|
|
|
|
dev->state = (dev->transmit == TRANSMITTER_MASTER) ? I2C_RECEIVE_WAIT : I2C_TRANSMIT;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
2020-11-23 14:49:49 -03:00
|
|
|
case I2C_NOTACKNOWLEDGE:
|
2020-11-22 00:19:13 -03:00
|
|
|
if (!dev->scl && scl) {
|
2020-11-23 14:49:49 -03:00
|
|
|
i2c_gpio_log(2, "I2C GPIO %s: Not acknowledging transfer\n", dev->bus_name);
|
2020-11-22 00:19:13 -03:00
|
|
|
sda = 1;
|
|
|
|
|
dev->pos = 0;
|
|
|
|
|
dev->state = I2C_IDLE;
|
|
|
|
|
dev->slave_state = SLAVE_IDLE;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
2020-11-20 01:22:04 -03:00
|
|
|
case I2C_TRANSACKNOWLEDGE:
|
|
|
|
|
if (!dev->scl && scl) {
|
|
|
|
|
if (sda) { /* not acknowledged; must be end of transfer */
|
2020-11-21 01:36:33 -03:00
|
|
|
i2c_gpio_log(2, "I2C GPIO %s: End of transfer\n", dev->bus_name);
|
2020-11-20 01:22:04 -03:00
|
|
|
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;
|
2020-11-21 01:36:33 -03:00
|
|
|
i2c_gpio_log(2, "I2C GPIO %s: Next byte = %02X\n", dev->bus_name, dev->byte);
|
2020-11-20 01:22:04 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case I2C_TRANSMIT_WAIT:
|
|
|
|
|
if (dev->scl && scl) {
|
2020-11-24 01:56:06 -03:00
|
|
|
if (dev->last_sda && !sda) { /* start condition */
|
2020-11-20 01:22:04 -03:00
|
|
|
i2c_gpio_next_byte(dev);
|
|
|
|
|
dev->pos = 0;
|
2020-11-21 01:36:33 -03:00
|
|
|
i2c_gpio_log(2, "I2C GPIO %s: Next byte = %02X\n", dev->bus_name, dev->byte);
|
2020-11-20 01:22:04 -03:00
|
|
|
}
|
2020-11-24 01:56:06 -03:00
|
|
|
if (!dev->last_sda && sda) { /* stop condition */
|
|
|
|
|
i2c_gpio_log(2, "I2C GPIO %s: Stop condition received (from TRANSMIT_WAIT)\n", dev->bus_name);
|
2020-11-20 01:22:04 -03:00
|
|
|
dev->state = I2C_IDLE;
|
|
|
|
|
i2c_gpio_stop(dev);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case I2C_TRANSMIT_START:
|
|
|
|
|
if (!dev->scl && scl)
|
|
|
|
|
dev->state = I2C_TRANSMIT;
|
2020-11-24 01:56:06 -03:00
|
|
|
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);
|
2020-11-20 01:22:04 -03:00
|
|
|
dev->state = I2C_IDLE;
|
|
|
|
|
i2c_gpio_stop(dev);
|
|
|
|
|
}
|
|
|
|
|
/* fall-through */
|
|
|
|
|
|
|
|
|
|
case I2C_TRANSMIT:
|
|
|
|
|
if (!dev->scl && scl) {
|
|
|
|
|
dev->scl = scl;
|
2020-11-21 01:36:33 -03:00
|
|
|
if (!dev->pos)
|
|
|
|
|
i2c_gpio_log(2, "I2C GPIO %s: Transmit byte %02X\n", dev->bus_name, dev->byte);
|
2020-11-20 01:22:04 -03:00
|
|
|
dev->sda = sda = dev->byte & 0x80;
|
2020-11-21 01:36:33 -03:00
|
|
|
i2c_gpio_log(2, "I2C GPIO %s: Transmit bit %02X %d\n", dev->bus_name, dev->byte, dev->pos);
|
2020-11-20 01:22:04 -03:00
|
|
|
dev->byte <<= 1;
|
|
|
|
|
dev->pos++;
|
|
|
|
|
return;
|
|
|
|
|
}
|
2020-11-21 01:36:33 -03:00
|
|
|
if (dev->scl && !scl && (dev->pos == 8)) {
|
2020-11-20 01:22:04 -03:00
|
|
|
dev->state = I2C_TRANSACKNOWLEDGE;
|
2020-11-21 01:36:33 -03:00
|
|
|
i2c_gpio_log(2, "I2C GPIO %s: Acknowledge mode\n", dev->bus_name);
|
|
|
|
|
}
|
2020-11-20 01:22:04 -03:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!dev->scl && scl)
|
|
|
|
|
dev->sda = sda;
|
|
|
|
|
dev->last_sda = sda;
|
|
|
|
|
dev->scl = scl;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
uint8_t
|
|
|
|
|
i2c_gpio_get_scl(void *dev_handle)
|
|
|
|
|
{
|
|
|
|
|
i2c_gpio_t *dev = (i2c_gpio_t *) dev_handle;
|
|
|
|
|
return dev->scl;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
uint8_t
|
|
|
|
|
i2c_gpio_get_sda(void *dev_handle)
|
|
|
|
|
{
|
|
|
|
|
i2c_gpio_t *dev = (i2c_gpio_t *) dev_handle;
|
2020-11-22 00:19:13 -03:00
|
|
|
switch (dev->state) {
|
|
|
|
|
case I2C_TRANSMIT:
|
|
|
|
|
case I2C_ACKNOWLEDGE:
|
|
|
|
|
return dev->sda;
|
|
|
|
|
|
|
|
|
|
case I2C_RECEIVE_WAIT:
|
2020-11-24 01:56:06 -03:00
|
|
|
return dev->receive_wait_sda;
|
2020-11-22 00:19:13 -03:00
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
2020-11-20 01:22:04 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void *
|
|
|
|
|
i2c_gpio_get_bus(void *dev_handle)
|
|
|
|
|
{
|
|
|
|
|
i2c_gpio_t *dev = (i2c_gpio_t *) dev_handle;
|
|
|
|
|
return dev->i2c;
|
|
|
|
|
}
|