18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci// 38c2ecf20Sopenharmony_ci// Author: Steve Chen <schen@mvista.com> 48c2ecf20Sopenharmony_ci// Copyright (C) 2008-2009, MontaVista Software, Inc. <source@mvista.com> 58c2ecf20Sopenharmony_ci// Author: Bartosz Golaszewski <bgolaszewski@baylibre.com> 68c2ecf20Sopenharmony_ci// Copyright (C) 2019, Texas Instruments 78c2ecf20Sopenharmony_ci// 88c2ecf20Sopenharmony_ci// TI Common Platform Interrupt Controller (cp_intc) driver 98c2ecf20Sopenharmony_ci 108c2ecf20Sopenharmony_ci#include <linux/export.h> 118c2ecf20Sopenharmony_ci#include <linux/init.h> 128c2ecf20Sopenharmony_ci#include <linux/irq.h> 138c2ecf20Sopenharmony_ci#include <linux/irqchip.h> 148c2ecf20Sopenharmony_ci#include <linux/irqchip/irq-davinci-cp-intc.h> 158c2ecf20Sopenharmony_ci#include <linux/irqdomain.h> 168c2ecf20Sopenharmony_ci#include <linux/io.h> 178c2ecf20Sopenharmony_ci#include <linux/of.h> 188c2ecf20Sopenharmony_ci#include <linux/of_address.h> 198c2ecf20Sopenharmony_ci#include <linux/of_irq.h> 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_ci#include <asm/exception.h> 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_ci#define DAVINCI_CP_INTC_CTRL 0x04 248c2ecf20Sopenharmony_ci#define DAVINCI_CP_INTC_HOST_CTRL 0x0c 258c2ecf20Sopenharmony_ci#define DAVINCI_CP_INTC_GLOBAL_ENABLE 0x10 268c2ecf20Sopenharmony_ci#define DAVINCI_CP_INTC_SYS_STAT_IDX_CLR 0x24 278c2ecf20Sopenharmony_ci#define DAVINCI_CP_INTC_SYS_ENABLE_IDX_SET 0x28 288c2ecf20Sopenharmony_ci#define DAVINCI_CP_INTC_SYS_ENABLE_IDX_CLR 0x2c 298c2ecf20Sopenharmony_ci#define DAVINCI_CP_INTC_HOST_ENABLE_IDX_SET 0x34 308c2ecf20Sopenharmony_ci#define DAVINCI_CP_INTC_HOST_ENABLE_IDX_CLR 0x38 318c2ecf20Sopenharmony_ci#define DAVINCI_CP_INTC_PRIO_IDX 0x80 328c2ecf20Sopenharmony_ci#define DAVINCI_CP_INTC_SYS_STAT_CLR(n) (0x0280 + (n << 2)) 338c2ecf20Sopenharmony_ci#define DAVINCI_CP_INTC_SYS_ENABLE_CLR(n) (0x0380 + (n << 2)) 348c2ecf20Sopenharmony_ci#define DAVINCI_CP_INTC_CHAN_MAP(n) (0x0400 + (n << 2)) 358c2ecf20Sopenharmony_ci#define DAVINCI_CP_INTC_SYS_POLARITY(n) (0x0d00 + (n << 2)) 368c2ecf20Sopenharmony_ci#define DAVINCI_CP_INTC_SYS_TYPE(n) (0x0d80 + (n << 2)) 378c2ecf20Sopenharmony_ci#define DAVINCI_CP_INTC_HOST_ENABLE(n) (0x1500 + (n << 2)) 388c2ecf20Sopenharmony_ci#define DAVINCI_CP_INTC_PRI_INDX_MASK GENMASK(9, 0) 398c2ecf20Sopenharmony_ci#define DAVINCI_CP_INTC_GPIR_NONE BIT(31) 408c2ecf20Sopenharmony_ci 418c2ecf20Sopenharmony_cistatic void __iomem *davinci_cp_intc_base; 428c2ecf20Sopenharmony_cistatic struct irq_domain *davinci_cp_intc_irq_domain; 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_cistatic inline unsigned int davinci_cp_intc_read(unsigned int offset) 458c2ecf20Sopenharmony_ci{ 468c2ecf20Sopenharmony_ci return readl_relaxed(davinci_cp_intc_base + offset); 478c2ecf20Sopenharmony_ci} 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_cistatic inline void davinci_cp_intc_write(unsigned long value, 508c2ecf20Sopenharmony_ci unsigned int offset) 518c2ecf20Sopenharmony_ci{ 528c2ecf20Sopenharmony_ci writel_relaxed(value, davinci_cp_intc_base + offset); 538c2ecf20Sopenharmony_ci} 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_cistatic void davinci_cp_intc_ack_irq(struct irq_data *d) 568c2ecf20Sopenharmony_ci{ 578c2ecf20Sopenharmony_ci davinci_cp_intc_write(d->hwirq, DAVINCI_CP_INTC_SYS_STAT_IDX_CLR); 588c2ecf20Sopenharmony_ci} 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_cistatic void davinci_cp_intc_mask_irq(struct irq_data *d) 618c2ecf20Sopenharmony_ci{ 628c2ecf20Sopenharmony_ci /* XXX don't know why we need to disable nIRQ here... */ 638c2ecf20Sopenharmony_ci davinci_cp_intc_write(1, DAVINCI_CP_INTC_HOST_ENABLE_IDX_CLR); 648c2ecf20Sopenharmony_ci davinci_cp_intc_write(d->hwirq, DAVINCI_CP_INTC_SYS_ENABLE_IDX_CLR); 658c2ecf20Sopenharmony_ci davinci_cp_intc_write(1, DAVINCI_CP_INTC_HOST_ENABLE_IDX_SET); 668c2ecf20Sopenharmony_ci} 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_cistatic void davinci_cp_intc_unmask_irq(struct irq_data *d) 698c2ecf20Sopenharmony_ci{ 708c2ecf20Sopenharmony_ci davinci_cp_intc_write(d->hwirq, DAVINCI_CP_INTC_SYS_ENABLE_IDX_SET); 718c2ecf20Sopenharmony_ci} 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_cistatic int davinci_cp_intc_set_irq_type(struct irq_data *d, 748c2ecf20Sopenharmony_ci unsigned int flow_type) 758c2ecf20Sopenharmony_ci{ 768c2ecf20Sopenharmony_ci unsigned int reg, mask, polarity, type; 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_ci reg = BIT_WORD(d->hwirq); 798c2ecf20Sopenharmony_ci mask = BIT_MASK(d->hwirq); 808c2ecf20Sopenharmony_ci polarity = davinci_cp_intc_read(DAVINCI_CP_INTC_SYS_POLARITY(reg)); 818c2ecf20Sopenharmony_ci type = davinci_cp_intc_read(DAVINCI_CP_INTC_SYS_TYPE(reg)); 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_ci switch (flow_type) { 848c2ecf20Sopenharmony_ci case IRQ_TYPE_EDGE_RISING: 858c2ecf20Sopenharmony_ci polarity |= mask; 868c2ecf20Sopenharmony_ci type |= mask; 878c2ecf20Sopenharmony_ci break; 888c2ecf20Sopenharmony_ci case IRQ_TYPE_EDGE_FALLING: 898c2ecf20Sopenharmony_ci polarity &= ~mask; 908c2ecf20Sopenharmony_ci type |= mask; 918c2ecf20Sopenharmony_ci break; 928c2ecf20Sopenharmony_ci case IRQ_TYPE_LEVEL_HIGH: 938c2ecf20Sopenharmony_ci polarity |= mask; 948c2ecf20Sopenharmony_ci type &= ~mask; 958c2ecf20Sopenharmony_ci break; 968c2ecf20Sopenharmony_ci case IRQ_TYPE_LEVEL_LOW: 978c2ecf20Sopenharmony_ci polarity &= ~mask; 988c2ecf20Sopenharmony_ci type &= ~mask; 998c2ecf20Sopenharmony_ci break; 1008c2ecf20Sopenharmony_ci default: 1018c2ecf20Sopenharmony_ci return -EINVAL; 1028c2ecf20Sopenharmony_ci } 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_ci davinci_cp_intc_write(polarity, DAVINCI_CP_INTC_SYS_POLARITY(reg)); 1058c2ecf20Sopenharmony_ci davinci_cp_intc_write(type, DAVINCI_CP_INTC_SYS_TYPE(reg)); 1068c2ecf20Sopenharmony_ci 1078c2ecf20Sopenharmony_ci return 0; 1088c2ecf20Sopenharmony_ci} 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_cistatic struct irq_chip davinci_cp_intc_irq_chip = { 1118c2ecf20Sopenharmony_ci .name = "cp_intc", 1128c2ecf20Sopenharmony_ci .irq_ack = davinci_cp_intc_ack_irq, 1138c2ecf20Sopenharmony_ci .irq_mask = davinci_cp_intc_mask_irq, 1148c2ecf20Sopenharmony_ci .irq_unmask = davinci_cp_intc_unmask_irq, 1158c2ecf20Sopenharmony_ci .irq_set_type = davinci_cp_intc_set_irq_type, 1168c2ecf20Sopenharmony_ci .flags = IRQCHIP_SKIP_SET_WAKE, 1178c2ecf20Sopenharmony_ci}; 1188c2ecf20Sopenharmony_ci 1198c2ecf20Sopenharmony_cistatic asmlinkage void __exception_irq_entry 1208c2ecf20Sopenharmony_cidavinci_cp_intc_handle_irq(struct pt_regs *regs) 1218c2ecf20Sopenharmony_ci{ 1228c2ecf20Sopenharmony_ci int gpir, irqnr, none; 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_ci /* 1258c2ecf20Sopenharmony_ci * The interrupt number is in first ten bits. The NONE field set to 1 1268c2ecf20Sopenharmony_ci * indicates a spurious irq. 1278c2ecf20Sopenharmony_ci */ 1288c2ecf20Sopenharmony_ci 1298c2ecf20Sopenharmony_ci gpir = davinci_cp_intc_read(DAVINCI_CP_INTC_PRIO_IDX); 1308c2ecf20Sopenharmony_ci irqnr = gpir & DAVINCI_CP_INTC_PRI_INDX_MASK; 1318c2ecf20Sopenharmony_ci none = gpir & DAVINCI_CP_INTC_GPIR_NONE; 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_ci if (unlikely(none)) { 1348c2ecf20Sopenharmony_ci pr_err_once("%s: spurious irq!\n", __func__); 1358c2ecf20Sopenharmony_ci return; 1368c2ecf20Sopenharmony_ci } 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_ci handle_domain_irq(davinci_cp_intc_irq_domain, irqnr, regs); 1398c2ecf20Sopenharmony_ci} 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_cistatic int davinci_cp_intc_host_map(struct irq_domain *h, unsigned int virq, 1428c2ecf20Sopenharmony_ci irq_hw_number_t hw) 1438c2ecf20Sopenharmony_ci{ 1448c2ecf20Sopenharmony_ci pr_debug("cp_intc_host_map(%d, 0x%lx)\n", virq, hw); 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_ci irq_set_chip(virq, &davinci_cp_intc_irq_chip); 1478c2ecf20Sopenharmony_ci irq_set_probe(virq); 1488c2ecf20Sopenharmony_ci irq_set_handler(virq, handle_edge_irq); 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_ci return 0; 1518c2ecf20Sopenharmony_ci} 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_cistatic const struct irq_domain_ops davinci_cp_intc_irq_domain_ops = { 1548c2ecf20Sopenharmony_ci .map = davinci_cp_intc_host_map, 1558c2ecf20Sopenharmony_ci .xlate = irq_domain_xlate_onetwocell, 1568c2ecf20Sopenharmony_ci}; 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_cistatic int __init 1598c2ecf20Sopenharmony_cidavinci_cp_intc_do_init(const struct davinci_cp_intc_config *config, 1608c2ecf20Sopenharmony_ci struct device_node *node) 1618c2ecf20Sopenharmony_ci{ 1628c2ecf20Sopenharmony_ci unsigned int num_regs = BITS_TO_LONGS(config->num_irqs); 1638c2ecf20Sopenharmony_ci int offset, irq_base; 1648c2ecf20Sopenharmony_ci void __iomem *req; 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_ci req = request_mem_region(config->reg.start, 1678c2ecf20Sopenharmony_ci resource_size(&config->reg), 1688c2ecf20Sopenharmony_ci "davinci-cp-intc"); 1698c2ecf20Sopenharmony_ci if (!req) { 1708c2ecf20Sopenharmony_ci pr_err("%s: register range busy\n", __func__); 1718c2ecf20Sopenharmony_ci return -EBUSY; 1728c2ecf20Sopenharmony_ci } 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_ci davinci_cp_intc_base = ioremap(config->reg.start, 1758c2ecf20Sopenharmony_ci resource_size(&config->reg)); 1768c2ecf20Sopenharmony_ci if (!davinci_cp_intc_base) { 1778c2ecf20Sopenharmony_ci pr_err("%s: unable to ioremap register range\n", __func__); 1788c2ecf20Sopenharmony_ci return -EINVAL; 1798c2ecf20Sopenharmony_ci } 1808c2ecf20Sopenharmony_ci 1818c2ecf20Sopenharmony_ci davinci_cp_intc_write(0, DAVINCI_CP_INTC_GLOBAL_ENABLE); 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_ci /* Disable all host interrupts */ 1848c2ecf20Sopenharmony_ci davinci_cp_intc_write(0, DAVINCI_CP_INTC_HOST_ENABLE(0)); 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_ci /* Disable system interrupts */ 1878c2ecf20Sopenharmony_ci for (offset = 0; offset < num_regs; offset++) 1888c2ecf20Sopenharmony_ci davinci_cp_intc_write(~0, 1898c2ecf20Sopenharmony_ci DAVINCI_CP_INTC_SYS_ENABLE_CLR(offset)); 1908c2ecf20Sopenharmony_ci 1918c2ecf20Sopenharmony_ci /* Set to normal mode, no nesting, no priority hold */ 1928c2ecf20Sopenharmony_ci davinci_cp_intc_write(0, DAVINCI_CP_INTC_CTRL); 1938c2ecf20Sopenharmony_ci davinci_cp_intc_write(0, DAVINCI_CP_INTC_HOST_CTRL); 1948c2ecf20Sopenharmony_ci 1958c2ecf20Sopenharmony_ci /* Clear system interrupt status */ 1968c2ecf20Sopenharmony_ci for (offset = 0; offset < num_regs; offset++) 1978c2ecf20Sopenharmony_ci davinci_cp_intc_write(~0, 1988c2ecf20Sopenharmony_ci DAVINCI_CP_INTC_SYS_STAT_CLR(offset)); 1998c2ecf20Sopenharmony_ci 2008c2ecf20Sopenharmony_ci /* Enable nIRQ (what about nFIQ?) */ 2018c2ecf20Sopenharmony_ci davinci_cp_intc_write(1, DAVINCI_CP_INTC_HOST_ENABLE_IDX_SET); 2028c2ecf20Sopenharmony_ci 2038c2ecf20Sopenharmony_ci /* Default all priorities to channel 7. */ 2048c2ecf20Sopenharmony_ci num_regs = (config->num_irqs + 3) >> 2; /* 4 channels per register */ 2058c2ecf20Sopenharmony_ci for (offset = 0; offset < num_regs; offset++) 2068c2ecf20Sopenharmony_ci davinci_cp_intc_write(0x07070707, 2078c2ecf20Sopenharmony_ci DAVINCI_CP_INTC_CHAN_MAP(offset)); 2088c2ecf20Sopenharmony_ci 2098c2ecf20Sopenharmony_ci irq_base = irq_alloc_descs(-1, 0, config->num_irqs, 0); 2108c2ecf20Sopenharmony_ci if (irq_base < 0) { 2118c2ecf20Sopenharmony_ci pr_err("%s: unable to allocate interrupt descriptors: %d\n", 2128c2ecf20Sopenharmony_ci __func__, irq_base); 2138c2ecf20Sopenharmony_ci return irq_base; 2148c2ecf20Sopenharmony_ci } 2158c2ecf20Sopenharmony_ci 2168c2ecf20Sopenharmony_ci davinci_cp_intc_irq_domain = irq_domain_add_legacy( 2178c2ecf20Sopenharmony_ci node, config->num_irqs, irq_base, 0, 2188c2ecf20Sopenharmony_ci &davinci_cp_intc_irq_domain_ops, NULL); 2198c2ecf20Sopenharmony_ci 2208c2ecf20Sopenharmony_ci if (!davinci_cp_intc_irq_domain) { 2218c2ecf20Sopenharmony_ci pr_err("%s: unable to create an interrupt domain\n", __func__); 2228c2ecf20Sopenharmony_ci return -EINVAL; 2238c2ecf20Sopenharmony_ci } 2248c2ecf20Sopenharmony_ci 2258c2ecf20Sopenharmony_ci set_handle_irq(davinci_cp_intc_handle_irq); 2268c2ecf20Sopenharmony_ci 2278c2ecf20Sopenharmony_ci /* Enable global interrupt */ 2288c2ecf20Sopenharmony_ci davinci_cp_intc_write(1, DAVINCI_CP_INTC_GLOBAL_ENABLE); 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_ci return 0; 2318c2ecf20Sopenharmony_ci} 2328c2ecf20Sopenharmony_ci 2338c2ecf20Sopenharmony_ciint __init davinci_cp_intc_init(const struct davinci_cp_intc_config *config) 2348c2ecf20Sopenharmony_ci{ 2358c2ecf20Sopenharmony_ci return davinci_cp_intc_do_init(config, NULL); 2368c2ecf20Sopenharmony_ci} 2378c2ecf20Sopenharmony_ci 2388c2ecf20Sopenharmony_cistatic int __init davinci_cp_intc_of_init(struct device_node *node, 2398c2ecf20Sopenharmony_ci struct device_node *parent) 2408c2ecf20Sopenharmony_ci{ 2418c2ecf20Sopenharmony_ci struct davinci_cp_intc_config config = { }; 2428c2ecf20Sopenharmony_ci int ret; 2438c2ecf20Sopenharmony_ci 2448c2ecf20Sopenharmony_ci ret = of_address_to_resource(node, 0, &config.reg); 2458c2ecf20Sopenharmony_ci if (ret) { 2468c2ecf20Sopenharmony_ci pr_err("%s: unable to get the register range from device-tree\n", 2478c2ecf20Sopenharmony_ci __func__); 2488c2ecf20Sopenharmony_ci return ret; 2498c2ecf20Sopenharmony_ci } 2508c2ecf20Sopenharmony_ci 2518c2ecf20Sopenharmony_ci ret = of_property_read_u32(node, "ti,intc-size", &config.num_irqs); 2528c2ecf20Sopenharmony_ci if (ret) { 2538c2ecf20Sopenharmony_ci pr_err("%s: unable to read the 'ti,intc-size' property\n", 2548c2ecf20Sopenharmony_ci __func__); 2558c2ecf20Sopenharmony_ci return ret; 2568c2ecf20Sopenharmony_ci } 2578c2ecf20Sopenharmony_ci 2588c2ecf20Sopenharmony_ci return davinci_cp_intc_do_init(&config, node); 2598c2ecf20Sopenharmony_ci} 2608c2ecf20Sopenharmony_ciIRQCHIP_DECLARE(cp_intc, "ti,cp-intc", davinci_cp_intc_of_init); 261