On Mon, Jun 22, 2020 at 01:54:46AM +0300, Ioana Ciornei wrote:
> Add a Lynx PCS module which exposes the necessary operations to drive
> the PCS using PHYLINK.
> 
> The majority of the code is extracted from the Felix DSA driver, which
> will be also modified in a later patch, and exposed as a separate module
> for code reusability purposes.
> 
> At the moment, USXGMII (only with in-band AN and speeds up to 2500),
> SGMII, QSGMII and 2500Base-X (only w/o in-band AN) are supported by the
> Lynx PCS module since these were also supported by Felix.
> 
> The module can only be enabled by the drivers in need and not user
> selectable.
> 
> Signed-off-by: Ioana Ciornei <ioana.cior...@nxp.com>
> ---
> Changes in v2:
>  * got rid of the mdio_lynx_pcs structure and directly exported the
>  functions without the need of an indirection
>  * solved the broken allmodconfig build test by making the module
>  tristate instead of bool
> 
> Changes in v3:
>  * renamed the file to pcs-lynx.c
> 
> 
>  MAINTAINERS                |   7 +
>  drivers/net/phy/Kconfig    |   6 +
>  drivers/net/phy/Makefile   |   1 +
>  drivers/net/phy/pcs-lynx.c | 337 +++++++++++++++++++++++++++++++++++++
>  include/linux/pcs-lynx.h   |  25 +++
>  5 files changed, 376 insertions(+)
>  create mode 100644 drivers/net/phy/pcs-lynx.c
>  create mode 100644 include/linux/pcs-lynx.h
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 301330e02bca..850d8b4f2d29 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -10168,6 +10168,13 @@ S:   Maintained
>  W:   http://linux-test-project.github.io/
>  T:   git git://github.com/linux-test-project/ltp.git
>  
> +LYNX PCS MODULE
> +M:   Ioana Ciornei <ioana.cior...@nxp.com>
> +L:   netdev@vger.kernel.org
> +S:   Maintained
> +F:   drivers/net/phy/pcs-lynx.c
> +F:   include/linux/pcs-lynx.h
> +
>  M68K ARCHITECTURE
>  M:   Geert Uytterhoeven <ge...@linux-m68k.org>
>  L:   linux-m...@lists.linux-m68k.org
> diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
> index f25702386d83..3a573afb21a3 100644
> --- a/drivers/net/phy/Kconfig
> +++ b/drivers/net/phy/Kconfig
> @@ -235,6 +235,12 @@ config MDIO_XPCS
>         This module provides helper functions for Synopsys DesignWare XPCS
>         controllers.
>  
> +config PCS_LYNX
> +     tristate
> +     help
> +       This module provides helper functions for Lynx PCS enablement
> +       representing the PCS as an MDIO device.
> +
>  endif
>  endif
>  
> diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
> index dc9e53b511d6..15ea3345fe3c 100644
> --- a/drivers/net/phy/Makefile
> +++ b/drivers/net/phy/Makefile
> @@ -47,6 +47,7 @@ obj-$(CONFIG_MDIO_SUN4I)    += mdio-sun4i.o
>  obj-$(CONFIG_MDIO_THUNDER)   += mdio-thunder.o
>  obj-$(CONFIG_MDIO_XGENE)     += mdio-xgene.o
>  obj-$(CONFIG_MDIO_XPCS)              += mdio-xpcs.o
> +obj-$(CONFIG_PCS_LYNX)               += pcs-lynx.o
>  
>  obj-$(CONFIG_NETWORK_PHY_TIMESTAMPING) += mii_timestamper.o
>  
> diff --git a/drivers/net/phy/pcs-lynx.c b/drivers/net/phy/pcs-lynx.c
> new file mode 100644
> index 000000000000..23bdd9db4340
> --- /dev/null
> +++ b/drivers/net/phy/pcs-lynx.c
> @@ -0,0 +1,337 @@
> +// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
> +/* Copyright 2020 NXP
> + * Lynx PCS MDIO helpers
> + */
> +
> +#include <linux/mdio.h>
> +#include <linux/phylink.h>
> +#include <linux/pcs-lynx.h>
> +
> +#define SGMII_CLOCK_PERIOD_NS                8 /* PCS is clocked at 125 MHz 
> */
> +#define SGMII_LINK_TIMER_VAL(ns)     ((u32)((ns) / SGMII_CLOCK_PERIOD_NS))
> +
> +#define SGMII_AN_LINK_TIMER_NS               1600000 /* defined by SGMII 
> spec */
> +
> +#define SGMII_LINK_TIMER_LO          0x12
> +#define SGMII_LINK_TIMER_HI          0x13
> +#define SGMII_IF_MODE                        0x14
> +#define SGMII_IF_MODE_SGMII_EN               BIT(0)
> +#define SGMII_IF_MODE_USE_SGMII_AN   BIT(1)
> +#define SGMII_IF_MODE_SPEED(x)               (((x) << 2) & GENMASK(3, 2))
> +#define SGMII_IF_MODE_SPEED_MSK              GENMASK(3, 2)
> +#define SGMII_IF_MODE_DUPLEX         BIT(4)

