From 49e1adec95cab65c208603084fa69ef053add8b7 Mon Sep 17 00:00:00 2001 From: Russell King Date: Tue, 17 Jan 2017 21:11:52 +0000 Subject: pci: mvebu: avoid changing the SCC bit in the link status register It seems on later Armada 38x, the slot clock configuration bit is not read-only, but can be written. This means that our RW1C protection ends up clearing this bit when the link control register is written. Adjust the mask so that we only avoid writing '1' bits to the RW1C bits of this register (bits 15 and 14 of the link status) rather than masking out all the status register bits. Signed-off-by: Russell King --- drivers/pci/host/pci-mvebu.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/pci/host/pci-mvebu.c b/drivers/pci/host/pci-mvebu.c index cd7d51988738..72d7292394dd 100644 --- a/drivers/pci/host/pci-mvebu.c +++ b/drivers/pci/host/pci-mvebu.c @@ -752,10 +752,11 @@ static int mvebu_sw_pci_bridge_write(struct mvebu_pcie_port *port, * If the mask is 0xffff0000, then we only want to write * the link control register, rather than clearing the * RW1C bits in the link status register. Mask out the - * status register bits. + * RW1C bits. */ if (mask == 0xffff0000) - value &= 0xffff; + value &= ~((PCI_EXP_LNKSTA_LABS | + PCI_EXP_LNKSTA_LBMS) << 16); mvebu_writel(port, value, PCIE_CAP_PCIEXP + PCI_EXP_LNKCTL); break; -- cgit From ee5003d0e5823c37d0254cbd83c9b01a57d3f671 Mon Sep 17 00:00:00 2001 From: Russell King Date: Sat, 9 Jul 2016 10:58:16 +0100 Subject: pci: mvebu: time out reset on link up If the port reports that the link is up while we are resetting, there's little point in waiting for the full duration. Signed-off-by: Russell King --- drivers/pci/host/pci-mvebu.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/drivers/pci/host/pci-mvebu.c b/drivers/pci/host/pci-mvebu.c index 72d7292394dd..bd8e78327c59 100644 --- a/drivers/pci/host/pci-mvebu.c +++ b/drivers/pci/host/pci-mvebu.c @@ -1183,6 +1183,7 @@ static int mvebu_pcie_powerup(struct mvebu_pcie_port *port) if (port->reset_gpio) { u32 reset_udelay = PCI_PM_D3COLD_WAIT * 1000; + unsigned int i; of_property_read_u32(port->dn, "reset-delay-us", &reset_udelay); @@ -1190,7 +1191,13 @@ static int mvebu_pcie_powerup(struct mvebu_pcie_port *port) udelay(100); gpiod_set_value_cansleep(port->reset_gpio, 0); - msleep(reset_udelay / 1000); + for (i = 0; i < reset_udelay; i += 1000) { + if (mvebu_pcie_link_up(port)) + break; + msleep(1); + } + + printk("%s: reset completed in %dus\n", port->name, i); } return 0; @@ -1278,15 +1285,16 @@ static int mvebu_pcie_probe(struct platform_device *pdev) if (!child) continue; - ret = mvebu_pcie_powerup(port); - if (ret < 0) - continue; - port->base = mvebu_pcie_map_registers(pdev, child, port); if (IS_ERR(port->base)) { dev_err(dev, "%s: cannot map registers\n", port->name); port->base = NULL; - mvebu_pcie_powerdown(port); + continue; + } + + ret = mvebu_pcie_powerup(port); + if (ret < 0) { + port->base = NULL; continue; } -- cgit From de4a069035179df677038a525f2cda58213e588c 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. Reviewed-by: Andrew Lunn Reviewed-by: Florian Fainelli 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 407b0b601ea8..82d915614646 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 \ - mdio-boardinfo.o + mdio-boardinfo.o phy-core.o libphy-$(CONFIG_SWPHY) += swphy.o libphy-$(CONFIG_LED_TRIGGER_PHY) += phy_led_triggers.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 97ff1278167b..3afa4cf2e813 100644 --- a/drivers/net/phy/phy.c +++ b/drivers/net/phy/phy.c @@ -1224,91 +1224,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 fb3857337151..02225fe78a5c 100644 --- a/include/linux/phy.h +++ b/include/linux/phy.h @@ -651,14 +651,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 @@ -752,16 +745,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 5520e8b52ef936c4056b640a9ae3d2991b01c43c 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. Reviewed-by: Andrew Lunn Reviewed-by: Florian Fainelli Signed-off-by: Russell King --- drivers/net/phy/phy-core.c | 33 +++++++++++++++++++++++++-------- include/linux/phy.h | 24 ++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 8 deletions(-) diff --git a/drivers/net/phy/phy-core.c b/drivers/net/phy/phy-core.c index b8d8276a3099..d791100afab2 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,21 @@ 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; + + 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); + } - regnum = MII_ADDR_C45 | ((devad & 0x1f) << 16) | (regnum & 0xffff); + phy_write_mmd_indirect(phydev, regnum, devad, val); - return mdiobus_write(phydev->mdio.bus, phydev->mdio.addr, regnum, val); + return 0; } EXPORT_SYMBOL(phy_write_mmd); diff --git a/include/linux/phy.h b/include/linux/phy.h index 02225fe78a5c..324e921cb5a1 100644 --- a/include/linux/phy.h +++ b/include/linux/phy.h @@ -587,6 +587,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 92bae5aef9625d24181796543b7d10c878c2722f Mon Sep 17 00:00:00 2001 From: Russell King Date: Wed, 11 Jan 2017 10:29:30 +0000 Subject: net: lan78xx: update for phy_(read|write)_mmd_indirect() removal lan78xx appears to use phylib in a rather weird way, accessing the PHY partly through phylib, and partly by making direct accesses to it, including to the Clause 45 registers. As the indirect MMD accessors are going away, update this driver to use the plain phy_(read|write)_mmd() accessors instead. Reviewed-by: Andrew Lunn Reviewed-by: Florian Fainelli Acked-by: Woojung Huh Signed-off-by: Russell King --- drivers/net/usb/lan78xx.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/net/usb/lan78xx.c b/drivers/net/usb/lan78xx.c index 636f48f19d1e..1bf62b371bf7 100644 --- a/drivers/net/usb/lan78xx.c +++ b/drivers/net/usb/lan78xx.c @@ -1952,10 +1952,10 @@ static int lan8835_fixup(struct phy_device *phydev) struct lan78xx_net *dev = netdev_priv(phydev->attached_dev); /* LED2/PME_N/IRQ_N/RGMII_ID pin to IRQ_N mode */ - buf = phy_read_mmd_indirect(phydev, 0x8010, 3); + buf = phy_read_mmd(phydev, MDIO_MMD_PCS, 0x8010); buf &= ~0x1800; buf |= 0x0800; - phy_write_mmd_indirect(phydev, 0x8010, 3, buf); + phy_write_mmd(phydev, MDIO_MMD_PCS, 0x8010, buf); /* RGMII MAC TXC Delay Enable */ ret = lan78xx_write_reg(dev, MAC_RGMII_ID, @@ -1975,11 +1975,11 @@ static int ksz9031rnx_fixup(struct phy_device *phydev) /* Micrel9301RNX PHY configuration */ /* RGMII Control Signal Pad Skew */ - phy_write_mmd_indirect(phydev, 4, 2, 0x0077); + phy_write_mmd(phydev, MDIO_MMD_WIS, 4, 0x0077); /* RGMII RX Data Pad Skew */ - phy_write_mmd_indirect(phydev, 5, 2, 0x7777); + phy_write_mmd(phydev, MDIO_MMD_WIS, 5, 0x7777); /* RGMII RX Clock Pad Skew */ - phy_write_mmd_indirect(phydev, 8, 2, 0x1FF); + phy_write_mmd(phydev, MDIO_MMD_WIS, 8, 0x1FF); dev->interface = PHY_INTERFACE_MODE_RGMII_RXID; -- cgit From f239ef49dd30e8b415bf26dd1fda4b4eeac07803 Mon Sep 17 00:00:00 2001 From: Russell King Date: Wed, 4 Jan 2017 19:30:30 +0000 Subject: net: phy: switch remaining users to phy_(read|write)_mmd() Switch everyone over to using phy_read_mmd() and phy_write_mmd() now that they are able to handle both Clause 22 indirect addressing and Clause 45 direct addressing methods to the MMD registers. Reviewed-by: Andrew Lunn Reviewed-by: Florian Fainelli Signed-off-by: Russell King --- drivers/net/phy/bcm-phy-lib.c | 12 ++++-------- drivers/net/phy/dp83867.c | 25 +++++++++++-------------- drivers/net/phy/intel-xway.c | 26 +++++++++++++------------- drivers/net/phy/microchip.c | 5 ++--- drivers/net/phy/phy.c | 25 ++++++++++--------------- drivers/net/phy/phy_device.c | 4 ++-- 6 files changed, 42 insertions(+), 55 deletions(-) diff --git a/drivers/net/phy/bcm-phy-lib.c b/drivers/net/phy/bcm-phy-lib.c index ab9ad689617c..90be6ee42dfa 100644 --- a/drivers/net/phy/bcm-phy-lib.c +++ b/drivers/net/phy/bcm-phy-lib.c @@ -201,8 +201,7 @@ int bcm_phy_set_eee(struct phy_device *phydev, bool enable) 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; @@ -211,12 +210,10 @@ int bcm_phy_set_eee(struct phy_device *phydev, bool enable) else 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; @@ -225,8 +222,7 @@ int bcm_phy_set_eee(struct phy_device *phydev, bool enable) else 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 19865530e0b1..b57f20e552ba 100644 --- a/drivers/net/phy/dp83867.c +++ b/drivers/net/phy/dp83867.c @@ -133,14 +133,14 @@ static int dp83867_config_port_mirroring(struct phy_device *phydev) (struct dp83867_private *)phydev->priv; u16 val; - val = phy_read_mmd_indirect(phydev, DP83867_CFG4, DP83867_DEVADDR); + val = phy_read_mmd(phydev, DP83867_DEVADDR, DP83867_CFG4); if (dp83867->port_mirroring == DP83867_PORT_MIRROING_EN) val |= DP83867_CFG4_PORT_MIRROR_EN; else val &= ~DP83867_CFG4_PORT_MIRROR_EN; - phy_write_mmd_indirect(phydev, DP83867_CFG4, DP83867_DEVADDR, val); + phy_write_mmd(phydev, DP83867_DEVADDR, DP83867_CFG4, val); return 0; } @@ -231,8 +231,7 @@ static int dp83867_config_init(struct phy_device *phydev) * register's bit 11 (marked as RESERVED). */ - bs = phy_read_mmd_indirect(phydev, DP83867_STRAP_STS1, - DP83867_DEVADDR); + bs = phy_read_mmd(phydev, DP83867_DEVADDR, DP83867_STRAP_STS1); if (bs & DP83867_STRAP_STS1_RESERVED) val &= ~DP83867_PHYCR_RESERVED_MASK; @@ -243,8 +242,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); @@ -255,25 +253,24 @@ 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); if (dp83867->io_impedance >= 0) { - val = phy_read_mmd_indirect(phydev, DP83867_IO_MUX_CFG, - DP83867_DEVADDR); + val = phy_read_mmd(phydev, DP83867_DEVADDR, + DP83867_IO_MUX_CFG); val &= ~DP83867_IO_MUX_CFG_IO_IMPEDANCE_CTRL; val |= dp83867->io_impedance & DP83867_IO_MUX_CFG_IO_IMPEDANCE_CTRL; - phy_write_mmd_indirect(phydev, DP83867_IO_MUX_CFG, - DP83867_DEVADDR, val); + phy_write_mmd(phydev, DP83867_DEVADDR, + DP83867_IO_MUX_CFG, val); } } diff --git a/drivers/net/phy/intel-xway.c b/drivers/net/phy/intel-xway.c index b1fd7bb0e4db..55f8c52dd2f1 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 324fbf6ad8ff..2b2f543cf9f0 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 3afa4cf2e813..f123ffd97b27 100644 --- a/drivers/net/phy/phy.c +++ b/drivers/net/phy/phy.c @@ -1259,8 +1259,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; @@ -1271,13 +1270,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; @@ -1290,14 +1287,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 */ @@ -1319,7 +1314,7 @@ int phy_get_eee_err(struct phy_device *phydev) if (!phydev->drv) return -EIO; - 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); @@ -1339,19 +1334,19 @@ int phy_ethtool_get_eee(struct phy_device *phydev, struct ethtool_eee *data) return -EIO; /* 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); @@ -1377,7 +1372,7 @@ int phy_ethtool_set_eee(struct phy_device *phydev, struct ethtool_eee *data) /* Mask prohibited EEE modes */ val &= ~phydev->eee_broken_modes; - 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; } diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c index 5198ccfa347f..1219eeab69d1 100644 --- a/drivers/net/phy/phy_device.c +++ b/drivers/net/phy/phy_device.c @@ -1217,7 +1217,7 @@ static int genphy_config_eee_advert(struct phy_device *phydev) * supported by the phy. If we read 0, EEE is not advertised * In both case, we don't need to continue */ - adv = phy_read_mmd_indirect(phydev, MDIO_AN_EEE_ADV, MDIO_MMD_AN); + adv = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV); if (adv <= 0) return 0; @@ -1228,7 +1228,7 @@ static int genphy_config_eee_advert(struct phy_device *phydev) if (old_adv == adv) return 0; - phy_write_mmd_indirect(phydev, MDIO_AN_EEE_ADV, MDIO_MMD_AN, adv); + phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV, adv); return 1; } -- cgit From 3adb28a8b86aad1f6a193e8a38816bf6335b6a5a 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. Reviewed-by: Andrew Lunn Reviewed-by: Florian Fainelli 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 da5b39268370..6a5fd18f062c 100644 --- a/drivers/net/phy/micrel.c +++ b/drivers/net/phy/micrel.c @@ -626,8 +626,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; } @@ -635,10 +634,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) @@ -946,8 +945,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 6a343c42be2584a86a6574976515cc24b6e81390 Mon Sep 17 00:00:00 2001 From: Russell King Date: Tue, 21 Mar 2017 13:05:07 +0000 Subject: net: phy: deprecate indirect MMD read/write methods Prepare to remove the indirect MMD accessors; code them in terms of the simpler accessors, and mark the indirect ones deprecated to avoid new users. Signed-off-by: Russell King --- drivers/net/phy/phy-core.c | 119 +++++++++++++-------------------------------- include/linux/phy.h | 31 ++++-------- 2 files changed, 44 insertions(+), 106 deletions(-) diff --git a/drivers/net/phy/phy-core.c b/drivers/net/phy/phy-core.c index d791100afab2..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,21 +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; - phy_write_mmd_indirect(phydev, regnum, devad, val); + mutex_lock(&bus->mdio_lock); + mmd_phy_indirect(bus, regnum, devad, phy_addr); - return 0; + /* 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 324e921cb5a1..3d40b9b2bf7c 100644 --- a/include/linux/phy.h +++ b/include/linux/phy.h @@ -611,24 +611,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, @@ -686,7 +668,11 @@ int phy_read_mmd(struct phy_device *phydev, int devad, u32 regnum); * Description: it reads data from the MMD registers (clause 22 to access to * clause 45) of the specified phy address. */ -int phy_read_mmd_indirect(struct phy_device *phydev, int prtad, int devad); +static inline __deprecated int phy_read_mmd_indirect(struct phy_device *phydev, + int prtad, int devad) +{ + return phy_read_mmd(phydev, devad, prtad); +} /** * phy_read - Convenience function for reading a given PHY register @@ -781,8 +767,11 @@ int phy_write_mmd(struct phy_device *phydev, int devad, u32 regnum, u16 val); * Description: Write data from the MMD registers of the specified * phy address. */ -void phy_write_mmd_indirect(struct phy_device *phydev, int prtad, - int devad, u32 data); +static inline __deprecated void phy_write_mmd_indirect( + struct phy_device *phydev, int prtad, int devad, u32 data) +{ + phy_write_mmd(phydev, devad, prtad, data); +} struct phy_device *phy_device_create(struct mii_bus *bus, int addr, int phy_id, bool is_c45, -- cgit From 94c6ef24d489a289a99af37f6b0e7ab797e30eef 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. Reviewed-by: Andrew Lunn Reviewed-by: Florian Fainelli Signed-off-by: Russell King --- include/linux/phy.h | 31 ------------------------------- 1 file changed, 31 deletions(-) diff --git a/include/linux/phy.h b/include/linux/phy.h index 3d40b9b2bf7c..6edefbd16cdb 100644 --- a/include/linux/phy.h +++ b/include/linux/phy.h @@ -659,21 +659,6 @@ struct phy_fixup { */ int phy_read_mmd(struct phy_device *phydev, int devad, u32 regnum); -/** - * phy_read_mmd_indirect - reads data from the MMD registers - * @phydev: The PHY device bus - * @prtad: MMD Address - * @addr: PHY address on the MII bus - * - * Description: it reads data from the MMD registers (clause 22 to access to - * clause 45) of the specified phy address. - */ -static inline __deprecated int phy_read_mmd_indirect(struct phy_device *phydev, - int prtad, int devad) -{ - return phy_read_mmd(phydev, devad, prtad); -} - /** * phy_read - Convenience function for reading a given PHY register * @phydev: the phy_device struct @@ -757,22 +742,6 @@ static inline bool phy_is_pseudo_fixed_link(struct phy_device *phydev) */ int phy_write_mmd(struct phy_device *phydev, int devad, u32 regnum, u16 val); -/** - * 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. - */ -static inline __deprecated void phy_write_mmd_indirect( - struct phy_device *phydev, int prtad, int devad, u32 data) -{ - phy_write_mmd(phydev, devad, prtad, data); -} - struct phy_device *phy_device_create(struct mii_bus *bus, int addr, int phy_id, bool is_c45, struct phy_c45_device_ids *c45_ids); -- cgit From 53f262cc1787bbf5b58c5abbfa9d6317cd98157d 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. Reviewed-by: Andrew Lunn Reviewed-by: Florian Fainelli 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 1af90da124a9a9094030c1c743c4b6f1bab22438 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. Reviewed-by: Florian Fainelli Signed-off-by: Russell King --- drivers/net/phy/phy.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c index f123ffd97b27..d89df329c008 100644 --- a/drivers/net/phy/phy.c +++ b/drivers/net/phy/phy.c @@ -1364,17 +1364,22 @@ 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; if (!phydev->drv) return -EIO; - /* Mask prohibited EEE modes */ - val &= ~phydev->eee_broken_modes; + /* Get Supported EEE */ + cap = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_PCS_EEE_ABLE); + if (cap < 0) + return cap; - phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV, val); + adv = ethtool_adv_to_mmd_eee_adv_t(data->advertised) & cap; - return 0; + /* Mask prohibited EEE modes */ + adv &= ~phydev->eee_broken_modes; + + return phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV, adv); } EXPORT_SYMBOL(phy_ethtool_set_eee); -- cgit From ba39c056a67a59ba2a1d6924b45700b81ab8243e Mon Sep 17 00:00:00 2001 From: Russell King Date: Thu, 5 Jan 2017 12:21:09 +0000 Subject: net: phy: restart phy autonegotiation after EEE advertisment change When the EEE advertisment is changed, we should restart autonegotiation to update the link partner with the new EEE settings. Add this trigger but only if the advertisment has changed. Reviewed-by: Florian Fainelli Signed-off-by: Russell King --- drivers/net/phy/phy.c | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c index d89df329c008..4224c3cfd4bb 100644 --- a/drivers/net/phy/phy.c +++ b/drivers/net/phy/phy.c @@ -1364,7 +1364,7 @@ EXPORT_SYMBOL(phy_ethtool_get_eee); */ int phy_ethtool_set_eee(struct phy_device *phydev, struct ethtool_eee *data) { - int cap, adv; + int cap, old_adv, adv, ret; if (!phydev->drv) return -EIO; @@ -1374,12 +1374,29 @@ int phy_ethtool_set_eee(struct phy_device *phydev, struct ethtool_eee *data) if (cap < 0) return cap; + old_adv = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV); + if (old_adv < 0) + return old_adv; + adv = ethtool_adv_to_mmd_eee_adv_t(data->advertised) & cap; /* Mask prohibited EEE modes */ adv &= ~phydev->eee_broken_modes; - return phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV, adv); + if (old_adv != adv) { + ret = phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV, adv); + if (ret < 0) + return ret; + + /* Restart autonegotiation so the new modes get sent to the + * link partner. + */ + ret = genphy_restart_aneg(phydev); + if (ret < 0) + return ret; + } + + return 0; } EXPORT_SYMBOL(phy_ethtool_set_eee); -- cgit From 7569ff7cc9addf6113c0d7a74a98eaf32c7d4c3c Mon Sep 17 00:00:00 2001 From: Russell King Date: Thu, 5 Jan 2017 09:34:42 +0000 Subject: net: phy: allow EEE with any interface mode EEE is able to work in any PHY interface mode, there is nothing which fundamentally restricts it to only a few modes. For example, EEE works in SGMII mode with the Marvell 88E1512. Rather than just adding SGMII mode to the list, Florian suggests removing the list of interface modes entirely: It actually sounds like we should just kill the check entirely, it does not appear that any of the interface mode would not fundamentally be able to support EEE, because the "lowest" mode we support is MII, and even there it's quite possible to support EEE. Signed-off-by: Russell King --- drivers/net/phy/phy.c | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c index 4224c3cfd4bb..3492ceedfb1b 100644 --- a/drivers/net/phy/phy.c +++ b/drivers/net/phy/phy.c @@ -1240,15 +1240,8 @@ int phy_init_eee(struct phy_device *phydev, bool clk_stop_enable) return -EIO; /* According to 802.3az,the EEE is supported only in full duplex-mode. - * Also EEE feature is active when core is operating with MII, GMII - * or RGMII (all kinds). Internal PHYs are also allowed to proceed and - * should return an error if they do not support EEE. */ - if ((phydev->duplex == DUPLEX_FULL) && - ((phydev->interface == PHY_INTERFACE_MODE_MII) || - (phydev->interface == PHY_INTERFACE_MODE_GMII) || - phy_interface_is_rgmii(phydev) || - phy_is_internal(phydev))) { + if (phydev->duplex == DUPLEX_FULL) { int eee_lp, eee_cap, eee_adv; u32 lp, cap, adv; int status; -- cgit From 52696c4261659c289ae9c0f418f025372bea1e2d 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 | 109 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 66 insertions(+), 43 deletions(-) diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c index 3492ceedfb1b..e98b6e275ed7 100644 --- a/drivers/net/phy/phy.c +++ b/drivers/net/phy/phy.c @@ -162,7 +162,9 @@ struct phy_setting { u32 setting; }; -/* A mapping of all SUPPORTED settings to speed/duplex */ +/* A mapping of all SUPPORTED settings to speed/duplex. This table + * must be grouped by speed and sorted in descending match priority + * - iow, descending speed. */ static const struct phy_setting settings[] = { { .speed = SPEED_10000, @@ -221,45 +223,70 @@ static const struct phy_setting settings[] = { }, }; -#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 + * @feature: allowed 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. * - * 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 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, u32 features, 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->setting & features) { + 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; + } + } + } - while (idx < ARRAY_SIZE(settings) && - (settings[idx].speed != speed || settings[idx].duplex != duplex)) - idx++; + 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++; - - return idx < MAX_NUM_SETTINGS ? idx : MAX_NUM_SETTINGS - 1; + return phy_lookup_setting(speed, duplex, supported, false); } /** @@ -279,11 +306,9 @@ unsigned int phy_supported_speeds(struct phy_device *phy, unsigned int count = 0; unsigned int idx = 0; - while (idx < MAX_NUM_SETTINGS && count < size) { - idx = phy_find_valid(idx, phy->supported); - + for (idx = 0; idx < ARRAY_SIZE(settings) && count < size; idx++) { if (!(settings[idx].setting & phy->supported)) - break; + continue; /* Assumes settings are grouped by speed */ if ((count == 0) || @@ -291,7 +316,6 @@ unsigned int phy_supported_speeds(struct phy_device *phy, speeds[count] = settings[idx].speed; count++; } - idx++; } return count; @@ -308,12 +332,7 @@ unsigned int phy_supported_speeds(struct phy_device *phy, */ static inline bool phy_check_valid(int speed, int duplex, u32 features) { - unsigned int idx; - - 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, features, true); } /** @@ -326,18 +345,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 093fec57fc2c75e1c21c2259b18ec8456f1ba7c9 Mon Sep 17 00:00:00 2001 From: Russell King Date: Mon, 27 Mar 2017 23:56:10 +0100 Subject: net: phy: simplify phy_supported_speeds() Simplify the loop in phy_supported_speeds(). Signed-off-by: Russell King --- drivers/net/phy/phy.c | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c index e98b6e275ed7..57db2104e2b4 100644 --- a/drivers/net/phy/phy.c +++ b/drivers/net/phy/phy.c @@ -306,17 +306,11 @@ unsigned int phy_supported_speeds(struct phy_device *phy, unsigned int count = 0; unsigned int idx = 0; - for (idx = 0; idx < ARRAY_SIZE(settings) && count < size; idx++) { - if (!(settings[idx].setting & phy->supported)) - continue; - + for (idx = 0; idx < ARRAY_SIZE(settings) && count < size; idx++) /* Assumes settings are grouped by speed */ - if ((count == 0) || - (speeds[count - 1] != settings[idx].speed)) { - speeds[count] = settings[idx].speed; - count++; - } - } + if ((settings[idx].setting & phy->supported) && + (count == 0 || speeds[count - 1] != settings[idx].speed)) + speeds[count++] = settings[idx].speed; return count; } -- cgit From b222cc60b97c84d32d3ea4ca7ba88c3965f09f9d 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 | 2 +- drivers/net/phy/phy-c45.c | 231 +++++++++++++++++++++++++++++++++++++++++++ drivers/net/phy/phy_device.c | 20 ++-- include/linux/phy.h | 12 +++ 4 files changed, 250 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 82d915614646..3f93da65ed7f 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 \ - mdio-boardinfo.o phy-core.o + mdio-boardinfo.o phy-c45.o phy-core.o libphy-$(CONFIG_SWPHY) += swphy.o libphy-$(CONFIG_LED_TRIGGER_PHY) += phy_led_triggers.o diff --git a/drivers/net/phy/phy-c45.c b/drivers/net/phy/phy-c45.c new file mode 100644 index 000000000000..252e49864215 --- /dev/null +++ b/drivers/net/phy/phy-c45.c @@ -0,0 +1,231 @@ +/* + * 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); + + /* 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) + 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 1219eeab69d1..040575dba98c 100644 --- a/drivers/net/phy/phy_device.c +++ b/drivers/net/phy/phy_device.c @@ -1483,27 +1483,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 6edefbd16cdb..95f27f1f4dd0 100644 --- a/include/linux/phy.h +++ b/include/linux/phy.h @@ -793,6 +793,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); @@ -807,6 +809,16 @@ static inline int genphy_no_soft_reset(struct phy_device *phydev) { return 0; } + +/* 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 3fb677c69a15b786354c0d6f69825f3270ec9cc2 Mon Sep 17 00:00:00 2001 From: Russell King Date: Fri, 13 Jan 2017 11:17:30 +0000 Subject: net: phy: hook up clause 45 autonegotiation restart genphy_restart_aneg() can only restart autonegotiation on clause 22 PHYs. Add a phy_restart_aneg() function which selects between the clause 22 and clause 45 restart functionality depending on the PHY type. Signed-off-by: Russell King --- drivers/net/phy/phy.c | 23 +++++++++++++++++++++-- include/linux/phy.h | 1 + 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c index 57db2104e2b4..04cb14eb155a 100644 --- a/drivers/net/phy/phy.c +++ b/drivers/net/phy/phy.c @@ -135,6 +135,25 @@ static int phy_config_interrupt(struct phy_device *phydev, u32 interrupts) return 0; } +/** + * phy_restart_aneg - restart auto-negotiation + * @phydev: target phy_device struct + * + * Restart the autonegotiation on @phydev. Returns >= 0 on success or + * negative errno on error. + */ +int phy_restart_aneg(struct phy_device *phydev) +{ + int ret; + + if (phydev->is_c45) + ret = genphy_c45_restart_aneg(phydev); + else + ret = genphy_restart_aneg(phydev); + + return ret; +} +EXPORT_SYMBOL_GPL(phy_restart_aneg); /** * phy_aneg_done - return auto-negotiation status @@ -1401,7 +1420,7 @@ int phy_ethtool_set_eee(struct phy_device *phydev, struct ethtool_eee *data) /* Restart autonegotiation so the new modes get sent to the * link partner. */ - ret = genphy_restart_aneg(phydev); + ret = phy_restart_aneg(phydev); if (ret < 0) return ret; } @@ -1460,6 +1479,6 @@ int phy_ethtool_nway_reset(struct net_device *ndev) if (!phydev->drv) return -EIO; - return genphy_restart_aneg(phydev); + return phy_restart_aneg(phydev); } EXPORT_SYMBOL(phy_ethtool_nway_reset); diff --git a/include/linux/phy.h b/include/linux/phy.h index 95f27f1f4dd0..ef3b617eae0b 100644 --- a/include/linux/phy.h +++ b/include/linux/phy.h @@ -770,6 +770,7 @@ int phy_start_aneg(struct phy_device *phydev); int phy_aneg_done(struct phy_device *phydev); int phy_stop_interrupts(struct phy_device *phydev); +int phy_restart_aneg(struct phy_device *phydev); static inline int phy_read_status(struct phy_device *phydev) { -- cgit From a4b4b9af610051ca39d36425bbe6faba228bd69a Mon Sep 17 00:00:00 2001 From: Russell King Date: Wed, 4 Jan 2017 10:38:47 +0000 Subject: net: phy: split out 10G genphy support Move the old 10G genphy support to sit beside the new clause 45 library functions, so all the 10G phy code is together. Signed-off-by: Russell King --- drivers/net/phy/phy-c45.c | 64 ++++++++++++++++++++++++++ drivers/net/phy/phy_device.c | 105 ++++++++----------------------------------- 2 files changed, 83 insertions(+), 86 deletions(-) diff --git a/drivers/net/phy/phy-c45.c b/drivers/net/phy/phy-c45.c index 252e49864215..ed77b1cb61de 100644 --- a/drivers/net/phy/phy-c45.c +++ b/drivers/net/phy/phy-c45.c @@ -229,3 +229,67 @@ int genphy_c45_read_pma(struct phy_device *phydev) return 0; } EXPORT_SYMBOL_GPL(genphy_c45_read_pma); + +/* The gen10g_* functions are the old Clause 45 stub */ + +static int gen10g_config_aneg(struct phy_device *phydev) +{ + return 0; +} + +static int gen10g_read_status(struct phy_device *phydev) +{ + u32 mmd_mask = phydev->c45_ids.devices_in_package; + int ret; + + /* For now just lie and say it's 10G all the time */ + phydev->speed = SPEED_10000; + phydev->duplex = DUPLEX_FULL; + + /* Avoid reading the vendor MMDs */ + mmd_mask &= ~(BIT(MDIO_MMD_VEND1) | BIT(MDIO_MMD_VEND2)); + + ret = genphy_c45_read_link(phydev, mmd_mask); + + phydev->link = ret > 0 ? 1 : 0; + + return 0; +} + +static int gen10g_soft_reset(struct phy_device *phydev) +{ + /* Do nothing for now */ + return 0; +} + +static int gen10g_config_init(struct phy_device *phydev) +{ + /* Temporarily just say we support everything */ + phydev->supported = SUPPORTED_10000baseT_Full; + phydev->advertising = SUPPORTED_10000baseT_Full; + + return 0; +} + +static int gen10g_suspend(struct phy_device *phydev) +{ + return 0; +} + +static int gen10g_resume(struct phy_device *phydev) +{ + return 0; +} + +struct phy_driver genphy_10g_driver = { + .phy_id = 0xffffffff, + .phy_id_mask = 0xffffffff, + .name = "Generic 10G PHY", + .soft_reset = gen10g_soft_reset, + .config_init = gen10g_config_init, + .features = 0, + .config_aneg = gen10g_config_aneg, + .read_status = gen10g_read_status, + .suspend = gen10g_suspend, + .resume = gen10g_resume, +}; diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c index 040575dba98c..cedd1fd91626 100644 --- a/drivers/net/phy/phy_device.c +++ b/drivers/net/phy/phy_device.c @@ -69,13 +69,8 @@ static void phy_mdio_device_remove(struct mdio_device *mdiodev) phy_device_remove(phydev); } -enum genphy_driver { - GENPHY_DRV_1G, - GENPHY_DRV_10G, - GENPHY_DRV_MAX -}; - -static struct phy_driver genphy_driver[GENPHY_DRV_MAX]; +static struct phy_driver genphy_driver; +extern struct phy_driver genphy_10g_driver; static LIST_HEAD(phy_fixup_list); static DEFINE_MUTEX(phy_fixup_lock); @@ -928,11 +923,9 @@ int phy_attach_direct(struct net_device *dev, struct phy_device *phydev, */ if (!d->driver) { if (phydev->is_c45) - d->driver = - &genphy_driver[GENPHY_DRV_10G].mdiodrv.driver; + d->driver = &genphy_10g_driver.mdiodrv.driver; else - d->driver = - &genphy_driver[GENPHY_DRV_1G].mdiodrv.driver; + d->driver = &genphy_driver.mdiodrv.driver; using_genphy = true; } @@ -1048,7 +1041,6 @@ void phy_detach(struct phy_device *phydev) struct net_device *dev = phydev->attached_dev; struct module *ndev_owner = dev->dev.parent->driver->owner; struct mii_bus *bus; - int i; phydev->attached_dev->phydev = NULL; phydev->attached_dev = NULL; @@ -1063,13 +1055,9 @@ void phy_detach(struct phy_device *phydev) * from the generic driver so that there's a chance a * real driver could be loaded */ - for (i = 0; i < ARRAY_SIZE(genphy_driver); i++) { - if (phydev->mdio.dev.driver == - &genphy_driver[i].mdiodrv.driver) { - device_release_driver(&phydev->mdio.dev); - break; - } - } + if (phydev->mdio.dev.driver == &genphy_10g_driver.mdiodrv.driver || + phydev->mdio.dev.driver == &genphy_driver.mdiodrv.driver) + device_release_driver(&phydev->mdio.dev); /* * The phydev might go away on the put_device() below, so avoid @@ -1343,11 +1331,6 @@ int genphy_aneg_done(struct phy_device *phydev) } EXPORT_SYMBOL(genphy_aneg_done); -static int gen10g_config_aneg(struct phy_device *phydev) -{ - return 0; -} - /** * genphy_update_link - update link status in @phydev * @phydev: target phy_device struct @@ -1481,25 +1464,6 @@ int genphy_read_status(struct phy_device *phydev) } EXPORT_SYMBOL(genphy_read_status); -static int gen10g_read_status(struct phy_device *phydev) -{ - u32 mmd_mask = phydev->c45_ids.devices_in_package; - int ret; - - /* For now just lie and say it's 10G all the time */ - phydev->speed = SPEED_10000; - phydev->duplex = DUPLEX_FULL; - - /* Avoid reading the vendor MMDs */ - mmd_mask &= ~(BIT(MDIO_MMD_VEND1) | BIT(MDIO_MMD_VEND2)); - - ret = genphy_c45_read_link(phydev, mmd_mask); - - phydev->link = ret > 0 ? 1 : 0; - - return 0; -} - /** * genphy_soft_reset - software reset the PHY via BMCR_RESET bit * @phydev: target phy_device struct @@ -1563,23 +1527,8 @@ int genphy_config_init(struct phy_device *phydev) return 0; } - -static int gen10g_soft_reset(struct phy_device *phydev) -{ - /* Do nothing for now */ - return 0; -} EXPORT_SYMBOL(genphy_config_init); -static int gen10g_config_init(struct phy_device *phydev) -{ - /* Temporarily just say we support everything */ - phydev->supported = SUPPORTED_10000baseT_Full; - phydev->advertising = SUPPORTED_10000baseT_Full; - - return 0; -} - int genphy_suspend(struct phy_device *phydev) { int value; @@ -1595,11 +1544,6 @@ int genphy_suspend(struct phy_device *phydev) } EXPORT_SYMBOL(genphy_suspend); -static int gen10g_suspend(struct phy_device *phydev) -{ - return 0; -} - int genphy_resume(struct phy_device *phydev) { int value; @@ -1615,11 +1559,6 @@ int genphy_resume(struct phy_device *phydev) } EXPORT_SYMBOL(genphy_resume); -static int gen10g_resume(struct phy_device *phydev) -{ - return 0; -} - static int __set_phy_supported(struct phy_device *phydev, u32 max_speed) { /* The default values for phydev->supported are provided by the PHY @@ -1851,8 +1790,7 @@ void phy_drivers_unregister(struct phy_driver *drv, int n) } EXPORT_SYMBOL(phy_drivers_unregister); -static struct phy_driver genphy_driver[] = { -{ +static struct phy_driver genphy_driver = { .phy_id = 0xffffffff, .phy_id_mask = 0xffffffff, .name = "Generic PHY", @@ -1866,18 +1804,7 @@ static struct phy_driver genphy_driver[] = { .read_status = genphy_read_status, .suspend = genphy_suspend, .resume = genphy_resume, -}, { - .phy_id = 0xffffffff, - .phy_id_mask = 0xffffffff, - .name = "Generic 10G PHY", - .soft_reset = gen10g_soft_reset, - .config_init = gen10g_config_init, - .features = 0, - .config_aneg = gen10g_config_aneg, - .read_status = gen10g_read_status, - .suspend = gen10g_suspend, - .resume = gen10g_resume, -} }; +}; static int __init phy_init(void) { @@ -1887,18 +1814,24 @@ static int __init phy_init(void) if (rc) return rc; - rc = phy_drivers_register(genphy_driver, - ARRAY_SIZE(genphy_driver), THIS_MODULE); + rc = phy_driver_register(&genphy_10g_driver, THIS_MODULE); if (rc) + goto err_10g; + + rc = phy_driver_register(&genphy_driver, THIS_MODULE); + if (rc) { + phy_driver_unregister(&genphy_10g_driver); +err_10g: mdio_bus_exit(); + } return rc; } static void __exit phy_exit(void) { - phy_drivers_unregister(genphy_driver, - ARRAY_SIZE(genphy_driver)); + phy_driver_unregister(&genphy_10g_driver); + phy_driver_unregister(&genphy_driver); mdio_bus_exit(); } -- cgit From a193dfe58a35fa2e9bfbbf555c41f9e338e54ccf Mon Sep 17 00:00:00 2001 From: Russell King Date: Wed, 11 Jan 2017 21:59:40 +0000 Subject: net: phy: add XAUI and 10GBASE-KR PHY connection types XAUI allows XGMII to reach an extended distance by using a XGXS layer at each end of the MAC to PHY link, operating over four Serdes lanes. 10GBASE-KR is a single lane Serdes backplane ethernet connection method with autonegotiation on the link. Some PHYs use this to connect to the ethernet interface at 10G speeds, switching to other connection types when utilising slower speeds. 10GBASE-KR is also used for XFI and SFI to connect to XFP and SFP fiber modules. Signed-off-by: Russell King --- Documentation/devicetree/bindings/net/ethernet.txt | 2 ++ include/linux/phy.h | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/Documentation/devicetree/bindings/net/ethernet.txt b/Documentation/devicetree/bindings/net/ethernet.txt index 3a6916909d90..d4abe9a98109 100644 --- a/Documentation/devicetree/bindings/net/ethernet.txt +++ b/Documentation/devicetree/bindings/net/ethernet.txt @@ -32,6 +32,8 @@ The following properties are common to the Ethernet controllers: * "2000base-x", * "2500base-x", * "rxaui" + * "xaui" + * "10gbase-kr" (10GBASE-KR, XFI, SFI) - phy-connection-type: the same as "phy-mode" property but described in ePAPR; - phy-handle: phandle, specifies a reference to a node representing a PHY device; this property is described in ePAPR and so preferred; diff --git a/include/linux/phy.h b/include/linux/phy.h index ef3b617eae0b..a77e0be9ba37 100644 --- a/include/linux/phy.h +++ b/include/linux/phy.h @@ -84,6 +84,8 @@ typedef enum { PHY_INTERFACE_MODE_1000BASEX, PHY_INTERFACE_MODE_2500BASEX, PHY_INTERFACE_MODE_RXAUI, + PHY_INTERFACE_MODE_XAUI, + PHY_INTERFACE_MODE_10GKR, /* 10GBASE-KR, XFI, SFI - single lane 10G Serdes) */ PHY_INTERFACE_MODE_MAX, } phy_interface_t; @@ -150,6 +152,10 @@ static inline const char *phy_modes(phy_interface_t interface) return "2500base-x"; case PHY_INTERFACE_MODE_RXAUI: return "rxaui"; + case PHY_INTERFACE_MODE_XAUI: + return "xaui"; + case PHY_INTERFACE_MODE_10GKR: + return "10gbase-kr"; default: return "unknown"; } -- cgit From 0c36cc87784e8692e0ce39ec18f6b6999cfef7e6 Mon Sep 17 00:00:00 2001 From: Russell King Date: Thu, 29 Dec 2016 11:03:34 +0000 Subject: net: phy: add Marvell Alaska X 88X3310 10Gigabit PHY support Add phylib support for the Marvell Alaska X 10 Gigabit PHY (MV88X3310). This phy is able to operate at 10G, 1G, 100M and 10M speeds, and only supports Clause 45 accesses. The PHY appears (based on the vendor IDs) to be two different vendors IP, with each devad containing several instances. This PHY driver has only been tested with the RJ45 copper port and a Marvell Armada 8040-based ethernet interface. It should be noted that to use the full range of speeds, MAC drivers need to also reconfigure the link mode as per phydev->interface, since the PHY automatically changes its interface mode depending on the negotiated speed. Signed-off-by: Russell King --- drivers/net/phy/Makefile | 2 +- drivers/net/phy/marvell10g.c | 357 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 358 insertions(+), 1 deletion(-) create mode 100644 drivers/net/phy/marvell10g.c diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile index 3f93da65ed7f..b56d9406c559 100644 --- a/drivers/net/phy/Makefile +++ b/drivers/net/phy/Makefile @@ -42,7 +42,7 @@ obj-$(CONFIG_ICPLUS_PHY) += icplus.o obj-$(CONFIG_INTEL_XWAY_PHY) += intel-xway.o obj-$(CONFIG_LSI_ET1011C_PHY) += et1011c.o obj-$(CONFIG_LXT_PHY) += lxt.o -obj-$(CONFIG_MARVELL_PHY) += marvell.o +obj-$(CONFIG_MARVELL_PHY) += marvell.o marvell10g.o obj-$(CONFIG_MESON_GXL_PHY) += meson-gxl.o obj-$(CONFIG_MICREL_KS8995MA) += spi_ks8995.o obj-$(CONFIG_MICREL_PHY) += micrel.o diff --git a/drivers/net/phy/marvell10g.c b/drivers/net/phy/marvell10g.c new file mode 100644 index 000000000000..e0a86ce0dd43 --- /dev/null +++ b/drivers/net/phy/marvell10g.c @@ -0,0 +1,357 @@ +/* + * Marvell 10G 88x3310 PHY driver + * + * Based upon the ID registers, this PHY appears to be a mixture of IPs + * from two different companies. + * + * There appears to be several different data paths through the PHY which + * are automatically managed by the PHY. The following has been determined + * via observation and experimentation: + * + * SGMII PHYXS -- BASE-T PCS -- 10G PMA -- AN -- Copper (for <= 1G) + * 10GBASE-KR PHYXS -- BASE-T PCS -- 10G PMA -- AN -- Copper (for 10G) + * 10GBASE-KR PHYXS -- BASE-R PCS -- Fiber + * + * If both the fiber and copper ports are connected, the first to gain + * link takes priority and the other port is completely locked out. + */ +#include + +enum { + MV_PCS_BASE_T = 0x0000, + MV_PCS_BASE_R = 0x1000, + MV_PCS_1000BASEX = 0x2000, + + /* These registers appear at 0x800X and 0xa00X - the 0xa00X control + * registers appear to set themselves to the 0x800X when AN is + * restarted, but status registers appear readable from either. + */ + MV_AN_CTRL1000 = 0x8000, /* 1000base-T control register */ + MV_AN_STAT1000 = 0x8001, /* 1000base-T status register */ + + /* The AN result register reflects the copper status */ + MV_AN_RESULT = 0xa016, + MV_AN_RESULT_SPD_10 = BIT(12), + MV_AN_RESULT_SPD_100 = BIT(13), + MV_AN_RESULT_SPD_1000 = BIT(14), + MV_AN_RESULT_SPD_10000 = BIT(15), +}; + +static int mv3310_modify(struct phy_device *phydev, int devad, u16 reg, + u16 mask, u16 bits) +{ + int old, val, ret; + + old = phy_read_mmd(phydev, devad, reg); + if (old < 0) + return old; + + val = (old & ~mask) | (bits & mask); + if (val == old) + return 0; + + ret = phy_write_mmd(phydev, devad, reg, val); + + return ret < 0 ? ret : 1; +} + +static int mv3310_probe(struct phy_device *phydev) +{ + u32 mmd_mask = MDIO_DEVS_PMAPMD | MDIO_DEVS_AN; + + if (!phydev->is_c45 || + (phydev->c45_ids.devices_in_package & mmd_mask) != mmd_mask) + return -ENODEV; + + return 0; +} + +static int mv3310_soft_reset(struct phy_device *phydev) +{ + return 0; +} + +static int mv3310_config_init(struct phy_device *phydev) +{ + __ETHTOOL_DECLARE_LINK_MODE_MASK(supported) = { 0, }; + u32 mask; + int val; + + /* Check that the PHY interface type is compatible */ + if (phydev->interface != PHY_INTERFACE_MODE_SGMII && + phydev->interface != PHY_INTERFACE_MODE_XGMII && + phydev->interface != PHY_INTERFACE_MODE_XAUI && + phydev->interface != PHY_INTERFACE_MODE_RXAUI && + phydev->interface != PHY_INTERFACE_MODE_10GKR) + return -ENODEV; + + __set_bit(ETHTOOL_LINK_MODE_Pause_BIT, supported); + __set_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, supported); + + if (phydev->c45_ids.devices_in_package & MDIO_DEVS_AN) { + val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1); + if (val < 0) + return val; + + if (val & MDIO_AN_STAT1_ABLE) + __set_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, supported); + } + + val = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_STAT2); + if (val < 0) + return val; + + /* Ethtool does not support the WAN mode bits */ + if (val & (MDIO_PMA_STAT2_10GBSR | MDIO_PMA_STAT2_10GBLR | + MDIO_PMA_STAT2_10GBER | MDIO_PMA_STAT2_10GBLX4 | + MDIO_PMA_STAT2_10GBSW | MDIO_PMA_STAT2_10GBLW | + MDIO_PMA_STAT2_10GBEW)) + __set_bit(ETHTOOL_LINK_MODE_FIBRE_BIT, supported); + if (val & MDIO_PMA_STAT2_10GBSR) + __set_bit(ETHTOOL_LINK_MODE_10000baseSR_Full_BIT, supported); + if (val & MDIO_PMA_STAT2_10GBLR) + __set_bit(ETHTOOL_LINK_MODE_10000baseLR_Full_BIT, supported); + if (val & MDIO_PMA_STAT2_10GBER) + __set_bit(ETHTOOL_LINK_MODE_10000baseER_Full_BIT, supported); + + if (val & MDIO_PMA_STAT2_EXTABLE) { + val = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_PMA_EXTABLE); + if (val < 0) + return val; + + if (val & (MDIO_PMA_EXTABLE_10GBT | MDIO_PMA_EXTABLE_1000BT | + MDIO_PMA_EXTABLE_100BTX | MDIO_PMA_EXTABLE_10BT)) + __set_bit(ETHTOOL_LINK_MODE_TP_BIT, supported); + if (val & MDIO_PMA_EXTABLE_10GBLRM) + __set_bit(ETHTOOL_LINK_MODE_FIBRE_BIT, supported); + if (val & (MDIO_PMA_EXTABLE_10GBKX4 | MDIO_PMA_EXTABLE_10GBKR | + MDIO_PMA_EXTABLE_1000BKX)) + __set_bit(ETHTOOL_LINK_MODE_Backplane_BIT, supported); + if (val & MDIO_PMA_EXTABLE_10GBLRM) + __set_bit(ETHTOOL_LINK_MODE_10000baseLRM_Full_BIT, supported); + if (val & MDIO_PMA_EXTABLE_10GBT) + __set_bit(ETHTOOL_LINK_MODE_10000baseT_Full_BIT, supported); + if (val & MDIO_PMA_EXTABLE_10GBKX4) + __set_bit(ETHTOOL_LINK_MODE_10000baseKX4_Full_BIT, supported); + if (val & MDIO_PMA_EXTABLE_10GBKR) + __set_bit(ETHTOOL_LINK_MODE_10000baseKR_Full_BIT, supported); + if (val & MDIO_PMA_EXTABLE_1000BT) + __set_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT, supported); + if (val & MDIO_PMA_EXTABLE_1000BKX) + __set_bit(ETHTOOL_LINK_MODE_1000baseKX_Full_BIT, supported); + if (val & MDIO_PMA_EXTABLE_100BTX) + __set_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT, supported); + if (val & MDIO_PMA_EXTABLE_10BT) + __set_bit(ETHTOOL_LINK_MODE_10baseT_Full_BIT, supported); + } + + if (!ethtool_convert_link_mode_to_legacy_u32(&mask, supported)) + dev_warn(&phydev->mdio.dev, + "PHY supports (%*pb) more modes than phylib supports, some modes not supported.\n", + __ETHTOOL_LINK_MODE_MASK_NBITS, supported); + + phydev->supported &= mask; + phydev->advertising &= phydev->supported; + + return 0; +} + +static int mv3310_config_aneg(struct phy_device *phydev) +{ + bool changed = false; + u32 advertising; + int ret; + + if (phydev->autoneg == AUTONEG_DISABLE) { + ret = genphy_c45_pma_setup_forced(phydev); + if (ret < 0) + return ret; + + return genphy_c45_an_disable_aneg(phydev); + } + + phydev->advertising &= phydev->supported; + advertising = phydev->advertising; + + ret = mv3310_modify(phydev, MDIO_MMD_AN, MDIO_AN_ADVERTISE, + ADVERTISE_ALL | ADVERTISE_100BASE4 | + ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM, + ethtool_adv_to_mii_adv_t(advertising)); + if (ret < 0) + return ret; + if (ret > 0) + changed = true; + + ret = mv3310_modify(phydev, MDIO_MMD_AN, MV_AN_CTRL1000, + ADVERTISE_1000FULL | ADVERTISE_1000HALF, + ethtool_adv_to_mii_ctrl1000_t(advertising)); + if (ret < 0) + return ret; + if (ret > 0) + changed = true; + + /* 10G control register */ + ret = mv3310_modify(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_CTRL, + MDIO_AN_10GBT_CTRL_ADV10G, + advertising & ADVERTISED_10000baseT_Full ? + MDIO_AN_10GBT_CTRL_ADV10G : 0); + if (ret < 0) + return ret; + if (ret > 0) + changed = true; + + if (changed) + ret = genphy_c45_restart_aneg(phydev); + + return ret; +} + +static int mv3310_aneg_done(struct phy_device *phydev) +{ + int val; + + val = phy_read_mmd(phydev, MDIO_MMD_PCS, MV_PCS_BASE_R + MDIO_STAT1); + if (val < 0) + return val; + + if (val & MDIO_STAT1_LSTATUS) + return 1; + + return genphy_c45_aneg_done(phydev); +} + +/* 10GBASE-ER,LR,LRM,SR do not support autonegotiation. */ +static int mv3310_read_10gbr_status(struct phy_device *phydev) +{ + phydev->link = 1; + phydev->speed = SPEED_10000; + phydev->duplex = DUPLEX_FULL; + + if (phydev->interface == PHY_INTERFACE_MODE_SGMII) + phydev->interface = PHY_INTERFACE_MODE_10GKR; + + return 0; +} + +static int mv3310_read_status(struct phy_device *phydev) +{ + u32 mmd_mask = phydev->c45_ids.devices_in_package; + int val; + + /* The vendor devads do not report link status. Avoid the PHYXS + * instance as there are three, and its status depends on the MAC + * being appropriately configured for the negotiated speed. + */ + mmd_mask &= ~(BIT(MDIO_MMD_VEND1) | BIT(MDIO_MMD_VEND2) | + BIT(MDIO_MMD_PHYXS)); + + phydev->speed = SPEED_UNKNOWN; + phydev->duplex = DUPLEX_UNKNOWN; + phydev->lp_advertising = 0; + phydev->link = 0; + phydev->pause = 0; + phydev->asym_pause = 0; + + val = phy_read_mmd(phydev, MDIO_MMD_PCS, MV_PCS_BASE_R + MDIO_STAT1); + if (val < 0) + return val; + + if (val & MDIO_STAT1_LSTATUS) + return mv3310_read_10gbr_status(phydev); + + val = genphy_c45_read_link(phydev, mmd_mask); + if (val < 0) + return val; + + phydev->link = val > 0 ? 1 : 0; + + val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1); + if (val < 0) + return val; + + if (val & MDIO_AN_STAT1_COMPLETE) { + val = genphy_c45_read_lpa(phydev); + if (val < 0) + return val; + + /* Read the link partner's 1G advertisment */ + val = phy_read_mmd(phydev, MDIO_MMD_AN, MV_AN_STAT1000); + if (val < 0) + return val; + + phydev->lp_advertising |= mii_stat1000_to_ethtool_lpa_t(val); + + if (phydev->autoneg == AUTONEG_ENABLE) { + /* 0xa016 appears to report speed in the top four bits */ + val = phy_read_mmd(phydev, MDIO_MMD_AN, MV_AN_RESULT); + if (val < 0) + return val; + + if (val & MV_AN_RESULT_SPD_10000) + phydev->speed = SPEED_10000; + else if (val & MV_AN_RESULT_SPD_1000) + phydev->speed = SPEED_1000; + else if (val & MV_AN_RESULT_SPD_100) + phydev->speed = SPEED_100; + else if (val & MV_AN_RESULT_SPD_10) + phydev->speed = SPEED_10; + + phydev->duplex = DUPLEX_FULL; + } + } + + if (phydev->autoneg != AUTONEG_ENABLE) { + val = genphy_c45_read_pma(phydev); + if (val < 0) + return val; + } + + if ((phydev->interface == PHY_INTERFACE_MODE_SGMII || + phydev->interface == PHY_INTERFACE_MODE_10GKR) && phydev->link) { + /* The PHY automatically switches its serdes interface (and + * active PHYXS instance) between Cisco SGMII and 10GBase-KR + * modes according to the speed. Florian suggests setting + * phydev->interface to communicate this to the MAC. Only do + * this if we are already in either SGMII or 10GBase-KR mode. + */ + if (phydev->speed == SPEED_10000) + phydev->interface = PHY_INTERFACE_MODE_10GKR; + else if (phydev->speed >= SPEED_10 && + phydev->speed < SPEED_10000) + phydev->interface = PHY_INTERFACE_MODE_SGMII; + } + + return 0; +} + +static struct phy_driver mv3310_drivers[] = { + { + .phy_id = 0x002b09aa, + .phy_id_mask = 0xffffffff, + .name = "mv88x3310", + .features = SUPPORTED_10baseT_Full | + SUPPORTED_100baseT_Full | + SUPPORTED_1000baseT_Full | + SUPPORTED_Autoneg | + SUPPORTED_TP | + SUPPORTED_FIBRE | + SUPPORTED_10000baseT_Full | + SUPPORTED_Backplane, + .probe = mv3310_probe, + .soft_reset = mv3310_soft_reset, + .config_init = mv3310_config_init, + .config_aneg = mv3310_config_aneg, + .aneg_done = mv3310_aneg_done, + .read_status = mv3310_read_status, + }, +}; + +module_phy_driver(mv3310_drivers); + +static struct mdio_device_id __maybe_unused mv3310_tbl[] = { + { 0x002b09aa, 0xffffffff }, + { }, +}; +MODULE_DEVICE_TABLE(mdio, mv3310_tbl); +MODULE_DESCRIPTION("Marvell Alaska X 10Gigabit Ethernet PHY driver (MV88X3310)"); +MODULE_LICENSE("GPL"); -- cgit From 03ee86eb411bd7e702ef5ebcc1fd604f88f2e925 Mon Sep 17 00:00:00 2001 From: Russell King Date: Thu, 5 Jan 2017 16:32:14 +0000 Subject: net: phy: allow settings table to support more than 32 link modes Allow the phy settings table to support more than 32 link modes by switching to the ethtool link mode bit number representation, rather than storing the mask. This will allow phylink and other ethtool code to share the settings table to look up settings. Signed-off-by: Russell King --- drivers/net/phy/phy.c | 44 ++++++++++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c index 04cb14eb155a..1648c4a9c097 100644 --- a/drivers/net/phy/phy.c +++ b/drivers/net/phy/phy.c @@ -178,7 +178,7 @@ EXPORT_SYMBOL(phy_aneg_done); struct phy_setting { int speed; int duplex; - u32 setting; + int bit; }; /* A mapping of all SUPPORTED settings to speed/duplex. This table @@ -188,57 +188,57 @@ 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, }, }; @@ -246,7 +246,8 @@ static const struct phy_setting settings[] = { * phy_lookup_setting - lookup a PHY setting * @speed: speed to match * @duplex: duplex to match - * @feature: allowed link modes + * @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 @@ -260,13 +261,14 @@ static const struct phy_setting settings[] = { * they all fail, %NULL will be returned. */ static const struct phy_setting * -phy_lookup_setting(int speed, int duplex, u32 features, bool exact) +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->setting & features) { + if (p->bit < maxbit && test_bit(p->bit, mask)) { last = p; if (p->speed == speed && p->duplex == duplex) { /* Exact match for speed and duplex */ @@ -305,7 +307,9 @@ phy_lookup_setting(int speed, int duplex, u32 features, bool exact) static const struct phy_setting * phy_find_valid(int speed, int duplex, u32 supported) { - return phy_lookup_setting(speed, duplex, supported, false); + unsigned long mask = supported; + + return phy_lookup_setting(speed, duplex, &mask, BITS_PER_LONG, false); } /** @@ -322,12 +326,14 @@ unsigned int phy_supported_speeds(struct phy_device *phy, unsigned int *speeds, unsigned int size) { + unsigned long supported = phy->supported; unsigned int count = 0; unsigned int idx = 0; for (idx = 0; idx < ARRAY_SIZE(settings) && count < size; idx++) /* Assumes settings are grouped by speed */ - if ((settings[idx].setting & phy->supported) && + if (setting[idx].bit < BITS_PER_LONG && + !test_bit(setting[idx].bit, &supported) && (count == 0 || speeds[count - 1] != settings[idx].speed)) speeds[count++] = settings[idx].speed; @@ -345,7 +351,9 @@ unsigned int phy_supported_speeds(struct phy_device *phy, */ static inline bool phy_check_valid(int speed, int duplex, u32 features) { - return !!phy_lookup_setting(speed, duplex, features, true); + unsigned long mask = features; + + return !!phy_lookup_setting(speed, duplex, &mask, BITS_PER_LONG, true); } /** -- cgit From e6907a3850b5b4c464e92748481c3c4a8668ff98 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 1648c4a9c097..0063b0ca4da6 100644 --- a/drivers/net/phy/phy.c +++ b/drivers/net/phy/phy.c @@ -39,26 +39,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); \ @@ -94,7 +74,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 a77e0be9ba37..b610490381b3 100644 --- a/include/linux/phy.h +++ b/include/linux/phy.h @@ -654,6 +654,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 d626c94f4971f585b92d6f194a360292bda927e5 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() and guts of phy_supported_speeds() 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. As we move the phy settings table, we also need to move the guts of phy_supported_speeds() as well. Signed-off-by: Russell King --- drivers/net/phy/phy-core.c | 126 +++++++++++++++++++++++++++++++++++++++++++ drivers/net/phy/phy.c | 130 +-------------------------------------------- include/linux/phy.h | 15 ++++++ 3 files changed, 142 insertions(+), 129 deletions(-) diff --git a/drivers/net/phy/phy-core.c b/drivers/net/phy/phy-core.c index ffa6c455f3c2..44b2ffbfb2c6 100644 --- a/drivers/net/phy/phy-core.c +++ b/drivers/net/phy/phy-core.c @@ -42,6 +42,132 @@ 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 grouped by speed and 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); + +size_t phy_speeds(unsigned int *speeds, size_t size, + unsigned long *mask, size_t maxbit) +{ + size_t count; + int i; + + for (i = 0, count = 0; i < ARRAY_SIZE(settings) && count < size; i++) + if (settings[i].bit < maxbit && + test_bit(settings[i].bit, mask) && + (count == 0 || speeds[count - 1] != settings[i].speed)) + speeds[count++] = settings[i].speed; + + return count; +} + 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 0063b0ca4da6..d3395a675896 100644 --- a/drivers/net/phy/phy.c +++ b/drivers/net/phy/phy.c @@ -152,125 +152,6 @@ int phy_aneg_done(struct phy_device *phydev) } EXPORT_SYMBOL(phy_aneg_done); -/* 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 grouped by speed and 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 @@ -307,17 +188,8 @@ unsigned int phy_supported_speeds(struct phy_device *phy, unsigned int size) { unsigned long supported = phy->supported; - unsigned int count = 0; - unsigned int idx = 0; - - for (idx = 0; idx < ARRAY_SIZE(settings) && count < size; idx++) - /* Assumes settings are grouped by speed */ - if (setting[idx].bit < BITS_PER_LONG && - !test_bit(setting[idx].bit, &supported) && - (count == 0 || speeds[count - 1] != settings[idx].speed)) - speeds[count++] = settings[idx].speed; - return count; + return phy_speeds(speeds, size, &supported, BITS_PER_LONG); } /** diff --git a/include/linux/phy.h b/include/linux/phy.h index b610490381b3..b63095608f34 100644 --- a/include/linux/phy.h +++ b/include/linux/phy.h @@ -657,6 +657,21 @@ 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); +size_t phy_speeds(unsigned int *speeds, size_t size, + unsigned long *mask, size_t maxbit); + /** * phy_read_mmd - Convenience function for reading a register * from an MMD on a given PHY. -- cgit From 85fee55810c6752c37a8a12b948a951ec8e4ff89 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 44b2ffbfb2c6..8de262eb2d7c 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 4f7ddbb0ec3b2ca375c875e99e121b60e69ded72 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, 35 insertions(+), 22 deletions(-) diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c index d3395a675896..f089dc93c6c7 100644 --- a/drivers/net/phy/phy.c +++ b/drivers/net/phy/phy.c @@ -882,9 +882,15 @@ void phy_start(struct phy_device *phydev) } EXPORT_SYMBOL(phy_start); -static void phy_adjust_link(struct phy_device *phydev) +static void phy_link_up(struct phy_device *phydev) { - phydev->adjust_link(phydev->attached_dev); + phydev->phy_link_change(phydev, true, true); + phy_led_trigger_change_speed(phydev); +} + +static void phy_link_down(struct phy_device *phydev, bool do_carrier) +{ + phydev->phy_link_change(phydev, false, do_carrier); phy_led_trigger_change_speed(phydev); } @@ -929,8 +935,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); - phy_adjust_link(phydev); + phy_link_down(phydev, true); break; } @@ -942,9 +947,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); - phy_adjust_link(phydev); - + phy_link_up(phydev); } else if (0 == phydev->link_timeout--) needs_aneg = true; break; @@ -969,8 +972,7 @@ void phy_state_machine(struct work_struct *work) } } phydev->state = PHY_RUNNING; - netif_carrier_on(phydev->attached_dev); - phy_adjust_link(phydev); + phy_link_up(phydev); } break; case PHY_FORCING: @@ -980,13 +982,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); } - - phy_adjust_link(phydev); break; case PHY_RUNNING: /* Only register a CHANGE if we are polling and link changed @@ -1018,14 +1019,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); } - phy_adjust_link(phydev); - if (phy_interrupt_is_valid(phydev)) err = phy_config_interrupt(phydev, PHY_INTERRUPT_ENABLED); @@ -1033,8 +1032,7 @@ void phy_state_machine(struct work_struct *work) case PHY_HALTED: if (phydev->link) { phydev->link = 0; - netif_carrier_off(phydev->attached_dev); - phy_adjust_link(phydev); + phy_link_down(phydev, true); do_suspend = true; } break; @@ -1054,11 +1052,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); } - phy_adjust_link(phydev); } else { phydev->state = PHY_AN; phydev->link_timeout = PHY_AN_TIMEOUT; @@ -1070,11 +1068,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); } - phy_adjust_link(phydev); } break; } diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c index cedd1fd91626..fb342f4a0cf0 100644 --- a/drivers/net/phy/phy_device.c +++ b/drivers/net/phy/phy_device.c @@ -688,6 +688,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 @@ -951,6 +964,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 b63095608f34..b5ab53d4f9a4 100644 --- a/include/linux/phy.h +++ b/include/linux/phy.h @@ -462,6 +462,7 @@ struct phy_device { u8 mdix; u8 mdix_ctrl; + 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 4a67ec5a043a086cd46c6b27bf50be0b2c899856 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 f089dc93c6c7..1defa0771e81 100644 --- a/drivers/net/phy/phy.c +++ b/drivers/net/phy/phy.c @@ -576,6 +576,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 771201c0f6bc81107fac87700312f1a3eda8ee29 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 8dbd59baa34d..c59f4acae479 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -118,6 +118,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 b56d9406c559..b6560a09126a 100644 --- a/drivers/net/phy/Makefile +++ b/drivers/net/phy/Makefile @@ -17,6 +17,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 c5aaabb9e8722b8cbee2d0a4a049e1bcdc752255 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 | 910 +++++++++++++++++++++++++++++++++++++++++++ include/linux/phy.h | 2 + include/linux/phylink.h | 139 +++++++ 6 files changed, 1063 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 c59f4acae479..16a6e375d742 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 b6560a09126a..8d64d2cc8eb5 100644 --- a/drivers/net/phy/Makefile +++ b/drivers/net/phy/Makefile @@ -5,6 +5,7 @@ libphy-y := phy.o phy_device.o mdio_bus.o mdio_device.o \ libphy-$(CONFIG_SWPHY) += swphy.o libphy-$(CONFIG_LED_TRIGGER_PHY) += phy_led_triggers.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 fb342f4a0cf0..0fbeb82d56d9 100644 --- a/drivers/net/phy/phy_device.c +++ b/drivers/net/phy/phy_device.c @@ -1059,6 +1059,7 @@ void phy_detach(struct phy_device *phydev) phydev->attached_dev->phydev = NULL; phydev->attached_dev = NULL; phy_suspend(phydev); + phydev->phylink = NULL; phy_led_triggers_unregister(phydev); diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c new file mode 100644 index 000000000000..204d887b64c2 --- /dev/null +++ b/drivers/net/phy/phylink.c @@ -0,0 +1,910 @@ +/* + * 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); +} + +void phylink_set_port_modes(unsigned long *mask) +{ + phylink_set(mask, TP); + phylink_set(mask, AUI); + phylink_set(mask, MII); + phylink_set(mask, FIBRE); + phylink_set(mask, BNC); + phylink_set(mask, Backplane); +} +EXPORT_SYMBOL_GPL(phylink_set_port_modes); + +static int phylink_is_empty_linkmode(const unsigned long *linkmode) +{ + __ETHTOOL_DECLARE_LINK_MODE_MASK(tmp) = { 0, }; + + phylink_set_port_modes(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 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_validate_support(struct phylink *pl, int mode, + unsigned long *supported) +{ + pl->ops->validate_support(pl->netdev, mode, supported); + + return phylink_is_empty_linkmode(supported) ? -EINVAL : 0; +} + +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", 0, + GPIOD_IN, "?"); + + 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; + } + + linkmode_zero(pl->supported); + phylink_set(pl->supported, MII); + phylink_set(pl->supported, Autoneg); + phylink_set(pl->supported, Asym_Pause); + phylink_set(pl->supported, Pause); + pl->link_config.an_enabled = true; + + 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); + pl->link_an_mode = MLO_AN_SGMII; + 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->link_an_mode == MLO_AN_8023Z) + pl->ops->mac_an_restart(pl->netdev); +} + +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_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_modes(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_is_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 b5ab53d4f9a4..8ebb39c77832 100644 --- a/include/linux/phy.h +++ b/include/linux/phy.h @@ -179,6 +179,7 @@ static inline const char *phy_modes(phy_interface_t interface) #define MII_ADDR_C45 (1<<30) struct device; +struct phylink; struct sk_buff; /* @@ -457,6 +458,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..e33a7d1d7625 --- /dev/null +++ b/include/linux/phylink.h @@ -0,0 +1,139 @@ +#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, /* Conventional PHY */ + MLO_AN_FIXED, /* Fixed-link mode */ + MLO_AN_SGMII, /* Cisco SGMII protocol */ + MLO_AN_8023Z, /* 1000base-X protocol */ +}; + +static inline bool phylink_autoneg_inband(unsigned int mode) +{ + return mode == MLO_AN_SGMII || mode == 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 + * @ndev: net_device structure associated with MAC + * @mode: desired negotiation operating mode + * @support: pointer to the desired support mask + * + * 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 *ndev, unsigned int mode, + unsigned long *support); + + /** + * validate_advert: validate and update the advertisment + * @ndev: net_device structure associated with the MAC + * @mode: desired negotiation operating mode + * @support: current support mask + * @advert: desired advertisment mask + * + * 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 *ndev, 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 */ + /** + * mac_config: configure the MAC for the selected mode and state + * @ndev: net_device structure for the MAC + * @mode: one of MLO_AN_FIXED, MLO_AN_PHY, MLO_AN_8023Z, MLO_AN_SGMII + * @state: state structure + * + * The action performed depends on the currently selected mode: + * + * %MLO_AN_FIXED, %MLO_AN_PHY: + * set the specified speed, duplex, pause mode, and phy interface + * mode in the provided @state. + * %MLO_AN_8023Z: + * place the link in 1000base-X mode, advertising the parameters + * given in advertising in @state. + * %MLO_AN_SGMII: + * place the link in Cisco SGMII mode - there is no advertisment + * to make as the PHY communicates the speed and duplex to the + * MAC over the in-band control word. Configuration of the pause + * mode is as per MLO_AN_PHY since this is not included. + */ + void (*mac_config)(struct net_device *ndev, unsigned int mode, + const struct phylink_link_state *state); + + /** + * mac_an_restart: restart 802.3z BaseX autonegotiation + * @ndev: net_device structure for the MAC + */ + void (*mac_an_restart)(struct net_device *ndev); + + 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) + +void phylink_set_port_modes(unsigned long *bits); + +#endif -- cgit From aad12dc40c1b56d5874be52696cbf6d78133213b 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 204d887b64c2..765cd971a18b 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; @@ -404,12 +410,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); } @@ -907,4 +921,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 e33a7d1d7625..62786d27a5fc 100644 --- a/include/linux/phylink.h +++ b/include/linux/phylink.h @@ -125,6 +125,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 4e26a01585b224ab7f3f2f6a23eecd563bb28f77 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 765cd971a18b..e474209a03f7 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -817,16 +817,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) { @@ -859,12 +936,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; @@ -883,36 +954,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 c32cd1c82725b2acf73669d88a5e6045d0eb267b 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 e474209a03f7..fae39e919459 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -790,6 +790,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 = phy_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 62786d27a5fc..1fa3ad72f01a 100644 --- a/include/linux/phylink.h +++ b/include/linux/phylink.h @@ -123,6 +123,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 66d63b73494db6b0f948d8925b8bbd200e085432 Mon Sep 17 00:00:00 2001 From: Russell King Date: Fri, 10 Mar 2017 11:53:11 +0000 Subject: phylink: add wake-on-lan support Add wake-on-lan support to phylink. Signed-off-by: Russell King --- drivers/net/phy/phylink.c | 25 +++++++++++++++++++++++++ include/linux/phylink.h | 3 +++ 2 files changed, 28 insertions(+) diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index fae39e919459..3ae907f16a93 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -613,6 +613,31 @@ void phylink_stop(struct phylink *pl) } EXPORT_SYMBOL_GPL(phylink_stop); +void phylink_ethtool_get_wol(struct phylink *pl, struct ethtool_wolinfo *wol) +{ + wol->supported = 0; + wol->wolopts = 0; + + mutex_lock(&pl->config_mutex); + if (pl->phydev) + phy_ethtool_get_wol(pl->phydev, wol); + mutex_unlock(&pl->config_mutex); +} +EXPORT_SYMBOL_GPL(phylink_ethtool_get_wol); + +int phylink_ethtool_set_wol(struct phylink *pl, struct ethtool_wolinfo *wol) +{ + int ret = -EOPNOTSUPP; + + mutex_lock(&pl->config_mutex); + if (pl->phydev) + ret = phy_ethtool_set_wol(pl->phydev, wol); + mutex_unlock(&pl->config_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(phylink_ethtool_set_wol); + static void phylink_merge_link_mode(unsigned long *dst, const unsigned long *b) { __ETHTOOL_DECLARE_LINK_MODE_MASK(mask); diff --git a/include/linux/phylink.h b/include/linux/phylink.h index 1fa3ad72f01a..f9f7725b91c2 100644 --- a/include/linux/phylink.h +++ b/include/linux/phylink.h @@ -119,6 +119,9 @@ void phylink_mac_change(struct phylink *, bool up); void phylink_start(struct phylink *); void phylink_stop(struct phylink *); +void phylink_ethtool_get_wol(struct phylink *, struct ethtool_wolinfo *); +int phylink_ethtool_set_wol(struct phylink *, struct ethtool_wolinfo *); + int phylink_ethtool_ksettings_get(struct phylink *, struct ethtool_link_ksettings *); int phylink_ethtool_ksettings_set(struct phylink *, -- cgit From 6b10f3465acd6fa6fecede58e8ae23794c0e981a 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 16a6e375d742..45bab7382c76 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -182,6 +182,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 8d64d2cc8eb5..c178f25c8fc0 100644 --- a/drivers/net/phy/Makefile +++ b/drivers/net/phy/Makefile @@ -25,6 +25,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 6898cefcecac8b6c4cc3f602085fc5b020e8a4e2 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 570f782e52ea25c54f2650640c1a0ea435c12844 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 | 467 +++++++++++++++++----------------- 2 files changed, 229 insertions(+), 240 deletions(-) diff --git a/drivers/net/ethernet/marvell/Kconfig b/drivers/net/ethernet/marvell/Kconfig index d2555e8b947e..dc0c994b5422 100644 --- a/drivers/net/ethernet/marvell/Kconfig +++ b/drivers/net/ethernet/marvell/Kconfig @@ -60,7 +60,7 @@ config MVNETA depends on ARCH_MVEBU || COMPILE_TEST depends on HAS_DMA select MVMDIO - select FIXED_PHY + select PHYLINK ---help--- This driver supports the network interface units in the Marvell ARMADA XP, ARMADA 370, ARMADA 38x and diff --git a/drivers/net/ethernet/marvell/mvneta.c b/drivers/net/ethernet/marvell/mvneta.c index 61dd4462411c..eee4ceb768ff 100644 --- a/drivers/net/ethernet/marvell/mvneta.c +++ b/drivers/net/ethernet/marvell/mvneta.c @@ -28,7 +28,7 @@ #include #include #include -#include +#include #include #include #include @@ -189,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) @@ -204,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) @@ -410,14 +417,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; @@ -1272,44 +1274,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; @@ -1462,7 +1426,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); @@ -2689,26 +2652,11 @@ static irqreturn_t mvneta_percpu_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 @@ -2724,7 +2672,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)) { @@ -2738,12 +2685,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 */ @@ -3056,7 +3002,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); @@ -3084,16 +3029,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); if (!pp->neta_armada3700) { for_each_online_cpu(cpu) { @@ -3247,99 +3191,196 @@ 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_port_modes(mask); + + /* 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) +{ + struct mvneta_port *pp = netdev_priv(ndev); + u32 gmac_an = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG); - pp->duplex = phydev->duplex; - pp->speed = phydev->speed; - } - } + 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; + + if (!phylink_autoneg_inband(mode)) { + /* 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; + } else if (mode == 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; + } else { + /* 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; + } - pp->link = phydev->link; - status_change = 1; + /* 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 (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); + 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 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 (!phylink_autoneg_inband(mode)) { + 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 int mvneta_mdio_probe(struct mvneta_port *pp) +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 (!phylink_autoneg_inband(mode)) { + 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 @@ -3618,10 +3659,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 */ @@ -3632,44 +3672,25 @@ 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; + return phylink_ethtool_ksettings_set(pp->phylink, cmd); +} - mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val); - } +/* 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); - 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_get(pp->phylink, cmd); +} - if (netif_running(ndev)) { - mvneta_port_down(pp); - mvneta_port_up(pp); - } - } +static int mvneta_ethtool_nway_reset(struct net_device *dev) +{ + struct mvneta_port *pp = netdev_priv(dev); - return phy_ethtool_ksettings_set(ndev->phydev, cmd); + return phylink_ethtool_nway_reset(pp->phylink); } /* Set interrupt coalescing for ethtools */ @@ -3777,26 +3798,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; } } @@ -3931,20 +3954,17 @@ static int mvneta_ethtool_get_rxfh(struct net_device *dev, u32 *indir, u8 *key, static void mvneta_ethtool_get_wol(struct net_device *dev, struct ethtool_wolinfo *wol) { - wol->supported = 0; - wol->wolopts = 0; + struct mvneta_port *pp = netdev_priv(dev); - if (dev->phydev) - phy_ethtool_get_wol(dev->phydev, wol); + phylink_ethtool_get_wol(pp->phylink, wol); } static int mvneta_ethtool_set_wol(struct net_device *dev, struct ethtool_wolinfo *wol) { - if (!dev->phydev) - return -EOPNOTSUPP; + struct mvneta_port *pp = netdev_priv(dev); - return phy_ethtool_set_wol(dev->phydev, wol); + return phylink_ethtool_set_wol(pp->phylink, wol); } static const struct net_device_ops mvneta_netdev_ops = { @@ -3960,7 +3980,7 @@ static const struct net_device_ops mvneta_netdev_ops = { }; static const struct ethtool_ops mvneta_eth_tool_ops = { - .nway_reset = phy_ethtool_nway_reset, + .nway_reset = mvneta_ethtool_nway_reset, .get_link = ethtool_op_get_link, .set_coalesce = mvneta_ethtool_set_coalesce, .get_coalesce = mvneta_ethtool_get_coalesce, @@ -3974,7 +3994,7 @@ static 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, .get_wol = mvneta_ethtool_get_wol, .set_wol = mvneta_ethtool_set_wol, @@ -4121,14 +4141,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; @@ -4144,31 +4163,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; @@ -4179,12 +4178,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; @@ -4206,7 +4200,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); @@ -4332,6 +4326,14 @@ static int mvneta_probe(struct platform_device *pdev) /* 9676 == 9700 - 20 and rounding to 8 */ dev->max_mtu = 9676; + 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"); @@ -4343,14 +4345,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: @@ -4361,16 +4355,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: @@ -4382,7 +4374,6 @@ err_free_netdev: static int mvneta_remove(struct platform_device *pdev) { struct net_device *dev = platform_get_drvdata(pdev); - struct device_node *dn = pdev->dev.of_node; struct mvneta_port *pp = netdev_priv(dev); unregister_netdev(dev); @@ -4390,10 +4381,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 69646d7310204beafdb0e99bb12c673cc8e3ab74 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 eee4ceb768ff..fb9e65c6dcf1 100644 --- a/drivers/net/ethernet/marvell/mvneta.c +++ b/drivers/net/ethernet/marvell/mvneta.c @@ -2687,9 +2687,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 */ @@ -3026,8 +3028,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); @@ -3488,8 +3489,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; @@ -3530,8 +3530,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 4f2b018f7c6dc069cdecef02b7c1dda0acf987ef 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 34ec4d4141b845027bcb7f8de6e76083deaeb534 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 3ae907f16a93..a749358ab2ca 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -155,6 +155,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")) @@ -306,6 +309,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); @@ -319,6 +372,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: @@ -327,9 +381,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: @@ -359,7 +416,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); @@ -387,6 +444,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; @@ -587,6 +645,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); @@ -829,6 +888,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 f9f7725b91c2..b890b01f15c6 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, /* Conventional PHY */ MLO_AN_FIXED, /* Fixed-link mode */ @@ -127,6 +131,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 92e38b2e86bf31d14829c5a7722635ab42b9bb4d 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 fb9e65c6dcf1..d404cd0a4995 100644 --- a/drivers/net/ethernet/marvell/mvneta.c +++ b/drivers/net/ethernet/marvell/mvneta.c @@ -3237,6 +3237,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; } @@ -3276,6 +3282,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; if (!phylink_autoneg_inband(mode)) { /* Phy or fixed speed */ @@ -3306,7 +3314,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; } @@ -3781,6 +3789,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) { @@ -3986,6 +4010,8 @@ static 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 0f918a4508a7bf2ca78bdfc3c42820a96d406063 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 d404cd0a4995..58e055d0bd21 100644 --- a/drivers/net/ethernet/marvell/mvneta.c +++ b/drivers/net/ethernet/marvell/mvneta.c @@ -3211,10 +3211,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 d68f5936faec2b59295407705f43387bcca227cd 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 58e055d0bd21..d947b78fc68c 100644 --- a/drivers/net/ethernet/marvell/mvneta.c +++ b/drivers/net/ethernet/marvell/mvneta.c @@ -3201,6 +3201,8 @@ static void mvneta_validate_support(struct net_device *ndev, unsigned int mode, phylink_set(mask, Autoneg); phylink_set_port_modes(mask); + /* 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); @@ -3213,9 +3215,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 b7e91c4165181af3816487d46727b822c6a588b7 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 | 55 ++++++++++++++++++++++++++++++++++- include/linux/phylink.h | 7 ++++- 3 files changed, 62 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/marvell/mvneta.c b/drivers/net/ethernet/marvell/mvneta.c index d947b78fc68c..db74030cf711 100644 --- a/drivers/net/ethernet/marvell/mvneta.c +++ b/drivers/net/ethernet/marvell/mvneta.c @@ -3354,7 +3354,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 a749358ab2ca..67fe6c0effce 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -408,7 +408,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); @@ -961,6 +962,58 @@ 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); + 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 b890b01f15c6..4872f498e335 100644 --- a/include/linux/phylink.h +++ b/include/linux/phylink.h @@ -107,7 +107,8 @@ struct phylink_mac_ops { void (*mac_an_restart)(struct net_device *ndev); 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 *, @@ -135,6 +136,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 6d1acf52c0b0d1cd29850bca1de27bfc03dd2229 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 db74030cf711..430d256e0cd7 100644 --- a/drivers/net/ethernet/marvell/mvneta.c +++ b/drivers/net/ethernet/marvell/mvneta.c @@ -244,6 +244,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 */ @@ -323,6 +329,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; @@ -331,6 +342,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", }, @@ -365,6 +377,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 { @@ -426,6 +439,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]; @@ -3339,6 +3356,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); @@ -3352,6 +3381,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, @@ -3368,6 +3400,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 = { @@ -3841,6 +3878,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; @@ -3991,6 +4035,47 @@ static int mvneta_ethtool_set_wol(struct net_device *dev, return phylink_ethtool_set_wol(pp->phylink, wol); } +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, @@ -4024,6 +4109,8 @@ static const struct ethtool_ops mvneta_eth_tool_ops = { .set_link_ksettings = mvneta_ethtool_set_link_ksettings, .get_wol = mvneta_ethtool_get_wol, .set_wol = mvneta_ethtool_set_wol, + .get_eee = mvneta_ethtool_get_eee, + .set_eee = mvneta_ethtool_set_eee, }; /* Initialize hw */ -- cgit From c7984331d3e7273117a1de52e125a0bab0199333 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 67fe6c0effce..8afc695fe7e5 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -962,6 +962,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; @@ -1238,6 +1268,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 4872f498e335..1a5968267834 100644 --- a/include/linux/phylink.h +++ b/include/linux/phylink.h @@ -111,6 +111,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 *); @@ -136,12 +141,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 f0ec1ebb97d848beed9fc2b0f1467325ff26950a 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 430d256e0cd7..77501468d6f9 100644 --- a/drivers/net/ethernet/marvell/mvneta.c +++ b/drivers/net/ethernet/marvell/mvneta.c @@ -4035,6 +4035,22 @@ static int mvneta_ethtool_set_wol(struct net_device *dev, return phylink_ethtool_set_wol(pp->phylink, wol); } +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) { @@ -4109,6 +4125,8 @@ static const struct ethtool_ops mvneta_eth_tool_ops = { .set_link_ksettings = mvneta_ethtool_set_link_ksettings, .get_wol = mvneta_ethtool_get_wol, .set_wol = mvneta_ethtool_set_wol, + .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 4ee98f08447400d7760916cc2943230b587b54a7 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 7e82fee1a62df80468a8233305698fe8edd33071 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 272b051a0199..c2ccee8799f4 100644 --- a/drivers/net/phy/marvell.c +++ b/drivers/net/phy/marvell.c @@ -2072,7 +2072,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 = &m88e1510_probe, .config_init = &m88e1510_config_init, -- cgit From 4039c32767ecef5d0d9433fc0229bc038e34aa8f 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 c2ccee8799f4..cb6ccdf0a91d 100644 --- a/drivers/net/phy/marvell.c +++ b/drivers/net/phy/marvell.c @@ -1924,7 +1924,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 d6dee956274e15d44d1c142fb5f07de5a663093f 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 cb6ccdf0a91d..e38f84291258 100644 --- a/drivers/net/phy/marvell.c +++ b/drivers/net/phy/marvell.c @@ -2094,7 +2094,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 = m88e1510_probe, .config_init = &marvell_config_init, -- cgit From 6e29f0087c0d7599ff945656dd878802f1f5136d 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 e38f84291258..f93a7a46feb7 100644 --- a/drivers/net/phy/marvell.c +++ b/drivers/net/phy/marvell.c @@ -1119,8 +1119,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 9f4bfb85c85851e88ccba1f2de928b316061fa73 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 8afc695fe7e5..fc0c6eca9101 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -271,8 +271,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, @@ -293,6 +294,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; @@ -373,19 +375,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; @@ -401,13 +422,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); @@ -443,8 +457,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; @@ -500,12 +516,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 1a5968267834..41bebd44e767 100644 --- a/include/linux/phylink.h +++ b/include/linux/phylink.h @@ -32,6 +32,7 @@ static inline bool phylink_autoneg_inband(unsigned int mode) 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 16d2efd8904d068a17f3edc2c5ba78f54e1ef132 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 fc0c6eca9101..bc1f2e63d541 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; }; @@ -369,6 +371,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) { @@ -434,6 +439,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); } @@ -648,6 +657,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 From 2be309597dd4adc42a25c6c23e600cc28711091b Mon Sep 17 00:00:00 2001 From: Russell King Date: Fri, 14 Apr 2017 20:17:04 +0100 Subject: sfp: fix eeprom reading Signed-off-by: Russell King --- drivers/net/phy/sfp.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c index 1f7001522a8c..1f5edc351b2d 100644 --- a/drivers/net/phy/sfp.c +++ b/drivers/net/phy/sfp.c @@ -1013,9 +1013,7 @@ static int sfp_module_eeprom(void *priv, struct ethtool_eeprom *ee, u8 *data) 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 = min_t(unsigned int, last, ETH_MODULE_SFF_8079_LEN); len -= first; ret = sfp->read(sfp, false, first, data, len); @@ -1025,8 +1023,11 @@ static int sfp_module_eeprom(void *priv, struct ethtool_eeprom *ee, u8 *data) first += len; data += len; } - if (first >= ETH_MODULE_SFF_8079_LEN && last > first) { - len = last - first; + if (first >= ETH_MODULE_SFF_8079_LEN && + first < ETH_MODULE_SFF_8472_LEN) { + len = min_t(unsigned int, last, ETH_MODULE_SFF_8472_LEN); + len -= first; + first -= ETH_MODULE_SFF_8079_LEN; ret = sfp->read(sfp, true, first, data, len); if (ret < 0) -- cgit From 2546b8933d66fdec4ece43606d46f395b6e550e3 Mon Sep 17 00:00:00 2001 From: Russell King Date: Fri, 14 Apr 2017 15:10:05 +0100 Subject: sfp: ensure module starts with tx disabled Signed-off-by: Russell King --- drivers/net/phy/sfp.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c index 1f5edc351b2d..294f59e140b0 100644 --- a/drivers/net/phy/sfp.c +++ b/drivers/net/phy/sfp.c @@ -1236,10 +1236,11 @@ static int sfp_probe(struct platform_device *pdev) phylink_register_module(sfp->phylink, sfp, &sfp_module_ops); } - 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; + /* Get the initial state, and always signal TX disable, + * since the network interface will not be up. + */ + sfp->state = sfp_get_state(sfp) | 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; -- cgit From b782030fcf3e0fd790f0972a439d4431a2fa0224 Mon Sep 17 00:00:00 2001 From: Russell King Date: Tue, 9 May 2017 16:43:35 +0100 Subject: sfp: hold the rtnetlink lock while calling phylink Hold the rtnetlink lock while causing phylink to prevent changes to the networking configuration. This will allow us to eliminate the configuration locking in phylink, and thus simplify the locking strategy. This will also allow phylink to attach the phy directly to the net device, so allowing a reduction in veneer code. Signed-off-by: Russell King --- drivers/net/phy/sfp.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c index 294f59e140b0..e8ab34879a96 100644 --- a/drivers/net/phy/sfp.c +++ b/drivers/net/phy/sfp.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -1045,7 +1046,9 @@ static void sfp_timeout(struct work_struct *work) { struct sfp *sfp = container_of(work, struct sfp, timeout.work); + rtnl_lock(); sfp_sm_event(sfp, SFP_E_TIMEOUT); + rtnl_unlock(); } static void sfp_check_state(struct sfp *sfp) @@ -1064,6 +1067,7 @@ static void sfp_check_state(struct sfp *sfp) state |= sfp->state & (SFP_F_TX_DISABLE | SFP_F_RATE_SELECT); sfp->state = state; + rtnl_lock(); if (changed & SFP_F_PRESENT) sfp_sm_event(sfp, state & SFP_F_PRESENT ? SFP_E_INSERT : SFP_E_REMOVE); @@ -1075,6 +1079,7 @@ static void sfp_check_state(struct sfp *sfp) if (changed & SFP_F_LOS) sfp_sm_event(sfp, state & SFP_F_LOS ? SFP_E_LOS_HIGH : SFP_E_LOS_LOW); + rtnl_unlock(); } static irqreturn_t sfp_irq(int irq, void *data) @@ -1232,8 +1237,10 @@ static int sfp_probe(struct platform_device *pdev) return -EPROBE_DEFER; } + rtnl_lock(); phylink_disable(sfp->phylink); phylink_register_module(sfp->phylink, sfp, &sfp_module_ops); + rtnl_unlock(); } /* Get the initial state, and always signal TX disable, @@ -1246,8 +1253,10 @@ static int sfp_probe(struct platform_device *pdev) sfp->state |= SFP_F_RATE_SELECT; sfp_set_state(sfp, sfp->state); sfp_module_tx_disable(sfp); + rtnl_lock(); if (sfp->state & SFP_F_PRESENT) sfp_sm_event(sfp, SFP_E_INSERT); + rtnl_unlock(); for (i = 0; i < GPIO_MAX; i++) { if (gpio_flags[i] != GPIOD_IN || !sfp->gpio[i]) -- cgit From 5be9a27de30e6ee5b108f9c4b02def0ecda9f9dc Mon Sep 17 00:00:00 2001 From: Russell King Date: Tue, 9 May 2017 16:54:48 +0100 Subject: phylink: switch to relying on rtnetlink lock Switch to relying on the rtnetlink lock rather than our private config mutex to ensure proper locking. Signed-off-by: Russell King --- drivers/net/phy/phylink.c | 144 +++++++++++++++++----------------------------- 1 file changed, 52 insertions(+), 92 deletions(-) diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index bc1f2e63d541..f180b947d090 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -42,7 +43,6 @@ struct phylink { struct list_head node; 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; @@ -56,7 +56,7 @@ struct phylink { struct phylink_link_state link_config; struct gpio_desc *link_gpio; - struct mutex state_mutex; /* may be taken within config_mutex */ + struct mutex state_mutex; struct phylink_link_state phy_state; struct work_struct resolve; @@ -463,7 +463,6 @@ struct phylink *phylink_create(struct net_device *ndev, struct device_node *np, return ERR_PTR(-ENOMEM); mutex_init(&pl->state_mutex); - mutex_init(&pl->config_mutex); INIT_WORK(&pl->resolve, phylink_resolve); pl->netdev = ndev; pl->phy_state.interface = iface; @@ -548,7 +547,6 @@ static int phylink_bringup_phy(struct phylink *pl, struct phy_device *phy) if (ret) return ret; - mutex_lock(&pl->config_mutex); phy->phylink = pl; phy->phy_link_change = phylink_phy_change; @@ -576,8 +574,6 @@ static int phylink_bringup_phy(struct phylink *pl, struct phy_device *phy) if (phy->irq > 0) phy_start_interrupts(phy); - mutex_unlock(&pl->config_mutex); - return 0; } @@ -640,7 +636,8 @@ void phylink_disconnect_phy(struct phylink *pl) { struct phy_device *phy; - mutex_lock(&pl->config_mutex); + WARN_ON(!lockdep_rtnl_is_held()); + phy = pl->phydev; mutex_lock(&pl->state_mutex); @@ -650,8 +647,6 @@ void phylink_disconnect_phy(struct phylink *pl) if (phy) phy_disconnect(phy); - - mutex_unlock(&pl->config_mutex); } EXPORT_SYMBOL_GPL(phylink_disconnect_phy); @@ -666,7 +661,7 @@ EXPORT_SYMBOL_GPL(phylink_mac_change); void phylink_start(struct phylink *pl) { - mutex_lock(&pl->config_mutex); + WARN_ON(!lockdep_rtnl_is_held()); netdev_info(pl->netdev, "configuring for %s link mode\n", phylink_an_mode_str(pl->link_an_mode)); @@ -683,34 +678,30 @@ void phylink_start(struct phylink *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); + WARN_ON(!lockdep_rtnl_is_held()); 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); void phylink_ethtool_get_wol(struct phylink *pl, struct ethtool_wolinfo *wol) { + WARN_ON(!lockdep_rtnl_is_held()); + wol->supported = 0; wol->wolopts = 0; - mutex_lock(&pl->config_mutex); if (pl->phydev) phy_ethtool_get_wol(pl->phydev, wol); - mutex_unlock(&pl->config_mutex); } EXPORT_SYMBOL_GPL(phylink_ethtool_get_wol); @@ -718,10 +709,10 @@ int phylink_ethtool_set_wol(struct phylink *pl, struct ethtool_wolinfo *wol) { int ret = -EOPNOTSUPP; - mutex_lock(&pl->config_mutex); + WARN_ON(!lockdep_rtnl_is_held()); + if (pl->phydev) ret = phy_ethtool_set_wol(pl->phydev, wol); - mutex_unlock(&pl->config_mutex); return ret; } @@ -749,12 +740,14 @@ static void phylink_get_ksettings(const struct phylink_link_state *state, AUTONEG_DISABLE; } -static int __phylink_ethtool_ksettings_get(struct phylink *pl, - struct ethtool_link_ksettings *kset) +int phylink_ethtool_ksettings_get(struct phylink *pl, + struct ethtool_link_ksettings *kset) { struct phylink_link_state link_state; int ret; + WARN_ON(!lockdep_rtnl_is_held()); + if (pl->phydev) { ret = phy_ethtool_ksettings_get(pl->phydev, kset); if (ret) @@ -795,26 +788,20 @@ static int __phylink_ethtool_ksettings_get(struct phylink *pl, 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) +int phylink_ethtool_ksettings_set(struct phylink *pl, + const struct ethtool_link_ksettings *kset) { struct ethtool_link_ksettings our_kset = *kset; int ret; + WARN_ON(!lockdep_rtnl_is_held()); + + if (kset->base.autoneg != AUTONEG_DISABLE && + kset->base.autoneg != AUTONEG_ENABLE) + return -EINVAL; + /* Mask out unsupported advertisments */ linkmode_and(our_kset.link_modes.advertising, kset->link_modes.advertising, pl->supported); @@ -886,33 +873,17 @@ static int __phylink_ethtool_ksettings_set(struct phylink *pl, 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); int phylink_ethtool_nway_reset(struct phylink *pl) { int ret = 0; - mutex_lock(&pl->config_mutex); + WARN_ON(!lockdep_rtnl_is_held()); + if (pl->phydev) ret = phy_restart_aneg(pl->phydev); phylink_mac_an_restart(pl); - mutex_unlock(&pl->config_mutex); return ret; } @@ -921,21 +892,21 @@ EXPORT_SYMBOL_GPL(phylink_ethtool_nway_reset); void phylink_ethtool_get_pauseparam(struct phylink *pl, struct ethtool_pauseparam *pause) { - mutex_lock(&pl->config_mutex); + WARN_ON(!lockdep_rtnl_is_held()); 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) +int phylink_ethtool_set_pauseparam(struct phylink *pl, + struct ethtool_pauseparam *pause) { struct phylink_link_state *config = &pl->link_config; + WARN_ON(!lockdep_rtnl_is_held()); + if (!phylink_test(pl->supported, Pause) && !phylink_test(pl->supported, Asym_Pause)) return -EOPNOTSUPP; @@ -977,18 +948,6 @@ static int __phylink_ethtool_set_pauseparam(struct phylink *pl, 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); int phylink_ethtool_get_module_info(struct phylink *pl, @@ -996,12 +955,11 @@ int phylink_ethtool_get_module_info(struct phylink *pl, { int ret = -EOPNOTSUPP; - mutex_lock(&pl->config_mutex); + WARN_ON(!lockdep_rtnl_is_held()); + 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); @@ -1011,11 +969,11 @@ int phylink_ethtool_get_module_eeprom(struct phylink *pl, { int ret = -EOPNOTSUPP; - mutex_lock(&pl->config_mutex); + WARN_ON(!lockdep_rtnl_is_held()); + if (pl->module_ops) ret = pl->module_ops->get_module_eeprom(pl->module_data, ee, buf); - mutex_unlock(&pl->config_mutex); return ret; } @@ -1025,10 +983,10 @@ int phylink_init_eee(struct phylink *pl, bool clk_stop_enable) { int ret = -EPROTONOSUPPORT; - mutex_lock(&pl->config_mutex); + WARN_ON(!lockdep_rtnl_is_held()); + if (pl->phydev) ret = phy_init_eee(pl->phydev, clk_stop_enable); - mutex_unlock(&pl->config_mutex); return ret; } @@ -1038,10 +996,10 @@ int phylink_get_eee_err(struct phylink *pl) { int ret = 0; - mutex_lock(&pl->config_mutex); + WARN_ON(!lockdep_rtnl_is_held()); + if (pl->phydev) ret = phy_get_eee_err(pl->phydev); - mutex_unlock(&pl->config_mutex); return ret; } @@ -1051,10 +1009,10 @@ int phylink_ethtool_get_eee(struct phylink *pl, struct ethtool_eee *eee) { int ret = -EOPNOTSUPP; - mutex_lock(&pl->config_mutex); + WARN_ON(!lockdep_rtnl_is_held()); + if (pl->phydev) ret = phy_ethtool_get_eee(pl->phydev, eee); - mutex_unlock(&pl->config_mutex); return ret; } @@ -1064,10 +1022,10 @@ int phylink_ethtool_set_eee(struct phylink *pl, struct ethtool_eee *eee) { int ret = -EOPNOTSUPP; - mutex_lock(&pl->config_mutex); + WARN_ON(!lockdep_rtnl_is_held()); + if (pl->phydev) ret = phy_ethtool_set_eee(pl->phydev, eee); - mutex_unlock(&pl->config_mutex); return ret; } @@ -1240,7 +1198,7 @@ int phylink_mii_ioctl(struct phylink *pl, struct ifreq *ifr, int cmd) struct mii_ioctl_data *mii = if_mii(ifr); int ret; - mutex_lock(&pl->config_mutex); + WARN_ON(!lockdep_rtnl_is_held()); if (pl->phydev) { /* PHYs only exist for MLO_AN_PHY and MLO_AN_SGMII */ @@ -1289,8 +1247,6 @@ int phylink_mii_ioctl(struct phylink *pl, struct ifreq *ifr, int cmd) } } - mutex_unlock(&pl->config_mutex); - return ret; } EXPORT_SYMBOL_GPL(phylink_mii_ioctl); @@ -1302,13 +1258,13 @@ int phylink_register_module(struct phylink *pl, void *data, { int ret = -EBUSY; - mutex_lock(&pl->config_mutex); + WARN_ON(!lockdep_rtnl_is_held()); + if (!pl->module_ops) { pl->module_ops = ops; pl->module_data = data; ret = 0; } - mutex_unlock(&pl->config_mutex); return ret; } @@ -1318,13 +1274,13 @@ int phylink_unregister_module(struct phylink *pl, void *data) { int ret = -EINVAL; - mutex_lock(&pl->config_mutex); + WARN_ON(!lockdep_rtnl_is_held()); + if (pl->module_data == data) { pl->module_ops = NULL; pl->module_data = NULL; ret = 0; } - mutex_unlock(&pl->config_mutex); return ret; } @@ -1332,6 +1288,8 @@ EXPORT_SYMBOL_GPL(phylink_unregister_module); void phylink_disable(struct phylink *pl) { + WARN_ON(!lockdep_rtnl_is_held()); + set_bit(PHYLINK_DISABLE_LINK, &pl->phylink_disable_state); flush_work(&pl->resolve); @@ -1341,6 +1299,8 @@ EXPORT_SYMBOL_GPL(phylink_disable); void phylink_enable(struct phylink *pl) { + WARN_ON(!lockdep_rtnl_is_held()); + clear_bit(PHYLINK_DISABLE_LINK, &pl->phylink_disable_state); phylink_run_resolve(pl); } @@ -1352,6 +1312,8 @@ int phylink_set_link(struct phylink *pl, unsigned int mode, u8 port, __ETHTOOL_DECLARE_LINK_MODE_MASK(mask); int ret = 0; + WARN_ON(!lockdep_rtnl_is_held()); + netdev_dbg(pl->netdev, "requesting link mode %s with support %*pb\n", phylink_an_mode_str(mode), __ETHTOOL_LINK_MODE_MASK_NBITS, support); @@ -1366,7 +1328,6 @@ int phylink_set_link(struct phylink *pl, unsigned int mode, u8 port, if (ret && mode != MLO_AN_PHY) return ret; - mutex_lock(&pl->config_mutex); if (mode == MLO_AN_8023Z && pl->phydev) { ret = -EINVAL; } else { @@ -1394,7 +1355,6 @@ int phylink_set_link(struct phylink *pl, unsigned int mode, u8 port, &pl->phylink_disable_state)) phylink_mac_config(pl, &pl->link_config); } - mutex_unlock(&pl->config_mutex); return ret; } -- cgit From 309078107c415afca95e0b6937de2c1494e0fa4a Mon Sep 17 00:00:00 2001 From: Russell King Date: Tue, 9 May 2017 17:16:15 +0100 Subject: phylink: attach phy to netdev Attach the phy to the net device so that the generic phy calls can access the attached phy without having to use phylink veneers. Signed-off-by: Russell King --- drivers/net/phy/phylink.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index f180b947d090..b4c384cffdef 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -554,7 +554,9 @@ static int phylink_bringup_phy(struct phylink *pl, struct phy_device *phy) "PHY [%s] driver [%s]\n", dev_name(&phy->mdio.dev), phy->drv->name); + mutex_lock(&phy->lock); mutex_lock(&pl->state_mutex); + pl->netdev->phydev = phy; pl->phydev = phy; linkmode_copy(pl->supported, mask); @@ -564,6 +566,7 @@ static int phylink_bringup_phy(struct phylink *pl, struct phy_device *phy) ethtool_convert_legacy_u32_to_link_mode(pl->link_config.advertising, phy->advertising); mutex_unlock(&pl->state_mutex); + mutex_unlock(&phy->lock); netdev_dbg(pl->netdev, "phy: setting supported %*pb advertising 0x%08x\n", @@ -639,14 +642,17 @@ void phylink_disconnect_phy(struct phylink *pl) WARN_ON(!lockdep_rtnl_is_held()); phy = pl->phydev; + if (phy) { + mutex_lock(&phy->lock); + mutex_lock(&pl->state_mutex); + pl->netdev->phydev = NULL; + pl->phydev = NULL; + mutex_unlock(&pl->state_mutex); + mutex_unlock(&phy->lock); + flush_work(&pl->resolve); - mutex_lock(&pl->state_mutex); - pl->phydev = NULL; - mutex_unlock(&pl->state_mutex); - flush_work(&pl->resolve); - - if (phy) phy_disconnect(phy); + } } EXPORT_SYMBOL_GPL(phylink_disconnect_phy); -- cgit From a3f3dd8bf4eb0984eea91f93e589b4d363d9dfaf Mon Sep 17 00:00:00 2001 From: Russell King Date: Tue, 9 May 2017 20:13:38 +0100 Subject: phylink: configure fixed link depending on phy interface mode As we now support 1000BaseX and 2500BaseX phy interface modes, we can select the appropriate autoneg mode and advertising masks from the phy interface mode. We also need to set the phy interface mode when different SFP modules are plugged in (causing changes in the autonegotiation mode.) Signed-off-by: Russell King --- drivers/net/phy/phylink.c | 47 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index b4c384cffdef..4b6a1cb66d2c 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -244,13 +244,33 @@ static int phylink_parse_mode(struct phylink *pl, struct device_node *np) phylink_set(pl->supported, Pause); pl->link_config.an_enabled = true; - 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); - pl->link_an_mode = MLO_AN_SGMII; + switch (pl->link_config.interface) { + case PHY_INTERFACE_MODE_SGMII: + 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); + pl->link_an_mode = MLO_AN_SGMII; + break; + + case PHY_INTERFACE_MODE_1000BASEX: + phylink_set(pl->supported, 1000baseX_Full); + pl->link_an_mode = MLO_AN_8023Z; + break; + + case PHY_INTERFACE_MODE_2500BASEX: + phylink_set(pl->supported, 2500baseX_Full); + pl->link_an_mode = MLO_AN_8023Z; + break; + + default: + netdev_err(pl->netdev, + "incorrect link mode %s for in-band status\n", + phy_modes(pl->link_config.interface)); + return -EINVAL; + } pl->ops->validate_support(pl->netdev, pl->link_an_mode, pl->supported); } @@ -1347,6 +1367,19 @@ int phylink_set_link(struct phylink *pl, unsigned int mode, u8 port, } if (pl->link_an_mode != mode) { + switch (mode) { + case MLO_AN_SGMII: + pl->link_config.interface = + PHY_INTERFACE_MODE_SGMII; + break; + case MLO_AN_8023Z: + pl->link_config.interface = + PHY_INTERFACE_MODE_1000BASEX; + break; + default: + pl->link_config.interface = pl->link_interface; + break; + } pl->link_an_mode = mode; changed = true; -- cgit