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_node_offset(unsigned int nid) |
68 | { |
69 | unsigned int offset = nid * MBIGEN_NODE_OFFSET; |
70 | |
71 | /* |
72 | * To avoid touched clear register in unexpected way, we need to directly |
73 | * skip clear register when access to more than 10 mbigen nodes. |
74 | */ |
75 | if (nid >= (REG_MBIGEN_CLEAR_OFFSET / MBIGEN_NODE_OFFSET)) |
76 | offset += MBIGEN_NODE_OFFSET; |
77 | |
78 | return offset; |
79 | } |
80 | |
81 | static inline unsigned int get_mbigen_vec_reg(irq_hw_number_t hwirq) |
82 | { |
83 | unsigned int nid, pin; |
84 | |
85 | hwirq -= RESERVED_IRQ_PER_MBIGEN_CHIP; |
86 | nid = hwirq / IRQS_PER_MBIGEN_NODE + 1; |
87 | pin = hwirq % IRQS_PER_MBIGEN_NODE; |
88 | |
89 | return pin * 4 + get_mbigen_node_offset(nid) + REG_MBIGEN_VEC_OFFSET; |
90 | } |
91 | |
92 | static inline void get_mbigen_type_reg(irq_hw_number_t hwirq, |
93 | u32 *mask, u32 *addr) |
94 | { |
95 | unsigned int nid, irq_ofst, ofst; |
96 | |
97 | hwirq -= RESERVED_IRQ_PER_MBIGEN_CHIP; |
98 | nid = hwirq / IRQS_PER_MBIGEN_NODE + 1; |
99 | irq_ofst = hwirq % IRQS_PER_MBIGEN_NODE; |
100 | |
101 | *mask = 1 << (irq_ofst % 32); |
102 | ofst = irq_ofst / 32 * 4; |
103 | |
104 | *addr = ofst + get_mbigen_node_offset(nid) + REG_MBIGEN_TYPE_OFFSET; |
105 | } |
106 | |
107 | static inline void get_mbigen_clear_reg(irq_hw_number_t hwirq, |
108 | u32 *mask, u32 *addr) |
109 | { |
110 | unsigned int ofst = (hwirq / 32) * 4; |
111 | |
112 | *mask = 1 << (hwirq % 32); |
113 | *addr = ofst + REG_MBIGEN_CLEAR_OFFSET; |
114 | } |
115 | |
116 | static void mbigen_eoi_irq(struct irq_data *data) |
117 | { |
118 | void __iomem *base = data->chip_data; |
119 | u32 mask, addr; |
120 | |
121 | get_mbigen_clear_reg(hwirq: data->hwirq, mask: &mask, addr: &addr); |
122 | |
123 | writel_relaxed(mask, base + addr); |
124 | |
125 | irq_chip_eoi_parent(data); |
126 | } |
127 | |
128 | static int mbigen_set_type(struct irq_data *data, unsigned int type) |
129 | { |
130 | void __iomem *base = data->chip_data; |
131 | u32 mask, addr, val; |
132 | |
133 | if (type != IRQ_TYPE_LEVEL_HIGH && type != IRQ_TYPE_EDGE_RISING) |
134 | return -EINVAL; |
135 | |
136 | get_mbigen_type_reg(hwirq: data->hwirq, mask: &mask, addr: &addr); |
137 | |
138 | val = readl_relaxed(base + addr); |
139 | |
140 | if (type == IRQ_TYPE_LEVEL_HIGH) |
141 | val |= mask; |
142 | else |
143 | val &= ~mask; |
144 | |
145 | writel_relaxed(val, base + addr); |
146 | |
147 | return 0; |
148 | } |
149 | |
150 | static void mbigen_write_msi_msg(struct irq_data *d, struct msi_msg *msg) |
151 | { |
152 | void __iomem *base = d->chip_data; |
153 | u32 val; |
154 | |
155 | if (!msg->address_lo && !msg->address_hi) |
156 | return; |
157 | |
158 | base += get_mbigen_vec_reg(hwirq: d->hwirq); |
159 | val = readl_relaxed(base); |
160 | |
161 | val &= ~(IRQ_EVENT_ID_MASK << IRQ_EVENT_ID_SHIFT); |
162 | val |= (msg->data << IRQ_EVENT_ID_SHIFT); |
163 | |
164 | /* The address of doorbell is encoded in mbigen register by default |
165 | * So,we don't need to program the doorbell address at here |
166 | */ |
167 | writel_relaxed(val, base); |
168 | } |
169 | |
170 | static int mbigen_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec, |
171 | unsigned long *hwirq, 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 void mbigen_domain_set_desc(msi_alloc_info_t *arg, struct msi_desc *desc) |
196 | { |
197 | arg->desc = desc; |
198 | arg->hwirq = (u32)desc->data.icookie.value; |
199 | } |
200 | |
201 | static const struct msi_domain_template mbigen_msi_template = { |
202 | .chip = { |
203 | .name = "mbigen-v2" , |
204 | .irq_mask = irq_chip_mask_parent, |
205 | .irq_unmask = irq_chip_unmask_parent, |
206 | .irq_eoi = mbigen_eoi_irq, |
207 | .irq_set_type = mbigen_set_type, |
208 | .irq_write_msi_msg = mbigen_write_msi_msg, |
209 | }, |
210 | |
211 | .ops = { |
212 | .set_desc = mbigen_domain_set_desc, |
213 | .msi_translate = mbigen_domain_translate, |
214 | }, |
215 | |
216 | .info = { |
217 | .bus_token = DOMAIN_BUS_WIRED_TO_MSI, |
218 | .flags = MSI_FLAG_USE_DEV_FWNODE, |
219 | }, |
220 | }; |
221 | |
222 | static bool mbigen_create_device_domain(struct device *dev, unsigned int size, |
223 | struct mbigen_device *mgn_chip) |
224 | { |
225 | if (WARN_ON_ONCE(!dev->msi.domain)) |
226 | return false; |
227 | |
228 | return msi_create_device_irq_domain(dev, domid: MSI_DEFAULT_DOMAIN, |
229 | template: &mbigen_msi_template, hwsize: size, |
230 | NULL, chip_data: mgn_chip->base); |
231 | } |
232 | |
233 | static int mbigen_of_create_domain(struct platform_device *pdev, |
234 | struct mbigen_device *mgn_chip) |
235 | { |
236 | struct platform_device *child; |
237 | u32 num_pins; |
238 | |
239 | for_each_child_of_node_scoped(pdev->dev.of_node, np) { |
240 | if (!of_property_read_bool(np, propname: "interrupt-controller" )) |
241 | continue; |
242 | |
243 | child = of_platform_device_create(np, NULL, NULL); |
244 | if (!child) |
245 | return -ENOMEM; |
246 | |
247 | if (of_property_read_u32(np: child->dev.of_node, propname: "num-pins" , |
248 | out_value: &num_pins) < 0) { |
249 | dev_err(&pdev->dev, "No num-pins property\n" ); |
250 | return -EINVAL; |
251 | } |
252 | |
253 | if (!mbigen_create_device_domain(dev: &child->dev, size: num_pins, mgn_chip)) |
254 | return -ENOMEM; |
255 | } |
256 | |
257 | return 0; |
258 | } |
259 | |
260 | #ifdef CONFIG_ACPI |
261 | static const struct acpi_device_id mbigen_acpi_match[] = { |
262 | { "HISI0152" , 0 }, |
263 | {} |
264 | }; |
265 | MODULE_DEVICE_TABLE(acpi, mbigen_acpi_match); |
266 | |
267 | static int mbigen_acpi_create_domain(struct platform_device *pdev, |
268 | struct mbigen_device *mgn_chip) |
269 | { |
270 | u32 num_pins = 0; |
271 | int ret; |
272 | |
273 | /* |
274 | * "num-pins" is the total number of interrupt pins implemented in |
275 | * this mbigen instance, and mbigen is an interrupt controller |
276 | * connected to ITS converting wired interrupts into MSI, so we |
277 | * use "num-pins" to alloc MSI vectors which are needed by client |
278 | * devices connected to it. |
279 | * |
280 | * Here is the DSDT device node used for mbigen in firmware: |
281 | * Device(MBI0) { |
282 | * Name(_HID, "HISI0152") |
283 | * Name(_UID, Zero) |
284 | * Name(_CRS, ResourceTemplate() { |
285 | * Memory32Fixed(ReadWrite, 0xa0080000, 0x10000) |
286 | * }) |
287 | * |
288 | * Name(_DSD, Package () { |
289 | * ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"), |
290 | * Package () { |
291 | * Package () {"num-pins", 378} |
292 | * } |
293 | * }) |
294 | * } |
295 | */ |
296 | ret = device_property_read_u32(dev: &pdev->dev, propname: "num-pins" , val: &num_pins); |
297 | if (ret || num_pins == 0) |
298 | return -EINVAL; |
299 | |
300 | if (!mbigen_create_device_domain(dev: &pdev->dev, size: num_pins, mgn_chip)) |
301 | return -ENOMEM; |
302 | |
303 | return 0; |
304 | } |
305 | #else |
306 | static inline int mbigen_acpi_create_domain(struct platform_device *pdev, |
307 | struct mbigen_device *mgn_chip) |
308 | { |
309 | return -ENODEV; |
310 | } |
311 | #endif |
312 | |
313 | static int mbigen_device_probe(struct platform_device *pdev) |
314 | { |
315 | struct mbigen_device *mgn_chip; |
316 | struct resource *res; |
317 | int err; |
318 | |
319 | mgn_chip = devm_kzalloc(dev: &pdev->dev, size: sizeof(*mgn_chip), GFP_KERNEL); |
320 | if (!mgn_chip) |
321 | return -ENOMEM; |
322 | |
323 | mgn_chip->pdev = pdev; |
324 | |
325 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
326 | if (!res) |
327 | return -EINVAL; |
328 | |
329 | mgn_chip->base = devm_ioremap(dev: &pdev->dev, offset: res->start, |
330 | size: resource_size(res)); |
331 | if (!mgn_chip->base) { |
332 | dev_err(&pdev->dev, "failed to ioremap %pR\n" , res); |
333 | return -ENOMEM; |
334 | } |
335 | |
336 | if (IS_ENABLED(CONFIG_OF) && pdev->dev.of_node) |
337 | err = mbigen_of_create_domain(pdev, mgn_chip); |
338 | else if (ACPI_COMPANION(&pdev->dev)) |
339 | err = mbigen_acpi_create_domain(pdev, mgn_chip); |
340 | else |
341 | err = -EINVAL; |
342 | |
343 | if (err) { |
344 | dev_err(&pdev->dev, "Failed to create mbi-gen irqdomain\n" ); |
345 | return err; |
346 | } |
347 | |
348 | platform_set_drvdata(pdev, data: mgn_chip); |
349 | return 0; |
350 | } |
351 | |
352 | static const struct of_device_id mbigen_of_match[] = { |
353 | { .compatible = "hisilicon,mbigen-v2" }, |
354 | { /* END */ } |
355 | }; |
356 | MODULE_DEVICE_TABLE(of, mbigen_of_match); |
357 | |
358 | static struct platform_driver mbigen_platform_driver = { |
359 | .driver = { |
360 | .name = "Hisilicon MBIGEN-V2" , |
361 | .of_match_table = mbigen_of_match, |
362 | .acpi_match_table = ACPI_PTR(mbigen_acpi_match), |
363 | .suppress_bind_attrs = true, |
364 | }, |
365 | .probe = mbigen_device_probe, |
366 | }; |
367 | |
368 | module_platform_driver(mbigen_platform_driver); |
369 | |
370 | MODULE_AUTHOR("Jun Ma <majun258@huawei.com>" ); |
371 | MODULE_AUTHOR("Yun Wu <wuyun.wu@huawei.com>" ); |
372 | MODULE_DESCRIPTION("HiSilicon MBI Generator driver" ); |
373 | |