// 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 #include #include #include #include #include #include #include #include #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 struct fwnode_handle *rtmd_get_bus_node(struct fwnode_handle *phynode) { struct fwnode_handle *parent = fwnode_get_parent(phynode); if (parent && fwnode_name_eq(parent, "ethernet-phy-package")) { struct fwnode_handle *grandparent = fwnode_get_parent(parent); fwnode_handle_put(parent); return grandparent; } return parent; } 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) = rtmd_get_bus_node(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 "); MODULE_DESCRIPTION("Realtek Otto MDIO driver"); MODULE_LICENSE("GPL");