1*d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only 2706cffc1SArd Biesheuvel /* 3706cffc1SArd Biesheuvel * Driver for Socionext External Interrupt Unit (EXIU) 4706cffc1SArd Biesheuvel * 5706cffc1SArd Biesheuvel * Copyright (c) 2017 Linaro, Ltd. <ard.biesheuvel@linaro.org> 6706cffc1SArd Biesheuvel * 7706cffc1SArd Biesheuvel * Based on irq-tegra.c: 8706cffc1SArd Biesheuvel * Copyright (C) 2011 Google, Inc. 9706cffc1SArd Biesheuvel * Copyright (C) 2010,2013, NVIDIA Corporation 10706cffc1SArd Biesheuvel */ 11706cffc1SArd Biesheuvel 12706cffc1SArd Biesheuvel #include <linux/interrupt.h> 13706cffc1SArd Biesheuvel #include <linux/io.h> 14706cffc1SArd Biesheuvel #include <linux/irq.h> 15706cffc1SArd Biesheuvel #include <linux/irqchip.h> 16706cffc1SArd Biesheuvel #include <linux/irqdomain.h> 17706cffc1SArd Biesheuvel #include <linux/of.h> 18706cffc1SArd Biesheuvel #include <linux/of_address.h> 19706cffc1SArd Biesheuvel #include <linux/of_irq.h> 20706cffc1SArd Biesheuvel 21706cffc1SArd Biesheuvel #include <dt-bindings/interrupt-controller/arm-gic.h> 22706cffc1SArd Biesheuvel 23706cffc1SArd Biesheuvel #define NUM_IRQS 32 24706cffc1SArd Biesheuvel 25706cffc1SArd Biesheuvel #define EIMASK 0x00 26706cffc1SArd Biesheuvel #define EISRCSEL 0x04 27706cffc1SArd Biesheuvel #define EIREQSTA 0x08 28706cffc1SArd Biesheuvel #define EIRAWREQSTA 0x0C 29706cffc1SArd Biesheuvel #define EIREQCLR 0x10 30706cffc1SArd Biesheuvel #define EILVL 0x14 31706cffc1SArd Biesheuvel #define EIEDG 0x18 32706cffc1SArd Biesheuvel #define EISIR 0x1C 33706cffc1SArd Biesheuvel 34706cffc1SArd Biesheuvel struct exiu_irq_data { 35706cffc1SArd Biesheuvel void __iomem *base; 36706cffc1SArd Biesheuvel u32 spi_base; 37706cffc1SArd Biesheuvel }; 38706cffc1SArd Biesheuvel 39706cffc1SArd Biesheuvel static void exiu_irq_eoi(struct irq_data *d) 40706cffc1SArd Biesheuvel { 41706cffc1SArd Biesheuvel struct exiu_irq_data *data = irq_data_get_irq_chip_data(d); 42706cffc1SArd Biesheuvel 43706cffc1SArd Biesheuvel writel(BIT(d->hwirq), data->base + EIREQCLR); 44706cffc1SArd Biesheuvel irq_chip_eoi_parent(d); 45706cffc1SArd Biesheuvel } 46706cffc1SArd Biesheuvel 47706cffc1SArd Biesheuvel static void exiu_irq_mask(struct irq_data *d) 48706cffc1SArd Biesheuvel { 49706cffc1SArd Biesheuvel struct exiu_irq_data *data = irq_data_get_irq_chip_data(d); 50706cffc1SArd Biesheuvel u32 val; 51706cffc1SArd Biesheuvel 52706cffc1SArd Biesheuvel val = readl_relaxed(data->base + EIMASK) | BIT(d->hwirq); 53706cffc1SArd Biesheuvel writel_relaxed(val, data->base + EIMASK); 54706cffc1SArd Biesheuvel irq_chip_mask_parent(d); 55706cffc1SArd Biesheuvel } 56706cffc1SArd Biesheuvel 57706cffc1SArd Biesheuvel static void exiu_irq_unmask(struct irq_data *d) 58706cffc1SArd Biesheuvel { 59706cffc1SArd Biesheuvel struct exiu_irq_data *data = irq_data_get_irq_chip_data(d); 60706cffc1SArd Biesheuvel u32 val; 61706cffc1SArd Biesheuvel 62706cffc1SArd Biesheuvel val = readl_relaxed(data->base + EIMASK) & ~BIT(d->hwirq); 63706cffc1SArd Biesheuvel writel_relaxed(val, data->base + EIMASK); 64706cffc1SArd Biesheuvel irq_chip_unmask_parent(d); 65706cffc1SArd Biesheuvel } 66706cffc1SArd Biesheuvel 67706cffc1SArd Biesheuvel static void exiu_irq_enable(struct irq_data *d) 68706cffc1SArd Biesheuvel { 69706cffc1SArd Biesheuvel struct exiu_irq_data *data = irq_data_get_irq_chip_data(d); 70706cffc1SArd Biesheuvel u32 val; 71706cffc1SArd Biesheuvel 72706cffc1SArd Biesheuvel /* clear interrupts that were latched while disabled */ 73706cffc1SArd Biesheuvel writel_relaxed(BIT(d->hwirq), data->base + EIREQCLR); 74706cffc1SArd Biesheuvel 75706cffc1SArd Biesheuvel val = readl_relaxed(data->base + EIMASK) & ~BIT(d->hwirq); 76706cffc1SArd Biesheuvel writel_relaxed(val, data->base + EIMASK); 77706cffc1SArd Biesheuvel irq_chip_enable_parent(d); 78706cffc1SArd Biesheuvel } 79706cffc1SArd Biesheuvel 80706cffc1SArd Biesheuvel static int exiu_irq_set_type(struct irq_data *d, unsigned int type) 81706cffc1SArd Biesheuvel { 82706cffc1SArd Biesheuvel struct exiu_irq_data *data = irq_data_get_irq_chip_data(d); 83706cffc1SArd Biesheuvel u32 val; 84706cffc1SArd Biesheuvel 85706cffc1SArd Biesheuvel val = readl_relaxed(data->base + EILVL); 86706cffc1SArd Biesheuvel if (type == IRQ_TYPE_EDGE_RISING || type == IRQ_TYPE_LEVEL_HIGH) 87706cffc1SArd Biesheuvel val |= BIT(d->hwirq); 88706cffc1SArd Biesheuvel else 89706cffc1SArd Biesheuvel val &= ~BIT(d->hwirq); 90706cffc1SArd Biesheuvel writel_relaxed(val, data->base + EILVL); 91706cffc1SArd Biesheuvel 92706cffc1SArd Biesheuvel val = readl_relaxed(data->base + EIEDG); 93706cffc1SArd Biesheuvel if (type == IRQ_TYPE_LEVEL_LOW || type == IRQ_TYPE_LEVEL_HIGH) 94706cffc1SArd Biesheuvel val &= ~BIT(d->hwirq); 95706cffc1SArd Biesheuvel else 96706cffc1SArd Biesheuvel val |= BIT(d->hwirq); 97706cffc1SArd Biesheuvel writel_relaxed(val, data->base + EIEDG); 98706cffc1SArd Biesheuvel 99706cffc1SArd Biesheuvel writel_relaxed(BIT(d->hwirq), data->base + EIREQCLR); 100706cffc1SArd Biesheuvel 101706cffc1SArd Biesheuvel return irq_chip_set_type_parent(d, IRQ_TYPE_LEVEL_HIGH); 102706cffc1SArd Biesheuvel } 103706cffc1SArd Biesheuvel 104706cffc1SArd Biesheuvel static struct irq_chip exiu_irq_chip = { 105706cffc1SArd Biesheuvel .name = "EXIU", 106706cffc1SArd Biesheuvel .irq_eoi = exiu_irq_eoi, 107706cffc1SArd Biesheuvel .irq_enable = exiu_irq_enable, 108706cffc1SArd Biesheuvel .irq_mask = exiu_irq_mask, 109706cffc1SArd Biesheuvel .irq_unmask = exiu_irq_unmask, 110706cffc1SArd Biesheuvel .irq_set_type = exiu_irq_set_type, 111706cffc1SArd Biesheuvel .irq_set_affinity = irq_chip_set_affinity_parent, 112706cffc1SArd Biesheuvel .flags = IRQCHIP_SET_TYPE_MASKED | 113706cffc1SArd Biesheuvel IRQCHIP_SKIP_SET_WAKE | 114706cffc1SArd Biesheuvel IRQCHIP_EOI_THREADED | 115706cffc1SArd Biesheuvel IRQCHIP_MASK_ON_SUSPEND, 116706cffc1SArd Biesheuvel }; 117706cffc1SArd Biesheuvel 118706cffc1SArd Biesheuvel static int exiu_domain_translate(struct irq_domain *domain, 119706cffc1SArd Biesheuvel struct irq_fwspec *fwspec, 120706cffc1SArd Biesheuvel unsigned long *hwirq, 121706cffc1SArd Biesheuvel unsigned int *type) 122706cffc1SArd Biesheuvel { 123706cffc1SArd Biesheuvel struct exiu_irq_data *info = domain->host_data; 124706cffc1SArd Biesheuvel 125706cffc1SArd Biesheuvel if (is_of_node(fwspec->fwnode)) { 126706cffc1SArd Biesheuvel if (fwspec->param_count != 3) 127706cffc1SArd Biesheuvel return -EINVAL; 128706cffc1SArd Biesheuvel 129706cffc1SArd Biesheuvel if (fwspec->param[0] != GIC_SPI) 130706cffc1SArd Biesheuvel return -EINVAL; /* No PPI should point to this domain */ 131706cffc1SArd Biesheuvel 132706cffc1SArd Biesheuvel *hwirq = fwspec->param[1] - info->spi_base; 133706cffc1SArd Biesheuvel *type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK; 134706cffc1SArd Biesheuvel return 0; 135706cffc1SArd Biesheuvel } 136706cffc1SArd Biesheuvel return -EINVAL; 137706cffc1SArd Biesheuvel } 138706cffc1SArd Biesheuvel 139706cffc1SArd Biesheuvel static int exiu_domain_alloc(struct irq_domain *dom, unsigned int virq, 140706cffc1SArd Biesheuvel unsigned int nr_irqs, void *data) 141706cffc1SArd Biesheuvel { 142706cffc1SArd Biesheuvel struct irq_fwspec *fwspec = data; 143706cffc1SArd Biesheuvel struct irq_fwspec parent_fwspec; 144706cffc1SArd Biesheuvel struct exiu_irq_data *info = dom->host_data; 145706cffc1SArd Biesheuvel irq_hw_number_t hwirq; 146706cffc1SArd Biesheuvel 147706cffc1SArd Biesheuvel if (fwspec->param_count != 3) 148706cffc1SArd Biesheuvel return -EINVAL; /* Not GIC compliant */ 149706cffc1SArd Biesheuvel if (fwspec->param[0] != GIC_SPI) 150706cffc1SArd Biesheuvel return -EINVAL; /* No PPI should point to this domain */ 151706cffc1SArd Biesheuvel 152706cffc1SArd Biesheuvel WARN_ON(nr_irqs != 1); 153706cffc1SArd Biesheuvel hwirq = fwspec->param[1] - info->spi_base; 154706cffc1SArd Biesheuvel irq_domain_set_hwirq_and_chip(dom, virq, hwirq, &exiu_irq_chip, info); 155706cffc1SArd Biesheuvel 156706cffc1SArd Biesheuvel parent_fwspec = *fwspec; 157706cffc1SArd Biesheuvel parent_fwspec.fwnode = dom->parent->fwnode; 158706cffc1SArd Biesheuvel return irq_domain_alloc_irqs_parent(dom, virq, nr_irqs, &parent_fwspec); 159706cffc1SArd Biesheuvel } 160706cffc1SArd Biesheuvel 161706cffc1SArd Biesheuvel static const struct irq_domain_ops exiu_domain_ops = { 162706cffc1SArd Biesheuvel .translate = exiu_domain_translate, 163706cffc1SArd Biesheuvel .alloc = exiu_domain_alloc, 164706cffc1SArd Biesheuvel .free = irq_domain_free_irqs_common, 165706cffc1SArd Biesheuvel }; 166706cffc1SArd Biesheuvel 167706cffc1SArd Biesheuvel static int __init exiu_init(struct device_node *node, 168706cffc1SArd Biesheuvel struct device_node *parent) 169706cffc1SArd Biesheuvel { 170706cffc1SArd Biesheuvel struct irq_domain *parent_domain, *domain; 171706cffc1SArd Biesheuvel struct exiu_irq_data *data; 172706cffc1SArd Biesheuvel int err; 173706cffc1SArd Biesheuvel 174706cffc1SArd Biesheuvel if (!parent) { 175706cffc1SArd Biesheuvel pr_err("%pOF: no parent, giving up\n", node); 176706cffc1SArd Biesheuvel return -ENODEV; 177706cffc1SArd Biesheuvel } 178706cffc1SArd Biesheuvel 179706cffc1SArd Biesheuvel parent_domain = irq_find_host(parent); 180706cffc1SArd Biesheuvel if (!parent_domain) { 181706cffc1SArd Biesheuvel pr_err("%pOF: unable to obtain parent domain\n", node); 182706cffc1SArd Biesheuvel return -ENXIO; 183706cffc1SArd Biesheuvel } 184706cffc1SArd Biesheuvel 185706cffc1SArd Biesheuvel data = kzalloc(sizeof(*data), GFP_KERNEL); 186706cffc1SArd Biesheuvel if (!data) 187706cffc1SArd Biesheuvel return -ENOMEM; 188706cffc1SArd Biesheuvel 189706cffc1SArd Biesheuvel if (of_property_read_u32(node, "socionext,spi-base", &data->spi_base)) { 190706cffc1SArd Biesheuvel pr_err("%pOF: failed to parse 'spi-base' property\n", node); 191706cffc1SArd Biesheuvel err = -ENODEV; 192706cffc1SArd Biesheuvel goto out_free; 193706cffc1SArd Biesheuvel } 194706cffc1SArd Biesheuvel 195706cffc1SArd Biesheuvel data->base = of_iomap(node, 0); 1960e54705bSWei Yongjun if (!data->base) { 1970e54705bSWei Yongjun err = -ENODEV; 198706cffc1SArd Biesheuvel goto out_free; 199706cffc1SArd Biesheuvel } 200706cffc1SArd Biesheuvel 201706cffc1SArd Biesheuvel /* clear and mask all interrupts */ 202706cffc1SArd Biesheuvel writel_relaxed(0xFFFFFFFF, data->base + EIREQCLR); 203706cffc1SArd Biesheuvel writel_relaxed(0xFFFFFFFF, data->base + EIMASK); 204706cffc1SArd Biesheuvel 205706cffc1SArd Biesheuvel domain = irq_domain_add_hierarchy(parent_domain, 0, NUM_IRQS, node, 206706cffc1SArd Biesheuvel &exiu_domain_ops, data); 207706cffc1SArd Biesheuvel if (!domain) { 208706cffc1SArd Biesheuvel pr_err("%pOF: failed to allocate domain\n", node); 209706cffc1SArd Biesheuvel err = -ENOMEM; 210706cffc1SArd Biesheuvel goto out_unmap; 211706cffc1SArd Biesheuvel } 212706cffc1SArd Biesheuvel 213706cffc1SArd Biesheuvel pr_info("%pOF: %d interrupts forwarded to %pOF\n", node, NUM_IRQS, 214706cffc1SArd Biesheuvel parent); 215706cffc1SArd Biesheuvel 216706cffc1SArd Biesheuvel return 0; 217706cffc1SArd Biesheuvel 218706cffc1SArd Biesheuvel out_unmap: 219706cffc1SArd Biesheuvel iounmap(data->base); 220706cffc1SArd Biesheuvel out_free: 221706cffc1SArd Biesheuvel kfree(data); 222706cffc1SArd Biesheuvel return err; 223706cffc1SArd Biesheuvel } 224706cffc1SArd Biesheuvel IRQCHIP_DECLARE(exiu, "socionext,synquacer-exiu", exiu_init); 225