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;
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;
}
/*
- * 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;
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);
/* i2c_dw_init() implicitly disables the adapter */
i2c_recover_bus(&dev->adapter);
i2c_dw_init(dev);
- goto done;
+ return ret;
}
/*
*/
__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);
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)
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;