2012-04-15 21:40:33 -07:00
/*
2010-10-01 10:40:37 +09:00
* Copyright ( c ) 2010 Samsung Electronics Co . , Ltd .
* Author : Kyungmin Park < kyungmin . park @ samsung . com >
* Author : Joonyoung Shim < jy0922 . shim @ samsung . com >
* Author : Marek Szyprowski < m . szyprowski @ samsung . com >
*
* 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 , or ( at your
* option ) any later version .
*
*/
# include <linux/kernel.h>
# include <linux/interrupt.h>
# include <linux/irq.h>
# include <linux/io.h>
# include <linux/gpio.h>
2011-03-15 21:17:43 +09:00
# include <linux/slab.h>
2010-10-01 10:40:37 +09:00
# include <mach/map.h>
# include <plat/gpio-core.h>
# include <plat/gpio-cfg.h>
2011-08-13 12:55:36 +09:00
# include <asm/mach/irq.h>
2012-09-14 20:24:30 +00:00
# define GPIO_BASE(chip) ((void __iomem *)((unsigned long)((chip)->base) & 0xFFFFF000u))
2010-10-01 10:40:37 +09:00
2011-03-15 21:17:43 +09:00
# define CON_OFFSET 0x700
# define MASK_OFFSET 0x900
# define PEND_OFFSET 0xA00
# define REG_OFFSET(x) ((x) << 2)
2010-10-01 10:40:37 +09:00
2011-03-15 21:17:43 +09:00
struct s5p_gpioint_bank {
struct list_head list ;
int start ;
int nr_groups ;
int irq ;
2011-08-30 20:47:32 +09:00
struct samsung_gpio_chip * * chips ;
2011-03-15 21:17:43 +09:00
void ( * handler ) ( unsigned int , struct irq_desc * ) ;
} ;
2012-01-21 12:00:13 +09:00
static LIST_HEAD ( banks ) ;
2010-10-01 10:40:37 +09:00
2011-05-09 10:08:00 +02:00
static int s5p_gpioint_set_type ( struct irq_data * d , unsigned int type )
2010-10-01 10:40:37 +09:00
{
2011-05-09 10:08:00 +02:00
struct irq_chip_generic * gc = irq_data_get_irq_chip_data ( d ) ;
struct irq_chip_type * ct = gc - > chip_types ;
unsigned int shift = ( d - > irq - gc - > irq_base ) < < 2 ;
2010-10-01 10:40:37 +09:00
switch ( type ) {
case IRQ_TYPE_EDGE_RISING :
2010-10-02 11:48:09 +09:00
type = S5P_IRQ_TYPE_EDGE_RISING ;
2010-10-01 10:40:37 +09:00
break ;
case IRQ_TYPE_EDGE_FALLING :
2010-10-02 11:48:09 +09:00
type = S5P_IRQ_TYPE_EDGE_FALLING ;
2010-10-01 10:40:37 +09:00
break ;
case IRQ_TYPE_EDGE_BOTH :
2010-10-02 11:48:09 +09:00
type = S5P_IRQ_TYPE_EDGE_BOTH ;
2010-10-01 10:40:37 +09:00
break ;
case IRQ_TYPE_LEVEL_HIGH :
2010-10-02 11:48:09 +09:00
type = S5P_IRQ_TYPE_LEVEL_HIGH ;
2010-10-01 10:40:37 +09:00
break ;
case IRQ_TYPE_LEVEL_LOW :
2010-10-02 11:48:09 +09:00
type = S5P_IRQ_TYPE_LEVEL_LOW ;
2010-10-01 10:40:37 +09:00
break ;
case IRQ_TYPE_NONE :
default :
printk ( KERN_WARNING " No irq type \n " ) ;
return - EINVAL ;
}
2011-05-09 10:08:00 +02:00
gc - > type_cache & = ~ ( 0x7 < < shift ) ;
gc - > type_cache | = type < < shift ;
writel ( gc - > type_cache , gc - > reg_base + ct - > regs . type ) ;
2010-10-01 10:40:37 +09:00
return 0 ;
}
static void s5p_gpioint_handler ( unsigned int irq , struct irq_desc * desc )
{
2011-03-24 13:25:22 +01:00
struct s5p_gpioint_bank * bank = irq_get_handler_data ( irq ) ;
2011-03-15 21:17:43 +09:00
int group , pend_offset , mask_offset ;
2010-10-01 10:40:37 +09:00
unsigned int pend , mask ;
2011-08-13 12:55:36 +09:00
struct irq_chip * chip = irq_get_chip ( irq ) ;
chained_irq_enter ( chip , desc ) ;
2011-03-15 21:17:43 +09:00
for ( group = 0 ; group < bank - > nr_groups ; group + + ) {
2011-08-30 20:47:32 +09:00
struct samsung_gpio_chip * chip = bank - > chips [ group ] ;
2011-03-15 21:17:43 +09:00
if ( ! chip )
continue ;
pend_offset = REG_OFFSET ( group ) ;
pend = __raw_readl ( GPIO_BASE ( chip ) + PEND_OFFSET + pend_offset ) ;
2010-10-01 10:40:37 +09:00
if ( ! pend )
continue ;
2011-03-15 21:17:43 +09:00
mask_offset = REG_OFFSET ( group ) ;
mask = __raw_readl ( GPIO_BASE ( chip ) + MASK_OFFSET + mask_offset ) ;
2010-10-01 10:40:37 +09:00
pend & = ~ mask ;
2011-03-15 21:17:43 +09:00
while ( pend ) {
int offset = fls ( pend ) - 1 ;
int real_irq = chip - > irq_base + offset ;
generic_handle_irq ( real_irq ) ;
pend & = ~ BIT ( offset ) ;
2010-10-01 10:40:37 +09:00
}
}
2011-08-13 12:55:36 +09:00
chained_irq_exit ( chip , desc ) ;
2010-10-01 10:40:37 +09:00
}
2011-08-30 20:47:32 +09:00
static __init int s5p_gpioint_add ( struct samsung_gpio_chip * chip )
2010-10-01 10:40:37 +09:00
{
static int used_gpioint_groups = 0 ;
2011-05-09 10:08:00 +02:00
int group = chip - > group ;
2011-09-26 13:16:45 +09:00
struct s5p_gpioint_bank * b , * bank = NULL ;
2011-05-09 10:08:00 +02:00
struct irq_chip_generic * gc ;
struct irq_chip_type * ct ;
2010-10-01 10:40:37 +09:00
if ( used_gpioint_groups > = S5P_GPIOINT_GROUP_COUNT )
return - ENOMEM ;
2011-09-26 13:16:45 +09:00
list_for_each_entry ( b , & banks , list ) {
if ( group > = b - > start & & group < b - > start + b - > nr_groups ) {
bank = b ;
2011-03-15 21:17:43 +09:00
break ;
2011-09-26 13:16:45 +09:00
}
2011-03-15 21:17:43 +09:00
}
if ( ! bank )
return - EINVAL ;
if ( ! bank - > handler ) {
2011-08-30 20:47:32 +09:00
bank - > chips = kzalloc ( sizeof ( struct samsung_gpio_chip * ) *
2011-03-15 21:17:43 +09:00
bank - > nr_groups , GFP_KERNEL ) ;
if ( ! bank - > chips )
return - ENOMEM ;
2011-03-24 13:25:22 +01:00
irq_set_chained_handler ( bank - > irq , s5p_gpioint_handler ) ;
irq_set_handler_data ( bank - > irq , bank ) ;
2011-03-15 21:17:43 +09:00
bank - > handler = s5p_gpioint_handler ;
printk ( KERN_INFO " Registered chained gpio int handler for interrupt %d. \n " ,
bank - > irq ) ;
}
/*
2011-03-30 22:57:33 -03:00
* chained GPIO irq has been successfully registered , allocate new gpio
2011-03-15 21:17:43 +09:00
* int group and assign irq nubmers
*/
2010-10-01 10:40:37 +09:00
chip - > irq_base = S5P_GPIOINT_BASE +
used_gpioint_groups * S5P_GPIOINT_GROUP_SIZE ;
used_gpioint_groups + + ;
2011-03-15 21:17:43 +09:00
bank - > chips [ group - bank - > start ] = chip ;
2011-05-09 10:08:00 +02:00
gc = irq_alloc_generic_chip ( " s5p_gpioint " , 1 , chip - > irq_base ,
2012-09-14 20:24:30 +00:00
GPIO_BASE ( chip ) ,
2011-05-09 10:08:00 +02:00
handle_level_irq ) ;
if ( ! gc )
return - ENOMEM ;
ct = gc - > chip_types ;
2011-07-06 12:41:31 -04:00
ct - > chip . irq_ack = irq_gc_ack_set_bit ;
2011-05-09 10:08:00 +02:00
ct - > chip . irq_mask = irq_gc_mask_set_bit ;
ct - > chip . irq_unmask = irq_gc_mask_clr_bit ;
ct - > chip . irq_set_type = s5p_gpioint_set_type ,
2011-10-21 18:04:54 +09:00
ct - > regs . ack = PEND_OFFSET + REG_OFFSET ( group - bank - > start ) ;
ct - > regs . mask = MASK_OFFSET + REG_OFFSET ( group - bank - > start ) ;
ct - > regs . type = CON_OFFSET + REG_OFFSET ( group - bank - > start ) ;
2011-05-09 10:08:00 +02:00
irq_setup_generic_chip ( gc , IRQ_MSK ( chip - > chip . ngpio ) ,
IRQ_GC_INIT_MASK_CACHE ,
IRQ_NOREQUEST | IRQ_NOPROBE , 0 ) ;
2010-10-01 10:40:37 +09:00
return 0 ;
}
int __init s5p_register_gpio_interrupt ( int pin )
{
2011-08-30 20:47:32 +09:00
struct samsung_gpio_chip * my_chip = samsung_gpiolib_getchip ( pin ) ;
2010-10-01 10:40:37 +09:00
int offset , group ;
int ret ;
if ( ! my_chip )
return - EINVAL ;
offset = pin - my_chip - > chip . base ;
group = my_chip - > group ;
/* check if the group has been already registered */
if ( my_chip - > irq_base )
return my_chip - > irq_base + offset ;
/* register gpio group */
ret = s5p_gpioint_add ( my_chip ) ;
if ( ret = = 0 ) {
2010-10-01 11:24:39 +09:00
my_chip - > chip . to_irq = samsung_gpiolib_to_irq ;
2010-10-01 10:40:37 +09:00
printk ( KERN_INFO " Registered interrupt support for gpio group %d. \n " ,
group ) ;
return my_chip - > irq_base + offset ;
}
return ret ;
}
2011-03-15 21:17:43 +09:00
int __init s5p_register_gpioint_bank ( int chain_irq , int start , int nr_groups )
{
struct s5p_gpioint_bank * bank ;
bank = kzalloc ( sizeof ( * bank ) , GFP_KERNEL ) ;
if ( ! bank )
return - ENOMEM ;
bank - > start = start ;
bank - > nr_groups = nr_groups ;
bank - > irq = chain_irq ;
list_add_tail ( & bank - > list , & banks ) ;
return 0 ;
}