Devicetree
 help / color / mirror / Atom feed
* [PATCH 0/2] Add support for EcoNet USB PHY
@ 2026-05-18 14:13 Caleb James DeLisle
  2026-05-18 14:13 ` [PATCH 1/2] dt-bindings: phy: econet: Document EN751221 " Caleb James DeLisle
  2026-05-18 14:13 ` [PATCH 2/2] phy: econet: Add EcoNet " Caleb James DeLisle
  0 siblings, 2 replies; 4+ messages in thread
From: Caleb James DeLisle @ 2026-05-18 14:13 UTC (permalink / raw)
  To: linux-mips
  Cc: vkoul, neil.armstrong, robh, krzk+dt, conor+dt, linux-phy,
	devicetree, linux-kernel, Caleb James DeLisle

Add support for the USB PHY found on EcoNet EN751221, EN751627, and
EN7528.

Caleb James DeLisle (2):
  dt-bindings: phy: econet: Document EN751221 USB PHY
  phy: econet: Add EcoNet USB PHY

 .../bindings/phy/econet,en751221-usb-phy.yaml | 128 ++++
 MAINTAINERS                                   |   7 +
 drivers/phy/Kconfig                           |  13 +
 drivers/phy/Makefile                          |   1 +
 drivers/phy/phy-econet-usb.c                  | 558 ++++++++++++++++++
 5 files changed, 707 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/phy/econet,en751221-usb-phy.yaml
 create mode 100644 drivers/phy/phy-econet-usb.c


base-commit: e98d21c170b01ddef366f023bbfcf6b31509fa83
-- 
2.39.5


^ permalink raw reply	[flat|nested] 4+ messages in thread

* [PATCH 1/2] dt-bindings: phy: econet: Document EN751221 USB PHY
  2026-05-18 14:13 [PATCH 0/2] Add support for EcoNet USB PHY Caleb James DeLisle
@ 2026-05-18 14:13 ` Caleb James DeLisle
  2026-05-18 14:13 ` [PATCH 2/2] phy: econet: Add EcoNet " Caleb James DeLisle
  1 sibling, 0 replies; 4+ messages in thread
From: Caleb James DeLisle @ 2026-05-18 14:13 UTC (permalink / raw)
  To: linux-mips
  Cc: vkoul, neil.armstrong, robh, krzk+dt, conor+dt, linux-phy,
	devicetree, linux-kernel, Caleb James DeLisle

Document the USB PHY devices which appear in EcoNet EN751221, EN751627,
and EN7528 based SoCs.

Signed-off-by: Caleb James DeLisle <cjd@cjdns.fr>
---
 .../bindings/phy/econet,en751221-usb-phy.yaml | 128 ++++++++++++++++++
 MAINTAINERS                                   |   6 +
 2 files changed, 134 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/phy/econet,en751221-usb-phy.yaml

diff --git a/Documentation/devicetree/bindings/phy/econet,en751221-usb-phy.yaml b/Documentation/devicetree/bindings/phy/econet,en751221-usb-phy.yaml
new file mode 100644
index 000000000000..a44f59601747
--- /dev/null
+++ b/Documentation/devicetree/bindings/phy/econet,en751221-usb-phy.yaml
@@ -0,0 +1,128 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+# Copyright (C) 2024 EcoNet
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/phy/econet,en751221-usb-phy.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: EcoNet EN751221 USB PHY
+
+maintainers:
+  - Caleb James DeLisle <cjd@cjdns.fr>
+
+description: |
+  USB PHY controller found on EcoNet EN751221 SoCs as well as on EN751627 and
+  EN7528. These devices generally have two ports, one of which is a USB 3.0,
+  and the other is USB 2.0. The USB 3.0 port is driven by one of two PHY
+  blocks, depending on whether the connected device has negotiated USB 3.0 or
+  2.0. These PHYs are also used on other EcoNet silicon in varying
+  configurations, such as only port 0 (the USB 3.0 port), or only port 1 (the
+  USB 2.0 port).
+
+properties:
+  compatible:
+    enum:
+      - econet,en751221-usb-phy
+      - econet,en751627-usb-phy
+      - econet,en7528-usb-phy
+
+  reg:
+    maxItems: 1
+
+  "#address-cells": true
+  "#size-cells": true
+  ranges: true
+
+  clocks:
+    maxItems: 1
+    description: |
+      Crystal oscillator clock source. EcoNet devices run at either 20Mhz or
+      25Mhz. 25Mhz devices require additional tuning in the USB 3.0 PHY.
+
+  clock-names:
+    items:
+      - const: xtal
+
+patternProperties:
+  "^usb-phy@[0-9a-f]+$":
+    type: object
+    description: USB 2.0 or 3.0 PHY sub-node.
+
+    properties:
+      compatible:
+        enum:
+          - econet,usb2-phy
+          - econet,usb3-phy
+
+      reg:
+        maxItems: 1
+
+      resets:
+        maxItems: 1
+
+      econet,usb-port-id:
+        $ref: /schemas/types.yaml#/definitions/uint32
+        enum: [0, 1]
+        description: |
+          Physical port number. Since USB 3.0 requires a second PHY for the 2.0
+          fallback, multiple PHYs can map to the same physical port.
+
+      "#phy-cells":
+        const: 0
+
+    required:
+      - compatible
+      - reg
+      - econet,usb-port-id
+      - "#phy-cells"
+
+    additionalProperties: false
+
+required:
+  - compatible
+  - reg
+  - "#address-cells"
+  - "#size-cells"
+  - ranges
+  - clocks
+  - clock-names
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/reset/econet,en751221-scu.h>
+
+    usb-phy@1fa80000 {
+      compatible = "econet,en751221-usb-phy";
+      reg = <0x1fa80000 0x700>;
+      #address-cells = <1>;
+      #size-cells = <1>;
+      ranges;
+      clocks = <&xtalclk>;
+      clock-names = "xtal";
+
+      usb-phy@1fa80800 {
+          compatible = "econet,usb2-phy";
+          reg = <0x1fa80800 0x100>;
+          resets = <&scuclk EN751221_USB_PHY_P0_RST>;
+          econet,usb-port-id = <0>;
+          #phy-cells = <0>;
+      };
+
+      usb-phy@1fa80900 {
+          compatible = "econet,usb3-phy";
+          reg = <0x1fa80900 0x700>;
+          resets = <&scuclk EN751221_USB_PHY_P0_RST>;
+          econet,usb-port-id = <0>;
+          #phy-cells = <0>;
+      };
+
+      usb-phy@1fa81000 {
+          compatible = "econet,usb2-phy";
+          reg = <0x1fa81000 0x100>;
+          resets = <&scuclk EN751221_USB_PHY_P1_RST>;
+          econet,usb-port-id = <1>;
+          #phy-cells = <0>;
+      };
+    };
diff --git a/MAINTAINERS b/MAINTAINERS
index f877e5aaf2c7..1a90a1736294 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -9204,6 +9204,12 @@ F:	drivers/irqchip/irq-econet-en751221.c
 F:	include/dt-bindings/clock/econet,en751221-scu.h
 F:	include/dt-bindings/reset/econet,en751221-scu.h
 
