diff options
author | Michael Walle <michael@walle.cc> | 2020-04-29 00:15:02 +0300 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2020-05-01 06:54:10 +0300 |
commit | cde0f4f81d1c11ccc214146e1c550bfe48629fac (patch) | |
tree | 9b4460d113b4360f80420f7c4f19af77b728cb8e /drivers/net/phy/at803x.c | |
parent | 838974e1e08a9724525fa13ef15d6e021c23c99d (diff) | |
download | linux-cde0f4f81d1c11ccc214146e1c550bfe48629fac.tar.xz |
net: phy: at803x: add downshift support
The AR8031 and AR8035 support the link speed downshift. Add driver
support for it. One peculiarity of these PHYs is that it needs a
software reset after changing the setting, thus add the .soft_reset()
op and do a phy_init_hw() if necessary.
This was tested on a custom board with the AR8031.
Signed-off-by: Michael Walle <michael@walle.cc>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'drivers/net/phy/at803x.c')
-rw-r--r-- | drivers/net/phy/at803x.c | 87 |
1 files changed, 87 insertions, 0 deletions
diff --git a/drivers/net/phy/at803x.c b/drivers/net/phy/at803x.c index 31b6edcc1fd1..f4fec5f644e9 100644 --- a/drivers/net/phy/at803x.c +++ b/drivers/net/phy/at803x.c @@ -43,6 +43,9 @@ #define AT803X_INTR_STATUS 0x13 #define AT803X_SMART_SPEED 0x14 +#define AT803X_SMART_SPEED_ENABLE BIT(5) +#define AT803X_SMART_SPEED_RETRY_LIMIT_MASK GENMASK(4, 2) +#define AT803X_SMART_SPEED_BYPASS_TIMER BIT(1) #define AT803X_LED_CONTROL 0x18 #define AT803X_DEVICE_ADDR 0x03 @@ -103,6 +106,10 @@ #define AT803X_CLK_OUT_STRENGTH_HALF 1 #define AT803X_CLK_OUT_STRENGTH_QUARTER 2 +#define AT803X_DEFAULT_DOWNSHIFT 5 +#define AT803X_MIN_DOWNSHIFT 2 +#define AT803X_MAX_DOWNSHIFT 9 + #define ATH9331_PHY_ID 0x004dd041 #define ATH8030_PHY_ID 0x004dd076 #define ATH8031_PHY_ID 0x004dd074 @@ -713,6 +720,80 @@ static int at803x_read_status(struct phy_device *phydev) return 0; } +static int at803x_get_downshift(struct phy_device *phydev, u8 *d) +{ + int val; + + val = phy_read(phydev, AT803X_SMART_SPEED); + if (val < 0) + return val; + + if (val & AT803X_SMART_SPEED_ENABLE) + *d = FIELD_GET(AT803X_SMART_SPEED_RETRY_LIMIT_MASK, val) + 2; + else + *d = DOWNSHIFT_DEV_DISABLE; + + return 0; +} + +static int at803x_set_downshift(struct phy_device *phydev, u8 cnt) +{ + u16 mask, set; + int ret; + + switch (cnt) { + case DOWNSHIFT_DEV_DEFAULT_COUNT: + cnt = AT803X_DEFAULT_DOWNSHIFT; + fallthrough; + case AT803X_MIN_DOWNSHIFT ... AT803X_MAX_DOWNSHIFT: + set = AT803X_SMART_SPEED_ENABLE | + AT803X_SMART_SPEED_BYPASS_TIMER | + FIELD_PREP(AT803X_SMART_SPEED_RETRY_LIMIT_MASK, cnt - 2); + mask = AT803X_SMART_SPEED_RETRY_LIMIT_MASK; + break; + case DOWNSHIFT_DEV_DISABLE: + set = 0; + mask = AT803X_SMART_SPEED_ENABLE | + AT803X_SMART_SPEED_BYPASS_TIMER; + break; + default: + return -EINVAL; + } + + ret = phy_modify_changed(phydev, AT803X_SMART_SPEED, mask, set); + + /* After changing the smart speed settings, we need to perform a + * software reset, use phy_init_hw() to make sure we set the + * reapply any values which might got lost during software reset. + */ + if (ret == 1) + ret = phy_init_hw(phydev); + + return ret; +} + +static int at803x_get_tunable(struct phy_device *phydev, + struct ethtool_tunable *tuna, void *data) +{ + switch (tuna->id) { + case ETHTOOL_PHY_DOWNSHIFT: + return at803x_get_downshift(phydev, data); + default: + return -EOPNOTSUPP; + } +} + +static int at803x_set_tunable(struct phy_device *phydev, + struct ethtool_tunable *tuna, const void *data) +{ + switch (tuna->id) { + case ETHTOOL_PHY_DOWNSHIFT: + return at803x_set_downshift(phydev, *(const u8 *)data); + default: + return -EOPNOTSUPP; + } +} + static struct phy_driver at803x_driver[] = { { /* Qualcomm Atheros AR8035 */ @@ -722,6 +803,7 @@ static struct phy_driver at803x_driver[] = { .probe = at803x_probe, .remove = at803x_remove, .config_init = at803x_config_init, + .soft_reset = genphy_soft_reset, .set_wol = at803x_set_wol, .get_wol = at803x_get_wol, .suspend = at803x_suspend, @@ -730,6 +812,8 @@ static struct phy_driver at803x_driver[] = { .read_status = at803x_read_status, .ack_interrupt = at803x_ack_interrupt, .config_intr = at803x_config_intr, + .get_tunable = at803x_get_tunable, + .set_tunable = at803x_set_tunable, }, { /* Qualcomm Atheros AR8030 */ .phy_id = ATH8030_PHY_ID, @@ -754,6 +838,7 @@ static struct phy_driver at803x_driver[] = { .probe = at803x_probe, .remove = at803x_remove, .config_init = at803x_config_init, + .soft_reset = genphy_soft_reset, .set_wol = at803x_set_wol, .get_wol = at803x_get_wol, .suspend = at803x_suspend, @@ -763,6 +848,8 @@ static struct phy_driver at803x_driver[] = { .aneg_done = at803x_aneg_done, .ack_interrupt = &at803x_ack_interrupt, .config_intr = &at803x_config_intr, + .get_tunable = at803x_get_tunable, + .set_tunable = at803x_set_tunable, }, { /* Qualcomm Atheros AR8032 */ PHY_ID_MATCH_EXACT(ATH8032_PHY_ID), |