1
1
openwrt/target/linux/realtek/files-6.18/drivers/net/mdio/mdio-realtek-otto.c
Markus Stockhausen 76e47b4421 realtek: mdio: convert to scoped_guard()
Upstream netdev does not like big guards. Especially
around debugging functions. Convert to scoped_guard()
and only lock the really needed code parts. This way
all debugging can run outside of the lock.

Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
Link: https://github.com/openwrt/openwrt/pull/23411
Signed-off-by: Robert Marko <robimarko@gmail.com>
2026-05-21 12:10:27 +02:00

1247 lines
36 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* On all Realtek switch platforms the hardware periodically reads the link status of all PHYs.
* The result is automatically written into MAC specific registers that can be consumed by a
* DSA driver. 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 that is fully understood, 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 the 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, i.e.
* bypassing the cache and paging engine of the MDIO controller. E.g. for RTL838x this is 4095.
*
* 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 design should be to keep the PHY code bus independent.
*
* To bring all this together one needs 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.
*
* While C45 clause handling is pretty standard the legacy functions basically track the accesses
* and the state of the bus with the rtmd_port attributes of the control structure. The page
* selection works as follows:
*
* phy_write(phydev, RTMD_PAGE_SELECT, 12) : store internal page 12 in driver
* phy_write(phydev, 7, 33) : write page=12, reg=7, val=33
*
* Any Realtek PHY that is 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 changed.
* For now it should be sufficient.
*/
#include <linux/fwnode.h>
#include <linux/fwnode_mdio.h>
#include <linux/mfd/syscon.h>
#include <linux/of.h>
#include <linux/of_mdio.h>
#include <linux/phy.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/types.h>
#define RTMD_MAX_PORTS 57
#define RTMD_MAX_SMI_BUSES 4
#define RTMD_838X_NUM_BUSES 1
#define RTMD_838X_NUM_PAGES 4096
#define RTMD_838X_NUM_PORTS 28
#define RTMD_839X_NUM_BUSES 2
#define RTMD_839X_NUM_PAGES 8192
#define RTMD_839X_NUM_PORTS 52
#define RTMD_930X_NUM_BUSES 4
#define RTMD_930X_NUM_PAGES 4096
#define RTMD_930X_NUM_PORTS 28
#define RTMD_931X_NUM_BUSES 4
#define RTMD_931X_NUM_PAGES 8192
#define RTMD_931X_NUM_PORTS 56
#define RTMD_PAGE_SELECT 0x1f
#define RTMD_RAW_PAGE(p) ((p) - 1)
#define RTMD_PHY_AQR113C_A 0x31c31c12
#define RTMD_PHY_AQR113C_B 0x31c31c13
#define RTMD_PHY_AQR813 0x31c31cb2
#define RTMD_PHY_RTL8218D 0x001cc983
#define RTMD_PHY_RTL8218E 0x001cc984
#define RTMD_PHY_RTL8221B_VB_CG 0x001cc849
#define RTMD_PHY_RTL8221B_VM_CG 0x001cc84a
#define RTMD_PHY_RTL8224 0x001ccad0
#define RTMD_PHY_RTL8226 0x001cc838
#define RTMD_PHY_RTL8261 0x001ccaf3
#define RTMD_PHY_MAC_1G 3
#define RTMD_PHY_MAC_2G_PLUS 1
#define RTMD_PHY_MAC_SDS 0
#define RTMD_PHY_POLL_MMD(dev, reg, bit) ((((bit) & GENMASK(3, 0)) << 21) | \
(((dev) & GENMASK(4, 0)) << 16) | \
((reg) & GENMASK(15, 0)))
/* MDIO bus registers/fields */
#define RTMD_C45_DATA(devnum, regnum) ((((devnum) & GENMASK(4, 0)) << 16) | \
((regnum) & GENMASK(15, 0)))
#define RTMD_DATA_IN_HI_OUT_LOW 16
#define RTMD_DATA_IN_LOW_OUT_HI 0
#define RTMD_RUN BIT(0)
#define RTMD_838X_SMI_GLB_CTRL 0xa100
#define RTMD_838X_SMI_GLB_PHY_MAN_24_27 BIT(7)
#define RTMD_838X_SMI_GLB_PHY_PATCH_DONE BIT(15)
#define RTMD_838X_SMI_ACCESS_PHY_CTRL_0 0xa1b8
#define RTMD_838X_SMI_ACCESS_PHY_CTRL_1 0xa1bc
#define RTMD_838X_CMD_FAIL 0 /* No hardware support */
#define RTMD_838X_CMD_READ_C22 0
#define RTMD_838X_CMD_READ_C45 BIT(1)
#define RTMD_838X_CMD_WRITE_C22 BIT(2)
#define RTMD_838X_CMD_WRITE_C45 (BIT(1) | BIT(2))
#define RTMD_838X_C22_DATA(page, reg) ((reg) << 20 | RTMD_PAGE_SELECT << 15 | (page) << 3)
#define RTMD_838X_SMI_ACCESS_PHY_CTRL_2 0xa1c0
#define RTMD_838X_SMI_ACCESS_PHY_CTRL_3 0xa1c4
#define RTMD_838X_SMI_POLL_CTRL 0xa17c
#define RTMD_838X_SMI_PORT0_5_ADDR_CTRL 0xa1c8
#define RTMD_839X_BCAST_PHYID_CTRL 0x03ec
#define RTMD_839X_PHYREG_ACCESS_CTRL 0x03dc
#define RTMD_839X_CMD_FAIL BIT(1)
#define RTMD_839X_CMD_READ_C22 0
#define RTMD_839X_CMD_READ_C45 BIT(2)
#define RTMD_839X_CMD_WRITE_C22 BIT(3)
#define RTMD_839X_CMD_WRITE_C45 (BIT(2) | BIT(3))
#define RTMD_839X_C22_DATA(page, reg) ((reg) << 5 | (page) << 10)
#define RTMD_839X_PHYREG_CTRL (0x03e0)
#define RTMD_839X_PHYREG_SKIP_EXT_PAGE GENMASK(8, 0)
#define RTMD_839X_PHYREG_DATA_CTRL 0x03f0
#define RTMD_839X_PHYREG_MMD_CTRL 0x03f4
#define RTMD_839X_PHYREG_PORT_CTRL(x) (0x03e4 + (x) * 4)
#define RTMD_839X_SMI_PORT_POLLING_CTRL 0x03fc
#define RTMD_839X_SMI_GLB_CTRL 0x03f8
#define RTMD_930X_SMI_GLB_CTRL 0xca00
#define RTMD_930X_SMI_GLB_INTF_SEL(bus) BIT(16 + (bus))
#define RTMD_930X_SMI_GLB_POLL_SEL(bus) BIT(20 + (bus))
#define RTMD_930X_SMI_ACCESS_PHY_CTRL_0 0xcb70
#define RTMD_930X_SMI_ACCESS_PHY_CTRL_1 0xcb74
#define RTMD_930X_CMD_FAIL BIT(25)
#define RTMD_930X_CMD_READ_C22 0
#define RTMD_930X_CMD_READ_C45 BIT(1)
#define RTMD_930X_CMD_WRITE_C22 BIT(2)
#define RTMD_930X_CMD_WRITE_C45 (BIT(1) | BIT(2))
#define RTMD_930X_C22_DATA(page, reg) ((reg) << 20 | RTMD_PAGE_SELECT << 15 | (page) << 3)
#define RTMD_930X_SMI_ACCESS_PHY_CTRL_2 0xcb78
#define RTMD_930X_SMI_ACCESS_PHY_CTRL_3 0xcb7c
#define RTMD_930X_SMI_PORT0_15_POLLING_SEL 0xca08
#define RTMD_930X_SMI_PORT16_27_POLLING_SEL 0xca0c
#define RTMD_930X_SMI_MAC_TYPE_CTRL 0xca04
#define RTMD_930X_SMI_MAC_TYPE_P0_23(pn) (GENMASK(1, 0) << (((pn) / 4) * 2))
#define RTMD_930X_SMI_MAC_TYPE_P24_27(pn) (GENMASK(2, 0) << (((pn) - 24) * 3 + 12))
#define RTMD_930X_SMI_POLL_CTRL 0xca90
#define RTMD_930X_SMI_PRVTE_POLLING_CTRL 0xca10
#define RTMD_930X_SMI_10G_POLLING_REG0_CFG 0xcbb4
#define RTMD_930X_SMI_10G_POLLING_REG9_CFG 0xcbb8
#define RTMD_930X_SMI_10G_POLLING_REG10_CFG 0xcbbc
#define RTMD_930X_SMI_PORT0_5_ADDR_CTRL 0xcb80
#define RTMD_931X_SMI_PORT_POLLING_CTRL 0x0ccc
#define RTMD_931X_SMI_INDRT_ACCESS_BC 0x0c14
#define RTMD_931X_SMI_INDRT_PORT(pn) ((pn) << 5)
#define RTMD_931X_SMI_GLB_CTRL0 0x0cc0
#define RTMD_931X_SMI_GLB_PRVTE0_POLL(bus) BIT(20 + (bus))
#define RTMD_931X_SMI_GLB_PRVTE1_POLL(bus) BIT(24 + (bus))
#define RTMD_931X_SMI_GLB_CTRL1 0x0cbc
#define RTMD_931X_SMI_GLB_FMT_SEL_C45(bus) BIT((bus) * 2 + 1)
#define RTMD_931X_SMI_GLB_FMT_SEL_FRC(bus) BIT((bus) * 2)
#define RTMD_931X_SMI_INDRT_ACCESS_CTRL_0 0x0c00
#define RTMD_931X_CMD_FAIL BIT(1)
#define RTMD_931X_CMD_READ_C22 0
#define RTMD_931X_CMD_READ_C45 BIT(3)
#define RTMD_931X_CMD_WRITE_C22 BIT(4)
#define RTMD_931X_CMD_WRITE_C45 (BIT(3) | BIT(4))
#define RTMD_931X_C22_DATA(page, reg) ((reg) << 6 | (page) << 11)
#define RTMD_931X_SMI_INDRT_ACCESS_CTRL_1 0x0c04
#define RTMD_931X_SMI_INDRT_ACCESS_CTRL_2(x) (0x0c08 + (x) * 4)
#define RTMD_931X_SMI_INDRT_ACCESS_CTRL_3 0x0c10
#define RTMD_931X_SMI_INDRT_ACCESS_MMD 0x0c18
#define RTMD_931X_SMI_PHY_ABLTY_GET_SEL(pn) (0x0cac + ((pn) / 16) * 4)
#define RTMD_931X_SMI_PHY_ABLTY_MDIO 0x0
#define RTMD_931X_SMI_PHY_ABLTY_SDS 0x2
#define RTMD_931X_SMI_PHY_ABLTY_MASK(pn) (GENMASK(1, 0) << (((pn) % 16) * 2))
#define RTMD_931X_SMI_PORT_POLLING_SEL 0x0c9c
#define RTMD_931X_SMI_PORT_ADDR_CTRL 0x0c74
#define RTMD_931X_SMI_10GPHY_POLLING_SEL0 0x0cf0
#define RTMD_931X_SMI_10GPHY_POLLING_SEL1 0x0cf4
#define RTMD_931X_SMI_10GPHY_POLLING_SEL2 0x0cf8
#define RTMD_931X_SMI_10GPHY_POLLING_SEL3 0x0cfc
#define RTMD_931X_SMI_10GPHY_POLLING_SEL4 0x0d00
#define for_each_phy_port(ctrl, pn) \
for_each_set_bit(pn, (ctrl)->phy_ports, RTMD_MAX_PORTS)
#define for_each_sds_port(ctrl, pn) \
for_each_set_bit(pn, (ctrl)->sds_ports, RTMD_MAX_PORTS)
struct rtmd_port {
int page;
bool raw;
u8 smi_addr;
u8 smi_bus;
};
struct rtmd_bus {
bool is_c45;
struct mii_bus *mii_bus;
};
struct rtmd_ctrl {
struct mutex lock; /* protect HW access */
struct regmap *map;
const struct rtmd_config *cfg;
struct rtmd_port port[RTMD_MAX_PORTS];
struct rtmd_bus bus[RTMD_MAX_SMI_BUSES];
DECLARE_BITMAP(phy_ports, RTMD_MAX_PORTS);
DECLARE_BITMAP(sds_ports, RTMD_MAX_PORTS);
};
struct rtmd_chan {
struct rtmd_ctrl *ctrl;
u8 smi_bus;
s8 port[PHY_MAX_ADDR];
};
struct rtmd_command_data {
u32 brdcast;
u32 c22_adr;
u32 c45_adr;
u32 ex_page;
u32 io_data;
u32 mask_lo;
u32 mask_hi;
};
struct rtmd_config {
u32 cmd_fail;
u32 cmd_io_shift;
struct rtmd_command_data cmd_regs;
int bus_map_base;
u16 num_buses;
u16 num_pages;
u16 num_ports;
u32 poll_ctrl;
int port_map_base;
int (*read_c22)(struct mii_bus *bus, u32 pn, u32 page, u32 reg, u32 *val);
int (*read_c45)(struct mii_bus *bus, u32 pn, u32 devnum, u32 regnum, u32 *val);
int (*setup_ctrl)(struct rtmd_ctrl *ctrl);
int (*setup_polling)(struct rtmd_ctrl *ctrl);
int (*write_c22)(struct mii_bus *bus, u32 pn, u32 page, u32 reg, u32 val);
int (*write_c45)(struct mii_bus *bus, u32 pn, u32 devnum, u32 regnum, u32 val);
};
struct rtmd_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 struct rtmd_ctrl *rtmd_bus_to_ctrl(struct mii_bus *bus)
{
return ((struct rtmd_chan *)bus->priv)->ctrl;
}
static int rtmd_phy_to_port(struct mii_bus *bus, int phy)
{
struct rtmd_chan *chan = bus->priv;
return chan->port[phy];
}
static int rtmd_run_cmd(struct mii_bus *bus, u32 cmd,
struct rtmd_command_data *cmd_data, u32 *val)
{
struct rtmd_ctrl *ctrl = rtmd_bus_to_ctrl(bus);
u32 cmdstate;
int ret;
if (ctrl->cfg->cmd_regs.mask_hi) {
/* high port count models have 3 extra command registers */
ret = regmap_write(ctrl->map, ctrl->cfg->cmd_regs.brdcast, cmd_data->brdcast);
if (ret)
return ret;
ret = regmap_write(ctrl->map, ctrl->cfg->cmd_regs.ex_page, cmd_data->ex_page);
if (ret)
return ret;
ret = regmap_write(ctrl->map, ctrl->cfg->cmd_regs.mask_hi, cmd_data->mask_hi);
if (ret)
return ret;
}
ret = regmap_write(ctrl->map, ctrl->cfg->cmd_regs.mask_lo, cmd_data->mask_lo);
if (ret)
return ret;
/* depending on model write output data to high or low word in I/O register */
ret = regmap_write(ctrl->map, ctrl->cfg->cmd_regs.io_data,
cmd_data->io_data << ctrl->cfg->cmd_io_shift);
if (ret)
return ret;
ret = regmap_write(ctrl->map, ctrl->cfg->cmd_regs.c45_adr, cmd_data->c45_adr);
if (ret)
return ret;
/* C22 data and command bits share the same register. */
ret = regmap_write(ctrl->map, ctrl->cfg->cmd_regs.c22_adr,
cmd_data->c22_adr | cmd | RTMD_RUN);
if (ret)
return ret;
ret = regmap_read_poll_timeout(ctrl->map, ctrl->cfg->cmd_regs.c22_adr,
cmdstate, !(cmdstate & RTMD_RUN), 20, 500000);
if (ret) {
dev_warn_once(&bus->dev, "access timed out\n");
return ret;
}
if (cmdstate & ctrl->cfg->cmd_fail) {
dev_warn_once(&bus->dev, "access failed\n");
return -EIO;
}
if (!val)
return 0;
/* read back data from opposite word in I/O register */
ret = regmap_read(ctrl->map, ctrl->cfg->cmd_regs.io_data, val);
if (!ret)
*val = (*val >> (16 - ctrl->cfg->cmd_io_shift)) & GENMASK(15, 0);
return ret;
}
static int rtmd_838x_read_c22(struct mii_bus *bus, u32 pn, u32 page, u32 reg, u32 *val)
{
struct rtmd_command_data cmd_data = {
.c22_adr = RTMD_838X_C22_DATA(page, reg),
.io_data = pn,
};
return rtmd_run_cmd(bus, RTMD_838X_CMD_READ_C22, &cmd_data, val);
}
static int rtmd_838x_write_c22(struct mii_bus *bus, u32 pn, u32 page, u32 reg, u32 val)
{
struct rtmd_command_data cmd_data = {
.c22_adr = RTMD_838X_C22_DATA(page, reg),
.io_data = val,
.mask_lo = BIT(pn),
};
return rtmd_run_cmd(bus, RTMD_838X_CMD_WRITE_C22, &cmd_data, NULL);
}
static int rtmd_838x_read_c45(struct mii_bus *bus, u32 pn, u32 devnum, u32 regnum, u32 *val)
{
struct rtmd_command_data cmd_data = {
.c45_adr = RTMD_C45_DATA(devnum, regnum),
.io_data = pn,
};
return rtmd_run_cmd(bus, RTMD_838X_CMD_READ_C45, &cmd_data, val);
}
static int rtmd_838x_write_c45(struct mii_bus *bus, u32 pn, u32 devnum, u32 regnum, u32 val)
{
struct rtmd_command_data cmd_data = {
.c45_adr = RTMD_C45_DATA(devnum, regnum),
.io_data = val,
.mask_lo = BIT(pn),
};
return rtmd_run_cmd(bus, RTMD_838X_CMD_WRITE_C45, &cmd_data, NULL);
}
static int rtmd_839x_read_c22(struct mii_bus *bus, u32 pn, u32 page, u32 reg, u32 *val)
{
struct rtmd_command_data cmd_data = {
.c22_adr = RTMD_839X_C22_DATA(page, reg),
.ex_page = RTMD_839X_PHYREG_SKIP_EXT_PAGE,
.io_data = pn,
};
return rtmd_run_cmd(bus, RTMD_839X_CMD_READ_C22, &cmd_data, val);
}
static int rtmd_839x_write_c22(struct mii_bus *bus, u32 pn, u32 page, u32 reg, u32 val)
{
struct rtmd_command_data cmd_data = {
.c22_adr = RTMD_839X_C22_DATA(page, reg),
.ex_page = RTMD_839X_PHYREG_SKIP_EXT_PAGE,
.io_data = val,
.mask_lo = (u32)(BIT_ULL(pn)),
.mask_hi = (u32)(BIT_ULL(pn) >> 32),
};
return rtmd_run_cmd(bus, RTMD_839X_CMD_WRITE_C22, &cmd_data, NULL);
}
static int rtmd_839x_read_c45(struct mii_bus *bus, u32 pn, u32 devnum, u32 regnum, u32 *val)
{
struct rtmd_command_data cmd_data = {
.c45_adr = RTMD_C45_DATA(devnum, regnum),
.io_data = pn,
};
return rtmd_run_cmd(bus, RTMD_839X_CMD_READ_C45, &cmd_data, val);
}
static int rtmd_839x_write_c45(struct mii_bus *bus, u32 pn, u32 devnum, u32 regnum, u32 val)
{
struct rtmd_command_data cmd_data = {
.c45_adr = RTMD_C45_DATA(devnum, regnum),
.io_data = val,
.mask_lo = (u32)(BIT_ULL(pn)),
.mask_hi = (u32)(BIT_ULL(pn) >> 32),
};
return rtmd_run_cmd(bus, RTMD_839X_CMD_WRITE_C45, &cmd_data, NULL);
}
static int rtmd_930x_read_c22(struct mii_bus *bus, u32 pn, u32 page, u32 reg, u32 *val)
{
struct rtmd_command_data cmd_data = {
.c22_adr = RTMD_930X_C22_DATA(page, reg),
.io_data = pn,
};
return rtmd_run_cmd(bus, RTMD_930X_CMD_READ_C22, &cmd_data, val);
}
static int rtmd_930x_write_c22(struct mii_bus *bus, u32 pn, u32 page, u32 reg, u32 val)
{
struct rtmd_command_data cmd_data = {
.c22_adr = RTMD_930X_C22_DATA(page, reg),
.io_data = val,
.mask_lo = BIT(pn),
};
return rtmd_run_cmd(bus, RTMD_930X_CMD_WRITE_C22, &cmd_data, NULL);
}
static int rtmd_930x_read_c45(struct mii_bus *bus, u32 pn, u32 devnum, u32 regnum, u32 *val)
{
struct rtmd_command_data cmd_data = {
.c45_adr = RTMD_C45_DATA(devnum, regnum),
.io_data = pn,
};
return rtmd_run_cmd(bus, RTMD_930X_CMD_READ_C45, &cmd_data, val);
}
static int rtmd_930x_write_c45(struct mii_bus *bus, u32 pn, u32 devnum, u32 regnum, u32 val)
{
struct rtmd_command_data cmd_data = {
.c45_adr = RTMD_C45_DATA(devnum, regnum),
.io_data = val,
.mask_lo = BIT(pn),
};
return rtmd_run_cmd(bus, RTMD_930X_CMD_WRITE_C45, &cmd_data, NULL);
}
static int rtmd_931x_read_c22(struct mii_bus *bus, u32 pn, u32 page, u32 reg, u32 *val)
{
struct rtmd_command_data cmd_data = {
.brdcast = RTMD_931X_SMI_INDRT_PORT(pn),
.c22_adr = RTMD_931X_C22_DATA(page, reg),
};
return rtmd_run_cmd(bus, RTMD_931X_CMD_READ_C22, &cmd_data, val);
}
static int rtmd_931x_write_c22(struct mii_bus *bus, u32 pn, u32 page, u32 reg, u32 val)
{
struct rtmd_command_data cmd_data = {
.c22_adr = RTMD_931X_C22_DATA(page, reg),
.mask_lo = (u32)(BIT_ULL(pn)),
.mask_hi = (u32)(BIT_ULL(pn) >> 32),
.io_data = val,
};
return rtmd_run_cmd(bus, RTMD_931X_CMD_WRITE_C22, &cmd_data, NULL);
}
static int rtmd_931x_read_c45(struct mii_bus *bus, u32 pn, u32 devnum, u32 regnum, u32 *val)
{
struct rtmd_command_data cmd_data = {
.brdcast = RTMD_931X_SMI_INDRT_PORT(pn),
.c45_adr = RTMD_C45_DATA(devnum, regnum),
};
return rtmd_run_cmd(bus, RTMD_931X_CMD_READ_C45, &cmd_data, val);
}
static int rtmd_931x_write_c45(struct mii_bus *bus, u32 pn, u32 devnum, u32 regnum, u32 val)
{
struct rtmd_command_data cmd_data = {
.c45_adr = RTMD_C45_DATA(devnum, regnum),
.mask_lo = (u32)(BIT_ULL(pn)),
.mask_hi = (u32)(BIT_ULL(pn) >> 32),
.io_data = val,
};
return rtmd_run_cmd(bus, RTMD_931X_CMD_WRITE_C45, &cmd_data, NULL);
}
static int rtmd_read_c45(struct mii_bus *bus, int phy, int devnum, int regnum)
{
struct rtmd_ctrl *ctrl = rtmd_bus_to_ctrl(bus);
int ret, pn, val = 0;
pn = rtmd_phy_to_port(bus, phy);
if (pn < 0)
return -ENOENT;
scoped_guard(mutex, &ctrl->lock)
ret = (*ctrl->cfg->read_c45)(bus, pn, devnum, regnum, &val);
dev_dbg(&bus->dev, "rd_MMD(phy=0x%02x, dev=0x%04x, reg=0x%04x) = 0x%04x, ret = %d\n",
phy, devnum, regnum, val, ret);
return ret ? ret : val;
}
static int rtmd_read_c22(struct mii_bus *bus, int phy, int regnum)
{
struct rtmd_ctrl *ctrl = rtmd_bus_to_ctrl(bus);
int ret, pn, page, val = 0;
pn = rtmd_phy_to_port(bus, phy);
if (pn < 0)
return -ENOENT;
scoped_guard(mutex, &ctrl->lock) {
page = ctrl->port[pn].page;
if (regnum == RTMD_PAGE_SELECT &&
page != RTMD_RAW_PAGE(ctrl->cfg->num_pages))
return page;
ctrl->port[pn].raw = (page == RTMD_RAW_PAGE(ctrl->cfg->num_pages));
ret = (*ctrl->cfg->read_c22)(bus, pn, page, regnum, &val);
}
dev_dbg(&bus->dev, "rd_PHY(phy=0x%02x, pag=0x%04x, reg=0x%04x) = 0x%04x, ret = %d\n",
phy, page, regnum, val, ret);
return ret ? ret : val;
}
static int rtmd_write_c45(struct mii_bus *bus, int phy, int devnum, int regnum, u16 val)
{
struct rtmd_ctrl *ctrl = rtmd_bus_to_ctrl(bus);
int ret, pn;
pn = rtmd_phy_to_port(bus, phy);
if (pn < 0)
return -ENOENT;
scoped_guard(mutex, &ctrl->lock)
ret = (*ctrl->cfg->write_c45)(bus, pn, devnum, regnum, val);
dev_dbg(&bus->dev, "wr_MMD(phy=0x%02x, dev=0x%04x, reg=0x%04x, val=0x%04x), ret = %d\n",
phy, devnum, regnum, val, ret);
return ret;
}
static int rtmd_write_c22(struct mii_bus *bus, int phy, int regnum, u16 val)
{
struct rtmd_ctrl *ctrl = rtmd_bus_to_ctrl(bus);
bool do_write = false;
int ret = 0, page, pn;
pn = rtmd_phy_to_port(bus, phy);
if (pn < 0)
return -ENOENT;
scoped_guard(mutex, &ctrl->lock) {
page = ctrl->port[pn].page;
if (regnum == RTMD_PAGE_SELECT)
ctrl->port[pn].page = val;
do_write = !ctrl->port[pn].raw &&
(regnum != RTMD_PAGE_SELECT ||
page == RTMD_RAW_PAGE(ctrl->cfg->num_pages));
if (do_write) {
ctrl->port[pn].raw = (page == RTMD_RAW_PAGE(ctrl->cfg->num_pages));
ret = (*ctrl->cfg->write_c22)(bus, pn, page, regnum, val);
} else {
ctrl->port[pn].raw = false;
}
}
if (do_write)
dev_dbg(&bus->dev,
"wr_PHY(phy=0x%02x, pag=0x%04x, reg=0x%04x, val=0x%04x), ret = %d\n",
phy, page, regnum, val, ret);
return ret;
}
static int rtmd_poll_port(struct rtmd_ctrl *ctrl, int pn, bool active)
{
return regmap_assign_bits(ctrl->map, ctrl->cfg->poll_ctrl + (pn / 32) * 4,
BIT(pn % 32), active);
}
static int rtmd_disable_polling(struct rtmd_ctrl *ctrl)
{
int pn, ret;
for (pn = 0; pn < ctrl->cfg->num_ports; pn++) {
ret = rtmd_poll_port(ctrl, pn, false);
if (ret)
return ret;
}
return 0;
}
static int rtmd_enable_polling(struct rtmd_ctrl *ctrl)
{
int pn, ret;
for_each_phy_port(ctrl, pn) {
ret = rtmd_poll_port(ctrl, pn, true);
if (ret)
return ret;
}
for_each_sds_port(ctrl, pn) {
ret = rtmd_poll_port(ctrl, pn, true);
if (ret)
return ret;
}
return 0;
}
static int rtmd_setup_smi_topology(struct rtmd_ctrl *ctrl)
{
u32 reg, mask, val, pn;
int ret;
for_each_phy_port(ctrl, pn) {
if (ctrl->cfg->bus_map_base) {
reg = ctrl->cfg->bus_map_base + (pn / 16) * 4;
mask = GENMASK(1, 0) << ((pn % 16) * 2);
val = (u32)ctrl->port[pn].smi_bus << __ffs(mask);
ret = regmap_update_bits(ctrl->map, reg, mask, val);
if (ret)
return ret;
}
if (ctrl->cfg->port_map_base) {
reg = ctrl->cfg->port_map_base + (pn / 6) * 4;
mask = GENMASK(4, 0) << ((pn % 6) * 5);
val = (u32)ctrl->port[pn].smi_addr << __ffs(mask);
ret = regmap_update_bits(ctrl->map, reg, mask, val);
if (ret)
return ret;
}
}
return 0;
}
static u32 rtmd_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 rtmd_get_phy_info(struct rtmd_ctrl *ctrl, int pn, struct rtmd_phy_info *phyinfo)
{
struct mii_bus *bus;
u32 smi_addr, phyid;
if (!test_bit(pn, ctrl->phy_ports))
return -EINVAL;
bus = ctrl->bus[ctrl->port[pn].smi_bus].mii_bus;
smi_addr = ctrl->port[pn].smi_addr;
phyid = rtmd_get_phy_id(mdiobus_get_phy(bus, smi_addr));
*phyinfo = (struct rtmd_phy_info){};
/* Determine PHY specific characteristics for polling fine tuning */
switch (phyid) {
case RTMD_PHY_AQR113C_A:
case RTMD_PHY_AQR113C_B:
case RTMD_PHY_AQR813:
phyinfo->mac_type = RTMD_PHY_MAC_2G_PLUS;
phyinfo->poll_duplex = RTMD_PHY_POLL_MMD(1, 0x0000, 8);
phyinfo->poll_adv_1000 = RTMD_PHY_POLL_MMD(7, 0xc400, 15);
phyinfo->poll_lpa_1000 = RTMD_PHY_POLL_MMD(7, 0xe820, 15);
break;
case RTMD_PHY_RTL8218D:
case RTMD_PHY_RTL8218E:
phyinfo->mac_type = RTMD_PHY_MAC_1G;
phyinfo->has_giga_lite = true;
break;
case RTMD_PHY_RTL8226:
case RTMD_PHY_RTL8221B_VB_CG:
case RTMD_PHY_RTL8221B_VM_CG:
case RTMD_PHY_RTL8224:
phyinfo->mac_type = RTMD_PHY_MAC_2G_PLUS;
phyinfo->has_giga_lite = true;
phyinfo->poll_duplex = RTMD_PHY_POLL_MMD(31, 0xa400, 8);
phyinfo->poll_adv_1000 = RTMD_PHY_POLL_MMD(31, 0xa412, 9);
phyinfo->poll_lpa_1000 = RTMD_PHY_POLL_MMD(31, 0xa414, 11);
break;
case RTMD_PHY_RTL8261:
phyinfo->mac_type = RTMD_PHY_MAC_2G_PLUS;
phyinfo->has_giga_lite = true;
phyinfo->has_res_reg = true;
break;
default:
dev_warn(&bus->dev, "skip polling setup for phy 0x%08x on port %d\n", phyid, pn);
return -EINVAL;
}
return 0;
}
static int rtmd_838x_setup_ctrl(struct rtmd_ctrl *ctrl)
{
/*
* 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.
*/
return regmap_set_bits(ctrl->map, RTMD_838X_SMI_GLB_CTRL,
RTMD_838X_SMI_GLB_PHY_PATCH_DONE);
}
static int rtmd_838x_setup_polling(struct rtmd_ctrl *ctrl)
{
/*
* 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.
*/
return regmap_assign_bits(ctrl->map, RTMD_838X_SMI_GLB_CTRL,
RTMD_838X_SMI_GLB_PHY_MAN_24_27,
test_bit(24, ctrl->phy_ports));
}
static int rtmd_930x_setup_ctrl(struct rtmd_ctrl *ctrl)
{
int ret;
/* Define C22/C45 bus feature set */
for (int smi_bus = 0; smi_bus < ctrl->cfg->num_buses; smi_bus++) {
ret = regmap_assign_bits(ctrl->map, RTMD_930X_SMI_GLB_CTRL,
RTMD_930X_SMI_GLB_INTF_SEL(smi_bus),
ctrl->bus[smi_bus].is_c45);
if (ret)
return ret;
}
return 0;
}
static int rtmd_930x_set_port_ability(struct rtmd_ctrl *ctrl, u32 pn, u32 ability)
{
u32 mask;
if (pn < 24)
mask = RTMD_930X_SMI_MAC_TYPE_P0_23(pn);
else if (pn < 28)
mask = RTMD_930X_SMI_MAC_TYPE_P24_27(pn);
else
return -EINVAL;
return regmap_update_bits(ctrl->map, RTMD_930X_SMI_MAC_TYPE_CTRL,
mask, ability << __ffs(mask));
}
static int rtmd_930x_setup_polling(struct rtmd_ctrl *ctrl)
{
struct rtmd_phy_info phyinfo;
int ret;
u32 pn;
/* set all ports to "SerDes driven" */
for (pn = 0; pn < ctrl->cfg->num_ports; pn++) {
ret = rtmd_930x_set_port_ability(ctrl, pn, RTMD_PHY_MAC_SDS);
if (ret)
return ret;
}
/* Define PHY specific polling parameters */
for_each_phy_port(ctrl, pn) {
if (rtmd_get_phy_info(ctrl, pn, &phyinfo))
continue;
/* set port to "PHY driven" */
ret = rtmd_930x_set_port_ability(ctrl, pn, phyinfo.mac_type);
if (ret)
return ret;
/* polling via standard or resolution register */
ret = regmap_assign_bits(ctrl->map, RTMD_930X_SMI_GLB_CTRL,
RTMD_930X_SMI_GLB_POLL_SEL(ctrl->port[pn].smi_bus),
phyinfo.has_res_reg);
if (ret)
return ret;
/* proprietary Realtek 1G/2.5 lite polling */
ret = regmap_assign_bits(ctrl->map, RTMD_930X_SMI_PRVTE_POLLING_CTRL,
BIT(pn), phyinfo.has_giga_lite);
if (ret)
return ret;
if (!phyinfo.poll_duplex && !phyinfo.poll_adv_1000 && !phyinfo.poll_lpa_1000)
continue;
/* Unique 10G polling setup enforced by hardware design. Always same 10G PHYs. */
ret = regmap_write(ctrl->map, RTMD_930X_SMI_10G_POLLING_REG0_CFG,
phyinfo.poll_duplex);
if (ret)
return ret;
ret = regmap_write(ctrl->map, RTMD_930X_SMI_10G_POLLING_REG9_CFG,
phyinfo.poll_adv_1000);
if (ret)
return ret;
ret = regmap_write(ctrl->map, RTMD_930X_SMI_10G_POLLING_REG10_CFG,
phyinfo.poll_lpa_1000);
if (ret)
return ret;
}
return 0;
}
static int rtmd_931x_setup_ctrl(struct rtmd_ctrl *ctrl)
{
int ret;
/* Define C22/C45 bus feature set (bit 1 of SMI_SETx_FMT_SEL) */
for (int smi_bus = 0; smi_bus < ctrl->cfg->num_buses; smi_bus++) {
ret = regmap_assign_bits(ctrl->map, RTMD_931X_SMI_GLB_CTRL1,
RTMD_931X_SMI_GLB_FMT_SEL_C45(smi_bus),
ctrl->bus[smi_bus].is_c45);
if (ret)
return ret;
}
return 0;
}
static int rtmd_931x_set_port_ability(struct rtmd_ctrl *ctrl, u32 pn, u32 ability)
{
u32 mask;
if (pn < ctrl->cfg->num_ports)
mask = RTMD_931X_SMI_PHY_ABLTY_MASK(pn);
else
return -EINVAL;
return regmap_update_bits(ctrl->map, RTMD_931X_SMI_PHY_ABLTY_GET_SEL(pn),
mask, ability << __ffs(mask));
}
static int rtmd_931x_setup_polling(struct rtmd_ctrl *ctrl)
{
struct rtmd_phy_info phyinfo;
int ret;
u32 pn;
/* set all ports to "SerDes driven" */
for (pn = 0; pn < ctrl->cfg->num_ports; pn++) {
ret = rtmd_931x_set_port_ability(ctrl, pn, RTMD_931X_SMI_PHY_ABLTY_SDS);
if (ret)
return ret;
}
/* Define PHY specific polling parameters */
for_each_phy_port(ctrl, pn) {
u8 smi_bus = ctrl->port[pn].smi_bus;
if (rtmd_get_phy_info(ctrl, pn, &phyinfo))
continue;
/* set port to "PHY driven" */
ret = rtmd_931x_set_port_ability(ctrl, pn, RTMD_931X_SMI_PHY_ABLTY_MDIO);
if (ret)
return ret;
ret = regmap_assign_bits(ctrl->map, RTMD_931X_SMI_GLB_CTRL0,
RTMD_931X_SMI_GLB_PRVTE0_POLL(smi_bus),
phyinfo.has_res_reg);
if (ret)
return ret;
ret = regmap_assign_bits(ctrl->map, RTMD_931X_SMI_GLB_CTRL0,
RTMD_931X_SMI_GLB_PRVTE1_POLL(smi_bus),
phyinfo.force_res);
if (ret)
return ret;
/* polling std. or proprietary format (bit 0 of SMI_SETx_FMT_SEL) */
ret = regmap_assign_bits(ctrl->map, RTMD_931X_SMI_GLB_CTRL1,
RTMD_931X_SMI_GLB_FMT_SEL_FRC(smi_bus),
phyinfo.force_res);
if (ret)
return ret;
if (!phyinfo.poll_duplex && !phyinfo.poll_adv_1000 && !phyinfo.poll_lpa_1000)
continue;
/* Unique 10G polling setup enforced by hardware design. Always same 10G PHYs. */
ret = regmap_write(ctrl->map, RTMD_931X_SMI_10GPHY_POLLING_SEL2,
phyinfo.poll_duplex);
if (ret)
return ret;
ret = regmap_write(ctrl->map, RTMD_931X_SMI_10GPHY_POLLING_SEL3,
phyinfo.poll_adv_1000);
if (ret)
return ret;
ret = regmap_write(ctrl->map, RTMD_931X_SMI_10GPHY_POLLING_SEL4,
phyinfo.poll_lpa_1000);
if (ret)
return ret;
}
return 0;
}
static int rtmd_map_ports(struct device *dev)
{
struct fwnode_handle *fw_dev = dev_fwnode(dev);
struct rtmd_ctrl *ctrl = dev_get_drvdata(dev);
int smi_bus, smi_addr, pn;
struct fwnode_handle *fw_parent __free(fwnode_handle) = fwnode_get_parent(fw_dev);
if (!fw_parent)
return -ENODEV;
struct fwnode_handle *fw_switch __free(fwnode_handle) =
fwnode_get_named_child_node(fw_parent, "ethernet-switch");
if (!fw_switch)
return dev_err_probe(dev, -ENODEV, "%pfwP missing ethernet-switch\n", fw_parent);
struct fwnode_handle *fw_ports __free(fwnode_handle) =
fwnode_get_named_child_node(fw_switch, "ethernet-ports");
if (!fw_ports)
return dev_err_probe(dev, -ENODEV, "%pfwP missing ethernet-ports\n", fw_switch);
fwnode_for_each_child_node_scoped(fw_ports, fw_port) {
if (fwnode_property_read_u32(fw_port, "reg", &pn))
continue;
if (pn == ctrl->cfg->num_ports)
continue;
if (pn > ctrl->cfg->num_ports)
return dev_err_probe(dev, -EINVAL, "%pfwP illegal port number\n", fw_port);
if (test_bit(pn, ctrl->phy_ports) ||
test_bit(pn, ctrl->sds_ports))
return dev_err_probe(dev, -EINVAL, "%pfwP duplicate port number\n",
fw_port);
struct fwnode_handle *fw_phy __free(fwnode_handle) =
fwnode_find_reference(fw_port, "phy-handle", 0);
if (IS_ERR(fw_phy)) {
/* port without a phy-handle is SerDes driven */
__set_bit(pn, ctrl->sds_ports);
continue;
}
if (fwnode_property_read_u32(fw_phy, "reg", &smi_addr))
return dev_err_probe(dev, -EINVAL, "%pfwP no phy address\n", fw_phy);
if (smi_addr >= PHY_MAX_ADDR)
return dev_err_probe(dev, -EINVAL, "%pfwP illegal phy address\n", fw_phy);
struct fwnode_handle *fw_bus __free(fwnode_handle) = fwnode_get_parent(fw_phy);
if (!fw_bus || fwnode_property_read_u32(fw_bus, "reg", &smi_bus))
return dev_err_probe(dev, -EINVAL, "%pfwP no bus number\n",
fw_bus ?: fw_phy);
if (smi_bus >= ctrl->cfg->num_buses)
return dev_err_probe(dev, -EINVAL, "%pfwP illegal bus number\n", fw_bus);
if (fwnode_device_is_compatible(fw_phy, "ethernet-phy-ieee802.3-c45"))
ctrl->bus[smi_bus].is_c45 = true;
ctrl->port[pn].smi_bus = smi_bus;
ctrl->port[pn].smi_addr = smi_addr;
__set_bit(pn, ctrl->phy_ports);
}
return 0;
}
static int rtmd_probe_one(struct device *dev, struct rtmd_ctrl *ctrl,
struct fwnode_handle *fw_bus)
{
struct rtmd_chan *chan;
struct mii_bus *bus;
int smi_bus, ret;
u32 pn;
ret = fwnode_property_read_u32(fw_bus, "reg", &smi_bus);
if (ret)
return dev_err_probe(dev, ret, "%pfwP no bus number\n", fw_bus);
if (smi_bus >= ctrl->cfg->num_buses)
return dev_err_probe(dev, -EINVAL, "%pfwP illegal bus number\n", fw_bus);
bus = devm_mdiobus_alloc_size(dev, sizeof(*chan));
if (!bus)
return -ENOMEM;
ctrl->bus[smi_bus].mii_bus = bus;
chan = bus->priv;
chan->ctrl = ctrl;
chan->smi_bus = smi_bus;
/* setup reverse lookup bus/phy -> port */
for (int smi_addr = 0; smi_addr < ARRAY_SIZE(chan->port); smi_addr++)
chan->port[smi_addr] = -1;
for_each_phy_port(ctrl, pn)
if (ctrl->port[pn].smi_bus == smi_bus)
chan->port[ctrl->port[pn].smi_addr] = pn;
bus->name = "Realtek MDIO bus";
bus->read = rtmd_read_c22;
bus->write = rtmd_write_c22;
bus->read_c45 = rtmd_read_c45;
bus->write_c45 = rtmd_write_c45;
bus->parent = dev;
snprintf(bus->id, MII_BUS_ID_SIZE, "realtek-mdio-%d", smi_bus);
ret = devm_of_mdiobus_register(dev, bus, to_of_node(fw_bus));
if (ret)
return dev_err_probe(dev, ret, "cannot register MDIO %d bus\n", smi_bus);
return 0;
}
static int rtmd_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct rtmd_ctrl *ctrl;
int ret;
ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL);
if (!ctrl)
return -ENOMEM;
ret = devm_mutex_init(dev, &ctrl->lock);
if (ret)
return ret;
platform_set_drvdata(pdev, ctrl);
ctrl->cfg = (const struct rtmd_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);
ret = rtmd_disable_polling(ctrl);
if (ret)
return ret;
ret = rtmd_map_ports(dev);
if (ret)
return ret;
ret = rtmd_setup_smi_topology(ctrl);
if (ret)
return ret;
if (ctrl->cfg->setup_ctrl) {
ret = ctrl->cfg->setup_ctrl(ctrl);
if (ret)
return ret;
}
device_for_each_child_node_scoped(dev, child) {
ret = rtmd_probe_one(dev, ctrl, child);
if (ret)
return ret;
}
if (ctrl->cfg->setup_polling) {
ret = ctrl->cfg->setup_polling(ctrl);
if (ret)
return ret;
}
ret = rtmd_enable_polling(ctrl);
if (ret)
return ret;
return 0;
}
static const struct rtmd_config rtmd_838x_cfg = {
.cmd_fail = RTMD_838X_CMD_FAIL,
.cmd_io_shift = RTMD_DATA_IN_HI_OUT_LOW,
.cmd_regs = {
.c22_adr = RTMD_838X_SMI_ACCESS_PHY_CTRL_1,
.c45_adr = RTMD_838X_SMI_ACCESS_PHY_CTRL_3,
.mask_lo = RTMD_838X_SMI_ACCESS_PHY_CTRL_0,
.io_data = RTMD_838X_SMI_ACCESS_PHY_CTRL_2,
},
.num_buses = RTMD_838X_NUM_BUSES,
.num_pages = RTMD_838X_NUM_PAGES,
.num_ports = RTMD_838X_NUM_PORTS,
.poll_ctrl = RTMD_838X_SMI_POLL_CTRL,
.port_map_base = RTMD_838X_SMI_PORT0_5_ADDR_CTRL,
.read_c22 = rtmd_838x_read_c22,
.read_c45 = rtmd_838x_read_c45,
.setup_ctrl = rtmd_838x_setup_ctrl,
.setup_polling = rtmd_838x_setup_polling,
.write_c22 = rtmd_838x_write_c22,
.write_c45 = rtmd_838x_write_c45,
};
static const struct rtmd_config rtmd_839x_cfg = {
.cmd_fail = RTMD_839X_CMD_FAIL,
.cmd_io_shift = RTMD_DATA_IN_HI_OUT_LOW,
.cmd_regs = {
.brdcast = RTMD_839X_BCAST_PHYID_CTRL,
.c22_adr = RTMD_839X_PHYREG_ACCESS_CTRL,
.c45_adr = RTMD_839X_PHYREG_MMD_CTRL,
.ex_page = RTMD_839X_PHYREG_CTRL,
.mask_lo = RTMD_839X_PHYREG_PORT_CTRL(0),
.mask_hi = RTMD_839X_PHYREG_PORT_CTRL(1),
.io_data = RTMD_839X_PHYREG_DATA_CTRL,
},
.num_buses = RTMD_839X_NUM_BUSES,
.num_pages = RTMD_839X_NUM_PAGES,
.num_ports = RTMD_839X_NUM_PORTS,
.poll_ctrl = RTMD_839X_SMI_PORT_POLLING_CTRL,
.read_c22 = rtmd_839x_read_c22,
.read_c45 = rtmd_839x_read_c45,
.write_c22 = rtmd_839x_write_c22,
.write_c45 = rtmd_839x_write_c45,
};
static const struct rtmd_config rtmd_930x_cfg = {
.cmd_fail = RTMD_930X_CMD_FAIL,
.cmd_io_shift = RTMD_DATA_IN_HI_OUT_LOW,
.cmd_regs = {
.c22_adr = RTMD_930X_SMI_ACCESS_PHY_CTRL_1,
.c45_adr = RTMD_930X_SMI_ACCESS_PHY_CTRL_3,
.mask_lo = RTMD_930X_SMI_ACCESS_PHY_CTRL_0,
.io_data = RTMD_930X_SMI_ACCESS_PHY_CTRL_2,
},
.bus_map_base = RTMD_930X_SMI_PORT0_15_POLLING_SEL,
.num_buses = RTMD_930X_NUM_BUSES,
.num_pages = RTMD_930X_NUM_PAGES,
.num_ports = RTMD_930X_NUM_PORTS,
.poll_ctrl = RTMD_930X_SMI_POLL_CTRL,
.port_map_base = RTMD_930X_SMI_PORT0_5_ADDR_CTRL,
.read_c22 = rtmd_930x_read_c22,
.read_c45 = rtmd_930x_read_c45,
.setup_ctrl = rtmd_930x_setup_ctrl,
.setup_polling = rtmd_930x_setup_polling,
.write_c22 = rtmd_930x_write_c22,
.write_c45 = rtmd_930x_write_c45,
};
static const struct rtmd_config rtmd_931x_cfg = {
.cmd_fail = RTMD_931X_CMD_FAIL,
.cmd_io_shift = RTMD_DATA_IN_LOW_OUT_HI,
.cmd_regs = {
.brdcast = RTMD_931X_SMI_INDRT_ACCESS_BC,
.c22_adr = RTMD_931X_SMI_INDRT_ACCESS_CTRL_0,
.c45_adr = RTMD_931X_SMI_INDRT_ACCESS_MMD,
.ex_page = RTMD_931X_SMI_INDRT_ACCESS_CTRL_1,
.mask_lo = RTMD_931X_SMI_INDRT_ACCESS_CTRL_2(0),
.mask_hi = RTMD_931X_SMI_INDRT_ACCESS_CTRL_2(1),
.io_data = RTMD_931X_SMI_INDRT_ACCESS_CTRL_3,
},
.bus_map_base = RTMD_931X_SMI_PORT_POLLING_SEL,
.num_buses = RTMD_931X_NUM_BUSES,
.num_pages = RTMD_931X_NUM_PAGES,
.num_ports = RTMD_931X_NUM_PORTS,
.poll_ctrl = RTMD_931X_SMI_PORT_POLLING_CTRL,
.port_map_base = RTMD_931X_SMI_PORT_ADDR_CTRL,
.read_c22 = rtmd_931x_read_c22,
.read_c45 = rtmd_931x_read_c45,
.setup_ctrl = rtmd_931x_setup_ctrl,
.setup_polling = rtmd_931x_setup_polling,
.write_c22 = rtmd_931x_write_c22,
.write_c45 = rtmd_931x_write_c45,
};
static const struct of_device_id rtmd_ids[] = {
{ .compatible = "realtek,rtl8380-mdio", .data = &rtmd_838x_cfg, },
{ .compatible = "realtek,rtl8392-mdio", .data = &rtmd_839x_cfg, },
{ .compatible = "realtek,rtl9301-mdio", .data = &rtmd_930x_cfg, },
{ .compatible = "realtek,rtl9311-mdio", .data = &rtmd_931x_cfg, },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, rtmd_ids);
static struct platform_driver rtmd_driver = {
.probe = rtmd_probe,
.driver = {
.name = "mdio-rtl-otto",
.of_match_table = rtmd_ids,
},
};
module_platform_driver(rtmd_driver);
MODULE_AUTHOR("Markus Stockhausen <markus.stockhausen@gmx.de>");
MODULE_DESCRIPTION("Realtek Otto MDIO driver");
MODULE_LICENSE("GPL");