From c12b79af730116936504afe97234f9afb6ac8fc0 Mon Sep 17 00:00:00 2001
From: Luo Jie <quic_luoj@quicinc.com>
Date: Mon, 23 Sep 2024 20:28:24 +0800
Subject: [PATCH] net: phy: qca808x: Add QCA8084 SerDes probe and remove
 functions

QCA8084 PHY package integrates the XPCS and PCS, which is used
to support 10G-QXGMII. XPCS includes 4 channels to connect with
Quad PHY, and PCS controls the interface mode configured.

XPCS and PCS are probed and removed by PHY package.

Change-Id: Ided0a5cd4c996dc2a2a0d0598e930fab060caaf8
Signed-off-by: Luo Jie <quic_luoj@quicinc.com>
Alex G: Use phy_package_get_*() accessors
Signed-off-by: Alexandru Gagniuc <mr.nuke.me@gmail.com>
---
 drivers/net/phy/qcom/Makefile         |   2 +-
 drivers/net/phy/qcom/qca8084_serdes.c | 249 ++++++++++++++++++++++++++
 drivers/net/phy/qcom/qca8084_serdes.h |  18 ++
 drivers/net/phy/qcom/qca808x.c        |  53 ++++++
 4 files changed, 321 insertions(+), 1 deletion(-)
 create mode 100644 drivers/net/phy/qcom/qca8084_serdes.c
 create mode 100644 drivers/net/phy/qcom/qca8084_serdes.h

--- a/drivers/net/phy/qcom/Makefile
+++ b/drivers/net/phy/qcom/Makefile
@@ -2,5 +2,5 @@
 obj-$(CONFIG_QCOM_NET_PHYLIB)	+= qcom-phy-lib.o
 obj-$(CONFIG_AT803X_PHY)	+= at803x.o
 obj-$(CONFIG_QCA83XX_PHY)	+= qca83xx.o
-obj-$(CONFIG_QCA808X_PHY)	+= qca808x.o
+obj-$(CONFIG_QCA808X_PHY)	+= qca808x.o qca8084_serdes.o
 obj-$(CONFIG_QCA807X_PHY)	+= qca807x.o
