Devicetree
 help / color / mirror / Atom feed
* [PATCH 1/2] dt-bindings: phy: Document Renesas Multi-Protocol PHY driver for R-Car X5H
@ 2026-08-28 19:21 Marek Vasut
  2026-08-28 19:21 ` [PATCH 2/2] phy: renesas: Add " Marek Vasut
                   ` (2 more replies)
  0 siblings, 3 replies; 6+ messages in thread
From: Marek Vasut @ 2026-08-28 19:21 UTC (permalink / raw)
  To: linux-phy
  Cc: Marek Vasut, Arnd Bergmann, Biju Das, Conor Dooley,
	Geert Uytterhoeven, Hai Pham, Krzysztof Kozlowski, Magnus Damm,
	Manivannan Sadhasivam, Neil Armstrong, Phong Hoang, Rob Herring,
	Tam Nguyen, Thanh Quan, Tommaso Merciai, Vinh Nguyen, Vinod Koul,
	Yoshihiro Shimoda, devicetree, linux-kernel, linux-renesas-soc

Document Renesas Multi-Protocol PHY present in R-Car X5H (R8A78000) SoC.
This is a multiplexing PHY block with inputs from five IPs, and outputs
to four separate IO blocks. The five inputs are two PCIe4, two USB 3.2,
and ethernet PCS, each connected into the PHY with up to 4 times 2 lanes.
The inputs can be multiplexed to the four IO blocks as needed.

Signed-off-by: Marek Vasut <marek.vasut+renesas@mailbox.org>
---
Cc: Arnd Bergmann <arnd@arndb.de>
Cc: Biju Das <biju.das.jz@bp.renesas.com>
Cc: Conor Dooley <conor+dt@kernel.org>
Cc: Geert Uytterhoeven <geert+renesas@glider.be>
Cc: Hai Pham <hai.pham.ud@renesas.com>
Cc: Krzysztof Kozlowski <krzk+dt@kernel.org>
Cc: Magnus Damm <magnus.damm@gmail.com>
Cc: Manivannan Sadhasivam <mani@kernel.org>
Cc: Neil Armstrong <neil.armstrong@linaro.org>
Cc: Phong Hoang <phong.hoang.wz@renesas.com>
Cc: Rob Herring <robh@kernel.org>
Cc: Tam Nguyen <tam.nguyen.xa@renesas.com>
Cc: Thanh Quan <thanh.quan.xn@renesas.com>
Cc: Tommaso Merciai <tommaso.merciai.xr@bp.renesas.com>
Cc: Vinh Nguyen <vinh.nguyen.xz@renesas.com>
Cc: Vinod Koul <vkoul@kernel.org>
Cc: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com>
Cc: devicetree@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Cc: linux-phy@lists.infradead.org
Cc: linux-renesas-soc@vger.kernel.org
---
 .../bindings/phy/renesas,rcar-gen5-mpphy.yaml | 125 ++++++++++++++++++
 1 file changed, 125 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/phy/renesas,rcar-gen5-mpphy.yaml

diff --git a/Documentation/devicetree/bindings/phy/renesas,rcar-gen5-mpphy.yaml b/Documentation/devicetree/bindings/phy/renesas,rcar-gen5-mpphy.yaml
new file mode 100644
index 0000000000000..2da8be2d3de82
--- /dev/null
+++ b/Documentation/devicetree/bindings/phy/renesas,rcar-gen5-mpphy.yaml
@@ -0,0 +1,125 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/phy/renesas,rcar-gen5-mpphy.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Renesas R-Car Generation 5 Multi-Protocol PHY
+
+maintainers:
+  - Marek Vasut <marek.vasut+renesas@mailbox.org>
+  - Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com>
+
+properties:
+  compatible:
+    items:
+      - const: renesas,r8a78000-mpphy   # R-Car X5H MPPHY
+      - const: renesas,rcar-gen5-mpphy  # R-Car Gen5 MPPHY
+
+  reg:
+    maxItems: 1
+
+  clocks:
+    minItems: 5
+    maxItems: 5
+
+  clock-names:
+    items:
+      - const: mpphy01
+      - const: mpphy11
+      - const: mpphy21
+      - const: mpphy31
+      - const: mpphy02
+
+  power-domains:
+    minItems: 4
+    maxItems: 4
+
+  power-domain-names:
+    items:
+      - const: mpp0
+      - const: mpp1
+      - const: mpp2
+      - const: mpp3
+
+  resets:
+    minItems: 5
+    maxItems: 5
+
+  reset-names:
+    items:
+      - const: mpphy01
+      - const: mpphy11
+      - const: mpphy21
+      - const: mpphy31
+      - const: mpphy02
+
+  '#phy-cells':
+    const: 1
+
+  renesas,num-lanes:
+    description: PCIe lane count for each PHY input.
+    $ref: /schemas/types.yaml#/definitions/uint32-array
+    minItems: 4
+    maxItems: 4
+
+  renesas,phy-type:
+    description: PHY multiplexing configuration for each output.
+    $ref: /schemas/types.yaml#/definitions/uint32-array
+    minItems: 8
+    maxItems: 8
+
+  renesas,external-ref-clock:
+    description: PHY external reference clock select for each output.
+    $ref: /schemas/types.yaml#/definitions/uint32-array
+    minItems: 4
+    maxItems: 4
+
+  renesas,output-repeat-ref-clock:
+    description: PHY repeat reference clock select for each output.
+    $ref: /schemas/types.yaml#/definitions/uint32-array
+    minItems: 4
+    maxItems: 4
+
+required:
+  - compatible
+  - reg
+  - clocks
+  - clock-names
+  - power-domains
+  - power-domain-names
+  - resets
+  - reset-names
+  - '#phy-cells'
+  - renesas,num-lanes
+  - renesas,phy-type
+  - renesas,external-ref-clock
+  - renesas,output-repeat-ref-clock
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/phy/phy.h>
+
+    phy@c9a00000 {
+      compatible = "renesas,r8a78000-mpphy", "renesas,rcar-gen5-mpphy";
+      reg = <0xc9a00000 0x100000>;
+      #phy-cells = <1>;
+
+      clocks = <&scmi_clk 340>, <&scmi_clk 341>, <&scmi_clk 342>,
+               <&scmi_clk 343>, <&scmi_clk 344>;
+      clock-names = "mpphy01", "mpphy11", "mpphy21", "mpphy31", "mpphy02";
+      power-domains = <&scmi_devpd 17>, <&scmi_devpd 18>,
+                      <&scmi_devpd 19>, <&scmi_devpd 20>;
+      power-domain-names = "mpp0", "mpp1", "mpp2", "mpp3";
+      resets = <&scmi_reset 340>, <&scmi_reset 341>, <&scmi_reset 342>,
+               <&scmi_reset 343>, <&scmi_reset 344>;
+      reset-names = "mpphy01", "mpphy11", "mpphy21", "mpphy31", "mpphy02";
+
+      renesas,num-lanes = <4 4 0 0>;
+      renesas,phy-type = <PHY_TYPE_PCIE 0>, <PHY_TYPE_PCIE 0>,
+                         <PHY_TYPE_XPCS 2>, <PHY_TYPE_USB3 1>;
+      renesas,external-ref-clock = <1 1 1 1>;
+      renesas,output-repeat-ref-clock = <0 0 0 0>;
+    };
-- 
2.53.0


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

* [PATCH 2/2] phy: renesas: Add Renesas Multi-Protocol PHY driver for R-Car X5H
  2026-08-28 19:21 [PATCH 1/2] dt-bindings: phy: Document Renesas Multi-Protocol PHY driver for R-Car X5H Marek Vasut
