2019-01-21 21:05:50 +03:00
// SPDX-License-Identifier: GPL-2.0+
2017-06-05 14:23:16 +03: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 15:46:22 +03:00
* via observation and experimentation for a setup using single - lane Serdes :
2017-06-05 14:23:16 +03: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 15:46:22 +03:00
* With XAUI , observation shows :
*
* XAUI PHYXS - - < appropriate PCS as above >
*
* and no switching of the host interface mode occurs .
*
2017-06-05 14:23:16 +03: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 12:31:45 +03:00
# include <linux/ctype.h>
# include <linux/hwmon.h>
2017-11-28 16:26:30 +03:00
# include <linux/marvell_phy.h>
2018-04-03 12:31:45 +03:00
# include <linux/phy.h>
2017-06-05 14:23:16 +03:00
2019-02-23 02:37:42 +03:00
# define MV_PHY_ALASKA_NBT_QUIRK_MASK 0xfffffffe
# define MV_PHY_ALASKA_NBT_QUIRK_REV (MARVELL_PHY_ID_88X3310 | 0xa)
2017-06-05 14:23:16 +03:00
enum {
MV_PCS_BASE_T = 0x0000 ,
MV_PCS_BASE_R = 0x1000 ,
MV_PCS_1000BASEX = 0x2000 ,
2017-12-29 15:46:27 +03:00
MV_PCS_PAIRSWAP = 0x8182 ,
MV_PCS_PAIRSWAP_MASK = 0x0003 ,
MV_PCS_PAIRSWAP_AB = 0x0002 ,
MV_PCS_PAIRSWAP_NONE = 0x0003 ,
2017-06-05 14:23:16 +03: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 12:31:45 +03:00
/* Vendor2 MMD registers */
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 14:23:16 +03:00
} ;
2018-04-03 12:31:45 +03: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 21:58:49 +03:00
return phy_modify_mmd ( phydev , MDIO_MMD_VEND2 , MV_V2_TEMP_CTRL ,
MV_V2_TEMP_CTRL_MASK , val ) ;
2018-04-03 12:31:45 +03: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
2017-06-05 14:23:16 +03:00
static int mv3310_probe ( struct phy_device * phydev )
{
2018-04-03 12:31:45 +03:00
struct mv3310_priv * priv ;
2017-06-05 14:23:16 +03:00
u32 mmd_mask = MDIO_DEVS_PMAPMD | MDIO_DEVS_AN ;
2018-04-03 12:31:45 +03:00
int ret ;
2017-06-05 14:23:16 +03:00
if ( ! phydev - > is_c45 | |
( phydev - > c45_ids . devices_in_package & mmd_mask ) ! = mmd_mask )
return - ENODEV ;
2018-04-03 12:31:45 +03:00
priv = devm_kzalloc ( & phydev - > mdio . dev , sizeof ( * priv ) , GFP_KERNEL ) ;
if ( ! priv )
return - ENOMEM ;
dev_set_drvdata ( & phydev - > mdio . dev , priv ) ;
ret = mv3310_hwmon_probe ( phydev ) ;
if ( ret )
return ret ;
return 0 ;
}
static int mv3310_suspend ( struct phy_device * phydev )
{
2017-06-05 14:23:16 +03:00
return 0 ;
}
2018-04-03 12:31:45 +03:00
static int mv3310_resume ( struct phy_device * phydev )
{
return mv3310_hwmon_config ( phydev , true ) ;
}
2019-02-23 02:37:42 +03: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 14:23:16 +03:00
static int mv3310_config_init ( struct phy_device * phydev )
{
/* Check that the PHY interface type is compatible */
if ( phydev - > interface ! = PHY_INTERFACE_MODE_SGMII & &
2019-02-23 02:37:40 +03:00
phydev - > interface ! = PHY_INTERFACE_MODE_2500BASEX & &
2017-06-05 14:23:16 +03:00
phydev - > interface ! = PHY_INTERFACE_MODE_XAUI & &
phydev - > interface ! = PHY_INTERFACE_MODE_RXAUI & &
phydev - > interface ! = PHY_INTERFACE_MODE_10GKR )
return - ENODEV ;
2019-02-23 02:37:38 +03:00
return 0 ;
}
static int mv3310_get_features ( struct phy_device * phydev )
{
int ret , val ;
2019-02-11 17:25:28 +03:00
ret = genphy_c45_pma_read_abilities ( phydev ) ;
if ( ret )
return ret ;
2017-06-05 14:23:16 +03:00
2019-02-23 02:37:42 +03: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 14:23:16 +03:00
return 0 ;
}
static int mv3310_config_aneg ( struct phy_device * phydev )
{
bool changed = false ;
2018-11-11 01:43:33 +03:00
u16 reg ;
2017-06-05 14:23:16 +03:00
int ret ;
2017-12-29 15:46:27 +03:00
/* We don't support manual MDI control */
phydev - > mdix_ctrl = ETH_TP_MDI_AUTO ;
2019-02-16 22:44:59 +03:00
if ( phydev - > autoneg = = AUTONEG_DISABLE )
return genphy_c45_pma_setup_forced ( phydev ) ;
2017-06-05 14:23:16 +03:00
2019-02-17 12:30:45 +03:00
ret = genphy_c45_an_config_aneg ( phydev ) ;
2017-06-05 14:23:16 +03:00
if ( ret < 0 )
return ret ;
if ( ret > 0 )
changed = true ;
2019-02-17 12:30:45 +03:00
/* Clause 45 has no standardized support for 1000BaseT, therefore
* use vendor registers for this mode .
*/
2018-11-11 01:43:33 +03:00
reg = linkmode_adv_to_mii_ctrl1000_t ( phydev - > advertising ) ;
2019-02-10 21:58:49 +03:00
ret = phy_modify_mmd_changed ( phydev , MDIO_MMD_AN , MV_AN_CTRL1000 ,
2019-02-06 09:38:43 +03:00
ADVERTISE_1000FULL | ADVERTISE_1000HALF , reg ) ;
2017-06-05 14:23:16 +03:00
if ( ret < 0 )
return ret ;
if ( ret > 0 )
changed = true ;
2019-02-18 23:27:46 +03:00
return genphy_c45_check_and_restart_aneg ( phydev , changed ) ;
2017-06-05 14:23:16 +03: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 15:46:32 +03:00
static void mv3310_update_interface ( struct phy_device * phydev )
{
if ( ( phydev - > interface = = PHY_INTERFACE_MODE_SGMII | |
2019-02-23 02:37:40 +03:00
phydev - > interface = = PHY_INTERFACE_MODE_2500BASEX | |
2017-12-29 15:46:32 +03:00
phydev - > interface = = PHY_INTERFACE_MODE_10GKR ) & & phydev - > link ) {
/* The PHY automatically switches its serdes interface (and
2019-02-23 02:37:40 +03:00
* active PHYXS instance ) between Cisco SGMII , 10 GBase - KR and
* 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 15:46:32 +03:00
*/
2019-02-23 02:37:40 +03:00
switch ( phydev - > speed ) {
case SPEED_10000 :
2017-12-29 15:46:32 +03:00
phydev - > interface = PHY_INTERFACE_MODE_10GKR ;
2019-02-23 02:37:40 +03:00
break ;
case SPEED_2500 :
phydev - > interface = PHY_INTERFACE_MODE_2500BASEX ;
break ;
case SPEED_1000 :
case SPEED_100 :
case SPEED_10 :
2017-12-29 15:46:32 +03:00
phydev - > interface = PHY_INTERFACE_MODE_SGMII ;
2019-02-23 02:37:40 +03:00
break ;
default :
break ;
}
2017-12-29 15:46:32 +03:00
}
}
2017-06-05 14:23:16 +03:00
/* 10GBASE-ER,LR,LRM,SR do not support autonegotiation. */
static int mv3310_read_10gbr_status ( struct phy_device * phydev )
{
phydev - > link = 1 ;
phydev - > speed = SPEED_10000 ;
phydev - > duplex = DUPLEX_FULL ;
2017-12-29 15:46:32 +03:00
mv3310_update_interface ( phydev ) ;
2017-06-05 14:23:16 +03:00
return 0 ;
}
static int mv3310_read_status ( struct phy_device * phydev )
{
int val ;
phydev - > speed = SPEED_UNKNOWN ;
phydev - > duplex = DUPLEX_UNKNOWN ;
2018-11-11 01:43:34 +03:00
linkmode_zero ( phydev - > lp_advertising ) ;
2017-06-05 14:23:16 +03:00
phydev - > link = 0 ;
phydev - > pause = 0 ;
phydev - > asym_pause = 0 ;
2017-12-29 15:46:27 +03:00
phydev - > mdix = 0 ;
2017-06-05 14:23:16 +03: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 )
return mv3310_read_10gbr_status ( phydev ) ;
2019-02-07 23:41:46 +03:00
val = genphy_c45_read_link ( phydev ) ;
2017-06-05 14:23:16 +03:00
if ( val < 0 )
return val ;
val = phy_read_mmd ( phydev , MDIO_MMD_AN , MDIO_STAT1 ) ;
if ( val < 0 )
return val ;
if ( val & MDIO_AN_STAT1_COMPLETE ) {
val = genphy_c45_read_lpa ( phydev ) ;
if ( val < 0 )
return val ;
2018-03-01 13:23:03 +03:00
/* Read the link partner's 1G advertisement */
2017-06-05 14:23:16 +03:00
val = phy_read_mmd ( phydev , MDIO_MMD_AN , MV_AN_STAT1000 ) ;
if ( val < 0 )
return val ;
2018-12-05 23:49:41 +03:00
mii_stat1000_mod_linkmode_lpa_t ( phydev - > lp_advertising , val ) ;
2017-06-05 14:23:16 +03:00
2017-12-29 15:46:43 +03:00
if ( phydev - > autoneg = = AUTONEG_ENABLE )
phy_resolve_aneg_linkmode ( phydev ) ;
2017-06-05 14:23:16 +03:00
}
if ( phydev - > autoneg ! = AUTONEG_ENABLE ) {
val = genphy_c45_read_pma ( phydev ) ;
if ( val < 0 )
return val ;
}
2017-12-29 15:46:27 +03:00
if ( phydev - > speed = = SPEED_10000 ) {
val = genphy_c45_read_mdix ( phydev ) ;
if ( val < 0 )
return val ;
} else {
val = phy_read_mmd ( phydev , MDIO_MMD_PCS , MV_PCS_PAIRSWAP ) ;
if ( val < 0 )
return val ;
switch ( val & MV_PCS_PAIRSWAP_MASK ) {
case MV_PCS_PAIRSWAP_AB :
phydev - > mdix = ETH_TP_MDI_X ;
break ;
case MV_PCS_PAIRSWAP_NONE :
phydev - > mdix = ETH_TP_MDI ;
break ;
default :
phydev - > mdix = ETH_TP_MDI_INVALID ;
break ;
}
}
2017-12-29 15:46:32 +03:00
mv3310_update_interface ( phydev ) ;
2017-06-05 14:23:16 +03:00
return 0 ;
}
static struct phy_driver mv3310_drivers [ ] = {
{
2019-02-23 02:37:41 +03:00
. phy_id = MARVELL_PHY_ID_88X3310 ,
2017-11-28 16:26:30 +03:00
. phy_id_mask = MARVELL_PHY_ID_MASK ,
2017-06-05 14:23:16 +03:00
. name = " mv88x3310 " ,
2019-02-23 02:37:38 +03:00
. get_features = mv3310_get_features ,
2019-03-02 19:13:11 +03:00
. soft_reset = genphy_no_soft_reset ,
2017-06-05 14:23:16 +03:00
. config_init = mv3310_config_init ,
2018-04-03 12:31:45 +03:00
. probe = mv3310_probe ,
. suspend = mv3310_suspend ,
. resume = mv3310_resume ,
2017-06-05 14:23:16 +03:00
. config_aneg = mv3310_config_aneg ,
. aneg_done = mv3310_aneg_done ,
. read_status = mv3310_read_status ,
} ,
2019-02-23 02:37:44 +03:00
{
. phy_id = MARVELL_PHY_ID_88E2110 ,
. phy_id_mask = MARVELL_PHY_ID_MASK ,
. name = " mv88x2110 " ,
2019-02-25 19:14:07 +03:00
. get_features = genphy_c45_pma_read_abilities ,
2019-02-23 02:37:44 +03:00
. probe = mv3310_probe ,
2019-03-02 19:13:11 +03:00
. soft_reset = genphy_no_soft_reset ,
2019-02-23 02:37:44 +03:00
. config_init = mv3310_config_init ,
. config_aneg = mv3310_config_aneg ,
. aneg_done = mv3310_aneg_done ,
. read_status = mv3310_read_status ,
} ,
2017-06-05 14:23:16 +03:00
} ;
module_phy_driver ( mv3310_drivers ) ;
static struct mdio_device_id __maybe_unused mv3310_tbl [ ] = {
2019-02-23 02:37:41 +03:00
{ MARVELL_PHY_ID_88X3310 , MARVELL_PHY_ID_MASK } ,
2019-02-23 02:37:44 +03:00
{ MARVELL_PHY_ID_88E2110 , MARVELL_PHY_ID_MASK } ,
2017-06-05 14:23:16 +03:00
{ } ,
} ;
MODULE_DEVICE_TABLE ( mdio , mv3310_tbl ) ;
MODULE_DESCRIPTION ( " Marvell Alaska X 10Gigabit Ethernet PHY driver (MV88X3310) " ) ;
MODULE_LICENSE ( " GPL " ) ;