2006-05-08 01:22:53 +04:00
/*
* drivers / net / phy / smsc . c
*
* Driver for SMSC PHYs
*
* Author : Herbert Valerio Riedel
*
* Copyright ( c ) 2006 Herbert Valerio Riedel < hvr @ gnu . org >
*
* This program is free software ; you can redistribute it and / or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation ; either version 2 of the License , or ( at your
* option ) any later version .
*
2012-04-16 15:13:29 +04:00
* Support added for SMSC LAN8187 and LAN8700 by steve . glendinning @ shawell . net
2008-04-28 21:37:29 +04:00
*
2006-05-08 01:22:53 +04:00
*/
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/mii.h>
# include <linux/ethtool.h>
# include <linux/phy.h>
# include <linux/netdevice.h>
2012-01-04 05:23:18 +04:00
# include <linux/smscphy.h>
2006-05-08 01:22:53 +04:00
2008-04-28 21:36:46 +04:00
static int smsc_phy_config_intr ( struct phy_device * phydev )
2006-05-08 01:22:53 +04: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 21:36:46 +04:00
static int smsc_phy_ack_interrupt ( struct phy_device * phydev )
2006-05-08 01:22:53 +04:00
{
int rc = phy_read ( phydev , MII_LAN83C185_ISF ) ;
return rc < 0 ? rc : 0 ;
}
2008-04-28 21:36:46 +04:00
static int smsc_phy_config_init ( struct phy_device * phydev )
2014-08-15 17:00:38 +04:00
{
2015-10-17 07:04:36 +03:00
int __maybe_unused len ;
struct device * dev __maybe_unused = & phydev - > dev ;
struct device_node * of_node __maybe_unused = dev - > of_node ;
2014-08-15 17:00:38 +04:00
int rc = phy_read ( phydev , MII_LAN83C185_CTRL_STATUS ) ;
2015-10-17 07:04:36 +03:00
int enable_energy = 1 ;
2014-08-15 17:00:38 +04:00
if ( rc < 0 )
return rc ;
2015-10-17 07:04:36 +03:00
if ( of_find_property ( of_node , " smsc,disable-energy-detect " , & len ) )
enable_energy = 0 ;
if ( enable_energy ) {
/* 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 17:00:38 +04:00
return smsc_phy_ack_interrupt ( phydev ) ;
}
static int smsc_phy_reset ( struct phy_device * phydev )
2006-05-08 01:22:53 +04:00
{
2012-12-04 12:52:10 +04: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 ) {
int timeout = 50000 ;
/* set "all capable" mode and reset the phy */
rc | = MII_LAN83C185_MODE_ALL ;
phy_write ( phydev , MII_LAN83C185_SPECIAL_MODES , rc ) ;
phy_write ( phydev , MII_BMCR , BMCR_RESET ) ;
/* wait end of reset (max 500 ms) */
do {
udelay ( 10 ) ;
if ( timeout - - = = 0 )
return - 1 ;
rc = phy_read ( phydev , MII_BMCR ) ;
} while ( rc & BMCR_RESET ) ;
}
2014-08-15 17:00:38 +04:00
return 0 ;
2006-05-08 01:22:53 +04:00
}
2012-11-15 13:00:57 +04:00
static int lan911x_config_init ( struct phy_device * phydev )
2012-09-25 14:17:42 +04:00
{
return smsc_phy_ack_interrupt ( phydev ) ;
}
2012-11-15 13:00:57 +04: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 13:00:57 +04:00
*/
static int lan87xx_read_status ( struct phy_device * phydev )
2010-01-07 07:35:14 +03:00
{
2012-11-15 13:00:57 +04:00
int err = genphy_read_status ( phydev ) ;
2015-08-14 20:11:02 +03:00
int i ;
2012-11-15 13:00:57 +04:00
if ( ! phydev - > link ) {
/* 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 ;
2015-08-14 20:11:02 +03:00
/* Wait max 640 ms to detect energy */
for ( i = 0 ; i < 64 ; i + + ) {
/* Sleep to allow link test pulses to be sent */
msleep ( 10 ) ;
rc = phy_read ( phydev , MII_LAN83C185_CTRL_STATUS ) ;
if ( rc < 0 )
return rc ;
if ( rc & MII_LAN83C185_ENERGYON )
break ;
2015-08-18 01:31:42 +03:00
}
2012-11-15 13:00:57 +04: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-07 07:35:14 +03:00
}
2006-05-08 01:22:53 +04:00
2012-07-04 09:44:34 +04:00
static struct phy_driver smsc_phy_driver [ ] = {
{
2006-05-08 01:22:53 +04:00
. phy_id = 0x0007c0a0 , /* OUI=0x00800f, Model#=0x0a */
. phy_id_mask = 0xfffffff0 ,
. name = " SMSC LAN83C185 " ,
. features = ( PHY_BASIC_FEATURES | SUPPORTED_Pause
| SUPPORTED_Asym_Pause ) ,
. flags = PHY_HAS_INTERRUPT | PHY_HAS_MAGICANEG ,
/* basic functions */
. config_aneg = genphy_config_aneg ,
. read_status = genphy_read_status ,
2008-04-28 21:36:46 +04:00
. config_init = smsc_phy_config_init ,
2014-08-15 17:00:38 +04:00
. soft_reset = smsc_phy_reset ,
2006-05-08 01:22:53 +04:00
/* IRQ related */
2008-04-28 21:36:46 +04:00
. ack_interrupt = smsc_phy_ack_interrupt ,
. config_intr = smsc_phy_config_intr ,
2006-05-08 01:22:53 +04:00
2009-01-23 01:07:43 +03:00
. suspend = genphy_suspend ,
. resume = genphy_resume ,
2006-05-08 01:22:53 +04:00
. driver = { . owner = THIS_MODULE , }
2012-07-04 09:44:34 +04:00
} , {
2008-04-28 21:37:29 +04:00
. phy_id = 0x0007c0b0 , /* OUI=0x00800f, Model#=0x0b */
. phy_id_mask = 0xfffffff0 ,
. name = " SMSC LAN8187 " ,
. features = ( PHY_BASIC_FEATURES | SUPPORTED_Pause
| SUPPORTED_Asym_Pause ) ,
. flags = PHY_HAS_INTERRUPT | PHY_HAS_MAGICANEG ,
/* basic functions */
. config_aneg = genphy_config_aneg ,
. read_status = genphy_read_status ,
. config_init = smsc_phy_config_init ,
2014-08-15 17:00:38 +04:00
. soft_reset = smsc_phy_reset ,
2008-04-28 21:37:29 +04:00
/* IRQ related */
. ack_interrupt = smsc_phy_ack_interrupt ,
. config_intr = smsc_phy_config_intr ,
2009-01-23 01:07:43 +03:00
. suspend = genphy_suspend ,
. resume = genphy_resume ,
2008-04-28 21:37:29 +04:00
. driver = { . owner = THIS_MODULE , }
2012-07-04 09:44:34 +04:00
} , {
2008-04-28 21:37:29 +04:00
. phy_id = 0x0007c0c0 , /* OUI=0x00800f, Model#=0x0c */
. phy_id_mask = 0xfffffff0 ,
. name = " SMSC LAN8700 " ,
. features = ( PHY_BASIC_FEATURES | SUPPORTED_Pause
| SUPPORTED_Asym_Pause ) ,
. flags = PHY_HAS_INTERRUPT | PHY_HAS_MAGICANEG ,
/* basic functions */
. config_aneg = genphy_config_aneg ,
2015-08-14 20:11:02 +03:00
. read_status = lan87xx_read_status ,
2008-04-28 21:37:29 +04:00
. config_init = smsc_phy_config_init ,
2014-08-15 17:00:38 +04:00
. soft_reset = smsc_phy_reset ,
2008-04-28 21:37:29 +04:00
/* IRQ related */
. ack_interrupt = smsc_phy_ack_interrupt ,
. config_intr = smsc_phy_config_intr ,
2009-01-23 01:07:43 +03:00
. suspend = genphy_suspend ,
. resume = genphy_resume ,
2008-04-28 21:37:29 +04:00
. driver = { . owner = THIS_MODULE , }
2012-07-04 09:44:34 +04:00
} , {
2008-11-05 03:35:37 +03:00
. phy_id = 0x0007c0d0 , /* OUI=0x00800f, Model#=0x0d */
. phy_id_mask = 0xfffffff0 ,
. name = " SMSC LAN911x Internal PHY " ,
. features = ( PHY_BASIC_FEATURES | SUPPORTED_Pause
| SUPPORTED_Asym_Pause ) ,
. flags = PHY_HAS_INTERRUPT | PHY_HAS_MAGICANEG ,
/* basic functions */
. config_aneg = genphy_config_aneg ,
. read_status = genphy_read_status ,
2010-01-07 07:35:14 +03:00
. config_init = lan911x_config_init ,
2008-11-05 03:35:37 +03:00
/* IRQ related */
. ack_interrupt = smsc_phy_ack_interrupt ,
. config_intr = smsc_phy_config_intr ,
2009-01-23 01:07:43 +03:00
. suspend = genphy_suspend ,
. resume = genphy_resume ,
2008-11-05 03:35:37 +03:00
. driver = { . owner = THIS_MODULE , }
2012-07-04 09:44:34 +04:00
} , {
2009-03-24 01:17:31 +03:00
. phy_id = 0x0007c0f0 , /* OUI=0x00800f, Model#=0x0f */
. phy_id_mask = 0xfffffff0 ,
. name = " SMSC LAN8710/LAN8720 " ,
. features = ( PHY_BASIC_FEATURES | SUPPORTED_Pause
| SUPPORTED_Asym_Pause ) ,
. flags = PHY_HAS_INTERRUPT | PHY_HAS_MAGICANEG ,
/* basic functions */
. config_aneg = genphy_config_aneg ,
2012-11-15 13:00:57 +04:00
. read_status = lan87xx_read_status ,
2012-12-06 19:16:02 +04:00
. config_init = smsc_phy_config_init ,
2014-08-15 17:00:38 +04:00
. soft_reset = smsc_phy_reset ,
2009-03-24 01:17:31 +03:00
/* IRQ related */
. ack_interrupt = smsc_phy_ack_interrupt ,
. config_intr = smsc_phy_config_intr ,
. suspend = genphy_suspend ,
. resume = genphy_resume ,
. driver = { . owner = THIS_MODULE , }
2012-07-04 09:44:34 +04:00
} } ;
2009-03-24 01:17:31 +03:00
2014-11-11 21:45:59 +03:00
module_phy_driver ( smsc_phy_driver ) ;
2006-05-08 01:22:53 +04:00
MODULE_DESCRIPTION ( " SMSC PHY driver " ) ;
MODULE_AUTHOR ( " Herbert Valerio Riedel " ) ;
MODULE_LICENSE ( " GPL " ) ;
2010-10-04 03:43:32 +04:00
static struct mdio_device_id __maybe_unused smsc_tbl [ ] = {
2010-04-02 05:05:56 +04:00
{ 0x0007c0a0 , 0xfffffff0 } ,
{ 0x0007c0b0 , 0xfffffff0 } ,
{ 0x0007c0c0 , 0xfffffff0 } ,
{ 0x0007c0d0 , 0xfffffff0 } ,
{ 0x0007c0f0 , 0xfffffff0 } ,
{ }
} ;
MODULE_DEVICE_TABLE ( mdio , smsc_tbl ) ;