// SPDX-License-Identifier: GPL-2.0-only

#include <linux/fwnode.h>
#include <linux/fwnode_mdio.h>
#include <linux/mfd/syscon.h>
#include <linux/of.h>
#include <linux/phy.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/types.h>

#define RTMDIO_MAX_PHY				57
#define RTMDIO_MAX_SMI_BUS			4
#define RTMDIO_PAGE_SELECT			0x1f

#define RTMDIO_PHY_AQR113C_A			0x31c31c12
#define RTMDIO_PHY_AQR113C_B			0x31c31c13
#define RTMDIO_PHY_AQR813			0x31c31cb2
#define RTMDIO_PHY_RTL8218D			0x001cc983
#define RTMDIO_PHY_RTL8218E			0x001cc984
#define RTMDIO_PHY_RTL8221B_VB_CG		0x001cc849
#define RTMDIO_PHY_RTL8221B_VM_CG		0x001cc84a
#define RTMDIO_PHY_RTL8224			0x001ccad0
#define RTMDIO_PHY_RTL8226			0x001cc838
#define RTMDIO_PHY_RTL8261			0x001ccaf3

#define RTMDIO_PHY_MAC_1G			3
#define RTMDIO_PHY_MAC_2G_PLUS			1

#define RTMDIO_PHY_POLL_MMD(dev, reg, bit)	((bit << 21) | (dev << 16) | reg)

/* MDIO bus registers/fields */
#define RTMDIO_RUN				BIT(0)

#define RTMDIO_838X_PHY_PATCH_DONE		BIT(15)
#define RTMDIO_838X_SMI_GLB_CTRL		(0xa100)
#define RTMDIO_838X_SMI_ACCESS_PHY_CTRL_0	(0xa1b8)
#define RTMDIO_838X_SMI_ACCESS_PHY_CTRL_1	(0xa1bc)
#define   RTMDIO_838X_CMD_FAIL			0
#define   RTMDIO_838X_CMD_READ_C22		0
#define   RTMDIO_838X_CMD_READ_C45		BIT(1)
#define   RTMDIO_838X_CMD_WRITE_C22		BIT(2)
#define   RTMDIO_838X_CMD_WRITE_C45		BIT(1) | BIT(2)
#define   RTMDIO_838X_CMD_MASK			GENMASK(2, 0)
#define RTMDIO_838X_SMI_ACCESS_PHY_CTRL_2	(0xa1c0)
#define RTMDIO_838X_SMI_ACCESS_PHY_CTRL_3	(0xa1c4)
#define RTMDIO_838X_SMI_POLL_CTRL		(0xa17c)
#define RTMDIO_838X_SMI_PORT0_5_ADDR_CTRL	(0xa1c8)

#define RTMDIO_839X_PHYREG_CTRL			(0x03E0)
#define RTMDIO_839X_PHYREG_PORT_CTRL		(0x03E4)
#define RTMDIO_839X_PHYREG_ACCESS_CTRL		(0x03DC)
#define   RTMDIO_839X_CMD_FAIL			BIT(1)
#define   RTMDIO_839X_CMD_READ_C22		0
#define   RTMDIO_839X_CMD_READ_C45		BIT(2)
#define   RTMDIO_839X_CMD_WRITE_C22		BIT(3)
#define   RTMDIO_839X_CMD_WRITE_C45		BIT(2) | BIT(3)
#define   RTMDIO_839X_CMD_MASK			GENMASK(3, 0)
#define RTMDIO_839X_PHYREG_DATA_CTRL		(0x03F0)
#define RTMDIO_839X_PHYREG_MMD_CTRL		(0x03F4)
#define RTMDIO_839X_SMI_PORT_POLLING_CTRL	(0x03fc)
#define RTMDIO_839X_SMI_GLB_CTRL		(0x03f8)

#define RTMDIO_930X_SMI_GLB_CTRL		(0xCA00)
#define RTMDIO_930X_SMI_ACCESS_PHY_CTRL_0	(0xCB70)
#define RTMDIO_930X_SMI_ACCESS_PHY_CTRL_1	(0xCB74)
#define   RTMDIO_930X_CMD_FAIL			BIT(25)
#define   RTMDIO_930X_CMD_READ_C22		0
#define   RTMDIO_930X_CMD_READ_C45		BIT(1)
#define   RTMDIO_930X_CMD_WRITE_C22		BIT(2)
#define   RTMDIO_930X_CMD_WRITE_C45		BIT(1) | BIT(2)
#define   RTMDIO_930X_CMD_MASK			GENMASK(2, 0) | BIT(25)
#define RTMDIO_930X_SMI_ACCESS_PHY_CTRL_2	(0xCB78)
#define RTMDIO_930X_SMI_ACCESS_PHY_CTRL_3	(0xCB7C)
#define RTMDIO_930X_SMI_PORT0_15_POLLING_SEL	(0xCA08)
#define RTMDIO_930X_SMI_PORT16_27_POLLING_SEL	(0xCA0C)
#define RTMDIO_930X_SMI_MAC_TYPE_CTRL		(0xCA04)
#define RTMDIO_930X_SMI_PRVTE_POLLING_CTRL	(0xCA10)
#define RTMDIO_930X_SMI_10G_POLLING_REG0_CFG	(0xCBB4)
#define RTMDIO_930X_SMI_10G_POLLING_REG9_CFG	(0xCBB8)
#define RTMDIO_930X_SMI_10G_POLLING_REG10_CFG	(0xCBBC)
#define RTMDIO_930X_SMI_PORT0_5_ADDR_CTRL	(0xCB80)

#define RTMDIO_931X_SMI_PORT_POLLING_CTRL	(0x0CCC)
#define RTMDIO_931X_SMI_INDRT_ACCESS_BC_CTRL	(0x0C14)
#define RTMDIO_931X_SMI_GLB_CTRL0		(0x0CC0)
#define RTMDIO_931X_SMI_GLB_CTRL1		(0x0CBC)
#define RTMDIO_931X_SMI_INDRT_ACCESS_CTRL_0	(0x0C00)
#define   RTMDIO_931X_CMD_FAIL			BIT(1)
#define   RTMDIO_931X_CMD_READ_C22		0
#define   RTMDIO_931X_CMD_READ_C45		BIT(3)
#define   RTMDIO_931X_CMD_WRITE_C22		BIT(4)
#define   RTMDIO_931X_CMD_WRITE_C45		BIT(3) | BIT(4)
#define   RTMDIO_931X_CMD_MASK			GENMASK(4, 0)
#define RTMDIO_931X_SMI_INDRT_ACCESS_CTRL_1	(0x0C04)
#define RTMDIO_931X_SMI_INDRT_ACCESS_CTRL_2	(0x0C08)
#define RTMDIO_931X_SMI_INDRT_ACCESS_CTRL_3	(0x0C10)
#define RTMDIO_931X_SMI_INDRT_ACCESS_MMD_CTRL	(0x0C18)
#define RTMDIO_931X_SMI_PHY_ABLTY_GET_SEL	(0x0CAC)
#define   RTMDIO_931X_SMY_PHY_ABLTY_MDIO	0x0
#define   RTMDIO_931X_SMI_PHY_ABLTY_SDS		0x2
#define RTMDIO_931X_SMI_PORT_POLLING_SEL	(0x0C9C)
#define RTMDIO_931X_SMI_PORT_ADDR_CTRL		(0x0C74)
#define RTMDIO_931X_SMI_10GPHY_POLLING_SEL0	(0x0CF0)
#define RTMDIO_931X_SMI_10GPHY_POLLING_SEL1	(0x0CF4)
#define RTMDIO_931X_SMI_10GPHY_POLLING_SEL2	(0x0CF8)
#define RTMDIO_931X_SMI_10GPHY_POLLING_SEL3	(0x0CFC)
#define RTMDIO_931X_SMI_10GPHY_POLLING_SEL4	(0x0D00)

