1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Copyright (C) 2020, Jiaxun Yang <jiaxun.yang@flygoat.com> 4 * Loongson HyperTransport Interrupt Vector support 5 */ 6 7 #define pr_fmt(fmt) "htvec: " fmt 8 9 #include <linux/interrupt.h> 10 #include <linux/irq.h> 11 #include <linux/irqchip.h> 12 #include <linux/irqdomain.h> 13 #include <linux/irqchip/chained_irq.h> 14 #include <linux/kernel.h> 15 #include <linux/platform_device.h> 16 #include <linux/of_address.h> 17 #include <linux/of_irq.h> 18 #include <linux/of_platform.h> 19 #include <linux/syscore_ops.h> 20 21 /* Registers */ 22 #define HTVEC_EN_OFF 0x20 23 #define HTVEC_MAX_PARENT_IRQ 8 24 #define VEC_COUNT_PER_REG 32 25 #define VEC_REG_IDX(irq_id) ((irq_id) / VEC_COUNT_PER_REG) 26 #define VEC_REG_BIT(irq_id) ((irq_id) % VEC_COUNT_PER_REG) 27 28 struct htvec { 29 int num_parents; 30 void __iomem *base; 31 struct irq_domain *htvec_domain; 32 raw_spinlock_t htvec_lock; 33 u32 saved_vec_en[HTVEC_MAX_PARENT_IRQ]; 34 }; 35 36 static struct htvec *htvec_priv; 37 38 static void htvec_irq_dispatch(struct irq_desc *desc) 39 { 40 int i; 41 u32 pending; 42 bool handled = false; 43 struct irq_chip *chip = irq_desc_get_chip(desc); 44 struct htvec *priv = irq_desc_get_handler_data(desc); 45 46 chained_irq_enter(chip, desc); 47 48 for (i = 0; i < priv->num_parents; i++) { 49 pending = readl(priv->base + 4 * i); 50 while (pending) { 51 int bit = __ffs(pending); 52 53 generic_handle_domain_irq(priv->htvec_domain, 54 bit + VEC_COUNT_PER_REG * i); 55 pending &= ~BIT(bit); 56 handled = true; 57 } 58 } 59 60 if (!handled) 61 spurious_interrupt(); 62 63 chained_irq_exit(chip, desc); 64 } 65 66 static void htvec_ack_irq(struct irq_data *d) 67 { 68 struct htvec *priv = irq_data_get_irq_chip_data(d); 69 70 writel(BIT(VEC_REG_BIT(d->hwirq)), 71 priv->base + VEC_REG_IDX(d->hwirq) * 4); 72 } 73 74 static void htvec_mask_irq(struct irq_data *d) 75 { 76 u32 reg; 77 void __iomem *addr; 78 struct htvec *priv = irq_data_get_irq_chip_data(d); 79 80 raw_spin_lock(&priv->htvec_lock); 81 addr = priv->base + HTVEC_EN_OFF; 82 addr += VEC_REG_IDX(d->hwirq) * 4; 83 reg = readl(addr); 84 reg &= ~BIT(VEC_REG_BIT(d->hwirq)); 85 writel(reg, addr); 86 raw_spin_unlock(&priv->htvec_lock); 87 } 88 89 static void htvec_unmask_irq(struct irq_data *d) 90 { 91 u32 reg; 92 void __iomem *addr; 93 struct htvec *priv = irq_data_get_irq_chip_data(d); 94 95 raw_spin_lock(&priv->htvec_lock); 96 addr = priv->base + HTVEC_EN_OFF; 97 addr += VEC_REG_IDX(d->hwirq) * 4; 98 reg = readl(addr); 99 reg |= BIT(VEC_REG_BIT(d->hwirq)); 100 writel(reg, addr); 101 raw_spin_unlock(&priv->htvec_lock); 102 } 103 104 static struct irq_chip htvec_irq_chip = { 105 .name = "LOONGSON_HTVEC", 106 .irq_mask = htvec_mask_irq, 107 .irq_unmask = htvec_unmask_irq, 108 .irq_ack = htvec_ack_irq, 109 }; 110 111 static int htvec_domain_alloc(struct irq_domain *domain, unsigned int virq, 112 unsigned int nr_irqs, void *arg) 113 { 114 int ret; 115 unsigned long hwirq; 116 unsigned int type, i; 117 struct htvec *priv = domain->host_data; 118 119 ret = irq_domain_translate_onecell(domain, arg, &hwirq, &type); 120 if (ret) 121 return ret; 122 123 for (i = 0; i < nr_irqs; i++) { 124 irq_domain_set_info(domain, virq + i, hwirq + i, &htvec_irq_chip, 125 priv, handle_edge_irq, NULL, NULL); 126 } 127 128 return 0; 129 } 130 131 static void htvec_domain_free(struct irq_domain *domain, unsigned int virq, 132 unsigned int nr_irqs) 133 { 134 int i; 135 136 for (i = 0; i < nr_irqs; i++) { 137 struct irq_data *d = irq_domain_get_irq_data(domain, virq + i); 138 139 irq_set_handler(virq + i, NULL); 140 irq_domain_reset_irq_data(d); 141 } 142 } 143 144 static const struct irq_domain_ops htvec_domain_ops = { 145 .translate = irq_domain_translate_onecell, 146 .alloc = htvec_domain_alloc, 147 .free = htvec_domain_free, 148 }; 149 150 static void htvec_reset(struct htvec *priv) 151 { 152 u32 idx; 153 154 /* Clear IRQ cause registers, mask all interrupts */ 155 for (idx = 0; idx < priv->num_parents; idx++) { 156 writel_relaxed(0x0, priv->base + HTVEC_EN_OFF + 4 * idx); 157 writel_relaxed(0xFFFFFFFF, priv->base + 4 * idx); 158 } 159 } 160 161 static int htvec_suspend(void) 162 { 163 int i; 164 165 for (i = 0; i < htvec_priv->num_parents; i++) 166 htvec_priv->saved_vec_en[i] = readl(htvec_priv->base + HTVEC_EN_OFF + 4 * i); 167 168 return 0; 169 } 170 171 static void htvec_resume(void) 172 { 173 int i; 174 175 for (i = 0; i < htvec_priv->num_parents; i++) 176 writel(htvec_priv->saved_vec_en[i], htvec_priv->base + HTVEC_EN_OFF + 4 * i); 177 } 178 179 static struct syscore_ops htvec_syscore_ops = { 180 .suspend = htvec_suspend, 181 .resume = htvec_resume, 182 }; 183 184 static int htvec_init(phys_addr_t addr, unsigned long size, 185 int num_parents, int parent_irq[], struct fwnode_handle *domain_handle) 186 { 187 int i; 188 struct htvec *priv; 189 190 priv = kzalloc(sizeof(*priv), GFP_KERNEL); 191 if (!priv) 192 return -ENOMEM; 193 194 priv->num_parents = num_parents; 195 priv->base = ioremap(addr, size); 196 raw_spin_lock_init(&priv->htvec_lock); 197 198 /* Setup IRQ domain */ 199 priv->htvec_domain = irq_domain_create_linear(domain_handle, 200 (VEC_COUNT_PER_REG * priv->num_parents), 201 &htvec_domain_ops, priv); 202 if (!priv->htvec_domain) { 203 pr_err("loongson-htvec: cannot add IRQ domain\n"); 204 goto iounmap_base; 205 } 206 207 htvec_reset(priv); 208 209 for (i = 0; i < priv->num_parents; i++) { 210 irq_set_chained_handler_and_data(parent_irq[i], 211 htvec_irq_dispatch, priv); 212 } 213 214 htvec_priv = priv; 215 216 register_syscore_ops(&htvec_syscore_ops); 217 218 return 0; 219 220 iounmap_base: 221 iounmap(priv->base); 222 kfree(priv); 223 224 return -EINVAL; 225 } 226 227 #ifdef CONFIG_OF 228 229 static int htvec_of_init(struct device_node *node, 230 struct device_node *parent) 231 { 232 int i, err; 233 int parent_irq[8]; 234 int num_parents = 0; 235 struct resource res; 236 237 if (of_address_to_resource(node, 0, &res)) 238 return -EINVAL; 239 240 /* Interrupt may come from any of the 8 interrupt lines */ 241 for (i = 0; i < HTVEC_MAX_PARENT_IRQ; i++) { 242 parent_irq[i] = irq_of_parse_and_map(node, i); 243 if (parent_irq[i] <= 0) 244 break; 245 246 num_parents++; 247 } 248 249 err = htvec_init(res.start, resource_size(&res), 250 num_parents, parent_irq, of_node_to_fwnode(node)); 251 if (err < 0) 252 return err; 253 254 return 0; 255 } 256 257 IRQCHIP_DECLARE(htvec, "loongson,htvec-1.0", htvec_of_init); 258 259 #endif 260 261 #ifdef CONFIG_ACPI 262 static int __init pch_pic_parse_madt(union acpi_subtable_headers *header, 263 const unsigned long end) 264 { 265 struct acpi_madt_bio_pic *pchpic_entry = (struct acpi_madt_bio_pic *)header; 266 267 return pch_pic_acpi_init(htvec_priv->htvec_domain, pchpic_entry); 268 } 269 270 static int __init pch_msi_parse_madt(union acpi_subtable_headers *header, 271 const unsigned long end) 272 { 273 struct acpi_madt_msi_pic *pchmsi_entry = (struct acpi_madt_msi_pic *)header; 274 275 return pch_msi_acpi_init(htvec_priv->htvec_domain, pchmsi_entry); 276 } 277 278 static int __init acpi_cascade_irqdomain_init(void) 279 { 280 int r; 281 282 r = acpi_table_parse_madt(ACPI_MADT_TYPE_BIO_PIC, pch_pic_parse_madt, 0); 283 if (r < 0) 284 return r; 285 286 r = acpi_table_parse_madt(ACPI_MADT_TYPE_MSI_PIC, pch_msi_parse_madt, 0); 287 if (r < 0) 288 return r; 289 290 return 0; 291 } 292 293 int __init htvec_acpi_init(struct irq_domain *parent, 294 struct acpi_madt_ht_pic *acpi_htvec) 295 { 296 int i, ret; 297 int num_parents, parent_irq[8]; 298 struct fwnode_handle *domain_handle; 299 300 if (!acpi_htvec) 301 return -EINVAL; 302 303 num_parents = HTVEC_MAX_PARENT_IRQ; 304 305 domain_handle = irq_domain_alloc_fwnode(&acpi_htvec->address); 306 if (!domain_handle) { 307 pr_err("Unable to allocate domain handle\n"); 308 return -ENOMEM; 309 } 310 311 /* Interrupt may come from any of the 8 interrupt lines */ 312 for (i = 0; i < HTVEC_MAX_PARENT_IRQ; i++) 313 parent_irq[i] = irq_create_mapping(parent, acpi_htvec->cascade[i]); 314 315 ret = htvec_init(acpi_htvec->address, acpi_htvec->size, 316 num_parents, parent_irq, domain_handle); 317 318 if (ret == 0) 319 ret = acpi_cascade_irqdomain_init(); 320 else 321 irq_domain_free_fwnode(domain_handle); 322 323 return ret; 324 } 325 326 #endif 327