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:
Родитель
c916e8e1ea
Коммит
49011e0c15
|
@ -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
|
||||||
|
|
Загрузка…
Ссылка в новой задаче