2019-01-21 19:05:50 +01:00
// SPDX-License-Identifier: GPL-2.0+
2006-05-07 23:22:53 +02:00
/*
* drivers / net / phy / smsc . c
*
* Driver for SMSC PHYs
*
* Author : Herbert Valerio Riedel
*
* Copyright ( c ) 2006 Herbert Valerio Riedel < hvr @ gnu . org >
*
2012-04-16 12:13:29 +01:00
* Support added for SMSC LAN8187 and LAN8700 by steve . glendinning @ shawell . net
2008-04-28 18:37:29 +01:00
*
2006-05-07 23:22:53 +02:00
*/
2020-09-09 15:45:00 +02:00
# include <linux/clk.h>
2006-05-07 23:22:53 +02:00
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/mii.h>
# include <linux/ethtool.h>
2017-03-28 23:45:06 +02:00
# include <linux/of.h>
2006-05-07 23:22:53 +02:00
# include <linux/phy.h>
# include <linux/netdevice.h>
2012-01-03 20:23:18 -05:00
# include <linux/smscphy.h>
2006-05-07 23:22:53 +02:00
2020-08-26 13:17:17 +02:00
/* Vendor-specific PHY Definitions */
/* EDPD NLP / crossover time configuration */
# define PHY_EDPD_CONFIG 16
# define PHY_EDPD_CONFIG_EXT_CROSSOVER_ 0x0001
/* Control/Status Indication Register */
# define SPECIAL_CTRL_STS 27
# define SPECIAL_CTRL_STS_OVRRD_AMDIX_ 0x8000
# define SPECIAL_CTRL_STS_AMDIX_ENABLE_ 0x4000
# define SPECIAL_CTRL_STS_AMDIX_STATE_ 0x2000
2017-06-03 20:00:36 +02:00
struct smsc_hw_stat {
const char * string ;
u8 reg ;
u8 bits ;
} ;
static struct smsc_hw_stat smsc_hw_stats [ ] = {
{ " phy_symbol_errors " , 26 , 16 } ,
} ;
2016-01-20 13:40:35 +01:00
struct smsc_phy_priv {
2022-05-12 10:42:06 +02:00
u16 intmask ;
2016-01-20 13:40:35 +01:00
bool energy_enable ;
} ;
2020-11-13 18:52:22 +02:00
static int smsc_phy_ack_interrupt ( struct phy_device * phydev )
{
int rc = phy_read ( phydev , MII_LAN83C185_ISF ) ;
return rc < 0 ? rc : 0 ;
}
2008-04-28 18:36:46 +01:00
static int smsc_phy_config_intr ( struct phy_device * phydev )
2006-05-07 23:22:53 +02:00
{
2020-09-09 15:44:57 +02:00
struct smsc_phy_priv * priv = phydev - > priv ;
int rc ;
if ( phydev - > interrupts = = PHY_INTERRUPT_ENABLED ) {
2020-11-13 18:52:22 +02:00
rc = smsc_phy_ack_interrupt ( phydev ) ;
if ( rc )
return rc ;
2022-05-12 10:42:06 +02:00
priv - > intmask = MII_LAN83C185_ISF_INT4 | MII_LAN83C185_ISF_INT6 ;
2020-09-09 15:44:57 +02:00
if ( priv - > energy_enable )
2022-05-12 10:42:06 +02:00
priv - > intmask | = MII_LAN83C185_ISF_INT7 ;
rc = phy_write ( phydev , MII_LAN83C185_IM , priv - > intmask ) ;
2020-11-13 18:52:22 +02:00
} else {
2022-05-12 10:42:06 +02:00
priv - > intmask = 0 ;
rc = phy_write ( phydev , MII_LAN83C185_IM , 0 ) ;
2020-11-13 18:52:22 +02:00
if ( rc )
return rc ;
2006-05-07 23:22:53 +02:00
2020-11-13 18:52:22 +02:00
rc = smsc_phy_ack_interrupt ( phydev ) ;
}
2006-05-07 23:22:53 +02:00
return rc < 0 ? rc : 0 ;
}
2020-11-13 18:52:21 +02:00
static irqreturn_t smsc_phy_handle_interrupt ( struct phy_device * phydev )
{
2022-05-12 10:42:06 +02:00
struct smsc_phy_priv * priv = phydev - > priv ;
int irq_status ;
2020-11-13 18:52:21 +02:00
irq_status = phy_read ( phydev , MII_LAN83C185_ISF ) ;
if ( irq_status < 0 ) {
2022-05-12 10:42:07 +02:00
if ( irq_status ! = - ENODEV )
phy_error ( phydev ) ;
2020-11-13 18:52:21 +02:00
return IRQ_NONE ;
}
2022-05-12 10:42:06 +02:00
if ( ! ( irq_status & priv - > intmask ) )
2020-11-13 18:52:21 +02:00
return IRQ_NONE ;
phy_trigger_machine ( phydev ) ;
return IRQ_HANDLED ;
}
2008-04-28 18:36:46 +01:00
static int smsc_phy_config_init ( struct phy_device * phydev )
2014-08-15 15:00:38 +02:00
{
2016-01-20 13:40:35 +01:00
struct smsc_phy_priv * priv = phydev - > priv ;
2020-09-09 15:44:58 +02:00
int rc ;
2022-06-20 13:04:50 +02:00
if ( ! priv - > energy_enable | | phydev - > irq ! = PHY_POLL )
2020-09-09 15:44:58 +02:00
return 0 ;
2016-01-20 13:40:35 +01:00
2020-09-09 15:44:58 +02:00
rc = phy_read ( phydev , MII_LAN83C185_CTRL_STATUS ) ;
2014-08-15 15:00:38 +02:00
if ( rc < 0 )
return rc ;
2020-09-09 15:44:58 +02:00
/* Enable energy detect mode for this SMSC Transceivers */
rc = phy_write ( phydev , MII_LAN83C185_CTRL_STATUS ,
rc | MII_LAN83C185_EDPWRDOWN ) ;
2022-06-20 11:28:39 +02:00
return rc ;
2014-08-15 15:00:38 +02:00
}
static int smsc_phy_reset ( struct phy_device * phydev )
2006-05-07 23:22:53 +02:00
{
2012-12-04 08:52:10 +00:00
int rc = phy_read ( phydev , MII_LAN83C185_SPECIAL_MODES ) ;
if ( rc < 0 )
return rc ;
/* If the SMSC PHY is in power down mode, then set it
* in all capable mode before using it .
*/
if ( ( rc & MII_LAN83C185_MODE_MASK ) = = MII_LAN83C185_MODE_POWERDOWN ) {
2016-06-06 10:47:47 +02:00
/* set "all capable" mode */
2012-12-04 08:52:10 +00:00
rc | = MII_LAN83C185_MODE_ALL ;
phy_write ( phydev , MII_LAN83C185_SPECIAL_MODES , rc ) ;
}
2016-06-06 10:47:47 +02:00
/* reset the phy */
return genphy_soft_reset ( phydev ) ;
2006-05-07 23:22:53 +02:00
}
2020-08-26 13:17:17 +02:00
static int lan87xx_config_aneg ( struct phy_device * phydev )
{
int rc ;
int val ;
switch ( phydev - > mdix_ctrl ) {
case ETH_TP_MDI :
val = SPECIAL_CTRL_STS_OVRRD_AMDIX_ ;
break ;
case ETH_TP_MDI_X :
val = SPECIAL_CTRL_STS_OVRRD_AMDIX_ |
SPECIAL_CTRL_STS_AMDIX_STATE_ ;
break ;
case ETH_TP_MDI_AUTO :
val = SPECIAL_CTRL_STS_AMDIX_ENABLE_ ;
break ;
default :
return genphy_config_aneg ( phydev ) ;
}
rc = phy_read ( phydev , SPECIAL_CTRL_STS ) ;
if ( rc < 0 )
return rc ;
rc & = ~ ( SPECIAL_CTRL_STS_OVRRD_AMDIX_ |
SPECIAL_CTRL_STS_AMDIX_ENABLE_ |
SPECIAL_CTRL_STS_AMDIX_STATE_ ) ;
rc | = val ;
phy_write ( phydev , SPECIAL_CTRL_STS , rc ) ;
phydev - > mdix = phydev - > mdix_ctrl ;
return genphy_config_aneg ( phydev ) ;
}
2021-03-29 11:45:36 +02:00
static int lan95xx_config_aneg_ext ( struct phy_device * phydev )
2020-08-26 13:17:17 +02:00
{
int rc ;
2021-03-29 11:45:36 +02:00
if ( phydev - > phy_id ! = 0x0007c0f0 ) /* not (LAN9500A or LAN9505A) */
return lan87xx_config_aneg ( phydev ) ;
2020-08-26 13:17:17 +02:00
/* Extend Manual AutoMDIX timer */
rc = phy_read ( phydev , PHY_EDPD_CONFIG ) ;
if ( rc < 0 )
return rc ;
rc | = PHY_EDPD_CONFIG_EXT_CROSSOVER_ ;
phy_write ( phydev , PHY_EDPD_CONFIG , rc ) ;
return lan87xx_config_aneg ( phydev ) ;
}
2012-11-15 09:00:57 +00:00
/*
2015-08-14 20:11:02 +03:00
* The LAN87xx suffers from rare absence of the ENERGYON - bit when Ethernet cable
* plugs in while LAN87xx is in Energy Detect Power - Down mode . This leads to
* unstable detection of plugging in Ethernet cable .
* This workaround disables Energy Detect Power - Down mode and waiting for
* response on link pulses to detect presence of plugged Ethernet cable .
* The Energy Detect Power - Down mode is enabled again in the end of procedure to
* save approximately 220 mW of power if cable is unplugged .
2022-06-20 13:04:50 +02:00
* The workaround is only applicable to poll mode . Energy Detect Power - Down may
* not be used in interrupt mode lest link change detection becomes unreliable .
2012-11-15 09:00:57 +00:00
*/
static int lan87xx_read_status ( struct phy_device * phydev )
2010-01-06 20:35:14 -08:00
{
2016-01-20 13:40:35 +01:00
struct smsc_phy_priv * priv = phydev - > priv ;
2012-11-15 09:00:57 +00:00
int err = genphy_read_status ( phydev ) ;
2022-06-20 13:04:50 +02:00
if ( ! phydev - > link & & priv - > energy_enable & & phydev - > irq = = PHY_POLL ) {
2012-11-15 09:00:57 +00:00
/* Disable EDPD to wake up PHY */
int rc = phy_read ( phydev , MII_LAN83C185_CTRL_STATUS ) ;
if ( rc < 0 )
return rc ;
rc = phy_write ( phydev , MII_LAN83C185_CTRL_STATUS ,
rc & ~ MII_LAN83C185_EDPWRDOWN ) ;
if ( rc < 0 )
return rc ;
2020-06-20 22:55:34 +08:00
/* Wait max 640 ms to detect energy and the timeout is not
* an actual error .
*/
read_poll_timeout ( phy_read , rc ,
rc & MII_LAN83C185_ENERGYON | | rc < 0 ,
10000 , 640000 , true , phydev ,
MII_LAN83C185_CTRL_STATUS ) ;
2020-03-23 23:05:59 +08:00
if ( rc < 0 )
return rc ;
2012-11-15 09:00:57 +00:00
/* Re-enable EDPD */
rc = phy_read ( phydev , MII_LAN83C185_CTRL_STATUS ) ;
if ( rc < 0 )
return rc ;
rc = phy_write ( phydev , MII_LAN83C185_CTRL_STATUS ,
rc | MII_LAN83C185_EDPWRDOWN ) ;
if ( rc < 0 )
return rc ;
}
return err ;
2010-01-06 20:35:14 -08:00
}
2006-05-07 23:22:53 +02:00
2017-06-03 20:00:36 +02:00
static int smsc_get_sset_count ( struct phy_device * phydev )
{
return ARRAY_SIZE ( smsc_hw_stats ) ;
}
static void smsc_get_strings ( struct phy_device * phydev , u8 * data )
{
int i ;
for ( i = 0 ; i < ARRAY_SIZE ( smsc_hw_stats ) ; i + + ) {
2017-06-20 22:40:46 +02:00
strncpy ( data + i * ETH_GSTRING_LEN ,
2017-06-03 20:00:36 +02:00
smsc_hw_stats [ i ] . string , ETH_GSTRING_LEN ) ;
}
}
static u64 smsc_get_stat ( struct phy_device * phydev , int i )
{
struct smsc_hw_stat stat = smsc_hw_stats [ i ] ;
int val ;
u64 ret ;
val = phy_read ( phydev , stat . reg ) ;
if ( val < 0 )
2018-04-27 16:18:58 +08:00
ret = U64_MAX ;
2017-06-03 20:00:36 +02:00
else
ret = val ;
return ret ;
}
static void smsc_get_stats ( struct phy_device * phydev ,
struct ethtool_stats * stats , u64 * data )
{
int i ;
for ( i = 0 ; i < ARRAY_SIZE ( smsc_hw_stats ) ; i + + )
data [ i ] = smsc_get_stat ( phydev , i ) ;
}
2016-01-20 13:40:35 +01:00
static int smsc_phy_probe ( struct phy_device * phydev )
{
struct device * dev = & phydev - > mdio . dev ;
struct device_node * of_node = dev - > of_node ;
struct smsc_phy_priv * priv ;
2022-08-28 19:26:55 +02:00
struct clk * refclk ;
2016-01-20 13:40:35 +01:00
priv = devm_kzalloc ( dev , sizeof ( * priv ) , GFP_KERNEL ) ;
if ( ! priv )
return - ENOMEM ;
priv - > energy_enable = true ;
if ( of_property_read_bool ( of_node , " smsc,disable-energy-detect " ) )
priv - > energy_enable = false ;
phydev - > priv = priv ;
2020-09-09 15:45:00 +02:00
/* Make clk optional to keep DTB backward compatibility. */
2022-08-28 19:26:55 +02:00
refclk = devm_clk_get_optional_enabled ( dev , NULL ) ;
if ( IS_ERR ( refclk ) )
return dev_err_probe ( dev , PTR_ERR ( refclk ) ,
2021-01-11 09:59:32 +01:00
" Failed to request clock \n " ) ;
2020-09-09 15:45:00 +02:00
2022-08-28 19:26:55 +02:00
return clk_set_rate ( refclk , 50 * 1000 * 1000 ) ;
2016-01-20 13:40:35 +01:00
}
2012-07-04 05:44:34 +00:00
static struct phy_driver smsc_phy_driver [ ] = {
{
2006-05-07 23:22:53 +02:00
. phy_id = 0x0007c0a0 , /* OUI=0x00800f, Model#=0x0a */
. phy_id_mask = 0xfffffff0 ,
. name = " SMSC LAN83C185 " ,
2019-04-12 20:47:03 +02:00
/* PHY_BASIC_FEATURES */
2006-05-07 23:22:53 +02:00
2016-01-20 13:40:35 +01:00
. probe = smsc_phy_probe ,
2006-05-07 23:22:53 +02:00
/* basic functions */
2008-04-28 18:36:46 +01:00
. config_init = smsc_phy_config_init ,
2014-08-15 15:00:38 +02:00
. soft_reset = smsc_phy_reset ,
2006-05-07 23:22:53 +02:00
/* IRQ related */
2008-04-28 18:36:46 +01:00
. config_intr = smsc_phy_config_intr ,
2020-11-13 18:52:21 +02:00
. handle_interrupt = smsc_phy_handle_interrupt ,
2006-05-07 23:22:53 +02:00
2009-01-22 14:07:43 -08:00
. suspend = genphy_suspend ,
. resume = genphy_resume ,
2012-07-04 05:44:34 +00:00
} , {
2008-04-28 18:37:29 +01:00
. phy_id = 0x0007c0b0 , /* OUI=0x00800f, Model#=0x0b */
. phy_id_mask = 0xfffffff0 ,
. name = " SMSC LAN8187 " ,
2019-04-12 20:47:03 +02:00
/* PHY_BASIC_FEATURES */
2008-04-28 18:37:29 +01:00
2016-01-20 13:40:35 +01:00
. probe = smsc_phy_probe ,
2008-04-28 18:37:29 +01:00
/* basic functions */
. config_init = smsc_phy_config_init ,
2014-08-15 15:00:38 +02:00
. soft_reset = smsc_phy_reset ,
2008-04-28 18:37:29 +01:00
/* IRQ related */
. config_intr = smsc_phy_config_intr ,
2020-11-13 18:52:21 +02:00
. handle_interrupt = smsc_phy_handle_interrupt ,
2008-04-28 18:37:29 +01:00
2017-06-03 20:00:36 +02:00
/* Statistics */
. get_sset_count = smsc_get_sset_count ,
. get_strings = smsc_get_strings ,
. get_stats = smsc_get_stats ,
2009-01-22 14:07:43 -08:00
. suspend = genphy_suspend ,
. resume = genphy_resume ,
2012-07-04 05:44:34 +00:00
} , {
2020-08-26 13:17:17 +02:00
/* This covers internal PHY (phy_id: 0x0007C0C3) for
* LAN9500 ( PID : 0x9500 ) , LAN9514 ( PID : 0xec00 ) , LAN9505 ( PID : 0x9505 )
*/
2008-04-28 18:37:29 +01:00
. phy_id = 0x0007c0c0 , /* OUI=0x00800f, Model#=0x0c */
. phy_id_mask = 0xfffffff0 ,
. name = " SMSC LAN8700 " ,
2019-04-12 20:47:03 +02:00
/* PHY_BASIC_FEATURES */
2008-04-28 18:37:29 +01:00
2016-01-20 13:40:35 +01:00
. probe = smsc_phy_probe ,
2008-04-28 18:37:29 +01:00
/* basic functions */
2015-08-14 20:11:02 +03:00
. read_status = lan87xx_read_status ,
2008-04-28 18:37:29 +01:00
. config_init = smsc_phy_config_init ,
2014-08-15 15:00:38 +02:00
. soft_reset = smsc_phy_reset ,
2020-08-26 13:17:17 +02:00
. config_aneg = lan87xx_config_aneg ,
2008-04-28 18:37:29 +01:00
/* IRQ related */
. config_intr = smsc_phy_config_intr ,
2020-11-13 18:52:21 +02:00
. handle_interrupt = smsc_phy_handle_interrupt ,
2008-04-28 18:37:29 +01:00
2017-06-03 20:00:36 +02:00
/* Statistics */
. get_sset_count = smsc_get_sset_count ,
. get_strings = smsc_get_strings ,
. get_stats = smsc_get_stats ,
2009-01-22 14:07:43 -08:00
. suspend = genphy_suspend ,
. resume = genphy_resume ,
2012-07-04 05:44:34 +00:00
} , {
2008-11-05 00:35:37 +00:00
. phy_id = 0x0007c0d0 , /* OUI=0x00800f, Model#=0x0d */
. phy_id_mask = 0xfffffff0 ,
. name = " SMSC LAN911x Internal PHY " ,
2019-04-12 20:47:03 +02:00
/* PHY_BASIC_FEATURES */
2008-11-05 00:35:37 +00:00
2016-01-20 13:40:35 +01:00
. probe = smsc_phy_probe ,
2008-11-05 00:35:37 +00:00
/* IRQ related */
. config_intr = smsc_phy_config_intr ,
2020-11-13 18:52:21 +02:00
. handle_interrupt = smsc_phy_handle_interrupt ,
2008-11-05 00:35:37 +00:00
2009-01-22 14:07:43 -08:00
. suspend = genphy_suspend ,
. resume = genphy_resume ,
2012-07-04 05:44:34 +00:00
} , {
2020-08-26 13:17:17 +02:00
/* This covers internal PHY (phy_id: 0x0007C0F0) for
* LAN9500A ( PID : 0x9E00 ) , LAN9505A ( PID : 0x9E01 )
*/
2009-03-23 15:17:31 -07:00
. phy_id = 0x0007c0f0 , /* OUI=0x00800f, Model#=0x0f */
. phy_id_mask = 0xfffffff0 ,
. name = " SMSC LAN8710/LAN8720 " ,
2019-04-12 20:47:03 +02:00
/* PHY_BASIC_FEATURES */
2009-03-23 15:17:31 -07:00
2016-01-20 13:40:35 +01:00
. probe = smsc_phy_probe ,
2009-03-23 15:17:31 -07:00
/* basic functions */
2012-11-15 09:00:57 +00:00
. read_status = lan87xx_read_status ,
2012-12-06 15:16:02 +00:00
. config_init = smsc_phy_config_init ,
2014-08-15 15:00:38 +02:00
. soft_reset = smsc_phy_reset ,
2021-03-29 11:45:36 +02:00
. config_aneg = lan95xx_config_aneg_ext ,
2009-03-23 15:17:31 -07:00
/* IRQ related */
. config_intr = smsc_phy_config_intr ,
2020-11-13 18:52:21 +02:00
. handle_interrupt = smsc_phy_handle_interrupt ,
2009-03-23 15:17:31 -07:00
2017-06-03 20:00:36 +02:00
/* Statistics */
. get_sset_count = smsc_get_sset_count ,
. get_strings = smsc_get_strings ,
. get_stats = smsc_get_stats ,
2016-01-09 04:54:21 -07:00
. suspend = genphy_suspend ,
. resume = genphy_resume ,
} , {
. phy_id = 0x0007c110 ,
. phy_id_mask = 0xfffffff0 ,
. name = " SMSC LAN8740 " ,
2019-04-12 20:47:03 +02:00
/* PHY_BASIC_FEATURES */
2019-10-23 11:44:24 +02:00
. flags = PHY_RST_AFTER_CLK_EN ,
2016-01-09 04:54:21 -07:00
2016-01-20 13:40:35 +01:00
. probe = smsc_phy_probe ,
2016-01-09 04:54:21 -07:00
/* basic functions */
. read_status = lan87xx_read_status ,
. config_init = smsc_phy_config_init ,
. soft_reset = smsc_phy_reset ,
/* IRQ related */
. config_intr = smsc_phy_config_intr ,
2020-11-13 18:52:21 +02:00
. handle_interrupt = smsc_phy_handle_interrupt ,
2016-01-09 04:54:21 -07:00
2017-06-03 20:00:36 +02:00
/* Statistics */
. get_sset_count = smsc_get_sset_count ,
. get_strings = smsc_get_strings ,
. get_stats = smsc_get_stats ,
2022-05-05 11:12:52 -07:00
. suspend = genphy_suspend ,
. resume = genphy_resume ,
} , {
2022-05-09 11:58:04 -07:00
. phy_id = 0x0007c130 , /* 0x0007c130 and 0x0007c131 */
/* This mask (0xfffffff2) is to differentiate from
* LAN88xx ( phy_id 0x0007c132 )
* and allows future phy_id revisions .
*/
2022-05-05 11:12:52 -07:00
. phy_id_mask = 0xfffffff2 ,
. name = " Microchip LAN8742 " ,
/* PHY_BASIC_FEATURES */
. flags = PHY_RST_AFTER_CLK_EN ,
. probe = smsc_phy_probe ,
/* basic functions */
. read_status = lan87xx_read_status ,
. config_init = smsc_phy_config_init ,
. soft_reset = smsc_phy_reset ,
/* IRQ related */
. config_intr = smsc_phy_config_intr ,
. handle_interrupt = smsc_phy_handle_interrupt ,
/* Statistics */
. get_sset_count = smsc_get_sset_count ,
. get_strings = smsc_get_strings ,
. get_stats = smsc_get_stats ,
2009-03-23 15:17:31 -07:00
. suspend = genphy_suspend ,
. resume = genphy_resume ,
2012-07-04 05:44:34 +00:00
} } ;
2009-03-23 15:17:31 -07:00
2014-11-11 19:45:59 +01:00
module_phy_driver ( smsc_phy_driver ) ;
2006-05-07 23:22:53 +02:00
MODULE_DESCRIPTION ( " SMSC PHY driver " ) ;
MODULE_AUTHOR ( " Herbert Valerio Riedel " ) ;
MODULE_LICENSE ( " GPL " ) ;
2010-10-03 23:43:32 +00:00
static struct mdio_device_id __maybe_unused smsc_tbl [ ] = {
2010-04-02 01:05:56 +00:00
{ 0x0007c0a0 , 0xfffffff0 } ,
{ 0x0007c0b0 , 0xfffffff0 } ,
{ 0x0007c0c0 , 0xfffffff0 } ,
{ 0x0007c0d0 , 0xfffffff0 } ,
{ 0x0007c0f0 , 0xfffffff0 } ,
2016-01-09 04:54:21 -07:00
{ 0x0007c110 , 0xfffffff0 } ,
2022-05-05 11:12:52 -07:00
{ 0x0007c130 , 0xfffffff2 } ,
2010-04-02 01:05:56 +00:00
{ }
} ;
MODULE_DEVICE_TABLE ( mdio , smsc_tbl ) ;