Contributors: 4
Author Tokens Token Proportion Commits Commit Proportion
Markus Stockhausen 2197 60.19% 23 88.46%
Chris Packham 1392 38.14% 1 3.85%
Manuel Stocker 60 1.64% 1 3.85%
Felix Gu 1 0.03% 1 3.85%
Total 3650 26


// SPDX-License-Identifier: GPL-2.0-only
/*
 * Realtek switches of the Otto series (RTL838x, RTL839x, RTL930x and RTL931x SoCs) have multiple
 * integrated MDIO controllers. This driver targets the ethernet MDIO controller. It serves only
 * 1G/2.5G/10G ethernet PHYs attached to up to 4 individual buses.
 *
 * The controller is programmed through MMIO. The MDIO communication is abstracted by the hardware
 * and uses the switch port number for its addressing. For this to work, mapping registers need to
 * be setup in advance. With that the controller translates each port based I/O operation into the
 * physical bus and address. This gives the following end-to-end communication
 *
 *     +----------+       +----------+           +----------+       +----------+
 *     |  phydev  |  ...  |  phydev  |           |  phydev  |  ...  |  phydev  |
 *     +----------+       +----------+           +----------+       +----------+
 *              |                  |               |                  |
 *   mii_bus 0  +------------------+               +------------------+  mii_bus 1
 *                                 |               |
 *           +-----------------------------------------------------+
 *           |  MDIO driver                                        |
 *           |                      translate bus/address -> port  |
 *           +-----------------------------------------------------+
 *                                        |                             Software
 *                             - - - - - - - - - - - - - - - - - - - - - - - - -
 *                                        |                             Hardware
 *           +-----------------------------------------------------+
 *           | MDIO controller                                     |
 *           |                      translate port -> bus/address  |
 *           +-----------------------------------------------------+
 *                                 |               |
 *       bus 0  +------------------+               +------------------+  bus 1
 *              |                  |               |                  |
 *     +----------+       +----------+           +----------+       +----------+
 *     | PHY 0/1  |  ...  | PHY 0/31 |           | PHY 1/1  |  ...  | PHY 1/31 |
 *     +----------+       +----------+           +----------+       +----------+
 *
 * The driver works out the mapping based on the MDIO bus described in device tree and phandles on
 * the ethernet-ports property.
 */

#include <linux/bitfield.h>
#include <linux/bitmap.h>
#include <linux/bits.h>
#include <linux/find.h>
#include <linux/mdio.h>
#include <linux/mfd/syscon.h>
#include <linux/mutex.h>
#include <linux/of_mdio.h>
#include <linux/phy.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/regmap.h>

#define RTL9300_NUM_BUSES			4
#define RTL9300_NUM_PAGES			4096
#define RTL9300_NUM_PORTS			28
#define RTL9300_SMI_GLB_CTRL			0xca00
#define   RTL9300_GLB_CTRL_INTF_SEL(intf)	BIT(16 + (intf))
#define RTL9300_SMI_PORT0_15_POLLING_SEL	0xca08
#define RTL9300_SMI_ACCESS_PHY_CTRL_0		0xcb70
#define RTL9300_SMI_ACCESS_PHY_CTRL_1		0xcb74
#define   RTL9300_PHY_CTRL_REG_ADDR		GENMASK(24, 20)
#define   RTL9300_PHY_CTRL_PARK_PAGE		GENMASK(19, 15)
#define   RTL9300_PHY_CTRL_MAIN_PAGE		GENMASK(14, 3)
#define   RTL9300_PHY_CTRL_WRITE		BIT(2)
#define   RTL9300_PHY_CTRL_READ			0
#define   RTL9300_PHY_CTRL_TYPE_C45		BIT(1)
#define   RTL9300_PHY_CTRL_TYPE_C22		0
#define   RTL9300_PHY_CTRL_FAIL			BIT(25)
#define RTL9300_SMI_ACCESS_PHY_CTRL_2		0xcb78
#define   RTL9300_PHY_CTRL_INDATA		GENMASK(31, 16)
#define   RTL9300_PHY_CTRL_DATA			GENMASK(15, 0)
#define RTL9300_SMI_ACCESS_PHY_CTRL_3		0xcb7c
#define RTL9300_SMI_PORT0_5_ADDR_CTRL		0xcb80

