2019-01-21 19:05:50 +01:00
// SPDX-License-Identifier: GPL-2.0+
2017-06-05 12:23:16 +01:00
/*
* Marvell 10 G 88 x3310 PHY driver
*
* Based upon the ID registers , this PHY appears to be a mixture of IPs
* from two different companies .
*
* There appears to be several different data paths through the PHY which
* are automatically managed by the PHY . The following has been determined
2017-12-29 12:46:22 +00:00
* via observation and experimentation for a setup using single - lane Serdes :
2017-06-05 12:23:16 +01:00
*
* SGMII PHYXS - - BASE - T PCS - - 10 G PMA - - AN - - Copper ( for < = 1 G )
* 10 GBASE - KR PHYXS - - BASE - T PCS - - 10 G PMA - - AN - - Copper ( for 10 G )
* 10 GBASE - KR PHYXS - - BASE - R PCS - - Fiber
*
2017-12-29 12:46:22 +00:00
* With XAUI , observation shows :
*
* XAUI PHYXS - - < appropriate PCS as above >
*
* and no switching of the host interface mode occurs .
*
2017-06-05 12:23:16 +01:00
* If both the fiber and copper ports are connected , the first to gain
* link takes priority and the other port is completely locked out .
*/
2018-04-03 10:31:45 +01:00
# include <linux/ctype.h>
2020-03-03 18:08:34 +00:00
# include <linux/delay.h>
2018-04-03 10:31:45 +01:00
# include <linux/hwmon.h>
2017-11-28 14:26:30 +01:00
# include <linux/marvell_phy.h>
2018-04-03 10:31:45 +01:00
# include <linux/phy.h>
2019-11-15 19:56:56 +00:00
# include <linux/sfp.h>
2017-06-05 12:23:16 +01:00
2019-02-23 00:37:42 +01:00
# define MV_PHY_ALASKA_NBT_QUIRK_MASK 0xfffffffe
# define MV_PHY_ALASKA_NBT_QUIRK_REV (MARVELL_PHY_ID_88X3310 | 0xa)
2017-06-05 12:23:16 +01:00
enum {
2019-05-28 10:34:42 +01:00
MV_PMA_BOOT = 0xc050 ,
MV_PMA_BOOT_FATAL = BIT ( 0 ) ,
2017-06-05 12:23:16 +01:00
MV_PCS_BASE_T = 0x0000 ,
MV_PCS_BASE_R = 0x1000 ,
MV_PCS_1000BASEX = 0x2000 ,
2020-03-03 18:08:34 +00:00
MV_PCS_CSCR1 = 0x8000 ,
2020-03-03 18:08:40 +00:00
MV_PCS_CSCR1_ED_MASK = 0x0300 ,
MV_PCS_CSCR1_ED_OFF = 0x0000 ,
MV_PCS_CSCR1_ED_RX = 0x0200 ,
MV_PCS_CSCR1_ED_NLP = 0x0300 ,
2020-03-03 18:08:34 +00:00
MV_PCS_CSCR1_MDIX_MASK = 0x0060 ,
MV_PCS_CSCR1_MDIX_MDI = 0x0000 ,
MV_PCS_CSCR1_MDIX_MDIX = 0x0020 ,
MV_PCS_CSCR1_MDIX_AUTO = 0x0060 ,
2020-02-27 09:46:36 +00:00
MV_PCS_CSSR1 = 0x8008 ,
MV_PCS_CSSR1_SPD1_MASK = 0xc000 ,
MV_PCS_CSSR1_SPD1_SPD2 = 0xc000 ,
MV_PCS_CSSR1_SPD1_1000 = 0x8000 ,
MV_PCS_CSSR1_SPD1_100 = 0x4000 ,
MV_PCS_CSSR1_SPD1_10 = 0x0000 ,
MV_PCS_CSSR1_DUPLEX_FULL = BIT ( 13 ) ,
MV_PCS_CSSR1_RESOLVED = BIT ( 11 ) ,
MV_PCS_CSSR1_MDIX = BIT ( 6 ) ,
MV_PCS_CSSR1_SPD2_MASK = 0x000c ,
MV_PCS_CSSR1_SPD2_5000 = 0x0008 ,
MV_PCS_CSSR1_SPD2_2500 = 0x0004 ,
MV_PCS_CSSR1_SPD2_10000 = 0x0000 ,
2017-12-29 12:46:27 +00:00
2017-06-05 12:23:16 +01:00
/* These registers appear at 0x800X and 0xa00X - the 0xa00X control
* registers appear to set themselves to the 0x800 X when AN is
* restarted , but status registers appear readable from either .
*/
MV_AN_CTRL1000 = 0x8000 , /* 1000base-T control register */
MV_AN_STAT1000 = 0x8001 , /* 1000base-T status register */
2018-04-03 10:31:45 +01:00
/* Vendor2 MMD registers */
2019-04-02 15:10:28 +02:00
MV_V2_PORT_CTRL = 0xf001 ,
MV_V2_PORT_CTRL_PWRDOWN = 0x0800 ,
2018-04-03 10:31:45 +01:00
MV_V2_TEMP_CTRL = 0xf08a ,
MV_V2_TEMP_CTRL_MASK = 0xc000 ,
MV_V2_TEMP_CTRL_SAMPLE = 0x0000 ,
MV_V2_TEMP_CTRL_DISABLE = 0xc000 ,
MV_V2_TEMP = 0xf08c ,
MV_V2_TEMP_UNKNOWN = 0x9600 , /* unknown function */
} ;
struct mv3310_priv {
struct device * hwmon_dev ;
char * hwmon_name ;
2017-06-05 12:23:16 +01:00
} ;
2018-04-03 10:31:45 +01:00
# ifdef CONFIG_HWMON
static umode_t mv3310_hwmon_is_visible ( const void * data ,
enum hwmon_sensor_types type ,
u32 attr , int channel )
{
if ( type = = hwmon_chip & & attr = = hwmon_chip_update_interval )
return 0444 ;
if ( type = = hwmon_temp & & attr = = hwmon_temp_input )
return 0444 ;
return 0 ;
}
static int mv3310_hwmon_read ( struct device * dev , enum hwmon_sensor_types type ,
u32 attr , int channel , long * value )
{
struct phy_device * phydev = dev_get_drvdata ( dev ) ;
int temp ;
if ( type = = hwmon_chip & & attr = = hwmon_chip_update_interval ) {
* value = MSEC_PER_SEC ;
return 0 ;
}
if ( type = = hwmon_temp & & attr = = hwmon_temp_input ) {
temp = phy_read_mmd ( phydev , MDIO_MMD_VEND2 , MV_V2_TEMP ) ;
if ( temp < 0 )
return temp ;
* value = ( ( temp & 0xff ) - 75 ) * 1000 ;
return 0 ;
}
return - EOPNOTSUPP ;
}
static const struct hwmon_ops mv3310_hwmon_ops = {
. is_visible = mv3310_hwmon_is_visible ,
. read = mv3310_hwmon_read ,
} ;
static u32 mv3310_hwmon_chip_config [ ] = {
HWMON_C_REGISTER_TZ | HWMON_C_UPDATE_INTERVAL ,
0 ,
} ;
static const struct hwmon_channel_info mv3310_hwmon_chip = {
. type = hwmon_chip ,
. config = mv3310_hwmon_chip_config ,
} ;
static u32 mv3310_hwmon_temp_config [ ] = {
HWMON_T_INPUT ,
0 ,
} ;
static const struct hwmon_channel_info mv3310_hwmon_temp = {
. type = hwmon_temp ,
. config = mv3310_hwmon_temp_config ,
} ;
static const struct hwmon_channel_info * mv3310_hwmon_info [ ] = {
& mv3310_hwmon_chip ,
& mv3310_hwmon_temp ,
NULL ,
} ;
static const struct hwmon_chip_info mv3310_hwmon_chip_info = {
. ops = & mv3310_hwmon_ops ,
. info = mv3310_hwmon_info ,
} ;
static int mv3310_hwmon_config ( struct phy_device * phydev , bool enable )
{
u16 val ;
int ret ;
ret = phy_write_mmd ( phydev , MDIO_MMD_VEND2 , MV_V2_TEMP ,
MV_V2_TEMP_UNKNOWN ) ;
if ( ret < 0 )
return ret ;
val = enable ? MV_V2_TEMP_CTRL_SAMPLE : MV_V2_TEMP_CTRL_DISABLE ;
2019-02-10 19:58:49 +01:00
return phy_modify_mmd ( phydev , MDIO_MMD_VEND2 , MV_V2_TEMP_CTRL ,
MV_V2_TEMP_CTRL_MASK , val ) ;
2018-04-03 10:31:45 +01:00
}
static void mv3310_hwmon_disable ( void * data )
{
struct phy_device * phydev = data ;
mv3310_hwmon_config ( phydev , false ) ;
}
static int mv3310_hwmon_probe ( struct phy_device * phydev )
{
struct device * dev = & phydev - > mdio . dev ;
struct mv3310_priv * priv = dev_get_drvdata ( & phydev - > mdio . dev ) ;
int i , j , ret ;
priv - > hwmon_name = devm_kstrdup ( dev , dev_name ( dev ) , GFP_KERNEL ) ;
if ( ! priv - > hwmon_name )
return - ENODEV ;
for ( i = j = 0 ; priv - > hwmon_name [ i ] ; i + + ) {
if ( isalnum ( priv - > hwmon_name [ i ] ) ) {
if ( i ! = j )
priv - > hwmon_name [ j ] = priv - > hwmon_name [ i ] ;
j + + ;
}
}
priv - > hwmon_name [ j ] = ' \0 ' ;
ret = mv3310_hwmon_config ( phydev , true ) ;
if ( ret )
return ret ;
ret = devm_add_action_or_reset ( dev , mv3310_hwmon_disable , phydev ) ;
if ( ret )
return ret ;
priv - > hwmon_dev = devm_hwmon_device_register_with_info ( dev ,
priv - > hwmon_name , phydev ,
& mv3310_hwmon_chip_info , NULL ) ;
return PTR_ERR_OR_ZERO ( priv - > hwmon_dev ) ;
}
# else
static inline int mv3310_hwmon_config ( struct phy_device * phydev , bool enable )
{
return 0 ;
}
static int mv3310_hwmon_probe ( struct phy_device * phydev )
{
return 0 ;
}
# endif
2020-03-03 18:08:45 +00:00
static int mv3310_power_down ( struct phy_device * phydev )
{
return phy_set_bits_mmd ( phydev , MDIO_MMD_VEND2 , MV_V2_PORT_CTRL ,
MV_V2_PORT_CTRL_PWRDOWN ) ;
}
static int mv3310_power_up ( struct phy_device * phydev )
{
return phy_clear_bits_mmd ( phydev , MDIO_MMD_VEND2 , MV_V2_PORT_CTRL ,
MV_V2_PORT_CTRL_PWRDOWN ) ;
}
2020-03-03 18:08:34 +00:00
static int mv3310_reset ( struct phy_device * phydev , u32 unit )
{
2020-03-23 23:05:56 +08:00
int val , err ;
2020-03-03 18:08:34 +00:00
err = phy_modify_mmd ( phydev , MDIO_MMD_PCS , unit + MDIO_CTRL1 ,
MDIO_CTRL1_RESET , MDIO_CTRL1_RESET ) ;
if ( err < 0 )
return err ;
2020-03-23 23:05:56 +08:00
return phy_read_mmd_poll_timeout ( phydev , MDIO_MMD_PCS ,
unit + MDIO_CTRL1 , val ,
! ( val & MDIO_CTRL1_RESET ) ,
5000 , 100000 , true ) ;
2020-03-03 18:08:34 +00:00
}
2020-03-03 18:08:40 +00:00
static int mv3310_get_edpd ( struct phy_device * phydev , u16 * edpd )
{
int val ;
val = phy_read_mmd ( phydev , MDIO_MMD_PCS , MV_PCS_CSCR1 ) ;
if ( val < 0 )
return val ;
switch ( val & MV_PCS_CSCR1_ED_MASK ) {
case MV_PCS_CSCR1_ED_NLP :
* edpd = 1000 ;
break ;
case MV_PCS_CSCR1_ED_RX :
* edpd = ETHTOOL_PHY_EDPD_NO_TX ;
break ;
default :
* edpd = ETHTOOL_PHY_EDPD_DISABLE ;
break ;
}
return 0 ;
}
static int mv3310_set_edpd ( struct phy_device * phydev , u16 edpd )
{
u16 val ;
int err ;
switch ( edpd ) {
case 1000 :
case ETHTOOL_PHY_EDPD_DFLT_TX_MSECS :
val = MV_PCS_CSCR1_ED_NLP ;
break ;
case ETHTOOL_PHY_EDPD_NO_TX :
val = MV_PCS_CSCR1_ED_RX ;
break ;
case ETHTOOL_PHY_EDPD_DISABLE :
val = MV_PCS_CSCR1_ED_OFF ;
break ;
default :
return - EINVAL ;
}
err = phy_modify_mmd_changed ( phydev , MDIO_MMD_PCS , MV_PCS_CSCR1 ,
MV_PCS_CSCR1_ED_MASK , val ) ;
if ( err > 0 )
err = mv3310_reset ( phydev , MV_PCS_BASE_T ) ;
return err ;
}
2019-11-15 19:56:56 +00:00
static int mv3310_sfp_insert ( void * upstream , const struct sfp_eeprom_id * id )
{
struct phy_device * phydev = upstream ;
__ETHTOOL_DECLARE_LINK_MODE_MASK ( support ) = { 0 , } ;
phy_interface_t iface ;
sfp_parse_support ( phydev - > sfp_bus , id , support ) ;
2019-12-11 10:55:59 +00:00
iface = sfp_select_interface ( phydev - > sfp_bus , support ) ;
2019-11-15 19:56:56 +00:00
2020-01-03 20:43:23 +00:00
if ( iface ! = PHY_INTERFACE_MODE_10GBASER ) {
2019-11-15 19:56:56 +00:00
dev_err ( & phydev - > mdio . dev , " incompatible SFP module inserted \n " ) ;
return - EINVAL ;
}
return 0 ;
}
static const struct sfp_upstream_ops mv3310_sfp_ops = {
. attach = phy_sfp_attach ,
. detach = phy_sfp_detach ,
. module_insert = mv3310_sfp_insert ,
} ;
2017-06-05 12:23:16 +01:00
static int mv3310_probe ( struct phy_device * phydev )
{
2018-04-03 10:31:45 +01:00
struct mv3310_priv * priv ;
2017-06-05 12:23:16 +01:00
u32 mmd_mask = MDIO_DEVS_PMAPMD | MDIO_DEVS_AN ;
2018-04-03 10:31:45 +01:00
int ret ;
2017-06-05 12:23:16 +01:00
if ( ! phydev - > is_c45 | |
( phydev - > c45_ids . devices_in_package & mmd_mask ) ! = mmd_mask )
return - ENODEV ;
2019-05-28 10:34:42 +01:00
ret = phy_read_mmd ( phydev , MDIO_MMD_PMAPMD , MV_PMA_BOOT ) ;
if ( ret < 0 )
return ret ;
if ( ret & MV_PMA_BOOT_FATAL ) {
dev_warn ( & phydev - > mdio . dev ,
" PHY failed to boot firmware, status=%04x \n " , ret ) ;
return - ENODEV ;
}
2018-04-03 10:31:45 +01:00
priv = devm_kzalloc ( & phydev - > mdio . dev , sizeof ( * priv ) , GFP_KERNEL ) ;
if ( ! priv )
return - ENOMEM ;
dev_set_drvdata ( & phydev - > mdio . dev , priv ) ;
2020-03-03 18:08:45 +00:00
/* Powering down the port when not in use saves about 600mW */
ret = mv3310_power_down ( phydev ) ;
if ( ret )
return ret ;
2018-04-03 10:31:45 +01:00
ret = mv3310_hwmon_probe ( phydev ) ;
if ( ret )
return ret ;
2019-11-15 19:56:56 +00:00
return phy_sfp_probe ( phydev , & mv3310_sfp_ops ) ;
2018-04-03 10:31:45 +01:00
}
static int mv3310_suspend ( struct phy_device * phydev )
{
2020-03-03 18:08:45 +00:00
return mv3310_power_down ( phydev ) ;
2017-06-05 12:23:16 +01:00
}
2018-04-03 10:31:45 +01:00
static int mv3310_resume ( struct phy_device * phydev )
{
2019-04-02 15:10:28 +02:00
int ret ;
2020-03-03 18:08:45 +00:00
ret = mv3310_power_up ( phydev ) ;
2019-04-02 15:10:28 +02:00
if ( ret )
return ret ;
2018-04-03 10:31:45 +01:00
return mv3310_hwmon_config ( phydev , true ) ;
}
2019-02-23 00:37:42 +01:00
/* Some PHYs in the Alaska family such as the 88X3310 and the 88E2010
* don ' t set bit 14 in PMA Extended Abilities ( 1.11 ) , although they do
* support 2.5 GBASET and 5 GBASET . For these models , we can still read their
* 2.5 G / 5 G extended abilities register ( 1.21 ) . We detect these models based on
* the PMA device identifier , with a mask matching models known to have this
* issue
*/
static bool mv3310_has_pma_ngbaset_quirk ( struct phy_device * phydev )
{
if ( ! ( phydev - > c45_ids . devices_in_package & MDIO_DEVS_PMAPMD ) )
return false ;
/* Only some revisions of the 88X3310 family PMA seem to be impacted */
return ( phydev - > c45_ids . device_ids [ MDIO_MMD_PMAPMD ] &
MV_PHY_ALASKA_NBT_QUIRK_MASK ) = = MV_PHY_ALASKA_NBT_QUIRK_REV ;
}
2017-06-05 12:23:16 +01:00
static int mv3310_config_init ( struct phy_device * phydev )
{
2020-03-03 18:08:45 +00:00
int err ;
2017-06-05 12:23:16 +01:00
/* Check that the PHY interface type is compatible */
if ( phydev - > interface ! = PHY_INTERFACE_MODE_SGMII & &
2019-02-23 00:37:40 +01:00
phydev - > interface ! = PHY_INTERFACE_MODE_2500BASEX & &
2017-06-05 12:23:16 +01:00
phydev - > interface ! = PHY_INTERFACE_MODE_XAUI & &
phydev - > interface ! = PHY_INTERFACE_MODE_RXAUI & &
2020-01-03 20:43:23 +00:00
phydev - > interface ! = PHY_INTERFACE_MODE_10GBASER )
2017-06-05 12:23:16 +01:00
return - ENODEV ;
2020-03-03 18:08:34 +00:00
phydev - > mdix_ctrl = ETH_TP_MDI_AUTO ;
2020-03-03 18:08:45 +00:00
/* Power up so reset works */
err = mv3310_power_up ( phydev ) ;
if ( err )
return err ;
2020-03-03 18:08:40 +00:00
/* Enable EDPD mode - saving 600mW */
return mv3310_set_edpd ( phydev , ETHTOOL_PHY_EDPD_DFLT_TX_MSECS ) ;
2019-02-23 00:37:38 +01:00
}
static int mv3310_get_features ( struct phy_device * phydev )
{
int ret , val ;
2019-02-11 15:25:28 +01:00
ret = genphy_c45_pma_read_abilities ( phydev ) ;
if ( ret )
return ret ;
2017-06-05 12:23:16 +01:00
2019-02-23 00:37:42 +01:00
if ( mv3310_has_pma_ngbaset_quirk ( phydev ) ) {
val = phy_read_mmd ( phydev , MDIO_MMD_PMAPMD ,
MDIO_PMA_NG_EXTABLE ) ;
if ( val < 0 )
return val ;
linkmode_mod_bit ( ETHTOOL_LINK_MODE_2500baseT_Full_BIT ,
phydev - > supported ,
val & MDIO_PMA_NG_EXTABLE_2_5GBT ) ;
linkmode_mod_bit ( ETHTOOL_LINK_MODE_5000baseT_Full_BIT ,
phydev - > supported ,
val & MDIO_PMA_NG_EXTABLE_5GBT ) ;
}
2017-06-05 12:23:16 +01:00
return 0 ;
}
2020-03-03 18:08:34 +00:00
static int mv3310_config_mdix ( struct phy_device * phydev )
{
u16 val ;
int err ;
switch ( phydev - > mdix_ctrl ) {
case ETH_TP_MDI_AUTO :
val = MV_PCS_CSCR1_MDIX_AUTO ;
break ;
case ETH_TP_MDI_X :
val = MV_PCS_CSCR1_MDIX_MDIX ;
break ;
case ETH_TP_MDI :
val = MV_PCS_CSCR1_MDIX_MDI ;
break ;
default :
return - EINVAL ;
}
err = phy_modify_mmd_changed ( phydev , MDIO_MMD_PCS , MV_PCS_CSCR1 ,
MV_PCS_CSCR1_MDIX_MASK , val ) ;
if ( err > 0 )
err = mv3310_reset ( phydev , MV_PCS_BASE_T ) ;
return err ;
}
2017-06-05 12:23:16 +01:00
static int mv3310_config_aneg ( struct phy_device * phydev )
{
bool changed = false ;
2018-11-10 23:43:33 +01:00
u16 reg ;
2017-06-05 12:23:16 +01:00
int ret ;
2020-03-03 18:08:34 +00:00
ret = mv3310_config_mdix ( phydev ) ;
if ( ret < 0 )
return ret ;
2017-12-29 12:46:27 +00:00
2019-02-16 20:44:59 +01:00
if ( phydev - > autoneg = = AUTONEG_DISABLE )
return genphy_c45_pma_setup_forced ( phydev ) ;
2017-06-05 12:23:16 +01:00
2019-02-17 10:30:45 +01:00
ret = genphy_c45_an_config_aneg ( phydev ) ;
2017-06-05 12:23:16 +01:00
if ( ret < 0 )
return ret ;
if ( ret > 0 )
changed = true ;
2019-02-17 10:30:45 +01:00
/* Clause 45 has no standardized support for 1000BaseT, therefore
* use vendor registers for this mode .
*/
2018-11-10 23:43:33 +01:00
reg = linkmode_adv_to_mii_ctrl1000_t ( phydev - > advertising ) ;
2019-02-10 19:58:49 +01:00
ret = phy_modify_mmd_changed ( phydev , MDIO_MMD_AN , MV_AN_CTRL1000 ,
2019-02-06 07:38:43 +01:00
ADVERTISE_1000FULL | ADVERTISE_1000HALF , reg ) ;
2017-06-05 12:23:16 +01:00
if ( ret < 0 )
return ret ;
if ( ret > 0 )
changed = true ;
2019-02-18 21:27:46 +01:00
return genphy_c45_check_and_restart_aneg ( phydev , changed ) ;
2017-06-05 12:23:16 +01:00
}
static int mv3310_aneg_done ( struct phy_device * phydev )
{
int val ;
val = phy_read_mmd ( phydev , MDIO_MMD_PCS , MV_PCS_BASE_R + MDIO_STAT1 ) ;
if ( val < 0 )
return val ;
if ( val & MDIO_STAT1_LSTATUS )
return 1 ;
return genphy_c45_aneg_done ( phydev ) ;
}
2017-12-29 12:46:32 +00:00
static void mv3310_update_interface ( struct phy_device * phydev )
{
if ( ( phydev - > interface = = PHY_INTERFACE_MODE_SGMII | |
2019-02-23 00:37:40 +01:00
phydev - > interface = = PHY_INTERFACE_MODE_2500BASEX | |
2020-01-03 20:43:23 +00:00
phydev - > interface = = PHY_INTERFACE_MODE_10GBASER ) & &
phydev - > link ) {
2017-12-29 12:46:32 +00:00
/* The PHY automatically switches its serdes interface (and
2020-01-03 20:43:23 +00:00
* active PHYXS instance ) between Cisco SGMII , 10 GBase - R and
2019-02-23 00:37:40 +01:00
* 2500 BaseX modes according to the speed . Florian suggests
* setting phydev - > interface to communicate this to the MAC .
* Only do this if we are already in one of the above modes .
2017-12-29 12:46:32 +00:00
*/
2019-02-23 00:37:40 +01:00
switch ( phydev - > speed ) {
case SPEED_10000 :
2020-01-03 20:43:23 +00:00
phydev - > interface = PHY_INTERFACE_MODE_10GBASER ;
2019-02-23 00:37:40 +01:00
break ;
case SPEED_2500 :
phydev - > interface = PHY_INTERFACE_MODE_2500BASEX ;
break ;
case SPEED_1000 :
case SPEED_100 :
case SPEED_10 :
2017-12-29 12:46:32 +00:00
phydev - > interface = PHY_INTERFACE_MODE_SGMII ;
2019-02-23 00:37:40 +01:00
break ;
default :
break ;
}
2017-12-29 12:46:32 +00:00
}
}
2017-06-05 12:23:16 +01:00
/* 10GBASE-ER,LR,LRM,SR do not support autonegotiation. */
2020-02-27 09:46:36 +00:00
static int mv3310_read_status_10gbaser ( struct phy_device * phydev )
2017-06-05 12:23:16 +01:00
{
phydev - > link = 1 ;
phydev - > speed = SPEED_10000 ;
phydev - > duplex = DUPLEX_FULL ;
return 0 ;
}
2020-02-27 09:46:36 +00:00
static int mv3310_read_status_copper ( struct phy_device * phydev )
2017-06-05 12:23:16 +01:00
{
2020-02-27 09:46:36 +00:00
int cssr1 , speed , val ;
2017-06-05 12:23:16 +01:00
2019-02-07 21:41:46 +01:00
val = genphy_c45_read_link ( phydev ) ;
2017-06-05 12:23:16 +01:00
if ( val < 0 )
return val ;
val = phy_read_mmd ( phydev , MDIO_MMD_AN , MDIO_STAT1 ) ;
if ( val < 0 )
return val ;
2020-02-27 09:46:36 +00:00
cssr1 = phy_read_mmd ( phydev , MDIO_MMD_PCS , MV_PCS_CSSR1 ) ;
if ( cssr1 < 0 )
return val ;
/* If the link settings are not resolved, mark the link down */
if ( ! ( cssr1 & MV_PCS_CSSR1_RESOLVED ) ) {
phydev - > link = 0 ;
return 0 ;
}
/* Read the copper link settings */
speed = cssr1 & MV_PCS_CSSR1_SPD1_MASK ;
if ( speed = = MV_PCS_CSSR1_SPD1_SPD2 )
speed | = cssr1 & MV_PCS_CSSR1_SPD2_MASK ;
switch ( speed ) {
case MV_PCS_CSSR1_SPD1_SPD2 | MV_PCS_CSSR1_SPD2_10000 :
phydev - > speed = SPEED_10000 ;
break ;
case MV_PCS_CSSR1_SPD1_SPD2 | MV_PCS_CSSR1_SPD2_5000 :
phydev - > speed = SPEED_5000 ;
break ;
case MV_PCS_CSSR1_SPD1_SPD2 | MV_PCS_CSSR1_SPD2_2500 :
phydev - > speed = SPEED_2500 ;
break ;
case MV_PCS_CSSR1_SPD1_1000 :
phydev - > speed = SPEED_1000 ;
break ;
case MV_PCS_CSSR1_SPD1_100 :
phydev - > speed = SPEED_100 ;
break ;
case MV_PCS_CSSR1_SPD1_10 :
phydev - > speed = SPEED_10 ;
break ;
}
phydev - > duplex = cssr1 & MV_PCS_CSSR1_DUPLEX_FULL ?
DUPLEX_FULL : DUPLEX_HALF ;
phydev - > mdix = cssr1 & MV_PCS_CSSR1_MDIX ?
ETH_TP_MDI_X : ETH_TP_MDI ;
2017-06-05 12:23:16 +01:00
if ( val & MDIO_AN_STAT1_COMPLETE ) {
val = genphy_c45_read_lpa ( phydev ) ;
if ( val < 0 )
return val ;
2018-03-01 10:23:03 +00:00
/* Read the link partner's 1G advertisement */
2017-06-05 12:23:16 +01:00
val = phy_read_mmd ( phydev , MDIO_MMD_AN , MV_AN_STAT1000 ) ;
if ( val < 0 )
return val ;
2018-12-05 21:49:41 +01:00
mii_stat1000_mod_linkmode_lpa_t ( phydev - > lp_advertising , val ) ;
2017-06-05 12:23:16 +01:00
2020-02-27 09:46:36 +00:00
/* Update the pause status */
phy_resolve_aneg_pause ( phydev ) ;
2017-06-05 12:23:16 +01:00
}
2020-02-27 09:46:36 +00:00
return 0 ;
}
2017-06-05 12:23:16 +01:00
2020-02-27 09:46:36 +00:00
static int mv3310_read_status ( struct phy_device * phydev )
{
int err , val ;
2017-12-29 12:46:27 +00:00
2020-02-27 09:46:36 +00:00
phydev - > speed = SPEED_UNKNOWN ;
phydev - > duplex = DUPLEX_UNKNOWN ;
linkmode_zero ( phydev - > lp_advertising ) ;
phydev - > link = 0 ;
phydev - > pause = 0 ;
phydev - > asym_pause = 0 ;
phydev - > mdix = ETH_TP_MDI_INVALID ;
2017-12-29 12:46:27 +00:00
2020-02-27 09:46:36 +00:00
val = phy_read_mmd ( phydev , MDIO_MMD_PCS , MV_PCS_BASE_R + MDIO_STAT1 ) ;
if ( val < 0 )
return val ;
if ( val & MDIO_STAT1_LSTATUS )
err = mv3310_read_status_10gbaser ( phydev ) ;
else
err = mv3310_read_status_copper ( phydev ) ;
if ( err < 0 )
return err ;
if ( phydev - > link )
mv3310_update_interface ( phydev ) ;
2017-06-05 12:23:16 +01:00
return 0 ;
}
2020-03-03 18:08:40 +00:00
static int mv3310_get_tunable ( struct phy_device * phydev ,
struct ethtool_tunable * tuna , void * data )
{
switch ( tuna - > id ) {
case ETHTOOL_PHY_EDPD :
return mv3310_get_edpd ( phydev , data ) ;
default :
return - EOPNOTSUPP ;
}
}
static int mv3310_set_tunable ( struct phy_device * phydev ,
struct ethtool_tunable * tuna , const void * data )
{
switch ( tuna - > id ) {
case ETHTOOL_PHY_EDPD :
return mv3310_set_edpd ( phydev , * ( u16 * ) data ) ;
default :
return - EOPNOTSUPP ;
}
}
2017-06-05 12:23:16 +01:00
static struct phy_driver mv3310_drivers [ ] = {
{
2019-02-23 00:37:41 +01:00
. phy_id = MARVELL_PHY_ID_88X3310 ,
2017-11-28 14:26:30 +01:00
. phy_id_mask = MARVELL_PHY_ID_MASK ,
2017-06-05 12:23:16 +01:00
. name = " mv88x3310 " ,
2019-02-23 00:37:38 +01:00
. get_features = mv3310_get_features ,
2019-03-02 17:13:11 +01:00
. soft_reset = genphy_no_soft_reset ,
2017-06-05 12:23:16 +01:00
. config_init = mv3310_config_init ,
2018-04-03 10:31:45 +01:00
. probe = mv3310_probe ,
. suspend = mv3310_suspend ,
. resume = mv3310_resume ,
2017-06-05 12:23:16 +01:00
. config_aneg = mv3310_config_aneg ,
. aneg_done = mv3310_aneg_done ,
. read_status = mv3310_read_status ,
2020-03-03 18:08:40 +00:00
. get_tunable = mv3310_get_tunable ,
. set_tunable = mv3310_set_tunable ,
2017-06-05 12:23:16 +01:00
} ,
2019-02-23 00:37:44 +01:00
{
. phy_id = MARVELL_PHY_ID_88E2110 ,
. phy_id_mask = MARVELL_PHY_ID_MASK ,
. name = " mv88x2110 " ,
. probe = mv3310_probe ,
2019-04-02 15:10:29 +02:00
. suspend = mv3310_suspend ,
. resume = mv3310_resume ,
2019-03-02 17:13:11 +01:00
. soft_reset = genphy_no_soft_reset ,
2019-02-23 00:37:44 +01:00
. config_init = mv3310_config_init ,
. config_aneg = mv3310_config_aneg ,
. aneg_done = mv3310_aneg_done ,
. read_status = mv3310_read_status ,
2020-03-03 18:08:40 +00:00
. get_tunable = mv3310_get_tunable ,
. set_tunable = mv3310_set_tunable ,
2019-02-23 00:37:44 +01:00
} ,
2017-06-05 12:23:16 +01:00
} ;
module_phy_driver ( mv3310_drivers ) ;
static struct mdio_device_id __maybe_unused mv3310_tbl [ ] = {
2019-02-23 00:37:41 +01:00
{ MARVELL_PHY_ID_88X3310 , MARVELL_PHY_ID_MASK } ,
2019-02-23 00:37:44 +01:00
{ MARVELL_PHY_ID_88E2110 , MARVELL_PHY_ID_MASK } ,
2017-06-05 12:23:16 +01:00
{ } ,
} ;
MODULE_DEVICE_TABLE ( mdio , mv3310_tbl ) ;
MODULE_DESCRIPTION ( " Marvell Alaska X 10Gigabit Ethernet PHY driver (MV88X3310) " ) ;
MODULE_LICENSE ( " GPL " ) ;