[PATCH net-next 04/11] net: enetc: add initial netc-blk-ctrl driver support

Wei Fang posted 11 patches 1 month, 2 weeks ago
There is a newer version of this series
[PATCH net-next 04/11] net: enetc: add initial netc-blk-ctrl driver support
Posted by Wei Fang 1 month, 2 weeks ago
The netc-blk-ctrl driver is used to configure Integrated Endpoint
Register Block (IERB) and Privileged Register Block (PRB) of NETC.
For i.MX platforms, it is also used to configure the NETCMIX block.

The IERB contains registers that are used for pre-boot initialization,
debug, and non-customer configuration. The PRB controls global reset
and global error handling for NETC. The NETCMIX block is mainly used
to set MII protocol and PCS protocol of the links, it also contains
settings for some other functions.

Note the IERB configuration registers can only be written after being
unlocked by PRB, otherwise, all write operations are inhibited. A warm
reset is performed when the IERB is unlocked, and it results in an FLR
to all NETC devices. Therefore, all NETC device drivers must be probed
or initialized after the warm reset is finished.

Signed-off-by: Wei Fang <wei.fang@nxp.com>
---
 drivers/net/ethernet/freescale/enetc/Kconfig  |  14 +
 drivers/net/ethernet/freescale/enetc/Makefile |   3 +
 .../ethernet/freescale/enetc/netc_blk_ctrl.c  | 476 ++++++++++++++++++
 include/linux/fsl/netc_global.h               |  39 ++
 4 files changed, 532 insertions(+)
 create mode 100644 drivers/net/ethernet/freescale/enetc/netc_blk_ctrl.c
 create mode 100644 include/linux/fsl/netc_global.h

diff --git a/drivers/net/ethernet/freescale/enetc/Kconfig b/drivers/net/ethernet/freescale/enetc/Kconfig
index 4d75e6807e92..51d80ea959d4 100644
--- a/drivers/net/ethernet/freescale/enetc/Kconfig
+++ b/drivers/net/ethernet/freescale/enetc/Kconfig
@@ -75,3 +75,17 @@ config FSL_ENETC_QOS
 	  enable/disable from user space via Qos commands(tc). In the kernel
 	  side, it can be loaded by Qos driver. Currently, it is only support
 	  taprio(802.1Qbv) and Credit Based Shaper(802.1Qbu).
+
+config NXP_NETC_BLK_CTRL
+	tristate "NETC blocks control driver"
+	help
+	  This driver configures Integrated Endpoint Register Block (IERB) and
+	  Privileged Register Block (PRB) of NETC. For i.MX platforms, it also
+	  includes the configuration of NETCMIX block.
+	  The IERB contains registers that are used for pre-boot initialization,
+	  debug, and non-customer configuration. The PRB controls global reset
+	  and global error handling for NETC. The NETCMIX block is mainly used
+	  to set MII protocol and PCS protocol of the links, it also contains
+	  settings for some other functions.
+
+	  If compiled as module (M), the module name is nxp-netc-blk-ctrl.
diff --git a/drivers/net/ethernet/freescale/enetc/Makefile b/drivers/net/ethernet/freescale/enetc/Makefile
index b13cbbabb2ea..5c277910d538 100644
--- a/drivers/net/ethernet/freescale/enetc/Makefile
+++ b/drivers/net/ethernet/freescale/enetc/Makefile
@@ -19,3 +19,6 @@ fsl-enetc-mdio-y := enetc_pci_mdio.o enetc_mdio.o
 
 obj-$(CONFIG_FSL_ENETC_PTP_CLOCK) += fsl-enetc-ptp.o
 fsl-enetc-ptp-y := enetc_ptp.o
