diff mbox series

[2/8] phy: Introduce Qualcomm ethernet uniphy driver

Message ID TYZPR01MB55568ACB534944D7DEB00C7AC9762@TYZPR01MB5556.apcprd01.prod.exchangelabs.com (mailing list archive)
State New
Headers show
Series ipq5018: enable ethernet support | expand

Commit Message

Ziyang Huang Jan. 21, 2024, 12:42 p.m. UTC
Signed-off-by: Ziyang Huang <hzyitc@outlook.com>
---
 drivers/phy/qualcomm/Kconfig               |   7 +
 drivers/phy/qualcomm/Makefile              |   2 +
 drivers/phy/qualcomm/phy-qcom-eth-uniphy.c | 494 +++++++++++++++++++++
 include/dt-bindings/phy/qcom-eth-uniphy.h  |  15 +
 4 files changed, 518 insertions(+)
 create mode 100644 drivers/phy/qualcomm/phy-qcom-eth-uniphy.c
 create mode 100644 include/dt-bindings/phy/qcom-eth-uniphy.h

Comments

Ziyang Huang Jan. 23, 2024, 3:58 p.m. UTC | #1
在 2024/1/21 20:42, Ziyang Huang 写道:
> +#define rmwl(addr, mask, val) \
> +	writel(((readl(addr) & ~(mask)) | ((val) & (mask))), addr)
> +
> +static int cmn_init(struct platform_device *pdev)
> +{
> +	struct resource *res;
> +	void __iomem *cmn_base;
> +	void __iomem *tcsr_base;
> +	u32 val;
> +
> +	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "cmn");
> +	if (!res)
> +		return 0;
> +
> +	cmn_base = devm_ioremap_resource(&pdev->dev, res);
> +	if (IS_ERR_OR_NULL(cmn_base))
> +		return PTR_ERR(cmn_base);
> +
> +	/* For IPQ50xx, tcsr is necessary to enable cmn block */
> +	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "tcsr");
> +	if (res) {
> +		tcsr_base = devm_ioremap_resource(&pdev->dev, res);
> +		if (IS_ERR_OR_NULL(tcsr_base))
> +			return PTR_ERR(tcsr_base);
> +
> +		rmwl((tcsr_base + TCSR_ETH_CMN), TCSR_ETH_CMN_ENABLE,
> +		     TCSR_ETH_CMN_ENABLE);
> +	}
> +
> +	rmwl((cmn_base + CMN_PLL_REFCLK_SRC),
> +	     CMN_PLL_REFCLK_SRC_FROM_MASK,
> +	     CMN_PLL_REFCLK_SRC_FROM_REG);
> +	rmwl((cmn_base + CMN_PLL_REFCLK),
> +	     (CMN_PLL_REFCLK_EXTERNAL | CMN_PLL_REFCLK_FREQ_MASK
> +	      | CMN_PLL_REFCLK_DIV_MASK),
> +	     (CMN_PLL_REFCLK_FREQ_48M | CMN_PLL_REFCLK_DIV(2)));
> +
> +	rmwl((cmn_base + CMN_PLL_CTRL), CMN_PLL_CTRL_RST_N, 0);
> +	msleep(1);
> +	rmwl((cmn_base + CMN_PLL_CTRL), CMN_PLL_CTRL_RST_N,
> +	     CMN_PLL_CTRL_RST_N);
> +	msleep(1);
> +
> +	return read_poll_timeout(readl, val,
> +				 (val & CMN_PLL_STATUS_LOCKED),
> +				 100, 200000, false,
> +				 (cmn_base + CMN_PLL_STATUS));
> +}
> +

Hi Andrew,

Sorry to bother you. But I can't make a decision here.

The CMN block (Seem like the Abbreviation of "component") controls the 
entire network block. It need to be configured before uniphy, mdio, 
gmac, etc.. In the past, Qualcomm put it in mdio driver. But UNIPHY need 
to been initializated before mdio because some PHYs/switchs use the 
outclk provided by UNIPHY as their main clocks.