#define for_each_phy(ctrl, addr) \
	for (int addr = 0; addr < (ctrl)->cfg->num_phys; addr++) \
		if ((ctrl)->smi_bus[addr] >= 0)

/*
 * On all Realtek switch platforms the hardware periodically reads the link status of all
 * PHYs. This is to some degree programmable, so that one can tell the hardware to read
 * specific C22 registers from specific pages, or C45 registers, to determine the current
 * link speed, duplex, flow-control, ...
 *
 * This happens without any need for the driver to do anything at runtime, completely
 * invisible and in a parallel hardware thread, independent of the CPU running Linux.
 * All one needs to do is to set it up once. Having the MAC link settings automatically
 * follow the PHY link status also happens to be the only way to control MAC port status
 * in a meaningful way, or at least it's the only way we fully understand, as this is
 * what every vendor firmware is doing.
 *
 * The hardware PHY polling unit doesn't care about bus locking, it just assumes that all
 * paged PHY operations are also done via the same hardware unit offering this PHY access
 * abstractions.
 *
 * Additionally at least the RTL838x and RTL839x devices are known to have a so called
 * raw mode. Using the special MAX_PAGE-1 with the MDIO controller found in Realtek
 * SoCs allows to access the PHY in raw mode, ie. bypassing the cache and paging engine
 * of the MDIO controller. E.g. for RTL838x this is 0xfff.
 *
 * On the other hand Realtek PHYs usually make use of select register 0x1f to switch
 * pages. There is no problem to issue separate page and access bus calls to the PHYs
 * when they are not attached to an Realtek SoC. The paradigm should be to keep the PHY
 * implementation bus independent.
 *
 * To bring all this together we need a tricky bus design that intercepts select page
 * calls but lets raw page accesses through. And especially knows how to handle raw
 * accesses to the select register. Additionally we need the possibility to write to
 * all 8 ports of the PHY individually.
 *
 * While the C45 clause stuff is pretty standard the legacy functions basically track
 * the accesses and the state of the bus with the attributes page[], raw[] and portaddr
 * of the bus_priv structure. The page selection works as follows:
 *
 * phy_write(phydev, RTMDIO_PAGE_SELECT, 12)	: store internal page 12 in driver
 * phy_write(phydev, 7, 33)			: write page=12, reg=7, val=33
 *
 * or simply
 *
 * phy_write_paged(phydev, 12, 7, 33)		: write page=12, reg=7, val=33
 *
 * Any Realtek PHY that will be connected to this bus must simply provide the standard
 * page functions:
 *
 * define RTL821X_PAGE_SELECT 0x1f
 *
 * static int rtl821x_read_page(struct phy_device *phydev)
 * {
 *   return __phy_read(phydev, RTL821X_PAGE_SELECT);
 * }
 *
 * static int rtl821x_write_page(struct phy_device *phydev, int page)
 * {
 *   return __phy_write(phydev, RTL821X_PAGE_SELECT, page);
 * }
 *
 * In case there are non Realtek PHYs attached to the bus the logic might need to be
 * reimplemented. For now it should be sufficient.
 */


struct rtmdio_ctrl {
	struct regmap *map;
	const struct rtmdio_config *cfg;
	int page[RTMDIO_MAX_PHY];
	bool raw[RTMDIO_MAX_PHY];
	int smi_bus[RTMDIO_MAX_PHY];
	int smi_addr[RTMDIO_MAX_PHY];
	bool smi_bus_isc45[RTMDIO_MAX_SMI_BUS];
};

struct rtmdio_config {
	int num_phys;
	int raw_page;
	int bus_map_base;
	int port_map_base;
	int (*read_mmd_phy)(struct mii_bus *bus, u32 addr, u32 devnum, u32 regnum, u32 *val);
	int (*read_phy)(struct mii_bus *bus, u32 addr, u32 page, u32 reg, u32 *val);
	int (*reset)(struct mii_bus *bus);
	void (*setup_polling)(struct mii_bus *bus);
	int (*write_mmd_phy)(struct mii_bus *bus, u32 addr, u32 devnum, u32 regnum, u32 val);
	int (*write_phy)(struct mii_bus *bus, u32 addr, u32 page, u32 reg, u32 val);
};

struct rtmdio_phy_info {
	int mac_type;
	bool has_giga_lite;
	bool has_res_reg;
	bool force_res;
	unsigned int poll_duplex;
	unsigned int poll_adv_1000;
	unsigned int poll_lpa_1000;
};

static int rtmdio_run_cmd(struct mii_bus *bus, int cmd, int mask, int regnum, int fail)
{
	struct rtmdio_ctrl *ctrl = bus->priv;
	int ret, val;

	ret = regmap_update_bits(ctrl->map, regnum, mask, cmd | RTMDIO_RUN);
	ret = regmap_read_poll_timeout(ctrl->map, regnum, val, !(val & RTMDIO_RUN), 20, 500000);
	if (ret)
		WARN_ONCE(1, "mdio bus access timed out\n");
	else if (val & fail) {
		WARN_ONCE(1, "mdio bus access failed\n");
		ret = -EIO;
	}

	return ret;
}

static int rtmdio_838x_run_cmd(struct mii_bus *bus, int cmd)
{
	return rtmdio_run_cmd(bus, cmd, RTMDIO_838X_CMD_MASK,
			      RTMDIO_838X_SMI_ACCESS_PHY_CTRL_1, RTMDIO_838X_CMD_FAIL);
}

