1 // SPDX-License-Identifier: GPL-2.0 2 // Copyright (C) 2018 Hangzhou C-SKY Microsystems co.,ltd. 3 4 #include <linux/kernel.h> 5 #include <linux/init.h> 6 #include <linux/of.h> 7 #include <linux/of_address.h> 8 #include <linux/module.h> 9 #include <linux/irqdomain.h> 10 #include <linux/irqchip.h> 11 #include <linux/irq.h> 12 #include <linux/interrupt.h> 13 #include <linux/smp.h> 14 #include <linux/io.h> 15 #include <asm/irq.h> 16 #include <asm/traps.h> 17 #include <asm/reg_ops.h> 18 19 static struct irq_domain *root_domain; 20 static void __iomem *INTCG_base; 21 static void __iomem *INTCL_base; 22 23 #define IPI_IRQ 15 24 #define INTC_IRQS 256 25 #define COMM_IRQ_BASE 32 26 27 #define INTCG_SIZE 0x8000 28 #define INTCL_SIZE 0x1000 29 30 #define INTCG_ICTLR 0x0 31 #define INTCG_CICFGR 0x100 32 #define INTCG_CIDSTR 0x1000 33 34 #define INTCL_PICTLR 0x0 35 #define INTCL_SIGR 0x60 36 #define INTCL_HPPIR 0x68 37 #define INTCL_RDYIR 0x6c 38 #define INTCL_SENR 0xa0 39 #define INTCL_CENR 0xa4 40 #define INTCL_CACR 0xb4 41 42 static DEFINE_PER_CPU(void __iomem *, intcl_reg); 43 44 static void csky_mpintc_handler(struct pt_regs *regs) 45 { 46 void __iomem *reg_base = this_cpu_read(intcl_reg); 47 48 do { 49 handle_domain_irq(root_domain, 50 readl_relaxed(reg_base + INTCL_RDYIR), 51 regs); 52 } while (readl_relaxed(reg_base + INTCL_HPPIR) & BIT(31)); 53 } 54 55 static void csky_mpintc_enable(struct irq_data *d) 56 { 57 void __iomem *reg_base = this_cpu_read(intcl_reg); 58 59 writel_relaxed(d->hwirq, reg_base + INTCL_SENR); 60 } 61 62 static void csky_mpintc_disable(struct irq_data *d) 63 { 64 void __iomem *reg_base = this_cpu_read(intcl_reg); 65 66 writel_relaxed(d->hwirq, reg_base + INTCL_CENR); 67 } 68 69 static void csky_mpintc_eoi(struct irq_data *d) 70 { 71 void __iomem *reg_base = this_cpu_read(intcl_reg); 72 73 writel_relaxed(d->hwirq, reg_base + INTCL_CACR); 74 } 75 76 #ifdef CONFIG_SMP 77 static int csky_irq_set_affinity(struct irq_data *d, 78 const struct cpumask *mask_val, 79 bool force) 80 { 81 unsigned int cpu; 82 unsigned int offset = 4 * (d->hwirq - COMM_IRQ_BASE); 83 84 if (!force) 85 cpu = cpumask_any_and(mask_val, cpu_online_mask); 86 else 87 cpu = cpumask_first(mask_val); 88 89 if (cpu >= nr_cpu_ids) 90 return -EINVAL; 91 92 /* Enable interrupt destination */ 93 cpu |= BIT(31); 94 95 writel_relaxed(cpu, INTCG_base + INTCG_CIDSTR + offset); 96 97 irq_data_update_effective_affinity(d, cpumask_of(cpu)); 98 99 return IRQ_SET_MASK_OK_DONE; 100 } 101 #endif 102 103 static struct irq_chip csky_irq_chip = { 104 .name = "C-SKY SMP Intc", 105 .irq_eoi = csky_mpintc_eoi, 106 .irq_enable = csky_mpintc_enable, 107 .irq_disable = csky_mpintc_disable, 108 #ifdef CONFIG_SMP 109 .irq_set_affinity = csky_irq_set_affinity, 110 #endif 111 }; 112 113 static int csky_irqdomain_map(struct irq_domain *d, unsigned int irq, 114 irq_hw_number_t hwirq) 115 { 116 if (hwirq < COMM_IRQ_BASE) { 117 irq_set_percpu_devid(irq); 118 irq_set_chip_and_handler(irq, &csky_irq_chip, 119 handle_percpu_irq); 120 } else { 121 irq_set_chip_and_handler(irq, &csky_irq_chip, 122 handle_fasteoi_irq); 123 } 124 125 return 0; 126 } 127 128 static const struct irq_domain_ops csky_irqdomain_ops = { 129 .map = csky_irqdomain_map, 130 .xlate = irq_domain_xlate_onecell, 131 }; 132 133 #ifdef CONFIG_SMP 134 static void csky_mpintc_send_ipi(const struct cpumask *mask) 135 { 136 void __iomem *reg_base = this_cpu_read(intcl_reg); 137 138 /* 139 * INTCL_SIGR[3:0] INTID 140 * INTCL_SIGR[8:15] CPUMASK 141 */ 142 writel_relaxed((*cpumask_bits(mask)) << 8 | IPI_IRQ, 143 reg_base + INTCL_SIGR); 144 } 145 #endif 146 147 /* C-SKY multi processor interrupt controller */ 148 static int __init 149 csky_mpintc_init(struct device_node *node, struct device_node *parent) 150 { 151 int ret; 152 unsigned int cpu, nr_irq; 153 #ifdef CONFIG_SMP 154 unsigned int ipi_irq; 155 #endif 156 157 if (parent) 158 return 0; 159 160 ret = of_property_read_u32(node, "csky,num-irqs", &nr_irq); 161 if (ret < 0) 162 nr_irq = INTC_IRQS; 163 164 if (INTCG_base == NULL) { 165 INTCG_base = ioremap(mfcr("cr<31, 14>"), 166 INTCL_SIZE*nr_cpu_ids + INTCG_SIZE); 167 if (INTCG_base == NULL) 168 return -EIO; 169 170 INTCL_base = INTCG_base + INTCG_SIZE; 171 172 writel_relaxed(BIT(0), INTCG_base + INTCG_ICTLR); 173 } 174 175 root_domain = irq_domain_add_linear(node, nr_irq, &csky_irqdomain_ops, 176 NULL); 177 if (!root_domain) 178 return -ENXIO; 179 180 /* for every cpu */ 181 for_each_present_cpu(cpu) { 182 per_cpu(intcl_reg, cpu) = INTCL_base + (INTCL_SIZE * cpu); 183 writel_relaxed(BIT(0), per_cpu(intcl_reg, cpu) + INTCL_PICTLR); 184 } 185 186 set_handle_irq(&csky_mpintc_handler); 187 188 #ifdef CONFIG_SMP 189 ipi_irq = irq_create_mapping(root_domain, IPI_IRQ); 190 if (!ipi_irq) 191 return -EIO; 192 193 set_send_ipi(&csky_mpintc_send_ipi, ipi_irq); 194 #endif 195 196 return 0; 197 } 198 IRQCHIP_DECLARE(csky_mpintc, "csky,mpintc", csky_mpintc_init); 199