diff mbox series

[net-next,v2,13/14] net: phy: add Broadcom BCM84881 PHY driver

Message ID E1ieKov-0004vw-Dk@rmk-PC.armlinux.org.uk
State Changes Requested
Delegated to: David Miller
Headers show
Series Add support for SFP+ copper modules | expand

Commit Message

Russell King (Oracle) Dec. 9, 2019, 3:19 p.m. UTC
Add a rudimentary Clause 45 driver for the BCM84881 PHY, found on
Methode DM7052 SFPs.

Signed-off-by: Russell King <rmk+kernel@armlinux.org.uk>
---
 drivers/net/phy/Kconfig    |   6 +
 drivers/net/phy/Makefile   |   1 +
 drivers/net/phy/bcm84881.c | 269 +++++++++++++++++++++++++++++++++++++
 3 files changed, 276 insertions(+)
 create mode 100644 drivers/net/phy/bcm84881.c

Comments

Florian Fainelli Dec. 10, 2019, 5:34 p.m. UTC | #1
On 12/9/19 7:19 AM, Russell King wrote:
> Add a rudimentary Clause 45 driver for the BCM84881 PHY, found on
> Methode DM7052 SFPs.
> 
> Signed-off-by: Russell King <rmk+kernel@armlinux.org.uk>

Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>

> ---
>  drivers/net/phy/Kconfig    |   6 +
>  drivers/net/phy/Makefile   |   1 +
>  drivers/net/phy/bcm84881.c | 269 +++++++++++++++++++++++++++++++++++++
>  3 files changed, 276 insertions(+)
>  create mode 100644 drivers/net/phy/bcm84881.c
> 
> diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
> index fe602648b99f..41272106dea9 100644
> --- a/drivers/net/phy/Kconfig
> +++ b/drivers/net/phy/Kconfig
> @@ -329,6 +329,12 @@ config BROADCOM_PHY
>  	  Currently supports the BCM5411, BCM5421, BCM5461, BCM54616S, BCM5464,
>  	  BCM5481, BCM54810 and BCM5482 PHYs.
>  
> +config BCM84881_PHY
> +	bool "Broadcom BCM84881 PHY"
> +	depends on PHYLIB=y
> +	---help---
> +	  Support the Broadcom BCM84881 PHY.

Cannot we make this tristate, I believe we cannot until there are more
fundamental issues (that you just reported) to be fixed, correct?
Russell King (Oracle) Dec. 10, 2019, 5:58 p.m. UTC | #2
On Tue, Dec 10, 2019 at 09:34:16AM -0800, Florian Fainelli wrote:
> On 12/9/19 7:19 AM, Russell King wrote:
> > Add a rudimentary Clause 45 driver for the BCM84881 PHY, found on
> > Methode DM7052 SFPs.
> > 
> > Signed-off-by: Russell King <rmk+kernel@armlinux.org.uk>
> 
> Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
> 
> > ---
> >  drivers/net/phy/Kconfig    |   6 +
> >  drivers/net/phy/Makefile   |   1 +
> >  drivers/net/phy/bcm84881.c | 269 +++++++++++++++++++++++++++++++++++++
> >  3 files changed, 276 insertions(+)
> >  create mode 100644 drivers/net/phy/bcm84881.c
> > 
> > diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
> > index fe602648b99f..41272106dea9 100644
> > --- a/drivers/net/phy/Kconfig
> > +++ b/drivers/net/phy/Kconfig
> > @@ -329,6 +329,12 @@ config BROADCOM_PHY
> >  	  Currently supports the BCM5411, BCM5421, BCM5461, BCM54616S, BCM5464,
> >  	  BCM5481, BCM54810 and BCM5482 PHYs.
> >  
> > +config BCM84881_PHY
> > +	bool "Broadcom BCM84881 PHY"
> > +	depends on PHYLIB=y
> > +	---help---
> > +	  Support the Broadcom BCM84881 PHY.
> 
> Cannot we make this tristate, I believe we cannot until there are more
> fundamental issues (that you just reported) to be fixed, correct?

