mirror of
				https://github.com/torvalds/linux.git
				synced 2025-10-31 08:38:45 +02:00 
			
		
		
		
	genirq: Generic chip: Add linear irq domain support
Provide infrastructure for irq chip implementations which work on linear irq domains. - Interface to allocate multiple generic chips which are associated to the irq domain. - Interface to get the generic chip pointer for a particular hardware interrupt in the domain. - irq domain mapping function to install the chip for a particular interrupt. Note: This lacks a removal function for now. [ Sebastian Hesselbarth: Mask cache and pointer math fixups ] Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Cc: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> Cc: Andrew Lunn <andrew@lunn.ch> Cc: Russell King - ARM Linux <linux@arm.linux.org.uk> Cc: Jason Cooper <jason@lakedaemon.net> Cc: Arnd Bergmann <arnd@arndb.de> Cc: Jean-Francois Moine <moinejf@free.fr> Cc: devicetree-discuss@lists.ozlabs.org Cc: Rob Herring <rob.herring@calxeda.com> Cc: Jason Gunthorpe <jgunthorpe@obsidianresearch.com> Cc: Gregory Clement <gregory.clement@free-electrons.com> Cc: Gerlando Falauto <gerlando.falauto@keymile.com> Cc: Rob Landley <rob@landley.net> Acked-by: Grant Likely <grant.likely@linaro.org> Cc: Maxime Ripard <maxime.ripard@free-electrons.com> Cc: Ezequiel Garcia <ezequiel.garcia@free-electrons.com> Cc: linux-arm-kernel@lists.infradead.org Cc: Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com> Link: http://lkml.kernel.org/r/20130506142539.450634298@linutronix.de Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
This commit is contained in:
		
							parent
							
								
									3528d82b68
								
							
						
					
					
						commit
						088f40b7b0
					
				
					 4 changed files with 223 additions and 12 deletions
				
			
		|  | @ -678,6 +678,8 @@ struct irq_chip_type { | |||
|  * @wake_active:	Interrupt is marked as an wakeup from suspend source | ||||
|  * @num_ct:		Number of available irq_chip_type instances (usually 1) | ||||
|  * @private:		Private data for non generic chip callbacks | ||||
|  * @installed:		bitfield to denote installed interrupts | ||||
|  * @domain:		irq domain pointer | ||||
|  * @list:		List head for keeping track of instances | ||||
|  * @chip_types:		Array of interrupt irq_chip_types | ||||
|  * | ||||
|  | @ -699,6 +701,8 @@ struct irq_chip_generic { | |||
| 	u32			wake_active; | ||||
| 	unsigned int		num_ct; | ||||
| 	void			*private; | ||||
| 	unsigned long		installed; | ||||
| 	struct irq_domain	*domain; | ||||
| 	struct list_head	list; | ||||
| 	struct irq_chip_type	chip_types[0]; | ||||
| }; | ||||
|  | @ -719,6 +723,24 @@ enum irq_gc_flags { | |||
| 	IRQ_GC_NO_MASK			= 1 << 3, | ||||
| }; | ||||
| 
 | ||||
| /*
 | ||||
|  * struct irq_domain_chip_generic - Generic irq chip data structure for irq domains | ||||
|  * @irqs_per_chip:	Number of interrupts per chip | ||||
|  * @num_chips:		Number of chips | ||||
|  * @irq_flags_to_set:	IRQ* flags to set on irq setup | ||||
|  * @irq_flags_to_clear:	IRQ* flags to clear on irq setup | ||||
|  * @gc_flags:		Generic chip specific setup flags | ||||
|  * @gc:			Array of pointers to generic interrupt chips | ||||
|  */ | ||||
| struct irq_domain_chip_generic { | ||||
| 	unsigned int		irqs_per_chip; | ||||
| 	unsigned int		num_chips; | ||||
| 	unsigned int		irq_flags_to_clear; | ||||
| 	unsigned int		irq_flags_to_set; | ||||
| 	enum irq_gc_flags	gc_flags; | ||||
| 	struct irq_chip_generic	*gc[0]; | ||||
| }; | ||||
| 
 | ||||
| /* Generic chip callback functions */ | ||||
| void irq_gc_noop(struct irq_data *d); | ||||
| void irq_gc_mask_disable_reg(struct irq_data *d); | ||||
|  | @ -742,6 +764,14 @@ int irq_setup_alt_chip(struct irq_data *d, unsigned int type); | |||
| void irq_remove_generic_chip(struct irq_chip_generic *gc, u32 msk, | ||||
| 			     unsigned int clr, unsigned int set); | ||||
| 
 | ||||
| struct irq_chip_generic *irq_get_domain_generic_chip(struct irq_domain *d, unsigned int hw_irq); | ||||
| int irq_alloc_domain_generic_chips(struct irq_domain *d, int irqs_per_chip, | ||||
| 				   int num_ct, const char *name, | ||||
| 				   irq_flow_handler_t handler, | ||||
| 				   unsigned int clr, unsigned int set, | ||||
| 				   enum irq_gc_flags flags); | ||||
| 
 | ||||
| 
 | ||||
| static inline struct irq_chip_type *irq_data_get_chip_type(struct irq_data *d) | ||||
| { | ||||
| 	return container_of(d->chip, struct irq_chip_type, chip); | ||||
|  |  | |||
|  | @ -66,6 +66,10 @@ struct irq_domain_ops { | |||
| 		     unsigned long *out_hwirq, unsigned int *out_type); | ||||
| }; | ||||
| 
 | ||||
| extern struct irq_domain_ops irq_generic_chip_ops; | ||||
| 
 | ||||
| struct irq_domain_chip_generic; | ||||
| 
 | ||||
| /**
 | ||||
|  * struct irq_domain - Hardware interrupt number translation object | ||||
|  * @link: Element in global irq_domain list. | ||||
|  | @ -109,8 +113,16 @@ struct irq_domain { | |||
| 
 | ||||
| 	/* Optional device node pointer */ | ||||
| 	struct device_node *of_node; | ||||
| 	/* Optional pointer to generic interrupt chips */ | ||||
| 	struct irq_domain_chip_generic *gc; | ||||
| }; | ||||
| 
 | ||||
