2019-01-21 21:10:19 +03:00
// SPDX-License-Identifier: GPL-2.0
2013-05-30 07:49:21 +04:00
/*
* Allwinner EMAC MDIO interface driver
*
* Copyright 2012 - 2013 Stefan Roese < sr @ denx . de >
* Copyright 2013 Maxime Ripard < maxime . ripard @ free - electrons . com >
*
* Based on the Linux driver provided by Allwinner :
* Copyright ( C ) 1997 Sten Wang
*/
# include <linux/delay.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/mutex.h>
# include <linux/of_address.h>
# include <linux/of_mdio.h>
# include <linux/phy.h>
# include <linux/platform_device.h>
# include <linux/regulator/consumer.h>
# define EMAC_MAC_MCMD_REG (0x00)
# define EMAC_MAC_MADR_REG (0x04)
# define EMAC_MAC_MWTD_REG (0x08)
# define EMAC_MAC_MRDD_REG (0x0c)
# define EMAC_MAC_MIND_REG (0x10)
# define EMAC_MAC_SSRR_REG (0x14)
# define MDIO_TIMEOUT (msecs_to_jiffies(100))
struct sun4i_mdio_data {
void __iomem * membase ;
struct regulator * regulator ;
} ;
static int sun4i_mdio_read ( struct mii_bus * bus , int mii_id , int regnum )
{
struct sun4i_mdio_data * data = bus - > priv ;
2013-07-23 05:09:18 +04:00
unsigned long timeout_jiffies ;
2013-05-30 07:49:21 +04:00
int value ;
/* issue the phy address and reg */
writel ( ( mii_id < < 8 ) | regnum , data - > membase + EMAC_MAC_MADR_REG ) ;
/* pull up the phy io line */
writel ( 0x1 , data - > membase + EMAC_MAC_MCMD_REG ) ;
/* Wait read complete */
2013-07-23 05:09:18 +04:00
timeout_jiffies = jiffies + MDIO_TIMEOUT ;
2013-05-30 07:49:21 +04:00
while ( readl ( data - > membase + EMAC_MAC_MIND_REG ) & 0x1 ) {
2013-07-23 05:09:18 +04:00
if ( time_is_before_jiffies ( timeout_jiffies ) )
2013-05-30 07:49:21 +04:00
return - ETIMEDOUT ;
msleep ( 1 ) ;
}
/* push down the phy io line */
writel ( 0x0 , data - > membase + EMAC_MAC_MCMD_REG ) ;
/* and read data */
value = readl ( data - > membase + EMAC_MAC_MRDD_REG ) ;
return value ;
}
static int sun4i_mdio_write ( struct mii_bus * bus , int mii_id , int regnum ,
u16 value )
{
struct sun4i_mdio_data * data = bus - > priv ;
2013-07-23 05:09:18 +04:00
unsigned long timeout_jiffies ;
2013-05-30 07:49:21 +04:00
/* issue the phy address and reg */
writel ( ( mii_id < < 8 ) | regnum , data - > membase + EMAC_MAC_MADR_REG ) ;
/* pull up the phy io line */
writel ( 0x1 , data - > membase + EMAC_MAC_MCMD_REG ) ;
/* Wait read complete */
2013-07-23 05:09:18 +04:00
timeout_jiffies = jiffies + MDIO_TIMEOUT ;
2013-05-30 07:49:21 +04:00
while ( readl ( data - > membase + EMAC_MAC_MIND_REG ) & 0x1 ) {
2013-07-23 05:09:18 +04:00
if ( time_is_before_jiffies ( timeout_jiffies ) )
2013-05-30 07:49:21 +04:00
return - ETIMEDOUT ;
msleep ( 1 ) ;
}
/* push down the phy io line */
writel ( 0x0 , data - > membase + EMAC_MAC_MCMD_REG ) ;
/* and write data */
writel ( value , data - > membase + EMAC_MAC_MWTD_REG ) ;
return 0 ;
}
static int sun4i_mdio_probe ( struct platform_device * pdev )
{
struct device_node * np = pdev - > dev . of_node ;
struct mii_bus * bus ;
struct sun4i_mdio_data * data ;
2013-08-26 17:11:57 +04:00
struct resource * res ;
2016-01-06 22:11:15 +03:00
int ret ;
2013-05-30 07:49:21 +04:00
bus = mdiobus_alloc_size ( sizeof ( * data ) ) ;
if ( ! bus )
return - ENOMEM ;
bus - > name = " sun4i_mii_bus " ;
bus - > read = & sun4i_mdio_read ;
bus - > write = & sun4i_mdio_write ;
snprintf ( bus - > id , MII_BUS_ID_SIZE , " %s-mii " , dev_name ( & pdev - > dev ) ) ;
bus - > parent = & pdev - > dev ;
data = bus - > priv ;
2013-08-26 17:11:57 +04:00
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
data - > membase = devm_ioremap_resource ( & pdev - > dev , res ) ;
if ( IS_ERR ( data - > membase ) ) {
ret = PTR_ERR ( data - > membase ) ;
goto err_out_free_mdiobus ;
2013-05-30 07:49:21 +04:00
}
data - > regulator = devm_regulator_get ( & pdev - > dev , " phy " ) ;
if ( IS_ERR ( data - > regulator ) ) {
2018-01-06 11:00:09 +03:00
if ( PTR_ERR ( data - > regulator ) = = - EPROBE_DEFER ) {
ret = - EPROBE_DEFER ;
goto err_out_free_mdiobus ;
}
2013-05-30 07:49:21 +04:00
dev_info ( & pdev - > dev , " no regulator found \n " ) ;
2016-03-21 12:02:31 +03:00
data - > regulator = NULL ;
2013-05-30 07:49:21 +04:00
} else {
ret = regulator_enable ( data - > regulator ) ;
if ( ret )
2013-08-26 17:11:57 +04:00
goto err_out_free_mdiobus ;
2013-05-30 07:49:21 +04:00
}
ret = of_mdiobus_register ( bus , np ) ;
if ( ret < 0 )
goto err_out_disable_regulator ;
platform_set_drvdata ( pdev , bus ) ;
return 0 ;
err_out_disable_regulator :
2016-03-21 12:02:31 +03:00
if ( data - > regulator )
regulator_disable ( data - > regulator ) ;
2013-05-30 07:49:21 +04:00
err_out_free_mdiobus :
mdiobus_free ( bus ) ;
return ret ;
}
static int sun4i_mdio_remove ( struct platform_device * pdev )
{
struct mii_bus * bus = platform_get_drvdata ( pdev ) ;
mdiobus_unregister ( bus ) ;
mdiobus_free ( bus ) ;
return 0 ;
}
static const struct of_device_id sun4i_mdio_dt_ids [ ] = {
2014-02-02 17:49:12 +04:00
{ . compatible = " allwinner,sun4i-a10-mdio " } ,
/* Deprecated */
2013-05-30 07:49:21 +04:00
{ . compatible = " allwinner,sun4i-mdio " } ,
{ }
} ;
MODULE_DEVICE_TABLE ( of , sun4i_mdio_dt_ids ) ;
static struct platform_driver sun4i_mdio_driver = {
. probe = sun4i_mdio_probe ,
. remove = sun4i_mdio_remove ,
. driver = {
. name = " sun4i-mdio " ,
. of_match_table = sun4i_mdio_dt_ids ,
} ,
} ;
module_platform_driver ( sun4i_mdio_driver ) ;
MODULE_DESCRIPTION ( " Allwinner EMAC MDIO interface driver " ) ;
MODULE_AUTHOR ( " Maxime Ripard <maxime.ripard@free-electrons.com> " ) ;
2019-01-21 21:10:19 +03:00
MODULE_LICENSE ( " GPL v2 " ) ;