Indeed.  The problem I saw was that although the bcm84881 has the
PHY correctly described, for whatever reason, the module was not
loaded.

What I think is going in is that with modern udev userspace,
request_module() is not functional, and we do not publish the
module IDs for Clause 45 PHYs via uevent.  Consequently, there
exists no mechanism to load a Clause 45 PHY driver from the
filesystem.
Russell King (Oracle) Dec. 10, 2019, 6:46 p.m. UTC | #3
On Tue, Dec 10, 2019 at 05:58:37PM +0000, Russell King - ARM Linux admin wrote:
> On Tue, Dec 10, 2019 at 09:34:16AM -0800, Florian Fainelli wrote:
> > On 12/9/19 7:19 AM, Russell King wrote:
> > > Add a rudimentary Clause 45 driver for the BCM84881 PHY, found on
> > > Methode DM7052 SFPs.
> > > 
> > > Signed-off-by: Russell King <rmk+kernel@armlinux.org.uk>
> > 
> > Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
> > 
> > > ---
> > >  drivers/net/phy/Kconfig    |   6 +
> > >  drivers/net/phy/Makefile   |   1 +
> > >  drivers/net/phy/bcm84881.c | 269 +++++++++++++++++++++++++++++++++++++
> > >  3 files changed, 276 insertions(+)
> > >  create mode 100644 drivers/net/phy/bcm84881.c
> > > 
> > > diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
> > > index fe602648b99f..41272106dea9 100644
> > > --- a/drivers/net/phy/Kconfig
> > > +++ b/drivers/net/phy/Kconfig
> > > @@ -329,6 +329,12 @@ config BROADCOM_PHY
> > >  	  Currently supports the BCM5411, BCM5421, BCM5461, BCM54616S, BCM5464,
> > >  	  BCM5481, BCM54810 and BCM5482 PHYs.
> > >  
> > > +config BCM84881_PHY
> > > +	bool "Broadcom BCM84881 PHY"
> > > +	depends on PHYLIB=y
> > > +	---help---
> > > +	  Support the Broadcom BCM84881 PHY.
> > 
> > Cannot we make this tristate, I believe we cannot until there are more
> > fundamental issues (that you just reported) to be fixed, correct?
> 
> Indeed.  The problem I saw was that although the bcm84881 has the
> PHY correctly described, for whatever reason, the module was not
> loaded.
> 
> What I think is going in is that with modern udev userspace,
> request_module() is not functional, and we do not publish the
> module IDs for Clause 45 PHYs via uevent.  Consequently, there
> exists no mechanism to load a Clause 45 PHY driver from the
> filesystem.

I just attempted booting with sfp as a module, bcm84881 as a module.
sfp has to be loaded for the SFP cage to be recognised, so module
loading is availble prior to the PHY being known to the kernel.

The SFP is probed, and the PHY identified (via my debug):

[    7.209549] sfp sfp: phy PMA devid: 0xae02 0x5151

The PHY is not bound to its driver at this point.

We then try to connect to the PHY, but the support mask is zero,
so we know nothing about what modes this PHY supports:

[    7.215985] mvneta f1034000.ethernet eno2: phylink_sfp_connect_phy: s=00,00000000,00000000 a=00,00000000,00000000
[    7.215997] mvneta f1034000.ethernet eno2: validation with support 00,00000000,00000000 failed: -22
[    7.226343] sfp sfp: sfp_add_phy failed: -22

and we fail - because we are unable to identify what mode we should
configure the MAC side for, because we have no idea what the
capabilities of the PHY are at this stage.

We can't wait until we've called phylink_attach_phy(), because that
configures the PHY for the phy interface mode that was passed in.

There is no sign of the bcm84881 module being loaded.

