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
next prev 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: linkBe 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.