From 3fb4727483faede452f9e669173b41a44a3dfa74 Mon Sep 17 00:00:00 2001 From: RichardG867 Date: Sun, 22 Nov 2020 00:19:13 -0300 Subject: [PATCH] I2C overhaul part 4: VIA and EEPROM edition --- src/acpi.c | 2 +- src/chipset/via_pipc.c | 4 +- src/device/hwm_gl518sm.c | 12 +- src/device/hwm_lm75.c | 22 ++-- src/device/hwm_lm78.c | 10 +- src/device/i2c.c | 56 +++++++--- src/device/i2c_eeprom.c | 49 +++++---- src/device/i2c_gpio.c | 74 ++++++++----- src/device/smbus_piix4.c | 188 +++++++++++++++++++++++++------- src/include/86box/i2c.h | 11 +- src/include/86box/smbus_piix4.h | 14 ++- 11 files changed, 313 insertions(+), 129 deletions(-) diff --git a/src/acpi.c b/src/acpi.c index ac5506ee6..cb1f4d91f 100644 --- a/src/acpi.c +++ b/src/acpi.c @@ -653,7 +653,7 @@ static void acpi_i2c_set(acpi_t *dev) { if (dev->i2c) { - /* Check direction as well due to pull-ups. */ + /* Check direction as well to account for the I2C pull-ups. */ i2c_gpio_set(dev->i2c, !(dev->regs.gpio_dir & 0x02) || (dev->regs.gpio_val & 0x02), !(dev->regs.gpio_dir & 0x04) || (dev->regs.gpio_val & 0x04)); } } diff --git a/src/chipset/via_pipc.c b/src/chipset/via_pipc.c index bbf82e083..5ff1d5e3b 100644 --- a/src/chipset/via_pipc.c +++ b/src/chipset/via_pipc.c @@ -868,7 +868,9 @@ pipc_init(const device_t *info) dev->nvr = device_add(&via_nvr_device); - if (dev->local >= VIA_PIPC_596A) + if (dev->local >= VIA_PIPC_686B) + dev->smbus = device_add(&via_smbus_device); + else if (dev->local >= VIA_PIPC_596A) dev->smbus = device_add(&piix4_smbus_device); if (dev->local >= VIA_PIPC_596A) diff --git a/src/device/hwm_gl518sm.c b/src/device/hwm_gl518sm.c index 33c9e6be3..f56b803ea 100644 --- a/src/device/hwm_gl518sm.c +++ b/src/device/hwm_gl518sm.c @@ -45,7 +45,7 @@ typedef struct { } gl518sm_t; -static void gl518sm_i2c_start(void *bus, uint8_t addr, void *priv); +static uint8_t gl518sm_i2c_start(void *bus, uint8_t addr, uint8_t read, void *priv); static uint8_t gl518sm_i2c_read(void *bus, uint8_t addr, void *priv); static uint16_t gl518sm_read(gl518sm_t *dev, uint8_t reg); static uint8_t gl518sm_i2c_write(void *bus, uint8_t addr, uint8_t data, void *priv); @@ -87,12 +87,14 @@ gl518sm_remap(gl518sm_t *dev, uint8_t addr) } -static void -gl518sm_i2c_start(void *bus, uint8_t addr, void *priv) +static uint8_t +gl518sm_i2c_start(void *bus, uint8_t addr, uint8_t read, void *priv) { gl518sm_t *dev = (gl518sm_t *) priv; dev->i2c_state = 0; + + return 1; } @@ -173,11 +175,11 @@ gl518sm_i2c_write(void *bus, uint8_t addr, uint8_t data, void *priv) break; case 1: - gl518sm_write(dev, dev->addr_register, data); + gl518sm_write(dev, dev->addr_register, (gl518sm_read(dev, dev->addr_register) & 0xff00) | data); break; case 2: - gl518sm_write(dev, dev->addr_register, (data << 8) | gl518sm_read(dev, dev->addr_register)); + gl518sm_write(dev, dev->addr_register, (gl518sm_read(dev, dev->addr_register) << 8) | data); break; default: diff --git a/src/device/hwm_lm75.c b/src/device/hwm_lm75.c index 21d2b9b5c..fdb9149ae 100644 --- a/src/device/hwm_lm75.c +++ b/src/device/hwm_lm75.c @@ -30,7 +30,7 @@ #define LM75_TEMP_TO_REG(t) ((t) << 8) -static void lm75_i2c_start(void *bus, uint8_t addr, void *priv); +static uint8_t lm75_i2c_start(void *bus, uint8_t addr, uint8_t read, void *priv); static uint8_t lm75_i2c_read(void *bus, uint8_t addr, void *priv); static uint8_t lm75_i2c_write(void *bus, uint8_t addr, uint8_t data, void *priv); static void lm75_reset(lm75_t *dev); @@ -59,7 +59,7 @@ lm75_log(const char *fmt, ...) void lm75_remap(lm75_t *dev, uint8_t addr) { - lm75_log("LM75: remapping to I2C %02Xh\n", addr); + lm75_log("LM75: remapping to SMBus %02Xh\n", addr); if (dev->i2c_addr < 0x80) i2c_removehandler(i2c_smbus, dev->i2c_addr, 1, lm75_i2c_start, lm75_i2c_read, lm75_i2c_write, NULL, dev); @@ -71,12 +71,14 @@ lm75_remap(lm75_t *dev, uint8_t addr) } -static void -lm75_i2c_start(void *bus, uint8_t addr, void *priv) +static uint8_t +lm75_i2c_start(void *bus, uint8_t addr, uint8_t read, void *priv) { lm75_t *dev = (lm75_t *) priv; dev->i2c_state = 0; + + return 1; } @@ -93,7 +95,7 @@ lm75_i2c_read(void *bus, uint8_t addr, void *priv) to access some of its proprietary registers. Pass this operation on to the main monitor address through an internal I2C call, if necessary. */ if ((dev->addr_register > 0x7) && ((dev->addr_register & 0xf8) != 0x50) && (dev->as99127f_i2c_addr < 0x80)) { - i2c_start(i2c_smbus, dev->as99127f_i2c_addr); + i2c_start(i2c_smbus, dev->as99127f_i2c_addr, 1); i2c_write(i2c_smbus, dev->as99127f_i2c_addr, dev->addr_register); ret = i2c_read(i2c_smbus, dev->as99127f_i2c_addr); i2c_stop(i2c_smbus, dev->as99127f_i2c_addr); @@ -149,7 +151,10 @@ lm75_i2c_write(void *bus, uint8_t addr, uint8_t data, void *priv) if ((dev->i2c_state > 2) || ((dev->i2c_state == 2) && ((dev->addr_register & 0x3) == 0x1))) { return 0; } else if (dev->i2c_state == 0) { - dev->addr_register = data; + dev->i2c_state = 1; + /* Linux lm75.c driver relies on a 3-bit address register that doesn't change if bit 2 is set. */ + if ((dev->as99127f_i2c_addr >= 0x80) && !(data & 0x04)) + dev->addr_register = (data & 0x7); return 1; } @@ -157,7 +162,7 @@ lm75_i2c_write(void *bus, uint8_t addr, uint8_t data, void *priv) to access some of its proprietary registers. Pass this operation on to the main monitor address through an internal I2C call, if necessary. */ if ((dev->addr_register > 0x7) && ((dev->addr_register & 0xf8) != 0x50) && (dev->as99127f_i2c_addr < 0x80)) { - i2c_start(i2c_smbus, dev->as99127f_i2c_addr); + i2c_start(i2c_smbus, dev->as99127f_i2c_addr, 0); i2c_write(i2c_smbus, dev->as99127f_i2c_addr, dev->addr_register); i2c_write(i2c_smbus, dev->as99127f_i2c_addr, data); i2c_stop(i2c_smbus, dev->as99127f_i2c_addr); @@ -175,13 +180,14 @@ lm75_i2c_write(void *bus, uint8_t addr, uint8_t data, void *priv) case 0x2: /* Thyst */ lm75_write(dev, (dev->i2c_state == 1) ? 0x3 : 0x4, data); break; + case 0x3: /* Tos */ lm75_write(dev, (dev->i2c_state == 1) ? 0x5 : 0x6, data); break; } } - if (++dev->i2c_state > 2) + if (dev->i2c_state == 1) dev->i2c_state = 2; return 1; diff --git a/src/device/hwm_lm78.c b/src/device/hwm_lm78.c index 351fe7165..ad4cb7425 100644 --- a/src/device/hwm_lm78.c +++ b/src/device/hwm_lm78.c @@ -60,7 +60,7 @@ typedef struct { } lm78_t; -static void lm78_i2c_start(void *bus, uint8_t addr, void *priv); +static uint8_t lm78_i2c_start(void *bus, uint8_t addr, uint8_t read, void *priv); static uint8_t lm78_isa_read(uint16_t port, void *priv); static uint8_t lm78_i2c_read(void *bus, uint8_t addr, void *priv); static uint8_t lm78_read(lm78_t *dev, uint8_t reg, uint8_t bank); @@ -69,7 +69,7 @@ static uint8_t lm78_i2c_write(void *bus, uint8_t addr, uint8_t data, void *priv) static uint8_t lm78_write(lm78_t *dev, uint8_t reg, uint8_t val, uint8_t bank); static void lm78_reset(lm78_t *dev, uint8_t initialization); -#define ENABLE_LM78_LOG 1 + #ifdef ENABLE_LM78_LOG int lm78_do_log = ENABLE_LM78_LOG; @@ -118,12 +118,14 @@ lm78_remap(lm78_t *dev, uint8_t addr) } -static void -lm78_i2c_start(void *bus, uint8_t addr, void *priv) +static uint8_t +lm78_i2c_start(void *bus, uint8_t addr, uint8_t read, void *priv) { lm78_t *dev = (lm78_t *) priv; dev->i2c_state = 0; + + return 1; } diff --git a/src/device/i2c.c b/src/device/i2c.c index a4562deaf..75ec557ab 100644 --- a/src/device/i2c.c +++ b/src/device/i2c.c @@ -30,7 +30,7 @@ typedef struct _i2c_ { - void (*start)(void *bus, uint8_t addr, void *priv); + uint8_t (*start)(void *bus, uint8_t addr, uint8_t read, void *priv); uint8_t (*read)(void *bus, uint8_t addr, void *priv); uint8_t (*write)(void *bus, uint8_t addr, uint8_t data, void *priv); void (*stop)(void *bus, uint8_t addr, void *priv); @@ -84,16 +84,43 @@ i2c_addbus(char *name) void i2c_removebus(void *bus_handle) { + int c; + i2c_t *p, *q; + i2c_bus_t *bus = (i2c_bus_t *) bus_handle; + if (!bus_handle) return; - free(bus_handle); + for (c = 0; c < NADDRS; c++) { + p = bus->devices[c]; + if (!p) + continue; + while(p) { + q = p->next; + free(p); + p = q; + } + } + + free(bus); +} + + +char * +i2c_getbusname(void *bus_handle) +{ + i2c_bus_t *bus = (i2c_bus_t *) bus_handle; + + if (!bus_handle) + return; + + return(bus->name); } void i2c_sethandler(void *bus_handle, uint8_t base, int size, - void (*start)(void *bus, uint8_t addr, void *priv), + uint8_t (*start)(void *bus, uint8_t addr, uint8_t read, void *priv), uint8_t (*read)(void *bus, uint8_t addr, void *priv), uint8_t (*write)(void *bus, uint8_t addr, uint8_t data, void *priv), void (*stop)(void *bus, uint8_t addr, void *priv), @@ -103,7 +130,7 @@ i2c_sethandler(void *bus_handle, uint8_t base, int size, i2c_t *p, *q = NULL; i2c_bus_t *bus = (i2c_bus_t *) bus_handle; - if (!bus_handle || ((base + size) >= NADDRS)) + if (!bus_handle || ((base + size) > NADDRS)) return; for (c = 0; c < size; c++) { @@ -133,7 +160,7 @@ i2c_sethandler(void *bus_handle, uint8_t base, int size, void i2c_removehandler(void *bus_handle, uint8_t base, int size, - void (*start)(void *bus, uint8_t addr, void *priv), + uint8_t (*start)(void *bus, uint8_t addr, uint8_t read, void *priv), uint8_t (*read)(void *bus, uint8_t addr, void *priv), uint8_t (*write)(void *bus, uint8_t addr, uint8_t data, void *priv), void (*stop)(void *bus, uint8_t addr, void *priv), @@ -143,7 +170,7 @@ i2c_removehandler(void *bus_handle, uint8_t base, int size, i2c_t *p, *q; i2c_bus_t *bus = (i2c_bus_t *) bus_handle; - if (!bus_handle || ((base + size) >= NADDRS)) + if (!bus_handle || ((base + size) > NADDRS)) return; for (c = 0; c < size; c++) { @@ -152,7 +179,7 @@ i2c_removehandler(void *bus_handle, uint8_t base, int size, continue; while(p) { q = p->next; - if ((p->read == read) && (p->write == write) && (p->priv == priv)) { + if ((p->start == start) && (p->read == read) && (p->write == write) && (p->stop == stop) && (p->priv == priv)) { if (p->prev) p->prev->next = p->next; else @@ -173,7 +200,7 @@ i2c_removehandler(void *bus_handle, uint8_t base, int size, void i2c_handler(int set, void *bus_handle, uint8_t base, int size, - void (*start)(void *bus, uint8_t addr, void *priv), + uint8_t (*start)(void *bus, uint8_t addr, uint8_t read, void *priv), uint8_t (*read)(void *bus, uint8_t addr, void *priv), uint8_t (*write)(void *bus, uint8_t addr, uint8_t data, void *priv), void (*stop)(void *bus, uint8_t addr, void *priv), @@ -200,26 +227,29 @@ i2c_has_device(void *bus_handle, uint8_t addr) } -void -i2c_start(void *bus_handle, uint8_t addr) +uint8_t +i2c_start(void *bus_handle, uint8_t addr, uint8_t read) { + uint8_t ret = 0; i2c_bus_t *bus = (i2c_bus_t *) bus_handle; i2c_t *p; if (!bus) - return; + return(ret); p = bus->devices[addr]; if (p) { while(p) { if (p->start) { - p->start(bus_handle, addr, p->priv); + ret |= p->start(bus_handle, addr, read, p->priv); } p = p->next; } } i2c_log("I2C: start(%s, %02X)\n", bus->name, addr); + + return(ret); } @@ -264,7 +294,7 @@ i2c_write(void *bus_handle, uint8_t addr, uint8_t data) if (p) { while(p) { if (p->write) { - ret = p->write(bus_handle, addr, data, p->priv); + ret |= p->write(bus_handle, addr, data, p->priv); } p = p->next; } diff --git a/src/device/i2c_eeprom.c b/src/device/i2c_eeprom.c index dac00aeb2..02e738c89 100644 --- a/src/device/i2c_eeprom.c +++ b/src/device/i2c_eeprom.c @@ -29,11 +29,11 @@ typedef struct { void *i2c; uint8_t addr, *data, writable; - uint16_t addr_mask, addr_register; - uint8_t i2c_state; + uint32_t addr_mask, addr_register; + uint8_t addr_len, addr_pos; } i2c_eeprom_t; -#define ENABLE_I2C_EEPROM_LOG 1 + #ifdef ENABLE_I2C_EEPROM_LOG int i2c_eeprom_do_log = ENABLE_I2C_EEPROM_LOG; @@ -54,15 +54,17 @@ i2c_eeprom_log(const char *fmt, ...) #endif -void -i2c_eeprom_start(void *bus, uint8_t addr, void *priv) +uint8_t +i2c_eeprom_start(void *bus, uint8_t addr, uint8_t read, void *priv) { i2c_eeprom_t *dev = (i2c_eeprom_t *) priv; - i2c_eeprom_log("I2C EEPROM: start()\n"); + i2c_eeprom_log("I2C EEPROM %s %02X: start()\n", i2c_getbusname(dev->i2c), dev->addr); - dev->i2c_state = 0; - dev->addr_register = (addr << 8) & dev->addr_mask; + dev->addr_pos = 0; + dev->addr_register = (addr << dev->addr_len) & dev->addr_mask; + + return 1; } @@ -72,7 +74,7 @@ i2c_eeprom_read(void *bus, uint8_t addr, void *priv) i2c_eeprom_t *dev = (i2c_eeprom_t *) priv; uint8_t ret = dev->data[dev->addr_register]; - i2c_eeprom_log("I2C EEPROM: read(%04X) = %02X\n", dev->addr_register, ret); + i2c_eeprom_log("I2C EEPROM %s %02X: read(%06X) = %02X\n", i2c_getbusname(dev->i2c), dev->addr, dev->addr_register, ret); if (++dev->addr_register > dev->addr_mask) /* roll-over */ dev->addr_register = 0; @@ -85,13 +87,17 @@ i2c_eeprom_write(void *bus, uint8_t addr, uint8_t data, void *priv) { i2c_eeprom_t *dev = (i2c_eeprom_t *) priv; - if (dev->i2c_state == 0) { - dev->i2c_state = 1; - dev->addr_register = ((addr << 8) | data) & dev->addr_mask; - i2c_eeprom_log("I2C EEPROM: write(address, %04X)\n", dev->addr_register); + if (dev->addr_pos < dev->addr_len) { + dev->addr_register <<= 8; + dev->addr_register |= data; + dev->addr_register &= (1 << dev->addr_len) - 1; + dev->addr_register |= addr << dev->addr_len; + dev->addr_register &= dev->addr_mask; + i2c_eeprom_log("I2C EEPROM %s %02X: write(address, %04X)\n", i2c_getbusname(dev->i2c), dev->addr, dev->addr_register); + dev->addr_pos += 8; } else { - i2c_eeprom_log("I2C EEPROM: write(%04X, %02X) = %s\n", dev->addr_register, data, dev->writable ? "accepted" : "blocked"); - if (dev->writable) + i2c_eeprom_log("I2C EEPROM %s %02X: write(%06X, %02X) = %d\n", i2c_getbusname(dev->i2c), dev->addr, dev->addr_register, data, !!dev->writable); + if (dev->writable) dev->data[dev->addr_register] = data; if (++dev->addr_register > dev->addr_mask) /* roll-over */ dev->addr_register = 0; @@ -103,21 +109,24 @@ i2c_eeprom_write(void *bus, uint8_t addr, uint8_t data, void *priv) void * -i2c_eeprom_init(void *i2c, uint8_t addr, uint8_t *data, uint16_t size, uint8_t writable) +i2c_eeprom_init(void *i2c, uint8_t addr, uint8_t *data, uint32_t size, uint8_t writable) { i2c_eeprom_t *dev = (i2c_eeprom_t *) malloc(sizeof(i2c_eeprom_t)); memset(dev, 0, sizeof(i2c_eeprom_t)); - i2c_eeprom_log("I2C EEPROM: init(%02X, %d, %d)\n", addr, size, writable); + size &= 0x7fffff; /* address space limit of 8 MB = 7 bits from I2C address + 16 bits */ + + i2c_eeprom_log("I2C EEPROM %s %02X: init(%d, %d)\n", i2c_getbusname(i2c), addr, size, writable); dev->i2c = i2c; dev->addr = addr; dev->data = data; dev->writable = writable; + dev->addr_len = (size >= 4096) ? 16 : 8; /* use 16-bit addresses on 24C32 and above */ dev->addr_mask = size - 1; - i2c_sethandler(i2c, dev->addr & ~(dev->addr_mask >> 8), (dev->addr_mask >> 8) + 1, i2c_eeprom_start, i2c_eeprom_read, i2c_eeprom_write, NULL, dev); + i2c_sethandler(i2c, dev->addr & ~(dev->addr_mask >> dev->addr_len), (dev->addr_mask >> dev->addr_len) + 1, i2c_eeprom_start, i2c_eeprom_read, i2c_eeprom_write, NULL, dev); return dev; } @@ -128,9 +137,9 @@ i2c_eeprom_close(void *dev_handle) { i2c_eeprom_t *dev = (i2c_eeprom_t *) dev_handle; - i2c_eeprom_log("I2C EEPROM: close()\n"); + i2c_eeprom_log("I2C EEPROM %s %02X: close()\n", i2c_getbusname(dev->i2c), dev->addr); - i2c_removehandler(dev->i2c, dev->addr & ~(dev->addr_mask >> 8), (dev->addr_mask >> 8) + 1, i2c_eeprom_start, i2c_eeprom_read, i2c_eeprom_write, NULL, dev); + i2c_removehandler(dev->i2c, dev->addr & ~(dev->addr_mask >> dev->addr_len), (dev->addr_mask >> dev->addr_len) + 1, i2c_eeprom_start, i2c_eeprom_read, i2c_eeprom_write, NULL, dev); free(dev); } diff --git a/src/device/i2c_gpio.c b/src/device/i2c_gpio.c index aff3d1960..bca2e4a1e 100644 --- a/src/device/i2c_gpio.c +++ b/src/device/i2c_gpio.c @@ -39,6 +39,7 @@ enum { I2C_TRANSMIT_START, I2C_TRANSMIT, I2C_ACKNOWLEDGE, + I2C_NEGACKNOWLEDGE, I2C_TRANSACKNOWLEDGE, I2C_TRANSMIT_WAIT }; @@ -55,7 +56,7 @@ typedef struct { char *bus_name; void *i2c; uint8_t scl, sda, state, slave_state, slave_addr, - slave_rw, last_sda, pos, transmit, byte; + slave_read, last_sda, pos, transmit, byte; } i2c_gpio_t; @@ -113,11 +114,11 @@ 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: next_byte() = %02X\n", dev->bus_name, dev->byte); + i2c_gpio_log(1, "I2C GPIO %s: Transmitting data %02X\n", dev->bus_name, dev->byte); } -void +uint8_t i2c_gpio_write(i2c_gpio_t *dev) { uint8_t i; @@ -126,19 +127,20 @@ i2c_gpio_write(i2c_gpio_t *dev) case SLAVE_IDLE: i = dev->slave_addr; dev->slave_addr = dev->byte >> 1; - dev->slave_rw = dev->byte & 1; + dev->slave_read = dev->byte & 1; - i2c_gpio_log(1, "I2C GPIO %s: Initiating transfer to address %02X rw %d\n", dev->bus_name, dev->slave_addr, dev->slave_rw); + 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)) { dev->slave_state = SLAVE_INVALID; - break; + dev->slave_addr = 0xff; + return I2C_NEGACKNOWLEDGE; } - if (i == 0xff) - i2c_start(dev->i2c, dev->slave_addr); + if (i == 0xff) /* start only once per transfer */ + i2c_start(dev->i2c, dev->slave_addr, dev->slave_read); - if (dev->slave_rw) { + if (dev->slave_read) { dev->slave_state = SLAVE_SENDDATA; dev->transmit = TRANSMITTER_SLAVE; dev->byte = i2c_read(dev->i2c, dev->slave_addr); @@ -150,15 +152,22 @@ i2c_gpio_write(i2c_gpio_t *dev) case SLAVE_RECEIVEADDR: i2c_gpio_log(1, "I2C GPIO %s: Receiving address %02X\n", dev->bus_name, dev->byte); - i2c_write(dev->i2c, dev->slave_addr, dev->byte); - dev->slave_state = dev->slave_rw ? SLAVE_SENDDATA : SLAVE_RECEIVEDATA; + dev->slave_state = dev->slave_read ? SLAVE_SENDDATA : SLAVE_RECEIVEDATA; + if (!i2c_write(dev->i2c, dev->slave_addr, dev->byte)) + return I2C_NEGACKNOWLEDGE; break; case SLAVE_RECEIVEDATA: i2c_gpio_log(1, "I2C GPIO %s: Receiving data %02X\n", dev->bus_name, dev->byte); - i2c_write(dev->i2c, dev->slave_addr, dev->byte); + if (!i2c_write(dev->i2c, dev->slave_addr, dev->byte)) + return I2C_NEGACKNOWLEDGE; break; + + case SLAVE_INVALID: + return I2C_NEGACKNOWLEDGE; } + + return I2C_ACKNOWLEDGE; } @@ -167,7 +176,7 @@ 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) + 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; @@ -183,8 +192,8 @@ i2c_gpio_set(void *dev_handle, uint8_t scl, uint8_t sda) switch (dev->state) { case I2C_IDLE: - /* !dev->scl check breaks NCR SDMS. */ - if (/*!dev->scl &&*/ scl && dev->last_sda && !sda) { /* start bit */ + /* dev->scl check breaks NCR SDMS. */ + if (scl && dev->last_sda && !sda) { /* start bit */ i2c_gpio_log(2, "I2C GPIO %s: Start bit received (from IDLE)\n", dev->bus_name); dev->state = I2C_RECEIVE; dev->pos = 0; @@ -203,10 +212,8 @@ i2c_gpio_set(void *dev_handle, uint8_t scl, uint8_t sda) dev->byte |= 1; else dev->byte &= 0xfe; - if (++dev->pos == 8) { - i2c_gpio_write(dev); - dev->state = I2C_ACKNOWLEDGE; - } + if (++dev->pos == 8) + dev->state = i2c_gpio_write(dev); } else if (dev->scl && scl) { if (sda && !dev->last_sda) { /* stop bit */ i2c_gpio_log(2, "I2C GPIO %s: Stop bit received (from RECEIVE)\n", dev->bus_name); @@ -222,13 +229,23 @@ i2c_gpio_set(void *dev_handle, uint8_t scl, uint8_t sda) case I2C_ACKNOWLEDGE: if (!dev->scl && scl) { - i2c_gpio_log(2, "I2C GPIO %s: Acknowledging transfer\n", dev->bus_name); + i2c_gpio_log(2, "I2C GPIO %s: Acknowledging transfer to %02X\n", dev->bus_name, dev->slave_addr); sda = 0; dev->pos = 0; dev->state = (dev->transmit == TRANSMITTER_MASTER) ? I2C_RECEIVE_WAIT : I2C_TRANSMIT; } break; + case I2C_NEGACKNOWLEDGE: + if (!dev->scl && scl) { + i2c_gpio_log(2, "I2C GPIO %s: Nacking transfer\n", dev->bus_name); + sda = 1; + dev->pos = 0; + dev->state = I2C_IDLE; + dev->slave_state = SLAVE_IDLE; + } + break; + case I2C_TRANSACKNOWLEDGE: if (!dev->scl && scl) { if (sda) { /* not acknowledged; must be end of transfer */ @@ -306,12 +323,17 @@ uint8_t i2c_gpio_get_sda(void *dev_handle) { i2c_gpio_t *dev = (i2c_gpio_t *) dev_handle; - if ((dev->state == I2C_TRANSMIT) || (dev->state == I2C_ACKNOWLEDGE)) - return dev->sda; - else if (dev->state == I2C_RECEIVE_WAIT) - return 0; /* ack */ - else - return 1; + switch (dev->state) { + case I2C_TRANSMIT: + case I2C_ACKNOWLEDGE: + return dev->sda; + + case I2C_RECEIVE_WAIT: + return 0; /* ack */ + + default: + return 1; + } } diff --git a/src/device/smbus_piix4.c b/src/device/smbus_piix4.c index 100a31ab8..ae41347f6 100644 --- a/src/device/smbus_piix4.c +++ b/src/device/smbus_piix4.c @@ -98,95 +98,189 @@ static void smbus_piix4_write(uint16_t addr, uint8_t val, void *priv) { smbus_piix4_t *dev = (smbus_piix4_t *) priv; - uint8_t smbus_addr, smbus_read, prev_stat; + uint8_t smbus_addr, cmd, read, block_len, prev_stat, timer_bytes = 0; smbus_piix4_log("SMBus PIIX4: write(%02X, %02X)\n", addr, val); prev_stat = dev->next_stat; - dev->next_stat = 0; + dev->next_stat = 0x00; switch (addr - dev->io_base) { case 0x00: - /* some status bits are reset by writing 1 to them */ - for (smbus_addr = 0x02; smbus_addr <= 0x10; smbus_addr <<= 1) { + for (smbus_addr = 0x02; smbus_addr <= 0x10; smbus_addr <<= 1) { /* handle clearable bits */ if (val & smbus_addr) dev->stat &= ~smbus_addr; } break; case 0x02: - dev->ctl = val & ~(0x40); /* START always reads 0 */ + dev->ctl = val & ((dev->local == SMBUS_VIA) ? 0x3f : 0x1f); if (val & 0x02) { /* cancel an in-progress command if KILL is set */ - /* cancel only if a command is in progress */ - if (prev_stat) { - dev->stat = 0x10; /* raise FAILED */ + if (prev_stat) { /* cancel only if a command is in progress */ timer_disable(&dev->response_timer); + dev->stat = 0x10; /* raise FAILED */ } } if (val & 0x40) { /* dispatch command if START is set */ + timer_bytes++; /* address */ + smbus_addr = (dev->addr >> 1); - if (!i2c_has_device(i2c_smbus, smbus_addr)) { - /* raise DEV_ERR if no device is at this address */ - dev->next_stat = 0x4; + read = dev->addr & 0x01; + + /* Raise DEV_ERR if no device is at this address, or if the device returned NAK when starting the transfer. */ + if (!i2c_has_device(i2c_smbus, smbus_addr) || !i2c_start(i2c_smbus, smbus_addr, read)) { + dev->next_stat = 0x04; break; } - smbus_read = dev->addr & 0x01; - /* start transaction */ - i2c_start(i2c_smbus, smbus_addr); + dev->next_stat = 0x02; /* raise INTER (command completed) by default */ - /* decode the 3-bit command protocol */ - dev->next_stat = 0x2; /* raise INTER (command completed) by default */ - switch ((val >> 2) & 0x7) { + /* Decode the command protocol. + VIA-specific modes (0x4 and [0x6:0xf]) are undocumented and required real hardware research. */ + cmd = (val >> 2) & 0xf; + smbus_piix4_log("SMBus PIIX4: protocol=%X cmd=%02X data0=%02X data1=%02X\n", cmd, dev->cmd, dev->data0, dev->data1); + switch (cmd) { case 0x0: /* quick R/W */ break; case 0x1: /* byte R/W */ - if (smbus_read) + if (read) /* byte read */ dev->data0 = i2c_read(i2c_smbus, smbus_addr); - else + else /* byte write */ i2c_write(i2c_smbus, smbus_addr, dev->data0); + timer_bytes++; + break; case 0x2: /* byte data R/W */ + /* command write */ i2c_write(i2c_smbus, smbus_addr, dev->cmd); - if (smbus_read) + timer_bytes++; + + if (read) /* byte read */ dev->data0 = i2c_read(i2c_smbus, smbus_addr); - else + else /* byte write */ i2c_write(i2c_smbus, smbus_addr, dev->data0); + timer_bytes++; + break; case 0x3: /* word data R/W */ + /* command write */ i2c_write(i2c_smbus, smbus_addr, dev->cmd); - if (smbus_read) { + timer_bytes++; + + if (read) { /* word read */ dev->data0 = i2c_read(i2c_smbus, smbus_addr); dev->data1 = i2c_read(i2c_smbus, smbus_addr); - } else { + } else { /* word write */ i2c_write(i2c_smbus, smbus_addr, dev->data0); i2c_write(i2c_smbus, smbus_addr, dev->data1); } + timer_bytes += 2; + + break; + + case 0x4: /* process call */ + if (dev->local != SMBUS_VIA) /* VIA only */ + goto unknown_protocol; + + if (!read) { /* command write (only when writing) */ + i2c_write(i2c_smbus, smbus_addr, dev->cmd); + timer_bytes++; + } + + /* fall-through */ + + case 0xc: /* I2C process call */ + if (!read) { /* word write (only when writing) */ + i2c_write(i2c_smbus, smbus_addr, dev->data0); + i2c_write(i2c_smbus, smbus_addr, dev->data1); + timer_bytes += 2; + } + + /* word read */ + dev->data0 = i2c_read(i2c_smbus, smbus_addr); + dev->data1 = i2c_read(i2c_smbus, smbus_addr); + timer_bytes += 2; + break; case 0x5: /* block R/W */ + timer_bytes++; /* account for the SMBus length byte now */ + + /* fall-through */ + + case 0xd: /* I2C block R/W */ i2c_write(i2c_smbus, smbus_addr, dev->cmd); - /* SMBus block data is preceded by a length */ - if (smbus_read) { - dev->data0 = i2c_read(i2c_smbus, smbus_addr); - for (smbus_read = 0; smbus_read < MIN(SMBUS_PIIX4_BLOCK_DATA_SIZE, dev->data0); smbus_read++) - dev->data[smbus_read] = i2c_read(i2c_smbus, smbus_addr); + timer_bytes++; + + if (read) { + /* block read [data0] (I2C) or [first byte] (SMBus) bytes */ + block_len = (cmd == 0x5) ? i2c_read(i2c_smbus, smbus_addr) : dev->data0; + for (read = 0; read < block_len; read++) + dev->data[read & SMBUS_PIIX4_BLOCK_DATA_MASK] = i2c_read(i2c_smbus, smbus_addr); } else { - i2c_write(i2c_smbus, smbus_addr, dev->data0); - for (smbus_read = 0; smbus_read < MIN(SMBUS_PIIX4_BLOCK_DATA_SIZE, dev->data0); smbus_read++) - i2c_write(i2c_smbus, smbus_addr, dev->data[smbus_read]); + block_len = dev->data0; + if (cmd == 0x5) /* send length [data0] as first byte on SMBus */ + i2c_write(i2c_smbus, smbus_addr, block_len); + /* block write [data0] bytes */ + for (read = 0; read < block_len; read++) { + if (!i2c_write(i2c_smbus, smbus_addr, dev->data[read & SMBUS_PIIX4_BLOCK_DATA_MASK])) + break; + } } + timer_bytes += read; + break; - default: - /* other command protocols have undefined behavior, but raise DEV_ERR to be safe */ - dev->next_stat = 0x4; + case 0x6: /* I2C with 10-bit address */ + if (dev->local != SMBUS_VIA) /* VIA only */ + goto unknown_protocol; + + /* command write */ + i2c_write(i2c_smbus, smbus_addr, dev->cmd); + timer_bytes += 1; + + /* fall-through */ + + case 0xe: /* I2C with 7-bit address */ + if (!read) { /* word write (only when writing) */ + i2c_write(i2c_smbus, smbus_addr, dev->data0); + i2c_write(i2c_smbus, smbus_addr, dev->data1); + timer_bytes += 2; + } + + /* block read [first byte] bytes */ + block_len = dev->data[0]; + for (read = 0; read < block_len; read++) + dev->data[read & SMBUS_PIIX4_BLOCK_DATA_MASK] = i2c_read(i2c_smbus, smbus_addr); + timer_bytes += read; + + break; + + case 0xf: /* universal */ + /* block write [data0] bytes */ + for (read = 0; read < dev->data0; read++) { + if (!i2c_write(i2c_smbus, smbus_addr, dev->data[read & SMBUS_PIIX4_BLOCK_DATA_MASK])) + break; + } + timer_bytes += read; + + /* block read [data1] bytes */ + for (read = 0; read < dev->data1; read++) + dev->data[read & SMBUS_PIIX4_BLOCK_DATA_MASK] = i2c_read(i2c_smbus, smbus_addr); + timer_bytes += read; + + break; + + default: /* unknown */ +unknown_protocol: + dev->next_stat = 0x04; /* raise DEV_ERR */ + timer_bytes = 0; break; } - /* stop transaction */ + /* Finish transfer. */ i2c_stop(i2c_smbus, smbus_addr); } break; @@ -214,11 +308,11 @@ smbus_piix4_write(uint16_t addr, uint8_t val, void *priv) break; } - /* if a status register update was given, dispatch it after 10us to ensure nothing breaks */ - if (dev->next_stat) { - dev->stat = 0x1; /* raise HOST_BUSY while waiting */ + if (dev->next_stat) { /* schedule dispatch of any pending status register update */ + dev->stat = 0x01; /* raise HOST_BUSY while waiting */ timer_disable(&dev->response_timer); - timer_set_delay_u64(&dev->response_timer, 10 * TIMER_USEC); + /* delay = ((half clock for start + half clock for stop) + (bytes * (8 bits + ack))) * 60us period measured on real VIA 686B */ + timer_set_delay_u64(&dev->response_timer, (1 + (timer_bytes * 9)) * 60 * TIMER_USEC); } } @@ -228,7 +322,7 @@ smbus_piix4_response(void *priv) { smbus_piix4_t *dev = (smbus_piix4_t *) priv; - /* dispatch the status register update */ + /* Dispatch the status register update. */ dev->stat = dev->next_stat; } @@ -253,7 +347,8 @@ smbus_piix4_init(const device_t *info) smbus_piix4_t *dev = (smbus_piix4_t *) malloc(sizeof(smbus_piix4_t)); memset(dev, 0, sizeof(smbus_piix4_t)); - i2c_smbus = dev->i2c = i2c_addbus("smbus_piix4"); + dev->local = info->local; + i2c_smbus = dev->i2c = i2c_addbus((dev->local == SMBUS_VIA) ? "smbus_vt82c686b" : "smbus_piix4"); timer_add(&dev->response_timer, smbus_piix4_response, dev, 0); @@ -277,7 +372,16 @@ smbus_piix4_close(void *priv) const device_t piix4_smbus_device = { "PIIX4-compatible SMBus Host Controller", DEVICE_AT, - 0, + SMBUS_PIIX4, + smbus_piix4_init, smbus_piix4_close, NULL, + { NULL }, NULL, NULL, + NULL +}; + +const device_t via_smbus_device = { + "VIA VT82C686B SMBus Host Controller", + DEVICE_AT, + SMBUS_VIA, smbus_piix4_init, smbus_piix4_close, NULL, { NULL }, NULL, NULL, NULL diff --git a/src/include/86box/i2c.h b/src/include/86box/i2c.h index 8db7efb7d..e2135225c 100644 --- a/src/include/86box/i2c.h +++ b/src/include/86box/i2c.h @@ -25,36 +25,37 @@ extern void *i2c_smbus; /* i2c.c */ extern void *i2c_addbus(char *name); extern void i2c_removebus(void *bus_handle); +extern char *i2c_getbusname(void *bus_handle); extern void i2c_sethandler(void *bus_handle, uint8_t base, int size, - void (*start)(void *bus, uint8_t addr, void *priv), + uint8_t (*start)(void *bus, uint8_t addr, uint8_t read, void *priv), uint8_t (*read)(void *bus, uint8_t addr, void *priv), uint8_t (*write)(void *bus, uint8_t addr, uint8_t data, void *priv), void (*stop)(void *bus, uint8_t addr, void *priv), void *priv); extern void i2c_removehandler(void *bus_handle, uint8_t base, int size, - void (*start)(void *bus, uint8_t addr, void *priv), + uint8_t (*start)(void *bus, uint8_t addr, uint8_t read, void *priv), uint8_t (*read)(void *bus, uint8_t addr, void *priv), uint8_t (*write)(void *bus, uint8_t addr, uint8_t data, void *priv), void (*stop)(void *bus, uint8_t addr, void *priv), void *priv); extern void i2c_handler(int set, void *bus_handle, uint8_t base, int size, - void (*start)(void *bus, uint8_t addr, void *priv), + uint8_t (*start)(void *bus, uint8_t addr, uint8_t read, void *priv), uint8_t (*read)(void *bus, uint8_t addr, void *priv), uint8_t (*write)(void *bus, uint8_t addr, uint8_t data, void *priv), void (*stop)(void *bus, uint8_t addr, void *priv), void *priv); extern uint8_t i2c_has_device(void *bus_handle, uint8_t addr); -extern void i2c_start(void *bus_handle, uint8_t addr); +extern uint8_t i2c_start(void *bus_handle, uint8_t addr, uint8_t read); extern uint8_t i2c_read(void *bus_handle, uint8_t addr); extern uint8_t i2c_write(void *bus_handle, uint8_t addr, uint8_t data); extern void i2c_stop(void *bus_handle, uint8_t addr); /* i2c_eeprom.c */ -extern void *i2c_eeprom_init(void *i2c, uint8_t addr, uint8_t *data, uint16_t size, uint8_t writable); +extern void *i2c_eeprom_init(void *i2c, uint8_t addr, uint8_t *data, uint32_t size, uint8_t writable); extern void i2c_eeprom_close(void *dev_handle); /* i2c_gpio.c */ diff --git a/src/include/86box/smbus_piix4.h b/src/include/86box/smbus_piix4.h index aa7188393..3173ead4e 100644 --- a/src/include/86box/smbus_piix4.h +++ b/src/include/86box/smbus_piix4.h @@ -19,15 +19,20 @@ #define SMBUS_PIIX4_BLOCK_DATA_SIZE 32 +#define SMBUS_PIIX4_BLOCK_DATA_MASK (SMBUS_PIIX4_BLOCK_DATA_SIZE - 1) -typedef struct -{ +enum { + SMBUS_PIIX4 = 0, + SMBUS_VIA +}; + +typedef struct { + uint32_t local; uint16_t io_base; uint8_t stat, next_stat, ctl, cmd, addr, data0, data1, - index, - data[SMBUS_PIIX4_BLOCK_DATA_SIZE]; + index, data[SMBUS_PIIX4_BLOCK_DATA_SIZE]; pc_timer_t response_timer; void *i2c; } smbus_piix4_t; @@ -38,6 +43,7 @@ extern void smbus_piix4_remap(smbus_piix4_t *dev, uint16_t new_io_base, uint8_t #ifdef EMU_DEVICE_H extern const device_t piix4_smbus_device; +extern const device_t via_smbus_device; #endif