1818e915fSJiaxun Yang // SPDX-License-Identifier: GPL-2.0 2818e915fSJiaxun Yang /* 3818e915fSJiaxun Yang * Copyright (C) 2020, Jiaxun Yang <jiaxun.yang@flygoat.com> 4818e915fSJiaxun Yang * Loongson HyperTransport Interrupt Vector support 5818e915fSJiaxun Yang */ 6818e915fSJiaxun Yang 7818e915fSJiaxun Yang #define pr_fmt(fmt) "htvec: " fmt 8818e915fSJiaxun Yang 9818e915fSJiaxun Yang #include <linux/interrupt.h> 10818e915fSJiaxun Yang #include <linux/irq.h> 11818e915fSJiaxun Yang #include <linux/irqchip.h> 12818e915fSJiaxun Yang #include <linux/irqdomain.h> 13818e915fSJiaxun Yang #include <linux/irqchip/chained_irq.h> 14818e915fSJiaxun Yang #include <linux/kernel.h> 15818e915fSJiaxun Yang #include <linux/platform_device.h> 16818e915fSJiaxun Yang #include <linux/of_address.h> 17818e915fSJiaxun Yang #include <linux/of_irq.h> 18818e915fSJiaxun Yang #include <linux/of_platform.h> 19818e915fSJiaxun Yang 20818e915fSJiaxun Yang /* Registers */ 21818e915fSJiaxun Yang #define HTVEC_EN_OFF 0x20 22818e915fSJiaxun Yang #define HTVEC_MAX_PARENT_IRQ 4 23818e915fSJiaxun Yang 24818e915fSJiaxun Yang #define VEC_COUNT_PER_REG 32 25818e915fSJiaxun Yang #define VEC_REG_COUNT 4 26818e915fSJiaxun Yang #define VEC_COUNT (VEC_COUNT_PER_REG * VEC_REG_COUNT) 27818e915fSJiaxun Yang #define VEC_REG_IDX(irq_id) ((irq_id) / VEC_COUNT_PER_REG) 28818e915fSJiaxun Yang #define VEC_REG_BIT(irq_id) ((irq_id) % VEC_COUNT_PER_REG) 29818e915fSJiaxun Yang 30818e915fSJiaxun Yang struct htvec { 31818e915fSJiaxun Yang void __iomem *base; 32818e915fSJiaxun Yang struct irq_domain *htvec_domain; 33818e915fSJiaxun Yang raw_spinlock_t htvec_lock; 34818e915fSJiaxun Yang }; 35818e915fSJiaxun Yang 36818e915fSJiaxun Yang static void htvec_irq_dispatch(struct irq_desc *desc) 37818e915fSJiaxun Yang { 38818e915fSJiaxun Yang int i; 39818e915fSJiaxun Yang u32 pending; 40818e915fSJiaxun Yang bool handled = false; 41818e915fSJiaxun Yang struct irq_chip *chip = irq_desc_get_chip(desc); 42818e915fSJiaxun Yang struct htvec *priv = irq_desc_get_handler_data(desc); 43818e915fSJiaxun Yang 44818e915fSJiaxun Yang chained_irq_enter(chip, desc); 45818e915fSJiaxun Yang 46818e915fSJiaxun Yang for (i = 0; i < VEC_REG_COUNT; i++) { 47818e915fSJiaxun Yang pending = readl(priv->base + 4 * i); 48818e915fSJiaxun Yang while (pending) { 49818e915fSJiaxun Yang int bit = __ffs(pending); 50818e915fSJiaxun Yang 51818e915fSJiaxun Yang generic_handle_irq(irq_linear_revmap(priv->htvec_domain, bit + 52818e915fSJiaxun Yang VEC_COUNT_PER_REG * i)); 53818e915fSJiaxun Yang pending &= ~BIT(bit); 54818e915fSJiaxun Yang handled = true; 55818e915fSJiaxun Yang } 56818e915fSJiaxun Yang } 57818e915fSJiaxun Yang 58818e915fSJiaxun Yang if (!handled) 59818e915fSJiaxun Yang spurious_interrupt(); 60818e915fSJiaxun Yang 61818e915fSJiaxun Yang chained_irq_exit(chip, desc); 62818e915fSJiaxun Yang } 63818e915fSJiaxun Yang 64818e915fSJiaxun Yang static void htvec_ack_irq(struct irq_data *d) 65818e915fSJiaxun Yang { 66818e915fSJiaxun Yang struct htvec *priv = irq_data_get_irq_chip_data(d); 67818e915fSJiaxun Yang 68818e915fSJiaxun Yang writel(BIT(VEC_REG_BIT(d->hwirq)), 69818e915fSJiaxun Yang priv->base + VEC_REG_IDX(d->hwirq) * 4); 70818e915fSJiaxun Yang } 71818e915fSJiaxun Yang 72818e915fSJiaxun Yang static void htvec_mask_irq(struct irq_data *d) 73818e915fSJiaxun Yang { 74818e915fSJiaxun Yang u32 reg; 75818e915fSJiaxun Yang void __iomem *addr; 76818e915fSJiaxun Yang struct htvec *priv = irq_data_get_irq_chip_data(d); 77818e915fSJiaxun Yang 78818e915fSJiaxun Yang raw_spin_lock(&priv->htvec_lock); 79818e915fSJiaxun Yang addr = priv->base + HTVEC_EN_OFF; 80818e915fSJiaxun Yang addr += VEC_REG_IDX(d->hwirq) * 4; 81818e915fSJiaxun Yang reg = readl(addr); 82818e915fSJiaxun Yang reg &= ~BIT(VEC_REG_BIT(d->hwirq)); 83818e915fSJiaxun Yang writel(reg, addr); 84818e915fSJiaxun Yang raw_spin_unlock(&priv->htvec_lock); 85818e915fSJiaxun Yang } 86818e915fSJiaxun Yang 87818e915fSJiaxun Yang static void htvec_unmask_irq(struct irq_data *d) 88818e915fSJiaxun Yang { 89818e915fSJiaxun Yang u32 reg; 90818e915fSJiaxun Yang void __iomem *addr; 91818e915fSJiaxun Yang struct htvec *priv = irq_data_get_irq_chip_data(d); 92818e915fSJiaxun Yang 93818e915fSJiaxun Yang raw_spin_lock(&priv->htvec_lock); 94818e915fSJiaxun Yang addr = priv->base + HTVEC_EN_OFF; 95818e915fSJiaxun Yang addr += VEC_REG_IDX(d->hwirq) * 4; 96818e915fSJiaxun Yang reg = readl(addr); 97818e915fSJiaxun Yang reg |= BIT(VEC_REG_BIT(d->hwirq)); 98818e915fSJiaxun Yang writel(reg, addr); 99818e915fSJiaxun Yang raw_spin_unlock(&priv->htvec_lock); 100818e915fSJiaxun Yang } 101818e915fSJiaxun Yang 102818e915fSJiaxun Yang static struct irq_chip htvec_irq_chip = { 103818e915fSJiaxun Yang .name = "LOONGSON_HTVEC", 104818e915fSJiaxun Yang .irq_mask = htvec_mask_irq, 105818e915fSJiaxun Yang .irq_unmask = htvec_unmask_irq, 106818e915fSJiaxun Yang .irq_ack = htvec_ack_irq, 107818e915fSJiaxun Yang }; 108818e915fSJiaxun Yang 109818e915fSJiaxun Yang static int htvec_domain_alloc(struct irq_domain *domain, unsigned int virq, 110818e915fSJiaxun Yang unsigned int nr_irqs, void *arg) 111818e915fSJiaxun Yang { 112818e915fSJiaxun Yang unsigned long hwirq; 113818e915fSJiaxun Yang unsigned int type, i; 114818e915fSJiaxun Yang struct htvec *priv = domain->host_data; 115818e915fSJiaxun Yang 116818e915fSJiaxun Yang irq_domain_translate_onecell(domain, arg, &hwirq, &type); 117818e915fSJiaxun Yang 118818e915fSJiaxun Yang for (i = 0; i < nr_irqs; i++) { 119818e915fSJiaxun Yang irq_domain_set_info(domain, virq + i, hwirq + i, &htvec_irq_chip, 120818e915fSJiaxun Yang priv, handle_edge_irq, NULL, NULL); 121818e915fSJiaxun Yang } 122818e915fSJiaxun Yang 123818e915fSJiaxun Yang return 0; 124818e915fSJiaxun Yang } 125818e915fSJiaxun Yang 126818e915fSJiaxun Yang static void htvec_domain_free(struct irq_domain *domain, unsigned int virq, 127818e915fSJiaxun Yang unsigned int nr_irqs) 128818e915fSJiaxun Yang { 129818e915fSJiaxun Yang int i; 130818e915fSJiaxun Yang 131818e915fSJiaxun Yang for (i = 0; i < nr_irqs; i++) { 132818e915fSJiaxun Yang struct irq_data *d = irq_domain_get_irq_data(domain, virq + i); 133818e915fSJiaxun Yang 134818e915fSJiaxun Yang irq_set_handler(virq + i, NULL); 135818e915fSJiaxun Yang irq_domain_reset_irq_data(d); 136818e915fSJiaxun Yang } 137818e915fSJiaxun Yang } 138818e915fSJiaxun Yang 139818e915fSJiaxun Yang static const struct irq_domain_ops htvec_domain_ops = { 140818e915fSJiaxun Yang .translate = irq_domain_translate_onecell, 141818e915fSJiaxun Yang .alloc = htvec_domain_alloc, 142818e915fSJiaxun Yang .free = htvec_domain_free, 143818e915fSJiaxun Yang }; 144818e915fSJiaxun Yang 145818e915fSJiaxun Yang static void htvec_reset(struct htvec *priv) 146818e915fSJiaxun Yang { 147818e915fSJiaxun Yang u32 idx; 148818e915fSJiaxun Yang 149818e915fSJiaxun Yang /* Clear IRQ cause registers, mask all interrupts */ 150818e915fSJiaxun Yang for (idx = 0; idx < VEC_REG_COUNT; idx++) { 151818e915fSJiaxun Yang writel_relaxed(0x0, priv->base + HTVEC_EN_OFF + 4 * idx); 152818e915fSJiaxun Yang writel_relaxed(0xFFFFFFFF, priv->base); 153818e915fSJiaxun Yang } 154818e915fSJiaxun Yang } 155818e915fSJiaxun Yang 156818e915fSJiaxun Yang static int htvec_of_init(struct device_node *node, 157818e915fSJiaxun Yang struct device_node *parent) 158818e915fSJiaxun Yang { 159818e915fSJiaxun Yang struct htvec *priv; 160818e915fSJiaxun Yang int err, parent_irq[4], num_parents = 0, i; 161818e915fSJiaxun Yang 162818e915fSJiaxun Yang priv = kzalloc(sizeof(*priv), GFP_KERNEL); 163818e915fSJiaxun Yang if (!priv) 164818e915fSJiaxun Yang return -ENOMEM; 165818e915fSJiaxun Yang 166818e915fSJiaxun Yang raw_spin_lock_init(&priv->htvec_lock); 167818e915fSJiaxun Yang priv->base = of_iomap(node, 0); 168818e915fSJiaxun Yang if (!priv->base) { 169818e915fSJiaxun Yang err = -ENOMEM; 170818e915fSJiaxun Yang goto free_priv; 171818e915fSJiaxun Yang } 172818e915fSJiaxun Yang 173818e915fSJiaxun Yang /* Interrupt may come from any of the 4 interrupt line */ 174818e915fSJiaxun Yang for (i = 0; i < HTVEC_MAX_PARENT_IRQ; i++) { 175818e915fSJiaxun Yang parent_irq[i] = irq_of_parse_and_map(node, i); 176818e915fSJiaxun Yang if (parent_irq[i] <= 0) 177818e915fSJiaxun Yang break; 178818e915fSJiaxun Yang 179818e915fSJiaxun Yang num_parents++; 180818e915fSJiaxun Yang } 181818e915fSJiaxun Yang 182818e915fSJiaxun Yang if (!num_parents) { 183818e915fSJiaxun Yang pr_err("Failed to get parent irqs\n"); 184818e915fSJiaxun Yang err = -ENODEV; 185818e915fSJiaxun Yang goto iounmap_base; 186818e915fSJiaxun Yang } 187818e915fSJiaxun Yang 188818e915fSJiaxun Yang priv->htvec_domain = irq_domain_create_linear(of_node_to_fwnode(node), 189818e915fSJiaxun Yang VEC_COUNT, 190818e915fSJiaxun Yang &htvec_domain_ops, 191818e915fSJiaxun Yang priv); 192818e915fSJiaxun Yang if (!priv->htvec_domain) { 193818e915fSJiaxun Yang pr_err("Failed to create IRQ domain\n"); 194818e915fSJiaxun Yang err = -ENOMEM; 195818e915fSJiaxun Yang goto iounmap_base; 196818e915fSJiaxun Yang } 197818e915fSJiaxun Yang 198818e915fSJiaxun Yang htvec_reset(priv); 199818e915fSJiaxun Yang 200818e915fSJiaxun Yang for (i = 0; i < num_parents; i++) 201818e915fSJiaxun Yang irq_set_chained_handler_and_data(parent_irq[i], 202818e915fSJiaxun Yang htvec_irq_dispatch, priv); 203818e915fSJiaxun Yang 204818e915fSJiaxun Yang return 0; 205818e915fSJiaxun Yang 206818e915fSJiaxun Yang iounmap_base: 207818e915fSJiaxun Yang iounmap(priv->base); 208818e915fSJiaxun Yang free_priv: 209818e915fSJiaxun Yang kfree(priv); 210818e915fSJiaxun Yang 211818e915fSJiaxun Yang return err; 212818e915fSJiaxun Yang } 213818e915fSJiaxun Yang 214818e915fSJiaxun Yang IRQCHIP_DECLARE(htvec, "loongson,htvec-1.0", htvec_of_init); 215