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("%s: couldn't map region %d\n",
182 			       node->full_name, i);
183 			ret = -ENODEV;
184 			goto out_free_intpol;
185 		}
186 	}
187 
188 	chip_data->intpol_idx = kcalloc(intpol_num,
189 					sizeof(*chip_data->intpol_idx),
190 					GFP_KERNEL);
191 	if (!chip_data->intpol_idx) {
192 		ret = -ENOMEM;
193 		goto out_free_intpol;
194 	}
195 
196 	chip_data->which_word = kcalloc(intpol_num,
197 					sizeof(*chip_data->which_word),
198 					GFP_KERNEL);
199 	if (!chip_data->which_word) {
200 		ret = -ENOMEM;
201 		goto out_free_intpol_idx;
202 	}
203 
204 	/*
205 	 * assign an index of the intpol_bases for each irq
206 	 * to set it fast later
207 	 */
208 	for (i = 0; i < intpol_num ; i++) {
209 		u32 word = i / 32, j;
210 
211 		for (j = 0; word >= chip_data->intpol_words[j] ; j++)
212 			word -= chip_data->intpol_words[j];
213 
214 		chip_data->intpol_idx[i] = j;
215 		chip_data->which_word[i] = word;
216 	}
217 
218 	domain = irq_domain_add_hierarchy(domain_parent, 0, intpol_num, node,
219 					  &sysirq_domain_ops, chip_data);
220 	if (!domain) {
221 		ret = -ENOMEM;
222 		goto out_free_which_word;
223 	}
224 	spin_lock_init(&chip_data->lock);
225 
226 	return 0;
227 
228 out_free_which_word:
229 	kfree(chip_data->which_word);
230 out_free_intpol_idx:
231 	kfree(chip_data->intpol_idx);
232 out_free_intpol:
233 	for (i = 0; i < nr_intpol_bases; i++)
234 		if (chip_data->intpol_bases[i])
235 			iounmap(chip_data->intpol_bases[i]);
236 	kfree(chip_data->intpol_bases);
237 out_free_intpol_words:
238 	kfree(chip_data->intpol_words);
239 out_free_chip:
240 	kfree(chip_data);
241 	return ret;
242 }
243 IRQCHIP_DECLARE(mtk_sysirq, "mediatek,mt6577-sysirq", mtk_sysirq_of_init);
244