2018-12-05 11:24:48 +03:00
// SPDX-License-Identifier: GPL-2.0
2013-02-27 12:15:01 +04:00
/*
* Renesas IRQC Driver
*
* Copyright ( C ) 2013 Magnus Damm
*/
# include <linux/init.h>
# include <linux/platform_device.h>
# include <linux/interrupt.h>
# include <linux/ioport.h>
# include <linux/io.h>
# include <linux/irq.h>
# include <linux/irqdomain.h>
# include <linux/err.h>
# include <linux/slab.h>
# include <linux/module.h>
2015-03-18 21:55:56 +03:00
# include <linux/pm_runtime.h>
2013-02-27 12:15:01 +04:00
2015-03-18 21:55:55 +03:00
# define IRQC_IRQ_MAX 32 /* maximum 32 interrupts per driver instance */
2013-02-27 12:15:01 +04:00
2015-03-18 21:55:55 +03:00
# define IRQC_REQ_STS 0x00 /* Interrupt Request Status Register */
# define IRQC_EN_STS 0x04 /* Interrupt Enable Status Register */
# define IRQC_EN_SET 0x08 /* Interrupt Enable Set Register */
2013-02-27 12:15:01 +04:00
# define IRQC_INT_CPU_BASE(n) (0x000 + ((n) * 0x10))
2015-03-18 21:55:55 +03:00
/* SYS-CPU vs. RT-CPU */
# define DETECT_STATUS 0x100 /* IRQn Detect Status Register */
# define MONITOR 0x104 /* IRQn Signal Level Monitor Register */
# define HLVL_STS 0x108 /* IRQn High Level Detect Status Register */
# define LLVL_STS 0x10c /* IRQn Low Level Detect Status Register */
# define S_R_EDGE_STS 0x110 /* IRQn Sync Rising Edge Detect Status Reg. */
# define S_F_EDGE_STS 0x114 /* IRQn Sync Falling Edge Detect Status Reg. */
# define A_R_EDGE_STS 0x118 /* IRQn Async Rising Edge Detect Status Reg. */
# define A_F_EDGE_STS 0x11c /* IRQn Async Falling Edge Detect Status Reg. */
# define CHTEN_STS 0x120 /* Chattering Reduction Status Register */
2013-02-27 12:15:01 +04:00
# define IRQC_CONFIG(n) (0x180 + ((n) * 0x04))
2015-03-18 21:55:55 +03:00
/* IRQn Configuration Register */
2013-02-27 12:15:01 +04:00
struct irqc_irq {
int hw_irq ;
int requested_irq ;
struct irqc_priv * p ;
} ;
struct irqc_priv {
void __iomem * iomem ;
void __iomem * cpu_int_base ;
struct irqc_irq irq [ IRQC_IRQ_MAX ] ;
unsigned int number_of_irqs ;
2019-05-27 15:04:11 +03:00
struct device * dev ;
2015-09-28 12:42:37 +03:00
struct irq_chip_generic * gc ;
2013-02-27 12:15:01 +04:00
struct irq_domain * irq_domain ;
2018-02-12 16:55:12 +03:00
atomic_t wakeup_path ;
2013-02-27 12:15:01 +04:00
} ;
2015-09-28 12:42:37 +03:00
static struct irqc_priv * irq_data_to_priv ( struct irq_data * data )
2013-02-27 12:15:01 +04:00
{
2015-09-28 12:42:37 +03:00
return data - > domain - > host_data ;
2013-02-27 12:15:01 +04:00
}
2015-09-28 12:42:37 +03:00
static void irqc_dbg ( struct irqc_irq * i , char * str )
2013-02-27 12:15:01 +04:00
{
2019-05-27 15:04:11 +03:00
dev_dbg ( i - > p - > dev , " %s (%d:%d) \n " , str , i - > requested_irq , i - > hw_irq ) ;
2013-02-27 12:15:01 +04:00
}
static unsigned char irqc_sense [ IRQ_TYPE_SENSE_MASK + 1 ] = {
2013-12-14 04:09:31 +04:00
[ IRQ_TYPE_LEVEL_LOW ] = 0x01 ,
[ IRQ_TYPE_LEVEL_HIGH ] = 0x02 ,
[ IRQ_TYPE_EDGE_FALLING ] = 0x04 , /* Synchronous */
[ IRQ_TYPE_EDGE_RISING ] = 0x08 , /* Synchronous */
[ IRQ_TYPE_EDGE_BOTH ] = 0x0c , /* Synchronous */
2013-02-27 12:15:01 +04:00
} ;
static int irqc_irq_set_type ( struct irq_data * d , unsigned int type )
{
2015-09-28 12:42:37 +03:00
struct irqc_priv * p = irq_data_to_priv ( d ) ;
2013-02-27 12:15:01 +04:00
int hw_irq = irqd_to_hwirq ( d ) ;
unsigned char value = irqc_sense [ type & IRQ_TYPE_SENSE_MASK ] ;
2015-02-26 13:43:32 +03:00
u32 tmp ;
2013-02-27 12:15:01 +04:00
irqc_dbg ( & p - > irq [ hw_irq ] , " sense " ) ;
2013-12-14 04:09:31 +04:00
if ( ! value )
2013-02-27 12:15:01 +04:00
return - EINVAL ;
tmp = ioread32 ( p - > iomem + IRQC_CONFIG ( hw_irq ) ) ;
tmp & = ~ 0x3f ;
2013-12-14 04:09:31 +04:00
tmp | = value ;
2013-02-27 12:15:01 +04:00
iowrite32 ( tmp , p - > iomem + IRQC_CONFIG ( hw_irq ) ) ;
return 0 ;
}
2015-04-01 15:00:06 +03:00
static int irqc_irq_set_wake ( struct irq_data * d , unsigned int on )
{
2015-09-28 12:42:37 +03:00
struct irqc_priv * p = irq_data_to_priv ( d ) ;
2015-09-08 20:00:36 +03:00
int hw_irq = irqd_to_hwirq ( d ) ;
irq_set_irq_wake ( p - > irq [ hw_irq ] . requested_irq , on ) ;
2015-04-01 15:00:06 +03:00
if ( on )
2018-02-12 16:55:12 +03:00
atomic_inc ( & p - > wakeup_path ) ;
2015-04-01 15:00:06 +03:00
else
2018-02-12 16:55:12 +03:00
atomic_dec ( & p - > wakeup_path ) ;
2015-04-01 15:00:06 +03:00
return 0 ;
}
2013-02-27 12:15:01 +04:00
static irqreturn_t irqc_irq_handler ( int irq , void * dev_id )
{
struct irqc_irq * i = dev_id ;
struct irqc_priv * p = i - > p ;
2015-02-26 13:43:32 +03:00
u32 bit = BIT ( i - > hw_irq ) ;
2013-02-27 12:15:01 +04:00
irqc_dbg ( i , " demux1 " ) ;
if ( ioread32 ( p - > iomem + DETECT_STATUS ) & bit ) {
iowrite32 ( bit , p - > iomem + DETECT_STATUS ) ;
irqc_dbg ( i , " demux2 " ) ;
2021-05-04 19:42:18 +03:00
generic_handle_domain_irq ( p - > irq_domain , i - > hw_irq ) ;
2013-02-27 12:15:01 +04:00
return IRQ_HANDLED ;
}
return IRQ_NONE ;
}
static int irqc_probe ( struct platform_device * pdev )
{
2019-05-27 15:04:10 +03:00
struct device * dev = & pdev - > dev ;
const char * name = dev_name ( dev ) ;
2013-02-27 12:15:01 +04:00
struct irqc_priv * p ;
int ret ;
int k ;
2019-05-27 15:04:12 +03:00
p = devm_kzalloc ( dev , sizeof ( * p ) , GFP_KERNEL ) ;
if ( ! p )
return - ENOMEM ;
2013-02-27 12:15:01 +04:00
2019-05-27 15:04:11 +03:00
p - > dev = dev ;
2013-02-27 12:15:01 +04:00
platform_set_drvdata ( pdev , p ) ;
2019-05-27 15:04:10 +03:00
pm_runtime_enable ( dev ) ;
pm_runtime_get_sync ( dev ) ;
2015-03-18 21:55:56 +03:00
2013-02-27 12:15:01 +04:00
/* allow any number of IRQs between 1 and IRQC_IRQ_MAX */
for ( k = 0 ; k < IRQC_IRQ_MAX ; k + + ) {
2021-12-16 21:21:21 +03:00
ret = platform_get_irq_optional ( pdev , k ) ;
if ( ret = = - ENXIO )
2013-02-27 12:15:01 +04:00
break ;
2021-12-16 21:21:21 +03:00
if ( ret < 0 )
goto err_runtime_pm_disable ;
2013-02-27 12:15:01 +04:00
p - > irq [ k ] . p = p ;
2015-07-20 13:06:35 +03:00
p - > irq [ k ] . hw_irq = k ;
2021-12-16 21:21:21 +03:00
p - > irq [ k ] . requested_irq = ret ;
2013-02-27 12:15:01 +04:00
}
p - > number_of_irqs = k ;
if ( p - > number_of_irqs < 1 ) {
2019-05-27 15:04:10 +03:00
dev_err ( dev , " not enough IRQ resources \n " ) ;
2013-02-27 12:15:01 +04:00
ret = - EINVAL ;
2019-05-27 15:04:12 +03:00
goto err_runtime_pm_disable ;
2013-02-27 12:15:01 +04:00
}
/* ioremap IOMEM and setup read/write callbacks */
2019-05-27 15:04:12 +03:00
p - > iomem = devm_platform_ioremap_resource ( pdev , 0 ) ;
if ( IS_ERR ( p - > iomem ) ) {
ret = PTR_ERR ( p - > iomem ) ;
goto err_runtime_pm_disable ;
2013-02-27 12:15:01 +04:00
}
p - > cpu_int_base = p - > iomem + IRQC_INT_CPU_BASE ( 0 ) ; /* SYS-SPI */
2019-05-27 15:04:10 +03:00
p - > irq_domain = irq_domain_add_linear ( dev - > of_node , p - > number_of_irqs ,
2015-09-28 12:42:37 +03:00
& irq_generic_chip_ops , p ) ;
2013-02-27 12:15:01 +04:00
if ( ! p - > irq_domain ) {
ret = - ENXIO ;
2019-05-27 15:04:10 +03:00
dev_err ( dev , " cannot initialize irq domain \n " ) ;
2019-05-27 15:04:12 +03:00
goto err_runtime_pm_disable ;
2013-02-27 12:15:01 +04:00
}
2015-09-28 12:42:37 +03:00
ret = irq_alloc_domain_generic_chips ( p - > irq_domain , p - > number_of_irqs ,
2019-06-07 12:58:56 +03:00
1 , " irqc " , handle_level_irq ,
2015-09-28 12:42:37 +03:00
0 , 0 , IRQ_GC_INIT_NESTED_LOCK ) ;
if ( ret ) {
2019-05-27 15:04:10 +03:00
dev_err ( dev , " cannot allocate generic chip \n " ) ;
2019-05-27 15:04:12 +03:00
goto err_remove_domain ;
2015-09-28 12:42:37 +03:00
}
p - > gc = irq_get_domain_generic_chip ( p - > irq_domain , 0 ) ;
p - > gc - > reg_base = p - > cpu_int_base ;
p - > gc - > chip_types [ 0 ] . regs . enable = IRQC_EN_SET ;
p - > gc - > chip_types [ 0 ] . regs . disable = IRQC_EN_STS ;
p - > gc - > chip_types [ 0 ] . chip . irq_mask = irq_gc_mask_disable_reg ;
p - > gc - > chip_types [ 0 ] . chip . irq_unmask = irq_gc_unmask_enable_reg ;
p - > gc - > chip_types [ 0 ] . chip . irq_set_type = irqc_irq_set_type ;
p - > gc - > chip_types [ 0 ] . chip . irq_set_wake = irqc_irq_set_wake ;
p - > gc - > chip_types [ 0 ] . chip . flags = IRQCHIP_MASK_ON_SUSPEND ;
2022-02-01 15:03:02 +03:00
irq_domain_set_pm_device ( p - > irq_domain , dev ) ;
2013-02-27 12:15:01 +04:00
/* request interrupts one by one */
for ( k = 0 ; k < p - > number_of_irqs ; k + + ) {
2019-05-27 15:04:12 +03:00
if ( devm_request_irq ( dev , p - > irq [ k ] . requested_irq ,
irqc_irq_handler , 0 , name , & p - > irq [ k ] ) ) {
2019-05-27 15:04:10 +03:00
dev_err ( dev , " failed to request IRQ \n " ) ;
2013-02-27 12:15:01 +04:00
ret = - ENOENT ;
2019-05-27 15:04:12 +03:00
goto err_remove_domain ;
2013-02-27 12:15:01 +04:00
}
}
2019-05-27 15:04:10 +03:00
dev_info ( dev , " driving %d irqs \n " , p - > number_of_irqs ) ;
2013-02-27 12:15:01 +04:00
return 0 ;
2019-05-27 15:04:12 +03:00
err_remove_domain :
2013-02-27 12:15:01 +04:00
irq_domain_remove ( p - > irq_domain ) ;
2019-05-27 15:04:12 +03:00
err_runtime_pm_disable :
2019-05-27 15:04:10 +03:00
pm_runtime_put ( dev ) ;
pm_runtime_disable ( dev ) ;
2013-02-27 12:15:01 +04:00
return ret ;
}
static int irqc_remove ( struct platform_device * pdev )
{
struct irqc_priv * p = platform_get_drvdata ( pdev ) ;
irq_domain_remove ( p - > irq_domain ) ;
2015-03-18 21:55:56 +03:00
pm_runtime_put ( & pdev - > dev ) ;
pm_runtime_disable ( & pdev - > dev ) ;
2013-02-27 12:15:01 +04:00
return 0 ;
}
2018-02-12 16:55:12 +03:00
static int __maybe_unused irqc_suspend ( struct device * dev )
{
struct irqc_priv * p = dev_get_drvdata ( dev ) ;
if ( atomic_read ( & p - > wakeup_path ) )
device_set_wakeup_path ( dev ) ;
return 0 ;
}
static SIMPLE_DEV_PM_OPS ( irqc_pm_ops , irqc_suspend , NULL ) ;
2013-03-06 10:23:39 +04:00
static const struct of_device_id irqc_dt_ids [ ] = {
{ . compatible = " renesas,irqc " , } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , irqc_dt_ids ) ;
2013-02-27 12:15:01 +04:00
static struct platform_driver irqc_device_driver = {
. probe = irqc_probe ,
. remove = irqc_remove ,
. driver = {
. name = " renesas_irqc " ,
2013-03-06 10:23:39 +04:00
. of_match_table = irqc_dt_ids ,
2018-02-12 16:55:12 +03:00
. pm = & irqc_pm_ops ,
2013-02-27 12:15:01 +04:00
}
} ;
static int __init irqc_init ( void )
{
return platform_driver_register ( & irqc_device_driver ) ;
}
postcore_initcall ( irqc_init ) ;
static void __exit irqc_exit ( void )
{
platform_driver_unregister ( & irqc_device_driver ) ;
}
module_exit ( irqc_exit ) ;
MODULE_AUTHOR ( " Magnus Damm " ) ;
MODULE_DESCRIPTION ( " Renesas IRQC Driver " ) ;