From 8880edba06d5deb52ebca7c3eb1bbdcdcc8ec2b6 Mon Sep 17 00:00:00 2001 From: Alex Marginean Date: Fri, 12 Jul 2019 10:13:50 +0300 Subject: net: add MDIO_MUX DM class 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 Reviewed-by: Bin Meng Acked-by: Joe Hershberger --- net/Makefile | 1 + net/mdio-mux-uclass.c | 232 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 233 insertions(+) create mode 100644 net/mdio-mux-uclass.c (limited to 'net') diff --git a/net/Makefile b/net/Makefile index 6251ff3991..826544f05f 100644 --- a/net/Makefile +++ b/net/Makefile @@ -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 index 0000000000..e425207d6e --- /dev/null +++ b/net/mdio-mux-uclass.c @@ -0,0 +1,232 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2019 + * Alex Marginean, NXP + */ + +#include +#include +#include +#include +#include +#include + +#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), +}; -- cgit v1.2.3