diff mbox series

[net-next,17/20] net: ethernet: qualcomm: Add PPE UNIPHY support for phylink

Message ID 20240110114033.32575-18-quic_luoj@quicinc.com
State New
Headers show
Series net: ethernet: Add qcom PPE driver | expand

Commit Message

Luo Jie Jan. 10, 2024, 11:40 a.m. UTC
From: Lei Wei <quic_leiwei@quicinc.com>

This driver adds support for PPE UNIPHY initialization and UNIPHY
PCS operations which used by phylink.

PPE supports maximum 6 GMAC or XGMAC ports which can be connected
with maximum 3 UNIPHYs. The UNIPHY registers and provides raw clock
to feeds NCCSS clocks to provide different clocks to PPE ports in
different link speed.

Signed-off-by: Lei Wei <quic_leiwei@quicinc.com>
Signed-off-by: Luo Jie <quic_luoj@quicinc.com>
---
 drivers/net/ethernet/qualcomm/ppe/Makefile    |   2 +-
 drivers/net/ethernet/qualcomm/ppe/ppe.c       |  25 +
 drivers/net/ethernet/qualcomm/ppe/ppe.h       |   2 +
 .../net/ethernet/qualcomm/ppe/ppe_uniphy.c    | 789 ++++++++++++++++++
 .../net/ethernet/qualcomm/ppe/ppe_uniphy.h    | 227 +++++
 include/linux/soc/qcom/ppe.h                  |   1 +
 6 files changed, 1045 insertions(+), 1 deletion(-)
 create mode 100644 drivers/net/ethernet/qualcomm/ppe/ppe_uniphy.c
 create mode 100644 drivers/net/ethernet/qualcomm/ppe/ppe_uniphy.h
diff mbox series

Patch

diff --git a/drivers/net/ethernet/qualcomm/ppe/Makefile b/drivers/net/ethernet/qualcomm/ppe/Makefile
index 516ea23443ab..487f62d5e38c 100644
--- a/drivers/net/ethernet/qualcomm/ppe/Makefile
+++ b/drivers/net/ethernet/qualcomm/ppe/Makefile
@@ -4,4 +4,4 @@ 
 #
 
 obj-$(CONFIG_QCOM_PPE) += qcom-ppe.o
-qcom-ppe-objs := ppe.o ppe_ops.o ppe_debugfs.o
+qcom-ppe-objs := ppe.o ppe_ops.o ppe_debugfs.o ppe_uniphy.o
diff --git a/drivers/net/ethernet/qualcomm/ppe/ppe.c b/drivers/net/ethernet/qualcomm/ppe/ppe.c
index 04f80589c05b..21040efe71fc 100644
--- a/drivers/net/ethernet/qualcomm/ppe/ppe.c
+++ b/drivers/net/ethernet/qualcomm/ppe/ppe.c
@@ -18,6 +18,7 @@ 
 #include "ppe_regs.h"
 #include "ppe_ops.h"
 #include "ppe_debugfs.h"
+#include "ppe_uniphy.h"
 
 #define PPE_SCHEDULER_PORT_NUM		8
 #define MPPE_SCHEDULER_PORT_NUM		3
@@ -176,6 +177,26 @@  int ppe_type_get(struct ppe_device *ppe_dev)
 	return ppe_dev_priv->ppe_type;
 }
 
+struct clk **ppe_clock_get(struct ppe_device *ppe_dev)
+{
+	struct ppe_data *ppe_dev_priv = ppe_dev->ppe_priv;
+
+	if (!ppe_dev_priv)
+		return NULL;
+
+	return ppe_dev_priv->clk;
+}
+
+struct reset_control **ppe_reset_get(struct ppe_device *ppe_dev)
+{
+	struct ppe_data *ppe_dev_priv = ppe_dev->ppe_priv;
+
+	if (!ppe_dev_priv)
+		return NULL;
+
+	return ppe_dev_priv->rst;
+}
+
 static int ppe_clock_set_enable(struct ppe_device *ppe_dev,
 				enum ppe_clk_id clk_id, unsigned long rate)
 {
@@ -1405,6 +1426,10 @@  static int qcom_ppe_probe(struct platform_device *pdev)
 				     ret,
 				     "ppe device hw init failed\n");
 
+	ppe_dev->uniphy = ppe_uniphy_setup(pdev);
+	if (IS_ERR(ppe_dev->uniphy))
+		return dev_err_probe(&pdev->dev, ret, "ppe uniphy initialization failed\n");
+
 	ppe_dev->ppe_ops = &qcom_ppe_ops;
 	ppe_dev->is_ppe_probed = true;
 	ppe_debugfs_setup(ppe_dev);
diff --git a/drivers/net/ethernet/qualcomm/ppe/ppe.h b/drivers/net/ethernet/qualcomm/ppe/ppe.h
index 828d467540c9..45b70f47cd21 100644
--- a/drivers/net/ethernet/qualcomm/ppe/ppe.h
+++ b/drivers/net/ethernet/qualcomm/ppe/ppe.h
@@ -173,6 +173,8 @@  struct ppe_scheduler_port_resource {
 };
 
 int ppe_type_get(struct ppe_device *ppe_dev);
+struct clk **ppe_clock_get(struct ppe_device *ppe_dev);
+struct reset_control **ppe_reset_get(struct ppe_device *ppe_dev);
 
 int ppe_write(struct ppe_device *ppe_dev, u32 reg, unsigned int val);
 int ppe_read(struct ppe_device *ppe_dev, u32 reg, unsigned int *val);
