Skip to content

Commit

Permalink
Merge branch 'net-phy-broadcom-cable-tester-support'
Browse files Browse the repository at this point in the history
Michael Walle says:

====================
net: phy: broadcom: cable tester support

Add cable tester support for the Broadcom PHYs. Support for it was
developed on a BCM54140 Quad PHY which RDB register access.

If there is a link partner the results are not as good as with an open
cable. I guess we could retry if the measurement until all pairs had at
least one valid result.

changes since v1:
 - added Reviewed-by: tags
 - removed "div by 2" for cross shorts, just mention it in the commit
   message. The results are inconclusive if the tests are repeated. So
   just report the length as is for now.
 - fixed typo in commit message
====================

Signed-off-by: David S. Miller <davem@davemloft.net>
  • Loading branch information
David S. Miller committed May 13, 2020
2 parents ea13d71 + f956af3 commit 87f785e
Show file tree
Hide file tree
Showing 4 changed files with 317 additions and 7 deletions.
259 changes: 252 additions & 7 deletions drivers/net/phy/bcm-phy-lib.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,45 +4,103 @@
*/

#include "bcm-phy-lib.h"
#include <linux/bitfield.h>
#include <linux/brcmphy.h>
#include <linux/export.h>
#include <linux/mdio.h>
#include <linux/module.h>
#include <linux/phy.h>
#include <linux/ethtool.h>
#include <linux/ethtool_netlink.h>

#define MII_BCM_CHANNEL_WIDTH 0x2000
#define BCM_CL45VEN_EEE_ADV 0x3c

int bcm_phy_write_exp(struct phy_device *phydev, u16 reg, u16 val)
int __bcm_phy_write_exp(struct phy_device *phydev, u16 reg, u16 val)
{
int rc;

rc = phy_write(phydev, MII_BCM54XX_EXP_SEL, reg);
rc = __phy_write(phydev, MII_BCM54XX_EXP_SEL, reg);
if (rc < 0)
return rc;

return phy_write(phydev, MII_BCM54XX_EXP_DATA, val);
return __phy_write(phydev, MII_BCM54XX_EXP_DATA, val);
}
EXPORT_SYMBOL_GPL(__bcm_phy_write_exp);

int bcm_phy_write_exp(struct phy_device *phydev, u16 reg, u16 val)
{
int rc;

phy_lock_mdio_bus(phydev);
rc = __bcm_phy_write_exp(phydev, reg, val);
phy_unlock_mdio_bus(phydev);

return rc;
}
EXPORT_SYMBOL_GPL(bcm_phy_write_exp);

int bcm_phy_read_exp(struct phy_device *phydev, u16 reg)
int __bcm_phy_read_exp(struct phy_device *phydev, u16 reg)
{
int val;

val = phy_write(phydev, MII_BCM54XX_EXP_SEL, reg);
val = __phy_write(phydev, MII_BCM54XX_EXP_SEL, reg);
if (val < 0)
return val;

val = phy_read(phydev, MII_BCM54XX_EXP_DATA);
val = __phy_read(phydev, MII_BCM54XX_EXP_DATA);

/* Restore default value. It's O.K. if this write fails. */
phy_write(phydev, MII_BCM54XX_EXP_SEL, 0);
__phy_write(phydev, MII_BCM54XX_EXP_SEL, 0);

return val;
}
EXPORT_SYMBOL_GPL(__bcm_phy_read_exp);

int bcm_phy_read_exp(struct phy_device *phydev, u16 reg)
{
int rc;

phy_lock_mdio_bus(phydev);
rc = __bcm_phy_read_exp(phydev, reg);
phy_unlock_mdio_bus(phydev);

return rc;
}
EXPORT_SYMBOL_GPL(bcm_phy_read_exp);

int __bcm_phy_modify_exp(struct phy_device *phydev, u16 reg, u16 mask, u16 set)
{
int new, ret;

ret = __phy_write(phydev, MII_BCM54XX_EXP_SEL, reg);
if (ret < 0)
return ret;

ret = __phy_read(phydev, MII_BCM54XX_EXP_DATA);
if (ret < 0)
return ret;

new = (ret & ~mask) | set;
if (new == ret)
return 0;

return __phy_write(phydev, MII_BCM54XX_EXP_DATA, new);
}
EXPORT_SYMBOL_GPL(__bcm_phy_modify_exp);

int bcm_phy_modify_exp(struct phy_device *phydev, u16 reg, u16 mask, u16 set)
{
int ret;

phy_lock_mdio_bus(phydev);
ret = __bcm_phy_modify_exp(phydev, reg, mask, set);
phy_unlock_mdio_bus(phydev);

return ret;
}
EXPORT_SYMBOL_GPL(bcm_phy_modify_exp);

