Fix nxprtc(4) on systems that support i2c repeat-start correctly.

An obscure footnote in the datasheets for the PCx2127, PCx2129, and
PCF8523 rtc chips states that the chips do not support i2c repeat-start
operations.  When the driver was originally written and tested, the i2c
bus on that system also didn't support repeat-start and just quietly
turned repeat-start operations into a stop-then-start, making it appear
that the nxprtc driver was working properly.

The repeat-start situation only comes up on reads, so instead of using
the standard iicdev_readfrom(), use a local nxprtc_readfrom(), which is
just a cut-and-pasted copy of iicdev_readfrom(), modified to send two
separate start-data-stop sequences instead of using repeat-start.
This commit is contained in:
ian 2019-07-15 21:40:58 +00:00
parent 64ee41ff55
commit 264fbd07fe

View File

@ -235,11 +235,44 @@ static nxprtc_compat_data compat_data[] = {
{NULL, TYPE_NONE},
};
static int
nxprtc_readfrom(device_t slavedev, uint8_t regaddr, void *buffer,
uint16_t buflen, int waithow)
{
struct iic_msg msg;
int err;
uint8_t slaveaddr;
/*
* Two transfers back to back with a stop and start between them; first we
* write the address-within-device, then we read from the device. This
* is used instead of the standard iicdev_readfrom() because some of the
* chips we service don't support i2c repeat-start operations (grrrrr)
* so we do two completely separate transfers with a full stop between.
*/
slaveaddr = iicbus_get_addr(slavedev);
msg.slave = slaveaddr;
msg.flags = IIC_M_WR;
msg.len = 1;
msg.buf = &regaddr;
if ((err = iicbus_transfer_excl(slavedev, &msg, 1, waithow)) != 0)
return (err);
msg.slave = slaveaddr;
msg.flags = IIC_M_RD;
msg.len = buflen;
msg.buf = buffer;
return (iicbus_transfer_excl(slavedev, &msg, 1, waithow));
}
static int
read_reg(struct nxprtc_softc *sc, uint8_t reg, uint8_t *val)
{
return (iicdev_readfrom(sc->dev, reg, val, sizeof(*val), WAITFLAGS));
return (nxprtc_readfrom(sc->dev, reg, val, sizeof(*val), WAITFLAGS));
}
static int
@ -272,7 +305,7 @@ read_timeregs(struct nxprtc_softc *sc, struct time_regs *tregs, uint8_t *tmr)
if (tmr1 != tmr2)
continue;
}
if ((err = iicdev_readfrom(sc->dev, sc->secaddr, tregs,
if ((err = nxprtc_readfrom(sc->dev, sc->secaddr, tregs,
sizeof(*tregs), WAITFLAGS)) != 0)
break;
} while (sc->use_timer && tregs->sec != sec);