Commit 652eb5af authored by Jakub Kicinski's avatar Jakub Kicinski
Browse files

Merge branch 'net-enetc-add-port-mdio-support-for-both-i-mx94-and-i-mx95'

Wei Fang says:

====================
net: enetc: add port MDIO support for both i.MX94 and i.MX95

The NETC IP has one external master MDIO interface (eMDIO) for managing
external PHYs, all ENETC ports share this eMDIO. The EMDIO function and
the ENETC port MDIO are the virtual ports of this eMDIO, ENETC can use
these virtual ports to access their PHYs. The difference is that EMDIO
function is a 'global port', it can access all the PHYs on the eMDIO, so
it provides a means for different software modules to share a single set
of MDIO signals to access their PHYs.

The ENETC port MDIO can only access its own external PHY. Furthermore,
its PHY address must be set to its corresponding LaBCR register in IERB
module, which is is a 64 KB size page containing registers that are used
for pre-boot initialization for all NETC PCIe functions. And this IERB
is owned by the host OS and it will be locked after the initialization,
so it cannot be configured at running time any more. The port MDIO can
only work properly when the PHY address accessed by it matches the value
of its corresponding LaBCR[MDIO_PHYAD_PRTAD]. Otherwise, the MDIO access
by the port MDIO will not take effect.

Note that the same PHY is either controlled by port MDIO or by the EMDIO
function. The netc-blk-ctrl driver will only set the PHY address in the
LaBCR register corresponding to the ENETC when the ENETC node contains
an mdio child node, and the ENETC driver will only create the port MDIO
bus then. An example in DTS is as follows, the EMDIO function will not\
access this PHY.

enetc_port0 {
        phy-handle = <&ethphy0>;
        phy-mode = "rgmii-id";

        mdio {
                #address-cells = <1>;
                #size-cells = <0>;

                ethphy0: ethernet-phy@1 {
                        reg = <1>;
                };
        };
};

If users want to use EMDIO funtion to manage the PHY, they only need to
place the PHY node in the emdio node. The same PHY must not be placed
simultaneously within the ENETC node. An example in DTS to use EMDIO
is as below.

netc_emdio {
        ethphy0: ethernet-phy@1 {
                reg = <1>;
        };

        ethphy2: ethernet-phy@8 {
                reg = <8>;
        };
};

In the host OS, when there are multiple ENETCs, they can all access their
PHYs using their own port MDIO, or they can all access their PHYs using
the EMDIO function, or they can partially use port MDIO and partially use
the EMDIO function.

Another typical use case of port MDIO is the Jailhouse usage. An ENETC is
assigned to a guest OS. The EMDIO function will be unavailable in the
guest OS because EMDIO is controlled by the host OS. Therefore, the ENETC
can use its port MDIO to manage its external PHY in this situation. In
this use case, the host OS's root dtb will disable the ENETC node, so the
host OS's ENETC driver will not probe the ENETC and its PHY.

In addition, this series also adds the internal MDIO bus support, each
ENETC has an internal MDIO interface for managing on-die PHY (PCS) if it
has PCS layer.
====================

Link: https://patch.msgid.link/20251119102557.1041881-1-wei.fang@nxp.com


Signed-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parents f0054f7b 10ba23a7
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -170,6 +170,9 @@
/* Port MAC 0/1 Maximum Frame Length Register */
#define ENETC4_PM_MAXFRM(mac)		(0x5014 + (mac) * 0x400)

/* Port internal MDIO base address, use to access PCS */
#define ENETC4_PM_IMDIO_BASE		0x5030

/* Port MAC 0/1 Pause Quanta Register */
#define ENETC4_PM_PAUSE_QUANTA(mac)	(0x5054 + (mac) * 0x400)

@@ -198,6 +201,9 @@
#define   SSP_1G			2
#define  PM_IF_MODE_ENA			BIT(15)

/* Port external MDIO Base address, use to access off-chip PHY */
#define ENETC4_EMDIO_BASE		0x5c00

/**********************ENETC Pseudo MAC port registers************************/
/* Port pseudo MAC receive octets counter (64-bit) */
#define ENETC4_PPMROCR			0x5080
+12 −2
Original line number Diff line number Diff line
@@ -176,7 +176,12 @@ static int enetc_mdio_probe(struct enetc_pf *pf, struct device_node *np)
	bus->parent = dev;
	mdio_priv = bus->priv;
	mdio_priv->hw = &pf->si->hw;

	if (is_enetc_rev1(pf->si))
		mdio_priv->mdio_base = ENETC_EMDIO_BASE;
	else
		mdio_priv->mdio_base = ENETC4_EMDIO_BASE;

	snprintf(bus->id, MII_BUS_ID_SIZE, "%s", dev_name(dev));

	err = of_mdiobus_register(bus, np);
