i2c: Add configurable I2C transfer timeout

This patch introduces CONFIG_I2C_TRANSFER_TIMEOUT_US,
which controls how long to wait for an I2C devices to
produce/accept all the data bytes in a single transfer.
(The device can delay transfer by stretching the clock of
the ack bit.)

The default value of this new setting is 500ms.  Existing
code had timeouts anywhere from tens of milliseconds to a
full second beween various drivers.  Drivers can still have
their own shorter timeouts for setup/communication with the
I2C host controller (as opposed to transactions with I2C
devices on the bus.)

In general, the timeout is not meant to be reached except in
situations where there is already serious problem with the
boot, and serves to make sure that some useful diagnostic
output is produced on the console.

Change-Id: I6423122f32aad1dbcee0bfe240cdaa8cb512791f
Signed-off-by: Jes B. Klinke <jbk@chromium.org>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/62278
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Julius Werner <jwerner@chromium.org>
diff --git a/src/device/Kconfig b/src/device/Kconfig
index 7f20d70..388972c 100644
--- a/src/device/Kconfig
+++ b/src/device/Kconfig
@@ -906,6 +906,14 @@
 	  I2C controller is not (yet) available. The platform code needs to
 	  provide bindings to manually toggle I2C lines.
 
+config I2C_TRANSFER_TIMEOUT_US
+	int "I2C transfer timeout in microseconds"
+	default 500000
+	help
+	  Timeout for a read/write transfers on the I2C bus, that is, the
+	  maximum time a device could stretch clock bits before the transfer
+	  is aborted and an error returned.
+
 config RESOURCE_ALLOCATOR_V3
 	bool
 	default n
diff --git a/src/drivers/i2c/designware/dw_i2c.c b/src/drivers/i2c/designware/dw_i2c.c
index 2cc236e..56f3f27 100644
--- a/src/drivers/i2c/designware/dw_i2c.c
+++ b/src/drivers/i2c/designware/dw_i2c.c
@@ -13,6 +13,8 @@
 
 /* Use a ~10ms timeout for various operations */
 #define DW_I2C_TIMEOUT_US		10000