+
+obj-$(CONFIG_NXP_NETC_BLK_CTRL) += nxp-netc-blk-ctrl.o
+nxp-netc-blk-ctrl-y := netc_blk_ctrl.o
\ No newline at end of file
diff --git a/drivers/net/ethernet/freescale/enetc/netc_blk_ctrl.c b/drivers/net/ethernet/freescale/enetc/netc_blk_ctrl.c
new file mode 100644
index 000000000000..b8eec980c199
--- /dev/null
+++ b/drivers/net/ethernet/freescale/enetc/netc_blk_ctrl.c
@@ -0,0 +1,476 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
+/*
+ * NXP NETC Blocks Control Driver
+ *
+ * Copyright 2024 NXP
+ */
+#include <linux/clk.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/fsl/netc_global.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_net.h>
+#include <linux/of_platform.h>
+#include <linux/phy.h>
+#include <linux/platform_device.h>
+#include <linux/seq_file.h>
+
+/* NETCMIX registers */
+#define IMX95_CFG_LINK_IO_VAR		0x0
+#define  IO_VAR_16FF_16G_SERDES		0x1
+#define  IO_VAR(port, var)		(((var) & 0xf) << ((port) << 2))
+
+#define IMX95_CFG_LINK_MII_PROT		0x4
+#define CFG_LINK_MII_PORT_0		GENMASK(3, 0)
+#define CFG_LINK_MII_PORT_1		GENMASK(7, 4)
+#define  MII_PROT_MII			0x0
+#define  MII_PROT_RMII			0x1
+#define  MII_PROT_RGMII			0x2
+#define  MII_PROT_SERIAL		0x3
+#define  MII_PROT(port, prot)		(((prot) & 0xf) << ((port) << 2))
+
+#define IMX95_CFG_LINK_PCS_PROT(a)	(0x8 + (a) * 4)
+#define PCS_PROT_1G_SGMII		BIT(0)
+#define PCS_PROT_2500M_SGMII		BIT(1)
+#define PCS_PROT_XFI			BIT(3)
+#define PCS_PROT_SFI			BIT(4)
+#define PCS_PROT_10G_SXGMII		BIT(6)
+
+/* NETC privileged register block register */
+#define PRB_NETCRR			0x100
+#define  NETCRR_SR			BIT(0)
+#define  NETCRR_LOCK			BIT(1)
+
+#define PRB_NETCSR			0x104
+#define  NETCSR_ERROR			BIT(0)
+#define  NETCSR_STATE			BIT(1)
+
+/* NETC integrated endpoint register block register */
+#define IERB_EMDIOFAUXR			0x344
+#define IERB_T0FAUXR			0x444
+#define IERB_EFAUXR(a)			(0x3044 + 0x100 * (a))
+#define IERB_VFAUXR(a)			(0x4004 + 0x40 * (a))
+#define FAUXR_LDID			GENMASK(3, 0)
+
+/* Platform information */
+#define IMX95_ENETC0_BUS_DEVFN		0x0
+#define IMX95_ENETC1_BUS_DEVFN		0x40
+#define IMX95_ENETC2_BUS_DEVFN		0x80
+
+/* Flags for different platforms */
+#define NETC_HAS_NETCMIX		BIT(0)
+
+struct netc_devinfo {
+	u32 flags;
+	int (*netcmix_init)(struct platform_device *pdev);
+	int (*ierb_init)(struct platform_device *pdev);
+};
+
+struct netc_blk_ctrl {
+	void __iomem *prb;
+	void __iomem *ierb;
+	void __iomem *netcmix;
+	struct clk *ipg_clk;
+
+	const struct netc_devinfo *devinfo;
+	struct platform_device *pdev;
+	struct dentry *debugfs_root;
+};
+
+static void netc_reg_write(void __iomem *base, u32 offset, u32 val)
+{
+	netc_write(base + offset, val);
+}
+
+static u32 netc_reg_read(void __iomem *base, u32 offset)
+{
+	return netc_read(base + offset);
+}
+
+static int netc_of_pci_get_bus_devfn(struct device_node *np)
+{
+	u32 reg[5];
+	int error;
+
+	error = of_property_read_u32_array(np, "reg", reg, ARRAY_SIZE(reg));
+	if (error)
+		return error;
+
+	return (reg[0] >> 8) & 0xffff;
+}
+
+static int netc_get_link_mii_protocol(phy_interface_t interface)
+{
+	switch (interface) {
+	case PHY_INTERFACE_MODE_MII:
+		return MII_PROT_MII;
+	case PHY_INTERFACE_MODE_RMII:
+		return MII_PROT_RMII;
+	case PHY_INTERFACE_MODE_RGMII:
+	case PHY_INTERFACE_MODE_RGMII_ID:
+	case PHY_INTERFACE_MODE_RGMII_RXID:
+	case PHY_INTERFACE_MODE_RGMII_TXID:
+		return MII_PROT_RGMII;
+	case PHY_INTERFACE_MODE_SGMII:
+	case PHY_INTERFACE_MODE_2500BASEX:
+	case PHY_INTERFACE_MODE_10GBASER:
+	case PHY_INTERFACE_MODE_XGMII:
+	case PHY_INTERFACE_MODE_USXGMII:
+		return MII_PROT_SERIAL;
+	default:
+		return -EINVAL;
+	}
+}
+
+static int imx95_netcmix_init(struct platform_device *pdev)
+{
+	struct netc_blk_ctrl *priv = platform_get_drvdata(pdev);
+	struct device_node *np = pdev->dev.of_node;
+	phy_interface_t interface;
+	int bus_devfn, mii_proto;
+	u32 val;
+	int err;
+
+	/* Default setting of MII protocol */
+	val = MII_PROT(0, MII_PROT_RGMII) | MII_PROT(1, MII_PROT_RGMII) |
+	      MII_PROT(2, MII_PROT_SERIAL);
+
+	/* Update the link MII protocol through parsing phy-mode */
+	for_each_available_child_of_node_scoped(np, child) {
+		for_each_available_child_of_node_scoped(child, gchild) {
+			if (!of_device_is_compatible(gchild, "nxp,imx95-enetc"))
+				continue;
+
+			bus_devfn = netc_of_pci_get_bus_devfn(gchild);
+			if (bus_devfn < 0)
+				return -EINVAL;
+
+			if (bus_devfn == IMX95_ENETC2_BUS_DEVFN)
+				continue;
+
+			err = of_get_phy_mode(gchild, &interface);
+			if (err)
+				continue;
+
+			mii_proto = netc_get_link_mii_protocol(interface);
+			if (mii_proto < 0)
+				return -EINVAL;
+
+			switch (bus_devfn) {
+			case IMX95_ENETC0_BUS_DEVFN:
+				val = u32_replace_bits(val, mii_proto,
+						       CFG_LINK_MII_PORT_0);
+				break;
+			case IMX95_ENETC1_BUS_DEVFN:
+				val = u32_replace_bits(val, mii_proto,
+						       CFG_LINK_MII_PORT_1);
+				break;
+			default:
+				return -EINVAL;
+			}
+		}
+	}
+
+	/* Configure Link I/O variant */
+	netc_reg_write(priv->netcmix, IMX95_CFG_LINK_IO_VAR,
+		       IO_VAR(2, IO_VAR_16FF_16G_SERDES));
+	/* Configure Link 2 PCS protocol */
+	netc_reg_write(priv->netcmix, IMX95_CFG_LINK_PCS_PROT(2),
+		       PCS_PROT_10G_SXGMII);
+	netc_reg_write(priv->netcmix, IMX95_CFG_LINK_MII_PROT, val);
+
+	return 0;
+}
+
+static bool netc_ierb_is_locked(struct netc_blk_ctrl *priv)
+{
+	return !!(netc_reg_read(priv->prb, PRB_NETCRR) & NETCRR_LOCK);
+}
+
+static int netc_lock_ierb(struct netc_blk_ctrl *priv)
+{
+	u32 val;
+
+	netc_reg_write(priv->prb, PRB_NETCRR, NETCRR_LOCK);
+
+	return read_poll_timeout(netc_reg_read, val, !(val & NETCSR_STATE),
+				 100, 2000, false, priv->prb, PRB_NETCSR);
+}
+
+static int netc_unlock_ierb_with_warm_reset(struct netc_blk_ctrl *priv)
+{
+	u32 val;
+
+	netc_reg_write(priv->prb, PRB_NETCRR, 0);
+
+	return read_poll_timeout(netc_reg_read, val, !(val & NETCRR_LOCK),
+				 1000, 100000, true, priv->prb, PRB_NETCRR);
+}
+
+static int imx95_ierb_init(struct platform_device *pdev)
+{
+	struct netc_blk_ctrl *priv = platform_get_drvdata(pdev);
+
+	/* EMDIO : No MSI-X intterupt */
+	netc_reg_write(priv->ierb, IERB_EMDIOFAUXR, 0);
+	/* ENETC0 PF */
+	netc_reg_write(priv->ierb, IERB_EFAUXR(0), 0);
+	/* ENETC0 VF0 */
+	netc_reg_write(priv->ierb, IERB_VFAUXR(0), 1);
+	/* ENETC0 VF1 */
+	netc_reg_write(priv->ierb, IERB_VFAUXR(1), 2);
+	/* ENETC1 PF */
+	netc_reg_write(priv->ierb, IERB_EFAUXR(1), 3);
+	/* ENETC1 VF0 */
+	netc_reg_write(priv->ierb, IERB_VFAUXR(2), 5);
+	/* ENETC1 VF1 */
+	netc_reg_write(priv->ierb, IERB_VFAUXR(3), 6);
+	/* ENETC2 PF */
+	netc_reg_write(priv->ierb, IERB_EFAUXR(2), 4);
+	/* ENETC2 VF0 */
+	netc_reg_write(priv->ierb, IERB_VFAUXR(4), 5);
+	/* ENETC2 VF1 */
+	netc_reg_write(priv->ierb, IERB_VFAUXR(5), 6);
+	/* NETC TIMER */
+	netc_reg_write(priv->ierb, IERB_T0FAUXR, 7);
+
+	return 0;
+}
+
+static int netc_ierb_init(struct platform_device *pdev)
+{
+	struct netc_blk_ctrl *priv = platform_get_drvdata(pdev);
+	const struct netc_devinfo *devinfo = priv->devinfo;
+	int err;
+
+	if (netc_ierb_is_locked(priv)) {
+		err = netc_unlock_ierb_with_warm_reset(priv);
+		if (err) {
+			dev_err(&pdev->dev, "Unlock IERB failed.\n");
+			return err;
+		}
+	}
+
+	if (devinfo->ierb_init) {
+		err = devinfo->ierb_init(pdev);
+		if (err)
+			return err;
+	}
+
+	err = netc_lock_ierb(priv);
+	if (err) {
+		dev_err(&pdev->dev, "Lock IERB failed.\n");
+		return err;
+	}
+
+	return 0;
+}
+
+#if IS_ENABLED(CONFIG_DEBUG_FS)
+static int netc_prb_show(struct seq_file *s, void *data)
+{
+	struct netc_blk_ctrl *priv = s->private;
+	u32 val;
+
+	val = netc_reg_read(priv->prb, PRB_NETCRR);
+	seq_printf(s, "[PRB NETCRR] Lock:%d SR:%d\n",
+		   (val & NETCRR_LOCK) ? 1 : 0,
+		   (val & NETCRR_SR) ? 1 : 0);
+
+	val = netc_reg_read(priv->prb, PRB_NETCSR);
+	seq_printf(s, "[PRB NETCSR] State:%d Error:%d\n",
+		   (val & NETCSR_STATE) ? 1 : 0,
+		   (val & NETCSR_ERROR) ? 1 : 0);
+
+	return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(netc_prb);
+
+static void netc_blk_ctrl_create_debugfs(struct netc_blk_ctrl *priv)
+{
+	struct dentry *root;
+
+	root = debugfs_create_dir("netc_blk_ctrl", NULL);
+	if (IS_ERR(root))
+		return;
+
+	priv->debugfs_root = root;
+
+	debugfs_create_file("prb", 0444, root, priv, &netc_prb_fops);
+}
+
+static void netc_blk_ctrl_remove_debugfs(struct netc_blk_ctrl *priv)
+{
+	debugfs_remove_recursive(priv->debugfs_root);
+	priv->debugfs_root = NULL;
+}
+
+#else
+
+static void netc_blk_ctrl_create_debugfs(struct netc_blk_ctrl *priv)
+{
+}
+
+static void netc_blk_ctrl_remove_debugfs(struct netc_blk_ctrl *priv)
+{
+}
+#endif
+
+static int netc_prb_check_error(struct netc_blk_ctrl *priv)
+{
+	u32 val;
+
+	val = netc_reg_read(priv->prb, PRB_NETCSR);
+	if (val & NETCSR_ERROR)
+		return -1;
+
+	return 0;
+}
+
+static const struct netc_devinfo imx95_devinfo = {
+	.flags = NETC_HAS_NETCMIX,
+	.netcmix_init = imx95_netcmix_init,
+	.ierb_init = imx95_ierb_init,
+};
+
+static const struct of_device_id netc_blk_ctrl_match[] = {
+	{ .compatible = "nxp,imx95-netc-blk-ctrl", .data = &imx95_devinfo },
+	{},
+};
+MODULE_DEVICE_TABLE(of, netc_blk_ctrl_match);
+
+static int netc_blk_ctrl_probe(struct platform_device *pdev)
+{
+	struct device_node *node = pdev->dev.of_node;
+	const struct netc_devinfo *devinfo;
+	struct device *dev = &pdev->dev;
+	const struct of_device_id *id;
+	struct netc_blk_ctrl *priv;
+	void __iomem *regs;
+	int err;
+
+	if (!node || !of_device_is_available(node)) {
+		dev_info(dev, "Device is disabled, skipping\n");
+		return -ENODEV;
+	}
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->pdev = pdev;
+	priv->ipg_clk = devm_clk_get_optional(dev, "ipg_clk");
+	if (IS_ERR(priv->ipg_clk)) {
+		dev_err(dev, "Get ipg_clk failed\n");
+		err = PTR_ERR(priv->ipg_clk);
+		return err;
+	}
+
+	err = clk_prepare_enable(priv->ipg_clk);
+	if (err) {
+		dev_err(dev, "Enable ipg_clk failed\n");
+		goto disable_ipg_clk;
+	}
+
+	id = of_match_device(netc_blk_ctrl_match, dev);
+	if (!id) {
+		dev_err(dev, "Cannot match device\n");
+		err = -EINVAL;
+		goto disable_ipg_clk;
+	}
+
+	devinfo = (struct netc_devinfo *)id->data;
+	if (!devinfo) {
+		dev_err(dev, "No device information\n");
+		err = -EINVAL;
+		goto disable_ipg_clk;
+	}
+	priv->devinfo = devinfo;
+
+	regs = devm_platform_ioremap_resource_byname(pdev, "ierb");
+	if (IS_ERR(regs)) {
+		err = PTR_ERR(regs);
+		dev_err(dev, "Missing IERB resource\n");
+		goto disable_ipg_clk;
+	}
+	priv->ierb = regs;
+
+	regs = devm_platform_ioremap_resource_byname(pdev, "prb");
+	if (IS_ERR(regs)) {
+		err = PTR_ERR(regs);
+		dev_err(dev, "Missing PRB resource\n");
+		goto disable_ipg_clk;
+	}
+	priv->prb = regs;
+
+	if (devinfo->flags & NETC_HAS_NETCMIX) {
+		regs = devm_platform_ioremap_resource_byname(pdev, "netcmix");
+		if (IS_ERR(regs)) {
+			err = PTR_ERR(regs);
+			dev_err(dev, "Missing NETCMIX resource\n");
+			goto disable_ipg_clk;
+		}
+		priv->netcmix = regs;
+	}
+
+	platform_set_drvdata(pdev, priv);
+
+	if (devinfo->netcmix_init) {
+		err = devinfo->netcmix_init(pdev);
+		if (err) {
+			dev_err(dev, "Initializing NETCMIX failed\n");
+			goto disable_ipg_clk;
+		}
+	}
+
+	err = netc_ierb_init(pdev);
+	if (err) {
+		dev_err(dev, "Initializing IERB failed.\n");
+		goto disable_ipg_clk;
+	}
+
+	if (netc_prb_check_error(priv) < 0)
+		dev_warn(dev, "The current IERB configuration is invalid.\n");
+
+	netc_blk_ctrl_create_debugfs(priv);
+
+	err = of_platform_populate(node, NULL, NULL, dev);
+	if (err) {
+		dev_err(dev, "of_platform_populate failed\n");
+		goto remove_debugfs;
+	}
+
+	return 0;
+
+remove_debugfs:
+	netc_blk_ctrl_remove_debugfs(priv);
+disable_ipg_clk:
+	clk_disable_unprepare(priv->ipg_clk);
+
+	return err;
+}
+
+static void netc_blk_ctrl_remove(struct platform_device *pdev)
+{
+	struct netc_blk_ctrl *priv = platform_get_drvdata(pdev);
+
+	of_platform_depopulate(&pdev->dev);
+	netc_blk_ctrl_remove_debugfs(priv);
+	clk_disable_unprepare(priv->ipg_clk);
+}
+
+static struct platform_driver netc_blk_ctrl_driver = {
+	.driver = {
+		.name = "nxp-netc-blk-ctrl",
+		.of_match_table = netc_blk_ctrl_match,
+	},
+	.probe = netc_blk_ctrl_probe,
+	.remove = netc_blk_ctrl_remove,
+};
+
+module_platform_driver(netc_blk_ctrl_driver);
+
+MODULE_DESCRIPTION("NXP NETC Blocks Control Driver");
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/include/linux/fsl/netc_global.h b/include/linux/fsl/netc_global.h
new file mode 100644
index 000000000000..f26b1b6f8813
--- /dev/null
+++ b/include/linux/fsl/netc_global.h
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) */
+/* Copyright 2024 NXP
+ */
+#ifndef __NETC_GLOBAL_H
+#define __NETC_GLOBAL_H
+
+#include <linux/io.h>
+
+static inline u32 netc_read(void __iomem *reg)
+{
+	return ioread32(reg);
+}
+
+#ifdef ioread64
+static inline u64 netc_read64(void __iomem *reg)
+{
+	return ioread64(reg);
+}
+#else
+static inline u64 netc_read64(void __iomem *reg)
+{
+	u32 low, high;
+	u64 val;
+
+	low = ioread32(reg);
+	high = ioread32(reg + 4);
+
+	val = (u64)high << 32 | low;
+
+	return val;
+}
+#endif
+
+static inline void netc_write(void __iomem *reg, u32 val)
+{
+	iowrite32(val, reg);
+}
+
+#endif
-- 
2.34.1
Re: [PATCH net-next 04/11] net: enetc: add initial netc-blk-ctrl driver support
Posted by Frank Li 1 month, 2 weeks ago
On Wed, Oct 09, 2024 at 05:51:09PM +0800, Wei Fang wrote:
> The netc-blk-ctrl driver is used to configure Integrated Endpoint
> Register Block (IERB) and Privileged Register Block (PRB) of NETC.
> For i.MX platforms, it is also used to configure the NETCMIX block.
>
> The IERB contains registers that are used for pre-boot initialization,
> debug, and non-customer configuration. The PRB controls global reset
> and global error handling for NETC. The NETCMIX block is mainly used
> to set MII protocol and PCS protocol of the links, it also contains
> settings for some other functions.
>
> Note the IERB configuration registers can only be written after being
> unlocked by PRB, otherwise, all write operations are inhibited. A warm
> reset is performed when the IERB is unlocked, and it results in an FLR
> to all NETC devices. Therefore, all NETC device drivers must be probed
> or initialized after the warm reset is finished.
>
> Signed-off-by: Wei Fang <wei.fang@nxp.com>
> ---
>  drivers/net/ethernet/freescale/enetc/Kconfig  |  14 +
>  drivers/net/ethernet/freescale/enetc/Makefile |   3 +
>  .../ethernet/freescale/enetc/netc_blk_ctrl.c  | 476 ++++++++++++++++++
>  include/linux/fsl/netc_global.h               |  39 ++
>  4 files changed, 532 insertions(+)
>  create mode 100644 drivers/net/ethernet/freescale/enetc/netc_blk_ctrl.c
>  create mode 100644 include/linux/fsl/netc_global.h
>
> diff --git a/drivers/net/ethernet/freescale/enetc/Kconfig b/drivers/net/ethernet/freescale/enetc/Kconfig
> index 4d75e6807e92..51d80ea959d4 100644
> --- a/drivers/net/ethernet/freescale/enetc/Kconfig
> +++ b/drivers/net/ethernet/freescale/enetc/Kconfig
> @@ -75,3 +75,17 @@ config FSL_ENETC_QOS
>  	  enable/disable from user space via Qos commands(tc). In the kernel
>  	  side, it can be loaded by Qos driver. Currently, it is only support
>  	  taprio(802.1Qbv) and Credit Based Shaper(802.1Qbu).
> +
> +config NXP_NETC_BLK_CTRL
> +	tristate "NETC blocks control driver"
> +	help
> +	  This driver configures Integrated Endpoint Register Block (IERB) and
> +	  Privileged Register Block (PRB) of NETC. For i.MX platforms, it also
> +	  includes the configuration of NETCMIX block.
> +	  The IERB contains registers that are used for pre-boot initialization,
> +	  debug, and non-customer configuration. The PRB controls global reset
> +	  and global error handling for NETC. The NETCMIX block is mainly used
> +	  to set MII protocol and PCS protocol of the links, it also contains
> +	  settings for some other functions.
> +
> +	  If compiled as module (M), the module name is nxp-netc-blk-ctrl.
> diff --git a/drivers/net/ethernet/freescale/enetc/Makefile b/drivers/net/ethernet/freescale/enetc/Makefile
> index b13cbbabb2ea..5c277910d538 100644
> --- a/drivers/net/ethernet/freescale/enetc/Makefile
> +++ b/drivers/net/ethernet/freescale/enetc/Makefile
> @@ -19,3 +19,6 @@ fsl-enetc-mdio-y := enetc_pci_mdio.o enetc_mdio.o
>
>  obj-$(CONFIG_FSL_ENETC_PTP_CLOCK) += fsl-enetc-ptp.o
>  fsl-enetc-ptp-y := enetc_ptp.o
> +
> +obj-$(CONFIG_NXP_NETC_BLK_CTRL) += nxp-netc-blk-ctrl.o
> +nxp-netc-blk-ctrl-y := netc_blk_ctrl.o
> \ No newline at end of file
> diff --git a/drivers/net/ethernet/freescale/enetc/netc_blk_ctrl.c b/drivers/net/ethernet/freescale/enetc/netc_blk_ctrl.c
> new file mode 100644
> index 000000000000..b8eec980c199
> --- /dev/null
> +++ b/drivers/net/ethernet/freescale/enetc/netc_blk_ctrl.c
> @@ -0,0 +1,476 @@
> +// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
> +/*
> + * NXP NETC Blocks Control Driver
> + *
> + * Copyright 2024 NXP
> + */
> +#include <linux/clk.h>
> +#include <linux/debugfs.h>
> +#include <linux/delay.h>
> +#include <linux/fsl/netc_global.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> +#include <linux/of_net.h>
> +#include <linux/of_platform.h>
> +#include <linux/phy.h>
> +#include <linux/platform_device.h>
> +#include <linux/seq_file.h>
> +
> +/* NETCMIX registers */
> +#define IMX95_CFG_LINK_IO_VAR		0x0
> +#define  IO_VAR_16FF_16G_SERDES		0x1
> +#define  IO_VAR(port, var)		(((var) & 0xf) << ((port) << 2))
> +
> +#define IMX95_CFG_LINK_MII_PROT		0x4
> +#define CFG_LINK_MII_PORT_0		GENMASK(3, 0)
> +#define CFG_LINK_MII_PORT_1		GENMASK(7, 4)