+ECONET USB PHY DRIVER
+M:	Caleb James DeLisle <cjd@cjdns.fr>
+L:	linux-mips@vger.kernel.org
+S:	Maintained
+F:	Documentation/devicetree/bindings/phy/econet,en751221-usb-phy.yaml
+
 ECRYPT FILE SYSTEM
 M:	Tyler Hicks <code@tyhicks.com>
 L:	ecryptfs@vger.kernel.org
-- 
2.39.5


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* [PATCH 2/2] phy: econet: Add EcoNet USB PHY
  2026-05-18 14:13 [PATCH 0/2] Add support for EcoNet USB PHY Caleb James DeLisle
  2026-05-18 14:13 ` [PATCH 1/2] dt-bindings: phy: econet: Document EN751221 " Caleb James DeLisle
@ 2026-05-18 14:13 ` Caleb James DeLisle
  2026-05-18 14:40   ` sashiko-bot
  1 sibling, 1 reply; 4+ messages in thread
From: Caleb James DeLisle @ 2026-05-18 14:13 UTC (permalink / raw)
  To: linux-mips
  Cc: vkoul, neil.armstrong, robh, krzk+dt, conor+dt, linux-phy,
	devicetree, linux-kernel, Caleb James DeLisle, Benjamin Larsson,
	Ahmed Naseef

Add support for USB PHY found on EcoNet EN751221, EN751627, and
EN7528 based SoCs. This PHY is somewhat similar to Mediatek tphy-v1,
but in terms of setup ritual it has essentially nothing in common.

Based on work originally by Benjamin Larsson with bug fixes and
added support for EN751627 and EN7528.

Tested on:
* EN7526G (SmartFiber XP8421-B) USB 2.0
* EN7526F (ChinaMobile GS3101) USB 2.0
* EN7516 (Zyxel EX3301-T0) USB 2.0

Tested by Ahmed Naseef on EN7528 (Dasan H660GM) USB 2.0

Tested by Benjamin Larsson on unreleased EN751221 board with USB 3.0

Originally-by: Benjamin Larsson <benjamin.larsson@genexis.eu>
Signed-off-by: Caleb James DeLisle <cjd@cjdns.fr>
Tested-by: Ahmed Naseef <naseefkm@gmail.com>
Tested-by: Benjamin Larsson <benjamin.larsson@genexis.eu>
Link: https://github.com/openwrt/openwrt/pull/23264#issuecomment-4415906568
---
 MAINTAINERS                  |   1 +
 drivers/phy/Kconfig          |  13 +
 drivers/phy/Makefile         |   1 +
 drivers/phy/phy-econet-usb.c | 558 +++++++++++++++++++++++++++++++++++
 4 files changed, 573 insertions(+)
 create mode 100644 drivers/phy/phy-econet-usb.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 1a90a1736294..74cf07bbc49f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -9209,6 +9209,7 @@ M:	Caleb James DeLisle <cjd@cjdns.fr>
 L:	linux-mips@vger.kernel.org
 S:	Maintained
 F:	Documentation/devicetree/bindings/phy/econet,en751221-usb-phy.yaml
+F:	drivers/phy/phy-econet-usb.c
 
 ECRYPT FILE SYSTEM
 M:	Tyler Hicks <code@tyhicks.com>
diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig
index 227b9a4c612e..0a3bec9af93e 100644
--- a/drivers/phy/Kconfig
+++ b/drivers/phy/Kconfig
@@ -66,6 +66,19 @@ config PHY_CAN_TRANSCEIVER
 	  functional modes using gpios and sets the attribute max link
 	  rate, for CAN drivers.
 
+config PHY_ECONET_USB
+	tristate "EcoNet USB PHY driver"
+	depends on ECONET || COMPILE_TEST
+	depends on OF
+	select GENERIC_PHY
+	select REGMAP_MMIO
+	help
+	  Enable support for the USB PHY on EcoNet EN751221, EN751627, and
+	  EN7528 SoCs. These PHYs support USB 2.0 and USB 3.0. SoCs typically
+	  have two ports, one supporting USB 2.0 only and the other supporting
+	  USB 3.0. Though some chip configurations have only the 2.0 capable
+	  port and some only have the 3.0 capable port.
+
 config PHY_GOOGLE_USB
 	tristate "Google Tensor SoC USB PHY driver"
 	select GENERIC_PHY
diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile
index f49d83f00a3d..8a58bb151198 100644
--- a/drivers/phy/Makefile
+++ b/drivers/phy/Makefile
@@ -9,6 +9,7 @@ obj-$(CONFIG_GENERIC_PHY)		+= phy-core.o
 obj-$(CONFIG_GENERIC_PHY_MIPI_DPHY)	+= phy-core-mipi-dphy.o
 obj-$(CONFIG_PHY_AIROHA_PCIE)		+= phy-airoha-pcie.o
 obj-$(CONFIG_PHY_CAN_TRANSCEIVER)	+= phy-can-transceiver.o
+obj-$(CONFIG_PHY_ECONET_USB)		+= phy-econet-usb.o
 obj-$(CONFIG_PHY_GOOGLE_USB)		+= phy-google-usb.o
 obj-$(CONFIG_USB_LGM_PHY)		+= phy-lgm-usb.o
 obj-$(CONFIG_PHY_LPC18XX_USB_OTG)	+= phy-lpc18xx-usb-otg.o
diff --git a/drivers/phy/phy-econet-usb.c b/drivers/phy/phy-econet-usb.c
new file mode 100644
index 000000000000..e8fa2e80eb13
--- /dev/null
+++ b/drivers/phy/phy-econet-usb.c
@@ -0,0 +1,558 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Author: Benjamin Larsson <benjamin.larsson@genexis.eu>
+ * Author: Caleb James DeLisle <cjd@cjdns.fr>
+ */
+
+#include <dt-bindings/phy/phy.h>
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/math.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+/* SSUSB SIFSLV SPLLC (0x0) */
+#define SPLLC					0x00
+
+/* SSUSB SIFSLV FMREG (0x100) offset based on SSUSB SIFSLV SPLLC */
+#define FMREG					0x100
+#define   FMCR0					0x00
+#define     RG_MONCLK_SEL			GENMASK(27, 26)
+#define     RG_FREQDET_EN			BIT(24)
+#define     RG_CYCLECNT				GENMASK(23, 0)
+#define   FMMONR0				0x0c
+#define     RG_FM_OUT				GENMASK(31, 0)
+#define   FMMONR1				0x10
+#define     RG_FM_VALID				BIT(0)
+#define     RG_FRCK_EN				BIT(8)
+
+/* SSUSB SIFSLV U2PHY COM (0x800, 0x1000) */
+#define U2PHY_COM				0x00
+#define   USBPHYACR0				0x10
+#define     RG_HSTX_SRCAL_EN			BIT(23)
+#define     RG_HSTX_SRCTRL			GENMASK(18, 16)
+#define   USBPHYACR3				0x1C
+#define     RG_PUPD				GENMASK(12, 8)
+#define       RG_PUPD_EN			BIT(12)
+#define       RG_PUPD_PD_DM			BIT(10)
+#define       RG_PUPD_PD_DP			BIT(8)
+#define     RG_REV				GENMASK(7, 0)
+#define       REVISION_P0			FIELD_PREP(RG_REV, 0x08)
+#define       REVISION_P0_DOWN			FIELD_PREP(RG_REV, 0x80)
+#define       REVISION_P1			FIELD_PREP(RG_REV, 0x00)
+#define   U2PHYDCR1				0x64
+#define     RG_USB20_SW_PLLMODE			GENMASK(19, 18)
+
+/* SSUSB SIFSLV U3PHYD (0x900) */
+#define U3PHYD					0x00
+#define   LFPS1					0x0c
+#define     RG_SSUSB_FWAKE_TH			GENMASK(21, 16)
+
+/* SSUSB SIFSLV U3PHYD BANK2 (0xA00) offset based on SSUSB SIFSLV U3PHYD */
+#define U3PHYD_BANK2				0x100
+#define   B2_PHYD_RXDET1			0x028
+#define     RG_SSUSB_RXDET_STB2_SET		GENMASK(17, 9)
+#define   B2_PHYD_RXDET2			0x02C
+#define     RG_SSUSB_RXDET_STB2_SET_P3		GENMASK(8, 0)
+
+/* SSUSB SIFSLV U3PHYA (0xB00) offset based on SSUSB SIFSLV U3PHYD */
+#define U3PHYA					0x200
+#define   U3PHYA_REG2				0x008
+#define     RG_SSUSB_SYSPLL_LF			BIT(31)
+#define     RG_SSUSB_SYSPLL_FBDIV		GENMASK(30, 24)
+#define     RG_SSUSB_SYSPLL_POSDIV		GENMASK(23, 22)
+#define     RG_SSUSB_SYSPLL_VCO_DIV_SEL		BIT(21)
+#define     RG_SSUSB_SYSPLL_BLP			BIT(20)
+#define     RG_SSUSB_SYSPLL_BP			BIT(19)
+#define     RG_SSUSB_SYSPLL_BR			BIT(18)
+#define     RG_SSUSB_SYSPLL_BC			BIT(17)
+#define     RG_SSUSB_SYSPLL_DIVEN		GENMASK(16, 14)
+#define     RG_SSUSB_SYSPLL_FPEN		BIT(13)
+#define     RG_SSUSB_SYSPLL_MONCK_EN		BIT(12)
+#define     RG_SSUSB_SYSPLL_MONVC_EN		BIT(11)
+#define     RG_SSUSB_SYSPLL_MONREF_EN		BIT(10)
+#define     RG_SSUSB_SYSPLL_VOD_EN		BIT(9)
+#define     RG_SSUSB_SYSPLL_CK_SEL		BIT(8)
+#define   U3PHYA_REG4				0x010
+#define     RG_SSUSB_SYSPLL_PCW_NCPO		GENMASK(31, 1)
+#define   U3PHYA_REG9				0x024
+#define     RG_SSUSB_PLL_DDS_DMY		GENMASK(31, 16)
+#define     RG_SSUSB_PLL_SSC_PRD		GENMASK(15, 0)
+#define   U3PHYA_REGB				0x02c
+#define     RG_SSUSB_RX_50_TAR			GENMASK(13, 12)
+
+/* SSUSB SIFSLV U3PHYA DA (0xC00) offset based on SSUSB SIFSLV U3PHYD */
+#define U3PHYA_DA				0x300
+#define   U3PHYA_DA_REG9			0x01C
+#define     RG_SSUSB_PLL_FBKDIV_PE2H		GENMASK(30, 24)
+#define     RG_SSUSB_PLL_FBKDIV_PE1D		GENMASK(22, 16)
+#define     RG_SSUSB_PLL_FBKDIV_PE1H		GENMASK(14, 8)
+#define     RG_SSUSB_PLL_FBKDIV_U3		GENMASK(6, 0)
+#define   U3PHYA_DA_REG12			0x024
+#define     RG_SSUSB_PLL_PCW_NCPO_U3		GENMASK(30, 0)
+#define   U3PHYA_DA_REG13			0x028
+#define     RG_SSUSB_PLL_PCW_NCPO_PE1H		GENMASK(30, 0)
+#define   U3PHYA_DA_REG14			0x02C
+#define     RG_SSUSB_PLL_PCW_NCPO_PE1D		GENMASK(30, 0)
+#define   U3PHYA_DA_REG15			0x030
+#define     RG_SSUSB_PLL_PCW_NCPO_PE2H		GENMASK(30, 0)
+#define   U3PHYA_DA_REG19			0x038
+#define     RG_SSUSB_PLL_SSC_DELTA1_PE1H	GENMASK(31, 16)
+#define     RG_SSUSB_PLL_SSC_DELTA1_U3		GENMASK(15, 0)
+#define   U3PHYA_DA_REG20			0x03C
+#define     RG_SSUSB_PLL_SSC_DELTA1_PE2H	GENMASK(31, 16)
+#define     RG_SSUSB_PLL_SSC_DELTA1_PE1D	GENMASK(15, 0)
+#define   U3PHYA_DA_REG21			0x040
+#define     RG_SSUSB_PLL_SSC_DELTA_U3		GENMASK(31, 16)
+#define     RG_SSUSB_PLL_SSC_DELTA1_PE2D	GENMASK(15, 0)
+#define   U3PHYA_DA_REG23			0x044
+#define     RG_SSUSB_PLL_SSC_DELTA_PE1D		GENMASK(31, 16)
+#define     RG_SSUSB_PLL_SSC_DELTA_PE1H		GENMASK(15, 0)
+#define   U3PHYA_DA_REG25			0x048
+#define     RG_SSUSB_PLL_SSC_DELTA_PE2D		GENMASK(31, 16)
+#define     RG_SSUSB_PLL_SSC_DELTA_PE2H		GENMASK(15, 0)
+
+#define ECONET_USB_PHY_U2_FM_DET_CYCLE_CNT	1024
+#define ECONET_USB_PHY_REF_CK			20
+#define ECONET_USB_PHY_U2_SR_COEF		28
+#define ECONET_USB_PHY_U2_SR_COEF_DIVISOR	1000
+#define ECONET_USB_PHY_U2_SR_DEFAULT		0x4
+
+#define ECONET_USB_PHY_FREQDET_SLEEP		1000 /* 1ms */
+#define ECONET_USB_PHY_FREQDET_TIMEOUT		(ECONET_USB_PHY_FREQDET_SLEEP * 10)
+
+#define ECONET_USB_PHY_MAX_INSTANCE		3
+
+#define ECONET_PORT_ID_FIELD			"econet,usb-port-id"
+
+enum econet_phy_soc {
+	SOC_EN751221 = 1,
+	SOC_EN751627,
+	SOC_EN7528,
+};
+
+/**
+ * econet_phy_instance - the individual PHY block
+ * @phy: The phy object
+ * @regmap: A regmap for the phy block, per DT subnode reg field
+ * @type: PHY_TYPE_USB2 / PHY_TYPE_USB3 depending on PHY block
+ * @port_id: Number of the physical port [0,1]
+ *
+ * There are typically 3 PHY blocks, 2x USB 2.0 blocks, and 1x USB 3.0. The USB
+ * 3.0 PHY is piggybacked on top of one of the USB2 ports and activates when
+ * the connected device negotiates a USB 3.0 link. Main actions like powering
+ * on the port are governed in the USB 2.0 block.
+ */
+struct econet_phy_instance {
+	struct phy	*phy;
+	struct regmap	*regmap;
+	int		type;
+	int		port_id;
+};
+
+/**
+ * econet_usb_phy_priv the main PHY driver structure
+ * @dev: The root device
+ * @regmap: A regmap of registers that are shared between the PHY blocks
+ * @clk: The system Xtal clock, if 25Mhz then we need additional configuration
+ * @phys: The individual PHY blocks, normally there are three: Two USB 2.0 and
+ *        one USB 3.0 which is shared on the same port at one of the USB 2.0.
+ * @soc: The ID of the device, which is relevant to some of the tuning ritual.
+ */
+struct econet_usb_phy_priv {
+	struct device			*dev;
+	struct regmap			*regmap;
+	struct clk			*clk;
+	struct econet_phy_instance	phys[ECONET_USB_PHY_MAX_INSTANCE];
+	enum econet_phy_soc		soc;
+};
+
+static void u2_slew_rate_calibration(struct econet_usb_phy_priv *priv,
+				     struct econet_phy_instance *instance)
+{
+	struct regmap *com = instance->regmap;
+	struct regmap *regmap = priv->regmap;
+	int fm_out = 0;
+	u32 srctrl;
+
+	/* Enable HS TX SR calibration */
+	regmap_set_bits(com, U2PHY_COM + USBPHYACR0, RG_HSTX_SRCAL_EN);
+
+	usleep_range(1000, 1500);
+
+	/* Enable Free run clock */
+	regmap_set_bits(regmap, FMREG + FMMONR1, RG_FRCK_EN);
+
+	/* Select Monitor Clock */
+	regmap_update_bits(regmap, FMREG + FMCR0, RG_MONCLK_SEL,
+			   FIELD_PREP(RG_MONCLK_SEL, instance->port_id));
+
+	/* Set cyclecnt */
+	regmap_update_bits(regmap, FMREG + FMCR0, RG_CYCLECNT,
+			   FIELD_PREP_CONST(RG_CYCLECNT,
+					    ECONET_USB_PHY_U2_FM_DET_CYCLE_CNT));
+
+	/* Enable Frequency meter */
+	regmap_set_bits(regmap, FMREG + FMCR0, RG_FREQDET_EN);
+
+	/* Timeout can happen and we will apply workaround at the end */
+	regmap_read_poll_timeout(regmap, FMREG + FMMONR0, fm_out,
+				 fm_out, ECONET_USB_PHY_FREQDET_SLEEP,
+				 ECONET_USB_PHY_FREQDET_TIMEOUT);
+
+	/* Disable Frequency meter */
+	regmap_clear_bits(regmap, FMREG + FMCR0, RG_FREQDET_EN);
+
+	/* Disable Free run clock */
+	regmap_clear_bits(regmap, FMREG + FMMONR1, RG_FRCK_EN);
+
+	/* Disable HS TX SR calibration */
+	regmap_clear_bits(com, U2PHY_COM + USBPHYACR0, RG_HSTX_SRCAL_EN);
+
+	usleep_range(1000, 1500);
+
+	if (!fm_out) {
+		srctrl = ECONET_USB_PHY_U2_SR_DEFAULT;
+		dev_warn(&instance->phy->dev,
+			 "frequency detection failed, using default slew rate 0x%x\n",
+			 srctrl);
+	} else {
+		/* (1024 / FM_OUT) * REF_CK * U2_SR_COEF (round to nearest) */
+		srctrl = ECONET_USB_PHY_REF_CK * ECONET_USB_PHY_U2_SR_COEF;
+		srctrl = (srctrl * ECONET_USB_PHY_U2_FM_DET_CYCLE_CNT) / fm_out;
+		srctrl = DIV_ROUND_CLOSEST(srctrl,
+					   ECONET_USB_PHY_U2_SR_COEF_DIVISOR);
+		dev_info(&instance->phy->dev,
+			 "frequency meter result 0x%x, set slew rate 0x%x\n",
+			 fm_out, srctrl);
+	}
+
+	/* Set slew rate */
+	regmap_update_bits(com, U2PHY_COM + USBPHYACR0, RG_HSTX_SRCTRL,
+			   FIELD_PREP(RG_HSTX_SRCTRL, srctrl));
+}
+
+static int econet_usb_phy_u2_init(struct econet_usb_phy_priv *priv,
+				  struct econet_phy_instance *instance)
+{
+	struct regmap *com = instance->regmap;
+
+	regmap_update_bits(com, U2PHY_COM + USBPHYACR3, RG_PUPD | RG_REV,
+			   instance->port_id == 0 ? REVISION_P0 : REVISION_P1);
+
+	/* set SW PLL Stable mode to 1 for U2 LPM device remote wakeup */
+	if (priv->soc != SOC_EN7528)
+		regmap_update_bits(com, U2PHY_COM + U2PHYDCR1,
+				   RG_USB20_SW_PLLMODE,
+				   FIELD_PREP(RG_USB20_SW_PLLMODE, 0x1));
+
+	u2_slew_rate_calibration(priv, instance);
+
+	usleep_range(1000, 1500);
+
+	return 0;
+}
+
+static int u3_setup_25mhz_xtal(struct econet_usb_phy_priv *priv,
+			       struct econet_phy_instance *instance)
+{
+	struct regmap *u3phyd = instance->regmap;
+
+	/* Setup 25MHz XTAL */
+	regmap_update_bits(u3phyd, U3PHYA_DA + U3PHYA_DA_REG9,
+			   RG_SSUSB_PLL_FBKDIV_U3,
+			   FIELD_PREP(RG_SSUSB_PLL_FBKDIV_U3, 0x18));
+	regmap_update_bits(u3phyd, U3PHYA_DA + U3PHYA_DA_REG9,
+			   RG_SSUSB_PLL_FBKDIV_PE1H,
+			   FIELD_PREP(RG_SSUSB_PLL_FBKDIV_PE1H, 0x18));
+	regmap_update_bits(u3phyd, U3PHYA_DA + U3PHYA_DA_REG9,
+			   RG_SSUSB_PLL_FBKDIV_PE2H,
+			   FIELD_PREP(RG_SSUSB_PLL_FBKDIV_PE2H, 0x18));
+
+	regmap_update_bits(u3phyd, U3PHYA_DA + U3PHYA_DA_REG12,
+			   RG_SSUSB_PLL_PCW_NCPO_U3,
+			   FIELD_PREP(RG_SSUSB_PLL_PCW_NCPO_U3, 0x18000000));
+
+	regmap_update_bits(u3phyd, U3PHYA_DA + U3PHYA_DA_REG13,
+			   RG_SSUSB_PLL_PCW_NCPO_PE1H,
+			   FIELD_PREP(RG_SSUSB_PLL_PCW_NCPO_PE1H, 0x18000000));
+
+	regmap_update_bits(u3phyd, U3PHYA_DA + U3PHYA_DA_REG15,
+			   RG_SSUSB_PLL_PCW_NCPO_PE2H,
+			   FIELD_PREP(RG_SSUSB_PLL_PCW_NCPO_PE2H, 0x18000000));
+
+	regmap_update_bits(u3phyd, U3PHYA_DA + U3PHYA_DA_REG19,
+			   RG_SSUSB_PLL_SSC_DELTA1_PE1H,
+			   FIELD_PREP(RG_SSUSB_PLL_SSC_DELTA1_PE1H, 0x4a));
+	regmap_update_bits(u3phyd, U3PHYA_DA + U3PHYA_DA_REG19,
+			   RG_SSUSB_PLL_SSC_DELTA1_U3,
+			   FIELD_PREP(RG_SSUSB_PLL_SSC_DELTA1_U3, 0x4a));
+
+	regmap_update_bits(u3phyd, U3PHYA_DA + U3PHYA_DA_REG20,
+			   RG_SSUSB_PLL_SSC_DELTA1_PE2H,
+			   FIELD_PREP(RG_SSUSB_PLL_SSC_DELTA1_PE2H, 0x4a));
+
+	regmap_update_bits(u3phyd, U3PHYA_DA + U3PHYA_DA_REG21,
+			   RG_SSUSB_PLL_SSC_DELTA_U3,
+			   FIELD_PREP(RG_SSUSB_PLL_SSC_DELTA_U3, 0x48));
+
+	regmap_update_bits(u3phyd, U3PHYA_DA + U3PHYA_DA_REG23,
+			   RG_SSUSB_PLL_SSC_DELTA_PE1H,
+			   FIELD_PREP(RG_SSUSB_PLL_SSC_DELTA_PE1H, 0x48));
+
+	regmap_update_bits(u3phyd, U3PHYA_DA + U3PHYA_DA_REG25,
+			   RG_SSUSB_PLL_SSC_DELTA_PE2H,
+			   FIELD_PREP(RG_SSUSB_PLL_SSC_DELTA_PE2H, 0x48));
+
+	regmap_update_bits(u3phyd, U3PHYA + U3PHYA_REG9,
+			   RG_SSUSB_PLL_SSC_PRD,
+			   FIELD_PREP(RG_SSUSB_PLL_SSC_PRD, 0x190));
+	regmap_update_bits(u3phyd, U3PHYA + U3PHYA_REG4,
+			   RG_SSUSB_SYSPLL_PCW_NCPO,
+			   FIELD_PREP(RG_SSUSB_SYSPLL_PCW_NCPO, 0xe000000));
+	regmap_update_bits(u3phyd, U3PHYA + U3PHYA_REG2,
+			   RG_SSUSB_SYSPLL_FBDIV,
+			   FIELD_PREP(RG_SSUSB_SYSPLL_FBDIV, 0xe));
+	return 0;
+}
+
+static int econet_usb_phy_u3_init(struct econet_usb_phy_priv *priv,
+				  struct econet_phy_instance *instance)
+{
+	struct regmap *u3phyd = instance->regmap;
+
+	if (priv->soc == SOC_EN751221) {
+		/* Patch TxDetRx Timing for E1 */
+		regmap_update_bits(u3phyd, U3PHYD_BANK2 + B2_PHYD_RXDET1,
+				   RG_SSUSB_RXDET_STB2_SET,
+				   FIELD_PREP(RG_SSUSB_RXDET_STB2_SET, 0x10));
+
+		regmap_update_bits(u3phyd, U3PHYD_BANK2 + B2_PHYD_RXDET2,
+				   RG_SSUSB_RXDET_STB2_SET_P3,
+				   FIELD_PREP(RG_SSUSB_RXDET_STB2_SET_P3, 0x10));
+
+		/* Patch LFPS Filter Threshold for E1 */
+		regmap_update_bits(u3phyd, U3PHYD + LFPS1, RG_SSUSB_FWAKE_TH,
+				   FIELD_PREP(RG_SSUSB_FWAKE_TH, 0x34));
+
+	} else if (priv->soc == SOC_EN7528) {
+		/* combo phy Rx R FT mean value too high, target R -5 Ohm */
+		regmap_update_bits(u3phyd, U3PHYA + U3PHYA_REGB,
+				   RG_SSUSB_RX_50_TAR,
+				   FIELD_PREP(RG_SSUSB_RX_50_TAR, 0x01));
+	}
+
+	if (clk_get_rate(priv->clk) == 25 * 1000 * 1000)
+		u3_setup_25mhz_xtal(priv, instance);
+
+	return 0;
+}
+
+static int econet_usb_phy_init(struct phy *phy)
+{
+	struct econet_phy_instance *instance = phy_get_drvdata(phy);
+	struct econet_usb_phy_priv *priv = dev_get_drvdata(phy->dev.parent);
+
+	if (instance->type == PHY_TYPE_USB2)
+		return econet_usb_phy_u2_init(priv, instance);
+	else if (instance->type == PHY_TYPE_USB3)
+		return econet_usb_phy_u3_init(priv, instance);
+
+	return -EINVAL;
+}
+
+static int econet_usb_phy_exit(struct phy *phy)
+{
+	struct econet_phy_instance *instance = phy_get_drvdata(phy);
+
+	/*
+	 * Disable the port by setting PHY revision to REVISION_P0_DOWN and
+	 * enabling pulldown resistors on D+ and D- lines. The USB 3.0 PHY
+	 * piggybacks on top of the USB 2.0 for the same port and does not
+	 * have any such switch.
+	 */
+	if (instance->type == PHY_TYPE_USB2)
+		return regmap_update_bits(instance->regmap,
+					  U2PHY_COM + USBPHYACR3,
+					  RG_PUPD | RG_REV,
+					  RG_PUPD_EN | RG_PUPD_PD_DM |
+					  RG_PUPD_PD_DP | REVISION_P0_DOWN);
+
+	return 0;
+}
+
+static struct phy *econet_usb_phy_xlate(struct device *dev,
+					const struct of_phandle_args *args)
+{
+	struct econet_usb_phy_priv *priv = dev_get_drvdata(dev);
+	struct device_node *phy_np = args->np;
+	int index;
+
+	if (!of_device_is_available(phy_np)) {
+		dev_err(dev, "can't use disabled phy\n");
+		return ERR_PTR(-EINVAL);
+	}
+
+	for (index = 0; index < ARRAY_SIZE(priv->phys); index++) {
+		/* Empty slots if the DT doesn't specify all subnodes */
+		if (!priv->phys[index].phy)
+			continue;
+
+		if (phy_np != priv->phys[index].phy->dev.of_node)
+			continue;
+
+		return priv->phys[index].phy;
+	}
+
+	dev_err(dev, "failed to find phy\n");
+	return ERR_PTR(-EINVAL);
+}
+
+static const struct phy_ops econet_phy = {
+	.init		= econet_usb_phy_init,
+	.exit		= econet_usb_phy_exit,
+	.owner		= THIS_MODULE,
+};
+
+static const struct regmap_config econet_usb_phy_regmap_config = {
+	.reg_bits	= 32,
+	.val_bits	= 32,
+	.reg_stride	= 4,
+};
+
+static const struct of_device_id econet_phy_id_table[] = {
+	{ .compatible = "econet,en751221-usb-phy", .data = (void *) SOC_EN751221 },
+	{ .compatible = "econet,en751627-usb-phy", .data = (void *) SOC_EN751627 },
+	{ .compatible = "econet,en7528-usb-phy", .data = (void *) SOC_EN7528 },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, econet_phy_id_table);
+
+static int econet_usb_phy_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct device_node *np = dev->of_node;
+	struct econet_usb_phy_priv *priv;
+	const struct of_device_id *match;
+	struct phy_provider *provider;
+	void *base;
+	int phy_n;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->dev = dev;
+
+	base = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(base))
+		return dev_err_probe(dev, PTR_ERR(base), "getting reg\n");
+
+	platform_set_drvdata(pdev, priv);
+	priv->regmap = devm_regmap_init_mmio(dev, base, &econet_usb_phy_regmap_config);
+	if (IS_ERR(priv->regmap))
+		return PTR_ERR(priv->regmap);
+
+	priv->clk = devm_clk_get(dev, NULL);
+	if (IS_ERR(priv->clk))
+		return dev_err_probe(dev, PTR_ERR(priv->clk), "getting clk\n");
+
+	match = of_match_node(econet_phy_id_table, np);
+	if (!match || !match->data)
+		return dev_err_probe(dev, -EINVAL, "no OF match data\n");
+
+	priv->soc = (enum econet_phy_soc)(uintptr_t)match->data;
+
+	phy_n = 0;
+	for_each_child_of_node_scoped(np, child_np) {
+		struct econet_phy_instance *instance;
+		void __iomem *phy_base;
+		struct device *subdev;
+		struct resource res;
+		int retval;
+
+		if (phy_n >= ARRAY_SIZE(priv->phys))
+			return dev_err_probe(dev, -EINVAL,
+					     "too many PHY subnodes\n");
+
+		instance = &priv->phys[phy_n];
+
+		if (of_device_is_compatible(child_np, "econet,usb2-phy"))
+			instance->type = PHY_TYPE_USB2;
+		else if (of_device_is_compatible(child_np, "econet,usb3-phy"))
+			instance->type = PHY_TYPE_USB3;
+		else
+			return dev_err_probe(dev, -EINVAL,
+					     "phy %d unknown type\n", phy_n);
+
+		instance->phy = devm_phy_create(dev, child_np, &econet_phy);
+		if (IS_ERR(instance->phy))
+			return dev_err_probe(dev, PTR_ERR(instance->phy),
+					     "failed to create phy\n");
+
+		subdev = &instance->phy->dev;
+		retval = of_address_to_resource(child_np, 0, &res);
+		if (retval)
+			return dev_err_probe(subdev, retval,
+					     "getting reg for phy %d\n",
+					     phy_n);
+
+		phy_base = devm_ioremap_resource(subdev, &res);
+		if (IS_ERR(phy_base))
+			return dev_err_probe(subdev, PTR_ERR(phy_base),
+					     "remapping reg for phy %d\n",
+					     phy_n);
+
+		instance->regmap =
+			devm_regmap_init_mmio(subdev, phy_base,
+					      &econet_usb_phy_regmap_config);
+		if (IS_ERR(instance->regmap))
+			return dev_err_probe(subdev, PTR_ERR(instance->regmap),
+					     "making regmap for phy %d\n",
+					     phy_n);
+
+		retval = device_property_read_u32(subdev, ECONET_PORT_ID_FIELD,
+						  &instance->port_id);
+		if (retval)
+			return dev_err_probe(subdev, retval,
+					     "getting %s for phy %d\n",
+					     ECONET_PORT_ID_FIELD, phy_n);
+
+		phy_set_drvdata(instance->phy, instance);
+
+		/*
+		 * Initial setup ritual disables both ports. On QFP silicon,
+		 * only one port is wired (port1) but port0 still has a PHY
+		 * present which is still disabled in setup.
+		 */
+		retval = econet_usb_phy_exit(instance->phy);
+		if (retval)
+			dev_warn(subdev, "stopping phy %d: %pe\n", phy_n,
+				 ERR_PTR(retval));
+
+		phy_n++;
+	}
+
+	provider = devm_of_phy_provider_register(dev, econet_usb_phy_xlate);
+
+	return PTR_ERR_OR_ZERO(provider);
+}
+
+static struct platform_driver econet_usb_phy_driver = {
+	.probe		= econet_usb_phy_probe,
+	.driver		= {
+		.name	= "econet-usb-phy",
+		.of_match_table = econet_phy_id_table,
+	},
+};
+
+module_platform_driver(econet_usb_phy_driver);
+
+MODULE_AUTHOR("Benjamin Larsson <benjamin.larsson@genexis.eu>");
+MODULE_AUTHOR("Caleb James DeLisle <cjd@cjdns.fr>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Econet USB PHY driver");
-- 
2.39.5


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* Re: [PATCH 2/2] phy: econet: Add EcoNet USB PHY
  2026-05-18 14:13 ` [PATCH 2/2] phy: econet: Add EcoNet " Caleb James DeLisle
