ipq40xx: qca8k: introduce proper PSGMII calibration
authorSerhii Serhieiev <adron@mstnt.com>
Mon, 25 Apr 2022 13:09:43 +0000 (15:09 +0200)
committerDavid Bauer <mail@david-bauer.net>
Sun, 2 Oct 2022 21:04:39 +0000 (23:04 +0200)
Serhii and others have experienced PSGMII link degradation up to point
that it actually does not pass packets at all or packets arrive as zeros.
This usually happened after a couple of hot reboots.

Serhii has managed to track it down to PSGMII calibration not being done
properly and has fixed it, so all of the code is Serhii-s work.

Signed-off-by: Serhii Serhieiev <adron@mstnt.com>
Signed-off-by: Robert Marko <robert.marko@sartura.hr>
target/linux/ipq40xx/files/drivers/net/dsa/qca/qca8k-ipq4019.c
target/linux/ipq40xx/files/drivers/net/dsa/qca/qca8k-ipq4019.h

index 4d794262055da6d784d8c3226f9a8e8a5f23d675..a8c3a20406ac930432e21d6be631b670106cdbb9 100644 (file)
@@ -602,24 +602,23 @@ qca8k_setup(struct dsa_switch *ds)
        return 0;
 }
 
-static int psgmii_vco_calibrate(struct dsa_switch *ds)
+static int psgmii_vco_calibrate(struct qca8k_priv *priv)
 {
-       struct qca8k_priv *priv = ds->priv;
        int val, ret;
 
        if (!priv->psgmii_ethphy) {
-               dev_err(ds->dev, "PSGMII eth PHY missing, calibration failed!\n");
+               dev_err(priv->dev, "PSGMII eth PHY missing, calibration failed!\n");
                return -ENODEV;
        }
 
        /* Fix PSGMII RX 20bit */
        ret = phy_write(priv->psgmii_ethphy, MII_BMCR, 0x5b);
-       /* Reset PSGMII PHY */
+       /* Reset PHY PSGMII */
        ret = phy_write(priv->psgmii_ethphy, MII_BMCR, 0x1b);
-       /* Release reset */
+       /* Release PHY PSGMII reset */
        ret = phy_write(priv->psgmii_ethphy, MII_BMCR, 0x5b);
 
-       /* Poll for VCO PLL calibration finish */
+       /* Poll for VCO PLL calibration finish - Malibu(QCA8075) */
        ret = phy_read_mmd_poll_timeout(priv->psgmii_ethphy,
                                        MDIO_MMD_PMAPMD,
                                        0x28, val,
@@ -627,9 +626,10 @@ static int psgmii_vco_calibrate(struct dsa_switch *ds)
                                        10000, 1000000,
                                        false);
        if (ret) {
-               dev_err(ds->dev, "QCA807x PSGMII VCO calibration PLL not ready\n");
+               dev_err(priv->dev, "QCA807x PSGMII VCO calibration PLL not ready\n");
                return ret;
        }
+       mdelay(50);
 
        /* Freeze PSGMII RX CDR */
        ret = phy_write(priv->psgmii_ethphy, MII_RESV2, 0x2230);
@@ -639,32 +639,328 @@ static int psgmii_vco_calibrate(struct dsa_switch *ds)
                        PSGMIIPHY_VCO_CALIBRATION_CONTROL_REGISTER_1,
                        PSGMIIPHY_REG_PLL_VCO_CALIB_RESTART);
 
-       /* Poll for PSGMIIPHY PLL calibration finish */
+       /* 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,
                                       10000, 1000000);
        if (ret) {
-               dev_err(ds->dev, "PSGMIIPHY VCO calibration PLL not ready\n");
+               dev_err(priv->dev, "IPQ PSGMIIPHY VCO calibration PLL not ready\n");
                return ret;
        }
+       mdelay(50);
 
        /* Release PSGMII RX CDR */
        ret = phy_write(priv->psgmii_ethphy, MII_RESV2, 0x3230);
-
        /* Release PSGMII RX 20bit */
        ret = phy_write(priv->psgmii_ethphy, MII_BMCR, 0x5f);
+       mdelay(200);
 
        return ret;
 }
 
