Commit d3c19c0a authored by David S. Miller's avatar David S. Miller

Merge branch 'ethtool-phy-downshift'

Allan W. Nielsen says:

====================
Adding PHY-Tunables and downshift support

(This is a re-post of the v3 patch set with a new cover letter - I was not
aware that the cover letters was used a commit comments in merge commits).

This series add support for PHY tunables, and uses this facility to
configure downshifting. The downshifting mechanism is implemented for MSCC
phys.

This series tries to address the comments provided back in mid October when
this feature was posted along with fast-link-failure. Fast-link-failure has
been separated out, but we would like to pick continue on that if/when we
agree on how the phy-tunables and downshifting should be done.

The proposed generic interface is similar to
ETHTOOL_GTUNABLE/ETHTOOL_STUNABLE, it uses the same type
(ethtool_tunable/tunable_type_id) but a new enum (phy_tunable_id) is added
to reflect the PHY tunable.

The implementation just call the newly added function pointers in
get_tunable/set_tunable phy_device structure.

To configure downshifting, the ethtool_tunable structure is used. 'id' must
be set to 'ETHTOOL_PHY_DOWNSHIFT', 'type_id' must be set to
'ETHTOOL_TUNABLE_U8' and 'data' value configure the amount of downshift
re-tries.

If configured to DOWNSHIFT_DEV_DISABLE, then downshift is disabled If
configured to DOWNSHIFT_DEV_DEFAULT_COUNT, then it is up to the device to
choose a device-specific re-try count.

Tested on Beaglebone Black with VSC 8531 PHY.

Change set:
v0:

- Link Speed downshift and Fast Link failure-2 features coded by using
  Device tree.
v1:
- Split the Downshift and FLF2 features in different set of patches.
- Removed DT access and implemented IOCTL access suggested by Andrew.
- Added function pointers in get_tunable/set_tunable phy_device structure
v2:
- Added trace message with a hist is printed when downshifting clould not
  be eanbled with the requested count
- (ethtool) Syntax is changed from "--set-phy-tunable downshift on|off|%d"
  to "--set-phy-tunable [downshift on|off [count N]]" - as requested by
  Andrew.
