]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
irqchip/aspeed-intc: Add AST27XX INTC support
authorKevin Chen <kevin_chen@aspeedtech.com>
Wed, 16 Oct 2024 02:24:10 +0000 (10:24 +0800)
committerThomas Gleixner <tglx@linutronix.de>
Thu, 17 Oct 2024 19:35:28 +0000 (21:35 +0200)
Support Aspeed Interrupt Controller on Aspeed Silicon SoCs.

ASPEED interrupt controller(INTC) maps the internal interrupt
sources to a parent interrupt controller, which can be GIC or INTC.

Signed-off-by: Kevin Chen <kevin_chen@aspeedtech.com>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Link: https://lore.kernel.org/all/20241016022410.1154574-3-kevin_chen@aspeedtech.com
drivers/irqchip/Makefile
drivers/irqchip/irq-aspeed-intc.c [new file with mode: 0644]

index 94ecaebff37f8f1abe9334d277643193614143b2..15ed6e952ecd34782af6ffd4cb1490bbf2794297 100644 (file)
@@ -85,6 +85,7 @@ obj-$(CONFIG_MVEBU_SEI)                       += irq-mvebu-sei.o
 obj-$(CONFIG_LS_EXTIRQ)                        += irq-ls-extirq.o
 obj-$(CONFIG_LS_SCFG_MSI)              += irq-ls-scfg-msi.o
 obj-$(CONFIG_ARCH_ASPEED)              += irq-aspeed-vic.o irq-aspeed-i2c-ic.o irq-aspeed-scu-ic.o
+obj-$(CONFIG_ARCH_ASPEED)              += irq-aspeed-intc.o
 obj-$(CONFIG_STM32MP_EXTI)             += irq-stm32mp-exti.o
 obj-$(CONFIG_STM32_EXTI)               += irq-stm32-exti.o
 obj-$(CONFIG_QCOM_IRQ_COMBINER)                += qcom-irq-combiner.o
diff --git a/drivers/irqchip/irq-aspeed-intc.c b/drivers/irqchip/irq-aspeed-intc.c
new file mode 100644 (file)
index 0000000..bd3b759
--- /dev/null
@@ -0,0 +1,139 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ *  Aspeed Interrupt Controller.
+ *
+ *  Copyright (C) 2023 ASPEED Technology Inc.
+ */
+
+#include <linux/bitops.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>
+#include <linux/io.h>
+#include <linux/spinlock.h>
+
+#define INTC_INT_ENABLE_REG    0x00
+#define INTC_INT_STATUS_REG    0x04
+#define INTC_IRQS_PER_WORD     32
+
+struct aspeed_intc_ic {
+       void __iomem            *base;
+       raw_spinlock_t          gic_lock;
+       raw_spinlock_t          intc_lock;
+       struct irq_domain       *irq_domain;
+};
+
+static void aspeed_intc_ic_irq_handler(struct irq_desc *desc)
+{
+       struct aspeed_intc_ic *intc_ic = irq_desc_get_handler_data(desc);
+       struct irq_chip *chip = irq_desc_get_chip(desc);
+
+       chained_irq_enter(chip, desc);
+
+       scoped_guard(raw_spinlock, &intc_ic->gic_lock) {
+               unsigned long bit, status;
+
+               status = readl(intc_ic->base + INTC_INT_STATUS_REG);
+               for_each_set_bit(bit, &status, INTC_IRQS_PER_WORD) {
+                       generic_handle_domain_irq(intc_ic->irq_domain, bit);
+                       writel(BIT(bit), intc_ic->base + INTC_INT_STATUS_REG);
+               }
+       }
+
+       chained_irq_exit(chip, desc);
+}
+
+static void aspeed_intc_irq_mask(struct irq_data *data)
+{
+       struct aspeed_intc_ic *intc_ic = irq_data_get_irq_chip_data(data);
+       unsigned int mask = readl(intc_ic->base + INTC_INT_ENABLE_REG) & ~BIT(data->hwirq);
+
+       guard(raw_spinlock)(&intc_ic->intc_lock);
+       writel(mask, intc_ic->base + INTC_INT_ENABLE_REG);
+}
+
+static void aspeed_intc_irq_unmask(struct irq_data *data)
+{
+       struct aspeed_intc_ic *intc_ic = irq_data_get_irq_chip_data(data);
+       unsigned int unmask = readl(intc_ic->base + INTC_INT_ENABLE_REG) | BIT(data->hwirq);
+
+       guard(raw_spinlock)(&intc_ic->intc_lock);
+       writel(unmask, intc_ic->base + INTC_INT_ENABLE_REG);
+}
+
+static struct irq_chip aspeed_intc_chip = {
+       .name                   = "ASPEED INTC",
+       .irq_mask               = aspeed_intc_irq_mask,
+       .irq_unmask             = aspeed_intc_irq_unmask,
+};
+
+static int aspeed_intc_ic_map_irq_domain(struct irq_domain *domain, unsigned int irq,
+                                        irq_hw_number_t hwirq)
+{
+       irq_set_chip_and_handler(irq, &aspeed_intc_chip, handle_level_irq);
+       irq_set_chip_data(irq, domain->host_data);
+
+       return 0;
+}
+
+static const struct irq_domain_ops aspeed_intc_ic_irq_domain_ops = {
+       .map = aspeed_intc_ic_map_irq_domain,
+};
+
+static int __init aspeed_intc_ic_of_init(struct device_node *node,
+                                        struct device_node *parent)
+{
+       struct aspeed_intc_ic *intc_ic;
+       int irq, i, ret = 0;
+
+       intc_ic = kzalloc(sizeof(*intc_ic), GFP_KERNEL);
+       if (!intc_ic)
+               return -ENOMEM;
+
+       intc_ic->base = of_iomap(node, 0);
+       if (!intc_ic->base) {
+               pr_err("Failed to iomap intc_ic base\n");
+               ret = -ENOMEM;
+               goto err_free_ic;
+       }
+       writel(0xffffffff, intc_ic->base + INTC_INT_STATUS_REG);
+       writel(0x0, intc_ic->base + INTC_INT_ENABLE_REG);
+
+       intc_ic->irq_domain = irq_domain_add_linear(node, INTC_IRQS_PER_WORD,
+                                                   &aspeed_intc_ic_irq_domain_ops, intc_ic);
+       if (!intc_ic->irq_domain) {
+               ret = -ENOMEM;
+               goto err_iounmap;
+       }
+
+       raw_spin_lock_init(&intc_ic->gic_lock);
+       raw_spin_lock_init(&intc_ic->intc_lock);
+
+       /* Check all the irq numbers valid. If not, unmaps all the base and frees the data. */
+       for (i = 0; i < of_irq_count(node); i++) {
+               irq = irq_of_parse_and_map(node, i);
+               if (!irq) {
+                       pr_err("Failed to get irq number\n");
+                       ret = -EINVAL;
+                       goto err_iounmap;
+               }
+       }
+
+       for (i = 0; i < of_irq_count(node); i++) {
+               irq = irq_of_parse_and_map(node, i);
+               irq_set_chained_handler_and_data(irq, aspeed_intc_ic_irq_handler, intc_ic);
+       }
+
+       return 0;
+
+err_iounmap:
+       iounmap(intc_ic->base);
+err_free_ic:
+       kfree(intc_ic);
+       return ret;
+}
+
+IRQCHIP_DECLARE(ast2700_intc_ic, "aspeed,ast2700-intc-ic", aspeed_intc_ic_of_init);