drivers: net: add NXP ENETC MDIO driver
authorAlex Marginean <alexandru.marginean@nxp.com>
Wed, 3 Jul 2019 09:11:41 +0000 (12:11 +0300)
committerJoe Hershberger <joe.hershberger@ni.com>
Thu, 25 Jul 2019 18:13:30 +0000 (13:13 -0500)
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>
drivers/net/Kconfig
drivers/net/Makefile
drivers/net/fsl_enetc.c
drivers/net/fsl_enetc.h
drivers/net/fsl_enetc_mdio.c [new file with mode: 0644]

index 802eadf..4d85fb1 100644 (file)
@@ -590,7 +590,7 @@ config HIGMACV300_ETH
 
 config FSL_ENETC
        bool "NXP ENETC Ethernet controller"
-       depends on DM_PCI && DM_ETH
+       depends on DM_PCI && DM_ETH && DM_MDIO
        help
          This driver supports the NXP ENETC Ethernet controller found on some
          of the NXP SoCs.
index a894a42..b6e40df 100644 (file)
@@ -79,4 +79,4 @@ obj-$(CONFIG_MEDIATEK_ETH) += mtk_eth.o
 obj-y += mscc_eswitch/
 obj-$(CONFIG_HIGMACV300_ETH) += higmacv300.o
 obj-$(CONFIG_MDIO_SANDBOX) += mdio_sandbox.o
-obj-$(CONFIG_FSL_ENETC) += fsl_enetc.o
+obj-$(CONFIG_FSL_ENETC) += fsl_enetc.o fsl_enetc_mdio.o
index 58ba63c..f14b484 100644 (file)
@@ -10,6 +10,7 @@
 #include <memalign.h>
 #include <asm/io.h>
 #include <pci.h>
+#include <miiphy.h>
 
 #include "fsl_enetc.h"
 
@@ -38,6 +39,59 @@ static int enetc_bind(struct udevice *dev)
        return 0;
 }
 
+/* Configure the actual/external ethernet PHY, if one is found */
+static void enetc_start_phy(struct udevice *dev)
+{
+       struct enetc_priv *priv = dev_get_priv(dev);
+       struct udevice *miidev;
+       struct phy_device *phy;
+       u32 phandle, phy_id;
+       ofnode phy_node;
+       int supported;
+
+       if (!ofnode_valid(dev->node)) {
+               enetc_dbg(dev, "no enetc ofnode found, skipping PHY set-up\n");
+               return;
+       }
+
+       if (ofnode_read_u32(dev->node, "phy-handle", &phandle)) {
+               enetc_dbg(dev, "phy-handle not found, skipping PHY set-up\n");
+               return;
+       }
+
+       phy_node = ofnode_get_by_phandle(phandle);
+       if (!ofnode_valid(phy_node)) {
+               enetc_dbg(dev, "invalid phy node, skipping PHY set-up\n");
+               return;
+       }
+       enetc_dbg(dev, "phy node: %s\n", ofnode_get_name(phy_node));
+
+       if (ofnode_read_u32(phy_node, "reg", &phy_id)) {
+               enetc_dbg(dev,
+                         "missing reg in PHY node, skipping PHY set-up\n");
+               return;
+       }
+
+       if (uclass_get_device_by_ofnode(UCLASS_MDIO,
+                                       ofnode_get_parent(phy_node),
+                                       &miidev)) {
+               enetc_dbg(dev, "can't find MDIO bus for node %s\n",
+                         ofnode_get_name(ofnode_get_parent(phy_node)));
+               return;
+       }
+
+       phy = dm_mdio_phy_connect(miidev, phy_id, dev, priv->if_type);
+       if (!phy) {
+               enetc_dbg(dev, "dm_mdio_phy_connect returned null\n");
+               return;
+       }
+
+       supported = GENMASK(6, 0); /* speeds up to 1G & AN */
+       phy->advertising = phy->supported & supported;
+       phy_config(phy);
+       phy_startup(phy);
+}
+
 /*
  * Probe ENETC driver:
  * - initialize port and station interface BARs
@@ -249,6 +303,9 @@ static int enetc_start(struct udevice *dev)
        enetc_setup_tx_bdr(dev);
        enetc_setup_rx_bdr(dev);
 
+       priv->if_type = PHY_INTERFACE_MODE_NONE;
+       enetc_start_phy(dev);
+
        return 0;
 }
 
index 155ecc8..fbb9dfa 100644 (file)
@@ -11,6 +11,7 @@
 
 /* PCI function IDs */
 #define PCI_DEVICE_ID_ENETC_ETH                0xE100
+#define PCI_DEVICE_ID_ENETC_MDIO       0xEE01
 
 /* ENETC Ethernet controller registers */
 /* Station interface register offsets */
@@ -143,6 +144,8 @@ struct enetc_priv {
        /* Rx/Tx buffer descriptor rings info */
        struct bd_ring tx_bdr;
        struct bd_ring rx_bdr;
+
+       int if_type;
 };
 
 /* register accessors */
@@ -165,4 +168,22 @@ struct enetc_priv {
 #define enetc_bdr_write(priv, t, n, off, val) \
                        enetc_write(priv, ENETC_BDR(t, n, off), val)
 
+/* ENETC external MDIO registers */
+#define ENETC_MDIO_BASE                0x1c00
+#define ENETC_MDIO_CFG         0x00
+#define  ENETC_EMDIO_CFG_C22   0x00809508
+#define  ENETC_EMDIO_CFG_C45   0x00809548
+#define  ENETC_EMDIO_CFG_RD_ER BIT(1)
+#define  ENETC_EMDIO_CFG_BSY   BIT(0)
+#define ENETC_MDIO_CTL         0x04
+#define  ENETC_MDIO_CTL_READ   BIT(15)
+#define ENETC_MDIO_DATA                0x08
+#define ENETC_MDIO_STAT                0x0c
+
+#define ENETC_MDIO_READ_ERR    0xffff
+
+struct enetc_mdio_priv {
+       void *regs_base;
+};
+
 #endif /* _ENETC_H */
diff --git a/drivers/net/fsl_enetc_mdio.c b/drivers/net/fsl_enetc_mdio.c
new file mode 100644 (file)
index 0000000..46afac0
--- /dev/null
@@ -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);