diff mbox series

[v2,2/2] i2c: qcom-geni: Add support for I2C High-Speed mode

Message ID 20260912-i2c-hs-v2-2-b0901791e244@oss.qualcomm.com
State Changes Requested
Delegated to: Andi Shyti
Headers show
Series i2c: Add I2C High-Speed mode support for qcom-geni | expand

Commit Message

Jyothi Kumar Seerapu Sept. 12, 2026, 1:04 p.m. UTC
Implement I2C High-Speed (HS) mode for the Qualcomm GENI I2C controller.
Detect HS mode requests based on 3.4 MHz frequency and configure the
hardware accordingly. When HS mode is active, set the source clock to
100 MHz and program timing parameters (TCYCLE=28, TLOW=38).

For FIFO/SE-DMA transfers, use HS-specific opcodes I2C_HS_WRITE (0xa)
and I2C_HS_READ (0xb). Transmit the master code at Fast Mode Plus
timing (1 MHz) before switching to 3.4 MHz for data transfer.

For GPI DMA, extend the configuration with a CONFIG1 TRE to pass HS
timing parameters to the DMA engine. Add gpi_i2c_config1 with
tcycle_cnt and tlow_cnt fields (defaults: 28 and 38).
Use the set_config1 flag in gpi_i2c_config to send CONFIG1 TRE before
the GO TRE in HS mode.

Signed-off-by: Jyothi Kumar Seerapu <jyothi.seerapu@oss.qualcomm.com>
---
 drivers/i2c/busses/i2c-qcom-geni.c | 127 +++++++++++++++++++++++++++++++++----
 1 file changed, 113 insertions(+), 14 deletions(-)

Comments

Mukesh Savaliya Sept. 17, 2026, 7:21 p.m. UTC | #1
On 9/12/2026 6:34 PM, Jyothi Kumar Seerapu wrote:
> Implement I2C High-Speed (HS) mode for the Qualcomm GENI I2C controller.
> Detect HS mode requests based on 3.4 MHz frequency and configure the
> hardware accordingly. When HS mode is active, set the source clock to
> 100 MHz and program timing parameters (TCYCLE=28, TLOW=38).
> 
> For FIFO/SE-DMA transfers, use HS-specific opcodes I2C_HS_WRITE (0xa)
> and I2C_HS_READ (0xb). Transmit the master code at Fast Mode Plus
> timing (1 MHz) before switching to 3.4 MHz for data transfer.
> 
> For GPI DMA, extend the configuration with a CONFIG1 TRE to pass HS
> timing parameters to the DMA engine. Add gpi_i2c_config1 with
> tcycle_cnt and tlow_cnt fields (defaults: 28 and 38).
> Use the set_config1 flag in gpi_i2c_config to send CONFIG1 TRE before
> the GO TRE in HS mode.
> 
> Signed-off-by: Jyothi Kumar Seerapu <jyothi.seerapu@oss.qualcomm.com>
> ---
>   drivers/i2c/busses/i2c-qcom-geni.c | 127 +++++++++++++++++++++++++++++++++----
>   1 file changed, 113 insertions(+), 14 deletions(-)

[...]

