forked from Openwrt-EcoNet/openwrt
Add the v5 of the PCS patch. This is the latest submission as of this writing. THe last four patches are not part of the submission. They make the series work with v6.12 kernel, resolve a circular dependency with the clocks, and add the DTS node. Include them as bundle. Link: https://lore.kernel.org/lkml/20250207-ipq_pcs_6-14_rc1-v5-0-be2ebec32921@quicinc.com/ Signed-off-by: Alexandru Gagniuc <mr.nuke.me@gmail.com> Link: https://github.com/openwrt/openwrt/pull/18796 Signed-off-by: Robert Marko <robimarko@gmail.com>
363 lines
9.4 KiB
Diff
363 lines
9.4 KiB
Diff
From 7de372abe7a4b5b380fdbeedd268445f234990c8 Mon Sep 17 00:00:00 2001
|
|
From: Lei Wei <quic_leiwei@quicinc.com>
|
|
Date: Mon, 29 Jan 2024 11:39:36 +0800
|
|
Subject: [PATCH] net: pcs: qcom-ipq9574: add changes not submitted upstream
|
|
|
|
Was ("net: pcs: Add driver for Qualcomm IPQ UNIPHY PCS").
|
|
|
|
The UNIPHY hardware block in Qualcomm's IPQ SoC based boards enables
|
|
PCS and XPCS functions, and helps in interfacing the Ethernet MAC in
|
|
IPQ SoC to external PHYs.
|
|
|
|
This patch adds the PCS driver support for the UNIPHY hardware used in
|
|
IPQ SoC based boards. Support for SGMII/QSGMII/PSGMII and USXGMII
|
|
interface modes are added in the driver.
|
|
|
|
Change-Id: Id2c8f993f121098f7b02186b53770b75bb539a93
|
|
Signed-off-by: Lei Wei <quic_leiwei@quicinc.com>
|
|
Alex G: Rebase original patch on top of 20250207 uniphy submission
|
|
Remove mutex that is not required according to
|
|
https://lore.kernel.org/lkml/Z3ZwURgIErzpzpEr@shell.armlinux.org.uk/
|
|
Signed-off-by: Alexandru Gagniuc <mr.nuke.me@gmail.com>
|
|
---
|
|
drivers/net/pcs/pcs-qcom-ipq9574.c | 180 +++++++++++++++++++++++-
|
|
include/linux/pcs/pcs-qcom-ipq-uniphy.h | 13 ++
|
|
2 files changed, 192 insertions(+), 1 deletion(-)
|
|
create mode 100644 include/linux/pcs/pcs-qcom-ipq-uniphy.h
|
|
|
|
--- a/drivers/net/pcs/pcs-qcom-ipq9574.c
|
|
+++ b/drivers/net/pcs/pcs-qcom-ipq9574.c
|
|
@@ -9,10 +9,12 @@
|
|
#include <linux/of.h>
|
|
#include <linux/of_platform.h>
|
|
#include <linux/pcs/pcs-qcom-ipq9574.h>
|
|
+#include <linux/pcs/pcs-qcom-ipq-uniphy.h>
|
|
#include <linux/phy.h>
|
|
#include <linux/phylink.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/regmap.h>
|
|
+#include <linux/reset.h>
|
|
|
|
#include <dt-bindings/net/qcom,ipq9574-pcs.h>
|
|
|
|
@@ -26,6 +28,7 @@
|
|
#define PCS_MODE_SEL_MASK GENMASK(12, 8)
|
|
#define PCS_MODE_SGMII FIELD_PREP(PCS_MODE_SEL_MASK, 0x4)
|
|
#define PCS_MODE_QSGMII FIELD_PREP(PCS_MODE_SEL_MASK, 0x1)
|
|
+#define PCS_MODE_PSGMII FIELD_PREP(PCS_MODE_SEL_MASK, 0x2)
|
|
#define PCS_MODE_XPCS FIELD_PREP(PCS_MODE_SEL_MASK, 0x10)
|
|
|
|
#define PCS_MII_CTRL(x) (0x480 + 0x18 * (x))
|
|
@@ -43,6 +46,8 @@
|
|
#define PCS_MII_STS_SPEED_10 0
|
|
#define PCS_MII_STS_SPEED_100 1
|
|
#define PCS_MII_STS_SPEED_1000 2
|
|
+#define PCS_MII_STS_PAUSE_TX_EN BIT(1)
|
|
+#define PCS_MII_STS_PAUSE_RX_EN BIT(0)
|
|
|
|
#define PCS_PLL_RESET 0x780
|
|
#define PCS_ANA_SW_RESET BIT(6)
|
|
@@ -95,12 +100,35 @@ struct ipq_pcs_mii {
|
|
struct clk *tx_clk;
|
|
};
|
|
|
|
+/* UNIPHY PCS reset ID */
|
|
+enum {
|
|
+ PCS_SYS_RESET,
|
|
+ PCS_AHB_RESET,
|
|
+ XPCS_RESET,
|
|
+ PCS_RESET_MAX
|
|
+};
|
|
+
|
|
+/* UNIPHY PCS reset name */
|
|
+static const char *const pcs_reset_name[PCS_RESET_MAX] = {
|
|
+ "sys",
|
|
+ "ahb",
|
|
+ "xpcs",
|
|
+};
|
|
+
|
|
+/* UNIPHY PCS channel clock ID */
|
|
+enum {
|
|
+ PCS_CH_RX_CLK,
|
|
+ PCS_CH_TX_CLK,
|
|
+ PCS_CH_CLK_MAX
|
|
+};
|
|
+
|
|
/* PCS private data */
|
|
struct ipq_pcs {
|
|
struct device *dev;
|
|
void __iomem *base;
|
|
struct regmap *regmap;
|
|
phy_interface_t interface;
|
|
+ struct reset_control *reset[PCS_RESET_MAX];
|
|
|
|
/* RX clock supplied to NSSCC */
|
|
struct clk_hw rx_hw;
|
|
@@ -150,6 +178,11 @@ static void ipq_pcs_get_state_sgmii(stru
|
|
state->duplex = DUPLEX_FULL;
|
|
else
|
|
state->duplex = DUPLEX_HALF;
|
|
+
|
|
+ if (val & PCS_MII_STS_PAUSE_TX_EN)
|
|
+ state->pause |= MLO_PAUSE_TX;
|
|
+ if (val & PCS_MII_STS_PAUSE_RX_EN)
|
|
+ state->pause |= MLO_PAUSE_RX;
|
|
}
|
|
|
|
static void ipq_pcs_get_state_usxgmii(struct ipq_pcs *qpcs,
|
|
@@ -203,6 +236,9 @@ static int ipq_pcs_config_mode(struct ip
|
|
unsigned int val;
|
|
int ret;
|
|
|
|
+ /* Assert XPCS reset */
|
|
+ reset_control_assert(qpcs->reset[XPCS_RESET]);
|
|
+
|
|
/* Configure PCS interface mode */
|
|
switch (interface) {
|
|
case PHY_INTERFACE_MODE_SGMII:
|
|
@@ -211,11 +247,16 @@ static int ipq_pcs_config_mode(struct ip
|
|
case PHY_INTERFACE_MODE_QSGMII:
|
|
val = PCS_MODE_QSGMII;
|
|
break;
|
|
+ case PHY_INTERFACE_MODE_PSGMII:
|
|
+ val = PCS_MODE_PSGMII;
|
|
+ break;
|
|
case PHY_INTERFACE_MODE_USXGMII:
|
|
val = PCS_MODE_XPCS;
|
|
rate = 312500000;
|
|
break;
|
|
default:
|
|
+ dev_err(qpcs->dev,
|
|
+ "interface %s not supported\n", phy_modes(interface));
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
@@ -300,6 +341,9 @@ static int ipq_pcs_config_usxgmii(struct
|
|
if (ret)
|
|
return ret;
|
|
|
|
+ /* Deassert XPCS and configure XPCS USXGMII */
|
|
+ reset_control_deassert(qpcs->reset[XPCS_RESET]);
|
|
+
|
|
ret = regmap_set_bits(qpcs->regmap, XPCS_DIG_CTRL, XPCS_USXG_EN);
|
|
if (ret)
|
|
return ret;
|
|
@@ -311,6 +355,91 @@ static int ipq_pcs_config_usxgmii(struct
|
|
return regmap_set_bits(qpcs->regmap, XPCS_MII_CTRL, XPCS_MII_AN_EN);
|
|
}
|
|
|
|
+static unsigned long ipq_unipcs_clock_rate_get_gmii(int speed)
|
|
+{
|
|
+ unsigned long rate = 0;
|
|
+
|
|
+ switch (speed) {
|
|
+ case SPEED_1000:
|
|
+ rate = 125000000;
|
|
+ break;
|
|
+ case SPEED_100:
|
|
+ rate = 25000000;
|
|
+ break;
|
|
+ case SPEED_10:
|
|
+ rate = 2500000;
|
|
+ break;
|
|
+ default:
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ return rate;
|
|
+}
|
|
+
|
|
+static unsigned long ipq_unipcs_clock_rate_get_xgmii(int speed)
|
|
+{
|
|
+ unsigned long rate = 0;
|
|
+
|
|
+ switch (speed) {
|
|
+ case SPEED_10000:
|
|
+ rate = 312500000;
|
|
+ break;
|
|
+ case SPEED_5000:
|
|
+ rate = 156250000;
|
|
+ break;
|
|
+ case SPEED_2500:
|
|
+ rate = 78125000;
|
|
+ break;
|
|
+ case SPEED_1000:
|
|
+ rate = 125000000;
|
|
+ break;
|
|
+ case SPEED_100:
|
|
+ rate = 12500000;
|
|
+ break;
|
|
+ case SPEED_10:
|
|
+ rate = 1250000;
|
|
+ break;
|
|
+ default:
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ return rate;
|
|
+}
|
|
+
|
|
+static void
|
|
+ipq_unipcs_link_up_clock_rate_set(struct ipq_pcs_mii *qunipcs_ch,
|
|
+ phy_interface_t interface,
|
|
+ int speed)
|
|
+{
|
|
+ struct ipq_pcs *qpcs = qunipcs_ch->qpcs;
|
|
+ unsigned long rate = 0;
|
|
+
|
|
+ switch (interface) {
|
|
+ case PHY_INTERFACE_MODE_SGMII:
|
|
+ case PHY_INTERFACE_MODE_QSGMII:
|
|
+ case PHY_INTERFACE_MODE_PSGMII:
|
|
+ rate = ipq_unipcs_clock_rate_get_gmii(speed);
|
|
+ break;
|
|
+ case PHY_INTERFACE_MODE_USXGMII:
|
|
+ rate = ipq_unipcs_clock_rate_get_xgmii(speed);
|
|
+ break;
|
|
+ default:
|
|
+ dev_err(qpcs->dev,
|
|
+ "interface %s not supported\n", phy_modes(interface));
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ if (rate == 0) {
|
|
+ dev_err(qpcs->dev, "Invalid PCS clock rate\n");
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ clk_set_rate(qunipcs_ch->rx_clk, rate);
|
|
+ clk_set_rate(qunipcs_ch->tx_clk, rate);
|
|
+
|
|
+ fsleep(10000);
|
|
+}
|
|
+
|
|
static int ipq_pcs_link_up_config_sgmii(struct ipq_pcs *qpcs,
|
|
int index,
|
|
unsigned int neg_mode,
|
|
@@ -467,6 +596,7 @@ static void ipq_pcs_get_state(struct phy
|
|
switch (state->interface) {
|
|
case PHY_INTERFACE_MODE_SGMII:
|
|
case PHY_INTERFACE_MODE_QSGMII:
|
|
+ case PHY_INTERFACE_MODE_PSGMII:
|
|
ipq_pcs_get_state_sgmii(qpcs, index, state);
|
|
break;
|
|
case PHY_INTERFACE_MODE_USXGMII:
|
|
@@ -497,10 +627,13 @@ static int ipq_pcs_config(struct phylink
|
|
switch (interface) {
|
|
case PHY_INTERFACE_MODE_SGMII:
|
|
case PHY_INTERFACE_MODE_QSGMII:
|
|
+ case PHY_INTERFACE_MODE_PSGMII:
|
|
return ipq_pcs_config_sgmii(qpcs, index, neg_mode, interface);
|
|
case PHY_INTERFACE_MODE_USXGMII:
|
|
return ipq_pcs_config_usxgmii(qpcs);
|
|
default:
|
|
+ dev_err(qpcs->dev,
|
|
+ "interface %s not supported\n", phy_modes(interface));
|
|
return -EOPNOTSUPP;
|
|
};
|
|
}
|
|
@@ -515,9 +648,14 @@ static void ipq_pcs_link_up(struct phyli
|
|
int index = qpcs_mii->index;
|
|
int ret;
|
|
|
|
+ /* Configure PCS channel interface clock rate */
|
|
+ ipq_unipcs_link_up_clock_rate_set(qpcs_mii, interface, speed);
|
|
+
|
|
+ /* Configure PCS speed and reset PCS adapter */
|
|
switch (interface) {
|
|
case PHY_INTERFACE_MODE_SGMII:
|
|
case PHY_INTERFACE_MODE_QSGMII:
|
|
+ case PHY_INTERFACE_MODE_PSGMII:
|
|
ret = ipq_pcs_link_up_config_sgmii(qpcs, index,
|
|
neg_mode, speed);
|
|
break;
|
|
@@ -525,6 +663,8 @@ static void ipq_pcs_link_up(struct phyli
|
|
ret = ipq_pcs_link_up_config_usxgmii(qpcs, speed);
|
|
break;
|
|
default:
|
|
+ dev_err(qpcs->dev,
|
|
+ "interface %s not supported\n", phy_modes(interface));
|
|
return;
|
|
}
|
|
|
|
@@ -735,12 +875,38 @@ static const struct regmap_config ipq_pc
|
|
.fast_io = true,
|
|
};
|
|
|
|
+/**
|
|
+ * ipq_unipcs_create() - Create Qualcomm IPQ UNIPHY PCS
|
|
+ * @np: Device tree node to the PCS
|
|
+ *
|
|
+ * Description: Create a phylink PCS instance for a PCS node @np.
|
|
+ *
|
|
+ * Return: A pointer to the phylink PCS instance or an error-pointer value.
|
|
+ */
|
|
+struct phylink_pcs *ipq_unipcs_create(struct device_node *np)
|
|
+{
|
|
+ return ipq_pcs_get(np);
|
|
+}
|
|
+EXPORT_SYMBOL(ipq_unipcs_create);
|
|
+
|
|
+/**
|
|
+ * ipq_unipcs_destroy() - Destroy Qualcomm IPQ UNIPHY PCS
|
|
+ * @pcs: PCS instance
|
|
+ *
|
|
+ * Description: Destroy a phylink PCS instance.
|
|
+ */
|
|
+void ipq_unipcs_destroy(struct phylink_pcs *pcs)
|
|
+{
|
|
+ ipq_pcs_put(pcs);
|
|
+}
|
|
+EXPORT_SYMBOL(ipq_unipcs_destroy);
|
|
+
|
|
static int ipq9574_pcs_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct ipq_pcs *qpcs;
|
|
struct clk *clk;
|
|
- int ret;
|
|
+ int i, ret;
|
|
|
|
qpcs = devm_kzalloc(dev, sizeof(*qpcs), GFP_KERNEL);
|
|
if (!qpcs)
|
|
@@ -762,11 +928,23 @@ static int ipq9574_pcs_probe(struct plat
|
|
if (IS_ERR(clk))
|
|
return dev_err_probe(dev, PTR_ERR(clk),
|
|
"Failed to enable SYS clock\n");
|
|
+ clk_set_rate(clk, 24000000);
|
|
|
|
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");
|
|
+ clk_set_rate(clk, 100000000);
|
|
+
|
|
+ for (i = 0; i < PCS_RESET_MAX; i++) {
|
|
+ qpcs->reset[i] =
|
|
+ devm_reset_control_get_optional_exclusive(dev,
|
|
+ pcs_reset_name[i]);
|
|
+
|
|
+ if (IS_ERR(qpcs->reset[i]))
|
|
+ dev_err(dev, "Failed to get the reset ID %s\n",
|
|
+ pcs_reset_name[i]);
|
|
+ }
|
|
|
|
ret = ipq_pcs_clk_register(qpcs);
|
|
if (ret)
|
|
--- /dev/null
|
|
+++ b/include/linux/pcs/pcs-qcom-ipq-uniphy.h
|
|
@@ -0,0 +1,13 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0-only */
|
|
+/*
|
|
+ * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
|
|
+ *
|
|
+ */
|
|
+
|
|
+#ifndef __LINUX_PCS_QCOM_IPQ_UNIPHY_H
|
|
+#define __LINUX_PCS_QCOM_IPQ_UNIPHY_H
|
|
+
|
|
+struct phylink_pcs *ipq_unipcs_create(struct device_node *np);
|
|
+void ipq_unipcs_destroy(struct phylink_pcs *pcs);
|
|
+
|
|
+#endif /* __LINUX_PCS_QCOM_IPQ_UNIPHY_H */
|