diff mbox series

[RFC,net-next] net: phy: Add basic support for Synopsys XPCS using a PHY driver

Message ID 4953fc69a26bee930bccdeb612f1ce740a4294df.1578921062.git.Jose.Abreu@synopsys.com
State RFC
Delegated to: David Miller
Headers show
Series [RFC,net-next] net: phy: Add basic support for Synopsys XPCS using a PHY driver | expand

Commit Message

Jose Abreu Jan. 13, 2020, 1:11 p.m. UTC
Adds the basic support for XPCS including support for USXGMII.

Signed-off-by: Jose Abreu <Jose.Abreu@synopsys.com>

---
Cc: Andrew Lunn <andrew@lunn.ch>
Cc: Florian Fainelli <f.fainelli@gmail.com>
Cc: Heiner Kallweit <hkallweit1@gmail.com>
Cc: "David S. Miller" <davem@davemloft.net>
Cc: linux-kernel@vger.kernel.org
Cc: netdev@vger.kernel.org
---
 MAINTAINERS                |   6 +
 drivers/net/phy/Kconfig    |   5 +
 drivers/net/phy/Makefile   |   1 +
 drivers/net/phy/synopsys.c | 634 +++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 646 insertions(+)
 create mode 100644 drivers/net/phy/synopsys.c

Comments

Andrew Lunn Jan. 13, 2020, 1:38 p.m. UTC | #1
On Mon, Jan 13, 2020 at 02:11:08PM +0100, Jose Abreu wrote:
> Adds the basic support for XPCS including support for USXGMII.

Hi Jose

Please could you describe the 'big picture'. What comes after the
XPCS? An SFP? A copper PHY? How in Linux do you combine this PHY and
whatever comes next using PHYLINK?

Or do only support backplane with this, and the next thing in the line
is the peers XPCS?

Thanks
	Andrew
Andy Shevchenko Jan. 13, 2020, 1:42 p.m. UTC | #2
On Mon, Jan 13, 2020 at 3:13 PM Jose Abreu <Jose.Abreu@synopsys.com> wrote:
>
> Adds the basic support for XPCS including support for USXGMII.

...

> +#define SYNOPSYS_XPHY_ID               0x7996ced0
> +#define SYNOPSYS_XPHY_MASK             0xffffffff

GENMASK() ?
(It seems bits.h is missed in the headers block)

...

> +#define DW_USXGMII_2500                        (BIT(5))
> +#define DW_USXGMII_1000                        (BIT(6))
> +#define DW_USXGMII_100                 (BIT(13))
> +#define DW_USXGMII_10                  (0)

Useless parentheses.

...

> +static int dw_poll_reset(struct phy_device *phydev, int dev)
> +{
> +       /* Poll until the reset bit clears (50ms per retry == 0.6 sec) */
> +       unsigned int retries = 12;
> +       int ret;
> +
> +       do {

> +               msleep(50);

It's a bit unusual to have timeout loop to be started with sleep.
Imagine the case when between writing a reset bit and actual checking
the scheduling happens and reset has been done You add here
unnecessary 50 ms of waiting.

> +               ret = phy_read_mmd(phydev, dev, MDIO_CTRL1);
> +               if (ret < 0)
> +                       return ret;
> +       } while (ret & MDIO_CTRL1_RESET && --retries);
> +       if (ret & MDIO_CTRL1_RESET)
> +               return -ETIMEDOUT;
> +
> +       return 0;
> +}
> +
> +static int __dw_soft_reset(struct phy_device *phydev, int dev, int reg)
> +{
> +       int val;

val?! Perhaps ret is better name?
Applicable to the rest of functions.

> +
> +       val = phy_write_mmd(phydev, dev, reg, MDIO_CTRL1_RESET);
> +       if (val < 0)
> +               return val;
> +

> +       val = dw_poll_reset(phydev, dev);
> +       if (val < 0)
> +               return val;
> +
> +       return 0;

return dw_poll_reset(...);

> +}
> +
> +static int dw_soft_reset(struct phy_device *phydev, u32 mmd_mask)
> +{
> +       int val, devad;
> +
> +       while (mmd_mask) {
> +               devad = __ffs(mmd_mask);
> +               mmd_mask &= ~BIT(devad);

for_each_set_bit()

> +
> +               val = __dw_soft_reset(phydev, devad, MDIO_CTRL1);
> +               if (val < 0)
> +                       return val;
> +       }
> +
> +       return 0;
> +}
> +
> +static int dw_read_link(struct phy_device *phydev, u32 mmd_mask)
> +{
> +       bool link = true;
> +       int val, devad;
> +
> +       while (mmd_mask) {
> +               devad = __ffs(mmd_mask);
> +               mmd_mask &= ~BIT(devad);

Ditto.

> +
> +               val = phy_read_mmd(phydev, devad, MDIO_STAT1);
> +               if (val < 0)
> +                       return val;
> +
> +               if (!(val & MDIO_STAT1_LSTATUS))
> +                       link = false;
> +       }
> +
> +       return link;
> +}

> +#define dw_warn(__phy, __args...) \

dw_warn() -> dw_warn_if_phy_link()

> +({ \
> +       if ((__phy)->link) \
> +               dev_warn(&(__phy)->mdio.dev, ##__args); \
> +})

...

> +       int val, speed_sel = 0x0;

Redundant assignment.

> +       switch (phydev->speed) {
> +       case SPEED_10:
> +               speed_sel = DW_USXGMII_10;
> +               break;
> +       case SPEED_100:
> +               speed_sel = DW_USXGMII_100;
> +               break;
> +       case SPEED_1000:
> +               speed_sel = DW_USXGMII_1000;
> +               break;
> +       case SPEED_2500:
> +               speed_sel = DW_USXGMII_2500;
> +               break;
> +       case SPEED_5000:
> +               speed_sel = DW_USXGMII_5000;
> +               break;
> +       case SPEED_10000:
> +               speed_sel = DW_USXGMII_10000;
> +               break;
> +       default:
> +               /* Nothing to do here */
> +               return 0;
> +       }

...

> +static int dw_config_aneg_c73(struct phy_device *phydev)
> +{
> +       u32 adv = 0;

Redundant assignment.

> +       int ret;
> +
> +       /* SR_AN_ADV3 */
> +       adv = phy_read_mmd(phydev, MDIO_MMD_AN, DW_SR_AN_ADV3);
> +       if (adv < 0)
> +               return adv;

> +}

...

> +       do {
> +               msleep(50);

Same as above about timeout loops.

> +               val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_CTRL1);
> +               if (val < 0)
> +                       return val;
> +       } while (val & MDIO_AN_CTRL1_RESTART && --retries);

...

