summaryrefslogtreecommitdiff
path: root/drivers/phy
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/phy')
-rw-r--r--drivers/phy/Kconfig14
-rw-r--r--drivers/phy/Makefile2
-rw-r--r--drivers/phy/marvell/Kconfig1
-rw-r--r--drivers/phy/qcom/Kconfig6
-rw-r--r--drivers/phy/qcom/phy-qcom-qmp-ufs.c62
-rw-r--r--drivers/phy/renesas/Kconfig12
-rw-r--r--drivers/phy/renesas/Makefile2
-rw-r--r--drivers/phy/renesas/r8a78000-ether-pcs.c424
-rw-r--r--drivers/phy/renesas/r8a78000-mp-phy.c225
-rw-r--r--drivers/phy/ti/phy-j721e-wiz.c29
10 files changed, 757 insertions, 20 deletions
diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig
index d36a5f00ef8..420d7c7a44d 100644
--- a/drivers/phy/Kconfig
+++ b/drivers/phy/Kconfig
@@ -289,11 +289,19 @@ config PHY_NPCM_USB
Support the USB PHY in NPCM SoCs
config PHY_IMX8MQ_USB
- bool "NXP i.MX8MQ/i.MX8MP/i.MX95 USB PHY Driver"
+ bool "NXP i.MX8MQ/i.MX8MP/i.MX95/i.MX94 USB PHY Driver"
depends on PHY
- depends on IMX8MQ || IMX8MP || IMX95
+ depends on IMX8MQ || IMX8MP || IMX95 || IMX94
help
- Support the USB3.0 PHY in NXP i.MX8MQ, i.MX8MP, and i.MX95 SoC
+ Support the USB3.0 PHY in NXP i.MX8MQ, i.MX8MP, i.MX95, and i.MX94 SoCs.
+
+config SPL_PHY_IMX8MQ_USB
+ bool "Enable NXP i.MX8MQ/i.MX8MP/i.MX95/i.MX94 USB3.0 PHY Driver in SPL"
+ depends on SPL_PHY
+ depends on IMX8MQ || IMX8MP || IMX95 || IMX94
+ help
+ Enable support for the USB3.0 PHY in NXP i.MX8MQ, i.MX8MP, i.MX95, and
+ i.MX94 SoCs in SPL.
config PHY_IMX8M_PCIE
bool "NXP i.MX8MM/i.MX8MP PCIe PHY Driver"
diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile
index 98c1ef8683b..5a6df0ecfeb 100644
--- a/drivers/phy/Makefile
+++ b/drivers/phy/Makefile
@@ -38,7 +38,7 @@ obj-$(CONFIG_PHY_DA8XX_USB) += phy-da8xx-usb.o
obj-$(CONFIG_PHY_EXYNOS_USBDRD) += phy-exynos-usbdrd.o
obj-$(CONFIG_PHY_MTK_TPHY) += phy-mtk-tphy.o
obj-$(CONFIG_PHY_NPCM_USB) += phy-npcm-usb.o
-obj-$(CONFIG_PHY_IMX8MQ_USB) += phy-imx8mq-usb.o
+obj-$(CONFIG_$(PHASE_)PHY_IMX8MQ_USB) += phy-imx8mq-usb.o
obj-$(CONFIG_PHY_IMX8M_PCIE) += phy-imx8m-pcie.o
obj-$(CONFIG_PHY_XILINX_ZYNQMP) += phy-zynqmp.o
obj-y += cadence/
diff --git a/drivers/phy/marvell/Kconfig b/drivers/phy/marvell/Kconfig
index b5f69c0a96d..c66197de143 100644
--- a/drivers/phy/marvell/Kconfig
+++ b/drivers/phy/marvell/Kconfig
@@ -1,5 +1,6 @@
config MVEBU_COMPHY_SUPPORT
bool "ComPhy SerDes driver"
+ depends on ARMADA_3700 || ARMADA_8K
help
Choose this option to add support
for Comphy driver.
diff --git a/drivers/phy/qcom/Kconfig b/drivers/phy/qcom/Kconfig
index 61e5e2fca41..0dd69f7ffd0 100644
--- a/drivers/phy/qcom/Kconfig
+++ b/drivers/phy/qcom/Kconfig
@@ -1,8 +1,8 @@
config MSM8916_USB_PHY
- bool "Qualcomm MSM8916 USB PHY support"
- depends on PHY
+ bool
+ select PHY
help
- Support the USB PHY in msm8916
+ Support the Qualcomm MSM8916 USB PHY
This PHY is found on qualcomm dragonboard410c development board.
diff --git a/drivers/phy/qcom/phy-qcom-qmp-ufs.c b/drivers/phy/qcom/phy-qcom-qmp-ufs.c
index f3c606847fb..907f34744eb 100644
--- a/drivers/phy/qcom/phy-qcom-qmp-ufs.c
+++ b/drivers/phy/qcom/phy-qcom-qmp-ufs.c
@@ -221,6 +221,36 @@ static const struct qmp_ufs_init_tbl sm8150_ufsphy_hs_g4_rx[] = {
QMP_PHY_INIT_CFG(QSERDES_V4_RX_RX_MODE_01_HIGH4, 0x3c),
};
+static const struct qmp_ufs_init_tbl sm7150_ufsphy_rx[] = {
+ QMP_PHY_INIT_CFG(QSERDES_V3_RX_SIGDET_LVL, 0x24),
+ QMP_PHY_INIT_CFG(QSERDES_V3_RX_SIGDET_CNTRL, 0x0f),
+ QMP_PHY_INIT_CFG(QSERDES_V3_RX_SIGDET_DEGLITCH_CNTRL, 0x1e),
+ QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_INTERFACE_MODE, 0x40),
+ QMP_PHY_INIT_CFG(QSERDES_V3_RX_UCDR_FASTLOCK_FO_GAIN, 0x0b),
+ QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_TERM_BW, 0x5b),
+ QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_EQU_ADAPTOR_CNTRL2, 0x06),
+ QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_EQU_ADAPTOR_CNTRL3, 0x04),
+ QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_EQU_ADAPTOR_CNTRL4, 0x1b),
+ QMP_PHY_INIT_CFG(QSERDES_V3_RX_UCDR_SVS_SO_GAIN_HALF, 0x04),
+ QMP_PHY_INIT_CFG(QSERDES_V3_RX_UCDR_SVS_SO_GAIN_QUARTER, 0x04),
+ QMP_PHY_INIT_CFG(QSERDES_V3_RX_UCDR_SVS_SO_GAIN, 0x04),
+ QMP_PHY_INIT_CFG(QSERDES_V3_RX_UCDR_SO_SATURATION_AND_ENABLE, 0x5b),
+ QMP_PHY_INIT_CFG(QSERDES_V3_RX_UCDR_PI_CONTROLS, 0x81),
+ QMP_PHY_INIT_CFG(QSERDES_V3_RX_UCDR_FASTLOCK_COUNT_LOW, 0x80),
+ QMP_PHY_INIT_CFG(QSERDES_V3_RX_RX_MODE_00, 0x59),
+};
+
+static const struct qmp_ufs_init_tbl sm7150_ufsphy_pcs[] = {
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_UFS_RX_SIGDET_CTRL2, 0x6f),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_UFS_TX_LARGE_AMP_DRV_LVL, 0x0f),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_UFS_TX_SMALL_AMP_DRV_LVL, 0x02),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_UFS_RX_SYM_RESYNC_CTRL, 0x03),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_UFS_TX_MID_TERM_CTRL1, 0x43),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_UFS_RX_SIGDET_CTRL1, 0x0f),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_UFS_RX_MIN_HIBERN8_TIME, 0xff),
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_UFS_MULTI_LANE_CTRL1, 0x02),
+};
+
static const struct qmp_ufs_init_tbl sm8150_ufsphy_serdes[] = {
QMP_PHY_INIT_CFG(QSERDES_V4_COM_SYSCLK_EN_SEL, 0xd9),
QMP_PHY_INIT_CFG(QSERDES_V4_COM_HSCLK_SEL, 0x11),
@@ -1018,6 +1048,36 @@ static const struct qmp_ufs_cfg sm8150_ufsphy_cfg = {
.no_pcs_sw_reset = false,
};
+static const struct qmp_ufs_cfg sm7150_ufsphy_cfg = {
+ .lanes = 1,
+
+ .offsets = &qmp_ufs_offsets,
+
+ .tbls = {
+ .serdes = sdm845_ufsphy_serdes,
+ .serdes_num = ARRAY_SIZE(sdm845_ufsphy_serdes),
+ .tx = sdm845_ufsphy_tx,
+ .tx_num = ARRAY_SIZE(sdm845_ufsphy_tx),
+ .rx = sm7150_ufsphy_rx,
+ .rx_num = ARRAY_SIZE(sm7150_ufsphy_rx),
+ .pcs = sm7150_ufsphy_pcs,
+ .pcs_num = ARRAY_SIZE(sm7150_ufsphy_pcs),
+ },
+ .tbls_hs_b = {
+ .serdes = sdm845_ufsphy_hs_b_serdes,
+ .serdes_num = ARRAY_SIZE(sdm845_ufsphy_hs_b_serdes),
+ },
+ .clk_list = sdm845_ufs_phy_clk_l,
+ .num_clks = ARRAY_SIZE(sdm845_ufs_phy_clk_l),
+ .vreg_list = qmp_ufs_vreg_l,
+ .num_vregs = ARRAY_SIZE(qmp_ufs_vreg_l),
+ .reset_list = qmp_ufs_reset_l,
+ .num_resets = ARRAY_SIZE(qmp_ufs_reset_l),
+ .regs = ufsphy_v3_regs_layout,
+
+ .no_pcs_sw_reset = true,
+};
+
static const struct qmp_ufs_cfg sm8250_ufsphy_cfg = {
.lanes = 2,
@@ -1593,6 +1653,8 @@ static struct phy_ops qmp_ufs_ops = {
static const struct udevice_id qmp_ufs_ids[] = {
{ .compatible = "qcom,sa8775p-qmp-ufs-phy", .data = (ulong)&sa8775p_ufsphy_cfg, },
{ .compatible = "qcom,sdm845-qmp-ufs-phy", .data = (ulong)&sdm845_ufsphy_cfg },
+ { .compatible = "qcom,sm6350-qmp-ufs-phy", .data = (ulong)&sdm845_ufsphy_cfg },
+ { .compatible = "qcom,sm7150-qmp-ufs-phy", .data = (ulong)&sm7150_ufsphy_cfg },
{ .compatible = "qcom,sm8150-qmp-ufs-phy", .data = (ulong)&sm8150_ufsphy_cfg },
{ .compatible = "qcom,sm8250-qmp-ufs-phy", .data = (ulong)&sm8250_ufsphy_cfg },
{ .compatible = "qcom,qcs8300-qmp-ufs-phy", .data = (ulong)&sa8775p_ufsphy_cfg },
diff --git a/drivers/phy/renesas/Kconfig b/drivers/phy/renesas/Kconfig
index 0efb0f8f337..affbee0500c 100644
--- a/drivers/phy/renesas/Kconfig
+++ b/drivers/phy/renesas/Kconfig
@@ -7,3 +7,15 @@ config PHY_R8A779F0_ETHERNET_SERDES
depends on RCAR_64 && PHY
help
Support for Ethernet SERDES found on Renesas R-Car S4-8 SoCs.
+
+config PHY_R8A78000_ETHERNET_PCS
+ tristate "Renesas R-Car X5H Ethernet PCS driver"
+ depends on RCAR_64 && PHY
+ help
+ Support for Ethernet PCS found on Renesas R-Car X5H SoCs.
+
+config PHY_R8A78000_MP_PHY
+ tristate "Renesas R-Car X5H Multi-Protocol PHY driver"
+ depends on RCAR_64 && PHY
+ help
+ Support for Multi-Protocol PHY on Renesas R-Car X5H SoCs.
diff --git a/drivers/phy/renesas/Makefile b/drivers/phy/renesas/Makefile
index fd6b8d964e5..12585c21f58 100644
--- a/drivers/phy/renesas/Makefile
+++ b/drivers/phy/renesas/Makefile
@@ -1 +1,3 @@
obj-$(CONFIG_PHY_R8A779F0_ETHERNET_SERDES) += r8a779f0-ether-serdes.o
+obj-$(CONFIG_PHY_R8A78000_ETHERNET_PCS) += r8a78000-ether-pcs.o
+obj-$(CONFIG_PHY_R8A78000_MP_PHY) += r8a78000-mp-phy.o
diff --git a/drivers/phy/renesas/r8a78000-ether-pcs.c b/drivers/phy/renesas/r8a78000-ether-pcs.c
new file mode 100644
index 00000000000..6343f89313a
--- /dev/null
+++ b/drivers/phy/renesas/r8a78000-ether-pcs.c
@@ -0,0 +1,424 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Renesas Ethernet PCS Device Driver
+ *
+ * Based on the Renesas Ethernet SERDES driver and updated for PCS support.
+ *
+ * Copyright (C) 2025 Renesas Electronics Corporation
+ */
+
+#include <asm/io.h>
+#include <clk-uclass.h>
+#include <clk.h>
+#include <div64.h>
+#include <dm.h>
+#include <dm/device_compat.h>
+#include <dm/lists.h>
+#include <dm/of_access.h>
+#include <generic-phy.h>
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/iopoll.h>
+#include <log.h>
+#include <reset.h>
+#include <syscon.h>
+
+#define R8A78000_ETH_PCS_NUM 8
+#define R8A78000_ETH_PCS_OFFSET 0x0400
+#define R8A78000_ETH_PCS_BANK_SELECT 0x03fc
+#define R8A78000_ETH_PCS_TIMEOUT_US 100000
+#define R8A78000_ETH_PCS_NUM_RETRY_LINKUP 8
+
+struct r8a78000_eth_pcs_drv_data;
+struct r8a78000_eth_pcs_channel {
+ struct r8a78000_eth_pcs_drv_data *dd;
+ struct phy *phy;
+ void __iomem *addr;
+ phy_interface_t phy_interface;
+ int speed;
+ int index;
+};
+
+struct r8a78000_eth_pcs_drv_data {
+ void __iomem *addr;
+ struct reset_ctl_bulk reset;
+ struct phy mpphy;
+ struct r8a78000_eth_pcs_channel channel[R8A78000_ETH_PCS_NUM];
+ struct clk_bulk clks;
+};
+
+/*
+ * The datasheet describes initialization procedure without any information
+ * about registers' name/bits. So, this is all black magic to initialize
+ * the hardware.
+ */
+static void r8a78000_eth_pcs_write32(void __iomem *addr, u32 offs, u32 bank, u32 data)
+{
+ writel(bank, addr + R8A78000_ETH_PCS_BANK_SELECT);
+ writel(data, addr + offs);
+}
+
+static int
+r8a78000_eth_pcs_reg_wait(struct r8a78000_eth_pcs_channel *channel,
+ u32 offs, u32 bank, u32 mask, u32 expected)
+{
+ u32 val = 0;
+ int ret;
+
+ writel(bank, channel->addr + R8A78000_ETH_PCS_BANK_SELECT);
+
+ ret = readl_poll_timeout(channel->addr + offs, val,
+ (val & mask) == expected,
+ R8A78000_ETH_PCS_TIMEOUT_US);
+ if (ret)
+ dev_dbg(channel->phy->dev,
+ "%s: index %d, offs %x, bank %x, mask %x, expected %x\n",
+ __func__, channel->index, offs, bank, mask, expected);
+
+ return ret;
+}
+
+static int
+r8a78000_eth_pcs_init_ram(struct r8a78000_eth_pcs_channel *channel, struct phy *mpphy)
+{
+ int ret;
+
+ ret = r8a78000_eth_pcs_reg_wait(channel, 0x026c, 0x180, BIT(0), 0x01);
+ if (ret)
+ return ret;
+
+ r8a78000_eth_pcs_write32(channel->addr, 0x026c, 0x180, 0x03);
+
+ ret = generic_phy_power_on(mpphy);
+ if (ret)
+ return ret;
+
+ ret = r8a78000_eth_pcs_reg_wait(channel, 0x0000, 0x300, BIT(15), 0);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int
+r8a78000_eth_pcs_common_setting(struct r8a78000_eth_pcs_channel *channel)
+{
+ int ret;
+
+ switch (channel->phy_interface) {
+ case PHY_INTERFACE_MODE_SGMII:
+ r8a78000_eth_pcs_write32(channel->addr, 0x001c, 0x300, 0x0001);
+ r8a78000_eth_pcs_write32(channel->addr, 0x0000, 0x380, 0x2000);
+ r8a78000_eth_pcs_write32(channel->addr, 0x0000, 0x1f00, 0x0140);
+ r8a78000_eth_pcs_write32(channel->addr, 0x0258, 0x180, 0x0018);
+ r8a78000_eth_pcs_write32(channel->addr, 0x01dc, 0x180, 0x000d);
+ r8a78000_eth_pcs_write32(channel->addr, 0x00f8, 0x180, 0x0016);
+ r8a78000_eth_pcs_write32(channel->addr, 0x0248, 0x180, 0x0016);
+ r8a78000_eth_pcs_write32(channel->addr, 0x0000, 0x300, 0x0c40);
+
+ ret = r8a78000_eth_pcs_reg_wait(channel, 0x0040, 0x380, GENMASK(4, 2), 0x06 << 2);
+ if (ret)
+ return ret;
+
+ r8a78000_eth_pcs_write32(channel->addr, 0x0000, 0x300, 0x0440);
+
+ ret = r8a78000_eth_pcs_reg_wait(channel, 0x0040, 0x380, GENMASK(4, 2), 0x04 << 2);
+ if (ret)
+ return ret;
+
+ r8a78000_eth_pcs_write32(channel->addr, 0x0014, 0x380, 0x0050);
+ r8a78000_eth_pcs_write32(channel->addr, 0x00d8, 0x180, 0x3000);
+ r8a78000_eth_pcs_write32(channel->addr, 0x00dc, 0x180, 0x0000);
+
+ return 0;
+ case PHY_INTERFACE_MODE_USXGMII:
+ r8a78000_eth_pcs_write32(channel->addr, 0x001c, 0x300, 0x0000);
+ r8a78000_eth_pcs_write32(channel->addr, 0x001c, 0x380, 0x0000);
+ r8a78000_eth_pcs_write32(channel->addr, 0x0000, 0x380, 0x2200);
+ r8a78000_eth_pcs_write32(channel->addr, 0x0258, 0x180, 0x0018);
+ r8a78000_eth_pcs_write32(channel->addr, 0x01dc, 0x180, 0x000d);
+ r8a78000_eth_pcs_write32(channel->addr, 0x00f8, 0x180, 0x001b);
+ r8a78000_eth_pcs_write32(channel->addr, 0x0248, 0x180, 0x001b);
+ r8a78000_eth_pcs_write32(channel->addr, 0x0000, 0x300, 0x0c40);
+
+ ret = r8a78000_eth_pcs_reg_wait(channel, 0x0040, 0x380, GENMASK(4, 2), 0x06 << 2);
+ if (ret)
+ return ret;
+
+ r8a78000_eth_pcs_write32(channel->addr, 0x0000, 0x300, 0x0440);
+
+ ret = r8a78000_eth_pcs_reg_wait(channel, 0x0040, 0x380, GENMASK(4, 2), 0x04 << 2);
+ if (ret)
+ return ret;
+
+ r8a78000_eth_pcs_write32(channel->addr, 0x0014, 0x380, 0x0050);
+ r8a78000_eth_pcs_write32(channel->addr, 0x00d8, 0x180, 0x1800);
+ r8a78000_eth_pcs_write32(channel->addr, 0x00dc, 0x180, 0x0012);
+
+ ret = r8a78000_eth_pcs_reg_wait(channel, 0x0080, 0x180, BIT(12), BIT(12));
+ if (ret)
+ return ret;
+
+ r8a78000_eth_pcs_write32(channel->addr, 0x0170, 0x180, 0x1000);
+
+ ret = r8a78000_eth_pcs_reg_wait(channel, 0x0260, 0x180, BIT(12), BIT(12));
+ if (ret)
+ return ret;
+
+ r8a78000_eth_pcs_write32(channel->addr, 0x0170, 0x180, 0x0000);
+
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int
+r8a78000_eth_pcs_chan_setting(struct r8a78000_eth_pcs_channel *channel)
+{
+ int ret;
+
+ switch (channel->phy_interface) {
+ case PHY_INTERFACE_MODE_SGMII:
+ /* For AN_ON */
+ r8a78000_eth_pcs_write32(channel->addr, 0x0004, 0x1f80, 0x0005);
+ r8a78000_eth_pcs_write32(channel->addr, 0x0000, 0x1f80, 0x2200);
+ r8a78000_eth_pcs_write32(channel->addr, 0x0000, 0x1f00, 0x3140);
+
+ ret = r8a78000_eth_pcs_reg_wait(channel, 0x0008, 0x1f80, BIT(0), BIT(0));
+ if (ret)
+ return ret;
+
+ break;
+ case PHY_INTERFACE_MODE_USXGMII:
+ /* For AN_ON */
+ r8a78000_eth_pcs_write32(channel->addr, 0x0004, 0x1f80, 0x0001);
+ r8a78000_eth_pcs_write32(channel->addr, 0x0028, 0x1f80, 0x0001);
+ r8a78000_eth_pcs_write32(channel->addr, 0x0000, 0x1f80, 0x2008);
+ r8a78000_eth_pcs_write32(channel->addr, 0x0000, 0x1f00, 0x3140);
+
+ ret = r8a78000_eth_pcs_reg_wait(channel, 0x0008, 0x1f80, BIT(0), BIT(0));
+ if (ret)
+ return ret;
+
+ r8a78000_eth_pcs_write32(channel->addr, 0x0008, 0x1f80, 0x0000);
+
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static int
+r8a78000_eth_pcs_chan_speed(struct r8a78000_eth_pcs_channel *channel)
+{
+ int ret;
+
+ switch (channel->phy_interface) {
+ case PHY_INTERFACE_MODE_SGMII:
+ /* Do nothing */
+ break;
+ case PHY_INTERFACE_MODE_USXGMII:
+ if (channel->speed == 10000)
+ r8a78000_eth_pcs_write32(channel->addr, 0x0000, 0x1f00, 0x2140);
+ else if (channel->speed == 2500)
+ r8a78000_eth_pcs_write32(channel->addr, 0x0000, 0x1f00, 0x0120);
+ else
+ return -EOPNOTSUPP;
+
+ r8a78000_eth_pcs_write32(channel->addr, 0x0000, 0x380, 0x2600);
+
+ ret = r8a78000_eth_pcs_reg_wait(channel, 0x0000, 0x380, BIT(10), 0);
+ if (ret)
+ return ret;
+
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static int r8a78000_eth_pcs_monitor_linkup(struct r8a78000_eth_pcs_channel *channel)
+{
+ int i, ret;
+
+ for (i = 0; i < R8A78000_ETH_PCS_NUM_RETRY_LINKUP; i++) {
+ ret = r8a78000_eth_pcs_reg_wait(channel, 0x0004, 0x300,
+ BIT(2), BIT(2));
+ if (!ret)
+ break;
+
+ /* restart */
+ r8a78000_eth_pcs_write32(channel->addr, 0x0144, 0x180, 0x0010);
+ udelay(1);
+ r8a78000_eth_pcs_write32(channel->addr, 0x0144, 0x180, 0x0000);
+ }
+
+ return ret;
+}
+
+static int r8a78000_eth_pcs_init(struct phy *p)
+{
+ struct r8a78000_eth_pcs_drv_data *dd = dev_get_priv(p->dev);
+ struct r8a78000_eth_pcs_channel *channel = dd->channel + p->id;
+ int ret;
+
+ ret = generic_phy_init(&dd->mpphy);
+ if (ret) {
+ dev_dbg(channel->phy->dev, "XPCS: Failed to init MPPHY\n");
+ return ret;
+ }
+
+ ret = r8a78000_eth_pcs_init_ram(channel, &dd->mpphy);
+ if (ret)
+ return ret;
+
+ ret = r8a78000_eth_pcs_common_setting(channel);
+
+ return ret;
+}
+
+static int r8a78000_eth_pcs_hw_init_late(struct r8a78000_eth_pcs_channel *channel)
+{
+ int ret;
+
+ ret = r8a78000_eth_pcs_chan_setting(channel);
+ if (ret)
+ return ret;
+
+ ret = r8a78000_eth_pcs_chan_speed(channel);
+ if (ret)
+ return ret;
+
+ r8a78000_eth_pcs_write32(channel->addr, 0x03c0, 0x380, 0x0000);
+
+ r8a78000_eth_pcs_write32(channel->addr, 0x03d0, 0x380, 0x0000);
+
+ return r8a78000_eth_pcs_monitor_linkup(channel);
+}
+
+static int r8a78000_eth_pcs_power_on(struct phy *p)
+{
+ struct r8a78000_eth_pcs_drv_data *dd = dev_get_priv(p->dev);
+ struct r8a78000_eth_pcs_channel *channel = dd->channel + p->id;
+
+ return r8a78000_eth_pcs_hw_init_late(channel);
+}
+
+static int r8a78000_eth_pcs_set_mode(struct phy *p, enum phy_mode mode,
+ int submode)
+{
+ struct r8a78000_eth_pcs_drv_data *dd = dev_get_priv(p->dev);
+ struct r8a78000_eth_pcs_channel *channel = dd->channel + p->id;
+
+ if (mode != PHY_MODE_ETHERNET)
+ return -EOPNOTSUPP;
+
+ switch (submode) {
+ case PHY_INTERFACE_MODE_GMII:
+ case PHY_INTERFACE_MODE_SGMII:
+ case PHY_INTERFACE_MODE_USXGMII:
+ channel->phy_interface = submode;
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int r8a78000_eth_pcs_set_speed(struct phy *p, int speed)
+{
+ struct r8a78000_eth_pcs_drv_data *dd = dev_get_priv(p->dev);
+ struct r8a78000_eth_pcs_channel *channel = dd->channel + p->id;
+
+ channel->speed = speed;
+
+ return 0;
+}
+
+static int r8a78000_eth_pcs_of_xlate(struct phy *phy,
+ struct ofnode_phandle_args *args)
+{
+ if (args->args_count < 1)
+ return -ENODEV;
+
+ if (args->args[0] >= R8A78000_ETH_PCS_NUM)
+ return -ENODEV;
+
+ phy->id = args->args[0];
+
+ return 0;
+}
+
+static const struct phy_ops r8a78000_eth_pcs_ops = {
+ .init = r8a78000_eth_pcs_init,
+ .power_on = r8a78000_eth_pcs_power_on,
+ .set_mode = r8a78000_eth_pcs_set_mode,
+ .set_speed = r8a78000_eth_pcs_set_speed,
+ .of_xlate = r8a78000_eth_pcs_of_xlate,
+};
+
+static const struct udevice_id r8a78000_eth_pcs_of_table[] = {
+ { .compatible = "renesas,r8a78000-ether-pcs", },
+ { }
+};
+
+static int r8a78000_eth_pcs_probe(struct udevice *dev)
+{
+ struct r8a78000_eth_pcs_drv_data *dd = dev_get_priv(dev);
+ int i, ret;
+
+ dd->addr = dev_read_addr_ptr(dev);
+ if (!dd->addr)
+ return -EINVAL;
+
+ ret = reset_get_bulk(dev, &dd->reset);
+ if (ret)
+ return ret;
+
+ ret = clk_get_bulk(dev, &dd->clks);
+ if (ret < 0)
+ goto err_clk_get;
+
+ ret = clk_enable_bulk(&dd->clks);
+ if (ret)
+ goto err_clk_enable;
+
+ reset_assert_bulk(&dd->reset);
+ reset_deassert_bulk(&dd->reset);
+
+ ret = generic_phy_get_by_index(dev, 0, &dd->mpphy);
+ if (ret)
+ goto err_phy_get;
+
+ for (i = 0; i < R8A78000_ETH_PCS_NUM; i++) {
+ struct r8a78000_eth_pcs_channel *channel = &dd->channel[i];
+
+ channel->addr = dd->addr + R8A78000_ETH_PCS_OFFSET * i;
+ channel->dd = dd;
+ channel->index = i;
+ }
+
+ return 0;
+
+err_phy_get:
+ clk_disable_bulk(&dd->clks);
+err_clk_enable:
+ clk_release_bulk(&dd->clks);
+err_clk_get:
+ reset_release_bulk(&dd->reset);
+ return ret;
+}
+
+U_BOOT_DRIVER(r8a78000_eth_pcs_driver_platform) = {
+ .name = "r8a78000_eth_pcs",
+ .id = UCLASS_PHY,
+ .of_match = r8a78000_eth_pcs_of_table,
+ .probe = r8a78000_eth_pcs_probe,
+ .ops = &r8a78000_eth_pcs_ops,
+ .priv_auto = sizeof(struct r8a78000_eth_pcs_drv_data),
+};
diff --git a/drivers/phy/renesas/r8a78000-mp-phy.c b/drivers/phy/renesas/r8a78000-mp-phy.c
new file mode 100644
index 00000000000..fba130a65a1
--- /dev/null
+++ b/drivers/phy/renesas/r8a78000-mp-phy.c
@@ -0,0 +1,225 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Renesas Multi-Protocol PHY device driver
+ *
+ * Copyright (C) 2025 Renesas Electronics Corporation
+ */
+
+#include <asm/io.h>
+#include <clk.h>
+#include <dm.h>
+#include <dm/device_compat.h>
+#include <generic-phy.h>
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <reset.h>
+
+/* Common registers */
+#define MPPHY_CMNCNT1 0x80000
+#define MPPHY_CMNCNT2 0x80004
+#define MPPHY_PCS0REG1 0x85000
+#define MPPHY_PCS0REG5 0x85010
+
+/* Channel register base and offsets */
+#define MPPHY_CHAN_BASE(ch) (0x81000 + (ch) * 0x1000)
+#define MPPHY_PXTEST_OFFSET 0x00C
+#define MPPHY_RXCNT_OFFSET 0x038
+#define MPPHY_SRAMCNT_OFFSET 0x040
+#define MPPHY_REFCLK_OFFSET 0x014
+#define MPPHY_CNTXT1_OFFSET 0x004
+#define MPPHY_CNTXT2_OFFSET 0x008
+#define MPPHY_TXREQ_OFFSET 0x044
+
+/* Channel specific registers */
+#define MPPHY_PXTEST(ch) (MPPHY_CHAN_BASE(ch) + MPPHY_PXTEST_OFFSET)
+#define MPPHY_PXRXCNT(ch) (MPPHY_CHAN_BASE(ch) + MPPHY_RXCNT_OFFSET)
+#define MPPHY_PXSRAMCNT(ch) (MPPHY_CHAN_BASE(ch) + MPPHY_SRAMCNT_OFFSET)
+#define MPPHY_PXREFCLK(ch) (MPPHY_CHAN_BASE(ch) + MPPHY_REFCLK_OFFSET)
+#define MPPHY_PXCNTXT1(ch) (MPPHY_CHAN_BASE(ch) + MPPHY_CNTXT1_OFFSET)
+#define MPPHY_PXCNTXT2(ch) (MPPHY_CHAN_BASE(ch) + MPPHY_CNTXT2_OFFSET)
+#define MPPHY_PXTXREQ(ch) (MPPHY_CHAN_BASE(ch) + MPPHY_TXREQ_OFFSET)
+
+/* Channel enable bit masks for MPPHY_CMNCNT1 register */
+#define MPPHY_CMNCNT1_CH_MASK(ch) (0xFF << ((ch) * 8))
+
+/* Channel enable bits for MPPHY_CMNCNT1 register */
+#define MPPHY_CMNCNT1_CH_EN(ch) ((ch) == 0 ? BIT(1) : BIT((ch) * 8))
+
+/* PCS0REG5 register mask and values for each channel */
+#define MPPHY_PCS0REG5_CH(ch) (0x03 << (24 + (ch) * 2))
+
+/* PCS0REG1 register bits */
+#define MPPHY_PCS0REG1_VAL 0x00010000
+
+/* PXTEST register bit */
+#define MPPHY_PXTEST_BIT BIT(0)
+
+/* PXRXCNT register reset value */
+#define MPPHY_PXRXCNT_RESET_VAL 0x202
+
+/* PXSRAMCNT register bits */
+#define MPPHY_PXSRAMCNT_BYPASS BIT(0)
+#define MPPHY_PXSRAMCNT_BIT3 BIT(3)
+#define BOOTLOAD_BYPASS_MODE 0x3
+#define SRAM_BYPASS_MODE 0xc
+#define SRAM_EXT_LD_DONE 0x10
+#define SRAM_INIT_DONE 0x20
+
+#define SRAM_CONTROL_SET_BIT \
+ (BOOTLOAD_BYPASS_MODE | SRAM_BYPASS_MODE | \
+ SRAM_EXT_LD_DONE | SRAM_INIT_DONE)
+
+/* CMNCNT1/2 clock settings */
+#define MPPHY_CMNCNT2_CLK_CH(ch) (0x30003 << ((ch) * 4))
+
+/* PXREFCLK register value */
+#define MPPHY_PXREFCLK_VAL 0x35
+
+/* PXTXREQ register value */
+#define MPPHY_PXTXREQ_VAL 0x8
+
+/* Context settings */
+#define MPPHY_CNTXT1_VALUE 0x02010002
+#define MPPHY_CNTXT2_VALUE 0x02020202 /* For channels 1-3 */
+#define MPPHY_CNTXT2_CH0_VALUE 0x02020201 /* Special for channel 0 */
+
+/* struct mpphy_priv - Private data for the MPPHY driver */
+struct mp_phy_priv {
+ struct phy *phy;
+ struct clk_bulk clks;
+ struct reset_ctl_bulk resets;
+ void __iomem *base;
+};
+
+static int mp_phy_init(struct phy *phy)
+{
+ struct mp_phy_priv *priv = dev_get_priv(phy->dev);
+
+ if (phy->id > 3) {
+ printf("Invalid channel ID: %ld\n", phy->id);
+ return -EINVAL;
+ }
+
+ clrsetbits_le32(priv->base + MPPHY_CMNCNT1, MPPHY_CMNCNT1_CH_MASK(phy->id),
+ MPPHY_CMNCNT1_CH_EN(phy->id));
+ setbits_le32(priv->base + MPPHY_PCS0REG5, MPPHY_PCS0REG5_CH(phy->id));
+ setbits_le32(priv->base + MPPHY_PCS0REG1, MPPHY_PCS0REG1_VAL);
+ setbits_le32(priv->base + MPPHY_PXTEST(phy->id), MPPHY_PXTEST_BIT);
+ clrbits_le32(priv->base + MPPHY_PCS0REG5, MPPHY_PCS0REG5_CH(phy->id));
+ clrbits_le32(priv->base + MPPHY_PCS0REG1, MPPHY_PCS0REG1_VAL);
+ clrbits_le32(priv->base + MPPHY_PXTEST(phy->id), MPPHY_PXTEST_BIT);
+
+ /* Set PHY RX/TX reset and SRAM bypass mode */
+ writel(MPPHY_PXRXCNT_RESET_VAL, priv->base + MPPHY_PXRXCNT(phy->id));
+ writel(MPPHY_PXSRAMCNT_BYPASS, priv->base + MPPHY_PXSRAMCNT(phy->id));
+ setbits_le32(priv->base + MPPHY_PXSRAMCNT(phy->id), MPPHY_PXSRAMCNT_BIT3);
+
+ /* Clock supply settings */
+ setbits_le32(priv->base + MPPHY_CMNCNT2, MPPHY_CMNCNT2_CLK_CH(phy->id));
+
+ setbits_le32(priv->base + MPPHY_PXREFCLK(phy->id), MPPHY_PXREFCLK_VAL);
+
+ /* Release PHY RX/TX reset */
+ writel(0x0, priv->base + MPPHY_PXRXCNT(phy->id));
+
+ /* Setting Context Restore Registers and select PHY2/PHY3 protocol */
+ writel(MPPHY_CNTXT1_VALUE, priv->base + MPPHY_PXCNTXT1(phy->id));
+ writel((phy->id == 0) ? MPPHY_CNTXT2_CH0_VALUE : MPPHY_CNTXT2_VALUE,
+ priv->base + MPPHY_PXCNTXT2(phy->id));
+ writel(MPPHY_PXTXREQ_VAL, priv->base + MPPHY_PXTXREQ(phy->id));
+
+ return 0;
+}
+
+static int mp_phy_late_init(struct phy *phy)
+{
+ struct mp_phy_priv *priv = dev_get_priv(phy->dev);
+
+ writel(SRAM_CONTROL_SET_BIT, priv->base + MPPHY_PXSRAMCNT(phy->id));
+
+ return 0;
+}
+
+static int mp_phy_set_mode(struct phy *phy, enum phy_mode mode, int submode)
+{
+ /* Current source code only supports Ethernet */
+ if (mode != PHY_MODE_ETHERNET)
+ return -EOPNOTSUPP;
+
+ return 0;
+}
+
+static int mp_phy_of_xlate(struct phy *phy, struct ofnode_phandle_args *args)
+{
+ if (args->args_count > 2)
+ return -EINVAL;
+
+ /* Set channel ID from first argument if available */
+ if (args->args_count)
+ phy->id = args->args[0];
+ else
+ phy->id = 0;
+
+ return 0;
+}
+
+static const struct phy_ops mp_phy_ops = {
+ .init = mp_phy_init,
+ .power_on = mp_phy_late_init,
+ .set_mode = mp_phy_set_mode,
+ .of_xlate = mp_phy_of_xlate,
+};
+
+static int mp_phy_probe(struct udevice *dev)
+{
+ struct mp_phy_priv *priv = dev_get_priv(dev);
+ int ret;
+
+ /* Get base address from device tree */
+ priv->base = dev_read_addr_ptr(dev);
+ if (!priv->base)
+ return -EINVAL;
+
+ ret = clk_get_bulk(dev, &priv->clks);
+ if (ret < 0)
+ return ret;
+
+ ret = clk_enable_bulk(&priv->clks);
+ if (ret)
+ goto err_clk_enable;
+
+ ret = reset_get_bulk(dev, &priv->resets);
+ if (ret)
+ goto err_reset_get;
+
+ ret = reset_assert_bulk(&priv->resets);
+ if (ret)
+ goto err_reset_assert;
+
+ ret = reset_deassert_bulk(&priv->resets);
+ if (ret)
+ goto err_reset_assert;
+
+ return 0;
+
+err_reset_assert:
+ reset_release_bulk(&priv->resets);
+err_reset_get:
+ clk_disable_bulk(&priv->clks);
+err_clk_enable:
+ clk_release_bulk(&priv->clks);
+ return ret;
+}
+
+static const struct udevice_id mp_phy_ids[] = {
+ { .compatible = "renesas,r8a78000-multi-protocol-phy" },
+ { }
+};
+
+U_BOOT_DRIVER(renesas_mpphy) = {
+ .name = "renesas_mpphy",
+ .id = UCLASS_PHY,
+ .of_match = mp_phy_ids,
+ .probe = mp_phy_probe,
+ .ops = &mp_phy_ops,
+ .priv_auto = sizeof(struct mp_phy_priv),
+};
diff --git a/drivers/phy/ti/phy-j721e-wiz.c b/drivers/phy/ti/phy-j721e-wiz.c
index 6e2d4bc2b05..eaf68d18f3a 100644
--- a/drivers/phy/ti/phy-j721e-wiz.c
+++ b/drivers/phy/ti/phy-j721e-wiz.c
@@ -1180,6 +1180,7 @@ static int j721e_wiz_probe(struct udevice *dev)
ofnode node;
struct regmap *regmap;
u32 num_lanes;
+ bool already_configured = false;
node = get_child_by_name(dev, "serdes");
@@ -1243,15 +1244,6 @@ static int j721e_wiz_probe(struct udevice *dev)
goto err_addr_to_resource;
}
- for (i = 0; i < wiz->num_lanes; i++) {
- regmap_field_read(wiz->p_enable[i], &val);
- if (val & (P_ENABLE | P_ENABLE_FORCE)) {
- dev_err(dev, "SERDES already configured\n");
- rc = -EBUSY;
- goto err_addr_to_resource;
- }
- }
-
rc = j721e_wiz_bind_of_clocks(wiz);
if (rc) {
dev_err(dev, "Failed to bind clocks\n");
@@ -1270,10 +1262,21 @@ static int j721e_wiz_probe(struct udevice *dev)
goto err_addr_to_resource;
}
- rc = wiz_init(wiz);
- if (rc) {
- dev_err(dev, "WIZ initialization failed\n");
- goto err_addr_to_resource;
+ for (i = 0; i < wiz->num_lanes; i++) {
+ regmap_field_read(wiz->p_enable[i], &val);
+ if (val & (P_ENABLE | P_ENABLE_FORCE)) {
+ dev_info(dev, "SERDES already configured, skipping wiz initialization\n");
+ already_configured = true;
+ break;
+ }
+ }
+
+ if (!already_configured) {
+ rc = wiz_init(wiz);
+ if (rc) {
+ dev_err(dev, "WIZ initialization failed\n");
+ goto err_addr_to_resource;
+ }
}
return 0;