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", &reg_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