drivers/net/phy/microchip_t1.c | 144 +++++++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+)
From: Tarun Alle <Tarun.Alle@microchip.com>
Add support for measuring Signal Quality Index for LAN887x T1 PHY.
Signal Quality Index (SQI) is measure of Link Channel Quality from
0 to 7, with 7 as the best. By default, a link loss event shall
indicate an SQI of 0.
Signed-off-by: Tarun Alle <Tarun.Alle@microchip.com>
---
drivers/net/phy/microchip_t1.c | 144 +++++++++++++++++++++++++++++++++
1 file changed, 144 insertions(+)
diff --git a/drivers/net/phy/microchip_t1.c b/drivers/net/phy/microchip_t1.c
index 5732ad65e7f9..01e0e71fca48 100644
--- a/drivers/net/phy/microchip_t1.c
+++ b/drivers/net/phy/microchip_t1.c
@@ -2,6 +2,7 @@
// Copyright (C) 2018 Microchip Technology
#include <linux/kernel.h>
+#include <linux/sort.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/mii.h>
@@ -188,6 +189,20 @@
#define LAN887X_EFUSE_READ_DAT9_SGMII_DIS BIT(9)
#define LAN887X_EFUSE_READ_DAT9_MAC_MODE GENMASK(1, 0)
+#define LAN887X_COEFF_PWR_DN_CONFIG_100 0x0404
+#define LAN887X_COEFF_PWR_DN_CONFIG_100_V 0x16d6
+#define LAN887X_SQI_CONFIG_100 0x042e
+#define LAN887X_SQI_CONFIG_100_V 0x9572
+#define LAN887X_SQI_MSE_100 0x483
+
+#define LAN887X_POKE_PEEK_100 0x040d
+#define LAN887X_POKE_PEEK_100_EN BIT(0)
+
+#define LAN887X_COEFF_MOD_CONFIG 0x080d
+#define LAN887X_COEFF_MOD_CONFIG_DCQ_COEFF_EN BIT(8)
+
+#define LAN887X_DCQ_SQI_STATUS 0x08b2
+
#define DRIVER_AUTHOR "Nisar Sayed <nisar.sayed@microchip.com>"
#define DRIVER_DESC "Microchip LAN87XX/LAN937x/LAN887x T1 PHY driver"
@@ -1420,6 +1435,133 @@ static void lan887x_get_strings(struct phy_device *phydev, u8 *data)
ethtool_puts(&data, lan887x_hw_stats[i].string);
}
+/* Compare block to sort in ascending order */
+static int data_compare(const void *a, const void *b)
+{
+ return *(u16 *)a - *(u16 *)b;
+}
+
+static int lan887x_get_sqi_100M(struct phy_device *phydev)
+{
+ u16 rawtable[200];
+ u32 sqiavg = 0;
+ u8 sqinum;
+ int rc;
+
+ /* Configuration of SQI 100M */
+ rc = phy_write_mmd(phydev, MDIO_MMD_VEND1,
+ LAN887X_COEFF_PWR_DN_CONFIG_100,
+ LAN887X_COEFF_PWR_DN_CONFIG_100_V);
+ if (rc < 0)
+ return rc;
+
+ rc = phy_write_mmd(phydev, MDIO_MMD_VEND1, LAN887X_SQI_CONFIG_100,
+ LAN887X_SQI_CONFIG_100_V);
+ if (rc < 0)
+ return rc;
+
+ rc = phy_read_mmd(phydev, MDIO_MMD_VEND1, LAN887X_SQI_CONFIG_100);
+ if (rc != LAN887X_SQI_CONFIG_100_V)
+ return -EINVAL;
+
+ rc = phy_modify_mmd(phydev, MDIO_MMD_VEND1, LAN887X_POKE_PEEK_100,
+ LAN887X_POKE_PEEK_100_EN,
+ LAN887X_POKE_PEEK_100_EN);
+ if (rc < 0)
+ return rc;
+
+ /* Required before reading register
+ * otherwise it will return high value
+ */
+ msleep(50);
+
+ /* Get 200 SQI raw readings */
+ for (int i = 0; i < 200; i++) {
+ rc = phy_write_mmd(phydev, MDIO_MMD_VEND1,
+ LAN887X_POKE_PEEK_100,
+ LAN887X_POKE_PEEK_100_EN);
+ if (rc < 0)
+ return rc;
+
+ rc = phy_read_mmd(phydev, MDIO_MMD_VEND1,
+ LAN887X_SQI_MSE_100);
+ if (rc < 0)
+ return rc;
+
+ rawtable[i] = rc;
+ rc = genphy_c45_read_link(phydev);
+ if (rc < 0)
+ return rc;
+
+ if (!phydev->link)
+ return -ENETDOWN;
+ }
+
+ /* Sort SQI raw readings in ascending order */
+ sort(rawtable, 200, sizeof(u16), data_compare, NULL);
+
+ /* Keep inliers and discard outliers */
+ for (int i = 40; i < 160; i++)
+ sqiavg += rawtable[i];
+
+ /* Get SQI average */
+ sqiavg /= 120;
+
+ if (sqiavg < 75)
+ sqinum = 7;
+ else if (sqiavg < 94)
+ sqinum = 6;
+ else if (sqiavg < 119)
+ sqinum = 5;
+ else if (sqiavg < 150)
+ sqinum = 4;
+ else if (sqiavg < 189)
+ sqinum = 3;
+ else if (sqiavg < 237)
+ sqinum = 2;
+ else if (sqiavg < 299)
+ sqinum = 1;
+ else
+ sqinum = 0;
+
+ return sqinum;
+}
+
+static int lan887x_get_sqi(struct phy_device *phydev)
+{
+ int rc, val;
+
+ if (phydev->speed != SPEED_1000 &&
+ phydev->speed != SPEED_100) {
+ return -EINVAL;
+ }
+
+ if (phydev->speed == SPEED_100)
+ return lan887x_get_sqi_100M(phydev);
+
+ /* Writing DCQ_COEFF_EN to trigger a SQI read */
+ rc = phy_set_bits_mmd(phydev, MDIO_MMD_VEND1,
+ LAN887X_COEFF_MOD_CONFIG,
+ LAN887X_COEFF_MOD_CONFIG_DCQ_COEFF_EN);
+ if (rc < 0)
+ return rc;
+
+ /* Wait for DCQ done */
+ rc = phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1,
+ LAN887X_COEFF_MOD_CONFIG, val, ((val &
+ LAN887X_COEFF_MOD_CONFIG_DCQ_COEFF_EN) !=
+ LAN887X_COEFF_MOD_CONFIG_DCQ_COEFF_EN),
+ 10, 200, true);
+ if (rc < 0)
+ return rc;
+
+ rc = phy_read_mmd(phydev, MDIO_MMD_VEND1, LAN887X_DCQ_SQI_STATUS);
+ if (rc < 0)
+ return rc;
+
+ return FIELD_GET(T1_DCQ_SQI_MSK, rc);
+}
+
static struct phy_driver microchip_t1_phy_driver[] = {
{
PHY_ID_MATCH_MODEL(PHY_ID_LAN87XX),
@@ -1468,6 +1610,8 @@ static struct phy_driver microchip_t1_phy_driver[] = {
.suspend = genphy_suspend,
.resume = genphy_resume,
.read_status = genphy_c45_read_status,
+ .get_sqi = lan887x_get_sqi,
+ .get_sqi_max = lan87xx_get_sqi_max,
}
};
--
2.34.1
> + /* Get 200 SQI raw readings */
> + for (int i = 0; i < 200; i++) {
Please replace all the hard coded 200 with ARRAY_SIZE(rawtable). That
makes it easier to tune the size of the table without causing buffer
overrun bugs.
> + rc = phy_write_mmd(phydev, MDIO_MMD_VEND1,
> + LAN887X_POKE_PEEK_100,
> + LAN887X_POKE_PEEK_100_EN);
> + if (rc < 0)
> + return rc;
> +
> + rc = phy_read_mmd(phydev, MDIO_MMD_VEND1,
> + LAN887X_SQI_MSE_100);
> + if (rc < 0)
> + return rc;
> +
> + rawtable[i] = rc;
> + rc = genphy_c45_read_link(phydev);
> + if (rc < 0)
> + return rc;
> +
> + if (!phydev->link)
> + return -ENETDOWN;
> + }
How long does this take?
genphy_c45_read_link() takes a few MDIO transaction, plus the two you
see here. So maybe 1000 MDIO bus transactions? Which could be
3000-4000 if it needs to use C45 over C22.
Do you have any data on the accuracy, with say 10, 20, 40, 80, 160
samples?
Can the genphy_c45_read_link() be moved out of the loop? If the link
is lost, is the sample totally random, or does it have a well defined
value? Looking at the link status every iteration, rather than before
and after collecting the samples, you are trying to protect against
the link going down and back up again. If it is taking a couple of
seconds to collect all the samples, i suppose that is possible, but if
its 50ms, do you really have to worry?
> +static int lan887x_get_sqi(struct phy_device *phydev)
> +{
> + int rc, val;
> +
> + if (phydev->speed != SPEED_1000 &&
> + phydev->speed != SPEED_100) {
> + return -EINVAL;
> + }
Can that happen? Does the PHY support SPEED_10? Or are you trying to
protect against SPEED_UNKOWN because the link is down? ENETDOWN might
be more appropriate that EINVAL.
Andrew
© 2016 - 2025 Red Hat, Inc.