2018-11-26 03:15:23 +03:00
// SPDX-License-Identifier: GPL-2.0
/*
* Texas Instruments CPSW Port ' s PHY Interface Mode selection Driver
*
* Copyright ( C ) 2018 Texas Instruments Incorporated - http : //www.ti.com/
*
* Based on cpsw - phy - sel . c driver created by Mugunthan V N < mugunthanvnm @ ti . com >
*/
# include <linux/platform_device.h>
# include <linux/module.h>
# include <linux/mfd/syscon.h>
# include <linux/of.h>
2020-08-28 23:19:43 +03:00
# include <linux/of_address.h>
2018-11-26 03:15:23 +03:00
# include <linux/of_net.h>
# include <linux/phy.h>
# include <linux/phy/phy.h>
# include <linux/regmap.h>
/* AM33xx SoC specific definitions for the CONTROL port */
# define AM33XX_GMII_SEL_MODE_MII 0
# define AM33XX_GMII_SEL_MODE_RMII 1
# define AM33XX_GMII_SEL_MODE_RGMII 2
enum {
2020-08-28 23:19:41 +03:00
PHY_GMII_SEL_PORT_MODE = 0 ,
2018-11-26 03:15:23 +03:00
PHY_GMII_SEL_RGMII_ID_MODE ,
PHY_GMII_SEL_RMII_IO_CLK_EN ,
PHY_GMII_SEL_LAST ,
} ;
struct phy_gmii_sel_phy_priv {
struct phy_gmii_sel_priv * priv ;
u32 id ;
struct phy * if_phy ;
int rmii_clock_external ;
int phy_if_mode ;
struct regmap_field * fields [ PHY_GMII_SEL_LAST ] ;
} ;
struct phy_gmii_sel_soc_data {
u32 num_ports ;
u32 features ;
const struct reg_field ( * regfields ) [ PHY_GMII_SEL_LAST ] ;
2020-08-28 23:19:43 +03:00
bool use_of_data ;
2018-11-26 03:15:23 +03:00
} ;
struct phy_gmii_sel_priv {
struct device * dev ;
const struct phy_gmii_sel_soc_data * soc_data ;
struct regmap * regmap ;
struct phy_provider * phy_provider ;
struct phy_gmii_sel_phy_priv * if_phys ;
2020-08-28 23:19:43 +03:00
u32 num_ports ;
u32 reg_offset ;
2018-11-26 03:15:23 +03:00
} ;
static int phy_gmii_sel_mode ( struct phy * phy , enum phy_mode mode , int submode )
{
struct phy_gmii_sel_phy_priv * if_phy = phy_get_drvdata ( phy ) ;
const struct phy_gmii_sel_soc_data * soc_data = if_phy - > priv - > soc_data ;
struct device * dev = if_phy - > priv - > dev ;
struct regmap_field * regfield ;
int ret , rgmii_id = 0 ;
u32 gmii_sel_mode = 0 ;
if ( mode ! = PHY_MODE_ETHERNET )
return - EINVAL ;
switch ( submode ) {
case PHY_INTERFACE_MODE_RMII :
gmii_sel_mode = AM33XX_GMII_SEL_MODE_RMII ;
break ;
case PHY_INTERFACE_MODE_RGMII :
2019-10-23 17:47:44 +03:00
case PHY_INTERFACE_MODE_RGMII_RXID :
2018-11-26 03:15:23 +03:00
gmii_sel_mode = AM33XX_GMII_SEL_MODE_RGMII ;
break ;
case PHY_INTERFACE_MODE_RGMII_ID :
case PHY_INTERFACE_MODE_RGMII_TXID :
gmii_sel_mode = AM33XX_GMII_SEL_MODE_RGMII ;
rgmii_id = 1 ;
break ;
case PHY_INTERFACE_MODE_MII :
2020-02-14 22:08:01 +03:00
case PHY_INTERFACE_MODE_GMII :
2020-02-14 22:08:00 +03:00
gmii_sel_mode = AM33XX_GMII_SEL_MODE_MII ;
2018-11-26 03:15:23 +03:00
break ;
default :
2020-02-14 22:08:00 +03:00
dev_warn ( dev , " port%u: unsupported mode: \" %s \" \n " ,
if_phy - > id , phy_modes ( submode ) ) ;
2018-11-26 03:15:23 +03:00
return - EINVAL ;
2018-12-09 17:20:03 +03:00
}
2018-11-26 03:15:23 +03:00
if_phy - > phy_if_mode = submode ;
dev_dbg ( dev , " %s id:%u mode:%u rgmii_id:%d rmii_clk_ext:%d \n " ,
2020-02-14 22:08:00 +03:00
__func__ , if_phy - > id , submode , rgmii_id ,
2018-11-26 03:15:23 +03:00
if_phy - > rmii_clock_external ) ;
regfield = if_phy - > fields [ PHY_GMII_SEL_PORT_MODE ] ;
ret = regmap_field_write ( regfield , gmii_sel_mode ) ;
if ( ret ) {
dev_err ( dev , " port%u: set mode fail %d " , if_phy - > id , ret ) ;
return ret ;
}
if ( soc_data - > features & BIT ( PHY_GMII_SEL_RGMII_ID_MODE ) & &
if_phy - > fields [ PHY_GMII_SEL_RGMII_ID_MODE ] ) {
regfield = if_phy - > fields [ PHY_GMII_SEL_RGMII_ID_MODE ] ;
ret = regmap_field_write ( regfield , rgmii_id ) ;
if ( ret )
return ret ;
}
if ( soc_data - > features & BIT ( PHY_GMII_SEL_RMII_IO_CLK_EN ) & &
if_phy - > fields [ PHY_GMII_SEL_RMII_IO_CLK_EN ] ) {
regfield = if_phy - > fields [ PHY_GMII_SEL_RMII_IO_CLK_EN ] ;
ret = regmap_field_write ( regfield ,
if_phy - > rmii_clock_external ) ;
}
return 0 ;
}
static const
struct reg_field phy_gmii_sel_fields_am33xx [ ] [ PHY_GMII_SEL_LAST ] = {
{
[ PHY_GMII_SEL_PORT_MODE ] = REG_FIELD ( 0x650 , 0 , 1 ) ,
[ PHY_GMII_SEL_RGMII_ID_MODE ] = REG_FIELD ( 0x650 , 4 , 4 ) ,
[ PHY_GMII_SEL_RMII_IO_CLK_EN ] = REG_FIELD ( 0x650 , 6 , 6 ) ,
} ,
{
[ PHY_GMII_SEL_PORT_MODE ] = REG_FIELD ( 0x650 , 2 , 3 ) ,
[ PHY_GMII_SEL_RGMII_ID_MODE ] = REG_FIELD ( 0x650 , 5 , 5 ) ,
[ PHY_GMII_SEL_RMII_IO_CLK_EN ] = REG_FIELD ( 0x650 , 7 , 7 ) ,
} ,
} ;
static const
struct phy_gmii_sel_soc_data phy_gmii_sel_soc_am33xx = {
. num_ports = 2 ,
. features = BIT ( PHY_GMII_SEL_RGMII_ID_MODE ) |
BIT ( PHY_GMII_SEL_RMII_IO_CLK_EN ) ,
. regfields = phy_gmii_sel_fields_am33xx ,
} ;
static const
struct reg_field phy_gmii_sel_fields_dra7 [ ] [ PHY_GMII_SEL_LAST ] = {
{
[ PHY_GMII_SEL_PORT_MODE ] = REG_FIELD ( 0x554 , 0 , 1 ) ,
} ,
{
[ PHY_GMII_SEL_PORT_MODE ] = REG_FIELD ( 0x554 , 4 , 5 ) ,
} ,
} ;
static const
struct phy_gmii_sel_soc_data phy_gmii_sel_soc_dra7 = {
. num_ports = 2 ,
. regfields = phy_gmii_sel_fields_dra7 ,
} ;
static const
struct phy_gmii_sel_soc_data phy_gmii_sel_soc_dm814 = {
. num_ports = 2 ,
. features = BIT ( PHY_GMII_SEL_RGMII_ID_MODE ) ,
. regfields = phy_gmii_sel_fields_am33xx ,
} ;
2020-03-03 19:00:27 +03:00
static const
struct reg_field phy_gmii_sel_fields_am654 [ ] [ PHY_GMII_SEL_LAST ] = {
2020-08-28 23:19:43 +03:00
{ [ PHY_GMII_SEL_PORT_MODE ] = REG_FIELD ( 0x0 , 0 , 2 ) , } ,
{ [ PHY_GMII_SEL_PORT_MODE ] = REG_FIELD ( 0x4 , 0 , 2 ) , } ,
{ [ PHY_GMII_SEL_PORT_MODE ] = REG_FIELD ( 0x8 , 0 , 2 ) , } ,
{ [ PHY_GMII_SEL_PORT_MODE ] = REG_FIELD ( 0xC , 0 , 2 ) , } ,
{ [ PHY_GMII_SEL_PORT_MODE ] = REG_FIELD ( 0x10 , 0 , 2 ) , } ,
{ [ PHY_GMII_SEL_PORT_MODE ] = REG_FIELD ( 0x14 , 0 , 2 ) , } ,
{ [ PHY_GMII_SEL_PORT_MODE ] = REG_FIELD ( 0x18 , 0 , 2 ) , } ,
{ [ PHY_GMII_SEL_PORT_MODE ] = REG_FIELD ( 0x1C , 0 , 2 ) , } ,
2020-03-03 19:00:27 +03:00
} ;
static const
struct phy_gmii_sel_soc_data phy_gmii_sel_soc_am654 = {
2020-08-28 23:19:43 +03:00
. use_of_data = true ,
2020-03-03 19:00:27 +03:00
. regfields = phy_gmii_sel_fields_am654 ,
} ;
2018-11-26 03:15:23 +03:00
static const struct of_device_id phy_gmii_sel_id_table [ ] = {
{
. compatible = " ti,am3352-phy-gmii-sel " ,
. data = & phy_gmii_sel_soc_am33xx ,
} ,
{
. compatible = " ti,dra7xx-phy-gmii-sel " ,
. data = & phy_gmii_sel_soc_dra7 ,
} ,
{
. compatible = " ti,am43xx-phy-gmii-sel " ,
. data = & phy_gmii_sel_soc_am33xx ,
} ,
{
. compatible = " ti,dm814-phy-gmii-sel " ,
. data = & phy_gmii_sel_soc_dm814 ,
} ,
2020-03-03 19:00:27 +03:00
{
. compatible = " ti,am654-phy-gmii-sel " ,
. data = & phy_gmii_sel_soc_am654 ,
} ,
2018-11-26 03:15:23 +03:00
{ }
} ;
MODULE_DEVICE_TABLE ( of , phy_gmii_sel_id_table ) ;
static const struct phy_ops phy_gmii_sel_ops = {
. set_mode = phy_gmii_sel_mode ,
. owner = THIS_MODULE ,
} ;
static struct phy * phy_gmii_sel_of_xlate ( struct device * dev ,
struct of_phandle_args * args )
{
struct phy_gmii_sel_priv * priv = dev_get_drvdata ( dev ) ;
int phy_id = args - > args [ 0 ] ;
if ( args - > args_count < 1 )
return ERR_PTR ( - EINVAL ) ;
2018-12-22 14:56:53 +03:00
if ( ! priv | | ! priv - > if_phys )
return ERR_PTR ( - ENODEV ) ;
2018-11-26 03:15:23 +03:00
if ( priv - > soc_data - > features & BIT ( PHY_GMII_SEL_RMII_IO_CLK_EN ) & &
args - > args_count < 2 )
return ERR_PTR ( - EINVAL ) ;
2020-08-28 23:19:43 +03:00
if ( phy_id > priv - > num_ports )
2018-11-26 03:15:23 +03:00
return ERR_PTR ( - EINVAL ) ;
if ( phy_id ! = priv - > if_phys [ phy_id - 1 ] . id )
return ERR_PTR ( - EINVAL ) ;
phy_id - - ;
if ( priv - > soc_data - > features & BIT ( PHY_GMII_SEL_RMII_IO_CLK_EN ) )
priv - > if_phys [ phy_id ] . rmii_clock_external = args - > args [ 1 ] ;
dev_dbg ( dev , " %s id:%u ext:%d \n " , __func__ ,
priv - > if_phys [ phy_id ] . id , args - > args [ 1 ] ) ;
return priv - > if_phys [ phy_id ] . if_phy ;
}
2020-08-28 23:19:41 +03:00
static int phy_gmii_init_phy ( struct phy_gmii_sel_priv * priv , int port ,
struct phy_gmii_sel_phy_priv * if_phy )
{
const struct phy_gmii_sel_soc_data * soc_data = priv - > soc_data ;
struct device * dev = priv - > dev ;
const struct reg_field * fields ;
struct regmap_field * regfield ;
struct reg_field field ;
int ret ;
if_phy - > id = port ;
if_phy - > priv = priv ;
fields = soc_data - > regfields [ port - 1 ] ;
field = * fields + + ;
2020-08-28 23:19:43 +03:00
field . reg + = priv - > reg_offset ;
2020-08-28 23:19:41 +03:00
dev_dbg ( dev , " %s field %x %d %d \n " , __func__ ,
field . reg , field . msb , field . lsb ) ;
regfield = devm_regmap_field_alloc ( dev , priv - > regmap , field ) ;
if ( IS_ERR ( regfield ) )
return PTR_ERR ( regfield ) ;
if_phy - > fields [ PHY_GMII_SEL_PORT_MODE ] = regfield ;
field = * fields + + ;
2020-08-28 23:19:43 +03:00
field . reg + = priv - > reg_offset ;
2020-08-28 23:19:42 +03:00
if ( soc_data - > features & BIT ( PHY_GMII_SEL_RGMII_ID_MODE ) ) {
2020-08-28 23:19:41 +03:00
regfield = devm_regmap_field_alloc ( dev ,
priv - > regmap ,
field ) ;
if ( IS_ERR ( regfield ) )
return PTR_ERR ( regfield ) ;
if_phy - > fields [ PHY_GMII_SEL_RGMII_ID_MODE ] = regfield ;
dev_dbg ( dev , " %s field %x %d %d \n " , __func__ ,
field . reg , field . msb , field . lsb ) ;
}
field = * fields ;
2020-08-28 23:19:43 +03:00
field . reg + = priv - > reg_offset ;
2020-08-28 23:19:42 +03:00
if ( soc_data - > features & BIT ( PHY_GMII_SEL_RMII_IO_CLK_EN ) ) {
2020-08-28 23:19:41 +03:00
regfield = devm_regmap_field_alloc ( dev ,
priv - > regmap ,
field ) ;
if ( IS_ERR ( regfield ) )
return PTR_ERR ( regfield ) ;
if_phy - > fields [ PHY_GMII_SEL_RMII_IO_CLK_EN ] = regfield ;
dev_dbg ( dev , " %s field %x %d %d \n " , __func__ ,
field . reg , field . msb , field . lsb ) ;
}
if_phy - > if_phy = devm_phy_create ( dev ,
priv - > dev - > of_node ,
& phy_gmii_sel_ops ) ;
if ( IS_ERR ( if_phy - > if_phy ) ) {
ret = PTR_ERR ( if_phy - > if_phy ) ;
dev_err ( dev , " Failed to create phy%d %d \n " , port , ret ) ;
return ret ;
}
phy_set_drvdata ( if_phy - > if_phy , if_phy ) ;
return 0 ;
}
2018-11-26 03:15:23 +03:00
static int phy_gmii_sel_init_ports ( struct phy_gmii_sel_priv * priv )
{
const struct phy_gmii_sel_soc_data * soc_data = priv - > soc_data ;
struct phy_gmii_sel_phy_priv * if_phys ;
2020-08-28 23:19:43 +03:00
struct device * dev = priv - > dev ;
int i , ret ;
if ( soc_data - > use_of_data ) {
const __be32 * offset ;
u64 size ;
2018-11-26 03:15:23 +03:00
2020-08-28 23:19:43 +03:00
offset = of_get_address ( dev - > of_node , 0 , & size , NULL ) ;
priv - > num_ports = size / sizeof ( u32 ) ;
if ( ! priv - > num_ports )
return - EINVAL ;
priv - > reg_offset = __be32_to_cpu ( * offset ) ;
}
2018-11-26 03:15:23 +03:00
2020-08-28 23:19:43 +03:00
if_phys = devm_kcalloc ( dev , priv - > num_ports ,
2018-11-26 03:15:23 +03:00
sizeof ( * if_phys ) , GFP_KERNEL ) ;
if ( ! if_phys )
return - ENOMEM ;
2020-08-28 23:19:43 +03:00
dev_dbg ( dev , " %s %d \n " , __func__ , priv - > num_ports ) ;
2018-11-26 03:15:23 +03:00
2020-08-28 23:19:43 +03:00
for ( i = 0 ; i < priv - > num_ports ; i + + ) {
2020-08-28 23:19:41 +03:00
ret = phy_gmii_init_phy ( priv , i + 1 , & if_phys [ i ] ) ;
if ( ret )
2018-11-26 03:15:23 +03:00
return ret ;
}
priv - > if_phys = if_phys ;
return 0 ;
}
static int phy_gmii_sel_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
struct device_node * node = dev - > of_node ;
const struct of_device_id * of_id ;
struct phy_gmii_sel_priv * priv ;
int ret ;
of_id = of_match_node ( phy_gmii_sel_id_table , pdev - > dev . of_node ) ;
if ( ! of_id )
return - EINVAL ;
priv = devm_kzalloc ( & pdev - > dev , sizeof ( * priv ) , GFP_KERNEL ) ;
if ( ! priv )
return - ENOMEM ;
priv - > dev = & pdev - > dev ;
priv - > soc_data = of_id - > data ;
2020-08-28 23:19:43 +03:00
priv - > num_ports = priv - > soc_data - > num_ports ;
2018-11-26 03:15:23 +03:00
priv - > regmap = syscon_node_to_regmap ( node - > parent ) ;
if ( IS_ERR ( priv - > regmap ) ) {
ret = PTR_ERR ( priv - > regmap ) ;
dev_err ( dev , " Failed to get syscon %d \n " , ret ) ;
return ret ;
}
ret = phy_gmii_sel_init_ports ( priv ) ;
if ( ret )
return ret ;
dev_set_drvdata ( & pdev - > dev , priv ) ;
priv - > phy_provider =
devm_of_phy_provider_register ( dev ,
phy_gmii_sel_of_xlate ) ;
if ( IS_ERR ( priv - > phy_provider ) ) {
ret = PTR_ERR ( priv - > phy_provider ) ;
dev_err ( dev , " Failed to create phy provider %d \n " , ret ) ;
return ret ;
}
return 0 ;
}
static struct platform_driver phy_gmii_sel_driver = {
. probe = phy_gmii_sel_probe ,
. driver = {
. name = " phy-gmii-sel " ,
. of_match_table = phy_gmii_sel_id_table ,
} ,
} ;
module_platform_driver ( phy_gmii_sel_driver ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_AUTHOR ( " Grygorii Strashko <grygorii.strashko@ti.com> " ) ;
MODULE_DESCRIPTION ( " TI CPSW Port's PHY Interface Mode selection Driver " ) ;