18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright 2011-2013 Freescale Semiconductor, Inc. 48c2ecf20Sopenharmony_ci * Copyright 2011 Linaro Ltd. 58c2ecf20Sopenharmony_ci */ 68c2ecf20Sopenharmony_ci 78c2ecf20Sopenharmony_ci#include <linux/io.h> 88c2ecf20Sopenharmony_ci#include <linux/irq.h> 98c2ecf20Sopenharmony_ci#include <linux/irqchip.h> 108c2ecf20Sopenharmony_ci#include <linux/of.h> 118c2ecf20Sopenharmony_ci#include <linux/of_address.h> 128c2ecf20Sopenharmony_ci#include <linux/of_irq.h> 138c2ecf20Sopenharmony_ci 148c2ecf20Sopenharmony_ci#include "common.h" 158c2ecf20Sopenharmony_ci#include "hardware.h" 168c2ecf20Sopenharmony_ci 178c2ecf20Sopenharmony_ci#define GPC_CNTR 0x0 188c2ecf20Sopenharmony_ci#define GPC_IMR1 0x008 198c2ecf20Sopenharmony_ci#define GPC_PGC_CPU_PDN 0x2a0 208c2ecf20Sopenharmony_ci#define GPC_PGC_CPU_PUPSCR 0x2a4 218c2ecf20Sopenharmony_ci#define GPC_PGC_CPU_PDNSCR 0x2a8 228c2ecf20Sopenharmony_ci#define GPC_PGC_SW2ISO_SHIFT 0x8 238c2ecf20Sopenharmony_ci#define GPC_PGC_SW_SHIFT 0x0 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_ci#define GPC_CNTR_L2_PGE_SHIFT 22 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_ci#define IMR_NUM 4 288c2ecf20Sopenharmony_ci#define GPC_MAX_IRQS (IMR_NUM * 32) 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_cistatic void __iomem *gpc_base; 318c2ecf20Sopenharmony_cistatic u32 gpc_wake_irqs[IMR_NUM]; 328c2ecf20Sopenharmony_cistatic u32 gpc_saved_imrs[IMR_NUM]; 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_civoid imx_gpc_set_arm_power_up_timing(u32 sw2iso, u32 sw) 358c2ecf20Sopenharmony_ci{ 368c2ecf20Sopenharmony_ci writel_relaxed((sw2iso << GPC_PGC_SW2ISO_SHIFT) | 378c2ecf20Sopenharmony_ci (sw << GPC_PGC_SW_SHIFT), gpc_base + GPC_PGC_CPU_PUPSCR); 388c2ecf20Sopenharmony_ci} 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_civoid imx_gpc_set_arm_power_down_timing(u32 sw2iso, u32 sw) 418c2ecf20Sopenharmony_ci{ 428c2ecf20Sopenharmony_ci writel_relaxed((sw2iso << GPC_PGC_SW2ISO_SHIFT) | 438c2ecf20Sopenharmony_ci (sw << GPC_PGC_SW_SHIFT), gpc_base + GPC_PGC_CPU_PDNSCR); 448c2ecf20Sopenharmony_ci} 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_civoid imx_gpc_set_arm_power_in_lpm(bool power_off) 478c2ecf20Sopenharmony_ci{ 488c2ecf20Sopenharmony_ci writel_relaxed(power_off, gpc_base + GPC_PGC_CPU_PDN); 498c2ecf20Sopenharmony_ci} 508c2ecf20Sopenharmony_ci 518c2ecf20Sopenharmony_civoid imx_gpc_set_l2_mem_power_in_lpm(bool power_off) 528c2ecf20Sopenharmony_ci{ 538c2ecf20Sopenharmony_ci u32 val; 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_ci val = readl_relaxed(gpc_base + GPC_CNTR); 568c2ecf20Sopenharmony_ci val &= ~(1 << GPC_CNTR_L2_PGE_SHIFT); 578c2ecf20Sopenharmony_ci if (power_off) 588c2ecf20Sopenharmony_ci val |= 1 << GPC_CNTR_L2_PGE_SHIFT; 598c2ecf20Sopenharmony_ci writel_relaxed(val, gpc_base + GPC_CNTR); 608c2ecf20Sopenharmony_ci} 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_civoid imx_gpc_pre_suspend(bool arm_power_off) 638c2ecf20Sopenharmony_ci{ 648c2ecf20Sopenharmony_ci void __iomem *reg_imr1 = gpc_base + GPC_IMR1; 658c2ecf20Sopenharmony_ci int i; 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_ci /* Tell GPC to power off ARM core when suspend */ 688c2ecf20Sopenharmony_ci if (arm_power_off) 698c2ecf20Sopenharmony_ci imx_gpc_set_arm_power_in_lpm(arm_power_off); 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_ci for (i = 0; i < IMR_NUM; i++) { 728c2ecf20Sopenharmony_ci gpc_saved_imrs[i] = readl_relaxed(reg_imr1 + i * 4); 738c2ecf20Sopenharmony_ci writel_relaxed(~gpc_wake_irqs[i], reg_imr1 + i * 4); 748c2ecf20Sopenharmony_ci } 758c2ecf20Sopenharmony_ci} 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_civoid imx_gpc_post_resume(void) 788c2ecf20Sopenharmony_ci{ 798c2ecf20Sopenharmony_ci void __iomem *reg_imr1 = gpc_base + GPC_IMR1; 808c2ecf20Sopenharmony_ci int i; 818c2ecf20Sopenharmony_ci 828c2ecf20Sopenharmony_ci /* Keep ARM core powered on for other low-power modes */ 838c2ecf20Sopenharmony_ci imx_gpc_set_arm_power_in_lpm(false); 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_ci for (i = 0; i < IMR_NUM; i++) 868c2ecf20Sopenharmony_ci writel_relaxed(gpc_saved_imrs[i], reg_imr1 + i * 4); 878c2ecf20Sopenharmony_ci} 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_cistatic int imx_gpc_irq_set_wake(struct irq_data *d, unsigned int on) 908c2ecf20Sopenharmony_ci{ 918c2ecf20Sopenharmony_ci unsigned int idx = d->hwirq / 32; 928c2ecf20Sopenharmony_ci u32 mask; 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_ci mask = 1 << d->hwirq % 32; 958c2ecf20Sopenharmony_ci gpc_wake_irqs[idx] = on ? gpc_wake_irqs[idx] | mask : 968c2ecf20Sopenharmony_ci gpc_wake_irqs[idx] & ~mask; 978c2ecf20Sopenharmony_ci 988c2ecf20Sopenharmony_ci /* 998c2ecf20Sopenharmony_ci * Do *not* call into the parent, as the GIC doesn't have any 1008c2ecf20Sopenharmony_ci * wake-up facility... 1018c2ecf20Sopenharmony_ci */ 1028c2ecf20Sopenharmony_ci return 0; 1038c2ecf20Sopenharmony_ci} 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_civoid imx_gpc_mask_all(void) 1068c2ecf20Sopenharmony_ci{ 1078c2ecf20Sopenharmony_ci void __iomem *reg_imr1 = gpc_base + GPC_IMR1; 1088c2ecf20Sopenharmony_ci int i; 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_ci for (i = 0; i < IMR_NUM; i++) { 1118c2ecf20Sopenharmony_ci gpc_saved_imrs[i] = readl_relaxed(reg_imr1 + i * 4); 1128c2ecf20Sopenharmony_ci writel_relaxed(~0, reg_imr1 + i * 4); 1138c2ecf20Sopenharmony_ci } 1148c2ecf20Sopenharmony_ci} 1158c2ecf20Sopenharmony_ci 1168c2ecf20Sopenharmony_civoid imx_gpc_restore_all(void) 1178c2ecf20Sopenharmony_ci{ 1188c2ecf20Sopenharmony_ci void __iomem *reg_imr1 = gpc_base + GPC_IMR1; 1198c2ecf20Sopenharmony_ci int i; 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_ci for (i = 0; i < IMR_NUM; i++) 1228c2ecf20Sopenharmony_ci writel_relaxed(gpc_saved_imrs[i], reg_imr1 + i * 4); 1238c2ecf20Sopenharmony_ci} 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_civoid imx_gpc_hwirq_unmask(unsigned int hwirq) 1268c2ecf20Sopenharmony_ci{ 1278c2ecf20Sopenharmony_ci void __iomem *reg; 1288c2ecf20Sopenharmony_ci u32 val; 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci reg = gpc_base + GPC_IMR1 + hwirq / 32 * 4; 1318c2ecf20Sopenharmony_ci val = readl_relaxed(reg); 1328c2ecf20Sopenharmony_ci val &= ~(1 << hwirq % 32); 1338c2ecf20Sopenharmony_ci writel_relaxed(val, reg); 1348c2ecf20Sopenharmony_ci} 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_civoid imx_gpc_hwirq_mask(unsigned int hwirq) 1378c2ecf20Sopenharmony_ci{ 1388c2ecf20Sopenharmony_ci void __iomem *reg; 1398c2ecf20Sopenharmony_ci u32 val; 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_ci reg = gpc_base + GPC_IMR1 + hwirq / 32 * 4; 1428c2ecf20Sopenharmony_ci val = readl_relaxed(reg); 1438c2ecf20Sopenharmony_ci val |= 1 << (hwirq % 32); 1448c2ecf20Sopenharmony_ci writel_relaxed(val, reg); 1458c2ecf20Sopenharmony_ci} 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_cistatic void imx_gpc_irq_unmask(struct irq_data *d) 1488c2ecf20Sopenharmony_ci{ 1498c2ecf20Sopenharmony_ci imx_gpc_hwirq_unmask(d->hwirq); 1508c2ecf20Sopenharmony_ci irq_chip_unmask_parent(d); 1518c2ecf20Sopenharmony_ci} 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_cistatic void imx_gpc_irq_mask(struct irq_data *d) 1548c2ecf20Sopenharmony_ci{ 1558c2ecf20Sopenharmony_ci imx_gpc_hwirq_mask(d->hwirq); 1568c2ecf20Sopenharmony_ci irq_chip_mask_parent(d); 1578c2ecf20Sopenharmony_ci} 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_cistatic struct irq_chip imx_gpc_chip = { 1608c2ecf20Sopenharmony_ci .name = "GPC", 1618c2ecf20Sopenharmony_ci .irq_eoi = irq_chip_eoi_parent, 1628c2ecf20Sopenharmony_ci .irq_mask = imx_gpc_irq_mask, 1638c2ecf20Sopenharmony_ci .irq_unmask = imx_gpc_irq_unmask, 1648c2ecf20Sopenharmony_ci .irq_retrigger = irq_chip_retrigger_hierarchy, 1658c2ecf20Sopenharmony_ci .irq_set_wake = imx_gpc_irq_set_wake, 1668c2ecf20Sopenharmony_ci .irq_set_type = irq_chip_set_type_parent, 1678c2ecf20Sopenharmony_ci#ifdef CONFIG_SMP 1688c2ecf20Sopenharmony_ci .irq_set_affinity = irq_chip_set_affinity_parent, 1698c2ecf20Sopenharmony_ci#endif 1708c2ecf20Sopenharmony_ci}; 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_cistatic int imx_gpc_domain_translate(struct irq_domain *d, 1738c2ecf20Sopenharmony_ci struct irq_fwspec *fwspec, 1748c2ecf20Sopenharmony_ci unsigned long *hwirq, 1758c2ecf20Sopenharmony_ci unsigned int *type) 1768c2ecf20Sopenharmony_ci{ 1778c2ecf20Sopenharmony_ci if (is_of_node(fwspec->fwnode)) { 1788c2ecf20Sopenharmony_ci if (fwspec->param_count != 3) 1798c2ecf20Sopenharmony_ci return -EINVAL; 1808c2ecf20Sopenharmony_ci 1818c2ecf20Sopenharmony_ci /* No PPI should point to this domain */ 1828c2ecf20Sopenharmony_ci if (fwspec->param[0] != 0) 1838c2ecf20Sopenharmony_ci return -EINVAL; 1848c2ecf20Sopenharmony_ci 1858c2ecf20Sopenharmony_ci *hwirq = fwspec->param[1]; 1868c2ecf20Sopenharmony_ci *type = fwspec->param[2]; 1878c2ecf20Sopenharmony_ci return 0; 1888c2ecf20Sopenharmony_ci } 1898c2ecf20Sopenharmony_ci 1908c2ecf20Sopenharmony_ci return -EINVAL; 1918c2ecf20Sopenharmony_ci} 1928c2ecf20Sopenharmony_ci 1938c2ecf20Sopenharmony_cistatic int imx_gpc_domain_alloc(struct irq_domain *domain, 1948c2ecf20Sopenharmony_ci unsigned int irq, 1958c2ecf20Sopenharmony_ci unsigned int nr_irqs, void *data) 1968c2ecf20Sopenharmony_ci{ 1978c2ecf20Sopenharmony_ci struct irq_fwspec *fwspec = data; 1988c2ecf20Sopenharmony_ci struct irq_fwspec parent_fwspec; 1998c2ecf20Sopenharmony_ci irq_hw_number_t hwirq; 2008c2ecf20Sopenharmony_ci int i; 2018c2ecf20Sopenharmony_ci 2028c2ecf20Sopenharmony_ci if (fwspec->param_count != 3) 2038c2ecf20Sopenharmony_ci return -EINVAL; /* Not GIC compliant */ 2048c2ecf20Sopenharmony_ci if (fwspec->param[0] != 0) 2058c2ecf20Sopenharmony_ci return -EINVAL; /* No PPI should point to this domain */ 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_ci hwirq = fwspec->param[1]; 2088c2ecf20Sopenharmony_ci if (hwirq >= GPC_MAX_IRQS) 2098c2ecf20Sopenharmony_ci return -EINVAL; /* Can't deal with this */ 2108c2ecf20Sopenharmony_ci 2118c2ecf20Sopenharmony_ci for (i = 0; i < nr_irqs; i++) 2128c2ecf20Sopenharmony_ci irq_domain_set_hwirq_and_chip(domain, irq + i, hwirq + i, 2138c2ecf20Sopenharmony_ci &imx_gpc_chip, NULL); 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_ci parent_fwspec = *fwspec; 2168c2ecf20Sopenharmony_ci parent_fwspec.fwnode = domain->parent->fwnode; 2178c2ecf20Sopenharmony_ci return irq_domain_alloc_irqs_parent(domain, irq, nr_irqs, 2188c2ecf20Sopenharmony_ci &parent_fwspec); 2198c2ecf20Sopenharmony_ci} 2208c2ecf20Sopenharmony_ci 2218c2ecf20Sopenharmony_cistatic const struct irq_domain_ops imx_gpc_domain_ops = { 2228c2ecf20Sopenharmony_ci .translate = imx_gpc_domain_translate, 2238c2ecf20Sopenharmony_ci .alloc = imx_gpc_domain_alloc, 2248c2ecf20Sopenharmony_ci .free = irq_domain_free_irqs_common, 2258c2ecf20Sopenharmony_ci}; 2268c2ecf20Sopenharmony_ci 2278c2ecf20Sopenharmony_cistatic int __init imx_gpc_init(struct device_node *node, 2288c2ecf20Sopenharmony_ci struct device_node *parent) 2298c2ecf20Sopenharmony_ci{ 2308c2ecf20Sopenharmony_ci struct irq_domain *parent_domain, *domain; 2318c2ecf20Sopenharmony_ci int i; 2328c2ecf20Sopenharmony_ci 2338c2ecf20Sopenharmony_ci if (!parent) { 2348c2ecf20Sopenharmony_ci pr_err("%pOF: no parent, giving up\n", node); 2358c2ecf20Sopenharmony_ci return -ENODEV; 2368c2ecf20Sopenharmony_ci } 2378c2ecf20Sopenharmony_ci 2388c2ecf20Sopenharmony_ci parent_domain = irq_find_host(parent); 2398c2ecf20Sopenharmony_ci if (!parent_domain) { 2408c2ecf20Sopenharmony_ci pr_err("%pOF: unable to obtain parent domain\n", node); 2418c2ecf20Sopenharmony_ci return -ENXIO; 2428c2ecf20Sopenharmony_ci } 2438c2ecf20Sopenharmony_ci 2448c2ecf20Sopenharmony_ci gpc_base = of_iomap(node, 0); 2458c2ecf20Sopenharmony_ci if (WARN_ON(!gpc_base)) 2468c2ecf20Sopenharmony_ci return -ENOMEM; 2478c2ecf20Sopenharmony_ci 2488c2ecf20Sopenharmony_ci domain = irq_domain_add_hierarchy(parent_domain, 0, GPC_MAX_IRQS, 2498c2ecf20Sopenharmony_ci node, &imx_gpc_domain_ops, 2508c2ecf20Sopenharmony_ci NULL); 2518c2ecf20Sopenharmony_ci if (!domain) { 2528c2ecf20Sopenharmony_ci iounmap(gpc_base); 2538c2ecf20Sopenharmony_ci return -ENOMEM; 2548c2ecf20Sopenharmony_ci } 2558c2ecf20Sopenharmony_ci 2568c2ecf20Sopenharmony_ci /* Initially mask all interrupts */ 2578c2ecf20Sopenharmony_ci for (i = 0; i < IMR_NUM; i++) 2588c2ecf20Sopenharmony_ci writel_relaxed(~0, gpc_base + GPC_IMR1 + i * 4); 2598c2ecf20Sopenharmony_ci 2608c2ecf20Sopenharmony_ci /* 2618c2ecf20Sopenharmony_ci * Clear the OF_POPULATED flag set in of_irq_init so that 2628c2ecf20Sopenharmony_ci * later the GPC power domain driver will not be skipped. 2638c2ecf20Sopenharmony_ci */ 2648c2ecf20Sopenharmony_ci of_node_clear_flag(node, OF_POPULATED); 2658c2ecf20Sopenharmony_ci 2668c2ecf20Sopenharmony_ci return 0; 2678c2ecf20Sopenharmony_ci} 2688c2ecf20Sopenharmony_ciIRQCHIP_DECLARE(imx_gpc, "fsl,imx6q-gpc", imx_gpc_init); 2698c2ecf20Sopenharmony_ci 2708c2ecf20Sopenharmony_civoid __init imx_gpc_check_dt(void) 2718c2ecf20Sopenharmony_ci{ 2728c2ecf20Sopenharmony_ci struct device_node *np; 2738c2ecf20Sopenharmony_ci 2748c2ecf20Sopenharmony_ci np = of_find_compatible_node(NULL, NULL, "fsl,imx6q-gpc"); 2758c2ecf20Sopenharmony_ci if (WARN_ON(!np)) 2768c2ecf20Sopenharmony_ci return; 2778c2ecf20Sopenharmony_ci 2788c2ecf20Sopenharmony_ci if (WARN_ON(!of_find_property(np, "interrupt-controller", NULL))) { 2798c2ecf20Sopenharmony_ci pr_warn("Outdated DT detected, suspend/resume will NOT work\n"); 2808c2ecf20Sopenharmony_ci 2818c2ecf20Sopenharmony_ci /* map GPC, so that at least CPUidle and WARs keep working */ 2828c2ecf20Sopenharmony_ci gpc_base = of_iomap(np, 0); 2838c2ecf20Sopenharmony_ci } 2848c2ecf20Sopenharmony_ci of_node_put(np); 2858c2ecf20Sopenharmony_ci} 286