2020-05-28 18:27:49 +03:00
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright ( C ) 2020 , Jiaxun Yang < jiaxun . yang @ flygoat . com >
* Loongson HyperTransport Interrupt Vector support
*/
# define pr_fmt(fmt) "htvec: " fmt
# include <linux/interrupt.h>
# include <linux/irq.h>
# include <linux/irqchip.h>
# include <linux/irqdomain.h>
# include <linux/irqchip/chained_irq.h>
# include <linux/kernel.h>
# include <linux/platform_device.h>
# include <linux/of_address.h>
# include <linux/of_irq.h>
# include <linux/of_platform.h>
/* Registers */
# define HTVEC_EN_OFF 0x20
2020-07-30 11:51:29 +03:00
# define HTVEC_MAX_PARENT_IRQ 8
2020-05-28 18:27:49 +03:00
# define VEC_COUNT_PER_REG 32
# define VEC_REG_IDX(irq_id) ((irq_id) / VEC_COUNT_PER_REG)
# define VEC_REG_BIT(irq_id) ((irq_id) % VEC_COUNT_PER_REG)
struct htvec {
2020-07-30 11:51:29 +03:00
int num_parents ;
2020-05-28 18:27:49 +03:00
void __iomem * base ;
struct irq_domain * htvec_domain ;
raw_spinlock_t htvec_lock ;
} ;
static void htvec_irq_dispatch ( struct irq_desc * desc )
{
int i ;
u32 pending ;
bool handled = false ;
struct irq_chip * chip = irq_desc_get_chip ( desc ) ;
struct htvec * priv = irq_desc_get_handler_data ( desc ) ;
chained_irq_enter ( chip , desc ) ;
2020-07-30 11:51:29 +03:00
for ( i = 0 ; i < priv - > num_parents ; i + + ) {
2020-05-28 18:27:49 +03:00
pending = readl ( priv - > base + 4 * i ) ;
while ( pending ) {
int bit = __ffs ( pending ) ;
2021-05-04 19:42:18 +03:00
generic_handle_domain_irq ( priv - > htvec_domain ,
bit + VEC_COUNT_PER_REG * i ) ;
2020-05-28 18:27:49 +03:00
pending & = ~ BIT ( bit ) ;
handled = true ;
}
}
if ( ! handled )
spurious_interrupt ( ) ;
chained_irq_exit ( chip , desc ) ;
}
static void htvec_ack_irq ( struct irq_data * d )
{
struct htvec * priv = irq_data_get_irq_chip_data ( d ) ;
writel ( BIT ( VEC_REG_BIT ( d - > hwirq ) ) ,
priv - > base + VEC_REG_IDX ( d - > hwirq ) * 4 ) ;
}
static void htvec_mask_irq ( struct irq_data * d )
{
u32 reg ;
void __iomem * addr ;
struct htvec * priv = irq_data_get_irq_chip_data ( d ) ;
raw_spin_lock ( & priv - > htvec_lock ) ;
addr = priv - > base + HTVEC_EN_OFF ;
addr + = VEC_REG_IDX ( d - > hwirq ) * 4 ;
reg = readl ( addr ) ;
reg & = ~ BIT ( VEC_REG_BIT ( d - > hwirq ) ) ;
writel ( reg , addr ) ;
raw_spin_unlock ( & priv - > htvec_lock ) ;
}
static void htvec_unmask_irq ( struct irq_data * d )
{
u32 reg ;
void __iomem * addr ;
struct htvec * priv = irq_data_get_irq_chip_data ( d ) ;
raw_spin_lock ( & priv - > htvec_lock ) ;
addr = priv - > base + HTVEC_EN_OFF ;
addr + = VEC_REG_IDX ( d - > hwirq ) * 4 ;
reg = readl ( addr ) ;
reg | = BIT ( VEC_REG_BIT ( d - > hwirq ) ) ;
writel ( reg , addr ) ;
raw_spin_unlock ( & priv - > htvec_lock ) ;
}
static struct irq_chip htvec_irq_chip = {
. name = " LOONGSON_HTVEC " ,
. irq_mask = htvec_mask_irq ,
. irq_unmask = htvec_unmask_irq ,
. irq_ack = htvec_ack_irq ,
} ;
static int htvec_domain_alloc ( struct irq_domain * domain , unsigned int virq ,
unsigned int nr_irqs , void * arg )
{
2020-07-07 05:12:48 +03:00
int ret ;
2020-05-28 18:27:49 +03:00
unsigned long hwirq ;
unsigned int type , i ;
struct htvec * priv = domain - > host_data ;
2020-07-07 05:12:48 +03:00
ret = irq_domain_translate_onecell ( domain , arg , & hwirq , & type ) ;
if ( ret )
return ret ;
2020-05-28 18:27:49 +03:00
for ( i = 0 ; i < nr_irqs ; i + + ) {
irq_domain_set_info ( domain , virq + i , hwirq + i , & htvec_irq_chip ,
priv , handle_edge_irq , NULL , NULL ) ;
}
return 0 ;
}
static void htvec_domain_free ( struct irq_domain * domain , unsigned int virq ,
unsigned int nr_irqs )
{
int i ;
for ( i = 0 ; i < nr_irqs ; i + + ) {
struct irq_data * d = irq_domain_get_irq_data ( domain , virq + i ) ;
irq_set_handler ( virq + i , NULL ) ;
irq_domain_reset_irq_data ( d ) ;
}
}
static const struct irq_domain_ops htvec_domain_ops = {
. translate = irq_domain_translate_onecell ,
. alloc = htvec_domain_alloc ,
. free = htvec_domain_free ,
} ;
static void htvec_reset ( struct htvec * priv )
{
u32 idx ;
/* Clear IRQ cause registers, mask all interrupts */
2020-07-30 11:51:29 +03:00
for ( idx = 0 ; idx < priv - > num_parents ; idx + + ) {
2020-05-28 18:27:49 +03:00
writel_relaxed ( 0x0 , priv - > base + HTVEC_EN_OFF + 4 * idx ) ;
2020-09-11 13:26:18 +03:00
writel_relaxed ( 0xFFFFFFFF , priv - > base + 4 * idx ) ;
2020-05-28 18:27:49 +03:00
}
}
static int htvec_of_init ( struct device_node * node ,
struct device_node * parent )
{
struct htvec * priv ;
2020-07-30 11:51:29 +03:00
int err , parent_irq [ 8 ] , i ;
2020-05-28 18:27:49 +03:00
priv = kzalloc ( sizeof ( * priv ) , GFP_KERNEL ) ;
if ( ! priv )
return - ENOMEM ;
raw_spin_lock_init ( & priv - > htvec_lock ) ;
priv - > base = of_iomap ( node , 0 ) ;
if ( ! priv - > base ) {
err = - ENOMEM ;
goto free_priv ;
}
2020-09-11 13:26:18 +03:00
/* Interrupt may come from any of the 8 interrupt lines */
2020-05-28 18:27:49 +03:00
for ( i = 0 ; i < HTVEC_MAX_PARENT_IRQ ; i + + ) {
parent_irq [ i ] = irq_of_parse_and_map ( node , i ) ;
if ( parent_irq [ i ] < = 0 )
break ;
2020-07-30 11:51:29 +03:00
priv - > num_parents + + ;
2020-05-28 18:27:49 +03:00
}
2020-07-30 11:51:29 +03:00
if ( ! priv - > num_parents ) {
2020-05-28 18:27:49 +03:00
pr_err ( " Failed to get parent irqs \n " ) ;
err = - ENODEV ;
goto iounmap_base ;
}
priv - > htvec_domain = irq_domain_create_linear ( of_node_to_fwnode ( node ) ,
2020-07-30 11:51:29 +03:00
( VEC_COUNT_PER_REG * priv - > num_parents ) ,
& htvec_domain_ops , priv ) ;
2020-05-28 18:27:49 +03:00
if ( ! priv - > htvec_domain ) {
pr_err ( " Failed to create IRQ domain \n " ) ;
err = - ENOMEM ;
2020-07-07 05:12:47 +03:00
goto irq_dispose ;
2020-05-28 18:27:49 +03:00
}
htvec_reset ( priv ) ;
2020-07-30 11:51:29 +03:00
for ( i = 0 ; i < priv - > num_parents ; i + + )
2020-05-28 18:27:49 +03:00
irq_set_chained_handler_and_data ( parent_irq [ i ] ,
htvec_irq_dispatch , priv ) ;
return 0 ;
2020-07-07 05:12:47 +03:00
irq_dispose :
for ( ; i > 0 ; i - - )
irq_dispose_mapping ( parent_irq [ i - 1 ] ) ;
2020-05-28 18:27:49 +03:00
iounmap_base :
iounmap ( priv - > base ) ;
free_priv :
kfree ( priv ) ;
return err ;
}
IRQCHIP_DECLARE ( htvec , " loongson,htvec-1.0 " , htvec_of_init ) ;