2018-12-17 17:01:20 +03:00
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright 2017 NXP
* Copyright ( C ) 2018 Pengutronix , Lucas Stach < kernel @ pengutronix . de >
*/
# include <linux/clk.h>
# include <linux/interrupt.h>
# include <linux/irq.h>
# include <linux/irqchip/chained_irq.h>
# include <linux/irqdomain.h>
# include <linux/kernel.h>
2019-02-20 14:40:51 +03:00
# include <linux/of_irq.h>
2018-12-17 17:01:20 +03:00
# include <linux/of_platform.h>
# include <linux/spinlock.h>
2019-02-20 14:40:47 +03:00
# define CTRL_STRIDE_OFF(_t, _r) (_t * 4 * _r)
2018-12-17 17:01:20 +03:00
# define CHANCTRL 0x0
# define CHANMASK(n, t) (CTRL_STRIDE_OFF(t, 0) + 0x4 * (n) + 0x4)
# define CHANSET(n, t) (CTRL_STRIDE_OFF(t, 1) + 0x4 * (n) + 0x4)
# define CHANSTATUS(n, t) (CTRL_STRIDE_OFF(t, 2) + 0x4 * (n) + 0x4)
# define CHAN_MINTDIS(t) (CTRL_STRIDE_OFF(t, 3) + 0x4)
# define CHAN_MASTRSTAT(t) (CTRL_STRIDE_OFF(t, 3) + 0x8)
2019-02-20 14:40:51 +03:00
# define CHAN_MAX_OUTPUT_INT 0x8
2018-12-17 17:01:20 +03:00
struct irqsteer_data {
void __iomem * regs ;
struct clk * ipg_clk ;
2019-02-20 14:40:51 +03:00
int irq [ CHAN_MAX_OUTPUT_INT ] ;
int irq_count ;
2018-12-17 17:01:20 +03:00
raw_spinlock_t lock ;
2019-02-20 14:40:47 +03:00
int reg_num ;
2018-12-17 17:01:20 +03:00
int channel ;
struct irq_domain * domain ;
u32 * saved_reg ;
} ;
static int imx_irqsteer_get_reg_index ( struct irqsteer_data * data ,
unsigned long irqnum )
{
2019-02-20 14:40:47 +03:00
return ( data - > reg_num - irqnum / 32 - 1 ) ;
2018-12-17 17:01:20 +03:00
}
static void imx_irqsteer_irq_unmask ( struct irq_data * d )
{
struct irqsteer_data * data = d - > chip_data ;
int idx = imx_irqsteer_get_reg_index ( data , d - > hwirq ) ;
unsigned long flags ;
u32 val ;
raw_spin_lock_irqsave ( & data - > lock , flags ) ;
2019-02-20 14:40:47 +03:00
val = readl_relaxed ( data - > regs + CHANMASK ( idx , data - > reg_num ) ) ;
2018-12-17 17:01:20 +03:00
val | = BIT ( d - > hwirq % 32 ) ;
2019-02-20 14:40:47 +03:00
writel_relaxed ( val , data - > regs + CHANMASK ( idx , data - > reg_num ) ) ;
2018-12-17 17:01:20 +03:00
raw_spin_unlock_irqrestore ( & data - > lock , flags ) ;
}
static void imx_irqsteer_irq_mask ( struct irq_data * d )
{
struct irqsteer_data * data = d - > chip_data ;
int idx = imx_irqsteer_get_reg_index ( data , d - > hwirq ) ;
unsigned long flags ;
u32 val ;
raw_spin_lock_irqsave ( & data - > lock , flags ) ;
2019-02-20 14:40:47 +03:00
val = readl_relaxed ( data - > regs + CHANMASK ( idx , data - > reg_num ) ) ;
2018-12-17 17:01:20 +03:00
val & = ~ BIT ( d - > hwirq % 32 ) ;
2019-02-20 14:40:47 +03:00
writel_relaxed ( val , data - > regs + CHANMASK ( idx , data - > reg_num ) ) ;
2018-12-17 17:01:20 +03:00
raw_spin_unlock_irqrestore ( & data - > lock , flags ) ;
}
static struct irq_chip imx_irqsteer_irq_chip = {
. name = " irqsteer " ,
. irq_mask = imx_irqsteer_irq_mask ,
. irq_unmask = imx_irqsteer_irq_unmask ,
} ;
static int imx_irqsteer_irq_map ( struct irq_domain * h , unsigned int irq ,
irq_hw_number_t hwirq )
{
irq_set_status_flags ( irq , IRQ_LEVEL ) ;
irq_set_chip_data ( irq , h - > host_data ) ;
irq_set_chip_and_handler ( irq , & imx_irqsteer_irq_chip , handle_level_irq ) ;
return 0 ;
}
static const struct irq_domain_ops imx_irqsteer_domain_ops = {
. map = imx_irqsteer_irq_map ,
. xlate = irq_domain_xlate_onecell ,
} ;
2019-02-20 14:40:51 +03:00
static int imx_irqsteer_get_hwirq_base ( struct irqsteer_data * data , u32 irq )
{
int i ;
for ( i = 0 ; i < data - > irq_count ; i + + ) {
if ( data - > irq [ i ] = = irq )
return i * 64 ;
}
return - EINVAL ;
}
2018-12-17 17:01:20 +03:00
static void imx_irqsteer_irq_handler ( struct irq_desc * desc )
{
struct irqsteer_data * data = irq_desc_get_handler_data ( desc ) ;
2019-02-20 14:40:51 +03:00
int hwirq ;
int irq , i ;
2018-12-17 17:01:20 +03:00
chained_irq_enter ( irq_desc_get_chip ( desc ) , desc ) ;
2019-02-20 14:40:51 +03:00
irq = irq_desc_get_irq ( desc ) ;
hwirq = imx_irqsteer_get_hwirq_base ( data , irq ) ;
if ( hwirq < 0 ) {
pr_warn ( " %s: unable to get hwirq base for irq %d \n " ,
__func__ , irq ) ;
return ;
}
for ( i = 0 ; i < 2 ; i + + , hwirq + = 32 ) {
int idx = imx_irqsteer_get_reg_index ( data , hwirq ) ;
2018-12-17 17:01:20 +03:00
unsigned long irqmap ;
int pos , virq ;
2019-02-20 14:40:51 +03:00
if ( hwirq > = data - > reg_num * 32 )
break ;
2018-12-17 17:01:20 +03:00
irqmap = readl_relaxed ( data - > regs +
2019-02-20 14:40:47 +03:00
CHANSTATUS ( idx , data - > reg_num ) ) ;
2018-12-17 17:01:20 +03:00
for_each_set_bit ( pos , & irqmap , 32 ) {
2019-02-20 14:40:51 +03:00
virq = irq_find_mapping ( data - > domain , pos + hwirq ) ;
2018-12-17 17:01:20 +03:00
if ( virq )
generic_handle_irq ( virq ) ;
}
}
chained_irq_exit ( irq_desc_get_chip ( desc ) , desc ) ;
}
static int imx_irqsteer_probe ( struct platform_device * pdev )
{
struct device_node * np = pdev - > dev . of_node ;
struct irqsteer_data * data ;
2019-02-20 14:40:51 +03:00
u32 irqs_num ;
int i , ret ;
2018-12-17 17:01:20 +03:00
data = devm_kzalloc ( & pdev - > dev , sizeof ( * data ) , GFP_KERNEL ) ;
if ( ! data )
return - ENOMEM ;
2019-04-01 09:21:51 +03:00
data - > regs = devm_platform_ioremap_resource ( pdev , 0 ) ;
2018-12-17 17:01:20 +03:00
if ( IS_ERR ( data - > regs ) ) {
dev_err ( & pdev - > dev , " failed to initialize reg \n " ) ;
return PTR_ERR ( data - > regs ) ;
}
data - > ipg_clk = devm_clk_get ( & pdev - > dev , " ipg " ) ;
if ( IS_ERR ( data - > ipg_clk ) ) {
ret = PTR_ERR ( data - > ipg_clk ) ;
if ( ret ! = - EPROBE_DEFER )
dev_err ( & pdev - > dev , " failed to get ipg clk: %d \n " , ret ) ;
return ret ;
}
raw_spin_lock_init ( & data - > lock ) ;
2019-03-04 23:02:18 +03:00
ret = of_property_read_u32 ( np , " fsl,num-irqs " , & irqs_num ) ;
if ( ret )
return ret ;
ret = of_property_read_u32 ( np , " fsl,channel " , & data - > channel ) ;
if ( ret )
return ret ;
2018-12-17 17:01:20 +03:00
2019-02-20 14:40:51 +03:00
/*
* There is one output irq for each group of 64 inputs .
* One register bit map can represent 32 input interrupts .
*/
data - > irq_count = DIV_ROUND_UP ( irqs_num , 64 ) ;
data - > reg_num = irqs_num / 32 ;
2019-02-20 14:40:47 +03:00
2018-12-17 17:01:20 +03:00
if ( IS_ENABLED ( CONFIG_PM_SLEEP ) ) {
data - > saved_reg = devm_kzalloc ( & pdev - > dev ,
2019-02-20 14:40:47 +03:00
sizeof ( u32 ) * data - > reg_num ,
2018-12-17 17:01:20 +03:00
GFP_KERNEL ) ;
if ( ! data - > saved_reg )
return - ENOMEM ;
}
ret = clk_prepare_enable ( data - > ipg_clk ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " failed to enable ipg clk: %d \n " , ret ) ;
return ret ;
}
/* steer all IRQs into configured channel */
writel_relaxed ( BIT ( data - > channel ) , data - > regs + CHANCTRL ) ;
2019-02-20 14:40:47 +03:00
data - > domain = irq_domain_add_linear ( np , data - > reg_num * 32 ,
2018-12-17 17:01:20 +03:00
& imx_irqsteer_domain_ops , data ) ;
if ( ! data - > domain ) {
dev_err ( & pdev - > dev , " failed to create IRQ domain \n " ) ;
2019-02-20 14:40:51 +03:00
ret = - ENOMEM ;
goto out ;
2018-12-17 17:01:20 +03:00
}
2019-02-20 14:40:51 +03:00
if ( ! data - > irq_count | | data - > irq_count > CHAN_MAX_OUTPUT_INT ) {
ret = - EINVAL ;
goto out ;
}
for ( i = 0 ; i < data - > irq_count ; i + + ) {
data - > irq [ i ] = irq_of_parse_and_map ( np , i ) ;
if ( ! data - > irq [ i ] ) {
ret = - EINVAL ;
goto out ;
}
irq_set_chained_handler_and_data ( data - > irq [ i ] ,
imx_irqsteer_irq_handler ,
data ) ;
}
2018-12-17 17:01:20 +03:00
platform_set_drvdata ( pdev , data ) ;
return 0 ;
2019-02-20 14:40:51 +03:00
out :
clk_disable_unprepare ( data - > ipg_clk ) ;
return ret ;
2018-12-17 17:01:20 +03:00
}
static int imx_irqsteer_remove ( struct platform_device * pdev )
{
struct irqsteer_data * irqsteer_data = platform_get_drvdata ( pdev ) ;
2019-02-20 14:40:51 +03:00
int i ;
for ( i = 0 ; i < irqsteer_data - > irq_count ; i + + )
irq_set_chained_handler_and_data ( irqsteer_data - > irq [ i ] ,
NULL , NULL ) ;
2018-12-17 17:01:20 +03:00
irq_domain_remove ( irqsteer_data - > domain ) ;
clk_disable_unprepare ( irqsteer_data - > ipg_clk ) ;
return 0 ;
}
# ifdef CONFIG_PM_SLEEP
static void imx_irqsteer_save_regs ( struct irqsteer_data * data )
{
int i ;
2019-02-20 14:40:47 +03:00
for ( i = 0 ; i < data - > reg_num ; i + + )
2018-12-17 17:01:20 +03:00
data - > saved_reg [ i ] = readl_relaxed ( data - > regs +
2019-02-20 14:40:47 +03:00
CHANMASK ( i , data - > reg_num ) ) ;
2018-12-17 17:01:20 +03:00
}
static void imx_irqsteer_restore_regs ( struct irqsteer_data * data )
{
int i ;
writel_relaxed ( BIT ( data - > channel ) , data - > regs + CHANCTRL ) ;
2019-02-20 14:40:47 +03:00
for ( i = 0 ; i < data - > reg_num ; i + + )
2018-12-17 17:01:20 +03:00
writel_relaxed ( data - > saved_reg [ i ] ,
2019-02-20 14:40:47 +03:00
data - > regs + CHANMASK ( i , data - > reg_num ) ) ;
2018-12-17 17:01:20 +03:00
}
static int imx_irqsteer_suspend ( struct device * dev )
{
struct irqsteer_data * irqsteer_data = dev_get_drvdata ( dev ) ;
imx_irqsteer_save_regs ( irqsteer_data ) ;
clk_disable_unprepare ( irqsteer_data - > ipg_clk ) ;
return 0 ;
}
static int imx_irqsteer_resume ( struct device * dev )
{
struct irqsteer_data * irqsteer_data = dev_get_drvdata ( dev ) ;
int ret ;
ret = clk_prepare_enable ( irqsteer_data - > ipg_clk ) ;
if ( ret ) {
dev_err ( dev , " failed to enable ipg clk: %d \n " , ret ) ;
return ret ;
}
imx_irqsteer_restore_regs ( irqsteer_data ) ;
return 0 ;
}
# endif
static const struct dev_pm_ops imx_irqsteer_pm_ops = {
SET_NOIRQ_SYSTEM_SLEEP_PM_OPS ( imx_irqsteer_suspend , imx_irqsteer_resume )
} ;
static const struct of_device_id imx_irqsteer_dt_ids [ ] = {
{ . compatible = " fsl,imx-irqsteer " , } ,
{ } ,
} ;
static struct platform_driver imx_irqsteer_driver = {
. driver = {
. name = " imx-irqsteer " ,
. of_match_table = imx_irqsteer_dt_ids ,
. pm = & imx_irqsteer_pm_ops ,
} ,
. probe = imx_irqsteer_probe ,
. remove = imx_irqsteer_remove ,
} ;
builtin_platform_driver ( imx_irqsteer_driver ) ;