| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * StarFive JH8100 External Interrupt Controller driver |
| * |
| * Copyright (C) 2023 StarFive Technology Co., Ltd. |
| * |
| * Author: Changhuang Liang <changhuang.liang@starfivetech.com> |
| */ |
| |
| #define pr_fmt(fmt) "irq-starfive-jh8100: " fmt |
| |
| #include <linux/bitops.h> |
| #include <linux/clk.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/reset.h> |
| #include <linux/spinlock.h> |
| |
| #define STARFIVE_INTC_SRC0_CLEAR 0x10 |
| #define STARFIVE_INTC_SRC0_MASK 0x14 |
| #define STARFIVE_INTC_SRC0_INT 0x1c |
| |
| #define STARFIVE_INTC_SRC_IRQ_NUM 32 |
| |
| struct starfive_irq_chip { |
| void __iomem *base; |
| struct irq_domain *domain; |
| raw_spinlock_t lock; |
| }; |
| |
| static void starfive_intc_bit_set(struct starfive_irq_chip *irqc, |
| u32 reg, u32 bit_mask) |
| { |
| u32 value; |
| |
| value = ioread32(irqc->base + reg); |
| value |= bit_mask; |
| iowrite32(value, irqc->base + reg); |
| } |
| |
| static void starfive_intc_bit_clear(struct starfive_irq_chip *irqc, |
| u32 reg, u32 bit_mask) |
| { |
| u32 value; |
| |
| value = ioread32(irqc->base + reg); |
| value &= ~bit_mask; |
| iowrite32(value, irqc->base + reg); |
| } |
| |
| static void starfive_intc_unmask(struct irq_data *d) |
| { |
| struct starfive_irq_chip *irqc = irq_data_get_irq_chip_data(d); |
| |
| raw_spin_lock(&irqc->lock); |
| starfive_intc_bit_clear(irqc, STARFIVE_INTC_SRC0_MASK, BIT(d->hwirq)); |
| raw_spin_unlock(&irqc->lock); |
| } |
| |
| static void starfive_intc_mask(struct irq_data *d) |
| { |
| struct starfive_irq_chip *irqc = irq_data_get_irq_chip_data(d); |
| |
| raw_spin_lock(&irqc->lock); |
| starfive_intc_bit_set(irqc, STARFIVE_INTC_SRC0_MASK, BIT(d->hwirq)); |
| raw_spin_unlock(&irqc->lock); |
| } |
| |
| static struct irq_chip intc_dev = { |
| .name = "StarFive JH8100 INTC", |
| .irq_unmask = starfive_intc_unmask, |
| .irq_mask = starfive_intc_mask, |
| }; |
| |
| static int starfive_intc_map(struct irq_domain *d, unsigned int irq, |
| irq_hw_number_t hwirq) |
| { |
| irq_domain_set_info(d, irq, hwirq, &intc_dev, d->host_data, |
| handle_level_irq, NULL, NULL); |
| |
| return 0; |
| } |
| |
| static const struct irq_domain_ops starfive_intc_domain_ops = { |
| .xlate = irq_domain_xlate_onecell, |
| .map = starfive_intc_map, |
| }; |
| |
| static void starfive_intc_irq_handler(struct irq_desc *desc) |
| { |
| struct starfive_irq_chip *irqc = irq_data_get_irq_handler_data(&desc->irq_data); |
| struct irq_chip *chip = irq_desc_get_chip(desc); |
| unsigned long value; |
| int hwirq; |
| |
| chained_irq_enter(chip, desc); |
| |
| value = ioread32(irqc->base + STARFIVE_INTC_SRC0_INT); |
| while (value) { |
| hwirq = ffs(value) - 1; |
| |
| generic_handle_domain_irq(irqc->domain, hwirq); |
| |
| starfive_intc_bit_set(irqc, STARFIVE_INTC_SRC0_CLEAR, BIT(hwirq)); |
| starfive_intc_bit_clear(irqc, STARFIVE_INTC_SRC0_CLEAR, BIT(hwirq)); |
| |
| __clear_bit(hwirq, &value); |
| } |
| |
| chained_irq_exit(chip, desc); |
| } |
| |
| static int __init starfive_intc_init(struct device_node *intc, |
| struct device_node *parent) |
| { |
| struct starfive_irq_chip *irqc; |
| struct reset_control *rst; |
| struct clk *clk; |
| int parent_irq; |
| int ret; |
| |
| irqc = kzalloc(sizeof(*irqc), GFP_KERNEL); |
| if (!irqc) |
| return -ENOMEM; |
| |
| irqc->base = of_iomap(intc, 0); |
| if (!irqc->base) { |
| pr_err("Unable to map registers\n"); |
| ret = -ENXIO; |
| goto err_free; |
| } |
| |
| rst = of_reset_control_get_exclusive(intc, NULL); |
| if (IS_ERR(rst)) { |
| pr_err("Unable to get reset control %pe\n", rst); |
| ret = PTR_ERR(rst); |
| goto err_unmap; |
| } |
| |
| clk = of_clk_get(intc, 0); |
| if (IS_ERR(clk)) { |
| pr_err("Unable to get clock %pe\n", clk); |
| ret = PTR_ERR(clk); |
| goto err_reset_put; |
| } |
| |
| ret = reset_control_deassert(rst); |
| if (ret) |
| goto err_clk_put; |
| |
| ret = clk_prepare_enable(clk); |
| if (ret) |
| goto err_reset_assert; |
| |
| raw_spin_lock_init(&irqc->lock); |
| |
| irqc->domain = irq_domain_add_linear(intc, STARFIVE_INTC_SRC_IRQ_NUM, |
| &starfive_intc_domain_ops, irqc); |
| if (!irqc->domain) { |
| pr_err("Unable to create IRQ domain\n"); |
| ret = -EINVAL; |
| goto err_clk_disable; |
| } |
| |
| parent_irq = of_irq_get(intc, 0); |
| if (parent_irq < 0) { |
| pr_err("Failed to get main IRQ: %d\n", parent_irq); |
| ret = parent_irq; |
| goto err_remove_domain; |
| } |
| |
| irq_set_chained_handler_and_data(parent_irq, starfive_intc_irq_handler, |
| irqc); |
| |
| pr_info("Interrupt controller register, nr_irqs %d\n", |
| STARFIVE_INTC_SRC_IRQ_NUM); |
| |
| return 0; |
| |
| err_remove_domain: |
| irq_domain_remove(irqc->domain); |
| err_clk_disable: |
| clk_disable_unprepare(clk); |
| err_reset_assert: |
| reset_control_assert(rst); |
| err_clk_put: |
| clk_put(clk); |
| err_reset_put: |
| reset_control_put(rst); |
| err_unmap: |
| iounmap(irqc->base); |
| err_free: |
| kfree(irqc); |
| return ret; |
| } |
| |
| IRQCHIP_PLATFORM_DRIVER_BEGIN(starfive_intc) |
| IRQCHIP_MATCH("starfive,jh8100-intc", starfive_intc_init) |
| IRQCHIP_PLATFORM_DRIVER_END(starfive_intc) |
| |
| MODULE_DESCRIPTION("StarFive JH8100 External Interrupt Controller"); |
| MODULE_LICENSE("GPL"); |
| MODULE_AUTHOR("Changhuang Liang <changhuang.liang@starfivetech.com>"); |