diff mbox series

[v11,4/4] net: phy: bcm-phy-lib: Implement BroadR-Reach link modes

Message ID 20240708102716.1246571-5-kamilh@axis.com (mailing list archive)
State Superseded
Delegated to: Netdev Maintainers
Headers show
Series net: phy: bcm5481x: add support for BroadR-Reach mode | expand

Checks

Context Check Description
netdev/series_format success Posting correctly formatted
netdev/tree_selection success Guessed tree name to be net-next
netdev/ynl success Generated files up to date; no warnings/errors; no diff in generated;
netdev/fixes_present success Fixes tag not required for -next series
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 816 this patch: 816
netdev/build_tools success No tools touched, skip
netdev/cc_maintainers success CCed 9 of 9 maintainers
netdev/build_clang success Errors and warnings before: 821 this patch: 821
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/deprecated_api success None detected
netdev/check_selftest success No net selftest shell script
netdev/verify_fixes success No Fixes tag
netdev/build_allmodconfig_warn success Errors and warnings before: 821 this patch: 821
netdev/checkpatch success total: 0 errors, 0 warnings, 0 checks, 596 lines checked
netdev/build_clang_rust success No Rust files in patch. Skipping build
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0
netdev/contest success net-next-2024-07-11--18-00 (tests: 696)

Commit Message

Kamil Horák (2N) July 8, 2024, 10:27 a.m. UTC
Implement single-pair BroadR-Reach modes on bcm5481x PHY by Broadcom.
Create set of functions alternative to IEEE 802.3 to handle
configuration of these modes on compatible Broadcom PHYs.
There is only subset of capabilities supported because of limited
collection of hardware available for the development.
For BroadR-Reach capable PHYs, the LRE (Long Reach Ethernet)
alternative register set is handled. Only bcm54811 PHY is verified,
for bcm54810, there is some support possible but untested. There
is no auto-negotiation of the link parameters (called LDS in the
Broadcom terminology, Long-Distance Signaling) for bcm54811.
It should be possible to enable LDS for bcm54810.

Signed-off-by: Kamil Horák (2N) <kamilh@axis.com>
---
 drivers/net/phy/bcm-phy-lib.c | 115 ++++++++++
 drivers/net/phy/bcm-phy-lib.h |   4 +
 drivers/net/phy/broadcom.c    | 405 ++++++++++++++++++++++++++++++++--
 3 files changed, 506 insertions(+), 18 deletions(-)

Comments

