[PATCH 3/3] i2c: designware: Add snps,dwc-i2c support and new compatible
From: Aniket Limaye
Date: Sat Sep 19 2026 - 05:10:03 EST
Add the register offset table and CON-register bit layout for the
DWC_i2c IP ("snps,dwc-i2c"), and register it in dw_i2c_of_match[] with
the new MODEL_DWC_I2C flag.
Compared to the existing DW_apb_i2c layout [1], DWC_i2c [0] differs
enough to need MODEL_DWC_I2C-gated special-casing in a few places,
besides the offset/bit-layout tables themselves:
- 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; treat it as always set.
The register offsets used for DWC_i2c are those present in TI TDA54 SoC.
[0]: DWC_i2c_reference.pdf
[1]: DW_apb_i2c_databook.pdf
Signed-off-by: Aniket Limaye <a-limaye@xxxxxx>
---
SMBus is not supported on TI TDA54 SoC, and hence this patch does not
add SMBus register offsets.
---
drivers/i2c/busses/i2c-designware-common.c | 139 +++++++++++++++++++++++++---
drivers/i2c/busses/i2c-designware-core.h | 26 +++++-
drivers/i2c/busses/i2c-designware-master.c | 26 ++++--
drivers/i2c/busses/i2c-designware-platdrv.c | 1 +
4 files changed, 168 insertions(+), 24 deletions(-)
diff --git a/drivers/i2c/busses/i2c-designware-common.c b/drivers/i2c/busses/i2c-designware-common.c
index a21aeb7f415a..cc05c822aa56 100644
--- a/drivers/i2c/busses/i2c-designware-common.c
+++ b/drivers/i2c/busses/i2c-designware-common.c
@@ -113,6 +113,41 @@ static const u32 dw_i2c_reg_offsets[DW_REG_IDX_MAX] = {
[DW_REG_IDX_COMP_TYPE] = DW_IC_COMP_TYPE,
};
+/* "snps,dwc-i2c" block-based register layout */
+#define DWC_OPERATION_BLK_OFFSET 0x00
+#define DWC_I2C_BLK_OFFSET 0x28
+#define DWC_FIFO_INTR_BLK_OFFSET 0xbc
+#define DWC_DEBUG_BLK_OFFSET 0xf8
+
+static const u32 dwc_i2c_reg_offsets[DW_REG_IDX_MAX] = {
+ [DW_REG_IDX_ENABLE] = DWC_OPERATION_BLK_OFFSET + 0x04,
+ [DW_REG_IDX_CAPABILITIES] = DWC_OPERATION_BLK_OFFSET + 0x0c,
+ [DW_REG_IDX_CON] = DWC_I2C_BLK_OFFSET + 0x04,
+ [DW_REG_IDX_TAR] = DWC_I2C_BLK_OFFSET + 0x08,
+ [DW_REG_IDX_SAR] = DWC_I2C_BLK_OFFSET + 0x0c, /* DAR on this IP */
+ [DW_REG_IDX_DATA_CMD] = DWC_I2C_BLK_OFFSET + 0x58,
+ [DW_REG_IDX_SS_SCL_HCNT] = DWC_I2C_BLK_OFFSET + 0x24, /* shared SS/FS pair */
+ [DW_REG_IDX_SS_SCL_LCNT] = DWC_I2C_BLK_OFFSET + 0x28,
+ [DW_REG_IDX_FS_SCL_HCNT] = DWC_I2C_BLK_OFFSET + 0x24,
+ [DW_REG_IDX_FS_SCL_LCNT] = DWC_I2C_BLK_OFFSET + 0x28,
+ [DW_REG_IDX_HS_SCL_HCNT] = DWC_I2C_BLK_OFFSET + 0x2c,
+ [DW_REG_IDX_HS_SCL_LCNT] = DWC_I2C_BLK_OFFSET + 0x30,
+ [DW_REG_IDX_SDA_HOLD] = DWC_I2C_BLK_OFFSET + 0x34,
+ [DW_REG_IDX_RX_TL] = DWC_I2C_BLK_OFFSET + 0x5c,
+ [DW_REG_IDX_TX_TL] = DWC_I2C_BLK_OFFSET + 0x60,
+ [DW_REG_IDX_INTR_STAT] = DWC_FIFO_INTR_BLK_OFFSET + 0x04,
+ [DW_REG_IDX_INTR_MASK] = DWC_FIFO_INTR_BLK_OFFSET + 0x08,
+ [DW_REG_IDX_RAW_INTR_STAT] = DWC_FIFO_INTR_BLK_OFFSET + 0x0c,
+ [DW_REG_IDX_CLR_INTR] = DWC_FIFO_INTR_BLK_OFFSET + 0x10,
+ [DW_REG_IDX_STATUS] = DWC_FIFO_INTR_BLK_OFFSET + 0x1c,
+ [DW_REG_IDX_TXFLR] = DWC_FIFO_INTR_BLK_OFFSET + 0x20,
+ [DW_REG_IDX_RXFLR] = DWC_FIFO_INTR_BLK_OFFSET + 0x24,
+ [DW_REG_IDX_TX_ABRT_SOURCE] = DWC_FIFO_INTR_BLK_OFFSET + 0x18,
+ [DW_REG_IDX_ENABLE_STATUS] = DWC_FIFO_INTR_BLK_OFFSET + 0x14,
+ [DW_REG_IDX_COMP_VERSION] = DWC_DEBUG_BLK_OFFSET + 0x08,
+ [DW_REG_IDX_COMP_TYPE] = DWC_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,
@@ -129,6 +164,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 "snps,dwc-i2c".
+ * There is no defined bit for RESTART_EN or SLAVE_DISABLE on this IP.
+ */
+static const struct dw_i2c_con_bits dwc_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_ALL] = DW_REG_IDX_CLR_INTR,
@@ -144,6 +199,21 @@ static const u32 dw_i2c_intr_clr[DW_INTR_IDX_MAX] = {
[DW_INTR_IDX_GEN_CALL] = DW_REG_IDX_CLR_GEN_CALL,
};
+/* "snps,dwc-i2c" DW_REG_IDX_CLR_INTR bit to write per logical interrupt */
+static const u32 dwc_i2c_intr_clr[DW_INTR_IDX_MAX] = {
+ [DW_INTR_IDX_ALL] = DWC_IC_INTR_CLR_INTR,
+ [DW_INTR_IDX_RX_UNDER] = DWC_IC_INTR_CLR_RX_UNDER,
+ [DW_INTR_IDX_RX_OVER] = DWC_IC_INTR_CLR_RX_OVER,
+ [DW_INTR_IDX_TX_OVER] = DWC_IC_INTR_CLR_TX_OVER,
+ [DW_INTR_IDX_RD_REQ] = DWC_IC_INTR_CLR_RD_REQ,
+ [DW_INTR_IDX_TX_ABRT] = DWC_IC_INTR_CLR_TX_ABRT,
+ [DW_INTR_IDX_RX_DONE] = DWC_IC_INTR_CLR_RX_DONE,
+ [DW_INTR_IDX_ACTIVITY] = DWC_IC_INTR_CLR_ACTIVITY,
+ [DW_INTR_IDX_STOP_DET] = DWC_IC_INTR_CLR_STOP_DET,
+ [DW_INTR_IDX_START_DET] = DWC_IC_INTR_CLR_START_DET,
+ [DW_INTR_IDX_GEN_CALL] = DWC_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
@@ -155,9 +225,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_DWC_I2C) {
+ dev->regs = dwc_i2c_reg_offsets;
+ dev->con_bits = &dwc_i2c_con_bits;
+ dev->intr_clr = dwc_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);
@@ -475,13 +551,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, dev->regs[DW_REG_IDX_SS_SCL_HCNT], dev->ss_hcnt);
- regmap_write(dev->map, dev->regs[DW_REG_IDX_SS_SCL_LCNT], dev->ss_lcnt);
-
- /* Write fast mode/fast mode plus timing parameters */
- regmap_write(dev->map, dev->regs[DW_REG_IDX_FS_SCL_HCNT], dev->fs_hcnt);
- regmap_write(dev->map, dev->regs[DW_REG_IDX_FS_SCL_LCNT], dev->fs_lcnt);
+ if (dev->flags & MODEL_DWC_I2C) {
+ /*
+ * 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, dev->regs[DW_REG_IDX_SS_SCL_HCNT], dev->ss_hcnt);
+ regmap_write(dev->map, dev->regs[DW_REG_IDX_SS_SCL_LCNT], dev->ss_lcnt);
+ } else {
+ regmap_write(dev->map, dev->regs[DW_REG_IDX_FS_SCL_HCNT], dev->fs_hcnt);
+ regmap_write(dev->map, dev->regs[DW_REG_IDX_FS_SCL_LCNT], dev->fs_lcnt);
+ }
+ } else {
+ /* Write standard speed timing parameters */
+ regmap_write(dev->map, dev->regs[DW_REG_IDX_SS_SCL_HCNT], dev->ss_hcnt);
+ regmap_write(dev->map, dev->regs[DW_REG_IDX_SS_SCL_LCNT], dev->ss_lcnt);
+
+ /* Write fast mode/fast mode plus timing parameters */
+ regmap_write(dev->map, dev->regs[DW_REG_IDX_FS_SCL_HCNT], dev->fs_hcnt);
+ regmap_write(dev->map, dev->regs[DW_REG_IDX_FS_SCL_LCNT], dev->fs_lcnt);
+ }
/* Write high speed timing parameters */
regmap_write(dev->map, dev->regs[DW_REG_IDX_HS_SCL_HCNT], dev->hs_hcnt);
@@ -513,6 +603,16 @@ void i2c_dw_set_mode(struct dw_i2c_dev *dev, int mode)
dev->mode = mode;
}
+/* Not every snps,dwc-i2c instance has the SMBus block; check IC_CAPABILITIES */
+static bool i2c_dwc_has_smbus(struct dw_i2c_dev *dev)
+{
+ u32 caps = 0;
+
+ regmap_read(dev->map, dev->regs[DW_REG_IDX_CAPABILITIES], &caps);
+
+ return caps & DWC_IC_CAPABILITIES_SMBUS;
+}
+
/**
* i2c_dw_init() - Initialize the DesignWare I2C hardware
* @dev: device private data
@@ -536,8 +636,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.
+ * For DWC-i2c, need to first check if SMBus is supported
*/
- regmap_write(dev->map, dev->regs[DW_REG_IDX_SMBUS_INTR_MASK], 0);
+ if (!(dev->flags & MODEL_DWC_I2C) || i2c_dwc_has_smbus(dev))
+ regmap_write(dev->map, dev->regs[DW_REG_IDX_SMBUS_INTR_MASK], 0);
i2c_dw_write_timings(dev);
@@ -581,6 +683,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_DWC_I2C) {
+ 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 */
@@ -897,6 +1007,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_DWC_I2C)
+ 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 2fad18582db7..08a29f70c5dd 100644
--- a/drivers/i2c/busses/i2c-designware-core.h
+++ b/drivers/i2c/busses/i2c-designware-core.h
@@ -88,6 +88,7 @@ enum dw_i2c_reg_idx {
DW_REG_IDX_COMP_PARAM_1,
DW_REG_IDX_COMP_VERSION,
DW_REG_IDX_COMP_TYPE,
+ DW_REG_IDX_CAPABILITIES,
DW_REG_IDX_MAX,
};
@@ -128,6 +129,21 @@ enum dw_i2c_intr_idx {
DW_INTR_IDX_MAX,
};
+#define DWC_IC_INTR_CLR_INTR BIT(0)
+#define DWC_IC_INTR_CLR_RX_UNDER BIT(1)
+#define DWC_IC_INTR_CLR_RX_OVER BIT(2)
+#define DWC_IC_INTR_CLR_TX_OVER BIT(3)
+#define DWC_IC_INTR_CLR_RD_REQ BIT(4)
+#define DWC_IC_INTR_CLR_TX_ABRT BIT(5)
+#define DWC_IC_INTR_CLR_RX_DONE BIT(6)
+#define DWC_IC_INTR_CLR_ACTIVITY BIT(7)
+#define DWC_IC_INTR_CLR_STOP_DET BIT(8)
+#define DWC_IC_INTR_CLR_START_DET BIT(9)
+#define DWC_IC_INTR_CLR_GEN_CALL BIT(10)
+
+/* IC_CAPABILITIES bit reporting whether SMBus is present on this instance */
+#define DWC_IC_CAPABILITIES_SMBUS BIT(10)
+
#define DW_IC_INTR_DEFAULT_MASK (DW_IC_INTR_RX_FULL | \
DW_IC_INTR_TX_ABRT | \
DW_IC_INTR_STOP_DET)
@@ -340,7 +356,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_DWC_I2C BIT(12)
+#define MODEL_MASK GENMASK(12, 8)
/*
* Enable UCSI interrupt by writing 0xd at register
@@ -402,12 +419,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 offset */
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->regs[dev->intr_clr[intr]], &dummy);
+ if (dev->flags & MODEL_DWC_I2C)
+ regmap_write(dev->map, dev->regs[DW_REG_IDX_CLR_INTR], dev->intr_clr[intr]);
+ else
+ regmap_read(dev->map, dev->regs[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 f3b952f730bc..8ed4d89e631c 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, dev->regs[DW_REG_IDX_COMP_PARAM_1], &comp_param1);
- i2c_dw_release_lock(dev);
- if (ret)
- return ret;
+ if (!(dev->flags & MODEL_DWC_I2C)) {
+ ret = i2c_dw_acquire_lock(dev);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(dev->map, dev->regs[DW_REG_IDX_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_DWC_I2C) &&
+ (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;
@@ -403,8 +406,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.
+ * snps,dwc-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_DWC_I2C)) &&
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..a3cd81cc2c0b 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 = "snps,dwc-i2c", .data = (void *)MODEL_DWC_I2C },
{}
};
MODULE_DEVICE_TABLE(of, dw_i2c_of_match);
--
2.53.0