2010-03-26 16:49:15 +00:00
/*
* wm8994 - irq . c - - Interrupt controller support for Wolfson WM8994
*
* Copyright 2010 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 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/module.h>
# include <linux/i2c.h>
# include <linux/irq.h>
# include <linux/mfd/core.h>
# include <linux/interrupt.h>
# include <linux/mfd/wm8994/core.h>
# include <linux/mfd/wm8994/registers.h>
# include <linux/delay.h>
struct wm8994_irq_data {
int reg ;
int mask ;
} ;
static struct wm8994_irq_data wm8994_irqs [ ] = {
[ WM8994_IRQ_TEMP_SHUT ] = {
. reg = 2 ,
. mask = WM8994_TEMP_SHUT_EINT ,
} ,
[ WM8994_IRQ_MIC1_DET ] = {
. reg = 2 ,
. mask = WM8994_MIC1_DET_EINT ,
} ,
[ WM8994_IRQ_MIC1_SHRT ] = {
. reg = 2 ,
. mask = WM8994_MIC1_SHRT_EINT ,
} ,
[ WM8994_IRQ_MIC2_DET ] = {
. reg = 2 ,
. mask = WM8994_MIC2_DET_EINT ,
} ,
[ WM8994_IRQ_MIC2_SHRT ] = {
. reg = 2 ,
. mask = WM8994_MIC2_SHRT_EINT ,
} ,
[ WM8994_IRQ_FLL1_LOCK ] = {
. reg = 2 ,
. mask = WM8994_FLL1_LOCK_EINT ,
} ,
[ WM8994_IRQ_FLL2_LOCK ] = {
. reg = 2 ,
. mask = WM8994_FLL2_LOCK_EINT ,
} ,
[ WM8994_IRQ_SRC1_LOCK ] = {
. reg = 2 ,
. mask = WM8994_SRC1_LOCK_EINT ,
} ,
[ WM8994_IRQ_SRC2_LOCK ] = {
. reg = 2 ,
. mask = WM8994_SRC2_LOCK_EINT ,
} ,
[ WM8994_IRQ_AIF1DRC1_SIG_DET ] = {
. reg = 2 ,
. mask = WM8994_AIF1DRC1_SIG_DET ,
} ,
[ WM8994_IRQ_AIF1DRC2_SIG_DET ] = {
. reg = 2 ,
. mask = WM8994_AIF1DRC2_SIG_DET_EINT ,
} ,
[ WM8994_IRQ_AIF2DRC_SIG_DET ] = {
. reg = 2 ,
. mask = WM8994_AIF2DRC_SIG_DET_EINT ,
} ,
[ WM8994_IRQ_FIFOS_ERR ] = {
. reg = 2 ,
. mask = WM8994_FIFOS_ERR_EINT ,
} ,
[ WM8994_IRQ_WSEQ_DONE ] = {
. reg = 2 ,
. mask = WM8994_WSEQ_DONE_EINT ,
} ,
[ WM8994_IRQ_DCS_DONE ] = {
. reg = 2 ,
. mask = WM8994_DCS_DONE_EINT ,
} ,
[ WM8994_IRQ_TEMP_WARN ] = {
. reg = 2 ,
. mask = WM8994_TEMP_WARN_EINT ,
} ,
[ WM8994_IRQ_GPIO ( 1 ) ] = {
. reg = 1 ,
. mask = WM8994_GP1_EINT ,
} ,
[ WM8994_IRQ_GPIO ( 2 ) ] = {
. reg = 1 ,
. mask = WM8994_GP2_EINT ,
} ,
[ WM8994_IRQ_GPIO ( 3 ) ] = {
. reg = 1 ,
. mask = WM8994_GP3_EINT ,
} ,
[ WM8994_IRQ_GPIO ( 4 ) ] = {
. reg = 1 ,
. mask = WM8994_GP4_EINT ,
} ,
[ WM8994_IRQ_GPIO ( 5 ) ] = {
. reg = 1 ,
. mask = WM8994_GP5_EINT ,
} ,
[ WM8994_IRQ_GPIO ( 6 ) ] = {
. reg = 1 ,
. mask = WM8994_GP6_EINT ,
} ,
[ WM8994_IRQ_GPIO ( 7 ) ] = {
. reg = 1 ,
. mask = WM8994_GP7_EINT ,
} ,
[ WM8994_IRQ_GPIO ( 8 ) ] = {
. reg = 1 ,
. mask = WM8994_GP8_EINT ,
} ,
[ WM8994_IRQ_GPIO ( 9 ) ] = {
. reg = 1 ,
. mask = WM8994_GP8_EINT ,
} ,
[ WM8994_IRQ_GPIO ( 10 ) ] = {
. reg = 1 ,
. mask = WM8994_GP10_EINT ,
} ,
[ WM8994_IRQ_GPIO ( 11 ) ] = {
. reg = 1 ,
. mask = WM8994_GP11_EINT ,
} ,
} ;
static inline int irq_data_to_status_reg ( struct wm8994_irq_data * irq_data )
{
return WM8994_INTERRUPT_STATUS_1 - 1 + irq_data - > reg ;
}
static inline int irq_data_to_mask_reg ( struct wm8994_irq_data * irq_data )
{
return WM8994_INTERRUPT_STATUS_1_MASK - 1 + irq_data - > reg ;
}
static inline struct wm8994_irq_data * irq_to_wm8994_irq ( struct wm8994 * wm8994 ,
int irq )
{
return & wm8994_irqs [ irq - wm8994 - > irq_base ] ;
}
2010-11-24 18:01:44 +00:00
static void wm8994_irq_lock ( struct irq_data * data )
2010-03-26 16:49:15 +00:00
{
2010-12-11 13:21:21 +00:00
struct wm8994 * wm8994 = irq_data_get_irq_chip_data ( data ) ;
2010-03-26 16:49:15 +00:00
mutex_lock ( & wm8994 - > irq_lock ) ;
}
2010-11-24 18:01:44 +00:00
static void wm8994_irq_sync_unlock ( struct irq_data * data )
2010-03-26 16:49:15 +00:00
{
2010-12-11 13:21:21 +00:00
struct wm8994 * wm8994 = irq_data_get_irq_chip_data ( data ) ;
2010-03-26 16:49:15 +00:00
int i ;
for ( i = 0 ; i < ARRAY_SIZE ( wm8994 - > irq_masks_cur ) ; i + + ) {
/* If there's been a change in the mask write it back
* to the hardware . */
if ( wm8994 - > irq_masks_cur [ i ] ! = wm8994 - > irq_masks_cache [ i ] ) {
wm8994 - > irq_masks_cache [ i ] = wm8994 - > irq_masks_cur [ i ] ;
wm8994_reg_write ( wm8994 ,
WM8994_INTERRUPT_STATUS_1_MASK + i ,
wm8994 - > irq_masks_cur [ i ] ) ;
}
}
mutex_unlock ( & wm8994 - > irq_lock ) ;
}
2011-03-01 20:12:45 +00:00
static void wm8994_irq_enable ( struct irq_data * data )
2010-03-26 16:49:15 +00:00
{
2010-12-11 13:21:21 +00:00
struct wm8994 * wm8994 = irq_data_get_irq_chip_data ( data ) ;
2010-11-24 18:01:44 +00:00
struct wm8994_irq_data * irq_data = irq_to_wm8994_irq ( wm8994 ,
data - > irq ) ;
2010-03-26 16:49:15 +00:00
wm8994 - > irq_masks_cur [ irq_data - > reg - 1 ] & = ~ irq_data - > mask ;
}
2011-03-01 20:12:45 +00:00
static void wm8994_irq_disable ( struct irq_data * data )
2010-03-26 16:49:15 +00:00
{
2010-12-11 13:21:21 +00:00
struct wm8994 * wm8994 = irq_data_get_irq_chip_data ( data ) ;
2010-11-24 18:01:44 +00:00
struct wm8994_irq_data * irq_data = irq_to_wm8994_irq ( wm8994 ,
data - > irq ) ;
2010-03-26 16:49:15 +00:00
wm8994 - > irq_masks_cur [ irq_data - > reg - 1 ] | = irq_data - > mask ;
}
static struct irq_chip wm8994_irq_chip = {
2010-11-24 18:01:44 +00:00
. name = " wm8994 " ,
. irq_bus_lock = wm8994_irq_lock ,
. irq_bus_sync_unlock = wm8994_irq_sync_unlock ,
2011-03-01 20:12:45 +00:00
. irq_disable = wm8994_irq_disable ,
. irq_enable = wm8994_irq_enable ,
2010-03-26 16:49:15 +00:00
} ;
/* The processing of the primary interrupt occurs in a thread so that
* we can interact with the device over I2C or SPI . */
static irqreturn_t wm8994_irq_thread ( int irq , void * data )
{
struct wm8994 * wm8994 = data ;
unsigned int i ;
u16 status [ WM8994_NUM_IRQ_REGS ] ;
int ret ;
ret = wm8994_bulk_read ( wm8994 , WM8994_INTERRUPT_STATUS_1 ,
WM8994_NUM_IRQ_REGS , status ) ;
if ( ret < 0 ) {
dev_err ( wm8994 - > dev , " Failed to read interrupt status: %d \n " ,
ret ) ;
return IRQ_NONE ;
}
2011-03-18 12:50:10 +00:00
/* Bit swap and apply masking */
for ( i = 0 ; i < WM8994_NUM_IRQ_REGS ; i + + ) {
status [ i ] = be16_to_cpu ( status [ i ] ) ;
2010-03-26 16:49:15 +00:00
status [ i ] & = ~ wm8994 - > irq_masks_cur [ i ] ;
2011-03-18 12:50:10 +00:00
}
2010-03-26 16:49:15 +00:00
/* Ack any unmasked IRQs */
for ( i = 0 ; i < ARRAY_SIZE ( status ) ; i + + ) {
if ( status [ i ] )
wm8994_reg_write ( wm8994 , WM8994_INTERRUPT_STATUS_1 + i ,
status [ i ] ) ;
}
2011-07-21 10:09:24 +01:00
/* Report */
for ( i = 0 ; i < ARRAY_SIZE ( wm8994_irqs ) ; i + + ) {
if ( status [ wm8994_irqs [ i ] . reg - 1 ] & wm8994_irqs [ i ] . mask )
handle_nested_irq ( wm8994 - > irq_base + i ) ;
}
2010-03-26 16:49:15 +00:00
return IRQ_HANDLED ;
}
int wm8994_irq_init ( struct wm8994 * wm8994 )
{
int i , cur_irq , ret ;
mutex_init ( & wm8994 - > irq_lock ) ;
/* Mask the individual interrupt sources */
for ( i = 0 ; i < ARRAY_SIZE ( wm8994 - > irq_masks_cur ) ; i + + ) {
wm8994 - > irq_masks_cur [ i ] = 0xffff ;
wm8994 - > irq_masks_cache [ i ] = 0xffff ;
wm8994_reg_write ( wm8994 , WM8994_INTERRUPT_STATUS_1_MASK + i ,
0xffff ) ;
}
if ( ! wm8994 - > irq ) {
dev_warn ( wm8994 - > dev ,
" No interrupt specified, no interrupts \n " ) ;
wm8994 - > irq_base = 0 ;
return 0 ;
}
if ( ! wm8994 - > irq_base ) {
dev_err ( wm8994 - > dev ,
" No interrupt base specified, no interrupts \n " ) ;
return 0 ;
}
/* Register them with genirq */
for ( cur_irq = wm8994 - > irq_base ;
cur_irq < ARRAY_SIZE ( wm8994_irqs ) + wm8994 - > irq_base ;
cur_irq + + ) {
2011-03-25 11:12:32 +00:00
irq_set_chip_data ( cur_irq , wm8994 ) ;
irq_set_chip_and_handler ( cur_irq , & wm8994_irq_chip ,
2010-03-26 16:49:15 +00:00
handle_edge_irq ) ;
2011-03-25 11:12:32 +00:00
irq_set_nested_thread ( cur_irq , 1 ) ;
2010-03-26 16:49:15 +00:00
/* 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 ( cur_irq , IRQF_VALID ) ;
# else
2011-03-25 11:12:32 +00:00
irq_set_noprobe ( cur_irq ) ;
2010-03-26 16:49:15 +00:00
# endif
}
ret = request_threaded_irq ( wm8994 - > irq , NULL , wm8994_irq_thread ,
IRQF_TRIGGER_HIGH | IRQF_ONESHOT ,
" wm8994 " , wm8994 ) ;
if ( ret ! = 0 ) {
dev_err ( wm8994 - > dev , " Failed to request IRQ %d: %d \n " ,
wm8994 - > irq , ret ) ;
return ret ;
}
/* Enable top level interrupt if it was masked */
wm8994_reg_write ( wm8994 , WM8994_INTERRUPT_CONTROL , 0 ) ;
return 0 ;
}
void wm8994_irq_exit ( struct wm8994 * wm8994 )
{
if ( wm8994 - > irq )
free_irq ( wm8994 - > irq , wm8994 ) ;
}