Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1761319AbbKUAT2 (ORCPT ); Fri, 20 Nov 2015 19:19:28 -0500 Received: from exsmtp03.microchip.com ([198.175.253.49]:24638 "EHLO email.microchip.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1761217AbbKUAT0 (ORCPT ); Fri, 20 Nov 2015 19:19:26 -0500 From: Joshua Henderson To: CC: , Cristian Birsan , Joshua Henderson , Thomas Gleixner , "Jason Cooper" , Marc Zyngier Subject: [PATCH 02/14] irqchip: irq-pic32-evic: Add support for PIC32 interrupt controller Date: Fri, 20 Nov 2015 17:17:14 -0700 Message-ID: <1448065205-15762-3-git-send-email-joshua.henderson@microchip.com> X-Mailer: git-send-email 1.7.9.5 In-Reply-To: <1448065205-15762-1-git-send-email-joshua.henderson@microchip.com> References: <1448065205-15762-1-git-send-email-joshua.henderson@microchip.com> MIME-Version: 1.0 Content-Type: text/plain Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 10066 Lines: 377 From: Cristian Birsan This adds support for the EVIC present on a PIC32MZDA. The following features are supported: - DT properties for EVIC and for devices that use interrupt lines - persistent and non-persistent interrupt handling - Priority, sub-priority and polariy settings for each interrupt line - irqdomain support Signed-off-by: Cristian Birsan Signed-off-by: Joshua Henderson --- drivers/irqchip/Makefile | 1 + drivers/irqchip/irq-pic32-evic.c | 309 ++++++++++++++++++++++++++++++++++++ include/linux/irqchip/pic32-evic.h | 19 +++ 3 files changed, 329 insertions(+) create mode 100644 drivers/irqchip/irq-pic32-evic.c create mode 100644 include/linux/irqchip/pic32-evic.h diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index 177f78f..e3608fc 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile @@ -55,3 +55,4 @@ obj-$(CONFIG_RENESAS_H8S_INTC) += irq-renesas-h8s.o obj-$(CONFIG_ARCH_SA1100) += irq-sa11x0.o obj-$(CONFIG_INGENIC_IRQ) += irq-ingenic.o obj-$(CONFIG_IMX_GPCV2) += irq-imx-gpcv2.o +obj-$(CONFIG_MACH_PIC32) += irq-pic32-evic.o diff --git a/drivers/irqchip/irq-pic32-evic.c b/drivers/irqchip/irq-pic32-evic.c new file mode 100644 index 0000000..7b87b43 --- /dev/null +++ b/drivers/irqchip/irq-pic32-evic.c @@ -0,0 +1,309 @@ +/* + * Cristian Birsan + * Copyright (C) 2015 Microchip Technology Inc. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +struct irq_domain *evic_irq_domain; +static struct evic __iomem *evic_base; + +static unsigned int *evic_irq_prio; + +struct pic_reg { + u32 val; /* value register*/ + u32 clr; /* clear register */ + u32 set; /* set register */ + u32 inv; /* inv register */ +} __packed; + +struct evic { + struct pic_reg intcon; + struct pic_reg priss; + struct pic_reg intstat; + struct pic_reg iptmr; + struct pic_reg ifs[6]; + u32 reserved1[8]; + struct pic_reg iec[6]; + u32 reserved2[8]; + struct pic_reg ipc[48]; + u32 reserved3[64]; + u32 off[191]; +} __packed; + +static int get_ext_irq_index(irq_hw_number_t hw); +static void evic_set_ext_irq_polarity(int ext_irq, u32 type); + +#define BIT_REG_MASK(bit, reg, mask) \ + do { \ + reg = bit/32; \ + mask = 1 << (bit % 32); \ + } while (0) + +asmlinkage void __weak plat_irq_dispatch(void) +{ + unsigned int irq, hwirq; + u32 reg, mask; + + hwirq = readl(&evic_base->intstat.val) & 0xFF; + + /* Check if the interrupt was really triggered by hardware*/ + BIT_REG_MASK(hwirq, reg, mask); + if (likely(readl(&evic_base->ifs[reg].val) & + readl(&evic_base->iec[reg].val) & mask)) { + irq = irq_linear_revmap(evic_irq_domain, hwirq); + do_IRQ(irq); + } else + spurious_interrupt(); +} + +/* mask off an interrupt */ +static inline void mask_pic32_irq(struct irq_data *irqd) +{ + u32 reg, mask; + unsigned int hwirq = irqd_to_hwirq(irqd); + + BIT_REG_MASK(hwirq, reg, mask); + writel(mask, &evic_base->iec[reg].clr); +} + +/* unmask an interrupt */ +static inline void unmask_pic32_irq(struct irq_data *irqd) +{ + u32 reg, mask; + unsigned int hwirq = irqd_to_hwirq(irqd); + + BIT_REG_MASK(hwirq, reg, mask); + writel(mask, &evic_base->iec[reg].set); +} + +/* acknowledge an interrupt */ +static void ack_pic32_irq(struct irq_data *irqd) +{ + u32 reg, mask; + unsigned int hwirq = irqd_to_hwirq(irqd); + + BIT_REG_MASK(hwirq, reg, mask); + writel(mask, &evic_base->ifs[reg].clr); +} + +/* mask off and acknowledge an interrupt */ +static inline void mask_ack_pic32_irq(struct irq_data *irqd) +{ + u32 reg, mask; + unsigned int hwirq = irqd_to_hwirq(irqd); + + BIT_REG_MASK(hwirq, reg, mask); + writel(mask, &evic_base->iec[reg].clr); + writel(mask, &evic_base->ifs[reg].clr); +} + +static int set_type_pic32_irq(struct irq_data *data, unsigned int flow_type) +{ + int index; + + switch (flow_type) { + + case IRQ_TYPE_EDGE_RISING: + case IRQ_TYPE_EDGE_FALLING: + irq_set_handler_locked(data, handle_edge_irq); + break; + + case IRQ_TYPE_LEVEL_HIGH: + case IRQ_TYPE_LEVEL_LOW: + irq_set_handler_locked(data, handle_fasteoi_irq); + break; + + default: + pr_err("Invalid interrupt type !\n"); + return -EINVAL; + } + + /* set polarity for external interrupts only */ + index = get_ext_irq_index(data->hwirq); + if (index >= 0) + evic_set_ext_irq_polarity(index, flow_type); + + return IRQ_SET_MASK_OK; +} + +static void pic32_bind_evic_interrupt(int irq, int set) +{ + writel(set, &evic_base->off[irq]); +} + +int pic32_get_c0_compare_int(void) +{ + int virq; + + virq = irq_create_mapping(evic_irq_domain, CORE_TIMER_INTERRUPT); + irq_set_irq_type(virq, IRQ_TYPE_EDGE_RISING); + return virq; +} + +static struct irq_chip pic32_irq_chip = { + .name = "PIC32-EVIC", + .irq_ack = ack_pic32_irq, + .irq_mask = mask_pic32_irq, + .irq_mask_ack = mask_ack_pic32_irq, + .irq_unmask = unmask_pic32_irq, + .irq_eoi = ack_pic32_irq, + .irq_set_type = set_type_pic32_irq, + .irq_enable = unmask_pic32_irq, + .irq_disable = mask_pic32_irq, +}; + +static void evic_set_irq_priority(int irq, int priority) +{ + u32 reg, shift; + + reg = irq / 4; + shift = (irq % 4) * 8; + + /* set priority */ + writel(INT_MASK << shift, &evic_base->ipc[reg].clr); + writel(priority << shift, &evic_base->ipc[reg].set); +} + +static void evic_set_ext_irq_polarity(int ext_irq, u32 type) +{ + if (WARN_ON(ext_irq >= NR_EXT_IRQS)) + return; + switch (type) { + case IRQ_TYPE_EDGE_RISING: + writel(1 << ext_irq, &evic_base->intcon.set); + break; + case IRQ_TYPE_EDGE_FALLING: + writel(1 << ext_irq, &evic_base->intcon.clr); + break; + default: + pr_err("Invalid external interrupt polarity !\n"); + } +} + +static int get_ext_irq_index(irq_hw_number_t hw) +{ + switch (hw) { + case EXTERNAL_INTERRUPT_0: + return 0; + case EXTERNAL_INTERRUPT_1: + return 1; + case EXTERNAL_INTERRUPT_2: + return 2; + case EXTERNAL_INTERRUPT_3: + return 3; + case EXTERNAL_INTERRUPT_4: + return 4; + default: + return -1; + } +} + +static int evic_intc_map(struct irq_domain *irqd, unsigned int virq, + irq_hw_number_t hw) +{ + u32 reg, mask; + + irq_set_chip(virq, &pic32_irq_chip); + + BIT_REG_MASK(hw, reg, mask); + + /* disable */ + writel(mask, &evic_base->iec[reg].clr); + + /* clear flag */ + writel(mask, &evic_base->ifs[reg].clr); + + evic_set_irq_priority(hw, evic_irq_prio[hw]); + + return 0; +} + +static int evic_irq_domain_xlate(struct irq_domain *d, + struct device_node *ctrlr, + const u32 *intspec, + unsigned int intsize, + irq_hw_number_t *out_hwirq, + unsigned int *out_type) +{ + /* Check for number of params */ + if (WARN_ON(intsize < 3)) + return -EINVAL; + if (WARN_ON(intspec[0] >= NR_IRQS)) + return -EINVAL; + /* Check for correct priority settings */ + if (WARN_ON((intspec[1] < MICROCHIP_EVIC_MIN_PRIORITY) + || (intspec[1] > MICROCHIP_EVIC_MAX_PRIORITY))) + return -EINVAL; + + *out_hwirq = intspec[0]; + + evic_irq_prio[*out_hwirq] = intspec[1]; + + *out_type = intspec[2]; + + return 0; +} + +static const struct irq_domain_ops evic_intc_irq_domain_ops = { + .map = evic_intc_map, + .xlate = evic_irq_domain_xlate, +}; + +#ifdef CONFIG_OF + +static int __init +microchip_evic_of_init(struct device_node *node, struct device_node *parent) +{ + struct resource res; + + if (WARN_ON(!node)) + return -ENODEV; + + evic_irq_prio = kcalloc(NR_IRQS, sizeof(*evic_irq_prio), + GFP_KERNEL); + if (!evic_irq_prio) + return -ENOMEM; + + evic_irq_prio[CORE_TIMER_INTERRUPT] = DEFAULT_INT_PRI; /* Default IRQ*/ + + if (of_address_to_resource(node, 0, &res)) + panic("Failed to get evic memory range"); + + if (request_mem_region(res.start, resource_size(&res), + res.name) == NULL) + panic("Failed to request evic memory"); + + evic_base = ioremap_nocache(res.start, resource_size(&res)); + if (!evic_base) + panic("Failed to remap evic memory"); + + board_bind_eic_interrupt = &pic32_bind_evic_interrupt; + + evic_irq_domain = irq_domain_add_linear(node, NR_IRQS, + &evic_intc_irq_domain_ops, NULL); + if (!evic_irq_domain) + panic("Failed to add linear irqdomain for EVIC"); + + irq_set_default_host(evic_irq_domain); + + return 0; +} + +IRQCHIP_DECLARE(microchip_evic, "microchip,evic-v2", microchip_evic_of_init); +#endif diff --git a/include/linux/irqchip/pic32-evic.h b/include/linux/irqchip/pic32-evic.h new file mode 100644 index 0000000..c514bae --- /dev/null +++ b/include/linux/irqchip/pic32-evic.h @@ -0,0 +1,19 @@ +/* + * Joshua Henderson, + * Copyright (C) 2015 Microchip Technology Inc. All rights reserved. + * + * This program is free software; you can distribute it and/or modify it + * under the terms of the GNU General Public License (Version 2) as + * published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ +#ifndef __LINUX_IRQCHIP_PIC32_EVIC_H +#define __LINUX_IRQCHIP_PIC32_EVIC_H + +extern int pic32_get_c0_compare_int(void); + +#endif /* __LINUX_IRQCHIP_PIC32_EVIC_H */ -- 1.7.9.5 -- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/