Re: [PATCH v1 3/3] i2c: imx: prevent rescheduling in non dma mode

From: Frank Li
Date: Mon Jul 15 2024 - 13:03:49 EST


On Mon, Jul 15, 2024 at 05:17:53PM +0200, Stefan Eichenberger wrote:
> From: Stefan Eichenberger <stefan.eichenberger@xxxxxxxxxxx>
>
> We are experiencing a problem with the i.MX I2C controller when
> communicating with SMBus devices. We are seeing devices time-out because
> the time between sending/receiving two bytes is too long, and the SMBus
> device returns to the idle state. This happens because the i.MX I2C
> controller sends and receives byte by byte. When a byte is sent or
> received, we get an interrupt and can send or receive the next byte.
>
> The current implementation sends a byte and then waits for an event
> generated by the interrupt subroutine. After the event is received, the
> next byte is sent and we wait again. This waiting allows the scheduler
> to reschedule other tasks, with the disadvantage that we may not send
> the next byte for a long time because the send task is not immediately
> scheduled. For example, if the rescheduling takes more than 25ms, this
> can cause SMBus devices to timeout and communication to fail.
>
> This patch changes the behavior so that we do not reschedule the
> send/receive task, but instead send or receive the next byte in the
> interrupt subroutine. This prevents rescheduling and drastically reduces
> the time between sending/receiving bytes. The cost in the interrupt
> subroutine is relatively small, we check what state we are in and then
> send/receive the next byte. Before we had to call wake_up, which is even
> less expensive. However, we also had to do some scheduling, which
> increased the overall cost compared to the new solution. The wake_up
> function to wake up the send/receive task is now only called when an
> error occurs or when the transfer is complete.
>
> Signed-off-by: Stefan Eichenberger <stefan.eichenberger@xxxxxxxxxxx>
> ---
> drivers/i2c/busses/i2c-imx.c | 257 ++++++++++++++++++++++++++++++++---
> 1 file changed, 235 insertions(+), 22 deletions(-)
>
> diff --git a/drivers/i2c/busses/i2c-imx.c b/drivers/i2c/busses/i2c-imx.c
> index e242166cb6638..ac21c2001596e 100644
> --- a/drivers/i2c/busses/i2c-imx.c
> +++ b/drivers/i2c/busses/i2c-imx.c
> @@ -197,6 +197,17 @@ struct imx_i2c_dma {
> enum dma_data_direction dma_data_dir;
> };
>
> +enum imx_i2c_state {
> + IMX_I2C_STATE_DONE,
> + IMX_I2C_STATE_FAILED,
> + IMX_I2C_STATE_WRITE,
> + IMX_I2C_STATE_DMA,
> + IMX_I2C_STATE_READ,
> + IMX_I2C_STATE_READ_CONTINUE,
> + IMX_I2C_STATE_READ_BLOCK_DATA,
> + IMX_I2C_STATE_READ_BLOCK_DATA_LEN,
> +};
> +
> struct imx_i2c_struct {
> struct i2c_adapter adapter;
> struct clk *clk;
> @@ -216,6 +227,12 @@ struct imx_i2c_struct {
> struct i2c_client *slave;
> enum i2c_slave_event last_slave_event;
>
> + struct i2c_msg *msg;
> + unsigned int msg_buf_idx;
> + int isr_result;
> + bool is_lastmsg;
> + enum imx_i2c_state state;
> +
> bool multi_master;
>
> /* For checking slave events. */
> @@ -908,11 +925,150 @@ static int i2c_imx_unreg_slave(struct i2c_client *client)
> return ret;
> }
>
> +static inline int i2c_imx_isr_acked(struct imx_i2c_struct *i2c_imx)
> +{
> + i2c_imx->isr_result = 0;
> +
> + if (imx_i2c_read_reg(i2c_imx, IMX_I2C_I2SR) & I2SR_RXAK) {
> + i2c_imx->state = IMX_I2C_STATE_FAILED;
> + i2c_imx->isr_result = -ENXIO;
> + wake_up(&i2c_imx->queue);
> + }
> +
> + return i2c_imx->isr_result;
> +}
> +
> +static inline int i2c_imx_isr_write(struct imx_i2c_struct *i2c_imx)
> +{
> + int result;
> +
> + result = i2c_imx_isr_acked(i2c_imx);
> + if (result)
> + return result;
> +
> + if (i2c_imx->msg->len == i2c_imx->msg_buf_idx)
> + return 0;
> +
> + imx_i2c_write_reg(i2c_imx->msg->buf[i2c_imx->msg_buf_idx++], i2c_imx, IMX_I2C_I2DR);
> +
> + return 1;
> +}
> +
> +static inline int i2c_imx_isr_read(struct imx_i2c_struct *i2c_imx)
> +{
> + int result;
> + unsigned int temp;
> +
> + result = i2c_imx_isr_acked(i2c_imx);
> + if (result)
> + return result;
> +
> + /* setup bus to read data */
> + temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR);
> + temp &= ~I2CR_MTX;
> + if (i2c_imx->msg->len - 1)
> + temp &= ~I2CR_TXAK;
> +
> + imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR);
> + imx_i2c_read_reg(i2c_imx, IMX_I2C_I2DR); /* dummy read */
> +
> + return 0;
> +}
> +
> +static inline void i2c_imx_isr_read_continue(struct imx_i2c_struct *i2c_imx)
> +{
> + unsigned int temp;
> +
> + if ((i2c_imx->msg->len - 1) == i2c_imx->msg_buf_idx) {
> + if (i2c_imx->is_lastmsg) {
> + /*
> + * It must generate STOP before read I2DR to prevent
> + * controller from generating another clock cycle
> + */
> + temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR);
> + if (!(temp & I2CR_MSTA))
> + i2c_imx->stopped = 1;
> + temp &= ~(I2CR_MSTA | I2CR_MTX);
> + imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR);
> + } else {
> + /*
> + * For i2c master receiver repeat restart operation like:
> + * read -> repeat MSTA -> read/write
> + * The controller must set MTX before read the last byte in
> + * the first read operation, otherwise the first read cost
> + * one extra clock cycle.
> + */
> + temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR);
> + temp |= I2CR_MTX;
> + imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR);
> + }
> + } else if (i2c_imx->msg_buf_idx == (i2c_imx->msg->len - 2)) {
> + temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR);
> + temp |= I2CR_TXAK;
> + imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR);
> + }
> +
> + i2c_imx->msg->buf[i2c_imx->msg_buf_idx++] = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2DR);

