| .. | .. |
|---|
| 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 |
|---|