From nobody Fri Sep 25 17:45:56 2026 Received: from mail-ot1-f53.google.com (mail-ot1-f53.google.com [209.85.210.53]) (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 409CC2DCBFA for ; Thu, 10 Sep 2026 00:15:45 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.210.53 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788999347; cv=none; b=giYv8mVqOkZlVjSxeooT6URW9ytxwrbuiy2Oj1FXeqPa+Fb9YLPKYraIaOwbFLCNkvhgOKzudXBjPFicj5ZdA23p9nD288Ekqlensiv+l18E9JVuWOWPOtNNooBNP/La6szsMNnum+/O26/n9JTJr8HY4sktVZCYOLAiytKohU0= 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=HdyCsRjS; arc=none smtp.client-ip=209.85.210.53 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 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="HdyCsRjS" Received: by mail-ot1-f53.google.com with SMTP id 46e09a7af769-7f4e729368fso5825703a34.0 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=vger.kernel.org; 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=HdyCsRjS7f+Pefnlxqq8fACHYBSruRE/XfiUqm2+gf074p/Cyi7iGrCu8jwBE5mRvm Kmkl3o1D9zll3TxFlyQKraqoA8FW0nvRhNkfqmQKBm0S+MSkImdVQgCr/spX1xBbKfT5 k1lIiHbuFawj92zAtAQ1bVKZHnd2gHHLBqqyFqq1lbGqZB1bJ71/XnyGvoiHJTWdBfnx 3eEyHakkMVQJ3as65qgq3cZeVM19rmTSYI0XFt79o5ClvuIs6drwzBlIrFExiVLPfeI6 MwyGVmEHYVjJHr98a51BTKddPqGgjkj2lf5WVfARLDPYOO5aiaSBJHruqfoFNsiL44iX MiTQ== 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=E0I4hMn+zJisFp2MZnM/mEWAXNRg+VERoNJb1fTYJYQg20y52mylLDVk3LLFE2mf/n vW2e6GHipdHk9hjgzQ2Dtmy+Rk91NQNh0zE1xEuALmKVJwXoWuWJV6f784QtVBEFD3nm ajnPrnoOXERGxwAn29iZRkU6SNM1rnTpmJCtO6ublCT3YBCxTvwab+ab2ey1sRcO4Mlc ZN31bI1Ug4uIygPXq6idR6b38LviBsQxryfeb7AdyPXQmRXsA+H3M9W0qDRi3Dh3FwD/ TgLfWYoTw35pusflMUWUvQR9tTY3yAm2IBqCxCDMhPvIb9F6rlmZ3jfaQbI2+Rkve5pU rpyA== X-Forwarded-Encrypted: i=1; AKwUvBwsX7829qIiOLxlb4WVCgduzG/JtrSlMieKt5edq821JzIoorNav8gwADSVWqaOFo8LqKQmvrnXWzX3Wu4=@vger.kernel.org X-Gm-Message-State: AFuF++nuwQH434y7nwaW3BbQIBjNnQZ/EX8PRbqcJjP2W91J9TAud45b rAfZUJpI7SMB2fOyVLc3x55uddWPz/v+JJPOMQNIKfOsJ9EVMYESukVB X-Gm-Gg: AYBFou0Pnv1zzcXKAPfngcdAvnimsDY5VG8BOQzXEWE9zVnqw9o5s78kwbXxF8qFn9y JkvcnHLn0fFhr6E9jvSiIq6KjFGzh+CF8h+yb5Z9XNlYRNR7RGaTqg8flvmDY4h3V99dqnmKcKk sZtqaOmgr2GhN+QjnDK+X4m29g3ncUfyOBtI+H+JvQc5MPk02urXyBXta94ZyvnPySETOu9ONil rkTOZ6vzZhDVzukmeQWDQNQdhTKM/jTUaP8GfXn28lIw9gz/xYKIKXUzO5fJiwqyNzUO5RKroUI v3+ObbxXm48ZGTKtjrA/foIFXPth/xdC9Bws5yH0wyv5BvxDEQgHfElSvjUWj4aTPAN3kIzXlco vden/wZuImEg81JUAk5RFY2EIYy1DALFKO5QLt7g6Pho2QhRhWULoGWBqyZ64GAjxK0EWmPWj6V uUTmyctK+B0rFJzn48tjCARyKDMcDxt6KZwJE3v1pLO0zSv4Vc7C0Qra0OnW91D3u2OjjhvbtDK iO9HkQJyaWKaXPqi+5l69NxEt+zUMJQ7psSbYzjw6OSYRWrwbfKVegSt7xbYZZT8RmJYtlzFUoj tCgBRRH07kwavBdXg4PcGnzuSn3q6W+kfRhDCduDcbhRwXxa4sp/oLJVw4KCqazRV9m75aT4Y6V Exyr2gIddYiyZOQHokFTzNcnlxmeEfg== 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-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: <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 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-pack= age.yaml b/Documentation/devicetree/bindings/net/x-powers,acx00-ephy-packag= e.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 re= port + the same Clause 22 PHY ID and use the same link-side register layout. The + package control paths differ. AC200 package control registers are access= ed + 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 compat= ible. + 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-signif= icant + byte first. Bits 3 through 0 contain the analog calibration code, bi= ts 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, retur= ned + as two bytes. A fixed AC200 package may provide a one-byte calibrati= on + 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 so= me + 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 =3D <1>; + #size-cells =3D <0>; + + ethernet-phy-package@0 { + compatible =3D "x-powers,acx00-ephy-package"; + reg =3D <0>; + #address-cells =3D <1>; + #size-cells =3D <0>; + clocks =3D <&pwm 5>; + vcc-supply =3D <®_3v3>; + nvmem-cells =3D <&ephy_configuration>; + nvmem-cell-names =3D "configuration"; + x-powers,ac200 =3D <&ac200>; + + ethernet-phy@0 { + compatible =3D "ethernet-phy-id0044.1400"; + reg =3D <0>; + }; + }; + }; + +... --=20 2.53.0 From nobody Fri Sep 25 17:45:56 2026 Received: from mail-ot1-f43.google.com (mail-ot1-f43.google.com [209.85.210.43]) (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 549FF2E542C for ; Thu, 10 Sep 2026 00:15:47 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.210.43 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788999353; cv=none; b=D9h6QBLf8RfvO8NilaMXZaqMCk0oIUu4ztNdPzfBTtB0V/p+GRr34YqdprT7ngyuEyBVmGuIE8unZmi5kRwe+1rS8+qvPJ+xlcFB7v2ZtKnFJidnxUHC3Qe4yauugrUgJ9YfoG1ILUJTV0DRP1VpILzBLjXFIrdEd1qLTBknBx4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788999353; c=relaxed/simple; bh=WeqkTTAnDHhndr2fBWczz2f9+0RGY67Fv6aV72bRtIA=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=nZ2fxak4p2WCvzYEgsBZwd8rOmRZJVyS/dbbb7putvLWbYwuzeQ9yMzYKotCqg/iJ8Zly9ve7qiXGTnAsl9Nq7gKgiZi63/ApfUSx1QXDOVsHpXAMKdqFROGZt1jziS8chMzVMZ2GDBQYTuuvoazbWA+KoBiMFi9fXWH9jDb514= 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=NP6KsCFf; arc=none smtp.client-ip=209.85.210.43 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 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="NP6KsCFf" Received: by mail-ot1-f43.google.com with SMTP id 46e09a7af769-7f6617c7536so3024471a34.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=vger.kernel.org; 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=NP6KsCFfO/7A0EIl//Wt9k8ZMHyAiHsuK+8npgLrUY1NYHW7HIWmNmkJNPh5zs8M4k xSQENNbiAfojlLwkfu0iHuM1dG4faLHdGNO+yxptye46bE0Jfhga6R2CXoIzKI1XBR15 wQZyhhyHg9cN3UvdZSrFspktJQzOOeeUWQAP13p90cajwVrXVvtrHU+Drsl6hRr3G9F5 59gygWQ8piGnZR5ZUVhclOs8j28jC1PE/d5Vj0Z21wb5AK0P0UtctA82EJ3Wk6kuDsVr y2EqO9iDrr3/v4RLAf3gqvrwRJMX7IGpW08/EvpEvunDJXPnj0AbwwckVrl+svsE2ZFx MDIA== 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=cJHZRBV+oTJ5AaXF6sq6gw6qPgnjkfBPWICMC0RR82oXN+eqvY5mFX5wygAP0RltDt zBJeEpEMkTmOt040rB8YS/fvGFKE50mlaKUoaQwqPTjk4rlW2pu8qVBpl2DjLAgIz97w pREzn6/x90pobVcP+pG2EbbMU5HY2N6ugkg4J7295JpWDorUnEdTPf0u+Vz5KATr78aX 24UsrsLnbvMiMcbUIlWHJD+E3knxNknHuqiM77hvO46N7Qc5Lj8Rgojx/Mn+KP1DM25H Sq97LYrGgpiDMO3ymnw35tPKxVis3mbVEvqTW0ViQWH789SsXY33V05+SBk/inMs7dtj Wtpg== X-Forwarded-Encrypted: i=1; AKwUvByz/R0d+AUgAI0vZBHEjDSfrszUHKF6fNQ6jvdnqprotEEUntOfTxLA8H114Oma465okFIH4GVSReHTXMw=@vger.kernel.org X-Gm-Message-State: AFuF++nzGFdMBVE/fy+BX62MZAGrtrYkDvzM2jAO6mpEt9ZIWJwVJjkj XDCmJ8d1pwn5S76e9CIm26FexsuHia1gw22DhVuaqS0QyUZ3VBinYnsn X-Gm-Gg: AYBFou3802JQT1kwR4X2G5fsvO0dBpevzcqmjUW1ho5aU6u3RZThvYfrRWHF37pD/T7 I4FZ1zX7+WqZBQ6AJeTMwywMkZU7QqM3iTlot1YdlJ0HI+27W/s3Rb/e2FFuFuPXUTCb1mYth3A tOTaRNRRaMhZu5aQw8WanPA65vBNj8QnRMCBBJzXmzzJx/pwf6CIhvrkOc5R2Ws5/1ZubxuQQ5Q tIqTHZAVKNy7ZAaGI769wAJ+LlXqkqvk0t1EqU7nK2+5uRPpc24nuNzpRsjGgHaUZwOf19YL5L0 89rZQykgwx9rrd8zzEADcLrViiTV/hEaD4mHvZbc+1ffK1G4H6dXd0Iph5fZAjL6f86g9sClfhO FZzq3JbNugWgBx2Uk2T7ZgcIVxjjSSkocEB7MVIfk8nzbBgh2VN6J9k/T7K40M1CCQmacFq/fhp dv6r37Q1Ej8Fc578azICGZGJnoIIf6eLjiynns22QTeClC2Q7z4AxwaJRb61mqzJm0QXTSPKnJU U0ncMtPFOTrybcrNDL6u5KkUG+6hY+V8PDUpBUnBVrV06m0ibCMTN7A/GhreQpqXpnh4Q9jrFVo a3n0KzOaqNQJ1EtUeWSXBuMrNdE/DPEx9d2gSbaZvw5QVo8BZbQZnd1Bx1VbiN9HpgOJK1xLZpX tSepoTSE4dJI5sXfTEMLLVkN8n2AR 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-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: <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 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 =20 +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) +=3D smsc.o obj-$(CONFIG_STE10XP) +=3D ste10Xp.o obj-$(CONFIG_TERANETICS_PHY) +=3D teranetics.o obj-$(CONFIG_VITESSE_PHY) +=3D vitesse.o +obj-$(CONFIG_XPOWERS_ACX00_PHY) +=3D xpowers/ obj-$(CONFIG_XILINX_GMII2RGMII) +=3D xilinx_gmii2rgmii.o diff --git a/drivers/net/phy/xpowers/Makefile b/drivers/net/phy/xpowers/Mak= efile 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 :=3D acx00.o ac200.o ac300.o +obj-$(CONFIG_XPOWERS_ACX00_PHY) +=3D xpowers-acx00.o diff --git a/drivers/net/phy/xpowers/ac200.c b/drivers/net/phy/xpowers/ac20= 0.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 =3D=3D 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 =3D 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 =3D 0; + + if (priv->powered) { + ret =3D ac200_ephy_ctl_write(priv, AC200_EPHY_CTL_REG, + ac200_ephy_ctl_config(priv) | + AC200_EPHY_SHUTDOWN); + err =3D ac200_ephy_ctl_write(priv, AC200_SYS_EPHY_CTL1_REG, 0); + if (!ret) + ret =3D err; + err =3D ac200_ephy_ctl_write(priv, AC200_SYS_EPHY_CTL0_REG, 0); + if (!ret) + ret =3D err; + + priv->powered =3D false; + } + + if (priv->supply_enabled) { + err =3D regulator_disable(priv->vcc); + if (!err) + priv->supply_enabled =3D false; + else if (!ret) + ret =3D err; + } + + return ret; +} + +static int ac200_ephy_ctl_power_off(struct acx00_ephy_control *control) +{ + struct ac200_ephy_ctl *priv =3D + 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 =3D + container_of(control, struct ac200_ephy_ctl, control); + int ret =3D 0; + + if (interface !=3D PHY_INTERFACE_MODE_MII && + interface !=3D PHY_INTERFACE_MODE_RMII) + return -EINVAL; + + if (priv->interface =3D=3D interface) + return 0; + + if (priv->powered) + ret =3D regmap_update_bits(priv->regmap, AC200_EPHY_CTL_REG, + AC200_EPHY_RMII_SEL, + interface =3D=3D PHY_INTERFACE_MODE_RMII ? + AC200_EPHY_RMII_SEL : 0); + if (!ret) + priv->interface =3D interface; + + return ret; +} + +static int ac200_ephy_ctl_power_on(struct acx00_ephy_control *control, + unsigned int phy_addr) +{ + struct ac200_ephy_ctl *priv =3D + 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 =3D=3D phy_addr) + return 0; + + if (priv->powered) { + ret =3D ac200_ephy_ctl_disable(priv); + if (ret) + return ret; + } + priv->phy_addr =3D phy_addr; + + if (!priv->supply_enabled) { + ret =3D regulator_enable(priv->vcc); + if (ret) + return ret; + priv->supply_enabled =3D true; + } + + ephy_ctl =3D 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 =3D ac200_ephy_ctl_write(priv, AC200_SYS_EPHY_CTL0_REG, 0); + if (ret) + goto err_disable; + + ret =3D ac200_ephy_ctl_write(priv, AC200_SYS_EPHY_CTL1_REG, + AC200_EPHY_MII_IO_ENABLE); + if (ret) + goto err_disable; + + ret =3D ac200_ephy_ctl_write(priv, AC200_EPHY_CTL_REG, + ephy_ctl | AC200_EPHY_SHUTDOWN); + if (ret) + goto err_disable; + + ret =3D ac200_ephy_ctl_write(priv, AC200_SYS_EPHY_CTL0_REG, + AC200_EPHY_RESET_DEASSERT | + AC200_EPHY_SYSCLK_ENABLE); + if (ret) + goto err_disable; + + ret =3D ac200_ephy_ctl_write(priv, AC200_EPHY_CTL_REG, ephy_ctl); + if (ret) + goto err_disable; + + priv->powered =3D true; + return 0; + +err_disable: + /* Attempt every step of the shutdown sequence after a partial start. */ + priv->powered =3D 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 =3D &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 =3D devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return ERR_PTR(-ENOMEM); + priv->dev =3D dev; + priv->vcc =3D 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 =3D 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 =3D of_find_i2c_device_by_node(ac200_node); + of_node_put(ac200_node); + if (!client) { + ret =3D 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 =3D 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 =3D dev_get_regmap(&client->dev, NULL); + device_unlock(&client->dev); + if (!priv->regmap) { + ret =3D dev_err_probe(dev, -EPROBE_DEFER, + "AC200 driver is not ready\n"); + goto out_put_client; + } + + if (!has_calibration) { + ret =3D regmap_read(priv->regmap, AC200_EFUSE_EPHY_REG, + &internal_calibration); + if (ret) + goto out_error; + calibration =3D internal_calibration; + } + + /* The vendor driver supplies no transfer function beyond this offset. */ + bps_effuse_code =3D (calibration + AC200_EPHY_BPS_EFFUSE_OFFSET) & + FIELD_MAX(AC200_EPHY_BPS_EFFUSE_MASK); + priv->ephy_ctl =3D + FIELD_PREP(AC200_EPHY_BPS_EFFUSE_MASK, bps_effuse_code); + /* EPHY_MODE and BIST_CLK_EN stay clear for normal operation. */ + + clk =3D clk_get(&client->dev, NULL); + if (IS_ERR(clk)) { + ret =3D PTR_ERR(clk); + goto out_error; + } + + /* The bound AC200 provider enables and exclusively pins this rate. */ + clk_rate =3D clk_get_rate(clk); + clk_put(clk); + + switch (clk_rate) { + case AC200_EPHY_CLK_RATE_24_MHZ: + priv->ephy_ctl |=3D AC200_EPHY_CLK_SEL_24_MHZ; + break; + case AC200_EPHY_CLK_RATE_27_MHZ: + break; + default: + ret =3D dev_err_probe(dev, -EINVAL, + "unsupported AC200 clock rate %lu Hz\n", + clk_rate); + goto out_put_client; + } + + priv->control.power_on =3D ac200_ephy_ctl_power_on; + priv->control.power_off =3D ac200_ephy_ctl_power_off; + priv->control.set_interface =3D ac200_ephy_ctl_set_interface; + /* MII is the reset default used until the MAC supplies its interface. */ + priv->interface =3D PHY_INTERFACE_MODE_MII; + put_device(&client->dev); + + return &priv->control; + +out_error: + ret =3D 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/ac30= 0.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 =3D __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 =3D __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 =3D __phy_package_read(priv->phydev, + AC300_EPHY_CONTROL_ADDR_OFFSET, regnum); + if (ret >=3D 0) { + u16 val =3D (ret & ~mask) | (set & mask); + + ret =3D val =3D=3D 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 =3D=3D 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 =3D 0; + + if (priv->powered) { + ret =3D ac300_ephy_ctl_write(priv, AC300_EPHY_CONFIG_REG, + ac300_ephy_ctl_config(priv) | + AC300_EPHY_SHUTDOWN); + err =3D ac300_ephy_ctl_write(priv, AC300_SYS_IO_REG, 0); + if (!ret) + ret =3D err; + err =3D ac300_ephy_ctl_write(priv, AC300_SYS_CONTROL_REG, + ac300_ephy_ctl_reset_value(priv)); + if (!ret) + ret =3D err; + + clk_disable_unprepare(priv->clk); + priv->powered =3D false; + } + + if (priv->supply_enabled) { + err =3D regulator_disable(priv->vcc); + if (!err) + priv->supply_enabled =3D false; + else if (!ret) + ret =3D err; + } + + return ret; +} + +static int ac300_ephy_ctl_power_off(struct acx00_ephy_control *control) +{ + struct ac300_ephy_ctl *priv =3D + 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 =3D + container_of(control, struct ac300_ephy_ctl, control); + int ret =3D 0; + + if (interface !=3D PHY_INTERFACE_MODE_MII && + interface !=3D PHY_INTERFACE_MODE_RMII) + return -EINVAL; + + if (priv->interface =3D=3D interface) + return 0; + + if (priv->powered) + ret =3D ac300_ephy_ctl_modify(priv, AC300_EPHY_CONFIG_REG, + AC300_EPHY_RMII_SEL, + interface =3D=3D PHY_INTERFACE_MODE_RMII ? + AC300_EPHY_RMII_SEL : 0); + if (!ret) + priv->interface =3D interface; + + return ret; +} + +static int ac300_ephy_ctl_power_on(struct acx00_ephy_control *control, + unsigned int phy_addr) +{ + struct ac300_ephy_ctl *priv =3D + container_of(control, struct ac300_ephy_ctl, control); + u8 package_status; + u16 reset_value; + int sys_control; + int ret; + + if (phy_addr !=3D ac300_ephy_ctl_link_addr(priv)) + return -EINVAL; + + if (priv->powered) + return 0; + + if (!priv->supply_enabled) { + ret =3D regulator_enable(priv->vcc); + if (ret) + return ret; + priv->supply_enabled =3D true; + + /* Wait for the power-on reset interval specified by the manual. */ + fsleep(10000); + } + + ret =3D clk_prepare_enable(priv->clk); + if (ret) + goto err_power_off; + priv->powered =3D true; + + /* Keep the external-supply configuration across chip resets. */ + reset_value =3D ac300_ephy_ctl_reset_value(priv); + ret =3D 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 =3D 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 =3D ac300_ephy_ctl_write(priv, AC300_SYS_CONTROL_REG, + priv->sys_control); + if (ret) + goto err_power_off; + + sys_control =3D ac300_ephy_ctl_read(priv, AC300_SYS_CONTROL_REG); + if (sys_control < 0) { + ret =3D sys_control; + goto err_power_off; + } + + package_status =3D FIELD_GET(AC300_PACKAGE_STATUS_MASK, sys_control); + if ((~package_status & AC300_PACKAGE_PHY_ADDR_MASK) !=3D + ac300_ephy_ctl_link_addr(priv)) { + ret =3D -EINVAL; + goto err_power_off; + } + + priv->internal_dldo =3D package_status & AC300_PACKAGE_POR_INTERNAL_DLDO; + priv->package_known =3D true; + ret =3D 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 =3D ac300_ephy_ctl_write(priv, AC300_SYS_IO_REG, + AC300_SYS_IO_VALUE); + if (ret) + goto err_power_off; + + fsleep(10000); + + ret =3D 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 =3D 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 =3D &phydev->mdio.dev; + struct ac300_ephy_ctl *priv; + unsigned long clk_rate; + u8 bgs_effuse_code; + int ret; + + priv =3D 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 =3D phydev; + + priv->vcc =3D 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 =3D 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 =3D devm_add_action_or_reset(dev, ac300_ephy_clk_put, priv->clk); + if (ret) + return ERR_PTR(ret); + + ret =3D 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 =3D clk_get_rate(priv->clk); + switch (clk_rate) { + case AC300_EPHY_CLK_RATE_24_MHZ: + priv->sys_control =3D AC300_EPHY_CLK_SEL_24_MHZ; + break; + case AC300_EPHY_CLK_RATE_25_MHZ: + priv->sys_control =3D AC300_EPHY_CLK_SEL_25_MHZ; + break; + case AC300_EPHY_CLK_RATE_27_MHZ: + priv->sys_control =3D 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 |=3D AC300_SYS_CONTROL_ENABLE_BITS; + + /* The vendor driver supplies no transfer function beyond this offset. */ + bgs_effuse_code =3D (calibration + AC300_EPHY_BGS_EFFUSE_OFFSET) & + FIELD_MAX(AC300_EPHY_BGS_EFFUSE_MASK); + priv->ephy_config =3D + 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 =3D ac300_ephy_ctl_power_on; + priv->control.power_off =3D ac300_ephy_ctl_power_off; + priv->control.set_interface =3D ac300_ephy_ctl_set_interface; + /* MII is the reset default used until the MAC supplies its interface. */ + priv->interface =3D PHY_INTERFACE_MODE_MII; + + return &priv->control; +} diff --git a/drivers/net/phy/xpowers/acx00.c b/drivers/net/phy/xpowers/acx0= 0.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 A= Cx00 + * 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 =3D __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 =3D phydev->priv; + + lockdep_assert_held(&priv->state_lock); + + if (phydev->interface =3D=3D PHY_INTERFACE_MODE_NA) + return 0; + if (phydev->interface !=3D PHY_INTERFACE_MODE_MII && + phydev->interface !=3D 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 =3D 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 =3D 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 =3D 0; + + cell =3D of_nvmem_cell_get(node, name); + if (IS_ERR(cell)) + return PTR_ERR(cell); + + buf =3D 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 =3D 0; i < len; i++) + val |=3D (u16)buf[i] << (8 * i); + kfree(buf); + *value =3D val; + + return 0; +} + +static int acx00_ephy_init_package(struct phy_device *phydev, + struct acx00_ephy_priv *priv) +{ + struct device *dev =3D &phydev->mdio.dev; + struct device_node *package_node; + bool selectable; + bool fixed_ac300; + bool has_configuration; + u8 calibration; + u32 base_addr; + u16 configuration =3D 0; + int ret; + + package_node =3D of_get_parent(dev->of_node); + if (!package_node) + return -EINVAL; + if (!of_node_name_eq(package_node, "ethernet-phy-package")) { + ret =3D dev_err_probe(dev, -EINVAL, + "PHY is not in an Ethernet PHY package\n"); + goto out_put_node; + } + + ret =3D of_property_read_u32(package_node, "reg", &base_addr); + if (ret || base_addr !=3D phydev->mdio.addr) { + ret =3D dev_err_probe(dev, -EINVAL, + "package and link PHY addresses differ\n"); + goto out_put_node; + } + + selectable =3D of_device_is_compatible(package_node, + "x-powers,acx00-ephy-package"); + fixed_ac300 =3D 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 =3D dev_err_probe(dev, -EINVAL, + "unsupported Ethernet PHY package\n"); + goto out_put_node; + } + + ret =3D devm_of_phy_package_join(dev, phydev, 0); + if (ret) + goto out_put_node; + + has_configuration =3D + of_property_match_string(package_node, "nvmem-cell-names", + "configuration") >=3D 0; + if (has_configuration) { + ret =3D acx00_ephy_read_nvmem_u16(package_node, "configuration", + selectable || fixed_ac300 ? 2 : 1, + &configuration); + if (ret) { + ret =3D dev_err_probe(dev, ret, + "failed to read package configuration\n"); + goto out_put_node; + } + } else if (selectable || fixed_ac300) { + ret =3D dev_err_probe(dev, -EINVAL, + "package configuration is required\n"); + goto out_put_node; + } + + if (selectable) { + priv->is_ac300 =3D + configuration & ACX00_EPHY_CONFIG_VARIANT_AC300; + } else { + priv->is_ac300 =3D fixed_ac300; + if (has_configuration && + !!(configuration & ACX00_EPHY_CONFIG_VARIANT_AC300) !=3D + priv->is_ac300) { + ret =3D dev_err_probe(dev, -EINVAL, + "configuration does not match package\n"); + goto out_put_node; + } + } + + calibration =3D FIELD_GET(ACX00_EPHY_CONFIG_CALIBRATION_MASK, + configuration); + + priv->use_low_calibration_tuning =3D + priv->is_ac300 && + !!(configuration & ACX00_EPHY_CONFIG_CALIBRATION_LOW); + priv->xmii_rx_clock_inverted =3D + of_property_read_bool(package_node, + "x-powers,xmii-rx-clock-inverted"); + + if (priv->is_ac300) + priv->control =3D + ac300_ephy_ctl_create(phydev, package_node, + calibration); + else + priv->control =3D + ac200_ephy_ctl_create(phydev, package_node, + has_configuration, + calibration); + ret =3D 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 =3D phydev->priv; + int ret; + + mutex_lock(&priv->state_lock); + ret =3D 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 =3D &phydev->mdio.dev; + struct acx00_ephy_priv *priv; + int ret; + + if (!dev->of_node) + return -ENODEV; + + priv =3D devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->phydev =3D phydev; + mutex_init(&priv->state_lock); + ret =3D acx00_ephy_init_package(phydev, priv); + if (ret) + return ret; + + phydev->priv =3D priv; + ret =3D devm_add_action_or_reset(dev, acx00_ephy_control_release, priv); + if (ret) + return ret; + + mutex_lock(&priv->state_lock); + ret =3D 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 =3D phydev->priv; + int ret; + + mutex_lock(&priv->state_lock); + + ret =3D acx00_ephy_set_interface(phydev); + if (ret) + goto out_power_off; + + ret =3D acx00_ephy_control_power_on(priv); + if (ret) + goto out_power_off; + + /* ACx00 can acknowledge reset in power-down without restarting. */ + ret =3D genphy_resume(phydev); + if (ret) + goto out_power_off; + + ret =3D 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 =3D phydev->priv; + u16 afe_eq_rx_detect =3D priv->is_ac300 ? + AC300_PAGE6_AFE_EQ_RX_DETECT_VALUE : + AC200_PAGE6_AFE_EQ_RX_DETECT_VALUE; + u16 tx_level_value =3D ACX00_PAGE6_TX_LEVEL_DEFAULT_VALUE; + u16 global_config; + int oldpage; + int ret; + + lockdep_assert_held(&priv->state_lock); + + global_config =3D FIELD_PREP(ACX00_PAGE0_MDI_MODE_MASK, + ACX00_PAGE0_MDI_MODE_AUTO); + if (priv->xmii_rx_clock_inverted) + global_config |=3D ACX00_PAGE0_XMII_RX_CLOCK_INVERT; + + ret =3D 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 =3D + ACX00_PAGE6_AFE_EQ_RX_DETECT_LOW_CAL_VALUE; + tx_level_value =3D ACX00_PAGE6_TX_LEVEL_LOW_CAL_VALUE; + } + + oldpage =3D phy_select_page(phydev, ACX00_PAGE_1); + if (oldpage < 0) + return phy_restore_page(phydev, oldpage, oldpage); + + ret =3D __phy_write(phydev, ACX00_PAGE1_APS_CONTROL_REG, + ACX00_PAGE1_APS_DISABLED_4S_VALUE); + if (ret) + goto out_restore_page; + ret =3D __phy_modify(phydev, ACX00_PAGE1_UAPS_CONTROL_REG, + ACX00_PAGE1_UAPS_ENABLE, 0); + if (ret) + goto out_restore_page; + ret =3D __phy_modify(phydev, ACX00_PAGE1_INTELLIGENT_EEE_CONTROL_REG, + ACX00_PAGE1_INTELLIGENT_EEE_ENABLE, 0); + if (ret) + goto out_restore_page; + + ret =3D acx00_ephy_write_page(phydev, ACX00_PAGE_2); + if (ret) + goto out_restore_page; + ret =3D __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 =3D acx00_ephy_write_page(phydev, ACX00_PAGE_6); + if (ret) + goto out_restore_page; + ret =3D __phy_write(phydev, ACX00_PAGE6_AFE_EQ_RX_DETECT_CONTROL_REG, + afe_eq_rx_detect); + if (ret) + goto out_restore_page; + ret =3D __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 =3D __phy_write(phydev, ACX00_PAGE6_ADC_CONTROL_REG, + ACX00_PAGE6_ADC_CONTROL_LOW_CAL_VALUE); + if (ret) + goto out_restore_page; + } + ret =3D __phy_write(phydev, ACX00_PAGE6_TX_LEVEL_REG, tx_level_value); + if (ret) + goto out_restore_page; + + ret =3D acx00_ephy_write_page(phydev, ACX00_PAGE_8); + if (ret) + goto out_restore_page; + if (priv->is_ac300 && priv->use_low_calibration_tuning) { + ret =3D __phy_write(phydev, ACX00_PAGE8_AUTO_CAL_CONTROL_REG, + ACX00_PAGE8_AUTO_CAL_LOW_VALUE); + if (ret) + goto out_restore_page; + } + ret =3D __phy_write(phydev, ACX00_PAGE8_AFE_CONTROL_REG, + ACX00_PAGE8_AFE_CONTROL_VALUE); + +out_restore_page: + ret =3D phy_restore_page(phydev, oldpage, ret); + if (ret) + return ret; + + /* Restore the standard EEE policy retained by phylib across resets. */ + ret =3D genphy_c45_an_config_eee_aneg(phydev); + if (ret <=3D 0) + return ret; + + return genphy_restart_aneg(phydev); +} + +static int acx00_ephy_config_init(struct phy_device *phydev) +{ + struct acx00_ephy_priv *priv =3D phydev->priv; + int ret; + + mutex_lock(&priv->state_lock); + ret =3D __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 =3D phydev->priv; + int ret; + + lockdep_assert_held(&phydev->lock); + lockdep_assert_held(&priv->state_lock); + + ret =3D acx00_ephy_set_interface(phydev); + if (ret) + goto out_power_off; + + ret =3D acx00_ephy_control_power_on(priv); + if (ret) + goto out_power_off; + + ret =3D genphy_resume(phydev); + if (ret) + goto out_power_off; + + /* Powering off the control block loses the vendor-page state. */ + ret =3D __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 =3D phydev->priv; + int ret; + + mutex_lock(&priv->state_lock); + ret =3D __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 =3D 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 =3D genphy_suspend(phydev); + if (ret) + goto out_unlock; + + ret =3D acx00_ephy_control_power_off(priv); + if (ret) { + resume_ret =3D __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 =3D 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 =3D 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 =3D of_get_parent(node); + if (!package_node) + return 0; + match =3D 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[] =3D { + { + PHY_ID_MATCH_MODEL(ACX00_EPHY_ID), + .name =3D "X-Powers AC200/AC300 EPHY", + .match_phy_device =3D acx00_ephy_match_phy_device, + .probe =3D acx00_ephy_probe, + .read_page =3D acx00_ephy_read_page, + .write_page =3D acx00_ephy_write_page, + .soft_reset =3D acx00_ephy_soft_reset, + .config_init =3D acx00_ephy_config_init, + .disable_autonomous_eee =3D acx00_ephy_disable_autonomous_eee, + .suspend =3D acx00_ephy_suspend, + .resume =3D acx00_ephy_resume, + }, +}; +module_phy_driver(acx00_ephy_driver); + +static const struct mdio_device_id __maybe_unused acx00_ephy_tbl[] =3D { + { 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/acx0= 0.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 --=20 2.53.0