-static int ipq4019_psgmii_configure(struct dsa_switch *ds)
+static void
+qca8k_switch_port_loopback_on_off(struct qca8k_priv *priv, int port, int on)
+{
+       u32 val = QCA8K_PORT_LOOKUP_LOOPBACK;
+
+       if (on == 0)
+               val = 0;
+
+       qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port),
+                 QCA8K_PORT_LOOKUP_LOOPBACK, val);
+}
+
+static int
+qca8k_wait_for_phy_link_state(struct phy_device *phy, int need_status)
+{
+       int a;
+       u16 status;
+
+       for (a = 0; a < 100; a++) {
+               status = phy_read(phy, MII_QCA8075_SSTATUS);
+               status &= QCA8075_PHY_SPEC_STATUS_LINK;
+               status = !!status;
+               if (status == need_status)
+                       return 0;
+               mdelay(8);
+       }
+
+       return -1;
+}
+
+static void
+qca8k_phy_loopback_on_off(struct qca8k_priv *priv, struct phy_device *phy,
+                         int sw_port, int on)
+{
+       if (on) {
+               phy_write(phy, MII_BMCR, BMCR_ANENABLE | BMCR_RESET);
+               phy_modify(phy, MII_BMCR, BMCR_PDOWN, BMCR_PDOWN);
+               qca8k_wait_for_phy_link_state(phy, 0);
+               qca8k_write(priv, QCA8K_REG_PORT_STATUS(sw_port), 0);
+               phy_write(phy, MII_BMCR,
+                       BMCR_SPEED1000 |
+                       BMCR_FULLDPLX |
+                       BMCR_LOOPBACK);
+               qca8k_wait_for_phy_link_state(phy, 1);
+               qca8k_write(priv, QCA8K_REG_PORT_STATUS(sw_port),
+                       QCA8K_PORT_STATUS_SPEED_1000 |
+                       QCA8K_PORT_STATUS_TXMAC |
+                       QCA8K_PORT_STATUS_RXMAC |
+                       QCA8K_PORT_STATUS_DUPLEX);
+               qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(sw_port),
+                       QCA8K_PORT_LOOKUP_STATE_FORWARD,
+                       QCA8K_PORT_LOOKUP_STATE_FORWARD);
+       } else { /* off */
+               qca8k_write(priv, QCA8K_REG_PORT_STATUS(sw_port), 0);
+               qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(sw_port),
+                       QCA8K_PORT_LOOKUP_STATE_DISABLED,
+                       QCA8K_PORT_LOOKUP_STATE_DISABLED);
+               phy_write(phy, MII_BMCR, BMCR_SPEED1000 | BMCR_ANENABLE | BMCR_RESET);
+               /* turn off the power of the phys - so that unused
+                        ports do not raise links */
+               phy_modify(phy, MII_BMCR, BMCR_PDOWN, BMCR_PDOWN);
+       }
+}
+
+static void
+qca8k_phy_pkt_gen_prep(struct qca8k_priv *priv, struct phy_device *phy,
+                      int pkts_num, int on)
+{
+       if (on) {
+               /* enable CRC checker and packets counters */
+               phy_write_mmd(phy, MDIO_MMD_AN, QCA8075_MMD7_CRC_AND_PKTS_COUNT, 0);
+               phy_write_mmd(phy, MDIO_MMD_AN, QCA8075_MMD7_CRC_AND_PKTS_COUNT,
+                       QCA8075_MMD7_CNT_FRAME_CHK_EN | QCA8075_MMD7_CNT_SELFCLR);
+               qca8k_wait_for_phy_link_state(phy, 1);
+               /* packet number */
+               phy_write_mmd(phy, MDIO_MMD_AN, QCA8075_MMD7_PKT_GEN_PKT_NUMB, pkts_num);
+               /* pkt size - 1504 bytes + 20 bytes */
+               phy_write_mmd(phy, MDIO_MMD_AN, QCA8075_MMD7_PKT_GEN_PKT_SIZE, 1504);
+       } else { /* off */
+               /* packet number */
+               phy_write_mmd(phy, MDIO_MMD_AN, QCA8075_MMD7_PKT_GEN_PKT_NUMB, 0);
+               /* disable CRC checker and packet counter */
+               phy_write_mmd(phy, MDIO_MMD_AN, QCA8075_MMD7_CRC_AND_PKTS_COUNT, 0);
+               /* disable traffic gen */
+               phy_write_mmd(phy, MDIO_MMD_AN, QCA8075_MMD7_PKT_GEN_CTRL, 0);
+       }
+}
+
+static void
+qca8k_wait_for_phy_pkt_gen_fin(struct qca8k_priv *priv, struct phy_device *phy)
+{
+       int val;
+       /* wait for all traffic end: 4096(pkt num)*1524(size)*8ns(125MHz)=49938us */
+       phy_read_mmd_poll_timeout(phy, MDIO_MMD_AN, QCA8075_MMD7_PKT_GEN_CTRL,
+                                 val, !(val & QCA8075_MMD7_PKT_GEN_INPROGR),
+                                 50000, 1000000, true);
+}
+
+static void
+qca8k_start_phy_pkt_gen(struct phy_device *phy)
+{
+       /* start traffic gen */
+       phy_write_mmd(phy, MDIO_MMD_AN, QCA8075_MMD7_PKT_GEN_CTRL,
+                     QCA8075_MMD7_PKT_GEN_START | QCA8075_MMD7_PKT_GEN_INPROGR);
+}
+
+static int
+qca8k_start_all_phys_pkt_gens(struct qca8k_priv *priv)
+{
+       struct phy_device *phy;
+       phy = phy_device_create(priv->bus, QCA8075_MDIO_BRDCST_PHY_ADDR,
+               0, 0, NULL);
+       if (!phy) {
+               dev_err(priv->dev, "unable to create mdio broadcast PHY(0x%x)\n",
+                       QCA8075_MDIO_BRDCST_PHY_ADDR);
+               return -ENODEV;
+       }
+
+       qca8k_start_phy_pkt_gen(phy);
+
+       phy_device_free(phy);
+       return 0;
+}
+
+static int
+qca8k_get_phy_pkt_gen_test_result(struct phy_device *phy, int pkts_num)
+{
+       u32 tx_ok, tx_error;
+       u32 rx_ok, rx_error;
+       u32 tx_ok_high16;
+       u32 rx_ok_high16;
+       u32 tx_all_ok, rx_all_ok;
+
+       /* check counters */
+       tx_ok = phy_read_mmd(phy, MDIO_MMD_AN, QCA8075_MMD7_EG_FRAME_RECV_CNT_LO);
+       tx_ok_high16 = phy_read_mmd(phy, MDIO_MMD_AN, QCA8075_MMD7_EG_FRAME_RECV_CNT_HI);
+       tx_error = phy_read_mmd(phy, MDIO_MMD_AN, QCA8075_MMD7_EG_FRAME_ERR_CNT);
+       rx_ok = phy_read_mmd(phy, MDIO_MMD_AN, QCA8075_MMD7_IG_FRAME_RECV_CNT_LO);
+       rx_ok_high16 = phy_read_mmd(phy, MDIO_MMD_AN, QCA8075_MMD7_IG_FRAME_RECV_CNT_HI);
+       rx_error = phy_read_mmd(phy, MDIO_MMD_AN, QCA8075_MMD7_IG_FRAME_ERR_CNT);
+       tx_all_ok = tx_ok + (tx_ok_high16 << 16);
+       rx_all_ok = rx_ok + (rx_ok_high16 << 16);
+
+       if (tx_all_ok < pkts_num)
+               return -1;
+       if(rx_all_ok < pkts_num)
+               return -2;
+       if(tx_error)
+               return -3;
+       if(rx_error)
+               return -4;
+       return 0; /* test is ok */
+}
+
+static
+void qca8k_phy_broadcast_write_on_off(struct qca8k_priv *priv,
+                                     struct phy_device *phy, int on)
+{
+       u32 val;
+
+       val = phy_read_mmd(phy, MDIO_MMD_AN, QCA8075_MMD7_MDIO_BRDCST_WRITE);
+
+       if (on == 0)
+               val &= ~QCA8075_MMD7_MDIO_BRDCST_WRITE_EN;
+       else
+               val |= QCA8075_MMD7_MDIO_BRDCST_WRITE_EN;
+
+       phy_write_mmd(phy, MDIO_MMD_AN, QCA8075_MMD7_MDIO_BRDCST_WRITE, val);
+}
+
+static int
+qca8k_test_dsa_port_for_errors(struct qca8k_priv *priv, struct phy_device *phy,
+                              int port, int test_phase)
+{
+       int res = 0;
+       const int test_pkts_num = QCA8075_PKT_GEN_PKTS_COUNT;
+
+       if (test_phase == 1) { /* start test preps */
+               qca8k_phy_loopback_on_off(priv, phy, port, 1);
+               qca8k_switch_port_loopback_on_off(priv, port, 1);
+               qca8k_phy_broadcast_write_on_off(priv, phy, 1);
+               qca8k_phy_pkt_gen_prep(priv, phy, test_pkts_num, 1);
+       } else if (test_phase == 2) {
+               /* wait for test results, collect it and cleanup */
+               qca8k_wait_for_phy_pkt_gen_fin(priv, phy);
+               res = qca8k_get_phy_pkt_gen_test_result(phy, test_pkts_num);
+               qca8k_phy_pkt_gen_prep(priv, phy, test_pkts_num, 0);
+               qca8k_phy_broadcast_write_on_off(priv, phy, 0);
+               qca8k_switch_port_loopback_on_off(priv, port, 0);
+               qca8k_phy_loopback_on_off(priv, phy, port, 0);
+       }
+
+       return res;
+}
+
+static int
+qca8k_do_dsa_sw_ports_self_test(struct qca8k_priv *priv, int parallel_test)
+{
+       struct device_node *dn = priv->dev->of_node;
+       struct device_node *ports, *port;
+       struct device_node *phy_dn;
+       struct phy_device *phy;
+       int reg, err = 0, test_phase;
+       u32 tests_result = 0;
+
+       ports = of_get_child_by_name(dn, "ports");
+       if (!ports) {
+               dev_err(priv->dev, "no ports child node found\n");
+                       return -EINVAL;
+       }
+
+       for (test_phase = 1; test_phase <= 2; test_phase++) {
+               if (parallel_test && test_phase == 2) {
+                       err = qca8k_start_all_phys_pkt_gens(priv);
+                       if (err)
+                               goto error;
+               }
+               for_each_available_child_of_node(ports, port) {
+                       err = of_property_read_u32(port, "reg", &reg);
+                       if (err)
+                               goto error;
+                       if (reg >= QCA8K_NUM_PORTS) {
+                               err = -EINVAL;
+                               goto error;
+                       }
+                       phy_dn = of_parse_phandle(port, "phy-handle", 0);
+                       if (phy_dn) {
+                               phy = of_phy_find_device(phy_dn);
+                               of_node_put(phy_dn);
+                               if (phy) {
+                                       int result;
+                                       result = qca8k_test_dsa_port_for_errors(priv,
+                                               phy, reg, test_phase);
+                                       if (!parallel_test && test_phase == 1)
+                                               qca8k_start_phy_pkt_gen(phy);
+                                       put_device(&phy->mdio.dev);
+                                       if (test_phase == 2) {
+                                               tests_result <<= 1;
+                                               if (result)
+                                                       tests_result |= 1;
+                                       }
+                               }
+                       }
+               }
+       }
+
+end:
+       of_node_put(ports);
+       qca8k_fdb_flush(priv);
+       return tests_result;
+error:
+       tests_result |= 0xf000;
+       goto end;
+}
+
+static int
+psgmii_vco_calibrate_and_test(struct dsa_switch *ds)
+{
+       int ret, a, test_result;
+       struct qca8k_priv *priv = ds->priv;
+
+       for (a = 0; a <= QCA8K_PSGMII_CALB_NUM; a++) {
+               ret = psgmii_vco_calibrate(priv);
+               if (ret)
+                       return ret;
+               /* first we run serial test */
+               test_result = qca8k_do_dsa_sw_ports_self_test(priv, 0);
+               /* and if it is ok then we run the test in parallel */
+               if (!test_result)
+                       test_result = qca8k_do_dsa_sw_ports_self_test(priv, 1);
+               if (!test_result) {
+                       if (a > 0) {
+                               dev_warn(priv->dev, "PSGMII work was stabilized after %d "
+                                       "calibration retries !\n", a);
+                       }
+                       return 0;
+               } else {
+                       schedule();
+                       if (a > 0 && a % 10 == 0) {
+                               dev_err(priv->dev, "PSGMII work is unstable !!! "
+                                       "Let's try to wait a bit ... %d\n", a);
+                               set_current_state(TASK_INTERRUPTIBLE);
+                               schedule_timeout(msecs_to_jiffies(a * 100));
+                       }
+               }
+       }
+
+       panic("PSGMII work is unstable !!! "
+               "Repeated recalibration attempts did not help(0x%x) !\n",
+               test_result);
+
+       return -EFAULT;
+}
+
+static int
+ipq4019_psgmii_configure(struct dsa_switch *ds)
 {
        struct qca8k_priv *priv = ds->priv;
        int ret;
 
        if (!priv->psgmii_calibrated) {
-               ret = psgmii_vco_calibrate(ds);
+               ret = psgmii_vco_calibrate_and_test(ds);
 
                ret = regmap_clear_bits(priv->psgmii, PSGMIIPHY_MODE_CONTROL,
                                        PSGMIIPHY_MODE_ATHR_CSCO_MODE_25M);
index a36eb0dadcb5e225a3afde4366d8cc119694a788..1133501335badf7f9a47d4082270d8654c85805b 100644 (file)
 #define   QCA8K_PORT_LOOKUP_STATE_FORWARD              (4 << 16)
 #define   QCA8K_PORT_LOOKUP_STATE                      GENMASK(18, 16)
 #define   QCA8K_PORT_LOOKUP_LEARN                      BIT(20)
+#define   QCA8K_PORT_LOOKUP_LOOPBACK   BIT(21)
 
 #define QCA8K_REG_GLOBAL_FC_THRESH                     0x800
 #define   QCA8K_GLOBAL_FC_GOL_XON_THRES(x)             ((x) << 16)
 #define PSGMIIPHY_VCO_CALIBRATION_CONTROL_REGISTER_2   0xa0
 #define   PSGMIIPHY_REG_PLL_VCO_CALIB_READY            BIT(0)
 
+#define   QCA8K_PSGMII_CALB_NUM                                100
+#define   MII_QCA8075_SSTATUS                          0x11
+#define   QCA8075_PHY_SPEC_STATUS_LINK                 BIT(10)
+#define   QCA8075_MMD7_CRC_AND_PKTS_COUNT              0x8029
+#define   QCA8075_MMD7_PKT_GEN_PKT_NUMB                        0x8021
+#define   QCA8075_MMD7_PKT_GEN_PKT_SIZE                        0x8062
+#define   QCA8075_MMD7_PKT_GEN_CTRL                    0x8020
+#define   QCA8075_MMD7_CNT_SELFCLR                     BIT(1)
+#define   QCA8075_MMD7_CNT_FRAME_CHK_EN                        BIT(0)
+#define   QCA8075_MMD7_PKT_GEN_START                   BIT(13)
+#define   QCA8075_MMD7_PKT_GEN_INPROGR                 BIT(15)
+#define   QCA8075_MMD7_IG_FRAME_RECV_CNT_HI            0x802a
+#define   QCA8075_MMD7_IG_FRAME_RECV_CNT_LO            0x802b
+#define   QCA8075_MMD7_IG_FRAME_ERR_CNT                        0x802c
+#define   QCA8075_MMD7_EG_FRAME_RECV_CNT_HI            0x802d
+#define   QCA8075_MMD7_EG_FRAME_RECV_CNT_LO            0x802e
+#define   QCA8075_MMD7_EG_FRAME_ERR_CNT                        0x802f
+#define   QCA8075_MMD7_MDIO_BRDCST_WRITE               0x8028
+#define   QCA8075_MMD7_MDIO_BRDCST_WRITE_EN            BIT(15)
+#define   QCA8075_MDIO_BRDCST_PHY_ADDR                 0x1f
+#define   QCA8075_PKT_GEN_PKTS_COUNT                   4096
+
 enum {
        QCA8K_PORT_SPEED_10M = 0,
        QCA8K_PORT_SPEED_100M = 1,