2019-06-10 11:34:43 +03:00
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2019 Amazon . com , Inc . or its affiliates . All Rights Reserved .
*/
# include <linux/bitfield.h>
# include <linux/irq.h>
# include <linux/irqchip.h>
# include <linux/irqchip/chained_irq.h>
# include <linux/irqdomain.h>
# include <linux/module.h>
# include <linux/of.h>
# include <linux/of_address.h>
# include <linux/of_irq.h>
/* FIC Registers */
# define AL_FIC_CAUSE 0x00
2019-09-09 11:39:18 +03:00
# define AL_FIC_SET_CAUSE 0x08
2019-06-10 11:34:43 +03:00
# define AL_FIC_MASK 0x10
# define AL_FIC_CONTROL 0x28
# define CONTROL_TRIGGER_RISING BIT(3)
# define CONTROL_MASK_MSI_X BIT(5)
# define NR_FIC_IRQS 32
MODULE_AUTHOR ( " Talel Shenhar " ) ;
MODULE_DESCRIPTION ( " Amazon's Annapurna Labs Interrupt Controller Driver " ) ;
enum al_fic_state {
AL_FIC_UNCONFIGURED = 0 ,
AL_FIC_CONFIGURED_LEVEL ,
AL_FIC_CONFIGURED_RISING_EDGE ,
} ;
struct al_fic {
void __iomem * base ;
struct irq_domain * domain ;
const char * name ;
unsigned int parent_irq ;
enum al_fic_state state ;
} ;
static void al_fic_set_trigger ( struct al_fic * fic ,
struct irq_chip_generic * gc ,
enum al_fic_state new_state )
{
irq_flow_handler_t handler ;
u32 control = readl_relaxed ( fic - > base + AL_FIC_CONTROL ) ;
if ( new_state = = AL_FIC_CONFIGURED_LEVEL ) {
handler = handle_level_irq ;
control & = ~ CONTROL_TRIGGER_RISING ;
} else {
handler = handle_edge_irq ;
control | = CONTROL_TRIGGER_RISING ;
}
gc - > chip_types - > handler = handler ;
fic - > state = new_state ;
writel_relaxed ( control , fic - > base + AL_FIC_CONTROL ) ;
}
static int al_fic_irq_set_type ( struct irq_data * data , unsigned int flow_type )
{
struct irq_chip_generic * gc = irq_data_get_irq_chip_data ( data ) ;
struct al_fic * fic = gc - > private ;
enum al_fic_state new_state ;
int ret = 0 ;
irq_gc_lock ( gc ) ;
if ( ( ( flow_type & IRQ_TYPE_SENSE_MASK ) ! = IRQ_TYPE_LEVEL_HIGH ) & &
( ( flow_type & IRQ_TYPE_SENSE_MASK ) ! = IRQ_TYPE_EDGE_RISING ) ) {
pr_debug ( " fic doesn't support flow type %d \n " , flow_type ) ;
ret = - EINVAL ;
goto err ;
}
new_state = ( flow_type & IRQ_TYPE_LEVEL_HIGH ) ?
AL_FIC_CONFIGURED_LEVEL : AL_FIC_CONFIGURED_RISING_EDGE ;
/*
* A given FIC instance can be either all level or all edge triggered .
* This is generally fixed depending on what pieces of HW it ' s wired up
* to .
*
* We configure it based on the sensitivity of the first source
* being setup , and reject any subsequent attempt at configuring it in a
* different way .
*/
if ( fic - > state = = AL_FIC_UNCONFIGURED ) {
al_fic_set_trigger ( fic , gc , new_state ) ;
} else if ( fic - > state ! = new_state ) {
pr_debug ( " fic %s state already configured to %d \n " ,
fic - > name , fic - > state ) ;
ret = - EINVAL ;
goto err ;
}
err :
irq_gc_unlock ( gc ) ;
return ret ;
}
static void al_fic_irq_handler ( struct irq_desc * desc )
{
struct al_fic * fic = irq_desc_get_handler_data ( desc ) ;
struct irq_domain * domain = fic - > domain ;
struct irq_chip * irqchip = irq_desc_get_chip ( desc ) ;
struct irq_chip_generic * gc = irq_get_domain_generic_chip ( domain , 0 ) ;
unsigned long pending ;
u32 hwirq ;
chained_irq_enter ( irqchip , desc ) ;
pending = readl_relaxed ( fic - > base + AL_FIC_CAUSE ) ;
pending & = ~ gc - > mask_cache ;
2021-05-04 19:42:18 +03:00
for_each_set_bit ( hwirq , & pending , NR_FIC_IRQS )
generic_handle_domain_irq ( domain , hwirq ) ;
2019-06-10 11:34:43 +03:00
chained_irq_exit ( irqchip , desc ) ;
}
2019-09-09 11:39:18 +03:00
static int al_fic_irq_retrigger ( struct irq_data * data )
{
struct irq_chip_generic * gc = irq_data_get_irq_chip_data ( data ) ;
struct al_fic * fic = gc - > private ;
writel_relaxed ( BIT ( data - > hwirq ) , fic - > base + AL_FIC_SET_CAUSE ) ;
return 1 ;
}
2019-06-10 11:34:43 +03:00
static int al_fic_register ( struct device_node * node ,
struct al_fic * fic )
{
struct irq_chip_generic * gc ;
int ret ;
fic - > domain = irq_domain_add_linear ( node ,
NR_FIC_IRQS ,
& irq_generic_chip_ops ,
fic ) ;
if ( ! fic - > domain ) {
pr_err ( " fail to add irq domain \n " ) ;
return - ENOMEM ;
}
ret = irq_alloc_domain_generic_chips ( fic - > domain ,
NR_FIC_IRQS ,
1 , fic - > name ,
handle_level_irq ,
0 , 0 , IRQ_GC_INIT_MASK_CACHE ) ;
if ( ret ) {
pr_err ( " fail to allocate generic chip (%d) \n " , ret ) ;
goto err_domain_remove ;
}
gc = irq_get_domain_generic_chip ( fic - > domain , 0 ) ;
gc - > reg_base = fic - > base ;
gc - > chip_types - > regs . mask = AL_FIC_MASK ;
gc - > chip_types - > regs . ack = AL_FIC_CAUSE ;
gc - > chip_types - > chip . irq_mask = irq_gc_mask_set_bit ;
gc - > chip_types - > chip . irq_unmask = irq_gc_mask_clr_bit ;
gc - > chip_types - > chip . irq_ack = irq_gc_ack_clr_bit ;
gc - > chip_types - > chip . irq_set_type = al_fic_irq_set_type ;
2019-09-09 11:39:18 +03:00
gc - > chip_types - > chip . irq_retrigger = al_fic_irq_retrigger ;
2019-06-10 11:34:43 +03:00
gc - > chip_types - > chip . flags = IRQCHIP_SKIP_SET_WAKE ;
gc - > private = fic ;
irq_set_chained_handler_and_data ( fic - > parent_irq ,
al_fic_irq_handler ,
fic ) ;
return 0 ;
err_domain_remove :
irq_domain_remove ( fic - > domain ) ;
return ret ;
}
/*
* al_fic_wire_init ( ) - initialize and configure fic in wire mode
* @ of_node : optional pointer to interrupt controller ' s device tree node .
* @ base : mmio to fic register
* @ name : name of the fic
* @ parent_irq : interrupt of parent
*
* This API will configure the fic hardware to to work in wire mode .
* In wire mode , fic hardware is generating a wire ( " wired " ) interrupt .
* Interrupt can be generated based on positive edge or level - configuration is
* to be determined based on connected hardware to this fic .
*/
static struct al_fic * al_fic_wire_init ( struct device_node * node ,
void __iomem * base ,
const char * name ,
unsigned int parent_irq )
{
struct al_fic * fic ;
int ret ;
u32 control = CONTROL_MASK_MSI_X ;
fic = kzalloc ( sizeof ( * fic ) , GFP_KERNEL ) ;
if ( ! fic )
return ERR_PTR ( - ENOMEM ) ;
fic - > base = base ;
fic - > parent_irq = parent_irq ;
fic - > name = name ;
/* mask out all interrupts */
writel_relaxed ( 0xFFFFFFFF , fic - > base + AL_FIC_MASK ) ;
/* clear any pending interrupt */
writel_relaxed ( 0 , fic - > base + AL_FIC_CAUSE ) ;
writel_relaxed ( control , fic - > base + AL_FIC_CONTROL ) ;
ret = al_fic_register ( node , fic ) ;
if ( ret ) {
pr_err ( " fail to register irqchip \n " ) ;
goto err_free ;
}
pr_debug ( " %s initialized successfully in Legacy mode (parent-irq=%u) \n " ,
fic - > name , parent_irq ) ;
return fic ;
err_free :
kfree ( fic ) ;
return ERR_PTR ( ret ) ;
}
static int __init al_fic_init_dt ( struct device_node * node ,
struct device_node * parent )
{
int ret ;
void __iomem * base ;
unsigned int parent_irq ;
struct al_fic * fic ;
if ( ! parent ) {
pr_err ( " %s: unsupported - device require a parent \n " ,
node - > name ) ;
return - EINVAL ;
}
base = of_iomap ( node , 0 ) ;
if ( ! base ) {
pr_err ( " %s: fail to map memory \n " , node - > name ) ;
return - ENOMEM ;
}
parent_irq = irq_of_parse_and_map ( node , 0 ) ;
if ( ! parent_irq ) {
pr_err ( " %s: fail to map irq \n " , node - > name ) ;
ret = - EINVAL ;
goto err_unmap ;
}
fic = al_fic_wire_init ( node ,
base ,
node - > name ,
parent_irq ) ;
if ( IS_ERR ( fic ) ) {
pr_err ( " %s: fail to initialize irqchip (%lu) \n " ,
node - > name ,
PTR_ERR ( fic ) ) ;
ret = PTR_ERR ( fic ) ;
goto err_irq_dispose ;
}
return 0 ;
err_irq_dispose :
irq_dispose_mapping ( parent_irq ) ;
err_unmap :
iounmap ( base ) ;
return ret ;
}
IRQCHIP_DECLARE ( al_fic , " amazon,al-fic " , al_fic_init_dt ) ;