@@ -221,7 +226,12 @@ static int enetc_imdio_create(struct enetc_pf *pf)
	bus->phy_mask = ~0;
	mdio_priv = bus->priv;
	mdio_priv->hw = &pf->si->hw;

	if (is_enetc_rev1(pf->si))
		mdio_priv->mdio_base = ENETC_PM_IMDIO_BASE;
	else
		mdio_priv->mdio_base = ENETC4_PM_IMDIO_BASE;

	snprintf(bus->id, MII_BUS_ID_SIZE, "%s-imdio", dev_name(dev));

	err = mdiobus_register(bus);
+197 −1
Original line number Diff line number Diff line
@@ -67,6 +67,9 @@
#define IERB_EMDIOFAUXR			0x344
#define IERB_T0FAUXR			0x444
#define IERB_ETBCR(a)			(0x300c + 0x100 * (a))
#define IERB_LBCR(a)			(0x1010 + 0x40 * (a))
#define  LBCR_MDIO_PHYAD_PRTAD(addr)	(((addr) & 0x1f) << 8)

#define IERB_EFAUXR(a)			(0x3044 + 0x100 * (a))
#define IERB_VFAUXR(a)			(0x4004 + 0x40 * (a))
#define FAUXR_LDID			GENMASK(3, 0)
@@ -322,6 +325,142 @@ static int netc_unlock_ierb_with_warm_reset(struct netc_blk_ctrl *priv)
				 1000, 100000, true, priv->prb, PRB_NETCRR);
}

static int netc_get_phy_addr(struct device_node *np)
{
	struct device_node *mdio_node, *phy_node;
	u32 addr = 0;
	int err = 0;

	mdio_node = of_get_child_by_name(np, "mdio");
	if (!mdio_node)
		return 0;

	phy_node = of_get_next_child(mdio_node, NULL);
	if (!phy_node)
		goto of_put_mdio_node;

	err = of_property_read_u32(phy_node, "reg", &addr);
	if (err)
		goto of_put_phy_node;

	if (addr >= PHY_MAX_ADDR)
		err = -EINVAL;

of_put_phy_node:
	of_node_put(phy_node);

of_put_mdio_node:
	of_node_put(mdio_node);

	return err ? err : addr;
}

static int netc_parse_emdio_phy_mask(struct device_node *np, u32 *phy_mask)
{
	u32 mask = 0;

	for_each_child_of_node_scoped(np, child) {
		u32 addr;
		int err;

		err = of_property_read_u32(child, "reg", &addr);
		if (err)
			return err;

		if (addr >= PHY_MAX_ADDR)
			return -EINVAL;

		mask |= BIT(addr);
	}

	*phy_mask = mask;

	return 0;
}

static int netc_get_emdio_phy_mask(struct device_node *np, u32 *phy_mask)
{
	for_each_child_of_node_scoped(np, child) {
		for_each_child_of_node_scoped(child, gchild) {
			if (!of_device_is_compatible(gchild, "pci1131,ee00"))
				continue;

			return netc_parse_emdio_phy_mask(gchild, phy_mask);
		}
	}

	return 0;
}

