| // 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); |