162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (C) 2019, Jiaxun Yang <jiaxun.yang@flygoat.com> 462306a36Sopenharmony_ci * Loongson-1 platform IRQ support 562306a36Sopenharmony_ci */ 662306a36Sopenharmony_ci 762306a36Sopenharmony_ci#include <linux/errno.h> 862306a36Sopenharmony_ci#include <linux/init.h> 962306a36Sopenharmony_ci#include <linux/types.h> 1062306a36Sopenharmony_ci#include <linux/interrupt.h> 1162306a36Sopenharmony_ci#include <linux/ioport.h> 1262306a36Sopenharmony_ci#include <linux/irqchip.h> 1362306a36Sopenharmony_ci#include <linux/of_address.h> 1462306a36Sopenharmony_ci#include <linux/of_irq.h> 1562306a36Sopenharmony_ci#include <linux/io.h> 1662306a36Sopenharmony_ci#include <linux/irqchip/chained_irq.h> 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_ci#define LS_REG_INTC_STATUS 0x00 1962306a36Sopenharmony_ci#define LS_REG_INTC_EN 0x04 2062306a36Sopenharmony_ci#define LS_REG_INTC_SET 0x08 2162306a36Sopenharmony_ci#define LS_REG_INTC_CLR 0x0c 2262306a36Sopenharmony_ci#define LS_REG_INTC_POL 0x10 2362306a36Sopenharmony_ci#define LS_REG_INTC_EDGE 0x14 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ci/** 2662306a36Sopenharmony_ci * struct ls1x_intc_priv - private ls1x-intc data. 2762306a36Sopenharmony_ci * @domain: IRQ domain. 2862306a36Sopenharmony_ci * @intc_base: IO Base of intc registers. 2962306a36Sopenharmony_ci */ 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_cistruct ls1x_intc_priv { 3262306a36Sopenharmony_ci struct irq_domain *domain; 3362306a36Sopenharmony_ci void __iomem *intc_base; 3462306a36Sopenharmony_ci}; 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_cistatic void ls1x_chained_handle_irq(struct irq_desc *desc) 3862306a36Sopenharmony_ci{ 3962306a36Sopenharmony_ci struct ls1x_intc_priv *priv = irq_desc_get_handler_data(desc); 4062306a36Sopenharmony_ci struct irq_chip *chip = irq_desc_get_chip(desc); 4162306a36Sopenharmony_ci u32 pending; 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_ci chained_irq_enter(chip, desc); 4462306a36Sopenharmony_ci pending = readl(priv->intc_base + LS_REG_INTC_STATUS) & 4562306a36Sopenharmony_ci readl(priv->intc_base + LS_REG_INTC_EN); 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_ci if (!pending) 4862306a36Sopenharmony_ci spurious_interrupt(); 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_ci while (pending) { 5162306a36Sopenharmony_ci int bit = __ffs(pending); 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_ci generic_handle_domain_irq(priv->domain, bit); 5462306a36Sopenharmony_ci pending &= ~BIT(bit); 5562306a36Sopenharmony_ci } 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci chained_irq_exit(chip, desc); 5862306a36Sopenharmony_ci} 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_cistatic void ls_intc_set_bit(struct irq_chip_generic *gc, 6162306a36Sopenharmony_ci unsigned int offset, 6262306a36Sopenharmony_ci u32 mask, bool set) 6362306a36Sopenharmony_ci{ 6462306a36Sopenharmony_ci if (set) 6562306a36Sopenharmony_ci writel(readl(gc->reg_base + offset) | mask, 6662306a36Sopenharmony_ci gc->reg_base + offset); 6762306a36Sopenharmony_ci else 6862306a36Sopenharmony_ci writel(readl(gc->reg_base + offset) & ~mask, 6962306a36Sopenharmony_ci gc->reg_base + offset); 7062306a36Sopenharmony_ci} 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_cistatic int ls_intc_set_type(struct irq_data *data, unsigned int type) 7362306a36Sopenharmony_ci{ 7462306a36Sopenharmony_ci struct irq_chip_generic *gc = irq_data_get_irq_chip_data(data); 7562306a36Sopenharmony_ci u32 mask = data->mask; 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_ci switch (type) { 7862306a36Sopenharmony_ci case IRQ_TYPE_LEVEL_HIGH: 7962306a36Sopenharmony_ci ls_intc_set_bit(gc, LS_REG_INTC_EDGE, mask, false); 8062306a36Sopenharmony_ci ls_intc_set_bit(gc, LS_REG_INTC_POL, mask, true); 8162306a36Sopenharmony_ci break; 8262306a36Sopenharmony_ci case IRQ_TYPE_LEVEL_LOW: 8362306a36Sopenharmony_ci ls_intc_set_bit(gc, LS_REG_INTC_EDGE, mask, false); 8462306a36Sopenharmony_ci ls_intc_set_bit(gc, LS_REG_INTC_POL, mask, false); 8562306a36Sopenharmony_ci break; 8662306a36Sopenharmony_ci case IRQ_TYPE_EDGE_RISING: 8762306a36Sopenharmony_ci ls_intc_set_bit(gc, LS_REG_INTC_EDGE, mask, true); 8862306a36Sopenharmony_ci ls_intc_set_bit(gc, LS_REG_INTC_POL, mask, true); 8962306a36Sopenharmony_ci break; 9062306a36Sopenharmony_ci case IRQ_TYPE_EDGE_FALLING: 9162306a36Sopenharmony_ci ls_intc_set_bit(gc, LS_REG_INTC_EDGE, mask, true); 9262306a36Sopenharmony_ci ls_intc_set_bit(gc, LS_REG_INTC_POL, mask, false); 9362306a36Sopenharmony_ci break; 9462306a36Sopenharmony_ci default: 9562306a36Sopenharmony_ci return -EINVAL; 9662306a36Sopenharmony_ci } 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_ci irqd_set_trigger_type(data, type); 9962306a36Sopenharmony_ci return irq_setup_alt_chip(data, type); 10062306a36Sopenharmony_ci} 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_cistatic int __init ls1x_intc_of_init(struct device_node *node, 10462306a36Sopenharmony_ci struct device_node *parent) 10562306a36Sopenharmony_ci{ 10662306a36Sopenharmony_ci struct irq_chip_generic *gc; 10762306a36Sopenharmony_ci struct irq_chip_type *ct; 10862306a36Sopenharmony_ci struct ls1x_intc_priv *priv; 10962306a36Sopenharmony_ci int parent_irq, err = 0; 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci priv = kzalloc(sizeof(*priv), GFP_KERNEL); 11262306a36Sopenharmony_ci if (!priv) 11362306a36Sopenharmony_ci return -ENOMEM; 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci priv->intc_base = of_iomap(node, 0); 11662306a36Sopenharmony_ci if (!priv->intc_base) { 11762306a36Sopenharmony_ci err = -ENODEV; 11862306a36Sopenharmony_ci goto out_free_priv; 11962306a36Sopenharmony_ci } 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci parent_irq = irq_of_parse_and_map(node, 0); 12262306a36Sopenharmony_ci if (!parent_irq) { 12362306a36Sopenharmony_ci pr_err("ls1x-irq: unable to get parent irq\n"); 12462306a36Sopenharmony_ci err = -ENODEV; 12562306a36Sopenharmony_ci goto out_iounmap; 12662306a36Sopenharmony_ci } 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci /* Set up an IRQ domain */ 12962306a36Sopenharmony_ci priv->domain = irq_domain_add_linear(node, 32, &irq_generic_chip_ops, 13062306a36Sopenharmony_ci NULL); 13162306a36Sopenharmony_ci if (!priv->domain) { 13262306a36Sopenharmony_ci pr_err("ls1x-irq: cannot add IRQ domain\n"); 13362306a36Sopenharmony_ci err = -ENOMEM; 13462306a36Sopenharmony_ci goto out_iounmap; 13562306a36Sopenharmony_ci } 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci err = irq_alloc_domain_generic_chips(priv->domain, 32, 2, 13862306a36Sopenharmony_ci node->full_name, handle_level_irq, 13962306a36Sopenharmony_ci IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_NOAUTOEN, 0, 14062306a36Sopenharmony_ci IRQ_GC_INIT_MASK_CACHE); 14162306a36Sopenharmony_ci if (err) { 14262306a36Sopenharmony_ci pr_err("ls1x-irq: unable to register IRQ domain\n"); 14362306a36Sopenharmony_ci goto out_free_domain; 14462306a36Sopenharmony_ci } 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci /* Mask all irqs */ 14762306a36Sopenharmony_ci writel(0x0, priv->intc_base + LS_REG_INTC_EN); 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_ci /* Ack all irqs */ 15062306a36Sopenharmony_ci writel(0xffffffff, priv->intc_base + LS_REG_INTC_CLR); 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_ci /* Set all irqs to high level triggered */ 15362306a36Sopenharmony_ci writel(0xffffffff, priv->intc_base + LS_REG_INTC_POL); 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci gc = irq_get_domain_generic_chip(priv->domain, 0); 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci gc->reg_base = priv->intc_base; 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci ct = gc->chip_types; 16062306a36Sopenharmony_ci ct[0].type = IRQ_TYPE_LEVEL_MASK; 16162306a36Sopenharmony_ci ct[0].regs.mask = LS_REG_INTC_EN; 16262306a36Sopenharmony_ci ct[0].regs.ack = LS_REG_INTC_CLR; 16362306a36Sopenharmony_ci ct[0].chip.irq_unmask = irq_gc_mask_set_bit; 16462306a36Sopenharmony_ci ct[0].chip.irq_mask = irq_gc_mask_clr_bit; 16562306a36Sopenharmony_ci ct[0].chip.irq_ack = irq_gc_ack_set_bit; 16662306a36Sopenharmony_ci ct[0].chip.irq_set_type = ls_intc_set_type; 16762306a36Sopenharmony_ci ct[0].handler = handle_level_irq; 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_ci ct[1].type = IRQ_TYPE_EDGE_BOTH; 17062306a36Sopenharmony_ci ct[1].regs.mask = LS_REG_INTC_EN; 17162306a36Sopenharmony_ci ct[1].regs.ack = LS_REG_INTC_CLR; 17262306a36Sopenharmony_ci ct[1].chip.irq_unmask = irq_gc_mask_set_bit; 17362306a36Sopenharmony_ci ct[1].chip.irq_mask = irq_gc_mask_clr_bit; 17462306a36Sopenharmony_ci ct[1].chip.irq_ack = irq_gc_ack_set_bit; 17562306a36Sopenharmony_ci ct[1].chip.irq_set_type = ls_intc_set_type; 17662306a36Sopenharmony_ci ct[1].handler = handle_edge_irq; 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci irq_set_chained_handler_and_data(parent_irq, 17962306a36Sopenharmony_ci ls1x_chained_handle_irq, priv); 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci return 0; 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_ciout_free_domain: 18462306a36Sopenharmony_ci irq_domain_remove(priv->domain); 18562306a36Sopenharmony_ciout_iounmap: 18662306a36Sopenharmony_ci iounmap(priv->intc_base); 18762306a36Sopenharmony_ciout_free_priv: 18862306a36Sopenharmony_ci kfree(priv); 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_ci return err; 19162306a36Sopenharmony_ci} 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_ciIRQCHIP_DECLARE(ls1x_intc, "loongson,ls1x-intc", ls1x_intc_of_init); 194