From nobody Tue Sep 29 14:55:08 2026 Received: from mail-oo1-f53.google.com (mail-oo1-f53.google.com [209.85.161.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 0B5613DC4DD for ; Fri, 7 Aug 2026 04:51:49 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.161.53 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786078322; cv=none; b=lPaK5Db8FXupqnb9wAU8erm1LMpqtLcSxdnKwmYJ+csf98OOBZJfdJkLX7Uy9I33tDSR7dy9r4Ukh2CPYIDfVJfEMB2j7vG5Z2q6EMg3DlG5/iYWmBzt4u/LoLOm20HfxK2fyjRRUURDyC+SdtSQU1N4++tQUUBQvIvL7KVnw14= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786078322; c=relaxed/simple; bh=akgH7FdSu8hkxoqBxRW5GrRYagY+QZDl3N0wKNP3Em8=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=BV0DOzAUpqNvK+1ODgzNy0O3bokK1MF061qMCnoaefCU+NZNpiOuM1xkTvT2T5lNf27Mso4DklsR+ucXY0mLSy8zqKwEsLjfyVUZ+UMICrNt257GZ3pq7FcwR23ApcUQ2rPU/I9Ee3DDbkx+BKpnf87N6bhwsujtL/O2Wx8Qsc0= 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=FVrsIXl6; arc=none smtp.client-ip=209.85.161.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="FVrsIXl6" Received: by mail-oo1-f53.google.com with SMTP id 006d021491bc7-6aec68b03e2so729069eaf.3 for ; Thu, 06 Aug 2026 21:51:44 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1786078299; x=1786683099; 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=+3lkDhLHoHy7Deukicju73PIuJo/3t9WiJOLY5QufbM=; b=FVrsIXl6OE9cuNfhHiseBY+FKmPwv7vKVL1xDapMz18tzSQKKWaEJfIOJgj5pV8qFB aM7MK092m7Gjy4UvnxKN0j6T7KwN/W6GFqN0prKcOV/j6AdcmXW5LZvDEAepzpOcsF4E H1ycg9blekSm06NNQvIXR9U5IMBoA6Zx0e2xM3QIR4cJulyqkCJkpU03x3yuVWhrVphH AKneDsx5xdNdyQHyKwjGtdEsw/9+ChzER2cH6tGaQPCP7hc+gT91AeW/dWzh711zuh5o VFrkt58GSNNiFbEo0gWyEhaQ/OMuQA683aFsyHnkCmbl5I0yDOMemgjV+cC6V5PHdEhh WEkw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1786078299; x=1786683099; 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=+3lkDhLHoHy7Deukicju73PIuJo/3t9WiJOLY5QufbM=; b=Ojua3KOJqxJyR8KU6y1F5QTjHeE3cOR4w2Elz3y0/27lXAMofRoDEm+dDjrYW+Ti6C 6wS1l7G5vra1ALvVvpgnVKVKmOkw8kd3UIDy4yvqhxz+EJ5OThGlb0DVwLcP0RT1DhPm r675hY/Ca1jKxI3QNrRKWTqVzaX8Wq0UqjKjDkgYDE3egCjEzOf5KXuYQqKfHqU2zsRG IRVAUrnL7K09eMfX4k5NyClnTjkO+E0xaUySnMvd+F0WbwJreJ+ujLuBjKVYVZWsWvm6 ITDxem2fD1p7KL0nGcjL9kz02q/UVMmZvkQnSswD3dQqwW5Rg7T+zFqiZXRLx2632CYM a5Aw== X-Forwarded-Encrypted: i=1; AHgh+RpoT1WmZPNe/Atu4f9aWYH3eQxAbZ1YkhiKy7i4niVVtLFOuNylzoHqbWIc2+XBMl0rm8p9OJ5G43v0EGk=@vger.kernel.org X-Gm-Message-State: AOJu0YyfINguUxlV9vEhEddH4aCpr1TBlYBvlKJkzzjsKfr3Pr2ePeYO TQk52aEB+lyS/GWL5HhR52tbJfzOrsd8LKJeUeX3TPzIwtpAG1U+jJ2V X-Gm-Gg: AR+sD12eueBpxTVzOg2LvUDL/E5GioErXiW+pS80/DOoj9lTcpFOynGw4VMLCHE+sAC Ohr0h79pVvGajIdO1xIcjuyP9N7a8RNyp9UNtDu87K9UGnYI6etqhCMsFnX68mGdfj0IoNjx4Qb m4C7hRIjX/ELclHospUsoImrkItI0UkSedCeLvdUQBaYR0k0EMaSjvkDFffd+xMVyObwrSPOXjs lSCxa/NxbwlPj68JNehtWBzFcHK8MYd6ka1Eq0Cwl13HauhaiRnSJfyzDujm6OpNljtr9QQ8x/4 f49wvE0PCalk5SA/muek1MgHYN0L5bZqXO0hQV/n9l9CZo/GAAm2UMo5X2xSThmT0vDwPURHrE2 1s1bLJv4atrKT+nIke6nq5ccNanZiSX2e9aBOm76icD4h9w24fueQZLN+H4cXcjxUM7VNrn25r4 avL5/SAjh1UrOYCjUbyMzekosc1bIxYAA9koMLapxd9znMAJuZNoNgv63DJjSkiiRNHLr2CSzZN 2XLaPMWOYGbSP1FksvsLVHnSy0h2Qlga+eS1zGrsYE9WBLDiFFF0OYoVP2toEDt6eTk21Q7ABKG 9+srmyBJHGOob4llOLx1ewyKoseg+VNQzJUijV9pl4bVvzAGcQaNZ7hPveDMWxIo7/RoYCoCA4T Y9qNB+Lv4TcV57Oph7w== X-Received: by 2002:a05:6820:1996:b0:6b0:1b86:fa8 with SMTP id 006d021491bc7-6b01b8625afmr4347810eaf.15.1786078298727; Thu, 06 Aug 2026 21:51:38 -0700 (PDT) Received: from [127.0.1.1] (184-96-154-59.hlrn.qwest.net. [184.96.154.59]) by smtp.gmail.com with ESMTPSA id 006d021491bc7-6b02bbbf755sm1519726eaf.2.2026.08.06.21.51.37 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 06 Aug 2026 21:51:38 -0700 (PDT) From: James Hilliard Date: Thu, 06 Aug 2026 22:51:26 -0600 Subject: [PATCH net-next v4 1/4] dt-bindings: mfd: x-powers: Add AC200 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: <20260806-submit-acx00-of-dynamic-v1-v4-1-9bf2f7441632@gmail.com> References: <20260806-submit-acx00-of-dynamic-v1-v4-0-9bf2f7441632@gmail.com> In-Reply-To: <20260806-submit-acx00-of-dynamic-v1-v4-0-9bf2f7441632@gmail.com> To: Lee Jones , Rob Herring , Krzysztof Kozlowski , Conor Dooley , James Hilliard , Andrew Lunn , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Andrew Lunn , Heiner Kallweit , Russell King Cc: mfd@lists.linux.dev, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, netdev@vger.kernel.org X-Mailer: b4 0.15.2 The AC200 is an I2C-controlled mixed-signal companion IC containing audio, video, RTC and Fast Ethernet PHY functions. Describe the parent device, its input clock and the four package supplies. The Ethernet PHY is represented by a PHY package on its MDIO bus, with a phandle back to this device for access to the package control registers, so it does not require an MFD child node. The input clock must run at 24 or 27 MHz when the Ethernet PHY is used, matching the rates encoded by its documented clock selector. Signed-off-by: James Hilliard --- .../devicetree/bindings/mfd/x-powers,ac200.yaml | 69 ++++++++++++++++++= ++++ 1 file changed, 69 insertions(+) diff --git a/Documentation/devicetree/bindings/mfd/x-powers,ac200.yaml b/Do= cumentation/devicetree/bindings/mfd/x-powers,ac200.yaml new file mode 100644 index 000000000000..b7e8d9bd5661 --- /dev/null +++ b/Documentation/devicetree/bindings/mfd/x-powers,ac200.yaml @@ -0,0 +1,69 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/mfd/x-powers,ac200.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: X-Powers AC200 mixed-signal IC + +maintainers: + - James Hilliard + +description: + The AC200 is a mixed-signal companion IC containing audio, video, RTC and + Fast Ethernet PHY functions. Its control registers are accessed over I2C. + +properties: + compatible: + const: x-powers,ac200 + + reg: + maxItems: 1 + + clocks: + maxItems: 1 + description: + AC200 input clock. When using the Ethernet PHY, its configured rate = must + be 24 or 27 MHz, matching the rates encoded by the documented EPHY c= lock + selector. + + ac-ldoin-supply: + description: 3.3 V supply for the audio-codec LDO input + + ephy-vcc-supply: + description: 3.3 V supply for the Ethernet PHY analog front end + + rtc-vcc-supply: + description: 3.3 V supply for the RTC + + tv-vcc-supply: + description: 3.3 V supply for the TV encoder DAC + +required: + - compatible + - reg + - clocks + - ac-ldoin-supply + - ephy-vcc-supply + - rtc-vcc-supply + - tv-vcc-supply + +additionalProperties: false + +examples: + - | + i2c { + #address-cells =3D <1>; + #size-cells =3D <0>; + + mixed-signal@10 { + compatible =3D "x-powers,ac200"; + reg =3D <0x10>; + clocks =3D <&pwm 5>; + ac-ldoin-supply =3D <®_3v3>; + ephy-vcc-supply =3D <®_3v3>; + rtc-vcc-supply =3D <®_3v3>; + tv-vcc-supply =3D <®_3v3>; + }; + }; +... --=20 2.53.0 From nobody Tue Sep 29 14:55:08 2026 Received: from mail-oo1-f48.google.com (mail-oo1-f48.google.com [209.85.161.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 76B15385515 for ; Fri, 7 Aug 2026 04:51:52 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.161.48 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786078320; cv=none; b=KsXNYSDKoYObkYebErhWWa1qI2hvRujLIGMo8ITKrgyUtJrkUY1iBiSBiGn/3UZspcJo4s96Wv1CLj8dt0hXDHllOE2GK1L5FzsghvS2eAiQssCyTPcxjJkgREv8yJ1q72pp9hjFPa4vFPub0Su6JF0Xr5oNs6iwnTdGOgh9rQ0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786078320; c=relaxed/simple; bh=TlpInWm9XOJkb5udZMBPKZH2dMguh+THVGbaxQuYsbY=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=svrTZtqrIaWaGqGhMYlHEixSgwJ9xQlya5wzKkzmB8DsJTeQ3adwRBbOqgy7GMRuuBIVPOVJN/bSjVTp6hcTa9TunYiEWvhnL2hkX7ehesLOAfOJYmjyzJQErqy1JNpUQrVAO4TbufdUDnQW2Avp8MNqyS9GRXGIgYeJTZYIsQI= 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=XrtD1kIp; arc=none smtp.client-ip=209.85.161.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 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="XrtD1kIp" Received: by mail-oo1-f48.google.com with SMTP id 006d021491bc7-6acb63301acso1323956eaf.0 for ; Thu, 06 Aug 2026 21:51:45 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1786078300; x=1786683100; 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=DINgOwIeeRrbClV4fqYU1hiU8YusN/1zP2aX4+OhDw0=; b=XrtD1kIpWlh+oEVOUecHJv1DhDhVa6AenIItcIiubZxTGM0WOWIXK3U6RYssVTFrVA nMUZ5l9Ycya4Xbq9+Do/LAIVGQGljiSpe8cb2srZyEpQsFpCVCOLnVOd+CXP0A8zZyeL F3VGH/J5MuEryKVhdTSzIDt59VL8/z/pwWmtFa60kNSa2GO7ybZKNkeSs3SUO5KpvAOZ f7bZRbKYsowWVodkmnbU1Qf57xemDkLUP7FqOagBQTzTiRNJW08BIbTTPStGX6n30mY5 /UirE/jDdkpkBqfb+XCk1HJ+QqAS0kwPZKoSHveG0SZWNIlEc7XZhws80DiSMkDjPBj/ gX7A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1786078300; x=1786683100; 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=DINgOwIeeRrbClV4fqYU1hiU8YusN/1zP2aX4+OhDw0=; b=T5GIHbYwAeh59+BWP4U88AlV3ZpTR3Uyl2GWJN7+wlXY4JHU9WsO2SCVdU4fVs/YUx GA4LSRTo9928FU9bHLOMuLubVwGlG7h52XaT/Z1Xz4UTUVJYsCBaDzIR/lhTx1YqEOKT g4cyVAZL9xprhwUFWd9LahKF0lNNx4SQWQjjaaJoSzoLo1HVltPQIti1f9Jx834a2AnQ gUoPylg/G3TIVajkYqV1K2WMzhaD4lg3cyabbPWKUQcPnc60k8gKWg+Sr08E8yR4p7es kYWFdpMNcLsWBn5/fFOhO5bIaGCRGLgG3ccDDfzsebYIdUuZ360GerkydGaXi24sWkTZ 2i3A== X-Forwarded-Encrypted: i=1; AHgh+RodI7NrPz9VtqFTmTLFxEVffduvxzNJhv07vOB/C0RC9DHDlL0rfevp7odP0VW2i65U+Nk0BY593jfGMBY=@vger.kernel.org X-Gm-Message-State: AOJu0YzsLebUkSKVlfR0I78yr3B8PxeTB2M1DHtTdrwe8U/3OGnNH5D/ Q6sCuOgIL8Qcp1O+t2oSDou9Z90LkwOGKiK0CqEX4fTpAt0Z2N1QUuPY X-Gm-Gg: AR+sD124mFOUbUqS0sizv2/Sa7YGIKg/OavzW3mVRAI68fnB8Gm5wLUo3wQGXoxQpXQ Tzwg+aMKttMFoSi+bbJqeLdHsfkoRUOwrGbCt3Y6yr8I3dpyUQpnURBnun8f2Sy6BZBkayQ1F4Y bou6DtmoxgzCW8EVNWR5++TmUtCkKf16YfoSWLIuKm9mjJNX72Q83eO6Ty/85tMPwDPgmIunU0W IHZ1SBGBG1lgkLwe/tVuuNmceeWkUW8SYUStAkeunjqSm0E+/rlSoctblYHZX7/3t5OX/iCpLJr KEC0Dk6gtH9vfNWa5ci+aQqylC96P+sEVey1cS40lbRUoQBXxhuBlw7ItJOrF//vr7lQr+LcTPZ n73jqRuPbZj1bSONj0yFAkt387Wtkpumr2iPqJ77Ak47TF6lS2YHUs+oLZTQfiXDDfghn+3JQhy NDOUzSytiW82V87ejItOUuGF84hSf6iffjHZWUYXxEORG2wtMetjpwh7rTxMrhJRea9dpm0ifWn NKiP3m1Mqf2e6KZAeSuVF27bmmGyhQ2+B2H5tV3kz4r6fVP7tkC6F4MPNUa+Wa+4SvvuXwwbHgQ 1wmcjMPpESoXe1TAOIbySc0KvdmxrXXw3fZcnyCVuOV75WL5FUPWHVwxU2SCmVTlonWIFpW5Ma0 kR4RLlcexZtnylgfG X-Received: by 2002:a05:6820:1693:b0:6a1:64df:7324 with SMTP id 006d021491bc7-6b016c97a1bmr4693925eaf.7.1786078299863; Thu, 06 Aug 2026 21:51:39 -0700 (PDT) Received: from [127.0.1.1] (184-96-154-59.hlrn.qwest.net. [184.96.154.59]) by smtp.gmail.com with ESMTPSA id 006d021491bc7-6b02bbbf755sm1519726eaf.2.2026.08.06.21.51.38 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 06 Aug 2026 21:51:39 -0700 (PDT) From: James Hilliard Date: Thu, 06 Aug 2026 22:51:27 -0600 Subject: [PATCH net-next v4 2/4] mfd: ac200: Add X-Powers AC200 support Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260806-submit-acx00-of-dynamic-v1-v4-2-9bf2f7441632@gmail.com> References: <20260806-submit-acx00-of-dynamic-v1-v4-0-9bf2f7441632@gmail.com> In-Reply-To: <20260806-submit-acx00-of-dynamic-v1-v4-0-9bf2f7441632@gmail.com> To: Lee Jones , Rob Herring , Krzysztof Kozlowski , Conor Dooley , James Hilliard , Andrew Lunn , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Andrew Lunn , Heiner Kallweit , Russell King Cc: mfd@lists.linux.dev, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, netdev@vger.kernel.org X-Mailer: b4 0.15.2 The X-Powers AC200 is a mixed-signal companion IC with a paged register map accessed over I2C. Enable the package supplies and input clock and prevent the clock rate from changing. Match the vendor driver's 40 ms wait before the first register access, initialize the paged regmap, report the chip and package revision, and apply common reset. The regmap is available to function drivers which reference the AC200, including the Ethernet PHY package driver. Cache only the common page selector. Individual functions can reset independently and invalidate their other registers without regmap's knowledge, so leave all functional registers volatile. Reset the chip during managed teardown and system shutdown. Consumers can use device links to ensure that they unbind before the shared AC200 resources are released. Signed-off-by: James Hilliard --- drivers/mfd/Kconfig | 11 ++++ drivers/mfd/Makefile | 1 + drivers/mfd/ac200.c | 176 +++++++++++++++++++++++++++++++++++++++++++++++= ++++ 3 files changed, 188 insertions(+) diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 763ce6a34782..499269c85812 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -205,6 +205,17 @@ config MFD_AC100 This driver include only the core APIs. You have to select individual components like codecs or RTC under the corresponding menus. =20 +config MFD_AC200 + tristate "X-Powers AC200" + depends on I2C + depends on OF + select REGMAP_I2C + help + Support for the X-Powers AC200 mixed-signal companion IC. The AC200 + contains audio, video, RTC and Fast Ethernet PHY functions and is + co-packaged with some Allwinner H6 and H616 SoCs. This driver provides + the shared register access used by the individual function drivers. + config MFD_AXP20X tristate select MFD_CORE diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index dd4bb7e77c33..890e76a9ad00 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -150,6 +150,7 @@ obj-$(CONFIG_MFD_DA9052_SPI) +=3D da9052-spi.o obj-$(CONFIG_MFD_DA9052_I2C) +=3D da9052-i2c.o =20 obj-$(CONFIG_MFD_AC100) +=3D ac100.o +obj-$(CONFIG_MFD_AC200) +=3D ac200.o obj-$(CONFIG_MFD_AXP20X) +=3D axp20x.o obj-$(CONFIG_MFD_AXP20X_I2C) +=3D axp20x-i2c.o obj-$(CONFIG_MFD_AXP20X_RSB) +=3D axp20x-rsb.o diff --git a/drivers/mfd/ac200.c b/drivers/mfd/ac200.c new file mode 100644 index 000000000000..798bb4173b14 --- /dev/null +++ b/drivers/mfd/ac200.c @@ -0,0 +1,176 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * MFD core driver for the X-Powers AC200 + * + * Copyright (C) 2019 Jernej Skrabec + * Copyright (C) 2026 James Hilliard + * + * Based on the AC100 driver: + * Copyright (C) 2016 Chen-Yu Tsai + */ + +#include +#include +#include +#include +#include +#include +#include + +#define AC200_SYS_VERSION_REG 0x0000 +#define AC200_SYS_VERSION_PACKAGE_MASK GENMASK(15, 14) +#define AC200_SYS_VERSION_CHIP_MASK GENMASK(11, 0) + +#define AC200_SYS_CONTROL_REG 0x0002 +#define AC200_SYS_CONTROL_CHIP_RESET_DEASSERT BIT(0) + +/* Interface register accessible from every register page. */ +#define AC200_TWI_REG_ADDR_H 0x00fe +#define AC200_MAX_REG 0xa1f2 + +struct ac200 { + struct regmap *regmap; +}; + +static const char * const ac200_supplies[] =3D { + "ac-ldoin", + "ephy-vcc", + "rtc-vcc", + "tv-vcc", +}; + +static const struct regmap_range_cfg ac200_range_cfg[] =3D { + { + .range_max =3D AC200_MAX_REG, + .selector_reg =3D AC200_TWI_REG_ADDR_H, + .selector_mask =3D 0xff, + .window_len =3D 256, + }, +}; + +/* + * Each AC200 sub-block can reset independently, invalidating its register + * contents without regmap's knowledge. Cache only the common page selecto= r; + * this avoids a selector read-modify-write for every access on the same p= age + * without ever returning stale functional-register values. + */ +static bool ac200_volatile_reg(struct device *dev, unsigned int reg) +{ + return reg !=3D AC200_TWI_REG_ADDR_H; +} + +static const struct regmap_config ac200_regmap_config =3D { + .name =3D "ac200", + .reg_bits =3D 8, + .reg_stride =3D 2, + .val_bits =3D 16, + .ranges =3D ac200_range_cfg, + .num_ranges =3D ARRAY_SIZE(ac200_range_cfg), + .max_register =3D AC200_MAX_REG, + .volatile_reg =3D ac200_volatile_reg, + .cache_type =3D REGCACHE_MAPLE, +}; + +static void ac200_disable(void *data) +{ + struct ac200 *ddata =3D data; + + regmap_write(ddata->regmap, AC200_SYS_CONTROL_REG, 0); +} + +static int ac200_probe(struct i2c_client *client) +{ + struct device *dev =3D &client->dev; + unsigned int version; + struct ac200 *ddata; + struct clk *clk; + int ret; + + ddata =3D devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL); + if (!ddata) + return -ENOMEM; + + ret =3D devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(ac200_supplies), + ac200_supplies); + if (ret) + return dev_err_probe(dev, ret, "failed to enable supplies\n"); + + clk =3D devm_clk_get_enabled(dev, NULL); + if (IS_ERR(clk)) + return dev_err_probe(dev, PTR_ERR(clk), + "failed to enable input clock\n"); + + ret =3D devm_clk_rate_exclusive_get(dev, clk); + if (ret) + return dev_err_probe(dev, ret, "failed to lock clock rate\n"); + + ddata->regmap =3D devm_regmap_init_i2c(client, &ac200_regmap_config); + if (IS_ERR(ddata->regmap)) + return dev_err_probe(dev, PTR_ERR(ddata->regmap), + "failed to initialize regmap\n"); + + i2c_set_clientdata(client, ddata); + + /* + * No minimum delay is documented. Match the vendor driver's 40 ms delay + * before its first AC200 register access after enabling the input clock. + */ + msleep(40); + + ret =3D regmap_read(ddata->regmap, AC200_SYS_VERSION_REG, &version); + if (ret) + return dev_err_probe(dev, ret, + "failed to read chip version\n"); + + dev_info(dev, "AC200 revision %#lx in package %lu\n", + FIELD_GET(AC200_SYS_VERSION_CHIP_MASK, version), + FIELD_GET(AC200_SYS_VERSION_PACKAGE_MASK, version)); + + /* Reset the chip after dependent function drivers have unbound. */ + ret =3D devm_add_action_or_reset(dev, ac200_disable, ddata); + if (ret) + return ret; + + ret =3D regmap_write(ddata->regmap, AC200_SYS_CONTROL_REG, 0); + if (ret) + return ret; + + ret =3D regmap_write(ddata->regmap, AC200_SYS_CONTROL_REG, + AC200_SYS_CONTROL_CHIP_RESET_DEASSERT); + if (ret) + return ret; + + return 0; +} + +static void ac200_shutdown(struct i2c_client *client) +{ + ac200_disable(i2c_get_clientdata(client)); +} + +static const struct of_device_id ac200_of_match[] =3D { + { .compatible =3D "x-powers,ac200" }, + { } +}; +MODULE_DEVICE_TABLE(of, ac200_of_match); + +static const struct i2c_device_id ac200_i2c_ids[] =3D { + { .name =3D "ac200" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ac200_i2c_ids); + +static struct i2c_driver ac200_driver =3D { + .driver =3D { + .name =3D "ac200", + .of_match_table =3D ac200_of_match, + }, + .probe =3D ac200_probe, + .shutdown =3D ac200_shutdown, + .id_table =3D ac200_i2c_ids, +}; +module_i2c_driver(ac200_driver); + +MODULE_AUTHOR("James Hilliard "); +MODULE_DESCRIPTION("X-Powers AC200 MFD core driver"); +MODULE_LICENSE("GPL"); --=20 2.53.0 From nobody Tue Sep 29 14:55:08 2026 Received: from mail-oo1-f48.google.com (mail-oo1-f48.google.com [209.85.161.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 7697F1D61BC for ; Fri, 7 Aug 2026 04:51:52 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.161.48 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786078328; cv=none; b=j0ZKom6jQjxSgXjBRnQWqdPcrGVuKZ4HaKnGh0tQA2f0bist/Qe6h9qRpQ3urb7iOg+1+FUA8fhji8oj16YsLK6fmjiZwenotwm3gVBxsW7zpWa47YDgNIeuMzwG7EkHHbV9czzlxne2OmYd6l4ykmzrRT9xiZKbt4cok1iZS0U= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786078328; c=relaxed/simple; bh=PQrmptIkB9+FHFCt7654nCnkDwp4ijPJju9VLqHK+oU=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=YEV654eNxgYUKwI23baiVNsHlIjwL4s2Soqsk/qJAnYzRnYCmu+2SZhjHdjOX4CSIXimU8YJ3YHPrXrL3qvNuYtZ0hJiIo5CwxA5ph+V1ww15C4p110gYwg++UxSa24OeViXAGOIVfMMoJZUsxqEHoc92rx030PfiLryGXztvVo= 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=PGOTj812; arc=none smtp.client-ip=209.85.161.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 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="PGOTj812" Received: by mail-oo1-f48.google.com with SMTP id 006d021491bc7-6ae8c4b9822so1308808eaf.1 for ; Thu, 06 Aug 2026 21:51:51 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1786078301; x=1786683101; 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=uKnDaJp5ZviVMN/jgF3Auq00JWcI1+Cd7CJPkOYq6WA=; b=PGOTj812feWXTUIA0b20qNu4rIu6f/JNC5wNYKLR86pdVgM4pBe7vuOCdAxuzhySyZ Wxm0j5TRZ4ECuPQchwI1q6J9/6rv2ZU9RyyJKHi35Dc4UbKc25ZnQWmC6JVRZTy+fyeh 4JLOz+oBThy1ZA4dvp3J0iUTNESlU5bFVK2+3yoTnSFVOaZq8G0QrtLQ0be33vROMDb4 dKiUgerE58wijzObOv+nc9neK4STGj6qRCZ/JCquB9TPQ552bVFWCUPrKPnWzIvxWO+y m6rdoDsnbFOtqz7Dndx5we8tP28PWnjBxWj3y6nSaG6BtWVWHJ4fOy6l4vd1WHGbFM6N jriQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1786078301; x=1786683101; 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=uKnDaJp5ZviVMN/jgF3Auq00JWcI1+Cd7CJPkOYq6WA=; b=J98U/OvU1x3ycnvxPIa47ekElGa48nlDhlWToabFglTc9syrd7qlP112uC++VEfkcG bsO2lyNtBrs2r918SfdNw2RNX8XeB9adddXeXrQA6FlwXOxFibO6J5C/sC0MzgI+XKvK ULvwFA768GknRWUYg2RmjUfSHvLTYZmFr7WTSsyC9r228j9c7IDJx1J3fnI856fAR3lT iXc3iUbscJPF2q4PE2tK6Bire+R4DVx15DxRIOntUi9fMOMy/uzfhqOzXxAzOedYnO6t i/uEq7CEsS1TCXxiJcrZxQFne88uo93K03X9Br7ErjV2aAdnAAYcXRM9mDdIYKKe3NSW 4e1Q== X-Forwarded-Encrypted: i=1; AHgh+RrIQidGk3TW4xPX3Et/2wCekN4IRxwcDQ4iBfqhAQUxIh/oMXvPb74Sr7Uf9QjYhKNVPPWTIMDsLxtCzBw=@vger.kernel.org X-Gm-Message-State: AOJu0YzzMQZoUjyS0N8K564vPNOGPllEoJPUe08X6Poe2R3WOuwdMLGD 0U3ja275YW03HlYius4pNIhTgbnc8DxzVZ2VbN+BOYwAvf1GvatGNabC X-Gm-Gg: AR+sD10T2DkMSdyKxi8b6Eh022VncuwGF4+GWdXeLbNe4gv0VyvImfQodpAIK3KR6r0 lgN1ZRXtxVzHH62JjY2tWGFrcfpeOv21dU5+kn2IC98HKW2Ad4sh3MlFvoffRqtUvLPxdSsqmq8 JBY7/5Rue9pnl+sbfGqq12lHUrIiEOIJG+Qqe/plcR2FKZx7nWjMrkO6rBUoB1hzywANzlkpYCo YMK/z0N1O/A1+CTOirrnsh+WLWJqWkT3NOiBw54G8UdHK4ShWOosAr36/+LTB05/0bGRchAQq9v 92h5r1JVGwzPxqsYBHHP7l4FS3M2KCDEMSUvFy7drgZm4AmIPSXN3hp/S1tL6a3YLLUjUY4FfRv mfer1FkD1PCpbQ2miK/mO66u3rxA/Tx3QrF6HH6kTaLGpUUGC3sqZs9FSxQ/JTiGILHxwIHNDIO KeV1qt0/IXhVXIA8j5RsBSytrejni/K8Ja204zXW5UVG0ihxccPE5z5qJtP2cTPe5Tu7nA7eYDK 6bFj9HRJPjFawzFZcq7vswjrU75JFG58srM5jeOffWYR36IqagM71AeG+ujVporS47aE8+ePKXK HYMNLYcwn03i4PH/us3Hm2132voo5r8SmxU+8Sp10ziHW9MS2zXll1/vi9eCxaVYOuA8bRqJVv7 QeIXR3re4B15mr/jO X-Received: by 2002:a05:6820:4d0a:b0:6a3:c5:30ca with SMTP id 006d021491bc7-6ae97025da8mr10409051eaf.31.1786078301063; Thu, 06 Aug 2026 21:51:41 -0700 (PDT) Received: from [127.0.1.1] (184-96-154-59.hlrn.qwest.net. [184.96.154.59]) by smtp.gmail.com with ESMTPSA id 006d021491bc7-6b02bbbf755sm1519726eaf.2.2026.08.06.21.51.39 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 06 Aug 2026 21:51:40 -0700 (PDT) From: James Hilliard Date: Thu, 06 Aug 2026 22:51:28 -0600 Subject: [PATCH net-next v4 3/4] 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: <20260806-submit-acx00-of-dynamic-v1-v4-3-9bf2f7441632@gmail.com> References: <20260806-submit-acx00-of-dynamic-v1-v4-0-9bf2f7441632@gmail.com> In-Reply-To: <20260806-submit-acx00-of-dynamic-v1-v4-0-9bf2f7441632@gmail.com> To: Lee Jones , Rob Herring , Krzysztof Kozlowski , Conor Dooley , James Hilliard , Andrew Lunn , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Andrew Lunn , Heiner Kallweit , Russell King Cc: mfd@lists.linux.dev, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, netdev@vger.kernel.org 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 uses registers in its I2C MFD, 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 its MFD. 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. 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 AC300-capable link addresses to 0 through 7 so the control address remains in the documented range 16 through 23. Signed-off-by: James Hilliard --- .../bindings/net/x-powers,acx00-ephy-package.yaml | 208 +++++++++++++++++= ++++ 1 file changed, 208 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..22bddc7a2f94 --- /dev/null +++ b/Documentation/devicetree/bindings/net/x-powers,acx00-ephy-package.yaml @@ -0,0 +1,208 @@ +# 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 control registers are part of an I2C= MFD, + 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. + +select: + properties: + compatible: + enum: + - x-powers,ac200-ephy-package + - x-powers,ac300-ephy-package + - x-powers,acx00-ephy-package + required: + - compatible + +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. The AC200 input clo= ck + belongs to the referenced AC200 MFD instead. + + vcc1-supply: + description: + 3.3 V supply for the AC300 I/O, bandgap, Ethernet PHY analog front e= nd, + and internal digital LDO + + nvmem-cells: + maxItems: 1 + description: + Packed EPHY configuration field. 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. A fixed AC200 package may provide o= nly + the low calibration bits or omit the cell and use its internal eFuse + copy instead. + + nvmem-cell-names: + const: configuration + + x-powers,ac200: + $ref: /schemas/types.yaml#/definitions/phandle + description: Reference to the AC200 MFD containing this 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 + +dependentRequired: + nvmem-cells: [ nvmem-cell-names ] + nvmem-cell-names: [ nvmem-cells ] + +allOf: + - $ref: ethernet-phy-package.yaml# + - if: + properties: + compatible: + const: x-powers,ac200-ephy-package + then: + properties: + clocks: false + vcc1-supply: false + required: + - x-powers,ac200 + - if: + properties: + compatible: + const: x-powers,ac300-ephy-package + then: + properties: + reg: + maximum: 7 + x-powers,ac200: false + required: + - clocks + - vcc1-supply + - nvmem-cells + - nvmem-cell-names + - if: + properties: + compatible: + const: x-powers,acx00-ephy-package + then: + properties: + reg: + maximum: 7 + required: + - clocks + - vcc1-supply + - nvmem-cells + - nvmem-cell-names + - x-powers,ac200 + +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>; + vcc1-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>; + }; + }; + }; + + - | + mdio { + #address-cells =3D <1>; + #size-cells =3D <0>; + + ethernet-phy-package@0 { + compatible =3D "x-powers,ac300-ephy-package"; + reg =3D <0>; + #address-cells =3D <1>; + #size-cells =3D <0>; + clocks =3D <&pwm 5>; + vcc1-supply =3D <®_3v3>; + nvmem-cells =3D <&ephy_configuration>; + nvmem-cell-names =3D "configuration"; + + ethernet-phy@0 { + compatible =3D "ethernet-phy-id0044.1400"; + reg =3D <0>; + }; + }; + }; + + - | + mdio { + #address-cells =3D <1>; + #size-cells =3D <0>; + + ethernet-phy-package@1 { + compatible =3D "x-powers,ac200-ephy-package"; + reg =3D <1>; + #address-cells =3D <1>; + #size-cells =3D <0>; + x-powers,ac200 =3D <&ac200>; + nvmem-cells =3D <&ephy_configuration>; + nvmem-cell-names =3D "configuration"; + + ethernet-phy@1 { + compatible =3D "ethernet-phy-id0044.1400"; + reg =3D <1>; + }; + }; + }; +... --=20 2.53.0 From nobody Tue Sep 29 14:55:08 2026 Received: from mail-oo1-f43.google.com (mail-oo1-f43.google.com [209.85.161.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 B889137D100 for ; Fri, 7 Aug 2026 04:51:53 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.161.43 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786078340; cv=none; b=IltmfIAORQLgp3XnPl1TMr1e7Rs7eJnX+03HlFPCGovTluLZAW8MJg32RY5uBhHiMMvmV9OBMZO5m8O8frxiFXFJT6zs82KKXRrbrHLFfRq3hzYo6st+XIfR7eu6udwVOiPlTqo04jew26Q4pQI5nMmBeWYRv4GqwiUvtUzWlwI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786078340; c=relaxed/simple; bh=gCMUcSf9c8LJ6kvKS/Ew458yUgGcqYa3mg1BT77pVuU=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=NbeQw05gtnaPWbzymDJMB7yAGNK0qGRd08SDpWjP+IfAIHDtIuKQZ75RROe5KvEfM5bYMYRlfZhsyLPaeKNXAcMczgsrnJuHPkbeCD6J9v8vj4Yx4Crw06g6pj3nQUkOZgCmobzUrmWsR3XfwkHu7lGDWxBKxWE2rlCGRVJmY8M= 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=Hiin/Utz; arc=none smtp.client-ip=209.85.161.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="Hiin/Utz" Received: by mail-oo1-f43.google.com with SMTP id 006d021491bc7-6b026020b92so465898eaf.1 for ; Thu, 06 Aug 2026 21:51:52 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1786078302; x=1786683102; 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=7LF3pOgerhWjVJnBeuaXEAde+cn12Vn138vnemlg0vY=; b=Hiin/UtzDwV8miZ/NMwAGYDKtRHAF//7sARbJeLPAEaHh+uRmhXQPxOwy7kgC/uhqR 4iQqIg4N0xuIKdCQNlrlgiZlhmo5GSu1d+FMZnkZLQMvAU2ErImgbFOyyZsCTDLCur1d H17iQFCSJytHROxxTSKhuWHddsmXGY0rweZUHjijK1dUNaEkygrF9+dkHgYIvng/HnHQ 9Gpt/T3/B6hs9qbopycjMO8jnM6B3WU10mTZUwTm5eRjufMYTlldjfyYSJ7u5ijKhsBw pEEqo+AwYh8OVcvx6FZE+ZOGQQ6hfeAEimzqIoayS7S12CFazFkRzUK03VlaCaeZcrN3 0CcQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1786078302; x=1786683102; 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=7LF3pOgerhWjVJnBeuaXEAde+cn12Vn138vnemlg0vY=; b=DTkfI4CrJilOQVwH+BSdAPfCV3qULcCBVnhlrdDKB3Z1TQPgIP1+Zi1ojo00p6/GpE o2oDbAcRB80OKevofvmX0AGbaPz+xN/7KoYtl5s5JVS996idvFAg8BPtl5fXdq4GslOL z7wNPuJJmG3Ns6g1iYsKYgKtRsjAn5xOu2gi+rjSb6FyU2ofxGscn1p56O3A09xvQ8/8 nbwGQ+EpneF5gdPN+stSsNQD24U79xfP7pcXYsYkeHoSF7iHyJL24EDRL10hGZJSLlqL 0jx6KCHPSQ3c3ei/XUkhSzpS7CS5E84fNhq9fY2A83/H/w/gzXy8guwUpMlyIz1dfSb1 Pu3Q== X-Forwarded-Encrypted: i=1; AHgh+RpT+NfS7+LMEWujAxXLhgzSeBcA9aT8BZUxGTSaOjPkpiUtsxH9qxyeL4te+zfyj13+3Fqf2f8whdfTsfg=@vger.kernel.org X-Gm-Message-State: AOJu0YxB9fYMbx8VTCw1XqYJUjJ9CuAOgI71X1zaF5KKeCWSXGxrlDS1 Q8Ih85EWtEOmlFkA0cGcU5a1vWfXhQLb4iyAyoznUPn1DppM9Sdqho7D X-Gm-Gg: AR+sD10GHUi2M7ngcIcswGFmBXL/tL0eh4nPM5m8vjlMuoVABnya9Z33pdkaH1ZP8AK dIGKM8Lo/FQY2eD8x46vu3a322SxZz5o7za8YBccJ/SB1PQNPn88noDP8fjc3UnZDFIGLdKirom 1iuBL8e2S50fbVE1+wCIVbKiOdtdskTfJmGuvlBm2Y/Wnf5v4Bo5wUrL0wyC9SK2/qGIuB5sTUj LTZ3QPp1miP/lsy6FEWvLAO/jyVIgxUlzcQ2VBBQT5X0q6a37CPaYZKskPdX1p3taqnFL6jHTmr /tMPQmnjxgxn+0oLIUjHaUGAxa6TgKmR6E/fVd5/AT0eax+d1haeu7aSxNULDM4+JYLaffAXj5Y pltpd6NtgguA708swWx9DOqkI/mrzZIZB2jq8ASDR1yXwQYB6FAZi8L1X5hKN//vqBeaRpCyid3 raYz1xefSE1ZBBmzdwfpegabQV2ZM8tXyO5CQd2EGFukmR7dGSdFU2gwnB4D6qXHD62K9smPcPg ZXxMDRF9KkHYNVZeB6utdiNFf54L8dOl99nU05bWY1PAU0X9UuDW783T03LhOOA726l88iNzfnC aaob0GkOE4jz4HU976fkEB/IwOUkF5s9gnqBHFtc2x5iBUxUD6z+XAAa4YEfR2nKe/f0fLgA/pe AvVIPlLfpM+NKvsefSsT0Zafsjd0= X-Received: by 2002:a05:6820:807:b0:6aa:1633:7af6 with SMTP id 006d021491bc7-6b02160a077mr3382532eaf.30.1786078302314; Thu, 06 Aug 2026 21:51:42 -0700 (PDT) Received: from [127.0.1.1] (184-96-154-59.hlrn.qwest.net. [184.96.154.59]) by smtp.gmail.com with ESMTPSA id 006d021491bc7-6b02bbbf755sm1519726eaf.2.2026.08.06.21.51.41 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 06 Aug 2026 21:51:41 -0700 (PDT) From: James Hilliard Date: Thu, 06 Aug 2026 22:51:29 -0600 Subject: [PATCH net-next v4 4/4] 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: <20260806-submit-acx00-of-dynamic-v1-v4-4-9bf2f7441632@gmail.com> References: <20260806-submit-acx00-of-dynamic-v1-v4-0-9bf2f7441632@gmail.com> In-Reply-To: <20260806-submit-acx00-of-dynamic-v1-v4-0-9bf2f7441632@gmail.com> To: Lee Jones , Rob Herring , Krzysztof Kozlowski , Conor Dooley , James Hilliard , Andrew Lunn , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Andrew Lunn , Heiner Kallweit , Russell King Cc: mfd@lists.linux.dev, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, netdev@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 enable the candidate AC200 I2C device. AC200 obtains the regmap of the referenced I2C MFD and keeps it bound with a device link. If CONFIG_OF_DYNAMIC is available, it can first enable an AC200 path whose unavailable nodes are explicitly marked fail-needs-probe. 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 the selected package calibration, select and lock its 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. 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. Power down the package control block during PHY suspend and restore the vendor configuration after resume. If shutdown fails, attempt to recover the running PHY before returning the original error. Signed-off-by: James Hilliard --- drivers/net/phy/Kconfig | 11 + drivers/net/phy/Makefile | 3 + drivers/net/phy/xpowers-acx00-ac200.c | 388 ++++++++++++++++++++++++ drivers/net/phy/xpowers-acx00-ac300.c | 404 +++++++++++++++++++++++++ drivers/net/phy/xpowers-acx00-main.c | 536 ++++++++++++++++++++++++++++++= ++++ drivers/net/phy/xpowers-acx00.h | 28 ++ 6 files changed, 1370 insertions(+) diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index a29d3fed8a05..27b8ffe4b539 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -475,6 +475,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 e23df5e836e9..b8a52d81eac6 100644 --- a/drivers/net/phy/Makefile +++ b/drivers/net/phy/Makefile @@ -100,4 +100,7 @@ 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 +xpowers-acx00-y :=3D xpowers-acx00-main.o xpowers-acx00-ac200.o \ + xpowers-acx00-ac300.o +obj-$(CONFIG_XPOWERS_ACX00_PHY) +=3D xpowers-acx00.o obj-$(CONFIG_XILINX_GMII2RGMII) +=3D xilinx_gmii2rgmii.o diff --git a/drivers/net/phy/xpowers-acx00-ac200.c b/drivers/net/phy/xpower= s-acx00-ac200.c new file mode 100644 index 000000000000..bc68f65b17f6 --- /dev/null +++ b/drivers/net/phy/xpowers-acx00-ac200.c @@ -0,0 +1,388 @@ +// 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 + +#include "xpowers-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 device *dev; + struct mutex lock; /* Serializes power sequencing and state. */ + u16 ephy_ctl; + unsigned int phy_addr; + phy_interface_t interface; + bool powered; +}; + +#if IS_ENABLED(CONFIG_OF_DYNAMIC) +static bool ac200_ephy_node_needs_probe(struct device_node *node) +{ + const char *status; + + return !of_property_read_string(node, "status", &status) && + !strcmp(status, "fail-needs-probe"); +} + +static int ac200_ephy_add_enable_path(struct device *dev, + struct of_changeset *ocs, + struct device_node *node, + bool *changed) +{ + struct device_node *parent; + int ret; + + parent =3D of_get_parent(node); + if (parent) { + ret =3D ac200_ephy_add_enable_path(dev, ocs, parent, changed); + of_node_put(parent); + if (ret) + return ret; + } + + if (of_device_is_available(node)) + return 0; + + if (!ac200_ephy_node_needs_probe(node)) + return dev_err_probe(dev, -ENODEV, + "%pOF is unavailable without fail-needs-probe\n", + node); + + ret =3D of_changeset_update_prop_string(ocs, node, "status", "okay"); + if (!ret) + *changed =3D true; + + return ret; +} + +static int ac200_ephy_enable_path(struct device *dev, + struct device_node *node) +{ + struct of_changeset *ocs; + bool changed =3D false; + int ret; + + ocs =3D kzalloc_obj(*ocs); + if (!ocs) + return -ENOMEM; + + of_changeset_init(ocs); + ret =3D ac200_ephy_add_enable_path(dev, ocs, node, &changed); + if (ret || !changed) + goto out_destroy; + + ret =3D of_changeset_apply(ocs); + if (ret) + goto out_destroy; + + /* Keep the applied status properties alive for the lifetime of the DT. */ + return 0; + +out_destroy: + of_changeset_destroy(ocs); + kfree(ocs); + return ret; +} +#endif + +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_power_off_locked(struct ac200_ephy_ctl *priv) +{ + int err; + int ret; + + if (!priv->powered) + return 0; + + 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; + + 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); + int ret; + + mutex_lock(&priv->lock); + ret =3D ac200_ephy_ctl_power_off_locked(priv); + mutex_unlock(&priv->lock); + + return ret; +} + +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; + + mutex_lock(&priv->lock); + if (priv->interface =3D=3D interface) + goto out_unlock; + + 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; + +out_unlock: + mutex_unlock(&priv->lock); + + 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; + + mutex_lock(&priv->lock); + if (priv->powered && priv->phy_addr =3D=3D phy_addr) { + ret =3D 0; + goto out_unlock; + } + if (priv->powered) { + ret =3D ac200_ephy_ctl_power_off_locked(priv); + if (ret) + goto out_unlock; + } + priv->phy_addr =3D phy_addr; + + ephy_ctl =3D ac200_ephy_ctl_config(priv); + + /* Start from a disabled state before applying the configuration. */ + 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; + goto out_unlock; + +err_disable: + /* Attempt every step of the shutdown sequence after a partial start. */ + priv->powered =3D true; + ac200_ephy_ctl_power_off_locked(priv); +out_unlock: + mutex_unlock(&priv->lock); + + 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; + mutex_init(&priv->lock); + + 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")); + +#if IS_ENABLED(CONFIG_OF_DYNAMIC) + ret =3D ac200_ephy_enable_path(dev, ac200_node); + if (ret) { + of_node_put(ac200_node); + return ERR_PTR(ret); + } +#endif + + 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; + } + + if (!device_trylock(&client->dev)) { + ret =3D dev_err_probe(dev, -EPROBE_DEFER, + "AC200 driver is not ready\n"); + goto out_put_client; + } + + 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_error; + } + + 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; + } + + 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-acx00-ac300.c b/drivers/net/phy/xpower= s-acx00-ac300.c new file mode 100644 index 000000000000..70a90b7a85a7 --- /dev/null +++ b/drivers/net/phy/xpowers-acx00-ac300.c @@ -0,0 +1,404 @@ +// 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 + +#include "phylib.h" +#include "xpowers-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 clk *clk; + struct mutex lock; /* Serializes power sequencing and state. */ + u16 sys_control; + u16 ephy_config; + phy_interface_t interface; + bool package_known; + bool internal_dldo; + 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_power_off_locked(struct ac300_ephy_ctl *priv) +{ + int err; + int ret; + + if (!priv->powered) + return 0; + + 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; + + 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); + int ret; + + mutex_lock(&priv->lock); + ret =3D ac300_ephy_ctl_power_off_locked(priv); + mutex_unlock(&priv->lock); + + return ret; +} + +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; + + mutex_lock(&priv->lock); + if (priv->interface =3D=3D interface) + goto out_unlock; + + 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; + +out_unlock: + mutex_unlock(&priv->lock); + + 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; + + mutex_lock(&priv->lock); + if (priv->powered) { + ret =3D 0; + goto out_unlock; + } + + ret =3D clk_prepare_enable(priv->clk); + if (ret) + goto out_unlock; + 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; + + goto out_unlock; + +err_power_off: + ac300_ephy_ctl_power_off_locked(priv); +out_unlock: + mutex_unlock(&priv->lock); + + return ret; +} + +static void ac300_ephy_regulator_disable(void *data) +{ + regulator_disable(data); +} + +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; + struct regulator *vcc1; + 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; + mutex_init(&priv->lock); + + vcc1 =3D devm_of_regulator_get(dev, package_node, "vcc1"); + if (IS_ERR(vcc1)) + return ERR_PTR(dev_err_probe(dev, PTR_ERR(vcc1), + "failed to get VCC1 supply\n")); + + ret =3D regulator_enable(vcc1); + if (ret) + return ERR_PTR(dev_err_probe(dev, ret, + "failed to enable VCC1 supply\n")); + + ret =3D devm_add_action_or_reset(dev, ac300_ephy_regulator_disable, + vcc1); + if (ret) + return ERR_PTR(ret); + + /* Wait for the power-on reset interval specified by the manual. */ + fsleep(10000); + + 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-main.c b/drivers/net/phy/xpowers= -acx00-main.c new file mode 100644 index 000000000000..b77c6169821c --- /dev/null +++ b/drivers/net/phy/xpowers-acx00-main.c @@ -0,0 +1,536 @@ +// 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 "phylib.h" +#include "xpowers-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; + 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) +{ + return priv->control->power_on(priv->control, + priv->phydev->mdio.addr); +} + +static int acx00_ephy_control_power_off(struct acx00_ephy_priv *priv) +{ + 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; + + 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_release(void *data) +{ + struct acx00_ephy_priv *priv =3D data; + 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 int acx00_ephy_read_nvmem_u16(struct device_node *node, + const char *name, 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 || len > sizeof(*value)) { + kfree(buf); + return len ? -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", + &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) +{ + return phy_modify_paged(phydev, ACX00_PAGE_1, + ACX00_PAGE1_INTELLIGENT_EEE_CONTROL_REG, + ACX00_PAGE1_INTELLIGENT_EEE_ENABLE, 0); +} + +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; + 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; + + ret =3D acx00_ephy_control_power_on(priv); + 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; + + ret =3D acx00_ephy_set_interface(phydev); + if (ret) + return ret; + + ret =3D acx00_ephy_control_power_on(priv); + if (ret) + return ret; + + /* ACx00 can acknowledge reset in power-down without restarting. */ + ret =3D genphy_resume(phydev); + if (ret) + return ret; + + return genphy_soft_reset(phydev); +} + +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; + + 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) + goto out_restore_page; + + 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. */ + return genphy_c45_an_config_eee_aneg(phydev); +} + +static int acx00_ephy_power_on_and_resume(struct phy_device *phydev) +{ + struct acx00_ephy_priv *priv =3D phydev->priv; + int ret; + + ret =3D acx00_ephy_set_interface(phydev); + if (ret) + return ret; + + ret =3D acx00_ephy_control_power_on(priv); + if (ret) + return ret; + + ret =3D genphy_resume(phydev); + if (ret) { + acx00_ephy_control_power_off(priv); + return ret; + } + + /* Powering off the control block loses the vendor-page state. */ + return acx00_ephy_config_init(phydev); +} + +static int acx00_ephy_resume(struct phy_device *phydev) +{ + return acx00_ephy_power_on_and_resume(phydev); +} + +static int acx00_ephy_suspend(struct phy_device *phydev) +{ + struct acx00_ephy_priv *priv =3D phydev->priv; + int resume_ret; + int ret; + + ret =3D genphy_suspend(phydev); + if (ret) + return ret; + + ret =3D acx00_ephy_control_power_off(priv); + if (ret) { + resume_ret =3D acx00_ephy_power_on_and_resume(phydev); + if (resume_ret) + phydev_warn(phydev, + "failed to recover from suspend error: %pe\n", + ERR_PTR(resume_ret)); + } + + 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