net: add MDIO_MUX DM class
authorAlex Marginean <alexandru.marginean@nxp.com>
Fri, 12 Jul 2019 07:13:50 +0000 (10:13 +0300)
committerJoe Hershberger <joe.hershberger@ni.com>
Thu, 18 Jul 2019 21:37:13 +0000 (16:37 -0500)
Adds a class for MDIO MUXes, which control access to a series of
downstream child MDIOs.
MDIO MUX drivers are required to implement a select function used to switch
between child buses.
MUX children are registered as MDIO buses and they can be used just like
regular MDIOs.

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
include/dm/uclass-id.h
include/miiphy.h
net/Makefile
net/mdio-mux-uclass.c [new file with mode: 0644]

index 635f8d72c2dccb35dcee4911dc9838eb8c7ed20b..0dc26ac254f40ec1072287af3e8fa7c9bb24ef7b 100644 (file)
@@ -24,6 +24,18 @@ config DM_MDIO
          This is currently implemented in net/mdio-uclass.c
          Look in include/miiphy.h for details.
 
+config DM_MDIO_MUX
+       bool "Enable Driver Model for MDIO MUX devices"
+       depends on DM_MDIO
+       help
+         Enable driver model for MDIO MUX devices
+
+         Adds UCLASS_MDIO_MUX DM class supporting MDIO MUXes.  Useful for
+         systems that support DM_MDIO and integrate one or multiple muxes on
+         the MDIO bus.
+         This is currently implemented in net/mdio-mux-uclass.c
+         Look in include/miiphy.h for details.
+
 config MDIO_SANDBOX
        depends on DM_MDIO && SANDBOX
        default y