static int rtmdio_838x_read_phy(struct mii_bus *bus, u32 addr, u32 page, u32 reg, u32 *val)
{
	struct rtmdio_ctrl *ctrl = bus->priv;
	u32 park_page = 31;
	int err;

	regmap_write(ctrl->map, RTMDIO_838X_SMI_ACCESS_PHY_CTRL_0, BIT(addr));
	regmap_write(ctrl->map, RTMDIO_838X_SMI_ACCESS_PHY_CTRL_2, addr << 16);
	regmap_write(ctrl->map, RTMDIO_838X_SMI_ACCESS_PHY_CTRL_1,
		     reg << 20 | park_page << 15 | page << 3);
	err = rtmdio_838x_run_cmd(bus, RTMDIO_838X_CMD_READ_C22);
	if (!err)
		err = regmap_read(ctrl->map, RTMDIO_838X_SMI_ACCESS_PHY_CTRL_2, val);
	if (!err)
		*val &= GENMASK(15, 0);

	return err;
}

static int rtmdio_838x_write_phy(struct mii_bus *bus, u32 addr, u32 page, u32 reg, u32 val)
{
	struct rtmdio_ctrl *ctrl = bus->priv;
	u32 park_page = 31;

	regmap_write(ctrl->map, RTMDIO_838X_SMI_ACCESS_PHY_CTRL_0, BIT(addr));
	regmap_write(ctrl->map, RTMDIO_838X_SMI_ACCESS_PHY_CTRL_2, val << 16);
	regmap_write(ctrl->map, RTMDIO_838X_SMI_ACCESS_PHY_CTRL_1,
		     reg << 20 | park_page << 15 | page << 3);

	return rtmdio_838x_run_cmd(bus, RTMDIO_838X_CMD_WRITE_C22);
}

static int rtmdio_838x_read_mmd_phy(struct mii_bus *bus, u32 addr, u32 devnum, u32 regnum, u32 *val)
{
	struct rtmdio_ctrl *ctrl = bus->priv;
	int err;

	regmap_write(ctrl->map, RTMDIO_838X_SMI_ACCESS_PHY_CTRL_0, BIT(addr));
	regmap_write(ctrl->map, RTMDIO_838X_SMI_ACCESS_PHY_CTRL_2, addr << 16);
	regmap_write(ctrl->map, RTMDIO_838X_SMI_ACCESS_PHY_CTRL_3, devnum << 16 | regnum);
	err = rtmdio_838x_run_cmd(bus, RTMDIO_838X_CMD_READ_C45);
	if (!err)
		err = regmap_read(ctrl->map, RTMDIO_838X_SMI_ACCESS_PHY_CTRL_2, val);
	if (!err)
		*val &= GENMASK(15, 0);

	return err;
}

static int rtmdio_838x_write_mmd_phy(struct mii_bus *bus, u32 addr, u32 devnum, u32 regnum, u32 val)
{
	struct rtmdio_ctrl *ctrl = bus->priv;

	regmap_write(ctrl->map, RTMDIO_838X_SMI_ACCESS_PHY_CTRL_0, BIT(addr));
	regmap_write(ctrl->map, RTMDIO_838X_SMI_ACCESS_PHY_CTRL_2, val << 16);
	regmap_write(ctrl->map, RTMDIO_838X_SMI_ACCESS_PHY_CTRL_3, devnum << 16 | regnum);

	return rtmdio_838x_run_cmd(bus, RTMDIO_838X_CMD_WRITE_C45);
}

static int rtmdio_839x_run_cmd(struct mii_bus *bus, int cmd)
{
	return rtmdio_run_cmd(bus, cmd, RTMDIO_839X_CMD_MASK,
			      RTMDIO_839X_PHYREG_ACCESS_CTRL, RTMDIO_839X_CMD_FAIL);
}

static int rtmdio_839x_read_phy(struct mii_bus *bus, u32 addr, u32 page, u32 reg, u32 *val)
{
	struct rtmdio_ctrl *ctrl = bus->priv;
	int err;

	regmap_write(ctrl->map, RTMDIO_839X_PHYREG_CTRL, 0x1ff);
	regmap_write(ctrl->map, RTMDIO_839X_PHYREG_DATA_CTRL, addr << 16);
	regmap_write(ctrl->map, RTMDIO_839X_PHYREG_ACCESS_CTRL,
		     reg << 5 | page << 10 | ((page == 0x1fff) ? 0x1f : 0) << 23);
	err = rtmdio_839x_run_cmd(bus, RTMDIO_839X_CMD_READ_C22);
	if (!err)
		err = regmap_read(ctrl->map, RTMDIO_839X_PHYREG_DATA_CTRL, val);
	if (!err)
		*val &= GENMASK(15, 0);

	return err;
}

static int rtmdio_839x_write_phy(struct mii_bus *bus, u32 addr, u32 page, u32 reg, u32 val)
{
	struct rtmdio_ctrl *ctrl = bus->priv;

	regmap_write(ctrl->map, RTMDIO_839X_PHYREG_CTRL, 0x1ff);
	regmap_write(ctrl->map, RTMDIO_839X_PHYREG_DATA_CTRL, val << 16);
	regmap_write(ctrl->map, RTMDIO_839X_PHYREG_PORT_CTRL, BIT_ULL(addr));
	regmap_write(ctrl->map, RTMDIO_839X_PHYREG_PORT_CTRL + 4, BIT_ULL(addr) >> 32);
	regmap_write(ctrl->map, RTMDIO_839X_PHYREG_ACCESS_CTRL,
		     reg << 5 | page << 10 | ((page == 0x1fff) ? 0x1f : 0) << 23);

	return rtmdio_839x_run_cmd(bus, RTMDIO_839X_CMD_WRITE_C22);
}

static int rtmdio_839x_read_mmd_phy(struct mii_bus *bus, u32 addr, u32 devnum, u32 regnum, u32 *val)
{
	struct rtmdio_ctrl *ctrl = bus->priv;
	int err;

	regmap_write(ctrl->map, RTMDIO_839X_PHYREG_DATA_CTRL,  addr << 16);
	regmap_write(ctrl->map, RTMDIO_839X_PHYREG_MMD_CTRL, (devnum << 16) | (regnum & 0xffff));
	err = rtmdio_839x_run_cmd(bus, RTMDIO_839X_CMD_READ_C45);
	if (!err)
		err = regmap_read(ctrl->map, RTMDIO_839X_PHYREG_DATA_CTRL, val);
	if (!err)
		*val &= GENMASK(15, 0);

	return err;
}

static int rtmdio_839x_write_mmd_phy(struct mii_bus *bus, u32 addr, u32 devnum, u32 regnum, u32 val)
{
	struct rtmdio_ctrl *ctrl = bus->priv;

	regmap_write(ctrl->map, RTMDIO_839X_PHYREG_PORT_CTRL, BIT_ULL(addr));
	regmap_write(ctrl->map, RTMDIO_839X_PHYREG_PORT_CTRL + 4, BIT_ULL(addr) >> 32);
	regmap_write(ctrl->map, RTMDIO_839X_PHYREG_DATA_CTRL, val << 16);
	regmap_write(ctrl->map, RTMDIO_839X_PHYREG_MMD_CTRL, (devnum << 16) | (regnum & 0xffff));

	return rtmdio_839x_run_cmd(bus, RTMDIO_839X_CMD_WRITE_C45);
}