Andrew Lunn July 11, 2024, 7:01 p.m. UTC | #1
> +static int bcm5481x_get_brrmode(struct phy_device *phydev, u8 *data)
>  {
> -	int err, reg;
> +	int reg;
>  
> -	/* 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)

bcm_phy_read_exp() could fail. So you should keep the test. Also, the
caller of this function does look at the return value.

> +/**
> + * 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 current brr mode setting of the PHY.
> + * Note that the LRE and IEEE sets of abilities are disjunct.
> + *
> + * Returns: 0 on success, < 0 on failure
> + */
> +static int bcm5481x_read_abilities(struct phy_device *phydev)
> +{
> +	int i, val, err;
> +	u8 brr_mode;
> +
> +	for (i = 0; i < ARRAY_SIZE(bcm54811_linkmodes); i++)
> +		linkmode_clear_bit(bcm54811_linkmodes[i], phydev->supported);
> +
> +	err = bcm5481x_get_brrmode(phydev, &brr_mode);

> +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 (on)
> +		reg |= BCM54810_EXP_BROADREACH_LRE_MISC_CTL_EN;
> +	else
> +		reg &= ~BCM54810_EXP_BROADREACH_LRE_MISC_CTL_EN;
> +

> +static int bcm54811_config_init(struct phy_device *phydev)
> +{
> +	struct device_node *np = phydev->mdio.dev.of_node;
> +	bool brr = false;
> +	int err, reg;
> +
>  	err = bcm54xx_config_init(phydev);
>  
>  	/* Enable CLK125 MUX on LED4 if ref clock is enabled. */
> @@ -576,29 +687,80 @@ static int bcm54811_config_init(struct phy_device *phydev)
>  			return err;
>  	}
>  
> -	return err;
> +	/* Configure BroadR-Reach function. */
> +	brr = of_property_read_bool(np, "brr-mode");
> +
> +	/* With BCM54811, BroadR-Reach implies no autoneg */
> +	if (brr)
> +		phydev->autoneg = 0;
> +
> +	return bcm5481x_set_brrmode(phydev, brr);
>  }

The ordering seems a bit strange here.

phy_probe() will call phydrv->get_features. At this point, the PHY is
in whatever mode it resets to, or maybe what it is strapped
to. phydev->supported could thus be set to standard IEEE modes,
despite the board design is actually for BroadR-Reach.

Sometime later, when the MAC is connected to the PHY config_init() is
called. At that point, you poke around in DT and find how the PHY is
connected to the cable. At that point, you set the PHY mode, and
change phydev->supported to reflect reality.

I really think that reading DT should be done much earlier, maybe in
the driver probe function, or maybe get_features. get_features should
always return the correct values from the board.

> +static int bcm5481_config_aneg(struct phy_device *phydev)
> +{
> +	u8 brr_mode;
> +	int ret;
> +
> +	ret = bcm5481x_get_brrmode(phydev, &brr_mode);

Rather than read it from the hardware every single time, could you
store the DT value in bcm54xx_phy_priv ?

> +/* Read LDS Link Partner Ability in BroadR-Reach mode */
> +static int bcm_read_lpa(struct phy_device *phydev)

This function seems to be missing an lds or lre prefix.

> +static int bcm_read_status_fixed(struct phy_device *phydev)

and here. Please make sure the naming is consistent. Anything which
only accesses lre or lds registers should make that clear in its name.

> +static int bcm54811_read_status(struct phy_device *phydev)
> +{
> +	u8 brr_mode;
> +	int err;
> +
> +	err = bcm5481x_get_brrmode(phydev, &brr_mode);
> +
> +	if (err)
> +		return err;
> +
> +	if (brr_mode) {
> +		/* Get the status in BroadRReach mode just like
> +		 *   genphy_read_status does in normal mode
> +		 */
> +
> +		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 = bcm_read_master_slave(phydev);
> +		if (err < 0)
> +			return err;
> +
> +		/* Read LDS Link Partner Ability */
> +		err = bcm_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 = bcm_read_status_fixed(phydev);
> +			if (err < 0)
> +				return err;
> +		}

This would probably look better if you pulled this code out into a
helper bcm54811_lre_read_status().

    Andrew

---
pw-bot: cr
Kamil Horák (2N) July 12, 2024, 3:10 p.m. UTC | #2
On 7/11/24 21:01, Andrew Lunn wrote:
>> +static int bcm5481x_get_brrmode(struct phy_device *phydev, u8 *data)
>>   {
>> -	int err, reg;
>> +	int reg;
>>   
>> -	/* 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)
> bcm_phy_read_exp() could fail. So you should keep the test. Also, the
> caller of this function does look at the return value.
True - it can at least return -EOPNOTSUPP from __mdiobus_read()
Trying to handle it.

This neglect can be found elsewhere such as bcm-phy-ptp.c  and eg. 
bcm54xx_config_init()

function. I feel that at least the latest one should be fixed but it 
would be unrelated to bcm54811,

so leaving it as-is for now.

>
>> +/**
>> + * 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 current brr mode setting of the PHY.
>> + * Note that the LRE and IEEE sets of abilities are disjunct.
>> + *
>> + * Returns: 0 on success, < 0 on failure
>> + */
>> +static int bcm5481x_read_abilities(struct phy_device *phydev)
>> +{
>> +	int i, val, err;
>> +	u8 brr_mode;
>> +
>> +	for (i = 0; i < ARRAY_SIZE(bcm54811_linkmodes); i++)
>> +		linkmode_clear_bit(bcm54811_linkmodes[i], phydev->supported);
>> +
>> +	err = bcm5481x_get_brrmode(phydev, &brr_mode);
>> +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 (on)
>> +		reg |= BCM54810_EXP_BROADREACH_LRE_MISC_CTL_EN;
>> +	else
>> +		reg &= ~BCM54810_EXP_BROADREACH_LRE_MISC_CTL_EN;
>> +
>> +static int bcm54811_config_init(struct phy_device *phydev)
>> +{
>> +	struct device_node *np = phydev->mdio.dev.of_node;
>> +	bool brr = false;
>> +	int err, reg;
>> +
>>   	err = bcm54xx_config_init(phydev);
>>   
>>   	/* Enable CLK125 MUX on LED4 if ref clock is enabled. */
>> @@ -576,29 +687,80 @@ static int bcm54811_config_init(struct phy_device *phydev)
>>   			return err;
>>   	}
>>   
>> -	return err;
>> +	/* Configure BroadR-Reach function. */
>> +	brr = of_property_read_bool(np, "brr-mode");
>> +
>> +	/* With BCM54811, BroadR-Reach implies no autoneg */
>> +	if (brr)
>> +		phydev->autoneg = 0;
>> +
>> +	return bcm5481x_set_brrmode(phydev, brr);
>>   }
> The ordering seems a bit strange here.
>
> phy_probe() will call phydrv->get_features. At this point, the PHY is
> in whatever mode it resets to, or maybe what it is strapped
> to. phydev->supported could thus be set to standard IEEE modes,
> despite the board design is actually for BroadR-Reach.
>
> Sometime later, when the MAC is connected to the PHY config_init() is
> called. At that point, you poke around in DT and find how the PHY is
> connected to the cable. At that point, you set the PHY mode, and
> change phydev->supported to reflect reality.
>
> I really think that reading DT should be done much earlier, maybe in
> the driver probe function, or maybe get_features. get_features should
> always return the correct values from the board.

Also true. This is a remnant of original approach using phy-tunable 
rather than dt to select the mode.

I do not expect a hot swappable design to be possible to appear, so 
fixing the logic as suggested.

>
>> +static int bcm5481_config_aneg(struct phy_device *phydev)
>> +{
>> +	u8 brr_mode;
>> +	int ret;
>> +
>> +	ret = bcm5481x_get_brrmode(phydev, &brr_mode);
> Rather than read it from the hardware every single time, could you
> store the DT value in bcm54xx_phy_priv ?

Done. Now we rely on the DT setting and never read the PHY state. It is 
vulnerable to external manipulation

of MDIO registers and PHY reset as both hardware and software (bit 15 of 
register 0 in both

IEEE and LRE modes) reset switch to IEEE mode.

>
>> +/* Read LDS Link Partner Ability in BroadR-Reach mode */
>> +static int bcm_read_lpa(struct phy_device *phydev)
> This function seems to be missing an lds or lre prefix.
>
>> +static int bcm_read_status_fixed(struct phy_device *phydev)
> and here. Please make sure the naming is consistent. Anything which
> only accesses lre or lds registers should make that clear in its name.

I feel this requires renaming stuff like bcm_read_status_fixed to 
lre_read_status_fixed etc. in every location

only handling LRE stuff, same logic already applied to eg. lre_update_link.

>
>> +static int bcm54811_read_status(struct phy_device *phydev)
>> +{
>> +	u8 brr_mode;
>> +	int err;
>> +
>> +	err = bcm5481x_get_brrmode(phydev, &brr_mode);
>> +
>> +	if (err)
>> +		return err;
>> +
>> +	if (brr_mode) {
>> +		/* Get the status in BroadRReach mode just like
>> +		 *   genphy_read_status does in normal mode
>> +		 */
>> +
>> +		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 = bcm_read_master_slave(phydev);
>> +		if (err < 0)
>> +			return err;
>> +
>> +		/* Read LDS Link Partner Ability */
>> +		err = bcm_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 = bcm_read_status_fixed(phydev);
>> +			if (err < 0)
>> +				return err;
>> +		}
> This would probably look better if you pulled this code out into a
> helper bcm54811_lre_read_status().
done

I've of course verified again that it works on the target device but 
unfortunately, I have no possibility

to test it on anything using BCM54811 in IEEE mode and with BCM54810 
which might be interesting for

someone using that PHY.


Kamil


>
>      Andrew
>
> ---
> pw-bot: cr
Andrew Lunn July 12, 2024, 3:45 p.m. UTC | #3
On Fri, Jul 12, 2024 at 05:10:48PM +0200, Kamil Horák (2N) wrote:
> 
> On 7/11/24 21:01, Andrew Lunn wrote:
> > > +static int bcm5481x_get_brrmode(struct phy_device *phydev, u8 *data)
> > >   {
> > > -	int err, reg;
> > > +	int reg;
> > > -	/* 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)
> > bcm_phy_read_exp() could fail. So you should keep the test. Also, the
> > caller of this function does look at the return value.
> True - it can at least return -EOPNOTSUPP from __mdiobus_read()
> Trying to handle it.
> 
> This neglect can be found elsewhere such as bcm-phy-ptp.c  and eg.
> bcm54xx_config_init()
> 
> function. I feel that at least the latest one should be fixed but it would
> be unrelated to bcm54811,
> 
> so leaving it as-is for now.

In general PHY drivers are a bit hit and miss with checking error
codes. If the first access works, it is very likely all further
accesses will work. If they fail, the hardware is probably dead and
there is little you can do about it other than report the error. So i
would say probe, suspend and resume should always check the error
codes, since that is where clock problems are likely to be. But after
that it is good practice to check error codes, but a driver is
unlikely to be NACKed because of missing checks.

> Done. Now we rely on the DT setting and never read the PHY state. It is
> vulnerable to external manipulation
> 
> of MDIO registers and PHY reset as both hardware and software (bit 15 of
> register 0 in both
> 
> IEEE and LRE modes) reset switch to IEEE mode.

I don't think this is any worse. With the old code you would of
silently swapped to standard IEEE modes, which cannot work. Now you
continue programming BRR registers, which just get ignored because it
is no longer in that mode.

But if somebody performed some sort of external manipulation, all bets
are off anyway. 

	Andrew
diff mbox series

Patch

diff --git a/drivers/net/phy/bcm-phy-lib.c b/drivers/net/phy/bcm-phy-lib.c
index 876f28fd8256..6c52f7dda514 100644
--- a/drivers/net/phy/bcm-phy-lib.c
+++ b/drivers/net/phy/bcm-phy-lib.c
@@ -794,6 +794,49 @@  static int _bcm_phy_cable_test_get_status(struct phy_device *phydev,
 	return ret;
 }
 
+static int bcm_setup_lre_forced(struct phy_device *phydev)
+{
+	u16 ctl = 0;
+
+	phydev->pause = 0;
+	phydev->asym_pause = 0;
+
+	if (phydev->speed == SPEED_100)
+		ctl |= LRECR_SPEED100;
+
+	if (phydev->duplex != DUPLEX_FULL)
+		return -EOPNOTSUPP;
+
+	return phy_modify(phydev, MII_BCM54XX_LRECR, LRECR_SPEED100, ctl);
+}
+
+/**
+ * bcm_linkmode_adv_to_lre_adv_t - translate linkmode advertisement to LDS
+ * @advertising: the linkmode advertisement settings
+ * Return: LDS Auto-Negotiation Advertised Ability register value
+ *
+ * A small helper function that translates linkmode advertisement
+ * settings to phy LDS autonegotiation advertisements for the
+ * MII_BCM54XX_LREANAA register of Broadcom PHYs capable of LDS
+ */
+static u32 bcm_linkmode_adv_to_lre_adv_t(unsigned long *advertising)
+{
+	u32 result = 0;
+
+	if (linkmode_test_bit(ETHTOOL_LINK_MODE_10baseT1BRR_Full_BIT,
+			      advertising))
+		result |= LREANAA_10_1PAIR;
+	if (linkmode_test_bit(ETHTOOL_LINK_MODE_100baseT1_Full_BIT,
+			      advertising))
+		result |= LREANAA_100_1PAIR;
+	if (linkmode_test_bit(ETHTOOL_LINK_MODE_Pause_BIT, advertising))
+		result |= LRELPA_PAUSE;
+	if (linkmode_test_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, advertising))
+		result |= LRELPA_PAUSE_ASYM;
+
+	return result;
+}
+
 int bcm_phy_cable_test_start(struct phy_device *phydev)
 {
 	return _bcm_phy_cable_test_start(phydev, false);
@@ -1066,6 +1109,78 @@  int bcm_phy_led_brightness_set(struct phy_device *phydev,
 }
 EXPORT_SYMBOL_GPL(bcm_phy_led_brightness_set);
 
+int bcm_setup_lre_master_slave(struct phy_device *phydev)
+{
+	u16 ctl = 0;
+
+	switch (phydev->master_slave_set) {
+	case MASTER_SLAVE_CFG_MASTER_PREFERRED:
+	case MASTER_SLAVE_CFG_MASTER_FORCE:
+		ctl = LRECR_MASTER;
+		break;
+	case MASTER_SLAVE_CFG_SLAVE_PREFERRED:
+	case MASTER_SLAVE_CFG_SLAVE_FORCE:
+		break;
+	case MASTER_SLAVE_CFG_UNKNOWN:
+	case MASTER_SLAVE_CFG_UNSUPPORTED:
+		return 0;
+	default:
+		phydev_warn(phydev, "Unsupported Master/Slave mode\n");
+		return -EOPNOTSUPP;
+	}
+
+	return phy_modify_changed(phydev, MII_BCM54XX_LRECR, LRECR_MASTER, ctl);
+}
+EXPORT_SYMBOL_GPL(bcm_setup_lre_master_slave);
+
+int bcm_config_lre_aneg(struct phy_device *phydev, bool changed)
+{
+	int err;
+
+	if (genphy_config_eee_advert(phydev))
+		changed = true;
+
+	err = bcm_setup_lre_master_slave(phydev);
+	if (err < 0)
+		return err;
+	else if (err)
+		changed = true;
+
+	if (phydev->autoneg != AUTONEG_ENABLE)
+		return bcm_setup_lre_forced(phydev);
+
+	err = bcm_config_lre_advert(phydev);
+	if (err < 0)
+		return err;
+	else if (err)
+		changed = true;
+
+	return genphy_check_and_restart_aneg(phydev, changed);
+}
+EXPORT_SYMBOL_GPL(bcm_config_lre_aneg);
+
+/**
+ * bcm_config_lre_advert - sanitize and advertise Long-Distance Signaling
+ *  auto-negotiation parameters
+ * @phydev: target phy_device struct
+ * Return:  0 if the PHY's advertisement hasn't changed, < 0 on error,
+ *          > 0 if it has changed
+ *
+ * Writes MII_BCM54XX_LREANAA with the appropriate values. The values are to be
+ *   sanitized before, to make sure we only advertise what is supported.
+ *  The sanitization is done already in phy_ethtool_ksettings_set()
+ */
+int bcm_config_lre_advert(struct phy_device *phydev)
+{
+	u32 adv = bcm_linkmode_adv_to_lre_adv_t(phydev->advertising);
+
+	/* Setup BroadR-Reach mode advertisement */
+	return phy_modify_changed(phydev, MII_BCM54XX_LREANAA,
+				 LRE_ADVERTISE_ALL | LREANAA_PAUSE |
+				 LREANAA_PAUSE_ASYM, adv);
+}
+EXPORT_SYMBOL_GPL(bcm_config_lre_advert);
+
 MODULE_DESCRIPTION("Broadcom PHY Library");
 MODULE_LICENSE("GPL v2");
 MODULE_AUTHOR("Broadcom Corporation");
diff --git a/drivers/net/phy/bcm-phy-lib.h b/drivers/net/phy/bcm-phy-lib.h
index b52189e45a84..bceddbc860eb 100644
--- a/drivers/net/phy/bcm-phy-lib.h
+++ b/drivers/net/phy/bcm-phy-lib.h
@@ -121,4 +121,8 @@  irqreturn_t bcm_phy_wol_isr(int irq, void *dev_id);
 int bcm_phy_led_brightness_set(struct phy_device *phydev,
 			       u8 index, enum led_brightness value);
 
+int bcm_setup_lre_master_slave(struct phy_device *phydev);
+int bcm_config_lre_aneg(struct phy_device *phydev, bool changed);
+int bcm_config_lre_advert(struct phy_device *phydev);
+
 #endif /* _LINUX_BCM_PHY_LIB_H */
diff --git a/drivers/net/phy/broadcom.c b/drivers/net/phy/broadcom.c
index 370e4ed45098..304ed78315de 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.
@@ -38,6 +40,28 @@  struct bcm54xx_phy_priv {
 	bool	wake_irq_enabled;
 };
 
+/* 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)
 {
 	struct bcm54xx_phy_priv *priv = phydev->priv;
@@ -553,18 +577,105 @@  static int bcm54810_write_mmd(struct phy_device *phydev, int devnum, u16 regnum,
 	return -EOPNOTSUPP;
 }
 
-static int bcm54811_config_init(struct phy_device *phydev)
+static int bcm5481x_get_brrmode(struct phy_device *phydev, u8 *data)
 {
-	int err, reg;
+	int reg;
 
-	/* 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)
+
+	*data = (reg & BCM54810_EXP_BROADREACH_LRE_MISC_CTL_EN) ? 1 : 0;
+
+	return 0;
+}
+
+/**
+ * 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 current brr mode setting of the PHY.
+ * Note that the LRE and IEEE sets of abilities are disjunct.
+ *
+ * Returns: 0 on success, < 0 on failure
+ */
+static int bcm5481x_read_abilities(struct phy_device *phydev)
+{
+	int i, val, err;
+	u8 brr_mode;
+
+	for (i = 0; i < ARRAY_SIZE(bcm54811_linkmodes); i++)
+		linkmode_clear_bit(bcm54811_linkmodes[i], phydev->supported);
+
+	err = bcm5481x_get_brrmode(phydev, &brr_mode);
+	if (err)
 		return err;
 
+	if (brr_mode) {
+		linkmode_set_bit_array(phy_basic_ports_array,
+				       ARRAY_SIZE(phy_basic_ports_array),
+				       phydev->supported);
+
+		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 genphy_read_abilities(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 (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;
+
+	/* Update the abilities based on the current brr on/off setting */
+	err = bcm5481x_read_abilities(phydev);
+	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 device_node *np = phydev->mdio.dev.of_node;
+	bool brr = false;
+	int err, reg;
+
 	err = bcm54xx_config_init(phydev);
 
 	/* Enable CLK125 MUX on LED4 if ref clock is enabled. */
@@ -576,29 +687,80 @@  static int bcm54811_config_init(struct phy_device *phydev)
 			return err;
 	}
 
-	return err;
+	/* Configure BroadR-Reach function. */
+	brr = of_property_read_bool(np, "brr-mode");
+
+	/* With BCM54811, BroadR-Reach implies no autoneg */
+	if (brr)
+		phydev->autoneg = 0;
+
+	return bcm5481x_set_brrmode(phydev, brr);
 }
 
-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)
+{
+	u8 brr_mode;
+	int ret;
+
+	ret = bcm5481x_get_brrmode(phydev, &brr_mode);
+	if (ret)
+		return ret;
+
+	/* Aneg firstly. */
+	if (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)
+{
+	u8 brr_mode;
+	int ret;
+
+	/* Aneg firstly. */
+	ret = bcm5481x_get_brrmode(phydev, &brr_mode);
+	if (ret)
+		return ret;
+
+	if (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 +1224,211 @@  static void bcm54xx_link_change_notify(struct phy_device *phydev)
 	bcm_phy_write_exp(phydev, MII_BCM54XX_EXP_EXP08, ret);
 }
 
+static int bcm_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 bcm_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 bcm_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;
+}
+
+static int bcm54811_read_status(struct phy_device *phydev)
+{
+	u8 brr_mode;
+	int err;
+
+	err = bcm5481x_get_brrmode(phydev, &brr_mode);
+
+	if (err)
+		return err;
+
+	if (brr_mode) {
+		/* Get the status in BroadRReach mode just like
+		 *   genphy_read_status does in normal mode
+		 */
+
+		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 = bcm_read_master_slave(phydev);
+		if (err < 0)
+			return err;
+
+		/* Read LDS Link Partner Ability */
+		err = bcm_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 = bcm_read_status_fixed(phydev);
+			if (err < 0)
+				return err;
+		}
+	} else {
+		err = genphy_read_status(phydev);
+	}
+
+	return err;
+}
+
 static struct phy_driver broadcom_drivers[] = {
 {
 	.phy_id		= PHY_ID_BCM5411,
@@ -1212,9 +1579,11 @@  static struct phy_driver broadcom_drivers[] = {
 	.get_stats	= bcm54xx_get_stats,
 	.probe		= bcm54xx_phy_probe,
 	.config_init    = bcm54811_config_init,
-	.config_aneg    = bcm5481_config_aneg,
+	.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,