162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright 2011-2013 Freescale Semiconductor, Inc. 462306a36Sopenharmony_ci * Copyright 2011 Linaro Ltd. 562306a36Sopenharmony_ci */ 662306a36Sopenharmony_ci 762306a36Sopenharmony_ci#include <linux/io.h> 862306a36Sopenharmony_ci#include <linux/irq.h> 962306a36Sopenharmony_ci#include <linux/irqchip.h> 1062306a36Sopenharmony_ci#include <linux/of.h> 1162306a36Sopenharmony_ci#include <linux/of_address.h> 1262306a36Sopenharmony_ci#include <linux/of_irq.h> 1362306a36Sopenharmony_ci 1462306a36Sopenharmony_ci#include "common.h" 1562306a36Sopenharmony_ci#include "hardware.h" 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ci#define GPC_CNTR 0x0 1862306a36Sopenharmony_ci#define GPC_IMR1 0x008 1962306a36Sopenharmony_ci#define GPC_PGC_CPU_PDN 0x2a0 2062306a36Sopenharmony_ci#define GPC_PGC_CPU_PUPSCR 0x2a4 2162306a36Sopenharmony_ci#define GPC_PGC_CPU_PDNSCR 0x2a8 2262306a36Sopenharmony_ci#define GPC_PGC_SW2ISO_SHIFT 0x8 2362306a36Sopenharmony_ci#define GPC_PGC_SW_SHIFT 0x0 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ci#define GPC_CNTR_L2_PGE_SHIFT 22 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_ci#define IMR_NUM 4 2862306a36Sopenharmony_ci#define GPC_MAX_IRQS (IMR_NUM * 32) 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_cistatic void __iomem *gpc_base; 3162306a36Sopenharmony_cistatic u32 gpc_wake_irqs[IMR_NUM]; 3262306a36Sopenharmony_cistatic u32 gpc_saved_imrs[IMR_NUM]; 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_civoid imx_gpc_set_arm_power_up_timing(u32 sw2iso, u32 sw) 3562306a36Sopenharmony_ci{ 3662306a36Sopenharmony_ci writel_relaxed((sw2iso << GPC_PGC_SW2ISO_SHIFT) | 3762306a36Sopenharmony_ci (sw << GPC_PGC_SW_SHIFT), gpc_base + GPC_PGC_CPU_PUPSCR); 3862306a36Sopenharmony_ci} 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_civoid imx_gpc_set_arm_power_down_timing(u32 sw2iso, u32 sw) 4162306a36Sopenharmony_ci{ 4262306a36Sopenharmony_ci writel_relaxed((sw2iso << GPC_PGC_SW2ISO_SHIFT) | 4362306a36Sopenharmony_ci (sw << GPC_PGC_SW_SHIFT), gpc_base + GPC_PGC_CPU_PDNSCR); 4462306a36Sopenharmony_ci} 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_civoid imx_gpc_set_arm_power_in_lpm(bool power_off) 4762306a36Sopenharmony_ci{ 4862306a36Sopenharmony_ci writel_relaxed(power_off, gpc_base + GPC_PGC_CPU_PDN); 4962306a36Sopenharmony_ci} 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_civoid imx_gpc_set_l2_mem_power_in_lpm(bool power_off) 5262306a36Sopenharmony_ci{ 5362306a36Sopenharmony_ci u32 val; 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_ci val = readl_relaxed(gpc_base + GPC_CNTR); 5662306a36Sopenharmony_ci val &= ~(1 << GPC_CNTR_L2_PGE_SHIFT); 5762306a36Sopenharmony_ci if (power_off) 5862306a36Sopenharmony_ci val |= 1 << GPC_CNTR_L2_PGE_SHIFT; 5962306a36Sopenharmony_ci writel_relaxed(val, gpc_base + GPC_CNTR); 6062306a36Sopenharmony_ci} 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_civoid imx_gpc_pre_suspend(bool arm_power_off) 6362306a36Sopenharmony_ci{ 6462306a36Sopenharmony_ci void __iomem *reg_imr1 = gpc_base + GPC_IMR1; 6562306a36Sopenharmony_ci int i; 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci /* Tell GPC to power off ARM core when suspend */ 6862306a36Sopenharmony_ci if (arm_power_off) 6962306a36Sopenharmony_ci imx_gpc_set_arm_power_in_lpm(arm_power_off); 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci for (i = 0; i < IMR_NUM; i++) { 7262306a36Sopenharmony_ci gpc_saved_imrs[i] = readl_relaxed(reg_imr1 + i * 4); 7362306a36Sopenharmony_ci writel_relaxed(~gpc_wake_irqs[i], reg_imr1 + i * 4); 7462306a36Sopenharmony_ci } 7562306a36Sopenharmony_ci} 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_civoid imx_gpc_post_resume(void) 7862306a36Sopenharmony_ci{ 7962306a36Sopenharmony_ci void __iomem *reg_imr1 = gpc_base + GPC_IMR1; 8062306a36Sopenharmony_ci int i; 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci /* Keep ARM core powered on for other low-power modes */ 8362306a36Sopenharmony_ci imx_gpc_set_arm_power_in_lpm(false); 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci for (i = 0; i < IMR_NUM; i++) 8662306a36Sopenharmony_ci writel_relaxed(gpc_saved_imrs[i], reg_imr1 + i * 4); 8762306a36Sopenharmony_ci} 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_cistatic int imx_gpc_irq_set_wake(struct irq_data *d, unsigned int on) 9062306a36Sopenharmony_ci{ 9162306a36Sopenharmony_ci unsigned int idx = d->hwirq / 32; 9262306a36Sopenharmony_ci u32 mask; 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci mask = 1 << d->hwirq % 32; 9562306a36Sopenharmony_ci gpc_wake_irqs[idx] = on ? gpc_wake_irqs[idx] | mask : 9662306a36Sopenharmony_ci gpc_wake_irqs[idx] & ~mask; 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_ci /* 9962306a36Sopenharmony_ci * Do *not* call into the parent, as the GIC doesn't have any 10062306a36Sopenharmony_ci * wake-up facility... 10162306a36Sopenharmony_ci */ 10262306a36Sopenharmony_ci return 0; 10362306a36Sopenharmony_ci} 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_civoid imx_gpc_mask_all(void) 10662306a36Sopenharmony_ci{ 10762306a36Sopenharmony_ci void __iomem *reg_imr1 = gpc_base + GPC_IMR1; 10862306a36Sopenharmony_ci int i; 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci for (i = 0; i < IMR_NUM; i++) { 11162306a36Sopenharmony_ci gpc_saved_imrs[i] = readl_relaxed(reg_imr1 + i * 4); 11262306a36Sopenharmony_ci writel_relaxed(~0, reg_imr1 + i * 4); 11362306a36Sopenharmony_ci } 11462306a36Sopenharmony_ci} 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_civoid imx_gpc_restore_all(void) 11762306a36Sopenharmony_ci{ 11862306a36Sopenharmony_ci void __iomem *reg_imr1 = gpc_base + GPC_IMR1; 11962306a36Sopenharmony_ci int i; 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci for (i = 0; i < IMR_NUM; i++) 12262306a36Sopenharmony_ci writel_relaxed(gpc_saved_imrs[i], reg_imr1 + i * 4); 12362306a36Sopenharmony_ci} 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_civoid imx_gpc_hwirq_unmask(unsigned int hwirq) 12662306a36Sopenharmony_ci{ 12762306a36Sopenharmony_ci void __iomem *reg; 12862306a36Sopenharmony_ci u32 val; 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_ci reg = gpc_base + GPC_IMR1 + hwirq / 32 * 4; 13162306a36Sopenharmony_ci val = readl_relaxed(reg); 13262306a36Sopenharmony_ci val &= ~(1 << hwirq % 32); 13362306a36Sopenharmony_ci writel_relaxed(val, reg); 13462306a36Sopenharmony_ci} 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_civoid imx_gpc_hwirq_mask(unsigned int hwirq) 13762306a36Sopenharmony_ci{ 13862306a36Sopenharmony_ci void __iomem *reg; 13962306a36Sopenharmony_ci u32 val; 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_ci reg = gpc_base + GPC_IMR1 + hwirq / 32 * 4; 14262306a36Sopenharmony_ci val = readl_relaxed(reg); 14362306a36Sopenharmony_ci val |= 1 << (hwirq % 32); 14462306a36Sopenharmony_ci writel_relaxed(val, reg); 14562306a36Sopenharmony_ci} 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_cistatic void imx_gpc_irq_unmask(struct irq_data *d) 14862306a36Sopenharmony_ci{ 14962306a36Sopenharmony_ci imx_gpc_hwirq_unmask(d->hwirq); 15062306a36Sopenharmony_ci irq_chip_unmask_parent(d); 15162306a36Sopenharmony_ci} 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_cistatic void imx_gpc_irq_mask(struct irq_data *d) 15462306a36Sopenharmony_ci{ 15562306a36Sopenharmony_ci imx_gpc_hwirq_mask(d->hwirq); 15662306a36Sopenharmony_ci irq_chip_mask_parent(d); 15762306a36Sopenharmony_ci} 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_cistatic struct irq_chip imx_gpc_chip = { 16062306a36Sopenharmony_ci .name = "GPC", 16162306a36Sopenharmony_ci .irq_eoi = irq_chip_eoi_parent, 16262306a36Sopenharmony_ci .irq_mask = imx_gpc_irq_mask, 16362306a36Sopenharmony_ci .irq_unmask = imx_gpc_irq_unmask, 16462306a36Sopenharmony_ci .irq_retrigger = irq_chip_retrigger_hierarchy, 16562306a36Sopenharmony_ci .irq_set_wake = imx_gpc_irq_set_wake, 16662306a36Sopenharmony_ci .irq_set_type = irq_chip_set_type_parent, 16762306a36Sopenharmony_ci#ifdef CONFIG_SMP 16862306a36Sopenharmony_ci .irq_set_affinity = irq_chip_set_affinity_parent, 16962306a36Sopenharmony_ci#endif 17062306a36Sopenharmony_ci}; 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_cistatic int imx_gpc_domain_translate(struct irq_domain *d, 17362306a36Sopenharmony_ci struct irq_fwspec *fwspec, 17462306a36Sopenharmony_ci unsigned long *hwirq, 17562306a36Sopenharmony_ci unsigned int *type) 17662306a36Sopenharmony_ci{ 17762306a36Sopenharmony_ci if (is_of_node(fwspec->fwnode)) { 17862306a36Sopenharmony_ci if (fwspec->param_count != 3) 17962306a36Sopenharmony_ci return -EINVAL; 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci /* No PPI should point to this domain */ 18262306a36Sopenharmony_ci if (fwspec->param[0] != 0) 18362306a36Sopenharmony_ci return -EINVAL; 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ci *hwirq = fwspec->param[1]; 18662306a36Sopenharmony_ci *type = fwspec->param[2]; 18762306a36Sopenharmony_ci return 0; 18862306a36Sopenharmony_ci } 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_ci return -EINVAL; 19162306a36Sopenharmony_ci} 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_cistatic int imx_gpc_domain_alloc(struct irq_domain *domain, 19462306a36Sopenharmony_ci unsigned int irq, 19562306a36Sopenharmony_ci unsigned int nr_irqs, void *data) 19662306a36Sopenharmony_ci{ 19762306a36Sopenharmony_ci struct irq_fwspec *fwspec = data; 19862306a36Sopenharmony_ci struct irq_fwspec parent_fwspec; 19962306a36Sopenharmony_ci irq_hw_number_t hwirq; 20062306a36Sopenharmony_ci int i; 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ci if (fwspec->param_count != 3) 20362306a36Sopenharmony_ci return -EINVAL; /* Not GIC compliant */ 20462306a36Sopenharmony_ci if (fwspec->param[0] != 0) 20562306a36Sopenharmony_ci return -EINVAL; /* No PPI should point to this domain */ 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci hwirq = fwspec->param[1]; 20862306a36Sopenharmony_ci if (hwirq >= GPC_MAX_IRQS) 20962306a36Sopenharmony_ci return -EINVAL; /* Can't deal with this */ 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci for (i = 0; i < nr_irqs; i++) 21262306a36Sopenharmony_ci irq_domain_set_hwirq_and_chip(domain, irq + i, hwirq + i, 21362306a36Sopenharmony_ci &imx_gpc_chip, NULL); 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_ci parent_fwspec = *fwspec; 21662306a36Sopenharmony_ci parent_fwspec.fwnode = domain->parent->fwnode; 21762306a36Sopenharmony_ci return irq_domain_alloc_irqs_parent(domain, irq, nr_irqs, 21862306a36Sopenharmony_ci &parent_fwspec); 21962306a36Sopenharmony_ci} 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_cistatic const struct irq_domain_ops imx_gpc_domain_ops = { 22262306a36Sopenharmony_ci .translate = imx_gpc_domain_translate, 22362306a36Sopenharmony_ci .alloc = imx_gpc_domain_alloc, 22462306a36Sopenharmony_ci .free = irq_domain_free_irqs_common, 22562306a36Sopenharmony_ci}; 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_cistatic int __init imx_gpc_init(struct device_node *node, 22862306a36Sopenharmony_ci struct device_node *parent) 22962306a36Sopenharmony_ci{ 23062306a36Sopenharmony_ci struct irq_domain *parent_domain, *domain; 23162306a36Sopenharmony_ci int i; 23262306a36Sopenharmony_ci 23362306a36Sopenharmony_ci if (!parent) { 23462306a36Sopenharmony_ci pr_err("%pOF: no parent, giving up\n", node); 23562306a36Sopenharmony_ci return -ENODEV; 23662306a36Sopenharmony_ci } 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_ci parent_domain = irq_find_host(parent); 23962306a36Sopenharmony_ci if (!parent_domain) { 24062306a36Sopenharmony_ci pr_err("%pOF: unable to obtain parent domain\n", node); 24162306a36Sopenharmony_ci return -ENXIO; 24262306a36Sopenharmony_ci } 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_ci gpc_base = of_iomap(node, 0); 24562306a36Sopenharmony_ci if (WARN_ON(!gpc_base)) 24662306a36Sopenharmony_ci return -ENOMEM; 24762306a36Sopenharmony_ci 24862306a36Sopenharmony_ci domain = irq_domain_add_hierarchy(parent_domain, 0, GPC_MAX_IRQS, 24962306a36Sopenharmony_ci node, &imx_gpc_domain_ops, 25062306a36Sopenharmony_ci NULL); 25162306a36Sopenharmony_ci if (!domain) { 25262306a36Sopenharmony_ci iounmap(gpc_base); 25362306a36Sopenharmony_ci return -ENOMEM; 25462306a36Sopenharmony_ci } 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ci /* Initially mask all interrupts */ 25762306a36Sopenharmony_ci for (i = 0; i < IMR_NUM; i++) 25862306a36Sopenharmony_ci writel_relaxed(~0, gpc_base + GPC_IMR1 + i * 4); 25962306a36Sopenharmony_ci 26062306a36Sopenharmony_ci /* 26162306a36Sopenharmony_ci * Clear the OF_POPULATED flag set in of_irq_init so that 26262306a36Sopenharmony_ci * later the GPC power domain driver will not be skipped. 26362306a36Sopenharmony_ci */ 26462306a36Sopenharmony_ci of_node_clear_flag(node, OF_POPULATED); 26562306a36Sopenharmony_ci 26662306a36Sopenharmony_ci return 0; 26762306a36Sopenharmony_ci} 26862306a36Sopenharmony_ciIRQCHIP_DECLARE(imx_gpc, "fsl,imx6q-gpc", imx_gpc_init); 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_civoid __init imx_gpc_check_dt(void) 27162306a36Sopenharmony_ci{ 27262306a36Sopenharmony_ci struct device_node *np; 27362306a36Sopenharmony_ci 27462306a36Sopenharmony_ci np = of_find_compatible_node(NULL, NULL, "fsl,imx6q-gpc"); 27562306a36Sopenharmony_ci if (WARN_ON(!np)) 27662306a36Sopenharmony_ci return; 27762306a36Sopenharmony_ci 27862306a36Sopenharmony_ci if (WARN_ON(!of_property_read_bool(np, "interrupt-controller"))) { 27962306a36Sopenharmony_ci pr_warn("Outdated DT detected, suspend/resume will NOT work\n"); 28062306a36Sopenharmony_ci 28162306a36Sopenharmony_ci /* map GPC, so that at least CPUidle and WARs keep working */ 28262306a36Sopenharmony_ci gpc_base = of_iomap(np, 0); 28362306a36Sopenharmony_ci } 28462306a36Sopenharmony_ci of_node_put(np); 28562306a36Sopenharmony_ci} 286