Commit | Line | Data |
---|---|---|
632dcc2c JY |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * Copyright (C) 2020, Jiaxun Yang <jiaxun.yang@flygoat.com> | |
4 | * Loongson PCH MSI support | |
5 | */ | |
6 | ||
7 | #define pr_fmt(fmt) "pch-msi: " fmt | |
8 | ||
9 | #include <linux/irqchip.h> | |
10 | #include <linux/msi.h> | |
11 | #include <linux/of.h> | |
12 | #include <linux/of_address.h> | |
13 | #include <linux/of_irq.h> | |
14 | #include <linux/of_pci.h> | |
15 | #include <linux/pci.h> | |
16 | #include <linux/slab.h> | |
17 | ||
18 | struct pch_msi_data { | |
19 | struct mutex msi_map_lock; | |
20 | phys_addr_t doorbell; | |
21 | u32 irq_first; /* The vector number that MSIs starts */ | |
22 | u32 num_irqs; /* The number of vectors for MSIs */ | |
23 | unsigned long *msi_map; | |
24 | }; | |
25 | ||
26 | static void pch_msi_mask_msi_irq(struct irq_data *d) | |
27 | { | |
28 | pci_msi_mask_irq(d); | |
29 | irq_chip_mask_parent(d); | |
30 | } | |
31 | ||
32 | static void pch_msi_unmask_msi_irq(struct irq_data *d) | |
33 | { | |
34 | irq_chip_unmask_parent(d); | |
35 | pci_msi_unmask_irq(d); | |
36 | } | |
37 | ||
38 | static struct irq_chip pch_msi_irq_chip = { | |
39 | .name = "PCH PCI MSI", | |
40 | .irq_mask = pch_msi_mask_msi_irq, | |
41 | .irq_unmask = pch_msi_unmask_msi_irq, | |
42 | .irq_ack = irq_chip_ack_parent, | |
43 | .irq_set_affinity = irq_chip_set_affinity_parent, | |
44 | }; | |
45 | ||
46 | static int pch_msi_allocate_hwirq(struct pch_msi_data *priv, int num_req) | |
47 | { | |
48 | int first; | |
49 | ||
50 | mutex_lock(&priv->msi_map_lock); | |
51 | ||
52 | first = bitmap_find_free_region(priv->msi_map, priv->num_irqs, | |
53 | get_count_order(num_req)); | |
54 | if (first < 0) { | |
55 | mutex_unlock(&priv->msi_map_lock); | |
56 | return -ENOSPC; | |
57 | } | |
58 | ||
59 | mutex_unlock(&priv->msi_map_lock); | |
60 | ||
61 | return priv->irq_first + first; | |
62 | } | |
63 | ||
64 | static void pch_msi_free_hwirq(struct pch_msi_data *priv, | |
65 | int hwirq, int num_req) | |
66 | { | |
67 | int first = hwirq - priv->irq_first; | |
68 | ||
69 | mutex_lock(&priv->msi_map_lock); | |
70 | bitmap_release_region(priv->msi_map, first, get_count_order(num_req)); | |
71 | mutex_unlock(&priv->msi_map_lock); | |
72 | } | |
73 | ||
74 | static void pch_msi_compose_msi_msg(struct irq_data *data, | |
75 | struct msi_msg *msg) | |
76 | { | |
77 | struct pch_msi_data *priv = irq_data_get_irq_chip_data(data); | |
78 | ||
79 | msg->address_hi = upper_32_bits(priv->doorbell); | |
80 | msg->address_lo = lower_32_bits(priv->doorbell); | |
81 | msg->data = data->hwirq; | |
82 | } | |
83 | ||
84 | static struct msi_domain_info pch_msi_domain_info = { | |
85 | .flags = MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS | | |
86 | MSI_FLAG_MULTI_PCI_MSI | MSI_FLAG_PCI_MSIX, | |
87 | .chip = &pch_msi_irq_chip, | |
88 | }; | |
89 | ||
90 | static struct irq_chip middle_irq_chip = { | |
91 | .name = "PCH MSI", | |
92 | .irq_mask = irq_chip_mask_parent, | |
93 | .irq_unmask = irq_chip_unmask_parent, | |
94 | .irq_ack = irq_chip_ack_parent, | |
95 | .irq_set_affinity = irq_chip_set_affinity_parent, | |
96 | .irq_compose_msi_msg = pch_msi_compose_msi_msg, | |
97 | }; | |
98 | ||
99 | static int pch_msi_parent_domain_alloc(struct irq_domain *domain, | |
100 | unsigned int virq, int hwirq) | |
101 | { | |
102 | struct irq_fwspec fwspec; | |
632dcc2c JY |
103 | |
104 | fwspec.fwnode = domain->parent->fwnode; | |
105 | fwspec.param_count = 1; | |
106 | fwspec.param[0] = hwirq; | |
107 | ||
b10cbca8 | 108 | return irq_domain_alloc_irqs_parent(domain, virq, 1, &fwspec); |
632dcc2c JY |
109 | } |
110 | ||
111 | static int pch_msi_middle_domain_alloc(struct irq_domain *domain, | |
112 | unsigned int virq, | |
113 | unsigned int nr_irqs, void *args) | |
114 | { | |
115 | struct pch_msi_data *priv = domain->host_data; | |
116 | int hwirq, err, i; | |
117 | ||
118 | hwirq = pch_msi_allocate_hwirq(priv, nr_irqs); | |
119 | if (hwirq < 0) | |
120 | return hwirq; | |
121 | ||
122 | for (i = 0; i < nr_irqs; i++) { | |
123 | err = pch_msi_parent_domain_alloc(domain, virq + i, hwirq + i); | |
124 | if (err) | |
125 | goto err_hwirq; | |
126 | ||
127 | irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq + i, | |
128 | &middle_irq_chip, priv); | |
129 | } | |
130 | ||
131 | return 0; | |
132 | ||
133 | err_hwirq: | |
134 | pch_msi_free_hwirq(priv, hwirq, nr_irqs); | |
135 | irq_domain_free_irqs_parent(domain, virq, i - 1); | |
136 | ||
137 | return err; | |
138 | } | |
139 | ||
140 | static void pch_msi_middle_domain_free(struct irq_domain *domain, | |
141 | unsigned int virq, | |
142 | unsigned int nr_irqs) | |
143 | { | |
144 | struct irq_data *d = irq_domain_get_irq_data(domain, virq); | |
145 | struct pch_msi_data *priv = irq_data_get_irq_chip_data(d); | |
146 | ||
147 | irq_domain_free_irqs_parent(domain, virq, nr_irqs); | |
148 | pch_msi_free_hwirq(priv, d->hwirq, nr_irqs); | |
149 | } | |
150 | ||
151 | static const struct irq_domain_ops pch_msi_middle_domain_ops = { | |
152 | .alloc = pch_msi_middle_domain_alloc, | |
153 | .free = pch_msi_middle_domain_free, | |
154 | }; | |
155 | ||
156 | static int pch_msi_init_domains(struct pch_msi_data *priv, | |
157 | struct device_node *node, | |
158 | struct irq_domain *parent) | |
159 | { | |
160 | struct irq_domain *middle_domain, *msi_domain; | |
161 | ||
162 | middle_domain = irq_domain_create_linear(of_node_to_fwnode(node), | |
163 | priv->num_irqs, | |
164 | &pch_msi_middle_domain_ops, | |
165 | priv); | |
166 | if (!middle_domain) { | |
167 | pr_err("Failed to create the MSI middle domain\n"); | |
168 | return -ENOMEM; | |
169 | } | |
170 | ||
171 | middle_domain->parent = parent; | |
172 | irq_domain_update_bus_token(middle_domain, DOMAIN_BUS_NEXUS); | |
173 | ||
174 | msi_domain = pci_msi_create_irq_domain(of_node_to_fwnode(node), | |
175 | &pch_msi_domain_info, | |
176 | middle_domain); | |
177 | if (!msi_domain) { | |
178 | pr_err("Failed to create PCI MSI domain\n"); | |
179 | irq_domain_remove(middle_domain); | |
180 | return -ENOMEM; | |
181 | } | |
182 | ||
183 | return 0; | |
184 | } | |
185 | ||
186 | static int pch_msi_init(struct device_node *node, | |
187 | struct device_node *parent) | |
188 | { | |
189 | struct pch_msi_data *priv; | |
190 | struct irq_domain *parent_domain; | |
191 | struct resource res; | |
192 | int ret; | |
193 | ||
194 | parent_domain = irq_find_host(parent); | |
195 | if (!parent_domain) { | |
196 | pr_err("Failed to find the parent domain\n"); | |
197 | return -ENXIO; | |
198 | } | |
199 | ||
200 | priv = kzalloc(sizeof(*priv), GFP_KERNEL); | |
201 | if (!priv) | |
202 | return -ENOMEM; | |
203 | ||
204 | mutex_init(&priv->msi_map_lock); | |
205 | ||
206 | ret = of_address_to_resource(node, 0, &res); | |
207 | if (ret) { | |
208 | pr_err("Failed to allocate resource\n"); | |
209 | goto err_priv; | |
210 | } | |
211 | ||
212 | priv->doorbell = res.start; | |
213 | ||
214 | if (of_property_read_u32(node, "loongson,msi-base-vec", | |
215 | &priv->irq_first)) { | |
216 | pr_err("Unable to parse MSI vec base\n"); | |
217 | ret = -EINVAL; | |
218 | goto err_priv; | |
219 | } | |
220 | ||
221 | if (of_property_read_u32(node, "loongson,msi-num-vecs", | |
222 | &priv->num_irqs)) { | |
223 | pr_err("Unable to parse MSI vec number\n"); | |
224 | ret = -EINVAL; | |
225 | goto err_priv; | |
226 | } | |
227 | ||
228 | priv->msi_map = bitmap_alloc(priv->num_irqs, GFP_KERNEL); | |
229 | if (!priv->msi_map) { | |
230 | ret = -ENOMEM; | |
231 | goto err_priv; | |
232 | } | |
233 | ||
234 | pr_debug("Registering %d MSIs, starting at %d\n", | |
235 | priv->num_irqs, priv->irq_first); | |
236 | ||
237 | ret = pch_msi_init_domains(priv, node, parent_domain); | |
238 | if (ret) | |
239 | goto err_map; | |
240 | ||
241 | return 0; | |
242 | ||
243 | err_map: | |
244 | kfree(priv->msi_map); | |
245 | err_priv: | |
246 | kfree(priv); | |
247 | return ret; | |
248 | } | |
249 | ||
250 | IRQCHIP_DECLARE(pch_msi, "loongson,pch-msi-1.0", pch_msi_init); |