@ 2026-05-18 14:40   ` sashiko-bot
  0 siblings, 0 replies; 4+ messages in thread
From: sashiko-bot @ 2026-05-18 14:40 UTC (permalink / raw)
  To: Caleb James DeLisle
  Cc: linux-phy, robh, devicetree, vkoul, neil.armstrong, conor+dt,
	olteanv

Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [High] Race condition on shared Frequency Meter registers during concurrent PHY initialization.
- [High] Hardware state machine race due to polling data register instead of valid bit.
- [High] Devres lifecycle bug caused by attaching memory and regmap resources to the child PHY device.
- [Medium] Iteration over disabled Device Tree nodes accesses unpowered hardware.
- [Medium] Missing bounds check on dynamically calculated slew rate configuration exposes hardware to silent bit truncation.
- [Low] Pointer signedness mismatch in `regmap_read_poll_timeout` causes compiler warnings and violates type safety.
--

commit ab614fd3da75925164048ae81b01b59ec760d67e
Author: Caleb James DeLisle <cjd@cjdns.fr>

phy: econet: Add EcoNet USB PHY

This commit adds support for the USB PHY found on EcoNet EN751221, EN751627,
and EN7528 based SoCs. It implements the setup and tuning rituals for both
USB 2.0 and USB 3.0 PHY blocks.

Link: https://github.com/openwrt/openwrt/pull/23264#issuecomment-4415906568

> diff --git a/drivers/phy/phy-econet-usb.c b/drivers/phy/phy-econet-usb.c
> new file mode 100644
> index 0000000000000..e8fa2e80eb130
> --- /dev/null
> +++ b/drivers/phy/phy-econet-usb.c

[ ... ]

> +static void u2_slew_rate_calibration(struct econet_usb_phy_priv *priv,
> +				     struct econet_phy_instance *instance)
> +{
> +	struct regmap *com = instance->regmap;
> +	struct regmap *regmap = priv->regmap;
> +	int fm_out = 0;

Does this pointer type cause compiler warnings?

regmap_read_poll_timeout() passes the address of fm_out to regmap_read(),
which expects an unsigned int pointer. Using a signed int can lead to
incompatible pointer type warnings.

> +	u32 srctrl;
> +
> +	/* Enable HS TX SR calibration */
> +	regmap_set_bits(com, U2PHY_COM + USBPHYACR0, RG_HSTX_SRCAL_EN);
> +
> +	usleep_range(1000, 1500);
> +
> +	/* Enable Free run clock */
> +	regmap_set_bits(regmap, FMREG + FMMONR1, RG_FRCK_EN);
> +
> +	/* Select Monitor Clock */
> +	regmap_update_bits(regmap, FMREG + FMCR0, RG_MONCLK_SEL,
> +			   FIELD_PREP(RG_MONCLK_SEL, instance->port_id));

Is it possible for multiple PHY ports to race when accessing these shared
FMREG registers?

The Linux PHY core serializes phy_init() only per-PHY instance, meaning
multiple ports could potentially initialize at the same time. If two
threads run this calibration concurrently, could one overwrite the active
port selection or prematurely clear the frequency detection enable bit
while the other is still polling?

Might this require a driver-level mutex to serialize access to the FMREG
hardware block?

> +
> +	/* Set cyclecnt */
> +	regmap_update_bits(regmap, FMREG + FMCR0, RG_CYCLECNT,
> +			   FIELD_PREP_CONST(RG_CYCLECNT,
> +					    ECONET_USB_PHY_U2_FM_DET_CYCLE_CNT));
> +
> +	/* Enable Frequency meter */
> +	regmap_set_bits(regmap, FMREG + FMCR0, RG_FREQDET_EN);
> +
> +	/* Timeout can happen and we will apply workaround at the end */
> +	regmap_read_poll_timeout(regmap, FMREG + FMMONR0, fm_out,
> +				 fm_out, ECONET_USB_PHY_FREQDET_SLEEP,
> +				 ECONET_USB_PHY_FREQDET_TIMEOUT);

Could this loop prematurely succeed by reading a stale value?

FMMONR0 holds the result of the calibration. If this function is called
a second time for another port, polling for fm_out != 0 might instantly
succeed using the previous run's result.

Would it be more robust to poll the FMMONR1 register for the RG_FM_VALID
flag before reading the result from FMMONR0?

[ ... ]

> +	} else {
> +		/* (1024 / FM_OUT) * REF_CK * U2_SR_COEF (round to nearest) */
> +		srctrl = ECONET_USB_PHY_REF_CK * ECONET_USB_PHY_U2_SR_COEF;
> +		srctrl = (srctrl * ECONET_USB_PHY_U2_FM_DET_CYCLE_CNT) / fm_out;
> +		srctrl = DIV_ROUND_CLOSEST(srctrl,
> +					   ECONET_USB_PHY_U2_SR_COEF_DIVISOR);
> +		dev_info(&instance->phy->dev,
> +			 "frequency meter result 0x%x, set slew rate 0x%x\n",
> +			 fm_out, srctrl);
> +	}
> +
> +	/* Set slew rate */
> +	regmap_update_bits(com, U2PHY_COM + USBPHYACR0, RG_HSTX_SRCTRL,
> +			   FIELD_PREP(RG_HSTX_SRCTRL, srctrl));

Is there a risk of silent bit truncation here?

The target field RG_HSTX_SRCTRL is only 3 bits wide. If the frequency meter
provides a low output value (for instance, due to a stale read), the division
above could yield a value much larger than 7.

Since FIELD_PREP masks runtime values without error, could this write
an unintended configuration to the hardware? Should the calculated srctrl
value be clamped before applying it?

[ ... ]

> +static int econet_usb_phy_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct device_node *np = dev->of_node;

[ ... ]

> +	phy_n = 0;
> +	for_each_child_of_node_scoped(np, child_np) {

Does this loop interact with disabled hardware blocks?

Using for_each_child_of_node_scoped() will iterate over all child nodes,
including those with status = "disabled" in the device tree.

This results in the driver mapping memory, initializing regmaps, and
calling econet_usb_phy_exit() to write to registers on unpowered or
unclocked hardware, which can sometimes cause system hangs.

Would it be safer to use for_each_available_child_of_node_scoped()?

> +		struct econet_phy_instance *instance;
> +		void __iomem *phy_base;
> +		struct device *subdev;
> +		struct resource res;
> +		int retval;

[ ... ]

> +		instance->phy = devm_phy_create(dev, child_np, &econet_phy);
> +		if (IS_ERR(instance->phy))
> +			return dev_err_probe(dev, PTR_ERR(instance->phy),
> +					     "failed to create phy\n");
> +
> +		subdev = &instance->phy->dev;
> +		retval = of_address_to_resource(child_np, 0, &res);
> +		if (retval)
> +			return dev_err_probe(subdev, retval,
> +					     "getting reg for phy %d\n",
> +					     phy_n);
> +
> +		phy_base = devm_ioremap_resource(subdev, &res);

Can using the child PHY device for resource allocations cause memory leaks
and probe failures?

The subdev pointer belongs to the child PHY device created by
devm_phy_create(). Devres resources are tied to the device's reference
count reaching zero.

If userspace holds an active sysfs file descriptor for the PHY device when
the parent platform driver unbinds, the subdev kobject stays alive and
the memory region is not freed.

If the platform driver is re-probed, it might fail with -EBUSY when
requesting the same memory region. Should devm_ioremap_resource and
devm_regmap_init_mmio be associated with the parent platform device instead?

> +		if (IS_ERR(phy_base))
> +			return dev_err_probe(subdev, PTR_ERR(phy_base),
> +					     "remapping reg for phy %d\n",
> +					     phy_n);
> +
> +		instance->regmap =
> +			devm_regmap_init_mmio(subdev, phy_base,
> +					      &econet_usb_phy_regmap_config);
> +		if (IS_ERR(instance->regmap))
> +			return dev_err_probe(subdev, PTR_ERR(instance->regmap),
> +					     "making regmap for phy %d\n",
> +					     phy_n);

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260518141343.401555-1-cjd@cjdns.fr?part=2

^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2026-05-18 14:40 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-18 14:13 [PATCH 0/2] Add support for EcoNet USB PHY Caleb James DeLisle
2026-05-18 14:13 ` [PATCH 1/2] dt-bindings: phy: econet: Document EN751221 " Caleb James DeLisle
2026-05-18 14:13 ` [PATCH 2/2] phy: econet: Add EcoNet " Caleb James DeLisle
2026-05-18 14:40   ` sashiko-bot

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox