1706cffc1SArd Biesheuvel /* 2706cffc1SArd Biesheuvel * Driver for Socionext External Interrupt Unit (EXIU) 3706cffc1SArd Biesheuvel * 40444638cSArd Biesheuvel * Copyright (c) 2017-2019 Linaro, Ltd. <ard.biesheuvel@linaro.org> 5706cffc1SArd Biesheuvel * 6706cffc1SArd Biesheuvel * Based on irq-tegra.c: 7706cffc1SArd Biesheuvel * Copyright (C) 2011 Google, Inc. 8706cffc1SArd Biesheuvel * Copyright (C) 2010,2013, NVIDIA Corporation 9706cffc1SArd Biesheuvel * 10706cffc1SArd Biesheuvel * This program is free software; you can redistribute it and/or modify 11706cffc1SArd Biesheuvel * it under the terms of the GNU General Public License version 2 as 12706cffc1SArd Biesheuvel * published by the Free Software Foundation. 13706cffc1SArd Biesheuvel */ 14706cffc1SArd Biesheuvel 15706cffc1SArd Biesheuvel #include <linux/interrupt.h> 16706cffc1SArd Biesheuvel #include <linux/io.h> 17706cffc1SArd Biesheuvel #include <linux/irq.h> 18706cffc1SArd Biesheuvel #include <linux/irqchip.h> 19706cffc1SArd Biesheuvel #include <linux/irqdomain.h> 20706cffc1SArd Biesheuvel #include <linux/of.h> 21706cffc1SArd Biesheuvel #include <linux/of_address.h> 22706cffc1SArd Biesheuvel #include <linux/of_irq.h> 23*3d090a36SArd Biesheuvel #include <linux/platform_device.h> 24706cffc1SArd Biesheuvel 25706cffc1SArd Biesheuvel #include <dt-bindings/interrupt-controller/arm-gic.h> 26706cffc1SArd Biesheuvel 27706cffc1SArd Biesheuvel #define NUM_IRQS 32 28706cffc1SArd Biesheuvel 29706cffc1SArd Biesheuvel #define EIMASK 0x00 30706cffc1SArd Biesheuvel #define EISRCSEL 0x04 31706cffc1SArd Biesheuvel #define EIREQSTA 0x08 32706cffc1SArd Biesheuvel #define EIRAWREQSTA 0x0C 33706cffc1SArd Biesheuvel #define EIREQCLR 0x10 34706cffc1SArd Biesheuvel #define EILVL 0x14 35706cffc1SArd Biesheuvel #define EIEDG 0x18 36706cffc1SArd Biesheuvel #define EISIR 0x1C 37706cffc1SArd Biesheuvel 38706cffc1SArd Biesheuvel struct exiu_irq_data { 39706cffc1SArd Biesheuvel void __iomem *base; 40706cffc1SArd Biesheuvel u32 spi_base; 41706cffc1SArd Biesheuvel }; 42706cffc1SArd Biesheuvel 43706cffc1SArd Biesheuvel static void exiu_irq_eoi(struct irq_data *d) 44706cffc1SArd Biesheuvel { 45706cffc1SArd Biesheuvel struct exiu_irq_data *data = irq_data_get_irq_chip_data(d); 46706cffc1SArd Biesheuvel 47706cffc1SArd Biesheuvel writel(BIT(d->hwirq), data->base + EIREQCLR); 48706cffc1SArd Biesheuvel irq_chip_eoi_parent(d); 49706cffc1SArd Biesheuvel } 50706cffc1SArd Biesheuvel 51706cffc1SArd Biesheuvel static void exiu_irq_mask(struct irq_data *d) 52706cffc1SArd Biesheuvel { 53706cffc1SArd Biesheuvel struct exiu_irq_data *data = irq_data_get_irq_chip_data(d); 54706cffc1SArd Biesheuvel u32 val; 55706cffc1SArd Biesheuvel 56706cffc1SArd Biesheuvel val = readl_relaxed(data->base + EIMASK) | BIT(d->hwirq); 57706cffc1SArd Biesheuvel writel_relaxed(val, data->base + EIMASK); 58706cffc1SArd Biesheuvel irq_chip_mask_parent(d); 59706cffc1SArd Biesheuvel } 60706cffc1SArd Biesheuvel 61706cffc1SArd Biesheuvel static void exiu_irq_unmask(struct irq_data *d) 62706cffc1SArd Biesheuvel { 63706cffc1SArd Biesheuvel struct exiu_irq_data *data = irq_data_get_irq_chip_data(d); 64706cffc1SArd Biesheuvel u32 val; 65706cffc1SArd Biesheuvel 66706cffc1SArd Biesheuvel val = readl_relaxed(data->base + EIMASK) & ~BIT(d->hwirq); 67706cffc1SArd Biesheuvel writel_relaxed(val, data->base + EIMASK); 68706cffc1SArd Biesheuvel irq_chip_unmask_parent(d); 69706cffc1SArd Biesheuvel } 70706cffc1SArd Biesheuvel 71706cffc1SArd Biesheuvel static void exiu_irq_enable(struct irq_data *d) 72706cffc1SArd Biesheuvel { 73706cffc1SArd Biesheuvel struct exiu_irq_data *data = irq_data_get_irq_chip_data(d); 74706cffc1SArd Biesheuvel u32 val; 75706cffc1SArd Biesheuvel 76706cffc1SArd Biesheuvel /* clear interrupts that were latched while disabled */ 77706cffc1SArd Biesheuvel writel_relaxed(BIT(d->hwirq), data->base + EIREQCLR); 78706cffc1SArd Biesheuvel 79706cffc1SArd Biesheuvel val = readl_relaxed(data->base + EIMASK) & ~BIT(d->hwirq); 80706cffc1SArd Biesheuvel writel_relaxed(val, data->base + EIMASK); 81706cffc1SArd Biesheuvel irq_chip_enable_parent(d); 82706cffc1SArd Biesheuvel } 83706cffc1SArd Biesheuvel 84706cffc1SArd Biesheuvel static int exiu_irq_set_type(struct irq_data *d, unsigned int type) 85706cffc1SArd Biesheuvel { 86706cffc1SArd Biesheuvel struct exiu_irq_data *data = irq_data_get_irq_chip_data(d); 87706cffc1SArd Biesheuvel u32 val; 88706cffc1SArd Biesheuvel 89706cffc1SArd Biesheuvel val = readl_relaxed(data->base + EILVL); 90706cffc1SArd Biesheuvel if (type == IRQ_TYPE_EDGE_RISING || type == IRQ_TYPE_LEVEL_HIGH) 91706cffc1SArd Biesheuvel val |= BIT(d->hwirq); 92706cffc1SArd Biesheuvel else 93706cffc1SArd Biesheuvel val &= ~BIT(d->hwirq); 94706cffc1SArd Biesheuvel writel_relaxed(val, data->base + EILVL); 95706cffc1SArd Biesheuvel 96706cffc1SArd Biesheuvel val = readl_relaxed(data->base + EIEDG); 97706cffc1SArd Biesheuvel if (type == IRQ_TYPE_LEVEL_LOW || type == IRQ_TYPE_LEVEL_HIGH) 98706cffc1SArd Biesheuvel val &= ~BIT(d->hwirq); 99706cffc1SArd Biesheuvel else 100706cffc1SArd Biesheuvel val |= BIT(d->hwirq); 101706cffc1SArd Biesheuvel writel_relaxed(val, data->base + EIEDG); 102706cffc1SArd Biesheuvel 103706cffc1SArd Biesheuvel writel_relaxed(BIT(d->hwirq), data->base + EIREQCLR); 104706cffc1SArd Biesheuvel 105706cffc1SArd Biesheuvel return irq_chip_set_type_parent(d, IRQ_TYPE_LEVEL_HIGH); 106706cffc1SArd Biesheuvel } 107706cffc1SArd Biesheuvel 108706cffc1SArd Biesheuvel static struct irq_chip exiu_irq_chip = { 109706cffc1SArd Biesheuvel .name = "EXIU", 110706cffc1SArd Biesheuvel .irq_eoi = exiu_irq_eoi, 111706cffc1SArd Biesheuvel .irq_enable = exiu_irq_enable, 112706cffc1SArd Biesheuvel .irq_mask = exiu_irq_mask, 113706cffc1SArd Biesheuvel .irq_unmask = exiu_irq_unmask, 114706cffc1SArd Biesheuvel .irq_set_type = exiu_irq_set_type, 115706cffc1SArd Biesheuvel .irq_set_affinity = irq_chip_set_affinity_parent, 116706cffc1SArd Biesheuvel .flags = IRQCHIP_SET_TYPE_MASKED | 117706cffc1SArd Biesheuvel IRQCHIP_SKIP_SET_WAKE | 118706cffc1SArd Biesheuvel IRQCHIP_EOI_THREADED | 119706cffc1SArd Biesheuvel IRQCHIP_MASK_ON_SUSPEND, 120706cffc1SArd Biesheuvel }; 121706cffc1SArd Biesheuvel 122706cffc1SArd Biesheuvel static int exiu_domain_translate(struct irq_domain *domain, 123706cffc1SArd Biesheuvel struct irq_fwspec *fwspec, 124706cffc1SArd Biesheuvel unsigned long *hwirq, 125706cffc1SArd Biesheuvel unsigned int *type) 126706cffc1SArd Biesheuvel { 127706cffc1SArd Biesheuvel struct exiu_irq_data *info = domain->host_data; 128706cffc1SArd Biesheuvel 129706cffc1SArd Biesheuvel if (is_of_node(fwspec->fwnode)) { 130706cffc1SArd Biesheuvel if (fwspec->param_count != 3) 131706cffc1SArd Biesheuvel return -EINVAL; 132706cffc1SArd Biesheuvel 133706cffc1SArd Biesheuvel if (fwspec->param[0] != GIC_SPI) 134706cffc1SArd Biesheuvel return -EINVAL; /* No PPI should point to this domain */ 135706cffc1SArd Biesheuvel 136706cffc1SArd Biesheuvel *hwirq = fwspec->param[1] - info->spi_base; 137706cffc1SArd Biesheuvel *type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK; 138*3d090a36SArd Biesheuvel } else { 139*3d090a36SArd Biesheuvel if (fwspec->param_count != 2) 140706cffc1SArd Biesheuvel return -EINVAL; 141*3d090a36SArd Biesheuvel *hwirq = fwspec->param[0]; 142*3d090a36SArd Biesheuvel *type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK; 143*3d090a36SArd Biesheuvel } 144*3d090a36SArd Biesheuvel return 0; 145706cffc1SArd Biesheuvel } 146706cffc1SArd Biesheuvel 147706cffc1SArd Biesheuvel static int exiu_domain_alloc(struct irq_domain *dom, unsigned int virq, 148706cffc1SArd Biesheuvel unsigned int nr_irqs, void *data) 149706cffc1SArd Biesheuvel { 150706cffc1SArd Biesheuvel struct irq_fwspec *fwspec = data; 151706cffc1SArd Biesheuvel struct irq_fwspec parent_fwspec; 152706cffc1SArd Biesheuvel struct exiu_irq_data *info = dom->host_data; 153706cffc1SArd Biesheuvel irq_hw_number_t hwirq; 154706cffc1SArd Biesheuvel 155*3d090a36SArd Biesheuvel parent_fwspec = *fwspec; 156*3d090a36SArd Biesheuvel if (is_of_node(dom->parent->fwnode)) { 157706cffc1SArd Biesheuvel if (fwspec->param_count != 3) 158706cffc1SArd Biesheuvel return -EINVAL; /* Not GIC compliant */ 159706cffc1SArd Biesheuvel if (fwspec->param[0] != GIC_SPI) 160706cffc1SArd Biesheuvel return -EINVAL; /* No PPI should point to this domain */ 161706cffc1SArd Biesheuvel 162706cffc1SArd Biesheuvel hwirq = fwspec->param[1] - info->spi_base; 163*3d090a36SArd Biesheuvel } else { 164*3d090a36SArd Biesheuvel hwirq = fwspec->param[0]; 165*3d090a36SArd Biesheuvel parent_fwspec.param[0] = hwirq + info->spi_base + 32; 166*3d090a36SArd Biesheuvel } 167*3d090a36SArd Biesheuvel WARN_ON(nr_irqs != 1); 168706cffc1SArd Biesheuvel irq_domain_set_hwirq_and_chip(dom, virq, hwirq, &exiu_irq_chip, info); 169706cffc1SArd Biesheuvel 170706cffc1SArd Biesheuvel parent_fwspec.fwnode = dom->parent->fwnode; 171706cffc1SArd Biesheuvel return irq_domain_alloc_irqs_parent(dom, virq, nr_irqs, &parent_fwspec); 172706cffc1SArd Biesheuvel } 173706cffc1SArd Biesheuvel 174706cffc1SArd Biesheuvel static const struct irq_domain_ops exiu_domain_ops = { 175706cffc1SArd Biesheuvel .translate = exiu_domain_translate, 176706cffc1SArd Biesheuvel .alloc = exiu_domain_alloc, 177706cffc1SArd Biesheuvel .free = irq_domain_free_irqs_common, 178706cffc1SArd Biesheuvel }; 179706cffc1SArd Biesheuvel 1800444638cSArd Biesheuvel static struct exiu_irq_data *exiu_init(const struct fwnode_handle *fwnode, 1810444638cSArd Biesheuvel struct resource *res) 1820444638cSArd Biesheuvel { 1830444638cSArd Biesheuvel struct exiu_irq_data *data; 1840444638cSArd Biesheuvel int err; 1850444638cSArd Biesheuvel 1860444638cSArd Biesheuvel data = kzalloc(sizeof(*data), GFP_KERNEL); 1870444638cSArd Biesheuvel if (!data) 1880444638cSArd Biesheuvel return ERR_PTR(-ENOMEM); 1890444638cSArd Biesheuvel 1900444638cSArd Biesheuvel if (fwnode_property_read_u32_array(fwnode, "socionext,spi-base", 1910444638cSArd Biesheuvel &data->spi_base, 1)) { 1920444638cSArd Biesheuvel err = -ENODEV; 1930444638cSArd Biesheuvel goto out_free; 1940444638cSArd Biesheuvel } 1950444638cSArd Biesheuvel 1960444638cSArd Biesheuvel data->base = ioremap(res->start, resource_size(res)); 1970444638cSArd Biesheuvel if (!data->base) { 1980444638cSArd Biesheuvel err = -ENODEV; 1990444638cSArd Biesheuvel goto out_free; 2000444638cSArd Biesheuvel } 2010444638cSArd Biesheuvel 2020444638cSArd Biesheuvel /* clear and mask all interrupts */ 2030444638cSArd Biesheuvel writel_relaxed(0xFFFFFFFF, data->base + EIREQCLR); 2040444638cSArd Biesheuvel writel_relaxed(0xFFFFFFFF, data->base + EIMASK); 2050444638cSArd Biesheuvel 2060444638cSArd Biesheuvel return data; 2070444638cSArd Biesheuvel 2080444638cSArd Biesheuvel out_free: 2090444638cSArd Biesheuvel kfree(data); 2100444638cSArd Biesheuvel return ERR_PTR(err); 2110444638cSArd Biesheuvel } 2120444638cSArd Biesheuvel 2130444638cSArd Biesheuvel static int __init exiu_dt_init(struct device_node *node, 214706cffc1SArd Biesheuvel struct device_node *parent) 215706cffc1SArd Biesheuvel { 216706cffc1SArd Biesheuvel struct irq_domain *parent_domain, *domain; 217706cffc1SArd Biesheuvel struct exiu_irq_data *data; 2180444638cSArd Biesheuvel struct resource res; 219706cffc1SArd Biesheuvel 220706cffc1SArd Biesheuvel if (!parent) { 221706cffc1SArd Biesheuvel pr_err("%pOF: no parent, giving up\n", node); 222706cffc1SArd Biesheuvel return -ENODEV; 223706cffc1SArd Biesheuvel } 224706cffc1SArd Biesheuvel 225706cffc1SArd Biesheuvel parent_domain = irq_find_host(parent); 226706cffc1SArd Biesheuvel if (!parent_domain) { 227706cffc1SArd Biesheuvel pr_err("%pOF: unable to obtain parent domain\n", node); 228706cffc1SArd Biesheuvel return -ENXIO; 229706cffc1SArd Biesheuvel } 230706cffc1SArd Biesheuvel 2310444638cSArd Biesheuvel if (of_address_to_resource(node, 0, &res)) { 2320444638cSArd Biesheuvel pr_err("%pOF: failed to parse memory resource\n", node); 2330444638cSArd Biesheuvel return -ENXIO; 234706cffc1SArd Biesheuvel } 235706cffc1SArd Biesheuvel 2360444638cSArd Biesheuvel data = exiu_init(of_node_to_fwnode(node), &res); 2370444638cSArd Biesheuvel if (IS_ERR(data)) 2380444638cSArd Biesheuvel return PTR_ERR(data); 239706cffc1SArd Biesheuvel 240706cffc1SArd Biesheuvel domain = irq_domain_add_hierarchy(parent_domain, 0, NUM_IRQS, node, 241706cffc1SArd Biesheuvel &exiu_domain_ops, data); 242706cffc1SArd Biesheuvel if (!domain) { 243706cffc1SArd Biesheuvel pr_err("%pOF: failed to allocate domain\n", node); 244706cffc1SArd Biesheuvel goto out_unmap; 245706cffc1SArd Biesheuvel } 246706cffc1SArd Biesheuvel 247706cffc1SArd Biesheuvel pr_info("%pOF: %d interrupts forwarded to %pOF\n", node, NUM_IRQS, 248706cffc1SArd Biesheuvel parent); 249706cffc1SArd Biesheuvel 250706cffc1SArd Biesheuvel return 0; 251706cffc1SArd Biesheuvel 252706cffc1SArd Biesheuvel out_unmap: 253706cffc1SArd Biesheuvel iounmap(data->base); 254706cffc1SArd Biesheuvel kfree(data); 2550444638cSArd Biesheuvel return -ENOMEM; 256706cffc1SArd Biesheuvel } 2570444638cSArd Biesheuvel IRQCHIP_DECLARE(exiu, "socionext,synquacer-exiu", exiu_dt_init); 258*3d090a36SArd Biesheuvel 259*3d090a36SArd Biesheuvel #ifdef CONFIG_ACPI 260*3d090a36SArd Biesheuvel static int exiu_acpi_probe(struct platform_device *pdev) 261*3d090a36SArd Biesheuvel { 262*3d090a36SArd Biesheuvel struct irq_domain *domain; 263*3d090a36SArd Biesheuvel struct exiu_irq_data *data; 264*3d090a36SArd Biesheuvel struct resource *res; 265*3d090a36SArd Biesheuvel 266*3d090a36SArd Biesheuvel res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 267*3d090a36SArd Biesheuvel if (!res) { 268*3d090a36SArd Biesheuvel dev_err(&pdev->dev, "failed to parse memory resource\n"); 269*3d090a36SArd Biesheuvel return -ENXIO; 270*3d090a36SArd Biesheuvel } 271*3d090a36SArd Biesheuvel 272*3d090a36SArd Biesheuvel data = exiu_init(dev_fwnode(&pdev->dev), res); 273*3d090a36SArd Biesheuvel if (IS_ERR(data)) 274*3d090a36SArd Biesheuvel return PTR_ERR(data); 275*3d090a36SArd Biesheuvel 276*3d090a36SArd Biesheuvel domain = acpi_irq_create_hierarchy(0, NUM_IRQS, dev_fwnode(&pdev->dev), 277*3d090a36SArd Biesheuvel &exiu_domain_ops, data); 278*3d090a36SArd Biesheuvel if (!domain) { 279*3d090a36SArd Biesheuvel dev_err(&pdev->dev, "failed to create IRQ domain\n"); 280*3d090a36SArd Biesheuvel goto out_unmap; 281*3d090a36SArd Biesheuvel } 282*3d090a36SArd Biesheuvel 283*3d090a36SArd Biesheuvel dev_info(&pdev->dev, "%d interrupts forwarded\n", NUM_IRQS); 284*3d090a36SArd Biesheuvel 285*3d090a36SArd Biesheuvel return 0; 286*3d090a36SArd Biesheuvel 287*3d090a36SArd Biesheuvel out_unmap: 288*3d090a36SArd Biesheuvel iounmap(data->base); 289*3d090a36SArd Biesheuvel kfree(data); 290*3d090a36SArd Biesheuvel return -ENOMEM; 291*3d090a36SArd Biesheuvel } 292*3d090a36SArd Biesheuvel 293*3d090a36SArd Biesheuvel static const struct acpi_device_id exiu_acpi_ids[] = { 294*3d090a36SArd Biesheuvel { "SCX0008" }, 295*3d090a36SArd Biesheuvel { /* sentinel */ } 296*3d090a36SArd Biesheuvel }; 297*3d090a36SArd Biesheuvel MODULE_DEVICE_TABLE(acpi, exiu_acpi_ids); 298*3d090a36SArd Biesheuvel 299*3d090a36SArd Biesheuvel static struct platform_driver exiu_driver = { 300*3d090a36SArd Biesheuvel .driver = { 301*3d090a36SArd Biesheuvel .name = "exiu", 302*3d090a36SArd Biesheuvel .acpi_match_table = exiu_acpi_ids, 303*3d090a36SArd Biesheuvel }, 304*3d090a36SArd Biesheuvel .probe = exiu_acpi_probe, 305*3d090a36SArd Biesheuvel }; 306*3d090a36SArd Biesheuvel builtin_platform_driver(exiu_driver); 307*3d090a36SArd Biesheuvel #endif 308