18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci *  drivers/irqchip/irq-crossbar.c
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci *  Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com
68c2ecf20Sopenharmony_ci *  Author: Sricharan R <r.sricharan@ti.com>
78c2ecf20Sopenharmony_ci */
88c2ecf20Sopenharmony_ci#include <linux/err.h>
98c2ecf20Sopenharmony_ci#include <linux/io.h>
108c2ecf20Sopenharmony_ci#include <linux/irqchip.h>
118c2ecf20Sopenharmony_ci#include <linux/irqdomain.h>
128c2ecf20Sopenharmony_ci#include <linux/of_address.h>
138c2ecf20Sopenharmony_ci#include <linux/of_irq.h>
148c2ecf20Sopenharmony_ci#include <linux/slab.h>
158c2ecf20Sopenharmony_ci
168c2ecf20Sopenharmony_ci#define IRQ_FREE	-1
178c2ecf20Sopenharmony_ci#define IRQ_RESERVED	-2
188c2ecf20Sopenharmony_ci#define IRQ_SKIP	-3
198c2ecf20Sopenharmony_ci#define GIC_IRQ_START	32
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_ci/**
228c2ecf20Sopenharmony_ci * struct crossbar_device - crossbar device description
238c2ecf20Sopenharmony_ci * @lock: spinlock serializing access to @irq_map
248c2ecf20Sopenharmony_ci * @int_max: maximum number of supported interrupts
258c2ecf20Sopenharmony_ci * @safe_map: safe default value to initialize the crossbar
268c2ecf20Sopenharmony_ci * @max_crossbar_sources: Maximum number of crossbar sources
278c2ecf20Sopenharmony_ci * @irq_map: array of interrupts to crossbar number mapping
288c2ecf20Sopenharmony_ci * @crossbar_base: crossbar base address
298c2ecf20Sopenharmony_ci * @register_offsets: offsets for each irq number
308c2ecf20Sopenharmony_ci * @write: register write function pointer
318c2ecf20Sopenharmony_ci */
328c2ecf20Sopenharmony_cistruct crossbar_device {
338c2ecf20Sopenharmony_ci	raw_spinlock_t lock;
348c2ecf20Sopenharmony_ci	uint int_max;
358c2ecf20Sopenharmony_ci	uint safe_map;
368c2ecf20Sopenharmony_ci	uint max_crossbar_sources;
378c2ecf20Sopenharmony_ci	uint *irq_map;
388c2ecf20Sopenharmony_ci	void __iomem *crossbar_base;
398c2ecf20Sopenharmony_ci	int *register_offsets;
408c2ecf20Sopenharmony_ci	void (*write)(int, int);
418c2ecf20Sopenharmony_ci};
428c2ecf20Sopenharmony_ci
438c2ecf20Sopenharmony_cistatic struct crossbar_device *cb;
448c2ecf20Sopenharmony_ci
458c2ecf20Sopenharmony_cistatic void crossbar_writel(int irq_no, int cb_no)
468c2ecf20Sopenharmony_ci{
478c2ecf20Sopenharmony_ci	writel(cb_no, cb->crossbar_base + cb->register_offsets[irq_no]);
488c2ecf20Sopenharmony_ci}
498c2ecf20Sopenharmony_ci
508c2ecf20Sopenharmony_cistatic void crossbar_writew(int irq_no, int cb_no)
518c2ecf20Sopenharmony_ci{
528c2ecf20Sopenharmony_ci	writew(cb_no, cb->crossbar_base + cb->register_offsets[irq_no]);
538c2ecf20Sopenharmony_ci}
548c2ecf20Sopenharmony_ci
558c2ecf20Sopenharmony_cistatic void crossbar_writeb(int irq_no, int cb_no)
568c2ecf20Sopenharmony_ci{
578c2ecf20Sopenharmony_ci	writeb(cb_no, cb->crossbar_base + cb->register_offsets[irq_no]);
588c2ecf20Sopenharmony_ci}
598c2ecf20Sopenharmony_ci
608c2ecf20Sopenharmony_cistatic struct irq_chip crossbar_chip = {
618c2ecf20Sopenharmony_ci	.name			= "CBAR",
628c2ecf20Sopenharmony_ci	.irq_eoi		= irq_chip_eoi_parent,
638c2ecf20Sopenharmony_ci	.irq_mask		= irq_chip_mask_parent,
648c2ecf20Sopenharmony_ci	.irq_unmask		= irq_chip_unmask_parent,
658c2ecf20Sopenharmony_ci	.irq_retrigger		= irq_chip_retrigger_hierarchy,
668c2ecf20Sopenharmony_ci	.irq_set_type		= irq_chip_set_type_parent,
678c2ecf20Sopenharmony_ci	.flags			= IRQCHIP_MASK_ON_SUSPEND |
688c2ecf20Sopenharmony_ci				  IRQCHIP_SKIP_SET_WAKE,
698c2ecf20Sopenharmony_ci#ifdef CONFIG_SMP
708c2ecf20Sopenharmony_ci	.irq_set_affinity	= irq_chip_set_affinity_parent,
718c2ecf20Sopenharmony_ci#endif
728c2ecf20Sopenharmony_ci};
738c2ecf20Sopenharmony_ci
748c2ecf20Sopenharmony_cistatic int allocate_gic_irq(struct irq_domain *domain, unsigned virq,
758c2ecf20Sopenharmony_ci			    irq_hw_number_t hwirq)
768c2ecf20Sopenharmony_ci{
778c2ecf20Sopenharmony_ci	struct irq_fwspec fwspec;
788c2ecf20Sopenharmony_ci	int i;
798c2ecf20Sopenharmony_ci	int err;
808c2ecf20Sopenharmony_ci
818c2ecf20Sopenharmony_ci	if (!irq_domain_get_of_node(domain->parent))
828c2ecf20Sopenharmony_ci		return -EINVAL;
838c2ecf20Sopenharmony_ci
848c2ecf20Sopenharmony_ci	raw_spin_lock(&cb->lock);
858c2ecf20Sopenharmony_ci	for (i = cb->int_max - 1; i >= 0; i--) {
868c2ecf20Sopenharmony_ci		if (cb->irq_map[i] == IRQ_FREE) {
878c2ecf20Sopenharmony_ci			cb->irq_map[i] = hwirq;
888c2ecf20Sopenharmony_ci			break;
898c2ecf20Sopenharmony_ci		}
908c2ecf20Sopenharmony_ci	}
918c2ecf20Sopenharmony_ci	raw_spin_unlock(&cb->lock);
928c2ecf20Sopenharmony_ci
938c2ecf20Sopenharmony_ci	if (i < 0)
948c2ecf20Sopenharmony_ci		return -ENODEV;
958c2ecf20Sopenharmony_ci
968c2ecf20Sopenharmony_ci	fwspec.fwnode = domain->parent->fwnode;
978c2ecf20Sopenharmony_ci	fwspec.param_count = 3;
988c2ecf20Sopenharmony_ci	fwspec.param[0] = 0;	/* SPI */
998c2ecf20Sopenharmony_ci	fwspec.param[1] = i;
1008c2ecf20Sopenharmony_ci	fwspec.param[2] = IRQ_TYPE_LEVEL_HIGH;
1018c2ecf20Sopenharmony_ci
1028c2ecf20Sopenharmony_ci	err = irq_domain_alloc_irqs_parent(domain, virq, 1, &fwspec);
1038c2ecf20Sopenharmony_ci	if (err)
1048c2ecf20Sopenharmony_ci		cb->irq_map[i] = IRQ_FREE;
1058c2ecf20Sopenharmony_ci	else
1068c2ecf20Sopenharmony_ci		cb->write(i, hwirq);
1078c2ecf20Sopenharmony_ci
1088c2ecf20Sopenharmony_ci	return err;
1098c2ecf20Sopenharmony_ci}
1108c2ecf20Sopenharmony_ci
1118c2ecf20Sopenharmony_cistatic int crossbar_domain_alloc(struct irq_domain *d, unsigned int virq,
1128c2ecf20Sopenharmony_ci				 unsigned int nr_irqs, void *data)
1138c2ecf20Sopenharmony_ci{
1148c2ecf20Sopenharmony_ci	struct irq_fwspec *fwspec = data;
1158c2ecf20Sopenharmony_ci	irq_hw_number_t hwirq;
1168c2ecf20Sopenharmony_ci	int i;
1178c2ecf20Sopenharmony_ci
1188c2ecf20Sopenharmony_ci	if (fwspec->param_count != 3)
1198c2ecf20Sopenharmony_ci		return -EINVAL;	/* Not GIC compliant */
1208c2ecf20Sopenharmony_ci	if (fwspec->param[0] != 0)
1218c2ecf20Sopenharmony_ci		return -EINVAL;	/* No PPI should point to this domain */
1228c2ecf20Sopenharmony_ci
1238c2ecf20Sopenharmony_ci	hwirq = fwspec->param[1];
1248c2ecf20Sopenharmony_ci	if ((hwirq + nr_irqs) > cb->max_crossbar_sources)
1258c2ecf20Sopenharmony_ci		return -EINVAL;	/* Can't deal with this */
1268c2ecf20Sopenharmony_ci
1278c2ecf20Sopenharmony_ci	for (i = 0; i < nr_irqs; i++) {
1288c2ecf20Sopenharmony_ci		int err = allocate_gic_irq(d, virq + i, hwirq + i);
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ci		if (err)
1318c2ecf20Sopenharmony_ci			return err;
1328c2ecf20Sopenharmony_ci
1338c2ecf20Sopenharmony_ci		irq_domain_set_hwirq_and_chip(d, virq + i, hwirq + i,
1348c2ecf20Sopenharmony_ci					      &crossbar_chip, NULL);
1358c2ecf20Sopenharmony_ci	}
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_ci	return 0;
1388c2ecf20Sopenharmony_ci}
1398c2ecf20Sopenharmony_ci
1408c2ecf20Sopenharmony_ci/**
1418c2ecf20Sopenharmony_ci * crossbar_domain_free - unmap/free a crossbar<->irq connection
1428c2ecf20Sopenharmony_ci * @domain: domain of irq to unmap
1438c2ecf20Sopenharmony_ci * @virq: virq number
1448c2ecf20Sopenharmony_ci * @nr_irqs: number of irqs to free
1458c2ecf20Sopenharmony_ci *
1468c2ecf20Sopenharmony_ci * We do not maintain a use count of total number of map/unmap
1478c2ecf20Sopenharmony_ci * calls for a particular irq to find out if a irq can be really
1488c2ecf20Sopenharmony_ci * unmapped. This is because unmap is called during irq_dispose_mapping(irq),
1498c2ecf20Sopenharmony_ci * after which irq is anyways unusable. So an explicit map has to be called
1508c2ecf20Sopenharmony_ci * after that.
1518c2ecf20Sopenharmony_ci */
1528c2ecf20Sopenharmony_cistatic void crossbar_domain_free(struct irq_domain *domain, unsigned int virq,
1538c2ecf20Sopenharmony_ci				 unsigned int nr_irqs)
1548c2ecf20Sopenharmony_ci{
1558c2ecf20Sopenharmony_ci	int i;
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_ci	raw_spin_lock(&cb->lock);
1588c2ecf20Sopenharmony_ci	for (i = 0; i < nr_irqs; i++) {
1598c2ecf20Sopenharmony_ci		struct irq_data *d = irq_domain_get_irq_data(domain, virq + i);
1608c2ecf20Sopenharmony_ci
1618c2ecf20Sopenharmony_ci		irq_domain_reset_irq_data(d);
1628c2ecf20Sopenharmony_ci		cb->irq_map[d->hwirq] = IRQ_FREE;
1638c2ecf20Sopenharmony_ci		cb->write(d->hwirq, cb->safe_map);
1648c2ecf20Sopenharmony_ci	}
1658c2ecf20Sopenharmony_ci	raw_spin_unlock(&cb->lock);
1668c2ecf20Sopenharmony_ci}
1678c2ecf20Sopenharmony_ci
1688c2ecf20Sopenharmony_cistatic int crossbar_domain_translate(struct irq_domain *d,
1698c2ecf20Sopenharmony_ci				     struct irq_fwspec *fwspec,
1708c2ecf20Sopenharmony_ci				     unsigned long *hwirq,
1718c2ecf20Sopenharmony_ci				     unsigned int *type)
1728c2ecf20Sopenharmony_ci{
1738c2ecf20Sopenharmony_ci	if (is_of_node(fwspec->fwnode)) {
1748c2ecf20Sopenharmony_ci		if (fwspec->param_count != 3)
1758c2ecf20Sopenharmony_ci			return -EINVAL;
1768c2ecf20Sopenharmony_ci
1778c2ecf20Sopenharmony_ci		/* No PPI should point to this domain */
1788c2ecf20Sopenharmony_ci		if (fwspec->param[0] != 0)
1798c2ecf20Sopenharmony_ci			return -EINVAL;
1808c2ecf20Sopenharmony_ci
1818c2ecf20Sopenharmony_ci		*hwirq = fwspec->param[1];
1828c2ecf20Sopenharmony_ci		*type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK;
1838c2ecf20Sopenharmony_ci		return 0;
1848c2ecf20Sopenharmony_ci	}
1858c2ecf20Sopenharmony_ci
1868c2ecf20Sopenharmony_ci	return -EINVAL;
1878c2ecf20Sopenharmony_ci}
1888c2ecf20Sopenharmony_ci
1898c2ecf20Sopenharmony_cistatic const struct irq_domain_ops crossbar_domain_ops = {
1908c2ecf20Sopenharmony_ci	.alloc		= crossbar_domain_alloc,
1918c2ecf20Sopenharmony_ci	.free		= crossbar_domain_free,
1928c2ecf20Sopenharmony_ci	.translate	= crossbar_domain_translate,
1938c2ecf20Sopenharmony_ci};
1948c2ecf20Sopenharmony_ci
1958c2ecf20Sopenharmony_cistatic int __init crossbar_of_init(struct device_node *node)
1968c2ecf20Sopenharmony_ci{
1978c2ecf20Sopenharmony_ci	u32 max = 0, entry, reg_size;
1988c2ecf20Sopenharmony_ci	int i, size, reserved = 0;
1998c2ecf20Sopenharmony_ci	const __be32 *irqsr;
2008c2ecf20Sopenharmony_ci	int ret = -ENOMEM;
2018c2ecf20Sopenharmony_ci
2028c2ecf20Sopenharmony_ci	cb = kzalloc(sizeof(*cb), GFP_KERNEL);
2038c2ecf20Sopenharmony_ci
2048c2ecf20Sopenharmony_ci	if (!cb)
2058c2ecf20Sopenharmony_ci		return ret;
2068c2ecf20Sopenharmony_ci
2078c2ecf20Sopenharmony_ci	cb->crossbar_base = of_iomap(node, 0);
2088c2ecf20Sopenharmony_ci	if (!cb->crossbar_base)
2098c2ecf20Sopenharmony_ci		goto err_cb;
2108c2ecf20Sopenharmony_ci
2118c2ecf20Sopenharmony_ci	of_property_read_u32(node, "ti,max-crossbar-sources",
2128c2ecf20Sopenharmony_ci			     &cb->max_crossbar_sources);
2138c2ecf20Sopenharmony_ci	if (!cb->max_crossbar_sources) {
2148c2ecf20Sopenharmony_ci		pr_err("missing 'ti,max-crossbar-sources' property\n");
2158c2ecf20Sopenharmony_ci		ret = -EINVAL;
2168c2ecf20Sopenharmony_ci		goto err_base;
2178c2ecf20Sopenharmony_ci	}
2188c2ecf20Sopenharmony_ci
2198c2ecf20Sopenharmony_ci	of_property_read_u32(node, "ti,max-irqs", &max);
2208c2ecf20Sopenharmony_ci	if (!max) {
2218c2ecf20Sopenharmony_ci		pr_err("missing 'ti,max-irqs' property\n");
2228c2ecf20Sopenharmony_ci		ret = -EINVAL;
2238c2ecf20Sopenharmony_ci		goto err_base;
2248c2ecf20Sopenharmony_ci	}
2258c2ecf20Sopenharmony_ci	cb->irq_map = kcalloc(max, sizeof(int), GFP_KERNEL);
2268c2ecf20Sopenharmony_ci	if (!cb->irq_map)
2278c2ecf20Sopenharmony_ci		goto err_base;
2288c2ecf20Sopenharmony_ci
2298c2ecf20Sopenharmony_ci	cb->int_max = max;
2308c2ecf20Sopenharmony_ci
2318c2ecf20Sopenharmony_ci	for (i = 0; i < max; i++)
2328c2ecf20Sopenharmony_ci		cb->irq_map[i] = IRQ_FREE;
2338c2ecf20Sopenharmony_ci
2348c2ecf20Sopenharmony_ci	/* Get and mark reserved irqs */
2358c2ecf20Sopenharmony_ci	irqsr = of_get_property(node, "ti,irqs-reserved", &size);
2368c2ecf20Sopenharmony_ci	if (irqsr) {
2378c2ecf20Sopenharmony_ci		size /= sizeof(__be32);
2388c2ecf20Sopenharmony_ci
2398c2ecf20Sopenharmony_ci		for (i = 0; i < size; i++) {
2408c2ecf20Sopenharmony_ci			of_property_read_u32_index(node,
2418c2ecf20Sopenharmony_ci						   "ti,irqs-reserved",
2428c2ecf20Sopenharmony_ci						   i, &entry);
2438c2ecf20Sopenharmony_ci			if (entry >= max) {
2448c2ecf20Sopenharmony_ci				pr_err("Invalid reserved entry\n");
2458c2ecf20Sopenharmony_ci				ret = -EINVAL;
2468c2ecf20Sopenharmony_ci				goto err_irq_map;
2478c2ecf20Sopenharmony_ci			}
2488c2ecf20Sopenharmony_ci			cb->irq_map[entry] = IRQ_RESERVED;
2498c2ecf20Sopenharmony_ci		}
2508c2ecf20Sopenharmony_ci	}
2518c2ecf20Sopenharmony_ci
2528c2ecf20Sopenharmony_ci	/* Skip irqs hardwired to bypass the crossbar */
2538c2ecf20Sopenharmony_ci	irqsr = of_get_property(node, "ti,irqs-skip", &size);
2548c2ecf20Sopenharmony_ci	if (irqsr) {
2558c2ecf20Sopenharmony_ci		size /= sizeof(__be32);
2568c2ecf20Sopenharmony_ci
2578c2ecf20Sopenharmony_ci		for (i = 0; i < size; i++) {
2588c2ecf20Sopenharmony_ci			of_property_read_u32_index(node,
2598c2ecf20Sopenharmony_ci						   "ti,irqs-skip",
2608c2ecf20Sopenharmony_ci						   i, &entry);
2618c2ecf20Sopenharmony_ci			if (entry >= max) {
2628c2ecf20Sopenharmony_ci				pr_err("Invalid skip entry\n");
2638c2ecf20Sopenharmony_ci				ret = -EINVAL;
2648c2ecf20Sopenharmony_ci				goto err_irq_map;
2658c2ecf20Sopenharmony_ci			}
2668c2ecf20Sopenharmony_ci			cb->irq_map[entry] = IRQ_SKIP;
2678c2ecf20Sopenharmony_ci		}
2688c2ecf20Sopenharmony_ci	}
2698c2ecf20Sopenharmony_ci
2708c2ecf20Sopenharmony_ci
2718c2ecf20Sopenharmony_ci	cb->register_offsets = kcalloc(max, sizeof(int), GFP_KERNEL);
2728c2ecf20Sopenharmony_ci	if (!cb->register_offsets)
2738c2ecf20Sopenharmony_ci		goto err_irq_map;
2748c2ecf20Sopenharmony_ci
2758c2ecf20Sopenharmony_ci	of_property_read_u32(node, "ti,reg-size", &reg_size);
2768c2ecf20Sopenharmony_ci
2778c2ecf20Sopenharmony_ci	switch (reg_size) {
2788c2ecf20Sopenharmony_ci	case 1:
2798c2ecf20Sopenharmony_ci		cb->write = crossbar_writeb;
2808c2ecf20Sopenharmony_ci		break;
2818c2ecf20Sopenharmony_ci	case 2:
2828c2ecf20Sopenharmony_ci		cb->write = crossbar_writew;
2838c2ecf20Sopenharmony_ci		break;
2848c2ecf20Sopenharmony_ci	case 4:
2858c2ecf20Sopenharmony_ci		cb->write = crossbar_writel;
2868c2ecf20Sopenharmony_ci		break;
2878c2ecf20Sopenharmony_ci	default:
2888c2ecf20Sopenharmony_ci		pr_err("Invalid reg-size property\n");
2898c2ecf20Sopenharmony_ci		ret = -EINVAL;
2908c2ecf20Sopenharmony_ci		goto err_reg_offset;
2918c2ecf20Sopenharmony_ci		break;
2928c2ecf20Sopenharmony_ci	}
2938c2ecf20Sopenharmony_ci
2948c2ecf20Sopenharmony_ci	/*
2958c2ecf20Sopenharmony_ci	 * Register offsets are not linear because of the
2968c2ecf20Sopenharmony_ci	 * reserved irqs. so find and store the offsets once.
2978c2ecf20Sopenharmony_ci	 */
2988c2ecf20Sopenharmony_ci	for (i = 0; i < max; i++) {
2998c2ecf20Sopenharmony_ci		if (cb->irq_map[i] == IRQ_RESERVED)
3008c2ecf20Sopenharmony_ci			continue;
3018c2ecf20Sopenharmony_ci
3028c2ecf20Sopenharmony_ci		cb->register_offsets[i] = reserved;
3038c2ecf20Sopenharmony_ci		reserved += reg_size;
3048c2ecf20Sopenharmony_ci	}
3058c2ecf20Sopenharmony_ci
3068c2ecf20Sopenharmony_ci	of_property_read_u32(node, "ti,irqs-safe-map", &cb->safe_map);
3078c2ecf20Sopenharmony_ci	/* Initialize the crossbar with safe map to start with */
3088c2ecf20Sopenharmony_ci	for (i = 0; i < max; i++) {
3098c2ecf20Sopenharmony_ci		if (cb->irq_map[i] == IRQ_RESERVED ||
3108c2ecf20Sopenharmony_ci		    cb->irq_map[i] == IRQ_SKIP)
3118c2ecf20Sopenharmony_ci			continue;
3128c2ecf20Sopenharmony_ci
3138c2ecf20Sopenharmony_ci		cb->write(i, cb->safe_map);
3148c2ecf20Sopenharmony_ci	}
3158c2ecf20Sopenharmony_ci
3168c2ecf20Sopenharmony_ci	raw_spin_lock_init(&cb->lock);
3178c2ecf20Sopenharmony_ci
3188c2ecf20Sopenharmony_ci	return 0;
3198c2ecf20Sopenharmony_ci
3208c2ecf20Sopenharmony_cierr_reg_offset:
3218c2ecf20Sopenharmony_ci	kfree(cb->register_offsets);
3228c2ecf20Sopenharmony_cierr_irq_map:
3238c2ecf20Sopenharmony_ci	kfree(cb->irq_map);
3248c2ecf20Sopenharmony_cierr_base:
3258c2ecf20Sopenharmony_ci	iounmap(cb->crossbar_base);
3268c2ecf20Sopenharmony_cierr_cb:
3278c2ecf20Sopenharmony_ci	kfree(cb);
3288c2ecf20Sopenharmony_ci
3298c2ecf20Sopenharmony_ci	cb = NULL;
3308c2ecf20Sopenharmony_ci	return ret;
3318c2ecf20Sopenharmony_ci}
3328c2ecf20Sopenharmony_ci
3338c2ecf20Sopenharmony_cistatic int __init irqcrossbar_init(struct device_node *node,
3348c2ecf20Sopenharmony_ci				   struct device_node *parent)
3358c2ecf20Sopenharmony_ci{
3368c2ecf20Sopenharmony_ci	struct irq_domain *parent_domain, *domain;
3378c2ecf20Sopenharmony_ci	int err;
3388c2ecf20Sopenharmony_ci
3398c2ecf20Sopenharmony_ci	if (!parent) {
3408c2ecf20Sopenharmony_ci		pr_err("%pOF: no parent, giving up\n", node);
3418c2ecf20Sopenharmony_ci		return -ENODEV;
3428c2ecf20Sopenharmony_ci	}
3438c2ecf20Sopenharmony_ci
3448c2ecf20Sopenharmony_ci	parent_domain = irq_find_host(parent);
3458c2ecf20Sopenharmony_ci	if (!parent_domain) {
3468c2ecf20Sopenharmony_ci		pr_err("%pOF: unable to obtain parent domain\n", node);
3478c2ecf20Sopenharmony_ci		return -ENXIO;
3488c2ecf20Sopenharmony_ci	}
3498c2ecf20Sopenharmony_ci
3508c2ecf20Sopenharmony_ci	err = crossbar_of_init(node);
3518c2ecf20Sopenharmony_ci	if (err)
3528c2ecf20Sopenharmony_ci		return err;
3538c2ecf20Sopenharmony_ci
3548c2ecf20Sopenharmony_ci	domain = irq_domain_add_hierarchy(parent_domain, 0,
3558c2ecf20Sopenharmony_ci					  cb->max_crossbar_sources,
3568c2ecf20Sopenharmony_ci					  node, &crossbar_domain_ops,
3578c2ecf20Sopenharmony_ci					  NULL);
3588c2ecf20Sopenharmony_ci	if (!domain) {
3598c2ecf20Sopenharmony_ci		pr_err("%pOF: failed to allocated domain\n", node);
3608c2ecf20Sopenharmony_ci		return -ENOMEM;
3618c2ecf20Sopenharmony_ci	}
3628c2ecf20Sopenharmony_ci
3638c2ecf20Sopenharmony_ci	return 0;
3648c2ecf20Sopenharmony_ci}
3658c2ecf20Sopenharmony_ci
3668c2ecf20Sopenharmony_ciIRQCHIP_DECLARE(ti_irqcrossbar, "ti,irq-crossbar", irqcrossbar_init);
367