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