2018-05-20 20:01:48 +03:00
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2014-2018 MediaTek Inc.
/*
* Library for MediaTek External Interrupt Support
*
* Author : Maoguang Meng < maoguang . meng @ mediatek . com >
* Sean Wang < sean . wang @ mediatek . com >
*
*/
# include <linux/delay.h>
# include <linux/err.h>
2018-09-13 14:58:21 +03:00
# include <linux/gpio/driver.h>
2018-05-20 20:01:48 +03:00
# include <linux/io.h>
2018-07-24 14:57:10 +03:00
# include <linux/irqchip/chained_irq.h>
2018-05-20 20:01:48 +03:00
# include <linux/irqdomain.h>
2020-04-07 23:08:16 +03:00
# include <linux/module.h>
2018-05-20 20:01:48 +03:00
# include <linux/of_irq.h>
# include <linux/platform_device.h>
# include "mtk-eint.h"
# define MTK_EINT_EDGE_SENSITIVE 0
# define MTK_EINT_LEVEL_SENSITIVE 1
# define MTK_EINT_DBNC_SET_DBNC_BITS 4
# define MTK_EINT_DBNC_RST_BIT (0x1 << 1)
# define MTK_EINT_DBNC_SET_EN (0x1 << 0)
static const struct mtk_eint_regs mtk_generic_eint_regs = {
. stat = 0x000 ,
. ack = 0x040 ,
. mask = 0x080 ,
. mask_set = 0x0c0 ,
. mask_clr = 0x100 ,
. sens = 0x140 ,
. sens_set = 0x180 ,
. sens_clr = 0x1c0 ,
. soft = 0x200 ,
. soft_set = 0x240 ,
. soft_clr = 0x280 ,
. pol = 0x300 ,
. pol_set = 0x340 ,
. pol_clr = 0x380 ,
. dom_en = 0x400 ,
. dbnc_ctrl = 0x500 ,
. dbnc_set = 0x600 ,
. dbnc_clr = 0x700 ,
} ;
static void __iomem * mtk_eint_get_offset ( struct mtk_eint * eint ,
unsigned int eint_num ,
unsigned int offset )
{
unsigned int eint_base = 0 ;
void __iomem * reg ;
if ( eint_num > = eint - > hw - > ap_num )
eint_base = eint - > hw - > ap_num ;
reg = eint - > base + offset + ( ( eint_num - eint_base ) / 32 ) * 4 ;
return reg ;
}
static unsigned int mtk_eint_can_en_debounce ( struct mtk_eint * eint ,
unsigned int eint_num )
{
unsigned int sens ;
unsigned int bit = BIT ( eint_num % 32 ) ;
void __iomem * reg = mtk_eint_get_offset ( eint , eint_num ,
eint - > regs - > sens ) ;
if ( readl ( reg ) & bit )
sens = MTK_EINT_LEVEL_SENSITIVE ;
else
sens = MTK_EINT_EDGE_SENSITIVE ;
if ( eint_num < eint - > hw - > db_cnt & & sens ! = MTK_EINT_EDGE_SENSITIVE )
return 1 ;
else
return 0 ;
}
static int mtk_eint_flip_edge ( struct mtk_eint * eint , int hwirq )
{
int start_level , curr_level ;
unsigned int reg_offset ;
u32 mask = BIT ( hwirq & 0x1f ) ;
u32 port = ( hwirq > > 5 ) & eint - > hw - > port_mask ;
void __iomem * reg = eint - > base + ( port < < 2 ) ;
curr_level = eint - > gpio_xlate - > get_gpio_state ( eint - > pctl , hwirq ) ;
do {
start_level = curr_level ;
if ( start_level )
reg_offset = eint - > regs - > pol_clr ;
else
reg_offset = eint - > regs - > pol_set ;
writel ( mask , reg + reg_offset ) ;
curr_level = eint - > gpio_xlate - > get_gpio_state ( eint - > pctl ,
hwirq ) ;
} while ( start_level ! = curr_level ) ;
return start_level ;
}
static void mtk_eint_mask ( struct irq_data * d )
{
struct mtk_eint * eint = irq_data_get_irq_chip_data ( d ) ;
u32 mask = BIT ( d - > hwirq & 0x1f ) ;
void __iomem * reg = mtk_eint_get_offset ( eint , d - > hwirq ,
eint - > regs - > mask_set ) ;
pinctrl: mediatek: Update cur_mask in mask/mask ops
During suspend/resume, mtk_eint_mask may be called while
wake_mask is active. For example, this happens if a wake-source
with an active interrupt handler wakes the system:
irq/pm.c:irq_pm_check_wakeup would disable the interrupt, so
that it can be handled later on in the resume flow.
However, this may happen before mtk_eint_do_resume is called:
in this case, wake_mask is loaded, and cur_mask is restored
from an older copy, re-enabling the interrupt, and causing
an interrupt storm (especially for level interrupts).
Step by step, for a line that has both wake and interrupt enabled:
1. cur_mask[irq] = 1; wake_mask[irq] = 1; EINT_EN[irq] = 1 (interrupt
enabled at hardware level)
2. System suspends, resumes due to that line (at this stage EINT_EN
== wake_mask)
3. irq_pm_check_wakeup is called, and disables the interrupt =>
EINT_EN[irq] = 0, but we still have cur_mask[irq] = 1
4. mtk_eint_do_resume is called, and restores EINT_EN = cur_mask, so
it reenables EINT_EN[irq] = 1 => interrupt storm as the driver
is not yet ready to handle the interrupt.
This patch fixes the issue in step 3, by recording all mask/unmask
changes in cur_mask. This also avoids the need to read the current
mask in eint_do_suspend, and we can remove mtk_eint_chip_read_mask
function.
The interrupt will be re-enabled properly later on, sometimes after
mtk_eint_do_resume, when the driver is ready to handle it.
Fixes: 58a5e1b64bb0 ("pinctrl: mediatek: Implement wake handler and suspend resume")
Signed-off-by: Nicolas Boichat <drinkcat@chromium.org>
Acked-by: Sean Wang <sean.wang@kernel.org>
Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
2019-06-26 06:54:45 +03:00
eint - > cur_mask [ d - > hwirq > > 5 ] & = ~ mask ;
2018-05-20 20:01:48 +03:00
writel ( mask , reg ) ;
}
static void mtk_eint_unmask ( struct irq_data * d )
{
struct mtk_eint * eint = irq_data_get_irq_chip_data ( d ) ;
u32 mask = BIT ( d - > hwirq & 0x1f ) ;
void __iomem * reg = mtk_eint_get_offset ( eint , d - > hwirq ,
eint - > regs - > mask_clr ) ;
pinctrl: mediatek: Update cur_mask in mask/mask ops
During suspend/resume, mtk_eint_mask may be called while
wake_mask is active. For example, this happens if a wake-source
with an active interrupt handler wakes the system:
irq/pm.c:irq_pm_check_wakeup would disable the interrupt, so
that it can be handled later on in the resume flow.
However, this may happen before mtk_eint_do_resume is called:
in this case, wake_mask is loaded, and cur_mask is restored
from an older copy, re-enabling the interrupt, and causing
an interrupt storm (especially for level interrupts).
Step by step, for a line that has both wake and interrupt enabled:
1. cur_mask[irq] = 1; wake_mask[irq] = 1; EINT_EN[irq] = 1 (interrupt
enabled at hardware level)
2. System suspends, resumes due to that line (at this stage EINT_EN
== wake_mask)
3. irq_pm_check_wakeup is called, and disables the interrupt =>
EINT_EN[irq] = 0, but we still have cur_mask[irq] = 1
4. mtk_eint_do_resume is called, and restores EINT_EN = cur_mask, so
it reenables EINT_EN[irq] = 1 => interrupt storm as the driver
is not yet ready to handle the interrupt.
This patch fixes the issue in step 3, by recording all mask/unmask
changes in cur_mask. This also avoids the need to read the current
mask in eint_do_suspend, and we can remove mtk_eint_chip_read_mask
function.
The interrupt will be re-enabled properly later on, sometimes after
mtk_eint_do_resume, when the driver is ready to handle it.
Fixes: 58a5e1b64bb0 ("pinctrl: mediatek: Implement wake handler and suspend resume")
Signed-off-by: Nicolas Boichat <drinkcat@chromium.org>
Acked-by: Sean Wang <sean.wang@kernel.org>
Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
2019-06-26 06:54:45 +03:00
eint - > cur_mask [ d - > hwirq > > 5 ] | = mask ;
2018-05-20 20:01:48 +03:00
writel ( mask , reg ) ;
if ( eint - > dual_edge [ d - > hwirq ] )
mtk_eint_flip_edge ( eint , d - > hwirq ) ;
}
static unsigned int mtk_eint_get_mask ( struct mtk_eint * eint ,
unsigned int eint_num )
{
unsigned int bit = BIT ( eint_num % 32 ) ;
void __iomem * reg = mtk_eint_get_offset ( eint , eint_num ,
eint - > regs - > mask ) ;
return ! ! ( readl ( reg ) & bit ) ;
}
static void mtk_eint_ack ( struct irq_data * d )
{
struct mtk_eint * eint = irq_data_get_irq_chip_data ( d ) ;
u32 mask = BIT ( d - > hwirq & 0x1f ) ;
void __iomem * reg = mtk_eint_get_offset ( eint , d - > hwirq ,
eint - > regs - > ack ) ;
writel ( mask , reg ) ;
}
static int mtk_eint_set_type ( struct irq_data * d , unsigned int type )
{
struct mtk_eint * eint = irq_data_get_irq_chip_data ( d ) ;
2021-01-25 07:17:53 +03:00
bool masked ;
2018-05-20 20:01:48 +03:00
u32 mask = BIT ( d - > hwirq & 0x1f ) ;
void __iomem * reg ;
if ( ( ( type & IRQ_TYPE_EDGE_BOTH ) & & ( type & IRQ_TYPE_LEVEL_MASK ) ) | |
( ( type & IRQ_TYPE_LEVEL_MASK ) = = IRQ_TYPE_LEVEL_MASK ) ) {
dev_err ( eint - > dev ,
" Can't configure IRQ%d (EINT%lu) for type 0x%X \n " ,
d - > irq , d - > hwirq , type ) ;
return - EINVAL ;
}
if ( ( type & IRQ_TYPE_EDGE_BOTH ) = = IRQ_TYPE_EDGE_BOTH )
eint - > dual_edge [ d - > hwirq ] = 1 ;
else
eint - > dual_edge [ d - > hwirq ] = 0 ;
2021-01-25 07:17:53 +03:00
if ( ! mtk_eint_get_mask ( eint , d - > hwirq ) ) {
mtk_eint_mask ( d ) ;
masked = false ;
} else {
masked = true ;
}
2018-05-20 20:01:48 +03:00
if ( type & ( IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_EDGE_FALLING ) ) {
reg = mtk_eint_get_offset ( eint , d - > hwirq , eint - > regs - > pol_clr ) ;
writel ( mask , reg ) ;
} else {
reg = mtk_eint_get_offset ( eint , d - > hwirq , eint - > regs - > pol_set ) ;
writel ( mask , reg ) ;
}
if ( type & ( IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING ) ) {
reg = mtk_eint_get_offset ( eint , d - > hwirq , eint - > regs - > sens_clr ) ;
writel ( mask , reg ) ;
} else {
reg = mtk_eint_get_offset ( eint , d - > hwirq , eint - > regs - > sens_set ) ;
writel ( mask , reg ) ;
}
2021-01-25 07:17:53 +03:00
mtk_eint_ack ( d ) ;
if ( ! masked )
mtk_eint_unmask ( d ) ;
2018-05-20 20:01:48 +03:00
return 0 ;
}
static int mtk_eint_irq_set_wake ( struct irq_data * d , unsigned int on )
{
struct mtk_eint * eint = irq_data_get_irq_chip_data ( d ) ;
int shift = d - > hwirq & 0x1f ;
int reg = d - > hwirq > > 5 ;
if ( on )
eint - > wake_mask [ reg ] | = BIT ( shift ) ;
else
eint - > wake_mask [ reg ] & = ~ BIT ( shift ) ;
return 0 ;
}
static void mtk_eint_chip_write_mask ( const struct mtk_eint * eint ,
void __iomem * base , u32 * buf )
{
int port ;
void __iomem * reg ;
for ( port = 0 ; port < eint - > hw - > ports ; port + + ) {
reg = base + ( port < < 2 ) ;
writel_relaxed ( ~ buf [ port ] , reg + eint - > regs - > mask_set ) ;
writel_relaxed ( buf [ port ] , reg + eint - > regs - > mask_clr ) ;
}
}
static int mtk_eint_irq_request_resources ( struct irq_data * d )
{
struct mtk_eint * eint = irq_data_get_irq_chip_data ( d ) ;
struct gpio_chip * gpio_c ;
unsigned int gpio_n ;
int err ;
err = eint - > gpio_xlate - > get_gpio_n ( eint - > pctl , d - > hwirq ,
& gpio_n , & gpio_c ) ;
if ( err < 0 ) {
dev_err ( eint - > dev , " Can not find pin \n " ) ;
return err ;
}
err = gpiochip_lock_as_irq ( gpio_c , gpio_n ) ;
if ( err < 0 ) {
dev_err ( eint - > dev , " unable to lock HW IRQ %lu for IRQ \n " ,
irqd_to_hwirq ( d ) ) ;
return err ;
}
err = eint - > gpio_xlate - > set_gpio_as_eint ( eint - > pctl , d - > hwirq ) ;
if ( err < 0 ) {
dev_err ( eint - > dev , " Can not eint mode \n " ) ;
return err ;
}
return 0 ;
}
static void mtk_eint_irq_release_resources ( struct irq_data * d )
{
struct mtk_eint * eint = irq_data_get_irq_chip_data ( d ) ;
struct gpio_chip * gpio_c ;
unsigned int gpio_n ;
eint - > gpio_xlate - > get_gpio_n ( eint - > pctl , d - > hwirq , & gpio_n ,
& gpio_c ) ;
gpiochip_unlock_as_irq ( gpio_c , gpio_n ) ;
}
static struct irq_chip mtk_eint_irq_chip = {
. name = " mt-eint " ,
. irq_disable = mtk_eint_mask ,
. irq_mask = mtk_eint_mask ,
. irq_unmask = mtk_eint_unmask ,
. irq_ack = mtk_eint_ack ,
. irq_set_type = mtk_eint_set_type ,
. irq_set_wake = mtk_eint_irq_set_wake ,
. irq_request_resources = mtk_eint_irq_request_resources ,
. irq_release_resources = mtk_eint_irq_release_resources ,
} ;
static unsigned int mtk_eint_hw_init ( struct mtk_eint * eint )
{
void __iomem * reg = eint - > base + eint - > regs - > dom_en ;
unsigned int i ;
for ( i = 0 ; i < eint - > hw - > ap_num ; i + = 32 ) {
writel ( 0xffffffff , reg ) ;
reg + = 4 ;
}
return 0 ;
}
static inline void
mtk_eint_debounce_process ( struct mtk_eint * eint , int index )
{
unsigned int rst , ctrl_offset ;
unsigned int bit , dbnc ;
ctrl_offset = ( index / 4 ) * 4 + eint - > regs - > dbnc_ctrl ;
dbnc = readl ( eint - > base + ctrl_offset ) ;
bit = MTK_EINT_DBNC_SET_EN < < ( ( index % 4 ) * 8 ) ;
if ( ( bit & dbnc ) > 0 ) {
ctrl_offset = ( index / 4 ) * 4 + eint - > regs - > dbnc_set ;
rst = MTK_EINT_DBNC_RST_BIT < < ( ( index % 4 ) * 8 ) ;
writel ( rst , eint - > base + ctrl_offset ) ;
}
}
static void mtk_eint_irq_handler ( struct irq_desc * desc )
{
struct irq_chip * chip = irq_desc_get_chip ( desc ) ;
struct mtk_eint * eint = irq_desc_get_handler_data ( desc ) ;
unsigned int status , eint_num ;
2021-05-04 19:42:18 +03:00
int offset , mask_offset , index ;
2018-05-20 20:01:48 +03:00
void __iomem * reg = mtk_eint_get_offset ( eint , 0 , eint - > regs - > stat ) ;
int dual_edge , start_level , curr_level ;
chained_irq_enter ( chip , desc ) ;
for ( eint_num = 0 ; eint_num < eint - > hw - > ap_num ; eint_num + = 32 ,
reg + = 4 ) {
status = readl ( reg ) ;
while ( status ) {
offset = __ffs ( status ) ;
pinctrl: mediatek: Ignore interrupts that are wake only during resume
Before suspending, mtk-eint would set the interrupt mask to the
one in wake_mask. However, some of these interrupts may not have a
corresponding interrupt handler, or the interrupt may be disabled.
On resume, the eint irq handler would trigger nevertheless,
and irq/pm.c:irq_pm_check_wakeup would be called, which would
try to call irq_disable. However, if the interrupt is not enabled
(irqd_irq_disabled(&desc->irq_data) is true), the call does nothing,
and the interrupt is left enabled in the eint driver.
Especially for level-sensitive interrupts, this will lead to an
interrupt storm on resume.
If we detect that an interrupt is only in wake_mask, but not in
cur_mask, we can just mask it out immediately (as mtk_eint_resume
would do anyway at a later stage in the resume sequence, when
restoring cur_mask).
Fixes: bf22ff45bed6 ("genirq: Avoid unnecessary low level irq function calls")
Signed-off-by: Nicolas Boichat <drinkcat@chromium.org>
Acked-by: Sean Wang <sean.wang@kernel.org>
Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
2019-04-29 06:55:14 +03:00
mask_offset = eint_num > > 5 ;
2018-05-20 20:01:48 +03:00
index = eint_num + offset ;
status & = ~ BIT ( offset ) ;
pinctrl: mediatek: Ignore interrupts that are wake only during resume
Before suspending, mtk-eint would set the interrupt mask to the
one in wake_mask. However, some of these interrupts may not have a
corresponding interrupt handler, or the interrupt may be disabled.
On resume, the eint irq handler would trigger nevertheless,
and irq/pm.c:irq_pm_check_wakeup would be called, which would
try to call irq_disable. However, if the interrupt is not enabled
(irqd_irq_disabled(&desc->irq_data) is true), the call does nothing,
and the interrupt is left enabled in the eint driver.
Especially for level-sensitive interrupts, this will lead to an
interrupt storm on resume.
If we detect that an interrupt is only in wake_mask, but not in
cur_mask, we can just mask it out immediately (as mtk_eint_resume
would do anyway at a later stage in the resume sequence, when
restoring cur_mask).
Fixes: bf22ff45bed6 ("genirq: Avoid unnecessary low level irq function calls")
Signed-off-by: Nicolas Boichat <drinkcat@chromium.org>
Acked-by: Sean Wang <sean.wang@kernel.org>
Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
2019-04-29 06:55:14 +03:00
/*
* If we get an interrupt on pin that was only required
* for wake ( but no real interrupt requested ) , mask the
* interrupt ( as would mtk_eint_resume do anyway later
* in the resume sequence ) .
*/
if ( eint - > wake_mask [ mask_offset ] & BIT ( offset ) & &
! ( eint - > cur_mask [ mask_offset ] & BIT ( offset ) ) ) {
writel_relaxed ( BIT ( offset ) , reg -
eint - > regs - > stat +
eint - > regs - > mask_set ) ;
}
2018-05-20 20:01:48 +03:00
dual_edge = eint - > dual_edge [ index ] ;
if ( dual_edge ) {
/*
* Clear soft - irq in case we raised it last
* time .
*/
writel ( BIT ( offset ) , reg - eint - > regs - > stat +
eint - > regs - > soft_clr ) ;
start_level =
eint - > gpio_xlate - > get_gpio_state ( eint - > pctl ,
index ) ;
}
2021-05-04 19:42:18 +03:00
generic_handle_domain_irq ( eint - > domain , index ) ;
2018-05-20 20:01:48 +03:00
if ( dual_edge ) {
curr_level = mtk_eint_flip_edge ( eint , index ) ;
/*
* If level changed , we might lost one edge
* interrupt , raised it through soft - irq .
*/
if ( start_level ! = curr_level )
writel ( BIT ( offset ) , reg -
eint - > regs - > stat +
eint - > regs - > soft_set ) ;
}
if ( index < eint - > hw - > db_cnt )
mtk_eint_debounce_process ( eint , index ) ;
}
}
chained_irq_exit ( chip , desc ) ;
}
int mtk_eint_do_suspend ( struct mtk_eint * eint )
{
mtk_eint_chip_write_mask ( eint , eint - > base , eint - > wake_mask ) ;
return 0 ;
}
2020-04-07 23:08:16 +03:00
EXPORT_SYMBOL_GPL ( mtk_eint_do_suspend ) ;
2018-05-20 20:01:48 +03:00
int mtk_eint_do_resume ( struct mtk_eint * eint )
{
mtk_eint_chip_write_mask ( eint , eint - > base , eint - > cur_mask ) ;
return 0 ;
}
2020-04-07 23:08:16 +03:00
EXPORT_SYMBOL_GPL ( mtk_eint_do_resume ) ;
2018-05-20 20:01:48 +03:00
int mtk_eint_set_debounce ( struct mtk_eint * eint , unsigned long eint_num ,
unsigned int debounce )
{
int virq , eint_offset ;
unsigned int set_offset , bit , clr_bit , clr_offset , rst , i , unmask ,
dbnc ;
static const unsigned int debounce_time [ ] = { 500 , 1000 , 16000 , 32000 ,
64000 , 128000 , 256000 } ;
struct irq_data * d ;
virq = irq_find_mapping ( eint - > domain , eint_num ) ;
eint_offset = ( eint_num % 4 ) * 8 ;
d = irq_get_irq_data ( virq ) ;
set_offset = ( eint_num / 4 ) * 4 + eint - > regs - > dbnc_set ;
clr_offset = ( eint_num / 4 ) * 4 + eint - > regs - > dbnc_clr ;
if ( ! mtk_eint_can_en_debounce ( eint , eint_num ) )
return - EINVAL ;
dbnc = ARRAY_SIZE ( debounce_time ) ;
for ( i = 0 ; i < ARRAY_SIZE ( debounce_time ) ; i + + ) {
if ( debounce < = debounce_time [ i ] ) {
dbnc = i ;
break ;
}
}
if ( ! mtk_eint_get_mask ( eint , eint_num ) ) {
mtk_eint_mask ( d ) ;
unmask = 1 ;
} else {
unmask = 0 ;
}
clr_bit = 0xff < < eint_offset ;
writel ( clr_bit , eint - > base + clr_offset ) ;
bit = ( ( dbnc < < MTK_EINT_DBNC_SET_DBNC_BITS ) | MTK_EINT_DBNC_SET_EN ) < <
eint_offset ;
rst = MTK_EINT_DBNC_RST_BIT < < eint_offset ;
writel ( rst | bit , eint - > base + set_offset ) ;
/*
* Delay a while ( more than 2 T ) to wait for hw debounce counter reset
* work correctly .
*/
udelay ( 1 ) ;
if ( unmask = = 1 )
mtk_eint_unmask ( d ) ;
return 0 ;
}
2020-04-07 23:08:16 +03:00
EXPORT_SYMBOL_GPL ( mtk_eint_set_debounce ) ;
2018-05-20 20:01:48 +03:00
int mtk_eint_find_irq ( struct mtk_eint * eint , unsigned long eint_n )
{
int irq ;
irq = irq_find_mapping ( eint - > domain , eint_n ) ;
if ( ! irq )
return - EINVAL ;
return irq ;
}
2020-04-07 23:08:16 +03:00
EXPORT_SYMBOL_GPL ( mtk_eint_find_irq ) ;
2018-05-20 20:01:48 +03:00
int mtk_eint_do_init ( struct mtk_eint * eint )
{
int i ;
/* If clients don't assign a specific regs, let's use generic one */
if ( ! eint - > regs )
eint - > regs = & mtk_generic_eint_regs ;
eint - > wake_mask = devm_kcalloc ( eint - > dev , eint - > hw - > ports ,
sizeof ( * eint - > wake_mask ) , GFP_KERNEL ) ;
if ( ! eint - > wake_mask )
return - ENOMEM ;
eint - > cur_mask = devm_kcalloc ( eint - > dev , eint - > hw - > ports ,
sizeof ( * eint - > cur_mask ) , GFP_KERNEL ) ;
if ( ! eint - > cur_mask )
return - ENOMEM ;
eint - > dual_edge = devm_kcalloc ( eint - > dev , eint - > hw - > ap_num ,
sizeof ( int ) , GFP_KERNEL ) ;
if ( ! eint - > dual_edge )
return - ENOMEM ;
eint - > domain = irq_domain_add_linear ( eint - > dev - > of_node ,
eint - > hw - > ap_num ,
& irq_domain_simple_ops , NULL ) ;
if ( ! eint - > domain )
return - ENOMEM ;
mtk_eint_hw_init ( eint ) ;
for ( i = 0 ; i < eint - > hw - > ap_num ; i + + ) {
int virq = irq_create_mapping ( eint - > domain , i ) ;
irq_set_chip_and_handler ( virq , & mtk_eint_irq_chip ,
handle_level_irq ) ;
irq_set_chip_data ( virq , eint ) ;
}
irq_set_chained_handler_and_data ( eint - > irq , mtk_eint_irq_handler ,
eint ) ;
return 0 ;
}
2020-04-07 23:08:16 +03:00
EXPORT_SYMBOL_GPL ( mtk_eint_do_init ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_DESCRIPTION ( " MediaTek EINT Driver " ) ;