Why not use loop to read all data from FIFO? I think read_reg use readb(),
suggest change to readb_relaxed(). The similar case for writeb. dma_engine
will use writel() at least once when start DMA. it should be enough for
memory barrier.

Because it move to irq handle, writex__relaxed() will help reduce some
register access time.

> +}
> +
> +static inline void i2c_imx_isr_read_block_data_len(struct imx_i2c_struct *i2c_imx)
> +{
> + u8 len = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2DR);
> +
> + if ((len == 0) || (len > I2C_SMBUS_BLOCK_MAX)) {
> + i2c_imx->isr_result = -EPROTO;
> + i2c_imx->state = IMX_I2C_STATE_FAILED;
> + wake_up(&i2c_imx->queue);
> + }
> + i2c_imx->msg->len += len;
> +}
> +
> static irqreturn_t i2c_imx_master_isr(struct imx_i2c_struct *i2c_imx, unsigned int status)
> {
> - /* save status register */
> - i2c_imx->i2csr = status;
> - wake_up(&i2c_imx->queue);
> + switch (i2c_imx->state) {
> + case IMX_I2C_STATE_DMA:
> + i2c_imx->i2csr = status;
> + wake_up(&i2c_imx->queue);
> + break;
> +
> + case IMX_I2C_STATE_READ:
> + if (i2c_imx_isr_read(i2c_imx))
> + break;
> + i2c_imx->state = IMX_I2C_STATE_READ_CONTINUE;
> + break;
> +
> + case IMX_I2C_STATE_READ_CONTINUE:
> + i2c_imx_isr_read_continue(i2c_imx);
> + if (i2c_imx->msg_buf_idx == i2c_imx->msg->len) {
> + i2c_imx->state = IMX_I2C_STATE_DONE;
> + wake_up(&i2c_imx->queue);
> + }
> + break;
> +
> + case IMX_I2C_STATE_READ_BLOCK_DATA:
> + if (i2c_imx_isr_read(i2c_imx))
> + break;
> + i2c_imx->state = IMX_I2C_STATE_READ_BLOCK_DATA_LEN;
> + break;
> +
> + case IMX_I2C_STATE_READ_BLOCK_DATA_LEN:
> + i2c_imx_isr_read_block_data_len(i2c_imx);
> + i2c_imx->state = IMX_I2C_STATE_READ_CONTINUE;
> + break;
> +
> + case IMX_I2C_STATE_WRITE:
> + if (i2c_imx_isr_write(i2c_imx))
> + break;
> + i2c_imx->state = IMX_I2C_STATE_DONE;
> + wake_up(&i2c_imx->queue);
> + break;
> +
> + default:
> + i2c_imx->i2csr = status;
> + i2c_imx->state = IMX_I2C_STATE_FAILED;
> + i2c_imx->isr_result = -EINVAL;
> + wake_up(&i2c_imx->queue);
> + }
>
> return IRQ_HANDLED;
> }
> @@ -959,6 +1115,8 @@ static int i2c_imx_dma_write(struct imx_i2c_struct *i2c_imx,
> struct imx_i2c_dma *dma = i2c_imx->dma;
> struct device *dev = &i2c_imx->adapter.dev;
>
> + i2c_imx->state = IMX_I2C_STATE_DMA;
> +
> dma->chan_using = dma->chan_tx;
> dma->dma_transfer_dir = DMA_MEM_TO_DEV;
> dma->dma_data_dir = DMA_TO_DEVICE;
> @@ -1012,15 +1170,14 @@ static int i2c_imx_dma_write(struct imx_i2c_struct *i2c_imx,
> }
>
> static int i2c_imx_start_read(struct imx_i2c_struct *i2c_imx,
> - struct i2c_msg *msgs, bool atomic,
> - bool use_dma)
> + struct i2c_msg *msgs, bool use_dma)
> {
> int result;
> unsigned int temp = 0;
>
> /* write slave address */
> imx_i2c_write_reg(i2c_8bit_addr_from_msg(msgs), i2c_imx, IMX_I2C_I2DR);
> - result = i2c_imx_trx_complete(i2c_imx, atomic);
> + result = i2c_imx_trx_complete(i2c_imx, !use_dma);
> if (result)
> return result;
> result = i2c_imx_acked(i2c_imx);
> @@ -1058,7 +1215,9 @@ static int i2c_imx_dma_read(struct imx_i2c_struct *i2c_imx,
> struct imx_i2c_dma *dma = i2c_imx->dma;
> struct device *dev = &i2c_imx->adapter.dev;
>
> - result = i2c_imx_start_read(i2c_imx, msgs, false, true);
> + i2c_imx->state = IMX_I2C_STATE_DMA;
> +
> + result = i2c_imx_start_read(i2c_imx, msgs, true);
> if (result)
> return result;
>
> @@ -1139,8 +1298,8 @@ static int i2c_imx_dma_read(struct imx_i2c_struct *i2c_imx,
> return 0;
> }
>
> -static int i2c_imx_write(struct imx_i2c_struct *i2c_imx, struct i2c_msg *msgs,
> - bool atomic)
> +static int i2c_imx_atomic_write(struct imx_i2c_struct *i2c_imx,
> + struct i2c_msg *msgs)
> {
> int i, result;
>
> @@ -1149,7 +1308,7 @@ static int i2c_imx_write(struct imx_i2c_struct *i2c_imx, struct i2c_msg *msgs,
>
> /* write slave address */
> imx_i2c_write_reg(i2c_8bit_addr_from_msg(msgs), i2c_imx, IMX_I2C_I2DR);
> - result = i2c_imx_trx_complete(i2c_imx, atomic);
> + result = i2c_imx_trx_complete(i2c_imx, true);
> if (result)
> return result;
> result = i2c_imx_acked(i2c_imx);
> @@ -1163,7 +1322,7 @@ static int i2c_imx_write(struct imx_i2c_struct *i2c_imx, struct i2c_msg *msgs,
> "<%s> write byte: B%d=0x%X\n",
> __func__, i, msgs->buf[i]);
> imx_i2c_write_reg(msgs->buf[i], i2c_imx, IMX_I2C_I2DR);
> - result = i2c_imx_trx_complete(i2c_imx, atomic);
> + result = i2c_imx_trx_complete(i2c_imx, true);
> if (result)
> return result;
> result = i2c_imx_acked(i2c_imx);
> @@ -1173,19 +1332,40 @@ static int i2c_imx_write(struct imx_i2c_struct *i2c_imx, struct i2c_msg *msgs,
> return 0;
> }
>
> -static int i2c_imx_atomic_write(struct imx_i2c_struct *i2c_imx, struct i2c_msg *msgs)
> +static int i2c_imx_write(struct imx_i2c_struct *i2c_imx, struct i2c_msg *msgs)
> {
> - return i2c_imx_write(i2c_imx, msgs, true);
> + dev_dbg(&i2c_imx->adapter.dev, "<%s> write slave address: addr=0x%x\n",
> + __func__, i2c_8bit_addr_from_msg(msgs));
> +
> + i2c_imx->state = IMX_I2C_STATE_WRITE;
> + i2c_imx->msg = msgs;
> + i2c_imx->msg_buf_idx = 0;
> + /* write slave address and start transmission */
> + imx_i2c_write_reg(i2c_8bit_addr_from_msg(msgs), i2c_imx, IMX_I2C_I2DR);
> + wait_event_timeout(i2c_imx->queue,
> + i2c_imx->state == IMX_I2C_STATE_DONE ||
> + i2c_imx->state == IMX_I2C_STATE_FAILED,
> + (msgs->len + 1)*HZ / 10);
> + if (i2c_imx->state == IMX_I2C_STATE_FAILED) {
> + dev_err(&i2c_imx->adapter.dev, "<%s> write failed with %d\n",
> + __func__, i2c_imx->isr_result);
> + return i2c_imx->isr_result;
> + }
> + if (i2c_imx->state != IMX_I2C_STATE_DONE) {
> + dev_err(&i2c_imx->adapter.dev, "<%s> write timedout\n", __func__);
> + return -ETIMEDOUT;
> + }
> + return 0;
> }
>
> -static int i2c_imx_read(struct imx_i2c_struct *i2c_imx, struct i2c_msg *msgs,
> - bool is_lastmsg, bool atomic)
> +static int i2c_imx_atomic_read(struct imx_i2c_struct *i2c_imx,
> + struct i2c_msg *msgs, bool is_lastmsg)
> {
> int i, result;
> unsigned int temp;
> int block_data = msgs->flags & I2C_M_RECV_LEN;
>
> - result = i2c_imx_start_read(i2c_imx, msgs, atomic, false);
> + result = i2c_imx_start_read(i2c_imx, msgs, false);
> if (result)
> return result;
>
> @@ -1195,7 +1375,7 @@ static int i2c_imx_read(struct imx_i2c_struct *i2c_imx, struct i2c_msg *msgs,
> for (i = 0; i < msgs->len; i++) {
> u8 len = 0;
>
> - result = i2c_imx_trx_complete(i2c_imx, atomic);
> + result = i2c_imx_trx_complete(i2c_imx, true);
> if (result)
> return result;
> /*
> @@ -1226,7 +1406,7 @@ static int i2c_imx_read(struct imx_i2c_struct *i2c_imx, struct i2c_msg *msgs,
> temp &= ~(I2CR_MSTA | I2CR_MTX);
> imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR);
> if (!i2c_imx->stopped)
> - i2c_imx_bus_busy(i2c_imx, 0, atomic);
> + i2c_imx_bus_busy(i2c_imx, 0, true);
> } else {
> /*
> * For i2c master receiver repeat restart operation like:
> @@ -1257,10 +1437,43 @@ static int i2c_imx_read(struct imx_i2c_struct *i2c_imx, struct i2c_msg *msgs,
> return 0;
> }
>
> -static int i2c_imx_atomic_read(struct imx_i2c_struct *i2c_imx, struct i2c_msg *msgs,
> +static int i2c_imx_read(struct imx_i2c_struct *i2c_imx, struct i2c_msg *msgs,
> bool is_lastmsg)
> {
> - return i2c_imx_read(i2c_imx, msgs, is_lastmsg, true);
> + int block_data = msgs->flags & I2C_M_RECV_LEN;
> +
> + dev_dbg(&i2c_imx->adapter.dev,
> + "<%s> write slave address: addr=0x%x\n",
> + __func__, i2c_8bit_addr_from_msg(msgs));
> +
> + i2c_imx->is_lastmsg = is_lastmsg;
> +
> + if (block_data)
> + i2c_imx->state = IMX_I2C_STATE_READ_BLOCK_DATA;
> + else
> + i2c_imx->state = IMX_I2C_STATE_READ;
> + i2c_imx->msg = msgs;
> + i2c_imx->msg_buf_idx = 0;
> +
> + /* write slave address */
> + imx_i2c_write_reg(i2c_8bit_addr_from_msg(msgs), i2c_imx, IMX_I2C_I2DR);
> + wait_event_timeout(i2c_imx->queue,
> + i2c_imx->state == IMX_I2C_STATE_DONE ||
> + i2c_imx->state == IMX_I2C_STATE_FAILED,
> + (msgs->len + 1)*HZ / 10);
> + if (i2c_imx->state == IMX_I2C_STATE_FAILED) {
> + dev_err(&i2c_imx->adapter.dev, "<%s> write failed with %d\n",
> + __func__, i2c_imx->isr_result);
> + return i2c_imx->isr_result;
> + }
> + if (i2c_imx->state != IMX_I2C_STATE_DONE) {
> + dev_err(&i2c_imx->adapter.dev, "<%s> write timedout\n", __func__);
> + return -ETIMEDOUT;
> + }
> + if (!i2c_imx->stopped)
> + return i2c_imx_bus_busy(i2c_imx, 0, false);
> +
> + return 0;
> }
>
> static int i2c_imx_xfer_common(struct i2c_adapter *adapter,
> @@ -1334,14 +1547,14 @@ static int i2c_imx_xfer_common(struct i2c_adapter *adapter,
> else if (use_dma && !block_data)
> result = i2c_imx_dma_read(i2c_imx, &msgs[i], is_lastmsg);
> else
> - result = i2c_imx_read(i2c_imx, &msgs[i], is_lastmsg, false);
> + result = i2c_imx_read(i2c_imx, &msgs[i], is_lastmsg);
> } else {
> if (atomic)
> result = i2c_imx_atomic_write(i2c_imx, &msgs[i]);
> else if (use_dma)
> result = i2c_imx_dma_write(i2c_imx, &msgs[i]);
> else
> - result = i2c_imx_write(i2c_imx, &msgs[i], false);
> + result = i2c_imx_write(i2c_imx, &msgs[i]);
> }
> if (result)
> goto fail0;
> --
> 2.43.0
>