need #include <linux/bits.h>

> +#define  MII_PROT_MII			0x0
> +#define  MII_PROT_RMII			0x1
> +#define  MII_PROT_RGMII			0x2
> +#define  MII_PROT_SERIAL		0x3
> +#define  MII_PROT(port, prot)		(((prot) & 0xf) << ((port) << 2))
> +
> +#define IMX95_CFG_LINK_PCS_PROT(a)	(0x8 + (a) * 4)
> +#define PCS_PROT_1G_SGMII		BIT(0)
> +#define PCS_PROT_2500M_SGMII		BIT(1)
> +#define PCS_PROT_XFI			BIT(3)
> +#define PCS_PROT_SFI			BIT(4)
> +#define PCS_PROT_10G_SXGMII		BIT(6)
> +
> +/* NETC privileged register block register */
> +#define PRB_NETCRR			0x100
> +#define  NETCRR_SR			BIT(0)
> +#define  NETCRR_LOCK			BIT(1)
> +
> +#define PRB_NETCSR			0x104
> +#define  NETCSR_ERROR			BIT(0)
> +#define  NETCSR_STATE			BIT(1)
> +
> +/* NETC integrated endpoint register block register */
> +#define IERB_EMDIOFAUXR			0x344
> +#define IERB_T0FAUXR			0x444
> +#define IERB_EFAUXR(a)			(0x3044 + 0x100 * (a))
> +#define IERB_VFAUXR(a)			(0x4004 + 0x40 * (a))
> +#define FAUXR_LDID			GENMASK(3, 0)
> +
> +/* Platform information */
> +#define IMX95_ENETC0_BUS_DEVFN		0x0
> +#define IMX95_ENETC1_BUS_DEVFN		0x40
> +#define IMX95_ENETC2_BUS_DEVFN		0x80
> +
> +/* Flags for different platforms */
> +#define NETC_HAS_NETCMIX		BIT(0)
> +
> +struct netc_devinfo {
> +	u32 flags;
> +	int (*netcmix_init)(struct platform_device *pdev);
> +	int (*ierb_init)(struct platform_device *pdev);
> +};
> +
> +struct netc_blk_ctrl {
> +	void __iomem *prb;
> +	void __iomem *ierb;
> +	void __iomem *netcmix;
> +	struct clk *ipg_clk;
> +
> +	const struct netc_devinfo *devinfo;
> +	struct platform_device *pdev;
> +	struct dentry *debugfs_root;
> +};
> +
> +static void netc_reg_write(void __iomem *base, u32 offset, u32 val)
> +{
> +	netc_write(base + offset, val);
> +}
> +
> +static u32 netc_reg_read(void __iomem *base, u32 offset)
> +{
> +	return netc_read(base + offset);
> +}
> +
> +static int netc_of_pci_get_bus_devfn(struct device_node *np)
> +{
> +	u32 reg[5];
> +	int error;
> +
> +	error = of_property_read_u32_array(np, "reg", reg, ARRAY_SIZE(reg));
> +	if (error)
> +		return error;

Avoid parse these common property "reg". if you need untranslate address
you can use of_property_read_reg(), if you need bus translated cpu
address, you can use platform_get_resource().

Frank
> +
> +	return (reg[0] >> 8) & 0xffff;
> +}
> +
> +static int netc_get_link_mii_protocol(phy_interface_t interface)
> +{
> +	switch (interface) {
> +	case PHY_INTERFACE_MODE_MII:
> +		return MII_PROT_MII;
> +	case PHY_INTERFACE_MODE_RMII:
> +		return MII_PROT_RMII;
> +	case PHY_INTERFACE_MODE_RGMII:
> +	case PHY_INTERFACE_MODE_RGMII_ID:
> +	case PHY_INTERFACE_MODE_RGMII_RXID:
> +	case PHY_INTERFACE_MODE_RGMII_TXID:
> +		return MII_PROT_RGMII;
> +	case PHY_INTERFACE_MODE_SGMII:
> +	case PHY_INTERFACE_MODE_2500BASEX:
> +	case PHY_INTERFACE_MODE_10GBASER:
> +	case PHY_INTERFACE_MODE_XGMII:
> +	case PHY_INTERFACE_MODE_USXGMII:
> +		return MII_PROT_SERIAL;
> +	default:
> +		return -EINVAL;
> +	}
> +}
> +
> +static int imx95_netcmix_init(struct platform_device *pdev)
> +{
> +	struct netc_blk_ctrl *priv = platform_get_drvdata(pdev);
> +	struct device_node *np = pdev->dev.of_node;
> +	phy_interface_t interface;
> +	int bus_devfn, mii_proto;
> +	u32 val;
> +	int err;
> +
> +	/* Default setting of MII protocol */
> +	val = MII_PROT(0, MII_PROT_RGMII) | MII_PROT(1, MII_PROT_RGMII) |
> +	      MII_PROT(2, MII_PROT_SERIAL);
> +
> +	/* Update the link MII protocol through parsing phy-mode */
> +	for_each_available_child_of_node_scoped(np, child) {
> +		for_each_available_child_of_node_scoped(child, gchild) {
> +			if (!of_device_is_compatible(gchild, "nxp,imx95-enetc"))
> +				continue;
> +
> +			bus_devfn = netc_of_pci_get_bus_devfn(gchild);
> +			if (bus_devfn < 0)
> +				return -EINVAL;
> +
> +			if (bus_devfn == IMX95_ENETC2_BUS_DEVFN)
> +				continue;
> +
> +			err = of_get_phy_mode(gchild, &interface);
> +			if (err)
> +				continue;
> +
> +			mii_proto = netc_get_link_mii_protocol(interface);
> +			if (mii_proto < 0)
> +				return -EINVAL;
> +
> +			switch (bus_devfn) {
> +			case IMX95_ENETC0_BUS_DEVFN:
> +				val = u32_replace_bits(val, mii_proto,
> +						       CFG_LINK_MII_PORT_0);
> +				break;
> +			case IMX95_ENETC1_BUS_DEVFN:
> +				val = u32_replace_bits(val, mii_proto,
> +						       CFG_LINK_MII_PORT_1);
> +				break;
> +			default:
> +				return -EINVAL;
> +			}
> +		}
> +	}
> +
> +	/* Configure Link I/O variant */
> +	netc_reg_write(priv->netcmix, IMX95_CFG_LINK_IO_VAR,
> +		       IO_VAR(2, IO_VAR_16FF_16G_SERDES));
> +	/* Configure Link 2 PCS protocol */
> +	netc_reg_write(priv->netcmix, IMX95_CFG_LINK_PCS_PROT(2),
> +		       PCS_PROT_10G_SXGMII);
> +	netc_reg_write(priv->netcmix, IMX95_CFG_LINK_MII_PROT, val);
> +
> +	return 0;
> +}
> +
> +static bool netc_ierb_is_locked(struct netc_blk_ctrl *priv)
> +{
> +	return !!(netc_reg_read(priv->prb, PRB_NETCRR) & NETCRR_LOCK);
> +}
> +
> +static int netc_lock_ierb(struct netc_blk_ctrl *priv)
> +{
> +	u32 val;
> +
> +	netc_reg_write(priv->prb, PRB_NETCRR, NETCRR_LOCK);
> +
> +	return read_poll_timeout(netc_reg_read, val, !(val & NETCSR_STATE),
> +				 100, 2000, false, priv->prb, PRB_NETCSR);
> +}
> +
> +static int netc_unlock_ierb_with_warm_reset(struct netc_blk_ctrl *priv)
> +{
> +	u32 val;
> +
> +	netc_reg_write(priv->prb, PRB_NETCRR, 0);
> +
> +	return read_poll_timeout(netc_reg_read, val, !(val & NETCRR_LOCK),
> +				 1000, 100000, true, priv->prb, PRB_NETCRR);
> +}
> +
> +static int imx95_ierb_init(struct platform_device *pdev)
> +{
> +	struct netc_blk_ctrl *priv = platform_get_drvdata(pdev);
> +
> +	/* EMDIO : No MSI-X intterupt */
> +	netc_reg_write(priv->ierb, IERB_EMDIOFAUXR, 0);
> +	/* ENETC0 PF */
> +	netc_reg_write(priv->ierb, IERB_EFAUXR(0), 0);
> +	/* ENETC0 VF0 */
> +	netc_reg_write(priv->ierb, IERB_VFAUXR(0), 1);
> +	/* ENETC0 VF1 */
> +	netc_reg_write(priv->ierb, IERB_VFAUXR(1), 2);
> +	/* ENETC1 PF */
> +	netc_reg_write(priv->ierb, IERB_EFAUXR(1), 3);
> +	/* ENETC1 VF0 */
> +	netc_reg_write(priv->ierb, IERB_VFAUXR(2), 5);
> +	/* ENETC1 VF1 */
> +	netc_reg_write(priv->ierb, IERB_VFAUXR(3), 6);
> +	/* ENETC2 PF */
> +	netc_reg_write(priv->ierb, IERB_EFAUXR(2), 4);
> +	/* ENETC2 VF0 */
> +	netc_reg_write(priv->ierb, IERB_VFAUXR(4), 5);
> +	/* ENETC2 VF1 */
> +	netc_reg_write(priv->ierb, IERB_VFAUXR(5), 6);
> +	/* NETC TIMER */
> +	netc_reg_write(priv->ierb, IERB_T0FAUXR, 7);
> +
> +	return 0;
> +}
> +
> +static int netc_ierb_init(struct platform_device *pdev)
> +{
> +	struct netc_blk_ctrl *priv = platform_get_drvdata(pdev);
> +	const struct netc_devinfo *devinfo = priv->devinfo;
> +	int err;
> +
> +	if (netc_ierb_is_locked(priv)) {
> +		err = netc_unlock_ierb_with_warm_reset(priv);
> +		if (err) {
> +			dev_err(&pdev->dev, "Unlock IERB failed.\n");
> +			return err;
> +		}
> +	}
> +
> +	if (devinfo->ierb_init) {
> +		err = devinfo->ierb_init(pdev);
> +		if (err)
> +			return err;
> +	}
> +
> +	err = netc_lock_ierb(priv);
> +	if (err) {
> +		dev_err(&pdev->dev, "Lock IERB failed.\n");
> +		return err;
> +	}
> +
> +	return 0;
> +}
> +
> +#if IS_ENABLED(CONFIG_DEBUG_FS)
> +static int netc_prb_show(struct seq_file *s, void *data)
> +{
> +	struct netc_blk_ctrl *priv = s->private;
> +	u32 val;
> +
> +	val = netc_reg_read(priv->prb, PRB_NETCRR);
> +	seq_printf(s, "[PRB NETCRR] Lock:%d SR:%d\n",
> +		   (val & NETCRR_LOCK) ? 1 : 0,
> +		   (val & NETCRR_SR) ? 1 : 0);
> +
> +	val = netc_reg_read(priv->prb, PRB_NETCSR);
> +	seq_printf(s, "[PRB NETCSR] State:%d Error:%d\n",
> +		   (val & NETCSR_STATE) ? 1 : 0,
> +		   (val & NETCSR_ERROR) ? 1 : 0);
> +
> +	return 0;
> +}
> +DEFINE_SHOW_ATTRIBUTE(netc_prb);
> +
> +static void netc_blk_ctrl_create_debugfs(struct netc_blk_ctrl *priv)
> +{
> +	struct dentry *root;
> +
> +	root = debugfs_create_dir("netc_blk_ctrl", NULL);
> +	if (IS_ERR(root))
> +		return;
> +
> +	priv->debugfs_root = root;
> +
> +	debugfs_create_file("prb", 0444, root, priv, &netc_prb_fops);
> +}
> +
> +static void netc_blk_ctrl_remove_debugfs(struct netc_blk_ctrl *priv)
> +{
> +	debugfs_remove_recursive(priv->debugfs_root);
> +	priv->debugfs_root = NULL;
> +}
> +
> +#else
> +
> +static void netc_blk_ctrl_create_debugfs(struct netc_blk_ctrl *priv)
> +{
> +}
> +
> +static void netc_blk_ctrl_remove_debugfs(struct netc_blk_ctrl *priv)
> +{
> +}
> +#endif
> +
> +static int netc_prb_check_error(struct netc_blk_ctrl *priv)
> +{
> +	u32 val;
> +
> +	val = netc_reg_read(priv->prb, PRB_NETCSR);
> +	if (val & NETCSR_ERROR)
> +		return -1;
> +
> +	return 0;
> +}
> +
> +static const struct netc_devinfo imx95_devinfo = {
> +	.flags = NETC_HAS_NETCMIX,
> +	.netcmix_init = imx95_netcmix_init,
> +	.ierb_init = imx95_ierb_init,
> +};
> +
> +static const struct of_device_id netc_blk_ctrl_match[] = {
> +	{ .compatible = "nxp,imx95-netc-blk-ctrl", .data = &imx95_devinfo },
> +	{},
> +};
> +MODULE_DEVICE_TABLE(of, netc_blk_ctrl_match);
> +
> +static int netc_blk_ctrl_probe(struct platform_device *pdev)
> +{
> +	struct device_node *node = pdev->dev.of_node;
> +	const struct netc_devinfo *devinfo;
> +	struct device *dev = &pdev->dev;
> +	const struct of_device_id *id;
> +	struct netc_blk_ctrl *priv;
> +	void __iomem *regs;
> +	int err;
> +
> +	if (!node || !of_device_is_available(node)) {
> +		dev_info(dev, "Device is disabled, skipping\n");
> +		return -ENODEV;
> +	}

look like needn't above check.

> +
> +	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> +	if (!priv)
> +		return -ENOMEM;
> +
> +	priv->pdev = pdev;
> +	priv->ipg_clk = devm_clk_get_optional(dev, "ipg_clk");
> +	if (IS_ERR(priv->ipg_clk)) {
> +		dev_err(dev, "Get ipg_clk failed\n");
> +		err = PTR_ERR(priv->ipg_clk);
> +		return err;

return dev_err_probe(dev, err, "...");
same for below error path.

> +	}
> +
> +	err = clk_prepare_enable(priv->ipg_clk);
> +	if (err) {
> +		dev_err(dev, "Enable ipg_clk failed\n");
> +		goto disable_ipg_clk;
> +	}
> +
> +	id = of_match_device(netc_blk_ctrl_match, dev);
> +	if (!id) {
> +		dev_err(dev, "Cannot match device\n");
> +		err = -EINVAL;
> +		goto disable_ipg_clk;
> +	}
> +
> +	devinfo = (struct netc_devinfo *)id->data;
> +	if (!devinfo) {
> +		dev_err(dev, "No device information\n");
> +		err = -EINVAL;
> +		goto disable_ipg_clk;
> +	}
> +	priv->devinfo = devinfo;
> +
> +	regs = devm_platform_ioremap_resource_byname(pdev, "ierb");
> +	if (IS_ERR(regs)) {
> +		err = PTR_ERR(regs);
> +		dev_err(dev, "Missing IERB resource\n");
> +		goto disable_ipg_clk;
> +	}
> +	priv->ierb = regs;
> +
> +	regs = devm_platform_ioremap_resource_byname(pdev, "prb");
> +	if (IS_ERR(regs)) {
> +		err = PTR_ERR(regs);
> +		dev_err(dev, "Missing PRB resource\n");
> +		goto disable_ipg_clk;
> +	}
> +	priv->prb = regs;
> +
> +	if (devinfo->flags & NETC_HAS_NETCMIX) {
> +		regs = devm_platform_ioremap_resource_byname(pdev, "netcmix");
> +		if (IS_ERR(regs)) {
> +			err = PTR_ERR(regs);
> +			dev_err(dev, "Missing NETCMIX resource\n");
> +			goto disable_ipg_clk;
> +		}
> +		priv->netcmix = regs;
> +	}
> +
> +	platform_set_drvdata(pdev, priv);
> +
> +	if (devinfo->netcmix_init) {
> +		err = devinfo->netcmix_init(pdev);
> +		if (err) {
> +			dev_err(dev, "Initializing NETCMIX failed\n");
> +			goto disable_ipg_clk;
> +		}
> +	}
> +
> +	err = netc_ierb_init(pdev);
> +	if (err) {
> +		dev_err(dev, "Initializing IERB failed.\n");
> +		goto disable_ipg_clk;
> +	}
> +
> +	if (netc_prb_check_error(priv) < 0)
> +		dev_warn(dev, "The current IERB configuration is invalid.\n");
> +
> +	netc_blk_ctrl_create_debugfs(priv);
> +
> +	err = of_platform_populate(node, NULL, NULL, dev);
> +	if (err) {
> +		dev_err(dev, "of_platform_populate failed\n");
> +		goto remove_debugfs;
> +	}
> +
> +	return 0;
> +
> +remove_debugfs:
> +	netc_blk_ctrl_remove_debugfs(priv);
> +disable_ipg_clk:
> +	clk_disable_unprepare(priv->ipg_clk);
> +
> +	return err;
> +}
> +
> +static void netc_blk_ctrl_remove(struct platform_device *pdev)
> +{
> +	struct netc_blk_ctrl *priv = platform_get_drvdata(pdev);
> +
> +	of_platform_depopulate(&pdev->dev);
> +	netc_blk_ctrl_remove_debugfs(priv);
> +	clk_disable_unprepare(priv->ipg_clk);
> +}
> +
> +static struct platform_driver netc_blk_ctrl_driver = {
> +	.driver = {
> +		.name = "nxp-netc-blk-ctrl",
> +		.of_match_table = netc_blk_ctrl_match,
> +	},
> +	.probe = netc_blk_ctrl_probe,
> +	.remove = netc_blk_ctrl_remove,
> +};
> +
> +module_platform_driver(netc_blk_ctrl_driver);
> +
> +MODULE_DESCRIPTION("NXP NETC Blocks Control Driver");
> +MODULE_LICENSE("Dual BSD/GPL");
> diff --git a/include/linux/fsl/netc_global.h b/include/linux/fsl/netc_global.h
> new file mode 100644
> index 000000000000..f26b1b6f8813
> --- /dev/null
> +++ b/include/linux/fsl/netc_global.h
> @@ -0,0 +1,39 @@
> +/* SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) */
> +/* Copyright 2024 NXP
> + */
> +#ifndef __NETC_GLOBAL_H
> +#define __NETC_GLOBAL_H
> +
> +#include <linux/io.h>
> +
> +static inline u32 netc_read(void __iomem *reg)
> +{
> +	return ioread32(reg);
> +}
> +
> +#ifdef ioread64
> +static inline u64 netc_read64(void __iomem *reg)
> +{
> +	return ioread64(reg);
> +}
> +#else
> +static inline u64 netc_read64(void __iomem *reg)
> +{
> +	u32 low, high;
> +	u64 val;
> +
> +	low = ioread32(reg);
> +	high = ioread32(reg + 4);
> +
> +	val = (u64)high << 32 | low;
> +
> +	return val;
> +}
> +#endif
> +
> +static inline void netc_write(void __iomem *reg, u32 val)
> +{
> +	iowrite32(val, reg);
> +}

why need two layer register read/write wrap?

netc_reg_write() -> netc_write() -> iowrite32();

Frank
> +
> +#endif
> --
> 2.34.1
>