162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Driver for MIPS Goldfish Programmable Interrupt Controller.
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Author: Miodrag Dinic <miodrag.dinic@mips.com>
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#include <linux/interrupt.h>
1162306a36Sopenharmony_ci#include <linux/irq.h>
1262306a36Sopenharmony_ci#include <linux/irqchip.h>
1362306a36Sopenharmony_ci#include <linux/irqchip/chained_irq.h>
1462306a36Sopenharmony_ci#include <linux/irqdomain.h>
1562306a36Sopenharmony_ci#include <linux/of_address.h>
1662306a36Sopenharmony_ci#include <linux/of_irq.h>
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci#define GFPIC_NR_IRQS			32
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci/* 8..39 Cascaded Goldfish PIC interrupts */
2162306a36Sopenharmony_ci#define GFPIC_IRQ_BASE			8
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci#define GFPIC_REG_IRQ_PENDING		0x04
2462306a36Sopenharmony_ci#define GFPIC_REG_IRQ_DISABLE_ALL	0x08
2562306a36Sopenharmony_ci#define GFPIC_REG_IRQ_DISABLE		0x0c
2662306a36Sopenharmony_ci#define GFPIC_REG_IRQ_ENABLE		0x10
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_cistruct goldfish_pic_data {
2962306a36Sopenharmony_ci	void __iomem *base;
3062306a36Sopenharmony_ci	struct irq_domain *irq_domain;
3162306a36Sopenharmony_ci};
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_cistatic void goldfish_pic_cascade(struct irq_desc *desc)
3462306a36Sopenharmony_ci{
3562306a36Sopenharmony_ci	struct goldfish_pic_data *gfpic = irq_desc_get_handler_data(desc);
3662306a36Sopenharmony_ci	struct irq_chip *host_chip = irq_desc_get_chip(desc);
3762306a36Sopenharmony_ci	u32 pending, hwirq;
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci	chained_irq_enter(host_chip, desc);
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci	pending = readl(gfpic->base + GFPIC_REG_IRQ_PENDING);
4262306a36Sopenharmony_ci	while (pending) {
4362306a36Sopenharmony_ci		hwirq = __fls(pending);
4462306a36Sopenharmony_ci		generic_handle_domain_irq(gfpic->irq_domain, hwirq);
4562306a36Sopenharmony_ci		pending &= ~(1 << hwirq);
4662306a36Sopenharmony_ci	}
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci	chained_irq_exit(host_chip, desc);
4962306a36Sopenharmony_ci}
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_cistatic const struct irq_domain_ops goldfish_irq_domain_ops = {
5262306a36Sopenharmony_ci	.xlate = irq_domain_xlate_onecell,
5362306a36Sopenharmony_ci};
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_cistatic int __init goldfish_pic_of_init(struct device_node *of_node,
5662306a36Sopenharmony_ci				       struct device_node *parent)
5762306a36Sopenharmony_ci{
5862306a36Sopenharmony_ci	struct goldfish_pic_data *gfpic;
5962306a36Sopenharmony_ci	struct irq_chip_generic *gc;
6062306a36Sopenharmony_ci	struct irq_chip_type *ct;
6162306a36Sopenharmony_ci	unsigned int parent_irq;
6262306a36Sopenharmony_ci	int ret = 0;
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci	gfpic = kzalloc(sizeof(*gfpic), GFP_KERNEL);
6562306a36Sopenharmony_ci	if (!gfpic) {
6662306a36Sopenharmony_ci		ret = -ENOMEM;
6762306a36Sopenharmony_ci		goto out_err;
6862306a36Sopenharmony_ci	}
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci	parent_irq = irq_of_parse_and_map(of_node, 0);
7162306a36Sopenharmony_ci	if (!parent_irq) {
7262306a36Sopenharmony_ci		pr_err("Failed to map parent IRQ!\n");
7362306a36Sopenharmony_ci		ret = -EINVAL;
7462306a36Sopenharmony_ci		goto out_free;
7562306a36Sopenharmony_ci	}
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci	gfpic->base = of_iomap(of_node, 0);
7862306a36Sopenharmony_ci	if (!gfpic->base) {
7962306a36Sopenharmony_ci		pr_err("Failed to map base address!\n");
8062306a36Sopenharmony_ci		ret = -ENOMEM;
8162306a36Sopenharmony_ci		goto out_unmap_irq;
8262306a36Sopenharmony_ci	}
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci	/* Mask interrupts. */
8562306a36Sopenharmony_ci	writel(1, gfpic->base + GFPIC_REG_IRQ_DISABLE_ALL);
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci	gc = irq_alloc_generic_chip("GFPIC", 1, GFPIC_IRQ_BASE, gfpic->base,
8862306a36Sopenharmony_ci				    handle_level_irq);
8962306a36Sopenharmony_ci	if (!gc) {
9062306a36Sopenharmony_ci		pr_err("Failed to allocate chip structures!\n");
9162306a36Sopenharmony_ci		ret = -ENOMEM;
9262306a36Sopenharmony_ci		goto out_iounmap;
9362306a36Sopenharmony_ci	}
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci	ct = gc->chip_types;
9662306a36Sopenharmony_ci	ct->regs.enable = GFPIC_REG_IRQ_ENABLE;
9762306a36Sopenharmony_ci	ct->regs.disable = GFPIC_REG_IRQ_DISABLE;
9862306a36Sopenharmony_ci	ct->chip.irq_unmask = irq_gc_unmask_enable_reg;
9962306a36Sopenharmony_ci	ct->chip.irq_mask = irq_gc_mask_disable_reg;
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci	irq_setup_generic_chip(gc, IRQ_MSK(GFPIC_NR_IRQS), 0,
10262306a36Sopenharmony_ci			       IRQ_NOPROBE | IRQ_LEVEL, 0);
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	gfpic->irq_domain = irq_domain_add_legacy(of_node, GFPIC_NR_IRQS,
10562306a36Sopenharmony_ci						  GFPIC_IRQ_BASE, 0,
10662306a36Sopenharmony_ci						  &goldfish_irq_domain_ops,
10762306a36Sopenharmony_ci						  NULL);
10862306a36Sopenharmony_ci	if (!gfpic->irq_domain) {
10962306a36Sopenharmony_ci		pr_err("Failed to add irqdomain!\n");
11062306a36Sopenharmony_ci		ret = -ENOMEM;
11162306a36Sopenharmony_ci		goto out_destroy_generic_chip;
11262306a36Sopenharmony_ci	}
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci	irq_set_chained_handler_and_data(parent_irq,
11562306a36Sopenharmony_ci					 goldfish_pic_cascade, gfpic);
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci	pr_info("Successfully registered.\n");
11862306a36Sopenharmony_ci	return 0;
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ciout_destroy_generic_chip:
12162306a36Sopenharmony_ci	irq_destroy_generic_chip(gc, IRQ_MSK(GFPIC_NR_IRQS),
12262306a36Sopenharmony_ci				 IRQ_NOPROBE | IRQ_LEVEL, 0);
12362306a36Sopenharmony_ciout_iounmap:
12462306a36Sopenharmony_ci	iounmap(gfpic->base);
12562306a36Sopenharmony_ciout_unmap_irq:
12662306a36Sopenharmony_ci	irq_dispose_mapping(parent_irq);
12762306a36Sopenharmony_ciout_free:
12862306a36Sopenharmony_ci	kfree(gfpic);
12962306a36Sopenharmony_ciout_err:
13062306a36Sopenharmony_ci	pr_err("Failed to initialize! (errno = %d)\n", ret);
13162306a36Sopenharmony_ci	return ret;
13262306a36Sopenharmony_ci}
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ciIRQCHIP_DECLARE(google_gf_pic, "google,goldfish-pic", goldfish_pic_of_init);
135