Re: [PATCH 1/2] i2c: tegra: Add logic to support different register offsets

Jon Hunter
2025-10-24T15:11:18Z

On 01/10/2025 16:36, Kartik Rajput wrote:
> Tegra410 use different offsets for existing I2C registers, update
> the logic to use appropriate offsets per SoC.
>
> Signed-off-by: Kartik Rajput <kkartik@nvidia.com>
> ---
> drivers/i2c/busses/i2c-tegra.c | 499 ++++++++++++++++++++++-----------
> 1 file changed, 334 insertions(+), 165 deletions(-)
>
> diff --git a/drivers/i2c/busses/i2c-tegra.c b/drivers/i2c/busses/i2c-tegra.c
> index 038809264526..1e26d67cbd30 100644
> --- a/drivers/i2c/busses/i2c-tegra.c
> +++ b/drivers/i2c/busses/i2c-tegra.c

...

> @@ -428,8 +519,8 @@ static int tegra_i2c_mutex_lock(struct tegra_i2c_dev *i2c_dev)
>
> if (i2c_dev->atomic_mode)
> ret = read_poll_timeout_atomic(tegra_i2c_mutex_trylock, locked, locked,
> - USEC_PER_MSEC, I2C_SW_MUTEX_TIMEOUT_US,
> - false, i2c_dev);
> + USEC_PER_MSEC, I2C_SW_MUTEX_TIMEOUT_US,
> + false, i2c_dev);

The above appears to be changed accidently?

> @@ -1062,13 +1151,13 @@ static irqreturn_t tegra_i2c_isr(int irq, void *dev_id)
> tegra_i2c_fill_tx_fifo(i2c_dev);
> else
> tegra_i2c_mask_irq(i2c_dev,
> - I2C_INT_TX_FIFO_DATA_REQ);
> + I2C_INT_TX_FIFO_DATA_REQ);

Same here.

