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