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

From: Ziyang Huang
Date: Sun Jan 21 2024 - 07:43:39 EST


Signed-off-by: Ziyang Huang <hzyitc@xxxxxxxxxxx>
---
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

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@xxxxxxxxxxx>");
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
--
2.40.1