1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Copyright (C) 2017-2018 Bartosz Golaszewski <brgl@bgdev.pl> |
4 | * Copyright (C) 2020 Bartosz Golaszewski <bgolaszewski@baylibre.com> |
5 | */ |
6 | |
7 | #include <linux/cleanup.h> |
8 | #include <linux/interrupt.h> |
9 | #include <linux/irq.h> |
10 | #include <linux/irq_sim.h> |
11 | #include <linux/irq_work.h> |
12 | #include <linux/slab.h> |
13 | |
14 | struct irq_sim_work_ctx { |
15 | struct irq_work work; |
16 | int irq_base; |
17 | unsigned int irq_count; |
18 | unsigned long *pending; |
19 | struct irq_domain *domain; |
20 | }; |
21 | |
22 | struct irq_sim_irq_ctx { |
23 | bool enabled; |
24 | struct irq_sim_work_ctx *work_ctx; |
25 | }; |
26 | |
27 | static void irq_sim_irqmask(struct irq_data *data) |
28 | { |
29 | struct irq_sim_irq_ctx *irq_ctx = irq_data_get_irq_chip_data(d: data); |
30 | |
31 | irq_ctx->enabled = false; |
32 | } |
33 | |
34 | static void irq_sim_irqunmask(struct irq_data *data) |
35 | { |
36 | struct irq_sim_irq_ctx *irq_ctx = irq_data_get_irq_chip_data(d: data); |
37 | |
38 | irq_ctx->enabled = true; |
39 | } |
40 | |
41 | static int irq_sim_set_type(struct irq_data *data, unsigned int type) |
42 | { |
43 | /* We only support rising and falling edge trigger types. */ |
44 | if (type & ~IRQ_TYPE_EDGE_BOTH) |
45 | return -EINVAL; |
46 | |
47 | irqd_set_trigger_type(d: data, type); |
48 | |
49 | return 0; |
50 | } |
51 | |
52 | static int irq_sim_get_irqchip_state(struct irq_data *data, |
53 | enum irqchip_irq_state which, bool *state) |
54 | { |
55 | struct irq_sim_irq_ctx *irq_ctx = irq_data_get_irq_chip_data(d: data); |
56 | irq_hw_number_t hwirq = irqd_to_hwirq(d: data); |
57 | |
58 | switch (which) { |
59 | case IRQCHIP_STATE_PENDING: |
60 | if (irq_ctx->enabled) |
61 | *state = test_bit(hwirq, irq_ctx->work_ctx->pending); |
62 | break; |
63 | default: |
64 | return -EINVAL; |
65 | } |
66 | |
67 | return 0; |
68 | } |
69 | |
70 | static int irq_sim_set_irqchip_state(struct irq_data *data, |
71 | enum irqchip_irq_state which, bool state) |
72 | { |
73 | struct irq_sim_irq_ctx *irq_ctx = irq_data_get_irq_chip_data(d: data); |
74 | irq_hw_number_t hwirq = irqd_to_hwirq(d: data); |
75 | |
76 | switch (which) { |
77 | case IRQCHIP_STATE_PENDING: |
78 | if (irq_ctx->enabled) { |
79 | assign_bit(nr: hwirq, addr: irq_ctx->work_ctx->pending, value: state); |
80 | if (state) |
81 | irq_work_queue(work: &irq_ctx->work_ctx->work); |
82 | } |
83 | break; |
84 | default: |
85 | return -EINVAL; |
86 | } |
87 | |
88 | return 0; |
89 | } |
90 | |
91 | static struct irq_chip irq_sim_irqchip = { |
92 | .name = "irq_sim" , |
93 | .irq_mask = irq_sim_irqmask, |
94 | .irq_unmask = irq_sim_irqunmask, |
95 | .irq_set_type = irq_sim_set_type, |
96 | .irq_get_irqchip_state = irq_sim_get_irqchip_state, |
97 | .irq_set_irqchip_state = irq_sim_set_irqchip_state, |
98 | }; |
99 | |
100 | static void irq_sim_handle_irq(struct irq_work *work) |
101 | { |
102 | struct irq_sim_work_ctx *work_ctx; |
103 | unsigned int offset = 0; |
104 | int irqnum; |
105 | |
106 | work_ctx = container_of(work, struct irq_sim_work_ctx, work); |
107 | |
108 | while (!bitmap_empty(src: work_ctx->pending, nbits: work_ctx->irq_count)) { |
109 | offset = find_next_bit(addr: work_ctx->pending, |
110 | size: work_ctx->irq_count, offset); |
111 | clear_bit(nr: offset, addr: work_ctx->pending); |
112 | irqnum = irq_find_mapping(domain: work_ctx->domain, hwirq: offset); |
113 | handle_simple_irq(desc: irq_to_desc(irq: irqnum)); |
114 | } |
115 | } |
116 | |
117 | static int irq_sim_domain_map(struct irq_domain *domain, |
118 | unsigned int virq, irq_hw_number_t hw) |
119 | { |
120 | struct irq_sim_work_ctx *work_ctx = domain->host_data; |
121 | struct irq_sim_irq_ctx *irq_ctx; |
122 | |
123 | irq_ctx = kzalloc(size: sizeof(*irq_ctx), GFP_KERNEL); |
124 | if (!irq_ctx) |
125 | return -ENOMEM; |
126 | |
127 | irq_set_chip(irq: virq, chip: &irq_sim_irqchip); |
128 | irq_set_chip_data(irq: virq, data: irq_ctx); |
129 | irq_set_handler(irq: virq, handle: handle_simple_irq); |
130 | irq_modify_status(irq: virq, clr: IRQ_NOREQUEST | IRQ_NOAUTOEN, set: IRQ_NOPROBE); |
131 | irq_ctx->work_ctx = work_ctx; |
132 | |
133 | return 0; |
134 | } |
135 | |
136 | static void irq_sim_domain_unmap(struct irq_domain *domain, unsigned int virq) |
137 | { |
138 | struct irq_sim_irq_ctx *irq_ctx; |
139 | struct irq_data *irqd; |
140 | |
141 | irqd = irq_domain_get_irq_data(domain, virq); |
142 | irq_ctx = irq_data_get_irq_chip_data(d: irqd); |
143 | |
144 | irq_set_handler(irq: virq, NULL); |
145 | irq_domain_reset_irq_data(irq_data: irqd); |
146 | kfree(objp: irq_ctx); |
147 | } |
148 | |
149 | static const struct irq_domain_ops irq_sim_domain_ops = { |
150 | .map = irq_sim_domain_map, |
151 | .unmap = irq_sim_domain_unmap, |
152 | }; |
153 | |
154 | /** |
155 | * irq_domain_create_sim - Create a new interrupt simulator irq_domain and |
156 | * allocate a range of dummy interrupts. |
157 | * |
158 | * @fwnode: struct fwnode_handle to be associated with this domain. |
159 | * @num_irqs: Number of interrupts to allocate. |
160 | * |
161 | * On success: return a new irq_domain object. |
162 | * On failure: a negative errno wrapped with ERR_PTR(). |
163 | */ |
164 | struct irq_domain *irq_domain_create_sim(struct fwnode_handle *fwnode, |
165 | unsigned int num_irqs) |
166 | { |
167 | struct irq_sim_work_ctx *work_ctx __free(kfree) = |
168 | kmalloc(size: sizeof(*work_ctx), GFP_KERNEL); |
169 | |
170 | if (!work_ctx) |
171 | return ERR_PTR(error: -ENOMEM); |
172 | |
173 | unsigned long *pending __free(bitmap) = bitmap_zalloc(nbits: num_irqs, GFP_KERNEL); |
174 | if (!pending) |
175 | return ERR_PTR(error: -ENOMEM); |
176 | |
177 | work_ctx->domain = irq_domain_create_linear(fwnode, size: num_irqs, |
178 | ops: &irq_sim_domain_ops, |
179 | host_data: work_ctx); |
180 | if (!work_ctx->domain) |
181 | return ERR_PTR(error: -ENOMEM); |
182 | |
183 | work_ctx->irq_count = num_irqs; |
184 | work_ctx->work = IRQ_WORK_INIT_HARD(irq_sim_handle_irq); |
185 | work_ctx->pending = no_free_ptr(pending); |
186 | |
187 | return no_free_ptr(work_ctx)->domain; |
188 | } |
189 | EXPORT_SYMBOL_GPL(irq_domain_create_sim); |
190 | |
191 | /** |
192 | * irq_domain_remove_sim - Deinitialize the interrupt simulator domain: free |
193 | * the interrupt descriptors and allocated memory. |
194 | * |
195 | * @domain: The interrupt simulator domain to tear down. |
196 | */ |
197 | void irq_domain_remove_sim(struct irq_domain *domain) |
198 | { |
199 | struct irq_sim_work_ctx *work_ctx = domain->host_data; |
200 | |
201 | irq_work_sync(work: &work_ctx->work); |
202 | bitmap_free(bitmap: work_ctx->pending); |
203 | kfree(objp: work_ctx); |
204 | |
205 | irq_domain_remove(host: domain); |
206 | } |
207 | EXPORT_SYMBOL_GPL(irq_domain_remove_sim); |
208 | |
209 | static void devm_irq_domain_remove_sim(void *data) |
210 | { |
211 | struct irq_domain *domain = data; |
212 | |
213 | irq_domain_remove_sim(domain); |
214 | } |
215 | |
216 | /** |
217 | * devm_irq_domain_create_sim - Create a new interrupt simulator for |
218 | * a managed device. |
219 | * |
220 | * @dev: Device to initialize the simulator object for. |
221 | * @fwnode: struct fwnode_handle to be associated with this domain. |
222 | * @num_irqs: Number of interrupts to allocate |
223 | * |
224 | * On success: return a new irq_domain object. |
225 | * On failure: a negative errno wrapped with ERR_PTR(). |
226 | */ |
227 | struct irq_domain *devm_irq_domain_create_sim(struct device *dev, |
228 | struct fwnode_handle *fwnode, |
229 | unsigned int num_irqs) |
230 | { |
231 | struct irq_domain *domain; |
232 | int ret; |
233 | |
234 | domain = irq_domain_create_sim(fwnode, num_irqs); |
235 | if (IS_ERR(ptr: domain)) |
236 | return domain; |
237 | |
238 | ret = devm_add_action_or_reset(dev, devm_irq_domain_remove_sim, domain); |
239 | if (ret) |
240 | return ERR_PTR(error: ret); |
241 | |
242 | return domain; |
243 | } |
244 | EXPORT_SYMBOL_GPL(devm_irq_domain_create_sim); |
245 | |