text: // SPDX-License-Identifier: GPL-2.0+
  /*
   * (C) Copyright 2019
   * Alex Marginean, NXP
   */
  
  #include <common.h>
  #include <dm.h>
  #include <log.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_plat(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_plat(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 (!dev_has_ofnode(mux)) {
  		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	= sizeof(struct mdio_mux_perdev_priv),
  	.per_child_plat_auto	= sizeof(struct mdio_mux_ch_data),
  };
  