>   static int geni_i2c_clk_map_idx(struct geni_i2c_dev *gi2c)
>   {
>   	const struct geni_i2c_clk_fld *itr;
>   
> +	/* Check if HS mode is requested */
> +	if (gi2c->clk_freq_out == I2C_HS_MODE_FREQ) {
> +		gi2c->is_hs_mode = true;
> +		/* For HS mode, source clock should be 100 MHz */
> +		itr = geni_i2c_clk_map_100mhz;
> +		/* For HS mode, start with 1MHz for master code */
why ? can you mention reason too ?
And also instead of 2, can we use the macro now for each speed ?> +	 
gi2c->clk_fld = &itr[2];
> +		return 0;
> +	}
> +
> +	gi2c->is_hs_mode = false;
> +
>   	if (clk_get_rate(gi2c->se.clk) == 32 * HZ_PER_MHZ)
>   		itr = geni_i2c_clk_map_32mhz;
>   	else
> @@ -219,7 +257,12 @@ static int qcom_geni_i2c_conf(struct geni_se *se, unsigned long freq)

[...]

> @@ -836,6 +886,13 @@ static int geni_i2c_gpi_xfer(struct geni_i2c_dev *gi2c, struct i2c_msg msgs[], i
>   
>   	gi2c->num_msgs = num;
>   	gi2c->is_tx_multi_desc_xfer = false;
> +	gi2c->config1_sent = false;
> +
> +	/* Initialize config1 TRE settings for HS mode */
> +	if (gi2c->is_hs_mode) {
> +		peripheral.config1.tcycle_cnt = I2C_HS_TCYCLE_CNT;
> +		peripheral.config1.tlow_cnt = I2C_HS_TLOW_CNT;
> +	}
>   
>   	tx_multi_xfer = &gi2c->i2c_multi_desc_config;
>   	memset(tx_multi_xfer, 0, sizeof(struct geni_i2c_gpi_multi_desc_xfer));
> @@ -883,14 +940,25 @@ static int geni_i2c_gpi_xfer(struct geni_i2c_dev *gi2c, struct i2c_msg msgs[], i
>   		if (i > 0 && (!(msgs[i].flags & I2C_M_RD)))
>   			peripheral.multi_msg = false;
>   
> -		ret =  geni_i2c_gpi(gi2c, msgs, &config,
> -				    &tx_addr, &tx_buf, I2C_WRITE, gi2c->tx_c);
> +		/* Set config1 TRE only for HS mode */
> +		if (gi2c->is_hs_mode && !gi2c->config1_sent) {
> +			peripheral.set_config1 = 1;
> +			gi2c->config1_sent = true;
> +		} else {
> +			peripheral.set_config1 = 0;
I guess default should be already 0, isn't it ?> +		}
> +
> +		ret =  geni_i2c_gpi(gi2c, msgs, &config, &tx_addr, &tx_buf,
> +				    gi2c->is_hs_mode ? I2C_HS_WRITE : I2C_WRITE, gi2c->tx_c);
>   		if (ret)
>   			goto err;
>   
> +		/* CONFIG1 TRE is only for the TX channel; clear before RX call */
> +		peripheral.set_config1 = 0;
> +
>   		if (msgs[i].flags & I2C_M_RD) {
> -			ret =  geni_i2c_gpi(gi2c, msgs, &config,
> -					    &rx_addr, &rx_buf, I2C_READ, gi2c->rx_c);
> +			ret =  geni_i2c_gpi(gi2c, msgs, &config, &rx_addr, &rx_buf,
> +					    gi2c->is_hs_mode ? I2C_HS_READ : I2C_READ, gi2c->rx_c);
>   			if (ret)
>   				goto err;
>   
> @@ -1037,6 +1105,7 @@ static int setup_gpi_dma(struct geni_i2c_dev *gi2c)
>   static int geni_i2c_init(struct geni_i2c_dev *gi2c)
>   {
>   	u32 proto, tx_depth;
> +	unsigned long freq_out;
>   	bool fifo_disable;
>   	int ret;
>   
> @@ -1046,6 +1115,36 @@ static int geni_i2c_init(struct geni_i2c_dev *gi2c)
>   		return ret;
>   	}
>   
> +	/* HS mode requires QUPv3 version >= 4.3 and source clock=100 MHz */
Supported for Version >= 4.3 (instead of requires)> +	if 
(gi2c->is_hs_mode) {
> +		u32 hw_ver = geni_se_get_qup_hw_version(&gi2c->se);
> +		u32 major = GENI_SE_VERSION_MAJOR(hw_ver);
> +		u32 minor = GENI_SE_VERSION_MINOR(hw_ver);
> +
> +		if (major < QUP_I2C_HS_MIN_MAJOR ||
> +		    (major == QUP_I2C_HS_MIN_MAJOR && minor < QUP_I2C_HS_MIN_MINOR)) {
> +			dev_err(gi2c->se.dev,
> +				"QUP HW v%u.%u does not support I2C HS mode (requires >= %u.%u)\n",
I don't think we need to say required version, just reporting supported 
or not, should be good enough IMO.> +				major, minor,
> +				QUP_I2C_HS_MIN_MAJOR, QUP_I2C_HS_MIN_MINOR);
> +			ret = -EOPNOTSUPP;
> +			goto err;
> +		}
> +
> +		ret = geni_se_clk_freq_match(&gi2c->se, I2C_HS_SRC_CLK_FREQ,
> +					     &gi2c->dfs_index, &freq_out, false);
> +		if (ret) {
> +			dev_err(gi2c->se.dev, "Failed to get DFS index for HS mode: %d\n", ret);
> +			goto err;
> +		}
> +
> +		ret = clk_set_rate(gi2c->se.clk, freq_out);
> +		if (ret) {
> +			dev_err(gi2c->se.dev, "Failed to set HS mode clock rate: %d\n", ret);
> +			goto err;
> +		}
> +	}
> +
>   	proto = geni_se_read_proto(&gi2c->se);
>   	if (proto == GENI_SE_INVALID_PROTO) {
>   		ret = geni_load_se_firmware(&gi2c->se, GENI_SE_I2C);
>
diff mbox series

Patch

diff --git a/drivers/i2c/busses/i2c-qcom-geni.c b/drivers/i2c/busses/i2c-qcom-geni.c
index 658636c1ee0e..03c7bf95aec6 100644
--- a/drivers/i2c/busses/i2c-qcom-geni.c
+++ b/drivers/i2c/busses/i2c-qcom-geni.c
@@ -21,9 +21,21 @@ 
 #include <linux/spinlock.h>
 #include <linux/units.h>
 
+#define SE_GENI_CFG_REG68		0x210
+#define SE_I2C_HS_TLOW_REG		0x268
 #define SE_I2C_TX_TRANS_LEN		0x26c
 #define SE_I2C_RX_TRANS_LEN		0x270
 #define SE_I2C_SCL_COUNTERS		0x278
+#define SE_I2C_HS_TCYCLE_REG		0x27c
+
+/* I2C High-Speed mode definitions */
+#define I2C_HS_MODE_FREQ		3400000
+#define I2C_HS_SRC_CLK_FREQ		(100 * HZ_PER_MHZ)
+#define I2C_HS_TCYCLE_CNT		28
+#define I2C_HS_TLOW_CNT			38
+/* I2C HS mode support requires QUPv3 core version >= 4.3 per HPG */
+#define QUP_I2C_HS_MIN_MAJOR		4
+#define QUP_I2C_HS_MIN_MINOR		3
 
 #define SE_I2C_ERR  (M_CMD_OVERRUN_EN | M_ILLEGAL_CMD_EN | M_CMD_FAILURE_EN |\
 			M_GP_IRQ_1_EN | M_GP_IRQ_3_EN | M_GP_IRQ_4_EN)
@@ -36,6 +48,9 @@ 
 #define I2C_ADDR_ONLY		0x4
 #define I2C_BUS_CLEAR		0x6
 #define I2C_STOP_ON_BUS		0x7
+#define I2C_HS_WRITE		0xa
+#define I2C_HS_READ		0xb
+
 /* M_CMD params for I2C */
 #define PRE_CMD_DELAY		BIT(0)
 #define TIMESTAMP_BEFORE	BIT(1)
@@ -138,6 +153,9 @@  struct geni_i2c_dev {
 	u32 num_msgs;
 	struct geni_i2c_gpi_multi_desc_xfer i2c_multi_desc_config;
 	const struct geni_i2c_desc *dev_data;
+	bool is_hs_mode;
+	bool config1_sent;
+	u32 dfs_index;
 };
 
 struct geni_i2c_err_log {
@@ -162,9 +180,9 @@  static const struct geni_i2c_err_log gi2c_log[] = {
 struct geni_i2c_clk_fld {
 	u32	clk_freq_out;
 	u8	clk_div;
-	u8	t_high_cnt;
-	u8	t_low_cnt;
-	u8	t_cycle_cnt;
+	u16	t_high_cnt;
+	u16	t_low_cnt;
+	u16	t_cycle_cnt;
 };
 
 /*
@@ -194,10 +212,30 @@  static const struct geni_i2c_clk_fld geni_i2c_clk_map_32mhz[] = {
 	{}
 };
 
+/* source_clock = 100 MHz */
+static const struct geni_i2c_clk_fld geni_i2c_clk_map_100mhz[] = {
+	{ I2C_MAX_STANDARD_MODE_FREQ, 1, 449, 548, 998 },
+	{ I2C_MAX_FAST_MODE_FREQ, 1, 76, 167, 248 },
+	{ I2C_MAX_FAST_MODE_PLUS_FREQ, 1, 23, 59, 98 },
+	{}
+};
+
 static int geni_i2c_clk_map_idx(struct geni_i2c_dev *gi2c)
 {
 	const struct geni_i2c_clk_fld *itr;
 
+	/* Check if HS mode is requested */
+	if (gi2c->clk_freq_out == I2C_HS_MODE_FREQ) {
+		gi2c->is_hs_mode = true;
+		/* For HS mode, source clock should be 100 MHz */
+		itr = geni_i2c_clk_map_100mhz;
+		/* For HS mode, start with 1MHz for master code */
+		gi2c->clk_fld = &itr[2];
+		return 0;
+	}
+
+	gi2c->is_hs_mode = false;
+
 	if (clk_get_rate(gi2c->se.clk) == 32 * HZ_PER_MHZ)
 		itr = geni_i2c_clk_map_32mhz;
 	else
@@ -219,7 +257,12 @@  static int qcom_geni_i2c_conf(struct geni_se *se, unsigned long freq)
 	const struct geni_i2c_clk_fld *itr = gi2c->clk_fld;
 	u32 val;
 
-	writel_relaxed(0, gi2c->se.base + SE_GENI_CLK_SEL);
+	if (gi2c->is_hs_mode) {
+		writel_relaxed(I2C_HS_TCYCLE_CNT, gi2c->se.base + SE_I2C_HS_TCYCLE_REG);
+		writel_relaxed(I2C_HS_TLOW_CNT, gi2c->se.base + SE_I2C_HS_TLOW_REG);
+	}
+
+	writel_relaxed(gi2c->dfs_index, gi2c->se.base + SE_GENI_CLK_SEL);
 
 	val = (itr->clk_div << CLK_DIV_SHFT) | SER_CLK_EN;
 	writel_relaxed(val, gi2c->se.base + GENI_SER_M_CLK_CFG);
@@ -500,7 +543,9 @@  static int geni_i2c_rx_one_msg(struct geni_i2c_dev *gi2c, struct i2c_msg *msg,
 		geni_se_select_mode(se, GENI_SE_FIFO);
 
 	writel_relaxed(len, se->base + SE_I2C_RX_TRANS_LEN);
-	geni_se_setup_m_cmd(se, I2C_READ, m_param);
+
+	geni_se_setup_m_cmd(se, gi2c->is_hs_mode ? I2C_HS_READ : I2C_READ,
+			    m_param);
 
 	if (dma_buf && geni_se_rx_dma_prep(se, dma_buf, len, &rx_dma)) {
 		geni_se_select_mode(se, GENI_SE_FIFO);
@@ -539,7 +584,9 @@  static int geni_i2c_tx_one_msg(struct geni_i2c_dev *gi2c, struct i2c_msg *msg,
 		geni_se_select_mode(se, GENI_SE_FIFO);
 
 	writel_relaxed(len, se->base + SE_I2C_TX_TRANS_LEN);
-	geni_se_setup_m_cmd(se, I2C_WRITE, m_param);
+
+	geni_se_setup_m_cmd(se, gi2c->is_hs_mode ? I2C_HS_WRITE : I2C_WRITE,
+			    m_param);
 
 	if (dma_buf && geni_se_tx_dma_prep(se, dma_buf, len, &tx_dma)) {
 		geni_se_select_mode(se, GENI_SE_FIFO);
@@ -689,7 +736,8 @@  static int geni_i2c_gpi(struct geni_i2c_dev *gi2c, struct i2c_msg msgs[],
 	 * programming an extra TX DMA TRE that would cause an unintended
 	 * write cycle on the I2C bus before the actual read operation.
 	 */
-	if (op == I2C_WRITE && msgs[msg_idx].flags & I2C_M_RD) {
+	if ((op == I2C_WRITE || op == I2C_HS_WRITE) &&
+	    msgs[msg_idx].flags & I2C_M_RD) {
 		peripheral->multi_msg = true;
 		goto skip_tx_dma_map;
 	}
@@ -700,7 +748,7 @@  static int geni_i2c_gpi(struct geni_i2c_dev *gi2c, struct i2c_msg msgs[],
 		goto out;
 	}
 
-	if (op == I2C_WRITE)
+	if (op == I2C_WRITE || op == I2C_HS_WRITE)
 		map_dirn = DMA_TO_DEVICE;
 	else
 		map_dirn = DMA_FROM_DEVICE;
@@ -737,7 +785,7 @@  static int geni_i2c_gpi(struct geni_i2c_dev *gi2c, struct i2c_msg msgs[],
 	peripheral->set_config = 0;
 	peripheral->multi_msg = true;
 
-	if (op == I2C_WRITE)
+	if (op == I2C_WRITE || op == I2C_HS_WRITE)
 		dma_dirn = DMA_MEM_TO_DEV;
 	else
 		dma_dirn = DMA_DEV_TO_MEM;
@@ -760,7 +808,8 @@  static int geni_i2c_gpi(struct geni_i2c_dev *gi2c, struct i2c_msg msgs[],
 	desc->callback_result = i2c_gpi_cb_result;
 	desc->callback_param = gi2c;
 
-	if (!((msgs[msg_idx].flags & I2C_M_RD) && op == I2C_WRITE))
+	if (!((msgs[msg_idx].flags & I2C_M_RD) &&
+	    (op == I2C_WRITE || op == I2C_HS_WRITE)))
 		gi2c_gpi_xfer->msg_idx_cnt++;
 
 	cookie = dmaengine_submit(desc);
@@ -827,6 +876,7 @@  static int geni_i2c_gpi_xfer(struct geni_i2c_dev *gi2c, struct i2c_msg msgs[], i
 	peripheral.high_count = itr->t_high_cnt;
 	peripheral.low_count = itr->t_low_cnt;
 	peripheral.clk_div = itr->clk_div;
+	peripheral.clk_src = gi2c->dfs_index;
 	peripheral.set_config = 1;
 	peripheral.multi_msg = false;
 
@@ -836,6 +886,13 @@  static int geni_i2c_gpi_xfer(struct geni_i2c_dev *gi2c, struct i2c_msg msgs[], i
 
 	gi2c->num_msgs = num;
 	gi2c->is_tx_multi_desc_xfer = false;
+	gi2c->config1_sent = false;
+
+	/* Initialize config1 TRE settings for HS mode */
+	if (gi2c->is_hs_mode) {
+		peripheral.config1.tcycle_cnt = I2C_HS_TCYCLE_CNT;
+		peripheral.config1.tlow_cnt = I2C_HS_TLOW_CNT;
+	}
 
 	tx_multi_xfer = &gi2c->i2c_multi_desc_config;
 	memset(tx_multi_xfer, 0, sizeof(struct geni_i2c_gpi_multi_desc_xfer));
@@ -883,14 +940,25 @@  static int geni_i2c_gpi_xfer(struct geni_i2c_dev *gi2c, struct i2c_msg msgs[], i
 		if (i > 0 && (!(msgs[i].flags & I2C_M_RD)))
 			peripheral.multi_msg = false;
 
-		ret =  geni_i2c_gpi(gi2c, msgs, &config,
-				    &tx_addr, &tx_buf, I2C_WRITE, gi2c->tx_c);
+		/* Set config1 TRE only for HS mode */
+		if (gi2c->is_hs_mode && !gi2c->config1_sent) {
+			peripheral.set_config1 = 1;
+			gi2c->config1_sent = true;
+		} else {
+			peripheral.set_config1 = 0;
+		}
+
+		ret =  geni_i2c_gpi(gi2c, msgs, &config, &tx_addr, &tx_buf,
+				    gi2c->is_hs_mode ? I2C_HS_WRITE : I2C_WRITE, gi2c->tx_c);
 		if (ret)
 			goto err;
 
+		/* CONFIG1 TRE is only for the TX channel; clear before RX call */
+		peripheral.set_config1 = 0;
+
 		if (msgs[i].flags & I2C_M_RD) {
-			ret =  geni_i2c_gpi(gi2c, msgs, &config,
-					    &rx_addr, &rx_buf, I2C_READ, gi2c->rx_c);
+			ret =  geni_i2c_gpi(gi2c, msgs, &config, &rx_addr, &rx_buf,
+					    gi2c->is_hs_mode ? I2C_HS_READ : I2C_READ, gi2c->rx_c);
 			if (ret)
 				goto err;
 
@@ -1037,6 +1105,7 @@  static int setup_gpi_dma(struct geni_i2c_dev *gi2c)
 static int geni_i2c_init(struct geni_i2c_dev *gi2c)
 {
 	u32 proto, tx_depth;
+	unsigned long freq_out;
 	bool fifo_disable;
 	int ret;
 
@@ -1046,6 +1115,36 @@  static int geni_i2c_init(struct geni_i2c_dev *gi2c)
 		return ret;
 	}
 
+	/* HS mode requires QUPv3 version >= 4.3 and source clock=100 MHz */
+	if (gi2c->is_hs_mode) {
+		u32 hw_ver = geni_se_get_qup_hw_version(&gi2c->se);
+		u32 major = GENI_SE_VERSION_MAJOR(hw_ver);
+		u32 minor = GENI_SE_VERSION_MINOR(hw_ver);
+
+		if (major < QUP_I2C_HS_MIN_MAJOR ||
+		    (major == QUP_I2C_HS_MIN_MAJOR && minor < QUP_I2C_HS_MIN_MINOR)) {
+			dev_err(gi2c->se.dev,
+				"QUP HW v%u.%u does not support I2C HS mode (requires >= %u.%u)\n",
+				major, minor,
+				QUP_I2C_HS_MIN_MAJOR, QUP_I2C_HS_MIN_MINOR);
+			ret = -EOPNOTSUPP;
+			goto err;
+		}
+
+		ret = geni_se_clk_freq_match(&gi2c->se, I2C_HS_SRC_CLK_FREQ,
+					     &gi2c->dfs_index, &freq_out, false);
+		if (ret) {
+			dev_err(gi2c->se.dev, "Failed to get DFS index for HS mode: %d\n", ret);
+			goto err;
+		}
+
+		ret = clk_set_rate(gi2c->se.clk, freq_out);
+		if (ret) {
+			dev_err(gi2c->se.dev, "Failed to set HS mode clock rate: %d\n", ret);
+			goto err;
+		}
+	}
+
 	proto = geni_se_read_proto(&gi2c->se);
 	if (proto == GENI_SE_INVALID_PROTO) {
 		ret = geni_load_se_firmware(&gi2c->se, GENI_SE_I2C);