2020-03-25 06:54:54 +03:00
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright ( C ) 2020 , Jiaxun Yang < jiaxun . yang @ flygoat . com >
* Loongson Local IO Interrupt Controller support
*/
# include <linux/errno.h>
# include <linux/init.h>
# include <linux/types.h>
# include <linux/interrupt.h>
# include <linux/ioport.h>
# include <linux/irqchip.h>
# include <linux/of_address.h>
# include <linux/of_irq.h>
# include <linux/io.h>
# include <linux/smp.h>
# include <linux/irqchip/chained_irq.h>
2022-05-31 13:04:10 +03:00
# ifdef CONFIG_MIPS
2021-03-04 14:00:57 +03:00
# include <loongson.h>
2022-05-31 13:04:10 +03:00
# else
# include <asm/loongson.h>
# endif
2020-03-25 06:54:54 +03:00
# define LIOINTC_CHIP_IRQ 32
2022-07-20 13:51:29 +03:00
# define LIOINTC_NUM_PARENT 4
2021-03-15 10:50:02 +03:00
# define LIOINTC_NUM_CORES 4
2020-03-25 06:54:54 +03:00
# define LIOINTC_INTC_CHIP_START 0x20
# define LIOINTC_REG_INTC_STATUS (LIOINTC_INTC_CHIP_START + 0x20)
# define LIOINTC_REG_INTC_EN_STATUS (LIOINTC_INTC_CHIP_START + 0x04)
# define LIOINTC_REG_INTC_ENABLE (LIOINTC_INTC_CHIP_START + 0x08)
# define LIOINTC_REG_INTC_DISABLE (LIOINTC_INTC_CHIP_START + 0x0c)
# define LIOINTC_REG_INTC_POL (LIOINTC_INTC_CHIP_START + 0x10)
# define LIOINTC_REG_INTC_EDGE (LIOINTC_INTC_CHIP_START + 0x14)
# define LIOINTC_SHIFT_INTx 4
2020-03-25 06:54:55 +03:00
# define LIOINTC_ERRATA_IRQ 10
2022-06-09 20:52:41 +03:00
# if defined(CONFIG_MIPS)
# define liointc_core_id get_ebase_cpunum()
# else
# define liointc_core_id get_csr_cpuid()
# endif
2020-03-25 06:54:54 +03:00
struct liointc_handler_data {
struct liointc_priv * priv ;
u32 parent_int_map ;
} ;
struct liointc_priv {
struct irq_chip_generic * gc ;
struct liointc_handler_data handler [ LIOINTC_NUM_PARENT ] ;
2021-03-15 10:50:02 +03:00
void __iomem * core_isr [ LIOINTC_NUM_CORES ] ;
2020-03-25 06:54:54 +03:00
u8 map_cache [ LIOINTC_CHIP_IRQ ] ;
2020-03-25 06:54:55 +03:00
bool has_lpc_irq_errata ;
2020-03-25 06:54:54 +03:00
} ;
2022-07-20 13:51:29 +03:00
struct fwnode_handle * liointc_handle ;
2020-03-25 06:54:54 +03:00
static void liointc_chained_handle_irq ( struct irq_desc * desc )
{
struct liointc_handler_data * handler = irq_desc_get_handler_data ( desc ) ;
struct irq_chip * chip = irq_desc_get_chip ( desc ) ;
struct irq_chip_generic * gc = handler - > priv - > gc ;
2022-06-09 20:52:41 +03:00
int core = liointc_core_id % LIOINTC_NUM_CORES ;
2020-03-25 06:54:54 +03:00
u32 pending ;
chained_irq_enter ( chip , desc ) ;
2021-03-15 10:50:02 +03:00
pending = readl ( handler - > priv - > core_isr [ core ] ) ;
2020-03-25 06:54:54 +03:00
2020-03-25 06:54:55 +03:00
if ( ! pending ) {
/* Always blame LPC IRQ if we have that bug */
if ( handler - > priv - > has_lpc_irq_errata & &
2020-07-30 11:51:28 +03:00
( handler - > parent_int_map & gc - > mask_cache &
2020-03-25 06:54:55 +03:00
BIT ( LIOINTC_ERRATA_IRQ ) ) )
pending = BIT ( LIOINTC_ERRATA_IRQ ) ;
else
spurious_interrupt ( ) ;
}
2020-03-25 06:54:54 +03:00
while ( pending ) {
int bit = __ffs ( pending ) ;
2021-05-04 19:42:18 +03:00
generic_handle_domain_irq ( gc - > domain , bit ) ;
2020-03-25 06:54:54 +03:00
pending & = ~ BIT ( bit ) ;
}
chained_irq_exit ( chip , desc ) ;
}
static void liointc_set_bit ( struct irq_chip_generic * gc ,
unsigned int offset ,
u32 mask , bool set )
{
if ( set )
writel ( readl ( gc - > reg_base + offset ) | mask ,
gc - > reg_base + offset ) ;
else
writel ( readl ( gc - > reg_base + offset ) & ~ mask ,
gc - > reg_base + offset ) ;
}
static int liointc_set_type ( struct irq_data * data , unsigned int type )
{
struct irq_chip_generic * gc = irq_data_get_irq_chip_data ( data ) ;
u32 mask = data - > mask ;
unsigned long flags ;
irq_gc_lock_irqsave ( gc , flags ) ;
switch ( type ) {
case IRQ_TYPE_LEVEL_HIGH :
liointc_set_bit ( gc , LIOINTC_REG_INTC_EDGE , mask , false ) ;
liointc_set_bit ( gc , LIOINTC_REG_INTC_POL , mask , true ) ;
break ;
case IRQ_TYPE_LEVEL_LOW :
liointc_set_bit ( gc , LIOINTC_REG_INTC_EDGE , mask , false ) ;
liointc_set_bit ( gc , LIOINTC_REG_INTC_POL , mask , false ) ;
break ;
case IRQ_TYPE_EDGE_RISING :
liointc_set_bit ( gc , LIOINTC_REG_INTC_EDGE , mask , true ) ;
liointc_set_bit ( gc , LIOINTC_REG_INTC_POL , mask , true ) ;
break ;
case IRQ_TYPE_EDGE_FALLING :
liointc_set_bit ( gc , LIOINTC_REG_INTC_EDGE , mask , true ) ;
liointc_set_bit ( gc , LIOINTC_REG_INTC_POL , mask , false ) ;
break ;
default :
2020-07-07 05:12:51 +03:00
irq_gc_unlock_irqrestore ( gc , flags ) ;
2020-03-25 06:54:54 +03:00
return - EINVAL ;
}
irq_gc_unlock_irqrestore ( gc , flags ) ;
irqd_set_trigger_type ( data , type ) ;
return 0 ;
}
static void liointc_resume ( struct irq_chip_generic * gc )
{
struct liointc_priv * priv = gc - > private ;
unsigned long flags ;
int i ;
irq_gc_lock_irqsave ( gc , flags ) ;
/* Disable all at first */
writel ( 0xffffffff , gc - > reg_base + LIOINTC_REG_INTC_DISABLE ) ;
2020-07-30 11:51:28 +03:00
/* Restore map cache */
2020-03-25 06:54:54 +03:00
for ( i = 0 ; i < LIOINTC_CHIP_IRQ ; i + + )
writeb ( priv - > map_cache [ i ] , gc - > reg_base + i ) ;
2020-07-30 11:51:28 +03:00
/* Restore mask cache */
writel ( gc - > mask_cache , gc - > reg_base + LIOINTC_REG_INTC_ENABLE ) ;
2020-03-25 06:54:54 +03:00
irq_gc_unlock_irqrestore ( gc , flags ) ;
}
2022-07-20 13:51:29 +03:00
static int parent_irq [ LIOINTC_NUM_PARENT ] ;
static u32 parent_int_map [ LIOINTC_NUM_PARENT ] ;
static const char * const parent_names [ ] = { " int0 " , " int1 " , " int2 " , " int3 " } ;
static const char * const core_reg_names [ ] = { " isr0 " , " isr1 " , " isr2 " , " isr3 " } ;
2021-03-15 10:50:02 +03:00
2022-07-20 13:51:29 +03:00
static int liointc_domain_xlate ( struct irq_domain * d , struct device_node * ctrlr ,
const u32 * intspec , unsigned int intsize ,
unsigned long * out_hwirq , unsigned int * out_type )
2021-03-15 10:50:02 +03:00
{
2022-07-20 13:51:29 +03:00
if ( WARN_ON ( intsize < 1 ) )
return - EINVAL ;
* out_hwirq = intspec [ 0 ] - GSI_MIN_CPU_IRQ ;
* out_type = IRQ_TYPE_NONE ;
return 0 ;
2021-03-15 10:50:02 +03:00
}
2020-03-25 06:54:54 +03:00
2022-07-20 13:51:29 +03:00
static const struct irq_domain_ops acpi_irq_gc_ops = {
. map = irq_map_generic_chip ,
. unmap = irq_unmap_generic_chip ,
. xlate = liointc_domain_xlate ,
} ;
static int liointc_init ( phys_addr_t addr , unsigned long size , int revision ,
struct fwnode_handle * domain_handle , struct device_node * node )
2020-03-25 06:54:54 +03:00
{
2022-07-20 13:51:29 +03:00
int i , err ;
void __iomem * base ;
struct irq_chip_type * ct ;
2020-03-25 06:54:54 +03:00
struct irq_chip_generic * gc ;
struct irq_domain * domain ;
struct liointc_priv * priv ;
priv = kzalloc ( sizeof ( * priv ) , GFP_KERNEL ) ;
if ( ! priv )
return - ENOMEM ;
2022-07-20 13:51:29 +03:00
base = ioremap ( addr , size ) ;
if ( ! base )
goto out_free_priv ;
2021-03-15 10:50:02 +03:00
2022-07-20 13:51:29 +03:00
for ( i = 0 ; i < LIOINTC_NUM_CORES ; i + + )
priv - > core_isr [ i ] = base + LIOINTC_REG_INTC_STATUS ;
2021-03-15 10:50:02 +03:00
2022-07-20 13:51:29 +03:00
for ( i = 0 ; i < LIOINTC_NUM_PARENT ; i + + )
priv - > handler [ i ] . parent_int_map = parent_int_map [ i ] ;
2020-03-25 06:54:54 +03:00
2022-07-20 13:51:29 +03:00
if ( revision > 1 ) {
for ( i = 0 ; i < LIOINTC_NUM_CORES ; i + + ) {
int index = of_property_match_string ( node ,
" reg-names " , core_reg_names [ i ] ) ;
2020-03-25 06:54:54 +03:00
2022-07-20 13:51:29 +03:00
if ( index < 0 )
2022-08-01 22:28:07 +03:00
goto out_iounmap ;
2020-03-25 06:54:54 +03:00
2022-07-20 13:51:29 +03:00
priv - > core_isr [ i ] = of_iomap ( node , index ) ;
}
}
2020-03-25 06:54:54 +03:00
/* Setup IRQ domain */
2022-07-20 13:51:29 +03:00
if ( ! acpi_disabled )
domain = irq_domain_create_linear ( domain_handle , LIOINTC_CHIP_IRQ ,
& acpi_irq_gc_ops , priv ) ;
else
domain = irq_domain_create_linear ( domain_handle , LIOINTC_CHIP_IRQ ,
2020-03-25 06:54:54 +03:00
& irq_generic_chip_ops , priv ) ;
if ( ! domain ) {
pr_err ( " loongson-liointc: cannot add IRQ domain \n " ) ;
2022-07-20 13:51:29 +03:00
goto out_iounmap ;
2020-03-25 06:54:54 +03:00
}
2022-07-20 13:51:29 +03:00
err = irq_alloc_domain_generic_chips ( domain , LIOINTC_CHIP_IRQ , 1 ,
( node ? node - > full_name : " LIOINTC " ) ,
handle_level_irq , 0 , IRQ_NOPROBE , 0 ) ;
2020-03-25 06:54:54 +03:00
if ( err ) {
pr_err ( " loongson-liointc: unable to register IRQ domain \n " ) ;
goto out_free_domain ;
}
/* Disable all IRQs */
writel ( 0xffffffff , base + LIOINTC_REG_INTC_DISABLE ) ;
/* Set to level triggered */
writel ( 0x0 , base + LIOINTC_REG_INTC_EDGE ) ;
/* Generate parent INT part of map cache */
for ( i = 0 ; i < LIOINTC_NUM_PARENT ; i + + ) {
u32 pending = priv - > handler [ i ] . parent_int_map ;
while ( pending ) {
int bit = __ffs ( pending ) ;
priv - > map_cache [ bit ] = BIT ( i ) < < LIOINTC_SHIFT_INTx ;
pending & = ~ BIT ( bit ) ;
}
}
for ( i = 0 ; i < LIOINTC_CHIP_IRQ ; i + + ) {
/* Generate core part of map cache */
priv - > map_cache [ i ] | = BIT ( loongson_sysconf . boot_cpu_id ) ;
writeb ( priv - > map_cache [ i ] , base + i ) ;
}
gc = irq_get_domain_generic_chip ( domain , 0 ) ;
gc - > private = priv ;
gc - > reg_base = base ;
gc - > domain = domain ;
gc - > resume = liointc_resume ;
ct = gc - > chip_types ;
ct - > regs . enable = LIOINTC_REG_INTC_ENABLE ;
ct - > regs . disable = LIOINTC_REG_INTC_DISABLE ;
ct - > chip . irq_unmask = irq_gc_unmask_enable_reg ;
ct - > chip . irq_mask = irq_gc_mask_disable_reg ;
ct - > chip . irq_mask_ack = irq_gc_mask_disable_reg ;
ct - > chip . irq_set_type = liointc_set_type ;
2020-07-30 11:51:28 +03:00
gc - > mask_cache = 0 ;
2020-03-25 06:54:54 +03:00
priv - > gc = gc ;
for ( i = 0 ; i < LIOINTC_NUM_PARENT ; i + + ) {
if ( parent_irq [ i ] < = 0 )
continue ;
priv - > handler [ i ] . priv = priv ;
irq_set_chained_handler_and_data ( parent_irq [ i ] ,
liointc_chained_handle_irq , & priv - > handler [ i ] ) ;
}
2022-07-20 13:51:29 +03:00
liointc_handle = domain_handle ;
2020-03-25 06:54:54 +03:00
return 0 ;
out_free_domain :
irq_domain_remove ( domain ) ;
2022-07-20 13:51:29 +03:00
out_iounmap :
2020-03-25 06:54:54 +03:00
iounmap ( base ) ;
out_free_priv :
kfree ( priv ) ;
2022-07-20 13:51:29 +03:00
return - EINVAL ;
}
# ifdef CONFIG_OF
static int __init liointc_of_init ( struct device_node * node ,
struct device_node * parent )
{
bool have_parent = FALSE ;
int sz , i , index , revision , err = 0 ;
struct resource res ;
if ( ! of_device_is_compatible ( node , " loongson,liointc-2.0 " ) ) {
index = 0 ;
revision = 1 ;
} else {
index = of_property_match_string ( node , " reg-names " , " main " ) ;
revision = 2 ;
}
if ( of_address_to_resource ( node , index , & res ) )
return - EINVAL ;
for ( i = 0 ; i < LIOINTC_NUM_PARENT ; i + + ) {
parent_irq [ i ] = of_irq_get_byname ( node , parent_names [ i ] ) ;
if ( parent_irq [ i ] > 0 )
have_parent = TRUE ;
}
if ( ! have_parent )
return - ENODEV ;
sz = of_property_read_variable_u32_array ( node ,
" loongson,parent_int_map " ,
& parent_int_map [ 0 ] ,
LIOINTC_NUM_PARENT ,
LIOINTC_NUM_PARENT ) ;
if ( sz < 4 ) {
pr_err ( " loongson-liointc: No parent_int_map \n " ) ;
return - ENODEV ;
}
err = liointc_init ( res . start , resource_size ( & res ) ,
revision , of_node_to_fwnode ( node ) , node ) ;
if ( err < 0 )
return err ;
return 0 ;
2020-03-25 06:54:54 +03:00
}
IRQCHIP_DECLARE ( loongson_liointc_1_0 , " loongson,liointc-1.0 " , liointc_of_init ) ;
IRQCHIP_DECLARE ( loongson_liointc_1_0a , " loongson,liointc-1.0a " , liointc_of_init ) ;
2021-03-15 10:50:02 +03:00
IRQCHIP_DECLARE ( loongson_liointc_2_0 , " loongson,liointc-2.0 " , liointc_of_init ) ;
2022-07-20 13:51:29 +03:00
# endif
# ifdef CONFIG_ACPI
int __init liointc_acpi_init ( struct irq_domain * parent , struct acpi_madt_lio_pic * acpi_liointc )
{
int ret ;
struct fwnode_handle * domain_handle ;
parent_int_map [ 0 ] = acpi_liointc - > cascade_map [ 0 ] ;
parent_int_map [ 1 ] = acpi_liointc - > cascade_map [ 1 ] ;
parent_irq [ 0 ] = irq_create_mapping ( parent , acpi_liointc - > cascade [ 0 ] ) ;
parent_irq [ 1 ] = irq_create_mapping ( parent , acpi_liointc - > cascade [ 1 ] ) ;
2022-08-08 13:50:20 +03:00
domain_handle = irq_domain_alloc_fwnode ( & acpi_liointc - > address ) ;
2022-07-20 13:51:29 +03:00
if ( ! domain_handle ) {
pr_err ( " Unable to allocate domain handle \n " ) ;
return - ENOMEM ;
}
ret = liointc_init ( acpi_liointc - > address , acpi_liointc - > size ,
1 , domain_handle , NULL ) ;
if ( ret )
irq_domain_free_fwnode ( domain_handle ) ;
return ret ;
}
# endif