18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Support for C64x+ Megamodule Interrupt Controller 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2010, 2011 Texas Instruments Incorporated 68c2ecf20Sopenharmony_ci * Contributed by: Mark Salter <msalter@redhat.com> 78c2ecf20Sopenharmony_ci */ 88c2ecf20Sopenharmony_ci#include <linux/module.h> 98c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 108c2ecf20Sopenharmony_ci#include <linux/io.h> 118c2ecf20Sopenharmony_ci#include <linux/of.h> 128c2ecf20Sopenharmony_ci#include <linux/of_irq.h> 138c2ecf20Sopenharmony_ci#include <linux/of_address.h> 148c2ecf20Sopenharmony_ci#include <linux/slab.h> 158c2ecf20Sopenharmony_ci#include <asm/soc.h> 168c2ecf20Sopenharmony_ci#include <asm/megamod-pic.h> 178c2ecf20Sopenharmony_ci 188c2ecf20Sopenharmony_ci#define NR_COMBINERS 4 198c2ecf20Sopenharmony_ci#define NR_MUX_OUTPUTS 12 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_ci#define IRQ_UNMAPPED 0xffff 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_ci/* 248c2ecf20Sopenharmony_ci * Megamodule Interrupt Controller register layout 258c2ecf20Sopenharmony_ci */ 268c2ecf20Sopenharmony_cistruct megamod_regs { 278c2ecf20Sopenharmony_ci u32 evtflag[8]; 288c2ecf20Sopenharmony_ci u32 evtset[8]; 298c2ecf20Sopenharmony_ci u32 evtclr[8]; 308c2ecf20Sopenharmony_ci u32 reserved0[8]; 318c2ecf20Sopenharmony_ci u32 evtmask[8]; 328c2ecf20Sopenharmony_ci u32 mevtflag[8]; 338c2ecf20Sopenharmony_ci u32 expmask[8]; 348c2ecf20Sopenharmony_ci u32 mexpflag[8]; 358c2ecf20Sopenharmony_ci u32 intmux_unused; 368c2ecf20Sopenharmony_ci u32 intmux[7]; 378c2ecf20Sopenharmony_ci u32 reserved1[8]; 388c2ecf20Sopenharmony_ci u32 aegmux[2]; 398c2ecf20Sopenharmony_ci u32 reserved2[14]; 408c2ecf20Sopenharmony_ci u32 intxstat; 418c2ecf20Sopenharmony_ci u32 intxclr; 428c2ecf20Sopenharmony_ci u32 intdmask; 438c2ecf20Sopenharmony_ci u32 reserved3[13]; 448c2ecf20Sopenharmony_ci u32 evtasrt; 458c2ecf20Sopenharmony_ci}; 468c2ecf20Sopenharmony_ci 478c2ecf20Sopenharmony_cistruct megamod_pic { 488c2ecf20Sopenharmony_ci struct irq_domain *irqhost; 498c2ecf20Sopenharmony_ci struct megamod_regs __iomem *regs; 508c2ecf20Sopenharmony_ci raw_spinlock_t lock; 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_ci /* hw mux mapping */ 538c2ecf20Sopenharmony_ci unsigned int output_to_irq[NR_MUX_OUTPUTS]; 548c2ecf20Sopenharmony_ci}; 558c2ecf20Sopenharmony_ci 568c2ecf20Sopenharmony_cistatic struct megamod_pic *mm_pic; 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_cistruct megamod_cascade_data { 598c2ecf20Sopenharmony_ci struct megamod_pic *pic; 608c2ecf20Sopenharmony_ci int index; 618c2ecf20Sopenharmony_ci}; 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_cistatic struct megamod_cascade_data cascade_data[NR_COMBINERS]; 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_cistatic void mask_megamod(struct irq_data *data) 668c2ecf20Sopenharmony_ci{ 678c2ecf20Sopenharmony_ci struct megamod_pic *pic = irq_data_get_irq_chip_data(data); 688c2ecf20Sopenharmony_ci irq_hw_number_t src = irqd_to_hwirq(data); 698c2ecf20Sopenharmony_ci u32 __iomem *evtmask = &pic->regs->evtmask[src / 32]; 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_ci raw_spin_lock(&pic->lock); 728c2ecf20Sopenharmony_ci soc_writel(soc_readl(evtmask) | (1 << (src & 31)), evtmask); 738c2ecf20Sopenharmony_ci raw_spin_unlock(&pic->lock); 748c2ecf20Sopenharmony_ci} 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_cistatic void unmask_megamod(struct irq_data *data) 778c2ecf20Sopenharmony_ci{ 788c2ecf20Sopenharmony_ci struct megamod_pic *pic = irq_data_get_irq_chip_data(data); 798c2ecf20Sopenharmony_ci irq_hw_number_t src = irqd_to_hwirq(data); 808c2ecf20Sopenharmony_ci u32 __iomem *evtmask = &pic->regs->evtmask[src / 32]; 818c2ecf20Sopenharmony_ci 828c2ecf20Sopenharmony_ci raw_spin_lock(&pic->lock); 838c2ecf20Sopenharmony_ci soc_writel(soc_readl(evtmask) & ~(1 << (src & 31)), evtmask); 848c2ecf20Sopenharmony_ci raw_spin_unlock(&pic->lock); 858c2ecf20Sopenharmony_ci} 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_cistatic struct irq_chip megamod_chip = { 888c2ecf20Sopenharmony_ci .name = "megamod", 898c2ecf20Sopenharmony_ci .irq_mask = mask_megamod, 908c2ecf20Sopenharmony_ci .irq_unmask = unmask_megamod, 918c2ecf20Sopenharmony_ci}; 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_cistatic void megamod_irq_cascade(struct irq_desc *desc) 948c2ecf20Sopenharmony_ci{ 958c2ecf20Sopenharmony_ci struct megamod_cascade_data *cascade; 968c2ecf20Sopenharmony_ci struct megamod_pic *pic; 978c2ecf20Sopenharmony_ci unsigned int irq; 988c2ecf20Sopenharmony_ci u32 events; 998c2ecf20Sopenharmony_ci int n, idx; 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_ci cascade = irq_desc_get_handler_data(desc); 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_ci pic = cascade->pic; 1048c2ecf20Sopenharmony_ci idx = cascade->index; 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_ci while ((events = soc_readl(&pic->regs->mevtflag[idx])) != 0) { 1078c2ecf20Sopenharmony_ci n = __ffs(events); 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_ci irq = irq_linear_revmap(pic->irqhost, idx * 32 + n); 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_ci soc_writel(1 << n, &pic->regs->evtclr[idx]); 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_ci generic_handle_irq(irq); 1148c2ecf20Sopenharmony_ci } 1158c2ecf20Sopenharmony_ci} 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_cistatic int megamod_map(struct irq_domain *h, unsigned int virq, 1188c2ecf20Sopenharmony_ci irq_hw_number_t hw) 1198c2ecf20Sopenharmony_ci{ 1208c2ecf20Sopenharmony_ci struct megamod_pic *pic = h->host_data; 1218c2ecf20Sopenharmony_ci int i; 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_ci /* We shouldn't see a hwirq which is muxed to core controller */ 1248c2ecf20Sopenharmony_ci for (i = 0; i < NR_MUX_OUTPUTS; i++) 1258c2ecf20Sopenharmony_ci if (pic->output_to_irq[i] == hw) 1268c2ecf20Sopenharmony_ci return -1; 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_ci irq_set_chip_data(virq, pic); 1298c2ecf20Sopenharmony_ci irq_set_chip_and_handler(virq, &megamod_chip, handle_level_irq); 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_ci /* Set default irq type */ 1328c2ecf20Sopenharmony_ci irq_set_irq_type(virq, IRQ_TYPE_NONE); 1338c2ecf20Sopenharmony_ci 1348c2ecf20Sopenharmony_ci return 0; 1358c2ecf20Sopenharmony_ci} 1368c2ecf20Sopenharmony_ci 1378c2ecf20Sopenharmony_cistatic const struct irq_domain_ops megamod_domain_ops = { 1388c2ecf20Sopenharmony_ci .map = megamod_map, 1398c2ecf20Sopenharmony_ci .xlate = irq_domain_xlate_onecell, 1408c2ecf20Sopenharmony_ci}; 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_cistatic void __init set_megamod_mux(struct megamod_pic *pic, int src, int output) 1438c2ecf20Sopenharmony_ci{ 1448c2ecf20Sopenharmony_ci int index, offset; 1458c2ecf20Sopenharmony_ci u32 val; 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_ci if (src < 0 || src >= (NR_COMBINERS * 32)) { 1488c2ecf20Sopenharmony_ci pic->output_to_irq[output] = IRQ_UNMAPPED; 1498c2ecf20Sopenharmony_ci return; 1508c2ecf20Sopenharmony_ci } 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_ci /* four mappings per mux register */ 1538c2ecf20Sopenharmony_ci index = output / 4; 1548c2ecf20Sopenharmony_ci offset = (output & 3) * 8; 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_ci val = soc_readl(&pic->regs->intmux[index]); 1578c2ecf20Sopenharmony_ci val &= ~(0xff << offset); 1588c2ecf20Sopenharmony_ci val |= src << offset; 1598c2ecf20Sopenharmony_ci soc_writel(val, &pic->regs->intmux[index]); 1608c2ecf20Sopenharmony_ci} 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_ci/* 1638c2ecf20Sopenharmony_ci * Parse the MUX mapping, if one exists. 1648c2ecf20Sopenharmony_ci * 1658c2ecf20Sopenharmony_ci * The MUX map is an array of up to 12 cells; one for each usable core priority 1668c2ecf20Sopenharmony_ci * interrupt. The value of a given cell is the megamodule interrupt source 1678c2ecf20Sopenharmony_ci * which is to me MUXed to the output corresponding to the cell position 1688c2ecf20Sopenharmony_ci * withing the array. The first cell in the array corresponds to priority 1698c2ecf20Sopenharmony_ci * 4 and the last (12th) cell corresponds to priority 15. The allowed 1708c2ecf20Sopenharmony_ci * values are 4 - ((NR_COMBINERS * 32) - 1). Note that the combined interrupt 1718c2ecf20Sopenharmony_ci * sources (0 - 3) are not allowed to be mapped through this property. They 1728c2ecf20Sopenharmony_ci * are handled through the "interrupts" property. This allows us to use a 1738c2ecf20Sopenharmony_ci * value of zero as a "do not map" placeholder. 1748c2ecf20Sopenharmony_ci */ 1758c2ecf20Sopenharmony_cistatic void __init parse_priority_map(struct megamod_pic *pic, 1768c2ecf20Sopenharmony_ci int *mapping, int size) 1778c2ecf20Sopenharmony_ci{ 1788c2ecf20Sopenharmony_ci struct device_node *np = irq_domain_get_of_node(pic->irqhost); 1798c2ecf20Sopenharmony_ci const __be32 *map; 1808c2ecf20Sopenharmony_ci int i, maplen; 1818c2ecf20Sopenharmony_ci u32 val; 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_ci map = of_get_property(np, "ti,c64x+megamod-pic-mux", &maplen); 1848c2ecf20Sopenharmony_ci if (map) { 1858c2ecf20Sopenharmony_ci maplen /= 4; 1868c2ecf20Sopenharmony_ci if (maplen > size) 1878c2ecf20Sopenharmony_ci maplen = size; 1888c2ecf20Sopenharmony_ci 1898c2ecf20Sopenharmony_ci for (i = 0; i < maplen; i++) { 1908c2ecf20Sopenharmony_ci val = be32_to_cpup(map); 1918c2ecf20Sopenharmony_ci if (val && val >= 4) 1928c2ecf20Sopenharmony_ci mapping[i] = val; 1938c2ecf20Sopenharmony_ci ++map; 1948c2ecf20Sopenharmony_ci } 1958c2ecf20Sopenharmony_ci } 1968c2ecf20Sopenharmony_ci} 1978c2ecf20Sopenharmony_ci 1988c2ecf20Sopenharmony_cistatic struct megamod_pic * __init init_megamod_pic(struct device_node *np) 1998c2ecf20Sopenharmony_ci{ 2008c2ecf20Sopenharmony_ci struct megamod_pic *pic; 2018c2ecf20Sopenharmony_ci int i, irq; 2028c2ecf20Sopenharmony_ci int mapping[NR_MUX_OUTPUTS]; 2038c2ecf20Sopenharmony_ci 2048c2ecf20Sopenharmony_ci pr_info("Initializing C64x+ Megamodule PIC\n"); 2058c2ecf20Sopenharmony_ci 2068c2ecf20Sopenharmony_ci pic = kzalloc(sizeof(struct megamod_pic), GFP_KERNEL); 2078c2ecf20Sopenharmony_ci if (!pic) { 2088c2ecf20Sopenharmony_ci pr_err("%pOF: Could not alloc PIC structure.\n", np); 2098c2ecf20Sopenharmony_ci return NULL; 2108c2ecf20Sopenharmony_ci } 2118c2ecf20Sopenharmony_ci 2128c2ecf20Sopenharmony_ci pic->irqhost = irq_domain_add_linear(np, NR_COMBINERS * 32, 2138c2ecf20Sopenharmony_ci &megamod_domain_ops, pic); 2148c2ecf20Sopenharmony_ci if (!pic->irqhost) { 2158c2ecf20Sopenharmony_ci pr_err("%pOF: Could not alloc host.\n", np); 2168c2ecf20Sopenharmony_ci goto error_free; 2178c2ecf20Sopenharmony_ci } 2188c2ecf20Sopenharmony_ci 2198c2ecf20Sopenharmony_ci pic->irqhost->host_data = pic; 2208c2ecf20Sopenharmony_ci 2218c2ecf20Sopenharmony_ci raw_spin_lock_init(&pic->lock); 2228c2ecf20Sopenharmony_ci 2238c2ecf20Sopenharmony_ci pic->regs = of_iomap(np, 0); 2248c2ecf20Sopenharmony_ci if (!pic->regs) { 2258c2ecf20Sopenharmony_ci pr_err("%pOF: Could not map registers.\n", np); 2268c2ecf20Sopenharmony_ci goto error_free; 2278c2ecf20Sopenharmony_ci } 2288c2ecf20Sopenharmony_ci 2298c2ecf20Sopenharmony_ci /* Initialize MUX map */ 2308c2ecf20Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(mapping); i++) 2318c2ecf20Sopenharmony_ci mapping[i] = IRQ_UNMAPPED; 2328c2ecf20Sopenharmony_ci 2338c2ecf20Sopenharmony_ci parse_priority_map(pic, mapping, ARRAY_SIZE(mapping)); 2348c2ecf20Sopenharmony_ci 2358c2ecf20Sopenharmony_ci /* 2368c2ecf20Sopenharmony_ci * We can have up to 12 interrupts cascading to the core controller. 2378c2ecf20Sopenharmony_ci * These cascades can be from the combined interrupt sources or for 2388c2ecf20Sopenharmony_ci * individual interrupt sources. The "interrupts" property only 2398c2ecf20Sopenharmony_ci * deals with the cascaded combined interrupts. The individual 2408c2ecf20Sopenharmony_ci * interrupts muxed to the core controller use the core controller 2418c2ecf20Sopenharmony_ci * as their interrupt parent. 2428c2ecf20Sopenharmony_ci */ 2438c2ecf20Sopenharmony_ci for (i = 0; i < NR_COMBINERS; i++) { 2448c2ecf20Sopenharmony_ci struct irq_data *irq_data; 2458c2ecf20Sopenharmony_ci irq_hw_number_t hwirq; 2468c2ecf20Sopenharmony_ci 2478c2ecf20Sopenharmony_ci irq = irq_of_parse_and_map(np, i); 2488c2ecf20Sopenharmony_ci if (irq == NO_IRQ) 2498c2ecf20Sopenharmony_ci continue; 2508c2ecf20Sopenharmony_ci 2518c2ecf20Sopenharmony_ci irq_data = irq_get_irq_data(irq); 2528c2ecf20Sopenharmony_ci if (!irq_data) { 2538c2ecf20Sopenharmony_ci pr_err("%pOF: combiner-%d no irq_data for virq %d!\n", 2548c2ecf20Sopenharmony_ci np, i, irq); 2558c2ecf20Sopenharmony_ci continue; 2568c2ecf20Sopenharmony_ci } 2578c2ecf20Sopenharmony_ci 2588c2ecf20Sopenharmony_ci hwirq = irq_data->hwirq; 2598c2ecf20Sopenharmony_ci 2608c2ecf20Sopenharmony_ci /* 2618c2ecf20Sopenharmony_ci * Check that device tree provided something in the range 2628c2ecf20Sopenharmony_ci * of the core priority interrupts (4 - 15). 2638c2ecf20Sopenharmony_ci */ 2648c2ecf20Sopenharmony_ci if (hwirq < 4 || hwirq >= NR_PRIORITY_IRQS) { 2658c2ecf20Sopenharmony_ci pr_err("%pOF: combiner-%d core irq %ld out of range!\n", 2668c2ecf20Sopenharmony_ci np, i, hwirq); 2678c2ecf20Sopenharmony_ci continue; 2688c2ecf20Sopenharmony_ci } 2698c2ecf20Sopenharmony_ci 2708c2ecf20Sopenharmony_ci /* record the mapping */ 2718c2ecf20Sopenharmony_ci mapping[hwirq - 4] = i; 2728c2ecf20Sopenharmony_ci 2738c2ecf20Sopenharmony_ci pr_debug("%pOF: combiner-%d cascading to hwirq %ld\n", 2748c2ecf20Sopenharmony_ci np, i, hwirq); 2758c2ecf20Sopenharmony_ci 2768c2ecf20Sopenharmony_ci cascade_data[i].pic = pic; 2778c2ecf20Sopenharmony_ci cascade_data[i].index = i; 2788c2ecf20Sopenharmony_ci 2798c2ecf20Sopenharmony_ci /* mask and clear all events in combiner */ 2808c2ecf20Sopenharmony_ci soc_writel(~0, &pic->regs->evtmask[i]); 2818c2ecf20Sopenharmony_ci soc_writel(~0, &pic->regs->evtclr[i]); 2828c2ecf20Sopenharmony_ci 2838c2ecf20Sopenharmony_ci irq_set_chained_handler_and_data(irq, megamod_irq_cascade, 2848c2ecf20Sopenharmony_ci &cascade_data[i]); 2858c2ecf20Sopenharmony_ci } 2868c2ecf20Sopenharmony_ci 2878c2ecf20Sopenharmony_ci /* Finally, set up the MUX registers */ 2888c2ecf20Sopenharmony_ci for (i = 0; i < NR_MUX_OUTPUTS; i++) { 2898c2ecf20Sopenharmony_ci if (mapping[i] != IRQ_UNMAPPED) { 2908c2ecf20Sopenharmony_ci pr_debug("%pOF: setting mux %d to priority %d\n", 2918c2ecf20Sopenharmony_ci np, mapping[i], i + 4); 2928c2ecf20Sopenharmony_ci set_megamod_mux(pic, mapping[i], i); 2938c2ecf20Sopenharmony_ci } 2948c2ecf20Sopenharmony_ci } 2958c2ecf20Sopenharmony_ci 2968c2ecf20Sopenharmony_ci return pic; 2978c2ecf20Sopenharmony_ci 2988c2ecf20Sopenharmony_cierror_free: 2998c2ecf20Sopenharmony_ci kfree(pic); 3008c2ecf20Sopenharmony_ci 3018c2ecf20Sopenharmony_ci return NULL; 3028c2ecf20Sopenharmony_ci} 3038c2ecf20Sopenharmony_ci 3048c2ecf20Sopenharmony_ci/* 3058c2ecf20Sopenharmony_ci * Return next active event after ACK'ing it. 3068c2ecf20Sopenharmony_ci * Return -1 if no events active. 3078c2ecf20Sopenharmony_ci */ 3088c2ecf20Sopenharmony_cistatic int get_exception(void) 3098c2ecf20Sopenharmony_ci{ 3108c2ecf20Sopenharmony_ci int i, bit; 3118c2ecf20Sopenharmony_ci u32 mask; 3128c2ecf20Sopenharmony_ci 3138c2ecf20Sopenharmony_ci for (i = 0; i < NR_COMBINERS; i++) { 3148c2ecf20Sopenharmony_ci mask = soc_readl(&mm_pic->regs->mexpflag[i]); 3158c2ecf20Sopenharmony_ci if (mask) { 3168c2ecf20Sopenharmony_ci bit = __ffs(mask); 3178c2ecf20Sopenharmony_ci soc_writel(1 << bit, &mm_pic->regs->evtclr[i]); 3188c2ecf20Sopenharmony_ci return (i * 32) + bit; 3198c2ecf20Sopenharmony_ci } 3208c2ecf20Sopenharmony_ci } 3218c2ecf20Sopenharmony_ci return -1; 3228c2ecf20Sopenharmony_ci} 3238c2ecf20Sopenharmony_ci 3248c2ecf20Sopenharmony_cistatic void assert_event(unsigned int val) 3258c2ecf20Sopenharmony_ci{ 3268c2ecf20Sopenharmony_ci soc_writel(val, &mm_pic->regs->evtasrt); 3278c2ecf20Sopenharmony_ci} 3288c2ecf20Sopenharmony_ci 3298c2ecf20Sopenharmony_civoid __init megamod_pic_init(void) 3308c2ecf20Sopenharmony_ci{ 3318c2ecf20Sopenharmony_ci struct device_node *np; 3328c2ecf20Sopenharmony_ci 3338c2ecf20Sopenharmony_ci np = of_find_compatible_node(NULL, NULL, "ti,c64x+megamod-pic"); 3348c2ecf20Sopenharmony_ci if (!np) 3358c2ecf20Sopenharmony_ci return; 3368c2ecf20Sopenharmony_ci 3378c2ecf20Sopenharmony_ci mm_pic = init_megamod_pic(np); 3388c2ecf20Sopenharmony_ci of_node_put(np); 3398c2ecf20Sopenharmony_ci 3408c2ecf20Sopenharmony_ci soc_ops.get_exception = get_exception; 3418c2ecf20Sopenharmony_ci soc_ops.assert_event = assert_event; 3428c2ecf20Sopenharmony_ci 3438c2ecf20Sopenharmony_ci return; 3448c2ecf20Sopenharmony_ci} 345