All of lore.kernel.org
 help / color / mirror / Atom feed
From: Ziyang Huang <hzyitc@outlook.com>
To: mcoquelin.stm32@gmail.com
Cc: alexandre.torgue@foss.st.com, richardcochran@gmail.com,
	p.zabel@pengutronix.de, matthias.bgg@gmail.com,
	angelogioacchino.delregno@collabora.com,
	linux-kernel@vger.kernel.org,
	linux-stm32@st-md-mailman.stormreply.com,
	linux-arm-kernel@lists.infradead.org, netdev@vger.kernel.org,
	linux-mediatek@lists.infradead.org,
	Ziyang Huang <hzyitc@outlook.com>
Subject: [PATCH 2/8] phy: Introduce Qualcomm ethernet uniphy driver
Date: Sun, 21 Jan 2024 20:42:31 +0800	[thread overview]
Message-ID: <TYZPR01MB55568ACB534944D7DEB00C7AC9762@TYZPR01MB5556.apcprd01.prod.exchangelabs.com> (raw)
In-Reply-To: <TYZPR01MB55563BD6A2B78402E4BB44D4C9762@TYZPR01MB5556.apcprd01.prod.exchangelabs.com>

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

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
-- 
2.40.1


WARNING: multiple messages have this Message-ID (diff)
From: Ziyang Huang <hzyitc@outlook.com>
To: mcoquelin.stm32@gmail.com
Cc: alexandre.torgue@foss.st.com, richardcochran@gmail.com,
	p.zabel@pengutronix.de, matthias.bgg@gmail.com,
	angelogioacchino.delregno@collabora.com,
	linux-kernel@vger.kernel.org,
	linux-stm32@st-md-mailman.stormreply.com,
	linux-arm-kernel@lists.infradead.org, netdev@vger.kernel.org,
	linux-mediatek@lists.infradead.org,
	Ziyang Huang <hzyitc@outlook.com>
Subject: [PATCH 2/8] phy: Introduce Qualcomm ethernet uniphy driver
Date: Sun, 21 Jan 2024 20:42:31 +0800	[thread overview]
Message-ID: <TYZPR01MB55568ACB534944D7DEB00C7AC9762@TYZPR01MB5556.apcprd01.prod.exchangelabs.com> (raw)
In-Reply-To: <TYZPR01MB55563BD6A2B78402E4BB44D4C9762@TYZPR01MB5556.apcprd01.prod.exchangelabs.com>

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

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
-- 
2.40.1


_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

  parent reply	other threads:[~2024-01-21 12:43 UTC|newest]

