Skip to content

Commit 470f1a7

Browse files
benoitmoninAndi Shyti
authored andcommitted
i2c: designware: Implement I2C_M_STOP support
Add the support of the I2C_M_STOP flag in i2c_msg by splitting i2c_dw_xfer() in two: __i2c_dw_xfer_one_part() for the core transfer logic and i2c_dw_xfer() for handling the high-level transaction management. In detail __i2c_dw_xfer_one_part() starts a transaction and wait for its completion, either with a STOP on the bus or an error. i2c_dw_xfer() loops over the messages to search for the I2C_M_STOP flag and calls __i2c_dw_xfer_one_part() for each part of the messages up to a STOP or the end of the messages array. i2c_dw_xfer() takes care of runtime PM and holds the hardware lock on the bus while calling __i2c_dw_xfer_one_part(), this allows grouping multiple accesses to device that support a STOP in a transaction when done via i2c_dev I2C_RDWR ioctl. Also, now that we have a lookup of the messages in i2c_dw_xfer() prior to each transaction, we use it to make sure the messages are valid for the transaction, via a new function i2c_dw_msg_is_valid(). We check that the target address does not change before starting the transaction instead of aborting the transfer while it is happening, as it was done in i2c_dw_xfer_msg(). The target address can only be changed after an I2C_M_STOP flag, i.e after a STOP on the i2c bus. The I2C_FUNC_PROTOCOL_MANGLING flag is added to the list of functionalities supported by the controller, except for the AMD NAVI i2c controller which uses its own xfer() function and is left untouched. Signed-off-by: Benoît Monin <benoit.monin@bootlin.com> Acked-by: Mika Westerberg <mika.westerberg@linux.intel.com> Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com> Signed-off-by: Andi Shyti <andi.shyti@kernel.org> Link: https://lore.kernel.org/r/20260130-i2c-dw-v6-1-08ca1e9ece07@bootlin.com
1 parent 51e8ce3 commit 470f1a7

1 file changed

Lines changed: 93 additions & 39 deletions

File tree

drivers/i2c/busses/i2c-designware-master.c

