From e29df9b0e7670eb4382379a58fa99eb263ee1ebc Mon Sep 17 00:00:00 2001 From: Russell King Date: Sat, 3 Oct 2015 09:13:05 +0100 Subject: cpuidle: mvebu: indicate failure to enter deeper sleep states The cpuidle ->enter method expects the return value to be the sleep state we entered. Returning negative numbers or other codes is not permissible since coupled CPU idle was merged. At least some of the mvebu_v7_cpu_suspend() implementations return the value from cpu_suspend(), which returns zero if the CPU vectors back into the kernel via cpu_resume() (the success case), or the non-zero return value of the suspend actor, or one (failure cases). We do not want to be returning the failure case value back to CPU idle as that indicates that we successfully entered one of the deeper idle states. Always return zero instead, indicating that we slept for the shortest amount of time. Signed-off-by: Russell King --- drivers/cpuidle/cpuidle-mvebu-v7.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/cpuidle/cpuidle-mvebu-v7.c b/drivers/cpuidle/cpuidle-mvebu-v7.c index 01a856971f05..18ded9e7cb34 100644 --- a/drivers/cpuidle/cpuidle-mvebu-v7.c +++ b/drivers/cpuidle/cpuidle-mvebu-v7.c @@ -39,8 +39,12 @@ static int mvebu_v7_enter_idle(struct cpuidle_device *dev, ret = mvebu_v7_cpu_suspend(deepidle); cpu_pm_exit(); + /* + * If we failed to enter the desired state, indicate that we + * slept lightly. + */ if (ret) - return ret; + return 0; return index; } -- cgit From 55c9212629a3af8bd038d45a975c1f0e41b2a5a6 Mon Sep 17 00:00:00 2001 From: Russell King Date: Thu, 29 Dec 2016 11:03:09 +0000 Subject: net: phy: add 802.3 clause 45 support to phylib Add generic helpers for 802.3 clause 45 PHYs for >= 10Gbps support. Signed-off-by: Russell King --- drivers/net/phy/Makefile | 3 +- drivers/net/phy/phy-c45.c | 234 +++++++++++++++++++++++++++++++++++++++++++ drivers/net/phy/phy_device.c | 20 ++-- include/linux/phy.h | 12 +++ 4 files changed, 254 insertions(+), 15 deletions(-) create mode 100644 drivers/net/phy/phy-c45.c diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile index e58667d111e7..e1bee18b2ec7 100644 --- a/drivers/net/phy/Makefile +++ b/drivers/net/phy/Makefile @@ -1,6 +1,7 @@ # Makefile for Linux PHY drivers and MDIO bus drivers -libphy-y := phy.o phy_device.o mdio_bus.o mdio_device.o +libphy-y := phy.o phy_device.o mdio_bus.o mdio_device.o \ + phy-c45.o libphy-$(CONFIG_SWPHY) += swphy.o obj-$(CONFIG_PHYLIB) += libphy.o diff --git a/drivers/net/phy/phy-c45.c b/drivers/net/phy/phy-c45.c new file mode 100644 index 000000000000..1d841efc4d79 --- /dev/null +++ b/drivers/net/phy/phy-c45.c @@ -0,0 +1,234 @@ +/* + * Clause 45 PHY support + */ +#include +#include +#include +#include +#include + +/** + * genphy_c45_setup_forced - configures a forced speed + * @phydev: target phy_device struct + */ +int genphy_c45_pma_setup_forced(struct phy_device *phydev) +{ + int ctrl1, ctrl2, ret; + + /* Half duplex is not supported */ + if (phydev->duplex != DUPLEX_FULL) + return -EINVAL; + + ctrl1 = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_CTRL1); + if (ctrl1 < 0) + return ctrl1; + + ctrl2 = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_CTRL2); + if (ctrl2 < 0) + return ctrl2; + + ctrl1 &= ~MDIO_CTRL1_SPEEDSEL; + /* PMA/PMD type selection is 1.7.5:0 not 1.7.3:0. See 45.2.1.6.1. */ + ctrl2 &= ~(MDIO_PMA_CTRL2_TYPE | 0x30); + + switch (phydev->speed) { + case SPEED_10: + ctrl2 |= MDIO_PMA_CTRL2_10BT; + break; + case SPEED_100: + ctrl1 |= MDIO_PMA_CTRL1_SPEED100; + ctrl2 |= MDIO_PMA_CTRL2_100BTX; + break; + case SPEED_1000: + ctrl1 |= MDIO_PMA_CTRL1_SPEED1000; + /* Assume 1000base-T */ + ctrl2 |= MDIO_PMA_CTRL2_1000BT; + break; + case SPEED_10000: + ctrl1 |= MDIO_CTRL1_SPEED10G; + /* Assume 10Gbase-T */ + ctrl2 |= MDIO_PMA_CTRL2_10GBT; + break; + default: + return -EINVAL; + } + + ret = phy_write_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_CTRL1, ctrl1); + if (ret < 0) + return ret; + + return phy_write_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_CTRL2, ctrl2); +} +EXPORT_SYMBOL_GPL(genphy_c45_pma_setup_forced); + +/** + * genphy_c45_an_disable_aneg - disable auto-negotiation + * @phydev: target phy_device struct + * + * Disable auto-negotiation in the Clause 45 PHY. The link parameters + * parameters are controlled through the PMA/PMD MMD registers. + * + * Returns zero on success, negative errno code on failure. + */ +int genphy_c45_an_disable_aneg(struct phy_device *phydev) +{ + int val; + + val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_CTRL1); + if (val < 0) + return val; + + val &= ~(MDIO_AN_CTRL1_ENABLE | MDIO_AN_CTRL1_RESTART); + + return phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_CTRL1, val); +} +EXPORT_SYMBOL_GPL(genphy_c45_an_disable_aneg); + +/** + * genphy_c45_restart_aneg - Enable and restart auto-negotiation + * @phydev: target phy_device struct + * + * This assumes that the auto-negotiation MMD is present. + * + * Enable and restart auto-negotiation. + */ +int genphy_c45_restart_aneg(struct phy_device *phydev) +{ + int val; + + val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_CTRL1); + if (val < 0) + return val; + + val |= MDIO_AN_CTRL1_ENABLE | MDIO_AN_CTRL1_RESTART; + + return phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_CTRL1, val); +} +EXPORT_SYMBOL_GPL(genphy_c45_restart_aneg); + +/** + * genphy_c45_aneg_done - return auto-negotiation complete status + * @phydev: target phy_device struct + * + * This assumes that the auto-negotiation MMD is present. + * + * Reads the status register from the auto-negotiation MMD, returning: + * - positive if auto-negotiation is complete + * - negative errno code on error + * - zero otherwise + */ +int genphy_c45_aneg_done(struct phy_device *phydev) +{ + int val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1); + + return val < 0 ? val : val & MDIO_AN_STAT1_COMPLETE ? 1 : 0; +} +EXPORT_SYMBOL_GPL(genphy_c45_aneg_done); + +/** + * genphy_c45_read_link - read the overall link status from the MMDs + * @phydev: target phy_device struct + * @mmd_mask: MMDs to read status from + * + * Read the link status from the specified MMDs, and if they all indicate + * that the link is up, return positive. If an error is encountered, + * a negative errno will be returned, otherwise zero. + */ +int genphy_c45_read_link(struct phy_device *phydev, u32 mmd_mask) +{ + int val, devad; + bool link = true; + + while (mmd_mask) { + devad = __ffs(mmd_mask); + mmd_mask &= ~BIT(devad); + + val = phy_read_mmd(phydev, devad, MDIO_STAT1); + if (val < 0) + return val; + + /* Read twice because link state is latched and a + * read moves the current state into the register + */ + val = phy_read_mmd(phydev, devad, MDIO_STAT1); + if (val < 0) + return val; + + if (!(val & MDIO_STAT1_LSTATUS)) + link = false; + } + + return link; +} +EXPORT_SYMBOL_GPL(genphy_c45_read_link); + +/** + * genphy_c45_read_lpa - read the link partner advertisment and pause + * @phydev: target phy_device struct + * + * Read the Clause 45 defined base (7.19) and 10G (7.33) status registers, + * filling in the link partner advertisment, pause and asym_pause members + * in @phydev. This assumes that the auto-negotiation MMD is present, and + * the backplane bit (7.48.0) is clear. Clause 45 PHY drivers are expected + * to fill in the remainder of the link partner advert from vendor registers. + */ +int genphy_c45_read_lpa(struct phy_device *phydev) +{ + int val; + + /* Read the link partner's base page advertisment */ + val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_LPA); + if (val < 0) + return val; + + phydev->lp_advertising = mii_lpa_to_ethtool_lpa_t(val); + phydev->pause = val & LPA_PAUSE_CAP ? 1 : 0; + phydev->asym_pause = val & LPA_PAUSE_ASYM ? 1 : 0; + + /* Read the link partner's 10G advertisment */ + val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_STAT); + if (val < 0) + return val; + + if (val & MDIO_AN_10GBT_STAT_LP10G) + phydev->lp_advertising |= ADVERTISED_10000baseT_Full; + + return 0; +} +EXPORT_SYMBOL_GPL(genphy_c45_read_lpa); + +/** + * genphy_c45_read_pma - read link speed etc from PMA + * @phydev: target phy_device struct + */ +int genphy_c45_read_pma(struct phy_device *phydev) +{ + int val; + + val = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_CTRL1); + if (val < 0) + return val; + + switch (val & MDIO_CTRL1_SPEEDSEL) { + case 0: + phydev->speed = SPEED_10; + break; + case MDIO_PMA_CTRL1_SPEED100: + phydev->speed = SPEED_100; + break; + case MDIO_PMA_CTRL1_SPEED1000: + phydev->speed = SPEED_1000; + break; + case MDIO_CTRL1_SPEED10G: + phydev->speed = SPEED_10000; + break; + default: + phydev->speed = SPEED_UNKNOWN; + break; + } + + phydev->duplex = DUPLEX_FULL; + + return 0; +} +EXPORT_SYMBOL_GPL(genphy_c45_read_pma); diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c index c4ceb082e970..443f48a6f369 100644 --- a/drivers/net/phy/phy_device.c +++ b/drivers/net/phy/phy_device.c @@ -1370,27 +1370,19 @@ EXPORT_SYMBOL(genphy_read_status); static int gen10g_read_status(struct phy_device *phydev) { - int devad, reg; u32 mmd_mask = phydev->c45_ids.devices_in_package; - - phydev->link = 1; + int ret; /* For now just lie and say it's 10G all the time */ phydev->speed = SPEED_10000; phydev->duplex = DUPLEX_FULL; - for (devad = 0; mmd_mask; devad++, mmd_mask = mmd_mask >> 1) { - if (!(mmd_mask & 1)) - continue; + /* Avoid reading the vendor MMDs */ + mmd_mask &= ~(BIT(MDIO_MMD_VEND1) | BIT(MDIO_MMD_VEND2)); - /* Read twice because link state is latched and a - * read moves the current state into the register - */ - phy_read_mmd(phydev, devad, MDIO_STAT1); - reg = phy_read_mmd(phydev, devad, MDIO_STAT1); - if (reg < 0 || !(reg & MDIO_STAT1_LSTATUS)) - phydev->link = 0; - } + ret = genphy_c45_read_link(phydev, mmd_mask); + + phydev->link = ret > 0 ? 1 : 0; return 0; } diff --git a/include/linux/phy.h b/include/linux/phy.h index e25f1830fbcf..e1e47a516b63 100644 --- a/include/linux/phy.h +++ b/include/linux/phy.h @@ -786,6 +786,8 @@ static inline const char *phydev_name(const struct phy_device *phydev) void phy_attached_print(struct phy_device *phydev, const char *fmt, ...) __printf(2, 3); void phy_attached_info(struct phy_device *phydev); + +/* Clause 22 PHY */ int genphy_config_init(struct phy_device *phydev); int genphy_setup_forced(struct phy_device *phydev); int genphy_restart_aneg(struct phy_device *phydev); @@ -796,6 +798,16 @@ int genphy_read_status(struct phy_device *phydev); int genphy_suspend(struct phy_device *phydev); int genphy_resume(struct phy_device *phydev); int genphy_soft_reset(struct phy_device *phydev); + +/* Clause 45 PHY */ +int genphy_c45_restart_aneg(struct phy_device *phydev); +int genphy_c45_aneg_done(struct phy_device *phydev); +int genphy_c45_read_link(struct phy_device *phydev, u32 mmd_mask); +int genphy_c45_read_lpa(struct phy_device *phydev); +int genphy_c45_read_pma(struct phy_device *phydev); +int genphy_c45_pma_setup_forced(struct phy_device *phydev); +int genphy_c45_an_disable_aneg(struct phy_device *phydev); + void phy_driver_unregister(struct phy_driver *drv); void phy_drivers_unregister(struct phy_driver *drv, int n); int phy_driver_register(struct phy_driver *new_driver, struct module *owner); -- cgit From cb0f3cdddf8d4080f4531bd48c812e6d7263ead6 Mon Sep 17 00:00:00 2001 From: Russell King Date: Thu, 5 Jan 2017 00:23:40 +0000 Subject: net: phy: don't double-read clause 45 status register One of the design decisions behind the link status bit in the status register is that it latches low on link loss. This is so that link loss events are not missed. Double-reading the status register means that we always read the current state of the link, clearing any link loss event. This can cause problems - for example, if the link has negotiated a different set of operating parameters, these will not be communicated to the MAC as the PHY state machine will still think that the link has remained active. Signed-off-by: Russell King --- drivers/net/phy/phy-c45.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/drivers/net/phy/phy-c45.c b/drivers/net/phy/phy-c45.c index 1d841efc4d79..252e49864215 100644 --- a/drivers/net/phy/phy-c45.c +++ b/drivers/net/phy/phy-c45.c @@ -143,12 +143,9 @@ int genphy_c45_read_link(struct phy_device *phydev, u32 mmd_mask) devad = __ffs(mmd_mask); mmd_mask &= ~BIT(devad); - val = phy_read_mmd(phydev, devad, MDIO_STAT1); - if (val < 0) - return val; - - /* Read twice because link state is latched and a - * read moves the current state into the register + /* The link state is latched low so that momentary link + * drops can be detected. Do not double-read the status + * register if the link is down. */ val = phy_read_mmd(phydev, devad, MDIO_STAT1); if (val < 0) -- cgit From ed8b9be7e9fb6965feddfe09457eee8bf6600abf Mon Sep 17 00:00:00 2001 From: Russell King Date: Wed, 4 Jan 2017 10:46:43 +0000 Subject: net: phy: move phy MMD accessors to phy-core.c Move the phy_(read|write)__mmd() helpers out of line, they will become our main MMD accessor functions, and so will be a little more complex. This complexity doesn't belong in an inline function. Also move the _indirect variants as well to keep like functionality together. Signed-off-by: Russell King --- drivers/net/phy/Makefile | 2 +- drivers/net/phy/phy-core.c | 135 +++++++++++++++++++++++++++++++++++++++++++++ drivers/net/phy/phy.c | 85 ---------------------------- include/linux/phy.h | 20 +------ 4 files changed, 138 insertions(+), 104 deletions(-) create mode 100644 drivers/net/phy/phy-core.c diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile index e1bee18b2ec7..926094571d9a 100644 --- a/drivers/net/phy/Makefile +++ b/drivers/net/phy/Makefile @@ -1,7 +1,7 @@ # Makefile for Linux PHY drivers and MDIO bus drivers libphy-y := phy.o phy_device.o mdio_bus.o mdio_device.o \ - phy-c45.o + phy-c45.o phy-core.o libphy-$(CONFIG_SWPHY) += swphy.o obj-$(CONFIG_PHYLIB) += libphy.o diff --git a/drivers/net/phy/phy-core.c b/drivers/net/phy/phy-core.c new file mode 100644 index 000000000000..b8d8276a3099 --- /dev/null +++ b/drivers/net/phy/phy-core.c @@ -0,0 +1,135 @@ +/* + * Core PHY library, taken from phy.c + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ +#include +#include + +static inline void mmd_phy_indirect(struct mii_bus *bus, int prtad, int devad, + int addr) +{ + /* Write the desired MMD Devad */ + bus->write(bus, addr, MII_MMD_CTRL, devad); + + /* Write the desired MMD register address */ + bus->write(bus, addr, MII_MMD_DATA, prtad); + + /* Select the Function : DATA with no post increment */ + bus->write(bus, addr, MII_MMD_CTRL, (devad | MII_MMD_CTRL_NOINCR)); +} + +/** + * phy_read_mmd_indirect - reads data from the MMD registers + * @phydev: The PHY device bus + * @prtad: MMD Address + * @devad: MMD DEVAD + * + * Description: it reads data from the MMD registers (clause 22 to access to + * clause 45) of the specified phy address. + * To read these register we have: + * 1) Write reg 13 // DEVAD + * 2) Write reg 14 // MMD Address + * 3) Write reg 13 // MMD Data Command for MMD DEVAD + * 3) Read reg 14 // Read MMD data + */ +int phy_read_mmd_indirect(struct phy_device *phydev, int prtad, int devad) +{ + struct phy_driver *phydrv = phydev->drv; + int addr = phydev->mdio.addr; + int value = -1; + + if (!phydrv->read_mmd_indirect) { + struct mii_bus *bus = phydev->mdio.bus; + + mutex_lock(&bus->mdio_lock); + mmd_phy_indirect(bus, prtad, devad, addr); + + /* Read the content of the MMD's selected register */ + value = bus->read(bus, addr, MII_MMD_DATA); + mutex_unlock(&bus->mdio_lock); + } else { + value = phydrv->read_mmd_indirect(phydev, prtad, devad, addr); + } + return value; +} +EXPORT_SYMBOL(phy_read_mmd_indirect); + +/** + * phy_read_mmd - Convenience function for reading a register + * from an MMD on a given PHY. + * @phydev: The phy_device struct + * @devad: The MMD to read from + * @regnum: The register on the MMD to read + * + * Same rules as for phy_read(); + */ +int phy_read_mmd(struct phy_device *phydev, int devad, u32 regnum) +{ + if (!phydev->is_c45) + return -EOPNOTSUPP; + + return mdiobus_read(phydev->mdio.bus, phydev->mdio.addr, + MII_ADDR_C45 | (devad << 16) | (regnum & 0xffff)); +} +EXPORT_SYMBOL(phy_read_mmd); + +/** + * phy_write_mmd_indirect - writes data to the MMD registers + * @phydev: The PHY device + * @prtad: MMD Address + * @devad: MMD DEVAD + * @data: data to write in the MMD register + * + * Description: Write data from the MMD registers of the specified + * phy address. + * To write these register we have: + * 1) Write reg 13 // DEVAD + * 2) Write reg 14 // MMD Address + * 3) Write reg 13 // MMD Data Command for MMD DEVAD + * 3) Write reg 14 // Write MMD data + */ +void phy_write_mmd_indirect(struct phy_device *phydev, int prtad, + int devad, u32 data) +{ + struct phy_driver *phydrv = phydev->drv; + int addr = phydev->mdio.addr; + + if (!phydrv->write_mmd_indirect) { + struct mii_bus *bus = phydev->mdio.bus; + + mutex_lock(&bus->mdio_lock); + mmd_phy_indirect(bus, prtad, devad, addr); + + /* Write the data into MMD's selected register */ + bus->write(bus, addr, MII_MMD_DATA, data); + mutex_unlock(&bus->mdio_lock); + } else { + phydrv->write_mmd_indirect(phydev, prtad, devad, addr, data); + } +} +EXPORT_SYMBOL(phy_write_mmd_indirect); + +/** + * phy_write_mmd - Convenience function for writing a register + * on an MMD on a given PHY. + * @phydev: The phy_device struct + * @devad: The MMD to read from + * @regnum: The register on the MMD to read + * @val: value to write to @regnum + * + * Same rules as for phy_write(); + */ +int phy_write_mmd(struct phy_device *phydev, int devad, u32 regnum, u16 val) +{ + if (!phydev->is_c45) + return -EOPNOTSUPP; + + regnum = MII_ADDR_C45 | ((devad & 0x1f) << 16) | (regnum & 0xffff); + + return mdiobus_write(phydev->mdio.bus, phydev->mdio.addr, regnum, val); +} +EXPORT_SYMBOL(phy_write_mmd); diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c index f424b867f73e..28f6648e2068 100644 --- a/drivers/net/phy/phy.c +++ b/drivers/net/phy/phy.c @@ -1125,91 +1125,6 @@ void phy_mac_interrupt(struct phy_device *phydev, int new_link) } EXPORT_SYMBOL(phy_mac_interrupt); -static inline void mmd_phy_indirect(struct mii_bus *bus, int prtad, int devad, - int addr) -{ - /* Write the desired MMD Devad */ - bus->write(bus, addr, MII_MMD_CTRL, devad); - - /* Write the desired MMD register address */ - bus->write(bus, addr, MII_MMD_DATA, prtad); - - /* Select the Function : DATA with no post increment */ - bus->write(bus, addr, MII_MMD_CTRL, (devad | MII_MMD_CTRL_NOINCR)); -} - -/** - * phy_read_mmd_indirect - reads data from the MMD registers - * @phydev: The PHY device bus - * @prtad: MMD Address - * @devad: MMD DEVAD - * - * Description: it reads data from the MMD registers (clause 22 to access to - * clause 45) of the specified phy address. - * To read these register we have: - * 1) Write reg 13 // DEVAD - * 2) Write reg 14 // MMD Address - * 3) Write reg 13 // MMD Data Command for MMD DEVAD - * 3) Read reg 14 // Read MMD data - */ -int phy_read_mmd_indirect(struct phy_device *phydev, int prtad, int devad) -{ - struct phy_driver *phydrv = phydev->drv; - int addr = phydev->mdio.addr; - int value = -1; - - if (!phydrv->read_mmd_indirect) { - struct mii_bus *bus = phydev->mdio.bus; - - mutex_lock(&bus->mdio_lock); - mmd_phy_indirect(bus, prtad, devad, addr); - - /* Read the content of the MMD's selected register */ - value = bus->read(bus, addr, MII_MMD_DATA); - mutex_unlock(&bus->mdio_lock); - } else { - value = phydrv->read_mmd_indirect(phydev, prtad, devad, addr); - } - return value; -} -EXPORT_SYMBOL(phy_read_mmd_indirect); - -/** - * phy_write_mmd_indirect - writes data to the MMD registers - * @phydev: The PHY device - * @prtad: MMD Address - * @devad: MMD DEVAD - * @data: data to write in the MMD register - * - * Description: Write data from the MMD registers of the specified - * phy address. - * To write these register we have: - * 1) Write reg 13 // DEVAD - * 2) Write reg 14 // MMD Address - * 3) Write reg 13 // MMD Data Command for MMD DEVAD - * 3) Write reg 14 // Write MMD data - */ -void phy_write_mmd_indirect(struct phy_device *phydev, int prtad, - int devad, u32 data) -{ - struct phy_driver *phydrv = phydev->drv; - int addr = phydev->mdio.addr; - - if (!phydrv->write_mmd_indirect) { - struct mii_bus *bus = phydev->mdio.bus; - - mutex_lock(&bus->mdio_lock); - mmd_phy_indirect(bus, prtad, devad, addr); - - /* Write the data into MMD's selected register */ - bus->write(bus, addr, MII_MMD_DATA, data); - mutex_unlock(&bus->mdio_lock); - } else { - phydrv->write_mmd_indirect(phydev, prtad, devad, addr, data); - } -} -EXPORT_SYMBOL(phy_write_mmd_indirect); - /** * phy_init_eee - init and check the EEE feature * @phydev: target phy_device struct diff --git a/include/linux/phy.h b/include/linux/phy.h index e1e47a516b63..bcd3de0d7f36 100644 --- a/include/linux/phy.h +++ b/include/linux/phy.h @@ -614,14 +614,7 @@ struct phy_fixup { * * Same rules as for phy_read(); */ -static inline int phy_read_mmd(struct phy_device *phydev, int devad, u32 regnum) -{ - if (!phydev->is_c45) - return -EOPNOTSUPP; - - return mdiobus_read(phydev->mdio.bus, phydev->mdio.addr, - MII_ADDR_C45 | (devad << 16) | (regnum & 0xffff)); -} +int phy_read_mmd(struct phy_device *phydev, int devad, u32 regnum); /** * phy_read_mmd_indirect - reads data from the MMD registers @@ -715,16 +708,7 @@ static inline bool phy_is_pseudo_fixed_link(struct phy_device *phydev) * * Same rules as for phy_write(); */ -static inline int phy_write_mmd(struct phy_device *phydev, int devad, - u32 regnum, u16 val) -{ - if (!phydev->is_c45) - return -EOPNOTSUPP; - - regnum = MII_ADDR_C45 | ((devad & 0x1f) << 16) | (regnum & 0xffff); - - return mdiobus_write(phydev->mdio.bus, phydev->mdio.addr, regnum, val); -} +int phy_write_mmd(struct phy_device *phydev, int devad, u32 regnum, u16 val); /** * phy_write_mmd_indirect - writes data to the MMD registers -- cgit From f0b673e2bdf0136a84df3d85213a754e8aaa96ee Mon Sep 17 00:00:00 2001 From: Russell King Date: Wed, 4 Jan 2017 19:20:21 +0000 Subject: net: phy: make phy_(read|write)_mmd() generic MMD accessors Make phy_(read|write)_mmd() generic 802.3 clause 45 register accessors for both Clause 22 and Clause 45 PHYs, using either the direct register reading for Clause 45, or the indirect method for Clause 22 PHYs. Allow this behaviour to be overriden by PHY drivers where necessary. Signed-off-by: Russell King --- drivers/net/phy/phy-core.c | 31 +++++++++++++++++++++++-------- include/linux/phy.h | 24 ++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 8 deletions(-) diff --git a/drivers/net/phy/phy-core.c b/drivers/net/phy/phy-core.c index b8d8276a3099..b50b3a64cf6a 100644 --- a/drivers/net/phy/phy-core.c +++ b/drivers/net/phy/phy-core.c @@ -69,11 +69,18 @@ EXPORT_SYMBOL(phy_read_mmd_indirect); */ int phy_read_mmd(struct phy_device *phydev, int devad, u32 regnum) { - if (!phydev->is_c45) - return -EOPNOTSUPP; + if (regnum > (u16)~0 || devad > 32) + return -EINVAL; - return mdiobus_read(phydev->mdio.bus, phydev->mdio.addr, - MII_ADDR_C45 | (devad << 16) | (regnum & 0xffff)); + if (phydev->drv->read_mmd) + return phydev->drv->read_mmd(phydev, devad, regnum); + + if (phydev->is_c45) { + u32 addr = MII_ADDR_C45 | (devad << 16) | (regnum & 0xffff); + return mdiobus_read(phydev->mdio.bus, phydev->mdio.addr, addr); + } + + return phy_read_mmd_indirect(phydev, regnum, devad); } EXPORT_SYMBOL(phy_read_mmd); @@ -125,11 +132,19 @@ EXPORT_SYMBOL(phy_write_mmd_indirect); */ int phy_write_mmd(struct phy_device *phydev, int devad, u32 regnum, u16 val) { - if (!phydev->is_c45) - return -EOPNOTSUPP; + if (regnum > (u16)~0 || devad > 32) + return -EINVAL; - regnum = MII_ADDR_C45 | ((devad & 0x1f) << 16) | (regnum & 0xffff); + if (phydev->drv->read_mmd) + return phydev->drv->write_mmd(phydev, devad, regnum, val); + + if (phydev->is_c45) { + u32 addr = MII_ADDR_C45 | (devad << 16) | (regnum & 0xffff); + + return mdiobus_write(phydev->mdio.bus, phydev->mdio.addr, + addr, val); + } - return mdiobus_write(phydev->mdio.bus, phydev->mdio.addr, regnum, val); + return phy_write_mmd_indirect(phydev, regnum, devad, val); } EXPORT_SYMBOL(phy_write_mmd); diff --git a/include/linux/phy.h b/include/linux/phy.h index bcd3de0d7f36..3bad77f233ca 100644 --- a/include/linux/phy.h +++ b/include/linux/phy.h @@ -557,6 +557,30 @@ struct phy_driver { */ void (*link_change_notify)(struct phy_device *dev); + /* + * Phy specific driver override for reading a MMD register. + * This function is optional for PHY specific drivers. When + * not provided, the default MMD read function will be used + * by phy_read_mmd(), which will use either a direct read for + * Clause 45 PHYs or an indirect read for Clause 22 PHYs. + * devnum is the MMD device number within the PHY device, + * regnum is the register within the selected MMD device. + */ + int (*read_mmd)(struct phy_device *dev, int devnum, u16 regnum); + + /* + * Phy specific driver override for writing a MMD register. + * This function is optional for PHY specific drivers. When + * not provided, the default MMD write function will be used + * by phy_write_mmd(), which will use either a direct write for + * Clause 45 PHYs, or an indirect write for Clause 22 PHYs. + * devnum is the MMD device number within the PHY device, + * regnum is the register within the selected MMD device. + * val is the value to be written. + */ + int (*write_mmd)(struct phy_device *dev, int devnum, u16 regnum, + u16 val); + /* A function provided by a phy specific driver to override the * the PHY driver framework support for reading a MMD register * from the PHY. If not supported, return -1. This function is -- cgit From 6343c8eb70e384bc2e5282f386427bb81a12b97a Mon Sep 17 00:00:00 2001 From: Russell King Date: Wed, 4 Jan 2017 19:30:30 +0000 Subject: net: phy: switch all users to phy_(read|write)_mmd() Switch everyone over to using phy_read_mmd() and phy_write_mmd() now that it is able to handle both Clause 22 indirect addressing and Clause 45 direct addressing. Signed-off-by: Russell King --- drivers/net/phy/bcm-phy-lib.c | 12 ++++-------- drivers/net/phy/dp83867.c | 10 ++++------ drivers/net/phy/intel-xway.c | 26 +++++++++++++------------- drivers/net/phy/microchip.c | 5 ++--- drivers/net/phy/phy.c | 25 ++++++++++--------------- 5 files changed, 33 insertions(+), 45 deletions(-) diff --git a/drivers/net/phy/bcm-phy-lib.c b/drivers/net/phy/bcm-phy-lib.c index df0416db0b88..db2c26da3dbb 100644 --- a/drivers/net/phy/bcm-phy-lib.c +++ b/drivers/net/phy/bcm-phy-lib.c @@ -183,26 +183,22 @@ int bcm_phy_enable_eee(struct phy_device *phydev) int val; /* Enable EEE at PHY level */ - val = phy_read_mmd_indirect(phydev, BRCM_CL45VEN_EEE_CONTROL, - MDIO_MMD_AN); + val = phy_read_mmd(phydev, MDIO_MMD_AN, BRCM_CL45VEN_EEE_CONTROL); if (val < 0) return val; val |= LPI_FEATURE_EN | LPI_FEATURE_EN_DIG1000X; - phy_write_mmd_indirect(phydev, BRCM_CL45VEN_EEE_CONTROL, - MDIO_MMD_AN, (u32)val); + phy_write_mmd(phydev, MDIO_MMD_AN, BRCM_CL45VEN_EEE_CONTROL, (u32)val); /* Advertise EEE */ - val = phy_read_mmd_indirect(phydev, BCM_CL45VEN_EEE_ADV, - MDIO_MMD_AN); + val = phy_read_mmd(phydev, MDIO_MMD_AN, BCM_CL45VEN_EEE_ADV); if (val < 0) return val; val |= (MDIO_AN_EEE_ADV_100TX | MDIO_AN_EEE_ADV_1000T); - phy_write_mmd_indirect(phydev, BCM_CL45VEN_EEE_ADV, - MDIO_MMD_AN, (u32)val); + phy_write_mmd(phydev, MDIO_MMD_AN, BCM_CL45VEN_EEE_ADV, (u32)val); return 0; } diff --git a/drivers/net/phy/dp83867.c b/drivers/net/phy/dp83867.c index 91177a4a32ad..3476d10fba87 100644 --- a/drivers/net/phy/dp83867.c +++ b/drivers/net/phy/dp83867.c @@ -164,8 +164,7 @@ static int dp83867_config_init(struct phy_device *phydev) if ((phydev->interface >= PHY_INTERFACE_MODE_RGMII_ID) && (phydev->interface <= PHY_INTERFACE_MODE_RGMII_RXID)) { - val = phy_read_mmd_indirect(phydev, DP83867_RGMIICTL, - DP83867_DEVADDR); + val = phy_read_mmd(phydev, DP83867_DEVADDR, DP83867_RGMIICTL); if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID) val |= (DP83867_RGMII_TX_CLK_DELAY_EN | DP83867_RGMII_RX_CLK_DELAY_EN); @@ -176,14 +175,13 @@ static int dp83867_config_init(struct phy_device *phydev) if (phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID) val |= DP83867_RGMII_RX_CLK_DELAY_EN; - phy_write_mmd_indirect(phydev, DP83867_RGMIICTL, - DP83867_DEVADDR, val); + phy_write_mmd(phydev, DP83867_DEVADDR, DP83867_RGMIICTL, val); delay = (dp83867->rx_id_delay | (dp83867->tx_id_delay << DP83867_RGMII_TX_CLK_DELAY_SHIFT)); - phy_write_mmd_indirect(phydev, DP83867_RGMIIDCTL, - DP83867_DEVADDR, delay); + phy_write_mmd(phydev, DP83867_DEVADDR, DP83867_RGMIIDCTL, + delay); } return 0; diff --git a/drivers/net/phy/intel-xway.c b/drivers/net/phy/intel-xway.c index c300ab5587b8..a238d551d2d4 100644 --- a/drivers/net/phy/intel-xway.c +++ b/drivers/net/phy/intel-xway.c @@ -166,13 +166,13 @@ static int xway_gphy_config_init(struct phy_device *phydev) /* Clear all pending interrupts */ phy_read(phydev, XWAY_MDIO_ISTAT); - phy_write_mmd_indirect(phydev, XWAY_MMD_LEDCH, MDIO_MMD_VEND2, - XWAY_MMD_LEDCH_NACS_NONE | - XWAY_MMD_LEDCH_SBF_F02HZ | - XWAY_MMD_LEDCH_FBF_F16HZ); - phy_write_mmd_indirect(phydev, XWAY_MMD_LEDCL, MDIO_MMD_VEND2, - XWAY_MMD_LEDCH_CBLINK_NONE | - XWAY_MMD_LEDCH_SCAN_NONE); + phy_write_mmd(phydev, MDIO_MMD_VEND2, XWAY_MMD_LEDCH, + XWAY_MMD_LEDCH_NACS_NONE | + XWAY_MMD_LEDCH_SBF_F02HZ | + XWAY_MMD_LEDCH_FBF_F16HZ); + phy_write_mmd(phydev, MDIO_MMD_VEND2, XWAY_MMD_LEDCL, + XWAY_MMD_LEDCH_CBLINK_NONE | + XWAY_MMD_LEDCH_SCAN_NONE); /** * In most cases only one LED is connected to this phy, so @@ -183,12 +183,12 @@ static int xway_gphy_config_init(struct phy_device *phydev) ledxh = XWAY_MMD_LEDxH_BLINKF_NONE | XWAY_MMD_LEDxH_CON_LINK10XX; ledxl = XWAY_MMD_LEDxL_PULSE_TXACT | XWAY_MMD_LEDxL_PULSE_RXACT | XWAY_MMD_LEDxL_BLINKS_NONE; - phy_write_mmd_indirect(phydev, XWAY_MMD_LED0H, MDIO_MMD_VEND2, ledxh); - phy_write_mmd_indirect(phydev, XWAY_MMD_LED0L, MDIO_MMD_VEND2, ledxl); - phy_write_mmd_indirect(phydev, XWAY_MMD_LED1H, MDIO_MMD_VEND2, ledxh); - phy_write_mmd_indirect(phydev, XWAY_MMD_LED1L, MDIO_MMD_VEND2, ledxl); - phy_write_mmd_indirect(phydev, XWAY_MMD_LED2H, MDIO_MMD_VEND2, ledxh); - phy_write_mmd_indirect(phydev, XWAY_MMD_LED2L, MDIO_MMD_VEND2, ledxl); + phy_write_mmd(phydev, MDIO_MMD_VEND2, XWAY_MMD_LED0H, ledxh); + phy_write_mmd(phydev, MDIO_MMD_VEND2, XWAY_MMD_LED0L, ledxl); + phy_write_mmd(phydev, MDIO_MMD_VEND2, XWAY_MMD_LED1H, ledxh); + phy_write_mmd(phydev, MDIO_MMD_VEND2, XWAY_MMD_LED1L, ledxl); + phy_write_mmd(phydev, MDIO_MMD_VEND2, XWAY_MMD_LED2H, ledxh); + phy_write_mmd(phydev, MDIO_MMD_VEND2, XWAY_MMD_LED2L, ledxl); return 0; } diff --git a/drivers/net/phy/microchip.c b/drivers/net/phy/microchip.c index 7c00e508a101..ad7baa61c8d2 100644 --- a/drivers/net/phy/microchip.c +++ b/drivers/net/phy/microchip.c @@ -78,9 +78,8 @@ static int lan88xx_probe(struct phy_device *phydev) priv->wolopts = 0; /* these values can be used to identify internal PHY */ - priv->chip_id = phy_read_mmd_indirect(phydev, LAN88XX_MMD3_CHIP_ID, 3); - priv->chip_rev = phy_read_mmd_indirect(phydev, LAN88XX_MMD3_CHIP_REV, - 3); + priv->chip_id = phy_read_mmd(phydev, 3, LAN88XX_MMD3_CHIP_ID); + priv->chip_rev = phy_read_mmd(phydev, 3, LAN88XX_MMD3_CHIP_REV); phydev->priv = priv; diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c index 28f6648e2068..82e19d1ce2fe 100644 --- a/drivers/net/phy/phy.c +++ b/drivers/net/phy/phy.c @@ -1157,8 +1157,7 @@ int phy_init_eee(struct phy_device *phydev, bool clk_stop_enable) return status; /* First check if the EEE ability is supported */ - eee_cap = phy_read_mmd_indirect(phydev, MDIO_PCS_EEE_ABLE, - MDIO_MMD_PCS); + eee_cap = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_PCS_EEE_ABLE); if (eee_cap <= 0) goto eee_exit_err; @@ -1169,13 +1168,11 @@ int phy_init_eee(struct phy_device *phydev, bool clk_stop_enable) /* Check which link settings negotiated and verify it in * the EEE advertising registers. */ - eee_lp = phy_read_mmd_indirect(phydev, MDIO_AN_EEE_LPABLE, - MDIO_MMD_AN); + eee_lp = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_LPABLE); if (eee_lp <= 0) goto eee_exit_err; - eee_adv = phy_read_mmd_indirect(phydev, MDIO_AN_EEE_ADV, - MDIO_MMD_AN); + eee_adv = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV); if (eee_adv <= 0) goto eee_exit_err; @@ -1188,14 +1185,12 @@ int phy_init_eee(struct phy_device *phydev, bool clk_stop_enable) /* Configure the PHY to stop receiving xMII * clock while it is signaling LPI. */ - int val = phy_read_mmd_indirect(phydev, MDIO_CTRL1, - MDIO_MMD_PCS); + int val = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_CTRL1); if (val < 0) return val; val |= MDIO_PCS_CTRL1_CLKSTOP_EN; - phy_write_mmd_indirect(phydev, MDIO_CTRL1, - MDIO_MMD_PCS, val); + phy_write_mmd(phydev, MDIO_MMD_PCS, MDIO_CTRL1, val); } return 0; /* EEE supported */ @@ -1214,7 +1209,7 @@ EXPORT_SYMBOL(phy_init_eee); */ int phy_get_eee_err(struct phy_device *phydev) { - return phy_read_mmd_indirect(phydev, MDIO_PCS_EEE_WK_ERR, MDIO_MMD_PCS); + return phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_PCS_EEE_WK_ERR); } EXPORT_SYMBOL(phy_get_eee_err); @@ -1231,19 +1226,19 @@ int phy_ethtool_get_eee(struct phy_device *phydev, struct ethtool_eee *data) int val; /* Get Supported EEE */ - val = phy_read_mmd_indirect(phydev, MDIO_PCS_EEE_ABLE, MDIO_MMD_PCS); + val = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_PCS_EEE_ABLE); if (val < 0) return val; data->supported = mmd_eee_cap_to_ethtool_sup_t(val); /* Get advertisement EEE */ - val = phy_read_mmd_indirect(phydev, MDIO_AN_EEE_ADV, MDIO_MMD_AN); + val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV); if (val < 0) return val; data->advertised = mmd_eee_adv_to_ethtool_adv_t(val); /* Get LP advertisement EEE */ - val = phy_read_mmd_indirect(phydev, MDIO_AN_EEE_LPABLE, MDIO_MMD_AN); + val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_LPABLE); if (val < 0) return val; data->lp_advertised = mmd_eee_adv_to_ethtool_adv_t(val); @@ -1263,7 +1258,7 @@ int phy_ethtool_set_eee(struct phy_device *phydev, struct ethtool_eee *data) { int val = ethtool_adv_to_mmd_eee_adv_t(data->advertised); - phy_write_mmd_indirect(phydev, MDIO_AN_EEE_ADV, MDIO_MMD_AN, val); + phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV, val); return 0; } -- cgit From 323367354db8b1b00f3f397ffe6d8251a2b5de17 Mon Sep 17 00:00:00 2001 From: Russell King Date: Wed, 4 Jan 2017 19:33:56 +0000 Subject: net: phy: convert micrel to new read_mmd/write_mmd driver methods Convert micrel to the new read_mmd/write_mmd driver methods. This Clause 22 PHY does not support any MMD access method. Signed-off-by: Russell King --- drivers/net/phy/micrel.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/drivers/net/phy/micrel.c b/drivers/net/phy/micrel.c index ea92d524d5a8..a358ae279aa2 100644 --- a/drivers/net/phy/micrel.c +++ b/drivers/net/phy/micrel.c @@ -637,8 +637,7 @@ static int ksz8873mll_config_aneg(struct phy_device *phydev) * MMD extended PHY registers. */ static int -ksz9021_rd_mmd_phyreg(struct phy_device *phydev, int ptrad, int devnum, - int regnum) +ksz9021_rd_mmd_phyreg(struct phy_device *phydev, int devad, u16 regnum) { return -1; } @@ -646,10 +645,10 @@ ksz9021_rd_mmd_phyreg(struct phy_device *phydev, int ptrad, int devnum, /* This routine does nothing since the Micrel ksz9021 does not support * standard IEEE MMD extended PHY registers. */ -static void -ksz9021_wr_mmd_phyreg(struct phy_device *phydev, int ptrad, int devnum, - int regnum, u32 val) +static int +ksz9021_wr_mmd_phyreg(struct phy_device *phydev, int devad, u16 regnum, u16 val) { + return -1; } static int kszphy_get_sset_count(struct phy_device *phydev) @@ -967,8 +966,8 @@ static struct phy_driver ksphy_driver[] = { .get_stats = kszphy_get_stats, .suspend = genphy_suspend, .resume = genphy_resume, - .read_mmd_indirect = ksz9021_rd_mmd_phyreg, - .write_mmd_indirect = ksz9021_wr_mmd_phyreg, + .read_mmd = ksz9021_rd_mmd_phyreg, + .write_mmd = ksz9021_wr_mmd_phyreg, }, { .phy_id = PHY_ID_KSZ9031, .phy_id_mask = MICREL_PHY_ID_MASK, -- cgit From 14276fa963028098616cf9ab6f3b8a69c3f4854b Mon Sep 17 00:00:00 2001 From: Russell King Date: Wed, 4 Jan 2017 19:40:24 +0000 Subject: net: phy: remove the indirect MMD read/write methods Remove the indirect MMD read/write methods which are now no longer necessary. Signed-off-by: Russell King --- drivers/net/phy/phy-core.c | 119 +++++++++++++-------------------------------- include/linux/phy.h | 18 ------- 2 files changed, 35 insertions(+), 102 deletions(-) diff --git a/drivers/net/phy/phy-core.c b/drivers/net/phy/phy-core.c index b50b3a64cf6a..80795ccd3fab 100644 --- a/drivers/net/phy/phy-core.c +++ b/drivers/net/phy/phy-core.c @@ -22,103 +22,42 @@ static inline void mmd_phy_indirect(struct mii_bus *bus, int prtad, int devad, bus->write(bus, addr, MII_MMD_CTRL, (devad | MII_MMD_CTRL_NOINCR)); } -/** - * phy_read_mmd_indirect - reads data from the MMD registers - * @phydev: The PHY device bus - * @prtad: MMD Address - * @devad: MMD DEVAD - * - * Description: it reads data from the MMD registers (clause 22 to access to - * clause 45) of the specified phy address. - * To read these register we have: - * 1) Write reg 13 // DEVAD - * 2) Write reg 14 // MMD Address - * 3) Write reg 13 // MMD Data Command for MMD DEVAD - * 3) Read reg 14 // Read MMD data - */ -int phy_read_mmd_indirect(struct phy_device *phydev, int prtad, int devad) -{ - struct phy_driver *phydrv = phydev->drv; - int addr = phydev->mdio.addr; - int value = -1; - - if (!phydrv->read_mmd_indirect) { - struct mii_bus *bus = phydev->mdio.bus; - - mutex_lock(&bus->mdio_lock); - mmd_phy_indirect(bus, prtad, devad, addr); - - /* Read the content of the MMD's selected register */ - value = bus->read(bus, addr, MII_MMD_DATA); - mutex_unlock(&bus->mdio_lock); - } else { - value = phydrv->read_mmd_indirect(phydev, prtad, devad, addr); - } - return value; -} -EXPORT_SYMBOL(phy_read_mmd_indirect); - /** * phy_read_mmd - Convenience function for reading a register * from an MMD on a given PHY. * @phydev: The phy_device struct - * @devad: The MMD to read from - * @regnum: The register on the MMD to read + * @devad: The MMD to read from (0..31) + * @regnum: The register on the MMD to read (0..65535) * * Same rules as for phy_read(); */ int phy_read_mmd(struct phy_device *phydev, int devad, u32 regnum) { + int val; + if (regnum > (u16)~0 || devad > 32) return -EINVAL; - if (phydev->drv->read_mmd) - return phydev->drv->read_mmd(phydev, devad, regnum); - - if (phydev->is_c45) { + if (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); - return mdiobus_read(phydev->mdio.bus, phydev->mdio.addr, addr); - } - - return phy_read_mmd_indirect(phydev, regnum, devad); -} -EXPORT_SYMBOL(phy_read_mmd); - -/** - * phy_write_mmd_indirect - writes data to the MMD registers - * @phydev: The PHY device - * @prtad: MMD Address - * @devad: MMD DEVAD - * @data: data to write in the MMD register - * - * Description: Write data from the MMD registers of the specified - * phy address. - * To write these register we have: - * 1) Write reg 13 // DEVAD - * 2) Write reg 14 // MMD Address - * 3) Write reg 13 // MMD Data Command for MMD DEVAD - * 3) Write reg 14 // Write MMD data - */ -void phy_write_mmd_indirect(struct phy_device *phydev, int prtad, - int devad, u32 data) -{ - struct phy_driver *phydrv = phydev->drv; - int addr = phydev->mdio.addr; - if (!phydrv->write_mmd_indirect) { + val = mdiobus_read(phydev->mdio.bus, phydev->mdio.addr, addr); + } else { struct mii_bus *bus = phydev->mdio.bus; + int phy_addr = phydev->mdio.addr; mutex_lock(&bus->mdio_lock); - mmd_phy_indirect(bus, prtad, devad, addr); + mmd_phy_indirect(bus, regnum, devad, phy_addr); - /* Write the data into MMD's selected register */ - bus->write(bus, addr, MII_MMD_DATA, data); + /* Read the content of the MMD's selected register */ + val = bus->read(bus, phy_addr, MII_MMD_DATA); mutex_unlock(&bus->mdio_lock); - } else { - phydrv->write_mmd_indirect(phydev, prtad, devad, addr, data); } + return val; } -EXPORT_SYMBOL(phy_write_mmd_indirect); +EXPORT_SYMBOL(phy_read_mmd); /** * phy_write_mmd - Convenience function for writing a register @@ -132,19 +71,31 @@ EXPORT_SYMBOL(phy_write_mmd_indirect); */ int phy_write_mmd(struct phy_device *phydev, int devad, u32 regnum, u16 val) { + int ret; + if (regnum > (u16)~0 || devad > 32) return -EINVAL; - if (phydev->drv->read_mmd) - return phydev->drv->write_mmd(phydev, devad, regnum, val); - - if (phydev->is_c45) { + if (phydev->drv->read_mmd) { + ret = phydev->drv->write_mmd(phydev, devad, regnum, val); + } else if (phydev->is_c45) { u32 addr = MII_ADDR_C45 | (devad << 16) | (regnum & 0xffff); - return mdiobus_write(phydev->mdio.bus, phydev->mdio.addr, - addr, val); - } + ret = mdiobus_write(phydev->mdio.bus, phydev->mdio.addr, + addr, val); + } else { + struct mii_bus *bus = phydev->mdio.bus; + int phy_addr = phydev->mdio.addr; - return phy_write_mmd_indirect(phydev, regnum, devad, val); + mutex_lock(&bus->mdio_lock); + mmd_phy_indirect(bus, regnum, devad, phy_addr); + + /* Write the data into MMD's selected register */ + bus->write(bus, phy_addr, MII_MMD_DATA, val); + mutex_unlock(&bus->mdio_lock); + + ret = 0; + } + return ret; } EXPORT_SYMBOL(phy_write_mmd); diff --git a/include/linux/phy.h b/include/linux/phy.h index 3bad77f233ca..ce01c6cfbd88 100644 --- a/include/linux/phy.h +++ b/include/linux/phy.h @@ -581,24 +581,6 @@ struct phy_driver { int (*write_mmd)(struct phy_device *dev, int devnum, u16 regnum, u16 val); - /* A function provided by a phy specific driver to override the - * the PHY driver framework support for reading a MMD register - * from the PHY. If not supported, return -1. This function is - * optional for PHY specific drivers, if not provided then the - * default MMD read function is used by the PHY framework. - */ - int (*read_mmd_indirect)(struct phy_device *dev, int ptrad, - int devnum, int regnum); - - /* A function provided by a phy specific driver to override the - * the PHY driver framework support for writing a MMD register - * from the PHY. This function is optional for PHY specific drivers, - * if not provided then the default MMD read function is used by - * the PHY framework. - */ - void (*write_mmd_indirect)(struct phy_device *dev, int ptrad, - int devnum, int regnum, u32 val); - /* Get the size and type of the eeprom contained within a plug-in * module */ int (*module_info)(struct phy_device *dev, -- cgit From 5fba97adb18287f36c450245f80c579f1efe6421 Mon Sep 17 00:00:00 2001 From: Russell King Date: Wed, 4 Jan 2017 20:06:05 +0000 Subject: net: phy: clean up mmd_phy_indirect() Make mmd_phy_indirect() use the same terminology as the rest of the code, making clear what each address is - phy address, devad, and register number. While here, remove the "inline" from this static function, leaving it to the compiler to decide whether to inline this function, and get rid of unnecessary parens. Signed-off-by: Russell King --- drivers/net/phy/phy-core.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/net/phy/phy-core.c b/drivers/net/phy/phy-core.c index 80795ccd3fab..357a4d0d7641 100644 --- a/drivers/net/phy/phy-core.c +++ b/drivers/net/phy/phy-core.c @@ -9,17 +9,17 @@ #include #include -static inline void mmd_phy_indirect(struct mii_bus *bus, int prtad, int devad, - int addr) +static void mmd_phy_indirect(struct mii_bus *bus, int phy_addr, int devad, + u16 regnum) { /* Write the desired MMD Devad */ - bus->write(bus, addr, MII_MMD_CTRL, devad); + bus->write(bus, phy_addr, MII_MMD_CTRL, devad); /* Write the desired MMD register address */ - bus->write(bus, addr, MII_MMD_DATA, prtad); + bus->write(bus, phy_addr, MII_MMD_DATA, regnum); /* Select the Function : DATA with no post increment */ - bus->write(bus, addr, MII_MMD_CTRL, (devad | MII_MMD_CTRL_NOINCR)); + bus->write(bus, phy_addr, MII_MMD_CTRL, devad | MII_MMD_CTRL_NOINCR); } /** @@ -49,7 +49,7 @@ int phy_read_mmd(struct phy_device *phydev, int devad, u32 regnum) int phy_addr = phydev->mdio.addr; mutex_lock(&bus->mdio_lock); - mmd_phy_indirect(bus, regnum, devad, phy_addr); + mmd_phy_indirect(bus, phy_addr, devad, regnum); /* Read the content of the MMD's selected register */ val = bus->read(bus, phy_addr, MII_MMD_DATA); @@ -88,7 +88,7 @@ int phy_write_mmd(struct phy_device *phydev, int devad, u32 regnum, u16 val) int phy_addr = phydev->mdio.addr; mutex_lock(&bus->mdio_lock); - mmd_phy_indirect(bus, regnum, devad, phy_addr); + mmd_phy_indirect(bus, phy_addr, devad, regnum); /* Write the data into MMD's selected register */ bus->write(bus, phy_addr, MII_MMD_DATA, val); -- cgit From 8263edcd6f63b01a37b8bbe08ce0198c426e61ec Mon Sep 17 00:00:00 2001 From: Russell King Date: Wed, 4 Jan 2017 21:00:51 +0000 Subject: net: phy: avoid setting unsupported EEE advertisments We currently allow userspace to set any EEE advertisments it desires, whether or not the PHY supports them. For example: # ethtool --set-eee eth1 advertise 0xffffffff # ethtool --show-eee eth1 EEE Settings for eth1: EEE status: disabled Tx LPI: disabled Supported EEE link modes: 100baseT/Full 1000baseT/Full 10000baseT/Full Advertised EEE link modes: 100baseT/Full 1000baseT/Full 1000baseKX/Full 10000baseT/Full 10000baseKX4/Full 10000baseKR/Full Clearly, this is not sane, we should only allow link modes that are supported to be advertised (as we do elsewhere.) Ensure that we mask the MDIO_AN_EEE_ADV value with the capabilities retrieved from the MDIO_PCS_EEE_ABLE register. Signed-off-by: Russell King --- drivers/net/phy/phy.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c index 82e19d1ce2fe..b6c1bb45b661 100644 --- a/drivers/net/phy/phy.c +++ b/drivers/net/phy/phy.c @@ -1256,11 +1256,16 @@ EXPORT_SYMBOL(phy_ethtool_get_eee); */ int phy_ethtool_set_eee(struct phy_device *phydev, struct ethtool_eee *data) { - int val = ethtool_adv_to_mmd_eee_adv_t(data->advertised); + int cap, adv; - phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV, val); + /* Get Supported EEE */ + cap = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_PCS_EEE_ABLE); + if (cap < 0) + return cap; - return 0; + adv = ethtool_adv_to_mmd_eee_adv_t(data->advertised) & cap; + + return phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV, adv); } EXPORT_SYMBOL(phy_ethtool_set_eee); -- cgit From 2cfc56e6fbb6d645772c627421f418da0b009a18 Mon Sep 17 00:00:00 2001 From: Russell King Date: Thu, 5 Jan 2017 16:32:14 +0000 Subject: net: phy: improve phylib correctness for non-autoneg settings phylib has some undesirable behaviour when forcing a link mode through ethtool. phylib uses this code: idx = phy_find_valid(phy_find_setting(phydev->speed, phydev->duplex), features); to find an index in the settings table. phy_find_setting() starts at index 0, and scans upwards looking for an exact speed and duplex match. When it doesn't find it, it returns MAX_NUM_SETTINGS - 1, which is 10baseT-Half duplex. phy_find_valid() then scans from the point (and effectively only checks one entry) before bailing out, returning MAX_NUM_SETTINGS - 1. phy_sanitize_settings() then sets ->speed to SPEED_10 and ->duplex to DUPLEX_HALF whether or not 10baseT-Half is supported or not. This goes against all the comments against these functions, and 10baseT-Half may not even be supported by the hardware. Rework these functions, introducing a new method of scanning the table. There are two modes of lookup that phylib wants: exact, and inexact. - in exact mode, we return either an exact match or failure - in inexact mode, we return an exact match if it exists, a match at the highest speed that is not greater than the requested speed (ignoring duplex), or failing that, the lowest supported speed, or failure. The biggest difference is that we always check whether the entry is supported before further consideration, so all unsupported entries are not considered as candidates. This results in arguably saner behaviour, better matches the comments, and is probably what users would expect. This becomes important as ethernet speeds increase, PHYs exist which do not support the 10Mbit speeds, and half-duplex is likely to become obsolete - it's already not even an option on 10Gbit and faster links. Signed-off-by: Russell King --- drivers/net/phy/phy.c | 128 +++++++++++++++++++++++++++++++------------------- 1 file changed, 80 insertions(+), 48 deletions(-) diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c index b6c1bb45b661..1bbf5a168dbc 100644 --- a/drivers/net/phy/phy.c +++ b/drivers/net/phy/phy.c @@ -157,107 +157,138 @@ static inline int phy_aneg_done(struct phy_device *phydev) struct phy_setting { int speed; int duplex; - u32 setting; + int bit; }; -/* A mapping of all SUPPORTED settings to speed/duplex */ +/* A mapping of all SUPPORTED settings to speed/duplex. This table + * must be sorted in descending match priority - iow, descending + * speed. */ static const struct phy_setting settings[] = { { .speed = SPEED_10000, .duplex = DUPLEX_FULL, - .setting = SUPPORTED_10000baseKR_Full, + .bit = ETHTOOL_LINK_MODE_10000baseKR_Full_BIT, }, { .speed = SPEED_10000, .duplex = DUPLEX_FULL, - .setting = SUPPORTED_10000baseKX4_Full, + .bit = ETHTOOL_LINK_MODE_10000baseKX4_Full_BIT, }, { .speed = SPEED_10000, .duplex = DUPLEX_FULL, - .setting = SUPPORTED_10000baseT_Full, + .bit = ETHTOOL_LINK_MODE_10000baseT_Full_BIT, }, { .speed = SPEED_2500, .duplex = DUPLEX_FULL, - .setting = SUPPORTED_2500baseX_Full, + .bit = ETHTOOL_LINK_MODE_2500baseX_Full_BIT, }, { .speed = SPEED_1000, .duplex = DUPLEX_FULL, - .setting = SUPPORTED_1000baseKX_Full, + .bit = ETHTOOL_LINK_MODE_1000baseKX_Full_BIT, }, { .speed = SPEED_1000, .duplex = DUPLEX_FULL, - .setting = SUPPORTED_1000baseT_Full, + .bit = ETHTOOL_LINK_MODE_1000baseT_Full_BIT, }, { .speed = SPEED_1000, .duplex = DUPLEX_HALF, - .setting = SUPPORTED_1000baseT_Half, + .bit = ETHTOOL_LINK_MODE_1000baseT_Half_BIT, }, { .speed = SPEED_100, .duplex = DUPLEX_FULL, - .setting = SUPPORTED_100baseT_Full, + .bit = ETHTOOL_LINK_MODE_100baseT_Full_BIT, }, { .speed = SPEED_100, .duplex = DUPLEX_HALF, - .setting = SUPPORTED_100baseT_Half, + .bit = ETHTOOL_LINK_MODE_100baseT_Half_BIT, }, { .speed = SPEED_10, .duplex = DUPLEX_FULL, - .setting = SUPPORTED_10baseT_Full, + .bit = ETHTOOL_LINK_MODE_10baseT_Full_BIT, }, { .speed = SPEED_10, .duplex = DUPLEX_HALF, - .setting = SUPPORTED_10baseT_Half, + .bit = ETHTOOL_LINK_MODE_10baseT_Half_BIT, }, }; -#define MAX_NUM_SETTINGS ARRAY_SIZE(settings) - /** - * phy_find_setting - find a PHY settings array entry that matches speed & duplex + * phy_lookup_setting - lookup a PHY setting * @speed: speed to match * @duplex: duplex to match + * @mask: allowed link modes + * @maxbit: bit size of link modes + * @exact: an exact match is required + * + * Search the settings array for a setting that matches the speed and + * duplex, and which is supported. * - * Description: Searches the settings array for the setting which - * matches the desired speed and duplex, and returns the index - * of that setting. Returns the index of the last setting if - * none of the others match. + * If @exact is unset, either an exact match or %NULL for no match will + * be returned. + * + * If @exact is set, an exact match, the fastest supported setting at + * or below the specified speed, the slowest supported setting, or if + * they all fail, %NULL will be returned. */ -static inline unsigned int phy_find_setting(int speed, int duplex) +static const struct phy_setting * +phy_lookup_setting(int speed, int duplex, const unsigned long *mask, + size_t maxbit, bool exact) { - unsigned int idx = 0; + const struct phy_setting *p, *match = NULL, *last = NULL; + int i; + + for (i = 0, p = settings; i < ARRAY_SIZE(settings); i++, p++) { + if (p->bit < maxbit && test_bit(p->bit, mask)) { + last = p; + if (p->speed == speed && p->duplex == duplex) { + /* Exact match for speed and duplex */ + match = p; + break; + } else if (!exact) { + if (!match && p->speed <= speed) + /* Candidate */ + match = p; - while (idx < ARRAY_SIZE(settings) && - (settings[idx].speed != speed || settings[idx].duplex != duplex)) - idx++; + if (p->speed < speed) + break; + } + } + } + + if (!match && !exact) + match = last; - return idx < MAX_NUM_SETTINGS ? idx : MAX_NUM_SETTINGS - 1; + return match; } /** - * phy_find_valid - find a PHY setting that matches the requested features mask - * @idx: The first index in settings[] to search - * @features: A mask of the valid settings + * phy_find_valid - find a PHY setting that matches the requested parameters + * @speed: desired speed + * @duplex: desired duplex + * @supported: mask of supported link modes * - * Description: Returns the index of the first valid setting less - * than or equal to the one pointed to by idx, as determined by - * the mask in features. Returns the index of the last setting - * if nothing else matches. + * Locate a supported phy setting that is, in priority order: + * - an exact match for the specified speed and duplex mode + * - a match for the specified speed, or slower speed + * - the slowest supported speed + * Returns the matched phy_setting entry, or %NULL if no supported phy + * settings were found. */ -static inline unsigned int phy_find_valid(unsigned int idx, u32 features) +static const struct phy_setting * +phy_find_valid(int speed, int duplex, u32 supported) { - while (idx < MAX_NUM_SETTINGS && !(settings[idx].setting & features)) - idx++; + unsigned long mask = supported; - return idx < MAX_NUM_SETTINGS ? idx : MAX_NUM_SETTINGS - 1; + return phy_lookup_setting(speed, duplex, &mask, BITS_PER_LONG, false); } /** @@ -271,12 +302,9 @@ static inline unsigned int phy_find_valid(unsigned int idx, u32 features) */ static inline bool phy_check_valid(int speed, int duplex, u32 features) { - unsigned int idx; + unsigned long mask = features; - idx = phy_find_valid(phy_find_setting(speed, duplex), features); - - return settings[idx].speed == speed && settings[idx].duplex == duplex && - (settings[idx].setting & features); + return !!phy_lookup_setting(speed, duplex, &mask, BITS_PER_LONG, true); } /** @@ -289,18 +317,22 @@ static inline bool phy_check_valid(int speed, int duplex, u32 features) */ static void phy_sanitize_settings(struct phy_device *phydev) { + const struct phy_setting *setting; u32 features = phydev->supported; - unsigned int idx; /* Sanitize settings based on PHY capabilities */ if ((features & SUPPORTED_Autoneg) == 0) phydev->autoneg = AUTONEG_DISABLE; - idx = phy_find_valid(phy_find_setting(phydev->speed, phydev->duplex), - features); - - phydev->speed = settings[idx].speed; - phydev->duplex = settings[idx].duplex; + setting = phy_find_valid(phydev->speed, phydev->duplex, features); + if (setting) { + phydev->speed = setting->speed; + phydev->duplex = setting->duplex; + } else { + /* We failed to find anything (no supported speeds?) */ + phydev->speed = SPEED_UNKNOWN; + phydev->duplex = DUPLEX_UNKNOWN; + } } /** -- cgit From 8d1efbcea454fb080314d434dd623adc11fac65a Mon Sep 17 00:00:00 2001 From: Russell King Date: Mon, 2 Jan 2017 17:52:18 +0000 Subject: net: phy: split out PHY speed and duplex string generation Other code would like to make use of this, so make the speed and duplex string generation visible, and place it in a separate file. Signed-off-by: Russell King --- drivers/net/phy/phy-core.c | 33 +++++++++++++++++++++++++++++++++ drivers/net/phy/phy.c | 22 +--------------------- include/linux/phy.h | 3 +++ 3 files changed, 37 insertions(+), 21 deletions(-) diff --git a/drivers/net/phy/phy-core.c b/drivers/net/phy/phy-core.c index 357a4d0d7641..ffa6c455f3c2 100644 --- a/drivers/net/phy/phy-core.c +++ b/drivers/net/phy/phy-core.c @@ -9,6 +9,39 @@ #include #include +const char *phy_speed_to_str(int speed) +{ + switch (speed) { + case SPEED_10: + return "10Mbps"; + case SPEED_100: + return "100Mbps"; + case SPEED_1000: + return "1Gbps"; + case SPEED_2500: + return "2.5Gbps"; + case SPEED_10000: + return "10Gbps"; + case SPEED_UNKNOWN: + return "Unknown"; + default: + return "Unsupported (update phy-core.c)"; + } +} +EXPORT_SYMBOL_GPL(phy_speed_to_str); + +const char *phy_duplex_to_str(unsigned int duplex) +{ + if (duplex == DUPLEX_HALF) + return "Half"; + if (duplex == DUPLEX_FULL) + return "Full"; + if (duplex == DUPLEX_UNKNOWN) + return "Unknown"; + return "Unsupported (update phy-core.c)"; +} +EXPORT_SYMBOL_GPL(phy_duplex_to_str); + static void mmd_phy_indirect(struct mii_bus *bus, int phy_addr, int devad, u16 regnum) { diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c index 1bbf5a168dbc..1e77135276dd 100644 --- a/drivers/net/phy/phy.c +++ b/drivers/net/phy/phy.c @@ -38,26 +38,6 @@ #include -static const char *phy_speed_to_str(int speed) -{ - switch (speed) { - case SPEED_10: - return "10Mbps"; - case SPEED_100: - return "100Mbps"; - case SPEED_1000: - return "1Gbps"; - case SPEED_2500: - return "2.5Gbps"; - case SPEED_10000: - return "10Gbps"; - case SPEED_UNKNOWN: - return "Unknown"; - default: - return "Unsupported (update phy.c)"; - } -} - #define PHY_STATE_STR(_state) \ case PHY_##_state: \ return __stringify(_state); \ @@ -93,7 +73,7 @@ void phy_print_status(struct phy_device *phydev) netdev_info(phydev->attached_dev, "Link is Up - %s/%s - flow control %s\n", phy_speed_to_str(phydev->speed), - DUPLEX_FULL == phydev->duplex ? "Full" : "Half", + phy_duplex_to_str(phydev->duplex), phydev->pause ? "rx/tx" : "off"); } else { netdev_info(phydev->attached_dev, "Link is Down\n"); diff --git a/include/linux/phy.h b/include/linux/phy.h index ce01c6cfbd88..36b6995b8166 100644 --- a/include/linux/phy.h +++ b/include/linux/phy.h @@ -611,6 +611,9 @@ struct phy_fixup { int (*run)(struct phy_device *phydev); }; +const char *phy_speed_to_str(int speed); +const char *phy_duplex_to_str(unsigned int duplex); + /** * phy_read_mmd - Convenience function for reading a register * from an MMD on a given PHY. -- cgit From a9fe8aa521100049f32065b0ca3f3f5b2af6d86e Mon Sep 17 00:00:00 2001 From: Russell King Date: Thu, 5 Jan 2017 16:47:39 +0000 Subject: net: phy: move phy_lookup_setting() to phy-core phy_lookup_setting() provides useful functionality in ethtool code outside phylib. Move it to phy-core and allow it to be re-used (eg, in phylink) rather than duplicated elsewhere. Note that this supports the larger linkmode space. Signed-off-by: Russell King --- drivers/net/phy/phy-core.c | 111 ++++++++++++++++++++++++++++++++++++++++++ drivers/net/phy/phy.c | 119 --------------------------------------------- include/linux/phy.h | 13 +++++ 3 files changed, 124 insertions(+), 119 deletions(-) diff --git a/drivers/net/phy/phy-core.c b/drivers/net/phy/phy-core.c index ffa6c455f3c2..90c2b07e6bf4 100644 --- a/drivers/net/phy/phy-core.c +++ b/drivers/net/phy/phy-core.c @@ -42,6 +42,117 @@ const char *phy_duplex_to_str(unsigned int duplex) } EXPORT_SYMBOL_GPL(phy_duplex_to_str); +/* A mapping of all SUPPORTED settings to speed/duplex. This table + * must be sorted in descending match priority - iow, descending + * speed. */ +static const struct phy_setting settings[] = { + { + .speed = SPEED_10000, + .duplex = DUPLEX_FULL, + .bit = ETHTOOL_LINK_MODE_10000baseKR_Full_BIT, + }, + { + .speed = SPEED_10000, + .duplex = DUPLEX_FULL, + .bit = ETHTOOL_LINK_MODE_10000baseKX4_Full_BIT, + }, + { + .speed = SPEED_10000, + .duplex = DUPLEX_FULL, + .bit = ETHTOOL_LINK_MODE_10000baseT_Full_BIT, + }, + { + .speed = SPEED_2500, + .duplex = DUPLEX_FULL, + .bit = ETHTOOL_LINK_MODE_2500baseX_Full_BIT, + }, + { + .speed = SPEED_1000, + .duplex = DUPLEX_FULL, + .bit = ETHTOOL_LINK_MODE_1000baseKX_Full_BIT, + }, + { + .speed = SPEED_1000, + .duplex = DUPLEX_FULL, + .bit = ETHTOOL_LINK_MODE_1000baseT_Full_BIT, + }, + { + .speed = SPEED_1000, + .duplex = DUPLEX_HALF, + .bit = ETHTOOL_LINK_MODE_1000baseT_Half_BIT, + }, + { + .speed = SPEED_100, + .duplex = DUPLEX_FULL, + .bit = ETHTOOL_LINK_MODE_100baseT_Full_BIT, + }, + { + .speed = SPEED_100, + .duplex = DUPLEX_HALF, + .bit = ETHTOOL_LINK_MODE_100baseT_Half_BIT, + }, + { + .speed = SPEED_10, + .duplex = DUPLEX_FULL, + .bit = ETHTOOL_LINK_MODE_10baseT_Full_BIT, + }, + { + .speed = SPEED_10, + .duplex = DUPLEX_HALF, + .bit = ETHTOOL_LINK_MODE_10baseT_Half_BIT, + }, +}; + +/** + * phy_lookup_setting - lookup a PHY setting + * @speed: speed to match + * @duplex: duplex to match + * @mask: allowed link modes + * @maxbit: bit size of link modes + * @exact: an exact match is required + * + * Search the settings array for a setting that matches the speed and + * duplex, and which is supported. + * + * If @exact is unset, either an exact match or %NULL for no match will + * be returned. + * + * If @exact is set, an exact match, the fastest supported setting at + * or below the specified speed, the slowest supported setting, or if + * they all fail, %NULL will be returned. + */ +const struct phy_setting * +phy_lookup_setting(int speed, int duplex, const unsigned long *mask, + size_t maxbit, bool exact) +{ + const struct phy_setting *p, *match = NULL, *last = NULL; + int i; + + for (i = 0, p = settings; i < ARRAY_SIZE(settings); i++, p++) { + if (p->bit < maxbit && test_bit(p->bit, mask)) { + last = p; + if (p->speed == speed && p->duplex == duplex) { + /* Exact match for speed and duplex */ + match = p; + break; + } else if (!exact) { + if (!match && p->speed <= speed) + /* Candidate */ + match = p; + + if (p->speed < speed) + break; + } + } + } + + if (!match && !exact) + match = last; + + return match; +} +EXPORT_SYMBOL_GPL(phy_lookup_setting); + static void mmd_phy_indirect(struct mii_bus *bus, int phy_addr, int devad, u16 regnum) { diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c index 1e77135276dd..feac3fb07f8c 100644 --- a/drivers/net/phy/phy.c +++ b/drivers/net/phy/phy.c @@ -131,125 +131,6 @@ static inline int phy_aneg_done(struct phy_device *phydev) return genphy_aneg_done(phydev); } -/* A structure for mapping a particular speed and duplex - * combination to a particular SUPPORTED and ADVERTISED value - */ -struct phy_setting { - int speed; - int duplex; - int bit; -}; - -/* A mapping of all SUPPORTED settings to speed/duplex. This table - * must be sorted in descending match priority - iow, descending - * speed. */ -static const struct phy_setting settings[] = { - { - .speed = SPEED_10000, - .duplex = DUPLEX_FULL, - .bit = ETHTOOL_LINK_MODE_10000baseKR_Full_BIT, - }, - { - .speed = SPEED_10000, - .duplex = DUPLEX_FULL, - .bit = ETHTOOL_LINK_MODE_10000baseKX4_Full_BIT, - }, - { - .speed = SPEED_10000, - .duplex = DUPLEX_FULL, - .bit = ETHTOOL_LINK_MODE_10000baseT_Full_BIT, - }, - { - .speed = SPEED_2500, - .duplex = DUPLEX_FULL, - .bit = ETHTOOL_LINK_MODE_2500baseX_Full_BIT, - }, - { - .speed = SPEED_1000, - .duplex = DUPLEX_FULL, - .bit = ETHTOOL_LINK_MODE_1000baseKX_Full_BIT, - }, - { - .speed = SPEED_1000, - .duplex = DUPLEX_FULL, - .bit = ETHTOOL_LINK_MODE_1000baseT_Full_BIT, - }, - { - .speed = SPEED_1000, - .duplex = DUPLEX_HALF, - .bit = ETHTOOL_LINK_MODE_1000baseT_Half_BIT, - }, - { - .speed = SPEED_100, - .duplex = DUPLEX_FULL, - .bit = ETHTOOL_LINK_MODE_100baseT_Full_BIT, - }, - { - .speed = SPEED_100, - .duplex = DUPLEX_HALF, - .bit = ETHTOOL_LINK_MODE_100baseT_Half_BIT, - }, - { - .speed = SPEED_10, - .duplex = DUPLEX_FULL, - .bit = ETHTOOL_LINK_MODE_10baseT_Full_BIT, - }, - { - .speed = SPEED_10, - .duplex = DUPLEX_HALF, - .bit = ETHTOOL_LINK_MODE_10baseT_Half_BIT, - }, -}; - -/** - * phy_lookup_setting - lookup a PHY setting - * @speed: speed to match - * @duplex: duplex to match - * @mask: allowed link modes - * @maxbit: bit size of link modes - * @exact: an exact match is required - * - * Search the settings array for a setting that matches the speed and - * duplex, and which is supported. - * - * If @exact is unset, either an exact match or %NULL for no match will - * be returned. - * - * If @exact is set, an exact match, the fastest supported setting at - * or below the specified speed, the slowest supported setting, or if - * they all fail, %NULL will be returned. - */ -static const struct phy_setting * -phy_lookup_setting(int speed, int duplex, const unsigned long *mask, - size_t maxbit, bool exact) -{ - const struct phy_setting *p, *match = NULL, *last = NULL; - int i; - - for (i = 0, p = settings; i < ARRAY_SIZE(settings); i++, p++) { - if (p->bit < maxbit && test_bit(p->bit, mask)) { - last = p; - if (p->speed == speed && p->duplex == duplex) { - /* Exact match for speed and duplex */ - match = p; - break; - } else if (!exact) { - if (!match && p->speed <= speed) - /* Candidate */ - match = p; - - if (p->speed < speed) - break; - } - } - } - - if (!match && !exact) - match = last; - - return match; -} - /** * phy_find_valid - find a PHY setting that matches the requested parameters * @speed: desired speed diff --git a/include/linux/phy.h b/include/linux/phy.h index 36b6995b8166..c1f05edcdf55 100644 --- a/include/linux/phy.h +++ b/include/linux/phy.h @@ -614,6 +614,19 @@ struct phy_fixup { const char *phy_speed_to_str(int speed); const char *phy_duplex_to_str(unsigned int duplex); +/* A structure for mapping a particular speed and duplex + * combination to a particular SUPPORTED and ADVERTISED value + */ +struct phy_setting { + u32 speed; + u8 duplex; + u8 bit; +}; + +const struct phy_setting * +phy_lookup_setting(int speed, int duplex, const unsigned long *mask, + size_t maxbit, bool exact); + /** * phy_read_mmd - Convenience function for reading a register * from an MMD on a given PHY. -- cgit From 6f7b4c2af983d61315b623ced4e6a9663cdd137a Mon Sep 17 00:00:00 2001 From: Russell King Date: Thu, 5 Jan 2017 16:57:48 +0000 Subject: net: phy: add 1000Base-X to phy settings table Add the missing 1000Base-X entry to the phy settings table. This was not included because the original code could not cope with more than 32 bits of link mode mask. Signed-off-by: Russell King --- drivers/net/phy/phy-core.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/net/phy/phy-core.c b/drivers/net/phy/phy-core.c index 90c2b07e6bf4..aa2d86b9d6ef 100644 --- a/drivers/net/phy/phy-core.c +++ b/drivers/net/phy/phy-core.c @@ -71,6 +71,11 @@ static const struct phy_setting settings[] = { .duplex = DUPLEX_FULL, .bit = ETHTOOL_LINK_MODE_1000baseKX_Full_BIT, }, + { + .speed = SPEED_1000, + .duplex = DUPLEX_FULL, + .bit = ETHTOOL_LINK_MODE_1000baseX_Full_BIT, + }, { .speed = SPEED_1000, .duplex = DUPLEX_FULL, -- cgit From 9935692bf0976149da724c69da63141f6894f669 Mon Sep 17 00:00:00 2001 From: Russell King Date: Fri, 18 Sep 2015 14:42:16 +0100 Subject: phy: provide a hook for link up/link down events Sometimes, we need to do additional work between the PHY coming up and marking the carrier present - for example, we may need to wait for the PHY to MAC link to finish negotiation. This changes phylib to provide a notification function pointer which avoids the built-in netif_carrier_on() and netif_carrier_off() functions. Standard ->adjust_link functionality is provided by hooking a helper into the new ->phy_link_change method. Reviewed-by: Florian Fainelli Signed-off-by: Russell King --- drivers/net/phy/phy.c | 42 ++++++++++++++++++++++-------------------- drivers/net/phy/phy_device.c | 14 ++++++++++++++ include/linux/phy.h | 1 + 3 files changed, 37 insertions(+), 20 deletions(-) diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c index feac3fb07f8c..94b79f7dea74 100644 --- a/drivers/net/phy/phy.c +++ b/drivers/net/phy/phy.c @@ -804,6 +804,16 @@ void phy_start(struct phy_device *phydev) } EXPORT_SYMBOL(phy_start); +static void phy_link_up(struct phy_device *phydev) +{ + phydev->phy_link_change(phydev, true, true); +} + +static void phy_link_down(struct phy_device *phydev, bool do_carrier) +{ + phydev->phy_link_change(phydev, false, do_carrier); +} + /** * phy_state_machine - Handle the state machine * @work: work_struct that describes the work to be done @@ -845,8 +855,7 @@ void phy_state_machine(struct work_struct *work) /* If the link is down, give up on negotiation for now */ if (!phydev->link) { phydev->state = PHY_NOLINK; - netif_carrier_off(phydev->attached_dev); - phydev->adjust_link(phydev->attached_dev); + phy_link_down(phydev, true); break; } @@ -858,9 +867,7 @@ void phy_state_machine(struct work_struct *work) /* If AN is done, we're running */ if (err > 0) { phydev->state = PHY_RUNNING; - netif_carrier_on(phydev->attached_dev); - phydev->adjust_link(phydev->attached_dev); - + phy_link_up(phydev); } else if (0 == phydev->link_timeout--) needs_aneg = true; break; @@ -885,8 +892,7 @@ void phy_state_machine(struct work_struct *work) } } phydev->state = PHY_RUNNING; - netif_carrier_on(phydev->attached_dev); - phydev->adjust_link(phydev->attached_dev); + phy_link_up(phydev); } break; case PHY_FORCING: @@ -896,13 +902,12 @@ void phy_state_machine(struct work_struct *work) if (phydev->link) { phydev->state = PHY_RUNNING; - netif_carrier_on(phydev->attached_dev); + phy_link_up(phydev); } else { if (0 == phydev->link_timeout--) needs_aneg = true; + phy_link_down(phydev, false); } - - phydev->adjust_link(phydev->attached_dev); break; case PHY_RUNNING: /* Only register a CHANGE if we are polling and link changed @@ -925,14 +930,12 @@ void phy_state_machine(struct work_struct *work) if (phydev->link) { phydev->state = PHY_RUNNING; - netif_carrier_on(phydev->attached_dev); + phy_link_up(phydev); } else { phydev->state = PHY_NOLINK; - netif_carrier_off(phydev->attached_dev); + phy_link_down(phydev, true); } - phydev->adjust_link(phydev->attached_dev); - if (phy_interrupt_is_valid(phydev)) err = phy_config_interrupt(phydev, PHY_INTERRUPT_ENABLED); @@ -940,8 +943,7 @@ void phy_state_machine(struct work_struct *work) case PHY_HALTED: if (phydev->link) { phydev->link = 0; - netif_carrier_off(phydev->attached_dev); - phydev->adjust_link(phydev->attached_dev); + phy_link_down(phydev, true); do_suspend = true; } break; @@ -961,11 +963,11 @@ void phy_state_machine(struct work_struct *work) if (phydev->link) { phydev->state = PHY_RUNNING; - netif_carrier_on(phydev->attached_dev); + phy_link_up(phydev); } else { phydev->state = PHY_NOLINK; + phy_link_down(phydev, false); } - phydev->adjust_link(phydev->attached_dev); } else { phydev->state = PHY_AN; phydev->link_timeout = PHY_AN_TIMEOUT; @@ -977,11 +979,11 @@ void phy_state_machine(struct work_struct *work) if (phydev->link) { phydev->state = PHY_RUNNING; - netif_carrier_on(phydev->attached_dev); + phy_link_up(phydev); } else { phydev->state = PHY_NOLINK; + phy_link_down(phydev, false); } - phydev->adjust_link(phydev->attached_dev); } break; } diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c index 443f48a6f369..78da1cf82fc5 100644 --- a/drivers/net/phy/phy_device.c +++ b/drivers/net/phy/phy_device.c @@ -645,6 +645,19 @@ struct phy_device *phy_find_first(struct mii_bus *bus) } EXPORT_SYMBOL(phy_find_first); +static void phy_link_change(struct phy_device *phydev, bool up, bool do_carrier) +{ + struct net_device *netdev = phydev->attached_dev; + + if (do_carrier) { + if (up) + netif_carrier_on(netdev); + else + netif_carrier_off(netdev); + } + phydev->adjust_link(netdev); +} + /** * phy_prepare_link - prepares the PHY layer to monitor link status * @phydev: target phy_device struct @@ -899,6 +912,7 @@ int phy_attach_direct(struct net_device *dev, struct phy_device *phydev, goto error; } + phydev->phy_link_change = phy_link_change; phydev->attached_dev = dev; dev->phydev = phydev; diff --git a/include/linux/phy.h b/include/linux/phy.h index c1f05edcdf55..987515d3dc0d 100644 --- a/include/linux/phy.h +++ b/include/linux/phy.h @@ -426,6 +426,7 @@ struct phy_device { u8 mdix; + void (*phy_link_change)(struct phy_device *, bool up, bool do_carrier); void (*adjust_link)(struct net_device *dev); }; #define to_phy_device(d) container_of(to_mdio_device(d), \ -- cgit From 488cef0a7dea75b46a94ee340c5315b660d1b896 Mon Sep 17 00:00:00 2001 From: Russell King Date: Fri, 16 Oct 2015 12:18:41 +0100 Subject: phy: export phy_start_machine() for phylink phylink will need phy_start_machine exported, so lets export it as a GPL symbol. Documentation/networking/phy.txt indicates that this should be a PHY API function. Reviewed-by: Florian Fainelli Signed-off-by: Russell King --- drivers/net/phy/phy.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c index 94b79f7dea74..34b236511d74 100644 --- a/drivers/net/phy/phy.c +++ b/drivers/net/phy/phy.c @@ -499,6 +499,7 @@ void phy_start_machine(struct phy_device *phydev) { queue_delayed_work(system_power_efficient_wq, &phydev->state_queue, HZ); } +EXPORT_SYMBOL_GPL(phy_start_machine); /** * phy_trigger_machine - trigger the state machine to run -- cgit From ab42481d0530d368ab117ff65f0692d44601bf34 Mon Sep 17 00:00:00 2001 From: Russell King Date: Fri, 25 Sep 2015 17:43:52 +0100 Subject: phy: add I2C mdio bus Add an I2C MDIO bus bridge library, to allow phylib to access PHYs which are connected to an I2C bus instead of the more conventional MDIO bus. Such PHYs can be found in SFP adapters and SFF modules. Since PHYs appear at I2C bus address 0x40..0x5f, and 0x50/0x51 are reserved for SFP EEPROMs/diagnostics, we must not allow the MDIO bus to access these I2C addresses. Signed-off-by: Russell King --- drivers/net/phy/Kconfig | 10 +++++ drivers/net/phy/Makefile | 1 + drivers/net/phy/mdio-i2c.c | 109 +++++++++++++++++++++++++++++++++++++++++++++ drivers/net/phy/mdio-i2c.h | 19 ++++++++ 4 files changed, 139 insertions(+) create mode 100644 drivers/net/phy/mdio-i2c.c create mode 100644 drivers/net/phy/mdio-i2c.h diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index 2651c8d8de2f..f08179a386ce 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -105,6 +105,16 @@ config MDIO_HISI_FEMAC This module provides a driver for the MDIO busses found in the Hisilicon SoC that have an Fast Ethernet MAC. +config MDIO_I2C + tristate + depends on I2C + help + Support I2C based PHYs. This provides a MDIO bus bridged + to I2C to allow PHYs connected in I2C mode to be accessed + using the existing infrastructure. + + This is library mode. + config MDIO_MOXART tristate "MOXA ART MDIO interface support" depends on ARCH_MOXART diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile index 926094571d9a..22065a6c707b 100644 --- a/drivers/net/phy/Makefile +++ b/drivers/net/phy/Makefile @@ -16,6 +16,7 @@ obj-$(CONFIG_MDIO_BUS_MUX_MMIOREG) += mdio-mux-mmioreg.o obj-$(CONFIG_MDIO_CAVIUM) += mdio-cavium.o obj-$(CONFIG_MDIO_GPIO) += mdio-gpio.o obj-$(CONFIG_MDIO_HISI_FEMAC) += mdio-hisi-femac.o +obj-$(CONFIG_MDIO_I2C) += mdio-i2c.o obj-$(CONFIG_MDIO_MOXART) += mdio-moxart.o obj-$(CONFIG_MDIO_OCTEON) += mdio-octeon.o obj-$(CONFIG_MDIO_SUN4I) += mdio-sun4i.o diff --git a/drivers/net/phy/mdio-i2c.c b/drivers/net/phy/mdio-i2c.c new file mode 100644 index 000000000000..6d24fd13ca86 --- /dev/null +++ b/drivers/net/phy/mdio-i2c.c @@ -0,0 +1,109 @@ +/* + * MDIO I2C bridge + * + * Copyright (C) 2015-2016 Russell King + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Network PHYs can appear on I2C buses when they are part of SFP module. + * This driver exposes these PHYs to the networking PHY code, allowing + * our PHY drivers access to these PHYs, and so allowing configuration + * of their settings. + */ +#include +#include + +#include "mdio-i2c.h" + +/* + * I2C bus addresses 0x50 and 0x51 are normally an EEPROM, which is + * specified to be present in SFP modules. These correspond with PHY + * addresses 16 and 17. Disallow access to these "phy" addresses. + */ +static bool i2c_mii_valid_phy_id(int phy_id) +{ + return phy_id != 0x10 && phy_id != 0x11; +} + +static unsigned int i2c_mii_phy_addr(int phy_id) +{ + return phy_id + 0x40; +} + +static int i2c_mii_read(struct mii_bus *bus, int phy_id, int reg) +{ + struct i2c_adapter *i2c = bus->priv; + struct i2c_msg msgs[2]; + u8 data[2], dev_addr = reg; + int bus_addr, ret; + + if (!i2c_mii_valid_phy_id(phy_id)) + return 0xffff; + + bus_addr = i2c_mii_phy_addr(phy_id); + msgs[0].addr = bus_addr; + msgs[0].flags = 0; + msgs[0].len = 1; + msgs[0].buf = &dev_addr; + msgs[1].addr = bus_addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = sizeof(data); + msgs[1].buf = data; + + ret = i2c_transfer(i2c, msgs, ARRAY_SIZE(msgs)); + if (ret != ARRAY_SIZE(msgs)) + return 0xffff; + + return data[0] << 8 | data[1]; +} + +static int i2c_mii_write(struct mii_bus *bus, int phy_id, int reg, u16 val) +{ + struct i2c_adapter *i2c = bus->priv; + struct i2c_msg msg; + int ret; + u8 data[3]; + + if (!i2c_mii_valid_phy_id(phy_id)) + return 0; + + data[0] = reg; + data[1] = val >> 8; + data[2] = val; + + msg.addr = i2c_mii_phy_addr(phy_id); + msg.flags = 0; + msg.len = 3; + msg.buf = data; + + ret = i2c_transfer(i2c, &msg, 1); + + return ret < 0 ? ret : 0; +} + +struct mii_bus *mdio_i2c_alloc(struct device *parent, struct i2c_adapter *i2c) +{ + struct mii_bus *mii; + + if (!i2c_check_functionality(i2c, I2C_FUNC_I2C)) + return ERR_PTR(-EINVAL); + + mii = mdiobus_alloc(); + if (!mii) + return ERR_PTR(-ENOMEM); + + snprintf(mii->id, MII_BUS_ID_SIZE, "i2c:%s", dev_name(parent)); + mii->parent = parent; + mii->read = i2c_mii_read; + mii->write = i2c_mii_write; + mii->priv = i2c; + + return mii; +} +EXPORT_SYMBOL_GPL(mdio_i2c_alloc); + +MODULE_AUTHOR("Russell King"); +MODULE_DESCRIPTION("MDIO I2C bridge library"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/phy/mdio-i2c.h b/drivers/net/phy/mdio-i2c.h new file mode 100644 index 000000000000..889ab57d7f3e --- /dev/null +++ b/drivers/net/phy/mdio-i2c.h @@ -0,0 +1,19 @@ +/* + * MDIO I2C bridge + * + * Copyright (C) 2015 Russell King + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#ifndef MDIO_I2C_H +#define MDIO_I2C_H + +struct device; +struct i2c_adapter; +struct mii_bus; + +struct mii_bus *mdio_i2c_alloc(struct device *parent, struct i2c_adapter *i2c); + +#endif -- cgit From dede9e7417b9615b475566cd93e737159b7027b5 Mon Sep 17 00:00:00 2001 From: Russell King Date: Tue, 22 Sep 2015 20:52:18 +0100 Subject: phylink: add phylink infrastructure The link between the ethernet MAC and its PHY has become more complex as the interface evolves. This is especially true with serdes links, where the part of the PHY is effectively integrated into the MAC. Serdes links can be connected to a variety of devices, including SFF modules soldered down onto the board with the MAC, a SFP cage with a hotpluggable SFP module which may contain a PHY or directly modulate the serdes signals onto optical media with or without a PHY, or even a classical PHY connection. Moreover, the negotiation information on serdes links comes in two varieties - SGMII mode, where the PHY provides its speed/duplex/flow control information to the MAC, and 1000base-X mode where both ends exchange their abilities and each resolve the link capabilities. This means we need a more flexible means to support these arrangements, particularly with the hotpluggable nature of SFP, where the PHY can be attached or detached after the network device has been brought up. Ethtool information can come from multiple sources: - we may have a PHY operating in either SGMII or 1000base-X mode, in which case we take ethtool/mii data directly from the PHY. - we may have a optical SFP module without a PHY, with the MAC operating in 1000base-X mode - the ethtool/mii data needs to come from the MAC. - we may have a copper SFP module with a PHY whic can't be accessed, which means we need to take ethtool/mii data from the MAC. Phylink aims to solve this by providing an intermediary between the MAC and PHY, providing a safe way for PHYs to be hotplugged, and allowing a SFP driver to reconfigure the serdes connection. Phylink also takes over support of fixed link connections, where the speed/duplex/flow control are fixed, but link status may be controlled by a GPIO signal. By avoiding the fixed-phy implementation, phylink can provide a faster response to link events: fixed-phy has to wait for phylib to operate its state machine, which can take several seconds. In comparison, phylink takes milliseconds. Signed-off-by: Russell King - remove sync status - rework supported and advertisment handling - add 1000base-x speed for fixed links - use functionality exported from phy-core, reworking __phylink_ethtool_ksettings_set for it --- drivers/net/phy/Kconfig | 10 + drivers/net/phy/Makefile | 1 + drivers/net/phy/phy_device.c | 1 + drivers/net/phy/phylink.c | 903 +++++++++++++++++++++++++++++++++++++++++++ include/linux/phy.h | 2 + include/linux/phylink.h | 100 +++++ 6 files changed, 1017 insertions(+) create mode 100644 drivers/net/phy/phylink.c create mode 100644 include/linux/phylink.h diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index f08179a386ce..d709afcd6295 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -10,6 +10,16 @@ menuconfig PHYLIB devices. This option provides infrastructure for managing PHY devices. +config PHYLINK + tristate + depends on NETDEVICES + select PHYLIB + select SWPHY + help + PHYlink models the link between the PHY and MAC, allowing fixed + configuration links, PHYs, and Serdes links with MAC level + autonegotiation modes. + if PHYLIB config SWPHY diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile index 22065a6c707b..28501b45ab3d 100644 --- a/drivers/net/phy/Makefile +++ b/drivers/net/phy/Makefile @@ -4,6 +4,7 @@ libphy-y := phy.o phy_device.o mdio_bus.o mdio_device.o \ phy-c45.o phy-core.o libphy-$(CONFIG_SWPHY) += swphy.o +obj-$(CONFIG_PHYLINK) += phylink.o obj-$(CONFIG_PHYLIB) += libphy.o obj-$(CONFIG_MDIO_BCM_IPROC) += mdio-bcm-iproc.o diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c index 78da1cf82fc5..5ac39f9fcfa6 100644 --- a/drivers/net/phy/phy_device.c +++ b/drivers/net/phy/phy_device.c @@ -1000,6 +1000,7 @@ void phy_detach(struct phy_device *phydev) phydev->attached_dev->phydev = NULL; phydev->attached_dev = NULL; phy_suspend(phydev); + phydev->phylink = NULL; /* If the device had no specific driver before (i.e. - it * was using the generic driver), we unbind the device diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c new file mode 100644 index 000000000000..5a6642bae633 --- /dev/null +++ b/drivers/net/phy/phylink.c @@ -0,0 +1,903 @@ +/* + * phylink models the MAC to optional PHY connection, supporting + * technologies such as SFP cages where the PHY is hot-pluggable. + * + * Copyright (C) 2015 Russell King + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "swphy.h" + +#define SUPPORTED_INTERFACES \ + (SUPPORTED_TP | SUPPORTED_MII | SUPPORTED_FIBRE | \ + SUPPORTED_BNC | SUPPORTED_AUI | SUPPORTED_Backplane) +#define ADVERTISED_INTERFACES \ + (ADVERTISED_TP | ADVERTISED_MII | ADVERTISED_FIBRE | \ + ADVERTISED_BNC | ADVERTISED_AUI | ADVERTISED_Backplane) + +enum { + PHYLINK_DISABLE_STOPPED, +}; + +struct phylink { + struct net_device *netdev; + const struct phylink_mac_ops *ops; + struct mutex config_mutex; + + unsigned long phylink_disable_state; /* bitmask of disables */ + struct phy_device *phydev; + phy_interface_t link_interface; /* PHY_INTERFACE_xxx */ + u8 link_an_mode; /* MLO_AN_xxx */ + u8 link_port; /* The current non-phy ethtool port */ + /* ethtool supported mask for ports */ + __ETHTOOL_DECLARE_LINK_MODE_MASK(supported); + + /* The link configuration settings */ + struct phylink_link_state link_config; + struct gpio_desc *link_gpio; + + struct mutex state_mutex; /* may be taken within config_mutex */ + struct phylink_link_state phy_state; + struct work_struct resolve; + + const struct phylink_module_ops *module_ops; + void *module_data; +}; + +static inline void linkmode_zero(unsigned long *dst) +{ + bitmap_zero(dst, __ETHTOOL_LINK_MODE_MASK_NBITS); +} + +static inline void linkmode_copy(unsigned long *dst, const unsigned long *src) +{ + bitmap_copy(dst, src, __ETHTOOL_LINK_MODE_MASK_NBITS); +} + +static inline void linkmode_and(unsigned long *dst, const unsigned long *a, + const unsigned long *b) +{ + bitmap_and(dst, a, b, __ETHTOOL_LINK_MODE_MASK_NBITS); +} + +static inline void linkmode_or(unsigned long *dst, const unsigned long *a, + const unsigned long *b) +{ + bitmap_or(dst, a, b, __ETHTOOL_LINK_MODE_MASK_NBITS); +} + +static inline bool linkmode_empty(const unsigned long *src) +{ + return bitmap_empty(src, __ETHTOOL_LINK_MODE_MASK_NBITS); +} + +static void phylink_set_port_bits(unsigned long *bits) +{ + __set_bit(ETHTOOL_LINK_MODE_TP_BIT, bits); + __set_bit(ETHTOOL_LINK_MODE_AUI_BIT, bits); + __set_bit(ETHTOOL_LINK_MODE_MII_BIT, bits); + __set_bit(ETHTOOL_LINK_MODE_FIBRE_BIT, bits); + __set_bit(ETHTOOL_LINK_MODE_BNC_BIT, bits); + __set_bit(ETHTOOL_LINK_MODE_Backplane_BIT, bits); +} + +static const char *phylink_an_mode_str(unsigned int mode) +{ + static const char *modestr[] = { + [MLO_AN_PHY] = "phy", + [MLO_AN_FIXED] = "fixed", + [MLO_AN_SGMII] = "SGMII", + [MLO_AN_8023Z] = "802.3z", + }; + + return mode < ARRAY_SIZE(modestr) ? modestr[mode] : "unknown"; +} + +static int phylink_parse_fixedlink(struct phylink *pl, struct device_node *np) +{ + __ETHTOOL_DECLARE_LINK_MODE_MASK(mask) = { 0, }; + struct device_node *fixed_node; + const struct phy_setting *s; + struct gpio_desc *desc; + const __be32 *fixed_prop; + u32 speed; + int ret, len; + + fixed_node = of_get_child_by_name(np, "fixed-link"); + if (fixed_node) { + ret = of_property_read_u32(fixed_node, "speed", &speed); + + pl->link_config.speed = speed; + pl->link_config.duplex = DUPLEX_HALF; + + if (of_property_read_bool(fixed_node, "full-duplex")) + pl->link_config.duplex = DUPLEX_FULL; + if (of_property_read_bool(fixed_node, "pause")) + pl->link_config.pause |= MLO_PAUSE_SYM; + if (of_property_read_bool(fixed_node, "asym-pause")) + pl->link_config.pause |= MLO_PAUSE_ASYM; + + if (ret == 0) { + desc = fwnode_get_named_gpiod(&fixed_node->fwnode, + "link-gpios"); + + if (!IS_ERR(desc)) + pl->link_gpio = desc; + else if (desc == ERR_PTR(-EPROBE_DEFER)) + ret = -EPROBE_DEFER; + } + of_node_put(fixed_node); + + if (ret) + return ret; + } else { + fixed_prop = of_get_property(np, "fixed-link", &len); + if (!fixed_prop) { + netdev_err(pl->netdev, "broken fixed-link?\n"); + return -EINVAL; + } + if (len == 5 * sizeof(*fixed_prop)) { + pl->link_config.duplex = be32_to_cpu(fixed_prop[1]) ? + DUPLEX_FULL : DUPLEX_HALF; + pl->link_config.speed = be32_to_cpu(fixed_prop[2]); + if (be32_to_cpu(fixed_prop[3])) + pl->link_config.pause |= MLO_PAUSE_SYM; + if (be32_to_cpu(fixed_prop[4])) + pl->link_config.pause |= MLO_PAUSE_ASYM; + } + } + + bitmap_fill(mask, __ETHTOOL_LINK_MODE_MASK_NBITS); + pl->ops->validate_support(pl->netdev, MLO_AN_FIXED, mask); + + pl->link_config.link = 1; + pl->link_config.an_complete = 1; + + if (pl->link_config.speed > SPEED_1000 && + pl->link_config.duplex != DUPLEX_FULL) + netdev_warn(pl->netdev, "fixed link specifies half duplex for %dMbps link?\n", + pl->link_config.speed); + + s = phy_lookup_setting(pl->link_config.speed, pl->link_config.duplex, + mask, __ETHTOOL_LINK_MODE_MASK_NBITS, true); + if (s) { + __set_bit(s->bit, pl->supported); + } else { + netdev_warn(pl->netdev, "fixed link %s duplex %dMbps not recognised\n", + pl->link_config.duplex == DUPLEX_FULL ? "full" : "half", + pl->link_config.speed); + } + return 0; +} + +static int phylink_parse_mode(struct phylink *pl, struct device_node *np) +{ + struct device_node *dn; + const char *managed; + + dn = of_get_child_by_name(np, "fixed-link"); + if (dn || of_find_property(np, "fixed-link", NULL)) + pl->link_an_mode = MLO_AN_FIXED; + of_node_put(dn); + + if (of_property_read_string(np, "managed", &managed) == 0 && + strcmp(managed, "in-band-status") == 0) { + if (pl->link_an_mode == MLO_AN_FIXED) { + netdev_err(pl->netdev, + "can't use both fixed-link and in-band-status\n"); + return -EINVAL; + } + phylink_set(pl->supported, 10baseT_Half); + phylink_set(pl->supported, 10baseT_Full); + phylink_set(pl->supported, 100baseT_Half); + phylink_set(pl->supported, 100baseT_Full); + phylink_set(pl->supported, 1000baseT_Half); + phylink_set(pl->supported, 1000baseT_Full); + phylink_set(pl->supported, Asym_Pause); + phylink_set(pl->supported, Pause); + pl->link_an_mode = MLO_AN_SGMII; + pl->link_config.an_enabled = true; + pl->ops->validate_support(pl->netdev, pl->link_an_mode, + pl->supported); + } + + return 0; +} + + +static void phylink_init_advert(struct phylink *pl, unsigned int mode, + const unsigned long *supported, + unsigned long *advertising) +{ + linkmode_copy(advertising, supported); + if (pl->ops->validate_advert) + pl->ops->validate_advert(pl->netdev, mode, supported, + advertising); +} + +static void phylink_mac_config(struct phylink *pl, + const struct phylink_link_state *state) +{ + netdev_dbg(pl->netdev, + "%s: mode=%s/%s/%s adv=%*pb pause=%02x link=%u an=%u\n", + __func__, phylink_an_mode_str(pl->link_an_mode), + phy_speed_to_str(state->speed), + phy_duplex_to_str(state->duplex), + __ETHTOOL_LINK_MODE_MASK_NBITS, state->advertising, + state->pause, state->link, state->an_enabled); + + pl->ops->mac_config(pl->netdev, pl->link_an_mode, state); +} + +static void phylink_mac_an_restart(struct phylink *pl) +{ + if (pl->link_config.an_enabled) + pl->ops->mac_an_restart(pl->netdev, pl->link_an_mode); +} + +static int phylink_get_mac_state(struct phylink *pl, struct phylink_link_state *state) +{ + struct net_device *ndev = pl->netdev; + + linkmode_copy(state->advertising, pl->link_config.advertising); + linkmode_zero(state->lp_advertising); + state->an_enabled = pl->link_config.an_enabled; + state->link = 1; + + return pl->ops->mac_link_state(ndev, state); +} + +/* The fixed state is... fixed except for the link state, + * which may be determined by a GPIO. + */ +static void phylink_get_fixed_state(struct phylink *pl, struct phylink_link_state *state) +{ + *state = pl->link_config; + if (pl->link_gpio) + state->link = !!gpiod_get_value(pl->link_gpio); +} + +static void phylink_resolve(struct work_struct *w) +{ + struct phylink *pl = container_of(w, struct phylink, resolve); + struct phylink_link_state link_state; + struct net_device *ndev = pl->netdev; + + mutex_lock(&pl->state_mutex); + if (pl->phylink_disable_state) { + link_state.link = false; + } else { + switch (pl->link_an_mode) { + case MLO_AN_PHY: + link_state = pl->phy_state; + break; + + case MLO_AN_FIXED: + phylink_get_fixed_state(pl, &link_state); + break; + + case MLO_AN_SGMII: + phylink_get_mac_state(pl, &link_state); + if (pl->phydev) + link_state.link = link_state.link && + pl->phy_state.link; + break; + + case MLO_AN_8023Z: + phylink_get_mac_state(pl, &link_state); + break; + } + } + + if (link_state.link != netif_carrier_ok(ndev)) { + if (!link_state.link) { + netif_carrier_off(ndev); + pl->ops->mac_link_down(ndev, pl->link_an_mode); + netdev_info(ndev, "Link is Down\n"); + } else { + /* If we have a PHY, we need the MAC updated with + * the current link parameters (eg, in SGMII mode, + * with flow control status.) + */ + if (pl->phydev) + phylink_mac_config(pl, &link_state); + + pl->ops->mac_link_up(ndev, pl->link_an_mode); + + netif_carrier_on(ndev); + + netdev_info(ndev, + "Link is Up - %s/%s - flow control %s\n", + phy_speed_to_str(link_state.speed), + phy_duplex_to_str(link_state.duplex), + link_state.pause ? "rx/tx" : "off"); + } + } + mutex_unlock(&pl->state_mutex); +} + +static void phylink_run_resolve(struct phylink *pl) +{ + if (!pl->phylink_disable_state) + queue_work(system_power_efficient_wq, &pl->resolve); +} + +struct phylink *phylink_create(struct net_device *ndev, struct device_node *np, + phy_interface_t iface, const struct phylink_mac_ops *ops) +{ + struct phylink *pl; + int ret; + + pl = kzalloc(sizeof(*pl), GFP_KERNEL); + if (!pl) + return ERR_PTR(-ENOMEM); + + mutex_init(&pl->state_mutex); + mutex_init(&pl->config_mutex); + INIT_WORK(&pl->resolve, phylink_resolve); + pl->netdev = ndev; + pl->link_interface = iface; + pl->link_port = PORT_MII; + pl->link_config.speed = SPEED_UNKNOWN; + pl->link_config.duplex = DUPLEX_UNKNOWN; + pl->ops = ops; + __set_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state); + + ret = phylink_parse_mode(pl, np); + if (ret < 0) { + kfree(pl); + return ERR_PTR(ret); + } + + if (pl->link_an_mode == MLO_AN_FIXED) { + ret = phylink_parse_fixedlink(pl, np); + if (ret < 0) { + kfree(pl); + return ERR_PTR(ret); + } + } + + phylink_set(pl->supported, MII); + phylink_init_advert(pl, pl->link_an_mode, pl->supported, + pl->link_config.advertising); + + return pl; +} +EXPORT_SYMBOL_GPL(phylink_create); + +void phylink_destroy(struct phylink *pl) +{ + cancel_work_sync(&pl->resolve); + kfree(pl); +} +EXPORT_SYMBOL_GPL(phylink_destroy); + +void phylink_phy_change(struct phy_device *phydev, bool up, bool do_carrier) +{ + struct phylink *pl = phydev->phylink; + + mutex_lock(&pl->state_mutex); + pl->phy_state.speed = phydev->speed; + pl->phy_state.duplex = phydev->duplex; + pl->phy_state.pause = MLO_PAUSE_NONE; + if (phydev->pause) + pl->phy_state.pause |= MLO_PAUSE_SYM; + if (phydev->asym_pause) + pl->phy_state.pause |= MLO_PAUSE_ASYM; + pl->phy_state.link = up; + mutex_unlock(&pl->state_mutex); + + phylink_run_resolve(pl); + + netdev_dbg(pl->netdev, "phy link %s %s/%s\n", up ? "up" : "down", + phy_speed_to_str(phydev->speed), + phy_duplex_to_str(phydev->duplex)); +} + +static int phylink_empty_linkmode(const unsigned long *linkmode) +{ + __ETHTOOL_DECLARE_LINK_MODE_MASK(tmp) = { 0, }; + + phylink_set_port_bits(tmp); + phylink_set(tmp, Autoneg); + phylink_set(tmp, Pause); + phylink_set(tmp, Asym_Pause); + + bitmap_andnot(tmp, linkmode, tmp, __ETHTOOL_LINK_MODE_MASK_NBITS); + + return linkmode_empty(tmp); +} + +static int phylink_validate_support(struct phylink *pl, int mode, + unsigned long *mask) +{ + pl->ops->validate_support(pl->netdev, mode, mask); + + return phylink_empty_linkmode(mask) ? -EINVAL : 0; +} + +static int phylink_bringup_phy(struct phylink *pl, struct phy_device *phy) +{ + __ETHTOOL_DECLARE_LINK_MODE_MASK(mask); + u32 advertising; + int ret; + + ethtool_convert_legacy_u32_to_link_mode(mask, phy->supported); + ret = phylink_validate_support(pl, pl->link_an_mode, mask); + if (ret) + return ret; + + mutex_lock(&pl->config_mutex); + phy->phylink = pl; + phy->phy_link_change = phylink_phy_change; + + netdev_info(pl->netdev, + "PHY [%s] driver [%s]\n", dev_name(&phy->mdio.dev), + phy->drv->name); + + mutex_lock(&pl->state_mutex); + pl->phydev = phy; + linkmode_copy(pl->supported, mask); + + /* Restrict the phy advertisment according to the MAC support. */ + ethtool_convert_link_mode_to_legacy_u32(&advertising, mask); + phy->advertising &= ADVERTISED_INTERFACES | advertising; + ethtool_convert_legacy_u32_to_link_mode(pl->link_config.advertising, + phy->advertising); + mutex_unlock(&pl->state_mutex); + + netdev_dbg(pl->netdev, + "phy: setting supported %*pb advertising 0x%08x\n", + __ETHTOOL_LINK_MODE_MASK_NBITS, pl->supported, + phy->advertising); + + phy_start_machine(phy); + if (phy->irq > 0) + phy_start_interrupts(phy); + + mutex_unlock(&pl->config_mutex); + + return 0; +} + +int phylink_connect_phy(struct phylink *pl, struct phy_device *phy) +{ + int ret; + + ret = phy_attach_direct(pl->netdev, phy, 0, pl->link_interface); + if (ret) + return ret; + + ret = phylink_bringup_phy(pl, phy); + if (ret) + phy_detach(phy); + + return ret; +} +EXPORT_SYMBOL_GPL(phylink_connect_phy); + +int phylink_of_phy_connect(struct phylink *pl, struct device_node *dn) +{ + struct device_node *phy_node; + struct phy_device *phy_dev; + int ret; + + /* Fixed links are handled without needing a PHY */ + if (pl->link_an_mode == MLO_AN_FIXED) + return 0; + + phy_node = of_parse_phandle(dn, "phy-handle", 0); + if (!phy_node) + phy_node = of_parse_phandle(dn, "phy", 0); + if (!phy_node) + phy_node = of_parse_phandle(dn, "phy-device", 0); + + if (!phy_node) { + if (pl->link_an_mode == MLO_AN_PHY) { + netdev_err(pl->netdev, "unable to find PHY node\n"); + return -ENODEV; + } + return 0; + } + + phy_dev = of_phy_attach(pl->netdev, phy_node, 0, pl->link_interface); + /* We're done with the phy_node handle */ + of_node_put(phy_node); + + if (!phy_dev) + return -ENODEV; + + ret = phylink_bringup_phy(pl, phy_dev); + if (ret) + phy_detach(phy_dev); + + return ret; +} +EXPORT_SYMBOL_GPL(phylink_of_phy_connect); + +void phylink_disconnect_phy(struct phylink *pl) +{ + struct phy_device *phy; + + mutex_lock(&pl->config_mutex); + phy = pl->phydev; + + mutex_lock(&pl->state_mutex); + pl->phydev = NULL; + mutex_unlock(&pl->state_mutex); + flush_work(&pl->resolve); + + if (phy) + phy_disconnect(phy); + + mutex_unlock(&pl->config_mutex); +} +EXPORT_SYMBOL_GPL(phylink_disconnect_phy); + +void phylink_mac_change(struct phylink *pl, bool up) +{ + phylink_run_resolve(pl); + netdev_dbg(pl->netdev, "mac link %s\n", up ? "up" : "down"); +} +EXPORT_SYMBOL_GPL(phylink_mac_change); + +void phylink_start(struct phylink *pl) +{ + mutex_lock(&pl->config_mutex); + + netdev_info(pl->netdev, "configuring for %s link mode\n", + phylink_an_mode_str(pl->link_an_mode)); + + /* Apply the link configuration to the MAC when starting. This allows + * a fixed-link to start with the correct parameters, and also + * ensures that we set the appropriate advertisment for Serdes links. + */ + phylink_mac_config(pl, &pl->link_config); + + clear_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state); + phylink_run_resolve(pl); + + if (pl->phydev) + phy_start(pl->phydev); + + mutex_unlock(&pl->config_mutex); +} +EXPORT_SYMBOL_GPL(phylink_start); + +void phylink_stop(struct phylink *pl) +{ + mutex_lock(&pl->config_mutex); + + if (pl->phydev) + phy_stop(pl->phydev); + + set_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state); + flush_work(&pl->resolve); + + mutex_unlock(&pl->config_mutex); +} +EXPORT_SYMBOL_GPL(phylink_stop); + +static void phylink_merge_link_mode(unsigned long *dst, const unsigned long *b) +{ + __ETHTOOL_DECLARE_LINK_MODE_MASK(mask); + + linkmode_zero(mask); + phylink_set_port_bits(mask); + + linkmode_and(dst, dst, mask); + linkmode_or(dst, dst, b); +} + +static void phylink_get_ksettings(const struct phylink_link_state *state, + struct ethtool_link_ksettings *kset) +{ + phylink_merge_link_mode(kset->link_modes.advertising, state->advertising); + linkmode_copy(kset->link_modes.lp_advertising, state->lp_advertising); + kset->base.speed = state->speed; + kset->base.duplex = state->duplex; + kset->base.autoneg = state->an_enabled ? AUTONEG_ENABLE : + AUTONEG_DISABLE; +} + +static int __phylink_ethtool_ksettings_get(struct phylink *pl, + struct ethtool_link_ksettings *kset) +{ + struct phylink_link_state link_state; + int ret; + + if (pl->phydev) { + ret = phy_ethtool_ksettings_get(pl->phydev, kset); + if (ret) + return ret; + } else { + kset->base.port = pl->link_port; + } + + linkmode_copy(kset->link_modes.supported, pl->supported); + + switch (pl->link_an_mode) { + case MLO_AN_FIXED: + /* We are using fixed settings. Report these as the + * current link settings - and note that these also + * represent the supported speeds/duplex/pause modes. + */ + phylink_get_fixed_state(pl, &link_state); + phylink_get_ksettings(&link_state, kset); + break; + + case MLO_AN_SGMII: + /* If there is a phy attached, then use the reported + * settings from the phy with no modification. + */ + if (pl->phydev) + break; + + case MLO_AN_8023Z: + phylink_get_mac_state(pl, &link_state); + + /* The MAC is reporting the link results from its own PCS + * layer via in-band status. Report these as the current + * link settings. + */ + phylink_get_ksettings(&link_state, kset); + break; + } + + return 0; +} + +int phylink_ethtool_ksettings_get(struct phylink *pl, + struct ethtool_link_ksettings *kset) +{ + int ret; + + mutex_lock(&pl->config_mutex); + ret = __phylink_ethtool_ksettings_get(pl, kset); + mutex_unlock(&pl->config_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(phylink_ethtool_ksettings_get); + +static int __phylink_ethtool_ksettings_set(struct phylink *pl, + const struct ethtool_link_ksettings *kset) +{ + struct ethtool_link_ksettings our_kset = *kset; + int ret; + + /* Mask out unsupported advertisments */ + linkmode_and(our_kset.link_modes.advertising, + kset->link_modes.advertising, pl->supported); + + if (pl->ops->validate_advert) + pl->ops->validate_advert(pl->netdev, pl->link_an_mode, + pl->supported, + our_kset.link_modes.advertising); + + /* FIXME: should we reject autoneg if phy/mac does not support it? */ + + if (kset->base.autoneg == AUTONEG_DISABLE) { + const struct phy_setting *s; + + /* Autonegotiation disabled, select a suitable speed and + * duplex. + */ + s = phy_lookup_setting(kset->base.speed, kset->base.duplex, + pl->supported, + __ETHTOOL_LINK_MODE_MASK_NBITS, false); + if (!s) + return -EINVAL; + + /* If we have a fixed link (as specified by firmware), refuse + * to change link parameters. + */ + if (pl->link_an_mode == MLO_AN_FIXED && + (s->speed != pl->link_config.speed || + s->duplex != pl->link_config.duplex)) + return -EINVAL; + + our_kset.base.speed = s->speed; + our_kset.base.duplex = s->duplex; + + __clear_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, + our_kset.link_modes.advertising); + } else { + /* If we have a fixed link, refuse to enable autonegotiation */ + if (pl->link_an_mode == MLO_AN_FIXED) + return -EINVAL; + + /* Autonegotiation enabled, validate advertisment */ + if (phylink_empty_linkmode(our_kset.link_modes.advertising)) + return -EINVAL; + + __set_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, + our_kset.link_modes.advertising); + } + + /* If we have a PHY, configure the phy */ + if (pl->phydev) { + ret = phy_ethtool_ksettings_set(pl->phydev, &our_kset); + if (ret) + return ret; + } + + mutex_lock(&pl->state_mutex); + /* Configure the MAC to match the new settings */ + linkmode_copy(pl->link_config.advertising, our_kset.link_modes.advertising); + pl->link_config.speed = our_kset.base.speed; + pl->link_config.duplex = our_kset.base.duplex; + pl->link_config.an_enabled = our_kset.base.autoneg != AUTONEG_DISABLE; + + if (!test_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state)) { + phylink_mac_config(pl, &pl->link_config); + phylink_mac_an_restart(pl); + } + mutex_unlock(&pl->state_mutex); + + return ret; +} + +int phylink_ethtool_ksettings_set(struct phylink *pl, + const struct ethtool_link_ksettings *kset) +{ + int ret; + + if (kset->base.autoneg != AUTONEG_DISABLE && + kset->base.autoneg != AUTONEG_ENABLE) + return -EINVAL; + + mutex_lock(&pl->config_mutex); + ret = __phylink_ethtool_ksettings_set(pl, kset); + mutex_unlock(&pl->config_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(phylink_ethtool_ksettings_set); + +/* This emulates MII registers for a fixed-mode phy operating as per the + * passed in state. "aneg" defines if we report negotiation is possible. + * + * FIXME: should deal with negotiation state too. + */ +static int phylink_mii_emul_read(struct net_device *ndev, unsigned int reg, + struct phylink_link_state *state, bool aneg) +{ + struct fixed_phy_status fs; + int val; + + fs.link = state->link; + fs.speed = state->speed; + fs.duplex = state->duplex; + fs.pause = state->pause & MLO_PAUSE_SYM; + fs.asym_pause = state->pause & MLO_PAUSE_ASYM; + + val = swphy_read_reg(reg, &fs); + if (reg == MII_BMSR) { + if (!state->an_complete) + val &= ~BMSR_ANEGCOMPLETE; + if (!aneg) + val &= ~BMSR_ANEGCAPABLE; + } + return val; +} + +static int phylink_mii_read(struct phylink *pl, unsigned int phy_id, + unsigned int reg) +{ + struct phylink_link_state state; + int val = 0xffff; + + /* PHYs only exist for MLO_AN_PHY and MLO_AN_SGMII */ + if (pl->phydev) + return mdiobus_read(pl->phydev->mdio.bus, phy_id, reg); + + switch (pl->link_an_mode) { + case MLO_AN_FIXED: + if (phy_id == 0) { + phylink_get_fixed_state(pl, &state); + val = phylink_mii_emul_read(pl->netdev, reg, &state, + true); + } + break; + + case MLO_AN_PHY: + return -EOPNOTSUPP; + + case MLO_AN_SGMII: + /* No phy, fall through to 8023z method */ + case MLO_AN_8023Z: + if (phy_id == 0) { + val = phylink_get_mac_state(pl, &state); + if (val < 0) + return val; + + val = phylink_mii_emul_read(pl->netdev, reg, &state, + true); + } + break; + } + + return val & 0xffff; +} + +static int phylink_mii_write(struct phylink *pl, unsigned int phy_id, + unsigned int reg, unsigned int val) +{ + /* PHYs only exist for MLO_AN_PHY and MLO_AN_SGMII */ + if (pl->phydev) { + mdiobus_write(pl->phydev->mdio.bus, phy_id, reg, val); + return 0; + } + + switch (pl->link_an_mode) { + case MLO_AN_FIXED: + break; + + case MLO_AN_PHY: + return -EOPNOTSUPP; + + case MLO_AN_SGMII: + /* No phy, fall through to 8023z method */ + case MLO_AN_8023Z: + break; + } + + return 0; +} + +int phylink_mii_ioctl(struct phylink *pl, struct ifreq *ifr, int cmd) +{ + struct mii_ioctl_data *mii_data = if_mii(ifr); + int val, ret; + + mutex_lock(&pl->config_mutex); + + switch (cmd) { + case SIOCGMIIPHY: + mii_data->phy_id = pl->phydev ? pl->phydev->mdio.addr : 0; + /* fallthrough */ + + case SIOCGMIIREG: + val = phylink_mii_read(pl, mii_data->phy_id, mii_data->reg_num); + if (val < 0) { + ret = val; + } else { + mii_data->val_out = val; + ret = 0; + } + break; + + case SIOCSMIIREG: + ret = phylink_mii_write(pl, mii_data->phy_id, mii_data->reg_num, + mii_data->val_in); + break; + + default: + ret = -EOPNOTSUPP; + if (pl->phydev) + ret = phy_mii_ioctl(pl->phydev, ifr, cmd); + break; + } + + mutex_unlock(&pl->config_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(phylink_mii_ioctl); + +MODULE_LICENSE("GPL"); diff --git a/include/linux/phy.h b/include/linux/phy.h index 987515d3dc0d..bcac19bd184a 100644 --- a/include/linux/phy.h +++ b/include/linux/phy.h @@ -153,6 +153,7 @@ static inline const char *phy_modes(phy_interface_t interface) #define MII_ADDR_C45 (1<<30) struct device; +struct phylink; struct sk_buff; /* @@ -422,6 +423,7 @@ struct phy_device { struct mutex lock; + struct phylink *phylink; struct net_device *attached_dev; u8 mdix; diff --git a/include/linux/phylink.h b/include/linux/phylink.h new file mode 100644 index 000000000000..3fef6e5fea82 --- /dev/null +++ b/include/linux/phylink.h @@ -0,0 +1,100 @@ +#ifndef NETDEV_PCS_H +#define NETDEV_PCS_H + +#include +#include +#include + +struct device_node; +struct ethtool_cmd; +struct net_device; + +enum { + MLO_PAUSE_NONE, + MLO_PAUSE_ASYM = BIT(0), + MLO_PAUSE_SYM = BIT(1), + + MLO_AN_PHY = 0, + MLO_AN_FIXED, + MLO_AN_SGMII, + MLO_AN_8023Z, +}; + +struct phylink_link_state { + __ETHTOOL_DECLARE_LINK_MODE_MASK(advertising); + __ETHTOOL_DECLARE_LINK_MODE_MASK(lp_advertising); + int speed; + int duplex; + int pause; + unsigned int link:1; + unsigned int an_enabled:1; + unsigned int an_complete:1; +}; + +struct phylink_mac_ops { + /** + * @validate_support: + * + * Validate and update the support mask provided by a PHY or + * module. Unsupported link modes should be cleared by the + * MAC. + * + * Note: the PHY may be able to transform from one connection + * technology to another, so, eg, don't clear 1000base-X just + * because the MAC is unable to support it. This is more about + * clearing unsupported speeds and duplex settings. + */ + void (*validate_support)(struct net_device *, unsigned int mode, + unsigned long *support); + + /** + * @validate_advert: + * + * Validate and update the advertisment mask, clearing bits that + * can not be advertised in the chosen mode or with each other. + */ + void (*validate_advert)(struct net_device *, unsigned int mode, + const unsigned long *support, + unsigned long *advert); + + /* Read the current link state from the hardware */ + int (*mac_link_state)(struct net_device *, struct phylink_link_state *); + + /* Configure the MAC */ + void (*mac_config)(struct net_device *, unsigned int mode, + const struct phylink_link_state *); + void (*mac_an_restart)(struct net_device *, unsigned int mode); + + void (*mac_link_down)(struct net_device *, unsigned int mode); + void (*mac_link_up)(struct net_device *, unsigned int mode); +}; + +struct phylink *phylink_create(struct net_device *, struct device_node *, + phy_interface_t iface, const struct phylink_mac_ops *ops); +void phylink_destroy(struct phylink *); + +int phylink_connect_phy(struct phylink *, struct phy_device *); +int phylink_of_phy_connect(struct phylink *, struct device_node *); +void phylink_disconnect_phy(struct phylink *); + +void phylink_mac_change(struct phylink *, bool up); + +void phylink_start(struct phylink *); +void phylink_stop(struct phylink *); + +int phylink_ethtool_ksettings_get(struct phylink *, + struct ethtool_link_ksettings *); +int phylink_ethtool_ksettings_set(struct phylink *, + const struct ethtool_link_ksettings *); +int phylink_mii_ioctl(struct phylink *, struct ifreq *, int); + +#define phylink_zero(bm) \ + bitmap_zero(bm, __ETHTOOL_LINK_MODE_MASK_NBITS) +#define __phylink_do_bit(op, bm, mode) \ + op(ETHTOOL_LINK_MODE_ ## mode ## _BIT, bm) + +#define phylink_set(bm, mode) __phylink_do_bit(__set_bit, bm, mode) +#define phylink_clear(bm, mode) __phylink_do_bit(__clear_bit, bm, mode) +#define phylink_test(bm, mode) __phylink_do_bit(test_bit, bm, mode) + +#endif -- cgit From e76d923c84a5be88138c95b4093c5c3529328b03 Mon Sep 17 00:00:00 2001 From: Russell King Date: Thu, 24 Sep 2015 11:01:13 +0100 Subject: phylink: add hooks for SFP support Add support to phylink for SFP, which needs to control and configure the ethernet MAC link state. Specifically, SFP needs to: 1. set the negotiation mode between SGMII and 1000base-X 2. attach and detach the module PHY 3. prevent the link coming up when errors are reported In the absence of a PHY, we also need to set the ethtool port type according to the module plugged in. Reviewed-by: Florian Fainelli Signed-off-by: Russell King - rework phylink_set_link_*(), combining into a single function. --- drivers/net/phy/phylink.c | 103 ++++++++++++++++++++++++++++++++++++++++++++++ include/linux/phylink.h | 6 +++ 2 files changed, 109 insertions(+) diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index 5a6642bae633..9441469b70fa 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -29,11 +30,16 @@ (ADVERTISED_TP | ADVERTISED_MII | ADVERTISED_FIBRE | \ ADVERTISED_BNC | ADVERTISED_AUI | ADVERTISED_Backplane) +static LIST_HEAD(phylinks); +static DEFINE_MUTEX(phylink_mutex); + enum { PHYLINK_DISABLE_STOPPED, + PHYLINK_DISABLE_LINK, }; struct phylink { + struct list_head node; struct net_device *netdev; const struct phylink_mac_ops *ops; struct mutex config_mutex; @@ -375,12 +381,20 @@ struct phylink *phylink_create(struct net_device *ndev, struct device_node *np, phylink_init_advert(pl, pl->link_an_mode, pl->supported, pl->link_config.advertising); + mutex_lock(&phylink_mutex); + list_add_tail(&pl->node, &phylinks); + mutex_unlock(&phylink_mutex); + return pl; } EXPORT_SYMBOL_GPL(phylink_create); void phylink_destroy(struct phylink *pl) { + mutex_lock(&phylink_mutex); + list_del(&pl->node); + mutex_unlock(&phylink_mutex); + cancel_work_sync(&pl->resolve); kfree(pl); } @@ -900,4 +914,93 @@ int phylink_mii_ioctl(struct phylink *pl, struct ifreq *ifr, int cmd) } EXPORT_SYMBOL_GPL(phylink_mii_ioctl); + + +void phylink_disable(struct phylink *pl) +{ + set_bit(PHYLINK_DISABLE_LINK, &pl->phylink_disable_state); + flush_work(&pl->resolve); + + netif_carrier_off(pl->netdev); +} +EXPORT_SYMBOL_GPL(phylink_disable); + +void phylink_enable(struct phylink *pl) +{ + clear_bit(PHYLINK_DISABLE_LINK, &pl->phylink_disable_state); + phylink_run_resolve(pl); +} +EXPORT_SYMBOL_GPL(phylink_enable); + +int phylink_set_link(struct phylink *pl, unsigned int mode, u8 port, + const unsigned long *support) +{ + __ETHTOOL_DECLARE_LINK_MODE_MASK(mask); + int ret = 0; + + netdev_dbg(pl->netdev, "requesting link mode %s with support %*pb\n", + phylink_an_mode_str(mode), + __ETHTOOL_LINK_MODE_MASK_NBITS, support); + + if (mode == MLO_AN_FIXED) + return -EINVAL; + + linkmode_copy(mask, support); + + /* Ignore errors if we're expecting a PHY to attach later */ + ret = phylink_validate_support(pl, mode, mask); + if (ret && mode != MLO_AN_PHY) + return ret; + + mutex_lock(&pl->config_mutex); + if (mode == MLO_AN_8023Z && pl->phydev) { + ret = -EINVAL; + } else { + bool changed = !bitmap_equal(pl->supported, mask, + __ETHTOOL_LINK_MODE_MASK_NBITS); + if (changed) { + linkmode_copy(pl->supported, mask); + + phylink_init_advert(pl, mode, mask, + pl->link_config.advertising); + } + + if (pl->link_an_mode != mode) { + pl->link_an_mode = mode; + + changed = true; + + netdev_info(pl->netdev, "switched to %s link mode\n", + phylink_an_mode_str(mode)); + } + + pl->link_port = port; + + if (changed && !test_bit(PHYLINK_DISABLE_STOPPED, + &pl->phylink_disable_state)) + phylink_mac_config(pl, &pl->link_config); + } + mutex_unlock(&pl->config_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(phylink_set_link); + +struct phylink *phylink_lookup_by_netdev(struct net_device *ndev) +{ + struct phylink *pl, *found = NULL; + + mutex_lock(&phylink_mutex); + list_for_each_entry(pl, &phylinks, node) + if (pl->netdev == ndev) { + found = pl; + break; + } + + mutex_unlock(&phylink_mutex); + + return found; +} +EXPORT_SYMBOL_GPL(phylink_lookup_by_netdev); + MODULE_LICENSE("GPL"); diff --git a/include/linux/phylink.h b/include/linux/phylink.h index 3fef6e5fea82..66544396cddf 100644 --- a/include/linux/phylink.h +++ b/include/linux/phylink.h @@ -88,6 +88,12 @@ int phylink_ethtool_ksettings_set(struct phylink *, const struct ethtool_link_ksettings *); int phylink_mii_ioctl(struct phylink *, struct ifreq *, int); +int phylink_set_link(struct phylink *pl, unsigned int mode, u8 port, + const unsigned long *support); +void phylink_disable(struct phylink *pl); +void phylink_enable(struct phylink *pl); +struct phylink *phylink_lookup_by_netdev(struct net_device *ndev); + #define phylink_zero(bm) \ bitmap_zero(bm, __ETHTOOL_LINK_MODE_MASK_NBITS) #define __phylink_do_bit(op, bm, mode) \ -- cgit From 3bbd1061b3de3462538f6ba44e4ad2ee7495e9c6 Mon Sep 17 00:00:00 2001 From: Russell King Date: Wed, 28 Dec 2016 09:21:32 +0000 Subject: phylink: add support for MII ioctl access to Clause 45 PHYs Add support for reading and writing the clause 45 MII registers. Signed-off-by: Russell King --- drivers/net/phy/phylink.c | 157 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 124 insertions(+), 33 deletions(-) diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index 9441469b70fa..91d1a3da8648 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -810,16 +810,93 @@ static int phylink_mii_emul_read(struct net_device *ndev, unsigned int reg, return val; } +static int phylink_phy_read(struct phylink *pl, unsigned int phy_id, + unsigned int reg) +{ + struct phy_device *phydev = pl->phydev; + int prtad, devad; + + if (mdio_phy_id_is_c45(phy_id)) { + prtad = mdio_phy_id_prtad(phy_id); + devad = mdio_phy_id_devad(phy_id); + devad = MII_ADDR_C45 | devad << 16 | reg; + } else if (phydev->is_c45) { + switch (reg) { + case MII_BMCR: + case MII_BMSR: + case MII_PHYSID1: + case MII_PHYSID2: + devad = __ffs(phydev->c45_ids.devices_in_package); + break; + case MII_ADVERTISE: + case MII_LPA: + if (!(phydev->c45_ids.devices_in_package & MDIO_DEVS_AN)) + return -EINVAL; + devad = MDIO_MMD_AN; + if (reg == MII_ADVERTISE) + reg = MDIO_AN_ADVERTISE; + else + reg = MDIO_AN_LPA; + break; + default: + return -EINVAL; + } + prtad = phy_id; + devad = MII_ADDR_C45 | devad << 16 | reg; + } else { + prtad = phy_id; + devad = reg; + } + return mdiobus_read(pl->phydev->mdio.bus, prtad, devad); +} + +static int phylink_phy_write(struct phylink *pl, unsigned int phy_id, + unsigned int reg, unsigned int val) +{ + struct phy_device *phydev = pl->phydev; + int prtad, devad; + + if (mdio_phy_id_is_c45(phy_id)) { + prtad = mdio_phy_id_prtad(phy_id); + devad = mdio_phy_id_devad(phy_id); + devad = MII_ADDR_C45 | devad << 16 | reg; + } else if (phydev->is_c45) { + switch (reg) { + case MII_BMCR: + case MII_BMSR: + case MII_PHYSID1: + case MII_PHYSID2: + devad = __ffs(phydev->c45_ids.devices_in_package); + break; + case MII_ADVERTISE: + case MII_LPA: + if (!(phydev->c45_ids.devices_in_package & MDIO_DEVS_AN)) + return -EINVAL; + devad = MDIO_MMD_AN; + if (reg == MII_ADVERTISE) + reg = MDIO_AN_ADVERTISE; + else + reg = MDIO_AN_LPA; + break; + default: + return -EINVAL; + } + prtad = phy_id; + devad = MII_ADDR_C45 | devad << 16 | reg; + } else { + prtad = phy_id; + devad = reg; + } + + return mdiobus_write(phydev->mdio.bus, prtad, devad, val); +} + static int phylink_mii_read(struct phylink *pl, unsigned int phy_id, unsigned int reg) { struct phylink_link_state state; int val = 0xffff; - /* PHYs only exist for MLO_AN_PHY and MLO_AN_SGMII */ - if (pl->phydev) - return mdiobus_read(pl->phydev->mdio.bus, phy_id, reg); - switch (pl->link_an_mode) { case MLO_AN_FIXED: if (phy_id == 0) { @@ -852,12 +929,6 @@ static int phylink_mii_read(struct phylink *pl, unsigned int phy_id, static int phylink_mii_write(struct phylink *pl, unsigned int phy_id, unsigned int reg, unsigned int val) { - /* PHYs only exist for MLO_AN_PHY and MLO_AN_SGMII */ - if (pl->phydev) { - mdiobus_write(pl->phydev->mdio.bus, phy_id, reg, val); - return 0; - } - switch (pl->link_an_mode) { case MLO_AN_FIXED: break; @@ -876,36 +947,56 @@ static int phylink_mii_write(struct phylink *pl, unsigned int phy_id, int phylink_mii_ioctl(struct phylink *pl, struct ifreq *ifr, int cmd) { - struct mii_ioctl_data *mii_data = if_mii(ifr); - int val, ret; + struct mii_ioctl_data *mii = if_mii(ifr); + int ret; mutex_lock(&pl->config_mutex); - switch (cmd) { - case SIOCGMIIPHY: - mii_data->phy_id = pl->phydev ? pl->phydev->mdio.addr : 0; - /* fallthrough */ + if (pl->phydev) { + /* PHYs only exist for MLO_AN_PHY and MLO_AN_SGMII */ + switch (cmd) { + case SIOCGMIIPHY: + mii->phy_id = pl->phydev->mdio.addr; + + case SIOCGMIIREG: + ret = phylink_phy_read(pl, mii->phy_id, mii->reg_num); + if (ret >= 0) { + mii->val_out = ret; + ret = 0; + } + break; - case SIOCGMIIREG: - val = phylink_mii_read(pl, mii_data->phy_id, mii_data->reg_num); - if (val < 0) { - ret = val; - } else { - mii_data->val_out = val; - ret = 0; + case SIOCSMIIREG: + ret = phylink_phy_write(pl, mii->phy_id, mii->reg_num, + mii->val_in); + break; + + default: + ret = phy_mii_ioctl(pl->phydev, ifr, cmd); + break; } - break; + } else { + switch (cmd) { + case SIOCGMIIPHY: + mii->phy_id = 0; + + case SIOCGMIIREG: + ret = phylink_mii_read(pl, mii->phy_id, mii->reg_num); + if (ret >= 0) { + mii->val_out = ret; + ret = 0; + } + break; - case SIOCSMIIREG: - ret = phylink_mii_write(pl, mii_data->phy_id, mii_data->reg_num, - mii_data->val_in); - break; + case SIOCSMIIREG: + ret = phylink_mii_write(pl, mii->phy_id, mii->reg_num, + mii->val_in); + break; - default: - ret = -EOPNOTSUPP; - if (pl->phydev) - ret = phy_mii_ioctl(pl->phydev, ifr, cmd); - break; + default: + ret = -EOPNOTSUPP; + break; + } } mutex_unlock(&pl->config_mutex); -- cgit From a8297749218facf8356365e3dc8a3c572c649312 Mon Sep 17 00:00:00 2001 From: Russell King Date: Sat, 12 Sep 2015 18:43:39 +0100 Subject: sfp: add phylink based SFP module support Add support for SFP hotpluggable modules via phylink. This supports both copper and optical SFP modules, which require different Serdes modes in order to properly negotiate the link. Optical SFP modules typically require the Serdes link to be talking 1000base-X mode - this is the gigabit ethernet mode defined by the 802.3 standard. Copper SFP modules typically integrate a PHY in the module to convert from Serdes to copper, and the PHY will be configured by the vendor to either present a 1000base-X Serdes link (for fixed 1000base-T) or a SGMII Serdes link. However, this is vendor defined, so we instead detect the PHY, switch the link to SGMII mode, and use traditional PHY based negotiation. Signed-off-by: Russell King - set port and port capability depending on connector type - move autoneg mode setting to probe function - set "supported" speed capabilities depending on reported ethernet capabilities - checks for short read - dump eeprom base ID when checksum fails --- drivers/net/phy/Kconfig | 5 + drivers/net/phy/Makefile | 2 + drivers/net/phy/sfp.c | 1071 ++++++++++++++++++++++++++++++++++++++++++++++ include/linux/sfp.h | 344 +++++++++++++++ 4 files changed, 1422 insertions(+) create mode 100644 drivers/net/phy/sfp.c create mode 100644 include/linux/sfp.h diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index d709afcd6295..5dde394dfcf8 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -169,6 +169,11 @@ config MDIO_XGENE comment "MII PHY device drivers" +config SFP + tristate "SFP cage support" + depends on I2C && PHYLINK + select MDIO_I2C + config AMD_PHY tristate "AMD PHYs" ---help--- diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile index 28501b45ab3d..4b82ffdaa38b 100644 --- a/drivers/net/phy/Makefile +++ b/drivers/net/phy/Makefile @@ -24,6 +24,8 @@ obj-$(CONFIG_MDIO_SUN4I) += mdio-sun4i.o obj-$(CONFIG_MDIO_THUNDER) += mdio-thunder.o obj-$(CONFIG_MDIO_XGENE) += mdio-xgene.o +obj-$(CONFIG_SFP) += sfp.o + obj-$(CONFIG_AMD_PHY) += amd.o obj-$(CONFIG_AQUANTIA_PHY) += aquantia.o obj-$(CONFIG_AT803X_PHY) += at803x.o diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c new file mode 100644 index 000000000000..11ce20ef7dbc --- /dev/null +++ b/drivers/net/phy/sfp.c @@ -0,0 +1,1071 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mdio-i2c.h" +#include "swphy.h" + +enum { + GPIO_MODDEF0, + GPIO_LOS, + GPIO_TX_FAULT, + GPIO_TX_DISABLE, + GPIO_RATE_SELECT, + GPIO_MAX, + + SFP_F_PRESENT = BIT(GPIO_MODDEF0), + SFP_F_LOS = BIT(GPIO_LOS), + SFP_F_TX_FAULT = BIT(GPIO_TX_FAULT), + SFP_F_TX_DISABLE = BIT(GPIO_TX_DISABLE), + SFP_F_RATE_SELECT = BIT(GPIO_RATE_SELECT), + + SFP_E_INSERT = 0, + SFP_E_REMOVE, + SFP_E_DEV_DOWN, + SFP_E_DEV_UP, + SFP_E_TX_FAULT, + SFP_E_TX_CLEAR, + SFP_E_LOS_HIGH, + SFP_E_LOS_LOW, + SFP_E_TIMEOUT, + + SFP_MOD_EMPTY = 0, + SFP_MOD_PROBE, + SFP_MOD_PRESENT, + SFP_MOD_ERROR, + + SFP_DEV_DOWN = 0, + SFP_DEV_UP, + + SFP_S_DOWN = 0, + SFP_S_INIT, + SFP_S_WAIT_LOS, + SFP_S_LINK_UP, + SFP_S_TX_FAULT, + SFP_S_REINIT, + SFP_S_TX_DISABLE, +}; + +static const char *gpio_of_names[] = { + "moddef0", + "los", + "tx-fault", + "tx-disable", + "rate-select", +}; + +static const enum gpiod_flags gpio_flags[] = { + GPIOD_IN, + GPIOD_IN, + GPIOD_IN, + GPIOD_ASIS, + GPIOD_ASIS, +}; + +#define T_INIT_JIFFIES msecs_to_jiffies(300) +#define T_RESET_US 10 +#define T_FAULT_RECOVER msecs_to_jiffies(1000) + +/* SFP module presence detection is poor: the three MOD DEF signals are + * the same length on the PCB, which means it's possible for MOD DEF 0 to + * connect before the I2C bus on MOD DEF 1/2. + * + * The SFP MSA specifies 300ms as t_init (the time taken for TX_FAULT to + * be deasserted) but makes no mention of the earliest time before we can + * access the I2C EEPROM. However, Avago modules require 300ms. + */ +#define T_PROBE_INIT msecs_to_jiffies(300) +#define T_PROBE_RETRY msecs_to_jiffies(100) + +/* + * SFP modules appear to always have their PHY configured for bus address + * 0x56 (which with mdio-i2c, translates to a PHY address of 22). + */ +#define SFP_PHY_ADDR 22 + +/* + * Give this long for the PHY to reset. + */ +#define T_PHY_RESET_MS 50 + +static DEFINE_MUTEX(sfp_mutex); + +struct sfp { + struct device *dev; + struct i2c_adapter *i2c; + struct mii_bus *i2c_mii; + struct net_device *ndev; + struct phylink *phylink; + struct phy_device *mod_phy; + + unsigned int (*get_state)(struct sfp *); + void (*set_state)(struct sfp *, unsigned int); + int (*read)(struct sfp *, bool, u8, void *, size_t); + + struct gpio_desc *gpio[GPIO_MAX]; + + unsigned int state; + struct delayed_work poll; + struct delayed_work timeout; + struct mutex sm_mutex; + unsigned char sm_mod_state; + unsigned char sm_dev_state; + unsigned short sm_state; + unsigned int sm_retries; + + struct sfp_eeprom_id id; + + struct notifier_block netdev_nb; +}; + +static unsigned long poll_jiffies; + +static unsigned int sfp_gpio_get_state(struct sfp *sfp) +{ + unsigned int i, state, v; + + for (i = state = 0; i < GPIO_MAX; i++) { + if (gpio_flags[i] != GPIOD_IN || !sfp->gpio[i]) + continue; + + v = gpiod_get_value_cansleep(sfp->gpio[i]); + if (v) + state |= BIT(i); + } + + return state; +} + +static void sfp_gpio_set_state(struct sfp *sfp, unsigned int state) +{ + if (state & SFP_F_PRESENT) { + /* If the module is present, drive the signals */ + if (sfp->gpio[GPIO_TX_DISABLE]) + gpiod_direction_output(sfp->gpio[GPIO_TX_DISABLE], + state & SFP_F_TX_DISABLE); + if (state & SFP_F_RATE_SELECT) + gpiod_direction_output(sfp->gpio[GPIO_RATE_SELECT], + state & SFP_F_RATE_SELECT); + } else { + /* Otherwise, let them float to the pull-ups */ + if (sfp->gpio[GPIO_TX_DISABLE]) + gpiod_direction_input(sfp->gpio[GPIO_TX_DISABLE]); + if (state & SFP_F_RATE_SELECT) + gpiod_direction_input(sfp->gpio[GPIO_RATE_SELECT]); + } +} + +static int sfp__i2c_read(struct i2c_adapter *i2c, u8 bus_addr, u8 dev_addr, + void *buf, size_t len) +{ + struct i2c_msg msgs[2]; + int ret; + + msgs[0].addr = bus_addr; + msgs[0].flags = 0; + msgs[0].len = 1; + msgs[0].buf = &dev_addr; + msgs[1].addr = bus_addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = len; + msgs[1].buf = buf; + + ret = i2c_transfer(i2c, msgs, ARRAY_SIZE(msgs)); + if (ret < 0) + return ret; + + return ret == ARRAY_SIZE(msgs) ? len : 0; +} + +static int sfp_i2c_read(struct sfp *sfp, bool a2, u8 addr, void *buf, + size_t len) +{ + return sfp__i2c_read(sfp->i2c, a2 ? 0x51 : 0x50, addr, buf, len); +} + +static int sfp_i2c_configure(struct sfp *sfp, struct i2c_adapter *i2c) +{ + struct mii_bus *i2c_mii; + int ret; + + if (!i2c_check_functionality(i2c, I2C_FUNC_I2C)) + return -EINVAL; + + sfp->i2c = i2c; + sfp->read = sfp_i2c_read; + + i2c_mii = mdio_i2c_alloc(sfp->dev, i2c); + if (IS_ERR(i2c_mii)) + return PTR_ERR(i2c_mii); + + i2c_mii->name = "SFP I2C Bus"; + i2c_mii->phy_mask = ~0; + + ret = mdiobus_register(i2c_mii); + if (ret < 0) { + mdiobus_free(i2c_mii); + return ret; + } + + sfp->i2c_mii = i2c_mii; + + return 0; +} + + +/* Interface */ +static unsigned int sfp_get_state(struct sfp *sfp) +{ + return sfp->get_state(sfp); +} + +static void sfp_set_state(struct sfp *sfp, unsigned int state) +{ + sfp->set_state(sfp, state); +} + +static int sfp_read(struct sfp *sfp, bool a2, u8 addr, void *buf, size_t len) +{ + return sfp->read(sfp, a2, addr, buf, len); +} + +static unsigned int sfp_check(void *buf, size_t len) +{ + u8 *p, check; + + for (p = buf, check = 0; len; p++, len--) + check += *p; + + return check; +} + +/* Helpers */ +static void sfp_module_tx_disable(struct sfp *sfp) +{ + dev_dbg(sfp->dev, "tx disable %u -> %u\n", + sfp->state & SFP_F_TX_DISABLE ? 1 : 0, 1); + sfp->state |= SFP_F_TX_DISABLE; + sfp_set_state(sfp, sfp->state); +} + +static void sfp_module_tx_enable(struct sfp *sfp) +{ + dev_dbg(sfp->dev, "tx disable %u -> %u\n", + sfp->state & SFP_F_TX_DISABLE ? 1 : 0, 0); + sfp->state &= ~SFP_F_TX_DISABLE; + sfp_set_state(sfp, sfp->state); +} + +static void sfp_module_tx_fault_reset(struct sfp *sfp) +{ + unsigned int state = sfp->state; + + if (state & SFP_F_TX_DISABLE) + return; + + sfp_set_state(sfp, state | SFP_F_TX_DISABLE); + + udelay(T_RESET_US); + + sfp_set_state(sfp, state); +} + +/* SFP state machine */ +static void sfp_sm_set_timer(struct sfp *sfp, unsigned int timeout) +{ + if (timeout) + mod_delayed_work(system_power_efficient_wq, &sfp->timeout, + timeout); + else + cancel_delayed_work(&sfp->timeout); +} + +static void sfp_sm_next(struct sfp *sfp, unsigned int state, + unsigned int timeout) +{ + sfp->sm_state = state; + sfp_sm_set_timer(sfp, timeout); +} + +static void sfp_sm_ins_next(struct sfp *sfp, unsigned int state, unsigned int timeout) +{ + sfp->sm_mod_state = state; + sfp_sm_set_timer(sfp, timeout); +} + +static void sfp_sm_phy_detach(struct sfp *sfp) +{ + phy_stop(sfp->mod_phy); + if (sfp->phylink) + phylink_disconnect_phy(sfp->phylink); + phy_device_remove(sfp->mod_phy); + phy_device_free(sfp->mod_phy); + sfp->mod_phy = NULL; +} + +static void sfp_sm_probe_phy(struct sfp *sfp) +{ + struct phy_device *phy; + int err; + + msleep(T_PHY_RESET_MS); + + phy = mdiobus_scan(sfp->i2c_mii, SFP_PHY_ADDR); + if (IS_ERR(phy)) { + dev_err(sfp->dev, "mdiobus scan returned %ld\n", PTR_ERR(phy)); + return; + } + if (!phy) { + dev_info(sfp->dev, "no PHY detected\n"); + return; + } + + if (sfp->phylink) { + err = phylink_connect_phy(sfp->phylink, phy); + if (err) { + phy_device_remove(phy); + phy_device_free(phy); + dev_err(sfp->dev, "phylink_connect_phy failed: %d\n", + err); + return; + } + } + + sfp->mod_phy = phy; + phy_start(phy); +} + +static void sfp_sm_link_up(struct sfp *sfp) +{ + if (sfp->phylink) + phylink_enable(sfp->phylink); + + sfp_sm_next(sfp, SFP_S_LINK_UP, 0); +} + +static void sfp_sm_link_down(struct sfp *sfp) +{ + if (sfp->phylink) + phylink_disable(sfp->phylink); +} + +static void sfp_sm_link_check_los(struct sfp *sfp) +{ + unsigned int los = sfp->state & SFP_F_LOS; + + /* FIXME: what if neither SFP_OPTIONS_LOS_INVERTED nor + * SFP_OPTIONS_LOS_NORMAL are set? For now, we assume + * the same as SFP_OPTIONS_LOS_NORMAL set. + */ + if (sfp->id.ext.options & SFP_OPTIONS_LOS_INVERTED) + los ^= SFP_F_LOS; + + if (los) + sfp_sm_next(sfp, SFP_S_WAIT_LOS, 0); + else + sfp_sm_link_up(sfp); +} + +static void sfp_sm_fault(struct sfp *sfp, bool warn) +{ + if (sfp->sm_retries && !--sfp->sm_retries) { + dev_err(sfp->dev, "module persistently indicates fault, disabling\n"); + sfp_sm_next(sfp, SFP_S_TX_DISABLE, 0); + } else { + if (warn) + dev_err(sfp->dev, "module transmit fault indicated\n"); + + sfp_sm_next(sfp, SFP_S_TX_FAULT, T_FAULT_RECOVER); + } +} + +static void sfp_sm_mod_init(struct sfp *sfp) +{ + sfp_module_tx_enable(sfp); + + /* Wait t_init before indicating that the link is up, provided the + * current state indicates no TX_FAULT. If TX_FAULT clears before + * this time, that's fine too. + */ + sfp_sm_next(sfp, SFP_S_INIT, T_INIT_JIFFIES); + sfp->sm_retries = 5; + + if (sfp->phylink) { + /* Setting the serdes link mode is guesswork: there's no + * field in the EEPROM which indicates what mode should + * be used. + * + * If it's a gigabit-only fiber module, it probably does + * not have a PHY, so switch to 802.3z negotiation mode. + * Otherwise, switch to SGMII mode (which is required to + * support non-gigabit speeds) and probe for a PHY. + */ + if (sfp->id.base.e1000_base_t || + sfp->id.base.e100_base_lx || + sfp->id.base.e100_base_fx) + sfp_sm_probe_phy(sfp); + } +} + +static int sfp_sm_mod_probe(struct sfp *sfp) +{ + /* SFP module inserted - read I2C data */ + struct sfp_eeprom_id id; + char vendor[17]; + char part[17]; + char sn[17]; + char date[9]; + char rev[5]; + u8 check; + int err; + + err = sfp_read(sfp, false, 0, &id, sizeof(id)); + if (err < 0) { + dev_err(sfp->dev, "failed to read EEPROM: %d\n", err); + return -EAGAIN; + } + + if (err != sizeof(id)) { + dev_err(sfp->dev, "EEPROM short read: %d\n", err); + return -EAGAIN; + } + + /* Validate the checksum over the base structure */ + check = sfp_check(&id.base, sizeof(id.base) - 1); + if (check != id.base.cc_base) { + dev_err(sfp->dev, + "EEPROM base structure checksum failure: 0x%02x\n", + check); + print_hex_dump(KERN_ERR, "sfp EE: ", DUMP_PREFIX_OFFSET, + 16, 1, &id, sizeof(id.base) - 1, true); + return -EINVAL; + } + + check = sfp_check(&id.ext, sizeof(id.ext) - 1); + if (check != id.ext.cc_ext) { + dev_err(sfp->dev, + "EEPROM extended structure checksum failure: 0x%02x\n", + check); + memset(&id.ext, 0, sizeof(id.ext)); + } + + sfp->id = id; + + memcpy(vendor, sfp->id.base.vendor_name, 16); + vendor[16] = '\0'; + memcpy(part, sfp->id.base.vendor_pn, 16); + part[16] = '\0'; + memcpy(rev, sfp->id.base.vendor_rev, 4); + rev[4] = '\0'; + memcpy(sn, sfp->id.ext.vendor_sn, 16); + sn[16] = '\0'; + memcpy(date, sfp->id.ext.datecode, 8); + date[8] = '\0'; + + dev_info(sfp->dev, "module %s %s rev %s sn %s dc %s\n", vendor, part, rev, sn, date); + + /* We only support SFP modules, not the legacy GBIC modules. */ + if (sfp->id.base.phys_id != SFP_PHYS_ID_SFP || + sfp->id.base.phys_ext_id != SFP_PHYS_EXT_ID_SFP) { + dev_err(sfp->dev, "module is not SFP - phys id 0x%02x 0x%02x\n", + sfp->id.base.phys_id, sfp->id.base.phys_ext_id); + return -EINVAL; + } + + /* + * What isn't clear from the SFP documentation is whether this + * specifies the encoding expected on the TD/RD lines, or whether + * the TD/RD lines are always 8b10b encoded, but the transceiver + * converts. Eg, think of a copper SFP supporting 1G/100M/10M + * ethernet: this requires 8b10b encoding for 1G, 4b5b for 100M, + * and manchester for 10M. + */ + /* 1Gbit ethernet requires 8b10b encoding */ + if (sfp->id.base.encoding != SFP_ENCODING_8B10B) { + dev_err(sfp->dev, "module does not support 8B10B encoding\n"); + return -EINVAL; + } + + if (sfp->phylink) { + __ETHTOOL_DECLARE_LINK_MODE_MASK(support) = { 0, }; + int mode; + u8 port; + + phylink_set(support, Autoneg); + phylink_set(support, Pause); + phylink_set(support, Asym_Pause); + + /* Set ethtool support from the compliance fields. */ + if (sfp->id.base.e10g_base_sr) + phylink_set(support, 10000baseSR_Full); + if (sfp->id.base.e10g_base_lr) + phylink_set(support, 10000baseLR_Full); + if (sfp->id.base.e10g_base_lrm) + phylink_set(support, 10000baseLRM_Full); + if (sfp->id.base.e10g_base_er) + phylink_set(support, 10000baseER_Full); + if (sfp->id.base.e1000_base_sx || + sfp->id.base.e1000_base_lx || + sfp->id.base.e1000_base_cx) + phylink_set(support, 1000baseX_Full); + if (sfp->id.base.e1000_base_t) { + phylink_set(support, 1000baseT_Half); + phylink_set(support, 1000baseT_Full); + } + + /* port is the physical connector, set this from the + * connector field. + */ + switch (sfp->id.base.connector) { + case SFP_CONNECTOR_SC: + case SFP_CONNECTOR_FIBERJACK: + case SFP_CONNECTOR_LC: + case SFP_CONNECTOR_MT_RJ: + case SFP_CONNECTOR_MU: + case SFP_CONNECTOR_OPTICAL_PIGTAIL: + phylink_set(support, FIBRE); + port = PORT_FIBRE; + break; + + case SFP_CONNECTOR_RJ45: + phylink_set(support, TP); + port = PORT_TP; + break; + + case SFP_CONNECTOR_UNSPEC: + if (sfp->id.base.e1000_base_t) { + phylink_set(support, TP); + port = PORT_TP; + break; + } + /* fallthrough */ + case SFP_CONNECTOR_SG: /* guess */ + case SFP_CONNECTOR_MPO_1X12: + case SFP_CONNECTOR_MPO_2X16: + case SFP_CONNECTOR_HSSDC_II: + case SFP_CONNECTOR_COPPER_PIGTAIL: + case SFP_CONNECTOR_NOSEPARATE: + case SFP_CONNECTOR_MXC_2X16: + default: + /* a guess at the supported link modes */ + dev_warn(sfp->dev, "Guessing link modes, please report...\n"); + phylink_set(support, 1000baseT_Half); + phylink_set(support, 1000baseT_Full); + port = PORT_OTHER; + break; + } + + /* Setting the serdes link mode is guesswork: there's no + * field in the EEPROM which indicates what mode should + * be used. + * + * If it's a gigabit-only fiber module, it probably does + * not have a PHY, so switch to 802.3z negotiation mode. + * Otherwise, switch to SGMII mode (which is required to + * support non-gigabit speeds) and probe for a PHY. + */ + if (!sfp->id.base.e1000_base_t && + !sfp->id.base.e100_base_lx && + !sfp->id.base.e100_base_fx) { + mode = MLO_AN_8023Z; + } else { + mode = MLO_AN_SGMII; + } + + phylink_set_link(sfp->phylink, mode, port, support); + } + + return 0; +} + +static void sfp_sm_mod_remove(struct sfp *sfp) +{ + if (sfp->mod_phy) + sfp_sm_phy_detach(sfp); + + sfp_module_tx_disable(sfp); + + memset(&sfp->id, 0, sizeof(sfp->id)); + + dev_info(sfp->dev, "module removed\n"); +} + +static void sfp_sm_event(struct sfp *sfp, unsigned int event) +{ + mutex_lock(&sfp->sm_mutex); + + dev_dbg(sfp->dev, "SM: enter %u:%u:%u event %u\n", + sfp->sm_mod_state, sfp->sm_dev_state, sfp->sm_state, event); + + /* This state machine tracks the insert/remove state of + * the module, and handles probing the on-board EEPROM. + */ + switch (sfp->sm_mod_state) { + default: + if (event == SFP_E_INSERT) { + sfp_module_tx_disable(sfp); + sfp_sm_ins_next(sfp, SFP_MOD_PROBE, T_PROBE_INIT); + } + break; + + case SFP_MOD_PROBE: + if (event == SFP_E_REMOVE) { + sfp_sm_ins_next(sfp, SFP_MOD_EMPTY, 0); + } else if (event == SFP_E_TIMEOUT) { + int err = sfp_sm_mod_probe(sfp); + + if (err == 0) + sfp_sm_ins_next(sfp, SFP_MOD_PRESENT, 0); + else if (err == -EAGAIN) + sfp_sm_set_timer(sfp, T_PROBE_RETRY); + else + sfp_sm_ins_next(sfp, SFP_MOD_ERROR, 0); + } + break; + + case SFP_MOD_PRESENT: + case SFP_MOD_ERROR: + if (event == SFP_E_REMOVE) { + sfp_sm_mod_remove(sfp); + sfp_sm_ins_next(sfp, SFP_MOD_EMPTY, 0); + } + break; + } + + /* This state machine tracks the netdev up/down state */ + switch (sfp->sm_dev_state) { + default: + if (event == SFP_E_DEV_UP) + sfp->sm_dev_state = SFP_DEV_UP; + break; + + case SFP_DEV_UP: + if (event == SFP_E_DEV_DOWN) { + /* If the module has a PHY, avoid raising TX disable + * as this resets the PHY. Otherwise, raise it to + * turn the laser off. + */ + if (!sfp->mod_phy) + sfp_module_tx_disable(sfp); + sfp->sm_dev_state = SFP_DEV_DOWN; + } + break; + } + + /* Some events are global */ + if (sfp->sm_state != SFP_S_DOWN && + (sfp->sm_mod_state != SFP_MOD_PRESENT || + sfp->sm_dev_state != SFP_DEV_UP)) { + if (sfp->sm_state == SFP_S_LINK_UP && + sfp->sm_dev_state == SFP_DEV_UP) + sfp_sm_link_down(sfp); + if (sfp->mod_phy) + sfp_sm_phy_detach(sfp); + sfp_sm_next(sfp, SFP_S_DOWN, 0); + mutex_unlock(&sfp->sm_mutex); + return; + } + + /* The main state machine */ + switch (sfp->sm_state) { + case SFP_S_DOWN: + if (sfp->sm_mod_state == SFP_MOD_PRESENT && + sfp->sm_dev_state == SFP_DEV_UP) + sfp_sm_mod_init(sfp); + break; + + case SFP_S_INIT: + if (event == SFP_E_TIMEOUT && sfp->state & SFP_F_TX_FAULT) + sfp_sm_fault(sfp, true); + else if (event == SFP_E_TIMEOUT || event == SFP_E_TX_CLEAR) + sfp_sm_link_check_los(sfp); + break; + + case SFP_S_WAIT_LOS: + if (event == SFP_E_TX_FAULT) + sfp_sm_fault(sfp, true); + else if (event == + (sfp->id.ext.options & SFP_OPTIONS_LOS_INVERTED ? + SFP_E_LOS_HIGH : SFP_E_LOS_LOW)) + sfp_sm_link_up(sfp); + break; + + case SFP_S_LINK_UP: + if (event == SFP_E_TX_FAULT) { + sfp_sm_link_down(sfp); + sfp_sm_fault(sfp, true); + } else if (event == + (sfp->id.ext.options & SFP_OPTIONS_LOS_INVERTED ? + SFP_E_LOS_LOW : SFP_E_LOS_HIGH)) { + sfp_sm_link_down(sfp); + sfp_sm_next(sfp, SFP_S_WAIT_LOS, 0); + } + break; + + case SFP_S_TX_FAULT: + if (event == SFP_E_TIMEOUT) { + sfp_module_tx_fault_reset(sfp); + sfp_sm_next(sfp, SFP_S_REINIT, T_INIT_JIFFIES); + } + break; + + case SFP_S_REINIT: + if (event == SFP_E_TIMEOUT && sfp->state & SFP_F_TX_FAULT) { + sfp_sm_fault(sfp, false); + } else if (event == SFP_E_TIMEOUT || event == SFP_E_TX_CLEAR) { + dev_info(sfp->dev, "module transmit fault recovered\n"); + sfp_sm_link_check_los(sfp); + } + break; + + case SFP_S_TX_DISABLE: + break; + } + + dev_dbg(sfp->dev, "SM: exit %u:%u:%u\n", + sfp->sm_mod_state, sfp->sm_dev_state, sfp->sm_state); + + mutex_unlock(&sfp->sm_mutex); +} + +#if 0 +static int sfp_phy_module_info(struct phy_device *phy, + struct ethtool_modinfo *modinfo) +{ + struct sfp *sfp = phy->priv; + + /* locking... and check module is present */ + + if (sfp->id.ext.sff8472_compliance) { + modinfo->type = ETH_MODULE_SFF_8472; + modinfo->eeprom_len = ETH_MODULE_SFF_8472_LEN; + } else { + modinfo->type = ETH_MODULE_SFF_8079; + modinfo->eeprom_len = ETH_MODULE_SFF_8079_LEN; + } + return 0; +} + +static int sfp_phy_module_eeprom(struct phy_device *phy, + struct ethtool_eeprom *ee, u8 *data) +{ + struct sfp *sfp = phy->priv; + unsigned int first, last, len; + int ret; + + if (ee->len == 0) + return -EINVAL; + + first = ee->offset; + last = ee->offset + ee->len; + if (first < ETH_MODULE_SFF_8079_LEN) { + len = last; + if (len > ETH_MODULE_SFF_8079_LEN) + len = ETH_MODULE_SFF_8079_LEN; + len -= first; + + ret = sfp->read(sfp, false, first, data, len); + if (ret < 0) + return ret; + + first += len; + data += len; + } + if (first >= ETH_MODULE_SFF_8079_LEN && last > first) { + len = last - first; + + ret = sfp->read(sfp, true, first, data, len); + if (ret < 0) + return ret; + } + return 0; +} +#endif + +static void sfp_timeout(struct work_struct *work) +{ + struct sfp *sfp = container_of(work, struct sfp, timeout.work); + + sfp_sm_event(sfp, SFP_E_TIMEOUT); +} + +static void sfp_check_state(struct sfp *sfp) +{ + unsigned int state, i, changed; + + state = sfp_get_state(sfp); + changed = state ^ sfp->state; + changed &= SFP_F_PRESENT | SFP_F_LOS | SFP_F_TX_FAULT; + + for (i = 0; i < GPIO_MAX; i++) + if (changed & BIT(i)) + dev_dbg(sfp->dev, "%s %u -> %u\n", gpio_of_names[i], + !!(sfp->state & BIT(i)), !!(state & BIT(i))); + + state |= sfp->state & (SFP_F_TX_DISABLE | SFP_F_RATE_SELECT); + sfp->state = state; + + if (changed & SFP_F_PRESENT) + sfp_sm_event(sfp, state & SFP_F_PRESENT ? + SFP_E_INSERT : SFP_E_REMOVE); + + if (changed & SFP_F_TX_FAULT) + sfp_sm_event(sfp, state & SFP_F_TX_FAULT ? + SFP_E_TX_FAULT : SFP_E_TX_CLEAR); + + if (changed & SFP_F_LOS) + sfp_sm_event(sfp, state & SFP_F_LOS ? + SFP_E_LOS_HIGH : SFP_E_LOS_LOW); +} + +static irqreturn_t sfp_irq(int irq, void *data) +{ + struct sfp *sfp = data; + + sfp_check_state(sfp); + + return IRQ_HANDLED; +} + +static void sfp_poll(struct work_struct *work) +{ + struct sfp *sfp = container_of(work, struct sfp, poll.work); + + sfp_check_state(sfp); + mod_delayed_work(system_wq, &sfp->poll, poll_jiffies); +} + +static int sfp_netdev_notify(struct notifier_block *nb, unsigned long act, void *data) +{ + struct sfp *sfp = container_of(nb, struct sfp, netdev_nb); + struct netdev_notifier_info *info = data; + struct net_device *ndev = info->dev; + + if (!sfp->ndev || ndev != sfp->ndev) + return NOTIFY_DONE; + + switch (act) { + case NETDEV_UP: + sfp_sm_event(sfp, SFP_E_DEV_UP); + break; + + case NETDEV_GOING_DOWN: + sfp_sm_event(sfp, SFP_E_DEV_DOWN); + break; + + case NETDEV_UNREGISTER: + if (sfp->mod_phy && sfp->phylink) + phylink_disconnect_phy(sfp->phylink); + sfp->phylink = NULL; + dev_put(sfp->ndev); + sfp->ndev = NULL; + break; + } + return NOTIFY_OK; +} + +static struct sfp *sfp_alloc(struct device *dev) +{ + struct sfp *sfp; + + sfp = kzalloc(sizeof(*sfp), GFP_KERNEL); + if (!sfp) + return ERR_PTR(-ENOMEM); + + sfp->dev = dev; + + mutex_init(&sfp->sm_mutex); + INIT_DELAYED_WORK(&sfp->poll, sfp_poll); + INIT_DELAYED_WORK(&sfp->timeout, sfp_timeout); + + sfp->netdev_nb.notifier_call = sfp_netdev_notify; + + return sfp; +} + +static void sfp_destroy(struct sfp *sfp) +{ + cancel_delayed_work_sync(&sfp->poll); + cancel_delayed_work_sync(&sfp->timeout); + if (sfp->i2c_mii) { + mdiobus_unregister(sfp->i2c_mii); + mdiobus_free(sfp->i2c_mii); + } + if (sfp->i2c) + i2c_put_adapter(sfp->i2c); + of_node_put(sfp->dev->of_node); + kfree(sfp); +} + +static void sfp_cleanup(void *data) +{ + struct sfp *sfp = data; + + sfp_destroy(sfp); +} + +static int sfp_probe(struct platform_device *pdev) +{ + struct sfp *sfp; + bool poll = false; + int irq, err, i; + + sfp = sfp_alloc(&pdev->dev); + if (IS_ERR(sfp)) + return PTR_ERR(sfp); + + platform_set_drvdata(pdev, sfp); + + err = devm_add_action(sfp->dev, sfp_cleanup, sfp); + if (err < 0) + return err; + + if (pdev->dev.of_node) { + struct device_node *node = pdev->dev.of_node; + struct device_node *np; + + np = of_parse_phandle(node, "i2c-bus", 0); + if (np) { + struct i2c_adapter *i2c; + + i2c = of_find_i2c_adapter_by_node(np); + of_node_put(np); + if (!i2c) + return -EPROBE_DEFER; + + err = sfp_i2c_configure(sfp, i2c); + if (err < 0) { + i2c_put_adapter(i2c); + return err; + } + } + + for (i = 0; i < GPIO_MAX; i++) { + sfp->gpio[i] = devm_gpiod_get_optional(sfp->dev, + gpio_of_names[i], gpio_flags[i]); + if (IS_ERR(sfp->gpio[i])) + return PTR_ERR(sfp->gpio[i]); + } + + sfp->get_state = sfp_gpio_get_state; + sfp->set_state = sfp_gpio_set_state; + + np = of_parse_phandle(node, "sfp,ethernet", 0); + if (!np) { + dev_err(sfp->dev, "missing sfp,ethernet property\n"); + return -EINVAL; + } + + sfp->ndev = of_find_net_device_by_node(np); + if (!sfp->ndev) { + dev_err(sfp->dev, "ethernet device not found\n"); + return -EPROBE_DEFER; + } + + dev_hold(sfp->ndev); + put_device(&sfp->ndev->dev); + + sfp->phylink = phylink_lookup_by_netdev(sfp->ndev); + if (!sfp->phylink) { + dev_err(sfp->dev, "phylink for %s not found\n", + netdev_name(sfp->ndev)); + return -EPROBE_DEFER; + } + + phylink_disable(sfp->phylink); + } + + sfp->state = sfp_get_state(sfp); + if (sfp->gpio[GPIO_TX_DISABLE] && + gpiod_get_value_cansleep(sfp->gpio[GPIO_TX_DISABLE])) + sfp->state |= SFP_F_TX_DISABLE; + if (sfp->gpio[GPIO_RATE_SELECT] && + gpiod_get_value_cansleep(sfp->gpio[GPIO_RATE_SELECT])) + sfp->state |= SFP_F_RATE_SELECT; + sfp_set_state(sfp, sfp->state); + sfp_module_tx_disable(sfp); + if (sfp->state & SFP_F_PRESENT) + sfp_sm_event(sfp, SFP_E_INSERT); + + for (i = 0; i < GPIO_MAX; i++) { + if (gpio_flags[i] != GPIOD_IN || !sfp->gpio[i]) + continue; + + irq = gpiod_to_irq(sfp->gpio[i]); + if (!irq) { + poll = true; + continue; + } + + err = devm_request_threaded_irq(sfp->dev, irq, NULL, sfp_irq, + IRQF_ONESHOT | + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING, + dev_name(sfp->dev), sfp); + if (err) + poll = true; + } + + if (poll) + mod_delayed_work(system_wq, &sfp->poll, poll_jiffies); + + register_netdevice_notifier(&sfp->netdev_nb); + + return 0; +} + +static int sfp_remove(struct platform_device *pdev) +{ + struct sfp *sfp = platform_get_drvdata(pdev); + + unregister_netdevice_notifier(&sfp->netdev_nb); + if (sfp->ndev) + dev_put(sfp->ndev); + + return 0; +} + +static const struct of_device_id sfp_of_match[] = { + { .compatible = "sff,sfp", }, + { }, +}; +MODULE_DEVICE_TABLE(of, sfp_of_match); + +static struct platform_driver sfp_driver = { + .probe = sfp_probe, + .remove = sfp_remove, + .driver = { + .name = "sfp", + .of_match_table = sfp_of_match, + }, +}; + +static int sfp_init(void) +{ + poll_jiffies = msecs_to_jiffies(100); + + return platform_driver_register(&sfp_driver); +} +module_init(sfp_init); + +static void sfp_exit(void) +{ + platform_driver_unregister(&sfp_driver); +} +module_exit(sfp_exit); + +MODULE_ALIAS("platform:sfp"); +MODULE_AUTHOR("Russell King"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/sfp.h b/include/linux/sfp.h new file mode 100644 index 000000000000..b5ca85c3ac34 --- /dev/null +++ b/include/linux/sfp.h @@ -0,0 +1,344 @@ +#ifndef LINUX_SFP_H +#define LINUX_SFP_H + +struct __packed sfp_eeprom_base { + u8 phys_id; + u8 phys_ext_id; + u8 connector; +#if defined __BIG_ENDIAN_BITFIELD + u8 e10g_base_er:1; + u8 e10g_base_lrm:1; + u8 e10g_base_lr:1; + u8 e10g_base_sr:1; + u8 if_1x_sx:1; + u8 if_1x_lx:1; + u8 if_1x_copper_active:1; + u8 if_1x_copper_passive:1; + + u8 escon_mmf_1310_led:1; + u8 escon_smf_1310_laser:1; + u8 sonet_oc192_short_reach:1; + u8 sonet_reach_bit1:1; + u8 sonet_reach_bit2:1; + u8 sonet_oc48_long_reach:1; + u8 sonet_oc48_intermediate_reach:1; + u8 sonet_oc48_short_reach:1; + + u8 unallocated_5_7:1; + u8 sonet_oc12_smf_long_reach:1; + u8 sonet_oc12_smf_intermediate_reach:1; + u8 sonet_oc12_short_reach:1; + u8 unallocated_5_3:1; + u8 sonet_oc3_smf_long_reach:1; + u8 sonet_oc3_smf_intermediate_reach:1; + u8 sonet_oc3_short_reach:1; + + u8 e_base_px:1; + u8 e_base_bx10:1; + u8 e100_base_fx:1; + u8 e100_base_lx:1; + u8 e1000_base_t:1; + u8 e1000_base_cx:1; + u8 e1000_base_lx:1; + u8 e1000_base_sx:1; + + u8 fc_ll_v:1; + u8 fc_ll_s:1; + u8 fc_ll_i:1; + u8 fc_ll_l:1; + u8 fc_ll_m:1; + u8 fc_tech_sa:1; + u8 fc_tech_lc:1; + u8 fc_tech_electrical_inter_enclosure:1; + + u8 fc_tech_electrical_intra_enclosure:1; + u8 fc_tech_sn:1; + u8 fc_tech_sl:1; + u8 fc_tech_ll:1; + u8 sfp_ct_active:1; + u8 sfp_ct_passive:1; + u8 unallocated_8_1:1; + u8 unallocated_8_0:1; + + u8 fc_media_tw:1; + u8 fc_media_tp:1; + u8 fc_media_mi:1; + u8 fc_media_tv:1; + u8 fc_media_m6:1; + u8 fc_media_m5:1; + u8 unallocated_9_1:1; + u8 fc_media_sm:1; + + u8 fc_speed_1200:1; + u8 fc_speed_800:1; + u8 fc_speed_1600:1; + u8 fc_speed_400:1; + u8 fc_speed_3200:1; + u8 fc_speed_200:1; + u8 unallocated_10_1:1; + u8 fc_speed_100:1; +#elif defined __LITTLE_ENDIAN_BITFIELD + u8 if_1x_copper_passive:1; + u8 if_1x_copper_active:1; + u8 if_1x_lx:1; + u8 if_1x_sx:1; + u8 e10g_base_sr:1; + u8 e10g_base_lr:1; + u8 e10g_base_lrm:1; + u8 e10g_base_er:1; + + u8 sonet_oc3_short_reach:1; + u8 sonet_oc3_smf_intermediate_reach:1; + u8 sonet_oc3_smf_long_reach:1; + u8 unallocated_5_3:1; + u8 sonet_oc12_short_reach:1; + u8 sonet_oc12_smf_intermediate_reach:1; + u8 sonet_oc12_smf_long_reach:1; + u8 unallocated_5_7:1; + + u8 sonet_oc48_short_reach:1; + u8 sonet_oc48_intermediate_reach:1; + u8 sonet_oc48_long_reach:1; + u8 sonet_reach_bit2:1; + u8 sonet_reach_bit1:1; + u8 sonet_oc192_short_reach:1; + u8 escon_smf_1310_laser:1; + u8 escon_mmf_1310_led:1; + + u8 e1000_base_sx:1; + u8 e1000_base_lx:1; + u8 e1000_base_cx:1; + u8 e1000_base_t:1; + u8 e100_base_lx:1; + u8 e100_base_fx:1; + u8 e_base_bx10:1; + u8 e_base_px:1; + + u8 fc_tech_electrical_inter_enclosure:1; + u8 fc_tech_lc:1; + u8 fc_tech_sa:1; + u8 fc_ll_m:1; + u8 fc_ll_l:1; + u8 fc_ll_i:1; + u8 fc_ll_s:1; + u8 fc_ll_v:1; + + u8 unallocated_8_0:1; + u8 unallocated_8_1:1; + u8 sfp_ct_passive:1; + u8 sfp_ct_active:1; + u8 fc_tech_ll:1; + u8 fc_tech_sl:1; + u8 fc_tech_sn:1; + u8 fc_tech_electrical_intra_enclosure:1; + + u8 fc_media_sm:1; + u8 unallocated_9_1:1; + u8 fc_media_m5:1; + u8 fc_media_m6:1; + u8 fc_media_tv:1; + u8 fc_media_mi:1; + u8 fc_media_tp:1; + u8 fc_media_tw:1; + + u8 fc_speed_100:1; + u8 unallocated_10_1:1; + u8 fc_speed_200:1; + u8 fc_speed_3200:1; + u8 fc_speed_400:1; + u8 fc_speed_1600:1; + u8 fc_speed_800:1; + u8 fc_speed_1200:1; +#else +#error Unknown Endian +#endif + u8 encoding; + u8 br_nominal; + u8 rate_id; + u8 link_len[6]; + char vendor_name[16]; + u8 reserved36; + char vendor_oui[3]; + char vendor_pn[16]; + char vendor_rev[4]; + union { + __be16 optical_wavelength; + u8 cable_spec; + }; + u8 reserved62; + u8 cc_base; +}; + +struct __packed sfp_eeprom_ext { + __be16 options; + u8 br_max; + u8 br_min; + char vendor_sn[16]; + char datecode[8]; + u8 diagmon; + u8 enhopts; + u8 sff8472_compliance; + u8 cc_ext; +}; + +struct __packed sfp_eeprom_id { + struct sfp_eeprom_base base; + struct sfp_eeprom_ext ext; +}; + +/* SFP EEPROM registers */ +enum { + SFP_PHYS_ID = 0x00, + SFP_PHYS_EXT_ID = 0x01, + SFP_CONNECTOR = 0x02, + SFP_COMPLIANCE = 0x03, + SFP_ENCODING = 0x0b, + SFP_BR_NOMINAL = 0x0c, + SFP_RATE_ID = 0x0d, + SFP_LINK_LEN_SM_KM = 0x0e, + SFP_LINK_LEN_SM_100M = 0x0f, + SFP_LINK_LEN_50UM_OM2_10M = 0x10, + SFP_LINK_LEN_62_5UM_OM1_10M = 0x11, + SFP_LINK_LEN_COPPER_1M = 0x12, + SFP_LINK_LEN_50UM_OM4_10M = 0x12, + SFP_LINK_LEN_50UM_OM3_10M = 0x13, + SFP_VENDOR_NAME = 0x14, + SFP_VENDOR_OUI = 0x25, + SFP_VENDOR_PN = 0x28, + SFP_VENDOR_REV = 0x38, + SFP_OPTICAL_WAVELENGTH_MSB = 0x3c, + SFP_OPTICAL_WAVELENGTH_LSB = 0x3d, + SFP_CABLE_SPEC = 0x3c, + SFP_CC_BASE = 0x3f, + SFP_OPTIONS = 0x40, /* 2 bytes, MSB, LSB */ + SFP_BR_MAX = 0x42, + SFP_BR_MIN = 0x43, + SFP_VENDOR_SN = 0x44, + SFP_DATECODE = 0x54, + SFP_DIAGMON = 0x5c, + SFP_ENHOPTS = 0x5d, + SFP_SFF8472_COMPLIANCE = 0x5e, + SFP_CC_EXT = 0x5f, + + SFP_PHYS_ID_SFP = 0x03, + SFP_PHYS_EXT_ID_SFP = 0x04, + SFP_CONNECTOR_UNSPEC = 0x00, + /* codes 01-05 not supportable on SFP, but some modules have single SC */ + SFP_CONNECTOR_SC = 0x01, + SFP_CONNECTOR_FIBERJACK = 0x06, + SFP_CONNECTOR_LC = 0x07, + SFP_CONNECTOR_MT_RJ = 0x08, + SFP_CONNECTOR_MU = 0x09, + SFP_CONNECTOR_SG = 0x0a, + SFP_CONNECTOR_OPTICAL_PIGTAIL = 0x0b, + SFP_CONNECTOR_MPO_1X12 = 0x0c, + SFP_CONNECTOR_MPO_2X16 = 0x0d, + SFP_CONNECTOR_HSSDC_II = 0x20, + SFP_CONNECTOR_COPPER_PIGTAIL = 0x21, + SFP_CONNECTOR_RJ45 = 0x22, + SFP_CONNECTOR_NOSEPARATE = 0x23, + SFP_CONNECTOR_MXC_2X16 = 0x24, + SFP_ENCODING_UNSPEC = 0x00, + SFP_ENCODING_8B10B = 0x01, + SFP_ENCODING_4B5B = 0x02, + SFP_ENCODING_NRZ = 0x03, + SFP_ENCODING_MANCHESTER = 0x04, + SFP_OPTIONS_HIGH_POWER_LEVEL = BIT(13), + SFP_OPTIONS_PAGING_A2 = BIT(12), + SFP_OPTIONS_RETIMER = BIT(11), + SFP_OPTIONS_COOLED_XCVR = BIT(10), + SFP_OPTIONS_POWER_DECL = BIT(9), + SFP_OPTIONS_RX_LINEAR_OUT = BIT(8), + SFP_OPTIONS_RX_DECISION_THRESH = BIT(7), + SFP_OPTIONS_TUNABLE_TX = BIT(6), + SFP_OPTIONS_RATE_SELECT = BIT(5), + SFP_OPTIONS_TX_DISABLE = BIT(4), + SFP_OPTIONS_TX_FAULT = BIT(3), + SFP_OPTIONS_LOS_INVERTED = BIT(2), + SFP_OPTIONS_LOS_NORMAL = BIT(1), + SFP_DIAGMON_DDM = BIT(6), + SFP_DIAGMON_INT_CAL = BIT(5), + SFP_DIAGMON_EXT_CAL = BIT(4), + SFP_DIAGMON_RXPWR_AVG = BIT(3), + SFP_DIAGMON_ADDRMODE = BIT(2), + SFP_ENHOPTS_ALARMWARN = BIT(7), + SFP_ENHOPTS_SOFT_TX_DISABLE = BIT(6), + SFP_ENHOPTS_SOFT_TX_FAULT = BIT(5), + SFP_ENHOPTS_SOFT_RX_LOS = BIT(4), + SFP_ENHOPTS_SOFT_RATE_SELECT = BIT(3), + SFP_ENHOPTS_APP_SELECT_SFF8079 = BIT(2), + SFP_ENHOPTS_SOFT_RATE_SFF8431 = BIT(1), + SFP_SFF8472_COMPLIANCE_NONE = 0x00, + SFP_SFF8472_COMPLIANCE_REV9_3 = 0x01, + SFP_SFF8472_COMPLIANCE_REV9_5 = 0x02, + SFP_SFF8472_COMPLIANCE_REV10_2 = 0x03, + SFP_SFF8472_COMPLIANCE_REV10_4 = 0x04, + SFP_SFF8472_COMPLIANCE_REV11_0 = 0x05, + SFP_SFF8472_COMPLIANCE_REV11_3 = 0x06, + SFP_SFF8472_COMPLIANCE_REV11_4 = 0x07, + SFP_SFF8472_COMPLIANCE_REV12_0 = 0x08, +}; + +/* SFP Diagnostics */ +enum { + /* Alarm and warnings stored MSB at lower address then LSB */ + SFP_TEMP_HIGH_ALARM = 0x00, + SFP_TEMP_LOW_ALARM = 0x02, + SFP_TEMP_HIGH_WARN = 0x04, + SFP_TEMP_LOW_WARN = 0x06, + SFP_VOLT_HIGH_ALARM = 0x08, + SFP_VOLT_LOW_ALARM = 0x0a, + SFP_VOLT_HIGH_WARN = 0x0c, + SFP_VOLT_LOW_WARN = 0x0e, + SFP_BIAS_HIGH_ALARM = 0x10, + SFP_BIAS_LOW_ALARM = 0x12, + SFP_BIAS_HIGH_WARN = 0x14, + SFP_BIAS_LOW_WARN = 0x16, + SFP_TXPWR_HIGH_ALARM = 0x18, + SFP_TXPWR_LOW_ALARM = 0x1a, + SFP_TXPWR_HIGH_WARN = 0x1c, + SFP_TXPWR_LOW_WARN = 0x1e, + SFP_RXPWR_HIGH_ALARM = 0x20, + SFP_RXPWR_LOW_ALARM = 0x22, + SFP_RXPWR_HIGH_WARN = 0x24, + SFP_RXPWR_LOW_WARN = 0x26, + SFP_LASER_TEMP_HIGH_ALARM = 0x28, + SFP_LASER_TEMP_LOW_ALARM = 0x2a, + SFP_LASER_TEMP_HIGH_WARN = 0x2c, + SFP_LASER_TEMP_LOW_WARN = 0x2e, + SFP_TEC_CUR_HIGH_ALARM = 0x30, + SFP_TEC_CUR_LOW_ALARM = 0x32, + SFP_TEC_CUR_HIGH_WARN = 0x34, + SFP_TEC_CUR_LOW_WARN = 0x36, + SFP_CAL_RXPWR4 = 0x38, + SFP_CAL_RXPWR3 = 0x3c, + SFP_CAL_RXPWR2 = 0x40, + SFP_CAL_RXPWR1 = 0x44, + SFP_CAL_RXPWR0 = 0x48, + SFP_CAL_TXI_SLOPE = 0x4c, + SFP_CAL_TXI_OFFSET = 0x4e, + SFP_CAL_TXPWR_SLOPE = 0x50, + SFP_CAL_TXPWR_OFFSET = 0x52, + SFP_CAL_T_SLOPE = 0x54, + SFP_CAL_T_OFFSET = 0x56, + SFP_CAL_V_SLOPE = 0x58, + SFP_CAL_V_OFFSET = 0x5a, + SFP_CHKSUM = 0x5f, + + SFP_TEMP = 0x60, + SFP_VCC = 0x62, + SFP_TX_BIAS = 0x64, + SFP_TX_POWER = 0x66, + SFP_RX_POWER = 0x68, + SFP_LASER_TEMP = 0x6a, + SFP_TEC_CUR = 0x6c, + + SFP_STATUS = 0x6e, + SFP_ALARM = 0x70, + + SFP_EXT_STATUS = 0x76, + SFP_VSL = 0x78, + SFP_PAGE = 0x7f, +}; + +#endif -- cgit From 62d8c1e7dad587fc5e73a99207041a44ed9ce299 Mon Sep 17 00:00:00 2001 From: Russell King Date: Sun, 13 Sep 2015 01:06:31 +0100 Subject: sfp: display SFP module information Signed-off-by: Russell King --- drivers/net/phy/sfp.c | 247 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 246 insertions(+), 1 deletion(-) diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c index 11ce20ef7dbc..0cdec71786d0 100644 --- a/drivers/net/phy/sfp.c +++ b/drivers/net/phy/sfp.c @@ -251,6 +251,182 @@ static unsigned int sfp_check(void *buf, size_t len) return check; } +static const char *sfp_link_len(char *buf, size_t size, unsigned int length, + unsigned int multiplier) +{ + if (length == 0) + return "unsupported/unspecified"; + + if (length == 255) { + *buf++ = '>'; + size -= 1; + length -= 1; + } + + length *= multiplier; + + if (length >= 1000) + snprintf(buf, size, "%u.%0*ukm", + length / 1000, + multiplier > 100 ? 1 : + multiplier > 10 ? 2 : 3, + length % 1000); + else + snprintf(buf, size, "%um", length); + + return buf; +} + +struct bitfield { + unsigned int mask; + unsigned int val; + const char *str; +}; + +static const struct bitfield sfp_options[] = { + { + .mask = SFP_OPTIONS_HIGH_POWER_LEVEL, + .val = SFP_OPTIONS_HIGH_POWER_LEVEL, + .str = "hpl", + }, { + .mask = SFP_OPTIONS_PAGING_A2, + .val = SFP_OPTIONS_PAGING_A2, + .str = "paginga2", + }, { + .mask = SFP_OPTIONS_RETIMER, + .val = SFP_OPTIONS_RETIMER, + .str = "retimer", + }, { + .mask = SFP_OPTIONS_COOLED_XCVR, + .val = SFP_OPTIONS_COOLED_XCVR, + .str = "cooled", + }, { + .mask = SFP_OPTIONS_POWER_DECL, + .val = SFP_OPTIONS_POWER_DECL, + .str = "powerdecl", + }, { + .mask = SFP_OPTIONS_RX_LINEAR_OUT, + .val = SFP_OPTIONS_RX_LINEAR_OUT, + .str = "rxlinear", + }, { + .mask = SFP_OPTIONS_RX_DECISION_THRESH, + .val = SFP_OPTIONS_RX_DECISION_THRESH, + .str = "rxthresh", + }, { + .mask = SFP_OPTIONS_TUNABLE_TX, + .val = SFP_OPTIONS_TUNABLE_TX, + .str = "tunabletx", + }, { + .mask = SFP_OPTIONS_RATE_SELECT, + .val = SFP_OPTIONS_RATE_SELECT, + .str = "ratesel", + }, { + .mask = SFP_OPTIONS_TX_DISABLE, + .val = SFP_OPTIONS_TX_DISABLE, + .str = "txdisable", + }, { + .mask = SFP_OPTIONS_TX_FAULT, + .val = SFP_OPTIONS_TX_FAULT, + .str = "txfault", + }, { + .mask = SFP_OPTIONS_LOS_INVERTED, + .val = SFP_OPTIONS_LOS_INVERTED, + .str = "los-", + }, { + .mask = SFP_OPTIONS_LOS_NORMAL, + .val = SFP_OPTIONS_LOS_NORMAL, + .str = "los+", + }, { } +}; + +static const struct bitfield diagmon[] = { + { + .mask = SFP_DIAGMON_DDM, + .val = SFP_DIAGMON_DDM, + .str = "ddm", + }, { + .mask = SFP_DIAGMON_INT_CAL, + .val = SFP_DIAGMON_INT_CAL, + .str = "intcal", + }, { + .mask = SFP_DIAGMON_EXT_CAL, + .val = SFP_DIAGMON_EXT_CAL, + .str = "extcal", + }, { + .mask = SFP_DIAGMON_RXPWR_AVG, + .val = SFP_DIAGMON_RXPWR_AVG, + .str = "rxpwravg", + }, { } +}; + +static const char *sfp_bitfield(char *out, size_t outsz, const struct bitfield *bits, unsigned int val) +{ + char *p = out; + int n; + + *p = '\0'; + while (bits->mask) { + if ((val & bits->mask) == bits->val) { + n = snprintf(p, outsz, "%s%s", + out != p ? ", " : "", + bits->str); + if (n == outsz) + break; + p += n; + outsz -= n; + } + bits++; + } + + return out; +} + +static const char *sfp_connector(unsigned int connector) +{ + switch (connector) { + case SFP_CONNECTOR_UNSPEC: + return "unknown/unspecified"; + case SFP_CONNECTOR_SC: + return "SC"; + case SFP_CONNECTOR_FIBERJACK: + return "Fiberjack"; + case SFP_CONNECTOR_LC: + return "LC"; + case SFP_CONNECTOR_MT_RJ: + return "MT-RJ"; + case SFP_CONNECTOR_MU: + return "MU"; + case SFP_CONNECTOR_SG: + return "SG"; + case SFP_CONNECTOR_OPTICAL_PIGTAIL: + return "Optical pigtail"; + case SFP_CONNECTOR_HSSDC_II: + return "HSSDC II"; + case SFP_CONNECTOR_COPPER_PIGTAIL: + return "Copper pigtail"; + default: + return "unknown"; + } +} + +static const char *sfp_encoding(unsigned int encoding) +{ + switch (encoding) { + case SFP_ENCODING_UNSPEC: + return "unspecified"; + case SFP_ENCODING_8B10B: + return "8b10b"; + case SFP_ENCODING_4B5B: + return "4b5b"; + case SFP_ENCODING_NRZ: + return "NRZ"; + case SFP_ENCODING_MANCHESTER: + return "MANCHESTER"; + default: + return "unknown"; + } +} + /* Helpers */ static void sfp_module_tx_disable(struct sfp *sfp) { @@ -428,6 +604,7 @@ static int sfp_sm_mod_probe(struct sfp *sfp) char sn[17]; char date[9]; char rev[5]; + char options[80]; u8 check; int err; @@ -471,10 +648,78 @@ static int sfp_sm_mod_probe(struct sfp *sfp) rev[4] = '\0'; memcpy(sn, sfp->id.ext.vendor_sn, 16); sn[16] = '\0'; - memcpy(date, sfp->id.ext.datecode, 8); + date[0] = sfp->id.ext.datecode[4]; + date[1] = sfp->id.ext.datecode[5]; + date[2] = '-'; + date[3] = sfp->id.ext.datecode[2]; + date[4] = sfp->id.ext.datecode[3]; + date[5] = '-'; + date[6] = sfp->id.ext.datecode[0]; + date[7] = sfp->id.ext.datecode[1]; date[8] = '\0'; dev_info(sfp->dev, "module %s %s rev %s sn %s dc %s\n", vendor, part, rev, sn, date); + dev_info(sfp->dev, " %s connector, encoding %s, nominal bitrate %u.%uGbps +%u%% -%u%%\n", + sfp_connector(sfp->id.base.connector), + sfp_encoding(sfp->id.base.encoding), + sfp->id.base.br_nominal / 10, + sfp->id.base.br_nominal % 10, + sfp->id.ext.br_max, sfp->id.ext.br_min); + dev_info(sfp->dev, " 1000BaseSX%c 1000BaseLX%c 1000BaseCX%c 1000BaseT%c 100BaseTLX%c 1000BaseFX%c BaseBX10%c BasePX%c\n", + sfp->id.base.e1000_base_sx ? '+' : '-', + sfp->id.base.e1000_base_lx ? '+' : '-', + sfp->id.base.e1000_base_cx ? '+' : '-', + sfp->id.base.e1000_base_t ? '+' : '-', + sfp->id.base.e100_base_lx ? '+' : '-', + sfp->id.base.e100_base_fx ? '+' : '-', + sfp->id.base.e_base_bx10 ? '+' : '-', + sfp->id.base.e_base_px ? '+' : '-'); + + if (!sfp->id.base.sfp_ct_passive && !sfp->id.base.sfp_ct_active && + !sfp->id.base.e1000_base_t) { + char len_9um[16], len_om[16]; + + dev_info(sfp->dev, " Wavelength %unm, fiber lengths:\n", + be16_to_cpup(&sfp->id.base.optical_wavelength)); + + if (sfp->id.base.link_len[0] == 255) + strcpy(len_9um, ">254km"); + else if (sfp->id.base.link_len[1] && sfp->id.base.link_len[1] != 255) + sprintf(len_9um, "%um", + sfp->id.base.link_len[1] * 100); + else if (sfp->id.base.link_len[0]) + sprintf(len_9um, "%ukm", sfp->id.base.link_len[0]); + else if (sfp->id.base.link_len[1] == 255) + strcpy(len_9um, ">25.4km"); + else + strcpy(len_9um, "unsupported"); + + dev_info(sfp->dev, " 9µm SM : %s\n", len_9um); + dev_info(sfp->dev, " 62.5µm MM OM1: %s\n", + sfp_link_len(len_om, sizeof(len_om), + sfp->id.base.link_len[3], 10)); + dev_info(sfp->dev, " 50µm MM OM2: %s\n", + sfp_link_len(len_om, sizeof(len_om), + sfp->id.base.link_len[2], 10)); + dev_info(sfp->dev, " 50µm MM OM3: %s\n", + sfp_link_len(len_om, sizeof(len_om), + sfp->id.base.link_len[5], 10)); + dev_info(sfp->dev, " 50µm MM OM4: %s\n", + sfp_link_len(len_om, sizeof(len_om), + sfp->id.base.link_len[4], 10)); + } else { + char len[16]; + dev_info(sfp->dev, " Copper length: %s\n", + sfp_link_len(len, sizeof(len), + sfp->id.base.link_len[4], 1)); + } + + dev_info(sfp->dev, " Options: %s\n", + sfp_bitfield(options, sizeof(options), sfp_options, + be16_to_cpu(sfp->id.ext.options))); + dev_info(sfp->dev, " Diagnostics: %s\n", + sfp_bitfield(options, sizeof(options), diagmon, + sfp->id.ext.diagmon)); /* We only support SFP modules, not the legacy GBIC modules. */ if (sfp->id.base.phys_id != SFP_PHYS_ID_SFP || -- cgit From f6ce9d0e422cf29e16761eeacf46b20452a536b1 Mon Sep 17 00:00:00 2001 From: Russell King Date: Wed, 16 Sep 2015 21:27:10 +0100 Subject: net: mvneta: convert to phylink Convert mvneta to use phylink, which models the MAC to PHY link in a generic, reusable form. Signed-off-by: Russell King - remove unused sync status --- drivers/net/ethernet/marvell/Kconfig | 2 +- drivers/net/ethernet/marvell/mvneta.c | 461 +++++++++++++++++----------------- 2 files changed, 232 insertions(+), 231 deletions(-) diff --git a/drivers/net/ethernet/marvell/Kconfig b/drivers/net/ethernet/marvell/Kconfig index 2664827ddecd..423124d4d56f 100644 --- a/drivers/net/ethernet/marvell/Kconfig +++ b/drivers/net/ethernet/marvell/Kconfig @@ -57,7 +57,7 @@ config MVNETA tristate "Marvell Armada 370/38x/XP network interface support" depends on PLAT_ORION select MVMDIO - select FIXED_PHY + select PHYLINK ---help--- This driver supports the network interface units in the Marvell ARMADA XP, ARMADA 370 and ARMADA 38x SoC family. diff --git a/drivers/net/ethernet/marvell/mvneta.c b/drivers/net/ethernet/marvell/mvneta.c index 707bc4680b9b..068fee29bc10 100644 --- a/drivers/net/ethernet/marvell/mvneta.c +++ b/drivers/net/ethernet/marvell/mvneta.c @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -188,6 +189,7 @@ #define MVNETA_GMAC_CTRL_0 0x2c00 #define MVNETA_GMAC_MAX_RX_SIZE_SHIFT 2 #define MVNETA_GMAC_MAX_RX_SIZE_MASK 0x7ffc +#define MVNETA_GMAC0_PORT_1000BASE_X BIT(1) #define MVNETA_GMAC0_PORT_ENABLE BIT(0) #define MVNETA_GMAC_CTRL_2 0x2c08 #define MVNETA_GMAC2_INBAND_AN_ENABLE BIT(0) @@ -203,13 +205,19 @@ #define MVNETA_GMAC_TX_FLOW_CTRL_ENABLE BIT(5) #define MVNETA_GMAC_RX_FLOW_CTRL_ACTIVE BIT(6) #define MVNETA_GMAC_TX_FLOW_CTRL_ACTIVE BIT(7) +#define MVNETA_GMAC_AN_COMPLETE BIT(11) +#define MVNETA_GMAC_SYNC_OK BIT(14) #define MVNETA_GMAC_AUTONEG_CONFIG 0x2c0c #define MVNETA_GMAC_FORCE_LINK_DOWN BIT(0) #define MVNETA_GMAC_FORCE_LINK_PASS BIT(1) #define MVNETA_GMAC_INBAND_AN_ENABLE BIT(2) +#define MVNETA_GMAC_AN_BYPASS_ENABLE BIT(3) +#define MVNETA_GMAC_INBAND_RESTART_AN BIT(4) #define MVNETA_GMAC_CONFIG_MII_SPEED BIT(5) #define MVNETA_GMAC_CONFIG_GMII_SPEED BIT(6) #define MVNETA_GMAC_AN_SPEED_EN BIT(7) +#define MVNETA_GMAC_CONFIG_FLOW_CTRL BIT(8) +#define MVNETA_GMAC_ADVERT_SYM_FLOW_CTRL BIT(9) #define MVNETA_GMAC_AN_FLOW_CTRL_EN BIT(11) #define MVNETA_GMAC_CONFIG_FULL_DUPLEX BIT(12) #define MVNETA_GMAC_AN_DUPLEX_EN BIT(13) @@ -399,14 +407,9 @@ struct mvneta_port { u16 tx_ring_size; u16 rx_ring_size; - struct mii_bus *mii_bus; - phy_interface_t phy_interface; - struct device_node *phy_node; - unsigned int link; - unsigned int duplex; - unsigned int speed; + struct device_node *dn; unsigned int tx_csum_limit; - unsigned int use_inband_status:1; + struct phylink *phylink; struct mvneta_bm *bm_priv; struct mvneta_bm_pool *pool_long; @@ -1240,44 +1243,6 @@ static void mvneta_set_other_mcast_table(struct mvneta_port *pp, int queue) mvreg_write(pp, MVNETA_DA_FILT_OTH_MCAST + offset, val); } -static void mvneta_set_autoneg(struct mvneta_port *pp, int enable) -{ - u32 val; - - if (enable) { - val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG); - val &= ~(MVNETA_GMAC_FORCE_LINK_PASS | - MVNETA_GMAC_FORCE_LINK_DOWN | - MVNETA_GMAC_AN_FLOW_CTRL_EN); - val |= MVNETA_GMAC_INBAND_AN_ENABLE | - MVNETA_GMAC_AN_SPEED_EN | - MVNETA_GMAC_AN_DUPLEX_EN; - mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val); - - val = mvreg_read(pp, MVNETA_GMAC_CLOCK_DIVIDER); - val |= MVNETA_GMAC_1MS_CLOCK_ENABLE; - mvreg_write(pp, MVNETA_GMAC_CLOCK_DIVIDER, val); - - val = mvreg_read(pp, MVNETA_GMAC_CTRL_2); - val |= MVNETA_GMAC2_INBAND_AN_ENABLE; - mvreg_write(pp, MVNETA_GMAC_CTRL_2, val); - } else { - val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG); - val &= ~(MVNETA_GMAC_INBAND_AN_ENABLE | - MVNETA_GMAC_AN_SPEED_EN | - MVNETA_GMAC_AN_DUPLEX_EN); - mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val); - - val = mvreg_read(pp, MVNETA_GMAC_CLOCK_DIVIDER); - val &= ~MVNETA_GMAC_1MS_CLOCK_ENABLE; - mvreg_write(pp, MVNETA_GMAC_CLOCK_DIVIDER, val); - - val = mvreg_read(pp, MVNETA_GMAC_CTRL_2); - val &= ~MVNETA_GMAC2_INBAND_AN_ENABLE; - mvreg_write(pp, MVNETA_GMAC_CTRL_2, val); - } -} - static void mvneta_percpu_unmask_interrupt(void *arg) { struct mvneta_port *pp = arg; @@ -1425,7 +1390,6 @@ static void mvneta_defaults_set(struct mvneta_port *pp) val &= ~MVNETA_PHY_POLLING_ENABLE; mvreg_write(pp, MVNETA_UNIT_CONTROL, val); - mvneta_set_autoneg(pp, pp->use_inband_status); mvneta_set_ucast_table(pp, -1); mvneta_set_special_mcast_table(pp, -1); mvneta_set_other_mcast_table(pp, -1); @@ -2618,26 +2582,11 @@ static irqreturn_t mvneta_isr(int irq, void *dev_id) return IRQ_HANDLED; } -static int mvneta_fixed_link_update(struct mvneta_port *pp, - struct phy_device *phy) +static void mvneta_link_change(struct mvneta_port *pp) { - struct fixed_phy_status status; - struct fixed_phy_status changed = {}; u32 gmac_stat = mvreg_read(pp, MVNETA_GMAC_STATUS); - status.link = !!(gmac_stat & MVNETA_GMAC_LINK_UP); - if (gmac_stat & MVNETA_GMAC_SPEED_1000) - status.speed = SPEED_1000; - else if (gmac_stat & MVNETA_GMAC_SPEED_100) - status.speed = SPEED_100; - else - status.speed = SPEED_10; - status.duplex = !!(gmac_stat & MVNETA_GMAC_FULL_DUPLEX); - changed.link = 1; - changed.speed = 1; - changed.duplex = 1; - fixed_phy_update_state(phy, &status, &changed); - return 0; + phylink_mac_change(pp->phylink, !!(gmac_stat & MVNETA_GMAC_LINK_UP)); } /* NAPI handler @@ -2653,7 +2602,6 @@ static int mvneta_poll(struct napi_struct *napi, int budget) u32 cause_rx_tx; int rx_queue; struct mvneta_port *pp = netdev_priv(napi->dev); - struct net_device *ndev = pp->dev; struct mvneta_pcpu_port *port = this_cpu_ptr(pp->ports); if (!netif_running(pp->dev)) { @@ -2667,12 +2615,11 @@ static int mvneta_poll(struct napi_struct *napi, int budget) u32 cause_misc = mvreg_read(pp, MVNETA_INTR_MISC_CAUSE); mvreg_write(pp, MVNETA_INTR_MISC_CAUSE, 0); - if (pp->use_inband_status && (cause_misc & - (MVNETA_CAUSE_PHY_STATUS_CHANGE | - MVNETA_CAUSE_LINK_CHANGE | - MVNETA_CAUSE_PSC_SYNC_CHANGE))) { - mvneta_fixed_link_update(pp, ndev->phydev); - } + + if (cause_misc & (MVNETA_CAUSE_PHY_STATUS_CHANGE | + MVNETA_CAUSE_LINK_CHANGE | + MVNETA_CAUSE_PSC_SYNC_CHANGE)) + mvneta_link_change(pp); } /* Release Tx descriptors */ @@ -2966,7 +2913,6 @@ static int mvneta_setup_txqs(struct mvneta_port *pp) static void mvneta_start_dev(struct mvneta_port *pp) { int cpu; - struct net_device *ndev = pp->dev; mvneta_max_rx_size_set(pp, pp->pkt_size); mvneta_txq_max_tx_size_set(pp, pp->pkt_size); @@ -2989,16 +2935,15 @@ static void mvneta_start_dev(struct mvneta_port *pp) MVNETA_CAUSE_LINK_CHANGE | MVNETA_CAUSE_PSC_SYNC_CHANGE); - phy_start(ndev->phydev); + phylink_start(pp->phylink); netif_tx_start_all_queues(pp->dev); } static void mvneta_stop_dev(struct mvneta_port *pp) { unsigned int cpu; - struct net_device *ndev = pp->dev; - phy_stop(ndev->phydev); + phylink_stop(pp->phylink); for_each_online_cpu(cpu) { struct mvneta_pcpu_port *port = per_cpu_ptr(pp->ports, cpu); @@ -3168,99 +3113,210 @@ static int mvneta_set_mac_addr(struct net_device *dev, void *addr) return 0; } -static void mvneta_adjust_link(struct net_device *ndev) +static void mvneta_validate_support(struct net_device *ndev, unsigned int mode, + unsigned long *support) +{ + __ETHTOOL_DECLARE_LINK_MODE_MASK(mask) = { 0, }; + + /* Allow all the expected bits */ + phylink_set(mask, Autoneg); + phylink_set(mask, TP); + phylink_set(mask, AUI); + phylink_set(mask, MII); + phylink_set(mask, FIBRE); + phylink_set(mask, BNC); + phylink_set(mask, Backplane); + + /* Half-duplex at speeds higher than 100Mbit is unsupported */ + phylink_set(mask, 1000baseT_Full); + phylink_set(mask, 1000baseX_Full); + + if (mode != MLO_AN_8023Z) { + /* 10M and 100M are only supported in non-802.3z mode */ + phylink_set(mask, 10baseT_Half); + phylink_set(mask, 10baseT_Full); + phylink_set(mask, 100baseT_Half); + phylink_set(mask, 100baseT_Full); + } else { + phylink_set(mask, Pause); + } + + bitmap_and(support, support, mask, __ETHTOOL_LINK_MODE_MASK_NBITS); +} + +static int mvneta_mac_link_state(struct net_device *ndev, + struct phylink_link_state *state) { struct mvneta_port *pp = netdev_priv(ndev); - struct phy_device *phydev = ndev->phydev; - int status_change = 0; + u32 gmac_stat; - if (phydev->link) { - if ((pp->speed != phydev->speed) || - (pp->duplex != phydev->duplex)) { - u32 val; + gmac_stat = mvreg_read(pp, MVNETA_GMAC_STATUS); - val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG); - val &= ~(MVNETA_GMAC_CONFIG_MII_SPEED | - MVNETA_GMAC_CONFIG_GMII_SPEED | - MVNETA_GMAC_CONFIG_FULL_DUPLEX); + if (gmac_stat & MVNETA_GMAC_SPEED_1000) + state->speed = SPEED_1000; + else if (gmac_stat & MVNETA_GMAC_SPEED_100) + state->speed = SPEED_100; + else + state->speed = SPEED_10; - if (phydev->duplex) - val |= MVNETA_GMAC_CONFIG_FULL_DUPLEX; + state->an_complete = !!(gmac_stat & MVNETA_GMAC_AN_COMPLETE); + state->link = !!(gmac_stat & MVNETA_GMAC_LINK_UP); + state->duplex = !!(gmac_stat & MVNETA_GMAC_FULL_DUPLEX); - if (phydev->speed == SPEED_1000) - val |= MVNETA_GMAC_CONFIG_GMII_SPEED; - else if (phydev->speed == SPEED_100) - val |= MVNETA_GMAC_CONFIG_MII_SPEED; + return 1; +} - mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val); +static void mvneta_mac_an_restart(struct net_device *ndev, unsigned int mode) +{ + struct mvneta_port *pp = netdev_priv(ndev); - pp->duplex = phydev->duplex; - pp->speed = phydev->speed; - } + if (mode == MLO_AN_8023Z) { + u32 gmac_an = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG); + + mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, + gmac_an | MVNETA_GMAC_INBAND_RESTART_AN); + mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, + gmac_an & ~MVNETA_GMAC_INBAND_RESTART_AN); } +} - if (phydev->link != pp->link) { - if (!phydev->link) { - pp->duplex = -1; - pp->speed = 0; - } +static void mvneta_mac_config(struct net_device *ndev, unsigned int mode, + const struct phylink_link_state *state) +{ + struct mvneta_port *pp = netdev_priv(ndev); + u32 new_ctrl0, gmac_ctrl0 = mvreg_read(pp, MVNETA_GMAC_CTRL_0); + u32 new_ctrl2, gmac_ctrl2 = mvreg_read(pp, MVNETA_GMAC_CTRL_2); + u32 new_clk, gmac_clk = mvreg_read(pp, MVNETA_GMAC_CLOCK_DIVIDER); + u32 new_an, gmac_an = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG); + + new_ctrl0 = gmac_ctrl0 & ~MVNETA_GMAC0_PORT_1000BASE_X; + new_ctrl2 = gmac_ctrl2 & ~MVNETA_GMAC2_INBAND_AN_ENABLE; + new_clk = gmac_clk & ~MVNETA_GMAC_1MS_CLOCK_ENABLE; + new_an = gmac_an & ~(MVNETA_GMAC_INBAND_AN_ENABLE | + MVNETA_GMAC_INBAND_RESTART_AN | + MVNETA_GMAC_CONFIG_MII_SPEED | + MVNETA_GMAC_CONFIG_GMII_SPEED | + MVNETA_GMAC_AN_SPEED_EN | + MVNETA_GMAC_ADVERT_SYM_FLOW_CTRL | + MVNETA_GMAC_CONFIG_FLOW_CTRL | + MVNETA_GMAC_AN_FLOW_CTRL_EN | + MVNETA_GMAC_CONFIG_FULL_DUPLEX | + MVNETA_GMAC_AN_DUPLEX_EN); + + if (phylink_test(state->advertising, Pause)) + new_an |= MVNETA_GMAC_ADVERT_SYM_FLOW_CTRL; + + switch (mode) { + case MLO_AN_SGMII: + /* SGMII mode receives the state from the PHY */ + new_ctrl2 |= MVNETA_GMAC2_INBAND_AN_ENABLE; + new_clk |= MVNETA_GMAC_1MS_CLOCK_ENABLE; + new_an = (new_an & ~(MVNETA_GMAC_FORCE_LINK_DOWN | + MVNETA_GMAC_FORCE_LINK_PASS)) | + MVNETA_GMAC_INBAND_AN_ENABLE | + MVNETA_GMAC_AN_SPEED_EN | + MVNETA_GMAC_AN_DUPLEX_EN; + break; - pp->link = phydev->link; - status_change = 1; + case MLO_AN_8023Z: + /* 802.3z negotiation - only 1000base-X */ + new_ctrl0 |= MVNETA_GMAC0_PORT_1000BASE_X; + new_clk |= MVNETA_GMAC_1MS_CLOCK_ENABLE; + new_an = (new_an & ~(MVNETA_GMAC_FORCE_LINK_DOWN | + MVNETA_GMAC_FORCE_LINK_PASS)) | + MVNETA_GMAC_INBAND_AN_ENABLE | + MVNETA_GMAC_CONFIG_GMII_SPEED | + /* The MAC only supports FD mode */ + MVNETA_GMAC_CONFIG_FULL_DUPLEX; + + if (state->an_enabled) + new_an |= MVNETA_GMAC_AN_FLOW_CTRL_EN; + break; + + default: + /* Phy or fixed speed */ + if (state->duplex) + new_an |= MVNETA_GMAC_CONFIG_FULL_DUPLEX; + + if (state->speed == SPEED_1000) + new_an |= MVNETA_GMAC_CONFIG_GMII_SPEED; + else if (state->speed == SPEED_100) + new_an |= MVNETA_GMAC_CONFIG_MII_SPEED; + break; } - if (status_change) { - if (phydev->link) { - if (!pp->use_inband_status) { - u32 val = mvreg_read(pp, - MVNETA_GMAC_AUTONEG_CONFIG); - val &= ~MVNETA_GMAC_FORCE_LINK_DOWN; - val |= MVNETA_GMAC_FORCE_LINK_PASS; - mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, - val); - } - mvneta_port_up(pp); - } else { - if (!pp->use_inband_status) { - u32 val = mvreg_read(pp, - MVNETA_GMAC_AUTONEG_CONFIG); - val &= ~MVNETA_GMAC_FORCE_LINK_PASS; - val |= MVNETA_GMAC_FORCE_LINK_DOWN; - mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, - val); - } - mvneta_port_down(pp); - } - phy_print_status(phydev); + /* Armada 370 documentation says we can only change the port mode + * and in-band enable when the link is down, so force it down + * while making these changes. We also do this for GMAC_CTRL2 */ + if ((new_ctrl0 ^ gmac_ctrl0) & MVNETA_GMAC0_PORT_1000BASE_X || + (new_ctrl2 ^ gmac_ctrl2) & MVNETA_GMAC2_INBAND_AN_ENABLE || + (new_an ^ gmac_an) & MVNETA_GMAC_INBAND_AN_ENABLE) { + mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, + (gmac_an & ~MVNETA_GMAC_FORCE_LINK_PASS) | + MVNETA_GMAC_FORCE_LINK_DOWN); } + + if (new_ctrl0 != gmac_ctrl0) + mvreg_write(pp, MVNETA_GMAC_CTRL_0, new_ctrl0); + if (new_ctrl2 != gmac_ctrl2) + mvreg_write(pp, MVNETA_GMAC_CTRL_2, new_ctrl2); + if (new_clk != gmac_clk) + mvreg_write(pp, MVNETA_GMAC_CLOCK_DIVIDER, new_clk); + if (new_an != gmac_an) + mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, new_an); } -static int mvneta_mdio_probe(struct mvneta_port *pp) +static void mvneta_mac_link_down(struct net_device *ndev, unsigned int mode) +{ + struct mvneta_port *pp = netdev_priv(ndev); + u32 val; + + mvneta_port_down(pp); + + if (mode == MLO_AN_PHY || mode == MLO_AN_FIXED) { + val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG); + val &= ~MVNETA_GMAC_FORCE_LINK_PASS; + val |= MVNETA_GMAC_FORCE_LINK_DOWN; + mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val); + } +} + +static void mvneta_mac_link_up(struct net_device *ndev, unsigned int mode) { - struct phy_device *phy_dev; + struct mvneta_port *pp = netdev_priv(ndev); + u32 val; - phy_dev = of_phy_connect(pp->dev, pp->phy_node, mvneta_adjust_link, 0, - pp->phy_interface); - if (!phy_dev) { - netdev_err(pp->dev, "could not find the PHY\n"); - return -ENODEV; + if (mode == MLO_AN_PHY || mode == MLO_AN_FIXED) { + val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG); + val &= ~MVNETA_GMAC_FORCE_LINK_DOWN; + val |= MVNETA_GMAC_FORCE_LINK_PASS; + mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val); } - phy_dev->supported &= PHY_GBIT_FEATURES; - phy_dev->advertising = phy_dev->supported; + mvneta_port_up(pp); +} - pp->link = 0; - pp->duplex = 0; - pp->speed = 0; +static const struct phylink_mac_ops mvneta_phylink_ops = { + .validate_support = mvneta_validate_support, + .mac_link_state = mvneta_mac_link_state, + .mac_an_restart = mvneta_mac_an_restart, + .mac_config = mvneta_mac_config, + .mac_link_down = mvneta_mac_link_down, + .mac_link_up = mvneta_mac_link_up, +}; - return 0; +static int mvneta_mdio_probe(struct mvneta_port *pp) +{ + int err = phylink_of_phy_connect(pp->phylink, pp->dn); + if (err) + netdev_err(pp->dev, "could not attach PHY\n"); + + return err; } static void mvneta_mdio_remove(struct mvneta_port *pp) { - struct net_device *ndev = pp->dev; - - phy_disconnect(ndev->phydev); + phylink_disconnect_phy(pp->phylink); } /* Electing a CPU must be done in an atomic way: it should be done @@ -3518,10 +3574,9 @@ static int mvneta_stop(struct net_device *dev) static int mvneta_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) { - if (!dev->phydev) - return -ENOTSUPP; + struct mvneta_port *pp = netdev_priv(dev); - return phy_mii_ioctl(dev->phydev, ifr, cmd); + return phylink_mii_ioctl(pp->phylink, ifr, cmd); } /* Ethtool methods */ @@ -3532,44 +3587,18 @@ mvneta_ethtool_set_link_ksettings(struct net_device *ndev, const struct ethtool_link_ksettings *cmd) { struct mvneta_port *pp = netdev_priv(ndev); - struct phy_device *phydev = ndev->phydev; - - if (!phydev) - return -ENODEV; - - if ((cmd->base.autoneg == AUTONEG_ENABLE) != pp->use_inband_status) { - u32 val; - mvneta_set_autoneg(pp, cmd->base.autoneg == AUTONEG_ENABLE); - - if (cmd->base.autoneg == AUTONEG_DISABLE) { - val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG); - val &= ~(MVNETA_GMAC_CONFIG_MII_SPEED | - MVNETA_GMAC_CONFIG_GMII_SPEED | - MVNETA_GMAC_CONFIG_FULL_DUPLEX); - - if (phydev->duplex) - val |= MVNETA_GMAC_CONFIG_FULL_DUPLEX; - - if (phydev->speed == SPEED_1000) - val |= MVNETA_GMAC_CONFIG_GMII_SPEED; - else if (phydev->speed == SPEED_100) - val |= MVNETA_GMAC_CONFIG_MII_SPEED; - - mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val); - } - - pp->use_inband_status = (cmd->base.autoneg == AUTONEG_ENABLE); - netdev_info(pp->dev, "autoneg status set to %i\n", - pp->use_inband_status); + return phylink_ethtool_ksettings_set(pp->phylink, cmd); +} - if (netif_running(ndev)) { - mvneta_port_down(pp); - mvneta_port_up(pp); - } - } +/* Get link ksettings for ethtools */ +static int +mvneta_ethtool_get_link_ksettings(struct net_device *ndev, + struct ethtool_link_ksettings *cmd) +{ + struct mvneta_port *pp = netdev_priv(ndev); - return phy_ethtool_ksettings_set(ndev->phydev, cmd); + return phylink_ethtool_ksettings_get(pp->phylink, cmd); } /* Set interrupt coalescing for ethtools */ @@ -3677,26 +3706,28 @@ static void mvneta_ethtool_update_stats(struct mvneta_port *pp) { const struct mvneta_statistic *s; void __iomem *base = pp->base; - u32 high, low, val; - u64 val64; + u32 high, low; + u64 val; int i; for (i = 0, s = mvneta_statistics; s < mvneta_statistics + ARRAY_SIZE(mvneta_statistics); s++, i++) { + val = 0; + switch (s->type) { case T_REG_32: val = readl_relaxed(base + s->offset); - pp->ethtool_stats[i] += val; break; case T_REG_64: /* Docs say to read low 32-bit then high */ low = readl_relaxed(base + s->offset); high = readl_relaxed(base + s->offset + 4); - val64 = (u64)high << 32 | low; - pp->ethtool_stats[i] += val64; + val = (u64)high << 32 | low; break; } + + pp->ethtool_stats[i] += val; } } @@ -3845,7 +3876,7 @@ const struct ethtool_ops mvneta_eth_tool_ops = { .get_rxnfc = mvneta_ethtool_get_rxnfc, .get_rxfh = mvneta_ethtool_get_rxfh, .set_rxfh = mvneta_ethtool_set_rxfh, - .get_link_ksettings = phy_ethtool_get_link_ksettings, + .get_link_ksettings = mvneta_ethtool_get_link_ksettings, .set_link_ksettings = mvneta_ethtool_set_link_ksettings, }; @@ -3972,14 +4003,13 @@ static int mvneta_probe(struct platform_device *pdev) const struct mbus_dram_target_info *dram_target_info; struct resource *res; struct device_node *dn = pdev->dev.of_node; - struct device_node *phy_node; struct device_node *bm_node; struct mvneta_port *pp; struct net_device *dev; + struct phylink *phylink; const char *dt_mac_addr; char hw_mac_addr[ETH_ALEN]; const char *mac_from; - const char *managed; int tx_csum_limit; int phy_mode; int err; @@ -3995,31 +4025,11 @@ static int mvneta_probe(struct platform_device *pdev) goto err_free_netdev; } - phy_node = of_parse_phandle(dn, "phy", 0); - if (!phy_node) { - if (!of_phy_is_fixed_link(dn)) { - dev_err(&pdev->dev, "no PHY specified\n"); - err = -ENODEV; - goto err_free_irq; - } - - err = of_phy_register_fixed_link(dn); - if (err < 0) { - dev_err(&pdev->dev, "cannot register fixed PHY\n"); - goto err_free_irq; - } - - /* In the case of a fixed PHY, the DT node associated - * to the PHY is the Ethernet MAC DT node. - */ - phy_node = of_node_get(dn); - } - phy_mode = of_get_phy_mode(dn); if (phy_mode < 0) { dev_err(&pdev->dev, "incorrect phy-mode\n"); err = -EINVAL; - goto err_put_phy_node; + goto err_free_irq; } dev->tx_queue_len = MVNETA_MAX_TXD; @@ -4030,12 +4040,7 @@ static int mvneta_probe(struct platform_device *pdev) pp = netdev_priv(dev); spin_lock_init(&pp->lock); - pp->phy_node = phy_node; - pp->phy_interface = phy_mode; - - err = of_property_read_string(dn, "managed", &managed); - pp->use_inband_status = (err == 0 && - strcmp(managed, "in-band-status") == 0); + pp->dn = dn; pp->rxq_def = rxq_def; @@ -4046,7 +4051,7 @@ static int mvneta_probe(struct platform_device *pdev) pp->clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(pp->clk)) { err = PTR_ERR(pp->clk); - goto err_put_phy_node; + goto err_free_irq; } clk_prepare_enable(pp->clk); @@ -4154,6 +4159,14 @@ static int mvneta_probe(struct platform_device *pdev) dev->priv_flags |= IFF_LIVE_ADDR_CHANGE; dev->gso_max_segs = MVNETA_MAX_TSO_SEGS; + phylink = phylink_create(dev, dn, phy_mode, &mvneta_phylink_ops); + if (IS_ERR(phylink)) { + err = PTR_ERR(phylink); + goto err_free_stats; + } + + pp->phylink = phylink; + err = register_netdev(dev); if (err < 0) { dev_err(&pdev->dev, "failed to register\n"); @@ -4165,14 +4178,6 @@ static int mvneta_probe(struct platform_device *pdev) platform_set_drvdata(pdev, pp->dev); - if (pp->use_inband_status) { - struct phy_device *phy = of_phy_find_device(dn); - - mvneta_fixed_link_update(pp, phy); - - put_device(&phy->mdio.dev); - } - return 0; err_netdev: @@ -4183,16 +4188,14 @@ err_netdev: 1 << pp->id); } err_free_stats: + if (pp->phylink) + phylink_destroy(pp->phylink); free_percpu(pp->stats); err_free_ports: free_percpu(pp->ports); err_clk: clk_disable_unprepare(pp->clk_bus); clk_disable_unprepare(pp->clk); -err_put_phy_node: - of_node_put(phy_node); - if (of_phy_is_fixed_link(dn)) - of_phy_deregister_fixed_link(dn); err_free_irq: irq_dispose_mapping(dev->irq); err_free_netdev: @@ -4212,10 +4215,8 @@ static int mvneta_remove(struct platform_device *pdev) clk_disable_unprepare(pp->clk); free_percpu(pp->ports); free_percpu(pp->stats); - if (of_phy_is_fixed_link(dn)) - of_phy_deregister_fixed_link(dn); irq_dispose_mapping(dev->irq); - of_node_put(pp->phy_node); + phylink_destroy(pp->phylink); free_netdev(dev); if (pp->bm_priv) { -- cgit From ba6c2ddcb81e5e5ecf7d6a58f82da52fcb0e722f Mon Sep 17 00:00:00 2001 From: Russell King Date: Sat, 24 Dec 2016 10:27:08 +0000 Subject: net: mvneta: disable MVNETA_CAUSE_PSC_SYNC_CHANGE interrupt The PSC sync change interrupt can fire multiple times while the link is down. As this isn't information we make use of, it's pointless having the interrupt enabled, so let's disable this interrupt. Signed-off-by: Russell King --- drivers/net/ethernet/marvell/mvneta.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/drivers/net/ethernet/marvell/mvneta.c b/drivers/net/ethernet/marvell/mvneta.c index 068fee29bc10..2adcfce67e30 100644 --- a/drivers/net/ethernet/marvell/mvneta.c +++ b/drivers/net/ethernet/marvell/mvneta.c @@ -2617,9 +2617,11 @@ static int mvneta_poll(struct napi_struct *napi, int budget) mvreg_write(pp, MVNETA_INTR_MISC_CAUSE, 0); if (cause_misc & (MVNETA_CAUSE_PHY_STATUS_CHANGE | - MVNETA_CAUSE_LINK_CHANGE | - MVNETA_CAUSE_PSC_SYNC_CHANGE)) + MVNETA_CAUSE_LINK_CHANGE)) { + printk(KERN_DEBUG "%s: cause 0x%08x:0x%08x\n", + __func__, cause_rx_tx, cause_misc); mvneta_link_change(pp); + } } /* Release Tx descriptors */ @@ -2932,8 +2934,7 @@ static void mvneta_start_dev(struct mvneta_port *pp) mvreg_write(pp, MVNETA_INTR_MISC_MASK, MVNETA_CAUSE_PHY_STATUS_CHANGE | - MVNETA_CAUSE_LINK_CHANGE | - MVNETA_CAUSE_PSC_SYNC_CHANGE); + MVNETA_CAUSE_LINK_CHANGE); phylink_start(pp->phylink); netif_tx_start_all_queues(pp->dev); @@ -3424,8 +3425,7 @@ static int mvneta_cpu_online(unsigned int cpu, struct hlist_node *node) on_each_cpu(mvneta_percpu_unmask_interrupt, pp, true); mvreg_write(pp, MVNETA_INTR_MISC_MASK, MVNETA_CAUSE_PHY_STATUS_CHANGE | - MVNETA_CAUSE_LINK_CHANGE | - MVNETA_CAUSE_PSC_SYNC_CHANGE); + MVNETA_CAUSE_LINK_CHANGE); netif_tx_start_all_queues(pp->dev); spin_unlock(&pp->lock); return 0; @@ -3466,8 +3466,7 @@ static int mvneta_cpu_dead(unsigned int cpu, struct hlist_node *node) on_each_cpu(mvneta_percpu_unmask_interrupt, pp, true); mvreg_write(pp, MVNETA_INTR_MISC_MASK, MVNETA_CAUSE_PHY_STATUS_CHANGE | - MVNETA_CAUSE_LINK_CHANGE | - MVNETA_CAUSE_PSC_SYNC_CHANGE); + MVNETA_CAUSE_LINK_CHANGE); netif_tx_start_all_queues(pp->dev); return 0; } -- cgit From e70997de9da853a342d464544cbd875637b97a24 Mon Sep 17 00:00:00 2001 From: Russell King Date: Fri, 2 Oct 2015 22:46:54 +0100 Subject: phy: fixed-phy: remove fixed_phy_update_state() mvneta is the only user of fixed_phy_update_state(), which has been converted to use phylink instead. Remove fixed_phy_update_state(). Reviewed-by: Florian Fainelli Signed-off-by: Russell King --- drivers/net/phy/fixed_phy.c | 31 ------------------------------- include/linux/phy_fixed.h | 9 --------- 2 files changed, 40 deletions(-) diff --git a/drivers/net/phy/fixed_phy.c b/drivers/net/phy/fixed_phy.c index eb5167210681..001fe1df7557 100644 --- a/drivers/net/phy/fixed_phy.c +++ b/drivers/net/phy/fixed_phy.c @@ -115,37 +115,6 @@ int fixed_phy_set_link_update(struct phy_device *phydev, } EXPORT_SYMBOL_GPL(fixed_phy_set_link_update); -int fixed_phy_update_state(struct phy_device *phydev, - const struct fixed_phy_status *status, - const struct fixed_phy_status *changed) -{ - struct fixed_mdio_bus *fmb = &platform_fmb; - struct fixed_phy *fp; - - if (!phydev || phydev->mdio.bus != fmb->mii_bus) - return -EINVAL; - - list_for_each_entry(fp, &fmb->phys, node) { - if (fp->addr == phydev->mdio.addr) { - write_seqcount_begin(&fp->seqcount); -#define _UPD(x) if (changed->x) \ - fp->status.x = status->x - _UPD(link); - _UPD(speed); - _UPD(duplex); - _UPD(pause); - _UPD(asym_pause); -#undef _UPD - fixed_phy_update(fp); - write_seqcount_end(&fp->seqcount); - return 0; - } - } - - return -ENOENT; -} -EXPORT_SYMBOL(fixed_phy_update_state); - int fixed_phy_add(unsigned int irq, int phy_addr, struct fixed_phy_status *status, int link_gpio) diff --git a/include/linux/phy_fixed.h b/include/linux/phy_fixed.h index 1d41ec44e39d..43a83fa75040 100644 --- a/include/linux/phy_fixed.h +++ b/include/linux/phy_fixed.h @@ -23,9 +23,6 @@ extern void fixed_phy_unregister(struct phy_device *phydev); extern int fixed_phy_set_link_update(struct phy_device *phydev, int (*link_update)(struct net_device *, struct fixed_phy_status *)); -extern int fixed_phy_update_state(struct phy_device *phydev, - const struct fixed_phy_status *status, - const struct fixed_phy_status *changed); #else static inline int fixed_phy_add(unsigned int irq, int phy_id, struct fixed_phy_status *status, @@ -49,12 +46,6 @@ static inline int fixed_phy_set_link_update(struct phy_device *phydev, { return -ENODEV; } -static inline int fixed_phy_update_state(struct phy_device *phydev, - const struct fixed_phy_status *status, - const struct fixed_phy_status *changed) -{ - return -ENODEV; -} #endif /* CONFIG_FIXED_PHY */ #endif /* __PHY_FIXED_H */ -- cgit From 5fbad84dd18ced0da44831f5b03b130036f6ebab Mon Sep 17 00:00:00 2001 From: Russell King Date: Thu, 1 Oct 2015 20:27:19 +0100 Subject: phylink: add ethtool nway_reset support Add ethtool nway_reset support to phylink, to allow userspace to request a re-negotiation of the link. Reviewed-by: Florian Fainelli Signed-off-by: Russell King --- drivers/net/phy/phylink.c | 14 ++++++++++++++ include/linux/phylink.h | 1 + 2 files changed, 15 insertions(+) diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index 91d1a3da8648..5f416314a76c 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -783,6 +783,20 @@ int phylink_ethtool_ksettings_set(struct phylink *pl, } EXPORT_SYMBOL_GPL(phylink_ethtool_ksettings_set); +int phylink_ethtool_nway_reset(struct phylink *pl) +{ + int ret = 0; + + mutex_lock(&pl->config_mutex); + if (pl->phydev) + ret = genphy_restart_aneg(pl->phydev); + phylink_mac_an_restart(pl); + mutex_unlock(&pl->config_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(phylink_ethtool_nway_reset); + /* This emulates MII registers for a fixed-mode phy operating as per the * passed in state. "aneg" defines if we report negotiation is possible. * diff --git a/include/linux/phylink.h b/include/linux/phylink.h index 66544396cddf..a341d0d0ec30 100644 --- a/include/linux/phylink.h +++ b/include/linux/phylink.h @@ -86,6 +86,7 @@ int phylink_ethtool_ksettings_get(struct phylink *, struct ethtool_link_ksettings *); int phylink_ethtool_ksettings_set(struct phylink *, const struct ethtool_link_ksettings *); +int phylink_ethtool_nway_reset(struct phylink *); int phylink_mii_ioctl(struct phylink *, struct ifreq *, int); int phylink_set_link(struct phylink *pl, unsigned int mode, u8 port, -- cgit From 8ab2f2d8ac1906e6b22fc96517605bc5413ae16e Mon Sep 17 00:00:00 2001 From: Russell King Date: Thu, 1 Oct 2015 19:40:31 +0100 Subject: net: mvneta: add nway_reset support Add ethtool nway_reset support to mvneta via phylink, so that userspace can request the link in whatever mode to be renegotiated via ethtool -r ethX. Signed-off-by: Russell King --- drivers/net/ethernet/marvell/mvneta.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/net/ethernet/marvell/mvneta.c b/drivers/net/ethernet/marvell/mvneta.c index 2adcfce67e30..7e44453ff043 100644 --- a/drivers/net/ethernet/marvell/mvneta.c +++ b/drivers/net/ethernet/marvell/mvneta.c @@ -3600,6 +3600,13 @@ mvneta_ethtool_get_link_ksettings(struct net_device *ndev, return phylink_ethtool_ksettings_get(pp->phylink, cmd); } +static int mvneta_ethtool_nway_reset(struct net_device *dev) +{ + struct mvneta_port *pp = netdev_priv(dev); + + return phylink_ethtool_nway_reset(pp->phylink); +} + /* Set interrupt coalescing for ethtools */ static int mvneta_ethtool_set_coalesce(struct net_device *dev, struct ethtool_coalesce *c) @@ -3863,6 +3870,7 @@ static const struct net_device_ops mvneta_netdev_ops = { const struct ethtool_ops mvneta_eth_tool_ops = { .get_link = ethtool_op_get_link, + .nway_reset = mvneta_ethtool_nway_reset, .set_coalesce = mvneta_ethtool_set_coalesce, .get_coalesce = mvneta_ethtool_get_coalesce, .get_drvinfo = mvneta_ethtool_get_drvinfo, -- cgit From 41ae56de7803e6a6a3c609a21ad579deae9488f2 Mon Sep 17 00:00:00 2001 From: Russell King Date: Thu, 1 Oct 2015 20:32:07 +0100 Subject: phylink: add flow control support Add flow control support, including ethtool support, to phylink. We add support to allow ethtool to get and set the current flow control settings, and the 802.3 specified resolution for the local and remote link partner abilities. Signed-off-by: Russell King --- drivers/net/phy/phylink.c | 136 +++++++++++++++++++++++++++++++++++++++++++++- include/linux/phylink.h | 8 +++ 2 files changed, 142 insertions(+), 2 deletions(-) diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index 5f416314a76c..417e4608b4a2 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -132,6 +132,9 @@ static int phylink_parse_fixedlink(struct phylink *pl, struct device_node *np) if (of_property_read_bool(fixed_node, "full-duplex")) pl->link_config.duplex = DUPLEX_FULL; + + /* We treat the "pause" and "asym-pause" terminology as + * defining the link partner's ability. */ if (of_property_read_bool(fixed_node, "pause")) pl->link_config.pause |= MLO_PAUSE_SYM; if (of_property_read_bool(fixed_node, "asym-pause")) @@ -277,6 +280,56 @@ static void phylink_get_fixed_state(struct phylink *pl, struct phylink_link_stat state->link = !!gpiod_get_value(pl->link_gpio); } +/* Flow control is resolved according to our and the link partners + * advertisments using the following drawn from the 802.3 specs: + * Local device Link partner + * Pause AsymDir Pause AsymDir Result + * 1 X 1 X TX+RX + * 0 1 1 1 RX + * 1 1 0 1 TX + */ +static void phylink_resolve_flow(struct phylink *pl, + struct phylink_link_state *state) +{ + int new_pause = 0; + + if (pl->link_config.pause & MLO_PAUSE_AN) { + int pause = 0; + + if (phylink_test(pl->link_config.advertising, Pause)) + pause |= MLO_PAUSE_SYM; + if (phylink_test(pl->link_config.advertising, Asym_Pause)) + pause |= MLO_PAUSE_ASYM; + + pause &= state->pause; + + if (pause & MLO_PAUSE_SYM) + new_pause = MLO_PAUSE_TX | MLO_PAUSE_RX; + else if (pause & MLO_PAUSE_ASYM) + new_pause = state->pause & MLO_PAUSE_SYM ? + MLO_PAUSE_RX : MLO_PAUSE_TX; + } else { + new_pause = pl->link_config.pause & MLO_PAUSE_TXRX_MASK; + } + + state->pause &= ~MLO_PAUSE_TXRX_MASK; + state->pause |= new_pause; +} + +static const char *phylink_pause_to_str(int pause) +{ + switch (pause & MLO_PAUSE_TXRX_MASK) { + case MLO_PAUSE_TX | MLO_PAUSE_RX: + return "rx/tx"; + case MLO_PAUSE_TX: + return "tx"; + case MLO_PAUSE_RX: + return "rx"; + default: + return "off"; + } +} + static void phylink_resolve(struct work_struct *w) { struct phylink *pl = container_of(w, struct phylink, resolve); @@ -290,6 +343,7 @@ static void phylink_resolve(struct work_struct *w) switch (pl->link_an_mode) { case MLO_AN_PHY: link_state = pl->phy_state; + phylink_resolve_flow(pl, &link_state); break; case MLO_AN_FIXED: @@ -298,9 +352,12 @@ static void phylink_resolve(struct work_struct *w) case MLO_AN_SGMII: phylink_get_mac_state(pl, &link_state); - if (pl->phydev) + if (pl->phydev) { link_state.link = link_state.link && pl->phy_state.link; + link_state.pause |= pl->phy_state.pause; + phylink_resolve_flow(pl, &link_state); + } break; case MLO_AN_8023Z: @@ -330,7 +387,7 @@ static void phylink_resolve(struct work_struct *w) "Link is Up - %s/%s - flow control %s\n", phy_speed_to_str(link_state.speed), phy_duplex_to_str(link_state.duplex), - link_state.pause ? "rx/tx" : "off"); + phylink_pause_to_str(link_state.pause)); } } mutex_unlock(&pl->state_mutex); @@ -358,6 +415,7 @@ struct phylink *phylink_create(struct net_device *ndev, struct device_node *np, pl->netdev = ndev; pl->link_interface = iface; pl->link_port = PORT_MII; + pl->link_config.pause = MLO_PAUSE_AN; pl->link_config.speed = SPEED_UNKNOWN; pl->link_config.duplex = DUPLEX_UNKNOWN; pl->ops = ops; @@ -580,6 +638,7 @@ void phylink_start(struct phylink *pl) * a fixed-link to start with the correct parameters, and also * ensures that we set the appropriate advertisment for Serdes links. */ + phylink_resolve_flow(pl, &pl->link_config); phylink_mac_config(pl, &pl->link_config); clear_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state); @@ -797,6 +856,79 @@ int phylink_ethtool_nway_reset(struct phylink *pl) } EXPORT_SYMBOL_GPL(phylink_ethtool_nway_reset); +void phylink_ethtool_get_pauseparam(struct phylink *pl, + struct ethtool_pauseparam *pause) +{ + mutex_lock(&pl->config_mutex); + + pause->autoneg = !!(pl->link_config.pause & MLO_PAUSE_AN); + pause->rx_pause = !!(pl->link_config.pause & MLO_PAUSE_RX); + pause->tx_pause = !!(pl->link_config.pause & MLO_PAUSE_TX); + + mutex_unlock(&pl->config_mutex); +} +EXPORT_SYMBOL_GPL(phylink_ethtool_get_pauseparam); + +static int __phylink_ethtool_set_pauseparam(struct phylink *pl, + struct ethtool_pauseparam *pause) +{ + struct phylink_link_state *config = &pl->link_config; + + if (!phylink_test(pl->supported, Pause) && + !phylink_test(pl->supported, Asym_Pause)) + return -EOPNOTSUPP; + + if (!phylink_test(pl->supported, Asym_Pause) && + !pause->autoneg && pause->rx_pause != pause->tx_pause) + return -EINVAL; + + config->pause &= ~(MLO_PAUSE_AN | MLO_PAUSE_TXRX_MASK); + + if (pause->autoneg) + config->pause |= MLO_PAUSE_AN; + if (pause->rx_pause) + config->pause |= MLO_PAUSE_RX; + if (pause->tx_pause) + config->pause |= MLO_PAUSE_TX; + + if (!test_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state)) { + switch (pl->link_an_mode) { + case MLO_AN_PHY: + /* Silently mark the carrier down, and then trigger a resolve */ + netif_carrier_off(pl->netdev); + phylink_run_resolve(pl); + break; + + case MLO_AN_FIXED: + /* Should we allow fixed links to change against the config? */ + phylink_resolve_flow(pl, config); + phylink_mac_config(pl, config); + break; + + case MLO_AN_SGMII: + case MLO_AN_8023Z: + phylink_mac_config(pl, config); + phylink_mac_an_restart(pl); + break; + } + } + + return 0; +} + +int phylink_ethtool_set_pauseparam(struct phylink *pl, + struct ethtool_pauseparam *pause) +{ + int ret; + + mutex_lock(&pl->config_mutex); + ret = __phylink_ethtool_set_pauseparam(pl, pause); + mutex_unlock(&pl->config_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(phylink_ethtool_set_pauseparam); + /* This emulates MII registers for a fixed-mode phy operating as per the * passed in state. "aneg" defines if we report negotiation is possible. * diff --git a/include/linux/phylink.h b/include/linux/phylink.h index a341d0d0ec30..9543f847d058 100644 --- a/include/linux/phylink.h +++ b/include/linux/phylink.h @@ -13,6 +13,10 @@ enum { MLO_PAUSE_NONE, MLO_PAUSE_ASYM = BIT(0), MLO_PAUSE_SYM = BIT(1), + MLO_PAUSE_RX = BIT(2), + MLO_PAUSE_TX = BIT(3), + MLO_PAUSE_TXRX_MASK = MLO_PAUSE_TX | MLO_PAUSE_RX, + MLO_PAUSE_AN = BIT(4), MLO_AN_PHY = 0, MLO_AN_FIXED, @@ -87,6 +91,10 @@ int phylink_ethtool_ksettings_get(struct phylink *, int phylink_ethtool_ksettings_set(struct phylink *, const struct ethtool_link_ksettings *); int phylink_ethtool_nway_reset(struct phylink *); +void phylink_ethtool_get_pauseparam(struct phylink *, + struct ethtool_pauseparam *); +int phylink_ethtool_set_pauseparam(struct phylink *, + struct ethtool_pauseparam *); int phylink_mii_ioctl(struct phylink *, struct ifreq *, int); int phylink_set_link(struct phylink *pl, unsigned int mode, u8 port, -- cgit From 1f707084559fcfd51ef5a57a935453d49a548fde Mon Sep 17 00:00:00 2001 From: Russell King Date: Thu, 1 Oct 2015 17:41:44 +0100 Subject: net: mvneta: add flow control support via phylink Add flow control support to mvneta, including the ethtool hooks. This uses the phylink code to calculate the result of autonegotiation where a phy is attached, and to handle the ethtool settings. Signed-off-by: Russell King --- drivers/net/ethernet/marvell/mvneta.c | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/marvell/mvneta.c b/drivers/net/ethernet/marvell/mvneta.c index 7e44453ff043..06c6955382b6 100644 --- a/drivers/net/ethernet/marvell/mvneta.c +++ b/drivers/net/ethernet/marvell/mvneta.c @@ -3164,6 +3164,12 @@ static int mvneta_mac_link_state(struct net_device *ndev, state->link = !!(gmac_stat & MVNETA_GMAC_LINK_UP); state->duplex = !!(gmac_stat & MVNETA_GMAC_FULL_DUPLEX); + state->pause = 0; + if (gmac_stat & MVNETA_GMAC_RX_FLOW_CTRL_ENABLE) + state->pause |= MLO_PAUSE_RX; + if (gmac_stat & MVNETA_GMAC_TX_FLOW_CTRL_ENABLE) + state->pause |= MLO_PAUSE_TX; + return 1; } @@ -3206,6 +3212,8 @@ static void mvneta_mac_config(struct net_device *ndev, unsigned int mode, if (phylink_test(state->advertising, Pause)) new_an |= MVNETA_GMAC_ADVERT_SYM_FLOW_CTRL; + if (state->pause & MLO_PAUSE_TXRX_MASK) + new_an |= MVNETA_GMAC_CONFIG_FLOW_CTRL; switch (mode) { case MLO_AN_SGMII: @@ -3230,7 +3238,7 @@ static void mvneta_mac_config(struct net_device *ndev, unsigned int mode, /* The MAC only supports FD mode */ MVNETA_GMAC_CONFIG_FULL_DUPLEX; - if (state->an_enabled) + if (state->pause & MLO_PAUSE_AN && state->an_enabled) new_an |= MVNETA_GMAC_AN_FLOW_CTRL_EN; break; @@ -3696,6 +3704,22 @@ static int mvneta_ethtool_set_ringparam(struct net_device *dev, return 0; } +static void mvneta_ethtool_get_pauseparam(struct net_device *dev, + struct ethtool_pauseparam *pause) +{ + struct mvneta_port *pp = netdev_priv(dev); + + phylink_ethtool_get_pauseparam(pp->phylink, pause); +} + +static int mvneta_ethtool_set_pauseparam(struct net_device *dev, + struct ethtool_pauseparam *pause) +{ + struct mvneta_port *pp = netdev_priv(dev); + + return phylink_ethtool_set_pauseparam(pp->phylink, pause); +} + static void mvneta_ethtool_get_strings(struct net_device *netdev, u32 sset, u8 *data) { @@ -3876,6 +3900,8 @@ const struct ethtool_ops mvneta_eth_tool_ops = { .get_drvinfo = mvneta_ethtool_get_drvinfo, .get_ringparam = mvneta_ethtool_get_ringparam, .set_ringparam = mvneta_ethtool_set_ringparam, + .get_pauseparam = mvneta_ethtool_get_pauseparam, + .set_pauseparam = mvneta_ethtool_set_pauseparam, .get_strings = mvneta_ethtool_get_strings, .get_ethtool_stats = mvneta_ethtool_get_stats, .get_sset_count = mvneta_ethtool_get_sset_count, -- cgit From 71eff06a95acc75a1b0eca1ee6bb40978f3cdbe0 Mon Sep 17 00:00:00 2001 From: Russell King Date: Thu, 1 Oct 2015 00:34:08 +0100 Subject: net: mvneta: enable flow control for PHY connections Enable flow control support for PHY connections by indicating our support via the ethtool capabilities. phylink takes care of the appropriate handling. Reviewed-by: Florian Fainelli Signed-off-by: Russell King --- drivers/net/ethernet/marvell/mvneta.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/marvell/mvneta.c b/drivers/net/ethernet/marvell/mvneta.c index 06c6955382b6..8f4fd355e547 100644 --- a/drivers/net/ethernet/marvell/mvneta.c +++ b/drivers/net/ethernet/marvell/mvneta.c @@ -3138,10 +3138,11 @@ static void mvneta_validate_support(struct net_device *ndev, unsigned int mode, phylink_set(mask, 10baseT_Full); phylink_set(mask, 100baseT_Half); phylink_set(mask, 100baseT_Full); - } else { - phylink_set(mask, Pause); } + if (mode != MLO_AN_FIXED) + phylink_set(mask, Pause); + bitmap_and(support, support, mask, __ETHTOOL_LINK_MODE_MASK_NBITS); } -- cgit From 657f579d2a150ea7a75e663a8850d0febdffe781 Mon Sep 17 00:00:00 2001 From: Russell King Date: Tue, 12 Jul 2016 00:04:13 +0100 Subject: net: mvneta: enable flow control for fixed connections Allow symetric flow control to be enabled for fixed link connections as well as other types of connections by setting the supported and advertised capability bits. Signed-off-by: Russell King --- drivers/net/ethernet/marvell/mvneta.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/marvell/mvneta.c b/drivers/net/ethernet/marvell/mvneta.c index 8f4fd355e547..3fd8c7704da6 100644 --- a/drivers/net/ethernet/marvell/mvneta.c +++ b/drivers/net/ethernet/marvell/mvneta.c @@ -3128,6 +3128,8 @@ static void mvneta_validate_support(struct net_device *ndev, unsigned int mode, phylink_set(mask, BNC); phylink_set(mask, Backplane); + /* Asymmetric pause is unsupported */ + phylink_set(mask, Pause); /* Half-duplex at speeds higher than 100Mbit is unsupported */ phylink_set(mask, 1000baseT_Full); phylink_set(mask, 1000baseX_Full); @@ -3140,9 +3142,6 @@ static void mvneta_validate_support(struct net_device *ndev, unsigned int mode, phylink_set(mask, 100baseT_Full); } - if (mode != MLO_AN_FIXED) - phylink_set(mask, Pause); - bitmap_and(support, support, mask, __ETHTOOL_LINK_MODE_MASK_NBITS); } -- cgit From aa581bfae513f47b3fc39a08c4f66fd8d96f313f Mon Sep 17 00:00:00 2001 From: Russell King Date: Thu, 1 Oct 2015 21:19:53 +0100 Subject: phylink: add EEE support Add EEE hooks to phylink to allow the phylib EEE functions for the connected phy to be safely accessed. Signed-off-by: Russell King --- drivers/net/ethernet/marvell/mvneta.c | 3 +- drivers/net/phy/phylink.c | 58 ++++++++++++++++++++++++++++++++++- include/linux/phylink.h | 7 ++++- 3 files changed, 65 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/marvell/mvneta.c b/drivers/net/ethernet/marvell/mvneta.c index 3fd8c7704da6..a2a5a23c479b 100644 --- a/drivers/net/ethernet/marvell/mvneta.c +++ b/drivers/net/ethernet/marvell/mvneta.c @@ -3290,7 +3290,8 @@ static void mvneta_mac_link_down(struct net_device *ndev, unsigned int mode) } } -static void mvneta_mac_link_up(struct net_device *ndev, unsigned int mode) +static void mvneta_mac_link_up(struct net_device *ndev, unsigned int mode, + struct phy_device *phy) { struct mvneta_port *pp = netdev_priv(ndev); u32 val; diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index 417e4608b4a2..1ae224610cae 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -379,7 +379,8 @@ static void phylink_resolve(struct work_struct *w) if (pl->phydev) phylink_mac_config(pl, &link_state); - pl->ops->mac_link_up(ndev, pl->link_an_mode); + pl->ops->mac_link_up(ndev, pl->link_an_mode, + pl->phydev); netif_carrier_on(ndev); @@ -929,6 +930,61 @@ int phylink_ethtool_set_pauseparam(struct phylink *pl, } EXPORT_SYMBOL_GPL(phylink_ethtool_set_pauseparam); +int phylink_init_eee(struct phylink *pl, bool clk_stop_enable) +{ + int ret = -EPROTONOSUPPORT; + + mutex_lock(&pl->config_mutex); + if (pl->phydev) + ret = phy_init_eee(pl->phydev, clk_stop_enable); + mutex_unlock(&pl->config_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(phylink_init_eee); + +int phylink_get_eee_err(struct phylink *pl) +{ + int ret = 0; + + mutex_lock(&pl->config_mutex); + if (pl->phydev) + ret = phy_get_eee_err(pl->phydev); + mutex_unlock(&pl->config_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(phylink_get_eee_err); + +int phylink_ethtool_get_eee(struct phylink *pl, struct ethtool_eee *eee) +{ + int ret = -EOPNOTSUPP; + + mutex_lock(&pl->config_mutex); + if (pl->phydev) + ret = phy_ethtool_get_eee(pl->phydev, eee); + mutex_unlock(&pl->config_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(phylink_ethtool_get_eee); + +int phylink_ethtool_set_eee(struct phylink *pl, struct ethtool_eee *eee) +{ + int ret = -EOPNOTSUPP; + + mutex_lock(&pl->config_mutex); + if (pl->phydev) { + ret = phy_ethtool_set_eee(pl->phydev, eee); + if (ret == 0 && eee->eee_enabled) + phy_start_aneg(pl->phydev); + } + mutex_unlock(&pl->config_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(phylink_ethtool_set_eee); + /* This emulates MII registers for a fixed-mode phy operating as per the * passed in state. "aneg" defines if we report negotiation is possible. * diff --git a/include/linux/phylink.h b/include/linux/phylink.h index 9543f847d058..817db16c94a1 100644 --- a/include/linux/phylink.h +++ b/include/linux/phylink.h @@ -70,7 +70,8 @@ struct phylink_mac_ops { void (*mac_an_restart)(struct net_device *, unsigned int mode); void (*mac_link_down)(struct net_device *, unsigned int mode); - void (*mac_link_up)(struct net_device *, unsigned int mode); + void (*mac_link_up)(struct net_device *, unsigned int mode, + struct phy_device *); }; struct phylink *phylink_create(struct net_device *, struct device_node *, @@ -95,6 +96,10 @@ void phylink_ethtool_get_pauseparam(struct phylink *, struct ethtool_pauseparam *); int phylink_ethtool_set_pauseparam(struct phylink *, struct ethtool_pauseparam *); +int phylink_init_eee(struct phylink *, bool); +int phylink_get_eee_err(struct phylink *); +int phylink_ethtool_get_eee(struct phylink *, struct ethtool_eee *); +int phylink_ethtool_set_eee(struct phylink *, struct ethtool_eee *); int phylink_mii_ioctl(struct phylink *, struct ifreq *, int); int phylink_set_link(struct phylink *pl, unsigned int mode, u8 port, -- cgit From b244d8f41f4df99dc248c696ba73efbf98e8aceb Mon Sep 17 00:00:00 2001 From: Russell King Date: Tue, 29 Sep 2015 15:17:39 +0100 Subject: net: mvneta: add EEE support Add EEE support to mvneta. This allows us to enable the low power idle support at MAC level if there is a PHY attached through phylink which supports LPI. The appropriate ethtool support is provided to allow the feature to be controlled, including ethtool statistics for EEE wakeup errors. Signed-off-by: Russell King --- drivers/net/ethernet/marvell/mvneta.c | 87 +++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/drivers/net/ethernet/marvell/mvneta.c b/drivers/net/ethernet/marvell/mvneta.c index a2a5a23c479b..1e5fde1b050c 100644 --- a/drivers/net/ethernet/marvell/mvneta.c +++ b/drivers/net/ethernet/marvell/mvneta.c @@ -243,6 +243,12 @@ #define MVNETA_TXQ_TOKEN_SIZE_REG(q) (0x3e40 + ((q) << 2)) #define MVNETA_TXQ_TOKEN_SIZE_MAX 0x7fffffff +#define MVNETA_LPI_CTRL_0 0x2cc0 +#define MVNETA_LPI_CTRL_1 0x2cc4 +#define MVNETA_LPI_REQUEST_ENABLE BIT(0) +#define MVNETA_LPI_CTRL_2 0x2cc8 +#define MVNETA_LPI_STATUS 0x2ccc + #define MVNETA_CAUSE_TXQ_SENT_DESC_ALL_MASK 0xff /* Descriptor ring Macros */ @@ -316,6 +322,11 @@ #define MVNETA_RX_GET_BM_POOL_ID(rxd) \ (((rxd)->status & MVNETA_RXD_BM_POOL_MASK) >> MVNETA_RXD_BM_POOL_SHIFT) +enum { + ETHTOOL_STAT_EEE_WAKEUP, + ETHTOOL_MAX_STATS, +}; + struct mvneta_statistic { unsigned short offset; unsigned short type; @@ -324,6 +335,7 @@ struct mvneta_statistic { #define T_REG_32 32 #define T_REG_64 64 +#define T_SW 1 static const struct mvneta_statistic mvneta_statistics[] = { { 0x3000, T_REG_64, "good_octets_received", }, @@ -358,6 +370,7 @@ static const struct mvneta_statistic mvneta_statistics[] = { { 0x304c, T_REG_32, "broadcast_frames_sent", }, { 0x3054, T_REG_32, "fc_sent", }, { 0x300c, T_REG_32, "internal_mac_transmit_err", }, + { ETHTOOL_STAT_EEE_WAKEUP, T_SW, "eee_wakeup_errors", }, }; struct mvneta_pcpu_stats { @@ -416,6 +429,10 @@ struct mvneta_port { struct mvneta_bm_pool *pool_short; int bm_win_id; + bool eee_enabled; + bool eee_active; + bool tx_lpi_enabled; + u64 ethtool_stats[ARRAY_SIZE(mvneta_statistics)]; u32 indir[MVNETA_RSS_LU_TABLE_SIZE]; @@ -3275,6 +3292,18 @@ static void mvneta_mac_config(struct net_device *ndev, unsigned int mode, mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, new_an); } +static void mvneta_set_eee(struct mvneta_port *pp, bool enable) +{ + u32 lpi_ctl1; + + lpi_ctl1 = mvreg_read(pp, MVNETA_LPI_CTRL_1); + if (enable) + lpi_ctl1 |= MVNETA_LPI_REQUEST_ENABLE; + else + lpi_ctl1 &= ~MVNETA_LPI_REQUEST_ENABLE; + mvreg_write(pp, MVNETA_LPI_CTRL_1, lpi_ctl1); +} + static void mvneta_mac_link_down(struct net_device *ndev, unsigned int mode) { struct mvneta_port *pp = netdev_priv(ndev); @@ -3288,6 +3317,9 @@ static void mvneta_mac_link_down(struct net_device *ndev, unsigned int mode) val |= MVNETA_GMAC_FORCE_LINK_DOWN; mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val); } + + pp->eee_active = false; + mvneta_set_eee(pp, false); } static void mvneta_mac_link_up(struct net_device *ndev, unsigned int mode, @@ -3304,6 +3336,11 @@ static void mvneta_mac_link_up(struct net_device *ndev, unsigned int mode, } mvneta_port_up(pp); + + if (phy && pp->eee_enabled) { + pp->eee_active = phy_init_eee(phy, 0) >= 0; + mvneta_set_eee(pp, pp->eee_active && pp->tx_lpi_enabled); + } } static const struct phylink_mac_ops mvneta_phylink_ops = { @@ -3756,6 +3793,13 @@ static void mvneta_ethtool_update_stats(struct mvneta_port *pp) high = readl_relaxed(base + s->offset + 4); val = (u64)high << 32 | low; break; + case T_SW: + switch (s->offset) { + case ETHTOOL_STAT_EEE_WAKEUP: + val = phylink_get_eee_err(pp->phylink); + break; + } + break; } pp->ethtool_stats[i] += val; @@ -3881,6 +3925,47 @@ static int mvneta_ethtool_get_rxfh(struct net_device *dev, u32 *indir, u8 *key, return 0; } +static int mvneta_ethtool_get_eee(struct net_device *dev, + struct ethtool_eee *eee) +{ + struct mvneta_port *pp = netdev_priv(dev); + u32 lpi_ctl0; + + lpi_ctl0 = mvreg_read(pp, MVNETA_LPI_CTRL_0); + + eee->eee_enabled = pp->eee_enabled; + eee->eee_active = pp->eee_active; + eee->tx_lpi_enabled = pp->tx_lpi_enabled; + eee->tx_lpi_timer = (lpi_ctl0) >> 8; // * scale; + + return phylink_ethtool_get_eee(pp->phylink, eee); +} + +static int mvneta_ethtool_set_eee(struct net_device *dev, + struct ethtool_eee *eee) +{ + struct mvneta_port *pp = netdev_priv(dev); + u32 lpi_ctl0; + + /* The Armada 37x documents do not give limits for this other than + * it being an 8-bit register. */ + if (eee->tx_lpi_enabled && + (eee->tx_lpi_timer < 0 || eee->tx_lpi_timer > 255)) + return -EINVAL; + + lpi_ctl0 = mvreg_read(pp, MVNETA_LPI_CTRL_0); + lpi_ctl0 &= ~(0xff << 8); + lpi_ctl0 |= eee->tx_lpi_timer << 8; + mvreg_write(pp, MVNETA_LPI_CTRL_0, lpi_ctl0); + + pp->eee_enabled = eee->eee_enabled; + pp->tx_lpi_enabled = eee->tx_lpi_enabled; + + mvneta_set_eee(pp, eee->tx_lpi_enabled && eee->eee_enabled); + + return phylink_ethtool_set_eee(pp->phylink, eee); +} + static const struct net_device_ops mvneta_netdev_ops = { .ndo_open = mvneta_open, .ndo_stop = mvneta_stop, @@ -3912,6 +3997,8 @@ const struct ethtool_ops mvneta_eth_tool_ops = { .set_rxfh = mvneta_ethtool_set_rxfh, .get_link_ksettings = mvneta_ethtool_get_link_ksettings, .set_link_ksettings = mvneta_ethtool_set_link_ksettings, + .get_eee = mvneta_ethtool_get_eee, + .set_eee = mvneta_ethtool_set_eee, }; /* Initialize hw */ -- cgit From 3052678ef09982137d9dc16a965df69b8640d9e0 Mon Sep 17 00:00:00 2001 From: Russell King Date: Thu, 1 Oct 2015 23:10:05 +0100 Subject: phylink: add module EEPROM support Add support for reading module EEPROMs through phylink. Reviewed-by: Florian Fainelli Signed-off-by: Russell King --- drivers/net/phy/phylink.c | 63 +++++++++++++++++++++++++++++++++++++++++++++++ include/linux/phylink.h | 12 +++++++++ 2 files changed, 75 insertions(+) diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index 1ae224610cae..6cac5ffb2a2c 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -930,6 +930,36 @@ int phylink_ethtool_set_pauseparam(struct phylink *pl, } EXPORT_SYMBOL_GPL(phylink_ethtool_set_pauseparam); +int phylink_ethtool_get_module_info(struct phylink *pl, + struct ethtool_modinfo *modinfo) +{ + int ret = -EOPNOTSUPP; + + mutex_lock(&pl->config_mutex); + if (pl->module_ops) + ret = pl->module_ops->get_module_info(pl->module_data, + modinfo); + mutex_unlock(&pl->config_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(phylink_ethtool_get_module_info); + +int phylink_ethtool_get_module_eeprom(struct phylink *pl, + struct ethtool_eeprom *ee, u8 *buf) +{ + int ret = -EOPNOTSUPP; + + mutex_lock(&pl->config_mutex); + if (pl->module_ops) + ret = pl->module_ops->get_module_eeprom(pl->module_data, ee, + buf); + mutex_unlock(&pl->config_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(phylink_ethtool_get_module_eeprom); + int phylink_init_eee(struct phylink *pl, bool clk_stop_enable) { int ret = -EPROTONOSUPPORT; @@ -1209,6 +1239,39 @@ EXPORT_SYMBOL_GPL(phylink_mii_ioctl); +int phylink_register_module(struct phylink *pl, void *data, + const struct phylink_module_ops *ops) +{ + int ret = -EBUSY; + + mutex_lock(&pl->config_mutex); + if (!pl->module_ops) { + pl->module_ops = ops; + pl->module_data = data; + ret = 0; + } + mutex_unlock(&pl->config_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(phylink_register_module); + +int phylink_unregister_module(struct phylink *pl, void *data) +{ + int ret = -EINVAL; + + mutex_lock(&pl->config_mutex); + if (pl->module_data == data) { + pl->module_ops = NULL; + pl->module_data = NULL; + ret = 0; + } + mutex_unlock(&pl->config_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(phylink_unregister_module); + void phylink_disable(struct phylink *pl) { set_bit(PHYLINK_DISABLE_LINK, &pl->phylink_disable_state); diff --git a/include/linux/phylink.h b/include/linux/phylink.h index 817db16c94a1..097a97a669ba 100644 --- a/include/linux/phylink.h +++ b/include/linux/phylink.h @@ -74,6 +74,11 @@ struct phylink_mac_ops { struct phy_device *); }; +struct phylink_module_ops { + int (*get_module_info)(void *, struct ethtool_modinfo *); + int (*get_module_eeprom)(void *, struct ethtool_eeprom *, u8 *); +}; + struct phylink *phylink_create(struct net_device *, struct device_node *, phy_interface_t iface, const struct phylink_mac_ops *ops); void phylink_destroy(struct phylink *); @@ -96,12 +101,19 @@ void phylink_ethtool_get_pauseparam(struct phylink *, struct ethtool_pauseparam *); int phylink_ethtool_set_pauseparam(struct phylink *, struct ethtool_pauseparam *); +int phylink_ethtool_get_module_info(struct phylink *, struct ethtool_modinfo *); +int phylink_ethtool_get_module_eeprom(struct phylink *, + struct ethtool_eeprom *, u8 *); int phylink_init_eee(struct phylink *, bool); int phylink_get_eee_err(struct phylink *); int phylink_ethtool_get_eee(struct phylink *, struct ethtool_eee *); int phylink_ethtool_set_eee(struct phylink *, struct ethtool_eee *); int phylink_mii_ioctl(struct phylink *, struct ifreq *, int); +int phylink_register_module(struct phylink *, void *, + const struct phylink_module_ops *); +int phylink_unregister_module(struct phylink *, void *); + int phylink_set_link(struct phylink *pl, unsigned int mode, u8 port, const unsigned long *support); void phylink_disable(struct phylink *pl); -- cgit From dd335ba2c3519eeda963f84e16688bd4ecd60adc Mon Sep 17 00:00:00 2001 From: Russell King Date: Thu, 1 Oct 2015 23:32:39 +0100 Subject: net: mvneta: add module EEPROM reading support Signed-off-by: Russell King --- drivers/net/ethernet/marvell/mvneta.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/drivers/net/ethernet/marvell/mvneta.c b/drivers/net/ethernet/marvell/mvneta.c index 1e5fde1b050c..4596735ff8bb 100644 --- a/drivers/net/ethernet/marvell/mvneta.c +++ b/drivers/net/ethernet/marvell/mvneta.c @@ -3925,6 +3925,22 @@ static int mvneta_ethtool_get_rxfh(struct net_device *dev, u32 *indir, u8 *key, return 0; } +static int mvneta_ethtool_get_module_info(struct net_device *dev, + struct ethtool_modinfo *modinfo) +{ + struct mvneta_port *pp = netdev_priv(dev); + + return phylink_ethtool_get_module_info(pp->phylink, modinfo); +} + +static int mvneta_ethtool_get_module_eeprom(struct net_device *dev, + struct ethtool_eeprom *ee, u8 *buf) +{ + struct mvneta_port *pp = netdev_priv(dev); + + return phylink_ethtool_get_module_eeprom(pp->phylink, ee, buf); +} + static int mvneta_ethtool_get_eee(struct net_device *dev, struct ethtool_eee *eee) { @@ -3997,6 +4013,8 @@ const struct ethtool_ops mvneta_eth_tool_ops = { .set_rxfh = mvneta_ethtool_set_rxfh, .get_link_ksettings = mvneta_ethtool_get_link_ksettings, .set_link_ksettings = mvneta_ethtool_set_link_ksettings, + .get_module_info = mvneta_ethtool_get_module_info, + .get_module_eeprom = mvneta_ethtool_get_module_eeprom, .get_eee = mvneta_ethtool_get_eee, .set_eee = mvneta_ethtool_set_eee, }; -- cgit From ae0f884dc22ca103b61a680e54dfdf6d3818ae32 Mon Sep 17 00:00:00 2001 From: Russell King Date: Thu, 8 Oct 2015 23:49:47 +0100 Subject: sfp/phylink: hook up eeprom functions Signed-off-by: Russell King --- drivers/net/phy/sfp.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c index 0cdec71786d0..1f7001522a8c 100644 --- a/drivers/net/phy/sfp.c +++ b/drivers/net/phy/sfp.c @@ -985,11 +985,9 @@ static void sfp_sm_event(struct sfp *sfp, unsigned int event) mutex_unlock(&sfp->sm_mutex); } -#if 0 -static int sfp_phy_module_info(struct phy_device *phy, - struct ethtool_modinfo *modinfo) +static int sfp_module_info(void *priv, struct ethtool_modinfo *modinfo) { - struct sfp *sfp = phy->priv; + struct sfp *sfp = priv; /* locking... and check module is present */ @@ -1003,10 +1001,9 @@ static int sfp_phy_module_info(struct phy_device *phy, return 0; } -static int sfp_phy_module_eeprom(struct phy_device *phy, - struct ethtool_eeprom *ee, u8 *data) +static int sfp_module_eeprom(void *priv, struct ethtool_eeprom *ee, u8 *data) { - struct sfp *sfp = phy->priv; + struct sfp *sfp = priv; unsigned int first, last, len; int ret; @@ -1037,7 +1034,11 @@ static int sfp_phy_module_eeprom(struct phy_device *phy, } return 0; } -#endif + +static const struct phylink_module_ops sfp_module_ops = { + .get_module_info = sfp_module_info, + .get_module_eeprom = sfp_module_eeprom, +}; static void sfp_timeout(struct work_struct *work) { @@ -1113,6 +1114,7 @@ static int sfp_netdev_notify(struct notifier_block *nb, unsigned long act, void case NETDEV_UNREGISTER: if (sfp->mod_phy && sfp->phylink) phylink_disconnect_phy(sfp->phylink); + phylink_unregister_module(sfp->phylink, sfp); sfp->phylink = NULL; dev_put(sfp->ndev); sfp->ndev = NULL; @@ -1230,6 +1232,7 @@ static int sfp_probe(struct platform_device *pdev) } phylink_disable(sfp->phylink); + phylink_register_module(sfp->phylink, sfp, &sfp_module_ops); } sfp->state = sfp_get_state(sfp); -- cgit From 3e05cd6dd21e3855b3a749ad8cd608e3800f7a30 Mon Sep 17 00:00:00 2001 From: Russell King Date: Thu, 1 Oct 2015 00:34:08 +0100 Subject: phy: marvell: 88E1512: add flow control support The Marvell PHYs support pause frame advertisments, so we should not be masking their support off. Add the necessary flag to the Marvell PHY to allow any MAC level pause frame support to be advertised. Reviewed-by: Florian Fainelli Signed-off-by: Russell King --- drivers/net/phy/marvell.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/phy/marvell.c b/drivers/net/phy/marvell.c index c2dcf02df202..d379efc34a62 100644 --- a/drivers/net/phy/marvell.c +++ b/drivers/net/phy/marvell.c @@ -1671,7 +1671,8 @@ static struct phy_driver marvell_drivers[] = { .phy_id = MARVELL_PHY_ID_88E1510, .phy_id_mask = MARVELL_PHY_ID_MASK, .name = "Marvell 88E1510", - .features = PHY_GBIT_FEATURES | SUPPORTED_FIBRE, + .features = PHY_GBIT_FEATURES | SUPPORTED_FIBRE | + SUPPORTED_Pause, .flags = PHY_HAS_INTERRUPT, .probe = marvell_probe, .config_init = &m88e1510_config_init, -- cgit From ad47e480e5153f230064f8d96a132ec17e080b90 Mon Sep 17 00:00:00 2001 From: Russell King Date: Tue, 12 Jul 2016 16:45:43 +0100 Subject: phy: marvell: 88E1111: add flow control support The Marvell PHYs support pause frame advertisments, so we should not be masking their support off. Add the necessary flag to the Marvell PHY to allow any MAC level pause frame support to be advertised. Signed-off-by: Russell King --- drivers/net/phy/marvell.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/phy/marvell.c b/drivers/net/phy/marvell.c index d379efc34a62..b0ac5f15534b 100644 --- a/drivers/net/phy/marvell.c +++ b/drivers/net/phy/marvell.c @@ -1523,7 +1523,7 @@ static struct phy_driver marvell_drivers[] = { .phy_id = MARVELL_PHY_ID_88E1111, .phy_id_mask = MARVELL_PHY_ID_MASK, .name = "Marvell 88E1111", - .features = PHY_GBIT_FEATURES, + .features = PHY_GBIT_FEATURES | SUPPORTED_Pause, .flags = PHY_HAS_INTERRUPT, .probe = marvell_probe, .config_init = &m88e1111_config_init, -- cgit From 780a0718a9686ee94d995e425d8737cf9daf84a3 Mon Sep 17 00:00:00 2001 From: Russell King Date: Tue, 12 Jul 2016 16:45:43 +0100 Subject: phy: marvell: 88E1540: add flow control support The Marvell PHYs support pause frame advertisments, so we should not be masking their support off. Add the necessary flag to the Marvell PHY to allow any MAC level pause frame support to be advertised. Signed-off-by: Russell King --- drivers/net/phy/marvell.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/phy/marvell.c b/drivers/net/phy/marvell.c index b0ac5f15534b..d0ad2c38ebfb 100644 --- a/drivers/net/phy/marvell.c +++ b/drivers/net/phy/marvell.c @@ -1691,7 +1691,7 @@ static struct phy_driver marvell_drivers[] = { .phy_id = MARVELL_PHY_ID_88E1540, .phy_id_mask = MARVELL_PHY_ID_MASK, .name = "Marvell 88E1540", - .features = PHY_GBIT_FEATURES, + .features = PHY_GBIT_FEATURES | SUPPORTED_Pause, .flags = PHY_HAS_INTERRUPT, .probe = marvell_probe, .config_init = &marvell_config_init, -- cgit From df66255fb56f0ab1c56f5b0b4c1e43947104e804 Mon Sep 17 00:00:00 2001 From: Russell King Date: Fri, 23 Dec 2016 18:54:01 +0000 Subject: net: phy: fix marvell phy status reading The Marvell driver incorrectly provides phydev->lp_advertising as the logical and of the link partner's advert and our advert. This is incorrect - this field is supposed to store the link parter's unmodified advertisment. This allows ethtool to report the correct link partner auto-negotiation status. Signed-off-by: Russell King --- drivers/net/phy/marvell.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/net/phy/marvell.c b/drivers/net/phy/marvell.c index d0ad2c38ebfb..ed9d8d9ede45 100644 --- a/drivers/net/phy/marvell.c +++ b/drivers/net/phy/marvell.c @@ -1106,8 +1106,6 @@ static int marvell_read_status_page(struct phy_device *phydev, int page) if (adv < 0) return adv; - lpa &= adv; - if (status & MII_M1011_PHY_STATUS_FULLDUPLEX) phydev->duplex = DUPLEX_FULL; else -- cgit From e9ee6b1b5a50375c724bb4a12daae03a9df99926 Mon Sep 17 00:00:00 2001 From: Russell King Date: Tue, 3 Jan 2017 18:34:17 +0000 Subject: phylink: propagate PHY interface mode to MAC driver Some 10Gigabit PHYs automatically switch the mode of their host interface depending on their negotiated speed. We need to communicate this to the MAC driver so the MAC can switch its host interface to match the PHYs new operating mode. Provide the current PHY interface mode to the MAC driver. Signed-off-by: Russell King --- drivers/net/phy/phylink.c | 40 +++++++++++++++++++++++++++++----------- include/linux/phylink.h | 1 + 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index 6cac5ffb2a2c..aa653e35d5a8 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -242,8 +242,9 @@ static void phylink_mac_config(struct phylink *pl, const struct phylink_link_state *state) { netdev_dbg(pl->netdev, - "%s: mode=%s/%s/%s adv=%*pb pause=%02x link=%u an=%u\n", + "%s: mode=%s/%s/%s/%s adv=%*pb pause=%02x link=%u an=%u\n", __func__, phylink_an_mode_str(pl->link_an_mode), + phy_modes(state->interface), phy_speed_to_str(state->speed), phy_duplex_to_str(state->duplex), __ETHTOOL_LINK_MODE_MASK_NBITS, state->advertising, @@ -264,6 +265,7 @@ static int phylink_get_mac_state(struct phylink *pl, struct phylink_link_state * linkmode_copy(state->advertising, pl->link_config.advertising); linkmode_zero(state->lp_advertising); + state->interface = pl->link_config.interface; state->an_enabled = pl->link_config.an_enabled; state->link = 1; @@ -344,19 +346,38 @@ static void phylink_resolve(struct work_struct *w) case MLO_AN_PHY: link_state = pl->phy_state; phylink_resolve_flow(pl, &link_state); + phylink_mac_config(pl, &link_state); break; case MLO_AN_FIXED: phylink_get_fixed_state(pl, &link_state); + phylink_mac_config(pl, &link_state); break; case MLO_AN_SGMII: phylink_get_mac_state(pl, &link_state); if (pl->phydev) { + bool changed = false; + link_state.link = link_state.link && pl->phy_state.link; - link_state.pause |= pl->phy_state.pause; - phylink_resolve_flow(pl, &link_state); + + if (pl->phy_state.interface != + link_state.interface) { + link_state.interface = pl->phy_state.interface; + changed = true; + } + + /* Propagate the flow control from the PHY + * to the MAC. Also propagate the interface + * if changed. + */ + if (pl->phy_state.link || changed) { + link_state.pause |= pl->phy_state.pause; + phylink_resolve_flow(pl, &link_state); + + phylink_mac_config(pl, &link_state); + } } break; @@ -372,13 +393,6 @@ static void phylink_resolve(struct work_struct *w) pl->ops->mac_link_down(ndev, pl->link_an_mode); netdev_info(ndev, "Link is Down\n"); } else { - /* If we have a PHY, we need the MAC updated with - * the current link parameters (eg, in SGMII mode, - * with flow control status.) - */ - if (pl->phydev) - phylink_mac_config(pl, &link_state); - pl->ops->mac_link_up(ndev, pl->link_an_mode, pl->phydev); @@ -414,8 +428,10 @@ struct phylink *phylink_create(struct net_device *ndev, struct device_node *np, mutex_init(&pl->config_mutex); INIT_WORK(&pl->resolve, phylink_resolve); pl->netdev = ndev; + pl->phy_state.interface = iface; pl->link_interface = iface; pl->link_port = PORT_MII; + pl->link_config.interface = iface; pl->link_config.pause = MLO_PAUSE_AN; pl->link_config.speed = SPEED_UNKNOWN; pl->link_config.duplex = DUPLEX_UNKNOWN; @@ -471,12 +487,14 @@ void phylink_phy_change(struct phy_device *phydev, bool up, bool do_carrier) pl->phy_state.pause |= MLO_PAUSE_SYM; if (phydev->asym_pause) pl->phy_state.pause |= MLO_PAUSE_ASYM; + pl->phy_state.interface = phydev->interface; pl->phy_state.link = up; mutex_unlock(&pl->state_mutex); phylink_run_resolve(pl); - netdev_dbg(pl->netdev, "phy link %s %s/%s\n", up ? "up" : "down", + netdev_dbg(pl->netdev, "phy link %s %s/%s/%s\n", up ? "up" : "down", + phy_modes(phydev->interface), phy_speed_to_str(phydev->speed), phy_duplex_to_str(phydev->duplex)); } diff --git a/include/linux/phylink.h b/include/linux/phylink.h index 097a97a669ba..9195c444aec5 100644 --- a/include/linux/phylink.h +++ b/include/linux/phylink.h @@ -27,6 +27,7 @@ enum { struct phylink_link_state { __ETHTOOL_DECLARE_LINK_MODE_MASK(advertising); __ETHTOOL_DECLARE_LINK_MODE_MASK(lp_advertising); + phy_interface_t interface; /* PHY_INTERFACE_xxx */ int speed; int duplex; int pause; -- cgit From bb861118a3596d1bd12aebcd5ed02f88e162782b Mon Sep 17 00:00:00 2001 From: Russell King Date: Mon, 19 Dec 2016 12:17:57 +0000 Subject: phylink: ensure link drops are reported When the MAC reports a link failure, it can be momentary. Ensure that the event is reported by latching the loss of link, so that the worker reports link down. Signed-off-by: Russell King --- drivers/net/phy/phylink.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index aa653e35d5a8..9ef8d80b6631 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -60,6 +60,8 @@ struct phylink { struct phylink_link_state phy_state; struct work_struct resolve; + bool mac_link_dropped; + const struct phylink_module_ops *module_ops; void *module_data; }; @@ -340,6 +342,9 @@ static void phylink_resolve(struct work_struct *w) mutex_lock(&pl->state_mutex); if (pl->phylink_disable_state) { + pl->mac_link_dropped = false; + link_state.link = false; + } else if (pl->mac_link_dropped) { link_state.link = false; } else { switch (pl->link_an_mode) { @@ -405,6 +410,10 @@ static void phylink_resolve(struct work_struct *w) phylink_pause_to_str(link_state.pause)); } } + if (!link_state.link && pl->mac_link_dropped) { + pl->mac_link_dropped = false; + queue_work(system_power_efficient_wq, &pl->resolve); + } mutex_unlock(&pl->state_mutex); } @@ -641,6 +650,8 @@ EXPORT_SYMBOL_GPL(phylink_disconnect_phy); void phylink_mac_change(struct phylink *pl, bool up) { + if (!up) + pl->mac_link_dropped = true; phylink_run_resolve(pl); netdev_dbg(pl->netdev, "mac link %s\n", up ? "up" : "down"); } -- cgit