Thread overview: 70+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-01-21 12:40 [PATCH 0/8] ipq5018: enable ethernet support Ziyang Huang
2024-01-21 12:40 ` Ziyang Huang
2024-01-21 12:42 ` [PATCH 1/8] net: phy: Introduce Qualcomm IPQ5018 internal PHY driver Ziyang Huang
2024-01-21 12:42   ` Ziyang Huang
2024-01-21 16:19   ` Andrew Lunn
2024-01-21 16:19     ` Andrew Lunn
2024-01-22 15:37     ` Ziyang Huang
2024-01-22 15:37       ` Ziyang Huang
2024-01-22 17:18       ` Andrew Lunn
2024-01-22 17:18         ` Andrew Lunn
2024-01-23 15:38         ` Ziyang Huang
2024-01-23 15:38           ` Ziyang Huang
2024-01-23 23:15           ` Andrew Lunn
2024-01-23 23:15             ` Andrew Lunn
2024-01-21 12:42 ` Ziyang Huang [this message]
2024-01-21 12:42   ` [PATCH 2/8] phy: Introduce Qualcomm ethernet uniphy driver Ziyang Huang
2024-01-23 15:58   ` Ziyang Huang
2024-01-23 15:58     ` Ziyang Huang
2024-01-23 23:25     ` Andrew Lunn
2024-01-23 23:25       ` Andrew Lunn
2024-01-21 12:42 ` [PATCH 3/8] net: stmmac: Introduce Qualcomm IPQ50xx DWMAC driver Ziyang Huang
2024-01-21 12:42   ` Ziyang Huang
2024-01-24  5:54   ` kernel test robot
2024-01-24  5:54     ` kernel test robot
2024-01-24  9:40   ` kernel test robot
2024-01-24  9:40     ` kernel test robot
2024-01-21 12:42 ` [PATCH 4/8] clk: qcom: gcc-ipq5018: correct gcc_gmac0_sys_clk reg Ziyang Huang
2024-01-21 12:42   ` Ziyang Huang
2024-01-21 16:28   ` Andrew Lunn
2024-01-21 16:28     ` Andrew Lunn
2024-01-22 15:39     ` Ziyang Huang
2024-01-22 15:39       ` Ziyang Huang
2024-01-21 12:42 ` [PATCH 5/8] clk: qcom: support for duplicate freq in RCG2 freq table Ziyang Huang
2024-01-21 12:42   ` Ziyang Huang
2024-01-21 16:57   ` Andrew Lunn
2024-01-21 16:57     ` Andrew Lunn
2024-01-22 16:35     ` Ziyang Huang
2024-01-22 16:35       ` Ziyang Huang
2024-01-22 17:34       ` Andrew Lunn
2024-01-22 17:34         ` Andrew Lunn
2024-01-23 15:43         ` Ziyang Huang
2024-01-23 15:43           ` Ziyang Huang
2024-01-22  7:55   ` Krzysztof Kozlowski
2024-01-22  7:55     ` Krzysztof Kozlowski
2024-01-22 14:48     ` Ziyang Huang
2024-01-22 14:48       ` Ziyang Huang
2024-01-21 12:42 ` [PATCH 6/8] net: mdio: ipq4019: support reset control Ziyang Huang
2024-01-21 12:42   ` Ziyang Huang
2024-01-21 16:35   ` Andrew Lunn
2024-01-21 16:35     ` Andrew Lunn
2024-01-22 15:52     ` Ziyang Huang
2024-01-22 15:52       ` Ziyang Huang
2024-01-21 12:42 ` [PATCH 7/8] arm64: dts: qcom: ipq5018: enable ethernet support Ziyang Huang
2024-01-21 12:42   ` Ziyang Huang
2024-01-21 16:45   ` Andrew Lunn
2024-01-21 16:45     ` Andrew Lunn
2024-01-22 15:52     ` Ziyang Huang
2024-01-22 15:52       ` Ziyang Huang
2024-01-22 17:27       ` Andrew Lunn
2024-01-22 17:27         ` Andrew Lunn
2024-01-21 12:42 ` [PATCH 8/8] arm64: dts: qcom: ipq5018-rdp432-c2: " Ziyang Huang
2024-01-21 12:42   ` Ziyang Huang
2024-01-22  7:54   ` Krzysztof Kozlowski
2024-01-22  7:54     ` Krzysztof Kozlowski
2024-01-24  0:53   ` kernel test robot
2024-01-24  0:53     ` kernel test robot
2024-01-21 15:51 ` [PATCH 0/8] ipq5018: " Andrew Lunn
2024-01-21 15:51   ` Andrew Lunn
2024-01-22 14:45   ` Ziyang Huang
2024-01-22 14:45     ` Ziyang Huang

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=TYZPR01MB55568ACB534944D7DEB00C7AC9762@TYZPR01MB5556.apcprd01.prod.exchangelabs.com \
    --to=hzyitc@outlook.com \
    --cc=alexandre.torgue@foss.st.com \
    --cc=angelogioacchino.delregno@collabora.com \
    --cc=linux-arm-kernel@lists.infradead.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-mediatek@lists.infradead.org \
    --cc=linux-stm32@st-md-mailman.stormreply.com \
    --cc=matthias.bgg@gmail.com \
    --cc=mcoquelin.stm32@gmail.com \
    --cc=netdev@vger.kernel.org \
    --cc=p.zabel@pengutronix.de \
    --cc=richardcochran@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.