Message ID | 20230811124624.12792-2-yann@sionneau.net |
---|---|
State | New |
Headers | show |
Series | [1/2] i2c: designware: fix __i2c_dw_disable in case master is holding SCL low | expand |
On Fri, Aug 11, 2023 at 02:46:24PM +0200, Yann Sionneau wrote: > From: Yann Sionneau <ysionneau@kalray.eu> > > Context: > It's not clear whether Linux SMBus stack supports receiving 0 > as byte count for SMBus read data block. > > Linux supports SMBus v2.0 spec, which says "The byte count may not be 0." > Which does not seem very clear to me, as a non-native speaker. > (Note that v3.0 of the spec says "The byte count may be 0.") > > Some drivers explicitly return -EPROTO in case of byte count 0. > > The issue: > Regardless of whether Linux supports byte count of 0, if this happens > the i2c-designware driver goes into an unrecoverable state holding > SCL low if the IP is synthesized with the IC_EMPTYFIFO_HOLD_MASTER_EN > parameter. > > The fix proposed by this patch: > Abort the transfer by sending a STOP condition on the bus. > > Another approach would be to ignore the issue and let the driver > timeout and disable the IP. The IP disabling is fixed by the previous > patch in this patch series. > The current patch just makes the recovery faster since Abort is sent > directly without waiting for the timeout to happen. With this patch, > disabling the IP is not necessary anymore. ... > + if ((tmp <= I2C_SMBUS_BLOCK_MAX) && (tmp != 0)) if (tmp && tmp <= I2C_SMBUS_BLOCK_MAX) > + len = i2c_dw_recv_len(dev, tmp); > + else > + i2c_dw_abort(dev);
Hi, Le 11/08/2023 à 16:01, Andy Shevchenko a écrit : > On Fri, Aug 11, 2023 at 02:46:24PM +0200, Yann Sionneau wrote: >> From: Yann Sionneau <ysionneau@kalray.eu> >> >> Context: >> It's not clear whether Linux SMBus stack supports receiving 0 >> as byte count for SMBus read data block. >> >> Linux supports SMBus v2.0 spec, which says "The byte count may not be 0." >> Which does not seem very clear to me, as a non-native speaker. >> (Note that v3.0 of the spec says "The byte count may be 0.") >> >> Some drivers explicitly return -EPROTO in case of byte count 0. >> >> The issue: >> Regardless of whether Linux supports byte count of 0, if this happens >> the i2c-designware driver goes into an unrecoverable state holding >> SCL low if the IP is synthesized with the IC_EMPTYFIFO_HOLD_MASTER_EN >> parameter. >> >> The fix proposed by this patch: >> Abort the transfer by sending a STOP condition on the bus. >> >> Another approach would be to ignore the issue and let the driver >> timeout and disable the IP. The IP disabling is fixed by the previous >> patch in this patch series. >> The current patch just makes the recovery faster since Abort is sent >> directly without waiting for the timeout to happen. With this patch, >> disabling the IP is not necessary anymore. > ... > >> + if ((tmp <= I2C_SMBUS_BLOCK_MAX) && (tmp != 0)) > if (tmp && tmp <= I2C_SMBUS_BLOCK_MAX) I find the tmp != 0 "more obvious", I am more used to "just tmp" when it's a pointer or a boolean, but maybe it's just me! I'll fix that in the V2 :) > >> + len = i2c_dw_recv_len(dev, tmp); >> + else >> + i2c_dw_abort(dev); Thanks!
diff --git a/drivers/i2c/busses/i2c-designware-master.c b/drivers/i2c/busses/i2c-designware-master.c index b55c19b2515a..fa5301566bb1 100644 --- a/drivers/i2c/busses/i2c-designware-master.c +++ b/drivers/i2c/busses/i2c-designware-master.c @@ -499,6 +499,15 @@ i2c_dw_recv_len(struct dw_i2c_dev *dev, u8 len) return len; } +static void +i2c_dw_abort(struct dw_i2c_dev *dev) +{ + u32 enable; + + regmap_read(dev->map, DW_IC_ENABLE, &enable); + regmap_write(dev->map, DW_IC_ENABLE, enable | DW_IC_ENABLE_ABORT); +} + static void i2c_dw_read(struct dw_i2c_dev *dev) { @@ -526,11 +535,16 @@ i2c_dw_read(struct dw_i2c_dev *dev) u32 flags = msgs[dev->msg_read_idx].flags; regmap_read(dev->map, DW_IC_DATA_CMD, &tmp); + tmp &= DW_IC_DATA_CMD_DAT; + /* Ensure length byte is a valid value */ - if (flags & I2C_M_RECV_LEN && - (tmp & DW_IC_DATA_CMD_DAT) <= I2C_SMBUS_BLOCK_MAX && (tmp & DW_IC_DATA_CMD_DAT) > 0) { - len = i2c_dw_recv_len(dev, tmp); + if (flags & I2C_M_RECV_LEN) { + if ((tmp <= I2C_SMBUS_BLOCK_MAX) && (tmp != 0)) + len = i2c_dw_recv_len(dev, tmp); + else + i2c_dw_abort(dev); } + *buf++ = tmp; dev->rx_outstanding--; }