2019-02-07 16:19:10 +00:00
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright ( C ) 2018 Russell King , Deep Blue Solutions Ltd .
*
* Partly derived from CP110 comphy driver by Antoine Tenart
* < antoine . tenart @ bootlin . com >
*/
# include <linux/delay.h>
# include <linux/iopoll.h>
# include <linux/module.h>
# include <linux/phy/phy.h>
# include <linux/phy.h>
# include <linux/platform_device.h>
# define MAX_A38X_COMPHY 6
# define MAX_A38X_PORTS 3
# define COMPHY_CFG1 0x00
# define COMPHY_CFG1_GEN_TX(x) ((x) << 26)
# define COMPHY_CFG1_GEN_TX_MSK COMPHY_CFG1_GEN_TX(15)
# define COMPHY_CFG1_GEN_RX(x) ((x) << 22)
# define COMPHY_CFG1_GEN_RX_MSK COMPHY_CFG1_GEN_RX(15)
# define GEN_SGMII_1_25GBPS 6
# define GEN_SGMII_3_125GBPS 8
# define COMPHY_STAT1 0x18
# define COMPHY_STAT1_PLL_RDY_TX BIT(3)
# define COMPHY_STAT1_PLL_RDY_RX BIT(2)
# define COMPHY_SELECTOR 0xfc
struct a38x_comphy ;
struct a38x_comphy_lane {
void __iomem * base ;
struct a38x_comphy * priv ;
unsigned int n ;
int port ;
} ;
struct a38x_comphy {
void __iomem * base ;
struct device * dev ;
struct a38x_comphy_lane lane [ MAX_A38X_COMPHY ] ;
} ;
static const u8 gbe_mux [ MAX_A38X_COMPHY ] [ MAX_A38X_PORTS ] = {
{ 0 , 0 , 0 } ,
{ 4 , 5 , 0 } ,
{ 0 , 4 , 0 } ,
{ 0 , 0 , 4 } ,
{ 0 , 3 , 0 } ,
{ 0 , 0 , 3 } ,
} ;
static void a38x_comphy_set_reg ( struct a38x_comphy_lane * lane ,
unsigned int offset , u32 mask , u32 value )
{
u32 val ;
val = readl_relaxed ( lane - > base + offset ) & ~ mask ;
writel ( val | value , lane - > base + offset ) ;
}
static void a38x_comphy_set_speed ( struct a38x_comphy_lane * lane ,
unsigned int gen_tx , unsigned int gen_rx )
{
a38x_comphy_set_reg ( lane , COMPHY_CFG1 ,
COMPHY_CFG1_GEN_TX_MSK | COMPHY_CFG1_GEN_RX_MSK ,
COMPHY_CFG1_GEN_TX ( gen_tx ) |
COMPHY_CFG1_GEN_RX ( gen_rx ) ) ;
}
static int a38x_comphy_poll ( struct a38x_comphy_lane * lane ,
unsigned int offset , u32 mask , u32 value )
{
u32 val ;
int ret ;
ret = readl_relaxed_poll_timeout_atomic ( lane - > base + offset , val ,
( val & mask ) = = value ,
1000 , 150000 ) ;
if ( ret )
dev_err ( lane - > priv - > dev ,
" comphy%u: timed out waiting for status \n " , lane - > n ) ;
return ret ;
}
/*
* We only support changing the speed for comphys configured for GBE .
* Since that is all we do , we only poll for PLL ready status .
*/
static int a38x_comphy_set_mode ( struct phy * phy , enum phy_mode mode , int sub )
{
struct a38x_comphy_lane * lane = phy_get_drvdata ( phy ) ;
unsigned int gen ;
if ( mode ! = PHY_MODE_ETHERNET )
return - EINVAL ;
switch ( sub ) {
case PHY_INTERFACE_MODE_SGMII :
case PHY_INTERFACE_MODE_1000BASEX :
gen = GEN_SGMII_1_25GBPS ;
break ;
case PHY_INTERFACE_MODE_2500BASEX :
gen = GEN_SGMII_3_125GBPS ;
break ;
default :
return - EINVAL ;
}
a38x_comphy_set_speed ( lane , gen , gen ) ;
return a38x_comphy_poll ( lane , COMPHY_STAT1 ,
COMPHY_STAT1_PLL_RDY_TX |
COMPHY_STAT1_PLL_RDY_RX ,
COMPHY_STAT1_PLL_RDY_TX |
COMPHY_STAT1_PLL_RDY_RX ) ;
}
static const struct phy_ops a38x_comphy_ops = {
. set_mode = a38x_comphy_set_mode ,
. owner = THIS_MODULE ,
} ;
static struct phy * a38x_comphy_xlate ( struct device * dev ,
struct of_phandle_args * args )
{
struct a38x_comphy_lane * lane ;
struct phy * phy ;
u32 val ;
if ( WARN_ON ( args - > args [ 0 ] > = MAX_A38X_PORTS ) )
return ERR_PTR ( - EINVAL ) ;
phy = of_phy_simple_xlate ( dev , args ) ;
if ( IS_ERR ( phy ) )
return phy ;
lane = phy_get_drvdata ( phy ) ;
if ( lane - > port > = 0 )
return ERR_PTR ( - EBUSY ) ;
lane - > port = args - > args [ 0 ] ;
val = readl_relaxed ( lane - > priv - > base + COMPHY_SELECTOR ) ;
val = ( val > > ( 4 * lane - > n ) ) & 0xf ;
if ( ! gbe_mux [ lane - > n ] [ lane - > port ] | |
val ! = gbe_mux [ lane - > n ] [ lane - > port ] ) {
dev_warn ( lane - > priv - > dev ,
" comphy%u: not configured for GBE \n " , lane - > n ) ;
phy = ERR_PTR ( - EINVAL ) ;
}
return phy ;
}
static int a38x_comphy_probe ( struct platform_device * pdev )
{
struct phy_provider * provider ;
struct device_node * child ;
struct a38x_comphy * priv ;
struct resource * res ;
void __iomem * base ;
priv = devm_kzalloc ( & pdev - > dev , sizeof ( * priv ) , GFP_KERNEL ) ;
if ( ! priv )
return - ENOMEM ;
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
base = devm_ioremap_resource ( & pdev - > dev , res ) ;
if ( IS_ERR ( base ) )
return PTR_ERR ( base ) ;
priv - > dev = & pdev - > dev ;
priv - > base = base ;
for_each_available_child_of_node ( pdev - > dev . of_node , child ) {
struct phy * phy ;
int ret ;
u32 val ;
ret = of_property_read_u32 ( child , " reg " , & val ) ;
if ( ret < 0 ) {
dev_err ( & pdev - > dev , " missing 'reg' property (%d) \n " ,
ret ) ;
continue ;
}
if ( val > = MAX_A38X_COMPHY | | priv - > lane [ val ] . base ) {
dev_err ( & pdev - > dev , " invalid 'reg' property \n " ) ;
continue ;
}
phy = devm_phy_create ( & pdev - > dev , child , & a38x_comphy_ops ) ;
2019-07-23 16:19:19 +05:30
if ( IS_ERR ( phy ) ) {
of_node_put ( child ) ;
2019-02-07 16:19:10 +00:00
return PTR_ERR ( phy ) ;
2019-07-23 16:19:19 +05:30
}
2019-02-07 16:19:10 +00:00
priv - > lane [ val ] . base = base + 0x28 * val ;
priv - > lane [ val ] . priv = priv ;
priv - > lane [ val ] . n = val ;
priv - > lane [ val ] . port = - 1 ;
phy_set_drvdata ( phy , & priv - > lane [ val ] ) ;
}
dev_set_drvdata ( & pdev - > dev , priv ) ;
provider = devm_of_phy_provider_register ( & pdev - > dev , a38x_comphy_xlate ) ;
return PTR_ERR_OR_ZERO ( provider ) ;
}
static const struct of_device_id a38x_comphy_of_match_table [ ] = {
{ . compatible = " marvell,armada-380-comphy " } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , a38x_comphy_of_match_table ) ;
static struct platform_driver a38x_comphy_driver = {
. probe = a38x_comphy_probe ,
. driver = {
. name = " armada-38x-comphy " ,
. of_match_table = a38x_comphy_of_match_table ,
} ,
} ;
module_platform_driver ( a38x_comphy_driver ) ;
MODULE_AUTHOR ( " Russell King <rmk+kernel@armlinux.org.uk> " ) ;
MODULE_DESCRIPTION ( " Common PHY driver for Armada 38x SoCs " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;