2011-09-06 10:39:44 +04:00
/*
2013-03-21 18:58:06 +04:00
* Copyright 2011 - 2013 Freescale Semiconductor , Inc .
2011-09-06 10:39:44 +04:00
* Copyright 2011 Linaro Ltd .
*
* The code contained herein is licensed under the GNU General Public
* License . You may obtain a copy of the GNU General Public License
* Version 2 or later at the following locations :
*
* http : //www.opensource.org/licenses/gpl-license.html
* http : //www.gnu.org/copyleft/gpl.html
*/
# include <linux/io.h>
# include <linux/irq.h>
2015-10-16 17:21:10 +03:00
# include <linux/irqchip.h>
2011-09-06 10:39:44 +04:00
# include <linux/of.h>
# include <linux/of_address.h>
# include <linux/of_irq.h>
2012-12-27 23:10:24 +04:00
# include <linux/irqchip/arm-gic.h>
2013-03-25 16:20:42 +04:00
# include "common.h"
2015-02-23 20:40:12 +03:00
# include "hardware.h"
2011-09-06 10:39:44 +04:00
2018-06-03 05:33:44 +03:00
# define GPC_CNTR 0x0
2011-09-06 10:39:44 +04:00
# define GPC_IMR1 0x008
# define GPC_PGC_CPU_PDN 0x2a0
2014-12-17 07:24:12 +03:00
# define GPC_PGC_CPU_PUPSCR 0x2a4
# define GPC_PGC_CPU_PDNSCR 0x2a8
# define GPC_PGC_SW2ISO_SHIFT 0x8
# define GPC_PGC_SW_SHIFT 0x0
2011-09-06 10:39:44 +04:00
2018-06-03 05:33:44 +03:00
# define GPC_CNTR_L2_PGE_SHIFT 22
2011-09-06 10:39:44 +04:00
# define IMR_NUM 4
2015-02-23 20:45:18 +03:00
# define GPC_MAX_IRQS (IMR_NUM * 32)
2011-09-06 10:39:44 +04:00
static void __iomem * gpc_base ;
static u32 gpc_wake_irqs [ IMR_NUM ] ;
static u32 gpc_saved_imrs [ IMR_NUM ] ;
2014-12-17 07:24:12 +03:00
void imx_gpc_set_arm_power_up_timing ( u32 sw2iso , u32 sw )
{
writel_relaxed ( ( sw2iso < < GPC_PGC_SW2ISO_SHIFT ) |
( sw < < GPC_PGC_SW_SHIFT ) , gpc_base + GPC_PGC_CPU_PUPSCR ) ;
}
void imx_gpc_set_arm_power_down_timing ( u32 sw2iso , u32 sw )
{
writel_relaxed ( ( sw2iso < < GPC_PGC_SW2ISO_SHIFT ) |
( sw < < GPC_PGC_SW_SHIFT ) , gpc_base + GPC_PGC_CPU_PDNSCR ) ;
}
void imx_gpc_set_arm_power_in_lpm ( bool power_off )
{
writel_relaxed ( power_off , gpc_base + GPC_PGC_CPU_PDN ) ;
}
2018-06-03 05:33:44 +03:00
void imx_gpc_set_l2_mem_power_in_lpm ( bool power_off )
{
u32 val ;
val = readl_relaxed ( gpc_base + GPC_CNTR ) ;
val & = ~ ( 1 < < GPC_CNTR_L2_PGE_SHIFT ) ;
if ( power_off )
val | = 1 < < GPC_CNTR_L2_PGE_SHIFT ;
writel_relaxed ( val , gpc_base + GPC_CNTR ) ;
}
2014-06-23 12:42:44 +04:00
void imx_gpc_pre_suspend ( bool arm_power_off )
2011-09-06 10:39:44 +04:00
{
void __iomem * reg_imr1 = gpc_base + GPC_IMR1 ;
int i ;
/* Tell GPC to power off ARM core when suspend */
2014-06-23 12:42:44 +04:00
if ( arm_power_off )
2014-12-17 07:24:12 +03:00
imx_gpc_set_arm_power_in_lpm ( arm_power_off ) ;
2011-09-06 10:39:44 +04:00
for ( i = 0 ; i < IMR_NUM ; i + + ) {
gpc_saved_imrs [ i ] = readl_relaxed ( reg_imr1 + i * 4 ) ;
writel_relaxed ( ~ gpc_wake_irqs [ i ] , reg_imr1 + i * 4 ) ;
}
}
void imx_gpc_post_resume ( void )
{
void __iomem * reg_imr1 = gpc_base + GPC_IMR1 ;
int i ;
/* Keep ARM core powered on for other low-power modes */
2014-12-17 07:24:12 +03:00
imx_gpc_set_arm_power_in_lpm ( false ) ;
2011-09-06 10:39:44 +04:00
for ( i = 0 ; i < IMR_NUM ; i + + )
writel_relaxed ( gpc_saved_imrs [ i ] , reg_imr1 + i * 4 ) ;
}
static int imx_gpc_irq_set_wake ( struct irq_data * d , unsigned int on )
{
2015-02-23 20:45:18 +03:00
unsigned int idx = d - > hwirq / 32 ;
2011-09-06 10:39:44 +04:00
u32 mask ;
2014-12-02 19:05:25 +03:00
mask = 1 < < d - > hwirq % 32 ;
2011-09-06 10:39:44 +04:00
gpc_wake_irqs [ idx ] = on ? gpc_wake_irqs [ idx ] | mask :
gpc_wake_irqs [ idx ] & ~ mask ;
2015-02-23 20:45:18 +03:00
/*
* Do * not * call into the parent , as the GIC doesn ' t have any
* wake - up facility . . .
*/
2011-09-06 10:39:44 +04:00
return 0 ;
}
2013-03-21 18:58:06 +04:00
void imx_gpc_mask_all ( void )
{
void __iomem * reg_imr1 = gpc_base + GPC_IMR1 ;
int i ;
for ( i = 0 ; i < IMR_NUM ; i + + ) {
gpc_saved_imrs [ i ] = readl_relaxed ( reg_imr1 + i * 4 ) ;
writel_relaxed ( ~ 0 , reg_imr1 + i * 4 ) ;
}
}
void imx_gpc_restore_all ( void )
{
void __iomem * reg_imr1 = gpc_base + GPC_IMR1 ;
int i ;
for ( i = 0 ; i < IMR_NUM ; i + + )
writel_relaxed ( gpc_saved_imrs [ i ] , reg_imr1 + i * 4 ) ;
}
2014-12-02 19:05:26 +03:00
void imx_gpc_hwirq_unmask ( unsigned int hwirq )
2011-09-06 10:39:44 +04:00
{
void __iomem * reg ;
u32 val ;
2015-02-23 20:45:18 +03:00
reg = gpc_base + GPC_IMR1 + hwirq / 32 * 4 ;
2011-09-06 10:39:44 +04:00
val = readl_relaxed ( reg ) ;
2014-12-02 19:05:26 +03:00
val & = ~ ( 1 < < hwirq % 32 ) ;
2011-09-06 10:39:44 +04:00
writel_relaxed ( val , reg ) ;
}
2014-12-02 19:05:26 +03:00
void imx_gpc_hwirq_mask ( unsigned int hwirq )
2011-09-06 10:39:44 +04:00
{
void __iomem * reg ;
u32 val ;
2015-02-23 20:45:18 +03:00
reg = gpc_base + GPC_IMR1 + hwirq / 32 * 4 ;
2014-12-02 19:05:26 +03:00
val = readl_relaxed ( reg ) ;
val | = 1 < < ( hwirq % 32 ) ;
writel_relaxed ( val , reg ) ;
}
static void imx_gpc_irq_unmask ( struct irq_data * d )
{
imx_gpc_hwirq_unmask ( d - > hwirq ) ;
2015-02-23 20:45:18 +03:00
irq_chip_unmask_parent ( d ) ;
2014-12-02 19:05:26 +03:00
}
static void imx_gpc_irq_mask ( struct irq_data * d )
{
imx_gpc_hwirq_mask ( d - > hwirq ) ;
2015-02-23 20:45:18 +03:00
irq_chip_mask_parent ( d ) ;
2011-09-06 10:39:44 +04:00
}
2015-02-23 20:45:18 +03:00
static struct irq_chip imx_gpc_chip = {
2015-03-12 11:40:37 +03:00
. name = " GPC " ,
. irq_eoi = irq_chip_eoi_parent ,
. irq_mask = imx_gpc_irq_mask ,
. irq_unmask = imx_gpc_irq_unmask ,
. irq_retrigger = irq_chip_retrigger_hierarchy ,
. irq_set_wake = imx_gpc_irq_set_wake ,
2015-10-20 13:44:19 +03:00
. irq_set_type = irq_chip_set_type_parent ,
2015-03-12 11:40:37 +03:00
# ifdef CONFIG_SMP
. irq_set_affinity = irq_chip_set_affinity_parent ,
# endif
2015-02-23 20:45:18 +03:00
} ;
2015-10-13 14:51:33 +03:00
static int imx_gpc_domain_translate ( struct irq_domain * d ,
struct irq_fwspec * fwspec ,
unsigned long * hwirq ,
unsigned int * type )
2011-09-06 10:39:44 +04:00
{
2015-10-13 14:51:33 +03:00
if ( is_of_node ( fwspec - > fwnode ) ) {
if ( fwspec - > param_count ! = 3 )
return - EINVAL ;
2015-02-23 20:45:18 +03:00
2015-10-13 14:51:33 +03:00
/* No PPI should point to this domain */
if ( fwspec - > param [ 0 ] ! = 0 )
return - EINVAL ;
* hwirq = fwspec - > param [ 1 ] ;
* type = fwspec - > param [ 2 ] ;
return 0 ;
}
return - EINVAL ;
2015-02-23 20:45:18 +03:00
}
static int imx_gpc_domain_alloc ( struct irq_domain * domain ,
unsigned int irq ,
unsigned int nr_irqs , void * data )
{
2015-10-13 14:51:33 +03:00
struct irq_fwspec * fwspec = data ;
struct irq_fwspec parent_fwspec ;
2015-02-23 20:45:18 +03:00
irq_hw_number_t hwirq ;
2012-12-04 18:55:13 +04:00
int i ;
2011-09-06 10:39:44 +04:00
2015-10-13 14:51:33 +03:00
if ( fwspec - > param_count ! = 3 )
2015-02-23 20:45:18 +03:00
return - EINVAL ; /* Not GIC compliant */
2015-10-13 14:51:33 +03:00
if ( fwspec - > param [ 0 ] ! = 0 )
2015-02-23 20:45:18 +03:00
return - EINVAL ; /* No PPI should point to this domain */
2015-10-13 14:51:33 +03:00
hwirq = fwspec - > param [ 1 ] ;
2015-02-23 20:45:18 +03:00
if ( hwirq > = GPC_MAX_IRQS )
return - EINVAL ; /* Can't deal with this */
for ( i = 0 ; i < nr_irqs ; i + + )
irq_domain_set_hwirq_and_chip ( domain , irq + i , hwirq + i ,
& imx_gpc_chip , NULL ) ;
2015-10-13 14:51:33 +03:00
parent_fwspec = * fwspec ;
parent_fwspec . fwnode = domain - > parent - > fwnode ;
return irq_domain_alloc_irqs_parent ( domain , irq , nr_irqs ,
& parent_fwspec ) ;
2015-02-23 20:45:18 +03:00
}
2015-04-27 15:51:39 +03:00
static const struct irq_domain_ops imx_gpc_domain_ops = {
2015-10-13 14:51:33 +03:00
. translate = imx_gpc_domain_translate ,
. alloc = imx_gpc_domain_alloc ,
. free = irq_domain_free_irqs_common ,
2015-02-23 20:45:18 +03:00
} ;
static int __init imx_gpc_init ( struct device_node * node ,
struct device_node * parent )
{
struct irq_domain * parent_domain , * domain ;
int i ;
if ( ! parent ) {
2017-07-21 22:28:32 +03:00
pr_err ( " %pOF: no parent, giving up \n " , node ) ;
2015-02-23 20:45:18 +03:00
return - ENODEV ;
}
parent_domain = irq_find_host ( parent ) ;
if ( ! parent_domain ) {
2017-07-21 22:28:32 +03:00
pr_err ( " %pOF: unable to obtain parent domain \n " , node ) ;
2015-02-23 20:45:18 +03:00
return - ENXIO ;
}
gpc_base = of_iomap ( node , 0 ) ;
if ( WARN_ON ( ! gpc_base ) )
return - ENOMEM ;
domain = irq_domain_add_hierarchy ( parent_domain , 0 , GPC_MAX_IRQS ,
node , & imx_gpc_domain_ops ,
NULL ) ;
if ( ! domain ) {
iounmap ( gpc_base ) ;
return - ENOMEM ;
}
2011-09-06 10:39:44 +04:00
2012-12-04 18:55:13 +04:00
/* Initially mask all interrupts */
for ( i = 0 ; i < IMR_NUM ; i + + )
writel_relaxed ( ~ 0 , gpc_base + GPC_IMR1 + i * 4 ) ;
2016-08-09 17:18:52 +03:00
/*
* Clear the OF_POPULATED flag set in of_irq_init so that
* later the GPC power domain driver will not be skipped .
*/
of_node_clear_flag ( node , OF_POPULATED ) ;
2015-02-23 20:45:18 +03:00
return 0 ;
2011-09-06 10:39:44 +04:00
}
2015-10-16 17:21:10 +03:00
IRQCHIP_DECLARE ( imx_gpc , " fsl,imx6q-gpc " , imx_gpc_init ) ;
2015-02-23 20:45:18 +03:00
2015-03-13 19:05:37 +03:00
void __init imx_gpc_check_dt ( void )
{
struct device_node * np ;
np = of_find_compatible_node ( NULL , NULL , " fsl,imx6q-gpc " ) ;
2015-05-26 19:43:36 +03:00
if ( WARN_ON ( ! np ) )
return ;
if ( WARN_ON ( ! of_find_property ( np , " interrupt-controller " , NULL ) ) ) {
pr_warn ( " Outdated DT detected, suspend/resume will NOT work \n " ) ;
/* map GPC, so that at least CPUidle and WARs keep working */
gpc_base = of_iomap ( np , 0 ) ;
}
2015-03-13 19:05:37 +03:00
}