116216333SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later 26a8e95b0SShawn Guo /* 36a8e95b0SShawn Guo * Copyright (C) 2009-2010 Freescale Semiconductor, Inc. All Rights Reserved. 47e4ac676SOleksij Rempel * Copyright (C) 2014 Oleksij Rempel <linux@rempel-privat.de> 57e4ac676SOleksij Rempel * Add Alphascale ASM9260 support. 66a8e95b0SShawn Guo */ 76a8e95b0SShawn Guo 86a8e95b0SShawn Guo #include <linux/kernel.h> 96a8e95b0SShawn Guo #include <linux/init.h> 106a8e95b0SShawn Guo #include <linux/irq.h> 1141a83e06SJoel Porquet #include <linux/irqchip.h> 126a8e95b0SShawn Guo #include <linux/irqdomain.h> 136a8e95b0SShawn Guo #include <linux/io.h> 146a8e95b0SShawn Guo #include <linux/of.h> 156a8e95b0SShawn Guo #include <linux/of_address.h> 166a8e95b0SShawn Guo #include <linux/of_irq.h> 176a8e95b0SShawn Guo #include <linux/stmp_device.h> 186a8e95b0SShawn Guo #include <asm/exception.h> 196a8e95b0SShawn Guo 207e4ac676SOleksij Rempel #include "alphascale_asm9260-icoll.h" 217e4ac676SOleksij Rempel 2225e34b44SOleksij Rempel /* 2325e34b44SOleksij Rempel * this device provide 4 offsets for each register: 2425e34b44SOleksij Rempel * 0x0 - plain read write mode 2525e34b44SOleksij Rempel * 0x4 - set mode, OR logic. 2625e34b44SOleksij Rempel * 0x8 - clr mode, XOR logic. 2725e34b44SOleksij Rempel * 0xc - togle mode. 2825e34b44SOleksij Rempel */ 2925e34b44SOleksij Rempel #define SET_REG 4 3025e34b44SOleksij Rempel #define CLR_REG 8 3125e34b44SOleksij Rempel 326a8e95b0SShawn Guo #define HW_ICOLL_VECTOR 0x0000 336a8e95b0SShawn Guo #define HW_ICOLL_LEVELACK 0x0010 346a8e95b0SShawn Guo #define HW_ICOLL_CTRL 0x0020 356a8e95b0SShawn Guo #define HW_ICOLL_STAT_OFFSET 0x0070 3625e34b44SOleksij Rempel #define HW_ICOLL_INTERRUPT0 0x0120 3725e34b44SOleksij Rempel #define HW_ICOLL_INTERRUPTn(n) ((n) * 0x10) 3825e34b44SOleksij Rempel #define BM_ICOLL_INTR_ENABLE BIT(2) 396a8e95b0SShawn Guo #define BV_ICOLL_LEVELACK_IRQLEVELACK__LEVEL0 0x1 406a8e95b0SShawn Guo 416a8e95b0SShawn Guo #define ICOLL_NUM_IRQS 128 426a8e95b0SShawn Guo 437e4ac676SOleksij Rempel enum icoll_type { 447e4ac676SOleksij Rempel ICOLL, 457e4ac676SOleksij Rempel ASM9260_ICOLL, 467e4ac676SOleksij Rempel }; 477e4ac676SOleksij Rempel 4825e34b44SOleksij Rempel struct icoll_priv { 4925e34b44SOleksij Rempel void __iomem *vector; 5025e34b44SOleksij Rempel void __iomem *levelack; 5125e34b44SOleksij Rempel void __iomem *ctrl; 5225e34b44SOleksij Rempel void __iomem *stat; 5325e34b44SOleksij Rempel void __iomem *intr; 547e4ac676SOleksij Rempel void __iomem *clear; 557e4ac676SOleksij Rempel enum icoll_type type; 5625e34b44SOleksij Rempel }; 5725e34b44SOleksij Rempel 5825e34b44SOleksij Rempel static struct icoll_priv icoll_priv; 596a8e95b0SShawn Guo static struct irq_domain *icoll_domain; 606a8e95b0SShawn Guo 61a359f757SIngo Molnar /* calculate bit offset depending on number of interrupt per register */ 627e4ac676SOleksij Rempel static u32 icoll_intr_bitshift(struct irq_data *d, u32 bit) 637e4ac676SOleksij Rempel { 647e4ac676SOleksij Rempel /* 657e4ac676SOleksij Rempel * mask lower part of hwirq to convert it 667e4ac676SOleksij Rempel * in 0, 1, 2 or 3 and then multiply it by 8 (or shift by 3) 677e4ac676SOleksij Rempel */ 687e4ac676SOleksij Rempel return bit << ((d->hwirq & 3) << 3); 697e4ac676SOleksij Rempel } 707e4ac676SOleksij Rempel 71a359f757SIngo Molnar /* calculate mem offset depending on number of interrupt per register */ 727e4ac676SOleksij Rempel static void __iomem *icoll_intr_reg(struct irq_data *d) 737e4ac676SOleksij Rempel { 747e4ac676SOleksij Rempel /* offset = hwirq / intr_per_reg * 0x10 */ 757e4ac676SOleksij Rempel return icoll_priv.intr + ((d->hwirq >> 2) * 0x10); 767e4ac676SOleksij Rempel } 777e4ac676SOleksij Rempel 786a8e95b0SShawn Guo static void icoll_ack_irq(struct irq_data *d) 796a8e95b0SShawn Guo { 806a8e95b0SShawn Guo /* 816a8e95b0SShawn Guo * The Interrupt Collector is able to prioritize irqs. 826a8e95b0SShawn Guo * Currently only level 0 is used. So acking can use 836a8e95b0SShawn Guo * BV_ICOLL_LEVELACK_IRQLEVELACK__LEVEL0 unconditionally. 846a8e95b0SShawn Guo */ 856a8e95b0SShawn Guo __raw_writel(BV_ICOLL_LEVELACK_IRQLEVELACK__LEVEL0, 8625e34b44SOleksij Rempel icoll_priv.levelack); 876a8e95b0SShawn Guo } 886a8e95b0SShawn Guo 896a8e95b0SShawn Guo static void icoll_mask_irq(struct irq_data *d) 906a8e95b0SShawn Guo { 9125e34b44SOleksij Rempel __raw_writel(BM_ICOLL_INTR_ENABLE, 9225e34b44SOleksij Rempel icoll_priv.intr + CLR_REG + HW_ICOLL_INTERRUPTn(d->hwirq)); 936a8e95b0SShawn Guo } 946a8e95b0SShawn Guo 956a8e95b0SShawn Guo static void icoll_unmask_irq(struct irq_data *d) 966a8e95b0SShawn Guo { 9725e34b44SOleksij Rempel __raw_writel(BM_ICOLL_INTR_ENABLE, 9825e34b44SOleksij Rempel icoll_priv.intr + SET_REG + HW_ICOLL_INTERRUPTn(d->hwirq)); 996a8e95b0SShawn Guo } 1006a8e95b0SShawn Guo 1017e4ac676SOleksij Rempel static void asm9260_mask_irq(struct irq_data *d) 1027e4ac676SOleksij Rempel { 1037e4ac676SOleksij Rempel __raw_writel(icoll_intr_bitshift(d, BM_ICOLL_INTR_ENABLE), 1047e4ac676SOleksij Rempel icoll_intr_reg(d) + CLR_REG); 1057e4ac676SOleksij Rempel } 1067e4ac676SOleksij Rempel 1077e4ac676SOleksij Rempel static void asm9260_unmask_irq(struct irq_data *d) 1087e4ac676SOleksij Rempel { 1097e4ac676SOleksij Rempel __raw_writel(ASM9260_BM_CLEAR_BIT(d->hwirq), 1107e4ac676SOleksij Rempel icoll_priv.clear + 1117e4ac676SOleksij Rempel ASM9260_HW_ICOLL_CLEARn(d->hwirq)); 1127e4ac676SOleksij Rempel 1137e4ac676SOleksij Rempel __raw_writel(icoll_intr_bitshift(d, BM_ICOLL_INTR_ENABLE), 1147e4ac676SOleksij Rempel icoll_intr_reg(d) + SET_REG); 1157e4ac676SOleksij Rempel } 1167e4ac676SOleksij Rempel 1176a8e95b0SShawn Guo static struct irq_chip mxs_icoll_chip = { 1186a8e95b0SShawn Guo .irq_ack = icoll_ack_irq, 1196a8e95b0SShawn Guo .irq_mask = icoll_mask_irq, 1206a8e95b0SShawn Guo .irq_unmask = icoll_unmask_irq, 12188e20c74SStefan Wahren .flags = IRQCHIP_MASK_ON_SUSPEND | 12288e20c74SStefan Wahren IRQCHIP_SKIP_SET_WAKE, 1236a8e95b0SShawn Guo }; 1246a8e95b0SShawn Guo 1257e4ac676SOleksij Rempel static struct irq_chip asm9260_icoll_chip = { 1267e4ac676SOleksij Rempel .irq_ack = icoll_ack_irq, 1277e4ac676SOleksij Rempel .irq_mask = asm9260_mask_irq, 1287e4ac676SOleksij Rempel .irq_unmask = asm9260_unmask_irq, 12988e20c74SStefan Wahren .flags = IRQCHIP_MASK_ON_SUSPEND | 13088e20c74SStefan Wahren IRQCHIP_SKIP_SET_WAKE, 1317e4ac676SOleksij Rempel }; 1327e4ac676SOleksij Rempel 1336a8e95b0SShawn Guo asmlinkage void __exception_irq_entry icoll_handle_irq(struct pt_regs *regs) 1346a8e95b0SShawn Guo { 1356a8e95b0SShawn Guo u32 irqnr; 1366a8e95b0SShawn Guo 13725e34b44SOleksij Rempel irqnr = __raw_readl(icoll_priv.stat); 13825e34b44SOleksij Rempel __raw_writel(irqnr, icoll_priv.vector); 139*0953fb26SMark Rutland generic_handle_domain_irq(icoll_domain, irqnr); 1406a8e95b0SShawn Guo } 1416a8e95b0SShawn Guo 1426a8e95b0SShawn Guo static int icoll_irq_domain_map(struct irq_domain *d, unsigned int virq, 1436a8e95b0SShawn Guo irq_hw_number_t hw) 1446a8e95b0SShawn Guo { 1457e4ac676SOleksij Rempel struct irq_chip *chip; 1467e4ac676SOleksij Rempel 1477e4ac676SOleksij Rempel if (icoll_priv.type == ICOLL) 1487e4ac676SOleksij Rempel chip = &mxs_icoll_chip; 1497e4ac676SOleksij Rempel else 1507e4ac676SOleksij Rempel chip = &asm9260_icoll_chip; 1517e4ac676SOleksij Rempel 1527e4ac676SOleksij Rempel irq_set_chip_and_handler(virq, chip, handle_level_irq); 1536a8e95b0SShawn Guo 1546a8e95b0SShawn Guo return 0; 1556a8e95b0SShawn Guo } 1566a8e95b0SShawn Guo 15796009736SKrzysztof Kozlowski static const struct irq_domain_ops icoll_irq_domain_ops = { 1586a8e95b0SShawn Guo .map = icoll_irq_domain_map, 1596a8e95b0SShawn Guo .xlate = irq_domain_xlate_onecell, 1606a8e95b0SShawn Guo }; 1616a8e95b0SShawn Guo 16225e34b44SOleksij Rempel static void __init icoll_add_domain(struct device_node *np, 16325e34b44SOleksij Rempel int num) 16425e34b44SOleksij Rempel { 16525e34b44SOleksij Rempel icoll_domain = irq_domain_add_linear(np, num, 16625e34b44SOleksij Rempel &icoll_irq_domain_ops, NULL); 16725e34b44SOleksij Rempel 16825e34b44SOleksij Rempel if (!icoll_domain) 169e81f54c6SRob Herring panic("%pOF: unable to create irq domain", np); 17025e34b44SOleksij Rempel } 17125e34b44SOleksij Rempel 17225e34b44SOleksij Rempel static void __iomem * __init icoll_init_iobase(struct device_node *np) 17325e34b44SOleksij Rempel { 17425e34b44SOleksij Rempel void __iomem *icoll_base; 17525e34b44SOleksij Rempel 17625e34b44SOleksij Rempel icoll_base = of_io_request_and_map(np, 0, np->name); 177edf8fcdcSVladimir Zapolskiy if (IS_ERR(icoll_base)) 178e81f54c6SRob Herring panic("%pOF: unable to map resource", np); 17925e34b44SOleksij Rempel return icoll_base; 18025e34b44SOleksij Rempel } 18125e34b44SOleksij Rempel 18210776b5fSRob Herring static int __init icoll_of_init(struct device_node *np, 1836a8e95b0SShawn Guo struct device_node *interrupt_parent) 1846a8e95b0SShawn Guo { 18525e34b44SOleksij Rempel void __iomem *icoll_base; 18625e34b44SOleksij Rempel 1877e4ac676SOleksij Rempel icoll_priv.type = ICOLL; 1887e4ac676SOleksij Rempel 18925e34b44SOleksij Rempel icoll_base = icoll_init_iobase(np); 19025e34b44SOleksij Rempel icoll_priv.vector = icoll_base + HW_ICOLL_VECTOR; 19125e34b44SOleksij Rempel icoll_priv.levelack = icoll_base + HW_ICOLL_LEVELACK; 19225e34b44SOleksij Rempel icoll_priv.ctrl = icoll_base + HW_ICOLL_CTRL; 19325e34b44SOleksij Rempel icoll_priv.stat = icoll_base + HW_ICOLL_STAT_OFFSET; 19425e34b44SOleksij Rempel icoll_priv.intr = icoll_base + HW_ICOLL_INTERRUPT0; 1957e4ac676SOleksij Rempel icoll_priv.clear = NULL; 1966a8e95b0SShawn Guo 1976a8e95b0SShawn Guo /* 1986a8e95b0SShawn Guo * Interrupt Collector reset, which initializes the priority 1996a8e95b0SShawn Guo * for each irq to level 0. 2006a8e95b0SShawn Guo */ 20125e34b44SOleksij Rempel stmp_reset_block(icoll_priv.ctrl); 2026a8e95b0SShawn Guo 20325e34b44SOleksij Rempel icoll_add_domain(np, ICOLL_NUM_IRQS); 204e59a8451SOleksij Rempel 205e59a8451SOleksij Rempel return 0; 2066a8e95b0SShawn Guo } 2076a8e95b0SShawn Guo IRQCHIP_DECLARE(mxs, "fsl,icoll", icoll_of_init); 2087e4ac676SOleksij Rempel 2097e4ac676SOleksij Rempel static int __init asm9260_of_init(struct device_node *np, 2107e4ac676SOleksij Rempel struct device_node *interrupt_parent) 2117e4ac676SOleksij Rempel { 2127e4ac676SOleksij Rempel void __iomem *icoll_base; 2137e4ac676SOleksij Rempel int i; 2147e4ac676SOleksij Rempel 2157e4ac676SOleksij Rempel icoll_priv.type = ASM9260_ICOLL; 2167e4ac676SOleksij Rempel 2177e4ac676SOleksij Rempel icoll_base = icoll_init_iobase(np); 2187e4ac676SOleksij Rempel icoll_priv.vector = icoll_base + ASM9260_HW_ICOLL_VECTOR; 2197e4ac676SOleksij Rempel icoll_priv.levelack = icoll_base + ASM9260_HW_ICOLL_LEVELACK; 2207e4ac676SOleksij Rempel icoll_priv.ctrl = icoll_base + ASM9260_HW_ICOLL_CTRL; 2217e4ac676SOleksij Rempel icoll_priv.stat = icoll_base + ASM9260_HW_ICOLL_STAT_OFFSET; 2227e4ac676SOleksij Rempel icoll_priv.intr = icoll_base + ASM9260_HW_ICOLL_INTERRUPT0; 2237e4ac676SOleksij Rempel icoll_priv.clear = icoll_base + ASM9260_HW_ICOLL_CLEAR0; 2247e4ac676SOleksij Rempel 2257e4ac676SOleksij Rempel writel_relaxed(ASM9260_BM_CTRL_IRQ_ENABLE, 2267e4ac676SOleksij Rempel icoll_priv.ctrl); 2277e4ac676SOleksij Rempel /* 2287e4ac676SOleksij Rempel * ASM9260 don't provide reset bit. So, we need to set level 0 2297e4ac676SOleksij Rempel * manually. 2307e4ac676SOleksij Rempel */ 2317e4ac676SOleksij Rempel for (i = 0; i < 16 * 0x10; i += 0x10) 2327e4ac676SOleksij Rempel writel(0, icoll_priv.intr + i); 2337e4ac676SOleksij Rempel 2347e4ac676SOleksij Rempel icoll_add_domain(np, ASM9260_NUM_IRQS); 235c5b63520SOleksij Rempel set_handle_irq(icoll_handle_irq); 2367e4ac676SOleksij Rempel 2377e4ac676SOleksij Rempel return 0; 2387e4ac676SOleksij Rempel } 2397e4ac676SOleksij Rempel IRQCHIP_DECLARE(asm9260, "alphascale,asm9260-icoll", asm9260_of_init); 240