2015-08-07 02:00:33 +03:00
/*
* Root interrupt controller for the BCM2836 ( Raspberry Pi 2 ) .
*
* Copyright 2015 Broadcom
*
* 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 .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*/
# include <linux/cpu.h>
# include <linux/of_address.h>
# include <linux/of_irq.h>
# include <linux/irqchip.h>
# include <linux/irqdomain.h>
2017-08-06 18:52:02 +03:00
# include <linux/irqchip/irq-bcm2836.h>
2015-12-27 00:47:21 +03:00
2017-08-06 18:52:02 +03:00
# include <asm/exception.h>
2015-08-07 02:00:33 +03:00
struct bcm2836_arm_irqchip_intc {
struct irq_domain * domain ;
void __iomem * base ;
} ;
static struct bcm2836_arm_irqchip_intc intc __read_mostly ;
static void bcm2836_arm_irqchip_mask_per_cpu_irq ( unsigned int reg_offset ,
unsigned int bit ,
int cpu )
{
void __iomem * reg = intc . base + reg_offset + 4 * cpu ;
writel ( readl ( reg ) & ~ BIT ( bit ) , reg ) ;
}
static void bcm2836_arm_irqchip_unmask_per_cpu_irq ( unsigned int reg_offset ,
unsigned int bit ,
int cpu )
{
void __iomem * reg = intc . base + reg_offset + 4 * cpu ;
writel ( readl ( reg ) | BIT ( bit ) , reg ) ;
}
static void bcm2836_arm_irqchip_mask_timer_irq ( struct irq_data * d )
{
bcm2836_arm_irqchip_mask_per_cpu_irq ( LOCAL_TIMER_INT_CONTROL0 ,
d - > hwirq - LOCAL_IRQ_CNTPSIRQ ,
smp_processor_id ( ) ) ;
}
static void bcm2836_arm_irqchip_unmask_timer_irq ( struct irq_data * d )
{
bcm2836_arm_irqchip_unmask_per_cpu_irq ( LOCAL_TIMER_INT_CONTROL0 ,
d - > hwirq - LOCAL_IRQ_CNTPSIRQ ,
smp_processor_id ( ) ) ;
}
static struct irq_chip bcm2836_arm_irqchip_timer = {
. name = " bcm2836-timer " ,
. irq_mask = bcm2836_arm_irqchip_mask_timer_irq ,
. irq_unmask = bcm2836_arm_irqchip_unmask_timer_irq ,
} ;
static void bcm2836_arm_irqchip_mask_pmu_irq ( struct irq_data * d )
{
writel ( 1 < < smp_processor_id ( ) , intc . base + LOCAL_PM_ROUTING_CLR ) ;
}
static void bcm2836_arm_irqchip_unmask_pmu_irq ( struct irq_data * d )
{
writel ( 1 < < smp_processor_id ( ) , intc . base + LOCAL_PM_ROUTING_SET ) ;
}
static struct irq_chip bcm2836_arm_irqchip_pmu = {
. name = " bcm2836-pmu " ,
. irq_mask = bcm2836_arm_irqchip_mask_pmu_irq ,
. irq_unmask = bcm2836_arm_irqchip_unmask_pmu_irq ,
} ;
static void bcm2836_arm_irqchip_mask_gpu_irq ( struct irq_data * d )
{
}
static void bcm2836_arm_irqchip_unmask_gpu_irq ( struct irq_data * d )
{
}
static struct irq_chip bcm2836_arm_irqchip_gpu = {
. name = " bcm2836-gpu " ,
. irq_mask = bcm2836_arm_irqchip_mask_gpu_irq ,
. irq_unmask = bcm2836_arm_irqchip_unmask_gpu_irq ,
} ;
2017-12-11 23:39:11 +03:00
static int bcm2836_map ( struct irq_domain * d , unsigned int irq ,
irq_hw_number_t hw )
{
struct irq_chip * chip ;
switch ( hw ) {
case LOCAL_IRQ_CNTPSIRQ :
case LOCAL_IRQ_CNTPNSIRQ :
case LOCAL_IRQ_CNTHPIRQ :
case LOCAL_IRQ_CNTVIRQ :
chip = & bcm2836_arm_irqchip_timer ;
break ;
case LOCAL_IRQ_GPU_FAST :
chip = & bcm2836_arm_irqchip_gpu ;
break ;
case LOCAL_IRQ_PMU_FAST :
chip = & bcm2836_arm_irqchip_pmu ;
break ;
default :
pr_warn_once ( " Unexpected hw irq: %lu \n " , hw ) ;
return - EINVAL ;
}
2015-08-07 02:00:33 +03:00
irq_set_percpu_devid ( irq ) ;
2017-12-11 23:39:11 +03:00
irq_domain_set_info ( d , irq , hw , chip , d - > host_data ,
handle_percpu_devid_irq , NULL , NULL ) ;
2015-08-07 02:00:33 +03:00
irq_set_status_flags ( irq , IRQ_NOAUTOEN ) ;
2017-12-11 23:39:11 +03:00
return 0 ;
2015-08-07 02:00:33 +03:00
}
static void
__exception_irq_entry bcm2836_arm_irqchip_handle_irq ( struct pt_regs * regs )
{
int cpu = smp_processor_id ( ) ;
u32 stat ;
stat = readl_relaxed ( intc . base + LOCAL_IRQ_PENDING0 + 4 * cpu ) ;
2015-12-27 00:47:24 +03:00
if ( stat & BIT ( LOCAL_IRQ_MAILBOX0 ) ) {
2015-08-07 02:00:33 +03:00
# ifdef CONFIG_SMP
void __iomem * mailbox0 = ( intc . base +
LOCAL_MAILBOX0_CLR0 + 16 * cpu ) ;
u32 mbox_val = readl ( mailbox0 ) ;
u32 ipi = ffs ( mbox_val ) - 1 ;
writel ( 1 < < ipi , mailbox0 ) ;
handle_IPI ( ipi , regs ) ;
# endif
2015-12-27 00:47:23 +03:00
} else if ( stat ) {
2015-08-07 02:00:33 +03:00
u32 hwirq = ffs ( stat ) - 1 ;
2016-06-01 00:05:27 +03:00
handle_domain_irq ( intc . domain , hwirq , regs ) ;
2015-08-07 02:00:33 +03:00
}
}
# ifdef CONFIG_SMP
static void bcm2836_arm_irqchip_send_ipi ( const struct cpumask * mask ,
unsigned int ipi )
{
int cpu ;
void __iomem * mailbox0_base = intc . base + LOCAL_MAILBOX0_SET0 ;
/*
* Ensure that stores to normal memory are visible to the
* other CPUs before issuing the IPI .
*/
2016-04-13 23:28:43 +03:00
smp_wmb ( ) ;
2015-08-07 02:00:33 +03:00
for_each_cpu ( cpu , mask ) {
writel ( 1 < < ipi , mailbox0_base + 16 * cpu ) ;
}
}
2016-07-13 20:16:07 +03:00
static int bcm2836_cpu_starting ( unsigned int cpu )
2015-08-07 02:00:33 +03:00
{
2016-07-13 20:16:07 +03:00
bcm2836_arm_irqchip_unmask_per_cpu_irq ( LOCAL_MAILBOX_INT_CONTROL0 , 0 ,
cpu ) ;
return 0 ;
2015-08-07 02:00:33 +03:00
}
2016-07-13 20:16:07 +03:00
static int bcm2836_cpu_dying ( unsigned int cpu )
{
bcm2836_arm_irqchip_mask_per_cpu_irq ( LOCAL_MAILBOX_INT_CONTROL0 , 0 ,
cpu ) ;
return 0 ;
}
2015-08-07 02:00:33 +03:00
# endif
static const struct irq_domain_ops bcm2836_arm_irqchip_intc_ops = {
2017-12-11 23:39:11 +03:00
. xlate = irq_domain_xlate_onetwocell ,
. map = bcm2836_map ,
2015-08-07 02:00:33 +03:00
} ;
static void
bcm2836_arm_irqchip_smp_init ( void )
{
# ifdef CONFIG_SMP
/* Unmask IPIs to the boot CPU. */
2016-07-13 20:16:07 +03:00
cpuhp_setup_state ( CPUHP_AP_IRQ_BCM2836_STARTING ,
2016-12-21 22:19:54 +03:00
" irqchip/bcm2836:starting " , bcm2836_cpu_starting ,
2016-07-13 20:16:07 +03:00
bcm2836_cpu_dying ) ;
2015-08-07 02:00:33 +03:00
set_smp_cross_call ( bcm2836_arm_irqchip_send_ipi ) ;
2016-04-13 23:28:41 +03:00
# endif
2015-08-07 02:00:33 +03:00
}
2015-12-27 00:47:21 +03:00
/*
* The LOCAL_IRQ_CNT * timer firings are based off of the external
* oscillator with some scaling . The firmware sets up CNTFRQ to
* report 19.2 Mhz , but doesn ' t set up the scaling registers .
*/
static void bcm2835_init_local_timer_frequency ( void )
{
/*
* Set the timer to source from the 19.2 Mhz crystal clock ( bit
* 8 unset ) , and only increment by 1 instead of 2 ( bit 9
* unset ) .
*/
writel ( 0 , intc . base + LOCAL_CONTROL ) ;
/*
* Set the timer prescaler to 1 : 1 ( timer freq = input freq *
* 2 * * 31 / prescaler )
*/
writel ( 0x80000000 , intc . base + LOCAL_PRESCALER ) ;
}
2015-08-07 02:00:33 +03:00
static int __init bcm2836_arm_irqchip_l1_intc_of_init ( struct device_node * node ,
struct device_node * parent )
{
intc . base = of_iomap ( node , 0 ) ;
if ( ! intc . base ) {
2017-07-19 00:43:10 +03:00
panic ( " %pOF: unable to map local interrupt registers \n " , node ) ;
2015-08-07 02:00:33 +03:00
}
2015-12-27 00:47:21 +03:00
bcm2835_init_local_timer_frequency ( ) ;
2015-08-07 02:00:33 +03:00
intc . domain = irq_domain_add_linear ( node , LAST_IRQ + 1 ,
& bcm2836_arm_irqchip_intc_ops ,
NULL ) ;
if ( ! intc . domain )
2017-07-19 00:43:10 +03:00
panic ( " %pOF: unable to create IRQ domain \n " , node ) ;
2015-08-07 02:00:33 +03:00
bcm2836_arm_irqchip_smp_init ( ) ;
set_handle_irq ( bcm2836_arm_irqchip_handle_irq ) ;
return 0 ;
}
IRQCHIP_DECLARE ( bcm2836_arm_irqchip_l1_intc , " brcm,bcm2836-l1-intc " ,
bcm2836_arm_irqchip_l1_intc_of_init ) ;