#define RTL9310_NUM_BUSES			4
#define RTL9310_NUM_PAGES			8192
#define RTL9310_NUM_PORTS			56
#define RTL9310_SMI_GLB_CTRL1			0x0cbc
#define   RTL9310_SMI_GLB_FMT_SEL_C45(intf)	BIT((intf) * 2 + 1)
#define RTL9310_SMI_INDRT_ACCESS_CTRL_0		0x0c00
#define   RTL9310_PHY_CTRL_REG_ADDR		GENMASK(10, 6)
#define   RTL9310_PHY_CTRL_MAIN_PAGE		GENMASK(23, 11)
#define   RTL9310_PHY_CTRL_READ			0
#define   RTL9310_PHY_CTRL_WRITE		BIT(4)
#define   RTL9310_PHY_CTRL_TYPE_C45		BIT(3)
#define   RTL9310_PHY_CTRL_TYPE_C22		0
#define   RTL9310_PHY_CTRL_FAIL			BIT(1)
#define RTL9310_SMI_INDRT_ACCESS_BC_PHYID_CTRL	0x0c14
#define   RTL9310_BC_PORT_ID			GENMASK(10, 5)
#define RTL9310_SMI_INDRT_ACCESS_CTRL_1		0x0c04
#define RTL9310_SMI_INDRT_ACCESS_CTRL_2_LOW	0x0c08
#define RTL9310_SMI_INDRT_ACCESS_CTRL_2_HIGH	0x0c0c
#define RTL9310_SMI_INDRT_ACCESS_CTRL_3		0x0c10 /* I/O fields flipped */
#define   RTL9310_PHY_CTRL_DATA			GENMASK(31, 16)
#define   RTL9310_PHY_CTRL_INDATA		GENMASK(15, 0)
#define RTL9310_SMI_INDRT_ACCESS_MMD_CTRL	0x0c18
#define RTL9310_SMI_PORT_ADDR_CTRL		0x0c74
#define RTL9310_SMI_PORT_POLLING_SEL		0x0c9c

#define PHY_CTRL_CMD				BIT(0)
#define PHY_CTRL_MMD_DEVAD			GENMASK(20, 16)
#define PHY_CTRL_MMD_REG			GENMASK(15, 0)

#define MAP_ADDRS_PER_REG			6
#define MAP_BITS_PER_ADDR			5
#define MAP_BITS_PER_BUS			2
#define MAP_BUSES_PER_REG			16
#define MAX_PORTS				56
#define MAX_SMI_BUSSES				4
#define RAW_PAGE(priv)				((priv)->info->num_pages - 1)


struct otto_emdio_cmd_regs {
	u32 c22_data;
	u32 c45_data;
	u32 io_data;
	u32 port_mask_low;
	/* additional registers for high port count models RTL839x/RTL931x */
	u32 port_mask_high;
	u32 broadcast;
	u32 ext_page;
};

struct otto_emdio_priv {
	const struct otto_emdio_info *info;
	struct regmap *regmap;
	struct mutex lock; /* protect HW access */
	DECLARE_BITMAP(valid_ports, MAX_PORTS);
	u8 smi_bus[MAX_PORTS];
	u8 smi_addr[MAX_PORTS];
	bool smi_bus_is_c45[MAX_SMI_BUSSES];
	struct mii_bus *bus[MAX_SMI_BUSSES];
};

