i2c: Allow I2C devices to NAK start events

Add a return value to the event handler.  Some I2C devices will
NAK if they have no data, so allow them to do this.  This required
the following changes:

Go through all the event handlers and change them to return int
and return 0.

Modify i2c_start_transfer to terminate the transaction on a NAK.

Modify smbus handing to not assert if a NAK occurs on a second
operation, and terminate the transaction and return -1 instead.

Add some information on semantics to I2CSlaveClass.

Signed-off-by: Corey Minyard <cminyard@mvista.com>
Reviewed-by: Peter Maydell <peter.maydell@linaro.org>
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
Corey Minyard 2017-01-09 11:40:20 +00:00 committed by Peter Maydell
parent ffe22bf510
commit d307c28ca9
14 changed files with 79 additions and 26 deletions

View file

@ -88,18 +88,26 @@ int i2c_bus_busy(I2CBus *bus)
return !QLIST_EMPTY(&bus->current_devs);
}
/*
* 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.
*/
/* TODO: Make this handle multiple masters. */
/*
* 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.
*/
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;

View file

@ -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)

View file

@ -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) {