2009-07-27 14:45:53 +01:00
/*
* wm831x - irq . c - - Interrupt controller support for Wolfson WM831x PMICs
*
* Copyright 2009 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/mfd/core.h>
# include <linux/interrupt.h>
# include <linux/mfd/wm831x/core.h>
# include <linux/mfd/wm831x/pdata.h>
# include <linux/mfd/wm831x/irq.h>
# include <linux/delay.h>
/*
* Since generic IRQs don ' t currently support interrupt controllers on
* interrupt driven buses we don ' t use genirq but instead provide an
* interface that looks very much like the standard ones . This leads
* to some bodges , including storing interrupt handler information in
* the static irq_data table we use to look up the data for individual
* interrupts , but hopefully won ' t last too long .
*/
struct wm831x_irq_data {
int primary ;
int reg ;
int mask ;
irq_handler_t handler ;
void * handler_data ;
} ;
static struct wm831x_irq_data wm831x_irqs [ ] = {
[ WM831X_IRQ_TEMP_THW ] = {
. primary = WM831X_TEMP_INT ,
. reg = 1 ,
. mask = WM831X_TEMP_THW_EINT ,
} ,
[ WM831X_IRQ_GPIO_1 ] = {
. primary = WM831X_GP_INT ,
. reg = 5 ,
. mask = WM831X_GP1_EINT ,
} ,
[ WM831X_IRQ_GPIO_2 ] = {
. primary = WM831X_GP_INT ,
. reg = 5 ,
. mask = WM831X_GP2_EINT ,
} ,
[ WM831X_IRQ_GPIO_3 ] = {
. primary = WM831X_GP_INT ,
. reg = 5 ,
. mask = WM831X_GP3_EINT ,
} ,
[ WM831X_IRQ_GPIO_4 ] = {
. primary = WM831X_GP_INT ,
. reg = 5 ,
. mask = WM831X_GP4_EINT ,
} ,
[ WM831X_IRQ_GPIO_5 ] = {
. primary = WM831X_GP_INT ,
. reg = 5 ,
. mask = WM831X_GP5_EINT ,
} ,
[ WM831X_IRQ_GPIO_6 ] = {
. primary = WM831X_GP_INT ,
. reg = 5 ,
. mask = WM831X_GP6_EINT ,
} ,
[ WM831X_IRQ_GPIO_7 ] = {
. primary = WM831X_GP_INT ,
. reg = 5 ,
. mask = WM831X_GP7_EINT ,
} ,
[ WM831X_IRQ_GPIO_8 ] = {
. primary = WM831X_GP_INT ,
. reg = 5 ,
. mask = WM831X_GP8_EINT ,
} ,
[ WM831X_IRQ_GPIO_9 ] = {
. primary = WM831X_GP_INT ,
. reg = 5 ,
. mask = WM831X_GP9_EINT ,
} ,
[ WM831X_IRQ_GPIO_10 ] = {
. primary = WM831X_GP_INT ,
. reg = 5 ,
. mask = WM831X_GP10_EINT ,
} ,
[ WM831X_IRQ_GPIO_11 ] = {
. primary = WM831X_GP_INT ,
. reg = 5 ,
. mask = WM831X_GP11_EINT ,
} ,
[ WM831X_IRQ_GPIO_12 ] = {
. primary = WM831X_GP_INT ,
. reg = 5 ,
. mask = WM831X_GP12_EINT ,
} ,
[ WM831X_IRQ_GPIO_13 ] = {
. primary = WM831X_GP_INT ,
. reg = 5 ,
. mask = WM831X_GP13_EINT ,
} ,
[ WM831X_IRQ_GPIO_14 ] = {
. primary = WM831X_GP_INT ,
. reg = 5 ,
. mask = WM831X_GP14_EINT ,
} ,
[ WM831X_IRQ_GPIO_15 ] = {
. primary = WM831X_GP_INT ,
. reg = 5 ,
. mask = WM831X_GP15_EINT ,
} ,
[ WM831X_IRQ_GPIO_16 ] = {
. primary = WM831X_GP_INT ,
. reg = 5 ,
. mask = WM831X_GP16_EINT ,
} ,
[ WM831X_IRQ_ON ] = {
. primary = WM831X_ON_PIN_INT ,
. reg = 1 ,
. mask = WM831X_ON_PIN_EINT ,
} ,
[ WM831X_IRQ_PPM_SYSLO ] = {
. primary = WM831X_PPM_INT ,
. reg = 1 ,
. mask = WM831X_PPM_SYSLO_EINT ,
} ,
[ WM831X_IRQ_PPM_PWR_SRC ] = {
. primary = WM831X_PPM_INT ,
. reg = 1 ,
. mask = WM831X_PPM_PWR_SRC_EINT ,
} ,
[ WM831X_IRQ_PPM_USB_CURR ] = {
. primary = WM831X_PPM_INT ,
. reg = 1 ,
. mask = WM831X_PPM_USB_CURR_EINT ,
} ,
[ WM831X_IRQ_WDOG_TO ] = {
. primary = WM831X_WDOG_INT ,
. reg = 1 ,
. mask = WM831X_WDOG_TO_EINT ,
} ,
[ WM831X_IRQ_RTC_PER ] = {
. primary = WM831X_RTC_INT ,
. reg = 1 ,
. mask = WM831X_RTC_PER_EINT ,
} ,
[ WM831X_IRQ_RTC_ALM ] = {
. primary = WM831X_RTC_INT ,
. reg = 1 ,
. mask = WM831X_RTC_ALM_EINT ,
} ,
[ WM831X_IRQ_CHG_BATT_HOT ] = {
. primary = WM831X_CHG_INT ,
. reg = 2 ,
. mask = WM831X_CHG_BATT_HOT_EINT ,
} ,
[ WM831X_IRQ_CHG_BATT_COLD ] = {
. primary = WM831X_CHG_INT ,
. reg = 2 ,
. mask = WM831X_CHG_BATT_COLD_EINT ,
} ,
[ WM831X_IRQ_CHG_BATT_FAIL ] = {
. primary = WM831X_CHG_INT ,
. reg = 2 ,
. mask = WM831X_CHG_BATT_FAIL_EINT ,
} ,
[ WM831X_IRQ_CHG_OV ] = {
. primary = WM831X_CHG_INT ,
. reg = 2 ,
. mask = WM831X_CHG_OV_EINT ,
} ,
[ WM831X_IRQ_CHG_END ] = {
. primary = WM831X_CHG_INT ,
. reg = 2 ,
. mask = WM831X_CHG_END_EINT ,
} ,
[ WM831X_IRQ_CHG_TO ] = {
. primary = WM831X_CHG_INT ,
. reg = 2 ,
. mask = WM831X_CHG_TO_EINT ,
} ,
[ WM831X_IRQ_CHG_MODE ] = {
. primary = WM831X_CHG_INT ,
. reg = 2 ,
. mask = WM831X_CHG_MODE_EINT ,
} ,
[ WM831X_IRQ_CHG_START ] = {
. primary = WM831X_CHG_INT ,
. reg = 2 ,
. mask = WM831X_CHG_START_EINT ,
} ,
[ WM831X_IRQ_TCHDATA ] = {
. primary = WM831X_TCHDATA_INT ,
. reg = 1 ,
. mask = WM831X_TCHDATA_EINT ,
} ,
[ WM831X_IRQ_TCHPD ] = {
. primary = WM831X_TCHPD_INT ,
. reg = 1 ,
. mask = WM831X_TCHPD_EINT ,
} ,
[ WM831X_IRQ_AUXADC_DATA ] = {
. primary = WM831X_AUXADC_INT ,
. reg = 1 ,
. mask = WM831X_AUXADC_DATA_EINT ,
} ,
[ WM831X_IRQ_AUXADC_DCOMP1 ] = {
. primary = WM831X_AUXADC_INT ,
. reg = 1 ,
. mask = WM831X_AUXADC_DCOMP1_EINT ,
} ,
[ WM831X_IRQ_AUXADC_DCOMP2 ] = {
. primary = WM831X_AUXADC_INT ,
. reg = 1 ,
. mask = WM831X_AUXADC_DCOMP2_EINT ,
} ,
[ WM831X_IRQ_AUXADC_DCOMP3 ] = {
. primary = WM831X_AUXADC_INT ,
. reg = 1 ,
. mask = WM831X_AUXADC_DCOMP3_EINT ,
} ,
[ WM831X_IRQ_AUXADC_DCOMP4 ] = {
. primary = WM831X_AUXADC_INT ,
. reg = 1 ,
. mask = WM831X_AUXADC_DCOMP4_EINT ,
} ,
[ WM831X_IRQ_CS1 ] = {
. primary = WM831X_CS_INT ,
. reg = 2 ,
. mask = WM831X_CS1_EINT ,
} ,
[ WM831X_IRQ_CS2 ] = {
. primary = WM831X_CS_INT ,
. reg = 2 ,
. mask = WM831X_CS2_EINT ,
} ,
[ WM831X_IRQ_HC_DC1 ] = {
. primary = WM831X_HC_INT ,
. reg = 4 ,
. mask = WM831X_HC_DC1_EINT ,
} ,
[ WM831X_IRQ_HC_DC2 ] = {
. primary = WM831X_HC_INT ,
. reg = 4 ,
. mask = WM831X_HC_DC2_EINT ,
} ,
[ WM831X_IRQ_UV_LDO1 ] = {
. primary = WM831X_UV_INT ,
. reg = 3 ,
. mask = WM831X_UV_LDO1_EINT ,
} ,
[ WM831X_IRQ_UV_LDO2 ] = {
. primary = WM831X_UV_INT ,
. reg = 3 ,
. mask = WM831X_UV_LDO2_EINT ,
} ,
[ WM831X_IRQ_UV_LDO3 ] = {
. primary = WM831X_UV_INT ,
. reg = 3 ,
. mask = WM831X_UV_LDO3_EINT ,
} ,
[ WM831X_IRQ_UV_LDO4 ] = {
. primary = WM831X_UV_INT ,
. reg = 3 ,
. mask = WM831X_UV_LDO4_EINT ,
} ,
[ WM831X_IRQ_UV_LDO5 ] = {
. primary = WM831X_UV_INT ,
. reg = 3 ,
. mask = WM831X_UV_LDO5_EINT ,
} ,
[ WM831X_IRQ_UV_LDO6 ] = {
. primary = WM831X_UV_INT ,
. reg = 3 ,
. mask = WM831X_UV_LDO6_EINT ,
} ,
[ WM831X_IRQ_UV_LDO7 ] = {
. primary = WM831X_UV_INT ,
. reg = 3 ,
. mask = WM831X_UV_LDO7_EINT ,
} ,
[ WM831X_IRQ_UV_LDO8 ] = {
. primary = WM831X_UV_INT ,
. reg = 3 ,
. mask = WM831X_UV_LDO8_EINT ,
} ,
[ WM831X_IRQ_UV_LDO9 ] = {
. primary = WM831X_UV_INT ,
. reg = 3 ,
. mask = WM831X_UV_LDO9_EINT ,
} ,
[ WM831X_IRQ_UV_LDO10 ] = {
. primary = WM831X_UV_INT ,
. reg = 3 ,
. mask = WM831X_UV_LDO10_EINT ,
} ,
[ WM831X_IRQ_UV_DC1 ] = {
. primary = WM831X_UV_INT ,
. reg = 4 ,
. mask = WM831X_UV_DC1_EINT ,
} ,
[ WM831X_IRQ_UV_DC2 ] = {
. primary = WM831X_UV_INT ,
. reg = 4 ,
. mask = WM831X_UV_DC2_EINT ,
} ,
[ WM831X_IRQ_UV_DC3 ] = {
. primary = WM831X_UV_INT ,
. reg = 4 ,
. mask = WM831X_UV_DC3_EINT ,
} ,
[ WM831X_IRQ_UV_DC4 ] = {
. primary = WM831X_UV_INT ,
. reg = 4 ,
. mask = WM831X_UV_DC4_EINT ,
} ,
} ;
static inline int irq_data_to_status_reg ( struct wm831x_irq_data * irq_data )
{
return WM831X_INTERRUPT_STATUS_1 - 1 + irq_data - > reg ;
}
static inline int irq_data_to_mask_reg ( struct wm831x_irq_data * irq_data )
{
return WM831X_INTERRUPT_STATUS_1_MASK - 1 + irq_data - > reg ;
}
static void __wm831x_enable_irq ( struct wm831x * wm831x , int irq )
{
struct wm831x_irq_data * irq_data = & wm831x_irqs [ irq ] ;
wm831x - > irq_masks [ irq_data - > reg - 1 ] & = ~ irq_data - > mask ;
wm831x_reg_write ( wm831x , irq_data_to_mask_reg ( irq_data ) ,
wm831x - > irq_masks [ irq_data - > reg - 1 ] ) ;
}
void wm831x_enable_irq ( struct wm831x * wm831x , int irq )
{
mutex_lock ( & wm831x - > irq_lock ) ;
__wm831x_enable_irq ( wm831x , irq ) ;
mutex_unlock ( & wm831x - > irq_lock ) ;
}
EXPORT_SYMBOL_GPL ( wm831x_enable_irq ) ;
static void __wm831x_disable_irq ( struct wm831x * wm831x , int irq )
{
struct wm831x_irq_data * irq_data = & wm831x_irqs [ irq ] ;
wm831x - > irq_masks [ irq_data - > reg - 1 ] | = irq_data - > mask ;
wm831x_reg_write ( wm831x , irq_data_to_mask_reg ( irq_data ) ,
wm831x - > irq_masks [ irq_data - > reg - 1 ] ) ;
}
void wm831x_disable_irq ( struct wm831x * wm831x , int irq )
{
mutex_lock ( & wm831x - > irq_lock ) ;
__wm831x_disable_irq ( wm831x , irq ) ;
mutex_unlock ( & wm831x - > irq_lock ) ;
}
EXPORT_SYMBOL_GPL ( wm831x_disable_irq ) ;
int wm831x_request_irq ( struct wm831x * wm831x ,
unsigned int irq , irq_handler_t handler ,
unsigned long flags , const char * name ,
void * dev )
{
int ret = 0 ;
if ( irq < 0 | | irq > = WM831X_NUM_IRQS )
return - EINVAL ;
mutex_lock ( & wm831x - > irq_lock ) ;
if ( wm831x_irqs [ irq ] . handler ) {
dev_err ( wm831x - > dev , " Already have handler for IRQ %d \n " , irq ) ;
ret = - EINVAL ;
goto out ;
}
wm831x_irqs [ irq ] . handler = handler ;
wm831x_irqs [ irq ] . handler_data = dev ;
__wm831x_enable_irq ( wm831x , irq ) ;
out :
mutex_unlock ( & wm831x - > irq_lock ) ;
return ret ;
}
EXPORT_SYMBOL_GPL ( wm831x_request_irq ) ;
void wm831x_free_irq ( struct wm831x * wm831x , unsigned int irq , void * data )
{
if ( irq < 0 | | irq > = WM831X_NUM_IRQS )
return ;
mutex_lock ( & wm831x - > irq_lock ) ;
wm831x_irqs [ irq ] . handler = NULL ;
wm831x_irqs [ irq ] . handler_data = NULL ;
__wm831x_disable_irq ( wm831x , irq ) ;
mutex_unlock ( & wm831x - > irq_lock ) ;
}
EXPORT_SYMBOL_GPL ( wm831x_free_irq ) ;
static void wm831x_handle_irq ( struct wm831x * wm831x , int irq , int status )
{
struct wm831x_irq_data * irq_data = & wm831x_irqs [ irq ] ;
if ( irq_data - > handler ) {
irq_data - > handler ( irq , irq_data - > handler_data ) ;
wm831x_reg_write ( wm831x , irq_data_to_status_reg ( irq_data ) ,
irq_data - > mask ) ;
} else {
dev_err ( wm831x - > dev , " Unhandled IRQ %d, masking \n " , irq ) ;
__wm831x_disable_irq ( wm831x , irq ) ;
}
}
/* Main interrupt handling occurs in a workqueue since we need
* interrupts enabled to interact with the chip . */
static void wm831x_irq_worker ( struct work_struct * work )
{
struct wm831x * wm831x = container_of ( work , struct wm831x , irq_work ) ;
unsigned int i ;
int primary ;
int status_regs [ 5 ] ;
int read [ 5 ] = { 0 } ;
int * status ;
primary = wm831x_reg_read ( wm831x , WM831X_SYSTEM_INTERRUPTS ) ;
if ( primary < 0 ) {
dev_err ( wm831x - > dev , " Failed to read system interrupt: %d \n " ,
primary ) ;
goto out ;
}
mutex_lock ( & wm831x - > irq_lock ) ;
for ( i = 0 ; i < ARRAY_SIZE ( wm831x_irqs ) ; i + + ) {
int offset = wm831x_irqs [ i ] . reg - 1 ;
if ( ! ( primary & wm831x_irqs [ i ] . primary ) )
continue ;
status = & status_regs [ offset ] ;
/* Hopefully there should only be one register to read
* each time otherwise we ought to do a block read . */
if ( ! read [ offset ] ) {
* status = wm831x_reg_read ( wm831x ,
irq_data_to_status_reg ( & wm831x_irqs [ i ] ) ) ;
if ( * status < 0 ) {
dev_err ( wm831x - > dev ,
" Failed to read IRQ status: %d \n " ,
* status ) ;
goto out_lock ;
}
/* Mask out the disabled IRQs */
* status & = ~ wm831x - > irq_masks [ offset ] ;
read [ offset ] = 1 ;
}
if ( * status & wm831x_irqs [ i ] . mask )
wm831x_handle_irq ( wm831x , i , * status ) ;
}
out_lock :
mutex_unlock ( & wm831x - > irq_lock ) ;
out :
enable_irq ( wm831x - > irq ) ;
}
static irqreturn_t wm831x_cpu_irq ( int irq , void * data )
{
struct wm831x * wm831x = data ;
/* Shut the interrupt to the CPU up and schedule the actual
* handler ; we can ' t check that the IRQ is asserted . */
disable_irq_nosync ( irq ) ;
queue_work ( wm831x - > irq_wq , & wm831x - > irq_work ) ;
return IRQ_HANDLED ;
}
int wm831x_irq_init ( struct wm831x * wm831x , int irq )
{
int i , ret ;
2009-10-19 11:07:05 +01:00
mutex_init ( & wm831x - > irq_lock ) ;
2009-07-27 14:45:53 +01:00
if ( ! irq ) {
dev_warn ( wm831x - > dev ,
" No interrupt specified - functionality limited \n " ) ;
return 0 ;
}
wm831x - > irq_wq = create_singlethread_workqueue ( " wm831x-irq " ) ;
if ( ! wm831x - > irq_wq ) {
dev_err ( wm831x - > dev , " Failed to allocate IRQ worker \n " ) ;
return - ESRCH ;
}
wm831x - > irq = irq ;
INIT_WORK ( & wm831x - > irq_work , wm831x_irq_worker ) ;
/* Mask the individual interrupt sources */
for ( i = 0 ; i < ARRAY_SIZE ( wm831x - > irq_masks ) ; i + + ) {
wm831x - > irq_masks [ i ] = 0xffff ;
wm831x_reg_write ( wm831x , WM831X_INTERRUPT_STATUS_1_MASK + i ,
0xffff ) ;
}
/* Enable top level interrupts, we mask at secondary level */
wm831x_reg_write ( wm831x , WM831X_SYSTEM_INTERRUPTS_MASK , 0 ) ;
/* We're good to go. We set IRQF_SHARED since there's a
* chance the driver will interoperate with another driver but
* the need to disable the IRQ while handing via I2C / SPI means
* that this may break and performance will be impacted . If
* this does happen it ' s a hardware design issue and the only
* other alternative would be polling .
*/
ret = request_irq ( irq , wm831x_cpu_irq , IRQF_TRIGGER_LOW | IRQF_SHARED ,
" wm831x " , wm831x ) ;
if ( ret ! = 0 ) {
dev_err ( wm831x - > dev , " Failed to request IRQ %d: %d \n " ,
irq , ret ) ;
return ret ;
}
return 0 ;
}
void wm831x_irq_exit ( struct wm831x * wm831x )
{
if ( wm831x - > irq )
free_irq ( wm831x - > irq , wm831x ) ;
}