struct otto_emdio_info {
	u32 addr_map_base;
	u32 bus_map_base;
	u32 cmd_fail;
	u32 cmd_read;
	u32 cmd_write;
	struct otto_emdio_cmd_regs cmd_regs;
	u8 num_buses;
	u8 num_ports;
	u16 num_pages;
	int (*setup_controller)(struct otto_emdio_priv *priv);
	int (*read_c22)(struct mii_bus *bus, int port, int regnum, u32 *value);
	int (*read_c45)(struct mii_bus *bus, int port, int dev_addr, int regnum, u32 *value);
	int (*write_c22)(struct mii_bus *bus, int port, int regnum, u16 value);
	int (*write_c45)(struct mii_bus *bus, int port, int dev_addr, int regnum, u16 value);
};

struct otto_emdio_chan {
	struct otto_emdio_priv *priv;
	u8 mdio_bus;
};

static int otto_emdio_phy_to_port(struct mii_bus *bus, int phy_id)
{
	struct otto_emdio_chan *chan = bus->priv;
	struct otto_emdio_priv *priv;
	int i;

	priv = chan->priv;

	for_each_set_bit(i, priv->valid_ports, priv->info->num_ports)
		if (priv->smi_bus[i] == chan->mdio_bus &&
		    priv->smi_addr[i] == phy_id)
			return i;

	return -ENOENT;
}

static struct otto_emdio_priv *otto_emdio_bus_to_priv(struct mii_bus *bus)
{
	struct otto_emdio_chan *chan = bus->priv;

	return chan->priv;
}

static int otto_emdio_run_cmd(struct mii_bus *bus, u32 cmd,
			      struct otto_emdio_cmd_regs *cmd_data)
{
	struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus);
	const struct otto_emdio_info *info = priv->info;
	u32 cmdstate;
	int ret;

	/* Defensive pre check just in case something goes horrible wrong */
	ret = regmap_read_poll_timeout(priv->regmap, info->cmd_regs.c22_data,
				       cmdstate, !(cmdstate & PHY_CTRL_CMD), 10, 1000);
	if (ret)
		return ret;

	/* Fill all registers. Hardware will read only the needed bits depending on command */
	if (info->cmd_regs.port_mask_high) {
		/* Fill extra registers for high port count models */
		ret = regmap_write(priv->regmap, info->cmd_regs.broadcast, cmd_data->broadcast);
		if (ret)
			return ret;

		ret = regmap_write(priv->regmap, info->cmd_regs.ext_page, cmd_data->ext_page);
		if (ret)
			return ret;

		ret = regmap_write(priv->regmap,
				   info->cmd_regs.port_mask_high, cmd_data->port_mask_high);
		if (ret)
			return ret;
	}

	ret = regmap_write(priv->regmap, info->cmd_regs.port_mask_low, cmd_data->port_mask_low);
	if (ret)
		return ret;

	ret = regmap_write(priv->regmap, info->cmd_regs.io_data, cmd_data->io_data);
	if (ret)
		return ret;

	ret = regmap_write(priv->regmap, info->cmd_regs.c45_data, cmd_data->c45_data);
	if (ret)
		return ret;

	/* C22 data and command bits share the same register. */
	ret = regmap_write(priv->regmap, info->cmd_regs.c22_data,
			   cmd_data->c22_data | cmd | PHY_CTRL_CMD);
	if (ret)
		return ret;

	ret = regmap_read_poll_timeout(priv->regmap, info->cmd_regs.c22_data,
				       cmdstate, !(cmdstate & PHY_CTRL_CMD), 10, 1000);
	if (ret)
		return ret;

	return cmdstate & info->cmd_fail ? -ENXIO : 0;
}

static int otto_emdio_read_cmd(struct mii_bus *bus, u32 cmd,
			       struct otto_emdio_cmd_regs *cmd_data, u32 mask, u32 *value)
{
	struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus);
	int ret;

	lockdep_assert_held(&priv->lock);
	ret = otto_emdio_run_cmd(bus, cmd | priv->info->cmd_read, cmd_data);
	if (ret)
		return ret;

	ret = regmap_read(priv->regmap, priv->info->cmd_regs.io_data, value);
	if (ret)
		return ret;

	*value = field_get(mask, *value);

	return 0;
}