> +static int dw_aneg_done(struct phy_device *phydev)
> +{
> +       int val;
> +
> +       val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1);
> +       if (val < 0)
> +               return val;
> +
> +       if (val & MDIO_AN_STAT1_COMPLETE) {
> +               val = phy_read_mmd(phydev, MDIO_MMD_AN, DW_SR_AN_LP_ABL1);
> +               if (val < 0)
> +                       return val;
> +
> +               /* Check if Aneg outcome is valid */
> +               if (!(val & 0x1))
> +                       goto fault;
> +

> +               return 1;

1?! What does it mean?

> +       }
> +
> +       if (val & MDIO_AN_STAT1_RFAULT)
> +               goto fault;
> +
> +       return 0;
> +fault:
> +       dev_err(&phydev->mdio.dev, "Invalid Autoneg result!\n");
> +       dev_err(&phydev->mdio.dev, "CTRL1=0x%x, STAT1=0x%x\n",
> +               phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_CTRL1),
> +               phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1));
> +       dev_err(&phydev->mdio.dev, "ADV1=0x%x, ADV2=0x%x, ADV3=0x%x\n",
> +               phy_read_mmd(phydev, MDIO_MMD_AN, DW_SR_AN_ADV1),
> +               phy_read_mmd(phydev, MDIO_MMD_AN, DW_SR_AN_ADV2),
> +               phy_read_mmd(phydev, MDIO_MMD_AN, DW_SR_AN_ADV3));
> +       dev_err(&phydev->mdio.dev, "LP_ADV1=0x%x, LP_ADV2=0x%x, LP_ADV3=0x%x\n",
> +               phy_read_mmd(phydev, MDIO_MMD_AN, DW_SR_AN_LP_ABL1),
> +               phy_read_mmd(phydev, MDIO_MMD_AN, DW_SR_AN_LP_ABL2),
> +               phy_read_mmd(phydev, MDIO_MMD_AN, DW_SR_AN_LP_ABL3));
> +
> +       val = dw_soft_reset(phydev, MDIO_DEVS_PCS);
> +       if (val < 0)
> +               return val;
> +
> +       return dw_config_aneg(phydev);
> +}

...

> +       phydev->pause = val & DW_C73_PAUSE ? 1 : 0;
> +       phydev->asym_pause = val & DW_C73_ASYM_PAUSE ? 1 : 0;

!!(x) should work as well. But I think compiler optimizes that ternary well.

...

> +               val = dw_aneg_done(phydev);
> +               if (val <= 0) {

This <= 0 should be explained. Why we set link when it's < 0 or
otherwise why we return 0 when link is set to false.

> +                       phydev->link = false;
> +                       return val;
> +               }

...

> +static struct mdio_device_id __maybe_unused dw_tbl[] = {
> +       { SYNOPSYS_XPHY_ID, SYNOPSYS_XPHY_MASK },
> +       { },

Comma is not needed.

> +};
Jose Abreu Jan. 13, 2020, 1:54 p.m. UTC | #3
From: Andrew Lunn <andrew@lunn.ch>
Date: Jan/13/2020, 13:38:45 (UTC+00:00)

> On Mon, Jan 13, 2020 at 02:11:08PM +0100, Jose Abreu wrote:
> > Adds the basic support for XPCS including support for USXGMII.
> 
> Hi Jose
> 
> Please could you describe the 'big picture'. What comes after the
> XPCS? An SFP? A copper PHY? How in Linux do you combine this PHY and
> whatever comes next using PHYLINK?
> 
> Or do only support backplane with this, and the next thing in the line
> is the peers XPCS?

My current setup is this:

Host PC x86 -> PCI -> XGMAC -> XPCS -> SERDES 10G-BASE-R -> QSFP+

The only piece that needs configuration besides XGMAC is the XPCS hereby 
I "called" it a PHY ... Anyway, this is an RFC because I'm not entirely 
sure about the approach. Hmm ?

---
Thanks,
Jose Miguel Abreu
Jose Abreu Jan. 13, 2020, 2:07 p.m. UTC | #4
From: Andy Shevchenko <andy.shevchenko@gmail.com>
Date: Jan/13/2020, 13:42:47 (UTC+00:00)

> > +#define SYNOPSYS_XPHY_ID               0x7996ced0
> > +#define SYNOPSYS_XPHY_MASK             0xffffffff
> 
> GENMASK() ?
> (It seems bits.h is missed in the headers block)

This is on purpose, it's an ID and the mask is more clearly read this 
way (in my opinion).

> 
> > +#define DW_USXGMII_2500                        (BIT(5))
> > +#define DW_USXGMII_1000                        (BIT(6))
> > +#define DW_USXGMII_100                 (BIT(13))
> > +#define DW_USXGMII_10                  (0)
> 
> Useless parentheses.

Just coding style, to keep it aligned with the other speeds, you can 
call it OCD :)

