2019-06-04 11:11:33 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2017-05-14 22:51:12 +03:00
/*
* General Purpose I2C multiplexer
*
* Copyright ( C ) 2017 Axentia Technologies AB
*
* Author : Peter Rosin < peda @ axentia . se >
*/
# include <linux/i2c.h>
# include <linux/i2c-mux.h>
# include <linux/module.h>
# include <linux/mux/consumer.h>
2023-07-14 20:46:16 +03:00
# include <linux/of.h>
2017-05-14 22:51:12 +03:00
# include <linux/platform_device.h>
struct mux {
struct mux_control * control ;
bool do_not_deselect ;
} ;
static int i2c_mux_select ( struct i2c_mux_core * muxc , u32 chan )
{
struct mux * mux = i2c_mux_priv ( muxc ) ;
int ret ;
ret = mux_control_select ( mux - > control , chan ) ;
mux - > do_not_deselect = ret < 0 ;
return ret ;
}
static int i2c_mux_deselect ( struct i2c_mux_core * muxc , u32 chan )
{
struct mux * mux = i2c_mux_priv ( muxc ) ;
if ( mux - > do_not_deselect )
return 0 ;
return mux_control_deselect ( mux - > control ) ;
}
static struct i2c_adapter * mux_parent_adapter ( struct device * dev )
{
struct device_node * np = dev - > of_node ;
struct device_node * parent_np ;
struct i2c_adapter * parent ;
parent_np = of_parse_phandle ( np , " i2c-parent " , 0 ) ;
if ( ! parent_np ) {
dev_err ( dev , " Cannot parse i2c-parent \n " ) ;
return ERR_PTR ( - ENODEV ) ;
}
2023-10-20 18:30:13 +03:00
parent = of_get_i2c_adapter_by_node ( parent_np ) ;
2017-05-14 22:51:12 +03:00
of_node_put ( parent_np ) ;
if ( ! parent )
return ERR_PTR ( - EPROBE_DEFER ) ;
return parent ;
}
static const struct of_device_id i2c_mux_of_match [ ] = {
{ . compatible = " i2c-mux " , } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , i2c_mux_of_match ) ;
static int i2c_mux_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
struct device_node * np = dev - > of_node ;
struct device_node * child ;
struct i2c_mux_core * muxc ;
struct mux * mux ;
struct i2c_adapter * parent ;
int children ;
int ret ;
if ( ! np )
return - ENODEV ;
mux = devm_kzalloc ( dev , sizeof ( * mux ) , GFP_KERNEL ) ;
if ( ! mux )
return - ENOMEM ;
mux - > control = devm_mux_control_get ( dev , NULL ) ;
2020-09-02 18:06:42 +03:00
if ( IS_ERR ( mux - > control ) )
return dev_err_probe ( dev , PTR_ERR ( mux - > control ) ,
" failed to get control-mux \n " ) ;
2017-05-14 22:51:12 +03:00
parent = mux_parent_adapter ( dev ) ;
2020-09-02 18:06:42 +03:00
if ( IS_ERR ( parent ) )
return dev_err_probe ( dev , PTR_ERR ( parent ) ,
" failed to get i2c-parent adapter \n " ) ;
2017-05-14 22:51:12 +03:00
children = of_get_child_count ( np ) ;
muxc = i2c_mux_alloc ( parent , dev , children , 0 , 0 ,
i2c_mux_select , i2c_mux_deselect ) ;
if ( ! muxc ) {
ret = - ENOMEM ;
goto err_parent ;
}
muxc - > priv = mux ;
platform_set_drvdata ( pdev , muxc ) ;
muxc - > mux_locked = of_property_read_bool ( np , " mux-locked " ) ;
for_each_child_of_node ( np , child ) {
u32 chan ;
ret = of_property_read_u32 ( child , " reg " , & chan ) ;
if ( ret < 0 ) {
2018-09-29 01:49:47 +03:00
dev_err ( dev , " no reg property for node '%pOFn' \n " ,
child ) ;
2017-05-14 22:51:12 +03:00
goto err_children ;
}
if ( chan > = mux_control_states ( mux - > control ) ) {
dev_err ( dev , " invalid reg %u \n " , chan ) ;
ret = - EINVAL ;
goto err_children ;
}
ret = i2c_mux_add_adapter ( muxc , 0 , chan , 0 ) ;
if ( ret )
goto err_children ;
}
dev_info ( dev , " %d-port mux on %s adapter \n " , children , parent - > name ) ;
return 0 ;
err_children :
2022-07-22 04:24:01 +03:00
of_node_put ( child ) ;
2017-05-14 22:51:12 +03:00
i2c_mux_del_adapters ( muxc ) ;
err_parent :
i2c_put_adapter ( parent ) ;
return ret ;
}
2023-05-08 23:51:38 +03:00
static void i2c_mux_remove ( struct platform_device * pdev )
2017-05-14 22:51:12 +03:00
{
struct i2c_mux_core * muxc = platform_get_drvdata ( pdev ) ;
i2c_mux_del_adapters ( muxc ) ;
i2c_put_adapter ( muxc - > parent ) ;
}
static struct platform_driver i2c_mux_driver = {
. probe = i2c_mux_probe ,
2023-05-08 23:51:38 +03:00
. remove_new = i2c_mux_remove ,
2017-05-14 22:51:12 +03:00
. driver = {
. name = " i2c-mux-gpmux " ,
. of_match_table = i2c_mux_of_match ,
} ,
} ;
module_platform_driver ( i2c_mux_driver ) ;
MODULE_DESCRIPTION ( " General Purpose I2C multiplexer driver " ) ;
MODULE_AUTHOR ( " Peter Rosin <peda@axentia.se> " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;