xref: /openbmc/linux/drivers/irqchip/irq-mvebu-odmi.c (revision 9a87ffc99ec8eb8d35eed7c4f816d75f5cc9662e)
1c27f29bbSThomas Petazzoni /*
2c27f29bbSThomas Petazzoni  * Copyright (C) 2016 Marvell
3c27f29bbSThomas Petazzoni  *
4c27f29bbSThomas Petazzoni  * Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
5c27f29bbSThomas Petazzoni  *
6c27f29bbSThomas Petazzoni  * This file is licensed under the terms of the GNU General Public
7c27f29bbSThomas Petazzoni  * License version 2.  This program is licensed "as is" without any
8c27f29bbSThomas Petazzoni  * warranty of any kind, whether express or implied.
9c27f29bbSThomas Petazzoni  */
10c27f29bbSThomas Petazzoni 
11c27f29bbSThomas Petazzoni #define pr_fmt(fmt) "GIC-ODMI: " fmt
12c27f29bbSThomas Petazzoni 
13c27f29bbSThomas Petazzoni #include <linux/irq.h>
14c27f29bbSThomas Petazzoni #include <linux/irqchip.h>
15c27f29bbSThomas Petazzoni #include <linux/irqdomain.h>
16c27f29bbSThomas Petazzoni #include <linux/kernel.h>
17c27f29bbSThomas Petazzoni #include <linux/msi.h>
18c27f29bbSThomas Petazzoni #include <linux/of_address.h>
19c27f29bbSThomas Petazzoni #include <linux/slab.h>
20c27f29bbSThomas Petazzoni #include <dt-bindings/interrupt-controller/arm-gic.h>
21c27f29bbSThomas Petazzoni 
22c27f29bbSThomas Petazzoni #define GICP_ODMIN_SET			0x40
23c27f29bbSThomas Petazzoni #define   GICP_ODMI_INT_NUM_SHIFT	12
24c27f29bbSThomas Petazzoni #define GICP_ODMIN_GM_EP_R0		0x110
25c27f29bbSThomas Petazzoni #define GICP_ODMIN_GM_EP_R1		0x114
26c27f29bbSThomas Petazzoni #define GICP_ODMIN_GM_EA_R0		0x108
27c27f29bbSThomas Petazzoni #define GICP_ODMIN_GM_EA_R1		0x118
28c27f29bbSThomas Petazzoni 
29c27f29bbSThomas Petazzoni /*
30c27f29bbSThomas Petazzoni  * We don't support the group events, so we simply have 8 interrupts
31c27f29bbSThomas Petazzoni  * per frame.
32c27f29bbSThomas Petazzoni  */
33c27f29bbSThomas Petazzoni #define NODMIS_SHIFT		3
34c27f29bbSThomas Petazzoni #define NODMIS_PER_FRAME	(1 << NODMIS_SHIFT)
35c27f29bbSThomas Petazzoni #define NODMIS_MASK		(NODMIS_PER_FRAME - 1)
36c27f29bbSThomas Petazzoni 
37c27f29bbSThomas Petazzoni struct odmi_data {
38c27f29bbSThomas Petazzoni 	struct resource res;
39c27f29bbSThomas Petazzoni 	void __iomem *base;
40c27f29bbSThomas Petazzoni 	unsigned int spi_base;
41c27f29bbSThomas Petazzoni };
42c27f29bbSThomas Petazzoni 
43c27f29bbSThomas Petazzoni static struct odmi_data *odmis;
44c27f29bbSThomas Petazzoni static unsigned long *odmis_bm;
45c27f29bbSThomas Petazzoni static unsigned int odmis_count;
46c27f29bbSThomas Petazzoni 
47c27f29bbSThomas Petazzoni /* Protects odmis_bm */
48c27f29bbSThomas Petazzoni static DEFINE_SPINLOCK(odmis_bm_lock);
49c27f29bbSThomas Petazzoni 
odmi_compose_msi_msg(struct irq_data * d,struct msi_msg * msg)50c27f29bbSThomas Petazzoni static void odmi_compose_msi_msg(struct irq_data *d, struct msi_msg *msg)
51c27f29bbSThomas Petazzoni {
52c27f29bbSThomas Petazzoni 	struct odmi_data *odmi;
53c27f29bbSThomas Petazzoni 	phys_addr_t addr;
54c27f29bbSThomas Petazzoni 	unsigned int odmin;
55c27f29bbSThomas Petazzoni 
56c27f29bbSThomas Petazzoni 	if (WARN_ON(d->hwirq >= odmis_count * NODMIS_PER_FRAME))
57c27f29bbSThomas Petazzoni 		return;
58c27f29bbSThomas Petazzoni 
59c27f29bbSThomas Petazzoni 	odmi = &odmis[d->hwirq >> NODMIS_SHIFT];
60c27f29bbSThomas Petazzoni 	odmin = d->hwirq & NODMIS_MASK;
61c27f29bbSThomas Petazzoni 
62c27f29bbSThomas Petazzoni 	addr = odmi->res.start + GICP_ODMIN_SET;
63c27f29bbSThomas Petazzoni 
64c27f29bbSThomas Petazzoni 	msg->address_hi = upper_32_bits(addr);
65c27f29bbSThomas Petazzoni 	msg->address_lo = lower_32_bits(addr);
66c27f29bbSThomas Petazzoni 	msg->data = odmin << GICP_ODMI_INT_NUM_SHIFT;
67c27f29bbSThomas Petazzoni }
68c27f29bbSThomas Petazzoni 
69c27f29bbSThomas Petazzoni static struct irq_chip odmi_irq_chip = {
70c27f29bbSThomas Petazzoni 	.name			= "ODMI",
71c27f29bbSThomas Petazzoni 	.irq_mask		= irq_chip_mask_parent,
72c27f29bbSThomas Petazzoni 	.irq_unmask		= irq_chip_unmask_parent,
73c27f29bbSThomas Petazzoni 	.irq_eoi		= irq_chip_eoi_parent,
740407daceSMarc Zyngier 	.irq_set_affinity	= irq_chip_set_affinity_parent,
75c27f29bbSThomas Petazzoni 	.irq_compose_msi_msg	= odmi_compose_msi_msg,
76c27f29bbSThomas Petazzoni };
77c27f29bbSThomas Petazzoni 
odmi_irq_domain_alloc(struct irq_domain * domain,unsigned int virq,unsigned int nr_irqs,void * args)78c27f29bbSThomas Petazzoni static int odmi_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,
79c27f29bbSThomas Petazzoni 				 unsigned int nr_irqs, void *args)
80c27f29bbSThomas Petazzoni {
81c27f29bbSThomas Petazzoni 	struct odmi_data *odmi = NULL;
82c27f29bbSThomas Petazzoni 	struct irq_fwspec fwspec;
83c27f29bbSThomas Petazzoni 	struct irq_data *d;
84c27f29bbSThomas Petazzoni 	unsigned int hwirq, odmin;
85c27f29bbSThomas Petazzoni 	int ret;
86c27f29bbSThomas Petazzoni 
87c27f29bbSThomas Petazzoni 	spin_lock(&odmis_bm_lock);
88c27f29bbSThomas Petazzoni 	hwirq = find_first_zero_bit(odmis_bm, NODMIS_PER_FRAME * odmis_count);
89c27f29bbSThomas Petazzoni 	if (hwirq >= NODMIS_PER_FRAME * odmis_count) {
90c27f29bbSThomas Petazzoni 		spin_unlock(&odmis_bm_lock);
91c27f29bbSThomas Petazzoni 		return -ENOSPC;
92c27f29bbSThomas Petazzoni 	}
93c27f29bbSThomas Petazzoni 
94c27f29bbSThomas Petazzoni 	__set_bit(hwirq, odmis_bm);
95c27f29bbSThomas Petazzoni 	spin_unlock(&odmis_bm_lock);
96c27f29bbSThomas Petazzoni 
97c27f29bbSThomas Petazzoni 	odmi = &odmis[hwirq >> NODMIS_SHIFT];
98c27f29bbSThomas Petazzoni 	odmin = hwirq & NODMIS_MASK;
99c27f29bbSThomas Petazzoni 
100c27f29bbSThomas Petazzoni 	fwspec.fwnode = domain->parent->fwnode;
101c27f29bbSThomas Petazzoni 	fwspec.param_count = 3;
102c27f29bbSThomas Petazzoni 	fwspec.param[0] = GIC_SPI;
103c27f29bbSThomas Petazzoni 	fwspec.param[1] = odmi->spi_base - 32 + odmin;
104c27f29bbSThomas Petazzoni 	fwspec.param[2] = IRQ_TYPE_EDGE_RISING;
105c27f29bbSThomas Petazzoni 
106c27f29bbSThomas Petazzoni 	ret = irq_domain_alloc_irqs_parent(domain, virq, 1, &fwspec);
107c27f29bbSThomas Petazzoni 	if (ret) {
108c27f29bbSThomas Petazzoni 		pr_err("Cannot allocate parent IRQ\n");
109c27f29bbSThomas Petazzoni 		spin_lock(&odmis_bm_lock);
110c27f29bbSThomas Petazzoni 		__clear_bit(odmin, odmis_bm);
111c27f29bbSThomas Petazzoni 		spin_unlock(&odmis_bm_lock);
112c27f29bbSThomas Petazzoni 		return ret;
113c27f29bbSThomas Petazzoni 	}
114c27f29bbSThomas Petazzoni 
115c27f29bbSThomas Petazzoni 	/* Configure the interrupt line to be edge */
116c27f29bbSThomas Petazzoni 	d = irq_domain_get_irq_data(domain->parent, virq);
117c27f29bbSThomas Petazzoni 	d->chip->irq_set_type(d, IRQ_TYPE_EDGE_RISING);
118c27f29bbSThomas Petazzoni 
119c27f29bbSThomas Petazzoni 	irq_domain_set_hwirq_and_chip(domain, virq, hwirq,
120c27f29bbSThomas Petazzoni 				      &odmi_irq_chip, NULL);
121c27f29bbSThomas Petazzoni 
122c27f29bbSThomas Petazzoni 	return 0;
123c27f29bbSThomas Petazzoni }
124c27f29bbSThomas Petazzoni 
odmi_irq_domain_free(struct irq_domain * domain,unsigned int virq,unsigned int nr_irqs)125c27f29bbSThomas Petazzoni static void odmi_irq_domain_free(struct irq_domain *domain,
126c27f29bbSThomas Petazzoni 				 unsigned int virq, unsigned int nr_irqs)
127c27f29bbSThomas Petazzoni {
128c27f29bbSThomas Petazzoni 	struct irq_data *d = irq_domain_get_irq_data(domain, virq);
129c27f29bbSThomas Petazzoni 
130c27f29bbSThomas Petazzoni 	if (d->hwirq >= odmis_count * NODMIS_PER_FRAME) {
131c27f29bbSThomas Petazzoni 		pr_err("Failed to teardown msi. Invalid hwirq %lu\n", d->hwirq);
132c27f29bbSThomas Petazzoni 		return;
133c27f29bbSThomas Petazzoni 	}
134c27f29bbSThomas Petazzoni 
135c27f29bbSThomas Petazzoni 	irq_domain_free_irqs_parent(domain, virq, nr_irqs);
136c27f29bbSThomas Petazzoni 
137c27f29bbSThomas Petazzoni 	/* Actually free the MSI */
138c27f29bbSThomas Petazzoni 	spin_lock(&odmis_bm_lock);
139c27f29bbSThomas Petazzoni 	__clear_bit(d->hwirq, odmis_bm);
140c27f29bbSThomas Petazzoni 	spin_unlock(&odmis_bm_lock);
141c27f29bbSThomas Petazzoni }
142c27f29bbSThomas Petazzoni 
143c27f29bbSThomas Petazzoni static const struct irq_domain_ops odmi_domain_ops = {
144c27f29bbSThomas Petazzoni 	.alloc	= odmi_irq_domain_alloc,
145c27f29bbSThomas Petazzoni 	.free	= odmi_irq_domain_free,
146c27f29bbSThomas Petazzoni };
147c27f29bbSThomas Petazzoni 
148c27f29bbSThomas Petazzoni static struct irq_chip odmi_msi_irq_chip = {
149c27f29bbSThomas Petazzoni 	.name	= "ODMI",
150c27f29bbSThomas Petazzoni };
151c27f29bbSThomas Petazzoni 
152c27f29bbSThomas Petazzoni static struct msi_domain_ops odmi_msi_ops = {
153c27f29bbSThomas Petazzoni };
154c27f29bbSThomas Petazzoni 
155c27f29bbSThomas Petazzoni static struct msi_domain_info odmi_msi_domain_info = {
156c27f29bbSThomas Petazzoni 	.flags	= (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS),
157c27f29bbSThomas Petazzoni 	.ops	= &odmi_msi_ops,
158c27f29bbSThomas Petazzoni 	.chip	= &odmi_msi_irq_chip,
159c27f29bbSThomas Petazzoni };
160c27f29bbSThomas Petazzoni 
mvebu_odmi_init(struct device_node * node,struct device_node * parent)161c27f29bbSThomas Petazzoni static int __init mvebu_odmi_init(struct device_node *node,
162c27f29bbSThomas Petazzoni 				  struct device_node *parent)
163c27f29bbSThomas Petazzoni {
164*f743f54fSJohan Hovold 	struct irq_domain *parent_domain, *inner_domain, *plat_domain;
165c27f29bbSThomas Petazzoni 	int ret, i;
166c27f29bbSThomas Petazzoni 
167c27f29bbSThomas Petazzoni 	if (of_property_read_u32(node, "marvell,odmi-frames", &odmis_count))
168c27f29bbSThomas Petazzoni 		return -EINVAL;
169c27f29bbSThomas Petazzoni 
170c27f29bbSThomas Petazzoni 	odmis = kcalloc(odmis_count, sizeof(struct odmi_data), GFP_KERNEL);
171c27f29bbSThomas Petazzoni 	if (!odmis)
172c27f29bbSThomas Petazzoni 		return -ENOMEM;
173c27f29bbSThomas Petazzoni 
174c980983dSAndy Shevchenko 	odmis_bm = bitmap_zalloc(odmis_count * NODMIS_PER_FRAME, GFP_KERNEL);
175c27f29bbSThomas Petazzoni 	if (!odmis_bm) {
176c27f29bbSThomas Petazzoni 		ret = -ENOMEM;
177c27f29bbSThomas Petazzoni 		goto err_alloc;
178c27f29bbSThomas Petazzoni 	}
179c27f29bbSThomas Petazzoni 
180c27f29bbSThomas Petazzoni 	for (i = 0; i < odmis_count; i++) {
181c27f29bbSThomas Petazzoni 		struct odmi_data *odmi = &odmis[i];
182c27f29bbSThomas Petazzoni 
183c27f29bbSThomas Petazzoni 		ret = of_address_to_resource(node, i, &odmi->res);
184c27f29bbSThomas Petazzoni 		if (ret)
185c27f29bbSThomas Petazzoni 			goto err_unmap;
186c27f29bbSThomas Petazzoni 
187c27f29bbSThomas Petazzoni 		odmi->base = of_io_request_and_map(node, i, "odmi");
188c27f29bbSThomas Petazzoni 		if (IS_ERR(odmi->base)) {
189c27f29bbSThomas Petazzoni 			ret = PTR_ERR(odmi->base);
190c27f29bbSThomas Petazzoni 			goto err_unmap;
191c27f29bbSThomas Petazzoni 		}
192c27f29bbSThomas Petazzoni 
193c27f29bbSThomas Petazzoni 		if (of_property_read_u32_index(node, "marvell,spi-base",
194c27f29bbSThomas Petazzoni 					       i, &odmi->spi_base)) {
195c27f29bbSThomas Petazzoni 			ret = -EINVAL;
196c27f29bbSThomas Petazzoni 			goto err_unmap;
197c27f29bbSThomas Petazzoni 		}
198c27f29bbSThomas Petazzoni 	}
199c27f29bbSThomas Petazzoni 
200*f743f54fSJohan Hovold 	parent_domain = irq_find_host(parent);
201*f743f54fSJohan Hovold 
202*f743f54fSJohan Hovold 	inner_domain = irq_domain_create_hierarchy(parent_domain, 0,
203c27f29bbSThomas Petazzoni 						   odmis_count * NODMIS_PER_FRAME,
204*f743f54fSJohan Hovold 						   of_node_to_fwnode(node),
205c27f29bbSThomas Petazzoni 						   &odmi_domain_ops, NULL);
206c27f29bbSThomas Petazzoni 	if (!inner_domain) {
207c27f29bbSThomas Petazzoni 		ret = -ENOMEM;
208c27f29bbSThomas Petazzoni 		goto err_unmap;
209c27f29bbSThomas Petazzoni 	}
210c27f29bbSThomas Petazzoni 
211c27f29bbSThomas Petazzoni 	plat_domain = platform_msi_create_irq_domain(of_node_to_fwnode(node),
212c27f29bbSThomas Petazzoni 						     &odmi_msi_domain_info,
213c27f29bbSThomas Petazzoni 						     inner_domain);
214c27f29bbSThomas Petazzoni 	if (!plat_domain) {
215c27f29bbSThomas Petazzoni 		ret = -ENOMEM;
216c27f29bbSThomas Petazzoni 		goto err_remove_inner;
217c27f29bbSThomas Petazzoni 	}
218c27f29bbSThomas Petazzoni 
219c27f29bbSThomas Petazzoni 	return 0;
220c27f29bbSThomas Petazzoni 
221c27f29bbSThomas Petazzoni err_remove_inner:
222c27f29bbSThomas Petazzoni 	irq_domain_remove(inner_domain);
223c27f29bbSThomas Petazzoni err_unmap:
224c27f29bbSThomas Petazzoni 	for (i = 0; i < odmis_count; i++) {
225c27f29bbSThomas Petazzoni 		struct odmi_data *odmi = &odmis[i];
226c27f29bbSThomas Petazzoni 
227c27f29bbSThomas Petazzoni 		if (odmi->base && !IS_ERR(odmi->base))
228c27f29bbSThomas Petazzoni 			iounmap(odmis[i].base);
229c27f29bbSThomas Petazzoni 	}
230c980983dSAndy Shevchenko 	bitmap_free(odmis_bm);
231c27f29bbSThomas Petazzoni err_alloc:
232c27f29bbSThomas Petazzoni 	kfree(odmis);
233c27f29bbSThomas Petazzoni 	return ret;
234c27f29bbSThomas Petazzoni }
235c27f29bbSThomas Petazzoni 
236c27f29bbSThomas Petazzoni IRQCHIP_DECLARE(mvebu_odmi, "marvell,odmi-controller", mvebu_odmi_init);
237