2009-12-14 02:25:31 +03:00
/*
* twl6030 - irq . c - TWL6030 irq support
*
* Copyright ( C ) 2005 - 2009 Texas Instruments , Inc .
*
* Modifications to defer interrupt handling to a kernel thread :
* Copyright ( C ) 2006 MontaVista Software , Inc .
*
* Based on tlv320aic23 . c :
* Copyright ( c ) by Kai Svahn < kai . svahn @ nokia . com >
*
* Code cleanup and modifications to IRQ handler .
* by syed khasim < x0khasim @ ti . com >
*
* TWL6030 specific code and IRQ handling changes by
* Jagadeesh Bhaskar Pakaravoor < j - pakaravoor @ ti . com >
* Balaji T K < balajitk @ ti . 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 .
*
* 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 .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307 USA
*/
# include <linux/init.h>
2011-07-10 20:41:10 +04:00
# include <linux/export.h>
2009-12-14 02:25:31 +03:00
# include <linux/interrupt.h>
# include <linux/irq.h>
# include <linux/kthread.h>
# include <linux/i2c/twl.h>
2010-09-24 21:13:20 +04:00
# include <linux/platform_device.h>
2011-10-04 13:52:29 +04:00
# include <linux/suspend.h>
2012-02-29 22:40:31 +04:00
# include <linux/of.h>
# include <linux/irqdomain.h>
2013-07-25 17:15:51 +04:00
# include <linux/of_device.h>
2009-12-14 02:25:31 +03:00
2010-10-19 13:02:48 +04:00
# include "twl-core.h"
2009-12-14 02:25:31 +03:00
/*
* TWL6030 ( unlike its predecessors , which had two level interrupt handling )
* three interrupt registers INT_STS_A , INT_STS_B and INT_STS_C .
* It exposes status bits saying who has raised an interrupt . There are
* three mask registers that corresponds to these status registers , that
* enables / disables these interrupts .
*
* We set up IRQs starting at a platform - specified base . An interrupt map table ,
* specifies mapping between interrupt number and the associated module .
*/
2012-02-29 22:40:31 +04:00
# define TWL6030_NR_IRQS 20
2009-12-14 02:25:31 +03:00
static int twl6030_interrupt_mapping [ 24 ] = {
PWR_INTR_OFFSET , /* Bit 0 PWRON */
PWR_INTR_OFFSET , /* Bit 1 RPWRON */
PWR_INTR_OFFSET , /* Bit 2 BAT_VLOW */
RTC_INTR_OFFSET , /* Bit 3 RTC_ALARM */
RTC_INTR_OFFSET , /* Bit 4 RTC_PERIOD */
HOTDIE_INTR_OFFSET , /* Bit 5 HOT_DIE */
SMPSLDO_INTR_OFFSET , /* Bit 6 VXXX_SHORT */
SMPSLDO_INTR_OFFSET , /* Bit 7 VMMC_SHORT */
SMPSLDO_INTR_OFFSET , /* Bit 8 VUSIM_SHORT */
BATDETECT_INTR_OFFSET , /* Bit 9 BAT */
SIMDETECT_INTR_OFFSET , /* Bit 10 SIM */
MMCDETECT_INTR_OFFSET , /* Bit 11 MMC */
RSV_INTR_OFFSET , /* Bit 12 Reserved */
MADC_INTR_OFFSET , /* Bit 13 GPADC_RT_EOC */
MADC_INTR_OFFSET , /* Bit 14 GPADC_SW_EOC */
GASGAUGE_INTR_OFFSET , /* Bit 15 CC_AUTOCAL */
USBOTG_INTR_OFFSET , /* Bit 16 ID_WKUP */
USBOTG_INTR_OFFSET , /* Bit 17 VBUS_WKUP */
USBOTG_INTR_OFFSET , /* Bit 18 ID */
2010-12-10 15:25:37 +03:00
USB_PRES_INTR_OFFSET , /* Bit 19 VBUS */
2009-12-14 02:25:31 +03:00
CHARGER_INTR_OFFSET , /* Bit 20 CHRG_CTRL */
2011-05-12 17:27:56 +04:00
CHARGERFAULT_INTR_OFFSET , /* Bit 21 EXT_CHRG */
CHARGERFAULT_INTR_OFFSET , /* Bit 22 INT_CHRG */
2009-12-14 02:25:31 +03:00
RSV_INTR_OFFSET , /* Bit 23 Reserved */
} ;
2013-07-25 17:15:51 +04:00
static int twl6032_interrupt_mapping [ 24 ] = {
PWR_INTR_OFFSET , /* Bit 0 PWRON */
PWR_INTR_OFFSET , /* Bit 1 RPWRON */
PWR_INTR_OFFSET , /* Bit 2 SYS_VLOW */
RTC_INTR_OFFSET , /* Bit 3 RTC_ALARM */
RTC_INTR_OFFSET , /* Bit 4 RTC_PERIOD */
HOTDIE_INTR_OFFSET , /* Bit 5 HOT_DIE */
SMPSLDO_INTR_OFFSET , /* Bit 6 VXXX_SHORT */
PWR_INTR_OFFSET , /* Bit 7 SPDURATION */
PWR_INTR_OFFSET , /* Bit 8 WATCHDOG */
BATDETECT_INTR_OFFSET , /* Bit 9 BAT */
SIMDETECT_INTR_OFFSET , /* Bit 10 SIM */
MMCDETECT_INTR_OFFSET , /* Bit 11 MMC */
MADC_INTR_OFFSET , /* Bit 12 GPADC_RT_EOC */
MADC_INTR_OFFSET , /* Bit 13 GPADC_SW_EOC */
GASGAUGE_INTR_OFFSET , /* Bit 14 CC_EOC */
GASGAUGE_INTR_OFFSET , /* Bit 15 CC_AUTOCAL */
USBOTG_INTR_OFFSET , /* Bit 16 ID_WKUP */
USBOTG_INTR_OFFSET , /* Bit 17 VBUS_WKUP */
USBOTG_INTR_OFFSET , /* Bit 18 ID */
USB_PRES_INTR_OFFSET , /* Bit 19 VBUS */
CHARGER_INTR_OFFSET , /* Bit 20 CHRG_CTRL */
CHARGERFAULT_INTR_OFFSET , /* Bit 21 EXT_CHRG */
CHARGERFAULT_INTR_OFFSET , /* Bit 22 INT_CHRG */
RSV_INTR_OFFSET , /* Bit 23 Reserved */
} ;
2009-12-14 02:25:31 +03:00
/*----------------------------------------------------------------------*/
2013-07-25 17:15:50 +04:00
struct twl6030_irq {
unsigned int irq_base ;
int twl_irq ;
bool irq_wake_enabled ;
atomic_t wakeirqs ;
struct notifier_block pm_nb ;
struct irq_chip irq_chip ;
struct irq_domain * irq_domain ;
2013-07-25 17:15:51 +04:00
const int * irq_mapping_tbl ;
2013-07-25 17:15:50 +04:00
} ;
2009-12-14 02:25:31 +03:00
2013-07-25 17:15:50 +04:00
static struct twl6030_irq * twl6030_irq ;
2011-10-04 13:52:29 +04:00
static int twl6030_irq_pm_notifier ( struct notifier_block * notifier ,
unsigned long pm_event , void * unused )
{
int chained_wakeups ;
2013-07-25 17:15:50 +04:00
struct twl6030_irq * pdata = container_of ( notifier , struct twl6030_irq ,
pm_nb ) ;
2011-10-04 13:52:29 +04:00
switch ( pm_event ) {
case PM_SUSPEND_PREPARE :
2013-07-25 17:15:50 +04:00
chained_wakeups = atomic_read ( & pdata - > wakeirqs ) ;
2011-10-04 13:52:29 +04:00
2013-07-25 17:15:50 +04:00
if ( chained_wakeups & & ! pdata - > irq_wake_enabled ) {
if ( enable_irq_wake ( pdata - > twl_irq ) )
2011-10-04 13:52:29 +04:00
pr_err ( " twl6030 IRQ wake enable failed \n " ) ;
else
2013-07-25 17:15:50 +04:00
pdata - > irq_wake_enabled = true ;
} else if ( ! chained_wakeups & & pdata - > irq_wake_enabled ) {
disable_irq_wake ( pdata - > twl_irq ) ;
pdata - > irq_wake_enabled = false ;
2011-10-04 13:52:29 +04:00
}
2013-07-25 17:15:50 +04:00
disable_irq ( pdata - > twl_irq ) ;
2011-10-04 13:52:29 +04:00
break ;
2011-09-27 03:44:24 +04:00
case PM_POST_SUSPEND :
2013-07-25 17:15:50 +04:00
enable_irq ( pdata - > twl_irq ) ;
2011-09-27 03:44:24 +04:00
break ;
2011-10-04 13:52:29 +04:00
default :
break ;
}
return NOTIFY_DONE ;
}
2009-12-14 02:25:31 +03:00
/*
2013-07-25 17:15:47 +04:00
* Threaded irq handler for the twl6030 interrupt .
* We query the interrupt controller in the twl6030 to determine
* which module is generating the interrupt request and call
* handle_nested_irq for that module .
*/
static irqreturn_t twl6030_irq_thread ( int irq , void * data )
2009-12-14 02:25:31 +03:00
{
2013-07-25 17:15:47 +04:00
int i , ret ;
union {
2009-12-14 02:25:31 +03:00
u8 bytes [ 4 ] ;
2013-12-23 21:11:46 +04:00
__le32 int_sts ;
2013-07-25 17:15:47 +04:00
} sts ;
2013-12-23 21:11:46 +04:00
u32 int_sts ; /* sts.int_sts converted to CPU endianness */
2013-07-25 17:15:50 +04:00
struct twl6030_irq * pdata = data ;
2009-12-14 02:25:31 +03:00
2013-07-25 17:15:47 +04:00
/* read INT_STS_A, B and C in one shot using a burst read */
ret = twl_i2c_read ( TWL_MODULE_PIH , sts . bytes , REG_INT_STS_A , 3 ) ;
if ( ret ) {
pr_warn ( " twl6030_irq: I2C error %d reading PIH ISR \n " , ret ) ;
return IRQ_HANDLED ;
}
2009-12-14 02:25:31 +03:00
2013-07-25 17:15:47 +04:00
sts . bytes [ 3 ] = 0 ; /* Only 24 bits are valid*/
2009-12-14 02:25:31 +03:00
2013-07-25 17:15:47 +04:00
/*
* Since VBUS status bit is not reliable for VBUS disconnect
* use CHARGER VBUS detection status bit instead .
*/
if ( sts . bytes [ 2 ] & 0x10 )
sts . bytes [ 2 ] | = 0x08 ;
2010-12-10 15:25:37 +03:00
2013-12-23 21:11:46 +04:00
int_sts = le32_to_cpu ( sts . int_sts ) ;
for ( i = 0 ; int_sts ; int_sts > > = 1 , i + + )
if ( int_sts & 0x1 ) {
2013-07-25 17:15:49 +04:00
int module_irq =
2013-07-25 17:15:50 +04:00
irq_find_mapping ( pdata - > irq_domain ,
2013-07-25 17:15:51 +04:00
pdata - > irq_mapping_tbl [ i ] ) ;
2013-07-25 17:15:49 +04:00
if ( module_irq )
handle_nested_irq ( module_irq ) ;
else
pr_err ( " twl6030_irq: Unmapped PIH ISR %u detected \n " ,
i ) ;
2013-07-25 17:15:47 +04:00
pr_debug ( " twl6030_irq: PIH ISR %u, virq%u \n " ,
i , module_irq ) ;
2009-12-14 02:25:31 +03:00
}
2012-02-23 06:03:45 +04:00
2013-07-25 17:15:47 +04:00
/*
* NOTE :
* Simulation confirms that documentation is wrong w . r . t the
* interrupt status clear operation . A single * byte * write to
* any one of STS_A to STS_C register results in all three
* STS registers being reset . Since it does not matter which
* value is written , all three registers are cleared on a
* single byte write , so we just use 0x0 to clear .
*/
ret = twl_i2c_write_u8 ( TWL_MODULE_PIH , 0x00 , REG_INT_STS_A ) ;
if ( ret )
pr_warn ( " twl6030_irq: I2C error in clearing PIH ISR \n " ) ;
2009-12-14 02:25:31 +03:00
return IRQ_HANDLED ;
}
/*----------------------------------------------------------------------*/
2012-02-23 06:03:59 +04:00
static int twl6030_irq_set_wake ( struct irq_data * d , unsigned int on )
2011-09-06 19:59:30 +04:00
{
2013-07-25 17:15:50 +04:00
struct twl6030_irq * pdata = irq_get_chip_data ( d - > irq ) ;
2011-10-04 13:52:29 +04:00
if ( on )
2013-07-25 17:15:50 +04:00
atomic_inc ( & pdata - > wakeirqs ) ;
2011-10-04 13:52:29 +04:00
else
2013-07-25 17:15:50 +04:00
atomic_dec ( & pdata - > wakeirqs ) ;
2011-09-06 19:59:30 +04:00
2011-10-04 13:52:29 +04:00
return 0 ;
2011-09-06 19:59:30 +04:00
}
2009-12-14 02:25:31 +03:00
int twl6030_interrupt_unmask ( u8 bit_mask , u8 offset )
{
int ret ;
u8 unmask_value ;
ret = twl_i2c_read_u8 ( TWL_MODULE_PIH , & unmask_value ,
REG_INT_STS_A + offset ) ;
unmask_value & = ( ~ ( bit_mask ) ) ;
ret | = twl_i2c_write_u8 ( TWL_MODULE_PIH , unmask_value ,
REG_INT_STS_A + offset ) ; /* unmask INT_MSK_A/B/C */
return ret ;
}
EXPORT_SYMBOL ( twl6030_interrupt_unmask ) ;
int twl6030_interrupt_mask ( u8 bit_mask , u8 offset )
{
int ret ;
u8 mask_value ;
ret = twl_i2c_read_u8 ( TWL_MODULE_PIH , & mask_value ,
REG_INT_STS_A + offset ) ;
mask_value | = ( bit_mask ) ;
ret | = twl_i2c_write_u8 ( TWL_MODULE_PIH , mask_value ,
REG_INT_STS_A + offset ) ; /* mask INT_MSK_A/B/C */
return ret ;
}
EXPORT_SYMBOL ( twl6030_interrupt_mask ) ;
2010-09-24 21:13:20 +04:00
int twl6030_mmc_card_detect_config ( void )
{
int ret ;
u8 reg_val = 0 ;
/* Unmasking the Card detect Interrupt line for MMC1 from Phoenix */
twl6030_interrupt_unmask ( TWL6030_MMCDETECT_INT_MASK ,
REG_INT_MSK_LINE_B ) ;
twl6030_interrupt_unmask ( TWL6030_MMCDETECT_INT_MASK ,
REG_INT_MSK_STS_B ) ;
/*
2011-03-31 05:57:33 +04:00
* Initially Configuring MMC_CTRL for receiving interrupts &
2010-09-24 21:13:20 +04:00
* Card status on TWL6030 for MMC1
*/
ret = twl_i2c_read_u8 ( TWL6030_MODULE_ID0 , & reg_val , TWL6030_MMCCTRL ) ;
if ( ret < 0 ) {
pr_err ( " twl6030: Failed to read MMCCTRL, error %d \n " , ret ) ;
return ret ;
}
reg_val & = ~ VMMC_AUTO_OFF ;
reg_val | = SW_FC ;
ret = twl_i2c_write_u8 ( TWL6030_MODULE_ID0 , reg_val , TWL6030_MMCCTRL ) ;
if ( ret < 0 ) {
pr_err ( " twl6030: Failed to write MMCCTRL, error %d \n " , ret ) ;
return ret ;
}
/* Configuring PullUp-PullDown register */
ret = twl_i2c_read_u8 ( TWL6030_MODULE_ID0 , & reg_val ,
TWL6030_CFG_INPUT_PUPD3 ) ;
if ( ret < 0 ) {
pr_err ( " twl6030: Failed to read CFG_INPUT_PUPD3, error %d \n " ,
ret ) ;
return ret ;
}
reg_val & = ~ ( MMC_PU | MMC_PD ) ;
ret = twl_i2c_write_u8 ( TWL6030_MODULE_ID0 , reg_val ,
TWL6030_CFG_INPUT_PUPD3 ) ;
if ( ret < 0 ) {
pr_err ( " twl6030: Failed to write CFG_INPUT_PUPD3, error %d \n " ,
ret ) ;
return ret ;
}
2012-03-02 19:15:22 +04:00
2013-07-25 17:15:50 +04:00
return irq_find_mapping ( twl6030_irq - > irq_domain ,
MMCDETECT_INTR_OFFSET ) ;
2010-09-24 21:13:20 +04:00
}
EXPORT_SYMBOL ( twl6030_mmc_card_detect_config ) ;
int twl6030_mmc_card_detect ( struct device * dev , int slot )
{
int ret = - EIO ;
u8 read_reg = 0 ;
struct platform_device * pdev = to_platform_device ( dev ) ;
if ( pdev - > id ) {
/* TWL6030 provide's Card detect support for
* only MMC1 controller .
*/
2011-03-31 05:57:33 +04:00
pr_err ( " Unknown MMC controller %d in %s \n " , pdev - > id , __func__ ) ;
2010-09-24 21:13:20 +04:00
return ret ;
}
/*
* BIT0 of MMC_CTRL on TWL6030 provides card status for MMC1
* 0 - Card not present , 1 - Card present
*/
ret = twl_i2c_read_u8 ( TWL6030_MODULE_ID0 , & read_reg ,
TWL6030_MMCCTRL ) ;
if ( ret > = 0 )
ret = read_reg & STS_MMC ;
return ret ;
}
EXPORT_SYMBOL ( twl6030_mmc_card_detect ) ;
2013-07-25 17:15:49 +04:00
static int twl6030_irq_map ( struct irq_domain * d , unsigned int virq ,
irq_hw_number_t hwirq )
{
2013-07-25 17:15:50 +04:00
struct twl6030_irq * pdata = d - > host_data ;
irq_set_chip_data ( virq , pdata ) ;
irq_set_chip_and_handler ( virq , & pdata - > irq_chip , handle_simple_irq ) ;
2013-07-25 17:15:49 +04:00
irq_set_nested_thread ( virq , true ) ;
2013-07-25 17:15:50 +04:00
irq_set_parent ( virq , pdata - > twl_irq ) ;
2013-07-25 17:15:49 +04:00
# ifdef CONFIG_ARM
/*
* ARM requires an extra step to clear IRQ_NOREQUEST , which it
* sets on behalf of every irq_chip . Also sets IRQ_NOPROBE .
*/
set_irq_flags ( virq , IRQF_VALID ) ;
# else
/* same effect on other architectures */
irq_set_noprobe ( virq ) ;
# endif
return 0 ;
}
static void twl6030_irq_unmap ( struct irq_domain * d , unsigned int virq )
{
# ifdef CONFIG_ARM
set_irq_flags ( virq , 0 ) ;
# endif
irq_set_chip_and_handler ( virq , NULL , NULL ) ;
irq_set_chip_data ( virq , NULL ) ;
}
static struct irq_domain_ops twl6030_irq_domain_ops = {
. map = twl6030_irq_map ,
. unmap = twl6030_irq_unmap ,
. xlate = irq_domain_xlate_onetwocell ,
} ;
2013-07-25 17:15:51 +04:00
static const struct of_device_id twl6030_of_match [ ] = {
{ . compatible = " ti,twl6030 " , & twl6030_interrupt_mapping } ,
{ . compatible = " ti,twl6032 " , & twl6032_interrupt_mapping } ,
{ } ,
} ;
2012-02-29 22:40:31 +04:00
int twl6030_init_irq ( struct device * dev , int irq_num )
2009-12-14 02:25:31 +03:00
{
2012-02-29 22:40:31 +04:00
struct device_node * node = dev - > of_node ;
2013-07-25 17:15:49 +04:00
int nr_irqs ;
2013-07-25 17:15:48 +04:00
int status ;
2012-11-13 12:28:45 +04:00
u8 mask [ 3 ] ;
2013-07-25 17:15:51 +04:00
const struct of_device_id * of_id ;
of_id = of_match_device ( twl6030_of_match , dev ) ;
if ( ! of_id | | ! of_id - > data ) {
dev_err ( dev , " Unknown TWL device model \n " ) ;
return - EINVAL ;
}
2012-02-29 22:40:31 +04:00
nr_irqs = TWL6030_NR_IRQS ;
2013-07-25 17:15:50 +04:00
twl6030_irq = devm_kzalloc ( dev , sizeof ( * twl6030_irq ) , GFP_KERNEL ) ;
if ( ! twl6030_irq ) {
dev_err ( dev , " twl6030_irq: Memory allocation failed \n " ) ;
return - ENOMEM ;
}
2012-11-13 12:28:45 +04:00
mask [ 0 ] = 0xFF ;
2009-12-14 02:25:31 +03:00
mask [ 1 ] = 0xFF ;
mask [ 2 ] = 0xFF ;
2012-03-02 14:11:26 +04:00
/* mask all int lines */
2013-07-25 17:15:48 +04:00
status = twl_i2c_write ( TWL_MODULE_PIH , & mask [ 0 ] , REG_INT_MSK_LINE_A , 3 ) ;
2012-03-02 14:11:26 +04:00
/* mask all int sts */
2013-07-25 17:15:48 +04:00
status | = twl_i2c_write ( TWL_MODULE_PIH , & mask [ 0 ] , REG_INT_MSK_STS_A , 3 ) ;
2012-03-02 14:11:26 +04:00
/* clear INT_STS_A,B,C */
2013-07-25 17:15:48 +04:00
status | = twl_i2c_write ( TWL_MODULE_PIH , & mask [ 0 ] , REG_INT_STS_A , 3 ) ;
if ( status < 0 ) {
dev_err ( dev , " I2C err writing TWL_MODULE_PIH: %d \n " , status ) ;
return status ;
}
2009-12-14 02:25:31 +03:00
2012-03-02 14:11:26 +04:00
/*
* install an irq handler for each of the modules ;
2009-12-14 02:25:31 +03:00
* clone dummy irq_chip since PIH can ' t * do * anything
*/
2013-07-25 17:15:50 +04:00
twl6030_irq - > irq_chip = dummy_irq_chip ;
twl6030_irq - > irq_chip . name = " twl6030 " ;
twl6030_irq - > irq_chip . irq_set_type = NULL ;
twl6030_irq - > irq_chip . irq_set_wake = twl6030_irq_set_wake ;
twl6030_irq - > pm_nb . notifier_call = twl6030_irq_pm_notifier ;
atomic_set ( & twl6030_irq - > wakeirqs , 0 ) ;
2013-07-25 17:15:51 +04:00
twl6030_irq - > irq_mapping_tbl = of_id - > data ;
2013-07-25 17:15:50 +04:00
twl6030_irq - > irq_domain =
irq_domain_add_linear ( node , nr_irqs ,
& twl6030_irq_domain_ops , twl6030_irq ) ;
if ( ! twl6030_irq - > irq_domain ) {
2013-07-25 17:15:49 +04:00
dev_err ( dev , " Can't add irq_domain \n " ) ;
return - ENOMEM ;
2009-12-14 02:25:31 +03:00
}
2013-07-25 17:15:49 +04:00
dev_info ( dev , " PIH (irq %d) nested IRQs \n " , irq_num ) ;
2009-12-14 02:25:31 +03:00
/* install an irq handler to demultiplex the TWL6030 interrupt */
2013-07-25 17:15:47 +04:00
status = request_threaded_irq ( irq_num , NULL , twl6030_irq_thread ,
2013-07-25 17:15:50 +04:00
IRQF_ONESHOT , " TWL6030-PIH " , twl6030_irq ) ;
2009-12-14 02:25:31 +03:00
if ( status < 0 ) {
2012-03-02 14:11:26 +04:00
dev_err ( dev , " could not claim irq %d: %d \n " , irq_num , status ) ;
2009-12-14 02:25:31 +03:00
goto fail_irq ;
}
2011-08-11 11:21:00 +04:00
2013-07-25 17:15:50 +04:00
twl6030_irq - > twl_irq = irq_num ;
register_pm_notifier ( & twl6030_irq - > pm_nb ) ;
2013-07-25 17:15:49 +04:00
return 0 ;
2009-12-14 02:25:31 +03:00
2011-08-11 11:21:00 +04:00
fail_irq :
2013-07-25 17:15:50 +04:00
irq_domain_remove ( twl6030_irq - > irq_domain ) ;
2009-12-14 02:25:31 +03:00
return status ;
}
int twl6030_exit_irq ( void )
{
2013-07-25 17:15:50 +04:00
if ( twl6030_irq & & twl6030_irq - > twl_irq ) {
unregister_pm_notifier ( & twl6030_irq - > pm_nb ) ;
free_irq ( twl6030_irq - > twl_irq , NULL ) ;
2013-07-25 17:15:49 +04:00
/*
* TODO : IRQ domain and allocated nested IRQ descriptors
* should be freed somehow here . Now It can ' t be done , because
* child devices will not be deleted during removing of
* TWL Core driver and they will still contain allocated
* virt IRQs in their Resources tables .
* The same prevents us from using devm_request_threaded_irq ( )
* in this module .
*/
2009-12-14 02:25:31 +03:00
}
return 0 ;
}