| // SPDX-License-Identifier: GPL-2.0 | 
 | // Copyright (C) 2018 Hangzhou C-SKY Microsystems co.,ltd. | 
 |  | 
 | #include <linux/kernel.h> | 
 | #include <linux/init.h> | 
 | #include <linux/of.h> | 
 | #include <linux/of_address.h> | 
 | #include <linux/module.h> | 
 | #include <linux/irqdomain.h> | 
 | #include <linux/irqchip.h> | 
 | #include <linux/irq.h> | 
 | #include <linux/interrupt.h> | 
 | #include <linux/io.h> | 
 | #include <asm/irq.h> | 
 |  | 
 | #define INTC_IRQS		64 | 
 |  | 
 | #define CK_INTC_ICR		0x00 | 
 | #define CK_INTC_PEN31_00	0x14 | 
 | #define CK_INTC_PEN63_32	0x2c | 
 | #define CK_INTC_NEN31_00	0x10 | 
 | #define CK_INTC_NEN63_32	0x28 | 
 | #define CK_INTC_SOURCE		0x40 | 
 | #define CK_INTC_DUAL_BASE	0x100 | 
 |  | 
 | #define GX_INTC_PEN31_00	0x00 | 
 | #define GX_INTC_PEN63_32	0x04 | 
 | #define GX_INTC_NEN31_00	0x40 | 
 | #define GX_INTC_NEN63_32	0x44 | 
 | #define GX_INTC_NMASK31_00	0x50 | 
 | #define GX_INTC_NMASK63_32	0x54 | 
 | #define GX_INTC_SOURCE		0x60 | 
 |  | 
 | static void __iomem *reg_base; | 
 | static struct irq_domain *root_domain; | 
 |  | 
 | static int nr_irq = INTC_IRQS; | 
 |  | 
 | /* | 
 |  * When controller support pulse signal, the PEN_reg will hold on signal | 
 |  * without software trigger. | 
 |  * | 
 |  * So, to support pulse signal we need to clear IFR_reg and the address of | 
 |  * IFR_offset is NEN_offset - 8. | 
 |  */ | 
 | static void irq_ck_mask_set_bit(struct irq_data *d) | 
 | { | 
 | 	struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); | 
 | 	struct irq_chip_type *ct = irq_data_get_chip_type(d); | 
 | 	unsigned long ifr = ct->regs.mask - 8; | 
 | 	u32 mask = d->mask; | 
 |  | 
 | 	irq_gc_lock(gc); | 
 | 	*ct->mask_cache |= mask; | 
 | 	irq_reg_writel(gc, *ct->mask_cache, ct->regs.mask); | 
 | 	irq_reg_writel(gc, irq_reg_readl(gc, ifr) & ~mask, ifr); | 
 | 	irq_gc_unlock(gc); | 
 | } | 
 |  | 
 | static void __init ck_set_gc(struct device_node *node, void __iomem *reg_base, | 
 | 			     u32 mask_reg, u32 irq_base) | 
 | { | 
 | 	struct irq_chip_generic *gc; | 
 |  | 
 | 	gc = irq_get_domain_generic_chip(root_domain, irq_base); | 
 | 	gc->reg_base = reg_base; | 
 | 	gc->chip_types[0].regs.mask = mask_reg; | 
 | 	gc->chip_types[0].chip.irq_mask = irq_gc_mask_clr_bit; | 
 | 	gc->chip_types[0].chip.irq_unmask = irq_gc_mask_set_bit; | 
 |  | 
 | 	if (of_find_property(node, "csky,support-pulse-signal", NULL)) | 
 | 		gc->chip_types[0].chip.irq_unmask = irq_ck_mask_set_bit; | 
 | } | 
 |  | 
 | static inline u32 build_channel_val(u32 idx, u32 magic) | 
 | { | 
 | 	u32 res; | 
 |  | 
 | 	/* | 
 | 	 * Set the same index for each channel | 
 | 	 */ | 
 | 	res = idx | (idx << 8) | (idx << 16) | (idx << 24); | 
 |  | 
 | 	/* | 
 | 	 * Set the channel magic number in descending order. | 
 | 	 * The magic is 0x00010203 for ck-intc | 
 | 	 * The magic is 0x03020100 for gx6605s-intc | 
 | 	 */ | 
 | 	return res | magic; | 
 | } | 
 |  | 
 | static inline void setup_irq_channel(u32 magic, void __iomem *reg_addr) | 
 | { | 
 | 	u32 i; | 
 |  | 
 | 	/* Setup 64 channel slots */ | 
 | 	for (i = 0; i < INTC_IRQS; i += 4) | 
 | 		writel(build_channel_val(i, magic), reg_addr + i); | 
 | } | 
 |  | 
 | static int __init | 
 | ck_intc_init_comm(struct device_node *node, struct device_node *parent) | 
 | { | 
 | 	int ret; | 
 |  | 
 | 	if (parent) { | 
 | 		pr_err("C-SKY Intc not a root irq controller\n"); | 
 | 		return -EINVAL; | 
 | 	} | 
 |  | 
 | 	reg_base = of_iomap(node, 0); | 
 | 	if (!reg_base) { | 
 | 		pr_err("C-SKY Intc unable to map: %p.\n", node); | 
 | 		return -EINVAL; | 
 | 	} | 
 |  | 
 | 	root_domain = irq_domain_add_linear(node, nr_irq, | 
 | 					    &irq_generic_chip_ops, NULL); | 
 | 	if (!root_domain) { | 
 | 		pr_err("C-SKY Intc irq_domain_add failed.\n"); | 
 | 		return -ENOMEM; | 
 | 	} | 
 |  | 
 | 	ret = irq_alloc_domain_generic_chips(root_domain, 32, 1, | 
 | 			"csky_intc", handle_level_irq, | 
 | 			IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_NOAUTOEN, 0, 0); | 
 | 	if (ret) { | 
 | 		pr_err("C-SKY Intc irq_alloc_gc failed.\n"); | 
 | 		return -ENOMEM; | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static inline bool handle_irq_perbit(struct pt_regs *regs, u32 hwirq, | 
 | 				     u32 irq_base) | 
 | { | 
 | 	if (hwirq == 0) | 
 | 		return 0; | 
 |  | 
 | 	handle_domain_irq(root_domain, irq_base + __fls(hwirq), regs); | 
 |  | 
 | 	return 1; | 
 | } | 
 |  | 
 | /* gx6605s 64 irqs interrupt controller */ | 
 | static void gx_irq_handler(struct pt_regs *regs) | 
 | { | 
 | 	bool ret; | 
 |  | 
 | retry: | 
 | 	ret = handle_irq_perbit(regs, | 
 | 			readl(reg_base + GX_INTC_PEN63_32), 32); | 
 | 	if (ret) | 
 | 		goto retry; | 
 |  | 
 | 	ret = handle_irq_perbit(regs, | 
 | 			readl(reg_base + GX_INTC_PEN31_00), 0); | 
 | 	if (ret) | 
 | 		goto retry; | 
 | } | 
 |  | 
 | static int __init | 
 | gx_intc_init(struct device_node *node, struct device_node *parent) | 
 | { | 
 | 	int ret; | 
 |  | 
 | 	ret = ck_intc_init_comm(node, parent); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	/* | 
 | 	 * Initial enable reg to disable all interrupts | 
 | 	 */ | 
 | 	writel(0x0, reg_base + GX_INTC_NEN31_00); | 
 | 	writel(0x0, reg_base + GX_INTC_NEN63_32); | 
 |  | 
 | 	/* | 
 | 	 * Initial mask reg with all unmasked, because we only use enalbe reg | 
 | 	 */ | 
 | 	writel(0x0, reg_base + GX_INTC_NMASK31_00); | 
 | 	writel(0x0, reg_base + GX_INTC_NMASK63_32); | 
 |  | 
 | 	setup_irq_channel(0x03020100, reg_base + GX_INTC_SOURCE); | 
 |  | 
 | 	ck_set_gc(node, reg_base, GX_INTC_NEN31_00, 0); | 
 | 	ck_set_gc(node, reg_base, GX_INTC_NEN63_32, 32); | 
 |  | 
 | 	set_handle_irq(gx_irq_handler); | 
 |  | 
 | 	return 0; | 
 | } | 
 | IRQCHIP_DECLARE(csky_gx6605s_intc, "csky,gx6605s-intc", gx_intc_init); | 
 |  | 
 | /* | 
 |  * C-SKY simple 64 irqs interrupt controller, dual-together could support 128 | 
 |  * irqs. | 
 |  */ | 
 | static void ck_irq_handler(struct pt_regs *regs) | 
 | { | 
 | 	bool ret; | 
 | 	void __iomem *reg_pen_lo = reg_base + CK_INTC_PEN31_00; | 
 | 	void __iomem *reg_pen_hi = reg_base + CK_INTC_PEN63_32; | 
 |  | 
 | retry: | 
 | 	/* handle 0 - 63 irqs */ | 
 | 	ret = handle_irq_perbit(regs, readl(reg_pen_hi), 32); | 
 | 	if (ret) | 
 | 		goto retry; | 
 |  | 
 | 	ret = handle_irq_perbit(regs, readl(reg_pen_lo), 0); | 
 | 	if (ret) | 
 | 		goto retry; | 
 |  | 
 | 	if (nr_irq == INTC_IRQS) | 
 | 		return; | 
 |  | 
 | 	/* handle 64 - 127 irqs */ | 
 | 	ret = handle_irq_perbit(regs, | 
 | 			readl(reg_pen_hi + CK_INTC_DUAL_BASE), 96); | 
 | 	if (ret) | 
 | 		goto retry; | 
 |  | 
 | 	ret = handle_irq_perbit(regs, | 
 | 			readl(reg_pen_lo + CK_INTC_DUAL_BASE), 64); | 
 | 	if (ret) | 
 | 		goto retry; | 
 | } | 
 |  | 
 | static int __init | 
 | ck_intc_init(struct device_node *node, struct device_node *parent) | 
 | { | 
 | 	int ret; | 
 |  | 
 | 	ret = ck_intc_init_comm(node, parent); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	/* Initial enable reg to disable all interrupts */ | 
 | 	writel(0, reg_base + CK_INTC_NEN31_00); | 
 | 	writel(0, reg_base + CK_INTC_NEN63_32); | 
 |  | 
 | 	/* Enable irq intc */ | 
 | 	writel(BIT(31), reg_base + CK_INTC_ICR); | 
 |  | 
 | 	ck_set_gc(node, reg_base, CK_INTC_NEN31_00, 0); | 
 | 	ck_set_gc(node, reg_base, CK_INTC_NEN63_32, 32); | 
 |  | 
 | 	setup_irq_channel(0x00010203, reg_base + CK_INTC_SOURCE); | 
 |  | 
 | 	set_handle_irq(ck_irq_handler); | 
 |  | 
 | 	return 0; | 
 | } | 
 | IRQCHIP_DECLARE(ck_intc, "csky,apb-intc", ck_intc_init); | 
 |  | 
 | static int __init | 
 | ck_dual_intc_init(struct device_node *node, struct device_node *parent) | 
 | { | 
 | 	int ret; | 
 |  | 
 | 	/* dual-apb-intc up to 128 irq sources*/ | 
 | 	nr_irq = INTC_IRQS * 2; | 
 |  | 
 | 	ret = ck_intc_init(node, parent); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	/* Initial enable reg to disable all interrupts */ | 
 | 	writel(0, reg_base + CK_INTC_NEN31_00 + CK_INTC_DUAL_BASE); | 
 | 	writel(0, reg_base + CK_INTC_NEN63_32 + CK_INTC_DUAL_BASE); | 
 |  | 
 | 	ck_set_gc(node, reg_base + CK_INTC_DUAL_BASE, CK_INTC_NEN31_00, 64); | 
 | 	ck_set_gc(node, reg_base + CK_INTC_DUAL_BASE, CK_INTC_NEN63_32, 96); | 
 |  | 
 | 	setup_irq_channel(0x00010203, | 
 | 			  reg_base + CK_INTC_SOURCE + CK_INTC_DUAL_BASE); | 
 |  | 
 | 	return 0; | 
 | } | 
 | IRQCHIP_DECLARE(ck_dual_intc, "csky,dual-apb-intc", ck_dual_intc_init); |