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