From nobody Mon Jun 8 06:36:52 2026 Received: from twmbx01.aspeedtech.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 000AD3D2FFC; Wed, 3 Jun 2026 05:50:24 +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=1780465826; cv=none; b=R1zriA1IXhVpBiR6sVQA7Xi6IZ0LaBAIk/A37CebdMj3tV10tj7ivEbZwJI9usbVVHDtSaZV6dzCwjJcWoNqEH/oEZdt7p7eYaQzkjB7KX0L/GvTiO4dTXTNlwhgTMz2hZFm9iUyzwDgn9BfvJiI3RdBS9d7nO/P0E3xN8HJwDQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780465826; c=relaxed/simple; bh=YPKMcrNDJaJX+8ptTV6qqSy9Lxm7XtwOftPiAB5gsvk=; h=From:Date:Subject:MIME-Version:Content-Type:Message-ID:References: In-Reply-To:To:CC; b=hgfKXkpf/W76qzIC3FScznkoCIaXQHLw0a9pliIi5YTAUZ+nDsEto9gMM6yHaVauRPEMehqYRxcXcqbjOir9xrnwK9PckM6btn62MRnmAk3Tb1dl/mqZ2zh2JRa0l+5DMN6ltadkB1MoEK2FqQVhiNClJajtgmtl3+cZKRxP9PE= 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; Wed, 3 Jun 2026 13:50:14 +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; Wed, 3 Jun 2026 13:50:14 +0800 From: Ryan Chen Date: Wed, 3 Jun 2026 13:50:13 +0800 Subject: [PATCH v31 1/5] 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: <20260603-upstream_i2c-v31-1-ba7a02714f22@aspeedtech.com> References: <20260603-upstream_i2c-v31-0-ba7a02714f22@aspeedtech.com> In-Reply-To: <20260603-upstream_i2c-v31-0-ba7a02714f22@aspeedtech.com> To: , , Andi Shyti , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Joel Stanley , Andrew Jeffery , "Benjamin Herrenschmidt" , Rayn Chen , Philipp Zabel CC: , , , , , , Ryan Chen , Conor Dooley X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1780465814; l=4676; i=ryan_chen@aspeedtech.com; s=20251126; h=from:subject:message-id; bh=YPKMcrNDJaJX+8ptTV6qqSy9Lxm7XtwOftPiAB5gsvk=; b=wa4XHPTuEaYeA3jwIGbJnp4YGL7gVWelgUi0BTLpfPmipntbPb5gFzWzMfTQKQ+A/PN0sC2/O 95hD/cPJ+nYAopHvOlfsnN7d7xogQn6yKFPvNcnj8UIRwyWCXZgLaro 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. - describe two reg regions (controller register block + buffer block); the second region is optional (minItems: 1) so existing AST2600 DTs that only declare the controller register block continue to validate - use clock-frequency for bus speed description - interrupts are required on AST2600 - use correct DTS coding style in example No compatible strings are changed. Acked-by: Conor Dooley Reviewed-by: Rob Herring (Arm) Signed-off-by: Ryan Chen --- Changes in v31: - Commit message body: clarify that the second reg region is optional (minItems: 1) rather than required, matching the schema and the v30 backward-compatibility fix (Sashiko AI review). Changes in v30: - Add minItems: 1 to reg so existing AST2600 DTs with a single reg region continue to validate (Sashiko AI review) - Retain bus-frequency as a deprecated property to avoid breaking existing AST2600 DTs under unevaluatedProperties: false (Sashiko AI review) 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 | 73 ++++++++++++++++++= ++++ .../devicetree/bindings/i2c/aspeed,i2c.yaml | 3 +- 2 files changed, 74 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..abc614315dff --- /dev/null +++ b/Documentation/devicetree/bindings/i2c/aspeed,ast2600-i2c.yaml @@ -0,0 +1,73 @@ +# 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: + minItems: 1 + 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 + + bus-frequency: + $ref: /schemas/types.yaml#/definitions/uint32 + deprecated: true + description: + Legacy name for clock-frequency. Existing AST2600 device trees + used this before the binding was split out. New device trees + should use the standard clock-frequency property instead. + minimum: 500 + maximum: 4000000 + + 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 Mon Jun 8 06:36:52 2026 Received: from twmbx01.aspeedtech.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 270F13ED5BE; Wed, 3 Jun 2026 05:50:27 +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=1780465829; cv=none; b=V41Oow8lTGFCSzCAhN3ecQOA0S1bRiTzvmJXwRHMBuk0yLFiNsoB/ICzw7ynRJisuhC34nwk9aSrc7OPHPNTY8T9gYqilIeHKu0gHlRF+kIRQRVvXBTI534VlU3nEnuClvKnKho5k5/8v9BNwTrxPmAgUw5AoPol1d8RowYJvKA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780465829; c=relaxed/simple; bh=bu5HMc2Z/yj0Ft5OgeLngLhHplEIK0fnHLwW0MpLC2E=; h=From:Date:Subject:MIME-Version:Content-Type:Message-ID:References: In-Reply-To:To:CC; b=qLwx95d/Maclsvfhmg0EHDnOsVWnxbCGT7nIdQUA1BKyGj2T6bWd8C/FFdwJtRI/dn/Sz/PE+OOCLfwbguMYszsjoiIfhPraYLqfwPFUlrdyte1G23vODXAuaxStTMyxCTF/s6QjUyJiZFTm6V2n48UtLSCP5Mepid5i74Y2Ebs= 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; Wed, 3 Jun 2026 13:50:14 +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; Wed, 3 Jun 2026 13:50:14 +0800 From: Ryan Chen Date: Wed, 3 Jun 2026 13:50:14 +0800 Subject: [PATCH v31 2/5] i2c: aspeed: Read clock-frequency via i2c_parse_fw_timings() 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: <20260603-upstream_i2c-v31-2-ba7a02714f22@aspeedtech.com> References: <20260603-upstream_i2c-v31-0-ba7a02714f22@aspeedtech.com> In-Reply-To: <20260603-upstream_i2c-v31-0-ba7a02714f22@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=1780465814; l=2076; i=ryan_chen@aspeedtech.com; s=20251126; h=from:subject:message-id; bh=bu5HMc2Z/yj0Ft5OgeLngLhHplEIK0fnHLwW0MpLC2E=; b=KP0zlKTKenjOVLWiWBSGmV3hQjE0t9FYRE5BSQXMnQycqU/ORxG6lHvdeI44SAhc3be6p9udA Sd/9iPuLwjKD6g0X2Fltr+SKrx3r0lJ2TX3+MyJWwPQ7BDfmPN85Z+F X-Developer-Key: i=ryan_chen@aspeedtech.com; a=ed25519; pk=Xe73xY6tcnkuRjjbVAB/oU30KdB3FvG4nuJuILj7ZVc= Use i2c_parse_fw_timings() to read the standard "clock-frequency" property, and fall back to "bus-frequency" only when the standard property is absent. This honors device trees written against the updated aspeed,ast2600-i2c binding without silently falling back to 100 kHz, while keeping existing in-tree device trees using "bus-frequency" working. Signed-off-by: Ryan Chen --- Changes in v31: - Zero-initialise `struct i2c_timings timings` so the bus-frequency fallback runs when clock-frequency is absent (Sashiko AI review). --- drivers/i2c/busses/i2c-aspeed.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/drivers/i2c/busses/i2c-aspeed.c b/drivers/i2c/busses/i2c-aspee= d.c index a26b74c71206..f00bd779146e 100644 --- a/drivers/i2c/busses/i2c-aspeed.c +++ b/drivers/i2c/busses/i2c-aspeed.c @@ -1000,6 +1000,7 @@ static int aspeed_i2c_probe_bus(struct platform_devic= e *pdev) const struct of_device_id *match; struct aspeed_i2c_bus *bus; struct clk *parent_clk; + struct i2c_timings timings =3D {}; int irq, ret; =20 bus =3D devm_kzalloc(&pdev->dev, sizeof(*bus), GFP_KERNEL); @@ -1025,12 +1026,18 @@ static int aspeed_i2c_probe_bus(struct platform_dev= ice *pdev) } reset_control_deassert(bus->rst); =20 - ret =3D of_property_read_u32(pdev->dev.of_node, - "bus-frequency", &bus->bus_frequency); - if (ret < 0) { - dev_err(&pdev->dev, - "Could not read bus-frequency property\n"); - bus->bus_frequency =3D I2C_MAX_STANDARD_MODE_FREQ; + i2c_parse_fw_timings(&pdev->dev, &timings, false); + if (timings.bus_freq_hz) { + bus->bus_frequency =3D timings.bus_freq_hz; + } else { + ret =3D of_property_read_u32(pdev->dev.of_node, + "bus-frequency", + &bus->bus_frequency); + if (ret < 0) { + dev_err(&pdev->dev, + "Could not read clock-frequency or bus-frequency property\n"); + bus->bus_frequency =3D I2C_MAX_STANDARD_MODE_FREQ; + } } =20 match =3D of_match_node(aspeed_i2c_bus_of_table, pdev->dev.of_node); --=20 2.34.1 From nobody Mon Jun 8 06:36:52 2026 Received: from twmbx01.aspeedtech.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 523433F4DD4; Wed, 3 Jun 2026 05:50:29 +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=1780465830; cv=none; b=cBfJmS3yHcwSZKqnGjDR+KHDcSwNlsOuYSq1c63JEkgevKdXJwSZaBbJ3w3p+6Ejt0cf1vdpiAzTk63mhE95lE1EKDGL05jzON6/tQKgcLSSsMMkIS1pLSclhd2I7sdjfGUD5lhNy/42/G09OeGWlNKFX9FwNEokEVrbO5kACHc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780465830; c=relaxed/simple; bh=P8FOm2OlqLJ/+F1CLt832mjsrW8d4pnBuooTxj2MoGw=; h=From:Date:Subject:MIME-Version:Content-Type:Message-ID:References: In-Reply-To:To:CC; b=Y45NOqoVDGKxqIwz/WkbH7l3dx/A38jgbbBNjHxH8aql5Cdimj0D0H28sGZcXpMe+XGFgumCYlfe7WoMpNIDVrw9rBU7VoMl4XjwZ7ja2UtqGfn0P4bwiEbm8ZhowpqLS1rSFlfOL1zEddVO+9zBFQ+1S3Tn04yJKy07LSL7hFI= 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; Wed, 3 Jun 2026 13:50:14 +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; Wed, 3 Jun 2026 13:50:14 +0800 From: Ryan Chen Date: Wed, 3 Jun 2026 13:50:15 +0800 Subject: [PATCH v31 3/5] dt-bindings: i2c: ast2600-i2c.yaml: Add global-regs 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: <20260603-upstream_i2c-v31-3-ba7a02714f22@aspeedtech.com> References: <20260603-upstream_i2c-v31-0-ba7a02714f22@aspeedtech.com> In-Reply-To: <20260603-upstream_i2c-v31-0-ba7a02714f22@aspeedtech.com> To: , , Andi Shyti , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Joel Stanley , Andrew Jeffery , "Benjamin Herrenschmidt" , Rayn Chen , Philipp Zabel CC: , , , , , , Ryan Chen , Conor Dooley X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1780465814; l=1769; i=ryan_chen@aspeedtech.com; s=20251126; h=from:subject:message-id; bh=P8FOm2OlqLJ/+F1CLt832mjsrW8d4pnBuooTxj2MoGw=; b=6bkT8VCm4y2Fw2+xp4f2lDpHbvzxgNBdDOdZ5PTWcuHEYDFw4tgZlQZr3r/D4I3jQQwnsPPuW 5Zt/E9Kk2RhAVpnpXOKHQQjYQqmAfeqkH02ImwqGwDwIea9YCfnAigI X-Developer-Key: i=ryan_chen@aspeedtech.com; a=ed25519; pk=Xe73xY6tcnkuRjjbVAB/oU30KdB3FvG4nuJuILj7ZVc= 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. Acked-by: Conor Dooley Reviewed-by: Rob Herring (Arm) Signed-off-by: Ryan Chen --- Changes in v29: - remove aspeed,enable-dma properties. 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. --- Documentation/devicetree/bindings/i2c/aspeed,ast2600-i2c.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Documentation/devicetree/bindings/i2c/aspeed,ast2600-i2c.yaml = b/Documentation/devicetree/bindings/i2c/aspeed,ast2600-i2c.yaml index abc614315dff..21469715d845 100644 --- a/Documentation/devicetree/bindings/i2c/aspeed,ast2600-i2c.yaml +++ b/Documentation/devicetree/bindings/i2c/aspeed,ast2600-i2c.yaml @@ -48,6 +48,12 @@ properties: resets: maxItems: 1 =20 + 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 @@ -70,4 +76,5 @@ examples: resets =3D <&syscon ASPEED_RESET_I2C>; clock-frequency =3D <100000>; interrupts =3D ; + aspeed,global-regs =3D <&i2c_global>; }; --=20 2.34.1 From nobody Mon Jun 8 06:36:52 2026 Received: from twmbx01.aspeedtech.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 18A3D3F58E9; Wed, 3 Jun 2026 05:50:31 +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=1780465834; cv=none; b=WAga4TthEJGfnPYmX8+j3cm55Cx1tOeUqS9wwgml6WladuoizBkyqNqPCcmLKKIiivw9CiO+ZN03mDpoT1PgEolQkff1v3zRt8FEpXHh5I0D8ILCk/OWOuNGI8DlTjNOwKw+z1SdGACYxgjUA/NvRuTf/rnzULkkM1YOOsBPYUg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780465834; c=relaxed/simple; bh=YiJt/KoNtWsDDa9F/Wr6M4a0jNDBDKKED94a+12zAsE=; h=From:Date:Subject:MIME-Version:Content-Type:Message-ID:References: In-Reply-To:To:CC; b=IOwyxt/xM2tjk6cHA1ne8aZOzgDISRnZdsN2kNHOrT5PRnbc/jcnosIBup0xtOq9klFgYJhr9Aq9YFMEVNh0BfpY3/FyGTjIDhgf10If1XQ8TgeLO6SqZbHpGfStnnAch8VWV0GZiBnn7aJCW8F/KGCjkmYY/5paO6ywuOGm1/w= 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; Wed, 3 Jun 2026 13:50:14 +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; Wed, 3 Jun 2026 13:50:14 +0800 From: Ryan Chen Date: Wed, 3 Jun 2026 13:50:16 +0800 Subject: [PATCH v31 4/5] 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: <20260603-upstream_i2c-v31-4-ba7a02714f22@aspeedtech.com> References: <20260603-upstream_i2c-v31-0-ba7a02714f22@aspeedtech.com> In-Reply-To: <20260603-upstream_i2c-v31-0-ba7a02714f22@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=1780465814; l=37446; i=ryan_chen@aspeedtech.com; s=20251126; h=from:subject:message-id; bh=YiJt/KoNtWsDDa9F/Wr6M4a0jNDBDKKED94a+12zAsE=; b=u4x0ksTIteM6n+Zg3wRkV9EnhDObWh8X/OdUctscEGZOv6lOJkeofCiALmCJh8CAxptNKyC5e H/q5hn/HEpGB6+ggIDGuy7ndPe1BQQlnb0XOg+UVHcJq+chXS8pYbwI 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) - 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. 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 v31: - Reject zero-length RX in ast2600_i2c_setup_buff_rx() with -EINVAL. AST2600_I2CC_SET_RX_BUF_LEN() encodes length as (x - 1), so passing 0 underflows to a 32-byte read and overruns msg->buf. Propagate the error from the RX_DONE continuation callsite in the controller packet IRQ handler so the transfer aborts cleanly instead of hanging until the SW timeout. - Address Sashiko AI code review feedback: - Clear i2c_bus->msgs on every controller_xfer() return path and bail out at the head of ast2600_i2c_controller_packet_irq() when i2c_bus->msgs is NULL. After a transfer times out, i2c-core releases the msgs array, leaving i2c_bus->msgs dangling; a late IRQ would otherwise dereference freed memory (UAF). - Clamp the HW-reported xfer_len against buf_size and remaining msg->buf space via a new ast2600_i2c_clamp_len() helper used in both the TX_ACK and RX_DONE branches. A HW glitch reporting a larger length than expected would otherwise overrun msg->buf (out-of-bounds write). - Use regmap_update_bits() for AST2600_I2CG_CTRL during global initialisation. The register is shared across all i2c buses, and regmap_write() would clobber bootloader/other-driver-set bits (e.g. SLAVE_PKT_NAK, M_S_SEPARATE_INTR) and lose its TOCTOU protection across parallel probes. - SMBus block read: when the slave reports recv_len =3D=3D 0, set controller_xfer_cnt =3D msg->len so the "msg done" check succeeds instead of issuing an extra 1-byte RX that would overwrite the legitimate length-byte in msg->buf[0]. - ast2600_i2c_recover_bus() timeout path: mirror the controller xfer timeout sequence (disable IER, synchronize_irq(), W1C ISR, reset master, restore IER) so a late BUS_RECOVER IRQ cannot spuriously complete an unrelated subsequent transfer. - Remove unused #include ; all APIs used by this driver are provided by property.h, mfd/syscon.h, and regmap.h. - Remove dead adap.algo_data assignment in probe(); since kernel 3.3, i2c_get_adapdata() reads via dev_get_drvdata() set by i2c_set_adapdata(), not from algo_data directly. Changes in v30: - Address Sashiko AI code review feedback: - Use manual i2c_add_adapter() / i2c_del_adapter() instead of devm_i2c_add_adapter() so the adapter is torn down before the hardware is disabled in remove(); otherwise client .remove() callbacks can fail or hang after FUN_CTRL/IER have been cleared. - synchronize_irq() and clear pending IRQ status on the controller timeout path to avoid the ISR racing with the next transfer and touching freed msgs. - Use clamp_t() for AC TIMING divisor / scl_low / scl_high so extreme clock-frequency values cannot underflow into the unsigned domain and corrupt the AC TIMING register. - Derive the RX buffer offset from buf_size instead of hardcoding 0x10, since the dual-pool split is configurable. - Clamp i2c-scl-clk-low-timeout-us to the TTIMEOUT field's 5-bit range (max 31 * 1024us) and emit a dev_warn() instead of letting AST2600_I2CC_TTIMEOUT()'s mask silently truncate larger values. - Return -EBUSY (not -ENOMEM) for every ast2600_i2c_do_start() failure path in the controller packet IRQ handler (NORMAL_STOP, TX_ACK, and RX_DONE branches). - Advertise I2C_AQ_NO_ZERO_LEN_READ via i2c_adapter_quirks so the i2c-core rejects zero-byte reads before they reach the driver. The AST2600 packet engine cannot encode a zero-length RX command and would otherwise stall waiting for an RX_DONE that never arrives. Changes in v29: - update commit message remove transfer mode selection. - remove dma/byte transfer, use buffer mode only. - remove sysfs file. - remove define I2C_TARGET_MSG_BUF_SIZE and AST2600_I2C_DMA_SIZE. - remove buf_index in struct ast2600_i2c_bus. 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(). --- drivers/i2c/busses/Makefile | 2 +- drivers/i2c/busses/i2c-aspeed.c | 5 + drivers/i2c/busses/i2c-ast2600.c | 864 +++++++++++++++++++++++++++++++++++= ++++ 3 files changed, 870 insertions(+), 1 deletion(-) 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 f00bd779146e..c96d30b97d16 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 @@ -1003,6 +1004,10 @@ static int aspeed_i2c_probe_bus(struct platform_devi= ce *pdev) struct i2c_timings timings =3D {}; 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..35f38e1f4799 --- /dev/null +++ b/drivers/i2c/busses/i2c-ast2600.c @@ -0,0 +1,864 @@ +// 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 + +#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 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 + +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; + u32 apb_clk; + u32 timeout; + int irq; + int cmd_err; + int msgs_index; + int msgs_count; + int controller_xfer_cnt; + size_t buf_size; + bool multi_master; + void __iomem *buf_base; +}; + +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 clamp_t(int, baseclk_idx, 0, 15); + divisor =3D clamp_t(int, divisor, 2, 32); + scl_low =3D clamp_t(int, divisor * 9 / 16 - 1, 0, 15); + scl_high =3D clamp_t(int, divisor - scl_low - 2, 1, 15); + 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"); + writel(0, i2c_bus->reg_base + AST2600_I2CM_IER); + synchronize_irq(i2c_bus->irq); + writel(readl(i2c_bus->reg_base + AST2600_I2CM_ISR), + i2c_bus->reg_base + AST2600_I2CM_ISR); + ctrl =3D readl(i2c_bus->reg_base + AST2600_I2CC_FUN_CTRL); + writel(ctrl & ~AST2600_I2CC_MASTER_EN, + i2c_bus->reg_base + AST2600_I2CC_FUN_CTRL); + writel(ctrl, i2c_bus->reg_base + AST2600_I2CC_FUN_CTRL); + writel(AST2600_I2CM_PKT_DONE | AST2600_I2CM_BUS_RECOVER, + i2c_bus->reg_base + AST2600_I2CM_IER); + 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_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_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; + } + + if (xfer_len <=3D 0) + return -EINVAL; + + 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_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); + + i2c_bus->controller_xfer_cnt =3D 0; + + if (msg->flags & I2C_M_RD) + return ast2600_i2c_setup_buff_rx(AST2600_I2CM_START_CMD, i2c_bus); + + return ast2600_i2c_setup_buff_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 int ast2600_i2c_clamp_len(struct ast2600_i2c_bus *i2c_bus, + struct i2c_msg *msg, int len) +{ + int remaining =3D msg->len - i2c_bus->controller_xfer_cnt; + + if (len > i2c_bus->buf_size) + len =3D i2c_bus->buf_size; + if (remaining < 0) + remaining =3D 0; + if (len > remaining) + len =3D remaining; + return len; +} + +static void ast2600_i2c_controller_packet_irq(struct ast2600_i2c_bus *i2c_= bus, u32 sts) +{ + struct i2c_msg *msg; + int xfer_len; + int i; + + if (!i2c_bus->msgs) + return; + msg =3D &i2c_bus->msgs[i2c_bus->msgs_index]; + + 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 -EBUSY; + 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: + xfer_len =3D AST2600_I2CC_GET_TX_BUF_LEN(readl(i2c_bus->reg_base + + AST2600_I2CC_BUFF_CTRL)); + xfer_len =3D ast2600_i2c_clamp_len(i2c_bus, msg, xfer_len); + 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 -EBUSY; + complete(&i2c_bus->cmd_complete); + } + } + } else { + ast2600_i2c_setup_buff_tx(0, i2c_bus); + } + break; + case AST2600_I2CM_RX_DONE: + case AST2600_I2CM_RX_DONE | AST2600_I2CM_NORMAL_STOP: + xfer_len =3D AST2600_I2CC_GET_RX_BUF_LEN(readl(i2c_bus->reg_base + + AST2600_I2CC_BUFF_CTRL)); + xfer_len =3D ast2600_i2c_clamp_len(i2c_bus, msg, xfer_len); + for (i =3D 0; i < xfer_len; i++) + msg->buf[i2c_bus->controller_xfer_cnt + i] =3D + readb(i2c_bus->buf_base + i2c_bus->buf_size + i); + + 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 msg->len; + 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 -EBUSY; + complete(&i2c_bus->cmd_complete); + } + } + } else if (ast2600_i2c_setup_buff_rx(0, i2c_bus)) { + i2c_bus->cmd_err =3D -EINVAL; + complete(&i2c_bus->cmd_complete); + } + 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(0, i2c_bus->reg_base + AST2600_I2CM_IER); + synchronize_irq(i2c_bus->irq); + writel(readl(i2c_bus->reg_base + AST2600_I2CM_ISR), + i2c_bus->reg_base + AST2600_I2CM_ISR); + + writel(ctrl & ~AST2600_I2CC_MASTER_EN, i2c_bus->reg_base + AST2600_I2CC_= FUN_CTRL); + writel(ctrl, i2c_bus->reg_base + AST2600_I2CC_FUN_CTRL); + + writel(AST2600_I2CM_PKT_DONE | AST2600_I2CM_BUS_RECOVER, + i2c_bus->reg_base + AST2600_I2CM_IER); + + /* + * 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: + i2c_bus->msgs =3D NULL; + 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); + + /* 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 const struct i2c_adapter_quirks ast2600_i2c_quirks =3D { + .flags =3D I2C_AQ_NO_ZERO_LEN_READ, +}; + +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_update_bits(i2c_bus->global_regs, AST2600_I2CG_CTRL, + AST2600_GLOBAL_INIT, AST2600_GLOBAL_INIT); + 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"); + + buf_base =3D devm_platform_get_and_ioremap_resource(pdev, 1, &res); + if (IS_ERR(buf_base)) + return dev_err_probe(dev, PTR_ERR(buf_base), "Missing buffer resource\n"= ); + i2c_bus->buf_base =3D buf_base; + i2c_bus->buf_size =3D resource_size(res) / 2; + + /* + * 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); + if (i2c_bus->timeout > GENMASK(4, 0)) { + dev_warn(dev, + "i2c-scl-clk-low-timeout-us exceeds HW max (31 * 1024us), clamped\n"); + i2c_bus->timeout =3D GENMASK(4, 0); + } + } + + 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.quirks =3D &ast2600_i2c_quirks; + 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)); + 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 i2c_add_adapter(&i2c_bus->adap); + if (ret) + 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); + + i2c_del_adapter(&i2c_bus->adap); + + /* 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 Mon Jun 8 06:36:52 2026 Received: from twmbx01.aspeedtech.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 C30683F4130; Wed, 3 Jun 2026 05:50:34 +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=1780465836; cv=none; b=kfL5gpVX68poI+fp5B4XAzfm00dz7Czb/1P4YHCd29aoyWHgfnWm66IogjshcEe+Pn3Mm31+7mB1+StmAzutLSboI7XM5ZjlGp8H9VxEZcYAPjeMYjduaiwDAKgC0f0Zu7+6U01bYx0tjpdMF6K9p40+s6a3ETp78dILVhJiz2c= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780465836; c=relaxed/simple; bh=TWZtKOLomCRsh6e9Ol51AxcvGHF7K5JDGt1GBZ8acW4=; h=From:Date:Subject:MIME-Version:Content-Type:Message-ID:References: In-Reply-To:To:CC; b=daGENl+MZc7FYB2CdRP3AuyFxyvykoOYaa+QUT82yv9CYotd9cpRqswuYULVuAzTfDKS07aTIhU2/CJgMvxuBhbsp0rZWo6qK76YVjm2RmFi8aH08KCE3IyoRP1DEk4oQqZ1U1AK3jj0TH5ns9pwBFvIwt5QgjgvUO9qZvNVJnM= 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; Wed, 3 Jun 2026 13:50:15 +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; Wed, 3 Jun 2026 13:50:15 +0800 From: Ryan Chen Date: Wed, 3 Jun 2026 13:50:17 +0800 Subject: [PATCH v31 5/5] 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: <20260603-upstream_i2c-v31-5-ba7a02714f22@aspeedtech.com> References: <20260603-upstream_i2c-v31-0-ba7a02714f22@aspeedtech.com> In-Reply-To: <20260603-upstream_i2c-v31-0-ba7a02714f22@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=1780465814; l=21388; i=ryan_chen@aspeedtech.com; s=20251126; h=from:subject:message-id; bh=TWZtKOLomCRsh6e9Ol51AxcvGHF7K5JDGt1GBZ8acW4=; b=d5BzjA4cqyV7fz50PeosM1Dgxc4+lUbwE2HVO6otb0+31z0mlfo4WzvMhfgiClc5Oy8854Mod Dx4DoHPEC7PCKk54ZYE2tv/GBE0Eq3hThwyHN7u84QHrsYcAH3HAJCG 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 This complements the existing controller-mode support, enabling dual-role capability. Signed-off-by: Ryan Chen --- Changes in v31: - Address Sashiko AI code review feedback on the target patch: - Clear target_active on any STOP. The previous condition `(sts & STOP) && !(sts & SLAVE_PENDING)` failed under coalesced STOP+SLAVE_PENDING IRQs and left target_active stuck true, deadlocking controller_xfer() with -EBUSY forever. - Enable the target IER inside reg_target() instead of unconditionally in probe(). unreg_target() disables it; without matching re-enable in reg_target() a subsequent re-registration would never receive IRQs. Also avoids spurious IRQ activity when no slave is registered. - Add the missing CMD_STS write in the SLAVE_PENDING|RX_DONE|WAIT_TX_DMA|STOP target ISR case so the HW state machine is re-armed (TRIGGER | TX_BUFF_EN); otherwise the controller stretches SCL until INACTIVE_TO recovers. - Default target ISR case now writes TARGET_TRIGGER_CMD to re-arm the HW state machine instead of silently breaking, which previously left the bus hung on unhandled sts combinations. - W1C-clear the ADDR1/2/3_NAK bits in HW (not just locally) in ast2600_i2c_target_irq(); stale ADDR_NAK bits made controller_xfer() see I2CS_ISR !=3D 0 and bounce every transfer with -EBUSY. - unreg_target(): replace masked ADDR_CTRL write with writel(0, ...); AST2600_I2CS_ADDR1_MASK covers only bits[6:0] (the 7-bit address), leaving ADDR1_ENABLE (BIT(7)) set after unregister. Although SLAVE_EN is cleared first, writing 0 is the correct and complete teardown. Changes in v30: - Address Sashiko AI code review feedback: - Force-stop path (target IRQ aborting an in-flight controller transfer): disable the controller IER and W1C-clear pending ISR before calling complete(), then restore the IER after the wake-up. Without the disable/clear sequence the controller IRQ handler can race with the target abort path and double-complete or touch freed msgs. - unreg_target() teardown ordering: disable the target IER first, then disable SLAVE_EN / clear ADDR_CTRL, synchronize_irq(), W1C pending ISR, and only then NULL i2c_bus->target and clear target_active. The old order left IER enabled while target was being cleared, allowing an in-flight handler to dereference a target pointer the caller had already freed. - reg_target() bring-up ordering: assign i2c_bus->target before enabling SLAVE_EN. Otherwise an IRQ that fires after SLAVE_EN is set but before the pointer is stored finds target =3D=3D NULL, exits without clearing the ISR, and the unmasked event re-fires as an IRQ storm. - Use writel() instead of writeb() when staging a TX byte into the target buffer. The AST2600 buffer SRAM only supports 32-bit accesses; byte writes are silently dropped (or, on some revisions, raise a bus fault), so a SLAVE_READ_REQUESTED reply never reaches the master. - reg_target() rejects 10-bit client addresses with -EAFNOSUPPORT. AST2600_I2CS_ADDR1 is only a 7-bit field; without the check, the high bits of a 10-bit address overflow into the adjacent ADDR2 field and silently corrupt a second target slot. - Initialise the local `u8 value` to 0 in the target packet IRQ handler. Its address is passed to i2c_slave_event() for events such as I2C_SLAVE_STOP / I2C_SLAVE_READ_REQUESTED; a slave backend that reads the byte before writing would otherwise leak uninitialised kernel stack. Changes in v29: - fix race between unreg_target and IRQ handler. - move i2cs ier enable from ast2600_i2c_init to probe after master ier enab= le. - remove dma/byte transfer, use buffer mode only. 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 | 359 +++++++++++++++++++++++++++++++++++= ++++ 1 file changed, 359 insertions(+) diff --git a/drivers/i2c/busses/i2c-ast2600.c b/drivers/i2c/busses/i2c-ast2= 600.c index 35f38e1f4799..38d372278b64 100644 --- a/drivers/i2c/busses/i2c-ast2600.c +++ b/drivers/i2c/busses/i2c-ast2600.c @@ -253,6 +253,11 @@ struct ast2600_i2c_bus { size_t buf_size; bool multi_master; void __iomem *buf_base; +#if IS_ENABLED(CONFIG_I2C_SLAVE) + /* target structure */ + bool target_active; + struct i2c_client *target; +#endif }; =20 static void ast2600_i2c_ac_timing_config(struct ast2600_i2c_bus *i2c_bus) @@ -346,6 +351,253 @@ 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_buff_irq(struct ast2600_i2c_bus *i2c= _bus, u32 sts) +{ + u8 value =3D 0; + int target_rx_len =3D 0; + u32 cmd =3D 0; + 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); + writel(0, i2c_bus->reg_base + AST2600_I2CM_IER); + writel(readl(i2c_bus->reg_base + AST2600_I2CM_ISR), + i2c_bus->reg_base + AST2600_I2CM_ISR); + i2c_bus->cmd_err =3D -EBUSY; + writel(0, i2c_bus->reg_base + AST2600_I2CC_BUFF_CTRL); + complete(&i2c_bus->cmd_complete); + writel(AST2600_I2CM_PKT_DONE | AST2600_I2CM_BUS_RECOVER, + i2c_bus->reg_base + AST2600_I2CM_IER); + } + + /* 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(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 + i2c_bus->buf_size + 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 + i2c_bus->buf_size + 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 + i2c_bus->buf_size + 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 + i2c_bus->buf_size + 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 + i2c_bus->buf_size + 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 + i2c_bus->buf_size + 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); + writel(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_WAIT_TX_DMA | AST2600_I2CS_SLAVE_MATCH: + i2c_slave_event(i2c_bus->target, I2C_SLAVE_READ_REQUESTED, &value); + writel(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 + i2c_bus->buf_size + 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 + i2c_bus->buf_size + 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); + } + writel(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)); + cmd =3D TARGET_TRIGGER_CMD; + 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) + WRITE_ONCE(i2c_bus->target_active, false); +} + +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 (isr & (AST2600_I2CS_ADDR1_NAK | AST2600_I2CS_ADDR2_NAK | + AST2600_I2CS_ADDR3_NAK)) { + writel(isr & (AST2600_I2CS_ADDR1_NAK | AST2600_I2CS_ADDR2_NAK | + AST2600_I2CS_ADDR3_NAK), + i2c_bus->reg_base + AST2600_I2CS_ISR); + isr &=3D ~(AST2600_I2CS_ADDR1_NAK | AST2600_I2CS_ADDR2_NAK | + AST2600_I2CS_ADDR3_NAK); + } + + if (AST2600_I2CS_ADDR_MASK & isr) + isr &=3D ~AST2600_I2CS_ADDR_MASK; + + if (AST2600_I2CS_PKT_DONE & isr) + ast2600_i2c_target_packet_buff_irq(i2c_bus, isr); + + return 1; +} +#endif + 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]; @@ -530,6 +782,20 @@ static void ast2600_i2c_controller_packet_irq(struct a= st2600_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: xfer_len =3D AST2600_I2CC_GET_RX_BUF_LEN(readl(i2c_bus->reg_base + AST2600_I2CC_BUFF_CTRL)); @@ -617,6 +883,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 @@ -633,12 +905,31 @@ static int ast2600_i2c_controller_xfer(struct i2c_ada= pter *adap, struct i2c_msg return ret; } =20 +#if IS_ENABLED(CONFIG_I2C_SLAVE) + if (i2c_bus->target_active) + return -EBUSY; + /* + * 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 to avoid buffer corruption. + */ + writel(0, i2c_bus->reg_base + AST2600_I2CS_IER); + if (readl(i2c_bus->reg_base + AST2600_I2CS_ISR) || i2c_bus->target_active= ) { + 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 */ + 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); @@ -666,6 +957,9 @@ static int ast2600_i2c_controller_xfer(struct i2c_adapt= er *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); @@ -702,8 +996,66 @@ static int ast2600_i2c_init(struct ast2600_i2c_bus *i2= c_bus) /* Clear Interrupt */ writel(GENMASK(27, 0), i2c_bus->reg_base + AST2600_I2CM_ISR); =20 +#if IS_ENABLED(CONFIG_I2C_SLAVE) + writel(GENMASK(27, 0), i2c_bus->reg_base + AST2600_I2CS_ISR); +#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; + + if (client->flags & I2C_CLIENT_TEN) + return -EAFNOSUPPORT; + + dev_dbg(i2c_bus->dev, "target addr %x\n", client->addr); + + writel(0, i2c_bus->reg_base + AST2600_I2CS_ADDR_CTRL); + + i2c_bus->target =3D client; + + writel(AST2600_I2CC_SLAVE_EN | readl(i2c_bus->reg_base + AST2600_I2CC_FUN= _CTRL), + i2c_bus->reg_base + AST2600_I2CC_FUN_CTRL); + + writel(cmd, i2c_bus->reg_base + AST2600_I2CS_CMD_STS); + /* Set target addr. */ + writel(client->addr | AST2600_I2CS_ADDR1_ENABLE, + i2c_bus->reg_base + AST2600_I2CS_ADDR_CTRL); + + writel(AST2600_I2CS_PKT_DONE, i2c_bus->reg_base + AST2600_I2CS_IER); + + 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; + + writel(0, i2c_bus->reg_base + AST2600_I2CS_IER); + + 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); + writel(0, i2c_bus->reg_base + AST2600_I2CS_ADDR_CTRL); + + synchronize_irq(i2c_bus->irq); + + writel(readl(i2c_bus->reg_base + AST2600_I2CS_ISR), + i2c_bus->reg_base + AST2600_I2CS_ISR); + + i2c_bus->target =3D NULL; + WRITE_ONCE(i2c_bus->target_active, false); + return 0; } +#endif =20 static u32 ast2600_i2c_functionality(struct i2c_adapter *adap) { @@ -713,6 +1065,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 const struct i2c_adapter_quirks ast2600_i2c_quirks =3D { @@ -756,6 +1112,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"); =20 --=20 2.34.1