diff --git a/drivers/net/ethernet/qualcomm/ppe/ppe_uniphy.c b/drivers/net/ethernet/qualcomm/ppe/ppe_uniphy.c
new file mode 100644
index 000000000000..3a2b6fc77a9c
--- /dev/null
+++ b/drivers/net/ethernet/qualcomm/ppe/ppe_uniphy.c
@@ -0,0 +1,789 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+/* PPE UNIPHY clock register and UNIPHY PCS operations for phylink.
+ *
+ * The PPE UNIPHY block is specifically used by PPE to connect the PPE MAC
+ * with the external PHYs or SFPs or Switches (fixed link). The PPE UNIPHY
+ * block includes serdes, PCS or XPCS and the control logic to support PPE
+ * ports to work in different interface mode and different link speed.
+ *
+ * The PPE UNIPHY block provides raw clock as the parent clock to NSSCC
+ * clocks and the NSSCC clocks can be configured to generate different
+ * port Tx and Rx clocks to PPE ports in different port link speed.
+ */
+
+#include <linux/clk.h>
+#include <linux/reset.h>
+#include <linux/clk-provider.h>
+#include <linux/soc/qcom/ppe.h>
+#include "ppe.h"
+#include "ppe_uniphy.h"
+
+/* UNIPHY clock direction */
+enum {
+	UNIPHY_RX = 0,
+	UNIPHY_TX,
+};
+
+/* UNIPHY clock data type */
+struct clk_uniphy {
+	struct clk_hw hw;
+	u8 index;
+	u8 dir;
+	unsigned long rate;
+};
+
+#define to_clk_uniphy(_hw)		container_of(_hw, struct clk_uniphy, hw)
+/* UNIPHY clock rate */
+#define UNIPHY_CLK_RATE_125M		125000000
+#define UNIPHY_CLK_RATE_312P5M		312500000
+
+static void ppe_uniphy_write(struct ppe_uniphy *uniphy, u32 val, u32 reg)
+{
+	if (reg >= UNIPHY_INDIRECT_ADDR_START) {
+		writel(FIELD_GET(UNIPHY_INDIRECT_ADDR_HIGH, reg),
+		       uniphy->base + UNIPHY_INDIRECT_AHB_ADDR);
+		writel(val, uniphy->base + UNIPHY_INDIRECT_DATA_ADDR(reg));
+	} else {
+		writel(val, uniphy->base + reg);
+	}
+}
+
+static u32 ppe_uniphy_read(struct ppe_uniphy *uniphy, u32 reg)
+{
+	if (reg >= UNIPHY_INDIRECT_ADDR_START) {
+		writel(FIELD_GET(UNIPHY_INDIRECT_ADDR_HIGH, reg),
+		       uniphy->base + UNIPHY_INDIRECT_AHB_ADDR);
+		return readl(uniphy->base + UNIPHY_INDIRECT_DATA_ADDR(reg));
+	} else {
+		return readl(uniphy->base + reg);
+	}
+}
+
+static int ppe_uniphy_mask(struct ppe_uniphy *uniphy, u32 reg, u32 mask, u32 set)
+{
+	u32 val;
+
+	val = ppe_uniphy_read(uniphy, reg);
+	val &= ~mask;
+	val |= set;
+	ppe_uniphy_write(uniphy, val, reg);
+
+	return 0;
+}
+
+static unsigned long clk_uniphy_recalc_rate(struct clk_hw *hw,
+					    unsigned long parent_rate)
+{
+	struct clk_uniphy *uniphy = to_clk_uniphy(hw);
+
+	return uniphy->rate;
+}
+
+static int clk_uniphy_determine_rate(struct clk_hw *hw,
+				     struct clk_rate_request *req)
+{
+	if (req->rate <= UNIPHY_CLK_RATE_125M)
+		req->rate = UNIPHY_CLK_RATE_125M;
+	else
+		req->rate = UNIPHY_CLK_RATE_312P5M;
+
+	return 0;
+}
+
+static int clk_uniphy_set_rate(struct clk_hw *hw, unsigned long rate,
+			       unsigned long parent_rate)
+{
+	struct clk_uniphy *uniphy = to_clk_uniphy(hw);
+
+	if (rate != UNIPHY_CLK_RATE_125M && rate != UNIPHY_CLK_RATE_312P5M)
+		return -1;
+
+	uniphy->rate = rate;
+
+	return 0;
+}
+
+static const struct clk_ops clk_uniphy_ops = {
+	.recalc_rate = clk_uniphy_recalc_rate,
+	.determine_rate = clk_uniphy_determine_rate,
+	.set_rate = clk_uniphy_set_rate,
+};
+
+static struct clk_uniphy uniphy0_gcc_rx_clk = {
+	.hw.init = &(struct clk_init_data){
+		.name = "uniphy0_gcc_rx_clk",
+		.ops = &clk_uniphy_ops,
+	},
+	.index = 0,
+	.dir = UNIPHY_RX,
+	.rate = UNIPHY_CLK_RATE_125M,
+};
+
+static struct clk_uniphy uniphy0_gcc_tx_clk = {
+	.hw.init = &(struct clk_init_data){
+		.name = "uniphy0_gcc_tx_clk",
+		.ops = &clk_uniphy_ops,
+	},
+	.index = 0,
+	.dir = UNIPHY_TX,
+	.rate = UNIPHY_CLK_RATE_125M,
+};
+
+static struct clk_uniphy uniphy1_gcc_rx_clk = {
+	.hw.init = &(struct clk_init_data){
+		.name = "uniphy1_gcc_rx_clk",
+		.ops = &clk_uniphy_ops,
+	},
+	.index = 1,
+	.dir = UNIPHY_RX,
+	.rate = UNIPHY_CLK_RATE_312P5M,
+};
+
+static struct clk_uniphy uniphy1_gcc_tx_clk = {
+	.hw.init = &(struct clk_init_data){
+		.name = "uniphy1_gcc_tx_clk",
+		.ops = &clk_uniphy_ops,
+	},
+	.index = 1,
+	.dir = UNIPHY_TX,
+	.rate = UNIPHY_CLK_RATE_312P5M,
+};
+
+static struct clk_uniphy uniphy2_gcc_rx_clk = {
+	.hw.init = &(struct clk_init_data){
+		.name = "uniphy2_gcc_rx_clk",
+		.ops = &clk_uniphy_ops,
+	},
+	.index = 2,
+	.dir = UNIPHY_RX,
+	.rate = UNIPHY_CLK_RATE_312P5M,
+};
+
+static struct clk_uniphy uniphy2_gcc_tx_clk = {
+	.hw.init = &(struct clk_init_data){
+		.name = "uniphy2_gcc_tx_clk",
+		.ops = &clk_uniphy_ops,
+	},
+	.index = 2,
+	.dir = UNIPHY_TX,
+	.rate = UNIPHY_CLK_RATE_312P5M,
+};
+
+static struct clk_hw *uniphy_raw_clks[] = {
+	&uniphy0_gcc_rx_clk.hw, &uniphy0_gcc_tx_clk.hw,
+	&uniphy1_gcc_rx_clk.hw, &uniphy1_gcc_tx_clk.hw,
+	&uniphy2_gcc_rx_clk.hw, &uniphy2_gcc_tx_clk.hw,
+};
+
+int ppe_uniphy_port_gcc_clock_en_set(struct ppe_uniphy *uniphy, int port, bool enable)
+{
+	struct clk **clock = ppe_clock_get(uniphy->ppe_dev);
+	enum ppe_clk_id rx_id, tx_id;
+	int err = 0;
+
+	rx_id = PPE_UNIPHY_PORT1_RX_CLK + ((port - 1) << 1);
+	tx_id = PPE_UNIPHY_PORT1_TX_CLK + ((port - 1) << 1);
+
+	if (enable) {
+		if (!IS_ERR(clock[rx_id])) {
+			err = clk_prepare_enable(clock[rx_id]);
+			if (err) {
+				dev_err(uniphy->ppe_dev->dev,
+					"Failed to enable uniphy port %d rx_clk(%d)\n",
+					port, rx_id);
+				return err;
+			}
+		}
+
+		if (!IS_ERR(clock[tx_id])) {
+			err = clk_prepare_enable(clock[tx_id]);
+			if (err) {
+				dev_err(uniphy->ppe_dev->dev,
+					"Failed to enable uniphy port %d tx_clk(%d)\n",
+					port, tx_id);
+				return err;
+			}
+		}
+	} else {
+		clk_disable_unprepare(clock[rx_id]);
+		clk_disable_unprepare(clock[tx_id]);
+	}
+
+	return 0;
+}
+
+static int ppe_uniphy_interface_gcc_clock_en_set(struct ppe_uniphy *uniphy, bool enable)
+{
+	int ppe_type = ppe_type_get(uniphy->ppe_dev);
+	int port = 0;
+
+	switch (uniphy->index) {
+	case 2:
+		ppe_uniphy_port_gcc_clock_en_set(uniphy, PPE_PORT6, enable);
+		break;
+	case 1:
+		if (ppe_type == PPE_TYPE_APPE)
+			ppe_uniphy_port_gcc_clock_en_set(uniphy, PPE_PORT5, enable);
+		else if (ppe_type == PPE_TYPE_MPPE)
+			ppe_uniphy_port_gcc_clock_en_set(uniphy, PPE_PORT2, enable);
+		break;
+	case 0:
+		if (ppe_type == PPE_TYPE_APPE) {
+			for (port = PPE_PORT1; port <= PPE_PORT4; port++)
+				ppe_uniphy_port_gcc_clock_en_set(uniphy, port, enable);
+		} else if (ppe_type == PPE_TYPE_MPPE) {
+			ppe_uniphy_port_gcc_clock_en_set(uniphy, PPE_PORT1, enable);
+		}
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static int ppe_uniphy_gcc_xpcs_reset(struct ppe_uniphy *uniphy, bool enable)
+{
+	struct reset_control **reset = ppe_reset_get(uniphy->ppe_dev);
+	enum ppe_rst_id id = PPE_UNIPHY0_XPCS_RST + uniphy->index;
+
+	if (IS_ERR(reset[id]))
+		return PTR_ERR(reset[id]);
+
+	if (enable)
+		return reset_control_assert(reset[id]);
+	else
+		return reset_control_deassert(reset[id]);
+}
+
+static int ppe_uniphy_gcc_software_reset(struct ppe_uniphy *uniphy)
+{
+	struct reset_control **reset = ppe_reset_get(uniphy->ppe_dev);
+	int ppe_type = ppe_type_get(uniphy->ppe_dev);
+	unsigned int index = uniphy->index;
+	int err = 0, port = 0;
+
+	/* Assert uniphy sys reset control */
+	if (!IS_ERR(reset[PPE_UNIPHY0_SYS_RST + index])) {
+		err = reset_control_assert(reset[PPE_UNIPHY0_SYS_RST + index]);
+		if (err)
+			return err;
+	}
+
+	/* Assert uniphy port reset control */
+	switch (ppe_type) {
+	case PPE_TYPE_APPE:
+		if (index == 0) {
+			for (port = PPE_PORT1; port <= PPE_PORT4; port++) {
+				if (!IS_ERR(reset[PPE_UNIPHY_PORT1_DIS + port - 1])) {
+					err = reset_control_assert(reset[PPE_UNIPHY_PORT1_DIS +
+								   port - 1]);
+					if (err)
+						return err;
+				}
+			}
+		} else {
+			if (!IS_ERR(reset[PPE_UNIPHY0_SOFT_RST + index])) {
+				err = reset_control_assert(reset[PPE_UNIPHY0_SOFT_RST + index]);
+				if (err)
+					return err;
+			}
+		}
+		break;
+	case PPE_TYPE_MPPE:
+		if (!IS_ERR(reset[PPE_UNIPHY_PORT1_RX_RST + (index << 1)])) {
+			err = reset_control_assert(reset[PPE_UNIPHY_PORT1_RX_RST + (index << 1)]);
+			if (err)
+				return err;
+		}
+
+		if (!IS_ERR(reset[PPE_UNIPHY_PORT1_TX_RST + (index << 1)])) {
+			err = reset_control_assert(reset[PPE_UNIPHY_PORT1_TX_RST + (index << 1)]);
+			if (err)
+				return err;
+		}
+		break;
+	default:
+		break;
+	}
+	fsleep(100000);
+
+	/* Deassert uniphy sys reset control */
+	if (!IS_ERR(reset[PPE_UNIPHY0_SYS_RST + index])) {
+		err = reset_control_deassert(reset[PPE_UNIPHY0_SYS_RST + index]);
+		if (err)
+			return err;
+	}
+
+	/* Deassert uniphy port reset control */
+	switch (ppe_type) {
+	case PPE_TYPE_APPE:
+		if (index == 0) {
+			for (port = PPE_PORT1; port <= PPE_PORT4; port++) {
+				if (!IS_ERR(reset[PPE_UNIPHY_PORT1_DIS + port - 1])) {
+					err = reset_control_deassert(reset[PPE_UNIPHY_PORT1_DIS +
+								     port - 1]);
+					if (err)
+						return err;
+				}
+			}
+		} else {
+			if (!IS_ERR(reset[PPE_UNIPHY0_SOFT_RST + index])) {
+				err = reset_control_deassert(reset[PPE_UNIPHY0_SOFT_RST + index]);
+				if (err)
+					return err;
+			}
+		}
+		break;
+	case PPE_TYPE_MPPE:
+		if (!IS_ERR(reset[PPE_UNIPHY_PORT1_RX_RST + (index << 1)])) {
+			err = reset_control_deassert(reset[PPE_UNIPHY_PORT1_RX_RST + (index << 1)]);
+			if (err)
+				return err;
+		}
+
+		if (!IS_ERR(reset[PPE_UNIPHY_PORT1_TX_RST + (index << 1)])) {
+			err = reset_control_deassert(reset[PPE_UNIPHY_PORT1_TX_RST + (index << 1)]);
+			if (err)
+				return err;
+		}
+		break;
+	default:
+		break;
+	}
+	fsleep(100000);
+
+	return err;
+}
+
+int ppe_uniphy_autoneg_complete_check(struct ppe_uniphy *uniphy, int port)
+{
+	u32 reg, val;
+	int channel, ret;
+
+	if (uniphy->interface == PHY_INTERFACE_MODE_USXGMII ||
+	    uniphy->interface == PHY_INTERFACE_MODE_QUSGMII) {
+		/* Only uniphy0 may have multi channels */
+		channel = (uniphy->index == 0) ? (port - 1) : 0;
+		reg = (channel == 0) ? VR_MII_AN_INTR_STS_ADDR :
+		       VR_MII_AN_INTR_STS_CHANNEL_ADDR(channel);
+
+		/* Wait auto negotiation complete */
+		ret = read_poll_timeout(ppe_uniphy_read, val,
+					(val & CL37_ANCMPLT_INTR),
+					1000, 100000, true,
+					uniphy, reg);
+		if (ret) {
+			dev_err(uniphy->ppe_dev->dev,
+				"uniphy %d auto negotiation timeout\n", uniphy->index);
+			return ret;
+		}
+
+		/* Clear auto negotiation complete interrupt */
+		ppe_uniphy_mask(uniphy, reg, CL37_ANCMPLT_INTR, 0);
+	}
+
+	return 0;
+}
+
+int ppe_uniphy_speed_set(struct ppe_uniphy *uniphy, int port, int speed)
+{
+	u32 reg, val;
+	int channel;
+
+	if (uniphy->interface == PHY_INTERFACE_MODE_USXGMII ||
+	    uniphy->interface == PHY_INTERFACE_MODE_QUSGMII) {
+		/* Only uniphy0 may have multiple channels */
+		channel = (uniphy->index == 0) ? (port - 1) : 0;
+
+		reg = (channel == 0) ? SR_MII_CTRL_ADDR :
+		       SR_MII_CTRL_CHANNEL_ADDR(channel);
+
+		switch (speed) {
+		case SPEED_100:
+			val = USXGMII_SPEED_100;
+			break;
+		case SPEED_1000:
+			val = USXGMII_SPEED_1000;
+			break;
+		case SPEED_2500:
+			val = USXGMII_SPEED_2500;
+			break;
+		case SPEED_5000:
+			val = USXGMII_SPEED_5000;
+			break;
+		case SPEED_10000:
+			val = USXGMII_SPEED_10000;
+			break;
+		case SPEED_10:
+			val = USXGMII_SPEED_10;
+			break;
+		default:
+			val = 0;
+			break;
+		}
+
+		ppe_uniphy_mask(uniphy, reg, USXGMII_SPEED_MASK, val);
+	}
+
+	return 0;
+}
+
+int ppe_uniphy_duplex_set(struct ppe_uniphy *uniphy, int port, int duplex)
+{
+	u32 reg;
+	int channel;
+
+	if (uniphy->interface == PHY_INTERFACE_MODE_USXGMII &&
+	    uniphy->interface == PHY_INTERFACE_MODE_QUSGMII) {
+		/* Only uniphy0 may have multiple channels */
+		channel = (uniphy->index == 0) ? (port - 1) : 0;
+
+		reg = (channel == 0) ? SR_MII_CTRL_ADDR :
+		       SR_MII_CTRL_CHANNEL_ADDR(channel);
+
+		ppe_uniphy_mask(uniphy, reg, USXGMII_DUPLEX_FULL,
+				(duplex == DUPLEX_FULL) ? USXGMII_DUPLEX_FULL : 0);
+	}
+
+	return 0;
+}
+
+int ppe_uniphy_adapter_reset(struct ppe_uniphy *uniphy, int port)
+{
+	int channel;
+
+	/* Only uniphy0 may have multiple channels */
+	channel = (uniphy->index == 0) ? (port - 1) : 0;
+
+	switch (uniphy->interface) {
+	case PHY_INTERFACE_MODE_USXGMII:
+	case PHY_INTERFACE_MODE_QUSGMII:
+		if (channel == 0)
+			ppe_uniphy_mask(uniphy,
+					VR_XS_PCS_DIG_CTRL1_ADDR,
+					USRA_RST, USRA_RST);
+		else
+			ppe_uniphy_mask(uniphy,
+					VR_MII_DIG_CTRL1_CHANNEL_ADDR(channel),
+					CHANNEL_USRA_RST, CHANNEL_USRA_RST);
+		break;
+	case PHY_INTERFACE_MODE_SGMII:
+	case PHY_INTERFACE_MODE_1000BASEX:
+	case PHY_INTERFACE_MODE_2500BASEX:
+	case PHY_INTERFACE_MODE_QSGMII:
+		ppe_uniphy_mask(uniphy,
+				UNIPHY_CHANNEL_INPUT_OUTPUT_4_ADDR(channel),
+				NEWADDEDFROMHERE_CH_ADP_SW_RSTN, 0);
+		ppe_uniphy_mask(uniphy,
+				UNIPHY_CHANNEL_INPUT_OUTPUT_4_ADDR(channel),
+				NEWADDEDFROMHERE_CH_ADP_SW_RSTN,
+				NEWADDEDFROMHERE_CH_ADP_SW_RSTN);
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static int ppe_pcs_config(struct phylink_pcs *pcs, unsigned int mode,
+			  phy_interface_t interface,
+			  const unsigned long *advertising,
+			  bool permit_pause_to_mac)
+{
+	struct ppe_uniphy *uniphy = pcs_to_ppe_uniphy(pcs);
+	unsigned long rate = 0;
+	int ret, channel = 0;
+	u32 val = 0;
+
+	if (uniphy->interface == interface)
+		return 0;
+
+	uniphy->interface = interface;
+
+	/* Disable gcc uniphy interface clock */
+	ppe_uniphy_interface_gcc_clock_en_set(uniphy, false);
+
+	/* Assert gcc uniphy xpcs reset control */
+	ppe_uniphy_gcc_xpcs_reset(uniphy, true);
+
+	/* Configure uniphy mode */
+	switch (interface) {
+	case PHY_INTERFACE_MODE_USXGMII:
+	case PHY_INTERFACE_MODE_10GBASER:
+	case PHY_INTERFACE_MODE_QUSGMII:
+		rate = UNIPHY_CLK_RATE_312P5M;
+		ppe_uniphy_mask(uniphy, UNIPHY_MODE_CTRL_ADDR,
+				USXGMII_MODE_CTRL_MASK, USXGMII_MODE_CTRL);
+		break;
+	case PHY_INTERFACE_MODE_2500BASEX:
+		rate = UNIPHY_CLK_RATE_312P5M;
+		ppe_uniphy_mask(uniphy, UNIPHY_MODE_CTRL_ADDR,
+				SGMIIPLUS_MODE_CTRL_MASK, SGMIIPLUS_MODE_CTRL);
+		break;
+	case PHY_INTERFACE_MODE_SGMII:
+	case PHY_INTERFACE_MODE_1000BASEX:
+		rate = UNIPHY_CLK_RATE_125M;
+		ppe_uniphy_mask(uniphy, UNIPHY_MODE_CTRL_ADDR,
+				SGMII_MODE_CTRL_MASK, SGMII_MODE_CTRL);
+		break;
+	case PHY_INTERFACE_MODE_QSGMII:
+		rate = UNIPHY_CLK_RATE_125M;
+		ppe_uniphy_mask(uniphy, UNIPHY_MODE_CTRL_ADDR,
+				QSGMII_MODE_CTRL_MASK, QSGMII_MODE_CTRL);
+		break;
+	default:
+		break;
+	}
+
+	if (interface == PHY_INTERFACE_MODE_QUSGMII)
+		ppe_uniphy_mask(uniphy, UNIPHY_QP_USXG_OPITON1_ADDR,
+				GMII_SRC_SEL, GMII_SRC_SEL);
+
+	if (interface == PHY_INTERFACE_MODE_10GBASER)
+		ppe_uniphy_mask(uniphy, UNIPHY_LINK_DETECT_ADDR,
+				DETECT_LOS_FROM_SFP, UNIPHY_10GR_LINK_LOSS);
+
+	/* Reset uniphy gcc software reset control */
+	ppe_uniphy_gcc_software_reset(uniphy);
+
+	/* Wait uniphy calibration completion */
+	ret = read_poll_timeout(ppe_uniphy_read, val,
+				(val & MMD1_REG_CALIBRATION_DONE_REG),
+				1000, 100000, true,
+				uniphy, UNIPHY_OFFSET_CALIB_4_ADDR);
+	if (ret) {
+		dev_err(uniphy->ppe_dev->dev,
+			"uniphy %d calibration timeout\n", uniphy->index);
+		return ret;
+	}
+
+	/* Enable gcc uniphy interface clk */
+	ppe_uniphy_interface_gcc_clock_en_set(uniphy, true);
+
+	/* Deassert gcc uniphy xpcs reset control */
+	if (interface == PHY_INTERFACE_MODE_USXGMII ||
+	    interface == PHY_INTERFACE_MODE_10GBASER ||
+		interface == PHY_INTERFACE_MODE_QUSGMII)
+		ppe_uniphy_gcc_xpcs_reset(uniphy, false);
+
+	if (interface == PHY_INTERFACE_MODE_USXGMII ||
+	    interface == PHY_INTERFACE_MODE_QUSGMII) {
+		/* Wait 10gr link up */
+		ret = read_poll_timeout(ppe_uniphy_read, val,
+					(val & SR_XS_PCS_KR_STS1_PLU),
+					1000, 100000, true,
+					uniphy, SR_XS_PCS_KR_STS1_ADDR);
+		if (ret)
+			dev_warn(uniphy->ppe_dev->dev,
+				 "uniphy %d 10gr linkup timeout\n", uniphy->index);
+
+		/* Enable usxgmii */
+		ppe_uniphy_mask(uniphy, VR_XS_PCS_DIG_CTRL1_ADDR, USXGMII_EN, USXGMII_EN);
+
+		if (interface == PHY_INTERFACE_MODE_QUSGMII) {
+			/* XPCS set quxgmii mode */
+			ppe_uniphy_mask(uniphy, VR_XS_PCS_DIG_STS_ADDR, AM_COUNT, QUXGMII_AM_COUNT);
+			ppe_uniphy_mask(uniphy, VR_XS_PCS_KR_CTRL_ADDR, USXG_MODE, QUXGMII_MODE);
+			/* XPCS software reset */
+			ppe_uniphy_mask(uniphy, VR_XS_PCS_DIG_CTRL1_ADDR, VR_RST, VR_RST);
+		}
+
+		/* Enable autoneg complete interrupt and 10M/100M 8bit mii width */
+		ppe_uniphy_mask(uniphy, VR_MII_AN_CTRL_ADDR,
+				MII_AN_INTR_EN | MII_CTRL, MII_AN_INTR_EN | MII_CTRL);
+
+		if (interface == PHY_INTERFACE_MODE_QUSGMII) {
+			for (channel = 1; channel <= 3; channel++)
+				ppe_uniphy_mask(uniphy, VR_MII_AN_CTRL_CHANNEL_ADDR(channel),
+						MII_AN_INTR_EN | MII_CTRL,
+						MII_AN_INTR_EN | MII_CTRL);
+			/* Disable TICD */
+			ppe_uniphy_mask(uniphy, VR_XAUI_MODE_CTRL_ADDR, IPG_CHECK, IPG_CHECK);
+			for (channel = 1; channel <= 3; channel++)
+				ppe_uniphy_mask(uniphy, VR_XAUI_MODE_CTRL_CHANNEL_ADDR(channel),
+						IPG_CHECK, IPG_CHECK);
+		}
+
+		/* Enable autoneg ability and usxgmii 10g speed and full duplex */
+		ppe_uniphy_mask(uniphy, SR_MII_CTRL_ADDR,
+				USXGMII_SPEED_MASK | AN_ENABLE | USXGMII_DUPLEX_FULL,
+				USXGMII_SPEED_10000 | AN_ENABLE | USXGMII_DUPLEX_FULL);
+		if (interface == PHY_INTERFACE_MODE_QUSGMII) {
+			for (channel = 1; channel <= 3; channel++)
+				ppe_uniphy_mask(uniphy, SR_MII_CTRL_CHANNEL_ADDR(channel),
+						USXGMII_SPEED_MASK | AN_ENABLE |
+						USXGMII_DUPLEX_FULL,
+						USXGMII_SPEED_10000 | AN_ENABLE |
+						USXGMII_DUPLEX_FULL);
+
+			/* Enable eee transparent mode */
+			ppe_uniphy_mask(uniphy, VR_XS_PCS_EEE_MCTRL0_ADDR,
+					MULT_FACT_100NS | SIGN_BIT,
+					FIELD_PREP(MULT_FACT_100NS, 0x1) | SIGN_BIT);
+			ppe_uniphy_mask(uniphy, VR_XS_PCS_EEE_TXTIMER_ADDR,
+					TSL_RES | T1U_RES | TWL_RES,
+					UNIPHY_XPCS_TSL_TIMER |
+					UNIPHY_XPCS_T1U_TIMER | UNIPHY_XPCS_TWL_TIMER);
+			ppe_uniphy_mask(uniphy, VR_XS_PCS_EEE_RXTIMER_ADDR,
+					RES_100U | TWR_RES,
+					UNIPHY_XPCS_100US_TIMER | UNIPHY_XPCS_TWR_TIMER);
+
+			ppe_uniphy_mask(uniphy, VR_XS_PCS_EEE_MCTRL1_ADDR,
+					TRN_LPI | TRN_RXLPI, TRN_LPI | TRN_RXLPI);
+			ppe_uniphy_mask(uniphy, VR_XS_PCS_EEE_MCTRL0_ADDR,
+					LTX_EN | LRX_EN, LTX_EN | LRX_EN);
+		}
+	}
+
+	/* Set uniphy raw clk rate */
+	clk_set_rate(uniphy_raw_clks[(uniphy->index << 1) + UNIPHY_RX]->clk,
+		     rate);
+	clk_set_rate(uniphy_raw_clks[(uniphy->index << 1) + UNIPHY_TX]->clk,
+		     rate);
+
+	dev_info(uniphy->ppe_dev->dev,
+		 "ppe pcs config uniphy index %d, interface %s\n",
+		 uniphy->index, phy_modes(interface));
+
+	return 0;
+}
+
+static void ppe_pcs_get_state(struct phylink_pcs *pcs,
+			      struct phylink_link_state *state)
+{
+	struct ppe_uniphy *uniphy = pcs_to_ppe_uniphy(pcs);
+	u32 val;
+
+	switch (state->interface) {
+	case PHY_INTERFACE_MODE_10GBASER:
+		val = ppe_uniphy_read(uniphy, SR_XS_PCS_KR_STS1_ADDR);
+		state->link = (val & SR_XS_PCS_KR_STS1_PLU) ? 1 : 0;
+		state->duplex = DUPLEX_FULL;
+		state->speed = SPEED_10000;
+		state->pause |= (MLO_PAUSE_RX | MLO_PAUSE_TX);
+		break;
+	case PHY_INTERFACE_MODE_2500BASEX:
+		val = ppe_uniphy_read(uniphy, UNIPHY_CHANNEL0_INPUT_OUTPUT_6_ADDR);
+		state->link = (val & NEWADDEDFROMHERE_CH0_LINK_MAC) ? 1 : 0;
+		state->duplex = DUPLEX_FULL;
+		state->speed = SPEED_2500;
+		state->pause |= (MLO_PAUSE_RX | MLO_PAUSE_TX);
+		break;
+	case PHY_INTERFACE_MODE_1000BASEX:
+	case PHY_INTERFACE_MODE_SGMII:
+		val = ppe_uniphy_read(uniphy, UNIPHY_CHANNEL0_INPUT_OUTPUT_6_ADDR);
+		state->link = (val & NEWADDEDFROMHERE_CH0_LINK_MAC) ? 1 : 0;
+		state->duplex = (val & NEWADDEDFROMHERE_CH0_DUPLEX_MODE_MAC) ?
+			DUPLEX_FULL : DUPLEX_HALF;
+		if (FIELD_GET(NEWADDEDFROMHERE_CH0_SPEED_MODE_MAC, val) == UNIPHY_SPEED_10M)
+			state->speed = SPEED_10;
+		else if (FIELD_GET(NEWADDEDFROMHERE_CH0_SPEED_MODE_MAC, val) == UNIPHY_SPEED_100M)
+			state->speed = SPEED_100;
+		else if (FIELD_GET(NEWADDEDFROMHERE_CH0_SPEED_MODE_MAC, val) == UNIPHY_SPEED_1000M)
+			state->speed = SPEED_1000;
+		state->pause |= (MLO_PAUSE_RX | MLO_PAUSE_TX);
+		break;
+	default:
+		break;
+	}
+}
+
+static void ppe_pcs_an_restart(struct phylink_pcs *pcs)
+{
+}
+
+static const struct phylink_pcs_ops ppe_pcs_ops = {
+	.pcs_get_state = ppe_pcs_get_state,
+	.pcs_config = ppe_pcs_config,
+	.pcs_an_restart = ppe_pcs_an_restart,
+};
+
+static void uniphy_clk_release_provider(void *res)
+{
+	of_clk_del_provider(res);
+}
+
+struct ppe_uniphy *ppe_uniphy_setup(struct platform_device *pdev)
+{
+	struct clk_hw_onecell_data *uniphy_clk_data = NULL;
+	struct device_node *np;
+	struct ppe_device *ppe_dev = platform_get_drvdata(pdev);
+	struct ppe_uniphy *uniphy;
+	int i, ret, clk_num = 0;
+
+	np = of_get_child_by_name(pdev->dev.of_node, "qcom-uniphy");
+	if (!np) {
+		dev_err(&pdev->dev, "Failed to find uniphy node\n");
+		return ERR_PTR(-ENODEV);
+	}
+
+	/* Register uniphy raw clock */
+	clk_num = of_property_count_strings(np, "clock-output-names");
+	if (clk_num < 0) {
+		dev_err(&pdev->dev, "%pOFn: invalid clock output count\n", np);
+		goto err_node_put;
+	}
+
+	uniphy_clk_data = devm_kzalloc(&pdev->dev,
+				       struct_size(uniphy_clk_data, hws, clk_num),
+				       GFP_KERNEL);
+	if (!uniphy_clk_data) {
+		ret = -ENOMEM;
+		goto err_node_put;
+	}
+
+	uniphy_clk_data->num = clk_num;
+	for (i = 0; i < clk_num; i++) {
+		ret = of_property_read_string_index(np, "clock-output-names", i,
+						    (const char **)&uniphy_raw_clks[i]->init->name);
+		if (ret) {
+			dev_err(&pdev->dev, "invalid clock name @ %pOFn\n", np);
+			goto err_node_put;
+		}
+
+		ret = devm_clk_hw_register(&pdev->dev, uniphy_raw_clks[i]);
+		if (ret)
+			goto err_node_put;
+		uniphy_clk_data->hws[i] = uniphy_raw_clks[i];
+	}
+
+	ret = of_clk_add_hw_provider(np, of_clk_hw_onecell_get, uniphy_clk_data);
+	if (ret)
+		goto err_node_put;
+
+	ret = devm_add_action_or_reset(&pdev->dev, uniphy_clk_release_provider, np);
+	if (ret)
+		goto err_node_put;
+
+	/* Initialize each uniphy structure */
+	uniphy = devm_kzalloc(&pdev->dev, sizeof(*uniphy) * (clk_num >> 1), GFP_KERNEL);
+	if (!uniphy) {
+		ret = -ENOMEM;
+		goto err_node_put;
+	}
+
+	for (i = 0; i < (clk_num >> 1); i++) {
+		uniphy[i].base = devm_of_iomap(&pdev->dev, np, i, NULL);
+		if (IS_ERR(uniphy[i].base)) {
+			ret = PTR_ERR(uniphy[i].base);
+			goto err_node_put;
+		}
+		uniphy[i].index = i;
+		uniphy[i].interface = PHY_INTERFACE_MODE_NA;
+		uniphy[i].ppe_dev = ppe_dev;
+		uniphy[i].pcs.ops = &ppe_pcs_ops;
+		uniphy[i].pcs.poll = true;
+	}
+	of_node_put(np);
+	return uniphy;
+
+err_node_put:
+	of_node_put(np);
+	return ERR_PTR(ret);
+}
diff --git a/drivers/net/ethernet/qualcomm/ppe/ppe_uniphy.h b/drivers/net/ethernet/qualcomm/ppe/ppe_uniphy.h
new file mode 100644
index 000000000000..ec547e520937
--- /dev/null
+++ b/drivers/net/ethernet/qualcomm/ppe/ppe_uniphy.h
@@ -0,0 +1,227 @@ 
+/* SPDX-License-Identifier: GPL-2.0-only
+ *
+ * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+/* PPE UNIPHY functions and UNIPHY hardware registers declarations. */
+
+#ifndef _PPE_UNIPHY_H_
+#define _PPE_UNIPHY_H_
+
+#include <linux/phylink.h>
+
+#define UNIPHY_INDIRECT_ADDR_START			0x8000
+#define UNIPHY_INDIRECT_AHB_ADDR			0x83fc
+#define UNIPHY_INDIRECT_ADDR_HIGH			GENMASK(20, 8)
+#define UNIPHY_INDIRECT_ADDR_LOW			GENMASK(7, 0)
+#define UNIPHY_INDIRECT_DATA_ADDR(reg)			(FIELD_PREP(GENMASK(15, 10), 0x20) | \
+							FIELD_PREP(GENMASK(9, 2), \
+							FIELD_GET(UNIPHY_INDIRECT_ADDR_LOW, reg)))
+
+/* [register] UNIPHY_MISC2 */
+#define UNIPHY_MISC2_ADDR				0x218
+#define PHY_MODE					GENMASK(6, 4)
+#define USXGMII_PHY_MODE				(FIELD_PREP(PHY_MODE, 0x7))
+#define SGMII_PLUS_PHY_MODE				(FIELD_PREP(PHY_MODE, 0x5))
+#define SGMII_PHY_MODE					(FIELD_PREP(PHY_MODE, 0x3))
+
+/* [register] UNIPHY_MODE_CTRL */
+#define UNIPHY_MODE_CTRL_ADDR				0x46c
+#define NEWADDEDFROMHERE_CH0_AUTONEG_MODE		BIT(0)
+#define NEWADDEDFROMHERE_CH1_CH0_SGMII			BIT(1)
+#define NEWADDEDFROMHERE_CH4_CH1_0_SGMII		BIT(2)
+#define NEWADDEDFROMHERE_SGMII_EVEN_LOW			BIT(3)
+#define NEWADDEDFROMHERE_CH0_MODE_CTRL_25M		GENMASK(6, 4)
+#define NEWADDEDFROMHERE_CH0_QSGMII_SGMII		BIT(8)
+#define NEWADDEDFROMHERE_CH0_PSGMII_QSGMII		BIT(9)
+#define NEWADDEDFROMHERE_SG_MODE			BIT(10)
+#define NEWADDEDFROMHERE_SGPLUS_MODE			BIT(11)
+#define NEWADDEDFROMHERE_XPCS_MODE			BIT(12)
+#define NEWADDEDFROMHERE_USXG_EN			BIT(13)
+#define NEWADDEDFROMHERE_SW_V17_V18			BIT(15)
+#define USXGMII_MODE_CTRL_MASK				GENMASK(12, 8)
+#define USXGMII_MODE_CTRL				NEWADDEDFROMHERE_XPCS_MODE
+#define TEN_GR_MODE_CTRL_MASK				GENMASK(12, 8)
+#define TEN_GR_MODE_CTRL				NEWADDEDFROMHERE_XPCS_MODE
+#define QUSGMII_MODE_CTRL_MASK				GENMASK(12, 8)
+#define QUSGMII_MODE_CTRL				NEWADDEDFROMHERE_XPCS_MODE
+#define SGMIIPLUS_MODE_CTRL_MASK			(NEWADDEDFROMHERE_CH0_AUTONEG_MODE | \
+								GENMASK(12, 8))
+#define SGMIIPLUS_MODE_CTRL				NEWADDEDFROMHERE_SGPLUS_MODE
+#define QSGMII_MODE_CTRL_MASK				(NEWADDEDFROMHERE_CH0_AUTONEG_MODE | \
+								GENMASK(12, 8))
+#define QSGMII_MODE_CTRL				NEWADDEDFROMHERE_CH0_PSGMII_QSGMII
+#define SGMII_MODE_CTRL_MASK				(NEWADDEDFROMHERE_CH0_AUTONEG_MODE | \
+								GENMASK(12, 8))
+#define SGMII_MODE_CTRL					NEWADDEDFROMHERE_SG_MODE
+
+/* [register] UNIPHY_CHANNEL_INPUT_OUTPUT_4 */
+#define UNIPHY_CHANNEL0_INPUT_OUTPUT_4_ADDR		0x480
+#define NEWADDEDFROMHERE_CH0_ADP_SW_RSTN		BIT(11)
+#define UNIPHY_CHANNEL1_INPUT_OUTPUT_4_ADDR		0x498
+#define NEWADDEDFROMHERE_CH1_ADP_SW_RSTN		BIT(11)
+#define UNIPHY_CHANNEL2_INPUT_OUTPUT_4_ADDR		0x4b0
+#define NEWADDEDFROMHERE_CH2_ADP_SW_RSTN		BIT(11)
+#define UNIPHY_CHANNEL3_INPUT_OUTPUT_4_ADDR		0x4c8
+#define NEWADDEDFROMHERE_CH3_ADP_SW_RSTN		BIT(11)
+#define UNIPHY_CHANNEL4_INPUT_OUTPUT_4_ADDR		0x4e0
+#define NEWADDEDFROMHERE_CH4_ADP_SW_RSTN		BIT(11)
+#define UNIPHY_CHANNEL_INPUT_OUTPUT_4_ADDR(x)		(0x480 + 0x18 * (x))
+#define NEWADDEDFROMHERE_CH_ADP_SW_RSTN			BIT(11)
+
+/* [register] UNIPHY_CHANNEL_INPUT_OUTPUT_6 */
+#define UNIPHY_CHANNEL0_INPUT_OUTPUT_6_ADDR		0x488
+#define NEWADDEDFROMHERE_CH0_LINK_MAC			BIT(7)
+#define NEWADDEDFROMHERE_CH0_DUPLEX_MODE_MAC		BIT(6)
+#define NEWADDEDFROMHERE_CH0_SPEED_MODE_MAC		GENMASK(5, 4)
+#define NEWADDEDFROMHERE_CH0_PAUSE_MAC			BIT(3)
+#define NEWADDEDFROMHERE_CH0_ASYM_PAUSE_MAC		BIT(2)
+#define NEWADDEDFROMHERE_CH0_TX_PAUSE_EN_MAC		BIT(1)
+#define NEWADDEDFROMHERE_CH0_RX_PAUSE_EN_MAC		BIT(0)
+#define UNIPHY_SPEED_10M				0
+#define UNIPHY_SPEED_100M				1
+#define UNIPHY_SPEED_1000M				2
+
+/* [register] UNIPHY_INSTANCE_LINK_DETECT */
+#define UNIPHY_LINK_DETECT_ADDR				0x570
+#define DETECT_LOS_FROM_SFP				GENMASK(8, 6)
+#define UNIPHY_10GR_LINK_LOSS				(FIELD_PREP(DETECT_LOS_FROM_SFP, 0x7))
+
+/* [register] UNIPHY_QP_USXG_OPITON1 */
+#define UNIPHY_QP_USXG_OPITON1_ADDR			0x584
+#define GMII_SRC_SEL					BIT(0)
+
+/* [register] UNIPHY_OFFSET_CALIB_4 */
+#define UNIPHY_OFFSET_CALIB_4_ADDR			0x1e0
+#define MMD1_REG_CALIBRATION_DONE_REG			BIT(7)
+#define UNIPHY_CALIBRATION_DONE				0x1
+
+/* [register] UNIPHY_PLL_RESET */
+#define UNIPHY_PLL_RESET_ADDR				0x780
+#define UPHY_ANA_EN_SW_RSTN				BIT(6)
+
+/* [register] SR_XS_PCS_KR_STS1 */
+#define SR_XS_PCS_KR_STS1_ADDR				0x30020
+#define SR_XS_PCS_KR_STS1_PLU				BIT(12)
+
+/* [register] VR_XS_PCS_DIG_CTRL1 */
+#define VR_XS_PCS_DIG_CTRL1_ADDR			0x38000
+#define USXGMII_EN					BIT(9)
+#define USRA_RST					BIT(10)
+#define VR_RST						BIT(15)
+
+/* [register] VR_XS_PCS_EEE_MCTRL0 */
+#define VR_XS_PCS_EEE_MCTRL0_ADDR			0x38006
+#define LTX_EN						BIT(0)
+#define LRX_EN						BIT(1)
+#define SIGN_BIT					BIT(6)
+#define MULT_FACT_100NS					GENMASK(11, 8)
+
+/* [register] VR_XS_PCS_KR_CTRL */
+#define VR_XS_PCS_KR_CTRL_ADDR	0x38007
+#define USXG_MODE					GENMASK(12, 10)
+#define QUXGMII_MODE					(FIELD_PREP(USXG_MODE, 0x5))
+
+/* [register] VR_XS_PCS_EEE_TXTIMER */
+#define VR_XS_PCS_EEE_TXTIMER_ADDR			0x38008
+#define TSL_RES						GENMASK(5, 0)
+#define T1U_RES						GENMASK(7, 6)
+#define TWL_RES						GENMASK(12, 8)
+#define UNIPHY_XPCS_TSL_TIMER				(FIELD_PREP(TSL_RES, 0xa))
+#define UNIPHY_XPCS_T1U_TIMER				(FIELD_PREP(TSL_RES, 0x3))
+#define UNIPHY_XPCS_TWL_TIMER				(FIELD_PREP(TSL_RES, 0x16))
+
+/* [register] VR_XS_PCS_EEE_RXTIMER  */
+#define VR_XS_PCS_EEE_RXTIMER_ADDR			0x38009
+#define RES_100U					GENMASK(7, 0)
+#define TWR_RES						GENMASK(13, 8)
+#define UNIPHY_XPCS_100US_TIMER				(FIELD_PREP(RES_100U, 0xc8))
+#define UNIPHY_XPCS_TWR_TIMER				(FIELD_PREP(RES_100U, 0x1c))
+
+/* [register] VR_XS_PCS_DIG_STS */
+#define VR_XS_PCS_DIG_STS_ADDR				0x3800a
+#define AM_COUNT					GENMASK(14, 0)
+#define QUXGMII_AM_COUNT				(FIELD_PREP(AM_COUNT, 0x6018))
+
+/* [register] VR_XS_PCS_EEE_MCTRL1 */
+#define VR_XS_PCS_EEE_MCTRL1_ADDR			0x3800b
+#define TRN_LPI						BIT(0)
+#define TRN_RXLPI					BIT(8)
+
+/* [register] VR_MII_1_DIG_CTRL1 */
+#define VR_MII_DIG_CTRL1_CHANNEL1_ADDR			0x1a8000
+#define VR_MII_DIG_CTRL1_CHANNEL2_ADDR			0x1b8000
+#define VR_MII_DIG_CTRL1_CHANNEL3_ADDR			0x1c8000
+#define VR_MII_DIG_CTRL1_CHANNEL_ADDR(x)		(0x1a8000 + 0x10000 * ((x) - 1))
+#define CHANNEL_USRA_RST				BIT(5)
+
+/* [register] VR_MII_AN_CTRL */
+#define VR_MII_AN_CTRL_ADDR				0x1f8001
+#define VR_MII_AN_CTRL_CHANNEL1_ADDR			0x1a8001
+#define VR_MII_AN_CTRL_CHANNEL2_ADDR			0x1b8001
+#define VR_MII_AN_CTRL_CHANNEL3_ADDR			0x1c8001
+#define VR_MII_AN_CTRL_CHANNEL_ADDR(x)			(0x1a8001 + 0x10000 * ((x) - 1))
+#define MII_AN_INTR_EN					BIT(0)
+#define MII_CTRL					BIT(8)
+
+/* [register] VR_MII_AN_INTR_STS */
+#define VR_MII_AN_INTR_STS_ADDR				0x1f8002
+#define VR_MII_AN_INTR_STS_CHANNEL1_ADDR		0x1a8002
+#define VR_MII_AN_INTR_STS_CHANNEL2_ADDR		0x1b8002
+#define VR_MII_AN_INTR_STS_CHANNEL3_ADDR		0x1c8002
+#define VR_MII_AN_INTR_STS_CHANNEL_ADDR(x)		(0x1a8002 + 0x10000 * ((x) - 1))
+#define CL37_ANCMPLT_INTR				BIT(0)
+
+/* [register] VR_XAUI_MODE_CTRL */
+#define VR_XAUI_MODE_CTRL_ADDR				0x1f8004
+#define VR_XAUI_MODE_CTRL_CHANNEL1_ADDR			0x1a8004
+#define VR_XAUI_MODE_CTRL_CHANNEL2_ADDR			0x1b8004
+#define VR_XAUI_MODE_CTRL_CHANNEL3_ADDR			0x1c8004
+#define VR_XAUI_MODE_CTRL_CHANNEL_ADDR(x)		(0x1a8004 + 0x10000 * ((x) - 1))
+#define IPG_CHECK					BIT(0)
+
+/* [register] SR_MII_CTRL */
+#define SR_MII_CTRL_ADDR				0x1f0000
+#define SR_MII_CTRL_CHANNEL1_ADDR			0x1a0000
+#define SR_MII_CTRL_CHANNEL2_ADDR			0x1b0000
+#define SR_MII_CTRL_CHANNEL3_ADDR			0x1c0000
+#define SR_MII_CTRL_CHANNEL_ADDR(x)			(0x1a0000 + 0x10000 * ((x) - 1))
+#define AN_ENABLE					BIT(12)
+#define USXGMII_DUPLEX_FULL				BIT(8)
+#define USXGMII_SPEED_MASK				(BIT(13) | BIT(6) | BIT(5))
+#define USXGMII_SPEED_10000				(BIT(13) | BIT(6))
+#define USXGMII_SPEED_5000				(BIT(13) | BIT(5))
+#define USXGMII_SPEED_2500				BIT(5)
+#define USXGMII_SPEED_1000				BIT(6)
+#define USXGMII_SPEED_100				BIT(13)
+#define USXGMII_SPEED_10				0
+
+/* PPE UNIPHY data type */
+struct ppe_uniphy {
+	void __iomem *base;
+	struct ppe_device *ppe_dev;
+	unsigned int index;
+	phy_interface_t interface;
+	struct phylink_pcs pcs;
+};
+
+#define pcs_to_ppe_uniphy(_pcs)				container_of(_pcs, struct ppe_uniphy, pcs)
+
+struct ppe_uniphy *ppe_uniphy_setup(struct platform_device *pdev);
+
+int ppe_uniphy_speed_set(struct ppe_uniphy *uniphy,
+			 int port, int speed);
+
+int ppe_uniphy_duplex_set(struct ppe_uniphy *uniphy,
+			  int port, int duplex);
+
+int ppe_uniphy_adapter_reset(struct ppe_uniphy *uniphy,
+			     int port);
+
+int ppe_uniphy_autoneg_complete_check(struct ppe_uniphy *uniphy,
+				      int port);
+
+int ppe_uniphy_port_gcc_clock_en_set(struct ppe_uniphy *uniphy,
+				     int port, bool enable);
+
+#endif /* _PPE_UNIPHY_H_ */
diff --git a/include/linux/soc/qcom/ppe.h b/include/linux/soc/qcom/ppe.h
index 268109c823ad..d3cb18df33fa 100644
--- a/include/linux/soc/qcom/ppe.h
+++ b/include/linux/soc/qcom/ppe.h
@@ -20,6 +20,7 @@  struct ppe_device {
 	struct dentry *debugfs_root;
 	bool is_ppe_probed;
 	void *ppe_priv;
+	void *uniphy;
 };
 
 /* PPE operations, which is used by the external driver like Ethernet