2012-11-12 17:03:47 +01:00
/*
* Driver for the MDIO interface of Marvell network interfaces .
*
* Since the MDIO interface of Marvell network interfaces is shared
* between all network interfaces , having a single driver allows to
* handle concurrent accesses properly ( you may have four Ethernet
* ports , but they in fact share the same SMI interface to access the
* MDIO bus ) . Moreover , this MDIO interface code is similar between
* the mv643xx_eth driver and the mvneta driver . For now , it is only
* used by the mvneta driver , but it could later be used by the
* mv643xx_eth driver as well .
*
* Copyright ( C ) 2012 Marvell
*
* Thomas Petazzoni < thomas . petazzoni @ free - electrons . com >
*
* This file is licensed under the terms of the GNU General Public
* License version 2. This program is licensed " as is " without any
* warranty of any kind , whether express or implied .
*/
# include <linux/init.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/mutex.h>
# include <linux/phy.h>
# include <linux/platform_device.h>
2012-11-19 11:39:42 +01:00
# include <linux/delay.h>
2013-03-22 03:39:25 +00:00
# include <linux/io.h>
# include <linux/of_mdio.h>
2012-11-12 17:03:47 +01:00
# define MVMDIO_SMI_DATA_SHIFT 0
# define MVMDIO_SMI_PHY_ADDR_SHIFT 16
# define MVMDIO_SMI_PHY_REG_SHIFT 21
# define MVMDIO_SMI_READ_OPERATION BIT(26)
# define MVMDIO_SMI_WRITE_OPERATION 0
# define MVMDIO_SMI_READ_VALID BIT(27)
# define MVMDIO_SMI_BUSY BIT(28)
struct orion_mdio_dev {
struct mutex lock ;
void __iomem * smireg ;
} ;
2012-11-19 11:40:15 +01:00
/* Wait for the SMI unit to be ready for another operation
2012-11-12 17:03:47 +01:00
*/
static int orion_mdio_wait_ready ( struct mii_bus * bus )
{
struct orion_mdio_dev * dev = bus - > priv ;
int count ;
u32 val ;
count = 0 ;
while ( 1 ) {
val = readl ( dev - > smireg ) ;
if ( ! ( val & MVMDIO_SMI_BUSY ) )
break ;
if ( count > 100 ) {
dev_err ( bus - > parent , " Timeout: SMI busy for too long \n " ) ;
return - ETIMEDOUT ;
}
udelay ( 10 ) ;
count + + ;
}
return 0 ;
}
static int orion_mdio_read ( struct mii_bus * bus , int mii_id ,
int regnum )
{
struct orion_mdio_dev * dev = bus - > priv ;
int count ;
u32 val ;
int ret ;
mutex_lock ( & dev - > lock ) ;
ret = orion_mdio_wait_ready ( bus ) ;
if ( ret < 0 ) {
mutex_unlock ( & dev - > lock ) ;
return ret ;
}
writel ( ( ( mii_id < < MVMDIO_SMI_PHY_ADDR_SHIFT ) |
( regnum < < MVMDIO_SMI_PHY_REG_SHIFT ) |
MVMDIO_SMI_READ_OPERATION ) ,
dev - > smireg ) ;
/* Wait for the value to become available */
count = 0 ;
while ( 1 ) {
val = readl ( dev - > smireg ) ;
if ( val & MVMDIO_SMI_READ_VALID )
break ;
if ( count > 100 ) {
dev_err ( bus - > parent , " Timeout when reading PHY \n " ) ;
mutex_unlock ( & dev - > lock ) ;
return - ETIMEDOUT ;
}
udelay ( 10 ) ;
count + + ;
}
mutex_unlock ( & dev - > lock ) ;
return val & 0xFFFF ;
}
static int orion_mdio_write ( struct mii_bus * bus , int mii_id ,
int regnum , u16 value )
{
struct orion_mdio_dev * dev = bus - > priv ;
int ret ;
mutex_lock ( & dev - > lock ) ;
ret = orion_mdio_wait_ready ( bus ) ;
if ( ret < 0 ) {
mutex_unlock ( & dev - > lock ) ;
return ret ;
}
writel ( ( ( mii_id < < MVMDIO_SMI_PHY_ADDR_SHIFT ) |
( regnum < < MVMDIO_SMI_PHY_REG_SHIFT ) |
MVMDIO_SMI_WRITE_OPERATION |
( value < < MVMDIO_SMI_DATA_SHIFT ) ) ,
dev - > smireg ) ;
mutex_unlock ( & dev - > lock ) ;
return 0 ;
}
static int orion_mdio_reset ( struct mii_bus * bus )
{
return 0 ;
}
2012-12-21 13:42:15 +00:00
static int orion_mdio_probe ( struct platform_device * pdev )
2012-11-12 17:03:47 +01:00
{
2013-03-22 03:39:25 +00:00
struct resource * r ;
2012-11-12 17:03:47 +01:00
struct mii_bus * bus ;
struct orion_mdio_dev * dev ;
int i , ret ;
2013-03-22 03:39:25 +00:00
r = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
if ( ! r ) {
dev_err ( & pdev - > dev , " No SMI register address given \n " ) ;
return - ENODEV ;
}
2012-11-12 17:03:47 +01:00
bus = mdiobus_alloc_size ( sizeof ( struct orion_mdio_dev ) ) ;
if ( ! bus ) {
dev_err ( & pdev - > dev , " Cannot allocate MDIO bus \n " ) ;
return - ENOMEM ;
}
bus - > name = " orion_mdio_bus " ;
bus - > read = orion_mdio_read ;
bus - > write = orion_mdio_write ;
bus - > reset = orion_mdio_reset ;
snprintf ( bus - > id , MII_BUS_ID_SIZE , " %s-mii " ,
dev_name ( & pdev - > dev ) ) ;
bus - > parent = & pdev - > dev ;
bus - > irq = kmalloc ( sizeof ( int ) * PHY_MAX_ADDR , GFP_KERNEL ) ;
if ( ! bus - > irq ) {
mdiobus_free ( bus ) ;
return - ENOMEM ;
}
for ( i = 0 ; i < PHY_MAX_ADDR ; i + + )
bus - > irq [ i ] = PHY_POLL ;
dev = bus - > priv ;
2013-03-22 03:39:25 +00:00
dev - > smireg = devm_ioremap ( & pdev - > dev , r - > start , resource_size ( r ) ) ;
2012-11-12 17:03:47 +01:00
if ( ! dev - > smireg ) {
2013-03-22 03:39:25 +00:00
dev_err ( & pdev - > dev , " Unable to remap SMI register \n " ) ;
2012-11-12 17:03:47 +01:00
kfree ( bus - > irq ) ;
mdiobus_free ( bus ) ;
return - ENODEV ;
}
mutex_init ( & dev - > lock ) ;
2013-03-22 03:39:25 +00:00
if ( pdev - > dev . of_node )
ret = of_mdiobus_register ( bus , pdev - > dev . of_node ) ;
else
ret = mdiobus_register ( bus ) ;
2012-11-12 17:03:47 +01:00
if ( ret < 0 ) {
dev_err ( & pdev - > dev , " Cannot register MDIO bus (%d) \n " , ret ) ;
kfree ( bus - > irq ) ;
mdiobus_free ( bus ) ;
return ret ;
}
platform_set_drvdata ( pdev , bus ) ;
return 0 ;
}
2012-12-21 13:42:15 +00:00
static int orion_mdio_remove ( struct platform_device * pdev )
2012-11-12 17:03:47 +01:00
{
struct mii_bus * bus = platform_get_drvdata ( pdev ) ;
mdiobus_unregister ( bus ) ;
kfree ( bus - > irq ) ;
mdiobus_free ( bus ) ;
return 0 ;
}
static const struct of_device_id orion_mdio_match [ ] = {
{ . compatible = " marvell,orion-mdio " } ,
{ }
} ;
MODULE_DEVICE_TABLE ( of , orion_mdio_match ) ;
static struct platform_driver orion_mdio_driver = {
. probe = orion_mdio_probe ,
2012-12-21 13:42:15 +00:00
. remove = orion_mdio_remove ,
2012-11-12 17:03:47 +01:00
. driver = {
. name = " orion-mdio " ,
. of_match_table = orion_mdio_match ,
} ,
} ;
module_platform_driver ( orion_mdio_driver ) ;
MODULE_DESCRIPTION ( " Marvell MDIO interface driver " ) ;
MODULE_AUTHOR ( " Thomas Petazzoni <thomas.petazzoni@free-electrons.com> " ) ;
MODULE_LICENSE ( " GPL " ) ;