> }
> }
>
> - i2c_writel(i2c_dev, status, I2C_INT_STATUS);
> + i2c_writel(i2c_dev, status, i2c_dev->hw->regs->int_status);
> if (IS_DVC(i2c_dev))
> - dvc_writel(i2c_dev, DVC_STATUS_I2C_DONE_INTR, DVC_STATUS);
> + dvc_writel(i2c_dev, DVC_STATUS_I2C_DONE_INTR, i2c_dev->hw->regs->dvc_status);
>
> /*
> * During message read XFER_COMPLETE interrupt is triggered prior to
> @@ -1104,10 +1193,10 @@ static irqreturn_t tegra_i2c_isr(int irq, void *dev_id)
> if (i2c_dev->hw->supports_bus_clear)
> tegra_i2c_mask_irq(i2c_dev, I2C_INT_BUS_CLR_DONE);
>
> - i2c_writel(i2c_dev, status, I2C_INT_STATUS);
> + i2c_writel(i2c_dev, status, i2c_dev->hw->regs->int_status);
>
> if (IS_DVC(i2c_dev))
> - dvc_writel(i2c_dev, DVC_STATUS_I2C_DONE_INTR, DVC_STATUS);
> + dvc_writel(i2c_dev, DVC_STATUS_I2C_DONE_INTR, i2c_dev->hw->regs->dvc_status);
>
> if (i2c_dev->dma_mode) {
> dmaengine_terminate_async(i2c_dev->dma_chan);
> @@ -1120,16 +1209,16 @@ static irqreturn_t tegra_i2c_isr(int irq, void *dev_id)
> }
>
> static void tegra_i2c_config_fifo_trig(struct tegra_i2c_dev *i2c_dev,
> - size_t len)
> + size_t len)

And here.

> {
> struct dma_slave_config slv_config = {0};
> u32 val, reg, dma_burst, reg_offset;
> int err;
>
> if (i2c_dev->hw->has_mst_fifo)
> - reg = I2C_MST_FIFO_CONTROL;
> + reg = i2c_dev->hw->regs->mst_fifo_control;
> else
> - reg = I2C_FIFO_CONTROL;
> + reg = i2c_dev->hw->regs->fifo_control;
>
> if (i2c_dev->dma_mode) {
> if (len & 0xF)
> @@ -1140,7 +1229,7 @@ static void tegra_i2c_config_fifo_trig(struct tegra_i2c_dev *i2c_dev,
> dma_burst = 8;
>
> if (i2c_dev->msg_read) {
> - reg_offset = tegra_i2c_reg_addr(i2c_dev, I2C_RX_FIFO);
> + reg_offset = i2c_dev->hw->regs->rx_fifo;
>
> slv_config.src_addr = i2c_dev->base_phys + reg_offset;
> slv_config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
> @@ -1151,7 +1240,7 @@ static void tegra_i2c_config_fifo_trig(struct tegra_i2c_dev *i2c_dev,
> else
> val = I2C_FIFO_CONTROL_RX_TRIG(dma_burst);
> } else {
> - reg_offset = tegra_i2c_reg_addr(i2c_dev, I2C_TX_FIFO);
> + reg_offset = i2c_dev->hw->regs->tx_fifo;
>
> slv_config.dst_addr = i2c_dev->base_phys + reg_offset;
> slv_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
> @@ -1187,14 +1276,14 @@ static void tegra_i2c_config_fifo_trig(struct tegra_i2c_dev *i2c_dev,
> }
>
> static unsigned long tegra_i2c_poll_completion(struct tegra_i2c_dev *i2c_dev,
> - struct completion *complete,
> - unsigned int timeout_ms)
> + struct completion *complete,
> + unsigned int timeout_ms)

And here.

> {
> ktime_t ktime = ktime_get();
> ktime_t ktimeout = ktime_add_ms(ktime, timeout_ms);
>
> do {
> - u32 status = i2c_readl(i2c_dev, I2C_INT_STATUS);
> + u32 status = i2c_readl(i2c_dev, i2c_dev->hw->regs->int_status);
>
> if (status)
> tegra_i2c_isr(i2c_dev->irq, i2c_dev);
> @@ -1253,14 +1342,14 @@ static int tegra_i2c_issue_bus_clear(struct i2c_adapter *adap)
>
> val = FIELD_PREP(I2C_BC_SCLK_THRESHOLD, 9) | I2C_BC_STOP_COND |
> I2C_BC_TERMINATE;
> - i2c_writel(i2c_dev, val, I2C_BUS_CLEAR_CNFG);
> + i2c_writel(i2c_dev, val, i2c_dev->hw->regs->bus_clear_cnfg);
>
> err = tegra_i2c_wait_for_config_load(i2c_dev);
> if (err)
> return err;
>
> val |= I2C_BC_ENABLE;
> - i2c_writel(i2c_dev, val, I2C_BUS_CLEAR_CNFG);
> + i2c_writel(i2c_dev, val, i2c_dev->hw->regs->bus_clear_cnfg);
> tegra_i2c_unmask_irq(i2c_dev, I2C_INT_BUS_CLR_DONE);
>
> time_left = tegra_i2c_wait_completion(i2c_dev, &i2c_dev->msg_complete, 50);
> @@ -1271,7 +1360,7 @@ static int tegra_i2c_issue_bus_clear(struct i2c_adapter *adap)
> return -ETIMEDOUT;
> }
>
> - val = i2c_readl(i2c_dev, I2C_BUS_CLEAR_STATUS);
> + val = i2c_readl(i2c_dev, i2c_dev->hw->regs->bus_clear_status);
> if (!(val & I2C_BC_STATUS)) {
> dev_err(i2c_dev->dev, "un-recovered arbitration lost\n");
> return -EIO;
> @@ -1281,29 +1370,29 @@ static int tegra_i2c_issue_bus_clear(struct i2c_adapter *adap)
> }
>
> static void tegra_i2c_push_packet_header(struct tegra_i2c_dev *i2c_dev,
> - struct i2c_msg *msg,
> - enum msg_end_type end_state)
> + struct i2c_msg *msg,
> + enum msg_end_type end_state)

And here.

> {
> u32 *dma_buf = i2c_dev->dma_buf;
> u32 packet_header;
>
> packet_header = FIELD_PREP(PACKET_HEADER0_HEADER_SIZE, 0) |
> FIELD_PREP(PACKET_HEADER0_PROTOCOL,
> - PACKET_HEADER0_PROTOCOL_I2C) |
> + PACKET_HEADER0_PROTOCOL_I2C) |

And here.

> FIELD_PREP(PACKET_HEADER0_CONT_ID, i2c_dev->cont_id) |
> FIELD_PREP(PACKET_HEADER0_PACKET_ID, 1);
>
> if (i2c_dev->dma_mode && !i2c_dev->msg_read)
> *dma_buf++ = packet_header;
> else
> - i2c_writel(i2c_dev, packet_header, I2C_TX_FIFO);
> + i2c_writel(i2c_dev, packet_header, i2c_dev->hw->regs->tx_fifo);
>
> packet_header = i2c_dev->msg_len - 1;
>
> if (i2c_dev->dma_mode && !i2c_dev->msg_read)
> *dma_buf++ = packet_header;
> else
> - i2c_writel(i2c_dev, packet_header, I2C_TX_FIFO);
> + i2c_writel(i2c_dev, packet_header, i2c_dev->hw->regs->tx_fifo);
>
> packet_header = I2C_HEADER_IE_ENABLE;
>
> @@ -1331,7 +1420,7 @@ static void tegra_i2c_push_packet_header(struct tegra_i2c_dev *i2c_dev,
> if (i2c_dev->dma_mode && !i2c_dev->msg_read)
> *dma_buf++ = packet_header;
> else
> - i2c_writel(i2c_dev, packet_header, I2C_TX_FIFO);
> + i2c_writel(i2c_dev, packet_header, i2c_dev->hw->regs->tx_fifo);
> }
>
> static int tegra_i2c_error_recover(struct tegra_i2c_dev *i2c_dev,
> @@ -1361,8 +1450,8 @@ static int tegra_i2c_error_recover(struct tegra_i2c_dev *i2c_dev,
> }
>
> static int tegra_i2c_xfer_msg(struct tegra_i2c_dev *i2c_dev,
> - struct i2c_msg *msg,
> - enum msg_end_type end_state)
> + struct i2c_msg *msg,
> + enum msg_end_type end_state)

And here.

> {
> unsigned long time_left, xfer_time = 100;
> size_t xfer_size;
> @@ -1413,7 +1502,7 @@ static int tegra_i2c_xfer_msg(struct tegra_i2c_dev *i2c_dev,
> * Total bits = 9 bits per byte (including ACK bit) + Start & stop bits
> */
> xfer_time += DIV_ROUND_CLOSEST(((xfer_size * 9) + 2) * MSEC_PER_SEC,
> - i2c_dev->timings.bus_freq_hz);
> + i2c_dev->timings.bus_freq_hz);

