Lets make the PCS one actually apply and refresh the rest. Signed-off-by: Robert Marko <robert.marko@sartura.hr>
300 lines
8.5 KiB
Diff
300 lines
8.5 KiB
Diff
From c03b5a514b319c03a1883142694afa5deb69af6f Mon Sep 17 00:00:00 2001
|
|
From: Lei Wei <quic_leiwei@quicinc.com>
|
|
Date: Fri, 7 Feb 2025 23:53:13 +0800
|
|
Subject: [PATCH] net: pcs: Add PCS driver for Qualcomm IPQ9574 SoC
|
|
|
|
The 'UNIPHY' PCS hardware block in Qualcomm's IPQ SoC supports
|
|
different interface modes to enable Ethernet MAC connections
|
|
for different types of external PHYs/switch. Each UNIPHY block
|
|
includes a SerDes and PCS/XPCS blocks, and can operate in either
|
|
PCS or XPCS modes. It supports 1Gbps and 2.5Gbps interface modes
|
|
(Ex: SGMII) using the PCS, and 10Gbps interface modes (Ex: USXGMII)
|
|
using the XPCS. There are three UNIPHY (PCS) instances in IPQ9574
|
|
SoC which support the six Ethernet ports in the SoC.
|
|
|
|
This patch adds support for the platform driver, probe and clock
|
|
registrations for the PCS driver. The platform driver creates an
|
|
'ipq_pcs' instance for each of the UNIPHY used on the given board.
|
|
|
|
Signed-off-by: Lei Wei <quic_leiwei@quicinc.com>
|
|
---
|
|
drivers/net/pcs/Kconfig | 9 ++
|
|
drivers/net/pcs/Makefile | 1 +
|
|
drivers/net/pcs/pcs-qcom-ipq9574.c | 245 +++++++++++++++++++++++++++++
|
|
3 files changed, 255 insertions(+)
|
|
create mode 100644 drivers/net/pcs/pcs-qcom-ipq9574.c
|
|
|
|
--- a/drivers/net/pcs/Kconfig
|
|
+++ b/drivers/net/pcs/Kconfig
|
|
@@ -46,6 +46,15 @@ config PCS_MTK_USXGMII
|
|
1000Base-X, 2500Base-X and Cisco SGMII are supported on the same
|
|
differential pairs via an embedded LynxI PCS.
|
|
|
|
+config PCS_QCOM_IPQ9574
|
|
+ tristate "Qualcomm IPQ9574 PCS"
|
|
+ depends on OF && (ARCH_QCOM || COMPILE_TEST)
|
|
+ depends on HAS_IOMEM && COMMON_CLK
|
|
+ help
|
|
+ This module provides driver for UNIPHY PCS available on Qualcomm
|
|
+ IPQ9574 SoC. The UNIPHY PCS supports both PCS and XPCS functions
|
|
+ to support different interface modes for MAC to PHY connections.
|
|
+
|
|
config PCS_RZN1_MIIC
|
|
tristate "Renesas RZ/N1, RZ/N2H, RZ/T2H MII converter"
|
|
depends on OF
|
|
--- a/drivers/net/pcs/Makefile
|
|
+++ b/drivers/net/pcs/Makefile
|
|
@@ -10,3 +10,4 @@ obj-$(CONFIG_PCS_LYNX) += pcs-lynx.o
|
|
obj-$(CONFIG_PCS_MTK_LYNXI) += pcs-mtk-lynxi.o
|
|
obj-$(CONFIG_PCS_MTK_USXGMII) += pcs-mtk-usxgmii.o
|
|
obj-$(CONFIG_PCS_RZN1_MIIC) += pcs-rzn1-miic.o
|
|
+obj-$(CONFIG_PCS_QCOM_IPQ9574) += pcs-qcom-ipq9574.o
|
|
--- /dev/null
|
|
+++ b/drivers/net/pcs/pcs-qcom-ipq9574.c
|
|
@@ -0,0 +1,245 @@
|
|
+// SPDX-License-Identifier: GPL-2.0-only
|
|
+/*
|
|
+ * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
|
|
+ */
|
|
+
|
|
+#include <linux/clk.h>
|
|
+#include <linux/clk-provider.h>
|
|
+#include <linux/device.h>
|
|
+#include <linux/phy.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <linux/regmap.h>
|
|
+
|
|
+#include <dt-bindings/net/qcom,ipq9574-pcs.h>
|
|
+
|
|
+#define XPCS_INDIRECT_ADDR 0x8000
|
|
+#define XPCS_INDIRECT_AHB_ADDR 0x83fc
|
|
+#define XPCS_INDIRECT_ADDR_H GENMASK(20, 8)
|
|
+#define XPCS_INDIRECT_ADDR_L GENMASK(7, 0)
|
|
+#define XPCS_INDIRECT_DATA_ADDR(reg) (FIELD_PREP(GENMASK(15, 10), 0x20) | \
|
|
+ FIELD_PREP(GENMASK(9, 2), \
|
|
+ FIELD_GET(XPCS_INDIRECT_ADDR_L, reg)))
|
|
+
|
|
+/* PCS private data */
|
|
+struct ipq_pcs {
|
|
+ struct device *dev;
|
|
+ void __iomem *base;
|
|
+ struct regmap *regmap;
|
|
+ phy_interface_t interface;
|
|
+
|
|
+ /* RX clock supplied to NSSCC */
|
|
+ struct clk_hw rx_hw;
|
|
+ /* TX clock supplied to NSSCC */
|
|
+ struct clk_hw tx_hw;
|
|
+};
|
|
+
|
|
+static unsigned long ipq_pcs_clk_rate_get(struct ipq_pcs *qpcs)
|
|
+{
|
|
+ switch (qpcs->interface) {
|
|
+ case PHY_INTERFACE_MODE_USXGMII:
|
|
+ return 312500000;
|
|
+ default:
|
|
+ return 125000000;
|
|
+ }
|
|
+}
|
|
+
|
|
+/* Return clock rate for the RX clock supplied to NSSCC
|
|
+ * as per the interface mode.
|
|
+ */
|
|
+static unsigned long ipq_pcs_rx_clk_recalc_rate(struct clk_hw *hw,
|
|
+ unsigned long parent_rate)
|
|
+{
|
|
+ struct ipq_pcs *qpcs = container_of(hw, struct ipq_pcs, rx_hw);
|
|
+
|
|
+ return ipq_pcs_clk_rate_get(qpcs);
|
|
+}
|
|
+
|
|
+/* Return clock rate for the TX clock supplied to NSSCC
|
|
+ * as per the interface mode.
|
|
+ */
|
|
+static unsigned long ipq_pcs_tx_clk_recalc_rate(struct clk_hw *hw,
|
|
+ unsigned long parent_rate)
|
|
+{
|
|
+ struct ipq_pcs *qpcs = container_of(hw, struct ipq_pcs, tx_hw);
|
|
+
|
|
+ return ipq_pcs_clk_rate_get(qpcs);
|
|
+}
|
|
+
|
|
+static int ipq_pcs_clk_determine_rate(struct clk_hw *hw,
|
|
+ struct clk_rate_request *req)
|
|
+{
|
|
+ switch (req->rate) {
|
|
+ case 125000000:
|
|
+ case 312500000:
|
|
+ return 0;
|
|
+ default:
|
|
+ return -EINVAL;
|
|
+ }
|
|
+}
|
|
+
|
|
+/* Clock ops for the RX clock supplied to NSSCC */
|
|
+static const struct clk_ops ipq_pcs_rx_clk_ops = {
|
|
+ .determine_rate = ipq_pcs_clk_determine_rate,
|
|
+ .recalc_rate = ipq_pcs_rx_clk_recalc_rate,
|
|
+};
|
|
+
|
|
+/* Clock ops for the TX clock supplied to NSSCC */
|
|
+static const struct clk_ops ipq_pcs_tx_clk_ops = {
|
|
+ .determine_rate = ipq_pcs_clk_determine_rate,
|
|
+ .recalc_rate = ipq_pcs_tx_clk_recalc_rate,
|
|
+};
|
|
+
|
|
+static struct clk_hw *ipq_pcs_clk_hw_get(struct of_phandle_args *clkspec,
|
|
+ void *data)
|
|
+{
|
|
+ struct ipq_pcs *qpcs = data;
|
|
+
|
|
+ switch (clkspec->args[0]) {
|
|
+ case PCS_RX_CLK:
|
|
+ return &qpcs->rx_hw;
|
|
+ case PCS_TX_CLK:
|
|
+ return &qpcs->tx_hw;
|
|
+ }
|
|
+
|
|
+ return ERR_PTR(-EINVAL);
|
|
+}
|
|
+
|
|
+/* Register the RX and TX clock which are output from SerDes to
|
|
+ * the NSSCC. The NSSCC driver assigns the RX and TX clock as
|
|
+ * parent, divides them to generate the MII RX and TX clock to
|
|
+ * each MII interface of the PCS as per the link speeds and
|
|
+ * interface modes.
|
|
+ */
|
|
+static int ipq_pcs_clk_register(struct ipq_pcs *qpcs)
|
|
+{
|
|
+ struct clk_init_data init = { };
|
|
+ int ret;
|
|
+
|
|
+ init.ops = &ipq_pcs_rx_clk_ops;
|
|
+ init.name = devm_kasprintf(qpcs->dev, GFP_KERNEL, "%s::rx_clk",
|
|
+ dev_name(qpcs->dev));
|
|
+ if (!init.name)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ qpcs->rx_hw.init = &init;
|
|
+ ret = devm_clk_hw_register(qpcs->dev, &qpcs->rx_hw);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ init.ops = &ipq_pcs_tx_clk_ops;
|
|
+ init.name = devm_kasprintf(qpcs->dev, GFP_KERNEL, "%s::tx_clk",
|
|
+ dev_name(qpcs->dev));
|
|
+ if (!init.name)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ qpcs->tx_hw.init = &init;
|
|
+ ret = devm_clk_hw_register(qpcs->dev, &qpcs->tx_hw);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ return devm_of_clk_add_hw_provider(qpcs->dev, ipq_pcs_clk_hw_get, qpcs);
|
|
+}
|
|
+
|
|
+static int ipq_pcs_regmap_read(void *context, unsigned int reg,
|
|
+ unsigned int *val)
|
|
+{
|
|
+ struct ipq_pcs *qpcs = context;
|
|
+
|
|
+ /* PCS uses direct AHB access while XPCS uses indirect AHB access */
|
|
+ if (reg >= XPCS_INDIRECT_ADDR) {
|
|
+ writel(FIELD_GET(XPCS_INDIRECT_ADDR_H, reg),
|
|
+ qpcs->base + XPCS_INDIRECT_AHB_ADDR);
|
|
+ *val = readl(qpcs->base + XPCS_INDIRECT_DATA_ADDR(reg));
|
|
+ } else {
|
|
+ *val = readl(qpcs->base + reg);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ipq_pcs_regmap_write(void *context, unsigned int reg,
|
|
+ unsigned int val)
|
|
+{
|
|
+ struct ipq_pcs *qpcs = context;
|
|
+
|
|
+ /* PCS uses direct AHB access while XPCS uses indirect AHB access */
|
|
+ if (reg >= XPCS_INDIRECT_ADDR) {
|
|
+ writel(FIELD_GET(XPCS_INDIRECT_ADDR_H, reg),
|
|
+ qpcs->base + XPCS_INDIRECT_AHB_ADDR);
|
|
+ writel(val, qpcs->base + XPCS_INDIRECT_DATA_ADDR(reg));
|
|
+ } else {
|
|
+ writel(val, qpcs->base + reg);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct regmap_config ipq_pcs_regmap_cfg = {
|
|
+ .reg_bits = 32,
|
|
+ .val_bits = 32,
|
|
+ .reg_read = ipq_pcs_regmap_read,
|
|
+ .reg_write = ipq_pcs_regmap_write,
|
|
+ .fast_io = true,
|
|
+};
|
|
+
|
|
+static int ipq9574_pcs_probe(struct platform_device *pdev)
|
|
+{
|
|
+ struct device *dev = &pdev->dev;
|
|
+ struct ipq_pcs *qpcs;
|
|
+ struct clk *clk;
|
|
+ int ret;
|
|
+
|
|
+ qpcs = devm_kzalloc(dev, sizeof(*qpcs), GFP_KERNEL);
|
|
+ if (!qpcs)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ qpcs->dev = dev;
|
|
+
|
|
+ qpcs->base = devm_platform_ioremap_resource(pdev, 0);
|
|
+ if (IS_ERR(qpcs->base))
|
|
+ return dev_err_probe(dev, PTR_ERR(qpcs->base),
|
|
+ "Failed to ioremap resource\n");
|
|
+
|
|
+ qpcs->regmap = devm_regmap_init(dev, NULL, qpcs, &ipq_pcs_regmap_cfg);
|
|
+ if (IS_ERR(qpcs->regmap))
|
|
+ return dev_err_probe(dev, PTR_ERR(qpcs->regmap),
|
|
+ "Failed to allocate register map\n");
|
|
+
|
|
+ clk = devm_clk_get_enabled(dev, "sys");
|
|
+ if (IS_ERR(clk))
|
|
+ return dev_err_probe(dev, PTR_ERR(clk),
|
|
+ "Failed to enable SYS clock\n");
|
|
+
|
|
+ clk = devm_clk_get_enabled(dev, "ahb");
|
|
+ if (IS_ERR(clk))
|
|
+ return dev_err_probe(dev, PTR_ERR(clk),
|
|
+ "Failed to enable AHB clock\n");
|
|
+
|
|
+ ret = ipq_pcs_clk_register(qpcs);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ platform_set_drvdata(pdev, qpcs);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct of_device_id ipq9574_pcs_of_mtable[] = {
|
|
+ { .compatible = "qcom,ipq9574-pcs" },
|
|
+ { /* sentinel */ },
|
|
+};
|
|
+MODULE_DEVICE_TABLE(of, ipq9574_pcs_of_mtable);
|
|
+
|
|
+static struct platform_driver ipq9574_pcs_driver = {
|
|
+ .driver = {
|
|
+ .name = "ipq9574_pcs",
|
|
+ .suppress_bind_attrs = true,
|
|
+ .of_match_table = ipq9574_pcs_of_mtable,
|
|
+ },
|
|
+ .probe = ipq9574_pcs_probe,
|
|
+};
|
|
+module_platform_driver(ipq9574_pcs_driver);
|
|
+
|
|
+MODULE_LICENSE("GPL");
|
|
+MODULE_DESCRIPTION("Qualcomm IPQ9574 PCS driver");
|
|
+MODULE_AUTHOR("Lei Wei <quic_leiwei@quicinc.com>");
|