@ 2026-08-28 19:21 ` Marek Vasut
  2026-08-28 19:35   ` sashiko-bot
  2026-09-03 10:15   ` Manivannan Sadhasivam
  2026-08-28 19:28 ` [PATCH 1/2] dt-bindings: phy: Document " sashiko-bot
  2026-08-31  8:13 ` Geert Uytterhoeven
  2 siblings, 2 replies; 6+ messages in thread
From: Marek Vasut @ 2026-08-28 19:21 UTC (permalink / raw)
  To: linux-phy
  Cc: Thanh Quan, Phong Hoang, Hai Pham, Yoshihiro Shimoda, Marek Vasut,
	Arnd Bergmann, Biju Das, Conor Dooley, Geert Uytterhoeven,
	Krzysztof Kozlowski, Magnus Damm, Manivannan Sadhasivam,
	Neil Armstrong, Rob Herring, Tam Nguyen, Tommaso Merciai,
	Vinh Nguyen, Vinod Koul, devicetree, linux-kernel,
	linux-renesas-soc

From: Thanh Quan <thanh.quan.xn@renesas.com>

Add Renesas Multi-Protocol PHY driver for R-Car X5H (R8A78000) SoC.

This is a multiplexing PHY block with inputs from five IPs, and outputs
to four separate IO blocks. The five inputs are two PCIe4, two USB 3.2,
and ethernet PCS, each connected into the PHY with up to 4 times 2 lanes.
The inputs can be multiplexed to the four IO blocks as needed.

The datasheet describes initialization procedure without many information
about registers' name/bits. So, this driver does contain a bit of magic
to initialize the hardware.

Signed-off-by: Thanh Quan <thanh.quan.xn@renesas.com>
Co-developed-by: Phong Hoang <phong.hoang.wz@renesas.com>
Signed-off-by: Phong Hoang <phong.hoang.wz@renesas.com>
Co-developed-by: Hai Pham <hai.pham.ud@renesas.com>
Signed-off-by: Hai Pham <hai.pham.ud@renesas.com>
Co-developed-by: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com>
Signed-off-by: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com>
Co-developed-by: Marek Vasut <marek.vasut+renesas@mailbox.org>
Signed-off-by: Marek Vasut <marek.vasut+renesas@mailbox.org>
---
Cc: Arnd Bergmann <arnd@arndb.de>
Cc: Biju Das <biju.das.jz@bp.renesas.com>
Cc: Conor Dooley <conor+dt@kernel.org>
Cc: Geert Uytterhoeven <geert+renesas@glider.be>
Cc: Hai Pham <hai.pham.ud@renesas.com>
Cc: Krzysztof Kozlowski <krzk+dt@kernel.org>
Cc: Magnus Damm <magnus.damm@gmail.com>
Cc: Manivannan Sadhasivam <mani@kernel.org>
Cc: Neil Armstrong <neil.armstrong@linaro.org>
Cc: Phong Hoang <phong.hoang.wz@renesas.com>
Cc: Rob Herring <robh@kernel.org>
Cc: Tam Nguyen <tam.nguyen.xa@renesas.com>
Cc: Thanh Quan <thanh.quan.xn@renesas.com>
Cc: Tommaso Merciai <tommaso.merciai.xr@bp.renesas.com>
Cc: Vinh Nguyen <vinh.nguyen.xz@renesas.com>
Cc: Vinod Koul <vkoul@kernel.org>
Cc: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com>
Cc: devicetree@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Cc: linux-phy@lists.infradead.org
Cc: linux-renesas-soc@vger.kernel.org
---
 drivers/phy/renesas/Kconfig               |   7 +
 drivers/phy/renesas/Makefile              |   1 +
 drivers/phy/renesas/phy-rcar-gen5-mpphy.c | 757 ++++++++++++++++++++++
 3 files changed, 765 insertions(+)
 create mode 100644 drivers/phy/renesas/phy-rcar-gen5-mpphy.c

diff --git a/drivers/phy/renesas/Kconfig b/drivers/phy/renesas/Kconfig
index 90a9ca2db7fc7..ac903b29cacbc 100644
--- a/drivers/phy/renesas/Kconfig
+++ b/drivers/phy/renesas/Kconfig
@@ -43,6 +43,13 @@ config PHY_RCAR_GEN3_USB3
 	help
 	  Support for USB 3.0 PHY found on Renesas R-Car generation 3 SoCs.
 
+config PHY_RCAR_GEN5_MPPHY
+	tristate "Renesas R-Car generation 5 Multi-Protocol PHY driver"
+	depends on ARCH_RENESAS || COMPILE_TEST
+	select GENERIC_PHY
+	help
+	  Support for Multi-Protocol PHY found on Renesas R-Car generation 5 SoCs.
+
 config PHY_RZ_G3E_USB3
 	tristate "Renesas RZ/G3E USB 3.0 PHY driver"
 	depends on ARCH_RENESAS || COMPILE_TEST
diff --git a/drivers/phy/renesas/Makefile b/drivers/phy/renesas/Makefile
index 0e98083f2f0c8..23d413cfa8ce0 100644
--- a/drivers/phy/renesas/Makefile
+++ b/drivers/phy/renesas/Makefile
@@ -4,4 +4,5 @@ obj-$(CONFIG_PHY_RCAR_GEN2)		+= phy-rcar-gen2.o
 obj-$(CONFIG_PHY_RCAR_GEN3_PCIE)	+= phy-rcar-gen3-pcie.o
 obj-$(CONFIG_PHY_RCAR_GEN3_USB2)	+= phy-rcar-gen3-usb2.o
 obj-$(CONFIG_PHY_RCAR_GEN3_USB3)	+= phy-rcar-gen3-usb3.o
+obj-$(CONFIG_PHY_RCAR_GEN5_MPPHY)	+= phy-rcar-gen5-mpphy.o
 obj-$(CONFIG_PHY_RZ_G3E_USB3)		+= phy-rzg3e-usb3.o
diff --git a/drivers/phy/renesas/phy-rcar-gen5-mpphy.c b/drivers/phy/renesas/phy-rcar-gen5-mpphy.c
new file mode 100644
index 0000000000000..31f17a4955e8e
--- /dev/null
+++ b/drivers/phy/renesas/phy-rcar-gen5-mpphy.c
@@ -0,0 +1,757 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Renesas Multi-Protocol PHY device driver
+ *
+ * Copyright (C) 2025-2026 Renesas Electronics Corporation
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/firmware.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/pm_domain.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <linux/types.h>
+
+#include <dt-bindings/phy/phy.h>
+
+#define MPPHY_NUM_CHANNELS		4
+
+/* Common registers */
+#define MPPHY_CMNCNT1			0x80000
+#define MPPHY_CMNCNT2			0x80004
+
+/* Channel register base and offsets */
+#define MPPHY_CHAN_BASE(ch)		(0x81000 + (ch) * 0x1000)
+#define MPPHY_PXCNTXT1(ch)		(MPPHY_CHAN_BASE(ch) + 0x4)
+#define MPPHY_PXCNTXT2(ch)		(MPPHY_CHAN_BASE(ch) + 0x8)
+#define MPPHY_PXTEST(ch)		(MPPHY_CHAN_BASE(ch) + 0xc)
+#define MPPHY_PXREFCLK(ch)		(MPPHY_CHAN_BASE(ch) + 0x14)
+#define MPPHY_PXRXREQ1(ch)		(MPPHY_CHAN_BASE(ch) + 0x24)
+#define MPPHY_PXRXCNT(ch)		(MPPHY_CHAN_BASE(ch) + 0x38)
+#define MPPHY_PXSRAMCNT(ch)		(MPPHY_CHAN_BASE(ch) + 0x40)
+#define MPPHY_PXTXREQ(ch)		(MPPHY_CHAN_BASE(ch) + 0x44)
+
+#define MPPHY_PCS0REG1			0x85000
+#define MPPHY_PCS0REG5			0x85010
+
+/* TCA (Type-C Adapter) Register Offsets within MP-PHY base */
+#define TCA_OFFSET(ch)			(0x90000 + (((ch) & 1) ? 0x10000 : 0))
+#define TCA_VBUS_CTRL			0x40
+
+/* PCS0REG1 register bits */
+#define MPPHY_PCS0REG1_VAL		BIT(16)
+
+/* PXTEST register bit */
+#define MPPHY_PXTEST_BIT		BIT(0)
+
+/* PXRXCNT register reset value */
+#define MPPHY_PXRXCNT_RESET_VAL		0x202
+
+/* PXSRAMCNT register bits */
+#define SRAM_EXT_LD_DONE		0x10
+
+/* PXREFCLK register value */
+#define MPPHY_PXREFCLK_VAL_ETH		0x55
+
+/* Firmware update */
+#define MPPHY_FW_BASE			0x10000
+#define MPPHY_FW_CH_OFFSET		0x20000
+#define MPPHY_FW_NAME			"rcar_gen5_mp_phy.bin"
+
+struct mp_phy_chan_priv {
+	struct phy *phy;
+	enum phy_mode protocol_id;
+	bool initialized;
+};
+
+struct mp_phy_priv {
+	void __iomem *base;
+	struct device *dev;
+	struct dev_pm_domain_list *pd_list;
+	struct reset_control_bulk_data resets[MPPHY_NUM_CHANNELS + 1];
+	struct clk_bulk_data *clks;
+	int num_clks;
+	const struct firmware *fw;
+	struct regmap *map;
+	struct mp_phy_chan_priv chan[MPPHY_NUM_CHANNELS];
+	u32 num_lanes[MPPHY_NUM_CHANNELS];
+	u32 write_cntxt1;
+	u32 cmncnt[2];
+	u8 sramcnt[MPPHY_NUM_CHANNELS];
+};
+
+#define MPPHY_PX_RD_RANGE(n)						\
+	regmap_reg_range(MPPHY_PXCNTXT1(n), MPPHY_CHAN_BASE(n)),	\
+	regmap_reg_range(MPPHY_PXREFCLK(n), MPPHY_PXREFCLK(n)),		\
+	regmap_reg_range(MPPHY_PXRXREQ1(n), MPPHY_PXRXREQ1(n)),		\
+	regmap_reg_range(MPPHY_PXRXCNT(n), MPPHY_PXRXCNT(n)),		\
+	regmap_reg_range(MPPHY_PXSRAMCNT(n), MPPHY_PXTXREQ(n))
+
+static const struct regmap_range mp_phy_readable_range[] = {
+	regmap_reg_range(MPPHY_CMNCNT1, MPPHY_CMNCNT2),
+	MPPHY_PX_RD_RANGE(0), MPPHY_PX_RD_RANGE(1),
+	MPPHY_PX_RD_RANGE(2), MPPHY_PX_RD_RANGE(3),
+	regmap_reg_range(MPPHY_PCS0REG1, MPPHY_PCS0REG1),
+	regmap_reg_range(MPPHY_PCS0REG5, MPPHY_PCS0REG5),
+	regmap_reg_range(TCA_OFFSET(0) + TCA_VBUS_CTRL, TCA_OFFSET(0) + TCA_VBUS_CTRL),
+	regmap_reg_range(TCA_OFFSET(1) + TCA_VBUS_CTRL, TCA_OFFSET(1) + TCA_VBUS_CTRL),
+};
+
+static const struct regmap_access_table mp_phy_readable_table = {
+	.yes_ranges = mp_phy_readable_range,
+	.n_yes_ranges = ARRAY_SIZE(mp_phy_readable_range),
+};
+
+#define MPPHY_PX_WR_RANGE(n)						\
+	regmap_reg_range(MPPHY_PXCNTXT1(n), MPPHY_CHAN_BASE(n)),	\
+	regmap_reg_range(MPPHY_PXREFCLK(n), MPPHY_PXREFCLK(n)),		\
+	regmap_reg_range(MPPHY_PXRXCNT(n), MPPHY_PXRXCNT(n)),		\
+	regmap_reg_range(MPPHY_PXSRAMCNT(n), MPPHY_PXTXREQ(n))
+
+static const struct regmap_range mp_phy_writeable_range[] = {
+	regmap_reg_range(MPPHY_FW_BASE, MPPHY_FW_BASE + 4 * MPPHY_FW_CH_OFFSET),
+	regmap_reg_range(MPPHY_CMNCNT1, MPPHY_CMNCNT2),
+	MPPHY_PX_WR_RANGE(0), MPPHY_PX_WR_RANGE(1),
+	MPPHY_PX_WR_RANGE(2), MPPHY_PX_WR_RANGE(3),
+	regmap_reg_range(MPPHY_PCS0REG1, MPPHY_PCS0REG1),
+	regmap_reg_range(MPPHY_PCS0REG5, MPPHY_PCS0REG5),
+	regmap_reg_range(TCA_OFFSET(0) + TCA_VBUS_CTRL, TCA_OFFSET(0) + TCA_VBUS_CTRL),
+	regmap_reg_range(TCA_OFFSET(1) + TCA_VBUS_CTRL, TCA_OFFSET(1) + TCA_VBUS_CTRL),
+};
+
+static const struct regmap_access_table mp_phy_writeable_table = {
+	.yes_ranges = mp_phy_writeable_range,
+	.n_yes_ranges = ARRAY_SIZE(mp_phy_writeable_range),
+};
+
+#define MPPHY_PX_VL_RANGE(n)						\
+	regmap_reg_range(MPPHY_PXRXREQ1(n), MPPHY_PXRXREQ1(n)),		\
+	regmap_reg_range(MPPHY_PXSRAMCNT(n), MPPHY_PXTXREQ(n))
+
+static const struct regmap_range mp_phy_volatile_range[] = {
+	MPPHY_PX_VL_RANGE(0), MPPHY_PX_VL_RANGE(1),
+	MPPHY_PX_VL_RANGE(2), MPPHY_PX_VL_RANGE(3),
+	regmap_reg_range(MPPHY_PCS0REG1, MPPHY_PCS0REG1),
+	regmap_reg_range(MPPHY_PCS0REG5, MPPHY_PCS0REG5),
+};
+
+static const struct regmap_access_table mp_phy_volatile_table = {
+	.yes_ranges = mp_phy_volatile_range,
+	.n_yes_ranges = ARRAY_SIZE(mp_phy_volatile_range),
+};
+
+static const struct regmap_config mp_phy_regmap_config = {
+	.fast_io	= true,
+	.reg_bits	= 32,
+	.val_bits	= 32,
+	.reg_stride	= 4,
+	.max_register	= TCA_OFFSET(1) + TCA_VBUS_CTRL,
+	.name		= "mpphy",
+	.rd_table	= &mp_phy_readable_table,
+	.wr_table	= &mp_phy_writeable_table,
+	.volatile_table	= &mp_phy_volatile_table,
+	.cache_type	= REGCACHE_MAPLE,
+};
+
+static int mp_phy_reg_wait(struct mp_phy_priv *priv, u32 offs, u32 mask, u32 expected)
+{
+	u32 val;
+	int ret;
+
+	ret = regmap_read_poll_timeout(priv->map, offs, val,
+				       (val & mask) == expected,
+				       0, 1000000);
+	if (ret) {
+		dev_err(priv->dev,
+			"Timeout waiting for offset: 0x%x, mask: 0x%x, expected: 0x%x\n",
+			offs, mask, expected);
+	}
+
+	return ret;
+}
+
+static void mp_phy_update_firmware(struct phy *phy)
+{
+	struct mp_phy_priv *priv = phy_get_drvdata(phy);
+	int i;
+
+	for (i = 0; i < priv->fw->size; i += 2) {
+		writew(priv->fw->data[i] | (priv->fw->data[i + 1] << 8),
+		       priv->base + MPPHY_FW_BASE + (MPPHY_FW_CH_OFFSET * phy->id) + i);
+	}
+}
+
+static int mp_phy_exit(struct phy *phy)
+{
+	struct mp_phy_priv *priv = phy_get_drvdata(phy);
+	struct mp_phy_chan_priv *chan = &priv->chan[phy->id];
+
+	if (!chan->initialized)
+		return 0;
+
+	chan->initialized = false;
+
+	pm_runtime_put_sync(priv->pd_list->pd_devs[phy->id]);
+
+	return 0;
+}
+
+static int mp_phy_init_ethernet(struct phy *phy)
+{
+	struct mp_phy_priv *priv = phy_get_drvdata(phy);
+
+	mp_phy_update_firmware(phy);
+
+	regmap_write(priv->map, MPPHY_PXRXCNT(phy->id), MPPHY_PXRXCNT_RESET_VAL);
+	regmap_set_bits(priv->map, MPPHY_PXREFCLK(phy->id), MPPHY_PXREFCLK_VAL_ETH);
+	regmap_clear_bits(priv->map, MPPHY_PXRXCNT(phy->id), BIT(9) | BIT(1));
+	regmap_set_bits(priv->map, MPPHY_PXTXREQ(phy->id), BIT(19) | BIT(3));
+
+	return 0;
+}
+
+static int mp_phy_init_pcie4(struct phy *phy)
+{
+	struct mp_phy_priv *priv = phy_get_drvdata(phy);
+
+	if (priv->num_lanes[phy->id] == 1 || priv->num_lanes[phy->id] == 2) {
+		if (phy->id == 0) {
+			regmap_set_bits(priv->map, MPPHY_PXCNTXT1(0), 0x2010002);
+			regmap_set_bits(priv->map, MPPHY_PXCNTXT2(0), 0x2020201);
+			regmap_set_bits(priv->map, MPPHY_PXTXREQ(0), 0x80004);
+			regmap_set_bits(priv->map, MPPHY_PXREFCLK(0), 0x30);
+			regmap_set_bits(priv->map, MPPHY_PXREFCLK(0), 0x4);
+			regmap_set_bits(priv->map, MPPHY_PXREFCLK(0), 0x1);
+			regmap_set_bits(priv->map, MPPHY_PXRXCNT(0), 0x202);
+			regmap_clear_bits(priv->map, MPPHY_PXRXCNT(0), 0x202);
+		} else if (phy->id == 1) {
+			regmap_set_bits(priv->map, MPPHY_PXCNTXT1(2), 0x2010002);
+			regmap_set_bits(priv->map, MPPHY_PXCNTXT2(2), 0x2020202);
+			regmap_set_bits(priv->map, MPPHY_PXTXREQ(2), 0x8);
+			regmap_set_bits(priv->map, MPPHY_PXREFCLK(2), 0x30);
+			regmap_set_bits(priv->map, MPPHY_PXREFCLK(2), 0x4);
+			regmap_set_bits(priv->map, MPPHY_PXREFCLK(2), 0x1);
+			regmap_set_bits(priv->map, MPPHY_PXRXCNT(2), 0x202);
+			regmap_clear_bits(priv->map, MPPHY_PXRXCNT(2), 0x202);
+		}
+	} else if (priv->num_lanes[phy->id] == 4) {
+		if (phy->id == 0) {
+			regmap_set_bits(priv->map, MPPHY_PXCNTXT1(0), 0x2010002);
+			regmap_set_bits(priv->map, MPPHY_PXCNTXT2(0), 0x2020201);
+			regmap_set_bits(priv->map, MPPHY_PXTXREQ(0), 0x8);
+			regmap_set_bits(priv->map, MPPHY_PXCNTXT1(1), 0x2010002);
+			regmap_set_bits(priv->map, MPPHY_PXCNTXT2(1), 0x2020201);
+			regmap_set_bits(priv->map, MPPHY_PXTXREQ(1), 0x8);
+			regmap_set_bits(priv->map, MPPHY_PXREFCLK(0), 0x30);
+			regmap_set_bits(priv->map, MPPHY_PXREFCLK(0), 0x4);
+			regmap_set_bits(priv->map, MPPHY_PXREFCLK(0), 0x1);
+			regmap_set_bits(priv->map, MPPHY_PXREFCLK(1), 0x30);
+			regmap_set_bits(priv->map, MPPHY_PXREFCLK(1), 0x4);
+			regmap_set_bits(priv->map, MPPHY_PXREFCLK(1), 0x1);
+			regmap_set_bits(priv->map, MPPHY_PXRXCNT(0), 0x202);
+			regmap_set_bits(priv->map, MPPHY_PXRXCNT(1), 0x202);
+			regmap_clear_bits(priv->map, MPPHY_PXRXCNT(0), 0x202);
+			regmap_clear_bits(priv->map, MPPHY_PXRXCNT(1), 0x202);
+		} else if (phy->id == 1) {
+			regmap_set_bits(priv->map, MPPHY_PXCNTXT1(2), 0x2010002);
+			regmap_set_bits(priv->map, MPPHY_PXCNTXT2(2), 0x2020202);
+			regmap_set_bits(priv->map, MPPHY_PXTXREQ(2), 0x8);
+			regmap_set_bits(priv->map, MPPHY_PXCNTXT1(3), 0x2010002);
+			regmap_set_bits(priv->map, MPPHY_PXCNTXT2(3), 0x2020202);
+			regmap_set_bits(priv->map, MPPHY_PXTXREQ(3), 0x8);
+			regmap_set_bits(priv->map, MPPHY_PXREFCLK(2), 0x30);
+			regmap_set_bits(priv->map, MPPHY_PXREFCLK(2), 0x4);
+			regmap_set_bits(priv->map, MPPHY_PXREFCLK(2), 0x1);
+			regmap_set_bits(priv->map, MPPHY_PXREFCLK(3), 0x30);
+			regmap_set_bits(priv->map, MPPHY_PXREFCLK(3), 0x4);
+			regmap_set_bits(priv->map, MPPHY_PXREFCLK(3), 0x1);
+
+			regmap_set_bits(priv->map, MPPHY_PXRXCNT(2), 0x202);
+			regmap_set_bits(priv->map, MPPHY_PXRXCNT(3), 0x202);
+			regmap_clear_bits(priv->map, MPPHY_PXRXCNT(2), 0x202);
+			regmap_clear_bits(priv->map, MPPHY_PXRXCNT(3), 0x202);
+		}
+	} else if (priv->num_lanes[phy->id] == 8) {
+		regmap_write(priv->map, MPPHY_PXCNTXT1(0), 0x2010002);
+		regmap_write(priv->map, MPPHY_PXCNTXT2(0), 0x2020201);
+		regmap_write(priv->map, MPPHY_PXTXREQ(0), 0x8);
+		regmap_write(priv->map, MPPHY_PXCNTXT1(1), 0x2010002);
+		regmap_write(priv->map, MPPHY_PXCNTXT2(1), 0x2020202);
+		regmap_write(priv->map, MPPHY_PXTXREQ(1), 0x8);
+		regmap_write(priv->map, MPPHY_PXCNTXT1(2), 0x2010002);
+		regmap_write(priv->map, MPPHY_PXCNTXT2(2), 0x2020202);
+		regmap_write(priv->map, MPPHY_PXTXREQ(2), 0x8);
+		regmap_write(priv->map, MPPHY_PXCNTXT1(3), 0x2010002);
+		regmap_write(priv->map, MPPHY_PXCNTXT2(3), 0x2020202);
+		regmap_write(priv->map, MPPHY_PXTXREQ(3), 0x8);
+
+		regmap_set_bits(priv->map, MPPHY_PXRXCNT(0), 0x202);
+		regmap_set_bits(priv->map, MPPHY_PXRXCNT(1), 0x202);
+		regmap_set_bits(priv->map, MPPHY_PXRXCNT(2), 0x202);
+		regmap_set_bits(priv->map, MPPHY_PXRXCNT(3), 0x202);
+
+		regmap_set_bits(priv->map, MPPHY_PXRXCNT(0), 0x202);
+		regmap_set_bits(priv->map, MPPHY_PXRXCNT(1), 0x202);
+		regmap_set_bits(priv->map, MPPHY_PXRXCNT(2), 0x202);
+		regmap_set_bits(priv->map, MPPHY_PXRXCNT(3), 0x202);
+
+		regmap_clear_bits(priv->map, MPPHY_PXRXCNT(0), 0x202);
+		regmap_clear_bits(priv->map, MPPHY_PXRXCNT(1), 0x202);
+		regmap_clear_bits(priv->map, MPPHY_PXRXCNT(2), 0x202);
+		regmap_clear_bits(priv->map, MPPHY_PXRXCNT(3), 0x202);
+	}
+
+	return 0;
+}
+
+static int mp_phy_init_usb(struct phy *phy)
+{
+	struct mp_phy_priv *priv = phy_get_drvdata(phy);
+	int ret;
+
+	ret = mp_phy_reg_wait(priv, MPPHY_PXSRAMCNT(phy->id), 0x20, 0x20);
+	if (ret)
+		return ret;
+
+	mp_phy_update_firmware(phy);
+	regmap_set_bits(priv->map, MPPHY_PXSRAMCNT(phy->id), SRAM_EXT_LD_DONE);
+	ret = mp_phy_reg_wait(priv, MPPHY_PXRXREQ1(phy->id), 0x2, 0);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int mp_phy_init(struct phy *phy)
+{
+	struct mp_phy_priv *priv = phy_get_drvdata(phy);
+	struct mp_phy_chan_priv *chan = &priv->chan[phy->id];
+	int ret;
+
+	/*
+	 * Note: Current source code support for Ethernet, PCIe
+	 * initialization is based on the bare metal code shared
+	 * by the board team.
+	 */
+	ret = pm_runtime_get_sync(priv->pd_list->pd_devs[phy->id]);
+	if (ret < 0) {
+		dev_err(priv->dev,
+			"Failed to power on domain for channel %d: %d\n",
+			phy->id, ret);
+		return ret;
+	}
+
+	/* Check if initialized with same protocol then skip */
+	if (chan->initialized)
+		return 0;
+
+	if (chan->protocol_id == PHY_MODE_PCIE)
+		ret = mp_phy_init_pcie4(phy);
+	else if (chan->protocol_id == PHY_MODE_ETHERNET)
+		ret = mp_phy_init_ethernet(phy);
+	else
+		ret = mp_phy_init_usb(phy);
+	if (ret)
+		return ret;
+
+	chan->initialized = true;
+	dev_dbg(priv->dev,
+		"Channel %d successfully initialized for protocol %d\n",
+		phy->id, chan->protocol_id);
+
+	return 0;
+}
+
+static int mp_phy_power_on(struct phy *phy)
+{
+	struct mp_phy_priv *priv = phy_get_drvdata(phy);
+	struct mp_phy_chan_priv *chan = &priv->chan[phy->id];
+	int ret;
+
+	if (!chan->initialized) {
+		dev_err(priv->dev, "Channel %d not initialized\n", phy->id);
+		return -EINVAL;
+	}
+
+	/*
+	 * The datasheet describes initialization procedure without full
+	 * information about the registers. Therefore, the source code is
+	 * based on the bare metal code shared by the board team.
+	 */
+	if (chan->protocol_id == PHY_MODE_PCIE) {
+		if (priv->num_lanes[phy->id] == 1 || priv->num_lanes[phy->id] == 2) {
+			ret = mp_phy_reg_wait(priv, MPPHY_PXSRAMCNT(phy->id), 0x20, 0x20);
+			if (ret)
+				return ret;
+			ret = mp_phy_reg_wait(priv, MPPHY_PXRXREQ1(phy->id), 0x2, 0);
+			if (ret)
+				return ret;
+		} else if (priv->num_lanes[phy->id] == 4) {
+			ret = mp_phy_reg_wait(priv, MPPHY_PXSRAMCNT(2 * phy->id), 0x20, 0x20);
+			if (ret)
+				return ret;
+			ret = mp_phy_reg_wait(priv, MPPHY_PXSRAMCNT((2 * phy->id) + 1), 0x20, 0x20);
+			if (ret)
+				return ret;
+			ret = mp_phy_reg_wait(priv, MPPHY_PXRXREQ1(2 * phy->id), 0x2, 0);
+			if (ret)
+				return ret;
+			ret = mp_phy_reg_wait(priv, MPPHY_PXRXREQ1((2 * phy->id) + 1), 0x2, 0);
+			if (ret)
+				return ret;
+		} else if (priv->num_lanes[phy->id] == 8) {
+			ret = mp_phy_reg_wait(priv, MPPHY_PXSRAMCNT(0), 0x20, 0x20);
+			if (ret)
+				return ret;
+			ret = mp_phy_reg_wait(priv, MPPHY_PXSRAMCNT(1), 0x20, 0x20);
+			if (ret)
+				return ret;
+			ret = mp_phy_reg_wait(priv, MPPHY_PXSRAMCNT(2), 0x20, 0x20);
+			if (ret)
+				return ret;
+			ret = mp_phy_reg_wait(priv, MPPHY_PXSRAMCNT(3), 0x20, 0x20);
+			if (ret)
+				return ret;
+			ret = mp_phy_reg_wait(priv, MPPHY_PXRXREQ1(0), 0x2, 0);
+			if (ret)
+				return ret;
+			ret = mp_phy_reg_wait(priv, MPPHY_PXRXREQ1(1), 0x2, 0);
+			if (ret)
+				return ret;
+			ret = mp_phy_reg_wait(priv, MPPHY_PXRXREQ1(2), 0x2, 0);
+			if (ret)
+				return ret;
+			ret = mp_phy_reg_wait(priv, MPPHY_PXRXREQ1(3), 0x2, 0);
+			if (ret)
+				return ret;
+		}
+	} else if (chan->protocol_id == PHY_MODE_ETHERNET) {
+		regmap_set_bits(priv->map, MPPHY_PXSRAMCNT(phy->id), SRAM_EXT_LD_DONE);
+		ret = mp_phy_reg_wait(priv, MPPHY_PXRXREQ1(phy->id), 0x2, 0);
+		if (ret)
+			return ret;
+	} else {	/* USB */
+		regmap_write(priv->map, TCA_OFFSET(phy->id) + TCA_VBUS_CTRL, 0x3e);
+	}
+
+	return 0;
+}
+
+static const struct phy_ops mp_phy_ops = {
+	.init		= mp_phy_init,
+	.exit		= mp_phy_exit,
+	.power_on	= mp_phy_power_on,
+	.owner		= THIS_MODULE,
+};
+
+static struct phy *mp_phy_xlate(struct device *dev,
+				const struct of_phandle_args *args)
+{
+	struct mp_phy_priv *priv = dev_get_drvdata(dev);
+	int id;
+
+	if (args->args_count > 1) {
+		dev_err(dev, "Invalid args_count: %d\n", args->args_count);
+		return ERR_PTR(-EINVAL);
+	}
+
+	if (args->args_count >= 1)
+		id = args->args[0];
+	else
+		id = 0;
+
+	return priv->chan[id].phy;
+}
+
+static int mp_phy_parse_dt(struct platform_device *pdev, struct mp_phy_priv *priv)
+{
+	struct device *dev = &pdev->dev;
+	bool need_fw = false;
+	u32 ext_ref_clk[4];
+	u32 out_ref_clk[4];
+	u32 phy_type[8];
+	int i, ret;
+
+	ret = device_property_read_u32_array(dev, "renesas,phy-type", phy_type, 8);
+	if (ret < 0)
+		return dev_err_probe(dev, -EINVAL, "Failed to read PHY configuration\n");
+
+	ret = device_property_read_u32_array(dev, "renesas,external-ref-clock",
+					     ext_ref_clk, 4);
+	if (ret < 0)
+		return dev_err_probe(dev, -EINVAL, "Failed to read PHY clock configuration\n");
+
+	ret = device_property_read_u32_array(dev, "renesas,output-repeat-ref-clock",
+					     out_ref_clk, 4);
+	if (ret < 0)
+		return dev_err_probe(dev, -EINVAL, "Failed to read PHY clock out configuration\n");
+
+	ret = device_property_read_u32_array(dev, "renesas,num-lanes", priv->num_lanes, 4);
+	if (ret < 0)
+		return dev_err_probe(dev, -EINVAL, "Failed to read PHY lane configuration\n");
+
+	/* Port 0 can be either PCIe4 channel 0 lanes 0,1 or Ethernet 0-1 */
+	if (!((phy_type[0] == PHY_TYPE_PCIE && phy_type[1] == 0) ||
+	      (phy_type[0] == PHY_TYPE_XPCS && phy_type[1] == 0))) {
+		return dev_err_probe(dev, -EINVAL, "Incorrect PHY port 0 configuration\n");
+	}
+
+	/* Port 1 can be either PCIe4 channel 0 lanes 2,3 or Ethernet 2-3 */
+	if (!((phy_type[2] == PHY_TYPE_PCIE && phy_type[3] == 0) ||
+	      (phy_type[2] == PHY_TYPE_XPCS && phy_type[3] == 1))) {
+		return dev_err_probe(dev, -EINVAL, "Incorrect PHY port 1 configuration\n");
+	}
+
+	/*
+	 * Port 2 can be either PCIe4 channel 0 lanes 4,5 or Ethernet 4-5 or
+	 * PCIe4 channel 1 lanes 0,1 or USB3.2 channel 0
+	 */
+	if (!((phy_type[4] == PHY_TYPE_PCIE && phy_type[5] == 0) ||
+	      (phy_type[4] == PHY_TYPE_PCIE && phy_type[5] == 1) ||
+	      (phy_type[4] == PHY_TYPE_XPCS && phy_type[5] == 2) ||
+	      (phy_type[4] == PHY_TYPE_USB3 && phy_type[5] == 0))) {
+		return dev_err_probe(dev, -EINVAL, "Incorrect PHY port 2 configuration\n");
+	}
+
+	/*
+	 * Port 3 can be either PCIe4 channel 0 lanes 6,7 or Ethernet 6-7 or
+	 * PCIe4 channel 1 lanes 2,3 or USB3.2 channel 1
+	 */
+	if (!((phy_type[6] == PHY_TYPE_PCIE && phy_type[7] == 0) ||
+	      (phy_type[6] == PHY_TYPE_PCIE && phy_type[7] == 1) ||
+	      (phy_type[6] == PHY_TYPE_XPCS && phy_type[7] == 3) ||
+	      (phy_type[6] == PHY_TYPE_USB3 && phy_type[7] == 1))) {
+		return dev_err_probe(dev, -EINVAL, "Incorrect PHY port 3 configuration\n");
+	}
+
+	priv->cmncnt[0] = 0;
+	priv->cmncnt[1] = 0x33330000;	/* All res_{ack,req}_in_sel are 1 */
+
+	for (i = 0; i < MPPHY_NUM_CHANNELS; i++) {
+		priv->chan[i].initialized = false;
+		priv->chan[i].protocol_id = PHY_MODE_INVALID;
+
+		if (phy_type[2 * i] == PHY_TYPE_PCIE) {
+			priv->chan[i].protocol_id = PHY_MODE_PCIE;
+			priv->sramcnt[i] = 0xf;
+			if (phy_type[(2 * i) + 1] == 0)	/* Channel 0 */
+				priv->cmncnt[0] |= 0x0 << (i * 8);
+			else				/* Channel 1 */
+				priv->cmncnt[0] |= 0x2 << (i * 8);
+		} else if (phy_type[2 * i] == PHY_TYPE_XPCS) {
+			priv->chan[i].protocol_id = PHY_MODE_ETHERNET;
+			priv->sramcnt[i] = 0x0;
+			priv->write_cntxt1 |= BIT(i);
+			priv->cmncnt[0] |= 0x1 << (i * 8);
+			need_fw = true;
+		} else if (phy_type[2 * i] == PHY_TYPE_USB3) {
+			priv->chan[i].protocol_id = PHY_MODE_USB_OTG;
+			priv->sramcnt[i] = 0x9;
+			priv->cmncnt[0] |= 0x3 << (i * 8);
+			need_fw = true;
+		} else {
+			/* Cannot be reached. */
+			return -EINVAL;
+		}
+
+		if (ext_ref_clk[i])
+			priv->cmncnt[1] |= BIT(i * 4);
+
+		if (out_ref_clk[i])
+			priv->cmncnt[1] |= BIT((i * 4) + 1);
+	}
+
+	if (!need_fw)
+		return 0;
+
+	return request_firmware(&priv->fw, MPPHY_FW_NAME, dev);
+}
+
+static int mp_phy_probe(struct platform_device *pdev)
+{
+	static const char *const pd_names[] = { "mpp0", "mpp1", "mpp2", "mpp3" };
+	const struct dev_pm_domain_attach_data pd_attach_data = {
+		.pd_names = pd_names,
+		.num_pd_names = ARRAY_SIZE(pd_names),
+		.pd_flags = 0,
+	};
+	struct device *dev = &pdev->dev;
+	struct phy_provider *provider;
+	struct mp_phy_priv *priv;
+	int i, ret;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->dev = dev;
+
+	ret = mp_phy_parse_dt(pdev, priv);
+	if (ret)
+		return ret;
+
+	priv->base = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(priv->base))
+		return dev_err_probe(dev, PTR_ERR(priv->base), "Failed to map PHY registers\n");
+
+	priv->map = devm_regmap_init_mmio(dev, priv->base, &mp_phy_regmap_config);
+	if (IS_ERR(priv->map))
+		return PTR_ERR(priv->map);
+
+	priv->num_clks = devm_clk_bulk_get_all(dev, &priv->clks);
+	if (priv->num_clks < 0)
+		return dev_err_probe(dev, priv->num_clks, "Failed to get PHY clocks\n");
+	if (priv->num_clks != 5)
+		return dev_err_probe(dev, -ENODEV, "Failed to get all PHY clocks\n");
+
+	/*
+	 * The reset ID order here does matters, reset_control_bulk_assert()
+	 * asserts these resets in this order, with mpphy02 reset being
+	 * asserted first, reset_control_bulk_deassert() deasserts these
+	 * resets in reverse order, with mpphy02 being reset being
+	 * deasserted last. This is the behavior the hardware expects.
+	 */
+	priv->resets[0].id = "mpphy02";
+	priv->resets[1].id = "mpphy01";
+	priv->resets[2].id = "mpphy11";
+	priv->resets[3].id = "mpphy21";
+	priv->resets[4].id = "mpphy31";
+	ret = devm_reset_control_bulk_get_exclusive(&pdev->dev, ARRAY_SIZE(priv->resets),
+						    priv->resets);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to get PHY resets\n");
+
+	platform_set_drvdata(pdev, priv);
+
+	ret = dev_pm_domain_attach_list(dev, &pd_attach_data, &priv->pd_list);
+	if (ret < 0)
+		return dev_err_probe(dev, ret, "Failed to attach power domains\n");
+
+	ret = devm_pm_runtime_enable(dev);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to enable PHY runtime PM\n");
+
+	provider = devm_of_phy_provider_register(dev, mp_phy_xlate);
+	if (IS_ERR(provider))
+		return dev_err_probe(dev, PTR_ERR(provider), "Failed to register PHY provider\n");
+
+	for (i = 0; i < MPPHY_NUM_CHANNELS; i++) {
+		priv->chan[i].phy = devm_phy_create(dev, NULL, &mp_phy_ops);
+		if (IS_ERR(priv->chan[i].phy)) {
+			return dev_err_probe(dev, PTR_ERR(priv->chan[i].phy),
+					     "Failed to create PHY %d\n", i);
+		}
+
+		priv->chan[i].phy->id = i;
+		phy_set_drvdata(priv->chan[i].phy, priv);
+	}
+
+	return pm_runtime_resume_and_get(dev);
+}
+
+static void mp_phy_remove(struct platform_device *pdev)
+{
+	struct mp_phy_priv *priv = dev_get_drvdata(&pdev->dev);
+	struct device *dev = &pdev->dev;
+
+	pm_runtime_put(dev);
+
+	dev_pm_domain_detach_list(priv->pd_list);
+
+	pm_runtime_disable(&pdev->dev);
+
+	if (priv->fw)
+		release_firmware(priv->fw);
+
+	platform_set_drvdata(pdev, NULL);
+}
+
+static int mp_phy_suspend(struct device *dev)
+{
+	struct mp_phy_priv *priv = dev_get_drvdata(dev);
+	int i;
+
+	for (i = 0; i < MPPHY_NUM_CHANNELS; i++)
+		priv->chan[i].initialized = false;
+
+	clk_bulk_disable_unprepare(priv->num_clks, priv->clks);
+
+	dev_info(dev, "Multi-Protocol PHY suspended\n");
+
+	return 0;
+}
+
+static int mp_phy_resume(struct device *dev)
+{
+	struct mp_phy_priv *priv = dev_get_drvdata(dev);
+	int i, ret;
+
+	ret = reset_control_bulk_assert(ARRAY_SIZE(priv->resets), priv->resets);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to assert PHY resets\n");
+
+	ret = reset_control_bulk_deassert(ARRAY_SIZE(priv->resets), priv->resets);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to deassert PHY resets\n");
+
+	ret = clk_bulk_prepare_enable(priv->num_clks, priv->clks);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to enable PHY clocks\n");
+
+	/* Reload configuration */
+	regmap_set_bits(priv->map, MPPHY_CMNCNT1, priv->cmncnt[0]);
+	regmap_write(priv->map, MPPHY_CMNCNT2, priv->cmncnt[1]);
+
+	for (i = 0; i < MPPHY_NUM_CHANNELS; i++) {
+		regmap_set_bits(priv->map, MPPHY_PXTEST(i), MPPHY_PXTEST_BIT);
+		regmap_write(priv->map, MPPHY_PXSRAMCNT(i), priv->sramcnt[i]);
+
+		if (priv->write_cntxt1 & BIT(i)) {
+			regmap_write(priv->map, MPPHY_CHAN_BASE(i) + 0x10c, 0xff0ff);
+			regmap_write(priv->map, MPPHY_PXCNTXT1(i), 0x180023);
+		}
+
+		regmap_clear_bits(priv->map, MPPHY_PXTEST(i), MPPHY_PXTEST_BIT);
+	}
+
+	regmap_clear_bits(priv->map, MPPHY_PCS0REG1, MPPHY_PCS0REG1_VAL);
+	regmap_clear_bits(priv->map, MPPHY_PCS0REG5, 0xff000000);
+
+	return 0;
+}
+
+static DEFINE_RUNTIME_DEV_PM_OPS(mp_phy_pm_ops, mp_phy_suspend, mp_phy_resume, NULL);
+
+static const struct of_device_id mp_phy_of_match[] = {
+	{ .compatible = "renesas,rcar-gen5-mpphy" },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, mp_phy_of_match);
+
+static struct platform_driver mp_phy_driver = {
+	.probe	= mp_phy_probe,
+	.remove	= mp_phy_remove,
+	.driver	= {
+		.name		= "renesas-mpphy",
+		.of_match_table	= mp_phy_of_match,
+		.pm		= pm_ptr(&mp_phy_pm_ops),
+		.probe_type	= PROBE_PREFER_ASYNCHRONOUS,
+	},
+};
+
+module_platform_driver(mp_phy_driver);
+
+MODULE_AUTHOR("Thanh Quan");
+MODULE_DESCRIPTION("Renesas Multi-Protocol PHY driver");
+MODULE_LICENSE("GPL");
+MODULE_FIRMWARE(MPPHY_FW_NAME);
-- 
2.53.0


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

* Re: [PATCH 1/2] dt-bindings: phy: Document Renesas Multi-Protocol PHY driver for R-Car X5H
  2026-08-28 19:21 [PATCH 1/2] dt-bindings: phy: Document Renesas Multi-Protocol PHY driver for R-Car X5H Marek Vasut
  2026-08-28 19:21 ` [PATCH 2/2] phy: renesas: Add " Marek Vasut
