linux/drivers/irqchip/irq-crossbar.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 *  drivers/irqchip/irq-crossbar.c
   4 *
   5 *  Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com
   6 *  Author: Sricharan R <r.sricharan@ti.com>
   7 */
   8#include <linux/err.h>
   9#include <linux/io.h>
  10#include <linux/irqchip.h>
  11#include <linux/irqdomain.h>
  12#include <linux/of_address.h>
  13#include <linux/of_irq.h>
  14#include <linux/slab.h>
  15
  16#define IRQ_FREE        -1
  17#define IRQ_RESERVED    -2
  18#define IRQ_SKIP        -3
  19#define GIC_IRQ_START   32
  20
  21/**
  22 * struct crossbar_device - crossbar device description
  23 * @lock: spinlock serializing access to @irq_map
  24 * @int_max: maximum number of supported interrupts
  25 * @safe_map: safe default value to initialize the crossbar
  26 * @max_crossbar_sources: Maximum number of crossbar sources
  27 * @irq_map: array of interrupts to crossbar number mapping
  28 * @crossbar_base: crossbar base address
  29 * @register_offsets: offsets for each irq number
  30 * @write: register write function pointer
  31 */
  32struct crossbar_device {
  33        raw_spinlock_t lock;
  34        uint int_max;
  35        uint safe_map;
  36        uint max_crossbar_sources;
  37        uint *irq_map;
  38        void __iomem *crossbar_base;
  39        int *register_offsets;
  40        void (*write)(int, int);
  41};
  42
  43static struct crossbar_device *cb;
  44
  45static void crossbar_writel(int irq_no, int cb_no)
  46{
  47        writel(cb_no, cb->crossbar_base + cb->register_offsets[irq_no]);
  48}
  49
  50static void crossbar_writew(int irq_no, int cb_no)
  51{
  52        writew(cb_no, cb->crossbar_base + cb->register_offsets[irq_no]);
  53}
  54
  55static void crossbar_writeb(int irq_no, int cb_no)
  56{
  57        writeb(cb_no, cb->crossbar_base + cb->register_offsets[irq_no]);
  58}
  59
  60static struct irq_chip crossbar_chip = {
  61        .name                   = "CBAR",
  62        .irq_eoi                = irq_chip_eoi_parent,
  63        .irq_mask               = irq_chip_mask_parent,
  64        .irq_unmask             = irq_chip_unmask_parent,
  65        .irq_retrigger          = irq_chip_retrigger_hierarchy,
  66        .irq_set_type           = irq_chip_set_type_parent,
  67        .flags                  = IRQCHIP_MASK_ON_SUSPEND |
  68                                  IRQCHIP_SKIP_SET_WAKE,
  69#ifdef CONFIG_SMP
  70        .irq_set_affinity       = irq_chip_set_affinity_parent,
  71#endif
  72};
  73
  74static int allocate_gic_irq(struct irq_domain *domain, unsigned virq,
  75                            irq_hw_number_t hwirq)
  76{
  77        struct irq_fwspec fwspec;
  78        int i;
  79        int err;
  80
  81        if (!irq_domain_get_of_node(domain->parent))
  82                return -EINVAL;
  83
  84        raw_spin_lock(&cb->lock);
  85        for (i = cb->int_max - 1; i >= 0; i--) {
  86                if (cb->irq_map[i] == IRQ_FREE) {
  87                        cb->irq_map[i] = hwirq;
  88                        break;
  89                }
  90        }
  91        raw_spin_unlock(&cb->lock);
  92
  93        if (i < 0)
  94                return -ENODEV;
  95
  96        fwspec.fwnode = domain->parent->fwnode;
  97        fwspec.param_count = 3;
  98        fwspec.param[0] = 0;    /* SPI */
  99        fwspec.param[1] = i;
 100        fwspec.param[2] = IRQ_TYPE_LEVEL_HIGH;
 101
 102        err = irq_domain_alloc_irqs_parent(domain, virq, 1, &fwspec);
 103        if (err)
 104                cb->irq_map[i] = IRQ_FREE;
 105        else
 106                cb->write(i, hwirq);
 107
 108        return err;
 109}
 110
 111static int crossbar_domain_alloc(struct irq_domain *d, unsigned int virq,
 112                                 unsigned int nr_irqs, void *data)
 113{
 114        struct irq_fwspec *fwspec = data;
 115        irq_hw_number_t hwirq;
 116        int i;
 117
 118        if (fwspec->param_count != 3)
 119                return -EINVAL; /* Not GIC compliant */
 120        if (fwspec->param[0] != 0)
 121                return -EINVAL; /* No PPI should point to this domain */
 122
 123        hwirq = fwspec->param[1];
 124        if ((hwirq + nr_irqs) > cb->max_crossbar_sources)
 125                return -EINVAL; /* Can't deal with this */
 126
 127        for (i = 0; i < nr_irqs; i++) {
 128                int err = allocate_gic_irq(d, virq + i, hwirq + i);
 129
 130                if (err)
 131                        return err;
 132
 133                irq_domain_set_hwirq_and_chip(d, virq + i, hwirq + i,
 134                                              &crossbar_chip, NULL);
 135        }
 136
 137        return 0;
 138}
 139
 140/**
 141 * crossbar_domain_free - unmap/free a crossbar<->irq connection
 142 * @domain: domain of irq to unmap
 143 * @virq: virq number
 144 * @nr_irqs: number of irqs to free
 145 *
 146 * We do not maintain a use count of total number of map/unmap
 147 * calls for a particular irq to find out if a irq can be really
 148 * unmapped. This is because unmap is called during irq_dispose_mapping(irq),
 149 * after which irq is anyways unusable. So an explicit map has to be called
 150 * after that.
 151 */
 152static void crossbar_domain_free(struct irq_domain *domain, unsigned int virq,
 153                                 unsigned int nr_irqs)
 154{
 155        int i;
 156
 157        raw_spin_lock(&cb->lock);
 158        for (i = 0; i < nr_irqs; i++) {
 159                struct irq_data *d = irq_domain_get_irq_data(domain, virq + i);
 160
 161                irq_domain_reset_irq_data(d);
 162                cb->irq_map[d->hwirq] = IRQ_FREE;
 163                cb->write(d->hwirq, cb->safe_map);
 164        }
 165        raw_spin_unlock(&cb->lock);
 166}
 167
 168static int crossbar_domain_translate(struct irq_domain *d,
 169                                     struct irq_fwspec *fwspec,
 170                                     unsigned long *hwirq,
 171                                     unsigned int *type)
 172{
 173        if (is_of_node(fwspec->fwnode)) {
 174                if (fwspec->param_count != 3)
 175                        return -EINVAL;
 176
 177                /* No PPI should point to this domain */
 178                if (fwspec->param[0] != 0)
 179                        return -EINVAL;
 180
 181                *hwirq = fwspec->param[1];
 182                *type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK;
 183                return 0;
 184        }
 185
 186        return -EINVAL;
 187}
 188
 189static const struct irq_domain_ops crossbar_domain_ops = {
 190        .alloc          = crossbar_domain_alloc,
 191        .free           = crossbar_domain_free,
 192        .translate      = crossbar_domain_translate,
 193};
 194
 195static int __init crossbar_of_init(struct device_node *node)
 196{
 197        u32 max = 0, entry, reg_size;
 198        int i, size, reserved = 0;
 199        const __be32 *irqsr;
 200        int ret = -ENOMEM;
 201
 202        cb = kzalloc(sizeof(*cb), GFP_KERNEL);
 203
 204        if (!cb)
 205                return ret;
 206
 207        cb->crossbar_base = of_iomap(node, 0);
 208        if (!cb->crossbar_base)
 209                goto err_cb;
 210
 211        of_property_read_u32(node, "ti,max-crossbar-sources",
 212                             &cb->max_crossbar_sources);
 213        if (!cb->max_crossbar_sources) {
 214                pr_err("missing 'ti,max-crossbar-sources' property\n");
 215                ret = -EINVAL;
 216                goto err_base;
 217        }
 218
 219        of_property_read_u32(node, "ti,max-irqs", &max);
 220        if (!max) {
 221                pr_err("missing 'ti,max-irqs' property\n");
 222                ret = -EINVAL;
 223                goto err_base;
 224        }
 225        cb->irq_map = kcalloc(max, sizeof(int), GFP_KERNEL);
 226        if (!cb->irq_map)
 227                goto err_base;
 228
 229        cb->int_max = max;
 230
 231        for (i = 0; i < max; i++)
 232                cb->irq_map[i] = IRQ_FREE;
 233
 234        /* Get and mark reserved irqs */
 235        irqsr = of_get_property(node, "ti,irqs-reserved", &size);
 236        if (irqsr) {
 237                size /= sizeof(__be32);
 238
 239                for (i = 0; i < size; i++) {
 240                        of_property_read_u32_index(node,
 241                                                   "ti,irqs-reserved",
 242                                                   i, &entry);
 243                        if (entry >= max) {
 244                                pr_err("Invalid reserved entry\n");
 245                                ret = -EINVAL;
 246                                goto err_irq_map;
 247                        }
 248                        cb->irq_map[entry] = IRQ_RESERVED;
 249                }
 250        }
 251
 252        /* Skip irqs hardwired to bypass the crossbar */
 253        irqsr = of_get_property(node, "ti,irqs-skip", &size);
 254        if (irqsr) {
 255                size /= sizeof(__be32);
 256
 257                for (i = 0; i < size; i++) {
 258                        of_property_read_u32_index(node,
 259                                                   "ti,irqs-skip",
 260                                                   i, &entry);
 261                        if (entry >= max) {
 262                                pr_err("Invalid skip entry\n");
 263                                ret = -EINVAL;
 264                                goto err_irq_map;
 265                        }
 266                        cb->irq_map[entry] = IRQ_SKIP;
 267                }
 268        }
 269
 270
 271        cb->register_offsets = kcalloc(max, sizeof(int), GFP_KERNEL);
 272        if (!cb->register_offsets)
 273                goto err_irq_map;
 274
 275        of_property_read_u32(node, "ti,reg-size", &reg_size);
 276
 277        switch (reg_size) {
 278        case 1:
 279                cb->write = crossbar_writeb;
 280                break;
 281        case 2:
 282                cb->write = crossbar_writew;
 283                break;
 284        case 4:
 285                cb->write = crossbar_writel;
 286                break;
 287        default:
 288                pr_err("Invalid reg-size property\n");
 289                ret = -EINVAL;
 290                goto err_reg_offset;
 291                break;
 292        }
 293
 294        /*
 295         * Register offsets are not linear because of the
 296         * reserved irqs. so find and store the offsets once.
 297         */
 298        for (i = 0; i < max; i++) {
 299                if (cb->irq_map[i] == IRQ_RESERVED)
 300                        continue;
 301
 302                cb->register_offsets[i] = reserved;
 303                reserved += reg_size;
 304        }
 305
 306        of_property_read_u32(node, "ti,irqs-safe-map", &cb->safe_map);
 307        /* Initialize the crossbar with safe map to start with */
 308        for (i = 0; i < max; i++) {
 309                if (cb->irq_map[i] == IRQ_RESERVED ||
 310                    cb->irq_map[i] == IRQ_SKIP)
 311                        continue;
 312
 313                cb->write(i, cb->safe_map);
 314        }
 315
 316        raw_spin_lock_init(&cb->lock);
 317
 318        return 0;
 319
 320err_reg_offset:
 321        kfree(cb->register_offsets);
 322err_irq_map:
 323        kfree(cb->irq_map);
 324err_base:
 325        iounmap(cb->crossbar_base);
 326err_cb:
 327        kfree(cb);
 328
 329        cb = NULL;
 330        return ret;
 331}
 332
 333static int __init irqcrossbar_init(struct device_node *node,
 334                                   struct device_node *parent)
 335{
 336        struct irq_domain *parent_domain, *domain;
 337        int err;
 338
 339        if (!parent) {
 340                pr_err("%pOF: no parent, giving up\n", node);
 341                return -ENODEV;
 342        }
 343
 344        parent_domain = irq_find_host(parent);
 345        if (!parent_domain) {
 346                pr_err("%pOF: unable to obtain parent domain\n", node);
 347                return -ENXIO;
 348        }
 349
 350        err = crossbar_of_init(node);
 351        if (err)
 352                return err;
 353
 354        domain = irq_domain_add_hierarchy(parent_domain, 0,
 355                                          cb->max_crossbar_sources,
 356                                          node, &crossbar_domain_ops,
 357                                          NULL);
 358        if (!domain) {
 359                pr_err("%pOF: failed to allocated domain\n", node);
 360                return -ENOMEM;
 361        }
 362
 363        return 0;
 364}
 365
 366IRQCHIP_DECLARE(ti_irqcrossbar, "ti,irq-crossbar", irqcrossbar_init);
 367