aboutsummaryrefslogtreecommitdiff
path: root/hw/i2c
diff options
context:
space:
mode:
Diffstat (limited to 'hw/i2c')
-rw-r--r--hw/i2c/core.c31
-rw-r--r--hw/i2c/i2c-ddc.c4
-rw-r--r--hw/i2c/smbus.c13
3 files changed, 37 insertions, 11 deletions
diff --git a/hw/i2c/core.c b/hw/i2c/core.c
index e40781ea3b..2c1234cdff 100644
--- a/hw/i2c/core.c
+++ b/hw/i2c/core.c
@@ -88,18 +88,26 @@ int i2c_bus_busy(I2CBus *bus)
return !QLIST_EMPTY(&bus->current_devs);
}
+/* TODO: Make this handle multiple masters. */
/*
- * Returns non-zero if the address is not valid. If this is called
- * again without an intervening i2c_end_transfer(), like in the SMBus
- * case where the operation is switched from write to read, this
- * function will not rescan the bus and thus cannot fail.
+ * Start or continue an i2c transaction. When this is called for the
+ * first time or after an i2c_end_transfer(), if it returns an error
+ * the bus transaction is terminated (or really never started). If
+ * this is called after another i2c_start_transfer() without an
+ * intervening i2c_end_transfer(), and it returns an error, the
+ * transaction will not be terminated. The caller must do it.
+ *
+ * This corresponds with the way real hardware works. The SMBus
+ * protocol uses a start transfer to switch from write to read mode
+ * without releasing the bus. If that fails, the bus is still
+ * in a transaction.
*/
-/* TODO: Make this handle multiple masters. */
int i2c_start_transfer(I2CBus *bus, uint8_t address, int recv)
{
BusChild *kid;
I2CSlaveClass *sc;
I2CNode *node;
+ bool bus_scanned = false;
if (address == I2C_BROADCAST) {
/*
@@ -130,6 +138,7 @@ int i2c_start_transfer(I2CBus *bus, uint8_t address, int recv)
}
}
}
+ bus_scanned = true;
}
if (QLIST_EMPTY(&bus->current_devs)) {
@@ -137,11 +146,21 @@ int i2c_start_transfer(I2CBus *bus, uint8_t address, int recv)
}
QLIST_FOREACH(node, &bus->current_devs, next) {
+ int rv;
+
sc = I2C_SLAVE_GET_CLASS(node->elt);
/* If the bus is already busy, assume this is a repeated
start condition. */
+
if (sc->event) {
- sc->event(node->elt, recv ? I2C_START_RECV : I2C_START_SEND);
+ rv = sc->event(node->elt, recv ? I2C_START_RECV : I2C_START_SEND);
+ if (rv && !bus->broadcast) {
+ if (bus_scanned) {
+ /* First call, terminate the transfer. */
+ i2c_end_transfer(bus);
+ }
+ return rv;
+ }
}
}
return 0;
diff --git a/hw/i2c/i2c-ddc.c b/hw/i2c/i2c-ddc.c
index 1227212934..66899d7233 100644
--- a/hw/i2c/i2c-ddc.c
+++ b/hw/i2c/i2c-ddc.c
@@ -230,13 +230,15 @@ static void i2c_ddc_reset(DeviceState *ds)
s->reg = 0;
}
-static void i2c_ddc_event(I2CSlave *i2c, enum i2c_event event)
+static int i2c_ddc_event(I2CSlave *i2c, enum i2c_event event)
{
I2CDDCState *s = I2CDDC(i2c);
if (event == I2C_START_SEND) {
s->firstbyte = true;
}
+
+ return 0;
}
static int i2c_ddc_rx(I2CSlave *i2c)
diff --git a/hw/i2c/smbus.c b/hw/i2c/smbus.c
index 5b4dd3eba4..2d1b79a689 100644
--- a/hw/i2c/smbus.c
+++ b/hw/i2c/smbus.c
@@ -67,7 +67,7 @@ static void smbus_do_write(SMBusDevice *dev)
}
}
-static void smbus_i2c_event(I2CSlave *s, enum i2c_event event)
+static int smbus_i2c_event(I2CSlave *s, enum i2c_event event)
{
SMBusDevice *dev = SMBUS_DEVICE(s);
@@ -148,6 +148,8 @@ static void smbus_i2c_event(I2CSlave *s, enum i2c_event event)
break;
}
}
+
+ return 0;
}
static int smbus_i2c_recv(I2CSlave *s)
@@ -249,7 +251,8 @@ int smbus_read_byte(I2CBus *bus, uint8_t addr, uint8_t command)
}
i2c_send(bus, command);
if (i2c_start_transfer(bus, addr, 1)) {
- assert(0);
+ i2c_end_transfer(bus);
+ return -1;
}
data = i2c_recv(bus);
i2c_nack(bus);
@@ -276,7 +279,8 @@ int smbus_read_word(I2CBus *bus, uint8_t addr, uint8_t command)
}
i2c_send(bus, command);
if (i2c_start_transfer(bus, addr, 1)) {
- assert(0);
+ i2c_end_transfer(bus);
+ return -1;
}
data = i2c_recv(bus);
data |= i2c_recv(bus) << 8;
@@ -307,7 +311,8 @@ int smbus_read_block(I2CBus *bus, uint8_t addr, uint8_t command, uint8_t *data)
}
i2c_send(bus, command);
if (i2c_start_transfer(bus, addr, 1)) {
- assert(0);
+ i2c_end_transfer(bus);
+ return -1;
}
len = i2c_recv(bus);
if (len > 32) {