2020-05-28 18:27:51 +03:00
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright ( C ) 2020 , Jiaxun Yang < jiaxun . yang @ flygoat . com >
* Loongson PCH PIC support
*/
# define pr_fmt(fmt) "pch-pic: " fmt
# include <linux/interrupt.h>
# include <linux/irq.h>
# include <linux/irqchip.h>
# include <linux/irqdomain.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:26 +03:00
# include <linux/syscore_ops.h>
2020-05-28 18:27:51 +03:00
/* Registers */
# define PCH_PIC_MASK 0x20
# define PCH_PIC_HTMSI_EN 0x40
# define PCH_PIC_EDGE 0x60
# define PCH_PIC_CLR 0x80
# define PCH_PIC_AUTO0 0xc0
# define PCH_PIC_AUTO1 0xe0
# define PCH_INT_ROUTE(irq) (0x100 + irq)
# define PCH_INT_HTVEC(irq) (0x200 + irq)
# define PCH_PIC_POL 0x3e0
# define PIC_COUNT_PER_REG 32
# define PIC_REG_COUNT 2
# define PIC_COUNT (PIC_COUNT_PER_REG * PIC_REG_COUNT)
# define PIC_REG_IDX(irq_id) ((irq_id) / PIC_COUNT_PER_REG)
# define PIC_REG_BIT(irq_id) ((irq_id) % PIC_COUNT_PER_REG)
2022-07-20 13:51:27 +03:00
static int nr_pics ;
2020-05-28 18:27:51 +03:00
struct pch_pic {
void __iomem * base ;
struct irq_domain * pic_domain ;
u32 ht_vec_base ;
raw_spinlock_t pic_lock ;
2022-07-20 13:51:27 +03:00
u32 vec_count ;
u32 gsi_base ;
2022-10-20 10:35:26 +03:00
u32 saved_vec_en [ PIC_REG_COUNT ] ;
u32 saved_vec_pol [ PIC_REG_COUNT ] ;
u32 saved_vec_edge [ PIC_REG_COUNT ] ;
2020-05-28 18:27:51 +03:00
} ;
2022-07-20 13:51:27 +03:00
static struct pch_pic * pch_pic_priv [ MAX_IO_PICS ] ;
struct fwnode_handle * pch_pic_handle [ MAX_IO_PICS ] ;
2020-05-28 18:27:51 +03:00
static void pch_pic_bitset ( struct pch_pic * priv , int offset , int bit )
{
u32 reg ;
void __iomem * addr = priv - > base + offset + PIC_REG_IDX ( bit ) * 4 ;
raw_spin_lock ( & priv - > pic_lock ) ;
reg = readl ( addr ) ;
reg | = BIT ( PIC_REG_BIT ( bit ) ) ;
writel ( reg , addr ) ;
raw_spin_unlock ( & priv - > pic_lock ) ;
}
static void pch_pic_bitclr ( struct pch_pic * priv , int offset , int bit )
{
u32 reg ;
void __iomem * addr = priv - > base + offset + PIC_REG_IDX ( bit ) * 4 ;
raw_spin_lock ( & priv - > pic_lock ) ;
reg = readl ( addr ) ;
reg & = ~ BIT ( PIC_REG_BIT ( bit ) ) ;
writel ( reg , addr ) ;
raw_spin_unlock ( & priv - > pic_lock ) ;
}
static void pch_pic_mask_irq ( struct irq_data * d )
{
struct pch_pic * priv = irq_data_get_irq_chip_data ( d ) ;
pch_pic_bitset ( priv , PCH_PIC_MASK , d - > hwirq ) ;
irq_chip_mask_parent ( d ) ;
}
static void pch_pic_unmask_irq ( struct irq_data * d )
{
struct pch_pic * priv = irq_data_get_irq_chip_data ( d ) ;
2020-07-30 11:51:30 +03:00
writel ( BIT ( PIC_REG_BIT ( d - > hwirq ) ) ,
priv - > base + PCH_PIC_CLR + PIC_REG_IDX ( d - > hwirq ) * 4 ) ;
2020-05-28 18:27:51 +03:00
irq_chip_unmask_parent ( d ) ;
pch_pic_bitclr ( priv , PCH_PIC_MASK , d - > hwirq ) ;
}
static int pch_pic_set_type ( struct irq_data * d , unsigned int type )
{
struct pch_pic * priv = irq_data_get_irq_chip_data ( d ) ;
int ret = 0 ;
switch ( type ) {
case IRQ_TYPE_EDGE_RISING :
pch_pic_bitset ( priv , PCH_PIC_EDGE , d - > hwirq ) ;
pch_pic_bitclr ( priv , PCH_PIC_POL , d - > hwirq ) ;
2021-08-05 16:22:16 +03:00
irq_set_handler_locked ( d , handle_edge_irq ) ;
2020-05-28 18:27:51 +03:00
break ;
case IRQ_TYPE_EDGE_FALLING :
pch_pic_bitset ( priv , PCH_PIC_EDGE , d - > hwirq ) ;
pch_pic_bitset ( priv , PCH_PIC_POL , d - > hwirq ) ;
2021-08-05 16:22:16 +03:00
irq_set_handler_locked ( d , handle_edge_irq ) ;
2020-05-28 18:27:51 +03:00
break ;
case IRQ_TYPE_LEVEL_HIGH :
pch_pic_bitclr ( priv , PCH_PIC_EDGE , d - > hwirq ) ;
pch_pic_bitclr ( priv , PCH_PIC_POL , d - > hwirq ) ;
2021-08-05 16:22:16 +03:00
irq_set_handler_locked ( d , handle_level_irq ) ;
2020-05-28 18:27:51 +03:00
break ;
case IRQ_TYPE_LEVEL_LOW :
pch_pic_bitclr ( priv , PCH_PIC_EDGE , d - > hwirq ) ;
pch_pic_bitset ( priv , PCH_PIC_POL , d - > hwirq ) ;
2021-08-05 16:22:16 +03:00
irq_set_handler_locked ( d , handle_level_irq ) ;
2020-05-28 18:27:51 +03:00
break ;
default :
ret = - EINVAL ;
break ;
}
return ret ;
}
2021-08-05 16:22:16 +03:00
static void pch_pic_ack_irq ( struct irq_data * d )
{
unsigned int reg ;
struct pch_pic * priv = irq_data_get_irq_chip_data ( d ) ;
reg = readl ( priv - > base + PCH_PIC_EDGE + PIC_REG_IDX ( d - > hwirq ) * 4 ) ;
if ( reg & BIT ( PIC_REG_BIT ( d - > hwirq ) ) ) {
writel ( BIT ( PIC_REG_BIT ( d - > hwirq ) ) ,
priv - > base + PCH_PIC_CLR + PIC_REG_IDX ( d - > hwirq ) * 4 ) ;
}
irq_chip_ack_parent ( d ) ;
}
2020-05-28 18:27:51 +03:00
static struct irq_chip pch_pic_irq_chip = {
. name = " PCH PIC " ,
. irq_mask = pch_pic_mask_irq ,
. irq_unmask = pch_pic_unmask_irq ,
2021-08-05 16:22:16 +03:00
. irq_ack = pch_pic_ack_irq ,
2020-05-28 18:27:51 +03:00
. irq_set_affinity = irq_chip_set_affinity_parent ,
. irq_set_type = pch_pic_set_type ,
2022-10-20 10:35:26 +03:00
. flags = IRQCHIP_SKIP_SET_WAKE ,
2020-05-28 18:27:51 +03:00
} ;
2022-07-20 13:51:27 +03:00
static int pch_pic_domain_translate ( struct irq_domain * d ,
struct irq_fwspec * fwspec ,
unsigned long * hwirq ,
unsigned int * type )
{
struct pch_pic * priv = d - > host_data ;
struct device_node * of_node = to_of_node ( fwspec - > fwnode ) ;
if ( of_node ) {
2022-10-22 10:59:53 +03:00
if ( fwspec - > param_count < 2 )
return - EINVAL ;
2022-07-20 13:51:27 +03:00
* hwirq = fwspec - > param [ 0 ] + priv - > ht_vec_base ;
* type = fwspec - > param [ 1 ] & IRQ_TYPE_SENSE_MASK ;
} else {
2022-10-22 10:59:54 +03:00
if ( fwspec - > param_count < 1 )
return - EINVAL ;
2022-07-20 13:51:27 +03:00
* hwirq = fwspec - > param [ 0 ] - priv - > gsi_base ;
2022-10-22 10:59:54 +03:00
if ( fwspec - > param_count > 1 )
* type = fwspec - > param [ 1 ] & IRQ_TYPE_SENSE_MASK ;
else
* type = IRQ_TYPE_NONE ;
2022-07-20 13:51:27 +03:00
}
return 0 ;
}
2020-05-28 18:27:51 +03:00
static int pch_pic_alloc ( struct irq_domain * domain , unsigned int virq ,
unsigned int nr_irqs , void * arg )
{
int err ;
unsigned int type ;
unsigned long hwirq ;
2020-07-07 05:12:49 +03:00
struct irq_fwspec * fwspec = arg ;
struct irq_fwspec parent_fwspec ;
2020-05-28 18:27:51 +03:00
struct pch_pic * priv = domain - > host_data ;
2022-07-20 13:51:27 +03:00
err = pch_pic_domain_translate ( domain , fwspec , & hwirq , & type ) ;
2020-07-07 05:12:49 +03:00
if ( err )
return err ;
2020-05-28 18:27:51 +03:00
2020-07-07 05:12:49 +03:00
parent_fwspec . fwnode = domain - > parent - > fwnode ;
parent_fwspec . param_count = 1 ;
2022-07-20 13:51:27 +03:00
parent_fwspec . param [ 0 ] = hwirq ;
2020-05-28 18:27:51 +03:00
2020-07-07 05:12:49 +03:00
err = irq_domain_alloc_irqs_parent ( domain , virq , 1 , & parent_fwspec ) ;
2020-05-28 18:27:51 +03:00
if ( err )
return err ;
irq_domain_set_info ( domain , virq , hwirq ,
& pch_pic_irq_chip , priv ,
2020-07-30 11:51:30 +03:00
handle_level_irq , NULL , NULL ) ;
2020-05-28 18:27:51 +03:00
irq_set_probe ( virq ) ;
return 0 ;
}
static const struct irq_domain_ops pch_pic_domain_ops = {
2022-07-20 13:51:27 +03:00
. translate = pch_pic_domain_translate ,
2020-05-28 18:27:51 +03:00
. alloc = pch_pic_alloc ,
. free = irq_domain_free_irqs_parent ,
} ;
static void pch_pic_reset ( struct pch_pic * priv )
{
int i ;
for ( i = 0 ; i < PIC_COUNT ; i + + ) {
2022-07-20 13:51:27 +03:00
/* Write vector ID */
2020-05-28 18:27:51 +03:00
writeb ( priv - > ht_vec_base + i , priv - > base + PCH_INT_HTVEC ( i ) ) ;
/* Hardcode route to HT0 Lo */
writeb ( 1 , priv - > base + PCH_INT_ROUTE ( i ) ) ;
}
for ( i = 0 ; i < PIC_REG_COUNT ; i + + ) {
/* Clear IRQ cause registers, mask all interrupts */
writel_relaxed ( 0xFFFFFFFF , priv - > base + PCH_PIC_MASK + 4 * i ) ;
writel_relaxed ( 0xFFFFFFFF , priv - > base + PCH_PIC_CLR + 4 * i ) ;
/* Clear auto bounce, we don't need that */
writel_relaxed ( 0 , priv - > base + PCH_PIC_AUTO0 + 4 * i ) ;
writel_relaxed ( 0 , priv - > base + PCH_PIC_AUTO1 + 4 * i ) ;
/* Enable HTMSI transformer */
writel_relaxed ( 0xFFFFFFFF , priv - > base + PCH_PIC_HTMSI_EN + 4 * i ) ;
}
}
2022-10-20 10:35:26 +03:00
static int pch_pic_suspend ( void )
{
int i , j ;
for ( i = 0 ; i < nr_pics ; i + + ) {
for ( j = 0 ; j < PIC_REG_COUNT ; j + + ) {
pch_pic_priv [ i ] - > saved_vec_pol [ j ] =
readl ( pch_pic_priv [ i ] - > base + PCH_PIC_POL + 4 * j ) ;
pch_pic_priv [ i ] - > saved_vec_edge [ j ] =
readl ( pch_pic_priv [ i ] - > base + PCH_PIC_EDGE + 4 * j ) ;
pch_pic_priv [ i ] - > saved_vec_en [ j ] =
readl ( pch_pic_priv [ i ] - > base + PCH_PIC_MASK + 4 * j ) ;
}
}
return 0 ;
}
static void pch_pic_resume ( void )
{
int i , j ;
for ( i = 0 ; i < nr_pics ; i + + ) {
pch_pic_reset ( pch_pic_priv [ i ] ) ;
for ( j = 0 ; j < PIC_REG_COUNT ; j + + ) {
writel ( pch_pic_priv [ i ] - > saved_vec_pol [ j ] ,
pch_pic_priv [ i ] - > base + PCH_PIC_POL + 4 * j ) ;
writel ( pch_pic_priv [ i ] - > saved_vec_edge [ j ] ,
pch_pic_priv [ i ] - > base + PCH_PIC_EDGE + 4 * j ) ;
writel ( pch_pic_priv [ i ] - > saved_vec_en [ j ] ,
pch_pic_priv [ i ] - > base + PCH_PIC_MASK + 4 * j ) ;
}
}
}
static struct syscore_ops pch_pic_syscore_ops = {
. suspend = pch_pic_suspend ,
. resume = pch_pic_resume ,
} ;
2022-07-20 13:51:27 +03:00
static int pch_pic_init ( phys_addr_t addr , unsigned long size , int vec_base ,
struct irq_domain * parent_domain , struct fwnode_handle * domain_handle ,
u32 gsi_base )
2020-05-28 18:27:51 +03:00
{
struct pch_pic * priv ;
priv = kzalloc ( sizeof ( * priv ) , GFP_KERNEL ) ;
if ( ! priv )
return - ENOMEM ;
raw_spin_lock_init ( & priv - > pic_lock ) ;
2022-07-20 13:51:27 +03:00
priv - > base = ioremap ( addr , size ) ;
if ( ! priv - > base )
2020-05-28 18:27:51 +03:00
goto free_priv ;
2022-07-20 13:51:27 +03:00
priv - > ht_vec_base = vec_base ;
priv - > vec_count = ( ( readq ( priv - > base ) > > 48 ) & 0xff ) + 1 ;
priv - > gsi_base = gsi_base ;
2020-05-28 18:27:51 +03:00
priv - > pic_domain = irq_domain_create_hierarchy ( parent_domain , 0 ,
2022-07-20 13:51:27 +03:00
priv - > vec_count , domain_handle ,
& pch_pic_domain_ops , priv ) ;
2020-05-28 18:27:51 +03:00
if ( ! priv - > pic_domain ) {
pr_err ( " Failed to create IRQ domain \n " ) ;
goto iounmap_base ;
}
pch_pic_reset ( priv ) ;
2022-07-20 13:51:27 +03:00
pch_pic_handle [ nr_pics ] = domain_handle ;
pch_pic_priv [ nr_pics + + ] = priv ;
2020-05-28 18:27:51 +03:00
2022-10-20 10:35:26 +03:00
register_syscore_ops ( & pch_pic_syscore_ops ) ;
2020-05-28 18:27:51 +03:00
return 0 ;
iounmap_base :
iounmap ( priv - > base ) ;
free_priv :
kfree ( priv ) ;
2022-07-20 13:51:27 +03:00
return - EINVAL ;
}
# ifdef CONFIG_OF
static int pch_pic_of_init ( struct device_node * node ,
struct device_node * parent )
{
int err , vec_base ;
struct resource res ;
struct irq_domain * parent_domain ;
if ( of_address_to_resource ( node , 0 , & res ) )
return - EINVAL ;
parent_domain = irq_find_host ( parent ) ;
if ( ! parent_domain ) {
pr_err ( " Failed to find the parent domain \n " ) ;
return - ENXIO ;
}
if ( of_property_read_u32 ( node , " loongson,pic-base-vec " , & vec_base ) ) {
pr_err ( " Failed to determine pic-base-vec \n " ) ;
return - EINVAL ;
}
err = pch_pic_init ( res . start , resource_size ( & res ) , vec_base ,
parent_domain , of_node_to_fwnode ( node ) , 0 ) ;
if ( err < 0 )
return err ;
return 0 ;
2020-05-28 18:27:51 +03:00
}
IRQCHIP_DECLARE ( pch_pic , " loongson,pch-pic-1.0 " , pch_pic_of_init ) ;
2022-07-20 13:51:27 +03:00
# endif
# ifdef CONFIG_ACPI
2022-08-08 12:32:05 +03:00
int find_pch_pic ( u32 gsi )
{
int i ;
/* Find the PCH_PIC that manages this GSI. */
for ( i = 0 ; i < MAX_IO_PICS ; i + + ) {
struct pch_pic * priv = pch_pic_priv [ i ] ;
if ( ! priv )
return - 1 ;
if ( gsi > = priv - > gsi_base & & gsi < ( priv - > gsi_base + priv - > vec_count ) )
return i ;
}
pr_err ( " ERROR: Unable to locate PCH_PIC for GSI %d \n " , gsi ) ;
return - 1 ;
}
2022-10-20 17:25:14 +03:00
static int __init pch_lpc_parse_madt ( union acpi_subtable_headers * header ,
const unsigned long end )
2022-07-20 13:51:27 +03:00
{
struct acpi_madt_lpc_pic * pchlpc_entry = ( struct acpi_madt_lpc_pic * ) header ;
return pch_lpc_acpi_init ( pch_pic_priv [ 0 ] - > pic_domain , pchlpc_entry ) ;
}
static int __init acpi_cascade_irqdomain_init ( void )
{
2022-10-20 17:25:14 +03:00
int r ;
r = acpi_table_parse_madt ( ACPI_MADT_TYPE_LPC_PIC , pch_lpc_parse_madt , 0 ) ;
if ( r < 0 )
return r ;
2022-07-20 13:51:27 +03:00
return 0 ;
}
int __init pch_pic_acpi_init ( struct irq_domain * parent ,
struct acpi_madt_bio_pic * acpi_pchpic )
{
int ret , vec_base ;
struct fwnode_handle * domain_handle ;
vec_base = acpi_pchpic - > gsi_base - GSI_MIN_PCH_IRQ ;
2022-08-08 13:50:20 +03:00
domain_handle = irq_domain_alloc_fwnode ( & acpi_pchpic - > address ) ;
2022-07-20 13:51:27 +03:00
if ( ! domain_handle ) {
pr_err ( " Unable to allocate domain handle \n " ) ;
return - ENOMEM ;
}
ret = pch_pic_init ( acpi_pchpic - > address , acpi_pchpic - > size ,
vec_base , parent , domain_handle , acpi_pchpic - > gsi_base ) ;
if ( ret < 0 ) {
irq_domain_free_fwnode ( domain_handle ) ;
return ret ;
}
if ( acpi_pchpic - > id = = 0 )
2022-10-20 17:25:14 +03:00
ret = acpi_cascade_irqdomain_init ( ) ;
2022-07-20 13:51:27 +03:00
return ret ;
}
# endif