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 40706cffc1SArd Biesheuvel static void exiu_irq_eoi(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); 45706cffc1SArd Biesheuvel irq_chip_eoi_parent(d); 46706cffc1SArd Biesheuvel } 47706cffc1SArd Biesheuvel 48706cffc1SArd Biesheuvel static void exiu_irq_mask(struct irq_data *d) 49706cffc1SArd Biesheuvel { 50706cffc1SArd Biesheuvel struct exiu_irq_data *data = irq_data_get_irq_chip_data(d); 51706cffc1SArd Biesheuvel u32 val; 52706cffc1SArd Biesheuvel 53706cffc1SArd Biesheuvel val = readl_relaxed(data->base + EIMASK) | BIT(d->hwirq); 54706cffc1SArd Biesheuvel writel_relaxed(val, data->base + EIMASK); 55706cffc1SArd Biesheuvel irq_chip_mask_parent(d); 56706cffc1SArd Biesheuvel } 57706cffc1SArd Biesheuvel 58706cffc1SArd Biesheuvel static void exiu_irq_unmask(struct irq_data *d) 59706cffc1SArd Biesheuvel { 60706cffc1SArd Biesheuvel struct exiu_irq_data *data = irq_data_get_irq_chip_data(d); 61706cffc1SArd Biesheuvel u32 val; 62706cffc1SArd Biesheuvel 63706cffc1SArd Biesheuvel val = readl_relaxed(data->base + EIMASK) & ~BIT(d->hwirq); 64706cffc1SArd Biesheuvel writel_relaxed(val, data->base + EIMASK); 65706cffc1SArd Biesheuvel irq_chip_unmask_parent(d); 66706cffc1SArd Biesheuvel } 67706cffc1SArd Biesheuvel 68706cffc1SArd Biesheuvel static void exiu_irq_enable(struct irq_data *d) 69706cffc1SArd Biesheuvel { 70706cffc1SArd Biesheuvel struct exiu_irq_data *data = irq_data_get_irq_chip_data(d); 71706cffc1SArd Biesheuvel u32 val; 72706cffc1SArd Biesheuvel 73706cffc1SArd Biesheuvel /* clear interrupts that were latched while disabled */ 74706cffc1SArd Biesheuvel writel_relaxed(BIT(d->hwirq), data->base + EIREQCLR); 75706cffc1SArd Biesheuvel 76706cffc1SArd Biesheuvel val = readl_relaxed(data->base + EIMASK) & ~BIT(d->hwirq); 77706cffc1SArd Biesheuvel writel_relaxed(val, data->base + EIMASK); 78706cffc1SArd Biesheuvel irq_chip_enable_parent(d); 79706cffc1SArd Biesheuvel } 80706cffc1SArd Biesheuvel 81706cffc1SArd Biesheuvel static int exiu_irq_set_type(struct irq_data *d, unsigned int type) 82706cffc1SArd Biesheuvel { 83706cffc1SArd Biesheuvel struct exiu_irq_data *data = irq_data_get_irq_chip_data(d); 84706cffc1SArd Biesheuvel u32 val; 85706cffc1SArd Biesheuvel 86706cffc1SArd Biesheuvel val = readl_relaxed(data->base + EILVL); 87706cffc1SArd Biesheuvel if (type == IRQ_TYPE_EDGE_RISING || type == IRQ_TYPE_LEVEL_HIGH) 88706cffc1SArd Biesheuvel val |= BIT(d->hwirq); 89706cffc1SArd Biesheuvel else 90706cffc1SArd Biesheuvel val &= ~BIT(d->hwirq); 91706cffc1SArd Biesheuvel writel_relaxed(val, data->base + EILVL); 92706cffc1SArd Biesheuvel 93706cffc1SArd Biesheuvel val = readl_relaxed(data->base + EIEDG); 94706cffc1SArd Biesheuvel if (type == IRQ_TYPE_LEVEL_LOW || type == IRQ_TYPE_LEVEL_HIGH) 95706cffc1SArd Biesheuvel val &= ~BIT(d->hwirq); 96706cffc1SArd Biesheuvel else 97706cffc1SArd Biesheuvel val |= BIT(d->hwirq); 98706cffc1SArd Biesheuvel writel_relaxed(val, data->base + EIEDG); 99706cffc1SArd Biesheuvel 100706cffc1SArd Biesheuvel writel_relaxed(BIT(d->hwirq), data->base + EIREQCLR); 101706cffc1SArd Biesheuvel 102706cffc1SArd Biesheuvel return irq_chip_set_type_parent(d, IRQ_TYPE_LEVEL_HIGH); 103706cffc1SArd Biesheuvel } 104706cffc1SArd Biesheuvel 105706cffc1SArd Biesheuvel static struct irq_chip exiu_irq_chip = { 106706cffc1SArd Biesheuvel .name = "EXIU", 107706cffc1SArd Biesheuvel .irq_eoi = exiu_irq_eoi, 108706cffc1SArd Biesheuvel .irq_enable = exiu_irq_enable, 109706cffc1SArd Biesheuvel .irq_mask = exiu_irq_mask, 110706cffc1SArd Biesheuvel .irq_unmask = exiu_irq_unmask, 111706cffc1SArd Biesheuvel .irq_set_type = exiu_irq_set_type, 112706cffc1SArd Biesheuvel .irq_set_affinity = irq_chip_set_affinity_parent, 113706cffc1SArd Biesheuvel .flags = IRQCHIP_SET_TYPE_MASKED | 114706cffc1SArd Biesheuvel IRQCHIP_SKIP_SET_WAKE | 115706cffc1SArd Biesheuvel IRQCHIP_EOI_THREADED | 116706cffc1SArd Biesheuvel IRQCHIP_MASK_ON_SUSPEND, 117706cffc1SArd Biesheuvel }; 118706cffc1SArd Biesheuvel 119706cffc1SArd Biesheuvel static int exiu_domain_translate(struct irq_domain *domain, 120706cffc1SArd Biesheuvel struct irq_fwspec *fwspec, 121706cffc1SArd Biesheuvel unsigned long *hwirq, 122706cffc1SArd Biesheuvel unsigned int *type) 123706cffc1SArd Biesheuvel { 124706cffc1SArd Biesheuvel struct exiu_irq_data *info = domain->host_data; 125706cffc1SArd Biesheuvel 126706cffc1SArd Biesheuvel if (is_of_node(fwspec->fwnode)) { 127706cffc1SArd Biesheuvel if (fwspec->param_count != 3) 128706cffc1SArd Biesheuvel return -EINVAL; 129706cffc1SArd Biesheuvel 130706cffc1SArd Biesheuvel if (fwspec->param[0] != GIC_SPI) 131706cffc1SArd Biesheuvel return -EINVAL; /* No PPI should point to this domain */ 132706cffc1SArd Biesheuvel 133706cffc1SArd Biesheuvel *hwirq = fwspec->param[1] - info->spi_base; 134706cffc1SArd Biesheuvel *type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK; 1353d090a36SArd Biesheuvel } else { 1363d090a36SArd Biesheuvel if (fwspec->param_count != 2) 137706cffc1SArd Biesheuvel return -EINVAL; 1383d090a36SArd Biesheuvel *hwirq = fwspec->param[0]; 139*d001e41eSChen Baozi *type = fwspec->param[1] & IRQ_TYPE_SENSE_MASK; 1403d090a36SArd Biesheuvel } 1413d090a36SArd Biesheuvel return 0; 142706cffc1SArd Biesheuvel } 143706cffc1SArd Biesheuvel 144706cffc1SArd Biesheuvel static int exiu_domain_alloc(struct irq_domain *dom, unsigned int virq, 145706cffc1SArd Biesheuvel unsigned int nr_irqs, void *data) 146706cffc1SArd Biesheuvel { 147706cffc1SArd Biesheuvel struct irq_fwspec *fwspec = data; 148706cffc1SArd Biesheuvel struct irq_fwspec parent_fwspec; 149706cffc1SArd Biesheuvel struct exiu_irq_data *info = dom->host_data; 150706cffc1SArd Biesheuvel irq_hw_number_t hwirq; 151706cffc1SArd Biesheuvel 1523d090a36SArd Biesheuvel parent_fwspec = *fwspec; 1533d090a36SArd Biesheuvel if (is_of_node(dom->parent->fwnode)) { 154706cffc1SArd Biesheuvel if (fwspec->param_count != 3) 155706cffc1SArd Biesheuvel return -EINVAL; /* Not GIC compliant */ 156706cffc1SArd Biesheuvel if (fwspec->param[0] != GIC_SPI) 157706cffc1SArd Biesheuvel return -EINVAL; /* No PPI should point to this domain */ 158706cffc1SArd Biesheuvel 159706cffc1SArd Biesheuvel hwirq = fwspec->param[1] - info->spi_base; 1603d090a36SArd Biesheuvel } else { 1613d090a36SArd Biesheuvel hwirq = fwspec->param[0]; 1623d090a36SArd Biesheuvel parent_fwspec.param[0] = hwirq + info->spi_base + 32; 1633d090a36SArd Biesheuvel } 1643d090a36SArd Biesheuvel WARN_ON(nr_irqs != 1); 165706cffc1SArd Biesheuvel irq_domain_set_hwirq_and_chip(dom, virq, hwirq, &exiu_irq_chip, info); 166706cffc1SArd Biesheuvel 167706cffc1SArd Biesheuvel parent_fwspec.fwnode = dom->parent->fwnode; 168706cffc1SArd Biesheuvel return irq_domain_alloc_irqs_parent(dom, virq, nr_irqs, &parent_fwspec); 169706cffc1SArd Biesheuvel } 170706cffc1SArd Biesheuvel 171706cffc1SArd Biesheuvel static const struct irq_domain_ops exiu_domain_ops = { 172706cffc1SArd Biesheuvel .translate = exiu_domain_translate, 173706cffc1SArd Biesheuvel .alloc = exiu_domain_alloc, 174706cffc1SArd Biesheuvel .free = irq_domain_free_irqs_common, 175706cffc1SArd Biesheuvel }; 176706cffc1SArd Biesheuvel 1770444638cSArd Biesheuvel static struct exiu_irq_data *exiu_init(const struct fwnode_handle *fwnode, 1780444638cSArd Biesheuvel struct resource *res) 1790444638cSArd Biesheuvel { 1800444638cSArd Biesheuvel struct exiu_irq_data *data; 1810444638cSArd Biesheuvel int err; 1820444638cSArd Biesheuvel 1830444638cSArd Biesheuvel data = kzalloc(sizeof(*data), GFP_KERNEL); 1840444638cSArd Biesheuvel if (!data) 1850444638cSArd Biesheuvel return ERR_PTR(-ENOMEM); 1860444638cSArd Biesheuvel 1870444638cSArd Biesheuvel if (fwnode_property_read_u32_array(fwnode, "socionext,spi-base", 1880444638cSArd Biesheuvel &data->spi_base, 1)) { 1890444638cSArd Biesheuvel err = -ENODEV; 1900444638cSArd Biesheuvel goto out_free; 1910444638cSArd Biesheuvel } 1920444638cSArd Biesheuvel 1930444638cSArd Biesheuvel data->base = ioremap(res->start, resource_size(res)); 1940444638cSArd Biesheuvel if (!data->base) { 1950444638cSArd Biesheuvel err = -ENODEV; 1960444638cSArd Biesheuvel goto out_free; 1970444638cSArd Biesheuvel } 1980444638cSArd Biesheuvel 1990444638cSArd Biesheuvel /* clear and mask all interrupts */ 2000444638cSArd Biesheuvel writel_relaxed(0xFFFFFFFF, data->base + EIREQCLR); 2010444638cSArd Biesheuvel writel_relaxed(0xFFFFFFFF, data->base + EIMASK); 2020444638cSArd Biesheuvel 2030444638cSArd Biesheuvel return data; 2040444638cSArd Biesheuvel 2050444638cSArd Biesheuvel out_free: 2060444638cSArd Biesheuvel kfree(data); 2070444638cSArd Biesheuvel return ERR_PTR(err); 2080444638cSArd Biesheuvel } 2090444638cSArd Biesheuvel 2100444638cSArd Biesheuvel static int __init exiu_dt_init(struct device_node *node, 211706cffc1SArd Biesheuvel struct device_node *parent) 212706cffc1SArd Biesheuvel { 213706cffc1SArd Biesheuvel struct irq_domain *parent_domain, *domain; 214706cffc1SArd Biesheuvel struct exiu_irq_data *data; 2150444638cSArd Biesheuvel struct resource res; 216706cffc1SArd Biesheuvel 217706cffc1SArd Biesheuvel if (!parent) { 218706cffc1SArd Biesheuvel pr_err("%pOF: no parent, giving up\n", node); 219706cffc1SArd Biesheuvel return -ENODEV; 220706cffc1SArd Biesheuvel } 221706cffc1SArd Biesheuvel 222706cffc1SArd Biesheuvel parent_domain = irq_find_host(parent); 223706cffc1SArd Biesheuvel if (!parent_domain) { 224706cffc1SArd Biesheuvel pr_err("%pOF: unable to obtain parent domain\n", node); 225706cffc1SArd Biesheuvel return -ENXIO; 226706cffc1SArd Biesheuvel } 227706cffc1SArd Biesheuvel 2280444638cSArd Biesheuvel if (of_address_to_resource(node, 0, &res)) { 2290444638cSArd Biesheuvel pr_err("%pOF: failed to parse memory resource\n", node); 2300444638cSArd Biesheuvel return -ENXIO; 231706cffc1SArd Biesheuvel } 232706cffc1SArd Biesheuvel 2330444638cSArd Biesheuvel data = exiu_init(of_node_to_fwnode(node), &res); 2340444638cSArd Biesheuvel if (IS_ERR(data)) 2350444638cSArd Biesheuvel return PTR_ERR(data); 236706cffc1SArd Biesheuvel 237706cffc1SArd Biesheuvel domain = irq_domain_add_hierarchy(parent_domain, 0, NUM_IRQS, node, 238706cffc1SArd Biesheuvel &exiu_domain_ops, data); 239706cffc1SArd Biesheuvel if (!domain) { 240706cffc1SArd Biesheuvel pr_err("%pOF: failed to allocate domain\n", node); 241706cffc1SArd Biesheuvel goto out_unmap; 242706cffc1SArd Biesheuvel } 243706cffc1SArd Biesheuvel 244706cffc1SArd Biesheuvel pr_info("%pOF: %d interrupts forwarded to %pOF\n", node, NUM_IRQS, 245706cffc1SArd Biesheuvel parent); 246706cffc1SArd Biesheuvel 247706cffc1SArd Biesheuvel return 0; 248706cffc1SArd Biesheuvel 249706cffc1SArd Biesheuvel out_unmap: 250706cffc1SArd Biesheuvel iounmap(data->base); 251706cffc1SArd Biesheuvel kfree(data); 2520444638cSArd Biesheuvel return -ENOMEM; 253706cffc1SArd Biesheuvel } 2540444638cSArd Biesheuvel IRQCHIP_DECLARE(exiu, "socionext,synquacer-exiu", exiu_dt_init); 2553d090a36SArd Biesheuvel 2563d090a36SArd Biesheuvel #ifdef CONFIG_ACPI 2573d090a36SArd Biesheuvel static int exiu_acpi_probe(struct platform_device *pdev) 2583d090a36SArd Biesheuvel { 2593d090a36SArd Biesheuvel struct irq_domain *domain; 2603d090a36SArd Biesheuvel struct exiu_irq_data *data; 2613d090a36SArd Biesheuvel struct resource *res; 2623d090a36SArd Biesheuvel 2633d090a36SArd Biesheuvel res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 2643d090a36SArd Biesheuvel if (!res) { 2653d090a36SArd Biesheuvel dev_err(&pdev->dev, "failed to parse memory resource\n"); 2663d090a36SArd Biesheuvel return -ENXIO; 2673d090a36SArd Biesheuvel } 2683d090a36SArd Biesheuvel 2693d090a36SArd Biesheuvel data = exiu_init(dev_fwnode(&pdev->dev), res); 2703d090a36SArd Biesheuvel if (IS_ERR(data)) 2713d090a36SArd Biesheuvel return PTR_ERR(data); 2723d090a36SArd Biesheuvel 2733d090a36SArd Biesheuvel domain = acpi_irq_create_hierarchy(0, NUM_IRQS, dev_fwnode(&pdev->dev), 2743d090a36SArd Biesheuvel &exiu_domain_ops, data); 2753d090a36SArd Biesheuvel if (!domain) { 2763d090a36SArd Biesheuvel dev_err(&pdev->dev, "failed to create IRQ domain\n"); 2773d090a36SArd Biesheuvel goto out_unmap; 2783d090a36SArd Biesheuvel } 2793d090a36SArd Biesheuvel 2803d090a36SArd Biesheuvel dev_info(&pdev->dev, "%d interrupts forwarded\n", NUM_IRQS); 2813d090a36SArd Biesheuvel 2823d090a36SArd Biesheuvel return 0; 2833d090a36SArd Biesheuvel 2843d090a36SArd Biesheuvel out_unmap: 2853d090a36SArd Biesheuvel iounmap(data->base); 2863d090a36SArd Biesheuvel kfree(data); 2873d090a36SArd Biesheuvel return -ENOMEM; 2883d090a36SArd Biesheuvel } 2893d090a36SArd Biesheuvel 2903d090a36SArd Biesheuvel static const struct acpi_device_id exiu_acpi_ids[] = { 2913d090a36SArd Biesheuvel { "SCX0008" }, 2923d090a36SArd Biesheuvel { /* sentinel */ } 2933d090a36SArd Biesheuvel }; 2943d090a36SArd Biesheuvel MODULE_DEVICE_TABLE(acpi, exiu_acpi_ids); 2953d090a36SArd Biesheuvel 2963d090a36SArd Biesheuvel static struct platform_driver exiu_driver = { 2973d090a36SArd Biesheuvel .driver = { 2983d090a36SArd Biesheuvel .name = "exiu", 2993d090a36SArd Biesheuvel .acpi_match_table = exiu_acpi_ids, 3003d090a36SArd Biesheuvel }, 3013d090a36SArd Biesheuvel .probe = exiu_acpi_probe, 3023d090a36SArd Biesheuvel }; 3033d090a36SArd Biesheuvel builtin_platform_driver(exiu_driver); 3043d090a36SArd Biesheuvel #endif 305