diff options
| -rw-r--r-- | MAINTAINERS | 1 | ||||
| -rw-r--r-- | arch/Kconfig | 1 | ||||
| -rw-r--r-- | arch/sandbox/dts/test.dts | 23 | ||||
| -rw-r--r-- | cmd/mdio.c | 5 | ||||
| -rw-r--r-- | doc/device-tree-bindings/net/mdio-mux.txt | 138 | ||||
| -rw-r--r-- | drivers/net/Kconfig | 22 | ||||
| -rw-r--r-- | drivers/net/Makefile | 1 | ||||
| -rw-r--r-- | drivers/net/designware.c | 20 | ||||
| -rw-r--r-- | drivers/net/mdio_mux_sandbox.c | 97 | ||||
| -rw-r--r-- | drivers/net/mdio_sandbox.c | 16 | ||||
| -rw-r--r-- | drivers/net/phy/aquantia.c | 28 | ||||
| -rw-r--r-- | drivers/net/phy/phy.c | 11 | ||||
| -rw-r--r-- | include/dm/uclass-id.h | 1 | ||||
| -rw-r--r-- | include/miiphy.h | 20 | ||||
| -rw-r--r-- | include/net.h | 4 | ||||
| -rw-r--r-- | include/phy.h | 80 | ||||
| -rw-r--r-- | net/Makefile | 1 | ||||
| -rw-r--r-- | net/mdio-mux-uclass.c | 232 | ||||
| -rw-r--r-- | test/dm/Makefile | 1 | ||||
| -rw-r--r-- | test/dm/mdio.c | 3 | ||||
| -rw-r--r-- | test/dm/mdio_mux.c | 80 | 
21 files changed, 770 insertions, 15 deletions
| diff --git a/MAINTAINERS b/MAINTAINERS index bc67c499657..2fcc60f40fb 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -616,6 +616,7 @@ M:	Joe Hershberger <joe.hershberger@ni.com>  S:	Maintained  T:	git https://gitlab.denx.de/u-boot/custodians/u-boot-net.git  F:	drivers/net/ +F:	include/net.h  F:	net/  NIOS diff --git a/arch/Kconfig b/arch/Kconfig index a946af816f2..949eb28dfad 100644 --- a/arch/Kconfig +++ b/arch/Kconfig @@ -125,6 +125,7 @@ config SANDBOX  	imply PCH  	imply PHYLIB  	imply DM_MDIO +	imply DM_MDIO_MUX  config SH  	bool "SuperH architecture" diff --git a/arch/sandbox/dts/test.dts b/arch/sandbox/dts/test.dts index 531c1afc973..a0856764f6a 100644 --- a/arch/sandbox/dts/test.dts +++ b/arch/sandbox/dts/test.dts @@ -824,7 +824,28 @@  		dma-names = "m2m", "tx0", "rx0";  	}; -	mdio-test { +	/* +	 * keep mdio-mux ahead of mdio so that the mux is removed first at the +	 * end of the test.  If parent mdio is removed first, clean-up of the +	 * mux will trigger a 2nd probe of parent-mdio, leaving parent-mdio +	 * active at the end of the test.  That it turn doesn't allow the mdio +	 * class to be destroyed, triggering an error. +	 */ +	mdio-mux-test { +		compatible = "sandbox,mdio-mux"; +		#address-cells = <1>; +		#size-cells = <0>; +		mdio-parent-bus = <&mdio>; + +		mdio-ch-test@0 { +			reg = <0>; +		}; +		mdio-ch-test@1 { +			reg = <1>; +		}; +	}; + +	mdio: mdio-test {  		compatible = "sandbox,mdio";  	};  }; diff --git a/cmd/mdio.c b/cmd/mdio.c index a6fa9266d0c..add6440813f 100644 --- a/cmd/mdio.c +++ b/cmd/mdio.c @@ -268,6 +268,11 @@ static int do_mdio(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])  		break;  	} +	if (!bus) { +		puts("No MDIO bus found\n"); +		return CMD_RET_FAILURE; +	} +  	if (op[0] == 'l') {  		mdio_list_devices(); diff --git a/doc/device-tree-bindings/net/mdio-mux.txt b/doc/device-tree-bindings/net/mdio-mux.txt new file mode 100644 index 00000000000..eaa31efda23 --- /dev/null +++ b/doc/device-tree-bindings/net/mdio-mux.txt @@ -0,0 +1,138 @@ +The expected structure of an MDIO MUX device tree node is described here.  This +is heavily based on current Linux specification. +One notable difference to Linux is that mdio-parent-bus is currently required +by U-Boot, not optional as is in Linux.  Current U-Boot MDIO MUX udevice class +implementation does not have specific support for MDIOs with an integrated MUX, +the property should be made optional if such support is added. + +The MDIO buses downstream of the MUX should be described in the device tree as +child nodes as indicated below. + +Required properties: +mdio-parent-bus = a phandle to the MDIO bus used to perform actual I/O.  This is +                  typically a real MDIO device, unless there are cascaded MUXes. +#address-cells = <1>, each MDIO group is identified by one 32b value. +#size-cells = <0> + +Other properties: +The properties described here are sufficient for MDIO MUX DM class code, but +MUX drivers may define additional properties, either required or optional. + +Required properties in child nodes: +reg = value to be configured on the MUX to select the respective downstream +      MDIO. + +Child nodes should normally contain PHY nodes, referenced by phandle from +ethernet nodes of the eth interfaces using these PHYs. + +Example structure, extracted from Linux bindings document: + +	/* The parent MDIO bus. */ +	smi1: mdio@1180000001900 { +		compatible = "cavium,octeon-3860-mdio"; +		#address-cells = <1>; +		#size-cells = <0>; +		reg = <0x11800 0x00001900 0x0 0x40>; +	}; +	/* +	 * An NXP sn74cbtlv3253 dual 1-of-4 switch controlled by a +	 * pair of GPIO lines.  Child busses 2 and 3 populated with 4 +	 * PHYs each. +	 */ +	mdio-mux { +		compatible = "mdio-mux-gpio"; +		gpios = <&gpio1 3 0>, <&gpio1 4 0>; +		mdio-parent-bus = <&smi1>; +		#address-cells = <1>; +		#size-cells = <0>; +		mdio@2 { +			reg = <2>; +			#address-cells = <1>; +			#size-cells = <0>; +			phy11: ethernet-phy@1 { +				reg = <1>; +				compatible = "marvell,88e1149r"; +				marvell,reg-init = <3 0x10 0 0x5777>, +					<3 0x11 0 0x00aa>, +					<3 0x12 0 0x4105>, +					<3 0x13 0 0x0a60>; +				interrupt-parent = <&gpio>; +				interrupts = <10 8>; /* Pin 10, active low */ +			}; +			phy12: ethernet-phy@2 { +				reg = <2>; +				compatible = "marvell,88e1149r"; +				marvell,reg-init = <3 0x10 0 0x5777>, +					<3 0x11 0 0x00aa>, +					<3 0x12 0 0x4105>, +					<3 0x13 0 0x0a60>; +				interrupt-parent = <&gpio>; +				interrupts = <10 8>; /* Pin 10, active low */ +			}; +			phy13: ethernet-phy@3 { +				reg = <3>; +				compatible = "marvell,88e1149r"; +				marvell,reg-init = <3 0x10 0 0x5777>, +					<3 0x11 0 0x00aa>, +					<3 0x12 0 0x4105>, +					<3 0x13 0 0x0a60>; +				interrupt-parent = <&gpio>; +				interrupts = <10 8>; /* Pin 10, active low */ +			}; +			phy14: ethernet-phy@4 { +				reg = <4>; +				compatible = "marvell,88e1149r"; +				marvell,reg-init = <3 0x10 0 0x5777>, +					<3 0x11 0 0x00aa>, +					<3 0x12 0 0x4105>, +					<3 0x13 0 0x0a60>; +				interrupt-parent = <&gpio>; +				interrupts = <10 8>; /* Pin 10, active low */ +			}; +		}; +		mdio@3 { +			reg = <3>; +			#address-cells = <1>; +			#size-cells = <0>; +			phy21: ethernet-phy@1 { +				reg = <1>; +				compatible = "marvell,88e1149r"; +				marvell,reg-init = <3 0x10 0 0x5777>, +					<3 0x11 0 0x00aa>, +					<3 0x12 0 0x4105>, +					<3 0x13 0 0x0a60>; +				interrupt-parent = <&gpio>; +				interrupts = <12 8>; /* Pin 12, active low */ +			}; +			phy22: ethernet-phy@2 { +				reg = <2>; +				compatible = "marvell,88e1149r"; +				marvell,reg-init = <3 0x10 0 0x5777>, +					<3 0x11 0 0x00aa>, +					<3 0x12 0 0x4105>, +					<3 0x13 0 0x0a60>; +				interrupt-parent = <&gpio>; +				interrupts = <12 8>; /* Pin 12, active low */ +			}; +			phy23: ethernet-phy@3 { +				reg = <3>; +				compatible = "marvell,88e1149r"; +				marvell,reg-init = <3 0x10 0 0x5777>, +					<3 0x11 0 0x00aa>, +					<3 0x12 0 0x4105>, +					<3 0x13 0 0x0a60>; +				interrupt-parent = <&gpio>; +				interrupts = <12 8>; /* Pin 12, active low */ +			}; +			phy24: ethernet-phy@4 { +				reg = <4>; +				compatible = "marvell,88e1149r"; +				marvell,reg-init = <3 0x10 0 0x5777>, +					<3 0x11 0 0x00aa>, +					<3 0x12 0 0x4105>, +					<3 0x13 0 0x0a60>; +				interrupt-parent = <&gpio>; +				interrupts = <12 8>; /* Pin 12, active low */ +			}; +		}; +	}; diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index 635f8d72c2d..403df5e9600 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -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 @@ -34,6 +46,16 @@ config MDIO_SANDBOX  	  This driver is used in for testing in test/dm/mdio.c +config MDIO_MUX_SANDBOX +	depends on DM_MDIO_MUX && MDIO_SANDBOX +	default y +	bool "Sandbox: Mocked MDIO-MUX driver" +	help +	  This driver implements dummy select/deselect ops mimicking a MUX on +	  the MDIO bux.  It uses mdio_sandbox driver as parent MDIO. + +	  This driver is used for testing in test/dm/mdio.c +  menuconfig NETDEVICES  	bool "Network device support"  	depends on NET diff --git a/drivers/net/Makefile b/drivers/net/Makefile index 40038427dbd..3c473b205d5 100644 --- a/drivers/net/Makefile +++ b/drivers/net/Makefile @@ -37,6 +37,7 @@ obj-$(CONFIG_LAN91C96) += lan91c96.o  obj-$(CONFIG_LPC32XX_ETH) += lpc32xx_eth.o  obj-$(CONFIG_MACB) += macb.o  obj-$(CONFIG_MCFFEC) += mcffec.o mcfmii.o +obj-$(CONFIG_MDIO_MUX_SANDBOX) += mdio_mux_sandbox.o  obj-$(CONFIG_MPC8XX_FEC) += mpc8xx_fec.o  obj-$(CONFIG_MT7628_ETH) += mt7628-eth.o  obj-$(CONFIG_MVGBE) += mvgbe.o diff --git a/drivers/net/designware.c b/drivers/net/designware.c index 2c5d9560c58..3b6cf5ddb50 100644 --- a/drivers/net/designware.c +++ b/drivers/net/designware.c @@ -677,10 +677,10 @@ int designware_eth_probe(struct udevice *dev)  	struct dw_eth_dev *priv = dev_get_priv(dev);  	u32 iobase = pdata->iobase;  	ulong ioaddr; -	int ret; +	int ret, err;  	struct reset_ctl_bulk reset_bulk;  #ifdef CONFIG_CLK -	int i, err, clock_nb; +	int i, clock_nb;  	priv->clock_count = 0;  	clock_nb = dev_count_phandle_with_args(dev, "clocks", "#clock-cells"); @@ -753,13 +753,23 @@ int designware_eth_probe(struct udevice *dev)  	priv->interface = pdata->phy_interface;  	priv->max_speed = pdata->max_speed; -	dw_mdio_init(dev->name, dev); +	ret = dw_mdio_init(dev->name, dev); +	if (ret) { +		err = ret; +		goto mdio_err; +	}  	priv->bus = miiphy_get_dev_by_name(dev->name);  	ret = dw_phy_init(priv, dev);  	debug("%s, ret=%d\n", __func__, ret); +	if (!ret) +		return 0; -	return ret; +	/* continue here for cleanup if no PHY found */ +	err = ret; +	mdio_unregister(priv->bus); +	mdio_free(priv->bus); +mdio_err:  #ifdef CONFIG_CLK  clk_err: @@ -767,8 +777,8 @@ clk_err:  	if (ret)  		pr_err("failed to disable all clocks\n"); -	return err;  #endif +	return err;  }  static int designware_eth_remove(struct udevice *dev) diff --git a/drivers/net/mdio_mux_sandbox.c b/drivers/net/mdio_mux_sandbox.c new file mode 100644 index 00000000000..3dba4d18a15 --- /dev/null +++ b/drivers/net/mdio_mux_sandbox.c @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2019 + * Alex Marginean, NXP + */ + +#include <dm.h> +#include <errno.h> +#include <miiphy.h> + +/* macros copied over from mdio_sandbox.c */ +#define SANDBOX_PHY_ADDR	5 +#define SANDBOX_PHY_REG_CNT	2 + +struct mdio_mux_sandbox_priv { +	int enabled; +	int sel; +}; + +static int mdio_mux_sandbox_mark_selection(struct udevice *dev, int sel) +{ +	struct udevice *mdio; +	struct mdio_ops *ops; +	int err; + +	/* +	 * find the sandbox parent mdio and write a register on the PHY there +	 * so the mux test can verify selection. +	 */ +	err = uclass_get_device_by_name(UCLASS_MDIO, "mdio-test", &mdio); +	if (err) +		return err; +	ops = mdio_get_ops(mdio); +	return ops->write(mdio, SANDBOX_PHY_ADDR, MDIO_DEVAD_NONE, +			  SANDBOX_PHY_REG_CNT - 1, (u16)sel); +} + +static int mdio_mux_sandbox_select(struct udevice *dev, int cur, int sel) +{ +	struct mdio_mux_sandbox_priv *priv = dev_get_priv(dev); + +	if (!priv->enabled) +		return -ENODEV; + +	if (cur != priv->sel) +		return -EINVAL; + +	priv->sel = sel; +	mdio_mux_sandbox_mark_selection(dev, priv->sel); + +	return 0; +} + +static int mdio_mux_sandbox_deselect(struct udevice *dev, int sel) +{ +	struct mdio_mux_sandbox_priv *priv = dev_get_priv(dev); + +	if (!priv->enabled) +		return -ENODEV; + +	if (sel != priv->sel) +		return -EINVAL; + +	priv->sel = -1; +	mdio_mux_sandbox_mark_selection(dev, priv->sel); + +	return 0; +} + +static const struct mdio_mux_ops mdio_mux_sandbox_ops = { +	.select = mdio_mux_sandbox_select, +	.deselect = mdio_mux_sandbox_deselect, +}; + +static int mdio_mux_sandbox_probe(struct udevice *dev) +{ +	struct mdio_mux_sandbox_priv *priv = dev_get_priv(dev); + +	priv->enabled = 1; +	priv->sel = -1; + +	return 0; +} + +static const struct udevice_id mdio_mux_sandbox_ids[] = { +	{ .compatible = "sandbox,mdio-mux" }, +	{ } +}; + +U_BOOT_DRIVER(mdio_mux_sandbox) = { +	.name		= "mdio_mux_sandbox", +	.id		= UCLASS_MDIO_MUX, +	.of_match	= mdio_mux_sandbox_ids, +	.probe		= mdio_mux_sandbox_probe, +	.ops		= &mdio_mux_sandbox_ops, +	.priv_auto_alloc_size = sizeof(struct mdio_mux_sandbox_priv), +}; diff --git a/drivers/net/mdio_sandbox.c b/drivers/net/mdio_sandbox.c index 07515e078c2..df053f53814 100644 --- a/drivers/net/mdio_sandbox.c +++ b/drivers/net/mdio_sandbox.c @@ -9,11 +9,11 @@  #include <miiphy.h>  #define SANDBOX_PHY_ADDR	5 -#define SANDBOX_PHY_REG		0 +#define SANDBOX_PHY_REG_CNT	2  struct mdio_sandbox_priv {  	int enabled; -	u16 reg; +	u16 reg[SANDBOX_PHY_REG_CNT];  };  static int mdio_sandbox_read(struct udevice *dev, int addr, int devad, int reg) @@ -27,10 +27,10 @@ static int mdio_sandbox_read(struct udevice *dev, int addr, int devad, int reg)  		return -ENODEV;  	if (devad != MDIO_DEVAD_NONE)  		return -ENODEV; -	if (reg != SANDBOX_PHY_REG) +	if (reg < 0 || reg > SANDBOX_PHY_REG_CNT)  		return -ENODEV; -	return priv->reg; +	return priv->reg[reg];  }  static int mdio_sandbox_write(struct udevice *dev, int addr, int devad, int reg, @@ -45,10 +45,10 @@ static int mdio_sandbox_write(struct udevice *dev, int addr, int devad, int reg,  		return -ENODEV;  	if (devad != MDIO_DEVAD_NONE)  		return -ENODEV; -	if (reg != SANDBOX_PHY_REG) +	if (reg < 0 || reg > SANDBOX_PHY_REG_CNT)  		return -ENODEV; -	priv->reg = val; +	priv->reg[reg] = val;  	return 0;  } @@ -56,8 +56,10 @@ static int mdio_sandbox_write(struct udevice *dev, int addr, int devad, int reg,  static int mdio_sandbox_reset(struct udevice *dev)  {  	struct mdio_sandbox_priv *priv = dev_get_priv(dev); +	int i; -	priv->reg = 0; +	for (i = 0; i < SANDBOX_PHY_REG_CNT; i++) +		priv->reg[i] = 0;  	return 0;  } diff --git a/drivers/net/phy/aquantia.c b/drivers/net/phy/aquantia.c index 5c3298d612c..465ec2d3423 100644 --- a/drivers/net/phy/aquantia.c +++ b/drivers/net/phy/aquantia.c @@ -461,6 +461,19 @@ struct phy_driver aqr107_driver = {  	.shutdown = &gen10g_shutdown,  }; +struct phy_driver aqr112_driver = { +	.name = "Aquantia AQR112", +	.uid = 0x3a1b660, +	.mask = 0xfffffff0, +	.features = PHY_10G_FEATURES, +	.mmds = (MDIO_MMD_PMAPMD | MDIO_MMD_PCS | +		 MDIO_MMD_PHYXS | MDIO_MMD_AN | +		 MDIO_MMD_VEND1), +	.config = &aquantia_config, +	.startup = &aquantia_startup, +	.shutdown = &gen10g_shutdown, +}; +  struct phy_driver aqr405_driver = {  	.name = "Aquantia AQR405",  	.uid = 0x3a1b4b2, @@ -474,6 +487,19 @@ struct phy_driver aqr405_driver = {  	.shutdown = &gen10g_shutdown,  }; +struct phy_driver aqr412_driver = { +	.name = "Aquantia AQR412", +	.uid = 0x3a1b710, +	.mask = 0xfffffff0, +	.features = PHY_10G_FEATURES, +	.mmds = (MDIO_MMD_PMAPMD | MDIO_MMD_PCS | +		 MDIO_MMD_PHYXS | MDIO_MMD_AN | +		 MDIO_MMD_VEND1), +	.config = &aquantia_config, +	.startup = &aquantia_startup, +	.shutdown = &gen10g_shutdown, +}; +  int phy_aquantia_init(void)  {  	phy_register(&aq1202_driver); @@ -481,7 +507,9 @@ int phy_aquantia_init(void)  	phy_register(&aqr105_driver);  	phy_register(&aqr106_driver);  	phy_register(&aqr107_driver); +	phy_register(&aqr112_driver);  	phy_register(&aqr405_driver); +	phy_register(&aqr412_driver);  	return 0;  } diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c index c1c1af9abdb..ae37dd6c1e3 100644 --- a/drivers/net/phy/phy.c +++ b/drivers/net/phy/phy.c @@ -727,12 +727,23 @@ static struct phy_device *create_phy_by_mask(struct mii_dev *bus,  	while (phy_mask) {  		int addr = ffs(phy_mask) - 1;  		int r = get_phy_id(bus, addr, devad, &phy_id); + +		/* +		 * If the PHY ID is flat 0 we ignore it.  There are C45 PHYs +		 * that return all 0s for C22 reads (like Aquantia AQR112) and +		 * there are C22 PHYs that return all 0s for C45 reads (like +		 * Atheros AR8035). +		 */ +		if (r == 0 && phy_id == 0) +			goto next; +  		/* If the PHY ID is mostly f's, we didn't find anything */  		if (r == 0 && (phy_id & 0x1fffffff) != 0x1fffffff) {  			is_c45 = (devad == MDIO_DEVAD_NONE) ? false : true;  			return phy_device_create(bus, addr, phy_id, is_c45,  						 interface);  		} +next:  		phy_mask &= ~(1 << addr);  	}  	return NULL; diff --git a/include/dm/uclass-id.h b/include/dm/uclass-id.h index f9300a64cee..d4d96106b37 100644 --- a/include/dm/uclass-id.h +++ b/include/dm/uclass-id.h @@ -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 */ diff --git a/include/miiphy.h b/include/miiphy.h index e6dd441983f..9b97d09f186 100644 --- a/include/miiphy.h +++ b/include/miiphy.h @@ -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 diff --git a/include/net.h b/include/net.h index 44b32385c40..7684076af63 100644 --- a/include/net.h +++ b/include/net.h @@ -728,7 +728,7 @@ static inline struct in_addr net_read_ip(void *from)  }  /* return ulong *in network byteorder* */ -static inline u32 net_read_u32(u32 *from) +static inline u32 net_read_u32(void *from)  {  	u32 l; @@ -749,7 +749,7 @@ static inline void net_copy_ip(void *to, void *from)  }  /* copy ulong */ -static inline void net_copy_u32(u32 *to, u32 *from) +static inline void net_copy_u32(void *to, void *from)  {  	memcpy((void *)to, (void *)from, sizeof(u32));  } diff --git a/include/phy.h b/include/phy.h index d01435d1aa1..f4530faeb99 100644 --- a/include/phy.h +++ b/include/phy.h @@ -246,15 +246,71 @@ static inline int is_10g_interface(phy_interface_t interface)  #endif +/** + * phy_init() - Initializes the PHY drivers + * + * This function registers all available PHY drivers + * + * @return 0 if OK, -ve on error + */  int phy_init(void); + +/** + * phy_reset() - Resets the specified PHY + * + * Issues a reset of the PHY and waits for it to complete + * + * @phydev:	PHY to reset + * @return 0 if OK, -ve on error + */  int phy_reset(struct phy_device *phydev); + +/** + * phy_find_by_mask() - Searches for a PHY on the specified MDIO bus + * + * The function checks the PHY addresses flagged in phy_mask and returns a + * phy_device pointer if it detects a PHY. + * This function should only be called if just one PHY is expected to be present + * in the set of addresses flagged in phy_mask.  If multiple PHYs are present, + * it is undefined which of these PHYs is returned. + * + * @bus:	MII/MDIO bus to scan + * @phy_mask:	bitmap of PYH addresses to scan + * @interface:	type of MAC-PHY interface + * @return pointer to phy_device if a PHY is found, or NULL otherwise + */  struct phy_device *phy_find_by_mask(struct mii_dev *bus, unsigned phy_mask,  		phy_interface_t interface); +  #ifdef CONFIG_DM_ETH + +/** + * phy_connect_dev() - Associates the given pair of PHY and Ethernet devices + * @phydev:	PHY device + * @dev:	Ethernet device + */  void phy_connect_dev(struct phy_device *phydev, struct udevice *dev); + +/** + * phy_connect() - Creates a PHY device for the Ethernet interface + * + * Creates a PHY device for the PHY at the given address, if one doesn't exist + * already, and associates it with the Ethernet device. + * The function may be called with addr <= 0, in this case addr value is ignored + * and the bus is scanned to detect a PHY.  Scanning should only be used if only + * one PHY is expected to be present on the MDIO bus, otherwise it is undefined + * which PHY is returned. + * + * @bus:	MII/MDIO bus that hosts the PHY + * @addr:	PHY address on MDIO bus + * @dev:	Ethernet device to associate to the PHY + * @interface:	type of MAC-PHY interface + * @return pointer to phy_device if a PHY is found, or NULL otherwise + */  struct phy_device *phy_connect(struct mii_dev *bus, int addr,  				struct udevice *dev,  				phy_interface_t interface); +  static inline ofnode phy_get_ofnode(struct phy_device *phydev)  {  	if (ofnode_valid(phydev->node)) @@ -263,10 +319,34 @@ static inline ofnode phy_get_ofnode(struct phy_device *phydev)  		return dev_ofnode(phydev->dev);  }  #else + +/** + * phy_connect_dev() - Associates the given pair of PHY and Ethernet devices + * @phydev:	PHY device + * @dev:	Ethernet device + */  void phy_connect_dev(struct phy_device *phydev, struct eth_device *dev); + +/** + * phy_connect() - Creates a PHY device for the Ethernet interface + * + * Creates a PHY device for the PHY at the given address, if one doesn't exist + * already, and associates it with the Ethernet device. + * The function may be called with addr <= 0, in this case addr value is ignored + * and the bus is scanned to detect a PHY.  Scanning should only be used if only + * one PHY is expected to be present on the MDIO bus, otherwise it is undefined + * which PHY is returned. + * + * @bus:	MII/MDIO bus that hosts the PHY + * @addr:	PHY address on MDIO bus + * @dev:	Ethernet device to associate to the PHY + * @interface:	type of MAC-PHY interface + * @return pointer to phy_device if a PHY is found, or NULL otherwise + */  struct phy_device *phy_connect(struct mii_dev *bus, int addr,  				struct eth_device *dev,  				phy_interface_t interface); +  static inline ofnode phy_get_ofnode(struct phy_device *phydev)  {  	return ofnode_null(); diff --git a/net/Makefile b/net/Makefile index 6251ff39915..826544f05f9 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 00000000000..e425207d6e4 --- /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 <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), +}; diff --git a/test/dm/Makefile b/test/dm/Makefile index 6a36cc0a328..7b4dd6e12e6 100644 --- a/test/dm/Makefile +++ b/test/dm/Makefile @@ -63,4 +63,5 @@ obj-$(CONFIG_TEE) += tee.o  obj-$(CONFIG_VIRTIO_SANDBOX) += virtio.o  obj-$(CONFIG_DMA) += dma.o  obj-$(CONFIG_DM_MDIO) += mdio.o +obj-$(CONFIG_DM_MDIO_MUX) += mdio_mux.o  endif diff --git a/test/dm/mdio.c b/test/dm/mdio.c index 5b66255f7d4..dc229aed6d0 100644 --- a/test/dm/mdio.c +++ b/test/dm/mdio.c @@ -13,6 +13,9 @@  /* macros copied over from mdio_sandbox.c */  #define SANDBOX_PHY_ADDR	5 +#define SANDBOX_PHY_REG_CNT	2 + +/* test using 1st register, 0 */  #define SANDBOX_PHY_REG		0  #define TEST_REG_VALUE		0xabcd diff --git a/test/dm/mdio_mux.c b/test/dm/mdio_mux.c new file mode 100644 index 00000000000..f962e09dbc2 --- /dev/null +++ b/test/dm/mdio_mux.c @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2019 + * Alex Marginean, NXP + */ + +#include <common.h> +#include <dm.h> +#include <dm/test.h> +#include <misc.h> +#include <test/ut.h> +#include <miiphy.h> + +/* macros copied over from mdio_sandbox.c */ +#define SANDBOX_PHY_ADDR	5 +#define SANDBOX_PHY_REG_CNT	2 + +#define TEST_REG_VALUE		0xabcd + +static int dm_test_mdio_mux(struct unit_test_state *uts) +{ +	struct uclass *uc; +	struct udevice *mux; +	struct udevice *mdio_ch0, *mdio_ch1, *mdio; +	struct mdio_ops *ops, *ops_parent; +	struct mdio_mux_ops *mmops; +	u16 reg; + +	ut_assertok(uclass_get(UCLASS_MDIO_MUX, &uc)); + +	ut_assertok(uclass_get_device_by_name(UCLASS_MDIO_MUX, "mdio-mux-test", +					      &mux)); + +	ut_assertok(uclass_get_device_by_name(UCLASS_MDIO, "mdio-ch-test@0", +					      &mdio_ch0)); +	ut_assertok(uclass_get_device_by_name(UCLASS_MDIO, "mdio-ch-test@1", +					      &mdio_ch1)); + +	ut_assertok(uclass_get_device_by_name(UCLASS_MDIO, "mdio-test", &mdio)); + +	ops = mdio_get_ops(mdio_ch0); +	ut_assertnonnull(ops); +	ut_assertnonnull(ops->read); +	ut_assertnonnull(ops->write); + +	mmops = mdio_mux_get_ops(mux); +	ut_assertnonnull(mmops); +	ut_assertnonnull(mmops->select); + +	ops_parent = mdio_get_ops(mdio); +	ut_assertnonnull(ops); +	ut_assertnonnull(ops->read); + +	/* +	 * mux driver sets last register on the emulated PHY whenever a group +	 * is selected to the selection #.  Just reading that register from +	 * either of the child buses should return the id of the child bus +	 */ +	reg = ops->read(mdio_ch0, SANDBOX_PHY_ADDR, MDIO_DEVAD_NONE, +			SANDBOX_PHY_REG_CNT - 1); +	ut_asserteq(reg, 0); + +	reg = ops->read(mdio_ch1, SANDBOX_PHY_ADDR, MDIO_DEVAD_NONE, +			SANDBOX_PHY_REG_CNT - 1); +	ut_asserteq(reg, 1); + +	mmops->select(mux, MDIO_MUX_SELECT_NONE, 5); +	reg = ops_parent->read(mdio, SANDBOX_PHY_ADDR, MDIO_DEVAD_NONE, +			SANDBOX_PHY_REG_CNT - 1); +	ut_asserteq(reg, 5); + +	mmops->deselect(mux, 5); +	reg = ops_parent->read(mdio, SANDBOX_PHY_ADDR, MDIO_DEVAD_NONE, +			SANDBOX_PHY_REG_CNT - 1); +	ut_asserteq(reg, (u16)MDIO_MUX_SELECT_NONE); + +	return 0; +} + +DM_TEST(dm_test_mdio_mux, DM_TESTF_SCAN_FDT); | 