@ 2026-08-28 19:28 ` sashiko-bot
  2026-08-31  8:13 ` Geert Uytterhoeven
  2 siblings, 0 replies; 6+ messages in thread
From: sashiko-bot @ 2026-08-28 19:28 UTC (permalink / raw)
  To: Marek Vasut
  Cc: robh, conor+dt, wsa+renesas, vkoul, neil.armstrong, olteanv,
	devicetree, linux-phy

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] The description for `renesas,num-lanes` incorrectly states it applies to "each PHY input", which contradicts the hardware topology and the schema constraints.
--

commit ae58224156f0a1174aafe97692e6bf7123fdb20b
Author: Marek Vasut <marek.vasut+renesas@mailbox.org>

dt-bindings: phy: Document Renesas Multi-Protocol PHY driver for R-Car X5H

This commit introduces device tree binding documentation for the Renesas
Multi-Protocol PHY found in the R-Car X5H SoC. It describes a multiplexing PHY
block that routes five inputs to four separate IO blocks.

> diff --git a/Documentation/devicetree/bindings/phy/renesas,rcar-gen5-mpphy.yaml b/Documentation/devicetree/bindings/phy/renesas,rcar-gen5-mpphy.yaml
> new file mode 100644
> index 0000000000000..2da8be2d3de82
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/phy/renesas,rcar-gen5-mpphy.yaml

