Centos-kernel-stream-9/drivers/net/phy/microchip_t1.c

1901 lines
52 KiB
C
Raw Normal View History

// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2018 Microchip Technology
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/mii.h>
#include <linux/phy.h>
#include <linux/ethtool.h>
#include <linux/ethtool_netlink.h>
#include <linux/bitfield.h>
#define PHY_ID_LAN87XX 0x0007c150
#define PHY_ID_LAN937X 0x0007c180
#define PHY_ID_LAN887X 0x0007c1f0
/* External Register Control Register */
#define LAN87XX_EXT_REG_CTL (0x14)
#define LAN87XX_EXT_REG_CTL_RD_CTL (0x1000)
#define LAN87XX_EXT_REG_CTL_WR_CTL (0x0800)
#define LAN87XX_REG_BANK_SEL_MASK GENMASK(10, 8)
#define LAN87XX_REG_ADDR_MASK GENMASK(7, 0)
/* External Register Read Data Register */
#define LAN87XX_EXT_REG_RD_DATA (0x15)
/* External Register Write Data Register */
#define LAN87XX_EXT_REG_WR_DATA (0x16)
/* Interrupt Source Register */
#define LAN87XX_INTERRUPT_SOURCE (0x18)
net: phy: lan87xx: change interrupt src of link_up to comm_ready JIRA: https://issues.redhat.com/browse/RHEL-2799 commit 5382033a35227c57a349d74752ad2527780159a9 Author: Arun Ramadoss <arun.ramadoss@microchip.com> Date: Mon Sep 5 20:57:50 2022 +0530 net: phy: lan87xx: change interrupt src of link_up to comm_ready Currently phy link up/down interrupt is enabled using the LAN87xx_INTERRUPT_MASK register. In the lan87xx_read_status function, phy link is determined using the T1_MODE_STAT_REG register comm_ready bit. comm_ready bit is set using the loc_rcvr_status & rem_rcvr_status. Whenever the phy link is up, LAN87xx_INTERRUPT_SOURCE link_up bit is set first but comm_ready bit takes some time to set based on local and remote receiver status. As per the current implementation, interrupt is triggered using link_up but the comm_ready bit is still cleared in the read_status function. So, link is always down. Initially tested with the shared interrupt mechanism with switch and internal phy which is working, but after implementing interrupt controller it is not working. It can fixed either by updating the read_status function to read from LAN87XX_INTERRUPT_SOURCE register or enable the interrupt mask for comm_ready bit. But the validation team recommends the use of comm_ready for link detection. This patch fixes by enabling the comm_ready bit for link_up in the LAN87XX_INTERRUPT_MASK_2 register (MISC Bank) and link_down in LAN87xx_INTERRUPT_MASK register. Fixes: 8a1b415d70b7 ("net: phy: added ethtool master-slave configuration support") Signed-off-by: Arun Ramadoss <arun.ramadoss@microchip.com> Reviewed-by: Andrew Lunn <andrew@lunn.ch> Link: https://lore.kernel.org/r/20220905152750.5079-1-arun.ramadoss@microchip.com Signed-off-by: Paolo Abeni <pabeni@redhat.com> Signed-off-by: Adrien Thierry <athierry@redhat.com>
2023-09-14 14:11:18 +00:00
#define LAN87XX_INTERRUPT_SOURCE_2 (0x08)
/* Interrupt Mask Register */
#define LAN87XX_INTERRUPT_MASK (0x19)
#define LAN87XX_MASK_LINK_UP (0x0004)
#define LAN87XX_MASK_LINK_DOWN (0x0002)
net: phy: lan87xx: change interrupt src of link_up to comm_ready JIRA: https://issues.redhat.com/browse/RHEL-2799 commit 5382033a35227c57a349d74752ad2527780159a9 Author: Arun Ramadoss <arun.ramadoss@microchip.com> Date: Mon Sep 5 20:57:50 2022 +0530 net: phy: lan87xx: change interrupt src of link_up to comm_ready Currently phy link up/down interrupt is enabled using the LAN87xx_INTERRUPT_MASK register. In the lan87xx_read_status function, phy link is determined using the T1_MODE_STAT_REG register comm_ready bit. comm_ready bit is set using the loc_rcvr_status & rem_rcvr_status. Whenever the phy link is up, LAN87xx_INTERRUPT_SOURCE link_up bit is set first but comm_ready bit takes some time to set based on local and remote receiver status. As per the current implementation, interrupt is triggered using link_up but the comm_ready bit is still cleared in the read_status function. So, link is always down. Initially tested with the shared interrupt mechanism with switch and internal phy which is working, but after implementing interrupt controller it is not working. It can fixed either by updating the read_status function to read from LAN87XX_INTERRUPT_SOURCE register or enable the interrupt mask for comm_ready bit. But the validation team recommends the use of comm_ready for link detection. This patch fixes by enabling the comm_ready bit for link_up in the LAN87XX_INTERRUPT_MASK_2 register (MISC Bank) and link_down in LAN87xx_INTERRUPT_MASK register. Fixes: 8a1b415d70b7 ("net: phy: added ethtool master-slave configuration support") Signed-off-by: Arun Ramadoss <arun.ramadoss@microchip.com> Reviewed-by: Andrew Lunn <andrew@lunn.ch> Link: https://lore.kernel.org/r/20220905152750.5079-1-arun.ramadoss@microchip.com Signed-off-by: Paolo Abeni <pabeni@redhat.com> Signed-off-by: Adrien Thierry <athierry@redhat.com>
2023-09-14 14:11:18 +00:00
#define LAN87XX_INTERRUPT_MASK_2 (0x09)
#define LAN87XX_MASK_COMM_RDY BIT(10)
/* MISC Control 1 Register */
#define LAN87XX_CTRL_1 (0x11)
#define LAN87XX_MASK_RGMII_TXC_DLY_EN (0x4000)
#define LAN87XX_MASK_RGMII_RXC_DLY_EN (0x2000)
/* phyaccess nested types */
#define PHYACC_ATTR_MODE_READ 0
#define PHYACC_ATTR_MODE_WRITE 1
#define PHYACC_ATTR_MODE_MODIFY 2
#define PHYACC_ATTR_MODE_POLL 3
#define PHYACC_ATTR_BANK_SMI 0
#define PHYACC_ATTR_BANK_MISC 1
#define PHYACC_ATTR_BANK_PCS 2
#define PHYACC_ATTR_BANK_AFE 3
#define PHYACC_ATTR_BANK_DSP 4
#define PHYACC_ATTR_BANK_MAX 7
/* measurement defines */
#define LAN87XX_CABLE_TEST_OK 0
#define LAN87XX_CABLE_TEST_OPEN 1
#define LAN87XX_CABLE_TEST_SAME_SHORT 2
/* T1 Registers */
#define T1_AFE_PORT_CFG1_REG 0x0B
#define T1_POWER_DOWN_CONTROL_REG 0x1A
#define T1_SLV_FD_MULT_CFG_REG 0x18
#define T1_CDR_CFG_PRE_LOCK_REG 0x05
#define T1_CDR_CFG_POST_LOCK_REG 0x06
#define T1_LCK_STG2_MUFACT_CFG_REG 0x1A
#define T1_LCK_STG3_MUFACT_CFG_REG 0x1B
#define T1_POST_LCK_MUFACT_CFG_REG 0x1C
#define T1_TX_RX_FIFO_CFG_REG 0x02
#define T1_TX_LPF_FIR_CFG_REG 0x55
#define T1_COEF_CLK_PWR_DN_CFG 0x04
#define T1_COEF_RW_CTL_CFG 0x0D
#define T1_SQI_CONFIG_REG 0x2E
#define T1_SQI_CONFIG2_REG 0x4A
#define T1_DCQ_SQI_REG 0xC3
#define T1_DCQ_SQI_MSK GENMASK(3, 1)
#define T1_MDIO_CONTROL2_REG 0x10
#define T1_INTERRUPT_SOURCE_REG 0x18
#define T1_INTERRUPT2_SOURCE_REG 0x08
#define T1_EQ_FD_STG1_FRZ_CFG 0x69
#define T1_EQ_FD_STG2_FRZ_CFG 0x6A
#define T1_EQ_FD_STG3_FRZ_CFG 0x6B
#define T1_EQ_FD_STG4_FRZ_CFG 0x6C
#define T1_EQ_WT_FD_LCK_FRZ_CFG 0x6D
#define T1_PST_EQ_LCK_STG1_FRZ_CFG 0x6E
#define T1_MODE_STAT_REG 0x11
#define T1_LINK_UP_MSK BIT(0)
/* SQI defines */
#define LAN87XX_MAX_SQI 0x07
/* Chiptop registers */
#define LAN887X_PMA_EXT_ABILITY_2 0x12
#define LAN887X_PMA_EXT_ABILITY_2_1000T1 BIT(1)
#define LAN887X_PMA_EXT_ABILITY_2_100T1 BIT(0)
/* DSP 100M registers */
#define LAN887x_CDR_CONFIG1_100 0x0405
#define LAN887x_LOCK1_EQLSR_CONFIG_100 0x0411
#define LAN887x_SLV_HD_MUFAC_CONFIG_100 0x0417
#define LAN887x_PLOCK_MUFAC_CONFIG_100 0x041c
#define LAN887x_PROT_DISABLE_100 0x0425
#define LAN887x_KF_LOOP_SAT_CONFIG_100 0x0454
/* DSP 1000M registers */
#define LAN887X_LOCK1_EQLSR_CONFIG 0x0811
#define LAN887X_LOCK3_EQLSR_CONFIG 0x0813
#define LAN887X_PROT_DISABLE 0x0825
#define LAN887X_FFE_GAIN6 0x0843
#define LAN887X_FFE_GAIN7 0x0844
#define LAN887X_FFE_GAIN8 0x0845
#define LAN887X_FFE_GAIN9 0x0846
#define LAN887X_ECHO_DELAY_CONFIG 0x08ec
#define LAN887X_FFE_MAX_CONFIG 0x08ee
/* PCS 1000M registers */
#define LAN887X_SCR_CONFIG_3 0x8043
#define LAN887X_INFO_FLD_CONFIG_5 0x8048
/* T1 afe registers */
#define LAN887X_ZQCAL_CONTROL_1 0x8080
#define LAN887X_AFE_PORT_TESTBUS_CTRL2 0x8089
#define LAN887X_AFE_PORT_TESTBUS_CTRL4 0x808b
#define LAN887X_AFE_PORT_TESTBUS_CTRL6 0x808d
#define LAN887X_TX_AMPLT_1000T1_REG 0x80b0
#define LAN887X_INIT_COEFF_DFE1_100 0x0422
/* PMA registers */
#define LAN887X_DSP_PMA_CONTROL 0x810e
#define LAN887X_DSP_PMA_CONTROL_LNK_SYNC BIT(4)
/* PCS 100M registers */
#define LAN887X_IDLE_ERR_TIMER_WIN 0x8204
#define LAN887X_IDLE_ERR_CNT_THRESH 0x8213
/* Misc registers */
#define LAN887X_REG_REG26 0x001a
#define LAN887X_REG_REG26_HW_INIT_SEQ_EN BIT(8)
/* Mis registers */
#define LAN887X_MIS_CFG_REG0 0xa00
#define LAN887X_MIS_CFG_REG0_RCLKOUT_DIS BIT(5)
#define LAN887X_MIS_CFG_REG0_MAC_MODE_SEL GENMASK(1, 0)
#define LAN887X_MAC_MODE_RGMII 0x01
#define LAN887X_MAC_MODE_SGMII 0x03
#define LAN887X_MIS_DLL_CFG_REG0 0xa01
#define LAN887X_MIS_DLL_CFG_REG1 0xa02
#define LAN887X_MIS_DLL_DELAY_EN BIT(15)
#define LAN887X_MIS_DLL_EN BIT(0)
#define LAN887X_MIS_DLL_CONF (LAN887X_MIS_DLL_DELAY_EN |\
LAN887X_MIS_DLL_EN)
#define LAN887X_MIS_CFG_REG2 0xa03
#define LAN887X_MIS_CFG_REG2_FE_LPBK_EN BIT(2)
#define LAN887X_MIS_PKT_STAT_REG0 0xa06
#define LAN887X_MIS_PKT_STAT_REG1 0xa07
#define LAN887X_MIS_PKT_STAT_REG3 0xa09
#define LAN887X_MIS_PKT_STAT_REG4 0xa0a
#define LAN887X_MIS_PKT_STAT_REG5 0xa0b
#define LAN887X_MIS_PKT_STAT_REG6 0xa0c
/* Chiptop common registers */
#define LAN887X_COMMON_LED3_LED2 0xc05
#define LAN887X_COMMON_LED2_MODE_SEL_MASK GENMASK(4, 0)
#define LAN887X_LED_LINK_ACT_ANY_SPEED 0x0
/* MX chip top registers */
#define LAN887X_CHIP_HARD_RST 0xf03e
#define LAN887X_CHIP_HARD_RST_RESET BIT(0)
#define LAN887X_CHIP_SOFT_RST 0xf03f
#define LAN887X_CHIP_SOFT_RST_RESET BIT(0)
#define LAN887X_SGMII_CTL 0xf01a
#define LAN887X_SGMII_CTL_SGMII_MUX_EN BIT(0)
#define LAN887X_SGMII_PCS_CFG 0xf034
#define LAN887X_SGMII_PCS_CFG_PCS_ENA BIT(9)
#define LAN887X_EFUSE_READ_DAT9 0xf209
#define LAN887X_EFUSE_READ_DAT9_SGMII_DIS BIT(9)
#define LAN887X_EFUSE_READ_DAT9_MAC_MODE GENMASK(1, 0)
#define LAN887X_CALIB_CONFIG_100 0x437
#define LAN887X_CALIB_CONFIG_100_CBL_DIAG_USE_LOCAL_SMPL BIT(5)
#define LAN887X_CALIB_CONFIG_100_CBL_DIAG_STB_SYNC_MODE BIT(4)
#define LAN887X_CALIB_CONFIG_100_CBL_DIAG_CLK_ALGN_MODE BIT(3)
#define LAN887X_CALIB_CONFIG_100_VAL \
(LAN887X_CALIB_CONFIG_100_CBL_DIAG_CLK_ALGN_MODE |\
LAN887X_CALIB_CONFIG_100_CBL_DIAG_STB_SYNC_MODE |\
LAN887X_CALIB_CONFIG_100_CBL_DIAG_USE_LOCAL_SMPL)
#define LAN887X_MAX_PGA_GAIN_100 0x44f
#define LAN887X_MIN_PGA_GAIN_100 0x450
#define LAN887X_START_CBL_DIAG_100 0x45a
#define LAN887X_CBL_DIAG_DONE BIT(1)
#define LAN887X_CBL_DIAG_START BIT(0)
#define LAN887X_CBL_DIAG_STOP 0x0
#define LAN887X_CBL_DIAG_TDR_THRESH_100 0x45b
#define LAN887X_CBL_DIAG_AGC_THRESH_100 0x45c
#define LAN887X_CBL_DIAG_MIN_WAIT_CONFIG_100 0x45d
#define LAN887X_CBL_DIAG_MAX_WAIT_CONFIG_100 0x45e
#define LAN887X_CBL_DIAG_CYC_CONFIG_100 0x45f
#define LAN887X_CBL_DIAG_TX_PULSE_CONFIG_100 0x460
#define LAN887X_CBL_DIAG_MIN_PGA_GAIN_100 0x462
#define LAN887X_CBL_DIAG_AGC_GAIN_100 0x497
#define LAN887X_CBL_DIAG_POS_PEAK_VALUE_100 0x499
#define LAN887X_CBL_DIAG_NEG_PEAK_VALUE_100 0x49a
#define LAN887X_CBL_DIAG_POS_PEAK_TIME_100 0x49c
#define LAN887X_CBL_DIAG_NEG_PEAK_TIME_100 0x49d
#define MICROCHIP_CABLE_NOISE_MARGIN 20
#define MICROCHIP_CABLE_TIME_MARGIN 89
#define MICROCHIP_CABLE_MIN_TIME_DIFF 96
#define MICROCHIP_CABLE_MAX_TIME_DIFF \
(MICROCHIP_CABLE_MIN_TIME_DIFF + MICROCHIP_CABLE_TIME_MARGIN)
#define DRIVER_AUTHOR "Nisar Sayed <nisar.sayed@microchip.com>"
#define DRIVER_DESC "Microchip LAN87XX/LAN937x/LAN887x T1 PHY driver"
/* TEST_MODE_NORMAL: Non-hybrid results to calculate cable status(open/short/ok)
* TEST_MODE_HYBRID: Hybrid results to calculate distance to fault
*/
enum cable_diag_mode {
TEST_MODE_NORMAL,
TEST_MODE_HYBRID
};
/* CD_TEST_INIT: Cable test is initated
* CD_TEST_DONE: Cable test is done
*/
enum cable_diag_state {
CD_TEST_INIT,
CD_TEST_DONE
};
struct access_ereg_val {
u8 mode;
u8 bank;
u8 offset;
u16 val;
u16 mask;
};
struct lan887x_hw_stat {
const char *string;
u8 mmd;
u16 reg;
u8 bits;
};
static const struct lan887x_hw_stat lan887x_hw_stats[] = {
{ "TX Good Count", MDIO_MMD_VEND1, LAN887X_MIS_PKT_STAT_REG0, 14},
{ "RX Good Count", MDIO_MMD_VEND1, LAN887X_MIS_PKT_STAT_REG1, 14},
{ "RX ERR Count detected by PCS", MDIO_MMD_VEND1, LAN887X_MIS_PKT_STAT_REG3, 16},
{ "TX CRC ERR Count", MDIO_MMD_VEND1, LAN887X_MIS_PKT_STAT_REG4, 8},
{ "RX CRC ERR Count", MDIO_MMD_VEND1, LAN887X_MIS_PKT_STAT_REG5, 8},
{ "RX ERR Count for SGMII MII2GMII", MDIO_MMD_VEND1, LAN887X_MIS_PKT_STAT_REG6, 8},
};
struct lan887x_regwr_map {
u8 mmd;
u16 reg;
u16 val;
};
struct lan887x_priv {
u64 stats[ARRAY_SIZE(lan887x_hw_stats)];
};
static int lan937x_dsp_workaround(struct phy_device *phydev, u16 ereg, u8 bank)
{
u8 prev_bank;
int rc = 0;
u16 val;
mutex_lock(&phydev->lock);
/* Read previous selected bank */
rc = phy_read(phydev, LAN87XX_EXT_REG_CTL);
if (rc < 0)
goto out_unlock;
/* store the prev_bank */
prev_bank = FIELD_GET(LAN87XX_REG_BANK_SEL_MASK, rc);
if (bank != prev_bank && bank == PHYACC_ATTR_BANK_DSP) {
val = ereg & ~LAN87XX_REG_ADDR_MASK;
val &= ~LAN87XX_EXT_REG_CTL_WR_CTL;
val |= LAN87XX_EXT_REG_CTL_RD_CTL;
/* access twice for DSP bank change,dummy access */
rc = phy_write(phydev, LAN87XX_EXT_REG_CTL, val);
}
out_unlock:
mutex_unlock(&phydev->lock);
return rc;
}
static int access_ereg(struct phy_device *phydev, u8 mode, u8 bank,
u8 offset, u16 val)
{
u16 ereg = 0;
int rc = 0;
if (mode > PHYACC_ATTR_MODE_WRITE || bank > PHYACC_ATTR_BANK_MAX)
return -EINVAL;
if (bank == PHYACC_ATTR_BANK_SMI) {
if (mode == PHYACC_ATTR_MODE_WRITE)
rc = phy_write(phydev, offset, val);
else
rc = phy_read(phydev, offset);
return rc;
}
if (mode == PHYACC_ATTR_MODE_WRITE) {
ereg = LAN87XX_EXT_REG_CTL_WR_CTL;
rc = phy_write(phydev, LAN87XX_EXT_REG_WR_DATA, val);
if (rc < 0)
return rc;
} else {
ereg = LAN87XX_EXT_REG_CTL_RD_CTL;
}
ereg |= (bank << 8) | offset;
/* DSP bank access workaround for lan937x */
if (phydev->phy_id == PHY_ID_LAN937X) {
rc = lan937x_dsp_workaround(phydev, ereg, bank);
if (rc < 0)
return rc;
}
rc = phy_write(phydev, LAN87XX_EXT_REG_CTL, ereg);
if (rc < 0)
return rc;
if (mode == PHYACC_ATTR_MODE_READ)
rc = phy_read(phydev, LAN87XX_EXT_REG_RD_DATA);
return rc;
}
static int access_ereg_modify_changed(struct phy_device *phydev,
u8 bank, u8 offset, u16 val, u16 mask)
{
int new = 0, rc = 0;
if (bank > PHYACC_ATTR_BANK_MAX)
return -EINVAL;
rc = access_ereg(phydev, PHYACC_ATTR_MODE_READ, bank, offset, val);
if (rc < 0)
return rc;
new = val | (rc & (mask ^ 0xFFFF));
rc = access_ereg(phydev, PHYACC_ATTR_MODE_WRITE, bank, offset, new);
return rc;
}
static int access_smi_poll_timeout(struct phy_device *phydev,
u8 offset, u16 mask, u16 clr)
{
int val;
return phy_read_poll_timeout(phydev, offset, val, (val & mask) == clr,
150, 30000, true);
}
static int lan87xx_config_rgmii_delay(struct phy_device *phydev)
{
int rc;
if (!phy_interface_is_rgmii(phydev))
return 0;
rc = access_ereg(phydev, PHYACC_ATTR_MODE_READ,
PHYACC_ATTR_BANK_MISC, LAN87XX_CTRL_1, 0);
if (rc < 0)
return rc;
switch (phydev->interface) {
case PHY_INTERFACE_MODE_RGMII:
rc &= ~LAN87XX_MASK_RGMII_TXC_DLY_EN;
rc &= ~LAN87XX_MASK_RGMII_RXC_DLY_EN;
break;
case PHY_INTERFACE_MODE_RGMII_ID:
rc |= LAN87XX_MASK_RGMII_TXC_DLY_EN;
rc |= LAN87XX_MASK_RGMII_RXC_DLY_EN;
break;
case PHY_INTERFACE_MODE_RGMII_RXID:
rc &= ~LAN87XX_MASK_RGMII_TXC_DLY_EN;
rc |= LAN87XX_MASK_RGMII_RXC_DLY_EN;
break;
case PHY_INTERFACE_MODE_RGMII_TXID:
rc |= LAN87XX_MASK_RGMII_TXC_DLY_EN;
rc &= ~LAN87XX_MASK_RGMII_RXC_DLY_EN;
break;
default:
return 0;
}
return access_ereg(phydev, PHYACC_ATTR_MODE_WRITE,
PHYACC_ATTR_BANK_MISC, LAN87XX_CTRL_1, rc);
}
net: phy: microchip: run phy initialization during each link update JIRA: https://issues.redhat.com/browse/RHEL-2799 commit d7bf56e0c591e189141430dc8bf4a1b4d0f117a2 Author: Rakesh Sankaranarayanan <rakesh.sankaranarayanan@microchip.com> Date: Fri Jan 20 16:17:33 2023 +0530 net: phy: microchip: run phy initialization during each link update PHY initialization is supposed to run on every mode changes. "lan87xx_config_aneg()" verifies every mode change using "phy_modify_changed()" function. Earlier code had phy_modify_changed() followed by genphy_soft_reset. But soft_reset resets all the pre-configured register values to default state, and lost all the initialization done. With this reason gen_phy_reset was removed. But it need to go through init sequence each time the mode changed. Update lan87xx_config_aneg() to invoke phy_init once successful mode update is detected. PHY init sequence added in lan87xx_phy_init() have slave init commands executed every time. Update the init sequence to run slave init only if phydev is in slave mode. Test setup contains LAN9370 EVB connected to SAMA5D3 (Running DSA), and issue can be reproduced by connecting link to any of the available ports after SAMA5D3 boot-up. With this issue, port will fail to update link state. But once the SAMA5D3 is reset with LAN9370 link in connected state itself, on boot-up link state will be reported as UP. But Again after some time, if link is moved to DOWN state, it will not get reported. Signed-off-by: Rakesh Sankaranarayanan <rakesh.sankaranarayanan@microchip.com> Reviewed-by: Andrew Lunn <andrew@lunn.ch> Link: https://lore.kernel.org/r/20230120104733.724701-1-rakesh.sankaranarayanan@microchip.com Signed-off-by: Jakub Kicinski <kuba@kernel.org> Signed-off-by: Adrien Thierry <athierry@redhat.com>
2023-09-14 14:11:39 +00:00
static int lan87xx_phy_init_cmd(struct phy_device *phydev,
const struct access_ereg_val *cmd_seq, int cnt)
{
int ret, i;
for (i = 0; i < cnt; i++) {
if (cmd_seq[i].mode == PHYACC_ATTR_MODE_POLL &&
cmd_seq[i].bank == PHYACC_ATTR_BANK_SMI) {
ret = access_smi_poll_timeout(phydev,
cmd_seq[i].offset,
cmd_seq[i].val,
cmd_seq[i].mask);
} else {
ret = access_ereg(phydev, cmd_seq[i].mode,
cmd_seq[i].bank, cmd_seq[i].offset,
cmd_seq[i].val);
}
if (ret < 0)
return ret;
}
return ret;
}
static int lan87xx_phy_init(struct phy_device *phydev)
{
net: phy: microchip: run phy initialization during each link update JIRA: https://issues.redhat.com/browse/RHEL-2799 commit d7bf56e0c591e189141430dc8bf4a1b4d0f117a2 Author: Rakesh Sankaranarayanan <rakesh.sankaranarayanan@microchip.com> Date: Fri Jan 20 16:17:33 2023 +0530 net: phy: microchip: run phy initialization during each link update PHY initialization is supposed to run on every mode changes. "lan87xx_config_aneg()" verifies every mode change using "phy_modify_changed()" function. Earlier code had phy_modify_changed() followed by genphy_soft_reset. But soft_reset resets all the pre-configured register values to default state, and lost all the initialization done. With this reason gen_phy_reset was removed. But it need to go through init sequence each time the mode changed. Update lan87xx_config_aneg() to invoke phy_init once successful mode update is detected. PHY init sequence added in lan87xx_phy_init() have slave init commands executed every time. Update the init sequence to run slave init only if phydev is in slave mode. Test setup contains LAN9370 EVB connected to SAMA5D3 (Running DSA), and issue can be reproduced by connecting link to any of the available ports after SAMA5D3 boot-up. With this issue, port will fail to update link state. But once the SAMA5D3 is reset with LAN9370 link in connected state itself, on boot-up link state will be reported as UP. But Again after some time, if link is moved to DOWN state, it will not get reported. Signed-off-by: Rakesh Sankaranarayanan <rakesh.sankaranarayanan@microchip.com> Reviewed-by: Andrew Lunn <andrew@lunn.ch> Link: https://lore.kernel.org/r/20230120104733.724701-1-rakesh.sankaranarayanan@microchip.com Signed-off-by: Jakub Kicinski <kuba@kernel.org> Signed-off-by: Adrien Thierry <athierry@redhat.com>
2023-09-14 14:11:39 +00:00
static const struct access_ereg_val hw_init[] = {
/* TXPD/TXAMP6 Configs */
{ PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_AFE,
T1_AFE_PORT_CFG1_REG, 0x002D, 0 },
/* HW_Init Hi and Force_ED */
{ PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_SMI,
T1_POWER_DOWN_CONTROL_REG, 0x0308, 0 },
net: phy: microchip: run phy initialization during each link update JIRA: https://issues.redhat.com/browse/RHEL-2799 commit d7bf56e0c591e189141430dc8bf4a1b4d0f117a2 Author: Rakesh Sankaranarayanan <rakesh.sankaranarayanan@microchip.com> Date: Fri Jan 20 16:17:33 2023 +0530 net: phy: microchip: run phy initialization during each link update PHY initialization is supposed to run on every mode changes. "lan87xx_config_aneg()" verifies every mode change using "phy_modify_changed()" function. Earlier code had phy_modify_changed() followed by genphy_soft_reset. But soft_reset resets all the pre-configured register values to default state, and lost all the initialization done. With this reason gen_phy_reset was removed. But it need to go through init sequence each time the mode changed. Update lan87xx_config_aneg() to invoke phy_init once successful mode update is detected. PHY init sequence added in lan87xx_phy_init() have slave init commands executed every time. Update the init sequence to run slave init only if phydev is in slave mode. Test setup contains LAN9370 EVB connected to SAMA5D3 (Running DSA), and issue can be reproduced by connecting link to any of the available ports after SAMA5D3 boot-up. With this issue, port will fail to update link state. But once the SAMA5D3 is reset with LAN9370 link in connected state itself, on boot-up link state will be reported as UP. But Again after some time, if link is moved to DOWN state, it will not get reported. Signed-off-by: Rakesh Sankaranarayanan <rakesh.sankaranarayanan@microchip.com> Reviewed-by: Andrew Lunn <andrew@lunn.ch> Link: https://lore.kernel.org/r/20230120104733.724701-1-rakesh.sankaranarayanan@microchip.com Signed-off-by: Jakub Kicinski <kuba@kernel.org> Signed-off-by: Adrien Thierry <athierry@redhat.com>
2023-09-14 14:11:39 +00:00
};
static const struct access_ereg_val slave_init[] = {
/* Equalizer Full Duplex Freeze - T1 Slave */
{ PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
T1_EQ_FD_STG1_FRZ_CFG, 0x0002, 0 },
{ PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
T1_EQ_FD_STG2_FRZ_CFG, 0x0002, 0 },
{ PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
T1_EQ_FD_STG3_FRZ_CFG, 0x0002, 0 },
{ PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
T1_EQ_FD_STG4_FRZ_CFG, 0x0002, 0 },
{ PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
T1_EQ_WT_FD_LCK_FRZ_CFG, 0x0002, 0 },
{ PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
T1_PST_EQ_LCK_STG1_FRZ_CFG, 0x0002, 0 },
net: phy: microchip: run phy initialization during each link update JIRA: https://issues.redhat.com/browse/RHEL-2799 commit d7bf56e0c591e189141430dc8bf4a1b4d0f117a2 Author: Rakesh Sankaranarayanan <rakesh.sankaranarayanan@microchip.com> Date: Fri Jan 20 16:17:33 2023 +0530 net: phy: microchip: run phy initialization during each link update PHY initialization is supposed to run on every mode changes. "lan87xx_config_aneg()" verifies every mode change using "phy_modify_changed()" function. Earlier code had phy_modify_changed() followed by genphy_soft_reset. But soft_reset resets all the pre-configured register values to default state, and lost all the initialization done. With this reason gen_phy_reset was removed. But it need to go through init sequence each time the mode changed. Update lan87xx_config_aneg() to invoke phy_init once successful mode update is detected. PHY init sequence added in lan87xx_phy_init() have slave init commands executed every time. Update the init sequence to run slave init only if phydev is in slave mode. Test setup contains LAN9370 EVB connected to SAMA5D3 (Running DSA), and issue can be reproduced by connecting link to any of the available ports after SAMA5D3 boot-up. With this issue, port will fail to update link state. But once the SAMA5D3 is reset with LAN9370 link in connected state itself, on boot-up link state will be reported as UP. But Again after some time, if link is moved to DOWN state, it will not get reported. Signed-off-by: Rakesh Sankaranarayanan <rakesh.sankaranarayanan@microchip.com> Reviewed-by: Andrew Lunn <andrew@lunn.ch> Link: https://lore.kernel.org/r/20230120104733.724701-1-rakesh.sankaranarayanan@microchip.com Signed-off-by: Jakub Kicinski <kuba@kernel.org> Signed-off-by: Adrien Thierry <athierry@redhat.com>
2023-09-14 14:11:39 +00:00
};
static const struct access_ereg_val phy_init[] = {
/* Slave Full Duplex Multi Configs */
{ PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
T1_SLV_FD_MULT_CFG_REG, 0x0D53, 0 },
/* CDR Pre and Post Lock Configs */
{ PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
T1_CDR_CFG_PRE_LOCK_REG, 0x0AB2, 0 },
{ PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
T1_CDR_CFG_POST_LOCK_REG, 0x0AB3, 0 },
/* Lock Stage 2-3 Multi Factor Config */
{ PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
T1_LCK_STG2_MUFACT_CFG_REG, 0x0AEA, 0 },
{ PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
T1_LCK_STG3_MUFACT_CFG_REG, 0x0AEB, 0 },
{ PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
T1_POST_LCK_MUFACT_CFG_REG, 0x0AEB, 0 },
/* Pointer delay */
{ PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
T1_TX_RX_FIFO_CFG_REG, 0x1C00, 0 },
/* Tx iir edits */
{ PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
T1_TX_LPF_FIR_CFG_REG, 0x1000, 0 },
{ PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
T1_TX_LPF_FIR_CFG_REG, 0x1861, 0 },
{ PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
T1_TX_LPF_FIR_CFG_REG, 0x1061, 0 },
{ PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
T1_TX_LPF_FIR_CFG_REG, 0x1922, 0 },
{ PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
T1_TX_LPF_FIR_CFG_REG, 0x1122, 0 },
{ PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
T1_TX_LPF_FIR_CFG_REG, 0x1983, 0 },
{ PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
T1_TX_LPF_FIR_CFG_REG, 0x1183, 0 },
{ PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
T1_TX_LPF_FIR_CFG_REG, 0x1944, 0 },
{ PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
T1_TX_LPF_FIR_CFG_REG, 0x1144, 0 },
{ PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
T1_TX_LPF_FIR_CFG_REG, 0x18c5, 0 },
{ PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
T1_TX_LPF_FIR_CFG_REG, 0x10c5, 0 },
{ PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
T1_TX_LPF_FIR_CFG_REG, 0x1846, 0 },
{ PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
T1_TX_LPF_FIR_CFG_REG, 0x1046, 0 },
{ PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
T1_TX_LPF_FIR_CFG_REG, 0x1807, 0 },
{ PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
T1_TX_LPF_FIR_CFG_REG, 0x1007, 0 },
{ PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
T1_TX_LPF_FIR_CFG_REG, 0x1808, 0 },
{ PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
T1_TX_LPF_FIR_CFG_REG, 0x1008, 0 },
{ PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
T1_TX_LPF_FIR_CFG_REG, 0x1809, 0 },
{ PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
T1_TX_LPF_FIR_CFG_REG, 0x1009, 0 },
{ PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
T1_TX_LPF_FIR_CFG_REG, 0x180A, 0 },
{ PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
T1_TX_LPF_FIR_CFG_REG, 0x100A, 0 },
{ PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
T1_TX_LPF_FIR_CFG_REG, 0x180B, 0 },
{ PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
T1_TX_LPF_FIR_CFG_REG, 0x100B, 0 },
{ PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
T1_TX_LPF_FIR_CFG_REG, 0x180C, 0 },
{ PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
T1_TX_LPF_FIR_CFG_REG, 0x100C, 0 },
{ PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
T1_TX_LPF_FIR_CFG_REG, 0x180D, 0 },
{ PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
T1_TX_LPF_FIR_CFG_REG, 0x100D, 0 },
{ PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
T1_TX_LPF_FIR_CFG_REG, 0x180E, 0 },
{ PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
T1_TX_LPF_FIR_CFG_REG, 0x100E, 0 },
{ PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
T1_TX_LPF_FIR_CFG_REG, 0x180F, 0 },
{ PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
T1_TX_LPF_FIR_CFG_REG, 0x100F, 0 },
{ PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
T1_TX_LPF_FIR_CFG_REG, 0x1810, 0 },
{ PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
T1_TX_LPF_FIR_CFG_REG, 0x1010, 0 },
{ PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
T1_TX_LPF_FIR_CFG_REG, 0x1811, 0 },
{ PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
T1_TX_LPF_FIR_CFG_REG, 0x1011, 0 },
{ PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
T1_TX_LPF_FIR_CFG_REG, 0x1000, 0 },
/* Setup SQI measurement */
{ PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
T1_COEF_CLK_PWR_DN_CFG, 0x16d6, 0 },
/* SQI enable */
{ PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
T1_SQI_CONFIG_REG, 0x9572, 0 },
/* SQI select mode 5 */
{ PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
T1_SQI_CONFIG2_REG, 0x0001, 0 },
/* Throws the first SQI reading */
{ PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP,
T1_COEF_RW_CTL_CFG, 0x0301, 0 },
{ PHYACC_ATTR_MODE_READ, PHYACC_ATTR_BANK_DSP,
T1_DCQ_SQI_REG, 0, 0 },
/* Flag LPS and WUR as idle errors */
{ PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_SMI,
T1_MDIO_CONTROL2_REG, 0x0014, 0 },
/* HW_Init toggle, undo force ED, TXPD off */
{ PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_SMI,
T1_POWER_DOWN_CONTROL_REG, 0x0200, 0 },
/* Reset PCS to trigger hardware initialization */
{ PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_SMI,
T1_MDIO_CONTROL2_REG, 0x0094, 0 },
/* Poll till Hardware is initialized */
{ PHYACC_ATTR_MODE_POLL, PHYACC_ATTR_BANK_SMI,
T1_MDIO_CONTROL2_REG, 0x0080, 0 },
/* Tx AMP - 0x06 */
{ PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_AFE,
T1_AFE_PORT_CFG1_REG, 0x000C, 0 },
/* Read INTERRUPT_SOURCE Register */
{ PHYACC_ATTR_MODE_READ, PHYACC_ATTR_BANK_SMI,
T1_INTERRUPT_SOURCE_REG, 0, 0 },
/* Read INTERRUPT_SOURCE Register */
{ PHYACC_ATTR_MODE_READ, PHYACC_ATTR_BANK_MISC,
T1_INTERRUPT2_SOURCE_REG, 0, 0 },
/* HW_Init Hi */
{ PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_SMI,
T1_POWER_DOWN_CONTROL_REG, 0x0300, 0 },
};
net: phy: microchip: run phy initialization during each link update JIRA: https://issues.redhat.com/browse/RHEL-2799 commit d7bf56e0c591e189141430dc8bf4a1b4d0f117a2 Author: Rakesh Sankaranarayanan <rakesh.sankaranarayanan@microchip.com> Date: Fri Jan 20 16:17:33 2023 +0530 net: phy: microchip: run phy initialization during each link update PHY initialization is supposed to run on every mode changes. "lan87xx_config_aneg()" verifies every mode change using "phy_modify_changed()" function. Earlier code had phy_modify_changed() followed by genphy_soft_reset. But soft_reset resets all the pre-configured register values to default state, and lost all the initialization done. With this reason gen_phy_reset was removed. But it need to go through init sequence each time the mode changed. Update lan87xx_config_aneg() to invoke phy_init once successful mode update is detected. PHY init sequence added in lan87xx_phy_init() have slave init commands executed every time. Update the init sequence to run slave init only if phydev is in slave mode. Test setup contains LAN9370 EVB connected to SAMA5D3 (Running DSA), and issue can be reproduced by connecting link to any of the available ports after SAMA5D3 boot-up. With this issue, port will fail to update link state. But once the SAMA5D3 is reset with LAN9370 link in connected state itself, on boot-up link state will be reported as UP. But Again after some time, if link is moved to DOWN state, it will not get reported. Signed-off-by: Rakesh Sankaranarayanan <rakesh.sankaranarayanan@microchip.com> Reviewed-by: Andrew Lunn <andrew@lunn.ch> Link: https://lore.kernel.org/r/20230120104733.724701-1-rakesh.sankaranarayanan@microchip.com Signed-off-by: Jakub Kicinski <kuba@kernel.org> Signed-off-by: Adrien Thierry <athierry@redhat.com>
2023-09-14 14:11:39 +00:00
int rc;
/* phy Soft reset */
rc = genphy_soft_reset(phydev);
if (rc < 0)
return rc;
/* PHY Initialization */
net: phy: microchip: run phy initialization during each link update JIRA: https://issues.redhat.com/browse/RHEL-2799 commit d7bf56e0c591e189141430dc8bf4a1b4d0f117a2 Author: Rakesh Sankaranarayanan <rakesh.sankaranarayanan@microchip.com> Date: Fri Jan 20 16:17:33 2023 +0530 net: phy: microchip: run phy initialization during each link update PHY initialization is supposed to run on every mode changes. "lan87xx_config_aneg()" verifies every mode change using "phy_modify_changed()" function. Earlier code had phy_modify_changed() followed by genphy_soft_reset. But soft_reset resets all the pre-configured register values to default state, and lost all the initialization done. With this reason gen_phy_reset was removed. But it need to go through init sequence each time the mode changed. Update lan87xx_config_aneg() to invoke phy_init once successful mode update is detected. PHY init sequence added in lan87xx_phy_init() have slave init commands executed every time. Update the init sequence to run slave init only if phydev is in slave mode. Test setup contains LAN9370 EVB connected to SAMA5D3 (Running DSA), and issue can be reproduced by connecting link to any of the available ports after SAMA5D3 boot-up. With this issue, port will fail to update link state. But once the SAMA5D3 is reset with LAN9370 link in connected state itself, on boot-up link state will be reported as UP. But Again after some time, if link is moved to DOWN state, it will not get reported. Signed-off-by: Rakesh Sankaranarayanan <rakesh.sankaranarayanan@microchip.com> Reviewed-by: Andrew Lunn <andrew@lunn.ch> Link: https://lore.kernel.org/r/20230120104733.724701-1-rakesh.sankaranarayanan@microchip.com Signed-off-by: Jakub Kicinski <kuba@kernel.org> Signed-off-by: Adrien Thierry <athierry@redhat.com>
2023-09-14 14:11:39 +00:00
rc = lan87xx_phy_init_cmd(phydev, hw_init, ARRAY_SIZE(hw_init));
if (rc < 0)
return rc;
rc = genphy_read_master_slave(phydev);
if (rc)
return rc;
/* The following squence needs to run only if phydev is in
* slave mode.
*/
if (phydev->master_slave_state == MASTER_SLAVE_STATE_SLAVE) {
rc = lan87xx_phy_init_cmd(phydev, slave_init,
ARRAY_SIZE(slave_init));
if (rc < 0)
return rc;
}
net: phy: microchip: run phy initialization during each link update JIRA: https://issues.redhat.com/browse/RHEL-2799 commit d7bf56e0c591e189141430dc8bf4a1b4d0f117a2 Author: Rakesh Sankaranarayanan <rakesh.sankaranarayanan@microchip.com> Date: Fri Jan 20 16:17:33 2023 +0530 net: phy: microchip: run phy initialization during each link update PHY initialization is supposed to run on every mode changes. "lan87xx_config_aneg()" verifies every mode change using "phy_modify_changed()" function. Earlier code had phy_modify_changed() followed by genphy_soft_reset. But soft_reset resets all the pre-configured register values to default state, and lost all the initialization done. With this reason gen_phy_reset was removed. But it need to go through init sequence each time the mode changed. Update lan87xx_config_aneg() to invoke phy_init once successful mode update is detected. PHY init sequence added in lan87xx_phy_init() have slave init commands executed every time. Update the init sequence to run slave init only if phydev is in slave mode. Test setup contains LAN9370 EVB connected to SAMA5D3 (Running DSA), and issue can be reproduced by connecting link to any of the available ports after SAMA5D3 boot-up. With this issue, port will fail to update link state. But once the SAMA5D3 is reset with LAN9370 link in connected state itself, on boot-up link state will be reported as UP. But Again after some time, if link is moved to DOWN state, it will not get reported. Signed-off-by: Rakesh Sankaranarayanan <rakesh.sankaranarayanan@microchip.com> Reviewed-by: Andrew Lunn <andrew@lunn.ch> Link: https://lore.kernel.org/r/20230120104733.724701-1-rakesh.sankaranarayanan@microchip.com Signed-off-by: Jakub Kicinski <kuba@kernel.org> Signed-off-by: Adrien Thierry <athierry@redhat.com>
2023-09-14 14:11:39 +00:00
rc = lan87xx_phy_init_cmd(phydev, phy_init, ARRAY_SIZE(phy_init));
if (rc < 0)
return rc;
return lan87xx_config_rgmii_delay(phydev);
}
static int lan87xx_phy_config_intr(struct phy_device *phydev)
{
int rc, val = 0;
if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
net: phy: lan87xx: change interrupt src of link_up to comm_ready JIRA: https://issues.redhat.com/browse/RHEL-2799 commit 5382033a35227c57a349d74752ad2527780159a9 Author: Arun Ramadoss <arun.ramadoss@microchip.com> Date: Mon Sep 5 20:57:50 2022 +0530 net: phy: lan87xx: change interrupt src of link_up to comm_ready Currently phy link up/down interrupt is enabled using the LAN87xx_INTERRUPT_MASK register. In the lan87xx_read_status function, phy link is determined using the T1_MODE_STAT_REG register comm_ready bit. comm_ready bit is set using the loc_rcvr_status & rem_rcvr_status. Whenever the phy link is up, LAN87xx_INTERRUPT_SOURCE link_up bit is set first but comm_ready bit takes some time to set based on local and remote receiver status. As per the current implementation, interrupt is triggered using link_up but the comm_ready bit is still cleared in the read_status function. So, link is always down. Initially tested with the shared interrupt mechanism with switch and internal phy which is working, but after implementing interrupt controller it is not working. It can fixed either by updating the read_status function to read from LAN87XX_INTERRUPT_SOURCE register or enable the interrupt mask for comm_ready bit. But the validation team recommends the use of comm_ready for link detection. This patch fixes by enabling the comm_ready bit for link_up in the LAN87XX_INTERRUPT_MASK_2 register (MISC Bank) and link_down in LAN87xx_INTERRUPT_MASK register. Fixes: 8a1b415d70b7 ("net: phy: added ethtool master-slave configuration support") Signed-off-by: Arun Ramadoss <arun.ramadoss@microchip.com> Reviewed-by: Andrew Lunn <andrew@lunn.ch> Link: https://lore.kernel.org/r/20220905152750.5079-1-arun.ramadoss@microchip.com Signed-off-by: Paolo Abeni <pabeni@redhat.com> Signed-off-by: Adrien Thierry <athierry@redhat.com>
2023-09-14 14:11:18 +00:00
/* clear all interrupt */
rc = phy_write(phydev, LAN87XX_INTERRUPT_MASK, val);
if (rc < 0)
return rc;
rc = phy_read(phydev, LAN87XX_INTERRUPT_SOURCE);
net: phy: lan87xx: change interrupt src of link_up to comm_ready JIRA: https://issues.redhat.com/browse/RHEL-2799 commit 5382033a35227c57a349d74752ad2527780159a9 Author: Arun Ramadoss <arun.ramadoss@microchip.com> Date: Mon Sep 5 20:57:50 2022 +0530 net: phy: lan87xx: change interrupt src of link_up to comm_ready Currently phy link up/down interrupt is enabled using the LAN87xx_INTERRUPT_MASK register. In the lan87xx_read_status function, phy link is determined using the T1_MODE_STAT_REG register comm_ready bit. comm_ready bit is set using the loc_rcvr_status & rem_rcvr_status. Whenever the phy link is up, LAN87xx_INTERRUPT_SOURCE link_up bit is set first but comm_ready bit takes some time to set based on local and remote receiver status. As per the current implementation, interrupt is triggered using link_up but the comm_ready bit is still cleared in the read_status function. So, link is always down. Initially tested with the shared interrupt mechanism with switch and internal phy which is working, but after implementing interrupt controller it is not working. It can fixed either by updating the read_status function to read from LAN87XX_INTERRUPT_SOURCE register or enable the interrupt mask for comm_ready bit. But the validation team recommends the use of comm_ready for link detection. This patch fixes by enabling the comm_ready bit for link_up in the LAN87XX_INTERRUPT_MASK_2 register (MISC Bank) and link_down in LAN87xx_INTERRUPT_MASK register. Fixes: 8a1b415d70b7 ("net: phy: added ethtool master-slave configuration support") Signed-off-by: Arun Ramadoss <arun.ramadoss@microchip.com> Reviewed-by: Andrew Lunn <andrew@lunn.ch> Link: https://lore.kernel.org/r/20220905152750.5079-1-arun.ramadoss@microchip.com Signed-off-by: Paolo Abeni <pabeni@redhat.com> Signed-off-by: Adrien Thierry <athierry@redhat.com>
2023-09-14 14:11:18 +00:00
if (rc < 0)
return rc;
rc = access_ereg(phydev, PHYACC_ATTR_MODE_WRITE,
PHYACC_ATTR_BANK_MISC,
LAN87XX_INTERRUPT_MASK_2, val);
if (rc < 0)
return rc;
rc = access_ereg(phydev, PHYACC_ATTR_MODE_READ,
PHYACC_ATTR_BANK_MISC,
LAN87XX_INTERRUPT_SOURCE_2, 0);
if (rc < 0)
return rc;
/* enable link down and comm ready interrupt */
val = LAN87XX_MASK_LINK_DOWN;
rc = phy_write(phydev, LAN87XX_INTERRUPT_MASK, val);
net: phy: lan87xx: change interrupt src of link_up to comm_ready JIRA: https://issues.redhat.com/browse/RHEL-2799 commit 5382033a35227c57a349d74752ad2527780159a9 Author: Arun Ramadoss <arun.ramadoss@microchip.com> Date: Mon Sep 5 20:57:50 2022 +0530 net: phy: lan87xx: change interrupt src of link_up to comm_ready Currently phy link up/down interrupt is enabled using the LAN87xx_INTERRUPT_MASK register. In the lan87xx_read_status function, phy link is determined using the T1_MODE_STAT_REG register comm_ready bit. comm_ready bit is set using the loc_rcvr_status & rem_rcvr_status. Whenever the phy link is up, LAN87xx_INTERRUPT_SOURCE link_up bit is set first but comm_ready bit takes some time to set based on local and remote receiver status. As per the current implementation, interrupt is triggered using link_up but the comm_ready bit is still cleared in the read_status function. So, link is always down. Initially tested with the shared interrupt mechanism with switch and internal phy which is working, but after implementing interrupt controller it is not working. It can fixed either by updating the read_status function to read from LAN87XX_INTERRUPT_SOURCE register or enable the interrupt mask for comm_ready bit. But the validation team recommends the use of comm_ready for link detection. This patch fixes by enabling the comm_ready bit for link_up in the LAN87XX_INTERRUPT_MASK_2 register (MISC Bank) and link_down in LAN87xx_INTERRUPT_MASK register. Fixes: 8a1b415d70b7 ("net: phy: added ethtool master-slave configuration support") Signed-off-by: Arun Ramadoss <arun.ramadoss@microchip.com> Reviewed-by: Andrew Lunn <andrew@lunn.ch> Link: https://lore.kernel.org/r/20220905152750.5079-1-arun.ramadoss@microchip.com Signed-off-by: Paolo Abeni <pabeni@redhat.com> Signed-off-by: Adrien Thierry <athierry@redhat.com>
2023-09-14 14:11:18 +00:00
if (rc < 0)
return rc;
val = LAN87XX_MASK_COMM_RDY;
rc = access_ereg(phydev, PHYACC_ATTR_MODE_WRITE,
PHYACC_ATTR_BANK_MISC,
LAN87XX_INTERRUPT_MASK_2, val);
} else {
rc = phy_write(phydev, LAN87XX_INTERRUPT_MASK, val);
net: phy: lan87xx: change interrupt src of link_up to comm_ready JIRA: https://issues.redhat.com/browse/RHEL-2799 commit 5382033a35227c57a349d74752ad2527780159a9 Author: Arun Ramadoss <arun.ramadoss@microchip.com> Date: Mon Sep 5 20:57:50 2022 +0530 net: phy: lan87xx: change interrupt src of link_up to comm_ready Currently phy link up/down interrupt is enabled using the LAN87xx_INTERRUPT_MASK register. In the lan87xx_read_status function, phy link is determined using the T1_MODE_STAT_REG register comm_ready bit. comm_ready bit is set using the loc_rcvr_status & rem_rcvr_status. Whenever the phy link is up, LAN87xx_INTERRUPT_SOURCE link_up bit is set first but comm_ready bit takes some time to set based on local and remote receiver status. As per the current implementation, interrupt is triggered using link_up but the comm_ready bit is still cleared in the read_status function. So, link is always down. Initially tested with the shared interrupt mechanism with switch and internal phy which is working, but after implementing interrupt controller it is not working. It can fixed either by updating the read_status function to read from LAN87XX_INTERRUPT_SOURCE register or enable the interrupt mask for comm_ready bit. But the validation team recommends the use of comm_ready for link detection. This patch fixes by enabling the comm_ready bit for link_up in the LAN87XX_INTERRUPT_MASK_2 register (MISC Bank) and link_down in LAN87xx_INTERRUPT_MASK register. Fixes: 8a1b415d70b7 ("net: phy: added ethtool master-slave configuration support") Signed-off-by: Arun Ramadoss <arun.ramadoss@microchip.com> Reviewed-by: Andrew Lunn <andrew@lunn.ch> Link: https://lore.kernel.org/r/20220905152750.5079-1-arun.ramadoss@microchip.com Signed-off-by: Paolo Abeni <pabeni@redhat.com> Signed-off-by: Adrien Thierry <athierry@redhat.com>
2023-09-14 14:11:18 +00:00
if (rc < 0)
return rc;
rc = phy_read(phydev, LAN87XX_INTERRUPT_SOURCE);
net: phy: lan87xx: change interrupt src of link_up to comm_ready JIRA: https://issues.redhat.com/browse/RHEL-2799 commit 5382033a35227c57a349d74752ad2527780159a9 Author: Arun Ramadoss <arun.ramadoss@microchip.com> Date: Mon Sep 5 20:57:50 2022 +0530 net: phy: lan87xx: change interrupt src of link_up to comm_ready Currently phy link up/down interrupt is enabled using the LAN87xx_INTERRUPT_MASK register. In the lan87xx_read_status function, phy link is determined using the T1_MODE_STAT_REG register comm_ready bit. comm_ready bit is set using the loc_rcvr_status & rem_rcvr_status. Whenever the phy link is up, LAN87xx_INTERRUPT_SOURCE link_up bit is set first but comm_ready bit takes some time to set based on local and remote receiver status. As per the current implementation, interrupt is triggered using link_up but the comm_ready bit is still cleared in the read_status function. So, link is always down. Initially tested with the shared interrupt mechanism with switch and internal phy which is working, but after implementing interrupt controller it is not working. It can fixed either by updating the read_status function to read from LAN87XX_INTERRUPT_SOURCE register or enable the interrupt mask for comm_ready bit. But the validation team recommends the use of comm_ready for link detection. This patch fixes by enabling the comm_ready bit for link_up in the LAN87XX_INTERRUPT_MASK_2 register (MISC Bank) and link_down in LAN87xx_INTERRUPT_MASK register. Fixes: 8a1b415d70b7 ("net: phy: added ethtool master-slave configuration support") Signed-off-by: Arun Ramadoss <arun.ramadoss@microchip.com> Reviewed-by: Andrew Lunn <andrew@lunn.ch> Link: https://lore.kernel.org/r/20220905152750.5079-1-arun.ramadoss@microchip.com Signed-off-by: Paolo Abeni <pabeni@redhat.com> Signed-off-by: Adrien Thierry <athierry@redhat.com>
2023-09-14 14:11:18 +00:00
if (rc < 0)
return rc;
rc = access_ereg(phydev, PHYACC_ATTR_MODE_WRITE,
PHYACC_ATTR_BANK_MISC,
LAN87XX_INTERRUPT_MASK_2, val);
if (rc < 0)
return rc;
rc = access_ereg(phydev, PHYACC_ATTR_MODE_READ,
PHYACC_ATTR_BANK_MISC,
LAN87XX_INTERRUPT_SOURCE_2, 0);
}
return rc < 0 ? rc : 0;
}
static irqreturn_t lan87xx_handle_interrupt(struct phy_device *phydev)
{
int irq_status;
net: phy: lan87xx: change interrupt src of link_up to comm_ready JIRA: https://issues.redhat.com/browse/RHEL-2799 commit 5382033a35227c57a349d74752ad2527780159a9 Author: Arun Ramadoss <arun.ramadoss@microchip.com> Date: Mon Sep 5 20:57:50 2022 +0530 net: phy: lan87xx: change interrupt src of link_up to comm_ready Currently phy link up/down interrupt is enabled using the LAN87xx_INTERRUPT_MASK register. In the lan87xx_read_status function, phy link is determined using the T1_MODE_STAT_REG register comm_ready bit. comm_ready bit is set using the loc_rcvr_status & rem_rcvr_status. Whenever the phy link is up, LAN87xx_INTERRUPT_SOURCE link_up bit is set first but comm_ready bit takes some time to set based on local and remote receiver status. As per the current implementation, interrupt is triggered using link_up but the comm_ready bit is still cleared in the read_status function. So, link is always down. Initially tested with the shared interrupt mechanism with switch and internal phy which is working, but after implementing interrupt controller it is not working. It can fixed either by updating the read_status function to read from LAN87XX_INTERRUPT_SOURCE register or enable the interrupt mask for comm_ready bit. But the validation team recommends the use of comm_ready for link detection. This patch fixes by enabling the comm_ready bit for link_up in the LAN87XX_INTERRUPT_MASK_2 register (MISC Bank) and link_down in LAN87xx_INTERRUPT_MASK register. Fixes: 8a1b415d70b7 ("net: phy: added ethtool master-slave configuration support") Signed-off-by: Arun Ramadoss <arun.ramadoss@microchip.com> Reviewed-by: Andrew Lunn <andrew@lunn.ch> Link: https://lore.kernel.org/r/20220905152750.5079-1-arun.ramadoss@microchip.com Signed-off-by: Paolo Abeni <pabeni@redhat.com> Signed-off-by: Adrien Thierry <athierry@redhat.com>
2023-09-14 14:11:18 +00:00
irq_status = access_ereg(phydev, PHYACC_ATTR_MODE_READ,
PHYACC_ATTR_BANK_MISC,
LAN87XX_INTERRUPT_SOURCE_2, 0);
if (irq_status < 0) {
phy_error(phydev);
return IRQ_NONE;
}
irq_status = phy_read(phydev, LAN87XX_INTERRUPT_SOURCE);
if (irq_status < 0) {
phy_error(phydev);
return IRQ_NONE;
}
if (irq_status == 0)
return IRQ_NONE;
phy_trigger_machine(phydev);
return IRQ_HANDLED;
}
static int lan87xx_config_init(struct phy_device *phydev)
{
int rc = lan87xx_phy_init(phydev);
return rc < 0 ? rc : 0;
}
static int microchip_cable_test_start_common(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 0;
}
static int lan87xx_cable_test_start(struct phy_device *phydev)
{
static const struct access_ereg_val cable_test[] = {
/* min wait */
{PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP, 93,
0, 0},
/* max wait */
{PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP, 94,
10, 0},
/* pulse cycle */
{PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP, 95,
90, 0},
/* cable diag thresh */
{PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP, 92,
60, 0},
/* max gain */
{PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP, 79,
31, 0},
/* clock align for each iteration */
{PHYACC_ATTR_MODE_MODIFY, PHYACC_ATTR_BANK_DSP, 55,
0, 0x0038},
/* max cycle wait config */
{PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP, 94,
70, 0},
/* start cable diag*/
{PHYACC_ATTR_MODE_WRITE, PHYACC_ATTR_BANK_DSP, 90,
1, 0},
};
int rc, i;
rc = microchip_cable_test_start_common(phydev);
if (rc < 0)
return rc;
/* start cable diag */
/* check if part is alive - if not, return diagnostic error */
rc = access_ereg(phydev, PHYACC_ATTR_MODE_READ, PHYACC_ATTR_BANK_SMI,
0x00, 0);
if (rc < 0)
return rc;
/* master/slave specific configs */
rc = access_ereg(phydev, PHYACC_ATTR_MODE_READ, PHYACC_ATTR_BANK_SMI,
0x0A, 0);
if (rc < 0)
return rc;
if ((rc & 0x4000) != 0x4000) {
/* DUT is Slave */
rc = access_ereg_modify_changed(phydev, PHYACC_ATTR_BANK_AFE,
0x0E, 0x5, 0x7);
if (rc < 0)
return rc;
rc = access_ereg_modify_changed(phydev, PHYACC_ATTR_BANK_SMI,
0x1A, 0x8, 0x8);
if (rc < 0)
return rc;
} else {
/* DUT is Master */
rc = access_ereg_modify_changed(phydev, PHYACC_ATTR_BANK_SMI,
0x10, 0x8, 0x40);
if (rc < 0)
return rc;
}
for (i = 0; i < ARRAY_SIZE(cable_test); i++) {
if (cable_test[i].mode == PHYACC_ATTR_MODE_MODIFY) {
rc = access_ereg_modify_changed(phydev,
cable_test[i].bank,
cable_test[i].offset,
cable_test[i].val,
cable_test[i].mask);
/* wait 50ms */
msleep(50);
} else {
rc = access_ereg(phydev, cable_test[i].mode,
cable_test[i].bank,
cable_test[i].offset,
cable_test[i].val);
}
if (rc < 0)
return rc;
}
/* cable diag started */
return 0;
}
static int lan87xx_cable_test_report_trans(u32 result)
{
switch (result) {
case LAN87XX_CABLE_TEST_OK:
return ETHTOOL_A_CABLE_RESULT_CODE_OK;
case LAN87XX_CABLE_TEST_OPEN:
return ETHTOOL_A_CABLE_RESULT_CODE_OPEN;
case LAN87XX_CABLE_TEST_SAME_SHORT:
return ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT;
default:
/* DIAGNOSTIC_ERROR */
return ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC;
}
}
static int lan87xx_cable_test_report(struct phy_device *phydev)
{
int pos_peak_cycle = 0, pos_peak_in_phases = 0, pos_peak_phase = 0;
int neg_peak_cycle = 0, neg_peak_in_phases = 0, neg_peak_phase = 0;
int noise_margin = 20, time_margin = 89, jitter_var = 30;
int min_time_diff = 96, max_time_diff = 96 + time_margin;
bool fault = false, check_a = false, check_b = false;
int gain_idx = 0, pos_peak = 0, neg_peak = 0;
int pos_peak_time = 0, neg_peak_time = 0;
int pos_peak_in_phases_hybrid = 0;
int detect = -1;
gain_idx = access_ereg(phydev, PHYACC_ATTR_MODE_READ,
PHYACC_ATTR_BANK_DSP, 151, 0);
/* read non-hybrid results */
pos_peak = access_ereg(phydev, PHYACC_ATTR_MODE_READ,
PHYACC_ATTR_BANK_DSP, 153, 0);
neg_peak = access_ereg(phydev, PHYACC_ATTR_MODE_READ,
PHYACC_ATTR_BANK_DSP, 154, 0);
pos_peak_time = access_ereg(phydev, PHYACC_ATTR_MODE_READ,
PHYACC_ATTR_BANK_DSP, 156, 0);
neg_peak_time = access_ereg(phydev, PHYACC_ATTR_MODE_READ,
PHYACC_ATTR_BANK_DSP, 157, 0);
pos_peak_cycle = (pos_peak_time >> 7) & 0x7F;
/* calculate non-hybrid values */
pos_peak_phase = pos_peak_time & 0x7F;
pos_peak_in_phases = (pos_peak_cycle * 96) + pos_peak_phase;
neg_peak_cycle = (neg_peak_time >> 7) & 0x7F;
neg_peak_phase = neg_peak_time & 0x7F;
neg_peak_in_phases = (neg_peak_cycle * 96) + neg_peak_phase;
/* process values */
check_a =
((pos_peak_in_phases - neg_peak_in_phases) >= min_time_diff) &&
((pos_peak_in_phases - neg_peak_in_phases) < max_time_diff) &&
pos_peak_in_phases_hybrid < pos_peak_in_phases &&
(pos_peak_in_phases_hybrid < (neg_peak_in_phases + jitter_var));
check_b =
((neg_peak_in_phases - pos_peak_in_phases) >= min_time_diff) &&
((neg_peak_in_phases - pos_peak_in_phases) < max_time_diff) &&
pos_peak_in_phases_hybrid < neg_peak_in_phases &&
(pos_peak_in_phases_hybrid < (pos_peak_in_phases + jitter_var));
if (pos_peak_in_phases > neg_peak_in_phases && check_a)
detect = 2;
else if ((neg_peak_in_phases > pos_peak_in_phases) && check_b)
detect = 1;
if (pos_peak > noise_margin && neg_peak > noise_margin &&
gain_idx >= 0) {
if (detect == 1 || detect == 2)
fault = true;
}
if (!fault)
detect = 0;
ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_A,
lan87xx_cable_test_report_trans(detect));
return phy_init_hw(phydev);
}
static int lan87xx_cable_test_get_status(struct phy_device *phydev,
bool *finished)
{
int rc = 0;
*finished = false;
/* check if cable diag was finished */
rc = access_ereg(phydev, PHYACC_ATTR_MODE_READ, PHYACC_ATTR_BANK_DSP,
90, 0);
if (rc < 0)
return rc;
if ((rc & 2) == 2) {
/* stop cable diag*/
rc = access_ereg(phydev, PHYACC_ATTR_MODE_WRITE,
PHYACC_ATTR_BANK_DSP,
90, 0);
if (rc < 0)
return rc;
*finished = true;
return lan87xx_cable_test_report(phydev);
}
return 0;
}
static int lan87xx_read_status(struct phy_device *phydev)
{
int rc = 0;
rc = phy_read(phydev, T1_MODE_STAT_REG);
if (rc < 0)
return rc;
if (rc & T1_LINK_UP_MSK)
phydev->link = 1;
else
phydev->link = 0;
phydev->speed = SPEED_UNKNOWN;
phydev->duplex = DUPLEX_UNKNOWN;
phydev->pause = 0;
phydev->asym_pause = 0;
rc = genphy_read_master_slave(phydev);
if (rc < 0)
return rc;
rc = genphy_read_status_fixed(phydev);
if (rc < 0)
return rc;
return rc;
}
static int lan87xx_config_aneg(struct phy_device *phydev)
{
u16 ctl = 0;
net: phy: microchip: run phy initialization during each link update JIRA: https://issues.redhat.com/browse/RHEL-2799 commit d7bf56e0c591e189141430dc8bf4a1b4d0f117a2 Author: Rakesh Sankaranarayanan <rakesh.sankaranarayanan@microchip.com> Date: Fri Jan 20 16:17:33 2023 +0530 net: phy: microchip: run phy initialization during each link update PHY initialization is supposed to run on every mode changes. "lan87xx_config_aneg()" verifies every mode change using "phy_modify_changed()" function. Earlier code had phy_modify_changed() followed by genphy_soft_reset. But soft_reset resets all the pre-configured register values to default state, and lost all the initialization done. With this reason gen_phy_reset was removed. But it need to go through init sequence each time the mode changed. Update lan87xx_config_aneg() to invoke phy_init once successful mode update is detected. PHY init sequence added in lan87xx_phy_init() have slave init commands executed every time. Update the init sequence to run slave init only if phydev is in slave mode. Test setup contains LAN9370 EVB connected to SAMA5D3 (Running DSA), and issue can be reproduced by connecting link to any of the available ports after SAMA5D3 boot-up. With this issue, port will fail to update link state. But once the SAMA5D3 is reset with LAN9370 link in connected state itself, on boot-up link state will be reported as UP. But Again after some time, if link is moved to DOWN state, it will not get reported. Signed-off-by: Rakesh Sankaranarayanan <rakesh.sankaranarayanan@microchip.com> Reviewed-by: Andrew Lunn <andrew@lunn.ch> Link: https://lore.kernel.org/r/20230120104733.724701-1-rakesh.sankaranarayanan@microchip.com Signed-off-by: Jakub Kicinski <kuba@kernel.org> Signed-off-by: Adrien Thierry <athierry@redhat.com>
2023-09-14 14:11:39 +00:00
int ret;
switch (phydev->master_slave_set) {
case MASTER_SLAVE_CFG_MASTER_FORCE:
ctl |= CTL1000_AS_MASTER;
break;
case MASTER_SLAVE_CFG_SLAVE_FORCE:
break;
case MASTER_SLAVE_CFG_UNKNOWN:
case MASTER_SLAVE_CFG_UNSUPPORTED:
return 0;
default:
phydev_warn(phydev, "Unsupported Master/Slave mode\n");
return -EOPNOTSUPP;
}
net: phy: microchip: run phy initialization during each link update JIRA: https://issues.redhat.com/browse/RHEL-2799 commit d7bf56e0c591e189141430dc8bf4a1b4d0f117a2 Author: Rakesh Sankaranarayanan <rakesh.sankaranarayanan@microchip.com> Date: Fri Jan 20 16:17:33 2023 +0530 net: phy: microchip: run phy initialization during each link update PHY initialization is supposed to run on every mode changes. "lan87xx_config_aneg()" verifies every mode change using "phy_modify_changed()" function. Earlier code had phy_modify_changed() followed by genphy_soft_reset. But soft_reset resets all the pre-configured register values to default state, and lost all the initialization done. With this reason gen_phy_reset was removed. But it need to go through init sequence each time the mode changed. Update lan87xx_config_aneg() to invoke phy_init once successful mode update is detected. PHY init sequence added in lan87xx_phy_init() have slave init commands executed every time. Update the init sequence to run slave init only if phydev is in slave mode. Test setup contains LAN9370 EVB connected to SAMA5D3 (Running DSA), and issue can be reproduced by connecting link to any of the available ports after SAMA5D3 boot-up. With this issue, port will fail to update link state. But once the SAMA5D3 is reset with LAN9370 link in connected state itself, on boot-up link state will be reported as UP. But Again after some time, if link is moved to DOWN state, it will not get reported. Signed-off-by: Rakesh Sankaranarayanan <rakesh.sankaranarayanan@microchip.com> Reviewed-by: Andrew Lunn <andrew@lunn.ch> Link: https://lore.kernel.org/r/20230120104733.724701-1-rakesh.sankaranarayanan@microchip.com Signed-off-by: Jakub Kicinski <kuba@kernel.org> Signed-off-by: Adrien Thierry <athierry@redhat.com>
2023-09-14 14:11:39 +00:00
ret = phy_modify_changed(phydev, MII_CTRL1000, CTL1000_AS_MASTER, ctl);
if (ret == 1)
return phy_init_hw(phydev);
return ret;
}
static int lan87xx_get_sqi(struct phy_device *phydev)
{
u8 sqi_value = 0;
int rc;
rc = access_ereg(phydev, PHYACC_ATTR_MODE_WRITE,
PHYACC_ATTR_BANK_DSP, T1_COEF_RW_CTL_CFG, 0x0301);
if (rc < 0)
return rc;
rc = access_ereg(phydev, PHYACC_ATTR_MODE_READ,
PHYACC_ATTR_BANK_DSP, T1_DCQ_SQI_REG, 0x0);
if (rc < 0)
return rc;
sqi_value = FIELD_GET(T1_DCQ_SQI_MSK, rc);
return sqi_value;
}
static int lan87xx_get_sqi_max(struct phy_device *phydev)
{
return LAN87XX_MAX_SQI;
}
static int lan887x_rgmii_init(struct phy_device *phydev)
{
int ret;
/* SGMII mux disable */
ret = phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1,
LAN887X_SGMII_CTL,
LAN887X_SGMII_CTL_SGMII_MUX_EN);
if (ret < 0)
return ret;
/* Select MAC_MODE as RGMII */
ret = phy_modify_mmd(phydev, MDIO_MMD_VEND1, LAN887X_MIS_CFG_REG0,
LAN887X_MIS_CFG_REG0_MAC_MODE_SEL,
LAN887X_MAC_MODE_RGMII);
if (ret < 0)
return ret;
/* Disable PCS */
ret = phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1,
LAN887X_SGMII_PCS_CFG,
LAN887X_SGMII_PCS_CFG_PCS_ENA);
if (ret < 0)
return ret;
/* LAN887x Errata: RGMII rx clock active in SGMII mode
* Disabled it for SGMII mode
* Re-enabling it for RGMII mode
*/
return phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1,
LAN887X_MIS_CFG_REG0,
LAN887X_MIS_CFG_REG0_RCLKOUT_DIS);
}
static int lan887x_sgmii_init(struct phy_device *phydev)
{
int ret;
/* SGMII mux enable */
ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND1,
LAN887X_SGMII_CTL,
LAN887X_SGMII_CTL_SGMII_MUX_EN);
if (ret < 0)
return ret;
/* Select MAC_MODE as SGMII */
ret = phy_modify_mmd(phydev, MDIO_MMD_VEND1, LAN887X_MIS_CFG_REG0,
LAN887X_MIS_CFG_REG0_MAC_MODE_SEL,
LAN887X_MAC_MODE_SGMII);
if (ret < 0)
return ret;
/* LAN887x Errata: RGMII rx clock active in SGMII mode.
* So disabling it for SGMII mode
*/
ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, LAN887X_MIS_CFG_REG0,
LAN887X_MIS_CFG_REG0_RCLKOUT_DIS);
if (ret < 0)
return ret;
/* Enable PCS */
return phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, LAN887X_SGMII_PCS_CFG,
LAN887X_SGMII_PCS_CFG_PCS_ENA);
}
static int lan887x_config_rgmii_en(struct phy_device *phydev)
{
int txc;
int rxc;
int ret;
ret = lan887x_rgmii_init(phydev);
if (ret < 0)
return ret;
/* Control bit to enable/disable TX DLL delay line in signal path */
txc = phy_read_mmd(phydev, MDIO_MMD_VEND1, LAN887X_MIS_DLL_CFG_REG0);
if (txc < 0)
return txc;
/* Control bit to enable/disable RX DLL delay line in signal path */
rxc = phy_read_mmd(phydev, MDIO_MMD_VEND1, LAN887X_MIS_DLL_CFG_REG1);
if (rxc < 0)
return rxc;
/* Configures the phy to enable RX/TX delay
* RGMII - TX & RX delays are either added by MAC or not needed,
* phy should not add
* RGMII_ID - Configures phy to enable TX & RX delays, MAC shouldn't add
* RGMII_RX_ID - Configures the PHY to enable the RX delay.
* The MAC shouldn't add the RX delay
* RGMII_TX_ID - Configures the PHY to enable the TX delay.
* The MAC shouldn't add the TX delay in this case
*/
switch (phydev->interface) {
case PHY_INTERFACE_MODE_RGMII:
txc &= ~LAN887X_MIS_DLL_CONF;
rxc &= ~LAN887X_MIS_DLL_CONF;
break;
case PHY_INTERFACE_MODE_RGMII_ID:
txc |= LAN887X_MIS_DLL_CONF;
rxc |= LAN887X_MIS_DLL_CONF;
break;
case PHY_INTERFACE_MODE_RGMII_RXID:
txc &= ~LAN887X_MIS_DLL_CONF;
rxc |= LAN887X_MIS_DLL_CONF;
break;
case PHY_INTERFACE_MODE_RGMII_TXID:
txc |= LAN887X_MIS_DLL_CONF;
rxc &= ~LAN887X_MIS_DLL_CONF;
break;
default:
WARN_ONCE(1, "Invalid phydev interface %d\n", phydev->interface);
return 0;
}
/* Configures the PHY to enable/disable RX delay in signal path */
ret = phy_modify_mmd(phydev, MDIO_MMD_VEND1, LAN887X_MIS_DLL_CFG_REG1,
LAN887X_MIS_DLL_CONF, rxc);
if (ret < 0)
return ret;
/* Configures the PHY to enable/disable the TX delay in signal path */
return phy_modify_mmd(phydev, MDIO_MMD_VEND1, LAN887X_MIS_DLL_CFG_REG0,
LAN887X_MIS_DLL_CONF, txc);
}
static int lan887x_config_phy_interface(struct phy_device *phydev)
{
int interface_mode;
int sgmii_dis;
int ret;
/* Read sku efuse data for interfaces supported by sku */
ret = phy_read_mmd(phydev, MDIO_MMD_VEND1, LAN887X_EFUSE_READ_DAT9);
if (ret < 0)
return ret;
/* If interface_mode is 1 then efuse sets RGMII operations.
* If interface mode is 3 then efuse sets SGMII operations.
*/
interface_mode = ret & LAN887X_EFUSE_READ_DAT9_MAC_MODE;
/* SGMII disable is set for RGMII operations */
sgmii_dis = ret & LAN887X_EFUSE_READ_DAT9_SGMII_DIS;
switch (phydev->interface) {
case PHY_INTERFACE_MODE_RGMII:
case PHY_INTERFACE_MODE_RGMII_ID:
case PHY_INTERFACE_MODE_RGMII_RXID:
case PHY_INTERFACE_MODE_RGMII_TXID:
/* Reject RGMII settings for SGMII only sku */
ret = -EOPNOTSUPP;
if (!((interface_mode & LAN887X_MAC_MODE_SGMII) ==
LAN887X_MAC_MODE_SGMII))
ret = lan887x_config_rgmii_en(phydev);
break;
case PHY_INTERFACE_MODE_SGMII:
/* Reject SGMII setting for RGMII only sku */
ret = -EOPNOTSUPP;
if (!sgmii_dis)
ret = lan887x_sgmii_init(phydev);
break;
default:
/* Reject setting for unsupported interfaces */
ret = -EOPNOTSUPP;
}
return ret;
}
static int lan887x_get_features(struct phy_device *phydev)
{
int ret;
ret = genphy_c45_pma_read_abilities(phydev);
if (ret < 0)
return ret;
/* Enable twisted pair */
linkmode_set_bit(ETHTOOL_LINK_MODE_TP_BIT, phydev->supported);
/* First patch only supports 100Mbps and 1000Mbps force-mode.
* T1 Auto-Negotiation (Clause 98 of IEEE 802.3) will be added later.
*/
linkmode_clear_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, phydev->supported);
return 0;
}
static int lan887x_phy_init(struct phy_device *phydev)
{
int ret;
/* Clear loopback */
ret = phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1,
LAN887X_MIS_CFG_REG2,
LAN887X_MIS_CFG_REG2_FE_LPBK_EN);
if (ret < 0)
return ret;
/* Configure default behavior of led to link and activity for any
* speed
*/
ret = phy_modify_mmd(phydev, MDIO_MMD_VEND1,
LAN887X_COMMON_LED3_LED2,
LAN887X_COMMON_LED2_MODE_SEL_MASK,
LAN887X_LED_LINK_ACT_ANY_SPEED);
if (ret < 0)
return ret;
/* PHY interface setup */
return lan887x_config_phy_interface(phydev);
}
static int lan887x_phy_config(struct phy_device *phydev,
const struct lan887x_regwr_map *reg_map, int cnt)
{
int ret;
for (int i = 0; i < cnt; i++) {
ret = phy_write_mmd(phydev, reg_map[i].mmd,
reg_map[i].reg, reg_map[i].val);
if (ret < 0)
return ret;
}
return 0;
}
static int lan887x_phy_setup(struct phy_device *phydev)
{
static const struct lan887x_regwr_map phy_cfg[] = {
/* PORT_AFE writes */
{MDIO_MMD_PMAPMD, LAN887X_ZQCAL_CONTROL_1, 0x4008},
{MDIO_MMD_PMAPMD, LAN887X_AFE_PORT_TESTBUS_CTRL2, 0x0000},
{MDIO_MMD_PMAPMD, LAN887X_AFE_PORT_TESTBUS_CTRL6, 0x0040},
/* 100T1_PCS_VENDOR writes */
{MDIO_MMD_PCS, LAN887X_IDLE_ERR_CNT_THRESH, 0x0008},
{MDIO_MMD_PCS, LAN887X_IDLE_ERR_TIMER_WIN, 0x800d},
/* 100T1 DSP writes */
{MDIO_MMD_VEND1, LAN887x_CDR_CONFIG1_100, 0x0ab1},
{MDIO_MMD_VEND1, LAN887x_LOCK1_EQLSR_CONFIG_100, 0x5274},
{MDIO_MMD_VEND1, LAN887x_SLV_HD_MUFAC_CONFIG_100, 0x0d74},
{MDIO_MMD_VEND1, LAN887x_PLOCK_MUFAC_CONFIG_100, 0x0aea},
{MDIO_MMD_VEND1, LAN887x_PROT_DISABLE_100, 0x0360},
{MDIO_MMD_VEND1, LAN887x_KF_LOOP_SAT_CONFIG_100, 0x0c30},
/* 1000T1 DSP writes */
{MDIO_MMD_VEND1, LAN887X_LOCK1_EQLSR_CONFIG, 0x2a78},
{MDIO_MMD_VEND1, LAN887X_LOCK3_EQLSR_CONFIG, 0x1368},
{MDIO_MMD_VEND1, LAN887X_PROT_DISABLE, 0x1354},
{MDIO_MMD_VEND1, LAN887X_FFE_GAIN6, 0x3C84},
{MDIO_MMD_VEND1, LAN887X_FFE_GAIN7, 0x3ca5},
{MDIO_MMD_VEND1, LAN887X_FFE_GAIN8, 0x3ca5},
{MDIO_MMD_VEND1, LAN887X_FFE_GAIN9, 0x3ca5},
{MDIO_MMD_VEND1, LAN887X_ECHO_DELAY_CONFIG, 0x0024},
{MDIO_MMD_VEND1, LAN887X_FFE_MAX_CONFIG, 0x227f},
/* 1000T1 PCS writes */
{MDIO_MMD_PCS, LAN887X_SCR_CONFIG_3, 0x1e00},
{MDIO_MMD_PCS, LAN887X_INFO_FLD_CONFIG_5, 0x0fa1},
};
return lan887x_phy_config(phydev, phy_cfg, ARRAY_SIZE(phy_cfg));
}
static int lan887x_100M_setup(struct phy_device *phydev)
{
int ret;
/* (Re)configure the speed/mode dependent T1 settings */
if (phydev->master_slave_set == MASTER_SLAVE_CFG_MASTER_FORCE ||
phydev->master_slave_set == MASTER_SLAVE_CFG_MASTER_PREFERRED){
static const struct lan887x_regwr_map phy_cfg[] = {
{MDIO_MMD_PMAPMD, LAN887X_AFE_PORT_TESTBUS_CTRL4, 0x00b8},
{MDIO_MMD_PMAPMD, LAN887X_TX_AMPLT_1000T1_REG, 0x0038},
{MDIO_MMD_VEND1, LAN887X_INIT_COEFF_DFE1_100, 0x000f},
};
ret = lan887x_phy_config(phydev, phy_cfg, ARRAY_SIZE(phy_cfg));
} else {
static const struct lan887x_regwr_map phy_cfg[] = {
{MDIO_MMD_PMAPMD, LAN887X_AFE_PORT_TESTBUS_CTRL4, 0x0038},
{MDIO_MMD_VEND1, LAN887X_INIT_COEFF_DFE1_100, 0x0014},
};
ret = lan887x_phy_config(phydev, phy_cfg, ARRAY_SIZE(phy_cfg));
}
if (ret < 0)
return ret;
return phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, LAN887X_REG_REG26,
LAN887X_REG_REG26_HW_INIT_SEQ_EN);
}
static int lan887x_1000M_setup(struct phy_device *phydev)
{
static const struct lan887x_regwr_map phy_cfg[] = {
{MDIO_MMD_PMAPMD, LAN887X_TX_AMPLT_1000T1_REG, 0x003f},
{MDIO_MMD_PMAPMD, LAN887X_AFE_PORT_TESTBUS_CTRL4, 0x00b8},
};
int ret;
/* (Re)configure the speed/mode dependent T1 settings */
ret = lan887x_phy_config(phydev, phy_cfg, ARRAY_SIZE(phy_cfg));
if (ret < 0)
return ret;
return phy_set_bits_mmd(phydev, MDIO_MMD_PMAPMD, LAN887X_DSP_PMA_CONTROL,
LAN887X_DSP_PMA_CONTROL_LNK_SYNC);
}
static int lan887x_link_setup(struct phy_device *phydev)
{
int ret = -EINVAL;
if (phydev->speed == SPEED_1000)
ret = lan887x_1000M_setup(phydev);
else if (phydev->speed == SPEED_100)
ret = lan887x_100M_setup(phydev);
return ret;
}
/* LAN887x Errata: speed configuration changes require soft reset
* and chip soft reset
*/
static int lan887x_phy_reset(struct phy_device *phydev)
{
int ret, val;
/* Clear 1000M link sync */
ret = phy_clear_bits_mmd(phydev, MDIO_MMD_PMAPMD, LAN887X_DSP_PMA_CONTROL,
LAN887X_DSP_PMA_CONTROL_LNK_SYNC);
if (ret < 0)
return ret;
/* Clear 100M link sync */
ret = phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, LAN887X_REG_REG26,
LAN887X_REG_REG26_HW_INIT_SEQ_EN);
if (ret < 0)
return ret;
/* Chiptop soft-reset to allow the speed/mode change */
ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, LAN887X_CHIP_SOFT_RST,
LAN887X_CHIP_SOFT_RST_RESET);
if (ret < 0)
return ret;
/* CL22 soft-reset to let the link re-train */
ret = phy_modify(phydev, MII_BMCR, BMCR_RESET, BMCR_RESET);
if (ret < 0)
return ret;
/* Wait for reset complete or timeout if > 10ms */
return phy_read_poll_timeout(phydev, MII_BMCR, val, !(val & BMCR_RESET),
5000, 10000, true);
}
static int lan887x_phy_reconfig(struct phy_device *phydev)
{
int ret;
linkmode_zero(phydev->advertising);
ret = genphy_c45_pma_setup_forced(phydev);
if (ret < 0)
return ret;
return lan887x_link_setup(phydev);
}
static int lan887x_config_aneg(struct phy_device *phydev)
{
int ret;
/* LAN887x Errata: speed configuration changes require soft reset
* and chip soft reset
*/
ret = lan887x_phy_reset(phydev);
if (ret < 0)
return ret;
return lan887x_phy_reconfig(phydev);
}
static int lan887x_probe(struct phy_device *phydev)
{
struct lan887x_priv *priv;
priv = devm_kzalloc(&phydev->mdio.dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
phydev->priv = priv;
return lan887x_phy_setup(phydev);
}
static u64 lan887x_get_stat(struct phy_device *phydev, int i)
{
struct lan887x_hw_stat stat = lan887x_hw_stats[i];
struct lan887x_priv *priv = phydev->priv;
int val;
u64 ret;
if (stat.mmd)
val = phy_read_mmd(phydev, stat.mmd, stat.reg);
else
val = phy_read(phydev, stat.reg);
if (val < 0) {
ret = U64_MAX;
} else {
val = val & ((1 << stat.bits) - 1);
priv->stats[i] += val;
ret = priv->stats[i];
}
return ret;
}
static void lan887x_get_stats(struct phy_device *phydev,
struct ethtool_stats *stats, u64 *data)
{
for (int i = 0; i < ARRAY_SIZE(lan887x_hw_stats); i++)
data[i] = lan887x_get_stat(phydev, i);
}
static int lan887x_get_sset_count(struct phy_device *phydev)
{
return ARRAY_SIZE(lan887x_hw_stats);
}
static void lan887x_get_strings(struct phy_device *phydev, u8 *data)
{
for (int i = 0; i < ARRAY_SIZE(lan887x_hw_stats); i++)
ethtool_puts(&data, lan887x_hw_stats[i].string);
}
static int lan887x_cd_reset(struct phy_device *phydev,
enum cable_diag_state cd_done)
{
u16 val;
int rc;
/* Chip hard-reset */
rc = phy_write_mmd(phydev, MDIO_MMD_VEND1, LAN887X_CHIP_HARD_RST,
LAN887X_CHIP_HARD_RST_RESET);
if (rc < 0)
return rc;
/* Wait for reset to complete */
rc = phy_read_poll_timeout(phydev, MII_PHYSID2, val,
((val & GENMASK(15, 4)) ==
(PHY_ID_LAN887X & GENMASK(15, 4))),
5000, 50000, true);
if (rc < 0)
return rc;
if (cd_done == CD_TEST_DONE) {
/* Cable diagnostics complete. Restore PHY. */
rc = lan887x_phy_setup(phydev);
if (rc < 0)
return rc;
rc = lan887x_phy_init(phydev);
if (rc < 0)
return rc;
rc = lan887x_phy_reconfig(phydev);
if (rc < 0)
return rc;
}
return 0;
}
static int lan887x_cable_test_prep(struct phy_device *phydev,
enum cable_diag_mode mode)
{
static const struct lan887x_regwr_map values[] = {
{MDIO_MMD_VEND1, LAN887X_MAX_PGA_GAIN_100, 0x1f},
{MDIO_MMD_VEND1, LAN887X_MIN_PGA_GAIN_100, 0x0},
{MDIO_MMD_VEND1, LAN887X_CBL_DIAG_TDR_THRESH_100, 0x1},
{MDIO_MMD_VEND1, LAN887X_CBL_DIAG_AGC_THRESH_100, 0x3c},
{MDIO_MMD_VEND1, LAN887X_CBL_DIAG_MIN_WAIT_CONFIG_100, 0x0},
{MDIO_MMD_VEND1, LAN887X_CBL_DIAG_MAX_WAIT_CONFIG_100, 0x46},
{MDIO_MMD_VEND1, LAN887X_CBL_DIAG_CYC_CONFIG_100, 0x5a},
{MDIO_MMD_VEND1, LAN887X_CBL_DIAG_TX_PULSE_CONFIG_100, 0x44d5},
{MDIO_MMD_VEND1, LAN887X_CBL_DIAG_MIN_PGA_GAIN_100, 0x0},
};
int rc;
rc = lan887x_cd_reset(phydev, CD_TEST_INIT);
if (rc < 0)
return rc;
/* Forcing DUT to master mode, as we don't care about
* mode during diagnostics
*/
rc = phy_write_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_PMA_PMD_BT1_CTRL,
MDIO_PMA_PMD_BT1_CTRL_CFG_MST);
if (rc < 0)
return rc;
rc = phy_write_mmd(phydev, MDIO_MMD_PMAPMD, 0x80b0, 0x0038);
if (rc < 0)
return rc;
rc = phy_modify_mmd(phydev, MDIO_MMD_VEND1,
LAN887X_CALIB_CONFIG_100, 0,
LAN887X_CALIB_CONFIG_100_VAL);
if (rc < 0)
return rc;
for (int i = 0; i < ARRAY_SIZE(values); i++) {
rc = phy_write_mmd(phydev, values[i].mmd, values[i].reg,
values[i].val);
if (rc < 0)
return rc;
if (mode &&
values[i].reg == LAN887X_CBL_DIAG_MAX_WAIT_CONFIG_100) {
rc = phy_write_mmd(phydev, values[i].mmd,
values[i].reg, 0xa);
if (rc < 0)
return rc;
}
}
if (mode == TEST_MODE_HYBRID) {
rc = phy_modify_mmd(phydev, MDIO_MMD_PMAPMD,
LAN887X_AFE_PORT_TESTBUS_CTRL4,
BIT(0), BIT(0));
if (rc < 0)
return rc;
}
/* HW_INIT 100T1, Get DUT running in 100T1 mode */
rc = phy_modify_mmd(phydev, MDIO_MMD_VEND1, LAN887X_REG_REG26,
LAN887X_REG_REG26_HW_INIT_SEQ_EN,
LAN887X_REG_REG26_HW_INIT_SEQ_EN);
if (rc < 0)
return rc;
/* Cable diag requires hard reset and is sensitive regarding the delays.
* Hard reset is expected into and out of cable diag.
* Wait for 50ms
*/
msleep(50);
/* Start cable diag */
return phy_write_mmd(phydev, MDIO_MMD_VEND1,
LAN887X_START_CBL_DIAG_100,
LAN887X_CBL_DIAG_START);
}
static int lan887x_cable_test_chk(struct phy_device *phydev,
enum cable_diag_mode mode)
{
int val;
int rc;
if (mode == TEST_MODE_HYBRID) {
/* Cable diag requires hard reset and is sensitive regarding the delays.
* Hard reset is expected into and out of cable diag.
* Wait for cable diag to complete.
* Minimum wait time is 50ms if the condition is not a match.
*/
rc = phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1,
LAN887X_START_CBL_DIAG_100, val,
((val & LAN887X_CBL_DIAG_DONE) ==
LAN887X_CBL_DIAG_DONE),
50000, 500000, false);
if (rc < 0)
return rc;
} else {
rc = phy_read_mmd(phydev, MDIO_MMD_VEND1,
LAN887X_START_CBL_DIAG_100);
if (rc < 0)
return rc;
if ((rc & LAN887X_CBL_DIAG_DONE) != LAN887X_CBL_DIAG_DONE)
return -EAGAIN;
}
/* Stop cable diag */
return phy_write_mmd(phydev, MDIO_MMD_VEND1,
LAN887X_START_CBL_DIAG_100,
LAN887X_CBL_DIAG_STOP);
}
static int lan887x_cable_test_start(struct phy_device *phydev)
{
int rc, ret;
rc = lan887x_cable_test_prep(phydev, TEST_MODE_NORMAL);
if (rc < 0) {
ret = lan887x_cd_reset(phydev, CD_TEST_DONE);
if (ret < 0)
return ret;
return rc;
}
return 0;
}
static int lan887x_cable_test_report(struct phy_device *phydev)
{
int pos_peak_cycle, pos_peak_cycle_hybrid, pos_peak_in_phases;
int pos_peak_time, pos_peak_time_hybrid, neg_peak_time;
int neg_peak_cycle, neg_peak_in_phases;
int pos_peak_in_phases_hybrid;
int gain_idx, gain_idx_hybrid;
int pos_peak_phase_hybrid;
int pos_peak, neg_peak;
int distance;
int detect;
int length;
int ret;
int rc;
/* Read non-hybrid results */
gain_idx = phy_read_mmd(phydev, MDIO_MMD_VEND1,
LAN887X_CBL_DIAG_AGC_GAIN_100);
if (gain_idx < 0) {
rc = gain_idx;
goto error;
}
pos_peak = phy_read_mmd(phydev, MDIO_MMD_VEND1,
LAN887X_CBL_DIAG_POS_PEAK_VALUE_100);
if (pos_peak < 0) {
rc = pos_peak;
goto error;
}
neg_peak = phy_read_mmd(phydev, MDIO_MMD_VEND1,
LAN887X_CBL_DIAG_NEG_PEAK_VALUE_100);
if (neg_peak < 0) {
rc = neg_peak;
goto error;
}
pos_peak_time = phy_read_mmd(phydev, MDIO_MMD_VEND1,
LAN887X_CBL_DIAG_POS_PEAK_TIME_100);
if (pos_peak_time < 0) {
rc = pos_peak_time;
goto error;
}
neg_peak_time = phy_read_mmd(phydev, MDIO_MMD_VEND1,
LAN887X_CBL_DIAG_NEG_PEAK_TIME_100);
if (neg_peak_time < 0) {
rc = neg_peak_time;
goto error;
}
/* Calculate non-hybrid values */
pos_peak_cycle = (pos_peak_time >> 7) & 0x7f;
pos_peak_in_phases = (pos_peak_cycle * 96) + (pos_peak_time & 0x7f);
neg_peak_cycle = (neg_peak_time >> 7) & 0x7f;
neg_peak_in_phases = (neg_peak_cycle * 96) + (neg_peak_time & 0x7f);
/* Deriving the status of cable */
if (pos_peak > MICROCHIP_CABLE_NOISE_MARGIN &&
neg_peak > MICROCHIP_CABLE_NOISE_MARGIN && gain_idx >= 0) {
if (pos_peak_in_phases > neg_peak_in_phases &&
((pos_peak_in_phases - neg_peak_in_phases) >=
MICROCHIP_CABLE_MIN_TIME_DIFF) &&
((pos_peak_in_phases - neg_peak_in_phases) <
MICROCHIP_CABLE_MAX_TIME_DIFF) &&
pos_peak_in_phases > 0) {
detect = LAN87XX_CABLE_TEST_SAME_SHORT;
} else if (neg_peak_in_phases > pos_peak_in_phases &&
((neg_peak_in_phases - pos_peak_in_phases) >=
MICROCHIP_CABLE_MIN_TIME_DIFF) &&
((neg_peak_in_phases - pos_peak_in_phases) <
MICROCHIP_CABLE_MAX_TIME_DIFF) &&
neg_peak_in_phases > 0) {
detect = LAN87XX_CABLE_TEST_OPEN;
} else {
detect = LAN87XX_CABLE_TEST_OK;
}
} else {
detect = LAN87XX_CABLE_TEST_OK;
}
if (detect == LAN87XX_CABLE_TEST_OK) {
distance = 0;
goto get_len;
}
/* Re-initialize PHY and start cable diag test */
rc = lan887x_cable_test_prep(phydev, TEST_MODE_HYBRID);
if (rc < 0)
goto cd_stop;
/* Wait for cable diag test completion */
rc = lan887x_cable_test_chk(phydev, TEST_MODE_HYBRID);
if (rc < 0)
goto cd_stop;
/* Read hybrid results */
gain_idx_hybrid = phy_read_mmd(phydev, MDIO_MMD_VEND1,
LAN887X_CBL_DIAG_AGC_GAIN_100);
if (gain_idx_hybrid < 0) {
rc = gain_idx_hybrid;
goto error;
}
pos_peak_time_hybrid = phy_read_mmd(phydev, MDIO_MMD_VEND1,
LAN887X_CBL_DIAG_POS_PEAK_TIME_100);
if (pos_peak_time_hybrid < 0) {
rc = pos_peak_time_hybrid;
goto error;
}
/* Calculate hybrid values to derive cable length to fault */
pos_peak_cycle_hybrid = (pos_peak_time_hybrid >> 7) & 0x7f;
pos_peak_phase_hybrid = pos_peak_time_hybrid & 0x7f;
pos_peak_in_phases_hybrid = pos_peak_cycle_hybrid * 96 +
pos_peak_phase_hybrid;
/* Distance to fault calculation.
* distance = (peak_in_phases - peak_in_phases_hybrid) *
* propagationconstant.
* constant to convert number of phases to meters
* propagationconstant = 0.015953
* (0.6811 * 2.9979 * 156.2499 * 0.0001 * 0.5)
* Applying constant 1.5953 as ethtool further devides by 100 to
* convert to meters.
*/
if (detect == LAN87XX_CABLE_TEST_OPEN) {
distance = (((pos_peak_in_phases - pos_peak_in_phases_hybrid)
* 15953) / 10000);
} else if (detect == LAN87XX_CABLE_TEST_SAME_SHORT) {
distance = (((neg_peak_in_phases - pos_peak_in_phases_hybrid)
* 15953) / 10000);
} else {
distance = 0;
}
get_len:
rc = lan887x_cd_reset(phydev, CD_TEST_DONE);
if (rc < 0)
return rc;
length = ((u32)distance & GENMASK(15, 0));
ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_A,
lan87xx_cable_test_report_trans(detect));
ethnl_cable_test_fault_length(phydev, ETHTOOL_A_CABLE_PAIR_A, length);
return 0;
cd_stop:
/* Stop cable diag */
ret = phy_write_mmd(phydev, MDIO_MMD_VEND1,
LAN887X_START_CBL_DIAG_100,
LAN887X_CBL_DIAG_STOP);
if (ret < 0)
return ret;
error:
/* Cable diag test failed */
ret = lan887x_cd_reset(phydev, CD_TEST_DONE);
if (ret < 0)
return ret;
/* Return error in failure case */
return rc;
}
static int lan887x_cable_test_get_status(struct phy_device *phydev,
bool *finished)
{
int rc;
rc = lan887x_cable_test_chk(phydev, TEST_MODE_NORMAL);
if (rc < 0) {
/* Let PHY statemachine poll again */
if (rc == -EAGAIN)
return 0;
return rc;
}
/* Cable diag test complete */
*finished = true;
/* Retrieve test status and cable length to fault */
return lan887x_cable_test_report(phydev);
}
static struct phy_driver microchip_t1_phy_driver[] = {
{
PHY_ID_MATCH_MODEL(PHY_ID_LAN87XX),
.name = "Microchip LAN87xx T1",
.flags = PHY_POLL_CABLE_TEST,
.features = PHY_BASIC_T1_FEATURES,
.config_init = lan87xx_config_init,
.config_intr = lan87xx_phy_config_intr,
.handle_interrupt = lan87xx_handle_interrupt,
.suspend = genphy_suspend,
.resume = genphy_resume,
.config_aneg = lan87xx_config_aneg,
.read_status = lan87xx_read_status,
.get_sqi = lan87xx_get_sqi,
.get_sqi_max = lan87xx_get_sqi_max,
.cable_test_start = lan87xx_cable_test_start,
.cable_test_get_status = lan87xx_cable_test_get_status,
},
{
PHY_ID_MATCH_MODEL(PHY_ID_LAN937X),
.name = "Microchip LAN937x T1",
.flags = PHY_POLL_CABLE_TEST,
.features = PHY_BASIC_T1_FEATURES,
.config_init = lan87xx_config_init,
.config_intr = lan87xx_phy_config_intr,
.handle_interrupt = lan87xx_handle_interrupt,
.suspend = genphy_suspend,
.resume = genphy_resume,
.config_aneg = lan87xx_config_aneg,
.read_status = lan87xx_read_status,
.get_sqi = lan87xx_get_sqi,
.get_sqi_max = lan87xx_get_sqi_max,
.cable_test_start = lan87xx_cable_test_start,
.cable_test_get_status = lan87xx_cable_test_get_status,
},
{
PHY_ID_MATCH_MODEL(PHY_ID_LAN887X),
.name = "Microchip LAN887x T1 PHY",
.flags = PHY_POLL_CABLE_TEST,
.probe = lan887x_probe,
.get_features = lan887x_get_features,
.config_init = lan887x_phy_init,
.config_aneg = lan887x_config_aneg,
.get_stats = lan887x_get_stats,
.get_sset_count = lan887x_get_sset_count,
.get_strings = lan887x_get_strings,
.suspend = genphy_suspend,
.resume = genphy_resume,
.read_status = genphy_c45_read_status,
.cable_test_start = lan887x_cable_test_start,
.cable_test_get_status = lan887x_cable_test_get_status,
}
};
module_phy_driver(microchip_t1_phy_driver);
static struct mdio_device_id __maybe_unused microchip_t1_tbl[] = {
{ PHY_ID_MATCH_MODEL(PHY_ID_LAN87XX) },
{ PHY_ID_MATCH_MODEL(PHY_ID_LAN937X) },
{ PHY_ID_MATCH_MODEL(PHY_ID_LAN887X) },
{ }
};
MODULE_DEVICE_TABLE(mdio, microchip_t1_tbl);
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_LICENSE("GPL");