2014-02-02 12:07:46 +04:00
/*
* CLPS711X IRQ driver
*
* Copyright ( C ) 2013 Alexander Shiyan < shc_work @ mail . ru >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*/
# include <linux/io.h>
# include <linux/irq.h>
2015-07-07 17:11:46 -04:00
# include <linux/irqchip.h>
2014-02-02 12:07:46 +04:00
# include <linux/irqdomain.h>
# include <linux/of_address.h>
# include <linux/of_irq.h>
# include <linux/slab.h>
# include <asm/exception.h>
# include <asm/mach/irq.h>
# define CLPS711X_INTSR1 (0x0240)
# define CLPS711X_INTMR1 (0x0280)
# define CLPS711X_BLEOI (0x0600)
# define CLPS711X_MCEOI (0x0640)
# define CLPS711X_TEOI (0x0680)
# define CLPS711X_TC1EOI (0x06c0)
# define CLPS711X_TC2EOI (0x0700)
# define CLPS711X_RTCEOI (0x0740)
# define CLPS711X_UMSEOI (0x0780)
# define CLPS711X_COEOI (0x07c0)
# define CLPS711X_INTSR2 (0x1240)
# define CLPS711X_INTMR2 (0x1280)
# define CLPS711X_SRXEOF (0x1600)
# define CLPS711X_KBDEOI (0x1700)
# define CLPS711X_INTSR3 (0x2240)
# define CLPS711X_INTMR3 (0x2280)
static const struct {
# define CLPS711X_FLAG_EN (1 << 0)
# define CLPS711X_FLAG_FIQ (1 << 1)
unsigned int flags ;
phys_addr_t eoi ;
} clps711x_irqs [ ] = {
[ 1 ] = { CLPS711X_FLAG_FIQ , CLPS711X_BLEOI , } ,
[ 3 ] = { CLPS711X_FLAG_FIQ , CLPS711X_MCEOI , } ,
[ 4 ] = { CLPS711X_FLAG_EN , CLPS711X_COEOI , } ,
[ 5 ] = { CLPS711X_FLAG_EN , } ,
[ 6 ] = { CLPS711X_FLAG_EN , } ,
[ 7 ] = { CLPS711X_FLAG_EN , } ,
[ 8 ] = { CLPS711X_FLAG_EN , CLPS711X_TC1EOI , } ,
[ 9 ] = { CLPS711X_FLAG_EN , CLPS711X_TC2EOI , } ,
[ 10 ] = { CLPS711X_FLAG_EN , CLPS711X_RTCEOI , } ,
[ 11 ] = { CLPS711X_FLAG_EN , CLPS711X_TEOI , } ,
[ 12 ] = { CLPS711X_FLAG_EN , } ,
[ 13 ] = { CLPS711X_FLAG_EN , } ,
[ 14 ] = { CLPS711X_FLAG_EN , CLPS711X_UMSEOI , } ,
[ 15 ] = { CLPS711X_FLAG_EN , CLPS711X_SRXEOF , } ,
[ 16 ] = { CLPS711X_FLAG_EN , CLPS711X_KBDEOI , } ,
[ 17 ] = { CLPS711X_FLAG_EN , } ,
[ 18 ] = { CLPS711X_FLAG_EN , } ,
[ 28 ] = { CLPS711X_FLAG_EN , } ,
[ 29 ] = { CLPS711X_FLAG_EN , } ,
[ 32 ] = { CLPS711X_FLAG_FIQ , } ,
} ;
static struct {
void __iomem * base ;
void __iomem * intmr [ 3 ] ;
void __iomem * intsr [ 3 ] ;
struct irq_domain * domain ;
struct irq_domain_ops ops ;
} * clps711x_intc ;
static asmlinkage void __exception_irq_entry clps711x_irqh ( struct pt_regs * regs )
{
2014-08-26 11:03:22 +01:00
u32 irqstat ;
2014-02-02 12:07:46 +04:00
do {
irqstat = readw_relaxed ( clps711x_intc - > intmr [ 0 ] ) &
readw_relaxed ( clps711x_intc - > intsr [ 0 ] ) ;
2014-08-26 11:03:22 +01:00
if ( irqstat )
handle_domain_irq ( clps711x_intc - > domain ,
fls ( irqstat ) - 1 , regs ) ;
2014-02-02 12:07:46 +04:00
irqstat = readw_relaxed ( clps711x_intc - > intmr [ 1 ] ) &
readw_relaxed ( clps711x_intc - > intsr [ 1 ] ) ;
2014-08-26 11:03:22 +01:00
if ( irqstat )
handle_domain_irq ( clps711x_intc - > domain ,
fls ( irqstat ) - 1 + 16 , regs ) ;
2014-02-02 12:07:46 +04:00
} while ( irqstat ) ;
}
static void clps711x_intc_eoi ( struct irq_data * d )
{
irq_hw_number_t hwirq = irqd_to_hwirq ( d ) ;
writel_relaxed ( 0 , clps711x_intc - > base + clps711x_irqs [ hwirq ] . eoi ) ;
}
static void clps711x_intc_mask ( struct irq_data * d )
{
irq_hw_number_t hwirq = irqd_to_hwirq ( d ) ;
void __iomem * intmr = clps711x_intc - > intmr [ hwirq / 16 ] ;
u32 tmp ;
tmp = readl_relaxed ( intmr ) ;
tmp & = ~ ( 1 < < ( hwirq % 16 ) ) ;
writel_relaxed ( tmp , intmr ) ;
}
static void clps711x_intc_unmask ( struct irq_data * d )
{
irq_hw_number_t hwirq = irqd_to_hwirq ( d ) ;
void __iomem * intmr = clps711x_intc - > intmr [ hwirq / 16 ] ;
u32 tmp ;
tmp = readl_relaxed ( intmr ) ;
tmp | = 1 < < ( hwirq % 16 ) ;
writel_relaxed ( tmp , intmr ) ;
}
static struct irq_chip clps711x_intc_chip = {
. name = " clps711x-intc " ,
. irq_eoi = clps711x_intc_eoi ,
. irq_mask = clps711x_intc_mask ,
. irq_unmask = clps711x_intc_unmask ,
} ;
static int __init clps711x_intc_irq_map ( struct irq_domain * h , unsigned int virq ,
irq_hw_number_t hw )
{
irq_flow_handler_t handler = handle_level_irq ;
2015-08-29 18:01:22 -05:00
unsigned int flags = 0 ;
2014-02-02 12:07:46 +04:00
if ( ! clps711x_irqs [ hw ] . flags )
return 0 ;
if ( clps711x_irqs [ hw ] . flags & CLPS711X_FLAG_FIQ ) {
handler = handle_bad_irq ;
2015-08-29 18:01:22 -05:00
flags | = IRQ_NOAUTOEN ;
2014-02-02 12:07:46 +04:00
} else if ( clps711x_irqs [ hw ] . eoi ) {
handler = handle_fasteoi_irq ;
}
/* Clear down pending interrupt */
if ( clps711x_irqs [ hw ] . eoi )
writel_relaxed ( 0 , clps711x_intc - > base + clps711x_irqs [ hw ] . eoi ) ;
irq_set_chip_and_handler ( virq , & clps711x_intc_chip , handler ) ;
2015-08-29 18:01:22 -05:00
irq_modify_status ( virq , IRQ_NOPROBE , flags ) ;
2014-02-02 12:07:46 +04:00
return 0 ;
}
static int __init _clps711x_intc_init ( struct device_node * np ,
phys_addr_t base , resource_size_t size )
{
int err ;
clps711x_intc = kzalloc ( sizeof ( * clps711x_intc ) , GFP_KERNEL ) ;
if ( ! clps711x_intc )
return - ENOMEM ;
clps711x_intc - > base = ioremap ( base , size ) ;
if ( ! clps711x_intc - > base ) {
err = - ENOMEM ;
goto out_kfree ;
}
clps711x_intc - > intsr [ 0 ] = clps711x_intc - > base + CLPS711X_INTSR1 ;
clps711x_intc - > intmr [ 0 ] = clps711x_intc - > base + CLPS711X_INTMR1 ;
clps711x_intc - > intsr [ 1 ] = clps711x_intc - > base + CLPS711X_INTSR2 ;
clps711x_intc - > intmr [ 1 ] = clps711x_intc - > base + CLPS711X_INTMR2 ;
clps711x_intc - > intsr [ 2 ] = clps711x_intc - > base + CLPS711X_INTSR3 ;
clps711x_intc - > intmr [ 2 ] = clps711x_intc - > base + CLPS711X_INTMR3 ;
/* Mask all interrupts */
writel_relaxed ( 0 , clps711x_intc - > intmr [ 0 ] ) ;
writel_relaxed ( 0 , clps711x_intc - > intmr [ 1 ] ) ;
writel_relaxed ( 0 , clps711x_intc - > intmr [ 2 ] ) ;
err = irq_alloc_descs ( - 1 , 0 , ARRAY_SIZE ( clps711x_irqs ) , numa_node_id ( ) ) ;
if ( IS_ERR_VALUE ( err ) )
goto out_iounmap ;
clps711x_intc - > ops . map = clps711x_intc_irq_map ;
clps711x_intc - > ops . xlate = irq_domain_xlate_onecell ;
clps711x_intc - > domain =
irq_domain_add_legacy ( np , ARRAY_SIZE ( clps711x_irqs ) ,
0 , 0 , & clps711x_intc - > ops , NULL ) ;
if ( ! clps711x_intc - > domain ) {
err = - ENOMEM ;
goto out_irqfree ;
}
irq_set_default_host ( clps711x_intc - > domain ) ;
set_handle_irq ( clps711x_irqh ) ;
# ifdef CONFIG_FIQ
init_FIQ ( 0 ) ;
# endif
return 0 ;
out_irqfree :
irq_free_descs ( 0 , ARRAY_SIZE ( clps711x_irqs ) ) ;
out_iounmap :
iounmap ( clps711x_intc - > base ) ;
out_kfree :
kfree ( clps711x_intc ) ;
return err ;
}
void __init clps711x_intc_init ( phys_addr_t base , resource_size_t size )
{
BUG_ON ( _clps711x_intc_init ( NULL , base , size ) ) ;
}
# ifdef CONFIG_IRQCHIP
static int __init clps711x_intc_init_dt ( struct device_node * np ,
struct device_node * parent )
{
struct resource res ;
int err ;
err = of_address_to_resource ( np , 0 , & res ) ;
if ( err )
return err ;
return _clps711x_intc_init ( np , res . start , resource_size ( & res ) ) ;
}
IRQCHIP_DECLARE ( clps711x , " cirrus,clps711x-intc " , clps711x_intc_init_dt ) ;
# endif