int bcm54xx_auxctl_read(struct phy_device *phydev, u16 regnum)
{
/* The register must be written to both the Shadow Register Select and
Expand Down Expand Up @@ -525,6 +583,193 @@ int bcm_phy_enable_jumbo(struct phy_device *phydev)
}
EXPORT_SYMBOL_GPL(bcm_phy_enable_jumbo);

int __bcm_phy_enable_rdb_access(struct phy_device *phydev)
{
return __bcm_phy_write_exp(phydev, BCM54XX_EXP_REG7E, 0);
}
EXPORT_SYMBOL_GPL(__bcm_phy_enable_rdb_access);

int __bcm_phy_enable_legacy_access(struct phy_device *phydev)
{
return __bcm_phy_write_rdb(phydev, BCM54XX_RDB_REG0087,
BCM54XX_ACCESS_MODE_LEGACY_EN);
}
EXPORT_SYMBOL_GPL(__bcm_phy_enable_legacy_access);

static int _bcm_phy_cable_test_start(struct phy_device *phydev, bool is_rdb)
{
u16 mask, set;
int ret;

/* Auto-negotiation must be enabled for cable diagnostics to work, but
* don't advertise any capabilities.
*/
phy_write(phydev, MII_BMCR, BMCR_ANENABLE);
phy_write(phydev, MII_ADVERTISE, ADVERTISE_CSMA);
phy_write(phydev, MII_CTRL1000, 0);

phy_lock_mdio_bus(phydev);
if (is_rdb) {
ret = __bcm_phy_enable_legacy_access(phydev);
if (ret)
goto out;
}

mask = BCM54XX_ECD_CTRL_CROSS_SHORT_DIS | BCM54XX_ECD_CTRL_UNIT_MASK;
set = BCM54XX_ECD_CTRL_RUN | BCM54XX_ECD_CTRL_BREAK_LINK |
FIELD_PREP(BCM54XX_ECD_CTRL_UNIT_MASK,
BCM54XX_ECD_CTRL_UNIT_CM);

ret = __bcm_phy_modify_exp(phydev, BCM54XX_EXP_ECD_CTRL, mask, set);

out:
/* re-enable the RDB access even if there was an error */
if (is_rdb)
ret = __bcm_phy_enable_rdb_access(phydev) ? : ret;

phy_unlock_mdio_bus(phydev);

return ret;
}

static int bcm_phy_cable_test_report_trans(int result)
{
switch (result) {
case BCM54XX_ECD_FAULT_TYPE_OK:
return ETHTOOL_A_CABLE_RESULT_CODE_OK;
case BCM54XX_ECD_FAULT_TYPE_OPEN:
return ETHTOOL_A_CABLE_RESULT_CODE_OPEN;
case BCM54XX_ECD_FAULT_TYPE_SAME_SHORT:
return ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT;
case BCM54XX_ECD_FAULT_TYPE_CROSS_SHORT:
return ETHTOOL_A_CABLE_RESULT_CODE_CROSS_SHORT;
case BCM54XX_ECD_FAULT_TYPE_INVALID:
case BCM54XX_ECD_FAULT_TYPE_BUSY:
default:
return ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC;
}
}

static bool bcm_phy_distance_valid(int result)
{
switch (result) {
case BCM54XX_ECD_FAULT_TYPE_OPEN:
case BCM54XX_ECD_FAULT_TYPE_SAME_SHORT:
case BCM54XX_ECD_FAULT_TYPE_CROSS_SHORT:
return true;
}
return false;
}

static int bcm_phy_report_length(struct phy_device *phydev, int pair)
{
int val;

val = __bcm_phy_read_exp(phydev,
BCM54XX_EXP_ECD_PAIR_A_LENGTH_RESULTS + pair);
if (val < 0)
return val;

if (val == BCM54XX_ECD_LENGTH_RESULTS_INVALID)
return 0;

ethnl_cable_test_fault_length(phydev, pair, val);

return 0;
}

static int _bcm_phy_cable_test_get_status(struct phy_device *phydev,
bool *finished, bool is_rdb)
{
int pair_a, pair_b, pair_c, pair_d, ret;

*finished = false;

phy_lock_mdio_bus(phydev);

if (is_rdb) {
ret = __bcm_phy_enable_legacy_access(phydev);
if (ret)
goto out;
}

ret = __bcm_phy_read_exp(phydev, BCM54XX_EXP_ECD_CTRL);
if (ret < 0)
goto out;

if (ret & BCM54XX_ECD_CTRL_IN_PROGRESS) {
ret = 0;
goto out;
}

ret = __bcm_phy_read_exp(phydev, BCM54XX_EXP_ECD_FAULT_TYPE);
if (ret < 0)
goto out;

pair_a = FIELD_GET(BCM54XX_ECD_FAULT_TYPE_PAIR_A_MASK, ret);
pair_b = FIELD_GET(BCM54XX_ECD_FAULT_TYPE_PAIR_B_MASK, ret);
pair_c = FIELD_GET(BCM54XX_ECD_FAULT_TYPE_PAIR_C_MASK, ret);
pair_d = FIELD_GET(BCM54XX_ECD_FAULT_TYPE_PAIR_D_MASK, ret);

ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_A,
bcm_phy_cable_test_report_trans(pair_a));
ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_B,
bcm_phy_cable_test_report_trans(pair_b));
ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_C,
bcm_phy_cable_test_report_trans(pair_c));
ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_D,
bcm_phy_cable_test_report_trans(pair_d));