static int otto_emdio_write_cmd(struct mii_bus *bus, u32 cmd,
				struct otto_emdio_cmd_regs *cmd_data)
{
	struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus);

	lockdep_assert_held(&priv->lock);

	return otto_emdio_run_cmd(bus, cmd | priv->info->cmd_write, cmd_data);
}

static int otto_emdio_9300_read_c22(struct mii_bus *bus, int port, int regnum, u32 *value)
{
	struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus);
	struct otto_emdio_cmd_regs cmd_data = {
		.c22_data	= FIELD_PREP(RTL9300_PHY_CTRL_REG_ADDR, regnum) |
				  FIELD_PREP(RTL9300_PHY_CTRL_PARK_PAGE, 0x1f) |
				  FIELD_PREP(RTL9300_PHY_CTRL_MAIN_PAGE, RAW_PAGE(priv)),
		.io_data	= FIELD_PREP(RTL9300_PHY_CTRL_INDATA, port),
	};

	return otto_emdio_read_cmd(bus, RTL9300_PHY_CTRL_TYPE_C22, &cmd_data,
				   RTL9300_PHY_CTRL_DATA, value);
}

static int otto_emdio_9300_write_c22(struct mii_bus *bus, int port, int regnum, u16 value)
{
	struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus);
	struct otto_emdio_cmd_regs cmd_data = {
		.c22_data	= FIELD_PREP(RTL9300_PHY_CTRL_REG_ADDR, regnum) |
				  FIELD_PREP(RTL9300_PHY_CTRL_PARK_PAGE, 0x1f) |
				  FIELD_PREP(RTL9300_PHY_CTRL_MAIN_PAGE, RAW_PAGE(priv)),
		.io_data	= FIELD_PREP(RTL9300_PHY_CTRL_INDATA, value),
		.port_mask_low	= BIT(port),
	};

	return otto_emdio_write_cmd(bus, RTL9300_PHY_CTRL_TYPE_C22, &cmd_data);
}

static int otto_emdio_9300_read_c45(struct mii_bus *bus, int port,
				    int dev_addr, int regnum, u32 *value)
{
	struct otto_emdio_cmd_regs cmd_data = {
		.c45_data	= FIELD_PREP(PHY_CTRL_MMD_DEVAD, dev_addr) |
				  FIELD_PREP(PHY_CTRL_MMD_REG, regnum),
		.io_data	= FIELD_PREP(RTL9300_PHY_CTRL_INDATA, port),
	};

	return otto_emdio_read_cmd(bus, RTL9300_PHY_CTRL_TYPE_C45, &cmd_data,
				   RTL9300_PHY_CTRL_DATA, value);
}

static int otto_emdio_9300_write_c45(struct mii_bus *bus, int port,
				     int dev_addr, int regnum, u16 value)
{
	struct otto_emdio_cmd_regs cmd_data = {
		.c45_data	= FIELD_PREP(PHY_CTRL_MMD_DEVAD, dev_addr) |
				  FIELD_PREP(PHY_CTRL_MMD_REG, regnum),
		.io_data	= FIELD_PREP(RTL9300_PHY_CTRL_INDATA, value),
		.port_mask_low	= BIT(port),
	};

	return otto_emdio_write_cmd(bus, RTL9300_PHY_CTRL_TYPE_C45, &cmd_data);
}

static int otto_emdio_9310_read_c22(struct mii_bus *bus, int port, int regnum, u32 *value)
{
	struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus);
	struct otto_emdio_cmd_regs cmd_data = {
		.broadcast	= FIELD_PREP(RTL9310_BC_PORT_ID, port),
		.c22_data	= FIELD_PREP(RTL9310_PHY_CTRL_REG_ADDR, regnum) |
				  FIELD_PREP(RTL9310_PHY_CTRL_MAIN_PAGE, RAW_PAGE(priv)),
	};

	return otto_emdio_read_cmd(bus, RTL9310_PHY_CTRL_TYPE_C22, &cmd_data,
				   RTL9310_PHY_CTRL_DATA, value);
}