Given that this is in the .c file, and this code will be re-used in
other places where there is support for more than Cisco SGMII, can
we lose the SGMII_ prefix please?  Maybe use names such as those
I have in "dpaa2-mac: add 1000BASE-X/SGMII PCS support" ?

(I hate the way a single lane gigabit serdes link that supports
1000base-x gets incorrectly called "SGMII".)

> +
> +#define USXGMII_ADVERTISE_LSTATUS(x) (((x) << 15) & BIT(15))
> +#define USXGMII_ADVERTISE_FDX                BIT(12)
> +#define USXGMII_ADVERTISE_SPEED(x)   (((x) << 9) & GENMASK(11, 9))
> +
> +#define USXGMII_LPA_LSTATUS(lpa)     ((lpa) >> 15)
> +#define USXGMII_LPA_DUPLEX(lpa)              (((lpa) & GENMASK(12, 12)) >> 
> 12)
> +#define USXGMII_LPA_SPEED(lpa)               (((lpa) & GENMASK(11, 9)) >> 9)
> +
> +enum usxgmii_speed {
> +     USXGMII_SPEED_10        = 0,
> +     USXGMII_SPEED_100       = 1,
> +     USXGMII_SPEED_1000      = 2,
> +     USXGMII_SPEED_2500      = 4,
> +};

These are not specific to the Lynx PCS, but are the standard layout
of the USXGMII word.  These ought to be in a header file similar to
what we do with the SGMII definitions in include/uapi/linux/mii.h.
I think as these are Clause 45, they possibly belong in
include/uapi/linux/mdio.h ?  In any case, one of my comments below
suggests that some of the uses of these definitions should be moved
into phylink's helpers.

> +
> +enum sgmii_speed {
> +     SGMII_SPEED_10          = 0,
> +     SGMII_SPEED_100         = 1,
> +     SGMII_SPEED_1000        = 2,
> +     SGMII_SPEED_2500        = 2,
> +};
> +
> +static void lynx_pcs_an_restart_usxgmii(struct mdio_device *pcs)
> +{
> +     mdiobus_c45_write(pcs->bus, pcs->addr,
> +                       MDIO_MMD_VEND2, MII_BMCR,
> +                       BMCR_RESET | BMCR_ANENABLE | BMCR_ANRESTART);
> +}

Phylink will not call *_an_restart() for USXGMII, so this code is
unreachable.

