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: V3: - make the return type of uninit function to void - reorder the Make file change to be alphabetically sorted - fix the compilation error when mdio-mux-regmap is built as module by adding CONFIG_MDIO_BUS_MUX_REGMAP_MODULE macro in mdio-mux.h V2: - Added Kconfig entry for regmap based mdio mux - restrict the comment lines to 80 chars - use kerneldoc formatting for this function documentation. drivers/net/phy/Kconfig | 14 +++ drivers/net/phy/Makefile | 1 + drivers/net/phy/mdio-mux-regmap.c | 169 ++++++++++++++++++++++++++++ include/linux/mdio-mux.h | 34 ++++++ 4 files changed, 218 insertions(+) diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index 3d187cd50eb0..93ef2505caba 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -87,6 +87,20 @@ config MDIO_BUS_MUX_MMIOREG Currently, only 8/16/32 bits registers are supported. +config MDIO_BUS_MUX_REGMAP + tristate "regmap device 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 bus + or spi bus or memory mapped FPGA. + 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 32 bits registers are supported. + config MDIO_CAVIUM tristate diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile index 5805c0b7d60e..99737128de8a 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..bbec43b30f3c --- /dev/null +++ b/drivers/net/phy/mdio-mux-regmap.c @@ -0,0 +1,169 @@ +// SPDX-License-Identifier: GPL-2.0+ + +/* Simple regmap based MDIO MUX driver + * + * Copyright 2018-2019 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 device *dev; + struct regmap *regmap; + u32 mux_reg; + u32 mask; +}; + +/** + * mdio_mux_regmap_switch_fn - This function is called by the mdio-mux layer + * when it thinks the mdio bus multiplexer needs + * to switch. + * @current_child: current value of the mux register (masked via s->mask). + * @desired_child: value of the 'reg' property of the target child MDIO node. + * @data: Private data used by this switch_fn passed to mdio_mux_init function + * via mdio_mux_init(.., .., .., .., data, ..). + * + * 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) + dev_dbg(s->dev, "%s %d -> %d\n", __func__, current_child, + desired_child); + return ret; +} + +/** + * mdio_mux_regmap_init - control MDIO bus muxing using regmap constructs. + * @dev: device with which regmap construct is associated. + * @mux_node: mdio bus mux node that contains parent mdio bus phandle. + * This node also contains sub nodes, where each subnode denotes + * a child mdio bus. All the child mdio buses are muxed, i.e. at a + * time only one of the child mdio buses can be used. + * @data: to store the address of data allocated by this function + */ +int mdio_mux_regmap_init(struct device *dev, + struct device_node *mux_node, + void **data) +{ + struct device_node *child; + struct mdio_mux_regmap_state *s; + int ret; + u32 val; + + dev_dbg(dev, "probing node %pOF\n", mux_node); + + s = devm_kzalloc(dev, sizeof(*s), GFP_KERNEL); + if (!s) + return -ENOMEM; + + s->regmap = dev_get_regmap(dev, NULL); + if (IS_ERR(s->regmap)) { + dev_err(dev, "Failed to get parent regmap\n"); + return PTR_ERR(s->regmap); + } + + ret = of_property_read_u32(mux_node, "reg", &s->mux_reg); + if (ret) { + dev_err(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(dev, "error while reading reg\n"); + return ret; + } + + ret = regmap_write(s->regmap, s->mux_reg, val); + if (ret) { + dev_err(dev, "error while writing reg\n"); + return ret; + } + + ret = of_property_read_u32(mux_node, "mux-mask", &s->mask); + if (ret) { + dev_err(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(mux_node, child) { + ret = of_property_read_u32(child, "reg", &val); + if (ret) { + dev_err(dev, "%pOF is missing a 'reg' property\n", + child); + of_node_put(child); + return -ENODEV; + } + if (val & ~s->mask) { + dev_err(dev, + "%pOF has a 'reg' value with unmasked bits\n", + child); + of_node_put(child); + return -ENODEV; + } + } + + ret = mdio_mux_init(dev, mux_node, mdio_mux_regmap_switch_fn, + &s->mux_handle, s, NULL); + if (ret) { + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to register mdio-mux bus %pOF\n", + mux_node); + return ret; + } + + *data = s; + + return 0; +} +EXPORT_SYMBOL_GPL(mdio_mux_regmap_init); + +/** + * mdio_mux_regmap_uninit - relinquish the control of MDIO bus muxing using + * regmap constructs. + * @data: address of data allocated by mdio_mux_regmap_init + */ +void mdio_mux_regmap_uninit(void *data) +{ + struct mdio_mux_regmap_state *s = data; + + mdio_mux_uninit(s->mux_handle); +} +EXPORT_SYMBOL_GPL(mdio_mux_regmap_uninit); + +MODULE_AUTHOR("Pankaj Bansal <pankaj.ban...@nxp.com>"); +MODULE_DESCRIPTION("regmap based MDIO MUX driver"); +MODULE_LICENSE("GPL"); + diff --git a/include/linux/mdio-mux.h b/include/linux/mdio-mux.h index a5d58f221939..bb5d0e2774bf 100644 --- a/include/linux/mdio-mux.h +++ b/include/linux/mdio-mux.h @@ -29,4 +29,38 @@ int mdio_mux_init(struct device *dev, void mdio_mux_uninit(void *mux_handle); +#if defined(CONFIG_MDIO_BUS_MUX_REGMAP) || \ + defined(CONFIG_MDIO_BUS_MUX_REGMAP_MODULE) +/** + * mdio_mux_regmap_init - control MDIO bus muxing using regmap constructs. + * @dev: device with which regmap construct is associated. + * @mux_node: mdio bus mux node that contains parent mdio bus phandle. + * This node also contains sub nodes, where each subnode denotes + * a child mdio bus. All the child mdio buses are muxed, i.e. at a + * time only one of the child mdio buses can be used. + * @data: to store the address of data allocated by this function + */ +int mdio_mux_regmap_init(struct device *dev, + struct device_node *mux_node, + void **data); + +/** + * mdio_mux_regmap_uninit - relinquish the control of MDIO bus muxing using + * regmap constructs. + * @data: address of data allocated by mdio_mux_regmap_init + */ +void mdio_mux_regmap_uninit(void *data); +#else /* CONFIG_MDIO_BUS_MUX_REGMAP(_MODULE) */ +static inline int mdio_mux_regmap_init(struct device *dev, + struct device_node *mux_node, + void **data) +{ + return -ENODEV; +} + +static inline void mdio_mux_regmap_uninit(void *data) +{ +} +#endif /* CONFIG_MDIO_BUS_MUX_REGMAP(_MODULE) */ + #endif /* __LINUX_MDIO_MUX_H */ -- 2.17.1