2020-01-16 00:29:40 +03:00
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Aspeed AST24XX , AST25XX , and AST26XX SCU Interrupt Controller
* Copyright 2019 IBM Corporation
*
* Eddie James < eajames @ linux . ibm . com >
*/
# include <linux/bitops.h>
# include <linux/irq.h>
# include <linux/irqchip.h>
# include <linux/irqchip/chained_irq.h>
# include <linux/irqdomain.h>
# include <linux/mfd/syscon.h>
# include <linux/of_irq.h>
# include <linux/regmap.h>
# define ASPEED_SCU_IC_REG 0x018
# define ASPEED_SCU_IC_SHIFT 0
# define ASPEED_SCU_IC_ENABLE GENMASK(6, ASPEED_SCU_IC_SHIFT)
# define ASPEED_SCU_IC_NUM_IRQS 7
# define ASPEED_SCU_IC_STATUS_SHIFT 16
# define ASPEED_AST2600_SCU_IC0_REG 0x560
# define ASPEED_AST2600_SCU_IC0_SHIFT 0
# define ASPEED_AST2600_SCU_IC0_ENABLE \
GENMASK ( 5 , ASPEED_AST2600_SCU_IC0_SHIFT )
# define ASPEED_AST2600_SCU_IC0_NUM_IRQS 6
# define ASPEED_AST2600_SCU_IC1_REG 0x570
# define ASPEED_AST2600_SCU_IC1_SHIFT 4
# define ASPEED_AST2600_SCU_IC1_ENABLE \
GENMASK ( 5 , ASPEED_AST2600_SCU_IC1_SHIFT )
# define ASPEED_AST2600_SCU_IC1_NUM_IRQS 2
struct aspeed_scu_ic {
unsigned long irq_enable ;
unsigned long irq_shift ;
unsigned int num_irqs ;
unsigned int reg ;
struct regmap * scu ;
struct irq_domain * irq_domain ;
} ;
static void aspeed_scu_ic_irq_handler ( struct irq_desc * desc )
{
unsigned int sts ;
unsigned long bit ;
unsigned long enabled ;
unsigned long max ;
unsigned long status ;
struct aspeed_scu_ic * scu_ic = irq_desc_get_handler_data ( desc ) ;
struct irq_chip * chip = irq_desc_get_chip ( desc ) ;
unsigned int mask = scu_ic - > irq_enable < < ASPEED_SCU_IC_STATUS_SHIFT ;
chained_irq_enter ( chip , desc ) ;
/*
* The SCU IC has just one register to control its operation and read
* status . The interrupt enable bits occupy the lower 16 bits of the
* register , while the interrupt status bits occupy the upper 16 bits .
* The status bit for a given interrupt is always 16 bits shifted from
* the enable bit for the same interrupt .
* Therefore , perform the IRQ operations in the enable bit space by
* shifting the status down to get the mapping and then back up to
* clear the bit .
*/
regmap_read ( scu_ic - > scu , scu_ic - > reg , & sts ) ;
enabled = sts & scu_ic - > irq_enable ;
status = ( sts > > ASPEED_SCU_IC_STATUS_SHIFT ) & enabled ;
bit = scu_ic - > irq_shift ;
max = scu_ic - > num_irqs + bit ;
for_each_set_bit_from ( bit , & status , max ) {
2021-05-04 19:42:18 +03:00
generic_handle_domain_irq ( scu_ic - > irq_domain ,
bit - scu_ic - > irq_shift ) ;
2020-01-16 00:29:40 +03:00
2021-11-24 12:43:48 +03:00
regmap_write_bits ( scu_ic - > scu , scu_ic - > reg , mask ,
BIT ( bit + ASPEED_SCU_IC_STATUS_SHIFT ) ) ;
2020-01-16 00:29:40 +03:00
}
chained_irq_exit ( chip , desc ) ;
}
static void aspeed_scu_ic_irq_mask ( struct irq_data * data )
{
struct aspeed_scu_ic * scu_ic = irq_data_get_irq_chip_data ( data ) ;
unsigned int mask = BIT ( data - > hwirq + scu_ic - > irq_shift ) |
( scu_ic - > irq_enable < < ASPEED_SCU_IC_STATUS_SHIFT ) ;
/*
* Status bits are cleared by writing 1. In order to prevent the mask
* operation from clearing the status bits , they should be under the
* mask and written with 0.
*/
regmap_update_bits ( scu_ic - > scu , scu_ic - > reg , mask , 0 ) ;
}
static void aspeed_scu_ic_irq_unmask ( struct irq_data * data )
{
struct aspeed_scu_ic * scu_ic = irq_data_get_irq_chip_data ( data ) ;
unsigned int bit = BIT ( data - > hwirq + scu_ic - > irq_shift ) ;
unsigned int mask = bit |
( scu_ic - > irq_enable < < ASPEED_SCU_IC_STATUS_SHIFT ) ;
/*
* Status bits are cleared by writing 1. In order to prevent the unmask
* operation from clearing the status bits , they should be under the
* mask and written with 0.
*/
regmap_update_bits ( scu_ic - > scu , scu_ic - > reg , mask , bit ) ;
}
static int aspeed_scu_ic_irq_set_affinity ( struct irq_data * data ,
const struct cpumask * dest ,
bool force )
{
return - EINVAL ;
}
static struct irq_chip aspeed_scu_ic_chip = {
. name = " aspeed-scu-ic " ,
. irq_mask = aspeed_scu_ic_irq_mask ,
. irq_unmask = aspeed_scu_ic_irq_unmask ,
. irq_set_affinity = aspeed_scu_ic_irq_set_affinity ,
} ;
static int aspeed_scu_ic_map ( struct irq_domain * domain , unsigned int irq ,
irq_hw_number_t hwirq )
{
irq_set_chip_and_handler ( irq , & aspeed_scu_ic_chip , handle_level_irq ) ;
irq_set_chip_data ( irq , domain - > host_data ) ;
return 0 ;
}
static const struct irq_domain_ops aspeed_scu_ic_domain_ops = {
. map = aspeed_scu_ic_map ,
} ;
static int aspeed_scu_ic_of_init_common ( struct aspeed_scu_ic * scu_ic ,
struct device_node * node )
{
int irq ;
int rc = 0 ;
if ( ! node - > parent ) {
rc = - ENODEV ;
goto err ;
}
scu_ic - > scu = syscon_node_to_regmap ( node - > parent ) ;
if ( IS_ERR ( scu_ic - > scu ) ) {
rc = PTR_ERR ( scu_ic - > scu ) ;
goto err ;
}
irq = irq_of_parse_and_map ( node , 0 ) ;
if ( irq < 0 ) {
rc = irq ;
goto err ;
}
scu_ic - > irq_domain = irq_domain_add_linear ( node , scu_ic - > num_irqs ,
& aspeed_scu_ic_domain_ops ,
scu_ic ) ;
if ( ! scu_ic - > irq_domain ) {
rc = - ENOMEM ;
goto err ;
}
irq_set_chained_handler_and_data ( irq , aspeed_scu_ic_irq_handler ,
scu_ic ) ;
return 0 ;
err :
kfree ( scu_ic ) ;
return rc ;
}
static int __init aspeed_scu_ic_of_init ( struct device_node * node ,
struct device_node * parent )
{
struct aspeed_scu_ic * scu_ic = kzalloc ( sizeof ( * scu_ic ) , GFP_KERNEL ) ;
if ( ! scu_ic )
return - ENOMEM ;
scu_ic - > irq_enable = ASPEED_SCU_IC_ENABLE ;
scu_ic - > irq_shift = ASPEED_SCU_IC_SHIFT ;
scu_ic - > num_irqs = ASPEED_SCU_IC_NUM_IRQS ;
scu_ic - > reg = ASPEED_SCU_IC_REG ;
return aspeed_scu_ic_of_init_common ( scu_ic , node ) ;
}
static int __init aspeed_ast2600_scu_ic0_of_init ( struct device_node * node ,
struct device_node * parent )
{
struct aspeed_scu_ic * scu_ic = kzalloc ( sizeof ( * scu_ic ) , GFP_KERNEL ) ;
if ( ! scu_ic )
return - ENOMEM ;
scu_ic - > irq_enable = ASPEED_AST2600_SCU_IC0_ENABLE ;
scu_ic - > irq_shift = ASPEED_AST2600_SCU_IC0_SHIFT ;
scu_ic - > num_irqs = ASPEED_AST2600_SCU_IC0_NUM_IRQS ;
scu_ic - > reg = ASPEED_AST2600_SCU_IC0_REG ;
return aspeed_scu_ic_of_init_common ( scu_ic , node ) ;
}
static int __init aspeed_ast2600_scu_ic1_of_init ( struct device_node * node ,
struct device_node * parent )
{
struct aspeed_scu_ic * scu_ic = kzalloc ( sizeof ( * scu_ic ) , GFP_KERNEL ) ;
if ( ! scu_ic )
return - ENOMEM ;
scu_ic - > irq_enable = ASPEED_AST2600_SCU_IC1_ENABLE ;
scu_ic - > irq_shift = ASPEED_AST2600_SCU_IC1_SHIFT ;
scu_ic - > num_irqs = ASPEED_AST2600_SCU_IC1_NUM_IRQS ;
scu_ic - > reg = ASPEED_AST2600_SCU_IC1_REG ;
return aspeed_scu_ic_of_init_common ( scu_ic , node ) ;
}
IRQCHIP_DECLARE ( ast2400_scu_ic , " aspeed,ast2400-scu-ic " , aspeed_scu_ic_of_init ) ;
IRQCHIP_DECLARE ( ast2500_scu_ic , " aspeed,ast2500-scu-ic " , aspeed_scu_ic_of_init ) ;
IRQCHIP_DECLARE ( ast2600_scu_ic0 , " aspeed,ast2600-scu-ic0 " ,
aspeed_ast2600_scu_ic0_of_init ) ;
IRQCHIP_DECLARE ( ast2600_scu_ic1 , " aspeed,ast2600-scu-ic1 " ,
aspeed_ast2600_scu_ic1_of_init ) ;