summaryrefslogtreecommitdiff
path: root/drivers/net/phy/adin.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/phy/adin.c')
-rw-r--r--drivers/net/phy/adin.c314
1 files changed, 291 insertions, 23 deletions
diff --git a/drivers/net/phy/adin.c b/drivers/net/phy/adin.c
index c7eabe4382fb..7fa713ca8d45 100644
--- a/drivers/net/phy/adin.c
+++ b/drivers/net/phy/adin.c
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: GPL-2.0+
-/**
+/*
* Driver for Analog Devices Industrial Ethernet PHYs
*
* Copyright 2019 Analog Devices Inc.
@@ -8,6 +8,7 @@
#include <linux/bitfield.h>
#include <linux/delay.h>
#include <linux/errno.h>
+#include <linux/ethtool_netlink.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/mii.h>
@@ -23,6 +24,7 @@
#define ADIN1300_PHY_CTRL1 0x0012
#define ADIN1300_AUTO_MDI_EN BIT(10)
#define ADIN1300_MAN_MDIX_EN BIT(9)
+#define ADIN1300_DIAG_CLK_EN BIT(2)
#define ADIN1300_RX_ERR_CNT 0x0014
@@ -66,12 +68,64 @@
#define ADIN1300_EEE_CAP_REG 0x8000
#define ADIN1300_EEE_ADV_REG 0x8001
#define ADIN1300_EEE_LPABLE_REG 0x8002
+
+#define ADIN1300_FLD_EN_REG 0x8E27
+#define ADIN1300_FLD_PCS_ERR_100_EN BIT(7)
+#define ADIN1300_FLD_PCS_ERR_1000_EN BIT(6)
+#define ADIN1300_FLD_SLCR_OUT_STUCK_100_EN BIT(5)
+#define ADIN1300_FLD_SLCR_OUT_STUCK_1000_EN BIT(4)
+#define ADIN1300_FLD_SLCR_IN_ZDET_100_EN BIT(3)
+#define ADIN1300_FLD_SLCR_IN_ZDET_1000_EN BIT(2)
+#define ADIN1300_FLD_SLCR_IN_INVLD_100_EN BIT(1)
+#define ADIN1300_FLD_SLCR_IN_INVLD_1000_EN BIT(0)
+/* These bits are the ones which are enabled by default. */
+#define ADIN1300_FLD_EN_ON \
+ (ADIN1300_FLD_SLCR_OUT_STUCK_100_EN | \
+ ADIN1300_FLD_SLCR_OUT_STUCK_1000_EN | \
+ ADIN1300_FLD_SLCR_IN_ZDET_100_EN | \
+ ADIN1300_FLD_SLCR_IN_ZDET_1000_EN | \
+ ADIN1300_FLD_SLCR_IN_INVLD_1000_EN)
+
#define ADIN1300_CLOCK_STOP_REG 0x9400
#define ADIN1300_LPI_WAKE_ERR_CNT_REG 0xa000
+#define ADIN1300_CDIAG_RUN 0xba1b
+#define ADIN1300_CDIAG_RUN_EN BIT(0)
+
+/*
+ * The XSIM3/2/1 and XSHRT3/2/1 are actually relative.
+ * For CDIAG_DTLD_RSLTS(0) it's ADIN1300_CDIAG_RSLT_XSIM3/2/1
+ * For CDIAG_DTLD_RSLTS(1) it's ADIN1300_CDIAG_RSLT_XSIM3/2/0
+ * For CDIAG_DTLD_RSLTS(2) it's ADIN1300_CDIAG_RSLT_XSIM3/1/0
+ * For CDIAG_DTLD_RSLTS(3) it's ADIN1300_CDIAG_RSLT_XSIM2/1/0
+ */
+#define ADIN1300_CDIAG_DTLD_RSLTS(x) (0xba1d + (x))
+#define ADIN1300_CDIAG_RSLT_BUSY BIT(10)
+#define ADIN1300_CDIAG_RSLT_XSIM3 BIT(9)
+#define ADIN1300_CDIAG_RSLT_XSIM2 BIT(8)
+#define ADIN1300_CDIAG_RSLT_XSIM1 BIT(7)
+#define ADIN1300_CDIAG_RSLT_SIM BIT(6)
+#define ADIN1300_CDIAG_RSLT_XSHRT3 BIT(5)
+#define ADIN1300_CDIAG_RSLT_XSHRT2 BIT(4)
+#define ADIN1300_CDIAG_RSLT_XSHRT1 BIT(3)
+#define ADIN1300_CDIAG_RSLT_SHRT BIT(2)
+#define ADIN1300_CDIAG_RSLT_OPEN BIT(1)
+#define ADIN1300_CDIAG_RSLT_GOOD BIT(0)
+
+#define ADIN1300_CDIAG_FLT_DIST(x) (0xba21 + (x))
+
#define ADIN1300_GE_SOFT_RESET_REG 0xff0c
#define ADIN1300_GE_SOFT_RESET BIT(0)
+#define ADIN1300_GE_CLK_CFG_REG 0xff1f
+#define ADIN1300_GE_CLK_CFG_MASK GENMASK(5, 0)
+#define ADIN1300_GE_CLK_CFG_RCVR_125 BIT(5)
+#define ADIN1300_GE_CLK_CFG_FREE_125 BIT(4)
+#define ADIN1300_GE_CLK_CFG_REF_EN BIT(3)
+#define ADIN1300_GE_CLK_CFG_HRT_RCVR BIT(2)
+#define ADIN1300_GE_CLK_CFG_HRT_FREE BIT(1)
+#define ADIN1300_GE_CLK_CFG_25 BIT(0)
+
#define ADIN1300_GE_RGMII_CFG_REG 0xff23
#define ADIN1300_GE_RGMII_RX_MSK GENMASK(8, 6)
#define ADIN1300_GE_RGMII_RX_SEL(x) \
@@ -106,8 +160,8 @@
/**
* struct adin_cfg_reg_map - map a config value to aregister value
- * @cfg value in device configuration
- * @reg value in the register
+ * @cfg: value in device configuration
+ * @reg: value in the register
*/
struct adin_cfg_reg_map {
int cfg;
@@ -135,9 +189,9 @@ static const struct adin_cfg_reg_map adin_rmii_fifo_depths[] = {
/**
* struct adin_clause45_mmd_map - map to convert Clause 45 regs to Clause 22
- * @devad device address used in Clause 45 access
- * @cl45_regnum register address defined by Clause 45
- * @adin_regnum equivalent register address accessible via Clause 22
+ * @devad: device address used in Clause 45 access
+ * @cl45_regnum: register address defined by Clause 45
+ * @adin_regnum: equivalent register address accessible via Clause 22
*/
struct adin_clause45_mmd_map {
int devad;
@@ -174,7 +228,7 @@ static const struct adin_hw_stat adin_hw_stats[] = {
/**
* struct adin_priv - ADIN PHY driver private data
- * stats statistic counters for the PHY
+ * @stats: statistic counters for the PHY
*/
struct adin_priv {
u64 stats[ARRAY_SIZE(adin_hw_stats)];
@@ -321,10 +375,9 @@ static int adin_set_downshift(struct phy_device *phydev, u8 cnt)
return -E2BIG;
val = FIELD_PREP(ADIN1300_DOWNSPEED_RETRIES_MSK, cnt);
- val |= ADIN1300_LINKING_EN;
rc = phy_modify(phydev, ADIN1300_PHY_CTRL3,
- ADIN1300_LINKING_EN | ADIN1300_DOWNSPEED_RETRIES_MSK,
+ ADIN1300_DOWNSPEED_RETRIES_MSK,
val);
if (rc < 0)
return rc;
@@ -366,10 +419,10 @@ static int adin_set_edpd(struct phy_device *phydev, u16 tx_interval)
switch (tx_interval) {
case 1000: /* 1 second */
- /* fallthrough */
+ fallthrough;
case ETHTOOL_PHY_EDPD_DFLT_TX_MSECS:
val |= ADIN1300_NRG_PD_TX_EN;
- /* fallthrough */
+ fallthrough;
case ETHTOOL_PHY_EDPD_NO_TX:
break;
default:
@@ -381,6 +434,37 @@ static int adin_set_edpd(struct phy_device *phydev, u16 tx_interval)
val);
}
+static int adin_get_fast_down(struct phy_device *phydev, u8 *msecs)
+{
+ int reg;
+
+ reg = phy_read_mmd(phydev, MDIO_MMD_VEND1, ADIN1300_FLD_EN_REG);
+ if (reg < 0)
+ return reg;
+
+ if (reg & ADIN1300_FLD_EN_ON)
+ *msecs = ETHTOOL_PHY_FAST_LINK_DOWN_ON;
+ else
+ *msecs = ETHTOOL_PHY_FAST_LINK_DOWN_OFF;
+
+ return 0;
+}
+
+static int adin_set_fast_down(struct phy_device *phydev, const u8 *msecs)
+{
+ if (*msecs == ETHTOOL_PHY_FAST_LINK_DOWN_ON)
+ return phy_set_bits_mmd(phydev, MDIO_MMD_VEND1,
+ ADIN1300_FLD_EN_REG,
+ ADIN1300_FLD_EN_ON);
+
+ if (*msecs == ETHTOOL_PHY_FAST_LINK_DOWN_OFF)
+ return phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1,
+ ADIN1300_FLD_EN_REG,
+ ADIN1300_FLD_EN_ON);
+
+ return -EINVAL;
+}
+
static int adin_get_tunable(struct phy_device *phydev,
struct ethtool_tunable *tuna, void *data)
{
@@ -389,6 +473,8 @@ static int adin_get_tunable(struct phy_device *phydev,
return adin_get_downshift(phydev, data);
case ETHTOOL_PHY_EDPD:
return adin_get_edpd(phydev, data);
+ case ETHTOOL_PHY_FAST_LINK_DOWN:
+ return adin_get_fast_down(phydev, data);
default:
return -EOPNOTSUPP;
}
@@ -402,11 +488,40 @@ static int adin_set_tunable(struct phy_device *phydev,
return adin_set_downshift(phydev, *(const u8 *)data);
case ETHTOOL_PHY_EDPD:
return adin_set_edpd(phydev, *(const u16 *)data);
+ case ETHTOOL_PHY_FAST_LINK_DOWN:
+ return adin_set_fast_down(phydev, data);
default:
return -EOPNOTSUPP;
}
}
+static int adin_config_clk_out(struct phy_device *phydev)
+{
+ struct device *dev = &phydev->mdio.dev;
+ const char *val = NULL;
+ u8 sel = 0;
+
+ device_property_read_string(dev, "adi,phy-output-clock", &val);
+ if (!val) {
+ /* property not present, do not enable GP_CLK pin */
+ } else if (strcmp(val, "25mhz-reference") == 0) {
+ sel |= ADIN1300_GE_CLK_CFG_25;
+ } else if (strcmp(val, "125mhz-free-running") == 0) {
+ sel |= ADIN1300_GE_CLK_CFG_FREE_125;
+ } else if (strcmp(val, "adaptive-free-running") == 0) {
+ sel |= ADIN1300_GE_CLK_CFG_HRT_FREE;
+ } else {
+ phydev_err(phydev, "invalid adi,phy-output-clock\n");
+ return -EINVAL;
+ }
+
+ if (device_property_read_bool(dev, "adi,phy-output-reference-clock"))
+ sel |= ADIN1300_GE_CLK_CFG_REF_EN;
+
+ return phy_modify_mmd(phydev, MDIO_MMD_VEND1, ADIN1300_GE_CLK_CFG_REG,
+ ADIN1300_GE_CLK_CFG_MASK, sel);
+}
+
static int adin_config_init(struct phy_device *phydev)
{
int rc;
@@ -429,6 +544,10 @@ static int adin_config_init(struct phy_device *phydev)
if (rc < 0)
return rc;
+ rc = adin_config_clk_out(phydev);
+ if (rc < 0)
+ return rc;
+
phydev_dbg(phydev, "PHY is using mode '%s'\n",
phy_modes(phydev->interface));
@@ -445,12 +564,43 @@ static int adin_phy_ack_intr(struct phy_device *phydev)
static int adin_phy_config_intr(struct phy_device *phydev)
{
- if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
- return phy_set_bits(phydev, ADIN1300_INT_MASK_REG,
- ADIN1300_INT_MASK_EN);
+ int err;
+
+ if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+ err = adin_phy_ack_intr(phydev);
+ if (err)
+ return err;
+
+ err = phy_set_bits(phydev, ADIN1300_INT_MASK_REG,
+ ADIN1300_INT_MASK_EN);
+ } else {
+ err = phy_clear_bits(phydev, ADIN1300_INT_MASK_REG,
+ ADIN1300_INT_MASK_EN);
+ if (err)
+ return err;
- return phy_clear_bits(phydev, ADIN1300_INT_MASK_REG,
- ADIN1300_INT_MASK_EN);
+ err = adin_phy_ack_intr(phydev);
+ }
+
+ return err;
+}
+
+static irqreturn_t adin_phy_handle_interrupt(struct phy_device *phydev)
+{
+ int irq_status;
+
+ irq_status = phy_read(phydev, ADIN1300_INT_STATUS_REG);
+ if (irq_status < 0) {
+ phy_error(phydev);
+ return IRQ_NONE;
+ }
+
+ if (!(irq_status & ADIN1300_INT_LINK_STAT_CHNG_EN))
+ return IRQ_NONE;
+
+ phy_trigger_machine(phydev);
+
+ return IRQ_HANDLED;
}
static int adin_cl45_to_adin_reg(struct phy_device *phydev, int devad,
@@ -555,6 +705,14 @@ static int adin_config_aneg(struct phy_device *phydev)
{
int ret;
+ ret = phy_clear_bits(phydev, ADIN1300_PHY_CTRL1, ADIN1300_DIAG_CLK_EN);
+ if (ret < 0)
+ return ret;
+
+ ret = phy_set_bits(phydev, ADIN1300_PHY_CTRL3, ADIN1300_LINKING_EN);
+ if (ret < 0)
+ return ret;
+
ret = adin_config_mdix(phydev);
if (ret)
return ret;
@@ -643,10 +801,8 @@ static void adin_get_strings(struct phy_device *phydev, u8 *data)
{
int i;
- for (i = 0; i < ARRAY_SIZE(adin_hw_stats); i++) {
- strlcpy(&data[i * ETH_GSTRING_LEN],
- adin_hw_stats[i].string, ETH_GSTRING_LEN);
- }
+ for (i = 0; i < ARRAY_SIZE(adin_hw_stats); i++)
+ ethtool_puts(&data, adin_hw_stats[i].string);
}
static int adin_read_mmd_stat_regs(struct phy_device *phydev,
@@ -725,10 +881,117 @@ static int adin_probe(struct phy_device *phydev)
return 0;
}
+static int adin_cable_test_start(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = phy_clear_bits(phydev, ADIN1300_PHY_CTRL3, ADIN1300_LINKING_EN);
+ if (ret < 0)
+ return ret;
+
+ ret = phy_clear_bits(phydev, ADIN1300_PHY_CTRL1, ADIN1300_DIAG_CLK_EN);
+ if (ret < 0)
+ return ret;
+
+ /* wait a bit for the clock to stabilize */
+ msleep(50);
+
+ return phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, ADIN1300_CDIAG_RUN,
+ ADIN1300_CDIAG_RUN_EN);
+}
+
+static int adin_cable_test_report_trans(int result)
+{
+ int mask;
+
+ if (result & ADIN1300_CDIAG_RSLT_GOOD)
+ return ETHTOOL_A_CABLE_RESULT_CODE_OK;
+ if (result & ADIN1300_CDIAG_RSLT_OPEN)
+ return ETHTOOL_A_CABLE_RESULT_CODE_OPEN;
+
+ /* short with other pairs */
+ mask = ADIN1300_CDIAG_RSLT_XSHRT3 |
+ ADIN1300_CDIAG_RSLT_XSHRT2 |
+ ADIN1300_CDIAG_RSLT_XSHRT1;
+ if (result & mask)
+ return ETHTOOL_A_CABLE_RESULT_CODE_CROSS_SHORT;
+
+ if (result & ADIN1300_CDIAG_RSLT_SHRT)
+ return ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT;
+
+ return ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC;
+}
+
+static int adin_cable_test_report_pair(struct phy_device *phydev,
+ unsigned int pair)
+{
+ int fault_rslt;
+ int ret;
+
+ ret = phy_read_mmd(phydev, MDIO_MMD_VEND1,
+ ADIN1300_CDIAG_DTLD_RSLTS(pair));
+ if (ret < 0)
+ return ret;
+
+ fault_rslt = adin_cable_test_report_trans(ret);
+
+ ret = ethnl_cable_test_result(phydev, pair, fault_rslt);
+ if (ret < 0)
+ return ret;
+
+ ret = phy_read_mmd(phydev, MDIO_MMD_VEND1,
+ ADIN1300_CDIAG_FLT_DIST(pair));
+ if (ret < 0)
+ return ret;
+
+ switch (fault_rslt) {
+ case ETHTOOL_A_CABLE_RESULT_CODE_OPEN:
+ case ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT:
+ case ETHTOOL_A_CABLE_RESULT_CODE_CROSS_SHORT:
+ return ethnl_cable_test_fault_length(phydev, pair, ret * 100);
+ default:
+ return 0;
+ }
+}
+
+static int adin_cable_test_report(struct phy_device *phydev)
+{
+ unsigned int pair;
+ int ret;
+
+ for (pair = ETHTOOL_A_CABLE_PAIR_A; pair <= ETHTOOL_A_CABLE_PAIR_D; pair++) {
+ ret = adin_cable_test_report_pair(phydev, pair);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int adin_cable_test_get_status(struct phy_device *phydev,
+ bool *finished)
+{
+ int ret;
+
+ *finished = false;
+
+ ret = phy_read_mmd(phydev, MDIO_MMD_VEND1, ADIN1300_CDIAG_RUN);
+ if (ret < 0)
+ return ret;
+
+ if (ret & ADIN1300_CDIAG_RUN_EN)
+ return 0;
+
+ *finished = true;
+
+ return adin_cable_test_report(phydev);
+}
+
static struct phy_driver adin_driver[] = {
{
PHY_ID_MATCH_MODEL(PHY_ID_ADIN1200),
.name = "ADIN1200",
+ .flags = PHY_POLL_CABLE_TEST,
.probe = adin_probe,
.config_init = adin_config_init,
.soft_reset = adin_soft_reset,
@@ -736,8 +999,8 @@ static struct phy_driver adin_driver[] = {
.read_status = adin_read_status,
.get_tunable = adin_get_tunable,
.set_tunable = adin_set_tunable,
- .ack_interrupt = adin_phy_ack_intr,
.config_intr = adin_phy_config_intr,
+ .handle_interrupt = adin_phy_handle_interrupt,
.get_sset_count = adin_get_sset_count,
.get_strings = adin_get_strings,
.get_stats = adin_get_stats,
@@ -745,10 +1008,13 @@ static struct phy_driver adin_driver[] = {
.suspend = genphy_suspend,
.read_mmd = adin_read_mmd,
.write_mmd = adin_write_mmd,
+ .cable_test_start = adin_cable_test_start,
+ .cable_test_get_status = adin_cable_test_get_status,
},
{
PHY_ID_MATCH_MODEL(PHY_ID_ADIN1300),
.name = "ADIN1300",
+ .flags = PHY_POLL_CABLE_TEST,
.probe = adin_probe,
.config_init = adin_config_init,
.soft_reset = adin_soft_reset,
@@ -756,8 +1022,8 @@ static struct phy_driver adin_driver[] = {
.read_status = adin_read_status,
.get_tunable = adin_get_tunable,
.set_tunable = adin_set_tunable,
- .ack_interrupt = adin_phy_ack_intr,
.config_intr = adin_phy_config_intr,
+ .handle_interrupt = adin_phy_handle_interrupt,
.get_sset_count = adin_get_sset_count,
.get_strings = adin_get_strings,
.get_stats = adin_get_stats,
@@ -765,12 +1031,14 @@ static struct phy_driver adin_driver[] = {
.suspend = genphy_suspend,
.read_mmd = adin_read_mmd,
.write_mmd = adin_write_mmd,
+ .cable_test_start = adin_cable_test_start,
+ .cable_test_get_status = adin_cable_test_get_status,
},
};
module_phy_driver(adin_driver);
-static struct mdio_device_id __maybe_unused adin_tbl[] = {
+static const struct mdio_device_id __maybe_unused adin_tbl[] = {
{ PHY_ID_MATCH_MODEL(PHY_ID_ADIN1200) },
{ PHY_ID_MATCH_MODEL(PHY_ID_ADIN1300) },
{ }