| #define IRQ_DOMAIN_MAP_LEGACY 0 /* driver allocated fixed range of irqs. | ||||
| 				 * ie. legacy 8259, gets irqs 1..15 */ | ||||
| #define IRQ_DOMAIN_MAP_NOMAP 1 /* no fast reverse mapping */ | ||||
| #define IRQ_DOMAIN_MAP_LINEAR 2 /* linear map of interrupts */ | ||||
| #define IRQ_DOMAIN_MAP_TREE 3 /* radix tree */ | ||||
| 
 | ||||
| #ifdef CONFIG_IRQ_DOMAIN | ||||
| struct irq_domain *irq_domain_add_simple(struct device_node *of_node, | ||||
| 					 unsigned int size, | ||||
|  |  | |||
|  | @ -7,6 +7,7 @@ | |||
| #include <linux/irq.h> | ||||
| #include <linux/slab.h> | ||||
| #include <linux/export.h> | ||||
| #include <linux/irqdomain.h> | ||||
| #include <linux/interrupt.h> | ||||
| #include <linux/kernel_stat.h> | ||||
| #include <linux/syscore_ops.h> | ||||
|  | @ -244,12 +245,156 @@ irq_gc_init_mask_cache(struct irq_chip_generic *gc, enum irq_gc_flags flags) | |||
| 	} | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * irq_alloc_domain_generic_chip - Allocate generic chips for an irq domain | ||||
|  * @d:			irq domain for which to allocate chips | ||||
|  * @irqs_per_chip:	Number of interrupts each chip handles | ||||
|  * @num_ct:		Number of irq_chip_type instances associated with this | ||||
|  * @name:		Name of the irq chip | ||||
|  * @handler:		Default flow handler associated with these chips | ||||
|  * @clr:		IRQ_* bits to clear in the mapping function | ||||
|  * @set:		IRQ_* bits to set in the mapping function | ||||
|  */ | ||||
| int irq_alloc_domain_generic_chips(struct irq_domain *d, int irqs_per_chip, | ||||
| 				   int num_ct, const char *name, | ||||
| 				   irq_flow_handler_t handler, | ||||
| 				   unsigned int clr, unsigned int set, | ||||
| 				   enum irq_gc_flags gcflags) | ||||
| { | ||||
| 	struct irq_domain_chip_generic *dgc; | ||||
| 	struct irq_chip_generic *gc; | ||||
| 	int numchips, sz, i; | ||||
| 	unsigned long flags; | ||||
| 	void *tmp; | ||||
| 
 | ||||
| 	if (d->gc) | ||||
| 		return -EBUSY; | ||||
| 
 | ||||
| 	if (d->revmap_type != IRQ_DOMAIN_MAP_LINEAR) | ||||
| 		return -EINVAL; | ||||
| 
 | ||||
| 	numchips = d->revmap_data.linear.size / irqs_per_chip; | ||||
| 	if (!numchips) | ||||
| 		return -EINVAL; | ||||
| 
 | ||||
| 	/* Allocate a pointer, generic chip and chiptypes for each chip */ | ||||
| 	sz = sizeof(*dgc) + numchips * sizeof(gc); | ||||
| 	sz += numchips * (sizeof(*gc) + num_ct * sizeof(struct irq_chip_type)); | ||||
| 
 | ||||
| 	tmp = dgc = kzalloc(sz, GFP_KERNEL); | ||||
| 	if (!dgc) | ||||
| 		return -ENOMEM; | ||||
| 	dgc->irqs_per_chip = irqs_per_chip; | ||||
| 	dgc->num_chips = numchips; | ||||
| 	dgc->irq_flags_to_set = set; | ||||
| 	dgc->irq_flags_to_clear = clr; | ||||
| 	dgc->gc_flags = gcflags; | ||||
| 	d->gc = dgc; | ||||
| 
 | ||||
| 	/* Calc pointer to the first generic chip */ | ||||
| 	tmp += sizeof(*dgc) + numchips * sizeof(gc); | ||||
| 	for (i = 0; i < numchips; i++) { | ||||
| 		/* Store the pointer to the generic chip */ | ||||
| 		dgc->gc[i] = gc = tmp; | ||||
| 		irq_init_generic_chip(gc, name, num_ct, i * irqs_per_chip, | ||||
| 				      NULL, handler); | ||||
| 		gc->domain = d; | ||||
| 		raw_spin_lock_irqsave(&gc_lock, flags); | ||||
| 		list_add_tail(&gc->list, &gc_list); | ||||
| 		raw_spin_unlock_irqrestore(&gc_lock, flags); | ||||
| 		/* Calc pointer to the next generic chip */ | ||||
| 		tmp += sizeof(*gc) + num_ct * sizeof(struct irq_chip_type); | ||||
| 	} | ||||
| 	return 0; | ||||
| } | ||||
| EXPORT_SYMBOL_GPL(irq_alloc_domain_generic_chips); | ||||
| 
 | ||||
