Commit | Line | Data |
---|---|---|
e6b78f2c AT |
1 | /* |
2 | * Annapurna Labs MSIX support services | |
3 | * | |
4 | * Copyright (C) 2016, Amazon.com, Inc. or its affiliates. All Rights Reserved. | |
5 | * | |
6 | * Antoine Tenart <antoine.tenart@free-electrons.com> | |
7 | * | |
8 | * This file is licensed under the terms of the GNU General Public | |
9 | * License version 2. This program is licensed "as is" without any | |
10 | * warranty of any kind, whether express or implied. | |
11 | */ | |
12 | ||
13 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | |
14 | ||
15 | #include <linux/irqchip.h> | |
16 | #include <linux/irqchip/arm-gic.h> | |
17 | #include <linux/msi.h> | |
18 | #include <linux/of.h> | |
19 | #include <linux/of_address.h> | |
20 | #include <linux/of_irq.h> | |
21 | #include <linux/of_pci.h> | |
22 | #include <linux/pci.h> | |
23 | #include <linux/slab.h> | |
24 | ||
25 | #include <asm/irq.h> | |
26 | #include <asm-generic/msi.h> | |
27 | ||
28 | /* MSIX message address format: local GIC target */ | |
29 | #define ALPINE_MSIX_SPI_TARGET_CLUSTER0 BIT(16) | |
30 | ||
31 | struct alpine_msix_data { | |
32 | spinlock_t msi_map_lock; | |
33 | phys_addr_t addr; | |
34 | u32 spi_first; /* The SGI number that MSIs start */ | |
35 | u32 num_spis; /* The number of SGIs for MSIs */ | |
36 | unsigned long *msi_map; | |
37 | }; | |
38 | ||
39 | static void alpine_msix_mask_msi_irq(struct irq_data *d) | |
40 | { | |
41 | pci_msi_mask_irq(d); | |
42 | irq_chip_mask_parent(d); | |
43 | } | |
44 | ||
45 | static void alpine_msix_unmask_msi_irq(struct irq_data *d) | |
46 | { | |
47 | pci_msi_unmask_irq(d); | |
48 | irq_chip_unmask_parent(d); | |
49 | } | |
50 | ||
51 | static struct irq_chip alpine_msix_irq_chip = { | |
52 | .name = "MSIx", | |
53 | .irq_mask = alpine_msix_mask_msi_irq, | |
54 | .irq_unmask = alpine_msix_unmask_msi_irq, | |
55 | .irq_eoi = irq_chip_eoi_parent, | |
56 | .irq_set_affinity = irq_chip_set_affinity_parent, | |
57 | }; | |
58 | ||
59 | static int alpine_msix_allocate_sgi(struct alpine_msix_data *priv, int num_req) | |
60 | { | |
61 | int first; | |
62 | ||
63 | spin_lock(&priv->msi_map_lock); | |
64 | ||
65 | first = bitmap_find_next_zero_area(priv->msi_map, priv->num_spis, 0, | |
66 | num_req, 0); | |
67 | if (first >= priv->num_spis) { | |
68 | spin_unlock(&priv->msi_map_lock); | |
69 | return -ENOSPC; | |
70 | } | |
71 | ||
72 | bitmap_set(priv->msi_map, first, num_req); | |
73 | ||
74 | spin_unlock(&priv->msi_map_lock); | |
75 | ||
76 | return priv->spi_first + first; | |
77 | } | |
78 | ||
79 | static void alpine_msix_free_sgi(struct alpine_msix_data *priv, unsigned sgi, | |
80 | int num_req) | |
81 | { | |
82 | int first = sgi - priv->spi_first; | |
83 | ||
84 | spin_lock(&priv->msi_map_lock); | |
85 | ||
86 | bitmap_clear(priv->msi_map, first, num_req); | |
87 | ||
88 | spin_unlock(&priv->msi_map_lock); | |
89 | } | |
90 | ||
91 | static void alpine_msix_compose_msi_msg(struct irq_data *data, | |
92 | struct msi_msg *msg) | |
93 | { | |
94 | struct alpine_msix_data *priv = irq_data_get_irq_chip_data(data); | |
95 | phys_addr_t msg_addr = priv->addr; | |
96 | ||
97 | msg_addr |= (data->hwirq << 3); | |
98 | ||
99 | msg->address_hi = upper_32_bits(msg_addr); | |
100 | msg->address_lo = lower_32_bits(msg_addr); | |
101 | msg->data = 0; | |
102 | } | |
103 | ||
104 | static struct msi_domain_info alpine_msix_domain_info = { | |
105 | .flags = MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS | | |
106 | MSI_FLAG_PCI_MSIX, | |
107 | .chip = &alpine_msix_irq_chip, | |
108 | }; | |
109 | ||
110 | static struct irq_chip middle_irq_chip = { | |
111 | .name = "alpine_msix_middle", | |
112 | .irq_mask = irq_chip_mask_parent, | |
113 | .irq_unmask = irq_chip_unmask_parent, | |
114 | .irq_eoi = irq_chip_eoi_parent, | |
115 | .irq_set_affinity = irq_chip_set_affinity_parent, | |
116 | .irq_compose_msi_msg = alpine_msix_compose_msi_msg, | |
117 | }; | |
118 | ||
119 | static int alpine_msix_gic_domain_alloc(struct irq_domain *domain, | |
120 | unsigned int virq, int sgi) | |
121 | { | |
122 | struct irq_fwspec fwspec; | |
123 | struct irq_data *d; | |
124 | int ret; | |
125 | ||
126 | if (!is_of_node(domain->parent->fwnode)) | |
127 | return -EINVAL; | |
128 | ||
129 | fwspec.fwnode = domain->parent->fwnode; | |
130 | fwspec.param_count = 3; | |
131 | fwspec.param[0] = 0; | |
132 | fwspec.param[1] = sgi; | |
133 | fwspec.param[2] = IRQ_TYPE_EDGE_RISING; | |
134 | ||
135 | ret = irq_domain_alloc_irqs_parent(domain, virq, 1, &fwspec); | |
136 | if (ret) | |
137 | return ret; | |
138 | ||
139 | d = irq_domain_get_irq_data(domain->parent, virq); | |
140 | d->chip->irq_set_type(d, IRQ_TYPE_EDGE_RISING); | |
141 | ||
142 | return 0; | |
143 | } | |
144 | ||
145 | static int alpine_msix_middle_domain_alloc(struct irq_domain *domain, | |
146 | unsigned int virq, | |
147 | unsigned int nr_irqs, void *args) | |
148 | { | |
149 | struct alpine_msix_data *priv = domain->host_data; | |
150 | int sgi, err, i; | |
151 | ||
152 | sgi = alpine_msix_allocate_sgi(priv, nr_irqs); | |
153 | if (sgi < 0) | |
154 | return sgi; | |
155 | ||
156 | for (i = 0; i < nr_irqs; i++) { | |
157 | err = alpine_msix_gic_domain_alloc(domain, virq + i, sgi + i); | |
158 | if (err) | |
159 | goto err_sgi; | |
160 | ||
161 | irq_domain_set_hwirq_and_chip(domain, virq + i, sgi + i, | |
162 | &middle_irq_chip, priv); | |
163 | } | |
164 | ||
165 | return 0; | |
166 | ||
167 | err_sgi: | |
168 | while (--i >= 0) | |
169 | irq_domain_free_irqs_parent(domain, virq, i); | |
170 | alpine_msix_free_sgi(priv, sgi, nr_irqs); | |
171 | return err; | |
172 | } | |
173 | ||
174 | static void alpine_msix_middle_domain_free(struct irq_domain *domain, | |
175 | unsigned int virq, | |
176 | unsigned int nr_irqs) | |
177 | { | |
178 | struct irq_data *d = irq_domain_get_irq_data(domain, virq); | |
179 | struct alpine_msix_data *priv = irq_data_get_irq_chip_data(d); | |
180 | ||
181 | irq_domain_free_irqs_parent(domain, virq, nr_irqs); | |
182 | alpine_msix_free_sgi(priv, d->hwirq, nr_irqs); | |
183 | } | |
184 | ||
185 | static const struct irq_domain_ops alpine_msix_middle_domain_ops = { | |
186 | .alloc = alpine_msix_middle_domain_alloc, | |
187 | .free = alpine_msix_middle_domain_free, | |
188 | }; | |
189 | ||
190 | static int alpine_msix_init_domains(struct alpine_msix_data *priv, | |
191 | struct device_node *node) | |
192 | { | |
193 | struct irq_domain *middle_domain, *msi_domain, *gic_domain; | |
194 | struct device_node *gic_node; | |
195 | ||
196 | gic_node = of_irq_find_parent(node); | |
197 | if (!gic_node) { | |
198 | pr_err("Failed to find the GIC node\n"); | |
199 | return -ENODEV; | |
200 | } | |
201 | ||
202 | gic_domain = irq_find_host(gic_node); | |
203 | if (!gic_domain) { | |
204 | pr_err("Failed to find the GIC domain\n"); | |
205 | return -ENXIO; | |
206 | } | |
207 | ||
208 | middle_domain = irq_domain_add_tree(NULL, | |
209 | &alpine_msix_middle_domain_ops, | |
210 | priv); | |
211 | if (!middle_domain) { | |
212 | pr_err("Failed to create the MSIX middle domain\n"); | |
213 | return -ENOMEM; | |
214 | } | |
215 | ||
216 | middle_domain->parent = gic_domain; | |
217 | ||
218 | msi_domain = pci_msi_create_irq_domain(of_node_to_fwnode(node), | |
219 | &alpine_msix_domain_info, | |
220 | middle_domain); | |
221 | if (!msi_domain) { | |
222 | pr_err("Failed to create MSI domain\n"); | |
143d36a3 | 223 | irq_domain_remove(middle_domain); |
e6b78f2c AT |
224 | return -ENOMEM; |
225 | } | |
226 | ||
227 | return 0; | |
228 | } | |
229 | ||
230 | static int alpine_msix_init(struct device_node *node, | |
231 | struct device_node *parent) | |
232 | { | |
233 | struct alpine_msix_data *priv; | |
234 | struct resource res; | |
235 | int ret; | |
236 | ||
237 | priv = kzalloc(sizeof(*priv), GFP_KERNEL); | |
238 | if (!priv) | |
239 | return -ENOMEM; | |
240 | ||
241 | spin_lock_init(&priv->msi_map_lock); | |
242 | ||
243 | ret = of_address_to_resource(node, 0, &res); | |
244 | if (ret) { | |
245 | pr_err("Failed to allocate resource\n"); | |
246 | goto err_priv; | |
247 | } | |
248 | ||
249 | /* | |
250 | * The 20 least significant bits of addr provide direct information | |
251 | * regarding the interrupt destination. | |
252 | * | |
253 | * To select the primary GIC as the target GIC, bits [18:17] must be set | |
254 | * to 0x0. In this case, bit 16 (SPI_TARGET_CLUSTER0) must be set. | |
255 | */ | |
256 | priv->addr = res.start & GENMASK_ULL(63,20); | |
257 | priv->addr |= ALPINE_MSIX_SPI_TARGET_CLUSTER0; | |
258 | ||
259 | if (of_property_read_u32(node, "al,msi-base-spi", &priv->spi_first)) { | |
260 | pr_err("Unable to parse MSI base\n"); | |
261 | ret = -EINVAL; | |
262 | goto err_priv; | |
263 | } | |
264 | ||
265 | if (of_property_read_u32(node, "al,msi-num-spis", &priv->num_spis)) { | |
266 | pr_err("Unable to parse MSI numbers\n"); | |
267 | ret = -EINVAL; | |
268 | goto err_priv; | |
269 | } | |
270 | ||
271 | priv->msi_map = kzalloc(sizeof(*priv->msi_map) * BITS_TO_LONGS(priv->num_spis), | |
272 | GFP_KERNEL); | |
273 | if (!priv->msi_map) { | |
274 | ret = -ENOMEM; | |
275 | goto err_priv; | |
276 | } | |
277 | ||
278 | pr_debug("Registering %d msixs, starting at %d\n", | |
279 | priv->num_spis, priv->spi_first); | |
280 | ||
281 | ret = alpine_msix_init_domains(priv, node); | |
282 | if (ret) | |
283 | goto err_map; | |
284 | ||
285 | return 0; | |
286 | ||
287 | err_map: | |
288 | kfree(priv->msi_map); | |
289 | err_priv: | |
290 | kfree(priv); | |
291 | return ret; | |
292 | } | |
293 | IRQCHIP_DECLARE(alpine_msix, "al,alpine-msix", alpine_msix_init); |