From nobody Thu Apr 2 07:43:47 2026 Received: from TWMBX01.aspeed.com (mail.aspeedtech.com [211.20.114.72]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id F087F3B3BE3; Mon, 30 Mar 2026 08:21:53 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=211.20.114.72 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774858916; cv=none; b=uYT4gbWXcxo+FPGdkjax9/XDUzp0tZWvnGZ1Jvk1w5uajuYAF6B2ufYX9nGZ6UA47Ec7VvfaXEjcCRXEpfyBkyUGNJX52Vxp+n7Ce34ZUNxHWkCKhQI2f2T279xix8qjHJemsJibD9kJGouIsTIfD41Uh/sMK+/fV2d6PaNuOz4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774858916; c=relaxed/simple; bh=y16S4hcxAJwB0pyqLtb5Y4AIAVCk4wmOv1io+0cDITE=; h=From:Date:Subject:MIME-Version:Content-Type:Message-ID:References: In-Reply-To:To:CC; b=BlYt2nktjYLDwTUPzJQButnZpWAcIm0bRvyPN116CvSzZsRAs0uSeZbtPrfluvUt4vN8uB4/8fiU+lW70+vyIIYd2oe3HisFPCE9pk7CUSlYhS55f+BuWnHyNu1O/z6O/bK9Be73GuFETK7pDXjxdE+C7CmWf9NDS8vITiVgHDU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=aspeedtech.com; spf=pass smtp.mailfrom=aspeedtech.com; arc=none smtp.client-ip=211.20.114.72 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=aspeedtech.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=aspeedtech.com Received: from TWMBX01.aspeed.com (192.168.0.62) by TWMBX01.aspeed.com (192.168.0.62) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.1748.10; Mon, 30 Mar 2026 16:21:46 +0800 Received: from [127.0.1.1] (192.168.10.13) by TWMBX01.aspeed.com (192.168.0.62) with Microsoft SMTP Server id 15.2.1748.10 via Frontend Transport; Mon, 30 Mar 2026 16:21:46 +0800 From: Ryan Chen Date: Mon, 30 Mar 2026 16:21:46 +0800 Subject: [PATCH v28 1/4] dt-bindings: i2c: Split AST2600 binding into a new YAML Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-ID: <20260330-upstream_i2c-v28-1-17bdae39c5cb@aspeedtech.com> References: <20260330-upstream_i2c-v28-0-17bdae39c5cb@aspeedtech.com> In-Reply-To: <20260330-upstream_i2c-v28-0-17bdae39c5cb@aspeedtech.com> To: , , Andi Shyti , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Joel Stanley , Andrew Jeffery , "Benjamin Herrenschmidt" , Rayn Chen , Philipp Zabel CC: , , , , , , Ryan Chen X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1774858906; l=3549; i=ryan_chen@aspeedtech.com; s=20251126; h=from:subject:message-id; bh=y16S4hcxAJwB0pyqLtb5Y4AIAVCk4wmOv1io+0cDITE=; b=xdzWguXSMv7+wIqZ9sDcrMRQF/QqZGUPMTqN7T04Ova6V5kJEgflklf7lM2evpRivPdwlpfZ3 pJy8xFog63XAD3eWnOiW/wCeo4jnvSdemxVRPuibeiLHJ77E99OCskS X-Developer-Key: i=ryan_chen@aspeedtech.com; a=ed25519; pk=Xe73xY6tcnkuRjjbVAB/oU30KdB3FvG4nuJuILj7ZVc= The AST2600 I2C controller introduces a completely new register layout with separate controller and target register blocks, unlike the mixed register layout used by AST2400/AST2500. Move AST2600 I2C binding from aspeed,i2c.yaml to a dedicated aspeed,ast2600-i2c.yaml schema. Besides the split, this also adjusts for AST2600-specific requirements. - require two reg regions (controller register block + buffer block) - use clock-frequency for bus speed description - interrupts are required on AST2600 - use correct DTS coding style in example No compatible strings are changed. Signed-off-by: Ryan Chen --- Changes in v26: - commit message: include details of changes from original binding - fix example property ordering to follow DTS coding style - use consistent "AST2600" naming --- .../bindings/i2c/aspeed,ast2600-i2c.yaml | 62 ++++++++++++++++++= ++++ .../devicetree/bindings/i2c/aspeed,i2c.yaml | 3 +- 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/i2c/aspeed,ast2600-i2c.yaml = b/Documentation/devicetree/bindings/i2c/aspeed,ast2600-i2c.yaml new file mode 100644 index 000000000000..de2c359037da --- /dev/null +++ b/Documentation/devicetree/bindings/i2c/aspeed,ast2600-i2c.yaml @@ -0,0 +1,62 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/i2c/aspeed,ast2600-i2c.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: ASPEED I2C on the AST2600 SoCs + +maintainers: + - Ryan Chen + +allOf: + - $ref: /schemas/i2c/i2c-controller.yaml# + +properties: + compatible: + enum: + - aspeed,ast2600-i2c-bus + + reg: + items: + - description: controller registers + - description: controller buffer space + + interrupts: + maxItems: 1 + + clocks: + maxItems: 1 + + clock-frequency: + description: Desired operating frequency of the I2C bus in Hz. + minimum: 500 + maximum: 4000000 + default: 100000 + + resets: + maxItems: 1 + +required: + - reg + - compatible + - clocks + - resets + - interrupts + +unevaluatedProperties: false + +examples: + - | + #include + #include + i2c@80 { + compatible =3D "aspeed,ast2600-i2c-bus"; + reg =3D <0x80 0x80>, <0xc00 0x20>; + #address-cells =3D <1>; + #size-cells =3D <0>; + clocks =3D <&syscon ASPEED_CLK_APB>; + resets =3D <&syscon ASPEED_RESET_I2C>; + clock-frequency =3D <100000>; + interrupts =3D ; + }; diff --git a/Documentation/devicetree/bindings/i2c/aspeed,i2c.yaml b/Docume= ntation/devicetree/bindings/i2c/aspeed,i2c.yaml index 5b9bd2feda3b..d4e4f412feba 100644 --- a/Documentation/devicetree/bindings/i2c/aspeed,i2c.yaml +++ b/Documentation/devicetree/bindings/i2c/aspeed,i2c.yaml @@ -4,7 +4,7 @@ $id: http://devicetree.org/schemas/i2c/aspeed,i2c.yaml# $schema: http://devicetree.org/meta-schemas/core.yaml# =20 -title: ASPEED I2C on the AST24XX, AST25XX, and AST26XX SoCs +title: ASPEED I2C on the AST24XX, AST25XX SoCs =20 maintainers: - Rayn Chen @@ -17,7 +17,6 @@ properties: enum: - aspeed,ast2400-i2c-bus - aspeed,ast2500-i2c-bus - - aspeed,ast2600-i2c-bus =20 reg: minItems: 1 --=20 2.34.1 From nobody Thu Apr 2 07:43:47 2026 Received: from TWMBX01.aspeed.com (mail.aspeedtech.com [211.20.114.72]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 563653B4EA4; Mon, 30 Mar 2026 08:21:56 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=211.20.114.72 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774858918; cv=none; b=dwlmhYnebFydIxXDSVb9L0kqP8U1Ll/vckQd+RWG09ju6lNeHcphQYgDyweILLKrMZ02xBRoYuhx7f2OzgH2pc64C/qcApMNVAAFPqHBR5UijTjtYztiHhTWs+yCfDd5UPakNQY7OohztCenkgrxmbR439JTrXyhHPYio3Sbik0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774858918; c=relaxed/simple; bh=Z2AXlRcw8rroVI4rnpHgF+99FZI/yNPMg8RNUT8tbwc=; h=From:Date:Subject:MIME-Version:Content-Type:Message-ID:References: In-Reply-To:To:CC; b=Eekuhq4QZUjbvW/yainC4hbQLPzXUu9gTT0OWDIItYupr2IFw6zB3T7rrj1/+VYDnzlFAILr66uqpESS1dprhRaJwyB0BlQlpwciQPUtUu9ENjC6FERRGsM59h8fYP/4IFg/GrtfbtXs8Cd/6A2fAT0gJFBNu2A6zZKU1DROSEE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=aspeedtech.com; spf=pass smtp.mailfrom=aspeedtech.com; arc=none smtp.client-ip=211.20.114.72 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=aspeedtech.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=aspeedtech.com Received: from TWMBX01.aspeed.com (192.168.0.62) by TWMBX01.aspeed.com (192.168.0.62) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.1748.10; Mon, 30 Mar 2026 16:21:47 +0800 Received: from [127.0.1.1] (192.168.10.13) by TWMBX01.aspeed.com (192.168.0.62) with Microsoft SMTP Server id 15.2.1748.10 via Frontend Transport; Mon, 30 Mar 2026 16:21:47 +0800 From: Ryan Chen Date: Mon, 30 Mar 2026 16:21:47 +0800 Subject: [PATCH v28 2/4] dt-bindings: i2c: ast2600-i2c.yaml: Add global-regs and enable-dma properties Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-ID: <20260330-upstream_i2c-v28-2-17bdae39c5cb@aspeedtech.com> References: <20260330-upstream_i2c-v28-0-17bdae39c5cb@aspeedtech.com> In-Reply-To: <20260330-upstream_i2c-v28-0-17bdae39c5cb@aspeedtech.com> To: , , Andi Shyti , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Joel Stanley , Andrew Jeffery , "Benjamin Herrenschmidt" , Rayn Chen , Philipp Zabel CC: , , , , , , Ryan Chen X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1774858906; l=1878; i=ryan_chen@aspeedtech.com; s=20251126; h=from:subject:message-id; bh=Z2AXlRcw8rroVI4rnpHgF+99FZI/yNPMg8RNUT8tbwc=; b=mRu3dj7XE8wlOn99k+9o1IjAAtiPtC4krH7vcIPJvKLUcC8Mo/TQob7qLzxbb10+44H3WcEbG O1gXVUoJBpIAqYXpj+1/h707e1Uzv7zRg1QBWHqw1f7Awi/EM2OCbhU X-Developer-Key: i=ryan_chen@aspeedtech.com; a=ed25519; pk=Xe73xY6tcnkuRjjbVAB/oU30KdB3FvG4nuJuILj7ZVc= Add aspeed,enable-dma boolean property to indicate that DMA is available for transfers on this I2C bus. Also add the aspeed,global-regs phandle to reference the AST2600 global registers syscon node, containing the SoC-common I2C register set. These properties apply only to the AST2600 binding. Legacy DTs remain unchanged. Signed-off-by: Ryan Chen --- Changes in v28: - update commit message correspond with aspeed,enable-dma. - remove aspeed,transfer-mode and add aspeed,enable-dma property and description. - Fix aspeed,enable-dma description to reflect hardware capability rather than software behavior Changes in v27: - change aspeed,transfer-mode to aspeed,enable-dma. --- .../devicetree/bindings/i2c/aspeed,ast2600-i2c.yaml | 12 ++++++++= ++++ 1 file changed, 12 insertions(+) diff --git a/Documentation/devicetree/bindings/i2c/aspeed,ast2600-i2c.yaml = b/Documentation/devicetree/bindings/i2c/aspeed,ast2600-i2c.yaml index de2c359037da..67b23d1a4cec 100644 --- a/Documentation/devicetree/bindings/i2c/aspeed,ast2600-i2c.yaml +++ b/Documentation/devicetree/bindings/i2c/aspeed,ast2600-i2c.yaml @@ -37,6 +37,16 @@ properties: resets: maxItems: 1 =20 + aspeed,enable-dma: + type: boolean + description: Indicates this I2C controller instance has DMA capability. + + aspeed,global-regs: + $ref: /schemas/types.yaml#/definitions/phandle + description: + Phandle reference to the i2c global syscon node, containing the + SoC-common i2c register set. + required: - reg - compatible @@ -59,4 +69,6 @@ examples: resets =3D <&syscon ASPEED_RESET_I2C>; clock-frequency =3D <100000>; interrupts =3D ; + aspeed,global-regs =3D <&i2c_global>; + aspeed,enable-dma; }; --=20 2.34.1 From nobody Thu Apr 2 07:43:47 2026 Received: from TWMBX01.aspeed.com (mail.aspeedtech.com [211.20.114.72]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 51A533B636F; Mon, 30 Mar 2026 08:21:58 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=211.20.114.72 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774858921; cv=none; b=qUu2wtLRGudYWsM+NsX/glkz0VAs1IzuN5h37ynhg/ubAuDd3VMSbgoj0P1oPVaxjOlgsHwNsNBt7gxvYJghcmLgHLK7cH08/+Xa5CnKbB2B75kNHhK+KrXfhGGdGqM8wYnnQch8yrEDFEIVjLs8LTvaF8m6myv4jHfu6BqW/Zc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774858921; c=relaxed/simple; bh=cVF//4Im50k5fb4Tq1B2xo1LcWJgiSFw1Gq1Rm0z8vk=; h=From:Date:Subject:MIME-Version:Content-Type:Message-ID:References: In-Reply-To:To:CC; b=NHcAwpAr8kGoAChCNG9VXKck55PbR9HHzE7YwR5PXNQm8yfdLFj6w5MJBWWOtD8gny/4z6+RFTOKLa2/8KTtCRPfKXW4FpWC7gZw4kwfyZQoAU7N3GcML1UQRUICAEbOJuoY+A8Pf3xTXQQQAW0w+Sas4xrDGGHB/cBpnRrswMA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=aspeedtech.com; spf=pass smtp.mailfrom=aspeedtech.com; arc=none smtp.client-ip=211.20.114.72 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=aspeedtech.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=aspeedtech.com Received: from TWMBX01.aspeed.com (192.168.0.62) by TWMBX01.aspeed.com (192.168.0.62) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.1748.10; Mon, 30 Mar 2026 16:21:47 +0800 Received: from [127.0.1.1] (192.168.10.13) by TWMBX01.aspeed.com (192.168.0.62) with Microsoft SMTP Server id 15.2.1748.10 via Frontend Transport; Mon, 30 Mar 2026 16:21:47 +0800 From: Ryan Chen Date: Mon, 30 Mar 2026 16:21:48 +0800 Subject: [PATCH v28 3/4] i2c: ast2600: Add controller driver for AST2600 new register set Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-ID: <20260330-upstream_i2c-v28-3-17bdae39c5cb@aspeedtech.com> References: <20260330-upstream_i2c-v28-0-17bdae39c5cb@aspeedtech.com> In-Reply-To: <20260330-upstream_i2c-v28-0-17bdae39c5cb@aspeedtech.com> To: , , Andi Shyti , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Joel Stanley , Andrew Jeffery , "Benjamin Herrenschmidt" , Rayn Chen , Philipp Zabel CC: , , , , , , Ryan Chen X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1774858906; l=40481; i=ryan_chen@aspeedtech.com; s=20251126; h=from:subject:message-id; bh=cVF//4Im50k5fb4Tq1B2xo1LcWJgiSFw1Gq1Rm0z8vk=; b=XJSi2hVibTN04pO/KQePZZcpA9gz70IF+pSkDFxz21x6/jPmRtBhHm4QxdJoyOMlRA0j/SxIr Rs1krY0/9YdB6p2XQTJ0jmxO1k7aAPJn/YgKdO96eIijBqMKlx5A/x+ X-Developer-Key: i=ryan_chen@aspeedtech.com; a=ed25519; pk=Xe73xY6tcnkuRjjbVAB/oU30KdB3FvG4nuJuILj7ZVc= The AST2600 introduces a new I2C controller register layout, selectable at runtime via global control registers. Compared to the legacy layout used on AST2400/AST2500, the new layout separates controller (master) and target (slave) registers and adds support for packet-based transfers The new register set extends the hardware capabilities with: - Enhanced clock divider configuration for improved timing precision - tCKHighMin timing control for SCL high pulse width - Dual pool buffer mode (separate Tx/Rx buffers) - Extended DMA support with larger buffer size and alignment handling - Dedicated DMA buffers for controller and target directions - Hardware-assisted bus recovery and timeout mechanisms This patch adds an AST2600-specific I2C controller driver implementing the new register layout, including support for packet-based transfers and byte, buffer and DMA transfer modes. The legacy and new register layouts represent the same AST2600 I2C controller IP and therefore share the existing compatible string: "aspeed,ast2600-i2c-bus" To preserve DT ABI compatibility, driver selection is performed at probe time based on DT contents. In particular, the new binding requires the `aspeed,global-regs` phandle, which is absent from legacy DTBs: - The new driver only probes successfully when `aspeed,global-regs` is present. - The existing i2c-aspeed driver returns -ENODEV for AST2600 nodes that provide `aspeed,global-regs`, allowing the new driver to bind. Signed-off-by: Ryan Chen --- Changes in v28: - Separate xfer_mode_store into distinct parse and availability-check steps by introducing ast2600_i2c_xfer_mode_check() - fix tx dma memcpy source point address. - Use a temporary variable for devm_platform_get_and_ioremap_resource() to avoid storing an ERR_PTR in i2c_bus->buf_base; drop the redundant NULL assignment in the error path since i2c_bus is kzalloc()ed - Add ABI documentation file Documentation/ABI/testing/sysfs-driver-ast2600-i2c Changes in v27: - remove aspeed,transfer-mode selection instead aspeed,dma-mode. - add sysfs for xfer mode. Changes in v25: - Rename AST2600_I2CM_SMBUS_ALT to AST2600_I2CM_SMBUS_ALERT. - Refactor transfer mode handling using setup_tx/setup_rx helpers. - Rework DMA handling to use pre-allocated buffers and reduce mapping overhead in interrupt context. - Fix IRQ status checks to use consistent (sts & value) style. - Move device_property_read_bool() to probe(). - Improve probe error handling. - Handle timeout condition in target_byte_irq(). - Rename "package" to "packet". - Remove target reset when master wait_for_completion_timeout(). --- Documentation/ABI/testing/sysfs-driver-ast2600-i2c | 19 + drivers/i2c/busses/Makefile | 2 +- drivers/i2c/busses/i2c-aspeed.c | 5 + drivers/i2c/busses/i2c-ast2600.c | 1080 ++++++++++++++++= ++++ 4 files changed, 1105 insertions(+), 1 deletion(-) diff --git a/Documentation/ABI/testing/sysfs-driver-ast2600-i2c b/Documenta= tion/ABI/testing/sysfs-driver-ast2600-i2c new file mode 100644 index 000000000000..7d2a69a7281a --- /dev/null +++ b/Documentation/ABI/testing/sysfs-driver-ast2600-i2c @@ -0,0 +1,19 @@ +What: /sys/bus/platform/drivers/i2c-ast2600/.../xfer_mode +Date: March 2026 +KernelVersion: 7.x +Contact: Ryan Chen +Description: Shows or sets the active transfer mode for an ASPEED AST2600 + I2C controller instance. + + Possible values: + + =3D=3D=3D=3D=3D=3D=3D=3D=3D =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + byte Programmed I/O, one byte at a time. + buffer Programmed I/O using the hardware FIFO buffer. + Only available if the controller has a buffer + resource defined in the device tree. + dma DMA transfer (if DMA is available for this + controller). + =3D=3D=3D=3D=3D=3D=3D=3D=3D =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + + Writing an unsupported or unavailable mode returns -EINVAL. diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile index 547123ab351f..ece201a67d41 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -37,7 +37,7 @@ obj-$(CONFIG_I2C_POWERMAC) +=3D i2c-powermac.o obj-$(CONFIG_I2C_ALTERA) +=3D i2c-altera.o obj-$(CONFIG_I2C_AMD_MP2) +=3D i2c-amd-mp2-pci.o i2c-amd-mp2-plat.o obj-$(CONFIG_I2C_AMD_ASF) +=3D i2c-amd-asf-plat.o -obj-$(CONFIG_I2C_ASPEED) +=3D i2c-aspeed.o +obj-$(CONFIG_I2C_ASPEED) +=3D i2c-aspeed.o i2c-ast2600.o obj-$(CONFIG_I2C_AT91) +=3D i2c-at91.o i2c-at91-y :=3D i2c-at91-core.o i2c-at91-master.o i2c-at91-$(CONFIG_I2C_AT91_SLAVE_EXPERIMENTAL) +=3D i2c-at91-slave.o diff --git a/drivers/i2c/busses/i2c-aspeed.c b/drivers/i2c/busses/i2c-aspee= d.c index a26b74c71206..8286fd2cd130 100644 --- a/drivers/i2c/busses/i2c-aspeed.c +++ b/drivers/i2c/busses/i2c-aspeed.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include =20 @@ -1002,6 +1003,10 @@ static int aspeed_i2c_probe_bus(struct platform_devi= ce *pdev) struct clk *parent_clk; int irq, ret; =20 + if (device_is_compatible(&pdev->dev, "aspeed,ast2600-i2c-bus") && + device_property_present(&pdev->dev, "aspeed,global-regs")) + return -ENODEV; + bus =3D devm_kzalloc(&pdev->dev, sizeof(*bus), GFP_KERNEL); if (!bus) return -ENOMEM; diff --git a/drivers/i2c/busses/i2c-ast2600.c b/drivers/i2c/busses/i2c-ast2= 600.c new file mode 100644 index 000000000000..c2368ba309a7 --- /dev/null +++ b/drivers/i2c/busses/i2c-ast2600.c @@ -0,0 +1,1080 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ASPEED AST2600 new register set I2C controller driver + * + * Copyright (C) 2026 ASPEED Technology Inc. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define AST2600_I2CG_ISR 0x00 +#define AST2600_I2CG_SLAVE_ISR 0x04 +#define AST2600_I2CG_OWNER 0x08 +#define AST2600_I2CG_CTRL 0x0C +#define AST2600_I2CG_CLK_DIV_CTRL 0x10 + +#define AST2600_I2CG_SLAVE_PKT_NAK BIT(4) +#define AST2600_I2CG_M_S_SEPARATE_INTR BIT(3) +#define AST2600_I2CG_CTRL_NEW_REG BIT(2) +#define AST2600_I2CG_CTRL_NEW_CLK_DIV BIT(1) +#define AST2600_GLOBAL_INIT \ + (AST2600_I2CG_CTRL_NEW_REG | AST2600_I2CG_CTRL_NEW_CLK_DIV) +/* + * APB clk : 100Mhz + * div : scl : baseclk [APB/((div/2) + 1)] : tBuf [1/bclk * 16] + * I2CG10[31:24] base clk4 for i2c auto recovery timeout counter (0xC6) + * I2CG10[23:16] base clk3 for Standard-mode (100Khz) min tBuf 4.7us + * 0x3c : 100.8Khz : 3.225Mhz : 4.96us + * 0x3d : 99.2Khz : 3.174Mhz : 5.04us + * 0x3e : 97.65Khz : 3.125Mhz : 5.12us + * 0x40 : 97.75Khz : 3.03Mhz : 5.28us + * 0x41 : 99.5Khz : 2.98Mhz : 5.36us (default) + * I2CG10[15:8] base clk2 for Fast-mode (400Khz) min tBuf 1.3us + * 0x12 : 400Khz : 10Mhz : 1.6us + * I2CG10[7:0] base clk1 for Fast-mode Plus (1Mhz) min tBuf 0.5us + * 0x08 : 1Mhz : 20Mhz : 0.8us + */ +#define I2CCG_DIV_CTRL 0xC6411208 + +/* 0x00 : I2CC Controller/Target Function Control Register */ +#define AST2600_I2CC_FUN_CTRL 0x00 +#define AST2600_I2CC_SLAVE_ADDR_RX_EN BIT(20) +#define AST2600_I2CC_MASTER_RETRY_MASK GENMASK(19, 18) +#define AST2600_I2CC_MASTER_RETRY(x) (((x) & GENMASK(1, 0)) << 18) +#define AST2600_I2CC_BUS_AUTO_RELEASE BIT(17) +#define AST2600_I2CC_M_SDA_LOCK_EN BIT(16) +#define AST2600_I2CC_MULTI_MASTER_DIS BIT(15) +#define AST2600_I2CC_M_SCL_DRIVE_EN BIT(14) +#define AST2600_I2CC_MSB_STS BIT(9) +#define AST2600_I2CC_SDA_DRIVE_1T_EN BIT(8) +#define AST2600_I2CC_M_SDA_DRIVE_1T_EN BIT(7) +#define AST2600_I2CC_M_HIGH_SPEED_EN BIT(6) +/* reserver 5 : 2 */ +#define AST2600_I2CC_SLAVE_EN BIT(1) +#define AST2600_I2CC_MASTER_EN BIT(0) + +/* 0x04 : I2CC Controller/Target Clock and AC Timing Control Register #1 */ +#define AST2600_I2CC_AC_TIMING 0x04 +#define AST2600_I2CC_TTIMEOUT(x) (((x) & GENMASK(4, 0)) << 24) +#define AST2600_I2CC_TCKHIGHMIN(x) (((x) & GENMASK(3, 0)) << 20) +#define AST2600_I2CC_TCKHIGH(x) (((x) & GENMASK(3, 0)) << 16) +#define AST2600_I2CC_TCKLOW(x) (((x) & GENMASK(3, 0)) << 12) +#define AST2600_I2CC_THDDAT(x) (((x) & GENMASK(1, 0)) << 10) +#define AST2600_I2CC_TOUTBASECLK(x) (((x) & GENMASK(1, 0)) << 8) +#define AST2600_I2CC_TBASECLK(x) ((x) & GENMASK(3, 0)) +#define AST2600_I2CC_AC_TIMING_MASK GENMASK(23, 0) + +/* 0x08 : I2CC Controller/Target Transmit/Receive Byte Buffer Register */ +#define AST2600_I2CC_STS_AND_BUFF 0x08 +#define AST2600_I2CC_TX_DIR_MASK GENMASK(31, 29) +#define AST2600_I2CC_SDA_OE BIT(28) +#define AST2600_I2CC_SDA_O BIT(27) +#define AST2600_I2CC_SCL_OE BIT(26) +#define AST2600_I2CC_SCL_O BIT(25) + +#define AST2600_I2CC_SCL_LINE_STS BIT(18) +#define AST2600_I2CC_SDA_LINE_STS BIT(17) +#define AST2600_I2CC_BUS_BUSY_STS BIT(16) + +#define AST2600_I2CC_GET_RX_BUFF(x) (((x) >> 8) & GENMASK(7, 0)) + +/* 0x0C : I2CC Controller/Target Pool Buffer Control Register */ +#define AST2600_I2CC_BUFF_CTRL 0x0C +#define AST2600_I2CC_GET_RX_BUF_LEN(x) (((x) & GENMASK(29, 24)) >> 24) +#define AST2600_I2CC_SET_RX_BUF_LEN(x) (((((x) - 1) & GENMASK(4, 0)) << 1= 6) | BIT(0)) +#define AST2600_I2CC_SET_TX_BUF_LEN(x) (((((x) - 1) & GENMASK(4, 0)) << 8= ) | BIT(0)) +#define AST2600_I2CC_GET_TX_BUF_LEN(x) ((((x) & GENMASK(12, 8)) >> 8)= + 1) + +/* 0x10 : I2CM Controller Interrupt Control Register */ +#define AST2600_I2CM_IER 0x10 +/* 0x14 : I2CM Controller Interrupt Status Register : WC */ +#define AST2600_I2CM_ISR 0x14 + +#define AST2600_I2CM_PKT_TIMEOUT BIT(18) +#define AST2600_I2CM_PKT_ERROR BIT(17) +#define AST2600_I2CM_PKT_DONE BIT(16) + +#define AST2600_I2CM_BUS_RECOVER_FAIL BIT(15) +#define AST2600_I2CM_SDA_DL_TO BIT(14) +#define AST2600_I2CM_BUS_RECOVER BIT(13) +#define AST2600_I2CM_SMBUS_ALERT BIT(12) + +#define AST2600_I2CM_SCL_LOW_TO BIT(6) +#define AST2600_I2CM_ABNORMAL BIT(5) +#define AST2600_I2CM_NORMAL_STOP BIT(4) +#define AST2600_I2CM_ARBIT_LOSS BIT(3) +#define AST2600_I2CM_RX_DONE BIT(2) +#define AST2600_I2CM_TX_NAK BIT(1) +#define AST2600_I2CM_TX_ACK BIT(0) + +/* 0x18 : I2CM Controller Command/Status Register */ +#define AST2600_I2CM_CMD_STS 0x18 +#define AST2600_I2CM_PKT_ADDR(x) (((x) & GENMASK(6, 0)) << 24) +#define AST2600_I2CM_PKT_EN BIT(16) +#define AST2600_I2CM_SDA_OE_OUT_DIR BIT(15) +#define AST2600_I2CM_SDA_O_OUT_DIR BIT(14) +#define AST2600_I2CM_SCL_OE_OUT_DIR BIT(13) +#define AST2600_I2CM_SCL_O_OUT_DIR BIT(12) +#define AST2600_I2CM_RECOVER_CMD_EN BIT(11) + +#define AST2600_I2CM_RX_DMA_EN BIT(9) +#define AST2600_I2CM_TX_DMA_EN BIT(8) +/* Command Bit */ +#define AST2600_I2CM_RX_BUFF_EN BIT(7) +#define AST2600_I2CM_TX_BUFF_EN BIT(6) +#define AST2600_I2CM_STOP_CMD BIT(5) +#define AST2600_I2CM_RX_CMD_LAST BIT(4) +#define AST2600_I2CM_RX_CMD BIT(3) + +#define AST2600_I2CM_TX_CMD BIT(1) +#define AST2600_I2CM_START_CMD BIT(0) + +/* 0x1C : I2CM Controller DMA Transfer Length Register */ +#define AST2600_I2CM_DMA_LEN 0x1C +/* Tx Rx support length 1 ~ 4096 */ +#define AST2600_I2CM_SET_RX_DMA_LEN(x) ((((x) & GENMASK(11, 0)) << 16) | B= IT(31)) +#define AST2600_I2CM_SET_TX_DMA_LEN(x) (((x) & GENMASK(11, 0)) | BIT(15)) + +/* 0x20 : I2CS Target Interrupt Control Register */ +#define AST2600_I2CS_IER 0x20 +/* 0x24 : I2CS Target Interrupt Status Register */ +#define AST2600_I2CS_ISR 0x24 + +#define AST2600_I2CS_ADDR_INDICATE_MASK GENMASK(31, 30) +#define AST2600_I2CS_SLAVE_PENDING BIT(29) + +#define AST2600_I2CS_WAIT_TX_DMA BIT(25) +#define AST2600_I2CS_WAIT_RX_DMA BIT(24) + +#define AST2600_I2CS_ADDR3_NAK BIT(22) +#define AST2600_I2CS_ADDR2_NAK BIT(21) +#define AST2600_I2CS_ADDR1_NAK BIT(20) + +#define AST2600_I2CS_ADDR_MASK GENMASK(19, 18) +#define AST2600_I2CS_PKT_ERROR BIT(17) +#define AST2600_I2CS_PKT_DONE BIT(16) +#define AST2600_I2CS_INACTIVE_TO BIT(15) + +#define AST2600_I2CS_SLAVE_MATCH BIT(7) +#define AST2600_I2CS_ABNOR_STOP BIT(5) +#define AST2600_I2CS_STOP BIT(4) +#define AST2600_I2CS_RX_DONE_NAK BIT(3) +#define AST2600_I2CS_RX_DONE BIT(2) +#define AST2600_I2CS_TX_NAK BIT(1) +#define AST2600_I2CS_TX_ACK BIT(0) + +/* 0x28 : I2CS Target CMD/Status Register */ +#define AST2600_I2CS_CMD_STS 0x28 +#define AST2600_I2CS_ACTIVE_ALL GENMASK(18, 17) +#define AST2600_I2CS_PKT_MODE_EN BIT(16) +#define AST2600_I2CS_AUTO_NAK_NOADDR BIT(15) +#define AST2600_I2CS_AUTO_NAK_EN BIT(14) + +#define AST2600_I2CS_ALT_EN BIT(10) +#define AST2600_I2CS_RX_DMA_EN BIT(9) +#define AST2600_I2CS_TX_DMA_EN BIT(8) +#define AST2600_I2CS_RX_BUFF_EN BIT(7) +#define AST2600_I2CS_TX_BUFF_EN BIT(6) +#define AST2600_I2CS_RX_CMD_LAST BIT(4) + +#define AST2600_I2CS_TX_CMD BIT(2) + +#define AST2600_I2CS_DMA_LEN 0x2C +#define AST2600_I2CS_SET_RX_DMA_LEN(x) (((((x) - 1) & GENMASK(11, 0)) << 1= 6) | BIT(31)) +#define AST2600_I2CS_SET_TX_DMA_LEN(x) ((((x) - 1) & GENMASK(11, 0)) | BIT= (15)) + +/* I2CM Controller DMA Tx Buffer Register */ +#define AST2600_I2CM_TX_DMA 0x30 +/* I2CM Controller DMA Rx Buffer Register */ +#define AST2600_I2CM_RX_DMA 0x34 +/* I2CS Target DMA Tx Buffer Register */ +#define AST2600_I2CS_TX_DMA 0x38 +/* I2CS Target DMA Rx Buffer Register */ +#define AST2600_I2CS_RX_DMA 0x3C + +#define AST2600_I2CS_ADDR_CTRL 0x40 + +#define AST2600_I2CS_ADDR3_MASK GENMASK(22, 16) +#define AST2600_I2CS_ADDR2_MASK GENMASK(14, 8) +#define AST2600_I2CS_ADDR1_MASK GENMASK(6, 0) + +#define AST2600_I2CM_DMA_LEN_STS 0x48 +#define AST2600_I2CS_DMA_LEN_STS 0x4C + +#define AST2600_I2C_GET_TX_DMA_LEN(x) ((x) & GENMASK(12, 0)) +#define AST2600_I2C_GET_RX_DMA_LEN(x) (((x) & GENMASK(28, 16)) >> 1= 6) + +/* 0x40 : Target Device Address Register */ +#define AST2600_I2CS_ADDR3_ENABLE BIT(23) +#define AST2600_I2CS_ADDR3(x) ((x) << 16) +#define AST2600_I2CS_ADDR2_ENABLE BIT(15) +#define AST2600_I2CS_ADDR2(x) ((x) << 8) +#define AST2600_I2CS_ADDR1_ENABLE BIT(7) +#define AST2600_I2CS_ADDR1(x) (x) + +#define I2C_TARGET_MSG_BUF_SIZE 4096 + +#define AST2600_I2C_DMA_SIZE 4096 + +#define CONTROLLER_TRIGGER_LAST_STOP (AST2600_I2CM_RX_CMD_LAST | AST2600_I= 2CM_STOP_CMD) +#define TARGET_TRIGGER_CMD (AST2600_I2CS_ACTIVE_ALL | AST2600_I2CS_PKT_MOD= E_EN) + +#define AST_I2C_TIMEOUT_CLK 0x1 + +enum xfer_mode { + BYTE_MODE, + BUFF_MODE, + DMA_MODE, +}; + +struct ast2600_i2c_bus { + struct i2c_adapter adap; + struct device *dev; + void __iomem *reg_base; + struct regmap *global_regs; + struct clk *clk; + struct i2c_timings timing_info; + struct completion cmd_complete; + struct i2c_msg *msgs; + u8 *controller_dma_buf; + dma_addr_t controller_dma_addr; + u32 apb_clk; + u32 timeout; + int irq; + int cmd_err; + int msgs_index; + int msgs_count; + int controller_xfer_cnt; + size_t buf_index; + size_t buf_size; + enum xfer_mode mode; + bool dma_available; + bool multi_master; + /* Buffer mode */ + void __iomem *buf_base; + int (*setup_tx)(u32 cmd, struct ast2600_i2c_bus *i2c_bus); + int (*setup_rx)(u32 cmd, struct ast2600_i2c_bus *i2c_bus); +}; + +static void ast2600_i2c_ac_timing_config(struct ast2600_i2c_bus *i2c_bus) +{ + unsigned long base_clk[16]; + int baseclk_idx =3D 0; + int divisor =3D 0; + u32 clk_div_reg; + u32 scl_low; + u32 scl_high; + u32 data; + + regmap_read(i2c_bus->global_regs, AST2600_I2CG_CLK_DIV_CTRL, &clk_div_reg= ); + + for (int i =3D 0; i < ARRAY_SIZE(base_clk); i++) { + if (i =3D=3D 0) + base_clk[i] =3D i2c_bus->apb_clk; + else if (i < 5) + base_clk[i] =3D (i2c_bus->apb_clk * 2) / + (((clk_div_reg >> ((i - 1) * 8)) & GENMASK(7, 0)) + 2); + else + base_clk[i] =3D base_clk[4] >> (i - 4); + + if ((base_clk[i] / i2c_bus->timing_info.bus_freq_hz) <=3D 32) { + baseclk_idx =3D i; + divisor =3D DIV_ROUND_UP(base_clk[i], i2c_bus->timing_info.bus_freq_hz); + break; + } + } + baseclk_idx =3D min(baseclk_idx, 15); + divisor =3D min(divisor, 32); + scl_low =3D min(divisor * 9 / 16 - 1, 15); + scl_high =3D (divisor - scl_low - 2) & GENMASK(3, 0); + data =3D (scl_high - 1) << 20 | scl_high << 16 | scl_low << 12 | baseclk_= idx; + if (i2c_bus->timeout) { + data |=3D AST2600_I2CC_TOUTBASECLK(AST_I2C_TIMEOUT_CLK); + data |=3D AST2600_I2CC_TTIMEOUT(i2c_bus->timeout); + } + + writel(data, i2c_bus->reg_base + AST2600_I2CC_AC_TIMING); +} + +static int ast2600_i2c_recover_bus(struct ast2600_i2c_bus *i2c_bus) +{ + u32 state =3D readl(i2c_bus->reg_base + AST2600_I2CC_STS_AND_BUFF); + int ret =3D 0; + u32 ctrl; + int r; + + dev_dbg(i2c_bus->dev, "%d-bus recovery bus [%x]\n", i2c_bus->adap.nr, sta= te); + + /* reset controller */ + ctrl =3D readl(i2c_bus->reg_base + AST2600_I2CC_FUN_CTRL); + writel(ctrl & ~AST2600_I2CC_MASTER_EN, i2c_bus->reg_base + AST2600_I2CC_F= UN_CTRL); + writel(ctrl, i2c_bus->reg_base + AST2600_I2CC_FUN_CTRL); + + reinit_completion(&i2c_bus->cmd_complete); + i2c_bus->cmd_err =3D 0; + + /* Check SDA/SCL status in the status register. */ + state =3D readl(i2c_bus->reg_base + AST2600_I2CC_STS_AND_BUFF); + if (!(state & AST2600_I2CC_SDA_LINE_STS) && (state & AST2600_I2CC_SCL_LIN= E_STS)) { + writel(AST2600_I2CM_RECOVER_CMD_EN, i2c_bus->reg_base + AST2600_I2CM_CMD= _STS); + r =3D wait_for_completion_timeout(&i2c_bus->cmd_complete, i2c_bus->adap.= timeout); + if (r =3D=3D 0) { + dev_dbg(i2c_bus->dev, "recovery timed out\n"); + return -ETIMEDOUT; + } else if (i2c_bus->cmd_err) { + dev_dbg(i2c_bus->dev, "recovery error\n"); + ret =3D -EPROTO; + } + } + + /* Recovery done */ + state =3D readl(i2c_bus->reg_base + AST2600_I2CC_STS_AND_BUFF); + if (state & AST2600_I2CC_BUS_BUSY_STS) { + dev_dbg(i2c_bus->dev, "Can't recover bus [%x]\n", state); + ret =3D -EPROTO; + } + + return ret; +} + +static int ast2600_i2c_setup_dma_tx(u32 cmd, struct ast2600_i2c_bus *i2c_b= us) +{ + struct i2c_msg *msg =3D &i2c_bus->msgs[i2c_bus->msgs_index]; + int xfer_len =3D msg->len - i2c_bus->controller_xfer_cnt; + + cmd |=3D AST2600_I2CM_PKT_EN; + + if (xfer_len > AST2600_I2C_DMA_SIZE) + xfer_len =3D AST2600_I2C_DMA_SIZE; + else if (i2c_bus->msgs_index + 1 =3D=3D i2c_bus->msgs_count) + cmd |=3D AST2600_I2CM_STOP_CMD; + + if (cmd & AST2600_I2CM_START_CMD) + cmd |=3D AST2600_I2CM_PKT_ADDR(msg->addr); + + if (xfer_len) { + memcpy(i2c_bus->controller_dma_buf, + msg->buf + i2c_bus->controller_xfer_cnt, xfer_len); + cmd |=3D AST2600_I2CM_TX_DMA_EN | AST2600_I2CM_TX_CMD; + writel(AST2600_I2CM_SET_TX_DMA_LEN(xfer_len - 1), + i2c_bus->reg_base + AST2600_I2CM_DMA_LEN); + } + + writel(cmd, i2c_bus->reg_base + AST2600_I2CM_CMD_STS); + + return 0; +} + +static int ast2600_i2c_setup_buff_tx(u32 cmd, struct ast2600_i2c_bus *i2c_= bus) +{ + struct i2c_msg *msg =3D &i2c_bus->msgs[i2c_bus->msgs_index]; + int xfer_len =3D msg->len - i2c_bus->controller_xfer_cnt; + u32 wbuf_dword; + int i; + + cmd |=3D AST2600_I2CM_PKT_EN; + + if (xfer_len > i2c_bus->buf_size) + xfer_len =3D i2c_bus->buf_size; + else if (i2c_bus->msgs_index + 1 =3D=3D i2c_bus->msgs_count) + cmd |=3D AST2600_I2CM_STOP_CMD; + + if (cmd & AST2600_I2CM_START_CMD) + cmd |=3D AST2600_I2CM_PKT_ADDR(msg->addr); + + if (xfer_len) { + cmd |=3D AST2600_I2CM_TX_BUFF_EN | AST2600_I2CM_TX_CMD; + /* + * The controller's buffer register supports dword writes only. + * Therefore, write dwords to the buffer register in a 4-byte aligned, + * and write the remaining unaligned data at the end. + */ + for (i =3D 0; i < xfer_len; i +=3D 4) { + int xfer_cnt =3D i2c_bus->controller_xfer_cnt + i; + + switch (min(xfer_len - i, 4) % 4) { + case 1: + wbuf_dword =3D msg->buf[xfer_cnt]; + break; + case 2: + wbuf_dword =3D get_unaligned_le16(&msg->buf[xfer_cnt]); + break; + case 3: + wbuf_dword =3D get_unaligned_le24(&msg->buf[xfer_cnt]); + break; + default: + wbuf_dword =3D get_unaligned_le32(&msg->buf[xfer_cnt]); + break; + } + writel(wbuf_dword, i2c_bus->buf_base + i); + } + writel(AST2600_I2CC_SET_TX_BUF_LEN(xfer_len), + i2c_bus->reg_base + AST2600_I2CC_BUFF_CTRL); + } + + writel(cmd, i2c_bus->reg_base + AST2600_I2CM_CMD_STS); + + return 0; +} + +static int ast2600_i2c_setup_byte_tx(u32 cmd, struct ast2600_i2c_bus *i2c_= bus) +{ + struct i2c_msg *msg =3D &i2c_bus->msgs[i2c_bus->msgs_index]; + int xfer_len; + + xfer_len =3D msg->len - i2c_bus->controller_xfer_cnt; + + cmd |=3D AST2600_I2CM_PKT_EN; + + if (cmd & AST2600_I2CM_START_CMD) + cmd |=3D AST2600_I2CM_PKT_ADDR(msg->addr); + + if ((i2c_bus->msgs_index + 1 =3D=3D i2c_bus->msgs_count) && + ((i2c_bus->controller_xfer_cnt + 1) =3D=3D msg->len)) + cmd |=3D AST2600_I2CM_STOP_CMD; + + if (xfer_len) { + cmd |=3D AST2600_I2CM_TX_CMD; + writel(msg->buf[i2c_bus->controller_xfer_cnt], + i2c_bus->reg_base + AST2600_I2CC_STS_AND_BUFF); + } + + writel(cmd, i2c_bus->reg_base + AST2600_I2CM_CMD_STS); + + return 0; +} + +static int ast2600_i2c_setup_dma_rx(u32 cmd, struct ast2600_i2c_bus *i2c_b= us) +{ + struct i2c_msg *msg =3D &i2c_bus->msgs[i2c_bus->msgs_index]; + int xfer_len =3D msg->len - i2c_bus->controller_xfer_cnt; + + cmd |=3D AST2600_I2CM_PKT_EN | AST2600_I2CM_RX_DMA_EN | AST2600_I2CM_RX_C= MD; + + if (msg->flags & I2C_M_RECV_LEN) + xfer_len =3D 1; + else if (xfer_len > AST2600_I2C_DMA_SIZE) + xfer_len =3D AST2600_I2C_DMA_SIZE; + else if (i2c_bus->msgs_index + 1 =3D=3D i2c_bus->msgs_count) + cmd |=3D CONTROLLER_TRIGGER_LAST_STOP; + + writel(AST2600_I2CM_SET_RX_DMA_LEN(xfer_len - 1), i2c_bus->reg_base + AST= 2600_I2CM_DMA_LEN); + + if (cmd & AST2600_I2CM_START_CMD) + cmd |=3D AST2600_I2CM_PKT_ADDR(msg->addr); + + writel(cmd, i2c_bus->reg_base + AST2600_I2CM_CMD_STS); + + return 0; +} + +static int ast2600_i2c_setup_buff_rx(u32 cmd, struct ast2600_i2c_bus *i2c_= bus) +{ + struct i2c_msg *msg =3D &i2c_bus->msgs[i2c_bus->msgs_index]; + int xfer_len =3D msg->len - i2c_bus->controller_xfer_cnt; + + cmd |=3D AST2600_I2CM_PKT_EN | AST2600_I2CM_RX_BUFF_EN | AST2600_I2CM_RX_= CMD; + + if (cmd & AST2600_I2CM_START_CMD) + cmd |=3D AST2600_I2CM_PKT_ADDR(msg->addr); + + if (msg->flags & I2C_M_RECV_LEN) { + dev_dbg(i2c_bus->dev, "smbus read\n"); + xfer_len =3D 1; + } else if (xfer_len > i2c_bus->buf_size) { + xfer_len =3D i2c_bus->buf_size; + } else if (i2c_bus->msgs_index + 1 =3D=3D i2c_bus->msgs_count) { + cmd |=3D CONTROLLER_TRIGGER_LAST_STOP; + } + writel(AST2600_I2CC_SET_RX_BUF_LEN(xfer_len), i2c_bus->reg_base + AST2600= _I2CC_BUFF_CTRL); + + writel(cmd, i2c_bus->reg_base + AST2600_I2CM_CMD_STS); + + return 0; +} + +static int ast2600_i2c_setup_byte_rx(u32 cmd, struct ast2600_i2c_bus *i2c_= bus) +{ + struct i2c_msg *msg =3D &i2c_bus->msgs[i2c_bus->msgs_index]; + + cmd |=3D AST2600_I2CM_PKT_EN | AST2600_I2CM_RX_CMD; + + if (cmd & AST2600_I2CM_START_CMD) + cmd |=3D AST2600_I2CM_PKT_ADDR(msg->addr); + + if (msg->flags & I2C_M_RECV_LEN) { + dev_dbg(i2c_bus->dev, "smbus read\n"); + } else if ((i2c_bus->msgs_index + 1 =3D=3D i2c_bus->msgs_count) && + ((i2c_bus->controller_xfer_cnt + 1) =3D=3D msg->len)) { + cmd |=3D CONTROLLER_TRIGGER_LAST_STOP; + } + + writel(cmd, i2c_bus->reg_base + AST2600_I2CM_CMD_STS); + + return 0; +} + +static int ast2600_i2c_do_start(struct ast2600_i2c_bus *i2c_bus) +{ + struct i2c_msg *msg =3D &i2c_bus->msgs[i2c_bus->msgs_index]; + + /* send start */ + dev_dbg(i2c_bus->dev, "[%d] %s %d byte%s %s 0x%02x\n", + i2c_bus->msgs_index, str_read_write(msg->flags & I2C_M_RD), + msg->len, str_plural(msg->len), + msg->flags & I2C_M_RD ? "from" : "to", msg->addr); + + if (!i2c_bus->setup_rx || !i2c_bus->setup_tx) + return -EINVAL; + + i2c_bus->controller_xfer_cnt =3D 0; + i2c_bus->buf_index =3D 0; + + if (msg->flags & I2C_M_RD) + return i2c_bus->setup_rx(AST2600_I2CM_START_CMD, i2c_bus); + + return i2c_bus->setup_tx(AST2600_I2CM_START_CMD, i2c_bus); +} + +static int ast2600_i2c_irq_err_to_errno(u32 irq_status) +{ + if (irq_status & AST2600_I2CM_ARBIT_LOSS) + return -EAGAIN; + if (irq_status & (AST2600_I2CM_SDA_DL_TO | AST2600_I2CM_SCL_LOW_TO)) + return -ETIMEDOUT; + if (irq_status & (AST2600_I2CM_ABNORMAL)) + return -EPROTO; + + return 0; +} + +static void ast2600_i2c_controller_packet_irq(struct ast2600_i2c_bus *i2c_= bus, u32 sts) +{ + struct i2c_msg *msg =3D &i2c_bus->msgs[i2c_bus->msgs_index]; + int xfer_len; + int i; + + sts &=3D ~AST2600_I2CM_PKT_DONE; + writel(AST2600_I2CM_PKT_DONE, i2c_bus->reg_base + AST2600_I2CM_ISR); + switch (sts) { + case AST2600_I2CM_PKT_ERROR: + i2c_bus->cmd_err =3D -EAGAIN; + complete(&i2c_bus->cmd_complete); + break; + case AST2600_I2CM_PKT_ERROR | AST2600_I2CM_TX_NAK: /* a0 fix for issue */ + fallthrough; + case AST2600_I2CM_PKT_ERROR | AST2600_I2CM_TX_NAK | AST2600_I2CM_NORMAL_S= TOP: + i2c_bus->cmd_err =3D -ENXIO; + complete(&i2c_bus->cmd_complete); + break; + case AST2600_I2CM_NORMAL_STOP: + /* write 0 byte only have stop isr */ + i2c_bus->msgs_index++; + if (i2c_bus->msgs_index < i2c_bus->msgs_count) { + if (ast2600_i2c_do_start(i2c_bus)) { + i2c_bus->cmd_err =3D -ENOMEM; + complete(&i2c_bus->cmd_complete); + } + } else { + i2c_bus->cmd_err =3D i2c_bus->msgs_index; + complete(&i2c_bus->cmd_complete); + } + break; + case AST2600_I2CM_TX_ACK: + case AST2600_I2CM_TX_ACK | AST2600_I2CM_NORMAL_STOP: + if (i2c_bus->mode =3D=3D DMA_MODE) + xfer_len =3D AST2600_I2C_GET_TX_DMA_LEN(readl(i2c_bus->reg_base + + AST2600_I2CM_DMA_LEN_STS)); + else if (i2c_bus->mode =3D=3D BUFF_MODE) + xfer_len =3D AST2600_I2CC_GET_TX_BUF_LEN(readl(i2c_bus->reg_base + + AST2600_I2CC_BUFF_CTRL)); + else + xfer_len =3D 1; + + i2c_bus->controller_xfer_cnt +=3D xfer_len; + + if (i2c_bus->controller_xfer_cnt =3D=3D msg->len) { + i2c_bus->msgs_index++; + if (i2c_bus->msgs_index =3D=3D i2c_bus->msgs_count) { + i2c_bus->cmd_err =3D i2c_bus->msgs_index; + complete(&i2c_bus->cmd_complete); + } else { + if (ast2600_i2c_do_start(i2c_bus)) { + i2c_bus->cmd_err =3D -ENOMEM; + complete(&i2c_bus->cmd_complete); + } + } + } else { + i2c_bus->setup_tx(0, i2c_bus); + } + break; + case AST2600_I2CM_RX_DONE: + case AST2600_I2CM_RX_DONE | AST2600_I2CM_NORMAL_STOP: + /* do next rx */ + if (i2c_bus->mode =3D=3D DMA_MODE) { + xfer_len =3D AST2600_I2C_GET_RX_DMA_LEN(readl(i2c_bus->reg_base + + AST2600_I2CM_DMA_LEN_STS)); + memcpy(&msg->buf[i2c_bus->controller_xfer_cnt], + i2c_bus->controller_dma_buf, xfer_len); + } else if (i2c_bus->mode =3D=3D BUFF_MODE) { + xfer_len =3D AST2600_I2CC_GET_RX_BUF_LEN(readl(i2c_bus->reg_base + + AST2600_I2CC_BUFF_CTRL)); + for (i =3D 0; i < xfer_len; i++) + msg->buf[i2c_bus->controller_xfer_cnt + i] =3D + readb(i2c_bus->buf_base + 0x10 + i); + } else { + xfer_len =3D 1; + msg->buf[i2c_bus->controller_xfer_cnt] =3D + AST2600_I2CC_GET_RX_BUFF(readl(i2c_bus->reg_base + + AST2600_I2CC_STS_AND_BUFF)); + } + + if (msg->flags & I2C_M_RECV_LEN) { + u8 recv_len =3D AST2600_I2CC_GET_RX_BUFF(readl(i2c_bus->reg_base + + AST2600_I2CC_STS_AND_BUFF)); + msg->len =3D min_t(unsigned int, recv_len, I2C_SMBUS_BLOCK_MAX); + msg->len +=3D ((msg->flags & I2C_CLIENT_PEC) ? 2 : 1); + msg->flags &=3D ~I2C_M_RECV_LEN; + if (!recv_len) + i2c_bus->controller_xfer_cnt =3D 0; + else + i2c_bus->controller_xfer_cnt =3D 1; + } else { + i2c_bus->controller_xfer_cnt +=3D xfer_len; + } + + if (i2c_bus->controller_xfer_cnt =3D=3D msg->len) { + i2c_bus->msgs_index++; + if (i2c_bus->msgs_index =3D=3D i2c_bus->msgs_count) { + i2c_bus->cmd_err =3D i2c_bus->msgs_index; + complete(&i2c_bus->cmd_complete); + } else { + if (ast2600_i2c_do_start(i2c_bus)) { + i2c_bus->cmd_err =3D -ENOMEM; + complete(&i2c_bus->cmd_complete); + } + } + } else { + i2c_bus->setup_rx(0, i2c_bus); + } + break; + default: + dev_dbg(i2c_bus->dev, "unhandled sts %x\n", sts); + break; + } +} + +static int ast2600_i2c_controller_irq(struct ast2600_i2c_bus *i2c_bus) +{ + u32 sts =3D readl(i2c_bus->reg_base + AST2600_I2CM_ISR); + u32 ctrl; + + sts &=3D ~AST2600_I2CM_SMBUS_ALERT; + + if (sts & AST2600_I2CM_BUS_RECOVER_FAIL) { + writel(AST2600_I2CM_BUS_RECOVER_FAIL, i2c_bus->reg_base + AST2600_I2CM_I= SR); + ctrl =3D readl(i2c_bus->reg_base + AST2600_I2CC_FUN_CTRL); + writel(0, i2c_bus->reg_base + AST2600_I2CC_FUN_CTRL); + writel(ctrl, i2c_bus->reg_base + AST2600_I2CC_FUN_CTRL); + i2c_bus->cmd_err =3D -EPROTO; + complete(&i2c_bus->cmd_complete); + return 1; + } + + if (sts & AST2600_I2CM_BUS_RECOVER) { + writel(AST2600_I2CM_BUS_RECOVER, i2c_bus->reg_base + AST2600_I2CM_ISR); + i2c_bus->cmd_err =3D 0; + complete(&i2c_bus->cmd_complete); + return 1; + } + + i2c_bus->cmd_err =3D ast2600_i2c_irq_err_to_errno(sts); + if (i2c_bus->cmd_err) { + writel(AST2600_I2CM_PKT_DONE, i2c_bus->reg_base + AST2600_I2CM_ISR); + complete(&i2c_bus->cmd_complete); + return 1; + } + + if (sts & AST2600_I2CM_PKT_DONE) { + ast2600_i2c_controller_packet_irq(i2c_bus, sts); + return 1; + } + + return 0; +} + +static irqreturn_t ast2600_i2c_bus_irq(int irq, void *dev_id) +{ + struct ast2600_i2c_bus *i2c_bus =3D dev_id; + + return IRQ_RETVAL(ast2600_i2c_controller_irq(i2c_bus)); +} + +static int ast2600_i2c_controller_xfer(struct i2c_adapter *adap, struct i2= c_msg *msgs, int num) +{ + struct ast2600_i2c_bus *i2c_bus =3D i2c_get_adapdata(adap); + unsigned long timeout; + int ret; + + if (!i2c_bus->multi_master && + (readl(i2c_bus->reg_base + AST2600_I2CC_STS_AND_BUFF) & AST2600_I2CC_= BUS_BUSY_STS)) { + ret =3D ast2600_i2c_recover_bus(i2c_bus); + if (ret) + return ret; + } + + i2c_bus->cmd_err =3D 0; + i2c_bus->msgs =3D msgs; + i2c_bus->msgs_index =3D 0; + i2c_bus->msgs_count =3D num; + reinit_completion(&i2c_bus->cmd_complete); + ret =3D ast2600_i2c_do_start(i2c_bus); + if (ret) + goto controller_out; + timeout =3D wait_for_completion_timeout(&i2c_bus->cmd_complete, i2c_bus->= adap.timeout); + if (timeout =3D=3D 0) { + u32 ctrl =3D readl(i2c_bus->reg_base + AST2600_I2CC_FUN_CTRL); + + dev_dbg(i2c_bus->dev, "timeout isr[%x], sts[%x]\n", + readl(i2c_bus->reg_base + AST2600_I2CM_ISR), + readl(i2c_bus->reg_base + AST2600_I2CC_STS_AND_BUFF)); + writel(ctrl & ~AST2600_I2CC_MASTER_EN, i2c_bus->reg_base + AST2600_I2CC_= FUN_CTRL); + writel(ctrl, i2c_bus->reg_base + AST2600_I2CC_FUN_CTRL); + + /* + * A slave holding SCL low can stall the transfer and trigger + * a master timeout. In multi-master mode, attempt bus recovery + * if the bus is still busy. + */ + if (i2c_bus->multi_master && + (readl(i2c_bus->reg_base + AST2600_I2CC_STS_AND_BUFF) & + AST2600_I2CC_BUS_BUSY_STS)) + ast2600_i2c_recover_bus(i2c_bus); + ret =3D -ETIMEDOUT; + } else { + ret =3D i2c_bus->cmd_err; + } + + dev_dbg(i2c_bus->dev, "bus%d-m: %d end\n", i2c_bus->adap.nr, i2c_bus->cmd= _err); + +controller_out: + return ret; +} + +static int ast2600_i2c_init(struct ast2600_i2c_bus *i2c_bus) +{ + u32 fun_ctrl =3D AST2600_I2CC_BUS_AUTO_RELEASE | AST2600_I2CC_MASTER_EN; + + /* I2C Reset */ + writel(0, i2c_bus->reg_base + AST2600_I2CC_FUN_CTRL); + + if (!i2c_bus->multi_master) + fun_ctrl |=3D AST2600_I2CC_MULTI_MASTER_DIS; + + /* Enable Controller Mode */ + writel(fun_ctrl, i2c_bus->reg_base + AST2600_I2CC_FUN_CTRL); + /* disable target address */ + writel(0, i2c_bus->reg_base + AST2600_I2CS_ADDR_CTRL); + + /* Set AC Timing */ + ast2600_i2c_ac_timing_config(i2c_bus); + + if (i2c_bus->dma_available) { + i2c_bus->controller_dma_buf =3D + dmam_alloc_coherent(i2c_bus->dev, AST2600_I2C_DMA_SIZE, + &i2c_bus->controller_dma_addr, GFP_KERNEL); + if (!i2c_bus->controller_dma_buf) + return -ENOMEM; + writel(i2c_bus->controller_dma_addr, + i2c_bus->reg_base + AST2600_I2CM_TX_DMA); + writel(i2c_bus->controller_dma_addr, + i2c_bus->reg_base + AST2600_I2CM_RX_DMA); + } + + /* Clear Interrupt */ + writel(GENMASK(27, 0), i2c_bus->reg_base + AST2600_I2CM_ISR); + + return 0; +} + +static u32 ast2600_i2c_functionality(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_SMBUS_BLOCK_DATA; +} + +static const struct i2c_algorithm i2c_ast2600_algorithm =3D { + .xfer =3D ast2600_i2c_controller_xfer, + .functionality =3D ast2600_i2c_functionality, +}; + +static void ast2600_i2c_set_xfer_mode(struct ast2600_i2c_bus *i2c_bus, + enum xfer_mode mode) +{ + i2c_bus->mode =3D mode; + + switch (mode) { + case DMA_MODE: + i2c_bus->setup_tx =3D ast2600_i2c_setup_dma_tx; + i2c_bus->setup_rx =3D ast2600_i2c_setup_dma_rx; + break; + case BYTE_MODE: + i2c_bus->setup_tx =3D ast2600_i2c_setup_byte_tx; + i2c_bus->setup_rx =3D ast2600_i2c_setup_byte_rx; + break; + case BUFF_MODE: + default: + i2c_bus->setup_tx =3D ast2600_i2c_setup_buff_tx; + i2c_bus->setup_rx =3D ast2600_i2c_setup_buff_rx; + break; + } +} + +static int ast2600_i2c_xfer_mode_parse(const char *buf, enum xfer_mode *mo= de) +{ + if (sysfs_streq(buf, "byte")) { + *mode =3D BYTE_MODE; + return 0; + } + + if (sysfs_streq(buf, "buffer")) { + *mode =3D BUFF_MODE; + return 0; + } + + if (sysfs_streq(buf, "dma")) { + *mode =3D DMA_MODE; + return 0; + } + + return -EINVAL; +} + +static int ast2600_i2c_xfer_mode_check(struct ast2600_i2c_bus *i2c_bus, + enum xfer_mode mode) +{ + if (mode =3D=3D BUFF_MODE && !i2c_bus->buf_base) + return -EINVAL; + if (mode =3D=3D DMA_MODE && !i2c_bus->dma_available) + return -EINVAL; + return 0; +} + +static const char *ast2600_i2c_xfer_mode_name(enum xfer_mode mode) +{ + switch (mode) { + case BYTE_MODE: + return "byte"; + case DMA_MODE: + return "dma"; + case BUFF_MODE: + default: + return "buffer"; + } +} + +static ssize_t xfer_mode_show(struct device *dev, struct device_attribute = *attr, char *buf) +{ + struct ast2600_i2c_bus *i2c_bus =3D dev_get_drvdata(dev); + + return sysfs_emit(buf, "%s\n", ast2600_i2c_xfer_mode_name(i2c_bus->mode)); +} + +static ssize_t xfer_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ast2600_i2c_bus *i2c_bus =3D dev_get_drvdata(dev); + enum xfer_mode mode; + int ret; + + ret =3D ast2600_i2c_xfer_mode_parse(buf, &mode); + if (ret) + return ret; + + ret =3D ast2600_i2c_xfer_mode_check(i2c_bus, mode); + if (ret) + return ret; + + i2c_lock_bus(&i2c_bus->adap, I2C_LOCK_ROOT_ADAPTER); + ast2600_i2c_set_xfer_mode(i2c_bus, mode); + i2c_unlock_bus(&i2c_bus->adap, I2C_LOCK_ROOT_ADAPTER); + + return count; +} + +static DEVICE_ATTR_RW(xfer_mode); + +static int ast2600_i2c_probe(struct platform_device *pdev) +{ + struct device *dev =3D &pdev->dev; + struct ast2600_i2c_bus *i2c_bus; + void __iomem *buf_base; + struct reset_control *rst; + struct resource *res; + u32 global_ctrl; + int ret; + + if (!device_property_present(dev, "aspeed,global-regs")) + return -ENODEV; + + i2c_bus =3D devm_kzalloc(dev, sizeof(*i2c_bus), GFP_KERNEL); + if (!i2c_bus) + return -ENOMEM; + + i2c_bus->reg_base =3D devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(i2c_bus->reg_base)) + return PTR_ERR(i2c_bus->reg_base); + + rst =3D devm_reset_control_get_shared_deasserted(dev, NULL); + if (IS_ERR(rst)) + return dev_err_probe(dev, PTR_ERR(rst), "Missing reset ctrl\n"); + + i2c_bus->global_regs =3D + syscon_regmap_lookup_by_phandle(dev_of_node(dev), "aspeed,global-regs"); + if (IS_ERR(i2c_bus->global_regs)) + return PTR_ERR(i2c_bus->global_regs); + + regmap_read(i2c_bus->global_regs, AST2600_I2CG_CTRL, &global_ctrl); + if ((global_ctrl & AST2600_GLOBAL_INIT) !=3D AST2600_GLOBAL_INIT) { + regmap_write(i2c_bus->global_regs, AST2600_I2CG_CTRL, AST2600_GLOBAL_INI= T); + regmap_write(i2c_bus->global_regs, AST2600_I2CG_CLK_DIV_CTRL, I2CCG_DIV_= CTRL); + } + + i2c_bus->dev =3D dev; + i2c_bus->multi_master =3D device_property_read_bool(dev, "multi-master"); + i2c_bus->dma_available =3D device_property_read_bool(dev, "aspeed,enable-= dma"); + + buf_base =3D devm_platform_get_and_ioremap_resource(pdev, 1, &res); + if (!IS_ERR(buf_base)) { + i2c_bus->buf_base =3D buf_base; + i2c_bus->buf_size =3D resource_size(res) / 2; + } + + enum xfer_mode mode; + + if (i2c_bus->dma_available) + mode =3D DMA_MODE; + else if (i2c_bus->buf_base) + mode =3D BUFF_MODE; + else + mode =3D BYTE_MODE; + + ast2600_i2c_set_xfer_mode(i2c_bus, mode); + + /* + * i2c timeout counter: use base clk4 1Mhz, + * per unit: 1/(1000/1024) =3D 1024us + */ + ret =3D device_property_read_u32(dev, "i2c-scl-clk-low-timeout-us", &i2c_= bus->timeout); + if (!ret) + i2c_bus->timeout =3D DIV_ROUND_UP(i2c_bus->timeout, 1024); + + init_completion(&i2c_bus->cmd_complete); + + i2c_bus->irq =3D platform_get_irq(pdev, 0); + if (i2c_bus->irq < 0) + return i2c_bus->irq; + + platform_set_drvdata(pdev, i2c_bus); + + i2c_bus->clk =3D devm_clk_get(i2c_bus->dev, NULL); + if (IS_ERR(i2c_bus->clk)) + return dev_err_probe(i2c_bus->dev, PTR_ERR(i2c_bus->clk), "Can't get clo= ck\n"); + + i2c_bus->apb_clk =3D clk_get_rate(i2c_bus->clk); + + i2c_parse_fw_timings(i2c_bus->dev, &i2c_bus->timing_info, true); + + /* Initialize the I2C adapter */ + i2c_bus->adap.owner =3D THIS_MODULE; + i2c_bus->adap.algo =3D &i2c_ast2600_algorithm; + i2c_bus->adap.retries =3D 0; + i2c_bus->adap.dev.parent =3D i2c_bus->dev; + device_set_node(&i2c_bus->adap.dev, dev_fwnode(dev)); + i2c_bus->adap.algo_data =3D i2c_bus; + strscpy(i2c_bus->adap.name, pdev->name); + i2c_set_adapdata(&i2c_bus->adap, i2c_bus); + + ret =3D ast2600_i2c_init(i2c_bus); + if (ret < 0) + return dev_err_probe(dev, ret, "Unable to initialize i2c %d\n", ret); + + ret =3D devm_request_irq(dev, i2c_bus->irq, ast2600_i2c_bus_irq, 0, + dev_name(dev), i2c_bus); + if (ret < 0) { + ret =3D dev_err_probe(dev, ret, "Unable to request irq %d\n", + i2c_bus->irq); + goto err; + } + + writel(AST2600_I2CM_PKT_DONE | AST2600_I2CM_BUS_RECOVER, + i2c_bus->reg_base + AST2600_I2CM_IER); + + ret =3D sysfs_create_file(&dev->kobj, &dev_attr_xfer_mode.attr); + if (ret) + goto err; + + ret =3D devm_i2c_add_adapter(dev, &i2c_bus->adap); + if (ret) { + sysfs_remove_file(&dev->kobj, &dev_attr_xfer_mode.attr); + goto err; + } + + return 0; + +err: + writel(0, i2c_bus->reg_base + AST2600_I2CC_FUN_CTRL); + writel(0, i2c_bus->reg_base + AST2600_I2CM_IER); + return ret; +} + +static void ast2600_i2c_remove(struct platform_device *pdev) +{ + struct ast2600_i2c_bus *i2c_bus =3D platform_get_drvdata(pdev); + + sysfs_remove_file(&pdev->dev.kobj, &dev_attr_xfer_mode.attr); + + /* Disable everything. */ + writel(0, i2c_bus->reg_base + AST2600_I2CC_FUN_CTRL); + writel(0, i2c_bus->reg_base + AST2600_I2CM_IER); +} + +static const struct of_device_id ast2600_i2c_of_match[] =3D { + { .compatible =3D "aspeed,ast2600-i2c-bus" }, + { } +}; +MODULE_DEVICE_TABLE(of, ast2600_i2c_of_match); + +static struct platform_driver ast2600_i2c_driver =3D { + .probe =3D ast2600_i2c_probe, + .remove =3D ast2600_i2c_remove, + .driver =3D { + .name =3D "ast2600-i2c", + .of_match_table =3D ast2600_i2c_of_match, + }, +}; +module_platform_driver(ast2600_i2c_driver); + +MODULE_AUTHOR("Ryan Chen "); +MODULE_DESCRIPTION("ASPEED AST2600 I2C Controller Driver"); +MODULE_LICENSE("GPL"); --=20 2.34.1 From nobody Thu Apr 2 07:43:47 2026 Received: from TWMBX01.aspeed.com (mail.aspeedtech.com [211.20.114.72]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id BE6C43B7B95; Mon, 30 Mar 2026 08:22:01 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=211.20.114.72 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774858924; cv=none; b=kz+55fob9bqUk6CTK0R94CSjCCvSyf93xbddSicEN0/EMHnNoDW2uwJNMHE5kc33ZPIWTUC8xlAKjKxfWOzQ47nFjG+waAgjvwkeKfVjcsmidVXY/RzlwTojW34G3Cb1iKQuOY4MDW3tABGLi8vDzlE7ezj4xHuLerXge1RKnEk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774858924; c=relaxed/simple; bh=qEy71FBUHzKmG83XwMeihp+/Nh+FGHPWZ6g0wGKot5Q=; h=From:Date:Subject:MIME-Version:Content-Type:Message-ID:References: In-Reply-To:To:CC; b=sxkvcaSiFgugN6odGH89SSTHiq6VegGHWS+Ze4HBENHVRnIOgNpjuQPS7bmiy9RFCz2fWokaQGPLwQBfGVVmvKLb/dCHY6zjAFDkzAwOrcl9tYquv617cCS8AQNEZnPYvKRaNGQk8BBDOQAsVbYnCC15gEp8zcelr8TLhF6LEUE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=aspeedtech.com; spf=pass smtp.mailfrom=aspeedtech.com; arc=none smtp.client-ip=211.20.114.72 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=aspeedtech.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=aspeedtech.com Received: from TWMBX01.aspeed.com (192.168.0.62) by TWMBX01.aspeed.com (192.168.0.62) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.1748.10; Mon, 30 Mar 2026 16:21:47 +0800 Received: from [127.0.1.1] (192.168.10.13) by TWMBX01.aspeed.com (192.168.0.62) with Microsoft SMTP Server id 15.2.1748.10 via Frontend Transport; Mon, 30 Mar 2026 16:21:47 +0800 From: Ryan Chen Date: Mon, 30 Mar 2026 16:21:49 +0800 Subject: [PATCH v28 4/4] i2c: ast2600: Add target mode support Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-ID: <20260330-upstream_i2c-v28-4-17bdae39c5cb@aspeedtech.com> References: <20260330-upstream_i2c-v28-0-17bdae39c5cb@aspeedtech.com> In-Reply-To: <20260330-upstream_i2c-v28-0-17bdae39c5cb@aspeedtech.com> To: , , Andi Shyti , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Joel Stanley , Andrew Jeffery , "Benjamin Herrenschmidt" , Rayn Chen , Philipp Zabel CC: , , , , , , Ryan Chen X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1774858906; l=26391; i=ryan_chen@aspeedtech.com; s=20251126; h=from:subject:message-id; bh=qEy71FBUHzKmG83XwMeihp+/Nh+FGHPWZ6g0wGKot5Q=; b=3ij5+azsEkfW7DHo1+BEk12rRLX44KXYs85m1yH1apXCQAK2JjitOcG1/2rcz21rFxdWmxVGD Gi29+QWhf5HBsz+bJ66eLa5D3NXCDzPaIp9k4aYlooXj7Z8wIz6ZzwV X-Developer-Key: i=ryan_chen@aspeedtech.com; a=ed25519; pk=Xe73xY6tcnkuRjjbVAB/oU30KdB3FvG4nuJuILj7ZVc= Add target mode support to the AST2600 I2C driver. Target mode features implemented include: - Add target interrupt handling - Address match and response logic - Separate Tx/Rx DMA address and length configuration This complements the existing controller-mode support, enabling dual-role capability. Signed-off-by: Ryan Chen --- Changes in v28: - fix typo condication -> condition - fix compile error, when disable CONFIG_I2C_SLAVE Changes in v26: - change int to bool target_operate - rename target_operate to target_active - use i2c_bus->target replace require IO - use WRITE_ONCE replace target_operate write. --- drivers/i2c/busses/i2c-ast2600.c | 566 +++++++++++++++++++++++++++++++++++= ++++ 1 file changed, 566 insertions(+) diff --git a/drivers/i2c/busses/i2c-ast2600.c b/drivers/i2c/busses/i2c-ast2= 600.c index c2368ba309a7..a9b77917a1fe 100644 --- a/drivers/i2c/busses/i2c-ast2600.c +++ b/drivers/i2c/busses/i2c-ast2600.c @@ -274,6 +274,13 @@ struct ast2600_i2c_bus { void __iomem *buf_base; int (*setup_tx)(u32 cmd, struct ast2600_i2c_bus *i2c_bus); int (*setup_rx)(u32 cmd, struct ast2600_i2c_bus *i2c_bus); +#if IS_ENABLED(CONFIG_I2C_SLAVE) + /* target structure */ + bool target_active; + unsigned char *target_dma_buf; + dma_addr_t target_dma_addr; + struct i2c_client *target; +#endif }; =20 static void ast2600_i2c_ac_timing_config(struct ast2600_i2c_bus *i2c_bus) @@ -357,6 +364,440 @@ static int ast2600_i2c_recover_bus(struct ast2600_i2c= _bus *i2c_bus) return ret; } =20 +#if IS_ENABLED(CONFIG_I2C_SLAVE) +static void ast2600_i2c_target_packet_dma_irq(struct ast2600_i2c_bus *i2c_= bus, u32 sts) +{ + int target_rx_len =3D 0; + u32 cmd =3D 0; + u8 value; + int i; + + sts &=3D ~(AST2600_I2CS_SLAVE_PENDING); + /* Handle i2c target timeout condition */ + if (sts & AST2600_I2CS_INACTIVE_TO) { + /* Reset timeout counter */ + u32 ac_timing =3D readl(i2c_bus->reg_base + AST2600_I2CC_AC_TIMING) & + AST2600_I2CC_AC_TIMING_MASK; + + writel(ac_timing, i2c_bus->reg_base + AST2600_I2CC_AC_TIMING); + ac_timing |=3D AST2600_I2CC_TTIMEOUT(i2c_bus->timeout); + writel(ac_timing, i2c_bus->reg_base + AST2600_I2CC_AC_TIMING); + cmd =3D TARGET_TRIGGER_CMD | AST2600_I2CS_RX_DMA_EN; + writel(AST2600_I2CS_SET_RX_DMA_LEN(I2C_TARGET_MSG_BUF_SIZE), + i2c_bus->reg_base + AST2600_I2CS_DMA_LEN); + writel(cmd, i2c_bus->reg_base + AST2600_I2CS_CMD_STS); + writel(AST2600_I2CS_PKT_DONE, i2c_bus->reg_base + AST2600_I2CS_ISR); + i2c_slave_event(i2c_bus->target, I2C_SLAVE_STOP, &value); + return; + } + + sts &=3D ~(AST2600_I2CS_PKT_DONE | AST2600_I2CS_PKT_ERROR); + + switch (sts) { + case AST2600_I2CS_SLAVE_MATCH | AST2600_I2CS_RX_DONE | AST2600_I2CS_WAIT_= RX_DMA: + case AST2600_I2CS_SLAVE_MATCH | AST2600_I2CS_WAIT_RX_DMA: + i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_REQUESTED, &value); + target_rx_len =3D AST2600_I2C_GET_RX_DMA_LEN(readl(i2c_bus->reg_base + + AST2600_I2CS_DMA_LEN_STS)); + for (i =3D 0; i < target_rx_len; i++) { + i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_RECEIVED, + &i2c_bus->target_dma_buf[i]); + } + writel(AST2600_I2CS_SET_RX_DMA_LEN(I2C_TARGET_MSG_BUF_SIZE), + i2c_bus->reg_base + AST2600_I2CS_DMA_LEN); + cmd =3D TARGET_TRIGGER_CMD | AST2600_I2CS_RX_DMA_EN; + break; + case AST2600_I2CS_SLAVE_MATCH | AST2600_I2CS_STOP: + i2c_slave_event(i2c_bus->target, I2C_SLAVE_STOP, &value); + writel(AST2600_I2CS_SET_RX_DMA_LEN(I2C_TARGET_MSG_BUF_SIZE), + i2c_bus->reg_base + AST2600_I2CS_DMA_LEN); + cmd =3D TARGET_TRIGGER_CMD | AST2600_I2CS_RX_DMA_EN; + break; + case AST2600_I2CS_SLAVE_MATCH | AST2600_I2CS_RX_DONE_NAK | + AST2600_I2CS_RX_DONE | AST2600_I2CS_STOP: + case AST2600_I2CS_SLAVE_MATCH | AST2600_I2CS_WAIT_RX_DMA | + AST2600_I2CS_RX_DONE | AST2600_I2CS_STOP: + case AST2600_I2CS_RX_DONE_NAK | AST2600_I2CS_RX_DONE | AST2600_I2CS_STOP: + case AST2600_I2CS_RX_DONE | AST2600_I2CS_WAIT_RX_DMA | AST2600_I2CS_STOP: + case AST2600_I2CS_RX_DONE | AST2600_I2CS_STOP: + case AST2600_I2CS_RX_DONE | AST2600_I2CS_WAIT_RX_DMA: + case AST2600_I2CS_SLAVE_MATCH | AST2600_I2CS_RX_DONE | AST2600_I2CS_STOP: + if (sts & AST2600_I2CS_SLAVE_MATCH) + i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_REQUESTED, &value); + + target_rx_len =3D AST2600_I2C_GET_RX_DMA_LEN(readl(i2c_bus->reg_base + + AST2600_I2CS_DMA_LEN_STS)); + for (i =3D 0; i < target_rx_len; i++) { + i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_RECEIVED, + &i2c_bus->target_dma_buf[i]); + } + writel(AST2600_I2CS_SET_RX_DMA_LEN(I2C_TARGET_MSG_BUF_SIZE), + i2c_bus->reg_base + AST2600_I2CS_DMA_LEN); + if (sts & AST2600_I2CS_STOP) + i2c_slave_event(i2c_bus->target, I2C_SLAVE_STOP, &value); + cmd =3D TARGET_TRIGGER_CMD | AST2600_I2CS_RX_DMA_EN; + break; + + /* it is Mw data Mr coming -> it need send tx */ + case AST2600_I2CS_RX_DONE | AST2600_I2CS_WAIT_TX_DMA: + case AST2600_I2CS_SLAVE_MATCH | AST2600_I2CS_RX_DONE | AST2600_I2CS_WAIT_= TX_DMA: + /* it should be repeat start read */ + if (sts & AST2600_I2CS_SLAVE_MATCH) + i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_REQUESTED, &value); + + target_rx_len =3D AST2600_I2C_GET_RX_DMA_LEN(readl(i2c_bus->reg_base + + AST2600_I2CS_DMA_LEN_STS)); + for (i =3D 0; i < target_rx_len; i++) { + i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_RECEIVED, + &i2c_bus->target_dma_buf[i]); + } + i2c_slave_event(i2c_bus->target, I2C_SLAVE_READ_REQUESTED, + &i2c_bus->target_dma_buf[0]); + writel(AST2600_I2CS_SET_TX_DMA_LEN(1), + i2c_bus->reg_base + AST2600_I2CS_DMA_LEN); + cmd =3D TARGET_TRIGGER_CMD | AST2600_I2CS_TX_DMA_EN; + break; + case AST2600_I2CS_SLAVE_MATCH | AST2600_I2CS_WAIT_TX_DMA: + /* First Start read */ + i2c_slave_event(i2c_bus->target, I2C_SLAVE_READ_REQUESTED, + &i2c_bus->target_dma_buf[0]); + writel(AST2600_I2CS_SET_TX_DMA_LEN(1), + i2c_bus->reg_base + AST2600_I2CS_DMA_LEN); + cmd =3D TARGET_TRIGGER_CMD | AST2600_I2CS_TX_DMA_EN; + break; + case AST2600_I2CS_WAIT_TX_DMA: + /* it should be next start read */ + i2c_slave_event(i2c_bus->target, I2C_SLAVE_READ_PROCESSED, + &i2c_bus->target_dma_buf[0]); + writel(AST2600_I2CS_SET_TX_DMA_LEN(1), + i2c_bus->reg_base + AST2600_I2CS_DMA_LEN); + cmd =3D TARGET_TRIGGER_CMD | AST2600_I2CS_TX_DMA_EN; + break; + case AST2600_I2CS_TX_NAK | AST2600_I2CS_STOP: + /* it just tx complete */ + i2c_slave_event(i2c_bus->target, I2C_SLAVE_STOP, &value); + writel(AST2600_I2CS_SET_RX_DMA_LEN(I2C_TARGET_MSG_BUF_SIZE), + i2c_bus->reg_base + AST2600_I2CS_DMA_LEN); + cmd =3D TARGET_TRIGGER_CMD | AST2600_I2CS_RX_DMA_EN; + break; + case AST2600_I2CS_SLAVE_MATCH | AST2600_I2CS_RX_DONE: + i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_REQUESTED, &value); + break; + case AST2600_I2CS_STOP: + i2c_slave_event(i2c_bus->target, I2C_SLAVE_STOP, &value); + break; + default: + dev_dbg(i2c_bus->dev, "unhandled target isr case %x, sts %x\n", sts, + readl(i2c_bus->reg_base + AST2600_I2CC_STS_AND_BUFF)); + break; + } + + if (cmd) + writel(cmd, i2c_bus->reg_base + AST2600_I2CS_CMD_STS); + writel(AST2600_I2CS_PKT_DONE, i2c_bus->reg_base + AST2600_I2CS_ISR); + readl(i2c_bus->reg_base + AST2600_I2CS_ISR); +} + +static void ast2600_i2c_target_packet_buff_irq(struct ast2600_i2c_bus *i2c= _bus, u32 sts) +{ + int target_rx_len =3D 0; + u32 cmd =3D 0; + u8 value; + int i; + + /* due to controller target is common buffer, need force the master stop = not issue */ + if (readl(i2c_bus->reg_base + AST2600_I2CM_CMD_STS) & GENMASK(15, 0)) { + writel(0, i2c_bus->reg_base + AST2600_I2CM_CMD_STS); + i2c_bus->cmd_err =3D -EBUSY; + writel(0, i2c_bus->reg_base + AST2600_I2CC_BUFF_CTRL); + complete(&i2c_bus->cmd_complete); + } + + /* Handle i2c target timeout condition */ + if (AST2600_I2CS_INACTIVE_TO & sts) { + /* Reset timeout counter */ + u32 ac_timing =3D readl(i2c_bus->reg_base + AST2600_I2CC_AC_TIMING) & + AST2600_I2CC_AC_TIMING_MASK; + + writel(ac_timing, i2c_bus->reg_base + AST2600_I2CC_AC_TIMING); + ac_timing |=3D AST2600_I2CC_TTIMEOUT(i2c_bus->timeout); + writel(ac_timing, i2c_bus->reg_base + AST2600_I2CC_AC_TIMING); + writel(TARGET_TRIGGER_CMD, i2c_bus->reg_base + AST2600_I2CS_CMD_STS); + writel(AST2600_I2CS_PKT_DONE, i2c_bus->reg_base + AST2600_I2CS_ISR); + i2c_slave_event(i2c_bus->target, I2C_SLAVE_STOP, &value); + WRITE_ONCE(i2c_bus->target_active, false); + return; + } + + sts &=3D ~(AST2600_I2CS_PKT_DONE | AST2600_I2CS_PKT_ERROR); + + if (sts & AST2600_I2CS_SLAVE_MATCH) + WRITE_ONCE(i2c_bus->target_active, true); + + switch (sts) { + case AST2600_I2CS_SLAVE_PENDING | AST2600_I2CS_WAIT_RX_DMA | + AST2600_I2CS_SLAVE_MATCH | AST2600_I2CS_RX_DONE | AST2600_I2CS_STOP: + case AST2600_I2CS_SLAVE_PENDING | + AST2600_I2CS_SLAVE_MATCH | AST2600_I2CS_RX_DONE | AST2600_I2CS_STOP: + case AST2600_I2CS_SLAVE_PENDING | + AST2600_I2CS_SLAVE_MATCH | AST2600_I2CS_STOP: + i2c_slave_event(i2c_bus->target, I2C_SLAVE_STOP, &value); + fallthrough; + case AST2600_I2CS_SLAVE_PENDING | + AST2600_I2CS_WAIT_RX_DMA | AST2600_I2CS_SLAVE_MATCH | AST2600_I2CS_RX_D= ONE: + case AST2600_I2CS_WAIT_RX_DMA | AST2600_I2CS_SLAVE_MATCH | AST2600_I2CS_R= X_DONE: + case AST2600_I2CS_WAIT_RX_DMA | AST2600_I2CS_SLAVE_MATCH: + i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_REQUESTED, &value); + cmd =3D TARGET_TRIGGER_CMD; + if (sts & AST2600_I2CS_RX_DONE) { + target_rx_len =3D AST2600_I2CC_GET_RX_BUF_LEN(readl(i2c_bus->reg_base + + AST2600_I2CC_BUFF_CTRL)); + for (i =3D 0; i < target_rx_len; i++) { + value =3D readb(i2c_bus->buf_base + 0x10 + i); + i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_RECEIVED, &value); + } + } + if (readl(i2c_bus->reg_base + AST2600_I2CS_CMD_STS) & AST2600_I2CS_RX_BU= FF_EN) + cmd =3D 0; + else + cmd =3D TARGET_TRIGGER_CMD | AST2600_I2CS_RX_BUFF_EN; + + writel(AST2600_I2CC_SET_RX_BUF_LEN(i2c_bus->buf_size), + i2c_bus->reg_base + AST2600_I2CC_BUFF_CTRL); + break; + case AST2600_I2CS_WAIT_RX_DMA | AST2600_I2CS_RX_DONE: + cmd =3D TARGET_TRIGGER_CMD; + target_rx_len =3D AST2600_I2CC_GET_RX_BUF_LEN(readl(i2c_bus->reg_base + + AST2600_I2CC_BUFF_CTRL)); + for (i =3D 0; i < target_rx_len; i++) { + value =3D readb(i2c_bus->buf_base + 0x10 + i); + i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_RECEIVED, &value); + } + cmd |=3D AST2600_I2CS_RX_BUFF_EN; + writel(AST2600_I2CC_SET_RX_BUF_LEN(i2c_bus->buf_size), + i2c_bus->reg_base + AST2600_I2CC_BUFF_CTRL); + break; + case AST2600_I2CS_SLAVE_PENDING | AST2600_I2CS_WAIT_RX_DMA | + AST2600_I2CS_RX_DONE | AST2600_I2CS_STOP: + cmd =3D TARGET_TRIGGER_CMD; + target_rx_len =3D AST2600_I2CC_GET_RX_BUF_LEN(readl(i2c_bus->reg_base + + AST2600_I2CC_BUFF_CTRL)); + for (i =3D 0; i < target_rx_len; i++) { + value =3D readb(i2c_bus->buf_base + 0x10 + i); + i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_RECEIVED, &value); + } + i2c_slave_event(i2c_bus->target, I2C_SLAVE_STOP, &value); + cmd |=3D AST2600_I2CS_RX_BUFF_EN; + writel(AST2600_I2CC_SET_RX_BUF_LEN(i2c_bus->buf_size), + i2c_bus->reg_base + AST2600_I2CC_BUFF_CTRL); + break; + case AST2600_I2CS_SLAVE_PENDING | AST2600_I2CS_RX_DONE | AST2600_I2CS_STO= P: + cmd =3D TARGET_TRIGGER_CMD; + target_rx_len =3D AST2600_I2CC_GET_RX_BUF_LEN(readl(i2c_bus->reg_base + + AST2600_I2CC_BUFF_CTRL)); + for (i =3D 0; i < target_rx_len; i++) { + value =3D readb(i2c_bus->buf_base + 0x10 + i); + i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_RECEIVED, &value); + } + /* workaround for avoid next start with len !=3D 0 */ + writel(BIT(0), i2c_bus->reg_base + AST2600_I2CC_BUFF_CTRL); + i2c_slave_event(i2c_bus->target, I2C_SLAVE_STOP, &value); + break; + case AST2600_I2CS_RX_DONE | AST2600_I2CS_STOP: + cmd =3D TARGET_TRIGGER_CMD; + target_rx_len =3D AST2600_I2CC_GET_RX_BUF_LEN(readl(i2c_bus->reg_base + + AST2600_I2CC_BUFF_CTRL)); + for (i =3D 0; i < target_rx_len; i++) { + value =3D readb(i2c_bus->buf_base + 0x10 + i); + i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_RECEIVED, &value); + } + /* workaround for avoid next start with len !=3D 0 */ + writel(BIT(0), i2c_bus->reg_base + AST2600_I2CC_BUFF_CTRL); + i2c_slave_event(i2c_bus->target, I2C_SLAVE_STOP, &value); + break; + case AST2600_I2CS_SLAVE_PENDING | AST2600_I2CS_RX_DONE | + AST2600_I2CS_WAIT_TX_DMA | AST2600_I2CS_STOP: + target_rx_len =3D AST2600_I2CC_GET_RX_BUF_LEN(readl(i2c_bus->reg_base + + AST2600_I2CC_BUFF_CTRL)); + for (i =3D 0; i < target_rx_len; i++) { + value =3D readb(i2c_bus->buf_base + 0x10 + i); + i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_RECEIVED, &value); + } + i2c_slave_event(i2c_bus->target, I2C_SLAVE_STOP, &value); + i2c_slave_event(i2c_bus->target, I2C_SLAVE_READ_REQUESTED, &value); + writeb(value, i2c_bus->buf_base); + break; + case AST2600_I2CS_WAIT_TX_DMA | AST2600_I2CS_SLAVE_MATCH: + i2c_slave_event(i2c_bus->target, I2C_SLAVE_READ_REQUESTED, &value); + writeb(value, i2c_bus->buf_base); + writel(AST2600_I2CC_SET_TX_BUF_LEN(1), + i2c_bus->reg_base + AST2600_I2CC_BUFF_CTRL); + cmd =3D TARGET_TRIGGER_CMD | AST2600_I2CS_TX_BUFF_EN; + break; + case AST2600_I2CS_SLAVE_PENDING | AST2600_I2CS_STOP | + AST2600_I2CS_TX_NAK | AST2600_I2CS_SLAVE_MATCH | AST2600_I2CS_RX_DON= E: + case AST2600_I2CS_SLAVE_PENDING | AST2600_I2CS_WAIT_RX_DMA | AST2600_I2CS= _STOP | + AST2600_I2CS_TX_NAK | AST2600_I2CS_SLAVE_MATCH | AST2600_I2CS_RX_DON= E: + i2c_slave_event(i2c_bus->target, I2C_SLAVE_STOP, &value); + i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_REQUESTED, &value); + target_rx_len =3D AST2600_I2CC_GET_RX_BUF_LEN(readl(i2c_bus->reg_base + + AST2600_I2CC_BUFF_CTRL)); + for (i =3D 0; i < target_rx_len; i++) { + value =3D readb(i2c_bus->buf_base + 0x10 + i); + i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_RECEIVED, &value); + } + writel(AST2600_I2CC_SET_RX_BUF_LEN(i2c_bus->buf_size), + i2c_bus->reg_base + AST2600_I2CC_BUFF_CTRL); + cmd =3D TARGET_TRIGGER_CMD | AST2600_I2CS_RX_BUFF_EN; + break; + case AST2600_I2CS_SLAVE_MATCH | AST2600_I2CS_WAIT_TX_DMA | AST2600_I2CS_R= X_DONE: + case AST2600_I2CS_WAIT_TX_DMA | AST2600_I2CS_RX_DONE: + case AST2600_I2CS_WAIT_TX_DMA: + if (sts & AST2600_I2CS_SLAVE_MATCH) + i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_REQUESTED, &value); + + if (sts & AST2600_I2CS_RX_DONE) { + target_rx_len =3D AST2600_I2CC_GET_RX_BUF_LEN(readl(i2c_bus->reg_base + + AST2600_I2CC_BUFF_CTRL)); + for (i =3D 0; i < target_rx_len; i++) { + value =3D readb(i2c_bus->buf_base + 0x10 + i); + i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_RECEIVED, &value); + } + i2c_slave_event(i2c_bus->target, I2C_SLAVE_READ_REQUESTED, &value); + } else { + i2c_slave_event(i2c_bus->target, I2C_SLAVE_READ_PROCESSED, &value); + } + writeb(value, i2c_bus->buf_base); + writel(AST2600_I2CC_SET_TX_BUF_LEN(1), + i2c_bus->reg_base + AST2600_I2CC_BUFF_CTRL); + cmd =3D TARGET_TRIGGER_CMD | AST2600_I2CS_TX_BUFF_EN; + break; + /* workaround : trigger the cmd twice to fix next state keep 1000000 */ + case AST2600_I2CS_SLAVE_MATCH | AST2600_I2CS_RX_DONE: + i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_REQUESTED, &value); + cmd =3D TARGET_TRIGGER_CMD | AST2600_I2CS_RX_BUFF_EN; + writel(cmd, i2c_bus->reg_base + AST2600_I2CS_CMD_STS); + break; + case AST2600_I2CS_TX_NAK | AST2600_I2CS_STOP: + case AST2600_I2CS_STOP: + cmd =3D TARGET_TRIGGER_CMD; + i2c_slave_event(i2c_bus->target, I2C_SLAVE_STOP, &value); + break; + default: + dev_dbg(i2c_bus->dev, "unhandled target isr case %x, sts %x\n", sts, + readl(i2c_bus->reg_base + AST2600_I2CC_STS_AND_BUFF)); + break; + } + + if (cmd) + writel(cmd, i2c_bus->reg_base + AST2600_I2CS_CMD_STS); + writel(AST2600_I2CS_PKT_DONE, i2c_bus->reg_base + AST2600_I2CS_ISR); + readl(i2c_bus->reg_base + AST2600_I2CS_ISR); + + if ((sts & AST2600_I2CS_STOP) && !(sts & AST2600_I2CS_SLAVE_PENDING)) + WRITE_ONCE(i2c_bus->target_active, false); +} + +static void ast2600_i2c_target_byte_irq(struct ast2600_i2c_bus *i2c_bus, u= 32 sts) +{ + u32 i2c_buff =3D readl(i2c_bus->reg_base + AST2600_I2CC_STS_AND_BUFF); + u32 cmd =3D AST2600_I2CS_ACTIVE_ALL; + u8 byte_data; + u8 value; + + /* Handle i2c target timeout condition */ + if (sts & AST2600_I2CS_INACTIVE_TO) { + /* Reset timeout counter */ + u32 ac_timing =3D readl(i2c_bus->reg_base + AST2600_I2CC_AC_TIMING) & + AST2600_I2CC_AC_TIMING_MASK; + + writel(ac_timing, i2c_bus->reg_base + AST2600_I2CC_AC_TIMING); + ac_timing |=3D AST2600_I2CC_TTIMEOUT(i2c_bus->timeout); + writel(ac_timing, i2c_bus->reg_base + AST2600_I2CC_AC_TIMING); + writel(AST2600_I2CS_ACTIVE_ALL, i2c_bus->reg_base + AST2600_I2CS_CMD_STS= ); + writel(sts, i2c_bus->reg_base + AST2600_I2CS_ISR); + readl(i2c_bus->reg_base + AST2600_I2CS_ISR); + i2c_slave_event(i2c_bus->target, I2C_SLAVE_STOP, &value); + WRITE_ONCE(i2c_bus->target_active, false); + return; + } + + switch (sts) { + case AST2600_I2CS_SLAVE_MATCH | AST2600_I2CS_RX_DONE | AST2600_I2CS_WAIT_= RX_DMA: + i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_REQUESTED, &value); + /* first address match is address */ + byte_data =3D AST2600_I2CC_GET_RX_BUFF(i2c_buff); + break; + case AST2600_I2CS_RX_DONE | AST2600_I2CS_WAIT_RX_DMA: + byte_data =3D AST2600_I2CC_GET_RX_BUFF(i2c_buff); + i2c_slave_event(i2c_bus->target, I2C_SLAVE_WRITE_RECEIVED, &byte_data); + break; + case AST2600_I2CS_SLAVE_MATCH | AST2600_I2CS_RX_DONE | AST2600_I2CS_WAIT_= TX_DMA: + cmd |=3D AST2600_I2CS_TX_CMD; + byte_data =3D AST2600_I2CC_GET_RX_BUFF(i2c_buff); + i2c_slave_event(i2c_bus->target, I2C_SLAVE_READ_REQUESTED, &byte_data); + writel(byte_data, i2c_bus->reg_base + AST2600_I2CC_STS_AND_BUFF); + break; + case AST2600_I2CS_TX_ACK | AST2600_I2CS_WAIT_TX_DMA: + cmd |=3D AST2600_I2CS_TX_CMD; + i2c_slave_event(i2c_bus->target, I2C_SLAVE_READ_PROCESSED, &byte_data); + writel(byte_data, i2c_bus->reg_base + AST2600_I2CC_STS_AND_BUFF); + break; + case AST2600_I2CS_STOP: + case AST2600_I2CS_STOP | AST2600_I2CS_TX_NAK: + i2c_slave_event(i2c_bus->target, I2C_SLAVE_STOP, &value); + break; + default: + dev_dbg(i2c_bus->dev, "unhandled pkt isr %x\n", sts); + break; + } + writel(cmd, i2c_bus->reg_base + AST2600_I2CS_CMD_STS); + writel(sts, i2c_bus->reg_base + AST2600_I2CS_ISR); + readl(i2c_bus->reg_base + AST2600_I2CS_ISR); +} + +static int ast2600_i2c_target_irq(struct ast2600_i2c_bus *i2c_bus) +{ + u32 ier =3D readl(i2c_bus->reg_base + AST2600_I2CS_IER); + u32 isr =3D readl(i2c_bus->reg_base + AST2600_I2CS_ISR); + + if (!(isr & ier)) + return 0; + + /* + * Target interrupt coming after controller packet done + * So need handle controller first. + */ + if (readl(i2c_bus->reg_base + AST2600_I2CM_ISR) & AST2600_I2CM_PKT_DONE) + return 0; + + isr &=3D ~(AST2600_I2CS_ADDR_INDICATE_MASK); + + if (AST2600_I2CS_ADDR1_NAK & isr) + isr &=3D ~AST2600_I2CS_ADDR1_NAK; + + if (AST2600_I2CS_ADDR2_NAK & isr) + isr &=3D ~AST2600_I2CS_ADDR2_NAK; + + if (AST2600_I2CS_ADDR3_NAK & isr) + isr &=3D ~AST2600_I2CS_ADDR3_NAK; + + if (AST2600_I2CS_ADDR_MASK & isr) + isr &=3D ~AST2600_I2CS_ADDR_MASK; + + if (AST2600_I2CS_PKT_DONE & isr) { + if (i2c_bus->mode =3D=3D DMA_MODE) + ast2600_i2c_target_packet_dma_irq(i2c_bus, isr); + else + ast2600_i2c_target_packet_buff_irq(i2c_bus, isr); + } else { + ast2600_i2c_target_byte_irq(i2c_bus, isr); + } + + return 1; +} +#endif + static int ast2600_i2c_setup_dma_tx(u32 cmd, struct ast2600_i2c_bus *i2c_b= us) { struct i2c_msg *msg =3D &i2c_bus->msgs[i2c_bus->msgs_index]; @@ -629,6 +1070,20 @@ static void ast2600_i2c_controller_packet_irq(struct = ast2600_i2c_bus *i2c_bus, u } break; case AST2600_I2CM_RX_DONE: +#if IS_ENABLED(CONFIG_I2C_SLAVE) + /* + * Workaround for controller/target packet mode enable rx done stuck iss= ue + * When controller go for first read (RX_DONE), target mode will also ef= fect + * Then controller will send nack, not operate anymore. + */ + if (readl(i2c_bus->reg_base + AST2600_I2CS_CMD_STS) & AST2600_I2CS_PKT_M= ODE_EN) { + u32 target_cmd =3D readl(i2c_bus->reg_base + AST2600_I2CS_CMD_STS); + + writel(0, i2c_bus->reg_base + AST2600_I2CS_CMD_STS); + writel(target_cmd, i2c_bus->reg_base + AST2600_I2CS_CMD_STS); + } + fallthrough; +#endif case AST2600_I2CM_RX_DONE | AST2600_I2CM_NORMAL_STOP: /* do next rx */ if (i2c_bus->mode =3D=3D DMA_MODE) { @@ -727,6 +1182,12 @@ static irqreturn_t ast2600_i2c_bus_irq(int irq, void = *dev_id) { struct ast2600_i2c_bus *i2c_bus =3D dev_id; =20 +#if IS_ENABLED(CONFIG_I2C_SLAVE) + if (i2c_bus->target) { + if (ast2600_i2c_target_irq(i2c_bus)) + return IRQ_HANDLED; + } +#endif return IRQ_RETVAL(ast2600_i2c_controller_irq(i2c_bus)); } =20 @@ -743,12 +1204,35 @@ static int ast2600_i2c_controller_xfer(struct i2c_ad= apter *adap, struct i2c_msg return ret; } =20 +#if IS_ENABLED(CONFIG_I2C_SLAVE) + if (i2c_bus->mode =3D=3D BUFF_MODE) { + if (i2c_bus->target_active) + return -EBUSY; + /** + * In BUFF_MODE, controller and target share the same buffer register, + * A target transaction can update buffer state asynchronously via IRQ, + * so block controller transfers while target is active, otherwise we can + * corrupt buffer. + */ + writel(0, i2c_bus->reg_base + AST2600_I2CS_IER); + if (readl(i2c_bus->reg_base + AST2600_I2CS_ISR) || i2c_bus->target_activ= e) { + writel(AST2600_I2CS_PKT_DONE, i2c_bus->reg_base + AST2600_I2CS_IER); + return -EBUSY; + } + } +#endif + i2c_bus->cmd_err =3D 0; i2c_bus->msgs =3D msgs; i2c_bus->msgs_index =3D 0; i2c_bus->msgs_count =3D num; reinit_completion(&i2c_bus->cmd_complete); ret =3D ast2600_i2c_do_start(i2c_bus); +#if IS_ENABLED(CONFIG_I2C_SLAVE) + /* avoid race condition target is wait and controller wait 1st target ope= rate */ + if (i2c_bus->mode =3D=3D BUFF_MODE) + writel(AST2600_I2CS_PKT_DONE, i2c_bus->reg_base + AST2600_I2CS_IER); +#endif if (ret) goto controller_out; timeout =3D wait_for_completion_timeout(&i2c_bus->cmd_complete, i2c_bus->= adap.timeout); @@ -767,6 +1251,9 @@ static int ast2600_i2c_controller_xfer(struct i2c_adap= ter *adap, struct i2c_msg * if the bus is still busy. */ if (i2c_bus->multi_master && +#if IS_ENABLED(CONFIG_I2C_SLAVE) + !i2c_bus->target_active && +#endif (readl(i2c_bus->reg_base + AST2600_I2CC_STS_AND_BUFF) & AST2600_I2CC_BUS_BUSY_STS)) ast2600_i2c_recover_bus(i2c_bus); @@ -814,8 +1301,80 @@ static int ast2600_i2c_init(struct ast2600_i2c_bus *i= 2c_bus) /* Clear Interrupt */ writel(GENMASK(27, 0), i2c_bus->reg_base + AST2600_I2CM_ISR); =20 +#if IS_ENABLED(CONFIG_I2C_SLAVE) + /* for memory buffer initial */ + if (i2c_bus->dma_available) { + i2c_bus->target_dma_buf =3D + dmam_alloc_coherent(i2c_bus->dev, I2C_TARGET_MSG_BUF_SIZE, + &i2c_bus->target_dma_addr, GFP_KERNEL); + if (!i2c_bus->target_dma_buf) + return -ENOMEM; + } + + writel(GENMASK(27, 0), i2c_bus->reg_base + AST2600_I2CS_ISR); + + if (i2c_bus->mode =3D=3D BYTE_MODE) + writel(GENMASK(15, 0), i2c_bus->reg_base + AST2600_I2CS_IER); + else + writel(AST2600_I2CS_PKT_DONE, i2c_bus->reg_base + AST2600_I2CS_IER); +#endif + + return 0; +} + +#if IS_ENABLED(CONFIG_I2C_SLAVE) +static int ast2600_i2c_reg_target(struct i2c_client *client) +{ + struct ast2600_i2c_bus *i2c_bus =3D i2c_get_adapdata(client->adapter); + u32 cmd =3D TARGET_TRIGGER_CMD; + + if (i2c_bus->target) + return -EINVAL; + + dev_dbg(i2c_bus->dev, "target addr %x\n", client->addr); + + writel(0, i2c_bus->reg_base + AST2600_I2CS_ADDR_CTRL); + writel(AST2600_I2CC_SLAVE_EN | readl(i2c_bus->reg_base + AST2600_I2CC_FUN= _CTRL), + i2c_bus->reg_base + AST2600_I2CC_FUN_CTRL); + + /* trigger rx buffer */ + if (i2c_bus->mode =3D=3D DMA_MODE) { + cmd |=3D AST2600_I2CS_RX_DMA_EN; + writel(i2c_bus->target_dma_addr, i2c_bus->reg_base + AST2600_I2CS_RX_DMA= ); + writel(i2c_bus->target_dma_addr, i2c_bus->reg_base + AST2600_I2CS_TX_DMA= ); + writel(AST2600_I2CS_SET_RX_DMA_LEN(I2C_TARGET_MSG_BUF_SIZE), + i2c_bus->reg_base + AST2600_I2CS_DMA_LEN); + } else if (i2c_bus->mode =3D=3D BUFF_MODE) { + cmd =3D TARGET_TRIGGER_CMD; + } else { + cmd &=3D ~AST2600_I2CS_PKT_MODE_EN; + } + + writel(cmd, i2c_bus->reg_base + AST2600_I2CS_CMD_STS); + i2c_bus->target =3D client; + /* Set target addr. */ + writel(client->addr | AST2600_I2CS_ADDR1_ENABLE, + i2c_bus->reg_base + AST2600_I2CS_ADDR_CTRL); + + return 0; +} + +static int ast2600_i2c_unreg_target(struct i2c_client *client) +{ + struct ast2600_i2c_bus *i2c_bus =3D i2c_get_adapdata(client->adapter); + u32 val; + + /* Turn off target mode. */ + val =3D readl(i2c_bus->reg_base + AST2600_I2CC_FUN_CTRL); + writel(val & ~AST2600_I2CC_SLAVE_EN, i2c_bus->reg_base + AST2600_I2CC_FUN= _CTRL); + val =3D readl(i2c_bus->reg_base + AST2600_I2CS_ADDR_CTRL); + writel(val & ~AST2600_I2CS_ADDR1_MASK, i2c_bus->reg_base + AST2600_I2CS_A= DDR_CTRL); + + i2c_bus->target =3D NULL; + return 0; } +#endif =20 static u32 ast2600_i2c_functionality(struct i2c_adapter *adap) { @@ -825,6 +1384,10 @@ static u32 ast2600_i2c_functionality(struct i2c_adapt= er *adap) static const struct i2c_algorithm i2c_ast2600_algorithm =3D { .xfer =3D ast2600_i2c_controller_xfer, .functionality =3D ast2600_i2c_functionality, +#if IS_ENABLED(CONFIG_I2C_SLAVE) + .reg_target =3D ast2600_i2c_reg_target, + .unreg_target =3D ast2600_i2c_unreg_target, +#endif }; =20 static void ast2600_i2c_set_xfer_mode(struct ast2600_i2c_bus *i2c_bus, @@ -960,6 +1523,9 @@ static int ast2600_i2c_probe(struct platform_device *p= dev) regmap_write(i2c_bus->global_regs, AST2600_I2CG_CLK_DIV_CTRL, I2CCG_DIV_= CTRL); } =20 +#if IS_ENABLED(CONFIG_I2C_SLAVE) + WRITE_ONCE(i2c_bus->target_active, false); +#endif i2c_bus->dev =3D dev; i2c_bus->multi_master =3D device_property_read_bool(dev, "multi-master"); i2c_bus->dma_available =3D device_property_read_bool(dev, "aspeed,enable-= dma"); --=20 2.34.1