diff options
Diffstat (limited to 'drivers/net/phy/dp83867.c')
-rw-r--r-- | drivers/net/phy/dp83867.c | 150 |
1 files changed, 150 insertions, 0 deletions
diff --git a/drivers/net/phy/dp83867.c b/drivers/net/phy/dp83867.c index 9a07ad137c2e..b55e3c0403ed 100644 --- a/drivers/net/phy/dp83867.c +++ b/drivers/net/phy/dp83867.c @@ -14,6 +14,7 @@ #include <linux/delay.h> #include <linux/netdevice.h> #include <linux/etherdevice.h> +#include <linux/bitfield.h> #include <dt-bindings/net/ti-dp83867.h> @@ -21,6 +22,7 @@ #define DP83867_DEVADDR 0x1f #define MII_DP83867_PHYCTRL 0x10 +#define MII_DP83867_PHYSTS 0x11 #define MII_DP83867_MICR 0x12 #define MII_DP83867_ISR 0x13 #define DP83867_CFG2 0x14 @@ -120,6 +122,24 @@ #define DP83867_IO_MUX_CFG_CLK_O_SEL_MASK (0x1f << 8) #define DP83867_IO_MUX_CFG_CLK_O_SEL_SHIFT 8 +/* PHY STS bits */ +#define DP83867_PHYSTS_1000 BIT(15) +#define DP83867_PHYSTS_100 BIT(14) +#define DP83867_PHYSTS_DUPLEX BIT(13) +#define DP83867_PHYSTS_LINK BIT(10) + +/* CFG2 bits */ +#define DP83867_DOWNSHIFT_EN (BIT(8) | BIT(9)) +#define DP83867_DOWNSHIFT_ATTEMPT_MASK (BIT(10) | BIT(11)) +#define DP83867_DOWNSHIFT_1_COUNT_VAL 0 +#define DP83867_DOWNSHIFT_2_COUNT_VAL 1 +#define DP83867_DOWNSHIFT_4_COUNT_VAL 2 +#define DP83867_DOWNSHIFT_8_COUNT_VAL 3 +#define DP83867_DOWNSHIFT_1_COUNT 1 +#define DP83867_DOWNSHIFT_2_COUNT 2 +#define DP83867_DOWNSHIFT_4_COUNT 4 +#define DP83867_DOWNSHIFT_8_COUNT 8 + /* CFG3 bits */ #define DP83867_CFG3_INT_OE BIT(7) #define DP83867_CFG3_ROBUST_AUTO_MDIX BIT(9) @@ -292,6 +312,126 @@ static int dp83867_config_intr(struct phy_device *phydev) return phy_write(phydev, MII_DP83867_MICR, micr_status); } +static int dp83867_read_status(struct phy_device *phydev) +{ + int status = phy_read(phydev, MII_DP83867_PHYSTS); + int ret; + + ret = genphy_read_status(phydev); + if (ret) + return ret; + + if (status < 0) + return status; + + if (status & DP83867_PHYSTS_DUPLEX) + phydev->duplex = DUPLEX_FULL; + else + phydev->duplex = DUPLEX_HALF; + + if (status & DP83867_PHYSTS_1000) + phydev->speed = SPEED_1000; + else if (status & DP83867_PHYSTS_100) + phydev->speed = SPEED_100; + else + phydev->speed = SPEED_10; + + return 0; +} + +static int dp83867_get_downshift(struct phy_device *phydev, u8 *data) +{ + int val, cnt, enable, count; + + val = phy_read(phydev, DP83867_CFG2); + if (val < 0) + return val; + + enable = FIELD_GET(DP83867_DOWNSHIFT_EN, val); + cnt = FIELD_GET(DP83867_DOWNSHIFT_ATTEMPT_MASK, val); + + switch (cnt) { + case DP83867_DOWNSHIFT_1_COUNT_VAL: + count = DP83867_DOWNSHIFT_1_COUNT; + break; + case DP83867_DOWNSHIFT_2_COUNT_VAL: + count = DP83867_DOWNSHIFT_2_COUNT; + break; + case DP83867_DOWNSHIFT_4_COUNT_VAL: + count = DP83867_DOWNSHIFT_4_COUNT; + break; + case DP83867_DOWNSHIFT_8_COUNT_VAL: + count = DP83867_DOWNSHIFT_8_COUNT; + break; + default: + return -EINVAL; + }; + + *data = enable ? count : DOWNSHIFT_DEV_DISABLE; + + return 0; +} + +static int dp83867_set_downshift(struct phy_device *phydev, u8 cnt) +{ + int val, count; + + if (cnt > DP83867_DOWNSHIFT_8_COUNT) + return -E2BIG; + + if (!cnt) + return phy_clear_bits(phydev, DP83867_CFG2, + DP83867_DOWNSHIFT_EN); + + switch (cnt) { + case DP83867_DOWNSHIFT_1_COUNT: + count = DP83867_DOWNSHIFT_1_COUNT_VAL; + break; + case DP83867_DOWNSHIFT_2_COUNT: + count = DP83867_DOWNSHIFT_2_COUNT_VAL; + break; + case DP83867_DOWNSHIFT_4_COUNT: + count = DP83867_DOWNSHIFT_4_COUNT_VAL; + break; + case DP83867_DOWNSHIFT_8_COUNT: + count = DP83867_DOWNSHIFT_8_COUNT_VAL; + break; + default: + phydev_err(phydev, + "Downshift count must be 1, 2, 4 or 8\n"); + return -EINVAL; + }; + + val = DP83867_DOWNSHIFT_EN; + val |= FIELD_PREP(DP83867_DOWNSHIFT_ATTEMPT_MASK, count); + + return phy_modify(phydev, DP83867_CFG2, + DP83867_DOWNSHIFT_EN | DP83867_DOWNSHIFT_ATTEMPT_MASK, + val); +} + +static int dp83867_get_tunable(struct phy_device *phydev, + struct ethtool_tunable *tuna, void *data) +{ + switch (tuna->id) { + case ETHTOOL_PHY_DOWNSHIFT: + return dp83867_get_downshift(phydev, data); + default: + return -EOPNOTSUPP; + } +} + +static int dp83867_set_tunable(struct phy_device *phydev, + struct ethtool_tunable *tuna, const void *data) +{ + switch (tuna->id) { + case ETHTOOL_PHY_DOWNSHIFT: + return dp83867_set_downshift(phydev, *(const u8 *)data); + default: + return -EOPNOTSUPP; + } +} + static int dp83867_config_port_mirroring(struct phy_device *phydev) { struct dp83867_private *dp83867 = @@ -472,6 +612,12 @@ static int dp83867_config_init(struct phy_device *phydev) int ret, val, bs; u16 delay; + /* Force speed optimization for the PHY even if it strapped */ + ret = phy_modify(phydev, DP83867_CFG2, DP83867_DOWNSHIFT_EN, + DP83867_DOWNSHIFT_EN); + if (ret) + return ret; + ret = dp83867_verify_rgmii_cfg(phydev); if (ret) return ret; @@ -674,6 +820,10 @@ static struct phy_driver dp83867_driver[] = { .config_init = dp83867_config_init, .soft_reset = dp83867_phy_reset, + .read_status = dp83867_read_status, + .get_tunable = dp83867_get_tunable, + .set_tunable = dp83867_set_tunable, + .get_wol = dp83867_get_wol, .set_wol = dp83867_set_wol, |