1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Texas Instruments' K3 Interrupt Router irqchip driver |
4 | * |
5 | * Copyright (C) 2018-2019 Texas Instruments Incorporated - https://www.ti.com/ |
6 | * Lokesh Vutla <lokeshvutla@ti.com> |
7 | */ |
8 | |
9 | #include <linux/err.h> |
10 | #include <linux/module.h> |
11 | #include <linux/moduleparam.h> |
12 | #include <linux/io.h> |
13 | #include <linux/irqchip.h> |
14 | #include <linux/irqdomain.h> |
15 | #include <linux/of.h> |
16 | #include <linux/of_irq.h> |
17 | #include <linux/platform_device.h> |
18 | #include <linux/soc/ti/ti_sci_protocol.h> |
19 | |
20 | /** |
21 | * struct ti_sci_intr_irq_domain - Structure representing a TISCI based |
22 | * Interrupt Router IRQ domain. |
23 | * @sci: Pointer to TISCI handle |
24 | * @out_irqs: TISCI resource pointer representing INTR irqs. |
25 | * @dev: Struct device pointer. |
26 | * @ti_sci_id: TI-SCI device identifier |
27 | * @type: Specifies the trigger type supported by this Interrupt Router |
28 | */ |
29 | struct ti_sci_intr_irq_domain { |
30 | const struct ti_sci_handle *sci; |
31 | struct ti_sci_resource *out_irqs; |
32 | struct device *dev; |
33 | u32 ti_sci_id; |
34 | u32 type; |
35 | }; |
36 | |
37 | static struct irq_chip ti_sci_intr_irq_chip = { |
38 | .name = "INTR" , |
39 | .irq_eoi = irq_chip_eoi_parent, |
40 | .irq_mask = irq_chip_mask_parent, |
41 | .irq_unmask = irq_chip_unmask_parent, |
42 | .irq_set_type = irq_chip_set_type_parent, |
43 | .irq_retrigger = irq_chip_retrigger_hierarchy, |
44 | .irq_set_affinity = irq_chip_set_affinity_parent, |
45 | }; |
46 | |
47 | /** |
48 | * ti_sci_intr_irq_domain_translate() - Retrieve hwirq and type from |
49 | * IRQ firmware specific handler. |
50 | * @domain: Pointer to IRQ domain |
51 | * @fwspec: Pointer to IRQ specific firmware structure |
52 | * @hwirq: IRQ number identified by hardware |
53 | * @type: IRQ type |
54 | * |
55 | * Return 0 if all went ok else appropriate error. |
56 | */ |
57 | static int ti_sci_intr_irq_domain_translate(struct irq_domain *domain, |
58 | struct irq_fwspec *fwspec, |
59 | unsigned long *hwirq, |
60 | unsigned int *type) |
61 | { |
62 | struct ti_sci_intr_irq_domain *intr = domain->host_data; |
63 | |
64 | if (fwspec->param_count != 1) |
65 | return -EINVAL; |
66 | |
67 | *hwirq = fwspec->param[0]; |
68 | *type = intr->type; |
69 | |
70 | return 0; |
71 | } |
72 | |
73 | /** |
74 | * ti_sci_intr_xlate_irq() - Translate hwirq to parent's hwirq. |
75 | * @intr: IRQ domain corresponding to Interrupt Router |
76 | * @irq: Hardware irq corresponding to the above irq domain |
77 | * |
78 | * Return parent irq number if translation is available else -ENOENT. |
79 | */ |
80 | static int ti_sci_intr_xlate_irq(struct ti_sci_intr_irq_domain *intr, u32 irq) |
81 | { |
82 | struct device_node *np = dev_of_node(dev: intr->dev); |
83 | u32 base, pbase, size, len; |
84 | const __be32 *range; |
85 | |
86 | range = of_get_property(node: np, name: "ti,interrupt-ranges" , lenp: &len); |
87 | if (!range) |
88 | return irq; |
89 | |
90 | for (len /= sizeof(*range); len >= 3; len -= 3) { |
91 | base = be32_to_cpu(*range++); |
92 | pbase = be32_to_cpu(*range++); |
93 | size = be32_to_cpu(*range++); |
94 | |
95 | if (base <= irq && irq < base + size) |
96 | return irq - base + pbase; |
97 | } |
98 | |
99 | return -ENOENT; |
100 | } |
101 | |
102 | /** |
103 | * ti_sci_intr_irq_domain_free() - Free the specified IRQs from the domain. |
104 | * @domain: Domain to which the irqs belong |
105 | * @virq: Linux virtual IRQ to be freed. |
106 | * @nr_irqs: Number of continuous irqs to be freed |
107 | */ |
108 | static void ti_sci_intr_irq_domain_free(struct irq_domain *domain, |
109 | unsigned int virq, unsigned int nr_irqs) |
110 | { |
111 | struct ti_sci_intr_irq_domain *intr = domain->host_data; |
112 | struct irq_data *data; |
113 | int out_irq; |
114 | |
115 | data = irq_domain_get_irq_data(domain, virq); |
116 | out_irq = (uintptr_t)data->chip_data; |
117 | |
118 | intr->sci->ops.rm_irq_ops.free_irq(intr->sci, |
119 | intr->ti_sci_id, data->hwirq, |
120 | intr->ti_sci_id, out_irq); |
121 | ti_sci_release_resource(res: intr->out_irqs, id: out_irq); |
122 | irq_domain_free_irqs_parent(domain, irq_base: virq, nr_irqs: 1); |
123 | irq_domain_reset_irq_data(irq_data: data); |
124 | } |
125 | |
126 | /** |
127 | * ti_sci_intr_alloc_parent_irq() - Allocate parent IRQ |
128 | * @domain: Pointer to the interrupt router IRQ domain |
129 | * @virq: Corresponding Linux virtual IRQ number |
130 | * @hwirq: Corresponding hwirq for the IRQ within this IRQ domain |
131 | * |
132 | * Returns intr output irq if all went well else appropriate error pointer. |
133 | */ |
134 | static int ti_sci_intr_alloc_parent_irq(struct irq_domain *domain, |
135 | unsigned int virq, u32 hwirq) |
136 | { |
137 | struct ti_sci_intr_irq_domain *intr = domain->host_data; |
138 | struct device_node *parent_node; |
139 | struct irq_fwspec fwspec; |
140 | int p_hwirq, err = 0; |
141 | u16 out_irq; |
142 | |
143 | out_irq = ti_sci_get_free_resource(res: intr->out_irqs); |
144 | if (out_irq == TI_SCI_RESOURCE_NULL) |
145 | return -EINVAL; |
146 | |
147 | p_hwirq = ti_sci_intr_xlate_irq(intr, irq: out_irq); |
148 | if (p_hwirq < 0) |
149 | goto err_irqs; |
150 | |
151 | parent_node = of_irq_find_parent(child: dev_of_node(dev: intr->dev)); |
152 | fwspec.fwnode = of_node_to_fwnode(node: parent_node); |
153 | |
154 | if (of_device_is_compatible(device: parent_node, "arm,gic-v3" )) { |
155 | /* Parent is GIC */ |
156 | fwspec.param_count = 3; |
157 | fwspec.param[0] = 0; /* SPI */ |
158 | fwspec.param[1] = p_hwirq - 32; /* SPI offset */ |
159 | fwspec.param[2] = intr->type; |
160 | } else { |
161 | /* Parent is Interrupt Router */ |
162 | fwspec.param_count = 1; |
163 | fwspec.param[0] = p_hwirq; |
164 | } |
165 | |
166 | err = irq_domain_alloc_irqs_parent(domain, irq_base: virq, nr_irqs: 1, arg: &fwspec); |
167 | if (err) |
168 | goto err_irqs; |
169 | |
170 | err = intr->sci->ops.rm_irq_ops.set_irq(intr->sci, |
171 | intr->ti_sci_id, hwirq, |
172 | intr->ti_sci_id, out_irq); |
173 | if (err) |
174 | goto err_msg; |
175 | |
176 | return out_irq; |
177 | |
178 | err_msg: |
179 | irq_domain_free_irqs_parent(domain, irq_base: virq, nr_irqs: 1); |
180 | err_irqs: |
181 | ti_sci_release_resource(res: intr->out_irqs, id: out_irq); |
182 | return err; |
183 | } |
184 | |
185 | /** |
186 | * ti_sci_intr_irq_domain_alloc() - Allocate Interrupt router IRQs |
187 | * @domain: Point to the interrupt router IRQ domain |
188 | * @virq: Corresponding Linux virtual IRQ number |
189 | * @nr_irqs: Continuous irqs to be allocated |
190 | * @data: Pointer to firmware specifier |
191 | * |
192 | * Return 0 if all went well else appropriate error value. |
193 | */ |
194 | static int ti_sci_intr_irq_domain_alloc(struct irq_domain *domain, |
195 | unsigned int virq, unsigned int nr_irqs, |
196 | void *data) |
197 | { |
198 | struct irq_fwspec *fwspec = data; |
199 | unsigned long hwirq; |
200 | unsigned int flags; |
201 | int err, out_irq; |
202 | |
203 | err = ti_sci_intr_irq_domain_translate(domain, fwspec, hwirq: &hwirq, type: &flags); |
204 | if (err) |
205 | return err; |
206 | |
207 | out_irq = ti_sci_intr_alloc_parent_irq(domain, virq, hwirq); |
208 | if (out_irq < 0) |
209 | return out_irq; |
210 | |
211 | irq_domain_set_hwirq_and_chip(domain, virq, hwirq, |
212 | chip: &ti_sci_intr_irq_chip, |
213 | chip_data: (void *)(uintptr_t)out_irq); |
214 | |
215 | return 0; |
216 | } |
217 | |
218 | static const struct irq_domain_ops ti_sci_intr_irq_domain_ops = { |
219 | .free = ti_sci_intr_irq_domain_free, |
220 | .alloc = ti_sci_intr_irq_domain_alloc, |
221 | .translate = ti_sci_intr_irq_domain_translate, |
222 | }; |
223 | |
224 | static int ti_sci_intr_irq_domain_probe(struct platform_device *pdev) |
225 | { |
226 | struct irq_domain *parent_domain, *domain; |
227 | struct ti_sci_intr_irq_domain *intr; |
228 | struct device_node *parent_node; |
229 | struct device *dev = &pdev->dev; |
230 | int ret; |
231 | |
232 | parent_node = of_irq_find_parent(child: dev_of_node(dev)); |
233 | if (!parent_node) { |
234 | dev_err(dev, "Failed to get IRQ parent node\n" ); |
235 | return -ENODEV; |
236 | } |
237 | |
238 | parent_domain = irq_find_host(node: parent_node); |
239 | of_node_put(node: parent_node); |
240 | if (!parent_domain) { |
241 | dev_err(dev, "Failed to find IRQ parent domain\n" ); |
242 | return -ENODEV; |
243 | } |
244 | |
245 | intr = devm_kzalloc(dev, size: sizeof(*intr), GFP_KERNEL); |
246 | if (!intr) |
247 | return -ENOMEM; |
248 | |
249 | intr->dev = dev; |
250 | ret = of_property_read_u32(np: dev_of_node(dev), propname: "ti,intr-trigger-type" , |
251 | out_value: &intr->type); |
252 | if (ret) { |
253 | dev_err(dev, "missing ti,intr-trigger-type property\n" ); |
254 | return -EINVAL; |
255 | } |
256 | |
257 | intr->sci = devm_ti_sci_get_by_phandle(dev, property: "ti,sci" ); |
258 | if (IS_ERR(ptr: intr->sci)) |
259 | return dev_err_probe(dev, err: PTR_ERR(ptr: intr->sci), |
260 | fmt: "ti,sci read fail\n" ); |
261 | |
262 | ret = of_property_read_u32(np: dev_of_node(dev), propname: "ti,sci-dev-id" , |
263 | out_value: &intr->ti_sci_id); |
264 | if (ret) { |
265 | dev_err(dev, "missing 'ti,sci-dev-id' property\n" ); |
266 | return -EINVAL; |
267 | } |
268 | |
269 | intr->out_irqs = devm_ti_sci_get_resource(handle: intr->sci, dev, |
270 | dev_id: intr->ti_sci_id, |
271 | TI_SCI_RESASG_SUBTYPE_IR_OUTPUT); |
272 | if (IS_ERR(ptr: intr->out_irqs)) { |
273 | dev_err(dev, "Destination irq resource allocation failed\n" ); |
274 | return PTR_ERR(ptr: intr->out_irqs); |
275 | } |
276 | |
277 | domain = irq_domain_add_hierarchy(parent: parent_domain, flags: 0, size: 0, node: dev_of_node(dev), |
278 | ops: &ti_sci_intr_irq_domain_ops, host_data: intr); |
279 | if (!domain) { |
280 | dev_err(dev, "Failed to allocate IRQ domain\n" ); |
281 | return -ENOMEM; |
282 | } |
283 | |
284 | dev_info(dev, "Interrupt Router %d domain created\n" , intr->ti_sci_id); |
285 | |
286 | return 0; |
287 | } |
288 | |
289 | static const struct of_device_id ti_sci_intr_irq_domain_of_match[] = { |
290 | { .compatible = "ti,sci-intr" , }, |
291 | { /* sentinel */ }, |
292 | }; |
293 | MODULE_DEVICE_TABLE(of, ti_sci_intr_irq_domain_of_match); |
294 | |
295 | static struct platform_driver ti_sci_intr_irq_domain_driver = { |
296 | .probe = ti_sci_intr_irq_domain_probe, |
297 | .driver = { |
298 | .name = "ti-sci-intr" , |
299 | .of_match_table = ti_sci_intr_irq_domain_of_match, |
300 | }, |
301 | }; |
302 | module_platform_driver(ti_sci_intr_irq_domain_driver); |
303 | |
304 | MODULE_AUTHOR("Lokesh Vutla <lokeshvutla@ticom>" ); |
305 | MODULE_DESCRIPTION("K3 Interrupt Router driver over TI SCI protocol" ); |
306 | |