Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-only
0002 /*
0003  *  drivers/irqchip/irq-crossbar.c
0004  *
0005  *  Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com
0006  *  Author: Sricharan R <r.sricharan@ti.com>
0007  */
0008 #include <linux/err.h>
0009 #include <linux/io.h>
0010 #include <linux/irqchip.h>
0011 #include <linux/irqdomain.h>
0012 #include <linux/of_address.h>
0013 #include <linux/of_irq.h>
0014 #include <linux/slab.h>
0015 
0016 #define IRQ_FREE    -1
0017 #define IRQ_RESERVED    -2
0018 #define IRQ_SKIP    -3
0019 #define GIC_IRQ_START   32
0020 
0021 /**
0022  * struct crossbar_device - crossbar device description
0023  * @lock: spinlock serializing access to @irq_map
0024  * @int_max: maximum number of supported interrupts
0025  * @safe_map: safe default value to initialize the crossbar
0026  * @max_crossbar_sources: Maximum number of crossbar sources
0027  * @irq_map: array of interrupts to crossbar number mapping
0028  * @crossbar_base: crossbar base address
0029  * @register_offsets: offsets for each irq number
0030  * @write: register write function pointer
0031  */
0032 struct crossbar_device {
0033     raw_spinlock_t lock;
0034     uint int_max;
0035     uint safe_map;
0036     uint max_crossbar_sources;
0037     uint *irq_map;
0038     void __iomem *crossbar_base;
0039     int *register_offsets;
0040     void (*write)(int, int);
0041 };
0042 
0043 static struct crossbar_device *cb;
0044 
0045 static void crossbar_writel(int irq_no, int cb_no)
0046 {
0047     writel(cb_no, cb->crossbar_base + cb->register_offsets[irq_no]);
0048 }
0049 
0050 static void crossbar_writew(int irq_no, int cb_no)
0051 {
0052     writew(cb_no, cb->crossbar_base + cb->register_offsets[irq_no]);
0053 }
0054 
0055 static void crossbar_writeb(int irq_no, int cb_no)
0056 {
0057     writeb(cb_no, cb->crossbar_base + cb->register_offsets[irq_no]);
0058 }
0059 
0060 static struct irq_chip crossbar_chip = {
0061     .name           = "CBAR",
0062     .irq_eoi        = irq_chip_eoi_parent,
0063     .irq_mask       = irq_chip_mask_parent,
0064     .irq_unmask     = irq_chip_unmask_parent,
0065     .irq_retrigger      = irq_chip_retrigger_hierarchy,
0066     .irq_set_type       = irq_chip_set_type_parent,
0067     .flags          = IRQCHIP_MASK_ON_SUSPEND |
0068                   IRQCHIP_SKIP_SET_WAKE,
0069 #ifdef CONFIG_SMP
0070     .irq_set_affinity   = irq_chip_set_affinity_parent,
0071 #endif
0072 };
0073 
0074 static int allocate_gic_irq(struct irq_domain *domain, unsigned virq,
0075                 irq_hw_number_t hwirq)
0076 {
0077     struct irq_fwspec fwspec;
0078     int i;
0079     int err;
0080 
0081     if (!irq_domain_get_of_node(domain->parent))
0082         return -EINVAL;
0083 
0084     raw_spin_lock(&cb->lock);
0085     for (i = cb->int_max - 1; i >= 0; i--) {
0086         if (cb->irq_map[i] == IRQ_FREE) {
0087             cb->irq_map[i] = hwirq;
0088             break;
0089         }
0090     }
0091     raw_spin_unlock(&cb->lock);
0092 
0093     if (i < 0)
0094         return -ENODEV;
0095 
0096     fwspec.fwnode = domain->parent->fwnode;
0097     fwspec.param_count = 3;
0098     fwspec.param[0] = 0;    /* SPI */
0099     fwspec.param[1] = i;
0100     fwspec.param[2] = IRQ_TYPE_LEVEL_HIGH;
0101 
0102     err = irq_domain_alloc_irqs_parent(domain, virq, 1, &fwspec);
0103     if (err)
0104         cb->irq_map[i] = IRQ_FREE;
0105     else
0106         cb->write(i, hwirq);
0107 
0108     return err;
0109 }
0110 
0111 static int crossbar_domain_alloc(struct irq_domain *d, unsigned int virq,
0112                  unsigned int nr_irqs, void *data)
0113 {
0114     struct irq_fwspec *fwspec = data;
0115     irq_hw_number_t hwirq;
0116     int i;
0117 
0118     if (fwspec->param_count != 3)
0119         return -EINVAL; /* Not GIC compliant */
0120     if (fwspec->param[0] != 0)
0121         return -EINVAL; /* No PPI should point to this domain */
0122 
0123     hwirq = fwspec->param[1];
0124     if ((hwirq + nr_irqs) > cb->max_crossbar_sources)
0125         return -EINVAL; /* Can't deal with this */
0126 
0127     for (i = 0; i < nr_irqs; i++) {
0128         int err = allocate_gic_irq(d, virq + i, hwirq + i);
0129 
0130         if (err)
0131             return err;
0132 
0133         irq_domain_set_hwirq_and_chip(d, virq + i, hwirq + i,
0134                           &crossbar_chip, NULL);
0135     }
0136 
0137     return 0;
0138 }
0139 
0140 /**
0141  * crossbar_domain_free - unmap/free a crossbar<->irq connection
0142  * @domain: domain of irq to unmap
0143  * @virq: virq number
0144  * @nr_irqs: number of irqs to free
0145  *
0146  * We do not maintain a use count of total number of map/unmap
0147  * calls for a particular irq to find out if a irq can be really
0148  * unmapped. This is because unmap is called during irq_dispose_mapping(irq),
0149  * after which irq is anyways unusable. So an explicit map has to be called
0150  * after that.
0151  */
0152 static void crossbar_domain_free(struct irq_domain *domain, unsigned int virq,
0153                  unsigned int nr_irqs)
0154 {
0155     int i;
0156 
0157     raw_spin_lock(&cb->lock);
0158     for (i = 0; i < nr_irqs; i++) {
0159         struct irq_data *d = irq_domain_get_irq_data(domain, virq + i);
0160 
0161         irq_domain_reset_irq_data(d);
0162         cb->irq_map[d->hwirq] = IRQ_FREE;
0163         cb->write(d->hwirq, cb->safe_map);
0164     }
0165     raw_spin_unlock(&cb->lock);
0166 }
0167 
0168 static int crossbar_domain_translate(struct irq_domain *d,
0169                      struct irq_fwspec *fwspec,
0170                      unsigned long *hwirq,
0171                      unsigned int *type)
0172 {
0173     if (is_of_node(fwspec->fwnode)) {
0174         if (fwspec->param_count != 3)
0175             return -EINVAL;
0176 
0177         /* No PPI should point to this domain */
0178         if (fwspec->param[0] != 0)
0179             return -EINVAL;
0180 
0181         *hwirq = fwspec->param[1];
0182         *type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK;
0183         return 0;
0184     }
0185 
0186     return -EINVAL;
0187 }
0188 
0189 static const struct irq_domain_ops crossbar_domain_ops = {
0190     .alloc      = crossbar_domain_alloc,
0191     .free       = crossbar_domain_free,
0192     .translate  = crossbar_domain_translate,
0193 };
0194 
0195 static int __init crossbar_of_init(struct device_node *node)
0196 {
0197     u32 max = 0, entry, reg_size;
0198     int i, size, reserved = 0;
0199     const __be32 *irqsr;
0200     int ret = -ENOMEM;
0201 
0202     cb = kzalloc(sizeof(*cb), GFP_KERNEL);
0203 
0204     if (!cb)
0205         return ret;
0206 
0207     cb->crossbar_base = of_iomap(node, 0);
0208     if (!cb->crossbar_base)
0209         goto err_cb;
0210 
0211     of_property_read_u32(node, "ti,max-crossbar-sources",
0212                  &cb->max_crossbar_sources);
0213     if (!cb->max_crossbar_sources) {
0214         pr_err("missing 'ti,max-crossbar-sources' property\n");
0215         ret = -EINVAL;
0216         goto err_base;
0217     }
0218 
0219     of_property_read_u32(node, "ti,max-irqs", &max);
0220     if (!max) {
0221         pr_err("missing 'ti,max-irqs' property\n");
0222         ret = -EINVAL;
0223         goto err_base;
0224     }
0225     cb->irq_map = kcalloc(max, sizeof(int), GFP_KERNEL);
0226     if (!cb->irq_map)
0227         goto err_base;
0228 
0229     cb->int_max = max;
0230 
0231     for (i = 0; i < max; i++)
0232         cb->irq_map[i] = IRQ_FREE;
0233 
0234     /* Get and mark reserved irqs */
0235     irqsr = of_get_property(node, "ti,irqs-reserved", &size);
0236     if (irqsr) {
0237         size /= sizeof(__be32);
0238 
0239         for (i = 0; i < size; i++) {
0240             of_property_read_u32_index(node,
0241                            "ti,irqs-reserved",
0242                            i, &entry);
0243             if (entry >= max) {
0244                 pr_err("Invalid reserved entry\n");
0245                 ret = -EINVAL;
0246                 goto err_irq_map;
0247             }
0248             cb->irq_map[entry] = IRQ_RESERVED;
0249         }
0250     }
0251 
0252     /* Skip irqs hardwired to bypass the crossbar */
0253     irqsr = of_get_property(node, "ti,irqs-skip", &size);
0254     if (irqsr) {
0255         size /= sizeof(__be32);
0256 
0257         for (i = 0; i < size; i++) {
0258             of_property_read_u32_index(node,
0259                            "ti,irqs-skip",
0260                            i, &entry);
0261             if (entry >= max) {
0262                 pr_err("Invalid skip entry\n");
0263                 ret = -EINVAL;
0264                 goto err_irq_map;
0265             }
0266             cb->irq_map[entry] = IRQ_SKIP;
0267         }
0268     }
0269 
0270 
0271     cb->register_offsets = kcalloc(max, sizeof(int), GFP_KERNEL);
0272     if (!cb->register_offsets)
0273         goto err_irq_map;
0274 
0275     of_property_read_u32(node, "ti,reg-size", &reg_size);
0276 
0277     switch (reg_size) {
0278     case 1:
0279         cb->write = crossbar_writeb;
0280         break;
0281     case 2:
0282         cb->write = crossbar_writew;
0283         break;
0284     case 4:
0285         cb->write = crossbar_writel;
0286         break;
0287     default:
0288         pr_err("Invalid reg-size property\n");
0289         ret = -EINVAL;
0290         goto err_reg_offset;
0291         break;
0292     }
0293 
0294     /*
0295      * Register offsets are not linear because of the
0296      * reserved irqs. so find and store the offsets once.
0297      */
0298     for (i = 0; i < max; i++) {
0299         if (cb->irq_map[i] == IRQ_RESERVED)
0300             continue;
0301 
0302         cb->register_offsets[i] = reserved;
0303         reserved += reg_size;
0304     }
0305 
0306     of_property_read_u32(node, "ti,irqs-safe-map", &cb->safe_map);
0307     /* Initialize the crossbar with safe map to start with */
0308     for (i = 0; i < max; i++) {
0309         if (cb->irq_map[i] == IRQ_RESERVED ||
0310             cb->irq_map[i] == IRQ_SKIP)
0311             continue;
0312 
0313         cb->write(i, cb->safe_map);
0314     }
0315 
0316     raw_spin_lock_init(&cb->lock);
0317 
0318     return 0;
0319 
0320 err_reg_offset:
0321     kfree(cb->register_offsets);
0322 err_irq_map:
0323     kfree(cb->irq_map);
0324 err_base:
0325     iounmap(cb->crossbar_base);
0326 err_cb:
0327     kfree(cb);
0328 
0329     cb = NULL;
0330     return ret;
0331 }
0332 
0333 static int __init irqcrossbar_init(struct device_node *node,
0334                    struct device_node *parent)
0335 {
0336     struct irq_domain *parent_domain, *domain;
0337     int err;
0338 
0339     if (!parent) {
0340         pr_err("%pOF: no parent, giving up\n", node);
0341         return -ENODEV;
0342     }
0343 
0344     parent_domain = irq_find_host(parent);
0345     if (!parent_domain) {
0346         pr_err("%pOF: unable to obtain parent domain\n", node);
0347         return -ENXIO;
0348     }
0349 
0350     err = crossbar_of_init(node);
0351     if (err)
0352         return err;
0353 
0354     domain = irq_domain_add_hierarchy(parent_domain, 0,
0355                       cb->max_crossbar_sources,
0356                       node, &crossbar_domain_ops,
0357                       NULL);
0358     if (!domain) {
0359         pr_err("%pOF: failed to allocated domain\n", node);
0360         return -ENOMEM;
0361     }
0362 
0363     return 0;
0364 }
0365 
0366 IRQCHIP_DECLARE(ti_irqcrossbar, "ti,irq-crossbar", irqcrossbar_init);