Merge tag 'upstream-5.2-rc1' of ssh://gitolite.kernel.org/pub/scm/linux/kernel/git...
[linux-2.6-microblaze.git] / drivers / irqchip / irq-mtk-sysirq.c
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
25 struct 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
34 static 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
68 static 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
78 static 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
99 static 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
124 static 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
130 static 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
227 out_free_which_word:
228         kfree(chip_data->which_word);
229 out_free_intpol_idx:
230         kfree(chip_data->intpol_idx);
231 out_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);
236 out_free_intpol_words:
237         kfree(chip_data->intpol_words);
238 out_free_chip:
239         kfree(chip_data);
240         return ret;
241 }
242 IRQCHIP_DECLARE(mtk_sysirq, "mediatek,mt6577-sysirq", mtk_sysirq_of_init);