Add support for an MDIO bus multiplexer controlled by a regmap device, like an FPGA.
Tested on a NXP LX2160AQDS board which uses the "QIXIS" FPGA attached to the i2c bus. Signed-off-by: Pankaj Bansal <pankaj.ban...@nxp.com> --- Notes: V2: - replaced be32_to_cpup with of_property_read_u32 - incorporated Andrew's comments drivers/net/phy/Kconfig | 13 +++ drivers/net/phy/Makefile | 1 + drivers/net/phy/mdio-mux-regmap.c | 171 ++++++++++++++++++++++++++++ 3 files changed, 185 insertions(+) diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index 82070792edbb..d1ac9e70cbb2 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -87,6 +87,19 @@ config MDIO_BUS_MUX_MMIOREG Currently, only 8/16/32 bits registers are supported. +config MDIO_BUS_MUX_REGMAP + tristate "REGMAP controlled MDIO bus multiplexers" + depends on OF_MDIO && REGMAP + select MDIO_BUS_MUX + help + This module provides a driver for MDIO bus multiplexers that + are controlled via a regmap device, like an FPGA connected to i2c. + The multiplexer connects one of several child MDIO busses to a + parent bus.Child bus selection is under the control of one of + the FPGA's registers. + + Currently, only upto 32 bits registers are supported. + config MDIO_CAVIUM tristate diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile index 5805c0b7d60e..33053f9f320d 100644 --- a/drivers/net/phy/Makefile +++ b/drivers/net/phy/Makefile @@ -29,6 +29,7 @@ obj-$(CONFIG_MDIO_BUS_MUX) += mdio-mux.o obj-$(CONFIG_MDIO_BUS_MUX_BCM_IPROC) += mdio-mux-bcm-iproc.o obj-$(CONFIG_MDIO_BUS_MUX_GPIO) += mdio-mux-gpio.o obj-$(CONFIG_MDIO_BUS_MUX_MMIOREG) += mdio-mux-mmioreg.o +obj-$(CONFIG_MDIO_BUS_MUX_REGMAP) += mdio-mux-regmap.o obj-$(CONFIG_MDIO_CAVIUM) += mdio-cavium.o obj-$(CONFIG_MDIO_GPIO) += mdio-gpio.o obj-$(CONFIG_MDIO_HISI_FEMAC) += mdio-hisi-femac.o diff --git a/drivers/net/phy/mdio-mux-regmap.c b/drivers/net/phy/mdio-mux-regmap.c new file mode 100644 index 000000000000..6068d05a728a --- /dev/null +++ b/drivers/net/phy/mdio-mux-regmap.c @@ -0,0 +1,171 @@ +// SPDX-License-Identifier: GPL-2.0+ + +/* Simple regmap based MDIO MUX driver + * + * Copyright 2018 NXP + * + * Based on mdio-mux-mmioreg.c by Timur Tabi + * + * Author: + * Pankaj Bansal <pankaj.ban...@nxp.com> + */ + +#include <linux/platform_device.h> +#include <linux/device.h> +#include <linux/of_mdio.h> +#include <linux/module.h> +#include <linux/phy.h> +#include <linux/mdio-mux.h> +#include <linux/regmap.h> + +struct mdio_mux_regmap_state { + void *mux_handle; + struct regmap *regmap; + u32 mux_reg; + u32 mask; +}; + +/* MDIO multiplexing switch function + * + * This function is called by the mdio-mux layer when it thinks the mdio bus + * multiplexer needs to switch. + * + * 'current_child' is the current value of the mux register (masked via + * s->mask). + * + * 'desired_child' is the value of the 'reg' property of the target child MDIO + * node. + * + * The first time this function is called, current_child == -1. + * + * If current_child == desired_child, then the mux is already set to the + * correct bus. + */ +static int mdio_mux_regmap_switch_fn(int current_child, int desired_child, + void *data) +{ + struct mdio_mux_regmap_state *s = data; + bool change; + int ret; + + ret = regmap_update_bits_check(s->regmap, + s->mux_reg, + s->mask, + desired_child, + &change); + + if (ret) + return ret; + if (change) + pr_debug("%s %d -> %d\n", __func__, current_child, + desired_child); + return ret; +} + +static int mdio_mux_regmap_probe(struct platform_device *pdev) +{ + struct device_node *np2, *np = pdev->dev.of_node; + struct mdio_mux_regmap_state *s; + int ret; + u32 val; + + dev_dbg(&pdev->dev, "probing node %pOF\n", np); + + s = devm_kzalloc(&pdev->dev, sizeof(*s), GFP_KERNEL); + if (!s) + return -ENOMEM; + + s->regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (IS_ERR(s->regmap)) { + dev_err(&pdev->dev, "Failed to get parent regmap\n"); + return PTR_ERR(s->regmap); + } + + ret = of_property_read_u32(np, "reg", &s->mux_reg); + if (ret) { + dev_err(&pdev->dev, "missing or invalid reg property\n"); + return -ENODEV; + } + + /* Test Register read write */ + ret = regmap_read(s->regmap, s->mux_reg, &val); + if (ret) { + dev_err(&pdev->dev, "error while reading reg\n"); + return ret; + } + + ret = regmap_write(s->regmap, s->mux_reg, val); + if (ret) { + dev_err(&pdev->dev, "error while writing reg\n"); + return ret; + } + + ret = of_property_read_u32(np, "mux-mask", &s->mask); + if (ret) { + dev_err(&pdev->dev, "missing or invalid mux-mask property\n"); + return -ENODEV; + } + + /* Verify that the 'reg' property of each child MDIO bus does not + * set any bits outside of the 'mask'. + */ + for_each_available_child_of_node(np, np2) { + ret = of_property_read_u32(np2, "reg", &val); + if (ret) { + dev_err(&pdev->dev, "mdio-mux child node %pOF is missing a 'reg' property\n", np2); + of_node_put(np2); + return -ENODEV; + } + if (val & ~s->mask) { + dev_err(&pdev->dev, "mdio-mux child node %pOF has a 'reg' value with unmasked bits\n", np2); + of_node_put(np2); + return -ENODEV; + } + } + + ret = mdio_mux_init(&pdev->dev, pdev->dev.of_node, + mdio_mux_regmap_switch_fn, + &s->mux_handle, s, NULL); + if (ret) { + if (ret != -EPROBE_DEFER) + dev_err(&pdev->dev, + "failed to register mdio-mux bus %pOF\n", np); + return ret; + } + + pdev->dev.platform_data = s; + + return 0; +} + +static int mdio_mux_regmap_remove(struct platform_device *pdev) +{ + struct mdio_mux_regmap_state *s = dev_get_platdata(&pdev->dev); + + mdio_mux_uninit(s->mux_handle); + + return 0; +} + +static const struct of_device_id mdio_mux_regmap_match[] = { + { + .compatible = "mdio-mux-regmap", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, mdio_mux_regmap_match); + +static struct platform_driver mdio_mux_regmap_driver = { + .driver = { + .name = "mdio-mux-regmap", + .of_match_table = mdio_mux_regmap_match, + }, + .probe = mdio_mux_regmap_probe, + .remove = mdio_mux_regmap_remove, +}; + +module_platform_driver(mdio_mux_regmap_driver); + +MODULE_AUTHOR("Pankaj Bansal <pankaj.ban...@nxp.com>"); +MODULE_DESCRIPTION("regmap device MDIO MUX driver"); +MODULE_LICENSE("GPL v2"); -- 2.17.1