This is the opposite problem to the one I recently posted about
regarding the 88e1111 issue, as here we rely on knowing something
about the PHY capabilities.

I think this is showing that phylink has a very difficult job trying
to match the capabilities of the SFP module with the capabilities of
the host MAC - we have no real idea what phy interface modes the
host MAC supports, or how the PHY is going to behave (which of the
many modes the PHY is going to choose for particular speeds.)
Plus the problem that the phylib support mask no longer reflects
the PHYs abilities after phy_attach_direct() has been called.

I thought I had something sorted, but only if the PHY driver is
built-in.
Russell King (Oracle) Dec. 19, 2019, 12:55 a.m. UTC | #4
On Tue, Dec 10, 2019 at 06:46:40PM +0000, Russell King - ARM Linux admin wrote:
> On Tue, Dec 10, 2019 at 05:58:37PM +0000, Russell King - ARM Linux admin wrote:
> > On Tue, Dec 10, 2019 at 09:34:16AM -0800, Florian Fainelli wrote:
> > > On 12/9/19 7:19 AM, Russell King wrote:
> > > > Add a rudimentary Clause 45 driver for the BCM84881 PHY, found on
> > > > Methode DM7052 SFPs.
> > > > 
> > > > Signed-off-by: Russell King <rmk+kernel@armlinux.org.uk>
> > > 
> > > Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
> > > 
> > > > ---
> > > >  drivers/net/phy/Kconfig    |   6 +
> > > >  drivers/net/phy/Makefile   |   1 +
> > > >  drivers/net/phy/bcm84881.c | 269 +++++++++++++++++++++++++++++++++++++
> > > >  3 files changed, 276 insertions(+)
> > > >  create mode 100644 drivers/net/phy/bcm84881.c
> > > > 
> > > > diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
> > > > index fe602648b99f..41272106dea9 100644
> > > > --- a/drivers/net/phy/Kconfig
> > > > +++ b/drivers/net/phy/Kconfig
> > > > @@ -329,6 +329,12 @@ config BROADCOM_PHY
> > > >  	  Currently supports the BCM5411, BCM5421, BCM5461, BCM54616S, BCM5464,
> > > >  	  BCM5481, BCM54810 and BCM5482 PHYs.
> > > >  
> > > > +config BCM84881_PHY
> > > > +	bool "Broadcom BCM84881 PHY"
> > > > +	depends on PHYLIB=y
> > > > +	---help---
> > > > +	  Support the Broadcom BCM84881 PHY.
> > > 
> > > Cannot we make this tristate, I believe we cannot until there are more
> > > fundamental issues (that you just reported) to be fixed, correct?
> > 
> > Indeed.  The problem I saw was that although the bcm84881 has the
> > PHY correctly described, for whatever reason, the module was not
> > loaded.
> > 
> > What I think is going in is that with modern udev userspace,
> > request_module() is not functional, and we do not publish the
> > module IDs for Clause 45 PHYs via uevent.  Consequently, there
> > exists no mechanism to load a Clause 45 PHY driver from the
> > filesystem.
> 
> I just attempted booting with sfp as a module, bcm84881 as a module.
> sfp has to be loaded for the SFP cage to be recognised, so module
> loading is availble prior to the PHY being known to the kernel.
> 
> The SFP is probed, and the PHY identified (via my debug):
> 
> [    7.209549] sfp sfp: phy PMA devid: 0xae02 0x5151
> 
> The PHY is not bound to its driver at this point.
> 
> We then try to connect to the PHY, but the support mask is zero,
> so we know nothing about what modes this PHY supports:
> 
> [    7.215985] mvneta f1034000.ethernet eno2: phylink_sfp_connect_phy: s=00,00000000,00000000 a=00,00000000,00000000
> [    7.215997] mvneta f1034000.ethernet eno2: validation with support 00,00000000,00000000 failed: -22
> [    7.226343] sfp sfp: sfp_add_phy failed: -22
> 
> and we fail - because we are unable to identify what mode we should
> configure the MAC side for, because we have no idea what the
> capabilities of the PHY are at this stage.
> 
> We can't wait until we've called phylink_attach_phy(), because that
> configures the PHY for the phy interface mode that was passed in.
> 
> There is no sign of the bcm84881 module being loaded.

