root/drivers/irqchip/irq-crossbar.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. crossbar_writel
  2. crossbar_writew
  3. crossbar_writeb
  4. allocate_gic_irq
  5. crossbar_domain_alloc
  6. crossbar_domain_free
  7. crossbar_domain_translate
  8. crossbar_of_init
  9. irqcrossbar_init

   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  */
  32 struct 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 
  43 static struct crossbar_device *cb;
  44 
  45 static void crossbar_writel(int irq_no, int cb_no)
  46 {
  47         writel(cb_no, cb->crossbar_base + cb->register_offsets[irq_no]);
  48 }
  49 
  50 static void crossbar_writew(int irq_no, int cb_no)
  51 {
  52         writew(cb_no, cb->crossbar_base + cb->register_offsets[irq_no]);
  53 }
  54 
  55 static void crossbar_writeb(int irq_no, int cb_no)
  56 {
  57         writeb(cb_no, cb->crossbar_base + cb->register_offsets[irq_no]);
  58 }
  59 
  60 static 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 
  74 static 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 
 111 static 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  */
 152 static 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 
 168 static 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 
 189 static 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 
 195 static 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 
 320 err_reg_offset:
 321         kfree(cb->register_offsets);
 322 err_irq_map:
 323         kfree(cb->irq_map);
 324 err_base:
 325         iounmap(cb->crossbar_base);
 326 err_cb:
 327         kfree(cb);
 328 
 329         cb = NULL;
 330         return ret;
 331 }
 332 
 333 static 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 
 366 IRQCHIP_DECLARE(ti_irqcrossbar, "ti,irq-crossbar", irqcrossbar_init);

/* [<][>][^][v][top][bottom][index][help] */