Commit fc879f72 authored by Andrew Lunn's avatar Andrew Lunn Committed by Jakub Kicinski
Browse files

net: phy: marvell: Add cable test support



The Marvell PHYs have a couple of different register sets for
performing cable tests. Page 7 provides the simplest to use.

v3:
s/mavell/marvell/g
Remove include of <uapi/linux/ethtool_netlink.h>

Signed-off-by: default avatarAndrew Lunn <andrew@lunn.ch>
Reviewed-by: default avatarFlorian Fainelli <f.fainelli@gmail.com>
Signed-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parent 1e2dc145
Loading
Loading
Loading
Loading
+201 −0
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@
#include <linux/module.h>
#include <linux/mii.h>
#include <linux/ethtool.h>
#include <linux/ethtool_netlink.h>
#include <linux/phy.h>
#include <linux/marvell_phy.h>
#include <linux/bitfield.h>
@@ -42,6 +43,7 @@
#define MII_MARVELL_MSCR_PAGE		0x02
#define MII_MARVELL_LED_PAGE		0x03
#define MII_MARVELL_MISC_TEST_PAGE	0x06
#define MII_MARVELL_VCT7_PAGE		0x07
#define MII_MARVELL_WOL_PAGE		0x11

#define MII_M1011_IEVENT		0x13
@@ -162,6 +164,36 @@
#define MII_88E1510_GEN_CTRL_REG_1_MODE_SGMII	0x1	/* SGMII to copper */
#define MII_88E1510_GEN_CTRL_REG_1_RESET	0x8000	/* Soft reset */

#define MII_VCT7_PAIR_0_DISTANCE	0x10
#define MII_VCT7_PAIR_1_DISTANCE	0x11
#define MII_VCT7_PAIR_2_DISTANCE	0x12
#define MII_VCT7_PAIR_3_DISTANCE	0x13

#define MII_VCT7_RESULTS	0x14
#define MII_VCT7_RESULTS_PAIR3_MASK	0xf000
#define MII_VCT7_RESULTS_PAIR2_MASK	0x0f00
#define MII_VCT7_RESULTS_PAIR1_MASK	0x00f0
#define MII_VCT7_RESULTS_PAIR0_MASK	0x000f
#define MII_VCT7_RESULTS_PAIR3_SHIFT	12
#define MII_VCT7_RESULTS_PAIR2_SHIFT	8
#define MII_VCT7_RESULTS_PAIR1_SHIFT	4
#define MII_VCT7_RESULTS_PAIR0_SHIFT	0
#define MII_VCT7_RESULTS_INVALID	0
#define MII_VCT7_RESULTS_OK		1
#define MII_VCT7_RESULTS_OPEN		2
#define MII_VCT7_RESULTS_SAME_SHORT	3
#define MII_VCT7_RESULTS_CROSS_SHORT	4
#define MII_VCT7_RESULTS_BUSY		9

#define MII_VCT7_CTRL		0x15
#define MII_VCT7_CTRL_RUN_NOW			BIT(15)
#define MII_VCT7_CTRL_RUN_ANEG			BIT(14)
#define MII_VCT7_CTRL_DISABLE_CROSS		BIT(13)
#define MII_VCT7_CTRL_RUN_AFTER_BREAK_LINK	BIT(12)
#define MII_VCT7_CTRL_IN_PROGRESS		BIT(11)
#define MII_VCT7_CTRL_METERS			BIT(10)
#define MII_VCT7_CTRL_CENTIMETERS		0

#define LPA_PAUSE_FIBER		0x180
#define LPA_PAUSE_ASYM_FIBER	0x100

@@ -1658,6 +1690,163 @@ static void marvell_get_stats(struct phy_device *phydev,
		data[i] = marvell_get_stat(phydev, i);
}

