2019-01-21 19:05:50 +01:00
// SPDX-License-Identifier: GPL-2.0
2015-10-06 12:25:48 -07:00
/*
2017-03-13 17:41:31 -07:00
* Copyright ( C ) 2015 - 2017 Broadcom
2015-10-06 12:25:48 -07:00
*/
# include "bcm-phy-lib.h"
2020-05-13 18:35:23 +02:00
# include <linux/bitfield.h>
2015-10-06 12:25:48 -07:00
# include <linux/brcmphy.h>
2023-05-11 10:21:09 -07:00
# include <linux/etherdevice.h>
2015-10-06 12:25:48 -07:00
# include <linux/export.h>
# include <linux/mdio.h>
2015-10-15 10:37:13 -07:00
# include <linux/module.h>
2015-10-06 12:25:48 -07:00
# include <linux/phy.h>
2016-11-29 09:57:17 -08:00
# include <linux/ethtool.h>
2020-05-13 18:35:23 +02:00
# include <linux/ethtool_netlink.h>
2023-05-11 10:21:09 -07:00
# include <linux/netdevice.h>
2015-10-06 12:25:48 -07:00
# define MII_BCM_CHANNEL_WIDTH 0x2000
# define BCM_CL45VEN_EEE_ADV 0x3c
2020-05-13 18:35:21 +02:00
int __bcm_phy_write_exp ( struct phy_device * phydev , u16 reg , u16 val )
2015-10-06 12:25:48 -07:00
{
int rc ;
2020-05-13 18:35:21 +02:00
rc = __phy_write ( phydev , MII_BCM54XX_EXP_SEL , reg ) ;
2015-10-06 12:25:48 -07:00
if ( rc < 0 )
return rc ;
2020-05-13 18:35:21 +02:00
return __phy_write ( phydev , MII_BCM54XX_EXP_DATA , val ) ;
}
EXPORT_SYMBOL_GPL ( __bcm_phy_write_exp ) ;
int bcm_phy_write_exp ( struct phy_device * phydev , u16 reg , u16 val )
{
int rc ;
phy_lock_mdio_bus ( phydev ) ;
rc = __bcm_phy_write_exp ( phydev , reg , val ) ;
phy_unlock_mdio_bus ( phydev ) ;
return rc ;
2015-10-06 12:25:48 -07:00
}
EXPORT_SYMBOL_GPL ( bcm_phy_write_exp ) ;
2020-05-13 18:35:21 +02:00
int __bcm_phy_read_exp ( struct phy_device * phydev , u16 reg )
2015-10-06 12:25:48 -07:00
{
int val ;
2020-05-13 18:35:21 +02:00
val = __phy_write ( phydev , MII_BCM54XX_EXP_SEL , reg ) ;
2015-10-06 12:25:48 -07:00
if ( val < 0 )
return val ;
2020-05-13 18:35:21 +02:00
val = __phy_read ( phydev , MII_BCM54XX_EXP_DATA ) ;
2015-10-06 12:25:48 -07:00
/* Restore default value. It's O.K. if this write fails. */
2020-05-13 18:35:21 +02:00
__phy_write ( phydev , MII_BCM54XX_EXP_SEL , 0 ) ;
2015-10-06 12:25:48 -07:00
return val ;
}
2020-05-13 18:35:21 +02:00
EXPORT_SYMBOL_GPL ( __bcm_phy_read_exp ) ;
int bcm_phy_read_exp ( struct phy_device * phydev , u16 reg )
{
int rc ;
phy_lock_mdio_bus ( phydev ) ;
rc = __bcm_phy_read_exp ( phydev , reg ) ;
phy_unlock_mdio_bus ( phydev ) ;
return rc ;
}
2015-10-06 12:25:48 -07:00
EXPORT_SYMBOL_GPL ( bcm_phy_read_exp ) ;
2020-05-13 18:35:22 +02:00
int __bcm_phy_modify_exp ( struct phy_device * phydev , u16 reg , u16 mask , u16 set )
{
int new , ret ;
ret = __phy_write ( phydev , MII_BCM54XX_EXP_SEL , reg ) ;
if ( ret < 0 )
return ret ;
ret = __phy_read ( phydev , MII_BCM54XX_EXP_DATA ) ;
if ( ret < 0 )
return ret ;
new = ( ret & ~ mask ) | set ;
if ( new = = ret )
return 0 ;
return __phy_write ( phydev , MII_BCM54XX_EXP_DATA , new ) ;
}
EXPORT_SYMBOL_GPL ( __bcm_phy_modify_exp ) ;
int bcm_phy_modify_exp ( struct phy_device * phydev , u16 reg , u16 mask , u16 set )
{
int ret ;
phy_lock_mdio_bus ( phydev ) ;
ret = __bcm_phy_modify_exp ( phydev , reg , mask , set ) ;
phy_unlock_mdio_bus ( phydev ) ;
return ret ;
}
EXPORT_SYMBOL_GPL ( bcm_phy_modify_exp ) ;
2016-11-22 11:40:54 -08:00
int bcm54xx_auxctl_read ( struct phy_device * phydev , u16 regnum )
{
/* The register must be written to both the Shadow Register Select and
* the Shadow Read Register Selector
*/
2018-05-22 16:22:26 -07:00
phy_write ( phydev , MII_BCM54XX_AUX_CTL , MII_BCM54XX_AUXCTL_SHDWSEL_MASK |
2016-11-22 11:40:54 -08:00
regnum < < MII_BCM54XX_AUXCTL_SHDWSEL_READ_SHIFT ) ;
return phy_read ( phydev , MII_BCM54XX_AUX_CTL ) ;
}
EXPORT_SYMBOL_GPL ( bcm54xx_auxctl_read ) ;
int bcm54xx_auxctl_write ( struct phy_device * phydev , u16 regnum , u16 val )
{
return phy_write ( phydev , MII_BCM54XX_AUX_CTL , regnum | val ) ;
}
EXPORT_SYMBOL ( bcm54xx_auxctl_write ) ;
2015-10-06 12:25:48 -07:00
int bcm_phy_write_misc ( struct phy_device * phydev ,
u16 reg , u16 chl , u16 val )
{
int rc ;
int tmp ;
rc = phy_write ( phydev , MII_BCM54XX_AUX_CTL ,
MII_BCM54XX_AUXCTL_SHDWSEL_MISC ) ;
if ( rc < 0 )
return rc ;
tmp = phy_read ( phydev , MII_BCM54XX_AUX_CTL ) ;
tmp | = MII_BCM54XX_AUXCTL_ACTL_SMDSP_ENA ;
rc = phy_write ( phydev , MII_BCM54XX_AUX_CTL , tmp ) ;
if ( rc < 0 )
return rc ;
tmp = ( chl * MII_BCM_CHANNEL_WIDTH ) | reg ;
rc = bcm_phy_write_exp ( phydev , tmp , val ) ;
return rc ;
}
EXPORT_SYMBOL_GPL ( bcm_phy_write_misc ) ;
int bcm_phy_read_misc ( struct phy_device * phydev ,
u16 reg , u16 chl )
{
int rc ;
int tmp ;
rc = phy_write ( phydev , MII_BCM54XX_AUX_CTL ,
MII_BCM54XX_AUXCTL_SHDWSEL_MISC ) ;
if ( rc < 0 )
return rc ;
tmp = phy_read ( phydev , MII_BCM54XX_AUX_CTL ) ;
tmp | = MII_BCM54XX_AUXCTL_ACTL_SMDSP_ENA ;
rc = phy_write ( phydev , MII_BCM54XX_AUX_CTL , tmp ) ;
if ( rc < 0 )
return rc ;
tmp = ( chl * MII_BCM_CHANNEL_WIDTH ) | reg ;
rc = bcm_phy_read_exp ( phydev , tmp ) ;
return rc ;
}
EXPORT_SYMBOL_GPL ( bcm_phy_read_misc ) ;
int bcm_phy_ack_intr ( struct phy_device * phydev )
{
int reg ;
/* Clear pending interrupts. */
reg = phy_read ( phydev , MII_BCM54XX_ISR ) ;
if ( reg < 0 )
return reg ;
return 0 ;
}
EXPORT_SYMBOL_GPL ( bcm_phy_ack_intr ) ;
int bcm_phy_config_intr ( struct phy_device * phydev )
{
2020-11-01 14:51:07 +02:00
int reg , err ;
2015-10-06 12:25:48 -07:00
reg = phy_read ( phydev , MII_BCM54XX_ECR ) ;
if ( reg < 0 )
return reg ;
2020-11-01 14:51:07 +02:00
if ( phydev - > interrupts = = PHY_INTERRUPT_ENABLED ) {
err = bcm_phy_ack_intr ( phydev ) ;
if ( err )
return err ;
2015-10-06 12:25:48 -07:00
reg & = ~ MII_BCM54XX_ECR_IM ;
2020-11-01 14:51:07 +02:00
err = phy_write ( phydev , MII_BCM54XX_ECR , reg ) ;
} else {
2015-10-06 12:25:48 -07:00
reg | = MII_BCM54XX_ECR_IM ;
2020-11-01 14:51:07 +02:00
err = phy_write ( phydev , MII_BCM54XX_ECR , reg ) ;
if ( err )
return err ;
2015-10-06 12:25:48 -07:00
2020-11-01 14:51:07 +02:00
err = bcm_phy_ack_intr ( phydev ) ;
}
return err ;
2015-10-06 12:25:48 -07:00
}
EXPORT_SYMBOL_GPL ( bcm_phy_config_intr ) ;
2020-11-01 14:51:06 +02:00
irqreturn_t bcm_phy_handle_interrupt ( struct phy_device * phydev )
{
int irq_status , irq_mask ;
irq_status = phy_read ( phydev , MII_BCM54XX_ISR ) ;
if ( irq_status < 0 ) {
phy_error ( phydev ) ;
return IRQ_NONE ;
}
/* If a bit from the Interrupt Mask register is set, the corresponding
* bit from the Interrupt Status register is masked . So read the IMR
* and then flip the bits to get the list of possible interrupt
* sources .
*/
irq_mask = phy_read ( phydev , MII_BCM54XX_IMR ) ;
if ( irq_mask < 0 ) {
phy_error ( phydev ) ;
return IRQ_NONE ;
}
irq_mask = ~ irq_mask ;
if ( ! ( irq_status & irq_mask ) )
return IRQ_NONE ;
phy_trigger_machine ( phydev ) ;
return IRQ_HANDLED ;
}
EXPORT_SYMBOL_GPL ( bcm_phy_handle_interrupt ) ;
2015-10-06 12:25:48 -07:00
int bcm_phy_read_shadow ( struct phy_device * phydev , u16 shadow )
{
phy_write ( phydev , MII_BCM54XX_SHD , MII_BCM54XX_SHD_VAL ( shadow ) ) ;
return MII_BCM54XX_SHD_DATA ( phy_read ( phydev , MII_BCM54XX_SHD ) ) ;
}
EXPORT_SYMBOL_GPL ( bcm_phy_read_shadow ) ;
int bcm_phy_write_shadow ( struct phy_device * phydev , u16 shadow ,
u16 val )
{
return phy_write ( phydev , MII_BCM54XX_SHD ,
MII_BCM54XX_SHD_WRITE |
MII_BCM54XX_SHD_VAL ( shadow ) |
MII_BCM54XX_SHD_DATA ( val ) ) ;
}
EXPORT_SYMBOL_GPL ( bcm_phy_write_shadow ) ;
2020-04-20 20:21:11 +02:00
int __bcm_phy_read_rdb ( struct phy_device * phydev , u16 rdb )
{
int val ;
val = __phy_write ( phydev , MII_BCM54XX_RDB_ADDR , rdb ) ;
if ( val < 0 )
return val ;
return __phy_read ( phydev , MII_BCM54XX_RDB_DATA ) ;
}
EXPORT_SYMBOL_GPL ( __bcm_phy_read_rdb ) ;
int bcm_phy_read_rdb ( struct phy_device * phydev , u16 rdb )
{
int ret ;
phy_lock_mdio_bus ( phydev ) ;
ret = __bcm_phy_read_rdb ( phydev , rdb ) ;
phy_unlock_mdio_bus ( phydev ) ;
return ret ;
}
EXPORT_SYMBOL_GPL ( bcm_phy_read_rdb ) ;
int __bcm_phy_write_rdb ( struct phy_device * phydev , u16 rdb , u16 val )
{
int ret ;
ret = __phy_write ( phydev , MII_BCM54XX_RDB_ADDR , rdb ) ;
if ( ret < 0 )
return ret ;
return __phy_write ( phydev , MII_BCM54XX_RDB_DATA , val ) ;
}
EXPORT_SYMBOL_GPL ( __bcm_phy_write_rdb ) ;
int bcm_phy_write_rdb ( struct phy_device * phydev , u16 rdb , u16 val )
{
int ret ;
phy_lock_mdio_bus ( phydev ) ;
ret = __bcm_phy_write_rdb ( phydev , rdb , val ) ;
phy_unlock_mdio_bus ( phydev ) ;
return ret ;
}
EXPORT_SYMBOL_GPL ( bcm_phy_write_rdb ) ;
int __bcm_phy_modify_rdb ( struct phy_device * phydev , u16 rdb , u16 mask , u16 set )
{
int new , ret ;
ret = __phy_write ( phydev , MII_BCM54XX_RDB_ADDR , rdb ) ;
if ( ret < 0 )
return ret ;
ret = __phy_read ( phydev , MII_BCM54XX_RDB_DATA ) ;
if ( ret < 0 )
return ret ;
new = ( ret & ~ mask ) | set ;
if ( new = = ret )
return 0 ;
return __phy_write ( phydev , MII_BCM54XX_RDB_DATA , new ) ;
}
EXPORT_SYMBOL_GPL ( __bcm_phy_modify_rdb ) ;
int bcm_phy_modify_rdb ( struct phy_device * phydev , u16 rdb , u16 mask , u16 set )
{
int ret ;
phy_lock_mdio_bus ( phydev ) ;
ret = __bcm_phy_modify_rdb ( phydev , rdb , mask , set ) ;
phy_unlock_mdio_bus ( phydev ) ;
return ret ;
}
EXPORT_SYMBOL_GPL ( bcm_phy_modify_rdb ) ;
2015-10-06 12:25:48 -07:00
int bcm_phy_enable_apd ( struct phy_device * phydev , bool dll_pwr_down )
{
int val ;
if ( dll_pwr_down ) {
val = bcm_phy_read_shadow ( phydev , BCM54XX_SHD_SCR3 ) ;
if ( val < 0 )
return val ;
val | = BCM54XX_SHD_SCR3_DLLAPD_DIS ;
bcm_phy_write_shadow ( phydev , BCM54XX_SHD_SCR3 , val ) ;
}
val = bcm_phy_read_shadow ( phydev , BCM54XX_SHD_APD ) ;
if ( val < 0 )
return val ;
/* Clear APD bits */
val & = BCM_APD_CLR_MASK ;
if ( phydev - > autoneg = = AUTONEG_ENABLE )
val | = BCM54XX_SHD_APD_EN ;
else
val | = BCM_NO_ANEG_APD_EN ;
/* Enable energy detect single link pulse for easy wakeup */
val | = BCM_APD_SINGLELP_EN ;
/* Enable Auto Power-Down (APD) for the PHY */
return bcm_phy_write_shadow ( phydev , BCM54XX_SHD_APD , val ) ;
}
EXPORT_SYMBOL_GPL ( bcm_phy_enable_apd ) ;
2016-11-22 11:40:56 -08:00
int bcm_phy_set_eee ( struct phy_device * phydev , bool enable )
2015-10-06 12:25:48 -07:00
{
2021-03-30 15:00:24 -07:00
int val , mask = 0 ;
2015-10-06 12:25:48 -07:00
/* Enable EEE at PHY level */
2017-03-21 16:36:53 +00:00
val = phy_read_mmd ( phydev , MDIO_MMD_AN , BRCM_CL45VEN_EEE_CONTROL ) ;
2015-10-06 12:25:48 -07:00
if ( val < 0 )
return val ;
2016-11-22 11:40:56 -08:00
if ( enable )
val | = LPI_FEATURE_EN | LPI_FEATURE_EN_DIG1000X ;
else
val & = ~ ( LPI_FEATURE_EN | LPI_FEATURE_EN_DIG1000X ) ;
2015-10-06 12:25:48 -07:00
2017-03-21 16:36:53 +00:00
phy_write_mmd ( phydev , MDIO_MMD_AN , BRCM_CL45VEN_EEE_CONTROL , ( u32 ) val ) ;
2015-10-06 12:25:48 -07:00
/* Advertise EEE */
2017-03-21 16:36:53 +00:00
val = phy_read_mmd ( phydev , MDIO_MMD_AN , BCM_CL45VEN_EEE_ADV ) ;
2015-10-06 12:25:48 -07:00
if ( val < 0 )
return val ;
2021-03-30 15:00:24 -07:00
if ( linkmode_test_bit ( ETHTOOL_LINK_MODE_1000baseT_Full_BIT ,
phydev - > supported ) )
mask | = MDIO_EEE_1000T ;
if ( linkmode_test_bit ( ETHTOOL_LINK_MODE_100baseT_Full_BIT ,
phydev - > supported ) )
mask | = MDIO_EEE_100TX ;
2016-11-22 11:40:56 -08:00
if ( enable )
2021-03-30 15:00:24 -07:00
val | = mask ;
2016-11-22 11:40:56 -08:00
else
2021-03-30 15:00:24 -07:00
val & = ~ mask ;
2015-10-06 12:25:48 -07:00
2017-03-21 16:36:53 +00:00
phy_write_mmd ( phydev , MDIO_MMD_AN , BCM_CL45VEN_EEE_ADV , ( u32 ) val ) ;
2015-10-06 12:25:48 -07:00
return 0 ;
}
2016-11-22 11:40:56 -08:00
EXPORT_SYMBOL_GPL ( bcm_phy_set_eee ) ;
2015-10-15 10:37:13 -07:00
2016-11-22 11:40:55 -08:00
int bcm_phy_downshift_get ( struct phy_device * phydev , u8 * count )
{
int val ;
val = bcm54xx_auxctl_read ( phydev , MII_BCM54XX_AUXCTL_SHDWSEL_MISC ) ;
if ( val < 0 )
return val ;
/* Check if wirespeed is enabled or not */
if ( ! ( val & MII_BCM54XX_AUXCTL_SHDWSEL_MISC_WIRESPEED_EN ) ) {
* count = DOWNSHIFT_DEV_DISABLE ;
return 0 ;
}
val = bcm_phy_read_shadow ( phydev , BCM54XX_SHD_SCR2 ) ;
if ( val < 0 )
return val ;
/* Downgrade after one link attempt */
if ( val & BCM54XX_SHD_SCR2_WSPD_RTRY_DIS ) {
* count = 1 ;
} else {
/* Downgrade after configured retry count */
val > > = BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_SHIFT ;
val & = BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_MASK ;
* count = val + BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_OFFSET ;
}
return 0 ;
}
EXPORT_SYMBOL_GPL ( bcm_phy_downshift_get ) ;
int bcm_phy_downshift_set ( struct phy_device * phydev , u8 count )
{
int val = 0 , ret = 0 ;
/* Range check the number given */
if ( count - BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_OFFSET >
BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_MASK & &
count ! = DOWNSHIFT_DEV_DEFAULT_COUNT ) {
return - ERANGE ;
}
val = bcm54xx_auxctl_read ( phydev , MII_BCM54XX_AUXCTL_SHDWSEL_MISC ) ;
if ( val < 0 )
return val ;
/* Se the write enable bit */
val | = MII_BCM54XX_AUXCTL_MISC_WREN ;
if ( count = = DOWNSHIFT_DEV_DISABLE ) {
val & = ~ MII_BCM54XX_AUXCTL_SHDWSEL_MISC_WIRESPEED_EN ;
return bcm54xx_auxctl_write ( phydev ,
MII_BCM54XX_AUXCTL_SHDWSEL_MISC ,
val ) ;
} else {
val | = MII_BCM54XX_AUXCTL_SHDWSEL_MISC_WIRESPEED_EN ;
ret = bcm54xx_auxctl_write ( phydev ,
MII_BCM54XX_AUXCTL_SHDWSEL_MISC ,
val ) ;
if ( ret < 0 )
return ret ;
}
val = bcm_phy_read_shadow ( phydev , BCM54XX_SHD_SCR2 ) ;
val & = ~ ( BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_MASK < <
BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_SHIFT |
BCM54XX_SHD_SCR2_WSPD_RTRY_DIS ) ;
switch ( count ) {
case 1 :
val | = BCM54XX_SHD_SCR2_WSPD_RTRY_DIS ;
break ;
case DOWNSHIFT_DEV_DEFAULT_COUNT :
val | = 1 < < BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_SHIFT ;
break ;
default :
val | = ( count - BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_OFFSET ) < <
BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_SHIFT ;
break ;
}
return bcm_phy_write_shadow ( phydev , BCM54XX_SHD_SCR2 , val ) ;
}
EXPORT_SYMBOL_GPL ( bcm_phy_downshift_set ) ;
2016-11-29 09:57:17 -08:00
struct bcm_phy_hw_stat {
const char * string ;
2023-05-31 16:17:29 -07:00
int devad ;
u16 reg ;
2016-11-29 09:57:17 -08:00
u8 shift ;
u8 bits ;
} ;
/* Counters freeze at either 0xffff or 0xff, better than nothing */
static const struct bcm_phy_hw_stat bcm_phy_hw_stats [ ] = {
2023-05-31 16:17:29 -07:00
{ " phy_receive_errors " , - 1 , MII_BRCM_CORE_BASE12 , 0 , 16 } ,
{ " phy_serdes_ber_errors " , - 1 , MII_BRCM_CORE_BASE13 , 8 , 8 } ,
{ " phy_false_carrier_sense_errors " , - 1 , MII_BRCM_CORE_BASE13 , 0 , 8 } ,
{ " phy_local_rcvr_nok " , - 1 , MII_BRCM_CORE_BASE14 , 8 , 8 } ,
{ " phy_remote_rcv_nok " , - 1 , MII_BRCM_CORE_BASE14 , 0 , 8 } ,
{ " phy_lpi_count " , MDIO_MMD_AN , BRCM_CL45VEN_EEE_LPI_CNT , 0 , 16 } ,
2016-11-29 09:57:17 -08:00
} ;
int bcm_phy_get_sset_count ( struct phy_device * phydev )
{
return ARRAY_SIZE ( bcm_phy_hw_stats ) ;
}
EXPORT_SYMBOL_GPL ( bcm_phy_get_sset_count ) ;
void bcm_phy_get_strings ( struct phy_device * phydev , u8 * data )
{
unsigned int i ;
for ( i = 0 ; i < ARRAY_SIZE ( bcm_phy_hw_stats ) ; i + + )
2022-08-30 22:14:52 +02:00
strscpy ( data + i * ETH_GSTRING_LEN ,
2018-03-02 15:08:39 -08:00
bcm_phy_hw_stats [ i ] . string , ETH_GSTRING_LEN ) ;
2016-11-29 09:57:17 -08:00
}
EXPORT_SYMBOL_GPL ( bcm_phy_get_strings ) ;
/* Caller is supposed to provide appropriate storage for the library code to
* access the shadow copy
*/
static u64 bcm_phy_get_stat ( struct phy_device * phydev , u64 * shadow ,
unsigned int i )
{
struct bcm_phy_hw_stat stat = bcm_phy_hw_stats [ i ] ;
int val ;
u64 ret ;
2023-05-31 16:17:29 -07:00
if ( stat . devad < 0 )
val = phy_read ( phydev , stat . reg ) ;
else
val = phy_read_mmd ( phydev , stat . devad , stat . reg ) ;
2016-11-29 09:57:17 -08:00
if ( val < 0 ) {
2018-04-27 16:18:58 +08:00
ret = U64_MAX ;
2016-11-29 09:57:17 -08:00
} else {
val > > = stat . shift ;
val = val & ( ( 1 < < stat . bits ) - 1 ) ;
shadow [ i ] + = val ;
ret = shadow [ i ] ;
}
return ret ;
}
void bcm_phy_get_stats ( struct phy_device * phydev , u64 * shadow ,
struct ethtool_stats * stats , u64 * data )
{
unsigned int i ;
for ( i = 0 ; i < ARRAY_SIZE ( bcm_phy_hw_stats ) ; i + + )
data [ i ] = bcm_phy_get_stat ( phydev , shadow , i ) ;
}
EXPORT_SYMBOL_GPL ( bcm_phy_get_stats ) ;
2019-03-20 12:53:12 -07:00
void bcm_phy_r_rc_cal_reset ( struct phy_device * phydev )
{
/* Reset R_CAL/RC_CAL Engine */
bcm_phy_write_exp_sel ( phydev , 0x00b0 , 0x0010 ) ;
/* Disable Reset R_AL/RC_CAL Engine */
bcm_phy_write_exp_sel ( phydev , 0x00b0 , 0x0000 ) ;
}
EXPORT_SYMBOL_GPL ( bcm_phy_r_rc_cal_reset ) ;
int bcm_phy_28nm_a0b0_afe_config_init ( struct phy_device * phydev )
{
/* Increase VCO range to prevent unlocking problem of PLL at low
* temp
*/
bcm_phy_write_misc ( phydev , PLL_PLLCTRL_1 , 0x0048 ) ;
/* Change Ki to 011 */
bcm_phy_write_misc ( phydev , PLL_PLLCTRL_2 , 0x021b ) ;
/* Disable loading of TVCO buffer to bandgap, set bandgap trim
* to 111
*/
bcm_phy_write_misc ( phydev , PLL_PLLCTRL_4 , 0x0e20 ) ;
/* Adjust bias current trim by -3 */
bcm_phy_write_misc ( phydev , DSP_TAP10 , 0x690b ) ;
/* Switch to CORE_BASE1E */
phy_write ( phydev , MII_BRCM_CORE_BASE1E , 0xd ) ;
bcm_phy_r_rc_cal_reset ( phydev ) ;
/* write AFE_RXCONFIG_0 */
bcm_phy_write_misc ( phydev , AFE_RXCONFIG_0 , 0xeb19 ) ;
/* write AFE_RXCONFIG_1 */
bcm_phy_write_misc ( phydev , AFE_RXCONFIG_1 , 0x9a3f ) ;
/* write AFE_RX_LP_COUNTER */
bcm_phy_write_misc ( phydev , AFE_RX_LP_COUNTER , 0x7fc0 ) ;
/* write AFE_HPF_TRIM_OTHERS */
bcm_phy_write_misc ( phydev , AFE_HPF_TRIM_OTHERS , 0x000b ) ;
/* write AFTE_TX_CONFIG */
bcm_phy_write_misc ( phydev , AFE_TX_CONFIG , 0x0800 ) ;
return 0 ;
}
EXPORT_SYMBOL_GPL ( bcm_phy_28nm_a0b0_afe_config_init ) ;
2020-03-27 21:55:40 +02:00
int bcm_phy_enable_jumbo ( struct phy_device * phydev )
{
int ret ;
ret = bcm54xx_auxctl_read ( phydev , MII_BCM54XX_AUXCTL_SHDWSEL_AUXCTL ) ;
if ( ret < 0 )
return ret ;
/* Enable extended length packet reception */
ret = bcm54xx_auxctl_write ( phydev , MII_BCM54XX_AUXCTL_SHDWSEL_AUXCTL ,
ret | MII_BCM54XX_AUXCTL_ACTL_EXT_PKT_LEN ) ;
if ( ret < 0 )
return ret ;
/* Enable the elastic FIFO for raising the transmission limit from
* 4.5 KB to 10 KB , at the expense of an additional 16 ns in propagation
* latency .
*/
return phy_set_bits ( phydev , MII_BCM54XX_ECR , MII_BCM54XX_ECR_FIFOE ) ;
}
EXPORT_SYMBOL_GPL ( bcm_phy_enable_jumbo ) ;
2020-05-30 22:34:04 +02:00
static int __bcm_phy_enable_rdb_access ( struct phy_device * phydev )
2020-05-13 18:35:23 +02:00
{
return __bcm_phy_write_exp ( phydev , BCM54XX_EXP_REG7E , 0 ) ;
}
2020-05-30 22:34:04 +02:00
static int __bcm_phy_enable_legacy_access ( struct phy_device * phydev )
2020-05-13 18:35:23 +02:00
{
return __bcm_phy_write_rdb ( phydev , BCM54XX_RDB_REG0087 ,
BCM54XX_ACCESS_MODE_LEGACY_EN ) ;
}
static int _bcm_phy_cable_test_start ( struct phy_device * phydev , bool is_rdb )
{
u16 mask , set ;
int ret ;
/* Auto-negotiation must be enabled for cable diagnostics to work, but
* don ' t advertise any capabilities .
*/
phy_write ( phydev , MII_BMCR , BMCR_ANENABLE ) ;
phy_write ( phydev , MII_ADVERTISE , ADVERTISE_CSMA ) ;
phy_write ( phydev , MII_CTRL1000 , 0 ) ;
phy_lock_mdio_bus ( phydev ) ;
if ( is_rdb ) {
ret = __bcm_phy_enable_legacy_access ( phydev ) ;
if ( ret )
goto out ;
}
mask = BCM54XX_ECD_CTRL_CROSS_SHORT_DIS | BCM54XX_ECD_CTRL_UNIT_MASK ;
set = BCM54XX_ECD_CTRL_RUN | BCM54XX_ECD_CTRL_BREAK_LINK |
FIELD_PREP ( BCM54XX_ECD_CTRL_UNIT_MASK ,
BCM54XX_ECD_CTRL_UNIT_CM ) ;
ret = __bcm_phy_modify_exp ( phydev , BCM54XX_EXP_ECD_CTRL , mask , set ) ;
out :
/* re-enable the RDB access even if there was an error */
if ( is_rdb )
ret = __bcm_phy_enable_rdb_access ( phydev ) ? : ret ;
phy_unlock_mdio_bus ( phydev ) ;
return ret ;
}
static int bcm_phy_cable_test_report_trans ( int result )
{
switch ( result ) {
case BCM54XX_ECD_FAULT_TYPE_OK :
return ETHTOOL_A_CABLE_RESULT_CODE_OK ;
case BCM54XX_ECD_FAULT_TYPE_OPEN :
return ETHTOOL_A_CABLE_RESULT_CODE_OPEN ;
case BCM54XX_ECD_FAULT_TYPE_SAME_SHORT :
return ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT ;
case BCM54XX_ECD_FAULT_TYPE_CROSS_SHORT :
return ETHTOOL_A_CABLE_RESULT_CODE_CROSS_SHORT ;
case BCM54XX_ECD_FAULT_TYPE_INVALID :
case BCM54XX_ECD_FAULT_TYPE_BUSY :
default :
return ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC ;
}
}
static bool bcm_phy_distance_valid ( int result )
{
switch ( result ) {
case BCM54XX_ECD_FAULT_TYPE_OPEN :
case BCM54XX_ECD_FAULT_TYPE_SAME_SHORT :
case BCM54XX_ECD_FAULT_TYPE_CROSS_SHORT :
return true ;
}
return false ;
}
static int bcm_phy_report_length ( struct phy_device * phydev , int pair )
{
int val ;
val = __bcm_phy_read_exp ( phydev ,
BCM54XX_EXP_ECD_PAIR_A_LENGTH_RESULTS + pair ) ;
if ( val < 0 )
return val ;
if ( val = = BCM54XX_ECD_LENGTH_RESULTS_INVALID )
return 0 ;
ethnl_cable_test_fault_length ( phydev , pair , val ) ;
return 0 ;
}
static int _bcm_phy_cable_test_get_status ( struct phy_device * phydev ,
bool * finished , bool is_rdb )
{
int pair_a , pair_b , pair_c , pair_d , ret ;
* finished = false ;
phy_lock_mdio_bus ( phydev ) ;
if ( is_rdb ) {
ret = __bcm_phy_enable_legacy_access ( phydev ) ;
if ( ret )
goto out ;
}
ret = __bcm_phy_read_exp ( phydev , BCM54XX_EXP_ECD_CTRL ) ;
if ( ret < 0 )
goto out ;
if ( ret & BCM54XX_ECD_CTRL_IN_PROGRESS ) {
ret = 0 ;
goto out ;
}
ret = __bcm_phy_read_exp ( phydev , BCM54XX_EXP_ECD_FAULT_TYPE ) ;
if ( ret < 0 )
goto out ;
pair_a = FIELD_GET ( BCM54XX_ECD_FAULT_TYPE_PAIR_A_MASK , ret ) ;
pair_b = FIELD_GET ( BCM54XX_ECD_FAULT_TYPE_PAIR_B_MASK , ret ) ;
pair_c = FIELD_GET ( BCM54XX_ECD_FAULT_TYPE_PAIR_C_MASK , ret ) ;
pair_d = FIELD_GET ( BCM54XX_ECD_FAULT_TYPE_PAIR_D_MASK , ret ) ;
ethnl_cable_test_result ( phydev , ETHTOOL_A_CABLE_PAIR_A ,
bcm_phy_cable_test_report_trans ( pair_a ) ) ;
ethnl_cable_test_result ( phydev , ETHTOOL_A_CABLE_PAIR_B ,
bcm_phy_cable_test_report_trans ( pair_b ) ) ;
ethnl_cable_test_result ( phydev , ETHTOOL_A_CABLE_PAIR_C ,
bcm_phy_cable_test_report_trans ( pair_c ) ) ;
ethnl_cable_test_result ( phydev , ETHTOOL_A_CABLE_PAIR_D ,
bcm_phy_cable_test_report_trans ( pair_d ) ) ;
if ( bcm_phy_distance_valid ( pair_a ) )
bcm_phy_report_length ( phydev , 0 ) ;
if ( bcm_phy_distance_valid ( pair_b ) )
bcm_phy_report_length ( phydev , 1 ) ;
if ( bcm_phy_distance_valid ( pair_c ) )
bcm_phy_report_length ( phydev , 2 ) ;
if ( bcm_phy_distance_valid ( pair_d ) )
bcm_phy_report_length ( phydev , 3 ) ;
ret = 0 ;
* finished = true ;
out :
/* re-enable the RDB access even if there was an error */
if ( is_rdb )
ret = __bcm_phy_enable_rdb_access ( phydev ) ? : ret ;
phy_unlock_mdio_bus ( phydev ) ;
return ret ;
}
int bcm_phy_cable_test_start ( struct phy_device * phydev )
{
return _bcm_phy_cable_test_start ( phydev , false ) ;
}
EXPORT_SYMBOL_GPL ( bcm_phy_cable_test_start ) ;
int bcm_phy_cable_test_get_status ( struct phy_device * phydev , bool * finished )
{
return _bcm_phy_cable_test_get_status ( phydev , finished , false ) ;
}
EXPORT_SYMBOL_GPL ( bcm_phy_cable_test_get_status ) ;
/* We assume that all PHYs which support RDB access can be switched to legacy
* mode . If , in the future , this is not true anymore , we have to re - implement
* this with RDB access .
*/
int bcm_phy_cable_test_start_rdb ( struct phy_device * phydev )
{
return _bcm_phy_cable_test_start ( phydev , true ) ;
}
EXPORT_SYMBOL_GPL ( bcm_phy_cable_test_start_rdb ) ;
int bcm_phy_cable_test_get_status_rdb ( struct phy_device * phydev ,
bool * finished )
{
return _bcm_phy_cable_test_get_status ( phydev , finished , true ) ;
}
EXPORT_SYMBOL_GPL ( bcm_phy_cable_test_get_status_rdb ) ;
2023-05-11 10:21:09 -07:00
# define BCM54XX_WOL_SUPPORTED_MASK (WAKE_UCAST | \
WAKE_MCAST | \
WAKE_BCAST | \
WAKE_MAGIC | \
WAKE_MAGICSECURE )
int bcm_phy_set_wol ( struct phy_device * phydev , struct ethtool_wolinfo * wol )
{
struct net_device * ndev = phydev - > attached_dev ;
u8 da [ ETH_ALEN ] , mask [ ETH_ALEN ] ;
unsigned int i ;
u16 ctl ;
int ret ;
/* Allow a MAC driver to play through its own Wake-on-LAN
* implementation
*/
if ( wol - > wolopts & ~ BCM54XX_WOL_SUPPORTED_MASK )
return - EOPNOTSUPP ;
/* The PHY supports passwords of 4, 6 and 8 bytes in size, but Linux's
* ethtool only supports 6 , for now .
*/
BUILD_BUG_ON ( sizeof ( wol - > sopass ) ! = ETH_ALEN ) ;
/* Clear previous interrupts */
ret = bcm_phy_read_exp ( phydev , BCM54XX_WOL_INT_STATUS ) ;
if ( ret < 0 )
return ret ;
ret = bcm_phy_read_exp ( phydev , BCM54XX_WOL_MAIN_CTL ) ;
if ( ret < 0 )
return ret ;
ctl = ret ;
if ( ! wol - > wolopts ) {
if ( phy_interrupt_is_valid ( phydev ) )
disable_irq_wake ( phydev - > irq ) ;
/* Leave all interrupts disabled */
ret = bcm_phy_write_exp ( phydev , BCM54XX_WOL_INT_MASK ,
BCM54XX_WOL_ALL_INTRS ) ;
if ( ret < 0 )
return ret ;
/* Disable the global Wake-on-LAN enable bit */
ctl & = ~ BCM54XX_WOL_EN ;
return bcm_phy_write_exp ( phydev , BCM54XX_WOL_MAIN_CTL , ctl ) ;
}
/* Clear the previously configured mode and mask mode for Wake-on-LAN */
ctl & = ~ ( BCM54XX_WOL_MODE_MASK < < BCM54XX_WOL_MODE_SHIFT ) ;
ctl & = ~ ( BCM54XX_WOL_MASK_MODE_MASK < < BCM54XX_WOL_MASK_MODE_SHIFT ) ;
ctl & = ~ BCM54XX_WOL_DIR_PKT_EN ;
ctl & = ~ ( BCM54XX_WOL_SECKEY_OPT_MASK < < BCM54XX_WOL_SECKEY_OPT_SHIFT ) ;
/* When using WAKE_MAGIC, we program the magic pattern filter to match
* the device ' s MAC address and we accept any MAC DA in the Ethernet
* frame .
*
* When using WAKE_UCAST , WAKE_BCAST or WAKE_MCAST , we program the
* following :
* - WAKE_UCAST - > MAC DA is the device ' s MAC with a perfect match
* - WAKE_MCAST - > MAC DA is X1 : XX : XX : XX : XX : XX where XX is don ' t care
* - WAKE_BCAST - > MAC DA is FF : FF : FF : FF : FF : FF with a perfect match
*
* Note that the Broadcast MAC DA is inherently going to match the
* multicast pattern being matched .
*/
memset ( mask , 0 , sizeof ( mask ) ) ;
if ( wol - > wolopts & WAKE_MCAST ) {
memset ( da , 0 , sizeof ( da ) ) ;
memset ( mask , 0xff , sizeof ( mask ) ) ;
da [ 0 ] = 0x01 ;
mask [ 0 ] = ~ da [ 0 ] ;
} else {
if ( wol - > wolopts & WAKE_UCAST ) {
ether_addr_copy ( da , ndev - > dev_addr ) ;
} else if ( wol - > wolopts & WAKE_BCAST ) {
eth_broadcast_addr ( da ) ;
} else if ( wol - > wolopts & WAKE_MAGICSECURE ) {
ether_addr_copy ( da , wol - > sopass ) ;
} else if ( wol - > wolopts & WAKE_MAGIC ) {
memset ( da , 0 , sizeof ( da ) ) ;
memset ( mask , 0xff , sizeof ( mask ) ) ;
}
}
for ( i = 0 ; i < ETH_ALEN / 2 ; i + + ) {
if ( wol - > wolopts & ( WAKE_MAGIC | WAKE_MAGICSECURE ) ) {
ret = bcm_phy_write_exp ( phydev ,
BCM54XX_WOL_MPD_DATA1 ( 2 - i ) ,
ndev - > dev_addr [ i * 2 ] < < 8 |
ndev - > dev_addr [ i * 2 + 1 ] ) ;
if ( ret < 0 )
return ret ;
}
ret = bcm_phy_write_exp ( phydev , BCM54XX_WOL_MPD_DATA2 ( 2 - i ) ,
da [ i * 2 ] < < 8 | da [ i * 2 + 1 ] ) ;
if ( ret < 0 )
return ret ;
ret = bcm_phy_write_exp ( phydev , BCM54XX_WOL_MASK ( 2 - i ) ,
mask [ i * 2 ] < < 8 | mask [ i * 2 + 1 ] ) ;
if ( ret )
return ret ;
}
if ( wol - > wolopts & WAKE_MAGICSECURE ) {
ctl | = BCM54XX_WOL_SECKEY_OPT_6B < <
BCM54XX_WOL_SECKEY_OPT_SHIFT ;
ctl | = BCM54XX_WOL_MODE_SINGLE_MPDSEC < < BCM54XX_WOL_MODE_SHIFT ;
ctl | = BCM54XX_WOL_MASK_MODE_DA_FF < <
BCM54XX_WOL_MASK_MODE_SHIFT ;
} else {
if ( wol - > wolopts & WAKE_MAGIC )
ctl | = BCM54XX_WOL_MODE_SINGLE_MPD ;
else
ctl | = BCM54XX_WOL_DIR_PKT_EN ;
ctl | = BCM54XX_WOL_MASK_MODE_DA_ONLY < <
BCM54XX_WOL_MASK_MODE_SHIFT ;
}
/* Globally enable Wake-on-LAN */
ctl | = BCM54XX_WOL_EN | BCM54XX_WOL_CRC_CHK ;
ret = bcm_phy_write_exp ( phydev , BCM54XX_WOL_MAIN_CTL , ctl ) ;
if ( ret < 0 )
return ret ;
/* Enable WOL interrupt on LED4 */
ret = bcm_phy_read_exp ( phydev , BCM54XX_TOP_MISC_LED_CTL ) ;
if ( ret < 0 )
return ret ;
ret | = BCM54XX_LED4_SEL_INTR ;
ret = bcm_phy_write_exp ( phydev , BCM54XX_TOP_MISC_LED_CTL , ret ) ;
if ( ret < 0 )
return ret ;
/* Enable all Wake-on-LAN interrupt sources */
ret = bcm_phy_write_exp ( phydev , BCM54XX_WOL_INT_MASK , 0 ) ;
if ( ret < 0 )
return ret ;
if ( phy_interrupt_is_valid ( phydev ) )
enable_irq_wake ( phydev - > irq ) ;
return 0 ;
}
EXPORT_SYMBOL_GPL ( bcm_phy_set_wol ) ;
void bcm_phy_get_wol ( struct phy_device * phydev , struct ethtool_wolinfo * wol )
{
struct net_device * ndev = phydev - > attached_dev ;
u8 da [ ETH_ALEN ] ;
unsigned int i ;
int ret ;
u16 ctl ;
wol - > supported = BCM54XX_WOL_SUPPORTED_MASK ;
wol - > wolopts = 0 ;
ret = bcm_phy_read_exp ( phydev , BCM54XX_WOL_MAIN_CTL ) ;
if ( ret < 0 )
return ;
ctl = ret ;
if ( ! ( ctl & BCM54XX_WOL_EN ) )
return ;
for ( i = 0 ; i < sizeof ( da ) / 2 ; i + + ) {
ret = bcm_phy_read_exp ( phydev ,
BCM54XX_WOL_MPD_DATA2 ( 2 - i ) ) ;
if ( ret < 0 )
return ;
da [ i * 2 ] = ret > > 8 ;
da [ i * 2 + 1 ] = ret & 0xff ;
}
if ( ctl & BCM54XX_WOL_DIR_PKT_EN ) {
if ( is_broadcast_ether_addr ( da ) )
wol - > wolopts | = WAKE_BCAST ;
else if ( is_multicast_ether_addr ( da ) )
wol - > wolopts | = WAKE_MCAST ;
else if ( ether_addr_equal ( da , ndev - > dev_addr ) )
wol - > wolopts | = WAKE_UCAST ;
} else {
ctl = ( ctl > > BCM54XX_WOL_MODE_SHIFT ) & BCM54XX_WOL_MODE_MASK ;
switch ( ctl ) {
case BCM54XX_WOL_MODE_SINGLE_MPD :
wol - > wolopts | = WAKE_MAGIC ;
break ;
case BCM54XX_WOL_MODE_SINGLE_MPDSEC :
wol - > wolopts | = WAKE_MAGICSECURE ;
memcpy ( wol - > sopass , da , sizeof ( da ) ) ;
break ;
default :
break ;
}
}
}
EXPORT_SYMBOL_GPL ( bcm_phy_get_wol ) ;
2023-05-25 10:59:15 -07:00
irqreturn_t bcm_phy_wol_isr ( int irq , void * dev_id )
{
return IRQ_HANDLED ;
}
EXPORT_SYMBOL_GPL ( bcm_phy_wol_isr ) ;
2023-06-07 11:34:53 -07:00
int bcm_phy_led_brightness_set ( struct phy_device * phydev ,
u8 index , enum led_brightness value )
{
u8 led_num ;
int ret ;
u16 reg ;
if ( index > = 4 )
return - EINVAL ;
/* Two LEDS per register */
led_num = index % 2 ;
reg = index > = 2 ? BCM54XX_SHD_LEDS2 : BCM54XX_SHD_LEDS1 ;
ret = bcm_phy_read_shadow ( phydev , reg ) ;
if ( ret < 0 )
return ret ;
ret & = ~ ( BCM_LED_SRC_MASK < < BCM54XX_SHD_LEDS_SHIFT ( led_num ) ) ;
if ( value = = LED_OFF )
ret | = BCM_LED_SRC_OFF < < BCM54XX_SHD_LEDS_SHIFT ( led_num ) ;
else
ret | = BCM_LED_SRC_ON < < BCM54XX_SHD_LEDS_SHIFT ( led_num ) ;
return bcm_phy_write_shadow ( phydev , reg , ret ) ;
}
EXPORT_SYMBOL_GPL ( bcm_phy_led_brightness_set ) ;
2015-10-15 10:37:13 -07:00
MODULE_DESCRIPTION ( " Broadcom PHY Library " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_AUTHOR ( " Broadcom Corporation " ) ;