2017-05-12 18:52:57 +02:00
/*
* Ingenic JZ47xx GPIO driver
*
* Copyright ( c ) 2017 Paul Cercueil < paul @ crapouillou . net >
*
* License terms : GNU General Public License ( GPL ) version 2
*/
# include <linux/gpio/driver.h>
# include <linux/interrupt.h>
# include <linux/io.h>
# include <linux/module.h>
# include <linux/of_address.h>
# include <linux/of_device.h>
# include <linux/of_irq.h>
# include <linux/pinctrl/consumer.h>
# include <linux/regmap.h>
# define GPIO_PIN 0x00
# define GPIO_MSK 0x20
# define JZ4740_GPIO_DATA 0x10
# define JZ4740_GPIO_SELECT 0x50
# define JZ4740_GPIO_DIR 0x60
# define JZ4740_GPIO_TRIG 0x70
# define JZ4740_GPIO_FLAG 0x80
# define JZ4770_GPIO_INT 0x10
# define JZ4770_GPIO_PAT1 0x30
# define JZ4770_GPIO_PAT0 0x40
# define JZ4770_GPIO_FLAG 0x50
# define REG_SET(x) ((x) + 0x4)
# define REG_CLEAR(x) ((x) + 0x8)
enum jz_version {
ID_JZ4740 ,
ID_JZ4770 ,
ID_JZ4780 ,
} ;
struct ingenic_gpio_chip {
struct regmap * map ;
struct gpio_chip gc ;
struct irq_chip irq_chip ;
unsigned int irq , reg_base ;
enum jz_version version ;
} ;
static u32 gpio_ingenic_read_reg ( struct ingenic_gpio_chip * jzgc , u8 reg )
{
unsigned int val ;
regmap_read ( jzgc - > map , jzgc - > reg_base + reg , & val ) ;
return ( u32 ) val ;
}
static void gpio_ingenic_set_bit ( struct ingenic_gpio_chip * jzgc ,
u8 reg , u8 offset , bool set )
{
if ( set )
reg = REG_SET ( reg ) ;
else
reg = REG_CLEAR ( reg ) ;
regmap_write ( jzgc - > map , jzgc - > reg_base + reg , BIT ( offset ) ) ;
}
static inline bool gpio_get_value ( struct ingenic_gpio_chip * jzgc , u8 offset )
{
unsigned int val = gpio_ingenic_read_reg ( jzgc , GPIO_PIN ) ;
return ! ! ( val & BIT ( offset ) ) ;
}
static void gpio_set_value ( struct ingenic_gpio_chip * jzgc , u8 offset , int value )
{
if ( jzgc - > version > = ID_JZ4770 )
gpio_ingenic_set_bit ( jzgc , JZ4770_GPIO_PAT0 , offset , ! ! value ) ;
else
gpio_ingenic_set_bit ( jzgc , JZ4740_GPIO_DATA , offset , ! ! value ) ;
}
static void irq_set_type ( struct ingenic_gpio_chip * jzgc ,
u8 offset , unsigned int type )
{
u8 reg1 , reg2 ;
if ( jzgc - > version > = ID_JZ4770 ) {
reg1 = JZ4770_GPIO_PAT1 ;
reg2 = JZ4770_GPIO_PAT0 ;
} else {
reg1 = JZ4740_GPIO_TRIG ;
reg2 = JZ4740_GPIO_DIR ;
}
switch ( type ) {
case IRQ_TYPE_EDGE_RISING :
gpio_ingenic_set_bit ( jzgc , reg2 , offset , true ) ;
gpio_ingenic_set_bit ( jzgc , reg1 , offset , true ) ;
break ;
case IRQ_TYPE_EDGE_FALLING :
gpio_ingenic_set_bit ( jzgc , reg2 , offset , false ) ;
gpio_ingenic_set_bit ( jzgc , reg1 , offset , true ) ;
break ;
case IRQ_TYPE_LEVEL_HIGH :
gpio_ingenic_set_bit ( jzgc , reg2 , offset , true ) ;
gpio_ingenic_set_bit ( jzgc , reg1 , offset , false ) ;
break ;
case IRQ_TYPE_LEVEL_LOW :
default :
gpio_ingenic_set_bit ( jzgc , reg2 , offset , false ) ;
gpio_ingenic_set_bit ( jzgc , reg1 , offset , false ) ;
break ;
}
}
static void ingenic_gpio_irq_mask ( struct irq_data * irqd )
{
struct gpio_chip * gc = irq_data_get_irq_chip_data ( irqd ) ;
struct ingenic_gpio_chip * jzgc = gpiochip_get_data ( gc ) ;
gpio_ingenic_set_bit ( jzgc , GPIO_MSK , irqd - > hwirq , true ) ;
}
static void ingenic_gpio_irq_unmask ( struct irq_data * irqd )
{
struct gpio_chip * gc = irq_data_get_irq_chip_data ( irqd ) ;
struct ingenic_gpio_chip * jzgc = gpiochip_get_data ( gc ) ;
gpio_ingenic_set_bit ( jzgc , GPIO_MSK , irqd - > hwirq , false ) ;
}
static void ingenic_gpio_irq_enable ( struct irq_data * irqd )
{
struct gpio_chip * gc = irq_data_get_irq_chip_data ( irqd ) ;
struct ingenic_gpio_chip * jzgc = gpiochip_get_data ( gc ) ;
int irq = irqd - > hwirq ;
if ( jzgc - > version > = ID_JZ4770 )
gpio_ingenic_set_bit ( jzgc , JZ4770_GPIO_INT , irq , true ) ;
else
gpio_ingenic_set_bit ( jzgc , JZ4740_GPIO_SELECT , irq , true ) ;
ingenic_gpio_irq_unmask ( irqd ) ;
}
static void ingenic_gpio_irq_disable ( struct irq_data * irqd )
{
struct gpio_chip * gc = irq_data_get_irq_chip_data ( irqd ) ;
struct ingenic_gpio_chip * jzgc = gpiochip_get_data ( gc ) ;
int irq = irqd - > hwirq ;
ingenic_gpio_irq_mask ( irqd ) ;
if ( jzgc - > version > = ID_JZ4770 )
gpio_ingenic_set_bit ( jzgc , JZ4770_GPIO_INT , irq , false ) ;
else
gpio_ingenic_set_bit ( jzgc , JZ4740_GPIO_SELECT , irq , false ) ;
}
static void ingenic_gpio_irq_ack ( struct irq_data * irqd )
{
struct gpio_chip * gc = irq_data_get_irq_chip_data ( irqd ) ;
struct ingenic_gpio_chip * jzgc = gpiochip_get_data ( gc ) ;
int irq = irqd - > hwirq ;
bool high ;
if ( irqd_get_trigger_type ( irqd ) = = IRQ_TYPE_EDGE_BOTH ) {
/*
* Switch to an interrupt for the opposite edge to the one that
* triggered the interrupt being ACKed .
*/
high = gpio_get_value ( jzgc , irq ) ;
if ( high )
irq_set_type ( jzgc , irq , IRQ_TYPE_EDGE_FALLING ) ;
else
irq_set_type ( jzgc , irq , IRQ_TYPE_EDGE_RISING ) ;
}
if ( jzgc - > version > = ID_JZ4770 )
gpio_ingenic_set_bit ( jzgc , JZ4770_GPIO_FLAG , irq , false ) ;
else
gpio_ingenic_set_bit ( jzgc , JZ4740_GPIO_DATA , irq , true ) ;
}
static int ingenic_gpio_irq_set_type ( struct irq_data * irqd , unsigned int type )
{
struct gpio_chip * gc = irq_data_get_irq_chip_data ( irqd ) ;
struct ingenic_gpio_chip * jzgc = gpiochip_get_data ( gc ) ;
switch ( type ) {
case IRQ_TYPE_EDGE_BOTH :
case IRQ_TYPE_EDGE_RISING :
case IRQ_TYPE_EDGE_FALLING :
irq_set_handler_locked ( irqd , handle_edge_irq ) ;
break ;
case IRQ_TYPE_LEVEL_HIGH :
case IRQ_TYPE_LEVEL_LOW :
irq_set_handler_locked ( irqd , handle_level_irq ) ;
break ;
default :
irq_set_handler_locked ( irqd , handle_bad_irq ) ;
}
if ( type = = IRQ_TYPE_EDGE_BOTH ) {
/*
* The hardware does not support interrupts on both edges . The
* best we can do is to set up a single - edge interrupt and then
* switch to the opposing edge when ACKing the interrupt .
*/
bool high = gpio_get_value ( jzgc , irqd - > hwirq ) ;
type = high ? IRQ_TYPE_EDGE_FALLING : IRQ_TYPE_EDGE_RISING ;
}
irq_set_type ( jzgc , irqd - > hwirq , type ) ;
return 0 ;
}
static int ingenic_gpio_irq_set_wake ( struct irq_data * irqd , unsigned int on )
{
struct gpio_chip * gc = irq_data_get_irq_chip_data ( irqd ) ;
struct ingenic_gpio_chip * jzgc = gpiochip_get_data ( gc ) ;
return irq_set_irq_wake ( jzgc - > irq , on ) ;
}
static void ingenic_gpio_irq_handler ( struct irq_desc * desc )
{
struct gpio_chip * gc = irq_desc_get_handler_data ( desc ) ;
struct ingenic_gpio_chip * jzgc = gpiochip_get_data ( gc ) ;
struct irq_chip * irq_chip = irq_data_get_irq_chip ( & desc - > irq_data ) ;
unsigned long flag , i ;
chained_irq_enter ( irq_chip , desc ) ;
if ( jzgc - > version > = ID_JZ4770 )
flag = gpio_ingenic_read_reg ( jzgc , JZ4770_GPIO_FLAG ) ;
else
flag = gpio_ingenic_read_reg ( jzgc , JZ4740_GPIO_FLAG ) ;
for_each_set_bit ( i , & flag , 32 )
2017-11-07 19:15:47 +01:00
generic_handle_irq ( irq_linear_revmap ( gc - > irq . domain , i ) ) ;
2017-05-12 18:52:57 +02:00
chained_irq_exit ( irq_chip , desc ) ;
}
static void ingenic_gpio_set ( struct gpio_chip * gc ,
unsigned int offset , int value )
{
struct ingenic_gpio_chip * jzgc = gpiochip_get_data ( gc ) ;
gpio_set_value ( jzgc , offset , value ) ;
}
static int ingenic_gpio_get ( struct gpio_chip * gc , unsigned int offset )
{
struct ingenic_gpio_chip * jzgc = gpiochip_get_data ( gc ) ;
return ( int ) gpio_get_value ( jzgc , offset ) ;
}
static int ingenic_gpio_direction_input ( struct gpio_chip * gc ,
unsigned int offset )
{
return pinctrl_gpio_direction_input ( gc - > base + offset ) ;
}
static int ingenic_gpio_direction_output ( struct gpio_chip * gc ,
unsigned int offset , int value )
{
ingenic_gpio_set ( gc , offset , value ) ;
return pinctrl_gpio_direction_output ( gc - > base + offset ) ;
}
static const struct of_device_id ingenic_gpio_of_match [ ] = {
{ . compatible = " ingenic,jz4740-gpio " , . data = ( void * ) ID_JZ4740 } ,
{ . compatible = " ingenic,jz4770-gpio " , . data = ( void * ) ID_JZ4770 } ,
{ . compatible = " ingenic,jz4780-gpio " , . data = ( void * ) ID_JZ4780 } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , ingenic_gpio_of_match ) ;
static int ingenic_gpio_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
const struct of_device_id * of_id = of_match_device (
ingenic_gpio_of_match , dev ) ;
struct ingenic_gpio_chip * jzgc ;
u32 bank ;
int err ;
jzgc = devm_kzalloc ( dev , sizeof ( * jzgc ) , GFP_KERNEL ) ;
if ( ! jzgc )
return - ENOMEM ;
jzgc - > map = dev_get_drvdata ( dev - > parent ) ;
if ( ! jzgc - > map ) {
dev_err ( dev , " Cannot get parent regmap \n " ) ;
return - ENXIO ;
}
err = of_property_read_u32 ( dev - > of_node , " reg " , & bank ) ;
if ( err ) {
dev_err ( dev , " Cannot read \" reg \" property: %i \n " , err ) ;
return err ;
}
jzgc - > reg_base = bank * 0x100 ;
jzgc - > gc . label = devm_kasprintf ( dev , GFP_KERNEL , " GPIO%c " , ' A ' + bank ) ;
if ( ! jzgc - > gc . label )
return - ENOMEM ;
/* DO NOT EXPAND THIS: FOR BACKWARD GPIO NUMBERSPACE COMPATIBIBILITY
* ONLY : WORK TO TRANSITION CONSUMERS TO USE THE GPIO DESCRIPTOR API IN
* < linux / gpio / consumer . h > INSTEAD .
*/
jzgc - > gc . base = bank * 32 ;
jzgc - > gc . ngpio = 32 ;
jzgc - > gc . parent = dev ;
jzgc - > gc . of_node = dev - > of_node ;
jzgc - > gc . owner = THIS_MODULE ;
jzgc - > version = ( enum jz_version ) of_id - > data ;
jzgc - > gc . set = ingenic_gpio_set ;
jzgc - > gc . get = ingenic_gpio_get ;
jzgc - > gc . direction_input = ingenic_gpio_direction_input ;
jzgc - > gc . direction_output = ingenic_gpio_direction_output ;
if ( of_property_read_bool ( dev - > of_node , " gpio-ranges " ) ) {
jzgc - > gc . request = gpiochip_generic_request ;
jzgc - > gc . free = gpiochip_generic_free ;
}
err = devm_gpiochip_add_data ( dev , & jzgc - > gc , jzgc ) ;
if ( err )
return err ;
jzgc - > irq = irq_of_parse_and_map ( dev - > of_node , 0 ) ;
if ( ! jzgc - > irq )
return - EINVAL ;
jzgc - > irq_chip . name = jzgc - > gc . label ;
jzgc - > irq_chip . irq_enable = ingenic_gpio_irq_enable ;
jzgc - > irq_chip . irq_disable = ingenic_gpio_irq_disable ;
jzgc - > irq_chip . irq_unmask = ingenic_gpio_irq_unmask ;
jzgc - > irq_chip . irq_mask = ingenic_gpio_irq_mask ;
jzgc - > irq_chip . irq_ack = ingenic_gpio_irq_ack ;
jzgc - > irq_chip . irq_set_type = ingenic_gpio_irq_set_type ;
jzgc - > irq_chip . irq_set_wake = ingenic_gpio_irq_set_wake ;
jzgc - > irq_chip . flags = IRQCHIP_MASK_ON_SUSPEND ;
err = gpiochip_irqchip_add ( & jzgc - > gc , & jzgc - > irq_chip , 0 ,
handle_level_irq , IRQ_TYPE_NONE ) ;
if ( err )
return err ;
gpiochip_set_chained_irqchip ( & jzgc - > gc , & jzgc - > irq_chip ,
jzgc - > irq , ingenic_gpio_irq_handler ) ;
return 0 ;
}
static int ingenic_gpio_remove ( struct platform_device * pdev )
{
return 0 ;
}
static struct platform_driver ingenic_gpio_driver = {
. driver = {
. name = " gpio-ingenic " ,
. of_match_table = of_match_ptr ( ingenic_gpio_of_match ) ,
} ,
. probe = ingenic_gpio_probe ,
. remove = ingenic_gpio_remove ,
} ;
static int __init ingenic_gpio_drv_register ( void )
{
return platform_driver_register ( & ingenic_gpio_driver ) ;
}
subsys_initcall ( ingenic_gpio_drv_register ) ;
static void __exit ingenic_gpio_drv_unregister ( void )
{
platform_driver_unregister ( & ingenic_gpio_driver ) ;
}
module_exit ( ingenic_gpio_drv_unregister ) ;
MODULE_AUTHOR ( " Paul Cercueil <paul@crapouillou.net> " ) ;
MODULE_DESCRIPTION ( " Ingenic JZ47xx GPIO driver " ) ;
MODULE_LICENSE ( " GPL " ) ;