1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Copyright (C) 2020, Jiaxun Yang <jiaxun.yang@flygoat.com> 4 * Loongson HTPIC IRQ support 5 */ 6 7 #include <linux/init.h> 8 #include <linux/of_address.h> 9 #include <linux/of_irq.h> 10 #include <linux/irqchip.h> 11 #include <linux/irqchip/chained_irq.h> 12 #include <linux/irq.h> 13 #include <linux/io.h> 14 #include <linux/syscore_ops.h> 15 16 #include <asm/i8259.h> 17 18 #define HTPIC_MAX_PARENT_IRQ 4 19 #define HTINT_NUM_VECTORS 8 20 #define HTINT_EN_OFF 0x20 21 22 struct loongson_htpic { 23 void __iomem *base; 24 struct irq_domain *domain; 25 }; 26 27 static struct loongson_htpic *htpic; 28 29 static void htpic_irq_dispatch(struct irq_desc *desc) 30 { 31 struct loongson_htpic *priv = irq_desc_get_handler_data(desc); 32 struct irq_chip *chip = irq_desc_get_chip(desc); 33 uint32_t pending; 34 35 chained_irq_enter(chip, desc); 36 pending = readl(priv->base); 37 /* Ack all IRQs at once, otherwise IRQ flood might happen */ 38 writel(pending, priv->base); 39 40 if (!pending) 41 spurious_interrupt(); 42 43 while (pending) { 44 int bit = __ffs(pending); 45 46 if (unlikely(bit > 15)) { 47 spurious_interrupt(); 48 break; 49 } 50 51 generic_handle_domain_irq(priv->domain, bit); 52 pending &= ~BIT(bit); 53 } 54 chained_irq_exit(chip, desc); 55 } 56 57 static void htpic_reg_init(void) 58 { 59 int i; 60 61 for (i = 0; i < HTINT_NUM_VECTORS; i++) { 62 /* Disable all HT Vectors */ 63 writel(0x0, htpic->base + HTINT_EN_OFF + i * 0x4); 64 /* Read back to force write */ 65 (void) readl(htpic->base + i * 0x4); 66 /* Ack all possible pending IRQs */ 67 writel(GENMASK(31, 0), htpic->base + i * 0x4); 68 } 69 70 /* Enable 16 vectors for PIC */ 71 writel(0xffff, htpic->base + HTINT_EN_OFF); 72 } 73 74 static void htpic_resume(void) 75 { 76 htpic_reg_init(); 77 } 78 79 struct syscore_ops htpic_syscore_ops = { 80 .resume = htpic_resume, 81 }; 82 83 static int __init htpic_of_init(struct device_node *node, struct device_node *parent) 84 { 85 unsigned int parent_irq[4]; 86 int i, err; 87 int num_parents = 0; 88 89 if (htpic) { 90 pr_err("loongson-htpic: Only one HTPIC is allowed in the system\n"); 91 return -ENODEV; 92 } 93 94 htpic = kzalloc(sizeof(*htpic), GFP_KERNEL); 95 if (!htpic) 96 return -ENOMEM; 97 98 htpic->base = of_iomap(node, 0); 99 if (!htpic->base) { 100 err = -ENODEV; 101 goto out_free; 102 } 103 104 htpic->domain = __init_i8259_irqs(node); 105 if (!htpic->domain) { 106 pr_err("loongson-htpic: Failed to initialize i8259 IRQs\n"); 107 err = -ENOMEM; 108 goto out_iounmap; 109 } 110 111 /* Interrupt may come from any of the 4 interrupt line */ 112 for (i = 0; i < HTPIC_MAX_PARENT_IRQ; i++) { 113 parent_irq[i] = irq_of_parse_and_map(node, i); 114 if (parent_irq[i] <= 0) 115 break; 116 117 num_parents++; 118 } 119 120 if (!num_parents) { 121 pr_err("loongson-htpic: Failed to get parent irqs\n"); 122 err = -ENODEV; 123 goto out_remove_domain; 124 } 125 126 htpic_reg_init(); 127 128 for (i = 0; i < num_parents; i++) { 129 irq_set_chained_handler_and_data(parent_irq[i], 130 htpic_irq_dispatch, htpic); 131 } 132 133 register_syscore_ops(&htpic_syscore_ops); 134 135 return 0; 136 137 out_remove_domain: 138 irq_domain_remove(htpic->domain); 139 out_iounmap: 140 iounmap(htpic->base); 141 out_free: 142 kfree(htpic); 143 return err; 144 } 145 146 IRQCHIP_DECLARE(loongson_htpic, "loongson,htpic-1.0", htpic_of_init); 147