if (bcm_phy_distance_valid(pair_a))
bcm_phy_report_length(phydev, 0);
if (bcm_phy_distance_valid(pair_b))
bcm_phy_report_length(phydev, 1);
if (bcm_phy_distance_valid(pair_c))
bcm_phy_report_length(phydev, 2);
if (bcm_phy_distance_valid(pair_d))
bcm_phy_report_length(phydev, 3);

ret = 0;
*finished = true;
out:
/* re-enable the RDB access even if there was an error */
if (is_rdb)
ret = __bcm_phy_enable_rdb_access(phydev) ? : ret;

phy_unlock_mdio_bus(phydev);

return ret;
}

int bcm_phy_cable_test_start(struct phy_device *phydev)
{
return _bcm_phy_cable_test_start(phydev, false);
}
EXPORT_SYMBOL_GPL(bcm_phy_cable_test_start);

int bcm_phy_cable_test_get_status(struct phy_device *phydev, bool *finished)
{
return _bcm_phy_cable_test_get_status(phydev, finished, false);
}
EXPORT_SYMBOL_GPL(bcm_phy_cable_test_get_status);

/* We assume that all PHYs which support RDB access can be switched to legacy
* mode. If, in the future, this is not true anymore, we have to re-implement
* this with RDB access.
*/
int bcm_phy_cable_test_start_rdb(struct phy_device *phydev)
{
return _bcm_phy_cable_test_start(phydev, true);
}
EXPORT_SYMBOL_GPL(bcm_phy_cable_test_start_rdb);

int bcm_phy_cable_test_get_status_rdb(struct phy_device *phydev,
bool *finished)
{
return _bcm_phy_cable_test_get_status(phydev, finished, true);
}
EXPORT_SYMBOL_GPL(bcm_phy_cable_test_get_status_rdb);

MODULE_DESCRIPTION("Broadcom PHY Library");
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Broadcom Corporation");
10 changes: 10 additions & 0 deletions drivers/net/phy/bcm-phy-lib.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,12 @@
#define AFE_HPF_TRIM_OTHERS MISC_ADDR(0x3a, 0)


int __bcm_phy_write_exp(struct phy_device *phydev, u16 reg, u16 val);
int __bcm_phy_read_exp(struct phy_device *phydev, u16 reg);
int __bcm_phy_modify_exp(struct phy_device *phydev, u16 reg, u16 mask, u16 set);
int bcm_phy_write_exp(struct phy_device *phydev, u16 reg, u16 val);
int bcm_phy_read_exp(struct phy_device *phydev, u16 reg);
int bcm_phy_modify_exp(struct phy_device *phydev, u16 reg, u16 mask, u16 set);

static inline int bcm_phy_write_exp_sel(struct phy_device *phydev,
u16 reg, u16 val)
Expand Down Expand Up @@ -76,4 +80,10 @@ void bcm_phy_r_rc_cal_reset(struct phy_device *phydev);
int bcm_phy_28nm_a0b0_afe_config_init(struct phy_device *phydev);
int bcm_phy_enable_jumbo(struct phy_device *phydev);

int bcm_phy_cable_test_get_status_rdb(struct phy_device *phydev,
bool *finished);
int bcm_phy_cable_test_start_rdb(struct phy_device *phydev);
int bcm_phy_cable_test_start(struct phy_device *phydev);
int bcm_phy_cable_test_get_status(struct phy_device *phydev, bool *finished);

#endif /* _LINUX_BCM_PHY_LIB_H */
3 changes: 3 additions & 0 deletions drivers/net/phy/bcm54140.c
Original file line number Diff line number Diff line change
Expand Up @@ -831,6 +831,7 @@ static struct phy_driver bcm54140_drivers[] = {
.phy_id = PHY_ID_BCM54140,
.phy_id_mask = BCM54140_PHY_ID_MASK,
.name = "Broadcom BCM54140",
.flags = PHY_POLL_CABLE_TEST,
.features = PHY_GBIT_FEATURES,
.config_init = bcm54140_config_init,
.did_interrupt = bcm54140_did_interrupt,
Expand All @@ -842,6 +843,8 @@ static struct phy_driver bcm54140_drivers[] = {
.soft_reset = genphy_soft_reset,
.get_tunable = bcm54140_get_tunable,
.set_tunable = bcm54140_set_tunable,
.cable_test_start = bcm_phy_cable_test_start_rdb,
.cable_test_get_status = bcm_phy_cable_test_get_status_rdb,
},
};
module_phy_driver(bcm54140_drivers);
Expand Down
Loading

0 comments on commit 87f785e

Please sign in to comment.