2015-06-02 17:34:37 +03:00
/*
* Driver for the Texas Instruments DP83867 PHY
*
* Copyright ( C ) 2015 Texas Instruments Inc .
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*/
# include <linux/ethtool.h>
# include <linux/kernel.h>
# include <linux/mii.h>
# include <linux/module.h>
# include <linux/of.h>
# include <linux/phy.h>
# include <dt-bindings/net/ti-dp83867.h>
# define DP83867_PHY_ID 0x2000a231
# define DP83867_DEVADDR 0x1f
# define MII_DP83867_PHYCTRL 0x10
# define MII_DP83867_MICR 0x12
# define MII_DP83867_ISR 0x13
# define DP83867_CTRL 0x1f
/* Extended Registers */
# define DP83867_RGMIICTL 0x0032
# define DP83867_RGMIIDCTL 0x0086
# define DP83867_SW_RESET BIT(15)
# define DP83867_SW_RESTART BIT(14)
/* MICR Interrupt bits */
# define MII_DP83867_MICR_AN_ERR_INT_EN BIT(15)
# define MII_DP83867_MICR_SPEED_CHNG_INT_EN BIT(14)
# define MII_DP83867_MICR_DUP_MODE_CHNG_INT_EN BIT(13)
# define MII_DP83867_MICR_PAGE_RXD_INT_EN BIT(12)
# define MII_DP83867_MICR_AUTONEG_COMP_INT_EN BIT(11)
# define MII_DP83867_MICR_LINK_STS_CHNG_INT_EN BIT(10)
# define MII_DP83867_MICR_FALSE_CARRIER_INT_EN BIT(8)
# define MII_DP83867_MICR_SLEEP_MODE_CHNG_INT_EN BIT(4)
# define MII_DP83867_MICR_WOL_INT_EN BIT(3)
# define MII_DP83867_MICR_XGMII_ERR_INT_EN BIT(2)
# define MII_DP83867_MICR_POL_CHNG_INT_EN BIT(1)
# define MII_DP83867_MICR_JABBER_INT_EN BIT(0)
/* RGMIICTL bits */
# define DP83867_RGMII_TX_CLK_DELAY_EN BIT(1)
# define DP83867_RGMII_RX_CLK_DELAY_EN BIT(0)
/* PHY CTRL bits */
# define DP83867_PHYCR_FIFO_DEPTH_SHIFT 14
2016-07-01 23:35:03 +03:00
# define DP83867_PHYCR_FIFO_DEPTH_MASK (3 << 14)
2015-06-02 17:34:37 +03:00
/* RGMIIDCTL bits */
# define DP83867_RGMII_TX_CLK_DELAY_SHIFT 4
struct dp83867_private {
int rx_id_delay ;
int tx_id_delay ;
int fifo_depth ;
} ;
static int dp83867_ack_interrupt ( struct phy_device * phydev )
{
int err = phy_read ( phydev , MII_DP83867_ISR ) ;
if ( err < 0 )
return err ;
return 0 ;
}
static int dp83867_config_intr ( struct phy_device * phydev )
{
int micr_status ;
if ( phydev - > interrupts = = PHY_INTERRUPT_ENABLED ) {
micr_status = phy_read ( phydev , MII_DP83867_MICR ) ;
if ( micr_status < 0 )
return micr_status ;
micr_status | =
( MII_DP83867_MICR_AN_ERR_INT_EN |
MII_DP83867_MICR_SPEED_CHNG_INT_EN |
MII_DP83867_MICR_DUP_MODE_CHNG_INT_EN |
MII_DP83867_MICR_SLEEP_MODE_CHNG_INT_EN ) ;
return phy_write ( phydev , MII_DP83867_MICR , micr_status ) ;
}
micr_status = 0x0 ;
return phy_write ( phydev , MII_DP83867_MICR , micr_status ) ;
}
# ifdef CONFIG_OF_MDIO
static int dp83867_of_init ( struct phy_device * phydev )
{
struct dp83867_private * dp83867 = phydev - > priv ;
2016-01-06 22:11:16 +03:00
struct device * dev = & phydev - > mdio . dev ;
2015-06-02 17:34:37 +03:00
struct device_node * of_node = dev - > of_node ;
int ret ;
2015-12-07 06:38:58 +03:00
if ( ! of_node )
2015-06-02 17:34:37 +03:00
return - ENODEV ;
2015-06-08 22:30:55 +03:00
ret = of_property_read_u32 ( of_node , " ti,rx-internal-delay " ,
2015-06-02 17:34:37 +03:00
& dp83867 - > rx_id_delay ) ;
if ( ret )
return ret ;
2015-06-08 22:30:55 +03:00
ret = of_property_read_u32 ( of_node , " ti,tx-internal-delay " ,
2015-06-02 17:34:37 +03:00
& dp83867 - > tx_id_delay ) ;
if ( ret )
return ret ;
2015-07-24 09:16:10 +03:00
return of_property_read_u32 ( of_node , " ti,fifo-depth " ,
2015-06-02 17:34:37 +03:00
& dp83867 - > fifo_depth ) ;
}
# else
static int dp83867_of_init ( struct phy_device * phydev )
{
return 0 ;
}
# endif /* CONFIG_OF_MDIO */
static int dp83867_config_init ( struct phy_device * phydev )
{
struct dp83867_private * dp83867 ;
2016-07-01 23:35:03 +03:00
int ret , val ;
u16 delay ;
2015-06-02 17:34:37 +03:00
if ( ! phydev - > priv ) {
2016-01-06 22:11:16 +03:00
dp83867 = devm_kzalloc ( & phydev - > mdio . dev , sizeof ( * dp83867 ) ,
2015-06-02 17:34:37 +03:00
GFP_KERNEL ) ;
if ( ! dp83867 )
return - ENOMEM ;
phydev - > priv = dp83867 ;
ret = dp83867_of_init ( phydev ) ;
if ( ret )
return ret ;
} else {
dp83867 = ( struct dp83867_private * ) phydev - > priv ;
}
if ( phy_interface_is_rgmii ( phydev ) ) {
2016-07-01 23:35:03 +03:00
val = phy_read ( phydev , MII_DP83867_PHYCTRL ) ;
if ( val < 0 )
return val ;
val & = ~ DP83867_PHYCR_FIFO_DEPTH_MASK ;
val | = ( dp83867 - > fifo_depth < < DP83867_PHYCR_FIFO_DEPTH_SHIFT ) ;
ret = phy_write ( phydev , MII_DP83867_PHYCTRL , val ) ;
2015-06-02 17:34:37 +03:00
if ( ret )
return ret ;
}
2015-07-21 20:06:45 +03:00
if ( ( phydev - > interface > = PHY_INTERFACE_MODE_RGMII_ID ) & &
2015-06-02 17:34:37 +03:00
( phydev - > interface < = PHY_INTERFACE_MODE_RGMII_RXID ) ) {
val = phy_read_mmd_indirect ( phydev , DP83867_RGMIICTL ,
2016-01-06 22:11:12 +03:00
DP83867_DEVADDR ) ;
2015-06-02 17:34:37 +03:00
if ( phydev - > interface = = PHY_INTERFACE_MODE_RGMII_ID )
val | = ( DP83867_RGMII_TX_CLK_DELAY_EN | DP83867_RGMII_RX_CLK_DELAY_EN ) ;
if ( phydev - > interface = = PHY_INTERFACE_MODE_RGMII_TXID )
val | = DP83867_RGMII_TX_CLK_DELAY_EN ;
if ( phydev - > interface = = PHY_INTERFACE_MODE_RGMII_RXID )
val | = DP83867_RGMII_RX_CLK_DELAY_EN ;
phy_write_mmd_indirect ( phydev , DP83867_RGMIICTL ,
2016-01-06 22:11:12 +03:00
DP83867_DEVADDR , val ) ;
2015-06-02 17:34:37 +03:00
delay = ( dp83867 - > rx_id_delay |
( dp83867 - > tx_id_delay < < DP83867_RGMII_TX_CLK_DELAY_SHIFT ) ) ;
phy_write_mmd_indirect ( phydev , DP83867_RGMIIDCTL ,
2016-01-06 22:11:12 +03:00
DP83867_DEVADDR , delay ) ;
2015-06-02 17:34:37 +03:00
}
return 0 ;
}
static int dp83867_phy_reset ( struct phy_device * phydev )
{
int err ;
err = phy_write ( phydev , DP83867_CTRL , DP83867_SW_RESET ) ;
if ( err < 0 )
return err ;
return dp83867_config_init ( phydev ) ;
}
static struct phy_driver dp83867_driver [ ] = {
{
. phy_id = DP83867_PHY_ID ,
. phy_id_mask = 0xfffffff0 ,
. name = " TI DP83867 " ,
. features = PHY_GBIT_FEATURES ,
. flags = PHY_HAS_INTERRUPT ,
. config_init = dp83867_config_init ,
. soft_reset = dp83867_phy_reset ,
/* IRQ related */
. ack_interrupt = dp83867_ack_interrupt ,
. config_intr = dp83867_config_intr ,
. config_aneg = genphy_config_aneg ,
. read_status = genphy_read_status ,
. suspend = genphy_suspend ,
. resume = genphy_resume ,
} ,
} ;
module_phy_driver ( dp83867_driver ) ;
static struct mdio_device_id __maybe_unused dp83867_tbl [ ] = {
{ DP83867_PHY_ID , 0xfffffff0 } ,
{ }
} ;
MODULE_DEVICE_TABLE ( mdio , dp83867_tbl ) ;
MODULE_DESCRIPTION ( " Texas Instruments DP83867 PHY driver " ) ;
MODULE_AUTHOR ( " Dan Murphy <dmurphy@ti.com " ) ;
MODULE_LICENSE ( " GPL " ) ;