1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (C) 2015 HiSilicon Limited, All Rights Reserved. |
4 | * Author: Jun Ma <majun258@huawei.com> |
5 | * Author: Yun Wu <wuyun.wu@huawei.com> |
6 | */ |
7 | |
8 | #include <linux/acpi.h> |
9 | #include <linux/interrupt.h> |
10 | #include <linux/irqchip.h> |
11 | #include <linux/module.h> |
12 | #include <linux/msi.h> |
13 | #include <linux/of_address.h> |
14 | #include <linux/of_irq.h> |
15 | #include <linux/of_platform.h> |
16 | #include <linux/platform_device.h> |
17 | #include <linux/slab.h> |
18 | |
19 | /* Interrupt numbers per mbigen node supported */ |
20 | #define IRQS_PER_MBIGEN_NODE 128 |
21 | |
22 | /* 64 irqs (Pin0-pin63) are reserved for each mbigen chip */ |
23 | #define RESERVED_IRQ_PER_MBIGEN_CHIP 64 |
24 | |
25 | /* The maximum IRQ pin number of mbigen chip(start from 0) */ |
26 | #define MAXIMUM_IRQ_PIN_NUM 1407 |
27 | |
28 | /* |
29 | * In mbigen vector register |
30 | * bit[21:12]: event id value |
31 | * bit[11:0]: device id |
32 | */ |
33 | #define IRQ_EVENT_ID_SHIFT 12 |
34 | #define IRQ_EVENT_ID_MASK 0x3ff |
35 | |
36 | /* register range of each mbigen node */ |
37 | #define MBIGEN_NODE_OFFSET 0x1000 |
38 | |
39 | /* offset of vector register in mbigen node */ |
40 | #define REG_MBIGEN_VEC_OFFSET 0x200 |
41 | |
42 | /* |
43 | * offset of clear register in mbigen node |
44 | * This register is used to clear the status |
45 | * of interrupt |
46 | */ |
47 | #define REG_MBIGEN_CLEAR_OFFSET 0xa000 |
48 | |
49 | /* |
50 | * offset of interrupt type register |
51 | * This register is used to configure interrupt |
52 | * trigger type |
53 | */ |
54 | #define REG_MBIGEN_TYPE_OFFSET 0x0 |
55 | |
56 | /** |
57 | * struct mbigen_device - holds the information of mbigen device. |
58 | * |
59 | * @pdev: pointer to the platform device structure of mbigen chip. |
60 | * @base: mapped address of this mbigen chip. |
61 | */ |
62 | struct mbigen_device { |
63 | struct platform_device *pdev; |
64 | void __iomem *base; |
65 | }; |
66 | |
67 | static inline unsigned int get_mbigen_vec_reg(irq_hw_number_t hwirq) |
68 | { |
69 | unsigned int nid, pin; |
70 | |
71 | hwirq -= RESERVED_IRQ_PER_MBIGEN_CHIP; |
72 | nid = hwirq / IRQS_PER_MBIGEN_NODE + 1; |
73 | pin = hwirq % IRQS_PER_MBIGEN_NODE; |
74 | |
75 | return pin * 4 + nid * MBIGEN_NODE_OFFSET |
76 | + REG_MBIGEN_VEC_OFFSET; |
77 | } |
78 | |
79 | static inline void get_mbigen_type_reg(irq_hw_number_t hwirq, |
80 | u32 *mask, u32 *addr) |
81 | { |
82 | unsigned int nid, irq_ofst, ofst; |
83 | |
84 | hwirq -= RESERVED_IRQ_PER_MBIGEN_CHIP; |
85 | nid = hwirq / IRQS_PER_MBIGEN_NODE + 1; |
86 | irq_ofst = hwirq % IRQS_PER_MBIGEN_NODE; |
87 | |
88 | *mask = 1 << (irq_ofst % 32); |
89 | ofst = irq_ofst / 32 * 4; |
90 | |
91 | *addr = ofst + nid * MBIGEN_NODE_OFFSET |
92 | + REG_MBIGEN_TYPE_OFFSET; |
93 | } |
94 | |
95 | static inline void get_mbigen_clear_reg(irq_hw_number_t hwirq, |
96 | u32 *mask, u32 *addr) |
97 | { |
98 | unsigned int ofst = (hwirq / 32) * 4; |
99 | |
100 | *mask = 1 << (hwirq % 32); |
101 | *addr = ofst + REG_MBIGEN_CLEAR_OFFSET; |
102 | } |
103 | |
104 | static void mbigen_eoi_irq(struct irq_data *data) |
105 | { |
106 | void __iomem *base = data->chip_data; |
107 | u32 mask, addr; |
108 | |
109 | get_mbigen_clear_reg(hwirq: data->hwirq, mask: &mask, addr: &addr); |
110 | |
111 | writel_relaxed(mask, base + addr); |
112 | |
113 | irq_chip_eoi_parent(data); |
114 | } |
115 | |
116 | static int mbigen_set_type(struct irq_data *data, unsigned int type) |
117 | { |
118 | void __iomem *base = data->chip_data; |
119 | u32 mask, addr, val; |
120 | |
121 | if (type != IRQ_TYPE_LEVEL_HIGH && type != IRQ_TYPE_EDGE_RISING) |
122 | return -EINVAL; |
123 | |
124 | get_mbigen_type_reg(hwirq: data->hwirq, mask: &mask, addr: &addr); |
125 | |
126 | val = readl_relaxed(base + addr); |
127 | |
128 | if (type == IRQ_TYPE_LEVEL_HIGH) |
129 | val |= mask; |
130 | else |
131 | val &= ~mask; |
132 | |
133 | writel_relaxed(val, base + addr); |
134 | |
135 | return 0; |
136 | } |
137 | |
138 | static struct irq_chip mbigen_irq_chip = { |
139 | .name = "mbigen-v2" , |
140 | .irq_mask = irq_chip_mask_parent, |
141 | .irq_unmask = irq_chip_unmask_parent, |
142 | .irq_eoi = mbigen_eoi_irq, |
143 | .irq_set_type = mbigen_set_type, |
144 | .irq_set_affinity = irq_chip_set_affinity_parent, |
145 | }; |
146 | |
147 | static void mbigen_write_msg(struct msi_desc *desc, struct msi_msg *msg) |
148 | { |
149 | struct irq_data *d = irq_get_irq_data(irq: desc->irq); |
150 | void __iomem *base = d->chip_data; |
151 | u32 val; |
152 | |
153 | if (!msg->address_lo && !msg->address_hi) |
154 | return; |
155 | |
156 | base += get_mbigen_vec_reg(hwirq: d->hwirq); |
157 | val = readl_relaxed(base); |
158 | |
159 | val &= ~(IRQ_EVENT_ID_MASK << IRQ_EVENT_ID_SHIFT); |
160 | val |= (msg->data << IRQ_EVENT_ID_SHIFT); |
161 | |
162 | /* The address of doorbell is encoded in mbigen register by default |
163 | * So,we don't need to program the doorbell address at here |
164 | */ |
165 | writel_relaxed(val, base); |
166 | } |
167 | |
168 | static int mbigen_domain_translate(struct irq_domain *d, |
169 | struct irq_fwspec *fwspec, |
170 | unsigned long *hwirq, |
171 | unsigned int *type) |
172 | { |
173 | if (is_of_node(fwnode: fwspec->fwnode) || is_acpi_device_node(fwnode: fwspec->fwnode)) { |
174 | if (fwspec->param_count != 2) |
175 | return -EINVAL; |
176 | |
177 | if ((fwspec->param[0] > MAXIMUM_IRQ_PIN_NUM) || |
178 | (fwspec->param[0] < RESERVED_IRQ_PER_MBIGEN_CHIP)) |
179 | return -EINVAL; |
180 | else |
181 | *hwirq = fwspec->param[0]; |
182 | |
183 | /* If there is no valid irq type, just use the default type */ |
184 | if ((fwspec->param[1] == IRQ_TYPE_EDGE_RISING) || |
185 | (fwspec->param[1] == IRQ_TYPE_LEVEL_HIGH)) |
186 | *type = fwspec->param[1]; |
187 | else |
188 | return -EINVAL; |
189 | |
190 | return 0; |
191 | } |
192 | return -EINVAL; |
193 | } |
194 | |
195 | static int mbigen_irq_domain_alloc(struct irq_domain *domain, |
196 | unsigned int virq, |
197 | unsigned int nr_irqs, |
198 | void *args) |
199 | { |
200 | struct irq_fwspec *fwspec = args; |
201 | irq_hw_number_t hwirq; |
202 | unsigned int type; |
203 | struct mbigen_device *mgn_chip; |
204 | int i, err; |
205 | |
206 | err = mbigen_domain_translate(d: domain, fwspec, hwirq: &hwirq, type: &type); |
207 | if (err) |
208 | return err; |
209 | |
210 | err = platform_msi_device_domain_alloc(domain, virq, nr_irqs); |
211 | if (err) |
212 | return err; |
213 | |
214 | mgn_chip = platform_msi_get_host_data(domain); |
215 | |
216 | for (i = 0; i < nr_irqs; i++) |
217 | irq_domain_set_hwirq_and_chip(domain, virq: virq + i, hwirq: hwirq + i, |
218 | chip: &mbigen_irq_chip, chip_data: mgn_chip->base); |
219 | |
220 | return 0; |
221 | } |
222 | |
223 | static void mbigen_irq_domain_free(struct irq_domain *domain, unsigned int virq, |
224 | unsigned int nr_irqs) |
225 | { |
226 | platform_msi_device_domain_free(domain, virq, nvec: nr_irqs); |
227 | } |
228 | |
229 | static const struct irq_domain_ops mbigen_domain_ops = { |
230 | .translate = mbigen_domain_translate, |
231 | .alloc = mbigen_irq_domain_alloc, |
232 | .free = mbigen_irq_domain_free, |
233 | }; |
234 | |
235 | static int mbigen_of_create_domain(struct platform_device *pdev, |
236 | struct mbigen_device *mgn_chip) |
237 | { |
238 | struct device *parent; |
239 | struct platform_device *child; |
240 | struct irq_domain *domain; |
241 | struct device_node *np; |
242 | u32 num_pins; |
243 | int ret = 0; |
244 | |
245 | parent = bus_get_dev_root(bus: &platform_bus_type); |
246 | if (!parent) |
247 | return -ENODEV; |
248 | |
249 | for_each_child_of_node(pdev->dev.of_node, np) { |
250 | if (!of_property_read_bool(np, propname: "interrupt-controller" )) |
251 | continue; |
252 | |
253 | child = of_platform_device_create(np, NULL, parent); |
254 | if (!child) { |
255 | ret = -ENOMEM; |
256 | break; |
257 | } |
258 | |
259 | if (of_property_read_u32(np: child->dev.of_node, propname: "num-pins" , |
260 | out_value: &num_pins) < 0) { |
261 | dev_err(&pdev->dev, "No num-pins property\n" ); |
262 | ret = -EINVAL; |
263 | break; |
264 | } |
265 | |
266 | domain = platform_msi_create_device_domain(&child->dev, num_pins, |
267 | mbigen_write_msg, |
268 | &mbigen_domain_ops, |
269 | mgn_chip); |
270 | if (!domain) { |
271 | ret = -ENOMEM; |
272 | break; |
273 | } |
274 | } |
275 | |
276 | put_device(dev: parent); |
277 | if (ret) |
278 | of_node_put(node: np); |
279 | |
280 | return ret; |
281 | } |
282 | |
283 | #ifdef CONFIG_ACPI |
284 | static const struct acpi_device_id mbigen_acpi_match[] = { |
285 | { "HISI0152" , 0 }, |
286 | {} |
287 | }; |
288 | MODULE_DEVICE_TABLE(acpi, mbigen_acpi_match); |
289 | |
290 | static int mbigen_acpi_create_domain(struct platform_device *pdev, |
291 | struct mbigen_device *mgn_chip) |
292 | { |
293 | struct irq_domain *domain; |
294 | u32 num_pins = 0; |
295 | int ret; |
296 | |
297 | /* |
298 | * "num-pins" is the total number of interrupt pins implemented in |
299 | * this mbigen instance, and mbigen is an interrupt controller |
300 | * connected to ITS converting wired interrupts into MSI, so we |
301 | * use "num-pins" to alloc MSI vectors which are needed by client |
302 | * devices connected to it. |
303 | * |
304 | * Here is the DSDT device node used for mbigen in firmware: |
305 | * Device(MBI0) { |
306 | * Name(_HID, "HISI0152") |
307 | * Name(_UID, Zero) |
308 | * Name(_CRS, ResourceTemplate() { |
309 | * Memory32Fixed(ReadWrite, 0xa0080000, 0x10000) |
310 | * }) |
311 | * |
312 | * Name(_DSD, Package () { |
313 | * ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"), |
314 | * Package () { |
315 | * Package () {"num-pins", 378} |
316 | * } |
317 | * }) |
318 | * } |
319 | */ |
320 | ret = device_property_read_u32(dev: &pdev->dev, propname: "num-pins" , val: &num_pins); |
321 | if (ret || num_pins == 0) |
322 | return -EINVAL; |
323 | |
324 | domain = platform_msi_create_device_domain(&pdev->dev, num_pins, |
325 | mbigen_write_msg, |
326 | &mbigen_domain_ops, |
327 | mgn_chip); |
328 | if (!domain) |
329 | return -ENOMEM; |
330 | |
331 | return 0; |
332 | } |
333 | #else |
334 | static inline int mbigen_acpi_create_domain(struct platform_device *pdev, |
335 | struct mbigen_device *mgn_chip) |
336 | { |
337 | return -ENODEV; |
338 | } |
339 | #endif |
340 | |
341 | static int mbigen_device_probe(struct platform_device *pdev) |
342 | { |
343 | struct mbigen_device *mgn_chip; |
344 | struct resource *res; |
345 | int err; |
346 | |
347 | mgn_chip = devm_kzalloc(dev: &pdev->dev, size: sizeof(*mgn_chip), GFP_KERNEL); |
348 | if (!mgn_chip) |
349 | return -ENOMEM; |
350 | |
351 | mgn_chip->pdev = pdev; |
352 | |
353 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
354 | if (!res) |
355 | return -EINVAL; |
356 | |
357 | mgn_chip->base = devm_ioremap(dev: &pdev->dev, offset: res->start, |
358 | size: resource_size(res)); |
359 | if (!mgn_chip->base) { |
360 | dev_err(&pdev->dev, "failed to ioremap %pR\n" , res); |
361 | return -ENOMEM; |
362 | } |
363 | |
364 | if (IS_ENABLED(CONFIG_OF) && pdev->dev.of_node) |
365 | err = mbigen_of_create_domain(pdev, mgn_chip); |
366 | else if (ACPI_COMPANION(&pdev->dev)) |
367 | err = mbigen_acpi_create_domain(pdev, mgn_chip); |
368 | else |
369 | err = -EINVAL; |
370 | |
371 | if (err) { |
372 | dev_err(&pdev->dev, "Failed to create mbi-gen irqdomain\n" ); |
373 | return err; |
374 | } |
375 | |
376 | platform_set_drvdata(pdev, data: mgn_chip); |
377 | return 0; |
378 | } |
379 | |
380 | static const struct of_device_id mbigen_of_match[] = { |
381 | { .compatible = "hisilicon,mbigen-v2" }, |
382 | { /* END */ } |
383 | }; |
384 | MODULE_DEVICE_TABLE(of, mbigen_of_match); |
385 | |
386 | static struct platform_driver mbigen_platform_driver = { |
387 | .driver = { |
388 | .name = "Hisilicon MBIGEN-V2" , |
389 | .of_match_table = mbigen_of_match, |
390 | .acpi_match_table = ACPI_PTR(mbigen_acpi_match), |
391 | .suppress_bind_attrs = true, |
392 | }, |
393 | .probe = mbigen_device_probe, |
394 | }; |
395 | |
396 | module_platform_driver(mbigen_platform_driver); |
397 | |
398 | MODULE_AUTHOR("Jun Ma <majun258@huawei.com>" ); |
399 | MODULE_AUTHOR("Yun Wu <wuyun.wu@huawei.com>" ); |
400 | MODULE_DESCRIPTION("HiSilicon MBI Generator driver" ); |
401 | |