Messages in this thread Patch in this message | | | From | qinjian <> | Subject | [PATCH] irqchip: Add support for Sunplus SP7021 interrupt controller | Date | Fri, 22 Oct 2021 16:23:28 +0800 |
| |
Add interrupt driver for Sunplus SP7021 SoC.
Signed-off-by: qinjian <qinjian@cqplus1.com> --- MAINTAINERS | 1 + drivers/irqchip/Kconfig | 9 + drivers/irqchip/Makefile | 1 + drivers/irqchip/irq-sp7021-intc.c | 557 ++++++++++++++++++++++++++++++ 4 files changed, 568 insertions(+) create mode 100644 drivers/irqchip/irq-sp7021-intc.c
diff --git a/MAINTAINERS b/MAINTAINERS index 123616bb9..65cd295e9 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2663,6 +2663,7 @@ F: Documentation/devicetree/bindings/arm/sunplus,sp7021.yaml F: Documentation/devicetree/bindings/clock/sunplus,sp7021-clkc.yaml F: Documentation/devicetree/bindings/interrupt-controller/sunplus,sp7021-intc.yaml F: Documentation/devicetree/bindings/reset/sunplus,reset.yaml +F: drivers/irqchip/irq-sp7021-intc.c F: include/dt-bindings/clock/sp-sp7021.h F: include/dt-bindings/interrupt-controller/sp7021-intc.h F: include/dt-bindings/reset/sp-sp7021.h diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index aca7b595c..52bd16363 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig @@ -602,4 +602,13 @@ config APPLE_AIC Support for the Apple Interrupt Controller found on Apple Silicon SoCs, such as the M1. +config SUNPLUS_SP7021_INTC + bool "Sunplus SP7021 interrupt controller" + help + Support for the Sunplus SP7021 Interrupt Controller IP core. + This is used as a primary controller with SP7021 ChipP and can also + be used as a secondary chained controller on SP7021 ChipC. + + If you don't know what to do here, say Y. + endmenu diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index f88cbf36a..75411f654 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile @@ -116,3 +116,4 @@ obj-$(CONFIG_MACH_REALTEK_RTL) += irq-realtek-rtl.o obj-$(CONFIG_WPCM450_AIC) += irq-wpcm450-aic.o obj-$(CONFIG_IRQ_IDT3243X) += irq-idt3243x.o obj-$(CONFIG_APPLE_AIC) += irq-apple-aic.o +obj-$(CONFIG_SUNPLUS_SP7021_INTC) += irq-sp7021-intc.o diff --git a/drivers/irqchip/irq-sp7021-intc.c b/drivers/irqchip/irq-sp7021-intc.c new file mode 100644 index 000000000..2d26681d3 --- /dev/null +++ b/drivers/irqchip/irq-sp7021-intc.c @@ -0,0 +1,557 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) Sunplus Technology Co., Ltd. + * All rights reserved. + */ +#include <linux/irq.h> +#include <linux/irqdomain.h> +#include <linux/io.h> +#ifdef CONFIG_ARM +#include <asm/exception.h> +#include <asm/mach/irq.h> +#else +#define __exception_irq_entry +#endif +#include <linux/irqchip.h> +#include <linux/irqchip/chained_irq.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <dt-bindings/interrupt-controller/sp7021-intc.h> + +#if defined(CONFIG_SOC_SP7021) +#define SUPPORT_IRQ_GRP_REG +#endif + +#define SP_INTC_HWIRQ_MIN 0 +#define SP_INTC_HWIRQ_MAX 223 + +struct intG0Reg_st { + /* Interrupt G0 */ + u32 intr_type[7]; + u32 intr_polarity[7]; + u32 priority[7]; + u32 intr_mask[7]; + u32 g15_reserved_0[4]; +}; + +struct intG1Reg_st { + /* Interrupt G1 */ + u32 intr_clr[7]; + u32 masked_fiqs[7]; + u32 masked_irqs[7]; + u32 g21_reserved_0[10]; +#ifdef SUPPORT_IRQ_GRP_REG + u32 intr_group; +#else + u32 rsvd_31; +#endif +}; + +static struct sp_intctl { + __iomem struct intG0Reg_st *g0; + __iomem struct intG1Reg_st *g1; + int hwirq_start; + int hwirq_end; /* exclude */ + struct irq_domain *domain; + struct device_node *node; + spinlock_t lock; + int virq[2]; +} sp_intc; + +#define WORKAROUND_FOR_EDGE_TRIGGER_BUG 1 +#ifdef WORKAROUND_FOR_EDGE_TRIGGER_BUG +#define HW_IRQ_GPIO_INT0 120 +#define HW_IRQ_GPIO_INT7 127 +#define SP_IRQ_TYPE_EDGE_NONE 0x00 +#define SP_IRQ_TYPE_EDGE_RISING 0x01 +#define SP_IRQ_TYPE_EDGE_FALLING 0x02 +#define SP_IRQ_TYPE_EDGE_ACTIVE 0x80 +static char edge_trigger[SP_INTC_HWIRQ_MAX-SP_INTC_HWIRQ_MIN]; +#endif + +static void sp_intc_ack_irq(struct irq_data *data); +static void sp_intc_mask_irq(struct irq_data *data); +static void sp_intc_unmask_irq(struct irq_data *data); +static int sp_intc_set_type(struct irq_data *data, unsigned int flow_type); + +static struct irq_chip sp_intc_chip = { + .name = "sp_intc", + .irq_ack = sp_intc_ack_irq, + .irq_mask = sp_intc_mask_irq, + .irq_unmask = sp_intc_unmask_irq, + .irq_set_type = sp_intc_set_type, +}; + +static void sp_intc_ack_irq(struct irq_data *data) +{ + u32 idx, mask; + + if ((data->hwirq < sp_intc.hwirq_start) || (data->hwirq >= sp_intc.hwirq_end)) + return; + + idx = data->hwirq / 32; + mask = (1 << (data->hwirq % 32)); + + spin_lock(&sp_intc.lock); +#ifdef WORKAROUND_FOR_EDGE_TRIGGER_BUG + if (edge_trigger[data->hwirq] & (SP_IRQ_TYPE_EDGE_RISING|SP_IRQ_TYPE_EDGE_FALLING)) { + u32 trig_lvl = readl_relaxed(&sp_intc.g0->intr_polarity[idx]); + + if (edge_trigger[data->hwirq] == SP_IRQ_TYPE_EDGE_RISING) + trig_lvl |= mask; + else + trig_lvl &= ~mask; + + writel_relaxed(trig_lvl, &sp_intc.g0->intr_polarity[idx]); + edge_trigger[data->hwirq] |= SP_IRQ_TYPE_EDGE_ACTIVE; + } +#endif + writel_relaxed(mask, &sp_intc.g1->intr_clr[idx]); + spin_unlock(&sp_intc.lock); +} + +static void sp_intc_mask_irq(struct irq_data *data) +{ + u32 idx, mask; + + if ((data->hwirq < sp_intc.hwirq_start) || (data->hwirq >= sp_intc.hwirq_end)) + return; + + idx = data->hwirq / 32; + + spin_lock(&sp_intc.lock); + mask = readl_relaxed(&sp_intc.g0->intr_mask[idx]); + mask &= ~(1 << (data->hwirq % 32)); + writel_relaxed(mask, &sp_intc.g0->intr_mask[idx]); + spin_unlock(&sp_intc.lock); +} + +static void sp_intc_unmask_irq(struct irq_data *data) +{ + u32 idx, mask; + + if ((data->hwirq < sp_intc.hwirq_start) || (data->hwirq >= sp_intc.hwirq_end)) + return; + + idx = data->hwirq / 32; + spin_lock(&sp_intc.lock); + mask = readl_relaxed(&sp_intc.g0->intr_mask[idx]); + mask |= (1 << (data->hwirq % 32)); + writel_relaxed(mask, &sp_intc.g0->intr_mask[idx]); + spin_unlock(&sp_intc.lock); +} + +static int sp_intc_set_type(struct irq_data *data, unsigned int flow_type) +{ + u32 idx, mask; + u32 edge_type; /* 0=level, 1=edge */ + u32 trig_lvl; /* 0=high, 1=low */ + unsigned long flags; + + if ((data->hwirq < sp_intc.hwirq_start) || (data->hwirq >= sp_intc.hwirq_end)) + return -EBADR; + + /* update the chip/handler */ + if (flow_type & IRQ_TYPE_LEVEL_MASK) + irq_set_chip_handler_name_locked(data, &sp_intc_chip, + handle_level_irq, NULL); + else + irq_set_chip_handler_name_locked(data, &sp_intc_chip, + handle_edge_irq, NULL); + + idx = data->hwirq / 32; + + spin_lock_irqsave(&sp_intc.lock, flags); + + edge_type = readl_relaxed(&sp_intc.g0->intr_type[idx]); + trig_lvl = readl_relaxed(&sp_intc.g0->intr_polarity[idx]); + mask = (1 << (data->hwirq % 32)); + + switch (flow_type) { + case IRQ_TYPE_EDGE_RISING: +#ifdef WORKAROUND_FOR_EDGE_TRIGGER_BUG + if ((data->hwirq >= HW_IRQ_GPIO_INT0) && (data->hwirq <= HW_IRQ_GPIO_INT7)) { + edge_trigger[data->hwirq] = SP_IRQ_TYPE_EDGE_RISING; + writel_relaxed((edge_type & ~mask), &sp_intc.g0->intr_type[idx]); + } else { + writel_relaxed((edge_type | mask), &sp_intc.g0->intr_type[idx]); + } +#else + writel_relaxed((edge_type | mask), &sp_intc.g0->intr_type[idx]); +#endif + writel_relaxed((trig_lvl & ~mask), &sp_intc.g0->intr_polarity[idx]); + break; + case IRQ_TYPE_EDGE_FALLING: +#ifdef WORKAROUND_FOR_EDGE_TRIGGER_BUG + if ((data->hwirq >= HW_IRQ_GPIO_INT0) && (data->hwirq <= HW_IRQ_GPIO_INT7)) { + edge_trigger[data->hwirq] = SP_IRQ_TYPE_EDGE_FALLING; + writel_relaxed((edge_type & ~mask), &sp_intc.g0->intr_type[idx]); + } else { + writel_relaxed((edge_type | mask), &sp_intc.g0->intr_type[idx]); + } +#else + writel_relaxed((edge_type | mask), &sp_intc.g0->intr_type[idx]); +#endif + writel_relaxed((trig_lvl | mask), &sp_intc.g0->intr_polarity[idx]); + break; + case IRQ_TYPE_LEVEL_HIGH: +#ifdef WORKAROUND_FOR_EDGE_TRIGGER_BUG + edge_trigger[data->hwirq] = SP_IRQ_TYPE_EDGE_NONE; +#endif + writel_relaxed((edge_type & ~mask), &sp_intc.g0->intr_type[idx]); + writel_relaxed((trig_lvl & ~mask), &sp_intc.g0->intr_polarity[idx]); + break; + case IRQ_TYPE_LEVEL_LOW: +#ifdef WORKAROUND_FOR_EDGE_TRIGGER_BUG + edge_trigger[data->hwirq] = SP_IRQ_TYPE_EDGE_NONE; +#endif + writel_relaxed((edge_type & ~mask), &sp_intc.g0->intr_type[idx]); + writel_relaxed((trig_lvl | mask), &sp_intc.g0->intr_polarity[idx]); + break; + default: +#ifdef WORKAROUND_FOR_EDGE_TRIGGER_BUG + edge_trigger[data->hwirq] = SP_IRQ_TYPE_EDGE_NONE; +#endif + spin_unlock_irqrestore(&sp_intc.lock, flags); + pr_err("%s: type=%d\n", __func__, flow_type); + return -EBADR; + } + + spin_unlock_irqrestore(&sp_intc.lock, flags); + + return IRQ_SET_MASK_OK; +} + +/* prio=1 (normal), prio=0 (dedicated) */ +static void sp_intc_set_prio(u32 hwirq, u32 prio) +{ + u32 idx, mask; + + if ((hwirq < sp_intc.hwirq_start) || (hwirq >= sp_intc.hwirq_end)) + return; + + idx = hwirq / 32; + + spin_lock(&sp_intc.lock); + mask = readl_relaxed(&sp_intc.g0->priority[idx]); + if (prio) + mask |= (1 << (hwirq % 32)); + else + mask &= ~(1 << (hwirq % 32)); + writel_relaxed(mask, &sp_intc.g0->priority[idx]); + spin_unlock(&sp_intc.lock); +} + +/* return -1 if no interrupt # */ +static int sp_intc_get_ext0_irq(void) +{ + int hwirq, mask; + int i; + +#ifdef SUPPORT_IRQ_GRP_REG + mask = (readl_relaxed(&sp_intc.g1->intr_group) >> 8) & 0x7f; /* [14:8] for irq group */ + if (mask) { + i = fls(mask) - 1; +#else + for (i = 0; i < 7; i++) { +#endif + mask = readl_relaxed(&sp_intc.g1->masked_irqs[i]); + if (mask) { + hwirq = (i << 5) + fls(mask) - 1; + return hwirq; + } + } + return -1; +} + +static int sp_intc_get_ext1_irq(void) +{ + int hwirq, mask; + int i; + +#ifdef SUPPORT_IRQ_GRP_REG + mask = (readl_relaxed(&sp_intc.g1->intr_group) >> 0) & 0x7f; /* [6:0] for fiq group */ + if (mask) { + i = fls(mask) - 1; +#else + for (i = 0; i < 7; i++) { +#endif + mask = readl_relaxed(&sp_intc.g1->masked_fiqs[i]); + if (mask) { + hwirq = (i << 5) + fls(mask) - 1; + return hwirq; + } + } + return -1; +} + +static void sp_intc_handle_ext0_cascaded(struct irq_desc *desc) +{ + struct irq_chip *host_chip = irq_desc_get_chip(desc); + int hwirq; + + chained_irq_enter(host_chip, desc); + + while ((hwirq = sp_intc_get_ext0_irq()) >= 0) { +#ifdef WORKAROUND_FOR_EDGE_TRIGGER_BUG + if (edge_trigger[hwirq] & SP_IRQ_TYPE_EDGE_ACTIVE) { + u32 idx = hwirq / 32; + u32 trig_lvl = readl_relaxed(&sp_intc.g0->intr_polarity[idx]); + u32 mask = 1 << (hwirq % 32); + + edge_trigger[hwirq] &= ~SP_IRQ_TYPE_EDGE_ACTIVE; + if (edge_trigger[hwirq] == SP_IRQ_TYPE_EDGE_RISING) + trig_lvl &= ~mask; + else + trig_lvl |= mask; + + writel_relaxed(trig_lvl, &sp_intc.g0->intr_polarity[idx]); + } else + generic_handle_irq(irq_find_mapping(sp_intc.domain, (unsigned int)hwirq)); +#else + generic_handle_irq(irq_find_mapping(sp_intc.domain, (unsigned int)hwirq)); +#endif + } + + chained_irq_exit(host_chip, desc); +} + +static void sp_intc_handle_ext1_cascaded(struct irq_desc *desc) +{ + struct irq_chip *host_chip = irq_desc_get_chip(desc); + int hwirq; + + chained_irq_enter(host_chip, desc); + + while ((hwirq = sp_intc_get_ext1_irq()) >= 0) { +#ifdef WORKAROUND_FOR_EDGE_TRIGGER_BUG + if (edge_trigger[hwirq] & SP_IRQ_TYPE_EDGE_ACTIVE) { + u32 idx = hwirq / 32; + u32 trig_lvl = readl_relaxed(&sp_intc.g0->intr_polarity[idx]); + u32 mask = 1 << (hwirq % 32); + + edge_trigger[hwirq] &= ~SP_IRQ_TYPE_EDGE_ACTIVE; + if (edge_trigger[hwirq] == SP_IRQ_TYPE_EDGE_RISING) + trig_lvl &= ~mask; + else + trig_lvl |= mask; + + writel_relaxed(trig_lvl, &sp_intc.g0->intr_polarity[idx]); + } else + generic_handle_irq(irq_find_mapping(sp_intc.domain, (unsigned int)hwirq)); +#else + generic_handle_irq(irq_find_mapping(sp_intc.domain, (unsigned int)hwirq)); +#endif + } + + chained_irq_exit(host_chip, desc); +} + +static int sp_intc_handle_one_round(struct pt_regs *regs) +{ + int ret = -EINVAL; + int hwirq; + + while ((hwirq = sp_intc_get_ext0_irq()) >= 0) { + handle_domain_irq(sp_intc.domain, hwirq, regs); + ret = 0; + } + + return ret; +} + +/* 8388: level-triggered hwirq# may come slower than IRQ */ +#define SPURIOUS_RETRY 30 + +static void __exception_irq_entry sp_intc_handle_irq(struct pt_regs *regs) +{ + int err; + int first_int = 1; + int retry = 0; + + do { + err = sp_intc_handle_one_round(regs); + + if (!err) + first_int = 0; + + if (first_int && err) { /* spurious irq */ + if (retry++ < SPURIOUS_RETRY) + continue; + } + } while (!err); +} + +static int sp_intc_irq_domain_map(struct irq_domain *domain, unsigned int irq, + irq_hw_number_t hwirq) +{ + irq_set_chip_and_handler(irq, &sp_intc_chip, handle_level_irq); + irq_set_chip_data(irq, &sp_intc_chip); + irq_set_noprobe(irq); + + return 0; +} + +static void __init sp_intc_chip_init(int hwirq_start, int hwirq_end, + void __iomem *base0, void __iomem *base1) +{ + int i; + + sp_intc.g0 = base0; + sp_intc.g1 = base1; + sp_intc.hwirq_start = hwirq_start; + sp_intc.hwirq_end = hwirq_end; + + for (i = 0; i < 7; i++) { + /* all mask */ + writel_relaxed(0, &sp_intc.g0->intr_mask[i]); + /* all edge */ + writel_relaxed(~0, &sp_intc.g0->intr_type[i]); + /* all high-active */ + writel_relaxed(0, &sp_intc.g0->intr_polarity[i]); + /* all irq */ + writel_relaxed(~0, &sp_intc.g0->priority[i]); + /* all clear */ + writel_relaxed(~0, &sp_intc.g1->intr_clr[i]); + } +} + +int sp_intc_xlate_of(struct irq_domain *d, struct device_node *node, + const u32 *intspec, unsigned int intsize, + irq_hw_number_t *out_hwirq, unsigned int *out_type) +{ + int ret = 0; + u32 ext_num; + + if (WARN_ON(intsize < 2)) + return -EINVAL; + + *out_hwirq = intspec[0]; + *out_type = intspec[1] & IRQ_TYPE_SENSE_MASK; + ext_num = (intspec[1] & SP_INTC_EXT_INT_MASK) >> SP_INTC_EXT_INT_SHFIT; + + /* set ext_int1 to high priority */ + if (ext_num != 1) + ext_num = 0; + + if (ext_num) + sp_intc_set_prio(*out_hwirq, 0); /* priority=0 */ + + return ret; +} + +static const struct irq_domain_ops sp_intc_dm_ops = { + .xlate = sp_intc_xlate_of, + .map = sp_intc_irq_domain_map, +}; + +int __init sp_intc_init(int hwirq_start, int irqs, void __iomem *base0, void __iomem *base1) +{ + sp_intc_chip_init(hwirq_start, hwirq_start + irqs, base0, base1); + + sp_intc.domain = irq_domain_add_legacy(NULL, irqs, hwirq_start, + sp_intc.hwirq_start, &sp_intc_dm_ops, &sp_intc); + if (!sp_intc.domain) + panic("%s: unable to create legacy domain\n", __func__); + + set_handle_irq(sp_intc_handle_irq); + + return 0; +} + +#if defined(CONFIG_SOC_SP7021) +static cpumask_t *u2cpumask(u32 mask, cpumask_t *cpumask) +{ + unsigned int i; + + for (i = 0; i < 4; i++) { + if (mask & (1 << i)) + cpumask_set_cpu(i, cpumask); + else + cpumask_clear_cpu(i, cpumask); + } + return cpumask; +} + +static int __init sp_intc_ext_adjust(void) +{ + u32 mask; + static cpumask_t cpumask; + struct device_node *node = sp_intc.node; + + if (num_online_cpus() <= 1) { + pr_info("single core: skip ext adjust\n"); + return 0; + } + + cpumask = CPU_MASK_NONE; + if (!of_property_read_u32(node, "ext0-mask", &mask)) { + pr_info("%d: ext0-mask=0x%x\n", sp_intc.virq[0], mask); + if (irq_set_affinity(sp_intc.virq[0], u2cpumask(mask, &cpumask))) + pr_err("failed to set ext0 cpumask=0x%x\n", mask); + } + + cpumask = CPU_MASK_NONE; + if (!of_property_read_u32(node, "ext1-mask", &mask)) { + pr_info("%d: ext1-mask=0x%x\n", sp_intc.virq[1], mask); + if (irq_set_affinity(sp_intc.virq[1], u2cpumask(mask, &cpumask))) + pr_err("failed to set ext1 cpumask=0x%x\n", mask); + } + + return 0; +} +arch_initcall(sp_intc_ext_adjust) +#endif + +#ifdef CONFIG_OF +int __init sp_intc_init_dt(struct device_node *node, struct device_node *parent) +{ + void __iomem *base0, *base1; + + base0 = of_iomap(node, 0); + if (!base0) + panic("unable to map sp-intc base 0\n"); + + base1 = of_iomap(node, 1); + if (!base1) + panic("unable to map sp-intc base 1\n"); + + sp_intc.node = node; + + sp_intc_chip_init(SP_INTC_HWIRQ_MIN, SP_INTC_HWIRQ_MAX, base0, base1); + + sp_intc.domain = irq_domain_add_linear(node, sp_intc.hwirq_end - sp_intc.hwirq_start, + &sp_intc_dm_ops, &sp_intc); + if (!sp_intc.domain) + panic("%s: unable to create linear domain\n", __func__); + + spin_lock_init(&sp_intc.lock); + + if (parent) { + sp_intc.virq[0] = irq_of_parse_and_map(node, 0); + if (sp_intc.virq[0]) { + irq_set_chained_handler_and_data(sp_intc.virq[0], + sp_intc_handle_ext0_cascaded, &sp_intc); + } else { + panic("%s: missed ext0 irq in DT\n", __func__); + } + + sp_intc.virq[1] = irq_of_parse_and_map(node, 1); + if (sp_intc.virq[1]) { + irq_set_chained_handler_and_data(sp_intc.virq[1], + sp_intc_handle_ext1_cascaded, &sp_intc); + } else { + panic("%s: missed ext1 irq in DT\n", __func__); + } + } else { + set_handle_irq(sp_intc_handle_irq); + } + + return 0; +} +IRQCHIP_DECLARE(sp_intc, "sunplus,sp7021-intc", sp_intc_init_dt); +#endif + +MODULE_AUTHOR("Qin Jian <qinjian@cqplus1.com>"); +MODULE_DESCRIPTION("Sunplus SP7021 Interrupt Controller Driver"); +MODULE_LICENSE("GPL v2"); -- 2.33.1
| |