xref: /openbmc/linux/kernel/irq/irq_sim.c (revision 21673fcb)
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