> > +static int dw_poll_reset(struct phy_device *phydev, int dev)
> > +{
> > +       /* Poll until the reset bit clears (50ms per retry == 0.6 sec) */
> > +       unsigned int retries = 12;
> > +       int ret;
> > +
> > +       do {
> 
> > +               msleep(50);
> 
> It's a bit unusual to have timeout loop to be started with sleep.
> Imagine the case when between writing a reset bit and actual checking
> the scheduling happens and reset has been done You add here
> unnecessary 50 ms of waiting.

Intended also. Actually, unconditional sleep allows to safely wait for 
reset and not try to poll the bit in the middle of reset where the FSM 
may not be operational and reads may not return correctly. This is also 
needed because in some configurations the XPCS does not allow reads in 
the middle of a reset.

> > +static int __dw_soft_reset(struct phy_device *phydev, int dev, int reg)
> > +{
> > +       int val;
> 
> val?! Perhaps ret is better name?
> Applicable to the rest of functions.

This is the most commonly used pattern in net/phy subsystem.

> > +#define dw_warn(__phy, __args...) \
> 
> dw_warn() -> dw_warn_if_phy_link()

Hmm, this will probably make the warns lines pass the 80 chars and I 
don't like to break lines :)

> > +static int dw_aneg_done(struct phy_device *phydev)
> > +{
> > +       int val;
> > +
> > +       val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1);
> > +       if (val < 0)
> > +               return val;
> > +
> > +       if (val & MDIO_AN_STAT1_COMPLETE) {
> > +               val = phy_read_mmd(phydev, MDIO_MMD_AN, DW_SR_AN_LP_ABL1);
> > +               if (val < 0)
> > +                       return val;
> > +
> > +               /* Check if Aneg outcome is valid */
> > +               if (!(val & 0x1))
> > +                       goto fault;
> > +
> 
> > +               return 1;
> 
> 1?! What does it mean?

Just like phy_aneg_done():
	"* Description: Return the auto-negotiation status from this @phydev
	 * Returns > 0 on success or < 0 on error. 0 means that 
auto-negotiation
	 * is still pending."

I'll add remaining changes in official version. Thanks for the review!

---
Thanks,
Jose Miguel Abreu
Russell King (Oracle) Jan. 13, 2020, 2:18 p.m. UTC | #5
On Mon, Jan 13, 2020 at 01:54:28PM +0000, Jose Abreu wrote:
> From: Andrew Lunn <andrew@lunn.ch>
> Date: Jan/13/2020, 13:38:45 (UTC+00:00)
> 
> > On Mon, Jan 13, 2020 at 02:11:08PM +0100, Jose Abreu wrote:
> > > Adds the basic support for XPCS including support for USXGMII.
> > 
> > Hi Jose
> > 
> > Please could you describe the 'big picture'. What comes after the
> > XPCS? An SFP? A copper PHY? How in Linux do you combine this PHY and
> > whatever comes next using PHYLINK?
> > 
> > Or do only support backplane with this, and the next thing in the line
> > is the peers XPCS?
> 
> My current setup is this:
> 
> Host PC x86 -> PCI -> XGMAC -> XPCS -> SERDES 10G-BASE-R -> QSFP+
> 
> The only piece that needs configuration besides XGMAC is the XPCS hereby 
> I "called" it a PHY ... Anyway, this is an RFC because I'm not entirely 
> sure about the approach. Hmm ?

I don't seem to have been copied on the original mail, so I'm jumping
in blind here.

In phylink, the mac_pcs_get_state() method is supposed to read from
the PCS - in your case, the XPCS.  This wasn't obvious when phylink
was first submitted, especially as mvneta and mvpp2 don't offer
802.3 MDIO compliant PCS interfaces.  Hence the recent change in
naming of that method.

I've recently suggested a patch to phylink to add a generic helper to
read the state from a generic 802.3 clause 37 PCS, but I guess that
won't be sufficient for an XPCS.  However, it should give some clues
if you're intending to use phylink.
Jose Abreu Jan. 13, 2020, 2:27 p.m. UTC | #6
From: Russell King - ARM Linux admin <linux@armlinux.org.uk>
Date: Jan/13/2020, 14:18:17 (UTC+00:00)

> On Mon, Jan 13, 2020 at 01:54:28PM +0000, Jose Abreu wrote:
> > From: Andrew Lunn <andrew@lunn.ch>
> > Date: Jan/13/2020, 13:38:45 (UTC+00:00)
> > 
> > > On Mon, Jan 13, 2020 at 02:11:08PM +0100, Jose Abreu wrote:
> > > > Adds the basic support for XPCS including support for USXGMII.
> > > 
> > > Hi Jose
> > > 
> > > Please could you describe the 'big picture'. What comes after the
> > > XPCS? An SFP? A copper PHY? How in Linux do you combine this PHY and
> > > whatever comes next using PHYLINK?
> > > 
> > > Or do only support backplane with this, and the next thing in the line
> > > is the peers XPCS?
> > 
> > My current setup is this:
> > 
> > Host PC x86 -> PCI -> XGMAC -> XPCS -> SERDES 10G-BASE-R -> QSFP+
> > 
> > The only piece that needs configuration besides XGMAC is the XPCS hereby 
> > I "called" it a PHY ... Anyway, this is an RFC because I'm not entirely 
> > sure about the approach. Hmm ?
> 
> I don't seem to have been copied on the original mail, so I'm jumping
> in blind here.

Thanks for the comments. I'll add you in next versions, you can find 
original posting at:
- https://patchwork.ozlabs.org/patch/1222133/

> In phylink, the 
mac_pcs_get_state() method is supposed to read from
> the PCS - in your case, the XPCS.  This wasn't obvious when phylink
> was first submitted, especially as mvneta and mvpp2 don't offer
> 802.3 MDIO compliant PCS interfaces.  Hence the recent change in
> naming of that method.

Yes I saw that. The thing is that this is a different and stand-alone 
IP. Maybe I can add it into some sort of module library in PHY folder ?

> I've recently suggested a patch to phylink to add a generic helper to
> read the state from a generic 802.3 clause 37 PCS, but I guess that
> won't be sufficient for an XPCS.  However, it should give some clues
> if you're intending to use phylink.

This uses Clause 73 for Autoneg. I can add that into PHYLINK core if you 
agree.
 
---
Thanks,
Jose Miguel Abreu
Vladimir Oltean Jan. 13, 2020, 2:50 p.m. UTC | #7
Hi Russell,

On Mon, 13 Jan 2020 at 16:19, Russell King - ARM Linux admin
<linux@armlinux.org.uk> wrote:
>
> I've recently suggested a patch to phylink to add a generic helper to
> read the state from a generic 802.3 clause 37 PCS, but I guess that
> won't be sufficient for an XPCS.  However, it should give some clues
> if you're intending to use phylink.
>

I don't think the PCS implementations out there are sufficiently
similar to be driven by a unified driver, and at least nothing
mandates that for now. Although the configuration interface is MDIO
with registers quasi-compliant to C22 or C45, many times bits in
BMCR/BMSR are not implemented, you can't typically achieve full
functionality [ sometimes not at all ] without writing to some
vendor-specific registers, there might be errata workarounds that need
to be implemented through PCS writes, often the PCS driver needs to be
correlated with a MMIO region corresponding to that SerDes lane for
stuff such as eye parameters.
The code duplication isn't even all that bad.

_But_ I am not sure how PHYLINK comes into play here. A PHY driver
should work with the plain PHY library too. Dealing with clause 73
autoneg indicates to me that this is more than just a MAC PCS,
therefore it shouldn't be tied in with PHYLINK.

Thanks,
-Vladimir
Vladimir Oltean Jan. 13, 2020, 2:55 p.m. UTC | #8
Hi Jose,

On Mon, 13 Jan 2020 at 16:28, Jose Abreu <Jose.Abreu@synopsys.com> wrote:
>
> This uses Clause 73 for Autoneg. I can add that into PHYLINK core if you
> agree.
>

Clauses 72 and 73 describe some PMD link training procedure too, as
part of auto-negotiation for 10GBase-KR. Does the XGPCS do any of
that? Is this series sufficient for link training to work when in the
10GBase-KR copper backplane link mode?

> ---
> Thanks,
> Jose Miguel Abreu

Regards,
-Vladimir
Jose Abreu Jan. 13, 2020, 3:05 p.m. UTC | #9
From: Vladimir Oltean <olteanv@gmail.com>
Date: Jan/13/2020, 14:55:51 (UTC+00:00)

> Clauses 72 and 73 describe some PMD link training procedure too, as
> part of auto-negotiation for 10GBase-KR. Does the XGPCS do any of
> that? Is this series sufficient for link training to work when in the
> 10GBase-KR copper backplane link mode?

Yes, its supported by the IP but not in the patch. It's working fine 
without the link training procedure. Actually, Clause 72 Link training is 
optional in the IP, and in the configuration I have now it's not enabled 
so even if I implemented it I would need a new HW to test it.

---
Thanks,
Jose Miguel Abreu
Russell King (Oracle) Jan. 13, 2020, 3:09 p.m. UTC | #10
On Mon, Jan 13, 2020 at 04:50:18PM +0200, Vladimir Oltean wrote:
> Hi Russell,
> 
> On Mon, 13 Jan 2020 at 16:19, Russell King - ARM Linux admin
> <linux@armlinux.org.uk> wrote:
> >
> > I've recently suggested a patch to phylink to add a generic helper to
> > read the state from a generic 802.3 clause 37 PCS, but I guess that
> > won't be sufficient for an XPCS.  However, it should give some clues
> > if you're intending to use phylink.
> >
> 
> I don't think the PCS implementations out there are sufficiently
> similar to be driven by a unified driver, and at least nothing
> mandates that for now. Although the configuration interface is MDIO
> with registers quasi-compliant to C22 or C45, many times bits in
> BMCR/BMSR are not implemented, you can't typically achieve full
> functionality [ sometimes not at all ] without writing to some
> vendor-specific registers, there might be errata workarounds that need
> to be implemented through PCS writes, often the PCS driver needs to be
> correlated with a MMIO region corresponding to that SerDes lane for
> stuff such as eye parameters.
> The code duplication isn't even all that bad.

That's opinion.

The PCS register set is defined in 802.3, and there are at least two
implementations I've come across that are compliant. I suspect there
are many more that are also compliant out there.

> _But_ I am not sure how PHYLINK comes into play here. A PHY driver
> should work with the plain PHY library too. Dealing with clause 73
> autoneg indicates to me that this is more than just a MAC PCS,
> therefore it shouldn't be tied in with PHYLINK.

I think you're looking at this wrong, or you have a misunderstanding
of where phylink sits in this.

phylink is there to help deal with SFPs, and to manage the MAC side
of the link which *includes* the MAC PCS, and interface that to
either a PHY or a SFP.  phylink has always assumed right from day
one that it will be talking to the MAC _and_ MAC PCS.
Jose Abreu Jan. 20, 2020, 10:31 a.m. UTC | #11
From: Russell King - ARM Linux admin <linux@armlinux.org.uk>
Date: Jan/13/2020, 14:18:17 (UTC+00:00)

> I've recently suggested a patch to phylink to add a generic helper to
> read the state from a generic 802.3 clause 37 PCS, but I guess that
> won't be sufficient for an XPCS.  However, it should give some clues
> if you're intending to use phylink.

So, I think for my particular setup (that has no "real" PHY) we can have 
something like this in SW PoV:

stmmac -> xpcs -> SW-PHY / Fixed PHY

- stmmac + xpcs state would be handled by phylink (MAC side)
- SW-PHY / Fixed PHY state would be handled by phylink (PHY side)

This would need updates for Fixed PHY to support >1G speeds.

---
Thanks,
Jose Miguel Abreu
Russell King (Oracle) Jan. 20, 2020, 10:50 a.m. UTC | #12
On Mon, Jan 20, 2020 at 10:31:17AM +0000, Jose Abreu wrote:
> From: Russell King - ARM Linux admin <linux@armlinux.org.uk>
> Date: Jan/13/2020, 14:18:17 (UTC+00:00)
> 
> > I've recently suggested a patch to phylink to add a generic helper to
> > read the state from a generic 802.3 clause 37 PCS, but I guess that
> > won't be sufficient for an XPCS.  However, it should give some clues
> > if you're intending to use phylink.
> 
> So, I think for my particular setup (that has no "real" PHY) we can have 
> something like this in SW PoV:
> 
> stmmac -> xpcs -> SW-PHY / Fixed PHY
> 
> - stmmac + xpcs state would be handled by phylink (MAC side)
> - SW-PHY / Fixed PHY state would be handled by phylink (PHY side)
> 
> This would need updates for Fixed PHY to support >1G speeds.

You don't want to do that if you have 1G SFPs.  Yes, you *can* do it
and make it work, but you miss out completely on the fact that the
link is supposed to be negotiated across the SFP link for 1G speeds,
and then you're into the realms of having to provide users ways to
edit the DT and reboot if the parameters at the link partner change.

Please, avoid fixed-links with SFPs where possible, and let's
implement things correctly.
Jose Abreu Jan. 20, 2020, 11:07 a.m. UTC | #13
From: Russell King - ARM Linux admin <linux@armlinux.org.uk>
Date: Jan/20/2020, 10:50:20 (UTC+00:00)

> On Mon, Jan 20, 2020 at 10:31:17AM +0000, Jose Abreu wrote:
> > From: Russell King - ARM Linux admin <linux@armlinux.org.uk>
> > Date: Jan/13/2020, 14:18:17 (UTC+00:00)
> > 
> > > I've recently suggested a patch to phylink to add a generic helper to
> > > read the state from a generic 802.3 clause 37 PCS, but I guess that
> > > won't be sufficient for an XPCS.  However, it should give some clues
> > > if you're intending to use phylink.
> > 
> > So, I think for my particular setup (that has no "real" PHY) we can have 
> > something like this in SW PoV:
> > 
> > stmmac -> xpcs -> SW-PHY / Fixed PHY
> > 
> > - stmmac + xpcs state would be handled by phylink (MAC side)
> > - SW-PHY / Fixed PHY state would be handled by phylink (PHY side)
> > 
> > This would need updates for Fixed PHY to support >1G speeds.
> 
> You don't want to do that if you have 1G SFPs.  Yes, you *can* do it
> and make it work, but you miss out completely on the fact that the
> link is supposed to be negotiated across the SFP link for 1G speeds,
> and then you're into the realms of having to provide users ways to
> edit the DT and reboot if the parameters at the link partner change.

You may have missed my answer to Andrew so I'll quote it here:

---
[...]

My current setup is this:

Host PC x86 -> PCI -> XGMAC -> XPCS -> SERDES 10G-BASE-R -> QSFP+

The only piece that needs configuration besides XGMAC is the XPCS hereby 

I "called" it a PHY [...]
---

So, besides not having a DT based setup to test changes, I also don't have 
access to SFP bus neither SERDES ... As you suggested, I would like to 
integrate XPCS with PHYLINK in stmmac but I'm not entirely sure on how to 
implement the remaining connections as the connect_phy() callbacks will 
fail because the only MMD device in the bus will be XPCS. That's why I 
suggested the Fixed PHY approach ...

---
Thanks,
Jose Miguel Abreu
Russell King (Oracle) Jan. 20, 2020, 11:39 a.m. UTC | #14
On Mon, Jan 20, 2020 at 11:07:23AM +0000, Jose Abreu wrote:
> From: Russell King - ARM Linux admin <linux@armlinux.org.uk>
> Date: Jan/20/2020, 10:50:20 (UTC+00:00)
> 
> > On Mon, Jan 20, 2020 at 10:31:17AM +0000, Jose Abreu wrote:
> > > From: Russell King - ARM Linux admin <linux@armlinux.org.uk>
> > > Date: Jan/13/2020, 14:18:17 (UTC+00:00)
> > > 
> > > > I've recently suggested a patch to phylink to add a generic helper to
> > > > read the state from a generic 802.3 clause 37 PCS, but I guess that
> > > > won't be sufficient for an XPCS.  However, it should give some clues
> > > > if you're intending to use phylink.
> > > 
> > > So, I think for my particular setup (that has no "real" PHY) we can have 
> > > something like this in SW PoV:
> > > 
> > > stmmac -> xpcs -> SW-PHY / Fixed PHY
> > > 
> > > - stmmac + xpcs state would be handled by phylink (MAC side)
> > > - SW-PHY / Fixed PHY state would be handled by phylink (PHY side)
> > > 
> > > This would need updates for Fixed PHY to support >1G speeds.
> > 
> > You don't want to do that if you have 1G SFPs.  Yes, you *can* do it
> > and make it work, but you miss out completely on the fact that the
> > link is supposed to be negotiated across the SFP link for 1G speeds,
> > and then you're into the realms of having to provide users ways to
> > edit the DT and reboot if the parameters at the link partner change.
> 
> You may have missed my answer to Andrew so I'll quote it here:
> 
> ---
> [...]
> 
> My current setup is this:
> 
> Host PC x86 -> PCI -> XGMAC -> XPCS -> SERDES 10G-BASE-R -> QSFP+
> 
> The only piece that needs configuration besides XGMAC is the XPCS hereby 
> 
> I "called" it a PHY [...]
> ---

I didn't miss this.

> So, besides not having a DT based setup to test changes, I also don't have 
> access to SFP bus neither SERDES ... As you suggested, I would like to 
> integrate XPCS with PHYLINK in stmmac but I'm not entirely sure on how to 
> implement the remaining connections as the connect_phy() callbacks will 
> fail because the only MMD device in the bus will be XPCS. That's why I 
> suggested the Fixed PHY approach ...

Having access to the SFP or not is not that relevent to the data link.
Generally, the SFP is not like a PHY, and doesn't take part in the
link negotiation unless it happens to contain a copper PHY.

Also, please, do not use fixed-phy support with phylink.  phylink
implements a replacement for that, where it supports fixed-links
without needing the fixed-phy stuff.  This is far more flexible
than fixed-phy which is restricted to the capabilities of clause 22
PHYs only.

To make fixed-phy support modes beyond clause 22 PHY capabilities
would need clause 45 register set emulation by swphy and a
corresponding clause 45 phylib driver; clause 45 annoyingly does
not define the 1G negotiation registers in the standard register
set, so every PHY vendor implements that using their own vendor
specific solution.

This is why phylink implements its own solution without using
fixed-phy (which I wish could be removed some day).

I would strongly recommend supporting the XPCS natively and not
via phylib.  Consider the case:

Host PC x86 -> PCI -> XGMAC -> XPCS -> SERDES 10G-BASE-R -> PHY -> RJ45

You can only have one phylib PHY attached to a network device via
connect_phy(); that is a restriction in the higher net layers.  If you
use phylib for the XPCS, how do you attach the PHY to the setup and
configure it?

Also, using a PHY via connect_phy() negates using fixed-link mode in
phylink, the two have always been exclusive.
Jack Ping CHNG Feb. 4, 2020, 9:41 a.m. UTC | #15
Hi Jose,
>
>> So, besides not having a DT based setup to test changes, I also don't 
>> have access to SFP bus neither SERDES ... As you suggested, I would 
>> like to integrate XPCS with PHYLINK in stmmac but I'm not entirely 
>> sure on how to implement the remaining connections as the 
>> connect_phy() callbacks will fail because the only MMD device in the 
>> bus will be XPCS. That's why I suggested the Fixed PHY approach ...
>
> Having access to the SFP or not is not that relevent to the data link.
> Generally, the SFP is not like a PHY, and doesn't take part in the
> link negotiation unless it happens to contain a copper PHY.
>
> Also, please, do not use fixed-phy support with phylink. phylink
> implements a replacement for that, where it supports fixed-links
> without needing the fixed-phy stuff. This is far more flexible
> than fixed-phy which is restricted to the capabilities of clause 22
> PHYs only.
>
> To make fixed-phy support modes beyond clause 22 PHY capabilities
> would need clause 45 register set emulation by swphy and a
> corresponding clause 45 phylib driver; clause 45 annoyingly does
> not define the 1G negotiation registers in the standard register
> set, so every PHY vendor implements that using their own vendor
> specific solution.
>
> This is why phylink implements its own solution without using
> fixed-phy (which I wish could be removed some day).
>
> I would strongly recommend supporting the XPCS natively and not
> via phylib. Consider the case:
>
> Host PC x86 -> PCI -> XGMAC -> XPCS -> SERDES 10G-BASE-R -> PHY -> RJ45
>
> You can only have one phylib PHY attached to a network device via
> connect_phy(); that is a restriction in the higher net layers. If you
> use phylib for the XPCS, how do you attach the PHY to the setup and
> configure it?
>
> Also, using a PHY via connect_phy() negates using fixed-link mode in
> phylink, the two have always been exclusive.

Currently our network SoC has something like this:
XGMAC-> XPCS -> Combo PHY -> PHY

In the xpcs driver probe(), get and calibrate the phy:

priv->phy = devm_phy_get(&pdev->dev, "phy");
if (IS_ERR(priv->phy)) {
     dev_warn(dev, "No phy\n");
     return PTR_ERR(priv->phy);
}

ret = phy_init(priv->phy);
if (ret)
     return ret;

ret = phy_power_on(priv->phy);
if (ret) {
     phy_exit(priv->phy);
     return ret;
}
ret = phy_calibrate(priv->phy);
if (ret) {
     phy_exit(priv->phy);
     return ret;
}

xpcs driver needs to handle phy or phy_device depending on the phy?

Best regards,
Chng Jack Ping
Jose Abreu March 9, 2020, 9:22 a.m. UTC | #16
From: Chng, Jack Ping <jack.ping.chng@linux.intel.com>
Date: Feb/04/2020, 09:41:35 (UTC+00:00)

> Currently our network SoC has something like this:
> XGMAC-> XPCS -> Combo PHY -> PHY
> 
> In the xpcs driver probe(), get and calibrate the phy:
> 
> priv->phy = devm_phy_get(&pdev->dev, "phy");
> if (IS_ERR(priv->phy)) {
>      dev_warn(dev, "No phy\n");
>      return PTR_ERR(priv->phy);
> }
> 
> ret = phy_init(priv->phy);
> if (ret)
>      return ret;
> 
> ret = phy_power_on(priv->phy);
> if (ret) {
>      phy_exit(priv->phy);
>      return ret;
> }
> ret = phy_calibrate(priv->phy);
> if (ret) {
>      phy_exit(priv->phy);
>      return ret;
> }
> 
> xpcs driver needs to handle phy or phy_device depending on the phy?

Apologies for the delayed answer.

I think XPCS should be agnostic of PHY so this should be handled by stmmac 
core and 





































































PHYLINK.

I submitted a new series: https://patchwork.ozlabs.org/project/netdev/list/?series=163171

Can you please test it ?

---
Thanks,
Jose Miguel Abreu
diff mbox series

Patch

diff --git a/MAINTAINERS b/MAINTAINERS
index 2549f10eb0b1..923a6425084a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15956,6 +15956,12 @@  L:	netdev@vger.kernel.org
 S:	Supported
 F:	drivers/net/ethernet/synopsys/
 
+SYNOPSYS DESIGNWARE ETHERNET PHY DRIVER
+M:	Jose Abreu <Jose.Abreu@synopsys.com>
+L:	netdev@vger.kernel.org
+S:	Supported
+F:	drivers/net/phy/synopsys.c
+
 SYNOPSYS DESIGNWARE I2C DRIVER
 M:	Jarkko Nikula <jarkko.nikula@linux.intel.com>
 R:	Andy Shevchenko <andriy.shevchenko@linux.intel.com>
diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index 2e016271e126..9762b2f68256 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -492,6 +492,11 @@  config TERANETICS_PHY
 	---help---
 	  Currently supports the Teranetics TN2020
 
+config SYNOPSYS_PHY
+	tristate "Synopsys PHYs"
+	---help---
+	  This is the driver for all Synopsys Ethernet PHYs.
+
 config VITESSE_PHY
 	tristate "Vitesse PHYs"
 	---help---
diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
index fe5badf13b65..01a453101b21 100644
--- a/drivers/net/phy/Makefile
+++ b/drivers/net/phy/Makefile
@@ -95,6 +95,7 @@  obj-$(CONFIG_RENESAS_PHY)	+= uPD60620.o
 obj-$(CONFIG_ROCKCHIP_PHY)	+= rockchip.o
 obj-$(CONFIG_SMSC_PHY)		+= smsc.o
 obj-$(CONFIG_STE10XP)		+= ste10Xp.o
+obj-$(CONFIG_SYNOPSYS_PHY)	+= synopsys.o
 obj-$(CONFIG_TERANETICS_PHY)	+= teranetics.o
 obj-$(CONFIG_VITESSE_PHY)	+= vitesse.o
 obj-$(CONFIG_XILINX_GMII2RGMII) += xilinx_gmii2rgmii.o
diff --git a/drivers/net/phy/synopsys.c b/drivers/net/phy/synopsys.c
new file mode 100644
index 000000000000..fed94a0ce47f
--- /dev/null
+++ b/drivers/net/phy/synopsys.c
@@ -0,0 +1,634 @@ 
+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
+/*
+ * Copyright (c) 2019 Synopsys, Inc. and/or its affiliates.
+ * Synopsys DesignWare PHYs driver
+ *
+ * Author: Jose Abreu <joabreu@synopsys.com>
+ */
+
+#include <linux/delay.h>
+#include <linux/mdio.h>
+#include <linux/phy.h>
+
+#define SYNOPSYS_XPHY_ID		0x7996ced0
+#define SYNOPSYS_XPHY_MASK		0xffffffff
+
+/* Vendor regs access */
+#define DW_VENDOR			BIT(15)
+
+/* VR_XS_PCS */
+#define DW_VR_XS_PCS_DIG_CTRL1		0x8000
+#define DW_USXGMII_RST			BIT(10)
+#define DW_USXGMII_EN			BIT(9)
+
+/* SR_MII */
+#define DW_USXGMII_FULL			BIT(8)
+#define DW_USXGMII_SS_MASK		(BIT(13) | BIT(6) | BIT(5))
+#define DW_USXGMII_10000		(BIT(13) | BIT(6))
+#define DW_USXGMII_5000			(BIT(13) | BIT(5))
+#define DW_USXGMII_2500			(BIT(5))
+#define DW_USXGMII_1000			(BIT(6))
+#define DW_USXGMII_100			(BIT(13))
+#define DW_USXGMII_10			(0)
+
+/* SR_AN */
+#define DW_SR_AN_ADV1			0x10
+#define DW_SR_AN_ADV2			0x11
+#define DW_SR_AN_ADV3			0x12
+#define DW_SR_AN_LP_ABL1		0x13
+#define DW_SR_AN_LP_ABL2		0x14
+#define DW_SR_AN_LP_ABL3		0x15
+
+/* Clause 73 Defines */
+/* AN_LP_ABL1 */
+#define DW_C73_PAUSE			BIT(10)
+#define DW_C73_ASYM_PAUSE		BIT(11)
+/* AN_LP_ABL2 */
+#define DW_C73_1000KX			BIT(5)
+#define DW_C73_10000KX4			BIT(6)
+#define DW_C73_10000KR			BIT(7)
+/* AN_LP_ABL3 */
+#define DW_C73_2500KX			BIT(0)
+#define DW_C73_5000KR			BIT(1)
+
+static int dw_read_vendor(struct phy_device *phydev, int dev, int reg)
+{
+	return phy_read_mmd(phydev, dev, DW_VENDOR | reg);
+}
+
+static int dw_write_vendor(struct phy_device *phydev, int dev, int reg, int val)
+{
+	return phy_write_mmd(phydev, dev, DW_VENDOR | reg, val);
+}
+
+static int dw_write_vpcs(struct phy_device *phydev, int reg, int val)
+{
+	return dw_write_vendor(phydev, MDIO_MMD_PCS, reg, val);
+}
+
+static int dw_read_vpcs(struct phy_device *phydev, int reg)
+{
+	return dw_read_vendor(phydev, MDIO_MMD_PCS, reg);
+}
+
+static int dw_poll_reset(struct phy_device *phydev, int dev)
+{
+	/* Poll until the reset bit clears (50ms per retry == 0.6 sec) */
+	unsigned int retries = 12;
+	int ret;
+
+	do {
+		msleep(50);
+		ret = phy_read_mmd(phydev, dev, MDIO_CTRL1);
+		if (ret < 0)
+			return ret;
+	} while (ret & MDIO_CTRL1_RESET && --retries);
+	if (ret & MDIO_CTRL1_RESET)
+		return -ETIMEDOUT;
+
+	return 0;
+}
+
+static int __dw_soft_reset(struct phy_device *phydev, int dev, int reg)
+{
+	int val;
+
+	val = phy_write_mmd(phydev, dev, reg, MDIO_CTRL1_RESET);
+	if (val < 0)
+		return val;
+
+	val = dw_poll_reset(phydev, dev);
+	if (val < 0)
+		return val;
+
+	return 0;
+}
+
+static int dw_soft_reset(struct phy_device *phydev, u32 mmd_mask)
+{
+	int val, devad;
+
+	while (mmd_mask) {
+		devad = __ffs(mmd_mask);
+		mmd_mask &= ~BIT(devad);
+
+		val = __dw_soft_reset(phydev, devad, MDIO_CTRL1);
+		if (val < 0)
+			return val;
+	}
+
+	return 0;
+}
+
+static int dw_read_link(struct phy_device *phydev, u32 mmd_mask)
+{
+	bool link = true;
+	int val, devad;
+
+	while (mmd_mask) {
+		devad = __ffs(mmd_mask);
+		mmd_mask &= ~BIT(devad);
+
+		val = phy_read_mmd(phydev, devad, MDIO_STAT1);
+		if (val < 0)
+			return val;
+
+		if (!(val & MDIO_STAT1_LSTATUS))
+			link = false;
+	}
+
+	return link;
+}
+
+#define dw_warn(__phy, __args...) \
+({ \
+	if ((__phy)->link) \
+		dev_warn(&(__phy)->mdio.dev, ##__args); \
+})
+
+static int dw_read_fault(struct phy_device *phydev)
+{
+	int val;
+
+	val = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_STAT1);
+	if (val < 0)
+		return val;
+
+	if (val & MDIO_STAT1_FAULT) {
+		dw_warn(phydev, "Link fault condition detected!\n");
+		return -EFAULT;
+	}
+
+	val = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_STAT2);
+	if (val < 0)
+		return val;
+
+	if (val & MDIO_STAT2_RXFAULT) {
+		dw_warn(phydev, "Receiver fault detected!\n");
+		return -EFAULT;
+	}
+	if (val & MDIO_STAT2_TXFAULT) {
+		dw_warn(phydev, "Transmitter fault detected!\n");
+		return -EFAULT;
+	}
+
+	val = dw_read_vendor(phydev, MDIO_MMD_PCS, 0x10);
+	if (val < 0)
+		return val;
+
+	if (val & GENMASK(6, 5)) {
+		dw_warn(phydev, "FIFO fault condition detected!\n");
+		return -EFAULT;
+	}
+
+	val = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_PCS_10GBRT_STAT1);
+	if (val < 0)
+		return val;
+
+	if (!(val & MDIO_PCS_10GBRT_STAT1_BLKLK))
+		dw_warn(phydev, "Link is not locked!\n");
+
+	val = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_PCS_10GBRT_STAT2);
+	if (val < 0)
+		return val;
+
+	if (val & MDIO_PCS_10GBRT_STAT2_ERR) {
+		dw_warn(phydev, "Link has errors!\n");
+		return -EFAULT;
+	}
+
+	return 0;
+}
+
+static int dw_read_pma(struct phy_device *phydev)
+{
+	int val;
+
+	/* Read USXGMII Mode if any */
+	val = phy_read_mmd(phydev, MDIO_MMD_PCS, DW_VR_XS_PCS_DIG_CTRL1);
+	if (val < 0)
+		return val;
+
+	if (val & DW_USXGMII_EN) {
+		val = phy_read_mmd(phydev, MDIO_MMD_VEND2, MDIO_CTRL1);
+		if (val < 0)
+			return val;
+
+		switch (val & DW_USXGMII_SS_MASK) {
+		case DW_USXGMII_5000:
+			phydev->speed = SPEED_5000;
+			break;
+		case DW_USXGMII_2500:
+			phydev->speed = SPEED_2500;
+			break;
+		case DW_USXGMII_10000:
+			phydev->speed = SPEED_10000;
+			break;
+		case DW_USXGMII_1000:
+			phydev->speed = SPEED_1000;
+			break;
+		case DW_USXGMII_100:
+			phydev->speed = SPEED_100;
+			break;
+		case DW_USXGMII_10:
+			phydev->speed = SPEED_10;
+			break;
+		default:
+			phydev->speed = SPEED_UNKNOWN;
+			break;
+		}
+	}
+
+	phydev->duplex = DUPLEX_FULL;
+	return 0;
+}
+
+static int dw_config_usxgmii(struct phy_device *phydev)
+{
+	int val, speed_sel = 0x0;
+
+	/* USXGMII only supports Full Duplex modes */
+	if (phydev->duplex != DUPLEX_FULL)
+		return 0;
+
+	switch (phydev->speed) {
+	case SPEED_10:
+		speed_sel = DW_USXGMII_10;
+		break;
+	case SPEED_100:
+		speed_sel = DW_USXGMII_100;
+		break;
+	case SPEED_1000:
+		speed_sel = DW_USXGMII_1000;
+		break;
+	case SPEED_2500:
+		speed_sel = DW_USXGMII_2500;
+		break;
+	case SPEED_5000:
+		speed_sel = DW_USXGMII_5000;
+		break;
+	case SPEED_10000:
+		speed_sel = DW_USXGMII_10000;
+		break;
+	default:
+		/* Nothing to do here */
+		return 0;
+	}
+
+	val = dw_read_vpcs(phydev, MDIO_CTRL1);
+	if (val < 0)
+		return val;
+
+	val = dw_write_vpcs(phydev, MDIO_CTRL1, val | DW_USXGMII_EN);
+	if (val < 0)
+		return val;
+
+	val = phy_read_mmd(phydev, MDIO_MMD_VEND2, MDIO_CTRL1);
+	if (val < 0)
+		return val;
+
+	val &= ~DW_USXGMII_SS_MASK;
+	val |= speed_sel | DW_USXGMII_FULL;
+
+	val = phy_write_mmd(phydev, MDIO_MMD_VEND2, MDIO_CTRL1, val);
+	if (val < 0)
+		return val;
+
+	val = dw_read_vpcs(phydev, MDIO_CTRL1);
+	if (val < 0)
+		return val;
+
+	val = dw_write_vpcs(phydev, MDIO_CTRL1, val | DW_USXGMII_RST);
+	if (val < 0)
+		return val;
+
+	return 0;
+}
+
+static int dw_get_features(struct phy_device *phydev)
+{
+	linkmode_set_bit(ETHTOOL_LINK_MODE_Pause_BIT, phydev->supported);
+	linkmode_set_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, phydev->supported);
+	linkmode_set_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, phydev->supported);
+	linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseKX_Full_BIT,
+			 phydev->supported);
+	linkmode_set_bit(ETHTOOL_LINK_MODE_10000baseKX4_Full_BIT,
+			 phydev->supported);
+	linkmode_set_bit(ETHTOOL_LINK_MODE_10000baseKR_Full_BIT,
+			 phydev->supported);
+	linkmode_set_bit(ETHTOOL_LINK_MODE_2500baseX_Full_BIT,
+			 phydev->supported);
+	phydev->interface = PHY_INTERFACE_MODE_USXGMII;
+	return 0;
+}
+
+static int dw_config_aneg_c73(struct phy_device *phydev)
+{
+	u32 adv = 0;
+	int ret;
+
+	/* SR_AN_ADV3 */
+	adv = phy_read_mmd(phydev, MDIO_MMD_AN, DW_SR_AN_ADV3);
+	if (adv < 0)
+		return adv;
+
+	adv &= ~DW_C73_2500KX;
+
+	if (linkmode_test_bit(ETHTOOL_LINK_MODE_2500baseX_Full_BIT,
+			      phydev->supported))
+		adv |= DW_C73_2500KX;
+
+	ret = phy_write_mmd(phydev, MDIO_MMD_AN, DW_SR_AN_ADV3, adv);
+	if (ret < 0)
+		return ret;
+
+	/* SR_AN_ADV2 */
+	adv = phy_read_mmd(phydev, MDIO_MMD_AN, DW_SR_AN_ADV2);
+	if (adv < 0)
+		return adv;
+
+	adv &= ~(DW_C73_1000KX | DW_C73_10000KX4 | DW_C73_10000KR);
+
+	if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseKX_Full_BIT,
+			      phydev->supported))
+		adv |= DW_C73_1000KX;
+	if (linkmode_test_bit(ETHTOOL_LINK_MODE_10000baseKX4_Full_BIT,
+			      phydev->supported))
+		adv |= DW_C73_10000KX4;
+	if (linkmode_test_bit(ETHTOOL_LINK_MODE_10000baseKR_Full_BIT,
+			      phydev->supported))
+		adv |= DW_C73_10000KR;
+
+	ret = phy_write_mmd(phydev, MDIO_MMD_AN, DW_SR_AN_ADV2, adv);
+	if (ret < 0)
+		return ret;
+
+	/* SR_AN_ADV1 */
+	adv = phy_read_mmd(phydev, MDIO_MMD_AN, DW_SR_AN_ADV1);
+	if (adv < 0)
+		return adv;
+
+	adv &= ~(DW_C73_PAUSE | DW_C73_ASYM_PAUSE);
+
+	if (linkmode_test_bit(ETHTOOL_LINK_MODE_Pause_BIT, phydev->supported))
+		adv |= DW_C73_PAUSE;
+	if (linkmode_test_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT,
+			      phydev->supported))
+		adv |= DW_C73_ASYM_PAUSE;
+
+	ret = phy_write_mmd(phydev, MDIO_MMD_AN, DW_SR_AN_ADV1, adv);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int dw_config_aneg(struct phy_device *phydev)
+{
+	unsigned int retries = 12;
+	int val;
+
+	val = dw_config_aneg_c73(phydev);
+	if (val < 0)
+		return val;
+
+	val = phy_modify_mmd(phydev, MDIO_MMD_AN, MDIO_CTRL1,
+			     MDIO_AN_CTRL1_ENABLE | MDIO_AN_CTRL1_RESTART,
+			     MDIO_AN_CTRL1_ENABLE | MDIO_AN_CTRL1_RESTART);
+	if (val < 0)
+		return val;
+
+	do {
+		msleep(50);
+		val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_CTRL1);
+		if (val < 0)
+			return val;
+	} while (val & MDIO_AN_CTRL1_RESTART && --retries);
+
+	if (val & MDIO_AN_CTRL1_RESTART)
+		return -ETIMEDOUT;
+
+	return 0;
+}
+
+static int dw_aneg_done(struct phy_device *phydev)
+{
+	int val;
+
+	val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1);
+	if (val < 0)
+		return val;
+
+	if (val & MDIO_AN_STAT1_COMPLETE) {
+		val = phy_read_mmd(phydev, MDIO_MMD_AN, DW_SR_AN_LP_ABL1);
+		if (val < 0)
+			return val;
+
+		/* Check if Aneg outcome is valid */
+		if (!(val & 0x1))
+			goto fault;
+
+		return 1;
+	}
+
+	if (val & MDIO_AN_STAT1_RFAULT)
+		goto fault;
+
+	return 0;
+fault:
+	dev_err(&phydev->mdio.dev, "Invalid Autoneg result!\n");
+	dev_err(&phydev->mdio.dev, "CTRL1=0x%x, STAT1=0x%x\n",
+		phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_CTRL1),
+		phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1));
+	dev_err(&phydev->mdio.dev, "ADV1=0x%x, ADV2=0x%x, ADV3=0x%x\n",
+		phy_read_mmd(phydev, MDIO_MMD_AN, DW_SR_AN_ADV1),
+		phy_read_mmd(phydev, MDIO_MMD_AN, DW_SR_AN_ADV2),
+		phy_read_mmd(phydev, MDIO_MMD_AN, DW_SR_AN_ADV3));
+	dev_err(&phydev->mdio.dev, "LP_ADV1=0x%x, LP_ADV2=0x%x, LP_ADV3=0x%x\n",
+		phy_read_mmd(phydev, MDIO_MMD_AN, DW_SR_AN_LP_ABL1),
+		phy_read_mmd(phydev, MDIO_MMD_AN, DW_SR_AN_LP_ABL2),
+		phy_read_mmd(phydev, MDIO_MMD_AN, DW_SR_AN_LP_ABL3));
+
+	val = dw_soft_reset(phydev, MDIO_DEVS_PCS);
+	if (val < 0)
+		return val;
+
+	return dw_config_aneg(phydev);
+}
+
+static int dw_read_lpa(struct phy_device *phydev)
+{
+	int val;
+
+	val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1);
+	if (val < 0)
+		return val;
+
+	if (!(val & MDIO_AN_STAT1_COMPLETE)) {
+		linkmode_clear_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
+				   phydev->lp_advertising);
+		mii_10gbt_stat_mod_linkmode_lpa_t(phydev->lp_advertising, 0);
+		mii_adv_mod_linkmode_adv_t(phydev->lp_advertising, 0);
+		phydev->pause = 0;
+		phydev->asym_pause = 0;
+
+		return 0;
+	}
+
+	linkmode_mod_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, phydev->lp_advertising,
+			 val & MDIO_AN_STAT1_LPABLE);
+
+	/* Clause 73 outcome */
+	val = phy_read_mmd(phydev, MDIO_MMD_AN, DW_SR_AN_LP_ABL3);
+	if (val < 0)
+		return val;
+
+	linkmode_mod_bit(ETHTOOL_LINK_MODE_2500baseX_Full_BIT,
+			 phydev->lp_advertising, val & DW_C73_2500KX);
+	/* TODO: 5G-KR */
+
+	val = phy_read_mmd(phydev, MDIO_MMD_AN, DW_SR_AN_LP_ABL2);
+	if (val < 0)
+		return val;
+
+	linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseKX_Full_BIT,
+			 phydev->lp_advertising, val & DW_C73_1000KX);
+	linkmode_mod_bit(ETHTOOL_LINK_MODE_10000baseKX4_Full_BIT,
+			 phydev->lp_advertising, val & DW_C73_10000KX4);
+	linkmode_mod_bit(ETHTOOL_LINK_MODE_10000baseKR_Full_BIT,
+			 phydev->lp_advertising, val & DW_C73_10000KR);
+
+	val = phy_read_mmd(phydev, MDIO_MMD_AN, DW_SR_AN_LP_ABL1);
+	if (val < 0)
+		return val;
+
+	linkmode_mod_bit(ETHTOOL_LINK_MODE_Pause_BIT, phydev->lp_advertising,
+			 val & DW_C73_PAUSE);
+	linkmode_mod_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT,
+			 phydev->lp_advertising, val & DW_C73_ASYM_PAUSE);
+
+	phydev->pause = val & DW_C73_PAUSE ? 1 : 0;
+	phydev->asym_pause = val & DW_C73_ASYM_PAUSE ? 1 : 0;
+
+	linkmode_and(phydev->lp_advertising, phydev->lp_advertising,
+		     phydev->advertising);
+	return 0;
+}
+
+static int dw_read_status(struct phy_device *phydev)
+{
+	int val, stat, prev_link = phydev->link;
+	u32 mmd_mask = MDIO_DEVS_PCS;
+
+	phydev->link = false;
+	phydev->speed = SPEED_UNKNOWN;
+	phydev->duplex = DUPLEX_UNKNOWN;
+	phydev->pause = 0;
+	phydev->asym_pause = 0;
+
+	if (phydev->autoneg == AUTONEG_ENABLE) {
+		val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_CTRL1);
+		if (val < 0)
+			return val;
+
+		if (val & MDIO_AN_CTRL1_RESTART)
+			return 0;
+
+		mmd_mask |= MDIO_DEVS_AN;
+	}
+
+	val = dw_read_link(phydev, mmd_mask);
+	if (val < 0)
+		return val;
+
+	phydev->link = val;
+
+	stat = dw_read_fault(phydev);
+	if (stat) {
+		val = dw_soft_reset(phydev, MDIO_DEVS_PCS);
+		if (val < 0)
+			return val;
+
+		phydev->link = false;
+
+		return dw_config_aneg(phydev);
+	}
+
+	if (phydev->autoneg == AUTONEG_ENABLE) {
+		val = dw_aneg_done(phydev);
+		if (val <= 0) {
+			phydev->link = false;
+			return val;
+		}
+
+		val = dw_read_lpa(phydev);
+		if (val < 0)
+			return val;
+
+		phy_resolve_aneg_linkmode(phydev);
+	} else {
+		val = dw_read_pma(phydev);
+		if (val < 0)
+			return val;
+	}
+
+	if (phydev->link && !prev_link) {
+		val = dw_config_usxgmii(phydev);
+		if (val < 0)
+			return val;
+	}
+
+	return 0;
+}
+
+static int dw_config_init(struct phy_device *phydev)
+{
+	int val;
+
+	val = dw_soft_reset(phydev, MDIO_DEVS_PCS);
+	if (val < 0)
+		return val;
+
+	return dw_config_aneg(phydev);
+}
+
+static int dw_suspend(struct phy_device *phydev)
+{
+	return 0;
+}
+
+static int dw_resume(struct phy_device *phydev)
+{
+	int val;
+
+	val = dw_soft_reset(phydev, MDIO_DEVS_PCS);
+	if (val < 0)
+		return val;
+
+	return dw_config_aneg(phydev);
+}
+
+static struct phy_driver dw_drivers[] = {
+	{
+		.phy_id		= SYNOPSYS_XPHY_ID,
+		.phy_id_mask	= SYNOPSYS_XPHY_MASK,
+		.name		= "Synopsys 10G",
+		.get_features	= dw_get_features,
+		.soft_reset	= genphy_no_soft_reset,
+		.config_init	= dw_config_init,
+		.suspend	= dw_suspend,
+		.resume		= dw_resume,
+		.config_aneg	= dw_config_aneg,
+		.aneg_done	= dw_aneg_done,
+		.read_status	= dw_read_status,
+	},
+};
+module_phy_driver(dw_drivers);
+
+static struct mdio_device_id __maybe_unused dw_tbl[] = {
+	{ SYNOPSYS_XPHY_ID, SYNOPSYS_XPHY_MASK },
+	{ },
+};
+MODULE_DEVICE_TABLE(mdio, dw_tbl);
+MODULE_DESCRIPTION("Synopsys DesignWare PHYs driver");
+MODULE_LICENSE("GPL");