Majority of kernel uses a_to_b(a) instead of b_from_a(a). Convert to that to be consistent with all helpers in the driver. Additionally drop inline function definitions. Let the compiler decide what is best. 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>
1240 lines
36 KiB
C
1240 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;
|
|
|
|
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, val = 0;
|
|
|
|
pn = rtmd_phy_to_port(bus, phy);
|
|
if (pn < 0)
|
|
return -ENOENT;
|
|
|
|
guard(mutex)(&ctrl->lock);
|
|
if (regnum == RTMD_PAGE_SELECT &&
|
|
ctrl->port[pn].page != RTMD_RAW_PAGE(ctrl->cfg->num_pages))
|
|
return ctrl->port[pn].page;
|
|
|
|
ctrl->port[pn].raw = (ctrl->port[pn].page == RTMD_RAW_PAGE(ctrl->cfg->num_pages));
|
|
|
|
ret = (*ctrl->cfg->read_c22)(bus, pn, ctrl->port[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, ctrl->port[pn].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;
|
|
|
|
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);
|
|
int ret, page, pn;
|
|
|
|
pn = rtmd_phy_to_port(bus, phy);
|
|
if (pn < 0)
|
|
return -ENOENT;
|
|
|
|
guard(mutex)(&ctrl->lock);
|
|
page = ctrl->port[pn].page;
|
|
|
|
if (regnum == RTMD_PAGE_SELECT)
|
|
ctrl->port[pn].page = val;
|
|
|
|
if (!ctrl->port[pn].raw &&
|
|
(regnum != RTMD_PAGE_SELECT || page == RTMD_RAW_PAGE(ctrl->cfg->num_pages))) {
|
|
ctrl->port[pn].raw = (page == RTMD_RAW_PAGE(ctrl->cfg->num_pages));
|
|
|
|
ret = (*ctrl->cfg->write_c22)(bus, pn, page, regnum, val);
|
|
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;
|
|
}
|
|
|
|
ctrl->port[pn].raw = false;
|
|
|
|
return 0;
|
|
}
|
|
|
|
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");
|