2012-05-02 19:16:38 +04:00
/*
* This file is subject to the terms and conditions of the GNU General Public
* License . See the file " COPYING " in the main directory of this archive
* for more details .
*
* Copyright ( C ) 2011 , 2012 Cavium , Inc .
*/
# include <linux/platform_device.h>
# include <linux/mdio-mux.h>
# include <linux/of_mdio.h>
# include <linux/device.h>
# include <linux/module.h>
# include <linux/phy.h>
# define DRV_DESCRIPTION "MDIO bus multiplexer driver"
struct mdio_mux_child_bus ;
struct mdio_mux_parent_bus {
struct mii_bus * mii_bus ;
int current_child ;
int parent_id ;
void * switch_data ;
int ( * switch_fn ) ( int current_child , int desired_child , void * data ) ;
/* List of our children linked through their next fields. */
struct mdio_mux_child_bus * children ;
} ;
struct mdio_mux_child_bus {
struct mii_bus * mii_bus ;
struct mdio_mux_parent_bus * parent ;
struct mdio_mux_child_bus * next ;
int bus_number ;
} ;
/*
* The parent bus ' lock is used to order access to the switch_fn .
*/
static int mdio_mux_read ( struct mii_bus * bus , int phy_id , int regnum )
{
struct mdio_mux_child_bus * cb = bus - > priv ;
struct mdio_mux_parent_bus * pb = cb - > parent ;
int r ;
2016-04-11 22:40:05 +03:00
mutex_lock_nested ( & pb - > mii_bus - > mdio_lock , MDIO_MUTEX_MUX ) ;
2012-05-02 19:16:38 +04:00
r = pb - > switch_fn ( pb - > current_child , cb - > bus_number , pb - > switch_data ) ;
if ( r )
goto out ;
pb - > current_child = cb - > bus_number ;
r = pb - > mii_bus - > read ( pb - > mii_bus , phy_id , regnum ) ;
out :
mutex_unlock ( & pb - > mii_bus - > mdio_lock ) ;
return r ;
}
/*
* The parent bus ' lock is used to order access to the switch_fn .
*/
static int mdio_mux_write ( struct mii_bus * bus , int phy_id ,
int regnum , u16 val )
{
struct mdio_mux_child_bus * cb = bus - > priv ;
struct mdio_mux_parent_bus * pb = cb - > parent ;
int r ;
2016-04-11 22:40:05 +03:00
mutex_lock_nested ( & pb - > mii_bus - > mdio_lock , MDIO_MUTEX_MUX ) ;
2012-05-02 19:16:38 +04:00
r = pb - > switch_fn ( pb - > current_child , cb - > bus_number , pb - > switch_data ) ;
if ( r )
goto out ;
pb - > current_child = cb - > bus_number ;
r = pb - > mii_bus - > write ( pb - > mii_bus , phy_id , regnum , val ) ;
out :
mutex_unlock ( & pb - > mii_bus - > mdio_lock ) ;
return r ;
}
static int parent_count ;
int mdio_mux_init ( struct device * dev ,
2017-09-04 19:30:14 +03:00
struct device_node * mux_node ,
2012-05-02 19:16:38 +04:00
int ( * switch_fn ) ( int cur , int desired , void * data ) ,
void * * mux_handle ,
2016-06-10 08:33:45 +03:00
void * data ,
struct mii_bus * mux_bus )
2012-05-02 19:16:38 +04:00
{
struct device_node * parent_bus_node ;
struct device_node * child_bus_node ;
int r , ret_val ;
struct mii_bus * parent_bus ;
struct mdio_mux_parent_bus * pb ;
struct mdio_mux_child_bus * cb ;
2017-09-04 19:30:14 +03:00
if ( ! mux_node )
2012-05-02 19:16:38 +04:00
return - ENODEV ;
2016-06-10 08:33:45 +03:00
if ( ! mux_bus ) {
2017-09-04 19:30:14 +03:00
parent_bus_node = of_parse_phandle ( mux_node ,
2016-06-10 08:33:45 +03:00
" mdio-parent-bus " , 0 ) ;
2012-05-02 19:16:38 +04:00
2016-06-10 08:33:45 +03:00
if ( ! parent_bus_node )
return - ENODEV ;
parent_bus = of_mdio_find_bus ( parent_bus_node ) ;
if ( ! parent_bus ) {
ret_val = - EPROBE_DEFER ;
goto err_parent_bus ;
}
} else {
2016-06-14 13:03:17 +03:00
parent_bus_node = NULL ;
2016-06-10 08:33:45 +03:00
parent_bus = mux_bus ;
2017-09-01 14:56:04 +03:00
get_device ( & parent_bus - > dev ) ;
2016-06-10 08:33:45 +03:00
}
2012-05-02 19:16:38 +04:00
pb = devm_kzalloc ( dev , sizeof ( * pb ) , GFP_KERNEL ) ;
2017-09-01 14:56:00 +03:00
if ( ! pb ) {
2012-05-02 19:16:38 +04:00
ret_val = - ENOMEM ;
2017-05-10 18:20:27 +03:00
goto err_pb_kz ;
2012-05-02 19:16:38 +04:00
}
pb - > switch_data = data ;
pb - > switch_fn = switch_fn ;
pb - > current_child = - 1 ;
pb - > parent_id = parent_count + + ;
pb - > mii_bus = parent_bus ;
ret_val = - ENODEV ;
2017-09-04 19:30:14 +03:00
for_each_available_child_of_node ( mux_node , child_bus_node ) {
2017-06-05 21:08:04 +03:00
int v ;
2012-05-02 19:16:38 +04:00
2017-07-10 15:35:23 +03:00
r = of_property_read_u32 ( child_bus_node , " reg " , & v ) ;
if ( r ) {
2017-05-31 22:44:50 +03:00
dev_err ( dev ,
2017-07-19 00:43:19 +03:00
" Error: Failed to find reg for child %pOF \n " ,
child_bus_node ) ;
2012-05-02 19:16:38 +04:00
continue ;
2017-05-31 22:44:50 +03:00
}
2012-05-02 19:16:38 +04:00
cb = devm_kzalloc ( dev , sizeof ( * cb ) , GFP_KERNEL ) ;
2017-09-01 14:56:00 +03:00
if ( ! cb ) {
2012-05-02 19:16:38 +04:00
ret_val = - ENOMEM ;
2017-05-31 22:44:50 +03:00
continue ;
2012-05-02 19:16:38 +04:00
}
cb - > bus_number = v ;
cb - > parent = pb ;
2015-12-14 15:51:51 +03:00
2012-05-02 19:16:38 +04:00
cb - > mii_bus = mdiobus_alloc ( ) ;
2015-12-14 15:51:51 +03:00
if ( ! cb - > mii_bus ) {
ret_val = - ENOMEM ;
2017-05-10 18:20:27 +03:00
devm_kfree ( dev , cb ) ;
2017-05-31 22:44:50 +03:00
continue ;
2015-12-14 15:51:51 +03:00
}
2012-05-02 19:16:38 +04:00
cb - > mii_bus - > priv = cb ;
2016-01-06 22:11:15 +03:00
2012-05-02 19:16:38 +04:00
cb - > mii_bus - > name = " mdio_mux " ;
snprintf ( cb - > mii_bus - > id , MII_BUS_ID_SIZE , " %x.%x " ,
pb - > parent_id , v ) ;
cb - > mii_bus - > parent = dev ;
cb - > mii_bus - > read = mdio_mux_read ;
cb - > mii_bus - > write = mdio_mux_write ;
r = of_mdiobus_register ( cb - > mii_bus , child_bus_node ) ;
if ( r ) {
2017-05-31 22:44:50 +03:00
dev_err ( dev ,
2017-07-19 00:43:19 +03:00
" Error: Failed to register MDIO bus for child %pOF \n " ,
child_bus_node ) ;
2012-05-02 19:16:38 +04:00
mdiobus_free ( cb - > mii_bus ) ;
devm_kfree ( dev , cb ) ;
} else {
cb - > next = pb - > children ;
pb - > children = cb ;
}
}
if ( pb - > children ) {
* mux_handle = pb ;
return 0 ;
}
2015-09-24 22:35:52 +03:00
2017-05-31 22:44:50 +03:00
dev_err ( dev , " Error: No acceptable child buses found \n " ) ;
2017-05-10 18:20:27 +03:00
devm_kfree ( dev , pb ) ;
err_pb_kz :
2017-09-01 14:56:04 +03:00
put_device ( & parent_bus - > dev ) ;
2012-05-02 19:16:38 +04:00
err_parent_bus :
2016-06-14 13:03:17 +03:00
of_node_put ( parent_bus_node ) ;
2012-05-02 19:16:38 +04:00
return ret_val ;
}
EXPORT_SYMBOL_GPL ( mdio_mux_init ) ;
void mdio_mux_uninit ( void * mux_handle )
{
struct mdio_mux_parent_bus * pb = mux_handle ;
struct mdio_mux_child_bus * cb = pb - > children ;
while ( cb ) {
mdiobus_unregister ( cb - > mii_bus ) ;
mdiobus_free ( cb - > mii_bus ) ;
cb = cb - > next ;
}
2015-09-24 22:35:52 +03:00
put_device ( & pb - > mii_bus - > dev ) ;
2012-05-02 19:16:38 +04:00
}
EXPORT_SYMBOL_GPL ( mdio_mux_uninit ) ;
MODULE_DESCRIPTION ( DRV_DESCRIPTION ) ;
MODULE_AUTHOR ( " David Daney " ) ;
MODULE_LICENSE ( " GPL " ) ;