2018-05-02 21:09:17 +05:30
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2018 Microchip Technology
# include <linux/kernel.h>
# include <linux/module.h>
2020-04-20 11:51:41 -04:00
# include <linux/delay.h>
2018-05-02 21:09:17 +05:30
# include <linux/mii.h>
# include <linux/phy.h>
2020-04-20 11:51:41 -04:00
/* External Register Control Register */
# define LAN87XX_EXT_REG_CTL (0x14)
# define LAN87XX_EXT_REG_CTL_RD_CTL (0x1000)
# define LAN87XX_EXT_REG_CTL_WR_CTL (0x0800)
/* External Register Read Data Register */
# define LAN87XX_EXT_REG_RD_DATA (0x15)
/* External Register Write Data Register */
# define LAN87XX_EXT_REG_WR_DATA (0x16)
2018-05-02 21:09:17 +05:30
/* Interrupt Source Register */
# define LAN87XX_INTERRUPT_SOURCE (0x18)
/* Interrupt Mask Register */
# define LAN87XX_INTERRUPT_MASK (0x19)
# define LAN87XX_MASK_LINK_UP (0x0004)
# define LAN87XX_MASK_LINK_DOWN (0x0002)
2020-04-20 11:51:41 -04:00
/* phyaccess nested types */
# define PHYACC_ATTR_MODE_READ 0
# define PHYACC_ATTR_MODE_WRITE 1
# define PHYACC_ATTR_MODE_MODIFY 2
# define PHYACC_ATTR_BANK_SMI 0
# define PHYACC_ATTR_BANK_MISC 1
# define PHYACC_ATTR_BANK_PCS 2
# define PHYACC_ATTR_BANK_AFE 3
# define PHYACC_ATTR_BANK_MAX 7
2018-05-02 21:09:17 +05:30
# define DRIVER_AUTHOR "Nisar Sayed <nisar.sayed@microchip.com>"
# define DRIVER_DESC "Microchip LAN87XX T1 PHY driver"
2020-04-20 11:51:41 -04:00
struct access_ereg_val {
u8 mode ;
u8 bank ;
u8 offset ;
u16 val ;
u16 mask ;
} ;
static int access_ereg ( struct phy_device * phydev , u8 mode , u8 bank ,
u8 offset , u16 val )
{
u16 ereg = 0 ;
int rc = 0 ;
if ( mode > PHYACC_ATTR_MODE_WRITE | | bank > PHYACC_ATTR_BANK_MAX )
return - EINVAL ;
if ( bank = = PHYACC_ATTR_BANK_SMI ) {
if ( mode = = PHYACC_ATTR_MODE_WRITE )
rc = phy_write ( phydev , offset , val ) ;
else
rc = phy_read ( phydev , offset ) ;
return rc ;
}
if ( mode = = PHYACC_ATTR_MODE_WRITE ) {
ereg = LAN87XX_EXT_REG_CTL_WR_CTL ;
rc = phy_write ( phydev , LAN87XX_EXT_REG_WR_DATA , val ) ;
if ( rc < 0 )
return rc ;
} else {
ereg = LAN87XX_EXT_REG_CTL_RD_CTL ;
}
ereg | = ( bank < < 8 ) | offset ;
rc = phy_write ( phydev , LAN87XX_EXT_REG_CTL , ereg ) ;
if ( rc < 0 )
return rc ;
if ( mode = = PHYACC_ATTR_MODE_READ )
rc = phy_read ( phydev , LAN87XX_EXT_REG_RD_DATA ) ;
return rc ;
}
static int access_ereg_modify_changed ( struct phy_device * phydev ,
u8 bank , u8 offset , u16 val , u16 mask )
{
int new = 0 , rc = 0 ;
if ( bank > PHYACC_ATTR_BANK_MAX )
return - EINVAL ;
rc = access_ereg ( phydev , PHYACC_ATTR_MODE_READ , bank , offset , val ) ;
if ( rc < 0 )
return rc ;
new = val | ( rc & ( mask ^ 0xFFFF ) ) ;
rc = access_ereg ( phydev , PHYACC_ATTR_MODE_WRITE , bank , offset , new ) ;
return rc ;
}
static int lan87xx_phy_init ( struct phy_device * phydev )
{
static const struct access_ereg_val init [ ] = {
/* TX Amplitude = 5 */
{ PHYACC_ATTR_MODE_MODIFY , PHYACC_ATTR_BANK_AFE , 0x0B ,
0x000A , 0x001E } ,
/* Clear SMI interrupts */
{ PHYACC_ATTR_MODE_READ , PHYACC_ATTR_BANK_SMI , 0x18 ,
0 , 0 } ,
/* Clear MISC interrupts */
{ PHYACC_ATTR_MODE_READ , PHYACC_ATTR_BANK_MISC , 0x08 ,
0 , 0 } ,
/* Turn on TC10 Ring Oscillator (ROSC) */
{ PHYACC_ATTR_MODE_MODIFY , PHYACC_ATTR_BANK_MISC , 0x20 ,
0x0020 , 0x0020 } ,
/* WUR Detect Length to 1.2uS, LPC Detect Length to 1.09uS */
{ PHYACC_ATTR_MODE_WRITE , PHYACC_ATTR_BANK_PCS , 0x20 ,
0x283C , 0 } ,
/* Wake_In Debounce Length to 39uS, Wake_Out Length to 79uS */
{ PHYACC_ATTR_MODE_WRITE , PHYACC_ATTR_BANK_MISC , 0x21 ,
0x274F , 0 } ,
/* Enable Auto Wake Forward to Wake_Out, ROSC on, Sleep,
* and Wake_In to wake PHY
*/
{ PHYACC_ATTR_MODE_WRITE , PHYACC_ATTR_BANK_MISC , 0x20 ,
0x80A7 , 0 } ,
/* Enable WUP Auto Fwd, Enable Wake on MDI, Wakeup Debouncer
* to 128 uS
*/
{ PHYACC_ATTR_MODE_WRITE , PHYACC_ATTR_BANK_MISC , 0x24 ,
0xF110 , 0 } ,
/* Enable HW Init */
{ PHYACC_ATTR_MODE_MODIFY , PHYACC_ATTR_BANK_SMI , 0x1A ,
0x0100 , 0x0100 } ,
} ;
int rc , i ;
/* Start manual initialization procedures in Managed Mode */
rc = access_ereg_modify_changed ( phydev , PHYACC_ATTR_BANK_SMI ,
0x1a , 0x0000 , 0x0100 ) ;
if ( rc < 0 )
return rc ;
/* Soft Reset the SMI block */
rc = access_ereg_modify_changed ( phydev , PHYACC_ATTR_BANK_SMI ,
0x00 , 0x8000 , 0x8000 ) ;
if ( rc < 0 )
return rc ;
/* Check to see if the self-clearing bit is cleared */
usleep_range ( 1000 , 2000 ) ;
rc = access_ereg ( phydev , PHYACC_ATTR_MODE_READ ,
PHYACC_ATTR_BANK_SMI , 0x00 , 0 ) ;
if ( rc < 0 )
return rc ;
if ( ( rc & 0x8000 ) ! = 0 )
return - ETIMEDOUT ;
/* PHY Initialization */
for ( i = 0 ; i < ARRAY_SIZE ( init ) ; i + + ) {
if ( init [ i ] . mode = = PHYACC_ATTR_MODE_MODIFY ) {
rc = access_ereg_modify_changed ( phydev , init [ i ] . bank ,
init [ i ] . offset ,
init [ i ] . val ,
init [ i ] . mask ) ;
} else {
rc = access_ereg ( phydev , init [ i ] . mode , init [ i ] . bank ,
init [ i ] . offset , init [ i ] . val ) ;
}
if ( rc < 0 )
return rc ;
}
return 0 ;
}
2018-05-02 21:09:17 +05:30
static int lan87xx_phy_config_intr ( struct phy_device * phydev )
{
int rc , val = 0 ;
if ( phydev - > interrupts = = PHY_INTERRUPT_ENABLED ) {
/* unmask all source and clear them before enable */
rc = phy_write ( phydev , LAN87XX_INTERRUPT_MASK , 0x7FFF ) ;
rc = phy_read ( phydev , LAN87XX_INTERRUPT_SOURCE ) ;
val = LAN87XX_MASK_LINK_UP | LAN87XX_MASK_LINK_DOWN ;
2020-11-13 18:52:12 +02:00
rc = phy_write ( phydev , LAN87XX_INTERRUPT_MASK , val ) ;
} else {
rc = phy_write ( phydev , LAN87XX_INTERRUPT_MASK , val ) ;
if ( rc )
return rc ;
2018-05-02 21:09:17 +05:30
2020-11-13 18:52:12 +02:00
rc = phy_read ( phydev , LAN87XX_INTERRUPT_SOURCE ) ;
}
2018-05-02 21:09:17 +05:30
return rc < 0 ? rc : 0 ;
}
2020-11-13 18:52:11 +02:00
static irqreturn_t lan87xx_handle_interrupt ( struct phy_device * phydev )
{
int irq_status ;
irq_status = phy_read ( phydev , LAN87XX_INTERRUPT_SOURCE ) ;
if ( irq_status < 0 ) {
phy_error ( phydev ) ;
return IRQ_NONE ;
}
if ( irq_status = = 0 )
return IRQ_NONE ;
phy_trigger_machine ( phydev ) ;
return IRQ_HANDLED ;
}
2020-04-20 11:51:41 -04:00
static int lan87xx_config_init ( struct phy_device * phydev )
{
int rc = lan87xx_phy_init ( phydev ) ;
return rc < 0 ? rc : 0 ;
}
2018-05-02 21:09:17 +05:30
static struct phy_driver microchip_t1_phy_driver [ ] = {
{
. phy_id = 0x0007c150 ,
. phy_id_mask = 0xfffffff0 ,
. name = " Microchip LAN87xx T1 " ,
2018-09-29 23:04:16 +02:00
. features = PHY_BASIC_T1_FEATURES ,
2018-05-02 21:09:17 +05:30
2020-04-20 11:51:41 -04:00
. config_init = lan87xx_config_init ,
2018-05-02 21:09:17 +05:30
. config_intr = lan87xx_phy_config_intr ,
2020-11-13 18:52:11 +02:00
. handle_interrupt = lan87xx_handle_interrupt ,
2018-05-02 21:09:17 +05:30
. suspend = genphy_suspend ,
. resume = genphy_resume ,
}
} ;
module_phy_driver ( microchip_t1_phy_driver ) ;
static struct mdio_device_id __maybe_unused microchip_t1_tbl [ ] = {
{ 0x0007c150 , 0xfffffff0 } ,
{ }
} ;
MODULE_DEVICE_TABLE ( mdio , microchip_t1_tbl ) ;
MODULE_AUTHOR ( DRIVER_AUTHOR ) ;
MODULE_DESCRIPTION ( DRIVER_DESC ) ;
MODULE_LICENSE ( " GPL " ) ;