| /**
 | ||||
|  * irq_get_domain_generic_chip - Get a pointer to the generic chip of a hw_irq | ||||
|  * @d:			irq domain pointer | ||||
|  * @hw_irq:		Hardware interrupt number | ||||
|  */ | ||||
| struct irq_chip_generic * | ||||
| irq_get_domain_generic_chip(struct irq_domain *d, unsigned int hw_irq) | ||||
| { | ||||
| 	struct irq_domain_chip_generic *dgc = d->gc; | ||||
| 	int idx; | ||||
| 
 | ||||
| 	if (!dgc) | ||||
| 		return NULL; | ||||
| 	idx = hw_irq / dgc->irqs_per_chip; | ||||
| 	if (idx >= dgc->num_chips) | ||||
| 		return NULL; | ||||
| 	return dgc->gc[idx]; | ||||
| } | ||||
| EXPORT_SYMBOL_GPL(irq_get_domain_generic_chip); | ||||
| 
 | ||||
| /*
 | ||||
|  * Separate lockdep class for interrupt chip which can nest irq_desc | ||||
|  * lock. | ||||
|  */ | ||||
| static struct lock_class_key irq_nested_lock_class; | ||||
| 
 | ||||
| /**
 | ||||
|  * irq_map_generic_chip - Map a generic chip for an irq domain | ||||
|  */ | ||||
| static int irq_map_generic_chip(struct irq_domain *d, unsigned int virq, | ||||
| 				irq_hw_number_t hw_irq) | ||||
| { | ||||
| 	struct irq_data *data = irq_get_irq_data(virq); | ||||
| 	struct irq_domain_chip_generic *dgc = d->gc; | ||||
| 	struct irq_chip_generic *gc; | ||||
| 	struct irq_chip_type *ct; | ||||
| 	struct irq_chip *chip; | ||||
| 	unsigned long flags; | ||||
| 	int idx; | ||||
| 
 | ||||
| 	if (!d->gc) | ||||
| 		return -ENODEV; | ||||
| 
 | ||||
| 	idx = hw_irq / dgc->irqs_per_chip; | ||||
| 	if (idx >= dgc->num_chips) | ||||
| 		return -EINVAL; | ||||
| 	gc = dgc->gc[idx]; | ||||
| 
 | ||||
| 	idx = hw_irq % dgc->irqs_per_chip; | ||||
| 
 | ||||
| 	if (test_bit(idx, &gc->installed)) | ||||
| 		return -EBUSY; | ||||
| 
 | ||||
| 	ct = gc->chip_types; | ||||
| 	chip = &ct->chip; | ||||
| 
 | ||||
| 	/* We only init the cache for the first mapping of a generic chip */ | ||||
| 	if (!gc->installed) { | ||||
| 		raw_spin_lock_irqsave(&gc->lock, flags); | ||||
| 		irq_gc_init_mask_cache(gc, dgc->gc_flags); | ||||
| 		raw_spin_unlock_irqrestore(&gc->lock, flags); | ||||
| 	} | ||||
| 
 | ||||
| 	/* Mark the interrupt as installed */ | ||||
| 	set_bit(idx, &gc->installed); | ||||
| 
 | ||||
| 	if (dgc->gc_flags & IRQ_GC_INIT_NESTED_LOCK) | ||||
| 		irq_set_lockdep_class(virq, &irq_nested_lock_class); | ||||
| 
 | ||||
| 	if (chip->irq_calc_mask) | ||||
| 		chip->irq_calc_mask(data); | ||||
| 	else | ||||
| 		data->mask = 1 << idx; | ||||
| 
 | ||||
| 	irq_set_chip_and_handler(virq, chip, ct->handler); | ||||
| 	irq_set_chip_data(virq, gc); | ||||
| 	irq_modify_status(virq, dgc->irq_flags_to_clear, dgc->irq_flags_to_set); | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| struct irq_domain_ops irq_generic_chip_ops = { | ||||
| 	.map	= irq_map_generic_chip, | ||||
| 	.xlate	= irq_domain_xlate_onetwocell, | ||||
| }; | ||||
| EXPORT_SYMBOL_GPL(irq_generic_chip_ops); | ||||
| 
 | ||||
| /**
 | ||||
|  * irq_setup_generic_chip - Setup a range of interrupts with a generic chip | ||||
|  * @gc:		Generic irq chip holding all data | ||||
|  | @ -354,6 +499,24 @@ void irq_remove_generic_chip(struct irq_chip_generic *gc, u32 msk, | |||
| } | ||||
| EXPORT_SYMBOL_GPL(irq_remove_generic_chip); | ||||
| 
 | ||||
| static struct irq_data *irq_gc_get_irq_data(struct irq_chip_generic *gc) | ||||
| { | ||||
| 	unsigned int virq; | ||||
| 
 | ||||
| 	if (!gc->domain) | ||||
| 		return irq_get_irq_data(gc->irq_base); | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * We don't know which of the irqs has been actually | ||||
| 	 * installed. Use the first one. | ||||
| 	 */ | ||||
| 	if (!gc->installed) | ||||
| 		return NULL; | ||||
| 
 | ||||
| 	virq = irq_find_mapping(gc->domain, gc->irq_base + __ffs(gc->installed)); | ||||
| 	return virq ? irq_get_irq_data(virq) : NULL; | ||||
| } | ||||
| 
 | ||||