So it seem like that it should be described in a separate node. But I 
couldn't find a suitable driver directory for it. Can you please give me 
some suggestions? Thanks.
Andrew Lunn Jan. 23, 2024, 11:25 p.m. UTC | #2
On Tue, Jan 23, 2024 at 11:58:26PM +0800, Ziyang Huang wrote:
> 在 2024/1/21 20:42, Ziyang Huang 写道:
> > +#define rmwl(addr, mask, val) \
> > +	writel(((readl(addr) & ~(mask)) | ((val) & (mask))), addr)
> > +
> > +static int cmn_init(struct platform_device *pdev)
> > +{
> > +	struct resource *res;
> > +	void __iomem *cmn_base;
> > +	void __iomem *tcsr_base;
> > +	u32 val;
> > +
> > +	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "cmn");
> > +	if (!res)
> > +		return 0;
> > +
> > +	cmn_base = devm_ioremap_resource(&pdev->dev, res);
> > +	if (IS_ERR_OR_NULL(cmn_base))
> > +		return PTR_ERR(cmn_base);
> > +
> > +	/* For IPQ50xx, tcsr is necessary to enable cmn block */
> > +	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "tcsr");
> > +	if (res) {
> > +		tcsr_base = devm_ioremap_resource(&pdev->dev, res);
> > +		if (IS_ERR_OR_NULL(tcsr_base))
> > +			return PTR_ERR(tcsr_base);
> > +
> > +		rmwl((tcsr_base + TCSR_ETH_CMN), TCSR_ETH_CMN_ENABLE,
> > +		     TCSR_ETH_CMN_ENABLE);
> > +	}
> > +
> > +	rmwl((cmn_base + CMN_PLL_REFCLK_SRC),
> > +	     CMN_PLL_REFCLK_SRC_FROM_MASK,
> > +	     CMN_PLL_REFCLK_SRC_FROM_REG);
> > +	rmwl((cmn_base + CMN_PLL_REFCLK),
> > +	     (CMN_PLL_REFCLK_EXTERNAL | CMN_PLL_REFCLK_FREQ_MASK
> > +	      | CMN_PLL_REFCLK_DIV_MASK),
> > +	     (CMN_PLL_REFCLK_FREQ_48M | CMN_PLL_REFCLK_DIV(2)));
> > +
> > +	rmwl((cmn_base + CMN_PLL_CTRL), CMN_PLL_CTRL_RST_N, 0);
> > +	msleep(1);
> > +	rmwl((cmn_base + CMN_PLL_CTRL), CMN_PLL_CTRL_RST_N,
> > +	     CMN_PLL_CTRL_RST_N);
> > +	msleep(1);
> > +
> > +	return read_poll_timeout(readl, val,
> > +				 (val & CMN_PLL_STATUS_LOCKED),
> > +				 100, 200000, false,
> > +				 (cmn_base + CMN_PLL_STATUS));
> > +}
> > +
> 
> Hi Andrew,
> 
> Sorry to bother you. But I can't make a decision here.
> 
> The CMN block (Seem like the Abbreviation of "component") controls the
> entire network block. It need to be configured before uniphy, mdio, gmac,
> etc.. In the past, Qualcomm put it in mdio driver. But UNIPHY need to been
> initializated before mdio because some PHYs/switchs use the outclk provided
> by UNIPHY as their main clocks.
> 
> So it seem like that it should be described in a separate node. But I
> couldn't find a suitable driver directory for it. Can you please give me
> some suggestions? Thanks.

Maybe drivers/soc/qcom.

Does it provide any resources to the uniphy, mdio, gmac, etc? Anything
which can be used to link all the bits together?

Looking at CMN_PLL_CTRL_RST_N, could it be considered as a reset
driver? Each of the other drivers have a phandle to it, and use the
reset API to take it out of reset when they probe? That should give
you some ordering guarantees.

    Andrew
diff mbox series

Patch

diff --git a/drivers/phy/qualcomm/Kconfig b/drivers/phy/qualcomm/Kconfig
index 97ca5952e34e..1cbbfd196115 100644
--- a/drivers/phy/qualcomm/Kconfig
+++ b/drivers/phy/qualcomm/Kconfig
@@ -28,6 +28,13 @@  config PHY_QCOM_EDP
 	  Enable this driver to support the Qualcomm eDP PHY found in various
 	  Qualcomm chipsets.
 
+config PHY_QCOM_ETH_UNIPHY
+	tristate "Qualcomm ethernet uniphy driver"
+	depends on OF && COMMON_CLK && (ARCH_QCOM || COMPILE_TEST)
+	select GENERIC_PHY
+	help
+	  Support for the Qualcomm ethernet uniphy.
+
 config PHY_QCOM_IPQ4019_USB
 	tristate "Qualcomm IPQ4019 USB PHY driver"
 	depends on OF && (ARCH_QCOM || COMPILE_TEST)
