summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/net/phy/bcm-phy-lib.c189
-rw-r--r--drivers/net/phy/bcm-phy-lib.h6
-rw-r--r--include/linux/brcmphy.h52
3 files changed, 247 insertions, 0 deletions
diff --git a/drivers/net/phy/bcm-phy-lib.c b/drivers/net/phy/bcm-phy-lib.c
index 41c728fbcfb2..cb92786e3ded 100644
--- a/drivers/net/phy/bcm-phy-lib.c
+++ b/drivers/net/phy/bcm-phy-lib.c
@@ -4,12 +4,14 @@
*/
#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
@@ -581,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");
diff --git a/drivers/net/phy/bcm-phy-lib.h b/drivers/net/phy/bcm-phy-lib.h
index b35d880220b9..237a8503c9b4 100644
--- a/drivers/net/phy/bcm-phy-lib.h
+++ b/drivers/net/phy/bcm-phy-lib.h
@@ -80,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 */
diff --git a/include/linux/brcmphy.h b/include/linux/brcmphy.h
index 58d0150acc3e..d41624db6de2 100644
--- a/include/linux/brcmphy.h
+++ b/include/linux/brcmphy.h
@@ -119,6 +119,11 @@
#define MII_BCM54XX_RDB_ADDR 0x1e
#define MII_BCM54XX_RDB_DATA 0x1f
+/* legacy access control via rdb/expansion register */
+#define BCM54XX_RDB_REG0087 0x0087
+#define BCM54XX_EXP_REG7E (MII_BCM54XX_EXP_SEL_ER + 0x7E)
+#define BCM54XX_ACCESS_MODE_LEGACY_EN BIT(15)
+
/*
* AUXILIARY CONTROL SHADOW ACCESS REGISTERS. (PHY REG 0x18)
*/
@@ -294,4 +299,51 @@
#define MII_BRCM_CORE_EXPB0 0xB0
#define MII_BRCM_CORE_EXPB1 0xB1
+/* Enhanced Cable Diagnostics */
+#define BCM54XX_RDB_ECD_CTRL 0x2a0
+#define BCM54XX_EXP_ECD_CTRL (MII_BCM54XX_EXP_SEL_ER + 0xc0)
+
+#define BCM54XX_ECD_CTRL_CABLE_TYPE_CAT3 1 /* CAT3 or worse */
+#define BCM54XX_ECD_CTRL_CABLE_TYPE_CAT5 0 /* CAT5 or better */
+#define BCM54XX_ECD_CTRL_CABLE_TYPE_MASK BIT(0) /* cable type */
+#define BCM54XX_ECD_CTRL_INVALID BIT(3) /* invalid result */
+#define BCM54XX_ECD_CTRL_UNIT_CM 0 /* centimeters */
+#define BCM54XX_ECD_CTRL_UNIT_M 1 /* meters */
+#define BCM54XX_ECD_CTRL_UNIT_MASK BIT(10) /* cable length unit */
+#define BCM54XX_ECD_CTRL_IN_PROGRESS BIT(11) /* test in progress */
+#define BCM54XX_ECD_CTRL_BREAK_LINK BIT(12) /* unconnect link
+ * during test
+ */
+#define BCM54XX_ECD_CTRL_CROSS_SHORT_DIS BIT(13) /* disable inter-pair
+ * short check
+ */
+#define BCM54XX_ECD_CTRL_RUN BIT(15) /* run immediate */
+
+#define BCM54XX_RDB_ECD_FAULT_TYPE 0x2a1
+#define BCM54XX_EXP_ECD_FAULT_TYPE (MII_BCM54XX_EXP_SEL_ER + 0xc1)
+#define BCM54XX_ECD_FAULT_TYPE_INVALID 0x0
+#define BCM54XX_ECD_FAULT_TYPE_OK 0x1
+#define BCM54XX_ECD_FAULT_TYPE_OPEN 0x2
+#define BCM54XX_ECD_FAULT_TYPE_SAME_SHORT 0x3 /* short same pair */
+#define BCM54XX_ECD_FAULT_TYPE_CROSS_SHORT 0x4 /* short different pairs */
+#define BCM54XX_ECD_FAULT_TYPE_BUSY 0x9
+#define BCM54XX_ECD_FAULT_TYPE_PAIR_D_MASK GENMASK(3, 0)
+#define BCM54XX_ECD_FAULT_TYPE_PAIR_C_MASK GENMASK(7, 4)
+#define BCM54XX_ECD_FAULT_TYPE_PAIR_B_MASK GENMASK(11, 8)
+#define BCM54XX_ECD_FAULT_TYPE_PAIR_A_MASK GENMASK(15, 12)
+#define BCM54XX_ECD_PAIR_A_LENGTH_RESULTS 0x2a2
+#define BCM54XX_ECD_PAIR_B_LENGTH_RESULTS 0x2a3
+#define BCM54XX_ECD_PAIR_C_LENGTH_RESULTS 0x2a4
+#define BCM54XX_ECD_PAIR_D_LENGTH_RESULTS 0x2a5
+
+#define BCM54XX_RDB_ECD_PAIR_A_LENGTH_RESULTS 0x2a2
+#define BCM54XX_EXP_ECD_PAIR_A_LENGTH_RESULTS (MII_BCM54XX_EXP_SEL_ER + 0xc2)
+#define BCM54XX_RDB_ECD_PAIR_B_LENGTH_RESULTS 0x2a3
+#define BCM54XX_EXP_ECD_PAIR_B_LENGTH_RESULTS (MII_BCM54XX_EXP_SEL_ER + 0xc3)
+#define BCM54XX_RDB_ECD_PAIR_C_LENGTH_RESULTS 0x2a4
+#define BCM54XX_EXP_ECD_PAIR_C_LENGTH_RESULTS (MII_BCM54XX_EXP_SEL_ER + 0xc4)
+#define BCM54XX_RDB_ECD_PAIR_D_LENGTH_RESULTS 0x2a5
+#define BCM54XX_EXP_ECD_PAIR_D_LENGTH_RESULTS (MII_BCM54XX_EXP_SEL_ER + 0xc5)
+#define BCM54XX_ECD_LENGTH_RESULTS_INVALID 0xffff
+
#endif /* _LINUX_BRCMPHY_H */