2012-10-14 23:07:16 +04:00
/*
* drivers / net / phy / at803x . c
*
* Driver for Atheros 803 x PHY
*
* Author : Matus Ujhelyi < ujhelyi . m @ gmail . com >
*
* 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 .
*/
# include <linux/phy.h>
# include <linux/module.h>
# include <linux/string.h>
# include <linux/netdevice.h>
# include <linux/etherdevice.h>
# define AT803X_INTR_ENABLE 0x12
# define AT803X_INTR_STATUS 0x13
# define AT803X_WOL_ENABLE 0x01
# define AT803X_DEVICE_ADDR 0x03
# define AT803X_LOC_MAC_ADDR_0_15_OFFSET 0x804C
# define AT803X_LOC_MAC_ADDR_16_31_OFFSET 0x804B
# define AT803X_LOC_MAC_ADDR_32_47_OFFSET 0x804A
# define AT803X_MMD_ACCESS_CONTROL 0x0D
# define AT803X_MMD_ACCESS_CONTROL_DATA 0x0E
# define AT803X_FUNC_DATA 0x4003
2014-03-28 11:39:41 +04:00
# define AT803X_INER 0x0012
# define AT803X_INER_INIT 0xec00
# define AT803X_INSR 0x0013
2013-06-04 00:10:06 +04:00
# define AT803X_DEBUG_ADDR 0x1D
# define AT803X_DEBUG_DATA 0x1E
# define AT803X_DEBUG_SYSTEM_MODE_CTRL 0x05
# define AT803X_DEBUG_RGMII_TX_CLK_DLY BIT(8)
2012-10-14 23:07:16 +04:00
MODULE_DESCRIPTION ( " Atheros 803x PHY driver " ) ;
MODULE_AUTHOR ( " Matus Ujhelyi " ) ;
MODULE_LICENSE ( " GPL " ) ;
2013-06-04 00:10:05 +04:00
static int at803x_set_wol ( struct phy_device * phydev ,
struct ethtool_wolinfo * wol )
2012-10-14 23:07:16 +04:00
{
struct net_device * ndev = phydev - > attached_dev ;
const u8 * mac ;
2013-06-04 00:10:05 +04:00
int ret ;
u32 value ;
2012-10-14 23:07:16 +04:00
unsigned int i , offsets [ ] = {
AT803X_LOC_MAC_ADDR_32_47_OFFSET ,
AT803X_LOC_MAC_ADDR_16_31_OFFSET ,
AT803X_LOC_MAC_ADDR_0_15_OFFSET ,
} ;
if ( ! ndev )
2013-06-04 00:10:05 +04:00
return - ENODEV ;
2012-10-14 23:07:16 +04:00
2013-06-04 00:10:05 +04:00
if ( wol - > wolopts & WAKE_MAGIC ) {
mac = ( const u8 * ) ndev - > dev_addr ;
2012-10-14 23:07:16 +04:00
2013-06-04 00:10:05 +04:00
if ( ! is_valid_ether_addr ( mac ) )
return - EFAULT ;
2012-10-14 23:07:16 +04:00
2013-06-04 00:10:05 +04:00
for ( i = 0 ; i < 3 ; i + + ) {
phy_write ( phydev , AT803X_MMD_ACCESS_CONTROL ,
2012-10-14 23:07:16 +04:00
AT803X_DEVICE_ADDR ) ;
2013-06-04 00:10:05 +04:00
phy_write ( phydev , AT803X_MMD_ACCESS_CONTROL_DATA ,
2012-10-14 23:07:16 +04:00
offsets [ i ] ) ;
2013-06-04 00:10:05 +04:00
phy_write ( phydev , AT803X_MMD_ACCESS_CONTROL ,
2012-10-14 23:07:16 +04:00
AT803X_FUNC_DATA ) ;
2013-06-04 00:10:05 +04:00
phy_write ( phydev , AT803X_MMD_ACCESS_CONTROL_DATA ,
2012-10-14 23:07:16 +04:00
mac [ ( i * 2 ) + 1 ] | ( mac [ ( i * 2 ) ] < < 8 ) ) ;
2013-06-04 00:10:05 +04:00
}
value = phy_read ( phydev , AT803X_INTR_ENABLE ) ;
value | = AT803X_WOL_ENABLE ;
ret = phy_write ( phydev , AT803X_INTR_ENABLE , value ) ;
if ( ret )
return ret ;
value = phy_read ( phydev , AT803X_INTR_STATUS ) ;
} else {
value = phy_read ( phydev , AT803X_INTR_ENABLE ) ;
value & = ( ~ AT803X_WOL_ENABLE ) ;
ret = phy_write ( phydev , AT803X_INTR_ENABLE , value ) ;
if ( ret )
return ret ;
value = phy_read ( phydev , AT803X_INTR_STATUS ) ;
2012-10-14 23:07:16 +04:00
}
2013-06-04 00:10:05 +04:00
return ret ;
}
static void at803x_get_wol ( struct phy_device * phydev ,
struct ethtool_wolinfo * wol )
{
u32 value ;
wol - > supported = WAKE_MAGIC ;
wol - > wolopts = 0 ;
value = phy_read ( phydev , AT803X_INTR_ENABLE ) ;
if ( value & AT803X_WOL_ENABLE )
wol - > wolopts | = WAKE_MAGIC ;
2012-10-14 23:07:16 +04:00
}
2013-09-21 18:53:02 +04:00
static int at803x_suspend ( struct phy_device * phydev )
{
int value ;
int wol_enabled ;
mutex_lock ( & phydev - > lock ) ;
value = phy_read ( phydev , AT803X_INTR_ENABLE ) ;
wol_enabled = value & AT803X_WOL_ENABLE ;
value = phy_read ( phydev , MII_BMCR ) ;
if ( wol_enabled )
value | = BMCR_ISOLATE ;
else
value | = BMCR_PDOWN ;
phy_write ( phydev , MII_BMCR , value ) ;
mutex_unlock ( & phydev - > lock ) ;
return 0 ;
}
static int at803x_resume ( struct phy_device * phydev )
{
int value ;
mutex_lock ( & phydev - > lock ) ;
value = phy_read ( phydev , MII_BMCR ) ;
value & = ~ ( BMCR_PDOWN | BMCR_ISOLATE ) ;
phy_write ( phydev , MII_BMCR , value ) ;
mutex_unlock ( & phydev - > lock ) ;
return 0 ;
}
2012-10-14 23:07:16 +04:00
static int at803x_config_init ( struct phy_device * phydev )
{
2013-06-04 00:10:06 +04:00
int ret ;
2012-10-14 23:07:16 +04:00
2014-04-16 19:19:13 +04:00
ret = genphy_config_init ( phydev ) ;
if ( ret < 0 )
return ret ;
2012-10-14 23:07:16 +04:00
2013-06-04 00:10:06 +04:00
if ( phydev - > interface = = PHY_INTERFACE_MODE_RGMII_TXID ) {
ret = phy_write ( phydev , AT803X_DEBUG_ADDR ,
AT803X_DEBUG_SYSTEM_MODE_CTRL ) ;
if ( ret )
return ret ;
ret = phy_write ( phydev , AT803X_DEBUG_DATA ,
AT803X_DEBUG_RGMII_TX_CLK_DLY ) ;
if ( ret )
return ret ;
}
2012-10-14 23:07:16 +04:00
return 0 ;
}
2014-03-28 11:39:41 +04:00
static int at803x_ack_interrupt ( struct phy_device * phydev )
{
int err ;
err = phy_read ( phydev , AT803X_INSR ) ;
return ( err < 0 ) ? err : 0 ;
}
static int at803x_config_intr ( struct phy_device * phydev )
{
int err ;
int value ;
value = phy_read ( phydev , AT803X_INER ) ;
if ( phydev - > interrupts = = PHY_INTERRUPT_ENABLED )
err = phy_write ( phydev , AT803X_INER ,
value | AT803X_INER_INIT ) ;
else
err = phy_write ( phydev , AT803X_INER , 0 ) ;
return err ;
}
2013-06-04 00:10:04 +04:00
static struct phy_driver at803x_driver [ ] = {
{
/* ATHEROS 8035 */
2012-10-14 23:07:16 +04:00
. phy_id = 0x004dd072 ,
. name = " Atheros 8035 ethernet " ,
. phy_id_mask = 0xffffffef ,
. config_init = at803x_config_init ,
2013-06-04 00:10:05 +04:00
. set_wol = at803x_set_wol ,
. get_wol = at803x_get_wol ,
2013-09-21 18:53:02 +04:00
. suspend = at803x_suspend ,
. resume = at803x_resume ,
2012-10-14 23:07:16 +04:00
. features = PHY_GBIT_FEATURES ,
. flags = PHY_HAS_INTERRUPT ,
2013-09-21 18:53:01 +04:00
. config_aneg = genphy_config_aneg ,
. read_status = genphy_read_status ,
2012-10-14 23:07:16 +04:00
. driver = {
. owner = THIS_MODULE ,
} ,
2013-06-04 00:10:04 +04:00
} , {
/* ATHEROS 8030 */
2012-10-14 23:07:16 +04:00
. phy_id = 0x004dd076 ,
. name = " Atheros 8030 ethernet " ,
. phy_id_mask = 0xffffffef ,
. config_init = at803x_config_init ,
2013-06-04 00:10:05 +04:00
. set_wol = at803x_set_wol ,
. get_wol = at803x_get_wol ,
2013-09-21 18:53:02 +04:00
. suspend = at803x_suspend ,
. resume = at803x_resume ,
2012-10-14 23:07:16 +04:00
. features = PHY_GBIT_FEATURES ,
. flags = PHY_HAS_INTERRUPT ,
2013-09-21 18:53:01 +04:00
. config_aneg = genphy_config_aneg ,
. read_status = genphy_read_status ,
2012-10-14 23:07:16 +04:00
. driver = {
. owner = THIS_MODULE ,
} ,
2013-06-04 00:10:07 +04:00
} , {
/* ATHEROS 8031 */
. phy_id = 0x004dd074 ,
. name = " Atheros 8031 ethernet " ,
. phy_id_mask = 0xffffffef ,
. config_init = at803x_config_init ,
. set_wol = at803x_set_wol ,
. get_wol = at803x_get_wol ,
2013-09-21 18:53:02 +04:00
. suspend = at803x_suspend ,
. resume = at803x_resume ,
2013-06-04 00:10:07 +04:00
. features = PHY_GBIT_FEATURES ,
. flags = PHY_HAS_INTERRUPT ,
2013-09-21 18:53:01 +04:00
. config_aneg = genphy_config_aneg ,
. read_status = genphy_read_status ,
2014-03-28 11:39:41 +04:00
. ack_interrupt = & at803x_ack_interrupt ,
. config_intr = & at803x_config_intr ,
2013-06-04 00:10:07 +04:00
. driver = {
. owner = THIS_MODULE ,
} ,
2013-06-04 00:10:04 +04:00
} } ;
2012-10-14 23:07:16 +04:00
static int __init atheros_init ( void )
{
2013-06-04 00:10:04 +04:00
return phy_drivers_register ( at803x_driver ,
ARRAY_SIZE ( at803x_driver ) ) ;
2012-10-14 23:07:16 +04:00
}
static void __exit atheros_exit ( void )
{
2014-04-22 23:21:47 +04:00
phy_drivers_unregister ( at803x_driver , ARRAY_SIZE ( at803x_driver ) ) ;
2012-10-14 23:07:16 +04:00
}
module_init ( atheros_init ) ;
module_exit ( atheros_exit ) ;
static struct mdio_device_id __maybe_unused atheros_tbl [ ] = {
{ 0x004dd076 , 0xffffffef } ,
2013-07-11 15:57:34 +04:00
{ 0x004dd074 , 0xffffffef } ,
2012-10-14 23:07:16 +04:00
{ 0x004dd072 , 0xffffffef } ,
{ }
} ;
MODULE_DEVICE_TABLE ( mdio , atheros_tbl ) ;