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
*/
# 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
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 {
bool energy_enable ;
} ;
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
{
int rc = phy_write ( phydev , MII_LAN83C185_IM ,
( ( PHY_INTERRUPT_ENABLED = = phydev - > interrupts )
? MII_LAN83C185_ISF_INT_PHYLIB_EVENTS
: 0 ) ) ;
return rc < 0 ? rc : 0 ;
}
2008-04-28 18:36:46 +01:00
static int smsc_phy_ack_interrupt ( struct phy_device * phydev )
2006-05-07 23:22:53 +02:00
{
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_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 ;
2014-08-15 15:00:38 +02:00
int rc = phy_read ( phydev , MII_LAN83C185_CTRL_STATUS ) ;
if ( rc < 0 )
return rc ;
2016-01-20 13:40:35 +01:00
if ( priv - > energy_enable ) {
2015-10-17 06:04:36 +02:00
/* Enable energy detect mode for this SMSC Transceivers */
rc = phy_write ( phydev , MII_LAN83C185_CTRL_STATUS ,
rc | MII_LAN83C185_EDPWRDOWN ) ;
if ( rc < 0 )
return rc ;
}
2014-08-15 15:00:38 +02:00
return smsc_phy_ack_interrupt ( phydev ) ;
}
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
}
2012-11-15 09:00:57 +00:00
static int lan911x_config_init ( struct phy_device * phydev )
2012-09-25 10:17:42 +00:00
{
return smsc_phy_ack_interrupt ( 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 .
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 ) ;
2016-01-20 13:40:35 +01:00
if ( ! phydev - > link & & priv - > energy_enable ) {
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 ;
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 ;
return 0 ;
}
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
. ack_interrupt = smsc_phy_ack_interrupt ,
. config_intr = smsc_phy_config_intr ,
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 */
. ack_interrupt = smsc_phy_ack_interrupt ,
. config_intr = smsc_phy_config_intr ,
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-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 ,
2008-04-28 18:37:29 +01:00
/* IRQ related */
. ack_interrupt = smsc_phy_ack_interrupt ,
. config_intr = smsc_phy_config_intr ,
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
/* basic functions */
2010-01-06 20:35:14 -08:00
. config_init = lan911x_config_init ,
2008-11-05 00:35:37 +00:00
/* IRQ related */
. ack_interrupt = smsc_phy_ack_interrupt ,
. config_intr = smsc_phy_config_intr ,
2009-01-22 14:07:43 -08:00
. suspend = genphy_suspend ,
. resume = genphy_resume ,
2012-07-04 05:44:34 +00:00
} , {
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 */
2018-11-09 18:17:22 +01:00
. flags = PHY_RST_AFTER_CLK_EN ,
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 ,
2009-03-23 15:17:31 -07:00
/* IRQ related */
. ack_interrupt = smsc_phy_ack_interrupt ,
. config_intr = smsc_phy_config_intr ,
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 */
. ack_interrupt = smsc_phy_ack_interrupt ,
. config_intr = smsc_phy_config_intr ,
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-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 } ,
2010-04-02 01:05:56 +00:00
{ }
} ;
MODULE_DEVICE_TABLE ( mdio , smsc_tbl ) ;