index f9300a64ceea2534c85adf2ce17a97d7981a7545..d4d96106b3728231c4e1f0abae97c1fac8b697f1 100644 (file)
@@ -59,6 +59,7 @@ enum uclass_id {
        UCLASS_MAILBOX,         /* Mailbox controller */
        UCLASS_MASS_STORAGE,    /* Mass storage device */
        UCLASS_MDIO,            /* MDIO bus */
+       UCLASS_MDIO_MUX,        /* MDIO MUX/switch */
        UCLASS_MISC,            /* Miscellaneous device */
        UCLASS_MMC,             /* SD / MMC card or chip */
        UCLASS_MOD_EXP,         /* RSA Mod Exp device */
index e6dd441983f2ab5447f103ede4d0682397591246..9b97d09f186ba09100e9999a5553439f8fe8035f 100644 (file)
@@ -167,4 +167,24 @@ struct phy_device *dm_mdio_phy_connect(struct udevice *dev, int addr,
 
 #endif
 
+#ifdef CONFIG_DM_MDIO_MUX
+
+/* indicates none of the child buses is selected */
+#define MDIO_MUX_SELECT_NONE   -1
+
+/**
+ * struct mdio_mux_ops - MDIO MUX operations
+ *
+ * @select: Selects a child bus
+ * @deselect: Clean up selection.  Optional, can be NULL
+ */
+struct mdio_mux_ops {
+       int (*select)(struct udevice *mux, int cur, int sel);
+       int (*deselect)(struct udevice *mux, int sel);
+};
+
+#define mdio_mux_get_ops(dev) ((struct mdio_mux_ops *)(dev)->driver->ops)
+
+#endif
+
 #endif
index 6251ff39915ed3c35ff49d4671d9c5362e73c06a..826544f05f912567652766aef9c680437280b87d 100644 (file)
@@ -16,6 +16,7 @@ else
 obj-$(CONFIG_NET)      += eth_legacy.o
 endif
 obj-$(CONFIG_DM_MDIO)  += mdio-uclass.o
+obj-$(CONFIG_DM_MDIO_MUX) += mdio-mux-uclass.o
 obj-$(CONFIG_NET)      += eth_common.o
 obj-$(CONFIG_CMD_LINK_LOCAL) += link_local.o
 obj-$(CONFIG_NET)      += net.o
diff --git a/net/mdio-mux-uclass.c b/net/mdio-mux-uclass.c
new file mode 100644 (file)
index 0000000..e425207
--- /dev/null
@@ -0,0 +1,232 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * (C) Copyright 2019
+ * Alex Marginean, NXP
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <miiphy.h>
+#include <dm/device-internal.h>
+#include <dm/uclass-internal.h>
+#include <dm/lists.h>
+
+#define MDIO_MUX_CHILD_DRV_NAME        "mdio-mux-bus-drv"
+
+/**
+ * struct mdio_mux_perdev_priv - Per-device class data for MDIO MUX DM
+ *
+ * @parent_mdio: Parent DM MDIO device, this is called for actual MDIO I/O after
+ *               setting up the mux.  Typically this is a real MDIO device,
+ *               unless there are cascaded muxes.
+ * @selected:    Current child bus selection.  Defaults to -1
+ */
+struct mdio_mux_perdev_priv {
+       struct udevice *mdio_parent;
+       int selected;
+};
+
+/*
+ * This source file uses three types of devices, as follows:
+ * - mux is the hardware MDIO MUX which selects between the existing child MDIO
+ * buses, this is the device relevant for MDIO MUX class of drivers.
+ * - ch is a child MDIO bus, this is just a representation of a mux selection,
+ * not a real piece of hardware.
+ * - mdio_parent is the actual MDIO bus called to perform reads/writes after
+ * the MUX is configured.  Typically this is a real MDIO device, unless there
+ * are cascaded muxes.
+ */
+
+/**
+ * struct mdio_mux_ch_data - Per-device data for child MDIOs
+ *
+ * @sel: Selection value used by the MDIO MUX to access this child MDIO bus
+ */
+struct mdio_mux_ch_data {
+       int sel;
+};
+
+static struct udevice *mmux_get_parent_mdio(struct udevice *mux)
+{
+       struct mdio_mux_perdev_priv *pdata = dev_get_uclass_priv(mux);
+
+       return pdata->mdio_parent;
+}
+
+static struct mdio_ops *mmux_get_mdio_parent_ops(struct udevice *mux)
+{
+       return mdio_get_ops(mmux_get_parent_mdio(mux));
+}
+
+/* call driver select function before performing MDIO r/w */
+static int mmux_change_sel(struct udevice *ch, bool sel)
+{
+       struct udevice *mux = ch->parent;
+       struct mdio_mux_perdev_priv *priv = dev_get_uclass_priv(mux);
+       struct mdio_mux_ops *ops = mdio_mux_get_ops(mux);
+       struct mdio_mux_ch_data *ch_data = dev_get_parent_platdata(ch);
+       int err = 0;
+
+       if (sel) {
+               err = ops->select(mux, priv->selected, ch_data->sel);
+               if (err)
+                       return err;
+
+               priv->selected = ch_data->sel;
+       } else {
+               if (ops->deselect) {
+                       ops->deselect(mux, ch_data->sel);
+                       priv->selected = MDIO_MUX_SELECT_NONE;
+               }
+       }
+
+       return 0;
+}
+
+/* Read wrapper, sets up the mux before issuing a read on parent MDIO bus */
+static int mmux_read(struct udevice *ch, int addr, int devad,
+                    int reg)
+{
+       struct udevice *mux = ch->parent;
+       struct udevice *parent_mdio = mmux_get_parent_mdio(mux);
+       struct mdio_ops *parent_ops = mmux_get_mdio_parent_ops(mux);
+       int err;
+
+       err = mmux_change_sel(ch, true);
+       if (err)
+               return err;
+
+       err = parent_ops->read(parent_mdio, addr, devad, reg);
+       mmux_change_sel(ch, false);
+
+       return err;
+}
+
+/* Write wrapper, sets up the mux before issuing a write on parent MDIO bus */
+static int mmux_write(struct udevice *ch, int addr, int devad,
+                     int reg, u16 val)
+{
+       struct udevice *mux = ch->parent;
+       struct udevice *parent_mdio = mmux_get_parent_mdio(mux);
+       struct mdio_ops *parent_ops = mmux_get_mdio_parent_ops(mux);
+       int err;
+
+       err = mmux_change_sel(ch, true);
+       if (err)
+               return err;
+
+       err = parent_ops->write(parent_mdio, addr, devad, reg, val);
+       mmux_change_sel(ch, false);
+
+       return err;
+}
+
+/* Reset wrapper, sets up the mux before issuing a reset on parent MDIO bus */
+static int mmux_reset(struct udevice *ch)
+{
+       struct udevice *mux = ch->parent;
+       struct udevice *parent_mdio = mmux_get_parent_mdio(mux);
+       struct mdio_ops *parent_ops = mmux_get_mdio_parent_ops(mux);
+       int err;
+
+       /* reset is optional, if it's not implemented just exit */
+       if (!parent_ops->reset)
+               return 0;
+
+       err = mmux_change_sel(ch, true);
+       if (err)
+               return err;
+
+       err = parent_ops->reset(parent_mdio);
+       mmux_change_sel(ch, false);
+
+       return err;
+}
+
+/* Picks up the mux selection value for each child */
+static int dm_mdio_mux_child_post_bind(struct udevice *ch)
+{
+       struct mdio_mux_ch_data *ch_data = dev_get_parent_platdata(ch);
+
+       ch_data->sel = dev_read_u32_default(ch, "reg", MDIO_MUX_SELECT_NONE);
+
+       if (ch_data->sel == MDIO_MUX_SELECT_NONE)
+               return -EINVAL;
+
+       return 0;
+}
+
+/* Explicitly bind child MDIOs after binding the mux */
+static int dm_mdio_mux_post_bind(struct udevice *mux)
+{
+       ofnode ch_node;
+       int err, first_err = 0;
+
+       if (!ofnode_valid(mux->node)) {
+               debug("%s: no mux node found, no child MDIO busses set up\n",
+                     __func__);
+               return 0;
+       }
+
+       /*
+        * we're going by Linux bindings so the child nodes do not have
+        * compatible strings.  We're going through them here and binding to
+        * them.
+        */
+       dev_for_each_subnode(ch_node, mux) {
+               struct udevice *ch_dev;
+               const char *ch_name;
+
+               ch_name = ofnode_get_name(ch_node);
+
+               err = device_bind_driver_to_node(mux, MDIO_MUX_CHILD_DRV_NAME,
+                                                ch_name, ch_node, &ch_dev);
+               /* try to bind all, but keep 1st error */
+               if (err && !first_err)
+                       first_err = err;
+       }
+
+       return first_err;
+}
+
+/* Get a reference to the parent MDIO bus, it should be bound by now */
+static int dm_mdio_mux_post_probe(struct udevice *mux)
+{
+       struct mdio_mux_perdev_priv *priv = dev_get_uclass_priv(mux);
+       int err;
+
+       priv->selected = MDIO_MUX_SELECT_NONE;
+
+       /* pick up mdio parent from device tree */
+       err = uclass_get_device_by_phandle(UCLASS_MDIO, mux, "mdio-parent-bus",
+                                          &priv->mdio_parent);
+       if (err) {
+               debug("%s: didn't find mdio-parent-bus\n", __func__);
+               return err;
+       }
+
+       return 0;
+}
+
+const struct mdio_ops mmux_child_mdio_ops = {
+       .read = mmux_read,
+       .write = mmux_write,
+       .reset = mmux_reset,
+};
+
+/* MDIO class driver used for MUX child MDIO buses */
+U_BOOT_DRIVER(mdio_mux_child) = {
+       .name           = MDIO_MUX_CHILD_DRV_NAME,
+       .id             = UCLASS_MDIO,
+       .ops            = &mmux_child_mdio_ops,
+};
+
+UCLASS_DRIVER(mdio_mux) = {
+       .id = UCLASS_MDIO_MUX,
+       .name = "mdio-mux",
+       .child_post_bind = dm_mdio_mux_child_post_bind,
+       .post_bind  = dm_mdio_mux_post_bind,
+       .post_probe = dm_mdio_mux_post_probe,
+       .per_device_auto_alloc_size = sizeof(struct mdio_mux_perdev_priv),
+       .per_child_platdata_auto_alloc_size = sizeof(struct mdio_mux_ch_data),
+};