v3:
- Fixed Spelling in "net: phy: Add downshift get/set support in Microsemi
  PHYs driver"
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 511d5d5b 310d9ad5
...@@ -46,8 +46,15 @@ enum rgmii_rx_clock_delay { ...@@ -46,8 +46,15 @@ enum rgmii_rx_clock_delay {
#define MSCC_EXT_PAGE_ACCESS 31 #define MSCC_EXT_PAGE_ACCESS 31
#define MSCC_PHY_PAGE_STANDARD 0x0000 /* Standard registers */ #define MSCC_PHY_PAGE_STANDARD 0x0000 /* Standard registers */
#define MSCC_PHY_PAGE_EXTENDED 0x0001 /* Extended registers */
#define MSCC_PHY_PAGE_EXTENDED_2 0x0002 /* Extended reg - page 2 */ #define MSCC_PHY_PAGE_EXTENDED_2 0x0002 /* Extended reg - page 2 */
/* Extended Page 1 Registers */
#define MSCC_PHY_ACTIPHY_CNTL 20
#define DOWNSHIFT_CNTL_MASK 0x001C
#define DOWNSHIFT_EN 0x0010
#define DOWNSHIFT_CNTL_POS 2
/* Extended Page 2 Registers */ /* Extended Page 2 Registers */
#define MSCC_PHY_RGMII_CNTL 20 #define MSCC_PHY_RGMII_CNTL 20
#define RGMII_RX_CLK_DELAY_MASK 0x0070 #define RGMII_RX_CLK_DELAY_MASK 0x0070
...@@ -75,6 +82,8 @@ enum rgmii_rx_clock_delay { ...@@ -75,6 +82,8 @@ enum rgmii_rx_clock_delay {
#define MSCC_VDDMAC_2500 2500 #define MSCC_VDDMAC_2500 2500
#define MSCC_VDDMAC_3300 3300 #define MSCC_VDDMAC_3300 3300
#define DOWNSHIFT_COUNT_MAX 5
struct vsc8531_private { struct vsc8531_private {
int rate_magic; int rate_magic;
}; };
...@@ -101,6 +110,66 @@ static int vsc85xx_phy_page_set(struct phy_device *phydev, u8 page) ...@@ -101,6 +110,66 @@ static int vsc85xx_phy_page_set(struct phy_device *phydev, u8 page)
return rc; return rc;
} }
static int vsc85xx_downshift_get(struct phy_device *phydev, u8 *count)
{
int rc;
u16 reg_val;
mutex_lock(&phydev->lock);
rc = vsc85xx_phy_page_set(phydev, MSCC_PHY_PAGE_EXTENDED);
if (rc != 0)
goto out_unlock;
reg_val = phy_read(phydev, MSCC_PHY_ACTIPHY_CNTL);
reg_val &= DOWNSHIFT_CNTL_MASK;
if (!(reg_val & DOWNSHIFT_EN))
*count = DOWNSHIFT_DEV_DISABLE;
else
*count = ((reg_val & ~DOWNSHIFT_EN) >> DOWNSHIFT_CNTL_POS) + 2;
rc = vsc85xx_phy_page_set(phydev, MSCC_PHY_PAGE_STANDARD);
out_unlock:
mutex_unlock(&phydev->lock);
return rc;
}
static int vsc85xx_downshift_set(struct phy_device *phydev, u8 count)
{
int rc;
u16 reg_val;
if (count == DOWNSHIFT_DEV_DEFAULT_COUNT) {
/* Default downshift count 3 (i.e. Bit3:2 = 0b01) */
count = ((1 << DOWNSHIFT_CNTL_POS) | DOWNSHIFT_EN);
} else if (count > DOWNSHIFT_COUNT_MAX || count == 1) {
phydev_err(phydev, "Downshift count should be 2,3,4 or 5\n");
return -ERANGE;
} else if (count) {
/* Downshift count is either 2,3,4 or 5 */
count = (((count - 2) << DOWNSHIFT_CNTL_POS) | DOWNSHIFT_EN);
}
mutex_lock(&phydev->lock);
rc = vsc85xx_phy_page_set(phydev, MSCC_PHY_PAGE_EXTENDED);
if (rc != 0)
goto out_unlock;
reg_val = phy_read(phydev, MSCC_PHY_ACTIPHY_CNTL);
reg_val &= ~(DOWNSHIFT_CNTL_MASK);
reg_val |= count;
rc = phy_write(phydev, MSCC_PHY_ACTIPHY_CNTL, reg_val);
if (rc != 0)
goto out_unlock;
rc = vsc85xx_phy_page_set(phydev, MSCC_PHY_PAGE_STANDARD);
out_unlock:
mutex_unlock(&phydev->lock);
return rc;
}
static int vsc85xx_wol_set(struct phy_device *phydev, static int vsc85xx_wol_set(struct phy_device *phydev,
struct ethtool_wolinfo *wol) struct ethtool_wolinfo *wol)
{ {
...@@ -329,6 +398,29 @@ static int vsc85xx_default_config(struct phy_device *phydev) ...@@ -329,6 +398,29 @@ static int vsc85xx_default_config(struct phy_device *phydev)
return rc; return rc;
} }
static int vsc85xx_get_tunable(struct phy_device *phydev,
struct ethtool_tunable *tuna, void *data)
{
switch (tuna->id) {
case ETHTOOL_PHY_DOWNSHIFT:
return vsc85xx_downshift_get(phydev, (u8 *)data);
default:
return -EINVAL;
}
}
static int vsc85xx_set_tunable(struct phy_device *phydev,
struct ethtool_tunable *tuna,
const void *data)
{
switch (tuna->id) {
case ETHTOOL_PHY_DOWNSHIFT:
return vsc85xx_downshift_set(phydev, *(u8 *)data);
default:
return -EINVAL;
}
}
static int vsc85xx_config_init(struct phy_device *phydev) static int vsc85xx_config_init(struct phy_device *phydev)
{ {
int rc; int rc;
...@@ -418,6 +510,8 @@ static struct phy_driver vsc85xx_driver[] = { ...@@ -418,6 +510,8 @@ static struct phy_driver vsc85xx_driver[] = {
.probe = &vsc85xx_probe, .probe = &vsc85xx_probe,
.set_wol = &vsc85xx_wol_set, .set_wol = &vsc85xx_wol_set,
.get_wol = &vsc85xx_wol_get, .get_wol = &vsc85xx_wol_get,
.get_tunable = &vsc85xx_get_tunable,
.set_tunable = &vsc85xx_set_tunable,
}, },
{ {
.phy_id = PHY_ID_VSC8531, .phy_id = PHY_ID_VSC8531,
...@@ -437,6 +531,8 @@ static struct phy_driver vsc85xx_driver[] = { ...@@ -437,6 +531,8 @@ static struct phy_driver vsc85xx_driver[] = {
.probe = &vsc85xx_probe, .probe = &vsc85xx_probe,
.set_wol = &vsc85xx_wol_set, .set_wol = &vsc85xx_wol_set,
.get_wol = &vsc85xx_wol_get, .get_wol = &vsc85xx_wol_get,
.get_tunable = &vsc85xx_get_tunable,
.set_tunable = &vsc85xx_set_tunable,
}, },
{ {
.phy_id = PHY_ID_VSC8540, .phy_id = PHY_ID_VSC8540,
...@@ -456,6 +552,8 @@ static struct phy_driver vsc85xx_driver[] = { ...@@ -456,6 +552,8 @@ static struct phy_driver vsc85xx_driver[] = {
.probe = &vsc85xx_probe, .probe = &vsc85xx_probe,
.set_wol = &vsc85xx_wol_set, .set_wol = &vsc85xx_wol_set,
.get_wol = &vsc85xx_wol_get, .get_wol = &vsc85xx_wol_get,
.get_tunable = &vsc85xx_get_tunable,
.set_tunable = &vsc85xx_set_tunable,
}, },
{ {
.phy_id = PHY_ID_VSC8541, .phy_id = PHY_ID_VSC8541,
...@@ -475,6 +573,8 @@ static struct phy_driver vsc85xx_driver[] = { ...@@ -475,6 +573,8 @@ static struct phy_driver vsc85xx_driver[] = {
.probe = &vsc85xx_probe, .probe = &vsc85xx_probe,
.set_wol = &vsc85xx_wol_set, .set_wol = &vsc85xx_wol_set,
.get_wol = &vsc85xx_wol_get, .get_wol = &vsc85xx_wol_get,
.get_tunable = &vsc85xx_get_tunable,
.set_tunable = &vsc85xx_set_tunable,
} }
}; };
......
...@@ -611,6 +611,13 @@ struct phy_driver { ...@@ -611,6 +611,13 @@ struct phy_driver {
void (*get_strings)(struct phy_device *dev, u8 *data); void (*get_strings)(struct phy_device *dev, u8 *data);
void (*get_stats)(struct phy_device *dev, void (*get_stats)(struct phy_device *dev,
struct ethtool_stats *stats, u64 *data); struct ethtool_stats *stats, u64 *data);
/* Get and Set PHY tunables */
int (*get_tunable)(struct phy_device *dev,
struct ethtool_tunable *tuna, void *data);
int (*set_tunable)(struct phy_device *dev,
struct ethtool_tunable *tuna,
const void *data);
}; };
#define to_phy_driver(d) container_of(to_mdio_common_driver(d), \ #define to_phy_driver(d) container_of(to_mdio_common_driver(d), \
struct phy_driver, mdiodrv) struct phy_driver, mdiodrv)
......
...@@ -248,6 +248,19 @@ struct ethtool_tunable { ...@@ -248,6 +248,19 @@ struct ethtool_tunable {
void *data[0]; void *data[0];
}; };
#define DOWNSHIFT_DEV_DEFAULT_COUNT 0xff
#define DOWNSHIFT_DEV_DISABLE 0
enum phy_tunable_id {
ETHTOOL_PHY_ID_UNSPEC,
ETHTOOL_PHY_DOWNSHIFT,
/*
* Add your fresh new phy tunable attribute above and remember to update
* phy_tunable_strings[] in net/core/ethtool.c
*/
__ETHTOOL_PHY_TUNABLE_COUNT,
};
/** /**
* struct ethtool_regs - hardware register dump * struct ethtool_regs - hardware register dump
* @cmd: Command number = %ETHTOOL_GREGS * @cmd: Command number = %ETHTOOL_GREGS
...@@ -548,6 +561,7 @@ struct ethtool_pauseparam { ...@@ -548,6 +561,7 @@ struct ethtool_pauseparam {
* @ETH_SS_FEATURES: Device feature names * @ETH_SS_FEATURES: Device feature names
* @ETH_SS_RSS_HASH_FUNCS: RSS hush function names * @ETH_SS_RSS_HASH_FUNCS: RSS hush function names
* @ETH_SS_PHY_STATS: Statistic names, for use with %ETHTOOL_GPHYSTATS * @ETH_SS_PHY_STATS: Statistic names, for use with %ETHTOOL_GPHYSTATS
* @ETH_SS_PHY_TUNABLES: PHY tunable names
*/ */
enum ethtool_stringset { enum ethtool_stringset {
ETH_SS_TEST = 0, ETH_SS_TEST = 0,
...@@ -558,6 +572,7 @@ enum ethtool_stringset { ...@@ -558,6 +572,7 @@ enum ethtool_stringset {
ETH_SS_RSS_HASH_FUNCS, ETH_SS_RSS_HASH_FUNCS,
ETH_SS_TUNABLES, ETH_SS_TUNABLES,
ETH_SS_PHY_STATS, ETH_SS_PHY_STATS,
ETH_SS_PHY_TUNABLES,
}; };
/** /**
...@@ -1313,7 +1328,8 @@ struct ethtool_per_queue_op { ...@@ -1313,7 +1328,8 @@ struct ethtool_per_queue_op {
#define ETHTOOL_GLINKSETTINGS 0x0000004c /* Get ethtool_link_settings */ #define ETHTOOL_GLINKSETTINGS 0x0000004c /* Get ethtool_link_settings */
#define ETHTOOL_SLINKSETTINGS 0x0000004d /* Set ethtool_link_settings */ #define ETHTOOL_SLINKSETTINGS 0x0000004d /* Set ethtool_link_settings */
#define ETHTOOL_PHY_GTUNABLE 0x0000004e /* Get PHY tunable configuration */
#define ETHTOOL_PHY_STUNABLE 0x0000004f /* Set PHY tunable configuration */
/* compatibility with older code */ /* compatibility with older code */
#define SPARC_ETH_GSET ETHTOOL_GSET #define SPARC_ETH_GSET ETHTOOL_GSET
......
...@@ -119,6 +119,12 @@ tunable_strings[__ETHTOOL_TUNABLE_COUNT][ETH_GSTRING_LEN] = { ...@@ -119,6 +119,12 @@ tunable_strings[__ETHTOOL_TUNABLE_COUNT][ETH_GSTRING_LEN] = {
[ETHTOOL_TX_COPYBREAK] = "tx-copybreak", [ETHTOOL_TX_COPYBREAK] = "tx-copybreak",
}; };
static const char
phy_tunable_strings[__ETHTOOL_PHY_TUNABLE_COUNT][ETH_GSTRING_LEN] = {
[ETHTOOL_ID_UNSPEC] = "Unspec",
[ETHTOOL_PHY_DOWNSHIFT] = "phy-downshift",
};
static int ethtool_get_features(struct net_device *dev, void __user *useraddr) static int ethtool_get_features(struct net_device *dev, void __user *useraddr)
{ {
struct ethtool_gfeatures cmd = { struct ethtool_gfeatures cmd = {
...@@ -227,6 +233,9 @@ static int __ethtool_get_sset_count(struct net_device *dev, int sset) ...@@ -227,6 +233,9 @@ static int __ethtool_get_sset_count(struct net_device *dev, int sset)
if (sset == ETH_SS_TUNABLES) if (sset == ETH_SS_TUNABLES)
return ARRAY_SIZE(tunable_strings); return ARRAY_SIZE(tunable_strings);
if (sset == ETH_SS_PHY_TUNABLES)
return ARRAY_SIZE(phy_tunable_strings);
if (sset == ETH_SS_PHY_STATS) { if (sset == ETH_SS_PHY_STATS) {
if (dev->phydev) if (dev->phydev)
return phy_get_sset_count(dev->phydev); return phy_get_sset_count(dev->phydev);
...@@ -253,6 +262,8 @@ static void __ethtool_get_strings(struct net_device *dev, ...@@ -253,6 +262,8 @@ static void __ethtool_get_strings(struct net_device *dev,
sizeof(rss_hash_func_strings)); sizeof(rss_hash_func_strings));
else if (stringset == ETH_SS_TUNABLES) else if (stringset == ETH_SS_TUNABLES)
memcpy(data, tunable_strings, sizeof(tunable_strings)); memcpy(data, tunable_strings, sizeof(tunable_strings));
else if (stringset == ETH_SS_PHY_TUNABLES)
memcpy(data, phy_tunable_strings, sizeof(phy_tunable_strings));
else if (stringset == ETH_SS_PHY_STATS) { else if (stringset == ETH_SS_PHY_STATS) {
struct phy_device *phydev = dev->phydev; struct phy_device *phydev = dev->phydev;
...@@ -2422,6 +2433,81 @@ static int ethtool_set_per_queue(struct net_device *dev, void __user *useraddr) ...@@ -2422,6 +2433,81 @@ static int ethtool_set_per_queue(struct net_device *dev, void __user *useraddr)
}; };
} }
static int ethtool_phy_tunable_valid(const struct ethtool_tunable *tuna)
{
switch (tuna->id) {
case ETHTOOL_PHY_DOWNSHIFT:
if (tuna->len != sizeof(u8) ||
tuna->type_id != ETHTOOL_TUNABLE_U8)
return -EINVAL;
break;
default:
return -EINVAL;
}
return 0;
}
static int get_phy_tunable(struct net_device *dev, void __user *useraddr)
{
int ret;
struct ethtool_tunable tuna;
struct phy_device *phydev = dev->phydev;
void *data;
if (!(phydev && phydev->drv && phydev->drv->get_tunable))
return -EOPNOTSUPP;
if (copy_from_user(&tuna, useraddr, sizeof(tuna)))
return -EFAULT;
ret = ethtool_phy_tunable_valid(&tuna);
if (ret)
return ret;
data = kmalloc(tuna.len, GFP_USER);
if (!data)
return -ENOMEM;
ret = phydev->drv->get_tunable(phydev, &tuna, data);
if (ret)
goto out;
useraddr += sizeof(tuna);
ret = -EFAULT;
if (copy_to_user(useraddr, data, tuna.len))
goto out;
ret = 0;
out:
kfree(data);
return ret;
}
static int set_phy_tunable(struct net_device *dev, void __user *useraddr)
{
int ret;
struct ethtool_tunable tuna;
struct phy_device *phydev = dev->phydev;
void *data;
if (!(phydev && phydev->drv && phydev->drv->set_tunable))
return -EOPNOTSUPP;
if (copy_from_user(&tuna, useraddr, sizeof(tuna)))
return -EFAULT;
ret = ethtool_phy_tunable_valid(&tuna);
if (ret)
return ret;
data = kmalloc(tuna.len, GFP_USER);
if (!data)
return -ENOMEM;
useraddr += sizeof(tuna);
ret = -EFAULT;
if (copy_from_user(data, useraddr, tuna.len))
goto out;
ret = phydev->drv->set_tunable(phydev, &tuna, data);
out:
kfree(data);
return ret;
}
/* The main entry point in this file. Called from net/core/dev_ioctl.c */ /* The main entry point in this file. Called from net/core/dev_ioctl.c */
int dev_ethtool(struct net *net, struct ifreq *ifr) int dev_ethtool(struct net *net, struct ifreq *ifr)
...@@ -2479,6 +2565,7 @@ int dev_ethtool(struct net *net, struct ifreq *ifr) ...@@ -2479,6 +2565,7 @@ int dev_ethtool(struct net *net, struct ifreq *ifr)
case ETHTOOL_GET_TS_INFO: case ETHTOOL_GET_TS_INFO:
case ETHTOOL_GEEE: case ETHTOOL_GEEE:
case ETHTOOL_GTUNABLE: case ETHTOOL_GTUNABLE:
case ETHTOOL_PHY_GTUNABLE:
break; break;
default: default:
if (!ns_capable(net->user_ns, CAP_NET_ADMIN)) if (!ns_capable(net->user_ns, CAP_NET_ADMIN))
...@@ -2684,6 +2771,12 @@ int dev_ethtool(struct net *net, struct ifreq *ifr) ...@@ -2684,6 +2771,12 @@ int dev_ethtool(struct net *net, struct ifreq *ifr)
case ETHTOOL_SLINKSETTINGS: case ETHTOOL_SLINKSETTINGS:
rc = ethtool_set_link_ksettings(dev, useraddr); rc = ethtool_set_link_ksettings(dev, useraddr);
break; break;
case ETHTOOL_PHY_GTUNABLE:
rc = get_phy_tunable(dev, useraddr);
break;
case ETHTOOL_PHY_STUNABLE:
rc = set_phy_tunable(dev, useraddr);
break;
default: default:
rc = -EOPNOTSUPP; rc = -EOPNOTSUPP;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment