Merge branch 'net-phy-add-Lynx-PCS-MDIO-module'

Ioana Ciornei says:

====================
net: phy: add Lynx PCS MDIO module

Add support for the Lynx PCS as a separate module in drivers/net/phy/.
The advantage of this structure is that multiple ethernet or switch
drivers used on NXP hardware (ENETC, Seville, Felix DSA switch etc) can
share the same implementation of PCS configuration and runtime
management.

The module implements phylink_pcs_ops and exports a phylink_pcs
(incorporated into a lynx_pcs) which can be directly passed to phylink
through phylink_pcs_set.

The first 3 patches add some missing pieces in phylink and the locked
mdiobus write accessor. Next, the Lynx PCS MDIO module is added as a
standalone module. The majority of the code is extracted from the Felix
DSA driver. The last patch makes the necessary changes in the Felix and
Seville drivers in order to use the new common PCS implementation.

At the moment, USXGMII (only with in-band AN), SGMII, QSGMII (with and
without in-band AN) and 2500Base-X (only w/o in-band AN) are supported
by the Lynx PCS MDIO module since these were also supported by Felix and
no functional change is intended at this time.

Changes in v2:
 * got rid of the mdio_lynx_pcs structure and directly exported the
 functions without the need of an indirection
 * made the necessary adjustments for this in the Felix DSA driver
 * solved the broken allmodconfig build test by making the module
 tristate instead of bool
 * fixed a memory leakage in the Felix driver (the pcs structure was
 allocated twice)

Changes in v3:
 * added support for PHYLINK PCS ops in DSA (patch 5/9)
 * cleanup in Felix PHYLINK operations and migrate to
 phylink_mac_link_up() being the callback of choice for applying MAC
 configuration (patches 6-8)

Changes in v4:
 * use the newly introduced phylink PCS mechanism
 * install the phylink_pcs in the phylink_mac_config DSA ops
 * remove the direct implementations of the PCS ops
 * do no use the SGMII_ prefix when referring to the IF_MORE register
 * add a phylink helper to decode the USXGMII code word
 * remove cleanup patches for Felix (these have been already accepted)
 * Seville (recently introduced) now has PCS support through the same
 Lynx PCS module

Changes in v5:
 - move the pcs-lynx driver to drivers/net/pcs
 - reword the commit message a bit in 4/5
 - add error checking and error propagation in 4/5
 - s/IF_MODE_DUPLEX/IF_MODE_HALF_DUPLEX in 4/5
====================

Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
David S. Miller 2020-08-31 12:52:33 -07:00
commit 10eb466794
13 changed files with 440 additions and 404 deletions

View file

@ -10293,6 +10293,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.ciornei@nxp.com>
L: netdev@vger.kernel.org
S: Supported
F: drivers/net/phy/pcs-lynx.c
F: include/linux/pcs-lynx.h
M68K ARCHITECTURE
M: Geert Uytterhoeven <geert@linux-m68k.org>
L: linux-m68k@lists.linux-m68k.org

View file

@ -8,6 +8,7 @@ config NET_DSA_MSCC_FELIX
select MSCC_OCELOT_SWITCH_LIB
select NET_DSA_TAG_OCELOT
select FSL_ENETC_MDIO
select PCS_LYNX
help
This driver supports network switches from the Vitesse /
Microsemi / Microchip Ocelot family of switching cores that are

View file

