152a65ff5SThomas Gleixner // SPDX-License-Identifier: GPL-2.0+
2b19af510SBartosz Golaszewski /*
3275220caSBartosz Golaszewski * Copyright (C) 2017-2018 Bartosz Golaszewski <brgl@bgdev.pl>
4337cbeb2SBartosz Golaszewski * Copyright (C) 2020 Bartosz Golaszewski <bgolaszewski@baylibre.com>
5b19af510SBartosz Golaszewski */
6b19af510SBartosz Golaszewski
7b19af510SBartosz Golaszewski #include <linux/irq.h>
8337cbeb2SBartosz Golaszewski #include <linux/irq_sim.h>
9337cbeb2SBartosz Golaszewski #include <linux/irq_work.h>
10337cbeb2SBartosz Golaszewski #include <linux/interrupt.h>
11337cbeb2SBartosz Golaszewski #include <linux/slab.h>
12337cbeb2SBartosz Golaszewski
13337cbeb2SBartosz Golaszewski struct irq_sim_work_ctx {
14337cbeb2SBartosz Golaszewski struct irq_work work;
15337cbeb2SBartosz Golaszewski int irq_base;
16337cbeb2SBartosz Golaszewski unsigned int irq_count;
17337cbeb2SBartosz Golaszewski unsigned long *pending;
18337cbeb2SBartosz Golaszewski struct irq_domain *domain;
19337cbeb2SBartosz Golaszewski };
20337cbeb2SBartosz Golaszewski
21337cbeb2SBartosz Golaszewski struct irq_sim_irq_ctx {
22337cbeb2SBartosz Golaszewski int irqnum;
23337cbeb2SBartosz Golaszewski bool enabled;
24337cbeb2SBartosz Golaszewski struct irq_sim_work_ctx *work_ctx;
25337cbeb2SBartosz Golaszewski };
26b19af510SBartosz Golaszewski
irq_sim_irqmask(struct irq_data * data)27b19af510SBartosz Golaszewski static void irq_sim_irqmask(struct irq_data *data)
28b19af510SBartosz Golaszewski {
29b19af510SBartosz Golaszewski struct irq_sim_irq_ctx *irq_ctx = irq_data_get_irq_chip_data(data);
30b19af510SBartosz Golaszewski
31b19af510SBartosz Golaszewski irq_ctx->enabled = false;
32b19af510SBartosz Golaszewski }
33b19af510SBartosz Golaszewski
irq_sim_irqunmask(struct irq_data * data)34b19af510SBartosz Golaszewski static void irq_sim_irqunmask(struct irq_data *data)
35b19af510SBartosz Golaszewski {
36b19af510SBartosz Golaszewski struct irq_sim_irq_ctx *irq_ctx = irq_data_get_irq_chip_data(data);
37b19af510SBartosz Golaszewski
38b19af510SBartosz Golaszewski irq_ctx->enabled = true;
39b19af510SBartosz Golaszewski }
40b19af510SBartosz Golaszewski
irq_sim_set_type(struct irq_data * data,unsigned int type)418d91ecc8SBartosz Golaszewski static int irq_sim_set_type(struct irq_data *data, unsigned int type)
428d91ecc8SBartosz Golaszewski {
438d91ecc8SBartosz Golaszewski /* We only support rising and falling edge trigger types. */
448d91ecc8SBartosz Golaszewski if (type & ~IRQ_TYPE_EDGE_BOTH)
458d91ecc8SBartosz Golaszewski return -EINVAL;
468d91ecc8SBartosz Golaszewski
478d91ecc8SBartosz Golaszewski irqd_set_trigger_type(data, type);
488d91ecc8SBartosz Golaszewski
498d91ecc8SBartosz Golaszewski return 0;
508d91ecc8SBartosz Golaszewski }
518d91ecc8SBartosz Golaszewski
irq_sim_get_irqchip_state(struct irq_data * data,enum irqchip_irq_state which,bool * state)52337cbeb2SBartosz Golaszewski static int irq_sim_get_irqchip_state(struct irq_data *data,
53337cbeb2SBartosz Golaszewski enum irqchip_irq_state which, bool *state)
54337cbeb2SBartosz Golaszewski {
55337cbeb2SBartosz Golaszewski struct irq_sim_irq_ctx *irq_ctx = irq_data_get_irq_chip_data(data);
56337cbeb2SBartosz Golaszewski irq_hw_number_t hwirq = irqd_to_hwirq(data);
57337cbeb2SBartosz Golaszewski
58337cbeb2SBartosz Golaszewski switch (which) {
59337cbeb2SBartosz Golaszewski case IRQCHIP_STATE_PENDING:
60337cbeb2SBartosz Golaszewski if (irq_ctx->enabled)
61337cbeb2SBartosz Golaszewski *state = test_bit(hwirq, irq_ctx->work_ctx->pending);
62337cbeb2SBartosz Golaszewski break;
63337cbeb2SBartosz Golaszewski default:
64337cbeb2SBartosz Golaszewski return -EINVAL;
65337cbeb2SBartosz Golaszewski }
66337cbeb2SBartosz Golaszewski
67337cbeb2SBartosz Golaszewski return 0;
68337cbeb2SBartosz Golaszewski }
69337cbeb2SBartosz Golaszewski
irq_sim_set_irqchip_state(struct irq_data * data,enum irqchip_irq_state which,bool state)70337cbeb2SBartosz Golaszewski static int irq_sim_set_irqchip_state(struct irq_data *data,
71337cbeb2SBartosz Golaszewski enum irqchip_irq_state which, bool state)
72337cbeb2SBartosz Golaszewski {
73337cbeb2SBartosz Golaszewski struct irq_sim_irq_ctx *irq_ctx = irq_data_get_irq_chip_data(data);
74337cbeb2SBartosz Golaszewski irq_hw_number_t hwirq = irqd_to_hwirq(data);
75337cbeb2SBartosz Golaszewski
76337cbeb2SBartosz Golaszewski switch (which) {
77337cbeb2SBartosz Golaszewski case IRQCHIP_STATE_PENDING:
78337cbeb2SBartosz Golaszewski if (irq_ctx->enabled) {
79337cbeb2SBartosz Golaszewski assign_bit(hwirq, irq_ctx->work_ctx->pending, state);
80337cbeb2SBartosz Golaszewski if (state)
81337cbeb2SBartosz Golaszewski irq_work_queue(&irq_ctx->work_ctx->work);
82337cbeb2SBartosz Golaszewski }
83337cbeb2SBartosz Golaszewski break;
84337cbeb2SBartosz Golaszewski default:
85337cbeb2SBartosz Golaszewski return -EINVAL;
86337cbeb2SBartosz Golaszewski }
87337cbeb2SBartosz Golaszewski
88337cbeb2SBartosz Golaszewski return 0;
89337cbeb2SBartosz Golaszewski }
90337cbeb2SBartosz Golaszewski
91b19af510SBartosz Golaszewski static struct irq_chip irq_sim_irqchip = {
92b19af510SBartosz Golaszewski .name = "irq_sim",
93b19af510SBartosz Golaszewski .irq_mask = irq_sim_irqmask,
94b19af510SBartosz Golaszewski .irq_unmask = irq_sim_irqunmask,
958d91ecc8SBartosz Golaszewski .irq_set_type = irq_sim_set_type,
96337cbeb2SBartosz Golaszewski .irq_get_irqchip_state = irq_sim_get_irqchip_state,
97337cbeb2SBartosz Golaszewski .irq_set_irqchip_state = irq_sim_set_irqchip_state,
98b19af510SBartosz Golaszewski };
99b19af510SBartosz Golaszewski
irq_sim_handle_irq(struct irq_work * work)100b19af510SBartosz Golaszewski static void irq_sim_handle_irq(struct irq_work *work)
101b19af510SBartosz Golaszewski {
102b19af510SBartosz Golaszewski struct irq_sim_work_ctx *work_ctx;
10306459901SBartosz Golaszewski unsigned int offset = 0;
10406459901SBartosz Golaszewski int irqnum;
105b19af510SBartosz Golaszewski
106b19af510SBartosz Golaszewski work_ctx = container_of(work, struct irq_sim_work_ctx, work);
10706459901SBartosz Golaszewski
108337cbeb2SBartosz Golaszewski while (!bitmap_empty(work_ctx->pending, work_ctx->irq_count)) {
10906459901SBartosz Golaszewski offset = find_next_bit(work_ctx->pending,
110337cbeb2SBartosz Golaszewski work_ctx->irq_count, offset);
11106459901SBartosz Golaszewski clear_bit(offset, work_ctx->pending);
112337cbeb2SBartosz Golaszewski irqnum = irq_find_mapping(work_ctx->domain, offset);
11306459901SBartosz Golaszewski handle_simple_irq(irq_to_desc(irqnum));
11406459901SBartosz Golaszewski }
115b19af510SBartosz Golaszewski }
116b19af510SBartosz Golaszewski
irq_sim_domain_map(struct irq_domain * domain,unsigned int virq,irq_hw_number_t hw)117337cbeb2SBartosz Golaszewski static int irq_sim_domain_map(struct irq_domain *domain,
118337cbeb2SBartosz Golaszewski unsigned int virq, irq_hw_number_t hw)
119b19af510SBartosz Golaszewski {
120337cbeb2SBartosz Golaszewski struct irq_sim_work_ctx *work_ctx = domain->host_data;
121337cbeb2SBartosz Golaszewski struct irq_sim_irq_ctx *irq_ctx;
122b19af510SBartosz Golaszewski
123337cbeb2SBartosz Golaszewski irq_ctx = kzalloc(sizeof(*irq_ctx), GFP_KERNEL);
124337cbeb2SBartosz Golaszewski if (!irq_ctx)
125b19af510SBartosz Golaszewski return -ENOMEM;
126b19af510SBartosz Golaszewski
127337cbeb2SBartosz Golaszewski irq_set_chip(virq, &irq_sim_irqchip);
128337cbeb2SBartosz Golaszewski irq_set_chip_data(virq, irq_ctx);
129337cbeb2SBartosz Golaszewski irq_set_handler(virq, handle_simple_irq);
130337cbeb2SBartosz Golaszewski irq_modify_status(virq, IRQ_NOREQUEST | IRQ_NOAUTOEN, IRQ_NOPROBE);
131337cbeb2SBartosz Golaszewski irq_ctx->work_ctx = work_ctx;
132337cbeb2SBartosz Golaszewski
133337cbeb2SBartosz Golaszewski return 0;
134b19af510SBartosz Golaszewski }
135b19af510SBartosz Golaszewski
irq_sim_domain_unmap(struct irq_domain * domain,unsigned int virq)136337cbeb2SBartosz Golaszewski static void irq_sim_domain_unmap(struct irq_domain *domain, unsigned int virq)
137337cbeb2SBartosz Golaszewski {
138337cbeb2SBartosz Golaszewski struct irq_sim_irq_ctx *irq_ctx;
139337cbeb2SBartosz Golaszewski struct irq_data *irqd;
140337cbeb2SBartosz Golaszewski
141337cbeb2SBartosz Golaszewski irqd = irq_domain_get_irq_data(domain, virq);
142337cbeb2SBartosz Golaszewski irq_ctx = irq_data_get_irq_chip_data(irqd);
143337cbeb2SBartosz Golaszewski
144337cbeb2SBartosz Golaszewski irq_set_handler(virq, NULL);
145337cbeb2SBartosz Golaszewski irq_domain_reset_irq_data(irqd);
146337cbeb2SBartosz Golaszewski kfree(irq_ctx);
14706459901SBartosz Golaszewski }
14806459901SBartosz Golaszewski
149337cbeb2SBartosz Golaszewski static const struct irq_domain_ops irq_sim_domain_ops = {
150337cbeb2SBartosz Golaszewski .map = irq_sim_domain_map,
151337cbeb2SBartosz Golaszewski .unmap = irq_sim_domain_unmap,
152337cbeb2SBartosz Golaszewski };
153b19af510SBartosz Golaszewski
154b19af510SBartosz Golaszewski /**
155337cbeb2SBartosz Golaszewski * irq_domain_create_sim - Create a new interrupt simulator irq_domain and
156337cbeb2SBartosz Golaszewski * allocate a range of dummy interrupts.
157b19af510SBartosz Golaszewski *
158ef4cb70aSAndy Shevchenko * @fwnode: struct fwnode_handle to be associated with this domain.
159337cbeb2SBartosz Golaszewski * @num_irqs: Number of interrupts to allocate.
160337cbeb2SBartosz Golaszewski *
161337cbeb2SBartosz Golaszewski * On success: return a new irq_domain object.
162337cbeb2SBartosz Golaszewski * On failure: a negative errno wrapped with ERR_PTR().
163b19af510SBartosz Golaszewski */
irq_domain_create_sim(struct fwnode_handle * fwnode,unsigned int num_irqs)164337cbeb2SBartosz Golaszewski struct irq_domain *irq_domain_create_sim(struct fwnode_handle *fwnode,
165337cbeb2SBartosz Golaszewski unsigned int num_irqs)
166b19af510SBartosz Golaszewski {
167337cbeb2SBartosz Golaszewski struct irq_sim_work_ctx *work_ctx;
168b19af510SBartosz Golaszewski
169337cbeb2SBartosz Golaszewski work_ctx = kmalloc(sizeof(*work_ctx), GFP_KERNEL);
170337cbeb2SBartosz Golaszewski if (!work_ctx)
171337cbeb2SBartosz Golaszewski goto err_out;
172337cbeb2SBartosz Golaszewski
173337cbeb2SBartosz Golaszewski work_ctx->pending = bitmap_zalloc(num_irqs, GFP_KERNEL);
174337cbeb2SBartosz Golaszewski if (!work_ctx->pending)
175337cbeb2SBartosz Golaszewski goto err_free_work_ctx;
176337cbeb2SBartosz Golaszewski
177337cbeb2SBartosz Golaszewski work_ctx->domain = irq_domain_create_linear(fwnode, num_irqs,
178337cbeb2SBartosz Golaszewski &irq_sim_domain_ops,
179337cbeb2SBartosz Golaszewski work_ctx);
180337cbeb2SBartosz Golaszewski if (!work_ctx->domain)
181337cbeb2SBartosz Golaszewski goto err_free_bitmap;
182337cbeb2SBartosz Golaszewski
183337cbeb2SBartosz Golaszewski work_ctx->irq_count = num_irqs;
184*21673fcbSSebastian Andrzej Siewior work_ctx->work = IRQ_WORK_INIT_HARD(irq_sim_handle_irq);
185337cbeb2SBartosz Golaszewski
186337cbeb2SBartosz Golaszewski return work_ctx->domain;
187337cbeb2SBartosz Golaszewski
188337cbeb2SBartosz Golaszewski err_free_bitmap:
189337cbeb2SBartosz Golaszewski bitmap_free(work_ctx->pending);
190337cbeb2SBartosz Golaszewski err_free_work_ctx:
191337cbeb2SBartosz Golaszewski kfree(work_ctx);
192337cbeb2SBartosz Golaszewski err_out:
193337cbeb2SBartosz Golaszewski return ERR_PTR(-ENOMEM);
194337cbeb2SBartosz Golaszewski }
195337cbeb2SBartosz Golaszewski EXPORT_SYMBOL_GPL(irq_domain_create_sim);
196337cbeb2SBartosz Golaszewski
197337cbeb2SBartosz Golaszewski /**
198337cbeb2SBartosz Golaszewski * irq_domain_remove_sim - Deinitialize the interrupt simulator domain: free
199337cbeb2SBartosz Golaszewski * the interrupt descriptors and allocated memory.
200337cbeb2SBartosz Golaszewski *
201337cbeb2SBartosz Golaszewski * @domain: The interrupt simulator domain to tear down.
202337cbeb2SBartosz Golaszewski */
irq_domain_remove_sim(struct irq_domain * domain)203337cbeb2SBartosz Golaszewski void irq_domain_remove_sim(struct irq_domain *domain)
204337cbeb2SBartosz Golaszewski {
205337cbeb2SBartosz Golaszewski struct irq_sim_work_ctx *work_ctx = domain->host_data;
206337cbeb2SBartosz Golaszewski
207337cbeb2SBartosz Golaszewski irq_work_sync(&work_ctx->work);
208337cbeb2SBartosz Golaszewski bitmap_free(work_ctx->pending);
209337cbeb2SBartosz Golaszewski kfree(work_ctx);
210337cbeb2SBartosz Golaszewski
211337cbeb2SBartosz Golaszewski irq_domain_remove(domain);
212337cbeb2SBartosz Golaszewski }
213337cbeb2SBartosz Golaszewski EXPORT_SYMBOL_GPL(irq_domain_remove_sim);
214337cbeb2SBartosz Golaszewski
devm_irq_domain_remove_sim(void * data)215883ccef3SBartosz Golaszewski static void devm_irq_domain_remove_sim(void *data)
21644e72c7eSBartosz Golaszewski {
217883ccef3SBartosz Golaszewski struct irq_domain *domain = data;
21844e72c7eSBartosz Golaszewski
219883ccef3SBartosz Golaszewski irq_domain_remove_sim(domain);
22044e72c7eSBartosz Golaszewski }
22144e72c7eSBartosz Golaszewski
22244e72c7eSBartosz Golaszewski /**
223337cbeb2SBartosz Golaszewski * devm_irq_domain_create_sim - Create a new interrupt simulator for
224337cbeb2SBartosz Golaszewski * a managed device.
22544e72c7eSBartosz Golaszewski *
22644e72c7eSBartosz Golaszewski * @dev: Device to initialize the simulator object for.
227ef4cb70aSAndy Shevchenko * @fwnode: struct fwnode_handle to be associated with this domain.
22844e72c7eSBartosz Golaszewski * @num_irqs: Number of interrupts to allocate
22944e72c7eSBartosz Golaszewski *
230337cbeb2SBartosz Golaszewski * On success: return a new irq_domain object.
231337cbeb2SBartosz Golaszewski * On failure: a negative errno wrapped with ERR_PTR().
23244e72c7eSBartosz Golaszewski */
devm_irq_domain_create_sim(struct device * dev,struct fwnode_handle * fwnode,unsigned int num_irqs)233337cbeb2SBartosz Golaszewski struct irq_domain *devm_irq_domain_create_sim(struct device *dev,
234337cbeb2SBartosz Golaszewski struct fwnode_handle *fwnode,
23544e72c7eSBartosz Golaszewski unsigned int num_irqs)
23644e72c7eSBartosz Golaszewski {
237883ccef3SBartosz Golaszewski struct irq_domain *domain;
238883ccef3SBartosz Golaszewski int ret;
23944e72c7eSBartosz Golaszewski
240883ccef3SBartosz Golaszewski domain = irq_domain_create_sim(fwnode, num_irqs);
241883ccef3SBartosz Golaszewski if (IS_ERR(domain))
242883ccef3SBartosz Golaszewski return domain;
24344e72c7eSBartosz Golaszewski
244883ccef3SBartosz Golaszewski ret = devm_add_action_or_reset(dev, devm_irq_domain_remove_sim, domain);
245883ccef3SBartosz Golaszewski if (ret)
246883ccef3SBartosz Golaszewski return ERR_PTR(ret);
24744e72c7eSBartosz Golaszewski
248883ccef3SBartosz Golaszewski return domain;
24944e72c7eSBartosz Golaszewski }
250337cbeb2SBartosz Golaszewski EXPORT_SYMBOL_GPL(devm_irq_domain_create_sim);
251