2018-05-14 23:04:55 +03:00
// SPDX-License-Identifier: (GPL-2.0 OR MIT)
/*
* Driver for the MDIO interface of Microsemi network switches .
*
* Author : Alexandre Belloni < alexandre . belloni @ bootlin . com >
* Copyright ( c ) 2017 Microsemi Corporation
*/
# include <linux/bitops.h>
2022-04-05 15:09:51 +03:00
# include <linux/clk.h>
2018-05-14 23:04:55 +03:00
# include <linux/io.h>
# include <linux/iopoll.h>
2021-03-15 13:49:05 +03:00
# include <linux/kernel.h>
2021-11-29 04:57:37 +03:00
# include <linux/mdio/mdio-mscc-miim.h>
2022-09-05 19:21:26 +03:00
# include <linux/mfd/ocelot.h>
2021-03-15 13:49:05 +03:00
# include <linux/module.h>
2018-05-14 23:04:55 +03:00
# include <linux/of_mdio.h>
2021-03-15 13:49:05 +03:00
# include <linux/phy.h>
# include <linux/platform_device.h>
2022-03-18 23:13:24 +03:00
# include <linux/property.h>
2021-11-29 04:57:35 +03:00
# include <linux/regmap.h>
2018-05-14 23:04:55 +03:00
# define MSCC_MIIM_REG_STATUS 0x0
2020-05-26 19:22:55 +03:00
# define MSCC_MIIM_STATUS_STAT_PENDING BIT(2)
2018-05-14 23:04:55 +03:00
# define MSCC_MIIM_STATUS_STAT_BUSY BIT(3)
# define MSCC_MIIM_REG_CMD 0x8
# define MSCC_MIIM_CMD_OPR_WRITE BIT(1)
# define MSCC_MIIM_CMD_OPR_READ BIT(2)
# define MSCC_MIIM_CMD_WRDATA_SHIFT 4
# define MSCC_MIIM_CMD_REGAD_SHIFT 20
# define MSCC_MIIM_CMD_PHYAD_SHIFT 25
# define MSCC_MIIM_CMD_VLD BIT(31)
# define MSCC_MIIM_REG_DATA 0xC
# define MSCC_MIIM_DATA_ERROR (BIT(16) | BIT(17))
2022-04-05 15:09:51 +03:00
# define MSCC_MIIM_REG_CFG 0x10
# define MSCC_MIIM_CFG_PRESCALE_MASK GENMASK(7, 0)
2018-05-14 23:04:55 +03:00
# define MSCC_PHY_REG_PHY_CFG 0x0
# define PHY_CFG_PHY_ENA (BIT(0) | BIT(1) | BIT(2) | BIT(3))
# define PHY_CFG_PHY_COMMON_RESET BIT(4)
# define PHY_CFG_PHY_RESET (BIT(5) | BIT(6) | BIT(7) | BIT(8))
# define MSCC_PHY_REG_PHY_STATUS 0x4
2022-03-18 23:13:24 +03:00
# define LAN966X_CUPHY_COMMON_CFG 0x0
# define CUPHY_COMMON_CFG_RESET_N BIT(0)
struct mscc_miim_info {
unsigned int phy_reset_offset ;
unsigned int phy_reset_bits ;
} ;
2018-05-14 23:04:55 +03:00
struct mscc_miim_dev {
2021-11-29 04:57:35 +03:00
struct regmap * regs ;
2021-11-29 04:57:37 +03:00
int mii_status_offset ;
2021-11-29 04:57:35 +03:00
struct regmap * phy_regs ;
2022-03-18 23:13:24 +03:00
const struct mscc_miim_info * info ;
2022-04-05 15:09:51 +03:00
struct clk * clk ;
u32 bus_freq ;
2018-05-14 23:04:55 +03:00
} ;
2020-05-26 19:22:56 +03:00
/* When high resolution timers aren't built-in: we can't use usleep_range() as
* we would sleep way too long . Use udelay ( ) instead .
*/
2021-11-29 04:57:35 +03:00
# define mscc_readx_poll_timeout(op, addr, val, cond, delay_us, timeout_us)\
( { \
if ( ! IS_ENABLED ( CONFIG_HIGH_RES_TIMERS ) ) \
readx_poll_timeout_atomic ( op , addr , val , cond , delay_us , \
timeout_us ) ; \
readx_poll_timeout ( op , addr , val , cond , delay_us , timeout_us ) ; \
2020-05-26 19:22:56 +03:00
} )
2021-11-29 04:57:35 +03:00
static int mscc_miim_status ( struct mii_bus * bus )
2018-05-14 23:04:55 +03:00
{
struct mscc_miim_dev * miim = bus - > priv ;
2021-11-29 04:57:35 +03:00
int val , ret ;
2021-11-29 04:57:37 +03:00
ret = regmap_read ( miim - > regs ,
MSCC_MIIM_REG_STATUS + miim - > mii_status_offset , & val ) ;
2021-11-29 04:57:35 +03:00
if ( ret < 0 ) {
WARN_ONCE ( 1 , " mscc miim status read error %d \n " , ret ) ;
return ret ;
}
return val ;
}
static int mscc_miim_wait_ready ( struct mii_bus * bus )
{
2018-05-14 23:04:55 +03:00
u32 val ;
2021-11-29 04:57:35 +03:00
return mscc_readx_poll_timeout ( mscc_miim_status , bus , val ,
2020-05-26 19:22:56 +03:00
! ( val & MSCC_MIIM_STATUS_STAT_BUSY ) , 50 ,
10000 ) ;
2018-05-14 23:04:55 +03:00
}
2020-05-26 19:22:55 +03:00
static int mscc_miim_wait_pending ( struct mii_bus * bus )
{
u32 val ;
2021-11-29 04:57:35 +03:00
return mscc_readx_poll_timeout ( mscc_miim_status , bus , val ,
2020-05-26 19:22:56 +03:00
! ( val & MSCC_MIIM_STATUS_STAT_PENDING ) ,
50 , 10000 ) ;
2020-05-26 19:22:55 +03:00
}
2018-05-14 23:04:55 +03:00
static int mscc_miim_read ( struct mii_bus * bus , int mii_id , int regnum )
{
struct mscc_miim_dev * miim = bus - > priv ;
u32 val ;
int ret ;
2022-04-05 15:02:33 +03:00
if ( regnum & MII_ADDR_C45 )
return - EOPNOTSUPP ;
2020-05-26 19:22:55 +03:00
ret = mscc_miim_wait_pending ( bus ) ;
2018-05-14 23:04:55 +03:00
if ( ret )
goto out ;
2021-11-29 04:57:37 +03:00
ret = regmap_write ( miim - > regs ,
MSCC_MIIM_REG_CMD + miim - > mii_status_offset ,
MSCC_MIIM_CMD_VLD |
2021-11-29 04:57:35 +03:00
( mii_id < < MSCC_MIIM_CMD_PHYAD_SHIFT ) |
( regnum < < MSCC_MIIM_CMD_REGAD_SHIFT ) |
MSCC_MIIM_CMD_OPR_READ ) ;
if ( ret < 0 ) {
WARN_ONCE ( 1 , " mscc miim write cmd reg error %d \n " , ret ) ;
goto out ;
}
2018-05-14 23:04:55 +03:00
ret = mscc_miim_wait_ready ( bus ) ;
if ( ret )
goto out ;
2021-11-29 04:57:37 +03:00
ret = regmap_read ( miim - > regs ,
MSCC_MIIM_REG_DATA + miim - > mii_status_offset , & val ) ;
2021-11-29 04:57:35 +03:00
if ( ret < 0 ) {
WARN_ONCE ( 1 , " mscc miim read data reg error %d \n " , ret ) ;
goto out ;
}
2018-05-14 23:04:55 +03:00
if ( val & MSCC_MIIM_DATA_ERROR ) {
ret = - EIO ;
goto out ;
}
ret = val & 0xFFFF ;
out :
return ret ;
}
static int mscc_miim_write ( struct mii_bus * bus , int mii_id ,
int regnum , u16 value )
{
struct mscc_miim_dev * miim = bus - > priv ;
int ret ;
2022-04-05 15:02:33 +03:00
if ( regnum & MII_ADDR_C45 )
return - EOPNOTSUPP ;
2020-05-26 19:22:55 +03:00
ret = mscc_miim_wait_pending ( bus ) ;
2018-05-14 23:04:55 +03:00
if ( ret < 0 )
goto out ;
2021-11-29 04:57:37 +03:00
ret = regmap_write ( miim - > regs ,
MSCC_MIIM_REG_CMD + miim - > mii_status_offset ,
MSCC_MIIM_CMD_VLD |
2021-11-29 04:57:35 +03:00
( mii_id < < MSCC_MIIM_CMD_PHYAD_SHIFT ) |
( regnum < < MSCC_MIIM_CMD_REGAD_SHIFT ) |
( value < < MSCC_MIIM_CMD_WRDATA_SHIFT ) |
MSCC_MIIM_CMD_OPR_WRITE ) ;
2018-05-14 23:04:55 +03:00
2021-11-29 04:57:35 +03:00
if ( ret < 0 )
WARN_ONCE ( 1 , " mscc miim write error %d \n " , ret ) ;
2018-05-14 23:04:55 +03:00
out :
return ret ;
}
static int mscc_miim_reset ( struct mii_bus * bus )
{
struct mscc_miim_dev * miim = bus - > priv ;
2022-03-18 23:13:24 +03:00
unsigned int offset , bits ;
2021-11-29 04:57:35 +03:00
int ret ;
2018-05-14 23:04:55 +03:00
2022-03-18 23:13:24 +03:00
if ( ! miim - > phy_regs )
return 0 ;
2021-11-29 04:57:35 +03:00
2022-03-18 23:13:24 +03:00
offset = miim - > info - > phy_reset_offset ;
bits = miim - > info - > phy_reset_bits ;
ret = regmap_update_bits ( miim - > phy_regs , offset , bits , 0 ) ;
if ( ret < 0 ) {
WARN_ONCE ( 1 , " mscc reset set error %d \n " , ret ) ;
return ret ;
}
2021-11-29 04:57:35 +03:00
2022-03-18 23:13:24 +03:00
ret = regmap_update_bits ( miim - > phy_regs , offset , bits , bits ) ;
if ( ret < 0 ) {
WARN_ONCE ( 1 , " mscc reset clear error %d \n " , ret ) ;
return ret ;
2018-05-14 23:04:55 +03:00
}
2022-03-18 23:13:24 +03:00
mdelay ( 500 ) ;
2018-05-14 23:04:55 +03:00
return 0 ;
}
2021-11-29 04:57:35 +03:00
static const struct regmap_config mscc_miim_regmap_config = {
. reg_bits = 32 ,
. val_bits = 32 ,
. reg_stride = 4 ,
} ;
2022-03-13 01:41:40 +03:00
static const struct regmap_config mscc_miim_phy_regmap_config = {
. reg_bits = 32 ,
. val_bits = 32 ,
. reg_stride = 4 ,
. name = " phy " ,
} ;
2021-11-29 04:57:37 +03:00
int mscc_miim_setup ( struct device * dev , struct mii_bus * * pbus , const char * name ,
struct regmap * mii_regmap , int status_offset )
2018-05-14 23:04:55 +03:00
{
2021-11-29 04:57:35 +03:00
struct mscc_miim_dev * miim ;
2021-09-28 10:17:20 +03:00
struct mii_bus * bus ;
2018-05-14 23:04:55 +03:00
2021-11-29 04:57:35 +03:00
bus = devm_mdiobus_alloc_size ( dev , sizeof ( * miim ) ) ;
2018-05-14 23:04:55 +03:00
if ( ! bus )
return - ENOMEM ;
2021-11-29 04:57:37 +03:00
bus - > name = name ;
2018-05-14 23:04:55 +03:00
bus - > read = mscc_miim_read ;
bus - > write = mscc_miim_write ;
bus - > reset = mscc_miim_reset ;
2021-11-29 04:57:35 +03:00
snprintf ( bus - > id , MII_BUS_ID_SIZE , " %s-mii " , dev_name ( dev ) ) ;
bus - > parent = dev ;
miim = bus - > priv ;
* pbus = bus ;
miim - > regs = mii_regmap ;
2021-11-29 04:57:37 +03:00
miim - > mii_status_offset = status_offset ;
* pbus = bus ;
2021-11-29 04:57:35 +03:00
return 0 ;
}
2021-11-29 04:57:37 +03:00
EXPORT_SYMBOL ( mscc_miim_setup ) ;
2021-11-29 04:57:35 +03:00
2022-04-05 15:09:51 +03:00
static int mscc_miim_clk_set ( struct mii_bus * bus )
{
struct mscc_miim_dev * miim = bus - > priv ;
unsigned long rate ;
u32 div ;
/* Keep the current settings */
if ( ! miim - > bus_freq )
return 0 ;
rate = clk_get_rate ( miim - > clk ) ;
div = DIV_ROUND_UP ( rate , 2 * miim - > bus_freq ) - 1 ;
if ( div = = 0 | | div & ~ MSCC_MIIM_CFG_PRESCALE_MASK ) {
dev_err ( & bus - > dev , " Incorrect MDIO clock frequency \n " ) ;
return - EINVAL ;
}
return regmap_update_bits ( miim - > regs , MSCC_MIIM_REG_CFG ,
MSCC_MIIM_CFG_PRESCALE_MASK , div ) ;
}
2021-11-29 04:57:35 +03:00
static int mscc_miim_probe ( struct platform_device * pdev )
{
2022-04-05 15:09:51 +03:00
struct device_node * np = pdev - > dev . of_node ;
2022-09-05 19:21:26 +03:00
struct regmap * mii_regmap , * phy_regmap ;
2022-04-08 02:44:45 +03:00
struct device * dev = & pdev - > dev ;
2021-11-29 04:57:35 +03:00
struct mscc_miim_dev * miim ;
struct mii_bus * bus ;
int ret ;
2018-05-14 23:04:55 +03:00
2022-09-05 19:21:26 +03:00
mii_regmap = ocelot_regmap_from_resource ( pdev , 0 ,
& mscc_miim_regmap_config ) ;
if ( IS_ERR ( mii_regmap ) )
return dev_err_probe ( dev , PTR_ERR ( mii_regmap ) ,
" Unable to create MIIM regmap \n " ) ;
2018-05-14 23:04:55 +03:00
2021-11-30 12:57:45 +03:00
/* This resource is optional */
2022-09-05 19:21:26 +03:00
phy_regmap = ocelot_regmap_from_resource_optional ( pdev , 1 ,
& mscc_miim_phy_regmap_config ) ;
if ( IS_ERR ( phy_regmap ) )
return dev_err_probe ( dev , PTR_ERR ( phy_regmap ) ,
" Unable to create phy register regmap \n " ) ;
2021-11-29 04:57:35 +03:00
2022-04-08 02:44:45 +03:00
ret = mscc_miim_setup ( dev , & bus , " mscc_miim " , mii_regmap , 0 ) ;
2021-11-29 04:57:35 +03:00
if ( ret < 0 ) {
2022-04-08 02:44:45 +03:00
dev_err ( dev , " Unable to setup the MDIO bus \n " ) ;
2021-11-29 04:57:35 +03:00
return ret ;
}
miim = bus - > priv ;
miim - > phy_regs = phy_regmap ;
2022-03-18 23:13:24 +03:00
2022-04-08 02:44:45 +03:00
miim - > info = device_get_match_data ( dev ) ;
2022-03-18 23:13:24 +03:00
if ( ! miim - > info )
return - EINVAL ;
2021-11-29 04:57:35 +03:00
2022-04-08 02:44:45 +03:00
miim - > clk = devm_clk_get_optional ( dev , NULL ) ;
2022-04-05 15:09:51 +03:00
if ( IS_ERR ( miim - > clk ) )
return PTR_ERR ( miim - > clk ) ;
of_property_read_u32 ( np , " clock-frequency " , & miim - > bus_freq ) ;
if ( miim - > bus_freq & & ! miim - > clk ) {
2022-04-08 02:44:45 +03:00
dev_err ( dev , " cannot use clock-frequency without a clock \n " ) ;
2022-04-05 15:09:51 +03:00
return - EINVAL ;
}
ret = clk_prepare_enable ( miim - > clk ) ;
if ( ret )
return ret ;
ret = mscc_miim_clk_set ( bus ) ;
if ( ret )
goto out_disable_clk ;
ret = of_mdiobus_register ( bus , np ) ;
2018-05-14 23:04:55 +03:00
if ( ret < 0 ) {
2022-04-08 02:44:45 +03:00
dev_err ( dev , " Cannot register MDIO bus (%d) \n " , ret ) ;
2022-04-05 15:09:51 +03:00
goto out_disable_clk ;
2018-05-14 23:04:55 +03:00
}
platform_set_drvdata ( pdev , bus ) ;
return 0 ;
2022-04-05 15:09:51 +03:00
out_disable_clk :
clk_disable_unprepare ( miim - > clk ) ;
return ret ;
2018-05-14 23:04:55 +03:00
}
static int mscc_miim_remove ( struct platform_device * pdev )
{
struct mii_bus * bus = platform_get_drvdata ( pdev ) ;
2022-04-05 15:09:51 +03:00
struct mscc_miim_dev * miim = bus - > priv ;
2018-05-14 23:04:55 +03:00
2022-04-05 15:09:51 +03:00
clk_disable_unprepare ( miim - > clk ) ;
2018-05-14 23:04:55 +03:00
mdiobus_unregister ( bus ) ;
return 0 ;
}
2022-03-18 23:13:24 +03:00
static const struct mscc_miim_info mscc_ocelot_miim_info = {
. phy_reset_offset = MSCC_PHY_REG_PHY_CFG ,
. phy_reset_bits = PHY_CFG_PHY_ENA | PHY_CFG_PHY_COMMON_RESET |
PHY_CFG_PHY_RESET ,
} ;
static const struct mscc_miim_info microchip_lan966x_miim_info = {
. phy_reset_offset = LAN966X_CUPHY_COMMON_CFG ,
. phy_reset_bits = CUPHY_COMMON_CFG_RESET_N ,
} ;
2018-05-14 23:04:55 +03:00
static const struct of_device_id mscc_miim_match [ ] = {
2022-03-18 23:13:24 +03:00
{
. compatible = " mscc,ocelot-miim " ,
. data = & mscc_ocelot_miim_info
} , {
. compatible = " microchip,lan966x-miim " ,
. data = & microchip_lan966x_miim_info
} ,
2018-05-14 23:04:55 +03:00
{ }
} ;
MODULE_DEVICE_TABLE ( of , mscc_miim_match ) ;
static struct platform_driver mscc_miim_driver = {
. probe = mscc_miim_probe ,
. remove = mscc_miim_remove ,
. driver = {
. name = " mscc-miim " ,
. of_match_table = mscc_miim_match ,
} ,
} ;
module_platform_driver ( mscc_miim_driver ) ;
MODULE_DESCRIPTION ( " Microsemi MIIM driver " ) ;
MODULE_AUTHOR ( " Alexandre Belloni <alexandre.belloni@bootlin.com> " ) ;
MODULE_LICENSE ( " Dual MIT/GPL " ) ;