@ -19,6 +19,7 @@
#include <linux/of_net.h>
#include <linux/pci.h>
#include <linux/of.h>
#include <linux/pcs-lynx.h>
#include <net/pkt_sched.h>
#include <net/dsa.h>
#include "felix.h"
@ -196,27 +197,16 @@ static void felix_phylink_validate(struct dsa_switch *ds, int port,
felix->info->phylink_validate(ocelot, port, supported, state);
}
static int felix_phylink_mac_pcs_get_state(struct dsa_switch *ds, int port,
struct phylink_link_state *state)
{
struct ocelot *ocelot = ds->priv;
struct felix *felix = ocelot_to_felix(ocelot);
if (felix->info->pcs_link_state)
felix->info->pcs_link_state(ocelot, port, state);
return 0;
}
static void felix_phylink_mac_config(struct dsa_switch *ds, int port,
unsigned int link_an_mode,
const struct phylink_link_state *state)
{
struct ocelot *ocelot = ds->priv;
struct felix *felix = ocelot_to_felix(ocelot);
struct dsa_port *dp = dsa_to_port(ds, port);
if (felix->info->pcs_config)
felix->info->pcs_config(ocelot, port, link_an_mode, state);
if (felix->pcs[port])
phylink_set_pcs(dp->pl, &felix->pcs[port]->pcs);
}
static void felix_phylink_mac_link_down(struct dsa_switch *ds, int port,
@ -306,10 +296,6 @@ static void felix_phylink_mac_link_up(struct dsa_switch *ds, int port,
ocelot_fields_write(ocelot, port,
QSYS_SWITCH_PORT_MODE_PORT_ENA, 1);
if (felix->info->pcs_link_up)
felix->info->pcs_link_up(ocelot, port, link_an_mode, interface,
speed, duplex);
if (felix->info->port_sched_speed_set)
felix->info->port_sched_speed_set(ocelot, port, speed);
}
@ -626,11 +612,6 @@ static int felix_setup(struct dsa_switch *ds)
ds->mtu_enforcement_ingress = true;
ds->configure_vlan_while_not_filtering = true;
/* It looks like the MAC/PCS interrupt register - PM0_IEVENT (0x8040)
* isn't instantiated for the Felix PF.
* In-band AN may take a few ms to complete, so we need to poll.
*/
ds->pcs_poll = true;
return 0;
}
@ -786,7 +767,6 @@ const struct dsa_switch_ops felix_switch_ops = {
.get_sset_count = felix_get_sset_count,
.get_ts_info = felix_get_ts_info,
.phylink_validate = felix_phylink_validate,
.phylink_mac_link_state = felix_phylink_mac_pcs_get_state,
.phylink_mac_config = felix_phylink_mac_config,
.phylink_mac_link_down = felix_phylink_mac_link_down,
.phylink_mac_link_up = felix_phylink_mac_link_up,

View file

@ -28,15 +28,6 @@ struct felix_info {
int imdio_pci_bar;
int (*mdio_bus_alloc)(struct ocelot *ocelot);
void (*mdio_bus_free)(struct ocelot *ocelot);
void (*pcs_config)(struct ocelot *ocelot, int port,
unsigned int link_an_mode,
const struct phylink_link_state *state);
void (*pcs_link_up)(struct ocelot *ocelot, int port,
unsigned int link_an_mode,
phy_interface_t interface,
int speed, int duplex);
void (*pcs_link_state)(struct ocelot *ocelot, int port,
struct phylink_link_state *state);
void (*phylink_validate)(struct ocelot *ocelot, int port,
unsigned long *supported,
struct phylink_link_state *state);
@ -59,20 +50,11 @@ struct felix {
const struct felix_info *info;
struct ocelot ocelot;
struct mii_bus *imdio;
struct phy_device **pcs;
struct lynx_pcs **pcs;
resource_size_t switch_base;
resource_size_t imdio_base;
};
void vsc9959_pcs_link_state(struct ocelot *ocelot, int port,
struct phylink_link_state *state);
void vsc9959_pcs_config(struct ocelot *ocelot, int port,
unsigned int link_an_mode,
const struct phylink_link_state *state);
void vsc9959_pcs_link_up(struct ocelot *ocelot, int port,
unsigned int link_an_mode,
phy_interface_t interface,
int speed, int duplex);
void vsc9959_mdio_bus_free(struct ocelot *ocelot);
#endif

View file

@ -9,6 +9,7 @@
#include <soc/mscc/ocelot_sys.h>
#include <soc/mscc/ocelot.h>
#include <linux/packing.h>
#include <linux/pcs-lynx.h>
#include <net/pkt_sched.h>
#include <linux/iopoll.h>
#include <linux/mdio.h>
@ -766,347 +767,6 @@ static int vsc9959_reset(struct ocelot *ocelot)
return 0;
}
/* We enable SGMII AN only when the PHY has managed = "in-band-status" in the
* device tree. If we are in MLO_AN_PHY mode, we program directly state->speed
* into the PCS, which is retrieved out-of-band over MDIO. This also has the
* benefit of working with SGMII fixed-links, like downstream switches, where
* both link partners attempt to operate as AN slaves and therefore AN never
* completes. But it also has the disadvantage that some PHY chips don't pass
* traffic if SGMII AN is enabled but not completed (acknowledged by us), so
* setting MLO_AN_INBAND is actually required for those.
*/
static void vsc9959_pcs_config_sgmii(struct phy_device *pcs,
unsigned int link_an_mode,
const struct phylink_link_state *state)
{
int bmsr, bmcr;
/* Some PHYs like VSC8234 don't like it when AN restarts on
* their system side and they restart line side AN too, going
* into an endless link up/down loop. Don't restart PCS AN if
* link is up already.
* We do check that AN is enabled just in case this is the 1st
* call, PCS detects a carrier but AN is disabled from power on
* or by boot loader.
*/
bmcr = phy_read(pcs, MII_BMCR);
if (bmcr < 0)
return;
bmsr = phy_read(pcs, MII_BMSR);
if (bmsr < 0)
return;
if ((bmcr & BMCR_ANENABLE) && (bmsr & BMSR_LSTATUS))
return;
/* SGMII spec requires tx_config_Reg[15:0] to be exactly 0x4001
* for the MAC PCS in order to acknowledge the AN.
*/
phy_write(pcs, MII_ADVERTISE, ADVERTISE_SGMII |
ADVERTISE_LPACK);
phy_write(pcs, ENETC_PCS_IF_MODE,
ENETC_PCS_IF_MODE_SGMII_EN |
ENETC_PCS_IF_MODE_USE_SGMII_AN);
/* Adjust link timer for SGMII */
phy_write(pcs, ENETC_PCS_LINK_TIMER1,
ENETC_PCS_LINK_TIMER1_VAL);
phy_write(pcs, ENETC_PCS_LINK_TIMER2,
ENETC_PCS_LINK_TIMER2_VAL);
phy_set_bits(pcs, MII_BMCR, BMCR_ANENABLE);
}
static void vsc9959_pcs_config_usxgmii(struct phy_device *pcs,
unsigned int link_an_mode,
const struct phylink_link_state *state)
{
/* Configure device ability for the USXGMII Replicator */
phy_write_mmd(pcs, MDIO_MMD_VEND2, MII_ADVERTISE,
MDIO_USXGMII_2500FULL |
MDIO_USXGMII_LINK |
ADVERTISE_SGMII |
ADVERTISE_LPACK);
}
void vsc9959_pcs_config(struct ocelot *ocelot, int port,
unsigned int link_an_mode,
const struct phylink_link_state *state)
{
struct felix *felix = ocelot_to_felix(ocelot);
struct phy_device *pcs = felix->pcs[port];
if (!pcs)
return;
/* The PCS does not implement the BMSR register fully, so capability
* detection via genphy_read_abilities does not work. Since we can get
* the PHY config word from the LPA register though, there is still
* value in using the generic phy_resolve_aneg_linkmode function. So
* populate the supported and advertising link modes manually here.
*/
linkmode_set_bit_array(phy_basic_ports_array,
ARRAY_SIZE(phy_basic_ports_array),
pcs->supported);
linkmode_set_bit(ETHTOOL_LINK_MODE_10baseT_Half_BIT, pcs->supported);
linkmode_set_bit(ETHTOOL_LINK_MODE_10baseT_Full_BIT, pcs->supported);
linkmode_set_bit(ETHTOOL_LINK_MODE_100baseT_Half_BIT, pcs->supported);
linkmode_set_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT, pcs->supported);
linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT, pcs->supported);
linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT, pcs->supported);
if (pcs->interface == PHY_INTERFACE_MODE_2500BASEX ||
pcs->interface == PHY_INTERFACE_MODE_USXGMII)
linkmode_set_bit(ETHTOOL_LINK_MODE_2500baseX_Full_BIT,
pcs->supported);
if (pcs->interface != PHY_INTERFACE_MODE_2500BASEX)
linkmode_set_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
pcs->supported);
phy_advertise_supported(pcs);
if (!phylink_autoneg_inband(link_an_mode))
return;
switch (pcs->interface) {
case PHY_INTERFACE_MODE_SGMII:
case PHY_INTERFACE_MODE_QSGMII:
vsc9959_pcs_config_sgmii(pcs, link_an_mode, state);
break;
case PHY_INTERFACE_MODE_2500BASEX:
phydev_err(pcs, "AN not supported on 3.125GHz SerDes lane\n");
break;
case PHY_INTERFACE_MODE_USXGMII:
vsc9959_pcs_config_usxgmii(pcs, link_an_mode, state);
break;
default:
dev_err(ocelot->dev, "Unsupported link mode %s\n",
phy_modes(pcs->interface));
}
}
static void vsc9959_pcs_link_up_sgmii(struct phy_device *pcs,
unsigned int link_an_mode,
int speed, int duplex)
{
u16 if_mode = ENETC_PCS_IF_MODE_SGMII_EN;
switch (speed) {
case SPEED_1000:
if_mode |= ENETC_PCS_IF_MODE_SGMII_SPEED(ENETC_PCS_SPEED_1000);
break;
case SPEED_100:
if_mode |= ENETC_PCS_IF_MODE_SGMII_SPEED(ENETC_PCS_SPEED_100);
break;
case SPEED_10:
if_mode |= ENETC_PCS_IF_MODE_SGMII_SPEED(ENETC_PCS_SPEED_10);
break;
default:
phydev_err(pcs, "Invalid PCS speed %d\n", speed);
return;
}
if (duplex == DUPLEX_HALF)
if_mode |= ENETC_PCS_IF_MODE_DUPLEX_HALF;
phy_write(pcs, ENETC_PCS_IF_MODE, if_mode);
phy_clear_bits(pcs, MII_BMCR, BMCR_ANENABLE);
}
/* 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 and BMCR MDIO registers
* 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 vsc9959_pcs_link_up_2500basex(struct phy_device *pcs,
unsigned int link_an_mode,
int speed, int duplex)
{
u16 if_mode = ENETC_PCS_IF_MODE_SGMII_SPEED(ENETC_PCS_SPEED_2500) |
ENETC_PCS_IF_MODE_SGMII_EN;
if (duplex == DUPLEX_HALF)
if_mode |= ENETC_PCS_IF_MODE_DUPLEX_HALF;
phy_write(pcs, ENETC_PCS_IF_MODE, if_mode);
phy_clear_bits(pcs, MII_BMCR, BMCR_ANENABLE);
}
void vsc9959_pcs_link_up(struct ocelot *ocelot, int port,
unsigned int link_an_mode,
phy_interface_t interface,
int speed, int duplex)
{
struct felix *felix = ocelot_to_felix(ocelot);
struct phy_device *pcs = felix->pcs[port];
if (!pcs)
return;
if (phylink_autoneg_inband(link_an_mode))
return;
switch (interface) {
case PHY_INTERFACE_MODE_SGMII:
case PHY_INTERFACE_MODE_QSGMII:
vsc9959_pcs_link_up_sgmii(pcs, link_an_mode, speed, duplex);
break;
case PHY_INTERFACE_MODE_2500BASEX:
vsc9959_pcs_link_up_2500basex(pcs, link_an_mode, speed,
duplex);
break;
case PHY_INTERFACE_MODE_USXGMII:
phydev_err(pcs, "USXGMII only supports in-band AN for now\n");
break;
default:
dev_err(ocelot->dev, "Unsupported link mode %s\n",
phy_modes(pcs->interface));
}
}
static void vsc9959_pcs_link_state_resolve(struct phy_device *pcs,
struct phylink_link_state *state)
{
state->an_complete = pcs->autoneg_complete;
state->an_enabled = pcs->autoneg;
state->link = pcs->link;
state->duplex = pcs->duplex;
state->speed = pcs->speed;
/* SGMII AN does not negotiate flow control, but that's ok,
* since phylink already knows that, and does:
* link_state.pause |= pl->phy_state.pause;
*/
state->pause = MLO_PAUSE_NONE;
phydev_dbg(pcs,
"mode=%s/%s/%s adv=%*pb lpa=%*pb link=%u an_enabled=%u an_complete=%u\n",
phy_modes(pcs->interface),
phy_speed_to_str(pcs->speed),
phy_duplex_to_str(pcs->duplex),
__ETHTOOL_LINK_MODE_MASK_NBITS, pcs->advertising,
__ETHTOOL_LINK_MODE_MASK_NBITS, pcs->lp_advertising,
pcs->link, pcs->autoneg, pcs->autoneg_complete);
}
static void vsc9959_pcs_link_state_sgmii(struct phy_device *pcs,
struct phylink_link_state *state)
{
int err;
err = genphy_update_link(pcs);
if (err < 0)
return;
if (pcs->autoneg_complete) {
u16 lpa = phy_read(pcs, MII_LPA);
mii_lpa_to_linkmode_lpa_sgmii(pcs->lp_advertising, lpa);
phy_resolve_aneg_linkmode(pcs);
}
}
static void vsc9959_pcs_link_state_2500basex(struct phy_device *pcs,
struct phylink_link_state *state)
{
int err;
err = genphy_update_link(pcs);
if (err < 0)
return;
pcs->speed = SPEED_2500;
pcs->asym_pause = true;
pcs->pause = true;
}
static void vsc9959_pcs_link_state_usxgmii(struct phy_device *pcs,
struct phylink_link_state *state)
{
int status, lpa;
status = phy_read_mmd(pcs, MDIO_MMD_VEND2, MII_BMSR);
if (status < 0)
return;
pcs->autoneg = true;
pcs->autoneg_complete = !!(status & BMSR_ANEGCOMPLETE);
pcs->link = !!(status & BMSR_LSTATUS);
if (!pcs->link || !pcs->autoneg_complete)
return;
lpa = phy_read_mmd(pcs, MDIO_MMD_VEND2, MII_LPA);
if (lpa < 0)
return;
switch (lpa & MDIO_USXGMII_SPD_MASK) {
case MDIO_USXGMII_10:
pcs->speed = SPEED_10;
break;
case MDIO_USXGMII_100:
pcs->speed = SPEED_100;
break;
case MDIO_USXGMII_1000:
pcs->speed = SPEED_1000;
break;
case MDIO_USXGMII_2500:
pcs->speed = SPEED_2500;
break;
default:
break;
}
if (lpa & MDIO_USXGMII_FULL_DUPLEX)
pcs->duplex = DUPLEX_FULL;
else
pcs->duplex = DUPLEX_HALF;
}
void vsc9959_pcs_link_state(struct ocelot *ocelot, int port,
struct phylink_link_state *state)
{
struct felix *felix = ocelot_to_felix(ocelot);
struct phy_device *pcs = felix->pcs[port];
if (!pcs)
return;
pcs->speed = SPEED_UNKNOWN;
pcs->duplex = DUPLEX_UNKNOWN;
pcs->pause = 0;
pcs->asym_pause = 0;
switch (pcs->interface) {
case PHY_INTERFACE_MODE_SGMII:
case PHY_INTERFACE_MODE_QSGMII:
vsc9959_pcs_link_state_sgmii(pcs, state);
break;
case PHY_INTERFACE_MODE_2500BASEX:
vsc9959_pcs_link_state_2500basex(pcs, state);
break;
case PHY_INTERFACE_MODE_USXGMII:
vsc9959_pcs_link_state_usxgmii(pcs, state);
break;
default:
return;
}
vsc9959_pcs_link_state_resolve(pcs, state);
}
static void vsc9959_phylink_validate(struct ocelot *ocelot, int port,
unsigned long *supported,
struct phylink_link_state *state)
@ -1195,7 +855,7 @@ static int vsc9959_mdio_bus_alloc(struct ocelot *ocelot)
int rc;
felix->pcs = devm_kcalloc(dev, felix->info->num_ports,
sizeof(struct phy_device *),
sizeof(struct lynx_pcs *),
GFP_KERNEL);
if (!felix->pcs) {
dev_err(dev, "failed to allocate array for PCS PHYs\n");
@ -1246,18 +906,26 @@ static int vsc9959_mdio_bus_alloc(struct ocelot *ocelot)
for (port = 0; port < felix->info->num_ports; port++) {
struct ocelot_port *ocelot_port = ocelot->ports[port];
struct phy_device *pcs;
bool is_c45 = false;
struct mdio_device *pcs;
struct lynx_pcs *lynx;
if (ocelot_port->phy_mode == PHY_INTERFACE_MODE_USXGMII)
is_c45 = true;
if (dsa_is_unused_port(felix->ds, port))
continue;
pcs = get_phy_device(felix->imdio, port, is_c45);
if (ocelot_port->phy_mode == PHY_INTERFACE_MODE_INTERNAL)
continue;
pcs = mdio_device_create(felix->imdio, port);
if (IS_ERR(pcs))
continue;
pcs->interface = ocelot_port->phy_mode;
felix->pcs[port] = pcs;
lynx = lynx_pcs_create(pcs);
if (!lynx) {
mdio_device_free(pcs);
continue;
}
felix->pcs[port] = lynx;
dev_info(dev, "Found PCS at internal MDIO address %d\n", port);
}
@ -1271,12 +939,13 @@ void vsc9959_mdio_bus_free(struct ocelot *ocelot)
int port;
for (port = 0; port < ocelot->num_phys_ports; port++) {
struct phy_device *pcs = felix->pcs[port];
struct lynx_pcs *pcs = felix->pcs[port];
if (!pcs)
continue;
put_device(&pcs->mdio.dev);
mdio_device_free(pcs->mdio);
lynx_pcs_destroy(pcs);
}
mdiobus_unregister(felix->imdio);
}
@ -1499,9 +1168,6 @@ static const struct felix_info felix_info_vsc9959 = {
.imdio_pci_bar = 0,
.mdio_bus_alloc = vsc9959_mdio_bus_alloc,
.mdio_bus_free = vsc9959_mdio_bus_free,
.pcs_config = vsc9959_pcs_config,
.pcs_link_up = vsc9959_pcs_link_up,
.pcs_link_state = vsc9959_pcs_link_state,
.phylink_validate = vsc9959_phylink_validate,
.prevalidate_phy_mode = vsc9959_prevalidate_phy_mode,
.port_setup_tc = vsc9959_port_setup_tc,

View file

@ -7,6 +7,7 @@
#include <soc/mscc/ocelot_sys.h>
#include <soc/mscc/ocelot.h>
#include <linux/of_platform.h>
#include <linux/pcs-lynx.h>
#include <linux/packing.h>
#include <linux/iopoll.h>
#include "felix.h"
@ -960,18 +961,27 @@ static int vsc9953_mdio_bus_alloc(struct ocelot *ocelot)
for (port = 0; port < felix->info->num_ports; port++) {
struct ocelot_port *ocelot_port = ocelot->ports[port];
struct phy_device *pcs;
int addr = port + 4;
struct mdio_device *pcs;
struct lynx_pcs *lynx;
if (dsa_is_unused_port(felix->ds, port))
continue;
if (ocelot_port->phy_mode == PHY_INTERFACE_MODE_INTERNAL)
continue;
pcs = get_phy_device(felix->imdio, addr, false);
pcs = mdio_device_create(felix->imdio, addr);
if (IS_ERR(pcs))
continue;
pcs->interface = ocelot_port->phy_mode;
felix->pcs[port] = pcs;
lynx = lynx_pcs_create(pcs);
if (!lynx) {
mdio_device_free(pcs);
continue;
}
felix->pcs[port] = lynx;
dev_info(dev, "Found PCS at internal MDIO address %d\n", addr);
}
@ -1013,9 +1023,6 @@ static const struct felix_info seville_info_vsc9953 = {
.num_ports = 10,
.mdio_bus_alloc = vsc9953_mdio_bus_alloc,
.mdio_bus_free = vsc9959_mdio_bus_free,
.pcs_config = vsc9959_pcs_config,
.pcs_link_up = vsc9959_pcs_link_up,
.pcs_link_state = vsc9959_pcs_link_state,
.phylink_validate = vsc9953_phylink_validate,
.prevalidate_phy_mode = vsc9953_prevalidate_phy_mode,
.xmit_template_populate = vsc9953_xmit_template_populate,

View file

@ -13,4 +13,10 @@ config PCS_XPCS
This module provides helper functions for Synopsys DesignWare XPCS
controllers.
config PCS_LYNX
tristate
help
This module provides helpers to phylink for managing the Lynx PCS
which is part of the Layerscape and QorIQ Ethernet SERDES.
endmenu

View file

@ -2,3 +2,4 @@
# Makefile for Linux PCS drivers
obj-$(CONFIG_PCS_XPCS) += pcs-xpcs.o
obj-$(CONFIG_PCS_LYNX) += pcs-lynx.o

312
drivers/net/pcs/pcs-lynx.c Normal file
View file

@ -0,0 +1,312 @@
// 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 LINK_TIMER_VAL(ns) ((u32)((ns) / SGMII_CLOCK_PERIOD_NS))
#define SGMII_AN_LINK_TIMER_NS 1600000 /* defined by SGMII spec */
#define LINK_TIMER_LO 0x12
#define LINK_TIMER_HI 0x13
#define IF_MODE 0x14
#define IF_MODE_SGMII_EN BIT(0)
#define IF_MODE_USE_SGMII_AN BIT(1)
#define IF_MODE_SPEED(x) (((x) << 2) & GENMASK(3, 2))
#define IF_MODE_SPEED_MSK GENMASK(3, 2)
#define IF_MODE_HALF_DUPLEX BIT(4)
enum sgmii_speed {
SGMII_SPEED_10 = 0,
SGMII_SPEED_100 = 1,
SGMII_SPEED_1000 = 2,
SGMII_SPEED_2500 = 2,
};
#define phylink_pcs_to_lynx(pl_pcs) container_of((pl_pcs), struct lynx_pcs, pcs)
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;
phylink_decode_usxgmii_word(state, lpa);
}
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;
state->duplex = DUPLEX_FULL;
}
static void lynx_pcs_get_state(struct phylink_pcs *pcs,
struct phylink_link_state *state)
{
struct lynx_pcs *lynx = phylink_pcs_to_lynx(pcs);
switch (state->interface) {
case PHY_INTERFACE_MODE_SGMII:
case PHY_INTERFACE_MODE_QSGMII:
phylink_mii_c22_pcs_get_state(lynx->mdio, state);
break;
case PHY_INTERFACE_MODE_2500BASEX:
lynx_pcs_get_state_2500basex(lynx->mdio, state);
break;
case PHY_INTERFACE_MODE_USXGMII:
lynx_pcs_get_state_usxgmii(lynx->mdio, state);
break;
default:
break;
}
dev_dbg(&lynx->mdio->dev,
"mode=%s/%s/%s link=%u an_enabled=%u an_complete=%u\n",
phy_modes(state->interface),
phy_speed_to_str(state->speed),
phy_duplex_to_str(state->duplex),
state->link, state->an_enabled, state->an_complete);
}
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;
if_mode = IF_MODE_SGMII_EN;
if (mode == MLO_AN_INBAND) {
u32 link_timer;
if_mode |= IF_MODE_USE_SGMII_AN;
/* Adjust link timer for SGMII */
link_timer = LINK_TIMER_VAL(SGMII_AN_LINK_TIMER_NS);
mdiobus_write(bus, addr, LINK_TIMER_LO, link_timer & 0xffff);
mdiobus_write(bus, addr, LINK_TIMER_HI, link_timer >> 16);
}
err = mdiobus_modify(bus, addr, IF_MODE,
IF_MODE_SGMII_EN | IF_MODE_USE_SGMII_AN,
if_mode);
if (err)
return err;
return phylink_mii_c22_pcs_config(pcs, mode, PHY_INTERFACE_MODE_SGMII,
advertising);
}
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;
if (!phylink_autoneg_inband(mode)) {
dev_err(&pcs->dev, "USXGMII only supports in-band AN for now\n");
return -EOPNOTSUPP;
}
/* Configure device ability for the USXGMII Replicator */
return mdiobus_c45_write(bus, addr, MDIO_MMD_VEND2, MII_ADVERTISE,
MDIO_USXGMII_10G | MDIO_USXGMII_LINK |
MDIO_USXGMII_FULL_DUPLEX |
ADVERTISE_SGMII | ADVERTISE_LPACK);
}
static int lynx_pcs_config(struct phylink_pcs *pcs, unsigned int mode,
phy_interface_t ifmode,
const unsigned long *advertising,
bool permit)
{
struct lynx_pcs *lynx = phylink_pcs_to_lynx(pcs);
switch (ifmode) {
case PHY_INTERFACE_MODE_SGMII:
case PHY_INTERFACE_MODE_QSGMII:
return lynx_pcs_config_sgmii(lynx->mdio, mode, advertising);
case PHY_INTERFACE_MODE_2500BASEX:
if (phylink_autoneg_inband(mode)) {
dev_err(&lynx->mdio->dev,
"AN not supported on 3.125GHz SerDes lane\n");
return -EOPNOTSUPP;
}
break;
case PHY_INTERFACE_MODE_USXGMII:
return lynx_pcs_config_usxgmii(lynx->mdio, mode, advertising);
default:
return -EOPNOTSUPP;
}
return 0;
}
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 |= IF_MODE_HALF_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 |= IF_MODE_SPEED(sgmii_speed);
mdiobus_modify(bus, addr, IF_MODE,
IF_MODE_HALF_DUPLEX | 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;
u16 if_mode = 0;
if (mode == MLO_AN_INBAND) {
dev_err(&pcs->dev, "AN not supported for 2500BaseX\n");
return;
}
if (duplex == DUPLEX_HALF)
if_mode |= IF_MODE_HALF_DUPLEX;
if_mode |= IF_MODE_SPEED(SGMII_SPEED_2500);
mdiobus_modify(bus, addr, IF_MODE,
IF_MODE_HALF_DUPLEX | IF_MODE_SPEED_MSK,
if_mode);
}
static void lynx_pcs_link_up(struct phylink_pcs *pcs, unsigned int mode,
phy_interface_t interface,
int speed, int duplex)
{
struct lynx_pcs *lynx = phylink_pcs_to_lynx(pcs);
switch (interface) {
case PHY_INTERFACE_MODE_SGMII:
case PHY_INTERFACE_MODE_QSGMII:
lynx_pcs_link_up_sgmii(lynx->mdio, mode, speed, duplex);
break;
case PHY_INTERFACE_MODE_2500BASEX:
lynx_pcs_link_up_2500basex(lynx->mdio, 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;
}
}
static const struct phylink_pcs_ops lynx_pcs_phylink_ops = {
.pcs_get_state = lynx_pcs_get_state,
.pcs_config = lynx_pcs_config,
.pcs_link_up = lynx_pcs_link_up,
};
struct lynx_pcs *lynx_pcs_create(struct mdio_device *mdio)
{
struct lynx_pcs *lynx_pcs;
lynx_pcs = kzalloc(sizeof(*lynx_pcs), GFP_KERNEL);
if (!lynx_pcs)
return NULL;
lynx_pcs->mdio = mdio;
lynx_pcs->pcs.ops = &lynx_pcs_phylink_ops;
lynx_pcs->pcs.poll = true;
return lynx_pcs;
}
EXPORT_SYMBOL(lynx_pcs_create);
void lynx_pcs_destroy(struct lynx_pcs *pcs)
{
kfree(pcs);
}
EXPORT_SYMBOL(lynx_pcs_destroy);
MODULE_LICENSE("Dual BSD/GPL");

View file

@ -2320,6 +2320,49 @@ static void phylink_decode_sgmii_word(struct phylink_link_state *state,
state->duplex = DUPLEX_HALF;
}
/**
* phylink_decode_usxgmii_word() - decode the USXGMII word from a MAC PCS
* @state: a pointer to a struct phylink_link_state.
* @lpa: a 16 bit value which stores the USXGMII auto-negotiation word
*
* Helper for MAC PCS supporting the USXGMII protocol and the auto-negotiation
* code word. Decode the USXGMII code word and populate the corresponding fields
* (speed, duplex) into the phylink_link_state structure.
*/
void phylink_decode_usxgmii_word(struct phylink_link_state *state,
uint16_t lpa)
{
switch (lpa & MDIO_USXGMII_SPD_MASK) {
case MDIO_USXGMII_10:
state->speed = SPEED_10;
break;
case MDIO_USXGMII_100:
state->speed = SPEED_100;
break;
case MDIO_USXGMII_1000:
state->speed = SPEED_1000;
break;
case MDIO_USXGMII_2500:
state->speed = SPEED_2500;
break;
case MDIO_USXGMII_5000:
state->speed = SPEED_5000;
break;
case MDIO_USXGMII_10G:
state->speed = SPEED_10000;
break;
default:
state->link = false;
return;
}
if (lpa & MDIO_USXGMII_FULL_DUPLEX)
state->duplex = DUPLEX_FULL;
else
state->duplex = DUPLEX_HALF;
}
EXPORT_SYMBOL_GPL(phylink_decode_usxgmii_word);
/**
* phylink_mii_c22_pcs_get_state() - read the MAC PCS state
* @pcs: a pointer to a &struct mdio_device.
@ -2363,6 +2406,7 @@ void phylink_mii_c22_pcs_get_state(struct mdio_device *pcs,
break;
case PHY_INTERFACE_MODE_SGMII:
case PHY_INTERFACE_MODE_QSGMII:
phylink_decode_sgmii_word(state, lpa);
break;

View file

@ -358,6 +358,12 @@ static inline int mdiobus_c45_read(struct mii_bus *bus, int prtad, int devad,
return mdiobus_read(bus, prtad, mdiobus_c45_addr(devad, regnum));
}
static inline int mdiobus_c45_write(struct mii_bus *bus, int prtad, int devad,
u16 regnum, u16 val)
{
return mdiobus_write(bus, prtad, mdiobus_c45_addr(devad, regnum), val);
}
int mdiobus_register_device(struct mdio_device *mdiodev);
int mdiobus_unregister_device(struct mdio_device *mdiodev);
bool mdiobus_is_registered_device(struct mii_bus *bus, int addr);

21
include/linux/pcs-lynx.h Normal file
View file

@ -0,0 +1,21 @@
/* 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/mdio.h>
#include <linux/phylink.h>
struct lynx_pcs {
struct phylink_pcs pcs;
struct mdio_device *mdio;
};
struct lynx_pcs *lynx_pcs_create(struct mdio_device *mdio);
void lynx_pcs_destroy(struct lynx_pcs *pcs);
#endif /* __LINUX_PCS_LYNX_H */

View file

@ -490,4 +490,7 @@ void phylink_mii_c22_pcs_an_restart(struct mdio_device *pcs);
void phylink_mii_c45_pcs_get_state(struct mdio_device *pcs,
struct phylink_link_state *state);
void phylink_decode_usxgmii_word(struct phylink_link_state *state,
uint16_t lpa);
#endif