2016-01-13 15:29:27 +01:00
/*
* Pinctrl based I2C DeMultiplexer
*
* Copyright ( C ) 2015 - 16 by Wolfram Sang , Sang Engineering < wsa @ sang - engineering . com >
* Copyright ( C ) 2015 - 16 by Renesas Electronics Corporation
*
* 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 ; version 2 of the License .
*
* See the bindings doc for DTS setup and the sysfs doc for usage information .
* ( look for filenames containing ' i2c - demux - pinctrl ' in Documentation / )
*/
# include <linux/i2c.h>
# include <linux/init.h>
# include <linux/module.h>
# include <linux/of.h>
# include <linux/pinctrl/consumer.h>
# include <linux/platform_device.h>
2018-04-30 14:08:42 +02:00
# include <linux/pm_runtime.h>
2016-01-13 15:29:27 +01:00
# include <linux/slab.h>
# include <linux/sysfs.h>
struct i2c_demux_pinctrl_chan {
struct device_node * parent_np ;
struct i2c_adapter * parent_adap ;
struct of_changeset chgset ;
} ;
struct i2c_demux_pinctrl_priv {
int cur_chan ;
int num_chan ;
struct device * dev ;
const char * bus_name ;
struct i2c_adapter cur_adap ;
struct i2c_algorithm algo ;
struct i2c_demux_pinctrl_chan chan [ ] ;
} ;
static int i2c_demux_master_xfer ( struct i2c_adapter * adap , struct i2c_msg msgs [ ] , int num )
{
struct i2c_demux_pinctrl_priv * priv = adap - > algo_data ;
struct i2c_adapter * parent = priv - > chan [ priv - > cur_chan ] . parent_adap ;
return __i2c_transfer ( parent , msgs , num ) ;
}
static u32 i2c_demux_functionality ( struct i2c_adapter * adap )
{
struct i2c_demux_pinctrl_priv * priv = adap - > algo_data ;
struct i2c_adapter * parent = priv - > chan [ priv - > cur_chan ] . parent_adap ;
return parent - > algo - > functionality ( parent ) ;
}
static int i2c_demux_activate_master ( struct i2c_demux_pinctrl_priv * priv , u32 new_chan )
{
struct i2c_adapter * adap ;
struct pinctrl * p ;
int ret ;
ret = of_changeset_apply ( & priv - > chan [ new_chan ] . chgset ) ;
if ( ret )
goto err ;
adap = of_find_i2c_adapter_by_node ( priv - > chan [ new_chan ] . parent_np ) ;
if ( ! adap ) {
ret = - ENODEV ;
2016-08-12 18:40:23 +02:00
goto err_with_revert ;
2016-01-13 15:29:27 +01:00
}
2016-11-06 21:20:32 +01:00
/*
* Check if there are pinctrl states at all . Note : we cant ' use
* devm_pinctrl_get_select ( ) because we need to distinguish between
* the - ENODEV from devm_pinctrl_get ( ) and pinctrl_lookup_state ( ) .
*/
p = devm_pinctrl_get ( adap - > dev . parent ) ;
2016-01-13 15:29:27 +01:00
if ( IS_ERR ( p ) ) {
ret = PTR_ERR ( p ) ;
2016-11-06 21:20:32 +01:00
/* continue if just no pinctrl states (e.g. i2c-gpio), otherwise exit */
if ( ret ! = - ENODEV )
goto err_with_put ;
} else {
/* there are states. check and use them */
struct pinctrl_state * s = pinctrl_lookup_state ( p , priv - > bus_name ) ;
if ( IS_ERR ( s ) ) {
ret = PTR_ERR ( s ) ;
goto err_with_put ;
}
ret = pinctrl_select_state ( p , s ) ;
if ( ret < 0 )
goto err_with_put ;
2016-01-13 15:29:27 +01:00
}
priv - > chan [ new_chan ] . parent_adap = adap ;
priv - > cur_chan = new_chan ;
/* Now fill out current adapter structure. cur_chan must be up to date */
priv - > algo . master_xfer = i2c_demux_master_xfer ;
priv - > algo . functionality = i2c_demux_functionality ;
snprintf ( priv - > cur_adap . name , sizeof ( priv - > cur_adap . name ) ,
" i2c-demux (master i2c-%d) " , i2c_adapter_id ( adap ) ) ;
priv - > cur_adap . owner = THIS_MODULE ;
priv - > cur_adap . algo = & priv - > algo ;
priv - > cur_adap . algo_data = priv ;
2018-05-21 09:29:38 +02:00
priv - > cur_adap . dev . parent = & adap - > dev ;
2016-01-13 15:29:27 +01:00
priv - > cur_adap . class = adap - > class ;
priv - > cur_adap . retries = adap - > retries ;
priv - > cur_adap . timeout = adap - > timeout ;
priv - > cur_adap . quirks = adap - > quirks ;
priv - > cur_adap . dev . of_node = priv - > dev - > of_node ;
ret = i2c_add_adapter ( & priv - > cur_adap ) ;
if ( ret < 0 )
goto err_with_put ;
return 0 ;
err_with_put :
i2c_put_adapter ( adap ) ;
2016-08-12 18:40:23 +02:00
err_with_revert :
of_changeset_revert ( & priv - > chan [ new_chan ] . chgset ) ;
2016-01-13 15:29:27 +01:00
err :
dev_err ( priv - > dev , " failed to setup demux-adapter %d (%d) \n " , new_chan , ret ) ;
2016-08-22 16:52:21 +02:00
priv - > cur_chan = - EINVAL ;
2016-01-13 15:29:27 +01:00
return ret ;
}
static int i2c_demux_deactivate_master ( struct i2c_demux_pinctrl_priv * priv )
{
int ret , cur = priv - > cur_chan ;
if ( cur < 0 )
return 0 ;
i2c_del_adapter ( & priv - > cur_adap ) ;
i2c_put_adapter ( priv - > chan [ cur ] . parent_adap ) ;
ret = of_changeset_revert ( & priv - > chan [ cur ] . chgset ) ;
priv - > chan [ cur ] . parent_adap = NULL ;
priv - > cur_chan = - EINVAL ;
return ret ;
}
static int i2c_demux_change_master ( struct i2c_demux_pinctrl_priv * priv , u32 new_chan )
{
int ret ;
if ( new_chan = = priv - > cur_chan )
return 0 ;
ret = i2c_demux_deactivate_master ( priv ) ;
if ( ret )
return ret ;
return i2c_demux_activate_master ( priv , new_chan ) ;
}
2016-03-31 16:40:05 +01:00
static ssize_t available_masters_show ( struct device * dev ,
struct device_attribute * attr ,
char * buf )
2016-01-13 15:29:27 +01:00
{
struct i2c_demux_pinctrl_priv * priv = dev_get_drvdata ( dev ) ;
int count = 0 , i ;
for ( i = 0 ; i < priv - > num_chan & & count < PAGE_SIZE ; i + + )
2017-07-18 16:43:06 -05:00
count + = scnprintf ( buf + count , PAGE_SIZE - count , " %d:%pOF%c " ,
i , priv - > chan [ i ] . parent_np ,
2016-03-31 16:40:05 +01:00
i = = priv - > num_chan - 1 ? ' \n ' : ' ' ) ;
2016-01-13 15:29:27 +01:00
return count ;
}
2016-03-31 16:40:05 +01:00
static DEVICE_ATTR_RO ( available_masters ) ;
2016-01-13 15:29:27 +01:00
2016-03-31 16:40:05 +01:00
static ssize_t current_master_show ( struct device * dev ,
struct device_attribute * attr ,
char * buf )
{
struct i2c_demux_pinctrl_priv * priv = dev_get_drvdata ( dev ) ;
return sprintf ( buf , " %d \n " , priv - > cur_chan ) ;
}
static ssize_t current_master_store ( struct device * dev ,
struct device_attribute * attr ,
const char * buf , size_t count )
2016-01-13 15:29:27 +01:00
{
struct i2c_demux_pinctrl_priv * priv = dev_get_drvdata ( dev ) ;
unsigned int val ;
int ret ;
ret = kstrtouint ( buf , 0 , & val ) ;
if ( ret < 0 )
return ret ;
if ( val > = priv - > num_chan )
return - EINVAL ;
ret = i2c_demux_change_master ( priv , val ) ;
return ret < 0 ? ret : count ;
}
2016-03-31 16:40:05 +01:00
static DEVICE_ATTR_RW ( current_master ) ;
2016-01-13 15:29:27 +01:00
static int i2c_demux_pinctrl_probe ( struct platform_device * pdev )
{
struct device_node * np = pdev - > dev . of_node ;
struct i2c_demux_pinctrl_priv * priv ;
2016-08-23 17:28:03 +02:00
struct property * props ;
2016-01-13 15:29:27 +01:00
int num_chan , i , j , err ;
num_chan = of_count_phandle_with_args ( np , " i2c-parent " , NULL ) ;
if ( num_chan < 2 ) {
dev_err ( & pdev - > dev , " Need at least two I2C masters to switch \n " ) ;
return - EINVAL ;
}
priv = devm_kzalloc ( & pdev - > dev , sizeof ( * priv )
+ num_chan * sizeof ( struct i2c_demux_pinctrl_chan ) , GFP_KERNEL ) ;
2016-08-23 17:28:03 +02:00
props = devm_kcalloc ( & pdev - > dev , num_chan , sizeof ( * props ) , GFP_KERNEL ) ;
if ( ! priv | | ! props )
2016-01-13 15:29:27 +01:00
return - ENOMEM ;
err = of_property_read_string ( np , " i2c-bus-name " , & priv - > bus_name ) ;
if ( err )
return err ;
for ( i = 0 ; i < num_chan ; i + + ) {
struct device_node * adap_np ;
adap_np = of_parse_phandle ( np , " i2c-parent " , i ) ;
if ( ! adap_np ) {
dev_err ( & pdev - > dev , " can't get phandle for parent %d \n " , i ) ;
err = - ENOENT ;
goto err_rollback ;
}
priv - > chan [ i ] . parent_np = adap_np ;
2016-08-23 17:28:03 +02:00
props [ i ] . name = devm_kstrdup ( & pdev - > dev , " status " , GFP_KERNEL ) ;
props [ i ] . value = devm_kstrdup ( & pdev - > dev , " ok " , GFP_KERNEL ) ;
props [ i ] . length = 3 ;
2016-01-13 15:29:27 +01:00
of_changeset_init ( & priv - > chan [ i ] . chgset ) ;
2016-08-23 17:28:03 +02:00
of_changeset_update_property ( & priv - > chan [ i ] . chgset , adap_np , & props [ i ] ) ;
2016-01-13 15:29:27 +01:00
}
priv - > num_chan = num_chan ;
priv - > dev = & pdev - > dev ;
platform_set_drvdata ( pdev , priv ) ;
2018-04-30 14:08:42 +02:00
pm_runtime_no_callbacks ( & pdev - > dev ) ;
2016-01-13 15:29:27 +01:00
/* switch to first parent as active master */
i2c_demux_activate_master ( priv , 0 ) ;
2016-03-31 16:40:05 +01:00
err = device_create_file ( & pdev - > dev , & dev_attr_available_masters ) ;
2016-01-13 15:29:27 +01:00
if ( err )
goto err_rollback ;
2016-03-31 16:40:05 +01:00
err = device_create_file ( & pdev - > dev , & dev_attr_current_master ) ;
if ( err )
goto err_rollback_available ;
2016-01-13 15:29:27 +01:00
return 0 ;
2016-03-31 16:40:05 +01:00
err_rollback_available :
device_remove_file ( & pdev - > dev , & dev_attr_available_masters ) ;
2016-01-13 15:29:27 +01:00
err_rollback :
for ( j = 0 ; j < i ; j + + ) {
of_node_put ( priv - > chan [ j ] . parent_np ) ;
of_changeset_destroy ( & priv - > chan [ j ] . chgset ) ;
}
return err ;
}
static int i2c_demux_pinctrl_remove ( struct platform_device * pdev )
{
struct i2c_demux_pinctrl_priv * priv = platform_get_drvdata ( pdev ) ;
int i ;
2016-03-31 16:40:05 +01:00
device_remove_file ( & pdev - > dev , & dev_attr_current_master ) ;
device_remove_file ( & pdev - > dev , & dev_attr_available_masters ) ;
2016-01-13 15:29:27 +01:00
i2c_demux_deactivate_master ( priv ) ;
for ( i = 0 ; i < priv - > num_chan ; i + + ) {
of_node_put ( priv - > chan [ i ] . parent_np ) ;
of_changeset_destroy ( & priv - > chan [ i ] . chgset ) ;
}
return 0 ;
}
static const struct of_device_id i2c_demux_pinctrl_of_match [ ] = {
{ . compatible = " i2c-demux-pinctrl " , } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , i2c_demux_pinctrl_of_match ) ;
static struct platform_driver i2c_demux_pinctrl_driver = {
. driver = {
. name = " i2c-demux-pinctrl " ,
. of_match_table = i2c_demux_pinctrl_of_match ,
} ,
. probe = i2c_demux_pinctrl_probe ,
. remove = i2c_demux_pinctrl_remove ,
} ;
module_platform_driver ( i2c_demux_pinctrl_driver ) ;
MODULE_DESCRIPTION ( " pinctrl-based I2C demux driver " ) ;
MODULE_AUTHOR ( " Wolfram Sang <wsa@sang-engineering.com> " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_ALIAS ( " platform:i2c-demux-pinctrl " ) ;