1d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only 2706cffc1SArd Biesheuvel /* 3706cffc1SArd Biesheuvel * Driver for Socionext External Interrupt Unit (EXIU) 4706cffc1SArd Biesheuvel * 50444638cSArd Biesheuvel * Copyright (c) 2017-2019 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> 203d090a36SArd Biesheuvel #include <linux/platform_device.h> 21706cffc1SArd Biesheuvel 22706cffc1SArd Biesheuvel #include <dt-bindings/interrupt-controller/arm-gic.h> 23706cffc1SArd Biesheuvel 24706cffc1SArd Biesheuvel #define NUM_IRQS 32 25706cffc1SArd Biesheuvel 26706cffc1SArd Biesheuvel #define EIMASK 0x00 27706cffc1SArd Biesheuvel #define EISRCSEL 0x04 28706cffc1SArd Biesheuvel #define EIREQSTA 0x08 29706cffc1SArd Biesheuvel #define EIRAWREQSTA 0x0C 30706cffc1SArd Biesheuvel #define EIREQCLR 0x10 31706cffc1SArd Biesheuvel #define EILVL 0x14 32706cffc1SArd Biesheuvel #define EIEDG 0x18 33706cffc1SArd Biesheuvel #define EISIR 0x1C 34706cffc1SArd Biesheuvel 35706cffc1SArd Biesheuvel struct exiu_irq_data { 36706cffc1SArd Biesheuvel void __iomem *base; 37706cffc1SArd Biesheuvel u32 spi_base; 38706cffc1SArd Biesheuvel }; 39706cffc1SArd Biesheuvel 40*4efc851cSDaniel Thompson static void exiu_irq_ack(struct irq_data *d) 41706cffc1SArd Biesheuvel { 42706cffc1SArd Biesheuvel struct exiu_irq_data *data = irq_data_get_irq_chip_data(d); 43706cffc1SArd Biesheuvel 44706cffc1SArd Biesheuvel writel(BIT(d->hwirq), data->base + EIREQCLR); 45*4efc851cSDaniel Thompson } 46*4efc851cSDaniel Thompson 47*4efc851cSDaniel Thompson static void exiu_irq_eoi(struct irq_data *d) 48*4efc851cSDaniel Thompson { 49*4efc851cSDaniel Thompson struct exiu_irq_data *data = irq_data_get_irq_chip_data(d); 50*4efc851cSDaniel Thompson 51*4efc851cSDaniel Thompson /* 52*4efc851cSDaniel Thompson * Level triggered interrupts are latched and must be cleared during 53*4efc851cSDaniel Thompson * EOI or the interrupt will be jammed on. Of course if a level 54*4efc851cSDaniel Thompson * triggered interrupt is still asserted then the write will not clear 55*4efc851cSDaniel Thompson * the interrupt. 56*4efc851cSDaniel Thompson */ 57*4efc851cSDaniel Thompson if (irqd_is_level_type(d)) 58*4efc851cSDaniel Thompson writel(BIT(d->hwirq), data->base + EIREQCLR); 59*4efc851cSDaniel Thompson 60706cffc1SArd Biesheuvel irq_chip_eoi_parent(d); 61706cffc1SArd Biesheuvel } 62706cffc1SArd Biesheuvel 63706cffc1SArd Biesheuvel static void exiu_irq_mask(struct irq_data *d) 64706cffc1SArd Biesheuvel { 65706cffc1SArd Biesheuvel struct exiu_irq_data *data = irq_data_get_irq_chip_data(d); 66706cffc1SArd Biesheuvel u32 val; 67706cffc1SArd Biesheuvel 68706cffc1SArd Biesheuvel val = readl_relaxed(data->base + EIMASK) | BIT(d->hwirq); 69706cffc1SArd Biesheuvel writel_relaxed(val, data->base + EIMASK); 70706cffc1SArd Biesheuvel irq_chip_mask_parent(d); 71706cffc1SArd Biesheuvel } 72706cffc1SArd Biesheuvel 73706cffc1SArd Biesheuvel static void exiu_irq_unmask(struct irq_data *d) 74706cffc1SArd Biesheuvel { 75706cffc1SArd Biesheuvel struct exiu_irq_data *data = irq_data_get_irq_chip_data(d); 76706cffc1SArd Biesheuvel u32 val; 77706cffc1SArd Biesheuvel 78706cffc1SArd Biesheuvel val = readl_relaxed(data->base + EIMASK) & ~BIT(d->hwirq); 79706cffc1SArd Biesheuvel writel_relaxed(val, data->base + EIMASK); 80706cffc1SArd Biesheuvel irq_chip_unmask_parent(d); 81706cffc1SArd Biesheuvel } 82706cffc1SArd Biesheuvel 83706cffc1SArd Biesheuvel static void exiu_irq_enable(struct irq_data *d) 84706cffc1SArd Biesheuvel { 85706cffc1SArd Biesheuvel struct exiu_irq_data *data = irq_data_get_irq_chip_data(d); 86706cffc1SArd Biesheuvel u32 val; 87706cffc1SArd Biesheuvel 88706cffc1SArd Biesheuvel /* clear interrupts that were latched while disabled */ 89706cffc1SArd Biesheuvel writel_relaxed(BIT(d->hwirq), data->base + EIREQCLR); 90706cffc1SArd Biesheuvel 91706cffc1SArd Biesheuvel val = readl_relaxed(data->base + EIMASK) & ~BIT(d->hwirq); 92706cffc1SArd Biesheuvel writel_relaxed(val, data->base + EIMASK); 93706cffc1SArd Biesheuvel irq_chip_enable_parent(d); 94706cffc1SArd Biesheuvel } 95706cffc1SArd Biesheuvel 96706cffc1SArd Biesheuvel static int exiu_irq_set_type(struct irq_data *d, unsigned int type) 97706cffc1SArd Biesheuvel { 98706cffc1SArd Biesheuvel struct exiu_irq_data *data = irq_data_get_irq_chip_data(d); 99706cffc1SArd Biesheuvel u32 val; 100706cffc1SArd Biesheuvel 101706cffc1SArd Biesheuvel val = readl_relaxed(data->base + EILVL); 102706cffc1SArd Biesheuvel if (type == IRQ_TYPE_EDGE_RISING || type == IRQ_TYPE_LEVEL_HIGH) 103706cffc1SArd Biesheuvel val |= BIT(d->hwirq); 104706cffc1SArd Biesheuvel else 105706cffc1SArd Biesheuvel val &= ~BIT(d->hwirq); 106706cffc1SArd Biesheuvel writel_relaxed(val, data->base + EILVL); 107706cffc1SArd Biesheuvel 108706cffc1SArd Biesheuvel val = readl_relaxed(data->base + EIEDG); 109*4efc851cSDaniel Thompson if (type == IRQ_TYPE_LEVEL_LOW || type == IRQ_TYPE_LEVEL_HIGH) { 110706cffc1SArd Biesheuvel val &= ~BIT(d->hwirq); 111*4efc851cSDaniel Thompson irq_set_handler_locked(d, handle_fasteoi_irq); 112*4efc851cSDaniel Thompson } else { 113706cffc1SArd Biesheuvel val |= BIT(d->hwirq); 114*4efc851cSDaniel Thompson irq_set_handler_locked(d, handle_fasteoi_ack_irq); 115*4efc851cSDaniel Thompson } 116706cffc1SArd Biesheuvel writel_relaxed(val, data->base + EIEDG); 117706cffc1SArd Biesheuvel 118706cffc1SArd Biesheuvel writel_relaxed(BIT(d->hwirq), data->base + EIREQCLR); 119706cffc1SArd Biesheuvel 120706cffc1SArd Biesheuvel return irq_chip_set_type_parent(d, IRQ_TYPE_LEVEL_HIGH); 121706cffc1SArd Biesheuvel } 122706cffc1SArd Biesheuvel 123706cffc1SArd Biesheuvel static struct irq_chip exiu_irq_chip = { 124706cffc1SArd Biesheuvel .name = "EXIU", 125*4efc851cSDaniel Thompson .irq_ack = exiu_irq_ack, 126706cffc1SArd Biesheuvel .irq_eoi = exiu_irq_eoi, 127706cffc1SArd Biesheuvel .irq_enable = exiu_irq_enable, 128706cffc1SArd Biesheuvel .irq_mask = exiu_irq_mask, 129706cffc1SArd Biesheuvel .irq_unmask = exiu_irq_unmask, 130706cffc1SArd Biesheuvel .irq_set_type = exiu_irq_set_type, 131706cffc1SArd Biesheuvel .irq_set_affinity = irq_chip_set_affinity_parent, 132706cffc1SArd Biesheuvel .flags = IRQCHIP_SET_TYPE_MASKED | 133706cffc1SArd Biesheuvel IRQCHIP_SKIP_SET_WAKE | 134706cffc1SArd Biesheuvel IRQCHIP_EOI_THREADED | 135706cffc1SArd Biesheuvel IRQCHIP_MASK_ON_SUSPEND, 136706cffc1SArd Biesheuvel }; 137706cffc1SArd Biesheuvel 138706cffc1SArd Biesheuvel static int exiu_domain_translate(struct irq_domain *domain, 139706cffc1SArd Biesheuvel struct irq_fwspec *fwspec, 140706cffc1SArd Biesheuvel unsigned long *hwirq, 141706cffc1SArd Biesheuvel unsigned int *type) 142706cffc1SArd Biesheuvel { 143706cffc1SArd Biesheuvel struct exiu_irq_data *info = domain->host_data; 144706cffc1SArd Biesheuvel 145706cffc1SArd Biesheuvel if (is_of_node(fwspec->fwnode)) { 146706cffc1SArd Biesheuvel if (fwspec->param_count != 3) 147706cffc1SArd Biesheuvel return -EINVAL; 148706cffc1SArd Biesheuvel 149706cffc1SArd Biesheuvel if (fwspec->param[0] != GIC_SPI) 150706cffc1SArd Biesheuvel return -EINVAL; /* No PPI should point to this domain */ 151706cffc1SArd Biesheuvel 152706cffc1SArd Biesheuvel *hwirq = fwspec->param[1] - info->spi_base; 153706cffc1SArd Biesheuvel *type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK; 1543d090a36SArd Biesheuvel } else { 1553d090a36SArd Biesheuvel if (fwspec->param_count != 2) 156706cffc1SArd Biesheuvel return -EINVAL; 1573d090a36SArd Biesheuvel *hwirq = fwspec->param[0]; 158d001e41eSChen Baozi *type = fwspec->param[1] & IRQ_TYPE_SENSE_MASK; 1593d090a36SArd Biesheuvel } 1603d090a36SArd Biesheuvel return 0; 161706cffc1SArd Biesheuvel } 162706cffc1SArd Biesheuvel 163706cffc1SArd Biesheuvel static int exiu_domain_alloc(struct irq_domain *dom, unsigned int virq, 164706cffc1SArd Biesheuvel unsigned int nr_irqs, void *data) 165706cffc1SArd Biesheuvel { 166706cffc1SArd Biesheuvel struct irq_fwspec *fwspec = data; 167706cffc1SArd Biesheuvel struct irq_fwspec parent_fwspec; 168706cffc1SArd Biesheuvel struct exiu_irq_data *info = dom->host_data; 169706cffc1SArd Biesheuvel irq_hw_number_t hwirq; 170706cffc1SArd Biesheuvel 1713d090a36SArd Biesheuvel parent_fwspec = *fwspec; 1723d090a36SArd Biesheuvel if (is_of_node(dom->parent->fwnode)) { 173706cffc1SArd Biesheuvel if (fwspec->param_count != 3) 174706cffc1SArd Biesheuvel return -EINVAL; /* Not GIC compliant */ 175706cffc1SArd Biesheuvel if (fwspec->param[0] != GIC_SPI) 176706cffc1SArd Biesheuvel return -EINVAL; /* No PPI should point to this domain */ 177706cffc1SArd Biesheuvel 178706cffc1SArd Biesheuvel hwirq = fwspec->param[1] - info->spi_base; 1793d090a36SArd Biesheuvel } else { 1803d090a36SArd Biesheuvel hwirq = fwspec->param[0]; 1813d090a36SArd Biesheuvel parent_fwspec.param[0] = hwirq + info->spi_base + 32; 1823d090a36SArd Biesheuvel } 1833d090a36SArd Biesheuvel WARN_ON(nr_irqs != 1); 184706cffc1SArd Biesheuvel irq_domain_set_hwirq_and_chip(dom, virq, hwirq, &exiu_irq_chip, info); 185706cffc1SArd Biesheuvel 186706cffc1SArd Biesheuvel parent_fwspec.fwnode = dom->parent->fwnode; 187706cffc1SArd Biesheuvel return irq_domain_alloc_irqs_parent(dom, virq, nr_irqs, &parent_fwspec); 188706cffc1SArd Biesheuvel } 189706cffc1SArd Biesheuvel 190706cffc1SArd Biesheuvel static const struct irq_domain_ops exiu_domain_ops = { 191706cffc1SArd Biesheuvel .translate = exiu_domain_translate, 192706cffc1SArd Biesheuvel .alloc = exiu_domain_alloc, 193706cffc1SArd Biesheuvel .free = irq_domain_free_irqs_common, 194706cffc1SArd Biesheuvel }; 195706cffc1SArd Biesheuvel 1960444638cSArd Biesheuvel static struct exiu_irq_data *exiu_init(const struct fwnode_handle *fwnode, 1970444638cSArd Biesheuvel struct resource *res) 1980444638cSArd Biesheuvel { 1990444638cSArd Biesheuvel struct exiu_irq_data *data; 2000444638cSArd Biesheuvel int err; 2010444638cSArd Biesheuvel 2020444638cSArd Biesheuvel data = kzalloc(sizeof(*data), GFP_KERNEL); 2030444638cSArd Biesheuvel if (!data) 2040444638cSArd Biesheuvel return ERR_PTR(-ENOMEM); 2050444638cSArd Biesheuvel 2060444638cSArd Biesheuvel if (fwnode_property_read_u32_array(fwnode, "socionext,spi-base", 2070444638cSArd Biesheuvel &data->spi_base, 1)) { 2080444638cSArd Biesheuvel err = -ENODEV; 2090444638cSArd Biesheuvel goto out_free; 2100444638cSArd Biesheuvel } 2110444638cSArd Biesheuvel 2120444638cSArd Biesheuvel data->base = ioremap(res->start, resource_size(res)); 2130444638cSArd Biesheuvel if (!data->base) { 2140444638cSArd Biesheuvel err = -ENODEV; 2150444638cSArd Biesheuvel goto out_free; 2160444638cSArd Biesheuvel } 2170444638cSArd Biesheuvel 2180444638cSArd Biesheuvel /* clear and mask all interrupts */ 2190444638cSArd Biesheuvel writel_relaxed(0xFFFFFFFF, data->base + EIREQCLR); 2200444638cSArd Biesheuvel writel_relaxed(0xFFFFFFFF, data->base + EIMASK); 2210444638cSArd Biesheuvel 2220444638cSArd Biesheuvel return data; 2230444638cSArd Biesheuvel 2240444638cSArd Biesheuvel out_free: 2250444638cSArd Biesheuvel kfree(data); 2260444638cSArd Biesheuvel return ERR_PTR(err); 2270444638cSArd Biesheuvel } 2280444638cSArd Biesheuvel 2290444638cSArd Biesheuvel static int __init exiu_dt_init(struct device_node *node, 230706cffc1SArd Biesheuvel struct device_node *parent) 231706cffc1SArd Biesheuvel { 232706cffc1SArd Biesheuvel struct irq_domain *parent_domain, *domain; 233706cffc1SArd Biesheuvel struct exiu_irq_data *data; 2340444638cSArd Biesheuvel struct resource res; 235706cffc1SArd Biesheuvel 236706cffc1SArd Biesheuvel if (!parent) { 237706cffc1SArd Biesheuvel pr_err("%pOF: no parent, giving up\n", node); 238706cffc1SArd Biesheuvel return -ENODEV; 239706cffc1SArd Biesheuvel } 240706cffc1SArd Biesheuvel 241706cffc1SArd Biesheuvel parent_domain = irq_find_host(parent); 242706cffc1SArd Biesheuvel if (!parent_domain) { 243706cffc1SArd Biesheuvel pr_err("%pOF: unable to obtain parent domain\n", node); 244706cffc1SArd Biesheuvel return -ENXIO; 245706cffc1SArd Biesheuvel } 246706cffc1SArd Biesheuvel 2470444638cSArd Biesheuvel if (of_address_to_resource(node, 0, &res)) { 2480444638cSArd Biesheuvel pr_err("%pOF: failed to parse memory resource\n", node); 2490444638cSArd Biesheuvel return -ENXIO; 250706cffc1SArd Biesheuvel } 251706cffc1SArd Biesheuvel 2520444638cSArd Biesheuvel data = exiu_init(of_node_to_fwnode(node), &res); 2530444638cSArd Biesheuvel if (IS_ERR(data)) 2540444638cSArd Biesheuvel return PTR_ERR(data); 255706cffc1SArd Biesheuvel 256706cffc1SArd Biesheuvel domain = irq_domain_add_hierarchy(parent_domain, 0, NUM_IRQS, node, 257706cffc1SArd Biesheuvel &exiu_domain_ops, data); 258706cffc1SArd Biesheuvel if (!domain) { 259706cffc1SArd Biesheuvel pr_err("%pOF: failed to allocate domain\n", node); 260706cffc1SArd Biesheuvel goto out_unmap; 261706cffc1SArd Biesheuvel } 262706cffc1SArd Biesheuvel 263706cffc1SArd Biesheuvel pr_info("%pOF: %d interrupts forwarded to %pOF\n", node, NUM_IRQS, 264706cffc1SArd Biesheuvel parent); 265706cffc1SArd Biesheuvel 266706cffc1SArd Biesheuvel return 0; 267706cffc1SArd Biesheuvel 268706cffc1SArd Biesheuvel out_unmap: 269706cffc1SArd Biesheuvel iounmap(data->base); 270706cffc1SArd Biesheuvel kfree(data); 2710444638cSArd Biesheuvel return -ENOMEM; 272706cffc1SArd Biesheuvel } 2730444638cSArd Biesheuvel IRQCHIP_DECLARE(exiu, "socionext,synquacer-exiu", exiu_dt_init); 2743d090a36SArd Biesheuvel 2753d090a36SArd Biesheuvel #ifdef CONFIG_ACPI 2763d090a36SArd Biesheuvel static int exiu_acpi_probe(struct platform_device *pdev) 2773d090a36SArd Biesheuvel { 2783d090a36SArd Biesheuvel struct irq_domain *domain; 2793d090a36SArd Biesheuvel struct exiu_irq_data *data; 2803d090a36SArd Biesheuvel struct resource *res; 2813d090a36SArd Biesheuvel 2823d090a36SArd Biesheuvel res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 2833d090a36SArd Biesheuvel if (!res) { 2843d090a36SArd Biesheuvel dev_err(&pdev->dev, "failed to parse memory resource\n"); 2853d090a36SArd Biesheuvel return -ENXIO; 2863d090a36SArd Biesheuvel } 2873d090a36SArd Biesheuvel 2883d090a36SArd Biesheuvel data = exiu_init(dev_fwnode(&pdev->dev), res); 2893d090a36SArd Biesheuvel if (IS_ERR(data)) 2903d090a36SArd Biesheuvel return PTR_ERR(data); 2913d090a36SArd Biesheuvel 2923d090a36SArd Biesheuvel domain = acpi_irq_create_hierarchy(0, NUM_IRQS, dev_fwnode(&pdev->dev), 2933d090a36SArd Biesheuvel &exiu_domain_ops, data); 2943d090a36SArd Biesheuvel if (!domain) { 2953d090a36SArd Biesheuvel dev_err(&pdev->dev, "failed to create IRQ domain\n"); 2963d090a36SArd Biesheuvel goto out_unmap; 2973d090a36SArd Biesheuvel } 2983d090a36SArd Biesheuvel 2993d090a36SArd Biesheuvel dev_info(&pdev->dev, "%d interrupts forwarded\n", NUM_IRQS); 3003d090a36SArd Biesheuvel 3013d090a36SArd Biesheuvel return 0; 3023d090a36SArd Biesheuvel 3033d090a36SArd Biesheuvel out_unmap: 3043d090a36SArd Biesheuvel iounmap(data->base); 3053d090a36SArd Biesheuvel kfree(data); 3063d090a36SArd Biesheuvel return -ENOMEM; 3073d090a36SArd Biesheuvel } 3083d090a36SArd Biesheuvel 3093d090a36SArd Biesheuvel static const struct acpi_device_id exiu_acpi_ids[] = { 3103d090a36SArd Biesheuvel { "SCX0008" }, 3113d090a36SArd Biesheuvel { /* sentinel */ } 3123d090a36SArd Biesheuvel }; 3133d090a36SArd Biesheuvel MODULE_DEVICE_TABLE(acpi, exiu_acpi_ids); 3143d090a36SArd Biesheuvel 3153d090a36SArd Biesheuvel static struct platform_driver exiu_driver = { 3163d090a36SArd Biesheuvel .driver = { 3173d090a36SArd Biesheuvel .name = "exiu", 3183d090a36SArd Biesheuvel .acpi_match_table = exiu_acpi_ids, 3193d090a36SArd Biesheuvel }, 3203d090a36SArd Biesheuvel .probe = exiu_acpi_probe, 3213d090a36SArd Biesheuvel }; 3223d090a36SArd Biesheuvel builtin_platform_driver(exiu_driver); 3233d090a36SArd Biesheuvel #endif 324