[PATCH 2/2] phy: renesas: Add Renesas Multi-Protocol PHY driver for R-Car X5H
Manivannan Sadhasivam
mani at kernel.org
Thu Sep 3 03:15:43 PDT 2026
On Fri, Aug 28, 2026 at 09:21:40PM +0200, Marek Vasut wrote:
> From: Thanh Quan <thanh.quan.xn at 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 at renesas.com>
> Co-developed-by: Phong Hoang <phong.hoang.wz at renesas.com>
> Signed-off-by: Phong Hoang <phong.hoang.wz at renesas.com>
> Co-developed-by: Hai Pham <hai.pham.ud at renesas.com>
> Signed-off-by: Hai Pham <hai.pham.ud at renesas.com>
> Co-developed-by: Yoshihiro Shimoda <yoshihiro.shimoda.uh at renesas.com>
> Signed-off-by: Yoshihiro Shimoda <yoshihiro.shimoda.uh at renesas.com>
> Co-developed-by: Marek Vasut <marek.vasut+renesas at mailbox.org>
> Signed-off-by: Marek Vasut <marek.vasut+renesas at mailbox.org>
> ---
> Cc: Arnd Bergmann <arnd at arndb.de>
> Cc: Biju Das <biju.das.jz at bp.renesas.com>
> Cc: Conor Dooley <conor+dt at kernel.org>
> Cc: Geert Uytterhoeven <geert+renesas at glider.be>
> Cc: Hai Pham <hai.pham.ud at renesas.com>
> Cc: Krzysztof Kozlowski <krzk+dt at kernel.org>
> Cc: Magnus Damm <magnus.damm at gmail.com>
> Cc: Manivannan Sadhasivam <mani at kernel.org>
> Cc: Neil Armstrong <neil.armstrong at linaro.org>
> Cc: Phong Hoang <phong.hoang.wz at renesas.com>
> Cc: Rob Herring <robh at kernel.org>
> Cc: Tam Nguyen <tam.nguyen.xa at renesas.com>
> Cc: Thanh Quan <thanh.quan.xn at renesas.com>
> Cc: Tommaso Merciai <tommaso.merciai.xr at bp.renesas.com>
> Cc: Vinh Nguyen <vinh.nguyen.xz at renesas.com>
> Cc: Vinod Koul <vkoul at kernel.org>
> Cc: Yoshihiro Shimoda <yoshihiro.shimoda.uh at renesas.com>
> Cc: devicetree at vger.kernel.org
> Cc: linux-kernel at vger.kernel.org
> Cc: linux-phy at lists.infradead.org
> Cc: linux-renesas-soc at 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
--
மணிவண்ணன் சதாசிவம்
More information about the linux-phy
mailing list