drivers: net: add NXP ENETC MDIO driver

Adds a driver for the MDIO interface currently integrated in LS1028A SoC.
This MDIO interface is shared by multiple ethernet interfaces and is
presented as a stand-alone PCI function on the SoC ECAM.
Ethernet has a functional dependency on MDIO, for simplicity there is a
single config option for both.

Signed-off-by: Alex Marginean <alexm.osslist@gmail.com>
Reviewed-by: Bin Meng <bmeng.cn@gmail.com>
Acked-by: Joe Hershberger <joe.hershberger@ni.com>
diff --git a/drivers/net/fsl_enetc_mdio.c b/drivers/net/fsl_enetc_mdio.c
new file mode 100644
index 0000000..46afac0
--- /dev/null
+++ b/drivers/net/fsl_enetc_mdio.c
@@ -0,0 +1,149 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * ENETC ethernet controller driver
+ * Copyright 2019 NXP
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <errno.h>
+#include <pci.h>
+#include <miiphy.h>
+#include <asm/io.h>
+#include <asm/processor.h>
+#include <miiphy.h>
+
+#include "fsl_enetc.h"
+
+static void enetc_mdio_wait_bsy(struct enetc_mdio_priv *priv)
+{
+	while (enetc_read(priv, ENETC_MDIO_CFG) & ENETC_EMDIO_CFG_BSY)
+		cpu_relax();
+}
+
+static int enetc_mdio_read_priv(struct enetc_mdio_priv *priv, int addr,
+				int devad, int reg)
+{
+	if (devad == MDIO_DEVAD_NONE)
+		enetc_write(priv, ENETC_MDIO_CFG, ENETC_EMDIO_CFG_C22);
+	else
+		enetc_write(priv, ENETC_MDIO_CFG, ENETC_EMDIO_CFG_C45);
+	enetc_mdio_wait_bsy(priv);
+
+	if (devad == MDIO_DEVAD_NONE) {
+		enetc_write(priv, ENETC_MDIO_CTL, ENETC_MDIO_CTL_READ |
+			    (addr << 5) | reg);
+	} else {
+		enetc_write(priv, ENETC_MDIO_CTL, (addr << 5) + devad);
+		enetc_mdio_wait_bsy(priv);
+
+		enetc_write(priv, ENETC_MDIO_STAT, reg);
+		enetc_mdio_wait_bsy(priv);
+
+		enetc_write(priv, ENETC_MDIO_CTL, ENETC_MDIO_CTL_READ |
+			    (addr << 5) | devad);
+	}
+
+	enetc_mdio_wait_bsy(priv);
+	if (enetc_read(priv, ENETC_MDIO_CFG) & ENETC_EMDIO_CFG_RD_ER)
+		return ENETC_MDIO_READ_ERR;
+
+	return enetc_read(priv, ENETC_MDIO_DATA);
+}
+
+static int enetc_mdio_write_priv(struct enetc_mdio_priv *priv, int addr,
+				 int devad, int reg, u16 val)
+{
+	if (devad == MDIO_DEVAD_NONE)
+		enetc_write(priv, ENETC_MDIO_CFG, ENETC_EMDIO_CFG_C22);
+	else
+		enetc_write(priv, ENETC_MDIO_CFG, ENETC_EMDIO_CFG_C45);
+	enetc_mdio_wait_bsy(priv);
+
+	if (devad != MDIO_DEVAD_NONE) {
+		enetc_write(priv, ENETC_MDIO_CTL, (addr << 5) + devad);
+		enetc_write(priv, ENETC_MDIO_STAT, reg);
+	} else {
+		enetc_write(priv, ENETC_MDIO_CTL, (addr << 5) + reg);
+	}
+	enetc_mdio_wait_bsy(priv);
+
+	enetc_write(priv, ENETC_MDIO_DATA, val);
+	enetc_mdio_wait_bsy(priv);
+
+	return 0;
+}
+
+/* DM wrappers */
+static int dm_enetc_mdio_read(struct udevice *dev, int addr, int devad, int reg)
+{
+	struct enetc_mdio_priv *priv = dev_get_priv(dev);
+
+	return enetc_mdio_read_priv(priv, addr, devad, reg);
+}
+
+static int dm_enetc_mdio_write(struct udevice *dev, int addr, int devad,
+			       int reg, u16 val)
+{
+	struct enetc_mdio_priv *priv = dev_get_priv(dev);
+
+	return enetc_mdio_write_priv(priv, addr, devad, reg, val);
+}
+
+static const struct mdio_ops enetc_mdio_ops = {
+	.read = dm_enetc_mdio_read,
+	.write = dm_enetc_mdio_write,
+};
+
+static int enetc_mdio_bind(struct udevice *dev)
+{
+	char name[16];
+	static int eth_num_devices;
+
+	/*
+	 * prefer using PCI function numbers to number interfaces, but these
+	 * are only available if dts nodes are present.  For PCI they are
+	 * optional, handle that case too.  Just in case some nodes are present
+	 * and some are not, use different naming scheme - enetc-N based on
+	 * PCI function # and enetc#N based on interface count
+	 */
+	if (ofnode_valid(dev->node))
+		sprintf(name, "emdio-%u", PCI_FUNC(pci_get_devfn(dev)));
+	else
+		sprintf(name, "emdio#%u", eth_num_devices++);
+	device_set_name(dev, name);
+
+	return 0;
+}
+
+static int enetc_mdio_probe(struct udevice *dev)
+{
+	struct enetc_mdio_priv *priv = dev_get_priv(dev);
+
+	priv->regs_base = dm_pci_map_bar(dev, PCI_BASE_ADDRESS_0, 0);
+	if (!priv->regs_base) {
+		enetc_dbg(dev, "failed to map BAR0\n");
+		return -EINVAL;
+	}
+
+	priv->regs_base += ENETC_MDIO_BASE;
+
+	dm_pci_clrset_config16(dev, PCI_COMMAND, 0, PCI_COMMAND_MEMORY);
+
+	return 0;
+}
+
+U_BOOT_DRIVER(enetc_mdio) = {
+	.name	= "enetc_mdio",
+	.id	= UCLASS_MDIO,
+	.bind	= enetc_mdio_bind,
+	.probe	= enetc_mdio_probe,
+	.ops	= &enetc_mdio_ops,
+	.priv_auto_alloc_size = sizeof(struct enetc_mdio_priv),
+};
+
+static struct pci_device_id enetc_mdio_ids[] = {
+	{ PCI_DEVICE(PCI_VENDOR_ID_FREESCALE, PCI_DEVICE_ID_ENETC_MDIO) },
+};
+
+U_BOOT_PCI_DEVICE(enetc_mdio, enetc_mdio_ids);