Okay, I see what is going on - I just added debug into __request_module,
and got:

[  234.729163] __request_module: mdio:-10101110000000100101000101010001
[  234.732561] __request_module: mdio:-10101110000000100101000101010001
[  234.735729] __request_module: mdio:00000011011000100000000000000000

on inserting this SFP.  This comes from this:

#define MDIO_ID_FMT "%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d"
#define MDIO_ID_ARGS(_id) \
        (_id)>>31, ((_id)>>30) & 1, ((_id)>>29) & 1, ((_id)>>28) & 1,   \
        ((_id)>>27) & 1, ((_id)>>26) & 1, ((_id)>>25) & 1, ((_id)>>24) & 1, \
        ((_id)>>23) & 1, ((_id)>>22) & 1, ((_id)>>21) & 1, ((_id)>>20) & 1, \
        ((_id)>>19) & 1, ((_id)>>18) & 1, ((_id)>>17) & 1, ((_id)>>16) & 1, \
        ((_id)>>15) & 1, ((_id)>>14) & 1, ((_id)>>13) & 1, ((_id)>>12) & 1, \
        ((_id)>>11) & 1, ((_id)>>10) & 1, ((_id)>>9) & 1, ((_id)>>8) & 1, \
        ((_id)>>7) & 1, ((_id)>>6) & 1, ((_id)>>5) & 1, ((_id)>>4) & 1, \
        ((_id)>>3) & 1, ((_id)>>2) & 1, ((_id)>>1) & 1, (_id) & 1

coupled with:

