12874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
24235ff50SMiodrag Dinic /*
34235ff50SMiodrag Dinic  * Driver for MIPS Goldfish Programmable Interrupt Controller.
44235ff50SMiodrag Dinic  *
54235ff50SMiodrag Dinic  * Author: Miodrag Dinic <miodrag.dinic@mips.com>
64235ff50SMiodrag Dinic  */
74235ff50SMiodrag Dinic 
84235ff50SMiodrag Dinic #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
94235ff50SMiodrag Dinic 
104235ff50SMiodrag Dinic #include <linux/interrupt.h>
114235ff50SMiodrag Dinic #include <linux/irq.h>
124235ff50SMiodrag Dinic #include <linux/irqchip.h>
134235ff50SMiodrag Dinic #include <linux/irqchip/chained_irq.h>
144235ff50SMiodrag Dinic #include <linux/irqdomain.h>
154235ff50SMiodrag Dinic #include <linux/of_address.h>
164235ff50SMiodrag Dinic #include <linux/of_irq.h>
174235ff50SMiodrag Dinic 
184235ff50SMiodrag Dinic #define GFPIC_NR_IRQS			32
194235ff50SMiodrag Dinic 
204235ff50SMiodrag Dinic /* 8..39 Cascaded Goldfish PIC interrupts */
214235ff50SMiodrag Dinic #define GFPIC_IRQ_BASE			8
224235ff50SMiodrag Dinic 
234235ff50SMiodrag Dinic #define GFPIC_REG_IRQ_PENDING		0x04
244235ff50SMiodrag Dinic #define GFPIC_REG_IRQ_DISABLE_ALL	0x08
254235ff50SMiodrag Dinic #define GFPIC_REG_IRQ_DISABLE		0x0c
264235ff50SMiodrag Dinic #define GFPIC_REG_IRQ_ENABLE		0x10
274235ff50SMiodrag Dinic 
284235ff50SMiodrag Dinic struct goldfish_pic_data {
294235ff50SMiodrag Dinic 	void __iomem *base;
304235ff50SMiodrag Dinic 	struct irq_domain *irq_domain;
314235ff50SMiodrag Dinic };
324235ff50SMiodrag Dinic 
goldfish_pic_cascade(struct irq_desc * desc)334235ff50SMiodrag Dinic static void goldfish_pic_cascade(struct irq_desc *desc)
344235ff50SMiodrag Dinic {
354235ff50SMiodrag Dinic 	struct goldfish_pic_data *gfpic = irq_desc_get_handler_data(desc);
364235ff50SMiodrag Dinic 	struct irq_chip *host_chip = irq_desc_get_chip(desc);
37*046a6ee2SMarc Zyngier 	u32 pending, hwirq;
384235ff50SMiodrag Dinic 
394235ff50SMiodrag Dinic 	chained_irq_enter(host_chip, desc);
404235ff50SMiodrag Dinic 
414235ff50SMiodrag Dinic 	pending = readl(gfpic->base + GFPIC_REG_IRQ_PENDING);
424235ff50SMiodrag Dinic 	while (pending) {
434235ff50SMiodrag Dinic 		hwirq = __fls(pending);
44*046a6ee2SMarc Zyngier 		generic_handle_domain_irq(gfpic->irq_domain, hwirq);
454235ff50SMiodrag Dinic 		pending &= ~(1 << hwirq);
464235ff50SMiodrag Dinic 	}
474235ff50SMiodrag Dinic 
484235ff50SMiodrag Dinic 	chained_irq_exit(host_chip, desc);
494235ff50SMiodrag Dinic }
504235ff50SMiodrag Dinic 
514235ff50SMiodrag Dinic static const struct irq_domain_ops goldfish_irq_domain_ops = {
524235ff50SMiodrag Dinic 	.xlate = irq_domain_xlate_onecell,
534235ff50SMiodrag Dinic };
544235ff50SMiodrag Dinic 
goldfish_pic_of_init(struct device_node * of_node,struct device_node * parent)554235ff50SMiodrag Dinic static int __init goldfish_pic_of_init(struct device_node *of_node,
564235ff50SMiodrag Dinic 				       struct device_node *parent)
574235ff50SMiodrag Dinic {
584235ff50SMiodrag Dinic 	struct goldfish_pic_data *gfpic;
594235ff50SMiodrag Dinic 	struct irq_chip_generic *gc;
604235ff50SMiodrag Dinic 	struct irq_chip_type *ct;
614235ff50SMiodrag Dinic 	unsigned int parent_irq;
624235ff50SMiodrag Dinic 	int ret = 0;
634235ff50SMiodrag Dinic 
644235ff50SMiodrag Dinic 	gfpic = kzalloc(sizeof(*gfpic), GFP_KERNEL);
654235ff50SMiodrag Dinic 	if (!gfpic) {
664235ff50SMiodrag Dinic 		ret = -ENOMEM;
674235ff50SMiodrag Dinic 		goto out_err;
684235ff50SMiodrag Dinic 	}
694235ff50SMiodrag Dinic 
704235ff50SMiodrag Dinic 	parent_irq = irq_of_parse_and_map(of_node, 0);
714235ff50SMiodrag Dinic 	if (!parent_irq) {
724235ff50SMiodrag Dinic 		pr_err("Failed to map parent IRQ!\n");
734235ff50SMiodrag Dinic 		ret = -EINVAL;
744235ff50SMiodrag Dinic 		goto out_free;
754235ff50SMiodrag Dinic 	}
764235ff50SMiodrag Dinic 
774235ff50SMiodrag Dinic 	gfpic->base = of_iomap(of_node, 0);
784235ff50SMiodrag Dinic 	if (!gfpic->base) {
794235ff50SMiodrag Dinic 		pr_err("Failed to map base address!\n");
804235ff50SMiodrag Dinic 		ret = -ENOMEM;
814235ff50SMiodrag Dinic 		goto out_unmap_irq;
824235ff50SMiodrag Dinic 	}
834235ff50SMiodrag Dinic 
844235ff50SMiodrag Dinic 	/* Mask interrupts. */
854235ff50SMiodrag Dinic 	writel(1, gfpic->base + GFPIC_REG_IRQ_DISABLE_ALL);
864235ff50SMiodrag Dinic 
874235ff50SMiodrag Dinic 	gc = irq_alloc_generic_chip("GFPIC", 1, GFPIC_IRQ_BASE, gfpic->base,
884235ff50SMiodrag Dinic 				    handle_level_irq);
894235ff50SMiodrag Dinic 	if (!gc) {
904235ff50SMiodrag Dinic 		pr_err("Failed to allocate chip structures!\n");
914235ff50SMiodrag Dinic 		ret = -ENOMEM;
924235ff50SMiodrag Dinic 		goto out_iounmap;
934235ff50SMiodrag Dinic 	}
944235ff50SMiodrag Dinic 
954235ff50SMiodrag Dinic 	ct = gc->chip_types;
964235ff50SMiodrag Dinic 	ct->regs.enable = GFPIC_REG_IRQ_ENABLE;
974235ff50SMiodrag Dinic 	ct->regs.disable = GFPIC_REG_IRQ_DISABLE;
984235ff50SMiodrag Dinic 	ct->chip.irq_unmask = irq_gc_unmask_enable_reg;
994235ff50SMiodrag Dinic 	ct->chip.irq_mask = irq_gc_mask_disable_reg;
1004235ff50SMiodrag Dinic 
1014235ff50SMiodrag Dinic 	irq_setup_generic_chip(gc, IRQ_MSK(GFPIC_NR_IRQS), 0,
1024235ff50SMiodrag Dinic 			       IRQ_NOPROBE | IRQ_LEVEL, 0);
1034235ff50SMiodrag Dinic 
1044235ff50SMiodrag Dinic 	gfpic->irq_domain = irq_domain_add_legacy(of_node, GFPIC_NR_IRQS,
1054235ff50SMiodrag Dinic 						  GFPIC_IRQ_BASE, 0,
1064235ff50SMiodrag Dinic 						  &goldfish_irq_domain_ops,
1074235ff50SMiodrag Dinic 						  NULL);
1084235ff50SMiodrag Dinic 	if (!gfpic->irq_domain) {
1094235ff50SMiodrag Dinic 		pr_err("Failed to add irqdomain!\n");
1104235ff50SMiodrag Dinic 		ret = -ENOMEM;
1114235ff50SMiodrag Dinic 		goto out_destroy_generic_chip;
1124235ff50SMiodrag Dinic 	}
1134235ff50SMiodrag Dinic 
1144235ff50SMiodrag Dinic 	irq_set_chained_handler_and_data(parent_irq,
1154235ff50SMiodrag Dinic 					 goldfish_pic_cascade, gfpic);
1164235ff50SMiodrag Dinic 
1174235ff50SMiodrag Dinic 	pr_info("Successfully registered.\n");
1184235ff50SMiodrag Dinic 	return 0;
1194235ff50SMiodrag Dinic 
1204235ff50SMiodrag Dinic out_destroy_generic_chip:
1214235ff50SMiodrag Dinic 	irq_destroy_generic_chip(gc, IRQ_MSK(GFPIC_NR_IRQS),
1224235ff50SMiodrag Dinic 				 IRQ_NOPROBE | IRQ_LEVEL, 0);
1234235ff50SMiodrag Dinic out_iounmap:
1244235ff50SMiodrag Dinic 	iounmap(gfpic->base);
1254235ff50SMiodrag Dinic out_unmap_irq:
1264235ff50SMiodrag Dinic 	irq_dispose_mapping(parent_irq);
1274235ff50SMiodrag Dinic out_free:
1284235ff50SMiodrag Dinic 	kfree(gfpic);
1294235ff50SMiodrag Dinic out_err:
1304235ff50SMiodrag Dinic 	pr_err("Failed to initialize! (errno = %d)\n", ret);
1314235ff50SMiodrag Dinic 	return ret;
1324235ff50SMiodrag Dinic }
1334235ff50SMiodrag Dinic 
1344235ff50SMiodrag Dinic IRQCHIP_DECLARE(google_gf_pic, "google,goldfish-pic", goldfish_pic_of_init);
135