2018-05-11 13:08:19 -05:00
// SPDX-License-Identifier: GPL-2.0
/*
* Driver for the Texas Instruments DP83TC811 PHY
*
* Copyright ( C ) 2018 Texas Instruments Incorporated - http : //www.ti.com/
*
*/
# include <linux/ethtool.h>
# include <linux/etherdevice.h>
# include <linux/kernel.h>
# include <linux/mii.h>
# include <linux/module.h>
# include <linux/of.h>
# include <linux/phy.h>
# include <linux/netdevice.h>
# define DP83TC811_PHY_ID 0x2000a253
# define DP83811_DEVADDR 0x1f
# define MII_DP83811_SGMII_CTRL 0x09
# define MII_DP83811_INT_STAT1 0x12
# define MII_DP83811_INT_STAT2 0x13
2018-06-29 10:35:45 -05:00
# define MII_DP83811_INT_STAT3 0x18
2018-05-11 13:08:19 -05:00
# define MII_DP83811_RESET_CTRL 0x1f
# define DP83811_HW_RESET BIT(15)
# define DP83811_SW_RESET BIT(14)
/* INT_STAT1 bits */
# define DP83811_RX_ERR_HF_INT_EN BIT(0)
# define DP83811_MS_TRAINING_INT_EN BIT(1)
# define DP83811_ANEG_COMPLETE_INT_EN BIT(2)
# define DP83811_ESD_EVENT_INT_EN BIT(3)
# define DP83811_WOL_INT_EN BIT(4)
# define DP83811_LINK_STAT_INT_EN BIT(5)
# define DP83811_ENERGY_DET_INT_EN BIT(6)
# define DP83811_LINK_QUAL_INT_EN BIT(7)
/* INT_STAT2 bits */
# define DP83811_JABBER_DET_INT_EN BIT(0)
# define DP83811_POLARITY_INT_EN BIT(1)
# define DP83811_SLEEP_MODE_INT_EN BIT(2)
# define DP83811_OVERTEMP_INT_EN BIT(3)
# define DP83811_OVERVOLTAGE_INT_EN BIT(6)
# define DP83811_UNDERVOLTAGE_INT_EN BIT(7)
2018-06-29 10:35:45 -05:00
/* INT_STAT3 bits */
# define DP83811_LPS_INT_EN BIT(0)
# define DP83811_NO_FRAME_INT_EN BIT(3)
# define DP83811_POR_DONE_INT_EN BIT(4)
2018-05-11 13:08:19 -05:00
# define MII_DP83811_RXSOP1 0x04a5
# define MII_DP83811_RXSOP2 0x04a6
# define MII_DP83811_RXSOP3 0x04a7
/* WoL Registers */
# define MII_DP83811_WOL_CFG 0x04a0
# define MII_DP83811_WOL_STAT 0x04a1
# define MII_DP83811_WOL_DA1 0x04a2
# define MII_DP83811_WOL_DA2 0x04a3
# define MII_DP83811_WOL_DA3 0x04a4
/* WoL bits */
# define DP83811_WOL_MAGIC_EN BIT(0)
# define DP83811_WOL_SECURE_ON BIT(5)
# define DP83811_WOL_EN BIT(7)
# define DP83811_WOL_INDICATION_SEL BIT(8)
# define DP83811_WOL_CLR_INDICATION BIT(11)
/* SGMII CTRL bits */
# define DP83811_TDR_AUTO BIT(8)
# define DP83811_SGMII_EN BIT(12)
# define DP83811_SGMII_AUTO_NEG_EN BIT(13)
# define DP83811_SGMII_TX_ERR_DIS BIT(14)
# define DP83811_SGMII_SOFT_RESET BIT(15)
static int dp83811_ack_interrupt ( struct phy_device * phydev )
{
int err ;
err = phy_read ( phydev , MII_DP83811_INT_STAT1 ) ;
if ( err < 0 )
return err ;
err = phy_read ( phydev , MII_DP83811_INT_STAT2 ) ;
if ( err < 0 )
return err ;
2018-06-29 10:35:45 -05:00
err = phy_read ( phydev , MII_DP83811_INT_STAT3 ) ;
if ( err < 0 )
return err ;
2018-05-11 13:08:19 -05:00
return 0 ;
}
static int dp83811_set_wol ( struct phy_device * phydev ,
struct ethtool_wolinfo * wol )
{
struct net_device * ndev = phydev - > attached_dev ;
const u8 * mac ;
u16 value ;
if ( wol - > wolopts & ( WAKE_MAGIC | WAKE_MAGICSECURE ) ) {
mac = ( const u8 * ) ndev - > dev_addr ;
if ( ! is_valid_ether_addr ( mac ) )
return - EINVAL ;
/* MAC addresses start with byte 5, but stored in mac[0].
* 811 PHYs store bytes 4 | 5 , 2 | 3 , 0 | 1
*/
phy_write_mmd ( phydev , DP83811_DEVADDR , MII_DP83811_WOL_DA1 ,
( mac [ 1 ] < < 8 ) | mac [ 0 ] ) ;
phy_write_mmd ( phydev , DP83811_DEVADDR , MII_DP83811_WOL_DA2 ,
( mac [ 3 ] < < 8 ) | mac [ 2 ] ) ;
phy_write_mmd ( phydev , DP83811_DEVADDR , MII_DP83811_WOL_DA3 ,
( mac [ 5 ] < < 8 ) | mac [ 4 ] ) ;
value = phy_read_mmd ( phydev , DP83811_DEVADDR ,
MII_DP83811_WOL_CFG ) ;
if ( wol - > wolopts & WAKE_MAGIC )
value | = DP83811_WOL_MAGIC_EN ;
else
value & = ~ DP83811_WOL_MAGIC_EN ;
if ( wol - > wolopts & WAKE_MAGICSECURE ) {
phy_write_mmd ( phydev , DP83811_DEVADDR ,
MII_DP83811_RXSOP1 ,
( wol - > sopass [ 1 ] < < 8 ) | wol - > sopass [ 0 ] ) ;
phy_write_mmd ( phydev , DP83811_DEVADDR ,
MII_DP83811_RXSOP2 ,
( wol - > sopass [ 3 ] < < 8 ) | wol - > sopass [ 2 ] ) ;
phy_write_mmd ( phydev , DP83811_DEVADDR ,
MII_DP83811_RXSOP3 ,
( wol - > sopass [ 5 ] < < 8 ) | wol - > sopass [ 4 ] ) ;
value | = DP83811_WOL_SECURE_ON ;
} else {
value & = ~ DP83811_WOL_SECURE_ON ;
}
2020-04-28 11:03:54 -05:00
/* Clear any pending WoL interrupt */
phy_read ( phydev , MII_DP83811_INT_STAT1 ) ;
value | = DP83811_WOL_EN | DP83811_WOL_INDICATION_SEL |
DP83811_WOL_CLR_INDICATION ;
return phy_write_mmd ( phydev , DP83811_DEVADDR ,
MII_DP83811_WOL_CFG , value ) ;
2018-05-11 13:08:19 -05:00
} else {
2020-04-28 11:03:54 -05:00
return phy_clear_bits_mmd ( phydev , DP83811_DEVADDR ,
MII_DP83811_WOL_CFG , DP83811_WOL_EN ) ;
2018-05-11 13:08:19 -05:00
}
}
static void dp83811_get_wol ( struct phy_device * phydev ,
struct ethtool_wolinfo * wol )
{
u16 sopass_val ;
int value ;
wol - > supported = ( WAKE_MAGIC | WAKE_MAGICSECURE ) ;
wol - > wolopts = 0 ;
value = phy_read_mmd ( phydev , DP83811_DEVADDR , MII_DP83811_WOL_CFG ) ;
if ( value & DP83811_WOL_MAGIC_EN )
wol - > wolopts | = WAKE_MAGIC ;
if ( value & DP83811_WOL_SECURE_ON ) {
sopass_val = phy_read_mmd ( phydev , DP83811_DEVADDR ,
MII_DP83811_RXSOP1 ) ;
wol - > sopass [ 0 ] = ( sopass_val & 0xff ) ;
wol - > sopass [ 1 ] = ( sopass_val > > 8 ) ;
sopass_val = phy_read_mmd ( phydev , DP83811_DEVADDR ,
MII_DP83811_RXSOP2 ) ;
wol - > sopass [ 2 ] = ( sopass_val & 0xff ) ;
wol - > sopass [ 3 ] = ( sopass_val > > 8 ) ;
sopass_val = phy_read_mmd ( phydev , DP83811_DEVADDR ,
MII_DP83811_RXSOP3 ) ;
wol - > sopass [ 4 ] = ( sopass_val & 0xff ) ;
wol - > sopass [ 5 ] = ( sopass_val > > 8 ) ;
wol - > wolopts | = WAKE_MAGICSECURE ;
}
/* WoL is not enabled so set wolopts to 0 */
if ( ! ( value & DP83811_WOL_EN ) )
wol - > wolopts = 0 ;
}
static int dp83811_config_intr ( struct phy_device * phydev )
{
int misr_status , err ;
if ( phydev - > interrupts = = PHY_INTERRUPT_ENABLED ) {
2020-11-23 17:38:14 +02:00
err = dp83811_ack_interrupt ( phydev ) ;
if ( err )
return err ;
2018-05-11 13:08:19 -05:00
misr_status = phy_read ( phydev , MII_DP83811_INT_STAT1 ) ;
if ( misr_status < 0 )
return misr_status ;
misr_status | = ( DP83811_RX_ERR_HF_INT_EN |
DP83811_MS_TRAINING_INT_EN |
DP83811_ANEG_COMPLETE_INT_EN |
DP83811_ESD_EVENT_INT_EN |
DP83811_WOL_INT_EN |
DP83811_LINK_STAT_INT_EN |
DP83811_ENERGY_DET_INT_EN |
DP83811_LINK_QUAL_INT_EN ) ;
err = phy_write ( phydev , MII_DP83811_INT_STAT1 , misr_status ) ;
if ( err < 0 )
return err ;
misr_status = phy_read ( phydev , MII_DP83811_INT_STAT2 ) ;
if ( misr_status < 0 )
return misr_status ;
misr_status | = ( DP83811_JABBER_DET_INT_EN |
DP83811_POLARITY_INT_EN |
DP83811_SLEEP_MODE_INT_EN |
DP83811_OVERTEMP_INT_EN |
DP83811_OVERVOLTAGE_INT_EN |
DP83811_UNDERVOLTAGE_INT_EN ) ;
err = phy_write ( phydev , MII_DP83811_INT_STAT2 , misr_status ) ;
2018-06-29 10:35:45 -05:00
if ( err < 0 )
return err ;
misr_status = phy_read ( phydev , MII_DP83811_INT_STAT3 ) ;
if ( misr_status < 0 )
return misr_status ;
misr_status | = ( DP83811_LPS_INT_EN |
DP83811_NO_FRAME_INT_EN |
DP83811_POR_DONE_INT_EN ) ;
err = phy_write ( phydev , MII_DP83811_INT_STAT3 , misr_status ) ;
2018-05-11 13:08:19 -05:00
} else {
err = phy_write ( phydev , MII_DP83811_INT_STAT1 , 0 ) ;
if ( err < 0 )
return err ;
2018-06-29 10:28:31 -05:00
err = phy_write ( phydev , MII_DP83811_INT_STAT2 , 0 ) ;
2018-06-29 10:35:45 -05:00
if ( err < 0 )
return err ;
err = phy_write ( phydev , MII_DP83811_INT_STAT3 , 0 ) ;
2020-11-23 17:38:14 +02:00
if ( err < 0 )
return err ;
err = dp83811_ack_interrupt ( phydev ) ;
2018-05-11 13:08:19 -05:00
}
return err ;
}
2020-11-23 17:38:13 +02:00
static irqreturn_t dp83811_handle_interrupt ( struct phy_device * phydev )
{
2021-02-26 17:30:20 +02:00
bool trigger_machine = false ;
2020-11-23 17:38:13 +02:00
int irq_status ;
/* The INT_STAT registers 1, 2 and 3 are holding the interrupt status
* in the upper half ( 15 : 8 ) , while the lower half ( 7 : 0 ) is used for
* controlling the interrupt enable state of those individual interrupt
* sources . To determine the possible interrupt sources , just read the
* INT_STAT * register and use it directly to know which interrupts have
* been enabled previously or not .
*/
irq_status = phy_read ( phydev , MII_DP83811_INT_STAT1 ) ;
if ( irq_status < 0 ) {
phy_error ( phydev ) ;
return IRQ_NONE ;
}
if ( irq_status & ( ( irq_status & GENMASK ( 7 , 0 ) ) < < 8 ) )
2021-02-26 17:30:20 +02:00
trigger_machine = true ;
2020-11-23 17:38:13 +02:00
irq_status = phy_read ( phydev , MII_DP83811_INT_STAT2 ) ;
if ( irq_status < 0 ) {
phy_error ( phydev ) ;
return IRQ_NONE ;
}
if ( irq_status & ( ( irq_status & GENMASK ( 7 , 0 ) ) < < 8 ) )
2021-02-26 17:30:20 +02:00
trigger_machine = true ;
2020-11-23 17:38:13 +02:00
irq_status = phy_read ( phydev , MII_DP83811_INT_STAT3 ) ;
if ( irq_status < 0 ) {
phy_error ( phydev ) ;
return IRQ_NONE ;
}
if ( irq_status & ( ( irq_status & GENMASK ( 7 , 0 ) ) < < 8 ) )
2021-02-26 17:30:20 +02:00
trigger_machine = true ;
2020-11-23 17:38:13 +02:00
2021-02-26 17:30:20 +02:00
if ( ! trigger_machine )
return IRQ_NONE ;
2020-11-23 17:38:13 +02:00
phy_trigger_machine ( phydev ) ;
return IRQ_HANDLED ;
}
2018-05-11 13:08:19 -05:00
static int dp83811_config_aneg ( struct phy_device * phydev )
{
int value , err ;
if ( phydev - > interface = = PHY_INTERFACE_MODE_SGMII ) {
value = phy_read ( phydev , MII_DP83811_SGMII_CTRL ) ;
if ( phydev - > autoneg = = AUTONEG_ENABLE ) {
err = phy_write ( phydev , MII_DP83811_SGMII_CTRL ,
( DP83811_SGMII_AUTO_NEG_EN | value ) ) ;
if ( err < 0 )
return err ;
} else {
err = phy_write ( phydev , MII_DP83811_SGMII_CTRL ,
( ~ DP83811_SGMII_AUTO_NEG_EN & value ) ) ;
if ( err < 0 )
return err ;
}
}
return genphy_config_aneg ( phydev ) ;
}
static int dp83811_config_init ( struct phy_device * phydev )
{
int value , err ;
2018-06-29 10:35:46 -05:00
value = phy_read ( phydev , MII_DP83811_SGMII_CTRL ) ;
2018-05-11 13:08:19 -05:00
if ( phydev - > interface = = PHY_INTERFACE_MODE_SGMII ) {
2018-06-29 10:35:46 -05:00
err = phy_write ( phydev , MII_DP83811_SGMII_CTRL ,
2018-05-11 13:08:19 -05:00
( DP83811_SGMII_EN | value ) ) ;
2018-06-29 10:35:46 -05:00
} else {
err = phy_write ( phydev , MII_DP83811_SGMII_CTRL ,
( ~ DP83811_SGMII_EN & value ) ) ;
2018-05-11 13:08:19 -05:00
}
2018-06-29 10:35:46 -05:00
if ( err < 0 )
return err ;
2018-05-11 13:08:19 -05:00
value = DP83811_WOL_MAGIC_EN | DP83811_WOL_SECURE_ON | DP83811_WOL_EN ;
2020-04-28 11:03:54 -05:00
return phy_clear_bits_mmd ( phydev , DP83811_DEVADDR , MII_DP83811_WOL_CFG ,
value ) ;
2018-05-11 13:08:19 -05:00
}
static int dp83811_phy_reset ( struct phy_device * phydev )
{
int err ;
err = phy_write ( phydev , MII_DP83811_RESET_CTRL , DP83811_HW_RESET ) ;
if ( err < 0 )
return err ;
return 0 ;
}
static int dp83811_suspend ( struct phy_device * phydev )
{
int value ;
value = phy_read_mmd ( phydev , DP83811_DEVADDR , MII_DP83811_WOL_CFG ) ;
if ( ! ( value & DP83811_WOL_EN ) )
genphy_suspend ( phydev ) ;
return 0 ;
}
static int dp83811_resume ( struct phy_device * phydev )
{
genphy_resume ( phydev ) ;
2019-02-06 07:38:43 +01:00
phy_set_bits_mmd ( phydev , DP83811_DEVADDR , MII_DP83811_WOL_CFG ,
DP83811_WOL_CLR_INDICATION ) ;
2018-05-11 13:08:19 -05:00
return 0 ;
}
static struct phy_driver dp83811_driver [ ] = {
{
. phy_id = DP83TC811_PHY_ID ,
. phy_id_mask = 0xfffffff0 ,
. name = " TI DP83TC811 " ,
2019-04-12 20:47:03 +02:00
/* PHY_BASIC_FEATURES */
2018-05-11 13:08:19 -05:00
. config_init = dp83811_config_init ,
. config_aneg = dp83811_config_aneg ,
. soft_reset = dp83811_phy_reset ,
. get_wol = dp83811_get_wol ,
. set_wol = dp83811_set_wol ,
. config_intr = dp83811_config_intr ,
2020-11-23 17:38:13 +02:00
. handle_interrupt = dp83811_handle_interrupt ,
2018-05-11 13:08:19 -05:00
. suspend = dp83811_suspend ,
. resume = dp83811_resume ,
} ,
} ;
module_phy_driver ( dp83811_driver ) ;
static struct mdio_device_id __maybe_unused dp83811_tbl [ ] = {
{ DP83TC811_PHY_ID , 0xfffffff0 } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( mdio , dp83811_tbl ) ;
MODULE_DESCRIPTION ( " Texas Instruments DP83TC811 PHY driver " ) ;
MODULE_AUTHOR ( " Dan Murphy <dmurphy@ti.com " ) ;
MODULE_LICENSE ( " GPL " ) ;