There is currently a difference how upstream and downstream define
the switch in the dts. Downstream holds the switch as a member
node below a root switchcore parent. Upstream uses the switch as
the parent.
Upstream:
ethernet-switch@1b000000 {
mdio-controller@ca00 { };
ethernet { };
ethernet-ports { };
}
Downstream:
switchcore@1b000000 {
ethernet-switch {
ethernet-ports { };
};
mdio-controller@ca00 { };
ethernet { };
}
Align downstream to upstream and merge the ethernet-switch into
the parent node. For this to work adapt the port lookup in the MDIO
and PCS driver.
Remark! With this commit the boot process will give the spurious
error message "rtl838x_eth 1b000000.ethernet-switch:ethernet eth0:
Failed to create a device link to DSA switch 1b000000.ethernet-switch"
This comes from the fact that the switch is the parent of the ethernet
device. Thus a link back from ethernet device to the switch is no
longer possible. Testing shows that the error is just cosmetic.
Link: https://github.com/openwrt/openwrt/pull/23599
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
1254 lines
36 KiB
C
1254 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 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_switch __free(fwnode_handle) = fwnode_get_parent(fw_dev);
|
|
if (!fw_switch)
|
|
return -ENODEV;
|
|
|
|
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 <markus.stockhausen@gmx.de>");
|
|
MODULE_DESCRIPTION("Realtek Otto MDIO driver");
|
|
MODULE_LICENSE("GPL");
|