lkml.org 
[lkml]   [2023]   [Nov]   [14]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patch in this message
    /
    From
    Subject[PATCH net-next v2 7/8] net: qualcomm: ipqess: add a PSGMII calibration procedure to the IPQESS driver
    Date
    The IPQ4019 Ethernet Switch Subsystem uses a PSGMII link to communicate
    with a QCA8075 5-port PHY. This 1G link requires calibration before it can
    be used reliably.

    This commit introduces a calibration procedure followed by thourough
    testing of the link between each switch port and its corresponding PHY
    port.

    Signed-off-by: Romain Gantois <romain.gantois@bootlin.com>
    ---
    drivers/net/ethernet/qualcomm/ipqess/Makefile | 2 +-
    .../ethernet/qualcomm/ipqess/ipqess_calib.c | 156 ++++++++++++++++++
    .../ethernet/qualcomm/ipqess/ipqess_port.c | 30 ++++
    .../ethernet/qualcomm/ipqess/ipqess_port.h | 4 +
    include/linux/dsa/qca8k.h | 1 +
    5 files changed, 192 insertions(+), 1 deletion(-)
    create mode 100644 drivers/net/ethernet/qualcomm/ipqess/ipqess_calib.c

    diff --git a/drivers/net/ethernet/qualcomm/ipqess/Makefile b/drivers/net/ethernet/qualcomm/ipqess/Makefile
    index b12142bbc7e5..5e755af579ae 100644
    --- a/drivers/net/ethernet/qualcomm/ipqess/Makefile
    +++ b/drivers/net/ethernet/qualcomm/ipqess/Makefile
    @@ -5,4 +5,4 @@

    obj-$(CONFIG_QCOM_IPQ4019_ESS) += ipqess.o

    -ipqess-objs := ipqess_port.o ipqess_switch.o ipqess_edma.o ipqess_ethtool.o ipqess_notifiers.o
    +ipqess-objs := ipqess_port.o ipqess_switch.o ipqess_edma.o ipqess_ethtool.o ipqess_notifiers.o ipqess_calib.o
    diff --git a/drivers/net/ethernet/qualcomm/ipqess/ipqess_calib.c b/drivers/net/ethernet/qualcomm/ipqess/ipqess_calib.c
    new file mode 100644
    index 000000000000..da9d492ca7eb
    --- /dev/null
    +++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess_calib.c
    @@ -0,0 +1,156 @@
    +// SPDX-License-Identifier: GPL-2.0
    +/*
    + * Calibration procedure for the IPQ4019 PSGMII link
    + *
    + * Copyright (C) 2009 Felix Fietkau <nbd@nbd.name>
    + * Copyright (C) 2011-2012, 2020-2021 Gabor Juhos <juhosg@openwrt.org>
    + * Copyright (c) 2015, 2019, The Linux Foundation. All rights reserved.
    + * Copyright (c) 2016 John Crispin <john@phrozen.org>
    + * Copyright (c) 2022 Robert Marko <robert.marko@sartura.hr>
    + * Copyright (c) 2023 Romain Gantois <romain.gantois@bootlin.com>
    + */
    +
    +#include <linux/dsa/qca8k.h>
    +#include <linux/phylink.h>
    +#include <linux/of_net.h>
    +#include <linux/of_mdio.h>
    +#include <linux/regmap.h>
    +
    +#include "ipqess_port.h"
    +#include "ipqess_switch.h"
    +
    +/* Nonstandard MII registers for the psgmii
    + * device on the IPQ4019 MDIO bus.
    + */
    +
    +#define PSGMII_RSTCTRL 0x0 /* Reset control register */
    +#define PSGMII_RSTCTRL_RST BIT(6)
    +#define PSGMII_RSTCTRL_RX20 BIT(2) /* Fix/release RX 20 bit */
    +
    +#define PSGMII_CDRCTRL 0x1a /* Clock and data recovery control register */
    +#define PSGMII_CDRCTRL_RELEASE BIT(12)
    +
    +/* Retry policy */
    +
    +#define PSGMII_CALIB_RETRIES 50
    +#define PSGMII_CALIB_RETRIES_BURST 5
    +#define PSGMII_CALIB_RETRY_DELAY 100
    +
    +/* PSGMII PHY specific definitions */
    +#define PSGMII_VCO_CALIB_INTERVAL 1000000
    +#define PSGMII_VCO_CALIB_TIMEOUT 10000
    +
    +static void ipqess_port_unprep_test(struct ipqess_port *port)
    +{
    + struct qca8k_priv *priv = port->sw->priv;
    + /* disable loopback on switch port */
    + qca8k_clear_bits(priv, QCA8K_PORT_LOOKUP_CTRL(port->index),
    + QCA8K_PORT_LOOKUP_LOOPBACK_EN);
    +
    + qca8k_write(priv, QCA8K_REG_PORT_STATUS(port->index), 0);
    + qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port->index),
    + QCA8K_PORT_LOOKUP_STATE_DISABLED,
    + QCA8K_PORT_LOOKUP_STATE_DISABLED);
    +}
    +
    +static void ipqess_port_prep_test(struct ipqess_port *port)
    +{
    + struct qca8k_priv *priv = port->sw->priv;
    +
    + qca8k_write(priv, QCA8K_REG_PORT_STATUS(port->index),
    + QCA8K_PORT_STATUS_SPEED_1000
    + | QCA8K_PORT_STATUS_TXMAC
    + | QCA8K_PORT_STATUS_RXMAC
    + | QCA8K_PORT_STATUS_DUPLEX);
    +
    + qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port->index),
    + QCA8K_PORT_LOOKUP_STATE_FORWARD,
    + QCA8K_PORT_LOOKUP_STATE_FORWARD);
    +
    + /* put switch port in loopback */
    + qca8k_set_bits(priv, QCA8K_PORT_LOOKUP_CTRL(port->index),
    + QCA8K_PORT_LOOKUP_LOOPBACK_EN);
    +}
    +
    +static int psgmii_vco_calibrate(struct ipqess_port *port)
    +{
    + struct ipqess_switch *sw = port->sw;
    + struct qca8k_priv *priv = sw->priv;
    + struct ipqess_port *other_port;
    + int val, ret, i;
    +
    + ret = phy_start_calibration(port->netdev->phydev);
    + if (ret) {
    + dev_err(priv->dev,
    + "PHY VCO calibration PLL not ready\n");
    + return ret;
    + }
    +
    + /* Start PSGMIIPHY VCO PLL calibration */
    + ret = regmap_set_bits(priv->psgmii,
    + PSGMIIPHY_VCO_CALIBRATION_CONTROL_REGISTER_1,
    + PSGMIIPHY_REG_PLL_VCO_CALIB_RESTART);
    +
    + /* Poll for PSGMIIPHY PLL calibration finish - Dakota(IPQ40xx) */
    + ret = regmap_read_poll_timeout(priv->psgmii,
    + PSGMIIPHY_VCO_CALIBRATION_CONTROL_REGISTER_2,
    + val,
    + val & PSGMIIPHY_REG_PLL_VCO_CALIB_READY,
    + PSGMII_VCO_CALIB_INTERVAL,
    + PSGMII_VCO_CALIB_TIMEOUT);
    + if (ret) {
    + dev_err(priv->dev,
    + "IPQ PSGMIIPHY VCO calibration PLL not ready\n");
    + return ret;
    + }
    +
    + /* Prepare all switch ports, in case we're dealing with a multiport PHY */
    + for (i = 0; i < IPQESS_SWITCH_MAX_PORTS; i++) {
    + other_port = sw->port_list[i];
    + if (!other_port)
    + continue;
    + ipqess_port_prep_test(other_port);
    + }
    +
    + ret = phy_stop_calibration(port->netdev->phydev);
    +
    + for (i = 0; i < IPQESS_SWITCH_MAX_PORTS; i++) {
    + other_port = sw->port_list[i];
    + if (!other_port)
    + continue;
    + ipqess_port_unprep_test(other_port);
    + }
    +
    + qca8k_fdb_flush(priv);
    +
    + return ret;
    +}
    +
    +int psgmii_calibrate_and_test(struct ipqess_port *port)
    +{
    + int ret, attempt;
    +
    + for (attempt = 0; attempt <= PSGMII_CALIB_RETRIES; attempt++) {
    + ret = psgmii_vco_calibrate(port);
    + if (!ret) {
    + netdev_dbg(port->netdev,
    + "PSGMII link stabilized after %d attempts\n",
    + attempt + 1);
    + return 0;
    + }
    +
    + /* On tested hardware, the link often stabilizes in 4 or 5 retries.
    + * If it still isn't stable, we wait a bit, then try another set
    + * of calibration attempts.
    + */
    + netdev_dbg(port->netdev, "PSGMII link is unstable! Retrying... %d/QCA8K_PSGMII_CALIB_RETRIES\n",
    + attempt + 1);
    + if (attempt % PSGMII_CALIB_RETRIES_BURST == 0)
    + schedule_timeout_interruptible(msecs_to_jiffies(PSGMII_CALIB_RETRY_DELAY));
    + else
    + schedule();
    + }
    +
    + netdev_err(port->netdev, "PSGMII work is unstable! Repeated recalibration attempts did not help!\n");
    + return -EFAULT;
    +}
    diff --git a/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.c b/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.c
    index 29420820c3d8..ea759707335a 100644
    --- a/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.c
    +++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.c
    @@ -1218,6 +1218,26 @@ int ipqess_port_check_8021q_upper(struct net_device *netdev,

    /* phylink ops */

    +static int
    +ipqess_psgmii_configure(struct ipqess_port *port)
    +{
    + int ret = 0;
    +
    + /* For this multi-port PHY, we only need one calibration run,
    + * don't rerun it for other ports
    + */
    + if (!atomic_cmpxchg(&port->sw->priv->psgmii_calibrated, 0, 1)) {
    + psgmii_calibrate_and_test(port);
    +
    + ret = regmap_clear_bits(port->sw->priv->psgmii, PSGMIIPHY_MODE_CONTROL,
    + PSGMIIPHY_MODE_ATHR_CSCO_MODE_25M);
    + ret = regmap_write(port->sw->priv->psgmii, PSGMIIPHY_TX_CONTROL,
    + PSGMIIPHY_TX_CONTROL_MAGIC_VALUE);
    + }
    +
    + return ret;
    +}
    +
    static void
    ipqess_phylink_mac_config(struct phylink_config *config,
    unsigned int mode,
    @@ -1233,12 +1253,22 @@ ipqess_phylink_mac_config(struct phylink_config *config,
    case 1:
    case 2:
    case 3:
    + if (state->interface == PHY_INTERFACE_MODE_PSGMII)
    + if (ipqess_psgmii_configure(port))
    + dev_err(priv->dev,
    + "PSGMII configuration failed!\n");
    + return;
    case 4:
    case 5:
    if (phy_interface_mode_is_rgmii(state->interface))
    regmap_set_bits(priv->regmap,
    QCA8K_IPQ4019_REG_RGMII_CTRL,
    QCA8K_IPQ4019_RGMII_CTRL_CLK);
    +
    + if (state->interface == PHY_INTERFACE_MODE_PSGMII)
    + if (ipqess_psgmii_configure(port))
    + dev_err(priv->dev,
    + "PSGMII configuration failed!\n");
    return;
    default:
    dev_err(priv->dev, "%s: unsupported port: %i\n", __func__,
    diff --git a/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.h b/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.h
    index 00f0dff9c39d..bc24a0507702 100644
    --- a/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.h
    +++ b/drivers/net/ethernet/qualcomm/ipqess/ipqess_port.h
    @@ -95,4 +95,8 @@ int ipqess_port_obj_del(struct net_device *netdev, const void *ctx,

    bool ipqess_port_offloads_bridge_port(struct ipqess_port *port,
    const struct net_device *netdev);
    +
    +/* Defined in ipqess_calib.c */
    +int psgmii_calibrate_and_test(struct ipqess_port *port);
    +
    #endif
    diff --git a/include/linux/dsa/qca8k.h b/include/linux/dsa/qca8k.h
    index 9ad016f7201e..72c488e7c498 100644
    --- a/include/linux/dsa/qca8k.h
    +++ b/include/linux/dsa/qca8k.h
    @@ -496,6 +496,7 @@ struct qca8k_priv {

    /* IPQ4019 specific */
    struct regmap *psgmii;
    + atomic_t psgmii_calibrated;
    };

    struct qca8k_mib_desc {
    --
    2.42.0
    \
     
     \ /
      Last update: 2023-11-20 13:58    [W:4.358 / U:0.008 seconds]
    ©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site