diff options
Diffstat (limited to 'drivers/net/phy/adin.c')
-rw-r--r-- | drivers/net/phy/adin.c | 86 |
1 files changed, 86 insertions, 0 deletions
diff --git a/drivers/net/phy/adin.c b/drivers/net/phy/adin.c index 5622a393e7cf..131b577a17e5 100644 --- a/drivers/net/phy/adin.c +++ b/drivers/net/phy/adin.c @@ -24,6 +24,17 @@ #define ADIN1300_AUTO_MDI_EN BIT(10) #define ADIN1300_MAN_MDIX_EN BIT(9) +#define ADIN1300_PHY_CTRL2 0x0016 +#define ADIN1300_DOWNSPEED_AN_100_EN BIT(11) +#define ADIN1300_DOWNSPEED_AN_10_EN BIT(10) +#define ADIN1300_GROUP_MDIO_EN BIT(6) +#define ADIN1300_DOWNSPEEDS_EN \ + (ADIN1300_DOWNSPEED_AN_100_EN | ADIN1300_DOWNSPEED_AN_10_EN) + +#define ADIN1300_PHY_CTRL3 0x0017 +#define ADIN1300_LINKING_EN BIT(13) +#define ADIN1300_DOWNSPEED_RETRIES_MSK GENMASK(12, 10) + #define ADIN1300_INT_MASK_REG 0x0018 #define ADIN1300_INT_MDIO_SYNC_EN BIT(9) #define ADIN1300_INT_ANEG_STAT_CHNG_EN BIT(8) @@ -243,6 +254,73 @@ static int adin_config_rmii_mode(struct phy_device *phydev) ADIN1300_GE_RMII_CFG_REG, reg); } +static int adin_get_downshift(struct phy_device *phydev, u8 *data) +{ + int val, cnt, enable; + + val = phy_read(phydev, ADIN1300_PHY_CTRL2); + if (val < 0) + return val; + + cnt = phy_read(phydev, ADIN1300_PHY_CTRL3); + if (cnt < 0) + return cnt; + + enable = FIELD_GET(ADIN1300_DOWNSPEEDS_EN, val); + cnt = FIELD_GET(ADIN1300_DOWNSPEED_RETRIES_MSK, cnt); + + *data = (enable && cnt) ? cnt : DOWNSHIFT_DEV_DISABLE; + + return 0; +} + +static int adin_set_downshift(struct phy_device *phydev, u8 cnt) +{ + u16 val; + int rc; + + if (cnt == DOWNSHIFT_DEV_DISABLE) + return phy_clear_bits(phydev, ADIN1300_PHY_CTRL2, + ADIN1300_DOWNSPEEDS_EN); + + if (cnt > 7) + return -E2BIG; + + val = FIELD_PREP(ADIN1300_DOWNSPEED_RETRIES_MSK, cnt); + val |= ADIN1300_LINKING_EN; + + rc = phy_modify(phydev, ADIN1300_PHY_CTRL3, + ADIN1300_LINKING_EN | ADIN1300_DOWNSPEED_RETRIES_MSK, + val); + if (rc < 0) + return rc; + + return phy_set_bits(phydev, ADIN1300_PHY_CTRL2, + ADIN1300_DOWNSPEEDS_EN); +} + +static int adin_get_tunable(struct phy_device *phydev, + struct ethtool_tunable *tuna, void *data) +{ + switch (tuna->id) { + case ETHTOOL_PHY_DOWNSHIFT: + return adin_get_downshift(phydev, data); + default: + return -EOPNOTSUPP; + } +} + +static int adin_set_tunable(struct phy_device *phydev, + struct ethtool_tunable *tuna, const void *data) +{ + switch (tuna->id) { + case ETHTOOL_PHY_DOWNSHIFT: + return adin_set_downshift(phydev, *(const u8 *)data); + default: + return -EOPNOTSUPP; + } +} + static int adin_config_init(struct phy_device *phydev) { int rc; @@ -261,6 +339,10 @@ static int adin_config_init(struct phy_device *phydev) if (rc < 0) return rc; + rc = adin_set_downshift(phydev, 4); + if (rc < 0) + return rc; + phydev_dbg(phydev, "PHY is using mode '%s'\n", phy_modes(phydev->interface)); @@ -474,6 +556,8 @@ static struct phy_driver adin_driver[] = { .soft_reset = adin_soft_reset, .config_aneg = adin_config_aneg, .read_status = adin_read_status, + .get_tunable = adin_get_tunable, + .set_tunable = adin_set_tunable, .ack_interrupt = adin_phy_ack_intr, .config_intr = adin_phy_config_intr, .resume = genphy_resume, @@ -488,6 +572,8 @@ static struct phy_driver adin_driver[] = { .soft_reset = adin_soft_reset, .config_aneg = adin_config_aneg, .read_status = adin_read_status, + .get_tunable = adin_get_tunable, + .set_tunable = adin_set_tunable, .ack_interrupt = adin_phy_ack_intr, .config_intr = adin_phy_config_intr, .resume = genphy_resume, |