* [PATCH v3 2/3] i2c: designware: Support per-variant reg/bit offsets and intr handling
2026-09-25 6:56 [PATCH v3 0/3] i2c: designware: Add TI TDA54 I2C support Aniket Limaye
2026-09-25 6:56 ` [PATCH v3 1/3] dt-bindings: i2c: dw: Add ti,tda54-i2c compatible Aniket Limaye
@ 2026-09-25 6:56 ` Aniket Limaye
2026-09-25 6:56 ` [PATCH v3 3/3] i2c: designware: Add ti,tda54-i2c support Aniket Limaye
2026-09-25 9:41 ` [PATCH v3 0/3] i2c: designware: Add TI TDA54 I2C support Andy Shevchenko
3 siblings, 0 replies; 9+ messages in thread
From: Aniket Limaye @ 2026-09-25 6:56 UTC (permalink / raw)
To: Andi Shyti, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Mika Westerberg, Nirujogi Pratap, Bin Du, Andy Shevchenko,
Matthew Brost, Thomas Hellström, Rodrigo Vivi, David Airlie,
Simona Vetter
Cc: linux-i2c, devicetree, linux-kernel, vigneshr, nm, u-kumar1,
lianfeng.ouyang, Ritwick.Sharma, intel-xe, dri-devel,
Aniket Limaye
Every DW_IC_* register offset and CON-register bit position is currently
baked in as a compile-time constant, which only works while there is a
single register layout.
- Make DW_IC_* a logical enum (enum dw_ic_reg_id) and the regmap
internally translates to physical offsets.
- Add a struct to hold the CON-register bitfield positions.
- Fold the read-to-clear interrupt-acknowledgment pattern into a new
i2c_dw_ack_intr() helper, driven by a per-variant dev->intr_clr[].
All of these are selected at probe time via the new
i2c_dw_select_variant().
Only one variant exists at this point (DW_apb_i2c), so this is a
mechanical, behavior-preserving change. It lays the groundwork for
adding a second register layout without duplicating the whole driver.
Signed-off-by: Aniket Limaye <a-limaye@ti.com>
---
Changes in v3:
- Revert to special handling for the max_register in regmap for
MODEL_AMD_NAVI_GPU instead of unconditionally including
AMD_UCSI_INTR_REG in the legal regmap range.
- Link to v2: https://lore.kernel.org/all/20260925-tda54-upstream-i2c-v2-2-261785e58493@ti.com/
Changes in v2:
- Move the Reg ID -> Offset mapping inside the regmap functions such
that existing calls to reg_read/write are unaltered. This does change
the reg_read()/reg_write() functions to take reg IDs as input rather
than their physical offsets, which also requires updates to the
xe_i2c.c driver to handle the same mapping.
- Link to v1: https://lore.kernel.org/r/20260919-tda54-upstream-i2c-v1-2-b0b9f77be18b@ti.com/
---
drivers/gpu/drm/xe/regs/xe_i2c_regs.h | 3 +-
drivers/i2c/busses/i2c-designware-amdisp.c | 1 +
drivers/i2c/busses/i2c-designware-common.c | 71 +++++++++++---
drivers/i2c/busses/i2c-designware-core.h | 52 ++++++++++-
drivers/i2c/busses/i2c-designware-master.c | 54 +++++------
drivers/i2c/busses/i2c-designware-pcidrv.c | 2 +
drivers/i2c/busses/i2c-designware-platdrv.c | 2 +
drivers/i2c/busses/i2c-designware-slave.c | 26 +++---
include/linux/designware_i2c.h | 137 +++++++++++++++++++---------
9 files changed, 252 insertions(+), 96 deletions(-)
diff --git a/drivers/gpu/drm/xe/regs/xe_i2c_regs.h b/drivers/gpu/drm/xe/regs/xe_i2c_regs.h
index 37550e4a20f8..86449efb4a98 100644
--- a/drivers/gpu/drm/xe/regs/xe_i2c_regs.h
+++ b/drivers/gpu/drm/xe/regs/xe_i2c_regs.h
@@ -2,6 +2,7 @@
#ifndef _XE_I2C_REGS_H_
#define _XE_I2C_REGS_H_
+#include <linux/designware_i2c.h>
#include <linux/pci_regs.h>
#include "xe_reg_defs.h"
@@ -20,6 +21,6 @@
#define I2C_CONFIG_CMD XE_REG(I2C_CONFIG_SPACE_OFFSET + PCI_COMMAND)
#define I2C_CONFIG_PMCSR XE_REG(I2C_CONFIG_SPACE_OFFSET + 0x84)
-#define I2C_REG(reg) XE_REG((reg) + I2C_MEM_SPACE_OFFSET)
+#define I2C_REG(reg) XE_REG(dw_i2c_reg_offsets[reg] + I2C_MEM_SPACE_OFFSET)
#endif /* _XE_I2C_REGS_H_ */
diff --git a/drivers/i2c/busses/i2c-designware-amdisp.c b/drivers/i2c/busses/i2c-designware-amdisp.c
index 9f0ec0fae6f2..f7aa075c9977 100644
--- a/drivers/i2c/busses/i2c-designware-amdisp.c
+++ b/drivers/i2c/busses/i2c-designware-amdisp.c
@@ -46,6 +46,7 @@ static int amd_isp_dw_i2c_plat_probe(struct platform_device *pdev)
isp_i2c_dev->flags |= ACCESS_POLLING;
platform_set_drvdata(pdev, isp_i2c_dev);
+ i2c_dw_select_variant(isp_i2c_dev);
isp_i2c_dev->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(isp_i2c_dev->base))
return dev_err_probe(&pdev->dev, PTR_ERR(isp_i2c_dev->base),
diff --git a/drivers/i2c/busses/i2c-designware-common.c b/drivers/i2c/busses/i2c-designware-common.c
index a1eca6cd4b75..bd25b9c582c1 100644
--- a/drivers/i2c/busses/i2c-designware-common.c
+++ b/drivers/i2c/busses/i2c-designware-common.c
@@ -72,11 +72,55 @@ static const char *const abort_sources[] = {
"incorrect slave-transmitter mode configuration",
};
+static const struct dw_i2c_con_bits dw_i2c_con_bits = {
+ .master = DW_IC_CON_MASTER,
+ .speed_std = DW_IC_CON_SPEED_STD,
+ .speed_fast = DW_IC_CON_SPEED_FAST,
+ .speed_high = DW_IC_CON_SPEED_HIGH,
+ .speed_mask = DW_IC_CON_SPEED_MASK,
+ .bit10_slave = DW_IC_CON_10BITADDR_SLAVE,
+ .bit10_master = DW_IC_CON_10BITADDR_MASTER,
+ .restart_en = DW_IC_CON_RESTART_EN,
+ .slave_disable = DW_IC_CON_SLAVE_DISABLE,
+ .stop_det_ifaddressed = DW_IC_CON_STOP_DET_IFADDRESSED,
+ .tx_empty_ctrl = DW_IC_CON_TX_EMPTY_CTRL,
+ .rx_fifo_full_hld_ctrl = DW_IC_CON_RX_FIFO_FULL_HLD_CTRL,
+ .bus_clear_ctrl = DW_IC_CON_BUS_CLEAR_CTRL,
+};
+
+/* "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,
+ [DW_INTR_IDX_RX_UNDER] = DW_IC_CLR_RX_UNDER,
+ [DW_INTR_IDX_RX_OVER] = DW_IC_CLR_RX_OVER,
+ [DW_INTR_IDX_TX_OVER] = DW_IC_CLR_TX_OVER,
+ [DW_INTR_IDX_RD_REQ] = DW_IC_CLR_RD_REQ,
+ [DW_INTR_IDX_TX_ABRT] = DW_IC_CLR_TX_ABRT,
+ [DW_INTR_IDX_RX_DONE] = DW_IC_CLR_RX_DONE,
+ [DW_INTR_IDX_ACTIVITY] = DW_IC_CLR_ACTIVITY,
+ [DW_INTR_IDX_STOP_DET] = DW_IC_CLR_STOP_DET,
+ [DW_INTR_IDX_START_DET] = DW_IC_CLR_START_DET,
+ [DW_INTR_IDX_GEN_CALL] = DW_IC_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
+ * @dev: device private data
+ */
+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;
+}
+EXPORT_SYMBOL_GPL(i2c_dw_select_variant);
+
static int dw_reg_read(void *context, unsigned int reg, unsigned int *val)
{
struct dw_i2c_dev *dev = context;
- *val = readl(dev->base + reg);
+ *val = readl(dev->base + dev->regs[reg]);
return 0;
}
@@ -85,7 +129,7 @@ static int dw_reg_write(void *context, unsigned int reg, unsigned int val)
{
struct dw_i2c_dev *dev = context;
- writel(val, dev->base + reg);
+ writel(val, dev->base + dev->regs[reg]);
return 0;
}
@@ -94,7 +138,7 @@ static int dw_reg_read_swab(void *context, unsigned int reg, unsigned int *val)
{
struct dw_i2c_dev *dev = context;
- *val = swab32(readl(dev->base + reg));
+ *val = swab32(readl(dev->base + dev->regs[reg]));
return 0;
}
@@ -103,7 +147,7 @@ static int dw_reg_write_swab(void *context, unsigned int reg, unsigned int val)
{
struct dw_i2c_dev *dev = context;
- writel(swab32(val), dev->base + reg);
+ writel(swab32(val), dev->base + dev->regs[reg]);
return 0;
}
@@ -111,9 +155,10 @@ static int dw_reg_write_swab(void *context, unsigned int reg, unsigned int val)
static int dw_reg_read_word(void *context, unsigned int reg, unsigned int *val)
{
struct dw_i2c_dev *dev = context;
+ unsigned int offset = dev->regs[reg];
- *val = readw(dev->base + reg) |
- (readw(dev->base + reg + DW_IC_REG_STEP_BYTES) << DW_IC_REG_WORD_SHIFT);
+ *val = readw(dev->base + offset) |
+ (readw(dev->base + offset + DW_IC_REG_STEP_BYTES) << DW_IC_REG_WORD_SHIFT);
return 0;
}
@@ -121,9 +166,10 @@ static int dw_reg_read_word(void *context, unsigned int reg, unsigned int *val)
static int dw_reg_write_word(void *context, unsigned int reg, unsigned int val)
{
struct dw_i2c_dev *dev = context;
+ unsigned int offset = dev->regs[reg];
- writew(val, dev->base + reg);
- writew(val >> DW_IC_REG_WORD_SHIFT, dev->base + reg + DW_IC_REG_STEP_BYTES);
+ writew(val, dev->base + offset);
+ writew(val >> DW_IC_REG_WORD_SHIFT, dev->base + offset + DW_IC_REG_STEP_BYTES);
return 0;
}
@@ -143,7 +189,7 @@ static int i2c_dw_init_regmap(struct dw_i2c_dev *dev)
struct regmap_config map_cfg = {
.reg_bits = 32,
.val_bits = 32,
- .reg_stride = 4,
+ .reg_stride = 1,
.disable_locking = true,
.reg_read = dw_reg_read,
.reg_write = dw_reg_write,
@@ -163,7 +209,7 @@ static int i2c_dw_init_regmap(struct dw_i2c_dev *dev)
if (ret)
return ret;
- reg = readl(dev->base + DW_IC_COMP_TYPE);
+ reg = readl(dev->base + dev->regs[DW_IC_COMP_TYPE]);
i2c_dw_release_lock(dev);
if ((dev->flags & MODEL_MASK) == MODEL_AMD_NAVI_GPU)
@@ -845,7 +891,6 @@ u32 i2c_dw_func(struct i2c_adapter *adap)
void i2c_dw_disable(struct dw_i2c_dev *dev)
{
- unsigned int dummy;
int ret;
ret = i2c_dw_acquire_lock(dev);
@@ -857,7 +902,7 @@ void i2c_dw_disable(struct dw_i2c_dev *dev)
/* Disable all interrupts */
__i2c_dw_write_intr_mask(dev, 0);
- regmap_read(dev->map, DW_IC_CLR_INTR, &dummy);
+ i2c_dw_ack_intr(dev, DW_INTR_IDX_INTR);
i2c_dw_release_lock(dev);
}
@@ -1055,7 +1100,7 @@ void i2c_dw_shutdown(struct dw_i2c_dev *dev)
* disable bit while the controller is still enabled.
*/
regmap_read(dev->map, DW_IC_CON, &con);
- con |= DW_IC_CON_SLAVE_DISABLE;
+ con |= dev->con_bits->slave_disable;
regmap_write(dev->map, DW_IC_CON, con);
i2c_dw_disable(dev);
diff --git a/drivers/i2c/busses/i2c-designware-core.h b/drivers/i2c/busses/i2c-designware-core.h
index 2c929a6e8da2..4c782594ab4e 100644
--- a/drivers/i2c/busses/i2c-designware-core.h
+++ b/drivers/i2c/busses/i2c-designware-core.h
@@ -43,6 +43,42 @@
#define DW_IC_SDA_HOLD_MIN_VERS 0x3131312A /* "111*" == v1.11* */
#define DW_IC_COMP_TYPE_VALUE 0x44570140 /* "DW" + 0x0140 */
+/*
+ * Bit positions within the CON register that could differ between IP
+ * variants.
+ */
+struct dw_i2c_con_bits {
+ u32 master;
+ u32 speed_std;
+ u32 speed_fast;
+ u32 speed_high;
+ u32 speed_mask;
+ u32 bit10_slave;
+ u32 bit10_master;
+ u32 restart_en;
+ u32 slave_disable;
+ u32 stop_det_ifaddressed;
+ u32 tx_empty_ctrl;
+ u32 rx_fifo_full_hld_ctrl;
+ u32 bus_clear_ctrl;
+};
+
+/* Logical interrupt IDs for i2c_dw_ack_intr() */
+enum dw_i2c_intr_idx {
+ DW_INTR_IDX_INTR,
+ DW_INTR_IDX_RX_UNDER,
+ DW_INTR_IDX_RX_OVER,
+ DW_INTR_IDX_TX_OVER,
+ DW_INTR_IDX_RD_REQ,
+ DW_INTR_IDX_TX_ABRT,
+ DW_INTR_IDX_RX_DONE,
+ DW_INTR_IDX_ACTIVITY,
+ DW_INTR_IDX_STOP_DET,
+ DW_INTR_IDX_START_DET,
+ DW_INTR_IDX_GEN_CALL,
+ DW_INTR_IDX_MAX,
+};
+
#define DW_IC_INTR_DEFAULT_MASK (DW_IC_INTR_RX_FULL | \
DW_IC_INTR_TX_ABRT | \
DW_IC_INTR_STOP_DET)
@@ -126,6 +162,9 @@ struct reset_control;
* struct dw_i2c_dev - private i2c-designware data
* @dev: driver model device node
* @map: IO registers map
+ * @regs: logical-to-physical register offset table for the active IP variant
+ * @con_bits: CON register bit-layout for the active IP variant
+ * @intr_clr: logical intr number to reg table for the active IP variant
* @sysmap: System controller registers map
* @base: IO registers pointer
* @ext: Extended IO registers pointer
@@ -189,6 +228,9 @@ struct reset_control;
struct dw_i2c_dev {
struct device *dev;
struct regmap *map;
+ const u32 *regs;
+ const struct dw_i2c_con_bits *con_bits;
+ const u32 *intr_clr;
struct regmap *sysmap;
void __iomem *base;
void __iomem *ext;
@@ -255,7 +297,6 @@ struct dw_i2c_dev {
* Enable UCSI interrupt by writing 0xd at register
* offset 0x474 specified in hardware specification.
*/
-#define AMD_UCSI_INTR_REG 0x474
#define AMD_UCSI_INTR_EN 0xd
#define TXGBE_TX_FIFO_DEPTH 4
@@ -265,6 +306,7 @@ struct i2c_dw_semaphore_callbacks {
int (*probe)(struct dw_i2c_dev *dev);
};
+void i2c_dw_select_variant(struct dw_i2c_dev *dev);
u32 i2c_dw_scl_hcnt(struct dw_i2c_dev *dev, unsigned int reg, u32 ic_clk,
u32 tSYMBOL, u32 tf, int offset);
u32 i2c_dw_scl_lcnt(struct dw_i2c_dev *dev, unsigned int reg, u32 ic_clk,
@@ -310,6 +352,14 @@ 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 */
+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);
+}
+
void __i2c_dw_disable(struct dw_i2c_dev *dev);
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 a1bcc3797e4f..329f958ded3e 100644
--- a/drivers/i2c/busses/i2c-designware-master.c
+++ b/drivers/i2c/busses/i2c-designware-master.c
@@ -133,14 +133,14 @@ static int i2c_dw_set_timings_master(struct dw_i2c_dev *dev)
fp_str, dev->fs_hcnt, dev->fs_lcnt);
/* Check is high speed possible and fall back to fast mode if not */
- if ((dev->master_cfg & DW_IC_CON_SPEED_MASK) ==
- DW_IC_CON_SPEED_HIGH) {
+ 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)
!= 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;
- dev->master_cfg &= ~DW_IC_CON_SPEED_MASK;
- dev->master_cfg |= DW_IC_CON_SPEED_FAST;
+ dev->master_cfg &= ~dev->con_bits->speed_mask;
+ dev->master_cfg |= dev->con_bits->speed_fast;
dev->hs_hcnt = 0;
dev->hs_lcnt = 0;
} else if (!dev->hs_hcnt || !dev->hs_lcnt) {
@@ -200,7 +200,7 @@ static void i2c_dw_xfer_init(struct dw_i2c_dev *dev)
/* If the slave address is ten bit address, enable 10BITADDR */
if (msgs[dev->msg_write_idx].flags & I2C_M_TEN) {
- ic_con = DW_IC_CON_10BITADDR_MASTER;
+ ic_con = dev->con_bits->bit10_master;
/*
* If I2C_DYNAMIC_TAR_UPDATE is set, the 10-bit addressing
* mode has to be enabled via bit 12 of IC_TAR register.
@@ -210,7 +210,7 @@ static void i2c_dw_xfer_init(struct dw_i2c_dev *dev)
ic_tar = DW_IC_TAR_10BITADDR_MASTER;
}
- regmap_update_bits(dev->map, DW_IC_CON, DW_IC_CON_10BITADDR_MASTER,
+ regmap_update_bits(dev->map, DW_IC_CON, dev->con_bits->bit10_master,
ic_con);
/*
@@ -230,7 +230,7 @@ static void i2c_dw_xfer_init(struct dw_i2c_dev *dev)
regmap_read(dev->map, DW_IC_ENABLE_STATUS, &dummy);
/* Clear and enable interrupts */
- regmap_read(dev->map, DW_IC_CLR_INTR, &dummy);
+ i2c_dw_ack_intr(dev, DW_INTR_IDX_INTR);
__i2c_dw_write_intr_mask(dev, DW_IC_INTR_MASTER_MASK);
}
@@ -399,8 +399,8 @@ i2c_dw_xfer_msg(struct dw_i2c_dev *dev)
* IC_RESTART_EN are set, we must manually
* set restart bit between messages.
*/
- if ((dev->master_cfg & DW_IC_CON_RESTART_EN) &&
- (dev->msg_write_idx > 0))
+ if (dev->master_cfg & dev->con_bits->restart_en &&
+ dev->msg_write_idx > 0)
need_restart = true;
}
@@ -574,7 +574,7 @@ i2c_dw_read(struct dw_i2c_dev *dev)
static u32 i2c_dw_read_clear_intrbits(struct dw_i2c_dev *dev)
{
- unsigned int stat, dummy;
+ unsigned int stat;
/*
* The IC_INTR_STAT register just indicates "enabled" interrupts.
@@ -603,32 +603,32 @@ static u32 i2c_dw_read_clear_intrbits(struct dw_i2c_dev *dev)
* Instead, use the separately-prepared IC_CLR_* registers.
*/
if (stat & DW_IC_INTR_RX_UNDER)
- regmap_read(dev->map, DW_IC_CLR_RX_UNDER, &dummy);
+ i2c_dw_ack_intr(dev, DW_INTR_IDX_RX_UNDER);
if (stat & DW_IC_INTR_RX_OVER)
- regmap_read(dev->map, DW_IC_CLR_RX_OVER, &dummy);
+ i2c_dw_ack_intr(dev, DW_INTR_IDX_RX_OVER);
if (stat & DW_IC_INTR_TX_OVER)
- regmap_read(dev->map, DW_IC_CLR_TX_OVER, &dummy);
+ i2c_dw_ack_intr(dev, DW_INTR_IDX_TX_OVER);
if (stat & DW_IC_INTR_RD_REQ)
- regmap_read(dev->map, DW_IC_CLR_RD_REQ, &dummy);
+ i2c_dw_ack_intr(dev, DW_INTR_IDX_RD_REQ);
if (stat & DW_IC_INTR_TX_ABRT) {
/*
* The IC_TX_ABRT_SOURCE register is cleared whenever
* the IC_CLR_TX_ABRT is read. Preserve it beforehand.
*/
regmap_read(dev->map, DW_IC_TX_ABRT_SOURCE, &dev->abort_source);
- regmap_read(dev->map, DW_IC_CLR_TX_ABRT, &dummy);
+ i2c_dw_ack_intr(dev, DW_INTR_IDX_TX_ABRT);
}
if (stat & DW_IC_INTR_RX_DONE)
- regmap_read(dev->map, DW_IC_CLR_RX_DONE, &dummy);
+ i2c_dw_ack_intr(dev, DW_INTR_IDX_RX_DONE);
if (stat & DW_IC_INTR_ACTIVITY)
- regmap_read(dev->map, DW_IC_CLR_ACTIVITY, &dummy);
+ i2c_dw_ack_intr(dev, DW_INTR_IDX_ACTIVITY);
if ((stat & DW_IC_INTR_STOP_DET) &&
((dev->rx_outstanding == 0) || (stat & DW_IC_INTR_RX_FULL)))
- regmap_read(dev->map, DW_IC_CLR_STOP_DET, &dummy);
+ i2c_dw_ack_intr(dev, DW_INTR_IDX_STOP_DET);
if (stat & DW_IC_INTR_START_DET)
- regmap_read(dev->map, DW_IC_CLR_START_DET, &dummy);
+ i2c_dw_ack_intr(dev, DW_INTR_IDX_START_DET);
if (stat & DW_IC_INTR_GEN_CALL)
- regmap_read(dev->map, DW_IC_CLR_GEN_CALL, &dummy);
+ i2c_dw_ack_intr(dev, DW_INTR_IDX_GEN_CALL);
return stat;
}
@@ -940,20 +940,20 @@ void i2c_dw_configure_master(struct dw_i2c_dev *dev)
if ((dev->flags & MODEL_MASK) != MODEL_AMD_NAVI_GPU)
dev->functionality |= I2C_FUNC_PROTOCOL_MANGLING;
- dev->master_cfg = DW_IC_CON_MASTER | DW_IC_CON_SLAVE_DISABLE |
- DW_IC_CON_RESTART_EN;
+ dev->master_cfg = dev->con_bits->master | dev->con_bits->slave_disable |
+ dev->con_bits->restart_en;
dev->mode = DW_IC_MASTER;
switch (t->bus_freq_hz) {
case I2C_MAX_STANDARD_MODE_FREQ:
- dev->master_cfg |= DW_IC_CON_SPEED_STD;
+ dev->master_cfg |= dev->con_bits->speed_std;
break;
case I2C_MAX_HIGH_SPEED_MODE_FREQ:
- dev->master_cfg |= DW_IC_CON_SPEED_HIGH;
+ dev->master_cfg |= dev->con_bits->speed_high;
break;
default:
- dev->master_cfg |= DW_IC_CON_SPEED_FAST;
+ dev->master_cfg |= dev->con_bits->speed_fast;
}
}
EXPORT_SYMBOL_GPL(i2c_dw_configure_master);
@@ -1042,8 +1042,8 @@ int i2c_dw_probe_master(struct dw_i2c_dev *dev)
if (ret)
return ret;
- if (ic_con & DW_IC_CON_BUS_CLEAR_CTRL)
- dev->master_cfg |= DW_IC_CON_BUS_CLEAR_CTRL;
+ if (ic_con & dev->con_bits->bus_clear_ctrl)
+ dev->master_cfg |= dev->con_bits->bus_clear_ctrl;
return i2c_dw_init_recovery_info(dev);
}
diff --git a/drivers/i2c/busses/i2c-designware-pcidrv.c b/drivers/i2c/busses/i2c-designware-pcidrv.c
index 468287922363..fc73106fb0f9 100644
--- a/drivers/i2c/busses/i2c-designware-pcidrv.c
+++ b/drivers/i2c/busses/i2c-designware-pcidrv.c
@@ -246,6 +246,8 @@ static int i2c_dw_pci_probe(struct pci_dev *pdev,
pci_set_drvdata(pdev, dev);
+ i2c_dw_select_variant(dev);
+
if (controller->setup) {
r = controller->setup(pdev, controller);
if (r)
diff --git a/drivers/i2c/busses/i2c-designware-platdrv.c b/drivers/i2c/busses/i2c-designware-platdrv.c
index 447af5523c2e..42b34c678146 100644
--- a/drivers/i2c/busses/i2c-designware-platdrv.c
+++ b/drivers/i2c/busses/i2c-designware-platdrv.c
@@ -156,6 +156,8 @@ static int dw_i2c_plat_probe(struct platform_device *pdev)
dev->flags = flags;
platform_set_drvdata(pdev, dev);
+ i2c_dw_select_variant(dev);
+
ret = dw_i2c_plat_request_regs(dev);
if (ret)
return ret;
diff --git a/drivers/i2c/busses/i2c-designware-slave.c b/drivers/i2c/busses/i2c-designware-slave.c
index 0abcc7757b23..0985e907c9c3 100644
--- a/drivers/i2c/busses/i2c-designware-slave.c
+++ b/drivers/i2c/busses/i2c-designware-slave.c
@@ -65,7 +65,7 @@ int i2c_dw_unreg_slave(struct i2c_client *slave)
static u32 i2c_dw_read_clear_intrbits_slave(struct dw_i2c_dev *dev)
{
- unsigned int stat, dummy;
+ unsigned int stat;
/*
* The IC_INTR_STAT register just indicates "enabled" interrupts.
@@ -89,23 +89,23 @@ static u32 i2c_dw_read_clear_intrbits_slave(struct dw_i2c_dev *dev)
* Instead, use the separately-prepared IC_CLR_* registers.
*/
if (stat & DW_IC_INTR_TX_ABRT)
- regmap_read(dev->map, DW_IC_CLR_TX_ABRT, &dummy);
+ i2c_dw_ack_intr(dev, DW_INTR_IDX_TX_ABRT);
if (stat & DW_IC_INTR_RX_UNDER)
- regmap_read(dev->map, DW_IC_CLR_RX_UNDER, &dummy);
+ i2c_dw_ack_intr(dev, DW_INTR_IDX_RX_UNDER);
if (stat & DW_IC_INTR_RX_OVER)
- regmap_read(dev->map, DW_IC_CLR_RX_OVER, &dummy);
+ i2c_dw_ack_intr(dev, DW_INTR_IDX_RX_OVER);
if (stat & DW_IC_INTR_TX_OVER)
- regmap_read(dev->map, DW_IC_CLR_TX_OVER, &dummy);
+ i2c_dw_ack_intr(dev, DW_INTR_IDX_TX_OVER);
if (stat & DW_IC_INTR_RX_DONE)
- regmap_read(dev->map, DW_IC_CLR_RX_DONE, &dummy);
+ i2c_dw_ack_intr(dev, DW_INTR_IDX_RX_DONE);
if (stat & DW_IC_INTR_ACTIVITY)
- regmap_read(dev->map, DW_IC_CLR_ACTIVITY, &dummy);
+ i2c_dw_ack_intr(dev, DW_INTR_IDX_ACTIVITY);
if (stat & DW_IC_INTR_STOP_DET)
- regmap_read(dev->map, DW_IC_CLR_STOP_DET, &dummy);
+ i2c_dw_ack_intr(dev, DW_INTR_IDX_STOP_DET);
if (stat & DW_IC_INTR_START_DET)
- regmap_read(dev->map, DW_IC_CLR_START_DET, &dummy);
+ i2c_dw_ack_intr(dev, DW_INTR_IDX_START_DET);
if (stat & DW_IC_INTR_GEN_CALL)
- regmap_read(dev->map, DW_IC_CLR_GEN_CALL, &dummy);
+ i2c_dw_ack_intr(dev, DW_INTR_IDX_GEN_CALL);
return stat;
}
@@ -155,7 +155,7 @@ irqreturn_t i2c_dw_isr_slave(struct dw_i2c_dev *dev)
if (stat & DW_IC_INTR_RD_REQ) {
if (slave_activity) {
- regmap_read(dev->map, DW_IC_CLR_RD_REQ, &tmp);
+ i2c_dw_ack_intr(dev, DW_INTR_IDX_RD_REQ);
if (!(dev->status & STATUS_READ_IN_PROGRESS)) {
i2c_slave_event(dev->slave,
@@ -185,8 +185,8 @@ void i2c_dw_configure_slave(struct dw_i2c_dev *dev)
dev->functionality |= I2C_FUNC_SLAVE;
- dev->slave_cfg = DW_IC_CON_RX_FIFO_FULL_HLD_CTRL |
- DW_IC_CON_RESTART_EN | DW_IC_CON_STOP_DET_IFADDRESSED;
+ dev->slave_cfg = dev->con_bits->rx_fifo_full_hld_ctrl |
+ dev->con_bits->restart_en | dev->con_bits->stop_det_ifaddressed;
}
EXPORT_SYMBOL_GPL(i2c_dw_configure_slave);
diff --git a/include/linux/designware_i2c.h b/include/linux/designware_i2c.h
index 53f37f18a722..994e0e5ea623 100644
--- a/include/linux/designware_i2c.h
+++ b/include/linux/designware_i2c.h
@@ -11,48 +11,103 @@
#include <linux/bits.h>
/*
- * Registers offset
+ * Logical register IDs. Each ID is resolved to a physical offset via a
+ * per-variant table (see dw_i2c_reg_offsets[] below); regmap backends
+ * (dw_reg_read()/dw_reg_write() and the Xe I2C_REG() macro) perform that
+ * lookup so callers never need to know the real offset.
*/
-#define DW_IC_CON 0x00
-#define DW_IC_TAR 0x04
-#define DW_IC_SAR 0x08
-#define DW_IC_DATA_CMD 0x10
-#define DW_IC_SS_SCL_HCNT 0x14
-#define DW_IC_SS_SCL_LCNT 0x18
-#define DW_IC_FS_SCL_HCNT 0x1c
-#define DW_IC_FS_SCL_LCNT 0x20
-#define DW_IC_HS_SCL_HCNT 0x24
-#define DW_IC_HS_SCL_LCNT 0x28
-#define DW_IC_INTR_STAT 0x2c
-#define DW_IC_INTR_MASK 0x30
-#define DW_IC_RAW_INTR_STAT 0x34
-#define DW_IC_RX_TL 0x38
-#define DW_IC_TX_TL 0x3c
-#define DW_IC_CLR_INTR 0x40
-#define DW_IC_CLR_RX_UNDER 0x44
-#define DW_IC_CLR_RX_OVER 0x48
-#define DW_IC_CLR_TX_OVER 0x4c
-#define DW_IC_CLR_RD_REQ 0x50
-#define DW_IC_CLR_TX_ABRT 0x54
-#define DW_IC_CLR_RX_DONE 0x58
-#define DW_IC_CLR_ACTIVITY 0x5c
-#define DW_IC_CLR_STOP_DET 0x60
-#define DW_IC_CLR_START_DET 0x64
-#define DW_IC_CLR_GEN_CALL 0x68
-#define DW_IC_ENABLE 0x6c
-#define DW_IC_STATUS 0x70
-#define DW_IC_TXFLR 0x74
-#define DW_IC_RXFLR 0x78
-#define DW_IC_SDA_HOLD 0x7c
-#define DW_IC_TX_ABRT_SOURCE 0x80
-#define DW_IC_ENABLE_STATUS 0x9c
-#define DW_IC_CLR_RESTART_DET 0xa8
-#define DW_IC_SMBUS_INTR_STAT 0xc8
-#define DW_IC_SMBUS_INTR_MASK 0xcc
-#define DW_IC_CLR_SMBUS_INTR 0xd4
-#define DW_IC_COMP_PARAM_1 0xf4
-#define DW_IC_COMP_VERSION 0xf8
-#define DW_IC_COMP_TYPE 0xfc
+enum dw_ic_reg_id {
+ DW_IC_CON,
+ DW_IC_TAR,
+ DW_IC_SAR,
+ DW_IC_DATA_CMD,
+ DW_IC_SS_SCL_HCNT,
+ DW_IC_SS_SCL_LCNT,
+ DW_IC_FS_SCL_HCNT,
+ DW_IC_FS_SCL_LCNT,
+ DW_IC_HS_SCL_HCNT,
+ DW_IC_HS_SCL_LCNT,
+ DW_IC_INTR_STAT,
+ DW_IC_INTR_MASK,
+ DW_IC_RAW_INTR_STAT,
+ DW_IC_RX_TL,
+ DW_IC_TX_TL,
+ DW_IC_CLR_INTR,
+ DW_IC_CLR_RX_UNDER,
+ DW_IC_CLR_RX_OVER,
+ DW_IC_CLR_TX_OVER,
+ DW_IC_CLR_RD_REQ,
+ DW_IC_CLR_TX_ABRT,
+ DW_IC_CLR_RX_DONE,
+ DW_IC_CLR_ACTIVITY,
+ DW_IC_CLR_STOP_DET,
+ DW_IC_CLR_START_DET,
+ DW_IC_CLR_GEN_CALL,
+ DW_IC_ENABLE,
+ DW_IC_STATUS,
+ DW_IC_TXFLR,
+ DW_IC_RXFLR,
+ DW_IC_SDA_HOLD,
+ DW_IC_TX_ABRT_SOURCE,
+ DW_IC_ENABLE_STATUS,
+ DW_IC_CLR_RESTART_DET,
+ DW_IC_SMBUS_INTR_STAT,
+ DW_IC_SMBUS_INTR_MASK,
+ DW_IC_CLR_SMBUS_INTR,
+ DW_IC_COMP_PARAM_1,
+ DW_IC_COMP_VERSION,
+ DW_IC_COMP_TYPE,
+ AMD_UCSI_INTR_REG,
+ DW_IC_REG_MAX
+};
+
+/*
+ * Default "snps,designware-i2c" register offsets, indexed by
+ * enum dw_ic_reg_id.
+ */
+static const __maybe_unused u32 dw_i2c_reg_offsets[DW_IC_REG_MAX] = {
+ [DW_IC_CON] = 0x00,
+ [DW_IC_TAR] = 0x04,
+ [DW_IC_SAR] = 0x08,
+ [DW_IC_DATA_CMD] = 0x10,
+ [DW_IC_SS_SCL_HCNT] = 0x14,
+ [DW_IC_SS_SCL_LCNT] = 0x18,
+ [DW_IC_FS_SCL_HCNT] = 0x1c,
+ [DW_IC_FS_SCL_LCNT] = 0x20,
+ [DW_IC_HS_SCL_HCNT] = 0x24,
+ [DW_IC_HS_SCL_LCNT] = 0x28,
+ [DW_IC_INTR_STAT] = 0x2c,
+ [DW_IC_INTR_MASK] = 0x30,
+ [DW_IC_RAW_INTR_STAT] = 0x34,
+ [DW_IC_RX_TL] = 0x38,
+ [DW_IC_TX_TL] = 0x3c,
+ [DW_IC_CLR_INTR] = 0x40,
+ [DW_IC_CLR_RX_UNDER] = 0x44,
+ [DW_IC_CLR_RX_OVER] = 0x48,
+ [DW_IC_CLR_TX_OVER] = 0x4c,
+ [DW_IC_CLR_RD_REQ] = 0x50,
+ [DW_IC_CLR_TX_ABRT] = 0x54,
+ [DW_IC_CLR_RX_DONE] = 0x58,
+ [DW_IC_CLR_ACTIVITY] = 0x5c,
+ [DW_IC_CLR_STOP_DET] = 0x60,
+ [DW_IC_CLR_START_DET] = 0x64,
+ [DW_IC_CLR_GEN_CALL] = 0x68,
+ [DW_IC_ENABLE] = 0x6c,
+ [DW_IC_STATUS] = 0x70,
+ [DW_IC_TXFLR] = 0x74,
+ [DW_IC_RXFLR] = 0x78,
+ [DW_IC_SDA_HOLD] = 0x7c,
+ [DW_IC_TX_ABRT_SOURCE] = 0x80,
+ [DW_IC_ENABLE_STATUS] = 0x9c,
+ [DW_IC_CLR_RESTART_DET] = 0xa8,
+ [DW_IC_SMBUS_INTR_STAT] = 0xc8,
+ [DW_IC_SMBUS_INTR_MASK] = 0xcc,
+ [DW_IC_CLR_SMBUS_INTR] = 0xd4,
+ [DW_IC_COMP_PARAM_1] = 0xf4,
+ [DW_IC_COMP_VERSION] = 0xf8,
+ [DW_IC_COMP_TYPE] = 0xfc,
+ [AMD_UCSI_INTR_REG] = 0x474,
+};
/* DW_IC_CON bits */
#define DW_IC_CON_MASTER BIT(0)
--
2.53.0
^ permalink raw reply [flat|nested] 9+ messages in thread* [PATCH v3 3/3] i2c: designware: Add ti,tda54-i2c support
2026-09-25 6:56 [PATCH v3 0/3] i2c: designware: Add TI TDA54 I2C support Aniket Limaye
2026-09-25 6:56 ` [PATCH v3 1/3] dt-bindings: i2c: dw: Add ti,tda54-i2c compatible Aniket Limaye
2026-09-25 6:56 ` [PATCH v3 2/3] i2c: designware: Support per-variant reg/bit offsets and intr handling Aniket Limaye
@ 2026-09-25 6:56 ` Aniket Limaye
2026-09-25 9:41 ` [PATCH v3 0/3] i2c: designware: Add TI TDA54 I2C support Andy Shevchenko
3 siblings, 0 replies; 9+ messages in thread
From: Aniket Limaye @ 2026-09-25 6:56 UTC (permalink / raw)
To: Andi Shyti, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Mika Westerberg, Nirujogi Pratap, Bin Du, Andy Shevchenko,
Matthew Brost, Thomas Hellström, Rodrigo Vivi, David Airlie,
Simona Vetter
Cc: linux-i2c, devicetree, linux-kernel, vigneshr, nm, u-kumar1,
lianfeng.ouyang, Ritwick.Sharma, intel-xe, dri-devel,
Aniket Limaye
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@ti.com>
---
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@ti.com/
---
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 bd25b9c582c1..8cb162a6aed8 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);
@@ -432,13 +507,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);
@@ -493,8 +582,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);
@@ -538,6 +629,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 */
@@ -854,6 +953,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
^ permalink raw reply [flat|nested] 9+ messages in thread