diff --git a/drivers/i2c/busses/i2c-designware-master.c b/drivers/i2c/busses/i2c-designware-master.c index 8ca254cbb2f8..a466511dadd4 100644 --- a/drivers/i2c/busses/i2c-designware-master.c +++ b/drivers/i2c/busses/i2c-designware-master.c @@ -377,7 +377,6 @@ i2c_dw_xfer_msg(struct dw_i2c_dev *dev) struct i2c_msg *msgs = dev->msgs; u32 intr_mask; int tx_limit, rx_limit; - u32 addr = msgs[dev->msg_write_idx].addr; u32 buf_len = dev->tx_buf_len; u8 *buf = dev->tx_buf; bool need_restart = false; @@ -388,18 +387,6 @@ i2c_dw_xfer_msg(struct dw_i2c_dev *dev) for (; dev->msg_write_idx < dev->msgs_num; dev->msg_write_idx++) { u32 flags = msgs[dev->msg_write_idx].flags; - /* - * If target address has changed, we need to - * reprogram the target address in the I2C - * adapter when we are done with this transfer. - */ - if (msgs[dev->msg_write_idx].addr != addr) { - dev_err(dev->dev, - "%s: invalid target address\n", __func__); - dev->msg_err = -EINVAL; - break; - } - if (!(dev->status & STATUS_WRITE_IN_PROGRESS)) { /* new i2c_msg */ buf = msgs[dev->msg_write_idx].buf; @@ -746,17 +733,15 @@ static int i2c_dw_wait_transfer(struct dw_i2c_dev *dev) } /* - * Prepare controller for a transaction and call i2c_dw_xfer_msg. + * Prepare controller for a transaction, start the transfer of the @msgs + * and wait for completion, either a STOP or a error. + * Return: 0 or a negative error code. */ static int -i2c_dw_xfer_common(struct dw_i2c_dev *dev, struct i2c_msg msgs[], int num) +__i2c_dw_xfer_one_part(struct dw_i2c_dev *dev, struct i2c_msg *msgs, size_t num) { int ret; - dev_dbg(dev->dev, "%s: msgs: %d\n", __func__, num); - - pm_runtime_get_sync(dev->dev); - reinit_completion(&dev->cmd_complete); dev->msgs = msgs; dev->msgs_num = num; @@ -768,13 +753,9 @@ i2c_dw_xfer_common(struct dw_i2c_dev *dev, struct i2c_msg msgs[], int num) dev->abort_source = 0; dev->rx_outstanding = 0; - ret = i2c_dw_acquire_lock(dev); - if (ret) - goto done_nolock; - ret = i2c_dw_wait_bus_not_busy(dev); if (ret < 0) - goto done; + return ret; /* Start the transfers */ i2c_dw_xfer_init(dev); @@ -786,7 +767,7 @@ i2c_dw_xfer_common(struct dw_i2c_dev *dev, struct i2c_msg msgs[], int num) /* i2c_dw_init() implicitly disables the adapter */ i2c_recover_bus(&dev->adapter); i2c_dw_init(dev); - goto done; + return ret; } /* @@ -809,28 +790,95 @@ i2c_dw_xfer_common(struct dw_i2c_dev *dev, struct i2c_msg msgs[], int num) */ __i2c_dw_disable_nowait(dev); - if (dev->msg_err) { - ret = dev->msg_err; - goto done; - } + if (dev->msg_err) + return dev->msg_err; /* No error */ - if (likely(!dev->cmd_err && !dev->status)) { - ret = num; - goto done; - } + if (likely(!dev->cmd_err && !dev->status)) + return 0; /* We have an error */ - if (dev->cmd_err == DW_IC_ERR_TX_ABRT) { - ret = i2c_dw_handle_tx_abort(dev); - goto done; - } + if (dev->cmd_err == DW_IC_ERR_TX_ABRT) + return i2c_dw_handle_tx_abort(dev); if (dev->status) dev_err(dev->dev, "transfer terminated early - interrupt latency too high?\n"); - ret = -EIO; + return -EIO; +} + +/* + * Verify that the message at index @idx can be processed as part + * of a single transaction. The @msgs array contains the messages + * of the transaction. The message is checked against its predecessor + * to ensure that it respects the limitation of the controller. + * Return: true if the message can be processed, false otherwise. + */ +static bool +i2c_dw_msg_is_valid(struct dw_i2c_dev *dev, const struct i2c_msg *msgs, size_t idx) +{ + /* + * The first message of a transaction is valid, + * no constraints from a previous message. + */ + if (!idx) + return true; + + /* + * We cannot change the target address during a transaction, so make + * sure the address is identical to the one of the previous message. + */ + if (msgs[idx - 1].addr != msgs[idx].addr) { + dev_err(dev->dev, "invalid target address\n"); + return false; + } + + return true; +} + +static int +i2c_dw_xfer_common(struct dw_i2c_dev *dev, struct i2c_msg msgs[], int num) +{ + struct i2c_msg *msgs_part; + size_t cnt; + int ret; + + dev_dbg(dev->dev, "msgs: %d\n", num); + + pm_runtime_get_sync(dev->dev); + + ret = i2c_dw_acquire_lock(dev); + if (ret) + goto done_nolock; + + /* + * If the I2C_M_STOP is present in some the messages, + * we do one transaction for each part up to the STOP. + */ + for (msgs_part = msgs; msgs_part < msgs + num; msgs_part += cnt) { + /* + * Count the messages in a transaction, up to a STOP or + * the end of the msgs. The last if below guarantees that + * we check all messages and that msg_parts and cnt are + * in-bounds of msgs and num. + */ + for (cnt = 1; ; cnt++) { + if (!i2c_dw_msg_is_valid(dev, msgs_part, cnt - 1)) { + ret = -EINVAL; + goto done; + } + + if ((msgs_part[cnt - 1].flags & I2C_M_STOP) || + (msgs_part + cnt == msgs + num)) + break; + } + + /* transfer one part up to a STOP */ + ret = __i2c_dw_xfer_one_part(dev, msgs_part, cnt); + if (ret < 0) + break; + } done: i2c_dw_set_mode(dev, DW_IC_SLAVE); @@ -840,7 +888,9 @@ i2c_dw_xfer_common(struct dw_i2c_dev *dev, struct i2c_msg msgs[], int num) done_nolock: pm_runtime_put_autosuspend(dev->dev); - return ret; + if (ret < 0) + return ret; + return num; } int i2c_dw_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) @@ -859,6 +909,10 @@ void i2c_dw_configure_master(struct dw_i2c_dev *dev) dev->functionality |= I2C_FUNC_10BIT_ADDR | DW_IC_DEFAULT_FUNCTIONALITY; + /* amd_i2c_dw_xfer_quirk() does not implement protocol mangling */ + if ((dev->flags & MODEL_MASK) != MODEL_AMD_NAVI_GPU) + dev->functionality |= I2C_FUNC_PROTOCOL_MANGLING; + dev->master_cfg = DW_IC_CON_MASTER | DW_IC_CON_SLAVE_DISABLE | DW_IC_CON_RESTART_EN;