xref: /openbmc/linux/drivers/irqchip/irq-mxs.c (revision 41a83e06)
16a8e95b0SShawn Guo /*
26a8e95b0SShawn Guo  * Copyright (C) 2009-2010 Freescale Semiconductor, Inc. All Rights Reserved.
36a8e95b0SShawn Guo  *
46a8e95b0SShawn Guo  * This program is free software; you can redistribute it and/or modify
56a8e95b0SShawn Guo  * it under the terms of the GNU General Public License as published by
66a8e95b0SShawn Guo  * the Free Software Foundation; either version 2 of the License, or
76a8e95b0SShawn Guo  * (at your option) any later version.
86a8e95b0SShawn Guo  *
96a8e95b0SShawn Guo  * This program is distributed in the hope that it will be useful,
106a8e95b0SShawn Guo  * but WITHOUT ANY WARRANTY; without even the implied warranty of
116a8e95b0SShawn Guo  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
126a8e95b0SShawn Guo  * GNU General Public License for more details.
136a8e95b0SShawn Guo  *
146a8e95b0SShawn Guo  * You should have received a copy of the GNU General Public License along
156a8e95b0SShawn Guo  * with this program; if not, write to the Free Software Foundation, Inc.,
166a8e95b0SShawn Guo  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
176a8e95b0SShawn Guo  */
186a8e95b0SShawn Guo 
196a8e95b0SShawn Guo #include <linux/kernel.h>
206a8e95b0SShawn Guo #include <linux/init.h>
216a8e95b0SShawn Guo #include <linux/irq.h>
2241a83e06SJoel Porquet #include <linux/irqchip.h>
236a8e95b0SShawn Guo #include <linux/irqdomain.h>
246a8e95b0SShawn Guo #include <linux/io.h>
256a8e95b0SShawn Guo #include <linux/of.h>
266a8e95b0SShawn Guo #include <linux/of_address.h>
276a8e95b0SShawn Guo #include <linux/of_irq.h>
286a8e95b0SShawn Guo #include <linux/stmp_device.h>
296a8e95b0SShawn Guo #include <asm/exception.h>
306a8e95b0SShawn Guo 
316a8e95b0SShawn Guo #define HW_ICOLL_VECTOR				0x0000
326a8e95b0SShawn Guo #define HW_ICOLL_LEVELACK			0x0010
336a8e95b0SShawn Guo #define HW_ICOLL_CTRL				0x0020
346a8e95b0SShawn Guo #define HW_ICOLL_STAT_OFFSET			0x0070
356a8e95b0SShawn Guo #define HW_ICOLL_INTERRUPTn_SET(n)		(0x0124 + (n) * 0x10)
366a8e95b0SShawn Guo #define HW_ICOLL_INTERRUPTn_CLR(n)		(0x0128 + (n) * 0x10)
376a8e95b0SShawn Guo #define BM_ICOLL_INTERRUPTn_ENABLE		0x00000004
386a8e95b0SShawn Guo #define BV_ICOLL_LEVELACK_IRQLEVELACK__LEVEL0	0x1
396a8e95b0SShawn Guo 
406a8e95b0SShawn Guo #define ICOLL_NUM_IRQS		128
416a8e95b0SShawn Guo 
426a8e95b0SShawn Guo static void __iomem *icoll_base;
436a8e95b0SShawn Guo static struct irq_domain *icoll_domain;
446a8e95b0SShawn Guo 
456a8e95b0SShawn Guo static void icoll_ack_irq(struct irq_data *d)
466a8e95b0SShawn Guo {
476a8e95b0SShawn Guo 	/*
486a8e95b0SShawn Guo 	 * The Interrupt Collector is able to prioritize irqs.
496a8e95b0SShawn Guo 	 * Currently only level 0 is used. So acking can use
506a8e95b0SShawn Guo 	 * BV_ICOLL_LEVELACK_IRQLEVELACK__LEVEL0 unconditionally.
516a8e95b0SShawn Guo 	 */
526a8e95b0SShawn Guo 	__raw_writel(BV_ICOLL_LEVELACK_IRQLEVELACK__LEVEL0,
536a8e95b0SShawn Guo 			icoll_base + HW_ICOLL_LEVELACK);
546a8e95b0SShawn Guo }
556a8e95b0SShawn Guo 
566a8e95b0SShawn Guo static void icoll_mask_irq(struct irq_data *d)
576a8e95b0SShawn Guo {
586a8e95b0SShawn Guo 	__raw_writel(BM_ICOLL_INTERRUPTn_ENABLE,
596a8e95b0SShawn Guo 			icoll_base + HW_ICOLL_INTERRUPTn_CLR(d->hwirq));
606a8e95b0SShawn Guo }
616a8e95b0SShawn Guo 
626a8e95b0SShawn Guo static void icoll_unmask_irq(struct irq_data *d)
636a8e95b0SShawn Guo {
646a8e95b0SShawn Guo 	__raw_writel(BM_ICOLL_INTERRUPTn_ENABLE,
656a8e95b0SShawn Guo 			icoll_base + HW_ICOLL_INTERRUPTn_SET(d->hwirq));
666a8e95b0SShawn Guo }
676a8e95b0SShawn Guo 
686a8e95b0SShawn Guo static struct irq_chip mxs_icoll_chip = {
696a8e95b0SShawn Guo 	.irq_ack = icoll_ack_irq,
706a8e95b0SShawn Guo 	.irq_mask = icoll_mask_irq,
716a8e95b0SShawn Guo 	.irq_unmask = icoll_unmask_irq,
726a8e95b0SShawn Guo };
736a8e95b0SShawn Guo 
746a8e95b0SShawn Guo asmlinkage void __exception_irq_entry icoll_handle_irq(struct pt_regs *regs)
756a8e95b0SShawn Guo {
766a8e95b0SShawn Guo 	u32 irqnr;
776a8e95b0SShawn Guo 
786a8e95b0SShawn Guo 	irqnr = __raw_readl(icoll_base + HW_ICOLL_STAT_OFFSET);
796a8e95b0SShawn Guo 	__raw_writel(irqnr, icoll_base + HW_ICOLL_VECTOR);
80b3410e5fSMarc Zyngier 	handle_domain_irq(icoll_domain, irqnr, regs);
816a8e95b0SShawn Guo }
826a8e95b0SShawn Guo 
836a8e95b0SShawn Guo static int icoll_irq_domain_map(struct irq_domain *d, unsigned int virq,
846a8e95b0SShawn Guo 				irq_hw_number_t hw)
856a8e95b0SShawn Guo {
866a8e95b0SShawn Guo 	irq_set_chip_and_handler(virq, &mxs_icoll_chip, handle_level_irq);
876a8e95b0SShawn Guo 	set_irq_flags(virq, IRQF_VALID);
886a8e95b0SShawn Guo 
896a8e95b0SShawn Guo 	return 0;
906a8e95b0SShawn Guo }
916a8e95b0SShawn Guo 
9296009736SKrzysztof Kozlowski static const struct irq_domain_ops icoll_irq_domain_ops = {
936a8e95b0SShawn Guo 	.map = icoll_irq_domain_map,
946a8e95b0SShawn Guo 	.xlate = irq_domain_xlate_onecell,
956a8e95b0SShawn Guo };
966a8e95b0SShawn Guo 
9710776b5fSRob Herring static int __init icoll_of_init(struct device_node *np,
986a8e95b0SShawn Guo 			  struct device_node *interrupt_parent)
996a8e95b0SShawn Guo {
1006a8e95b0SShawn Guo 	icoll_base = of_iomap(np, 0);
1016a8e95b0SShawn Guo 	WARN_ON(!icoll_base);
1026a8e95b0SShawn Guo 
1036a8e95b0SShawn Guo 	/*
1046a8e95b0SShawn Guo 	 * Interrupt Collector reset, which initializes the priority
1056a8e95b0SShawn Guo 	 * for each irq to level 0.
1066a8e95b0SShawn Guo 	 */
1076a8e95b0SShawn Guo 	stmp_reset_block(icoll_base + HW_ICOLL_CTRL);
1086a8e95b0SShawn Guo 
1096a8e95b0SShawn Guo 	icoll_domain = irq_domain_add_linear(np, ICOLL_NUM_IRQS,
1106a8e95b0SShawn Guo 					     &icoll_irq_domain_ops, NULL);
11110776b5fSRob Herring 	return icoll_domain ? 0 : -ENODEV;
1126a8e95b0SShawn Guo }
1136a8e95b0SShawn Guo IRQCHIP_DECLARE(mxs, "fsl,icoll", icoll_of_init);
114