From ddabca757a4eb29f342f7f1d01634ccdbe0e3844 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Le=20Goater?= Date: Fri, 2 Jun 2017 11:51:49 +0100 Subject: aspeed/i2c: improve command handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Multiple I2C commands can be fired simultaneously and the controller execute the commands following these priorities: (1) Master Start Command (2) Master Transmit Command (3) Slave Transmit Command or Master Receive Command (4) Master Stop Command The current code is incorrect with respect to the above sequence and needs to be reworked to handle each individual command. Signed-off-by: Cédric Le Goater Message-id: 1494827476-1487-2-git-send-email-clg@kaod.org Signed-off-by: Peter Maydell --- hw/i2c/aspeed_i2c.c | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) (limited to 'hw/i2c') diff --git a/hw/i2c/aspeed_i2c.c b/hw/i2c/aspeed_i2c.c index ce5b1f0fa4..56a4fdf5c5 100644 --- a/hw/i2c/aspeed_i2c.c +++ b/hw/i2c/aspeed_i2c.c @@ -171,6 +171,7 @@ static uint64_t aspeed_i2c_bus_read(void *opaque, hwaddr offset, static void aspeed_i2c_bus_handle_cmd(AspeedI2CBus *bus, uint64_t value) { + bus->cmd &= ~0xFFFF; bus->cmd |= value & 0xFFFF; bus->intr_status = 0; @@ -182,15 +183,27 @@ static void aspeed_i2c_bus_handle_cmd(AspeedI2CBus *bus, uint64_t value) bus->intr_status |= I2CD_INTR_TX_ACK; } - } else if (bus->cmd & I2CD_M_TX_CMD) { + /* START command is also a TX command, as the slave address is + * sent on the bus */ + bus->cmd &= ~(I2CD_M_START_CMD | I2CD_M_TX_CMD); + + /* No slave found */ + if (!i2c_bus_busy(bus->bus)) { + return; + } + } + + if (bus->cmd & I2CD_M_TX_CMD) { if (i2c_send(bus->bus, bus->buf)) { bus->intr_status |= (I2CD_INTR_TX_NAK | I2CD_INTR_ABNORMAL); i2c_end_transfer(bus->bus); } else { bus->intr_status |= I2CD_INTR_TX_ACK; } + bus->cmd &= ~I2CD_M_TX_CMD; + } - } else if (bus->cmd & I2CD_M_RX_CMD) { + if (bus->cmd & I2CD_M_RX_CMD) { int ret = i2c_recv(bus->bus); if (ret < 0) { qemu_log_mask(LOG_GUEST_ERROR, "%s: read failed\n", __func__); @@ -199,6 +212,7 @@ static void aspeed_i2c_bus_handle_cmd(AspeedI2CBus *bus, uint64_t value) bus->intr_status |= I2CD_INTR_RX_DONE; } bus->buf = (ret & I2CD_BYTE_BUF_RX_MASK) << I2CD_BYTE_BUF_RX_SHIFT; + bus->cmd &= ~I2CD_M_RX_CMD; } if (bus->cmd & (I2CD_M_STOP_CMD | I2CD_M_S_RX_CMD_LAST)) { @@ -208,11 +222,8 @@ static void aspeed_i2c_bus_handle_cmd(AspeedI2CBus *bus, uint64_t value) i2c_end_transfer(bus->bus); bus->intr_status |= I2CD_INTR_NORMAL_STOP; } + bus->cmd &= ~I2CD_M_STOP_CMD; } - - /* command is handled, reset it and check for interrupts */ - bus->cmd &= ~0xFFFF; - aspeed_i2c_bus_raise_interrupt(bus); } static void aspeed_i2c_bus_write(void *opaque, hwaddr offset, @@ -262,6 +273,7 @@ static void aspeed_i2c_bus_write(void *opaque, hwaddr offset, } aspeed_i2c_bus_handle_cmd(bus, value); + aspeed_i2c_bus_raise_interrupt(bus); break; default: -- cgit v1.2.3 From d0efdc1686405c3873703a8617daba54d3fbb75f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Le=20Goater?= Date: Fri, 2 Jun 2017 11:51:49 +0100 Subject: aspeed/i2c: handle LAST command under the RX command MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Today, the LAST command is handled with the STOP command but this is incorrect. Also nack the I2C bus when a LAST is issued. Signed-off-by: Cédric Le Goater Message-id: 1494827476-1487-3-git-send-email-clg@kaod.org Signed-off-by: Peter Maydell --- hw/i2c/aspeed_i2c.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'hw/i2c') diff --git a/hw/i2c/aspeed_i2c.c b/hw/i2c/aspeed_i2c.c index 56a4fdf5c5..67004c6753 100644 --- a/hw/i2c/aspeed_i2c.c +++ b/hw/i2c/aspeed_i2c.c @@ -203,7 +203,7 @@ static void aspeed_i2c_bus_handle_cmd(AspeedI2CBus *bus, uint64_t value) bus->cmd &= ~I2CD_M_TX_CMD; } - if (bus->cmd & I2CD_M_RX_CMD) { + if (bus->cmd & (I2CD_M_RX_CMD | I2CD_M_S_RX_CMD_LAST)) { int ret = i2c_recv(bus->bus); if (ret < 0) { qemu_log_mask(LOG_GUEST_ERROR, "%s: read failed\n", __func__); @@ -212,10 +212,13 @@ static void aspeed_i2c_bus_handle_cmd(AspeedI2CBus *bus, uint64_t value) bus->intr_status |= I2CD_INTR_RX_DONE; } bus->buf = (ret & I2CD_BYTE_BUF_RX_MASK) << I2CD_BYTE_BUF_RX_SHIFT; - bus->cmd &= ~I2CD_M_RX_CMD; + if (bus->cmd & I2CD_M_S_RX_CMD_LAST) { + i2c_nack(bus->bus); + } + bus->cmd &= ~(I2CD_M_RX_CMD | I2CD_M_S_RX_CMD_LAST); } - if (bus->cmd & (I2CD_M_STOP_CMD | I2CD_M_S_RX_CMD_LAST)) { + if (bus->cmd & I2CD_M_STOP_CMD) { if (!i2c_bus_busy(bus->bus)) { bus->intr_status |= I2CD_INTR_ABNORMAL; } else { -- cgit v1.2.3 From 4960f084cfb4d42bb8995dd23ccad168c3e4ad3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Le=20Goater?= Date: Fri, 2 Jun 2017 11:51:49 +0100 Subject: aspeed/i2c: introduce a state machine MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Aspeed I2C controller maintains a state machine in the command register, which is mostly used for debug. Let's start adding a few states to handle abnormal STOP commands. Today, the model uses the busy status of the bus as a condition to do so but it is not precise enough. Also remove the ABNORMAL bit for failing TX commands. This is incorrect with respect to the specs. Signed-off-by: Cédric Le Goater Message-id: 1494827476-1487-4-git-send-email-clg@kaod.org Signed-off-by: Peter Maydell --- hw/i2c/aspeed_i2c.c | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) (limited to 'hw/i2c') diff --git a/hw/i2c/aspeed_i2c.c b/hw/i2c/aspeed_i2c.c index 67004c6753..c762c7366a 100644 --- a/hw/i2c/aspeed_i2c.c +++ b/hw/i2c/aspeed_i2c.c @@ -169,6 +169,21 @@ static uint64_t aspeed_i2c_bus_read(void *opaque, hwaddr offset, } } +static void aspeed_i2c_set_state(AspeedI2CBus *bus, uint8_t state) +{ + bus->cmd &= ~(I2CD_TX_STATE_MASK << I2CD_TX_STATE_SHIFT); + bus->cmd |= (state & I2CD_TX_STATE_MASK) << I2CD_TX_STATE_SHIFT; +} + +static uint8_t aspeed_i2c_get_state(AspeedI2CBus *bus) +{ + return (bus->cmd >> I2CD_TX_STATE_SHIFT) & I2CD_TX_STATE_MASK; +} + +/* + * The state machine needs some refinement. It is only used to track + * invalid STOP commands for the moment. + */ static void aspeed_i2c_bus_handle_cmd(AspeedI2CBus *bus, uint64_t value) { bus->cmd &= ~0xFFFF; @@ -176,6 +191,11 @@ static void aspeed_i2c_bus_handle_cmd(AspeedI2CBus *bus, uint64_t value) bus->intr_status = 0; if (bus->cmd & I2CD_M_START_CMD) { + uint8_t state = aspeed_i2c_get_state(bus) & I2CD_MACTIVE ? + I2CD_MSTARTR : I2CD_MSTART; + + aspeed_i2c_set_state(bus, state); + if (i2c_start_transfer(bus->bus, extract32(bus->buf, 1, 7), extract32(bus->buf, 0, 1))) { bus->intr_status |= I2CD_INTR_TX_NAK; @@ -191,20 +211,26 @@ static void aspeed_i2c_bus_handle_cmd(AspeedI2CBus *bus, uint64_t value) if (!i2c_bus_busy(bus->bus)) { return; } + aspeed_i2c_set_state(bus, I2CD_MACTIVE); } if (bus->cmd & I2CD_M_TX_CMD) { + aspeed_i2c_set_state(bus, I2CD_MTXD); if (i2c_send(bus->bus, bus->buf)) { - bus->intr_status |= (I2CD_INTR_TX_NAK | I2CD_INTR_ABNORMAL); + bus->intr_status |= (I2CD_INTR_TX_NAK); i2c_end_transfer(bus->bus); } else { bus->intr_status |= I2CD_INTR_TX_ACK; } bus->cmd &= ~I2CD_M_TX_CMD; + aspeed_i2c_set_state(bus, I2CD_MACTIVE); } if (bus->cmd & (I2CD_M_RX_CMD | I2CD_M_S_RX_CMD_LAST)) { - int ret = i2c_recv(bus->bus); + int ret; + + aspeed_i2c_set_state(bus, I2CD_MRXD); + ret = i2c_recv(bus->bus); if (ret < 0) { qemu_log_mask(LOG_GUEST_ERROR, "%s: read failed\n", __func__); ret = 0xff; @@ -216,16 +242,20 @@ static void aspeed_i2c_bus_handle_cmd(AspeedI2CBus *bus, uint64_t value) i2c_nack(bus->bus); } bus->cmd &= ~(I2CD_M_RX_CMD | I2CD_M_S_RX_CMD_LAST); + aspeed_i2c_set_state(bus, I2CD_MACTIVE); } if (bus->cmd & I2CD_M_STOP_CMD) { - if (!i2c_bus_busy(bus->bus)) { + if (!(aspeed_i2c_get_state(bus) & I2CD_MACTIVE)) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: abnormal stop\n", __func__); bus->intr_status |= I2CD_INTR_ABNORMAL; } else { + aspeed_i2c_set_state(bus, I2CD_MSTOP); i2c_end_transfer(bus->bus); bus->intr_status |= I2CD_INTR_NORMAL_STOP; } bus->cmd &= ~I2CD_M_STOP_CMD; + aspeed_i2c_set_state(bus, I2CD_IDLE); } } -- cgit v1.2.3