| // SPDX-License-Identifier: GPL-2.0 | 
 | /* | 
 |  *  Copyright (C) 2019, Jiaxun Yang <jiaxun.yang@flygoat.com> | 
 |  *  Loongson-1 platform IRQ support | 
 |  */ | 
 |  | 
 | #include <linux/errno.h> | 
 | #include <linux/init.h> | 
 | #include <linux/types.h> | 
 | #include <linux/interrupt.h> | 
 | #include <linux/ioport.h> | 
 | #include <linux/irqchip.h> | 
 | #include <linux/of_address.h> | 
 | #include <linux/of_irq.h> | 
 | #include <linux/io.h> | 
 | #include <linux/irqchip/chained_irq.h> | 
 |  | 
 | #define LS_REG_INTC_STATUS	0x00 | 
 | #define LS_REG_INTC_EN	0x04 | 
 | #define LS_REG_INTC_SET	0x08 | 
 | #define LS_REG_INTC_CLR	0x0c | 
 | #define LS_REG_INTC_POL	0x10 | 
 | #define LS_REG_INTC_EDGE	0x14 | 
 |  | 
 | /** | 
 |  * struct ls1x_intc_priv - private ls1x-intc data. | 
 |  * @domain:		IRQ domain. | 
 |  * @intc_base:	IO Base of intc registers. | 
 |  */ | 
 |  | 
 | struct ls1x_intc_priv { | 
 | 	struct irq_domain	*domain; | 
 | 	void __iomem		*intc_base; | 
 | }; | 
 |  | 
 |  | 
 | static void ls1x_chained_handle_irq(struct irq_desc *desc) | 
 | { | 
 | 	struct ls1x_intc_priv *priv = irq_desc_get_handler_data(desc); | 
 | 	struct irq_chip *chip = irq_desc_get_chip(desc); | 
 | 	u32 pending; | 
 |  | 
 | 	chained_irq_enter(chip, desc); | 
 | 	pending = readl(priv->intc_base + LS_REG_INTC_STATUS) & | 
 | 			readl(priv->intc_base + LS_REG_INTC_EN); | 
 |  | 
 | 	if (!pending) | 
 | 		spurious_interrupt(); | 
 |  | 
 | 	while (pending) { | 
 | 		int bit = __ffs(pending); | 
 |  | 
 | 		generic_handle_domain_irq(priv->domain, bit); | 
 | 		pending &= ~BIT(bit); | 
 | 	} | 
 |  | 
 | 	chained_irq_exit(chip, desc); | 
 | } | 
 |  | 
 | static void ls_intc_set_bit(struct irq_chip_generic *gc, | 
 | 							unsigned int offset, | 
 | 							u32 mask, bool set) | 
 | { | 
 | 	if (set) | 
 | 		writel(readl(gc->reg_base + offset) | mask, | 
 | 		gc->reg_base + offset); | 
 | 	else | 
 | 		writel(readl(gc->reg_base + offset) & ~mask, | 
 | 		gc->reg_base + offset); | 
 | } | 
 |  | 
 | static int ls_intc_set_type(struct irq_data *data, unsigned int type) | 
 | { | 
 | 	struct irq_chip_generic *gc = irq_data_get_irq_chip_data(data); | 
 | 	u32 mask = data->mask; | 
 |  | 
 | 	switch (type) { | 
 | 	case IRQ_TYPE_LEVEL_HIGH: | 
 | 		ls_intc_set_bit(gc, LS_REG_INTC_EDGE, mask, false); | 
 | 		ls_intc_set_bit(gc, LS_REG_INTC_POL, mask, true); | 
 | 		break; | 
 | 	case IRQ_TYPE_LEVEL_LOW: | 
 | 		ls_intc_set_bit(gc, LS_REG_INTC_EDGE, mask, false); | 
 | 		ls_intc_set_bit(gc, LS_REG_INTC_POL, mask, false); | 
 | 		break; | 
 | 	case IRQ_TYPE_EDGE_RISING: | 
 | 		ls_intc_set_bit(gc, LS_REG_INTC_EDGE, mask, true); | 
 | 		ls_intc_set_bit(gc, LS_REG_INTC_POL, mask, true); | 
 | 		break; | 
 | 	case IRQ_TYPE_EDGE_FALLING: | 
 | 		ls_intc_set_bit(gc, LS_REG_INTC_EDGE, mask, true); | 
 | 		ls_intc_set_bit(gc, LS_REG_INTC_POL, mask, false); | 
 | 		break; | 
 | 	default: | 
 | 		return -EINVAL; | 
 | 	} | 
 |  | 
 | 	irqd_set_trigger_type(data, type); | 
 | 	return irq_setup_alt_chip(data, type); | 
 | } | 
 |  | 
 |  | 
 | static int __init ls1x_intc_of_init(struct device_node *node, | 
 | 				       struct device_node *parent) | 
 | { | 
 | 	struct irq_chip_generic *gc; | 
 | 	struct irq_chip_type *ct; | 
 | 	struct ls1x_intc_priv *priv; | 
 | 	int parent_irq, err = 0; | 
 |  | 
 | 	priv = kzalloc(sizeof(*priv), GFP_KERNEL); | 
 | 	if (!priv) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	priv->intc_base = of_iomap(node, 0); | 
 | 	if (!priv->intc_base) { | 
 | 		err = -ENODEV; | 
 | 		goto out_free_priv; | 
 | 	} | 
 |  | 
 | 	parent_irq = irq_of_parse_and_map(node, 0); | 
 | 	if (!parent_irq) { | 
 | 		pr_err("ls1x-irq: unable to get parent irq\n"); | 
 | 		err =  -ENODEV; | 
 | 		goto out_iounmap; | 
 | 	} | 
 |  | 
 | 	/* Set up an IRQ domain */ | 
 | 	priv->domain = irq_domain_add_linear(node, 32, &irq_generic_chip_ops, | 
 | 					     NULL); | 
 | 	if (!priv->domain) { | 
 | 		pr_err("ls1x-irq: cannot add IRQ domain\n"); | 
 | 		err = -ENOMEM; | 
 | 		goto out_iounmap; | 
 | 	} | 
 |  | 
 | 	err = irq_alloc_domain_generic_chips(priv->domain, 32, 2, | 
 | 		node->full_name, handle_level_irq, | 
 | 		IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_NOAUTOEN, 0, | 
 | 		IRQ_GC_INIT_MASK_CACHE); | 
 | 	if (err) { | 
 | 		pr_err("ls1x-irq: unable to register IRQ domain\n"); | 
 | 		goto out_free_domain; | 
 | 	} | 
 |  | 
 | 	/* Mask all irqs */ | 
 | 	writel(0x0, priv->intc_base + LS_REG_INTC_EN); | 
 |  | 
 | 	/* Ack all irqs */ | 
 | 	writel(0xffffffff, priv->intc_base + LS_REG_INTC_CLR); | 
 |  | 
 | 	/* Set all irqs to high level triggered */ | 
 | 	writel(0xffffffff, priv->intc_base + LS_REG_INTC_POL); | 
 |  | 
 | 	gc = irq_get_domain_generic_chip(priv->domain, 0); | 
 |  | 
 | 	gc->reg_base = priv->intc_base; | 
 |  | 
 | 	ct = gc->chip_types; | 
 | 	ct[0].type = IRQ_TYPE_LEVEL_MASK; | 
 | 	ct[0].regs.mask = LS_REG_INTC_EN; | 
 | 	ct[0].regs.ack = LS_REG_INTC_CLR; | 
 | 	ct[0].chip.irq_unmask = irq_gc_mask_set_bit; | 
 | 	ct[0].chip.irq_mask = irq_gc_mask_clr_bit; | 
 | 	ct[0].chip.irq_ack = irq_gc_ack_set_bit; | 
 | 	ct[0].chip.irq_set_type = ls_intc_set_type; | 
 | 	ct[0].handler = handle_level_irq; | 
 |  | 
 | 	ct[1].type = IRQ_TYPE_EDGE_BOTH; | 
 | 	ct[1].regs.mask = LS_REG_INTC_EN; | 
 | 	ct[1].regs.ack = LS_REG_INTC_CLR; | 
 | 	ct[1].chip.irq_unmask = irq_gc_mask_set_bit; | 
 | 	ct[1].chip.irq_mask = irq_gc_mask_clr_bit; | 
 | 	ct[1].chip.irq_ack = irq_gc_ack_set_bit; | 
 | 	ct[1].chip.irq_set_type = ls_intc_set_type; | 
 | 	ct[1].handler = handle_edge_irq; | 
 |  | 
 | 	irq_set_chained_handler_and_data(parent_irq, | 
 | 		ls1x_chained_handle_irq, priv); | 
 |  | 
 | 	return 0; | 
 |  | 
 | out_free_domain: | 
 | 	irq_domain_remove(priv->domain); | 
 | out_iounmap: | 
 | 	iounmap(priv->intc_base); | 
 | out_free_priv: | 
 | 	kfree(priv); | 
 |  | 
 | 	return err; | 
 | } | 
 |  | 
 | IRQCHIP_DECLARE(ls1x_intc, "loongson,ls1x-intc", ls1x_intc_of_init); |