2019-05-27 09:55:21 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2017-08-23 04:31:47 +03:00
/*
* Driver for UniPhier AIDET ( ARM Interrupt Detector )
*
* Copyright ( C ) 2017 Socionext Inc .
* Author : Masahiro Yamada < yamada . masahiro @ socionext . com >
*/
# include <linux/bitops.h>
# include <linux/init.h>
# include <linux/irq.h>
# include <linux/irqdomain.h>
# include <linux/kernel.h>
# include <linux/of.h>
# include <linux/of_device.h>
# include <linux/of_irq.h>
# include <linux/platform_device.h>
# include <linux/spinlock.h>
# define UNIPHIER_AIDET_NR_IRQS 256
# define UNIPHIER_AIDET_DETCONF 0x04 /* inverter register base */
struct uniphier_aidet_priv {
struct irq_domain * domain ;
void __iomem * reg_base ;
spinlock_t lock ;
u32 saved_vals [ UNIPHIER_AIDET_NR_IRQS / 32 ] ;
} ;
static void uniphier_aidet_reg_update ( struct uniphier_aidet_priv * priv ,
unsigned int reg , u32 mask , u32 val )
{
unsigned long flags ;
u32 tmp ;
spin_lock_irqsave ( & priv - > lock , flags ) ;
tmp = readl_relaxed ( priv - > reg_base + reg ) ;
tmp & = ~ mask ;
tmp | = mask & val ;
writel_relaxed ( tmp , priv - > reg_base + reg ) ;
spin_unlock_irqrestore ( & priv - > lock , flags ) ;
}
static void uniphier_aidet_detconf_update ( struct uniphier_aidet_priv * priv ,
unsigned long index , unsigned int val )
{
unsigned int reg ;
u32 mask ;
reg = UNIPHIER_AIDET_DETCONF + index / 32 * 4 ;
mask = BIT ( index % 32 ) ;
uniphier_aidet_reg_update ( priv , reg , mask , val ? mask : 0 ) ;
}
static int uniphier_aidet_irq_set_type ( struct irq_data * data , unsigned int type )
{
struct uniphier_aidet_priv * priv = data - > chip_data ;
unsigned int val ;
/* enable inverter for active low triggers */
switch ( type ) {
case IRQ_TYPE_EDGE_RISING :
case IRQ_TYPE_LEVEL_HIGH :
val = 0 ;
break ;
case IRQ_TYPE_EDGE_FALLING :
val = 1 ;
type = IRQ_TYPE_EDGE_RISING ;
break ;
case IRQ_TYPE_LEVEL_LOW :
val = 1 ;
type = IRQ_TYPE_LEVEL_HIGH ;
break ;
default :
return - EINVAL ;
}
uniphier_aidet_detconf_update ( priv , data - > hwirq , val ) ;
return irq_chip_set_type_parent ( data , type ) ;
}
static struct irq_chip uniphier_aidet_irq_chip = {
. name = " AIDET " ,
. irq_mask = irq_chip_mask_parent ,
. irq_unmask = irq_chip_unmask_parent ,
. irq_eoi = irq_chip_eoi_parent ,
. irq_set_affinity = irq_chip_set_affinity_parent ,
. irq_set_type = uniphier_aidet_irq_set_type ,
} ;
static int uniphier_aidet_domain_translate ( struct irq_domain * domain ,
struct irq_fwspec * fwspec ,
unsigned long * out_hwirq ,
unsigned int * out_type )
{
if ( WARN_ON ( fwspec - > param_count < 2 ) )
return - EINVAL ;
* out_hwirq = fwspec - > param [ 0 ] ;
* out_type = fwspec - > param [ 1 ] & IRQ_TYPE_SENSE_MASK ;
return 0 ;
}
static int uniphier_aidet_domain_alloc ( struct irq_domain * domain ,
unsigned int virq , unsigned int nr_irqs ,
void * arg )
{
struct irq_fwspec parent_fwspec ;
irq_hw_number_t hwirq ;
unsigned int type ;
int ret ;
if ( nr_irqs ! = 1 )
return - EINVAL ;
ret = uniphier_aidet_domain_translate ( domain , arg , & hwirq , & type ) ;
if ( ret )
return ret ;
switch ( type ) {
case IRQ_TYPE_EDGE_RISING :
case IRQ_TYPE_LEVEL_HIGH :
break ;
case IRQ_TYPE_EDGE_FALLING :
type = IRQ_TYPE_EDGE_RISING ;
break ;
case IRQ_TYPE_LEVEL_LOW :
type = IRQ_TYPE_LEVEL_HIGH ;
break ;
default :
return - EINVAL ;
}
if ( hwirq > = UNIPHIER_AIDET_NR_IRQS )
return - ENXIO ;
ret = irq_domain_set_hwirq_and_chip ( domain , virq , hwirq ,
& uniphier_aidet_irq_chip ,
domain - > host_data ) ;
if ( ret )
return ret ;
/* parent is GIC */
parent_fwspec . fwnode = domain - > parent - > fwnode ;
parent_fwspec . param_count = 3 ;
parent_fwspec . param [ 0 ] = 0 ; /* SPI */
parent_fwspec . param [ 1 ] = hwirq ;
parent_fwspec . param [ 2 ] = type ;
return irq_domain_alloc_irqs_parent ( domain , virq , 1 , & parent_fwspec ) ;
}
static const struct irq_domain_ops uniphier_aidet_domain_ops = {
. alloc = uniphier_aidet_domain_alloc ,
. free = irq_domain_free_irqs_common ,
. translate = uniphier_aidet_domain_translate ,
} ;
static int uniphier_aidet_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
struct device_node * parent_np ;
struct irq_domain * parent_domain ;
struct uniphier_aidet_priv * priv ;
struct resource * res ;
parent_np = of_irq_find_parent ( dev - > of_node ) ;
if ( ! parent_np )
return - ENXIO ;
parent_domain = irq_find_host ( parent_np ) ;
of_node_put ( parent_np ) ;
if ( ! parent_domain )
return - EPROBE_DEFER ;
priv = devm_kzalloc ( dev , sizeof ( * priv ) , GFP_KERNEL ) ;
if ( ! priv )
return - ENOMEM ;
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
priv - > reg_base = devm_ioremap_resource ( dev , res ) ;
if ( IS_ERR ( priv - > reg_base ) )
return PTR_ERR ( priv - > reg_base ) ;
spin_lock_init ( & priv - > lock ) ;
priv - > domain = irq_domain_create_hierarchy (
parent_domain , 0 ,
UNIPHIER_AIDET_NR_IRQS ,
of_node_to_fwnode ( dev - > of_node ) ,
& uniphier_aidet_domain_ops , priv ) ;
if ( ! priv - > domain )
return - ENOMEM ;
platform_set_drvdata ( pdev , priv ) ;
return 0 ;
}
static int __maybe_unused uniphier_aidet_suspend ( struct device * dev )
{
struct uniphier_aidet_priv * priv = dev_get_drvdata ( dev ) ;
int i ;
for ( i = 0 ; i < ARRAY_SIZE ( priv - > saved_vals ) ; i + + )
priv - > saved_vals [ i ] = readl_relaxed (
priv - > reg_base + UNIPHIER_AIDET_DETCONF + i * 4 ) ;
return 0 ;
}
static int __maybe_unused uniphier_aidet_resume ( struct device * dev )
{
struct uniphier_aidet_priv * priv = dev_get_drvdata ( dev ) ;
int i ;
for ( i = 0 ; i < ARRAY_SIZE ( priv - > saved_vals ) ; i + + )
writel_relaxed ( priv - > saved_vals [ i ] ,
priv - > reg_base + UNIPHIER_AIDET_DETCONF + i * 4 ) ;
return 0 ;
}
static const struct dev_pm_ops uniphier_aidet_pm_ops = {
SET_NOIRQ_SYSTEM_SLEEP_PM_OPS ( uniphier_aidet_suspend ,
uniphier_aidet_resume )
} ;
static const struct of_device_id uniphier_aidet_match [ ] = {
{ . compatible = " socionext,uniphier-ld4-aidet " } ,
{ . compatible = " socionext,uniphier-pro4-aidet " } ,
{ . compatible = " socionext,uniphier-sld8-aidet " } ,
{ . compatible = " socionext,uniphier-pro5-aidet " } ,
{ . compatible = " socionext,uniphier-pxs2-aidet " } ,
{ . compatible = " socionext,uniphier-ld11-aidet " } ,
{ . compatible = " socionext,uniphier-ld20-aidet " } ,
{ . compatible = " socionext,uniphier-pxs3-aidet " } ,
{ /* sentinel */ }
} ;
static struct platform_driver uniphier_aidet_driver = {
. probe = uniphier_aidet_probe ,
. driver = {
. name = " uniphier-aidet " ,
. of_match_table = uniphier_aidet_match ,
. pm = & uniphier_aidet_pm_ops ,
} ,
} ;
builtin_platform_driver ( uniphier_aidet_driver ) ;