net: phy: micrel: ksz886x/ksz8081: add cabletest support

This patch support for cable test for the ksz886x switches and the
ksz8081 PHY.

The patch was tested on a KSZ8873RLL switch with following results:

- port 1:
  - provides invalid values, thus return -ENOTSUPP
    (Errata: DS80000830A: "LinkMD does not work on Port 1",
     http://ww1.microchip.com/downloads/en/DeviceDoc/KSZ8873-Errata-DS80000830A.pdf)

- port 2:
  - can detect distance
  - can detect open on each wire of pair A (wire 1 and 2)
  - can detect open only on one wire of pair B (only wire 3)
  - can detect short between wires of a pair (wires 1 + 2 or 3 + 6)
  - short between pairs is detected as open.
    For example short between wires 2 + 3 is detected as open.

Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
Oleksij Rempel 2021-06-14 06:31:25 +02:00 коммит произвёл David S. Miller
Родитель c916e8e1ea
Коммит 49011e0c15
3 изменённых файлов: 194 добавлений и 0 удалений

Просмотреть файл

@ -970,6 +970,18 @@ static enum dsa_tag_protocol ksz8_get_tag_protocol(struct dsa_switch *ds,
DSA_TAG_PROTO_KSZ9893 : DSA_TAG_PROTO_KSZ8795; DSA_TAG_PROTO_KSZ9893 : DSA_TAG_PROTO_KSZ8795;
} }
static u32 ksz8_sw_get_phy_flags(struct dsa_switch *ds, int port)
{
/* Silicon Errata Sheet (DS80000830A):
* Port 1 does not work with LinkMD Cable-Testing.
* Port 1 does not respond to received PAUSE control frames.
*/
if (!port)
return MICREL_KSZ8_P1_ERRATA;
return 0;
}
static void ksz8_get_strings(struct dsa_switch *ds, int port, static void ksz8_get_strings(struct dsa_switch *ds, int port,
u32 stringset, uint8_t *buf) u32 stringset, uint8_t *buf)
{ {
@ -1503,6 +1515,7 @@ unsupported:
static const struct dsa_switch_ops ksz8_switch_ops = { static const struct dsa_switch_ops ksz8_switch_ops = {
.get_tag_protocol = ksz8_get_tag_protocol, .get_tag_protocol = ksz8_get_tag_protocol,
.get_phy_flags = ksz8_sw_get_phy_flags,
.setup = ksz8_setup, .setup = ksz8_setup,
.phy_read = ksz_phy_read16, .phy_read = ksz_phy_read16,
.phy_write = ksz_phy_write16, .phy_write = ksz_phy_write16,

Просмотреть файл

@ -20,6 +20,7 @@
*/ */
#include <linux/bitfield.h> #include <linux/bitfield.h>
#include <linux/ethtool_netlink.h>
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/phy.h> #include <linux/phy.h>
@ -53,6 +54,18 @@
#define KSZPHY_INTCS_STATUS (KSZPHY_INTCS_LINK_DOWN_STATUS |\ #define KSZPHY_INTCS_STATUS (KSZPHY_INTCS_LINK_DOWN_STATUS |\
KSZPHY_INTCS_LINK_UP_STATUS) KSZPHY_INTCS_LINK_UP_STATUS)
/* LinkMD Control/Status */
#define KSZ8081_LMD 0x1d
#define KSZ8081_LMD_ENABLE_TEST BIT(15)
#define KSZ8081_LMD_STAT_NORMAL 0
#define KSZ8081_LMD_STAT_OPEN 1
#define KSZ8081_LMD_STAT_SHORT 2
#define KSZ8081_LMD_STAT_FAIL 3
#define KSZ8081_LMD_STAT_MASK GENMASK(14, 13)
/* Short cable (<10 meter) has been detected by LinkMD */
#define KSZ8081_LMD_SHORT_INDICATOR BIT(12)
#define KSZ8081_LMD_DELTA_TIME_MASK GENMASK(8, 0)
/* PHY Control 1 */ /* PHY Control 1 */
#define MII_KSZPHY_CTRL_1 0x1e #define MII_KSZPHY_CTRL_1 0x1e
#define KSZ8081_CTRL1_MDIX_STAT BIT(4) #define KSZ8081_CTRL1_MDIX_STAT BIT(4)
@ -1363,6 +1376,167 @@ static int kszphy_probe(struct phy_device *phydev)
return 0; return 0;
} }
static int ksz886x_cable_test_start(struct phy_device *phydev)
{
if (phydev->dev_flags & MICREL_KSZ8_P1_ERRATA)
return -EOPNOTSUPP;
/* If autoneg is enabled, we won't be able to test cross pair
* short. In this case, the PHY will "detect" a link and
* confuse the internal state machine - disable auto neg here.
* If autoneg is disabled, we should set the speed to 10mbit.
*/
return phy_clear_bits(phydev, MII_BMCR, BMCR_ANENABLE | BMCR_SPEED100);
}
static int ksz886x_cable_test_result_trans(u16 status)
{
switch (FIELD_GET(KSZ8081_LMD_STAT_MASK, status)) {
case KSZ8081_LMD_STAT_NORMAL:
return ETHTOOL_A_CABLE_RESULT_CODE_OK;
case KSZ8081_LMD_STAT_SHORT:
return ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT;
case KSZ8081_LMD_STAT_OPEN:
return ETHTOOL_A_CABLE_RESULT_CODE_OPEN;
case KSZ8081_LMD_STAT_FAIL:
fallthrough;
default:
return ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC;
}
}
static bool ksz886x_cable_test_failed(u16 status)
{
return FIELD_GET(KSZ8081_LMD_STAT_MASK, status) ==
KSZ8081_LMD_STAT_FAIL;
}
static bool ksz886x_cable_test_fault_length_valid(u16 status)
{
switch (FIELD_GET(KSZ8081_LMD_STAT_MASK, status)) {
case KSZ8081_LMD_STAT_OPEN:
fallthrough;
case KSZ8081_LMD_STAT_SHORT:
return true;
}
return false;
}
static int ksz886x_cable_test_fault_length(u16 status)
{
int dt;
/* According to the data sheet the distance to the fault is
* DELTA_TIME * 0.4 meters.
*/
dt = FIELD_GET(KSZ8081_LMD_DELTA_TIME_MASK, status);
return (dt * 400) / 10;
}
static int ksz886x_cable_test_wait_for_completion(struct phy_device *phydev)
{
int val, ret;
ret = phy_read_poll_timeout(phydev, KSZ8081_LMD, val,
!(val & KSZ8081_LMD_ENABLE_TEST),
30000, 100000, true);
return ret < 0 ? ret : 0;
}
static int ksz886x_cable_test_one_pair(struct phy_device *phydev, int pair)
{
static const int ethtool_pair[] = {
ETHTOOL_A_CABLE_PAIR_A,
ETHTOOL_A_CABLE_PAIR_B,
};
int ret, val, mdix;
/* There is no way to choice the pair, like we do one ksz9031.
* We can workaround this limitation by using the MDI-X functionality.
*/
if (pair == 0)
mdix = ETH_TP_MDI;
else
mdix = ETH_TP_MDI_X;
switch (phydev->phy_id & MICREL_PHY_ID_MASK) {
case PHY_ID_KSZ8081:
ret = ksz8081_config_mdix(phydev, mdix);
break;
case PHY_ID_KSZ886X:
ret = ksz886x_config_mdix(phydev, mdix);
break;
default:
ret = -ENODEV;
}
if (ret)
return ret;
/* Now we are ready to fire. This command will send a 100ns pulse
* to the pair.
*/
ret = phy_write(phydev, KSZ8081_LMD, KSZ8081_LMD_ENABLE_TEST);
if (ret)
return ret;
ret = ksz886x_cable_test_wait_for_completion(phydev);
if (ret)
return ret;
val = phy_read(phydev, KSZ8081_LMD);
if (val < 0)
return val;
if (ksz886x_cable_test_failed(val))
return -EAGAIN;
ret = ethnl_cable_test_result(phydev, ethtool_pair[pair],
ksz886x_cable_test_result_trans(val));
if (ret)
return ret;
if (!ksz886x_cable_test_fault_length_valid(val))
return 0;
return ethnl_cable_test_fault_length(phydev, ethtool_pair[pair],
ksz886x_cable_test_fault_length(val));
}
static int ksz886x_cable_test_get_status(struct phy_device *phydev,
bool *finished)
{
unsigned long pair_mask = 0x3;
int retries = 20;
int pair, ret;
*finished = false;
/* Try harder if link partner is active */
while (pair_mask && retries--) {
for_each_set_bit(pair, &pair_mask, 4) {
ret = ksz886x_cable_test_one_pair(phydev, pair);
if (ret == -EAGAIN)
continue;
if (ret < 0)
return ret;
clear_bit(pair, &pair_mask);
}
/* If link partner is in autonegotiation mode it will send 2ms
* of FLPs with at least 6ms of silence.
* Add 2ms sleep to have better chances to hit this silence.
*/
if (pair_mask)
msleep(2);
}
*finished = true;
return ret;
}
static struct phy_driver ksphy_driver[] = { static struct phy_driver ksphy_driver[] = {
{ {
.phy_id = PHY_ID_KS8737, .phy_id = PHY_ID_KS8737,
@ -1469,6 +1643,7 @@ static struct phy_driver ksphy_driver[] = {
.phy_id = PHY_ID_KSZ8081, .phy_id = PHY_ID_KSZ8081,
.name = "Micrel KSZ8081 or KSZ8091", .name = "Micrel KSZ8081 or KSZ8091",
.phy_id_mask = MICREL_PHY_ID_MASK, .phy_id_mask = MICREL_PHY_ID_MASK,
.flags = PHY_POLL_CABLE_TEST,
/* PHY_BASIC_FEATURES */ /* PHY_BASIC_FEATURES */
.driver_data = &ksz8081_type, .driver_data = &ksz8081_type,
.probe = kszphy_probe, .probe = kszphy_probe,
@ -1483,6 +1658,8 @@ static struct phy_driver ksphy_driver[] = {
.get_stats = kszphy_get_stats, .get_stats = kszphy_get_stats,
.suspend = kszphy_suspend, .suspend = kszphy_suspend,
.resume = kszphy_resume, .resume = kszphy_resume,
.cable_test_start = ksz886x_cable_test_start,
.cable_test_get_status = ksz886x_cable_test_get_status,
}, { }, {
.phy_id = PHY_ID_KSZ8061, .phy_id = PHY_ID_KSZ8061,
.name = "Micrel KSZ8061", .name = "Micrel KSZ8061",
@ -1571,11 +1748,14 @@ static struct phy_driver ksphy_driver[] = {
.phy_id_mask = MICREL_PHY_ID_MASK, .phy_id_mask = MICREL_PHY_ID_MASK,
.name = "Micrel KSZ8851 Ethernet MAC or KSZ886X Switch", .name = "Micrel KSZ8851 Ethernet MAC or KSZ886X Switch",
/* PHY_BASIC_FEATURES */ /* PHY_BASIC_FEATURES */
.flags = PHY_POLL_CABLE_TEST,
.config_init = kszphy_config_init, .config_init = kszphy_config_init,
.config_aneg = ksz886x_config_aneg, .config_aneg = ksz886x_config_aneg,
.read_status = ksz886x_read_status, .read_status = ksz886x_read_status,
.suspend = genphy_suspend, .suspend = genphy_suspend,
.resume = genphy_resume, .resume = genphy_resume,
.cable_test_start = ksz886x_cable_test_start,
.cable_test_get_status = ksz886x_cable_test_get_status,
}, { }, {
.name = "Micrel KSZ87XX Switch", .name = "Micrel KSZ87XX Switch",
/* PHY_BASIC_FEATURES */ /* PHY_BASIC_FEATURES */

Просмотреть файл

@ -39,6 +39,7 @@
/* struct phy_device dev_flags definitions */ /* struct phy_device dev_flags definitions */
#define MICREL_PHY_50MHZ_CLK 0x00000001 #define MICREL_PHY_50MHZ_CLK 0x00000001
#define MICREL_PHY_FXEN 0x00000002 #define MICREL_PHY_FXEN 0x00000002
#define MICREL_KSZ8_P1_ERRATA 0x00000003
#define MICREL_KSZ9021_EXTREG_CTRL 0xB #define MICREL_KSZ9021_EXTREG_CTRL 0xB
#define MICREL_KSZ9021_EXTREG_DATA_WRITE 0xC #define MICREL_KSZ9021_EXTREG_DATA_WRITE 0xC