static int rtmdio_930x_run_cmd(struct mii_bus *bus, int cmd)
{
	return rtmdio_run_cmd(bus, cmd, RTMDIO_930X_CMD_MASK,
			      RTMDIO_930X_SMI_ACCESS_PHY_CTRL_1, RTMDIO_930X_CMD_FAIL);
}

static int rtmdio_930x_write_phy(struct mii_bus *bus, u32 addr, u32 page, u32 reg, u32 val)
{
	struct rtmdio_ctrl *ctrl = bus->priv;
	u32 park_page = 31;

	regmap_write(ctrl->map, RTMDIO_930X_SMI_ACCESS_PHY_CTRL_0, BIT(addr));
	regmap_write(ctrl->map, RTMDIO_930X_SMI_ACCESS_PHY_CTRL_2, val << 16);
	regmap_write(ctrl->map, RTMDIO_930X_SMI_ACCESS_PHY_CTRL_1,
		     reg << 20 | page << 3 | park_page << 15);

	return rtmdio_930x_run_cmd(bus, RTMDIO_930X_CMD_WRITE_C22);
}

static int rtmdio_930x_read_phy(struct mii_bus *bus, u32 addr, u32 page, u32 reg, u32 *val)
{
	struct rtmdio_ctrl *ctrl = bus->priv;
	u32 park_page = 31;
	int err;

	regmap_write(ctrl->map, RTMDIO_930X_SMI_ACCESS_PHY_CTRL_2, addr << 16);
	regmap_write(ctrl->map, RTMDIO_930X_SMI_ACCESS_PHY_CTRL_1,
		     reg << 20 | page << 3 | park_page << 15);
	err = rtmdio_930x_run_cmd(bus, RTMDIO_930X_CMD_READ_C22);
	if (!err)
		err = regmap_read(ctrl->map, RTMDIO_930X_SMI_ACCESS_PHY_CTRL_2, val);
	if (!err)
		*val &= GENMASK(15, 0);

	return err;
}

static int rtmdio_930x_write_mmd_phy(struct mii_bus *bus, u32 addr, u32 devnum, u32 regnum, u32 val)
{
	struct rtmdio_ctrl *ctrl = bus->priv;

	regmap_write(ctrl->map, RTMDIO_930X_SMI_ACCESS_PHY_CTRL_0, BIT(addr));
	regmap_write(ctrl->map, RTMDIO_930X_SMI_ACCESS_PHY_CTRL_2, val << 16);
	regmap_write(ctrl->map, RTMDIO_930X_SMI_ACCESS_PHY_CTRL_3, (devnum << 16) | (regnum & 0xffff));

	return rtmdio_930x_run_cmd(bus, RTMDIO_930X_CMD_WRITE_C45);
}

static int rtmdio_930x_read_mmd_phy(struct mii_bus *bus, u32 addr, u32 devnum, u32 regnum, u32 *val)
{
	struct rtmdio_ctrl *ctrl = bus->priv;
	int err ;

	regmap_write(ctrl->map, RTMDIO_930X_SMI_ACCESS_PHY_CTRL_2, addr << 16);
	regmap_write(ctrl->map, RTMDIO_930X_SMI_ACCESS_PHY_CTRL_3, (devnum << 16) | (regnum & 0xffff));
	err = rtmdio_930x_run_cmd(bus, RTMDIO_930X_CMD_READ_C45);
	if (!err)
		err = regmap_read(ctrl->map, RTMDIO_930X_SMI_ACCESS_PHY_CTRL_2, val);
	if (!err)
		*val &= GENMASK(15, 0);

	return err;
}

static int rtmdio_931x_run_cmd(struct mii_bus *bus, int cmd)
{
	return rtmdio_run_cmd(bus, cmd, RTMDIO_931X_CMD_MASK,
			      RTMDIO_931X_SMI_INDRT_ACCESS_CTRL_0, RTMDIO_931X_CMD_FAIL);
}

static int rtmdio_931x_write_phy(struct mii_bus *bus, u32 addr, u32 page, u32 reg, u32 val)
{
	struct rtmdio_ctrl *ctrl = bus->priv;
	u64 mask = BIT_ULL(addr);

	regmap_write(ctrl->map, RTMDIO_931X_SMI_INDRT_ACCESS_CTRL_2, (u32)mask);
	regmap_write(ctrl->map, RTMDIO_931X_SMI_INDRT_ACCESS_CTRL_2 + 4, (u32)(mask >> 32));
	regmap_write(ctrl->map, RTMDIO_931X_SMI_INDRT_ACCESS_CTRL_3, val);
	regmap_write(ctrl->map, RTMDIO_931X_SMI_INDRT_ACCESS_CTRL_0, reg << 6 | page << 11);
	regmap_write(ctrl->map, RTMDIO_931X_SMI_INDRT_ACCESS_CTRL_1, 0x1ff);

	return rtmdio_931x_run_cmd(bus, RTMDIO_931X_CMD_WRITE_C22);
}

static int rtmdio_931x_read_phy(struct mii_bus *bus, u32 addr, u32 page, u32 reg, u32 *val)
{
	struct rtmdio_ctrl *ctrl = bus->priv;
	int err;

	regmap_write(ctrl->map, RTMDIO_931X_SMI_INDRT_ACCESS_BC_CTRL, addr << 5);
	regmap_write(ctrl->map, RTMDIO_931X_SMI_INDRT_ACCESS_CTRL_0, reg << 6 | page << 11);
	err = rtmdio_931x_run_cmd(bus, RTMDIO_931X_CMD_READ_C22);
	if (!err)
		err = regmap_read(ctrl->map, RTMDIO_931X_SMI_INDRT_ACCESS_CTRL_3, val);
	if (!err)
		*val >>= 16;

	return err;
}

static int rtmdio_931x_read_mmd_phy(struct mii_bus *bus, u32 addr, u32 devnum, u32 regnum, u32 *val)
{
	struct rtmdio_ctrl *ctrl = bus->priv;
	int err;

	regmap_write(ctrl->map, RTMDIO_931X_SMI_INDRT_ACCESS_BC_CTRL, addr << 5);
	regmap_write(ctrl->map, RTMDIO_931X_SMI_INDRT_ACCESS_MMD_CTRL, (devnum << 16) | (regnum & 0xffff));
	err = rtmdio_931x_run_cmd(bus, RTMDIO_931X_CMD_READ_C45);
	if (!err)
		err = regmap_read(ctrl->map, RTMDIO_931X_SMI_INDRT_ACCESS_CTRL_3, val);
	if (!err)
		*val >>= 16;

	return err;
}