static int imx95_enetc_mdio_phyaddr_config(struct platform_device *pdev)
{
	struct netc_blk_ctrl *priv = platform_get_drvdata(pdev);
	struct device_node *np = pdev->dev.of_node;
	struct device *dev = &pdev->dev;
	int bus_devfn, addr, err;
	u32 phy_mask = 0;

	err = netc_get_emdio_phy_mask(np, &phy_mask);
	if (err) {
		dev_err(dev, "Failed to get PHY address mask\n");
		return err;
	}

	/* Update the port EMDIO PHY address through parsing phy properties.
	 * This is needed when using the port EMDIO but it's harmless when
	 * using the central EMDIO. So apply it on all cases.
	 */
	for_each_child_of_node_scoped(np, child) {
		for_each_child_of_node_scoped(child, gchild) {
			if (!of_device_is_compatible(gchild, "pci1131,e101"))
				continue;

			bus_devfn = netc_of_pci_get_bus_devfn(gchild);
			if (bus_devfn < 0) {
				dev_err(dev, "Failed to get BDF number\n");
				return bus_devfn;
			}

			addr = netc_get_phy_addr(gchild);
			if (addr < 0) {
				dev_err(dev, "Failed to get PHY address\n");
				return addr;
			}

			if (phy_mask & BIT(addr)) {
				dev_err(dev,
					"Find same PHY address in EMDIO and ENETC node\n");
				return -EINVAL;
			}

			/* The default value of LaBCR[MDIO_PHYAD_PRTAD ] is
			 * 0, so no need to set the register.
			 */
			if (!addr)
				continue;

			switch (bus_devfn) {
			case IMX95_ENETC0_BUS_DEVFN:
				netc_reg_write(priv->ierb, IERB_LBCR(0),
					       LBCR_MDIO_PHYAD_PRTAD(addr));
				break;
			case IMX95_ENETC1_BUS_DEVFN:
				netc_reg_write(priv->ierb, IERB_LBCR(1),
					       LBCR_MDIO_PHYAD_PRTAD(addr));
				break;
			case IMX95_ENETC2_BUS_DEVFN:
				netc_reg_write(priv->ierb, IERB_LBCR(2),
					       LBCR_MDIO_PHYAD_PRTAD(addr));
				break;
			default:
				break;
			}
		}
	}

	return 0;
}

static int imx95_ierb_init(struct platform_device *pdev)
{
	struct netc_blk_ctrl *priv = platform_get_drvdata(pdev);
@@ -349,7 +488,7 @@ static int imx95_ierb_init(struct platform_device *pdev)
	/* NETC TIMER */
	netc_reg_write(priv->ierb, IERB_T0FAUXR, 7);

	return 0;
	return imx95_enetc_mdio_phyaddr_config(pdev);
}

static int imx94_get_enetc_id(struct device_node *np)
@@ -424,12 +563,64 @@ static int imx94_enetc_update_tid(struct netc_blk_ctrl *priv,
	return 0;
}

static int imx94_enetc_mdio_phyaddr_config(struct netc_blk_ctrl *priv,
					   struct device_node *np,
					   u32 phy_mask)
{
	struct device *dev = &priv->pdev->dev;
	int bus_devfn, addr;

	bus_devfn = netc_of_pci_get_bus_devfn(np);
	if (bus_devfn < 0) {
		dev_err(dev, "Failed to get BDF number\n");
		return bus_devfn;
	}

	addr = netc_get_phy_addr(np);
	if (addr <= 0) {
		dev_err(dev, "Failed to get PHY address\n");
		return addr;
	}

	if (phy_mask & BIT(addr)) {
		dev_err(dev,
			"Find same PHY address in EMDIO and ENETC node\n");
		return -EINVAL;
	}

	switch (bus_devfn) {
	case IMX94_ENETC0_BUS_DEVFN:
		netc_reg_write(priv->ierb, IERB_LBCR(IMX94_ENETC0_LINK),
			       LBCR_MDIO_PHYAD_PRTAD(addr));
		break;
	case IMX94_ENETC1_BUS_DEVFN:
		netc_reg_write(priv->ierb, IERB_LBCR(IMX94_ENETC1_LINK),
			       LBCR_MDIO_PHYAD_PRTAD(addr));
		break;
	case IMX94_ENETC2_BUS_DEVFN:
		netc_reg_write(priv->ierb, IERB_LBCR(IMX94_ENETC2_LINK),
			       LBCR_MDIO_PHYAD_PRTAD(addr));
		break;
	default:
		break;
	}

	return 0;
}

static int imx94_ierb_init(struct platform_device *pdev)
{
	struct netc_blk_ctrl *priv = platform_get_drvdata(pdev);
	struct device_node *np = pdev->dev.of_node;
	u32 phy_mask = 0;
	int err;

	err = netc_get_emdio_phy_mask(np, &phy_mask);
	if (err) {
		dev_err(&pdev->dev, "Failed to get PHY address mask\n");
		return err;
	}

	for_each_child_of_node_scoped(np, child) {
		for_each_child_of_node_scoped(child, gchild) {
			if (!of_device_is_compatible(gchild, "pci1131,e101"))
@@ -438,6 +629,11 @@ static int imx94_ierb_init(struct platform_device *pdev)
			err = imx94_enetc_update_tid(priv, gchild);
			if (err)
				return err;

			err = imx94_enetc_mdio_phyaddr_config(priv, gchild,
							      phy_mask);
			if (err)
				return err;
		}
	}