linux/drivers/irqchip/irq-uniphier-aidet.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * Driver for UniPhier AIDET (ARM Interrupt Detector)
   4 *
   5 * Copyright (C) 2017 Socionext Inc.
   6 *   Author: Masahiro Yamada <yamada.masahiro@socionext.com>
   7 */
   8
   9#include <linux/bitops.h>
  10#include <linux/init.h>
  11#include <linux/irq.h>
  12#include <linux/irqdomain.h>
  13#include <linux/kernel.h>
  14#include <linux/of.h>
  15#include <linux/of_device.h>
  16#include <linux/of_irq.h>
  17#include <linux/platform_device.h>
  18#include <linux/spinlock.h>
  19
  20#define UNIPHIER_AIDET_NR_IRQS          256
  21
  22#define UNIPHIER_AIDET_DETCONF          0x04    /* inverter register base */
  23
  24struct uniphier_aidet_priv {
  25        struct irq_domain *domain;
  26        void __iomem *reg_base;
  27        spinlock_t lock;
  28        u32 saved_vals[UNIPHIER_AIDET_NR_IRQS / 32];
  29};
  30
  31static void uniphier_aidet_reg_update(struct uniphier_aidet_priv *priv,
  32                                      unsigned int reg, u32 mask, u32 val)
  33{
  34        unsigned long flags;
  35        u32 tmp;
  36
  37        spin_lock_irqsave(&priv->lock, flags);
  38        tmp = readl_relaxed(priv->reg_base + reg);
  39        tmp &= ~mask;
  40        tmp |= mask & val;
  41        writel_relaxed(tmp, priv->reg_base + reg);
  42        spin_unlock_irqrestore(&priv->lock, flags);
  43}
  44
  45static void uniphier_aidet_detconf_update(struct uniphier_aidet_priv *priv,
  46                                          unsigned long index, unsigned int val)
  47{
  48        unsigned int reg;
  49        u32 mask;
  50
  51        reg = UNIPHIER_AIDET_DETCONF + index / 32 * 4;
  52        mask = BIT(index % 32);
  53
  54        uniphier_aidet_reg_update(priv, reg, mask, val ? mask : 0);
  55}
  56
  57static int uniphier_aidet_irq_set_type(struct irq_data *data, unsigned int type)
  58{
  59        struct uniphier_aidet_priv *priv = data->chip_data;
  60        unsigned int val;
  61
  62        /* enable inverter for active low triggers */
  63        switch (type) {
  64        case IRQ_TYPE_EDGE_RISING:
  65        case IRQ_TYPE_LEVEL_HIGH:
  66                val = 0;
  67                break;
  68        case IRQ_TYPE_EDGE_FALLING:
  69                val = 1;
  70                type = IRQ_TYPE_EDGE_RISING;
  71                break;
  72        case IRQ_TYPE_LEVEL_LOW:
  73                val = 1;
  74                type = IRQ_TYPE_LEVEL_HIGH;
  75                break;
  76        default:
  77                return -EINVAL;
  78        }
  79
  80        uniphier_aidet_detconf_update(priv, data->hwirq, val);
  81
  82        return irq_chip_set_type_parent(data, type);
  83}
  84
  85static struct irq_chip uniphier_aidet_irq_chip = {
  86        .name = "AIDET",
  87        .irq_mask = irq_chip_mask_parent,
  88        .irq_unmask = irq_chip_unmask_parent,
  89        .irq_eoi = irq_chip_eoi_parent,
  90        .irq_set_affinity = irq_chip_set_affinity_parent,
  91        .irq_set_type = uniphier_aidet_irq_set_type,
  92};
  93
  94static int uniphier_aidet_domain_translate(struct irq_domain *domain,
  95                                           struct irq_fwspec *fwspec,
  96                                           unsigned long *out_hwirq,
  97                                           unsigned int *out_type)
  98{
  99        if (WARN_ON(fwspec->param_count < 2))
 100                return -EINVAL;
 101
 102        *out_hwirq = fwspec->param[0];
 103        *out_type = fwspec->param[1] & IRQ_TYPE_SENSE_MASK;
 104
 105        return 0;
 106}
 107
 108static int uniphier_aidet_domain_alloc(struct irq_domain *domain,
 109                                       unsigned int virq, unsigned int nr_irqs,
 110                                       void *arg)
 111{
 112        struct irq_fwspec parent_fwspec;
 113        irq_hw_number_t hwirq;
 114        unsigned int type;
 115        int ret;
 116
 117        if (nr_irqs != 1)
 118                return -EINVAL;
 119
 120        ret = uniphier_aidet_domain_translate(domain, arg, &hwirq, &type);
 121        if (ret)
 122                return ret;
 123
 124        switch (type) {
 125        case IRQ_TYPE_EDGE_RISING:
 126        case IRQ_TYPE_LEVEL_HIGH:
 127                break;
 128        case IRQ_TYPE_EDGE_FALLING:
 129                type = IRQ_TYPE_EDGE_RISING;
 130                break;
 131        case IRQ_TYPE_LEVEL_LOW:
 132                type = IRQ_TYPE_LEVEL_HIGH;
 133                break;
 134        default:
 135                return -EINVAL;
 136        }
 137
 138        if (hwirq >= UNIPHIER_AIDET_NR_IRQS)
 139                return -ENXIO;
 140
 141        ret = irq_domain_set_hwirq_and_chip(domain, virq, hwirq,
 142                                            &uniphier_aidet_irq_chip,
 143                                            domain->host_data);
 144        if (ret)
 145                return ret;
 146
 147        /* parent is GIC */
 148        parent_fwspec.fwnode = domain->parent->fwnode;
 149        parent_fwspec.param_count = 3;
 150        parent_fwspec.param[0] = 0;             /* SPI */
 151        parent_fwspec.param[1] = hwirq;
 152        parent_fwspec.param[2] = type;
 153
 154        return irq_domain_alloc_irqs_parent(domain, virq, 1, &parent_fwspec);
 155}
 156
 157static const struct irq_domain_ops uniphier_aidet_domain_ops = {
 158        .alloc = uniphier_aidet_domain_alloc,
 159        .free = irq_domain_free_irqs_common,
 160        .translate = uniphier_aidet_domain_translate,
 161};
 162
 163static int uniphier_aidet_probe(struct platform_device *pdev)
 164{
 165        struct device *dev = &pdev->dev;
 166        struct device_node *parent_np;
 167        struct irq_domain *parent_domain;
 168        struct uniphier_aidet_priv *priv;
 169
 170        parent_np = of_irq_find_parent(dev->of_node);
 171        if (!parent_np)
 172                return -ENXIO;
 173
 174        parent_domain = irq_find_host(parent_np);
 175        of_node_put(parent_np);
 176        if (!parent_domain)
 177                return -EPROBE_DEFER;
 178
 179        priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
 180        if (!priv)
 181                return -ENOMEM;
 182
 183        priv->reg_base = devm_platform_ioremap_resource(pdev, 0);
 184        if (IS_ERR(priv->reg_base))
 185                return PTR_ERR(priv->reg_base);
 186
 187        spin_lock_init(&priv->lock);
 188
 189        priv->domain = irq_domain_create_hierarchy(
 190                                        parent_domain, 0,
 191                                        UNIPHIER_AIDET_NR_IRQS,
 192                                        of_node_to_fwnode(dev->of_node),
 193                                        &uniphier_aidet_domain_ops, priv);
 194        if (!priv->domain)
 195                return -ENOMEM;
 196
 197        platform_set_drvdata(pdev, priv);
 198
 199        return 0;
 200}
 201
 202static int __maybe_unused uniphier_aidet_suspend(struct device *dev)
 203{
 204        struct uniphier_aidet_priv *priv = dev_get_drvdata(dev);
 205        int i;
 206
 207        for (i = 0; i < ARRAY_SIZE(priv->saved_vals); i++)
 208                priv->saved_vals[i] = readl_relaxed(
 209                        priv->reg_base + UNIPHIER_AIDET_DETCONF + i * 4);
 210
 211        return 0;
 212}
 213
 214static int __maybe_unused uniphier_aidet_resume(struct device *dev)
 215{
 216        struct uniphier_aidet_priv *priv = dev_get_drvdata(dev);
 217        int i;
 218
 219        for (i = 0; i < ARRAY_SIZE(priv->saved_vals); i++)
 220                writel_relaxed(priv->saved_vals[i],
 221                               priv->reg_base + UNIPHIER_AIDET_DETCONF + i * 4);
 222
 223        return 0;
 224}
 225
 226static const struct dev_pm_ops uniphier_aidet_pm_ops = {
 227        SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(uniphier_aidet_suspend,
 228                                      uniphier_aidet_resume)
 229};
 230
 231static const struct of_device_id uniphier_aidet_match[] = {
 232        { .compatible = "socionext,uniphier-ld4-aidet" },
 233        { .compatible = "socionext,uniphier-pro4-aidet" },
 234        { .compatible = "socionext,uniphier-sld8-aidet" },
 235        { .compatible = "socionext,uniphier-pro5-aidet" },
 236        { .compatible = "socionext,uniphier-pxs2-aidet" },
 237        { .compatible = "socionext,uniphier-ld11-aidet" },
 238        { .compatible = "socionext,uniphier-ld20-aidet" },
 239        { .compatible = "socionext,uniphier-pxs3-aidet" },
 240        { /* sentinel */ }
 241};
 242
 243static struct platform_driver uniphier_aidet_driver = {
 244        .probe = uniphier_aidet_probe,
 245        .driver = {
 246                .name = "uniphier-aidet",
 247                .of_match_table = uniphier_aidet_match,
 248                .pm = &uniphier_aidet_pm_ops,
 249        },
 250};
 251builtin_platform_driver(uniphier_aidet_driver);
 252