2013-03-13 20:32:13 +09:00
/*
* Renesas R - Car GPIO Support
*
* Copyright ( C ) 2013 Magnus Damm
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*/
# include <linux/err.h>
# include <linux/gpio.h>
# include <linux/init.h>
# include <linux/interrupt.h>
# include <linux/io.h>
# include <linux/ioport.h>
# include <linux/irq.h>
# include <linux/irqdomain.h>
# include <linux/module.h>
2013-10-16 15:35:02 +05:30
# include <linux/of.h>
2013-03-10 03:27:00 +01:00
# include <linux/pinctrl/consumer.h>
2013-03-13 20:32:13 +09:00
# include <linux/platform_data/gpio-rcar.h>
# include <linux/platform_device.h>
2014-04-14 20:33:13 +02:00
# include <linux/pm_runtime.h>
2013-03-13 20:32:13 +09:00
# include <linux/spinlock.h>
# include <linux/slab.h>
struct gpio_rcar_priv {
void __iomem * base ;
spinlock_t lock ;
struct gpio_rcar_config config ;
struct platform_device * pdev ;
struct gpio_chip gpio_chip ;
struct irq_chip irq_chip ;
struct irq_domain * irq_domain ;
} ;
# define IOINTSEL 0x00
# define INOUTSEL 0x04
# define OUTDT 0x08
# define INDT 0x0c
# define INTDT 0x10
# define INTCLR 0x14
# define INTMSK 0x18
# define MSKCLR 0x1c
# define POSNEG 0x20
# define EDGLEVEL 0x24
# define FILONOFF 0x28
2013-05-24 18:47:24 +09:00
# define BOTHEDGE 0x4c
2013-03-13 20:32:13 +09:00
2013-05-21 13:40:06 +02:00
# define RCAR_MAX_GPIO_PER_BANK 32
2013-03-13 20:32:13 +09:00
static inline u32 gpio_rcar_read ( struct gpio_rcar_priv * p , int offs )
{
return ioread32 ( p - > base + offs ) ;
}
static inline void gpio_rcar_write ( struct gpio_rcar_priv * p , int offs ,
u32 value )
{
iowrite32 ( value , p - > base + offs ) ;
}
static void gpio_rcar_modify_bit ( struct gpio_rcar_priv * p , int offs ,
int bit , bool value )
{
u32 tmp = gpio_rcar_read ( p , offs ) ;
if ( value )
tmp | = BIT ( bit ) ;
else
tmp & = ~ BIT ( bit ) ;
gpio_rcar_write ( p , offs , tmp ) ;
}
static void gpio_rcar_irq_disable ( struct irq_data * d )
{
struct gpio_rcar_priv * p = irq_data_get_irq_chip_data ( d ) ;
gpio_rcar_write ( p , INTMSK , ~ BIT ( irqd_to_hwirq ( d ) ) ) ;
}
static void gpio_rcar_irq_enable ( struct irq_data * d )
{
struct gpio_rcar_priv * p = irq_data_get_irq_chip_data ( d ) ;
gpio_rcar_write ( p , MSKCLR , BIT ( irqd_to_hwirq ( d ) ) ) ;
}
static void gpio_rcar_config_interrupt_input_mode ( struct gpio_rcar_priv * p ,
unsigned int hwirq ,
bool active_high_rising_edge ,
2013-05-24 18:47:24 +09:00
bool level_trigger ,
bool both )
2013-03-13 20:32:13 +09:00
{
unsigned long flags ;
/* follow steps in the GPIO documentation for
* " Setting Edge-Sensitive Interrupt Input Mode " and
* " Setting Level-Sensitive Interrupt Input Mode "
*/
spin_lock_irqsave ( & p - > lock , flags ) ;
/* Configure postive or negative logic in POSNEG */
gpio_rcar_modify_bit ( p , POSNEG , hwirq , ! active_high_rising_edge ) ;
/* Configure edge or level trigger in EDGLEVEL */
gpio_rcar_modify_bit ( p , EDGLEVEL , hwirq , ! level_trigger ) ;
2013-05-24 18:47:24 +09:00
/* Select one edge or both edges in BOTHEDGE */
if ( p - > config . has_both_edge_trigger )
gpio_rcar_modify_bit ( p , BOTHEDGE , hwirq , both ) ;
2013-03-13 20:32:13 +09:00
/* Select "Interrupt Input Mode" in IOINTSEL */
gpio_rcar_modify_bit ( p , IOINTSEL , hwirq , true ) ;
/* Write INTCLR in case of edge trigger */
if ( ! level_trigger )
gpio_rcar_write ( p , INTCLR , BIT ( hwirq ) ) ;
spin_unlock_irqrestore ( & p - > lock , flags ) ;
}
static int gpio_rcar_irq_set_type ( struct irq_data * d , unsigned int type )
{
struct gpio_rcar_priv * p = irq_data_get_irq_chip_data ( d ) ;
unsigned int hwirq = irqd_to_hwirq ( d ) ;
dev_dbg ( & p - > pdev - > dev , " sense irq = %d, type = %d \n " , hwirq , type ) ;
switch ( type & IRQ_TYPE_SENSE_MASK ) {
case IRQ_TYPE_LEVEL_HIGH :
2013-05-24 18:47:24 +09:00
gpio_rcar_config_interrupt_input_mode ( p , hwirq , true , true ,
false ) ;
2013-03-13 20:32:13 +09:00
break ;
case IRQ_TYPE_LEVEL_LOW :
2013-05-24 18:47:24 +09:00
gpio_rcar_config_interrupt_input_mode ( p , hwirq , false , true ,
false ) ;
2013-03-13 20:32:13 +09:00
break ;
case IRQ_TYPE_EDGE_RISING :
2013-05-24 18:47:24 +09:00
gpio_rcar_config_interrupt_input_mode ( p , hwirq , true , false ,
false ) ;
2013-03-13 20:32:13 +09:00
break ;
case IRQ_TYPE_EDGE_FALLING :
2013-05-24 18:47:24 +09:00
gpio_rcar_config_interrupt_input_mode ( p , hwirq , false , false ,
false ) ;
break ;
case IRQ_TYPE_EDGE_BOTH :
if ( ! p - > config . has_both_edge_trigger )
return - EINVAL ;
gpio_rcar_config_interrupt_input_mode ( p , hwirq , true , false ,
true ) ;
2013-03-13 20:32:13 +09:00
break ;
default :
return - EINVAL ;
}
return 0 ;
}
static irqreturn_t gpio_rcar_irq_handler ( int irq , void * dev_id )
{
struct gpio_rcar_priv * p = dev_id ;
u32 pending ;
unsigned int offset , irqs_handled = 0 ;
2013-11-29 22:04:09 +04:00
while ( ( pending = gpio_rcar_read ( p , INTDT ) &
gpio_rcar_read ( p , INTMSK ) ) ) {
2013-03-13 20:32:13 +09:00
offset = __ffs ( pending ) ;
gpio_rcar_write ( p , INTCLR , BIT ( offset ) ) ;
generic_handle_irq ( irq_find_mapping ( p - > irq_domain , offset ) ) ;
irqs_handled + + ;
}
return irqs_handled ? IRQ_HANDLED : IRQ_NONE ;
}
static inline struct gpio_rcar_priv * gpio_to_priv ( struct gpio_chip * chip )
{
return container_of ( chip , struct gpio_rcar_priv , gpio_chip ) ;
}
static void gpio_rcar_config_general_input_output_mode ( struct gpio_chip * chip ,
unsigned int gpio ,
bool output )
{
struct gpio_rcar_priv * p = gpio_to_priv ( chip ) ;
unsigned long flags ;
/* follow steps in the GPIO documentation for
* " Setting General Output Mode " and
* " Setting General Input Mode "
*/
spin_lock_irqsave ( & p - > lock , flags ) ;
/* Configure postive logic in POSNEG */
gpio_rcar_modify_bit ( p , POSNEG , gpio , false ) ;
/* Select "General Input/Output Mode" in IOINTSEL */
gpio_rcar_modify_bit ( p , IOINTSEL , gpio , false ) ;
/* Select Input Mode or Output Mode in INOUTSEL */
gpio_rcar_modify_bit ( p , INOUTSEL , gpio , output ) ;
spin_unlock_irqrestore ( & p - > lock , flags ) ;
}
2013-03-10 03:27:00 +01:00
static int gpio_rcar_request ( struct gpio_chip * chip , unsigned offset )
{
return pinctrl_request_gpio ( chip - > base + offset ) ;
}
static void gpio_rcar_free ( struct gpio_chip * chip , unsigned offset )
{
pinctrl_free_gpio ( chip - > base + offset ) ;
/* Set the GPIO as an input to ensure that the next GPIO request won't
* drive the GPIO pin as an output .
*/
gpio_rcar_config_general_input_output_mode ( chip , offset , false ) ;
}
2013-03-13 20:32:13 +09:00
static int gpio_rcar_direction_input ( struct gpio_chip * chip , unsigned offset )
{
gpio_rcar_config_general_input_output_mode ( chip , offset , false ) ;
return 0 ;
}
static int gpio_rcar_get ( struct gpio_chip * chip , unsigned offset )
{
2013-06-17 08:41:52 +09:00
u32 bit = BIT ( offset ) ;
/* testing on r8a7790 shows that INDT does not show correct pin state
* when configured as output , so use OUTDT in case of output pins */
if ( gpio_rcar_read ( gpio_to_priv ( chip ) , INOUTSEL ) & bit )
2014-06-24 04:19:50 +02:00
return ! ! ( gpio_rcar_read ( gpio_to_priv ( chip ) , OUTDT ) & bit ) ;
2013-06-17 08:41:52 +09:00
else
2014-06-24 04:19:50 +02:00
return ! ! ( gpio_rcar_read ( gpio_to_priv ( chip ) , INDT ) & bit ) ;
2013-03-13 20:32:13 +09:00
}
static void gpio_rcar_set ( struct gpio_chip * chip , unsigned offset , int value )
{
struct gpio_rcar_priv * p = gpio_to_priv ( chip ) ;
unsigned long flags ;
spin_lock_irqsave ( & p - > lock , flags ) ;
gpio_rcar_modify_bit ( p , OUTDT , offset , value ) ;
spin_unlock_irqrestore ( & p - > lock , flags ) ;
}
static int gpio_rcar_direction_output ( struct gpio_chip * chip , unsigned offset ,
int value )
{
/* write GPIO value to output before selecting output mode of pin */
gpio_rcar_set ( chip , offset , value ) ;
gpio_rcar_config_general_input_output_mode ( chip , offset , true ) ;
return 0 ;
}
static int gpio_rcar_to_irq ( struct gpio_chip * chip , unsigned offset )
{
return irq_create_mapping ( gpio_to_priv ( chip ) - > irq_domain , offset ) ;
}
2013-10-11 19:43:39 +02:00
static int gpio_rcar_irq_domain_map ( struct irq_domain * h , unsigned int irq ,
irq_hw_number_t hwirq )
2013-03-13 20:32:13 +09:00
{
struct gpio_rcar_priv * p = h - > host_data ;
2013-10-11 19:43:39 +02:00
dev_dbg ( & p - > pdev - > dev , " map hw irq = %d, irq = %d \n " , ( int ) hwirq , irq ) ;
2013-03-13 20:32:13 +09:00
2013-10-11 19:43:39 +02:00
irq_set_chip_data ( irq , h - > host_data ) ;
irq_set_chip_and_handler ( irq , & p - > irq_chip , handle_level_irq ) ;
set_irq_flags ( irq , IRQF_VALID ) ; /* kill me now */
2013-03-13 20:32:13 +09:00
return 0 ;
}
static struct irq_domain_ops gpio_rcar_irq_domain_ops = {
. map = gpio_rcar_irq_domain_map ,
2014-07-08 12:46:46 +02:00
. xlate = irq_domain_xlate_twocell ,
2013-03-13 20:32:13 +09:00
} ;
2013-11-29 14:48:00 +01:00
struct gpio_rcar_info {
bool has_both_edge_trigger ;
} ;
static const struct of_device_id gpio_rcar_of_table [ ] = {
{
. compatible = " renesas,gpio-r8a7790 " ,
. data = ( void * ) & ( const struct gpio_rcar_info ) {
. has_both_edge_trigger = true ,
} ,
} , {
. compatible = " renesas,gpio-r8a7791 " ,
. data = ( void * ) & ( const struct gpio_rcar_info ) {
. has_both_edge_trigger = true ,
} ,
} , {
. compatible = " renesas,gpio-rcar " ,
. data = ( void * ) & ( const struct gpio_rcar_info ) {
. has_both_edge_trigger = false ,
} ,
} , {
/* Terminator */
} ,
} ;
MODULE_DEVICE_TABLE ( of , gpio_rcar_of_table ) ;
static int gpio_rcar_parse_pdata ( struct gpio_rcar_priv * p )
2013-05-21 13:40:06 +02:00
{
2013-07-30 17:08:05 +09:00
struct gpio_rcar_config * pdata = dev_get_platdata ( & p - > pdev - > dev ) ;
2013-05-21 13:40:06 +02:00
struct device_node * np = p - > pdev - > dev . of_node ;
struct of_phandle_args args ;
int ret ;
2013-06-18 12:29:49 +02:00
if ( pdata ) {
2013-05-21 13:40:06 +02:00
p - > config = * pdata ;
2013-06-18 12:29:49 +02:00
} else if ( IS_ENABLED ( CONFIG_OF ) & & np ) {
2013-11-29 14:48:00 +01:00
const struct of_device_id * match ;
const struct gpio_rcar_info * info ;
match = of_match_node ( gpio_rcar_of_table , np ) ;
if ( ! match )
return - EINVAL ;
info = match - > data ;
2013-09-11 15:51:01 +02:00
ret = of_parse_phandle_with_fixed_args ( np , " gpio-ranges " , 3 , 0 ,
& args ) ;
p - > config . number_of_pins = ret = = 0 ? args . args [ 2 ]
2013-05-21 13:40:06 +02:00
: RCAR_MAX_GPIO_PER_BANK ;
p - > config . gpio_base = - 1 ;
2013-11-29 14:48:00 +01:00
p - > config . has_both_edge_trigger = info - > has_both_edge_trigger ;
2013-05-21 13:40:06 +02:00
}
if ( p - > config . number_of_pins = = 0 | |
p - > config . number_of_pins > RCAR_MAX_GPIO_PER_BANK ) {
dev_warn ( & p - > pdev - > dev ,
" Invalid number of gpio lines %u, using %u \n " ,
p - > config . number_of_pins , RCAR_MAX_GPIO_PER_BANK ) ;
p - > config . number_of_pins = RCAR_MAX_GPIO_PER_BANK ;
}
2013-11-29 14:48:00 +01:00
return 0 ;
2013-05-21 13:40:06 +02:00
}
2013-03-13 20:32:13 +09:00
static int gpio_rcar_probe ( struct platform_device * pdev )
{
struct gpio_rcar_priv * p ;
struct resource * io , * irq ;
struct gpio_chip * gpio_chip ;
struct irq_chip * irq_chip ;
2014-03-27 21:47:36 +01:00
struct device * dev = & pdev - > dev ;
const char * name = dev_name ( dev ) ;
2013-03-13 20:32:13 +09:00
int ret ;
2014-03-27 21:47:36 +01:00
p = devm_kzalloc ( dev , sizeof ( * p ) , GFP_KERNEL ) ;
2013-03-13 20:32:13 +09:00
if ( ! p ) {
ret = - ENOMEM ;
goto err0 ;
}
p - > pdev = pdev ;
spin_lock_init ( & p - > lock ) ;
2013-05-21 13:40:06 +02:00
/* Get device configuration from DT node or platform data. */
2013-11-29 14:48:00 +01:00
ret = gpio_rcar_parse_pdata ( p ) ;
if ( ret < 0 )
return ret ;
2013-05-21 13:40:06 +02:00
platform_set_drvdata ( pdev , p ) ;
2014-04-14 20:33:13 +02:00
pm_runtime_enable ( dev ) ;
pm_runtime_get_sync ( dev ) ;
2013-03-13 20:32:13 +09:00
io = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
irq = platform_get_resource ( pdev , IORESOURCE_IRQ , 0 ) ;
if ( ! io | | ! irq ) {
2014-03-27 21:47:36 +01:00
dev_err ( dev , " missing IRQ or IOMEM \n " ) ;
2013-03-13 20:32:13 +09:00
ret = - EINVAL ;
goto err0 ;
}
2014-03-27 21:47:36 +01:00
p - > base = devm_ioremap_nocache ( dev , io - > start , resource_size ( io ) ) ;
2013-03-13 20:32:13 +09:00
if ( ! p - > base ) {
2014-03-27 21:47:36 +01:00
dev_err ( dev , " failed to remap I/O memory \n " ) ;
2013-03-13 20:32:13 +09:00
ret = - ENXIO ;
goto err0 ;
}
gpio_chip = & p - > gpio_chip ;
2013-03-10 03:27:00 +01:00
gpio_chip - > request = gpio_rcar_request ;
gpio_chip - > free = gpio_rcar_free ;
2013-03-13 20:32:13 +09:00
gpio_chip - > direction_input = gpio_rcar_direction_input ;
gpio_chip - > get = gpio_rcar_get ;
gpio_chip - > direction_output = gpio_rcar_direction_output ;
gpio_chip - > set = gpio_rcar_set ;
gpio_chip - > to_irq = gpio_rcar_to_irq ;
gpio_chip - > label = name ;
2014-03-27 21:47:36 +01:00
gpio_chip - > dev = dev ;
2013-03-13 20:32:13 +09:00
gpio_chip - > owner = THIS_MODULE ;
gpio_chip - > base = p - > config . gpio_base ;
gpio_chip - > ngpio = p - > config . number_of_pins ;
irq_chip = & p - > irq_chip ;
irq_chip - > name = name ;
irq_chip - > irq_mask = gpio_rcar_irq_disable ;
irq_chip - > irq_unmask = gpio_rcar_irq_enable ;
irq_chip - > irq_set_type = gpio_rcar_irq_set_type ;
2013-11-20 09:23:17 +09:00
irq_chip - > flags = IRQCHIP_SKIP_SET_WAKE | IRQCHIP_SET_TYPE_MASKED
| IRQCHIP_MASK_ON_SUSPEND ;
2013-03-13 20:32:13 +09:00
p - > irq_domain = irq_domain_add_simple ( pdev - > dev . of_node ,
p - > config . number_of_pins ,
p - > config . irq_base ,
& gpio_rcar_irq_domain_ops , p ) ;
if ( ! p - > irq_domain ) {
ret = - ENXIO ;
2014-03-27 21:47:36 +01:00
dev_err ( dev , " cannot initialize irq domain \n " ) ;
2013-11-07 10:56:51 +03:00
goto err0 ;
2013-03-13 20:32:13 +09:00
}
2014-03-27 21:47:36 +01:00
if ( devm_request_irq ( dev , irq - > start , gpio_rcar_irq_handler ,
IRQF_SHARED , name , p ) ) {
dev_err ( dev , " failed to request IRQ \n " ) ;
2013-03-13 20:32:13 +09:00
ret = - ENOENT ;
goto err1 ;
}
ret = gpiochip_add ( gpio_chip ) ;
if ( ret ) {
2014-03-27 21:47:36 +01:00
dev_err ( dev , " failed to add GPIO controller \n " ) ;
2013-03-13 20:32:13 +09:00
goto err1 ;
}
2014-03-27 21:47:36 +01:00
dev_info ( dev , " driving %d GPIOs \n " , p - > config . number_of_pins ) ;
2013-03-13 20:32:13 +09:00
/* warn in case of mismatch if irq base is specified */
if ( p - > config . irq_base ) {
ret = irq_find_mapping ( p - > irq_domain , 0 ) ;
if ( p - > config . irq_base ! = ret )
2014-03-27 21:47:36 +01:00
dev_warn ( dev , " irq base mismatch (%u/%u) \n " ,
2013-03-13 20:32:13 +09:00
p - > config . irq_base , ret ) ;
}
2013-05-21 13:40:06 +02:00
if ( p - > config . pctl_name ) {
ret = gpiochip_add_pin_range ( gpio_chip , p - > config . pctl_name , 0 ,
gpio_chip - > base , gpio_chip - > ngpio ) ;
if ( ret < 0 )
2014-03-27 21:47:36 +01:00
dev_warn ( dev , " failed to add pin range \n " ) ;
2013-05-21 13:40:06 +02:00
}
2013-03-10 03:27:00 +01:00
2013-03-13 20:32:13 +09:00
return 0 ;
err1 :
irq_domain_remove ( p - > irq_domain ) ;
err0 :
2014-04-14 20:33:13 +02:00
pm_runtime_put ( dev ) ;
pm_runtime_disable ( dev ) ;
2013-03-13 20:32:13 +09:00
return ret ;
}
static int gpio_rcar_remove ( struct platform_device * pdev )
{
struct gpio_rcar_priv * p = platform_get_drvdata ( pdev ) ;
2014-07-12 22:30:12 +02:00
gpiochip_remove ( & p - > gpio_chip ) ;
2013-03-13 20:32:13 +09:00
irq_domain_remove ( p - > irq_domain ) ;
2014-04-14 20:33:13 +02:00
pm_runtime_put ( & pdev - > dev ) ;
pm_runtime_disable ( & pdev - > dev ) ;
2013-03-13 20:32:13 +09:00
return 0 ;
}
static struct platform_driver gpio_rcar_device_driver = {
. probe = gpio_rcar_probe ,
. remove = gpio_rcar_remove ,
. driver = {
. name = " gpio_rcar " ,
2013-05-21 13:40:06 +02:00
. of_match_table = of_match_ptr ( gpio_rcar_of_table ) ,
2013-03-13 20:32:13 +09:00
}
} ;
module_platform_driver ( gpio_rcar_device_driver ) ;
MODULE_AUTHOR ( " Magnus Damm " ) ;
MODULE_DESCRIPTION ( " Renesas R-Car GPIO Driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;