diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
commit | 2c3c1048746a4622d8c89a29670120dc8fab93c4 (patch) | |
tree | 848558de17fb3008cdf4d861b01ac7781903ce39 /drivers/net/pcs | |
parent | Initial commit. (diff) | |
download | linux-upstream.tar.xz linux-upstream.zip |
Adding upstream version 6.1.76.upstream/6.1.76upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/net/pcs')
-rw-r--r-- | drivers/net/pcs/Kconfig | 35 | ||||
-rw-r--r-- | drivers/net/pcs/Makefile | 9 | ||||
-rw-r--r-- | drivers/net/pcs/pcs-altera-tse.c | 175 | ||||
-rw-r--r-- | drivers/net/pcs/pcs-lynx.c | 346 | ||||
-rw-r--r-- | drivers/net/pcs/pcs-rzn1-miic.c | 537 | ||||
-rw-r--r-- | drivers/net/pcs/pcs-xpcs-nxp.c | 185 | ||||
-rw-r--r-- | drivers/net/pcs/pcs-xpcs.c | 1321 | ||||
-rw-r--r-- | drivers/net/pcs/pcs-xpcs.h | 114 |
8 files changed, 2722 insertions, 0 deletions
diff --git a/drivers/net/pcs/Kconfig b/drivers/net/pcs/Kconfig new file mode 100644 index 000000000..6e7e6c346 --- /dev/null +++ b/drivers/net/pcs/Kconfig @@ -0,0 +1,35 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# PCS Layer Configuration +# + +menu "PCS device drivers" + +config PCS_XPCS + tristate + select PHYLINK + help + 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. + +config PCS_RZN1_MIIC + tristate "Renesas RZ/N1 MII converter" + depends on OF && (ARCH_RZN1 || COMPILE_TEST) + help + This module provides a driver for the MII converter that is available + on RZ/N1 SoCs. This PCS converts MII to RMII/RGMII or can be set in + pass-through mode for MII. + +config PCS_ALTERA_TSE + tristate + help + This module provides helper functions for the Altera Triple Speed + Ethernet SGMII PCS, that can be found on the Intel Socfpga family. + +endmenu diff --git a/drivers/net/pcs/Makefile b/drivers/net/pcs/Makefile new file mode 100644 index 000000000..4c780d8f2 --- /dev/null +++ b/drivers/net/pcs/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0 +# Makefile for Linux PCS drivers + +pcs_xpcs-$(CONFIG_PCS_XPCS) := pcs-xpcs.o pcs-xpcs-nxp.o + +obj-$(CONFIG_PCS_XPCS) += pcs_xpcs.o +obj-$(CONFIG_PCS_LYNX) += pcs-lynx.o +obj-$(CONFIG_PCS_RZN1_MIIC) += pcs-rzn1-miic.o +obj-$(CONFIG_PCS_ALTERA_TSE) += pcs-altera-tse.o diff --git a/drivers/net/pcs/pcs-altera-tse.c b/drivers/net/pcs/pcs-altera-tse.c new file mode 100644 index 000000000..97a7cabff --- /dev/null +++ b/drivers/net/pcs/pcs-altera-tse.c @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2022 Bootlin + * + * Maxime Chevallier <maxime.chevallier@bootlin.com> + */ + +#include <linux/netdevice.h> +#include <linux/phy.h> +#include <linux/phylink.h> +#include <linux/pcs-altera-tse.h> + +/* SGMII PCS register addresses + */ +#define SGMII_PCS_SCRATCH 0x10 +#define SGMII_PCS_REV 0x11 +#define SGMII_PCS_LINK_TIMER_0 0x12 +#define SGMII_PCS_LINK_TIMER_REG(x) (0x12 + (x)) +#define SGMII_PCS_LINK_TIMER_1 0x13 +#define SGMII_PCS_IF_MODE 0x14 +#define PCS_IF_MODE_SGMII_ENA BIT(0) +#define PCS_IF_MODE_USE_SGMII_AN BIT(1) +#define PCS_IF_MODE_SGMI_SPEED_MASK GENMASK(3, 2) +#define PCS_IF_MODE_SGMI_SPEED_10 (0 << 2) +#define PCS_IF_MODE_SGMI_SPEED_100 (1 << 2) +#define PCS_IF_MODE_SGMI_SPEED_1000 (2 << 2) +#define PCS_IF_MODE_SGMI_HALF_DUPLEX BIT(4) +#define PCS_IF_MODE_SGMI_PHY_AN BIT(5) +#define SGMII_PCS_DIS_READ_TO 0x15 +#define SGMII_PCS_READ_TO 0x16 +#define SGMII_PCS_SW_RESET_TIMEOUT 100 /* usecs */ + +struct altera_tse_pcs { + struct phylink_pcs pcs; + void __iomem *base; + int reg_width; +}; + +static struct altera_tse_pcs *phylink_pcs_to_tse_pcs(struct phylink_pcs *pcs) +{ + return container_of(pcs, struct altera_tse_pcs, pcs); +} + +static u16 tse_pcs_read(struct altera_tse_pcs *tse_pcs, int regnum) +{ + if (tse_pcs->reg_width == 4) + return readl(tse_pcs->base + regnum * 4); + else + return readw(tse_pcs->base + regnum * 2); +} + +static void tse_pcs_write(struct altera_tse_pcs *tse_pcs, int regnum, + u16 value) +{ + if (tse_pcs->reg_width == 4) + writel(value, tse_pcs->base + regnum * 4); + else + writew(value, tse_pcs->base + regnum * 2); +} + +static int tse_pcs_reset(struct altera_tse_pcs *tse_pcs) +{ + int i = 0; + u16 bmcr; + + /* Reset PCS block */ + bmcr = tse_pcs_read(tse_pcs, MII_BMCR); + bmcr |= BMCR_RESET; + tse_pcs_write(tse_pcs, MII_BMCR, bmcr); + + for (i = 0; i < SGMII_PCS_SW_RESET_TIMEOUT; i++) { + if (!(tse_pcs_read(tse_pcs, MII_BMCR) & BMCR_RESET)) + return 0; + udelay(1); + } + + return -ETIMEDOUT; +} + +static int alt_tse_pcs_validate(struct phylink_pcs *pcs, + unsigned long *supported, + const struct phylink_link_state *state) +{ + if (state->interface == PHY_INTERFACE_MODE_SGMII || + state->interface == PHY_INTERFACE_MODE_1000BASEX) + return 1; + + return -EINVAL; +} + +static int alt_tse_pcs_config(struct phylink_pcs *pcs, unsigned int mode, + phy_interface_t interface, + const unsigned long *advertising, + bool permit_pause_to_mac) +{ + struct altera_tse_pcs *tse_pcs = phylink_pcs_to_tse_pcs(pcs); + u32 ctrl, if_mode; + + ctrl = tse_pcs_read(tse_pcs, MII_BMCR); + if_mode = tse_pcs_read(tse_pcs, SGMII_PCS_IF_MODE); + + /* Set link timer to 1.6ms, as per the MegaCore Function User Guide */ + tse_pcs_write(tse_pcs, SGMII_PCS_LINK_TIMER_0, 0x0D40); + tse_pcs_write(tse_pcs, SGMII_PCS_LINK_TIMER_1, 0x03); + + if (interface == PHY_INTERFACE_MODE_SGMII) { + if_mode |= PCS_IF_MODE_USE_SGMII_AN | PCS_IF_MODE_SGMII_ENA; + } else if (interface == PHY_INTERFACE_MODE_1000BASEX) { + if_mode &= ~(PCS_IF_MODE_USE_SGMII_AN | PCS_IF_MODE_SGMII_ENA); + if_mode |= PCS_IF_MODE_SGMI_SPEED_1000; + } + + ctrl |= (BMCR_SPEED1000 | BMCR_FULLDPLX | BMCR_ANENABLE); + + tse_pcs_write(tse_pcs, MII_BMCR, ctrl); + tse_pcs_write(tse_pcs, SGMII_PCS_IF_MODE, if_mode); + + return tse_pcs_reset(tse_pcs); +} + +static void alt_tse_pcs_get_state(struct phylink_pcs *pcs, + struct phylink_link_state *state) +{ + struct altera_tse_pcs *tse_pcs = phylink_pcs_to_tse_pcs(pcs); + u16 bmsr, lpa; + + bmsr = tse_pcs_read(tse_pcs, MII_BMSR); + lpa = tse_pcs_read(tse_pcs, MII_LPA); + + phylink_mii_c22_pcs_decode_state(state, bmsr, lpa); +} + +static void alt_tse_pcs_an_restart(struct phylink_pcs *pcs) +{ + struct altera_tse_pcs *tse_pcs = phylink_pcs_to_tse_pcs(pcs); + u16 bmcr; + + bmcr = tse_pcs_read(tse_pcs, MII_BMCR); + bmcr |= BMCR_ANRESTART; + tse_pcs_write(tse_pcs, MII_BMCR, bmcr); + + /* This PCS seems to require a soft reset to re-sync the AN logic */ + tse_pcs_reset(tse_pcs); +} + +static const struct phylink_pcs_ops alt_tse_pcs_ops = { + .pcs_validate = alt_tse_pcs_validate, + .pcs_get_state = alt_tse_pcs_get_state, + .pcs_config = alt_tse_pcs_config, + .pcs_an_restart = alt_tse_pcs_an_restart, +}; + +struct phylink_pcs *alt_tse_pcs_create(struct net_device *ndev, + void __iomem *pcs_base, int reg_width) +{ + struct altera_tse_pcs *tse_pcs; + + if (reg_width != 4 && reg_width != 2) + return ERR_PTR(-EINVAL); + + tse_pcs = devm_kzalloc(&ndev->dev, sizeof(*tse_pcs), GFP_KERNEL); + if (!tse_pcs) + return ERR_PTR(-ENOMEM); + + tse_pcs->pcs.ops = &alt_tse_pcs_ops; + tse_pcs->base = pcs_base; + tse_pcs->reg_width = reg_width; + + return &tse_pcs->pcs; +} +EXPORT_SYMBOL_GPL(alt_tse_pcs_create); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Altera TSE PCS driver"); +MODULE_AUTHOR("Maxime Chevallier <maxime.chevallier@bootlin.com>"); diff --git a/drivers/net/pcs/pcs-lynx.c b/drivers/net/pcs/pcs-lynx.c new file mode 100644 index 000000000..7d5fc7f54 --- /dev/null +++ b/drivers/net/pcs/pcs-lynx.c @@ -0,0 +1,346 @@ +// 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 IEEE8023_LINK_TIMER_NS 10000000 + +#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) + +struct lynx_pcs { + struct phylink_pcs pcs; + struct mdio_device *mdio; +}; + +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) +#define lynx_to_phylink_pcs(lynx) (&(lynx)->pcs) + +struct mdio_device *lynx_get_mdio_device(struct phylink_pcs *pcs) +{ + struct lynx_pcs *lynx = phylink_pcs_to_lynx(pcs); + + return lynx->mdio; +} +EXPORT_SYMBOL(lynx_get_mdio_device); + +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) +{ + int bmsr, lpa; + + bmsr = mdiodev_read(pcs, MII_BMSR); + lpa = mdiodev_read(pcs, 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_1000BASEX: + 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; + case PHY_INTERFACE_MODE_10GBASER: + phylink_mii_c45_pcs_get_state(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_giga(struct mdio_device *pcs, unsigned int mode, + phy_interface_t interface, + const unsigned long *advertising) +{ + u32 link_timer; + u16 if_mode; + int err; + + if (interface == PHY_INTERFACE_MODE_1000BASEX) { + link_timer = LINK_TIMER_VAL(IEEE8023_LINK_TIMER_NS); + mdiodev_write(pcs, LINK_TIMER_LO, link_timer & 0xffff); + mdiodev_write(pcs, LINK_TIMER_HI, link_timer >> 16); + + if_mode = 0; + } else { + if_mode = IF_MODE_SGMII_EN; + if (mode == MLO_AN_INBAND) { + if_mode |= IF_MODE_USE_SGMII_AN; + + /* Adjust link timer for SGMII */ + link_timer = LINK_TIMER_VAL(SGMII_AN_LINK_TIMER_NS); + mdiodev_write(pcs, LINK_TIMER_LO, link_timer & 0xffff); + mdiodev_write(pcs, LINK_TIMER_HI, link_timer >> 16); + } + } + + err = mdiodev_modify(pcs, 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, interface, 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_1000BASEX: + case PHY_INTERFACE_MODE_SGMII: + case PHY_INTERFACE_MODE_QSGMII: + return lynx_pcs_config_giga(lynx->mdio, mode, ifmode, + 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); + case PHY_INTERFACE_MODE_10GBASER: + /* Nothing to do here for 10GBASER */ + break; + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static void lynx_pcs_an_restart(struct phylink_pcs *pcs) +{ + struct lynx_pcs *lynx = phylink_pcs_to_lynx(pcs); + + phylink_mii_c22_pcs_an_restart(lynx->mdio); +} + +static void lynx_pcs_link_up_sgmii(struct mdio_device *pcs, unsigned int mode, + int speed, int duplex) +{ + u16 if_mode = 0, sgmii_speed; + + /* 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); + + mdiodev_modify(pcs, 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) +{ + 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); + + mdiodev_modify(pcs, 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_an_restart = lynx_pcs_an_restart, + .pcs_link_up = lynx_pcs_link_up, +}; + +struct phylink_pcs *lynx_pcs_create(struct mdio_device *mdio) +{ + struct lynx_pcs *lynx; + + lynx = kzalloc(sizeof(*lynx), GFP_KERNEL); + if (!lynx) + return NULL; + + lynx->mdio = mdio; + lynx->pcs.ops = &lynx_pcs_phylink_ops; + lynx->pcs.poll = true; + + return lynx_to_phylink_pcs(lynx); +} +EXPORT_SYMBOL(lynx_pcs_create); + +void lynx_pcs_destroy(struct phylink_pcs *pcs) +{ + struct lynx_pcs *lynx = phylink_pcs_to_lynx(pcs); + + kfree(lynx); +} +EXPORT_SYMBOL(lynx_pcs_destroy); + +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/net/pcs/pcs-rzn1-miic.c b/drivers/net/pcs/pcs-rzn1-miic.c new file mode 100644 index 000000000..847ab37f1 --- /dev/null +++ b/drivers/net/pcs/pcs-rzn1-miic.c @@ -0,0 +1,537 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2022 Schneider Electric + * + * Clément Léger <clement.leger@bootlin.com> + */ + +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/mdio.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/pcs-rzn1-miic.h> +#include <linux/phylink.h> +#include <linux/pm_runtime.h> +#include <dt-bindings/net/pcs-rzn1-miic.h> + +#define MIIC_PRCMD 0x0 +#define MIIC_ESID_CODE 0x4 + +#define MIIC_MODCTRL 0x20 +#define MIIC_MODCTRL_SW_MODE GENMASK(4, 0) + +#define MIIC_CONVCTRL(port) (0x100 + (port) * 4) + +#define MIIC_CONVCTRL_CONV_SPEED GENMASK(1, 0) +#define CONV_MODE_10MBPS 0 +#define CONV_MODE_100MBPS 1 +#define CONV_MODE_1000MBPS 2 + +#define MIIC_CONVCTRL_CONV_MODE GENMASK(3, 2) +#define CONV_MODE_MII 0 +#define CONV_MODE_RMII 1 +#define CONV_MODE_RGMII 2 + +#define MIIC_CONVCTRL_FULLD BIT(8) +#define MIIC_CONVCTRL_RGMII_LINK BIT(12) +#define MIIC_CONVCTRL_RGMII_DUPLEX BIT(13) +#define MIIC_CONVCTRL_RGMII_SPEED GENMASK(15, 14) + +#define MIIC_CONVRST 0x114 +#define MIIC_CONVRST_PHYIF_RST(port) BIT(port) +#define MIIC_CONVRST_PHYIF_RST_MASK GENMASK(4, 0) + +#define MIIC_SWCTRL 0x304 +#define MIIC_SWDUPC 0x308 + +#define MIIC_MAX_NR_PORTS 5 + +#define MIIC_MODCTRL_CONF_CONV_NUM 6 +#define MIIC_MODCTRL_CONF_NONE -1 + +/** + * struct modctrl_match - Matching table entry for convctrl configuration + * See section 8.2.1 of manual. + * @mode_cfg: Configuration value for convctrl + * @conv: Configuration of ethernet port muxes. First index is SWITCH_PORTIN, + * then index 1 - 5 are CONV1 - CONV5. + */ +struct modctrl_match { + u32 mode_cfg; + u8 conv[MIIC_MODCTRL_CONF_CONV_NUM]; +}; + +static struct modctrl_match modctrl_match_table[] = { + {0x0, {MIIC_RTOS_PORT, MIIC_GMAC1_PORT, MIIC_SWITCH_PORTD, + MIIC_SWITCH_PORTC, MIIC_SERCOS_PORTB, MIIC_SERCOS_PORTA}}, + {0x1, {MIIC_RTOS_PORT, MIIC_GMAC1_PORT, MIIC_SWITCH_PORTD, + MIIC_SWITCH_PORTC, MIIC_ETHERCAT_PORTB, MIIC_ETHERCAT_PORTA}}, + {0x2, {MIIC_RTOS_PORT, MIIC_GMAC1_PORT, MIIC_SWITCH_PORTD, + MIIC_ETHERCAT_PORTC, MIIC_ETHERCAT_PORTB, MIIC_ETHERCAT_PORTA}}, + {0x3, {MIIC_RTOS_PORT, MIIC_GMAC1_PORT, MIIC_SWITCH_PORTD, + MIIC_SWITCH_PORTC, MIIC_SWITCH_PORTB, MIIC_SWITCH_PORTA}}, + + {0x8, {MIIC_RTOS_PORT, MIIC_GMAC1_PORT, MIIC_SWITCH_PORTD, + MIIC_SWITCH_PORTC, MIIC_SERCOS_PORTB, MIIC_SERCOS_PORTA}}, + {0x9, {MIIC_RTOS_PORT, MIIC_GMAC1_PORT, MIIC_SWITCH_PORTD, + MIIC_SWITCH_PORTC, MIIC_ETHERCAT_PORTB, MIIC_ETHERCAT_PORTA}}, + {0xA, {MIIC_RTOS_PORT, MIIC_GMAC1_PORT, MIIC_SWITCH_PORTD, + MIIC_ETHERCAT_PORTC, MIIC_ETHERCAT_PORTB, MIIC_ETHERCAT_PORTA}}, + {0xB, {MIIC_RTOS_PORT, MIIC_GMAC1_PORT, MIIC_SWITCH_PORTD, + MIIC_SWITCH_PORTC, MIIC_SWITCH_PORTB, MIIC_SWITCH_PORTA}}, + + {0x10, {MIIC_GMAC2_PORT, MIIC_GMAC1_PORT, MIIC_SWITCH_PORTD, + MIIC_SWITCH_PORTC, MIIC_SERCOS_PORTB, MIIC_SERCOS_PORTA}}, + {0x11, {MIIC_GMAC2_PORT, MIIC_GMAC1_PORT, MIIC_SWITCH_PORTD, + MIIC_SWITCH_PORTC, MIIC_ETHERCAT_PORTB, MIIC_ETHERCAT_PORTA}}, + {0x12, {MIIC_GMAC2_PORT, MIIC_GMAC1_PORT, MIIC_SWITCH_PORTD, + MIIC_ETHERCAT_PORTC, MIIC_ETHERCAT_PORTB, MIIC_ETHERCAT_PORTA}}, + {0x13, {MIIC_GMAC2_PORT, MIIC_GMAC1_PORT, MIIC_SWITCH_PORTD, + MIIC_SWITCH_PORTC, MIIC_SWITCH_PORTB, MIIC_SWITCH_PORTA}} +}; + +static const char * const conf_to_string[] = { + [MIIC_GMAC1_PORT] = "GMAC1_PORT", + [MIIC_GMAC2_PORT] = "GMAC2_PORT", + [MIIC_RTOS_PORT] = "RTOS_PORT", + [MIIC_SERCOS_PORTA] = "SERCOS_PORTA", + [MIIC_SERCOS_PORTB] = "SERCOS_PORTB", + [MIIC_ETHERCAT_PORTA] = "ETHERCAT_PORTA", + [MIIC_ETHERCAT_PORTB] = "ETHERCAT_PORTB", + [MIIC_ETHERCAT_PORTC] = "ETHERCAT_PORTC", + [MIIC_SWITCH_PORTA] = "SWITCH_PORTA", + [MIIC_SWITCH_PORTB] = "SWITCH_PORTB", + [MIIC_SWITCH_PORTC] = "SWITCH_PORTC", + [MIIC_SWITCH_PORTD] = "SWITCH_PORTD", + [MIIC_HSR_PORTA] = "HSR_PORTA", + [MIIC_HSR_PORTB] = "HSR_PORTB", +}; + +static const char *index_to_string[MIIC_MODCTRL_CONF_CONV_NUM] = { + "SWITCH_PORTIN", + "CONV1", + "CONV2", + "CONV3", + "CONV4", + "CONV5", +}; + +/** + * struct miic - MII converter structure + * @base: base address of the MII converter + * @dev: Device associated to the MII converter + * @clks: Clocks used for this device + * @nclk: Number of clocks + * @lock: Lock used for read-modify-write access + */ +struct miic { + void __iomem *base; + struct device *dev; + struct clk_bulk_data *clks; + int nclk; + spinlock_t lock; +}; + +/** + * struct miic_port - Per port MII converter struct + * @miic: backiling to MII converter structure + * @pcs: PCS structure associated to the port + * @port: port number + * @interface: interface mode of the port + */ +struct miic_port { + struct miic *miic; + struct phylink_pcs pcs; + int port; + phy_interface_t interface; +}; + +static struct miic_port *phylink_pcs_to_miic_port(struct phylink_pcs *pcs) +{ + return container_of(pcs, struct miic_port, pcs); +} + +static void miic_reg_writel(struct miic *miic, int offset, u32 value) +{ + writel(value, miic->base + offset); +} + +static u32 miic_reg_readl(struct miic *miic, int offset) +{ + return readl(miic->base + offset); +} + +static void miic_reg_rmw(struct miic *miic, int offset, u32 mask, u32 val) +{ + u32 reg; + + spin_lock(&miic->lock); + + reg = miic_reg_readl(miic, offset); + reg &= ~mask; + reg |= val; + miic_reg_writel(miic, offset, reg); + + spin_unlock(&miic->lock); +} + +static void miic_converter_enable(struct miic *miic, int port, int enable) +{ + u32 val = 0; + + if (enable) + val = MIIC_CONVRST_PHYIF_RST(port); + + miic_reg_rmw(miic, MIIC_CONVRST, MIIC_CONVRST_PHYIF_RST(port), val); +} + +static int miic_config(struct phylink_pcs *pcs, unsigned int mode, + phy_interface_t interface, + const unsigned long *advertising, bool permit) +{ + struct miic_port *miic_port = phylink_pcs_to_miic_port(pcs); + struct miic *miic = miic_port->miic; + u32 speed, conv_mode, val, mask; + int port = miic_port->port; + + switch (interface) { + case PHY_INTERFACE_MODE_RMII: + conv_mode = CONV_MODE_RMII; + speed = CONV_MODE_100MBPS; + break; + case PHY_INTERFACE_MODE_RGMII: + case PHY_INTERFACE_MODE_RGMII_ID: + case PHY_INTERFACE_MODE_RGMII_TXID: + case PHY_INTERFACE_MODE_RGMII_RXID: + conv_mode = CONV_MODE_RGMII; + speed = CONV_MODE_1000MBPS; + break; + case PHY_INTERFACE_MODE_MII: + conv_mode = CONV_MODE_MII; + /* When in MII mode, speed should be set to 0 (which is actually + * CONV_MODE_10MBPS) + */ + speed = CONV_MODE_10MBPS; + break; + default: + return -EOPNOTSUPP; + } + + val = FIELD_PREP(MIIC_CONVCTRL_CONV_MODE, conv_mode); + mask = MIIC_CONVCTRL_CONV_MODE; + + /* Update speed only if we are going to change the interface because + * the link might already be up and it would break it if the speed is + * changed. + */ + if (interface != miic_port->interface) { + val |= FIELD_PREP(MIIC_CONVCTRL_CONV_SPEED, speed); + mask |= MIIC_CONVCTRL_CONV_SPEED; + miic_port->interface = interface; + } + + miic_reg_rmw(miic, MIIC_CONVCTRL(port), mask, val); + miic_converter_enable(miic_port->miic, miic_port->port, 1); + + return 0; +} + +static void miic_link_up(struct phylink_pcs *pcs, unsigned int mode, + phy_interface_t interface, int speed, int duplex) +{ + struct miic_port *miic_port = phylink_pcs_to_miic_port(pcs); + struct miic *miic = miic_port->miic; + u32 conv_speed = 0, val = 0; + int port = miic_port->port; + + if (duplex == DUPLEX_FULL) + val |= MIIC_CONVCTRL_FULLD; + + /* No speed in MII through-mode */ + if (interface != PHY_INTERFACE_MODE_MII) { + switch (speed) { + case SPEED_1000: + conv_speed = CONV_MODE_1000MBPS; + break; + case SPEED_100: + conv_speed = CONV_MODE_100MBPS; + break; + case SPEED_10: + conv_speed = CONV_MODE_10MBPS; + break; + default: + return; + } + } + + val |= FIELD_PREP(MIIC_CONVCTRL_CONV_SPEED, conv_speed); + + miic_reg_rmw(miic, MIIC_CONVCTRL(port), + (MIIC_CONVCTRL_CONV_SPEED | MIIC_CONVCTRL_FULLD), val); +} + +static int miic_validate(struct phylink_pcs *pcs, unsigned long *supported, + const struct phylink_link_state *state) +{ + if (phy_interface_mode_is_rgmii(state->interface) || + state->interface == PHY_INTERFACE_MODE_RMII || + state->interface == PHY_INTERFACE_MODE_MII) + return 1; + + return -EINVAL; +} + +static const struct phylink_pcs_ops miic_phylink_ops = { + .pcs_validate = miic_validate, + .pcs_config = miic_config, + .pcs_link_up = miic_link_up, +}; + +struct phylink_pcs *miic_create(struct device *dev, struct device_node *np) +{ + struct platform_device *pdev; + struct miic_port *miic_port; + struct device_node *pcs_np; + struct miic *miic; + u32 port; + + if (!of_device_is_available(np)) + return ERR_PTR(-ENODEV); + + if (of_property_read_u32(np, "reg", &port)) + return ERR_PTR(-EINVAL); + + if (port > MIIC_MAX_NR_PORTS || port < 1) + return ERR_PTR(-EINVAL); + + /* The PCS pdev is attached to the parent node */ + pcs_np = of_get_parent(np); + if (!pcs_np) + return ERR_PTR(-ENODEV); + + if (!of_device_is_available(pcs_np)) { + of_node_put(pcs_np); + return ERR_PTR(-ENODEV); + } + + pdev = of_find_device_by_node(pcs_np); + of_node_put(pcs_np); + if (!pdev || !platform_get_drvdata(pdev)) { + if (pdev) + put_device(&pdev->dev); + return ERR_PTR(-EPROBE_DEFER); + } + + miic_port = kzalloc(sizeof(*miic_port), GFP_KERNEL); + if (!miic_port) { + put_device(&pdev->dev); + return ERR_PTR(-ENOMEM); + } + + miic = platform_get_drvdata(pdev); + device_link_add(dev, miic->dev, DL_FLAG_AUTOREMOVE_CONSUMER); + put_device(&pdev->dev); + + miic_port->miic = miic; + miic_port->port = port - 1; + miic_port->pcs.ops = &miic_phylink_ops; + + return &miic_port->pcs; +} +EXPORT_SYMBOL(miic_create); + +void miic_destroy(struct phylink_pcs *pcs) +{ + struct miic_port *miic_port = phylink_pcs_to_miic_port(pcs); + + miic_converter_enable(miic_port->miic, miic_port->port, 0); + kfree(miic_port); +} +EXPORT_SYMBOL(miic_destroy); + +static int miic_init_hw(struct miic *miic, u32 cfg_mode) +{ + int port; + + /* Unlock write access to accessory registers (cf datasheet). If this + * is going to be used in conjunction with the Cortex-M3, this sequence + * will have to be moved in register write + */ + miic_reg_writel(miic, MIIC_PRCMD, 0x00A5); + miic_reg_writel(miic, MIIC_PRCMD, 0x0001); + miic_reg_writel(miic, MIIC_PRCMD, 0xFFFE); + miic_reg_writel(miic, MIIC_PRCMD, 0x0001); + + miic_reg_writel(miic, MIIC_MODCTRL, + FIELD_PREP(MIIC_MODCTRL_SW_MODE, cfg_mode)); + + for (port = 0; port < MIIC_MAX_NR_PORTS; port++) { + miic_converter_enable(miic, port, 0); + /* Disable speed/duplex control from these registers, datasheet + * says switch registers should be used to setup switch port + * speed and duplex. + */ + miic_reg_writel(miic, MIIC_SWCTRL, 0x0); + miic_reg_writel(miic, MIIC_SWDUPC, 0x0); + } + + return 0; +} + +static bool miic_modctrl_match(s8 table_val[MIIC_MODCTRL_CONF_CONV_NUM], + s8 dt_val[MIIC_MODCTRL_CONF_CONV_NUM]) +{ + int i; + + for (i = 0; i < MIIC_MODCTRL_CONF_CONV_NUM; i++) { + if (dt_val[i] == MIIC_MODCTRL_CONF_NONE) + continue; + + if (dt_val[i] != table_val[i]) + return false; + } + + return true; +} + +static void miic_dump_conf(struct device *dev, + s8 conf[MIIC_MODCTRL_CONF_CONV_NUM]) +{ + const char *conf_name; + int i; + + for (i = 0; i < MIIC_MODCTRL_CONF_CONV_NUM; i++) { + if (conf[i] != MIIC_MODCTRL_CONF_NONE) + conf_name = conf_to_string[conf[i]]; + else + conf_name = "NONE"; + + dev_err(dev, "%s: %s\n", index_to_string[i], conf_name); + } +} + +static int miic_match_dt_conf(struct device *dev, + s8 dt_val[MIIC_MODCTRL_CONF_CONV_NUM], + u32 *mode_cfg) +{ + struct modctrl_match *table_entry; + int i; + + for (i = 0; i < ARRAY_SIZE(modctrl_match_table); i++) { + table_entry = &modctrl_match_table[i]; + + if (miic_modctrl_match(table_entry->conv, dt_val)) { + *mode_cfg = table_entry->mode_cfg; + return 0; + } + } + + dev_err(dev, "Failed to apply requested configuration\n"); + miic_dump_conf(dev, dt_val); + + return -EINVAL; +} + +static int miic_parse_dt(struct device *dev, u32 *mode_cfg) +{ + s8 dt_val[MIIC_MODCTRL_CONF_CONV_NUM]; + struct device_node *np = dev->of_node; + struct device_node *conv; + u32 conf; + int port; + + memset(dt_val, MIIC_MODCTRL_CONF_NONE, sizeof(dt_val)); + + if (of_property_read_u32(np, "renesas,miic-switch-portin", &conf) == 0) + dt_val[0] = conf; + + for_each_child_of_node(np, conv) { + if (of_property_read_u32(conv, "reg", &port)) + continue; + + if (!of_device_is_available(conv)) + continue; + + if (of_property_read_u32(conv, "renesas,miic-input", &conf) == 0) + dt_val[port] = conf; + } + + return miic_match_dt_conf(dev, dt_val, mode_cfg); +} + +static int miic_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct miic *miic; + u32 mode_cfg; + int ret; + + ret = miic_parse_dt(dev, &mode_cfg); + if (ret < 0) + return ret; + + miic = devm_kzalloc(dev, sizeof(*miic), GFP_KERNEL); + if (!miic) + return -ENOMEM; + + spin_lock_init(&miic->lock); + miic->dev = dev; + miic->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(miic->base)) + return PTR_ERR(miic->base); + + ret = devm_pm_runtime_enable(dev); + if (ret < 0) + return ret; + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) + return ret; + + ret = miic_init_hw(miic, mode_cfg); + if (ret) + goto disable_runtime_pm; + + /* miic_create() relies on that fact that data are attached to the + * platform device to determine if the driver is ready so this needs to + * be the last thing to be done after everything is initialized + * properly. + */ + platform_set_drvdata(pdev, miic); + + return 0; + +disable_runtime_pm: + pm_runtime_put(dev); + + return ret; +} + +static int miic_remove(struct platform_device *pdev) +{ + pm_runtime_put(&pdev->dev); + + return 0; +} + +static const struct of_device_id miic_of_mtable[] = { + { .compatible = "renesas,rzn1-miic" }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, miic_of_mtable); + +static struct platform_driver miic_driver = { + .driver = { + .name = "rzn1_miic", + .suppress_bind_attrs = true, + .of_match_table = miic_of_mtable, + }, + .probe = miic_probe, + .remove = miic_remove, +}; +module_platform_driver(miic_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Renesas MII converter PCS driver"); +MODULE_AUTHOR("Clément Léger <clement.leger@bootlin.com>"); diff --git a/drivers/net/pcs/pcs-xpcs-nxp.c b/drivers/net/pcs/pcs-xpcs-nxp.c new file mode 100644 index 000000000..d16fc58cd --- /dev/null +++ b/drivers/net/pcs/pcs-xpcs-nxp.c @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright 2021 NXP + */ +#include <linux/pcs/pcs-xpcs.h> +#include "pcs-xpcs.h" + +/* LANE_DRIVER1_0 register */ +#define SJA1110_LANE_DRIVER1_0 0x8038 +#define SJA1110_TXDRV(x) (((x) << 12) & GENMASK(14, 12)) + +/* LANE_DRIVER2_0 register */ +#define SJA1110_LANE_DRIVER2_0 0x803a +#define SJA1110_TXDRVTRIM_LSB(x) ((x) & GENMASK_ULL(15, 0)) + +/* LANE_DRIVER2_1 register */ +#define SJA1110_LANE_DRIVER2_1 0x803b +#define SJA1110_LANE_DRIVER2_1_RSV BIT(9) +#define SJA1110_TXDRVTRIM_MSB(x) (((x) & GENMASK_ULL(23, 16)) >> 16) + +/* LANE_TRIM register */ +#define SJA1110_LANE_TRIM 0x8040 +#define SJA1110_TXTEN BIT(11) +#define SJA1110_TXRTRIM(x) (((x) << 8) & GENMASK(10, 8)) +#define SJA1110_TXPLL_BWSEL BIT(7) +#define SJA1110_RXTEN BIT(6) +#define SJA1110_RXRTRIM(x) (((x) << 3) & GENMASK(5, 3)) +#define SJA1110_CDR_GAIN BIT(2) +#define SJA1110_ACCOUPLE_RXVCM_EN BIT(0) + +/* LANE_DATAPATH_1 register */ +#define SJA1110_LANE_DATAPATH_1 0x8037 + +/* POWERDOWN_ENABLE register */ +#define SJA1110_POWERDOWN_ENABLE 0x8041 +#define SJA1110_TXPLL_PD BIT(12) +#define SJA1110_TXPD BIT(11) +#define SJA1110_RXPKDETEN BIT(10) +#define SJA1110_RXCH_PD BIT(9) +#define SJA1110_RXBIAS_PD BIT(8) +#define SJA1110_RESET_SER_EN BIT(7) +#define SJA1110_RESET_SER BIT(6) +#define SJA1110_RESET_DES BIT(5) +#define SJA1110_RCVEN BIT(4) + +/* RXPLL_CTRL0 register */ +#define SJA1110_RXPLL_CTRL0 0x8065 +#define SJA1110_RXPLL_FBDIV(x) (((x) << 2) & GENMASK(9, 2)) + +/* RXPLL_CTRL1 register */ +#define SJA1110_RXPLL_CTRL1 0x8066 +#define SJA1110_RXPLL_REFDIV(x) ((x) & GENMASK(4, 0)) + +/* TXPLL_CTRL0 register */ +#define SJA1110_TXPLL_CTRL0 0x806d +#define SJA1110_TXPLL_FBDIV(x) ((x) & GENMASK(11, 0)) + +/* TXPLL_CTRL1 register */ +#define SJA1110_TXPLL_CTRL1 0x806e +#define SJA1110_TXPLL_REFDIV(x) ((x) & GENMASK(5, 0)) + +/* RX_DATA_DETECT register */ +#define SJA1110_RX_DATA_DETECT 0x8045 + +/* RX_CDR_CTLE register */ +#define SJA1110_RX_CDR_CTLE 0x8042 + +/* In NXP SJA1105, the PCS is integrated with a PMA that has the TX lane + * polarity inverted by default (PLUS is MINUS, MINUS is PLUS). To obtain + * normal non-inverted behavior, the TX lane polarity must be inverted in the + * PCS, via the DIGITAL_CONTROL_2 register. + */ +int nxp_sja1105_sgmii_pma_config(struct dw_xpcs *xpcs) +{ + return xpcs_write(xpcs, MDIO_MMD_VEND2, DW_VR_MII_DIG_CTRL2, + DW_VR_MII_DIG_CTRL2_TX_POL_INV); +} + +static int nxp_sja1110_pma_config(struct dw_xpcs *xpcs, + u16 txpll_fbdiv, u16 txpll_refdiv, + u16 rxpll_fbdiv, u16 rxpll_refdiv, + u16 rx_cdr_ctle) +{ + u16 val; + int ret; + + /* Program TX PLL feedback divider and reference divider settings for + * correct oscillation frequency. + */ + ret = xpcs_write(xpcs, MDIO_MMD_VEND2, SJA1110_TXPLL_CTRL0, + SJA1110_TXPLL_FBDIV(txpll_fbdiv)); + if (ret < 0) + return ret; + + ret = xpcs_write(xpcs, MDIO_MMD_VEND2, SJA1110_TXPLL_CTRL1, + SJA1110_TXPLL_REFDIV(txpll_refdiv)); + if (ret < 0) + return ret; + + /* Program transmitter amplitude and disable amplitude trimming */ + ret = xpcs_write(xpcs, MDIO_MMD_VEND2, SJA1110_LANE_DRIVER1_0, + SJA1110_TXDRV(0x5)); + if (ret < 0) + return ret; + + val = SJA1110_TXDRVTRIM_LSB(0xffffffull); + + ret = xpcs_write(xpcs, MDIO_MMD_VEND2, SJA1110_LANE_DRIVER2_0, val); + if (ret < 0) + return ret; + + val = SJA1110_TXDRVTRIM_MSB(0xffffffull) | SJA1110_LANE_DRIVER2_1_RSV; + + ret = xpcs_write(xpcs, MDIO_MMD_VEND2, SJA1110_LANE_DRIVER2_1, val); + if (ret < 0) + return ret; + + /* Enable input and output resistor terminations for low BER. */ + val = SJA1110_ACCOUPLE_RXVCM_EN | SJA1110_CDR_GAIN | + SJA1110_RXRTRIM(4) | SJA1110_RXTEN | SJA1110_TXPLL_BWSEL | + SJA1110_TXRTRIM(3) | SJA1110_TXTEN; + + ret = xpcs_write(xpcs, MDIO_MMD_VEND2, SJA1110_LANE_TRIM, val); + if (ret < 0) + return ret; + + /* Select PCS as transmitter data source. */ + ret = xpcs_write(xpcs, MDIO_MMD_VEND2, SJA1110_LANE_DATAPATH_1, 0); + if (ret < 0) + return ret; + + /* Program RX PLL feedback divider and reference divider for correct + * oscillation frequency. + */ + ret = xpcs_write(xpcs, MDIO_MMD_VEND2, SJA1110_RXPLL_CTRL0, + SJA1110_RXPLL_FBDIV(rxpll_fbdiv)); + if (ret < 0) + return ret; + + ret = xpcs_write(xpcs, MDIO_MMD_VEND2, SJA1110_RXPLL_CTRL1, + SJA1110_RXPLL_REFDIV(rxpll_refdiv)); + if (ret < 0) + return ret; + + /* Program threshold for receiver signal detector. + * Enable control of RXPLL by receiver signal detector to disable RXPLL + * when an input signal is not present. + */ + ret = xpcs_write(xpcs, MDIO_MMD_VEND2, SJA1110_RX_DATA_DETECT, 0x0005); + if (ret < 0) + return ret; + + /* Enable TX and RX PLLs and circuits. + * Release reset of PMA to enable data flow to/from PCS. + */ + ret = xpcs_read(xpcs, MDIO_MMD_VEND2, SJA1110_POWERDOWN_ENABLE); + if (ret < 0) + return ret; + + val = ret & ~(SJA1110_TXPLL_PD | SJA1110_TXPD | SJA1110_RXCH_PD | + SJA1110_RXBIAS_PD | SJA1110_RESET_SER_EN | + SJA1110_RESET_SER | SJA1110_RESET_DES); + val |= SJA1110_RXPKDETEN | SJA1110_RCVEN; + + ret = xpcs_write(xpcs, MDIO_MMD_VEND2, SJA1110_POWERDOWN_ENABLE, val); + if (ret < 0) + return ret; + + /* Program continuous-time linear equalizer (CTLE) settings. */ + ret = xpcs_write(xpcs, MDIO_MMD_VEND2, SJA1110_RX_CDR_CTLE, + rx_cdr_ctle); + if (ret < 0) + return ret; + + return 0; +} + +int nxp_sja1110_sgmii_pma_config(struct dw_xpcs *xpcs) +{ + return nxp_sja1110_pma_config(xpcs, 0x19, 0x1, 0x19, 0x1, 0x212a); +} + +int nxp_sja1110_2500basex_pma_config(struct dw_xpcs *xpcs) +{ + return nxp_sja1110_pma_config(xpcs, 0x7d, 0x2, 0x7d, 0x2, 0x732a); +} diff --git a/drivers/net/pcs/pcs-xpcs.c b/drivers/net/pcs/pcs-xpcs.c new file mode 100644 index 000000000..3f882bce3 --- /dev/null +++ b/drivers/net/pcs/pcs-xpcs.c @@ -0,0 +1,1321 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2020 Synopsys, Inc. and/or its affiliates. + * Synopsys DesignWare XPCS helpers + * + * Author: Jose Abreu <Jose.Abreu@synopsys.com> + */ + +#include <linux/delay.h> +#include <linux/pcs/pcs-xpcs.h> +#include <linux/mdio.h> +#include <linux/phylink.h> +#include <linux/workqueue.h> +#include "pcs-xpcs.h" + +#define phylink_pcs_to_xpcs(pl_pcs) \ + container_of((pl_pcs), struct dw_xpcs, pcs) + +static const int xpcs_usxgmii_features[] = { + ETHTOOL_LINK_MODE_Pause_BIT, + ETHTOOL_LINK_MODE_Asym_Pause_BIT, + ETHTOOL_LINK_MODE_Autoneg_BIT, + ETHTOOL_LINK_MODE_1000baseKX_Full_BIT, + ETHTOOL_LINK_MODE_10000baseKX4_Full_BIT, + ETHTOOL_LINK_MODE_10000baseKR_Full_BIT, + ETHTOOL_LINK_MODE_2500baseX_Full_BIT, + __ETHTOOL_LINK_MODE_MASK_NBITS, +}; + +static const int xpcs_10gkr_features[] = { + ETHTOOL_LINK_MODE_Pause_BIT, + ETHTOOL_LINK_MODE_Asym_Pause_BIT, + ETHTOOL_LINK_MODE_10000baseKR_Full_BIT, + __ETHTOOL_LINK_MODE_MASK_NBITS, +}; + +static const int xpcs_xlgmii_features[] = { + ETHTOOL_LINK_MODE_Pause_BIT, + ETHTOOL_LINK_MODE_Asym_Pause_BIT, + ETHTOOL_LINK_MODE_25000baseCR_Full_BIT, + ETHTOOL_LINK_MODE_25000baseKR_Full_BIT, + ETHTOOL_LINK_MODE_25000baseSR_Full_BIT, + ETHTOOL_LINK_MODE_40000baseKR4_Full_BIT, + ETHTOOL_LINK_MODE_40000baseCR4_Full_BIT, + ETHTOOL_LINK_MODE_40000baseSR4_Full_BIT, + ETHTOOL_LINK_MODE_40000baseLR4_Full_BIT, + ETHTOOL_LINK_MODE_50000baseCR2_Full_BIT, + ETHTOOL_LINK_MODE_50000baseKR2_Full_BIT, + ETHTOOL_LINK_MODE_50000baseSR2_Full_BIT, + ETHTOOL_LINK_MODE_50000baseKR_Full_BIT, + ETHTOOL_LINK_MODE_50000baseSR_Full_BIT, + ETHTOOL_LINK_MODE_50000baseCR_Full_BIT, + ETHTOOL_LINK_MODE_50000baseLR_ER_FR_Full_BIT, + ETHTOOL_LINK_MODE_50000baseDR_Full_BIT, + ETHTOOL_LINK_MODE_100000baseKR4_Full_BIT, + ETHTOOL_LINK_MODE_100000baseSR4_Full_BIT, + ETHTOOL_LINK_MODE_100000baseCR4_Full_BIT, + ETHTOOL_LINK_MODE_100000baseLR4_ER4_Full_BIT, + ETHTOOL_LINK_MODE_100000baseKR2_Full_BIT, + ETHTOOL_LINK_MODE_100000baseSR2_Full_BIT, + ETHTOOL_LINK_MODE_100000baseCR2_Full_BIT, + ETHTOOL_LINK_MODE_100000baseLR2_ER2_FR2_Full_BIT, + ETHTOOL_LINK_MODE_100000baseDR2_Full_BIT, + __ETHTOOL_LINK_MODE_MASK_NBITS, +}; + +static const int xpcs_sgmii_features[] = { + ETHTOOL_LINK_MODE_Pause_BIT, + ETHTOOL_LINK_MODE_Asym_Pause_BIT, + ETHTOOL_LINK_MODE_Autoneg_BIT, + ETHTOOL_LINK_MODE_10baseT_Half_BIT, + ETHTOOL_LINK_MODE_10baseT_Full_BIT, + ETHTOOL_LINK_MODE_100baseT_Half_BIT, + ETHTOOL_LINK_MODE_100baseT_Full_BIT, + ETHTOOL_LINK_MODE_1000baseT_Half_BIT, + ETHTOOL_LINK_MODE_1000baseT_Full_BIT, + __ETHTOOL_LINK_MODE_MASK_NBITS, +}; + +static const int xpcs_1000basex_features[] = { + ETHTOOL_LINK_MODE_Pause_BIT, + ETHTOOL_LINK_MODE_Asym_Pause_BIT, + ETHTOOL_LINK_MODE_Autoneg_BIT, + ETHTOOL_LINK_MODE_1000baseX_Full_BIT, + __ETHTOOL_LINK_MODE_MASK_NBITS, +}; + +static const int xpcs_2500basex_features[] = { + ETHTOOL_LINK_MODE_Pause_BIT, + ETHTOOL_LINK_MODE_Asym_Pause_BIT, + ETHTOOL_LINK_MODE_Autoneg_BIT, + ETHTOOL_LINK_MODE_2500baseX_Full_BIT, + ETHTOOL_LINK_MODE_2500baseT_Full_BIT, + __ETHTOOL_LINK_MODE_MASK_NBITS, +}; + +static const phy_interface_t xpcs_usxgmii_interfaces[] = { + PHY_INTERFACE_MODE_USXGMII, +}; + +static const phy_interface_t xpcs_10gkr_interfaces[] = { + PHY_INTERFACE_MODE_10GKR, +}; + +static const phy_interface_t xpcs_xlgmii_interfaces[] = { + PHY_INTERFACE_MODE_XLGMII, +}; + +static const phy_interface_t xpcs_sgmii_interfaces[] = { + PHY_INTERFACE_MODE_SGMII, +}; + +static const phy_interface_t xpcs_1000basex_interfaces[] = { + PHY_INTERFACE_MODE_1000BASEX, +}; + +static const phy_interface_t xpcs_2500basex_interfaces[] = { + PHY_INTERFACE_MODE_2500BASEX, + PHY_INTERFACE_MODE_MAX, +}; + +enum { + DW_XPCS_USXGMII, + DW_XPCS_10GKR, + DW_XPCS_XLGMII, + DW_XPCS_SGMII, + DW_XPCS_1000BASEX, + DW_XPCS_2500BASEX, + DW_XPCS_INTERFACE_MAX, +}; + +struct xpcs_compat { + const int *supported; + const phy_interface_t *interface; + int num_interfaces; + int an_mode; + int (*pma_config)(struct dw_xpcs *xpcs); +}; + +struct xpcs_id { + u32 id; + u32 mask; + const struct xpcs_compat *compat; +}; + +static const struct xpcs_compat *xpcs_find_compat(const struct xpcs_id *id, + phy_interface_t interface) +{ + int i, j; + + for (i = 0; i < DW_XPCS_INTERFACE_MAX; i++) { + const struct xpcs_compat *compat = &id->compat[i]; + + for (j = 0; j < compat->num_interfaces; j++) + if (compat->interface[j] == interface) + return compat; + } + + return NULL; +} + +int xpcs_get_an_mode(struct dw_xpcs *xpcs, phy_interface_t interface) +{ + const struct xpcs_compat *compat; + + compat = xpcs_find_compat(xpcs->id, interface); + if (!compat) + return -ENODEV; + + return compat->an_mode; +} +EXPORT_SYMBOL_GPL(xpcs_get_an_mode); + +static bool __xpcs_linkmode_supported(const struct xpcs_compat *compat, + enum ethtool_link_mode_bit_indices linkmode) +{ + int i; + + for (i = 0; compat->supported[i] != __ETHTOOL_LINK_MODE_MASK_NBITS; i++) + if (compat->supported[i] == linkmode) + return true; + + return false; +} + +#define xpcs_linkmode_supported(compat, mode) \ + __xpcs_linkmode_supported(compat, ETHTOOL_LINK_MODE_ ## mode ## _BIT) + +int xpcs_read(struct dw_xpcs *xpcs, int dev, u32 reg) +{ + struct mii_bus *bus = xpcs->mdiodev->bus; + int addr = xpcs->mdiodev->addr; + + return mdiobus_c45_read(bus, addr, dev, reg); +} + +int xpcs_write(struct dw_xpcs *xpcs, int dev, u32 reg, u16 val) +{ + struct mii_bus *bus = xpcs->mdiodev->bus; + int addr = xpcs->mdiodev->addr; + + return mdiobus_c45_write(bus, addr, dev, reg, val); +} + +static int xpcs_modify_changed(struct dw_xpcs *xpcs, int dev, u32 reg, + u16 mask, u16 set) +{ + u32 reg_addr = mdiobus_c45_addr(dev, reg); + + return mdiodev_modify_changed(xpcs->mdiodev, reg_addr, mask, set); +} + +static int xpcs_read_vendor(struct dw_xpcs *xpcs, int dev, u32 reg) +{ + return xpcs_read(xpcs, dev, DW_VENDOR | reg); +} + +static int xpcs_write_vendor(struct dw_xpcs *xpcs, int dev, int reg, + u16 val) +{ + return xpcs_write(xpcs, dev, DW_VENDOR | reg, val); +} + +static int xpcs_read_vpcs(struct dw_xpcs *xpcs, int reg) +{ + return xpcs_read_vendor(xpcs, MDIO_MMD_PCS, reg); +} + +static int xpcs_write_vpcs(struct dw_xpcs *xpcs, int reg, u16 val) +{ + return xpcs_write_vendor(xpcs, MDIO_MMD_PCS, reg, val); +} + +static int xpcs_poll_reset(struct dw_xpcs *xpcs, int dev) +{ + /* Poll until the reset bit clears (50ms per retry == 0.6 sec) */ + unsigned int retries = 12; + int ret; + + do { + msleep(50); + ret = xpcs_read(xpcs, dev, MDIO_CTRL1); + if (ret < 0) + return ret; + } while (ret & MDIO_CTRL1_RESET && --retries); + + return (ret & MDIO_CTRL1_RESET) ? -ETIMEDOUT : 0; +} + +static int xpcs_soft_reset(struct dw_xpcs *xpcs, + const struct xpcs_compat *compat) +{ + int ret, dev; + + switch (compat->an_mode) { + case DW_AN_C73: + dev = MDIO_MMD_PCS; + break; + case DW_AN_C37_SGMII: + case DW_2500BASEX: + case DW_AN_C37_1000BASEX: + dev = MDIO_MMD_VEND2; + break; + default: + return -1; + } + + ret = xpcs_write(xpcs, dev, MDIO_CTRL1, MDIO_CTRL1_RESET); + if (ret < 0) + return ret; + + return xpcs_poll_reset(xpcs, dev); +} + +#define xpcs_warn(__xpcs, __state, __args...) \ +({ \ + if ((__state)->link) \ + dev_warn(&(__xpcs)->mdiodev->dev, ##__args); \ +}) + +static int xpcs_read_fault_c73(struct dw_xpcs *xpcs, + struct phylink_link_state *state) +{ + int ret; + + ret = xpcs_read(xpcs, MDIO_MMD_PCS, MDIO_STAT1); + if (ret < 0) + return ret; + + if (ret & MDIO_STAT1_FAULT) { + xpcs_warn(xpcs, state, "Link fault condition detected!\n"); + return -EFAULT; + } + + ret = xpcs_read(xpcs, MDIO_MMD_PCS, MDIO_STAT2); + if (ret < 0) + return ret; + + if (ret & MDIO_STAT2_RXFAULT) + xpcs_warn(xpcs, state, "Receiver fault detected!\n"); + if (ret & MDIO_STAT2_TXFAULT) + xpcs_warn(xpcs, state, "Transmitter fault detected!\n"); + + ret = xpcs_read_vendor(xpcs, MDIO_MMD_PCS, DW_VR_XS_PCS_DIG_STS); + if (ret < 0) + return ret; + + if (ret & DW_RXFIFO_ERR) { + xpcs_warn(xpcs, state, "FIFO fault condition detected!\n"); + return -EFAULT; + } + + ret = xpcs_read(xpcs, MDIO_MMD_PCS, MDIO_PCS_10GBRT_STAT1); + if (ret < 0) + return ret; + + if (!(ret & MDIO_PCS_10GBRT_STAT1_BLKLK)) + xpcs_warn(xpcs, state, "Link is not locked!\n"); + + ret = xpcs_read(xpcs, MDIO_MMD_PCS, MDIO_PCS_10GBRT_STAT2); + if (ret < 0) + return ret; + + if (ret & MDIO_PCS_10GBRT_STAT2_ERR) { + xpcs_warn(xpcs, state, "Link has errors!\n"); + return -EFAULT; + } + + return 0; +} + +static int xpcs_read_link_c73(struct dw_xpcs *xpcs) +{ + bool link = true; + int ret; + + ret = xpcs_read(xpcs, MDIO_MMD_PCS, MDIO_STAT1); + if (ret < 0) + return ret; + + if (!(ret & MDIO_STAT1_LSTATUS)) + link = false; + + return link; +} + +static int xpcs_get_max_usxgmii_speed(const unsigned long *supported) +{ + int max = SPEED_UNKNOWN; + + if (phylink_test(supported, 1000baseKX_Full)) + max = SPEED_1000; + if (phylink_test(supported, 2500baseX_Full)) + max = SPEED_2500; + if (phylink_test(supported, 10000baseKX4_Full)) + max = SPEED_10000; + if (phylink_test(supported, 10000baseKR_Full)) + max = SPEED_10000; + + return max; +} + +static void xpcs_config_usxgmii(struct dw_xpcs *xpcs, int speed) +{ + int ret, speed_sel; + + switch (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; + } + + ret = xpcs_read_vpcs(xpcs, MDIO_CTRL1); + if (ret < 0) + goto out; + + ret = xpcs_write_vpcs(xpcs, MDIO_CTRL1, ret | DW_USXGMII_EN); + if (ret < 0) + goto out; + + ret = xpcs_read(xpcs, MDIO_MMD_VEND2, MDIO_CTRL1); + if (ret < 0) + goto out; + + ret &= ~DW_USXGMII_SS_MASK; + ret |= speed_sel | DW_USXGMII_FULL; + + ret = xpcs_write(xpcs, MDIO_MMD_VEND2, MDIO_CTRL1, ret); + if (ret < 0) + goto out; + + ret = xpcs_read_vpcs(xpcs, MDIO_CTRL1); + if (ret < 0) + goto out; + + ret = xpcs_write_vpcs(xpcs, MDIO_CTRL1, ret | DW_USXGMII_RST); + if (ret < 0) + goto out; + + return; + +out: + pr_err("%s: XPCS access returned %pe\n", __func__, ERR_PTR(ret)); +} + +static int _xpcs_config_aneg_c73(struct dw_xpcs *xpcs, + const struct xpcs_compat *compat) +{ + int ret, adv; + + /* By default, in USXGMII mode XPCS operates at 10G baud and + * replicates data to achieve lower speeds. Hereby, in this + * default configuration we need to advertise all supported + * modes and not only the ones we want to use. + */ + + /* SR_AN_ADV3 */ + adv = 0; + if (xpcs_linkmode_supported(compat, 2500baseX_Full)) + adv |= DW_C73_2500KX; + + /* TODO: 5000baseKR */ + + ret = xpcs_write(xpcs, MDIO_MMD_AN, DW_SR_AN_ADV3, adv); + if (ret < 0) + return ret; + + /* SR_AN_ADV2 */ + adv = 0; + if (xpcs_linkmode_supported(compat, 1000baseKX_Full)) + adv |= DW_C73_1000KX; + if (xpcs_linkmode_supported(compat, 10000baseKX4_Full)) + adv |= DW_C73_10000KX4; + if (xpcs_linkmode_supported(compat, 10000baseKR_Full)) + adv |= DW_C73_10000KR; + + ret = xpcs_write(xpcs, MDIO_MMD_AN, DW_SR_AN_ADV2, adv); + if (ret < 0) + return ret; + + /* SR_AN_ADV1 */ + adv = DW_C73_AN_ADV_SF; + if (xpcs_linkmode_supported(compat, Pause)) + adv |= DW_C73_PAUSE; + if (xpcs_linkmode_supported(compat, Asym_Pause)) + adv |= DW_C73_ASYM_PAUSE; + + return xpcs_write(xpcs, MDIO_MMD_AN, DW_SR_AN_ADV1, adv); +} + +static int xpcs_config_aneg_c73(struct dw_xpcs *xpcs, + const struct xpcs_compat *compat) +{ + int ret; + + ret = _xpcs_config_aneg_c73(xpcs, compat); + if (ret < 0) + return ret; + + ret = xpcs_read(xpcs, MDIO_MMD_AN, MDIO_CTRL1); + if (ret < 0) + return ret; + + ret |= MDIO_AN_CTRL1_ENABLE | MDIO_AN_CTRL1_RESTART; + + return xpcs_write(xpcs, MDIO_MMD_AN, MDIO_CTRL1, ret); +} + +static int xpcs_aneg_done_c73(struct dw_xpcs *xpcs, + struct phylink_link_state *state, + const struct xpcs_compat *compat) +{ + int ret; + + ret = xpcs_read(xpcs, MDIO_MMD_AN, MDIO_STAT1); + if (ret < 0) + return ret; + + if (ret & MDIO_AN_STAT1_COMPLETE) { + ret = xpcs_read(xpcs, MDIO_MMD_AN, DW_SR_AN_LP_ABL1); + if (ret < 0) + return ret; + + /* Check if Aneg outcome is valid */ + if (!(ret & DW_C73_AN_ADV_SF)) { + xpcs_config_aneg_c73(xpcs, compat); + return 0; + } + + return 1; + } + + return 0; +} + +static int xpcs_read_lpa_c73(struct dw_xpcs *xpcs, + struct phylink_link_state *state) +{ + int ret; + + ret = xpcs_read(xpcs, MDIO_MMD_AN, MDIO_STAT1); + if (ret < 0) + return ret; + + if (!(ret & MDIO_AN_STAT1_LPABLE)) { + phylink_clear(state->lp_advertising, Autoneg); + return 0; + } + + phylink_set(state->lp_advertising, Autoneg); + + /* Clause 73 outcome */ + ret = xpcs_read(xpcs, MDIO_MMD_AN, DW_SR_AN_LP_ABL3); + if (ret < 0) + return ret; + + if (ret & DW_C73_2500KX) + phylink_set(state->lp_advertising, 2500baseX_Full); + + ret = xpcs_read(xpcs, MDIO_MMD_AN, DW_SR_AN_LP_ABL2); + if (ret < 0) + return ret; + + if (ret & DW_C73_1000KX) + phylink_set(state->lp_advertising, 1000baseKX_Full); + if (ret & DW_C73_10000KX4) + phylink_set(state->lp_advertising, 10000baseKX4_Full); + if (ret & DW_C73_10000KR) + phylink_set(state->lp_advertising, 10000baseKR_Full); + + ret = xpcs_read(xpcs, MDIO_MMD_AN, DW_SR_AN_LP_ABL1); + if (ret < 0) + return ret; + + if (ret & DW_C73_PAUSE) + phylink_set(state->lp_advertising, Pause); + if (ret & DW_C73_ASYM_PAUSE) + phylink_set(state->lp_advertising, Asym_Pause); + + linkmode_and(state->lp_advertising, state->lp_advertising, + state->advertising); + return 0; +} + +static void xpcs_resolve_lpa_c73(struct dw_xpcs *xpcs, + struct phylink_link_state *state) +{ + int max_speed = xpcs_get_max_usxgmii_speed(state->lp_advertising); + + state->pause = MLO_PAUSE_TX | MLO_PAUSE_RX; + state->speed = max_speed; + state->duplex = DUPLEX_FULL; +} + +static int xpcs_get_max_xlgmii_speed(struct dw_xpcs *xpcs, + struct phylink_link_state *state) +{ + unsigned long *adv = state->advertising; + int speed = SPEED_UNKNOWN; + int bit; + + for_each_set_bit(bit, adv, __ETHTOOL_LINK_MODE_MASK_NBITS) { + int new_speed = SPEED_UNKNOWN; + + switch (bit) { + case ETHTOOL_LINK_MODE_25000baseCR_Full_BIT: + case ETHTOOL_LINK_MODE_25000baseKR_Full_BIT: + case ETHTOOL_LINK_MODE_25000baseSR_Full_BIT: + new_speed = SPEED_25000; + break; + case ETHTOOL_LINK_MODE_40000baseKR4_Full_BIT: + case ETHTOOL_LINK_MODE_40000baseCR4_Full_BIT: + case ETHTOOL_LINK_MODE_40000baseSR4_Full_BIT: + case ETHTOOL_LINK_MODE_40000baseLR4_Full_BIT: + new_speed = SPEED_40000; + break; + case ETHTOOL_LINK_MODE_50000baseCR2_Full_BIT: + case ETHTOOL_LINK_MODE_50000baseKR2_Full_BIT: + case ETHTOOL_LINK_MODE_50000baseSR2_Full_BIT: + case ETHTOOL_LINK_MODE_50000baseKR_Full_BIT: + case ETHTOOL_LINK_MODE_50000baseSR_Full_BIT: + case ETHTOOL_LINK_MODE_50000baseCR_Full_BIT: + case ETHTOOL_LINK_MODE_50000baseLR_ER_FR_Full_BIT: + case ETHTOOL_LINK_MODE_50000baseDR_Full_BIT: + new_speed = SPEED_50000; + break; + case ETHTOOL_LINK_MODE_100000baseKR4_Full_BIT: + case ETHTOOL_LINK_MODE_100000baseSR4_Full_BIT: + case ETHTOOL_LINK_MODE_100000baseCR4_Full_BIT: + case ETHTOOL_LINK_MODE_100000baseLR4_ER4_Full_BIT: + case ETHTOOL_LINK_MODE_100000baseKR2_Full_BIT: + case ETHTOOL_LINK_MODE_100000baseSR2_Full_BIT: + case ETHTOOL_LINK_MODE_100000baseCR2_Full_BIT: + case ETHTOOL_LINK_MODE_100000baseLR2_ER2_FR2_Full_BIT: + case ETHTOOL_LINK_MODE_100000baseDR2_Full_BIT: + new_speed = SPEED_100000; + break; + default: + continue; + } + + if (new_speed > speed) + speed = new_speed; + } + + return speed; +} + +static void xpcs_resolve_pma(struct dw_xpcs *xpcs, + struct phylink_link_state *state) +{ + state->pause = MLO_PAUSE_TX | MLO_PAUSE_RX; + state->duplex = DUPLEX_FULL; + + switch (state->interface) { + case PHY_INTERFACE_MODE_10GKR: + state->speed = SPEED_10000; + break; + case PHY_INTERFACE_MODE_XLGMII: + state->speed = xpcs_get_max_xlgmii_speed(xpcs, state); + break; + default: + state->speed = SPEED_UNKNOWN; + break; + } +} + +static int xpcs_validate(struct phylink_pcs *pcs, unsigned long *supported, + const struct phylink_link_state *state) +{ + __ETHTOOL_DECLARE_LINK_MODE_MASK(xpcs_supported) = { 0, }; + const struct xpcs_compat *compat; + struct dw_xpcs *xpcs; + int i; + + xpcs = phylink_pcs_to_xpcs(pcs); + compat = xpcs_find_compat(xpcs->id, state->interface); + + /* Populate the supported link modes for this PHY interface type. + * FIXME: what about the port modes and autoneg bit? This masks + * all those away. + */ + if (compat) + for (i = 0; compat->supported[i] != __ETHTOOL_LINK_MODE_MASK_NBITS; i++) + set_bit(compat->supported[i], xpcs_supported); + + linkmode_and(supported, supported, xpcs_supported); + + return 0; +} + +void xpcs_get_interfaces(struct dw_xpcs *xpcs, unsigned long *interfaces) +{ + int i, j; + + for (i = 0; i < DW_XPCS_INTERFACE_MAX; i++) { + const struct xpcs_compat *compat = &xpcs->id->compat[i]; + + for (j = 0; j < compat->num_interfaces; j++) + if (compat->interface[j] < PHY_INTERFACE_MODE_MAX) + __set_bit(compat->interface[j], interfaces); + } +} +EXPORT_SYMBOL_GPL(xpcs_get_interfaces); + +int xpcs_config_eee(struct dw_xpcs *xpcs, int mult_fact_100ns, int enable) +{ + int ret; + + ret = xpcs_read(xpcs, MDIO_MMD_VEND2, DW_VR_MII_EEE_MCTRL0); + if (ret < 0) + return ret; + + if (enable) { + /* Enable EEE */ + ret = DW_VR_MII_EEE_LTX_EN | DW_VR_MII_EEE_LRX_EN | + DW_VR_MII_EEE_TX_QUIET_EN | DW_VR_MII_EEE_RX_QUIET_EN | + DW_VR_MII_EEE_TX_EN_CTRL | DW_VR_MII_EEE_RX_EN_CTRL | + mult_fact_100ns << DW_VR_MII_EEE_MULT_FACT_100NS_SHIFT; + } else { + ret &= ~(DW_VR_MII_EEE_LTX_EN | DW_VR_MII_EEE_LRX_EN | + DW_VR_MII_EEE_TX_QUIET_EN | DW_VR_MII_EEE_RX_QUIET_EN | + DW_VR_MII_EEE_TX_EN_CTRL | DW_VR_MII_EEE_RX_EN_CTRL | + DW_VR_MII_EEE_MULT_FACT_100NS); + } + + ret = xpcs_write(xpcs, MDIO_MMD_VEND2, DW_VR_MII_EEE_MCTRL0, ret); + if (ret < 0) + return ret; + + ret = xpcs_read(xpcs, MDIO_MMD_VEND2, DW_VR_MII_EEE_MCTRL1); + if (ret < 0) + return ret; + + if (enable) + ret |= DW_VR_MII_EEE_TRN_LPI; + else + ret &= ~DW_VR_MII_EEE_TRN_LPI; + + return xpcs_write(xpcs, MDIO_MMD_VEND2, DW_VR_MII_EEE_MCTRL1, ret); +} +EXPORT_SYMBOL_GPL(xpcs_config_eee); + +static int xpcs_config_aneg_c37_sgmii(struct dw_xpcs *xpcs, unsigned int mode) +{ + int ret, mdio_ctrl; + + /* For AN for C37 SGMII mode, the settings are :- + * 1) VR_MII_MMD_CTRL Bit(12) [AN_ENABLE] = 0b (Disable SGMII AN in case + it is already enabled) + * 2) VR_MII_AN_CTRL Bit(2:1)[PCS_MODE] = 10b (SGMII AN) + * 3) VR_MII_AN_CTRL Bit(3) [TX_CONFIG] = 0b (MAC side SGMII) + * DW xPCS used with DW EQoS MAC is always MAC side SGMII. + * 4) VR_MII_DIG_CTRL1 Bit(9) [MAC_AUTO_SW] = 1b (Automatic + * speed/duplex mode change by HW after SGMII AN complete) + * 5) VR_MII_MMD_CTRL Bit(12) [AN_ENABLE] = 1b (Enable SGMII AN) + * + * Note: Since it is MAC side SGMII, there is no need to set + * SR_MII_AN_ADV. MAC side SGMII receives AN Tx Config from + * PHY about the link state change after C28 AN is completed + * between PHY and Link Partner. There is also no need to + * trigger AN restart for MAC-side SGMII. + */ + mdio_ctrl = xpcs_read(xpcs, MDIO_MMD_VEND2, DW_VR_MII_MMD_CTRL); + if (mdio_ctrl < 0) + return mdio_ctrl; + + if (mdio_ctrl & AN_CL37_EN) { + ret = xpcs_write(xpcs, MDIO_MMD_VEND2, DW_VR_MII_MMD_CTRL, + mdio_ctrl & ~AN_CL37_EN); + if (ret < 0) + return ret; + } + + ret = xpcs_read(xpcs, MDIO_MMD_VEND2, DW_VR_MII_AN_CTRL); + if (ret < 0) + return ret; + + ret &= ~(DW_VR_MII_PCS_MODE_MASK | DW_VR_MII_TX_CONFIG_MASK); + ret |= (DW_VR_MII_PCS_MODE_C37_SGMII << + DW_VR_MII_AN_CTRL_PCS_MODE_SHIFT & + DW_VR_MII_PCS_MODE_MASK); + ret |= (DW_VR_MII_TX_CONFIG_MAC_SIDE_SGMII << + DW_VR_MII_AN_CTRL_TX_CONFIG_SHIFT & + DW_VR_MII_TX_CONFIG_MASK); + ret = xpcs_write(xpcs, MDIO_MMD_VEND2, DW_VR_MII_AN_CTRL, ret); + if (ret < 0) + return ret; + + ret = xpcs_read(xpcs, MDIO_MMD_VEND2, DW_VR_MII_DIG_CTRL1); + if (ret < 0) + return ret; + + if (phylink_autoneg_inband(mode)) + ret |= DW_VR_MII_DIG_CTRL1_MAC_AUTO_SW; + else + ret &= ~DW_VR_MII_DIG_CTRL1_MAC_AUTO_SW; + + ret = xpcs_write(xpcs, MDIO_MMD_VEND2, DW_VR_MII_DIG_CTRL1, ret); + if (ret < 0) + return ret; + + if (phylink_autoneg_inband(mode)) + ret = xpcs_write(xpcs, MDIO_MMD_VEND2, DW_VR_MII_MMD_CTRL, + mdio_ctrl | AN_CL37_EN); + + return ret; +} + +static int xpcs_config_aneg_c37_1000basex(struct dw_xpcs *xpcs, unsigned int mode, + const unsigned long *advertising) +{ + phy_interface_t interface = PHY_INTERFACE_MODE_1000BASEX; + int ret, mdio_ctrl, adv; + bool changed = 0; + + /* According to Chap 7.12, to set 1000BASE-X C37 AN, AN must + * be disabled first:- + * 1) VR_MII_MMD_CTRL Bit(12)[AN_ENABLE] = 0b + * 2) VR_MII_AN_CTRL Bit(2:1)[PCS_MODE] = 00b (1000BASE-X C37) + */ + mdio_ctrl = xpcs_read(xpcs, MDIO_MMD_VEND2, DW_VR_MII_MMD_CTRL); + if (mdio_ctrl < 0) + return mdio_ctrl; + + if (mdio_ctrl & AN_CL37_EN) { + ret = xpcs_write(xpcs, MDIO_MMD_VEND2, DW_VR_MII_MMD_CTRL, + mdio_ctrl & ~AN_CL37_EN); + if (ret < 0) + return ret; + } + + ret = xpcs_read(xpcs, MDIO_MMD_VEND2, DW_VR_MII_AN_CTRL); + if (ret < 0) + return ret; + + ret &= ~DW_VR_MII_PCS_MODE_MASK; + ret = xpcs_write(xpcs, MDIO_MMD_VEND2, DW_VR_MII_AN_CTRL, ret); + if (ret < 0) + return ret; + + /* Check for advertising changes and update the C45 MII ADV + * register accordingly. + */ + adv = phylink_mii_c22_pcs_encode_advertisement(interface, + advertising); + if (adv >= 0) { + ret = xpcs_modify_changed(xpcs, MDIO_MMD_VEND2, + MII_ADVERTISE, 0xffff, adv); + if (ret < 0) + return ret; + + changed = ret; + } + + /* Clear CL37 AN complete status */ + ret = xpcs_write(xpcs, MDIO_MMD_VEND2, DW_VR_MII_AN_INTR_STS, 0); + if (ret < 0) + return ret; + + if (phylink_autoneg_inband(mode) && + linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, advertising)) { + ret = xpcs_write(xpcs, MDIO_MMD_VEND2, DW_VR_MII_MMD_CTRL, + mdio_ctrl | AN_CL37_EN); + if (ret < 0) + return ret; + } + + return changed; +} + +static int xpcs_config_2500basex(struct dw_xpcs *xpcs) +{ + int ret; + + ret = xpcs_read(xpcs, MDIO_MMD_VEND2, DW_VR_MII_DIG_CTRL1); + if (ret < 0) + return ret; + ret |= DW_VR_MII_DIG_CTRL1_2G5_EN; + ret &= ~DW_VR_MII_DIG_CTRL1_MAC_AUTO_SW; + ret = xpcs_write(xpcs, MDIO_MMD_VEND2, DW_VR_MII_DIG_CTRL1, ret); + if (ret < 0) + return ret; + + ret = xpcs_read(xpcs, MDIO_MMD_VEND2, DW_VR_MII_MMD_CTRL); + if (ret < 0) + return ret; + ret &= ~AN_CL37_EN; + ret |= SGMII_SPEED_SS6; + ret &= ~SGMII_SPEED_SS13; + return xpcs_write(xpcs, MDIO_MMD_VEND2, DW_VR_MII_MMD_CTRL, ret); +} + +int xpcs_do_config(struct dw_xpcs *xpcs, phy_interface_t interface, + unsigned int mode, const unsigned long *advertising) +{ + const struct xpcs_compat *compat; + int ret; + + compat = xpcs_find_compat(xpcs->id, interface); + if (!compat) + return -ENODEV; + + switch (compat->an_mode) { + case DW_AN_C73: + if (test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, advertising)) { + ret = xpcs_config_aneg_c73(xpcs, compat); + if (ret) + return ret; + } + break; + case DW_AN_C37_SGMII: + ret = xpcs_config_aneg_c37_sgmii(xpcs, mode); + if (ret) + return ret; + break; + case DW_AN_C37_1000BASEX: + ret = xpcs_config_aneg_c37_1000basex(xpcs, mode, + advertising); + if (ret) + return ret; + break; + case DW_2500BASEX: + ret = xpcs_config_2500basex(xpcs); + if (ret) + return ret; + break; + default: + return -1; + } + + if (compat->pma_config) { + ret = compat->pma_config(xpcs); + if (ret) + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(xpcs_do_config); + +static int xpcs_config(struct phylink_pcs *pcs, unsigned int mode, + phy_interface_t interface, + const unsigned long *advertising, + bool permit_pause_to_mac) +{ + struct dw_xpcs *xpcs = phylink_pcs_to_xpcs(pcs); + + return xpcs_do_config(xpcs, interface, mode, advertising); +} + +static int xpcs_get_state_c73(struct dw_xpcs *xpcs, + struct phylink_link_state *state, + const struct xpcs_compat *compat) +{ + int ret; + + /* Link needs to be read first ... */ + state->link = xpcs_read_link_c73(xpcs) > 0 ? 1 : 0; + + /* ... and then we check the faults. */ + ret = xpcs_read_fault_c73(xpcs, state); + if (ret) { + ret = xpcs_soft_reset(xpcs, compat); + if (ret) + return ret; + + state->link = 0; + + return xpcs_do_config(xpcs, state->interface, MLO_AN_INBAND, NULL); + } + + if (state->an_enabled && xpcs_aneg_done_c73(xpcs, state, compat)) { + state->an_complete = true; + xpcs_read_lpa_c73(xpcs, state); + xpcs_resolve_lpa_c73(xpcs, state); + } else if (state->an_enabled) { + state->link = 0; + } else if (state->link) { + xpcs_resolve_pma(xpcs, state); + } + + return 0; +} + +static int xpcs_get_state_c37_sgmii(struct dw_xpcs *xpcs, + struct phylink_link_state *state) +{ + int ret; + + /* Reset link_state */ + state->link = false; + state->speed = SPEED_UNKNOWN; + state->duplex = DUPLEX_UNKNOWN; + state->pause = 0; + + /* For C37 SGMII mode, we check DW_VR_MII_AN_INTR_STS for link + * status, speed and duplex. + */ + ret = xpcs_read(xpcs, MDIO_MMD_VEND2, DW_VR_MII_AN_INTR_STS); + if (ret < 0) + return ret; + + if (ret & DW_VR_MII_C37_ANSGM_SP_LNKSTS) { + int speed_value; + + state->link = true; + + speed_value = (ret & DW_VR_MII_AN_STS_C37_ANSGM_SP) >> + DW_VR_MII_AN_STS_C37_ANSGM_SP_SHIFT; + if (speed_value == DW_VR_MII_C37_ANSGM_SP_1000) + state->speed = SPEED_1000; + else if (speed_value == DW_VR_MII_C37_ANSGM_SP_100) + state->speed = SPEED_100; + else + state->speed = SPEED_10; + + if (ret & DW_VR_MII_AN_STS_C37_ANSGM_FD) + state->duplex = DUPLEX_FULL; + else + state->duplex = DUPLEX_HALF; + } + + return 0; +} + +static int xpcs_get_state_c37_1000basex(struct dw_xpcs *xpcs, + struct phylink_link_state *state) +{ + int lpa, bmsr; + + if (state->an_enabled) { + /* Reset link state */ + state->link = false; + + lpa = xpcs_read(xpcs, MDIO_MMD_VEND2, MII_LPA); + if (lpa < 0 || lpa & LPA_RFAULT) + return lpa; + + bmsr = xpcs_read(xpcs, MDIO_MMD_VEND2, MII_BMSR); + if (bmsr < 0) + return bmsr; + + phylink_mii_c22_pcs_decode_state(state, bmsr, lpa); + } + + return 0; +} + +static void xpcs_get_state(struct phylink_pcs *pcs, + struct phylink_link_state *state) +{ + struct dw_xpcs *xpcs = phylink_pcs_to_xpcs(pcs); + const struct xpcs_compat *compat; + int ret; + + compat = xpcs_find_compat(xpcs->id, state->interface); + if (!compat) + return; + + switch (compat->an_mode) { + case DW_AN_C73: + ret = xpcs_get_state_c73(xpcs, state, compat); + if (ret) { + pr_err("xpcs_get_state_c73 returned %pe\n", + ERR_PTR(ret)); + return; + } + break; + case DW_AN_C37_SGMII: + ret = xpcs_get_state_c37_sgmii(xpcs, state); + if (ret) { + pr_err("xpcs_get_state_c37_sgmii returned %pe\n", + ERR_PTR(ret)); + } + break; + case DW_AN_C37_1000BASEX: + ret = xpcs_get_state_c37_1000basex(xpcs, state); + if (ret) { + pr_err("xpcs_get_state_c37_1000basex returned %pe\n", + ERR_PTR(ret)); + } + break; + default: + return; + } +} + +static void xpcs_link_up_sgmii(struct dw_xpcs *xpcs, unsigned int mode, + int speed, int duplex) +{ + int val, ret; + + if (phylink_autoneg_inband(mode)) + return; + + val = mii_bmcr_encode_fixed(speed, duplex); + ret = xpcs_write(xpcs, MDIO_MMD_VEND2, MDIO_CTRL1, val); + if (ret) + pr_err("%s: xpcs_write returned %pe\n", __func__, ERR_PTR(ret)); +} + +static void xpcs_link_up_1000basex(struct dw_xpcs *xpcs, unsigned int mode, + int speed, int duplex) +{ + int val, ret; + + if (phylink_autoneg_inband(mode)) + return; + + switch (speed) { + case SPEED_1000: + val = BMCR_SPEED1000; + break; + case SPEED_100: + case SPEED_10: + default: + pr_err("%s: speed = %d\n", __func__, speed); + return; + } + + if (duplex == DUPLEX_FULL) + val |= BMCR_FULLDPLX; + else + pr_err("%s: half duplex not supported\n", __func__); + + ret = xpcs_write(xpcs, MDIO_MMD_VEND2, MDIO_CTRL1, val); + if (ret) + pr_err("%s: xpcs_write returned %pe\n", __func__, ERR_PTR(ret)); +} + +void xpcs_link_up(struct phylink_pcs *pcs, unsigned int mode, + phy_interface_t interface, int speed, int duplex) +{ + struct dw_xpcs *xpcs = phylink_pcs_to_xpcs(pcs); + + if (interface == PHY_INTERFACE_MODE_USXGMII) + return xpcs_config_usxgmii(xpcs, speed); + if (interface == PHY_INTERFACE_MODE_SGMII) + return xpcs_link_up_sgmii(xpcs, mode, speed, duplex); + if (interface == PHY_INTERFACE_MODE_1000BASEX) + return xpcs_link_up_1000basex(xpcs, mode, speed, duplex); +} +EXPORT_SYMBOL_GPL(xpcs_link_up); + +static void xpcs_an_restart(struct phylink_pcs *pcs) +{ + struct dw_xpcs *xpcs = phylink_pcs_to_xpcs(pcs); + int ret; + + ret = xpcs_read(xpcs, MDIO_MMD_VEND2, MDIO_CTRL1); + if (ret >= 0) { + ret |= BMCR_ANRESTART; + xpcs_write(xpcs, MDIO_MMD_VEND2, MDIO_CTRL1, ret); + } +} + +static u32 xpcs_get_id(struct dw_xpcs *xpcs) +{ + int ret; + u32 id; + + /* First, search C73 PCS using PCS MMD */ + ret = xpcs_read(xpcs, MDIO_MMD_PCS, MII_PHYSID1); + if (ret < 0) + return 0xffffffff; + + id = ret << 16; + + ret = xpcs_read(xpcs, MDIO_MMD_PCS, MII_PHYSID2); + if (ret < 0) + return 0xffffffff; + + /* If Device IDs are not all zeros or all ones, + * we found C73 AN-type device + */ + if ((id | ret) && (id | ret) != 0xffffffff) + return id | ret; + + /* Next, search C37 PCS using Vendor-Specific MII MMD */ + ret = xpcs_read(xpcs, MDIO_MMD_VEND2, MII_PHYSID1); + if (ret < 0) + return 0xffffffff; + + id = ret << 16; + + ret = xpcs_read(xpcs, MDIO_MMD_VEND2, MII_PHYSID2); + if (ret < 0) + return 0xffffffff; + + /* If Device IDs are not all zeros, we found C37 AN-type device */ + if (id | ret) + return id | ret; + + return 0xffffffff; +} + +static const struct xpcs_compat synopsys_xpcs_compat[DW_XPCS_INTERFACE_MAX] = { + [DW_XPCS_USXGMII] = { + .supported = xpcs_usxgmii_features, + .interface = xpcs_usxgmii_interfaces, + .num_interfaces = ARRAY_SIZE(xpcs_usxgmii_interfaces), + .an_mode = DW_AN_C73, + }, + [DW_XPCS_10GKR] = { + .supported = xpcs_10gkr_features, + .interface = xpcs_10gkr_interfaces, + .num_interfaces = ARRAY_SIZE(xpcs_10gkr_interfaces), + .an_mode = DW_AN_C73, + }, + [DW_XPCS_XLGMII] = { + .supported = xpcs_xlgmii_features, + .interface = xpcs_xlgmii_interfaces, + .num_interfaces = ARRAY_SIZE(xpcs_xlgmii_interfaces), + .an_mode = DW_AN_C73, + }, + [DW_XPCS_SGMII] = { + .supported = xpcs_sgmii_features, + .interface = xpcs_sgmii_interfaces, + .num_interfaces = ARRAY_SIZE(xpcs_sgmii_interfaces), + .an_mode = DW_AN_C37_SGMII, + }, + [DW_XPCS_1000BASEX] = { + .supported = xpcs_1000basex_features, + .interface = xpcs_1000basex_interfaces, + .num_interfaces = ARRAY_SIZE(xpcs_1000basex_interfaces), + .an_mode = DW_AN_C37_1000BASEX, + }, + [DW_XPCS_2500BASEX] = { + .supported = xpcs_2500basex_features, + .interface = xpcs_2500basex_interfaces, + .num_interfaces = ARRAY_SIZE(xpcs_2500basex_features), + .an_mode = DW_2500BASEX, + }, +}; + +static const struct xpcs_compat nxp_sja1105_xpcs_compat[DW_XPCS_INTERFACE_MAX] = { + [DW_XPCS_SGMII] = { + .supported = xpcs_sgmii_features, + .interface = xpcs_sgmii_interfaces, + .num_interfaces = ARRAY_SIZE(xpcs_sgmii_interfaces), + .an_mode = DW_AN_C37_SGMII, + .pma_config = nxp_sja1105_sgmii_pma_config, + }, +}; + +static const struct xpcs_compat nxp_sja1110_xpcs_compat[DW_XPCS_INTERFACE_MAX] = { + [DW_XPCS_SGMII] = { + .supported = xpcs_sgmii_features, + .interface = xpcs_sgmii_interfaces, + .num_interfaces = ARRAY_SIZE(xpcs_sgmii_interfaces), + .an_mode = DW_AN_C37_SGMII, + .pma_config = nxp_sja1110_sgmii_pma_config, + }, + [DW_XPCS_2500BASEX] = { + .supported = xpcs_2500basex_features, + .interface = xpcs_2500basex_interfaces, + .num_interfaces = ARRAY_SIZE(xpcs_2500basex_interfaces), + .an_mode = DW_2500BASEX, + .pma_config = nxp_sja1110_2500basex_pma_config, + }, +}; + +static const struct xpcs_id xpcs_id_list[] = { + { + .id = SYNOPSYS_XPCS_ID, + .mask = SYNOPSYS_XPCS_MASK, + .compat = synopsys_xpcs_compat, + }, { + .id = NXP_SJA1105_XPCS_ID, + .mask = SYNOPSYS_XPCS_MASK, + .compat = nxp_sja1105_xpcs_compat, + }, { + .id = NXP_SJA1110_XPCS_ID, + .mask = SYNOPSYS_XPCS_MASK, + .compat = nxp_sja1110_xpcs_compat, + }, +}; + +static const struct phylink_pcs_ops xpcs_phylink_ops = { + .pcs_validate = xpcs_validate, + .pcs_config = xpcs_config, + .pcs_get_state = xpcs_get_state, + .pcs_an_restart = xpcs_an_restart, + .pcs_link_up = xpcs_link_up, +}; + +struct dw_xpcs *xpcs_create(struct mdio_device *mdiodev, + phy_interface_t interface) +{ + struct dw_xpcs *xpcs; + u32 xpcs_id; + int i, ret; + + xpcs = kzalloc(sizeof(*xpcs), GFP_KERNEL); + if (!xpcs) + return ERR_PTR(-ENOMEM); + + xpcs->mdiodev = mdiodev; + + xpcs_id = xpcs_get_id(xpcs); + + for (i = 0; i < ARRAY_SIZE(xpcs_id_list); i++) { + const struct xpcs_id *entry = &xpcs_id_list[i]; + const struct xpcs_compat *compat; + + if ((xpcs_id & entry->mask) != entry->id) + continue; + + xpcs->id = entry; + + compat = xpcs_find_compat(entry, interface); + if (!compat) { + ret = -ENODEV; + goto out; + } + + xpcs->pcs.ops = &xpcs_phylink_ops; + xpcs->pcs.poll = true; + + ret = xpcs_soft_reset(xpcs, compat); + if (ret) + goto out; + + return xpcs; + } + + ret = -ENODEV; + +out: + kfree(xpcs); + + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(xpcs_create); + +void xpcs_destroy(struct dw_xpcs *xpcs) +{ + kfree(xpcs); +} +EXPORT_SYMBOL_GPL(xpcs_destroy); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/pcs/pcs-xpcs.h b/drivers/net/pcs/pcs-xpcs.h new file mode 100644 index 000000000..770df5032 --- /dev/null +++ b/drivers/net/pcs/pcs-xpcs.h @@ -0,0 +1,114 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2020 Synopsys, Inc. and/or its affiliates. + * Synopsys DesignWare XPCS helpers + * + * Author: Jose Abreu <Jose.Abreu@synopsys.com> + */ + +#define SYNOPSYS_XPCS_ID 0x7996ced0 +#define SYNOPSYS_XPCS_MASK 0xffffffff + +/* Vendor regs access */ +#define DW_VENDOR BIT(15) + +/* VR_XS_PCS */ +#define DW_USXGMII_RST BIT(10) +#define DW_USXGMII_EN BIT(9) +#define DW_VR_XS_PCS_DIG_STS 0x0010 +#define DW_RXFIFO_ERR GENMASK(6, 5) + +/* 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) +#define DW_C73_AN_ADV_SF 0x1 +/* 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) + +/* Clause 37 Defines */ +/* VR MII MMD registers offsets */ +#define DW_VR_MII_MMD_CTRL 0x0000 +#define DW_VR_MII_DIG_CTRL1 0x8000 +#define DW_VR_MII_AN_CTRL 0x8001 +#define DW_VR_MII_AN_INTR_STS 0x8002 +/* Enable 2.5G Mode */ +#define DW_VR_MII_DIG_CTRL1_2G5_EN BIT(2) +/* EEE Mode Control Register */ +#define DW_VR_MII_EEE_MCTRL0 0x8006 +#define DW_VR_MII_EEE_MCTRL1 0x800b +#define DW_VR_MII_DIG_CTRL2 0x80e1 + +/* VR_MII_DIG_CTRL1 */ +#define DW_VR_MII_DIG_CTRL1_MAC_AUTO_SW BIT(9) + +/* VR_MII_DIG_CTRL2 */ +#define DW_VR_MII_DIG_CTRL2_TX_POL_INV BIT(4) +#define DW_VR_MII_DIG_CTRL2_RX_POL_INV BIT(0) + +/* VR_MII_AN_CTRL */ +#define DW_VR_MII_AN_CTRL_TX_CONFIG_SHIFT 3 +#define DW_VR_MII_TX_CONFIG_MASK BIT(3) +#define DW_VR_MII_TX_CONFIG_PHY_SIDE_SGMII 0x1 +#define DW_VR_MII_TX_CONFIG_MAC_SIDE_SGMII 0x0 +#define DW_VR_MII_AN_CTRL_PCS_MODE_SHIFT 1 +#define DW_VR_MII_PCS_MODE_MASK GENMASK(2, 1) +#define DW_VR_MII_PCS_MODE_C37_1000BASEX 0x0 +#define DW_VR_MII_PCS_MODE_C37_SGMII 0x2 + +/* VR_MII_AN_INTR_STS */ +#define DW_VR_MII_AN_STS_C37_ANSGM_FD BIT(1) +#define DW_VR_MII_AN_STS_C37_ANSGM_SP_SHIFT 2 +#define DW_VR_MII_AN_STS_C37_ANSGM_SP GENMASK(3, 2) +#define DW_VR_MII_C37_ANSGM_SP_10 0x0 +#define DW_VR_MII_C37_ANSGM_SP_100 0x1 +#define DW_VR_MII_C37_ANSGM_SP_1000 0x2 +#define DW_VR_MII_C37_ANSGM_SP_LNKSTS BIT(4) + +/* SR MII MMD Control defines */ +#define AN_CL37_EN BIT(12) /* Enable Clause 37 auto-nego */ +#define SGMII_SPEED_SS13 BIT(13) /* SGMII speed along with SS6 */ +#define SGMII_SPEED_SS6 BIT(6) /* SGMII speed along with SS13 */ + +/* VR MII EEE Control 0 defines */ +#define DW_VR_MII_EEE_LTX_EN BIT(0) /* LPI Tx Enable */ +#define DW_VR_MII_EEE_LRX_EN BIT(1) /* LPI Rx Enable */ +#define DW_VR_MII_EEE_TX_QUIET_EN BIT(2) /* Tx Quiet Enable */ +#define DW_VR_MII_EEE_RX_QUIET_EN BIT(3) /* Rx Quiet Enable */ +#define DW_VR_MII_EEE_TX_EN_CTRL BIT(4) /* Tx Control Enable */ +#define DW_VR_MII_EEE_RX_EN_CTRL BIT(7) /* Rx Control Enable */ + +#define DW_VR_MII_EEE_MULT_FACT_100NS_SHIFT 8 +#define DW_VR_MII_EEE_MULT_FACT_100NS GENMASK(11, 8) + +/* VR MII EEE Control 1 defines */ +#define DW_VR_MII_EEE_TRN_LPI BIT(0) /* Transparent Mode Enable */ + +int xpcs_read(struct dw_xpcs *xpcs, int dev, u32 reg); +int xpcs_write(struct dw_xpcs *xpcs, int dev, u32 reg, u16 val); +int nxp_sja1105_sgmii_pma_config(struct dw_xpcs *xpcs); +int nxp_sja1110_sgmii_pma_config(struct dw_xpcs *xpcs); +int nxp_sja1110_2500basex_pma_config(struct dw_xpcs *xpcs); |