diff options
Diffstat (limited to 'drivers/irqchip/irq-ti-sci-intr.c')
-rw-r--r-- | drivers/irqchip/irq-ti-sci-intr.c | 275 |
1 files changed, 275 insertions, 0 deletions
diff --git a/drivers/irqchip/irq-ti-sci-intr.c b/drivers/irqchip/irq-ti-sci-intr.c new file mode 100644 index 000000000000..59d51a20bbd8 --- /dev/null +++ b/drivers/irqchip/irq-ti-sci-intr.c | |||
@@ -0,0 +1,275 @@ | |||
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 - http://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_platform.h> | ||
16 | #include <linux/of_address.h> | ||
17 | #include <linux/of_irq.h> | ||
18 | #include <linux/soc/ti/ti_sci_protocol.h> | ||
19 | |||
20 | #define TI_SCI_DEV_ID_MASK 0xffff | ||
21 | #define TI_SCI_DEV_ID_SHIFT 16 | ||
22 | #define TI_SCI_IRQ_ID_MASK 0xffff | ||
23 | #define TI_SCI_IRQ_ID_SHIFT 0 | ||
24 | #define HWIRQ_TO_DEVID(hwirq) (((hwirq) >> (TI_SCI_DEV_ID_SHIFT)) & \ | ||
25 | (TI_SCI_DEV_ID_MASK)) | ||
26 | #define HWIRQ_TO_IRQID(hwirq) ((hwirq) & (TI_SCI_IRQ_ID_MASK)) | ||
27 | #define TO_HWIRQ(dev, index) ((((dev) & TI_SCI_DEV_ID_MASK) << \ | ||
28 | TI_SCI_DEV_ID_SHIFT) | \ | ||
29 | ((index) & TI_SCI_IRQ_ID_MASK)) | ||
30 | |||
31 | /** | ||
32 | * struct ti_sci_intr_irq_domain - Structure representing a TISCI based | ||
33 | * Interrupt Router IRQ domain. | ||
34 | * @sci: Pointer to TISCI handle | ||
35 | * @dst_irq: TISCI resource pointer representing GIC irq controller. | ||
36 | * @dst_id: TISCI device ID of the GIC irq controller. | ||
37 | * @type: Specifies the trigger type supported by this Interrupt Router | ||
38 | */ | ||
39 | struct ti_sci_intr_irq_domain { | ||
40 | const struct ti_sci_handle *sci; | ||
41 | struct ti_sci_resource *dst_irq; | ||
42 | u32 dst_id; | ||
43 | u32 type; | ||
44 | }; | ||
45 | |||
46 | static struct irq_chip ti_sci_intr_irq_chip = { | ||
47 | .name = "INTR", | ||
48 | .irq_eoi = irq_chip_eoi_parent, | ||
49 | .irq_mask = irq_chip_mask_parent, | ||
50 | .irq_unmask = irq_chip_unmask_parent, | ||
51 | .irq_set_type = irq_chip_set_type_parent, | ||
52 | .irq_retrigger = irq_chip_retrigger_hierarchy, | ||
53 | .irq_set_affinity = irq_chip_set_affinity_parent, | ||
54 | }; | ||
55 | |||
56 | /** | ||
57 | * ti_sci_intr_irq_domain_translate() - Retrieve hwirq and type from | ||
58 | * IRQ firmware specific handler. | ||
59 | * @domain: Pointer to IRQ domain | ||
60 | * @fwspec: Pointer to IRQ specific firmware structure | ||
61 | * @hwirq: IRQ number identified by hardware | ||
62 | * @type: IRQ type | ||
63 | * | ||
64 | * Return 0 if all went ok else appropriate error. | ||
65 | */ | ||
66 | static int ti_sci_intr_irq_domain_translate(struct irq_domain *domain, | ||
67 | struct irq_fwspec *fwspec, | ||
68 | unsigned long *hwirq, | ||
69 | unsigned int *type) | ||
70 | { | ||
71 | struct ti_sci_intr_irq_domain *intr = domain->host_data; | ||
72 | |||
73 | if (fwspec->param_count != 2) | ||
74 | return -EINVAL; | ||
75 | |||
76 | *hwirq = TO_HWIRQ(fwspec->param[0], fwspec->param[1]); | ||
77 | *type = intr->type; | ||
78 | |||
79 | return 0; | ||
80 | } | ||
81 | |||
82 | /** | ||
83 | * ti_sci_intr_irq_domain_free() - Free the specified IRQs from the domain. | ||
84 | * @domain: Domain to which the irqs belong | ||
85 | * @virq: Linux virtual IRQ to be freed. | ||
86 | * @nr_irqs: Number of continuous irqs to be freed | ||
87 | */ | ||
88 | static void ti_sci_intr_irq_domain_free(struct irq_domain *domain, | ||
89 | unsigned int virq, unsigned int nr_irqs) | ||
90 | { | ||
91 | struct ti_sci_intr_irq_domain *intr = domain->host_data; | ||
92 | struct irq_data *data, *parent_data; | ||
93 | u16 dev_id, irq_index; | ||
94 | |||
95 | parent_data = irq_domain_get_irq_data(domain->parent, virq); | ||
96 | data = irq_domain_get_irq_data(domain, virq); | ||
97 | irq_index = HWIRQ_TO_IRQID(data->hwirq); | ||
98 | dev_id = HWIRQ_TO_DEVID(data->hwirq); | ||
99 | |||
100 | intr->sci->ops.rm_irq_ops.free_irq(intr->sci, dev_id, irq_index, | ||
101 | intr->dst_id, parent_data->hwirq); | ||
102 | ti_sci_release_resource(intr->dst_irq, parent_data->hwirq); | ||
103 | irq_domain_free_irqs_parent(domain, virq, 1); | ||
104 | irq_domain_reset_irq_data(data); | ||
105 | } | ||
106 | |||
107 | /** | ||
108 | * ti_sci_intr_alloc_gic_irq() - Allocate GIC specific IRQ | ||
109 | * @domain: Pointer to the interrupt router IRQ domain | ||
110 | * @virq: Corresponding Linux virtual IRQ number | ||
111 | * @hwirq: Corresponding hwirq for the IRQ within this IRQ domain | ||
112 | * | ||
113 | * Returns 0 if all went well else appropriate error pointer. | ||
114 | */ | ||
115 | static int ti_sci_intr_alloc_gic_irq(struct irq_domain *domain, | ||
116 | unsigned int virq, u32 hwirq) | ||
117 | { | ||
118 | struct ti_sci_intr_irq_domain *intr = domain->host_data; | ||
119 | struct irq_fwspec fwspec; | ||
120 | u16 dev_id, irq_index; | ||
121 | u16 dst_irq; | ||
122 | int err; | ||
123 | |||
124 | dev_id = HWIRQ_TO_DEVID(hwirq); | ||
125 | irq_index = HWIRQ_TO_IRQID(hwirq); | ||
126 | |||
127 | dst_irq = ti_sci_get_free_resource(intr->dst_irq); | ||
128 | if (dst_irq == TI_SCI_RESOURCE_NULL) | ||
129 | return -EINVAL; | ||
130 | |||
131 | fwspec.fwnode = domain->parent->fwnode; | ||
132 | fwspec.param_count = 3; | ||
133 | fwspec.param[0] = 0; /* SPI */ | ||
134 | fwspec.param[1] = dst_irq - 32; /* SPI offset */ | ||
135 | fwspec.param[2] = intr->type; | ||
136 | |||
137 | err = irq_domain_alloc_irqs_parent(domain, virq, 1, &fwspec); | ||
138 | if (err) | ||
139 | goto err_irqs; | ||
140 | |||
141 | err = intr->sci->ops.rm_irq_ops.set_irq(intr->sci, dev_id, irq_index, | ||
142 | intr->dst_id, dst_irq); | ||
143 | if (err) | ||
144 | goto err_msg; | ||
145 | |||
146 | return 0; | ||
147 | |||
148 | err_msg: | ||
149 | irq_domain_free_irqs_parent(domain, virq, 1); | ||
150 | err_irqs: | ||
151 | ti_sci_release_resource(intr->dst_irq, dst_irq); | ||
152 | return err; | ||
153 | } | ||
154 | |||
155 | /** | ||
156 | * ti_sci_intr_irq_domain_alloc() - Allocate Interrupt router IRQs | ||
157 | * @domain: Point to the interrupt router IRQ domain | ||
158 | * @virq: Corresponding Linux virtual IRQ number | ||
159 | * @nr_irqs: Continuous irqs to be allocated | ||
160 | * @data: Pointer to firmware specifier | ||
161 | * | ||
162 | * Return 0 if all went well else appropriate error value. | ||
163 | */ | ||
164 | static int ti_sci_intr_irq_domain_alloc(struct irq_domain *domain, | ||
165 | unsigned int virq, unsigned int nr_irqs, | ||
166 | void *data) | ||
167 | { | ||
168 | struct irq_fwspec *fwspec = data; | ||
169 | unsigned long hwirq; | ||
170 | unsigned int flags; | ||
171 | int err; | ||
172 | |||
173 | err = ti_sci_intr_irq_domain_translate(domain, fwspec, &hwirq, &flags); | ||
174 | if (err) | ||
175 | return err; | ||
176 | |||
177 | err = ti_sci_intr_alloc_gic_irq(domain, virq, hwirq); | ||
178 | if (err) | ||
179 | return err; | ||
180 | |||
181 | irq_domain_set_hwirq_and_chip(domain, virq, hwirq, | ||
182 | &ti_sci_intr_irq_chip, NULL); | ||
183 | |||
184 | return 0; | ||
185 | } | ||
186 | |||
187 | static const struct irq_domain_ops ti_sci_intr_irq_domain_ops = { | ||
188 | .free = ti_sci_intr_irq_domain_free, | ||
189 | .alloc = ti_sci_intr_irq_domain_alloc, | ||
190 | .translate = ti_sci_intr_irq_domain_translate, | ||
191 | }; | ||
192 | |||
193 | static int ti_sci_intr_irq_domain_probe(struct platform_device *pdev) | ||
194 | { | ||
195 | struct irq_domain *parent_domain, *domain; | ||
196 | struct ti_sci_intr_irq_domain *intr; | ||
197 | struct device_node *parent_node; | ||
198 | struct device *dev = &pdev->dev; | ||
199 | int ret; | ||
200 | |||
201 | parent_node = of_irq_find_parent(dev_of_node(dev)); | ||
202 | if (!parent_node) { | ||
203 | dev_err(dev, "Failed to get IRQ parent node\n"); | ||
204 | return -ENODEV; | ||
205 | } | ||
206 | |||
207 | parent_domain = irq_find_host(parent_node); | ||
208 | if (!parent_domain) { | ||
209 | dev_err(dev, "Failed to find IRQ parent domain\n"); | ||
210 | return -ENODEV; | ||
211 | } | ||
212 | |||
213 | intr = devm_kzalloc(dev, sizeof(*intr), GFP_KERNEL); | ||
214 | if (!intr) | ||
215 | return -ENOMEM; | ||
216 | |||
217 | ret = of_property_read_u32(dev_of_node(dev), "ti,intr-trigger-type", | ||
218 | &intr->type); | ||
219 | if (ret) { | ||
220 | dev_err(dev, "missing ti,intr-trigger-type property\n"); | ||
221 | return -EINVAL; | ||
222 | } | ||
223 | |||
224 | intr->sci = devm_ti_sci_get_by_phandle(dev, "ti,sci"); | ||
225 | if (IS_ERR(intr->sci)) { | ||
226 | ret = PTR_ERR(intr->sci); | ||
227 | if (ret != -EPROBE_DEFER) | ||
228 | dev_err(dev, "ti,sci read fail %d\n", ret); | ||
229 | intr->sci = NULL; | ||
230 | return ret; | ||
231 | } | ||
232 | |||
233 | ret = of_property_read_u32(dev_of_node(dev), "ti,sci-dst-id", | ||
234 | &intr->dst_id); | ||
235 | if (ret) { | ||
236 | dev_err(dev, "missing 'ti,sci-dst-id' property\n"); | ||
237 | return -EINVAL; | ||
238 | } | ||
239 | |||
240 | intr->dst_irq = devm_ti_sci_get_of_resource(intr->sci, dev, | ||
241 | intr->dst_id, | ||
242 | "ti,sci-rm-range-girq"); | ||
243 | if (IS_ERR(intr->dst_irq)) { | ||
244 | dev_err(dev, "Destination irq resource allocation failed\n"); | ||
245 | return PTR_ERR(intr->dst_irq); | ||
246 | } | ||
247 | |||
248 | domain = irq_domain_add_hierarchy(parent_domain, 0, 0, dev_of_node(dev), | ||
249 | &ti_sci_intr_irq_domain_ops, intr); | ||
250 | if (!domain) { | ||
251 | dev_err(dev, "Failed to allocate IRQ domain\n"); | ||
252 | return -ENOMEM; | ||
253 | } | ||
254 | |||
255 | return 0; | ||
256 | } | ||
257 | |||
258 | static const struct of_device_id ti_sci_intr_irq_domain_of_match[] = { | ||
259 | { .compatible = "ti,sci-intr", }, | ||
260 | { /* sentinel */ }, | ||
261 | }; | ||
262 | MODULE_DEVICE_TABLE(of, ti_sci_intr_irq_domain_of_match); | ||
263 | |||
264 | static struct platform_driver ti_sci_intr_irq_domain_driver = { | ||
265 | .probe = ti_sci_intr_irq_domain_probe, | ||
266 | .driver = { | ||
267 | .name = "ti-sci-intr", | ||
268 | .of_match_table = ti_sci_intr_irq_domain_of_match, | ||
269 | }, | ||
270 | }; | ||
271 | module_platform_driver(ti_sci_intr_irq_domain_driver); | ||
272 | |||
273 | MODULE_AUTHOR("Lokesh Vutla <lokeshvutla@ticom>"); | ||
274 | MODULE_DESCRIPTION("K3 Interrupt Router driver over TI SCI protocol"); | ||
275 | MODULE_LICENSE("GPL v2"); | ||