|  | // SPDX-License-Identifier: GPL-2.0 | 
|  | /* | 
|  | *  Copyright (C) 2020, Jiaxun Yang <jiaxun.yang@flygoat.com> | 
|  | *  Loongson HTPIC IRQ support | 
|  | */ | 
|  |  | 
|  | #include <linux/init.h> | 
|  | #include <linux/of_address.h> | 
|  | #include <linux/of_irq.h> | 
|  | #include <linux/irqchip.h> | 
|  | #include <linux/irqchip/chained_irq.h> | 
|  | #include <linux/irq.h> | 
|  | #include <linux/io.h> | 
|  | #include <linux/syscore_ops.h> | 
|  |  | 
|  | #include <asm/i8259.h> | 
|  |  | 
|  | #define HTPIC_MAX_PARENT_IRQ	4 | 
|  | #define HTINT_NUM_VECTORS	8 | 
|  | #define HTINT_EN_OFF		0x20 | 
|  |  | 
|  | struct loongson_htpic { | 
|  | void __iomem *base; | 
|  | struct irq_domain *domain; | 
|  | }; | 
|  |  | 
|  | static struct loongson_htpic *htpic; | 
|  |  | 
|  | static void htpic_irq_dispatch(struct irq_desc *desc) | 
|  | { | 
|  | struct loongson_htpic *priv = irq_desc_get_handler_data(desc); | 
|  | struct irq_chip *chip = irq_desc_get_chip(desc); | 
|  | uint32_t pending; | 
|  |  | 
|  | chained_irq_enter(chip, desc); | 
|  | pending = readl(priv->base); | 
|  | /* Ack all IRQs at once, otherwise IRQ flood might happen */ | 
|  | writel(pending, priv->base); | 
|  |  | 
|  | if (!pending) | 
|  | spurious_interrupt(); | 
|  |  | 
|  | while (pending) { | 
|  | int bit = __ffs(pending); | 
|  |  | 
|  | if (unlikely(bit > 15)) { | 
|  | spurious_interrupt(); | 
|  | break; | 
|  | } | 
|  |  | 
|  | generic_handle_domain_irq(priv->domain, bit); | 
|  | pending &= ~BIT(bit); | 
|  | } | 
|  | chained_irq_exit(chip, desc); | 
|  | } | 
|  |  | 
|  | static void htpic_reg_init(void) | 
|  | { | 
|  | int i; | 
|  |  | 
|  | for (i = 0; i < HTINT_NUM_VECTORS; i++) { | 
|  | /* Disable all HT Vectors */ | 
|  | writel(0x0, htpic->base + HTINT_EN_OFF + i * 0x4); | 
|  | /* Read back to force write */ | 
|  | (void) readl(htpic->base + i * 0x4); | 
|  | /* Ack all possible pending IRQs */ | 
|  | writel(GENMASK(31, 0), htpic->base + i * 0x4); | 
|  | } | 
|  |  | 
|  | /* Enable 16 vectors for PIC */ | 
|  | writel(0xffff, htpic->base + HTINT_EN_OFF); | 
|  | } | 
|  |  | 
|  | static void htpic_resume(void) | 
|  | { | 
|  | htpic_reg_init(); | 
|  | } | 
|  |  | 
|  | struct syscore_ops htpic_syscore_ops = { | 
|  | .resume		= htpic_resume, | 
|  | }; | 
|  |  | 
|  | static int __init htpic_of_init(struct device_node *node, struct device_node *parent) | 
|  | { | 
|  | unsigned int parent_irq[4]; | 
|  | int i, err; | 
|  | int num_parents = 0; | 
|  |  | 
|  | if (htpic) { | 
|  | pr_err("loongson-htpic: Only one HTPIC is allowed in the system\n"); | 
|  | return -ENODEV; | 
|  | } | 
|  |  | 
|  | htpic = kzalloc(sizeof(*htpic), GFP_KERNEL); | 
|  | if (!htpic) | 
|  | return -ENOMEM; | 
|  |  | 
|  | htpic->base = of_iomap(node, 0); | 
|  | if (!htpic->base) { | 
|  | err = -ENODEV; | 
|  | goto out_free; | 
|  | } | 
|  |  | 
|  | htpic->domain = __init_i8259_irqs(node); | 
|  | if (!htpic->domain) { | 
|  | pr_err("loongson-htpic: Failed to initialize i8259 IRQs\n"); | 
|  | err = -ENOMEM; | 
|  | goto out_iounmap; | 
|  | } | 
|  |  | 
|  | /* Interrupt may come from any of the 4 interrupt line */ | 
|  | for (i = 0; i < HTPIC_MAX_PARENT_IRQ; i++) { | 
|  | parent_irq[i] = irq_of_parse_and_map(node, i); | 
|  | if (parent_irq[i] <= 0) | 
|  | break; | 
|  |  | 
|  | num_parents++; | 
|  | } | 
|  |  | 
|  | if (!num_parents) { | 
|  | pr_err("loongson-htpic: Failed to get parent irqs\n"); | 
|  | err = -ENODEV; | 
|  | goto out_remove_domain; | 
|  | } | 
|  |  | 
|  | htpic_reg_init(); | 
|  |  | 
|  | for (i = 0; i < num_parents; i++) { | 
|  | irq_set_chained_handler_and_data(parent_irq[i], | 
|  | htpic_irq_dispatch, htpic); | 
|  | } | 
|  |  | 
|  | register_syscore_ops(&htpic_syscore_ops); | 
|  |  | 
|  | return 0; | 
|  |  | 
|  | out_remove_domain: | 
|  | irq_domain_remove(htpic->domain); | 
|  | out_iounmap: | 
|  | iounmap(htpic->base); | 
|  | out_free: | 
|  | kfree(htpic); | 
|  | return err; | 
|  | } | 
|  |  | 
|  | IRQCHIP_DECLARE(loongson_htpic, "loongson,htpic-1.0", htpic_of_init); |