static int otto_emdio_9310_write_c22(struct mii_bus *bus, int port, int regnum, u16 value)
{
	struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus);
	struct otto_emdio_cmd_regs cmd_data = {
		.c22_data	= FIELD_PREP(RTL9310_PHY_CTRL_REG_ADDR, regnum) |
				  FIELD_PREP(RTL9310_PHY_CTRL_MAIN_PAGE, RAW_PAGE(priv)),
		.io_data	= FIELD_PREP(RTL9310_PHY_CTRL_INDATA, value),
		.port_mask_high	= (u32)(BIT_ULL(port) >> 32),
		.port_mask_low	= (u32)(BIT_ULL(port)),
	};

	return otto_emdio_write_cmd(bus, RTL9310_PHY_CTRL_TYPE_C22, &cmd_data);
}

static int otto_emdio_9310_read_c45(struct mii_bus *bus, int port,
				    int dev_addr, int regnum, u32 *value)
{
	struct otto_emdio_cmd_regs cmd_data = {
		.broadcast	= FIELD_PREP(RTL9310_BC_PORT_ID, port),
		.c45_data	= FIELD_PREP(PHY_CTRL_MMD_DEVAD, dev_addr) |
				  FIELD_PREP(PHY_CTRL_MMD_REG, regnum),
	};

	return otto_emdio_read_cmd(bus, RTL9310_PHY_CTRL_TYPE_C45, &cmd_data,
				   RTL9310_PHY_CTRL_DATA, value);
}

static int otto_emdio_9310_write_c45(struct mii_bus *bus, int port,
				     int dev_addr, int regnum, u16 value)
{
	struct otto_emdio_cmd_regs cmd_data = {
		.c45_data	= FIELD_PREP(PHY_CTRL_MMD_DEVAD, dev_addr) |
				  FIELD_PREP(PHY_CTRL_MMD_REG, regnum),
		.io_data	= FIELD_PREP(RTL9310_PHY_CTRL_INDATA, value),
		.port_mask_high	= (u32)(BIT_ULL(port) >> 32),
		.port_mask_low	= (u32)(BIT_ULL(port)),
	};

	return otto_emdio_write_cmd(bus, RTL9310_PHY_CTRL_TYPE_C45, &cmd_data);
}

static int otto_emdio_read_c22(struct mii_bus *bus, int phy_id, int regnum)
{
	struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus);
	int ret, port;
	u32 value;

	port = otto_emdio_phy_to_port(bus, phy_id);
	if (port < 0)
		return port;

	scoped_guard(mutex, &priv->lock)
		ret = priv->info->read_c22(bus, port, regnum, &value);

	return ret ? ret : value;
}

static int otto_emdio_write_c22(struct mii_bus *bus, int phy_id, int regnum, u16 value)
{
	struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus);
	int ret, port;

	port = otto_emdio_phy_to_port(bus, phy_id);
	if (port < 0)
		return port;

	scoped_guard(mutex, &priv->lock)
		ret = priv->info->write_c22(bus, port, regnum, value);

	return ret;
}

static int otto_emdio_read_c45(struct mii_bus *bus, int phy_id, int dev_addr, int regnum)
{
	struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus);
	int ret, port;
	u32 value;

	port = otto_emdio_phy_to_port(bus, phy_id);
	if (port < 0)
		return port;

	scoped_guard(mutex, &priv->lock)
		ret = priv->info->read_c45(bus, port, dev_addr, regnum, &value);

	return ret ? ret : value;
}

static int otto_emdio_write_c45(struct mii_bus *bus, int phy_id,
				int dev_addr, int regnum, u16 value)
{
	struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus);
	int ret, port;

	port = otto_emdio_phy_to_port(bus, phy_id);
	if (port < 0)
		return port;

	scoped_guard(mutex, &priv->lock)
		ret = priv->info->write_c45(bus, port, dev_addr, regnum, value);

	return ret;
}