static int marvell_vct7_cable_test_start(struct phy_device *phydev)
{
	int bmcr, bmsr, ret;

	/* If auto-negotiation is enabled, but not complete, the cable
	 * test never completes. So disable auto-neg.
	 */
	bmcr = phy_read(phydev, MII_BMCR);
	if (bmcr < 0)
		return bmcr;

	bmsr = phy_read(phydev, MII_BMSR);

	if (bmsr < 0)
		return bmsr;

	if (bmcr & BMCR_ANENABLE) {
		ret =  phy_modify(phydev, MII_BMCR, BMCR_ANENABLE, 0);
		if (ret < 0)
			return ret;
		ret = genphy_soft_reset(phydev);
		if (ret < 0)
			return ret;
	}

	/* If the link is up, allow it some time to go down */
	if (bmsr & BMSR_LSTATUS)
		msleep(1500);

	return phy_write_paged(phydev, MII_MARVELL_VCT7_PAGE,
			       MII_VCT7_CTRL,
			       MII_VCT7_CTRL_RUN_NOW |
			       MII_VCT7_CTRL_CENTIMETERS);
}

static int marvell_vct7_distance_to_length(int distance, bool meter)
{
	if (meter)
		distance *= 100;

	return distance;
}

static bool marvell_vct7_distance_valid(int result)
{
	switch (result) {
	case MII_VCT7_RESULTS_OPEN:
	case MII_VCT7_RESULTS_SAME_SHORT:
	case MII_VCT7_RESULTS_CROSS_SHORT:
		return true;
	}
	return false;
}

static int marvell_vct7_report_length(struct phy_device *phydev,
				      int pair, bool meter)
{
	int length;
	int ret;

	ret = phy_read_paged(phydev, MII_MARVELL_VCT7_PAGE,
			     MII_VCT7_PAIR_0_DISTANCE + pair);
	if (ret < 0)
		return ret;

	length = marvell_vct7_distance_to_length(ret, meter);

	ethnl_cable_test_fault_length(phydev, pair, length);

	return 0;
}

static int marvell_vct7_cable_test_report_trans(int result)
{
	switch (result) {
	case MII_VCT7_RESULTS_OK:
		return ETHTOOL_A_CABLE_RESULT_CODE_OK;
	case MII_VCT7_RESULTS_OPEN:
		return ETHTOOL_A_CABLE_RESULT_CODE_OPEN;
	case MII_VCT7_RESULTS_SAME_SHORT:
		return ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT;
	case MII_VCT7_RESULTS_CROSS_SHORT:
		return ETHTOOL_A_CABLE_RESULT_CODE_CROSS_SHORT;
	default:
		return ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC;
	}
}

static int marvell_vct7_cable_test_report(struct phy_device *phydev)
{
	int pair0, pair1, pair2, pair3;
	bool meter;
	int ret;

	ret = phy_read_paged(phydev, MII_MARVELL_VCT7_PAGE,
			     MII_VCT7_RESULTS);
	if (ret < 0)
		return ret;

	pair3 = (ret & MII_VCT7_RESULTS_PAIR3_MASK) >>
		MII_VCT7_RESULTS_PAIR3_SHIFT;
	pair2 = (ret & MII_VCT7_RESULTS_PAIR2_MASK) >>
		MII_VCT7_RESULTS_PAIR2_SHIFT;
	pair1 = (ret & MII_VCT7_RESULTS_PAIR1_MASK) >>
		MII_VCT7_RESULTS_PAIR1_SHIFT;
	pair0 = (ret & MII_VCT7_RESULTS_PAIR0_MASK) >>
		MII_VCT7_RESULTS_PAIR0_SHIFT;

	ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_A,
				marvell_vct7_cable_test_report_trans(pair0));
	ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_B,
				marvell_vct7_cable_test_report_trans(pair1));
	ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_C,
				marvell_vct7_cable_test_report_trans(pair2));
	ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_D,
				marvell_vct7_cable_test_report_trans(pair3));

	ret = phy_read_paged(phydev, MII_MARVELL_VCT7_PAGE, MII_VCT7_CTRL);
	if (ret < 0)
		return ret;

	meter = ret & MII_VCT7_CTRL_METERS;

	if (marvell_vct7_distance_valid(pair0))
		marvell_vct7_report_length(phydev, 0, meter);
	if (marvell_vct7_distance_valid(pair1))
		marvell_vct7_report_length(phydev, 1, meter);
	if (marvell_vct7_distance_valid(pair2))
		marvell_vct7_report_length(phydev, 2, meter);
	if (marvell_vct7_distance_valid(pair3))
		marvell_vct7_report_length(phydev, 3, meter);

	return 0;
}

