[openwrt/openwrt] ipq40xx: qca8k: introduce proper PSGMII calibration
LEDE Commits
lede-commits at lists.infradead.org
Sun Oct 2 14:05:51 PDT 2022
blocktrron pushed a commit to openwrt/openwrt.git, branch master:
https://git.openwrt.org/ad9ecd33ccb4fa1334e8986960ade356fa200fda
commit ad9ecd33ccb4fa1334e8986960ade356fa200fda
Author: Serhii Serhieiev <adron at mstnt.com>
AuthorDate: Mon Apr 25 15:09:43 2022 +0200
ipq40xx: qca8k: introduce proper PSGMII calibration
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 at mstnt.com>
Signed-off-by: Robert Marko <robert.marko at sartura.hr>
---
.../files/drivers/net/dsa/qca/qca8k-ipq4019.c | 320 ++++++++++++++++++++-
.../files/drivers/net/dsa/qca/qca8k-ipq4019.h | 23 ++
2 files changed, 331 insertions(+), 12 deletions(-)
diff --git a/target/linux/ipq40xx/files/drivers/net/dsa/qca/qca8k-ipq4019.c b/target/linux/ipq40xx/files/drivers/net/dsa/qca/qca8k-ipq4019.c
index 4d79426205..a8c3a20406 100644
--- a/target/linux/ipq40xx/files/drivers/net/dsa/qca/qca8k-ipq4019.c
+++ b/target/linux/ipq40xx/files/drivers/net/dsa/qca/qca8k-ipq4019.c
@@ -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", ®);
+ 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);
diff --git a/target/linux/ipq40xx/files/drivers/net/dsa/qca/qca8k-ipq4019.h b/target/linux/ipq40xx/files/drivers/net/dsa/qca/qca8k-ipq4019.h
index a36eb0dadc..1133501335 100644
--- a/target/linux/ipq40xx/files/drivers/net/dsa/qca/qca8k-ipq4019.h
+++ b/target/linux/ipq40xx/files/drivers/net/dsa/qca/qca8k-ipq4019.h
@@ -150,6 +150,7 @@
#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)
@@ -206,6 +207,28 @@
#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,
More information about the lede-commits
mailing list