2020-05-11 17:52:55 +03:00
// SPDX-License-Identifier: GPL-2.0-only
//
// GPIO Aggregator
//
// Copyright (C) 2019-2020 Glider bv
# define DRV_NAME "gpio-aggregator"
# define pr_fmt(fmt) DRV_NAME ": " fmt
# include <linux/bitmap.h>
# include <linux/bitops.h>
# include <linux/ctype.h>
2020-07-01 14:42:12 +03:00
# include <linux/gpio.h>
2020-05-11 17:52:55 +03:00
# include <linux/gpio/consumer.h>
# include <linux/gpio/driver.h>
# include <linux/gpio/machine.h>
# include <linux/idr.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/mutex.h>
# include <linux/overflow.h>
# include <linux/platform_device.h>
# include <linux/spinlock.h>
# include <linux/string.h>
2022-09-02 15:42:02 +03:00
# define AGGREGATOR_MAX_GPIOS 512
2020-05-11 17:52:55 +03:00
/*
* GPIO Aggregator sysfs interface
*/
struct gpio_aggregator {
struct gpiod_lookup_table * lookups ;
struct platform_device * pdev ;
char args [ ] ;
} ;
static DEFINE_MUTEX ( gpio_aggregator_lock ) ; /* protects idr */
static DEFINE_IDR ( gpio_aggregator_idr ) ;
static int aggr_add_gpio ( struct gpio_aggregator * aggr , const char * key ,
int hwnum , unsigned int * n )
{
struct gpiod_lookup_table * lookups ;
lookups = krealloc ( aggr - > lookups , struct_size ( lookups , table , * n + 2 ) ,
GFP_KERNEL ) ;
if ( ! lookups )
return - ENOMEM ;
2021-01-22 15:38:52 +03:00
lookups - > table [ * n ] = GPIO_LOOKUP_IDX ( key , hwnum , NULL , * n , 0 ) ;
2020-05-11 17:52:55 +03:00
( * n ) + + ;
memset ( & lookups - > table [ * n ] , 0 , sizeof ( lookups - > table [ * n ] ) ) ;
aggr - > lookups = lookups ;
return 0 ;
}
static int aggr_parse ( struct gpio_aggregator * aggr )
{
2021-03-01 19:59:32 +03:00
char * args = skip_spaces ( aggr - > args ) ;
2021-01-22 15:38:51 +03:00
char * name , * offsets , * p ;
2020-07-01 14:42:12 +03:00
unsigned long * bitmap ;
unsigned int i , n = 0 ;
int error = 0 ;
2022-09-02 15:42:02 +03:00
bitmap = bitmap_alloc ( AGGREGATOR_MAX_GPIOS , GFP_KERNEL ) ;
2020-07-01 14:42:12 +03:00
if ( ! bitmap )
return - ENOMEM ;
2020-05-11 17:52:55 +03:00
2021-03-01 19:59:32 +03:00
args = next_arg ( args , & name , & p ) ;
while ( * args ) {
args = next_arg ( args , & offsets , & p ) ;
2020-05-11 17:52:55 +03:00
2021-01-22 15:38:51 +03:00
p = get_options ( offsets , 0 , & error ) ;
if ( error = = 0 | | * p ) {
2020-05-11 17:52:55 +03:00
/* Named GPIO line */
error = aggr_add_gpio ( aggr , name , U16_MAX , & n ) ;
if ( error )
2020-07-01 14:42:12 +03:00
goto free_bitmap ;
2020-05-11 17:52:55 +03:00
name = offsets ;
continue ;
}
/* GPIO chip + offset(s) */
2022-09-02 15:42:02 +03:00
error = bitmap_parselist ( offsets , bitmap , AGGREGATOR_MAX_GPIOS ) ;
2020-07-01 14:42:12 +03:00
if ( error ) {
pr_err ( " Cannot parse %s: %d \n " , offsets , error ) ;
goto free_bitmap ;
}
2022-09-02 15:42:02 +03:00
for_each_set_bit ( i , bitmap , AGGREGATOR_MAX_GPIOS ) {
2020-07-01 14:42:12 +03:00
error = aggr_add_gpio ( aggr , name , i , & n ) ;
if ( error )
goto free_bitmap ;
2020-05-11 17:52:55 +03:00
}
2021-03-01 19:59:32 +03:00
args = next_arg ( args , & name , & p ) ;
2020-05-11 17:52:55 +03:00
}
if ( ! n ) {
pr_err ( " No GPIOs specified \n " ) ;
2020-07-01 14:42:12 +03:00
error = - EINVAL ;
2020-05-11 17:52:55 +03:00
}
2020-07-01 14:42:12 +03:00
free_bitmap :
bitmap_free ( bitmap ) ;
return error ;
2020-05-11 17:52:55 +03:00
}
static ssize_t new_device_store ( struct device_driver * driver , const char * buf ,
size_t count )
{
struct gpio_aggregator * aggr ;
struct platform_device * pdev ;
int res , id ;
/* kernfs guarantees string termination, so count + 1 is safe */
aggr = kzalloc ( sizeof ( * aggr ) + count + 1 , GFP_KERNEL ) ;
if ( ! aggr )
return - ENOMEM ;
memcpy ( aggr - > args , buf , count + 1 ) ;
aggr - > lookups = kzalloc ( struct_size ( aggr - > lookups , table , 1 ) ,
GFP_KERNEL ) ;
if ( ! aggr - > lookups ) {
res = - ENOMEM ;
goto free_ga ;
}
mutex_lock ( & gpio_aggregator_lock ) ;
id = idr_alloc ( & gpio_aggregator_idr , aggr , 0 , 0 , GFP_KERNEL ) ;
mutex_unlock ( & gpio_aggregator_lock ) ;
if ( id < 0 ) {
res = id ;
goto free_table ;
}
aggr - > lookups - > dev_id = kasprintf ( GFP_KERNEL , " %s.%d " , DRV_NAME , id ) ;
if ( ! aggr - > lookups - > dev_id ) {
res = - ENOMEM ;
goto remove_idr ;
}
res = aggr_parse ( aggr ) ;
if ( res )
goto free_dev_id ;
gpiod_add_lookup_table ( aggr - > lookups ) ;
pdev = platform_device_register_simple ( DRV_NAME , id , NULL , 0 ) ;
if ( IS_ERR ( pdev ) ) {
res = PTR_ERR ( pdev ) ;
goto remove_table ;
}
aggr - > pdev = pdev ;
return count ;
remove_table :
gpiod_remove_lookup_table ( aggr - > lookups ) ;
free_dev_id :
kfree ( aggr - > lookups - > dev_id ) ;
remove_idr :
mutex_lock ( & gpio_aggregator_lock ) ;
idr_remove ( & gpio_aggregator_idr , id ) ;
mutex_unlock ( & gpio_aggregator_lock ) ;
free_table :
kfree ( aggr - > lookups ) ;
free_ga :
kfree ( aggr ) ;
return res ;
}
static DRIVER_ATTR_WO ( new_device ) ;
static void gpio_aggregator_free ( struct gpio_aggregator * aggr )
{
platform_device_unregister ( aggr - > pdev ) ;
gpiod_remove_lookup_table ( aggr - > lookups ) ;
kfree ( aggr - > lookups - > dev_id ) ;
kfree ( aggr - > lookups ) ;
kfree ( aggr ) ;
}
static ssize_t delete_device_store ( struct device_driver * driver ,
const char * buf , size_t count )
{
struct gpio_aggregator * aggr ;
unsigned int id ;
int error ;
if ( ! str_has_prefix ( buf , DRV_NAME " . " ) )
return - EINVAL ;
error = kstrtouint ( buf + strlen ( DRV_NAME " . " ) , 10 , & id ) ;
if ( error )
return error ;
mutex_lock ( & gpio_aggregator_lock ) ;
aggr = idr_remove ( & gpio_aggregator_idr , id ) ;
mutex_unlock ( & gpio_aggregator_lock ) ;
if ( ! aggr )
return - ENOENT ;
gpio_aggregator_free ( aggr ) ;
return count ;
}
static DRIVER_ATTR_WO ( delete_device ) ;
static struct attribute * gpio_aggregator_attrs [ ] = {
& driver_attr_new_device . attr ,
& driver_attr_delete_device . attr ,
2021-01-22 15:38:53 +03:00
NULL
2020-05-11 17:52:55 +03:00
} ;
ATTRIBUTE_GROUPS ( gpio_aggregator ) ;
static int __exit gpio_aggregator_idr_remove ( int id , void * p , void * data )
{
gpio_aggregator_free ( p ) ;
return 0 ;
}
static void __exit gpio_aggregator_remove_all ( void )
{
mutex_lock ( & gpio_aggregator_lock ) ;
idr_for_each ( & gpio_aggregator_idr , gpio_aggregator_idr_remove , NULL ) ;
idr_destroy ( & gpio_aggregator_idr ) ;
mutex_unlock ( & gpio_aggregator_lock ) ;
}
/*
* GPIO Forwarder
*/
struct gpiochip_fwd {
struct gpio_chip chip ;
struct gpio_desc * * descs ;
union {
struct mutex mlock ; /* protects tmp[] if can_sleep */
spinlock_t slock ; /* protects tmp[] if !can_sleep */
} ;
unsigned long tmp [ ] ; /* values and descs for multiple ops */
} ;
2021-10-04 15:45:45 +03:00
# define fwd_tmp_values(fwd) &(fwd)->tmp[0]
# define fwd_tmp_descs(fwd) (void *)&(fwd)->tmp[BITS_TO_LONGS((fwd)->chip.ngpio)]
# define fwd_tmp_size(ngpios) (BITS_TO_LONGS((ngpios)) + (ngpios))
2020-05-11 17:52:55 +03:00
static int gpio_fwd_get_direction ( struct gpio_chip * chip , unsigned int offset )
{
struct gpiochip_fwd * fwd = gpiochip_get_data ( chip ) ;
return gpiod_get_direction ( fwd - > descs [ offset ] ) ;
}
static int gpio_fwd_direction_input ( struct gpio_chip * chip , unsigned int offset )
{
struct gpiochip_fwd * fwd = gpiochip_get_data ( chip ) ;
return gpiod_direction_input ( fwd - > descs [ offset ] ) ;
}
static int gpio_fwd_direction_output ( struct gpio_chip * chip ,
unsigned int offset , int value )
{
struct gpiochip_fwd * fwd = gpiochip_get_data ( chip ) ;
return gpiod_direction_output ( fwd - > descs [ offset ] , value ) ;
}
static int gpio_fwd_get ( struct gpio_chip * chip , unsigned int offset )
{
struct gpiochip_fwd * fwd = gpiochip_get_data ( chip ) ;
2022-01-31 13:35:53 +03:00
return chip - > can_sleep ? gpiod_get_value_cansleep ( fwd - > descs [ offset ] )
: gpiod_get_value ( fwd - > descs [ offset ] ) ;
2020-05-11 17:52:55 +03:00
}
2020-08-10 14:43:53 +03:00
static int gpio_fwd_get_multiple ( struct gpiochip_fwd * fwd , unsigned long * mask ,
2020-05-11 17:52:55 +03:00
unsigned long * bits )
{
2021-10-04 15:45:45 +03:00
struct gpio_desc * * descs = fwd_tmp_descs ( fwd ) ;
unsigned long * values = fwd_tmp_values ( fwd ) ;
2020-05-11 17:52:55 +03:00
unsigned int i , j = 0 ;
int error ;
bitmap_clear ( values , 0 , fwd - > chip . ngpio ) ;
for_each_set_bit ( i , mask , fwd - > chip . ngpio )
descs [ j + + ] = fwd - > descs [ i ] ;
2022-01-31 13:35:53 +03:00
if ( fwd - > chip . can_sleep )
error = gpiod_get_array_value_cansleep ( j , descs , NULL , values ) ;
else
error = gpiod_get_array_value ( j , descs , NULL , values ) ;
2020-08-10 14:43:53 +03:00
if ( error )
return error ;
2020-05-11 17:52:55 +03:00
2020-08-10 14:43:53 +03:00
j = 0 ;
for_each_set_bit ( i , mask , fwd - > chip . ngpio )
__assign_bit ( i , bits , test_bit ( j + + , values ) ) ;
return 0 ;
}
static int gpio_fwd_get_multiple_locked ( struct gpio_chip * chip ,
unsigned long * mask , unsigned long * bits )
{
struct gpiochip_fwd * fwd = gpiochip_get_data ( chip ) ;
unsigned long flags ;
int error ;
if ( chip - > can_sleep ) {
mutex_lock ( & fwd - > mlock ) ;
error = gpio_fwd_get_multiple ( fwd , mask , bits ) ;
2020-05-11 17:52:55 +03:00
mutex_unlock ( & fwd - > mlock ) ;
2020-08-10 14:43:53 +03:00
} else {
spin_lock_irqsave ( & fwd - > slock , flags ) ;
error = gpio_fwd_get_multiple ( fwd , mask , bits ) ;
2020-05-11 17:52:55 +03:00
spin_unlock_irqrestore ( & fwd - > slock , flags ) ;
2020-08-10 14:43:53 +03:00
}
2020-05-11 17:52:55 +03:00
return error ;
}
static void gpio_fwd_set ( struct gpio_chip * chip , unsigned int offset , int value )
{
struct gpiochip_fwd * fwd = gpiochip_get_data ( chip ) ;
2022-01-31 13:35:53 +03:00
if ( chip - > can_sleep )
gpiod_set_value_cansleep ( fwd - > descs [ offset ] , value ) ;
else
gpiod_set_value ( fwd - > descs [ offset ] , value ) ;
2020-05-11 17:52:55 +03:00
}
2020-08-10 14:43:53 +03:00
static void gpio_fwd_set_multiple ( struct gpiochip_fwd * fwd , unsigned long * mask ,
2020-05-11 17:52:55 +03:00
unsigned long * bits )
{
2021-10-04 15:45:45 +03:00
struct gpio_desc * * descs = fwd_tmp_descs ( fwd ) ;
unsigned long * values = fwd_tmp_values ( fwd ) ;
2020-05-11 17:52:55 +03:00
unsigned int i , j = 0 ;
for_each_set_bit ( i , mask , fwd - > chip . ngpio ) {
__assign_bit ( j , values , test_bit ( i , bits ) ) ;
descs [ j + + ] = fwd - > descs [ i ] ;
}
2022-01-31 13:35:53 +03:00
if ( fwd - > chip . can_sleep )
gpiod_set_array_value_cansleep ( j , descs , NULL , values ) ;
else
gpiod_set_array_value ( j , descs , NULL , values ) ;
2020-08-10 14:43:53 +03:00
}
2020-05-11 17:52:55 +03:00
2020-08-10 14:43:53 +03:00
static void gpio_fwd_set_multiple_locked ( struct gpio_chip * chip ,
unsigned long * mask , unsigned long * bits )
{
struct gpiochip_fwd * fwd = gpiochip_get_data ( chip ) ;
unsigned long flags ;
if ( chip - > can_sleep ) {
mutex_lock ( & fwd - > mlock ) ;
gpio_fwd_set_multiple ( fwd , mask , bits ) ;
2020-05-11 17:52:55 +03:00
mutex_unlock ( & fwd - > mlock ) ;
2020-08-10 14:43:53 +03:00
} else {
spin_lock_irqsave ( & fwd - > slock , flags ) ;
gpio_fwd_set_multiple ( fwd , mask , bits ) ;
2020-05-11 17:52:55 +03:00
spin_unlock_irqrestore ( & fwd - > slock , flags ) ;
2020-08-10 14:43:53 +03:00
}
2020-05-11 17:52:55 +03:00
}
static int gpio_fwd_set_config ( struct gpio_chip * chip , unsigned int offset ,
unsigned long config )
{
struct gpiochip_fwd * fwd = gpiochip_get_data ( chip ) ;
return gpiod_set_config ( fwd - > descs [ offset ] , config ) ;
}
2021-12-03 17:06:44 +03:00
static int gpio_fwd_to_irq ( struct gpio_chip * chip , unsigned int offset )
{
struct gpiochip_fwd * fwd = gpiochip_get_data ( chip ) ;
return gpiod_to_irq ( fwd - > descs [ offset ] ) ;
}
2020-05-11 17:52:55 +03:00
/**
* gpiochip_fwd_create ( ) - Create a new GPIO forwarder
* @ dev : Parent device pointer
* @ ngpios : Number of GPIOs in the forwarder .
* @ descs : Array containing the GPIO descriptors to forward to .
* This array must contain @ ngpios entries , and must not be deallocated
* before the forwarder has been destroyed again .
*
* This function creates a new gpiochip , which forwards all GPIO operations to
* the passed GPIO descriptors .
*
* Return : An opaque object pointer , or an ERR_PTR ( ) - encoded negative error
* code on failure .
*/
static struct gpiochip_fwd * gpiochip_fwd_create ( struct device * dev ,
unsigned int ngpios ,
struct gpio_desc * descs [ ] )
{
const char * label = dev_name ( dev ) ;
struct gpiochip_fwd * fwd ;
struct gpio_chip * chip ;
unsigned int i ;
int error ;
2021-10-04 15:45:45 +03:00
fwd = devm_kzalloc ( dev , struct_size ( fwd , tmp , fwd_tmp_size ( ngpios ) ) ,
GFP_KERNEL ) ;
2020-05-11 17:52:55 +03:00
if ( ! fwd )
return ERR_PTR ( - ENOMEM ) ;
chip = & fwd - > chip ;
/*
* If any of the GPIO lines are sleeping , then the entire forwarder
* will be sleeping .
* If any of the chips support . set_config ( ) , then the forwarder will
* support setting configs .
*/
for ( i = 0 ; i < ngpios ; i + + ) {
struct gpio_chip * parent = gpiod_to_chip ( descs [ i ] ) ;
2021-12-03 17:06:44 +03:00
dev_dbg ( dev , " %u => gpio %d irq %d \n " , i ,
desc_to_gpio ( descs [ i ] ) , gpiod_to_irq ( descs [ i ] ) ) ;
2020-05-11 17:52:55 +03:00
if ( gpiod_cansleep ( descs [ i ] ) )
chip - > can_sleep = true ;
if ( parent & & parent - > set_config )
chip - > set_config = gpio_fwd_set_config ;
}
chip - > label = label ;
chip - > parent = dev ;
chip - > owner = THIS_MODULE ;
chip - > get_direction = gpio_fwd_get_direction ;
chip - > direction_input = gpio_fwd_direction_input ;
chip - > direction_output = gpio_fwd_direction_output ;
chip - > get = gpio_fwd_get ;
2020-08-10 14:43:53 +03:00
chip - > get_multiple = gpio_fwd_get_multiple_locked ;
2020-05-11 17:52:55 +03:00
chip - > set = gpio_fwd_set ;
2020-08-10 14:43:53 +03:00
chip - > set_multiple = gpio_fwd_set_multiple_locked ;
2021-12-03 17:06:44 +03:00
chip - > to_irq = gpio_fwd_to_irq ;
2020-05-11 17:52:55 +03:00
chip - > base = - 1 ;
chip - > ngpio = ngpios ;
fwd - > descs = descs ;
if ( chip - > can_sleep )
mutex_init ( & fwd - > mlock ) ;
else
spin_lock_init ( & fwd - > slock ) ;
error = devm_gpiochip_add_data ( dev , chip , fwd ) ;
if ( error )
return ERR_PTR ( error ) ;
return fwd ;
}
/*
* GPIO Aggregator platform device
*/
static int gpio_aggregator_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
struct gpio_desc * * descs ;
struct gpiochip_fwd * fwd ;
int i , n ;
n = gpiod_count ( dev , NULL ) ;
if ( n < 0 )
return n ;
descs = devm_kmalloc_array ( dev , n , sizeof ( * descs ) , GFP_KERNEL ) ;
if ( ! descs )
return - ENOMEM ;
for ( i = 0 ; i < n ; i + + ) {
descs [ i ] = devm_gpiod_get_index ( dev , NULL , i , GPIOD_ASIS ) ;
if ( IS_ERR ( descs [ i ] ) )
return PTR_ERR ( descs [ i ] ) ;
}
fwd = gpiochip_fwd_create ( dev , n , descs ) ;
if ( IS_ERR ( fwd ) )
return PTR_ERR ( fwd ) ;
platform_set_drvdata ( pdev , fwd ) ;
return 0 ;
}
# ifdef CONFIG_OF
static const struct of_device_id gpio_aggregator_dt_ids [ ] = {
/*
* Add GPIO - operated devices controlled from userspace below ,
* or use " driver_override " in sysfs
*/
2021-01-22 15:38:53 +03:00
{ }
2020-05-11 17:52:55 +03:00
} ;
MODULE_DEVICE_TABLE ( of , gpio_aggregator_dt_ids ) ;
# endif
static struct platform_driver gpio_aggregator_driver = {
. probe = gpio_aggregator_probe ,
. driver = {
. name = DRV_NAME ,
. groups = gpio_aggregator_groups ,
. of_match_table = of_match_ptr ( gpio_aggregator_dt_ids ) ,
} ,
} ;
static int __init gpio_aggregator_init ( void )
{
return platform_driver_register ( & gpio_aggregator_driver ) ;
}
module_init ( gpio_aggregator_init ) ;
static void __exit gpio_aggregator_exit ( void )
{
gpio_aggregator_remove_all ( ) ;
platform_driver_unregister ( & gpio_aggregator_driver ) ;
}
module_exit ( gpio_aggregator_exit ) ;
MODULE_AUTHOR ( " Geert Uytterhoeven <geert+renesas@glider.be> " ) ;
MODULE_DESCRIPTION ( " GPIO Aggregator " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;