1 /* 2 * Driver for Socionext External Interrupt Unit (EXIU) 3 * 4 * Copyright (c) 2017 Linaro, Ltd. <ard.biesheuvel@linaro.org> 5 * 6 * Based on irq-tegra.c: 7 * Copyright (C) 2011 Google, Inc. 8 * Copyright (C) 2010,2013, NVIDIA Corporation 9 * 10 * This program is free software; you can redistribute it and/or modify 11 * it under the terms of the GNU General Public License version 2 as 12 * published by the Free Software Foundation. 13 */ 14 15 #include <linux/interrupt.h> 16 #include <linux/io.h> 17 #include <linux/irq.h> 18 #include <linux/irqchip.h> 19 #include <linux/irqdomain.h> 20 #include <linux/of.h> 21 #include <linux/of_address.h> 22 #include <linux/of_irq.h> 23 24 #include <dt-bindings/interrupt-controller/arm-gic.h> 25 26 #define NUM_IRQS 32 27 28 #define EIMASK 0x00 29 #define EISRCSEL 0x04 30 #define EIREQSTA 0x08 31 #define EIRAWREQSTA 0x0C 32 #define EIREQCLR 0x10 33 #define EILVL 0x14 34 #define EIEDG 0x18 35 #define EISIR 0x1C 36 37 struct exiu_irq_data { 38 void __iomem *base; 39 u32 spi_base; 40 }; 41 42 static void exiu_irq_eoi(struct irq_data *d) 43 { 44 struct exiu_irq_data *data = irq_data_get_irq_chip_data(d); 45 46 writel(BIT(d->hwirq), data->base + EIREQCLR); 47 irq_chip_eoi_parent(d); 48 } 49 50 static void exiu_irq_mask(struct irq_data *d) 51 { 52 struct exiu_irq_data *data = irq_data_get_irq_chip_data(d); 53 u32 val; 54 55 val = readl_relaxed(data->base + EIMASK) | BIT(d->hwirq); 56 writel_relaxed(val, data->base + EIMASK); 57 irq_chip_mask_parent(d); 58 } 59 60 static void exiu_irq_unmask(struct irq_data *d) 61 { 62 struct exiu_irq_data *data = irq_data_get_irq_chip_data(d); 63 u32 val; 64 65 val = readl_relaxed(data->base + EIMASK) & ~BIT(d->hwirq); 66 writel_relaxed(val, data->base + EIMASK); 67 irq_chip_unmask_parent(d); 68 } 69 70 static void exiu_irq_enable(struct irq_data *d) 71 { 72 struct exiu_irq_data *data = irq_data_get_irq_chip_data(d); 73 u32 val; 74 75 /* clear interrupts that were latched while disabled */ 76 writel_relaxed(BIT(d->hwirq), data->base + EIREQCLR); 77 78 val = readl_relaxed(data->base + EIMASK) & ~BIT(d->hwirq); 79 writel_relaxed(val, data->base + EIMASK); 80 irq_chip_enable_parent(d); 81 } 82 83 static int exiu_irq_set_type(struct irq_data *d, unsigned int type) 84 { 85 struct exiu_irq_data *data = irq_data_get_irq_chip_data(d); 86 u32 val; 87 88 val = readl_relaxed(data->base + EILVL); 89 if (type == IRQ_TYPE_EDGE_RISING || type == IRQ_TYPE_LEVEL_HIGH) 90 val |= BIT(d->hwirq); 91 else 92 val &= ~BIT(d->hwirq); 93 writel_relaxed(val, data->base + EILVL); 94 95 val = readl_relaxed(data->base + EIEDG); 96 if (type == IRQ_TYPE_LEVEL_LOW || type == IRQ_TYPE_LEVEL_HIGH) 97 val &= ~BIT(d->hwirq); 98 else 99 val |= BIT(d->hwirq); 100 writel_relaxed(val, data->base + EIEDG); 101 102 writel_relaxed(BIT(d->hwirq), data->base + EIREQCLR); 103 104 return irq_chip_set_type_parent(d, IRQ_TYPE_LEVEL_HIGH); 105 } 106 107 static struct irq_chip exiu_irq_chip = { 108 .name = "EXIU", 109 .irq_eoi = exiu_irq_eoi, 110 .irq_enable = exiu_irq_enable, 111 .irq_mask = exiu_irq_mask, 112 .irq_unmask = exiu_irq_unmask, 113 .irq_set_type = exiu_irq_set_type, 114 .irq_set_affinity = irq_chip_set_affinity_parent, 115 .flags = IRQCHIP_SET_TYPE_MASKED | 116 IRQCHIP_SKIP_SET_WAKE | 117 IRQCHIP_EOI_THREADED | 118 IRQCHIP_MASK_ON_SUSPEND, 119 }; 120 121 static int exiu_domain_translate(struct irq_domain *domain, 122 struct irq_fwspec *fwspec, 123 unsigned long *hwirq, 124 unsigned int *type) 125 { 126 struct exiu_irq_data *info = domain->host_data; 127 128 if (is_of_node(fwspec->fwnode)) { 129 if (fwspec->param_count != 3) 130 return -EINVAL; 131 132 if (fwspec->param[0] != GIC_SPI) 133 return -EINVAL; /* No PPI should point to this domain */ 134 135 *hwirq = fwspec->param[1] - info->spi_base; 136 *type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK; 137 return 0; 138 } 139 return -EINVAL; 140 } 141 142 static int exiu_domain_alloc(struct irq_domain *dom, unsigned int virq, 143 unsigned int nr_irqs, void *data) 144 { 145 struct irq_fwspec *fwspec = data; 146 struct irq_fwspec parent_fwspec; 147 struct exiu_irq_data *info = dom->host_data; 148 irq_hw_number_t hwirq; 149 150 if (fwspec->param_count != 3) 151 return -EINVAL; /* Not GIC compliant */ 152 if (fwspec->param[0] != GIC_SPI) 153 return -EINVAL; /* No PPI should point to this domain */ 154 155 WARN_ON(nr_irqs != 1); 156 hwirq = fwspec->param[1] - info->spi_base; 157 irq_domain_set_hwirq_and_chip(dom, virq, hwirq, &exiu_irq_chip, info); 158 159 parent_fwspec = *fwspec; 160 parent_fwspec.fwnode = dom->parent->fwnode; 161 return irq_domain_alloc_irqs_parent(dom, virq, nr_irqs, &parent_fwspec); 162 } 163 164 static const struct irq_domain_ops exiu_domain_ops = { 165 .translate = exiu_domain_translate, 166 .alloc = exiu_domain_alloc, 167 .free = irq_domain_free_irqs_common, 168 }; 169 170 static int __init exiu_init(struct device_node *node, 171 struct device_node *parent) 172 { 173 struct irq_domain *parent_domain, *domain; 174 struct exiu_irq_data *data; 175 int err; 176 177 if (!parent) { 178 pr_err("%pOF: no parent, giving up\n", node); 179 return -ENODEV; 180 } 181 182 parent_domain = irq_find_host(parent); 183 if (!parent_domain) { 184 pr_err("%pOF: unable to obtain parent domain\n", node); 185 return -ENXIO; 186 } 187 188 data = kzalloc(sizeof(*data), GFP_KERNEL); 189 if (!data) 190 return -ENOMEM; 191 192 if (of_property_read_u32(node, "socionext,spi-base", &data->spi_base)) { 193 pr_err("%pOF: failed to parse 'spi-base' property\n", node); 194 err = -ENODEV; 195 goto out_free; 196 } 197 198 data->base = of_iomap(node, 0); 199 if (!data->base) { 200 err = -ENODEV; 201 goto out_free; 202 } 203 204 /* clear and mask all interrupts */ 205 writel_relaxed(0xFFFFFFFF, data->base + EIREQCLR); 206 writel_relaxed(0xFFFFFFFF, data->base + EIMASK); 207 208 domain = irq_domain_add_hierarchy(parent_domain, 0, NUM_IRQS, node, 209 &exiu_domain_ops, data); 210 if (!domain) { 211 pr_err("%pOF: failed to allocate domain\n", node); 212 err = -ENOMEM; 213 goto out_unmap; 214 } 215 216 pr_info("%pOF: %d interrupts forwarded to %pOF\n", node, NUM_IRQS, 217 parent); 218 219 return 0; 220 221 out_unmap: 222 iounmap(data->base); 223 out_free: 224 kfree(data); 225 return err; 226 } 227 IRQCHIP_DECLARE(exiu, "socionext,synquacer-exiu", exiu_init); 228