Skip to content

Commit

Permalink
vf_i2c: update I2C controller logic
Browse files Browse the repository at this point in the history
Update the I2C controller logic to be more consistent with the
newer version of the controller reference manual.
This makes it work better on modern LS/LX platforms and avoids
unnecessary delays.  Also fixes a lock leak.

MFC after:	7 days
Tested by:	bz (LS1088a FDT), Pierre-Luc Drouin (Honeycomb, ACPI)
Differential Revision:	https://reviews.freebsd.org/D44021
  • Loading branch information
pldrouin authored and bsdjhb committed Aug 6, 2024
2 parents c13dd76 + 4484711 commit 4a45049
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 92 deletions.
148 changes: 59 additions & 89 deletions sys/dev/iicbus/controller/vybrid/vf_i2c.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@

/*
* Vybrid Family Inter-Integrated Circuit (I2C)
* Chapter 48, Vybrid Reference Manual, Rev. 5, 07/2013
* Originally based on Chapter 48, Vybrid Reference Manual, Rev. 5, 07/2013
* Currently based on Chapter 21, LX2160A Reference Manual, Rev. 1, 10/2021
*
* The current implementation is based on the original driver by Ruslan Bukin,
* later modified by Dawid Górecki, and split into FDT and ACPI drivers by Val
Expand Down Expand Up @@ -184,6 +185,7 @@ vf_i2c_attach_common(device_t dev)
mtx_unlock(&sc->mutex);

sc->iicbus = device_add_child(dev, "iicbus", -1);

if (sc->iicbus == NULL) {
device_printf(dev, "could not add iicbus child");
mtx_destroy(&sc->mutex);
Expand Down Expand Up @@ -233,24 +235,6 @@ i2c_detach(device_t dev)
return (0);
}

/* Wait for transfer interrupt flag */
static int
wait_for_iif(struct vf_i2c_softc *sc)
{
int retry;

retry = 1000;
while (retry --) {
if (READ1(sc, I2C_IBSR) & IBSR_IBIF) {
WRITE1(sc, I2C_IBSR, IBSR_IBIF);
return (IIC_NOERR);
}
DELAY(10);
}

return (IIC_ETIMEOUT);
}

