From nobody Thu Jun 11 08:16:04 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 B2BEF349CD9; Thu, 11 Jun 2026 05:31:36 +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=1781155900; cv=none; b=occgpdNPTiqkC0NAPEHuSTr/O9G8cpwZsjIWYb1kfmmfhu6yk1+0o5yZ7vdS0+V6H7LuAJWDUX3rF9lAovO9NhSJ5IMmcQHSwpXrJCQqdqGvkSG7BrrddMjzcYu//K8nslbS5N5+TXCvpm3SQWAdBScRGLNUXS1HUn66tciHvfk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1781155900; c=relaxed/simple; bh=ktYoafNweHhZs0ZmbKaHwFTXK1XM2KW9vSEeOcsvt7o=; h=From:Date:Subject:MIME-Version:Content-Type:Message-ID:References: In-Reply-To:To:CC; b=gVcysfX6yCe/N749iSVGhR1EyNvQfqbFhaCOApMkU1AYTrwXDEi/3FU2Ydy8L24mtY0txhxWKp6KT/ImBpqrlqlPjmt3y2Sim3RdHTeQHzLREr2Hjz/5kkZyCyI3fqGvnD2FuPJ8lsBsLtJxfFFunnA1nCD53h7DE+z4iE/svi4= 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; Thu, 11 Jun 2026 13:31:22 +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; Thu, 11 Jun 2026 13:31:22 +0800 From: Ryan Chen Date: Thu, 11 Jun 2026 13:31:21 +0800 Subject: [PATCH v32 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: <20260611-upstream_i2c-v32-1-b66eba921d01@aspeedtech.com> References: <20260611-upstream_i2c-v32-0-b66eba921d01@aspeedtech.com> In-Reply-To: <20260611-upstream_i2c-v32-0-b66eba921d01@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=1781155882; l=5270; i=ryan_chen@aspeedtech.com; s=20251126; h=from:subject:message-id; bh=ktYoafNweHhZs0ZmbKaHwFTXK1XM2KW9vSEeOcsvt7o=; b=loGxDmNtTBPaGERD3L5/JcMP0GWi/8th6ucJY6PXWTTHgvdJbATFy6WKHEesg10PK/+MLj08g lcF6m0Y/uNXCUx3JtTMQ+LADYDoqloSz4zAGuopDauJC0dqbdSLeD+o 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 +- MAINTAINERS | 1 + 3 files changed, 75 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 diff --git a/MAINTAINERS b/MAINTAINERS index 882214b0e7db..f9c929e86e64 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2596,6 +2596,7 @@ R: Joel Stanley L: linux-i2c@vger.kernel.org L: openbmc@lists.ozlabs.org (moderated for non-subscribers) S: Maintained +F: Documentation/devicetree/bindings/i2c/aspeed,ast2600-i2c.yaml F: Documentation/devicetree/bindings/i2c/aspeed,i2c.yaml F: Documentation/devicetree/bindings/interrupt-controller/aspeed,ast2400-i= 2c-ic.yaml F: drivers/i2c/busses/i2c-aspeed.c --=20 2.34.1 From nobody Thu Jun 11 08:16:04 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 8A13E3644D4; Thu, 11 Jun 2026 05:31:44 +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=1781155908; cv=none; b=oZWMi0VOm9ww8cdfnNK5PJcvtdlJ441yKAeCTxgZ0SdBPobLQ2b6lGMtl9FXSdu8abR0yO7BHBT6PG9FHvP1rC490M5vI59ziq74skR58JVhM5E2Cr+97VJzNJBWX6BkSUXxnKXMsU0aQf1rDR2Tu4A5/ETDLKKVR8tVCj9yWS4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1781155908; c=relaxed/simple; bh=bu5HMc2Z/yj0Ft5OgeLngLhHplEIK0fnHLwW0MpLC2E=; h=From:Date:Subject:MIME-Version:Content-Type:Message-ID:References: In-Reply-To:To:CC; b=Urxm2KExRB/l/Cf3qdCgiSL0I9dtLNhgbB3R6nDxGm6ARqOlHtnCpcj20YJ82Wz9k4i3dRsE8SfpGOXD8S/+PA74wYRTPxrpA2KYUl9Tm1Ps09cJ/g6aaC9l4upYlj2qgKblMqhEWiOeqFX4zOHkMCcD/UcwKofEKzgONblTJVU= 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; Thu, 11 Jun 2026 13:31:22 +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; Thu, 11 Jun 2026 13:31:22 +0800 From: Ryan Chen Date: Thu, 11 Jun 2026 13:31:22 +0800 Subject: [PATCH v32 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: <20260611-upstream_i2c-v32-2-b66eba921d01@aspeedtech.com> References: <20260611-upstream_i2c-v32-0-b66eba921d01@aspeedtech.com> In-Reply-To: <20260611-upstream_i2c-v32-0-b66eba921d01@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=1781155882; l=2076; i=ryan_chen@aspeedtech.com; s=20251126; h=from:subject:message-id; bh=bu5HMc2Z/yj0Ft5OgeLngLhHplEIK0fnHLwW0MpLC2E=; b=HXKCWHwKPDa00Strvo6vZc8oFyJTvdo6B4fYFhi6p9v9h0diXPPb5sVbNbkTA3jNbObmXUL5n Iv5+qWNIYU9DJihtcKgZfBhpHIYx04fHOehH9qf4wZiW05Jx6sEypWb 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 Thu Jun 11 08:16:04 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 A83AE352FA5; Thu, 11 Jun 2026 05:31:40 +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=1781155904; cv=none; b=YpWQ6gptQNSjWcbTaR235LTaf1zD/Bi7wjUZf4gtA52UxQd1prBrX+vMwIGdzxlSgKSuTpFh4f6OEFtvVANxorvgxIK/5yXR7VeseObbtJGrdNe9iyxwr+Vbm7sv/4Y4A91e532TdPxX81ZrQm1y9AnnR8kGzmk3EcK6IoU66+A= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1781155904; c=relaxed/simple; bh=r2T5rQ6TN/II/E6wQiYvjiwSm3+xG1X8ZJATJXCx5iE=; h=From:Date:Subject:MIME-Version:Content-Type:Message-ID:References: In-Reply-To:To:CC; b=A3e+7f2TZQWN/ioN/OYMJ7bBXuQ0g5Y8kOjo2UjLg/70LPFlO2GNccpgGME2VCxp0aLhuEkPl15QDbnWss1WAHOt0JA+Pq1J5qoO/wjlBV4FqBi70iSTgmlpgkCYtirXkQjcJZBmFNZ6HBD9GNYe9bgzD9UsKtLvBRIQpe2N1os= 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; Thu, 11 Jun 2026 13:31:22 +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; Thu, 11 Jun 2026 13:31:22 +0800 From: Ryan Chen Date: Thu, 11 Jun 2026 13:31:23 +0800 Subject: [PATCH v32 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: <20260611-upstream_i2c-v32-3-b66eba921d01@aspeedtech.com> References: <20260611-upstream_i2c-v32-0-b66eba921d01@aspeedtech.com> In-Reply-To: <20260611-upstream_i2c-v32-0-b66eba921d01@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=1781155882; l=3063; i=ryan_chen@aspeedtech.com; s=20251126; h=from:subject:message-id; bh=r2T5rQ6TN/II/E6wQiYvjiwSm3+xG1X8ZJATJXCx5iE=; b=AlS4OvZRWj5Vy4mXQ6/Xq3RTtvL57/E0g/GuLDwWSJ8lmRKiCPDoCyI7hbOtA13jHgnnaYltH Ol0jpJIWHh1Bu0IYUraFf6oaVlERw9QLRXSaR0j8mKBx+Hqv+MgmTbT 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 v32: - Add if/then conditional schema: when aspeed,global-regs is present, require reg to have at least two items. The new driver unconditionally maps resource index 1 (the buffer SRAM region); a DT with one reg entry and aspeed,global-regs passes schema validation but fails probe. The constraint makes the schema consistent with driver behaviour. - Fix binding example to use the correct AST2600 clock header (ast2600-clock.h) and ASPEED_CLK_APB2 instead of the legacy aspeed-clock.h, where index 26 (ASPEED_CLK_APB) maps to the UART5 gate clock on AST2600 rather than the APB2 bus clock used by the I2C controller. 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. --- .../devicetree/bindings/i2c/aspeed,ast2600-i2c.yaml | 19 +++++++++++++++= ++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/i2c/aspeed,ast2600-i2c.yaml = b/Documentation/devicetree/bindings/i2c/aspeed,ast2600-i2c.yaml index abc614315dff..eb6e316c112f 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 @@ -55,19 +61,28 @@ required: - resets - interrupts =20 +if: + required: + - aspeed,global-regs +then: + properties: + reg: + minItems: 2 + unevaluatedProperties: false =20 examples: - | - #include + #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>; + clocks =3D <&syscon ASPEED_CLK_APB2>; resets =3D <&syscon ASPEED_RESET_I2C>; clock-frequency =3D <100000>; interrupts =3D ; + aspeed,global-regs =3D <&i2c_global>; }; --=20 2.34.1 From nobody Thu Jun 11 08:16:04 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 B9270360EC7; Thu, 11 Jun 2026 05:31:48 +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=1781155915; cv=none; b=eXuDMlhJe7yk3YNwl0a9jArKgmKLkPqoOQmmte5S7kmjsMa52U5t8NlXt2IBFB2SbCsVCoh40ejVDEl+sA6Nek9+mOdkqjRTOj3JH10qD6R3nESpz5HxIbf5EG7pa76FJHBr7TWg/gUTZ8buR8vqESYgVPpjjKsukq2KdLi1dA4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1781155915; c=relaxed/simple; bh=aCcGii9xy4Xb714/Y+qgfqWtkYIDh7+xj1QJzJbeTFk=; h=From:Date:Subject:MIME-Version:Content-Type:Message-ID:References: In-Reply-To:To:CC; b=cnCw9TvTvOrdg4CUqFpTn3I2ttorR3bQrtFa9dW6cwCIq5pTTG0Fqdm0uJTVJAKf+L4Rr6x9736AhaENwGeNWjU+CwYDYqAXqrx44sfxI2mKtscvRd3yMRHENRQ9kFDdJBdLF+U0ISRHsk3JKid0WQ+aOvrGpr1l++/5ObD71lM= 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; Thu, 11 Jun 2026 13:31:22 +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; Thu, 11 Jun 2026 13:31:22 +0800 From: Ryan Chen Date: Thu, 11 Jun 2026 13:31:24 +0800 Subject: [PATCH v32 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: <20260611-upstream_i2c-v32-4-b66eba921d01@aspeedtech.com> References: <20260611-upstream_i2c-v32-0-b66eba921d01@aspeedtech.com> In-Reply-To: <20260611-upstream_i2c-v32-0-b66eba921d01@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=1781155882; l=41753; i=ryan_chen@aspeedtech.com; s=20251126; h=from:subject:message-id; bh=aCcGii9xy4Xb714/Y+qgfqWtkYIDh7+xj1QJzJbeTFk=; b=7itOAAIjgikyoe5NIfE5uQA4lvGqjohP9Oh6Bhl3LvYWLmetjbkaHeoBYbc45H7pCPJRUyF0z S/96UyzKWRkA9VQ4YIUtwBcWs8qjI3YMnPYONQodE/JhThP8u67j73D 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 v32: - Add MAINTAINERS entry for drivers/i2c/busses/i2c-ast2600.c so that get_maintainer.pl correctly identifies the maintainer when patches touch this file. - Fix interrupt storm: clear PKT_DONE in the IRQ handler when msgs is NULL; per the AST2600 datasheet, clearing PKT_DONE (bit 16) automatically clears all associated status bits [6:0], [15:13] and [18:17] so no extra writes to the ISR are needed. - Fix out-of-bounds access: guard msgs_index against msgs_count before indexing the msgs array in ast2600_i2c_controller_packet_irq(). - Fix use-after-free: use WRITE_ONCE() to null msgs before calling complete() in all IRQ completion paths so trailing IRQs bail out immediately instead of dereferencing freed memory. - Fix race condition in timeout path: null msgs before re-enabling IER so that a late IRQ cannot access the caller's freed message buffer. - Fix 0-length SMBus block read hanging the bus: issue a standalone STOP via CONTROLLER_TRIGGER_LAST_STOP, set stop_pending, and poll for NORMAL_STOP from process context in ast2600_i2c_wait_stop(). - Initialize clk_div_reg to I2CCG_DIV_CTRL (hardware reset default) and global_ctrl to 0 to avoid using uninitialized values if regmap_read() fails. - Guard against clock-frequency =3D <0> in DT; i2c_parse_fw_timings() does not reject an explicit zero, which would cause a divide-by-zero in ast2600_i2c_ac_timing_config(); default to 100 kHz in that case. - Remove AST2600_I2CM_BUS_RECOVER_FAIL from IER writes; per the AST2600 datasheet, bit 15 is Reserved in I2CM10 (IER) and only exists as a status bit in I2CM14 (ISR). 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(). --- MAINTAINERS | 1 + drivers/i2c/busses/Makefile | 2 +- drivers/i2c/busses/i2c-aspeed.c | 5 + drivers/i2c/busses/i2c-ast2600.c | 931 +++++++++++++++++++++++++++++++++++= ++++ 4 files changed, 938 insertions(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index f9c929e86e64..2a6748cee946 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2600,6 +2600,7 @@ F: Documentation/devicetree/bindings/i2c/aspeed,ast26= 00-i2c.yaml F: Documentation/devicetree/bindings/i2c/aspeed,i2c.yaml F: Documentation/devicetree/bindings/interrupt-controller/aspeed,ast2400-i= 2c-ic.yaml F: drivers/i2c/busses/i2c-aspeed.c +F: drivers/i2c/busses/i2c-ast2600.c F: drivers/irqchip/irq-aspeed-i2c-ic.c =20 ARM/ASPEED MACHINE SUPPORT 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..70f37dc77468 --- /dev/null +++ b/drivers/i2c/busses/i2c-ast2600.c @@ -0,0 +1,931 @@ +// 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 + +#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; + bool stop_pending; + 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 =3D I2CCG_DIV_CTRL; + 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 int ast2600_i2c_wait_stop(struct ast2600_i2c_bus *i2c_bus) +{ + u32 sts; + int ret; + + ret =3D readl_poll_timeout(i2c_bus->reg_base + AST2600_I2CM_ISR, sts, + sts & AST2600_I2CM_NORMAL_STOP, 1000, 10000); + if (ret) + return ret; + + writel(AST2600_I2CM_NORMAL_STOP, i2c_bus->reg_base + AST2600_I2CM_ISR); + + return 0; +} + +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) { + writel(AST2600_I2CM_PKT_DONE, i2c_bus->reg_base + AST2600_I2CM_ISR); + return; + } + if (i2c_bus->msgs_index >=3D i2c_bus->msgs_count) { + writel(AST2600_I2CM_PKT_DONE, i2c_bus->reg_base + AST2600_I2CM_ISR); + 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: + WRITE_ONCE(i2c_bus->msgs, NULL); + 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: + WRITE_ONCE(i2c_bus->msgs, NULL); + 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)) { + WRITE_ONCE(i2c_bus->msgs, NULL); + i2c_bus->cmd_err =3D -EBUSY; + complete(&i2c_bus->cmd_complete); + } + } else { + WRITE_ONCE(i2c_bus->msgs, NULL); + 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) { + WRITE_ONCE(i2c_bus->msgs, NULL); + i2c_bus->cmd_err =3D i2c_bus->msgs_index; + complete(&i2c_bus->cmd_complete); + } else { + if (ast2600_i2c_do_start(i2c_bus)) { + WRITE_ONCE(i2c_bus->msgs, NULL); + 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) { + /* + * Workaround: a standalone STOP triggers NORMAL_STOP in + * ISR without generating another IRQ, so poll for it from + * process context. + */ + i2c_bus->controller_xfer_cnt =3D msg->len; + WRITE_ONCE(i2c_bus->stop_pending, true); + writel(CONTROLLER_TRIGGER_LAST_STOP, + i2c_bus->reg_base + AST2600_I2CM_CMD_STS); + + WRITE_ONCE(i2c_bus->msgs, NULL); + i2c_bus->cmd_err =3D -EPROTO; + complete(&i2c_bus->cmd_complete); + break; + } + 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) { + WRITE_ONCE(i2c_bus->msgs, NULL); + i2c_bus->cmd_err =3D i2c_bus->msgs_index; + complete(&i2c_bus->cmd_complete); + } else { + if (ast2600_i2c_do_start(i2c_bus)) { + WRITE_ONCE(i2c_bus->msgs, NULL); + i2c_bus->cmd_err =3D -EBUSY; + complete(&i2c_bus->cmd_complete); + } + } + } else if (ast2600_i2c_setup_buff_rx(0, i2c_bus)) { + WRITE_ONCE(i2c_bus->msgs, NULL); + 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); + WRITE_ONCE(i2c_bus->msgs, NULL); + 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); + WRITE_ONCE(i2c_bus->msgs, NULL); + 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) { + WRITE_ONCE(i2c_bus->msgs, NULL); + 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; + WRITE_ONCE(i2c_bus->stop_pending, false); + 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); + + WRITE_ONCE(i2c_bus->msgs, NULL); + 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; + } + + if (READ_ONCE(i2c_bus->stop_pending)) { + int stop_ret; + + stop_ret =3D ast2600_i2c_wait_stop(i2c_bus); + WRITE_ONCE(i2c_bus->stop_pending, false); + WRITE_ONCE(i2c_bus->msgs, NULL); + if (stop_ret) { + i2c_bus->cmd_err =3D stop_ret; + ret =3D stop_ret; + } + } + + 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 =3D 0; + 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); + if (!i2c_bus->timing_info.bus_freq_hz) { + dev_warn(dev, "invalid clock-frequency 0, using default 100kHz\n"); + i2c_bus->timing_info.bus_freq_hz =3D I2C_MAX_STANDARD_MODE_FREQ; + } + + /* 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 Thu Jun 11 08:16:04 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 B065B33263B; Thu, 11 Jun 2026 05:31:55 +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=1781155922; cv=none; b=L0ijR250bih0dHJMhjmloxpPIwzs+ECu9dJ7lJqV5Oan7sEMQkdzHNJwJCznEVqzrJxaifJ4Nvi2CdYSJH9/tCYdSfp5D1NcaDDlnPWxiecMBkGViZ9qdv2NI8VUF0eSrt1e5gLiemlC/5TzN4cMwvMtIusAUiVfKQW28ffdf9M= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1781155922; c=relaxed/simple; bh=npiiI9Zmbv87UuwKAkL7Ed+1nleBYWdl9HM4z6KU3kk=; h=From:Date:Subject:MIME-Version:Content-Type:Message-ID:References: In-Reply-To:To:CC; b=JZBR0ovx0r8S9tN+ddswgugsvJBYXeNiD2LYI94gTLyhqfMb+QriLi3iX8LWQtDiRi6j+wkp0JWKYBgziJo0Icn/1zRFzg75Ozo/AsoBWOyG90Hc2/7l3jFtn5EhaZJxQLXUZwPMGY+FR9FAIRSlsUW/0bgEAwhtLtY4WjxKp84= 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; Thu, 11 Jun 2026 13:31:22 +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; Thu, 11 Jun 2026 13:31:22 +0800 From: Ryan Chen Date: Thu, 11 Jun 2026 13:31:25 +0800 Subject: [PATCH v32 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: <20260611-upstream_i2c-v32-5-b66eba921d01@aspeedtech.com> References: <20260611-upstream_i2c-v32-0-b66eba921d01@aspeedtech.com> In-Reply-To: <20260611-upstream_i2c-v32-0-b66eba921d01@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=1781155882; l=23148; i=ryan_chen@aspeedtech.com; s=20251126; h=from:subject:message-id; bh=npiiI9Zmbv87UuwKAkL7Ed+1nleBYWdl9HM4z6KU3kk=; b=BHPN/I5Q1pdUt/2RgNpYwwi/i+Z56QMx08pmym8qDE/+LIuOaKLFaubr4YeBN2LX2/67JBLq4 2XM9WujfCGZAxZAvAxWNjp4f24bDmGuHx7YL+gtqSGHY6hytHiXZbmL 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 v32: - Fix target RX data loss in the master-abort fast path: remove the BUFF_CTRL zeroing that preceded the controller IER clear. The shared BUFF_CTRL register holds the pending target RX length in bits [29:24]; zeroing it before the switch statement causes target_rx_len to read back as 0, silently discarding all bytes already received by the target. - Fix use-after-free in master-abort fast path: null msgs and re-enable the controller IER before calling complete(), not after. Calling complete() first allows process context to immediately start a new i2c_transfer() and install a new msgs pointer; a stale controller IRQ firing during the subsequent IER restore would then dereference the new transfer's msgs buffer. - Fix shared-buffer corruption on coalesced STOP+SLAVE_MATCH IRQ: restore the SLAVE_PENDING guard on the target_active =3D false transition. SLAVE_PENDING (bit 29) is set precisely when a new address-match is already queued while the previous DMA receive has not yet been processed. Clearing target_active unconditionally on STOP in that case lets the controller side overwrite the shared Tx/Rx buffer before the new target transaction has been re-armed. - Use READ_ONCE() for all process-context reads of target_active. The IRQ path writes target_active with WRITE_ONCE(); plain loads in process context allow the compiler to cache the value across the IER-disable window, potentially seeing a stale false and starting a controller transfer that races with an active target transaction. 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 70f37dc77468..aace0c7eeb72 100644 --- a/drivers/i2c/busses/i2c-ast2600.c +++ b/drivers/i2c/busses/i2c-ast2600.c @@ -255,6 +255,11 @@ struct ast2600_i2c_bus { bool multi_master; bool stop_pending; 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) @@ -348,6 +353,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; + WRITE_ONCE(i2c_bus->msgs, NULL); + writel(AST2600_I2CM_PKT_DONE | AST2600_I2CM_BUS_RECOVER, + i2c_bus->reg_base + AST2600_I2CM_IER); + complete(&i2c_bus->cmd_complete); + } + + /* 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) && !(sts & AST2600_I2CS_SLAVE_PENDING)) + 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]; @@ -559,6 +811,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)); @@ -666,6 +932,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 @@ -682,6 +954,21 @@ static int ast2600_i2c_controller_xfer(struct i2c_adap= ter *adap, struct i2c_msg return ret; } =20 +#if IS_ENABLED(CONFIG_I2C_SLAVE) + if (READ_ONCE(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) || READ_ONCE(i2c_bus->tar= get_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; @@ -689,6 +976,10 @@ static int ast2600_i2c_controller_xfer(struct i2c_adap= ter *adap, struct i2c_msg WRITE_ONCE(i2c_bus->stop_pending, false); 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); @@ -717,6 +1008,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) + !READ_ONCE(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); @@ -765,8 +1059,66 @@ 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) + 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) { @@ -776,6 +1128,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 { @@ -819,6 +1175,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