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 >
*
* 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 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>
# 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
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
{
int rc = phy_read ( phydev , MII_LAN83C185_CTRL_STATUS ) ;
if ( rc < 0 )
return rc ;
/* 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 ;
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 ) {
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 15:00:38 +02:00
return 0 ;
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
/*
* The LAN8710 / LAN8720 requires a minimum of 2 link pulses within 64 ms of each
* other in order to set the ENERGYON bit and exit EDPD mode . If a link partner
* does send the pulses within this interval , the PHY will remained powered
* down .
*
* This workaround will manually toggle the PHY on / off upon calls to read_status
* in order to generate link test pulses if the link is down . If a link partner
* is present , it will respond to the pulses , which will cause the ENERGYON bit
* to be set and will cause the EDPD mode to be exited .
*/
static int lan87xx_read_status ( struct phy_device * phydev )
2010-01-06 20:35:14 -08:00
{
2012-11-15 09:00:57 +00:00
int err = genphy_read_status ( phydev ) ;
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 ;
/* Sleep 64 ms to allow ~5 link test pulses to be sent */
msleep ( 64 ) ;
/* 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
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 " ,
. 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 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 ,
2006-05-07 23:22:53 +02:00
. driver = { . owner = THIS_MODULE , }
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 " ,
. 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 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 ,
2009-01-22 14:07:43 -08:00
. suspend = genphy_suspend ,
. resume = genphy_resume ,
2008-04-28 18:37:29 +01:00
. driver = { . owner = THIS_MODULE , }
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 " ,
. 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 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 ,
2009-01-22 14:07:43 -08:00
. suspend = genphy_suspend ,
. resume = genphy_resume ,
2008-04-28 18:37:29 +01:00
. driver = { . owner = THIS_MODULE , }
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 " ,
. 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-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 ,
2008-11-05 00:35:37 +00:00
. driver = { . owner = THIS_MODULE , }
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 " ,
. 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 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 ,
. suspend = genphy_suspend ,
. resume = genphy_resume ,
. driver = { . owner = THIS_MODULE , }
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 } ,
{ }
} ;
MODULE_DEVICE_TABLE ( mdio , smsc_tbl ) ;