2006-08-15 10:00:29 +04:00
/*
2007-12-07 01:51:22 +03:00
* Fixed MDIO bus ( MDIO bus emulation with fixed PHYs )
2006-08-15 10:00:29 +04:00
*
2007-12-07 01:51:22 +03:00
* Author : Vitaly Bordug < vbordug @ ru . mvista . com >
* Anton Vorontsov < avorontsov @ ru . mvista . com >
2006-08-15 10:00:29 +04:00
*
2007-12-07 01:51:22 +03:00
* Copyright ( c ) 2006 - 2007 MontaVista Software , Inc .
2006-08-15 10:00:29 +04:00
*
* This program is free software ; you can redistribute it and / or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation ; either version 2 of the License , or ( at your
* option ) any later version .
*/
2007-12-07 01:51:22 +03:00
2006-08-15 10:00:29 +04:00
# include <linux/kernel.h>
# include <linux/module.h>
2007-12-07 01:51:22 +03:00
# include <linux/platform_device.h>
# include <linux/list.h>
2006-08-15 10:00:29 +04:00
# include <linux/mii.h>
# include <linux/phy.h>
2007-08-11 01:05:16 +04:00
# include <linux/phy_fixed.h>
2009-04-11 12:52:29 +04:00
# include <linux/err.h>
2006-08-15 10:00:29 +04:00
2007-12-07 01:51:22 +03:00
# define MII_REGS_NUM 29
2006-08-15 10:00:29 +04:00
2007-12-07 01:51:22 +03:00
struct fixed_mdio_bus {
int irqs [ PHY_MAX_ADDR ] ;
2008-10-09 03:29:57 +04:00
struct mii_bus * mii_bus ;
2007-12-07 01:51:22 +03:00
struct list_head phys ;
} ;
2006-08-15 10:00:29 +04:00
2007-12-07 01:51:22 +03:00
struct fixed_phy {
int id ;
u16 regs [ MII_REGS_NUM ] ;
struct phy_device * phydev ;
struct fixed_phy_status status ;
int ( * link_update ) ( struct net_device * , struct fixed_phy_status * ) ;
struct list_head node ;
} ;
2007-08-11 01:05:16 +04:00
2007-12-07 01:51:22 +03:00
static struct platform_device * pdev ;
static struct fixed_mdio_bus platform_fmb = {
. phys = LIST_HEAD_INIT ( platform_fmb . phys ) ,
} ;
2007-08-11 01:05:16 +04:00
2007-12-07 01:51:22 +03:00
static int fixed_phy_update_regs ( struct fixed_phy * fp )
2006-08-15 10:00:29 +04:00
{
2007-12-07 01:51:22 +03:00
u16 bmsr = BMSR_ANEGCAPABLE ;
2006-08-15 10:00:29 +04:00
u16 bmcr = 0 ;
2007-12-07 01:51:22 +03:00
u16 lpagb = 0 ;
u16 lpa = 0 ;
2006-08-15 10:00:29 +04:00
2007-12-07 01:51:22 +03:00
if ( fp - > status . duplex ) {
2006-08-15 10:00:29 +04:00
bmcr | = BMCR_FULLDPLX ;
2007-12-07 01:51:22 +03:00
switch ( fp - > status . speed ) {
case 1000 :
bmsr | = BMSR_ESTATEN ;
bmcr | = BMCR_SPEED1000 ;
lpagb | = LPA_1000FULL ;
break ;
2006-08-15 10:00:29 +04:00
case 100 :
bmsr | = BMSR_100FULL ;
bmcr | = BMCR_SPEED100 ;
2007-12-07 01:51:22 +03:00
lpa | = LPA_100FULL ;
2007-08-11 01:05:16 +04:00
break ;
2006-08-15 10:00:29 +04:00
case 10 :
bmsr | = BMSR_10FULL ;
2007-12-07 01:51:22 +03:00
lpa | = LPA_10FULL ;
2007-08-11 01:05:16 +04:00
break ;
2007-12-07 01:51:22 +03:00
default :
printk ( KERN_WARNING " fixed phy: unknown speed \n " ) ;
return - EINVAL ;
2006-08-15 10:00:29 +04:00
}
} else {
2007-12-07 01:51:22 +03:00
switch ( fp - > status . speed ) {
case 1000 :
bmsr | = BMSR_ESTATEN ;
bmcr | = BMCR_SPEED1000 ;
lpagb | = LPA_1000HALF ;
break ;
2006-08-15 10:00:29 +04:00
case 100 :
bmsr | = BMSR_100HALF ;
bmcr | = BMCR_SPEED100 ;
2007-12-07 01:51:22 +03:00
lpa | = LPA_100HALF ;
2007-08-11 01:05:16 +04:00
break ;
2006-08-15 10:00:29 +04:00
case 10 :
2007-12-07 01:51:22 +03:00
bmsr | = BMSR_10HALF ;
lpa | = LPA_10HALF ;
2007-08-11 01:05:16 +04:00
break ;
2007-12-07 01:51:22 +03:00
default :
printk ( KERN_WARNING " fixed phy: unknown speed \n " ) ;
return - EINVAL ;
2006-08-15 10:00:29 +04:00
}
}
2007-12-07 01:51:22 +03:00
if ( fp - > status . link )
bmsr | = BMSR_LSTATUS | BMSR_ANEGCOMPLETE ;
if ( fp - > status . pause )
lpa | = LPA_PAUSE_CAP ;
if ( fp - > status . asym_pause )
lpa | = LPA_PAUSE_ASYM ;
fp - > regs [ MII_PHYSID1 ] = fp - > id > > 16 ;
fp - > regs [ MII_PHYSID2 ] = fp - > id ;
fp - > regs [ MII_BMSR ] = bmsr ;
fp - > regs [ MII_BMCR ] = bmcr ;
fp - > regs [ MII_LPA ] = lpa ;
fp - > regs [ MII_STAT1000 ] = lpagb ;
2006-08-15 10:00:29 +04:00
return 0 ;
}
2007-12-07 01:51:22 +03:00
static int fixed_mdio_read ( struct mii_bus * bus , int phy_id , int reg_num )
2006-08-15 10:00:29 +04:00
{
2008-10-09 20:45:04 +04:00
struct fixed_mdio_bus * fmb = bus - > priv ;
2007-12-07 01:51:22 +03:00
struct fixed_phy * fp ;
if ( reg_num > = MII_REGS_NUM )
return - 1 ;
list_for_each_entry ( fp , & fmb - > phys , node ) {
if ( fp - > id = = phy_id ) {
/* Issue callback if user registered it. */
if ( fp - > link_update ) {
fp - > link_update ( fp - > phydev - > attached_dev ,
& fp - > status ) ;
fixed_phy_update_regs ( fp ) ;
2006-08-15 10:00:29 +04:00
}
2007-12-07 01:51:22 +03:00
return fp - > regs [ reg_num ] ;
2007-08-11 01:05:16 +04:00
}
2007-12-07 01:51:22 +03:00
}
2006-08-15 10:00:29 +04:00
2007-12-07 01:51:22 +03:00
return 0xFFFF ;
2006-08-15 10:00:29 +04:00
}
2007-12-07 01:51:22 +03:00
static int fixed_mdio_write ( struct mii_bus * bus , int phy_id , int reg_num ,
u16 val )
2006-08-15 10:00:29 +04:00
{
return 0 ;
}
2007-12-07 01:51:22 +03:00
/*
* If something weird is required to be done with link / speed ,
* network driver is able to assign a function to implement this .
* May be useful for PHY ' s that need to be software - driven .
*/
int fixed_phy_set_link_update ( struct phy_device * phydev ,
int ( * link_update ) ( struct net_device * ,
struct fixed_phy_status * ) )
2006-08-15 10:00:29 +04:00
{
2007-12-07 01:51:22 +03:00
struct fixed_mdio_bus * fmb = & platform_fmb ;
struct fixed_phy * fp ;
2006-08-15 10:00:29 +04:00
2007-12-07 01:51:22 +03:00
if ( ! link_update | | ! phydev | | ! phydev - > bus )
return - EINVAL ;
2006-08-15 10:00:29 +04:00
2007-12-07 01:51:22 +03:00
list_for_each_entry ( fp , & fmb - > phys , node ) {
if ( fp - > id = = phydev - > phy_id ) {
fp - > link_update = link_update ;
fp - > phydev = phydev ;
return 0 ;
}
}
2006-08-15 10:00:29 +04:00
2007-12-07 01:51:22 +03:00
return - ENOENT ;
2007-08-11 01:05:16 +04:00
}
2007-12-07 01:51:22 +03:00
EXPORT_SYMBOL_GPL ( fixed_phy_set_link_update ) ;
2007-08-11 01:05:16 +04:00
2007-12-07 01:51:22 +03:00
int fixed_phy_add ( unsigned int irq , int phy_id ,
struct fixed_phy_status * status )
2006-08-15 10:00:29 +04:00
{
2007-12-07 01:51:22 +03:00
int ret ;
struct fixed_mdio_bus * fmb = & platform_fmb ;
struct fixed_phy * fp ;
2006-08-15 10:00:29 +04:00
2007-12-07 01:51:22 +03:00
fp = kzalloc ( sizeof ( * fp ) , GFP_KERNEL ) ;
if ( ! fp )
return - ENOMEM ;
2006-08-15 10:00:29 +04:00
2007-12-07 01:51:22 +03:00
memset ( fp - > regs , 0xFF , sizeof ( fp - > regs [ 0 ] ) * MII_REGS_NUM ) ;
2006-08-15 10:00:29 +04:00
2007-12-07 01:51:22 +03:00
fmb - > irqs [ phy_id ] = irq ;
2006-08-15 10:00:29 +04:00
2007-12-07 01:51:22 +03:00
fp - > id = phy_id ;
fp - > status = * status ;
2007-08-11 01:05:16 +04:00
2007-12-07 01:51:22 +03:00
ret = fixed_phy_update_regs ( fp ) ;
if ( ret )
goto err_regs ;
2006-08-15 10:00:29 +04:00
2007-12-07 01:51:22 +03:00
list_add_tail ( & fp - > node , & fmb - > phys ) ;
2007-08-11 01:05:16 +04:00
2007-12-07 01:51:22 +03:00
return 0 ;
2006-08-15 10:00:29 +04:00
2007-12-07 01:51:22 +03:00
err_regs :
kfree ( fp ) ;
return ret ;
}
EXPORT_SYMBOL_GPL ( fixed_phy_add ) ;
2006-08-15 10:00:29 +04:00
2007-12-07 01:51:22 +03:00
static int __init fixed_mdio_bus_init ( void )
{
struct fixed_mdio_bus * fmb = & platform_fmb ;
int ret ;
2006-08-15 10:00:29 +04:00
2007-12-07 01:51:22 +03:00
pdev = platform_device_register_simple ( " Fixed MDIO bus " , 0 , NULL , 0 ) ;
2009-04-11 12:52:29 +04:00
if ( IS_ERR ( pdev ) ) {
ret = PTR_ERR ( pdev ) ;
2007-12-07 01:51:22 +03:00
goto err_pdev ;
}
2006-08-15 10:00:29 +04:00
2008-10-09 03:29:57 +04:00
fmb - > mii_bus = mdiobus_alloc ( ) ;
if ( fmb - > mii_bus = = NULL ) {
ret = - ENOMEM ;
goto err_mdiobus_reg ;
}
2006-08-15 10:00:29 +04:00
2008-10-09 03:29:57 +04:00
snprintf ( fmb - > mii_bus - > id , MII_BUS_ID_SIZE , " 0 " ) ;
fmb - > mii_bus - > name = " Fixed MDIO Bus " ;
2008-10-09 20:45:04 +04:00
fmb - > mii_bus - > priv = fmb ;
2008-10-09 03:29:57 +04:00
fmb - > mii_bus - > parent = & pdev - > dev ;
fmb - > mii_bus - > read = & fixed_mdio_read ;
fmb - > mii_bus - > write = & fixed_mdio_write ;
fmb - > mii_bus - > irq = fmb - > irqs ;
ret = mdiobus_register ( fmb - > mii_bus ) ;
2007-12-07 01:51:22 +03:00
if ( ret )
2008-10-09 03:29:57 +04:00
goto err_mdiobus_alloc ;
2006-08-15 10:00:29 +04:00
2007-12-07 01:51:22 +03:00
return 0 ;
2006-08-15 10:00:29 +04:00
2008-10-09 03:29:57 +04:00
err_mdiobus_alloc :
mdiobus_free ( fmb - > mii_bus ) ;
2007-12-07 01:51:22 +03:00
err_mdiobus_reg :
platform_device_unregister ( pdev ) ;
err_pdev :
return ret ;
}
module_init ( fixed_mdio_bus_init ) ;
2006-08-15 10:00:29 +04:00
2007-12-07 01:51:22 +03:00
static void __exit fixed_mdio_bus_exit ( void )
{
struct fixed_mdio_bus * fmb = & platform_fmb ;
2008-02-03 00:15:02 +03:00
struct fixed_phy * fp , * tmp ;
2006-08-15 10:00:29 +04:00
2008-10-09 03:29:57 +04:00
mdiobus_unregister ( fmb - > mii_bus ) ;
mdiobus_free ( fmb - > mii_bus ) ;
2007-12-07 01:51:22 +03:00
platform_device_unregister ( pdev ) ;
2006-08-15 10:00:29 +04:00
2008-02-03 00:15:02 +03:00
list_for_each_entry_safe ( fp , tmp , & fmb - > phys , node ) {
2007-12-07 01:51:22 +03:00
list_del ( & fp - > node ) ;
kfree ( fp ) ;
2007-08-11 01:05:16 +04:00
}
2006-08-15 10:00:29 +04:00
}
2007-12-07 01:51:22 +03:00
module_exit ( fixed_mdio_bus_exit ) ;
2006-08-15 10:00:29 +04:00
2007-12-07 01:51:22 +03:00
MODULE_DESCRIPTION ( " Fixed MDIO bus (MDIO bus emulation with fixed PHYs) " ) ;
2006-08-15 10:00:29 +04:00
MODULE_AUTHOR ( " Vitaly Bordug " ) ;
MODULE_LICENSE ( " GPL " ) ;