static int rtmdio_931x_write_mmd_phy(struct mii_bus *bus, u32 addr, u32 devnum, u32 regnum, u32 val)
{
	struct rtmdio_ctrl *ctrl = bus->priv;
	u64 mask = BIT_ULL(addr);

	regmap_write(ctrl->map, RTMDIO_931X_SMI_INDRT_ACCESS_CTRL_2, (u32)mask);
	regmap_write(ctrl->map, RTMDIO_931X_SMI_INDRT_ACCESS_CTRL_2 + 4, (u32)(mask >> 32));
	regmap_write(ctrl->map, RTMDIO_931X_SMI_INDRT_ACCESS_CTRL_3, val);
	regmap_write(ctrl->map, RTMDIO_931X_SMI_INDRT_ACCESS_MMD_CTRL, (devnum << 16) | (regnum & 0xffff));

	return rtmdio_931x_run_cmd(bus, RTMDIO_931X_CMD_WRITE_C45);
}

static int rtmdio_read_c45(struct mii_bus *bus, int addr, int devnum, int regnum)
{
	struct rtmdio_ctrl *ctrl = bus->priv;
	int err, val;

	if (addr >= ctrl->cfg->num_phys)
		return -ENODEV;

	err = (*ctrl->cfg->read_mmd_phy)(bus, addr, devnum, regnum, &val);
	pr_debug("rd_MMD(adr=%d, dev=%d, reg=%d) = %d, err = %d\n",
		 addr, devnum, regnum, val, err);
	return err ? err : val;
}

static int rtmdio_read(struct mii_bus *bus, int addr, int regnum)
{
	struct rtmdio_ctrl *ctrl = bus->priv;
	int err, val;

	if (addr >= ctrl->cfg->num_phys)
		return -ENODEV;

	if (regnum == RTMDIO_PAGE_SELECT && ctrl->page[addr] != ctrl->cfg->raw_page)
		return ctrl->page[addr];

	ctrl->raw[addr] = (ctrl->page[addr] == ctrl->cfg->raw_page);

	err = (*ctrl->cfg->read_phy)(bus, addr, ctrl->page[addr], regnum, &val);
	pr_debug("rd_PHY(adr=%d, pag=%d, reg=%d) = %d, err = %d\n",
		 addr, ctrl->page[addr], regnum, val, err);
	return err ? err : val;
}

static int rtmdio_write_c45(struct mii_bus *bus, int addr, int devnum, int regnum, u16 val)
{
	struct rtmdio_ctrl *ctrl = bus->priv;
	int err;

	if (addr >= ctrl->cfg->num_phys)
		return -ENODEV;

	err = (*ctrl->cfg->write_mmd_phy)(bus, addr, devnum, regnum, val);
	pr_debug("wr_MMD(adr=%d, dev=%d, reg=%d, val=%d) err = %d\n",
		 addr, devnum, regnum, val, err);
	return err;
}

static int rtmdio_write(struct mii_bus *bus, int addr, int regnum, u16 val)
{
	struct rtmdio_ctrl *ctrl = bus->priv;
	int err, page;

	if (addr >= ctrl->cfg->num_phys)
		return -ENODEV;

	page = ctrl->page[addr];

	if (regnum == RTMDIO_PAGE_SELECT)
		ctrl->page[addr] = val;

	if (!ctrl->raw[addr] && (regnum != RTMDIO_PAGE_SELECT || page == ctrl->cfg->raw_page)) {
		ctrl->raw[addr] = (page == ctrl->cfg->raw_page);

		err = (*ctrl->cfg->write_phy)(bus, addr, page, regnum, val);
		pr_debug("wr_PHY(adr=%d, pag=%d, reg=%d, val=%d) err = %d\n",
			 addr, page, regnum, val, err);
		return err;
	}

	ctrl->raw[addr] = false;
	return 0;
}

static void rtmdio_setup_smi_topology(struct mii_bus *bus)
{
	struct rtmdio_ctrl *ctrl = bus->priv;
	u32 reg, mask, val;

	for_each_phy(ctrl, addr) {
		if (ctrl->cfg->bus_map_base) {
			reg = (addr / 16) * 4;
			mask = 0x3 << ((addr % 16) * 2);
			val = ctrl->smi_bus[addr] << ((addr % 16) * 2);
			regmap_update_bits(ctrl->map, ctrl->cfg->bus_map_base + reg, mask, val);
		}

		if (ctrl->cfg->port_map_base) {
			reg = (addr / 6) * 4;
			mask = 0x1f << ((addr % 6) * 5);
			val = ctrl->smi_addr[addr] << ((addr % 6) * 5);
			regmap_update_bits(ctrl->map, ctrl->cfg->port_map_base + reg, mask, val);
		}
	}
}

static u32 rtmdio_get_phy_id(struct phy_device *phydev)
{
	if (!phydev)
		return 0;

	if (phydev->is_c45) {
		for (int devad = 0; devad < MDIO_MMD_NUM; devad++) {
			u32 phyid = phydev->c45_ids.device_ids[devad];

			if (phyid && phyid != 0xffffffff)
				return phyid;
		}
	}

	return phydev->phy_id;
}

static int rtmdio_get_phy_info(struct mii_bus *bus, int addr, struct rtmdio_phy_info *phyinfo)
{
	struct phy_device *phydev = mdiobus_get_phy(bus, addr);
	u32 phyid = rtmdio_get_phy_id(phydev);
	int ret = 0;

	/*
	 * Depending on the attached PHY the polling mechanism must be fine tuned. Basically
	 * this boils down to which registers must be read and if there are any special
	 * features.
	 */
	memset(phyinfo, 0, sizeof(*phyinfo));

	switch(phyid) {
	case RTMDIO_PHY_AQR113C_A:
	case RTMDIO_PHY_AQR113C_B:
	case RTMDIO_PHY_AQR813:
		phyinfo->mac_type = RTMDIO_PHY_MAC_2G_PLUS;
		phyinfo->poll_duplex = RTMDIO_PHY_POLL_MMD(1, 0x0000, 8);
		phyinfo->poll_adv_1000 = RTMDIO_PHY_POLL_MMD(7, 0xc400, 15);
		phyinfo->poll_lpa_1000 = RTMDIO_PHY_POLL_MMD(7, 0xe820, 15);
		break;
	case RTMDIO_PHY_RTL8218D:
	case RTMDIO_PHY_RTL8218E:
		phyinfo->mac_type = RTMDIO_PHY_MAC_1G;
		phyinfo->has_giga_lite = true;
		break;
	case RTMDIO_PHY_RTL8226:
	case RTMDIO_PHY_RTL8221B_VB_CG:
	case RTMDIO_PHY_RTL8221B_VM_CG:
	case RTMDIO_PHY_RTL8224:
		phyinfo->mac_type = RTMDIO_PHY_MAC_2G_PLUS;
		phyinfo->has_giga_lite = true;
		phyinfo->poll_duplex = RTMDIO_PHY_POLL_MMD(31, 0xa400, 8);
		phyinfo->poll_adv_1000 = RTMDIO_PHY_POLL_MMD(31, 0xa412, 9);
		phyinfo->poll_lpa_1000 = RTMDIO_PHY_POLL_MMD(31, 0xa414, 11);
		break;
	case RTMDIO_PHY_RTL8261:
		phyinfo->mac_type = RTMDIO_PHY_MAC_2G_PLUS;
		phyinfo->has_giga_lite = true;
		phyinfo->has_res_reg = true;
		break;
	default:
		pr_warn("skip polling setup for unknown PHY %08x on address %d\n", phyid, addr);
		ret = -EINVAL;
		break;
	}

	return ret;
}