+/* Timeout for waiting for FIFO to flush */
+#define DW_I2C_FLUSH_TIMEOUT_US		160000
 
 /* High and low times in different speed modes (in ns) */
 enum {
@@ -290,7 +292,7 @@
 	struct stopwatch sw;
 
 	/* Start timeout for up to 16 bytes in FIFO */
-	stopwatch_init_usecs_expire(&sw, 16 * DW_I2C_TIMEOUT_US);
+	stopwatch_init_usecs_expire(&sw, DW_I2C_FLUSH_TIMEOUT_US);
 
 	while (!stopwatch_expired(&sw)) {
 		uint32_t status = read32(&regs->status);
@@ -316,7 +318,7 @@
 	struct stopwatch sw;
 	uint32_t cmd = CMD_DATA_CMD; /* Read op */
 
-	stopwatch_init_usecs_expire(&sw, DW_I2C_TIMEOUT_US);
+	stopwatch_init_usecs_expire(&sw, CONFIG_I2C_TRANSFER_TIMEOUT_US);
 
 	if (!(segment->flags & I2C_M_RD)) {
 		/* Write op only: Wait for FIFO not full */
@@ -409,7 +411,7 @@
 	}
 
 	/* Wait for interrupt status to indicate transfer is complete */
-	stopwatch_init_usecs_expire(&sw, DW_I2C_TIMEOUT_US);
+	stopwatch_init_usecs_expire(&sw, CONFIG_I2C_TRANSFER_TIMEOUT_US);
 	while (!(read32(&regs->raw_intr_stat) & INTR_STAT_STOP_DET)) {
 		if (stopwatch_expired(&sw)) {
 			printk(BIOS_ERR, "I2C stop bit not received\n");
@@ -436,7 +438,7 @@
 	}
 
 	/* Flush the RX FIFO in case it is not empty */
-	stopwatch_init_usecs_expire(&sw, 16 * DW_I2C_TIMEOUT_US);
+	stopwatch_init_usecs_expire(&sw, DW_I2C_FLUSH_TIMEOUT_US);
 	while (read32(&regs->status) & STATUS_RX_FIFO_NOT_EMPTY) {
 		if (stopwatch_expired(&sw)) {
 			printk(BIOS_ERR, "I2C timeout flushing RX FIFO\n");
diff --git a/src/soc/cavium/cn81xx/twsi.c b/src/soc/cavium/cn81xx/twsi.c
index bb91b88..b1c1e66 100644
--- a/src/soc/cavium/cn81xx/twsi.c
+++ b/src/soc/cavium/cn81xx/twsi.c
@@ -12,6 +12,7 @@
 #include <delay.h>
 #include <device/mmio.h>
 #include <soc/addressmap.h>
+#include <timer.h>
 
 #define TWSI_THP		24
 
@@ -348,17 +349,15 @@
  *
  * @return	0 for success, 1 if timeout
  */
-static int twsi_wait(void *baseaddr)
+static int twsi_wait(void *baseaddr, struct stopwatch *timeout)
 {
-	unsigned long timeout = 500000;
 	u8 twsi_ctl;
 
 	printk(BIOS_SPEW, "%s(%p)\n", __func__, baseaddr);
 	do {
 		twsi_ctl = twsi_read_ctl(baseaddr);
 		twsi_ctl &= TWSI_CTL_IFLG;
-		timeout--;
-	} while (!twsi_ctl && timeout > 0);
+	} while (!twsi_ctl && !stopwatch_expired(timeout));
 
 	printk(BIOS_SPEW, "  return: %u\n", !twsi_ctl);
 	return !twsi_ctl;
@@ -438,10 +437,12 @@
 {
 	int result;
 	u8 stat;
+	struct stopwatch timeout;
 
 	printk(BIOS_SPEW, "%s(%p)\n", __func__, baseaddr);
+	stopwatch_init_usecs_expire(&timeout, CONFIG_I2C_TRANSFER_TIMEOUT_US);
 	twsi_write_ctl(baseaddr, TWSI_CTL_STA | TWSI_CTL_ENAB);
-	result = twsi_wait(baseaddr);
+	result = twsi_wait(baseaddr, &timeout);
 	if (result) {
 		stat = twsi_read_status(baseaddr);
 		printk(BIOS_SPEW, "%s: result: 0x%x, status: 0x%x\n", __func__,
@@ -475,9 +476,11 @@
 	union twsx_sw_twsi twsi_sw;
 	unsigned int curr = 0;
 	int result;
+	struct stopwatch timeout;
 
 	printk(BIOS_SPEW, "%s(%p, 0x%x, %p, 0x%x)\n", __func__, baseaddr,
 	       slave_addr, buffer, length);
+	stopwatch_init_usecs_expire(&timeout, CONFIG_I2C_TRANSFER_TIMEOUT_US);
 	result = twsi_start(baseaddr);
 	if (result) {
 		printk(BIOS_ERR, "%s: Could not start BUS transaction\n",
@@ -485,7 +488,7 @@
 		return -1;
 	}
 
-	result = twsi_wait(baseaddr);
+	result = twsi_wait(baseaddr, &timeout);
 	if (result) {
 		printk(BIOS_ERR, "%s: wait failed\n", __func__);
 		return result;
@@ -500,7 +503,7 @@
 	twsi_write_ctl(baseaddr, TWSI_CTL_ENAB);
 
 	printk(BIOS_SPEW, "%s: Waiting\n", __func__);
-	result = twsi_wait(baseaddr);
+	result = twsi_wait(baseaddr, &timeout);
 	if (result) {
 		printk(BIOS_ERR, "%s: Timed out writing slave address 0x%x\n",
 		      __func__, slave_addr);
@@ -521,7 +524,7 @@
 		twsi_write_sw(baseaddr, twsi_sw);
 		twsi_write_ctl(baseaddr, TWSI_CTL_ENAB);
 
-		result = twsi_wait(baseaddr);
+		result = twsi_wait(baseaddr, &timeout);
 		if (result) {
 			printk(BIOS_ERR, "%s: Timed out writing data to 0x%x\n",
 			      __func__, slave_addr);
@@ -549,16 +552,18 @@
 	union twsx_sw_twsi twsi_sw;
 	unsigned int curr = 0;
 	int result;
+	struct stopwatch timeout;
 
 	printk(BIOS_SPEW, "%s(%p, 0x%x, %p, %u)\n", __func__, baseaddr,
 	       slave_addr, buffer, length);
+	stopwatch_init_usecs_expire(&timeout, CONFIG_I2C_TRANSFER_TIMEOUT_US);
 	result = twsi_start(baseaddr);
 	if (result) {
 		printk(BIOS_ERR, "%s: start failed\n", __func__);
 		return result;
 	}
 
-	result = twsi_wait(baseaddr);
+	result = twsi_wait(baseaddr, &timeout);
 	if (result) {
 		printk(BIOS_ERR, "%s: wait failed\n", __func__);
 		return result;
@@ -574,7 +579,7 @@
 	twsi_write_sw(baseaddr, twsi_sw);
 	twsi_write_ctl(baseaddr, TWSI_CTL_ENAB);
 
-	result = twsi_wait(baseaddr);
+	result = twsi_wait(baseaddr, &timeout);
 	if (result) {
 		printk(BIOS_ERR, "%s: waiting for sending addr failed\n", __func__);
 		return result;
@@ -590,7 +595,7 @@
 		twsi_write_ctl(baseaddr, TWSI_CTL_ENAB |
 				((curr < length - 1) ? TWSI_CTL_AAK : 0));
 
-		result = twsi_wait(baseaddr);
+		result = twsi_wait(baseaddr, &timeout);
 		if (result) {
 			printk(BIOS_ERR, "%s: waiting for data failed\n",
 			       __func__);
diff --git a/src/soc/intel/quark/i2c.c b/src/soc/intel/quark/i2c.c
index bb62a7a..42ca25d 100644
--- a/src/soc/intel/quark/i2c.c
+++ b/src/soc/intel/quark/i2c.c
@@ -243,7 +243,7 @@
 	status = regs->ic_clr_tx_abrt;
 
 	/* Start the timeout */
-	stopwatch_init_msecs_expire(&timeout, 1000);
+	stopwatch_init_usecs_expire(&timeout, CONFIG_I2C_TRANSFER_TIMEOUT_US);
 
 	/* Process each of the segments */
 	total_bytes = 0;
diff --git a/src/soc/mediatek/common/i2c.c b/src/soc/mediatek/common/i2c.c
index c47e08e..b4386b9 100644
--- a/src/soc/mediatek/common/i2c.c
+++ b/src/soc/mediatek/common/i2c.c
@@ -239,7 +239,7 @@
 	/* start transfer transaction */
 	write32(&regs->start, 0x1);
 
-	stopwatch_init_msecs_expire(&sw, 100);
+	stopwatch_init_usecs_expire(&sw, CONFIG_I2C_TRANSFER_TIMEOUT_US);
 
 	/* polling mode : see if transaction complete */
 	while (1) {
diff --git a/src/soc/qualcomm/common/include/soc/qup_se_handlers_common.h b/src/soc/qualcomm/common/include/soc/qup_se_handlers_common.h
index bab7434..a004c79 100644
--- a/src/soc/qualcomm/common/include/soc/qup_se_handlers_common.h
+++ b/src/soc/qualcomm/common/include/soc/qup_se_handlers_common.h
@@ -463,6 +463,6 @@
 void qup_m_cancel_and_abort(unsigned int bus);
 void qup_s_cancel_and_abort(unsigned int bus);
 int qup_handle_transfer(unsigned int bus, const void *dout, void *din,
-	int size);
+	int size, struct stopwatch *timeout);
 
 #endif /* __SOC_COMMON_QCOM_QUP_SE_H__ */
diff --git a/src/soc/qualcomm/common/qup_se_handler.c b/src/soc/qualcomm/common/qup_se_handler.c
index bb7be37..7dd4f45 100644
--- a/src/soc/qualcomm/common/qup_se_handler.c
+++ b/src/soc/qualcomm/common/qup_se_handler.c
@@ -149,15 +149,14 @@
 	}
 }
 
-int qup_handle_transfer(unsigned int bus, const void *dout, void *din, int size)
+int qup_handle_transfer(unsigned int bus, const void *dout, void *din, int size,
+			struct stopwatch *timeout)
 {
 	unsigned int m_irq;
-	struct stopwatch sw;
 	unsigned int rx_rem_bytes = din ? size : 0;
 	unsigned int tx_rem_bytes = dout ? size : 0;
 	struct qup_regs *regs = qup[bus].regs;
 
-	stopwatch_init_msecs_expire(&sw, 1000);
 	do {
 		m_irq = qup_wait_for_m_irq(bus);
 		if ((m_irq & M_RX_FIFO_WATERMARK_EN) ||
@@ -172,7 +171,7 @@
 			break;
 		}
 		write32(&regs->geni_m_irq_clear, m_irq);
-	} while (!stopwatch_expired(&sw));
+	} while (!stopwatch_expired(timeout));
 
 	if (!(m_irq & M_CMD_DONE_EN) || tx_rem_bytes || rx_rem_bytes) {
 		printk(BIOS_INFO, "%s:Error: Transfer failed\n", __func__);
diff --git a/src/soc/qualcomm/common/qupv3_i2c.c b/src/soc/qualcomm/common/qupv3_i2c.c
index 606b3bf..8f0880e 100644
--- a/src/soc/qualcomm/common/qupv3_i2c.c
+++ b/src/soc/qualcomm/common/qupv3_i2c.c
@@ -117,6 +117,7 @@
 	unsigned int master_cmd_reg_val = (cmd << M_OPCODE_SHFT);
 	struct qup_regs *regs = qup[bus].regs;
 	void *dout = NULL, *din = NULL;
+	struct stopwatch timeout;
 
 	if (!(segment.flags & I2C_M_RD)) {
 		write32(&regs->i2c_tx_trans_len, segment.len);
@@ -130,7 +131,8 @@
 	master_cmd_reg_val |= (prams & M_PARAMS_MSK);
 	write32(&regs->geni_m_cmd0, master_cmd_reg_val);
 
-	return qup_handle_transfer(bus, dout, din, segment.len);
+	stopwatch_init_usecs_expire(&timeout, CONFIG_I2C_TRANSFER_TIMEOUT_US);
+	return qup_handle_transfer(bus, dout, din, segment.len, &timeout);
 }
 
 int platform_i2c_transfer(unsigned int bus, struct i2c_msg *segments,
diff --git a/src/soc/qualcomm/common/qupv3_spi.c b/src/soc/qualcomm/common/qupv3_spi.c
index c69ecf9..1bb5c75 100644
--- a/src/soc/qualcomm/common/qupv3_spi.c
+++ b/src/soc/qualcomm/common/qupv3_spi.c
@@ -83,6 +83,7 @@
 	int size;
 	unsigned int se_bus = slave->bus;
 	struct qup_regs *regs = qup[se_bus].regs;
+	struct stopwatch timeout;
 
 	if ((bytes_in == 0) && (bytes_out == 0))
 		return 0;
@@ -114,7 +115,8 @@
 
 	qup_setup_m_cmd(se_bus, m_cmd, m_param);
 
-	if (qup_handle_transfer(se_bus, dout, din, size))
+	stopwatch_init_msecs_expire(&timeout, 1000);
+	if (qup_handle_transfer(se_bus, dout, din, size, &timeout))
 		return -1;
 
 	qup_spi_xfer(slave, dout + size, MAX((int)bytes_out - size, 0),
diff --git a/src/soc/qualcomm/ipq40xx/qup.c b/src/soc/qualcomm/ipq40xx/qup.c
index 76f0797..88e9169 100644
--- a/src/soc/qualcomm/ipq40xx/qup.c
+++ b/src/soc/qualcomm/ipq40xx/qup.c
@@ -3,6 +3,7 @@
 #include <device/mmio.h>
 #include <console/console.h>
 #include <delay.h>
+#include <timer.h>
 #include <soc/iomap.h>
 #include <soc/qup.h>
 
@@ -93,35 +94,33 @@
 	return QUP_SUCCESS;
 }
 
-static qup_return_t qup_fifo_wait_for(blsp_qup_id_t id, uint32_t status)
+static qup_return_t qup_fifo_wait_for(blsp_qup_id_t id, uint32_t status,
+				      struct stopwatch *timeout)
 {
 	qup_return_t ret = QUP_ERR_UNDEFINED;
-	unsigned int count = TIMEOUT_CNT;
 
 	while (!(read32(QUP_ADDR(id, QUP_OPERATIONAL)) & status)) {
 		ret = qup_i2c_master_status(id);
 		if (ret)
 			return ret;
-		if (count == 0)
+		if (stopwatch_expired(timeout))
 			return QUP_ERR_TIMEOUT;
-		count--;
 	}
 
 	return QUP_SUCCESS;
 }
 
-static qup_return_t qup_fifo_wait_while(blsp_qup_id_t id, uint32_t status)
+static qup_return_t qup_fifo_wait_while(blsp_qup_id_t id, uint32_t status,
+					struct stopwatch *timeout)
 {
 	qup_return_t ret = QUP_ERR_UNDEFINED;
-	unsigned int count = TIMEOUT_CNT;
 
 	while (read32(QUP_ADDR(id, QUP_OPERATIONAL)) & status) {
 		ret = qup_i2c_master_status(id);
 		if (ret)
 			return ret;
-		if (count == 0)
+		if (stopwatch_expired(timeout))
 			return QUP_ERR_TIMEOUT;
-		count--;
 	}
 
 	return QUP_SUCCESS;
@@ -139,7 +138,8 @@
 	return tag;
 }
 
-static inline qup_return_t qup_i2c_write_fifo_flush(blsp_qup_id_t id)
+static inline qup_return_t qup_i2c_write_fifo_flush(blsp_qup_id_t id,
+						    struct stopwatch *timeout)
 {
 	qup_return_t ret = QUP_ERR_UNDEFINED;
 
@@ -147,7 +147,7 @@
 
 	mdelay(4);	/* TPM seems to need this */
 
-	ret = qup_fifo_wait_while(id, OUTPUT_FIFO_NOT_EMPTY);
+	ret = qup_fifo_wait_while(id, OUTPUT_FIFO_NOT_EMPTY, timeout);
 	if (ret)
 		return ret;
 
@@ -168,6 +168,7 @@
 	unsigned int data_len = p_tx_obj->p.iic.data_len;
 	unsigned int idx = 0;
 	uint32_t tag, *fifo = QUP_ADDR(id, QUP_OUTPUT_FIFO);
+	struct stopwatch timeout;
 
 	qup_reset_master_status(id);
 
@@ -196,6 +197,7 @@
 
 	qup_write32(fifo, tag);
 
+	stopwatch_init_usecs_expire(&timeout, CONFIG_I2C_TRANSFER_TIMEOUT_US);
 	while (data_len) {
 
 		tag = qup_i2c_create_output_tag(data_len == 1 && stop_seq,
@@ -213,7 +215,7 @@
 
 		qup_write32(fifo, tag);
 
-		ret = qup_i2c_write_fifo_flush(id);
+		ret = qup_i2c_write_fifo_flush(id, &timeout);
 
 		if (ret) {
 			printk(QUPDBG "%s: error\n", __func__);
@@ -221,7 +223,7 @@
 		}
 	}
 
-	ret = qup_i2c_write_fifo_flush(id);
+	ret = qup_i2c_write_fifo_flush(id, &timeout);
 
 	qup_set_state(id, QUP_STATE_RESET);
 
@@ -285,6 +287,7 @@
 	unsigned int data_len = p_tx_obj->p.iic.data_len;
 	unsigned int idx = 0;
 	uint32_t *fifo = QUP_ADDR(id, QUP_OUTPUT_FIFO);
+	struct stopwatch timeout;
 
 	qup_reset_master_status(id);
 
@@ -303,13 +306,14 @@
 				  (QUP_I2C_ADDR(addr) | QUP_I2C_SLAVE_READ)) |
 				((QUP_I2C_RECV_SEQ | data_len) << 16));
 
-	ret = qup_i2c_write_fifo_flush(id);
+	stopwatch_init_usecs_expire(&timeout, CONFIG_I2C_TRANSFER_TIMEOUT_US);
+	ret = qup_i2c_write_fifo_flush(id, &timeout);
 	if (ret) {
 		printk(QUPDBG "%s: OUTPUT_FIFO_NOT_EMPTY\n", __func__);
 		return ret;
 	}
 
-	ret = qup_fifo_wait_for(id, INPUT_SERVICE_FLAG);
+	ret = qup_fifo_wait_for(id, INPUT_SERVICE_FLAG, &timeout);
 	if (ret) {
 		printk(QUPDBG "%s: INPUT_SERVICE_FLAG\n", __func__);
 		return ret;
diff --git a/src/soc/qualcomm/ipq806x/qup.c b/src/soc/qualcomm/ipq806x/qup.c
index e2acde3..e7b45d6e 100644
--- a/src/soc/qualcomm/ipq806x/qup.c
+++ b/src/soc/qualcomm/ipq806x/qup.c
@@ -3,6 +3,7 @@
 #include <device/mmio.h>
 #include <console/console.h>
 #include <delay.h>
+#include <timer.h>
 #include <soc/iomap.h>
 #include <soc/qup.h>
 
@@ -87,35 +88,33 @@
 	return QUP_SUCCESS;
 }
 
-static qup_return_t qup_fifo_wait_for(gsbi_id_t gsbi_id, uint32_t status)
+static qup_return_t qup_fifo_wait_for(gsbi_id_t gsbi_id, uint32_t status,
+				      struct stopwatch *timeout)
 {
 	qup_return_t ret = QUP_ERR_UNDEFINED;
-	unsigned int count = TIMEOUT_CNT;
 
 	while (!(read32(QUP_ADDR(gsbi_id, QUP_OPERATIONAL)) & status)) {
 		ret = qup_i2c_master_status(gsbi_id);
 		if (ret)
 			return ret;
-		if (count == 0)
+		if (stopwatch_expired(timeout))
 			return QUP_ERR_TIMEOUT;
-		count--;
 	}
 
 	return QUP_SUCCESS;
 }
 
-static qup_return_t qup_fifo_wait_while(gsbi_id_t gsbi_id, uint32_t status)
+static qup_return_t qup_fifo_wait_while(gsbi_id_t gsbi_id, uint32_t status,
+					struct stopwatch *timeout)
 {
 	qup_return_t ret = QUP_ERR_UNDEFINED;
-	unsigned int count = TIMEOUT_CNT;
 
 	while (read32(QUP_ADDR(gsbi_id, QUP_OPERATIONAL)) & status) {
 		ret = qup_i2c_master_status(gsbi_id);
 		if (ret)
 			return ret;
-		if (count == 0)
+		if (stopwatch_expired(timeout))
 			return QUP_ERR_TIMEOUT;
-		count--;
 	}
 
 	return QUP_SUCCESS;
@@ -129,6 +128,7 @@
 	uint8_t *data_ptr = p_tx_obj->p.iic.data;
 	unsigned int data_len = p_tx_obj->p.iic.data_len;
 	unsigned int idx = 0;
+	struct stopwatch timeout;
 
 	qup_reset_master_status(gsbi_id);
 	qup_set_state(gsbi_id, QUP_STATE_RUN);
@@ -136,6 +136,7 @@
 	write32(QUP_ADDR(gsbi_id, QUP_OUTPUT_FIFO),
 		(QUP_I2C_START_SEQ | QUP_I2C_ADDR(addr)));
 
+	stopwatch_init_usecs_expire(&timeout, CONFIG_I2C_TRANSFER_TIMEOUT_US);
 	while (data_len) {
 		if (data_len == 1 && stop_seq) {
 			write32(QUP_ADDR(gsbi_id, QUP_OUTPUT_FIFO),
@@ -147,7 +148,8 @@
 		data_len--;
 		idx++;
 		if (data_len) {
-			ret = qup_fifo_wait_while(gsbi_id, OUTPUT_FIFO_FULL);
+			ret = qup_fifo_wait_while(gsbi_id, OUTPUT_FIFO_FULL,
+						  &timeout);
 			if (ret)
 				return ret;
 		}
@@ -166,7 +168,7 @@
 		}
 	}
 
-	ret = qup_fifo_wait_while(gsbi_id, OUTPUT_FIFO_NOT_EMPTY);
+	ret = qup_fifo_wait_while(gsbi_id, OUTPUT_FIFO_NOT_EMPTY, &timeout);
 	if (ret)
 		return ret;
 
@@ -202,6 +204,7 @@
 	uint8_t *data_ptr = p_tx_obj->p.iic.data;
 	unsigned int data_len = p_tx_obj->p.iic.data_len;
 	unsigned int idx = 0;
+	struct stopwatch timeout;
 
 	qup_reset_master_status(gsbi_id);
 	qup_set_state(gsbi_id, QUP_STATE_RUN);
@@ -212,7 +215,8 @@
 	write32(QUP_ADDR(gsbi_id, QUP_OUTPUT_FIFO),
 		QUP_I2C_RECV_SEQ | data_len);
 
-	ret = qup_fifo_wait_while(gsbi_id, OUTPUT_FIFO_NOT_EMPTY);
+	stopwatch_init_usecs_expire(&timeout, CONFIG_I2C_TRANSFER_TIMEOUT_US);
+	ret = qup_fifo_wait_while(gsbi_id, OUTPUT_FIFO_NOT_EMPTY, &timeout);
 	if (ret)
 		return ret;
 
@@ -221,7 +225,7 @@
 	while (data_len) {
 		uint32_t data;
 
-		ret = qup_fifo_wait_for(gsbi_id, INPUT_SERVICE_FLAG);
+		ret = qup_fifo_wait_for(gsbi_id, INPUT_SERVICE_FLAG, &timeout);
 		if (ret)
 			return ret;
 
diff --git a/src/soc/qualcomm/qcs405/qup.c b/src/soc/qualcomm/qcs405/qup.c
index 316cd9f..e3a4e5e 100644
--- a/src/soc/qualcomm/qcs405/qup.c
+++ b/src/soc/qualcomm/qcs405/qup.c
@@ -3,6 +3,7 @@
 #include <device/mmio.h>
 #include <console/console.h>
 #include <delay.h>
+#include <timer.h>
 #include <soc/gpio.h>
 #include <soc/iomap.h>
 #include <soc/qup.h>
@@ -129,35 +130,33 @@
 	return QUP_SUCCESS;
 }
 
-static qup_return_t qup_fifo_wait_for(blsp_qup_id_t id, uint32_t status)
+static qup_return_t qup_fifo_wait_for(blsp_qup_id_t id, uint32_t status,
+				      struct stopwatch *timeout)
 {
 	qup_return_t ret = QUP_ERR_UNDEFINED;
-	unsigned int count = TIMEOUT_CNT;
 
 	while (!(read32(QUP_ADDR(id, QUP_OPERATIONAL)) & status)) {
 		ret = qup_i2c_master_status(id);
 		if (ret)
 			return ret;
-		if (count == 0)
+		if (stopwatch_expired(timeout))
 			return QUP_ERR_TIMEOUT;
-		count--;
 	}
 
 	return QUP_SUCCESS;
 }
 
-static qup_return_t qup_fifo_wait_while(blsp_qup_id_t id, uint32_t status)
+static qup_return_t qup_fifo_wait_while(blsp_qup_id_t id, uint32_t status,
+					struct stopwatch *timeout)
 {
 	qup_return_t ret = QUP_ERR_UNDEFINED;
-	unsigned int count = TIMEOUT_CNT;
 
 	while (read32(QUP_ADDR(id, QUP_OPERATIONAL)) & status) {
 		ret = qup_i2c_master_status(id);
 		if (ret)
 			return ret;
-		if (count == 0)
+		if (stopwatch_expired(timeout))
 			return QUP_ERR_TIMEOUT;
-		count--;
 	}
 
 	return QUP_SUCCESS;
@@ -175,7 +174,8 @@
 	return tag;
 }
 
-static inline qup_return_t qup_i2c_write_fifo_flush(blsp_qup_id_t id)
+static inline qup_return_t qup_i2c_write_fifo_flush(blsp_qup_id_t id,
+						    struct stopwatch *timeout)
 {
 	qup_return_t ret = QUP_ERR_UNDEFINED;
 
@@ -183,7 +183,7 @@
 
 	mdelay(4);	/* TPM seems to need this */
 
-	ret = qup_fifo_wait_while(id, OUTPUT_FIFO_NOT_EMPTY);
+	ret = qup_fifo_wait_while(id, OUTPUT_FIFO_NOT_EMPTY, timeout);
 	if (ret)
 		return ret;
 
@@ -204,6 +204,7 @@
 	unsigned int data_len = p_tx_obj->p.iic.data_len;
 	unsigned int idx = 0;
 	uint32_t tag, *fifo = QUP_ADDR(id, QUP_OUTPUT_FIFO);
+	struct stopwatch timeout;
 
 	qup_reset_master_status(id);
 
@@ -232,6 +233,7 @@
 
 	qup_write32(fifo, tag);
 
+	stopwatch_init_usecs_expire(&timeout, CONFIG_I2C_TRANSFER_TIMEOUT_US);
 	while (data_len) {
 
 		tag = qup_i2c_create_output_tag(data_len == 1 && stop_seq,
@@ -249,7 +251,7 @@
 
 		qup_write32(fifo, tag);
 
-		ret = qup_i2c_write_fifo_flush(id);
+		ret = qup_i2c_write_fifo_flush(id, &timeout);
 
 		if (ret) {
 			printk(QUPDBG "%s: error\n", __func__);
@@ -257,7 +259,7 @@
 		}
 	}
 
-	ret = qup_i2c_write_fifo_flush(id);
+	ret = qup_i2c_write_fifo_flush(id, &timeout);
 
 	qup_set_state(id, QUP_STATE_RESET);
 
@@ -321,6 +323,7 @@
 	unsigned int data_len = p_tx_obj->p.iic.data_len;
 	unsigned int idx = 0;
 	uint32_t *fifo = QUP_ADDR(id, QUP_OUTPUT_FIFO);
+	struct stopwatch timeout;
 
 	qup_reset_master_status(id);
 
@@ -339,13 +342,14 @@
 				  (QUP_I2C_ADDR(addr) | QUP_I2C_SLAVE_READ)) |
 				((QUP_I2C_RECV_SEQ | data_len) << 16));
 
-	ret = qup_i2c_write_fifo_flush(id);
+	stopwatch_init_usecs_expire(&timeout, CONFIG_I2C_TRANSFER_TIMEOUT_US);
+	ret = qup_i2c_write_fifo_flush(id, &timeout);
 	if (ret) {
 		printk(QUPDBG "%s: OUTPUT_FIFO_NOT_EMPTY\n", __func__);
 		return ret;
 	}
 
-	ret = qup_fifo_wait_for(id, INPUT_SERVICE_FLAG);
+	ret = qup_fifo_wait_for(id, INPUT_SERVICE_FLAG, &timeout);
 	if (ret) {
 		printk(QUPDBG "%s: INPUT_SERVICE_FLAG\n", __func__);
 		return ret;
diff --git a/src/soc/rockchip/common/i2c.c b/src/soc/rockchip/common/i2c.c
index dbbfd3e..a0498a5 100644
--- a/src/soc/rockchip/common/i2c.c
+++ b/src/soc/rockchip/common/i2c.c
@@ -114,7 +114,6 @@
 {
 	int res = 0;
 	uint8_t *data = segment.buf;
-	int timeout = I2C_TIMEOUT_US;
 	unsigned int bytes_remaining = segment.len;
 	unsigned int con = 0;
 
@@ -122,6 +121,7 @@
 	write32(&reg_addr->i2c_mrxraddr, 0);
 	con = I2C_MODE_TRX | I2C_EN | I2C_ACT2NAK;
 	while (bytes_remaining) {
+		int timeout = CONFIG_I2C_TRANSFER_TIMEOUT_US;
 		size_t size = MIN(bytes_remaining, 32);
 		bytes_remaining -= size;
 		if (!bytes_remaining)
@@ -132,7 +132,6 @@
 		write32(&reg_addr->i2c_con, con);
 		write32(&reg_addr->i2c_mrxcnt, size);
 
-		timeout = I2C_TIMEOUT_US;
 		while (timeout--) {
 			if (read32(&reg_addr->i2c_ipd) & I2C_NAKRCVI) {
 				write32(&reg_addr->i2c_mrxcnt, 0);
@@ -161,7 +160,6 @@
 {
 	int res = 0;
 	uint8_t *data = segment.buf;
-	int timeout = I2C_TIMEOUT_US;
 	int bytes_remaining = segment.len + 1;
 
 	/* Prepend one byte for the slave address to the transfer. */
@@ -169,6 +167,7 @@
 	int prefsz = 1;
 
 	while (bytes_remaining) {
+		int timeout = CONFIG_I2C_TRANSFER_TIMEOUT_US;
 		size_t size = MIN(bytes_remaining, 32);
 		buffer_to_fifo32_prefix(data, prefix, prefsz, size,
 					&reg_addr->txdata, 4, 4);
@@ -180,7 +179,6 @@
 			I2C_EN | I2C_MODE_TX | I2C_ACT2NAK);
 		write32(&reg_addr->i2c_mtxcnt, size);
 
-		timeout = I2C_TIMEOUT_US;
 		while (timeout--) {
 			if (read32(&reg_addr->i2c_ipd) & I2C_NAKRCVI) {
 				write32(&reg_addr->i2c_mtxcnt, 0);
diff --git a/src/soc/samsung/exynos5250/i2c.c b/src/soc/samsung/exynos5250/i2c.c
index bc5570d..5ce4c7d 100644
--- a/src/soc/samsung/exynos5250/i2c.c
+++ b/src/soc/samsung/exynos5250/i2c.c
@@ -8,6 +8,9 @@
 #include <soc/clk.h>
 #include <soc/i2c.h>
 #include <soc/periph.h>
+#include <timer.h>
+
+#define I2C_TIMEOUT_US (1000 * USECS_PER_MSEC)
 
 struct __packed i2c_regs
 {
@@ -119,9 +122,9 @@
 	return !(read8(&regs->stat) & I2cStatAck);
 }
 
-static int i2c_wait_for_idle(struct i2c_regs *regs)
+static int i2c_wait_for_idle(struct i2c_regs *regs, int timeout_us)
 {
-	int timeout = 1000 * 100; // 1s.
+	int timeout = DIV_ROUND_UP(timeout_us, 10);
 	while (timeout--) {
 		if (!(read8(&regs->stat) & I2cStatBusy))
 			return 0;
@@ -131,9 +134,9 @@
 	return 1;
 }
 
-static int i2c_wait_for_int(struct i2c_regs *regs)
+static int i2c_wait_for_int(struct i2c_regs *regs, int timeout_us)
 {
-	int timeout = 1000 * 100; // 1s.
+	int timeout = DIV_ROUND_UP(timeout_us, 10);
 	while (timeout--) {
 		if (i2c_int_pending(regs))
 			return 0;
@@ -148,7 +151,7 @@
 	uint8_t mode = read8(&regs->stat) & (I2cStatModeMask);
 	write8(&regs->stat, mode | I2cStatEnable);
 	i2c_clear_int(regs);
-	return i2c_wait_for_idle(regs);
+	return i2c_wait_for_idle(regs, I2C_TIMEOUT_US);
 }
 
 static int i2c_send_start(struct i2c_regs *regs, int read, int chip)
@@ -158,7 +161,7 @@
 	write8(&regs->stat, mode | I2cStatStartStop | I2cStatEnable);
 	i2c_clear_int(regs);
 
-	if (i2c_wait_for_int(regs))
+	if (i2c_wait_for_int(regs, I2C_TIMEOUT_US))
 		return 1;
 
 	if (!i2c_got_ack(regs)) {
@@ -180,7 +183,7 @@
 		write8(&regs->ds, data[i]);
 
 		i2c_clear_int(regs);
-		if (i2c_wait_for_int(regs))
+		if (i2c_wait_for_int(regs, CONFIG_I2C_TRANSFER_TIMEOUT_US))
 			return 1;
 
 		if (!i2c_got_ack(regs)) {
@@ -204,7 +207,7 @@
 			i2c_ack_disable(regs);
 
 		i2c_clear_int(regs);
-		if (i2c_wait_for_int(regs))
+		if (i2c_wait_for_int(regs, CONFIG_I2C_TRANSFER_TIMEOUT_US))
 			return 1;
 
 		data[i] = read8(&regs->ds);
@@ -220,7 +223,7 @@
 	struct i2c_regs *regs = i2c->regs;
 	int res = 0;
 
-	if (!regs || i2c_wait_for_idle(regs))
+	if (!regs || i2c_wait_for_idle(regs, I2C_TIMEOUT_US))
 		return 1;
 
 	write8(&regs->stat, I2cStatMasterXmit | I2cStatEnable);
diff --git a/src/soc/samsung/exynos5420/i2c.c b/src/soc/samsung/exynos5420/i2c.c
index ab17d52..49505a0 100644
--- a/src/soc/samsung/exynos5420/i2c.c
+++ b/src/soc/samsung/exynos5420/i2c.c
@@ -11,6 +11,8 @@
 #include <soc/pinmux.h>
 #include <timer.h>
 
+#define I2C_TIMEOUT_US (1000 * USECS_PER_MSEC)
+
 struct __packed i2c_regs
 {
 	uint8_t con;
@@ -508,9 +510,9 @@
 	return !(read8(&regs->stat) & I2cStatAck);
 }
 
-static int i2c_wait_for_idle(struct i2c_regs *regs)
+static int i2c_wait_for_idle(struct i2c_regs *regs, int timeout_us)
 {
-	int timeout = 1000 * 100; // 1s.
+	int timeout = timeout_us / 10;
 	while (timeout--) {
 		if (!(read8(&regs->stat) & I2cStatBusy))
 			return 0;
@@ -520,9 +522,9 @@
 	return 1;
 }
 
-static int i2c_wait_for_int(struct i2c_regs *regs)
+static int i2c_wait_for_int(struct i2c_regs *regs, int timeout_us)
 {
-	int timeout = 1000 * 100; // 1s.
+	int timeout = timeout_us / 10;
 	while (timeout--) {
 		if (i2c_int_pending(regs))
 			return 0;
@@ -537,7 +539,7 @@
 	uint8_t mode = read8(&regs->stat) & (I2cStatModeMask);
 	write8(&regs->stat, mode | I2cStatEnable);
 	i2c_clear_int(regs);
-	return i2c_wait_for_idle(regs);
+	return i2c_wait_for_idle(regs, I2C_TIMEOUT_US);
 }
 
 static int i2c_send_start(struct i2c_regs *regs, int read, int chip)
@@ -547,7 +549,7 @@
 	write8(&regs->stat, mode | I2cStatStartStop | I2cStatEnable);
 	i2c_clear_int(regs);
 
-	if (i2c_wait_for_int(regs))
+	if (i2c_wait_for_int(regs, I2C_TIMEOUT_US))
 		return 1;
 
 	if (!i2c_got_ack(regs)) {
@@ -569,7 +571,7 @@
 		write8(&regs->ds, data[i]);
 
 		i2c_clear_int(regs);
-		if (i2c_wait_for_int(regs))
+		if (i2c_wait_for_int(regs, CONFIG_I2C_TRANSFER_TIMEOUT_US))
 			return 1;
 
 		if (!i2c_got_ack(regs)) {
@@ -593,7 +595,7 @@
 			i2c_ack_disable(regs);
 
 		i2c_clear_int(regs);
-		if (i2c_wait_for_int(regs))
+		if (i2c_wait_for_int(regs, CONFIG_I2C_TRANSFER_TIMEOUT_US))
 			return 1;
 
 		data[i] = read8(&regs->ds);
@@ -611,7 +613,7 @@
 	struct i2c_regs *regs = i2c->regs;
 	int res = 0;
 
-	if (!regs || i2c_wait_for_idle(regs))
+	if (!regs || i2c_wait_for_idle(regs, I2C_TIMEOUT_US))
 		return 1;
 
 	write8(&regs->stat, I2cStatMasterXmit | I2cStatEnable);