--- /dev/null
+++ b/drivers/net/phy/qcom/qca8084_serdes.c
@@ -0,0 +1,249 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2024, Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#include <linux/clk.h>
+#include <linux/dev_printk.h>
+#include <linux/mdio.h>
+#include <linux/of.h>
+#include <linux/phy.h>
+#include <linux/property.h>
+#include <linux/reset.h>
+
+#include "qca8084_serdes.h"
+
+/* XPCS includes 4 channels, each channel has the different MMD ID for
+ * configuring auto-negotiation complete interrupt, mii-4bit, auto-
+ * negotiation capabilities and TX configuration for the connected PHY.
+ *
+ * MMD31 is for channel 0;
+ * MMD26 is for channel 1;
+ * MMD27 is for channel 2;
+ * MMD28 is for channel 3;
+ */
+#define QCA8084_CHANNEL_MAX			4
+
+enum pcs_clk_id {
+	PCS_CLK,
+	PCS_RX_ROOT_CLK,
+	PCS_TX_ROOT_CLK,
+	PCS_CLK_MAX
+};
+
+enum xpcs_clk_id {
+	XPCS_XGMII_RX_CLK,
+	XPCS_XGMII_TX_CLK,
+	XPCS_RX_CLK,
+	XPCS_TX_CLK,
+	XPCS_PORT_RX_CLK,
+	XPCS_PORT_TX_CLK,
+	XPCS_RX_SRC_CLK,
+	XPCS_TX_SRC_CLK,
+	XPCS_CLK_MAX
+};
+
+struct qca8084_xpcs_channel_priv {
+	int ch_id;
+	struct reset_control *rstcs;
+	struct clk *clks[XPCS_CLK_MAX];
+};
+
+struct qca8084_pcs_data {
+	struct reset_control *rstc;
+	struct clk *clks[PCS_CLK_MAX];
+};
+
+struct qca8084_xpcs_data {
+	struct reset_control *rstc;
+	struct qca8084_xpcs_channel_priv xpcs_ch[QCA8084_CHANNEL_MAX];
+};
+
+static const char *const xpcs_clock_names[XPCS_CLK_MAX] = {
+	[XPCS_XGMII_RX_CLK] =	"xgmii_rx",
+	[XPCS_XGMII_TX_CLK] =	"xgmii_tx",
+	[XPCS_RX_CLK] =		"xpcs_rx",
+	[XPCS_TX_CLK] =		"xpcs_tx",
+	[XPCS_PORT_RX_CLK] =	"port_rx",
+	[XPCS_PORT_TX_CLK] =	"port_tx",
+	[XPCS_RX_SRC_CLK] =	"rx_src",
+	[XPCS_TX_SRC_CLK] =	"tx_src",
+};
+
+static const char *const pcs_clock_names[PCS_CLK_MAX] = {
+	[PCS_CLK] =		"pcs",
+	[PCS_RX_ROOT_CLK] =	"pcs_rx_root",
+	[PCS_TX_ROOT_CLK] =	"pcs_tx_root",
+};
+
+struct mdio_device *qca8084_package_pcs_probe(struct device_node *pcs_np)
+{
+	struct qca8084_pcs_data *pcs_data;
+	struct mdio_device *mdiodev;
+	struct reset_control *rstc;
+	struct device *dev;
+	struct clk *clk;
+	int i;
+
+	mdiodev = fwnode_mdio_find_device(of_fwnode_handle(pcs_np));
+	if (!mdiodev)
+		return ERR_PTR(-EPROBE_DEFER);
+
+	dev = &mdiodev->dev;
+	pcs_data = devm_kzalloc(dev, sizeof(*pcs_data), GFP_KERNEL);
+	if (!pcs_data) {
+		dev_err(dev, "Allocate PCS data failed\n");
+		return ERR_PTR(-ENOMEM);
+	}
+
+	rstc = devm_reset_control_get_exclusive(dev, NULL);
+	if (IS_ERR(rstc)) {
+		dev_err(dev, "Get PCS reset failed\n");
+		return ERR_CAST(rstc);
+	}
+
+	pcs_data->rstc = rstc;
+
+	for (i = 0; i < ARRAY_SIZE(pcs_clock_names); i++) {
+		clk = devm_clk_get(dev, pcs_clock_names[i]);
+		if (IS_ERR(clk)) {
+			dev_err(dev, "Failed to get the PCS clock ID %s\n",
+				pcs_clock_names[i]);
+			return ERR_CAST(clk);
+		}
+		pcs_data->clks[i] = clk;
+	}
+
+	mdiodev_set_drvdata(mdiodev, pcs_data);
+
+	return mdiodev;
+}
+
+struct mdio_device *qca8084_package_xpcs_probe(struct device_node *xpcs_np)
+{
+	struct qca8084_xpcs_data *xpcs_data;
+	struct mdio_device *mdiodev;
+	struct reset_control *rstc;
+	struct device_node *child;
+	struct device *dev;
+	struct clk *clk;
+	int i, j, node;
+
+	mdiodev = fwnode_mdio_find_device(of_fwnode_handle(xpcs_np));
+	if (!mdiodev)
+		return ERR_PTR(-EPROBE_DEFER);
+
+	dev = &mdiodev->dev;
+
+	xpcs_data = devm_kzalloc(dev, sizeof(*xpcs_data), GFP_KERNEL);
+	if (!xpcs_data) {
+		dev_err(dev, "Allocate XPCS data failed\n");
+		return ERR_PTR(-ENOMEM);
+	}
+
+	rstc = devm_reset_control_get_exclusive(dev, NULL);
+	if (IS_ERR(rstc)) {
+		dev_err(dev, "Get XPCS reset failed\n");
+		return ERR_CAST(rstc);
+	}
+
+	xpcs_data->rstc = rstc;
+
+	/* Sanity check the number of channel sub nodes */
+	node = of_get_available_child_count(xpcs_np);
+	if (node != QCA8084_CHANNEL_MAX)
+		return ERR_PTR(-EINVAL);
+
+	node = 0;
+	for_each_available_child_of_node(xpcs_np, child) {
+		struct qca8084_xpcs_channel_priv *ch_data;
+		u32 channel;
+
+		/* The subnode name must be 'channel'. */
+		if (!(of_node_name_eq(child, "channel")))
+			continue;
+
+		if (of_property_read_u32(child, "reg", &channel)) {
+			dev_err(dev, "%s: Failed to get reg\n",
+				child->full_name);
+
+			mdiodev = ERR_PTR(-EINVAL);
+			goto put_ch_clk_rst;
+		}
+
+		if (channel >= QCA8084_CHANNEL_MAX) {
+			dev_err(dev, "%s: Invalid reg %d\n",
+				child->full_name, channel);
+
+			mdiodev = ERR_PTR(-EINVAL);
+			goto put_ch_clk_rst;
+		}
+
+		ch_data = &xpcs_data->xpcs_ch[node];
+		ch_data->ch_id = channel;
+
+		ch_data->rstcs = of_reset_control_array_get_exclusive(child);
+		if (IS_ERR(ch_data->rstcs)) {
+			dev_err(dev, "%s: Failed to get reset\n",
+				child->full_name);
+
+			mdiodev = ERR_CAST(ch_data->rstcs);
+			goto put_ch_clk_rst;
+		}
+
+		for (j = 0; j < ARRAY_SIZE(xpcs_clock_names); j++) {
+			clk = of_clk_get_by_name(child, xpcs_clock_names[j]);
+			if (IS_ERR(clk)) {
+				dev_err(dev, "Failed to get the clock ID %s\n",
+					xpcs_clock_names[j]);
+				mdiodev = ERR_CAST(clk);
+				goto put_ch_child;
+			}
+			ch_data->clks[j] = clk;
+		}
+
+		node++;
+	}
+
+	mdiodev_set_drvdata(mdiodev, xpcs_data);
+
+	return mdiodev;
+
+put_ch_child:
+	node++;
+
+put_ch_clk_rst:
+	for (i = 0; i < node; i++) {
+		j--;
+		while (j >= 0) {
+			clk_put(xpcs_data->xpcs_ch[i].clks[j]);
+			j--;
+		}
+
+		j = ARRAY_SIZE(xpcs_clock_names);
+	}
+
+	for (i = 0; i < node; i++)
+		reset_control_put(xpcs_data->xpcs_ch[i].rstcs);
+
+	of_node_put(child);
+
+	return mdiodev;
+}
+
+void qca8084_package_xpcs_and_pcs_remove(struct mdio_device *xpcs_mdiodev,
+					 struct mdio_device *pcs_mdiodev)
+{
+	struct qca8084_xpcs_data *xpcs_data = mdiodev_get_drvdata(xpcs_mdiodev);
+	int i, j;
+
+	for (i = 0; i < ARRAY_SIZE(xpcs_data->xpcs_ch); i++) {
+		reset_control_put(xpcs_data->xpcs_ch[i].rstcs);
+
+		for (j = 0; j < ARRAY_SIZE(xpcs_data->xpcs_ch[i].clks); j++)
+			clk_put(xpcs_data->xpcs_ch[i].clks[j]);
+	}
+
+	mdio_device_put(xpcs_mdiodev);
+	mdio_device_put(pcs_mdiodev);
+}
--- /dev/null
+++ b/drivers/net/phy/qcom/qca8084_serdes.h
@@ -0,0 +1,18 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for QCA8084 SerDes
+ *
+ * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#ifndef _QCA8084_SERDES_H_
+#define _QCA8084_SERDES_H_
+
+#include <linux/mdio.h>
+#include <linux/of.h>
+
+struct mdio_device *qca8084_package_pcs_probe(struct device_node *pcs_np);
+struct mdio_device *qca8084_package_xpcs_probe(struct device_node *xpcs_np);
+void qca8084_package_xpcs_and_pcs_remove(struct mdio_device *xpcs_mdiodev,
+					 struct mdio_device *pcs_mdiodev);
+#endif /* _QCA8084_SERDES_H_ */
--- a/drivers/net/phy/qcom/qca808x.c
+++ b/drivers/net/phy/qcom/qca808x.c
@@ -8,6 +8,7 @@
 #include <linux/clk.h>
 
 #include "../phylib.h"
