* [PATCH v4 0/2] i2c: Add Synopsys Advanced I2C (DWC_i2c) support for TI TDA54
@ 2026-10-27 11:30 Aniket Limaye
2026-10-27 11:30 ` [PATCH v4 1/2] dt-bindings: i2c: Add Synopsys DWC_i2c controller Aniket Limaye
2026-10-27 11:30 ` [PATCH v4 2/2] i2c: Add Synopsys Advanced I2C (DWC_i2c) driver Aniket Limaye
0 siblings, 2 replies; 3+ messages in thread
From: Aniket Limaye @ 2026-10-27 11:30 UTC (permalink / raw)
To: Andi Shyti, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel
Cc: linux-i2c, devicetree, linux-kernel, Aniket Limaye, lianfeng.ouyang
Add support for the Synopsys Advanced I2C (DWC_i2c [0])
controller present on the TI TDA54 SoC.
This series supports controller mode only: no target mode support. TI
TDA54's DWC_i2c instance does not implement an SMBus block, so the new
driver does not touch any SMBus registers.
* Why a separate driver:
Earlier revisions of this series extended the existing DW_apb_i2c
driver (drivers/i2c/busses/i2c-designware-*) to also handle DWC_i2c,
selecting between the two register layouts at runtime via a new
MODEL_TI_TDA54 flag.
This revision instead adds a new, separate, standalone driver
(drivers/i2c/busses/i2c-dwc-adv.c) and a new binding
(Documentation/devicetree/bindings/i2c/snps,dwc-adv-i2c.yaml), since
DWC_i2c is not register compatible with DW_apb_i2c. Based on review
feedback from previous versions, extending the existing DW_apb_i2c is
not attempted to get away from any hacks and workarounds added there
over the years and to avoid all of the complexity of v3 resulting from
updating each regmap access.
The cost is that the message xfers, abort handling and timing formulas
follow i2c-designware-master.c closely and are duplicated, so fixes
there have to be mirrored by hand. Sharing them would need every
register access, and the interrupt acknowledgment abstracted, with some
possible special casing; same as v3.
* Delta from DW_apb_i2c:
DWC_i2c differs from DW_apb_i2c as follows:
- Register layout (see below): DW_apb_i2c uses a flat set of fixed
register offsets. DWC_i2c groups registers into blocks (OPERATION,
CORE, TIMING, FIFO, INTR, DEBUG, ...) that are discovered at runtime
by walking a chain of block headers, each encoding the length to the
next block and an ID that the driver validates.
- CON register bitfield (see below): DWC_i2c's CON register has no
RESTART_EN or SLAVE_DISABLE bits; restart is implicitly always
enabled in hardware.
- Interrupt acknowledgment: DW_apb_i2c acknowledges interrupts by
reading from N dedicated CLR_* registers. DWC_i2c instead writes a
bitmask to a single CLR_INTR register.
- Shared SS/FS Timing registers: DW_apb_i2c has separate HCNT/LCNT
register pairs for standard and fast speed. DWC_i2c shares a single
HCNT/LCNT pair between the two; whichever value set matches the
configured speed is written at init time.
- Bus capacitance for HS mode: DW_apb_i2c modelled this through DT,
while in DWC_i2c this can be read (100/400pf) from
IC_I2C_CAPABILITIES.IC_CAP_LOADING
- FIFO depth / high-speed mode: DW_apb_i2c has a COMP_PARAM_1
register that can be used to autodetect FIFO depth and high-speed
support. DWC_i2c has no such register; FIFO depth comes from the
compatible string's driver match data instead. High speed mode is
always supported.
Register layout differences from i2c-designware (DW_apb_i2c):
(New driver does not make use of flat map like below, and instead only
stores offsets from respective headers)
1. Reg offsets DW_apb_i2c DWC_i2c (TDA54)
DWC_ADV_IC_I2C_CAPABILITIES NA 0x10
DWC_ADV_IC_I2C_BLOCK_OFFSET NA 0x14
DWC_ADV_IC_I2C_CORE_HEADER NA 0x28
DWC_ADV_IC_TIMING_CTRL_HEADER NA 0x44
DWC_ADV_IC_FIFO_CTRL_HEADER NA 0x7c
DWC_ADV_IC_INTR_DEBUG_HEADER NA 0xbc
DWC_ADV_IC_HWID_HEADER NA 0xf8
[DW_IC_CON] 0x00 0x2c
[DW_IC_TAR] 0x04 0x30
[DW_IC_SAR] 0x08 0x34
[DW_IC_DATA_CMD] 0x10 0x80
[DW_IC_SS_SCL_HCNT] 0x14 0x4c
[DW_IC_SS_SCL_LCNT] 0x18 0x50
[DW_IC_FS_SCL_HCNT] 0x1c NA /* same as SS */
[DW_IC_FS_SCL_LCNT] 0x20 NA /* same as SS */
[DW_IC_HS_SCL_HCNT] 0x24 0x54
[DW_IC_HS_SCL_LCNT] 0x28 0x58
[DW_IC_INTR_STAT] 0x2c 0xc0
[DW_IC_INTR_MASK] 0x30 0xc4
[DW_IC_RAW_INTR_STAT] 0x34 0xc8
[DW_IC_RX_TL] 0x38 0x84
[DW_IC_TX_TL] 0x3c 0x88
[DW_IC_CLR_INTR] 0x40 0xcc
[DW_IC_CLR_RX_UNDER] 0x44 NA
[DW_IC_CLR_RX_OVER] 0x48 NA
[DW_IC_CLR_TX_OVER] 0x4c NA
[DW_IC_CLR_RD_REQ] 0x50 NA
[DW_IC_CLR_TX_ABRT] 0x54 NA
[DW_IC_CLR_RX_DONE] 0x58 NA
[DW_IC_CLR_ACTIVITY] 0x5c NA
[DW_IC_CLR_STOP_DET] 0x60 NA
[DW_IC_CLR_START_DET] 0x64 NA
[DW_IC_CLR_GEN_CALL] 0x68 NA
[DW_IC_ENABLE] 0x6c 0x04
[DW_IC_STATUS] 0x70 0xd8
[DW_IC_TXFLR] 0x74 0xdc
[DW_IC_RXFLR] 0x78 0xe0
[DW_IC_SDA_HOLD] 0x7c 0x5c
[DW_IC_TX_ABRT_SOURCE] 0x80 0xd4
[DW_IC_ENABLE_STATUS] 0x9c 0xd0
[DW_IC_CLR_RESTART_DET] 0xa8 NA
[DW_IC_SMBUS_INTR_STAT] 0xc8 NA
[DW_IC_SMBUS_INTR_MASK] 0xcc NA
[DW_IC_CLR_SMBUS_INTR] 0xd4 NA
[DW_IC_COMP_PARAM_1] 0xf4 NA
[DW_IC_COMP_VERSION] 0xf8 0x100
[DW_IC_COMP_TYPE] 0xfc 0x104
2. DW_IC_CON bitfields:
DW_IC_CON_MASTER BIT(0) BIT(0)
DW_IC_CON_SPEED_STD (1 << 1) (1 << 4)
DW_IC_CON_SPEED_FAST (2 << 1) (2 << 4)
DW_IC_CON_SPEED_HIGH (3 << 1) (3 << 4)
DW_IC_CON_SPEED_MASK GENMASK(2, 1) GENMASK(5, 4)
DW_IC_CON_10BITADDR_SLAVE BIT(3) BIT(8)
DW_IC_CON_10BITADDR_MASTER BIT(4) BIT(9)
DW_IC_CON_RESTART_EN BIT(5) NA
DW_IC_CON_SLAVE_DISABLE BIT(6) NA
DW_IC_CON_STOP_DET_IFADDRESSED BIT(7) BIT(10)
DW_IC_CON_TX_EMPTY_CTRL BIT(8) BIT(11)
DW_IC_CON_RX_FIFO_FULL_HLD_CTRL BIT(9) BIT(12)
DW_IC_CON_BUS_CLEAR_CTRL BIT(11) BIT(14)
The same IP is also used by the StarFive JHB100 SoC [1]. The block
header walk is meant to accommodate such SoC specific layouts, with
their own compatible and match data.
[0]: Synopsys Advanced I2C Controller Reference Manual (Version 2.00a-lca00)
[1]: https://lore.kernel.org/r/20260527085039.44435-1-lianfeng.ouyang@starfivetech.com
Signed-off-by: Aniket Limaye <a-limaye@ti.com>
---
Changes in v4:
- Drop the approach of extending the existing i2c-designware-* driver
with a MODEL_TI_TDA54 variant. Instead, add a new standalone driver
(i2c-dwc-adv.c) and a new binding (snps,dwc-adv-i2c.yaml), since
DWC_i2c is not register compatible with DW_apb_i2c. (Andy Shevchenko)
- Drop the snps,tx-fifo-depth/snps,rx-fifo-depth DT properties from
the new binding; FIFO depth is now tied to the compatible string via
driver match data instead. (Conor Dooley)
- Link to v3: https://lore.kernel.org/r/20260925-tda54-upstream-i2c-v3-0-544d74e992ff@ti.com
Changes in v3:
- [PATCH 2/3] 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. (sashiko)
- Link to v2: https://lore.kernel.org/r/20260925-tda54-upstream-i2c-v2-0-261785e58493@ti.com
Changes in v2:
- Rename compatible/flag: snps,dwc-i2c -> ti,tda54-i2c,
MODEL_DWC_I2C -> MODEL_TI_TDA54.
- 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.
- 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-0-b0b9f77be18b@ti.com
---
Aniket Limaye (2):
dt-bindings: i2c: Add Synopsys DWC_i2c controller
i2c: Add Synopsys Advanced I2C (DWC_i2c) driver
.../devicetree/bindings/i2c/snps,dwc-adv-i2c.yaml | 75 ++
drivers/i2c/busses/Kconfig | 11 +
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-dwc-adv.c | 1333 ++++++++++++++++++++
4 files changed, 1420 insertions(+)
---
base-commit: 8f7df005a7564bd7ffa75da9c3085d5b996f1e16
change-id: 20260919-tda54-upstream-i2c-d0c67f16b4fc
Best regards,
--
Aniket Limaye <a-limaye@ti.com>
^ permalink raw reply [flat|nested] 3+ messages in thread
* [PATCH v4 1/2] dt-bindings: i2c: Add Synopsys DWC_i2c controller
2026-10-27 11:30 [PATCH v4 0/2] i2c: Add Synopsys Advanced I2C (DWC_i2c) support for TI TDA54 Aniket Limaye
@ 2026-10-27 11:30 ` Aniket Limaye
2026-10-27 11:30 ` [PATCH v4 2/2] i2c: Add Synopsys Advanced I2C (DWC_i2c) driver Aniket Limaye
1 sibling, 0 replies; 3+ messages in thread
From: Aniket Limaye @ 2026-10-27 11:30 UTC (permalink / raw)
To: Andi Shyti, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel
Cc: linux-i2c, devicetree, linux-kernel, Aniket Limaye, lianfeng.ouyang
Add documentation for the Synopsys Advanced I2C (DWC_i2c) controller, as
found on the TI TDA54 SoC.
The block cannot work on its own and needs SoC integration, e.g. the
FIFO depths are not discoverable from the IP. Do not add a generic
compatible and only list SoC specific compatibles. The file is named
after the IP so that other SoCs integrating it can be added here.
Signed-off-by: Aniket Limaye <a-limaye@ti.com>
---
Changes in v4:
- Drop the approach of extending the existing i2c-designware-* driver
with a MODEL_TI_TDA54 variant. Instead, add a new standalone driver
(i2c-dwc-adv.c) and a new binding (snps,dwc-adv-i2c.yaml), since
DWC_i2c is not register compatible with DW_apb_i2c. (Andy Shevchenko)
- Drop the snps,tx-fifo-depth/snps,rx-fifo-depth DT properties from
the new binding; FIFO depth is now tied to the compatible string via
driver match data instead. (Conor Dooley)
- Link to v3: https://lore.kernel.org/all/20260925-tda54-upstream-i2c-v3-1-544d74e992ff@ti.com/
Changes in v2:
- Rename snps,dwc-i2c to ti,tda54-i2c
- Link to v1: https://lore.kernel.org/r/20260919-tda54-upstream-i2c-v1-1-b0b9f77be18b@ti.com/
---
.../devicetree/bindings/i2c/snps,dwc-adv-i2c.yaml | 75 ++++++++++++++++++++++
1 file changed, 75 insertions(+)
diff --git a/Documentation/devicetree/bindings/i2c/snps,dwc-adv-i2c.yaml b/Documentation/devicetree/bindings/i2c/snps,dwc-adv-i2c.yaml
new file mode 100644
index 000000000000..7ad22536825e
--- /dev/null
+++ b/Documentation/devicetree/bindings/i2c/snps,dwc-adv-i2c.yaml
@@ -0,0 +1,75 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/i2c/snps,dwc-adv-i2c.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Synopsys DesignWare Advanced I2C (DWC_i2c) Controller
+
+maintainers:
+ - Aniket Limaye <a-limaye@ti.com>
+
+allOf:
+ - $ref: /schemas/i2c/i2c-controller.yaml#
+
+properties:
+ compatible:
+ enum:
+ - ti,tda54-i2c
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+ clocks:
+ items:
+ - description: I2C functional clock (ic_clk)
+ - description: Register interface clock (pclk)
+
+ clock-names:
+ items:
+ - const: ref
+ - const: pclk
+
+ resets:
+ maxItems: 1
+
+ clock-frequency:
+ enum: [100000, 400000, 1000000, 3400000]
+ default: 400000
+
+ i2c-sda-hold-time-ns: true
+
+ i2c-scl-falling-time-ns:
+ default: 300
+
+ i2c-sda-falling-time-ns:
+ default: 300
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - clocks
+ - clock-names
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+
+ i2c@53b00000 {
+ compatible = "ti,tda54-i2c";
+ reg = <0x53b00000 0x1000>;
+ interrupts = <GIC_SPI 166 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&sysclk>, <&pclk>;
+ clock-names = "ref", "pclk";
+ clock-frequency = <400000>;
+ i2c-sda-hold-time-ns = <300>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ };
+...
--
2.53.0
^ permalink raw reply [flat|nested] 3+ messages in thread
* [PATCH v4 2/2] i2c: Add Synopsys Advanced I2C (DWC_i2c) driver
2026-10-27 11:30 [PATCH v4 0/2] i2c: Add Synopsys Advanced I2C (DWC_i2c) support for TI TDA54 Aniket Limaye
2026-10-27 11:30 ` [PATCH v4 1/2] dt-bindings: i2c: Add Synopsys DWC_i2c controller Aniket Limaye
@ 2026-10-27 11:30 ` Aniket Limaye
1 sibling, 0 replies; 3+ messages in thread
From: Aniket Limaye @ 2026-10-27 11:30 UTC (permalink / raw)
To: Andi Shyti, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel
Cc: linux-i2c, devicetree, linux-kernel, Aniket Limaye, lianfeng.ouyang
Add a new platform driver for the Synopsys Advanced I2C (DWC_i2c),
controller mode only.
The register offsets are defined with respect to header registers for
each block. These header registers are discovered at runtime by walking
the block header chain.
Signed-off-by: Aniket Limaye <a-limaye@ti.com>
---
Changes in v4:
- Drop the approach of extending the existing i2c-designware-* driver
with a MODEL_TI_TDA54 variant. Instead, add a new standalone driver
(i2c-dwc-adv.c) and a new binding (snps,dwc-adv-i2c.yaml), since
DWC_i2c is not register compatible with DW_apb_i2c. (Andy Shevchenko)
- Drop the snps,tx-fifo-depth/snps,rx-fifo-depth DT properties from
the new binding; FIFO depth is now tied to the compatible string via
driver match data instead. (Conor Dooley)
- Link to v3: https://lore.kernel.org/all/20260925-tda54-upstream-i2c-v3-2-544d74e992ff@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/i2c/busses/Kconfig | 11 +
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-dwc-adv.c | 1333 ++++++++++++++++++++++++++++++++++++++
3 files changed, 1345 insertions(+)
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index 275aaa7f3583..ea91221638e8 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -649,6 +649,17 @@ config I2C_DIGICOLOR
This driver can also be built as a module. If so, the module
will be called i2c-digicolor.
+config I2C_DWC_ADV
+ tristate "Synopsys Advanced I2C (DWC_i2c) driver"
+ depends on ARCH_K3 || COMPILE_TEST
+ help
+ If you say yes to this option, support will be included for the
+ Synopsys Advanced I2C controller (DWC_i2c) driver. Only controller
+ mode is supported.
+
+ This driver can also be built as a module. If so, the module
+ will be called i2c-dwc-adv.
+
config I2C_EG20T
tristate "Intel EG20T PCH/LAPIS Semicon IOH(ML7213/ML7223/ML7831) I2C"
depends on PCI && (X86_32 || MIPS || COMPILE_TEST)
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index e5440de0fe14..bf710baa9c11 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -62,6 +62,7 @@ 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_DIGICOLOR) += i2c-digicolor.o
+obj-$(CONFIG_I2C_DWC_ADV) += i2c-dwc-adv.o
obj-$(CONFIG_I2C_EG20T) += i2c-eg20t.o
obj-$(CONFIG_I2C_EMEV2) += i2c-emev2.o
obj-$(CONFIG_I2C_EXYNOS5) += i2c-exynos5.o
diff --git a/drivers/i2c/busses/i2c-dwc-adv.c b/drivers/i2c/busses/i2c-dwc-adv.c
new file mode 100644
index 000000000000..1c5b7b154bed
--- /dev/null
+++ b/drivers/i2c/busses/i2c-dwc-adv.c
@@ -0,0 +1,1333 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Synopsys Advanced I2C (DWC_i2c) controller driver
+ *
+ * Based on the Synopsys DesignWare I2C adapter driver (i2c-designware-*),
+ * which is based on the TI DAVINCI I2C adapter driver.
+ *
+ * Copyright (C) 2006 Texas Instruments.
+ * Copyright (C) 2007 MontaVista Software Inc.
+ * Copyright (C) 2009 Provigent Ltd.
+ * Copyright (C) 2026 Texas Instruments Incorporated - https://www.ti.com/
+ */
+
+#include <linux/array_size.h>
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/clk.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/math.h>
+#include <linux/math64.h>
+#include <linux/minmax.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/property.h>
+#include <linux/reset.h>
+#include <linux/units.h>
+
+#define DWC_ADV_I2C_OP_ENABLE 0x04
+#define DWC_ADV_I2C_OP_I2C_CAPABILITIES 0x10
+#define DWC_ADV_I2C_OP_I2C_CAPABILITIES_LOADING_400 BIT(3)
+#define DWC_ADV_I2C_OP_I2C_BLOCK_OFFSET 0x14
+
+#define DWC_ADV_I2C_HEADER_ID GENMASK(7, 0)
+#define DWC_ADV_I2C_HEADER_LENGTH GENMASK(23, 8)
+#define DWC_ADV_I2C_HEADER_ID_CORE 0xa1
+#define DWC_ADV_I2C_HEADER_ID_TIMING 0xa2
+#define DWC_ADV_I2C_HEADER_ID_FIFO 0xa3
+#define DWC_ADV_I2C_HEADER_ID_INTR_DEBUG 0xa4
+#define DWC_ADV_I2C_HEADER_ID_HWID 0xa5
+
+#define DWC_ADV_I2C_CORE_CON 0x04
+#define DWC_ADV_I2C_CORE_TAR 0x08
+
+#define DWC_ADV_I2C_TIMING_HCNT 0x08
+#define DWC_ADV_I2C_TIMING_LCNT 0x0c
+#define DWC_ADV_I2C_TIMING_HS_HCNT 0x10
+#define DWC_ADV_I2C_TIMING_HS_LCNT 0x14
+#define DWC_ADV_I2C_TIMING_SDA_HOLD 0x18
+
+#define DWC_ADV_I2C_FIFO_DATA_CMD 0x04
+#define DWC_ADV_I2C_FIFO_RX_TL 0x08
+#define DWC_ADV_I2C_FIFO_TX_TL 0x0c
+
+#define DWC_ADV_I2C_INTR_INTR_STAT 0x04
+#define DWC_ADV_I2C_INTR_INTR_MASK 0x08
+#define DWC_ADV_I2C_INTR_RAW_INTR_STAT 0x0c
+#define DWC_ADV_I2C_INTR_CLR_INTR 0x10
+#define DWC_ADV_I2C_INTR_ENABLE_STATUS 0x14
+#define DWC_ADV_I2C_INTR_TX_ABRT_SOURCE 0x18
+#define DWC_ADV_I2C_INTR_STATUS 0x1c
+#define DWC_ADV_I2C_INTR_TXFLR 0x20
+#define DWC_ADV_I2C_INTR_RXFLR 0x24
+
+#define DWC_ADV_I2C_HWID_COMP_TYPE 0x0c
+#define DWC_ADV_I2C_COMP_TYPE_VALUE 0x44570140
+
+#define DWC_ADV_I2C_CON_CONTROLLER BIT(0)
+#define DWC_ADV_I2C_CON_SPEED_MASK GENMASK(5, 4)
+#define DWC_ADV_I2C_CON_SPEED_STD BIT(4)
+#define DWC_ADV_I2C_CON_SPEED_FAST BIT(5)
+#define DWC_ADV_I2C_CON_SPEED_HIGH (BIT(4) | BIT(5))
+#define DWC_ADV_I2C_CON_10BITADDR_CTRLR BIT(9)
+
+#define DWC_ADV_I2C_TAR_10BITADDR_CTRLR BIT(12)
+
+#define DWC_ADV_I2C_DATA_CMD_DAT GENMASK(7, 0)
+#define DWC_ADV_I2C_DATA_CMD_READ BIT(8)
+#define DWC_ADV_I2C_DATA_CMD_STOP BIT(9)
+#define DWC_ADV_I2C_DATA_CMD_RESTART BIT(10)
+
+#define DWC_ADV_I2C_INTR_RX_UNDER BIT(0)
+#define DWC_ADV_I2C_INTR_RX_OVER BIT(1)
+#define DWC_ADV_I2C_INTR_RX_FULL BIT(2)
+#define DWC_ADV_I2C_INTR_TX_OVER BIT(3)
+#define DWC_ADV_I2C_INTR_TX_EMPTY BIT(4)
+#define DWC_ADV_I2C_INTR_RD_REQ BIT(5)
+#define DWC_ADV_I2C_INTR_TX_ABRT BIT(6)
+#define DWC_ADV_I2C_INTR_RX_DONE BIT(7)
+#define DWC_ADV_I2C_INTR_ACTIVITY BIT(8)
+#define DWC_ADV_I2C_INTR_STOP_DET BIT(9)
+#define DWC_ADV_I2C_INTR_START_DET BIT(10)
+#define DWC_ADV_I2C_INTR_GEN_CALL BIT(11)
+#define DWC_ADV_I2C_INTR_CTRLR_ON_HOLD BIT(13)
+
+#define DWC_ADV_I2C_INTR_CLR_ALL BIT(0)
+#define DWC_ADV_I2C_INTR_CLR_RX_UNDER BIT(1)
+#define DWC_ADV_I2C_INTR_CLR_RX_OVER BIT(2)
+#define DWC_ADV_I2C_INTR_CLR_TX_OVER BIT(3)
+#define DWC_ADV_I2C_INTR_CLR_RD_REQ BIT(4)
+#define DWC_ADV_I2C_INTR_CLR_TX_ABRT BIT(5)
+#define DWC_ADV_I2C_INTR_CLR_RX_DONE BIT(6)
+#define DWC_ADV_I2C_INTR_CLR_ACTIVITY BIT(7)
+#define DWC_ADV_I2C_INTR_CLR_STOP_DET BIT(8)
+#define DWC_ADV_I2C_INTR_CLR_START_DET BIT(9)
+#define DWC_ADV_I2C_INTR_CLR_GEN_CALL BIT(10)
+
+#define DWC_ADV_I2C_CTRLR_INTR_MASK \
+ (DWC_ADV_I2C_INTR_RX_FULL | DWC_ADV_I2C_INTR_TX_ABRT | \
+ DWC_ADV_I2C_INTR_STOP_DET | DWC_ADV_I2C_INTR_TX_EMPTY)
+
+#define DWC_ADV_I2C_ENABLE_ENABLE BIT(0)
+#define DWC_ADV_I2C_ENABLE_ABORT BIT(1)
+
+#define DWC_ADV_I2C_STATUS_ACTIVITY BIT(0)
+#define DWC_ADV_I2C_STATUS_CTRLR_ACTIVITY BIT(5)
+#define DWC_ADV_I2C_STATUS_CTRLR_HOLD_TX_FIFO_EMPTY BIT(7)
+
+enum dwc_adv_i2c_abrt_bit {
+ DWC_ADV_I2C_ABRT_7B_ADDR_NOACK = 0,
+ DWC_ADV_I2C_ABRT_10ADDR1_NOACK = 1,
+ DWC_ADV_I2C_ABRT_10ADDR2_NOACK = 2,
+ DWC_ADV_I2C_ABRT_TXDATA_NOACK = 3,
+ DWC_ADV_I2C_ABRT_GCALL_NOACK = 4,
+ DWC_ADV_I2C_ABRT_GCALL_READ = 5,
+ DWC_ADV_I2C_ABRT_HS_ACKDET = 6,
+ DWC_ADV_I2C_ABRT_SBYTE_ACKDET = 7,
+ DWC_ADV_I2C_ABRT_CTRLR_DIS = 11,
+ DWC_ADV_I2C_ARB_LOST = 12,
+ DWC_ADV_I2C_ABRT_USER_TRMNT = 16,
+ DWC_ADV_I2C_ABRT_SDA_STUCK_AT_LOW = 17,
+};
+
+#define TX_ABRT_7B_ADDR_NOACK BIT(DWC_ADV_I2C_ABRT_7B_ADDR_NOACK)
+#define TX_ABRT_10ADDR1_NOACK BIT(DWC_ADV_I2C_ABRT_10ADDR1_NOACK)
+#define TX_ABRT_10ADDR2_NOACK BIT(DWC_ADV_I2C_ABRT_10ADDR2_NOACK)
+#define TX_ABRT_TXDATA_NOACK BIT(DWC_ADV_I2C_ABRT_TXDATA_NOACK)
+#define TX_ABRT_GCALL_NOACK BIT(DWC_ADV_I2C_ABRT_GCALL_NOACK)
+#define TX_ABRT_GCALL_READ BIT(DWC_ADV_I2C_ABRT_GCALL_READ)
+#define TX_ARB_LOST BIT(DWC_ADV_I2C_ARB_LOST)
+#define TX_ABRT_USER_TRMNT BIT(DWC_ADV_I2C_ABRT_USER_TRMNT)
+#define TX_ABRT_NOACK \
+ (TX_ABRT_7B_ADDR_NOACK | \
+ TX_ABRT_10ADDR1_NOACK | \
+ TX_ABRT_10ADDR2_NOACK | \
+ TX_ABRT_TXDATA_NOACK | \
+ TX_ABRT_GCALL_NOACK)
+
+#define DWC_ADV_I2C_STATUS_SW_ACTIVE BIT(0)
+#define DWC_ADV_I2C_STATUS_SW_WRITE_IN_PROGRESS BIT(1)
+#define DWC_ADV_I2C_STATUS_SW_READ_IN_PROGRESS BIT(2)
+#define DWC_ADV_I2C_STATUS_SW_MASK GENMASK(2, 0)
+
+#define DWC_ADV_I2C_ABORT_POLL_US 10
+#define DWC_ADV_I2C_ABORT_TIMEOUT_US 100
+#define DWC_ADV_I2C_DISABLE_POLL_US 250
+#define DWC_ADV_I2C_DISABLE_TIMEOUT_US 25000
+
+#define DWC_ADV_I2C_XFER_TIMEOUT_MS 1000
+#define DWC_ADV_I2C_AUTOSUSPEND_DELAY_MS 1000
+
+static const char * const dwc_adv_i2c_abort_sources[] = {
+ [DWC_ADV_I2C_ABRT_7B_ADDR_NOACK] =
+ "target address not acknowledged",
+ [DWC_ADV_I2C_ABRT_10ADDR1_NOACK] =
+ "10-bit first address byte not acknowledged",
+ [DWC_ADV_I2C_ABRT_10ADDR2_NOACK] =
+ "10-bit second address byte not acknowledged",
+ [DWC_ADV_I2C_ABRT_TXDATA_NOACK] =
+ "data not acknowledged",
+ [DWC_ADV_I2C_ABRT_GCALL_NOACK] =
+ "general call not acknowledged",
+ [DWC_ADV_I2C_ABRT_GCALL_READ] =
+ "general call read",
+ [DWC_ADV_I2C_ABRT_HS_ACKDET] =
+ "high speed controller code acknowledged",
+ [DWC_ADV_I2C_ABRT_SBYTE_ACKDET] =
+ "start byte acknowledged",
+ [DWC_ADV_I2C_ABRT_CTRLR_DIS] =
+ "disabled while active",
+ [DWC_ADV_I2C_ARB_LOST] =
+ "arbitration lost",
+ [DWC_ADV_I2C_ABRT_USER_TRMNT] =
+ "terminated by user request",
+ [DWC_ADV_I2C_ABRT_SDA_STUCK_AT_LOW] =
+ "SDA stuck at low",
+};
+
+struct dwc_adv_i2c_regs {
+ u32 enable;
+ u32 con;
+ u32 tar;
+ u32 ss_fs_hcnt;
+ u32 ss_fs_lcnt;
+ u32 hs_hcnt;
+ u32 hs_lcnt;
+ u32 sda_hold;
+ u32 data_cmd;
+ u32 rx_tl;
+ u32 tx_tl;
+ u32 intr_stat;
+ u32 intr_mask;
+ u32 raw_intr_stat;
+ u32 clr_intr;
+ u32 enable_status;
+ u32 tx_abrt_source;
+ u32 status;
+ u32 txflr;
+ u32 rxflr;
+};
+
+struct dwc_adv_i2c_dev {
+ struct device *dev;
+ void __iomem *base;
+ struct clk *clk;
+ struct clk *pclk;
+ struct reset_control *rst;
+ struct i2c_adapter adap;
+ struct completion cmd_complete;
+ struct i2c_bus_recovery_info rinfo;
+
+ struct dwc_adv_i2c_regs regs;
+ u32 tx_fifo_depth;
+ u32 rx_fifo_depth;
+
+ struct i2c_timings timings;
+ bool hs_loading_400pf;
+ u32 sda_hold_time;
+
+ u32 ctrlr_cfg;
+ u32 ss_hcnt;
+ u32 ss_lcnt;
+ u32 fs_hcnt;
+ u32 fs_lcnt;
+ u32 hs_hcnt;
+ u32 hs_lcnt;
+
+ struct i2c_msg *msgs;
+ int msgs_num;
+ int msg_write_idx;
+ int msg_read_idx;
+ int msg_err;
+ int cmd_err;
+ unsigned int status;
+ unsigned int abort_source;
+ int rx_outstanding;
+ bool need_restart;
+
+ u8 *tx_buf;
+ size_t tx_buf_len;
+ u8 *rx_buf;
+ size_t rx_buf_len;
+};
+
+struct dwc_adv_i2c_match_data {
+ u32 tx_fifo_depth;
+ u32 rx_fifo_depth;
+};
+
+static const struct dwc_adv_i2c_match_data tda54_i2c_match_data = {
+ .tx_fifo_depth = 32,
+ .rx_fifo_depth = 32,
+};
+
+static inline u32 dwc_adv_reg_read(struct dwc_adv_i2c_dev *dev, u32 offset)
+{
+ return readl(dev->base + offset);
+}
+
+static inline void dwc_adv_reg_write(struct dwc_adv_i2c_dev *dev, u32 offset, u32 val)
+{
+ writel(val, dev->base + offset);
+}
+
+static int dwc_adv_i2c_next_header(struct dwc_adv_i2c_dev *dev, u32 hdr_offset,
+ u32 expected_id, u32 *next_offset)
+{
+ u32 hdr = dwc_adv_reg_read(dev, hdr_offset);
+ u32 id = FIELD_GET(DWC_ADV_I2C_HEADER_ID, hdr);
+ u32 length_words = FIELD_GET(DWC_ADV_I2C_HEADER_LENGTH, hdr);
+
+ if (id != expected_id) {
+ return dev_err_probe(dev->dev, -ENODEV,
+ "header at offset 0x%x has ID 0x%x, expected 0x%x\n",
+ hdr_offset, id, expected_id);
+ }
+
+ if (!length_words) {
+ return dev_err_probe(dev->dev, -ENODEV,
+ "zero length header at offset 0x%x\n", hdr_offset);
+ }
+
+ if (next_offset)
+ *next_offset = hdr_offset + length_words * 4;
+
+ return 0;
+}
+
+/*
+ * Register offsets are relative to per-block header registers. Walk the
+ * header chain to find each block, starting from the core block offset.
+ */
+static int dwc_adv_i2c_discover_regs(struct dwc_adv_i2c_dev *dev)
+{
+ struct dwc_adv_i2c_regs *regs = &dev->regs;
+ u32 core_header, timing_header, fifo_header, intr_header, hwid_header;
+ u32 caps, comp_type;
+ int ret;
+
+ regs->enable = DWC_ADV_I2C_OP_ENABLE;
+ caps = dwc_adv_reg_read(dev, DWC_ADV_I2C_OP_I2C_CAPABILITIES);
+ dev->hs_loading_400pf = caps & DWC_ADV_I2C_OP_I2C_CAPABILITIES_LOADING_400;
+ core_header = dwc_adv_reg_read(dev, DWC_ADV_I2C_OP_I2C_BLOCK_OFFSET);
+ if (!core_header)
+ return dev_err_probe(dev->dev, -ENODEV, "no I2C block offset\n");
+
+ regs->con = core_header + DWC_ADV_I2C_CORE_CON;
+ regs->tar = core_header + DWC_ADV_I2C_CORE_TAR;
+
+ ret = dwc_adv_i2c_next_header(dev, core_header,
+ DWC_ADV_I2C_HEADER_ID_CORE, &timing_header);
+ if (ret)
+ return ret;
+
+ regs->ss_fs_hcnt = timing_header + DWC_ADV_I2C_TIMING_HCNT;
+ regs->ss_fs_lcnt = timing_header + DWC_ADV_I2C_TIMING_LCNT;
+ regs->hs_hcnt = timing_header + DWC_ADV_I2C_TIMING_HS_HCNT;
+ regs->hs_lcnt = timing_header + DWC_ADV_I2C_TIMING_HS_LCNT;
+ regs->sda_hold = timing_header + DWC_ADV_I2C_TIMING_SDA_HOLD;
+
+ ret = dwc_adv_i2c_next_header(dev, timing_header,
+ DWC_ADV_I2C_HEADER_ID_TIMING, &fifo_header);
+ if (ret)
+ return ret;
+
+ regs->data_cmd = fifo_header + DWC_ADV_I2C_FIFO_DATA_CMD;
+ regs->rx_tl = fifo_header + DWC_ADV_I2C_FIFO_RX_TL;
+ regs->tx_tl = fifo_header + DWC_ADV_I2C_FIFO_TX_TL;
+
+ ret = dwc_adv_i2c_next_header(dev, fifo_header,
+ DWC_ADV_I2C_HEADER_ID_FIFO, &intr_header);
+ if (ret)
+ return ret;
+
+ regs->intr_stat = intr_header + DWC_ADV_I2C_INTR_INTR_STAT;
+ regs->intr_mask = intr_header + DWC_ADV_I2C_INTR_INTR_MASK;
+ regs->raw_intr_stat = intr_header + DWC_ADV_I2C_INTR_RAW_INTR_STAT;
+ regs->clr_intr = intr_header + DWC_ADV_I2C_INTR_CLR_INTR;
+ regs->enable_status = intr_header + DWC_ADV_I2C_INTR_ENABLE_STATUS;
+ regs->tx_abrt_source = intr_header + DWC_ADV_I2C_INTR_TX_ABRT_SOURCE;
+ regs->status = intr_header + DWC_ADV_I2C_INTR_STATUS;
+ regs->txflr = intr_header + DWC_ADV_I2C_INTR_TXFLR;
+ regs->rxflr = intr_header + DWC_ADV_I2C_INTR_RXFLR;
+
+ ret = dwc_adv_i2c_next_header(dev, intr_header,
+ DWC_ADV_I2C_HEADER_ID_INTR_DEBUG, &hwid_header);
+ if (ret)
+ return ret;
+
+ ret = dwc_adv_i2c_next_header(dev, hwid_header,
+ DWC_ADV_I2C_HEADER_ID_HWID, NULL);
+ if (ret)
+ return ret;
+
+ comp_type = dwc_adv_reg_read(dev, hwid_header + DWC_ADV_I2C_HWID_COMP_TYPE);
+ if (comp_type != DWC_ADV_I2C_COMP_TYPE_VALUE)
+ return dev_err_probe(dev->dev, -ENODEV,
+ "unknown Synopsys component type: 0x%08x\n",
+ comp_type);
+
+ return 0;
+}
+
+static void dwc_adv_i2c_ack_intr(struct dwc_adv_i2c_dev *dev, u32 clr_bit)
+{
+ dwc_adv_reg_write(dev, dev->regs.clr_intr, clr_bit);
+}
+
+static u32 dwc_adv_i2c_scl_hcnt(u32 ic_clk, u32 tsymbol, u32 tf, int offset)
+{
+ return DIV_ROUND_CLOSEST_ULL((u64)ic_clk * (tsymbol + tf), MICRO) - 3 + offset;
+}
+
+static u32 dwc_adv_i2c_scl_lcnt(u32 ic_clk, u32 tlow, u32 tf, int offset)
+{
+ return DIV_ROUND_CLOSEST_ULL((u64)ic_clk * (tlow + tf), MICRO) - 1 + offset;
+}
+
+static void dwc_adv_i2c_set_timings(struct dwc_adv_i2c_dev *dev)
+{
+ struct i2c_timings *t = &dev->timings;
+ u32 ic_clk = clk_get_rate(dev->clk) / 1000;
+ u32 sda_falling_time = t->sda_fall_ns ?: 300;
+ u32 scl_falling_time = t->scl_fall_ns ?: 300;
+ u32 t_high, t_low;
+
+ dev->ss_hcnt = dwc_adv_i2c_scl_hcnt(ic_clk, 4000, sda_falling_time, 0);
+ dev->ss_lcnt = dwc_adv_i2c_scl_lcnt(ic_clk, 4700, scl_falling_time, 0);
+
+ if (t->bus_freq_hz == I2C_MAX_FAST_MODE_PLUS_FREQ) {
+ dev->fs_hcnt = dwc_adv_i2c_scl_hcnt(ic_clk, 260, sda_falling_time, 0);
+ dev->fs_lcnt = dwc_adv_i2c_scl_lcnt(ic_clk, 500, scl_falling_time, 0);
+ } else {
+ dev->fs_hcnt = dwc_adv_i2c_scl_hcnt(ic_clk, 600, sda_falling_time, 0);
+ dev->fs_lcnt = dwc_adv_i2c_scl_lcnt(ic_clk, 1300, scl_falling_time, 0);
+ }
+
+ if (dev->hs_loading_400pf) {
+ t_high = 120;
+ t_low = 320;
+ } else {
+ t_high = 60;
+ t_low = 160;
+ }
+ dev->hs_hcnt = dwc_adv_i2c_scl_hcnt(ic_clk, t_high, sda_falling_time, 0);
+ dev->hs_lcnt = dwc_adv_i2c_scl_lcnt(ic_clk, t_low, scl_falling_time, 0);
+}
+
+static void dwc_adv_i2c_write_timings(struct dwc_adv_i2c_dev *dev)
+{
+ u32 speed = dev->ctrlr_cfg & DWC_ADV_I2C_CON_SPEED_MASK;
+
+ if (speed == DWC_ADV_I2C_CON_SPEED_HIGH) {
+ dwc_adv_reg_write(dev, dev->regs.hs_hcnt, dev->hs_hcnt);
+ dwc_adv_reg_write(dev, dev->regs.hs_lcnt, dev->hs_lcnt);
+ dwc_adv_reg_write(dev, dev->regs.ss_fs_hcnt, dev->fs_hcnt);
+ dwc_adv_reg_write(dev, dev->regs.ss_fs_lcnt, dev->fs_lcnt);
+ } else if (speed == DWC_ADV_I2C_CON_SPEED_STD) {
+ dwc_adv_reg_write(dev, dev->regs.ss_fs_hcnt, dev->ss_hcnt);
+ dwc_adv_reg_write(dev, dev->regs.ss_fs_lcnt, dev->ss_lcnt);
+ } else {
+ dwc_adv_reg_write(dev, dev->regs.ss_fs_hcnt, dev->fs_hcnt);
+ dwc_adv_reg_write(dev, dev->regs.ss_fs_lcnt, dev->fs_lcnt);
+ }
+}
+
+static void __dwc_adv_i2c_enable(struct dwc_adv_i2c_dev *dev)
+{
+ dev->status |= DWC_ADV_I2C_STATUS_SW_ACTIVE;
+ dwc_adv_reg_write(dev, dev->regs.enable, DWC_ADV_I2C_ENABLE_ENABLE);
+}
+
+static void __dwc_adv_i2c_disable_nowait(struct dwc_adv_i2c_dev *dev)
+{
+ u32 enable = dwc_adv_reg_read(dev, dev->regs.enable);
+
+ dwc_adv_reg_write(dev, dev->regs.enable, enable & ~DWC_ADV_I2C_ENABLE_ENABLE);
+ dev->status &= ~DWC_ADV_I2C_STATUS_SW_ACTIVE;
+}
+
+static u32 dwc_adv_i2c_try_disable(struct dwc_adv_i2c_dev *dev)
+{
+ __dwc_adv_i2c_disable_nowait(dev);
+
+ return dwc_adv_reg_read(dev, dev->regs.enable_status);
+}
+
+/*
+ * If the controller is holding the bus with an empty TX FIFO, terminate the
+ * transfer first. IC_ENABLE.TERMINATE is only honoured while ENABLE is set.
+ */
+static void __dwc_adv_i2c_disable(struct dwc_adv_i2c_dev *dev)
+{
+ u32 raw_intr_stat = dwc_adv_reg_read(dev, dev->regs.raw_intr_stat);
+ u32 ic_status = dwc_adv_reg_read(dev, dev->regs.status);
+ u32 enable = dwc_adv_reg_read(dev, dev->regs.enable);
+ bool abort_needed = (raw_intr_stat & DWC_ADV_I2C_INTR_CTRLR_ON_HOLD) ||
+ (ic_status & DWC_ADV_I2C_STATUS_CTRLR_HOLD_TX_FIFO_EMPTY);
+ u32 status;
+ int ret;
+
+ if (abort_needed) {
+ if (!(enable & DWC_ADV_I2C_ENABLE_ENABLE)) {
+ dwc_adv_reg_write(dev, dev->regs.enable, DWC_ADV_I2C_ENABLE_ENABLE);
+ /* Let ENABLE take effect: 10 signaling periods */
+ fsleep(DIV_ROUND_CLOSEST_ULL(10 * MICRO, dev->timings.bus_freq_hz));
+ enable |= DWC_ADV_I2C_ENABLE_ENABLE;
+ }
+ dwc_adv_reg_write(dev, dev->regs.enable, enable | DWC_ADV_I2C_ENABLE_ABORT);
+
+ ret = readl_poll_timeout(dev->base + dev->regs.enable, enable,
+ !(enable & DWC_ADV_I2C_ENABLE_ABORT),
+ DWC_ADV_I2C_ABORT_POLL_US,
+ DWC_ADV_I2C_ABORT_TIMEOUT_US);
+ if (ret)
+ dev_err(dev->dev, "timeout while trying to abort current transfer\n");
+ }
+
+ ret = read_poll_timeout(dwc_adv_i2c_try_disable, status,
+ !(status & DWC_ADV_I2C_ENABLE_ENABLE),
+ DWC_ADV_I2C_DISABLE_POLL_US,
+ DWC_ADV_I2C_DISABLE_TIMEOUT_US, false, dev);
+ if (ret)
+ dev_warn(dev->dev, "timeout in disabling adapter\n");
+}
+
+static void dwc_adv_i2c_init(struct dwc_adv_i2c_dev *dev)
+{
+ __dwc_adv_i2c_disable(dev);
+
+ dwc_adv_i2c_write_timings(dev);
+
+ if (dev->sda_hold_time)
+ dwc_adv_reg_write(dev, dev->regs.sda_hold, dev->sda_hold_time);
+
+ dwc_adv_reg_write(dev, dev->regs.tx_tl, dev->tx_fifo_depth / 2);
+ dwc_adv_reg_write(dev, dev->regs.rx_tl, 0);
+ dwc_adv_reg_write(dev, dev->regs.con, dev->ctrlr_cfg);
+}
+
+static int dwc_adv_i2c_wait_bus_not_busy(struct dwc_adv_i2c_dev *dev)
+{
+ u32 status;
+ int ret;
+
+ ret = readl_poll_timeout(dev->base + dev->regs.status, status,
+ !(status & DWC_ADV_I2C_STATUS_ACTIVITY),
+ 1000, 20000);
+ if (ret) {
+ dev_warn(dev->dev, "timeout waiting for bus ready\n");
+ i2c_recover_bus(&dev->adap);
+
+ status = dwc_adv_reg_read(dev, dev->regs.status);
+ if (!(status & DWC_ADV_I2C_STATUS_ACTIVITY))
+ ret = 0;
+ }
+
+ return ret;
+}
+
+/*
+ * Wait for the controller to go idle once the transfer has completed (STOP_DET
+ * or TX_ABRT), so that disabling it does not cut the transfer short.
+ * Return: true if it is still active after the timeout.
+ */
+static bool dwc_adv_i2c_is_controller_active(struct dwc_adv_i2c_dev *dev)
+{
+ u32 status;
+
+ status = dwc_adv_reg_read(dev, dev->regs.status);
+ if (!(status & DWC_ADV_I2C_STATUS_CTRLR_ACTIVITY))
+ return false;
+
+ return readl_poll_timeout(dev->base + dev->regs.status, status,
+ !(status & DWC_ADV_I2C_STATUS_CTRLR_ACTIVITY),
+ 1100, 20000) != 0;
+}
+
+static void dwc_adv_i2c_xfer_init(struct dwc_adv_i2c_dev *dev)
+{
+ struct i2c_msg *msg = &dev->msgs[dev->msg_write_idx];
+ u32 con;
+
+ __dwc_adv_i2c_disable(dev);
+
+ /* If the target address is ten bit address, enable 10BITADDR */
+ con = dev->ctrlr_cfg;
+ if (msg->flags & I2C_M_TEN)
+ con |= DWC_ADV_I2C_CON_10BITADDR_CTRLR;
+ else
+ con &= ~DWC_ADV_I2C_CON_10BITADDR_CTRLR;
+ dwc_adv_reg_write(dev, dev->regs.con, con);
+
+ /*
+ * With IC_DYNAMIC_TAR_UPDATE the 10-bit addressing mode is controlled
+ * by IC_TAR bit 12 and the IC_CTRL bit is read-only. This cannot be
+ * detected from the registers, so always set it.
+ */
+ dwc_adv_reg_write(dev, dev->regs.tar, msg->addr |
+ ((msg->flags & I2C_M_TEN) ? DWC_ADV_I2C_TAR_10BITADDR_CTRLR : 0));
+
+ /*
+ * Enforce disabled interrupts: IC_INTR_MASK resets with TX_EMPTY
+ * unmasked, which would fire as soon as the controller is enabled.
+ */
+ dwc_adv_reg_write(dev, dev->regs.intr_mask, 0);
+
+ /* Enable the adapter, then clear and enable interrupts */
+ __dwc_adv_i2c_enable(dev);
+ dwc_adv_i2c_ack_intr(dev, DWC_ADV_I2C_INTR_CLR_ALL);
+ dwc_adv_reg_write(dev, dev->regs.intr_mask, DWC_ADV_I2C_CTRLR_INTR_MASK);
+}
+
+/*
+ * Initiate (and continue) low level controller read/write transaction.
+ * This function is only called from dwc_adv_i2c_isr(), and pumps i2c_msg
+ * messages into the TX FIFO. Even if the size of i2c_msg data is longer
+ * than the TX FIFO, it handles everything.
+ */
+static void dwc_adv_i2c_xfer_msg(struct dwc_adv_i2c_dev *dev)
+{
+ struct i2c_msg *msgs = dev->msgs;
+ u32 intr_mask = DWC_ADV_I2C_CTRLR_INTR_MASK;
+ int tx_limit, rx_limit;
+ u32 buf_len = dev->tx_buf_len;
+ u8 *buf = dev->tx_buf;
+ u32 cmd;
+ int rx_tl;
+
+ for (; dev->msg_write_idx < dev->msgs_num; dev->msg_write_idx++) {
+ u32 flags = msgs[dev->msg_write_idx].flags;
+
+ if (!(dev->status & DWC_ADV_I2C_STATUS_SW_WRITE_IN_PROGRESS)) {
+ buf = msgs[dev->msg_write_idx].buf;
+ buf_len = msgs[dev->msg_write_idx].len;
+ }
+
+ tx_limit = dev->tx_fifo_depth - dwc_adv_reg_read(dev, dev->regs.txflr);
+ rx_limit = dev->rx_fifo_depth - dwc_adv_reg_read(dev, dev->regs.rxflr);
+
+ while (buf_len > 0 && tx_limit > 0 && rx_limit > 0) {
+ cmd = 0;
+
+ /*
+ * The bus is held while the TX FIFO is empty unless
+ * STOP is requested in IC_DATA_CMD, so always set it
+ * on the last byte of the last message.
+ *
+ * i2c-core always sets the buffer length of
+ * I2C_FUNC_SMBUS_BLOCK_DATA to 1. The length will be
+ * adjusted when receiving the first byte. Thus we
+ * can't stop the transaction here.
+ */
+ if (dev->msg_write_idx == dev->msgs_num - 1 &&
+ buf_len == 1 && !(flags & I2C_M_RECV_LEN))
+ cmd |= DWC_ADV_I2C_DATA_CMD_STOP;
+
+ /*
+ * A RESTART is only issued automatically when the
+ * direction changes. Request it explicitly at the
+ * start of each following message.
+ */
+ if (dev->need_restart) {
+ cmd |= DWC_ADV_I2C_DATA_CMD_RESTART;
+ dev->need_restart = false;
+ }
+
+ if (msgs[dev->msg_write_idx].flags & I2C_M_RD) {
+ /* Avoid rx buffer overrun */
+ if (dev->rx_outstanding >= dev->rx_fifo_depth)
+ break;
+ dwc_adv_reg_write(dev, dev->regs.data_cmd,
+ cmd | DWC_ADV_I2C_DATA_CMD_READ);
+ rx_limit--;
+ dev->rx_outstanding++;
+ } else {
+ dwc_adv_reg_write(dev, dev->regs.data_cmd, cmd | *buf++);
+ }
+ tx_limit--;
+ buf_len--;
+ }
+
+ dev->tx_buf = buf;
+ dev->tx_buf_len = buf_len;
+
+ /*
+ * Because we don't know the buffer length in the
+ * I2C_FUNC_SMBUS_BLOCK_DATA case, we can't stop the
+ * transaction here. Also disable the TX_EMPTY IRQ while
+ * waiting for the data length byte to avoid a flood of
+ * bogus interrupts.
+ */
+ if (flags & I2C_M_RECV_LEN) {
+ dev->status |= DWC_ADV_I2C_STATUS_SW_WRITE_IN_PROGRESS;
+ intr_mask &= ~DWC_ADV_I2C_INTR_TX_EMPTY;
+ break;
+ }
+
+ if (buf_len > 0) {
+ dev->status |= DWC_ADV_I2C_STATUS_SW_WRITE_IN_PROGRESS;
+ break;
+ }
+ dev->status &= ~DWC_ADV_I2C_STATUS_SW_WRITE_IN_PROGRESS;
+ dev->need_restart = true;
+ }
+
+ /*
+ * If i2c_msg index search is completed, we don't need TX_EMPTY
+ * interrupt any more.
+ */
+ if (dev->msg_write_idx == dev->msgs_num)
+ intr_mask &= ~DWC_ADV_I2C_INTR_TX_EMPTY;
+
+ if (dev->msg_err)
+ intr_mask = 0;
+
+ /*
+ * Size the RX FIFO threshold to the reads already queued, so the
+ * controller raises one RX_FULL for the whole burst instead of one per
+ * received byte. While messages remain to be queued, cap it so RX_FULL
+ * still arrives in time to drain the FIFO and let the next TX_EMPTY
+ * queue the rest.
+ */
+ rx_tl = dev->rx_outstanding;
+ if (dev->msg_write_idx < dev->msgs_num)
+ rx_tl = min_t(int, rx_tl, dev->rx_fifo_depth / 2);
+ dwc_adv_reg_write(dev, dev->regs.rx_tl, rx_tl ? rx_tl - 1 : 0);
+
+ dwc_adv_reg_write(dev, dev->regs.intr_mask, intr_mask);
+}
+
+static u8 dwc_adv_i2c_recv_len(struct dwc_adv_i2c_dev *dev, u8 len)
+{
+ struct i2c_msg *msgs = dev->msgs;
+ u32 flags = msgs[dev->msg_read_idx].flags;
+ u32 intr_mask;
+
+ /*
+ * Adjust the buffer length and mask the flag
+ * after receiving the first byte.
+ */
+ len += (flags & I2C_CLIENT_PEC) ? 2 : 1;
+ dev->tx_buf_len = len - min_t(int, len, dev->rx_outstanding);
+ msgs[dev->msg_read_idx].len = len;
+ msgs[dev->msg_read_idx].flags &= ~I2C_M_RECV_LEN;
+
+ /*
+ * Received buffer length, re-enable TX_EMPTY interrupt
+ * to resume the SMBus transaction.
+ */
+ intr_mask = dwc_adv_reg_read(dev, dev->regs.intr_mask);
+ intr_mask |= DWC_ADV_I2C_INTR_TX_EMPTY;
+ dwc_adv_reg_write(dev, dev->regs.intr_mask, intr_mask);
+
+ return len;
+}
+
+static void dwc_adv_i2c_read(struct dwc_adv_i2c_dev *dev)
+{
+ struct i2c_msg *msgs = dev->msgs;
+ int rx_valid;
+
+ for (; dev->msg_read_idx < dev->msgs_num; dev->msg_read_idx++) {
+ u32 len;
+ u8 *buf;
+
+ if (!(msgs[dev->msg_read_idx].flags & I2C_M_RD))
+ continue;
+
+ if (!(dev->status & DWC_ADV_I2C_STATUS_SW_READ_IN_PROGRESS)) {
+ len = msgs[dev->msg_read_idx].len;
+ buf = msgs[dev->msg_read_idx].buf;
+ } else {
+ len = dev->rx_buf_len;
+ buf = dev->rx_buf;
+ }
+
+ rx_valid = dwc_adv_reg_read(dev, dev->regs.rxflr);
+
+ for (; len > 0 && rx_valid > 0; len--, rx_valid--) {
+ u32 data = dwc_adv_reg_read(dev, dev->regs.data_cmd) &
+ DWC_ADV_I2C_DATA_CMD_DAT;
+
+ if (msgs[dev->msg_read_idx].flags & I2C_M_RECV_LEN) {
+ /*
+ * STOP is only set after receiving the block
+ * data response length. When that length is
+ * invalid, read one more byte with STOP set to
+ * complete the transaction.
+ */
+ if (!data || data > I2C_SMBUS_BLOCK_MAX)
+ data = 1;
+
+ len = dwc_adv_i2c_recv_len(dev, data);
+ }
+ *buf++ = data;
+ dev->rx_outstanding--;
+ }
+
+ if (len > 0) {
+ dev->status |= DWC_ADV_I2C_STATUS_SW_READ_IN_PROGRESS;
+ dev->rx_buf_len = len;
+ dev->rx_buf = buf;
+ return;
+ }
+ dev->status &= ~DWC_ADV_I2C_STATUS_SW_READ_IN_PROGRESS;
+ }
+}
+
+/*
+ * Clear the sources individually: IC_CLR_INTR could clear interrupts raised
+ * between reading IC_INTR_STAT and the clear. The abort source is saved
+ * before TX_ABRT is cleared, as clearing also resets IC_TX_TRMNT_SOURCE.
+ */
+static u32 dwc_adv_i2c_read_clear_intrbits(struct dwc_adv_i2c_dev *dev)
+{
+ u32 stat = dwc_adv_reg_read(dev, dev->regs.intr_stat);
+
+ if (stat & DWC_ADV_I2C_INTR_RX_UNDER)
+ dwc_adv_i2c_ack_intr(dev, DWC_ADV_I2C_INTR_CLR_RX_UNDER);
+ if (stat & DWC_ADV_I2C_INTR_RX_OVER)
+ dwc_adv_i2c_ack_intr(dev, DWC_ADV_I2C_INTR_CLR_RX_OVER);
+ if (stat & DWC_ADV_I2C_INTR_TX_OVER)
+ dwc_adv_i2c_ack_intr(dev, DWC_ADV_I2C_INTR_CLR_TX_OVER);
+ if (stat & DWC_ADV_I2C_INTR_RD_REQ)
+ dwc_adv_i2c_ack_intr(dev, DWC_ADV_I2C_INTR_CLR_RD_REQ);
+ if (stat & DWC_ADV_I2C_INTR_TX_ABRT) {
+ dev->abort_source = dwc_adv_reg_read(dev, dev->regs.tx_abrt_source);
+ dwc_adv_i2c_ack_intr(dev, DWC_ADV_I2C_INTR_CLR_TX_ABRT);
+ }
+ if (stat & DWC_ADV_I2C_INTR_RX_DONE)
+ dwc_adv_i2c_ack_intr(dev, DWC_ADV_I2C_INTR_CLR_RX_DONE);
+ if (stat & DWC_ADV_I2C_INTR_ACTIVITY)
+ dwc_adv_i2c_ack_intr(dev, DWC_ADV_I2C_INTR_CLR_ACTIVITY);
+ if (stat & DWC_ADV_I2C_INTR_STOP_DET &&
+ (dev->rx_outstanding == 0 || (stat & DWC_ADV_I2C_INTR_RX_FULL)))
+ dwc_adv_i2c_ack_intr(dev, DWC_ADV_I2C_INTR_CLR_STOP_DET);
+ if (stat & DWC_ADV_I2C_INTR_START_DET)
+ dwc_adv_i2c_ack_intr(dev, DWC_ADV_I2C_INTR_CLR_START_DET);
+ if (stat & DWC_ADV_I2C_INTR_GEN_CALL)
+ dwc_adv_i2c_ack_intr(dev, DWC_ADV_I2C_INTR_CLR_GEN_CALL);
+
+ return stat;
+}
+
+static void dwc_adv_i2c_process_transfer(struct dwc_adv_i2c_dev *dev, u32 stat)
+{
+ if (stat & DWC_ADV_I2C_INTR_TX_ABRT) {
+ /* TX_ABRT flushes the TX/RX FIFOs, nothing left to process */
+ dev->cmd_err |= DWC_ADV_I2C_INTR_TX_ABRT;
+ dev->status &= ~DWC_ADV_I2C_STATUS_SW_MASK;
+ dev->rx_outstanding = 0;
+ dwc_adv_reg_write(dev, dev->regs.intr_mask, 0);
+ goto tx_aborted;
+ }
+
+ if (stat & DWC_ADV_I2C_INTR_RX_FULL)
+ dwc_adv_i2c_read(dev);
+
+ if (stat & DWC_ADV_I2C_INTR_TX_EMPTY)
+ dwc_adv_i2c_xfer_msg(dev);
+
+ /* A STOP in the middle of a read or write is an error */
+ if ((stat & DWC_ADV_I2C_INTR_STOP_DET) &&
+ (dev->status & (DWC_ADV_I2C_STATUS_SW_WRITE_IN_PROGRESS |
+ DWC_ADV_I2C_STATUS_SW_READ_IN_PROGRESS))) {
+ dev_err(dev->dev, "spurious STOP, status 0x%x\n", dev->status);
+ dev->rx_outstanding = 0;
+ dev->msg_err = -EIO;
+ }
+
+tx_aborted:
+ if (((stat & (DWC_ADV_I2C_INTR_TX_ABRT | DWC_ADV_I2C_INTR_STOP_DET)) ||
+ dev->msg_err) && dev->rx_outstanding == 0)
+ complete(&dev->cmd_complete);
+}
+
+static irqreturn_t dwc_adv_i2c_handle_irq(struct dwc_adv_i2c_dev *dev)
+{
+ u32 enabled, stat;
+
+ enabled = dwc_adv_reg_read(dev, dev->regs.enable);
+ stat = dwc_adv_reg_read(dev, dev->regs.raw_intr_stat);
+
+ if (!enabled || !(stat & ~DWC_ADV_I2C_INTR_ACTIVITY))
+ return IRQ_NONE;
+
+ if (stat == GENMASK(31, 0))
+ return IRQ_NONE;
+
+ stat = dwc_adv_i2c_read_clear_intrbits(dev);
+
+ if (!(dev->status & DWC_ADV_I2C_STATUS_SW_ACTIVE)) {
+ dwc_adv_reg_write(dev, dev->regs.intr_mask, 0);
+ return IRQ_HANDLED;
+ }
+
+ dwc_adv_i2c_process_transfer(dev, stat);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t dwc_adv_i2c_isr(int irq, void *dev_id)
+{
+ struct dwc_adv_i2c_dev *dev = dev_id;
+ irqreturn_t ret;
+ int pm;
+
+ pm = pm_runtime_get_if_active(dev->dev);
+ if (!pm)
+ return IRQ_NONE;
+
+ ret = dwc_adv_i2c_handle_irq(dev);
+
+ if (pm > 0)
+ pm_runtime_put_autosuspend(dev->dev);
+
+ return ret;
+}
+
+static int dwc_adv_i2c_handle_tx_abort(struct dwc_adv_i2c_dev *dev)
+{
+ u32 abort_source = dev->abort_source;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(dwc_adv_i2c_abort_sources); i++) {
+ if (!(abort_source & BIT(i)) || !dwc_adv_i2c_abort_sources[i])
+ continue;
+ if (BIT(i) & (TX_ABRT_NOACK | TX_ABRT_USER_TRMNT))
+ dev_dbg(dev->dev, "%s\n", dwc_adv_i2c_abort_sources[i]);
+ else
+ dev_err(dev->dev, "%s\n", dwc_adv_i2c_abort_sources[i]);
+ }
+
+ if (abort_source & TX_ABRT_NOACK)
+ return -EREMOTEIO;
+ if (abort_source & TX_ARB_LOST)
+ return -EAGAIN;
+ if (abort_source & TX_ABRT_GCALL_READ)
+ return -EINVAL;
+
+ return -EIO;
+}
+
+static bool dwc_adv_i2c_msg_is_valid(struct dwc_adv_i2c_dev *dev,
+ struct i2c_msg *msgs, int idx)
+{
+ if (idx == 0)
+ return true;
+
+ if (msgs[idx - 1].addr != msgs[idx].addr) {
+ dev_err(dev->dev, "invalid target address\n");
+ return false;
+ }
+
+ return true;
+}
+
+static int dwc_adv_i2c_xfer_one_part(struct dwc_adv_i2c_dev *dev)
+{
+ int ret;
+
+ reinit_completion(&dev->cmd_complete);
+ dev->cmd_err = 0;
+ dev->msg_write_idx = 0;
+ dev->msg_read_idx = 0;
+ dev->msg_err = 0;
+ dev->status = 0;
+ dev->abort_source = 0;
+ dev->rx_outstanding = 0;
+ dev->need_restart = false;
+
+ ret = dwc_adv_i2c_wait_bus_not_busy(dev);
+ if (ret)
+ return ret;
+
+ dwc_adv_i2c_xfer_init(dev);
+
+ if (!wait_for_completion_timeout(&dev->cmd_complete, dev->adap.timeout)) {
+ dev_err(dev->dev, "controller timed out\n");
+ i2c_recover_bus(&dev->adap);
+ dwc_adv_i2c_init(dev);
+ return -ETIMEDOUT;
+ }
+
+ if (dwc_adv_i2c_is_controller_active(dev)) {
+ /*
+ * If the controller is still active after the timeout, attempt a
+ * bus recovery to clear any potentially locked state.
+ */
+ dev_err(dev->dev, "controller active after xfer, recovering\n");
+ i2c_recover_bus(&dev->adap);
+ dwc_adv_i2c_init(dev);
+ } else {
+ /*
+ * Disable the controller before returning, so that late
+ * interrupts do not race with the following transfer.
+ */
+ __dwc_adv_i2c_disable_nowait(dev);
+ }
+
+ if (dev->msg_err)
+ return dev->msg_err;
+
+ if (!dev->cmd_err && !dev->status)
+ return 0;
+
+ if (dev->cmd_err & DWC_ADV_I2C_INTR_TX_ABRT)
+ return dwc_adv_i2c_handle_tx_abort(dev);
+
+ if (dev->status)
+ dev_err(dev->dev, "transfer terminated early, status 0x%x\n", dev->status);
+
+ return -EIO;
+}
+
+static int dwc_adv_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
+{
+ struct dwc_adv_i2c_dev *dev = i2c_get_adapdata(adap);
+ int ret, idx, start = 0;
+
+ PM_RUNTIME_ACQUIRE_AUTOSUSPEND(dev->dev, pm);
+ if (PM_RUNTIME_ACQUIRE_ERR(&pm))
+ return -ENXIO;
+
+ for (idx = 0; idx < num; idx++) {
+ if (!dwc_adv_i2c_msg_is_valid(dev, &msgs[start], idx - start))
+ return -EINVAL;
+
+ if (!(msgs[idx].flags & I2C_M_STOP) && idx != num - 1)
+ continue;
+
+ dev->msgs = &msgs[start];
+ dev->msgs_num = idx - start + 1;
+
+ ret = dwc_adv_i2c_xfer_one_part(dev);
+ if (ret)
+ return ret;
+
+ start = idx + 1;
+ }
+
+ return num;
+}
+
+static u32 dwc_adv_i2c_func(struct i2c_adapter *adap)
+{
+ return 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 | I2C_FUNC_10BIT_ADDR | I2C_FUNC_PROTOCOL_MANGLING;
+}
+
+static const struct i2c_algorithm dwc_adv_i2c_algo = {
+ .xfer = dwc_adv_i2c_xfer,
+ .functionality = dwc_adv_i2c_func,
+};
+
+static const struct i2c_adapter_quirks dwc_adv_i2c_quirks = {
+ .flags = I2C_AQ_NO_ZERO_LEN,
+};
+
+static int dwc_adv_i2c_prepare_clk(struct dwc_adv_i2c_dev *dev, bool prepare)
+{
+ int ret;
+
+ if (!prepare) {
+ clk_disable_unprepare(dev->clk);
+ clk_disable_unprepare(dev->pclk);
+ return 0;
+ }
+
+ ret = clk_prepare_enable(dev->pclk);
+ if (ret)
+ return ret;
+
+ ret = clk_prepare_enable(dev->clk);
+ if (ret)
+ clk_disable_unprepare(dev->pclk);
+
+ return ret;
+}
+
+static void dwc_adv_i2c_prepare_recovery(struct i2c_adapter *adap)
+{
+ struct dwc_adv_i2c_dev *dev = i2c_get_adapdata(adap);
+
+ __dwc_adv_i2c_disable(dev);
+ reset_control_assert(dev->rst);
+ dwc_adv_i2c_prepare_clk(dev, false);
+}
+
+static void dwc_adv_i2c_unprepare_recovery(struct i2c_adapter *adap)
+{
+ struct dwc_adv_i2c_dev *dev = i2c_get_adapdata(adap);
+ int ret;
+
+ ret = dwc_adv_i2c_prepare_clk(dev, true);
+ if (ret) {
+ dev_err(dev->dev, "failed to enable clock for recovery: %d\n", ret);
+ reset_control_deassert(dev->rst);
+ return;
+ }
+
+ reset_control_deassert(dev->rst);
+ dwc_adv_i2c_init(dev);
+}
+
+static int dwc_adv_i2c_init_recovery_info(struct dwc_adv_i2c_dev *dev)
+{
+ struct i2c_bus_recovery_info *rinfo = &dev->rinfo;
+
+ rinfo->scl_gpiod = devm_gpiod_get_optional(dev->dev, "scl", GPIOD_OUT_HIGH);
+ if (IS_ERR_OR_NULL(rinfo->scl_gpiod))
+ return PTR_ERR_OR_ZERO(rinfo->scl_gpiod);
+
+ rinfo->sda_gpiod = devm_gpiod_get_optional(dev->dev, "sda", GPIOD_IN);
+ if (IS_ERR(rinfo->sda_gpiod))
+ return PTR_ERR(rinfo->sda_gpiod);
+
+ rinfo->pinctrl = devm_pinctrl_get(dev->dev);
+ if (IS_ERR(rinfo->pinctrl)) {
+ if (PTR_ERR(rinfo->pinctrl) == -EPROBE_DEFER)
+ return -EPROBE_DEFER;
+ rinfo->pinctrl = NULL;
+ }
+
+ rinfo->recover_bus = i2c_generic_scl_recovery;
+ rinfo->prepare_recovery = dwc_adv_i2c_prepare_recovery;
+ rinfo->unprepare_recovery = dwc_adv_i2c_unprepare_recovery;
+ dev->adap.bus_recovery_info = rinfo;
+
+ return 0;
+}
+
+static void dwc_adv_i2c_configure_controller(struct dwc_adv_i2c_dev *dev)
+{
+ dev->ctrlr_cfg = DWC_ADV_I2C_CON_CONTROLLER;
+
+ switch (dev->timings.bus_freq_hz) {
+ case I2C_MAX_STANDARD_MODE_FREQ:
+ dev->ctrlr_cfg |= DWC_ADV_I2C_CON_SPEED_STD;
+ break;
+ case I2C_MAX_HIGH_SPEED_MODE_FREQ:
+ dev->ctrlr_cfg |= DWC_ADV_I2C_CON_SPEED_HIGH;
+ break;
+ default:
+ dev->ctrlr_cfg |= DWC_ADV_I2C_CON_SPEED_FAST;
+ break;
+ }
+}
+
+static int dwc_adv_i2c_parse_fw(struct dwc_adv_i2c_dev *dev)
+{
+ struct device *device = dev->dev;
+
+ i2c_parse_fw_timings(device, &dev->timings, false);
+ if (!dev->timings.bus_freq_hz)
+ dev->timings.bus_freq_hz = I2C_MAX_FAST_MODE_FREQ;
+
+ switch (dev->timings.bus_freq_hz) {
+ case I2C_MAX_STANDARD_MODE_FREQ:
+ case I2C_MAX_FAST_MODE_FREQ:
+ case I2C_MAX_FAST_MODE_PLUS_FREQ:
+ case I2C_MAX_HIGH_SPEED_MODE_FREQ:
+ break;
+ default:
+ return dev_err_probe(device, -EINVAL, "invalid clock-frequency %u\n",
+ dev->timings.bus_freq_hz);
+ }
+
+ return 0;
+}
+
+static int dwc_adv_i2c_runtime_suspend(struct device *device)
+{
+ struct dwc_adv_i2c_dev *dev = dev_get_drvdata(device);
+
+ __dwc_adv_i2c_disable(dev);
+ dwc_adv_i2c_prepare_clk(dev, false);
+
+ return 0;
+}
+
+static int dwc_adv_i2c_runtime_resume(struct device *device)
+{
+ struct dwc_adv_i2c_dev *dev = dev_get_drvdata(device);
+ int ret;
+
+ ret = dwc_adv_i2c_prepare_clk(dev, true);
+ if (ret)
+ return ret;
+
+ dwc_adv_i2c_init(dev);
+
+ return 0;
+}
+
+static int dwc_adv_i2c_suspend(struct device *device)
+{
+ struct dwc_adv_i2c_dev *dev = dev_get_drvdata(device);
+
+ i2c_mark_adapter_suspended(&dev->adap);
+
+ return pm_runtime_force_suspend(device);
+}
+
+static int dwc_adv_i2c_resume(struct device *device)
+{
+ struct dwc_adv_i2c_dev *dev = dev_get_drvdata(device);
+ int ret;
+
+ ret = pm_runtime_force_resume(device);
+ if (ret)
+ return ret;
+
+ i2c_mark_adapter_resumed(&dev->adap);
+
+ return 0;
+}
+
+static const struct dev_pm_ops dwc_adv_i2c_pm_ops = {
+ LATE_SYSTEM_SLEEP_PM_OPS(dwc_adv_i2c_suspend, dwc_adv_i2c_resume)
+ RUNTIME_PM_OPS(dwc_adv_i2c_runtime_suspend, dwc_adv_i2c_runtime_resume, NULL)
+};
+
+static void dwc_adv_i2c_pm_teardown(void *data)
+{
+ struct dwc_adv_i2c_dev *dev = data;
+ int ret;
+
+ ret = pm_runtime_resume_and_get(dev->dev);
+ if (!ret)
+ __dwc_adv_i2c_disable(dev);
+ pm_runtime_dont_use_autosuspend(dev->dev);
+ if (!ret)
+ pm_runtime_put_noidle(dev->dev);
+ pm_runtime_disable(dev->dev);
+}
+
+static int dwc_adv_i2c_probe(struct platform_device *pdev)
+{
+ struct device *device = &pdev->dev;
+ const struct dwc_adv_i2c_match_data *match_data;
+ struct dwc_adv_i2c_dev *dev;
+ int irq, ret;
+
+ match_data = device_get_match_data(device);
+ if (!match_data)
+ return -ENODEV;
+
+ dev = devm_kzalloc(device, sizeof(*dev), GFP_KERNEL);
+ if (!dev)
+ return -ENOMEM;
+
+ dev->dev = device;
+ dev->tx_fifo_depth = match_data->tx_fifo_depth;
+ dev->rx_fifo_depth = match_data->rx_fifo_depth;
+
+ dev->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(dev->base))
+ return PTR_ERR(dev->base);
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ dev->rst = devm_reset_control_get_optional_exclusive_deasserted(device, NULL);
+ if (IS_ERR(dev->rst))
+ return dev_err_probe(device, PTR_ERR(dev->rst), "failed to get reset\n");
+
+ ret = dwc_adv_i2c_parse_fw(dev);
+ if (ret)
+ return ret;
+
+ dwc_adv_i2c_configure_controller(dev);
+
+ dev->pclk = devm_clk_get_enabled(device, "pclk");
+ if (IS_ERR(dev->pclk))
+ return dev_err_probe(device, PTR_ERR(dev->pclk), "failed to get pclk\n");
+
+ dev->clk = devm_clk_get_enabled(device, "ref");
+ if (IS_ERR(dev->clk))
+ return dev_err_probe(device, PTR_ERR(dev->clk), "failed to get ref clock\n");
+
+ ret = dwc_adv_i2c_discover_regs(dev);
+ if (ret)
+ return ret;
+
+ if (dev->timings.sda_hold_ns)
+ dev->sda_hold_time =
+ DIV_S64_ROUND_CLOSEST((s64)clk_get_rate(dev->clk) / 1000 *
+ dev->timings.sda_hold_ns, MICRO);
+
+ dwc_adv_i2c_set_timings(dev);
+
+ init_completion(&dev->cmd_complete);
+
+ platform_set_drvdata(pdev, dev);
+
+ ret = devm_request_irq(device, irq, dwc_adv_i2c_isr, 0, dev_name(device), dev);
+ if (ret)
+ return dev_err_probe(device, ret, "failed to request irq %d\n", irq);
+
+ ret = dwc_adv_i2c_init_recovery_info(dev);
+ if (ret)
+ return dev_err_probe(device, ret, "failed to init bus recovery\n");
+
+ i2c_set_adapdata(&dev->adap, dev);
+ dev->adap.owner = THIS_MODULE;
+ strscpy(dev->adap.name, "Synopsys Advanced I2C adapter",
+ sizeof(dev->adap.name));
+ dev->adap.algo = &dwc_adv_i2c_algo;
+ dev->adap.quirks = &dwc_adv_i2c_quirks;
+ dev->adap.dev.parent = device;
+ dev->adap.timeout = msecs_to_jiffies(DWC_ADV_I2C_XFER_TIMEOUT_MS);
+ dev->adap.retries = 3;
+ device_set_node(&dev->adap.dev, dev_fwnode(device));
+
+ dwc_adv_i2c_init(dev);
+
+ pm_runtime_set_autosuspend_delay(device, DWC_ADV_I2C_AUTOSUSPEND_DELAY_MS);
+ pm_runtime_use_autosuspend(device);
+ ret = pm_runtime_set_active(device);
+ if (ret)
+ return dev_err_probe(device, ret, "failed to set runtime PM active\n");
+
+ pm_runtime_enable(device);
+
+ ret = devm_add_action_or_reset(device, dwc_adv_i2c_pm_teardown, dev);
+ if (ret)
+ return ret;
+
+ return devm_i2c_add_adapter(device, &dev->adap);
+}
+
+static const struct of_device_id dwc_adv_i2c_of_match[] = {
+ { .compatible = "ti,tda54-i2c", .data = &tda54_i2c_match_data },
+ { }
+};
+MODULE_DEVICE_TABLE(of, dwc_adv_i2c_of_match);
+
+static struct platform_driver dwc_adv_i2c_driver = {
+ .probe = dwc_adv_i2c_probe,
+ .driver = {
+ .name = "i2c-dwc-adv",
+ .of_match_table = dwc_adv_i2c_of_match,
+ .pm = pm_ptr(&dwc_adv_i2c_pm_ops),
+ },
+};
+module_platform_driver(dwc_adv_i2c_driver);
+
+MODULE_AUTHOR("Aniket Limaye <a-limaye@ti.com>");
+MODULE_DESCRIPTION("Synopsys Advanced I2C (DWC_i2c) driver");
+MODULE_LICENSE("GPL");
--
2.53.0
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-10-27 11:32 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-27 11:30 [PATCH v4 0/2] i2c: Add Synopsys Advanced I2C (DWC_i2c) support for TI TDA54 Aniket Limaye
2026-10-27 11:30 ` [PATCH v4 1/2] dt-bindings: i2c: Add Synopsys DWC_i2c controller Aniket Limaye
2026-10-27 11:30 ` [PATCH v4 2/2] i2c: Add Synopsys Advanced I2C (DWC_i2c) driver Aniket Limaye
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®