162306a36Sopenharmony_ci/*
262306a36Sopenharmony_ci * Support for virtual IRQ subgroups.
362306a36Sopenharmony_ci *
462306a36Sopenharmony_ci * Copyright (C) 2010  Paul Mundt
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * This file is subject to the terms and conditions of the GNU General Public
762306a36Sopenharmony_ci * License.  See the file "COPYING" in the main directory of this archive
862306a36Sopenharmony_ci * for more details.
962306a36Sopenharmony_ci */
1062306a36Sopenharmony_ci#define pr_fmt(fmt) "intc: " fmt
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci#include <linux/slab.h>
1362306a36Sopenharmony_ci#include <linux/irq.h>
1462306a36Sopenharmony_ci#include <linux/list.h>
1562306a36Sopenharmony_ci#include <linux/radix-tree.h>
1662306a36Sopenharmony_ci#include <linux/spinlock.h>
1762306a36Sopenharmony_ci#include <linux/export.h>
1862306a36Sopenharmony_ci#include "internals.h"
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_cistatic struct intc_map_entry intc_irq_xlate[INTC_NR_IRQS];
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_cistruct intc_virq_list {
2362306a36Sopenharmony_ci	unsigned int irq;
2462306a36Sopenharmony_ci	struct intc_virq_list *next;
2562306a36Sopenharmony_ci};
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci#define for_each_virq(entry, head) \
2862306a36Sopenharmony_ci	for (entry = head; entry; entry = entry->next)
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ci/*
3162306a36Sopenharmony_ci * Tags for the radix tree
3262306a36Sopenharmony_ci */
3362306a36Sopenharmony_ci#define INTC_TAG_VIRQ_NEEDS_ALLOC	0
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_civoid intc_irq_xlate_set(unsigned int irq, intc_enum id, struct intc_desc_int *d)
3662306a36Sopenharmony_ci{
3762306a36Sopenharmony_ci	unsigned long flags;
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci	raw_spin_lock_irqsave(&intc_big_lock, flags);
4062306a36Sopenharmony_ci	intc_irq_xlate[irq].enum_id = id;
4162306a36Sopenharmony_ci	intc_irq_xlate[irq].desc = d;
4262306a36Sopenharmony_ci	raw_spin_unlock_irqrestore(&intc_big_lock, flags);
4362306a36Sopenharmony_ci}
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_cistruct intc_map_entry *intc_irq_xlate_get(unsigned int irq)
4662306a36Sopenharmony_ci{
4762306a36Sopenharmony_ci	return intc_irq_xlate + irq;
4862306a36Sopenharmony_ci}
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ciint intc_irq_lookup(const char *chipname, intc_enum enum_id)
5162306a36Sopenharmony_ci{
5262306a36Sopenharmony_ci	struct intc_map_entry *ptr;
5362306a36Sopenharmony_ci	struct intc_desc_int *d;
5462306a36Sopenharmony_ci	int irq = -1;
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_ci	list_for_each_entry(d, &intc_list, list) {
5762306a36Sopenharmony_ci		int tagged;
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci		if (strcmp(d->chip.name, chipname) != 0)
6062306a36Sopenharmony_ci			continue;
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci		/*
6362306a36Sopenharmony_ci		 * Catch early lookups for subgroup VIRQs that have not
6462306a36Sopenharmony_ci		 * yet been allocated an IRQ. This already includes a
6562306a36Sopenharmony_ci		 * fast-path out if the tree is untagged, so there is no
6662306a36Sopenharmony_ci		 * need to explicitly test the root tree.
6762306a36Sopenharmony_ci		 */
6862306a36Sopenharmony_ci		tagged = radix_tree_tag_get(&d->tree, enum_id,
6962306a36Sopenharmony_ci					    INTC_TAG_VIRQ_NEEDS_ALLOC);
7062306a36Sopenharmony_ci		if (unlikely(tagged))
7162306a36Sopenharmony_ci			break;
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci		ptr = radix_tree_lookup(&d->tree, enum_id);
7462306a36Sopenharmony_ci		if (ptr) {
7562306a36Sopenharmony_ci			irq = ptr - intc_irq_xlate;
7662306a36Sopenharmony_ci			break;
7762306a36Sopenharmony_ci		}
7862306a36Sopenharmony_ci	}
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci	return irq;
8162306a36Sopenharmony_ci}
8262306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(intc_irq_lookup);
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_cistatic int add_virq_to_pirq(unsigned int irq, unsigned int virq)
8562306a36Sopenharmony_ci{
8662306a36Sopenharmony_ci	struct intc_virq_list *entry;
8762306a36Sopenharmony_ci	struct intc_virq_list **last = NULL;
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci	/* scan for duplicates */
9062306a36Sopenharmony_ci	for_each_virq(entry, irq_get_handler_data(irq)) {
9162306a36Sopenharmony_ci		if (entry->irq == virq)
9262306a36Sopenharmony_ci			return 0;
9362306a36Sopenharmony_ci		last = &entry->next;
9462306a36Sopenharmony_ci	}
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci	entry = kzalloc(sizeof(struct intc_virq_list), GFP_ATOMIC);
9762306a36Sopenharmony_ci	if (!entry)
9862306a36Sopenharmony_ci		return -ENOMEM;
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci	entry->irq = virq;
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci	if (last)
10362306a36Sopenharmony_ci		*last = entry;
10462306a36Sopenharmony_ci	else
10562306a36Sopenharmony_ci		irq_set_handler_data(irq, entry);
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci	return 0;
10862306a36Sopenharmony_ci}
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_cistatic void intc_virq_handler(struct irq_desc *desc)
11162306a36Sopenharmony_ci{
11262306a36Sopenharmony_ci	unsigned int irq = irq_desc_get_irq(desc);
11362306a36Sopenharmony_ci	struct irq_data *data = irq_desc_get_irq_data(desc);
11462306a36Sopenharmony_ci	struct irq_chip *chip = irq_data_get_irq_chip(data);
11562306a36Sopenharmony_ci	struct intc_virq_list *entry, *vlist = irq_data_get_irq_handler_data(data);
11662306a36Sopenharmony_ci	struct intc_desc_int *d = get_intc_desc(irq);
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci	chip->irq_mask_ack(data);
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	for_each_virq(entry, vlist) {
12162306a36Sopenharmony_ci		unsigned long addr, handle;
12262306a36Sopenharmony_ci		struct irq_desc *vdesc = irq_to_desc(entry->irq);
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci		if (vdesc) {
12562306a36Sopenharmony_ci			handle = (unsigned long)irq_desc_get_handler_data(vdesc);
12662306a36Sopenharmony_ci			addr = INTC_REG(d, _INTC_ADDR_E(handle), 0);
12762306a36Sopenharmony_ci			if (intc_reg_fns[_INTC_FN(handle)](addr, handle, 0))
12862306a36Sopenharmony_ci				generic_handle_irq_desc(vdesc);
12962306a36Sopenharmony_ci		}
13062306a36Sopenharmony_ci	}
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci	chip->irq_unmask(data);
13362306a36Sopenharmony_ci}
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_cistatic unsigned long __init intc_subgroup_data(struct intc_subgroup *subgroup,
13662306a36Sopenharmony_ci					       struct intc_desc_int *d,
13762306a36Sopenharmony_ci					       unsigned int index)
13862306a36Sopenharmony_ci{
13962306a36Sopenharmony_ci	unsigned int fn = REG_FN_TEST_BASE + (subgroup->reg_width >> 3) - 1;
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ci	return _INTC_MK(fn, MODE_ENABLE_REG, intc_get_reg(d, subgroup->reg),
14262306a36Sopenharmony_ci			0, 1, (subgroup->reg_width - 1) - index);
14362306a36Sopenharmony_ci}
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_cistatic void __init intc_subgroup_init_one(struct intc_desc *desc,
14662306a36Sopenharmony_ci					  struct intc_desc_int *d,
14762306a36Sopenharmony_ci					  struct intc_subgroup *subgroup)
14862306a36Sopenharmony_ci{
14962306a36Sopenharmony_ci	struct intc_map_entry *mapped;
15062306a36Sopenharmony_ci	unsigned int pirq;
15162306a36Sopenharmony_ci	unsigned long flags;
15262306a36Sopenharmony_ci	int i;
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci	mapped = radix_tree_lookup(&d->tree, subgroup->parent_id);
15562306a36Sopenharmony_ci	if (!mapped) {
15662306a36Sopenharmony_ci		WARN_ON(1);
15762306a36Sopenharmony_ci		return;
15862306a36Sopenharmony_ci	}
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci	pirq = mapped - intc_irq_xlate;
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci	raw_spin_lock_irqsave(&d->lock, flags);
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(subgroup->enum_ids); i++) {
16562306a36Sopenharmony_ci		struct intc_subgroup_entry *entry;
16662306a36Sopenharmony_ci		int err;
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_ci		if (!subgroup->enum_ids[i])
16962306a36Sopenharmony_ci			continue;
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci		entry = kmalloc(sizeof(*entry), GFP_NOWAIT);
17262306a36Sopenharmony_ci		if (!entry)
17362306a36Sopenharmony_ci			break;
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_ci		entry->pirq = pirq;
17662306a36Sopenharmony_ci		entry->enum_id = subgroup->enum_ids[i];
17762306a36Sopenharmony_ci		entry->handle = intc_subgroup_data(subgroup, d, i);
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_ci		err = radix_tree_insert(&d->tree, entry->enum_id, entry);
18062306a36Sopenharmony_ci		if (unlikely(err < 0))
18162306a36Sopenharmony_ci			break;
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ci		radix_tree_tag_set(&d->tree, entry->enum_id,
18462306a36Sopenharmony_ci				   INTC_TAG_VIRQ_NEEDS_ALLOC);
18562306a36Sopenharmony_ci	}
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci	raw_spin_unlock_irqrestore(&d->lock, flags);
18862306a36Sopenharmony_ci}
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_civoid __init intc_subgroup_init(struct intc_desc *desc, struct intc_desc_int *d)
19162306a36Sopenharmony_ci{
19262306a36Sopenharmony_ci	int i;
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_ci	if (!desc->hw.subgroups)
19562306a36Sopenharmony_ci		return;
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci	for (i = 0; i < desc->hw.nr_subgroups; i++)
19862306a36Sopenharmony_ci		intc_subgroup_init_one(desc, d, desc->hw.subgroups + i);
19962306a36Sopenharmony_ci}
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_cistatic void __init intc_subgroup_map(struct intc_desc_int *d)
20262306a36Sopenharmony_ci{
20362306a36Sopenharmony_ci	struct intc_subgroup_entry *entries[32];
20462306a36Sopenharmony_ci	unsigned long flags;
20562306a36Sopenharmony_ci	unsigned int nr_found;
20662306a36Sopenharmony_ci	int i;
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci	raw_spin_lock_irqsave(&d->lock, flags);
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_cirestart:
21162306a36Sopenharmony_ci	nr_found = radix_tree_gang_lookup_tag_slot(&d->tree,
21262306a36Sopenharmony_ci			(void ***)entries, 0, ARRAY_SIZE(entries),
21362306a36Sopenharmony_ci			INTC_TAG_VIRQ_NEEDS_ALLOC);
21462306a36Sopenharmony_ci
21562306a36Sopenharmony_ci	for (i = 0; i < nr_found; i++) {
21662306a36Sopenharmony_ci		struct intc_subgroup_entry *entry;
21762306a36Sopenharmony_ci		int irq;
21862306a36Sopenharmony_ci
21962306a36Sopenharmony_ci		entry = radix_tree_deref_slot((void **)entries[i]);
22062306a36Sopenharmony_ci		if (unlikely(!entry))
22162306a36Sopenharmony_ci			continue;
22262306a36Sopenharmony_ci		if (radix_tree_deref_retry(entry))
22362306a36Sopenharmony_ci			goto restart;
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_ci		irq = irq_alloc_desc(numa_node_id());
22662306a36Sopenharmony_ci		if (unlikely(irq < 0)) {
22762306a36Sopenharmony_ci			pr_err("no more free IRQs, bailing..\n");
22862306a36Sopenharmony_ci			break;
22962306a36Sopenharmony_ci		}
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_ci		activate_irq(irq);
23262306a36Sopenharmony_ci
23362306a36Sopenharmony_ci		pr_info("Setting up a chained VIRQ from %d -> %d\n",
23462306a36Sopenharmony_ci			irq, entry->pirq);
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_ci		intc_irq_xlate_set(irq, entry->enum_id, d);
23762306a36Sopenharmony_ci
23862306a36Sopenharmony_ci		irq_set_chip_and_handler_name(irq, irq_get_chip(entry->pirq),
23962306a36Sopenharmony_ci					      handle_simple_irq, "virq");
24062306a36Sopenharmony_ci		irq_set_chip_data(irq, irq_get_chip_data(entry->pirq));
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_ci		irq_set_handler_data(irq, (void *)entry->handle);
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_ci		/*
24562306a36Sopenharmony_ci		 * Set the virtual IRQ as non-threadable.
24662306a36Sopenharmony_ci		 */
24762306a36Sopenharmony_ci		irq_set_nothread(irq);
24862306a36Sopenharmony_ci
24962306a36Sopenharmony_ci		/* Set handler data before installing the handler */
25062306a36Sopenharmony_ci		add_virq_to_pirq(entry->pirq, irq);
25162306a36Sopenharmony_ci		irq_set_chained_handler(entry->pirq, intc_virq_handler);
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_ci		radix_tree_tag_clear(&d->tree, entry->enum_id,
25462306a36Sopenharmony_ci				     INTC_TAG_VIRQ_NEEDS_ALLOC);
25562306a36Sopenharmony_ci		radix_tree_replace_slot(&d->tree, (void **)entries[i],
25662306a36Sopenharmony_ci					&intc_irq_xlate[irq]);
25762306a36Sopenharmony_ci	}
25862306a36Sopenharmony_ci
25962306a36Sopenharmony_ci	raw_spin_unlock_irqrestore(&d->lock, flags);
26062306a36Sopenharmony_ci}
26162306a36Sopenharmony_ci
26262306a36Sopenharmony_civoid __init intc_finalize(void)
26362306a36Sopenharmony_ci{
26462306a36Sopenharmony_ci	struct intc_desc_int *d;
26562306a36Sopenharmony_ci
26662306a36Sopenharmony_ci	list_for_each_entry(d, &intc_list, list)
26762306a36Sopenharmony_ci		if (radix_tree_tagged(&d->tree, INTC_TAG_VIRQ_NEEDS_ALLOC))
26862306a36Sopenharmony_ci			intc_subgroup_map(d);
26962306a36Sopenharmony_ci}
270