[ ... ]

> +  '#phy-cells':
> +    const: 1
> +
> +  renesas,num-lanes:
> +    description: PCIe lane count for each PHY input.

[Severity: Low]
Does this description accurately reflect the schema and hardware topology?

Looking at properties.renesas,num-lanes.description, the text states the
property applies to each PHY input, but the commit message notes there are
five inputs and four outputs. This appears to contradict the array
constraints immediately below it which enforce a maximum of 4 items. 

Should this description perhaps say "each PHY output" instead, or should the
array limits be updated to allow 5 items for the inputs?

> +    $ref: /schemas/types.yaml#/definitions/uint32-array
> +    minItems: 4
> +    maxItems: 4
> +
> +  renesas,phy-type:

[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260828192223.402422-1-marek.vasut+renesas@mailbox.org?part=1

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

* Re: [PATCH 2/2] phy: renesas: Add Renesas Multi-Protocol PHY driver for R-Car X5H
  2026-08-28 19:21 ` [PATCH 2/2] phy: renesas: Add " Marek Vasut
@ 2026-08-28 19:35   ` sashiko-bot
  2026-09-03 10:15   ` Manivannan Sadhasivam
  1 sibling, 0 replies; 6+ messages in thread
From: sashiko-bot @ 2026-08-28 19:35 UTC (permalink / raw)
  To: Marek Vasut
  Cc: devicetree, olteanv, neil.armstrong, linux-phy, conor+dt, vkoul,
	wsa+renesas, robh

Thank you for your contribution! Sashiko AI review found 11 potential issue(s) to consider:
- [High] Regmap readable and writable ranges specify inverted address bounds and omit valid registers, preventing the driver from initializing hardware registers.
- [High] Runtime PM usage counter leaks when `mp_phy_init()` encounters an error or returns early.
- [High] Missing firmware size validation leads to out-of-bounds memory accesses and memory-mapped I/O corruption.
- [High] Inconsistent use of `phy->id` in PCIe initialization leads to incorrect port configuration and out-of-bounds register accesses.
- [High] `mp_phy_suspend()` clears `chan->initialized` without dropping PM domains, permanently bricking PHY channels after a suspend/resume cycle.
- [High] Unconditional register access in `mp_phy_resume()` for all channels without checking if their respective power domains are turned on.
- [Medium] Memory and resource leaks in `mp_phy_probe()` error paths: firmware buffer and attached power domains are not released on subsequent initialization failures.
- [Medium] Asynchronous `pm_runtime_put()` is immediately followed by `pm_runtime_disable()` during device removal, violating PM subsystem guidelines.
- [Medium] Missing bounds check on device tree index in `mp_phy_xlate()` leads to an out-of-bounds array access.
- [Medium] `reset_control_bulk_deassert()` iterates sequentially, violating the hardware requirement to deassert `mpphy02` last.
- [Medium] PHY provider is exposed to consumers before PHY instances are created and hardware is powered on.
--

commit 54820191afa1ae9e0aa531e1706a7cd7acbab1ca
Author: Thanh Quan <thanh.quan.xn@renesas.com>
Subject: phy: renesas: Add Renesas Multi-Protocol PHY driver for R-Car X5H

This commit introduces a new Renesas Multi-Protocol PHY driver for the R-Car
X5H SoC. It supports multiplexing five input IPs (PCIe4, USB 3.2, Ethernet)
to four separate IO blocks based on the device tree configuration.

> diff --git a/drivers/phy/renesas/phy-rcar-gen5-mpphy.c b/drivers/phy/renesas/phy-rcar-gen5-mpphy.c
> new file mode 100644
> --- /dev/null
> +++ b/drivers/phy/renesas/phy-rcar-gen5-mpphy.c
[ ... ]
> +#define MPPHY_PX_RD_RANGE(n)						\
> +	regmap_reg_range(MPPHY_PXCNTXT1(n), MPPHY_CHAN_BASE(n)),	\
> +	regmap_reg_range(MPPHY_PXREFCLK(n), MPPHY_PXREFCLK(n)),		\
> +	regmap_reg_range(MPPHY_PXRXREQ1(n), MPPHY_PXRXREQ1(n)),		\
> +	regmap_reg_range(MPPHY_PXRXCNT(n), MPPHY_PXRXCNT(n)),		\
> +	regmap_reg_range(MPPHY_PXSRAMCNT(n), MPPHY_PXTXREQ(n))

[Severity: High]
Does this inverted range cause regmap to reject accesses?
MPPHY_PXCNTXT1(n) is defined as (MPPHY_CHAN_BASE(n) + 0x4).
Since range_min > range_max, regmap_reg_range will set a range where the
minimum is greater than the maximum, preventing the driver from reading
these hardware registers during initialization.

[ ... ]
> +static void mp_phy_update_firmware(struct phy *phy)
> +{
> +	struct mp_phy_priv *priv = phy_get_drvdata(phy);
> +	int i;
> +
> +	for (i = 0; i < priv->fw->size; i += 2) {
> +		writew(priv->fw->data[i] | (priv->fw->data[i + 1] << 8),
> +		       priv->base + MPPHY_FW_BASE + (MPPHY_FW_CH_OFFSET * phy->id) + i);
> +	}
> +}

[Severity: High]
Does this code risk an out-of-bounds read or memory corruption if the
firmware size is odd or larger than expected?
Since mp_phy_parse_dt() doesn't validate priv->fw->size, a large firmware
file could write beyond the allocated MPPHY_FW_CH_OFFSET (0x20000) or outside
the I/O region completely. If the size is odd, reading priv->fw->data[i + 1]
will result in an out-of-bounds access.

[ ... ]
> +static int mp_phy_init_pcie4(struct phy *phy)
> +{
> +	struct mp_phy_priv *priv = phy_get_drvdata(phy);
> +
> +	if (priv->num_lanes[phy->id] == 1 || priv->num_lanes[phy->id] == 2) {
> +		if (phy->id == 0) {
> +			regmap_set_bits(priv->map, MPPHY_PXCNTXT1(0), 0x2010002);
> +			regmap_set_bits(priv->map, MPPHY_PXCNTXT2(0), 0x2020201);
> +			regmap_set_bits(priv->map, MPPHY_PXTXREQ(0), 0x80004);
> +			regmap_set_bits(priv->map, MPPHY_PXREFCLK(0), 0x30);
> +			regmap_set_bits(priv->map, MPPHY_PXREFCLK(0), 0x4);
> +			regmap_set_bits(priv->map, MPPHY_PXREFCLK(0), 0x1);
> +			regmap_set_bits(priv->map, MPPHY_PXRXCNT(0), 0x202);
> +			regmap_clear_bits(priv->map, MPPHY_PXRXCNT(0), 0x202);
> +		} else if (phy->id == 1) {
> +			regmap_set_bits(priv->map, MPPHY_PXCNTXT1(2), 0x2010002);

[Severity: High]
Is phy->id used consistently here?
The code seems to treat phy->id as a controller index rather than a port index,
ignoring cases where phy->id is 2 or 3. This could lead to incorrect port
configuration if phy->id maps directly to the port being initialized.

[ ... ]
> +static int mp_phy_init(struct phy *phy)
> +{
> +	struct mp_phy_priv *priv = phy_get_drvdata(phy);
> +	struct mp_phy_chan_priv *chan = &priv->chan[phy->id];
> +	int ret;
> +
> +	/*
> +	 * Note: Current source code support for Ethernet, PCIe
> +	 * initialization is based on the bare metal code shared
> +	 * by the board team.
> +	 */
> +	ret = pm_runtime_get_sync(priv->pd_list->pd_devs[phy->id]);
> +	if (ret < 0) {
> +		dev_err(priv->dev,
> +			"Failed to power on domain for channel %d: %d\n",
> +			phy->id, ret);
> +		return ret;
> +	}
> +
> +	/* Check if initialized with same protocol then skip */
> +	if (chan->initialized)
> +		return 0;

[Severity: High]
Does this code leak the power management usage counter?
If chan->initialized is true, the function returns directly without dropping
the reference acquired by pm_runtime_get_sync().

> +
> +	if (chan->protocol_id == PHY_MODE_PCIE)
> +		ret = mp_phy_init_pcie4(phy);
> +	else if (chan->protocol_id == PHY_MODE_ETHERNET)
> +		ret = mp_phy_init_ethernet(phy);
> +	else
> +		ret = mp_phy_init_usb(phy);
> +	if (ret)
> +		return ret;

[Severity: High]
Could this error path also leak the PM usage counter?
If protocol initialization fails, the function returns without decrementing
the usage counter, preventing the power domain from ever suspending.

[ ... ]
> +static int mp_phy_power_on(struct phy *phy)
> +{
> +	struct mp_phy_priv *priv = phy_get_drvdata(phy);
> +	struct mp_phy_chan_priv *chan = &priv->chan[phy->id];
> +	int ret;
> +
> +	if (!chan->initialized) {
> +		dev_err(priv->dev, "Channel %d not initialized\n", phy->id);
> +		return -EINVAL;
> +	}

[Severity: High]
Will this unconditionally fail after a suspend/resume cycle?
Since mp_phy_suspend() clears chan->initialized for all channels without
dropping their associated PM domain references, subsequent calls to
phy_power_on() after resume will hit this condition and fail.

[ ... ]
> +		} else if (priv->num_lanes[phy->id] == 4) {
> +			ret = mp_phy_reg_wait(priv, MPPHY_PXSRAMCNT(2 * phy->id), 0x20, 0x20);
> +			if (ret)
> +				return ret;
> +			ret = mp_phy_reg_wait(priv, MPPHY_PXSRAMCNT((2 * phy->id) + 1), 0x20, 0x20);

[Severity: High]
Can this result in an out-of-bounds regmap access?
If phy->id is 2, (2 * phy->id) + 1 evaluates to 5, which exceeds the
maximum channel index (MPPHY_NUM_CHANNELS is 4).

[ ... ]
> +static struct phy *mp_phy_xlate(struct device *dev,
> +				const struct of_phandle_args *args)
> +{
> +	struct mp_phy_priv *priv = dev_get_drvdata(dev);
> +	int id;
> +
> +	if (args->args_count > 1) {
> +		dev_err(dev, "Invalid args_count: %d\n", args->args_count);
> +		return ERR_PTR(-EINVAL);
> +	}
> +
> +	if (args->args_count >= 1)
> +		id = args->args[0];
> +	else
> +		id = 0;
> +
> +	return priv->chan[id].phy;
> +}

[Severity: Medium]
Does this code overflow the priv->chan[] array?
If a consumer passes an id >= MPPHY_NUM_CHANNELS via the device tree,
this will result in an out-of-bounds array access since there is no
bounds checking on the raw argument before indexing the array.

[ ... ]
> +static int mp_phy_probe(struct platform_device *pdev)
> +{
[ ... ]
> +	ret = mp_phy_parse_dt(pdev, priv);
> +	if (ret)
> +		return ret;
> +
> +	priv->base = devm_platform_ioremap_resource(pdev, 0);
> +	if (IS_ERR(priv->base))
> +		return dev_err_probe(dev, PTR_ERR(priv->base), "Failed to map PHY registers\n");

[Severity: Medium]
Does this error path leak the allocated firmware?
mp_phy_parse_dt() can successfully allocate priv->fw via request_firmware().
If devm_platform_ioremap_resource() fails, the function returns directly
without calling release_firmware().

[ ... ]
> +	/*
> +	 * The reset ID order here does matters, reset_control_bulk_assert()
> +	 * asserts these resets in this order, with mpphy02 reset being
> +	 * asserted first, reset_control_bulk_deassert() deasserts these
> +	 * resets in reverse order, with mpphy02 being reset being
> +	 * deasserted last. This is the behavior the hardware expects.
> +	 */
> +	priv->resets[0].id = "mpphy02";
> +	priv->resets[1].id = "mpphy01";
> +	priv->resets[2].id = "mpphy11";
> +	priv->resets[3].id = "mpphy21";
> +	priv->resets[4].id = "mpphy31";
> +	ret = devm_reset_control_bulk_get_exclusive(&pdev->dev, ARRAY_SIZE(priv->resets),
> +						    priv->resets);

[Severity: Medium]
Will reset_control_bulk_deassert() actually deassert these resets in reverse
order?
The reset_control_bulk_deassert() API processes the array sequentially unless
it is unwinding from a failure. This would mean mpphy02 is deasserted first
rather than last, violating the hardware expectation described in the comment.

[ ... ]
> +	ret = devm_pm_runtime_enable(dev);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "Failed to enable PHY runtime PM\n");

[Severity: Medium]
Could this error path leak priv->fw and the power domains?
If devm_pm_runtime_enable() fails, the function returns without calling
release_firmware() or dev_pm_domain_detach_list().

> +
> +	provider = devm_of_phy_provider_register(dev, mp_phy_xlate);
> +	if (IS_ERR(provider))
> +		return dev_err_probe(dev, PTR_ERR(provider), "Failed to register PHY provider\n");
> +
> +	for (i = 0; i < MPPHY_NUM_CHANNELS; i++) {
> +		priv->chan[i].phy = devm_phy_create(dev, NULL, &mp_phy_ops);
> +		if (IS_ERR(priv->chan[i].phy)) {
> +			return dev_err_probe(dev, PTR_ERR(priv->chan[i].phy),
> +					     "Failed to create PHY %d\n", i);
> +		}
> +
> +		priv->chan[i].phy->id = i;
> +		phy_set_drvdata(priv->chan[i].phy, priv);
> +	}

[Severity: Medium]
Does registering the provider here expose the PHY to consumers prematurely?
The provider is registered before the PHY objects (priv->chan[i].phy) are
created. If a consumer probes immediately, mp_phy_xlate() would return a
NULL pointer.

[ ... ]
> +static void mp_phy_remove(struct platform_device *pdev)
> +{
> +	struct mp_phy_priv *priv = dev_get_drvdata(&pdev->dev);
> +	struct device *dev = &pdev->dev;
> +
> +	pm_runtime_put(dev);
> +
> +	dev_pm_domain_detach_list(priv->pd_list);
> +
> +	pm_runtime_disable(&pdev->dev);

[Severity: Medium]
Can this result in an aborted power state transition?
According to PM subsystem rules, calling pm_runtime_disable() immediately
after an asynchronous pm_runtime_put() can cancel pending suspend work.
Should pm_runtime_put_sync() be used here instead?

[ ... ]
> +static int mp_phy_suspend(struct device *dev)
> +{
> +	struct mp_phy_priv *priv = dev_get_drvdata(dev);
> +	int i;
> +
> +	for (i = 0; i < MPPHY_NUM_CHANNELS; i++)
> +		priv->chan[i].initialized = false;
> +
> +	clk_bulk_disable_unprepare(priv->num_clks, priv->clks);
> +
> +	dev_info(dev, "Multi-Protocol PHY suspended\n");
> +
> +	return 0;
> +}

[Severity: High]
Will clearing chan->initialized here break the PHY on resume?
This flag is cleared without decrementing the associated PM domain usage
counter. On resume, it is never restored to true, causing subsequent calls
to mp_phy_power_on() to fail unconditionally.

[ ... ]
> +static int mp_phy_resume(struct device *dev)
> +{
[ ... ]
> +	for (i = 0; i < MPPHY_NUM_CHANNELS; i++) {
> +		regmap_set_bits(priv->map, MPPHY_PXTEST(i), MPPHY_PXTEST_BIT);
> +		regmap_write(priv->map, MPPHY_PXSRAMCNT(i), priv->sramcnt[i]);
> +
> +		if (priv->write_cntxt1 & BIT(i)) {
> +			regmap_write(priv->map, MPPHY_CHAN_BASE(i) + 0x10c, 0xff0ff);

[Severity: High]
Are we guaranteed that the power domains for all 4 channels are turned on here?
If a channel is unused and its power domain is off, accessing its registers
blindly in this loop could lead to a bus fault and system crash.

[Severity: High]
Also, will regmap reject this write to 0x10c?
This register offset does not appear to be declared in the
mp_phy_writeable_range table, meaning the regmap core will return an -EIO
error.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260828192223.402422-1-marek.vasut+renesas@mailbox.org?part=2

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

* Re: [PATCH 1/2] dt-bindings: phy: Document Renesas Multi-Protocol PHY driver for R-Car X5H
  2026-08-28 19:21 [PATCH 1/2] dt-bindings: phy: Document Renesas Multi-Protocol PHY driver for R-Car X5H Marek Vasut
  2026-08-28 19:21 ` [PATCH 2/2] phy: renesas: Add " Marek Vasut
  2026-08-28 19:28 ` [PATCH 1/2] dt-bindings: phy: Document " sashiko-bot
@ 2026-08-31  8:13 ` Geert Uytterhoeven
  2 siblings, 0 replies; 6+ messages in thread
From: Geert Uytterhoeven @ 2026-08-31  8:13 UTC (permalink / raw)
  To: Marek Vasut
  Cc: linux-phy, Arnd Bergmann, Biju Das, Conor Dooley, Hai Pham,
	Krzysztof Kozlowski, Magnus Damm, Manivannan Sadhasivam,
	Neil Armstrong, Phong Hoang, Rob Herring, Tam Nguyen, Thanh Quan,
	Tommaso Merciai, Vinh Nguyen, Vinod Koul, Yoshihiro Shimoda,
	devicetree, linux-kernel, linux-renesas-soc

Hi Marek,

On Fri, 28 Aug 2026 at 21:22, Marek Vasut
<marek.vasut+renesas@mailbox.org> wrote:
> Document Renesas Multi-Protocol PHY present in R-Car X5H (R8A78000) SoC.
> This is a multiplexing PHY block with inputs from five IPs, and outputs
> to four separate IO blocks. The five inputs are two PCIe4, two USB 3.2,
> and ethernet PCS, each connected into the PHY with up to 4 times 2 lanes.
> The inputs can be multiplexed to the four IO blocks as needed.
>
> Signed-off-by: Marek Vasut <marek.vasut+renesas@mailbox.org>

Thanks for your patch!

> --- /dev/null
> +++ b/Documentation/devicetree/bindings/phy/renesas,rcar-gen5-mpphy.yaml
> @@ -0,0 +1,125 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/phy/renesas,rcar-gen5-mpphy.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Renesas R-Car Generation 5 Multi-Protocol PHY
> +
> +maintainers:
> +  - Marek Vasut <marek.vasut+renesas@mailbox.org>
> +  - Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com>
> +
> +properties:
> +  compatible:
> +    items:
> +      - const: renesas,r8a78000-mpphy   # R-Car X5H MPPHY
> +      - const: renesas,rcar-gen5-mpphy  # R-Car Gen5 MPPHY
> +
> +  reg:
> +    maxItems: 1
> +
> +  clocks:
> +    minItems: 5
> +    maxItems: 5
> +
> +  clock-names:
> +    items:
> +      - const: mpphy01
> +      - const: mpphy11
> +      - const: mpphy21
> +      - const: mpphy31
> +      - const: mpphy02

These look like provider-centric instead of consumer-centric names.
Please add descriptions, so it is more clear what they are, especially
for the last entry (which should perhaps be the second entry?).

> +
> +  power-domains:
> +    minItems: 4
> +    maxItems: 4

Why four? (yeah, SCMI...)

> +
> +  power-domain-names:
> +    items:
> +      - const: mpp0
> +      - const: mpp1
> +      - const: mpp2
> +      - const: mpp3

Likewise.

> +
> +  resets:
> +    minItems: 5
> +    maxItems: 5
> +
> +  reset-names:
> +    items:
> +      - const: mpphy01
> +      - const: mpphy11
> +      - const: mpphy21
> +      - const: mpphy31
> +      - const: mpphy02

These look like provider-centric instead of consumer-centric names.

Gr{oetje,eeting}s,

                        Geert

-- 
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org

In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
                                -- Linus Torvalds

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

* Re: [PATCH 2/2] phy: renesas: Add Renesas Multi-Protocol PHY driver for R-Car X5H
  2026-08-28 19:21 ` [PATCH 2/2] phy: renesas: Add " Marek Vasut
  2026-08-28 19:35   ` sashiko-bot
@ 2026-09-03 10:15   ` Manivannan Sadhasivam
  1 sibling, 0 replies; 6+ messages in thread
From: Manivannan Sadhasivam @ 2026-09-03 10:15 UTC (permalink / raw)
  To: Marek Vasut
  Cc: linux-phy, Thanh Quan, Phong Hoang, Hai Pham, Yoshihiro Shimoda,
	Arnd Bergmann, Biju Das, Conor Dooley, Geert Uytterhoeven,
	Krzysztof Kozlowski, Magnus Damm, Neil Armstrong, Rob Herring,
	Tam Nguyen, Tommaso Merciai, Vinh Nguyen, Vinod Koul, devicetree,
	linux-kernel, linux-renesas-soc

On Fri, Aug 28, 2026 at 09:21:40PM +0200, Marek Vasut wrote:
> From: Thanh Quan <thanh.quan.xn@renesas.com>
> 
> Add Renesas Multi-Protocol PHY driver for R-Car X5H (R8A78000) SoC.
> 
> This is a multiplexing PHY block with inputs from five IPs, and outputs
> to four separate IO blocks. The five inputs are two PCIe4, two USB 3.2,
> and ethernet PCS, each connected into the PHY with up to 4 times 2 lanes.
> The inputs can be multiplexed to the four IO blocks as needed.
> 
> The datasheet describes initialization procedure without many information
> about registers' name/bits. So, this driver does contain a bit of magic
> to initialize the hardware.
> 
> Signed-off-by: Thanh Quan <thanh.quan.xn@renesas.com>
> Co-developed-by: Phong Hoang <phong.hoang.wz@renesas.com>
> Signed-off-by: Phong Hoang <phong.hoang.wz@renesas.com>
> Co-developed-by: Hai Pham <hai.pham.ud@renesas.com>
> Signed-off-by: Hai Pham <hai.pham.ud@renesas.com>
> Co-developed-by: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com>
> Signed-off-by: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com>
> Co-developed-by: Marek Vasut <marek.vasut+renesas@mailbox.org>
> Signed-off-by: Marek Vasut <marek.vasut+renesas@mailbox.org>
> ---
> Cc: Arnd Bergmann <arnd@arndb.de>
> Cc: Biju Das <biju.das.jz@bp.renesas.com>
> Cc: Conor Dooley <conor+dt@kernel.org>
> Cc: Geert Uytterhoeven <geert+renesas@glider.be>
> Cc: Hai Pham <hai.pham.ud@renesas.com>
> Cc: Krzysztof Kozlowski <krzk+dt@kernel.org>
> Cc: Magnus Damm <magnus.damm@gmail.com>
> Cc: Manivannan Sadhasivam <mani@kernel.org>
> Cc: Neil Armstrong <neil.armstrong@linaro.org>
> Cc: Phong Hoang <phong.hoang.wz@renesas.com>
> Cc: Rob Herring <robh@kernel.org>
> Cc: Tam Nguyen <tam.nguyen.xa@renesas.com>
> Cc: Thanh Quan <thanh.quan.xn@renesas.com>
> Cc: Tommaso Merciai <tommaso.merciai.xr@bp.renesas.com>
> Cc: Vinh Nguyen <vinh.nguyen.xz@renesas.com>
> Cc: Vinod Koul <vkoul@kernel.org>
> Cc: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com>
> Cc: devicetree@vger.kernel.org
> Cc: linux-kernel@vger.kernel.org
> Cc: linux-phy@lists.infradead.org
> Cc: linux-renesas-soc@vger.kernel.org
> ---
>  drivers/phy/renesas/Kconfig               |   7 +
>  drivers/phy/renesas/Makefile              |   1 +
>  drivers/phy/renesas/phy-rcar-gen5-mpphy.c | 757 ++++++++++++++++++++++
>  3 files changed, 765 insertions(+)
>  create mode 100644 drivers/phy/renesas/phy-rcar-gen5-mpphy.c
> 
> diff --git a/drivers/phy/renesas/Kconfig b/drivers/phy/renesas/Kconfig
> index 90a9ca2db7fc7..ac903b29cacbc 100644
> --- a/drivers/phy/renesas/Kconfig
> +++ b/drivers/phy/renesas/Kconfig
> @@ -43,6 +43,13 @@ config PHY_RCAR_GEN3_USB3
>  	help
>  	  Support for USB 3.0 PHY found on Renesas R-Car generation 3 SoCs.
>  
> +config PHY_RCAR_GEN5_MPPHY
> +	tristate "Renesas R-Car generation 5 Multi-Protocol PHY driver"
> +	depends on ARCH_RENESAS || COMPILE_TEST
> +	select GENERIC_PHY
> +	help
> +	  Support for Multi-Protocol PHY found on Renesas R-Car generation 5 SoCs.
> +
>  config PHY_RZ_G3E_USB3
>  	tristate "Renesas RZ/G3E USB 3.0 PHY driver"
>  	depends on ARCH_RENESAS || COMPILE_TEST
> diff --git a/drivers/phy/renesas/Makefile b/drivers/phy/renesas/Makefile
> index 0e98083f2f0c8..23d413cfa8ce0 100644
> --- a/drivers/phy/renesas/Makefile
> +++ b/drivers/phy/renesas/Makefile
> @@ -4,4 +4,5 @@ obj-$(CONFIG_PHY_RCAR_GEN2)		+= phy-rcar-gen2.o
>  obj-$(CONFIG_PHY_RCAR_GEN3_PCIE)	+= phy-rcar-gen3-pcie.o
>  obj-$(CONFIG_PHY_RCAR_GEN3_USB2)	+= phy-rcar-gen3-usb2.o
>  obj-$(CONFIG_PHY_RCAR_GEN3_USB3)	+= phy-rcar-gen3-usb3.o
> +obj-$(CONFIG_PHY_RCAR_GEN5_MPPHY)	+= phy-rcar-gen5-mpphy.o
>  obj-$(CONFIG_PHY_RZ_G3E_USB3)		+= phy-rzg3e-usb3.o
> diff --git a/drivers/phy/renesas/phy-rcar-gen5-mpphy.c b/drivers/phy/renesas/phy-rcar-gen5-mpphy.c
> new file mode 100644
> index 0000000000000..31f17a4955e8e
> --- /dev/null
> +++ b/drivers/phy/renesas/phy-rcar-gen5-mpphy.c
> @@ -0,0 +1,757 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Renesas Multi-Protocol PHY device driver
> + *
> + * Copyright (C) 2025-2026 Renesas Electronics Corporation
> + */
> +
> +#include <linux/bitfield.h>
> +#include <linux/bitops.h>
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/firmware.h>
> +#include <linux/io.h>
> +#include <linux/iopoll.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> +#include <linux/phy/phy.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_domain.h>
> +#include <linux/regmap.h>
> +#include <linux/reset.h>
> +#include <linux/types.h>
> +
> +#include <dt-bindings/phy/phy.h>
> +
> +#define MPPHY_NUM_CHANNELS		4
> +
> +/* Common registers */
> +#define MPPHY_CMNCNT1			0x80000
> +#define MPPHY_CMNCNT2			0x80004
> +
> +/* Channel register base and offsets */
> +#define MPPHY_CHAN_BASE(ch)		(0x81000 + (ch) * 0x1000)
> +#define MPPHY_PXCNTXT1(ch)		(MPPHY_CHAN_BASE(ch) + 0x4)
> +#define MPPHY_PXCNTXT2(ch)		(MPPHY_CHAN_BASE(ch) + 0x8)
> +#define MPPHY_PXTEST(ch)		(MPPHY_CHAN_BASE(ch) + 0xc)
> +#define MPPHY_PXREFCLK(ch)		(MPPHY_CHAN_BASE(ch) + 0x14)
> +#define MPPHY_PXRXREQ1(ch)		(MPPHY_CHAN_BASE(ch) + 0x24)
> +#define MPPHY_PXRXCNT(ch)		(MPPHY_CHAN_BASE(ch) + 0x38)
> +#define MPPHY_PXSRAMCNT(ch)		(MPPHY_CHAN_BASE(ch) + 0x40)
> +#define MPPHY_PXTXREQ(ch)		(MPPHY_CHAN_BASE(ch) + 0x44)
> +
> +#define MPPHY_PCS0REG1			0x85000
> +#define MPPHY_PCS0REG5			0x85010
> +
> +/* TCA (Type-C Adapter) Register Offsets within MP-PHY base */
> +#define TCA_OFFSET(ch)			(0x90000 + (((ch) & 1) ? 0x10000 : 0))
> +#define TCA_VBUS_CTRL			0x40
> +
> +/* PCS0REG1 register bits */
> +#define MPPHY_PCS0REG1_VAL		BIT(16)
> +
> +/* PXTEST register bit */
> +#define MPPHY_PXTEST_BIT		BIT(0)
> +
> +/* PXRXCNT register reset value */
> +#define MPPHY_PXRXCNT_RESET_VAL		0x202
> +
> +/* PXSRAMCNT register bits */
> +#define SRAM_EXT_LD_DONE		0x10
> +
> +/* PXREFCLK register value */
> +#define MPPHY_PXREFCLK_VAL_ETH		0x55
> +
> +/* Firmware update */
> +#define MPPHY_FW_BASE			0x10000
> +#define MPPHY_FW_CH_OFFSET		0x20000
> +#define MPPHY_FW_NAME			"rcar_gen5_mp_phy.bin"

Is this firmware redistributable? I would assume not as like other Renesas IP
firmware. If so, you should add a documentation about how an user can get the
firmware, and package it. Like,
Documentation/PCI/controller/rcar-pcie-firmware.rst

> +
> +struct mp_phy_chan_priv {
> +	struct phy *phy;
> +	enum phy_mode protocol_id;
> +	bool initialized;
> +};
> +

[...]

> +static int mp_phy_init(struct phy *phy)
> +{
> +	struct mp_phy_priv *priv = phy_get_drvdata(phy);
> +	struct mp_phy_chan_priv *chan = &priv->chan[phy->id];
> +	int ret;
> +
> +	/*
> +	 * Note: Current source code support for Ethernet, PCIe
> +	 * initialization is based on the bare metal code shared
> +	 * by the board team.
> +	 */

Nit: Make use of 80 column for comments

> +	ret = pm_runtime_get_sync(priv->pd_list->pd_devs[phy->id]);
> +	if (ret < 0) {
> +		dev_err(priv->dev,
> +			"Failed to power on domain for channel %d: %d\n",
> +			phy->id, ret);
> +		return ret;
> +	}
> +
> +	/* Check if initialized with same protocol then skip */
> +	if (chan->initialized)

Comment indicates that the check is supposed to check the protocol, but the code
is not doing it.

> +		return 0;
> +
> +	if (chan->protocol_id == PHY_MODE_PCIE)
> +		ret = mp_phy_init_pcie4(phy);
> +	else if (chan->protocol_id == PHY_MODE_ETHERNET)
> +		ret = mp_phy_init_ethernet(phy);
> +	else
> +		ret = mp_phy_init_usb(phy);
> +	if (ret)
> +		return ret;
> +
> +	chan->initialized = true;
> +	dev_dbg(priv->dev,
> +		"Channel %d successfully initialized for protocol %d\n",
> +		phy->id, chan->protocol_id);
> +
> +	return 0;
> +}
> +

[...]

> +static int mp_phy_probe(struct platform_device *pdev)
> +{
> +	static const char *const pd_names[] = { "mpp0", "mpp1", "mpp2", "mpp3" };
> +	const struct dev_pm_domain_attach_data pd_attach_data = {
> +		.pd_names = pd_names,
> +		.num_pd_names = ARRAY_SIZE(pd_names),
> +		.pd_flags = 0,
> +	};
> +	struct device *dev = &pdev->dev;
> +	struct phy_provider *provider;
> +	struct mp_phy_priv *priv;
> +	int i, ret;
> +
> +	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> +	if (!priv)
> +		return -ENOMEM;
> +
> +	priv->dev = dev;
> +
> +	ret = mp_phy_parse_dt(pdev, priv);
> +	if (ret)
> +		return ret;
> +
> +	priv->base = devm_platform_ioremap_resource(pdev, 0);
> +	if (IS_ERR(priv->base))
> +		return dev_err_probe(dev, PTR_ERR(priv->base), "Failed to map PHY registers\n");
> +
> +	priv->map = devm_regmap_init_mmio(dev, priv->base, &mp_phy_regmap_config);
> +	if (IS_ERR(priv->map))
> +		return PTR_ERR(priv->map);
> +
> +	priv->num_clks = devm_clk_bulk_get_all(dev, &priv->clks);
> +	if (priv->num_clks < 0)
> +		return dev_err_probe(dev, priv->num_clks, "Failed to get PHY clocks\n");
> +	if (priv->num_clks != 5)
> +		return dev_err_probe(dev, -ENODEV, "Failed to get all PHY clocks\n");

devm_clk_bulk_get_all() will fail if any of the clocks cannot be acquired. So I
don't see a need to check for clock count.

> +
> +	/*
> +	 * The reset ID order here does matters, reset_control_bulk_assert()
> +	 * asserts these resets in this order, with mpphy02 reset being
> +	 * asserted first, reset_control_bulk_deassert() deasserts these
> +	 * resets in reverse order, with mpphy02 being reset being
> +	 * deasserted last. This is the behavior the hardware expects.
> +	 */
> +	priv->resets[0].id = "mpphy02";
> +	priv->resets[1].id = "mpphy01";
> +	priv->resets[2].id = "mpphy11";
> +	priv->resets[3].id = "mpphy21";
> +	priv->resets[4].id = "mpphy31";
> +	ret = devm_reset_control_bulk_get_exclusive(&pdev->dev, ARRAY_SIZE(priv->resets),
> +						    priv->resets);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "Failed to get PHY resets\n");
> +
> +	platform_set_drvdata(pdev, priv);
> +
> +	ret = dev_pm_domain_attach_list(dev, &pd_attach_data, &priv->pd_list);
> +	if (ret < 0)
> +		return dev_err_probe(dev, ret, "Failed to attach power domains\n");
> +
> +	ret = devm_pm_runtime_enable(dev);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "Failed to enable PHY runtime PM\n");
> +
> +	provider = devm_of_phy_provider_register(dev, mp_phy_xlate);
> +	if (IS_ERR(provider))
> +		return dev_err_probe(dev, PTR_ERR(provider), "Failed to register PHY provider\n");
> +
> +	for (i = 0; i < MPPHY_NUM_CHANNELS; i++) {
> +		priv->chan[i].phy = devm_phy_create(dev, NULL, &mp_phy_ops);
> +		if (IS_ERR(priv->chan[i].phy)) {
> +			return dev_err_probe(dev, PTR_ERR(priv->chan[i].phy),
> +					     "Failed to create PHY %d\n", i);
> +		}
> +
> +		priv->chan[i].phy->id = i;
> +		phy_set_drvdata(priv->chan[i].phy, priv);
> +	}
> +
> +	return pm_runtime_resume_and_get(dev);

Since the runtime usage count is incremented and not dropped anywhere else, the
driver will never runtime suspend.

> +}
> +
> +static void mp_phy_remove(struct platform_device *pdev)
> +{
> +	struct mp_phy_priv *priv = dev_get_drvdata(&pdev->dev);
> +	struct device *dev = &pdev->dev;
> +
> +	pm_runtime_put(dev);
> +
> +	dev_pm_domain_detach_list(priv->pd_list);
> +
> +	pm_runtime_disable(&pdev->dev);
> +
> +	if (priv->fw)
> +		release_firmware(priv->fw);
> +
> +	platform_set_drvdata(pdev, NULL);
> +}
> +
> +static int mp_phy_suspend(struct device *dev)
> +{
> +	struct mp_phy_priv *priv = dev_get_drvdata(dev);
> +	int i;
> +
> +	for (i = 0; i < MPPHY_NUM_CHANNELS; i++)
> +		priv->chan[i].initialized = false;
> +
> +	clk_bulk_disable_unprepare(priv->num_clks, priv->clks);
> +
> +	dev_info(dev, "Multi-Protocol PHY suspended\n");

Please avoid spamming the log.

I'm wondering how you managed to test runtime PM with
pm_runtime_resume_and_get() above.

- Mani

-- 
மணிவண்ணன் சதாசிவம்

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

end of thread, other threads:[~2026-09-03 10:16 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-28 19:21 [PATCH 1/2] dt-bindings: phy: Document Renesas Multi-Protocol PHY driver for R-Car X5H Marek Vasut
2026-08-28 19:21 ` [PATCH 2/2] phy: renesas: Add " Marek Vasut
2026-08-28 19:35   ` sashiko-bot
2026-09-03 10:15   ` Manivannan Sadhasivam
2026-08-28 19:28 ` [PATCH 1/2] dt-bindings: phy: Document " sashiko-bot
2026-08-31  8:13 ` Geert Uytterhoeven

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