static int otto_emdio_write_mapping(struct otto_emdio_priv *priv, u32 base, u32 port,
				    u32 vals_per_reg, u32 bits_per_val, u32 value)
{
	u32 shift = (port % vals_per_reg) * bits_per_val;
	u32 reg = base + (port / vals_per_reg) * 4;
	u32 mask = GENMASK(bits_per_val - 1, 0);

	return regmap_update_bits(priv->regmap, reg, mask << shift, value << shift);
}

static int otto_emdio_setup_topology(struct otto_emdio_priv *priv)
{
	const struct otto_emdio_info *info = priv->info;
	u32 port;
	int ret;

	for_each_set_bit(port, priv->valid_ports, info->num_ports) {
		if (info->bus_map_base) {
			ret = otto_emdio_write_mapping(priv, info->bus_map_base, port,
						       MAP_BUSES_PER_REG, MAP_BITS_PER_BUS,
						       priv->smi_bus[port]);
			if (ret)
				return ret;
		}
		if (info->addr_map_base) {
			ret = otto_emdio_write_mapping(priv, info->addr_map_base, port,
						       MAP_ADDRS_PER_REG, MAP_BITS_PER_ADDR,
						       priv->smi_addr[port]);
			if (ret)
				return ret;
		}
	}

	return 0;
}

static int otto_emdio_9300_setup_controller(struct otto_emdio_priv *priv)
{
	u32 glb_ctrl_mask = 0, glb_ctrl_val = 0;
	struct regmap *regmap = priv->regmap;
	int i, err;

	/* Put the interfaces into C45 mode if required */
	glb_ctrl_mask = GENMASK(19, 16);
	for (i = 0; i < priv->info->num_buses; i++)
		if (priv->smi_bus_is_c45[i])
			glb_ctrl_val |= RTL9300_GLB_CTRL_INTF_SEL(i);

	err = regmap_update_bits(regmap, RTL9300_SMI_GLB_CTRL,
				 glb_ctrl_mask, glb_ctrl_val);
	if (err)
		return err;

	return 0;
}

static int otto_emdio_9310_setup_controller(struct otto_emdio_priv *priv)
{
	int i, err;

	/* Put the interfaces into C45 mode if required */
	for (i = 0; i < priv->info->num_buses; i++) {
		err = regmap_assign_bits(priv->regmap, RTL9310_SMI_GLB_CTRL1,
					 RTL9310_SMI_GLB_FMT_SEL_C45(i),
					 priv->smi_bus_is_c45[i]);
		if (err)
			return err;
	}

	return 0;
}

static int otto_emdio_probe_one(struct device *dev, struct otto_emdio_priv *priv,
				 struct fwnode_handle *node)
{
	struct otto_emdio_chan *chan;
	struct mii_bus *bus;
	u32 mdio_bus;
	int err;

	err = fwnode_property_read_u32(node, "reg", &mdio_bus);
	if (err)
		return dev_err_probe(dev, err, "undefined smi bus number\n");

	if (mdio_bus >= priv->info->num_buses)
		return dev_err_probe(dev, -EINVAL,
				     "illegal (dangling) smi bus number %d\n", mdio_bus);

	bus = devm_mdiobus_alloc_size(dev, sizeof(*chan));
	if (!bus)
		return -ENOMEM;

	bus->name = "Realtek Switch MDIO Bus";
	if (priv->smi_bus_is_c45[mdio_bus]) {
		bus->read_c45 = otto_emdio_read_c45;
		bus->write_c45 = otto_emdio_write_c45;
	} else {
		bus->read = otto_emdio_read_c22;
		bus->write = otto_emdio_write_c22;
	}
	bus->parent = dev;
	chan = bus->priv;
	chan->mdio_bus = mdio_bus;
	chan->priv = priv;

	snprintf(bus->id, MII_BUS_ID_SIZE, "%s-%d", dev_name(dev), mdio_bus);

	err = devm_of_mdiobus_register(dev, bus, to_of_node(node));
	if (err)
		return dev_err_probe(dev, err, "cannot register MDIO bus\n");

	return 0;
}

static struct device_node *otto_emdio_get_bus_node(struct device_node *dn)
{
	struct device_node *parent = of_get_parent(dn);
	struct device_node *grandparent;

