aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/i2c/mxc_i2c.c95
1 files changed, 64 insertions, 31 deletions
diff --git a/drivers/i2c/mxc_i2c.c b/drivers/i2c/mxc_i2c.c
index f17a47f..23119cc 100644
--- a/drivers/i2c/mxc_i2c.c
+++ b/drivers/i2c/mxc_i2c.c
@@ -482,8 +482,13 @@ static int i2c_write_data(struct mxc_i2c_bus *i2c_bus, u8 chip, const u8 *buf,
return ret;
}
+/* Will generate a STOP after the last byte if "last" is true, i.e. this is the
+ * final message of a transaction. If not, it switches the bus back to TX mode
+ * and does not send a STOP, leaving the bus in a state where a repeated start
+ * and address can be sent for another message.
+ */
static int i2c_read_data(struct mxc_i2c_bus *i2c_bus, uchar chip, uchar *buf,
- int len)
+ int len, bool last)
{
int ret;
unsigned int temp;
@@ -513,17 +518,31 @@ static int i2c_read_data(struct mxc_i2c_bus *i2c_bus, uchar chip, uchar *buf,
return ret;
}
- /*
- * It must generate STOP before read I2DR to prevent
- * controller from generating another clock cycle
- */
if (i == (len - 1)) {
- i2c_imx_stop(i2c_bus);
+ /* Final byte has already been received by master! When
+ * we read it from I2DR, the master will start another
+ * cycle. We must program it first to send a STOP or
+ * switch to TX to avoid this.
+ */
+ if (last) {
+ i2c_imx_stop(i2c_bus);
+ } else {
+ /* Final read, no stop, switch back to tx */
+ temp = readb(base + (I2CR << reg_shift));
+ temp |= I2CR_MTX | I2CR_TX_NO_AK;
+ writeb(temp, base + (I2CR << reg_shift));
+ }
} else if (i == (len - 2)) {
+ /* Master has already recevied penultimate byte. When
+ * we read it from I2DR, master will start RX of final
+ * byte. We must set TX_NO_AK now so it does not ACK
+ * that final byte.
+ */
temp = readb(base + (I2CR << reg_shift));
temp |= I2CR_TX_NO_AK;
writeb(temp, base + (I2CR << reg_shift));
}
+
writeb(I2SR_IIF_CLEAR, base + (I2SR << reg_shift));
buf[i] = readb(base + (I2DR << reg_shift));
}
@@ -533,7 +552,9 @@ static int i2c_read_data(struct mxc_i2c_bus *i2c_bus, uchar chip, uchar *buf,
debug(" 0x%02x", buf[ret]);
debug("\n");
- i2c_imx_stop(i2c_bus);
+ /* It is not clear to me that this is necessary */
+ if (last)
+ i2c_imx_stop(i2c_bus);
return 0;
}
@@ -585,7 +606,7 @@ static int bus_i2c_read(struct mxc_i2c_bus *i2c_bus, u8 chip, u32 addr,
return ret;
}
- ret = i2c_read_data(i2c_bus, chip, buf, len);
+ ret = i2c_read_data(i2c_bus, chip, buf, len, true);
i2c_imx_stop(i2c_bus);
return ret;
@@ -939,42 +960,54 @@ static int mxc_i2c_xfer(struct udevice *bus, struct i2c_msg *msg, int nmsgs)
ulong base = i2c_bus->base;
int reg_shift = i2c_bus->driver_data & I2C_QUIRK_FLAG ?
VF610_I2C_REGSHIFT : IMX_I2C_REGSHIFT;
+ int read_mode;
- /*
- * Here the 3rd parameter addr and the 4th one alen are set to 0,
- * because here we only want to send out chip address. The register
- * address is wrapped in msg.
+ /* Here address len is set to -1 to not send any address at first.
+ * Otherwise i2c_init_transfer will send the chip address with write
+ * mode set. This is wrong if the 1st message is read.
*/
- ret = i2c_init_transfer(i2c_bus, msg->addr, 0, 0);
+ ret = i2c_init_transfer(i2c_bus, msg->addr, 0, -1);
if (ret < 0) {
debug("i2c_init_transfer error: %d\n", ret);
return ret;
}
+ read_mode = -1; /* So it's always different on the first message */
for (; nmsgs > 0; nmsgs--, msg++) {
- bool next_is_read = nmsgs > 1 && (msg[1].flags & I2C_M_RD);
- debug("i2c_xfer: chip=0x%x, len=0x%x\n", msg->addr, msg->len);
- if (msg->flags & I2C_M_RD)
- ret = i2c_read_data(i2c_bus, msg->addr, msg->buf,
- msg->len);
- else {
- ret = i2c_write_data(i2c_bus, msg->addr, msg->buf,
- msg->len);
- if (ret)
- break;
- if (next_is_read) {
- /* Reuse ret */
+ const int msg_is_read = !!(msg->flags & I2C_M_RD);
+
+ debug("i2c_xfer: chip=0x%x, len=0x%x, dir=%c\n", msg->addr,
+ msg->len, msg_is_read ? 'R' : 'W');
+
+ if (msg_is_read != read_mode) {
+ /* Send repeated start if not 1st message */
+ if (read_mode != -1) {
+ debug("i2c_xfer: [RSTART]\n");
ret = readb(base + (I2CR << reg_shift));
ret |= I2CR_RSTA;
writeb(ret, base + (I2CR << reg_shift));
-
- ret = tx_byte(i2c_bus, (msg->addr << 1) | 1);
- if (ret < 0) {
- i2c_imx_stop(i2c_bus);
- break;
- }
}
+ debug("i2c_xfer: [ADDR %02x | %c]\n", msg->addr,
+ msg_is_read ? 'R' : 'W');
+ ret = tx_byte(i2c_bus, (msg->addr << 1) | msg_is_read);
+ if (ret < 0) {
+ debug("i2c_xfer: [STOP]\n");
+ i2c_imx_stop(i2c_bus);
+ break;
+ }
+ read_mode = msg_is_read;
}
+
+ if (msg->flags & I2C_M_RD)
+ ret = i2c_read_data(i2c_bus, msg->addr, msg->buf,
+ msg->len, nmsgs == 1 ||
+ (msg->flags & I2C_M_STOP));
+ else
+ ret = i2c_write_data(i2c_bus, msg->addr, msg->buf,
+ msg->len);
+
+ if (ret < 0)
+ break;
}
if (ret)