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>
2022-10-20 10:35:24 +03:00
# include <linux/syscore_ops.h>
2020-05-28 18:27:49 +03:00
/* 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 ;
2022-10-20 10:35:24 +03:00
u32 saved_vec_en [ HTVEC_MAX_PARENT_IRQ ] ;
2020-05-28 18:27:49 +03:00
} ;
2022-10-20 17:25:35 +03:00
static struct htvec * htvec_priv ;
2020-05-28 18:27:49 +03:00
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
}
}
2022-10-20 10:35:24 +03:00
static int htvec_suspend ( void )
{
int i ;
for ( i = 0 ; i < htvec_priv - > num_parents ; i + + )
htvec_priv - > saved_vec_en [ i ] = readl ( htvec_priv - > base + HTVEC_EN_OFF + 4 * i ) ;
return 0 ;
}
static void htvec_resume ( void )
{
int i ;
for ( i = 0 ; i < htvec_priv - > num_parents ; i + + )
writel ( htvec_priv - > saved_vec_en [ i ] , htvec_priv - > base + HTVEC_EN_OFF + 4 * i ) ;
}
static struct syscore_ops htvec_syscore_ops = {
. suspend = htvec_suspend ,
. resume = htvec_resume ,
} ;
2022-10-20 17:25:35 +03:00
static int htvec_init ( phys_addr_t addr , unsigned long size ,
int num_parents , int parent_irq [ ] , struct fwnode_handle * domain_handle )
2020-05-28 18:27:49 +03:00
{
2022-10-20 17:25:35 +03:00
int i ;
2020-05-28 18:27:49 +03:00
struct htvec * priv ;
priv = kzalloc ( sizeof ( * priv ) , GFP_KERNEL ) ;
if ( ! priv )
return - ENOMEM ;
2022-10-20 17:25:35 +03:00
priv - > num_parents = num_parents ;
priv - > base = ioremap ( addr , size ) ;
2020-05-28 18:27:49 +03:00
raw_spin_lock_init ( & priv - > htvec_lock ) ;
2022-10-20 17:25:35 +03:00
/* Setup IRQ domain */
priv - > htvec_domain = irq_domain_create_linear ( domain_handle ,
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 ) {
2022-10-20 17:25:35 +03:00
pr_err ( " loongson-htvec: cannot add IRQ domain \n " ) ;
goto iounmap_base ;
2020-05-28 18:27:49 +03:00
}
htvec_reset ( priv ) ;
2022-10-20 17:25:35 +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 ) ;
2022-10-20 17:25:35 +03:00
}
htvec_priv = priv ;
2020-05-28 18:27:49 +03:00
2022-10-20 10:35:24 +03:00
register_syscore_ops ( & htvec_syscore_ops ) ;
2020-05-28 18:27:49 +03:00
return 0 ;
iounmap_base :
iounmap ( priv - > base ) ;
kfree ( priv ) ;
2022-10-20 17:25:35 +03:00
return - EINVAL ;
}
# ifdef CONFIG_OF
static int htvec_of_init ( struct device_node * node ,
struct device_node * parent )
{
int i , err ;
int parent_irq [ 8 ] ;
int num_parents = 0 ;
struct resource res ;
if ( of_address_to_resource ( node , 0 , & res ) )
return - EINVAL ;
/* Interrupt may come from any of the 8 interrupt lines */
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 ;
num_parents + + ;
}
err = htvec_init ( res . start , resource_size ( & res ) ,
num_parents , parent_irq , of_node_to_fwnode ( node ) ) ;
if ( err < 0 )
return err ;
return 0 ;
2020-05-28 18:27:49 +03:00
}
IRQCHIP_DECLARE ( htvec , " loongson,htvec-1.0 " , htvec_of_init ) ;
2022-10-20 17:25:35 +03:00
# endif
# ifdef CONFIG_ACPI
static int __init pch_pic_parse_madt ( union acpi_subtable_headers * header ,
const unsigned long end )
{
struct acpi_madt_bio_pic * pchpic_entry = ( struct acpi_madt_bio_pic * ) header ;
return pch_pic_acpi_init ( htvec_priv - > htvec_domain , pchpic_entry ) ;
}
static int __init pch_msi_parse_madt ( union acpi_subtable_headers * header ,
const unsigned long end )
{
struct acpi_madt_msi_pic * pchmsi_entry = ( struct acpi_madt_msi_pic * ) header ;
return pch_msi_acpi_init ( htvec_priv - > htvec_domain , pchmsi_entry ) ;
}
static int __init acpi_cascade_irqdomain_init ( void )
{
int r ;
r = acpi_table_parse_madt ( ACPI_MADT_TYPE_BIO_PIC , pch_pic_parse_madt , 0 ) ;
if ( r < 0 )
return r ;
r = acpi_table_parse_madt ( ACPI_MADT_TYPE_MSI_PIC , pch_msi_parse_madt , 0 ) ;
if ( r < 0 )
return r ;
return 0 ;
}
int __init htvec_acpi_init ( struct irq_domain * parent ,
struct acpi_madt_ht_pic * acpi_htvec )
{
int i , ret ;
int num_parents , parent_irq [ 8 ] ;
struct fwnode_handle * domain_handle ;
if ( ! acpi_htvec )
return - EINVAL ;
num_parents = HTVEC_MAX_PARENT_IRQ ;
domain_handle = irq_domain_alloc_fwnode ( & acpi_htvec - > address ) ;
if ( ! domain_handle ) {
pr_err ( " Unable to allocate domain handle \n " ) ;
return - ENOMEM ;
}
/* Interrupt may come from any of the 8 interrupt lines */
for ( i = 0 ; i < HTVEC_MAX_PARENT_IRQ ; i + + )
parent_irq [ i ] = irq_create_mapping ( parent , acpi_htvec - > cascade [ i ] ) ;
ret = htvec_init ( acpi_htvec - > address , acpi_htvec - > size ,
num_parents , parent_irq , domain_handle ) ;
if ( ret = = 0 )
ret = acpi_cascade_irqdomain_init ( ) ;
else
irq_domain_free_fwnode ( domain_handle ) ;
return ret ;
}
# endif