162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * drivers/irqchip/irq-crossbar.c 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com 662306a36Sopenharmony_ci * Author: Sricharan R <r.sricharan@ti.com> 762306a36Sopenharmony_ci */ 862306a36Sopenharmony_ci#include <linux/err.h> 962306a36Sopenharmony_ci#include <linux/io.h> 1062306a36Sopenharmony_ci#include <linux/irqchip.h> 1162306a36Sopenharmony_ci#include <linux/irqdomain.h> 1262306a36Sopenharmony_ci#include <linux/of_address.h> 1362306a36Sopenharmony_ci#include <linux/of_irq.h> 1462306a36Sopenharmony_ci#include <linux/slab.h> 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_ci#define IRQ_FREE -1 1762306a36Sopenharmony_ci#define IRQ_RESERVED -2 1862306a36Sopenharmony_ci#define IRQ_SKIP -3 1962306a36Sopenharmony_ci#define GIC_IRQ_START 32 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_ci/** 2262306a36Sopenharmony_ci * struct crossbar_device - crossbar device description 2362306a36Sopenharmony_ci * @lock: spinlock serializing access to @irq_map 2462306a36Sopenharmony_ci * @int_max: maximum number of supported interrupts 2562306a36Sopenharmony_ci * @safe_map: safe default value to initialize the crossbar 2662306a36Sopenharmony_ci * @max_crossbar_sources: Maximum number of crossbar sources 2762306a36Sopenharmony_ci * @irq_map: array of interrupts to crossbar number mapping 2862306a36Sopenharmony_ci * @crossbar_base: crossbar base address 2962306a36Sopenharmony_ci * @register_offsets: offsets for each irq number 3062306a36Sopenharmony_ci * @write: register write function pointer 3162306a36Sopenharmony_ci */ 3262306a36Sopenharmony_cistruct crossbar_device { 3362306a36Sopenharmony_ci raw_spinlock_t lock; 3462306a36Sopenharmony_ci uint int_max; 3562306a36Sopenharmony_ci uint safe_map; 3662306a36Sopenharmony_ci uint max_crossbar_sources; 3762306a36Sopenharmony_ci uint *irq_map; 3862306a36Sopenharmony_ci void __iomem *crossbar_base; 3962306a36Sopenharmony_ci int *register_offsets; 4062306a36Sopenharmony_ci void (*write)(int, int); 4162306a36Sopenharmony_ci}; 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_cistatic struct crossbar_device *cb; 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_cistatic void crossbar_writel(int irq_no, int cb_no) 4662306a36Sopenharmony_ci{ 4762306a36Sopenharmony_ci writel(cb_no, cb->crossbar_base + cb->register_offsets[irq_no]); 4862306a36Sopenharmony_ci} 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_cistatic void crossbar_writew(int irq_no, int cb_no) 5162306a36Sopenharmony_ci{ 5262306a36Sopenharmony_ci writew(cb_no, cb->crossbar_base + cb->register_offsets[irq_no]); 5362306a36Sopenharmony_ci} 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_cistatic void crossbar_writeb(int irq_no, int cb_no) 5662306a36Sopenharmony_ci{ 5762306a36Sopenharmony_ci writeb(cb_no, cb->crossbar_base + cb->register_offsets[irq_no]); 5862306a36Sopenharmony_ci} 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_cistatic struct irq_chip crossbar_chip = { 6162306a36Sopenharmony_ci .name = "CBAR", 6262306a36Sopenharmony_ci .irq_eoi = irq_chip_eoi_parent, 6362306a36Sopenharmony_ci .irq_mask = irq_chip_mask_parent, 6462306a36Sopenharmony_ci .irq_unmask = irq_chip_unmask_parent, 6562306a36Sopenharmony_ci .irq_retrigger = irq_chip_retrigger_hierarchy, 6662306a36Sopenharmony_ci .irq_set_type = irq_chip_set_type_parent, 6762306a36Sopenharmony_ci .flags = IRQCHIP_MASK_ON_SUSPEND | 6862306a36Sopenharmony_ci IRQCHIP_SKIP_SET_WAKE, 6962306a36Sopenharmony_ci#ifdef CONFIG_SMP 7062306a36Sopenharmony_ci .irq_set_affinity = irq_chip_set_affinity_parent, 7162306a36Sopenharmony_ci#endif 7262306a36Sopenharmony_ci}; 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_cistatic int allocate_gic_irq(struct irq_domain *domain, unsigned virq, 7562306a36Sopenharmony_ci irq_hw_number_t hwirq) 7662306a36Sopenharmony_ci{ 7762306a36Sopenharmony_ci struct irq_fwspec fwspec; 7862306a36Sopenharmony_ci int i; 7962306a36Sopenharmony_ci int err; 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci if (!irq_domain_get_of_node(domain->parent)) 8262306a36Sopenharmony_ci return -EINVAL; 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci raw_spin_lock(&cb->lock); 8562306a36Sopenharmony_ci for (i = cb->int_max - 1; i >= 0; i--) { 8662306a36Sopenharmony_ci if (cb->irq_map[i] == IRQ_FREE) { 8762306a36Sopenharmony_ci cb->irq_map[i] = hwirq; 8862306a36Sopenharmony_ci break; 8962306a36Sopenharmony_ci } 9062306a36Sopenharmony_ci } 9162306a36Sopenharmony_ci raw_spin_unlock(&cb->lock); 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci if (i < 0) 9462306a36Sopenharmony_ci return -ENODEV; 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci fwspec.fwnode = domain->parent->fwnode; 9762306a36Sopenharmony_ci fwspec.param_count = 3; 9862306a36Sopenharmony_ci fwspec.param[0] = 0; /* SPI */ 9962306a36Sopenharmony_ci fwspec.param[1] = i; 10062306a36Sopenharmony_ci fwspec.param[2] = IRQ_TYPE_LEVEL_HIGH; 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_ci err = irq_domain_alloc_irqs_parent(domain, virq, 1, &fwspec); 10362306a36Sopenharmony_ci if (err) 10462306a36Sopenharmony_ci cb->irq_map[i] = IRQ_FREE; 10562306a36Sopenharmony_ci else 10662306a36Sopenharmony_ci cb->write(i, hwirq); 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci return err; 10962306a36Sopenharmony_ci} 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_cistatic int crossbar_domain_alloc(struct irq_domain *d, unsigned int virq, 11262306a36Sopenharmony_ci unsigned int nr_irqs, void *data) 11362306a36Sopenharmony_ci{ 11462306a36Sopenharmony_ci struct irq_fwspec *fwspec = data; 11562306a36Sopenharmony_ci irq_hw_number_t hwirq; 11662306a36Sopenharmony_ci int i; 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci if (fwspec->param_count != 3) 11962306a36Sopenharmony_ci return -EINVAL; /* Not GIC compliant */ 12062306a36Sopenharmony_ci if (fwspec->param[0] != 0) 12162306a36Sopenharmony_ci return -EINVAL; /* No PPI should point to this domain */ 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci hwirq = fwspec->param[1]; 12462306a36Sopenharmony_ci if ((hwirq + nr_irqs) > cb->max_crossbar_sources) 12562306a36Sopenharmony_ci return -EINVAL; /* Can't deal with this */ 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci for (i = 0; i < nr_irqs; i++) { 12862306a36Sopenharmony_ci int err = allocate_gic_irq(d, virq + i, hwirq + i); 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_ci if (err) 13162306a36Sopenharmony_ci return err; 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci irq_domain_set_hwirq_and_chip(d, virq + i, hwirq + i, 13462306a36Sopenharmony_ci &crossbar_chip, NULL); 13562306a36Sopenharmony_ci } 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci return 0; 13862306a36Sopenharmony_ci} 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci/** 14162306a36Sopenharmony_ci * crossbar_domain_free - unmap/free a crossbar<->irq connection 14262306a36Sopenharmony_ci * @domain: domain of irq to unmap 14362306a36Sopenharmony_ci * @virq: virq number 14462306a36Sopenharmony_ci * @nr_irqs: number of irqs to free 14562306a36Sopenharmony_ci * 14662306a36Sopenharmony_ci * We do not maintain a use count of total number of map/unmap 14762306a36Sopenharmony_ci * calls for a particular irq to find out if a irq can be really 14862306a36Sopenharmony_ci * unmapped. This is because unmap is called during irq_dispose_mapping(irq), 14962306a36Sopenharmony_ci * after which irq is anyways unusable. So an explicit map has to be called 15062306a36Sopenharmony_ci * after that. 15162306a36Sopenharmony_ci */ 15262306a36Sopenharmony_cistatic void crossbar_domain_free(struct irq_domain *domain, unsigned int virq, 15362306a36Sopenharmony_ci unsigned int nr_irqs) 15462306a36Sopenharmony_ci{ 15562306a36Sopenharmony_ci int i; 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci raw_spin_lock(&cb->lock); 15862306a36Sopenharmony_ci for (i = 0; i < nr_irqs; i++) { 15962306a36Sopenharmony_ci struct irq_data *d = irq_domain_get_irq_data(domain, virq + i); 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci irq_domain_reset_irq_data(d); 16262306a36Sopenharmony_ci cb->irq_map[d->hwirq] = IRQ_FREE; 16362306a36Sopenharmony_ci cb->write(d->hwirq, cb->safe_map); 16462306a36Sopenharmony_ci } 16562306a36Sopenharmony_ci raw_spin_unlock(&cb->lock); 16662306a36Sopenharmony_ci} 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_cistatic int crossbar_domain_translate(struct irq_domain *d, 16962306a36Sopenharmony_ci struct irq_fwspec *fwspec, 17062306a36Sopenharmony_ci unsigned long *hwirq, 17162306a36Sopenharmony_ci unsigned int *type) 17262306a36Sopenharmony_ci{ 17362306a36Sopenharmony_ci if (is_of_node(fwspec->fwnode)) { 17462306a36Sopenharmony_ci if (fwspec->param_count != 3) 17562306a36Sopenharmony_ci return -EINVAL; 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_ci /* No PPI should point to this domain */ 17862306a36Sopenharmony_ci if (fwspec->param[0] != 0) 17962306a36Sopenharmony_ci return -EINVAL; 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci *hwirq = fwspec->param[1]; 18262306a36Sopenharmony_ci *type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK; 18362306a36Sopenharmony_ci return 0; 18462306a36Sopenharmony_ci } 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci return -EINVAL; 18762306a36Sopenharmony_ci} 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_cistatic const struct irq_domain_ops crossbar_domain_ops = { 19062306a36Sopenharmony_ci .alloc = crossbar_domain_alloc, 19162306a36Sopenharmony_ci .free = crossbar_domain_free, 19262306a36Sopenharmony_ci .translate = crossbar_domain_translate, 19362306a36Sopenharmony_ci}; 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_cistatic int __init crossbar_of_init(struct device_node *node) 19662306a36Sopenharmony_ci{ 19762306a36Sopenharmony_ci u32 max = 0, entry, reg_size; 19862306a36Sopenharmony_ci int i, size, reserved = 0; 19962306a36Sopenharmony_ci const __be32 *irqsr; 20062306a36Sopenharmony_ci int ret = -ENOMEM; 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ci cb = kzalloc(sizeof(*cb), GFP_KERNEL); 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_ci if (!cb) 20562306a36Sopenharmony_ci return ret; 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci cb->crossbar_base = of_iomap(node, 0); 20862306a36Sopenharmony_ci if (!cb->crossbar_base) 20962306a36Sopenharmony_ci goto err_cb; 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci of_property_read_u32(node, "ti,max-crossbar-sources", 21262306a36Sopenharmony_ci &cb->max_crossbar_sources); 21362306a36Sopenharmony_ci if (!cb->max_crossbar_sources) { 21462306a36Sopenharmony_ci pr_err("missing 'ti,max-crossbar-sources' property\n"); 21562306a36Sopenharmony_ci ret = -EINVAL; 21662306a36Sopenharmony_ci goto err_base; 21762306a36Sopenharmony_ci } 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_ci of_property_read_u32(node, "ti,max-irqs", &max); 22062306a36Sopenharmony_ci if (!max) { 22162306a36Sopenharmony_ci pr_err("missing 'ti,max-irqs' property\n"); 22262306a36Sopenharmony_ci ret = -EINVAL; 22362306a36Sopenharmony_ci goto err_base; 22462306a36Sopenharmony_ci } 22562306a36Sopenharmony_ci cb->irq_map = kcalloc(max, sizeof(int), GFP_KERNEL); 22662306a36Sopenharmony_ci if (!cb->irq_map) 22762306a36Sopenharmony_ci goto err_base; 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_ci cb->int_max = max; 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_ci for (i = 0; i < max; i++) 23262306a36Sopenharmony_ci cb->irq_map[i] = IRQ_FREE; 23362306a36Sopenharmony_ci 23462306a36Sopenharmony_ci /* Get and mark reserved irqs */ 23562306a36Sopenharmony_ci irqsr = of_get_property(node, "ti,irqs-reserved", &size); 23662306a36Sopenharmony_ci if (irqsr) { 23762306a36Sopenharmony_ci size /= sizeof(__be32); 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_ci for (i = 0; i < size; i++) { 24062306a36Sopenharmony_ci of_property_read_u32_index(node, 24162306a36Sopenharmony_ci "ti,irqs-reserved", 24262306a36Sopenharmony_ci i, &entry); 24362306a36Sopenharmony_ci if (entry >= max) { 24462306a36Sopenharmony_ci pr_err("Invalid reserved entry\n"); 24562306a36Sopenharmony_ci ret = -EINVAL; 24662306a36Sopenharmony_ci goto err_irq_map; 24762306a36Sopenharmony_ci } 24862306a36Sopenharmony_ci cb->irq_map[entry] = IRQ_RESERVED; 24962306a36Sopenharmony_ci } 25062306a36Sopenharmony_ci } 25162306a36Sopenharmony_ci 25262306a36Sopenharmony_ci /* Skip irqs hardwired to bypass the crossbar */ 25362306a36Sopenharmony_ci irqsr = of_get_property(node, "ti,irqs-skip", &size); 25462306a36Sopenharmony_ci if (irqsr) { 25562306a36Sopenharmony_ci size /= sizeof(__be32); 25662306a36Sopenharmony_ci 25762306a36Sopenharmony_ci for (i = 0; i < size; i++) { 25862306a36Sopenharmony_ci of_property_read_u32_index(node, 25962306a36Sopenharmony_ci "ti,irqs-skip", 26062306a36Sopenharmony_ci i, &entry); 26162306a36Sopenharmony_ci if (entry >= max) { 26262306a36Sopenharmony_ci pr_err("Invalid skip entry\n"); 26362306a36Sopenharmony_ci ret = -EINVAL; 26462306a36Sopenharmony_ci goto err_irq_map; 26562306a36Sopenharmony_ci } 26662306a36Sopenharmony_ci cb->irq_map[entry] = IRQ_SKIP; 26762306a36Sopenharmony_ci } 26862306a36Sopenharmony_ci } 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_ci 27162306a36Sopenharmony_ci cb->register_offsets = kcalloc(max, sizeof(int), GFP_KERNEL); 27262306a36Sopenharmony_ci if (!cb->register_offsets) 27362306a36Sopenharmony_ci goto err_irq_map; 27462306a36Sopenharmony_ci 27562306a36Sopenharmony_ci of_property_read_u32(node, "ti,reg-size", ®_size); 27662306a36Sopenharmony_ci 27762306a36Sopenharmony_ci switch (reg_size) { 27862306a36Sopenharmony_ci case 1: 27962306a36Sopenharmony_ci cb->write = crossbar_writeb; 28062306a36Sopenharmony_ci break; 28162306a36Sopenharmony_ci case 2: 28262306a36Sopenharmony_ci cb->write = crossbar_writew; 28362306a36Sopenharmony_ci break; 28462306a36Sopenharmony_ci case 4: 28562306a36Sopenharmony_ci cb->write = crossbar_writel; 28662306a36Sopenharmony_ci break; 28762306a36Sopenharmony_ci default: 28862306a36Sopenharmony_ci pr_err("Invalid reg-size property\n"); 28962306a36Sopenharmony_ci ret = -EINVAL; 29062306a36Sopenharmony_ci goto err_reg_offset; 29162306a36Sopenharmony_ci break; 29262306a36Sopenharmony_ci } 29362306a36Sopenharmony_ci 29462306a36Sopenharmony_ci /* 29562306a36Sopenharmony_ci * Register offsets are not linear because of the 29662306a36Sopenharmony_ci * reserved irqs. so find and store the offsets once. 29762306a36Sopenharmony_ci */ 29862306a36Sopenharmony_ci for (i = 0; i < max; i++) { 29962306a36Sopenharmony_ci if (cb->irq_map[i] == IRQ_RESERVED) 30062306a36Sopenharmony_ci continue; 30162306a36Sopenharmony_ci 30262306a36Sopenharmony_ci cb->register_offsets[i] = reserved; 30362306a36Sopenharmony_ci reserved += reg_size; 30462306a36Sopenharmony_ci } 30562306a36Sopenharmony_ci 30662306a36Sopenharmony_ci of_property_read_u32(node, "ti,irqs-safe-map", &cb->safe_map); 30762306a36Sopenharmony_ci /* Initialize the crossbar with safe map to start with */ 30862306a36Sopenharmony_ci for (i = 0; i < max; i++) { 30962306a36Sopenharmony_ci if (cb->irq_map[i] == IRQ_RESERVED || 31062306a36Sopenharmony_ci cb->irq_map[i] == IRQ_SKIP) 31162306a36Sopenharmony_ci continue; 31262306a36Sopenharmony_ci 31362306a36Sopenharmony_ci cb->write(i, cb->safe_map); 31462306a36Sopenharmony_ci } 31562306a36Sopenharmony_ci 31662306a36Sopenharmony_ci raw_spin_lock_init(&cb->lock); 31762306a36Sopenharmony_ci 31862306a36Sopenharmony_ci return 0; 31962306a36Sopenharmony_ci 32062306a36Sopenharmony_cierr_reg_offset: 32162306a36Sopenharmony_ci kfree(cb->register_offsets); 32262306a36Sopenharmony_cierr_irq_map: 32362306a36Sopenharmony_ci kfree(cb->irq_map); 32462306a36Sopenharmony_cierr_base: 32562306a36Sopenharmony_ci iounmap(cb->crossbar_base); 32662306a36Sopenharmony_cierr_cb: 32762306a36Sopenharmony_ci kfree(cb); 32862306a36Sopenharmony_ci 32962306a36Sopenharmony_ci cb = NULL; 33062306a36Sopenharmony_ci return ret; 33162306a36Sopenharmony_ci} 33262306a36Sopenharmony_ci 33362306a36Sopenharmony_cistatic int __init irqcrossbar_init(struct device_node *node, 33462306a36Sopenharmony_ci struct device_node *parent) 33562306a36Sopenharmony_ci{ 33662306a36Sopenharmony_ci struct irq_domain *parent_domain, *domain; 33762306a36Sopenharmony_ci int err; 33862306a36Sopenharmony_ci 33962306a36Sopenharmony_ci if (!parent) { 34062306a36Sopenharmony_ci pr_err("%pOF: no parent, giving up\n", node); 34162306a36Sopenharmony_ci return -ENODEV; 34262306a36Sopenharmony_ci } 34362306a36Sopenharmony_ci 34462306a36Sopenharmony_ci parent_domain = irq_find_host(parent); 34562306a36Sopenharmony_ci if (!parent_domain) { 34662306a36Sopenharmony_ci pr_err("%pOF: unable to obtain parent domain\n", node); 34762306a36Sopenharmony_ci return -ENXIO; 34862306a36Sopenharmony_ci } 34962306a36Sopenharmony_ci 35062306a36Sopenharmony_ci err = crossbar_of_init(node); 35162306a36Sopenharmony_ci if (err) 35262306a36Sopenharmony_ci return err; 35362306a36Sopenharmony_ci 35462306a36Sopenharmony_ci domain = irq_domain_add_hierarchy(parent_domain, 0, 35562306a36Sopenharmony_ci cb->max_crossbar_sources, 35662306a36Sopenharmony_ci node, &crossbar_domain_ops, 35762306a36Sopenharmony_ci NULL); 35862306a36Sopenharmony_ci if (!domain) { 35962306a36Sopenharmony_ci pr_err("%pOF: failed to allocated domain\n", node); 36062306a36Sopenharmony_ci return -ENOMEM; 36162306a36Sopenharmony_ci } 36262306a36Sopenharmony_ci 36362306a36Sopenharmony_ci return 0; 36462306a36Sopenharmony_ci} 36562306a36Sopenharmony_ci 36662306a36Sopenharmony_ciIRQCHIP_DECLARE(ti_irqcrossbar, "ti,irq-crossbar", irqcrossbar_init); 367