root/drivers/net/mdio/mdio-realtek-rtl9300.c
// 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");