1165cd04fSOleksij Rempel // SPDX-License-Identifier: GPL-2.0 2165cd04fSOleksij Rempel /* Driver for the Texas Instruments DP83TD510 PHY 3165cd04fSOleksij Rempel * Copyright (c) 2022 Pengutronix, Oleksij Rempel <kernel@pengutronix.de> 4165cd04fSOleksij Rempel */ 5165cd04fSOleksij Rempel 6165cd04fSOleksij Rempel #include <linux/bitfield.h> 7165cd04fSOleksij Rempel #include <linux/kernel.h> 8165cd04fSOleksij Rempel #include <linux/module.h> 9165cd04fSOleksij Rempel #include <linux/phy.h> 10165cd04fSOleksij Rempel 11165cd04fSOleksij Rempel #define DP83TD510E_PHY_ID 0x20000181 12165cd04fSOleksij Rempel 13165cd04fSOleksij Rempel /* MDIO_MMD_VEND2 registers */ 14165cd04fSOleksij Rempel #define DP83TD510E_PHY_STS 0x10 15165cd04fSOleksij Rempel #define DP83TD510E_STS_MII_INT BIT(7) 16165cd04fSOleksij Rempel #define DP83TD510E_LINK_STATUS BIT(0) 17165cd04fSOleksij Rempel 18165cd04fSOleksij Rempel #define DP83TD510E_GEN_CFG 0x11 19165cd04fSOleksij Rempel #define DP83TD510E_GENCFG_INT_POLARITY BIT(3) 20165cd04fSOleksij Rempel #define DP83TD510E_GENCFG_INT_EN BIT(1) 21165cd04fSOleksij Rempel #define DP83TD510E_GENCFG_INT_OE BIT(0) 22165cd04fSOleksij Rempel 23165cd04fSOleksij Rempel #define DP83TD510E_INTERRUPT_REG_1 0x12 24165cd04fSOleksij Rempel #define DP83TD510E_INT1_LINK BIT(13) 25165cd04fSOleksij Rempel #define DP83TD510E_INT1_LINK_EN BIT(5) 26165cd04fSOleksij Rempel 27165cd04fSOleksij Rempel #define DP83TD510E_AN_STAT_1 0x60c 28165cd04fSOleksij Rempel #define DP83TD510E_MASTER_SLAVE_RESOL_FAIL BIT(15) 29165cd04fSOleksij Rempel 30*a80d8fb7SOleksij Rempel #define DP83TD510E_MSE_DETECT 0xa85 31*a80d8fb7SOleksij Rempel 32*a80d8fb7SOleksij Rempel #define DP83TD510_SQI_MAX 7 33*a80d8fb7SOleksij Rempel 34*a80d8fb7SOleksij Rempel /* Register values are converted to SNR(dB) as suggested by 35*a80d8fb7SOleksij Rempel * "Application Report - DP83TD510E Cable Diagnostics Toolkit": 36*a80d8fb7SOleksij Rempel * SNR(dB) = -10 * log10 (VAL/2^17) - 1.76 dB. 37*a80d8fb7SOleksij Rempel * SQI ranges are implemented according to "OPEN ALLIANCE - Advanced diagnostic 38*a80d8fb7SOleksij Rempel * features for 100BASE-T1 automotive Ethernet PHYs" 39*a80d8fb7SOleksij Rempel */ 40*a80d8fb7SOleksij Rempel static const u16 dp83td510_mse_sqi_map[] = { 41*a80d8fb7SOleksij Rempel 0x0569, /* < 18dB */ 42*a80d8fb7SOleksij Rempel 0x044c, /* 18dB =< SNR < 19dB */ 43*a80d8fb7SOleksij Rempel 0x0369, /* 19dB =< SNR < 20dB */ 44*a80d8fb7SOleksij Rempel 0x02b6, /* 20dB =< SNR < 21dB */ 45*a80d8fb7SOleksij Rempel 0x0227, /* 21dB =< SNR < 22dB */ 46*a80d8fb7SOleksij Rempel 0x01b6, /* 22dB =< SNR < 23dB */ 47*a80d8fb7SOleksij Rempel 0x015b, /* 23dB =< SNR < 24dB */ 48*a80d8fb7SOleksij Rempel 0x0000 /* 24dB =< SNR */ 49*a80d8fb7SOleksij Rempel }; 50*a80d8fb7SOleksij Rempel 51165cd04fSOleksij Rempel static int dp83td510_config_intr(struct phy_device *phydev) 52165cd04fSOleksij Rempel { 53165cd04fSOleksij Rempel int ret; 54165cd04fSOleksij Rempel 55165cd04fSOleksij Rempel if (phydev->interrupts == PHY_INTERRUPT_ENABLED) { 56165cd04fSOleksij Rempel /* Clear any pending interrupts */ 57165cd04fSOleksij Rempel ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, DP83TD510E_PHY_STS, 58165cd04fSOleksij Rempel 0x0); 59165cd04fSOleksij Rempel if (ret) 60165cd04fSOleksij Rempel return ret; 61165cd04fSOleksij Rempel 62165cd04fSOleksij Rempel ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, 63165cd04fSOleksij Rempel DP83TD510E_INTERRUPT_REG_1, 64165cd04fSOleksij Rempel DP83TD510E_INT1_LINK_EN); 65165cd04fSOleksij Rempel if (ret) 66165cd04fSOleksij Rempel return ret; 67165cd04fSOleksij Rempel 68165cd04fSOleksij Rempel ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND2, 69165cd04fSOleksij Rempel DP83TD510E_GEN_CFG, 70165cd04fSOleksij Rempel DP83TD510E_GENCFG_INT_POLARITY | 71165cd04fSOleksij Rempel DP83TD510E_GENCFG_INT_EN | 72165cd04fSOleksij Rempel DP83TD510E_GENCFG_INT_OE); 73165cd04fSOleksij Rempel } else { 74165cd04fSOleksij Rempel ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, 75165cd04fSOleksij Rempel DP83TD510E_INTERRUPT_REG_1, 0x0); 76165cd04fSOleksij Rempel if (ret) 77165cd04fSOleksij Rempel return ret; 78165cd04fSOleksij Rempel 79165cd04fSOleksij Rempel ret = phy_clear_bits_mmd(phydev, MDIO_MMD_VEND2, 80165cd04fSOleksij Rempel DP83TD510E_GEN_CFG, 81165cd04fSOleksij Rempel DP83TD510E_GENCFG_INT_EN); 82165cd04fSOleksij Rempel if (ret) 83165cd04fSOleksij Rempel return ret; 84165cd04fSOleksij Rempel 85165cd04fSOleksij Rempel /* Clear any pending interrupts */ 86165cd04fSOleksij Rempel ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, DP83TD510E_PHY_STS, 87165cd04fSOleksij Rempel 0x0); 88165cd04fSOleksij Rempel } 89165cd04fSOleksij Rempel 90165cd04fSOleksij Rempel return ret; 91165cd04fSOleksij Rempel } 92165cd04fSOleksij Rempel 93165cd04fSOleksij Rempel static irqreturn_t dp83td510_handle_interrupt(struct phy_device *phydev) 94165cd04fSOleksij Rempel { 95165cd04fSOleksij Rempel int ret; 96165cd04fSOleksij Rempel 97165cd04fSOleksij Rempel ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, DP83TD510E_PHY_STS); 98165cd04fSOleksij Rempel if (ret < 0) { 99165cd04fSOleksij Rempel phy_error(phydev); 100165cd04fSOleksij Rempel return IRQ_NONE; 101165cd04fSOleksij Rempel } else if (!(ret & DP83TD510E_STS_MII_INT)) { 102165cd04fSOleksij Rempel return IRQ_NONE; 103165cd04fSOleksij Rempel } 104165cd04fSOleksij Rempel 105165cd04fSOleksij Rempel /* Read the current enabled interrupts */ 106165cd04fSOleksij Rempel ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, DP83TD510E_INTERRUPT_REG_1); 107165cd04fSOleksij Rempel if (ret < 0) { 108165cd04fSOleksij Rempel phy_error(phydev); 109165cd04fSOleksij Rempel return IRQ_NONE; 110165cd04fSOleksij Rempel } else if (!(ret & DP83TD510E_INT1_LINK_EN) || 111165cd04fSOleksij Rempel !(ret & DP83TD510E_INT1_LINK)) { 112165cd04fSOleksij Rempel return IRQ_NONE; 113165cd04fSOleksij Rempel } 114165cd04fSOleksij Rempel 115165cd04fSOleksij Rempel phy_trigger_machine(phydev); 116165cd04fSOleksij Rempel 117165cd04fSOleksij Rempel return IRQ_HANDLED; 118165cd04fSOleksij Rempel } 119165cd04fSOleksij Rempel 120165cd04fSOleksij Rempel static int dp83td510_read_status(struct phy_device *phydev) 121165cd04fSOleksij Rempel { 122165cd04fSOleksij Rempel u16 phy_sts; 123165cd04fSOleksij Rempel int ret; 124165cd04fSOleksij Rempel 125165cd04fSOleksij Rempel phydev->speed = SPEED_UNKNOWN; 126165cd04fSOleksij Rempel phydev->duplex = DUPLEX_UNKNOWN; 127165cd04fSOleksij Rempel phydev->pause = 0; 128165cd04fSOleksij Rempel phydev->asym_pause = 0; 129165cd04fSOleksij Rempel linkmode_zero(phydev->lp_advertising); 130165cd04fSOleksij Rempel 131165cd04fSOleksij Rempel phy_sts = phy_read(phydev, DP83TD510E_PHY_STS); 132165cd04fSOleksij Rempel 133165cd04fSOleksij Rempel phydev->link = !!(phy_sts & DP83TD510E_LINK_STATUS); 134165cd04fSOleksij Rempel if (phydev->link) { 135165cd04fSOleksij Rempel /* This PHY supports only one link mode: 10BaseT1L_Full */ 136165cd04fSOleksij Rempel phydev->duplex = DUPLEX_FULL; 137165cd04fSOleksij Rempel phydev->speed = SPEED_10; 138165cd04fSOleksij Rempel 139165cd04fSOleksij Rempel if (phydev->autoneg == AUTONEG_ENABLE) { 140165cd04fSOleksij Rempel ret = genphy_c45_read_lpa(phydev); 141165cd04fSOleksij Rempel if (ret) 142165cd04fSOleksij Rempel return ret; 143165cd04fSOleksij Rempel 144165cd04fSOleksij Rempel phy_resolve_aneg_linkmode(phydev); 145165cd04fSOleksij Rempel } 146165cd04fSOleksij Rempel } 147165cd04fSOleksij Rempel 148165cd04fSOleksij Rempel if (phydev->autoneg == AUTONEG_ENABLE) { 149165cd04fSOleksij Rempel ret = genphy_c45_baset1_read_status(phydev); 150165cd04fSOleksij Rempel if (ret < 0) 151165cd04fSOleksij Rempel return ret; 152165cd04fSOleksij Rempel 153165cd04fSOleksij Rempel ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, 154165cd04fSOleksij Rempel DP83TD510E_AN_STAT_1); 155165cd04fSOleksij Rempel if (ret < 0) 156165cd04fSOleksij Rempel return ret; 157165cd04fSOleksij Rempel 158165cd04fSOleksij Rempel if (ret & DP83TD510E_MASTER_SLAVE_RESOL_FAIL) 159165cd04fSOleksij Rempel phydev->master_slave_state = MASTER_SLAVE_STATE_ERR; 160165cd04fSOleksij Rempel } else { 161165cd04fSOleksij Rempel return genphy_c45_pma_baset1_read_master_slave(phydev); 162165cd04fSOleksij Rempel } 163165cd04fSOleksij Rempel 164165cd04fSOleksij Rempel return 0; 165165cd04fSOleksij Rempel } 166165cd04fSOleksij Rempel 167165cd04fSOleksij Rempel static int dp83td510_config_aneg(struct phy_device *phydev) 168165cd04fSOleksij Rempel { 169165cd04fSOleksij Rempel bool changed = false; 170165cd04fSOleksij Rempel int ret; 171165cd04fSOleksij Rempel 172165cd04fSOleksij Rempel ret = genphy_c45_pma_baset1_setup_master_slave(phydev); 173165cd04fSOleksij Rempel if (ret < 0) 174165cd04fSOleksij Rempel return ret; 175165cd04fSOleksij Rempel 176165cd04fSOleksij Rempel if (phydev->autoneg == AUTONEG_DISABLE) 177165cd04fSOleksij Rempel return genphy_c45_an_disable_aneg(phydev); 178165cd04fSOleksij Rempel 179165cd04fSOleksij Rempel ret = genphy_c45_an_config_aneg(phydev); 180165cd04fSOleksij Rempel if (ret < 0) 181165cd04fSOleksij Rempel return ret; 182165cd04fSOleksij Rempel if (ret > 0) 183165cd04fSOleksij Rempel changed = true; 184165cd04fSOleksij Rempel 185165cd04fSOleksij Rempel return genphy_c45_check_and_restart_aneg(phydev, changed); 186165cd04fSOleksij Rempel } 187165cd04fSOleksij Rempel 188*a80d8fb7SOleksij Rempel static int dp83td510_get_sqi(struct phy_device *phydev) 189*a80d8fb7SOleksij Rempel { 190*a80d8fb7SOleksij Rempel int sqi, ret; 191*a80d8fb7SOleksij Rempel u16 mse_val; 192*a80d8fb7SOleksij Rempel 193*a80d8fb7SOleksij Rempel if (!phydev->link) 194*a80d8fb7SOleksij Rempel return 0; 195*a80d8fb7SOleksij Rempel 196*a80d8fb7SOleksij Rempel ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, DP83TD510E_MSE_DETECT); 197*a80d8fb7SOleksij Rempel if (ret < 0) 198*a80d8fb7SOleksij Rempel return ret; 199*a80d8fb7SOleksij Rempel 200*a80d8fb7SOleksij Rempel mse_val = 0xFFFF & ret; 201*a80d8fb7SOleksij Rempel for (sqi = 0; sqi < ARRAY_SIZE(dp83td510_mse_sqi_map); sqi++) { 202*a80d8fb7SOleksij Rempel if (mse_val >= dp83td510_mse_sqi_map[sqi]) 203*a80d8fb7SOleksij Rempel return sqi; 204*a80d8fb7SOleksij Rempel } 205*a80d8fb7SOleksij Rempel 206*a80d8fb7SOleksij Rempel return -EINVAL; 207*a80d8fb7SOleksij Rempel } 208*a80d8fb7SOleksij Rempel 209*a80d8fb7SOleksij Rempel static int dp83td510_get_sqi_max(struct phy_device *phydev) 210*a80d8fb7SOleksij Rempel { 211*a80d8fb7SOleksij Rempel return DP83TD510_SQI_MAX; 212*a80d8fb7SOleksij Rempel } 213*a80d8fb7SOleksij Rempel 214165cd04fSOleksij Rempel static int dp83td510_get_features(struct phy_device *phydev) 215165cd04fSOleksij Rempel { 216165cd04fSOleksij Rempel /* This PHY can't respond on MDIO bus if no RMII clock is enabled. 217165cd04fSOleksij Rempel * In case RMII mode is used (most meaningful mode for this PHY) and 218165cd04fSOleksij Rempel * the PHY do not have own XTAL, and CLK providing MAC is not probed, 219165cd04fSOleksij Rempel * we won't be able to read all needed ability registers. 220165cd04fSOleksij Rempel * So provide it manually. 221165cd04fSOleksij Rempel */ 222165cd04fSOleksij Rempel 223165cd04fSOleksij Rempel linkmode_set_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, phydev->supported); 224165cd04fSOleksij Rempel linkmode_set_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, phydev->supported); 225165cd04fSOleksij Rempel linkmode_set_bit(ETHTOOL_LINK_MODE_Pause_BIT, phydev->supported); 226165cd04fSOleksij Rempel linkmode_set_bit(ETHTOOL_LINK_MODE_10baseT1L_Full_BIT, 227165cd04fSOleksij Rempel phydev->supported); 228165cd04fSOleksij Rempel 229165cd04fSOleksij Rempel return 0; 230165cd04fSOleksij Rempel } 231165cd04fSOleksij Rempel 232165cd04fSOleksij Rempel static struct phy_driver dp83td510_driver[] = { 233165cd04fSOleksij Rempel { 234165cd04fSOleksij Rempel PHY_ID_MATCH_MODEL(DP83TD510E_PHY_ID), 235165cd04fSOleksij Rempel .name = "TI DP83TD510E", 236165cd04fSOleksij Rempel 237165cd04fSOleksij Rempel .config_aneg = dp83td510_config_aneg, 238165cd04fSOleksij Rempel .read_status = dp83td510_read_status, 239165cd04fSOleksij Rempel .get_features = dp83td510_get_features, 240165cd04fSOleksij Rempel .config_intr = dp83td510_config_intr, 241165cd04fSOleksij Rempel .handle_interrupt = dp83td510_handle_interrupt, 242*a80d8fb7SOleksij Rempel .get_sqi = dp83td510_get_sqi, 243*a80d8fb7SOleksij Rempel .get_sqi_max = dp83td510_get_sqi_max, 244165cd04fSOleksij Rempel 245165cd04fSOleksij Rempel .suspend = genphy_suspend, 246165cd04fSOleksij Rempel .resume = genphy_resume, 247165cd04fSOleksij Rempel } }; 248165cd04fSOleksij Rempel module_phy_driver(dp83td510_driver); 249165cd04fSOleksij Rempel 250165cd04fSOleksij Rempel static struct mdio_device_id __maybe_unused dp83td510_tbl[] = { 251165cd04fSOleksij Rempel { PHY_ID_MATCH_MODEL(DP83TD510E_PHY_ID) }, 252165cd04fSOleksij Rempel { } 253165cd04fSOleksij Rempel }; 254165cd04fSOleksij Rempel MODULE_DEVICE_TABLE(mdio, dp83td510_tbl); 255165cd04fSOleksij Rempel 256165cd04fSOleksij Rempel MODULE_DESCRIPTION("Texas Instruments DP83TD510E PHY driver"); 257165cd04fSOleksij Rempel MODULE_AUTHOR("Oleksij Rempel <kernel@pengutronix.de>"); 258165cd04fSOleksij Rempel MODULE_LICENSE("GPL v2"); 259