[PATCH v2 3/3] i2c: designware: Add ti,tda54-i2c support
From: Aniket Limaye
Date: Thu Sep 24 2026 - 19:42:31 EST
Compared to the existing DW_apb_i2c layout [1], DWC_i2c present in TDA54
SoC [0] differs enough to need MODEL_TI_TDA54-gated special-casing in a
few places:
- Add new register offsets table for TI TDA54 SoC.
- The DW_IC_CON register bitfields are also updated.
- Interrupts are acknowledged by writing a bitmask to a single CLR_INTR
register instead of reading N dedicated CLR_* registers;
i2c_dw_ack_intr() picks the right method based on dev->flags.
- One HCNT/LCNT register pair is shared between standard and fast speed
instead of having one pair each; i2c_dw_write_timings() writes
whichever value set matches the configured speed.
- No COMP_PARAM_1 register, so FIFO depth and high-speed-mode support
can't be autodetected: FIFO depth now comes from the required
snps,tx-fifo-depth/snps,rx-fifo-depth DT properties, and the
high-speed capability check is skipped.
- No defined CON.RESTART_EN bit; treated as always set.
- TDA54 SoC does not have SMBus block, skip accesses to those registers.
[0]: DWC_i2c_reference.pdf
[1]: DW_apb_i2c_databook.pdf
Signed-off-by: Aniket Limaye <a-limaye@xxxxxx>
---
Changes in v2:
- Rename compatible/flag: snps,dwc-i2c -> ti,tda54-i2c,
MODEL_DWC_I2C -> MODEL_TI_TDA54.
- Remove i2c_dwc_has_smbus(); just skip the SMBUS_INTR_MASK write under
MODEL_TI_TDA54.
- Link to v1: https://lore.kernel.org/r/20260919-tda54-upstream-i2c-v1-3-b0b9f77be18b@xxxxxx/
---
drivers/i2c/busses/i2c-designware-common.c | 128 +++++++++++++++++++++++++---
drivers/i2c/busses/i2c-designware-core.h | 22 ++++-
drivers/i2c/busses/i2c-designware-master.c | 26 +++---
drivers/i2c/busses/i2c-designware-platdrv.c | 1 +
4 files changed, 153 insertions(+), 24 deletions(-)
diff --git a/drivers/i2c/busses/i2c-designware-common.c b/drivers/i2c/busses/i2c-designware-common.c
index eeeb1f189205..a0a54d4445df 100644
--- a/drivers/i2c/busses/i2c-designware-common.c
+++ b/drivers/i2c/busses/i2c-designware-common.c
@@ -72,6 +72,40 @@ static const char *const abort_sources[] = {
"incorrect slave-transmitter mode configuration",
};
+/* "ti,tda54-i2c" block-based register layout */
+#define DWC_ADV_OPERATION_BLK_OFFSET 0x00
+#define DWC_ADV_I2C_BLK_OFFSET 0x28
+#define DWC_ADV_FIFO_INTR_BLK_OFFSET 0xbc
+#define DWC_ADV_DEBUG_BLK_OFFSET 0xf8
+
+static const u32 tda54_i2c_reg_offsets[DW_IC_REG_MAX] = {
+ [DW_IC_ENABLE] = DWC_ADV_OPERATION_BLK_OFFSET + 0x04,
+ [DW_IC_CON] = DWC_ADV_I2C_BLK_OFFSET + 0x04,
+ [DW_IC_TAR] = DWC_ADV_I2C_BLK_OFFSET + 0x08,
+ [DW_IC_SAR] = DWC_ADV_I2C_BLK_OFFSET + 0x0c,
+ [DW_IC_DATA_CMD] = DWC_ADV_I2C_BLK_OFFSET + 0x58,
+ [DW_IC_SS_SCL_HCNT] = DWC_ADV_I2C_BLK_OFFSET + 0x24, /* shared SS/FS pair */
+ [DW_IC_SS_SCL_LCNT] = DWC_ADV_I2C_BLK_OFFSET + 0x28,
+ [DW_IC_FS_SCL_HCNT] = DWC_ADV_I2C_BLK_OFFSET + 0x24,
+ [DW_IC_FS_SCL_LCNT] = DWC_ADV_I2C_BLK_OFFSET + 0x28,
+ [DW_IC_HS_SCL_HCNT] = DWC_ADV_I2C_BLK_OFFSET + 0x2c,
+ [DW_IC_HS_SCL_LCNT] = DWC_ADV_I2C_BLK_OFFSET + 0x30,
+ [DW_IC_SDA_HOLD] = DWC_ADV_I2C_BLK_OFFSET + 0x34,
+ [DW_IC_RX_TL] = DWC_ADV_I2C_BLK_OFFSET + 0x5c,
+ [DW_IC_TX_TL] = DWC_ADV_I2C_BLK_OFFSET + 0x60,
+ [DW_IC_INTR_STAT] = DWC_ADV_FIFO_INTR_BLK_OFFSET + 0x04,
+ [DW_IC_INTR_MASK] = DWC_ADV_FIFO_INTR_BLK_OFFSET + 0x08,
+ [DW_IC_RAW_INTR_STAT] = DWC_ADV_FIFO_INTR_BLK_OFFSET + 0x0c,
+ [DW_IC_CLR_INTR] = DWC_ADV_FIFO_INTR_BLK_OFFSET + 0x10,
+ [DW_IC_STATUS] = DWC_ADV_FIFO_INTR_BLK_OFFSET + 0x1c,
+ [DW_IC_TXFLR] = DWC_ADV_FIFO_INTR_BLK_OFFSET + 0x20,
+ [DW_IC_RXFLR] = DWC_ADV_FIFO_INTR_BLK_OFFSET + 0x24,
+ [DW_IC_TX_ABRT_SOURCE] = DWC_ADV_FIFO_INTR_BLK_OFFSET + 0x18,
+ [DW_IC_ENABLE_STATUS] = DWC_ADV_FIFO_INTR_BLK_OFFSET + 0x14,
+ [DW_IC_COMP_VERSION] = DWC_ADV_DEBUG_BLK_OFFSET + 0x08,
+ [DW_IC_COMP_TYPE] = DWC_ADV_DEBUG_BLK_OFFSET + 0x0c,
+};
+
static const struct dw_i2c_con_bits dw_i2c_con_bits = {
.master = DW_IC_CON_MASTER,
.speed_std = DW_IC_CON_SPEED_STD,
@@ -88,6 +122,26 @@ static const struct dw_i2c_con_bits dw_i2c_con_bits = {
.bus_clear_ctrl = DW_IC_CON_BUS_CLEAR_CTRL,
};
+/*
+ * DWC_IC_CTRL bit layout for "ti,tda54-i2c".
+ * There is no defined bit for RESTART_EN or SLAVE_DISABLE on this IP.
+ */
+static const struct dw_i2c_con_bits dwc_adv_i2c_con_bits = {
+ .master = BIT(0),
+ .speed_std = (1 << 4),
+ .speed_fast = (2 << 4),
+ .speed_high = (3 << 4),
+ .speed_mask = GENMASK(5, 4),
+ .bit10_slave = BIT(8),
+ .bit10_master = BIT(9),
+ .restart_en = 0,
+ .slave_disable = 0,
+ .stop_det_ifaddressed = BIT(10),
+ .tx_empty_ctrl = BIT(11),
+ .rx_fifo_full_hld_ctrl = BIT(12),
+ .bus_clear_ctrl = 0,
+};
+
/* "snps,designware-i2c": dedicated read-to-clear register ID per logical interrupt */
static const u32 dw_i2c_intr_clr[DW_INTR_IDX_MAX] = {
[DW_INTR_IDX_INTR] = DW_IC_CLR_INTR,
@@ -103,6 +157,21 @@ static const u32 dw_i2c_intr_clr[DW_INTR_IDX_MAX] = {
[DW_INTR_IDX_GEN_CALL] = DW_IC_CLR_GEN_CALL,
};
+/* "ti,tda54-i2c" CLR_INTR bit to write per logical interrupt */
+static const u32 dwc_adv_i2c_intr_clr[DW_INTR_IDX_MAX] = {
+ [DW_INTR_IDX_INTR] = DWC_ADV_IC_INTR_CLR_INTR,
+ [DW_INTR_IDX_RX_UNDER] = DWC_ADV_IC_INTR_CLR_RX_UNDER,
+ [DW_INTR_IDX_RX_OVER] = DWC_ADV_IC_INTR_CLR_RX_OVER,
+ [DW_INTR_IDX_TX_OVER] = DWC_ADV_IC_INTR_CLR_TX_OVER,
+ [DW_INTR_IDX_RD_REQ] = DWC_ADV_IC_INTR_CLR_RD_REQ,
+ [DW_INTR_IDX_TX_ABRT] = DWC_ADV_IC_INTR_CLR_TX_ABRT,
+ [DW_INTR_IDX_RX_DONE] = DWC_ADV_IC_INTR_CLR_RX_DONE,
+ [DW_INTR_IDX_ACTIVITY] = DWC_ADV_IC_INTR_CLR_ACTIVITY,
+ [DW_INTR_IDX_STOP_DET] = DWC_ADV_IC_INTR_CLR_STOP_DET,
+ [DW_INTR_IDX_START_DET] = DWC_ADV_IC_INTR_CLR_START_DET,
+ [DW_INTR_IDX_GEN_CALL] = DWC_ADV_IC_INTR_CLR_GEN_CALL,
+};
+
/**
* i2c_dw_select_variant() - Pick the register offset table, CON-register bit
* layout and interrupt-ack mapping matching this device's IP variant
@@ -110,9 +179,15 @@ static const u32 dw_i2c_intr_clr[DW_INTR_IDX_MAX] = {
*/
void i2c_dw_select_variant(struct dw_i2c_dev *dev)
{
- dev->regs = dw_i2c_reg_offsets;
- dev->con_bits = &dw_i2c_con_bits;
- dev->intr_clr = dw_i2c_intr_clr;
+ if (dev->flags & MODEL_TI_TDA54) {
+ dev->regs = tda54_i2c_reg_offsets;
+ dev->con_bits = &dwc_adv_i2c_con_bits;
+ dev->intr_clr = dwc_adv_i2c_intr_clr;
+ } else {
+ dev->regs = dw_i2c_reg_offsets;
+ dev->con_bits = &dw_i2c_con_bits;
+ dev->intr_clr = dw_i2c_intr_clr;
+ }
}
EXPORT_SYMBOL_GPL(i2c_dw_select_variant);
@@ -429,13 +504,27 @@ static void i2c_dw_configure_mode(struct dw_i2c_dev *dev, int mode)
static void i2c_dw_write_timings(struct dw_i2c_dev *dev)
{
- /* Write standard speed timing parameters */
- regmap_write(dev->map, DW_IC_SS_SCL_HCNT, dev->ss_hcnt);
- regmap_write(dev->map, DW_IC_SS_SCL_LCNT, dev->ss_lcnt);
-
- /* Write fast mode/fast mode plus timing parameters */
- regmap_write(dev->map, DW_IC_FS_SCL_HCNT, dev->fs_hcnt);
- regmap_write(dev->map, DW_IC_FS_SCL_LCNT, dev->fs_lcnt);
+ if (dev->flags & MODEL_TI_TDA54) {
+ /*
+ * Only one HCNT/LCNT register pair backs both speeds on
+ * this IP -- write whichever value set matches master_cfg.
+ */
+ if ((dev->master_cfg & dev->con_bits->speed_mask) == dev->con_bits->speed_std) {
+ regmap_write(dev->map, DW_IC_SS_SCL_HCNT, dev->ss_hcnt);
+ regmap_write(dev->map, DW_IC_SS_SCL_LCNT, dev->ss_lcnt);
+ } else {
+ regmap_write(dev->map, DW_IC_FS_SCL_HCNT, dev->fs_hcnt);
+ regmap_write(dev->map, DW_IC_FS_SCL_LCNT, dev->fs_lcnt);
+ }
+ } else {
+ /* Write standard speed timing parameters */
+ regmap_write(dev->map, DW_IC_SS_SCL_HCNT, dev->ss_hcnt);
+ regmap_write(dev->map, DW_IC_SS_SCL_LCNT, dev->ss_lcnt);
+
+ /* Write fast mode/fast mode plus timing parameters */
+ regmap_write(dev->map, DW_IC_FS_SCL_HCNT, dev->fs_hcnt);
+ regmap_write(dev->map, DW_IC_FS_SCL_LCNT, dev->fs_lcnt);
+ }
/* Write high speed timing parameters */
regmap_write(dev->map, DW_IC_HS_SCL_HCNT, dev->hs_hcnt);
@@ -490,8 +579,10 @@ int i2c_dw_init(struct dw_i2c_dev *dev)
* Mask SMBus interrupts to block storms from broken
* firmware that leaves IC_SMBUS=1; the handler never
* services them.
+ * TDA54 SoC has no SMBus block, so skip it.
*/
- regmap_write(dev->map, DW_IC_SMBUS_INTR_MASK, 0);
+ if (!(dev->flags & MODEL_TI_TDA54))
+ regmap_write(dev->map, DW_IC_SMBUS_INTR_MASK, 0);
i2c_dw_write_timings(dev);
@@ -535,6 +626,14 @@ int i2c_dw_fw_parse_and_configure(struct dw_i2c_dev *dev)
if (device_property_read_u32(device, "snps,bus-capacitance-pf", &dev->bus_capacitance_pF))
dev->bus_capacitance_pF = DW_IC_DEFAULT_BUS_CAPACITANCE_pF;
+ if (dev->flags & MODEL_TI_TDA54) {
+ device_property_read_u32(device, "snps,tx-fifo-depth", &dev->tx_fifo_depth);
+ device_property_read_u32(device, "snps,rx-fifo-depth", &dev->rx_fifo_depth);
+
+ if (!dev->tx_fifo_depth || !dev->rx_fifo_depth)
+ return -EINVAL;
+ }
+
dev->clk_freq_optimized = device_property_read_bool(device, "snps,clk-freq-optimized");
/* Mobileye controllers do not hold the clock on empty FIFO */
@@ -851,6 +950,13 @@ static int i2c_dw_set_fifo_size(struct dw_i2c_dev *dev)
return 0;
}
+ /*
+ * DW_IC_COMP_PARAM_1 not implemented on this IP;
+ * fifo depth set in i2c_dw_fw_parse_and_configure().
+ */
+ if (dev->flags & MODEL_TI_TDA54)
+ return 0;
+
/*
* Try to detect the FIFO depth if not set by interface driver,
* the depth could be from 2 to 256 from HW spec.
diff --git a/drivers/i2c/busses/i2c-designware-core.h b/drivers/i2c/busses/i2c-designware-core.h
index 4c782594ab4e..bd96abf63c39 100644
--- a/drivers/i2c/busses/i2c-designware-core.h
+++ b/drivers/i2c/busses/i2c-designware-core.h
@@ -79,6 +79,18 @@ enum dw_i2c_intr_idx {
DW_INTR_IDX_MAX,
};
+#define DWC_ADV_IC_INTR_CLR_INTR BIT(0)
+#define DWC_ADV_IC_INTR_CLR_RX_UNDER BIT(1)
+#define DWC_ADV_IC_INTR_CLR_RX_OVER BIT(2)
+#define DWC_ADV_IC_INTR_CLR_TX_OVER BIT(3)
+#define DWC_ADV_IC_INTR_CLR_RD_REQ BIT(4)
+#define DWC_ADV_IC_INTR_CLR_TX_ABRT BIT(5)
+#define DWC_ADV_IC_INTR_CLR_RX_DONE BIT(6)
+#define DWC_ADV_IC_INTR_CLR_ACTIVITY BIT(7)
+#define DWC_ADV_IC_INTR_CLR_STOP_DET BIT(8)
+#define DWC_ADV_IC_INTR_CLR_START_DET BIT(9)
+#define DWC_ADV_IC_INTR_CLR_GEN_CALL BIT(10)
+
#define DW_IC_INTR_DEFAULT_MASK (DW_IC_INTR_RX_FULL | \
DW_IC_INTR_TX_ABRT | \
DW_IC_INTR_STOP_DET)
@@ -291,7 +303,8 @@ struct dw_i2c_dev {
#define MODEL_AMD_NAVI_GPU BIT(10)
#define MODEL_WANGXUN_SP BIT(11)
-#define MODEL_MASK GENMASK(11, 8)
+#define MODEL_TI_TDA54 BIT(12)
+#define MODEL_MASK GENMASK(12, 8)
/*
* Enable UCSI interrupt by writing 0xd at register
@@ -352,12 +365,15 @@ static inline void __i2c_dw_read_intr_mask(struct dw_i2c_dev *dev,
*intr_mask = dev->sw_mask;
}
-/* Acknowledge a logical interrupt via dev->intr_clr[]: reg ID */
+/* Acknowledge a logical interrupt via dev->intr_clr[]: reg ID or bit value */
static inline void i2c_dw_ack_intr(struct dw_i2c_dev *dev, enum dw_i2c_intr_idx intr)
{
unsigned int dummy;
- regmap_read(dev->map, dev->intr_clr[intr], &dummy);
+ if (dev->flags & MODEL_TI_TDA54)
+ regmap_write(dev->map, DW_IC_CLR_INTR, dev->intr_clr[intr]);
+ else
+ regmap_read(dev->map, dev->intr_clr[intr], &dummy);
}
void __i2c_dw_disable(struct dw_i2c_dev *dev);
diff --git a/drivers/i2c/busses/i2c-designware-master.c b/drivers/i2c/busses/i2c-designware-master.c
index 329f958ded3e..94594f6349d4 100644
--- a/drivers/i2c/busses/i2c-designware-master.c
+++ b/drivers/i2c/busses/i2c-designware-master.c
@@ -42,14 +42,16 @@ static int i2c_dw_set_timings_master(struct dw_i2c_dev *dev)
u32 ic_clk;
int ret;
- ret = i2c_dw_acquire_lock(dev);
- if (ret)
- return ret;
-
- ret = regmap_read(dev->map, DW_IC_COMP_PARAM_1, &comp_param1);
- i2c_dw_release_lock(dev);
- if (ret)
- return ret;
+ if (!(dev->flags & MODEL_TI_TDA54)) {
+ ret = i2c_dw_acquire_lock(dev);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(dev->map, DW_IC_COMP_PARAM_1, &comp_param1);
+ i2c_dw_release_lock(dev);
+ if (ret)
+ return ret;
+ }
/* Set standard and fast speed dividers for high/low periods */
sda_falling_time = t->sda_fall_ns ?: 300; /* ns */
@@ -135,7 +137,8 @@ static int i2c_dw_set_timings_master(struct dw_i2c_dev *dev)
/* Check is high speed possible and fall back to fast mode if not */
if ((dev->master_cfg & dev->con_bits->speed_mask) ==
dev->con_bits->speed_high) {
- if ((comp_param1 & DW_IC_COMP_PARAM_1_SPEED_MODE_MASK)
+ if (!(dev->flags & MODEL_TI_TDA54) &&
+ (comp_param1 & DW_IC_COMP_PARAM_1_SPEED_MODE_MASK)
!= DW_IC_COMP_PARAM_1_SPEED_MODE_HIGH) {
dev_err(dev->dev, "High Speed not supported!\n");
t->bus_freq_hz = I2C_MAX_FAST_MODE_FREQ;
@@ -398,8 +401,11 @@ i2c_dw_xfer_msg(struct dw_i2c_dev *dev)
* If both IC_EMPTYFIFO_HOLD_MASTER_EN and
* IC_RESTART_EN are set, we must manually
* set restart bit between messages.
+ * ti,tda54-i2c does not define a CON.RESTART_EN
+ * bit, and behaves like it is set to 1.
*/
- if (dev->master_cfg & dev->con_bits->restart_en &&
+ if (((dev->master_cfg & dev->con_bits->restart_en) ||
+ (dev->flags & MODEL_TI_TDA54)) &&
dev->msg_write_idx > 0)
need_restart = true;
}
diff --git a/drivers/i2c/busses/i2c-designware-platdrv.c b/drivers/i2c/busses/i2c-designware-platdrv.c
index 42b34c678146..069c3dc0da10 100644
--- a/drivers/i2c/busses/i2c-designware-platdrv.c
+++ b/drivers/i2c/busses/i2c-designware-platdrv.c
@@ -257,6 +257,7 @@ static const struct of_device_id dw_i2c_of_match[] = {
{ .compatible = "mobileye,eyeq6lplus-i2c" },
{ .compatible = "mscc,ocelot-i2c" },
{ .compatible = "snps,designware-i2c" },
+ { .compatible = "ti,tda54-i2c", .data = (void *)MODEL_TI_TDA54 },
{}
};
MODULE_DEVICE_TABLE(of, dw_i2c_of_match);
--
2.53.0