linux/drivers/irqchip/irq-goldfish-pic.c
Thomas Gleixner 2874c5fd28 treewide: Replace GPLv2 boilerplate/reference with SPDX - rule 152
Based on 1 normalized pattern(s):

  this program is free software you can redistribute it and or modify
  it under the terms of the gnu general public license as published by
  the free software foundation either version 2 of the license or at
  your option any later version

extracted by the scancode license scanner the SPDX license identifier

  GPL-2.0-or-later

has been chosen to replace the boilerplate/reference in 3029 file(s).

Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Reviewed-by: Allison Randal <allison@lohutok.net>
Cc: linux-spdx@vger.kernel.org
Link: https://lkml.kernel.org/r/20190527070032.746973796@linutronix.de
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2019-05-30 11:26:32 -07:00

136 lines
3.3 KiB
C

// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Driver for MIPS Goldfish Programmable Interrupt Controller.
*
* Author: Miodrag Dinic <miodrag.dinic@mips.com>
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/irqchip.h>
#include <linux/irqchip/chained_irq.h>
#include <linux/irqdomain.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#define GFPIC_NR_IRQS 32
/* 8..39 Cascaded Goldfish PIC interrupts */
#define GFPIC_IRQ_BASE 8
#define GFPIC_REG_IRQ_PENDING 0x04
#define GFPIC_REG_IRQ_DISABLE_ALL 0x08
#define GFPIC_REG_IRQ_DISABLE 0x0c
#define GFPIC_REG_IRQ_ENABLE 0x10
struct goldfish_pic_data {
void __iomem *base;
struct irq_domain *irq_domain;
};
static void goldfish_pic_cascade(struct irq_desc *desc)
{
struct goldfish_pic_data *gfpic = irq_desc_get_handler_data(desc);
struct irq_chip *host_chip = irq_desc_get_chip(desc);
u32 pending, hwirq, virq;
chained_irq_enter(host_chip, desc);
pending = readl(gfpic->base + GFPIC_REG_IRQ_PENDING);
while (pending) {
hwirq = __fls(pending);
virq = irq_linear_revmap(gfpic->irq_domain, hwirq);
generic_handle_irq(virq);
pending &= ~(1 << hwirq);
}
chained_irq_exit(host_chip, desc);
}
static const struct irq_domain_ops goldfish_irq_domain_ops = {
.xlate = irq_domain_xlate_onecell,
};
static int __init goldfish_pic_of_init(struct device_node *of_node,
struct device_node *parent)
{
struct goldfish_pic_data *gfpic;
struct irq_chip_generic *gc;
struct irq_chip_type *ct;
unsigned int parent_irq;
int ret = 0;
gfpic = kzalloc(sizeof(*gfpic), GFP_KERNEL);
if (!gfpic) {
ret = -ENOMEM;
goto out_err;
}
parent_irq = irq_of_parse_and_map(of_node, 0);
if (!parent_irq) {
pr_err("Failed to map parent IRQ!\n");
ret = -EINVAL;
goto out_free;
}
gfpic->base = of_iomap(of_node, 0);
if (!gfpic->base) {
pr_err("Failed to map base address!\n");
ret = -ENOMEM;
goto out_unmap_irq;
}
/* Mask interrupts. */
writel(1, gfpic->base + GFPIC_REG_IRQ_DISABLE_ALL);
gc = irq_alloc_generic_chip("GFPIC", 1, GFPIC_IRQ_BASE, gfpic->base,
handle_level_irq);
if (!gc) {
pr_err("Failed to allocate chip structures!\n");
ret = -ENOMEM;
goto out_iounmap;
}
ct = gc->chip_types;
ct->regs.enable = GFPIC_REG_IRQ_ENABLE;
ct->regs.disable = GFPIC_REG_IRQ_DISABLE;
ct->chip.irq_unmask = irq_gc_unmask_enable_reg;
ct->chip.irq_mask = irq_gc_mask_disable_reg;
irq_setup_generic_chip(gc, IRQ_MSK(GFPIC_NR_IRQS), 0,
IRQ_NOPROBE | IRQ_LEVEL, 0);
gfpic->irq_domain = irq_domain_add_legacy(of_node, GFPIC_NR_IRQS,
GFPIC_IRQ_BASE, 0,
&goldfish_irq_domain_ops,
NULL);
if (!gfpic->irq_domain) {
pr_err("Failed to add irqdomain!\n");
ret = -ENOMEM;
goto out_destroy_generic_chip;
}
irq_set_chained_handler_and_data(parent_irq,
goldfish_pic_cascade, gfpic);
pr_info("Successfully registered.\n");
return 0;
out_destroy_generic_chip:
irq_destroy_generic_chip(gc, IRQ_MSK(GFPIC_NR_IRQS),
IRQ_NOPROBE | IRQ_LEVEL, 0);
out_iounmap:
iounmap(gfpic->base);
out_unmap_irq:
irq_dispose_mapping(parent_irq);
out_free:
kfree(gfpic);
out_err:
pr_err("Failed to initialize! (errno = %d)\n", ret);
return ret;
}
IRQCHIP_DECLARE(google_gf_pic, "google,goldfish-pic", goldfish_pic_of_init);