forked from mirrors/linux
		
	irqchip/imx-irqsteer: Add multi output interrupts support
One irqsteer channel can support up to 8 output interrupts. Cc: Marc Zyngier <marc.zyngier@arm.com> Cc: Lucas Stach <l.stach@pengutronix.de> Cc: Shawn Guo <shawnguo@kernel.org> Reviewed-by: Lucas Stach <l.stach@pengutronix.de> Signed-off-by: Dong Aisheng <aisheng.dong@nxp.com> Signed-off-by: Marc Zyngier <marc.zyngier@arm.com>
This commit is contained in:
		
							parent
							
								
									deb904e45b
								
							
						
					
					
						commit
						28528fca49
					
				
					 1 changed files with 68 additions and 20 deletions
				
			
		|  | @ -10,6 +10,7 @@ | |||
| #include <linux/irqchip/chained_irq.h> | ||||
| #include <linux/irqdomain.h> | ||||
| #include <linux/kernel.h> | ||||
| #include <linux/of_irq.h> | ||||
| #include <linux/of_platform.h> | ||||
| #include <linux/spinlock.h> | ||||
| 
 | ||||
|  | @ -21,10 +22,13 @@ | |||
| #define CHAN_MINTDIS(t)		(CTRL_STRIDE_OFF(t, 3) + 0x4) | ||||
| #define CHAN_MASTRSTAT(t)	(CTRL_STRIDE_OFF(t, 3) + 0x8) | ||||
| 
 | ||||
| #define CHAN_MAX_OUTPUT_INT	0x8 | ||||
| 
 | ||||
