2022-05-06 06:23:57 +02:00
// SPDX-License-Identifier: GPL-2.0
/* Driver for the Texas Instruments DP83TD510 PHY
* Copyright ( c ) 2022 Pengutronix , Oleksij Rempel < kernel @ pengutronix . de >
*/
# include <linux/bitfield.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/phy.h>
# define DP83TD510E_PHY_ID 0x20000181
/* MDIO_MMD_VEND2 registers */
# define DP83TD510E_PHY_STS 0x10
2023-06-21 06:38:48 +02:00
/* Bit 7 - mii_interrupt, active high. Clears on read.
* Note : Clearing does not necessarily deactivate IRQ pin if interrupts pending .
* This differs from the DP83TD510E datasheet ( 2020 ) which states this bit
* clears on write 0.
*/
2022-05-06 06:23:57 +02:00
# define DP83TD510E_STS_MII_INT BIT(7)
# define DP83TD510E_LINK_STATUS BIT(0)
# define DP83TD510E_GEN_CFG 0x11
# define DP83TD510E_GENCFG_INT_POLARITY BIT(3)
# define DP83TD510E_GENCFG_INT_EN BIT(1)
# define DP83TD510E_GENCFG_INT_OE BIT(0)
# define DP83TD510E_INTERRUPT_REG_1 0x12
# define DP83TD510E_INT1_LINK BIT(13)
# define DP83TD510E_INT1_LINK_EN BIT(5)
# define DP83TD510E_AN_STAT_1 0x60c
# define DP83TD510E_MASTER_SLAVE_RESOL_FAIL BIT(15)
2022-06-20 13:56:01 +02:00
# define DP83TD510E_MSE_DETECT 0xa85
# define DP83TD510_SQI_MAX 7
/* Register values are converted to SNR(dB) as suggested by
* " Application Report - DP83TD510E Cable Diagnostics Toolkit " :
* SNR ( dB ) = - 10 * log10 ( VAL / 2 ^ 17 ) - 1.76 dB .
* SQI ranges are implemented according to " OPEN ALLIANCE - Advanced diagnostic
* features for 100 BASE - T1 automotive Ethernet PHYs "
*/
static const u16 dp83td510_mse_sqi_map [ ] = {
0x0569 , /* < 18dB */
0x044c , /* 18dB =< SNR < 19dB */
0x0369 , /* 19dB =< SNR < 20dB */
0x02b6 , /* 20dB =< SNR < 21dB */
0x0227 , /* 21dB =< SNR < 22dB */
0x01b6 , /* 22dB =< SNR < 23dB */
0x015b , /* 23dB =< SNR < 24dB */
0x0000 /* 24dB =< SNR */
} ;
2022-05-06 06:23:57 +02:00
static int dp83td510_config_intr ( struct phy_device * phydev )
{
int ret ;
if ( phydev - > interrupts = = PHY_INTERRUPT_ENABLED ) {
ret = phy_write_mmd ( phydev , MDIO_MMD_VEND2 ,
DP83TD510E_INTERRUPT_REG_1 ,
DP83TD510E_INT1_LINK_EN ) ;
if ( ret )
return ret ;
ret = phy_set_bits_mmd ( phydev , MDIO_MMD_VEND2 ,
DP83TD510E_GEN_CFG ,
DP83TD510E_GENCFG_INT_POLARITY |
DP83TD510E_GENCFG_INT_EN |
DP83TD510E_GENCFG_INT_OE ) ;
} else {
ret = phy_write_mmd ( phydev , MDIO_MMD_VEND2 ,
DP83TD510E_INTERRUPT_REG_1 , 0x0 ) ;
if ( ret )
return ret ;
ret = phy_clear_bits_mmd ( phydev , MDIO_MMD_VEND2 ,
DP83TD510E_GEN_CFG ,
DP83TD510E_GENCFG_INT_EN ) ;
if ( ret )
return ret ;
}
return ret ;
}
static irqreturn_t dp83td510_handle_interrupt ( struct phy_device * phydev )
{
int ret ;
/* Read the current enabled interrupts */
ret = phy_read_mmd ( phydev , MDIO_MMD_VEND2 , DP83TD510E_INTERRUPT_REG_1 ) ;
if ( ret < 0 ) {
phy_error ( phydev ) ;
return IRQ_NONE ;
} else if ( ! ( ret & DP83TD510E_INT1_LINK_EN ) | |
! ( ret & DP83TD510E_INT1_LINK ) ) {
return IRQ_NONE ;
}
phy_trigger_machine ( phydev ) ;
return IRQ_HANDLED ;
}
static int dp83td510_read_status ( struct phy_device * phydev )
{
u16 phy_sts ;
int ret ;
phydev - > speed = SPEED_UNKNOWN ;
phydev - > duplex = DUPLEX_UNKNOWN ;
phydev - > pause = 0 ;
phydev - > asym_pause = 0 ;
linkmode_zero ( phydev - > lp_advertising ) ;
phy_sts = phy_read ( phydev , DP83TD510E_PHY_STS ) ;
phydev - > link = ! ! ( phy_sts & DP83TD510E_LINK_STATUS ) ;
if ( phydev - > link ) {
/* This PHY supports only one link mode: 10BaseT1L_Full */
phydev - > duplex = DUPLEX_FULL ;
phydev - > speed = SPEED_10 ;
if ( phydev - > autoneg = = AUTONEG_ENABLE ) {
ret = genphy_c45_read_lpa ( phydev ) ;
if ( ret )
return ret ;
phy_resolve_aneg_linkmode ( phydev ) ;
}
}
if ( phydev - > autoneg = = AUTONEG_ENABLE ) {
ret = genphy_c45_baset1_read_status ( phydev ) ;
if ( ret < 0 )
return ret ;
ret = phy_read_mmd ( phydev , MDIO_MMD_VEND2 ,
DP83TD510E_AN_STAT_1 ) ;
if ( ret < 0 )
return ret ;
if ( ret & DP83TD510E_MASTER_SLAVE_RESOL_FAIL )
phydev - > master_slave_state = MASTER_SLAVE_STATE_ERR ;
} else {
return genphy_c45_pma_baset1_read_master_slave ( phydev ) ;
}
return 0 ;
}
static int dp83td510_config_aneg ( struct phy_device * phydev )
{
bool changed = false ;
int ret ;
ret = genphy_c45_pma_baset1_setup_master_slave ( phydev ) ;
if ( ret < 0 )
return ret ;
if ( phydev - > autoneg = = AUTONEG_DISABLE )
return genphy_c45_an_disable_aneg ( phydev ) ;
ret = genphy_c45_an_config_aneg ( phydev ) ;
if ( ret < 0 )
return ret ;
if ( ret > 0 )
changed = true ;
return genphy_c45_check_and_restart_aneg ( phydev , changed ) ;
}
2022-06-20 13:56:01 +02:00
static int dp83td510_get_sqi ( struct phy_device * phydev )
{
int sqi , ret ;
u16 mse_val ;
if ( ! phydev - > link )
return 0 ;
ret = phy_read_mmd ( phydev , MDIO_MMD_VEND2 , DP83TD510E_MSE_DETECT ) ;
if ( ret < 0 )
return ret ;
mse_val = 0xFFFF & ret ;
for ( sqi = 0 ; sqi < ARRAY_SIZE ( dp83td510_mse_sqi_map ) ; sqi + + ) {
if ( mse_val > = dp83td510_mse_sqi_map [ sqi ] )
return sqi ;
}
return - EINVAL ;
}
static int dp83td510_get_sqi_max ( struct phy_device * phydev )
{
return DP83TD510_SQI_MAX ;
}
2022-05-06 06:23:57 +02:00
static int dp83td510_get_features ( struct phy_device * phydev )
{
/* This PHY can't respond on MDIO bus if no RMII clock is enabled.
* In case RMII mode is used ( most meaningful mode for this PHY ) and
* the PHY do not have own XTAL , and CLK providing MAC is not probed ,
* we won ' t be able to read all needed ability registers .
* So provide it manually .
*/
linkmode_set_bit ( ETHTOOL_LINK_MODE_Autoneg_BIT , phydev - > supported ) ;
linkmode_set_bit ( ETHTOOL_LINK_MODE_Asym_Pause_BIT , phydev - > supported ) ;
linkmode_set_bit ( ETHTOOL_LINK_MODE_Pause_BIT , phydev - > supported ) ;
linkmode_set_bit ( ETHTOOL_LINK_MODE_10baseT1L_Full_BIT ,
phydev - > supported ) ;
return 0 ;
}
static struct phy_driver dp83td510_driver [ ] = {
{
PHY_ID_MATCH_MODEL ( DP83TD510E_PHY_ID ) ,
. name = " TI DP83TD510E " ,
. config_aneg = dp83td510_config_aneg ,
. read_status = dp83td510_read_status ,
. get_features = dp83td510_get_features ,
. config_intr = dp83td510_config_intr ,
. handle_interrupt = dp83td510_handle_interrupt ,
2022-06-20 13:56:01 +02:00
. get_sqi = dp83td510_get_sqi ,
. get_sqi_max = dp83td510_get_sqi_max ,
2022-05-06 06:23:57 +02:00
. suspend = genphy_suspend ,
. resume = genphy_resume ,
} } ;
module_phy_driver ( dp83td510_driver ) ;
static struct mdio_device_id __maybe_unused dp83td510_tbl [ ] = {
{ PHY_ID_MATCH_MODEL ( DP83TD510E_PHY_ID ) } ,
{ }
} ;
MODULE_DEVICE_TABLE ( mdio , dp83td510_tbl ) ;
MODULE_DESCRIPTION ( " Texas Instruments DP83TD510E PHY driver " ) ;
MODULE_AUTHOR ( " Oleksij Rempel <kernel@pengutronix.de> " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;