diff --git a/drivers/net/dsa/realtek/realtek.h b/drivers/net/dsa/realtek/realtek.h index 75a127545e5d..1c0ca6daf545 100644 --- a/drivers/net/dsa/realtek/realtek.h +++ b/drivers/net/dsa/realtek/realtek.h @@ -156,6 +156,10 @@ struct realtek_ops { int (*phy_read)(struct realtek_priv *priv, int phy, int regnum); int (*phy_write)(struct realtek_priv *priv, int phy, int regnum, u16 val); + int (*phy_read_c45)(struct realtek_priv *priv, int phy, int devad, + int regnum); + int (*phy_write_c45)(struct realtek_priv *priv, int phy, int devad, + int regnum, u16 val); }; struct realtek_variant { diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c index f8220d832e46..87ec924a0611 100644 --- a/drivers/net/dsa/realtek/rtl8365mb_main.c +++ b/drivers/net/dsa/realtek/rtl8365mb_main.c @@ -101,6 +101,7 @@ #include #include #include +#include #include #include "realtek.h" @@ -199,9 +200,21 @@ #define RTL8365MB_GPHY_OCP_MSB_0_CFG_CPU_OCPADR_MASK 0x0FC0 #define RTL8365MB_PHY_OCP_ADDR_PREFIX_MASK 0xFC00 +/* The full 16-bit OCP address is split across two registers: bits [15:10] are + * the prefix (RTL8365MB_PHY_OCP_ADDR_PREFIX_MASK above), and bits [9:1] go into + * the ADDRESS register as two fields, [5:1] and [9:6]. Bit 0 is always 0 - PHY + * OCP registers are 2-byte aligned. + */ +#define RTL8365MB_PHY_OCP_ADDR_5_1_MASK GENMASK(5, 1) +#define RTL8365MB_PHY_OCP_ADDR_9_6_MASK GENMASK(9, 6) + /* The PHY OCP addresses of PHY registers 0~31 start here */ #define RTL8365MB_PHY_OCP_ADDR_PHYREG_BASE 0xA400 +#define RTL8365MB_PHY_OCP_ADDR_EEE_ABLE 0xA5C4 +#define RTL8365MB_PHY_OCP_ADDR_EEE_ADV 0xA5D0 +#define RTL8365MB_PHY_OCP_ADDR_EEE_LPABLE 0xA5D2 + /* External interface port mode values - used in DIGITAL_INTERFACE_SELECT */ #define RTL8365MB_EXT_PORT_MODE_DISABLE 0 #define RTL8365MB_EXT_PORT_MODE_RGMII 1 @@ -871,6 +884,8 @@ static int rtl8365mb_phy_poll_busy(struct realtek_priv *priv) static int rtl8365mb_phy_ocp_prepare(struct realtek_priv *priv, int phy, u32 ocp_addr) { + u16 ocp_addr_lo = FIELD_GET(RTL8365MB_PHY_OCP_ADDR_5_1_MASK, ocp_addr); + u16 ocp_addr_hi = FIELD_GET(RTL8365MB_PHY_OCP_ADDR_9_6_MASK, ocp_addr); u32 val; int ret; @@ -887,9 +902,9 @@ static int rtl8365mb_phy_ocp_prepare(struct realtek_priv *priv, int phy, val = RTL8365MB_PHY_BASE; val |= FIELD_PREP(RTL8365MB_INDIRECT_ACCESS_ADDRESS_PHYNUM_MASK, phy); val |= FIELD_PREP(RTL8365MB_INDIRECT_ACCESS_ADDRESS_OCPADR_5_1_MASK, - ocp_addr >> 1); + ocp_addr_lo); val |= FIELD_PREP(RTL8365MB_INDIRECT_ACCESS_ADDRESS_OCPADR_9_6_MASK, - ocp_addr >> 6); + ocp_addr_hi); ret = regmap_write(priv->map_nolock, RTL8365MB_INDIRECT_ACCESS_ADDRESS_REG, val); if (ret) @@ -1040,6 +1055,66 @@ static int rtl8365mb_phy_write(struct realtek_priv *priv, int phy, int regnum, return 0; } +static int rtl8365mb_phy_read_c45(struct realtek_priv *priv, int phy, int devad, + int regnum) +{ + u32 ocp_addr; + u16 val; + int ret; + + if (phy > RTL8365MB_PHYADDRMAX) + return -EINVAL; + + if (devad == MDIO_MMD_PCS && regnum == MDIO_PCS_EEE_ABLE) + ocp_addr = RTL8365MB_PHY_OCP_ADDR_EEE_ABLE; + else if (devad == MDIO_MMD_AN && regnum == MDIO_AN_EEE_ADV) + ocp_addr = RTL8365MB_PHY_OCP_ADDR_EEE_ADV; + else if (devad == MDIO_MMD_AN && regnum == MDIO_AN_EEE_LPABLE) + ocp_addr = RTL8365MB_PHY_OCP_ADDR_EEE_LPABLE; + else + /* Only the EEE registers are mapped; others read as 0, as the + * hardware does, so the generic MMD code is not tripped up by + * an error. + */ + return 0; + + ret = rtl8365mb_phy_ocp_read(priv, phy, ocp_addr, &val); + if (ret) { + dev_err(priv->dev, + "failed to read PHY%d OCP %04x, ret %d\n", phy, ocp_addr, + ret); + return ret; + } + + return val; +} + +static int rtl8365mb_phy_write_c45(struct realtek_priv *priv, int phy, + int devad, int regnum, u16 val) +{ + int ret; + + if (phy > RTL8365MB_PHYADDRMAX) + return -EINVAL; + + /* Only the EEE advertisement register is writable; writes to other + * registers are ignored, as the hardware does. + */ + if (devad != MDIO_MMD_AN || regnum != MDIO_AN_EEE_ADV) + return 0; + + ret = rtl8365mb_phy_ocp_write(priv, phy, RTL8365MB_PHY_OCP_ADDR_EEE_ADV, + val); + if (ret) { + dev_err(priv->dev, + "failed to write PHY%d OCP %04x, ret %d\n", phy, + RTL8365MB_PHY_OCP_ADDR_EEE_ADV, ret); + return ret; + } + + return 0; +} + static const struct rtl8365mb_extint * rtl8365mb_get_port_extint(struct realtek_priv *priv, int port) { @@ -1639,6 +1714,14 @@ static void rtl8365mb_phylink_get_caps(struct dsa_switch *ds, int port, */ __set_bit(PHY_INTERFACE_MODE_GMII, config->supported_interfaces); + + /* Integrated PHYs support EEE at 100M/1G; the hardware manages + * LPI on its own, so just advertise LPI awareness to phylink. + */ + memcpy(config->lpi_interfaces, config->supported_interfaces, + sizeof(config->lpi_interfaces)); + config->lpi_capabilities = MAC_100FD | MAC_1000FD; + config->eee_enabled_default = true; return; } @@ -3275,18 +3358,66 @@ static int rtl8365mb_detect(struct realtek_priv *priv) return 0; } +static int rtl8365mb_phylink_mac_enable_tx_lpi(struct phylink_config *config, + u32 timer, bool tx_clock_stop) +{ + /* The hardware manages LPI itself; there is no MAC-level LPI control. + * This callback only signals LPI awareness to phylink. + */ + return 0; +} + +static void rtl8365mb_phylink_mac_disable_tx_lpi(struct phylink_config *config) +{ +} + static const struct phylink_mac_ops rtl8365mb_phylink_mac_ops = { .mac_select_pcs = rtl8365mb_phylink_mac_select_pcs, .mac_config = rtl8365mb_phylink_mac_config, .mac_link_down = rtl8365mb_phylink_mac_link_down, .mac_link_up = rtl8365mb_phylink_mac_link_up, + .mac_enable_tx_lpi = rtl8365mb_phylink_mac_enable_tx_lpi, + .mac_disable_tx_lpi = rtl8365mb_phylink_mac_disable_tx_lpi, }; +static bool rtl8365mb_support_eee(struct dsa_switch *ds, int port) +{ + /* Only integrated-PHY ports support EEE, not the external RGMII ports. */ + return !rtl8365mb_get_port_extint(ds->priv, port); +} + +static int rtl8365mb_set_mac_eee(struct dsa_switch *ds, int port, + struct ethtool_keee *e) +{ + struct realtek_priv *priv = ds->priv; + + /* The only LPI timing control (tx_lpi_timer) is a single global + * per-speed register shared by all ports, so it cannot be set from this + * per-port callback; leave it at its reset default. Per-port EEE is + * driven through the PHY advertisement. Reject the per-port TX LPI knobs + * rather than silently ignoring them. + */ + if (!e->tx_lpi_enabled) { + dev_err(priv->dev, "disabling EEE TX LPI is not supported\n"); + return -EOPNOTSUPP; + } + + if (e->tx_lpi_timer) { + dev_err(priv->dev, + "setting the EEE TX LPI timer is not supported\n"); + return -EOPNOTSUPP; + } + + return 0; +} + static const struct dsa_switch_ops rtl8365mb_switch_ops = { .get_tag_protocol = rtl8365mb_get_tag_protocol, .change_tag_protocol = rtl8365mb_change_tag_protocol, .setup = rtl8365mb_setup, .teardown = rtl8365mb_teardown, + .support_eee = rtl8365mb_support_eee, + .set_mac_eee = rtl8365mb_set_mac_eee, .phylink_get_caps = rtl8365mb_phylink_get_caps, .port_bridge_join = rtl83xx_port_bridge_join, .port_bridge_leave = rtl83xx_port_bridge_leave, @@ -3332,6 +3463,8 @@ static const struct realtek_ops rtl8365mb_ops = { .l2_flush = rtl8365mb_l2_flush, .phy_read = rtl8365mb_phy_read, .phy_write = rtl8365mb_phy_write, + .phy_read_c45 = rtl8365mb_phy_read_c45, + .phy_write_c45 = rtl8365mb_phy_write_c45, }; static const char *const rtl8365mb_supplies[] = { diff --git a/drivers/net/dsa/realtek/rtl83xx.c b/drivers/net/dsa/realtek/rtl83xx.c index 8121ec12fd8b..ad42c05d0a88 100644 --- a/drivers/net/dsa/realtek/rtl83xx.c +++ b/drivers/net/dsa/realtek/rtl83xx.c @@ -62,6 +62,22 @@ static int rtl83xx_user_mdio_write(struct mii_bus *bus, int addr, int regnum, return priv->ops->phy_write(priv, addr, regnum, val); } +static int rtl83xx_user_mdio_read_c45(struct mii_bus *bus, int addr, int devad, + int regnum) +{ + struct realtek_priv *priv = bus->priv; + + return priv->ops->phy_read_c45(priv, addr, devad, regnum); +} + +static int rtl83xx_user_mdio_write_c45(struct mii_bus *bus, int addr, int devad, + int regnum, u16 val) +{ + struct realtek_priv *priv = bus->priv; + + return priv->ops->phy_write_c45(priv, addr, devad, regnum, val); +} + /** * rtl83xx_setup_user_mdio() - register the user mii bus driver * @ds: DSA switch associated with this user_mii_bus @@ -95,6 +111,10 @@ int rtl83xx_setup_user_mdio(struct dsa_switch *ds) bus->name = "Realtek user MII"; bus->read = rtl83xx_user_mdio_read; bus->write = rtl83xx_user_mdio_write; + if (priv->ops->phy_read_c45) + bus->read_c45 = rtl83xx_user_mdio_read_c45; + if (priv->ops->phy_write_c45) + bus->write_c45 = rtl83xx_user_mdio_write_c45; snprintf(bus->id, MII_BUS_ID_SIZE, "%s:user_mii", dev_name(priv->dev)); bus->parent = priv->dev; diff --git a/drivers/net/phy/realtek/realtek_main.c b/drivers/net/phy/realtek/realtek_main.c index 1e670638dd1c..3b3352e2cdc6 100644 --- a/drivers/net/phy/realtek/realtek_main.c +++ b/drivers/net/phy/realtek/realtek_main.c @@ -3349,6 +3349,8 @@ static struct phy_driver realtek_drvs[] = { }, { PHY_ID_MATCH_EXACT(0x001cc942), .name = "RTL8365MB-VC Gigabit Ethernet", + .read_mmd = genphy_read_mmd_c45, + .write_mmd = genphy_write_mmd_c45, /* Interrupt handling analogous to RTL8366RB */ .config_intr = genphy_no_config_intr, .handle_interrupt = genphy_handle_interrupt_no_ack,