2015-07-31 16:58:42 +08:00
/*
* Driver for Aquantia PHY
*
* Author : Shaohui Xie < Shaohui . Xie @ freescale . com >
*
* Copyright 2015 Freescale Semiconductor , Inc .
*
* This file is licensed under the terms of the GNU General Public License
* version 2. This program is licensed " as is " without any warranty of any
* kind , whether express or implied .
*/
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/delay.h>
# include <linux/mii.h>
# include <linux/ethtool.h>
# include <linux/phy.h>
# include <linux/mdio.h>
# define PHY_ID_AQ1202 0x03a1b445
# define PHY_ID_AQ2104 0x03a1b460
# define PHY_ID_AQR105 0x03a1b4a2
# define PHY_ID_AQR405 0x03a1b4b0
# define PHY_AQUANTIA_FEATURES (SUPPORTED_10000baseT_Full | \
SUPPORTED_1000baseT_Full | \
SUPPORTED_100baseT_Full | \
PHY_DEFAULT_FEATURES )
static int aquantia_config_aneg ( struct phy_device * phydev )
{
phydev - > supported = PHY_AQUANTIA_FEATURES ;
phydev - > advertising = phydev - > supported ;
return 0 ;
}
static int aquantia_aneg_done ( struct phy_device * phydev )
{
int reg ;
reg = phy_read_mmd ( phydev , MDIO_MMD_AN , MDIO_STAT1 ) ;
return ( reg < 0 ) ? reg : ( reg & BMSR_ANEGCOMPLETE ) ;
}
2015-08-21 15:29:29 +08:00
static int aquantia_config_intr ( struct phy_device * phydev )
{
int err ;
if ( phydev - > interrupts = = PHY_INTERRUPT_ENABLED ) {
err = phy_write_mmd ( phydev , MDIO_MMD_AN , 0xd401 , 1 ) ;
if ( err < 0 )
return err ;
err = phy_write_mmd ( phydev , MDIO_MMD_VEND1 , 0xff00 , 1 ) ;
if ( err < 0 )
return err ;
err = phy_write_mmd ( phydev , MDIO_MMD_VEND1 , 0xff01 , 0x1001 ) ;
} else {
err = phy_write_mmd ( phydev , MDIO_MMD_AN , 0xd401 , 0 ) ;
if ( err < 0 )
return err ;
err = phy_write_mmd ( phydev , MDIO_MMD_VEND1 , 0xff00 , 0 ) ;
if ( err < 0 )
return err ;
err = phy_write_mmd ( phydev , MDIO_MMD_VEND1 , 0xff01 , 0 ) ;
}
return err ;
}
static int aquantia_ack_interrupt ( struct phy_device * phydev )
{
int reg ;
reg = phy_read_mmd ( phydev , MDIO_MMD_AN , 0xcc01 ) ;
return ( reg < 0 ) ? reg : 0 ;
}
2015-07-31 16:58:42 +08:00
static int aquantia_read_status ( struct phy_device * phydev )
{
int reg ;
reg = phy_read_mmd ( phydev , MDIO_MMD_AN , MDIO_STAT1 ) ;
reg = phy_read_mmd ( phydev , MDIO_MMD_AN , MDIO_STAT1 ) ;
if ( reg & MDIO_STAT1_LSTATUS )
phydev - > link = 1 ;
else
phydev - > link = 0 ;
reg = phy_read_mmd ( phydev , MDIO_MMD_AN , 0xc800 ) ;
mdelay ( 10 ) ;
reg = phy_read_mmd ( phydev , MDIO_MMD_AN , 0xc800 ) ;
switch ( reg ) {
case 0x9 :
phydev - > speed = SPEED_2500 ;
break ;
case 0x5 :
phydev - > speed = SPEED_1000 ;
break ;
case 0x3 :
phydev - > speed = SPEED_100 ;
break ;
case 0x7 :
default :
phydev - > speed = SPEED_10000 ;
break ;
}
phydev - > duplex = DUPLEX_FULL ;
return 0 ;
}
static struct phy_driver aquantia_driver [ ] = {
{
. phy_id = PHY_ID_AQ1202 ,
. phy_id_mask = 0xfffffff0 ,
. name = " Aquantia AQ1202 " ,
. features = PHY_AQUANTIA_FEATURES ,
2015-08-21 15:29:29 +08:00
. flags = PHY_HAS_INTERRUPT ,
2015-07-31 16:58:42 +08:00
. aneg_done = aquantia_aneg_done ,
. config_aneg = aquantia_config_aneg ,
2015-08-21 15:29:29 +08:00
. config_intr = aquantia_config_intr ,
. ack_interrupt = aquantia_ack_interrupt ,
2015-07-31 16:58:42 +08:00
. read_status = aquantia_read_status ,
. driver = { . owner = THIS_MODULE , } ,
} ,
{
. phy_id = PHY_ID_AQ2104 ,
. phy_id_mask = 0xfffffff0 ,
. name = " Aquantia AQ2104 " ,
. features = PHY_AQUANTIA_FEATURES ,
2015-08-21 15:29:29 +08:00
. flags = PHY_HAS_INTERRUPT ,
2015-07-31 16:58:42 +08:00
. aneg_done = aquantia_aneg_done ,
. config_aneg = aquantia_config_aneg ,
2015-08-21 15:29:29 +08:00
. config_intr = aquantia_config_intr ,
. ack_interrupt = aquantia_ack_interrupt ,
2015-07-31 16:58:42 +08:00
. read_status = aquantia_read_status ,
. driver = { . owner = THIS_MODULE , } ,
} ,
{
. phy_id = PHY_ID_AQR105 ,
. phy_id_mask = 0xfffffff0 ,
. name = " Aquantia AQR105 " ,
. features = PHY_AQUANTIA_FEATURES ,
2015-08-21 15:29:29 +08:00
. flags = PHY_HAS_INTERRUPT ,
2015-07-31 16:58:42 +08:00
. aneg_done = aquantia_aneg_done ,
. config_aneg = aquantia_config_aneg ,
2015-08-21 15:29:29 +08:00
. config_intr = aquantia_config_intr ,
. ack_interrupt = aquantia_ack_interrupt ,
2015-07-31 16:58:42 +08:00
. read_status = aquantia_read_status ,
. driver = { . owner = THIS_MODULE , } ,
} ,
{
. phy_id = PHY_ID_AQR405 ,
. phy_id_mask = 0xfffffff0 ,
. name = " Aquantia AQR405 " ,
. features = PHY_AQUANTIA_FEATURES ,
2015-08-21 15:29:29 +08:00
. flags = PHY_HAS_INTERRUPT ,
2015-07-31 16:58:42 +08:00
. aneg_done = aquantia_aneg_done ,
. config_aneg = aquantia_config_aneg ,
2015-08-21 15:29:29 +08:00
. config_intr = aquantia_config_intr ,
. ack_interrupt = aquantia_ack_interrupt ,
2015-07-31 16:58:42 +08:00
. read_status = aquantia_read_status ,
. driver = { . owner = THIS_MODULE , } ,
} ,
} ;
static int __init aquantia_init ( void )
{
return phy_drivers_register ( aquantia_driver ,
ARRAY_SIZE ( aquantia_driver ) ) ;
}
static void __exit aquantia_exit ( void )
{
return phy_drivers_unregister ( aquantia_driver ,
ARRAY_SIZE ( aquantia_driver ) ) ;
}
module_init ( aquantia_init ) ;
module_exit ( aquantia_exit ) ;
static struct mdio_device_id __maybe_unused aquantia_tbl [ ] = {
{ PHY_ID_AQ1202 , 0xfffffff0 } ,
{ PHY_ID_AQ2104 , 0xfffffff0 } ,
{ PHY_ID_AQR105 , 0xfffffff0 } ,
{ PHY_ID_AQR405 , 0xfffffff0 } ,
{ }
} ;
MODULE_DEVICE_TABLE ( mdio , aquantia_tbl ) ;
MODULE_DESCRIPTION ( " Aquantia PHY driver " ) ;
MODULE_AUTHOR ( " Shaohui Xie <Shaohui.Xie@freescale.com> " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;