1
0
mirror of https://git.openwrt.org/openwrt/openwrt.git synced 2024-06-13 10:49:13 +02:00

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@mstnt.com>
Signed-off-by: Robert Marko <robert.marko@sartura.hr>
This commit is contained in:
Serhii Serhieiev 2022-04-25 15:09:43 +02:00 committed by David Bauer
parent f5c62c6e91
commit ad9ecd33cc
2 changed files with 331 additions and 12 deletions

View 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);

View File

@ -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,