static int marvell_vct7_cable_test_get_status(struct phy_device *phydev,
					      bool *finished)
{
	int ret;

	*finished = false;

	ret = phy_read_paged(phydev, MII_MARVELL_VCT7_PAGE,
			     MII_VCT7_CTRL);

	if (ret < 0)
		return ret;

	if (!(ret & MII_VCT7_CTRL_IN_PROGRESS)) {
		*finished = true;

		return marvell_vct7_cable_test_report(phydev);
	}

	return 0;
}

#ifdef CONFIG_HWMON
static int m88e1121_get_temp(struct phy_device *phydev, long *temp)
{
@@ -2353,6 +2542,7 @@ static struct phy_driver marvell_drivers[] = {
		.phy_id_mask = MARVELL_PHY_ID_MASK,
		.name = "Marvell 88E1510",
		.features = PHY_GBIT_FIBRE_FEATURES,
		.flags = PHY_POLL_CABLE_TEST,
		.probe = &m88e1510_probe,
		.config_init = &m88e1510_config_init,
		.config_aneg = &m88e1510_config_aneg,
@@ -2372,12 +2562,15 @@ static struct phy_driver marvell_drivers[] = {
		.set_loopback = genphy_loopback,
		.get_tunable = m88e1011_get_tunable,
		.set_tunable = m88e1011_set_tunable,
		.cable_test_start = marvell_vct7_cable_test_start,
		.cable_test_get_status = marvell_vct7_cable_test_get_status,
	},
	{
		.phy_id = MARVELL_PHY_ID_88E1540,
		.phy_id_mask = MARVELL_PHY_ID_MASK,
		.name = "Marvell 88E1540",
		/* PHY_GBIT_FEATURES */
		.flags = PHY_POLL_CABLE_TEST,
		.probe = m88e1510_probe,
		.config_init = &marvell_config_init,
		.config_aneg = &m88e1510_config_aneg,
@@ -2394,6 +2587,8 @@ static struct phy_driver marvell_drivers[] = {
		.get_stats = marvell_get_stats,
		.get_tunable = m88e1540_get_tunable,
		.set_tunable = m88e1540_set_tunable,
		.cable_test_start = marvell_vct7_cable_test_start,
		.cable_test_get_status = marvell_vct7_cable_test_get_status,
	},
	{
		.phy_id = MARVELL_PHY_ID_88E1545,
@@ -2401,6 +2596,7 @@ static struct phy_driver marvell_drivers[] = {
		.name = "Marvell 88E1545",
		.probe = m88e1510_probe,
		/* PHY_GBIT_FEATURES */
		.flags = PHY_POLL_CABLE_TEST,
		.config_init = &marvell_config_init,
		.config_aneg = &m88e1510_config_aneg,
		.read_status = &marvell_read_status,
@@ -2416,6 +2612,8 @@ static struct phy_driver marvell_drivers[] = {
		.get_stats = marvell_get_stats,
		.get_tunable = m88e1540_get_tunable,
		.set_tunable = m88e1540_set_tunable,
		.cable_test_start = marvell_vct7_cable_test_start,
		.cable_test_get_status = marvell_vct7_cable_test_get_status,
	},
	{
		.phy_id = MARVELL_PHY_ID_88E3016,
@@ -2442,6 +2640,7 @@ static struct phy_driver marvell_drivers[] = {
		.phy_id_mask = MARVELL_PHY_ID_MASK,
		.name = "Marvell 88E6390",
		/* PHY_GBIT_FEATURES */
		.flags = PHY_POLL_CABLE_TEST,
		.probe = m88e6390_probe,
		.config_init = &marvell_config_init,
		.config_aneg = &m88e6390_config_aneg,
@@ -2458,6 +2657,8 @@ static struct phy_driver marvell_drivers[] = {
		.get_stats = marvell_get_stats,
		.get_tunable = m88e1540_get_tunable,
		.set_tunable = m88e1540_set_tunable,
		.cable_test_start = marvell_vct7_cable_test_start,
		.cable_test_get_status = marvell_vct7_cable_test_get_status,
	},
};