summaryrefslogtreecommitdiff
path: root/kernel/irq/irqdomain.c
blob: 7e251cd810b749cb09d13ef475ea85cdf1d94850 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
#include <linux/irq.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/slab.h>

static LIST_HEAD(irq_domain_list);
static DEFINE_MUTEX(irq_domain_mutex);

/**
 * irq_domain_add() - Register an irq_domain
 * @domain: ptr to initialized irq_domain structure
 *
 * Registers an irq_domain structure.  The irq_domain must at a minimum be
 * initialized with an ops structure pointer, and either a ->to_irq hook or
 * a valid irq_base value.  Everything else is optional.
 */
void irq_domain_add(struct irq_domain *domain)
{
	mutex_lock(&irq_domain_mutex);
	list_add(&domain->list, &irq_domain_list);
	mutex_unlock(&irq_domain_mutex);
}

/**
 * irq_domain_del() - Unregister an irq_domain
 * @domain: ptr to registered irq_domain.
 */
void irq_domain_del(struct irq_domain *domain)
{
	mutex_lock(&irq_domain_mutex);
	list_del(&domain->list);
	mutex_unlock(&irq_domain_mutex);
}

/**
 * irq_domain_map() - Allocate and/or increment a reference to a hwirq
 *
 * TODO: Establish a linux irq number mapping for a hardware irq.  If the
 * mapping already exists, then increment the reference count and return the
 * linux irq number.
 *
 * At the moment this function is an empty stub since irq_domain initially
 * only supports the common case of mapping hw irq numbers into a contiguous
 * range of pre-allocated linux irq_descs based at irq_domain->irq_base.  When
 * irq_domains are extended either to support non-contiguous mappings (ie. to
 * support MSI interrupts) or to remove preallocation of all irq_descs (as
 * powerpc does so that irq_descs are only allocated for in-use irq inputs),
 * then this function will be extended to implement the irq_desc allocation
 * and reference counting.
 *
 * Any caller to this function must arrange to also call irq_domain_unmap()
 * if the irq ever becomes unused again.
 */
unsigned int irq_domain_map(struct irq_domain *domain, irq_hw_number_t hwirq)
{
	int irq = irq_domain_to_irq(domain, hwirq);
	struct irq_data *d = irq_get_irq_data(irq);

	d->domain = domain;
	d->hwirq = hwirq;

	return irq;
}

/**
 * irq_domain_unmap() - Release a reference to a hwirq
 *
 * TODO: decrement the reference count on a hardware irq number.  If the ref
 * count reaches zero, then the irq_desc can be freed.
 *
 * At the moment this function is an empty stub.  See the comment on
 * irq_domain_map() for details.
 */
void irq_domain_unmap(struct irq_domain *domain, irq_hw_number_t hwirq)
{
	int irq = irq_domain_to_irq(domain, hwirq);
	struct irq_data *d = irq_get_irq_data(irq);

	d->domain = NULL;
}

#if defined(CONFIG_OF_IRQ) && !defined(CONFIG_PPC)

/*
 * A handful of architectures use NO_IRQ, which must be used to avoid breaking
 * things when using the device tree.  This is intended to be temporary until
 * the remaining NO_IRQ users can be removed
 */
#ifndef NO_IRQ
#define NO_IRQ 0
#endif

/**
 * irq_create_of_mapping() - Map a linux irq number from a DT interrupt spec
 *
 * Used by the device tree interrupt mapping code to translate a device tree
 * interrupt specifier to a valid linux irq number.  Returns either a valid
 * linux IRQ number or 0.
 *
 * When the caller no longer need the irq number returned by this function it
 * should arrange to call irq_dispose_mapping().
 */
unsigned int irq_create_of_mapping(struct device_node *controller,
				   const u32 *intspec, unsigned int intsize)
{
	struct irq_domain *domain;
	irq_hw_number_t hwirq;
	unsigned int irq, type;
	int rc = -ENODEV;

	/* Find a domain which can translate the irq spec */
	mutex_lock(&irq_domain_mutex);
	list_for_each_entry(domain, &irq_domain_list, list) {
		if (!domain->ops->dt_translate)
			continue;
		rc = domain->ops->dt_translate(domain, controller,
					intspec, intsize, &hwirq, &type);
		if (rc == 0)
			break;
	}
	mutex_unlock(&irq_domain_mutex);

#if defined(CONFIG_MIPS) || defined(CONFIG_MICROBLAZE)
	/*
	 * Temporary: preserves current behaviour until mips/microblaze
	 * register an irq_domain
	 */
	if (rc != 0) {
		pr_debug("%s: fallback mapping for irq=%i\n",
			 controller->full_name, irq);
		return intspec[0];
	}
#else
	if (rc != 0)
		return NO_IRQ;
#endif

	irq = irq_domain_map(domain, hwirq);
	if (type != IRQ_TYPE_NONE)
		irq_set_irq_type(irq, type);
	pr_debug("%s: mapped hwirq=%i to irq=%i, flags=%x\n",
		 controller->full_name, (int)hwirq, irq, type);
	return irq;
}
EXPORT_SYMBOL_GPL(irq_create_of_mapping);

/**
 * irq_dispose_mapping() - Discard a mapping created by irq_create_of_mapping()
 * @irq: linux irq number to be discarded
 *
 * Calling this function indicates the caller no longer needs a reference to
 * the linux irq number returned by a prior call to irq_create_of_mapping().
 */
void irq_dispose_mapping(unsigned int irq)
{
	struct irq_data *d = irq_get_irq_data(irq);
	irq_domain_unmap(d->domain, d->hwirq);
}
EXPORT_SYMBOL_GPL(irq_dispose_mapping);

int irq_domain_simple_dt_translate(struct irq_domain *d,
			    struct device_node *controller,
			    const u32 *intspec, unsigned int intsize,
			    irq_hw_number_t *out_hwirq, unsigned int *out_type)
{
	if (d->of_node != controller)
		return -EINVAL;
	if (intsize != 1)
		return -EINVAL;

	*out_hwirq = intspec[0];
	*out_type = IRQ_TYPE_NONE;
	return 0;
}

struct irq_domain_ops irq_domain_simple_ops = {
	.dt_translate = irq_domain_simple_dt_translate,
};

/**
 * irq_domain_create_simple() - Set up a 'simple' translation range
 */
void irq_domain_add_simple(struct device_node *controller, int irq_base)
{
	struct irq_domain *domain;

	domain = kzalloc(sizeof(*domain), GFP_KERNEL);
	if (!domain) {
		WARN_ON(1);
		return;
	}

	domain->irq_base = irq_base;
	domain->of_node = of_node_get(controller);
	domain->ops = &irq_domain_simple_ops;
	irq_domain_add(domain);
}
EXPORT_SYMBOL_GPL(irq_domain_add_simple);

void irq_domain_generate_simple(const struct of_device_id *match,
				u64 phys_base, unsigned int irq_start)
{
	struct device_node *node;
	pr_info("looking for phys_base=%llx, irq_start=%i\n",
		(unsigned long long) phys_base, (int) irq_start);
	node = of_find_matching_node_by_address(NULL, match, phys_base);
	if (node)
		irq_domain_add_simple(node, irq_start);
	else
		pr_info("no node found\n");
}
EXPORT_SYMBOL_GPL(irq_domain_generate_simple);
#endif