2012-06-19 19:34:23 +04:00
/*
* Arizona interrupt support
*
* Copyright 2012 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/delay.h>
# include <linux/gpio.h>
# include <linux/interrupt.h>
# include <linux/irq.h>
# include <linux/irqdomain.h>
# include <linux/module.h>
# include <linux/pm_runtime.h>
# include <linux/regmap.h>
# include <linux/regulator/consumer.h>
# include <linux/slab.h>
# include <linux/mfd/arizona/core.h>
# include <linux/mfd/arizona/registers.h>
# include "arizona.h"
2016-11-22 19:10:27 +03:00
# define ARIZONA_AOD_IRQ_INDEX 0
# define ARIZONA_MAIN_IRQ_INDEX 1
2012-06-19 19:34:23 +04:00
static int arizona_map_irq ( struct arizona * arizona , int irq )
{
int ret ;
2015-11-03 18:08:32 +03:00
if ( arizona - > aod_irq_chip ) {
ret = regmap_irq_get_virq ( arizona - > aod_irq_chip , irq ) ;
if ( ret > = 0 )
return ret ;
}
2012-06-19 19:34:23 +04:00
2015-11-03 18:08:32 +03:00
return regmap_irq_get_virq ( arizona - > irq_chip , irq ) ;
2012-06-19 19:34:23 +04:00
}
int arizona_request_irq ( struct arizona * arizona , int irq , char * name ,
irq_handler_t handler , void * data )
{
irq = arizona_map_irq ( arizona , irq ) ;
if ( irq < 0 )
return irq ;
return request_threaded_irq ( irq , NULL , handler , IRQF_ONESHOT ,
name , data ) ;
}
EXPORT_SYMBOL_GPL ( arizona_request_irq ) ;
void arizona_free_irq ( struct arizona * arizona , int irq , void * data )
{
irq = arizona_map_irq ( arizona , irq ) ;
if ( irq < 0 )
return ;
free_irq ( irq , data ) ;
}
EXPORT_SYMBOL_GPL ( arizona_free_irq ) ;
int arizona_set_irq_wake ( struct arizona * arizona , int irq , int on )
{
irq = arizona_map_irq ( arizona , irq ) ;
if ( irq < 0 )
return irq ;
return irq_set_irq_wake ( irq , on ) ;
}
EXPORT_SYMBOL_GPL ( arizona_set_irq_wake ) ;
static irqreturn_t arizona_boot_done ( int irq , void * data )
{
struct arizona * arizona = data ;
dev_dbg ( arizona - > dev , " Boot done \n " ) ;
return IRQ_HANDLED ;
}
static irqreturn_t arizona_ctrlif_err ( int irq , void * data )
{
struct arizona * arizona = data ;
/*
* For pretty much all potential sources a register cache sync
* won ' t help , we ' ve just got a software bug somewhere .
*/
dev_err ( arizona - > dev , " Control interface error \n " ) ;
return IRQ_HANDLED ;
}
static irqreturn_t arizona_irq_thread ( int irq , void * data )
{
struct arizona * arizona = data ;
2013-03-25 03:05:58 +04:00
bool poll ;
2012-08-21 23:02:03 +04:00
unsigned int val ;
2012-09-08 05:00:59 +04:00
int ret ;
2012-06-19 19:34:23 +04:00
ret = pm_runtime_get_sync ( arizona - > dev ) ;
if ( ret < 0 ) {
dev_err ( arizona - > dev , " Failed to resume device: %d \n " , ret ) ;
return IRQ_NONE ;
}
2013-03-25 03:05:58 +04:00
do {
poll = false ;
2016-06-15 12:28:44 +03:00
if ( arizona - > aod_irq_chip ) {
/*
* Check the AOD status register to determine whether
* the nested IRQ handler should be called .
*/
ret = regmap_read ( arizona - > regmap ,
ARIZONA_AOD_IRQ1 , & val ) ;
if ( ret )
dev_warn ( arizona - > dev ,
" Failed to read AOD IRQ1 %d \n " , ret ) ;
else if ( val )
handle_nested_irq (
irq_find_mapping ( arizona - > virq , 0 ) ) ;
}
2013-03-25 03:05:58 +04:00
/*
* Check if one of the main interrupts is asserted and only
* check that domain if it is .
*/
ret = regmap_read ( arizona - > regmap , ARIZONA_IRQ_PIN_STATUS ,
& val ) ;
if ( ret = = 0 & & val & ARIZONA_IRQ1_STS ) {
handle_nested_irq ( irq_find_mapping ( arizona - > virq , 1 ) ) ;
} else if ( ret ! = 0 ) {
dev_err ( arizona - > dev ,
" Failed to read main IRQ status: %d \n " , ret ) ;
}
2012-08-21 23:02:03 +04:00
2013-03-25 03:05:58 +04:00
/*
* Poll the IRQ pin status to see if we ' re really done
* if the interrupt controller can ' t do it for us .
*/
if ( ! arizona - > pdata . irq_gpio ) {
break ;
} else if ( arizona - > pdata . irq_flags & IRQF_TRIGGER_RISING & &
gpio_get_value_cansleep ( arizona - > pdata . irq_gpio ) ) {
poll = true ;
} else if ( arizona - > pdata . irq_flags & IRQF_TRIGGER_FALLING & &
! gpio_get_value_cansleep ( arizona - > pdata . irq_gpio ) ) {
poll = true ;
}
} while ( poll ) ;
2012-06-19 19:34:23 +04:00
pm_runtime_mark_last_busy ( arizona - > dev ) ;
pm_runtime_put_autosuspend ( arizona - > dev ) ;
return IRQ_HANDLED ;
}
static void arizona_irq_enable ( struct irq_data * data )
{
}
static void arizona_irq_disable ( struct irq_data * data )
{
}
2014-09-01 18:29:11 +04:00
static int arizona_irq_set_wake ( struct irq_data * data , unsigned int on )
{
struct arizona * arizona = irq_data_get_irq_chip_data ( data ) ;
return irq_set_irq_wake ( arizona - > irq , on ) ;
}
2012-06-19 19:34:23 +04:00
static struct irq_chip arizona_irq_chip = {
. name = " arizona " ,
. irq_disable = arizona_irq_disable ,
. irq_enable = arizona_irq_enable ,
2014-09-01 18:29:11 +04:00
. irq_set_wake = arizona_irq_set_wake ,
2012-06-19 19:34:23 +04:00
} ;
2016-03-25 17:27:09 +03:00
static struct lock_class_key arizona_irq_lock_class ;
2012-06-19 19:34:23 +04:00
static int arizona_irq_map ( struct irq_domain * h , unsigned int virq ,
irq_hw_number_t hw )
{
2015-09-11 18:07:56 +03:00
struct arizona * data = h - > host_data ;
2012-06-19 19:34:23 +04:00
irq_set_chip_data ( virq , data ) ;
2016-03-25 17:27:09 +03:00
irq_set_lockdep_class ( virq , & arizona_irq_lock_class ) ;
2014-09-09 20:00:09 +04:00
irq_set_chip_and_handler ( virq , & arizona_irq_chip , handle_simple_irq ) ;
2012-06-19 19:34:23 +04:00
irq_set_nested_thread ( virq , 1 ) ;
irq_set_noprobe ( virq ) ;
return 0 ;
}
2015-04-27 15:54:13 +03:00
static const struct irq_domain_ops arizona_domain_ops = {
2012-06-19 19:34:23 +04:00
. map = arizona_irq_map ,
. xlate = irq_domain_xlate_twocell ,
} ;
int arizona_irq_init ( struct arizona * arizona )
{
int flags = IRQF_ONESHOT ;
2016-11-22 19:10:26 +03:00
int ret ;
2012-06-19 19:34:23 +04:00
const struct regmap_irq_chip * aod , * irq ;
2013-03-25 03:16:56 +04:00
struct irq_data * irq_data ;
2016-11-22 19:10:28 +03:00
unsigned int virq ;
2012-06-19 19:34:23 +04:00
2014-07-15 14:21:50 +04:00
arizona - > ctrlif_error = true ;
2012-06-19 19:34:23 +04:00
switch ( arizona - > type ) {
2012-07-05 23:35:31 +04:00
# ifdef CONFIG_MFD_WM5102
2012-06-19 19:34:23 +04:00
case WM5102 :
aod = & wm5102_aod ;
irq = & wm5102_irq ;
2012-08-07 22:57:53 +04:00
2014-07-15 14:21:50 +04:00
arizona - > ctrlif_error = false ;
2012-06-19 19:34:23 +04:00
break ;
2012-07-10 15:37:58 +04:00
# endif
# ifdef CONFIG_MFD_WM5110
case WM5110 :
2015-01-17 18:21:22 +03:00
case WM8280 :
2012-07-10 15:37:58 +04:00
aod = & wm5110_aod ;
2014-07-15 14:21:48 +04:00
switch ( arizona - > rev ) {
case 0 . . . 2 :
irq = & wm5110_irq ;
break ;
default :
irq = & wm5110_revd_irq ;
break ;
}
2012-08-07 22:57:53 +04:00
2014-07-15 14:21:50 +04:00
arizona - > ctrlif_error = false ;
2012-07-10 15:37:58 +04:00
break ;
2012-07-05 23:35:31 +04:00
# endif
2015-11-03 18:08:32 +03:00
# ifdef CONFIG_MFD_CS47L24
case WM1831 :
case CS47L24 :
aod = NULL ;
irq = & cs47l24_irq ;
arizona - > ctrlif_error = false ;
break ;
# endif
2013-06-13 12:43:29 +04:00
# ifdef CONFIG_MFD_WM8997
case WM8997 :
aod = & wm8997_aod ;
irq = & wm8997_irq ;
2014-07-15 14:21:50 +04:00
arizona - > ctrlif_error = false ;
2013-06-13 12:43:29 +04:00
break ;
# endif
2015-07-03 18:16:35 +03:00
# ifdef CONFIG_MFD_WM8998
case WM8998 :
case WM1814 :
aod = & wm8998_aod ;
irq = & wm8998_irq ;
arizona - > ctrlif_error = false ;
break ;
# endif
2012-06-19 19:34:23 +04:00
default :
BUG_ON ( " Unknown Arizona class device " = = NULL ) ;
return - EINVAL ;
}
2013-01-17 11:54:18 +04:00
/* Disable all wake sources by default */
regmap_write ( arizona - > regmap , ARIZONA_WAKE_CONTROL , 0 ) ;
2013-03-25 03:16:56 +04:00
/* Read the flags from the interrupt controller if not specified */
if ( ! arizona - > pdata . irq_flags ) {
irq_data = irq_get_irq_data ( arizona - > irq ) ;
if ( ! irq_data ) {
dev_err ( arizona - > dev , " Invalid IRQ: %d \n " ,
arizona - > irq ) ;
return - EINVAL ;
}
arizona - > pdata . irq_flags = irqd_get_trigger_type ( irq_data ) ;
switch ( arizona - > pdata . irq_flags ) {
case IRQF_TRIGGER_LOW :
case IRQF_TRIGGER_HIGH :
case IRQF_TRIGGER_RISING :
case IRQF_TRIGGER_FALLING :
break ;
case IRQ_TYPE_NONE :
default :
/* Device default */
arizona - > pdata . irq_flags = IRQF_TRIGGER_LOW ;
break ;
}
}
2013-03-22 15:59:33 +04:00
if ( arizona - > pdata . irq_flags & ( IRQF_TRIGGER_HIGH |
IRQF_TRIGGER_RISING ) ) {
2012-06-19 19:34:23 +04:00
ret = regmap_update_bits ( arizona - > regmap , ARIZONA_IRQ_CTRL_1 ,
ARIZONA_IRQ_POL , 0 ) ;
if ( ret ! = 0 ) {
dev_err ( arizona - > dev , " Couldn't set IRQ polarity: %d \n " ,
ret ) ;
goto err ;
}
}
2013-03-22 15:59:33 +04:00
flags | = arizona - > pdata . irq_flags ;
2012-06-19 19:34:23 +04:00
/* Allocate a virtual IRQ domain to distribute to the regmap domains */
arizona - > virq = irq_domain_add_linear ( NULL , 2 , & arizona_domain_ops ,
arizona ) ;
if ( ! arizona - > virq ) {
2012-12-05 06:46:26 +04:00
dev_err ( arizona - > dev , " Failed to add core IRQ domain \n " ) ;
2012-06-19 19:34:23 +04:00
ret = - EINVAL ;
goto err ;
}
2015-11-03 18:08:32 +03:00
if ( aod ) {
2016-11-22 19:10:28 +03:00
virq = irq_create_mapping ( arizona - > virq , ARIZONA_AOD_IRQ_INDEX ) ;
if ( ! virq ) {
dev_err ( arizona - > dev , " Failed to map AOD IRQs \n " ) ;
ret = - EINVAL ;
goto err_domain ;
}
ret = regmap_add_irq_chip ( arizona - > regmap , virq , IRQF_ONESHOT ,
0 , aod , & arizona - > aod_irq_chip ) ;
2015-11-03 18:08:32 +03:00
if ( ret ! = 0 ) {
dev_err ( arizona - > dev ,
" Failed to add AOD IRQs: %d \n " , ret ) ;
2016-11-22 19:10:28 +03:00
goto err_map_aod ;
2015-11-03 18:08:32 +03:00
}
2012-06-19 19:34:23 +04:00
}
2016-11-22 19:10:28 +03:00
virq = irq_create_mapping ( arizona - > virq , ARIZONA_MAIN_IRQ_INDEX ) ;
if ( ! virq ) {
dev_err ( arizona - > dev , " Failed to map main IRQs \n " ) ;
ret = - EINVAL ;
goto err_aod ;
}
ret = regmap_add_irq_chip ( arizona - > regmap , virq , IRQF_ONESHOT ,
0 , irq , & arizona - > irq_chip ) ;
2012-06-19 19:34:23 +04:00
if ( ret ! = 0 ) {
2014-05-19 20:35:29 +04:00
dev_err ( arizona - > dev , " Failed to add main IRQs: %d \n " , ret ) ;
2016-11-22 19:10:28 +03:00
goto err_map_main_irq ;
2012-06-19 19:34:23 +04:00
}
2013-03-25 03:05:58 +04:00
/* Used to emulate edge trigger and to work around broken pinmux */
if ( arizona - > pdata . irq_gpio ) {
if ( gpio_to_irq ( arizona - > pdata . irq_gpio ) ! = arizona - > irq ) {
dev_warn ( arizona - > dev , " IRQ %d is not GPIO %d (%d) \n " ,
arizona - > irq , arizona - > pdata . irq_gpio ,
gpio_to_irq ( arizona - > pdata . irq_gpio ) ) ;
arizona - > irq = gpio_to_irq ( arizona - > pdata . irq_gpio ) ;
}
ret = devm_gpio_request_one ( arizona - > dev ,
arizona - > pdata . irq_gpio ,
GPIOF_IN , " arizona IRQ " ) ;
if ( ret ! = 0 ) {
dev_err ( arizona - > dev ,
" Failed to request IRQ GPIO %d:: %d \n " ,
arizona - > pdata . irq_gpio , ret ) ;
arizona - > pdata . irq_gpio = 0 ;
}
}
2012-06-19 19:34:23 +04:00
ret = request_threaded_irq ( arizona - > irq , NULL , arizona_irq_thread ,
flags , " arizona " , arizona ) ;
if ( ret ! = 0 ) {
2013-03-19 13:51:14 +04:00
dev_err ( arizona - > dev , " Failed to request primary IRQ %d: %d \n " ,
2012-06-19 19:34:23 +04:00
arizona - > irq , ret ) ;
goto err_main_irq ;
}
2015-12-16 16:53:59 +03:00
/* Make sure the boot done IRQ is unmasked for resumes */
2016-11-22 19:10:26 +03:00
ret = arizona_request_irq ( arizona , ARIZONA_IRQ_BOOT_DONE , " Boot done " ,
arizona_boot_done , arizona ) ;
2015-12-16 16:53:59 +03:00
if ( ret ! = 0 ) {
dev_err ( arizona - > dev , " Failed to request boot done %d: %d \n " ,
arizona - > irq , ret ) ;
goto err_boot_done ;
}
/* Handle control interface errors in the core */
if ( arizona - > ctrlif_error ) {
2016-11-22 19:10:26 +03:00
ret = arizona_request_irq ( arizona , ARIZONA_IRQ_CTRLIF_ERR ,
" Control interface error " ,
arizona_ctrlif_err , arizona ) ;
2015-12-16 16:53:59 +03:00
if ( ret ! = 0 ) {
dev_err ( arizona - > dev ,
" Failed to request CTRLIF_ERR %d: %d \n " ,
arizona - > irq , ret ) ;
goto err_ctrlif ;
}
}
2012-06-19 19:34:23 +04:00
return 0 ;
err_ctrlif :
2016-11-22 19:10:26 +03:00
arizona_free_irq ( arizona , ARIZONA_IRQ_BOOT_DONE , arizona ) ;
2012-06-19 19:34:23 +04:00
err_boot_done :
2015-12-16 16:53:59 +03:00
free_irq ( arizona - > irq , arizona ) ;
err_main_irq :
2016-11-22 19:10:27 +03:00
regmap_del_irq_chip ( irq_find_mapping ( arizona - > virq ,
ARIZONA_MAIN_IRQ_INDEX ) ,
2012-06-19 19:34:23 +04:00
arizona - > irq_chip ) ;
2016-11-22 19:10:28 +03:00
err_map_main_irq :
irq_dispose_mapping ( irq_find_mapping ( arizona - > virq ,
ARIZONA_MAIN_IRQ_INDEX ) ) ;
2012-06-19 19:34:23 +04:00
err_aod :
2016-11-22 19:10:27 +03:00
regmap_del_irq_chip ( irq_find_mapping ( arizona - > virq ,
ARIZONA_AOD_IRQ_INDEX ) ,
2012-06-19 19:34:23 +04:00
arizona - > aod_irq_chip ) ;
2016-11-22 19:10:28 +03:00
err_map_aod :
irq_dispose_mapping ( irq_find_mapping ( arizona - > virq ,
ARIZONA_AOD_IRQ_INDEX ) ) ;
err_domain :
irq_domain_remove ( arizona - > virq ) ;
2012-06-19 19:34:23 +04:00
err :
return ret ;
}
int arizona_irq_exit ( struct arizona * arizona )
{
2016-11-22 19:10:28 +03:00
unsigned int virq ;
2014-07-15 14:21:50 +04:00
if ( arizona - > ctrlif_error )
2016-11-22 19:10:26 +03:00
arizona_free_irq ( arizona , ARIZONA_IRQ_CTRLIF_ERR , arizona ) ;
arizona_free_irq ( arizona , ARIZONA_IRQ_BOOT_DONE , arizona ) ;
2016-11-22 19:10:28 +03:00
virq = irq_find_mapping ( arizona - > virq , ARIZONA_MAIN_IRQ_INDEX ) ;
regmap_del_irq_chip ( virq , arizona - > irq_chip ) ;
irq_dispose_mapping ( virq ) ;
virq = irq_find_mapping ( arizona - > virq , ARIZONA_AOD_IRQ_INDEX ) ;
regmap_del_irq_chip ( virq , arizona - > aod_irq_chip ) ;
irq_dispose_mapping ( virq ) ;
irq_domain_remove ( arizona - > virq ) ;
2012-06-19 19:34:23 +04:00
free_irq ( arizona - > irq , arizona ) ;
return 0 ;
}