static int rtmdio_838x_reset(struct mii_bus *bus)
{
	struct rtmdio_ctrl *ctrl = bus->priv;

	/*
	 * PHY_PATCH_DONE enables phy control via SoC. This is required for phy access,
	 * including patching. Must always be set before the phys are probed.
	 */
	regmap_update_bits(ctrl->map, RTMDIO_838X_SMI_GLB_CTRL,
			   RTMDIO_838X_PHY_PATCH_DONE, RTMDIO_838X_PHY_PATCH_DONE);

	return 0;
}

static void rtmdio_838x_setup_polling(struct mii_bus *bus)
{
	struct rtmdio_ctrl *ctrl = bus->priv;
	int combo_phy;

	/* Disable MAC polling for PHY config. It will be activated later in the DSA driver */
	regmap_write(ctrl->map, RTMDIO_838X_SMI_POLL_CTRL, 0);

	/*
	 * Control bits EX_PHY_MAN_xxx have an important effect on the detection of the media
	 * status (fibre/copper) of a PHY. Once activated, register MAC_LINK_MEDIA_STS can
	 * give the real media status (0=copper, 1=fibre). For now assume that if address 24 is
	 * PHY driven, it must be a combo PHY and media detection is needed.
	 */
	combo_phy = ctrl->smi_bus[24] < 0 ? 0 : BIT(7);
	regmap_update_bits(ctrl->map, RTMDIO_838X_SMI_GLB_CTRL, BIT(7), combo_phy);
}

static int rtmdio_839x_reset(struct mii_bus *bus)
{
	struct rtmdio_ctrl *ctrl = bus->priv;

	return 0;

	pr_debug("%s called\n", __func__);
	/* BUG: The following does not work, but should! */
	/* Disable MAC polling the PHY so that we can start configuration */
	regmap_write(ctrl->map, RTMDIO_839X_SMI_PORT_POLLING_CTRL, 0);
	regmap_write(ctrl->map, RTMDIO_839X_SMI_PORT_POLLING_CTRL + 4, 0);
	/* Disable PHY polling via SoC */
	regmap_update_bits(ctrl->map, RTMDIO_839X_SMI_GLB_CTRL, BIT(7), 0);

	/* Probably should reset all PHYs here... */
	return 0;
}

static int rtmdio_930x_reset(struct mii_bus *bus)
{
	struct rtmdio_ctrl *ctrl = bus->priv;
	unsigned int mask, val;

	/* Define C22/C45 bus feature set */
	for (int addr = 0; addr < RTMDIO_MAX_SMI_BUS; addr++) {
		mask = BIT(16 + addr);
		val = ctrl->smi_bus_isc45[addr] ? mask : 0;
		regmap_update_bits(ctrl->map, RTMDIO_930X_SMI_GLB_CTRL, mask, val);
	}

	return 0;
}

static void rtmdio_930x_setup_polling(struct mii_bus *bus)
{
	struct rtmdio_ctrl *ctrl = bus->priv;
	struct rtmdio_phy_info phyinfo;
	unsigned int mask, val;

	/* set everthing to "SerDes driven" */
	regmap_write(ctrl->map, RTMDIO_930X_SMI_MAC_TYPE_CTRL, 0);

	/* Define PHY specific polling parameters */
	for_each_phy(ctrl, addr) {
		if (rtmdio_get_phy_info(bus, addr, &phyinfo))
			continue;

		/* set to "PHY driven" */
		mask = addr > 23 ? 0x7 << ((addr - 24) * 3 + 12): 0x3 << ((addr / 4) * 2);
		val = phyinfo.mac_type << (ffs(mask) - 1);
		regmap_update_bits(ctrl->map, RTMDIO_930X_SMI_MAC_TYPE_CTRL, mask, val);

		/* polling via standard or resolution register */
		mask = BIT(20 + ctrl->smi_bus[addr]);
		val = phyinfo.has_res_reg ? mask : 0;
		regmap_update_bits(ctrl->map, RTMDIO_930X_SMI_GLB_CTRL, mask, val);

		/* proprietary Realtek 1G/2.5 lite polling */
		mask = BIT(addr);
		val = phyinfo.has_giga_lite ? mask : 0;
		regmap_update_bits(ctrl->map, RTMDIO_930X_SMI_PRVTE_POLLING_CTRL, mask, val);

		/* special duplex/advertisement polling registers */
		if (phyinfo.poll_duplex || phyinfo.poll_adv_1000 || phyinfo.poll_lpa_1000) {
			regmap_write(ctrl->map, RTMDIO_930X_SMI_10G_POLLING_REG0_CFG, phyinfo.poll_duplex);
			regmap_write(ctrl->map, RTMDIO_930X_SMI_10G_POLLING_REG9_CFG, phyinfo.poll_adv_1000);
			regmap_write(ctrl->map, RTMDIO_930X_SMI_10G_POLLING_REG10_CFG, phyinfo.poll_lpa_1000);
		}
	}

	regmap_read(ctrl->map, RTMDIO_930X_SMI_GLB_CTRL, &val);
	pr_debug("%s: RTMDIO_930X_SMI_GLB_CTRL %08x\n", __func__, val);
	regmap_read(ctrl->map, RTMDIO_930X_SMI_PORT0_15_POLLING_SEL, &val);
	pr_debug("%s: RTMDIO_930X_SMI_PORT0_15_POLLING_SEL %08x\n", __func__, val);
	regmap_read(ctrl->map, RTMDIO_930X_SMI_PORT16_27_POLLING_SEL, &val);
	pr_debug("%s: RTMDIO_930X_SMI_PORT16_27_POLLING_SEL %08x\n", __func__, val);
	regmap_read(ctrl->map, RTMDIO_930X_SMI_MAC_TYPE_CTRL, &val);
	pr_debug("%s: RTMDIO_930X_SMI_MAC_TYPE_CTRL %08x\n", __func__, val);
	regmap_read(ctrl->map, RTMDIO_930X_SMI_10G_POLLING_REG0_CFG, &val);
	pr_debug("%s: RTMDIO_930X_SMI_10G_POLLING_REG0_CFG %08x\n", __func__, val);
	regmap_read(ctrl->map, RTMDIO_930X_SMI_10G_POLLING_REG9_CFG, &val);
	pr_debug("%s: RTMDIO_930X_SMI_10G_POLLING_REG9_CFG %08x\n", __func__, val);
	regmap_read(ctrl->map, RTMDIO_930X_SMI_10G_POLLING_REG10_CFG, &val);
	pr_debug("%s: RTMDIO_930X_SMI_10G_POLLING_REG10_CFG %08x\n", __func__, val);
	regmap_read(ctrl->map, RTMDIO_930X_SMI_PRVTE_POLLING_CTRL, &val);
	pr_debug("%s: RTMDIO_930X_SMI_PRVTE_POLLING_CTRL %08x\n", __func__, val);
}

