2019-01-21 19:05:50 +01:00
// SPDX-License-Identifier: GPL-2.0+
2016-11-04 16:51:23 +01:00
/*
* Amlogic Meson GXL Internal PHY Driver
*
* Copyright ( C ) 2015 Amlogic , Inc . All rights reserved .
* Copyright ( C ) 2016 BayLibre , SAS . All rights reserved .
* Author : Neil Armstrong < narmstrong @ baylibre . com >
*/
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/mii.h>
# include <linux/ethtool.h>
# include <linux/phy.h>
# include <linux/netdevice.h>
2017-12-08 12:08:11 +01:00
# include <linux/bitfield.h>
2016-11-04 16:51:23 +01:00
2017-12-18 10:44:41 +01:00
# define TSTCNTL 20
# define TSTCNTL_READ BIT(15)
# define TSTCNTL_WRITE BIT(14)
# define TSTCNTL_REG_BANK_SEL GENMASK(12, 11)
# define TSTCNTL_TEST_MODE BIT(10)
# define TSTCNTL_READ_ADDRESS GENMASK(9, 5)
# define TSTCNTL_WRITE_ADDRESS GENMASK(4, 0)
# define TSTREAD1 21
# define TSTWRITE 23
2017-12-18 10:44:45 +01:00
# define INTSRC_FLAG 29
# define INTSRC_ANEG_PR BIT(1)
# define INTSRC_PARALLEL_FAULT BIT(2)
# define INTSRC_ANEG_LP_ACK BIT(3)
# define INTSRC_LINK_DOWN BIT(4)
# define INTSRC_REMOTE_FAULT BIT(5)
# define INTSRC_ANEG_COMPLETE BIT(6)
2022-03-03 08:54:15 +01:00
# define INTSRC_ENERGY_DETECT BIT(7)
2017-12-18 10:44:45 +01:00
# define INTSRC_MASK 30
2017-12-18 10:44:41 +01:00
2022-03-03 08:54:15 +01:00
# define INT_SOURCES (INTSRC_LINK_DOWN | INTSRC_ANEG_COMPLETE | \
INTSRC_ENERGY_DETECT )
2017-12-18 10:44:41 +01:00
# define BANK_ANALOG_DSP 0
# define BANK_WOL 1
# define BANK_BIST 3
/* WOL Registers */
# define LPI_STATUS 0xc
# define LPI_STATUS_RSV12 BIT(12)
/* BIST Registers */
# define FR_PLL_CONTROL 0x1b
# define FR_PLL_DIV0 0x1c
# define FR_PLL_DIV1 0x1d
2017-12-18 10:44:42 +01:00
static int meson_gxl_open_banks ( struct phy_device * phydev )
2016-11-04 16:51:23 +01:00
{
2017-12-18 10:44:40 +01:00
int ret ;
2017-12-18 10:44:42 +01:00
/* Enable Analog and DSP register Bank access by
* toggling TSTCNTL_TEST_MODE bit in the TSTCNTL register
*/
2017-12-18 10:44:41 +01:00
ret = phy_write ( phydev , TSTCNTL , 0 ) ;
2017-12-18 10:44:40 +01:00
if ( ret )
return ret ;
2017-12-18 10:44:41 +01:00
ret = phy_write ( phydev , TSTCNTL , TSTCNTL_TEST_MODE ) ;
2017-12-18 10:44:40 +01:00
if ( ret )
return ret ;
2017-12-18 10:44:41 +01:00
ret = phy_write ( phydev , TSTCNTL , 0 ) ;
2017-12-18 10:44:40 +01:00
if ( ret )
return ret ;
2017-12-18 10:44:42 +01:00
return phy_write ( phydev , TSTCNTL , TSTCNTL_TEST_MODE ) ;
}
2016-11-04 16:51:23 +01:00
2017-12-18 10:44:42 +01:00
static void meson_gxl_close_banks ( struct phy_device * phydev )
{
phy_write ( phydev , TSTCNTL , 0 ) ;
}
static int meson_gxl_read_reg ( struct phy_device * phydev ,
unsigned int bank , unsigned int reg )
{
int ret ;
ret = meson_gxl_open_banks ( phydev ) ;
2017-12-18 10:44:40 +01:00
if ( ret )
2017-12-18 10:44:42 +01:00
goto out ;
ret = phy_write ( phydev , TSTCNTL , TSTCNTL_READ |
FIELD_PREP ( TSTCNTL_REG_BANK_SEL , bank ) |
TSTCNTL_TEST_MODE |
FIELD_PREP ( TSTCNTL_READ_ADDRESS , reg ) ) ;
2017-12-18 10:44:40 +01:00
if ( ret )
2017-12-18 10:44:42 +01:00
goto out ;
2016-11-04 16:51:23 +01:00
2017-12-18 10:44:42 +01:00
ret = phy_read ( phydev , TSTREAD1 ) ;
out :
/* Close the bank access on our way out */
meson_gxl_close_banks ( phydev ) ;
return ret ;
}
static int meson_gxl_write_reg ( struct phy_device * phydev ,
unsigned int bank , unsigned int reg ,
uint16_t value )
{
int ret ;
ret = meson_gxl_open_banks ( phydev ) ;
2017-12-18 10:44:40 +01:00
if ( ret )
2017-12-18 10:44:42 +01:00
goto out ;
ret = phy_write ( phydev , TSTWRITE , value ) ;
2017-12-18 10:44:40 +01:00
if ( ret )
2017-12-18 10:44:42 +01:00
goto out ;
2016-11-04 16:51:23 +01:00
2017-12-18 10:44:42 +01:00
ret = phy_write ( phydev , TSTCNTL , TSTCNTL_WRITE |
FIELD_PREP ( TSTCNTL_REG_BANK_SEL , bank ) |
TSTCNTL_TEST_MODE |
FIELD_PREP ( TSTCNTL_WRITE_ADDRESS , reg ) ) ;
out :
/* Close the bank access on our way out */
meson_gxl_close_banks ( phydev ) ;
return ret ;
}
static int meson_gxl_config_init ( struct phy_device * phydev )
{
int ret ;
/* Enable fractional PLL */
ret = meson_gxl_write_reg ( phydev , BANK_BIST , FR_PLL_CONTROL , 0x5 ) ;
2017-12-18 10:44:40 +01:00
if ( ret )
return ret ;
2016-11-04 16:51:23 +01:00
/* Program fraction FR_PLL_DIV1 */
2017-12-18 10:44:42 +01:00
ret = meson_gxl_write_reg ( phydev , BANK_BIST , FR_PLL_DIV1 , 0x029a ) ;
2017-12-18 10:44:40 +01:00
if ( ret )
return ret ;
2017-12-18 10:44:42 +01:00
/* Program fraction FR_PLL_DIV1 */
ret = meson_gxl_write_reg ( phydev , BANK_BIST , FR_PLL_DIV0 , 0xaaaa ) ;
2017-12-18 10:44:40 +01:00
if ( ret )
return ret ;
2016-11-04 16:51:23 +01:00
2019-08-17 12:29:25 +02:00
return 0 ;
2016-11-04 16:51:23 +01:00
}
2017-12-08 12:08:11 +01:00
/* This function is provided to cope with the possible failures of this phy
* during aneg process . When aneg fails , the PHY reports that aneg is done
* but the value found in MII_LPA is wrong :
* - Early failures : MII_LPA is just 0x0001 . if MII_EXPANSION reports that
* the link partner ( LP ) supports aneg but the LP never acked our base
* code word , it is likely that we never sent it to begin with .
* - Late failures : MII_LPA is filled with a value which seems to make sense
* but it actually is not what the LP is advertising . It seems that we
* can detect this using a magic bit in the WOL bank ( reg 12 - bit 12 ) .
* If this particular bit is not set when aneg is reported being done ,
* it means MII_LPA is likely to be wrong .
*
* In both case , forcing a restart of the aneg process solve the problem .
* When this failure happens , the first retry is usually successful but ,
* in some cases , it may take up to 6 retries to get a decent result
*/
2017-12-12 13:03:11 +00:00
static int meson_gxl_read_status ( struct phy_device * phydev )
2017-12-08 12:08:11 +01:00
{
int ret , wol , lpa , exp ;
if ( phydev - > autoneg = = AUTONEG_ENABLE ) {
ret = genphy_aneg_done ( phydev ) ;
if ( ret < 0 )
return ret ;
else if ( ! ret )
goto read_status_continue ;
2017-12-18 10:44:42 +01:00
/* Aneg is done, let's check everything is fine */
wol = meson_gxl_read_reg ( phydev , BANK_WOL , LPI_STATUS ) ;
2017-12-08 12:08:11 +01:00
if ( wol < 0 )
return wol ;
lpa = phy_read ( phydev , MII_LPA ) ;
if ( lpa < 0 )
return lpa ;
exp = phy_read ( phydev , MII_EXPANSION ) ;
if ( exp < 0 )
return exp ;
2017-12-18 10:44:41 +01:00
if ( ! ( wol & LPI_STATUS_RSV12 ) | |
2017-12-08 12:08:11 +01:00
( ( exp & EXPANSION_NWAY ) & & ! ( lpa & LPA_LPACK ) ) ) {
/* Looks like aneg failed after all */
phydev_dbg ( phydev , " LPA corruption - aneg restart \n " ) ;
return genphy_restart_aneg ( phydev ) ;
}
}
read_status_continue :
return genphy_read_status ( phydev ) ;
}
2017-12-18 10:44:45 +01:00
static int meson_gxl_ack_interrupt ( struct phy_device * phydev )
{
int ret = phy_read ( phydev , INTSRC_FLAG ) ;
return ret < 0 ? ret : 0 ;
}
static int meson_gxl_config_intr ( struct phy_device * phydev )
{
2019-03-14 14:49:45 +01:00
int ret ;
2017-12-18 10:44:45 +01:00
if ( phydev - > interrupts = = PHY_INTERRUPT_ENABLED ) {
2020-11-23 17:38:08 +02:00
/* Ack any pending IRQ */
ret = meson_gxl_ack_interrupt ( phydev ) ;
if ( ret )
return ret ;
2022-03-03 08:54:15 +01:00
ret = phy_write ( phydev , INTSRC_MASK , INT_SOURCES ) ;
2017-12-18 10:44:45 +01:00
} else {
2022-03-03 08:54:15 +01:00
ret = phy_write ( phydev , INTSRC_MASK , 0 ) ;
2017-12-18 10:44:45 +01:00
2020-11-23 17:38:08 +02:00
/* Ack any pending IRQ */
ret = meson_gxl_ack_interrupt ( phydev ) ;
}
2019-03-14 14:49:45 +01:00
2020-11-23 17:38:08 +02:00
return ret ;
2017-12-18 10:44:45 +01:00
}
2020-11-23 17:38:07 +02:00
static irqreturn_t meson_gxl_handle_interrupt ( struct phy_device * phydev )
{
int irq_status ;
irq_status = phy_read ( phydev , INTSRC_FLAG ) ;
if ( irq_status < 0 ) {
phy_error ( phydev ) ;
return IRQ_NONE ;
}
2022-03-03 08:54:15 +01:00
irq_status & = INT_SOURCES ;
2020-11-23 17:38:07 +02:00
if ( irq_status = = 0 )
return IRQ_NONE ;
2022-03-03 08:54:15 +01:00
/* Aneg-complete interrupt is used for link-up detection */
if ( phydev - > autoneg = = AUTONEG_ENABLE & &
irq_status = = INTSRC_ENERGY_DETECT )
return IRQ_HANDLED ;
2022-08-31 21:20:49 +02:00
phy_trigger_machine ( phydev ) ;
2020-11-23 17:38:07 +02:00
return IRQ_HANDLED ;
}
2016-11-04 16:51:23 +01:00
static struct phy_driver meson_gxl_phy [ ] = {
{
2019-04-04 15:11:47 +02:00
PHY_ID_MATCH_EXACT ( 0x01814400 ) ,
2016-11-04 16:51:23 +01:00
. name = " Meson GXL Internal PHY " ,
2019-04-12 20:47:03 +02:00
/* PHY_BASIC_FEATURES */
2018-11-09 18:17:22 +01:00
. flags = PHY_IS_INTERNAL ,
2019-01-13 01:22:55 +01:00
. soft_reset = genphy_soft_reset ,
2016-11-04 16:51:23 +01:00
. config_init = meson_gxl_config_init ,
2017-12-08 12:08:11 +01:00
. read_status = meson_gxl_read_status ,
2017-12-18 10:44:45 +01:00
. config_intr = meson_gxl_config_intr ,
2020-11-23 17:38:07 +02:00
. handle_interrupt = meson_gxl_handle_interrupt ,
2016-11-04 16:51:23 +01:00
. suspend = genphy_suspend ,
. resume = genphy_resume ,
2019-04-04 15:11:46 +02:00
} , {
PHY_ID_MATCH_EXACT ( 0x01803301 ) ,
. name = " Meson G12A Internal PHY " ,
2019-04-12 20:47:03 +02:00
/* PHY_BASIC_FEATURES */
2019-04-04 15:11:46 +02:00
. flags = PHY_IS_INTERNAL ,
. soft_reset = genphy_soft_reset ,
. config_intr = meson_gxl_config_intr ,
2020-11-23 17:38:07 +02:00
. handle_interrupt = meson_gxl_handle_interrupt ,
2019-04-04 15:11:46 +02:00
. suspend = genphy_suspend ,
. resume = genphy_resume ,
2016-11-04 16:51:23 +01:00
} ,
} ;
static struct mdio_device_id __maybe_unused meson_gxl_tbl [ ] = {
2019-04-04 15:11:47 +02:00
{ PHY_ID_MATCH_VENDOR ( 0x01814400 ) } ,
2019-04-04 15:11:46 +02:00
{ PHY_ID_MATCH_VENDOR ( 0x01803301 ) } ,
2016-11-04 16:51:23 +01:00
{ }
} ;
module_phy_driver ( meson_gxl_phy ) ;
MODULE_DEVICE_TABLE ( mdio , meson_gxl_tbl ) ;
MODULE_DESCRIPTION ( " Amlogic Meson GXL Internal PHY driver " ) ;
MODULE_AUTHOR ( " Baoqi wang " ) ;
MODULE_AUTHOR ( " Neil Armstrong <narmstrong@baylibre.com> " ) ;
2017-12-18 10:44:46 +01:00
MODULE_AUTHOR ( " Jerome Brunet <jbrunet@baylibre.com> " ) ;
2016-11-04 16:51:23 +01:00
MODULE_LICENSE ( " GPL " ) ;