> +
> +void lynx_pcs_an_restart(struct mdio_device *pcs, phy_interface_t ifmode)
> +{
> +     switch (ifmode) {
> +     case PHY_INTERFACE_MODE_SGMII:
> +     case PHY_INTERFACE_MODE_QSGMII:
> +             phylink_mii_c22_pcs_an_restart(pcs);

Phylink will not call *_an_restart() for SGMII, so this code is
unreachable.

> +             break;
> +     case PHY_INTERFACE_MODE_USXGMII:
> +             lynx_pcs_an_restart_usxgmii(pcs);
> +             break;
> +     case PHY_INTERFACE_MODE_2500BASEX:
> +             break;
> +     default:
> +             dev_err(&pcs->dev, "Invalid PCS interface type %s\n",
> +                     phy_modes(ifmode));
> +             break;
> +     }
> +}
> +EXPORT_SYMBOL(lynx_pcs_an_restart);
> +
> +static void lynx_pcs_get_state_usxgmii(struct mdio_device *pcs,
> +                                    struct phylink_link_state *state)
> +{
> +     struct mii_bus *bus = pcs->bus;
> +     int addr = pcs->addr;
> +     int status, lpa;
> +
> +     status = mdiobus_c45_read(bus, addr, MDIO_MMD_VEND2, MII_BMSR);
> +     if (status < 0)
> +             return;
> +
> +     state->link = !!(status & MDIO_STAT1_LSTATUS);
> +     state->an_complete = !!(status & MDIO_AN_STAT1_COMPLETE);
> +     if (!state->link || !state->an_complete)
> +             return;
> +
> +     lpa = mdiobus_c45_read(bus, addr, MDIO_MMD_VEND2, MII_LPA);
> +     if (lpa < 0)
> +             return;
> +
> +     switch (USXGMII_LPA_SPEED(lpa)) {
> +     case USXGMII_SPEED_10:
> +             state->speed = SPEED_10;
> +             break;
> +     case USXGMII_SPEED_100:
> +             state->speed = SPEED_100;
> +             break;
> +     case USXGMII_SPEED_1000:
> +             state->speed = SPEED_1000;
> +             break;
> +     case USXGMII_SPEED_2500:
> +             state->speed = SPEED_2500;
> +             break;
> +     default:
> +             break;
> +     }
> +
> +     if (USXGMII_LPA_DUPLEX(lpa))
> +             state->duplex = DUPLEX_FULL;
> +     else
> +             state->duplex = DUPLEX_HALF;

This should be added to phylink_mii_c45_pcs_get_state().  There is
nothing that is Lynx PCS specific here.

> +}
> +
> +static void lynx_pcs_get_state_2500basex(struct mdio_device *pcs,
> +                                      struct phylink_link_state *state)
> +{
> +     struct mii_bus *bus = pcs->bus;
> +     int addr = pcs->addr;
> +     int bmsr, lpa;
> +
> +     bmsr = mdiobus_read(bus, addr, MII_BMSR);
> +     lpa = mdiobus_read(bus, addr, MII_LPA);
> +     if (bmsr < 0 || lpa < 0) {
> +             state->link = false;
> +             return;
> +     }
> +
> +     state->link = !!(bmsr & BMSR_LSTATUS);
> +     state->an_complete = !!(bmsr & BMSR_ANEGCOMPLETE);
> +     if (!state->link)
> +             return;
> +
> +     state->speed = SPEED_2500;
> +     state->pause |= MLO_PAUSE_TX | MLO_PAUSE_RX;

How do you know the other side is using pause frames, or is capable
of dealing with them?

In any case, phylink_mii_c22_pcs_get_state() should be expanded to
deal with the non-inband cases, where we are only interested in the
link state.  It isn't passed the link AN mode, which may be an
issue that needs resolving in some way.

> +}
> +
> +void lynx_pcs_get_state(struct mdio_device *pcs, phy_interface_t ifmode,
> +                     struct phylink_link_state *state)
> +{
> +     switch (ifmode) {
> +     case PHY_INTERFACE_MODE_SGMII:
> +     case PHY_INTERFACE_MODE_QSGMII:
> +             phylink_mii_c22_pcs_get_state(pcs, state);
> +             break;
> +     case PHY_INTERFACE_MODE_2500BASEX:
> +             lynx_pcs_get_state_2500basex(pcs, state);
> +             break;
> +     case PHY_INTERFACE_MODE_USXGMII:
> +             lynx_pcs_get_state_usxgmii(pcs, state);
> +             break;
> +     default:
> +             break;
> +     }
> +
> +     dev_dbg(&pcs->dev,
> +             "mode=%s/%s/%s link=%u an_enabled=%u an_complete=%u\n",
> +             phy_modes(ifmode),
> +             phy_speed_to_str(state->speed),
> +             phy_duplex_to_str(state->duplex),
> +             state->link, state->an_enabled, state->an_complete);
> +}
> +EXPORT_SYMBOL(lynx_pcs_get_state);
> +
> +static int lynx_pcs_config_sgmii(struct mdio_device *pcs, unsigned int mode,
> +                              const unsigned long *advertising)
> +{
> +     struct mii_bus *bus = pcs->bus;
> +     int addr = pcs->addr;
> +     u16 if_mode;
> +     int err;
> +
> +     /* SGMII spec requires tx_config_Reg[15:0] to be exactly 0x4001
> +      * for the MAC PCS in order to acknowledge the AN.
> +      */
> +     mdiobus_write(bus, addr, MII_ADVERTISE,
> +                   ADVERTISE_SGMII | ADVERTISE_LPACK);

This will be overwritten by phylink_mii_c22_pcs_config() below.

> +
> +     if_mode = SGMII_IF_MODE_SGMII_EN;
> +     if (mode == MLO_AN_INBAND) {
> +             u32 link_timer;
> +
> +             if_mode |= SGMII_IF_MODE_USE_SGMII_AN;
> +
> +             /* Adjust link timer for SGMII */
> +             link_timer = SGMII_LINK_TIMER_VAL(SGMII_AN_LINK_TIMER_NS);
> +             mdiobus_write(bus, addr, SGMII_LINK_TIMER_LO, link_timer & 
> 0xffff);
> +             mdiobus_write(bus, addr, SGMII_LINK_TIMER_HI, link_timer >> 16);
> +     }
> +     mdiobus_modify(bus, addr, SGMII_IF_MODE,
> +                    SGMII_IF_MODE_SGMII_EN | SGMII_IF_MODE_USE_SGMII_AN,
> +                    if_mode);
> +
> +     err = phylink_mii_c22_pcs_config(pcs, mode, PHY_INTERFACE_MODE_SGMII,
> +                                      advertising);
> +     return err;
> +}
> +
> +static int lynx_pcs_config_usxgmii(struct mdio_device *pcs, unsigned int 
> mode,
> +                                const unsigned long *advertising)
> +{
> +     struct mii_bus *bus = pcs->bus;
> +     int addr = pcs->addr;
> +
> +     /* Configure device ability for the USXGMII Replicator */
> +     mdiobus_c45_write(bus, addr, MDIO_MMD_VEND2, MII_ADVERTISE,
> +                       USXGMII_ADVERTISE_SPEED(USXGMII_SPEED_2500) |
> +                       USXGMII_ADVERTISE_LSTATUS(1) |
> +                       ADVERTISE_SGMII |
> +                       ADVERTISE_LPACK |
> +                       USXGMII_ADVERTISE_FDX);
> +     return 0;
> +}
> +
> +int lynx_pcs_config(struct mdio_device *pcs, unsigned int mode,
> +                 phy_interface_t ifmode,
> +                 const unsigned long *advertising)
> +{
> +     switch (ifmode) {
> +     case PHY_INTERFACE_MODE_SGMII:
> +     case PHY_INTERFACE_MODE_QSGMII:
> +             lynx_pcs_config_sgmii(pcs, mode, advertising);
> +             break;
> +     case PHY_INTERFACE_MODE_2500BASEX:
> +             /* 2500Base-X only works without in-band AN,
> +              * thus nothing to do here
> +              */
> +             break;
> +     case PHY_INTERFACE_MODE_USXGMII:
> +             lynx_pcs_config_usxgmii(pcs, mode, advertising);
> +             break;
> +     default:
> +             return -EOPNOTSUPP;
> +     }
> +
> +     return 0;
> +}
> +EXPORT_SYMBOL(lynx_pcs_config);
> +
> +static void lynx_pcs_link_up_sgmii(struct mdio_device *pcs, unsigned int 
> mode,
> +                                int speed, int duplex)
> +{
> +     struct mii_bus *bus = pcs->bus;
> +     u16 if_mode = 0, sgmii_speed;
> +     int addr = pcs->addr;
> +
> +     /* The PCS needs to be configured manually only
> +      * when not operating on in-band mode
> +      */
> +     if (mode == MLO_AN_INBAND)
> +             return;
> +
> +     if (duplex == DUPLEX_HALF)
> +             if_mode |= SGMII_IF_MODE_DUPLEX;
> +
> +     switch (speed) {
> +     case SPEED_1000:
> +             sgmii_speed = SGMII_SPEED_1000;
> +             break;
> +     case SPEED_100:
> +             sgmii_speed = SGMII_SPEED_100;
> +             break;
> +     case SPEED_10:
> +             sgmii_speed = SGMII_SPEED_10;
> +             break;
> +     case SPEED_UNKNOWN:
> +             /* Silently don't do anything */
> +             return;
> +     default:
> +             dev_err(&pcs->dev, "Invalid PCS speed %d\n", speed);
> +             return;
> +     }
> +     if_mode |= SGMII_IF_MODE_SPEED(sgmii_speed);
> +
> +     mdiobus_modify(bus, addr, SGMII_IF_MODE,
> +                    SGMII_IF_MODE_DUPLEX | SGMII_IF_MODE_SPEED_MSK,
> +                    if_mode);
> +}
> +
> +/* 2500Base-X is SerDes protocol 7 on Felix and 6 on ENETC. It is a SerDes 
> lane
> + * clocked at 3.125 GHz which encodes symbols with 8b/10b and does not have
> + * auto-negotiation of any link parameters. Electrically it is compatible 
> with
> + * a single lane of XAUI.
> + * The hardware reference manual wants to call this mode SGMII, but it isn't
> + * really, since the fundamental features of SGMII:
> + * - Downgrading the link speed by duplicating symbols
> + * - Auto-negotiation
> + * are not there.
> + * The speed is configured at 1000 in the IF_MODE because the clock frequency
> + * is actually given by a PLL configured in the Reset Configuration Word 
> (RCW).
> + * Since there is no difference between fixed speed SGMII w/o AN and 802.3z 
> w/o
> + * AN, we call this PHY interface type 2500Base-X. In case a PHY negotiates a
> + * lower link speed on line side, the system-side interface remains fixed at
> + * 2500 Mbps and we do rate adaptation through pause frames.
> + */
> +static void lynx_pcs_link_up_2500basex(struct mdio_device *pcs,
> +                                    unsigned int mode,
> +                                    int speed, int duplex)
> +{
> +     struct mii_bus *bus = pcs->bus;
> +     int addr = pcs->addr;
> +
> +     if (mode == MLO_AN_INBAND) {
> +             dev_err(&pcs->dev, "AN not supported for 2500BaseX\n");
> +             return;
> +     }
> +
> +     mdiobus_write(bus, addr, SGMII_IF_MODE,
> +                   SGMII_IF_MODE_SGMII_EN |
> +                   SGMII_IF_MODE_SPEED(SGMII_SPEED_2500));
> +}
> +
> +void lynx_pcs_link_up(struct mdio_device *pcs, unsigned int mode,
> +                   phy_interface_t interface,
> +                   int speed, int duplex)
> +{
> +     switch (interface) {
> +     case PHY_INTERFACE_MODE_SGMII:
> +     case PHY_INTERFACE_MODE_QSGMII:
> +             lynx_pcs_link_up_sgmii(pcs, mode, speed, duplex);
> +             break;
> +     case PHY_INTERFACE_MODE_2500BASEX:
> +             lynx_pcs_link_up_2500basex(pcs, mode, speed, duplex);
> +             break;
> +     case PHY_INTERFACE_MODE_USXGMII:
> +             /* At the moment, only in-band AN is supported for USXGMII
> +              * so nothing to do in link_up
> +              */
> +             break;
> +     default:
> +             break;
> +     }
> +}
> +EXPORT_SYMBOL(lynx_pcs_link_up);
> +
> +MODULE_LICENSE("Dual BSD/GPL");
> diff --git a/include/linux/pcs-lynx.h b/include/linux/pcs-lynx.h
> new file mode 100644
> index 000000000000..336fccb8c55f
> --- /dev/null
> +++ b/include/linux/pcs-lynx.h
> @@ -0,0 +1,25 @@
> +/* SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) */
> +/* Copyright 2020 NXP
> + * Lynx PCS helpers
> + */
> +
> +#ifndef __LINUX_PCS_LYNX_H
> +#define __LINUX_PCS_LYNX_H
> +
> +#include <linux/phy.h>
> +#include <linux/mdio.h>
> +
> +void lynx_pcs_an_restart(struct mdio_device *pcs, phy_interface_t ifmode);
> +
> +void lynx_pcs_get_state(struct mdio_device *pcs, phy_interface_t ifmode,
> +                     struct phylink_link_state *state);
> +
> +int lynx_pcs_config(struct mdio_device *pcs, unsigned int mode,
> +                 phy_interface_t ifmode,
> +                 const unsigned long *advertising);
> +
> +void lynx_pcs_link_up(struct mdio_device *pcs, unsigned int mode,
> +                   phy_interface_t interface,
> +                   int speed, int duplex);
> +
> +#endif /* __LINUX_PCS_LYNX_H */
> -- 
> 2.25.1
> 
> 

-- 
RMK's Patch system: https://www.armlinux.org.uk/developer/patches/
FTTP is here! 40Mbps down 10Mbps up. Decent connectivity at last!

Reply via email to