diff --git a/drivers/phy/qualcomm/Makefile b/drivers/phy/qualcomm/Makefile
index b030858e0f8d..a9f01e688553 100644
--- a/drivers/phy/qualcomm/Makefile
+++ b/drivers/phy/qualcomm/Makefile
@@ -21,4 +21,6 @@  obj-$(CONFIG_PHY_QCOM_USB_HS_28NM)	+= phy-qcom-usb-hs-28nm.o
 obj-$(CONFIG_PHY_QCOM_USB_SS)		+= phy-qcom-usb-ss.o
 obj-$(CONFIG_PHY_QCOM_USB_SNPS_FEMTO_V2)+= phy-qcom-snps-femto-v2.o
 obj-$(CONFIG_PHY_QCOM_IPQ806X_USB)	+= phy-qcom-ipq806x-usb.o
+
+obj-$(CONFIG_PHY_QCOM_ETH_UNIPHY)	+= phy-qcom-eth-uniphy.o
 obj-$(CONFIG_PHY_QCOM_SGMII_ETH)	+= phy-qcom-sgmii-eth.o
diff --git a/drivers/phy/qualcomm/phy-qcom-eth-uniphy.c b/drivers/phy/qualcomm/phy-qcom-eth-uniphy.c
new file mode 100644
index 000000000000..71d4cefb8adb
--- /dev/null
+++ b/drivers/phy/qualcomm/phy-qcom-eth-uniphy.c
@@ -0,0 +1,494 @@ 
+/*
+ * UNIPHY is the PCS between MAC and PHY which controls the mode of
+ * physical ports. Depends on different SoC, it can support
+ * SGMII/SGMII+/USXGMII. What's more, in some SoC it also support
+ * QSGMII/PSGMII which combine multi SGMII line into single physical port.
+ *
+ * =======================================================================
+ *        ________________________________
+ *       |  _______   IPQ807x             |
+ *       | | GMAC0 |__                    |
+ *       | |_______|  \                   |               _________
+ *       |  _______    \                  |          ____|   GPHY  |
+ *       | | GMAC1 |__  \     _________   |         /    | /Switch |
+ *       | |_______|  \  \___|         |  |     SGMII(+) |_________|
+ *       |  _______    \_____|         |  | P0    /
+ *       | | GMAC2 |_________| UNIPHY0 |--|-----or
+ *       | |_______|    _____|         |  |       \
+ *       |  _______    /   __|         |  |    (Q/P)SGMII __________
+ *       | | GMAC3 |__/   /  |_________|  |         \____| (Q/P)PHY |
+ *       | |_______|     /                |              |__________|
+ *       |  _______     /                 |
+ *       | | GMAC4 |--or                  |               _________
+ *       | |_______|    \     _________   | P1           | (X)GPHY |
+ *       |  ________     or--| UNIPHY1 |--|----SGMII(+)--| /Switch |
+ *       | | XGMAC0 |___/    |_________|  |    /USXGMII  |_________|
+ *       | |________|                     |
+ *       |  ________                      |
+ *       | | GMAC5  |___                  |               _________
+ *       | |________|   \     _________   | P2           | (X)GPHY |
+ *       |  ________     or--| UNIPHY2 |--|----SGMII(+)--| /Switch |
+ *       | | XGMAC1 |___/    |_________|  |    /USXGMII  |_________|
+ *       | |________|                     |
+ *       |________________________________|
+ *
+ * =======================================================================
+ *       _________________________________
+ *       |  _______   IPQ50xx   ______    | P0             ______
+ *       | | GMAC0 |___________| GPHY |---|------UTP------| RJ45 |
+ *       | |_______|           |______|   |               |______|
+ *       |  _______           _________   |               _________
+ *       | | GMAC1 |_________| UNIPHY0 |  | P1           |   GPHY  |
+ *       | |_______|         |_________|--|----SGMII(+)--| /Switch |
+ *       |________________________________|              |_________|
+ *
+ * =======================================================================
+ */
+
+#include <dt-bindings/phy/qcom-eth-uniphy.h>
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/iopoll.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+
+
+#define TCSR_ETH_CMN				0x0
+#define  TCSR_ETH_CMN_ENABLE			BIT(0)
+
+
+#define CMN_PLL_REFCLK_SRC			0x28
+#define  CMN_PLL_REFCLK_SRC_FROM_MASK		GENMASK(9, 8)
+#define   CMN_PLL_REFCLK_SRC_FROM(x)		FIELD_PREP(CMN_PLL_REFCLK_SRC_FROM_MASK, (x))
+#define   CMN_PLL_REFCLK_SRC_FROM_REG		CMN_PLL_REFCLK_SRC_FROM(0)
+#define   CMN_PLL_REFCLK_SRC_FROM_LOGIC		CMN_PLL_REFCLK_SRC_FROM(1)
+#define   CMN_PLL_REFCLK_SRC_FROM_PCS		CMN_PLL_REFCLK_SRC_FROM(2)
+
+#define CMN_PLL_REFCLK				0x784
+#define  CMN_PLL_REFCLK_EXTERNAL		BIT(9)
+#define  CMN_PLL_REFCLK_DIV_MASK		GENMASK(8, 4)
+#define   CMN_PLL_REFCLK_DIV(x)			FIELD_PREP(CMN_PLL_REFCLK_DIV_MASK, (x))
+#define  CMN_PLL_REFCLK_FREQ_MASK		GENMASK(3, 0)
+#define   CMN_PLL_REFCLK_FREQ(x)		FIELD_PREP(CMN_PLL_REFCLK_FREQ_MASK, (x))
+#define   CMN_PLL_REFCLK_FREQ_25M		CMN_PLL_REFCLK_FREQ(3)
+#define   CMN_PLL_REFCLK_FREQ_31250K		CMN_PLL_REFCLK_FREQ(4)
+#define   CMN_PLL_REFCLK_FREQ_40M		CMN_PLL_REFCLK_FREQ(6)
+#define   CMN_PLL_REFCLK_FREQ_48M		CMN_PLL_REFCLK_FREQ(7)
+#define   CMN_PLL_REFCLK_FREQ_50M		CMN_PLL_REFCLK_FREQ(8)
+
+#define CMN_PLL_CTRL				0x780
+#define  CMN_PLL_CTRL_RST_N			BIT(6)
+
+#define CMN_PLL_STATUS				0x64
+#define  CMN_PLL_STATUS_LOCKED			BIT(2)
+
+
+#define IPQ50XX_UNIPHY_CLKOUT			0x74
+#define  IPQ50XX_UNIPHY_CLKOUT_DS_MASK		GENMASK(3, 2)
+#define   IPQ50XX_UNIPHY_CLKOUT_DS(x)		FIELD_PREP(IPQ50XX_UNIPHY_CLKOUT_DS_MASK, (x))
+#define   IPQ50XX_UNIPHY_CLKOUT_DS_2_8V		IPQ50XX_UNIPHY_CLKOUT_DS(0)
+#define   IPQ50XX_UNIPHY_CLKOUT_DS_1_5V		IPQ50XX_UNIPHY_CLKOUT_DS(1)
+#define  IPQ50XX_UNIPHY_CLKOUT_DIV_MASK		GENMASK(1, 1)
+#define   IPQ50XX_UNIPHY_CLKOUT_DIV(x)		FIELD_PREP(IPQ50XX_UNIPHY_CLKOUT_DIV_MASK, (x))
+#define   IPQ50XX_UNIPHY_CLKOUT_DIV_50M		IPQ50XX_UNIPHY_CLKOUT_DIV(0)
+#define   IPQ50XX_UNIPHY_CLKOUT_DIV_25M		IPQ50XX_UNIPHY_CLKOUT_DIV(1)
+#define  IPQ50XX_UNIPHY_CLKOUT_ENABLE		BIT(0)
+
+#define IPQ53XX_UNIPHY_CLKOUT			0x610
+#define  IPQ53XX_UNIPHY_CLKOUT_LDO_LEVEL_MASK	GENMASK(10, 8)
+#define  IPQ53XX_UNIPHY_CLKOUT_DIV_MASK		GENMASK(5, 5)
+#define   IPQ53XX_UNIPHY_CLKOUT_DIV(x)		FIELD_PREP(IPQ53XX_UNIPHY_CLKOUT_DIV_MASK, (x))
+#define   IPQ53XX_UNIPHY_CLKOUT_DIV_50M		IPQ53XX_UNIPHY_CLKOUT_DIV(0)
+#define   IPQ53XX_UNIPHY_CLKOUT_DIV_25M		IPQ53XX_UNIPHY_CLKOUT_DIV(1)
+#define  IPQ53XX_UNIPHY_CLKOUT_PULLDOWN		BIT(3)
+#define  IPQ53XX_UNIPHY_CLKOUT_DS_MASK		GENMASK(2, 1)
+#define   IPQ53XX_UNIPHY_CLKOUT_DS(x)		FIELD_PREP(IPQ53XX_UNIPHY_CLKOUT_DS_MASK, (x))
+#define   IPQ53XX_UNIPHY_CLKOUT_DS_2_8V		IPQ53XX_UNIPHY_CLKOUT_DS(0)
+#define   IPQ53XX_UNIPHY_CLKOUT_DS_1_5V		IPQ53XX_UNIPHY_CLKOUT_DS(1)
+#define  IPQ53XX_UNIPHY_CLKOUT_ENABLE		BIT(0)
+
+
+#define UNIPHY_MODE				0x46c
+#define  UNIPHY_MODE_USXG			BIT(13)
+#define  UNIPHY_MODE_XPCS			BIT(12)
+#define  UNIPHY_MODE_SGMIIPLUS			BIT(11)
+#define  UNIPHY_MODE_SGMII			BIT(10)
+#define  UNIPHY_MODE_PSGMII			BIT(9)
+#define  UNIPHY_MODE_QSGMII			BIT(8)
+#define  UNIPHY_MODE_CH0_MODE_MASK		GENMASK(6, 4)
+#define   UNIPHY_MODE_CH0_MODE(x)		FIELD_PREP(UNIPHY_MODE_CH0_MODE_MASK, (x))
+#define   UNIPHY_MODE_CH0_MODE_1000BASEX	UNIPHY_MODE_CH0_MODE(0)
+#define   UNIPHY_MODE_CH0_MODE_MAC		UNIPHY_MODE_CH0_MODE(2)
+#define  UNIPHY_MODE_SGMII_CHANNEL_MASK		GENMASK(2, 1)
+#define   UNIPHY_MODE_SGMII_CHANNEL(x)		FIELD_PREP(UNIPHY_MODE_SGMII_CHANNEL_MASK, (x))
+#define   UNIPHY_MODE_SGMII_CHANNEL_0		UNIPHY_MODE_SGMII_CHANNEL(0)
+#define   UNIPHY_MODE_SGMII_CHANNEL_1		UNIPHY_MODE_SGMII_CHANNEL(1)
+#define   UNIPHY_MODE_SGMII_CHANNEL_4		UNIPHY_MODE_SGMII_CHANNEL(2)
+#define  UNIPHY_MODE_AN_MODE_MASK		BIT(0)
+#define   UNIPHY_MODE_AN_MODE(x)		FIELD_PREP(UNIPHY_MODE_AN_MODE_MASK, (x))
+#define   UNIPHY_MODE_AN_MODE_ATHEROS		UNIPHY_MODE_AN_MODE(0)
+#define   UNIPHY_MODE_AN_MODE_STANDARD		UNIPHY_MODE_AN_MODE(1)
+
+#define UNIPHY_PLL_CTRL				0x780
+#define  UNIPHY_PLL_CTRL_RST_N			BIT(6)
+
+#define UNIPHY_CALIBRATION			0x1E0
+#define  UNIPHY_CALIBRATION_DONE		BIT(7)
+
+
+#define UNIPHY_CHANNEL(x)			(0x480 + 0x18 * (x))
+#define  UNIPHY_CHANNEL_RSTN			BIT(11)
+#define  UNIPHY_CHANNEL_FORCE_SPEED_25M		BIT(3)
+
+#define UNIPHY_SGMII				0x218
+#define  UNIPHY_SGMII_MODE_MASK			GENMASK(6, 4)
+#define   UNIPHY_SGMII_MODE(x)			FIELD_PREP(UNIPHY_SGMII_MODE_MASK, (x))
+#define   UNIPHY_SGMII_MODE_SGMII		UNIPHY_SGMII_MODE(3)
+#define   UNIPHY_SGMII_MODE_SGMIIPLUS		UNIPHY_SGMII_MODE(5)
+#define   UNIPHY_SGMII_MODE_USXGMII		UNIPHY_SGMII_MODE(7)
+#define  UNIPHY_SGMII_RATE_MASK			GENMASK(1, 0)
+#define   UNIPHY_SGMII_RATE(x)			FIELD_PREP(UNIPHY_SGMII_RATE_MASK, (x))
+
+
+#define SGMII_CLK_RATE				125000000 /* 125M */
+#define SGMII_PLUS_CLK_RATE			312500000 /* 312.5M */
+
+
+struct qcom_eth_uniphy {
+	struct device *dev;
+	void __iomem *base;
+	int num_clks;
+	struct clk_bulk_data *clks;
+	struct reset_control *rst;
+
+	int mode;
+
+	struct clk_hw *clk_rx, *clk_tx;
+	struct clk_hw_onecell_data *clk_data;
+};
+
+
+#define rmwl(addr, mask, val) \
+	writel(((readl(addr) & ~(mask)) | ((val) & (mask))), addr)
+
+static int cmn_init(struct platform_device *pdev)
+{
+	struct resource *res;
+	void __iomem *cmn_base;
+	void __iomem *tcsr_base;
+	u32 val;
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "cmn");
+	if (!res)
+		return 0;
+
+	cmn_base = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR_OR_NULL(cmn_base))
+		return PTR_ERR(cmn_base);
+
+	/* For IPQ50xx, tcsr is necessary to enable cmn block */
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "tcsr");
+	if (res) {
+		tcsr_base = devm_ioremap_resource(&pdev->dev, res);
+		if (IS_ERR_OR_NULL(tcsr_base))
+			return PTR_ERR(tcsr_base);
+
+		rmwl((tcsr_base + TCSR_ETH_CMN), TCSR_ETH_CMN_ENABLE,
+		     TCSR_ETH_CMN_ENABLE);
+	}
+
+	rmwl((cmn_base + CMN_PLL_REFCLK_SRC),
+	     CMN_PLL_REFCLK_SRC_FROM_MASK,
+	     CMN_PLL_REFCLK_SRC_FROM_REG);
+	rmwl((cmn_base + CMN_PLL_REFCLK),
+	     (CMN_PLL_REFCLK_EXTERNAL | CMN_PLL_REFCLK_FREQ_MASK
+	      | CMN_PLL_REFCLK_DIV_MASK),
+	     (CMN_PLL_REFCLK_FREQ_48M | CMN_PLL_REFCLK_DIV(2)));
+
+	rmwl((cmn_base + CMN_PLL_CTRL), CMN_PLL_CTRL_RST_N, 0);
+	msleep(1);
+	rmwl((cmn_base + CMN_PLL_CTRL), CMN_PLL_CTRL_RST_N,
+	     CMN_PLL_CTRL_RST_N);
+	msleep(1);
+
+	return read_poll_timeout(readl, val,
+				 (val & CMN_PLL_STATUS_LOCKED),
+				 100, 200000, false,
+				 (cmn_base + CMN_PLL_STATUS));
+}
+
+
+static void uniphy_write(struct qcom_eth_uniphy *uniphy, int addr, u32 val)
+{
+	writel(val, (uniphy->base + addr));
+}
+
+static u32 uniphy_read(struct qcom_eth_uniphy *uniphy, int addr)
+{
+	return readl((uniphy->base + addr));
+}
+
+static void uniphy_rmw(struct qcom_eth_uniphy *uniphy, int addr, u32 mask, u32 val)
+{
+	u32 v = uniphy_read(uniphy, addr);
+	v &= ~mask;
+	v |= val & mask;
+	uniphy_write(uniphy, addr, v);
+}
+
+static int uniphy_clkout_init(struct qcom_eth_uniphy *uniphy)
+{
+	u32 val;
+	int ret;
+
+	ret = of_property_read_u32(uniphy->dev->of_node, "clkout-frequency", &val);
+	if (ret == -EINVAL)
+		return 0;
+	else if (ret < 0)
+		return ret;
+
+	switch(val) {
+		case QCOM_ETH_UNIPHY_CLKOUT_FREQ_25M:
+			uniphy_rmw(uniphy, IPQ50XX_UNIPHY_CLKOUT,
+				   IPQ50XX_UNIPHY_CLKOUT_DIV_MASK,
+				   IPQ50XX_UNIPHY_CLKOUT_DIV_25M);
+			break;
+		case QCOM_ETH_UNIPHY_CLKOUT_FREQ_50M:
+			uniphy_rmw(uniphy, IPQ50XX_UNIPHY_CLKOUT,
+				   IPQ50XX_UNIPHY_CLKOUT_DIV_MASK,
+				   IPQ50XX_UNIPHY_CLKOUT_DIV_50M);
+			break;
+		default:
+			dev_err(uniphy->dev, "Unsupported clkout-frequency: %d\n", val);
+			return -EINVAL;
+	}
+
+	ret = of_property_read_u32(uniphy->dev->of_node, "clkout-drive-strength", &val);
+	if (ret != -EINVAL) {
+		if (ret < 0)
+			return ret;
+
+		switch(val) {
+			case QCOM_ETH_UNIPHY_CLKOUT_DS_1_5V:
+				uniphy_rmw(uniphy, IPQ50XX_UNIPHY_CLKOUT,
+					   IPQ50XX_UNIPHY_CLKOUT_DS_MASK,
+					   IPQ50XX_UNIPHY_CLKOUT_DS_1_5V);
+				break;
+			case QCOM_ETH_UNIPHY_CLKOUT_DS_2_8V:
+				uniphy_rmw(uniphy, IPQ50XX_UNIPHY_CLKOUT,
+					   IPQ50XX_UNIPHY_CLKOUT_DS_MASK,
+					   IPQ50XX_UNIPHY_CLKOUT_DS_2_8V);
+				break;
+			default:
+				dev_err(uniphy->dev, "Unsupported clkout-drive-strength: %d\n", val);
+				return -EINVAL;
+		}
+
+	}
+
+	uniphy_rmw(uniphy, IPQ50XX_UNIPHY_CLKOUT,
+		   IPQ50XX_UNIPHY_CLKOUT_ENABLE,
+		   IPQ50XX_UNIPHY_CLKOUT_ENABLE);
+
+	return 0;
+}
+
+static int uniphy_mode_set(struct qcom_eth_uniphy *uniphy)
+{
+	int ret;
+
+	ret = of_property_read_u32(uniphy->dev->of_node, "mode",
+				   &uniphy->mode);
+	if (ret < 0)
+		return ret;
+
+	switch(uniphy->mode) {
+		case QCOM_ETH_UNIPHY_MODE_SGMII:
+			uniphy_write(uniphy, UNIPHY_MODE,
+				     UNIPHY_MODE_SGMII);
+			uniphy_rmw(uniphy, UNIPHY_SGMII,
+				   UNIPHY_SGMII_MODE_MASK,
+				   UNIPHY_SGMII_MODE_SGMII);
+			break;
+		default:
+			dev_err(uniphy->dev, "Unsupported mode: %d\n",
+				uniphy->mode);
+			return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int uniphy_calibrate(struct qcom_eth_uniphy *uniphy)
+{
+	u32 val;
+
+	uniphy_rmw(uniphy, UNIPHY_PLL_CTRL, UNIPHY_PLL_CTRL_RST_N, 0);
+	msleep(1);
+	uniphy_rmw(uniphy, UNIPHY_PLL_CTRL, UNIPHY_PLL_CTRL_RST_N,
+		   UNIPHY_PLL_CTRL_RST_N);
+	msleep(1);
+
+	return read_poll_timeout(uniphy_read, val,
+				 (val & UNIPHY_CALIBRATION_DONE),
+				 100, 200000, false,
+				 uniphy, UNIPHY_CALIBRATION);
+}
+
+static int uniphy_clk_register(struct qcom_eth_uniphy *uniphy)
+{
+	unsigned long rate;
+	char name[64];
+	int ret;
+
+	switch (uniphy->mode) {
+		case QCOM_ETH_UNIPHY_MODE_SGMII:
+			rate = SGMII_CLK_RATE;
+			break;
+	}
+
+	snprintf(name, sizeof(name), "%s#rx", dev_name(uniphy->dev));
+	uniphy->clk_rx = clk_hw_register_fixed_rate(uniphy->dev, name,
+						    NULL, 0, rate);
+	if (IS_ERR_OR_NULL(uniphy->clk_rx))
+		return dev_err_probe(uniphy->dev, PTR_ERR(uniphy->clk_rx),
+				     "failed to register rx clock\n");
+
+	snprintf(name, sizeof(name), "%s#tx", dev_name(uniphy->dev));
+	uniphy->clk_tx = clk_hw_register_fixed_rate(uniphy->dev, name,
+						    NULL, 0, rate);
+	if (IS_ERR_OR_NULL(uniphy->clk_tx))
+		return dev_err_probe(uniphy->dev, PTR_ERR(uniphy->clk_tx),
+				     "failed to register rx clock\n");
+
+	uniphy->clk_data = devm_kzalloc(uniphy->dev,
+					struct_size(uniphy->clk_data, hws, 2),
+					GFP_KERNEL);
+	if (!uniphy->clk_data)
+		return dev_err_probe(uniphy->dev, -ENOMEM,
+				     "failed to allocate clk_data\n");
+
+	uniphy->clk_data->num = 2;
+	uniphy->clk_data->hws[0] = uniphy->clk_rx;
+	uniphy->clk_data->hws[1] = uniphy->clk_tx;
+	ret = of_clk_add_hw_provider(uniphy->dev->of_node,
+				     of_clk_hw_onecell_get,
+				     uniphy->clk_data);
+	if (ret)
+		return dev_err_probe(uniphy->dev, ret,
+				     "fail to register clock provider\n");
+
+	return 0;
+}
+
+static int qcom_eth_uniphy_calibrate(struct phy *phy)
+{
+	struct qcom_eth_uniphy *uniphy = phy_get_drvdata(phy);
+	dev_info(uniphy->dev, "calibrating\n");
+	return uniphy_calibrate(uniphy);
+}
+
+static const struct phy_ops qcom_eth_uniphy_ops = {
+	.calibrate = qcom_eth_uniphy_calibrate,
+	.owner = THIS_MODULE,
+};
+
+static int qcom_eth_uniphy_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct qcom_eth_uniphy *uniphy;
+	struct phy *phy;
+	struct phy_provider *phy_provider;
+	int ret;
+
+	ret = cmn_init(pdev);
+	if (ret)
+		return dev_err_probe(dev, ret,
+				     "failed to init cmn block\n");
+
+	uniphy = devm_kzalloc(dev, sizeof(*uniphy), GFP_KERNEL);
+	if (!uniphy)
+		return dev_err_probe(dev, -ENOMEM,
+				     "failed to allocate priv\n");
+
+	uniphy->dev = dev;
+	uniphy->base = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(uniphy->base))
+		return dev_err_probe(dev, PTR_ERR(uniphy->base),
+				     "failed to ioremap base\n");
+
+	uniphy->num_clks = devm_clk_bulk_get_all(uniphy->dev, &uniphy->clks);
+	if (uniphy->num_clks < 0)
+		return dev_err_probe(uniphy->dev, uniphy->num_clks,
+				     "failed to acquire clocks\n");
+
+	ret = clk_bulk_prepare_enable(uniphy->num_clks, uniphy->clks);
+	if (ret)
+		return dev_err_probe(uniphy->dev, ret,
+				     "failed to enable clocks\n");
+
+	uniphy->rst = devm_reset_control_array_get_exclusive(uniphy->dev);
+	if (IS_ERR_OR_NULL(uniphy->rst))
+		return dev_err_probe(uniphy->dev, PTR_ERR(uniphy->rst),
+				     "failed to acquire reset\n");
+
+	ret = reset_control_reset(uniphy->rst);
+	if (ret)
+		return dev_err_probe(uniphy->dev, ret,
+				     "failed to reset\n");
+
+	ret = uniphy_clkout_init(uniphy);
+	if (ret)
+		return dev_err_probe(dev, ret,
+				     "failed to init clkout\n");
+
+	ret = uniphy_mode_set(uniphy);
+	if (ret)
+		return dev_err_probe(dev, ret,
+				     "failed to set mode\n");
+
+	ret = uniphy_calibrate(uniphy);
+	if (ret)
+		return dev_err_probe(dev, ret,
+				     "failed to calibrate\n");
+
+	ret = uniphy_clk_register(uniphy);
+	if (ret)
+		return dev_err_probe(dev, ret,
+				     "failed to register clocks\n");
+
+	phy = devm_phy_create(dev, dev->of_node, &qcom_eth_uniphy_ops);
+	if (IS_ERR(phy))
+		return dev_err_probe(dev, PTR_ERR(phy),
+				     "failed to register phy\n");
+
+	phy_set_drvdata(phy, uniphy);
+
+	phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+	if (IS_ERR(phy_provider))
+		return dev_err_probe(dev, PTR_ERR(phy_provider),
+				     "failed to register phy provider\n");
+
+	return 0;
+}
+
+static const struct of_device_id qcom_eth_uniphy_of_match[] = {
+	{ .compatible = "qcom,ipq5018-eth-uniphy" },
+	{}
+};
+MODULE_DEVICE_TABLE(of, qcom_eth_uniphy_of_match);
+
+static struct platform_driver qcom_eth_uniphy_driver = {
+	.probe	= qcom_eth_uniphy_probe,
+	.driver	= {
+		.name		= "qcom-eth-uniphy",
+		.of_match_table	= qcom_eth_uniphy_of_match,
+	},
+};
+module_platform_driver(qcom_eth_uniphy_driver);
+
+MODULE_DESCRIPTION("Qualcomm ethernet uniphy driver");
+MODULE_AUTHOR("Ziyang Huang <hzyitc@outlook.com>");
diff --git a/include/dt-bindings/phy/qcom-eth-uniphy.h b/include/dt-bindings/phy/qcom-eth-uniphy.h
new file mode 100644
index 000000000000..038f82522ccd
--- /dev/null
+++ b/include/dt-bindings/phy/qcom-eth-uniphy.h
@@ -0,0 +1,15 @@ 
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef _DT_BINDINGS_PHY_QCOM_ETH_UNIPHY
+#define _DT_BINDINGS_PHY_QCOM_ETH_UNIPHY
+
+#define QCOM_ETH_UNIPHY_MODE_SGMII		0
+#define QCOM_ETH_UNIPHY_MODE_SGMII_PLUS		1
+
+#define QCOM_ETH_UNIPHY_CLKOUT_FREQ_25M		25000000
+#define QCOM_ETH_UNIPHY_CLKOUT_FREQ_50M		50000000
+
+#define QCOM_ETH_UNIPHY_CLKOUT_DS_1_5V		1500
+#define QCOM_ETH_UNIPHY_CLKOUT_DS_2_8V		2800
+
+#endif