|
6 | 6 | #include <linux/delay.h>
|
7 | 7 | #include <linux/mii.h>
|
8 | 8 | #include <linux/phy.h>
|
| 9 | +#include <linux/sort.h> |
9 | 10 | #include <linux/ethtool.h>
|
10 | 11 | #include <linux/ethtool_netlink.h>
|
11 | 12 | #include <linux/bitfield.h>
|
|
238 | 239 | #define LAN887X_MX_CHIP_TOP_ALL_MSK (LAN887X_INT_MSK_T1_PHY_INT_MSK |\
|
239 | 240 | LAN887X_MX_CHIP_TOP_LINK_MSK)
|
240 | 241 |
|
| 242 | +#define LAN887X_COEFF_PWR_DN_CONFIG_100 0x0404 |
| 243 | +#define LAN887X_COEFF_PWR_DN_CONFIG_100_V 0x16d6 |
| 244 | +#define LAN887X_SQI_CONFIG_100 0x042e |
| 245 | +#define LAN887X_SQI_CONFIG_100_V 0x9572 |
| 246 | +#define LAN887X_SQI_MSE_100 0x483 |
| 247 | + |
| 248 | +#define LAN887X_POKE_PEEK_100 0x040d |
| 249 | +#define LAN887X_POKE_PEEK_100_EN BIT(0) |
| 250 | + |
| 251 | +#define LAN887X_COEFF_MOD_CONFIG 0x080d |
| 252 | +#define LAN887X_COEFF_MOD_CONFIG_DCQ_COEFF_EN BIT(8) |
| 253 | + |
| 254 | +#define LAN887X_DCQ_SQI_STATUS 0x08b2 |
| 255 | + |
| 256 | +/* SQI raw samples count */ |
| 257 | +#define SQI_SAMPLES 200 |
| 258 | + |
| 259 | +/* Samples percentage considered for SQI calculation */ |
| 260 | +#define SQI_INLINERS_PERCENT 60 |
| 261 | + |
| 262 | +/* Samples count considered for SQI calculation */ |
| 263 | +#define SQI_INLIERS_NUM (SQI_SAMPLES * SQI_INLINERS_PERCENT / 100) |
| 264 | + |
| 265 | +/* Start offset of samples */ |
| 266 | +#define SQI_INLIERS_START ((SQI_SAMPLES - SQI_INLIERS_NUM) / 2) |
| 267 | + |
| 268 | +/* End offset of samples */ |
| 269 | +#define SQI_INLIERS_END (SQI_INLIERS_START + SQI_INLIERS_NUM) |
| 270 | + |
241 | 271 | #define DRIVER_AUTHOR "Nisar Sayed < [email protected]>"
|
242 | 272 | #define DRIVER_DESC "Microchip LAN87XX/LAN937x/LAN887x T1 PHY driver"
|
243 | 273 |
|
@@ -1889,6 +1919,145 @@ static int lan887x_cable_test_get_status(struct phy_device *phydev,
|
1889 | 1919 | return lan887x_cable_test_report(phydev);
|
1890 | 1920 | }
|
1891 | 1921 |
|
| 1922 | +/* Compare block to sort in ascending order */ |
| 1923 | +static int sqi_compare(const void *a, const void *b) |
| 1924 | +{ |
| 1925 | + return *(u16 *)a - *(u16 *)b; |
| 1926 | +} |
| 1927 | + |
| 1928 | +static int lan887x_get_sqi_100M(struct phy_device *phydev) |
| 1929 | +{ |
| 1930 | + u16 rawtable[SQI_SAMPLES]; |
| 1931 | + u32 sqiavg = 0; |
| 1932 | + u8 sqinum = 0; |
| 1933 | + int rc, i; |
| 1934 | + |
| 1935 | + /* Configuration of SQI 100M */ |
| 1936 | + rc = phy_write_mmd(phydev, MDIO_MMD_VEND1, |
| 1937 | + LAN887X_COEFF_PWR_DN_CONFIG_100, |
| 1938 | + LAN887X_COEFF_PWR_DN_CONFIG_100_V); |
| 1939 | + if (rc < 0) |
| 1940 | + return rc; |
| 1941 | + |
| 1942 | + rc = phy_write_mmd(phydev, MDIO_MMD_VEND1, LAN887X_SQI_CONFIG_100, |
| 1943 | + LAN887X_SQI_CONFIG_100_V); |
| 1944 | + if (rc < 0) |
| 1945 | + return rc; |
| 1946 | + |
| 1947 | + rc = phy_read_mmd(phydev, MDIO_MMD_VEND1, LAN887X_SQI_CONFIG_100); |
| 1948 | + if (rc != LAN887X_SQI_CONFIG_100_V) |
| 1949 | + return -EINVAL; |
| 1950 | + |
| 1951 | + rc = phy_modify_mmd(phydev, MDIO_MMD_VEND1, LAN887X_POKE_PEEK_100, |
| 1952 | + LAN887X_POKE_PEEK_100_EN, |
| 1953 | + LAN887X_POKE_PEEK_100_EN); |
| 1954 | + if (rc < 0) |
| 1955 | + return rc; |
| 1956 | + |
| 1957 | + /* Required before reading register |
| 1958 | + * otherwise it will return high value |
| 1959 | + */ |
| 1960 | + msleep(50); |
| 1961 | + |
| 1962 | + /* Link check before raw readings */ |
| 1963 | + rc = genphy_c45_read_link(phydev); |
| 1964 | + if (rc < 0) |
| 1965 | + return rc; |
| 1966 | + |
| 1967 | + if (!phydev->link) |
| 1968 | + return -ENETDOWN; |
| 1969 | + |
| 1970 | + /* Get 200 SQI raw readings */ |
| 1971 | + for (i = 0; i < SQI_SAMPLES; i++) { |
| 1972 | + rc = phy_write_mmd(phydev, MDIO_MMD_VEND1, |
| 1973 | + LAN887X_POKE_PEEK_100, |
| 1974 | + LAN887X_POKE_PEEK_100_EN); |
| 1975 | + if (rc < 0) |
| 1976 | + return rc; |
| 1977 | + |
| 1978 | + rc = phy_read_mmd(phydev, MDIO_MMD_VEND1, |
| 1979 | + LAN887X_SQI_MSE_100); |
| 1980 | + if (rc < 0) |
| 1981 | + return rc; |
| 1982 | + |
| 1983 | + rawtable[i] = (u16)rc; |
| 1984 | + } |
| 1985 | + |
| 1986 | + /* Link check after raw readings */ |
| 1987 | + rc = genphy_c45_read_link(phydev); |
| 1988 | + if (rc < 0) |
| 1989 | + return rc; |
| 1990 | + |
| 1991 | + if (!phydev->link) |
| 1992 | + return -ENETDOWN; |
| 1993 | + |
| 1994 | + /* Sort SQI raw readings in ascending order */ |
| 1995 | + sort(rawtable, SQI_SAMPLES, sizeof(u16), sqi_compare, NULL); |
| 1996 | + |
| 1997 | + /* Keep inliers and discard outliers */ |
| 1998 | + for (i = SQI_INLIERS_START; i < SQI_INLIERS_END; i++) |
| 1999 | + sqiavg += rawtable[i]; |
| 2000 | + |
| 2001 | + /* Handle invalid samples */ |
| 2002 | + if (sqiavg != 0) { |
| 2003 | + /* Get SQI average */ |
| 2004 | + sqiavg /= SQI_INLIERS_NUM; |
| 2005 | + |
| 2006 | + if (sqiavg < 75) |
| 2007 | + sqinum = 7; |
| 2008 | + else if (sqiavg < 94) |
| 2009 | + sqinum = 6; |
| 2010 | + else if (sqiavg < 119) |
| 2011 | + sqinum = 5; |
| 2012 | + else if (sqiavg < 150) |
| 2013 | + sqinum = 4; |
| 2014 | + else if (sqiavg < 189) |
| 2015 | + sqinum = 3; |
| 2016 | + else if (sqiavg < 237) |
| 2017 | + sqinum = 2; |
| 2018 | + else if (sqiavg < 299) |
| 2019 | + sqinum = 1; |
| 2020 | + else |
| 2021 | + sqinum = 0; |
| 2022 | + } |
| 2023 | + |
| 2024 | + return sqinum; |
| 2025 | +} |
| 2026 | + |
| 2027 | +static int lan887x_get_sqi(struct phy_device *phydev) |
| 2028 | +{ |
| 2029 | + int rc, val; |
| 2030 | + |
| 2031 | + if (phydev->speed != SPEED_1000 && |
| 2032 | + phydev->speed != SPEED_100) |
| 2033 | + return -ENETDOWN; |
| 2034 | + |
| 2035 | + if (phydev->speed == SPEED_100) |
| 2036 | + return lan887x_get_sqi_100M(phydev); |
| 2037 | + |
| 2038 | + /* Writing DCQ_COEFF_EN to trigger a SQI read */ |
| 2039 | + rc = phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, |
| 2040 | + LAN887X_COEFF_MOD_CONFIG, |
| 2041 | + LAN887X_COEFF_MOD_CONFIG_DCQ_COEFF_EN); |
| 2042 | + if (rc < 0) |
| 2043 | + return rc; |
| 2044 | + |
| 2045 | + /* Wait for DCQ done */ |
| 2046 | + rc = phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1, |
| 2047 | + LAN887X_COEFF_MOD_CONFIG, val, ((val & |
| 2048 | + LAN887X_COEFF_MOD_CONFIG_DCQ_COEFF_EN) != |
| 2049 | + LAN887X_COEFF_MOD_CONFIG_DCQ_COEFF_EN), |
| 2050 | + 10, 200, true); |
| 2051 | + if (rc < 0) |
| 2052 | + return rc; |
| 2053 | + |
| 2054 | + rc = phy_read_mmd(phydev, MDIO_MMD_VEND1, LAN887X_DCQ_SQI_STATUS); |
| 2055 | + if (rc < 0) |
| 2056 | + return rc; |
| 2057 | + |
| 2058 | + return FIELD_GET(T1_DCQ_SQI_MSK, rc); |
| 2059 | +} |
| 2060 | + |
1892 | 2061 | static struct phy_driver microchip_t1_phy_driver[] = {
|
1893 | 2062 | {
|
1894 | 2063 | PHY_ID_MATCH_MODEL(PHY_ID_LAN87XX),
|
@@ -1942,6 +2111,8 @@ static struct phy_driver microchip_t1_phy_driver[] = {
|
1942 | 2111 | .cable_test_get_status = lan887x_cable_test_get_status,
|
1943 | 2112 | .config_intr = lan887x_config_intr,
|
1944 | 2113 | .handle_interrupt = lan887x_handle_interrupt,
|
| 2114 | + .get_sqi = lan887x_get_sqi, |
| 2115 | + .get_sqi_max = lan87xx_get_sqi_max, |
1945 | 2116 | }
|
1946 | 2117 | };
|
1947 | 2118 |
|
|
0 commit comments