The SOC interrupt controller driver for the Abilis Systems TB10x series of SOCs based on ARC700 CPUs. This driver is required to boot the arch/arc/plat-tb10x platform already present in linux-next. Please consider this patch for inclusion in 3.10. This is the rebase of a previous version on linux-next. Signed-off-by: Christian Ruppert <christian.ruppert@xxxxxxxxxx> Signed-off-by: Pierrick Hascoet <pierrick.hascoet@xxxxxxxxxx> --- .../interrupt-controller/abilis,tb10x_ictl.txt | 38 +++ drivers/irqchip/Makefile | 1 + drivers/irqchip/irq-tb10x.c | 254 ++++++++++++++++++++ 3 files changed, 293 insertions(+), 0 deletions(-) create mode 100644 Documentation/devicetree/bindings/interrupt-controller/abilis,tb10x_ictl.txt create mode 100644 drivers/irqchip/irq-tb10x.c diff --git a/Documentation/devicetree/bindings/interrupt-controller/abilis,tb10x_ictl.txt b/Documentation/devicetree/bindings/interrupt-controller/abilis,tb10x_ictl.txt new file mode 100644 index 0000000..bc292cd --- /dev/null +++ b/Documentation/devicetree/bindings/interrupt-controller/abilis,tb10x_ictl.txt @@ -0,0 +1,38 @@ +TB10x Top Level Interrupt Controller +==================================== + +The Abilis TB10x SOC contains a custom interrupt controller. It performs +one-to-one mapping of external interrupt sources to CPU interrupts and +provides support for reconfigurable trigger modes. + +Required properties +------------------- + +- compatible: Should be "abilis,tb10x_ictl" +- reg: specifies physical base address and size of register range. +- interrupt-congroller: Identifies the node as an interrupt controller. +- #interrupt cells: Specifies the number of cells used to encode an interrupt + source connected to this controller. The value shall be 2. +- interrupt-parent: Specifies the parent interrupt controller. +- interrupts: Specifies the list of interrupt lines which are handled by + the interrupt controller in the parent controller's notation. Interrupts + are mapped one-to-one to parent interrupts. + +Example +------- + +intc: interrupt-controller { /* Parent interrupt controller */ + interrupt-controller; + #interrupt-cells = <1>; /* For example below */ + /* ... */ +}; + +tb10x_ictl: pic@2000 { /* TB10x interrupt controller */ + compatible = "abilis,tb10x_ictl"; + reg = <0x2000 0x20>; + interrupt-controller; + #interrupt-cells = <2>; + interrupt-parent = <&intc>; + interrupts = <5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 + 20 21 22 23 24 25 26 27 28 29 30 31>; +}; diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index 6a02eac..ad81a2c 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_METAG) += irq-metag-ext.o obj-$(CONFIG_METAG_PERFCOUNTER_IRQS) += irq-metag.o obj-$(CONFIG_ARCH_SUNXI) += irq-sun4i.o obj-$(CONFIG_ARCH_SPEAR3XX) += spear-shirq.o +obj-$(CONFIG_ARC_PLAT_TB10X) += irq-tb10x.o obj-$(CONFIG_ARM_GIC) += irq-gic.o obj-$(CONFIG_ARM_VIC) += irq-vic.o obj-$(CONFIG_SIRF_IRQ) += irq-sirfsoc.o diff --git a/drivers/irqchip/irq-tb10x.c b/drivers/irqchip/irq-tb10x.c new file mode 100644 index 0000000..542de1e --- /dev/null +++ b/drivers/irqchip/irq-tb10x.c @@ -0,0 +1,254 @@ +/* + * Abilis Systems interrupt controller driver + * + * Copyright (C) Abilis Systems 2012 + * + * Author: Christian Ruppert <christian.ruppert@xxxxxxxxxx> + * + * This program is free software; you can redistribute 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 that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/interrupt.h> +#include <linux/irqdomain.h> +#include <linux/of_irq.h> +#include <linux/of_address.h> +#include <linux/of_platform.h> +#include <linux/io.h> +#include <linux/slab.h> +#include "../../drivers/irqchip/irqchip.h" + +#define AB_IRQCTL_INT_ENABLE (0x00) +#define AB_IRQCTL_INT_STATUS (0x04) +#define AB_IRQCTL_SRC_MODE (0x08) +#define AB_IRQCTL_SRC_POLARITY (0x0C) +#define AB_IRQCTL_INT_MODE (0x10) +#define AB_IRQCTL_INT_POLARITY (0x14) +#define AB_IRQCTL_INT_FORCE (0x18) + +struct tb10x_ictl { + void *base; + struct irq_domain *domain; +}; + +static inline void ab_irqctl_writereg(struct tb10x_ictl *ictl, u32 reg, + u32 val) +{ + iowrite32(val, (u32 *)(ictl->base + reg)); +} + +static inline u32 ab_irqctl_readreg(struct tb10x_ictl *ictl, u32 reg) +{ + return ioread32((u32 *)(ictl->base + reg)); +} + +static int tb10x_irq_set_type(struct irq_data *data, unsigned int flow_type) +{ + struct tb10x_ictl *ictl = irq_data_get_irq_chip_data(data); + unsigned int irq = data->irq; + unsigned int hwirq = irqd_to_hwirq(data); + uint32_t im, mod, pol; + + im = 1UL << hwirq; + + mod = ab_irqctl_readreg(ictl, AB_IRQCTL_SRC_MODE) | im; + pol = ab_irqctl_readreg(ictl, AB_IRQCTL_SRC_POLARITY) | im; + + switch (flow_type & IRQF_TRIGGER_MASK) { + case IRQ_TYPE_EDGE_FALLING: + pol ^= im; + break; + case IRQ_TYPE_LEVEL_HIGH: + mod ^= im; + break; + case IRQ_TYPE_NONE: + flow_type = IRQ_TYPE_LEVEL_LOW; + case IRQ_TYPE_LEVEL_LOW: + mod ^= im; + pol ^= im; + break; + case IRQ_TYPE_EDGE_RISING: + break; + default: + pr_err("%s: Cannot assign multiple trigger modes to IRQ %d.\n", + __func__, irq); + return -EBADR; + } + + irqd_set_trigger_type(data, flow_type); + + ab_irqctl_writereg(ictl, AB_IRQCTL_SRC_MODE, mod); + ab_irqctl_writereg(ictl, AB_IRQCTL_SRC_POLARITY, pol); + ab_irqctl_writereg(ictl, AB_IRQCTL_INT_STATUS, im); + + if (flow_type & (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_LEVEL_HIGH)) + __irq_set_handler_locked(irq, handle_level_irq); + else + __irq_set_handler_locked(irq, handle_edge_irq); + + return IRQ_SET_MASK_OK; +} + +static void tb10x_irq_unmask(struct irq_data *data) +{ + struct tb10x_ictl *ictl = irq_data_get_irq_chip_data(data); + unsigned int hwirq = irqd_to_hwirq(data); + + ab_irqctl_writereg(ictl, AB_IRQCTL_INT_ENABLE, + ab_irqctl_readreg(ictl, AB_IRQCTL_INT_ENABLE) | (1 << hwirq)); +} + +static void tb10x_irq_mask(struct irq_data *data) +{ + struct tb10x_ictl *ictl = irq_data_get_irq_chip_data(data); + unsigned int hwirq = irqd_to_hwirq(data); + + ab_irqctl_writereg(ictl, AB_IRQCTL_INT_ENABLE, + ab_irqctl_readreg(ictl, AB_IRQCTL_INT_ENABLE) & ~(1 << hwirq)); +} + +static void tb10x_irq_ack(struct irq_data *data) +{ + struct tb10x_ictl *ictl = irq_data_get_irq_chip_data(data); + unsigned int hwirq = irqd_to_hwirq(data); + + ab_irqctl_writereg(ictl, AB_IRQCTL_INT_STATUS, + ab_irqctl_readreg(ictl, AB_IRQCTL_SRC_MODE) + & (1 << hwirq)); +} + +static struct irq_chip irq_tb10x_chip = { + .name = "TB10x-ICTL", + .irq_ack = tb10x_irq_ack, + .irq_mask = tb10x_irq_mask, + .irq_unmask = tb10x_irq_unmask, + .irq_set_type = tb10x_irq_set_type, +}; + +static int tb10x_irq_map(struct irq_domain *d, unsigned int irq, + irq_hw_number_t hw) +{ + irq_set_chip_data(irq, d->host_data); + irq_set_chip_and_handler(irq, &irq_tb10x_chip, handle_level_irq); + + return 0; +} + +static int tb10x_irq_xlate(struct irq_domain *d, struct device_node *node, + const u32 *intspec, unsigned int intsize, + unsigned long *out_hwirq, unsigned int *out_type) +{ + static const unsigned int tb10x_xlate_types[] = { + IRQ_TYPE_EDGE_RISING, + IRQ_TYPE_LEVEL_LOW, + IRQ_TYPE_LEVEL_HIGH, + IRQ_TYPE_EDGE_FALLING, + }; + + if (WARN_ON(intsize != 2)) + return -EINVAL; + + if (WARN_ON(intspec[1] >= ARRAY_SIZE(tb10x_xlate_types))) + return -EINVAL; + + *out_hwirq = intspec[0]; + *out_type = tb10x_xlate_types[intspec[1]]; + return 0; +} + +static struct irq_domain_ops irq_tb10x_domain_ops = { + .map = tb10x_irq_map, + .xlate = tb10x_irq_xlate, +}; + +static void tb10x_irq_cascade(unsigned int irq, struct irq_desc *desc) +{ + struct tb10x_ictl *ictl = irq_desc_get_handler_data(desc); + + generic_handle_irq(irq_find_mapping(ictl->domain, irq)); +} + +static int __init of_tb10x_init_irq(struct device_node *ictl, + struct device_node *parent) +{ + int i; + int ret; + int nrirqs = of_irq_count(ictl); + struct resource mem; + struct tb10x_ictl *ic; + + ic = kmalloc(sizeof(struct tb10x_ictl), GFP_KERNEL); + if (!ic) + return -ENOMEM; + + ic->domain = irq_domain_add_tree(ictl, &irq_tb10x_domain_ops, ic); + if (!ic->domain) { + ret = -ENOMEM; + goto irq_domain_add_fail; + } + + if (of_address_to_resource(ictl, 0, &mem)) { + pr_err("%s: No registers declared in DeviceTree.\n", + ictl->name); + ret = -EINVAL; + goto of_reg_get_fail; + } + + if (!request_mem_region(mem.start, resource_size(&mem), + irq_tb10x_chip.name)) { + ret = -EBUSY; + goto reg_request_fail; + } + + ic->base = ioremap(mem.start, resource_size(&mem)); + if (!ic->base) { + ret = -EBUSY; + goto ioremap_fail; + } + + for (i = 0; i < nrirqs; i++) { + unsigned int irq, hwirq; + struct irq_data *irq_data; + + irq = irq_of_parse_and_map(ictl, i); + irq_data = irq_get_irq_data(irq); + + if (!irq_data) + continue; + + hwirq = irqd_to_hwirq(irq_data); + + if (irq != hwirq) + panic("IRQ %d maps to hardware IRQ %d.\n", irq, hwirq); + + irq_set_handler_data(irq, ic); + irq_set_chained_handler(irq, tb10x_irq_cascade); + } + + ab_irqctl_writereg(ic, AB_IRQCTL_INT_ENABLE, 0); + ab_irqctl_writereg(ic, AB_IRQCTL_INT_MODE, 0); + ab_irqctl_writereg(ic, AB_IRQCTL_INT_POLARITY, 0); + ab_irqctl_writereg(ic, AB_IRQCTL_INT_STATUS, ~0UL); + + return 0; + +ioremap_fail: + release_mem_region(mem.start, resource_size(&mem)); +reg_request_fail: +of_reg_get_fail: + irq_domain_remove(ic->domain); +irq_domain_add_fail: + kfree(ic); + return ret; +} +IRQCHIP_DECLARE(tb10x_intc, "abilis,tb10x_ictl", of_tb10x_init_irq); -- 1.7.1 -- To unsubscribe from this list: send the line "unsubscribe linux-doc" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html