xref: /openbmc/linux/drivers/irqchip/irq-mxs.c (revision 88e20c74)
16a8e95b0SShawn Guo /*
26a8e95b0SShawn Guo  * Copyright (C) 2009-2010 Freescale Semiconductor, Inc. All Rights Reserved.
37e4ac676SOleksij Rempel  * Copyright (C) 2014 Oleksij Rempel <linux@rempel-privat.de>
47e4ac676SOleksij Rempel  *	Add Alphascale ASM9260 support.
56a8e95b0SShawn Guo  *
66a8e95b0SShawn Guo  * This program is free software; you can redistribute it and/or modify
76a8e95b0SShawn Guo  * it under the terms of the GNU General Public License as published by
86a8e95b0SShawn Guo  * the Free Software Foundation; either version 2 of the License, or
96a8e95b0SShawn Guo  * (at your option) any later version.
106a8e95b0SShawn Guo  *
116a8e95b0SShawn Guo  * This program is distributed in the hope that it will be useful,
126a8e95b0SShawn Guo  * but WITHOUT ANY WARRANTY; without even the implied warranty of
136a8e95b0SShawn Guo  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
146a8e95b0SShawn Guo  * GNU General Public License for more details.
156a8e95b0SShawn Guo  *
166a8e95b0SShawn Guo  * You should have received a copy of the GNU General Public License along
176a8e95b0SShawn Guo  * with this program; if not, write to the Free Software Foundation, Inc.,
186a8e95b0SShawn Guo  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
196a8e95b0SShawn Guo  */
206a8e95b0SShawn Guo 
216a8e95b0SShawn Guo #include <linux/kernel.h>
226a8e95b0SShawn Guo #include <linux/init.h>
236a8e95b0SShawn Guo #include <linux/irq.h>
2441a83e06SJoel Porquet #include <linux/irqchip.h>
256a8e95b0SShawn Guo #include <linux/irqdomain.h>
266a8e95b0SShawn Guo #include <linux/io.h>
276a8e95b0SShawn Guo #include <linux/of.h>
286a8e95b0SShawn Guo #include <linux/of_address.h>
296a8e95b0SShawn Guo #include <linux/of_irq.h>
306a8e95b0SShawn Guo #include <linux/stmp_device.h>
316a8e95b0SShawn Guo #include <asm/exception.h>
326a8e95b0SShawn Guo 
337e4ac676SOleksij Rempel #include "alphascale_asm9260-icoll.h"
347e4ac676SOleksij Rempel 
3525e34b44SOleksij Rempel /*
3625e34b44SOleksij Rempel  * this device provide 4 offsets for each register:
3725e34b44SOleksij Rempel  * 0x0 - plain read write mode
3825e34b44SOleksij Rempel  * 0x4 - set mode, OR logic.
3925e34b44SOleksij Rempel  * 0x8 - clr mode, XOR logic.
4025e34b44SOleksij Rempel  * 0xc - togle mode.
4125e34b44SOleksij Rempel  */
4225e34b44SOleksij Rempel #define SET_REG 4
4325e34b44SOleksij Rempel #define CLR_REG 8
4425e34b44SOleksij Rempel 
456a8e95b0SShawn Guo #define HW_ICOLL_VECTOR				0x0000
466a8e95b0SShawn Guo #define HW_ICOLL_LEVELACK			0x0010
476a8e95b0SShawn Guo #define HW_ICOLL_CTRL				0x0020
486a8e95b0SShawn Guo #define HW_ICOLL_STAT_OFFSET			0x0070
4925e34b44SOleksij Rempel #define HW_ICOLL_INTERRUPT0			0x0120
5025e34b44SOleksij Rempel #define HW_ICOLL_INTERRUPTn(n)			((n) * 0x10)
5125e34b44SOleksij Rempel #define BM_ICOLL_INTR_ENABLE			BIT(2)
526a8e95b0SShawn Guo #define BV_ICOLL_LEVELACK_IRQLEVELACK__LEVEL0	0x1
536a8e95b0SShawn Guo 
546a8e95b0SShawn Guo #define ICOLL_NUM_IRQS		128
556a8e95b0SShawn Guo 
567e4ac676SOleksij Rempel enum icoll_type {
577e4ac676SOleksij Rempel 	ICOLL,
587e4ac676SOleksij Rempel 	ASM9260_ICOLL,
597e4ac676SOleksij Rempel };
607e4ac676SOleksij Rempel 
6125e34b44SOleksij Rempel struct icoll_priv {
6225e34b44SOleksij Rempel 	void __iomem *vector;
6325e34b44SOleksij Rempel 	void __iomem *levelack;
6425e34b44SOleksij Rempel 	void __iomem *ctrl;
6525e34b44SOleksij Rempel 	void __iomem *stat;
6625e34b44SOleksij Rempel 	void __iomem *intr;
677e4ac676SOleksij Rempel 	void __iomem *clear;
687e4ac676SOleksij Rempel 	enum icoll_type type;
6925e34b44SOleksij Rempel };
7025e34b44SOleksij Rempel 
7125e34b44SOleksij Rempel static struct icoll_priv icoll_priv;
726a8e95b0SShawn Guo static struct irq_domain *icoll_domain;
736a8e95b0SShawn Guo 
747e4ac676SOleksij Rempel /* calculate bit offset depending on number of intterupt per register */
757e4ac676SOleksij Rempel static u32 icoll_intr_bitshift(struct irq_data *d, u32 bit)
767e4ac676SOleksij Rempel {
777e4ac676SOleksij Rempel 	/*
787e4ac676SOleksij Rempel 	 * mask lower part of hwirq to convert it
797e4ac676SOleksij Rempel 	 * in 0, 1, 2 or 3 and then multiply it by 8 (or shift by 3)
807e4ac676SOleksij Rempel 	 */
817e4ac676SOleksij Rempel 	return bit << ((d->hwirq & 3) << 3);
827e4ac676SOleksij Rempel }
837e4ac676SOleksij Rempel 
847e4ac676SOleksij Rempel /* calculate mem offset depending on number of intterupt per register */
857e4ac676SOleksij Rempel static void __iomem *icoll_intr_reg(struct irq_data *d)
867e4ac676SOleksij Rempel {
877e4ac676SOleksij Rempel 	/* offset = hwirq / intr_per_reg * 0x10 */
887e4ac676SOleksij Rempel 	return icoll_priv.intr + ((d->hwirq >> 2) * 0x10);
897e4ac676SOleksij Rempel }
907e4ac676SOleksij Rempel 
916a8e95b0SShawn Guo static void icoll_ack_irq(struct irq_data *d)
926a8e95b0SShawn Guo {
936a8e95b0SShawn Guo 	/*
946a8e95b0SShawn Guo 	 * The Interrupt Collector is able to prioritize irqs.
956a8e95b0SShawn Guo 	 * Currently only level 0 is used. So acking can use
966a8e95b0SShawn Guo 	 * BV_ICOLL_LEVELACK_IRQLEVELACK__LEVEL0 unconditionally.
976a8e95b0SShawn Guo 	 */
986a8e95b0SShawn Guo 	__raw_writel(BV_ICOLL_LEVELACK_IRQLEVELACK__LEVEL0,
9925e34b44SOleksij Rempel 			icoll_priv.levelack);
1006a8e95b0SShawn Guo }
1016a8e95b0SShawn Guo 
1026a8e95b0SShawn Guo static void icoll_mask_irq(struct irq_data *d)
1036a8e95b0SShawn Guo {
10425e34b44SOleksij Rempel 	__raw_writel(BM_ICOLL_INTR_ENABLE,
10525e34b44SOleksij Rempel 			icoll_priv.intr + CLR_REG + HW_ICOLL_INTERRUPTn(d->hwirq));
1066a8e95b0SShawn Guo }
1076a8e95b0SShawn Guo 
1086a8e95b0SShawn Guo static void icoll_unmask_irq(struct irq_data *d)
1096a8e95b0SShawn Guo {
11025e34b44SOleksij Rempel 	__raw_writel(BM_ICOLL_INTR_ENABLE,
11125e34b44SOleksij Rempel 			icoll_priv.intr + SET_REG + HW_ICOLL_INTERRUPTn(d->hwirq));
1126a8e95b0SShawn Guo }
1136a8e95b0SShawn Guo 
1147e4ac676SOleksij Rempel static void asm9260_mask_irq(struct irq_data *d)
1157e4ac676SOleksij Rempel {
1167e4ac676SOleksij Rempel 	__raw_writel(icoll_intr_bitshift(d, BM_ICOLL_INTR_ENABLE),
1177e4ac676SOleksij Rempel 			icoll_intr_reg(d) + CLR_REG);
1187e4ac676SOleksij Rempel }
1197e4ac676SOleksij Rempel 
1207e4ac676SOleksij Rempel static void asm9260_unmask_irq(struct irq_data *d)
1217e4ac676SOleksij Rempel {
1227e4ac676SOleksij Rempel 	__raw_writel(ASM9260_BM_CLEAR_BIT(d->hwirq),
1237e4ac676SOleksij Rempel 		     icoll_priv.clear +
1247e4ac676SOleksij Rempel 		     ASM9260_HW_ICOLL_CLEARn(d->hwirq));
1257e4ac676SOleksij Rempel 
1267e4ac676SOleksij Rempel 	__raw_writel(icoll_intr_bitshift(d, BM_ICOLL_INTR_ENABLE),
1277e4ac676SOleksij Rempel 			icoll_intr_reg(d) + SET_REG);
1287e4ac676SOleksij Rempel }
1297e4ac676SOleksij Rempel 
1306a8e95b0SShawn Guo static struct irq_chip mxs_icoll_chip = {
1316a8e95b0SShawn Guo 	.irq_ack = icoll_ack_irq,
1326a8e95b0SShawn Guo 	.irq_mask = icoll_mask_irq,
1336a8e95b0SShawn Guo 	.irq_unmask = icoll_unmask_irq,
13488e20c74SStefan Wahren 	.flags = IRQCHIP_MASK_ON_SUSPEND |
13588e20c74SStefan Wahren 		 IRQCHIP_SKIP_SET_WAKE,
1366a8e95b0SShawn Guo };
1376a8e95b0SShawn Guo 
1387e4ac676SOleksij Rempel static struct irq_chip asm9260_icoll_chip = {
1397e4ac676SOleksij Rempel 	.irq_ack = icoll_ack_irq,
1407e4ac676SOleksij Rempel 	.irq_mask = asm9260_mask_irq,
1417e4ac676SOleksij Rempel 	.irq_unmask = asm9260_unmask_irq,
14288e20c74SStefan Wahren 	.flags = IRQCHIP_MASK_ON_SUSPEND |
14388e20c74SStefan Wahren 		 IRQCHIP_SKIP_SET_WAKE,
1447e4ac676SOleksij Rempel };
1457e4ac676SOleksij Rempel 
1466a8e95b0SShawn Guo asmlinkage void __exception_irq_entry icoll_handle_irq(struct pt_regs *regs)
1476a8e95b0SShawn Guo {
1486a8e95b0SShawn Guo 	u32 irqnr;
1496a8e95b0SShawn Guo 
15025e34b44SOleksij Rempel 	irqnr = __raw_readl(icoll_priv.stat);
15125e34b44SOleksij Rempel 	__raw_writel(irqnr, icoll_priv.vector);
152b3410e5fSMarc Zyngier 	handle_domain_irq(icoll_domain, irqnr, regs);
1536a8e95b0SShawn Guo }
1546a8e95b0SShawn Guo 
1556a8e95b0SShawn Guo static int icoll_irq_domain_map(struct irq_domain *d, unsigned int virq,
1566a8e95b0SShawn Guo 				irq_hw_number_t hw)
1576a8e95b0SShawn Guo {
1587e4ac676SOleksij Rempel 	struct irq_chip *chip;
1597e4ac676SOleksij Rempel 
1607e4ac676SOleksij Rempel 	if (icoll_priv.type == ICOLL)
1617e4ac676SOleksij Rempel 		chip = &mxs_icoll_chip;
1627e4ac676SOleksij Rempel 	else
1637e4ac676SOleksij Rempel 		chip = &asm9260_icoll_chip;
1647e4ac676SOleksij Rempel 
1657e4ac676SOleksij Rempel 	irq_set_chip_and_handler(virq, chip, handle_level_irq);
1666a8e95b0SShawn Guo 
1676a8e95b0SShawn Guo 	return 0;
1686a8e95b0SShawn Guo }
1696a8e95b0SShawn Guo 
17096009736SKrzysztof Kozlowski static const struct irq_domain_ops icoll_irq_domain_ops = {
1716a8e95b0SShawn Guo 	.map = icoll_irq_domain_map,
1726a8e95b0SShawn Guo 	.xlate = irq_domain_xlate_onecell,
1736a8e95b0SShawn Guo };
1746a8e95b0SShawn Guo 
17525e34b44SOleksij Rempel static void __init icoll_add_domain(struct device_node *np,
17625e34b44SOleksij Rempel 			  int num)
17725e34b44SOleksij Rempel {
17825e34b44SOleksij Rempel 	icoll_domain = irq_domain_add_linear(np, num,
17925e34b44SOleksij Rempel 					     &icoll_irq_domain_ops, NULL);
18025e34b44SOleksij Rempel 
18125e34b44SOleksij Rempel 	if (!icoll_domain)
18225e34b44SOleksij Rempel 		panic("%s: unable to create irq domain", np->full_name);
18325e34b44SOleksij Rempel }
18425e34b44SOleksij Rempel 
18525e34b44SOleksij Rempel static void __iomem * __init icoll_init_iobase(struct device_node *np)
18625e34b44SOleksij Rempel {
18725e34b44SOleksij Rempel 	void __iomem *icoll_base;
18825e34b44SOleksij Rempel 
18925e34b44SOleksij Rempel 	icoll_base = of_io_request_and_map(np, 0, np->name);
190edf8fcdcSVladimir Zapolskiy 	if (IS_ERR(icoll_base))
19125e34b44SOleksij Rempel 		panic("%s: unable to map resource", np->full_name);
19225e34b44SOleksij Rempel 	return icoll_base;
19325e34b44SOleksij Rempel }
19425e34b44SOleksij Rempel 
19510776b5fSRob Herring static int __init icoll_of_init(struct device_node *np,
1966a8e95b0SShawn Guo 			  struct device_node *interrupt_parent)
1976a8e95b0SShawn Guo {
19825e34b44SOleksij Rempel 	void __iomem *icoll_base;
19925e34b44SOleksij Rempel 
2007e4ac676SOleksij Rempel 	icoll_priv.type = ICOLL;
2017e4ac676SOleksij Rempel 
20225e34b44SOleksij Rempel 	icoll_base		= icoll_init_iobase(np);
20325e34b44SOleksij Rempel 	icoll_priv.vector	= icoll_base + HW_ICOLL_VECTOR;
20425e34b44SOleksij Rempel 	icoll_priv.levelack	= icoll_base + HW_ICOLL_LEVELACK;
20525e34b44SOleksij Rempel 	icoll_priv.ctrl		= icoll_base + HW_ICOLL_CTRL;
20625e34b44SOleksij Rempel 	icoll_priv.stat		= icoll_base + HW_ICOLL_STAT_OFFSET;
20725e34b44SOleksij Rempel 	icoll_priv.intr		= icoll_base + HW_ICOLL_INTERRUPT0;
2087e4ac676SOleksij Rempel 	icoll_priv.clear	= NULL;
2096a8e95b0SShawn Guo 
2106a8e95b0SShawn Guo 	/*
2116a8e95b0SShawn Guo 	 * Interrupt Collector reset, which initializes the priority
2126a8e95b0SShawn Guo 	 * for each irq to level 0.
2136a8e95b0SShawn Guo 	 */
21425e34b44SOleksij Rempel 	stmp_reset_block(icoll_priv.ctrl);
2156a8e95b0SShawn Guo 
21625e34b44SOleksij Rempel 	icoll_add_domain(np, ICOLL_NUM_IRQS);
217e59a8451SOleksij Rempel 
218e59a8451SOleksij Rempel 	return 0;
2196a8e95b0SShawn Guo }
2206a8e95b0SShawn Guo IRQCHIP_DECLARE(mxs, "fsl,icoll", icoll_of_init);
2217e4ac676SOleksij Rempel 
2227e4ac676SOleksij Rempel static int __init asm9260_of_init(struct device_node *np,
2237e4ac676SOleksij Rempel 			  struct device_node *interrupt_parent)
2247e4ac676SOleksij Rempel {
2257e4ac676SOleksij Rempel 	void __iomem *icoll_base;
2267e4ac676SOleksij Rempel 	int i;
2277e4ac676SOleksij Rempel 
2287e4ac676SOleksij Rempel 	icoll_priv.type = ASM9260_ICOLL;
2297e4ac676SOleksij Rempel 
2307e4ac676SOleksij Rempel 	icoll_base = icoll_init_iobase(np);
2317e4ac676SOleksij Rempel 	icoll_priv.vector	= icoll_base + ASM9260_HW_ICOLL_VECTOR;
2327e4ac676SOleksij Rempel 	icoll_priv.levelack	= icoll_base + ASM9260_HW_ICOLL_LEVELACK;
2337e4ac676SOleksij Rempel 	icoll_priv.ctrl		= icoll_base + ASM9260_HW_ICOLL_CTRL;
2347e4ac676SOleksij Rempel 	icoll_priv.stat		= icoll_base + ASM9260_HW_ICOLL_STAT_OFFSET;
2357e4ac676SOleksij Rempel 	icoll_priv.intr		= icoll_base + ASM9260_HW_ICOLL_INTERRUPT0;
2367e4ac676SOleksij Rempel 	icoll_priv.clear	= icoll_base + ASM9260_HW_ICOLL_CLEAR0;
2377e4ac676SOleksij Rempel 
2387e4ac676SOleksij Rempel 	writel_relaxed(ASM9260_BM_CTRL_IRQ_ENABLE,
2397e4ac676SOleksij Rempel 			icoll_priv.ctrl);
2407e4ac676SOleksij Rempel 	/*
2417e4ac676SOleksij Rempel 	 * ASM9260 don't provide reset bit. So, we need to set level 0
2427e4ac676SOleksij Rempel 	 * manually.
2437e4ac676SOleksij Rempel 	 */
2447e4ac676SOleksij Rempel 	for (i = 0; i < 16 * 0x10; i += 0x10)
2457e4ac676SOleksij Rempel 		writel(0, icoll_priv.intr + i);
2467e4ac676SOleksij Rempel 
2477e4ac676SOleksij Rempel 	icoll_add_domain(np, ASM9260_NUM_IRQS);
248c5b63520SOleksij Rempel 	set_handle_irq(icoll_handle_irq);
2497e4ac676SOleksij Rempel 
2507e4ac676SOleksij Rempel 	return 0;
2517e4ac676SOleksij Rempel }
2527e4ac676SOleksij Rempel IRQCHIP_DECLARE(asm9260, "alphascale,asm9260-icoll", asm9260_of_init);
253