2011-10-29 01:50:49 +04:00
/*
* regmap based irq_chip
*
* Copyright 2011 Wolfson Microelectronics plc
*
* Author : Mark Brown < broonie @ opensource . wolfsonmicro . com >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation .
*/
# include <linux/export.h>
2012-01-22 20:23:42 +04:00
# include <linux/device.h>
2011-10-29 01:50:49 +04:00
# include <linux/regmap.h>
# include <linux/irq.h>
# include <linux/interrupt.h>
2012-05-13 13:59:56 +04:00
# include <linux/irqdomain.h>
2011-10-29 01:50:49 +04:00
# include <linux/slab.h>
# include "internal.h"
struct regmap_irq_chip_data {
struct mutex lock ;
2012-08-01 21:40:47 +04:00
struct irq_chip irq_chip ;
2011-10-29 01:50:49 +04:00
struct regmap * map ;
2012-06-01 00:01:46 +04:00
const struct regmap_irq_chip * chip ;
2011-10-29 01:50:49 +04:00
int irq_base ;
2012-05-13 13:59:56 +04:00
struct irq_domain * domain ;
2011-10-29 01:50:49 +04:00
2012-06-05 17:34:03 +04:00
int irq ;
int wake_count ;
2011-10-29 01:50:49 +04:00
unsigned int * status_buf ;
unsigned int * mask_buf ;
unsigned int * mask_buf_def ;
2012-06-05 17:34:03 +04:00
unsigned int * wake_buf ;
2012-05-14 17:40:43 +04:00
unsigned int irq_reg_stride ;
2011-10-29 01:50:49 +04:00
} ;
static inline const
struct regmap_irq * irq_to_regmap_irq ( struct regmap_irq_chip_data * data ,
int irq )
{
2012-05-13 13:59:56 +04:00
return & data - > chip - > irqs [ irq ] ;
2011-10-29 01:50:49 +04:00
}
static void regmap_irq_lock ( struct irq_data * data )
{
struct regmap_irq_chip_data * d = irq_data_get_irq_chip_data ( data ) ;
mutex_lock ( & d - > lock ) ;
}
static void regmap_irq_sync_unlock ( struct irq_data * data )
{
struct regmap_irq_chip_data * d = irq_data_get_irq_chip_data ( data ) ;
2012-04-11 09:37:22 +04:00
struct regmap * map = d - > map ;
2011-10-29 01:50:49 +04:00
int i , ret ;
2012-07-27 23:01:54 +04:00
u32 reg ;
2011-10-29 01:50:49 +04:00
/*
* If there ' s been a change in the mask write it back to the
* hardware . We rely on the use of the regmap core cache to
* suppress pointless writes .
*/
for ( i = 0 ; i < d - > chip - > num_regs ; i + + ) {
2012-07-27 23:01:54 +04:00
reg = d - > chip - > mask_base +
( i * map - > reg_stride * d - > irq_reg_stride ) ;
ret = regmap_update_bits ( d - > map , reg ,
2011-10-29 01:50:49 +04:00
d - > mask_buf_def [ i ] , d - > mask_buf [ i ] ) ;
if ( ret ! = 0 )
dev_err ( d - > map - > dev , " Failed to sync masks in %x \n " ,
2012-07-27 23:01:54 +04:00
reg ) ;
2011-10-29 01:50:49 +04:00
}
2012-06-05 17:34:03 +04:00
/* If we've changed our wakeup count propagate it to the parent */
if ( d - > wake_count < 0 )
for ( i = d - > wake_count ; i < 0 ; i + + )
irq_set_irq_wake ( d - > irq , 0 ) ;
else if ( d - > wake_count > 0 )
for ( i = 0 ; i < d - > wake_count ; i + + )
irq_set_irq_wake ( d - > irq , 1 ) ;
d - > wake_count = 0 ;
2011-10-29 01:50:49 +04:00
mutex_unlock ( & d - > lock ) ;
}
static void regmap_irq_enable ( struct irq_data * data )
{
struct regmap_irq_chip_data * d = irq_data_get_irq_chip_data ( data ) ;
2012-04-11 09:37:22 +04:00
struct regmap * map = d - > map ;
2012-05-13 13:59:56 +04:00
const struct regmap_irq * irq_data = irq_to_regmap_irq ( d , data - > hwirq ) ;
2011-10-29 01:50:49 +04:00
2012-04-09 23:40:24 +04:00
d - > mask_buf [ irq_data - > reg_offset / map - > reg_stride ] & = ~ irq_data - > mask ;
2011-10-29 01:50:49 +04:00
}
static void regmap_irq_disable ( struct irq_data * data )
{
struct regmap_irq_chip_data * d = irq_data_get_irq_chip_data ( data ) ;
2012-04-11 09:37:22 +04:00
struct regmap * map = d - > map ;
2012-05-13 13:59:56 +04:00
const struct regmap_irq * irq_data = irq_to_regmap_irq ( d , data - > hwirq ) ;
2011-10-29 01:50:49 +04:00
2012-04-09 23:40:24 +04:00
d - > mask_buf [ irq_data - > reg_offset / map - > reg_stride ] | = irq_data - > mask ;
2011-10-29 01:50:49 +04:00
}
2012-06-05 17:34:03 +04:00
static int regmap_irq_set_wake ( struct irq_data * data , unsigned int on )
{
struct regmap_irq_chip_data * d = irq_data_get_irq_chip_data ( data ) ;
struct regmap * map = d - > map ;
const struct regmap_irq * irq_data = irq_to_regmap_irq ( d , data - > hwirq ) ;
if ( ! d - > chip - > wake_base )
return - EINVAL ;
if ( on ) {
d - > wake_buf [ irq_data - > reg_offset / map - > reg_stride ]
& = ~ irq_data - > mask ;
d - > wake_count + + ;
} else {
d - > wake_buf [ irq_data - > reg_offset / map - > reg_stride ]
| = irq_data - > mask ;
d - > wake_count - - ;
}
return 0 ;
}
2012-08-01 21:40:47 +04:00
static const struct irq_chip regmap_irq_chip = {
2011-10-29 01:50:49 +04:00
. irq_bus_lock = regmap_irq_lock ,
. irq_bus_sync_unlock = regmap_irq_sync_unlock ,
. irq_disable = regmap_irq_disable ,
. irq_enable = regmap_irq_enable ,
2012-06-05 17:34:03 +04:00
. irq_set_wake = regmap_irq_set_wake ,
2011-10-29 01:50:49 +04:00
} ;
static irqreturn_t regmap_irq_thread ( int irq , void * d )
{
struct regmap_irq_chip_data * data = d ;
2012-06-01 00:01:46 +04:00
const struct regmap_irq_chip * chip = data - > chip ;
2011-10-29 01:50:49 +04:00
struct regmap * map = data - > map ;
int ret , i ;
2011-11-28 22:50:39 +04:00
bool handled = false ;
2012-07-27 23:01:54 +04:00
u32 reg ;
2011-10-29 01:50:49 +04:00
/*
* Ignore masked IRQs and ack if we need to ; we ack early so
* there is no race between handling and acknowleding the
* interrupt . We assume that typically few of the interrupts
* will fire simultaneously so don ' t worry about overhead from
* doing a write per register .
*/
for ( i = 0 ; i < data - > chip - > num_regs ; i + + ) {
2012-05-17 16:59:40 +04:00
ret = regmap_read ( map , chip - > status_base + ( i * map - > reg_stride
2012-05-14 17:40:43 +04:00
* data - > irq_reg_stride ) ,
& data - > status_buf [ i ] ) ;
if ( ret ! = 0 ) {
dev_err ( map - > dev , " Failed to read IRQ status: %d \n " ,
ret ) ;
2011-10-29 01:50:49 +04:00
return IRQ_NONE ;
}
data - > status_buf [ i ] & = ~ data - > mask_buf [ i ] ;
if ( data - > status_buf [ i ] & & chip - > ack_base ) {
2012-07-27 23:01:54 +04:00
reg = chip - > ack_base +
( i * map - > reg_stride * data - > irq_reg_stride ) ;
ret = regmap_write ( map , reg , data - > status_buf [ i ] ) ;
2011-10-29 01:50:49 +04:00
if ( ret ! = 0 )
dev_err ( map - > dev , " Failed to ack 0x%x: %d \n " ,
2012-07-27 23:01:54 +04:00
reg , ret ) ;
2011-10-29 01:50:49 +04:00
}
}
for ( i = 0 ; i < chip - > num_irqs ; i + + ) {
2012-04-09 23:40:24 +04:00
if ( data - > status_buf [ chip - > irqs [ i ] . reg_offset /
map - > reg_stride ] & chip - > irqs [ i ] . mask ) {
2012-05-13 13:59:56 +04:00
handle_nested_irq ( irq_find_mapping ( data - > domain , i ) ) ;
2011-11-28 22:50:39 +04:00
handled = true ;
2011-10-29 01:50:49 +04:00
}
}
2011-11-28 22:50:39 +04:00
if ( handled )
return IRQ_HANDLED ;
else
return IRQ_NONE ;
2011-10-29 01:50:49 +04:00
}
2012-05-13 13:59:56 +04:00
static int regmap_irq_map ( struct irq_domain * h , unsigned int virq ,
irq_hw_number_t hw )
{
struct regmap_irq_chip_data * data = h - > host_data ;
irq_set_chip_data ( virq , data ) ;
2012-08-01 21:40:47 +04:00
irq_set_chip_and_handler ( virq , & data - > irq_chip , handle_edge_irq ) ;
2012-05-13 13:59:56 +04:00
irq_set_nested_thread ( virq , 1 ) ;
/* ARM needs us to explicitly flag the IRQ as valid
* and will set them noprobe when we do so . */
# ifdef CONFIG_ARM
set_irq_flags ( virq , IRQF_VALID ) ;
# else
irq_set_noprobe ( virq ) ;
# endif
return 0 ;
}
static struct irq_domain_ops regmap_domain_ops = {
. map = regmap_irq_map ,
. xlate = irq_domain_xlate_twocell ,
} ;
2011-10-29 01:50:49 +04:00
/**
* regmap_add_irq_chip ( ) : Use standard regmap IRQ controller handling
*
* map : The regmap for the device .
* irq : The IRQ the device uses to signal interrupts
* irq_flags : The IRQF_ flags to use for the primary interrupt .
* chip : Configuration for the interrupt controller .
* data : Runtime data structure for the controller , allocated on success
*
* Returns 0 on success or an errno on failure .
*
* In order for this to be efficient the chip really should use a
* register cache . The chip driver is responsible for restoring the
* register values used by the IRQ controller over suspend and resume .
*/
int regmap_add_irq_chip ( struct regmap * map , int irq , int irq_flags ,
2012-06-01 00:01:46 +04:00
int irq_base , const struct regmap_irq_chip * chip ,
2011-10-29 01:50:49 +04:00
struct regmap_irq_chip_data * * data )
{
struct regmap_irq_chip_data * d ;
2012-05-13 13:59:56 +04:00
int i ;
2011-10-29 01:50:49 +04:00
int ret = - ENOMEM ;
2012-07-27 23:01:54 +04:00
u32 reg ;
2011-10-29 01:50:49 +04:00
2012-04-09 23:40:24 +04:00
for ( i = 0 ; i < chip - > num_irqs ; i + + ) {
if ( chip - > irqs [ i ] . reg_offset % map - > reg_stride )
return - EINVAL ;
if ( chip - > irqs [ i ] . reg_offset / map - > reg_stride > =
chip - > num_regs )
return - EINVAL ;
}
2012-05-13 13:59:56 +04:00
if ( irq_base ) {
irq_base = irq_alloc_descs ( irq_base , 0 , chip - > num_irqs , 0 ) ;
if ( irq_base < 0 ) {
dev_warn ( map - > dev , " Failed to allocate IRQs: %d \n " ,
irq_base ) ;
return irq_base ;
}
2011-10-29 01:50:49 +04:00
}
d = kzalloc ( sizeof ( * d ) , GFP_KERNEL ) ;
if ( ! d )
return - ENOMEM ;
2012-05-13 14:18:34 +04:00
* data = d ;
2011-10-29 01:50:49 +04:00
d - > status_buf = kzalloc ( sizeof ( unsigned int ) * chip - > num_regs ,
GFP_KERNEL ) ;
if ( ! d - > status_buf )
goto err_alloc ;
d - > mask_buf = kzalloc ( sizeof ( unsigned int ) * chip - > num_regs ,
GFP_KERNEL ) ;
if ( ! d - > mask_buf )
goto err_alloc ;
d - > mask_buf_def = kzalloc ( sizeof ( unsigned int ) * chip - > num_regs ,
GFP_KERNEL ) ;
if ( ! d - > mask_buf_def )
goto err_alloc ;
2012-06-05 17:34:03 +04:00
if ( chip - > wake_base ) {
d - > wake_buf = kzalloc ( sizeof ( unsigned int ) * chip - > num_regs ,
GFP_KERNEL ) ;
if ( ! d - > wake_buf )
goto err_alloc ;
}
2012-08-01 21:40:47 +04:00
d - > irq_chip = regmap_irq_chip ;
2012-08-01 21:40:48 +04:00
d - > irq_chip . name = chip - > name ;
2012-08-01 21:40:49 +04:00
if ( ! chip - > wake_base ) {
d - > irq_chip . irq_set_wake = NULL ;
d - > irq_chip . flags | = IRQCHIP_MASK_ON_SUSPEND |
IRQCHIP_SKIP_SET_WAKE ;
}
2012-06-05 17:34:03 +04:00
d - > irq = irq ;
2011-10-29 01:50:49 +04:00
d - > map = map ;
d - > chip = chip ;
d - > irq_base = irq_base ;
2012-05-14 17:40:43 +04:00
if ( chip - > irq_reg_stride )
d - > irq_reg_stride = chip - > irq_reg_stride ;
else
d - > irq_reg_stride = 1 ;
2011-10-29 01:50:49 +04:00
mutex_init ( & d - > lock ) ;
for ( i = 0 ; i < chip - > num_irqs ; i + + )
2012-04-09 23:40:24 +04:00
d - > mask_buf_def [ chip - > irqs [ i ] . reg_offset / map - > reg_stride ]
2011-10-29 01:50:49 +04:00
| = chip - > irqs [ i ] . mask ;
/* Mask all the interrupts by default */
for ( i = 0 ; i < chip - > num_regs ; i + + ) {
d - > mask_buf [ i ] = d - > mask_buf_def [ i ] ;
2012-07-27 23:01:54 +04:00
reg = chip - > mask_base +
( i * map - > reg_stride * d - > irq_reg_stride ) ;
2012-08-01 23:29:14 +04:00
ret = regmap_update_bits ( map , reg ,
d - > mask_buf [ i ] , d - > mask_buf [ i ] ) ;
2011-10-29 01:50:49 +04:00
if ( ret ! = 0 ) {
dev_err ( map - > dev , " Failed to set masks in 0x%x: %d \n " ,
2012-07-27 23:01:54 +04:00
reg , ret ) ;
2011-10-29 01:50:49 +04:00
goto err_alloc ;
}
}
2012-08-01 23:57:24 +04:00
/* Wake is disabled by default */
if ( d - > wake_buf ) {
for ( i = 0 ; i < chip - > num_regs ; i + + ) {
d - > wake_buf [ i ] = d - > mask_buf_def [ i ] ;
reg = chip - > wake_base +
( i * map - > reg_stride * d - > irq_reg_stride ) ;
ret = regmap_update_bits ( map , reg , d - > wake_buf [ i ] ,
d - > wake_buf [ i ] ) ;
if ( ret ! = 0 ) {
dev_err ( map - > dev , " Failed to set masks in 0x%x: %d \n " ,
reg , ret ) ;
goto err_alloc ;
}
}
}
2012-05-13 13:59:56 +04:00
if ( irq_base )
d - > domain = irq_domain_add_legacy ( map - > dev - > of_node ,
chip - > num_irqs , irq_base , 0 ,
& regmap_domain_ops , d ) ;
else
d - > domain = irq_domain_add_linear ( map - > dev - > of_node ,
chip - > num_irqs ,
& regmap_domain_ops , d ) ;
if ( ! d - > domain ) {
dev_err ( map - > dev , " Failed to create IRQ domain \n " ) ;
ret = - ENOMEM ;
goto err_alloc ;
2011-10-29 01:50:49 +04:00
}
ret = request_threaded_irq ( irq , NULL , regmap_irq_thread , irq_flags ,
chip - > name , d ) ;
if ( ret ! = 0 ) {
dev_err ( map - > dev , " Failed to request IRQ %d: %d \n " , irq , ret ) ;
2012-05-13 13:59:56 +04:00
goto err_domain ;
2011-10-29 01:50:49 +04:00
}
return 0 ;
2012-05-13 13:59:56 +04:00
err_domain :
/* Should really dispose of the domain but... */
2011-10-29 01:50:49 +04:00
err_alloc :
2012-06-05 17:34:03 +04:00
kfree ( d - > wake_buf ) ;
2011-10-29 01:50:49 +04:00
kfree ( d - > mask_buf_def ) ;
kfree ( d - > mask_buf ) ;
kfree ( d - > status_buf ) ;
kfree ( d ) ;
return ret ;
}
EXPORT_SYMBOL_GPL ( regmap_add_irq_chip ) ;
/**
* regmap_del_irq_chip ( ) : Stop interrupt handling for a regmap IRQ chip
*
* @ irq : Primary IRQ for the device
* @ d : regmap_irq_chip_data allocated by regmap_add_irq_chip ( )
*/
void regmap_del_irq_chip ( int irq , struct regmap_irq_chip_data * d )
{
if ( ! d )
return ;
free_irq ( irq , d ) ;
2012-05-13 13:59:56 +04:00
/* We should unmap the domain but... */
2012-06-05 17:34:03 +04:00
kfree ( d - > wake_buf ) ;
2011-10-29 01:50:49 +04:00
kfree ( d - > mask_buf_def ) ;
kfree ( d - > mask_buf ) ;
kfree ( d - > status_buf ) ;
kfree ( d ) ;
}
EXPORT_SYMBOL_GPL ( regmap_del_irq_chip ) ;
2011-12-05 20:10:15 +04:00
/**
* regmap_irq_chip_get_base ( ) : Retrieve interrupt base for a regmap IRQ chip
*
* Useful for drivers to request their own IRQs .
*
* @ data : regmap_irq controller to operate on .
*/
int regmap_irq_chip_get_base ( struct regmap_irq_chip_data * data )
{
2012-05-13 13:59:56 +04:00
WARN_ON ( ! data - > irq_base ) ;
2011-12-05 20:10:15 +04:00
return data - > irq_base ;
}
EXPORT_SYMBOL_GPL ( regmap_irq_chip_get_base ) ;
2012-05-13 13:59:56 +04:00
/**
* regmap_irq_get_virq ( ) : Map an interrupt on a chip to a virtual IRQ
*
* Useful for drivers to request their own IRQs .
*
* @ data : regmap_irq controller to operate on .
* @ irq : index of the interrupt requested in the chip IRQs
*/
int regmap_irq_get_virq ( struct regmap_irq_chip_data * data , int irq )
{
2012-06-05 17:29:36 +04:00
/* Handle holes in the IRQ list */
if ( ! data - > chip - > irqs [ irq ] . mask )
return - EINVAL ;
2012-05-13 13:59:56 +04:00
return irq_create_mapping ( data - > domain , irq ) ;
}
EXPORT_SYMBOL_GPL ( regmap_irq_get_virq ) ;