| struct irqsteer_data { | ||||
| 	void __iomem		*regs; | ||||
| 	struct clk		*ipg_clk; | ||||
| 	int			irq; | ||||
| 	int			irq[CHAN_MAX_OUTPUT_INT]; | ||||
| 	int			irq_count; | ||||
| 	raw_spinlock_t		lock; | ||||
| 	int			reg_num; | ||||
| 	int			channel; | ||||
|  | @ -87,23 +91,47 @@ static const struct irq_domain_ops imx_irqsteer_domain_ops = { | |||
| 	.xlate		= irq_domain_xlate_onecell, | ||||
| }; | ||||
| 
 | ||||
| static int imx_irqsteer_get_hwirq_base(struct irqsteer_data *data, u32 irq) | ||||
| { | ||||
| 	int i; | ||||
| 
 | ||||
| 	for (i = 0; i < data->irq_count; i++) { | ||||
| 		if (data->irq[i] == irq) | ||||
| 			return i * 64; | ||||
| 	} | ||||
| 
 | ||||
| 	return -EINVAL; | ||||
| } | ||||
| 
 | ||||
| static void imx_irqsteer_irq_handler(struct irq_desc *desc) | ||||
| { | ||||
| 	struct irqsteer_data *data = irq_desc_get_handler_data(desc); | ||||
| 	int i; | ||||
| 	int hwirq; | ||||
| 	int irq, i; | ||||
| 
 | ||||
| 	chained_irq_enter(irq_desc_get_chip(desc), desc); | ||||
| 
 | ||||
| 	for (i = 0; i < data->reg_num * 32; i += 32) { | ||||
| 		int idx = imx_irqsteer_get_reg_index(data, i); | ||||
| 	irq = irq_desc_get_irq(desc); | ||||
| 	hwirq = imx_irqsteer_get_hwirq_base(data, irq); | ||||
| 	if (hwirq < 0) { | ||||
| 		pr_warn("%s: unable to get hwirq base for irq %d\n", | ||||
| 			__func__, irq); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	for (i = 0; i < 2; i++, hwirq += 32) { | ||||
| 		int idx = imx_irqsteer_get_reg_index(data, hwirq); | ||||
| 		unsigned long irqmap; | ||||
| 		int pos, virq; | ||||
| 
 | ||||
| 		if (hwirq >= data->reg_num * 32) | ||||
| 			break; | ||||
| 
 | ||||
| 		irqmap = readl_relaxed(data->regs + | ||||
| 				       CHANSTATUS(idx, data->reg_num)); | ||||
| 
 | ||||
| 		for_each_set_bit(pos, &irqmap, 32) { | ||||
| 			virq = irq_find_mapping(data->domain, pos + i); | ||||
| 			virq = irq_find_mapping(data->domain, pos + hwirq); | ||||
| 			if (virq) | ||||
| 				generic_handle_irq(virq); | ||||
| 		} | ||||
|  | @ -117,7 +145,8 @@ static int imx_irqsteer_probe(struct platform_device *pdev) | |||
| 	struct device_node *np = pdev->dev.of_node; | ||||
| 	struct irqsteer_data *data; | ||||
| 	struct resource *res; | ||||
| 	int ret; | ||||
| 	u32 irqs_num; | ||||
| 	int i, ret; | ||||
| 
 | ||||
| 	data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); | ||||
| 	if (!data) | ||||
|  | @ -130,12 +159,6 @@ static int imx_irqsteer_probe(struct platform_device *pdev) | |||
| 		return PTR_ERR(data->regs); | ||||
| 	} | ||||
| 
 | ||||
| 	data->irq = platform_get_irq(pdev, 0); | ||||
| 	if (data->irq <= 0) { | ||||
| 		dev_err(&pdev->dev, "failed to get irq\n"); | ||||
| 		return -ENODEV; | ||||
| 	} | ||||
| 
 | ||||
| 	data->ipg_clk = devm_clk_get(&pdev->dev, "ipg"); | ||||
| 	if (IS_ERR(data->ipg_clk)) { | ||||
| 		ret = PTR_ERR(data->ipg_clk); | ||||
|  | @ -146,11 +169,15 @@ static int imx_irqsteer_probe(struct platform_device *pdev) | |||
| 
 | ||||
| 	raw_spin_lock_init(&data->lock); | ||||
| 
 | ||||
| 	of_property_read_u32(np, "fsl,num-irqs", &data->reg_num); | ||||
| 	of_property_read_u32(np, "fsl,num-irqs", &irqs_num); | ||||
| 	of_property_read_u32(np, "fsl,channel", &data->channel); | ||||
| 
 | ||||
| 	/* one register bit map represents 32 input interrupts */ | ||||
| 	data->reg_num /= 32; | ||||
| 	/*
 | ||||
| 	 * There is one output irq for each group of 64 inputs. | ||||
| 	 * One register bit map can represent 32 input interrupts. | ||||
| 	 */ | ||||
| 	data->irq_count = DIV_ROUND_UP(irqs_num, 64); | ||||
| 	data->reg_num = irqs_num / 32; | ||||
| 
 | ||||
| 	if (IS_ENABLED(CONFIG_PM_SLEEP)) { | ||||
| 		data->saved_reg = devm_kzalloc(&pdev->dev, | ||||
|  | @ -173,23 +200,44 @@ static int imx_irqsteer_probe(struct platform_device *pdev) | |||
| 					     &imx_irqsteer_domain_ops, data); | ||||
| 	if (!data->domain) { | ||||
| 		dev_err(&pdev->dev, "failed to create IRQ domain\n"); | ||||
| 		clk_disable_unprepare(data->ipg_clk); | ||||
| 		return -ENOMEM; | ||||
| 		ret = -ENOMEM; | ||||
| 		goto out; | ||||
| 	} | ||||
| 
 | ||||
| 	irq_set_chained_handler_and_data(data->irq, imx_irqsteer_irq_handler, | ||||
| 					 data); | ||||
| 	if (!data->irq_count || data->irq_count > CHAN_MAX_OUTPUT_INT) { | ||||
| 		ret = -EINVAL; | ||||
| 		goto out; | ||||
| 	} | ||||
| 
 | ||||
| 	for (i = 0; i < data->irq_count; i++) { | ||||
| 		data->irq[i] = irq_of_parse_and_map(np, i); | ||||
| 		if (!data->irq[i]) { | ||||
| 			ret = -EINVAL; | ||||
| 			goto out; | ||||
| 		} | ||||
| 
 | ||||
| 		irq_set_chained_handler_and_data(data->irq[i], | ||||
| 						 imx_irqsteer_irq_handler, | ||||
| 						 data); | ||||
| 	} | ||||
| 
 | ||||
| 	platform_set_drvdata(pdev, data); | ||||
| 
 | ||||
| 	return 0; | ||||
| out: | ||||
| 	clk_disable_unprepare(data->ipg_clk); | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| static int imx_irqsteer_remove(struct platform_device *pdev) | ||||
| { | ||||
| 	struct irqsteer_data *irqsteer_data = platform_get_drvdata(pdev); | ||||
| 	int i; | ||||
| 
 | ||||
| 	for (i = 0; i < irqsteer_data->irq_count; i++) | ||||
| 		irq_set_chained_handler_and_data(irqsteer_data->irq[i], | ||||
| 						 NULL, NULL); | ||||
| 
 | ||||
| 	irq_set_chained_handler_and_data(irqsteer_data->irq, NULL, NULL); | ||||
| 	irq_domain_remove(irqsteer_data->domain); | ||||
| 
 | ||||
| 	clk_disable_unprepare(irqsteer_data->ipg_clk); | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue
	
	 Aisheng Dong
						Aisheng Dong