2019-01-21 19:34:53 +01:00
// SPDX-License-Identifier: GPL-2.0
2012-06-27 07:33:38 +00:00
/*
* Copyright ( C ) 2011 - 2012 Cavium , Inc .
*/
# include <linux/module.h>
# include <linux/phy.h>
# include <linux/of.h>
# define PHY_ID_BCM8706 0x0143bdc1
# define PHY_ID_BCM8727 0x0143bff0
# define BCM87XX_PMD_RX_SIGNAL_DETECT (MII_ADDR_C45 | 0x1000a)
# define BCM87XX_10GBASER_PCS_STATUS (MII_ADDR_C45 | 0x30020)
# define BCM87XX_XGXS_LANE_STATUS (MII_ADDR_C45 | 0x40018)
# define BCM87XX_LASI_CONTROL (MII_ADDR_C45 | 0x39002)
# define BCM87XX_LASI_STATUS (MII_ADDR_C45 | 0x39005)
# if IS_ENABLED(CONFIG_OF_MDIO)
/* Set and/or override some configuration registers based on the
2012-07-04 05:05:45 +00:00
* broadcom , c45 - reg - init property stored in the of_node for the phydev .
2012-06-27 07:33:38 +00:00
*
* broadcom , c45 - reg - init = < devid reg mask value > , . . . ;
*
* There may be one or more sets of < devid reg mask value > :
*
* devid : which sub - device to use .
* reg : the register .
* mask : if non - zero , ANDed with existing register value .
* value : ORed with the masked value and written to the regiser .
*
*/
static int bcm87xx_of_reg_init ( struct phy_device * phydev )
{
const __be32 * paddr ;
const __be32 * paddr_end ;
int len , ret ;
2016-01-06 20:11:16 +01:00
if ( ! phydev - > mdio . dev . of_node )
2012-06-27 07:33:38 +00:00
return 0 ;
2016-01-06 20:11:16 +01:00
paddr = of_get_property ( phydev - > mdio . dev . of_node ,
2012-06-27 07:33:38 +00:00
" broadcom,c45-reg-init " , & len ) ;
if ( ! paddr )
return 0 ;
paddr_end = paddr + ( len / = sizeof ( * paddr ) ) ;
ret = 0 ;
while ( paddr + 3 < paddr_end ) {
u16 devid = be32_to_cpup ( paddr + + ) ;
u16 reg = be32_to_cpup ( paddr + + ) ;
u16 mask = be32_to_cpup ( paddr + + ) ;
u16 val_bits = be32_to_cpup ( paddr + + ) ;
int val ;
u32 regnum = MII_ADDR_C45 | ( devid < < 16 ) | reg ;
val = 0 ;
if ( mask ) {
val = phy_read ( phydev , regnum ) ;
if ( val < 0 ) {
ret = val ;
goto err ;
}
val & = mask ;
}
val | = val_bits ;
ret = phy_write ( phydev , regnum , val ) ;
if ( ret < 0 )
goto err ;
}
err :
return ret ;
}
# else
static int bcm87xx_of_reg_init ( struct phy_device * phydev )
{
return 0 ;
}
# endif /* CONFIG_OF_MDIO */
2019-05-24 22:24:19 +02:00
static int bcm87xx_get_features ( struct phy_device * phydev )
2012-06-27 07:33:38 +00:00
{
2018-11-10 23:43:33 +01:00
linkmode_set_bit ( ETHTOOL_LINK_MODE_10000baseR_FEC_BIT ,
phydev - > supported ) ;
2012-06-27 07:33:38 +00:00
return 0 ;
}
2019-05-24 22:24:19 +02:00
static int bcm87xx_config_init ( struct phy_device * phydev )
{
return bcm87xx_of_reg_init ( phydev ) ;
}
2012-06-27 07:33:38 +00:00
static int bcm87xx_config_aneg ( struct phy_device * phydev )
{
return - EINVAL ;
}
static int bcm87xx_read_status ( struct phy_device * phydev )
{
int rx_signal_detect ;
int pcs_status ;
int xgxs_lane_status ;
rx_signal_detect = phy_read ( phydev , BCM87XX_PMD_RX_SIGNAL_DETECT ) ;
if ( rx_signal_detect < 0 )
return rx_signal_detect ;
if ( ( rx_signal_detect & 1 ) = = 0 )
goto no_link ;
pcs_status = phy_read ( phydev , BCM87XX_10GBASER_PCS_STATUS ) ;
if ( pcs_status < 0 )
return pcs_status ;
if ( ( pcs_status & 1 ) = = 0 )
goto no_link ;
xgxs_lane_status = phy_read ( phydev , BCM87XX_XGXS_LANE_STATUS ) ;
if ( xgxs_lane_status < 0 )
return xgxs_lane_status ;
if ( ( xgxs_lane_status & 0x1000 ) = = 0 )
goto no_link ;
phydev - > speed = 10000 ;
phydev - > link = 1 ;
phydev - > duplex = 1 ;
return 0 ;
no_link :
phydev - > link = 0 ;
return 0 ;
}
static int bcm87xx_config_intr ( struct phy_device * phydev )
{
int reg , err ;
reg = phy_read ( phydev , BCM87XX_LASI_CONTROL ) ;
if ( reg < 0 )
return reg ;
if ( phydev - > interrupts = = PHY_INTERRUPT_ENABLED )
reg | = 1 ;
else
reg & = ~ 1 ;
err = phy_write ( phydev , BCM87XX_LASI_CONTROL , reg ) ;
return err ;
}
static int bcm87xx_did_interrupt ( struct phy_device * phydev )
{
int reg ;
reg = phy_read ( phydev , BCM87XX_LASI_STATUS ) ;
if ( reg < 0 ) {
2016-01-06 20:11:09 +01:00
phydev_err ( phydev ,
" Error: Read of BCM87XX_LASI_STATUS failed: %d \n " ,
reg ) ;
2012-06-27 07:33:38 +00:00
return 0 ;
}
return ( reg & 1 ) ! = 0 ;
}
static int bcm87xx_ack_interrupt ( struct phy_device * phydev )
{
/* Reading the LASI status clears it. */
bcm87xx_did_interrupt ( phydev ) ;
return 0 ;
}
static int bcm8706_match_phy_device ( struct phy_device * phydev )
{
return phydev - > c45_ids . device_ids [ 4 ] = = PHY_ID_BCM8706 ;
}
static int bcm8727_match_phy_device ( struct phy_device * phydev )
{
return phydev - > c45_ids . device_ids [ 4 ] = = PHY_ID_BCM8727 ;
}
2012-07-04 05:44:34 +00:00
static struct phy_driver bcm87xx_driver [ ] = {
{
2012-06-27 07:33:38 +00:00
. phy_id = PHY_ID_BCM8706 ,
. phy_id_mask = 0xffffffff ,
. name = " Broadcom BCM8706 " ,
2019-05-24 22:24:19 +02:00
. get_features = bcm87xx_get_features ,
2012-06-27 07:33:38 +00:00
. config_init = bcm87xx_config_init ,
. config_aneg = bcm87xx_config_aneg ,
. read_status = bcm87xx_read_status ,
. ack_interrupt = bcm87xx_ack_interrupt ,
. config_intr = bcm87xx_config_intr ,
. did_interrupt = bcm87xx_did_interrupt ,
. match_phy_device = bcm8706_match_phy_device ,
2012-07-04 05:44:34 +00:00
} , {
2012-06-27 07:33:38 +00:00
. phy_id = PHY_ID_BCM8727 ,
. phy_id_mask = 0xffffffff ,
. name = " Broadcom BCM8727 " ,
2019-05-24 22:24:19 +02:00
. get_features = bcm87xx_get_features ,
2012-06-27 07:33:38 +00:00
. config_init = bcm87xx_config_init ,
. config_aneg = bcm87xx_config_aneg ,
. read_status = bcm87xx_read_status ,
. ack_interrupt = bcm87xx_ack_interrupt ,
. config_intr = bcm87xx_config_intr ,
. did_interrupt = bcm87xx_did_interrupt ,
. match_phy_device = bcm8727_match_phy_device ,
2012-07-04 05:44:34 +00:00
} } ;
2012-06-27 07:33:38 +00:00
2014-11-11 19:45:59 +01:00
module_phy_driver ( bcm87xx_driver ) ;
2012-09-21 16:44:18 +00:00
2019-01-21 19:34:53 +01:00
MODULE_LICENSE ( " GPL v2 " ) ;