static int rtmdio_931x_reset(struct mii_bus *bus)
{
	struct rtmdio_ctrl *ctrl = bus->priv;
	u32 c45_mask = 0;

	/* Disable polling for configuration purposes */
	regmap_write(ctrl->map, RTMDIO_931X_SMI_PORT_POLLING_CTRL, 0);
	regmap_write(ctrl->map, RTMDIO_931X_SMI_PORT_POLLING_CTRL + 4, 0);
	msleep(100);

	/* Define C22/C45 bus feature set */
	for (int i = 0; i < RTMDIO_MAX_SMI_BUS; i++) {
		if (ctrl->smi_bus_isc45[i])
			c45_mask |= 0x2 << (i * 2);  /* Std. C45, non-standard is 0x3 */
	}
	regmap_update_bits(ctrl->map, RTMDIO_931X_SMI_GLB_CTRL1, GENMASK(7, 0), c45_mask);

	return 0;
}

static void rtmdio_931x_setup_polling(struct mii_bus *bus)
{
	struct rtmdio_ctrl *ctrl = bus->priv;
	struct rtmdio_phy_info phyinfo;
	u32 val;

	/* set everything to "SerDes driven" */
	for (int reg = 0; reg < 4; reg++)
		regmap_write(ctrl->map, RTMDIO_931X_SMI_PHY_ABLTY_GET_SEL + reg * 4,
			     RTMDIO_931X_SMI_PHY_ABLTY_SDS * 0x55555555U);

	/* Define PHY specific polling parameters */
	for_each_phy(ctrl, addr) {
		int smi = ctrl->smi_bus[addr];
		unsigned int mask, val;
		
		if (rtmdio_get_phy_info(bus, addr, &phyinfo))
			continue;

		/* set to "PHY driven" */
		mask = GENMASK(1, 0) << ((addr % 16) * 2);
		val = RTMDIO_931X_SMY_PHY_ABLTY_MDIO << (ffs(mask) - 1);
		regmap_update_bits(ctrl->map, RTMDIO_931X_SMI_PHY_ABLTY_GET_SEL + (addr / 16) * 4,
				   mask, val);
		mask = val = 0;

		/* PRVTE0 polling */
		mask |= BIT(20 + smi);
		if (phyinfo.has_res_reg)
			val |= BIT(20 + smi);

		/* PRVTE1 polling */
		mask |= BIT(24 + smi);
		if (phyinfo.force_res)
			val |= BIT(24 + smi);

		regmap_update_bits(ctrl->map, RTMDIO_931X_SMI_GLB_CTRL0, mask, val);

		/* polling std. or proprietary format (bit 0 of SMI_SETX_FMT_SEL) */
		mask = BIT(smi * 2);
		val = phyinfo.force_res ? mask : 0;
		regmap_update_bits(ctrl->map, RTMDIO_931X_SMI_GLB_CTRL1, mask, val);

		/* special polling registers */
		if (phyinfo.poll_duplex || phyinfo.poll_adv_1000 || phyinfo.poll_lpa_1000) {
			regmap_write(ctrl->map, RTMDIO_931X_SMI_10GPHY_POLLING_SEL2, phyinfo.poll_duplex);
			regmap_write(ctrl->map, RTMDIO_931X_SMI_10GPHY_POLLING_SEL3, phyinfo.poll_adv_1000);
			regmap_write(ctrl->map, RTMDIO_931X_SMI_10GPHY_POLLING_SEL4, phyinfo.poll_lpa_1000);
		}
	}

	regmap_read(ctrl->map, RTMDIO_931X_SMI_GLB_CTRL0, &val);
	pr_debug("%s: RTMDIO_931X_SMI_GLB_CTRL0 %08x\n", __func__, val);
	regmap_read(ctrl->map, RTMDIO_931X_SMI_GLB_CTRL1, &val);
	pr_debug("%s: RTMDIO_931X_SMI_GLB_CTRL1 %08x\n", __func__, val);
	regmap_read(ctrl->map, RTMDIO_931X_SMI_PORT_POLLING_SEL, &val);
	pr_debug("%s: RTMDIO_931X_SMI_PORT_POLLING_SEL_0_15 %08x\n", __func__, val);
	regmap_read(ctrl->map, RTMDIO_931X_SMI_PORT_POLLING_SEL + 4, &val);
	pr_debug("%s: RTMDIO_931X_SMI_PORT_POLLING_SEL_16_27 %08x\n", __func__, val);
	regmap_read(ctrl->map, RTMDIO_931X_SMI_PORT_POLLING_SEL + 8, &val);
	pr_debug("%s: RTMDIO_931X_SMI_PORT_POLLING_SEL_28_43 %08x\n", __func__, val);
	regmap_read(ctrl->map, RTMDIO_931X_SMI_PORT_POLLING_SEL + 12, &val);
	pr_debug("%s: RTMDIO_931X_SMI_PORT_POLLING_SEL_44_55 %08x\n", __func__, val);
	regmap_read(ctrl->map, RTMDIO_931X_SMI_10GPHY_POLLING_SEL0, &val);
	pr_debug("%s: RTMDIO_931X_SMI_10GPHY_POLLING_SEL0 %08x\n", __func__, val);
	regmap_read(ctrl->map, RTMDIO_931X_SMI_10GPHY_POLLING_SEL1, &val);
	pr_debug("%s: RTMDIO_931X_SMI_10GPHY_POLLING_SEL1 %08x\n", __func__, val);
	regmap_read(ctrl->map, RTMDIO_931X_SMI_10GPHY_POLLING_SEL2, &val);
	pr_debug("%s: RTMDIO_931X_SMI_10GPHY_POLLING_SEL2 %08x\n", __func__, val);
	regmap_read(ctrl->map, RTMDIO_931X_SMI_10GPHY_POLLING_SEL3, &val);
	pr_debug("%s: RTMDIO_931X_SMI_10GPHY_POLLING_SEL3 %08x\n", __func__, val);
	regmap_read(ctrl->map, RTMDIO_931X_SMI_10GPHY_POLLING_SEL4, &val);
	pr_debug("%s: RTMDIO_931X_SMI_10GPHY_POLLING_SEL4 %08x\n", __func__, val);
}