/* Wait for free bus */
static int
wait_for_nibb(struct vf_i2c_softc *sc)
Expand All @@ -272,14 +256,24 @@ static int
wait_for_icf(struct vf_i2c_softc *sc)
{
int retry;
uint8_t ibsr;

vf_i2c_dbg(sc, "i2c wait for transfer complete + interrupt flag\n");

retry = 1000;
while (retry --) {
if (READ1(sc, I2C_IBSR) & IBSR_TCF) {
if (READ1(sc, I2C_IBSR) & IBSR_IBIF) {
WRITE1(sc, I2C_IBSR, IBSR_IBIF);
return (IIC_NOERR);
ibsr = READ1(sc, I2C_IBSR);

if (ibsr & IBSR_IBIF) {
WRITE1(sc, I2C_IBSR, IBSR_IBIF);

if (ibsr & IBSR_IBAL) {
WRITE1(sc, I2C_IBSR, IBSR_IBAL);
return (IIC_EBUSBSY);
}

if (ibsr & IBSR_TCF)
return (IIC_NOERR);
}
DELAY(10);
}
Expand Down Expand Up @@ -309,30 +303,24 @@ i2c_repeated_start(device_t dev, u_char slave, int timeout)

mtx_lock(&sc->mutex);

WRITE1(sc, I2C_IBAD, slave);

if ((READ1(sc, I2C_IBSR) & IBSR_IBB) == 0) {
vf_i2c_dbg(sc, "cant i2c repeat start: bus is no longer busy\n");
mtx_unlock(&sc->mutex);
return (IIC_EBUSERR);
}

/* Set repeated start condition */
DELAY(10);

reg = READ1(sc, I2C_IBCR);
reg |= (IBCR_RSTA | IBCR_IBIE);
WRITE1(sc, I2C_IBCR, reg);

DELAY(10);

/* Write target address - LSB is R/W bit */
WRITE1(sc, I2C_IBDR, slave);

error = wait_for_iif(sc);
error = wait_for_icf(sc);

if (!tx_acked(sc)) {
vf_i2c_dbg(sc,
"cant i2c start: missing ACK after slave addres\n");
mtx_unlock(&sc->mutex);
vf_i2c_dbg(sc, "cant i2c repeat start: missing ACK after slave address\n");
return (IIC_ENOACK);
}

Expand All @@ -357,27 +345,32 @@ i2c_start(device_t dev, u_char slave, int timeout)

mtx_lock(&sc->mutex);

WRITE1(sc, I2C_IBAD, slave);
error = wait_for_nibb(sc);

if (READ1(sc, I2C_IBSR) & IBSR_IBB) {
/* Reset controller if bus is still busy. */
if (error == IIC_ETIMEOUT) {
WRITE1(sc, I2C_IBCR, IBCR_MDIS);
DELAY(1000);
WRITE1(sc, I2C_IBCR, IBCR_NOACK);
error = wait_for_nibb(sc);
}

if (error != 0) {
mtx_unlock(&sc->mutex);
vf_i2c_dbg(sc, "cant i2c start: IIC_EBUSBSY\n");
return (IIC_EBUSERR);
vf_i2c_dbg(sc, "cant i2c start: %i\n", error);
return (error);
}

/* Set start condition */
reg = (IBCR_MSSL | IBCR_NOACK | IBCR_IBIE);
reg = (IBCR_MSSL | IBCR_NOACK | IBCR_IBIE | IBCR_TXRX);
WRITE1(sc, I2C_IBCR, reg);

DELAY(100);

reg |= (IBCR_TXRX);
WRITE1(sc, I2C_IBCR, reg);
WRITE1(sc, I2C_IBSR, IBSR_IBIF);

/* Write target address - LSB is R/W bit */
WRITE1(sc, I2C_IBDR, slave);

error = wait_for_iif(sc);
error = wait_for_icf(sc);
if (error != 0) {
mtx_unlock(&sc->mutex);
vf_i2c_dbg(sc, "cant i2c start: iif error\n");
Expand All @@ -386,8 +379,7 @@ i2c_start(device_t dev, u_char slave, int timeout)
mtx_unlock(&sc->mutex);

if (!tx_acked(sc)) {
vf_i2c_dbg(sc,
"cant i2c start: missing QACK after slave addres\n");
vf_i2c_dbg(sc, "cant i2c start: missing ACK after slave address\n");
return (IIC_ENOACK);
}

Expand All @@ -405,16 +397,9 @@ i2c_stop(device_t dev)

mtx_lock(&sc->mutex);

WRITE1(sc, I2C_IBCR, IBCR_NOACK | IBCR_IBIE);

DELAY(100);
if ((READ1(sc, I2C_IBCR) & IBCR_MSSL) != 0)
WRITE1(sc, I2C_IBCR, IBCR_NOACK | IBCR_IBIE);

/* Reset controller if bus still busy after STOP */
if (wait_for_nibb(sc) == IIC_ETIMEOUT) {
WRITE1(sc, I2C_IBCR, IBCR_MDIS);
DELAY(1000);
WRITE1(sc, I2C_IBCR, IBCR_NOACK);
}
mtx_unlock(&sc->mutex);

return (IIC_NOERR);
Expand Down Expand Up @@ -469,27 +454,14 @@ i2c_reset(device_t dev, u_char speed, u_char addr, u_char *oldadr)
div_reg = i2c_get_div_val(dev);
vf_i2c_dbg(sc, "i2c reset\n");

switch (speed) {
case IIC_FAST:
case IIC_SLOW:
case IIC_UNKNOWN:
case IIC_FASTEST:
default:
break;
}

mtx_lock(&sc->mutex);
WRITE1(sc, I2C_IBCR, IBCR_MDIS);

DELAY(1000);

if(div_reg != DIV_REG_UNSET)
WRITE1(sc, I2C_IBFD, div_reg);

WRITE1(sc, I2C_IBCR, 0x0); /* Enable i2c */

DELAY(1000);

mtx_unlock(&sc->mutex);

return (IIC_NOERR);
Expand All @@ -511,36 +483,34 @@ i2c_read(device_t dev, char *buf, int len, int *read, int last, int delay)

if (len) {
if (len == 1)
WRITE1(sc, I2C_IBCR, IBCR_IBIE | IBCR_MSSL | \
IBCR_NOACK);
WRITE1(sc, I2C_IBCR, IBCR_IBIE | IBCR_MSSL | IBCR_NOACK);
else
WRITE1(sc, I2C_IBCR, IBCR_IBIE | IBCR_MSSL);

/* dummy read */
READ1(sc, I2C_IBDR);
DELAY(1000);
}

while (*read < len) {
error = wait_for_icf(sc);
if (error != 0) {
mtx_unlock(&sc->mutex);
return (error);
}
while (*read < len) {
error = wait_for_icf(sc);
if (error != 0) {
mtx_unlock(&sc->mutex);
return (error);
}

if ((*read == len - 2) && last) {
/* NO ACK on last byte */
WRITE1(sc, I2C_IBCR, IBCR_IBIE | IBCR_MSSL | \
IBCR_NOACK);
}
if (last) {
if (*read == len - 2) {
/* NO ACK on last byte */
WRITE1(sc, I2C_IBCR, IBCR_IBIE | IBCR_MSSL | IBCR_NOACK);

if ((*read == len - 1) && last) {
/* Transfer done, remove master bit */
WRITE1(sc, I2C_IBCR, IBCR_IBIE | IBCR_NOACK);
}
} else if (*read == len - 1) {
/* Transfer done, remove master bit */
WRITE1(sc, I2C_IBCR, IBCR_IBIE | IBCR_NOACK);
}
}

*buf++ = READ1(sc, I2C_IBDR);
(*read)++;
*buf++ = READ1(sc, I2C_IBDR);
(*read)++;
}
}
mtx_unlock(&sc->mutex);

Expand All @@ -563,7 +533,7 @@ i2c_write(device_t dev, const char *buf, int len, int *sent, int timeout)
while (*sent < len) {
WRITE1(sc, I2C_IBDR, *buf++);

error = wait_for_iif(sc);
error = wait_for_icf(sc);
if (error != 0) {
mtx_unlock(&sc->mutex);
return (error);
Expand Down
3 changes: 2 additions & 1 deletion sys/dev/iicbus/controller/vybrid/vf_i2c.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@

/*
* Vybrid Family Inter-Integrated Circuit (I2C)
* Chapter 48, Vybrid Reference Manual, Rev. 5, 07/2013
* Originally based on Chapter 48, Vybrid Reference Manual, Rev. 5, 07/2013
* Currently based on Chapter 21, LX2160A Reference Manual, Rev. 1, 10/2021
*
* The current implementation is based on the original driver by Ruslan Bukin,
* later modified by Dawid Górecki, and split into FDT and ACPI drivers by Val
Expand Down
3 changes: 2 additions & 1 deletion sys/dev/iicbus/controller/vybrid/vf_i2c_acpi.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@

/*
* Vybrid Family Inter-Integrated Circuit (I2C)
* Chapter 48, Vybrid Reference Manual, Rev. 5, 07/2013
* Originally based on Chapter 48, Vybrid Reference Manual, Rev. 5, 07/2013
* Currently based on Chapter 21, LX2160A Reference Manual, Rev. 1, 10/2021
*
* The current implementation is based on the original driver by Ruslan Bukin,
* later modified by Dawid Górecki, and split into FDT and ACPI drivers by Val
Expand Down
3 changes: 2 additions & 1 deletion sys/dev/iicbus/controller/vybrid/vf_i2c_fdt.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@

/*
* Vybrid Family Inter-Integrated Circuit (I2C)
* Chapter 48, Vybrid Reference Manual, Rev. 5, 07/2013
* Originally based on Chapter 48, Vybrid Reference Manual, Rev. 5, 07/2013
* Currently based on Chapter 21, LX2160A Reference Manual, Rev. 1, 10/2021
*
* The current implementation is based on the original driver by Ruslan Bukin,
* later modified by Dawid Górecki, and split into FDT and ACPI drivers by Val
Expand Down

0 comments on commit 4a45049

Please sign in to comment.