2018-09-28 16:15:30 +03:00
// SPDX-License-Identifier: GPL-2.0+
//
// Synopsys CREG (Control REGisters) GPIO driver
//
// Copyright (C) 2018 Synopsys
// Author: Eugeniy Paltsev <Eugeniy.Paltsev@synopsys.com>
# include <linux/gpio/driver.h>
# include <linux/io.h>
# include <linux/of.h>
# include <linux/of_platform.h>
# define MAX_GPIO 32
struct creg_layout {
u8 ngpio ;
u8 shift [ MAX_GPIO ] ;
u8 on [ MAX_GPIO ] ;
u8 off [ MAX_GPIO ] ;
u8 bit_per_gpio [ MAX_GPIO ] ;
} ;
struct creg_gpio {
struct gpio_chip gc ;
void __iomem * regs ;
spinlock_t lock ;
const struct creg_layout * layout ;
} ;
static void creg_gpio_set ( struct gpio_chip * gc , unsigned int offset , int val )
{
struct creg_gpio * hcg = gpiochip_get_data ( gc ) ;
const struct creg_layout * layout = hcg - > layout ;
u32 reg , reg_shift , value ;
unsigned long flags ;
int i ;
value = val ? hcg - > layout - > on [ offset ] : hcg - > layout - > off [ offset ] ;
reg_shift = layout - > shift [ offset ] ;
for ( i = 0 ; i < offset ; i + + )
reg_shift + = layout - > bit_per_gpio [ i ] + layout - > shift [ i ] ;
spin_lock_irqsave ( & hcg - > lock , flags ) ;
reg = readl ( hcg - > regs ) ;
reg & = ~ ( GENMASK ( layout - > bit_per_gpio [ i ] - 1 , 0 ) < < reg_shift ) ;
reg | = ( value < < reg_shift ) ;
writel ( reg , hcg - > regs ) ;
spin_unlock_irqrestore ( & hcg - > lock , flags ) ;
}
static int creg_gpio_dir_out ( struct gpio_chip * gc , unsigned int offset , int val )
{
creg_gpio_set ( gc , offset , val ) ;
return 0 ;
}
static int creg_gpio_validate_pg ( struct device * dev , struct creg_gpio * hcg ,
int i )
{
const struct creg_layout * layout = hcg - > layout ;
if ( layout - > bit_per_gpio [ i ] < 1 | | layout - > bit_per_gpio [ i ] > 8 )
return - EINVAL ;
2020-01-18 13:53:19 +03:00
/* Check that on value fits its placeholder */
2018-09-28 16:15:30 +03:00
if ( GENMASK ( 31 , layout - > bit_per_gpio [ i ] ) & layout - > on [ i ] )
return - EINVAL ;
2020-01-18 13:53:19 +03:00
/* Check that off value fits its placeholder */
2018-09-28 16:15:30 +03:00
if ( GENMASK ( 31 , layout - > bit_per_gpio [ i ] ) & layout - > off [ i ] )
return - EINVAL ;
if ( layout - > on [ i ] = = layout - > off [ i ] )
return - EINVAL ;
return 0 ;
}
static int creg_gpio_validate ( struct device * dev , struct creg_gpio * hcg ,
u32 ngpios )
{
u32 reg_len = 0 ;
int i ;
if ( hcg - > layout - > ngpio < 1 | | hcg - > layout - > ngpio > MAX_GPIO )
return - EINVAL ;
if ( ngpios < 1 | | ngpios > hcg - > layout - > ngpio ) {
dev_err ( dev , " ngpios must be in [1:%u] \n " , hcg - > layout - > ngpio ) ;
return - EINVAL ;
}
for ( i = 0 ; i < hcg - > layout - > ngpio ; i + + ) {
if ( creg_gpio_validate_pg ( dev , hcg , i ) )
return - EINVAL ;
reg_len + = hcg - > layout - > shift [ i ] + hcg - > layout - > bit_per_gpio [ i ] ;
}
/* Check that we fit in 32 bit register */
if ( reg_len > 32 )
return - EINVAL ;
return 0 ;
}
static const struct creg_layout hsdk_cs_ctl = {
. ngpio = 10 ,
. shift = { 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 } ,
. off = { 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 } ,
. on = { 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 } ,
. bit_per_gpio = { 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 }
} ;
static const struct creg_layout axs10x_flsh_cs_ctl = {
. ngpio = 1 ,
. shift = { 0 } ,
. off = { 1 } ,
. on = { 3 } ,
. bit_per_gpio = { 2 }
} ;
static const struct of_device_id creg_gpio_ids [ ] = {
{
. compatible = " snps,creg-gpio-axs10x " ,
. data = & axs10x_flsh_cs_ctl
} , {
. compatible = " snps,creg-gpio-hsdk " ,
. data = & hsdk_cs_ctl
} , { /* sentinel */ }
} ;
static int creg_gpio_probe ( struct platform_device * pdev )
{
const struct of_device_id * match ;
struct device * dev = & pdev - > dev ;
struct creg_gpio * hcg ;
u32 ngpios ;
int ret ;
hcg = devm_kzalloc ( dev , sizeof ( struct creg_gpio ) , GFP_KERNEL ) ;
if ( ! hcg )
return - ENOMEM ;
2019-09-06 16:10:32 +03:00
hcg - > regs = devm_platform_ioremap_resource ( pdev , 0 ) ;
2018-09-28 16:15:30 +03:00
if ( IS_ERR ( hcg - > regs ) )
return PTR_ERR ( hcg - > regs ) ;
match = of_match_node ( creg_gpio_ids , pdev - > dev . of_node ) ;
hcg - > layout = match - > data ;
if ( ! hcg - > layout )
return - EINVAL ;
ret = of_property_read_u32 ( dev - > of_node , " ngpios " , & ngpios ) ;
if ( ret )
return ret ;
ret = creg_gpio_validate ( dev , hcg , ngpios ) ;
if ( ret )
return ret ;
spin_lock_init ( & hcg - > lock ) ;
hcg - > gc . label = dev_name ( dev ) ;
hcg - > gc . base = - 1 ;
hcg - > gc . ngpio = ngpios ;
hcg - > gc . set = creg_gpio_set ;
hcg - > gc . direction_output = creg_gpio_dir_out ;
hcg - > gc . of_node = dev - > of_node ;
ret = devm_gpiochip_add_data ( dev , & hcg - > gc , hcg ) ;
if ( ret )
return ret ;
dev_info ( dev , " GPIO controller with %d gpios probed \n " , ngpios ) ;
return 0 ;
}
static struct platform_driver creg_gpio_snps_driver = {
. driver = {
. name = " snps-creg-gpio " ,
. of_match_table = creg_gpio_ids ,
} ,
. probe = creg_gpio_probe ,
} ;
builtin_platform_driver ( creg_gpio_snps_driver ) ;