linux/drivers/irqchip/irq-sni-exiu.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * Driver for Socionext External Interrupt Unit (EXIU)
   4 *
   5 * Copyright (c) 2017-2019 Linaro, Ltd. <ard.biesheuvel@linaro.org>
   6 *
   7 * Based on irq-tegra.c:
   8 *   Copyright (C) 2011 Google, Inc.
   9 *   Copyright (C) 2010,2013, NVIDIA Corporation
  10 */
  11
  12#include <linux/interrupt.h>
  13#include <linux/io.h>
  14#include <linux/irq.h>
  15#include <linux/irqchip.h>
  16#include <linux/irqdomain.h>
  17#include <linux/of.h>
  18#include <linux/of_address.h>
  19#include <linux/of_irq.h>
  20#include <linux/platform_device.h>
  21
  22#include <dt-bindings/interrupt-controller/arm-gic.h>
  23
  24#define NUM_IRQS        32
  25
  26#define EIMASK          0x00
  27#define EISRCSEL        0x04
  28#define EIREQSTA        0x08
  29#define EIRAWREQSTA     0x0C
  30#define EIREQCLR        0x10
  31#define EILVL           0x14
  32#define EIEDG           0x18
  33#define EISIR           0x1C
  34
  35struct exiu_irq_data {
  36        void __iomem    *base;
  37        u32             spi_base;
  38};
  39
  40static void exiu_irq_eoi(struct irq_data *d)
  41{
  42        struct exiu_irq_data *data = irq_data_get_irq_chip_data(d);
  43
  44        writel(BIT(d->hwirq), data->base + EIREQCLR);
  45        irq_chip_eoi_parent(d);
  46}
  47
  48static void exiu_irq_mask(struct irq_data *d)
  49{
  50        struct exiu_irq_data *data = irq_data_get_irq_chip_data(d);
  51        u32 val;
  52
  53        val = readl_relaxed(data->base + EIMASK) | BIT(d->hwirq);
  54        writel_relaxed(val, data->base + EIMASK);
  55        irq_chip_mask_parent(d);
  56}
  57
  58static void exiu_irq_unmask(struct irq_data *d)
  59{
  60        struct exiu_irq_data *data = irq_data_get_irq_chip_data(d);
  61        u32 val;
  62
  63        val = readl_relaxed(data->base + EIMASK) & ~BIT(d->hwirq);
  64        writel_relaxed(val, data->base + EIMASK);
  65        irq_chip_unmask_parent(d);
  66}
  67
  68static void exiu_irq_enable(struct irq_data *d)
  69{
  70        struct exiu_irq_data *data = irq_data_get_irq_chip_data(d);
  71        u32 val;
  72
  73        /* clear interrupts that were latched while disabled */
  74        writel_relaxed(BIT(d->hwirq), data->base + EIREQCLR);
  75
  76        val = readl_relaxed(data->base + EIMASK) & ~BIT(d->hwirq);
  77        writel_relaxed(val, data->base + EIMASK);
  78        irq_chip_enable_parent(d);
  79}
  80
  81static int exiu_irq_set_type(struct irq_data *d, unsigned int type)
  82{
  83        struct exiu_irq_data *data = irq_data_get_irq_chip_data(d);
  84        u32 val;
  85
  86        val = readl_relaxed(data->base + EILVL);
  87        if (type == IRQ_TYPE_EDGE_RISING || type == IRQ_TYPE_LEVEL_HIGH)
  88                val |= BIT(d->hwirq);
  89        else
  90                val &= ~BIT(d->hwirq);
  91        writel_relaxed(val, data->base + EILVL);
  92
  93        val = readl_relaxed(data->base + EIEDG);
  94        if (type == IRQ_TYPE_LEVEL_LOW || type == IRQ_TYPE_LEVEL_HIGH)
  95                val &= ~BIT(d->hwirq);
  96        else
  97                val |= BIT(d->hwirq);
  98        writel_relaxed(val, data->base + EIEDG);
  99
 100        writel_relaxed(BIT(d->hwirq), data->base + EIREQCLR);
 101
 102        return irq_chip_set_type_parent(d, IRQ_TYPE_LEVEL_HIGH);
 103}
 104
 105static struct irq_chip exiu_irq_chip = {
 106        .name                   = "EXIU",
 107        .irq_eoi                = exiu_irq_eoi,
 108        .irq_enable             = exiu_irq_enable,
 109        .irq_mask               = exiu_irq_mask,
 110        .irq_unmask             = exiu_irq_unmask,
 111        .irq_set_type           = exiu_irq_set_type,
 112        .irq_set_affinity       = irq_chip_set_affinity_parent,
 113        .flags                  = IRQCHIP_SET_TYPE_MASKED |
 114                                  IRQCHIP_SKIP_SET_WAKE |
 115                                  IRQCHIP_EOI_THREADED |
 116                                  IRQCHIP_MASK_ON_SUSPEND,
 117};
 118
 119static int exiu_domain_translate(struct irq_domain *domain,
 120                                 struct irq_fwspec *fwspec,
 121                                 unsigned long *hwirq,
 122                                 unsigned int *type)
 123{
 124        struct exiu_irq_data *info = domain->host_data;
 125
 126        if (is_of_node(fwspec->fwnode)) {
 127                if (fwspec->param_count != 3)
 128                        return -EINVAL;
 129
 130                if (fwspec->param[0] != GIC_SPI)
 131                        return -EINVAL; /* No PPI should point to this domain */
 132
 133                *hwirq = fwspec->param[1] - info->spi_base;
 134                *type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK;
 135        } else {
 136                if (fwspec->param_count != 2)
 137                        return -EINVAL;
 138                *hwirq = fwspec->param[0];
 139                *type = fwspec->param[1] & IRQ_TYPE_SENSE_MASK;
 140        }
 141        return 0;
 142}
 143
 144static int exiu_domain_alloc(struct irq_domain *dom, unsigned int virq,
 145                             unsigned int nr_irqs, void *data)
 146{
 147        struct irq_fwspec *fwspec = data;
 148        struct irq_fwspec parent_fwspec;
 149        struct exiu_irq_data *info = dom->host_data;
 150        irq_hw_number_t hwirq;
 151
 152        parent_fwspec = *fwspec;
 153        if (is_of_node(dom->parent->fwnode)) {
 154                if (fwspec->param_count != 3)
 155                        return -EINVAL; /* Not GIC compliant */
 156                if (fwspec->param[0] != GIC_SPI)
 157                        return -EINVAL; /* No PPI should point to this domain */
 158
 159                hwirq = fwspec->param[1] - info->spi_base;
 160        } else {
 161                hwirq = fwspec->param[0];
 162                parent_fwspec.param[0] = hwirq + info->spi_base + 32;
 163        }
 164        WARN_ON(nr_irqs != 1);
 165        irq_domain_set_hwirq_and_chip(dom, virq, hwirq, &exiu_irq_chip, info);
 166
 167        parent_fwspec.fwnode = dom->parent->fwnode;
 168        return irq_domain_alloc_irqs_parent(dom, virq, nr_irqs, &parent_fwspec);
 169}
 170
 171static const struct irq_domain_ops exiu_domain_ops = {
 172        .translate      = exiu_domain_translate,
 173        .alloc          = exiu_domain_alloc,
 174        .free           = irq_domain_free_irqs_common,
 175};
 176
 177static struct exiu_irq_data *exiu_init(const struct fwnode_handle *fwnode,
 178                                       struct resource *res)
 179{
 180        struct exiu_irq_data *data;
 181        int err;
 182
 183        data = kzalloc(sizeof(*data), GFP_KERNEL);
 184        if (!data)
 185                return ERR_PTR(-ENOMEM);
 186
 187        if (fwnode_property_read_u32_array(fwnode, "socionext,spi-base",
 188                                           &data->spi_base, 1)) {
 189                err = -ENODEV;
 190                goto out_free;
 191        }
 192
 193        data->base = ioremap(res->start, resource_size(res));
 194        if (!data->base) {
 195                err = -ENODEV;
 196                goto out_free;
 197        }
 198
 199        /* clear and mask all interrupts */
 200        writel_relaxed(0xFFFFFFFF, data->base + EIREQCLR);
 201        writel_relaxed(0xFFFFFFFF, data->base + EIMASK);
 202
 203        return data;
 204
 205out_free:
 206        kfree(data);
 207        return ERR_PTR(err);
 208}
 209
 210static int __init exiu_dt_init(struct device_node *node,
 211                               struct device_node *parent)
 212{
 213        struct irq_domain *parent_domain, *domain;
 214        struct exiu_irq_data *data;
 215        struct resource res;
 216
 217        if (!parent) {
 218                pr_err("%pOF: no parent, giving up\n", node);
 219                return -ENODEV;
 220        }
 221
 222        parent_domain = irq_find_host(parent);
 223        if (!parent_domain) {
 224                pr_err("%pOF: unable to obtain parent domain\n", node);
 225                return -ENXIO;
 226        }
 227
 228        if (of_address_to_resource(node, 0, &res)) {
 229                pr_err("%pOF: failed to parse memory resource\n", node);
 230                return -ENXIO;
 231        }
 232
 233        data = exiu_init(of_node_to_fwnode(node), &res);
 234        if (IS_ERR(data))
 235                return PTR_ERR(data);
 236
 237        domain = irq_domain_add_hierarchy(parent_domain, 0, NUM_IRQS, node,
 238                                          &exiu_domain_ops, data);
 239        if (!domain) {
 240                pr_err("%pOF: failed to allocate domain\n", node);
 241                goto out_unmap;
 242        }
 243
 244        pr_info("%pOF: %d interrupts forwarded to %pOF\n", node, NUM_IRQS,
 245                parent);
 246
 247        return 0;
 248
 249out_unmap:
 250        iounmap(data->base);
 251        kfree(data);
 252        return -ENOMEM;
 253}
 254IRQCHIP_DECLARE(exiu, "socionext,synquacer-exiu", exiu_dt_init);
 255
 256#ifdef CONFIG_ACPI
 257static int exiu_acpi_probe(struct platform_device *pdev)
 258{
 259        struct irq_domain *domain;
 260        struct exiu_irq_data *data;
 261        struct resource *res;
 262
 263        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 264        if (!res) {
 265                dev_err(&pdev->dev, "failed to parse memory resource\n");
 266                return -ENXIO;
 267        }
 268
 269        data = exiu_init(dev_fwnode(&pdev->dev), res);
 270        if (IS_ERR(data))
 271                return PTR_ERR(data);
 272
 273        domain = acpi_irq_create_hierarchy(0, NUM_IRQS, dev_fwnode(&pdev->dev),
 274                                           &exiu_domain_ops, data);
 275        if (!domain) {
 276                dev_err(&pdev->dev, "failed to create IRQ domain\n");
 277                goto out_unmap;
 278        }
 279
 280        dev_info(&pdev->dev, "%d interrupts forwarded\n", NUM_IRQS);
 281
 282        return 0;
 283
 284out_unmap:
 285        iounmap(data->base);
 286        kfree(data);
 287        return -ENOMEM;
 288}
 289
 290static const struct acpi_device_id exiu_acpi_ids[] = {
 291        { "SCX0008" },
 292        { /* sentinel */ }
 293};
 294MODULE_DEVICE_TABLE(acpi, exiu_acpi_ids);
 295
 296static struct platform_driver exiu_driver = {
 297        .driver = {
 298                .name = "exiu",
 299                .acpi_match_table = exiu_acpi_ids,
 300        },
 301        .probe = exiu_acpi_probe,
 302};
 303builtin_platform_driver(exiu_driver);
 304#endif
 305