+#include "qca8084_serdes.h"
 #include "qcom.h"
 
 /* ADC threshold */
@@ -172,11 +173,13 @@ enum {
 
 struct qca808x_priv {
 	int led_polarity_mode;
+	int channel_id;
 };
 
 struct qca808x_shared_priv {
 	int package_mode;
 	struct clk *clk[PACKAGE_CLK_MAX];
+	struct mdio_device *mdiodev[2];	/* PCS and XPCS mdio device */
 };
 
 static const char *const qca8084_package_clk_name[PACKAGE_CLK_MAX] = {
@@ -354,6 +357,8 @@ static int qca808x_probe(struct phy_devi
 {
 	struct device *dev = &phydev->mdio.dev;
 	struct qca808x_priv *priv;
+	u32 ch_id = 0;
+	int ret;
 
 	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
 	if (!priv)
@@ -362,6 +367,14 @@ static int qca808x_probe(struct phy_devi
 	/* Init LED polarity mode to -1 */
 	priv->led_polarity_mode = -1;
 
+	/* DT property qcom,xpcs-channel" is optional and only available for
+	 * 10G-QXGMII mode.
+	 */
+	ret = of_property_read_u32(dev->of_node, "qcom,xpcs-channel", &ch_id);
+	if (ret && ret != -EINVAL)
+		return ret;
+
+	priv->channel_id = ch_id;
 	phydev->priv = priv;
 
 	return 0;
@@ -1012,6 +1025,7 @@ static int qca8084_phy_package_probe_onc
 	struct device_node *np = phy_package_get_node(phydev);
 	struct qca808x_shared_priv *shared_priv;
 	struct reset_control *rstc;
+	struct device_node *child;
 	int i, ret, clear, set;
 	struct clk *clk;
 
@@ -1072,6 +1086,26 @@ static int qca8084_phy_package_probe_onc
 	if (ret && ret != -EINVAL)
 		return ret;
 
+	for_each_available_child_of_node(np, child) {
+		struct mdio_device *mdiodev;
+
+		if (of_node_name_eq(child, "pcs-phy")) {
+			mdiodev = qca8084_package_pcs_probe(child);
+			if (IS_ERR(mdiodev))
+				return PTR_ERR(mdiodev);
+
+			shared_priv->mdiodev[0] = mdiodev;
+		}
+
+		if (of_node_name_eq(child, "xpcs-phy")) {
+			mdiodev = qca8084_package_xpcs_probe(child);
+			if (IS_ERR(mdiodev))
+				return PTR_ERR(mdiodev);
+
+			shared_priv->mdiodev[1] = mdiodev;
+		}
+	}
+
 	rstc = of_reset_control_get_exclusive(np, NULL);
 	if (IS_ERR(rstc))
 		return dev_err_probe(&phydev->mdio.dev, PTR_ERR(rstc),
@@ -1081,6 +1115,14 @@ static int qca8084_phy_package_probe_onc
 	return reset_control_deassert(rstc);
 }
 
+static void qca8084_phy_package_remove_once(struct phy_device *phydev)
+{
+	struct qca808x_shared_priv *shared_priv = phy_package_get_priv(phydev);;
+
+	qca8084_package_xpcs_and_pcs_remove(shared_priv->mdiodev[1],
+					    shared_priv->mdiodev[0]);
+}
+
 static int qca8084_probe(struct phy_device *phydev)
 {
 	struct qca808x_shared_priv *shared_priv;
@@ -1099,6 +1141,10 @@ static int qca8084_probe(struct phy_devi
 			return ret;
 	}
 
+	ret = qca808x_probe(phydev);
+	if (ret)
+		return ret;
+
 	/* Enable clock of PHY device, so that the PHY register
 	 * can be accessed to get PHY features.
 	 */
@@ -1116,6 +1162,12 @@ static int qca8084_probe(struct phy_devi
 	return reset_control_deassert(rstc);
 }
 
+static void qca8084_remove(struct phy_device *phydev)
+{
+	if (phy_package_remove_once(phydev))
+		qca8084_phy_package_remove_once(phydev);
+}
+
 static struct phy_driver qca808x_driver[] = {
 {
 	/* Qualcomm QCA8081 */
@@ -1167,6 +1219,7 @@ static struct phy_driver qca808x_driver[
 	.config_init		= qca8084_config_init,
 	.link_change_notify	= qca8084_link_change_notify,
 	.probe			= qca8084_probe,
+	.remove			= qca8084_remove,
 }, };
 
 module_phy_driver(qca808x_driver);
