linux/drivers/irqchip/irq-mtk-sysirq.c
<<
>>
Prefs
   1/*
   2 * Copyright (c) 2014 MediaTek Inc.
   3 * Author: Joe.C <yingjoe.chen@mediatek.com>
   4 *
   5 * This program is free software; you can redistribute it and/or modify
   6 * it under the terms of the GNU General Public License version 2 as
   7 * published by the Free Software Foundation.
   8 *
   9 * This program is distributed in the hope that it will be useful,
  10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12 * GNU General Public License for more details.
  13 */
  14
  15#include <linux/irq.h>
  16#include <linux/irqchip.h>
  17#include <linux/irqdomain.h>
  18#include <linux/of.h>
  19#include <linux/of_irq.h>
  20#include <linux/of_address.h>
  21#include <linux/io.h>
  22#include <linux/slab.h>
  23#include <linux/spinlock.h>
  24
  25struct mtk_sysirq_chip_data {
  26        spinlock_t lock;
  27        u32 nr_intpol_bases;
  28        void __iomem **intpol_bases;
  29        u32 *intpol_words;
  30        u8 *intpol_idx;
  31        u16 *which_word;
  32};
  33
  34static int mtk_sysirq_set_type(struct irq_data *data, unsigned int type)
  35{
  36        irq_hw_number_t hwirq = data->hwirq;
  37        struct mtk_sysirq_chip_data *chip_data = data->chip_data;
  38        u8 intpol_idx = chip_data->intpol_idx[hwirq];
  39        void __iomem *base;
  40        u32 offset, reg_index, value;
  41        unsigned long flags;
  42        int ret;
  43
  44        base = chip_data->intpol_bases[intpol_idx];
  45        reg_index = chip_data->which_word[hwirq];
  46        offset = hwirq & 0x1f;
  47
  48        spin_lock_irqsave(&chip_data->lock, flags);
  49        value = readl_relaxed(base + reg_index * 4);
  50        if (type == IRQ_TYPE_LEVEL_LOW || type == IRQ_TYPE_EDGE_FALLING) {
  51                if (type == IRQ_TYPE_LEVEL_LOW)
  52                        type = IRQ_TYPE_LEVEL_HIGH;
  53                else
  54                        type = IRQ_TYPE_EDGE_RISING;
  55                value |= (1 << offset);
  56        } else {
  57                value &= ~(1 << offset);
  58        }
  59
  60        writel_relaxed(value, base + reg_index * 4);
  61
  62        data = data->parent_data;
  63        ret = data->chip->irq_set_type(data, type);
  64        spin_unlock_irqrestore(&chip_data->lock, flags);
  65        return ret;
  66}
  67
  68static struct irq_chip mtk_sysirq_chip = {
  69        .name                   = "MT_SYSIRQ",
  70        .irq_mask               = irq_chip_mask_parent,
  71        .irq_unmask             = irq_chip_unmask_parent,
  72        .irq_eoi                = irq_chip_eoi_parent,
  73        .irq_set_type           = mtk_sysirq_set_type,
  74        .irq_retrigger          = irq_chip_retrigger_hierarchy,
  75        .irq_set_affinity       = irq_chip_set_affinity_parent,
  76};
  77
  78static int mtk_sysirq_domain_translate(struct irq_domain *d,
  79                                       struct irq_fwspec *fwspec,
  80                                       unsigned long *hwirq,
  81                                       unsigned int *type)
  82{
  83        if (is_of_node(fwspec->fwnode)) {
  84                if (fwspec->param_count != 3)
  85                        return -EINVAL;
  86
  87                /* No PPI should point to this domain */
  88                if (fwspec->param[0] != 0)
  89                        return -EINVAL;
  90
  91                *hwirq = fwspec->param[1];
  92                *type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK;
  93                return 0;
  94        }
  95
  96        return -EINVAL;
  97}
  98
  99static int mtk_sysirq_domain_alloc(struct irq_domain *domain, unsigned int virq,
 100                                   unsigned int nr_irqs, void *arg)
 101{
 102        int i;
 103        irq_hw_number_t hwirq;
 104        struct irq_fwspec *fwspec = arg;
 105        struct irq_fwspec gic_fwspec = *fwspec;
 106
 107        if (fwspec->param_count != 3)
 108                return -EINVAL;
 109
 110        /* sysirq doesn't support PPI */
 111        if (fwspec->param[0])
 112                return -EINVAL;
 113
 114        hwirq = fwspec->param[1];
 115        for (i = 0; i < nr_irqs; i++)
 116                irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq + i,
 117                                              &mtk_sysirq_chip,
 118                                              domain->host_data);
 119
 120        gic_fwspec.fwnode = domain->parent->fwnode;
 121        return irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, &gic_fwspec);
 122}
 123
 124static const struct irq_domain_ops sysirq_domain_ops = {
 125        .translate      = mtk_sysirq_domain_translate,
 126        .alloc          = mtk_sysirq_domain_alloc,
 127        .free           = irq_domain_free_irqs_common,
 128};
 129
 130static int __init mtk_sysirq_of_init(struct device_node *node,
 131                                     struct device_node *parent)
 132{
 133        struct irq_domain *domain, *domain_parent;
 134        struct mtk_sysirq_chip_data *chip_data;
 135        int ret, size, intpol_num = 0, nr_intpol_bases = 0, i = 0;
 136
 137        domain_parent = irq_find_host(parent);
 138        if (!domain_parent) {
 139                pr_err("mtk_sysirq: interrupt-parent not found\n");
 140                return -EINVAL;
 141        }
 142
 143        chip_data = kzalloc(sizeof(*chip_data), GFP_KERNEL);
 144        if (!chip_data)
 145                return -ENOMEM;
 146
 147        while (of_get_address(node, i++, NULL, NULL))
 148                nr_intpol_bases++;
 149
 150        if (nr_intpol_bases == 0) {
 151                pr_err("mtk_sysirq: base address not specified\n");
 152                ret = -EINVAL;
 153                goto out_free_chip;
 154        }
 155
 156        chip_data->intpol_words = kcalloc(nr_intpol_bases,
 157                                          sizeof(*chip_data->intpol_words),
 158                                          GFP_KERNEL);
 159        if (!chip_data->intpol_words) {
 160                ret = -ENOMEM;
 161                goto out_free_chip;
 162        }
 163
 164        chip_data->intpol_bases = kcalloc(nr_intpol_bases,
 165                                          sizeof(*chip_data->intpol_bases),
 166                                          GFP_KERNEL);
 167        if (!chip_data->intpol_bases) {
 168                ret = -ENOMEM;
 169                goto out_free_intpol_words;
 170        }
 171
 172        for (i = 0; i < nr_intpol_bases; i++) {
 173                struct resource res;
 174
 175                ret = of_address_to_resource(node, i, &res);
 176                size = resource_size(&res);
 177                intpol_num += size * 8;
 178                chip_data->intpol_words[i] = size / 4;
 179                chip_data->intpol_bases[i] = of_iomap(node, i);
 180                if (ret || !chip_data->intpol_bases[i]) {
 181                        pr_err("%pOF: couldn't map region %d\n", node, i);
 182                        ret = -ENODEV;
 183                        goto out_free_intpol;
 184                }
 185        }
 186
 187        chip_data->intpol_idx = kcalloc(intpol_num,
 188                                        sizeof(*chip_data->intpol_idx),
 189                                        GFP_KERNEL);
 190        if (!chip_data->intpol_idx) {
 191                ret = -ENOMEM;
 192                goto out_free_intpol;
 193        }
 194
 195        chip_data->which_word = kcalloc(intpol_num,
 196                                        sizeof(*chip_data->which_word),
 197                                        GFP_KERNEL);
 198        if (!chip_data->which_word) {
 199                ret = -ENOMEM;
 200                goto out_free_intpol_idx;
 201        }
 202
 203        /*
 204         * assign an index of the intpol_bases for each irq
 205         * to set it fast later
 206         */
 207        for (i = 0; i < intpol_num ; i++) {
 208                u32 word = i / 32, j;
 209
 210                for (j = 0; word >= chip_data->intpol_words[j] ; j++)
 211                        word -= chip_data->intpol_words[j];
 212
 213                chip_data->intpol_idx[i] = j;
 214                chip_data->which_word[i] = word;
 215        }
 216
 217        domain = irq_domain_add_hierarchy(domain_parent, 0, intpol_num, node,
 218                                          &sysirq_domain_ops, chip_data);
 219        if (!domain) {
 220                ret = -ENOMEM;
 221                goto out_free_which_word;
 222        }
 223        spin_lock_init(&chip_data->lock);
 224
 225        return 0;
 226
 227out_free_which_word:
 228        kfree(chip_data->which_word);
 229out_free_intpol_idx:
 230        kfree(chip_data->intpol_idx);
 231out_free_intpol:
 232        for (i = 0; i < nr_intpol_bases; i++)
 233                if (chip_data->intpol_bases[i])
 234                        iounmap(chip_data->intpol_bases[i]);
 235        kfree(chip_data->intpol_bases);
 236out_free_intpol_words:
 237        kfree(chip_data->intpol_words);
 238out_free_chip:
 239        kfree(chip_data);
 240        return ret;
 241}
 242IRQCHIP_DECLARE(mtk_sysirq, "mediatek,mt6577-sysirq", mtk_sysirq_of_init);
 243