diff options
Diffstat (limited to 'drivers/net/phy')
-rw-r--r-- | drivers/net/phy/Kconfig | 17 | ||||
-rw-r--r-- | drivers/net/phy/Makefile | 1 | ||||
-rw-r--r-- | drivers/net/phy/at803x.c | 312 | ||||
-rw-r--r-- | drivers/net/phy/broadcom.c | 89 | ||||
-rw-r--r-- | drivers/net/phy/dp83867.c | 152 | ||||
-rw-r--r-- | drivers/net/phy/dp83869.c | 431 | ||||
-rw-r--r-- | drivers/net/phy/marvell.c | 255 | ||||
-rw-r--r-- | drivers/net/phy/marvell10g.c | 25 | ||||
-rw-r--r-- | drivers/net/phy/mdio-sun4i.c | 3 | ||||
-rw-r--r-- | drivers/net/phy/mdio_bus.c | 8 | ||||
-rw-r--r-- | drivers/net/phy/mscc.c | 208 | ||||
-rw-r--r-- | drivers/net/phy/phy-core.c | 44 | ||||
-rw-r--r-- | drivers/net/phy/phy.c | 67 | ||||
-rw-r--r-- | drivers/net/phy/phy_device.c | 220 | ||||
-rw-r--r-- | drivers/net/phy/phylink.c | 95 | ||||
-rw-r--r-- | drivers/net/phy/sfp-bus.c | 216 | ||||
-rw-r--r-- | drivers/net/phy/sfp.c | 630 |
17 files changed, 2298 insertions, 475 deletions
diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index fe602648b99f..5848219005d7 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -282,11 +282,6 @@ config AX88796B_PHY Currently supports the Asix Electronics PHY found in the X-Surf 100 AX88796B package. -config AT803X_PHY - tristate "AT803X PHYs" - ---help--- - Currently supports the AT8030 and AT8035 model - config BCM63XX_PHY tristate "Broadcom 63xx SOCs internal PHY" depends on BCM63XX || COMPILE_TEST @@ -364,6 +359,12 @@ config DP83867_PHY ---help--- Currently supports the DP83867 PHY. +config DP83869_PHY + tristate "Texas Instruments DP83869 Gigabit PHY" + ---help--- + Currently supports the DP83869 PHY. This PHY supports copper and + fiber connections. + config FIXED_PHY tristate "MDIO Bus/PHY emulation with fixed speed/link PHYs" depends on PHYLIB @@ -444,6 +445,12 @@ config NXP_TJA11XX_PHY ---help--- Currently supports the NXP TJA1100 and TJA1101 PHY. +config AT803X_PHY + tristate "Qualcomm Atheros AR803X PHYs" + depends on REGULATOR + help + Currently supports the AR8030, AR8031, AR8033 and AR8035 model + config QSEMI_PHY tristate "Quality Semiconductor PHYs" ---help--- diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile index a03437e091f3..b433ec3bf9a6 100644 --- a/drivers/net/phy/Makefile +++ b/drivers/net/phy/Makefile @@ -70,6 +70,7 @@ obj-$(CONFIG_DP83822_PHY) += dp83822.o obj-$(CONFIG_DP83TC811_PHY) += dp83tc811.o obj-$(CONFIG_DP83848_PHY) += dp83848.o obj-$(CONFIG_DP83867_PHY) += dp83867.o +obj-$(CONFIG_DP83869_PHY) += dp83869.o obj-$(CONFIG_FIXED_PHY) += fixed_phy.o obj-$(CONFIG_ICPLUS_PHY) += icplus.o obj-$(CONFIG_INTEL_XWAY_PHY) += intel-xway.o diff --git a/drivers/net/phy/at803x.c b/drivers/net/phy/at803x.c index 1eb5d4fb8925..aee62610bade 100644 --- a/drivers/net/phy/at803x.c +++ b/drivers/net/phy/at803x.c @@ -2,7 +2,7 @@ /* * drivers/net/phy/at803x.c * - * Driver for Atheros 803x PHY + * Driver for Qualcomm Atheros AR803x PHY * * Author: Matus Ujhelyi <ujhelyi.m@gmail.com> */ @@ -13,7 +13,12 @@ #include <linux/netdevice.h> #include <linux/etherdevice.h> #include <linux/of_gpio.h> +#include <linux/bitfield.h> #include <linux/gpio/consumer.h> +#include <linux/regulator/of_regulator.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/consumer.h> +#include <dt-bindings/net/qca-ar803x.h> #define AT803X_SPECIFIC_STATUS 0x11 #define AT803X_SS_SPEED_MASK (3 << 14) @@ -62,17 +67,60 @@ #define AT803X_DEBUG_REG_5 0x05 #define AT803X_DEBUG_TX_CLK_DLY_EN BIT(8) +#define AT803X_DEBUG_REG_1F 0x1F +#define AT803X_DEBUG_PLL_ON BIT(2) +#define AT803X_DEBUG_RGMII_1V8 BIT(3) + +/* AT803x supports either the XTAL input pad, an internal PLL or the + * DSP as clock reference for the clock output pad. The XTAL reference + * is only used for 25 MHz output, all other frequencies need the PLL. + * The DSP as a clock reference is used in synchronous ethernet + * applications. + * + * By default the PLL is only enabled if there is a link. Otherwise + * the PHY will go into low power state and disabled the PLL. You can + * set the PLL_ON bit (see debug register 0x1f) to keep the PLL always + * enabled. + */ +#define AT803X_MMD7_CLK25M 0x8016 +#define AT803X_CLK_OUT_MASK GENMASK(4, 2) +#define AT803X_CLK_OUT_25MHZ_XTAL 0 +#define AT803X_CLK_OUT_25MHZ_DSP 1 +#define AT803X_CLK_OUT_50MHZ_PLL 2 +#define AT803X_CLK_OUT_50MHZ_DSP 3 +#define AT803X_CLK_OUT_62_5MHZ_PLL 4 +#define AT803X_CLK_OUT_62_5MHZ_DSP 5 +#define AT803X_CLK_OUT_125MHZ_PLL 6 +#define AT803X_CLK_OUT_125MHZ_DSP 7 + +/* The AR8035 has another mask which is compatible with the AR8031/AR8033 mask + * but doesn't support choosing between XTAL/PLL and DSP. + */ +#define AT8035_CLK_OUT_MASK GENMASK(4, 3) + +#define AT803X_CLK_OUT_STRENGTH_MASK GENMASK(8, 7) +#define AT803X_CLK_OUT_STRENGTH_FULL 0 +#define AT803X_CLK_OUT_STRENGTH_HALF 1 +#define AT803X_CLK_OUT_STRENGTH_QUARTER 2 + +#define ATH9331_PHY_ID 0x004dd041 #define ATH8030_PHY_ID 0x004dd076 #define ATH8031_PHY_ID 0x004dd074 #define ATH8035_PHY_ID 0x004dd072 #define AT803X_PHY_ID_MASK 0xffffffef -MODULE_DESCRIPTION("Atheros 803x PHY driver"); +MODULE_DESCRIPTION("Qualcomm Atheros AR803x PHY driver"); MODULE_AUTHOR("Matus Ujhelyi"); MODULE_LICENSE("GPL"); struct at803x_priv { - bool phy_reset:1; + int flags; +#define AT803X_KEEP_PLL_ENABLED BIT(0) /* don't turn off internal PLL */ + u16 clk_25m_reg; + u16 clk_25m_mask; + struct regulator_dev *vddio_rdev; + struct regulator_dev *vddh_rdev; + struct regulator *vddio; }; struct at803x_context { @@ -240,6 +288,193 @@ static int at803x_resume(struct phy_device *phydev) return phy_modify(phydev, MII_BMCR, BMCR_PDOWN | BMCR_ISOLATE, 0); } +static int at803x_rgmii_reg_set_voltage_sel(struct regulator_dev *rdev, + unsigned int selector) +{ + struct phy_device *phydev = rdev_get_drvdata(rdev); + + if (selector) + return at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_1F, + 0, AT803X_DEBUG_RGMII_1V8); + else + return at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_1F, + AT803X_DEBUG_RGMII_1V8, 0); +} + +static int at803x_rgmii_reg_get_voltage_sel(struct regulator_dev *rdev) +{ + struct phy_device *phydev = rdev_get_drvdata(rdev); + int val; + + val = at803x_debug_reg_read(phydev, AT803X_DEBUG_REG_1F); + if (val < 0) + return val; + + return (val & AT803X_DEBUG_RGMII_1V8) ? 1 : 0; +} + +static struct regulator_ops vddio_regulator_ops = { + .list_voltage = regulator_list_voltage_table, + .set_voltage_sel = at803x_rgmii_reg_set_voltage_sel, + .get_voltage_sel = at803x_rgmii_reg_get_voltage_sel, +}; + +static const unsigned int vddio_voltage_table[] = { + 1500000, + 1800000, +}; + +static const struct regulator_desc vddio_desc = { + .name = "vddio", + .of_match = of_match_ptr("vddio-regulator"), + .n_voltages = ARRAY_SIZE(vddio_voltage_table), + .volt_table = vddio_voltage_table, + .ops = &vddio_regulator_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, +}; + +static struct regulator_ops vddh_regulator_ops = { +}; + +static const struct regulator_desc vddh_desc = { + .name = "vddh", + .of_match = of_match_ptr("vddh-regulator"), + .n_voltages = 1, + .fixed_uV = 2500000, + .ops = &vddh_regulator_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, +}; + +static int at8031_register_regulators(struct phy_device *phydev) +{ + struct at803x_priv *priv = phydev->priv; + struct device *dev = &phydev->mdio.dev; + struct regulator_config config = { }; + + config.dev = dev; + config.driver_data = phydev; + + priv->vddio_rdev = devm_regulator_register(dev, &vddio_desc, &config); + if (IS_ERR(priv->vddio_rdev)) { + phydev_err(phydev, "failed to register VDDIO regulator\n"); + return PTR_ERR(priv->vddio_rdev); + } + + priv->vddh_rdev = devm_regulator_register(dev, &vddh_desc, &config); + if (IS_ERR(priv->vddh_rdev)) { + phydev_err(phydev, "failed to register VDDH regulator\n"); + return PTR_ERR(priv->vddh_rdev); + } + + return 0; +} + +static bool at803x_match_phy_id(struct phy_device *phydev, u32 phy_id) +{ + return (phydev->phy_id & phydev->drv->phy_id_mask) + == (phy_id & phydev->drv->phy_id_mask); +} + +static int at803x_parse_dt(struct phy_device *phydev) +{ + struct device_node *node = phydev->mdio.dev.of_node; + struct at803x_priv *priv = phydev->priv; + unsigned int sel, mask; + u32 freq, strength; + int ret; + + if (!IS_ENABLED(CONFIG_OF_MDIO)) + return 0; + + ret = of_property_read_u32(node, "qca,clk-out-frequency", &freq); + if (!ret) { + mask = AT803X_CLK_OUT_MASK; + switch (freq) { + case 25000000: + sel = AT803X_CLK_OUT_25MHZ_XTAL; + break; + case 50000000: + sel = AT803X_CLK_OUT_50MHZ_PLL; + break; + case 62500000: + sel = AT803X_CLK_OUT_62_5MHZ_PLL; + break; + case 125000000: + sel = AT803X_CLK_OUT_125MHZ_PLL; + break; + default: + phydev_err(phydev, "invalid qca,clk-out-frequency\n"); + return -EINVAL; + } + + priv->clk_25m_reg |= FIELD_PREP(mask, sel); + priv->clk_25m_mask |= mask; + + /* Fixup for the AR8030/AR8035. This chip has another mask and + * doesn't support the DSP reference. Eg. the lowest bit of the + * mask. The upper two bits select the same frequencies. Mask + * the lowest bit here. + * + * Warning: + * There was no datasheet for the AR8030 available so this is + * just a guess. But the AR8035 is listed as pin compatible + * to the AR8030 so there might be a good chance it works on + * the AR8030 too. + */ + if (at803x_match_phy_id(phydev, ATH8030_PHY_ID) || + at803x_match_phy_id(phydev, ATH8035_PHY_ID)) { + priv->clk_25m_reg &= ~AT8035_CLK_OUT_MASK; + priv->clk_25m_mask &= ~AT8035_CLK_OUT_MASK; + } + } + + ret = of_property_read_u32(node, "qca,clk-out-strength", &strength); + if (!ret) { + priv->clk_25m_mask |= AT803X_CLK_OUT_STRENGTH_MASK; + switch (strength) { + case AR803X_STRENGTH_FULL: + priv->clk_25m_reg |= AT803X_CLK_OUT_STRENGTH_FULL; + break; + case AR803X_STRENGTH_HALF: + priv->clk_25m_reg |= AT803X_CLK_OUT_STRENGTH_HALF; + break; + case AR803X_STRENGTH_QUARTER: + priv->clk_25m_reg |= AT803X_CLK_OUT_STRENGTH_QUARTER; + break; + default: + phydev_err(phydev, "invalid qca,clk-out-strength\n"); + return -EINVAL; + } + } + + /* Only supported on AR8031/AR8033, the AR8030/AR8035 use strapping + * options. + */ + if (at803x_match_phy_id(phydev, ATH8031_PHY_ID)) { + if (of_property_read_bool(node, "qca,keep-pll-enabled")) + priv->flags |= AT803X_KEEP_PLL_ENABLED; + + ret = at8031_register_regulators(phydev); + if (ret < 0) + return ret; + + priv->vddio = devm_regulator_get_optional(&phydev->mdio.dev, + "vddio"); + if (IS_ERR(priv->vddio)) { + phydev_err(phydev, "failed to get VDDIO regulator\n"); + return PTR_ERR(priv->vddio); + } + + ret = regulator_enable(priv->vddio); + if (ret < 0) + return ret; + } + + return 0; +} + static int at803x_probe(struct phy_device *phydev) { struct device *dev = &phydev->mdio.dev; @@ -251,7 +486,40 @@ static int at803x_probe(struct phy_device *phydev) phydev->priv = priv; - return 0; + return at803x_parse_dt(phydev); +} + +static int at803x_clk_out_config(struct phy_device *phydev) +{ + struct at803x_priv *priv = phydev->priv; + int val; + + if (!priv->clk_25m_mask) + return 0; + + val = phy_read_mmd(phydev, MDIO_MMD_AN, AT803X_MMD7_CLK25M); + if (val < 0) + return val; + + val &= ~priv->clk_25m_mask; + val |= priv->clk_25m_reg; + + return phy_write_mmd(phydev, MDIO_MMD_AN, AT803X_MMD7_CLK25M, val); +} + +static int at8031_pll_config(struct phy_device *phydev) +{ + struct at803x_priv *priv = phydev->priv; + + /* The default after hardware reset is PLL OFF. After a soft reset, the + * values are retained. + */ + if (priv->flags & AT803X_KEEP_PLL_ENABLED) + return at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_1F, + 0, AT803X_DEBUG_PLL_ON); + else + return at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_1F, + AT803X_DEBUG_PLL_ON, 0); } static int at803x_config_init(struct phy_device *phydev) @@ -276,8 +544,20 @@ static int at803x_config_init(struct phy_device *phydev) ret = at803x_enable_tx_delay(phydev); else ret = at803x_disable_tx_delay(phydev); + if (ret < 0) + return ret; - return ret; + ret = at803x_clk_out_config(phydev); + if (ret < 0) + return ret; + + if (at803x_match_phy_id(phydev, ATH8031_PHY_ID)) { + ret = at8031_pll_config(phydev); + if (ret < 0) + return ret; + } + + return 0; } static int at803x_ack_interrupt(struct phy_device *phydev) @@ -426,9 +706,9 @@ static int at803x_read_status(struct phy_device *phydev) static struct phy_driver at803x_driver[] = { { - /* ATHEROS 8035 */ + /* Qualcomm Atheros AR8035 */ .phy_id = ATH8035_PHY_ID, - .name = "Atheros 8035 ethernet", + .name = "Qualcomm Atheros AR8035", .phy_id_mask = AT803X_PHY_ID_MASK, .probe = at803x_probe, .config_init = at803x_config_init, @@ -441,9 +721,9 @@ static struct phy_driver at803x_driver[] = { .ack_interrupt = at803x_ack_interrupt, .config_intr = at803x_config_intr, }, { - /* ATHEROS 8030 */ + /* Qualcomm Atheros AR8030 */ .phy_id = ATH8030_PHY_ID, - .name = "Atheros 8030 ethernet", + .name = "Qualcomm Atheros AR8030", .phy_id_mask = AT803X_PHY_ID_MASK, .probe = at803x_probe, .config_init = at803x_config_init, @@ -456,9 +736,9 @@ static struct phy_driver at803x_driver[] = { .ack_interrupt = at803x_ack_interrupt, .config_intr = at803x_config_intr, }, { - /* ATHEROS 8031 */ + /* Qualcomm Atheros AR8031/AR8033 */ .phy_id = ATH8031_PHY_ID, - .name = "Atheros 8031 ethernet", + .name = "Qualcomm Atheros AR8031/AR8033", .phy_id_mask = AT803X_PHY_ID_MASK, .probe = at803x_probe, .config_init = at803x_config_init, @@ -471,6 +751,15 @@ static struct phy_driver at803x_driver[] = { .aneg_done = at803x_aneg_done, .ack_interrupt = &at803x_ack_interrupt, .config_intr = &at803x_config_intr, +}, { + /* ATHEROS AR9331 */ + PHY_ID_MATCH_EXACT(ATH9331_PHY_ID), + .name = "Qualcomm Atheros AR9331 built-in PHY", + .suspend = at803x_suspend, + .resume = at803x_resume, + /* PHY_BASIC_FEATURES */ + .ack_interrupt = &at803x_ack_interrupt, + .config_intr = &at803x_config_intr, } }; module_phy_driver(at803x_driver); @@ -479,6 +768,7 @@ static struct mdio_device_id __maybe_unused atheros_tbl[] = { { ATH8030_PHY_ID, AT803X_PHY_ID_MASK }, { ATH8031_PHY_ID, AT803X_PHY_ID_MASK }, { ATH8035_PHY_ID, AT803X_PHY_ID_MASK }, + { PHY_ID_MATCH_EXACT(ATH9331_PHY_ID) }, { } }; diff --git a/drivers/net/phy/broadcom.c b/drivers/net/phy/broadcom.c index 937d0059e8ac..7d68b28bb893 100644 --- a/drivers/net/phy/broadcom.c +++ b/drivers/net/phy/broadcom.c @@ -26,18 +26,13 @@ MODULE_DESCRIPTION("Broadcom PHY driver"); MODULE_AUTHOR("Maciej W. Rozycki"); MODULE_LICENSE("GPL"); +static int bcm54xx_config_clock_delay(struct phy_device *phydev); + static int bcm54210e_config_init(struct phy_device *phydev) { int val; - val = bcm54xx_auxctl_read(phydev, MII_BCM54XX_AUXCTL_SHDWSEL_MISC); - val &= ~MII_BCM54XX_AUXCTL_SHDWSEL_MISC_RGMII_SKEW_EN; - val |= MII_BCM54XX_AUXCTL_MISC_WREN; - bcm54xx_auxctl_write(phydev, MII_BCM54XX_AUXCTL_SHDWSEL_MISC, val); - - val = bcm_phy_read_shadow(phydev, BCM54810_SHD_CLK_CTL); - val &= ~BCM54810_SHD_CLK_CTL_GTXCLK_EN; - bcm_phy_write_shadow(phydev, BCM54810_SHD_CLK_CTL, val); + bcm54xx_config_clock_delay(phydev); if (phydev->dev_flags & PHY_BRCM_EN_MASTER_MODE) { val = phy_read(phydev, MII_CTRL1000); @@ -52,26 +47,7 @@ static int bcm54612e_config_init(struct phy_device *phydev) { int reg; - /* Clear TX internal delay unless requested. */ - if ((phydev->interface != PHY_INTERFACE_MODE_RGMII_ID) && - (phydev->interface != PHY_INTERFACE_MODE_RGMII_TXID)) { - /* Disable TXD to GTXCLK clock delay (default set) */ - /* Bit 9 is the only field in shadow register 00011 */ - bcm_phy_write_shadow(phydev, 0x03, 0); - } - - /* Clear RX internal delay unless requested. */ - if ((phydev->interface != PHY_INTERFACE_MODE_RGMII_ID) && - (phydev->interface != PHY_INTERFACE_MODE_RGMII_RXID)) { - reg = bcm54xx_auxctl_read(phydev, - MII_BCM54XX_AUXCTL_SHDWSEL_MISC); - /* Disable RXD to RXC delay (default set) */ - reg &= ~MII_BCM54XX_AUXCTL_SHDWSEL_MISC_RGMII_SKEW_EN; - /* Clear shadow selector field */ - reg &= ~MII_BCM54XX_AUXCTL_SHDWSEL_MASK; - bcm54xx_auxctl_write(phydev, MII_BCM54XX_AUXCTL_SHDWSEL_MISC, - MII_BCM54XX_AUXCTL_MISC_WREN | reg); - } + bcm54xx_config_clock_delay(phydev); /* Enable CLK125 MUX on LED4 if ref clock is enabled. */ if (!(phydev->dev_flags & PHY_BRCM_RX_REFCLK_UNUSED)) { @@ -383,9 +359,9 @@ static int bcm5482_config_init(struct phy_device *phydev) /* * Select 1000BASE-X register set (primary SerDes) */ - reg = bcm_phy_read_shadow(phydev, BCM5482_SHD_MODE); - bcm_phy_write_shadow(phydev, BCM5482_SHD_MODE, - reg | BCM5482_SHD_MODE_1000BX); + reg = bcm_phy_read_shadow(phydev, BCM54XX_SHD_MODE); + bcm_phy_write_shadow(phydev, BCM54XX_SHD_MODE, + reg | BCM54XX_SHD_MODE_1000BX); /* * LED1=ACTIVITYLED, LED3=LINKSPD[2] @@ -451,12 +427,47 @@ static int bcm5481_config_aneg(struct phy_device *phydev) return ret; } +static int bcm54616s_probe(struct phy_device *phydev) +{ + int val, intf_sel; + + val = bcm_phy_read_shadow(phydev, BCM54XX_SHD_MODE); + if (val < 0) + return val; + + /* The PHY is strapped in RGMII-fiber mode when INTERF_SEL[1:0] + * is 01b, and the link between PHY and its link partner can be + * either 1000Base-X or 100Base-FX. + * RGMII-1000Base-X is properly supported, but RGMII-100Base-FX + * support is still missing as of now. + */ + intf_sel = (val & BCM54XX_SHD_INTF_SEL_MASK) >> 1; + if (intf_sel == 1) { + val = bcm_phy_read_shadow(phydev, BCM54616S_SHD_100FX_CTRL); + if (val < 0) + return val; + + /* Bit 0 of the SerDes 100-FX Control register, when set + * to 1, sets the MII/RGMII -> 100BASE-FX configuration. + * When this bit is set to 0, it sets the GMII/RGMII -> + * 1000BASE-X configuration. + */ + if (!(val & BCM54616S_100FX_MODE)) + phydev->dev_flags |= PHY_BCM_FLAGS_MODE_1000BX; + } + + return 0; +} + static int bcm54616s_config_aneg(struct phy_device *phydev) { int ret; /* Aneg firsly. */ - ret = genphy_config_aneg(phydev); + if (phydev->dev_flags & PHY_BCM_FLAGS_MODE_1000BX) + ret = genphy_c37_config_aneg(phydev); + else + ret = genphy_config_aneg(phydev); /* Then we can set up the delay. */ bcm54xx_config_clock_delay(phydev); @@ -464,6 +475,18 @@ static int bcm54616s_config_aneg(struct phy_device *phydev) return ret; } +static int bcm54616s_read_status(struct phy_device *phydev) +{ + int err; + + if (phydev->dev_flags & PHY_BCM_FLAGS_MODE_1000BX) + err = genphy_c37_read_status(phydev); + else + err = genphy_read_status(phydev); + + return err; +} + static int brcm_phy_setbits(struct phy_device *phydev, int reg, int set) { int val; @@ -655,6 +678,8 @@ static struct phy_driver broadcom_drivers[] = { .config_aneg = bcm54616s_config_aneg, .ack_interrupt = bcm_phy_ack_intr, .config_intr = bcm_phy_config_intr, + .read_status = bcm54616s_read_status, + .probe = bcm54616s_probe, }, { .phy_id = PHY_ID_BCM5464, .phy_id_mask = 0xfffffff0, diff --git a/drivers/net/phy/dp83867.c b/drivers/net/phy/dp83867.c index 37fceaf9fa10..0b95e7a2e273 100644 --- a/drivers/net/phy/dp83867.c +++ b/drivers/net/phy/dp83867.c @@ -12,6 +12,8 @@ #include <linux/of.h> #include <linux/phy.h> #include <linux/delay.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> #include <dt-bindings/net/ti-dp83867.h> @@ -21,8 +23,9 @@ #define MII_DP83867_PHYCTRL 0x10 #define MII_DP83867_MICR 0x12 #define MII_DP83867_ISR 0x13 -#define DP83867_CTRL 0x1f +#define DP83867_CFG2 0x14 #define DP83867_CFG3 0x1e +#define DP83867_CTRL 0x1f /* Extended Registers */ #define DP83867_CFG4 0x0031 @@ -36,6 +39,13 @@ #define DP83867_STRAP_STS1 0x006E #define DP83867_STRAP_STS2 0x006f #define DP83867_RGMIIDCTL 0x0086 +#define DP83867_RXFCFG 0x0134 +#define DP83867_RXFPMD1 0x0136 +#define DP83867_RXFPMD2 0x0137 +#define DP83867_RXFPMD3 0x0138 +#define DP83867_RXFSOP1 0x0139 +#define DP83867_RXFSOP2 0x013A +#define DP83867_RXFSOP3 0x013B #define DP83867_IO_MUX_CFG 0x0170 #define DP83867_SGMIICTL 0x00D3 #define DP83867_10M_SGMII_CFG 0x016F @@ -65,6 +75,13 @@ /* SGMIICTL bits */ #define DP83867_SGMII_TYPE BIT(14) +/* RXFCFG bits*/ +#define DP83867_WOL_MAGIC_EN BIT(0) +#define DP83867_WOL_BCAST_EN BIT(2) +#define DP83867_WOL_UCAST_EN BIT(4) +#define DP83867_WOL_SEC_EN BIT(5) +#define DP83867_WOL_ENH_MAC BIT(7) + /* STRAP_STS1 bits */ #define DP83867_STRAP_STS1_RESERVED BIT(11) @@ -95,6 +112,10 @@ #define DP83867_IO_MUX_CFG_CLK_O_SEL_MASK (0x1f << 8) #define DP83867_IO_MUX_CFG_CLK_O_SEL_SHIFT 8 +/* CFG3 bits */ +#define DP83867_CFG3_INT_OE BIT(7) +#define DP83867_CFG3_ROBUST_AUTO_MDIX BIT(9) + /* CFG4 bits */ #define DP83867_CFG4_PORT_MIRROR_EN BIT(0) @@ -126,6 +147,115 @@ static int dp83867_ack_interrupt(struct phy_device *phydev) return 0; } +static int dp83867_set_wol(struct phy_device *phydev, + struct ethtool_wolinfo *wol) +{ + struct net_device *ndev = phydev->attached_dev; + u16 val_rxcfg, val_micr; + u8 *mac; + + val_rxcfg = phy_read_mmd(phydev, DP83867_DEVADDR, DP83867_RXFCFG); + val_micr = phy_read(phydev, MII_DP83867_MICR); + + if (wol->wolopts & (WAKE_MAGIC | WAKE_MAGICSECURE | WAKE_UCAST | + WAKE_BCAST)) { + val_rxcfg |= DP83867_WOL_ENH_MAC; + val_micr |= MII_DP83867_MICR_WOL_INT_EN; + + if (wol->wolopts & WAKE_MAGIC) { + mac = (u8 *)ndev->dev_addr; + + if (!is_valid_ether_addr(mac)) + return -EINVAL; + + phy_write_mmd(phydev, DP83867_DEVADDR, DP83867_RXFPMD1, + (mac[1] << 8 | mac[0])); + phy_write_mmd(phydev, DP83867_DEVADDR, DP83867_RXFPMD2, + (mac[3] << 8 | mac[2])); + phy_write_mmd(phydev, DP83867_DEVADDR, DP83867_RXFPMD3, + (mac[5] << 8 | mac[4])); + + val_rxcfg |= DP83867_WOL_MAGIC_EN; + } else { + val_rxcfg &= ~DP83867_WOL_MAGIC_EN; + } + + if (wol->wolopts & WAKE_MAGICSECURE) { + phy_write_mmd(phydev, DP83867_DEVADDR, DP83867_RXFSOP1, + (wol->sopass[1] << 8) | wol->sopass[0]); + phy_write_mmd(phydev, DP83867_DEVADDR, DP83867_RXFSOP1, + (wol->sopass[3] << 8) | wol->sopass[2]); + phy_write_mmd(phydev, DP83867_DEVADDR, DP83867_RXFSOP1, + (wol->sopass[5] << 8) | wol->sopass[4]); + + val_rxcfg |= DP83867_WOL_SEC_EN; + } else { + val_rxcfg &= ~DP83867_WOL_SEC_EN; + } + + if (wol->wolopts & WAKE_UCAST) + val_rxcfg |= DP83867_WOL_UCAST_EN; + else + val_rxcfg &= ~DP83867_WOL_UCAST_EN; + + if (wol->wolopts & WAKE_BCAST) + val_rxcfg |= DP83867_WOL_BCAST_EN; + else + val_rxcfg &= ~DP83867_WOL_BCAST_EN; + } else { + val_rxcfg &= ~DP83867_WOL_ENH_MAC; + val_micr &= ~MII_DP83867_MICR_WOL_INT_EN; + } + + phy_write_mmd(phydev, DP83867_DEVADDR, DP83867_RXFCFG, val_rxcfg); + phy_write(phydev, MII_DP83867_MICR, val_micr); + + return 0; +} + +static void dp83867_get_wol(struct phy_device *phydev, + struct ethtool_wolinfo *wol) +{ + u16 value, sopass_val; + + wol->supported = (WAKE_UCAST | WAKE_BCAST | WAKE_MAGIC | + WAKE_MAGICSECURE); + wol->wolopts = 0; + + value = phy_read_mmd(phydev, DP83867_DEVADDR, DP83867_RXFCFG); + + if (value & DP83867_WOL_UCAST_EN) + wol->wolopts |= WAKE_UCAST; + + if (value & DP83867_WOL_BCAST_EN) + wol->wolopts |= WAKE_BCAST; + + if (value & DP83867_WOL_MAGIC_EN) + wol->wolopts |= WAKE_MAGIC; + + if (value & DP83867_WOL_SEC_EN) { + sopass_val = phy_read_mmd(phydev, DP83867_DEVADDR, + DP83867_RXFSOP1); + wol->sopass[0] = (sopass_val & 0xff); + wol->sopass[1] = (sopass_val >> 8); + + sopass_val = phy_read_mmd(phydev, DP83867_DEVADDR, + DP83867_RXFSOP2); + wol->sopass[2] = (sopass_val & 0xff); + wol->sopass[3] = (sopass_val >> 8); + + sopass_val = phy_read_mmd(phydev, DP83867_DEVADDR, + DP83867_RXFSOP3); + wol->sopass[4] = (sopass_val & 0xff); + wol->sopass[5] = (sopass_val >> 8); + + wol->wolopts |= WAKE_MAGICSECURE; + } + + if (!(value & DP83867_WOL_ENH_MAC)) + wol->wolopts = 0; +} + static int dp83867_config_intr(struct phy_device *phydev) { int micr_status; @@ -295,7 +425,7 @@ static int dp83867_probe(struct phy_device *phydev) phydev->priv = dp83867; - return 0; + return dp83867_of_init(phydev); } static int dp83867_config_init(struct phy_device *phydev) @@ -304,10 +434,6 @@ static int dp83867_config_init(struct phy_device *phydev) int ret, val, bs; u16 delay; - ret = dp83867_of_init(phydev); - if (ret) - return ret; - /* RX_DV/RX_CTRL strapped in mode 1 or mode 2 workaround */ if (dp83867->rxctrl_strap_quirk) phy_clear_bits_mmd(phydev, DP83867_DEVADDR, DP83867_CFG4, @@ -410,12 +536,13 @@ static int dp83867_config_init(struct phy_device *phydev) phy_write_mmd(phydev, DP83867_DEVADDR, DP83867_SGMIICTL, val); } + val = phy_read(phydev, DP83867_CFG3); /* Enable Interrupt output INT_OE in CFG3 register */ - if (phy_interrupt_is_valid(phydev)) { - val = phy_read(phydev, DP83867_CFG3); - val |= BIT(7); - phy_write(phydev, DP83867_CFG3, val); - } + if (phy_interrupt_is_valid(phydev)) + val |= DP83867_CFG3_INT_OE; + + val |= DP83867_CFG3_ROBUST_AUTO_MDIX; + phy_write(phydev, DP83867_CFG3, val); if (dp83867->port_mirroring != DP83867_PORT_MIRROING_KEEP) dp83867_config_port_mirroring(phydev); @@ -463,6 +590,9 @@ static struct phy_driver dp83867_driver[] = { .config_init = dp83867_config_init, .soft_reset = dp83867_phy_reset, + .get_wol = dp83867_get_wol, + .set_wol = dp83867_set_wol, + /* IRQ related */ .ack_interrupt = dp83867_ack_interrupt, .config_intr = dp83867_config_intr, diff --git a/drivers/net/phy/dp83869.c b/drivers/net/phy/dp83869.c new file mode 100644 index 000000000000..1c7a7c57dec3 --- /dev/null +++ b/drivers/net/phy/dp83869.c @@ -0,0 +1,431 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Driver for the Texas Instruments DP83869 PHY + * Copyright (C) 2019 Texas Instruments Inc. + */ + +#include <linux/ethtool.h> +#include <linux/kernel.h> +#include <linux/mii.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/phy.h> +#include <linux/delay.h> + +#include <dt-bindings/net/ti-dp83869.h> + +#define DP83869_PHY_ID 0x2000a0f1 +#define DP83869_DEVADDR 0x1f + +#define MII_DP83869_PHYCTRL 0x10 +#define MII_DP83869_MICR 0x12 +#define MII_DP83869_ISR 0x13 +#define DP83869_CTRL 0x1f +#define DP83869_CFG4 0x1e + +/* Extended Registers */ +#define DP83869_GEN_CFG3 0x0031 +#define DP83869_RGMIICTL 0x0032 +#define DP83869_STRAP_STS1 0x006e +#define DP83869_RGMIIDCTL 0x0086 +#define DP83869_IO_MUX_CFG 0x0170 +#define DP83869_OP_MODE 0x01df +#define DP83869_FX_CTRL 0x0c00 + +#define DP83869_SW_RESET BIT(15) +#define DP83869_SW_RESTART BIT(14) + +/* MICR Interrupt bits */ +#define MII_DP83869_MICR_AN_ERR_INT_EN BIT(15) +#define MII_DP83869_MICR_SPEED_CHNG_INT_EN BIT(14) +#define MII_DP83869_MICR_DUP_MODE_CHNG_INT_EN BIT(13) +#define MII_DP83869_MICR_PAGE_RXD_INT_EN BIT(12) +#define MII_DP83869_MICR_AUTONEG_COMP_INT_EN BIT(11) +#define MII_DP83869_MICR_LINK_STS_CHNG_INT_EN BIT(10) +#define MII_DP83869_MICR_FALSE_CARRIER_INT_EN BIT(8) +#define MII_DP83869_MICR_SLEEP_MODE_CHNG_INT_EN BIT(4) +#define MII_DP83869_MICR_WOL_INT_EN BIT(3) +#define MII_DP83869_MICR_XGMII_ERR_INT_EN BIT(2) +#define MII_DP83869_MICR_POL_CHNG_INT_EN BIT(1) +#define MII_DP83869_MICR_JABBER_INT_EN BIT(0) + +#define MII_DP83869_BMCR_DEFAULT (BMCR_ANENABLE | \ + BMCR_FULLDPLX | \ + BMCR_SPEED1000) + +/* This is the same bit mask as the BMCR so re-use the BMCR default */ +#define DP83869_FX_CTRL_DEFAULT MII_DP83869_BMCR_DEFAULT + +/* CFG1 bits */ +#define DP83869_CFG1_DEFAULT (ADVERTISE_1000HALF | \ + ADVERTISE_1000FULL | \ + CTL1000_AS_MASTER) + +/* RGMIICTL bits */ +#define DP83869_RGMII_TX_CLK_DELAY_EN BIT(1) +#define DP83869_RGMII_RX_CLK_DELAY_EN BIT(0) + +/* STRAP_STS1 bits */ +#define DP83869_STRAP_STS1_RESERVED BIT(11) + +/* PHYCTRL bits */ +#define DP83869_RX_FIFO_SHIFT 12 +#define DP83869_TX_FIFO_SHIFT 14 + +/* PHY_CTRL lower bytes 0x48 are declared as reserved */ +#define DP83869_PHY_CTRL_DEFAULT 0x48 +#define DP83869_PHYCR_FIFO_DEPTH_MASK GENMASK(15, 12) +#define DP83869_PHYCR_RESERVED_MASK BIT(11) + +/* RGMIIDCTL bits */ +#define DP83869_RGMII_TX_CLK_DELAY_SHIFT 4 + +/* IO_MUX_CFG bits */ +#define DP83869_IO_MUX_CFG_IO_IMPEDANCE_CTRL 0x1f + +#define DP83869_IO_MUX_CFG_IO_IMPEDANCE_MAX 0x0 +#define DP83869_IO_MUX_CFG_IO_IMPEDANCE_MIN 0x1f +#define DP83869_IO_MUX_CFG_CLK_O_SEL_MASK (0x1f << 8) +#define DP83869_IO_MUX_CFG_CLK_O_SEL_SHIFT 8 + +/* CFG3 bits */ +#define DP83869_CFG3_PORT_MIRROR_EN BIT(0) + +/* CFG4 bits */ +#define DP83869_INT_OE BIT(7) + +/* OP MODE */ +#define DP83869_OP_MODE_MII BIT(5) +#define DP83869_SGMII_RGMII_BRIDGE BIT(6) + +enum { + DP83869_PORT_MIRRORING_KEEP, + DP83869_PORT_MIRRORING_EN, + DP83869_PORT_MIRRORING_DIS, +}; + +struct dp83869_private { + int tx_fifo_depth; + int rx_fifo_depth; + int io_impedance; + int port_mirroring; + bool rxctrl_strap_quirk; + int clk_output_sel; + int mode; +}; + +static int dp83869_ack_interrupt(struct phy_device *phydev) +{ + int err = phy_read(phydev, MII_DP83869_ISR); + + if (err < 0) + return err; + + return 0; +} + +static int dp83869_config_intr(struct phy_device *phydev) +{ + int micr_status = 0; + + if (phydev->interrupts == PHY_INTERRUPT_ENABLED) { + micr_status = phy_read(phydev, MII_DP83869_MICR); + if (micr_status < 0) + return micr_status; + + micr_status |= + (MII_DP83869_MICR_AN_ERR_INT_EN | + MII_DP83869_MICR_SPEED_CHNG_INT_EN | + MII_DP83869_MICR_AUTONEG_COMP_INT_EN | + MII_DP83869_MICR_LINK_STS_CHNG_INT_EN | + MII_DP83869_MICR_DUP_MODE_CHNG_INT_EN | + MII_DP83869_MICR_SLEEP_MODE_CHNG_INT_EN); + + return phy_write(phydev, MII_DP83869_MICR, micr_status); + } + + return phy_write(phydev, MII_DP83869_MICR, micr_status); +} + +static int dp83869_config_port_mirroring(struct phy_device *phydev) +{ + struct dp83869_private *dp83869 = phydev->priv; + + if (dp83869->port_mirroring == DP83869_PORT_MIRRORING_EN) + phy_set_bits_mmd(phydev, DP83869_DEVADDR, DP83869_GEN_CFG3, + DP83869_CFG3_PORT_MIRROR_EN); + else + phy_clear_bits_mmd(phydev, DP83869_DEVADDR, DP83869_GEN_CFG3, + DP83869_CFG3_PORT_MIRROR_EN); + + return 0; +} + +#ifdef CONFIG_OF_MDIO +static int dp83869_of_init(struct phy_device *phydev) +{ + struct dp83869_private *dp83869 = phydev->priv; + struct device *dev = &phydev->mdio.dev; + struct device_node *of_node = dev->of_node; + int ret; + + if (!of_node) + return -ENODEV; + + dp83869->io_impedance = -EINVAL; + + /* Optional configuration */ + ret = of_property_read_u32(of_node, "ti,clk-output-sel", + &dp83869->clk_output_sel); + if (ret || dp83869->clk_output_sel > DP83869_CLK_O_SEL_REF_CLK) + dp83869->clk_output_sel = DP83869_CLK_O_SEL_REF_CLK; + + ret = of_property_read_u32(of_node, "ti,op-mode", &dp83869->mode); + if (ret == 0) { + if (dp83869->mode < DP83869_RGMII_COPPER_ETHERNET || + dp83869->mode > DP83869_SGMII_COPPER_ETHERNET) + return -EINVAL; + } + + if (of_property_read_bool(of_node, "ti,max-output-impedance")) + dp83869->io_impedance = DP83869_IO_MUX_CFG_IO_IMPEDANCE_MAX; + else if (of_property_read_bool(of_node, "ti,min-output-impedance")) + dp83869->io_impedance = DP83869_IO_MUX_CFG_IO_IMPEDANCE_MIN; + + if (of_property_read_bool(of_node, "enet-phy-lane-swap")) + dp83869->port_mirroring = DP83869_PORT_MIRRORING_EN; + else + dp83869->port_mirroring = DP83869_PORT_MIRRORING_DIS; + + if (of_property_read_u32(of_node, "rx-fifo-depth", + &dp83869->rx_fifo_depth)) + dp83869->rx_fifo_depth = DP83869_PHYCR_FIFO_DEPTH_4_B_NIB; + + if (of_property_read_u32(of_node, "tx-fifo-depth", + &dp83869->tx_fifo_depth)) + dp83869->tx_fifo_depth = DP83869_PHYCR_FIFO_DEPTH_4_B_NIB; + + return 0; +} +#else +static int dp83869_of_init(struct phy_device *phydev) +{ + return 0; +} +#endif /* CONFIG_OF_MDIO */ + +static int dp83869_configure_rgmii(struct phy_device *phydev, + struct dp83869_private *dp83869) +{ + int ret, val; + + if (phy_interface_is_rgmii(phydev)) { + val = phy_read(phydev, MII_DP83869_PHYCTRL); + if (val < 0) + return val; + + val &= ~DP83869_PHYCR_FIFO_DEPTH_MASK; + val |= (dp83869->tx_fifo_depth << DP83869_TX_FIFO_SHIFT); + val |= (dp83869->rx_fifo_depth << DP83869_RX_FIFO_SHIFT); + + ret = phy_write(phydev, MII_DP83869_PHYCTRL, val); + if (ret) + return ret; + } + + if (dp83869->io_impedance >= 0) + phy_modify_mmd(phydev, DP83869_DEVADDR, + DP83869_IO_MUX_CFG, + DP83869_IO_MUX_CFG_IO_IMPEDANCE_CTRL, + dp83869->io_impedance & + DP83869_IO_MUX_CFG_IO_IMPEDANCE_CTRL); + + return 0; +} + +static int dp83869_configure_mode(struct phy_device *phydev, + struct dp83869_private *dp83869) +{ + int phy_ctrl_val; + int ret; + + if (dp83869->mode < DP83869_RGMII_COPPER_ETHERNET || + dp83869->mode > DP83869_SGMII_COPPER_ETHERNET) + return -EINVAL; + + /* Below init sequence for each operational mode is defined in + * section 9.4.8 of the datasheet. + */ + ret = phy_write_mmd(phydev, DP83869_DEVADDR, DP83869_OP_MODE, + dp83869->mode); + if (ret) + return ret; + + ret = phy_write(phydev, MII_BMCR, MII_DP83869_BMCR_DEFAULT); + if (ret) + return ret; + + phy_ctrl_val = (dp83869->rx_fifo_depth << DP83869_RX_FIFO_SHIFT | + dp83869->tx_fifo_depth << DP83869_TX_FIFO_SHIFT | + DP83869_PHY_CTRL_DEFAULT); + + switch (dp83869->mode) { + case DP83869_RGMII_COPPER_ETHERNET: + ret = phy_write(phydev, MII_DP83869_PHYCTRL, + phy_ctrl_val); + if (ret) + return ret; + + ret = phy_write(phydev, MII_CTRL1000, DP83869_CFG1_DEFAULT); + if (ret) + return ret; + + ret = dp83869_configure_rgmii(phydev, dp83869); + if (ret) + return ret; + break; + case DP83869_RGMII_SGMII_BRIDGE: + phy_modify_mmd(phydev, DP83869_DEVADDR, DP83869_OP_MODE, + DP83869_SGMII_RGMII_BRIDGE, + DP83869_SGMII_RGMII_BRIDGE); + + ret = phy_write_mmd(phydev, DP83869_DEVADDR, + DP83869_FX_CTRL, DP83869_FX_CTRL_DEFAULT); + if (ret) + return ret; + + break; + case DP83869_1000M_MEDIA_CONVERT: + ret = phy_write(phydev, MII_DP83869_PHYCTRL, + phy_ctrl_val); + if (ret) + return ret; + + ret = phy_write_mmd(phydev, DP83869_DEVADDR, + DP83869_FX_CTRL, DP83869_FX_CTRL_DEFAULT); + if (ret) + return ret; + break; + case DP83869_100M_MEDIA_CONVERT: + ret = phy_write(phydev, MII_DP83869_PHYCTRL, + phy_ctrl_val); + if (ret) + return ret; + break; + case DP83869_SGMII_COPPER_ETHERNET: + ret = phy_write(phydev, MII_DP83869_PHYCTRL, + phy_ctrl_val); + if (ret) + return ret; + + ret = phy_write(phydev, MII_CTRL1000, DP83869_CFG1_DEFAULT); + if (ret) + return ret; + + ret = phy_write_mmd(phydev, DP83869_DEVADDR, + DP83869_FX_CTRL, DP83869_FX_CTRL_DEFAULT); + if (ret) + return ret; + + break; + case DP83869_RGMII_1000_BASE: + case DP83869_RGMII_100_BASE: + break; + default: + return -EINVAL; + }; + + return 0; +} + +static int dp83869_config_init(struct phy_device *phydev) +{ + struct dp83869_private *dp83869 = phydev->priv; + int ret, val; + + ret = dp83869_configure_mode(phydev, dp83869); + if (ret) + return ret; + + /* Enable Interrupt output INT_OE in CFG4 register */ + if (phy_interrupt_is_valid(phydev)) { + val = phy_read(phydev, DP83869_CFG4); + val |= DP83869_INT_OE; + phy_write(phydev, DP83869_CFG4, val); + } + + if (dp83869->port_mirroring != DP83869_PORT_MIRRORING_KEEP) + dp83869_config_port_mirroring(phydev); + + /* Clock output selection if muxing property is set */ + if (dp83869->clk_output_sel != DP83869_CLK_O_SEL_REF_CLK) + phy_modify_mmd(phydev, DP83869_DEVADDR, DP83869_IO_MUX_CFG, + DP83869_IO_MUX_CFG_CLK_O_SEL_MASK, + dp83869->clk_output_sel << + DP83869_IO_MUX_CFG_CLK_O_SEL_SHIFT); + + return 0; +} + +static int dp83869_probe(struct phy_device *phydev) +{ + struct dp83869_private *dp83869; + int ret; + + dp83869 = devm_kzalloc(&phydev->mdio.dev, sizeof(*dp83869), + GFP_KERNEL); + if (!dp83869) + return -ENOMEM; + + phydev->priv = dp83869; + + ret = dp83869_of_init(phydev); + if (ret) + return ret; + + return dp83869_config_init(phydev); +} + +static int dp83869_phy_reset(struct phy_device *phydev) +{ + int ret; + + ret = phy_write(phydev, DP83869_CTRL, DP83869_SW_RESET); + if (ret < 0) + return ret; + + usleep_range(10, 20); + + /* Global sw reset sets all registers to default. + * Need to set the registers in the PHY to the right config. + */ + return dp83869_config_init(phydev); +} + +static struct phy_driver dp83869_driver[] = { + { + PHY_ID_MATCH_MODEL(DP83869_PHY_ID), + .name = "TI DP83869", + + .probe = dp83869_probe, + .config_init = dp83869_config_init, + .soft_reset = dp83869_phy_reset, + + /* IRQ related */ + .ack_interrupt = dp83869_ack_interrupt, + .config_intr = dp83869_config_intr, + + .suspend = genphy_suspend, + .resume = genphy_resume, + }, +}; +module_phy_driver(dp83869_driver); + +static struct mdio_device_id __maybe_unused dp83869_tbl[] = { + { PHY_ID_MATCH_MODEL(DP83869_PHY_ID) }, + { } +}; +MODULE_DEVICE_TABLE(mdio, dp83869_tbl); + +MODULE_DESCRIPTION("Texas Instruments DP83869 PHY driver"); +MODULE_AUTHOR("Dan Murphy <dmurphy@ti.com"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/phy/marvell.c b/drivers/net/phy/marvell.c index a7796134e3be..b1fbd1937328 100644 --- a/drivers/net/phy/marvell.c +++ b/drivers/net/phy/marvell.c @@ -53,16 +53,22 @@ #define MII_M1011_PHY_SCR 0x10 #define MII_M1011_PHY_SCR_DOWNSHIFT_EN BIT(11) -#define MII_M1011_PHY_SCR_DOWNSHIFT_SHIFT 12 -#define MII_M1011_PHY_SRC_DOWNSHIFT_MASK 0x7800 +#define MII_M1011_PHY_SCR_DOWNSHIFT_MASK GENMASK(14, 12) +#define MII_M1011_PHY_SCR_DOWNSHIFT_MAX 8 #define MII_M1011_PHY_SCR_MDI (0x0 << 5) #define MII_M1011_PHY_SCR_MDI_X (0x1 << 5) #define MII_M1011_PHY_SCR_AUTO_CROSS (0x3 << 5) +#define MII_M1011_PHY_SSR 0x11 +#define MII_M1011_PHY_SSR_DOWNSHIFT BIT(5) + #define MII_M1111_PHY_LED_CONTROL 0x18 #define MII_M1111_PHY_LED_DIRECT 0x4100 #define MII_M1111_PHY_LED_COMBINE 0x411c #define MII_M1111_PHY_EXT_CR 0x14 +#define MII_M1111_PHY_EXT_CR_DOWNSHIFT_MASK GENMASK(11, 9) +#define MII_M1111_PHY_EXT_CR_DOWNSHIFT_MAX 8 +#define MII_M1111_PHY_EXT_CR_DOWNSHIFT_EN BIT(8) #define MII_M1111_RGMII_RX_DELAY BIT(7) #define MII_M1111_RGMII_TX_DELAY BIT(1) #define MII_M1111_PHY_EXT_SR 0x1b @@ -273,23 +279,6 @@ static int marvell_set_polarity(struct phy_device *phydev, int polarity) return val != reg; } -static int marvell_set_downshift(struct phy_device *phydev, bool enable, - u8 retries) -{ - int reg; - - reg = phy_read(phydev, MII_M1011_PHY_SCR); - if (reg < 0) - return reg; - - reg &= MII_M1011_PHY_SRC_DOWNSHIFT_MASK; - reg |= ((retries - 1) << MII_M1011_PHY_SCR_DOWNSHIFT_SHIFT); - if (enable) - reg |= MII_M1011_PHY_SCR_DOWNSHIFT_EN; - - return phy_write(phydev, MII_M1011_PHY_SCR, reg); -} - static int marvell_config_aneg(struct phy_device *phydev) { int changed = 0; @@ -658,41 +647,6 @@ static int marvell_config_init(struct phy_device *phydev) return marvell_of_reg_init(phydev); } -static int m88e1116r_config_init(struct phy_device *phydev) -{ - int err; - - err = genphy_soft_reset(phydev); - if (err < 0) - return err; - - msleep(500); - - err = marvell_set_page(phydev, MII_MARVELL_COPPER_PAGE); - if (err < 0) - return err; - - err = marvell_set_polarity(phydev, phydev->mdix_ctrl); - if (err < 0) - return err; - - err = marvell_set_downshift(phydev, true, 8); - if (err < 0) - return err; - - if (phy_interface_is_rgmii(phydev)) { - err = m88e1121_config_aneg_rgmii_delays(phydev); - if (err < 0) - return err; - } - - err = genphy_soft_reset(phydev); - if (err < 0) - return err; - - return marvell_config_init(phydev); -} - static int m88e3016_config_init(struct phy_device *phydev) { int ret; @@ -833,6 +787,172 @@ static int m88e1111_config_init(struct phy_device *phydev) return genphy_soft_reset(phydev); } +static int m88e1111_get_downshift(struct phy_device *phydev, u8 *data) +{ + int val, cnt, enable; + + val = phy_read(phydev, MII_M1111_PHY_EXT_CR); + if (val < 0) + return val; + + enable = FIELD_GET(MII_M1111_PHY_EXT_CR_DOWNSHIFT_EN, val); + cnt = FIELD_GET(MII_M1111_PHY_EXT_CR_DOWNSHIFT_MASK, val) + 1; + + *data = enable ? cnt : DOWNSHIFT_DEV_DISABLE; + + return 0; +} + +static int m88e1111_set_downshift(struct phy_device *phydev, u8 cnt) +{ + int val; + + if (cnt > MII_M1111_PHY_EXT_CR_DOWNSHIFT_MAX) + return -E2BIG; + + if (!cnt) + return phy_clear_bits(phydev, MII_M1111_PHY_EXT_CR, + MII_M1111_PHY_EXT_CR_DOWNSHIFT_EN); + + val = MII_M1111_PHY_EXT_CR_DOWNSHIFT_EN; + val |= FIELD_PREP(MII_M1111_PHY_EXT_CR_DOWNSHIFT_MASK, cnt - 1); + + return phy_modify(phydev, MII_M1111_PHY_EXT_CR, + MII_M1111_PHY_EXT_CR_DOWNSHIFT_EN | + MII_M1111_PHY_EXT_CR_DOWNSHIFT_MASK, + val); +} + +static int m88e1111_get_tunable(struct phy_device *phydev, + struct ethtool_tunable *tuna, void *data) +{ + switch (tuna->id) { + case ETHTOOL_PHY_DOWNSHIFT: + return m88e1111_get_downshift(phydev, data); + default: + return -EOPNOTSUPP; + } +} + +static int m88e1111_set_tunable(struct phy_device *phydev, + struct ethtool_tunable *tuna, const void *data) +{ + switch (tuna->id) { + case ETHTOOL_PHY_DOWNSHIFT: + return m88e1111_set_downshift(phydev, *(const u8 *)data); + default: + return -EOPNOTSUPP; + } +} + +static int m88e1011_get_downshift(struct phy_device *phydev, u8 *data) +{ + int val, cnt, enable; + + val = phy_read(phydev, MII_M1011_PHY_SCR); + if (val < 0) + return val; + + enable = FIELD_GET(MII_M1011_PHY_SCR_DOWNSHIFT_EN, val); + cnt = FIELD_GET(MII_M1011_PHY_SCR_DOWNSHIFT_MASK, val) + 1; + + *data = enable ? cnt : DOWNSHIFT_DEV_DISABLE; + + return 0; +} + +static int m88e1011_set_downshift(struct phy_device *phydev, u8 cnt) +{ + int val; + + if (cnt > MII_M1011_PHY_SCR_DOWNSHIFT_MAX) + return -E2BIG; + + if (!cnt) + return phy_clear_bits(phydev, MII_M1011_PHY_SCR, + MII_M1011_PHY_SCR_DOWNSHIFT_EN); + + val = MII_M1011_PHY_SCR_DOWNSHIFT_EN; + val |= FIELD_PREP(MII_M1011_PHY_SCR_DOWNSHIFT_MASK, cnt - 1); + + return phy_modify(phydev, MII_M1011_PHY_SCR, + MII_M1011_PHY_SCR_DOWNSHIFT_EN | + MII_M1011_PHY_SCR_DOWNSHIFT_MASK, + val); +} + +static int m88e1011_get_tunable(struct phy_device *phydev, + struct ethtool_tunable *tuna, void *data) +{ + switch (tuna->id) { + case ETHTOOL_PHY_DOWNSHIFT: + return m88e1011_get_downshift(phydev, data); + default: + return -EOPNOTSUPP; + } +} + +static int m88e1011_set_tunable(struct phy_device *phydev, + struct ethtool_tunable *tuna, const void *data) +{ + switch (tuna->id) { + case ETHTOOL_PHY_DOWNSHIFT: + return m88e1011_set_downshift(phydev, *(const u8 *)data); + default: + return -EOPNOTSUPP; + } +} + +static void m88e1011_link_change_notify(struct phy_device *phydev) +{ + int status; + + if (phydev->state != PHY_RUNNING) + return; + + /* we may be on fiber page currently */ + status = phy_read_paged(phydev, MII_MARVELL_COPPER_PAGE, + MII_M1011_PHY_SSR); + + if (status > 0 && status & MII_M1011_PHY_SSR_DOWNSHIFT) + phydev_warn(phydev, "Downshift occurred! Cabling may be defective.\n"); +} + +static int m88e1116r_config_init(struct phy_device *phydev) +{ + int err; + + err = genphy_soft_reset(phydev); + if (err < 0) + return err; + + msleep(500); + + err = marvell_set_page(phydev, MII_MARVELL_COPPER_PAGE); + if (err < 0) + return err; + + err = marvell_set_polarity(phydev, phydev->mdix_ctrl); + if (err < 0) + return err; + + err = m88e1011_set_downshift(phydev, 8); + if (err < 0) + return err; + + if (phy_interface_is_rgmii(phydev)) { + err = m88e1121_config_aneg_rgmii_delays(phydev); + if (err < 0) + return err; + } + + err = genphy_soft_reset(phydev); + if (err < 0) + return err; + + return marvell_config_init(phydev); +} + static int m88e1318_config_init(struct phy_device *phydev) { if (phy_interrupt_is_valid(phydev)) { @@ -1117,6 +1237,8 @@ static int m88e1540_get_tunable(struct phy_device *phydev, switch (tuna->id) { case ETHTOOL_PHY_FAST_LINK_DOWN: return m88e1540_get_fld(phydev, data); + case ETHTOOL_PHY_DOWNSHIFT: + return m88e1011_get_downshift(phydev, data); default: return -EOPNOTSUPP; } @@ -1128,6 +1250,8 @@ static int m88e1540_set_tunable(struct phy_device *phydev, switch (tuna->id) { case ETHTOOL_PHY_FAST_LINK_DOWN: return m88e1540_set_fld(phydev, data); + case ETHTOOL_PHY_DOWNSHIFT: + return m88e1011_set_downshift(phydev, *(const u8 *)data); default: return -EOPNOTSUPP; } @@ -2163,6 +2287,9 @@ static struct phy_driver marvell_drivers[] = { .get_sset_count = marvell_get_sset_count, .get_strings = marvell_get_strings, .get_stats = marvell_get_stats, + .get_tunable = m88e1011_get_tunable, + .set_tunable = m88e1011_set_tunable, + .link_change_notify = m88e1011_link_change_notify, }, { .phy_id = MARVELL_PHY_ID_88E1111, @@ -2182,6 +2309,9 @@ static struct phy_driver marvell_drivers[] = { .get_sset_count = marvell_get_sset_count, .get_strings = marvell_get_strings, .get_stats = marvell_get_stats, + .get_tunable = m88e1111_get_tunable, + .set_tunable = m88e1111_set_tunable, + .link_change_notify = m88e1011_link_change_notify, }, { .phy_id = MARVELL_PHY_ID_88E1118, @@ -2220,6 +2350,9 @@ static struct phy_driver marvell_drivers[] = { .get_sset_count = marvell_get_sset_count, .get_strings = marvell_get_strings, .get_stats = marvell_get_stats, + .get_tunable = m88e1011_get_tunable, + .set_tunable = m88e1011_set_tunable, + .link_change_notify = m88e1011_link_change_notify, }, { .phy_id = MARVELL_PHY_ID_88E1318S, @@ -2261,6 +2394,9 @@ static struct phy_driver marvell_drivers[] = { .get_sset_count = marvell_get_sset_count, .get_strings = marvell_get_strings, .get_stats = marvell_get_stats, + .get_tunable = m88e1111_get_tunable, + .set_tunable = m88e1111_set_tunable, + .link_change_notify = m88e1011_link_change_notify, }, { .phy_id = MARVELL_PHY_ID_88E1149R, @@ -2314,6 +2450,9 @@ static struct phy_driver marvell_drivers[] = { .get_sset_count = marvell_get_sset_count, .get_strings = marvell_get_strings, .get_stats = marvell_get_stats, + .get_tunable = m88e1011_get_tunable, + .set_tunable = m88e1011_set_tunable, + .link_change_notify = m88e1011_link_change_notify, }, { .phy_id = MARVELL_PHY_ID_88E1510, @@ -2337,6 +2476,9 @@ static struct phy_driver marvell_drivers[] = { .get_strings = marvell_get_strings, .get_stats = marvell_get_stats, .set_loopback = genphy_loopback, + .get_tunable = m88e1011_get_tunable, + .set_tunable = m88e1011_set_tunable, + .link_change_notify = m88e1011_link_change_notify, }, { .phy_id = MARVELL_PHY_ID_88E1540, @@ -2359,6 +2501,7 @@ static struct phy_driver marvell_drivers[] = { .get_stats = marvell_get_stats, .get_tunable = m88e1540_get_tunable, .set_tunable = m88e1540_set_tunable, + .link_change_notify = m88e1011_link_change_notify, }, { .phy_id = MARVELL_PHY_ID_88E1545, @@ -2379,6 +2522,9 @@ static struct phy_driver marvell_drivers[] = { .get_sset_count = marvell_get_sset_count, .get_strings = marvell_get_strings, .get_stats = marvell_get_stats, + .get_tunable = m88e1540_get_tunable, + .set_tunable = m88e1540_set_tunable, + .link_change_notify = m88e1011_link_change_notify, }, { .phy_id = MARVELL_PHY_ID_88E3016, @@ -2421,6 +2567,7 @@ static struct phy_driver marvell_drivers[] = { .get_stats = marvell_get_stats, .get_tunable = m88e1540_get_tunable, .set_tunable = m88e1540_set_tunable, + .link_change_notify = m88e1011_link_change_notify, }, }; diff --git a/drivers/net/phy/marvell10g.c b/drivers/net/phy/marvell10g.c index 3b99882692e3..1bf13017d288 100644 --- a/drivers/net/phy/marvell10g.c +++ b/drivers/net/phy/marvell10g.c @@ -26,6 +26,7 @@ #include <linux/hwmon.h> #include <linux/marvell_phy.h> #include <linux/phy.h> +#include <linux/sfp.h> #define MV_PHY_ALASKA_NBT_QUIRK_MASK 0xfffffffe #define MV_PHY_ALASKA_NBT_QUIRK_REV (MARVELL_PHY_ID_88X3310 | 0xa) @@ -206,6 +207,28 @@ static int mv3310_hwmon_probe(struct phy_device *phydev) } #endif +static int mv3310_sfp_insert(void *upstream, const struct sfp_eeprom_id *id) +{ + struct phy_device *phydev = upstream; + __ETHTOOL_DECLARE_LINK_MODE_MASK(support) = { 0, }; + phy_interface_t iface; + + sfp_parse_support(phydev->sfp_bus, id, support); + iface = sfp_select_interface(phydev->sfp_bus, id, support); + + if (iface != PHY_INTERFACE_MODE_10GKR) { + dev_err(&phydev->mdio.dev, "incompatible SFP module inserted\n"); + return -EINVAL; + } + return 0; +} + +static const struct sfp_upstream_ops mv3310_sfp_ops = { + .attach = phy_sfp_attach, + .detach = phy_sfp_detach, + .module_insert = mv3310_sfp_insert, +}; + static int mv3310_probe(struct phy_device *phydev) { struct mv3310_priv *priv; @@ -236,7 +259,7 @@ static int mv3310_probe(struct phy_device *phydev) if (ret) return ret; - return 0; + return phy_sfp_probe(phydev, &mv3310_sfp_ops); } static int mv3310_suspend(struct phy_device *phydev) diff --git a/drivers/net/phy/mdio-sun4i.c b/drivers/net/phy/mdio-sun4i.c index 58d6504495e0..f798de3276dc 100644 --- a/drivers/net/phy/mdio-sun4i.c +++ b/drivers/net/phy/mdio-sun4i.c @@ -145,8 +145,11 @@ err_out_free_mdiobus: static int sun4i_mdio_remove(struct platform_device *pdev) { struct mii_bus *bus = platform_get_drvdata(pdev); + struct sun4i_mdio_data *data = bus->priv; mdiobus_unregister(bus); + if (data->regulator) + regulator_disable(data->regulator); mdiobus_free(bus); return 0; diff --git a/drivers/net/phy/mdio_bus.c b/drivers/net/phy/mdio_bus.c index 35876562e32a..229e480179ff 100644 --- a/drivers/net/phy/mdio_bus.c +++ b/drivers/net/phy/mdio_bus.c @@ -62,10 +62,10 @@ static int mdiobus_register_reset(struct mdio_device *mdiodev) struct reset_control *reset = NULL; if (mdiodev->dev.of_node) - reset = devm_reset_control_get_exclusive(&mdiodev->dev, - "phy"); + reset = of_reset_control_get_exclusive(mdiodev->dev.of_node, + "phy"); if (IS_ERR(reset)) { - if (PTR_ERR(reset) == -ENOENT || PTR_ERR(reset) == -ENOSYS) + if (PTR_ERR(reset) == -ENOENT || PTR_ERR(reset) == -ENOTSUPP) reset = NULL; else return PTR_ERR(reset); @@ -107,6 +107,8 @@ int mdiobus_unregister_device(struct mdio_device *mdiodev) if (mdiodev->bus->mdio_map[mdiodev->addr] != mdiodev) return -EINVAL; + reset_control_put(mdiodev->reset_ctrl); + mdiodev->bus->mdio_map[mdiodev->addr] = NULL; return 0; diff --git a/drivers/net/phy/mscc.c b/drivers/net/phy/mscc.c index 7ada1fd9ca71..d5f8f351d9ef 100644 --- a/drivers/net/phy/mscc.c +++ b/drivers/net/phy/mscc.c @@ -252,13 +252,21 @@ enum rgmii_rx_clock_delay { #define MSCC_PHY_TR_LSB 17 #define MSCC_PHY_TR_MSB 18 -/* Microsemi PHY ID's */ +/* Microsemi PHY ID's + * Code assumes lowest nibble is 0 + */ +#define PHY_ID_VSC8504 0x000704c0 #define PHY_ID_VSC8514 0x00070670 #define PHY_ID_VSC8530 0x00070560 #define PHY_ID_VSC8531 0x00070570 #define PHY_ID_VSC8540 0x00070760 #define PHY_ID_VSC8541 0x00070770 +#define PHY_ID_VSC8552 0x000704e0 +#define PHY_ID_VSC856X 0x000707e0 +#define PHY_ID_VSC8572 0x000704d0 #define PHY_ID_VSC8574 0x000704a0 +#define PHY_ID_VSC8575 0x000707d0 +#define PHY_ID_VSC8582 0x000707b0 #define PHY_ID_VSC8584 0x000707c0 #define MSCC_VDDMAC_1500 1500 @@ -895,7 +903,7 @@ static void vsc85xx_tr_write(struct phy_device *phydev, u16 addr, u32 val) static int vsc8531_pre_init_seq_set(struct phy_device *phydev) { int rc; - const struct reg_val init_seq[] = { + static const struct reg_val init_seq[] = { {0x0f90, 0x00688980}, {0x0696, 0x00000003}, {0x07fa, 0x0050100f}, @@ -939,7 +947,7 @@ out_unlock: static int vsc85xx_eee_init_seq_set(struct phy_device *phydev) { - const struct reg_val init_eee[] = { + static const struct reg_val init_eee[] = { {0x0f82, 0x0012b00a}, {0x1686, 0x00000004}, {0x168c, 0x00d2c46f}, @@ -1224,7 +1232,7 @@ out: /* bus->mdio_lock should be locked when using this function */ static int vsc8574_config_pre_init(struct phy_device *phydev) { - const struct reg_val pre_init1[] = { + static const struct reg_val pre_init1[] = { {0x0fae, 0x000401bd}, {0x0fac, 0x000f000f}, {0x17a0, 0x00a0f147}, @@ -1272,7 +1280,7 @@ static int vsc8574_config_pre_init(struct phy_device *phydev) {0x0fee, 0x0004a6a1}, {0x0ffe, 0x00b01807}, }; - const struct reg_val pre_init2[] = { + static const struct reg_val pre_init2[] = { {0x0486, 0x0008a518}, {0x0488, 0x006dc696}, {0x048a, 0x00000912}, @@ -1427,7 +1435,7 @@ out: /* bus->mdio_lock should be locked when using this function */ static int vsc8584_config_pre_init(struct phy_device *phydev) { - const struct reg_val pre_init1[] = { + static const struct reg_val pre_init1[] = { {0x07fa, 0x0050100f}, {0x1688, 0x00049f81}, {0x0f90, 0x00688980}, @@ -1451,7 +1459,7 @@ static int vsc8584_config_pre_init(struct phy_device *phydev) {0x16b2, 0x00007000}, {0x16b4, 0x00000814}, }; - const struct reg_val pre_init2[] = { + static const struct reg_val pre_init2[] = { {0x0486, 0x0008a518}, {0x0488, 0x006dc696}, {0x048a, 0x00000912}, @@ -1595,6 +1603,9 @@ static bool vsc8584_is_pkg_init(struct phy_device *phydev, bool reversed) else addr = vsc8531->base_addr + i; + if (!map[addr]) + continue; + phy = container_of(map[addr], struct phy_device, mdio); if ((phy->phy_id & phydev->drv->phy_id_mask) != @@ -1647,14 +1658,29 @@ static int vsc8584_config_init(struct phy_device *phydev) * in this pre-init function. */ if (!vsc8584_is_pkg_init(phydev, val & PHY_ADDR_REVERSED ? 1 : 0)) { - if ((phydev->phy_id & phydev->drv->phy_id_mask) == - (PHY_ID_VSC8574 & phydev->drv->phy_id_mask)) + /* The following switch statement assumes that the lowest + * nibble of the phy_id_mask is always 0. This works because + * the lowest nibble of the PHY_ID's below are also 0. + */ + WARN_ON(phydev->drv->phy_id_mask & 0xf); + + switch (phydev->phy_id & phydev->drv->phy_id_mask) { + case PHY_ID_VSC8504: + case PHY_ID_VSC8552: + case PHY_ID_VSC8572: + case PHY_ID_VSC8574: ret = vsc8574_config_pre_init(phydev); - else if ((phydev->phy_id & phydev->drv->phy_id_mask) == - (PHY_ID_VSC8584 & phydev->drv->phy_id_mask)) + break; + case PHY_ID_VSC856X: + case PHY_ID_VSC8575: + case PHY_ID_VSC8582: + case PHY_ID_VSC8584: ret = vsc8584_config_pre_init(phydev); - else + break; + default: ret = -EINVAL; + break; + } if (ret) goto err; @@ -1786,7 +1812,7 @@ static int vsc8514_config_pre_init(struct phy_device *phydev) * values to handle hardware performance of PHY. They * are set at Power-On state and remain until PHY Reset. */ - const struct reg_val pre_init1[] = { + static const struct reg_val pre_init1[] = { {0x0f90, 0x00688980}, {0x0786, 0x00000003}, {0x07fa, 0x0050100f}, @@ -2322,6 +2348,32 @@ static int vsc85xx_probe(struct phy_device *phydev) /* Microsemi VSC85xx PHYs */ static struct phy_driver vsc85xx_driver[] = { { + .phy_id = PHY_ID_VSC8504, + .name = "Microsemi GE VSC8504 SyncE", + .phy_id_mask = 0xfffffff0, + /* PHY_GBIT_FEATURES */ + .soft_reset = &genphy_soft_reset, + .config_init = &vsc8584_config_init, + .config_aneg = &vsc85xx_config_aneg, + .aneg_done = &genphy_aneg_done, + .read_status = &vsc85xx_read_status, + .ack_interrupt = &vsc85xx_ack_interrupt, + .config_intr = &vsc85xx_config_intr, + .did_interrupt = &vsc8584_did_interrupt, + .suspend = &genphy_suspend, + .resume = &genphy_resume, + .probe = &vsc8574_probe, + .set_wol = &vsc85xx_wol_set, + .get_wol = &vsc85xx_wol_get, + .get_tunable = &vsc85xx_get_tunable, + .set_tunable = &vsc85xx_set_tunable, + .read_page = &vsc85xx_phy_read_page, + .write_page = &vsc85xx_phy_write_page, + .get_sset_count = &vsc85xx_get_sset_count, + .get_strings = &vsc85xx_get_strings, + .get_stats = &vsc85xx_get_stats, +}, +{ .phy_id = PHY_ID_VSC8514, .name = "Microsemi GE VSC8514 SyncE", .phy_id_mask = 0xfffffff0, @@ -2445,6 +2497,82 @@ static struct phy_driver vsc85xx_driver[] = { .get_stats = &vsc85xx_get_stats, }, { + .phy_id = PHY_ID_VSC8552, + .name = "Microsemi GE VSC8552 SyncE", + .phy_id_mask = 0xfffffff0, + /* PHY_GBIT_FEATURES */ + .soft_reset = &genphy_soft_reset, + .config_init = &vsc8584_config_init, + .config_aneg = &vsc85xx_config_aneg, + .aneg_done = &genphy_aneg_done, + .read_status = &vsc85xx_read_status, + .ack_interrupt = &vsc85xx_ack_interrupt, + .config_intr = &vsc85xx_config_intr, + .did_interrupt = &vsc8584_did_interrupt, + .suspend = &genphy_suspend, + .resume = &genphy_resume, + .probe = &vsc8574_probe, + .set_wol = &vsc85xx_wol_set, + .get_wol = &vsc85xx_wol_get, + .get_tunable = &vsc85xx_get_tunable, + .set_tunable = &vsc85xx_set_tunable, + .read_page = &vsc85xx_phy_read_page, + .write_page = &vsc85xx_phy_write_page, + .get_sset_count = &vsc85xx_get_sset_count, + .get_strings = &vsc85xx_get_strings, + .get_stats = &vsc85xx_get_stats, +}, +{ + .phy_id = PHY_ID_VSC856X, + .name = "Microsemi GE VSC856X SyncE", + .phy_id_mask = 0xfffffff0, + /* PHY_GBIT_FEATURES */ + .soft_reset = &genphy_soft_reset, + .config_init = &vsc8584_config_init, + .config_aneg = &vsc85xx_config_aneg, + .aneg_done = &genphy_aneg_done, + .read_status = &vsc85xx_read_status, + .ack_interrupt = &vsc85xx_ack_interrupt, + .config_intr = &vsc85xx_config_intr, + .did_interrupt = &vsc8584_did_interrupt, + .suspend = &genphy_suspend, + .resume = &genphy_resume, + .probe = &vsc8584_probe, + .get_tunable = &vsc85xx_get_tunable, + .set_tunable = &vsc85xx_set_tunable, + .read_page = &vsc85xx_phy_read_page, + .write_page = &vsc85xx_phy_write_page, + .get_sset_count = &vsc85xx_get_sset_count, + .get_strings = &vsc85xx_get_strings, + .get_stats = &vsc85xx_get_stats, +}, +{ + .phy_id = PHY_ID_VSC8572, + .name = "Microsemi GE VSC8572 SyncE", + .phy_id_mask = 0xfffffff0, + /* PHY_GBIT_FEATURES */ + .soft_reset = &genphy_soft_reset, + .config_init = &vsc8584_config_init, + .config_aneg = &vsc85xx_config_aneg, + .aneg_done = &genphy_aneg_done, + .read_status = &vsc85xx_read_status, + .ack_interrupt = &vsc85xx_ack_interrupt, + .config_intr = &vsc85xx_config_intr, + .did_interrupt = &vsc8584_did_interrupt, + .suspend = &genphy_suspend, + .resume = &genphy_resume, + .probe = &vsc8574_probe, + .set_wol = &vsc85xx_wol_set, + .get_wol = &vsc85xx_wol_get, + .get_tunable = &vsc85xx_get_tunable, + .set_tunable = &vsc85xx_set_tunable, + .read_page = &vsc85xx_phy_read_page, + .write_page = &vsc85xx_phy_write_page, + .get_sset_count = &vsc85xx_get_sset_count, + .get_strings = &vsc85xx_get_strings, + .get_stats = &vsc85xx_get_stats, +}, +{ .phy_id = PHY_ID_VSC8574, .name = "Microsemi GE VSC8574 SyncE", .phy_id_mask = 0xfffffff0, @@ -2471,6 +2599,54 @@ static struct phy_driver vsc85xx_driver[] = { .get_stats = &vsc85xx_get_stats, }, { + .phy_id = PHY_ID_VSC8575, + .name = "Microsemi GE VSC8575 SyncE", + .phy_id_mask = 0xfffffff0, + /* PHY_GBIT_FEATURES */ + .soft_reset = &genphy_soft_reset, + .config_init = &vsc8584_config_init, + .config_aneg = &vsc85xx_config_aneg, + .aneg_done = &genphy_aneg_done, + .read_status = &vsc85xx_read_status, + .ack_interrupt = &vsc85xx_ack_interrupt, + .config_intr = &vsc85xx_config_intr, + .did_interrupt = &vsc8584_did_interrupt, + .suspend = &genphy_suspend, + .resume = &genphy_resume, + .probe = &vsc8584_probe, + .get_tunable = &vsc85xx_get_tunable, + .set_tunable = &vsc85xx_set_tunable, + .read_page = &vsc85xx_phy_read_page, + .write_page = &vsc85xx_phy_write_page, + .get_sset_count = &vsc85xx_get_sset_count, + .get_strings = &vsc85xx_get_strings, + .get_stats = &vsc85xx_get_stats, +}, +{ + .phy_id = PHY_ID_VSC8582, + .name = "Microsemi GE VSC8582 SyncE", + .phy_id_mask = 0xfffffff0, + /* PHY_GBIT_FEATURES */ + .soft_reset = &genphy_soft_reset, + .config_init = &vsc8584_config_init, + .config_aneg = &vsc85xx_config_aneg, + .aneg_done = &genphy_aneg_done, + .read_status = &vsc85xx_read_status, + .ack_interrupt = &vsc85xx_ack_interrupt, + .config_intr = &vsc85xx_config_intr, + .did_interrupt = &vsc8584_did_interrupt, + .suspend = &genphy_suspend, + .resume = &genphy_resume, + .probe = &vsc8584_probe, + .get_tunable = &vsc85xx_get_tunable, + .set_tunable = &vsc85xx_set_tunable, + .read_page = &vsc85xx_phy_read_page, + .write_page = &vsc85xx_phy_write_page, + .get_sset_count = &vsc85xx_get_sset_count, + .get_strings = &vsc85xx_get_strings, + .get_stats = &vsc85xx_get_stats, +}, +{ .phy_id = PHY_ID_VSC8584, .name = "Microsemi GE VSC8584 SyncE", .phy_id_mask = 0xfffffff0, @@ -2500,12 +2676,18 @@ static struct phy_driver vsc85xx_driver[] = { module_phy_driver(vsc85xx_driver); static struct mdio_device_id __maybe_unused vsc85xx_tbl[] = { + { PHY_ID_VSC8504, 0xfffffff0, }, { PHY_ID_VSC8514, 0xfffffff0, }, { PHY_ID_VSC8530, 0xfffffff0, }, { PHY_ID_VSC8531, 0xfffffff0, }, { PHY_ID_VSC8540, 0xfffffff0, }, { PHY_ID_VSC8541, 0xfffffff0, }, + { PHY_ID_VSC8552, 0xfffffff0, }, + { PHY_ID_VSC856X, 0xfffffff0, }, + { PHY_ID_VSC8572, 0xfffffff0, }, { PHY_ID_VSC8574, 0xfffffff0, }, + { PHY_ID_VSC8575, 0xfffffff0, }, + { PHY_ID_VSC8582, 0xfffffff0, }, { PHY_ID_VSC8584, 0xfffffff0, }, { } }; diff --git a/drivers/net/phy/phy-core.c b/drivers/net/phy/phy-core.c index 9412669b579c..769a076514b0 100644 --- a/drivers/net/phy/phy-core.c +++ b/drivers/net/phy/phy-core.c @@ -8,7 +8,7 @@ const char *phy_speed_to_str(int speed) { - BUILD_BUG_ON_MSG(__ETHTOOL_LINK_MODE_MASK_NBITS != 69, + BUILD_BUG_ON_MSG(__ETHTOOL_LINK_MODE_MASK_NBITS != 74, "Enum ethtool_link_mode_bit_indices and phylib are out of sync. " "If a speed or mode has been added please update phy_speed_to_str " "and the PHY settings array.\n"); @@ -42,6 +42,8 @@ const char *phy_speed_to_str(int speed) return "100Gbps"; case SPEED_200000: return "200Gbps"; + case SPEED_400000: + return "400Gbps"; case SPEED_UNKNOWN: return "Unknown"; default: @@ -70,6 +72,12 @@ EXPORT_SYMBOL_GPL(phy_duplex_to_str); .bit = ETHTOOL_LINK_MODE_ ## b ## _BIT} static const struct phy_setting settings[] = { + /* 400G */ + PHY_SETTING( 400000, FULL, 400000baseCR8_Full ), + PHY_SETTING( 400000, FULL, 400000baseKR8_Full ), + PHY_SETTING( 400000, FULL, 400000baseLR8_ER8_FR8_Full ), + PHY_SETTING( 400000, FULL, 400000baseDR8_Full ), + PHY_SETTING( 400000, FULL, 400000baseSR8_Full ), /* 200G */ PHY_SETTING( 200000, FULL, 200000baseCR4_Full ), PHY_SETTING( 200000, FULL, 200000baseKR4_Full ), @@ -411,9 +419,9 @@ int phy_read_mmd(struct phy_device *phydev, int devad, u32 regnum) { int ret; - mutex_lock(&phydev->mdio.bus->mdio_lock); + phy_lock_mdio_bus(phydev); ret = __phy_read_mmd(phydev, devad, regnum); - mutex_unlock(&phydev->mdio.bus->mdio_lock); + phy_unlock_mdio_bus(phydev); return ret; } @@ -472,9 +480,9 @@ int phy_write_mmd(struct phy_device *phydev, int devad, u32 regnum, u16 val) { int ret; - mutex_lock(&phydev->mdio.bus->mdio_lock); + phy_lock_mdio_bus(phydev); ret = __phy_write_mmd(phydev, devad, regnum, val); - mutex_unlock(&phydev->mdio.bus->mdio_lock); + phy_unlock_mdio_bus(phydev); return ret; } @@ -528,9 +536,9 @@ int phy_modify_changed(struct phy_device *phydev, u32 regnum, u16 mask, u16 set) { int ret; - mutex_lock(&phydev->mdio.bus->mdio_lock); + phy_lock_mdio_bus(phydev); ret = __phy_modify_changed(phydev, regnum, mask, set); - mutex_unlock(&phydev->mdio.bus->mdio_lock); + phy_unlock_mdio_bus(phydev); return ret; } @@ -572,9 +580,9 @@ int phy_modify(struct phy_device *phydev, u32 regnum, u16 mask, u16 set) { int ret; - mutex_lock(&phydev->mdio.bus->mdio_lock); + phy_lock_mdio_bus(phydev); ret = __phy_modify(phydev, regnum, mask, set); - mutex_unlock(&phydev->mdio.bus->mdio_lock); + phy_unlock_mdio_bus(phydev); return ret; } @@ -631,9 +639,9 @@ int phy_modify_mmd_changed(struct phy_device *phydev, int devad, u32 regnum, { int ret; - mutex_lock(&phydev->mdio.bus->mdio_lock); + phy_lock_mdio_bus(phydev); ret = __phy_modify_mmd_changed(phydev, devad, regnum, mask, set); - mutex_unlock(&phydev->mdio.bus->mdio_lock); + phy_unlock_mdio_bus(phydev); return ret; } @@ -679,9 +687,9 @@ int phy_modify_mmd(struct phy_device *phydev, int devad, u32 regnum, { int ret; - mutex_lock(&phydev->mdio.bus->mdio_lock); + phy_lock_mdio_bus(phydev); ret = __phy_modify_mmd(phydev, devad, regnum, mask, set); - mutex_unlock(&phydev->mdio.bus->mdio_lock); + phy_unlock_mdio_bus(phydev); return ret; } @@ -689,11 +697,17 @@ EXPORT_SYMBOL_GPL(phy_modify_mmd); static int __phy_read_page(struct phy_device *phydev) { + if (WARN_ONCE(!phydev->drv->read_page, "read_page callback not available, PHY driver not loaded?\n")) + return -EOPNOTSUPP; + return phydev->drv->read_page(phydev); } static int __phy_write_page(struct phy_device *phydev, int page) { + if (WARN_ONCE(!phydev->drv->write_page, "write_page callback not available, PHY driver not loaded?\n")) + return -EOPNOTSUPP; + return phydev->drv->write_page(phydev, page); } @@ -707,7 +721,7 @@ static int __phy_write_page(struct phy_device *phydev, int page) */ int phy_save_page(struct phy_device *phydev) { - mutex_lock(&phydev->mdio.bus->mdio_lock); + phy_lock_mdio_bus(phydev); return __phy_read_page(phydev); } EXPORT_SYMBOL_GPL(phy_save_page); @@ -774,7 +788,7 @@ int phy_restore_page(struct phy_device *phydev, int oldpage, int ret) ret = oldpage; } - mutex_unlock(&phydev->mdio.bus->mdio_lock); + phy_unlock_mdio_bus(phydev); return ret; } diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c index 105d389b58e7..80be4d691e5b 100644 --- a/drivers/net/phy/phy.c +++ b/drivers/net/phy/phy.c @@ -23,6 +23,7 @@ #include <linux/ethtool.h> #include <linux/phy.h> #include <linux/phy_led_triggers.h> +#include <linux/sfp.h> #include <linux/workqueue.h> #include <linux/mdio.h> #include <linux/io.h> @@ -252,66 +253,6 @@ static void phy_sanitize_settings(struct phy_device *phydev) } } -/** - * phy_ethtool_sset - generic ethtool sset function, handles all the details - * @phydev: target phy_device struct - * @cmd: ethtool_cmd - * - * A few notes about parameter checking: - * - * - We don't set port or transceiver, so we don't care what they - * were set to. - * - phy_start_aneg() will make sure forced settings are sane, and - * choose the next best ones from the ones selected, so we don't - * care if ethtool tries to give us bad values. - */ -int phy_ethtool_sset(struct phy_device *phydev, struct ethtool_cmd *cmd) -{ - __ETHTOOL_DECLARE_LINK_MODE_MASK(advertising); - u32 speed = ethtool_cmd_speed(cmd); - - if (cmd->phy_address != phydev->mdio.addr) - return -EINVAL; - - /* We make sure that we don't pass unsupported values in to the PHY */ - ethtool_convert_legacy_u32_to_link_mode(advertising, cmd->advertising); - linkmode_and(advertising, advertising, phydev->supported); - - /* Verify the settings we care about. */ - if (cmd->autoneg != AUTONEG_ENABLE && cmd->autoneg != AUTONEG_DISABLE) - return -EINVAL; - - if (cmd->autoneg == AUTONEG_ENABLE && cmd->advertising == 0) - return -EINVAL; - - if (cmd->autoneg == AUTONEG_DISABLE && - ((speed != SPEED_1000 && - speed != SPEED_100 && - speed != SPEED_10) || - (cmd->duplex != DUPLEX_HALF && - cmd->duplex != DUPLEX_FULL))) - return -EINVAL; - - phydev->autoneg = cmd->autoneg; - - phydev->speed = speed; - - linkmode_copy(phydev->advertising, advertising); - - linkmode_mod_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, - phydev->advertising, AUTONEG_ENABLE == cmd->autoneg); - - phydev->duplex = cmd->duplex; - - phydev->mdix_ctrl = cmd->eth_tp_mdix_ctrl; - - /* Restart the PHY */ - phy_start_aneg(phydev); - - return 0; -} -EXPORT_SYMBOL(phy_ethtool_sset); - int phy_ethtool_ksettings_set(struct phy_device *phydev, const struct ethtool_link_ksettings *cmd) { @@ -841,6 +782,9 @@ void phy_stop(struct phy_device *phydev) mutex_lock(&phydev->lock); + if (phydev->sfp_bus) + sfp_upstream_stop(phydev->sfp_bus); + phydev->state = PHY_HALTED; mutex_unlock(&phydev->lock); @@ -875,6 +819,9 @@ void phy_start(struct phy_device *phydev) goto out; } + if (phydev->sfp_bus) + sfp_upstream_start(phydev->sfp_bus); + /* if phy was suspended, bring the physical link up again */ __phy_resume(phydev); diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c index adb66a2fae18..0887ed2bb050 100644 --- a/drivers/net/phy/phy_device.c +++ b/drivers/net/phy/phy_device.c @@ -27,6 +27,7 @@ #include <linux/bitmap.h> #include <linux/phy.h> #include <linux/phy_led_triggers.h> +#include <linux/sfp.h> #include <linux/mdio.h> #include <linux/io.h> #include <linux/uaccess.h> @@ -488,7 +489,7 @@ static int phy_bus_match(struct device *dev, struct device_driver *drv) if (phydev->is_c45) { for (i = 1; i < num_ids; i++) { - if (!(phydev->c45_ids.devices_in_package & (1 << i))) + if (phydev->c45_ids.device_ids[i] == 0xffffffff) continue; if ((phydrv->phy_id & phydrv->phy_id_mask) == @@ -596,8 +597,8 @@ struct phy_device *phy_device_create(struct mii_bus *bus, int addr, int phy_id, mdiodev->device_free = phy_mdio_device_free; mdiodev->device_remove = phy_mdio_device_remove; - dev->speed = 0; - dev->duplex = -1; + dev->speed = SPEED_UNKNOWN; + dev->duplex = DUPLEX_UNKNOWN; dev->pause = 0; dev->asym_pause = 0; dev->link = 0; @@ -632,7 +633,7 @@ struct phy_device *phy_device_create(struct mii_bus *bus, int addr, int phy_id, int i; for (i = 1; i < num_ids; i++) { - if (!(c45_ids->devices_in_package & (1 << i))) + if (c45_ids->device_ids[i] == 0xffffffff) continue; ret = phy_request_driver_module(dev, @@ -812,10 +813,13 @@ static int get_phy_id(struct mii_bus *bus, int addr, u32 *phy_id, */ struct phy_device *get_phy_device(struct mii_bus *bus, int addr, bool is_c45) { - struct phy_c45_device_ids c45_ids = {0}; + struct phy_c45_device_ids c45_ids; u32 phy_id = 0; int r; + c45_ids.devices_in_package = 0; + memset(c45_ids.device_ids, 0xff, sizeof(c45_ids.device_ids)); + r = get_phy_id(bus, addr, &phy_id, is_c45, &c45_ids); if (r) return ERR_PTR(r); @@ -1175,6 +1179,65 @@ phy_standalone_show(struct device *dev, struct device_attribute *attr, static DEVICE_ATTR_RO(phy_standalone); /** + * phy_sfp_attach - attach the SFP bus to the PHY upstream network device + * @upstream: pointer to the phy device + * @bus: sfp bus representing cage being attached + * + * This is used to fill in the sfp_upstream_ops .attach member. + */ +void phy_sfp_attach(void *upstream, struct sfp_bus *bus) +{ + struct phy_device *phydev = upstream; + + if (phydev->attached_dev) + phydev->attached_dev->sfp_bus = bus; + phydev->sfp_bus_attached = true; +} +EXPORT_SYMBOL(phy_sfp_attach); + +/** + * phy_sfp_detach - detach the SFP bus from the PHY upstream network device + * @upstream: pointer to the phy device + * @bus: sfp bus representing cage being attached + * + * This is used to fill in the sfp_upstream_ops .detach member. + */ +void phy_sfp_detach(void *upstream, struct sfp_bus *bus) +{ + struct phy_device *phydev = upstream; + + if (phydev->attached_dev) + phydev->attached_dev->sfp_bus = NULL; + phydev->sfp_bus_attached = false; +} +EXPORT_SYMBOL(phy_sfp_detach); + +/** + * phy_sfp_probe - probe for a SFP cage attached to this PHY device + * @phydev: Pointer to phy_device + * @ops: SFP's upstream operations + */ +int phy_sfp_probe(struct phy_device *phydev, + const struct sfp_upstream_ops *ops) +{ + struct sfp_bus *bus; + int ret; + + if (phydev->mdio.dev.fwnode) { + bus = sfp_bus_find_fwnode(phydev->mdio.dev.fwnode); + if (IS_ERR(bus)) + return PTR_ERR(bus); + + phydev->sfp_bus = bus; + + ret = sfp_bus_add_upstream(bus, phydev, ops); + sfp_bus_put(bus); + } + return 0; +} +EXPORT_SYMBOL(phy_sfp_probe); + +/** * phy_attach_direct - attach a network device to a given PHY device pointer * @dev: network device to attach * @phydev: Pointer to phy_device to attach @@ -1249,6 +1312,9 @@ int phy_attach_direct(struct net_device *dev, struct phy_device *phydev, if (dev) { phydev->attached_dev = dev; dev->phydev = phydev; + + if (phydev->sfp_bus_attached) + dev->sfp_bus = phydev->sfp_bus; } /* Some Ethernet drivers try to connect to a PHY device before @@ -1270,7 +1336,7 @@ int phy_attach_direct(struct net_device *dev, struct phy_device *phydev, phydev_err(phydev, "error creating 'phy_standalone' sysfs entry\n"); } - phydev->dev_flags = flags; + phydev->dev_flags |= flags; phydev->interface = interface; @@ -1608,6 +1674,40 @@ static int genphy_config_advert(struct phy_device *phydev) } /** + * genphy_c37_config_advert - sanitize and advertise auto-negotiation parameters + * @phydev: target phy_device struct + * + * Description: Writes MII_ADVERTISE with the appropriate values, + * after sanitizing the values to make sure we only advertise + * what is supported. Returns < 0 on error, 0 if the PHY's advertisement + * hasn't changed, and > 0 if it has changed. This function is intended + * for Clause 37 1000Base-X mode. + */ +static int genphy_c37_config_advert(struct phy_device *phydev) +{ + u16 adv = 0; + + /* Only allow advertising what this PHY supports */ + linkmode_and(phydev->advertising, phydev->advertising, + phydev->supported); + + if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseX_Full_BIT, + phydev->advertising)) + adv |= ADVERTISE_1000XFULL; + if (linkmode_test_bit(ETHTOOL_LINK_MODE_Pause_BIT, + phydev->advertising)) + adv |= ADVERTISE_1000XPAUSE; + if (linkmode_test_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, + phydev->advertising)) + adv |= ADVERTISE_1000XPSE_ASYM; + + return phy_modify_changed(phydev, MII_ADVERTISE, + ADVERTISE_1000XFULL | ADVERTISE_1000XPAUSE | + ADVERTISE_1000XHALF | ADVERTISE_1000XPSE_ASYM, + adv); +} + +/** * genphy_config_eee_advert - disable unwanted eee mode advertisement * @phydev: target phy_device struct * @@ -1716,6 +1816,54 @@ int __genphy_config_aneg(struct phy_device *phydev, bool changed) EXPORT_SYMBOL(__genphy_config_aneg); /** + * genphy_c37_config_aneg - restart auto-negotiation or write BMCR + * @phydev: target phy_device struct + * + * Description: If auto-negotiation is enabled, we configure the + * advertising, and then restart auto-negotiation. If it is not + * enabled, then we write the BMCR. This function is intended + * for use with Clause 37 1000Base-X mode. + */ +int genphy_c37_config_aneg(struct phy_device *phydev) +{ + int err, changed; + + if (phydev->autoneg != AUTONEG_ENABLE) + return genphy_setup_forced(phydev); + + err = phy_modify(phydev, MII_BMCR, BMCR_SPEED1000 | BMCR_SPEED100, + BMCR_SPEED1000); + if (err) + return err; + + changed = genphy_c37_config_advert(phydev); + if (changed < 0) /* error */ + return changed; + + if (!changed) { + /* Advertisement hasn't changed, but maybe aneg was never on to + * begin with? Or maybe phy was isolated? + */ + int ctl = phy_read(phydev, MII_BMCR); + + if (ctl < 0) + return ctl; + + if (!(ctl & BMCR_ANENABLE) || (ctl & BMCR_ISOLATE)) + changed = 1; /* do restart aneg */ + } + + /* Only restart aneg if we are advertising something different + * than we were before. + */ + if (changed > 0) + return genphy_restart_aneg(phydev); + + return 0; +} +EXPORT_SYMBOL(genphy_c37_config_aneg); + +/** * genphy_aneg_done - return auto-negotiation status * @phydev: target phy_device struct * @@ -1887,6 +2035,63 @@ int genphy_read_status(struct phy_device *phydev) EXPORT_SYMBOL(genphy_read_status); /** + * genphy_c37_read_status - check the link status and update current link state + * @phydev: target phy_device struct + * + * Description: Check the link, then figure out the current state + * by comparing what we advertise with what the link partner + * advertises. This function is for Clause 37 1000Base-X mode. + */ +int genphy_c37_read_status(struct phy_device *phydev) +{ + int lpa, err, old_link = phydev->link; + + /* Update the link, but return if there was an error */ + err = genphy_update_link(phydev); + if (err) + return err; + + /* why bother the PHY if nothing can have changed */ + if (phydev->autoneg == AUTONEG_ENABLE && old_link && phydev->link) + return 0; + + phydev->duplex = DUPLEX_UNKNOWN; + phydev->pause = 0; + phydev->asym_pause = 0; + + if (phydev->autoneg == AUTONEG_ENABLE && phydev->autoneg_complete) { + lpa = phy_read(phydev, MII_LPA); + if (lpa < 0) + return lpa; + + linkmode_mod_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, + phydev->lp_advertising, lpa & LPA_LPACK); + linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseX_Full_BIT, + phydev->lp_advertising, lpa & LPA_1000XFULL); + linkmode_mod_bit(ETHTOOL_LINK_MODE_Pause_BIT, + phydev->lp_advertising, lpa & LPA_1000XPAUSE); + linkmode_mod_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, + phydev->lp_advertising, + lpa & LPA_1000XPAUSE_ASYM); + + phy_resolve_aneg_linkmode(phydev); + } else if (phydev->autoneg == AUTONEG_DISABLE) { + int bmcr = phy_read(phydev, MII_BMCR); + + if (bmcr < 0) + return bmcr; + + if (bmcr & BMCR_FULLDPLX) + phydev->duplex = DUPLEX_FULL; + else + phydev->duplex = DUPLEX_HALF; + } + + return 0; +} +EXPORT_SYMBOL(genphy_c37_read_status); + +/** * genphy_soft_reset - software reset the PHY via BMCR_RESET bit * @phydev: target phy_device struct * @@ -2279,6 +2484,9 @@ static int phy_remove(struct device *dev) phydev->state = PHY_DOWN; mutex_unlock(&phydev->lock); + sfp_bus_del_upstream(phydev->sfp_bus); + phydev->sfp_bus = NULL; + if (phydev->drv && phydev->drv->remove) { phydev->drv->remove(phydev); diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index a578f7ebf715..9a616d6bc4eb 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -133,9 +133,7 @@ static int phylink_is_empty_linkmode(const unsigned long *linkmode) phylink_set(tmp, Pause); phylink_set(tmp, Asym_Pause); - bitmap_andnot(tmp, linkmode, tmp, __ETHTOOL_LINK_MODE_MASK_NBITS); - - return linkmode_empty(tmp); + return linkmode_subset(linkmode, tmp); } static const char *phylink_an_mode_str(unsigned int mode) @@ -359,9 +357,9 @@ static void phylink_mac_an_restart(struct phylink *pl) pl->ops->mac_an_restart(pl->config); } -static int phylink_get_mac_state(struct phylink *pl, struct phylink_link_state *state) +static void phylink_mac_pcs_get_state(struct phylink *pl, + struct phylink_link_state *state) { - linkmode_copy(state->advertising, pl->link_config.advertising); linkmode_zero(state->lp_advertising); state->interface = pl->link_config.interface; @@ -372,7 +370,7 @@ static int phylink_get_mac_state(struct phylink *pl, struct phylink_link_state * state->an_complete = 0; state->link = 1; - return pl->ops->mac_link_state(pl->config, state); + pl->ops->mac_pcs_get_state(pl->config, state); } /* The fixed state is... fixed except for the link state, @@ -495,7 +493,7 @@ static void phylink_resolve(struct work_struct *w) break; case MLO_AN_INBAND: - phylink_get_mac_state(pl, &link_state); + phylink_mac_pcs_get_state(pl, &link_state); /* If we have a phy, the "up" state is the union of * both the PHY and the MAC */ @@ -566,28 +564,22 @@ static const struct sfp_upstream_ops sfp_phylink_ops; static int phylink_register_sfp(struct phylink *pl, struct fwnode_handle *fwnode) { - struct fwnode_reference_args ref; + struct sfp_bus *bus; int ret; - if (!fwnode) - return 0; - - ret = fwnode_property_get_reference_args(fwnode, "sfp", NULL, - 0, 0, &ref); - if (ret < 0) { - if (ret == -ENOENT) - return 0; - - phylink_err(pl, "unable to parse \"sfp\" node: %d\n", - ret); + bus = sfp_bus_find_fwnode(fwnode); + if (IS_ERR(bus)) { + ret = PTR_ERR(bus); + phylink_err(pl, "unable to attach SFP bus: %d\n", ret); return ret; } - pl->sfp_bus = sfp_register_upstream(ref.fwnode, pl, &sfp_phylink_ops); - if (!pl->sfp_bus) - return -ENOMEM; + pl->sfp_bus = bus; - return 0; + ret = sfp_bus_add_upstream(bus, pl, &sfp_phylink_ops); + sfp_bus_put(bus); + + return ret; } /** @@ -601,6 +593,8 @@ static int phylink_register_sfp(struct phylink *pl, * Create a new phylink instance, and parse the link parameters found in @np. * This will parse in-band modes, fixed-link or SFP configuration. * + * Note: the rtnl lock must not be held when calling this function. + * * Returns a pointer to a &struct phylink, or an error-pointer value. Users * must use IS_ERR() to check for errors from this function. */ @@ -678,11 +672,12 @@ EXPORT_SYMBOL_GPL(phylink_create); * * Destroy a phylink instance. Any PHY that has been attached must have been * cleaned up via phylink_disconnect_phy() prior to calling this function. + * + * Note: the rtnl lock must not be held when calling this function. */ void phylink_destroy(struct phylink *pl) { - if (pl->sfp_bus) - sfp_unregister_upstream(pl->sfp_bus); + sfp_bus_del_upstream(pl->sfp_bus); if (pl->link_gpio) gpiod_put(pl->link_gpio); @@ -722,11 +717,6 @@ static int phylink_bringup_phy(struct phylink *pl, struct phy_device *phy) __ETHTOOL_DECLARE_LINK_MODE_MASK(supported); int ret; - memset(&config, 0, sizeof(config)); - linkmode_copy(supported, phy->supported); - linkmode_copy(config.advertising, phy->advertising); - config.interface = pl->link_config.interface; - /* * This is the new way of dealing with flow control for PHYs, * as described by Timur Tabi in commit 529ed1275263 ("net: phy: @@ -734,10 +724,12 @@ static int phylink_bringup_phy(struct phylink *pl, struct phy_device *phy) * using our validate call to the MAC, we rely upon the MAC * clearing the bits from both supported and advertising fields. */ - if (phylink_test(supported, Pause)) - phylink_set(config.advertising, Pause); - if (phylink_test(supported, Asym_Pause)) - phylink_set(config.advertising, Asym_Pause); + phy_support_asym_pause(phy); + + memset(&config, 0, sizeof(config)); + linkmode_copy(supported, phy->supported); + linkmode_copy(config.advertising, phy->advertising); + config.interface = pl->link_config.interface; ret = phylink_validate(pl, supported, &config); if (ret) @@ -1150,7 +1142,7 @@ int phylink_ethtool_ksettings_get(struct phylink *pl, if (pl->phydev) break; - phylink_get_mac_state(pl, &link_state); + phylink_mac_pcs_get_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 @@ -1254,7 +1246,13 @@ int phylink_ethtool_ksettings_set(struct phylink *pl, 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)) { + /* If we have a PHY, phylib will call our link state function if the + * mode has changed, which will trigger a resolve and update the MAC + * configuration. For a fixed link, this isn't able to change any + * parameters, which just leaves inband mode. + */ + if (pl->link_an_mode == MLO_AN_INBAND && + !test_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state)) { phylink_mac_config(pl, &pl->link_config); phylink_mac_an_restart(pl); } @@ -1334,15 +1332,16 @@ int phylink_ethtool_set_pauseparam(struct phylink *pl, if (pause->tx_pause) config->pause |= MLO_PAUSE_TX; - if (!test_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state)) { + /* If we have a PHY, phylib will call our link state function if the + * mode has changed, which will trigger a resolve and update the MAC + * configuration. + */ + if (pl->phydev) { + phy_set_asym_pause(pl->phydev, pause->rx_pause, + pause->tx_pause); + } else 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 */ - if (pl->netdev) - 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); @@ -1562,10 +1561,7 @@ static int phylink_mii_read(struct phylink *pl, unsigned int phy_id, case MLO_AN_INBAND: if (phy_id == 0) { - val = phylink_get_mac_state(pl, &state); - if (val < 0) - return val; - + phylink_mac_pcs_get_state(pl, &state); val = phylink_mii_emul_read(reg, &state); } break; @@ -1744,8 +1740,7 @@ static int phylink_sfp_module_insert(void *upstream, if (phy_interface_mode_is_8023z(iface) && pl->phydev) return -EINVAL; - changed = !bitmap_equal(pl->supported, support, - __ETHTOOL_LINK_MODE_MASK_NBITS); + changed = !linkmode_equal(pl->supported, support); if (changed) { linkmode_copy(pl->supported, support); linkmode_copy(pl->link_config.advertising, config.advertising); diff --git a/drivers/net/phy/sfp-bus.c b/drivers/net/phy/sfp-bus.c index b23fc41896ef..5a72093ab6e7 100644 --- a/drivers/net/phy/sfp-bus.c +++ b/drivers/net/phy/sfp-bus.c @@ -4,11 +4,18 @@ #include <linux/list.h> #include <linux/mutex.h> #include <linux/phylink.h> +#include <linux/property.h> #include <linux/rtnetlink.h> #include <linux/slab.h> #include "sfp.h" +struct sfp_quirk { + const char *vendor; + const char *part; + void (*modes)(const struct sfp_eeprom_id *id, unsigned long *modes); +}; + /** * struct sfp_bus - internal representation of a sfp bus */ @@ -21,6 +28,7 @@ struct sfp_bus { const struct sfp_socket_ops *socket_ops; struct device *sfp_dev; struct sfp *sfp; + const struct sfp_quirk *sfp_quirk; const struct sfp_upstream_ops *upstream_ops; void *upstream; @@ -30,6 +38,71 @@ struct sfp_bus { bool started; }; +static void sfp_quirk_2500basex(const struct sfp_eeprom_id *id, + unsigned long *modes) +{ + phylink_set(modes, 2500baseX_Full); +} + +static const struct sfp_quirk sfp_quirks[] = { + { + // Alcatel Lucent G-010S-P can operate at 2500base-X, but + // incorrectly report 2500MBd NRZ in their EEPROM + .vendor = "ALCATELLUCENT", + .part = "G010SP", + .modes = sfp_quirk_2500basex, + }, { + // Alcatel Lucent G-010S-A can operate at 2500base-X, but + // report 3.2GBd NRZ in their EEPROM + .vendor = "ALCATELLUCENT", + .part = "3FE46541AA", + .modes = sfp_quirk_2500basex, + }, { + // Huawei MA5671A can operate at 2500base-X, but report 1.2GBd + // NRZ in their EEPROM + .vendor = "HUAWEI", + .part = "MA5671A", + .modes = sfp_quirk_2500basex, + }, +}; + +static size_t sfp_strlen(const char *str, size_t maxlen) +{ + size_t size, i; + + /* Trailing characters should be filled with space chars */ + for (i = 0, size = 0; i < maxlen; i++) + if (str[i] != ' ') + size = i + 1; + + return size; +} + +static bool sfp_match(const char *qs, const char *str, size_t len) +{ + if (!qs) + return true; + if (strlen(qs) != len) + return false; + return !strncmp(qs, str, len); +} + +static const struct sfp_quirk *sfp_lookup_quirk(const struct sfp_eeprom_id *id) +{ + const struct sfp_quirk *q; + unsigned int i; + size_t vs, ps; + + vs = sfp_strlen(id->base.vendor_name, ARRAY_SIZE(id->base.vendor_name)); + ps = sfp_strlen(id->base.vendor_pn, ARRAY_SIZE(id->base.vendor_pn)); + + for (i = 0, q = sfp_quirks; i < ARRAY_SIZE(sfp_quirks); i++, q++) + if (sfp_match(q->vendor, id->base.vendor_name, vs) && + sfp_match(q->part, id->base.vendor_pn, ps)) + return q; + + return NULL; +} /** * sfp_parse_port() - Parse the EEPROM base ID, setting the port type * @bus: a pointer to the &struct sfp_bus structure for the sfp module @@ -233,6 +306,9 @@ void sfp_parse_support(struct sfp_bus *bus, const struct sfp_eeprom_id *id, phylink_set(modes, 1000baseX_Full); } + if (bus->sfp_quirk) + bus->sfp_quirk->modes(id, modes); + bitmap_or(support, support, modes, __ETHTOOL_LINK_MODE_MASK_NBITS); phylink_set(support, Autoneg); @@ -328,10 +404,19 @@ static void sfp_bus_release(struct kref *kref) kfree(bus); } -static void sfp_bus_put(struct sfp_bus *bus) +/** + * sfp_bus_put() - put a reference on the &struct sfp_bus + * @bus: the &struct sfp_bus found via sfp_bus_find_fwnode() + * + * Put a reference on the &struct sfp_bus and free the underlying structure + * if this was the last reference. + */ +void sfp_bus_put(struct sfp_bus *bus) { - kref_put_mutex(&bus->kref, sfp_bus_release, &sfp_mutex); + if (bus) + kref_put_mutex(&bus->kref, sfp_bus_release, &sfp_mutex); } +EXPORT_SYMBOL_GPL(sfp_bus_put); static int sfp_register_bus(struct sfp_bus *bus) { @@ -347,11 +432,11 @@ static int sfp_register_bus(struct sfp_bus *bus) return ret; } } + bus->registered = true; bus->socket_ops->attach(bus->sfp); if (bus->started) bus->socket_ops->start(bus->sfp); bus->upstream_ops->attach(bus->upstream, bus); - bus->registered = true; return 0; } @@ -445,64 +530,111 @@ static void sfp_upstream_clear(struct sfp_bus *bus) } /** - * sfp_register_upstream() - Register the neighbouring device - * @fwnode: firmware node for the SFP bus + * sfp_bus_find_fwnode() - parse and locate the SFP bus from fwnode + * @fwnode: firmware node for the parent device (MAC or PHY) + * + * Parse the parent device's firmware node for a SFP bus, and locate + * the sfp_bus structure, incrementing its reference count. This must + * be put via sfp_bus_put() when done. + * + * Returns: on success, a pointer to the sfp_bus structure, + * %NULL if no SFP is specified, + * on failure, an error pointer value: + * corresponding to the errors detailed for + * fwnode_property_get_reference_args(). + * %-ENOMEM if we failed to allocate the bus. + * an error from the upstream's connect_phy() method. + */ +struct sfp_bus *sfp_bus_find_fwnode(struct fwnode_handle *fwnode) +{ + struct fwnode_reference_args ref; + struct sfp_bus *bus; + int ret; + + ret = fwnode_property_get_reference_args(fwnode, "sfp", NULL, + 0, 0, &ref); + if (ret == -ENOENT) + return NULL; + else if (ret < 0) + return ERR_PTR(ret); + + bus = sfp_bus_get(ref.fwnode); + fwnode_handle_put(ref.fwnode); + if (!bus) + return ERR_PTR(-ENOMEM); + + return bus; +} +EXPORT_SYMBOL_GPL(sfp_bus_find_fwnode); + +/** + * sfp_bus_add_upstream() - parse and register the neighbouring device + * @bus: the &struct sfp_bus found via sfp_bus_find_fwnode() * @upstream: the upstream private data * @ops: the upstream's &struct sfp_upstream_ops * - * Register the upstream device (eg, PHY) with the SFP bus. MAC drivers - * should use phylink, which will call this function for them. Returns - * a pointer to the allocated &struct sfp_bus. + * Add upstream driver for the SFP bus, and if the bus is complete, register + * the SFP bus using sfp_register_upstream(). This takes a reference on the + * bus, so it is safe to put the bus after this call. * - * On error, returns %NULL. + * Returns: on success, a pointer to the sfp_bus structure, + * %NULL if no SFP is specified, + * on failure, an error pointer value: + * corresponding to the errors detailed for + * fwnode_property_get_reference_args(). + * %-ENOMEM if we failed to allocate the bus. + * an error from the upstream's connect_phy() method. */ -struct sfp_bus *sfp_register_upstream(struct fwnode_handle *fwnode, - void *upstream, - const struct sfp_upstream_ops *ops) +int sfp_bus_add_upstream(struct sfp_bus *bus, void *upstream, + const struct sfp_upstream_ops *ops) { - struct sfp_bus *bus = sfp_bus_get(fwnode); - int ret = 0; + int ret; - if (bus) { - rtnl_lock(); - bus->upstream_ops = ops; - bus->upstream = upstream; + /* If no bus, return success */ + if (!bus) + return 0; - if (bus->sfp) { - ret = sfp_register_bus(bus); - if (ret) - sfp_upstream_clear(bus); - } - rtnl_unlock(); + rtnl_lock(); + kref_get(&bus->kref); + bus->upstream_ops = ops; + bus->upstream = upstream; + + if (bus->sfp) { + ret = sfp_register_bus(bus); + if (ret) + sfp_upstream_clear(bus); + } else { + ret = 0; } + rtnl_unlock(); - if (ret) { + if (ret) sfp_bus_put(bus); - bus = NULL; - } - return bus; + return ret; } -EXPORT_SYMBOL_GPL(sfp_register_upstream); +EXPORT_SYMBOL_GPL(sfp_bus_add_upstream); /** - * sfp_unregister_upstream() - Unregister sfp bus + * sfp_bus_del_upstream() - Delete a sfp bus * @bus: a pointer to the &struct sfp_bus structure for the sfp module * - * Unregister a previously registered upstream connection for the SFP - * module. @bus is returned from sfp_register_upstream(). + * Delete a previously registered upstream connection for the SFP + * module. @bus should have been added by sfp_bus_add_upstream(). */ -void sfp_unregister_upstream(struct sfp_bus *bus) +void sfp_bus_del_upstream(struct sfp_bus *bus) { - rtnl_lock(); - if (bus->sfp) - sfp_unregister_bus(bus); - sfp_upstream_clear(bus); - rtnl_unlock(); + if (bus) { + rtnl_lock(); + if (bus->sfp) + sfp_unregister_bus(bus); + sfp_upstream_clear(bus); + rtnl_unlock(); - sfp_bus_put(bus); + sfp_bus_put(bus); + } } -EXPORT_SYMBOL_GPL(sfp_unregister_upstream); +EXPORT_SYMBOL_GPL(sfp_bus_del_upstream); /* Socket driver entry points */ int sfp_add_phy(struct sfp_bus *bus, struct phy_device *phydev) @@ -553,6 +685,8 @@ int sfp_module_insert(struct sfp_bus *bus, const struct sfp_eeprom_id *id) const struct sfp_upstream_ops *ops = sfp_get_upstream_ops(bus); int ret = 0; + bus->sfp_quirk = sfp_lookup_quirk(id); + if (ops && ops->module_insert) ret = ops->module_insert(bus->upstream, id); @@ -566,6 +700,8 @@ void sfp_module_remove(struct sfp_bus *bus) if (ops && ops->module_remove) ops->module_remove(bus->upstream); + + bus->sfp_quirk = NULL; } EXPORT_SYMBOL_GPL(sfp_module_remove); diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c index 272d5773573e..bdbbb76f8fd3 100644 --- a/drivers/net/phy/sfp.c +++ b/drivers/net/phy/sfp.c @@ -36,6 +36,8 @@ enum { SFP_E_INSERT = 0, SFP_E_REMOVE, + SFP_E_DEV_ATTACH, + SFP_E_DEV_DETACH, SFP_E_DEV_DOWN, SFP_E_DEV_UP, SFP_E_TX_FAULT, @@ -45,16 +47,21 @@ enum { SFP_E_TIMEOUT, SFP_MOD_EMPTY = 0, + SFP_MOD_ERROR, SFP_MOD_PROBE, + SFP_MOD_WAITDEV, SFP_MOD_HPOWER, + SFP_MOD_WAITPWR, SFP_MOD_PRESENT, - SFP_MOD_ERROR, - SFP_DEV_DOWN = 0, + SFP_DEV_DETACHED = 0, + SFP_DEV_DOWN, SFP_DEV_UP, SFP_S_DOWN = 0, + SFP_S_WAIT, SFP_S_INIT, + SFP_S_INIT_TX_FAULT, SFP_S_WAIT_LOS, SFP_S_LINK_UP, SFP_S_TX_FAULT, @@ -64,10 +71,12 @@ enum { static const char * const mod_state_strings[] = { [SFP_MOD_EMPTY] = "empty", + [SFP_MOD_ERROR] = "error", [SFP_MOD_PROBE] = "probe", + [SFP_MOD_WAITDEV] = "waitdev", [SFP_MOD_HPOWER] = "hpower", + [SFP_MOD_WAITPWR] = "waitpwr", [SFP_MOD_PRESENT] = "present", - [SFP_MOD_ERROR] = "error", }; static const char *mod_state_to_str(unsigned short mod_state) @@ -78,6 +87,7 @@ static const char *mod_state_to_str(unsigned short mod_state) } static const char * const dev_state_strings[] = { + [SFP_DEV_DETACHED] = "detached", [SFP_DEV_DOWN] = "down", [SFP_DEV_UP] = "up", }; @@ -92,6 +102,8 @@ static const char *dev_state_to_str(unsigned short dev_state) static const char * const event_strings[] = { [SFP_E_INSERT] = "insert", [SFP_E_REMOVE] = "remove", + [SFP_E_DEV_ATTACH] = "dev_attach", + [SFP_E_DEV_DETACH] = "dev_detach", [SFP_E_DEV_DOWN] = "dev_down", [SFP_E_DEV_UP] = "dev_up", [SFP_E_TX_FAULT] = "tx_fault", @@ -110,7 +122,9 @@ static const char *event_to_str(unsigned short event) static const char * const sm_state_strings[] = { [SFP_S_DOWN] = "down", + [SFP_S_WAIT] = "wait", [SFP_S_INIT] = "init", + [SFP_S_INIT_TX_FAULT] = "init_tx_fault", [SFP_S_WAIT_LOS] = "wait_los", [SFP_S_LINK_UP] = "link_up", [SFP_S_TX_FAULT] = "tx_fault", @@ -141,6 +155,7 @@ static const enum gpiod_flags gpio_flags[] = { GPIOD_ASIS, }; +#define T_WAIT msecs_to_jiffies(50) #define T_INIT_JIFFIES msecs_to_jiffies(300) #define T_RESET_US 10 #define T_FAULT_RECOVER msecs_to_jiffies(1000) @@ -149,22 +164,21 @@ static const enum gpiod_flags gpio_flags[] = { * 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. + * The SFF-8472 specifies t_serial ("Time from power on until module is + * ready for data transmission over the two wire serial bus.") as 300ms. */ -#define T_PROBE_INIT msecs_to_jiffies(300) -#define T_HPOWER_LEVEL msecs_to_jiffies(300) -#define T_PROBE_RETRY msecs_to_jiffies(100) +#define T_SERIAL msecs_to_jiffies(300) +#define T_HPOWER_LEVEL msecs_to_jiffies(300) +#define T_PROBE_RETRY_INIT msecs_to_jiffies(100) +#define R_PROBE_RETRY_INIT 10 +#define T_PROBE_RETRY_SLOW msecs_to_jiffies(5000) +#define R_PROBE_RETRY_SLOW 12 /* 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 - struct sff_data { unsigned int gpios; bool (*module_supported)(const struct sfp_eeprom_id *id); @@ -187,20 +201,28 @@ struct sfp { struct gpio_desc *gpio[GPIO_MAX]; int gpio_irq[GPIO_MAX]; - bool attached; + bool need_poll; + struct mutex st_mutex; /* Protects state */ + unsigned int state_soft_mask; unsigned int state; struct delayed_work poll; struct delayed_work timeout; struct mutex sm_mutex; /* Protects state machine */ unsigned char sm_mod_state; + unsigned char sm_mod_tries_init; + unsigned char sm_mod_tries; unsigned char sm_dev_state; unsigned short sm_state; unsigned int sm_retries; struct sfp_eeprom_id id; + unsigned int module_power_mW; + #if IS_ENABLED(CONFIG_HWMON) struct sfp_diag diag; + struct delayed_work hwmon_probe; + unsigned int hwmon_tries; struct device *hwmon_dev; char *hwmon_name; #endif @@ -376,24 +398,90 @@ static int sfp_i2c_configure(struct sfp *sfp, struct i2c_adapter *i2c) } /* Interface */ -static unsigned int sfp_get_state(struct sfp *sfp) +static int sfp_read(struct sfp *sfp, bool a2, u8 addr, void *buf, size_t len) { - return sfp->get_state(sfp); + return sfp->read(sfp, a2, addr, buf, len); } -static void sfp_set_state(struct sfp *sfp, unsigned int state) +static int sfp_write(struct sfp *sfp, bool a2, u8 addr, void *buf, size_t len) { - sfp->set_state(sfp, state); + return sfp->write(sfp, a2, addr, buf, len); } -static int sfp_read(struct sfp *sfp, bool a2, u8 addr, void *buf, size_t len) +static unsigned int sfp_soft_get_state(struct sfp *sfp) { - return sfp->read(sfp, a2, addr, buf, len); + unsigned int state = 0; + u8 status; + + if (sfp_read(sfp, true, SFP_STATUS, &status, sizeof(status)) == + sizeof(status)) { + if (status & SFP_STATUS_RX_LOS) + state |= SFP_F_LOS; + if (status & SFP_STATUS_TX_FAULT) + state |= SFP_F_TX_FAULT; + } + + return state & sfp->state_soft_mask; } -static int sfp_write(struct sfp *sfp, bool a2, u8 addr, void *buf, size_t len) +static void sfp_soft_set_state(struct sfp *sfp, unsigned int state) { - return sfp->write(sfp, a2, addr, buf, len); + u8 status; + + if (sfp_read(sfp, true, SFP_STATUS, &status, sizeof(status)) == + sizeof(status)) { + if (state & SFP_F_TX_DISABLE) + status |= SFP_STATUS_TX_DISABLE_FORCE; + else + status &= ~SFP_STATUS_TX_DISABLE_FORCE; + + sfp_write(sfp, true, SFP_STATUS, &status, sizeof(status)); + } +} + +static void sfp_soft_start_poll(struct sfp *sfp) +{ + const struct sfp_eeprom_id *id = &sfp->id; + + sfp->state_soft_mask = 0; + if (id->ext.enhopts & SFP_ENHOPTS_SOFT_TX_DISABLE && + !sfp->gpio[GPIO_TX_DISABLE]) + sfp->state_soft_mask |= SFP_F_TX_DISABLE; + if (id->ext.enhopts & SFP_ENHOPTS_SOFT_TX_FAULT && + !sfp->gpio[GPIO_TX_FAULT]) + sfp->state_soft_mask |= SFP_F_TX_FAULT; + if (id->ext.enhopts & SFP_ENHOPTS_SOFT_RX_LOS && + !sfp->gpio[GPIO_LOS]) + sfp->state_soft_mask |= SFP_F_LOS; + + if (sfp->state_soft_mask & (SFP_F_LOS | SFP_F_TX_FAULT) && + !sfp->need_poll) + mod_delayed_work(system_wq, &sfp->poll, poll_jiffies); +} + +static void sfp_soft_stop_poll(struct sfp *sfp) +{ + sfp->state_soft_mask = 0; +} + +static unsigned int sfp_get_state(struct sfp *sfp) +{ + unsigned int state = sfp->get_state(sfp); + + if (state & SFP_F_PRESENT && + sfp->state_soft_mask & (SFP_F_LOS | SFP_F_TX_FAULT)) + state |= sfp_soft_get_state(sfp); + + return state; +} + +static void sfp_set_state(struct sfp *sfp, unsigned int state) +{ + sfp->set_state(sfp, state); + + if (state & SFP_F_PRESENT && + sfp->state_soft_mask & SFP_F_TX_DISABLE) + sfp_soft_set_state(sfp, state); } static unsigned int sfp_check(void *buf, size_t len) @@ -1142,29 +1230,27 @@ static const struct hwmon_chip_info sfp_hwmon_chip_info = { .info = sfp_hwmon_info, }; -static int sfp_hwmon_insert(struct sfp *sfp) +static void sfp_hwmon_probe(struct work_struct *work) { + struct sfp *sfp = container_of(work, struct sfp, hwmon_probe.work); int err, i; - if (sfp->id.ext.sff8472_compliance == SFP_SFF8472_COMPLIANCE_NONE) - return 0; - - if (!(sfp->id.ext.diagmon & SFP_DIAGMON_DDM)) - return 0; - - if (sfp->id.ext.diagmon & SFP_DIAGMON_ADDRMODE) - /* This driver in general does not support address - * change. - */ - return 0; - err = sfp_read(sfp, true, 0, &sfp->diag, sizeof(sfp->diag)); - if (err < 0) - return err; + if (err < 0) { + if (sfp->hwmon_tries--) { + mod_delayed_work(system_wq, &sfp->hwmon_probe, + T_PROBE_RETRY_SLOW); + } else { + dev_warn(sfp->dev, "hwmon probe failed: %d\n", err); + } + return; + } sfp->hwmon_name = kstrdup(dev_name(sfp->dev), GFP_KERNEL); - if (!sfp->hwmon_name) - return -ENODEV; + if (!sfp->hwmon_name) { + dev_err(sfp->dev, "out of memory for hwmon name\n"); + return; + } for (i = 0; sfp->hwmon_name[i]; i++) if (hwmon_is_bad_char(sfp->hwmon_name[i])) @@ -1174,18 +1260,52 @@ static int sfp_hwmon_insert(struct sfp *sfp) sfp->hwmon_name, sfp, &sfp_hwmon_chip_info, NULL); + if (IS_ERR(sfp->hwmon_dev)) + dev_err(sfp->dev, "failed to register hwmon device: %ld\n", + PTR_ERR(sfp->hwmon_dev)); +} + +static int sfp_hwmon_insert(struct sfp *sfp) +{ + if (sfp->id.ext.sff8472_compliance == SFP_SFF8472_COMPLIANCE_NONE) + return 0; + + if (!(sfp->id.ext.diagmon & SFP_DIAGMON_DDM)) + return 0; - return PTR_ERR_OR_ZERO(sfp->hwmon_dev); + if (sfp->id.ext.diagmon & SFP_DIAGMON_ADDRMODE) + /* This driver in general does not support address + * change. + */ + return 0; + + mod_delayed_work(system_wq, &sfp->hwmon_probe, 1); + sfp->hwmon_tries = R_PROBE_RETRY_SLOW; + + return 0; } static void sfp_hwmon_remove(struct sfp *sfp) { + cancel_delayed_work_sync(&sfp->hwmon_probe); if (!IS_ERR_OR_NULL(sfp->hwmon_dev)) { hwmon_device_unregister(sfp->hwmon_dev); sfp->hwmon_dev = NULL; kfree(sfp->hwmon_name); } } + +static int sfp_hwmon_init(struct sfp *sfp) +{ + INIT_DELAYED_WORK(&sfp->hwmon_probe, sfp_hwmon_probe); + + return 0; +} + +static void sfp_hwmon_exit(struct sfp *sfp) +{ + cancel_delayed_work_sync(&sfp->hwmon_probe); +} #else static int sfp_hwmon_insert(struct sfp *sfp) { @@ -1195,6 +1315,15 @@ static int sfp_hwmon_insert(struct sfp *sfp) static void sfp_hwmon_remove(struct sfp *sfp) { } + +static int sfp_hwmon_init(struct sfp *sfp) +{ + return 0; +} + +static void sfp_hwmon_exit(struct sfp *sfp) +{ +} #endif /* Helpers */ @@ -1245,7 +1374,7 @@ static void sfp_sm_next(struct sfp *sfp, unsigned int state, sfp_sm_set_timer(sfp, timeout); } -static void sfp_sm_ins_next(struct sfp *sfp, unsigned int state, +static void sfp_sm_mod_next(struct sfp *sfp, unsigned int state, unsigned int timeout) { sfp->sm_mod_state = state; @@ -1266,8 +1395,6 @@ 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 (phy == ERR_PTR(-ENODEV)) { dev_info(sfp->dev, "no PHY detected\n"); @@ -1335,7 +1462,7 @@ static bool sfp_los_event_inactive(struct sfp *sfp, unsigned int event) event == SFP_E_LOS_LOW); } -static void sfp_sm_fault(struct sfp *sfp, bool warn) +static void sfp_sm_fault(struct sfp *sfp, unsigned int next_state, bool warn) { if (sfp->sm_retries && !--sfp->sm_retries) { dev_err(sfp->dev, @@ -1345,21 +1472,12 @@ static void sfp_sm_fault(struct sfp *sfp, bool warn) if (warn) dev_err(sfp->dev, "module transmit fault indicated\n"); - sfp_sm_next(sfp, SFP_S_TX_FAULT, T_FAULT_RECOVER); + sfp_sm_next(sfp, next_state, T_FAULT_RECOVER); } } -static void sfp_sm_mod_init(struct sfp *sfp) +static void sfp_sm_probe_for_phy(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; - /* Setting the serdes link mode is guesswork: there's no * field in the EEPROM which indicates what mode should * be used. @@ -1375,69 +1493,83 @@ static void sfp_sm_mod_init(struct sfp *sfp) sfp_sm_probe_phy(sfp); } -static int sfp_sm_mod_hpower(struct sfp *sfp) +static int sfp_module_parse_power(struct sfp *sfp) { - u32 power; - u8 val; - int err; + u32 power_mW = 1000; - power = 1000; if (sfp->id.ext.options & cpu_to_be16(SFP_OPTIONS_POWER_DECL)) - power = 1500; + power_mW = 1500; if (sfp->id.ext.options & cpu_to_be16(SFP_OPTIONS_HIGH_POWER_LEVEL)) - power = 2000; - - if (sfp->id.ext.sff8472_compliance == SFP_SFF8472_COMPLIANCE_NONE && - (sfp->id.ext.diagmon & (SFP_DIAGMON_DDM | SFP_DIAGMON_ADDRMODE)) != - SFP_DIAGMON_DDM) { - /* The module appears not to implement bus address 0xa2, - * or requires an address change sequence, so assume that - * the module powers up in the indicated power mode. - */ - if (power > sfp->max_power_mW) { + power_mW = 2000; + + if (power_mW > sfp->max_power_mW) { + /* Module power specification exceeds the allowed maximum. */ + if (sfp->id.ext.sff8472_compliance == + SFP_SFF8472_COMPLIANCE_NONE && + !(sfp->id.ext.diagmon & SFP_DIAGMON_DDM)) { + /* The module appears not to implement bus address + * 0xa2, so assume that the module powers up in the + * indicated mode. + */ dev_err(sfp->dev, "Host does not support %u.%uW modules\n", - power / 1000, (power / 100) % 10); + power_mW / 1000, (power_mW / 100) % 10); return -EINVAL; + } else { + dev_warn(sfp->dev, + "Host does not support %u.%uW modules, module left in power mode 1\n", + power_mW / 1000, (power_mW / 100) % 10); + return 0; } - return 0; } - if (power > sfp->max_power_mW) { + /* If the module requires a higher power mode, but also requires + * an address change sequence, warn the user that the module may + * not be functional. + */ + if (sfp->id.ext.diagmon & SFP_DIAGMON_ADDRMODE && power_mW > 1000) { dev_warn(sfp->dev, - "Host does not support %u.%uW modules, module left in power mode 1\n", - power / 1000, (power / 100) % 10); + "Address Change Sequence not supported but module requires %u.%uW, module may not be functional\n", + power_mW / 1000, (power_mW / 100) % 10); return 0; } - if (power <= 1000) - return 0; + sfp->module_power_mW = power_mW; + + return 0; +} + +static int sfp_sm_mod_hpower(struct sfp *sfp, bool enable) +{ + u8 val; + int err; err = sfp_read(sfp, true, SFP_EXT_STATUS, &val, sizeof(val)); if (err != sizeof(val)) { dev_err(sfp->dev, "Failed to read EEPROM: %d\n", err); - err = -EAGAIN; - goto err; + return -EAGAIN; } - val |= BIT(0); + if (enable) + val |= BIT(0); + else + val &= ~BIT(0); err = sfp_write(sfp, true, SFP_EXT_STATUS, &val, sizeof(val)); if (err != sizeof(val)) { dev_err(sfp->dev, "Failed to write EEPROM: %d\n", err); - err = -EAGAIN; - goto err; + return -EAGAIN; } - dev_info(sfp->dev, "Module switched to %u.%uW power level\n", - power / 1000, (power / 100) % 10); - return T_HPOWER_LEVEL; + if (enable) + dev_info(sfp->dev, "Module switched to %u.%uW power level\n", + sfp->module_power_mW / 1000, + (sfp->module_power_mW / 100) % 10); -err: - return err; + return 0; } -static int sfp_sm_mod_probe(struct sfp *sfp) +static int sfp_sm_mod_probe(struct sfp *sfp, bool report) { /* SFP module inserted - read I2C data */ struct sfp_eeprom_id id; @@ -1447,7 +1579,8 @@ static int sfp_sm_mod_probe(struct sfp *sfp) ret = sfp_read(sfp, false, 0, &id, sizeof(id)); if (ret < 0) { - dev_err(sfp->dev, "failed to read EEPROM: %d\n", ret); + if (report) + dev_err(sfp->dev, "failed to read EEPROM: %d\n", ret); return -EAGAIN; } @@ -1505,7 +1638,7 @@ static int sfp_sm_mod_probe(struct sfp *sfp) (int)sizeof(id.ext.datecode), id.ext.datecode); /* Check whether we support this module */ - if (!sfp->type->module_supported(&sfp->id)) { + if (!sfp->type->module_supported(&id)) { dev_err(sfp->dev, "module is not supported - phys id 0x%02x 0x%02x\n", sfp->id.base.phys_id, sfp->id.base.phys_ext_id); @@ -1517,106 +1650,174 @@ static int sfp_sm_mod_probe(struct sfp *sfp) dev_warn(sfp->dev, "module address swap to access page 0xA2 is not supported.\n"); - ret = sfp_hwmon_insert(sfp); - if (ret < 0) - return ret; - - ret = sfp_module_insert(sfp->sfp_bus, &sfp->id); + /* Parse the module power requirement */ + ret = sfp_module_parse_power(sfp); if (ret < 0) return ret; - return sfp_sm_mod_hpower(sfp); + return 0; } static void sfp_sm_mod_remove(struct sfp *sfp) { - sfp_module_remove(sfp->sfp_bus); + if (sfp->sm_mod_state > SFP_MOD_WAITDEV) + sfp_module_remove(sfp->sfp_bus); sfp_hwmon_remove(sfp); - if (sfp->mod_phy) - sfp_sm_phy_detach(sfp); - - sfp_module_tx_disable(sfp); - memset(&sfp->id, 0, sizeof(sfp->id)); + sfp->module_power_mW = 0; dev_info(sfp->dev, "module removed\n"); } -static void sfp_sm_event(struct sfp *sfp, unsigned int event) +/* This state machine tracks the upstream's state */ +static void sfp_sm_device(struct sfp *sfp, unsigned int event) { - mutex_lock(&sfp->sm_mutex); + switch (sfp->sm_dev_state) { + default: + if (event == SFP_E_DEV_ATTACH) + sfp->sm_dev_state = SFP_DEV_DOWN; + break; - dev_dbg(sfp->dev, "SM: enter %s:%s:%s event %s\n", - mod_state_to_str(sfp->sm_mod_state), - dev_state_to_str(sfp->sm_dev_state), - sm_state_to_str(sfp->sm_state), - event_to_str(event)); + case SFP_DEV_DOWN: + if (event == SFP_E_DEV_DETACH) + sfp->sm_dev_state = SFP_DEV_DETACHED; + else if (event == SFP_E_DEV_UP) + sfp->sm_dev_state = SFP_DEV_UP; + break; + + case SFP_DEV_UP: + if (event == SFP_E_DEV_DETACH) + sfp->sm_dev_state = SFP_DEV_DETACHED; + else if (event == SFP_E_DEV_DOWN) + sfp->sm_dev_state = SFP_DEV_DOWN; + break; + } +} + +/* This state machine tracks the insert/remove state of the module, probes + * the on-board EEPROM, and sets up the power level. + */ +static void sfp_sm_module(struct sfp *sfp, unsigned int event) +{ + int err; + + /* Handle remove event globally, it resets this state machine */ + if (event == SFP_E_REMOVE) { + if (sfp->sm_mod_state > SFP_MOD_PROBE) + sfp_sm_mod_remove(sfp); + sfp_sm_mod_next(sfp, SFP_MOD_EMPTY, 0); + return; + } + + /* Handle device detach globally */ + if (sfp->sm_dev_state < SFP_DEV_DOWN && + sfp->sm_mod_state > SFP_MOD_WAITDEV) { + if (sfp->module_power_mW > 1000 && + sfp->sm_mod_state > SFP_MOD_HPOWER) + sfp_sm_mod_hpower(sfp, false); + sfp_sm_mod_next(sfp, SFP_MOD_WAITDEV, 0); + return; + } - /* 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->attached) { - sfp_module_tx_disable(sfp); - sfp_sm_ins_next(sfp, SFP_MOD_PROBE, T_PROBE_INIT); + if (event == SFP_E_INSERT) { + sfp_sm_mod_next(sfp, SFP_MOD_PROBE, T_SERIAL); + sfp->sm_mod_tries_init = R_PROBE_RETRY_INIT; + sfp->sm_mod_tries = R_PROBE_RETRY_SLOW; } 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 val = sfp_sm_mod_probe(sfp); - - if (val == 0) - sfp_sm_ins_next(sfp, SFP_MOD_PRESENT, 0); - else if (val > 0) - sfp_sm_ins_next(sfp, SFP_MOD_HPOWER, val); - else if (val != -EAGAIN) - sfp_sm_ins_next(sfp, SFP_MOD_ERROR, 0); - else - sfp_sm_set_timer(sfp, T_PROBE_RETRY); + /* Wait for T_PROBE_INIT to time out */ + if (event != SFP_E_TIMEOUT) + break; + + err = sfp_sm_mod_probe(sfp, sfp->sm_mod_tries == 1); + if (err == -EAGAIN) { + if (sfp->sm_mod_tries_init && + --sfp->sm_mod_tries_init) { + sfp_sm_set_timer(sfp, T_PROBE_RETRY_INIT); + break; + } else if (sfp->sm_mod_tries && --sfp->sm_mod_tries) { + if (sfp->sm_mod_tries == R_PROBE_RETRY_SLOW - 1) + dev_warn(sfp->dev, + "please wait, module slow to respond\n"); + sfp_sm_set_timer(sfp, T_PROBE_RETRY_SLOW); + break; + } + } + if (err < 0) { + sfp_sm_mod_next(sfp, SFP_MOD_ERROR, 0); + break; } - break; - case SFP_MOD_HPOWER: - if (event == SFP_E_TIMEOUT) { - sfp_sm_ins_next(sfp, SFP_MOD_PRESENT, 0); + sfp_sm_mod_next(sfp, SFP_MOD_WAITDEV, 0); + /* fall through */ + case SFP_MOD_WAITDEV: + /* Ensure that the device is attached before proceeding */ + if (sfp->sm_dev_state < SFP_DEV_DOWN) + break; + + /* Report the module insertion to the upstream device */ + err = sfp_module_insert(sfp->sfp_bus, &sfp->id); + if (err < 0) { + sfp_sm_mod_next(sfp, SFP_MOD_ERROR, 0); break; } - /* fallthrough */ - 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); + + /* If this is a power level 1 module, we are done */ + if (sfp->module_power_mW <= 1000) + goto insert; + + sfp_sm_mod_next(sfp, SFP_MOD_HPOWER, 0); + /* fall through */ + case SFP_MOD_HPOWER: + /* Enable high power mode */ + err = sfp_sm_mod_hpower(sfp, true); + if (err < 0) { + if (err != -EAGAIN) { + sfp_module_remove(sfp->sfp_bus); + sfp_sm_mod_next(sfp, SFP_MOD_ERROR, 0); + } else { + sfp_sm_set_timer(sfp, T_PROBE_RETRY_INIT); + } + break; } + + sfp_sm_mod_next(sfp, SFP_MOD_WAITPWR, T_HPOWER_LEVEL); 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; + case SFP_MOD_WAITPWR: + /* Wait for T_HPOWER_LEVEL to time out */ + if (event != SFP_E_TIMEOUT) + break; + + insert: + sfp_sm_mod_next(sfp, SFP_MOD_PRESENT, 0); 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; - } + case SFP_MOD_PRESENT: + case SFP_MOD_ERROR: break; } +#if IS_ENABLED(CONFIG_HWMON) + if (sfp->sm_mod_state >= SFP_MOD_WAITDEV && + IS_ERR_OR_NULL(sfp->hwmon_dev)) { + err = sfp_hwmon_insert(sfp); + if (err) + dev_warn(sfp->dev, "hwmon probe failed: %d\n", err); + } +#endif +} + +static void sfp_sm_main(struct sfp *sfp, unsigned int event) +{ + unsigned long timeout; + /* Some events are global */ if (sfp->sm_state != SFP_S_DOWN && (sfp->sm_mod_state != SFP_MOD_PRESENT || @@ -1626,29 +1827,87 @@ static void sfp_sm_event(struct sfp *sfp, unsigned int event) sfp_sm_link_down(sfp); if (sfp->mod_phy) sfp_sm_phy_detach(sfp); + sfp_module_tx_disable(sfp); + sfp_soft_stop_poll(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); + if (sfp->sm_mod_state != SFP_MOD_PRESENT || + sfp->sm_dev_state != SFP_DEV_UP) + break; + + if (!(sfp->id.ext.diagmon & SFP_DIAGMON_ADDRMODE)) + sfp_soft_start_poll(sfp); + + sfp_module_tx_enable(sfp); + + /* Initialise the fault clearance retries */ + sfp->sm_retries = 5; + + /* We need to check the TX_FAULT state, which is not defined + * while TX_DISABLE is asserted. The earliest we want to do + * anything (such as probe for a PHY) is 50ms. + */ + sfp_sm_next(sfp, SFP_S_WAIT, T_WAIT); + break; + + case SFP_S_WAIT: + if (event != SFP_E_TIMEOUT) + break; + + if (sfp->state & SFP_F_TX_FAULT) { + /* 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. + */ + timeout = T_INIT_JIFFIES; + if (timeout > T_WAIT) + timeout -= T_WAIT; + else + timeout = 1; + + sfp_sm_next(sfp, SFP_S_INIT, timeout); + } else { + /* TX_FAULT is not asserted, assume the module has + * finished initialising. + */ + goto init_done; + } 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) + if (event == SFP_E_TIMEOUT && sfp->state & SFP_F_TX_FAULT) { + /* TX_FAULT is still asserted after t_init, so assume + * there is a fault. + */ + sfp_sm_fault(sfp, SFP_S_INIT_TX_FAULT, + sfp->sm_retries == 5); + } else if (event == SFP_E_TIMEOUT || event == SFP_E_TX_CLEAR) { + init_done: /* TX_FAULT deasserted or we timed out with TX_FAULT + * clear. Probe for the PHY and check the LOS state. + */ + sfp_sm_probe_for_phy(sfp); sfp_sm_link_check_los(sfp); + + /* Reset the fault retry count */ + sfp->sm_retries = 5; + } + break; + + case SFP_S_INIT_TX_FAULT: + if (event == SFP_E_TIMEOUT) { + sfp_module_tx_fault_reset(sfp); + sfp_sm_next(sfp, SFP_S_INIT, T_INIT_JIFFIES); + } break; case SFP_S_WAIT_LOS: if (event == SFP_E_TX_FAULT) - sfp_sm_fault(sfp, true); + sfp_sm_fault(sfp, SFP_S_TX_FAULT, true); else if (sfp_los_event_inactive(sfp, event)) sfp_sm_link_up(sfp); break; @@ -1656,7 +1915,7 @@ static void sfp_sm_event(struct sfp *sfp, unsigned int event) case SFP_S_LINK_UP: if (event == SFP_E_TX_FAULT) { sfp_sm_link_down(sfp); - sfp_sm_fault(sfp, true); + sfp_sm_fault(sfp, SFP_S_TX_FAULT, true); } else if (sfp_los_event_active(sfp, event)) { sfp_sm_link_down(sfp); sfp_sm_next(sfp, SFP_S_WAIT_LOS, 0); @@ -1672,7 +1931,7 @@ static void sfp_sm_event(struct sfp *sfp, unsigned int event) case SFP_S_REINIT: if (event == SFP_E_TIMEOUT && sfp->state & SFP_F_TX_FAULT) { - sfp_sm_fault(sfp, false); + sfp_sm_fault(sfp, SFP_S_TX_FAULT, 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); @@ -1682,6 +1941,21 @@ static void sfp_sm_event(struct sfp *sfp, unsigned int event) case SFP_S_TX_DISABLE: break; } +} + +static void sfp_sm_event(struct sfp *sfp, unsigned int event) +{ + mutex_lock(&sfp->sm_mutex); + + dev_dbg(sfp->dev, "SM: enter %s:%s:%s event %s\n", + mod_state_to_str(sfp->sm_mod_state), + dev_state_to_str(sfp->sm_dev_state), + sm_state_to_str(sfp->sm_state), + event_to_str(event)); + + sfp_sm_device(sfp, event); + sfp_sm_module(sfp, event); + sfp_sm_main(sfp, event); dev_dbg(sfp->dev, "SM: exit %s:%s:%s\n", mod_state_to_str(sfp->sm_mod_state), @@ -1693,15 +1967,12 @@ static void sfp_sm_event(struct sfp *sfp, unsigned int event) static void sfp_attach(struct sfp *sfp) { - sfp->attached = true; - if (sfp->state & SFP_F_PRESENT) - sfp_sm_event(sfp, SFP_E_INSERT); + sfp_sm_event(sfp, SFP_E_DEV_ATTACH); } static void sfp_detach(struct sfp *sfp) { - sfp->attached = false; - sfp_sm_event(sfp, SFP_E_REMOVE); + sfp_sm_event(sfp, SFP_E_DEV_DETACH); } static void sfp_start(struct sfp *sfp) @@ -1828,7 +2099,10 @@ 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); + + if (sfp->state_soft_mask & (SFP_F_LOS | SFP_F_TX_FAULT) || + sfp->need_poll) + mod_delayed_work(system_wq, &sfp->poll, poll_jiffies); } static struct sfp *sfp_alloc(struct device *dev) @@ -1846,6 +2120,8 @@ static struct sfp *sfp_alloc(struct device *dev) INIT_DELAYED_WORK(&sfp->poll, sfp_poll); INIT_DELAYED_WORK(&sfp->timeout, sfp_timeout); + sfp_hwmon_init(sfp); + return sfp; } @@ -1853,6 +2129,8 @@ static void sfp_cleanup(void *data) { struct sfp *sfp = data; + sfp_hwmon_exit(sfp); + cancel_delayed_work_sync(&sfp->poll); cancel_delayed_work_sync(&sfp->timeout); if (sfp->i2c_mii) { @@ -1869,7 +2147,6 @@ static int sfp_probe(struct platform_device *pdev) const struct sff_data *sff; struct i2c_adapter *i2c; struct sfp *sfp; - bool poll = false; int err, i; sfp = sfp_alloc(&pdev->dev); @@ -1964,6 +2241,11 @@ 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); + if (sfp->state & SFP_F_PRESENT) { + rtnl_lock(); + 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]) @@ -1971,7 +2253,7 @@ static int sfp_probe(struct platform_device *pdev) sfp->gpio_irq[i] = gpiod_to_irq(sfp->gpio[i]); if (!sfp->gpio_irq[i]) { - poll = true; + sfp->need_poll = true; continue; } @@ -1983,11 +2265,11 @@ static int sfp_probe(struct platform_device *pdev) dev_name(sfp->dev), sfp); if (err) { sfp->gpio_irq[i] = 0; - poll = true; + sfp->need_poll = true; } } - if (poll) + if (sfp->need_poll) mod_delayed_work(system_wq, &sfp->poll, poll_jiffies); /* We could have an issue in cases no Tx disable pin is available or |