static int phy_request_driver_module(struct phy_device *dev, int phy_id)
{
...
        ret = request_module(MDIO_MODULE_PREFIX MDIO_ID_FMT,
                             MDIO_ID_ARGS(phy_id));

The signed-ness of the parameter passed into MDIO_ID_ARGS() matters.
Hence, (0xae025151)>>31 becomes -1, and %d prints it as -1.

phy_id should be u32, just like it is everywhere else in phylib. Also,
MDIO_ID_ARGS() should probably be adapted to not care about the signed-
ness of its argument.

Thoughts?
Florian Fainelli Dec. 19, 2019, 12:57 a.m. UTC | #5
On 12/18/19 4:55 PM, Russell King - ARM Linux admin wrote:
> On Tue, Dec 10, 2019 at 06:46:40PM +0000, Russell King - ARM Linux admin wrote:
>> On Tue, Dec 10, 2019 at 05:58:37PM +0000, Russell King - ARM Linux admin wrote:
>>> On Tue, Dec 10, 2019 at 09:34:16AM -0800, Florian Fainelli wrote:
>>>> On 12/9/19 7:19 AM, Russell King wrote:
>>>>> Add a rudimentary Clause 45 driver for the BCM84881 PHY, found on
>>>>> Methode DM7052 SFPs.
>>>>>
>>>>> Signed-off-by: Russell King <rmk+kernel@armlinux.org.uk>
>>>>
>>>> Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
>>>>
>>>>> ---
>>>>>  drivers/net/phy/Kconfig    |   6 +
>>>>>  drivers/net/phy/Makefile   |   1 +
>>>>>  drivers/net/phy/bcm84881.c | 269 +++++++++++++++++++++++++++++++++++++
>>>>>  3 files changed, 276 insertions(+)
>>>>>  create mode 100644 drivers/net/phy/bcm84881.c
>>>>>
>>>>> diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
>>>>> index fe602648b99f..41272106dea9 100644
>>>>> --- a/drivers/net/phy/Kconfig
>>>>> +++ b/drivers/net/phy/Kconfig
>>>>> @@ -329,6 +329,12 @@ config BROADCOM_PHY
>>>>>  	  Currently supports the BCM5411, BCM5421, BCM5461, BCM54616S, BCM5464,
>>>>>  	  BCM5481, BCM54810 and BCM5482 PHYs.
>>>>>  
>>>>> +config BCM84881_PHY
>>>>> +	bool "Broadcom BCM84881 PHY"
>>>>> +	depends on PHYLIB=y
>>>>> +	---help---
>>>>> +	  Support the Broadcom BCM84881 PHY.
>>>>
>>>> Cannot we make this tristate, I believe we cannot until there are more
>>>> fundamental issues (that you just reported) to be fixed, correct?
>>>
>>> Indeed.  The problem I saw was that although the bcm84881 has the
>>> PHY correctly described, for whatever reason, the module was not
>>> loaded.
>>>
>>> What I think is going in is that with modern udev userspace,
>>> request_module() is not functional, and we do not publish the
>>> module IDs for Clause 45 PHYs via uevent.  Consequently, there
>>> exists no mechanism to load a Clause 45 PHY driver from the
>>> filesystem.
>>
>> I just attempted booting with sfp as a module, bcm84881 as a module.
>> sfp has to be loaded for the SFP cage to be recognised, so module
>> loading is availble prior to the PHY being known to the kernel.
>>
>> The SFP is probed, and the PHY identified (via my debug):
>>
>> [    7.209549] sfp sfp: phy PMA devid: 0xae02 0x5151
>>
>> The PHY is not bound to its driver at this point.
>>
>> We then try to connect to the PHY, but the support mask is zero,
>> so we know nothing about what modes this PHY supports:
>>
>> [    7.215985] mvneta f1034000.ethernet eno2: phylink_sfp_connect_phy: s=00,00000000,00000000 a=00,00000000,00000000
>> [    7.215997] mvneta f1034000.ethernet eno2: validation with support 00,00000000,00000000 failed: -22
>> [    7.226343] sfp sfp: sfp_add_phy failed: -22
>>
>> and we fail - because we are unable to identify what mode we should
>> configure the MAC side for, because we have no idea what the
>> capabilities of the PHY are at this stage.
>>
>> We can't wait until we've called phylink_attach_phy(), because that
>> configures the PHY for the phy interface mode that was passed in.
>>
>> There is no sign of the bcm84881 module being loaded.
> 
> Okay, I see what is going on - I just added debug into __request_module,
> and got:
> 
> [  234.729163] __request_module: mdio:-10101110000000100101000101010001
> [  234.732561] __request_module: mdio:-10101110000000100101000101010001
> [  234.735729] __request_module: mdio:00000011011000100000000000000000
> 
> on inserting this SFP.  This comes from this:
> 
> #define MDIO_ID_FMT "%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d"
> #define MDIO_ID_ARGS(_id) \
>         (_id)>>31, ((_id)>>30) & 1, ((_id)>>29) & 1, ((_id)>>28) & 1,   \
>         ((_id)>>27) & 1, ((_id)>>26) & 1, ((_id)>>25) & 1, ((_id)>>24) & 1, \
>         ((_id)>>23) & 1, ((_id)>>22) & 1, ((_id)>>21) & 1, ((_id)>>20) & 1, \
>         ((_id)>>19) & 1, ((_id)>>18) & 1, ((_id)>>17) & 1, ((_id)>>16) & 1, \
>         ((_id)>>15) & 1, ((_id)>>14) & 1, ((_id)>>13) & 1, ((_id)>>12) & 1, \
>         ((_id)>>11) & 1, ((_id)>>10) & 1, ((_id)>>9) & 1, ((_id)>>8) & 1, \
>         ((_id)>>7) & 1, ((_id)>>6) & 1, ((_id)>>5) & 1, ((_id)>>4) & 1, \
>         ((_id)>>3) & 1, ((_id)>>2) & 1, ((_id)>>1) & 1, (_id) & 1
> 
> coupled with:
> 
> static int phy_request_driver_module(struct phy_device *dev, int phy_id)
> {
> ...
>         ret = request_module(MDIO_MODULE_PREFIX MDIO_ID_FMT,
>                              MDIO_ID_ARGS(phy_id));
> 
> The signed-ness of the parameter passed into MDIO_ID_ARGS() matters.
> Hence, (0xae025151)>>31 becomes -1, and %d prints it as -1.
> 
> phy_id should be u32, just like it is everywhere else in phylib. Also,
> MDIO_ID_ARGS() should probably be adapted to not care about the signed-
> ness of its argument.
> 
> Thoughts?

Doh, yes that should be fixed, and that does make sense.
diff mbox series

Patch

diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index fe602648b99f..41272106dea9 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -329,6 +329,12 @@  config BROADCOM_PHY
 	  Currently supports the BCM5411, BCM5421, BCM5461, BCM54616S, BCM5464,
 	  BCM5481, BCM54810 and BCM5482 PHYs.
 
+config BCM84881_PHY
+	bool "Broadcom BCM84881 PHY"
+	depends on PHYLIB=y
+	---help---
+	  Support the Broadcom BCM84881 PHY.
+
 config CICADA_PHY
 	tristate "Cicada PHYs"
 	---help---
diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
index a03437e091f3..d3b8152443e7 100644
--- a/drivers/net/phy/Makefile
+++ b/drivers/net/phy/Makefile
@@ -62,6 +62,7 @@  obj-$(CONFIG_BCM87XX_PHY)	+= bcm87xx.o
 obj-$(CONFIG_BCM_CYGNUS_PHY)	+= bcm-cygnus.o
 obj-$(CONFIG_BCM_NET_PHYLIB)	+= bcm-phy-lib.o
 obj-$(CONFIG_BROADCOM_PHY)	+= broadcom.o
+obj-$(CONFIG_BCM84881_PHY)	+= bcm84881.o
 obj-$(CONFIG_CICADA_PHY)	+= cicada.o
 obj-$(CONFIG_CORTINA_PHY)	+= cortina.o
 obj-$(CONFIG_DAVICOM_PHY)	+= davicom.o
diff --git a/drivers/net/phy/bcm84881.c b/drivers/net/phy/bcm84881.c
new file mode 100644
index 000000000000..db59911b9b3c
--- /dev/null
+++ b/drivers/net/phy/bcm84881.c
@@ -0,0 +1,269 @@ 
+// SPDX-License-Identifier: GPL-2.0
+// Broadcom BCM84881 NBASE-T PHY driver, as found on a SFP+ module.
+// Copyright (C) 2019 Russell King, Deep Blue Solutions Ltd.
+//
+// Like the Marvell 88x3310, the Broadcom 84881 changes its host-side
+// interface according to the operating speed between 10GBASE-R,
+// 2500BASE-X and SGMII (but unlike the 88x3310, without the control
+// word).
+//
+// This driver only supports those aspects of the PHY that I'm able to
+// observe and test with the SFP+ module, which is an incomplete subset
+// of what this PHY is able to support. For example, I only assume it
+// supports a single lane Serdes connection, but it may be that the PHY
+// is able to support more than that.
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/phy.h>
+
+enum {
+	MDIO_AN_C22 = 0xffe0,
+};
+
+static int bcm84881_wait_init(struct phy_device *phydev)
+{
+	unsigned int tries = 20;
+	int ret, val;
+
+	do {
+		val = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_CTRL1);
+		if (val < 0) {
+			ret = val;
+			break;
+		}
+		if (!(val & MDIO_CTRL1_RESET)) {
+			ret = 0;
+			break;
+		}
+		if (!--tries) {
+			ret = -ETIMEDOUT;
+			break;
+		}
+		msleep(100);
+	} while (1);
+
+	if (ret)
+		phydev_err(phydev, "%s failed: %d\n", __func__, ret);
+
+	return ret;
+}
+
+static int bcm84881_config_init(struct phy_device *phydev)
+{
+	switch (phydev->interface) {
+	case PHY_INTERFACE_MODE_SGMII:
+	case PHY_INTERFACE_MODE_2500BASEX:
+	case PHY_INTERFACE_MODE_10GKR:
+		break;
+	default:
+		return -ENODEV;
+	}
+	return 0;
+}
+
+static int bcm84881_probe(struct phy_device *phydev)
+{
+	/* This driver requires PMAPMD and AN blocks */
+	const u32 mmd_mask = MDIO_DEVS_PMAPMD | MDIO_DEVS_AN;
+
+	if (!phydev->is_c45 ||
+	    (phydev->c45_ids.devices_in_package & mmd_mask) != mmd_mask)
+		return -ENODEV;
+
+	return 0;
+}
+
+static int bcm84881_get_features(struct phy_device *phydev)
+{
+	int ret;
+
+	ret = genphy_c45_pma_read_abilities(phydev);
+	if (ret)
+		return ret;
+
+	/* Although the PHY sets bit 1.11.8, it does not support 10M modes */
+	linkmode_clear_bit(ETHTOOL_LINK_MODE_10baseT_Half_BIT,
+			   phydev->supported);
+	linkmode_clear_bit(ETHTOOL_LINK_MODE_10baseT_Full_BIT,
+			   phydev->supported);
+
+	return 0;
+}
+
+static int bcm84881_config_aneg(struct phy_device *phydev)
+{
+	bool changed = false;
+	u32 adv;
+	int ret;
+
+	/* Wait for the PHY to finish initialising, otherwise our
+	 * advertisement may be overwritten.
+	 */
+	ret = bcm84881_wait_init(phydev);
+	if (ret)
+		return ret;
+
+	/* We don't support manual MDI control */
+	phydev->mdix_ctrl = ETH_TP_MDI_AUTO;
+
+	/* disabled autoneg doesn't seem to work with this PHY */
+	if (phydev->autoneg == AUTONEG_DISABLE)
+		return -EINVAL;
+
+	ret = genphy_c45_an_config_aneg(phydev);
+	if (ret < 0)
+		return ret;
+	if (ret > 0)
+		changed = true;
+
+	adv = linkmode_adv_to_mii_ctrl1000_t(phydev->advertising);
+	ret = phy_modify_mmd_changed(phydev, MDIO_MMD_AN,
+				     MDIO_AN_C22 + MII_CTRL1000,
+				     ADVERTISE_1000FULL | ADVERTISE_1000HALF,
+				     adv);
+	if (ret < 0)
+		return ret;
+	if (ret > 0)
+		changed = true;
+
+	return genphy_c45_check_and_restart_aneg(phydev, changed);
+}
+
+static int bcm84881_aneg_done(struct phy_device *phydev)
+{
+	int bmsr, val;
+
+	val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1);
+	if (val < 0)
+		return val;
+
+	bmsr = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_C22 + MII_BMSR);
+	if (bmsr < 0)
+		return val;
+
+	return !!(val & MDIO_AN_STAT1_COMPLETE) &&
+	       !!(bmsr & BMSR_ANEGCOMPLETE);
+}
+
+static int bcm84881_read_status(struct phy_device *phydev)
+{
+	unsigned int mode;
+	int bmsr, val;
+
+	val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_CTRL1);
+	if (val < 0)
+		return val;
+
+	if (val & MDIO_AN_CTRL1_RESTART) {
+		phydev->link = 0;
+		return 0;
+	}
+
+	val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1);
+	if (val < 0)
+		return val;
+
+	bmsr = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_C22 + MII_BMSR);
+	if (bmsr < 0)
+		return val;
+
+	phydev->autoneg_complete = !!(val & MDIO_AN_STAT1_COMPLETE) &&
+				   !!(bmsr & BMSR_ANEGCOMPLETE);
+	phydev->link = !!(val & MDIO_STAT1_LSTATUS) &&
+		       !!(bmsr & BMSR_LSTATUS);
+	if (phydev->autoneg == AUTONEG_ENABLE && !phydev->autoneg_complete)
+		phydev->link = false;
+
+	if (!phydev->link)
+		return 0;
+
+	linkmode_zero(phydev->lp_advertising);
+	phydev->speed = SPEED_UNKNOWN;
+	phydev->duplex = DUPLEX_UNKNOWN;
+	phydev->pause = 0;
+	phydev->asym_pause = 0;
+	phydev->mdix = 0;
+
+	if (phydev->autoneg_complete) {
+		val = genphy_c45_read_lpa(phydev);
+		if (val < 0)
+			return val;
+
+		val = phy_read_mmd(phydev, MDIO_MMD_AN,
+				   MDIO_AN_C22 + MII_STAT1000);
+		if (val < 0)
+			return val;
+
+		mii_stat1000_mod_linkmode_lpa_t(phydev->lp_advertising, val);
+
+		if (phydev->autoneg == AUTONEG_ENABLE)
+			phy_resolve_aneg_linkmode(phydev);
+	}
+
+	if (phydev->autoneg == AUTONEG_DISABLE) {
+		/* disabled autoneg doesn't seem to work, so force the link
+		 * down.
+		 */
+		phydev->link = 0;
+		return 0;
+	}
+
+	/* Set the host link mode - we set the phy interface mode and
+	 * the speed according to this register so that downshift works.
+	 * We leave the duplex setting as per the resolution from the
+	 * above.
+	 */
+	val = phy_read_mmd(phydev, MDIO_MMD_VEND1, 0x4011);
+	mode = (val & 0x1e) >> 1;
+	if (mode == 1 || mode == 2)
+		phydev->interface = PHY_INTERFACE_MODE_SGMII;
+	else if (mode == 3)
+		phydev->interface = PHY_INTERFACE_MODE_10GKR;
+	else if (mode == 4)
+		phydev->interface = PHY_INTERFACE_MODE_2500BASEX;
+	switch (mode & 7) {
+	case 1:
+		phydev->speed = SPEED_100;
+		break;
+	case 2:
+		phydev->speed = SPEED_1000;
+		break;
+	case 3:
+		phydev->speed = SPEED_10000;
+		break;
+	case 4:
+		phydev->speed = SPEED_2500;
+		break;
+	case 5:
+		phydev->speed = SPEED_5000;
+		break;
+	}
+
+	return genphy_c45_read_mdix(phydev);
+}
+
+static struct phy_driver bcm84881_drivers[] = {
+	{
+		.phy_id		= 0xae025150,
+		.phy_id_mask	= 0xfffffff0,
+		.name		= "Broadcom BCM84881",
+		.config_init	= bcm84881_config_init,
+		.probe		= bcm84881_probe,
+		.get_features	= bcm84881_get_features,
+		.config_aneg	= bcm84881_config_aneg,
+		.aneg_done	= bcm84881_aneg_done,
+		.read_status	= bcm84881_read_status,
+	},
+};
+
+module_phy_driver(bcm84881_drivers);
+
+/* FIXME: module auto-loading for Clause 45 PHYs seems non-functional */
+static struct mdio_device_id __maybe_unused bcm84881_tbl[] = {
+	{ 0xae025150, 0xfffffff0 },
+	{ },
+};
+MODULE_AUTHOR("Russell King");
+MODULE_DESCRIPTION("Broadcom BCM84881 PHY driver");
+MODULE_DEVICE_TABLE(mdio, bcm84881_tbl);
+MODULE_LICENSE("GPL");