	if (parent && of_node_name_eq(parent, "ethernet-phy-package")) {
		grandparent = of_get_parent(parent);
		of_node_put(parent);

		return grandparent;
	}

	return parent;
}

/* The mdio-controller is part of a switch block so we parse the sibling
 * ethernet-ports node and build a mapping of the switch port to MDIO bus/addr
 * based on the phy-handle.
 */
static int otto_emdio_map_ports(struct device *dev)
{
	struct device_node *ports_dn, *phy_dn, *bus_dn, *ctrl_dn;
	struct otto_emdio_priv *priv = dev_get_drvdata(dev);
	struct device *parent = dev->parent;
	int addr, err = 0;
	u32 bus, pn;

	ports_dn = of_get_child_by_name(parent->of_node, "ethernet-ports");
	if (!ports_dn)
		return dev_err_probe(dev, -EINVAL, "%pfwP missing ethernet-ports\n",
				     dev_fwnode(parent));

	for_each_available_child_of_node_scoped(ports_dn, port_dn) {
		ctrl_dn = NULL;
		bus_dn = NULL;
		phy_dn = of_parse_phandle(port_dn, "phy-handle", 0);
		/* skip ports without phys */
		if (!phy_dn)
			continue;

		bus_dn = otto_emdio_get_bus_node(phy_dn);
		if (!bus_dn)
			goto put_nodes;

		ctrl_dn = of_get_parent(bus_dn);
		/* only map ports that are connected to this mdio-controller */
		if (ctrl_dn != dev->of_node)
			goto put_nodes;

		err = of_property_read_u32(port_dn, "reg", &pn);
		if (err)
			goto put_nodes;

		if (pn >= priv->info->num_ports) {
			err = dev_err_probe(dev, -EINVAL, "illegal port number %d\n", pn);
			goto put_nodes;
		}

		if (test_bit(pn, priv->valid_ports)) {
			err = dev_err_probe(dev, -EINVAL, "duplicated port number %d\n", pn);
			goto put_nodes;
		}

		err = of_property_read_u32(bus_dn, "reg", &bus);
		if (err)
			goto put_nodes;

		if (bus >= priv->info->num_buses) {
			err = dev_err_probe(dev, -EINVAL, "illegal smi bus number %d\n", bus);
			goto put_nodes;
		}

		addr = of_mdio_parse_addr(dev, phy_dn);
		if (addr < 0) {
			err = addr;
			goto put_nodes;
		}

		/*
		 * The MDIO accesses from the kernel work with the PHY polling unit in the
		 * switch. The PPU either operates in GPHY (i.e. clause 22) or 10GPHY mode
		 * (i.e. clause 45). Select 10GPHY mode if there is at least one PHY that
		 * declares compatible = "ethernet-phy-ieee802.3-c45".
		 */
		if (of_device_is_compatible(phy_dn, "ethernet-phy-ieee802.3-c45"))
			priv->smi_bus_is_c45[bus] = true;

		__set_bit(pn, priv->valid_ports);
		priv->smi_bus[pn] = bus;
		priv->smi_addr[pn] = addr;
put_nodes:
		of_node_put(bus_dn);
		of_node_put(phy_dn);
		of_node_put(ctrl_dn);
		if (err)
			break;
	}

	of_node_put(ports_dn);

	return err;
}

static int otto_emdio_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct otto_emdio_priv *priv;
	int err;

	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
	if (!priv)
		return -ENOMEM;

	err = devm_mutex_init(dev, &priv->lock);
	if (err)
		return err;

	priv->info = device_get_match_data(dev);
	priv->regmap = syscon_node_to_regmap(dev->parent->of_node);
	if (IS_ERR(priv->regmap))
		return PTR_ERR(priv->regmap);

	platform_set_drvdata(pdev, priv);

	err = otto_emdio_map_ports(dev);
	if (err)
		return err;

	err = otto_emdio_setup_topology(priv);
	if (err)
		return err;

	if (priv->info->setup_controller) {
		err = priv->info->setup_controller(priv);
		if (err)
			return dev_err_probe(dev, err, "failed to setup MDIO bus controller\n");
	}

	device_for_each_child_node_scoped(dev, child) {
		err = otto_emdio_probe_one(dev, priv, child);
		if (err)
			return err;
	}

	return 0;
}

