xref: /linux/drivers/irqchip/irq-meson-gpio.c (revision 3eb66e91a25497065c5322b1268cbc3953642227)
1215f4cc0SJerome Brunet /*
2215f4cc0SJerome Brunet  * Copyright (c) 2015 Endless Mobile, Inc.
3215f4cc0SJerome Brunet  * Author: Carlo Caione <carlo@endlessm.com>
4215f4cc0SJerome Brunet  * Copyright (c) 2016 BayLibre, SAS.
5215f4cc0SJerome Brunet  * Author: Jerome Brunet <jbrunet@baylibre.com>
6215f4cc0SJerome Brunet  *
7215f4cc0SJerome Brunet  * This program is free software; you can redistribute it and/or modify
8215f4cc0SJerome Brunet  * it under the terms of version 2 of the GNU General Public License as
9215f4cc0SJerome Brunet  * published by the Free Software Foundation.
10215f4cc0SJerome Brunet  *
11215f4cc0SJerome Brunet  * This program is distributed in the hope that it will be useful, but
12215f4cc0SJerome Brunet  * WITHOUT ANY WARRANTY; without even the implied warranty of
13215f4cc0SJerome Brunet  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14215f4cc0SJerome Brunet  * General Public License for more details.
15215f4cc0SJerome Brunet  *
16215f4cc0SJerome Brunet  * You should have received a copy of the GNU General Public License
17215f4cc0SJerome Brunet  * along with this program; if not, see <http://www.gnu.org/licenses/>.
18215f4cc0SJerome Brunet  * The full GNU General Public License is included in this distribution
19215f4cc0SJerome Brunet  * in the file called COPYING.
20215f4cc0SJerome Brunet  */
21215f4cc0SJerome Brunet 
22215f4cc0SJerome Brunet #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
23215f4cc0SJerome Brunet 
24215f4cc0SJerome Brunet #include <linux/io.h>
25215f4cc0SJerome Brunet #include <linux/module.h>
26215f4cc0SJerome Brunet #include <linux/irq.h>
27215f4cc0SJerome Brunet #include <linux/irqdomain.h>
28215f4cc0SJerome Brunet #include <linux/irqchip.h>
29215f4cc0SJerome Brunet #include <linux/of.h>
30215f4cc0SJerome Brunet #include <linux/of_address.h>
31215f4cc0SJerome Brunet 
32215f4cc0SJerome Brunet #define NUM_CHANNEL 8
33215f4cc0SJerome Brunet #define MAX_INPUT_MUX 256
34215f4cc0SJerome Brunet 
35215f4cc0SJerome Brunet #define REG_EDGE_POL	0x00
36215f4cc0SJerome Brunet #define REG_PIN_03_SEL	0x04
37215f4cc0SJerome Brunet #define REG_PIN_47_SEL	0x08
38215f4cc0SJerome Brunet #define REG_FILTER_SEL	0x0c
39215f4cc0SJerome Brunet 
40215f4cc0SJerome Brunet #define REG_EDGE_POL_MASK(x)	(BIT(x) | BIT(16 + (x)))
41215f4cc0SJerome Brunet #define REG_EDGE_POL_EDGE(x)	BIT(x)
42215f4cc0SJerome Brunet #define REG_EDGE_POL_LOW(x)	BIT(16 + (x))
43215f4cc0SJerome Brunet #define REG_PIN_SEL_SHIFT(x)	(((x) % 4) * 8)
44215f4cc0SJerome Brunet #define REG_FILTER_SEL_SHIFT(x)	((x) * 4)
45215f4cc0SJerome Brunet 
46215f4cc0SJerome Brunet struct meson_gpio_irq_params {
47215f4cc0SJerome Brunet 	unsigned int nr_hwirq;
48215f4cc0SJerome Brunet };
49215f4cc0SJerome Brunet 
504e4cb1b1SMartin Blumenstingl static const struct meson_gpio_irq_params meson8_params = {
514e4cb1b1SMartin Blumenstingl 	.nr_hwirq = 134,
524e4cb1b1SMartin Blumenstingl };
534e4cb1b1SMartin Blumenstingl 
54215f4cc0SJerome Brunet static const struct meson_gpio_irq_params meson8b_params = {
55215f4cc0SJerome Brunet 	.nr_hwirq = 119,
56215f4cc0SJerome Brunet };
57215f4cc0SJerome Brunet 
58215f4cc0SJerome Brunet static const struct meson_gpio_irq_params gxbb_params = {
59215f4cc0SJerome Brunet 	.nr_hwirq = 133,
60215f4cc0SJerome Brunet };
61215f4cc0SJerome Brunet 
62215f4cc0SJerome Brunet static const struct meson_gpio_irq_params gxl_params = {
63215f4cc0SJerome Brunet 	.nr_hwirq = 110,
64215f4cc0SJerome Brunet };
65215f4cc0SJerome Brunet 
66*868c4e07SYixun Lan static const struct meson_gpio_irq_params axg_params = {
67*868c4e07SYixun Lan 	.nr_hwirq = 100,
68*868c4e07SYixun Lan };
69*868c4e07SYixun Lan 
70215f4cc0SJerome Brunet static const struct of_device_id meson_irq_gpio_matches[] = {
714e4cb1b1SMartin Blumenstingl 	{ .compatible = "amlogic,meson8-gpio-intc", .data = &meson8_params },
72215f4cc0SJerome Brunet 	{ .compatible = "amlogic,meson8b-gpio-intc", .data = &meson8b_params },
73215f4cc0SJerome Brunet 	{ .compatible = "amlogic,meson-gxbb-gpio-intc", .data = &gxbb_params },
74215f4cc0SJerome Brunet 	{ .compatible = "amlogic,meson-gxl-gpio-intc", .data = &gxl_params },
75*868c4e07SYixun Lan 	{ .compatible = "amlogic,meson-axg-gpio-intc", .data = &axg_params },
76215f4cc0SJerome Brunet 	{ }
77215f4cc0SJerome Brunet };
78215f4cc0SJerome Brunet 
79215f4cc0SJerome Brunet struct meson_gpio_irq_controller {
80215f4cc0SJerome Brunet 	unsigned int nr_hwirq;
81215f4cc0SJerome Brunet 	void __iomem *base;
82215f4cc0SJerome Brunet 	u32 channel_irqs[NUM_CHANNEL];
83215f4cc0SJerome Brunet 	DECLARE_BITMAP(channel_map, NUM_CHANNEL);
84215f4cc0SJerome Brunet 	spinlock_t lock;
85215f4cc0SJerome Brunet };
86215f4cc0SJerome Brunet 
87215f4cc0SJerome Brunet static void meson_gpio_irq_update_bits(struct meson_gpio_irq_controller *ctl,
88215f4cc0SJerome Brunet 				       unsigned int reg, u32 mask, u32 val)
89215f4cc0SJerome Brunet {
90215f4cc0SJerome Brunet 	u32 tmp;
91215f4cc0SJerome Brunet 
92215f4cc0SJerome Brunet 	tmp = readl_relaxed(ctl->base + reg);
93215f4cc0SJerome Brunet 	tmp &= ~mask;
94215f4cc0SJerome Brunet 	tmp |= val;
95215f4cc0SJerome Brunet 	writel_relaxed(tmp, ctl->base + reg);
96215f4cc0SJerome Brunet }
97215f4cc0SJerome Brunet 
98215f4cc0SJerome Brunet static unsigned int meson_gpio_irq_channel_to_reg(unsigned int channel)
99215f4cc0SJerome Brunet {
100215f4cc0SJerome Brunet 	return (channel < 4) ? REG_PIN_03_SEL : REG_PIN_47_SEL;
101215f4cc0SJerome Brunet }
102215f4cc0SJerome Brunet 
103215f4cc0SJerome Brunet static int
104215f4cc0SJerome Brunet meson_gpio_irq_request_channel(struct meson_gpio_irq_controller *ctl,
105215f4cc0SJerome Brunet 			       unsigned long  hwirq,
106215f4cc0SJerome Brunet 			       u32 **channel_hwirq)
107215f4cc0SJerome Brunet {
108215f4cc0SJerome Brunet 	unsigned int reg, idx;
109215f4cc0SJerome Brunet 
110215f4cc0SJerome Brunet 	spin_lock(&ctl->lock);
111215f4cc0SJerome Brunet 
112215f4cc0SJerome Brunet 	/* Find a free channel */
113215f4cc0SJerome Brunet 	idx = find_first_zero_bit(ctl->channel_map, NUM_CHANNEL);
114215f4cc0SJerome Brunet 	if (idx >= NUM_CHANNEL) {
115215f4cc0SJerome Brunet 		spin_unlock(&ctl->lock);
116215f4cc0SJerome Brunet 		pr_err("No channel available\n");
117215f4cc0SJerome Brunet 		return -ENOSPC;
118215f4cc0SJerome Brunet 	}
119215f4cc0SJerome Brunet 
120215f4cc0SJerome Brunet 	/* Mark the channel as used */
121215f4cc0SJerome Brunet 	set_bit(idx, ctl->channel_map);
122215f4cc0SJerome Brunet 
123215f4cc0SJerome Brunet 	/*
124215f4cc0SJerome Brunet 	 * Setup the mux of the channel to route the signal of the pad
125215f4cc0SJerome Brunet 	 * to the appropriate input of the GIC
126215f4cc0SJerome Brunet 	 */
127215f4cc0SJerome Brunet 	reg = meson_gpio_irq_channel_to_reg(idx);
128215f4cc0SJerome Brunet 	meson_gpio_irq_update_bits(ctl, reg,
129215f4cc0SJerome Brunet 				   0xff << REG_PIN_SEL_SHIFT(idx),
130215f4cc0SJerome Brunet 				   hwirq << REG_PIN_SEL_SHIFT(idx));
131215f4cc0SJerome Brunet 
132215f4cc0SJerome Brunet 	/*
133215f4cc0SJerome Brunet 	 * Get the hwirq number assigned to this channel through
134215f4cc0SJerome Brunet 	 * a pointer the channel_irq table. The added benifit of this
135215f4cc0SJerome Brunet 	 * method is that we can also retrieve the channel index with
136215f4cc0SJerome Brunet 	 * it, using the table base.
137215f4cc0SJerome Brunet 	 */
138215f4cc0SJerome Brunet 	*channel_hwirq = &(ctl->channel_irqs[idx]);
139215f4cc0SJerome Brunet 
140215f4cc0SJerome Brunet 	spin_unlock(&ctl->lock);
141215f4cc0SJerome Brunet 
142215f4cc0SJerome Brunet 	pr_debug("hwirq %lu assigned to channel %d - irq %u\n",
143215f4cc0SJerome Brunet 		 hwirq, idx, **channel_hwirq);
144215f4cc0SJerome Brunet 
145215f4cc0SJerome Brunet 	return 0;
146215f4cc0SJerome Brunet }
147215f4cc0SJerome Brunet 
148215f4cc0SJerome Brunet static unsigned int
149215f4cc0SJerome Brunet meson_gpio_irq_get_channel_idx(struct meson_gpio_irq_controller *ctl,
150215f4cc0SJerome Brunet 			       u32 *channel_hwirq)
151215f4cc0SJerome Brunet {
152215f4cc0SJerome Brunet 	return channel_hwirq - ctl->channel_irqs;
153215f4cc0SJerome Brunet }
154215f4cc0SJerome Brunet 
155215f4cc0SJerome Brunet static void
156215f4cc0SJerome Brunet meson_gpio_irq_release_channel(struct meson_gpio_irq_controller *ctl,
157215f4cc0SJerome Brunet 			       u32 *channel_hwirq)
158215f4cc0SJerome Brunet {
159215f4cc0SJerome Brunet 	unsigned int idx;
160215f4cc0SJerome Brunet 
161215f4cc0SJerome Brunet 	idx = meson_gpio_irq_get_channel_idx(ctl, channel_hwirq);
162215f4cc0SJerome Brunet 	clear_bit(idx, ctl->channel_map);
163215f4cc0SJerome Brunet }
164215f4cc0SJerome Brunet 
165215f4cc0SJerome Brunet static int meson_gpio_irq_type_setup(struct meson_gpio_irq_controller *ctl,
166215f4cc0SJerome Brunet 				     unsigned int type,
167215f4cc0SJerome Brunet 				     u32 *channel_hwirq)
168215f4cc0SJerome Brunet {
169215f4cc0SJerome Brunet 	u32 val = 0;
170215f4cc0SJerome Brunet 	unsigned int idx;
171215f4cc0SJerome Brunet 
172215f4cc0SJerome Brunet 	idx = meson_gpio_irq_get_channel_idx(ctl, channel_hwirq);
173215f4cc0SJerome Brunet 
174215f4cc0SJerome Brunet 	/*
175215f4cc0SJerome Brunet 	 * The controller has a filter block to operate in either LEVEL or
176215f4cc0SJerome Brunet 	 * EDGE mode, then signal is sent to the GIC. To enable LEVEL_LOW and
177215f4cc0SJerome Brunet 	 * EDGE_FALLING support (which the GIC does not support), the filter
178215f4cc0SJerome Brunet 	 * block is also able to invert the input signal it gets before
179215f4cc0SJerome Brunet 	 * providing it to the GIC.
180215f4cc0SJerome Brunet 	 */
181215f4cc0SJerome Brunet 	type &= IRQ_TYPE_SENSE_MASK;
182215f4cc0SJerome Brunet 
183215f4cc0SJerome Brunet 	if (type == IRQ_TYPE_EDGE_BOTH)
184215f4cc0SJerome Brunet 		return -EINVAL;
185215f4cc0SJerome Brunet 
186215f4cc0SJerome Brunet 	if (type & (IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING))
187215f4cc0SJerome Brunet 		val |= REG_EDGE_POL_EDGE(idx);
188215f4cc0SJerome Brunet 
189215f4cc0SJerome Brunet 	if (type & (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_EDGE_FALLING))
190215f4cc0SJerome Brunet 		val |= REG_EDGE_POL_LOW(idx);
191215f4cc0SJerome Brunet 
192215f4cc0SJerome Brunet 	spin_lock(&ctl->lock);
193215f4cc0SJerome Brunet 
194215f4cc0SJerome Brunet 	meson_gpio_irq_update_bits(ctl, REG_EDGE_POL,
195215f4cc0SJerome Brunet 				   REG_EDGE_POL_MASK(idx), val);
196215f4cc0SJerome Brunet 
197215f4cc0SJerome Brunet 	spin_unlock(&ctl->lock);
198215f4cc0SJerome Brunet 
199215f4cc0SJerome Brunet 	return 0;
200215f4cc0SJerome Brunet }
201215f4cc0SJerome Brunet 
202215f4cc0SJerome Brunet static unsigned int meson_gpio_irq_type_output(unsigned int type)
203215f4cc0SJerome Brunet {
204215f4cc0SJerome Brunet 	unsigned int sense = type & IRQ_TYPE_SENSE_MASK;
205215f4cc0SJerome Brunet 
206215f4cc0SJerome Brunet 	type &= ~IRQ_TYPE_SENSE_MASK;
207215f4cc0SJerome Brunet 
208215f4cc0SJerome Brunet 	/*
209215f4cc0SJerome Brunet 	 * The polarity of the signal provided to the GIC should always
210215f4cc0SJerome Brunet 	 * be high.
211215f4cc0SJerome Brunet 	 */
212215f4cc0SJerome Brunet 	if (sense & (IRQ_TYPE_LEVEL_HIGH | IRQ_TYPE_LEVEL_LOW))
213215f4cc0SJerome Brunet 		type |= IRQ_TYPE_LEVEL_HIGH;
214215f4cc0SJerome Brunet 	else if (sense & (IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING))
215215f4cc0SJerome Brunet 		type |= IRQ_TYPE_EDGE_RISING;
216215f4cc0SJerome Brunet 
217215f4cc0SJerome Brunet 	return type;
218215f4cc0SJerome Brunet }
219215f4cc0SJerome Brunet 
220215f4cc0SJerome Brunet static int meson_gpio_irq_set_type(struct irq_data *data, unsigned int type)
221215f4cc0SJerome Brunet {
222215f4cc0SJerome Brunet 	struct meson_gpio_irq_controller *ctl = data->domain->host_data;
223215f4cc0SJerome Brunet 	u32 *channel_hwirq = irq_data_get_irq_chip_data(data);
224215f4cc0SJerome Brunet 	int ret;
225215f4cc0SJerome Brunet 
226215f4cc0SJerome Brunet 	ret = meson_gpio_irq_type_setup(ctl, type, channel_hwirq);
227215f4cc0SJerome Brunet 	if (ret)
228215f4cc0SJerome Brunet 		return ret;
229215f4cc0SJerome Brunet 
230215f4cc0SJerome Brunet 	return irq_chip_set_type_parent(data,
231215f4cc0SJerome Brunet 					meson_gpio_irq_type_output(type));
232215f4cc0SJerome Brunet }
233215f4cc0SJerome Brunet 
234215f4cc0SJerome Brunet static struct irq_chip meson_gpio_irq_chip = {
235215f4cc0SJerome Brunet 	.name			= "meson-gpio-irqchip",
236215f4cc0SJerome Brunet 	.irq_mask		= irq_chip_mask_parent,
237215f4cc0SJerome Brunet 	.irq_unmask		= irq_chip_unmask_parent,
238215f4cc0SJerome Brunet 	.irq_eoi		= irq_chip_eoi_parent,
239215f4cc0SJerome Brunet 	.irq_set_type		= meson_gpio_irq_set_type,
240215f4cc0SJerome Brunet 	.irq_retrigger		= irq_chip_retrigger_hierarchy,
241215f4cc0SJerome Brunet #ifdef CONFIG_SMP
242215f4cc0SJerome Brunet 	.irq_set_affinity	= irq_chip_set_affinity_parent,
243215f4cc0SJerome Brunet #endif
244215f4cc0SJerome Brunet 	.flags			= IRQCHIP_SET_TYPE_MASKED,
245215f4cc0SJerome Brunet };
246215f4cc0SJerome Brunet 
247215f4cc0SJerome Brunet static int meson_gpio_irq_domain_translate(struct irq_domain *domain,
248215f4cc0SJerome Brunet 					   struct irq_fwspec *fwspec,
249215f4cc0SJerome Brunet 					   unsigned long *hwirq,
250215f4cc0SJerome Brunet 					   unsigned int *type)
251215f4cc0SJerome Brunet {
252215f4cc0SJerome Brunet 	if (is_of_node(fwspec->fwnode) && fwspec->param_count == 2) {
253215f4cc0SJerome Brunet 		*hwirq	= fwspec->param[0];
254215f4cc0SJerome Brunet 		*type	= fwspec->param[1];
255215f4cc0SJerome Brunet 		return 0;
256215f4cc0SJerome Brunet 	}
257215f4cc0SJerome Brunet 
258215f4cc0SJerome Brunet 	return -EINVAL;
259215f4cc0SJerome Brunet }
260215f4cc0SJerome Brunet 
261215f4cc0SJerome Brunet static int meson_gpio_irq_allocate_gic_irq(struct irq_domain *domain,
262215f4cc0SJerome Brunet 					   unsigned int virq,
263215f4cc0SJerome Brunet 					   u32 hwirq,
264215f4cc0SJerome Brunet 					   unsigned int type)
265215f4cc0SJerome Brunet {
266215f4cc0SJerome Brunet 	struct irq_fwspec fwspec;
267215f4cc0SJerome Brunet 
268215f4cc0SJerome Brunet 	fwspec.fwnode = domain->parent->fwnode;
269215f4cc0SJerome Brunet 	fwspec.param_count = 3;
270215f4cc0SJerome Brunet 	fwspec.param[0] = 0;	/* SPI */
271215f4cc0SJerome Brunet 	fwspec.param[1] = hwirq;
272215f4cc0SJerome Brunet 	fwspec.param[2] = meson_gpio_irq_type_output(type);
273215f4cc0SJerome Brunet 
274215f4cc0SJerome Brunet 	return irq_domain_alloc_irqs_parent(domain, virq, 1, &fwspec);
275215f4cc0SJerome Brunet }
276215f4cc0SJerome Brunet 
277215f4cc0SJerome Brunet static int meson_gpio_irq_domain_alloc(struct irq_domain *domain,
278215f4cc0SJerome Brunet 				       unsigned int virq,
279215f4cc0SJerome Brunet 				       unsigned int nr_irqs,
280215f4cc0SJerome Brunet 				       void *data)
281215f4cc0SJerome Brunet {
282215f4cc0SJerome Brunet 	struct irq_fwspec *fwspec = data;
283215f4cc0SJerome Brunet 	struct meson_gpio_irq_controller *ctl = domain->host_data;
284215f4cc0SJerome Brunet 	unsigned long hwirq;
285215f4cc0SJerome Brunet 	u32 *channel_hwirq;
286215f4cc0SJerome Brunet 	unsigned int type;
287215f4cc0SJerome Brunet 	int ret;
288215f4cc0SJerome Brunet 
289215f4cc0SJerome Brunet 	if (WARN_ON(nr_irqs != 1))
290215f4cc0SJerome Brunet 		return -EINVAL;
291215f4cc0SJerome Brunet 
292215f4cc0SJerome Brunet 	ret = meson_gpio_irq_domain_translate(domain, fwspec, &hwirq, &type);
293215f4cc0SJerome Brunet 	if (ret)
294215f4cc0SJerome Brunet 		return ret;
295215f4cc0SJerome Brunet 
296215f4cc0SJerome Brunet 	ret = meson_gpio_irq_request_channel(ctl, hwirq, &channel_hwirq);
297215f4cc0SJerome Brunet 	if (ret)
298215f4cc0SJerome Brunet 		return ret;
299215f4cc0SJerome Brunet 
300215f4cc0SJerome Brunet 	ret = meson_gpio_irq_allocate_gic_irq(domain, virq,
301215f4cc0SJerome Brunet 					      *channel_hwirq, type);
302215f4cc0SJerome Brunet 	if (ret < 0) {
303215f4cc0SJerome Brunet 		pr_err("failed to allocate gic irq %u\n", *channel_hwirq);
304215f4cc0SJerome Brunet 		meson_gpio_irq_release_channel(ctl, channel_hwirq);
305215f4cc0SJerome Brunet 		return ret;
306215f4cc0SJerome Brunet 	}
307215f4cc0SJerome Brunet 
308215f4cc0SJerome Brunet 	irq_domain_set_hwirq_and_chip(domain, virq, hwirq,
309215f4cc0SJerome Brunet 				      &meson_gpio_irq_chip, channel_hwirq);
310215f4cc0SJerome Brunet 
311215f4cc0SJerome Brunet 	return 0;
312215f4cc0SJerome Brunet }
313215f4cc0SJerome Brunet 
314215f4cc0SJerome Brunet static void meson_gpio_irq_domain_free(struct irq_domain *domain,
315215f4cc0SJerome Brunet 				       unsigned int virq,
316215f4cc0SJerome Brunet 				       unsigned int nr_irqs)
317215f4cc0SJerome Brunet {
318215f4cc0SJerome Brunet 	struct meson_gpio_irq_controller *ctl = domain->host_data;
319215f4cc0SJerome Brunet 	struct irq_data *irq_data;
320215f4cc0SJerome Brunet 	u32 *channel_hwirq;
321215f4cc0SJerome Brunet 
322215f4cc0SJerome Brunet 	if (WARN_ON(nr_irqs != 1))
323215f4cc0SJerome Brunet 		return;
324215f4cc0SJerome Brunet 
325215f4cc0SJerome Brunet 	irq_domain_free_irqs_parent(domain, virq, 1);
326215f4cc0SJerome Brunet 
327215f4cc0SJerome Brunet 	irq_data = irq_domain_get_irq_data(domain, virq);
328215f4cc0SJerome Brunet 	channel_hwirq = irq_data_get_irq_chip_data(irq_data);
329215f4cc0SJerome Brunet 
330215f4cc0SJerome Brunet 	meson_gpio_irq_release_channel(ctl, channel_hwirq);
331215f4cc0SJerome Brunet }
332215f4cc0SJerome Brunet 
333215f4cc0SJerome Brunet static const struct irq_domain_ops meson_gpio_irq_domain_ops = {
334215f4cc0SJerome Brunet 	.alloc		= meson_gpio_irq_domain_alloc,
335215f4cc0SJerome Brunet 	.free		= meson_gpio_irq_domain_free,
336215f4cc0SJerome Brunet 	.translate	= meson_gpio_irq_domain_translate,
337215f4cc0SJerome Brunet };
338215f4cc0SJerome Brunet 
339215f4cc0SJerome Brunet static int __init meson_gpio_irq_parse_dt(struct device_node *node,
340215f4cc0SJerome Brunet 					  struct meson_gpio_irq_controller *ctl)
341215f4cc0SJerome Brunet {
342215f4cc0SJerome Brunet 	const struct of_device_id *match;
343215f4cc0SJerome Brunet 	const struct meson_gpio_irq_params *params;
344215f4cc0SJerome Brunet 	int ret;
345215f4cc0SJerome Brunet 
346215f4cc0SJerome Brunet 	match = of_match_node(meson_irq_gpio_matches, node);
347215f4cc0SJerome Brunet 	if (!match)
348215f4cc0SJerome Brunet 		return -ENODEV;
349215f4cc0SJerome Brunet 
350215f4cc0SJerome Brunet 	params = match->data;
351215f4cc0SJerome Brunet 	ctl->nr_hwirq = params->nr_hwirq;
352215f4cc0SJerome Brunet 
353215f4cc0SJerome Brunet 	ret = of_property_read_variable_u32_array(node,
354215f4cc0SJerome Brunet 						  "amlogic,channel-interrupts",
355215f4cc0SJerome Brunet 						  ctl->channel_irqs,
356215f4cc0SJerome Brunet 						  NUM_CHANNEL,
357215f4cc0SJerome Brunet 						  NUM_CHANNEL);
358215f4cc0SJerome Brunet 	if (ret < 0) {
359215f4cc0SJerome Brunet 		pr_err("can't get %d channel interrupts\n", NUM_CHANNEL);
360215f4cc0SJerome Brunet 		return ret;
361215f4cc0SJerome Brunet 	}
362215f4cc0SJerome Brunet 
363215f4cc0SJerome Brunet 	return 0;
364215f4cc0SJerome Brunet }
365215f4cc0SJerome Brunet 
366215f4cc0SJerome Brunet static int __init meson_gpio_irq_of_init(struct device_node *node,
367215f4cc0SJerome Brunet 					 struct device_node *parent)
368215f4cc0SJerome Brunet {
369215f4cc0SJerome Brunet 	struct irq_domain *domain, *parent_domain;
370215f4cc0SJerome Brunet 	struct meson_gpio_irq_controller *ctl;
371215f4cc0SJerome Brunet 	int ret;
372215f4cc0SJerome Brunet 
373215f4cc0SJerome Brunet 	if (!parent) {
374215f4cc0SJerome Brunet 		pr_err("missing parent interrupt node\n");
375215f4cc0SJerome Brunet 		return -ENODEV;
376215f4cc0SJerome Brunet 	}
377215f4cc0SJerome Brunet 
378215f4cc0SJerome Brunet 	parent_domain = irq_find_host(parent);
379215f4cc0SJerome Brunet 	if (!parent_domain) {
380215f4cc0SJerome Brunet 		pr_err("unable to obtain parent domain\n");
381215f4cc0SJerome Brunet 		return -ENXIO;
382215f4cc0SJerome Brunet 	}
383215f4cc0SJerome Brunet 
384215f4cc0SJerome Brunet 	ctl = kzalloc(sizeof(*ctl), GFP_KERNEL);
385215f4cc0SJerome Brunet 	if (!ctl)
386215f4cc0SJerome Brunet 		return -ENOMEM;
387215f4cc0SJerome Brunet 
388215f4cc0SJerome Brunet 	spin_lock_init(&ctl->lock);
389215f4cc0SJerome Brunet 
390215f4cc0SJerome Brunet 	ctl->base = of_iomap(node, 0);
391215f4cc0SJerome Brunet 	if (!ctl->base) {
392215f4cc0SJerome Brunet 		ret = -ENOMEM;
393215f4cc0SJerome Brunet 		goto free_ctl;
394215f4cc0SJerome Brunet 	}
395215f4cc0SJerome Brunet 
396215f4cc0SJerome Brunet 	ret = meson_gpio_irq_parse_dt(node, ctl);
397215f4cc0SJerome Brunet 	if (ret)
398215f4cc0SJerome Brunet 		goto free_channel_irqs;
399215f4cc0SJerome Brunet 
400215f4cc0SJerome Brunet 	domain = irq_domain_create_hierarchy(parent_domain, 0, ctl->nr_hwirq,
401215f4cc0SJerome Brunet 					     of_node_to_fwnode(node),
402215f4cc0SJerome Brunet 					     &meson_gpio_irq_domain_ops,
403215f4cc0SJerome Brunet 					     ctl);
404215f4cc0SJerome Brunet 	if (!domain) {
405215f4cc0SJerome Brunet 		pr_err("failed to add domain\n");
406215f4cc0SJerome Brunet 		ret = -ENODEV;
407215f4cc0SJerome Brunet 		goto free_channel_irqs;
408215f4cc0SJerome Brunet 	}
409215f4cc0SJerome Brunet 
410215f4cc0SJerome Brunet 	pr_info("%d to %d gpio interrupt mux initialized\n",
411215f4cc0SJerome Brunet 		ctl->nr_hwirq, NUM_CHANNEL);
412215f4cc0SJerome Brunet 
413215f4cc0SJerome Brunet 	return 0;
414215f4cc0SJerome Brunet 
415215f4cc0SJerome Brunet free_channel_irqs:
416215f4cc0SJerome Brunet 	iounmap(ctl->base);
417215f4cc0SJerome Brunet free_ctl:
418215f4cc0SJerome Brunet 	kfree(ctl);
419215f4cc0SJerome Brunet 
420215f4cc0SJerome Brunet 	return ret;
421215f4cc0SJerome Brunet }
422215f4cc0SJerome Brunet 
423215f4cc0SJerome Brunet IRQCHIP_DECLARE(meson_gpio_intc, "amlogic,meson-gpio-intc",
424215f4cc0SJerome Brunet 		meson_gpio_irq_of_init);
425