162306a36Sopenharmony_ci// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (C) Sunplus Technology Co., Ltd.
462306a36Sopenharmony_ci *       All rights reserved.
562306a36Sopenharmony_ci */
662306a36Sopenharmony_ci#include <linux/irq.h>
762306a36Sopenharmony_ci#include <linux/irqdomain.h>
862306a36Sopenharmony_ci#include <linux/io.h>
962306a36Sopenharmony_ci#include <linux/irqchip.h>
1062306a36Sopenharmony_ci#include <linux/irqchip/chained_irq.h>
1162306a36Sopenharmony_ci#include <linux/of_address.h>
1262306a36Sopenharmony_ci#include <linux/of_irq.h>
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci#define SP_INTC_HWIRQ_MIN	0
1562306a36Sopenharmony_ci#define SP_INTC_HWIRQ_MAX	223
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci#define SP_INTC_NR_IRQS		(SP_INTC_HWIRQ_MAX - SP_INTC_HWIRQ_MIN + 1)
1862306a36Sopenharmony_ci#define SP_INTC_NR_GROUPS	DIV_ROUND_UP(SP_INTC_NR_IRQS, 32)
1962306a36Sopenharmony_ci#define SP_INTC_REG_SIZE	(SP_INTC_NR_GROUPS * 4)
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci/* REG_GROUP_0 regs */
2262306a36Sopenharmony_ci#define REG_INTR_TYPE		(sp_intc.g0)
2362306a36Sopenharmony_ci#define REG_INTR_POLARITY	(REG_INTR_TYPE     + SP_INTC_REG_SIZE)
2462306a36Sopenharmony_ci#define REG_INTR_PRIORITY	(REG_INTR_POLARITY + SP_INTC_REG_SIZE)
2562306a36Sopenharmony_ci#define REG_INTR_MASK		(REG_INTR_PRIORITY + SP_INTC_REG_SIZE)
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci/* REG_GROUP_1 regs */
2862306a36Sopenharmony_ci#define REG_INTR_CLEAR		(sp_intc.g1)
2962306a36Sopenharmony_ci#define REG_MASKED_EXT1		(REG_INTR_CLEAR    + SP_INTC_REG_SIZE)
3062306a36Sopenharmony_ci#define REG_MASKED_EXT0		(REG_MASKED_EXT1   + SP_INTC_REG_SIZE)
3162306a36Sopenharmony_ci#define REG_INTR_GROUP		(REG_INTR_CLEAR    + 31 * 4)
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci#define GROUP_MASK		(BIT(SP_INTC_NR_GROUPS) - 1)
3462306a36Sopenharmony_ci#define GROUP_SHIFT_EXT1	(0)
3562306a36Sopenharmony_ci#define GROUP_SHIFT_EXT0	(8)
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci/*
3862306a36Sopenharmony_ci * When GPIO_INT0~7 set to edge trigger, doesn't work properly.
3962306a36Sopenharmony_ci * WORKAROUND: change it to level trigger, and toggle the polarity
4062306a36Sopenharmony_ci * at ACK/Handler to make the HW work.
4162306a36Sopenharmony_ci */
4262306a36Sopenharmony_ci#define GPIO_INT0_HWIRQ		120
4362306a36Sopenharmony_ci#define GPIO_INT7_HWIRQ		127
4462306a36Sopenharmony_ci#define IS_GPIO_INT(irq)					\
4562306a36Sopenharmony_ci({								\
4662306a36Sopenharmony_ci	u32 i = irq;						\
4762306a36Sopenharmony_ci	(i >= GPIO_INT0_HWIRQ) && (i <= GPIO_INT7_HWIRQ);	\
4862306a36Sopenharmony_ci})
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ci/* index of states */
5162306a36Sopenharmony_cienum {
5262306a36Sopenharmony_ci	_IS_EDGE = 0,
5362306a36Sopenharmony_ci	_IS_LOW,
5462306a36Sopenharmony_ci	_IS_ACTIVE
5562306a36Sopenharmony_ci};
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_ci#define STATE_BIT(irq, idx)		(((irq) - GPIO_INT0_HWIRQ) * 3 + (idx))
5862306a36Sopenharmony_ci#define ASSIGN_STATE(irq, idx, v)	assign_bit(STATE_BIT(irq, idx), sp_intc.states, v)
5962306a36Sopenharmony_ci#define TEST_STATE(irq, idx)		test_bit(STATE_BIT(irq, idx), sp_intc.states)
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_cistatic struct sp_intctl {
6262306a36Sopenharmony_ci	/*
6362306a36Sopenharmony_ci	 * REG_GROUP_0: include type/polarity/priority/mask regs.
6462306a36Sopenharmony_ci	 * REG_GROUP_1: include clear/masked_ext0/masked_ext1/group regs.
6562306a36Sopenharmony_ci	 */
6662306a36Sopenharmony_ci	void __iomem *g0; // REG_GROUP_0 base
6762306a36Sopenharmony_ci	void __iomem *g1; // REG_GROUP_1 base
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci	struct irq_domain *domain;
7062306a36Sopenharmony_ci	raw_spinlock_t lock;
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ci	/*
7362306a36Sopenharmony_ci	 * store GPIO_INT states
7462306a36Sopenharmony_ci	 * each interrupt has 3 states: is_edge, is_low, is_active
7562306a36Sopenharmony_ci	 */
7662306a36Sopenharmony_ci	DECLARE_BITMAP(states, (GPIO_INT7_HWIRQ - GPIO_INT0_HWIRQ + 1) * 3);
7762306a36Sopenharmony_ci} sp_intc;
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_cistatic struct irq_chip sp_intc_chip;
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_cistatic void sp_intc_assign_bit(u32 hwirq, void __iomem *base, bool value)
8262306a36Sopenharmony_ci{
8362306a36Sopenharmony_ci	u32 offset, mask;
8462306a36Sopenharmony_ci	unsigned long flags;
8562306a36Sopenharmony_ci	void __iomem *reg;
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci	offset = (hwirq / 32) * 4;
8862306a36Sopenharmony_ci	reg = base + offset;
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci	raw_spin_lock_irqsave(&sp_intc.lock, flags);
9162306a36Sopenharmony_ci	mask = readl_relaxed(reg);
9262306a36Sopenharmony_ci	if (value)
9362306a36Sopenharmony_ci		mask |= BIT(hwirq % 32);
9462306a36Sopenharmony_ci	else
9562306a36Sopenharmony_ci		mask &= ~BIT(hwirq % 32);
9662306a36Sopenharmony_ci	writel_relaxed(mask, reg);
9762306a36Sopenharmony_ci	raw_spin_unlock_irqrestore(&sp_intc.lock, flags);
9862306a36Sopenharmony_ci}
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_cistatic void sp_intc_ack_irq(struct irq_data *d)
10162306a36Sopenharmony_ci{
10262306a36Sopenharmony_ci	u32 hwirq = d->hwirq;
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	if (unlikely(IS_GPIO_INT(hwirq) && TEST_STATE(hwirq, _IS_EDGE))) { // WORKAROUND
10562306a36Sopenharmony_ci		sp_intc_assign_bit(hwirq, REG_INTR_POLARITY, !TEST_STATE(hwirq, _IS_LOW));
10662306a36Sopenharmony_ci		ASSIGN_STATE(hwirq, _IS_ACTIVE, true);
10762306a36Sopenharmony_ci	}
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci	sp_intc_assign_bit(hwirq, REG_INTR_CLEAR, 1);
11062306a36Sopenharmony_ci}
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_cistatic void sp_intc_mask_irq(struct irq_data *d)
11362306a36Sopenharmony_ci{
11462306a36Sopenharmony_ci	sp_intc_assign_bit(d->hwirq, REG_INTR_MASK, 0);
11562306a36Sopenharmony_ci}
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_cistatic void sp_intc_unmask_irq(struct irq_data *d)
11862306a36Sopenharmony_ci{
11962306a36Sopenharmony_ci	sp_intc_assign_bit(d->hwirq, REG_INTR_MASK, 1);
12062306a36Sopenharmony_ci}
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_cistatic int sp_intc_set_type(struct irq_data *d, unsigned int type)
12362306a36Sopenharmony_ci{
12462306a36Sopenharmony_ci	u32 hwirq = d->hwirq;
12562306a36Sopenharmony_ci	bool is_edge = !(type & IRQ_TYPE_LEVEL_MASK);
12662306a36Sopenharmony_ci	bool is_low = (type == IRQ_TYPE_LEVEL_LOW || type == IRQ_TYPE_EDGE_FALLING);
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	irq_set_handler_locked(d, is_edge ? handle_edge_irq : handle_level_irq);
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci	if (unlikely(IS_GPIO_INT(hwirq) && is_edge)) { // WORKAROUND
13162306a36Sopenharmony_ci		/* store states */
13262306a36Sopenharmony_ci		ASSIGN_STATE(hwirq, _IS_EDGE, is_edge);
13362306a36Sopenharmony_ci		ASSIGN_STATE(hwirq, _IS_LOW, is_low);
13462306a36Sopenharmony_ci		ASSIGN_STATE(hwirq, _IS_ACTIVE, false);
13562306a36Sopenharmony_ci		/* change to level */
13662306a36Sopenharmony_ci		is_edge = false;
13762306a36Sopenharmony_ci	}
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci	sp_intc_assign_bit(hwirq, REG_INTR_TYPE, is_edge);
14062306a36Sopenharmony_ci	sp_intc_assign_bit(hwirq, REG_INTR_POLARITY, is_low);
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci	return 0;
14362306a36Sopenharmony_ci}
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_cistatic int sp_intc_get_ext_irq(int ext_num)
14662306a36Sopenharmony_ci{
14762306a36Sopenharmony_ci	void __iomem *base = ext_num ? REG_MASKED_EXT1 : REG_MASKED_EXT0;
14862306a36Sopenharmony_ci	u32 shift = ext_num ? GROUP_SHIFT_EXT1 : GROUP_SHIFT_EXT0;
14962306a36Sopenharmony_ci	u32 groups;
15062306a36Sopenharmony_ci	u32 pending_group;
15162306a36Sopenharmony_ci	u32 group;
15262306a36Sopenharmony_ci	u32 pending_irq;
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci	groups = readl_relaxed(REG_INTR_GROUP);
15562306a36Sopenharmony_ci	pending_group = (groups >> shift) & GROUP_MASK;
15662306a36Sopenharmony_ci	if (!pending_group)
15762306a36Sopenharmony_ci		return -1;
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci	group = fls(pending_group) - 1;
16062306a36Sopenharmony_ci	pending_irq = readl_relaxed(base + group * 4);
16162306a36Sopenharmony_ci	if (!pending_irq)
16262306a36Sopenharmony_ci		return -1;
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci	return (group * 32) + fls(pending_irq) - 1;
16562306a36Sopenharmony_ci}
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_cistatic void sp_intc_handle_ext_cascaded(struct irq_desc *desc)
16862306a36Sopenharmony_ci{
16962306a36Sopenharmony_ci	struct irq_chip *chip = irq_desc_get_chip(desc);
17062306a36Sopenharmony_ci	int ext_num = (uintptr_t)irq_desc_get_handler_data(desc);
17162306a36Sopenharmony_ci	int hwirq;
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci	chained_irq_enter(chip, desc);
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_ci	while ((hwirq = sp_intc_get_ext_irq(ext_num)) >= 0) {
17662306a36Sopenharmony_ci		if (unlikely(IS_GPIO_INT(hwirq) && TEST_STATE(hwirq, _IS_ACTIVE))) { // WORKAROUND
17762306a36Sopenharmony_ci			ASSIGN_STATE(hwirq, _IS_ACTIVE, false);
17862306a36Sopenharmony_ci			sp_intc_assign_bit(hwirq, REG_INTR_POLARITY, TEST_STATE(hwirq, _IS_LOW));
17962306a36Sopenharmony_ci		} else {
18062306a36Sopenharmony_ci			generic_handle_domain_irq(sp_intc.domain, hwirq);
18162306a36Sopenharmony_ci		}
18262306a36Sopenharmony_ci	}
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_ci	chained_irq_exit(chip, desc);
18562306a36Sopenharmony_ci}
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_cistatic struct irq_chip sp_intc_chip = {
18862306a36Sopenharmony_ci	.name = "sp_intc",
18962306a36Sopenharmony_ci	.irq_ack = sp_intc_ack_irq,
19062306a36Sopenharmony_ci	.irq_mask = sp_intc_mask_irq,
19162306a36Sopenharmony_ci	.irq_unmask = sp_intc_unmask_irq,
19262306a36Sopenharmony_ci	.irq_set_type = sp_intc_set_type,
19362306a36Sopenharmony_ci};
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_cistatic int sp_intc_irq_domain_map(struct irq_domain *domain,
19662306a36Sopenharmony_ci				  unsigned int irq, irq_hw_number_t hwirq)
19762306a36Sopenharmony_ci{
19862306a36Sopenharmony_ci	irq_set_chip_and_handler(irq, &sp_intc_chip, handle_level_irq);
19962306a36Sopenharmony_ci	irq_set_chip_data(irq, &sp_intc_chip);
20062306a36Sopenharmony_ci	irq_set_noprobe(irq);
20162306a36Sopenharmony_ci
20262306a36Sopenharmony_ci	return 0;
20362306a36Sopenharmony_ci}
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_cistatic const struct irq_domain_ops sp_intc_dm_ops = {
20662306a36Sopenharmony_ci	.xlate = irq_domain_xlate_twocell,
20762306a36Sopenharmony_ci	.map = sp_intc_irq_domain_map,
20862306a36Sopenharmony_ci};
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_cistatic int sp_intc_irq_map(struct device_node *node, int i)
21162306a36Sopenharmony_ci{
21262306a36Sopenharmony_ci	unsigned int irq;
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_ci	irq = irq_of_parse_and_map(node, i);
21562306a36Sopenharmony_ci	if (!irq)
21662306a36Sopenharmony_ci		return -ENOENT;
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ci	irq_set_chained_handler_and_data(irq, sp_intc_handle_ext_cascaded, (void *)(uintptr_t)i);
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_ci	return 0;
22162306a36Sopenharmony_ci}
22262306a36Sopenharmony_ci
22362306a36Sopenharmony_cistatic int __init sp_intc_init_dt(struct device_node *node, struct device_node *parent)
22462306a36Sopenharmony_ci{
22562306a36Sopenharmony_ci	int i, ret;
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_ci	sp_intc.g0 = of_iomap(node, 0);
22862306a36Sopenharmony_ci	if (!sp_intc.g0)
22962306a36Sopenharmony_ci		return -ENXIO;
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_ci	sp_intc.g1 = of_iomap(node, 1);
23262306a36Sopenharmony_ci	if (!sp_intc.g1) {
23362306a36Sopenharmony_ci		ret = -ENXIO;
23462306a36Sopenharmony_ci		goto out_unmap0;
23562306a36Sopenharmony_ci	}
23662306a36Sopenharmony_ci
23762306a36Sopenharmony_ci	ret = sp_intc_irq_map(node, 0); // EXT_INT0
23862306a36Sopenharmony_ci	if (ret)
23962306a36Sopenharmony_ci		goto out_unmap1;
24062306a36Sopenharmony_ci
24162306a36Sopenharmony_ci	ret = sp_intc_irq_map(node, 1); // EXT_INT1
24262306a36Sopenharmony_ci	if (ret)
24362306a36Sopenharmony_ci		goto out_unmap1;
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci	/* initial regs */
24662306a36Sopenharmony_ci	for (i = 0; i < SP_INTC_NR_GROUPS; i++) {
24762306a36Sopenharmony_ci		/* all mask */
24862306a36Sopenharmony_ci		writel_relaxed(0, REG_INTR_MASK + i * 4);
24962306a36Sopenharmony_ci		/* all edge */
25062306a36Sopenharmony_ci		writel_relaxed(~0, REG_INTR_TYPE + i * 4);
25162306a36Sopenharmony_ci		/* all high-active */
25262306a36Sopenharmony_ci		writel_relaxed(0, REG_INTR_POLARITY + i * 4);
25362306a36Sopenharmony_ci		/* all EXT_INT0 */
25462306a36Sopenharmony_ci		writel_relaxed(~0, REG_INTR_PRIORITY + i * 4);
25562306a36Sopenharmony_ci		/* all clear */
25662306a36Sopenharmony_ci		writel_relaxed(~0, REG_INTR_CLEAR + i * 4);
25762306a36Sopenharmony_ci	}
25862306a36Sopenharmony_ci
25962306a36Sopenharmony_ci	sp_intc.domain = irq_domain_add_linear(node, SP_INTC_NR_IRQS,
26062306a36Sopenharmony_ci					       &sp_intc_dm_ops, &sp_intc);
26162306a36Sopenharmony_ci	if (!sp_intc.domain) {
26262306a36Sopenharmony_ci		ret = -ENOMEM;
26362306a36Sopenharmony_ci		goto out_unmap1;
26462306a36Sopenharmony_ci	}
26562306a36Sopenharmony_ci
26662306a36Sopenharmony_ci	raw_spin_lock_init(&sp_intc.lock);
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_ci	return 0;
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_ciout_unmap1:
27162306a36Sopenharmony_ci	iounmap(sp_intc.g1);
27262306a36Sopenharmony_ciout_unmap0:
27362306a36Sopenharmony_ci	iounmap(sp_intc.g0);
27462306a36Sopenharmony_ci
27562306a36Sopenharmony_ci	return ret;
27662306a36Sopenharmony_ci}
27762306a36Sopenharmony_ci
27862306a36Sopenharmony_ciIRQCHIP_DECLARE(sp_intc, "sunplus,sp7021-intc", sp_intc_init_dt);
279