static const struct otto_emdio_info otto_emdio_9300_info = {
	.addr_map_base = RTL9300_SMI_PORT0_5_ADDR_CTRL,
	.bus_map_base = RTL9300_SMI_PORT0_15_POLLING_SEL,
	.cmd_fail = RTL9300_PHY_CTRL_FAIL,
	.cmd_read = RTL9300_PHY_CTRL_READ,
	.cmd_write = RTL9300_PHY_CTRL_WRITE,
	.cmd_regs = {
		.c22_data = RTL9300_SMI_ACCESS_PHY_CTRL_1,
		.c45_data = RTL9300_SMI_ACCESS_PHY_CTRL_3,
		.io_data = RTL9300_SMI_ACCESS_PHY_CTRL_2,
		.port_mask_low = RTL9300_SMI_ACCESS_PHY_CTRL_0,
	},
	.num_buses = RTL9300_NUM_BUSES,
	.num_ports = RTL9300_NUM_PORTS,
	.num_pages = RTL9300_NUM_PAGES,
	.setup_controller = otto_emdio_9300_setup_controller,
	.read_c22 = otto_emdio_9300_read_c22,
	.read_c45 = otto_emdio_9300_read_c45,
	.write_c22 = otto_emdio_9300_write_c22,
	.write_c45 = otto_emdio_9300_write_c45,
};

static const struct otto_emdio_info otto_emdio_9310_info = {
	.addr_map_base = RTL9310_SMI_PORT_ADDR_CTRL,
	.bus_map_base = RTL9310_SMI_PORT_POLLING_SEL,
	.cmd_fail = RTL9310_PHY_CTRL_FAIL,
	.cmd_read = RTL9310_PHY_CTRL_READ,
	.cmd_write = RTL9310_PHY_CTRL_WRITE,
	.cmd_regs = {
		.broadcast = RTL9310_SMI_INDRT_ACCESS_BC_PHYID_CTRL,
		.c22_data = RTL9310_SMI_INDRT_ACCESS_CTRL_0,
		.c45_data = RTL9310_SMI_INDRT_ACCESS_MMD_CTRL,
		.ext_page = RTL9310_SMI_INDRT_ACCESS_CTRL_1,
		.io_data = RTL9310_SMI_INDRT_ACCESS_CTRL_3,
		.port_mask_low = RTL9310_SMI_INDRT_ACCESS_CTRL_2_LOW,
		.port_mask_high = RTL9310_SMI_INDRT_ACCESS_CTRL_2_HIGH,
	},
	.num_buses = RTL9310_NUM_BUSES,
	.num_pages = RTL9310_NUM_PAGES,
	.num_ports = RTL9310_NUM_PORTS,
	.setup_controller = otto_emdio_9310_setup_controller,
	.read_c22 = otto_emdio_9310_read_c22,
	.read_c45 = otto_emdio_9310_read_c45,
	.write_c22 = otto_emdio_9310_write_c22,
	.write_c45 = otto_emdio_9310_write_c45,
};

static const struct of_device_id otto_emdio_ids[] = {
	{ .compatible = "realtek,rtl9301-mdio", .data = &otto_emdio_9300_info },
	{ .compatible = "realtek,rtl9311-mdio", .data = &otto_emdio_9310_info },
	{}
};
MODULE_DEVICE_TABLE(of, otto_emdio_ids);

static struct platform_driver otto_emdio_driver = {
	.probe = otto_emdio_probe,
	.driver = {
		.name = "mdio-rtl9300",
		.of_match_table = otto_emdio_ids,
	},
};

module_platform_driver(otto_emdio_driver);

MODULE_DESCRIPTION("RTL9300 MDIO driver");
MODULE_LICENSE("GPL");