[PATCH v1 3/3] i2c: dwc: Add StarFive JHB100 I2C master/slave support
From: lianfeng.ouyang
Date: Thu May 21 2026 - 00:07:38 EST
From: Lianfeng Ouyang <lianfeng.ouyang@xxxxxxxxxxxxxxxx>
Add support for the Synopsys DesignWare Core (DWC) I2C controller
found on the StarFive JHB100 platform. The controller supports both
master and slave modes and uses a different register layout compared
to the standard DesignWare I2C IP.
Key differences handled by this driver:
1. All key registers have different offsets. The driver redefines
these offsets in i2c-dwc-core.h.
2. Interrupts are cleared by writing to a separate INTR_CLR register
(write-1-to-clear), unlike the read-to-clear scheme of the
standard IP.
3. The IC_ENABLE register has an additional TX_CMD_BLOCK control bit
which must be managed during enable/disable sequences.
4. Master and slave require separate probe callbacks and cannot rely
solely on the runtime mode switching provided by i2c_dw_set_mode().
The driver leverages the common i2c-designware-core infrastructure
exported by the previous patch, while implementing its own timing
calculation, FIFO configuration, and interrupt handling to accommodate
the DWC register offsets and bit definitions.
Signed-off-by: Lianfeng Ouyang <lianfeng.ouyang@xxxxxxxxxxxxxxxx>
---
MAINTAINERS | 7 +
drivers/i2c/busses/Kconfig | 34 +++
drivers/i2c/busses/Makefile | 3 +
drivers/i2c/busses/i2c-dwc-core.h | 184 ++++++++++++
drivers/i2c/busses/i2c-dwc-master.c | 441 ++++++++++++++++++++++++++++
drivers/i2c/busses/i2c-dwc-slave.c | 180 ++++++++++++
6 files changed, 849 insertions(+)
create mode 100644 drivers/i2c/busses/i2c-dwc-core.h
create mode 100644 drivers/i2c/busses/i2c-dwc-master.c
create mode 100644 drivers/i2c/busses/i2c-dwc-slave.c
diff --git a/MAINTAINERS b/MAINTAINERS
index d3a6b3f6b6a0..730388b94e56 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -25677,6 +25677,13 @@ L: linux-i2c@xxxxxxxxxxxxxxx
S: Supported
F: drivers/i2c/busses/i2c-designware-*
+SYNOPSYS DWC I2C DRIVER
+M: Lianfeng Ouyang <lianfeng.ouyang@xxxxxxxxxxxxxxxx>
+L: linux-i2c@xxxxxxxxxxxxxxx
+S: Supported
+F: Documentation/devicetree/bindings/i2c/snps,dwc-i2c.yaml
+F: drivers/i2c/busses/i2c-dwc-*
+
SYNOPSYS DESIGNWARE I2C DRIVER - AMDISP
M: Nirujogi Pratap <pratap.nirujogi@xxxxxxx>
M: Bin Du <bin.du@xxxxxxx>
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index 7cb6b9b864a7..7775b4f40d88 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -640,6 +640,40 @@ config I2C_DESIGNWARE_PCI
endif
+config I2C_DWC_CORE
+ tristate "Synopsys DWC I2C adapter"
+ select REGMAP
+ help
+ This is a driver for the Synopsys DesignWare CORE (DWC) I2C controller,
+ a variant of the DesignWare I2C IP with a different register layout.
+ It shares common infrastructure with the standard DesignWare I2C
+ driver (I2C_DESIGNWARE_CORE)
+
+if I2C_DWC_CORE
+
+config I2C_DWC_SLAVE
+ bool "Synopsys DWC Slave"
+ default I2C_DWC_CORE
+ select I2C_SLAVE
+ help
+ If you say yes to this option, support will be included for the
+ Synopsys DWC I2C slave adapter.
+
+ This is not a standalone module, this module compiles together with
+ i2c-dwc-core.
+
+config I2C_DWC_PLATFORM
+ tristate "Synopsys DWC Platform"
+ default I2C_DWC_CORE
+ help
+ If you say yes to this option, support will be included for the
+ Synopsys DWC I2C adapter.
+
+ This driver can also be built as a module. If so, the module
+ will be called i2c-dwc-platform.
+
+endif
+
config I2C_DIGICOLOR
tristate "Conexant Digicolor I2C driver"
depends on ARCH_DIGICOLOR || COMPILE_TEST
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 547123ab351f..3ed593011578 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -61,6 +61,9 @@ i2c-designware-platform-$(CONFIG_I2C_DESIGNWARE_BAYTRAIL) += i2c-designware-bayt
obj-$(CONFIG_I2C_DESIGNWARE_AMDISP) += i2c-designware-amdisp.o
obj-$(CONFIG_I2C_DESIGNWARE_PCI) += i2c-designware-pci.o
i2c-designware-pci-y := i2c-designware-pcidrv.o
+obj-$(CONFIG_I2C_DWC_CORE) += i2c-dwc-core.o
+i2c-dwc-core-y += i2c-dwc-master.o
+i2c-dwc-core-$(CONFIG_I2C_DWC_SLAVE) += i2c-dwc-slave.o
obj-$(CONFIG_I2C_DIGICOLOR) += i2c-digicolor.o
obj-$(CONFIG_I2C_EG20T) += i2c-eg20t.o
obj-$(CONFIG_I2C_EMEV2) += i2c-emev2.o
diff --git a/drivers/i2c/busses/i2c-dwc-core.h b/drivers/i2c/busses/i2c-dwc-core.h
new file mode 100644
index 000000000000..eba2eaf116c9
--- /dev/null
+++ b/drivers/i2c/busses/i2c-dwc-core.h
@@ -0,0 +1,184 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Synopsys DWC I2C adapter driver.
+ *
+ * Based on the TI DAVINCI I2C adapter driver.
+ *
+ * Copyright (C) 2006 Texas Instruments.
+ * Copyright (C) 2007 MontaVista Software Inc.
+ * Copyright (C) 2009 Provigent Ltd.
+ */
+
+#include <linux/bits.h>
+#include <linux/compiler_types.h>
+#include <linux/completion.h>
+#include <linux/dev_printk.h>
+#include <linux/errno.h>
+#include <linux/i2c.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+
+#define IC_DFLT_OPERATION_REG_OFFSET 0x00
+#define IC_DFLT_I2C_REG_OFFSET 0x20
+#define IC_DFLT_SMBUS_REG_OFFSET 0xFC
+
+#define DW_IC_DEFAULT_FUNCTIONALITY (I2C_FUNC_I2C | \
+ I2C_FUNC_SMBUS_BYTE | \
+ I2C_FUNC_SMBUS_BYTE_DATA | \
+ I2C_FUNC_SMBUS_WORD_DATA | \
+ I2C_FUNC_SMBUS_BLOCK_DATA | \
+ I2C_FUNC_SMBUS_I2C_BLOCK)
+
+#define DWC_IC_CTRL_OP_MODE BIT(0)
+#define DWC_IC_CTRL_SPEED_STD BIT(4)
+#define DWC_IC_CTRL_SPEED_FAST (2 << 4)
+#define DWC_IC_CTRL_SPEED_HIGH (3 << 4)
+#define DWC_IC_CTRL_SPEED_MASK GENMASK(5, 4)
+#define DWC_IC_CTRL_10BITADDR_TGT BIT(8)
+#define DWC_IC_CTRL_10BITADDR_CTRLR BIT(9)
+#define DWC_IC_CTRL_STOP_DET_IFADDRESSED BIT(10)
+#define DWC_IC_CTRL_TX_EMPTY_CTRL BIT(11)
+#define DWC_IC_CTRL_RX_FIFO_FULL_HLD_CTRL BIT(12)
+#define DWC_IC_CTRL_BUS_CLEAR_CTRL BIT(14)
+
+#define DWC_IC_DATA_CMD_DAT GENMASK(7, 0)
+#define DWC_IC_DATA_CMD_FIRST_DATA_BYTE BIT(11)
+#define DWC_IC_DATA_CMD_STOP BIT(9)
+#define DWC_IC_DATA_CMD_CMD BIT(8)
+
+/*
+ * Registers offset
+ */
+#undef DW_IC_ENABLE
+#define DW_IC_ENABLE (IC_DFLT_OPERATION_REG_OFFSET + 0x04)
+#define DWC_IC_CAPABILITIES (IC_DFLT_OPERATION_REG_OFFSET + 0x0c)
+#define DWC_IC_SMBUS_CAPABILITIES (IC_DFLT_OPERATION_REG_OFFSET + 0x18)
+
+#define DWC_IC_CTRL (IC_DFLT_I2C_REG_OFFSET + 0x04)
+#undef DW_IC_TAR
+#define DW_IC_TAR (IC_DFLT_I2C_REG_OFFSET + 0x08)
+#define DWC_IC_DAR (IC_DFLT_I2C_REG_OFFSET + 0x0C)
+#define DWC_IC_SCL_HCNT (IC_DFLT_I2C_REG_OFFSET + 0x24)
+#define DWC_IC_SCL_LCNT (IC_DFLT_I2C_REG_OFFSET + 0x28)
+#define DWC_IC_HS_SCL_HCNT (IC_DFLT_I2C_REG_OFFSET + 0x2c)
+#define DWC_IC_HS_SCL_LCNT (IC_DFLT_I2C_REG_OFFSET + 0x30)
+#undef DW_IC_SDA_HOLD
+#define DW_IC_SDA_HOLD (IC_DFLT_I2C_REG_OFFSET + 0x34)
+#define DWC_IC_SPKLEN (IC_DFLT_I2C_REG_OFFSET + 0x3c)
+#define DWC_IC_HS_SPKLEN (IC_DFLT_I2C_REG_OFFSET + 0x40)
+#define DWC_IC_SCL_STUCK_AT_LOW_TIMEOUT (IC_DFLT_I2C_REG_OFFSET + 0x44)
+#define DWC_IC_SCL_STUCK_AT_LOW_TIMEOUT_MAX (IC_DFLT_I2C_REG_OFFSET + 0x48)
+#define DWC_IC_SDA_STUCK_AT_LOW_TIMEOUT (IC_DFLT_I2C_REG_OFFSET + 0x4c)
+#undef DW_IC_DATA_CMD
+#define DW_IC_DATA_CMD (IC_DFLT_I2C_REG_OFFSET + 0x58)
+#define DWC_IC_RX_TL (IC_DFLT_I2C_REG_OFFSET + 0x5c)
+#define DWC_IC_TX_TL (IC_DFLT_I2C_REG_OFFSET + 0x60)
+#undef DW_IC_INTR_STAT
+#define DW_IC_INTR_STAT (IC_DFLT_I2C_REG_OFFSET + 0x74)
+#undef DW_IC_INTR_MASK
+#define DW_IC_INTR_MASK (IC_DFLT_I2C_REG_OFFSET + 0x78)
+#undef DW_IC_RAW_INTR_STAT
+#define DW_IC_RAW_INTR_STAT (IC_DFLT_I2C_REG_OFFSET + 0x7c)
+#define DWC_IC_INTR_CLR (IC_DFLT_I2C_REG_OFFSET + 0x80)
+#define DWC_CLR_INTR BIT(0)
+#define DWC_IC_CLR_RX_UNDER BIT(1)
+#define DWC_IC_CLR_RX_OVER BIT(2)
+#define DWC_IC_CLR_TX_OVER BIT(3)
+#define DWC_IC_CLR_RD_REQ BIT(4)
+#define DWC_IC_CLR_TX_ABRT BIT(5)
+#define DWC_IC_CLR_RX_DONE BIT(6)
+#define DWC_IC_CLR_ACTIVITY BIT(7)
+#define DWC_IC_CLR_STOP_DET BIT(8)
+#define DWC_IC_CLR_START_DET BIT(9)
+#define DWC_IC_CLR_GEN_CALL BIT(10)
+#define DWC_IC_CLR_RESTART_DET BIT(11)
+#define DWC_IC_CLR_SCL_STUCK_DET BIT(12)
+#undef DW_IC_ENABLE_STATUS
+#define DW_IC_ENABLE_STATUS (IC_DFLT_I2C_REG_OFFSET + 0x84)
+#define DWC_IC_TX_TRMNT_SOURCE (IC_DFLT_I2C_REG_OFFSET + 0x88)
+#undef DW_IC_STATUS
+#define DW_IC_STATUS (IC_DFLT_I2C_REG_OFFSET + 0x8c)
+#undef DW_IC_TXFLR
+#define DW_IC_TXFLR (IC_DFLT_I2C_REG_OFFSET + 0x90)
+#undef DW_IC_RXFLR
+#define DW_IC_RXFLR (IC_DFLT_I2C_REG_OFFSET + 0x94)
+#define DW_IC_COMP_PARAM_1 0xf4
+#undef DW_IC_COMP_VERSION
+#define DW_IC_COMP_VERSION (IC_DFLT_I2C_REG_OFFSET + 0xa4)
+#define DWC_IC_SDA_HOLD_MIN_VERS 0x3131312A /* "111*" == v1.11* */
+
+#undef DW_IC_COMP_TYPE
+#define DW_IC_COMP_TYPE (IC_DFLT_I2C_REG_OFFSET + 0xa8)
+#define DWC_IC_INTR_SCL_STUCK_AT_LOW BIT(14)
+
+#define DWC_IC_STATUS_ACTIVITY BIT(0)
+#define DWC_IC_STATUS_TFE BIT(2)
+#define DWC_IC_STATUS_RFNE BIT(3)
+#define DWC_IC_STATUS_CTRLR_ACTIVITY BIT(5)
+#define DWC_IC_STATUS_TGT_ACTIVITY BIT(6)
+
+#define DW_IC_SDA_HOLD_TX_SHIFT 0
+#define DW_IC_SDA_HOLD_TX_MASK GENMASK(15, 0)
+
+#define DW_IC_ERR_TX_ABRT 0x1
+
+#define DWC_IC_TAR_SMBUS_QUICK_CMD BIT(16)
+#define DWC_IC_TAR_SPECIAL BIT(11)
+
+#define DWC_IC_COMP_PARAM_1_SPEED_MODE_HIGH (BIT(4) | BIT(5))
+#define DWC_IC_COMP_PARAM_1_SPEED_MODE_MASK GENMASK(5, 4)
+
+#define DWC_IC_SMBUS_ARP_CTRL (IC_DFLT_SMBUS_REG_OFFSET + 0x8)
+#define DWC_IC_SMBUS_INTR_STAT (IC_DFLT_SMBUS_REG_OFFSET + 0x28)
+#define DWC_IC_SMBUS_INTR_CLR (IC_DFLT_SMBUS_REG_OFFSET + 0x34)
+#define DWC_CLR_SMBUS_ALERT_DET BIT(10)
+#define DWC_R_SMBUS_ALERT_DET BIT(10)
+
+#define DWC_IC_DAR4_EN 19
+#define DWC_IC_DAR3_EN 18
+#define DWC_IC_DAR2_EN 17
+#define DWC_IC_DAR_EN 16
+
+#define DWC_IC_ENABLE_DAR_EN BIT(DWC_IC_DAR_EN)
+#define DWC_IC_ENABLE_DAR2_EN BIT(DWC_IC_DAR2_EN)
+#define DWC_IC_ENABLE_DAR3_EN BIT(DWC_IC_DAR3_EN)
+#define DWC_IC_ENABLE_DAR4_EN BIT(DWC_IC_DAR4_EN)
+
+#define DW_IC_ENABLE_TX_CMD_BLOCK BIT(2)
+
+#define DWC_IC_SMBUS 10
+
+#define DWC_IC_CAPABILITIES_IC_SMBUS BIT(DWC_IC_SMBUS)
+
+#define DWC_IC_SMBUS_ARP 2
+
+#define DWC_IC_SMBUS_CAPABILITIES_SMBUS_ARP BIT(DWC_IC_SMBUS_ARP)
+
+#define DWC_IC_SMBUS_ARP_CTRL_NARP_DEVICE_TYPE 0
+
+#undef TXGBE_RX_FIFO_DEPTH
+#define TXGBE_RX_FIFO_DEPTH 0
+
+extern const struct i2c_algorithm i2c_dw_slave_algo;
+int i2c_dw_probe_slave(struct dw_i2c_dev *dev);
+int i2c_dw_probe_master(struct dw_i2c_dev *dev);
+void i2c_dw_read_clear_intrbits_common(struct dw_i2c_dev *dev);
+
+static inline void __i2c_dw_enable(struct dw_i2c_dev *dev)
+{
+ int val;
+
+ dev->status |= STATUS_ACTIVE;
+ regmap_read(dev->map, DW_IC_ENABLE, &val);
+ regmap_write(dev->map, DW_IC_ENABLE,
+ ((val & ~DW_IC_ENABLE_TX_CMD_BLOCK) | DW_IC_ENABLE_ENABLE));
+}
+
+static inline void __i2c_dw_disable_nowait(struct dw_i2c_dev *dev)
+{
+ int val;
+
+ regmap_read(dev->map, DW_IC_ENABLE, &val);
+ regmap_write(dev->map, DW_IC_ENABLE, val & ~DW_IC_ENABLE_ENABLE);
+ dev->status &= ~STATUS_ACTIVE;
+}
diff --git a/drivers/i2c/busses/i2c-dwc-master.c b/drivers/i2c/busses/i2c-dwc-master.c
new file mode 100644
index 000000000000..6e8d1828f99e
--- /dev/null
+++ b/drivers/i2c/busses/i2c-dwc-master.c
@@ -0,0 +1,441 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Synopsys DWC I2C adapter driver (master only).
+ *
+ * Based on the TI DAVINCI I2C adapter driver.
+ *
+ * Copyright (C) 2006 Texas Instruments.
+ * Copyright (C) 2007 MontaVista Software Inc.
+ * Copyright (C) 2009 Provigent Ltd.
+ */
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/export.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <linux/units.h>
+
+#include "i2c-designware-core.h"
+
+#define AMD_TIMEOUT_MIN_US 25
+#define AMD_TIMEOUT_MAX_US 250
+#define AMD_MASTERCFG_MASK GENMASK(15, 0)
+
+/**
+ * i2c_dwc_scl_hcnt() - Calculate SCL HCNT
+ * @ic_clk: Input clock in kHz
+ * @thigh: Duration in ns of logic 1 to generate
+ * @tr: SCL rise time in ns
+ * @spk_cnt: Spike count
+ */
+static u32 i2c_dwc_scl_hcnt(u32 ic_clk, u32 thigh, u32 tr, u32 spk_cnt)
+{
+ u64 min_thigh_cnt, rise_cnt;
+
+ /* Formula: cnt = f_kHz * t_ns * 10^(-6) */
+ min_thigh_cnt = DIV_ROUND_CLOSEST_ULL((u64)ic_clk * thigh, MICRO);
+ rise_cnt = DIV_ROUND_CLOSEST_ULL((u64)ic_clk * tr, MICRO);
+
+ return max(5, min_thigh_cnt + rise_cnt - spk_cnt - 3);
+}
+
+/**
+ * i2c_dwc_scl_lcnt() - Calculate SCL LCNT
+ * @ic_clk: Input clock in kHz
+ * @tlow: Duration in ns of logic 0 to generate
+ * @tf: SCL fall time in ns
+ */
+static u32 i2c_dwc_scl_lcnt(u32 ic_clk, u32 tlow, u32 tf)
+{
+ u64 min_tlow_cnt, fall_cnt;
+
+ /* Formula: cnt = f_kHz * t_ns * 10^(-6) */
+ min_tlow_cnt = DIV_ROUND_CLOSEST_ULL((u64)ic_clk * tlow, MICRO);
+ fall_cnt = DIV_ROUND_CLOSEST_ULL((u64)ic_clk * tf, MICRO);
+
+ return max(6, min_tlow_cnt + fall_cnt);
+}
+
+static void i2c_dwc_configure_fifo_master(struct dw_i2c_dev *dev)
+{
+ /* Configure Tx/Rx FIFO threshold levels */
+ regmap_write(dev->map, DWC_IC_TX_TL, dev->tx_fifo_depth / 2);
+ regmap_write(dev->map, DWC_IC_RX_TL, 0);
+
+ /* Configure the I2C master */
+ regmap_write(dev->map, DWC_IC_CTRL, dev->master_cfg);
+}
+
+static int i2c_dwc_set_timings_master(struct dw_i2c_dev *dev)
+{
+ u32 scl_falling_time = 0, scl_rising_time = 0;
+ u32 scl_high_time = 0, scl_low_time = 0;
+ struct i2c_timings *t = &dev->timings;
+ unsigned int comp_param1;
+ u32 ic_clk, spk_cnt;
+ int ret;
+
+ ret = i2c_dw_acquire_lock(dev);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(dev->map, DWC_IC_CTRL, &comp_param1);
+ i2c_dw_release_lock(dev);
+ if (ret)
+ return ret;
+
+ ic_clk = i2c_dw_clk_rate(dev);
+
+ /* 50ns maximum spike */
+ spk_cnt = DIV_ROUND_CLOSEST_ULL((u64)ic_clk * 50, MICRO);
+
+ regmap_write(dev->map, DWC_IC_HS_SPKLEN, spk_cnt);
+ regmap_write(dev->map, DWC_IC_SPKLEN, spk_cnt);
+
+ /* Parse user defined rise time and fall time*/
+ if (t->scl_rise_ns)
+ scl_rising_time = t->scl_rise_ns;
+
+ if (t->scl_fall_ns)
+ scl_falling_time = t->scl_fall_ns;
+
+ /* Ensure the rise time and fall time should not lower than t_rise_max
+ * and t_fall_max specification, else it would run faster than expected
+ * frequency
+ */
+ switch (t->bus_freq_hz) {
+ case I2C_MAX_STANDARD_MODE_FREQ:
+ scl_rising_time = max(scl_rising_time, 1000);
+ scl_falling_time = max(scl_falling_time, 300);
+ scl_high_time = 4000; /* tHIGH_min = 4.0 us */
+ scl_low_time = 4700; /* tLOW_min = 4.7 us */
+ break;
+ case I2C_MAX_FAST_MODE_FREQ:
+ scl_rising_time = max(scl_rising_time, 300);
+ scl_falling_time = max(scl_falling_time, 300);
+ scl_high_time = 600; /* tHIGH_min = 600 ns */
+ scl_low_time = 1300; /* tLOW_min = 1.3 us */
+ break;
+ case I2C_MAX_FAST_MODE_PLUS_FREQ:
+ scl_rising_time = max(scl_rising_time, 120);
+ scl_falling_time = max(scl_falling_time, 120);
+ scl_high_time = 260; /* tHIGH_min = 260 ns */
+ scl_low_time = 500; /* tLOW_min = 500 ns */
+ break;
+ case I2C_MAX_HIGH_SPEED_MODE_FREQ:
+ scl_rising_time = max(scl_rising_time, 40);
+ scl_falling_time = max(scl_falling_time, 40);
+ scl_high_time = 60; /* tHIGH_min = 60 ns */
+ scl_low_time = 160; /* tLOW_min = 160 ns */
+ break;
+ default:
+ scl_rising_time = max(scl_rising_time, 1000);
+ scl_falling_time = max(scl_falling_time, 300);
+ scl_high_time = 4000; /* tHIGH_min = 4.0 us */
+ scl_low_time = 4700; /* tLOW_min = 4.7 us */
+ break;
+ }
+
+ ic_clk = i2c_dw_clk_rate(dev);
+
+ if (!dev->scl_hcnt || !dev->scl_lcnt) {
+ dev->scl_hcnt = i2c_dwc_scl_hcnt(ic_clk, scl_high_time,
+ scl_rising_time, spk_cnt);
+ dev->scl_lcnt = i2c_dwc_scl_lcnt(ic_clk, scl_low_time,
+ scl_falling_time);
+ }
+
+ dev_dbg(dev->dev, "Bus speed: %s\n", i2c_freq_mode_string(t->bus_freq_hz));
+
+ /* Check is high speed possible and fall back to fast mode if not */
+ if ((dev->master_cfg & DWC_IC_CTRL_SPEED_MASK) == DWC_IC_CTRL_SPEED_HIGH) {
+ if ((comp_param1 & DWC_IC_COMP_PARAM_1_SPEED_MODE_MASK)
+ != DWC_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 &= ~DWC_IC_CTRL_SPEED_MASK;
+ dev->master_cfg |= DWC_IC_CTRL_SPEED_FAST;
+ dev->hs_hcnt = 0;
+ dev->hs_lcnt = 0;
+
+ /* Replace with I2C_MAX_FAST_MODE_PLUS_FREQ */
+ scl_rising_time = max(scl_rising_time, 120);
+ scl_falling_time = max(scl_falling_time, 120);
+ scl_high_time = 260; /* tHIGH_min = 260 ns */
+ scl_low_time = 500; /* tLOW_min = 500 ns */
+
+ dev->scl_hcnt = i2c_dwc_scl_hcnt(ic_clk, scl_high_time,
+ scl_rising_time, spk_cnt);
+ dev->scl_lcnt = i2c_dwc_scl_lcnt(ic_clk, scl_low_time,
+ scl_falling_time);
+ } else if (!dev->hs_hcnt || !dev->hs_lcnt) {
+ dev->hs_hcnt = dev->scl_hcnt;
+ dev->hs_lcnt = dev->scl_lcnt;
+ }
+ dev_dbg(dev->dev, "High Speed Mode HCNT:LCNT = %d:%d\n",
+ dev->hs_hcnt, dev->hs_lcnt);
+ } else {
+ dev_dbg(dev->dev, "HCNT:LCNT: %d:%d\n", dev->scl_hcnt, dev->scl_lcnt);
+ }
+
+ return 0;
+}
+
+int i2c_dw_init(struct dw_i2c_dev *dev)
+{
+ int ret;
+
+ ret = i2c_dw_acquire_lock(dev);
+ if (ret)
+ return ret;
+
+ /* Disable the adapter */
+ __i2c_dw_disable(dev);
+
+ /* Write standard speed timing parameters */
+ regmap_write(dev->map, DWC_IC_SCL_HCNT, dev->scl_hcnt);
+ regmap_write(dev->map, DWC_IC_SCL_LCNT, dev->scl_lcnt);
+
+ /* Write high speed timing parameters if supported */
+ if (dev->hs_hcnt && dev->hs_lcnt) {
+ regmap_write(dev->map, DWC_IC_HS_SCL_HCNT, dev->hs_hcnt);
+ regmap_write(dev->map, DWC_IC_HS_SCL_LCNT, dev->hs_lcnt);
+ }
+
+ /* Write SDA hold time if supported */
+ if (dev->sda_hold_time)
+ regmap_write(dev->map, DW_IC_SDA_HOLD, dev->sda_hold_time);
+
+ i2c_dwc_configure_fifo_master(dev);
+ i2c_dw_release_lock(dev);
+
+ return 0;
+}
+
+static int i2c_dwc_set_sda_hold_time(struct dw_i2c_dev *dev)
+{
+ int ret = regmap_write_bits(dev->map,
+ DW_IC_SDA_HOLD,
+ DW_IC_SDA_HOLD_TX_MASK,
+ dev->sda_hold_time);
+ return ret;
+}
+
+void i2c_dw_xfer_init(struct dw_i2c_dev *dev)
+{
+ struct i2c_msg *msgs = dev->msgs;
+ u32 ic_ctrl = 0, ic_tar = 0;
+ unsigned int dummy;
+
+ /* Disable the adapter */
+ __i2c_dw_disable(dev);
+
+ /* If the slave address is ten bit address, enable 10BITADDR */
+ if (msgs[dev->msg_write_idx].flags & I2C_M_TEN) {
+ ic_ctrl = DWC_IC_CTRL_10BITADDR_CTRLR;
+ /*
+ * If I2C_DYNAMIC_TAR_UPDATE is set, the 10-bit addressing
+ * mode has to be enabled via bit 12 of IC_TAR register.
+ * We set it always as I2C_DYNAMIC_TAR_UPDATE can't be
+ * detected from registers.
+ */
+ ic_tar = DW_IC_TAR_10BITADDR_MASTER;
+ }
+
+ /* From i2c-core-smbus.c, I2C_SMBUS_QUICK intentionally has msg[0].len = 0 */
+ if (dev->msgs[0].len == 0)
+ ic_tar |= DWC_IC_TAR_SMBUS_QUICK_CMD | DWC_IC_TAR_SPECIAL;
+
+ regmap_update_bits(dev->map, DWC_IC_CTRL, DWC_IC_CTRL_10BITADDR_CTRLR,
+ ic_ctrl);
+
+ /*
+ * Set the slave (target) address and enable 10-bit addressing mode
+ * if applicable.
+ */
+ regmap_write(dev->map, DW_IC_TAR,
+ msgs[dev->msg_write_idx].addr | ic_tar);
+
+ /* Enforce disabled interrupts (due to HW issues) */
+ regmap_write(dev->map, DW_IC_INTR_MASK, 0);
+
+ /* Enable the adapter */
+ __i2c_dw_enable(dev);
+
+ /* Dummy read to avoid the register getting stuck on Bay Trail */
+ regmap_read(dev->map, DW_IC_ENABLE_STATUS, &dummy);
+
+ /* Clear and enable interrupts */
+ regmap_write(dev->map, DWC_IC_INTR_CLR, DWC_CLR_INTR);
+ regmap_write(dev->map, DW_IC_INTR_MASK, DW_IC_INTR_MASTER_MASK);
+}
+
+void i2c_dw_read_clear_intrbits_common(struct dw_i2c_dev *dev)
+{
+ unsigned int stat;
+
+ /*
+ * The IC_INTR_STAT register just indicates "enabled" interrupts.
+ * The unmasked raw version of interrupt status bits is available
+ * in the IC_RAW_INTR_STAT register.
+ *
+ * That is,
+ * stat = readl(IC_INTR_STAT);
+ * equals to,
+ * stat = readl(IC_RAW_INTR_STAT) & readl(IC_INTR_MASK);
+ *
+ * The raw version might be useful for debugging purposes.
+ */
+ regmap_read(dev->map, DW_IC_INTR_STAT, &stat);
+ /*
+ * Do not use the IC_CLR_INTR register to clear interrupts, or
+ * you'll miss some interrupts, triggered during the period from
+ * readl(IC_INTR_STAT) to readl(IC_CLR_INTR).
+ *
+ * Instead, use the separately-prepared IC_CLR_* registers.
+ */
+ if (stat & DW_IC_INTR_RX_UNDER)
+ regmap_write(dev->map, DWC_IC_INTR_CLR, DWC_IC_CLR_RX_UNDER);
+ if (stat & DW_IC_INTR_RX_OVER)
+ regmap_write(dev->map, DWC_IC_INTR_CLR, DWC_IC_CLR_RX_OVER);
+ if (stat & DW_IC_INTR_TX_OVER)
+ regmap_write(dev->map, DWC_IC_INTR_CLR, DWC_IC_CLR_TX_OVER);
+ if (stat & DW_IC_INTR_RD_REQ)
+ regmap_write(dev->map, DWC_IC_INTR_CLR, DWC_IC_CLR_RD_REQ);
+ if (stat & DW_IC_INTR_RX_DONE)
+ regmap_write(dev->map, DWC_IC_INTR_CLR, DWC_IC_CLR_RX_DONE);
+ if (stat & DW_IC_INTR_ACTIVITY)
+ regmap_write(dev->map, DWC_IC_INTR_CLR, DWC_IC_CLR_ACTIVITY);
+ if (stat & DW_IC_INTR_START_DET)
+ regmap_write(dev->map, DWC_IC_INTR_CLR, DWC_IC_CLR_START_DET);
+ if (stat & DW_IC_INTR_GEN_CALL)
+ regmap_write(dev->map, DWC_IC_INTR_CLR, DWC_IC_CLR_GEN_CALL);
+}
+EXPORT_SYMBOL_GPL(i2c_dw_read_clear_intrbits_common);
+
+u32 i2c_dw_read_clear_intrbits(struct dw_i2c_dev *dev)
+{
+ unsigned int stat;
+
+ /*
+ * The IC_INTR_STAT register just indicates "enabled" interrupts.
+ * The unmasked raw version of interrupt status bits is available
+ * in the IC_RAW_INTR_STAT register.
+ *
+ * That is,
+ * stat = readl(IC_INTR_STAT);
+ * equals to,
+ * stat = readl(IC_RAW_INTR_STAT) & readl(IC_INTR_MASK);
+ *
+ * The raw version might be useful for debugging purposes.
+ */
+ regmap_read(dev->map, DW_IC_INTR_STAT, &stat);
+
+ 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, DWC_IC_TX_TRMNT_SOURCE, &dev->abort_source);
+ regmap_write(dev->map, DWC_IC_INTR_CLR, DWC_IC_CLR_TX_ABRT);
+ }
+ if ((stat & DW_IC_INTR_STOP_DET) &&
+ (dev->rx_outstanding == 0 || (stat & DW_IC_INTR_RX_FULL)))
+ regmap_write(dev->map, DWC_IC_INTR_CLR, DWC_IC_CLR_STOP_DET);
+ if (stat & DW_IC_INTR_RESTART_DET)
+ regmap_write(dev->map, DWC_IC_INTR_CLR, DWC_IC_CLR_RESTART_DET);
+ if (stat & DWC_IC_INTR_SCL_STUCK_AT_LOW)
+ regmap_write(dev->map, DWC_IC_INTR_CLR, DWC_IC_CLR_SCL_STUCK_DET);
+
+ i2c_dw_read_clear_intrbits_common(dev);
+
+ return stat;
+}
+
+void i2c_dw_configure_master(struct dw_i2c_dev *dev)
+{
+ struct i2c_timings *t = &dev->timings;
+
+ dev->functionality = I2C_FUNC_10BIT_ADDR | DW_IC_DEFAULT_FUNCTIONALITY |
+ I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_PEC;
+
+ dev->master_cfg = DWC_IC_CTRL_OP_MODE;
+
+ dev->mode = DW_IC_MASTER;
+
+ switch (t->bus_freq_hz) {
+ case I2C_MAX_STANDARD_MODE_FREQ:
+ dev->master_cfg |= DWC_IC_CTRL_SPEED_STD;
+ break;
+ case I2C_MAX_FAST_MODE_FREQ:
+ dev->master_cfg |= DWC_IC_CTRL_SPEED_FAST;
+ break;
+ case I2C_MAX_FAST_MODE_PLUS_FREQ:
+ dev->master_cfg |= DWC_IC_CTRL_SPEED_FAST;
+ break;
+ case I2C_MAX_HIGH_SPEED_MODE_FREQ:
+ dev->master_cfg |= DWC_IC_CTRL_SPEED_HIGH;
+ break;
+ default:
+ dev->master_cfg |= DWC_IC_CTRL_SPEED_FAST;
+ }
+
+ dev->set_sda_hold_time = i2c_dwc_set_sda_hold_time;
+}
+
+int i2c_dw_probe_master(struct dw_i2c_dev *dev)
+{
+ struct i2c_adapter *adap = &dev->adapter;
+ unsigned int ic_ctrl;
+ int ret;
+
+ init_completion(&dev->cmd_complete);
+
+ ret = i2c_dwc_set_timings_master(dev);
+ if (ret)
+ return ret;
+
+ /* Lock the bus for accessing DWC_IC_CTRL */
+ ret = i2c_dw_acquire_lock(dev);
+ if (ret)
+ return ret;
+
+ /*
+ * On AMD platforms BIOS advertises the bus clear feature
+ * and enables the SCL/SDA stuck low. SMU FW does the
+ * bus recovery process. Driver should not ignore this BIOS
+ * advertisement of bus clear feature.
+ */
+ ret = regmap_read(dev->map, DWC_IC_CTRL, &ic_ctrl);
+ i2c_dw_release_lock(dev);
+ if (ret)
+ return ret;
+
+ if (ic_ctrl & DWC_IC_CTRL_BUS_CLEAR_CTRL)
+ dev->master_cfg |= DWC_IC_CTRL_BUS_CLEAR_CTRL;
+
+ ret = i2c_dw_init(dev);
+ if (ret)
+ return ret;
+
+ snprintf(adap->name, sizeof(adap->name),
+ "Synopsys DWC I2C adapter");
+
+ ret = i2c_dw_init_recovery_info(dev);
+ if (ret)
+ return ret;
+
+ return ret;
+}
+
+MODULE_AUTHOR("Sankarshan shukla <sashukla@xxxxxxxxxxxx>");
+MODULE_DESCRIPTION("Synopsys DWC I2C bus master adapter");
+MODULE_LICENSE("GPL");
diff --git a/drivers/i2c/busses/i2c-dwc-slave.c b/drivers/i2c/busses/i2c-dwc-slave.c
new file mode 100644
index 000000000000..5369eb881238
--- /dev/null
+++ b/drivers/i2c/busses/i2c-dwc-slave.c
@@ -0,0 +1,180 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Synopsys DWC I2C adapter driver (slave only).
+ *
+ * Based on the Synopsys DWC I2C adapter driver (master).
+ *
+ * Copyright (C) 2016 Synopsys Inc.
+ */
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/of_platform.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+
+#include "i2c-designware-core.h"
+
+void i2c_dw_set_mode(struct dw_i2c_dev *dev, int mode)
+{
+ if (dev->mode == DW_IC_SLAVE && mode == DW_IC_SLAVE) {
+ regmap_write(dev->map, DWC_IC_DAR, dev->slave->addr);
+ __i2c_dw_enable(dev);
+ dev->status = 0;
+ }
+}
+
+static void i2c_dwc_configure_fifo_slave(struct dw_i2c_dev *dev)
+{
+ /* Configure the I2C slave. */
+ regmap_write(dev->map, DWC_IC_CTRL, 0);
+ regmap_write(dev->map, DWC_IC_CTRL, dev->slave_cfg);
+ regmap_write(dev->map, DW_IC_INTR_MASK, DW_IC_INTR_SLAVE_MASK);
+}
+
+/**
+ * i2c_dwc_init_slave() - Initialize the DWC i2c slave hardware
+ * @dev: device private data
+ *
+ * This function configures and enables the I2C in slave mode.
+ * This function is called during I2C init function, and in case of timeout at
+ * run time.
+ */
+static int i2c_dwc_init_slave(struct dw_i2c_dev *dev)
+{
+ int ret;
+ int r_value;
+
+ ret = i2c_dw_acquire_lock(dev);
+ if (ret)
+ return ret;
+
+ /* Disable the adapter. */
+ __i2c_dw_disable(dev);
+
+ /* Write SDA hold time if supported */
+ if (dev->sda_hold_time)
+ regmap_write(dev->map, DW_IC_SDA_HOLD, dev->sda_hold_time);
+
+ regmap_read(dev->map, DWC_IC_CAPABILITIES, &r_value);
+ if (r_value & DWC_IC_CAPABILITIES_IC_SMBUS) {
+ regmap_read(dev->map, DWC_IC_SMBUS_CAPABILITIES, &r_value);
+ if (r_value & DWC_IC_SMBUS_CAPABILITIES_SMBUS_ARP) {
+ regmap_write(dev->map, DW_IC_ENABLE, DWC_IC_ENABLE_DAR_EN);
+ regmap_write(dev->map, DWC_IC_SMBUS_ARP_CTRL,
+ DWC_IC_SMBUS_ARP_CTRL_NARP_DEVICE_TYPE);
+ }
+ }
+
+ i2c_dwc_configure_fifo_slave(dev);
+ i2c_dw_release_lock(dev);
+
+ return 0;
+}
+
+u32 i2c_dw_read_clear_intrbits_slave(struct dw_i2c_dev *dev)
+{
+ unsigned int stat;
+ unsigned int smbus_stat;
+
+ /*
+ * The IC_INTR_STAT register just indicates "enabled" interrupts.
+ * The unmasked raw version of interrupt status bits is available
+ * in the IC_RAW_INTR_STAT register.
+ *
+ * That is,
+ * stat = readl(IC_INTR_STAT);
+ * equals to,
+ * stat = readl(IC_RAW_INTR_STAT) & readl(IC_INTR_MASK);
+ *
+ * The raw version might be useful for debugging purposes.
+ */
+ regmap_read(dev->map, DW_IC_INTR_STAT, &stat);
+ regmap_read(dev->map, DWC_IC_SMBUS_INTR_STAT, &smbus_stat);
+
+ /*
+ * Do not use the IC_CLR_INTR register to clear interrupts, or
+ * you'll miss some interrupts, triggered during the period from
+ * readl(IC_INTR_STAT) to readl(IC_CLR_INTR).
+ *
+ * Instead, use the separately-prepared IC_CLR_* registers.
+ */
+
+ if (stat & DW_IC_INTR_TX_ABRT)
+ regmap_write(dev->map, DWC_IC_INTR_CLR, DWC_IC_CLR_TX_ABRT);
+ if (stat & DW_IC_INTR_STOP_DET)
+ regmap_write(dev->map, DWC_IC_INTR_CLR, DWC_IC_CLR_STOP_DET);
+ if (smbus_stat & DWC_R_SMBUS_ALERT_DET)
+ regmap_write(dev->map, DWC_IC_SMBUS_INTR_CLR, DWC_CLR_SMBUS_ALERT_DET);
+
+ i2c_dw_read_clear_intrbits_common(dev);
+
+ return stat;
+}
+
+#if IS_ENABLED(CONFIG_I2C_DWC_SLAVE)
+static int i2c_dwc_xfer_ms(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
+{
+ struct dw_i2c_dev *dev = i2c_get_adapdata(adap);
+
+ if (!dev->ms_adapter)
+ return -ENXIO;
+
+ return i2c_dw_xfer(dev->ms_adapter, msgs, num);
+}
+#endif
+
+const struct i2c_algorithm i2c_dw_slave_algo = {
+ .master_xfer = i2c_dwc_xfer_ms,
+ .functionality = i2c_dw_func,
+ .reg_slave = i2c_dw_reg_slave,
+ .unreg_slave = i2c_dw_unreg_slave,
+};
+
+void i2c_dw_configure_slave(struct dw_i2c_dev *dev)
+{
+ dev->functionality = I2C_FUNC_SLAVE | DW_IC_DEFAULT_FUNCTIONALITY;
+
+ dev->slave_cfg = DWC_IC_CTRL_RX_FIFO_FULL_HLD_CTRL |
+ DWC_IC_CTRL_STOP_DET_IFADDRESSED;
+
+ dev->mode = DW_IC_SLAVE;
+}
+
+int i2c_dw_probe_slave(struct dw_i2c_dev *dev)
+{
+ struct i2c_adapter *adap = &dev->adapter;
+ struct device_node *ms_node;
+ int ret;
+
+ ret = i2c_dwc_init_slave(dev);
+ if (ret)
+ return ret;
+
+ snprintf(adap->name, sizeof(adap->name), "Synopsys DWC I2C Slave adapter");
+
+ /* Get the specified I2C master adapter from DTS for MCTP */
+ ms_node = of_parse_phandle(dev->dev->of_node, "starfive,mctp-i2c-ms", 0);
+ if (ms_node) {
+ struct platform_device *ms_pdev = of_find_device_by_node(ms_node);
+
+ if (ms_pdev) {
+ struct dw_i2c_dev *ms_dev = platform_get_drvdata(ms_pdev);
+
+ if (ms_dev)
+ dev->ms_adapter = &ms_dev->adapter;
+ }
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(i2c_dw_probe_slave);
+
+MODULE_AUTHOR("Sankarshan shukla <sashukla@xxxxxxxxxxxx>");
+MODULE_DESCRIPTION("Synopsys DWC I2C bus slave adapter");
+MODULE_LICENSE("GPL");
--
2.43.0