.. | .. |
---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-only |
---|
1 | 2 | /* |
---|
2 | 3 | * Driver for Socionext External Interrupt Unit (EXIU) |
---|
3 | 4 | * |
---|
4 | | - * Copyright (c) 2017 Linaro, Ltd. <ard.biesheuvel@linaro.org> |
---|
| 5 | + * Copyright (c) 2017-2019 Linaro, Ltd. <ard.biesheuvel@linaro.org> |
---|
5 | 6 | * |
---|
6 | 7 | * Based on irq-tegra.c: |
---|
7 | 8 | * Copyright (C) 2011 Google, Inc. |
---|
8 | 9 | * Copyright (C) 2010,2013, NVIDIA Corporation |
---|
9 | | - * |
---|
10 | | - * This program is free software; you can redistribute it and/or modify |
---|
11 | | - * it under the terms of the GNU General Public License version 2 as |
---|
12 | | - * published by the Free Software Foundation. |
---|
13 | 10 | */ |
---|
14 | 11 | |
---|
15 | 12 | #include <linux/interrupt.h> |
---|
.. | .. |
---|
20 | 17 | #include <linux/of.h> |
---|
21 | 18 | #include <linux/of_address.h> |
---|
22 | 19 | #include <linux/of_irq.h> |
---|
| 20 | +#include <linux/platform_device.h> |
---|
23 | 21 | |
---|
24 | 22 | #include <dt-bindings/interrupt-controller/arm-gic.h> |
---|
25 | 23 | |
---|
.. | .. |
---|
39 | 37 | u32 spi_base; |
---|
40 | 38 | }; |
---|
41 | 39 | |
---|
42 | | -static void exiu_irq_eoi(struct irq_data *d) |
---|
| 40 | +static void exiu_irq_ack(struct irq_data *d) |
---|
43 | 41 | { |
---|
44 | 42 | struct exiu_irq_data *data = irq_data_get_irq_chip_data(d); |
---|
45 | 43 | |
---|
46 | 44 | writel(BIT(d->hwirq), data->base + EIREQCLR); |
---|
| 45 | +} |
---|
| 46 | + |
---|
| 47 | +static void exiu_irq_eoi(struct irq_data *d) |
---|
| 48 | +{ |
---|
| 49 | + struct exiu_irq_data *data = irq_data_get_irq_chip_data(d); |
---|
| 50 | + |
---|
| 51 | + /* |
---|
| 52 | + * Level triggered interrupts are latched and must be cleared during |
---|
| 53 | + * EOI or the interrupt will be jammed on. Of course if a level |
---|
| 54 | + * triggered interrupt is still asserted then the write will not clear |
---|
| 55 | + * the interrupt. |
---|
| 56 | + */ |
---|
| 57 | + if (irqd_is_level_type(d)) |
---|
| 58 | + writel(BIT(d->hwirq), data->base + EIREQCLR); |
---|
| 59 | + |
---|
47 | 60 | irq_chip_eoi_parent(d); |
---|
48 | 61 | } |
---|
49 | 62 | |
---|
.. | .. |
---|
93 | 106 | writel_relaxed(val, data->base + EILVL); |
---|
94 | 107 | |
---|
95 | 108 | val = readl_relaxed(data->base + EIEDG); |
---|
96 | | - if (type == IRQ_TYPE_LEVEL_LOW || type == IRQ_TYPE_LEVEL_HIGH) |
---|
| 109 | + if (type == IRQ_TYPE_LEVEL_LOW || type == IRQ_TYPE_LEVEL_HIGH) { |
---|
97 | 110 | val &= ~BIT(d->hwirq); |
---|
98 | | - else |
---|
| 111 | + irq_set_handler_locked(d, handle_fasteoi_irq); |
---|
| 112 | + } else { |
---|
99 | 113 | val |= BIT(d->hwirq); |
---|
| 114 | + irq_set_handler_locked(d, handle_fasteoi_ack_irq); |
---|
| 115 | + } |
---|
100 | 116 | writel_relaxed(val, data->base + EIEDG); |
---|
101 | 117 | |
---|
102 | 118 | writel_relaxed(BIT(d->hwirq), data->base + EIREQCLR); |
---|
.. | .. |
---|
106 | 122 | |
---|
107 | 123 | static struct irq_chip exiu_irq_chip = { |
---|
108 | 124 | .name = "EXIU", |
---|
| 125 | + .irq_ack = exiu_irq_ack, |
---|
109 | 126 | .irq_eoi = exiu_irq_eoi, |
---|
110 | 127 | .irq_enable = exiu_irq_enable, |
---|
111 | 128 | .irq_mask = exiu_irq_mask, |
---|
.. | .. |
---|
134 | 151 | |
---|
135 | 152 | *hwirq = fwspec->param[1] - info->spi_base; |
---|
136 | 153 | *type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK; |
---|
137 | | - return 0; |
---|
| 154 | + } else { |
---|
| 155 | + if (fwspec->param_count != 2) |
---|
| 156 | + return -EINVAL; |
---|
| 157 | + *hwirq = fwspec->param[0]; |
---|
| 158 | + *type = fwspec->param[1] & IRQ_TYPE_SENSE_MASK; |
---|
138 | 159 | } |
---|
139 | | - return -EINVAL; |
---|
| 160 | + return 0; |
---|
140 | 161 | } |
---|
141 | 162 | |
---|
142 | 163 | static int exiu_domain_alloc(struct irq_domain *dom, unsigned int virq, |
---|
.. | .. |
---|
147 | 168 | struct exiu_irq_data *info = dom->host_data; |
---|
148 | 169 | irq_hw_number_t hwirq; |
---|
149 | 170 | |
---|
150 | | - if (fwspec->param_count != 3) |
---|
151 | | - return -EINVAL; /* Not GIC compliant */ |
---|
152 | | - if (fwspec->param[0] != GIC_SPI) |
---|
153 | | - return -EINVAL; /* No PPI should point to this domain */ |
---|
| 171 | + parent_fwspec = *fwspec; |
---|
| 172 | + if (is_of_node(dom->parent->fwnode)) { |
---|
| 173 | + if (fwspec->param_count != 3) |
---|
| 174 | + return -EINVAL; /* Not GIC compliant */ |
---|
| 175 | + if (fwspec->param[0] != GIC_SPI) |
---|
| 176 | + return -EINVAL; /* No PPI should point to this domain */ |
---|
154 | 177 | |
---|
| 178 | + hwirq = fwspec->param[1] - info->spi_base; |
---|
| 179 | + } else { |
---|
| 180 | + hwirq = fwspec->param[0]; |
---|
| 181 | + parent_fwspec.param[0] = hwirq + info->spi_base + 32; |
---|
| 182 | + } |
---|
155 | 183 | WARN_ON(nr_irqs != 1); |
---|
156 | | - hwirq = fwspec->param[1] - info->spi_base; |
---|
157 | 184 | irq_domain_set_hwirq_and_chip(dom, virq, hwirq, &exiu_irq_chip, info); |
---|
158 | 185 | |
---|
159 | | - parent_fwspec = *fwspec; |
---|
160 | 186 | parent_fwspec.fwnode = dom->parent->fwnode; |
---|
161 | 187 | return irq_domain_alloc_irqs_parent(dom, virq, nr_irqs, &parent_fwspec); |
---|
162 | 188 | } |
---|
.. | .. |
---|
167 | 193 | .free = irq_domain_free_irqs_common, |
---|
168 | 194 | }; |
---|
169 | 195 | |
---|
170 | | -static int __init exiu_init(struct device_node *node, |
---|
171 | | - struct device_node *parent) |
---|
| 196 | +static struct exiu_irq_data *exiu_init(const struct fwnode_handle *fwnode, |
---|
| 197 | + struct resource *res) |
---|
| 198 | +{ |
---|
| 199 | + struct exiu_irq_data *data; |
---|
| 200 | + int err; |
---|
| 201 | + |
---|
| 202 | + data = kzalloc(sizeof(*data), GFP_KERNEL); |
---|
| 203 | + if (!data) |
---|
| 204 | + return ERR_PTR(-ENOMEM); |
---|
| 205 | + |
---|
| 206 | + if (fwnode_property_read_u32_array(fwnode, "socionext,spi-base", |
---|
| 207 | + &data->spi_base, 1)) { |
---|
| 208 | + err = -ENODEV; |
---|
| 209 | + goto out_free; |
---|
| 210 | + } |
---|
| 211 | + |
---|
| 212 | + data->base = ioremap(res->start, resource_size(res)); |
---|
| 213 | + if (!data->base) { |
---|
| 214 | + err = -ENODEV; |
---|
| 215 | + goto out_free; |
---|
| 216 | + } |
---|
| 217 | + |
---|
| 218 | + /* clear and mask all interrupts */ |
---|
| 219 | + writel_relaxed(0xFFFFFFFF, data->base + EIREQCLR); |
---|
| 220 | + writel_relaxed(0xFFFFFFFF, data->base + EIMASK); |
---|
| 221 | + |
---|
| 222 | + return data; |
---|
| 223 | + |
---|
| 224 | +out_free: |
---|
| 225 | + kfree(data); |
---|
| 226 | + return ERR_PTR(err); |
---|
| 227 | +} |
---|
| 228 | + |
---|
| 229 | +static int __init exiu_dt_init(struct device_node *node, |
---|
| 230 | + struct device_node *parent) |
---|
172 | 231 | { |
---|
173 | 232 | struct irq_domain *parent_domain, *domain; |
---|
174 | 233 | struct exiu_irq_data *data; |
---|
175 | | - int err; |
---|
| 234 | + struct resource res; |
---|
176 | 235 | |
---|
177 | 236 | if (!parent) { |
---|
178 | 237 | pr_err("%pOF: no parent, giving up\n", node); |
---|
.. | .. |
---|
185 | 244 | return -ENXIO; |
---|
186 | 245 | } |
---|
187 | 246 | |
---|
188 | | - data = kzalloc(sizeof(*data), GFP_KERNEL); |
---|
189 | | - if (!data) |
---|
190 | | - return -ENOMEM; |
---|
191 | | - |
---|
192 | | - if (of_property_read_u32(node, "socionext,spi-base", &data->spi_base)) { |
---|
193 | | - pr_err("%pOF: failed to parse 'spi-base' property\n", node); |
---|
194 | | - err = -ENODEV; |
---|
195 | | - goto out_free; |
---|
| 247 | + if (of_address_to_resource(node, 0, &res)) { |
---|
| 248 | + pr_err("%pOF: failed to parse memory resource\n", node); |
---|
| 249 | + return -ENXIO; |
---|
196 | 250 | } |
---|
197 | 251 | |
---|
198 | | - data->base = of_iomap(node, 0); |
---|
199 | | - if (!data->base) { |
---|
200 | | - err = -ENODEV; |
---|
201 | | - goto out_free; |
---|
202 | | - } |
---|
203 | | - |
---|
204 | | - /* clear and mask all interrupts */ |
---|
205 | | - writel_relaxed(0xFFFFFFFF, data->base + EIREQCLR); |
---|
206 | | - writel_relaxed(0xFFFFFFFF, data->base + EIMASK); |
---|
| 252 | + data = exiu_init(of_node_to_fwnode(node), &res); |
---|
| 253 | + if (IS_ERR(data)) |
---|
| 254 | + return PTR_ERR(data); |
---|
207 | 255 | |
---|
208 | 256 | domain = irq_domain_add_hierarchy(parent_domain, 0, NUM_IRQS, node, |
---|
209 | 257 | &exiu_domain_ops, data); |
---|
210 | 258 | if (!domain) { |
---|
211 | 259 | pr_err("%pOF: failed to allocate domain\n", node); |
---|
212 | | - err = -ENOMEM; |
---|
213 | 260 | goto out_unmap; |
---|
214 | 261 | } |
---|
215 | 262 | |
---|
.. | .. |
---|
220 | 267 | |
---|
221 | 268 | out_unmap: |
---|
222 | 269 | iounmap(data->base); |
---|
223 | | -out_free: |
---|
224 | 270 | kfree(data); |
---|
225 | | - return err; |
---|
| 271 | + return -ENOMEM; |
---|
226 | 272 | } |
---|
227 | | -IRQCHIP_DECLARE(exiu, "socionext,synquacer-exiu", exiu_init); |
---|
| 273 | +IRQCHIP_DECLARE(exiu, "socionext,synquacer-exiu", exiu_dt_init); |
---|
| 274 | + |
---|
| 275 | +#ifdef CONFIG_ACPI |
---|
| 276 | +static int exiu_acpi_probe(struct platform_device *pdev) |
---|
| 277 | +{ |
---|
| 278 | + struct irq_domain *domain; |
---|
| 279 | + struct exiu_irq_data *data; |
---|
| 280 | + struct resource *res; |
---|
| 281 | + |
---|
| 282 | + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
---|
| 283 | + if (!res) { |
---|
| 284 | + dev_err(&pdev->dev, "failed to parse memory resource\n"); |
---|
| 285 | + return -ENXIO; |
---|
| 286 | + } |
---|
| 287 | + |
---|
| 288 | + data = exiu_init(dev_fwnode(&pdev->dev), res); |
---|
| 289 | + if (IS_ERR(data)) |
---|
| 290 | + return PTR_ERR(data); |
---|
| 291 | + |
---|
| 292 | + domain = acpi_irq_create_hierarchy(0, NUM_IRQS, dev_fwnode(&pdev->dev), |
---|
| 293 | + &exiu_domain_ops, data); |
---|
| 294 | + if (!domain) { |
---|
| 295 | + dev_err(&pdev->dev, "failed to create IRQ domain\n"); |
---|
| 296 | + goto out_unmap; |
---|
| 297 | + } |
---|
| 298 | + |
---|
| 299 | + dev_info(&pdev->dev, "%d interrupts forwarded\n", NUM_IRQS); |
---|
| 300 | + |
---|
| 301 | + return 0; |
---|
| 302 | + |
---|
| 303 | +out_unmap: |
---|
| 304 | + iounmap(data->base); |
---|
| 305 | + kfree(data); |
---|
| 306 | + return -ENOMEM; |
---|
| 307 | +} |
---|
| 308 | + |
---|
| 309 | +static const struct acpi_device_id exiu_acpi_ids[] = { |
---|
| 310 | + { "SCX0008" }, |
---|
| 311 | + { /* sentinel */ } |
---|
| 312 | +}; |
---|
| 313 | +MODULE_DEVICE_TABLE(acpi, exiu_acpi_ids); |
---|
| 314 | + |
---|
| 315 | +static struct platform_driver exiu_driver = { |
---|
| 316 | + .driver = { |
---|
| 317 | + .name = "exiu", |
---|
| 318 | + .acpi_match_table = exiu_acpi_ids, |
---|
| 319 | + }, |
---|
| 320 | + .probe = exiu_acpi_probe, |
---|
| 321 | +}; |
---|
| 322 | +builtin_platform_driver(exiu_driver); |
---|
| 323 | +#endif |
---|