
Introduce support for the Qualcomm IPQ50xx SoC. This series adds support for the following components: - minimal boot support: GCC/pinctrl/watchdog/CPUFreq/SDI (upstreamed) - USB2 (upstreamed) - Thermal/Tsens - PCIe gen2 1&2-lane PHY and controller - PWM and PWM LED - QPIC SPI NAND controller - CMN PLL Block (provider of fixed rate clocks to GCC/ethernet/more.) - Ethernet: IPQ5018 Internal GE PHY (1 gbps) - Remoteproc MPD driver for IPQ5018 (2.4G) & QCN6122 (5/6G) Wifi Co-developed-by: Ziyang Huang <hzyitc@outlook.com> Signed-off-by: Ziyang Huang <hzyitc@outlook.com> Signed-off-by: George Moussalem <george.moussalem@outlook.com> Link: https://github.com/openwrt/openwrt/pull/17182 Signed-off-by: Robert Marko <robimarko@gmail.com>
185 lines
5.4 KiB
Diff
185 lines
5.4 KiB
Diff
From 77ad12b3a5e21cae859247c0b82cf9a5b661e531 Mon Sep 17 00:00:00 2001
|
|
From: Ziyang Huang <hzyitc@outlook.com>
|
|
Date: Sun, 8 Sep 2024 16:40:11 +0800
|
|
Subject: [PATCH 1/3] net: phy: qcom: Introduce IPQ5018 internal PHY driver
|
|
|
|
Introduce the internal GE PHY driver, part of the Qualcomm IPQ50xx SoC.
|
|
The driver registers two clock providers needed and referenced by the GCC
|
|
using DT properties and phandles.
|
|
|
|
Signed-off-by: Ziyang Huang <hzyitc@outlook.com>
|
|
Signed-off-by: George Moussalem <george.moussalem@outlook.com>
|
|
---
|
|
drivers/net/phy/qcom/Kconfig | 6 ++
|
|
drivers/net/phy/qcom/Makefile | 1 +
|
|
drivers/net/phy/qcom/ipq5018.c | 138 +++++++++++++++++++++++++++++++++
|
|
3 files changed, 145 insertions(+)
|
|
create mode 100644 drivers/net/phy/qcom/ipq5018.c
|
|
|
|
--- a/drivers/net/phy/qcom/Kconfig
|
|
+++ b/drivers/net/phy/qcom/Kconfig
|
|
@@ -9,6 +9,12 @@ config AT803X_PHY
|
|
help
|
|
Currently supports the AR8030, AR8031, AR8033, AR8035 model
|
|
|
|
+config IPQ5018_PHY
|
|
+ tristate "Qualcomm IPQ5018 internal PHYs"
|
|
+ select QCOM_NET_PHYLIB
|
|
+ help
|
|
+ Currently supports the Qualcomm IPQ5018 internal PHY
|
|
+
|
|
config QCA83XX_PHY
|
|
tristate "Qualcomm Atheros QCA833x PHYs"
|
|
select QCOM_NET_PHYLIB
|
|
--- a/drivers/net/phy/qcom/Makefile
|
|
+++ b/drivers/net/phy/qcom/Makefile
|
|
@@ -1,6 +1,7 @@
|
|
# SPDX-License-Identifier: GPL-2.0
|
|
obj-$(CONFIG_QCOM_NET_PHYLIB) += qcom-phy-lib.o
|
|
obj-$(CONFIG_AT803X_PHY) += at803x.o
|
|
+obj-$(CONFIG_IPQ5018_PHY) += ipq5018.o
|
|
obj-$(CONFIG_QCA83XX_PHY) += qca83xx.o
|
|
obj-$(CONFIG_QCA808X_PHY) += qca808x.o
|
|
obj-$(CONFIG_QCA807X_PHY) += qca807x.o
|
|
--- /dev/null
|
|
+++ b/drivers/net/phy/qcom/ipq5018.c
|
|
@@ -0,0 +1,138 @@
|
|
+#include <linux/bitfield.h>
|
|
+#include <linux/clk.h>
|
|
+#include <linux/clk-provider.h>
|
|
+#include <linux/phy.h>
|
|
+#include <linux/reset.h>
|
|
+
|
|
+#include "qcom.h"
|
|
+
|
|
+#define IPQ5018_PHY_ID 0x004dd0c0
|
|
+
|
|
+#define TX_RX_CLK_RATE 125000000 /* 125M */
|
|
+
|
|
+#define IPQ5018_PHY_FIFO_CONTROL 0x19
|
|
+#define IPQ5018_PHY_FIFO_RESET GENMASK(1, 0)
|
|
+
|
|
+struct ipq5018_phy {
|
|
+ int num_clks;
|
|
+ struct clk_bulk_data *clks;
|
|
+ struct reset_control *rst;
|
|
+
|
|
+ struct clk_hw *clk_rx, *clk_tx;
|
|
+ struct clk_hw_onecell_data *clk_data;
|
|
+};
|
|
+
|
|
+static int ipq5018_probe(struct phy_device *phydev)
|
|
+{
|
|
+ struct ipq5018_phy *priv;
|
|
+ struct device *dev = &phydev->mdio.dev;
|
|
+ char name[64];
|
|
+ int ret;
|
|
+
|
|
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
|
+ if (!priv)
|
|
+ return dev_err_probe(dev, -ENOMEM,
|
|
+ "failed to allocate priv\n");
|
|
+
|
|
+ priv->num_clks = devm_clk_bulk_get_all(dev, &priv->clks);
|
|
+ if (priv->num_clks < 0)
|
|
+ return dev_err_probe(dev, priv->num_clks,
|
|
+ "failed to acquire clocks\n");
|
|
+
|
|
+ ret = clk_bulk_prepare_enable(priv->num_clks, priv->clks);
|
|
+ if (ret)
|
|
+ return dev_err_probe(dev, ret,
|
|
+ "failed to enable clocks\n");
|
|
+
|
|
+ priv->rst = devm_reset_control_array_get_exclusive(dev);
|
|
+ if (IS_ERR_OR_NULL(priv->rst))
|
|
+ return dev_err_probe(dev, PTR_ERR(priv->rst),
|
|
+ "failed to acquire reset\n");
|
|
+
|
|
+ ret = reset_control_reset(priv->rst);
|
|
+ if (ret)
|
|
+ return dev_err_probe(dev, ret,
|
|
+ "failed to reset\n");
|
|
+
|
|
+ snprintf(name, sizeof(name), "%s#rx", dev_name(dev));
|
|
+ priv->clk_rx = clk_hw_register_fixed_rate(dev, name, NULL, 0,
|
|
+ TX_RX_CLK_RATE);
|
|
+ if (IS_ERR_OR_NULL(priv->clk_rx))
|
|
+ return dev_err_probe(dev, PTR_ERR(priv->clk_rx),
|
|
+ "failed to register rx clock\n");
|
|
+
|
|
+ snprintf(name, sizeof(name), "%s#tx", dev_name(dev));
|
|
+ priv->clk_tx = clk_hw_register_fixed_rate(dev, name, NULL, 0,
|
|
+ TX_RX_CLK_RATE);
|
|
+ if (IS_ERR_OR_NULL(priv->clk_tx))
|
|
+ return dev_err_probe(dev, PTR_ERR(priv->clk_tx),
|
|
+ "failed to register tx clock\n");
|
|
+
|
|
+ priv->clk_data = devm_kzalloc(dev,
|
|
+ struct_size(priv->clk_data, hws, 2),
|
|
+ GFP_KERNEL);
|
|
+ if (!priv->clk_data)
|
|
+ return dev_err_probe(dev, -ENOMEM,
|
|
+ "failed to allocate clk_data\n");
|
|
+
|
|
+ priv->clk_data->num = 2;
|
|
+ priv->clk_data->hws[0] = priv->clk_rx;
|
|
+ priv->clk_data->hws[1] = priv->clk_tx;
|
|
+ ret = of_clk_add_hw_provider(dev->of_node, of_clk_hw_onecell_get,
|
|
+ priv->clk_data);
|
|
+ if (ret)
|
|
+ return dev_err_probe(dev, ret,
|
|
+ "fail to register clock provider\n");
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ipq5018_soft_reset(struct phy_device *phydev)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ ret = phy_modify(phydev, IPQ5018_PHY_FIFO_CONTROL,
|
|
+ IPQ5018_PHY_FIFO_RESET, 0);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ msleep(50);
|
|
+
|
|
+ ret = phy_modify(phydev, IPQ5018_PHY_FIFO_CONTROL,
|
|
+ IPQ5018_PHY_FIFO_RESET, IPQ5018_PHY_FIFO_RESET);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ipq5018_cable_test_start(struct phy_device *phydev)
|
|
+{
|
|
+ /* we do all the (time consuming) work later */
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static struct phy_driver ipq5018_internal_phy_driver[] = {
|
|
+ {
|
|
+ PHY_ID_MATCH_EXACT(IPQ5018_PHY_ID),
|
|
+ .name = "Qualcomm IPQ5018 internal PHY",
|
|
+ .flags = PHY_IS_INTERNAL | PHY_POLL_CABLE_TEST,
|
|
+ .probe = ipq5018_probe,
|
|
+ .soft_reset = ipq5018_soft_reset,
|
|
+ .read_status = at803x_read_status,
|
|
+ .config_intr = at803x_config_intr,
|
|
+ .handle_interrupt = at803x_handle_interrupt,
|
|
+ .cable_test_start = ipq5018_cable_test_start,
|
|
+ .cable_test_get_status = qca808x_cable_test_get_status,
|
|
+ },
|
|
+};
|
|
+module_phy_driver(ipq5018_internal_phy_driver);
|
|
+
|
|
+static struct mdio_device_id __maybe_unused ipq5018_internal_phy_ids[] = {
|
|
+ { PHY_ID_MATCH_EXACT(IPQ5018_PHY_ID) },
|
|
+ { }
|
|
+};
|
|
+MODULE_DEVICE_TABLE(mdio, ipq5018_internal_phy_ids);
|
|
+
|
|
+MODULE_DESCRIPTION("Qualcomm IPQ5018 internal PHY driver");
|
|
+MODULE_AUTHOR("Ziyang Huang <hzyitc@outlook.com>");
|