summaryrefslogtreecommitdiffstats
path: root/drivers/net/phy/phy-core.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/phy/phy-core.c')
-rw-r--r--drivers/net/phy/phy-core.c134
1 files changed, 103 insertions, 31 deletions
diff --git a/drivers/net/phy/phy-core.c b/drivers/net/phy/phy-core.c
index 16667fbac8bf..a4d2d59fceca 100644
--- a/drivers/net/phy/phy-core.c
+++ b/drivers/net/phy/phy-core.c
@@ -8,7 +8,7 @@
const char *phy_speed_to_str(int speed)
{
- BUILD_BUG_ON_MSG(__ETHTOOL_LINK_MODE_MASK_NBITS != 69,
+ BUILD_BUG_ON_MSG(__ETHTOOL_LINK_MODE_MASK_NBITS != 74,
"Enum ethtool_link_mode_bit_indices and phylib are out of sync. "
"If a speed or mode has been added please update phy_speed_to_str "
"and the PHY settings array.\n");
@@ -42,6 +42,8 @@ const char *phy_speed_to_str(int speed)
return "100Gbps";
case SPEED_200000:
return "200Gbps";
+ case SPEED_400000:
+ return "400Gbps";
case SPEED_UNKNOWN:
return "Unknown";
default:
@@ -70,6 +72,12 @@ EXPORT_SYMBOL_GPL(phy_duplex_to_str);
.bit = ETHTOOL_LINK_MODE_ ## b ## _BIT}
static const struct phy_setting settings[] = {
+ /* 400G */
+ PHY_SETTING( 400000, FULL, 400000baseCR8_Full ),
+ PHY_SETTING( 400000, FULL, 400000baseKR8_Full ),
+ PHY_SETTING( 400000, FULL, 400000baseLR8_ER8_FR8_Full ),
+ PHY_SETTING( 400000, FULL, 400000baseDR8_Full ),
+ PHY_SETTING( 400000, FULL, 400000baseSR8_Full ),
/* 200G */
PHY_SETTING( 200000, FULL, 200000baseCR4_Full ),
PHY_SETTING( 200000, FULL, 200000baseKR4_Full ),
@@ -207,14 +215,14 @@ size_t phy_speeds(unsigned int *speeds, size_t size,
return count;
}
-static int __set_phy_supported(struct phy_device *phydev, u32 max_speed)
+static int __set_linkmode_max_speed(u32 max_speed, unsigned long *addr)
{
const struct phy_setting *p;
int i;
for (i = 0, p = settings; i < ARRAY_SIZE(settings); i++, p++) {
if (p->speed > max_speed)
- linkmode_clear_bit(p->bit, phydev->supported);
+ linkmode_clear_bit(p->bit, addr);
else
break;
}
@@ -222,6 +230,11 @@ static int __set_phy_supported(struct phy_device *phydev, u32 max_speed)
return 0;
}
+static int __set_phy_supported(struct phy_device *phydev, u32 max_speed)
+{
+ return __set_linkmode_max_speed(max_speed, phydev->supported);
+}
+
int phy_set_max_speed(struct phy_device *phydev, u32 max_speed)
{
int err;
@@ -278,6 +291,18 @@ void of_set_phy_eee_broken(struct phy_device *phydev)
phydev->eee_broken_modes = broken;
}
+void phy_resolve_aneg_pause(struct phy_device *phydev)
+{
+ if (phydev->duplex == DUPLEX_FULL) {
+ phydev->pause = linkmode_test_bit(ETHTOOL_LINK_MODE_Pause_BIT,
+ phydev->lp_advertising);
+ phydev->asym_pause = linkmode_test_bit(
+ ETHTOOL_LINK_MODE_Asym_Pause_BIT,
+ phydev->lp_advertising);
+ }
+}
+EXPORT_SYMBOL_GPL(phy_resolve_aneg_pause);
+
/**
* phy_resolve_aneg_linkmode - resolve the advertisements into phy settings
* @phydev: The phy_device struct
@@ -300,16 +325,38 @@ void phy_resolve_aneg_linkmode(struct phy_device *phydev)
break;
}
- if (phydev->duplex == DUPLEX_FULL) {
- phydev->pause = linkmode_test_bit(ETHTOOL_LINK_MODE_Pause_BIT,
- phydev->lp_advertising);
- phydev->asym_pause = linkmode_test_bit(
- ETHTOOL_LINK_MODE_Asym_Pause_BIT,
- phydev->lp_advertising);
- }
+ phy_resolve_aneg_pause(phydev);
}
EXPORT_SYMBOL_GPL(phy_resolve_aneg_linkmode);
+static int phy_resolve_min_speed(struct phy_device *phydev, bool fdx_only)
+{
+ __ETHTOOL_DECLARE_LINK_MODE_MASK(common);
+ int i = ARRAY_SIZE(settings);
+
+ linkmode_and(common, phydev->lp_advertising, phydev->advertising);
+
+ while (--i >= 0) {
+ if (test_bit(settings[i].bit, common)) {
+ if (fdx_only && settings[i].duplex != DUPLEX_FULL)
+ continue;
+ return settings[i].speed;
+ }
+ }
+
+ return SPEED_UNKNOWN;
+}
+
+int phy_speed_down_core(struct phy_device *phydev)
+{
+ int min_common_speed = phy_resolve_min_speed(phydev, true);
+
+ if (min_common_speed == SPEED_UNKNOWN)
+ return -EINVAL;
+
+ return __set_linkmode_max_speed(min_common_speed, phydev->advertising);
+}
+
static void mmd_phy_indirect(struct mii_bus *bus, int phy_addr, int devad,
u16 regnum)
{
@@ -340,7 +387,7 @@ int __phy_read_mmd(struct phy_device *phydev, int devad, u32 regnum)
if (regnum > (u16)~0 || devad > 32)
return -EINVAL;
- if (phydev->drv->read_mmd) {
+ if (phydev->drv && phydev->drv->read_mmd) {
val = phydev->drv->read_mmd(phydev, devad, regnum);
} else if (phydev->is_c45) {
u32 addr = MII_ADDR_C45 | (devad << 16) | (regnum & 0xffff);
@@ -372,9 +419,9 @@ int phy_read_mmd(struct phy_device *phydev, int devad, u32 regnum)
{
int ret;
- mutex_lock(&phydev->mdio.bus->mdio_lock);
+ phy_lock_mdio_bus(phydev);
ret = __phy_read_mmd(phydev, devad, regnum);
- mutex_unlock(&phydev->mdio.bus->mdio_lock);
+ phy_unlock_mdio_bus(phydev);
return ret;
}
@@ -397,7 +444,7 @@ int __phy_write_mmd(struct phy_device *phydev, int devad, u32 regnum, u16 val)
if (regnum > (u16)~0 || devad > 32)
return -EINVAL;
- if (phydev->drv->write_mmd) {
+ if (phydev->drv && phydev->drv->write_mmd) {
ret = phydev->drv->write_mmd(phydev, devad, regnum, val);
} else if (phydev->is_c45) {
u32 addr = MII_ADDR_C45 | (devad << 16) | (regnum & 0xffff);
@@ -433,9 +480,9 @@ int phy_write_mmd(struct phy_device *phydev, int devad, u32 regnum, u16 val)
{
int ret;
- mutex_lock(&phydev->mdio.bus->mdio_lock);
+ phy_lock_mdio_bus(phydev);
ret = __phy_write_mmd(phydev, devad, regnum, val);
- mutex_unlock(&phydev->mdio.bus->mdio_lock);
+ phy_unlock_mdio_bus(phydev);
return ret;
}
@@ -489,9 +536,9 @@ int phy_modify_changed(struct phy_device *phydev, u32 regnum, u16 mask, u16 set)
{
int ret;
- mutex_lock(&phydev->mdio.bus->mdio_lock);
+ phy_lock_mdio_bus(phydev);
ret = __phy_modify_changed(phydev, regnum, mask, set);
- mutex_unlock(&phydev->mdio.bus->mdio_lock);
+ phy_unlock_mdio_bus(phydev);
return ret;
}
@@ -533,9 +580,9 @@ int phy_modify(struct phy_device *phydev, u32 regnum, u16 mask, u16 set)
{
int ret;
- mutex_lock(&phydev->mdio.bus->mdio_lock);
+ phy_lock_mdio_bus(phydev);
ret = __phy_modify(phydev, regnum, mask, set);
- mutex_unlock(&phydev->mdio.bus->mdio_lock);
+ phy_unlock_mdio_bus(phydev);
return ret;
}
@@ -592,9 +639,9 @@ int phy_modify_mmd_changed(struct phy_device *phydev, int devad, u32 regnum,
{
int ret;
- mutex_lock(&phydev->mdio.bus->mdio_lock);
+ phy_lock_mdio_bus(phydev);
ret = __phy_modify_mmd_changed(phydev, devad, regnum, mask, set);
- mutex_unlock(&phydev->mdio.bus->mdio_lock);
+ phy_unlock_mdio_bus(phydev);
return ret;
}
@@ -640,9 +687,9 @@ int phy_modify_mmd(struct phy_device *phydev, int devad, u32 regnum,
{
int ret;
- mutex_lock(&phydev->mdio.bus->mdio_lock);
+ phy_lock_mdio_bus(phydev);
ret = __phy_modify_mmd(phydev, devad, regnum, mask, set);
- mutex_unlock(&phydev->mdio.bus->mdio_lock);
+ phy_unlock_mdio_bus(phydev);
return ret;
}
@@ -650,11 +697,17 @@ EXPORT_SYMBOL_GPL(phy_modify_mmd);
static int __phy_read_page(struct phy_device *phydev)
{
+ if (WARN_ONCE(!phydev->drv->read_page, "read_page callback not available, PHY driver not loaded?\n"))
+ return -EOPNOTSUPP;
+
return phydev->drv->read_page(phydev);
}
static int __phy_write_page(struct phy_device *phydev, int page)
{
+ if (WARN_ONCE(!phydev->drv->write_page, "write_page callback not available, PHY driver not loaded?\n"))
+ return -EOPNOTSUPP;
+
return phydev->drv->write_page(phydev, page);
}
@@ -668,7 +721,7 @@ static int __phy_write_page(struct phy_device *phydev, int page)
*/
int phy_save_page(struct phy_device *phydev)
{
- mutex_lock(&phydev->mdio.bus->mdio_lock);
+ phy_lock_mdio_bus(phydev);
return __phy_read_page(phydev);
}
EXPORT_SYMBOL_GPL(phy_save_page);
@@ -735,7 +788,7 @@ int phy_restore_page(struct phy_device *phydev, int oldpage, int ret)
ret = oldpage;
}
- mutex_unlock(&phydev->mdio.bus->mdio_lock);
+ phy_unlock_mdio_bus(phydev);
return ret;
}
@@ -783,24 +836,43 @@ int phy_write_paged(struct phy_device *phydev, int page, u32 regnum, u16 val)
EXPORT_SYMBOL(phy_write_paged);
/**
- * phy_modify_paged() - Convenience function for modifying a paged register
+ * phy_modify_paged_changed() - Function for modifying a paged register
* @phydev: a pointer to a &struct phy_device
* @page: the page for the phy
* @regnum: register number
* @mask: bit mask of bits to clear
* @set: bit mask of bits to set
*
- * Same rules as for phy_read() and phy_write().
+ * Returns negative errno, 0 if there was no change, and 1 in case of change
*/
-int phy_modify_paged(struct phy_device *phydev, int page, u32 regnum,
- u16 mask, u16 set)
+int phy_modify_paged_changed(struct phy_device *phydev, int page, u32 regnum,
+ u16 mask, u16 set)
{
int ret = 0, oldpage;
oldpage = phy_select_page(phydev, page);
if (oldpage >= 0)
- ret = __phy_modify(phydev, regnum, mask, set);
+ ret = __phy_modify_changed(phydev, regnum, mask, set);
return phy_restore_page(phydev, oldpage, ret);
}
+EXPORT_SYMBOL(phy_modify_paged_changed);
+
+/**
+ * phy_modify_paged() - Convenience function for modifying a paged register
+ * @phydev: a pointer to a &struct phy_device
+ * @page: the page for the phy
+ * @regnum: register number
+ * @mask: bit mask of bits to clear
+ * @set: bit mask of bits to set
+ *
+ * Same rules as for phy_read() and phy_write().
+ */
+int phy_modify_paged(struct phy_device *phydev, int page, u32 regnum,
+ u16 mask, u16 set)
+{
+ int ret = phy_modify_paged_changed(phydev, page, regnum, mask, set);
+
+ return ret < 0 ? ret : 0;
+}
EXPORT_SYMBOL(phy_modify_paged);
OpenPOWER on IntegriCloud