static int rtmdio_reset(struct mii_bus *bus)
{
	struct rtmdio_ctrl *ctrl = bus->priv;

	return ctrl->cfg->reset(bus);
}

static int rtmdio_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct device_node *np, *dn[RTMDIO_MAX_PHY];
	struct rtmdio_ctrl *ctrl;
	struct mii_bus *bus;
	int ret, addr;

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

	ctrl = bus->priv;
	ctrl->cfg = (const struct rtmdio_config *)device_get_match_data(dev);
	ctrl->map = syscon_node_to_regmap(pdev->dev.of_node->parent);
	if (IS_ERR(ctrl->map))
		return PTR_ERR(ctrl->map);

	for (addr = 0; addr < RTMDIO_MAX_PHY; addr++)
		ctrl->smi_bus[addr] = -1;

	for_each_node_by_name(np, "ethernet-phy") {
		if (of_property_read_u32(np, "reg", &addr))
			continue;

		if (addr < 0 || addr >= ctrl->cfg->num_phys) {
			dev_err(dev, "illegal address number %d\n", addr);
			of_node_put(np);
			return -EINVAL;
		}

		of_property_read_u32(np->parent, "reg", &ctrl->smi_bus[addr]);
		if (of_property_read_u32(np, "realtek,smi-address", &ctrl->smi_addr[addr]))
			ctrl->smi_addr[addr] = addr;

		if (ctrl->smi_bus[addr] < 0 || ctrl->smi_bus[addr] >= RTMDIO_MAX_SMI_BUS) {
			dev_err(dev, "illegal SMI bus number %d\n", ctrl->smi_bus[addr]);
			of_node_put(np);
			return -EINVAL;
		}

		if (of_device_is_compatible(np, "ethernet-phy-ieee802.3-c45"))
			ctrl->smi_bus_isc45[ctrl->smi_bus[addr]] = true;

		dn[addr] = of_node_get(np);
	}

	bus->name = "Realtek MDIO bus";
	bus->reset = rtmdio_reset;
	bus->read = rtmdio_read;
	bus->write = rtmdio_write;
	bus->read_c45 = rtmdio_read_c45;
	bus->write_c45 = rtmdio_write_c45;
	bus->parent = dev;
	bus->phy_mask = ~0;
	snprintf(bus->id, MII_BUS_ID_SIZE, "realtek-mdio");

	device_set_node(&bus->dev, of_fwnode_handle(dev->of_node));

	rtmdio_setup_smi_topology(bus);
	ret = devm_mdiobus_register(dev, bus);
	if (ret)
		return ret;

	for_each_phy(ctrl, addr) {
		ret = fwnode_mdiobus_register_phy(bus, of_fwnode_handle(dn[addr]), addr);
		of_node_put(dn[addr]);
		if (ret)
			return ret;
	}

	if (ctrl->cfg->setup_polling)
		ctrl->cfg->setup_polling(bus);

	return 0;
}

static const struct rtmdio_config rtmdio_838x_cfg = {
	.num_phys	= 28,
	.raw_page	= 4095,
	.port_map_base	= RTMDIO_838X_SMI_PORT0_5_ADDR_CTRL,
	.read_mmd_phy	= rtmdio_838x_read_mmd_phy,
	.read_phy	= rtmdio_838x_read_phy,
	.reset		= rtmdio_838x_reset,
	.setup_polling	= rtmdio_838x_setup_polling,
	.write_mmd_phy	= rtmdio_838x_write_mmd_phy,
	.write_phy	= rtmdio_838x_write_phy,
};

static const struct rtmdio_config rtmdio_839x_cfg = {
	.num_phys	= 52,
	.raw_page	= 8191,
	.read_mmd_phy	= rtmdio_839x_read_mmd_phy,
	.read_phy	= rtmdio_839x_read_phy,
	.reset		= rtmdio_839x_reset,
	.write_mmd_phy	= rtmdio_839x_write_mmd_phy,
	.write_phy	= rtmdio_839x_write_phy,
};

static const struct rtmdio_config rtmdio_930x_cfg = {
	.num_phys	= 28,
	.raw_page	= 4095,
	.bus_map_base	= RTMDIO_930X_SMI_PORT0_15_POLLING_SEL,
	.port_map_base	= RTMDIO_930X_SMI_PORT0_5_ADDR_CTRL,
	.read_mmd_phy	= rtmdio_930x_read_mmd_phy,
	.read_phy	= rtmdio_930x_read_phy,
	.reset		= rtmdio_930x_reset,
	.setup_polling	= rtmdio_930x_setup_polling,
	.write_mmd_phy	= rtmdio_930x_write_mmd_phy,
	.write_phy	= rtmdio_930x_write_phy,
};

static const struct rtmdio_config rtmdio_931x_cfg = {
	.num_phys	= 56,
	.raw_page	= 8191,
	.bus_map_base	= RTMDIO_931X_SMI_PORT_POLLING_SEL,
	.port_map_base	= RTMDIO_931X_SMI_PORT_ADDR_CTRL,
	.read_mmd_phy	= rtmdio_931x_read_mmd_phy,
	.read_phy	= rtmdio_931x_read_phy,
	.reset		= rtmdio_931x_reset,
	.setup_polling	= rtmdio_931x_setup_polling,
	.write_mmd_phy	= rtmdio_931x_write_mmd_phy,
	.write_phy	= rtmdio_931x_write_phy,
};

static const struct of_device_id rtmdio_ids[] = {
	{
		.compatible = "realtek,rtl8380-mdio",
		.data = &rtmdio_838x_cfg,
	},
	{
		.compatible = "realtek,rtl8392-mdio",
		.data = &rtmdio_839x_cfg,
	},
	{
		.compatible = "realtek,rtl9301-mdio",
		.data = &rtmdio_930x_cfg,
	},
	{
		.compatible = "realtek,rtl9311-mdio",
		.data = &rtmdio_931x_cfg,
	},
	{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, rtmdio_ids);

static struct platform_driver rtmdio_driver = {
	.probe = rtmdio_probe,
	.driver = {
		.name = "mdio-rtl-otto",
		.of_match_table = rtmdio_ids,
	},
};

module_platform_driver(rtmdio_driver);

MODULE_DESCRIPTION("RTL83xx/RTL93xx MDIO driver");
MODULE_LICENSE("GPL");
