diff options
Diffstat (limited to 'drivers/net/phy/broadcom.c')
-rw-r--r-- | drivers/net/phy/broadcom.c | 403 |
1 files changed, 375 insertions, 28 deletions
diff --git a/drivers/net/phy/broadcom.c b/drivers/net/phy/broadcom.c index 370e4ed45098..ddded162c44c 100644 --- a/drivers/net/phy/broadcom.c +++ b/drivers/net/phy/broadcom.c @@ -5,6 +5,8 @@ * Broadcom BCM5411, BCM5421 and BCM5461 Gigabit Ethernet * transceivers. * + * Broadcom BCM54810, BCM54811 BroadR-Reach transceivers. + * * Copyright (c) 2006 Maciej W. Rozycki * * Inspired by code written by Amy Fong. @@ -36,6 +38,29 @@ struct bcm54xx_phy_priv { struct bcm_ptp_private *ptp; int wake_irq; bool wake_irq_enabled; + bool brr_mode; +}; + +/* Link modes for BCM58411 PHY */ +static const int bcm54811_linkmodes[] = { + ETHTOOL_LINK_MODE_100baseT1_Full_BIT, + ETHTOOL_LINK_MODE_10baseT1BRR_Full_BIT, + ETHTOOL_LINK_MODE_1000baseT_Full_BIT, + ETHTOOL_LINK_MODE_1000baseX_Full_BIT, + ETHTOOL_LINK_MODE_1000baseT_Half_BIT, + ETHTOOL_LINK_MODE_100baseT_Full_BIT, + ETHTOOL_LINK_MODE_100baseT_Half_BIT, + ETHTOOL_LINK_MODE_10baseT_Full_BIT, + ETHTOOL_LINK_MODE_10baseT_Half_BIT +}; + +/* Long-Distance Signaling (BroadR-Reach mode aneg) relevant linkmode bits */ +static const int lds_br_bits[] = { + ETHTOOL_LINK_MODE_Autoneg_BIT, + ETHTOOL_LINK_MODE_Pause_BIT, + ETHTOOL_LINK_MODE_Asym_Pause_BIT, + ETHTOOL_LINK_MODE_10baseT1BRR_Full_BIT, + ETHTOOL_LINK_MODE_100baseT1_Full_BIT }; static bool bcm54xx_phy_can_wakeup(struct phy_device *phydev) @@ -347,6 +372,61 @@ static void bcm54xx_ptp_config_init(struct phy_device *phydev) bcm_ptp_config_init(phydev); } +static int bcm5481x_set_brrmode(struct phy_device *phydev, bool on) +{ + int reg; + int err; + u16 val; + + reg = bcm_phy_read_exp(phydev, BCM54810_EXP_BROADREACH_LRE_MISC_CTL); + + if (reg < 0) + return reg; + + if (on) + reg |= BCM54810_EXP_BROADREACH_LRE_MISC_CTL_EN; + else + reg &= ~BCM54810_EXP_BROADREACH_LRE_MISC_CTL_EN; + + err = bcm_phy_write_exp(phydev, + BCM54810_EXP_BROADREACH_LRE_MISC_CTL, reg); + if (err) + return err; + + /* Ensure LRE or IEEE register set is accessed according to the brr + * on/off, thus set the override + */ + val = BCM54811_EXP_BROADREACH_LRE_OVERLAY_CTL_EN; + if (!on) + val |= BCM54811_EXP_BROADREACH_LRE_OVERLAY_CTL_OVERRIDE_VAL; + + return bcm_phy_write_exp(phydev, + BCM54811_EXP_BROADREACH_LRE_OVERLAY_CTL, val); +} + +static int bcm54811_config_init(struct phy_device *phydev) +{ + struct bcm54xx_phy_priv *priv = phydev->priv; + int err, reg; + + /* Enable CLK125 MUX on LED4 if ref clock is enabled. */ + if (!(phydev->dev_flags & PHY_BRCM_RX_REFCLK_UNUSED)) { + reg = bcm_phy_read_exp(phydev, BCM54612E_EXP_SPARE0); + if (reg < 0) + return reg; + err = bcm_phy_write_exp(phydev, BCM54612E_EXP_SPARE0, + BCM54612E_LED4_CLK125OUT_EN | reg); + if (err < 0) + return err; + } + + /* With BCM54811, BroadR-Reach implies no autoneg */ + if (priv->brr_mode) + phydev->autoneg = 0; + + return bcm5481x_set_brrmode(phydev, priv->brr_mode); +} + static int bcm54xx_config_init(struct phy_device *phydev) { int reg, err, val; @@ -399,6 +479,9 @@ static int bcm54xx_config_init(struct phy_device *phydev) BCM54810_EXP_BROADREACH_LRE_MISC_CTL, val); break; + case PHY_ID_BCM54811: + err = bcm54811_config_init(phydev); + break; } if (err) return err; @@ -553,52 +636,117 @@ static int bcm54810_write_mmd(struct phy_device *phydev, int devnum, u16 regnum, return -EOPNOTSUPP; } -static int bcm54811_config_init(struct phy_device *phydev) + +/** + * bcm5481x_read_abilities - read PHY abilities from LRESR or Clause 22 + * (BMSR) registers, based on whether the PHY is in BroadR-Reach or IEEE mode + * @phydev: target phy_device struct + * + * Description: Reads the PHY's abilities and populates phydev->supported + * accordingly. The register to read the abilities from is determined by + * the brr mode setting of the PHY as read from the device tree. + * Note that the LRE and IEEE sets of abilities are disjunct, in other words, + * not only the link modes differ, but also the auto-negotiation and + * master-slave setup is controlled differently. + * + * Returns: 0 on success, < 0 on failure + */ +static int bcm5481x_read_abilities(struct phy_device *phydev) { - int err, reg; + struct device_node *np = phydev->mdio.dev.of_node; + struct bcm54xx_phy_priv *priv = phydev->priv; + int i, val, err; - /* Disable BroadR-Reach function. */ - reg = bcm_phy_read_exp(phydev, BCM54810_EXP_BROADREACH_LRE_MISC_CTL); - reg &= ~BCM54810_EXP_BROADREACH_LRE_MISC_CTL_EN; - err = bcm_phy_write_exp(phydev, BCM54810_EXP_BROADREACH_LRE_MISC_CTL, - reg); - if (err < 0) + for (i = 0; i < ARRAY_SIZE(bcm54811_linkmodes); i++) + linkmode_clear_bit(bcm54811_linkmodes[i], phydev->supported); + + priv->brr_mode = of_property_read_bool(np, "brr-mode"); + + /* Set BroadR-Reach mode as configured in the DT. */ + err = bcm5481x_set_brrmode(phydev, priv->brr_mode); + if (err) return err; - err = bcm54xx_config_init(phydev); + if (priv->brr_mode) { + linkmode_set_bit_array(phy_basic_ports_array, + ARRAY_SIZE(phy_basic_ports_array), + phydev->supported); - /* Enable CLK125 MUX on LED4 if ref clock is enabled. */ - if (!(phydev->dev_flags & PHY_BRCM_RX_REFCLK_UNUSED)) { - reg = bcm_phy_read_exp(phydev, BCM54612E_EXP_SPARE0); - err = bcm_phy_write_exp(phydev, BCM54612E_EXP_SPARE0, - BCM54612E_LED4_CLK125OUT_EN | reg); - if (err < 0) - return err; + val = phy_read(phydev, MII_BCM54XX_LRESR); + if (val < 0) + return val; + + linkmode_mod_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, + phydev->supported, + val & LRESR_LDSABILITY); + linkmode_mod_bit(ETHTOOL_LINK_MODE_100baseT1_Full_BIT, + phydev->supported, + val & LRESR_100_1PAIR); + linkmode_mod_bit(ETHTOOL_LINK_MODE_10baseT1BRR_Full_BIT, + phydev->supported, + val & LRESR_10_1PAIR); + return 0; } - return err; + return genphy_read_abilities(phydev); } -static int bcm5481_config_aneg(struct phy_device *phydev) +static int bcm5481x_config_delay_swap(struct phy_device *phydev) { struct device_node *np = phydev->mdio.dev.of_node; - int ret; - - /* Aneg firstly. */ - ret = genphy_config_aneg(phydev); - /* Then we can set up the delay. */ + /* Set up the delay. */ bcm54xx_config_clock_delay(phydev); if (of_property_read_bool(np, "enet-phy-lane-swap")) { /* Lane Swap - Undocumented register...magic! */ - ret = bcm_phy_write_exp(phydev, MII_BCM54XX_EXP_SEL_ER + 0x9, - 0x11B); + int ret = bcm_phy_write_exp(phydev, + MII_BCM54XX_EXP_SEL_ER + 0x9, + 0x11B); if (ret < 0) return ret; } - return ret; + return 0; +} + +static int bcm5481_config_aneg(struct phy_device *phydev) +{ + struct bcm54xx_phy_priv *priv = phydev->priv; + int ret; + + /* Aneg firstly. */ + if (priv->brr_mode) + ret = bcm_config_lre_aneg(phydev, false); + else + ret = genphy_config_aneg(phydev); + + if (ret) + return ret; + + /* Then we can set up the delay and swap. */ + return bcm5481x_config_delay_swap(phydev); +} + +static int bcm54811_config_aneg(struct phy_device *phydev) +{ + struct bcm54xx_phy_priv *priv = phydev->priv; + int ret; + + /* Aneg firstly. */ + if (priv->brr_mode) { + /* BCM54811 is only capable of autonegotiation in IEEE mode */ + phydev->autoneg = 0; + ret = bcm_config_lre_aneg(phydev, false); + } else { + ret = genphy_config_aneg(phydev); + } + + if (ret) + return ret; + + /* Then we can set up the delay and swap. */ + return bcm5481x_config_delay_swap(phydev); } struct bcm54616s_phy_priv { @@ -1062,6 +1210,203 @@ static void bcm54xx_link_change_notify(struct phy_device *phydev) bcm_phy_write_exp(phydev, MII_BCM54XX_EXP_EXP08, ret); } +static int lre_read_master_slave(struct phy_device *phydev) +{ + int cfg = MASTER_SLAVE_CFG_UNKNOWN, state; + int val; + + /* In BroadR-Reach mode we are always capable of master-slave + * and there is no preferred master or slave configuration + */ + phydev->master_slave_get = MASTER_SLAVE_CFG_UNKNOWN; + phydev->master_slave_state = MASTER_SLAVE_STATE_UNKNOWN; + + val = phy_read(phydev, MII_BCM54XX_LRECR); + if (val < 0) + return val; + + if ((val & LRECR_LDSEN) == 0) { + if (val & LRECR_MASTER) + cfg = MASTER_SLAVE_CFG_MASTER_FORCE; + else + cfg = MASTER_SLAVE_CFG_SLAVE_FORCE; + } + + val = phy_read(phydev, MII_BCM54XX_LRELDSE); + if (val < 0) + return val; + + if (val & LDSE_MASTER) + state = MASTER_SLAVE_STATE_MASTER; + else + state = MASTER_SLAVE_STATE_SLAVE; + + phydev->master_slave_get = cfg; + phydev->master_slave_state = state; + + return 0; +} + +/* Read LDS Link Partner Ability in BroadR-Reach mode */ +static int lre_read_lpa(struct phy_device *phydev) +{ + int i, lrelpa; + + if (phydev->autoneg != AUTONEG_ENABLE) { + if (!phydev->autoneg_complete) { + /* aneg not yet done, reset all relevant bits */ + for (i = 0; i < ARRAY_SIZE(lds_br_bits); i++) + linkmode_clear_bit(lds_br_bits[i], + phydev->lp_advertising); + + return 0; + } + + /* Long-Distance Signaling Link Partner Ability */ + lrelpa = phy_read(phydev, MII_BCM54XX_LRELPA); + if (lrelpa < 0) + return lrelpa; + + linkmode_mod_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, + phydev->lp_advertising, + lrelpa & LRELPA_PAUSE_ASYM); + linkmode_mod_bit(ETHTOOL_LINK_MODE_Pause_BIT, + phydev->lp_advertising, + lrelpa & LRELPA_PAUSE); + linkmode_mod_bit(ETHTOOL_LINK_MODE_100baseT1_Full_BIT, + phydev->lp_advertising, + lrelpa & LRELPA_100_1PAIR); + linkmode_mod_bit(ETHTOOL_LINK_MODE_10baseT1BRR_Full_BIT, + phydev->lp_advertising, + lrelpa & LRELPA_10_1PAIR); + } else { + linkmode_zero(phydev->lp_advertising); + } + + return 0; +} + +static int lre_read_status_fixed(struct phy_device *phydev) +{ + int lrecr = phy_read(phydev, MII_BCM54XX_LRECR); + + if (lrecr < 0) + return lrecr; + + phydev->duplex = DUPLEX_FULL; + + if (lrecr & LRECR_SPEED100) + phydev->speed = SPEED_100; + else + phydev->speed = SPEED_10; + + return 0; +} + +/** + * lre_update_link - update link status in @phydev + * @phydev: target phy_device struct + * Return: 0 on success, < 0 on error + * + * Description: Update the value in phydev->link to reflect the + * current link value. In order to do this, we need to read + * the status register twice, keeping the second value. + * This is a genphy_update_link modified to work on LRE registers + * of BroadR-Reach PHY + */ +static int lre_update_link(struct phy_device *phydev) +{ + int status = 0, lrecr; + + lrecr = phy_read(phydev, MII_BCM54XX_LRECR); + if (lrecr < 0) + return lrecr; + + /* Autoneg is being started, therefore disregard BMSR value and + * report link as down. + */ + if (lrecr & BMCR_ANRESTART) + goto done; + + /* The link state is latched low so that momentary link + * drops can be detected. Do not double-read the status + * in polling mode to detect such short link drops except + * the link was already down. + */ + if (!phy_polling_mode(phydev) || !phydev->link) { + status = phy_read(phydev, MII_BCM54XX_LRESR); + if (status < 0) + return status; + else if (status & LRESR_LSTATUS) + goto done; + } + + /* Read link and autonegotiation status */ + status = phy_read(phydev, MII_BCM54XX_LRESR); + if (status < 0) + return status; +done: + phydev->link = status & LRESR_LSTATUS ? 1 : 0; + phydev->autoneg_complete = status & LRESR_LDSCOMPLETE ? 1 : 0; + + /* Consider the case that autoneg was started and "aneg complete" + * bit has been reset, but "link up" bit not yet. + */ + if (phydev->autoneg == AUTONEG_ENABLE && !phydev->autoneg_complete) + phydev->link = 0; + + return 0; +} + +/* Get the status in BroadRReach mode just like genphy_read_status does +* in normal mode +*/ +static int bcm54811_lre_read_status(struct phy_device *phydev) +{ + int err, old_link = phydev->link; + + /* Update the link, but return if there was an error */ + err = lre_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->speed = SPEED_UNKNOWN; + phydev->duplex = DUPLEX_UNKNOWN; + phydev->pause = 0; + phydev->asym_pause = 0; + + err = lre_read_master_slave(phydev); + if (err < 0) + return err; + + /* Read LDS Link Partner Ability */ + err = lre_read_lpa(phydev); + if (err < 0) + return err; + + if (phydev->autoneg == AUTONEG_ENABLE && phydev->autoneg_complete) + phy_resolve_aneg_linkmode(phydev); + else if (phydev->autoneg == AUTONEG_DISABLE) + err = lre_read_status_fixed(phydev); + + return err; +} + +static int bcm54811_read_status(struct phy_device *phydev) +{ + struct bcm54xx_phy_priv *priv = phydev->priv; + + if (priv->brr_mode) + return bcm54811_lre_read_status(phydev); + + return genphy_read_status(phydev); +} + static struct phy_driver broadcom_drivers[] = { { .phy_id = PHY_ID_BCM5411, @@ -1211,10 +1556,12 @@ static struct phy_driver broadcom_drivers[] = { .get_strings = bcm_phy_get_strings, .get_stats = bcm54xx_get_stats, .probe = bcm54xx_phy_probe, - .config_init = bcm54811_config_init, - .config_aneg = bcm5481_config_aneg, + .config_init = bcm54xx_config_init, + .config_aneg = bcm54811_config_aneg, .config_intr = bcm_phy_config_intr, .handle_interrupt = bcm_phy_handle_interrupt, + .read_status = bcm54811_read_status, + .get_features = bcm5481x_read_abilities, .suspend = bcm54xx_suspend, .resume = bcm54xx_resume, .link_change_notify = bcm54xx_link_change_notify, |