And here.

>
> int_mask = I2C_INT_NO_ACK | I2C_INT_ARBITRATION_LOST;
> tegra_i2c_unmask_irq(i2c_dev, int_mask);
> @@ -1452,12 +1541,12 @@ static int tegra_i2c_xfer_msg(struct tegra_i2c_dev *i2c_dev,
>
> tegra_i2c_unmask_irq(i2c_dev, int_mask);
> dev_dbg(i2c_dev->dev, "unmasked IRQ: %02x\n",
> - i2c_readl(i2c_dev, I2C_INT_MASK));
> + i2c_readl(i2c_dev, i2c_dev->hw->regs->int_mask));
>
> if (i2c_dev->dma_mode) {
> time_left = tegra_i2c_wait_completion(i2c_dev,
> - &i2c_dev->dma_complete,
> - xfer_time);
> + &i2c_dev->dma_complete,
> + xfer_time);

And here.

>
> /*
> * Synchronize DMA first, since dmaengine_terminate_sync()
> @@ -1477,7 +1566,7 @@ static int tegra_i2c_xfer_msg(struct tegra_i2c_dev *i2c_dev,
> }
>
> time_left = tegra_i2c_wait_completion(i2c_dev, &i2c_dev->msg_complete,
> - xfer_time);
> + xfer_time);

And here.

>
> tegra_i2c_mask_irq(i2c_dev, int_mask);
>
> @@ -1623,7 +1712,75 @@ static const struct tegra_i2c_hw_feature tegra20_i2c_hw = {
> .has_interface_timing_reg = false,
> .has_hs_mode_support = false,
> .has_mutex = false,
> + .is_dvc = false,
> + .is_vi = false,
> + .regs = &tegra20_i2c_regs,
> +};
> +
> +#if IS_ENABLED(CONFIG_ARCH_TEGRA_2x_SOC)
> +static const struct tegra_i2c_hw_feature tegra20_dvc_i2c_hw = {
> + .has_continue_xfer_support = false,
> + .has_per_pkt_xfer_complete_irq = false,
> + .clk_divisor_hs_mode = 3,
> + .clk_divisor_std_mode = 0,
> + .clk_divisor_fast_mode = 0,
> + .clk_divisor_fast_plus_mode = 0,
> + .has_config_load_reg = false,
> + .has_multi_master_mode = false,
> + .has_slcg_override_reg = false,
> + .has_mst_fifo = false,
> + .has_mst_reset = false,
> + .quirks = &tegra_i2c_quirks,
> + .supports_bus_clear = false,
> + .has_apb_dma = true,
> + .tlow_std_mode = 0x4,
> + .thigh_std_mode = 0x2,
> + .tlow_fast_fastplus_mode = 0x4,
> + .thigh_fast_fastplus_mode = 0x2,
> + .setup_hold_time_std_mode = 0x0,
> + .setup_hold_time_fast_fast_plus_mode = 0x0,
> + .setup_hold_time_hs_mode = 0x0,
> + .has_interface_timing_reg = false,
> + .has_hs_mode_support = false,
> + .has_mutex = false,
> + .is_dvc = true,
> + .is_vi = false,
> + .regs = &tegra20_i2c_regs_dvc,
> +};
> +#endif

Seems like we are duplicating a lot of the above. I am not sure if it
would be better to have a higher level struct so that the common
features bit is not duplicated? I guess that we are only adding two more
of these structures so may be not a big deal?

Also, I would prefer this adding the above tegra20_dvc_i2c_hw was a
separate patch to this because this is a large patch and it would be
clear to add these in their own patch.

> static void tegra_i2c_parse_dt(struct tegra_i2c_dev *i2c_dev)
> {
> - struct device_node *np = i2c_dev->dev->of_node;
> bool multi_mode;
>
> i2c_parse_fw_timings(i2c_dev->dev, &i2c_dev->timings, true);
>
> multi_mode = device_property_read_bool(i2c_dev->dev, "multi-master");
> i2c_dev->multimaster_mode = multi_mode;
> -
> - if (IS_ENABLED(CONFIG_ARCH_TEGRA_2x_SOC) &&
> - of_device_is_compatible(np, "nvidia,tegra20-i2c-dvc"))
> - i2c_dev->is_dvc = true;
> -
> - if (IS_ENABLED(CONFIG_ARCH_TEGRA_210_SOC) &&
> - of_device_is_compatible(np, "nvidia,tegra210-i2c-vi"))
> - i2c_dev->is_vi = true;
> }

Thinking about it, wouldn't it be simpler to keep the above? We could
also populate the regs offset dynamically too. Would this save the
duplication? May be we could only do this for Tegra20 and Tegra210 devices?

Jon

--
nvpublic