From patchwork Thu Sep 10 00:15:37 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: James Hilliard X-Patchwork-Id: 3256 Return-Path: X-Original-To: noreply@patchwork.local Delivered-To: noreply@patchwork.local Received: from tor.lore.kernel.org (tor.lore.kernel.org [172.105.105.114]) by mxe881.netcup.net (Postfix) with ESMTPS id 73D101C02DE for ; Thu, 10 Sep 2026 02:16:00 +0200 (CEST) Authentication-Results: mxe881; dkim=pass header.d=gmail.com; spf=pass (sender IP is 172.105.105.114) smtp.mailfrom=linux-sunxi+bounces-25759-noreply=patchwork.local@lists.linux.dev smtp.helo=tor.lore.kernel.org Received-SPF: pass (mxe881: domain of lists.linux.dev designates 172.105.105.114 as permitted sender) client-ip=172.105.105.114; envelope-from=linux-sunxi+bounces-25759-noreply=patchwork.local@lists.linux.dev; helo=tor.lore.kernel.org; Received: from smtp.subspace.kernel.org (conduit.subspace.kernel.org [100.90.174.1]) by tor.lore.kernel.org (Postfix) with ESMTP id DECFD364DE for ; Thu, 10 Sep 2026 00:15:48 +0000 (UTC) Received: from localhost.localdomain (localhost.localdomain [127.0.0.1]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 75EC523EAB0; Thu, 10 Sep 2026 00:15:47 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="As3I9L1B" X-Original-To: linux-sunxi@lists.linux.dev Received: from mail-ot1-f48.google.com (mail-ot1-f48.google.com [209.85.210.48]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 382E429D294 for ; Thu, 10 Sep 2026 00:15:45 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.210.48 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788999347; cv=none; b=MGFI3xxQk6sg1o7D9kce/zqwpeMLALXP5dLtwtwANjuxXhZNffy38niLZzgLpct70vqVFm878zlVZBOWpwozd+YGf2o/Ih8Gn6tSUYJC7MWPvt9H8JrVV7XqKdWHylkWihiM2SaKQ5UWsGtMXU+c4wLRyPk86AVU3MECWpGIZZA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788999347; c=relaxed/simple; bh=xLPiA0DBmtmDhP4JFN6i0AdhaVLCSoeCypLXVz9mbq0=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=Qt5ROWJ7VXJjKMMo+2TLTC0HqS+dWgKE+SFdpXOyNVsxXGmU0XQn6YvW+xBFq6qcZbuDm0r13f1r114SIw8q9MgxPFvQLaavJaAXstvncYzfMi5otQbbl5WtU7L6Z6o/wKz1KkmZKS+qOsLVTBvK78pBa/uuMwdinfuLEdmJ8cY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=As3I9L1B; arc=none smtp.client-ip=209.85.210.48 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Received: by mail-ot1-f48.google.com with SMTP id 46e09a7af769-7ffa69b7462so2351637a34.3 for ; Wed, 09 Sep 2026 17:15:45 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1788999344; x=1789604144; darn=lists.linux.dev; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=rHq53JNthhgN14GwB6JcJHW5rTqBpPDjiEjix8XiDJU=; b=As3I9L1BEzQmIPXEM79IXWoJaO6I+wamH7zbfH1mv7l45l03NhfeLI4qPQihVv75OK 88Q7uzk+b1u0TAAMRTdssJPIti3mti085SEC1deeTbCBrwye4FjNZ3gqap6AWkxj7sAY c1Fgwf9w+99Xntbon8cGdW+x/Nfw1adqD5kUFD9RFzRg5sUvTMvwcFN2S2asGQ4uivYq bvNmfBjhKab+nDO7nHhHlWGBTFz7/7oyf5Fjn+t3vTYnJiWTCmKOHgXhn+vfNQkgB7+C C7qypTZ0gfi8mzcXQRIl7vamxfSmxx34h8QyJNinsVIeVf4mNL539ypsGzSenZm1pfxM L+8w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788999344; x=1789604144; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=rHq53JNthhgN14GwB6JcJHW5rTqBpPDjiEjix8XiDJU=; b=W4dfU3yTq0xTRL1EkUG6ifjI/ONBYwJNM7U2XZgiG/0IY/kExpSJEYhlW8Vvx+HECO SsoDYU0lZYKhrw6EYz3AcxQYDn+q85Vm/ckJLOjpLZAM31KMzpEc5lOT/szrTSeF+F09 eNfTpD86vpqpRGZ28CBj4aN8ZbM0aM32Y2cbJ9f3Oip9JfJolzsfrqm+32I98S/a609l tzPctDFUzItlKF2DPsek7S/Jwut0Dd6kCHzze7sHAf8K/hhPm5ScFCiyAHxi58q0E2yt 5BZAC41SlyVj0/EFWODKMoUWvE7cAmnKrg8Y3k1nZ77FU6+DAuIenbPWZ1YfLRa0naZ5 VKng== X-Forwarded-Encrypted: i=1; AKwUvBxJfLJ0dfqZhUIsaXK3nXNQlWBkSgFDvXf5ioUPZ07UWCLz0d7UVOhekKoBMied0or2N1APGM9RsXJvFg==@lists.linux.dev X-Gm-Message-State: AFuF++ntWbhrYtIKZehyuH2RE7MVKmGuiEE7HoIrnLBkXn7X69wGVqVd l+Tx5vyCL8w74FxlIEgzFrSplPYxqptY95VWhleuWpFhoX18/CKWZ2a3 X-Gm-Gg: AYBFou0FM6RNMUCvTo62+Qq991hITyMIAcHsAsOH8XZZygvHbRmZL+Mo0mpn3CjGzJr oSo1QR4QAKfzcbRshha8yX4pWx8i53z4JrFVV0umix5r4zvxQhTGYFfrdcR2QL/r1L33DuH0Xhx 2+Q2rL1+pi4SGRWc07qppQdC2f9bDiv0ndgKIg5zm9YU3BkOtz14NMJdg8shvIn5fEDh59vKzWM nuBqtn4bGMGgFxtaB0xQUc7L5wt+SZzgmDesvQDFpjZv7GdSNIslA0HzZx5eJVlJ8/01zjV4j6A p/jUhN06O6v1TXwkHRezR4zH1H6nRsnP1ctBOyL32mzyJ+hQnfuvaFGfrDxXnc2+zX6NPdRhtrw N2Ygz1kz9GDDz8qZcZqCBfWbknAp4M2rfzwShmL3Nu/oBsjibgf9HaG0aFkWYiyI6y/6mwBIRmk YaZ4xjOFY1pctTSixW4qmBzR4ZJp8VhLl5aDO3UoH9eO1+QqeAdQlNSj7TQ9cBIRGpLo0J9G/IQ 8ufoaHazgZpLWjsOfX7WtmvCaX4Pfb1nvwA2OMlTIKBBdvr7j7103fnnANx8J2NkuHNNtTHuewV YQMRWOtjubdmcbTO64iXmUWiHkOm8K/6w+T4glQ738ImIpo8cu0PJtBSq/Pa/tLApQJ3ThZC8UC 5kd4OvMbt7CtPieXiafKoDpEbRo0VgQ== X-Received: by 2002:a05:6830:348f:b0:7fa:ac4f:78e with SMTP id 46e09a7af769-7faac4f0e60mr20603797a34.26.1788999344089; Wed, 09 Sep 2026 17:15:44 -0700 (PDT) Received: from [127.0.1.1] (184-96-151-165.hlrn.qwest.net. [184.96.151.165]) by smtp.gmail.com with ESMTPSA id 46e09a7af769-7fc68d5ae2bsm12468402a34.3.2026.09.09.17.15.42 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 09 Sep 2026 17:15:43 -0700 (PDT) From: James Hilliard Date: Wed, 09 Sep 2026 18:15:37 -0600 Subject: [PATCH net-next v11 1/2] dt-bindings: net: x-powers: add AC200/AC300 EPHY packages Precedence: bulk X-Mailing-List: linux-sunxi@lists.linux.dev List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20260909-submit-acx00-of-dynamic-v1-v11-1-eb45e89ef918@gmail.com> References: <20260909-submit-acx00-of-dynamic-v1-v11-0-eb45e89ef918@gmail.com> In-Reply-To: <20260909-submit-acx00-of-dynamic-v1-v11-0-eb45e89ef918@gmail.com> To: Andrew Lunn , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Andrew Lunn , Heiner Kallweit , Russell King , Rob Herring , Krzysztof Kozlowski , Conor Dooley , James Hilliard Cc: Arnd Bergmann , "Jagielski, Jedrzej" , Andre Przywara , Chen-Yu Tsai , =?utf-8?q?Jernej_=C5=A0krabec?= , Maxime Chevallier , linux-sunxi@lists.linux.dev, netdev@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Krzysztof Kozlowski X-Mailer: b4 0.15.2 X-MORS-Enabled: yes X-MORS-DOMAIN: patchwork.local X-MORS-HOSTING: hosting172546 X-MORS-USER: hosting172546 X-getmail-retrieved-from-mailbox: =?utf-8?q?INBOX?= The AC200 and AC300 contain compatible Fast Ethernet link PHYs which report the same Clause 22 identifier and use the same link-side register layout. Their package control paths differ: AC200 control registers are accessed through the I2C register interface of the companion IC, while the AC300 control range is at a second Clause 22 address fixed at 16 plus the link address. Describe both variants as Ethernet PHY packages. The package reg value is the link PHY base address and the child supplies the documented PHY ID, because the identification registers cannot be read until package setup has completed. The AC300 driver reaches its control range with the PHY package offset helpers. The AC200 package instead references the companion IC described by the separately submitted x-powers,ac200 MFD binding. Its I2C interface provides access to the package controls. Give fixed hardware variant-specific compatibles. Also provide an ACx00 compatible for systems which can contain either package, with one packed NVMEM field containing calibration in bits 3 through 0, the AC300 selector in bit 8 and its low-calibration selector in bit 9. Such a description contains the potential resources for both backends; the driver consumes only those selected by the configuration field. Document the little-endian encoding and two-byte full configuration, while allowing a one-byte calibration value for fixed AC200 packages. Describe the package clock, supply and optional xMII receive-clock inversion needed by some integrations. A fixed AC200 package may omit the NVMEM field and use its internal calibration copy. Limit both the package and child link-PHY addresses in AC300-capable descriptions to 0 through 7 so the control address remains in the documented range 16 through 23. Signed-off-by: James Hilliard Reviewed-by: Krzysztof Kozlowski --- .../bindings/net/x-powers,acx00-ephy-package.yaml | 170 +++++++++++++++++++++ 1 file changed, 170 insertions(+) diff --git a/Documentation/devicetree/bindings/net/x-powers,acx00-ephy-package.yaml b/Documentation/devicetree/bindings/net/x-powers,acx00-ephy-package.yaml new file mode 100644 index 000000000000..781695d9d9f1 --- /dev/null +++ b/Documentation/devicetree/bindings/net/x-powers,acx00-ephy-package.yaml @@ -0,0 +1,170 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/net/x-powers,acx00-ephy-package.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: X-Powers AC200 and AC300 Ethernet PHY packages + +maintainers: + - James Hilliard + +description: + The AC200 and AC300 contain compatible Fast Ethernet PHY blocks which report + the same Clause 22 PHY ID and use the same link-side register layout. The + package control paths differ. AC200 package control registers are accessed + through the companion IC's I2C register interface, while AC300 control + registers occupy a second Clause 22 address fixed at 16 plus the link PHY + address. + + Systems with a known package use the corresponding AC200 or AC300 compatible. + Systems which can contain either package use the ACx00 compatible and a + packed NVMEM configuration field to select the backend at runtime. + + The link PHY cannot report its ID until the package control sequence has + completed, so its child node supplies the documented PHY ID. The package and + child use the same base address. The AC200 driver programs that address into + the package, while the AC300 driver derives its control address from it. + +properties: + compatible: + enum: + - x-powers,ac200-ephy-package + - x-powers,ac300-ephy-package + - x-powers,acx00-ephy-package + + reg: + maximum: 31 + + clocks: + maxItems: 1 + description: AC300 input clock, running at 24, 25, or 27 MHz + + vcc-supply: + description: + 3.3 V package supply. This is connected to EPHY_VCC on AC200 and VCC1 + on AC300. + + nvmem-cells: + maxItems: 1 + description: + Packed little-endian EPHY configuration value, with the least-significant + byte first. Bits 3 through 0 contain the analog calibration code, bits 7 + through 4 are reserved, bit 8 selects AC300 when set and AC200 when clear, + and bit 9 requests the AC300 low-calibration tuning sequence. Bits 15 + through 10 are reserved. + + AC300 and selectable ACx00 packages require the full configuration + field, including bits 8 and 9 even when their values are zero, returned + as two bytes. A fixed AC200 package may provide a one-byte calibration + value or a two-byte configuration with bit 8 clear, or omit the cell + and use its internal eFuse calibration copy. + + nvmem-cell-names: + const: configuration + + x-powers,ac200: + $ref: /schemas/types.yaml#/definitions/phandle + description: + Phandle to the AC200 companion IC described by the x-powers,ac200 MFD + binding. Its I2C interface provides access to the package control + registers used to power and configure the Ethernet PHY. + + x-powers,xmii-rx-clock-inverted: + type: boolean + description: + Invert the xMII receive clock inside the PHY. This is required by some + SoC integrations, including the Allwinner H6 AC200 connection. + +patternProperties: + ^ethernet-phy@[a-f0-9]+$: + $ref: ethernet-phy.yaml# + properties: + compatible: + const: ethernet-phy-id0044.1400 + required: + - compatible + unevaluatedProperties: false + +required: + - compatible + - vcc-supply + +dependentRequired: + nvmem-cells: [ nvmem-cell-names ] + +allOf: + - $ref: ethernet-phy-package.yaml# + - if: + properties: + compatible: + const: x-powers,ac200-ephy-package + then: + properties: + clocks: false + required: + - x-powers,ac200 + - if: + properties: + compatible: + const: x-powers,ac300-ephy-package + then: + properties: + x-powers,ac200: false + required: + - clocks + - nvmem-cells + - nvmem-cell-names + - if: + properties: + compatible: + const: x-powers,acx00-ephy-package + then: + required: + - clocks + - nvmem-cells + - nvmem-cell-names + - x-powers,ac200 + - if: + properties: + compatible: + enum: + - x-powers,ac300-ephy-package + - x-powers,acx00-ephy-package + then: + properties: + reg: + maximum: 7 + patternProperties: + ^ethernet-phy@[a-f0-9]+$: + properties: + reg: + maximum: 7 + +unevaluatedProperties: false + +examples: + - | + mdio { + #address-cells = <1>; + #size-cells = <0>; + + ethernet-phy-package@0 { + compatible = "x-powers,acx00-ephy-package"; + reg = <0>; + #address-cells = <1>; + #size-cells = <0>; + clocks = <&pwm 5>; + vcc-supply = <®_3v3>; + nvmem-cells = <&ephy_configuration>; + nvmem-cell-names = "configuration"; + x-powers,ac200 = <&ac200>; + + ethernet-phy@0 { + compatible = "ethernet-phy-id0044.1400"; + reg = <0>; + }; + }; + }; + +... From patchwork Thu Sep 10 00:15:38 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: James Hilliard X-Patchwork-Id: 3257 Return-Path: X-Original-To: noreply@patchwork.local Delivered-To: noreply@patchwork.local Received: from sea.lore.kernel.org (sea.lore.kernel.org [172.234.253.10]) by mxe881.netcup.net (Postfix) with ESMTPS id 0FBBD1C02DE for ; Thu, 10 Sep 2026 02:18:53 +0200 (CEST) Authentication-Results: mxe881; dkim=pass header.d=gmail.com; spf=pass (sender IP is 172.234.253.10) smtp.mailfrom=linux-sunxi+bounces-25760-noreply=patchwork.local@lists.linux.dev smtp.helo=sea.lore.kernel.org Received-SPF: pass (mxe881: domain of lists.linux.dev designates 172.234.253.10 as permitted sender) client-ip=172.234.253.10; envelope-from=linux-sunxi+bounces-25760-noreply=patchwork.local@lists.linux.dev; helo=sea.lore.kernel.org; Received: from smtp.subspace.kernel.org (conduit.subspace.kernel.org [100.90.174.1]) by sea.lore.kernel.org (Postfix) with ESMTP id A88856005C for ; Thu, 10 Sep 2026 00:15:52 +0000 (UTC) Received: from localhost.localdomain (localhost.localdomain [127.0.0.1]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 7BFB029BDBB; Thu, 10 Sep 2026 00:15:52 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="rLYYkz+1" X-Original-To: linux-sunxi@lists.linux.dev Received: from mail-ot1-f42.google.com (mail-ot1-f42.google.com [209.85.210.42]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 6B8282E2DD2 for ; Thu, 10 Sep 2026 00:15:47 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.210.42 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788999352; cv=none; b=LXjP0nSWOzqT6LvAP+4KPO6OmRG7haxSROHnZnK2H+kNJcJqK6IiwSH5mm0ZjiC3lojKh92ZsJCRwWEJaA3+SNHIag3JRJBi4YQn+hVRYRVGzOmEWQM5bKkykRkSvnIkfi/5wq92oJwQH5bGLAYb29FvAGkI6w8S7C5kQR9XnHQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788999352; c=relaxed/simple; bh=WeqkTTAnDHhndr2fBWczz2f9+0RGY67Fv6aV72bRtIA=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=asTuPpfvNti28r1DQA2qwBxpKuJSsEtdpANsmFAMUZHLOEePWmNVTzkaJfzuaF9EUDNhOREu8g/ey8SjH/5cj8qHn+p9F9GE5EPl3uj8J2ffvuCgNtP7+v3Ja5GbvoCcPfrJQhEQg2lfloFocdT6ZADFO4ALfs4iUj/DT/TT+gY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=rLYYkz+1; arc=none smtp.client-ip=209.85.210.42 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Received: by mail-ot1-f42.google.com with SMTP id 46e09a7af769-7f6617c7536so3024470a34.1 for ; Wed, 09 Sep 2026 17:15:47 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1788999347; x=1789604147; darn=lists.linux.dev; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=x5sh/kqZCtSihkwagj6kVz9Or2et6p32a21gtsMings=; b=rLYYkz+1DPkVBF4QxjlMlZEdPMKgxqf006klHXp3Iy1XknkGMjfmNajdt+fz+eX8gI jb4gfYqdHIZ3zeSDJTSJkX2CzrnEVzSUDMRQvu1U9/VB/ASXo3LYLdUe4BK5jUiCHKb9 qkdcS5Z9B77bBH67XKdOXeBMh5asFFJt+DNwOV+dWyVa60l1YUjxyY64tGKqwm7TZXAk +A6Hry0X3XRSi/FB98IBDFlFpQOTdgcqHgOfpMPDGYRlOzmrJErAOxWbsBbrqlNZu3T3 w4uWMCIhTrc0keKPUiHdwD2P/HpbO05XwcDaubhIYy584XhYyI8vo/PiT480fjoO4R4q jZ5Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788999347; x=1789604147; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=x5sh/kqZCtSihkwagj6kVz9Or2et6p32a21gtsMings=; b=jSmUuPZg9JniI0g9b9OZSLQnNMTw2f0jnlq/PA8htI8x5f5MPR5xUg/m9w1cZEL5Yz vv1ZM+Mb7wlNHli6OStO11p7aRbXMmoTwtomkVmiXc2JzLj0eH6ac+KZgUOZEURdUhNo D3nINK0bLx8CF+1OLhON6Q+ENu6/8PyjHDJPdjKqZYhi0Wl5RwRYEIxoDq1GIn1U01D5 3EnHPMRgJ1NK+z5irPcqK48tY3RVLBlGyrdEiow1yMkOcmZsTOM26pyckmNV46RUP9/c eVCnZwLAifCSI1cEXmjmZ+d7tsPRZZsk5Fn2EDS5GLXfKF5c1xN8oOfMPq3WcjTcG76w B3iQ== X-Forwarded-Encrypted: i=1; AKwUvByiJty5FfhUx5LoK7hX8hdda0Hk6Btw/zWZVR3bkYi9XPtg/z7WAVYSluPThMg78Q54eqqikvyJjcPsdQ==@lists.linux.dev X-Gm-Message-State: AFuF++lrOxa1z6QNHCV01y3db9TY0SkBz6r2dctHmLqDRwoE9wOmt5Ww h0seYPfhCT/2IanpcJxjuT5dXdUL+RXpb5ERp5VtVoCNPk217e0SCnqS X-Gm-Gg: AYBFou0sbYo1NXqbcQfApmcPS59jwMybItQktF0uanAIrUJnV6anqklfcWWKYbvL8xc qqJqmuFzR1odLLZZ+halCYqeDMpobko4hQXTMXSrzwBavUqBbARBFQn0td0jpgRNSQJWNkbc3nt ItnwlpEgWIVKVzHQUWkBCtmhtFfEdPQrQQOzwBRcpx56WdZLIGGMnw3GSdCXc6wZ2rHCM/2q9AL 7FNZuZObTr8uoYBGU3bItx6kCJdOH6TXyJEW0twIeSZjiuI3HeH/moO47QgV/+BsdreaPzbfF7h i+mGgMRNNFA6danFVwptl89zkI6Zo8J3fQOQ1+6xajW/BDgMDlSfhIgm1NAD8quBaXyLUa54TaG TJHSdKt2veti0pbdWA4nd6eMrhM+Il0vbKh8lNkAKUs6MH/7W5vbfJ5Vq+1+xV7NenM2Rfog3c5 SwN2SgrgH7dD1p8WEHXHYRAiKTGF9Be9JxITGZq6YRK/36LptjbEePzqMWPlxgc9Y1j7z084vGi +lxAI54mCOVNxCfbrCEdh7op1LAMkT7TB0u1csOJQb8jUaRbY+Le7BotHyRHmAyP8oOS2g4d60P fdP/irvLGgV7/OoFVg5vozRxU4PGYzS+dNELEs9IogmMEBDEHTm/iFdzga2xp4xMTouWIBV8X0D NlNvUt9hNU5ha1JmcNprZ0zyyFhUN X-Received: by 2002:a05:6830:2114:b0:7f4:eb76:8820 with SMTP id 46e09a7af769-7fa20de5ccfmr23092969a34.13.1788999346753; Wed, 09 Sep 2026 17:15:46 -0700 (PDT) Received: from [127.0.1.1] (184-96-151-165.hlrn.qwest.net. [184.96.151.165]) by smtp.gmail.com with ESMTPSA id 46e09a7af769-7fc68d5ae2bsm12468402a34.3.2026.09.09.17.15.44 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 09 Sep 2026 17:15:45 -0700 (PDT) From: James Hilliard Date: Wed, 09 Sep 2026 18:15:38 -0600 Subject: [PATCH net-next v11 2/2] net: phy: add X-Powers AC200/AC300 EPHY driver Precedence: bulk X-Mailing-List: linux-sunxi@lists.linux.dev List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20260909-submit-acx00-of-dynamic-v1-v11-2-eb45e89ef918@gmail.com> References: <20260909-submit-acx00-of-dynamic-v1-v11-0-eb45e89ef918@gmail.com> In-Reply-To: <20260909-submit-acx00-of-dynamic-v1-v11-0-eb45e89ef918@gmail.com> To: Andrew Lunn , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Andrew Lunn , Heiner Kallweit , Russell King , Rob Herring , Krzysztof Kozlowski , Conor Dooley , James Hilliard Cc: Arnd Bergmann , "Jagielski, Jedrzej" , Andre Przywara , Chen-Yu Tsai , =?utf-8?q?Jernej_=C5=A0krabec?= , Maxime Chevallier , linux-sunxi@lists.linux.dev, netdev@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org X-Mailer: b4 0.15.2 X-Rspamd-Server: rspamd-worker-8404 X-Spamd-Result: default: False [-1.16 / 15.00]; BAYES_HAM(-5.50)[100.00%]; RBL_SENDERSCORE(2.00)[172.234.253.10:from]; SUSPICIOUS_RECIPS(1.50)[]; DMARC_POLICY_SOFTFAIL(1.00)[gmail.com : SPF not aligned (relaxed), No valid DKIM,none]; MAILLIST(-0.15)[generic]; MIME_GOOD(-0.10)[text/plain]; BAD_REP_POLICIES(0.10)[]; HAS_LIST_UNSUB(-0.01)[]; ARC_ALLOW(0.00)[subspace.kernel.org:s=arc-20240116:i=1]; RCPT_COUNT_TWELVE(0.00)[22]; FROM_HAS_DN(0.00)[]; PRECEDENCE_BULK(0.00)[]; TAGGED_RCPT(0.00)[netdev,dt]; FORGED_SENDER_MAILLIST(0.00)[]; DBL_BLOCKED_OPENRESOLVER(0.00)[sea.lore.kernel.org:rdns,sea.lore.kernel.org:helo,arm.com:email]; FREEMAIL_CC(0.00)[arndb.de,intel.com,arm.com,kernel.org,gmail.com,bootlin.com,lists.linux.dev,vger.kernel.org]; RCVD_VIA_SMTP_AUTH(0.00)[]; RCVD_COUNT_FIVE(0.00)[6]; ASN(0.00)[asn:63949, ipnet:172.234.224.0/19, country:SG]; FREEMAIL_FROM(0.00)[gmail.com]; R_SPF_ALLOW(0.00)[+ip4:172.234.253.10]; TO_DN_SOME(0.00)[]; RECEIVED_SPAMHAUS_BLOCKED_OPENRESOLVER(0.00)[100.90.174.1:received,184.96.151.165:received]; FROM_NEQ_ENVFROM(0.00)[jameshilliard1@gmail.com,linux-sunxi@lists.linux.dev]; FREEMAIL_TO(0.00)[lunn.ch,davemloft.net,google.com,kernel.org,redhat.com,gmail.com,armlinux.org.uk]; MIME_TRACE(0.00)[0:+]; TAGGED_FROM(0.00)[bounces-25760-noreply=patchwork.local]; FORGED_RECIPIENTS_MAILLIST(0.00)[]; MID_RHS_MATCH_FROM(0.00)[]; RCVD_TLS_LAST(0.00)[]; RBL_SPAMHAUS_BLOCKED_OPENRESOLVER(0.00)[172.234.253.10:from] X-Rspamd-Queue-Id: 0FBBD1C02DE X-MORS-Enabled: yes X-MORS-DOMAIN: patchwork.local X-MORS-HOSTING: hosting172546 X-MORS-USER: hosting172546 X-getmail-retrieved-from-mailbox: =?utf-8?q?INBOX?= The AC200 and AC300 contain compatible Fast Ethernet link PHYs, but the link endpoint is inaccessible until package-specific control registers have powered and configured it. Add one PHY driver which binds the link child and joins its parent Ethernet PHY package. Fixed package compatibles select the corresponding backend. The generic ACx00 compatible reads one packed configuration field and selects the backend before touching backend-specific resources. The AC300 path therefore does not resolve or access the candidate AC200 I2C device. Require two returned NVMEM bytes for AC300 and selectable ACx00 packages before selecting the backend. Keep the existing little-endian decoding and one-byte calibration support for fixed AC200 packages. AC200 locates the I2C device referenced by the package and establishes a managed device link. It verifies that the provider is fully bound under the supplier device lock before retrieving the regmap attached to the I2C device. The companion MFD provider enables and exclusively pins the input clock rate while it is bound. The link keeps the supplier bound until the PHY consumer has unbound. Firmware must make the selected AC200 provider path available before the PHY probes. AC300 uses the PHY package helpers to access the control range at base address plus 16. Keeping both backends in the same module lets the common link implementation own the complete PHY without registering artificial control devices. Obtain and manage the selected package supply and calibration, manage the AC300 input clock, validate or program the link address, and apply the required reset, clock, I/O and shutdown sequences. Start in the hardware-default MII mode so the forced PHY device can probe before a MAC attaches, then apply the MAC-provided MII or RMII mode before the normal PHY soft reset. AC200 specifies no additional delay between its EPHY controls; the provider's input-clock startup delay is already complete before its regmap becomes available. Apply the common vendor analog initialization, optional AC300 low-calibration tuning and board-selected receive-clock inversion. Keep MDI/MDI-X in automatic mode. Preserve standard MAC-managed EEE support while disabling the vendor PHY-autonomous Intelligent EEE mode. Restart autonegotiation when restoring the standard EEE advertisement changes it. Serialize package state and the multi-register power sequences across the different phylib callback lock contexts. Power down the package control block and its supply during PHY suspend, then restore them and the vendor configuration during resume. If shutdown fails after resetting the PHY, restore the vendor settings and standard advertisement or forced speed/duplex under the phylib lock before returning the original error. A failed suspend is not followed by a normal resume to restore these settings. Power down again if recovery fails, and unwind package power if reset, resume or configuration fails after power-on. Signed-off-by: James Hilliard --- drivers/net/phy/Kconfig | 11 + drivers/net/phy/Makefile | 1 + drivers/net/phy/xpowers/Makefile | 3 + drivers/net/phy/xpowers/ac200.c | 314 +++++++++++++++++++ drivers/net/phy/xpowers/ac300.c | 387 ++++++++++++++++++++++++ drivers/net/phy/xpowers/acx00.c | 633 +++++++++++++++++++++++++++++++++++++++ drivers/net/phy/xpowers/acx00.h | 28 ++ 7 files changed, 1377 insertions(+) diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index b4ef927fd4a6..d3835597e379 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -484,6 +484,17 @@ config VITESSE_PHY help Currently supports the vsc8244 +config XPOWERS_ACX00_PHY + tristate "X-Powers AC200/AC300 Ethernet PHY" + depends on COMMON_CLK && NVMEM && OF_MDIO && REGULATOR + depends on I2C || !I2C + select PHY_PACKAGE + help + Enable the Fast Ethernet PHY driver shared by the X-Powers AC200 + and AC300 companion ICs. The driver configures their common MDIO PHY + registers and the package-specific AC200 I2C or AC300 MDIO control + registers. + config XILINX_GMII2RGMII tristate "Xilinx GMII2RGMII converter driver" help diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile index 25c4a3c2429f..49575fa18522 100644 --- a/drivers/net/phy/Makefile +++ b/drivers/net/phy/Makefile @@ -101,4 +101,5 @@ obj-$(CONFIG_SMSC_PHY) += smsc.o obj-$(CONFIG_STE10XP) += ste10Xp.o obj-$(CONFIG_TERANETICS_PHY) += teranetics.o obj-$(CONFIG_VITESSE_PHY) += vitesse.o +obj-$(CONFIG_XPOWERS_ACX00_PHY) += xpowers/ obj-$(CONFIG_XILINX_GMII2RGMII) += xilinx_gmii2rgmii.o diff --git a/drivers/net/phy/xpowers/Makefile b/drivers/net/phy/xpowers/Makefile new file mode 100644 index 000000000000..ae977a795832 --- /dev/null +++ b/drivers/net/phy/xpowers/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0 +xpowers-acx00-y := acx00.o ac200.o ac300.o +obj-$(CONFIG_XPOWERS_ACX00_PHY) += xpowers-acx00.o diff --git a/drivers/net/phy/xpowers/ac200.c b/drivers/net/phy/xpowers/ac200.c new file mode 100644 index 000000000000..afdd2a3dfbe4 --- /dev/null +++ b/drivers/net/phy/xpowers/ac200.c @@ -0,0 +1,314 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * X-Powers AC200 Ethernet PHY package backend + * + * Copyright (c) 2022 Arm Ltd. (Andre Przywara ) + * Copyright (C) 2026 James Hilliard + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "acx00.h" + +#define AC200_EPHY_BPS_EFFUSE_OFFSET 3 +#define AC200_EPHY_CLK_RATE_24_MHZ 24000000 +#define AC200_EPHY_CLK_RATE_27_MHZ 27000000 + +#define AC200_SYS_EPHY_CTL0_REG 0x0014 +#define AC200_EPHY_RESET_DEASSERT BIT(0) +#define AC200_EPHY_SYSCLK_ENABLE BIT(1) + +#define AC200_SYS_EPHY_CTL1_REG 0x0016 +#define AC200_EPHY_MII_IO_ENABLE BIT(0) + +/* AC200-internal copy of the Ethernet PHY calibration eFuse. */ +#define AC200_EFUSE_EPHY_REG 0x8004 + +#define AC200_EPHY_CTL_REG 0x6000 +#define AC200_EPHY_SHUTDOWN BIT(0) +#define AC200_EPHY_CLK_SEL_24_MHZ BIT(2) +#define AC200_EPHY_PHY_ADDR_MASK GENMASK(8, 4) +#define AC200_EPHY_RMII_SEL BIT(11) +#define AC200_EPHY_BPS_EFFUSE_MASK GENMASK(15, 12) + +struct ac200_ephy_ctl { + struct acx00_ephy_control control; + struct regmap *regmap; + struct regulator *vcc; + struct device *dev; + u16 ephy_ctl; + unsigned int phy_addr; + phy_interface_t interface; + bool supply_enabled; + bool powered; +}; + +static u16 ac200_ephy_ctl_config(const struct ac200_ephy_ctl *priv) +{ + return priv->ephy_ctl | + (priv->interface == PHY_INTERFACE_MODE_RMII ? + AC200_EPHY_RMII_SEL : 0) | + FIELD_PREP(AC200_EPHY_PHY_ADDR_MASK, priv->phy_addr); +} + +static int ac200_ephy_ctl_write(struct ac200_ephy_ctl *priv, + unsigned int reg, u16 value) +{ + int ret; + + ret = regmap_write(priv->regmap, reg, value); + if (ret) + dev_err(priv->dev, "failed to write register %#x: %pe\n", + reg, ERR_PTR(ret)); + + return ret; +} + +static int ac200_ephy_ctl_disable(struct ac200_ephy_ctl *priv) +{ + int err; + int ret = 0; + + if (priv->powered) { + ret = ac200_ephy_ctl_write(priv, AC200_EPHY_CTL_REG, + ac200_ephy_ctl_config(priv) | + AC200_EPHY_SHUTDOWN); + err = ac200_ephy_ctl_write(priv, AC200_SYS_EPHY_CTL1_REG, 0); + if (!ret) + ret = err; + err = ac200_ephy_ctl_write(priv, AC200_SYS_EPHY_CTL0_REG, 0); + if (!ret) + ret = err; + + priv->powered = false; + } + + if (priv->supply_enabled) { + err = regulator_disable(priv->vcc); + if (!err) + priv->supply_enabled = false; + else if (!ret) + ret = err; + } + + return ret; +} + +static int ac200_ephy_ctl_power_off(struct acx00_ephy_control *control) +{ + struct ac200_ephy_ctl *priv = + container_of(control, struct ac200_ephy_ctl, control); + + return ac200_ephy_ctl_disable(priv); +} + +static int +ac200_ephy_ctl_set_interface(struct acx00_ephy_control *control, + phy_interface_t interface) +{ + struct ac200_ephy_ctl *priv = + container_of(control, struct ac200_ephy_ctl, control); + int ret = 0; + + if (interface != PHY_INTERFACE_MODE_MII && + interface != PHY_INTERFACE_MODE_RMII) + return -EINVAL; + + if (priv->interface == interface) + return 0; + + if (priv->powered) + ret = regmap_update_bits(priv->regmap, AC200_EPHY_CTL_REG, + AC200_EPHY_RMII_SEL, + interface == PHY_INTERFACE_MODE_RMII ? + AC200_EPHY_RMII_SEL : 0); + if (!ret) + priv->interface = interface; + + return ret; +} + +static int ac200_ephy_ctl_power_on(struct acx00_ephy_control *control, + unsigned int phy_addr) +{ + struct ac200_ephy_ctl *priv = + container_of(control, struct ac200_ephy_ctl, control); + u16 ephy_ctl; + int ret; + + if (phy_addr > FIELD_MAX(AC200_EPHY_PHY_ADDR_MASK)) + return -EINVAL; + + if (priv->powered && priv->phy_addr == phy_addr) + return 0; + + if (priv->powered) { + ret = ac200_ephy_ctl_disable(priv); + if (ret) + return ret; + } + priv->phy_addr = phy_addr; + + if (!priv->supply_enabled) { + ret = regulator_enable(priv->vcc); + if (ret) + return ret; + priv->supply_enabled = true; + } + + ephy_ctl = ac200_ephy_ctl_config(priv); + + /* + * Start from a disabled state before applying the configuration. AC200 + * specifies no additional delay between these controls. The companion + * MFD provider has completed its input-clock startup delay before + * publishing the regmap. + */ + ret = ac200_ephy_ctl_write(priv, AC200_SYS_EPHY_CTL0_REG, 0); + if (ret) + goto err_disable; + + ret = ac200_ephy_ctl_write(priv, AC200_SYS_EPHY_CTL1_REG, + AC200_EPHY_MII_IO_ENABLE); + if (ret) + goto err_disable; + + ret = ac200_ephy_ctl_write(priv, AC200_EPHY_CTL_REG, + ephy_ctl | AC200_EPHY_SHUTDOWN); + if (ret) + goto err_disable; + + ret = ac200_ephy_ctl_write(priv, AC200_SYS_EPHY_CTL0_REG, + AC200_EPHY_RESET_DEASSERT | + AC200_EPHY_SYSCLK_ENABLE); + if (ret) + goto err_disable; + + ret = ac200_ephy_ctl_write(priv, AC200_EPHY_CTL_REG, ephy_ctl); + if (ret) + goto err_disable; + + priv->powered = true; + return 0; + +err_disable: + /* Attempt every step of the shutdown sequence after a partial start. */ + priv->powered = true; + ac200_ephy_ctl_disable(priv); + + return ret; +} + +struct acx00_ephy_control * +ac200_ephy_ctl_create(struct phy_device *phydev, + struct device_node *package_node, + bool has_calibration, u8 calibration) +{ + struct device *dev = &phydev->mdio.dev; + unsigned int internal_calibration; + struct device_node *ac200_node; + struct ac200_ephy_ctl *priv; + struct i2c_client *client; + unsigned long clk_rate; + u8 bps_effuse_code; + struct clk *clk; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return ERR_PTR(-ENOMEM); + priv->dev = dev; + priv->vcc = devm_of_regulator_get(dev, package_node, "vcc"); + if (IS_ERR(priv->vcc)) + return ERR_PTR(dev_err_probe(dev, PTR_ERR(priv->vcc), + "failed to get package supply\n")); + + ac200_node = of_parse_phandle(package_node, "x-powers,ac200", 0); + if (!ac200_node) + return ERR_PTR(dev_err_probe(dev, -EINVAL, + "missing x-powers,ac200 reference\n")); + client = of_find_i2c_device_by_node(ac200_node); + of_node_put(ac200_node); + if (!client) { + ret = IS_ENABLED(CONFIG_I2C) ? -EPROBE_DEFER : -ENODEV; + return ERR_PTR(dev_err_probe(dev, ret, + "AC200 device is not registered\n")); + } + + if (!device_link_add(dev, &client->dev, + DL_FLAG_AUTOREMOVE_CONSUMER)) { + ret = dev_err_probe(dev, -EINVAL, + "failed to link AC200 device\n"); + goto out_put_client; + } + + device_lock(&client->dev); + if (device_is_bound(&client->dev)) + priv->regmap = dev_get_regmap(&client->dev, NULL); + device_unlock(&client->dev); + if (!priv->regmap) { + ret = dev_err_probe(dev, -EPROBE_DEFER, + "AC200 driver is not ready\n"); + goto out_put_client; + } + + if (!has_calibration) { + ret = regmap_read(priv->regmap, AC200_EFUSE_EPHY_REG, + &internal_calibration); + if (ret) + goto out_error; + calibration = internal_calibration; + } + + /* The vendor driver supplies no transfer function beyond this offset. */ + bps_effuse_code = (calibration + AC200_EPHY_BPS_EFFUSE_OFFSET) & + FIELD_MAX(AC200_EPHY_BPS_EFFUSE_MASK); + priv->ephy_ctl = + FIELD_PREP(AC200_EPHY_BPS_EFFUSE_MASK, bps_effuse_code); + /* EPHY_MODE and BIST_CLK_EN stay clear for normal operation. */ + + clk = clk_get(&client->dev, NULL); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + goto out_error; + } + + /* The bound AC200 provider enables and exclusively pins this rate. */ + clk_rate = clk_get_rate(clk); + clk_put(clk); + + switch (clk_rate) { + case AC200_EPHY_CLK_RATE_24_MHZ: + priv->ephy_ctl |= AC200_EPHY_CLK_SEL_24_MHZ; + break; + case AC200_EPHY_CLK_RATE_27_MHZ: + break; + default: + ret = dev_err_probe(dev, -EINVAL, + "unsupported AC200 clock rate %lu Hz\n", + clk_rate); + goto out_put_client; + } + + priv->control.power_on = ac200_ephy_ctl_power_on; + priv->control.power_off = ac200_ephy_ctl_power_off; + priv->control.set_interface = ac200_ephy_ctl_set_interface; + /* MII is the reset default used until the MAC supplies its interface. */ + priv->interface = PHY_INTERFACE_MODE_MII; + put_device(&client->dev); + + return &priv->control; + +out_error: + ret = dev_err_probe(dev, ret, "failed to initialize AC200 control\n"); +out_put_client: + put_device(&client->dev); + return ERR_PTR(ret); +} diff --git a/drivers/net/phy/xpowers/ac300.c b/drivers/net/phy/xpowers/ac300.c new file mode 100644 index 000000000000..aa560432a14c --- /dev/null +++ b/drivers/net/phy/xpowers/ac300.c @@ -0,0 +1,387 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * X-Powers AC300 Ethernet PHY package backend + * + * Copyright (C) 2026 James Hilliard + */ + +#include +#include +#include +#include +#include +#include + +#include "../phylib.h" +#include "acx00.h" + +#define AC300_EPHY_BGS_EFFUSE_OFFSET 3 +#define AC300_EPHY_CLK_RATE_24_MHZ 24000000 +#define AC300_EPHY_CLK_RATE_25_MHZ 25000000 +#define AC300_EPHY_CLK_RATE_27_MHZ 27000000 +#define AC300_SYS_CONTROL_REG 0x00 +#define AC300_PACKAGE_STATUS_MASK GENMASK(11, 8) +#define AC300_EPHY_CLK_SEL_MASK GENMASK(7, 6) +#define AC300_EPHY_CLK_SEL_25_MHZ FIELD_PREP(AC300_EPHY_CLK_SEL_MASK, 0) +#define AC300_EPHY_CLK_SEL_27_MHZ FIELD_PREP(AC300_EPHY_CLK_SEL_MASK, 1) +#define AC300_EPHY_CLK_SEL_24_MHZ FIELD_PREP(AC300_EPHY_CLK_SEL_MASK, 2) +#define AC300_EFUSE_CLK_ENABLE BIT(5) +#define AC300_EPHY_REG_CLK_ENABLE BIT(4) +#define AC300_CLKIN_GATING_ENABLE BIT(2) +#define AC300_EPHY_RESET_DEASSERT BIT(1) +#define AC300_CHIP_RESET_DEASSERT BIT(0) + +#define AC300_PACKAGE_POR_INTERNAL_DLDO BIT(3) +#define AC300_PACKAGE_PHY_ADDR_MASK GENMASK(2, 0) + +#define AC300_SYS_BIAS1_REG 0x02 +#define AC300_INTERNAL_DLDO_ENABLE BIT(15) + +#define AC300_SYS_IO_REG 0x05 +#define AC300_MDIO_DRV_MASK GENMASK(15, 14) +#define AC300_MII_DRV_MASK GENMASK(11, 10) +#define AC300_IO_DRV_LEVEL_2 2 +#define AC300_CLKIN_PAD_ENABLE BIT(4) +#define AC300_EPHY_MII_IO_ENABLE BIT(0) + +#define AC300_EPHY_CONFIG_REG 0x06 +#define AC300_EPHY_BGS_EFFUSE_MASK GENMASK(15, 12) +#define AC300_EPHY_RMII_SEL BIT(11) +#define AC300_EPHY_SHUTDOWN BIT(0) + +#define AC300_SYS_CONTROL_ENABLE_BITS \ + (AC300_EFUSE_CLK_ENABLE | AC300_EPHY_REG_CLK_ENABLE | \ + AC300_CLKIN_GATING_ENABLE | AC300_EPHY_RESET_DEASSERT | \ + AC300_CHIP_RESET_DEASSERT) + +#define AC300_SYS_IO_VALUE \ + (FIELD_PREP(AC300_MDIO_DRV_MASK, AC300_IO_DRV_LEVEL_2) | \ + FIELD_PREP(AC300_MII_DRV_MASK, AC300_IO_DRV_LEVEL_2) | \ + AC300_CLKIN_PAD_ENABLE | AC300_EPHY_MII_IO_ENABLE) + +struct ac300_ephy_ctl { + struct acx00_ephy_control control; + struct phy_device *phydev; + struct regulator *vcc; + struct clk *clk; + u16 sys_control; + u16 ephy_config; + phy_interface_t interface; + bool package_known; + bool internal_dldo; + bool supply_enabled; + bool powered; +}; + +static unsigned int +ac300_ephy_ctl_link_addr(const struct ac300_ephy_ctl *priv) +{ + return priv->phydev->mdio.addr; +} + +static int ac300_ephy_ctl_read(struct ac300_ephy_ctl *priv, u32 regnum) +{ + int ret; + + phy_lock_mdio_bus(priv->phydev); + ret = __phy_package_read(priv->phydev, + AC300_EPHY_CONTROL_ADDR_OFFSET, regnum); + phy_unlock_mdio_bus(priv->phydev); + + return ret; +} + +static int ac300_ephy_ctl_write(struct ac300_ephy_ctl *priv, u32 regnum, + u16 val) +{ + int ret; + + phy_lock_mdio_bus(priv->phydev); + ret = __phy_package_write(priv->phydev, + AC300_EPHY_CONTROL_ADDR_OFFSET, regnum, val); + phy_unlock_mdio_bus(priv->phydev); + + return ret; +} + +static int ac300_ephy_ctl_modify(struct ac300_ephy_ctl *priv, u32 regnum, + u16 mask, u16 set) +{ + int ret; + + phy_lock_mdio_bus(priv->phydev); + ret = __phy_package_read(priv->phydev, + AC300_EPHY_CONTROL_ADDR_OFFSET, regnum); + if (ret >= 0) { + u16 val = (ret & ~mask) | (set & mask); + + ret = val == ret ? 0 : + __phy_package_write(priv->phydev, + AC300_EPHY_CONTROL_ADDR_OFFSET, + regnum, val); + } + phy_unlock_mdio_bus(priv->phydev); + + return ret; +} + +static u16 ac300_ephy_ctl_config(const struct ac300_ephy_ctl *priv) +{ + return priv->ephy_config | + (priv->interface == PHY_INTERFACE_MODE_RMII ? + AC300_EPHY_RMII_SEL : 0); +} + +static u16 ac300_ephy_ctl_reset_value(const struct ac300_ephy_ctl *priv) +{ + /* + * A chip reset restores DLDOEN to one. Until PKG_STATUS has identified + * the supply arrangement, preserve any external-VDD setup left by the + * bootloader by asserting only the EPHY reset. + */ + return !priv->package_known || !priv->internal_dldo ? + AC300_CHIP_RESET_DEASSERT : 0; +} + +static int ac300_ephy_ctl_disable(struct ac300_ephy_ctl *priv) +{ + int err; + int ret = 0; + + if (priv->powered) { + ret = ac300_ephy_ctl_write(priv, AC300_EPHY_CONFIG_REG, + ac300_ephy_ctl_config(priv) | + AC300_EPHY_SHUTDOWN); + err = ac300_ephy_ctl_write(priv, AC300_SYS_IO_REG, 0); + if (!ret) + ret = err; + err = ac300_ephy_ctl_write(priv, AC300_SYS_CONTROL_REG, + ac300_ephy_ctl_reset_value(priv)); + if (!ret) + ret = err; + + clk_disable_unprepare(priv->clk); + priv->powered = false; + } + + if (priv->supply_enabled) { + err = regulator_disable(priv->vcc); + if (!err) + priv->supply_enabled = false; + else if (!ret) + ret = err; + } + + return ret; +} + +static int ac300_ephy_ctl_power_off(struct acx00_ephy_control *control) +{ + struct ac300_ephy_ctl *priv = + container_of(control, struct ac300_ephy_ctl, control); + + return ac300_ephy_ctl_disable(priv); +} + +static int +ac300_ephy_ctl_set_interface(struct acx00_ephy_control *control, + phy_interface_t interface) +{ + struct ac300_ephy_ctl *priv = + container_of(control, struct ac300_ephy_ctl, control); + int ret = 0; + + if (interface != PHY_INTERFACE_MODE_MII && + interface != PHY_INTERFACE_MODE_RMII) + return -EINVAL; + + if (priv->interface == interface) + return 0; + + if (priv->powered) + ret = ac300_ephy_ctl_modify(priv, AC300_EPHY_CONFIG_REG, + AC300_EPHY_RMII_SEL, + interface == PHY_INTERFACE_MODE_RMII ? + AC300_EPHY_RMII_SEL : 0); + if (!ret) + priv->interface = interface; + + return ret; +} + +static int ac300_ephy_ctl_power_on(struct acx00_ephy_control *control, + unsigned int phy_addr) +{ + struct ac300_ephy_ctl *priv = + container_of(control, struct ac300_ephy_ctl, control); + u8 package_status; + u16 reset_value; + int sys_control; + int ret; + + if (phy_addr != ac300_ephy_ctl_link_addr(priv)) + return -EINVAL; + + if (priv->powered) + return 0; + + if (!priv->supply_enabled) { + ret = regulator_enable(priv->vcc); + if (ret) + return ret; + priv->supply_enabled = true; + + /* Wait for the power-on reset interval specified by the manual. */ + fsleep(10000); + } + + ret = clk_prepare_enable(priv->clk); + if (ret) + goto err_power_off; + priv->powered = true; + + /* Keep the external-supply configuration across chip resets. */ + reset_value = ac300_ephy_ctl_reset_value(priv); + ret = ac300_ephy_ctl_write(priv, AC300_SYS_CONTROL_REG, reset_value); + if (ret) + goto err_power_off; + + /* The manual requires both resets to be released before the clocks. */ + ret = ac300_ephy_ctl_write(priv, AC300_SYS_CONTROL_REG, + AC300_EPHY_RESET_DEASSERT | + AC300_CHIP_RESET_DEASSERT); + if (ret) + goto err_power_off; + + /* Retain the vendor clock-enable defaults, including the eFuse clock. */ + ret = ac300_ephy_ctl_write(priv, AC300_SYS_CONTROL_REG, + priv->sys_control); + if (ret) + goto err_power_off; + + sys_control = ac300_ephy_ctl_read(priv, AC300_SYS_CONTROL_REG); + if (sys_control < 0) { + ret = sys_control; + goto err_power_off; + } + + package_status = FIELD_GET(AC300_PACKAGE_STATUS_MASK, sys_control); + if ((~package_status & AC300_PACKAGE_PHY_ADDR_MASK) != + ac300_ephy_ctl_link_addr(priv)) { + ret = -EINVAL; + goto err_power_off; + } + + priv->internal_dldo = package_status & AC300_PACKAGE_POR_INTERNAL_DLDO; + priv->package_known = true; + ret = ac300_ephy_ctl_modify(priv, AC300_SYS_BIAS1_REG, + AC300_INTERNAL_DLDO_ENABLE, + priv->internal_dldo ? + AC300_INTERNAL_DLDO_ENABLE : 0); + if (ret) + goto err_power_off; + + /* Keep the documented default drive level and leave the IRQ disabled. */ + ret = ac300_ephy_ctl_write(priv, AC300_SYS_IO_REG, + AC300_SYS_IO_VALUE); + if (ret) + goto err_power_off; + + fsleep(10000); + + ret = ac300_ephy_ctl_write(priv, AC300_EPHY_CONFIG_REG, + ac300_ephy_ctl_config(priv) | + AC300_EPHY_SHUTDOWN); + if (ret) + goto err_power_off; + + fsleep(10000); + + ret = ac300_ephy_ctl_write(priv, AC300_EPHY_CONFIG_REG, + ac300_ephy_ctl_config(priv)); + if (ret) + goto err_power_off; + + return 0; + +err_power_off: + ac300_ephy_ctl_disable(priv); + + return ret; +} + +static void ac300_ephy_clk_put(void *data) +{ + clk_put(data); +} + +struct acx00_ephy_control * +ac300_ephy_ctl_create(struct phy_device *phydev, + struct device_node *package_node, u8 calibration) +{ + struct device *dev = &phydev->mdio.dev; + struct ac300_ephy_ctl *priv; + unsigned long clk_rate; + u8 bgs_effuse_code; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return ERR_PTR(-ENOMEM); + if (phydev->mdio.addr > FIELD_MAX(AC300_PACKAGE_PHY_ADDR_MASK)) + return ERR_PTR(dev_err_probe(dev, -EINVAL, + "link address is outside the package range\n")); + priv->phydev = phydev; + + priv->vcc = devm_of_regulator_get(dev, package_node, "vcc"); + if (IS_ERR(priv->vcc)) + return ERR_PTR(dev_err_probe(dev, PTR_ERR(priv->vcc), + "failed to get package supply\n")); + + priv->clk = of_clk_get(package_node, 0); + if (IS_ERR(priv->clk)) + return ERR_PTR(dev_err_probe(dev, PTR_ERR(priv->clk), + "failed to get input clock\n")); + + ret = devm_add_action_or_reset(dev, ac300_ephy_clk_put, priv->clk); + if (ret) + return ERR_PTR(ret); + + ret = devm_clk_rate_exclusive_get(dev, priv->clk); + if (ret) + return ERR_PTR(dev_err_probe(dev, ret, + "failed to lock clock rate\n")); + + clk_rate = clk_get_rate(priv->clk); + switch (clk_rate) { + case AC300_EPHY_CLK_RATE_24_MHZ: + priv->sys_control = AC300_EPHY_CLK_SEL_24_MHZ; + break; + case AC300_EPHY_CLK_RATE_25_MHZ: + priv->sys_control = AC300_EPHY_CLK_SEL_25_MHZ; + break; + case AC300_EPHY_CLK_RATE_27_MHZ: + priv->sys_control = AC300_EPHY_CLK_SEL_27_MHZ; + break; + default: + return ERR_PTR(dev_err_probe(dev, -EINVAL, + "unsupported input clock rate %lu Hz\n", + clk_rate)); + } + priv->sys_control |= AC300_SYS_CONTROL_ENABLE_BITS; + + /* The vendor driver supplies no transfer function beyond this offset. */ + bgs_effuse_code = (calibration + AC300_EPHY_BGS_EFFUSE_OFFSET) & + FIELD_MAX(AC300_EPHY_BGS_EFFUSE_MASK); + priv->ephy_config = + FIELD_PREP(AC300_EPHY_BGS_EFFUSE_MASK, bgs_effuse_code); + /* EPHY_MODE and BIST_CLK_EN stay clear for normal operation. */ + + priv->control.power_on = ac300_ephy_ctl_power_on; + priv->control.power_off = ac300_ephy_ctl_power_off; + priv->control.set_interface = ac300_ephy_ctl_set_interface; + /* MII is the reset default used until the MAC supplies its interface. */ + priv->interface = PHY_INTERFACE_MODE_MII; + + return &priv->control; +} diff --git a/drivers/net/phy/xpowers/acx00.c b/drivers/net/phy/xpowers/acx00.c new file mode 100644 index 000000000000..d19ba6d8069a --- /dev/null +++ b/drivers/net/phy/xpowers/acx00.c @@ -0,0 +1,633 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * X-Powers AC200/AC300 Ethernet PHY driver + * + * Copyright (C) 2019 Jernej Skrabec + * Copyright (C) 2026 James Hilliard + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../phylib.h" +#include "acx00.h" + +#define ACX00_EPHY_ID 0x00441400 + +#define ACX00_EPHY_CONFIG_CALIBRATION_MASK GENMASK(3, 0) +#define ACX00_EPHY_CONFIG_VARIANT_AC300 BIT(8) +#define ACX00_EPHY_CONFIG_CALIBRATION_LOW BIT(9) + +#define ACX00_PAGE_SELECT_REG 0x1f +#define ACX00_PAGE_SELECT_MASK GENMASK(12, 8) +#define ACX00_PAGE_0 0 +#define ACX00_PAGE_1 1 +#define ACX00_PAGE_2 2 +#define ACX00_PAGE_6 6 +#define ACX00_PAGE_8 8 + +#define ACX00_PAGE0_GLOBAL_CONFIG_REG 0x13 +#define ACX00_PAGE0_XMII_RX_CLOCK_INVERT BIT(12) +#define ACX00_PAGE0_MDI_MODE_MASK GENMASK(1, 0) +#define ACX00_PAGE0_MDI_MODE_AUTO 2 + +#define ACX00_PAGE1_APS_CONTROL_REG 0x12 +#define ACX00_PAGE1_APS_DISABLED_4S_VALUE 0x4824 +#define ACX00_PAGE1_UAPS_CONTROL_REG 0x13 +#define ACX00_PAGE1_UAPS_ENABLE BIT(15) +#define ACX00_PAGE1_INTELLIGENT_EEE_CONTROL_REG 0x17 +#define ACX00_PAGE1_INTELLIGENT_EEE_ENABLE BIT(3) + +#define ACX00_PAGE2_TX_DATA_CONTROL_REG 0x18 +#define ACX00_PAGE2_10BT_FIR_SELECT_MASK GENMASK(14, 12) +#define ACX00_PAGE2_10BT_FIR_SELECT_DEFAULT 0 + +#define ACX00_PAGE6_ADC_CONTROL_REG 0x10 +#define ACX00_PAGE6_ADC_CONTROL_LOW_CAL_VALUE 0x5523 +#define ACX00_PAGE6_AFE_RX_CONTROL_REG 0x13 +#define ACX00_PAGE6_AFE_RX_CONTROL_VALUE 0xf000 +#define ACX00_PAGE6_AFE_EQ_RX_DETECT_CONTROL_REG 0x14 +#define AC200_PAGE6_AFE_EQ_RX_DETECT_VALUE 0x708f +#define AC300_PAGE6_AFE_EQ_RX_DETECT_VALUE 0x708b +#define ACX00_PAGE6_AFE_EQ_RX_DETECT_LOW_CAL_VALUE 0x7809 +#define ACX00_PAGE6_TX_LEVEL_REG 0x15 +#define ACX00_PAGE6_TX_LEVEL_100M_MASK GENMASK(15, 8) +#define ACX00_PAGE6_TX_LEVEL_10M_MASK GENMASK(7, 0) +#define ACX00_PAGE6_TX_LEVEL_VALUE(_100m, _10m) \ + (FIELD_PREP(ACX00_PAGE6_TX_LEVEL_100M_MASK, (_100m)) | \ + FIELD_PREP(ACX00_PAGE6_TX_LEVEL_10M_MASK, (_10m))) +#define ACX00_PAGE6_TX_LEVEL_DEFAULT_VALUE \ + ACX00_PAGE6_TX_LEVEL_VALUE(0x15, 0x30) +#define ACX00_PAGE6_TX_LEVEL_LOW_CAL_VALUE \ + ACX00_PAGE6_TX_LEVEL_VALUE(0x35, 0x33) + +#define ACX00_PAGE8_AFE_CONTROL_REG 0x18 +#define ACX00_PAGE8_AFE_CONTROL_VALUE 0x00bc +#define ACX00_PAGE8_AUTO_CAL_CONTROL_REG 0x1d +#define ACX00_PAGE8_AUTO_CAL_TX_LEVEL_ADJUST_BYPASS BIT(11) +#define ACX00_PAGE8_AUTO_CAL_LOW_CAL_OPAQUE_BITS 0x0044 +#define ACX00_PAGE8_AUTO_CAL_LOW_VALUE \ + (ACX00_PAGE8_AUTO_CAL_TX_LEVEL_ADJUST_BYPASS | \ + ACX00_PAGE8_AUTO_CAL_LOW_CAL_OPAQUE_BITS) + +/* + * Another integration of this exact-ID PHY documents its digital vendor + * register map, which also matches the observed ACx00 reset values. The ACx00 + * analog-page field encodings remain unpublished, so keep those as opaque + * vendor initialization values instead of inventing bit definitions. + */ + +struct acx00_ephy_priv { + struct phy_device *phydev; + struct acx00_ephy_control *control; + /* Serializes package state and multi-register power sequences. */ + struct mutex state_lock; + bool is_ac300; + bool use_low_calibration_tuning; + bool xmii_rx_clock_inverted; +}; + +static int acx00_ephy_read_page(struct phy_device *phydev) +{ + int ret; + + ret = __phy_read(phydev, ACX00_PAGE_SELECT_REG); + if (ret < 0) + return ret; + + return FIELD_GET(ACX00_PAGE_SELECT_MASK, ret); +} + +static int acx00_ephy_write_page(struct phy_device *phydev, int page) +{ + return __phy_write(phydev, ACX00_PAGE_SELECT_REG, + FIELD_PREP(ACX00_PAGE_SELECT_MASK, page)); +} + +static int acx00_ephy_control_power_on(struct acx00_ephy_priv *priv) +{ + lockdep_assert_held(&priv->state_lock); + + return priv->control->power_on(priv->control, + priv->phydev->mdio.addr); +} + +static int acx00_ephy_control_power_off(struct acx00_ephy_priv *priv) +{ + lockdep_assert_held(&priv->state_lock); + + return priv->control->power_off(priv->control); +} + +static int acx00_ephy_set_interface(struct phy_device *phydev) +{ + struct acx00_ephy_priv *priv = phydev->priv; + + lockdep_assert_held(&priv->state_lock); + + if (phydev->interface == PHY_INTERFACE_MODE_NA) + return 0; + if (phydev->interface != PHY_INTERFACE_MODE_MII && + phydev->interface != PHY_INTERFACE_MODE_RMII) + return -EINVAL; + + return priv->control->set_interface(priv->control, + phydev->interface); +} + +static void acx00_ephy_control_power_off_warn(struct acx00_ephy_priv *priv) +{ + int ret; + + ret = acx00_ephy_control_power_off(priv); + if (ret) + phydev_warn(priv->phydev, + "failed to power off control block: %pe\n", + ERR_PTR(ret)); +} + +static void acx00_ephy_control_release(void *data) +{ + struct acx00_ephy_priv *priv = data; + + mutex_lock(&priv->state_lock); + acx00_ephy_control_power_off_warn(priv); + mutex_unlock(&priv->state_lock); +} + +static int acx00_ephy_read_nvmem_u16(struct device_node *node, + const char *name, size_t min_len, + u16 *value) +{ + struct nvmem_cell *cell; + size_t i; + size_t len; + u8 *buf; + u16 val = 0; + + cell = of_nvmem_cell_get(node, name); + if (IS_ERR(cell)) + return PTR_ERR(cell); + + buf = nvmem_cell_read(cell, &len); + nvmem_cell_put(cell); + if (IS_ERR(buf)) + return PTR_ERR(buf); + if (len < min_len || len > sizeof(*value)) { + kfree(buf); + return len > sizeof(*value) ? -ERANGE : -EINVAL; + } + + for (i = 0; i < len; i++) + val |= (u16)buf[i] << (8 * i); + kfree(buf); + *value = val; + + return 0; +} + +static int acx00_ephy_init_package(struct phy_device *phydev, + struct acx00_ephy_priv *priv) +{ + struct device *dev = &phydev->mdio.dev; + struct device_node *package_node; + bool selectable; + bool fixed_ac300; + bool has_configuration; + u8 calibration; + u32 base_addr; + u16 configuration = 0; + int ret; + + package_node = of_get_parent(dev->of_node); + if (!package_node) + return -EINVAL; + if (!of_node_name_eq(package_node, "ethernet-phy-package")) { + ret = dev_err_probe(dev, -EINVAL, + "PHY is not in an Ethernet PHY package\n"); + goto out_put_node; + } + + ret = of_property_read_u32(package_node, "reg", &base_addr); + if (ret || base_addr != phydev->mdio.addr) { + ret = dev_err_probe(dev, -EINVAL, + "package and link PHY addresses differ\n"); + goto out_put_node; + } + + selectable = of_device_is_compatible(package_node, + "x-powers,acx00-ephy-package"); + fixed_ac300 = of_device_is_compatible(package_node, + "x-powers,ac300-ephy-package"); + if (!selectable && !fixed_ac300 && + !of_device_is_compatible(package_node, + "x-powers,ac200-ephy-package")) { + ret = dev_err_probe(dev, -EINVAL, + "unsupported Ethernet PHY package\n"); + goto out_put_node; + } + + ret = devm_of_phy_package_join(dev, phydev, 0); + if (ret) + goto out_put_node; + + has_configuration = + of_property_match_string(package_node, "nvmem-cell-names", + "configuration") >= 0; + if (has_configuration) { + ret = acx00_ephy_read_nvmem_u16(package_node, "configuration", + selectable || fixed_ac300 ? 2 : 1, + &configuration); + if (ret) { + ret = dev_err_probe(dev, ret, + "failed to read package configuration\n"); + goto out_put_node; + } + } else if (selectable || fixed_ac300) { + ret = dev_err_probe(dev, -EINVAL, + "package configuration is required\n"); + goto out_put_node; + } + + if (selectable) { + priv->is_ac300 = + configuration & ACX00_EPHY_CONFIG_VARIANT_AC300; + } else { + priv->is_ac300 = fixed_ac300; + if (has_configuration && + !!(configuration & ACX00_EPHY_CONFIG_VARIANT_AC300) != + priv->is_ac300) { + ret = dev_err_probe(dev, -EINVAL, + "configuration does not match package\n"); + goto out_put_node; + } + } + + calibration = FIELD_GET(ACX00_EPHY_CONFIG_CALIBRATION_MASK, + configuration); + + priv->use_low_calibration_tuning = + priv->is_ac300 && + !!(configuration & ACX00_EPHY_CONFIG_CALIBRATION_LOW); + priv->xmii_rx_clock_inverted = + of_property_read_bool(package_node, + "x-powers,xmii-rx-clock-inverted"); + + if (priv->is_ac300) + priv->control = + ac300_ephy_ctl_create(phydev, package_node, + calibration); + else + priv->control = + ac200_ephy_ctl_create(phydev, package_node, + has_configuration, + calibration); + ret = PTR_ERR_OR_ZERO(priv->control); + +out_put_node: + of_node_put(package_node); + return ret; +} + +static int acx00_ephy_disable_autonomous_eee(struct phy_device *phydev) +{ + struct acx00_ephy_priv *priv = phydev->priv; + int ret; + + mutex_lock(&priv->state_lock); + ret = phy_modify_paged(phydev, ACX00_PAGE_1, + ACX00_PAGE1_INTELLIGENT_EEE_CONTROL_REG, + ACX00_PAGE1_INTELLIGENT_EEE_ENABLE, 0); + mutex_unlock(&priv->state_lock); + + return ret; +} + +static int acx00_ephy_probe(struct phy_device *phydev) +{ + struct device *dev = &phydev->mdio.dev; + struct acx00_ephy_priv *priv; + int ret; + + if (!dev->of_node) + return -ENODEV; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->phydev = phydev; + mutex_init(&priv->state_lock); + ret = acx00_ephy_init_package(phydev, priv); + if (ret) + return ret; + + phydev->priv = priv; + ret = devm_add_action_or_reset(dev, acx00_ephy_control_release, priv); + if (ret) + return ret; + + mutex_lock(&priv->state_lock); + ret = acx00_ephy_control_power_on(priv); + mutex_unlock(&priv->state_lock); + if (ret) + return dev_err_probe(dev, ret, + "failed to power on control block\n"); + + return 0; +} + +static int acx00_ephy_soft_reset(struct phy_device *phydev) +{ + struct acx00_ephy_priv *priv = phydev->priv; + int ret; + + mutex_lock(&priv->state_lock); + + ret = acx00_ephy_set_interface(phydev); + if (ret) + goto out_power_off; + + ret = acx00_ephy_control_power_on(priv); + if (ret) + goto out_power_off; + + /* ACx00 can acknowledge reset in power-down without restarting. */ + ret = genphy_resume(phydev); + if (ret) + goto out_power_off; + + ret = genphy_soft_reset(phydev); + if (ret) + goto out_power_off; + + goto out_unlock; + +out_power_off: + acx00_ephy_control_power_off_warn(priv); +out_unlock: + mutex_unlock(&priv->state_lock); + + return ret; +} + +static int __acx00_ephy_config_init(struct phy_device *phydev) +{ + struct acx00_ephy_priv *priv = phydev->priv; + u16 afe_eq_rx_detect = priv->is_ac300 ? + AC300_PAGE6_AFE_EQ_RX_DETECT_VALUE : + AC200_PAGE6_AFE_EQ_RX_DETECT_VALUE; + u16 tx_level_value = ACX00_PAGE6_TX_LEVEL_DEFAULT_VALUE; + u16 global_config; + int oldpage; + int ret; + + lockdep_assert_held(&priv->state_lock); + + global_config = FIELD_PREP(ACX00_PAGE0_MDI_MODE_MASK, + ACX00_PAGE0_MDI_MODE_AUTO); + if (priv->xmii_rx_clock_inverted) + global_config |= ACX00_PAGE0_XMII_RX_CLOCK_INVERT; + + ret = phy_modify_paged(phydev, ACX00_PAGE_0, + ACX00_PAGE0_GLOBAL_CONFIG_REG, + ACX00_PAGE0_XMII_RX_CLOCK_INVERT | + ACX00_PAGE0_MDI_MODE_MASK, global_config); + if (ret) + return ret; + + if (priv->is_ac300 && priv->use_low_calibration_tuning) { + afe_eq_rx_detect = + ACX00_PAGE6_AFE_EQ_RX_DETECT_LOW_CAL_VALUE; + tx_level_value = ACX00_PAGE6_TX_LEVEL_LOW_CAL_VALUE; + } + + oldpage = phy_select_page(phydev, ACX00_PAGE_1); + if (oldpage < 0) + return phy_restore_page(phydev, oldpage, oldpage); + + ret = __phy_write(phydev, ACX00_PAGE1_APS_CONTROL_REG, + ACX00_PAGE1_APS_DISABLED_4S_VALUE); + if (ret) + goto out_restore_page; + ret = __phy_modify(phydev, ACX00_PAGE1_UAPS_CONTROL_REG, + ACX00_PAGE1_UAPS_ENABLE, 0); + if (ret) + goto out_restore_page; + ret = __phy_modify(phydev, ACX00_PAGE1_INTELLIGENT_EEE_CONTROL_REG, + ACX00_PAGE1_INTELLIGENT_EEE_ENABLE, 0); + if (ret) + goto out_restore_page; + + ret = acx00_ephy_write_page(phydev, ACX00_PAGE_2); + if (ret) + goto out_restore_page; + ret = __phy_modify(phydev, ACX00_PAGE2_TX_DATA_CONTROL_REG, + ACX00_PAGE2_10BT_FIR_SELECT_MASK, + FIELD_PREP(ACX00_PAGE2_10BT_FIR_SELECT_MASK, + ACX00_PAGE2_10BT_FIR_SELECT_DEFAULT)); + if (ret) + goto out_restore_page; + + ret = acx00_ephy_write_page(phydev, ACX00_PAGE_6); + if (ret) + goto out_restore_page; + ret = __phy_write(phydev, ACX00_PAGE6_AFE_EQ_RX_DETECT_CONTROL_REG, + afe_eq_rx_detect); + if (ret) + goto out_restore_page; + ret = __phy_write(phydev, ACX00_PAGE6_AFE_RX_CONTROL_REG, + ACX00_PAGE6_AFE_RX_CONTROL_VALUE); + if (ret) + goto out_restore_page; + if (priv->is_ac300 && priv->use_low_calibration_tuning) { + ret = __phy_write(phydev, ACX00_PAGE6_ADC_CONTROL_REG, + ACX00_PAGE6_ADC_CONTROL_LOW_CAL_VALUE); + if (ret) + goto out_restore_page; + } + ret = __phy_write(phydev, ACX00_PAGE6_TX_LEVEL_REG, tx_level_value); + if (ret) + goto out_restore_page; + + ret = acx00_ephy_write_page(phydev, ACX00_PAGE_8); + if (ret) + goto out_restore_page; + if (priv->is_ac300 && priv->use_low_calibration_tuning) { + ret = __phy_write(phydev, ACX00_PAGE8_AUTO_CAL_CONTROL_REG, + ACX00_PAGE8_AUTO_CAL_LOW_VALUE); + if (ret) + goto out_restore_page; + } + ret = __phy_write(phydev, ACX00_PAGE8_AFE_CONTROL_REG, + ACX00_PAGE8_AFE_CONTROL_VALUE); + +out_restore_page: + ret = phy_restore_page(phydev, oldpage, ret); + if (ret) + return ret; + + /* Restore the standard EEE policy retained by phylib across resets. */ + ret = genphy_c45_an_config_eee_aneg(phydev); + if (ret <= 0) + return ret; + + return genphy_restart_aneg(phydev); +} + +static int acx00_ephy_config_init(struct phy_device *phydev) +{ + struct acx00_ephy_priv *priv = phydev->priv; + int ret; + + mutex_lock(&priv->state_lock); + ret = __acx00_ephy_config_init(phydev); + if (ret) + acx00_ephy_control_power_off_warn(priv); + mutex_unlock(&priv->state_lock); + + return ret; +} + +static int __acx00_ephy_power_on_and_resume(struct phy_device *phydev) +{ + struct acx00_ephy_priv *priv = phydev->priv; + int ret; + + lockdep_assert_held(&phydev->lock); + lockdep_assert_held(&priv->state_lock); + + ret = acx00_ephy_set_interface(phydev); + if (ret) + goto out_power_off; + + ret = acx00_ephy_control_power_on(priv); + if (ret) + goto out_power_off; + + ret = genphy_resume(phydev); + if (ret) + goto out_power_off; + + /* Powering off the control block loses the vendor-page state. */ + ret = __acx00_ephy_config_init(phydev); + if (ret) + goto out_power_off; + + return 0; + +out_power_off: + acx00_ephy_control_power_off_warn(priv); + + return ret; +} + +static int acx00_ephy_resume(struct phy_device *phydev) +{ + struct acx00_ephy_priv *priv = phydev->priv; + int ret; + + mutex_lock(&priv->state_lock); + ret = __acx00_ephy_power_on_and_resume(phydev); + mutex_unlock(&priv->state_lock); + + return ret; +} + +static int acx00_ephy_suspend(struct phy_device *phydev) +{ + struct acx00_ephy_priv *priv = phydev->priv; + int resume_ret; + int ret; + + /* The suspend callback is invoked without the phylib lock held. */ + mutex_lock(&phydev->lock); + mutex_lock(&priv->state_lock); + + ret = genphy_suspend(phydev); + if (ret) + goto out_unlock; + + ret = acx00_ephy_control_power_off(priv); + if (ret) { + resume_ret = __acx00_ephy_power_on_and_resume(phydev); + if (!resume_ret) { + /* + * The package reset also loses the standard link settings. + * A failed suspend is not followed by resume, so restore + * advertisement or forced speed/duplex before returning. + */ + resume_ret = genphy_config_aneg(phydev); + if (resume_ret) + acx00_ephy_control_power_off_warn(priv); + } + if (resume_ret) + phydev_warn(phydev, + "failed to recover from suspend error: %pe\n", + ERR_PTR(resume_ret)); + } + +out_unlock: + mutex_unlock(&priv->state_lock); + mutex_unlock(&phydev->lock); + + return ret; +} + +static int acx00_ephy_match_phy_device(struct phy_device *phydev, + const struct phy_driver *phydrv) +{ + struct device_node *node = phydev->mdio.dev.of_node; + struct device_node *package_node; + bool match; + + if (!genphy_match_phy_device(phydev, phydrv) || !node) + return 0; + + /* RK630 reports the same PHY ID, so also match the package identity. */ + package_node = of_get_parent(node); + if (!package_node) + return 0; + match = of_device_is_compatible(package_node, + "x-powers,ac200-ephy-package") || + of_device_is_compatible(package_node, + "x-powers,ac300-ephy-package") || + of_device_is_compatible(package_node, + "x-powers,acx00-ephy-package"); + of_node_put(package_node); + + return match; +} + +static struct phy_driver acx00_ephy_driver[] = { + { + PHY_ID_MATCH_MODEL(ACX00_EPHY_ID), + .name = "X-Powers AC200/AC300 EPHY", + .match_phy_device = acx00_ephy_match_phy_device, + .probe = acx00_ephy_probe, + .read_page = acx00_ephy_read_page, + .write_page = acx00_ephy_write_page, + .soft_reset = acx00_ephy_soft_reset, + .config_init = acx00_ephy_config_init, + .disable_autonomous_eee = acx00_ephy_disable_autonomous_eee, + .suspend = acx00_ephy_suspend, + .resume = acx00_ephy_resume, + }, +}; +module_phy_driver(acx00_ephy_driver); + +static const struct mdio_device_id __maybe_unused acx00_ephy_tbl[] = { + { PHY_ID_MATCH_MODEL(ACX00_EPHY_ID) }, + { } +}; +MODULE_DEVICE_TABLE(mdio, acx00_ephy_tbl); + +MODULE_AUTHOR("James Hilliard "); +MODULE_DESCRIPTION("X-Powers AC200/AC300 Ethernet PHY driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/phy/xpowers/acx00.h b/drivers/net/phy/xpowers/acx00.h new file mode 100644 index 000000000000..7df6084c7a77 --- /dev/null +++ b/drivers/net/phy/xpowers/acx00.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef __DRIVERS_NET_PHY_XPOWERS_ACX00_H +#define __DRIVERS_NET_PHY_XPOWERS_ACX00_H + +#include +#include + +#define AC300_EPHY_CONTROL_ADDR_OFFSET 16 + +struct device_node; + +struct acx00_ephy_control { + int (*power_on)(struct acx00_ephy_control *control, + unsigned int phy_addr); + int (*power_off)(struct acx00_ephy_control *control); + int (*set_interface)(struct acx00_ephy_control *control, + phy_interface_t interface); +}; + +struct acx00_ephy_control * +ac200_ephy_ctl_create(struct phy_device *phydev, + struct device_node *package_node, + bool has_calibration, u8 calibration); +struct acx00_ephy_control * +ac300_ephy_ctl_create(struct phy_device *phydev, + struct device_node *package_node, u8 calibration); + +#endif