2020-05-28 17:58:44 +03:00
// SPDX-License-Identifier: GPL-2.0-only
/*
* regmap based generic GPIO driver
*
* Copyright 2020 Michael Walle < michael @ walle . cc >
*/
# include <linux/gpio/driver.h>
# include <linux/gpio/regmap.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/regmap.h>
struct gpio_regmap {
struct device * parent ;
struct regmap * regmap ;
struct gpio_chip gpio_chip ;
int reg_stride ;
int ngpio_per_reg ;
unsigned int reg_dat_base ;
unsigned int reg_set_base ;
unsigned int reg_clr_base ;
unsigned int reg_dir_in_base ;
unsigned int reg_dir_out_base ;
int ( * reg_mask_xlate ) ( struct gpio_regmap * gpio , unsigned int base ,
unsigned int offset , unsigned int * reg ,
unsigned int * mask ) ;
void * driver_data ;
} ;
static unsigned int gpio_regmap_addr ( unsigned int addr )
{
if ( addr = = GPIO_REGMAP_ADDR_ZERO )
return 0 ;
return addr ;
}
static int gpio_regmap_simple_xlate ( struct gpio_regmap * gpio ,
unsigned int base , unsigned int offset ,
unsigned int * reg , unsigned int * mask )
{
unsigned int line = offset % gpio - > ngpio_per_reg ;
unsigned int stride = offset / gpio - > ngpio_per_reg ;
* reg = base + stride * gpio - > reg_stride ;
* mask = BIT ( line ) ;
return 0 ;
}
static int gpio_regmap_get ( struct gpio_chip * chip , unsigned int offset )
{
struct gpio_regmap * gpio = gpiochip_get_data ( chip ) ;
unsigned int base , val , reg , mask ;
int ret ;
/* we might not have an output register if we are input only */
if ( gpio - > reg_dat_base )
base = gpio_regmap_addr ( gpio - > reg_dat_base ) ;
else
base = gpio_regmap_addr ( gpio - > reg_set_base ) ;
ret = gpio - > reg_mask_xlate ( gpio , base , offset , & reg , & mask ) ;
if ( ret )
return ret ;
ret = regmap_read ( gpio - > regmap , reg , & val ) ;
if ( ret )
return ret ;
return ! ! ( val & mask ) ;
}
static void gpio_regmap_set ( struct gpio_chip * chip , unsigned int offset ,
int val )
{
struct gpio_regmap * gpio = gpiochip_get_data ( chip ) ;
unsigned int base = gpio_regmap_addr ( gpio - > reg_set_base ) ;
unsigned int reg , mask ;
gpio - > reg_mask_xlate ( gpio , base , offset , & reg , & mask ) ;
if ( val )
regmap_update_bits ( gpio - > regmap , reg , mask , mask ) ;
else
regmap_update_bits ( gpio - > regmap , reg , mask , 0 ) ;
}
static void gpio_regmap_set_with_clear ( struct gpio_chip * chip ,
unsigned int offset , int val )
{
struct gpio_regmap * gpio = gpiochip_get_data ( chip ) ;
unsigned int base , reg , mask ;
if ( val )
base = gpio_regmap_addr ( gpio - > reg_set_base ) ;
else
base = gpio_regmap_addr ( gpio - > reg_clr_base ) ;
gpio - > reg_mask_xlate ( gpio , base , offset , & reg , & mask ) ;
regmap_write ( gpio - > regmap , reg , mask ) ;
}
static int gpio_regmap_get_direction ( struct gpio_chip * chip ,
unsigned int offset )
{
struct gpio_regmap * gpio = gpiochip_get_data ( chip ) ;
unsigned int base , val , reg , mask ;
int invert , ret ;
if ( gpio - > reg_dir_out_base ) {
base = gpio_regmap_addr ( gpio - > reg_dir_out_base ) ;
invert = 0 ;
} else if ( gpio - > reg_dir_in_base ) {
base = gpio_regmap_addr ( gpio - > reg_dir_in_base ) ;
invert = 1 ;
} else {
return - EOPNOTSUPP ;
}
ret = gpio - > reg_mask_xlate ( gpio , base , offset , & reg , & mask ) ;
if ( ret )
return ret ;
ret = regmap_read ( gpio - > regmap , reg , & val ) ;
if ( ret )
return ret ;
if ( ! ! ( val & mask ) ^ invert )
return GPIO_LINE_DIRECTION_OUT ;
else
return GPIO_LINE_DIRECTION_IN ;
}
static int gpio_regmap_set_direction ( struct gpio_chip * chip ,
unsigned int offset , bool output )
{
struct gpio_regmap * gpio = gpiochip_get_data ( chip ) ;
unsigned int base , val , reg , mask ;
int invert , ret ;
if ( gpio - > reg_dir_out_base ) {
base = gpio_regmap_addr ( gpio - > reg_dir_out_base ) ;
invert = 0 ;
} else if ( gpio - > reg_dir_in_base ) {
base = gpio_regmap_addr ( gpio - > reg_dir_in_base ) ;
invert = 1 ;
} else {
return - EOPNOTSUPP ;
}
ret = gpio - > reg_mask_xlate ( gpio , base , offset , & reg , & mask ) ;
if ( ret )
return ret ;
if ( invert )
val = output ? 0 : mask ;
else
val = output ? mask : 0 ;
return regmap_update_bits ( gpio - > regmap , reg , mask , val ) ;
}
static int gpio_regmap_direction_input ( struct gpio_chip * chip ,
unsigned int offset )
{
return gpio_regmap_set_direction ( chip , offset , false ) ;
}
static int gpio_regmap_direction_output ( struct gpio_chip * chip ,
unsigned int offset , int value )
{
gpio_regmap_set ( chip , offset , value ) ;
return gpio_regmap_set_direction ( chip , offset , true ) ;
}
void * gpio_regmap_get_drvdata ( struct gpio_regmap * gpio )
{
return gpio - > driver_data ;
}
EXPORT_SYMBOL_GPL ( gpio_regmap_get_drvdata ) ;
/**
* gpio_regmap_register ( ) - Register a generic regmap GPIO controller
* @ config : configuration for gpio_regmap
*
* Return : A pointer to the registered gpio_regmap or ERR_PTR error value .
*/
struct gpio_regmap * gpio_regmap_register ( const struct gpio_regmap_config * config )
{
struct gpio_regmap * gpio ;
struct gpio_chip * chip ;
int ret ;
if ( ! config - > parent )
return ERR_PTR ( - EINVAL ) ;
if ( ! config - > ngpio )
return ERR_PTR ( - EINVAL ) ;
/* we need at least one */
if ( ! config - > reg_dat_base & & ! config - > reg_set_base )
return ERR_PTR ( - EINVAL ) ;
/* if we have a direction register we need both input and output */
if ( ( config - > reg_dir_out_base | | config - > reg_dir_in_base ) & &
( ! config - > reg_dat_base | | ! config - > reg_set_base ) )
return ERR_PTR ( - EINVAL ) ;
/* we don't support having both registers simultaneously for now */
if ( config - > reg_dir_out_base & & config - > reg_dir_in_base )
return ERR_PTR ( - EINVAL ) ;
gpio = kzalloc ( sizeof ( * gpio ) , GFP_KERNEL ) ;
if ( ! gpio )
return ERR_PTR ( - ENOMEM ) ;
gpio - > parent = config - > parent ;
2021-06-05 01:58:57 +03:00
gpio - > driver_data = config - > drvdata ;
2020-05-28 17:58:44 +03:00
gpio - > regmap = config - > regmap ;
gpio - > ngpio_per_reg = config - > ngpio_per_reg ;
gpio - > reg_stride = config - > reg_stride ;
gpio - > reg_mask_xlate = config - > reg_mask_xlate ;
gpio - > reg_dat_base = config - > reg_dat_base ;
gpio - > reg_set_base = config - > reg_set_base ;
gpio - > reg_clr_base = config - > reg_clr_base ;
gpio - > reg_dir_in_base = config - > reg_dir_in_base ;
gpio - > reg_dir_out_base = config - > reg_dir_out_base ;
/* if not set, assume there is only one register */
if ( ! gpio - > ngpio_per_reg )
gpio - > ngpio_per_reg = config - > ngpio ;
/* if not set, assume they are consecutive */
if ( ! gpio - > reg_stride )
gpio - > reg_stride = 1 ;
if ( ! gpio - > reg_mask_xlate )
gpio - > reg_mask_xlate = gpio_regmap_simple_xlate ;
chip = & gpio - > gpio_chip ;
chip - > parent = config - > parent ;
chip - > base = - 1 ;
chip - > ngpio = config - > ngpio ;
chip - > names = config - > names ;
chip - > label = config - > label ? : dev_name ( config - > parent ) ;
2021-03-24 11:19:03 +03:00
# if defined(CONFIG_OF_GPIO)
/* gpiolib will use of_node of the parent if chip->of_node is NULL */
chip - > of_node = to_of_node ( config - > fwnode ) ;
# endif /* CONFIG_OF_GPIO */
2020-05-28 17:58:44 +03:00
/*
* If our regmap is fast_io we should probably set can_sleep to false .
* Right now , the regmap doesn ' t save this property , nor is there any
* access function for it .
* The only regmap type which uses fast_io is regmap - mmio . For now ,
* assume a safe default of true here .
*/
chip - > can_sleep = true ;
chip - > get = gpio_regmap_get ;
if ( gpio - > reg_set_base & & gpio - > reg_clr_base )
chip - > set = gpio_regmap_set_with_clear ;
else if ( gpio - > reg_set_base )
chip - > set = gpio_regmap_set ;
if ( gpio - > reg_dir_in_base | | gpio - > reg_dir_out_base ) {
chip - > get_direction = gpio_regmap_get_direction ;
chip - > direction_input = gpio_regmap_direction_input ;
chip - > direction_output = gpio_regmap_direction_output ;
}
ret = gpiochip_add_data ( chip , gpio ) ;
if ( ret < 0 )
goto err_free_gpio ;
if ( config - > irq_domain ) {
ret = gpiochip_irqchip_add_domain ( chip , config - > irq_domain ) ;
if ( ret )
goto err_remove_gpiochip ;
}
return gpio ;
err_remove_gpiochip :
gpiochip_remove ( chip ) ;
err_free_gpio :
kfree ( gpio ) ;
return ERR_PTR ( ret ) ;
}
EXPORT_SYMBOL_GPL ( gpio_regmap_register ) ;
/**
* gpio_regmap_unregister ( ) - Unregister a generic regmap GPIO controller
* @ gpio : gpio_regmap device to unregister
*/
void gpio_regmap_unregister ( struct gpio_regmap * gpio )
{
gpiochip_remove ( & gpio - > gpio_chip ) ;
kfree ( gpio ) ;
}
EXPORT_SYMBOL_GPL ( gpio_regmap_unregister ) ;
2021-06-03 13:00:21 +03:00
static void devm_gpio_regmap_unregister ( void * res )
2020-05-28 17:58:44 +03:00
{
2021-06-03 13:00:21 +03:00
gpio_regmap_unregister ( res ) ;
2020-05-28 17:58:44 +03:00
}
/**
* devm_gpio_regmap_register ( ) - resource managed gpio_regmap_register ( )
* @ dev : device that is registering this GPIO device
* @ config : configuration for gpio_regmap
*
* Managed gpio_regmap_register ( ) . For generic regmap GPIO device registered by
* this function , gpio_regmap_unregister ( ) is automatically called on driver
* detach . See gpio_regmap_register ( ) for more information .
*
* Return : A pointer to the registered gpio_regmap or ERR_PTR error value .
*/
struct gpio_regmap * devm_gpio_regmap_register ( struct device * dev ,
const struct gpio_regmap_config * config )
{
2021-06-03 13:00:21 +03:00
struct gpio_regmap * gpio ;
int ret ;
2020-05-28 17:58:44 +03:00
gpio = gpio_regmap_register ( config ) ;
2021-06-03 13:00:21 +03:00
if ( IS_ERR ( gpio ) )
return gpio ;
ret = devm_add_action_or_reset ( dev , devm_gpio_regmap_unregister , gpio ) ;
if ( ret )
return ERR_PTR ( ret ) ;
2020-05-28 17:58:44 +03:00
return gpio ;
}
EXPORT_SYMBOL_GPL ( devm_gpio_regmap_register ) ;
MODULE_AUTHOR ( " Michael Walle <michael@walle.cc> " ) ;
MODULE_DESCRIPTION ( " GPIO generic regmap driver core " ) ;
MODULE_LICENSE ( " GPL " ) ;