| #ifdef CONFIG_PM | ||||
| static int irq_gc_suspend(void) | ||||
| { | ||||
|  | @ -362,8 +525,12 @@ static int irq_gc_suspend(void) | |||
| 	list_for_each_entry(gc, &gc_list, list) { | ||||
| 		struct irq_chip_type *ct = gc->chip_types; | ||||
| 
 | ||||
| 		if (ct->chip.irq_suspend) | ||||
| 			ct->chip.irq_suspend(irq_get_irq_data(gc->irq_base)); | ||||
| 		if (ct->chip.irq_suspend) { | ||||
| 			struct irq_data *data = irq_gc_get_irq_data(gc); | ||||
| 
 | ||||
| 			if (data) | ||||
| 				ct->chip.irq_suspend(data); | ||||
| 		} | ||||
| 	} | ||||
| 	return 0; | ||||
| } | ||||
|  | @ -375,8 +542,12 @@ static void irq_gc_resume(void) | |||
| 	list_for_each_entry(gc, &gc_list, list) { | ||||
| 		struct irq_chip_type *ct = gc->chip_types; | ||||
| 
 | ||||
| 		if (ct->chip.irq_resume) | ||||
| 			ct->chip.irq_resume(irq_get_irq_data(gc->irq_base)); | ||||
| 		if (ct->chip.irq_resume) { | ||||
| 			struct irq_data *data = irq_gc_get_irq_data(gc); | ||||
| 
 | ||||
| 			if (data) | ||||
| 				ct->chip.irq_resume(data); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| #else | ||||
|  | @ -391,8 +562,12 @@ static void irq_gc_shutdown(void) | |||
| 	list_for_each_entry(gc, &gc_list, list) { | ||||
| 		struct irq_chip_type *ct = gc->chip_types; | ||||
| 
 | ||||
| 		if (ct->chip.irq_pm_shutdown) | ||||
| 			ct->chip.irq_pm_shutdown(irq_get_irq_data(gc->irq_base)); | ||||
| 		if (ct->chip.irq_pm_shutdown) { | ||||
| 			struct irq_data *data = irq_gc_get_irq_data(gc); | ||||
| 
 | ||||
| 			if (data) | ||||
| 				ct->chip.irq_pm_shutdown(data); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -16,12 +16,6 @@ | |||
| #include <linux/smp.h> | ||||
| #include <linux/fs.h> | ||||
| 
 | ||||
| #define IRQ_DOMAIN_MAP_LEGACY 0 /* driver allocated fixed range of irqs. | ||||
| 				 * ie. legacy 8259, gets irqs 1..15 */ | ||||
| #define IRQ_DOMAIN_MAP_NOMAP 1 /* no fast reverse mapping */ | ||||
| #define IRQ_DOMAIN_MAP_LINEAR 2 /* linear map of interrupts */ | ||||
| #define IRQ_DOMAIN_MAP_TREE 3 /* radix tree */ | ||||
| 
 | ||||
| static LIST_HEAD(irq_domain_list); | ||||
| static DEFINE_MUTEX(irq_domain_mutex); | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue
	
	 Thomas Gleixner
						Thomas Gleixner