Lines changed: 93 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,6 @@ i2c_dw_xfer_msg(struct dw_i2c_dev *dev)
377377
struct i2c_msg *msgs = dev->msgs;
378378
u32 intr_mask;
379379
int tx_limit, rx_limit;
380-
u32 addr = msgs[dev->msg_write_idx].addr;
381380
u32 buf_len = dev->tx_buf_len;
382381
u8 *buf = dev->tx_buf;
383382
bool need_restart = false;
@@ -388,18 +387,6 @@ i2c_dw_xfer_msg(struct dw_i2c_dev *dev)
388387
for (; dev->msg_write_idx < dev->msgs_num; dev->msg_write_idx++) {
389388
u32 flags = msgs[dev->msg_write_idx].flags;
390389

391-
/*
392-
* If target address has changed, we need to
393-
* reprogram the target address in the I2C
394-
* adapter when we are done with this transfer.
395-
*/
396-
if (msgs[dev->msg_write_idx].addr != addr) {
397-
dev_err(dev->dev,
398-
"%s: invalid target address\n", __func__);
399-
dev->msg_err = -EINVAL;
400-
break;
401-
}
402-
403390
if (!(dev->status & STATUS_WRITE_IN_PROGRESS)) {
404391
/* new i2c_msg */
405392
buf = msgs[dev->msg_write_idx].buf;
@@ -746,17 +733,15 @@ static int i2c_dw_wait_transfer(struct dw_i2c_dev *dev)
746733
}
747734

748735
/*
749-
* Prepare controller for a transaction and call i2c_dw_xfer_msg.
736+
* Prepare controller for a transaction, start the transfer of the @msgs
737+
* and wait for completion, either a STOP or a error.
738+
* Return: 0 or a negative error code.
750739
*/
751740
static int
752-
i2c_dw_xfer_common(struct dw_i2c_dev *dev, struct i2c_msg msgs[], int num)
741+
__i2c_dw_xfer_one_part(struct dw_i2c_dev *dev, struct i2c_msg *msgs, size_t num)
753742
{
754743
int ret;
755744

756-
dev_dbg(dev->dev, "%s: msgs: %d\n", __func__, num);
757-
758-
pm_runtime_get_sync(dev->dev);
759-
760745
reinit_completion(&dev->cmd_complete);
761746
dev->msgs = msgs;
762747
dev->msgs_num = num;
@@ -768,13 +753,9 @@ i2c_dw_xfer_common(struct dw_i2c_dev *dev, struct i2c_msg msgs[], int num)
768753
dev->abort_source = 0;
769754
dev->rx_outstanding = 0;
770755

771-
ret = i2c_dw_acquire_lock(dev);
772-
if (ret)
773-
goto done_nolock;
774-
775756
ret = i2c_dw_wait_bus_not_busy(dev);
776757
if (ret < 0)
777-
goto done;
758+
return ret;
778759

779760
/* Start the transfers */
780761
i2c_dw_xfer_init(dev);
@@ -786,7 +767,7 @@ i2c_dw_xfer_common(struct dw_i2c_dev *dev, struct i2c_msg msgs[], int num)
786767
/* i2c_dw_init() implicitly disables the adapter */
787768
i2c_recover_bus(&dev->adapter);
788769
i2c_dw_init(dev);
789-
goto done;
770+
return ret;
790771
}
791772

792773
/*
@@ -809,28 +790,95 @@ i2c_dw_xfer_common(struct dw_i2c_dev *dev, struct i2c_msg msgs[], int num)
809790
*/
810791
__i2c_dw_disable_nowait(dev);
811792

812-
if (dev->msg_err) {
813-
ret = dev->msg_err;
814-
goto done;
815-
}
793+
if (dev->msg_err)
794+
return dev->msg_err;
816795

817796
/* No error */
818-
if (likely(!dev->cmd_err && !dev->status)) {
819-
ret = num;
820-
goto done;
821-
}
797+
if (likely(!dev->cmd_err && !dev->status))
798+
return 0;
822799

823800
/* We have an error */
824-
if (dev->cmd_err == DW_IC_ERR_TX_ABRT) {
825-
ret = i2c_dw_handle_tx_abort(dev);
826-
goto done;
827-
}
801+
if (dev->cmd_err == DW_IC_ERR_TX_ABRT)
802+
return i2c_dw_handle_tx_abort(dev);
828803

829804
if (dev->status)
830805
dev_err(dev->dev,
831806
"transfer terminated early - interrupt latency too high?\n");
832807

833-
ret = -EIO;
808+
return -EIO;
809+
}
810+
811+
/*
812+
* Verify that the message at index @idx can be processed as part
813+
* of a single transaction. The @msgs array contains the messages
814+
* of the transaction. The message is checked against its predecessor
815+
* to ensure that it respects the limitation of the controller.
816+
* Return: true if the message can be processed, false otherwise.
817+
*/
818+
static bool
819+
i2c_dw_msg_is_valid(struct dw_i2c_dev *dev, const struct i2c_msg *msgs, size_t idx)
820+
{
821+
/*
822+
* The first message of a transaction is valid,
823+
* no constraints from a previous message.
824+
*/
825+
if (!idx)
826+
return true;
827+
828+
/*
829+
* We cannot change the target address during a transaction, so make
830+
* sure the address is identical to the one of the previous message.
831+
*/
832+
if (msgs[idx - 1].addr != msgs[idx].addr) {
833+
dev_err(dev->dev, "invalid target address\n");
834+
return false;
835+
}
836+
837+
return true;
838+
}
839+
840+
static int
841+
i2c_dw_xfer_common(struct dw_i2c_dev *dev, struct i2c_msg msgs[], int num)
842+
{
843+
struct i2c_msg *msgs_part;
844+
size_t cnt;
845+
int ret;
846+
847+
dev_dbg(dev->dev, "msgs: %d\n", num);
848+
849+
pm_runtime_get_sync(dev->dev);
850+
851+
ret = i2c_dw_acquire_lock(dev);
852+
if (ret)
853+
goto done_nolock;
854+
855+
/*
856+
* If the I2C_M_STOP is present in some the messages,
857+
* we do one transaction for each part up to the STOP.
858+
*/
859+
for (msgs_part = msgs; msgs_part < msgs + num; msgs_part += cnt) {
860+
/*
861+
* Count the messages in a transaction, up to a STOP or
862+
* the end of the msgs. The last if below guarantees that
863+
* we check all messages and that msg_parts and cnt are
864+
* in-bounds of msgs and num.
865+
*/
866+
for (cnt = 1; ; cnt++) {
867+
if (!i2c_dw_msg_is_valid(dev, msgs_part, cnt - 1)) {
868+
ret = -EINVAL;
869+
goto done;
870+
}
871+
872+
if ((msgs_part[cnt - 1].flags & I2C_M_STOP) ||
873+
(msgs_part + cnt == msgs + num))
874+
break;
875+
}
876+
877+
/* transfer one part up to a STOP */
878+
ret = __i2c_dw_xfer_one_part(dev, msgs_part, cnt);
879+
if (ret < 0)
880+
break;
881+
}
834882

835883
done:
836884
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)
840888
done_nolock:
841889
pm_runtime_put_autosuspend(dev->dev);
842890

843-
return ret;
891+
if (ret < 0)
892+
return ret;
893+
return num;
844894
}
845895

846896
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)
859909

860910
dev->functionality |= I2C_FUNC_10BIT_ADDR | DW_IC_DEFAULT_FUNCTIONALITY;
861911

912+
/* amd_i2c_dw_xfer_quirk() does not implement protocol mangling */
913+
if ((dev->flags & MODEL_MASK) != MODEL_AMD_NAVI_GPU)
914+
dev->functionality |= I2C_FUNC_PROTOCOL_MANGLING;
915+
862916
dev->master_cfg = DW_IC_CON_MASTER | DW_IC_CON_SLAVE_DISABLE |
863917
DW_IC_CON_RESTART_EN;
864918

0 commit comments

Comments
 (0)