100fa3461SClaudiu Beznea // SPDX-License-Identifier: GPL-2.0-only
200fa3461SClaudiu Beznea /*
300fa3461SClaudiu Beznea * Microchip External Interrupt Controller driver
400fa3461SClaudiu Beznea *
500fa3461SClaudiu Beznea * Copyright (C) 2021 Microchip Technology Inc. and its subsidiaries
600fa3461SClaudiu Beznea *
700fa3461SClaudiu Beznea * Author: Claudiu Beznea <claudiu.beznea@microchip.com>
800fa3461SClaudiu Beznea */
900fa3461SClaudiu Beznea #include <linux/clk.h>
1000fa3461SClaudiu Beznea #include <linux/delay.h>
1100fa3461SClaudiu Beznea #include <linux/interrupt.h>
1200fa3461SClaudiu Beznea #include <linux/irqchip.h>
1300fa3461SClaudiu Beznea #include <linux/of_address.h>
1400fa3461SClaudiu Beznea #include <linux/of_irq.h>
1500fa3461SClaudiu Beznea #include <linux/syscore_ops.h>
1600fa3461SClaudiu Beznea
1700fa3461SClaudiu Beznea #include <dt-bindings/interrupt-controller/arm-gic.h>
1800fa3461SClaudiu Beznea
1900fa3461SClaudiu Beznea #define MCHP_EIC_GFCS (0x0)
2000fa3461SClaudiu Beznea #define MCHP_EIC_SCFG(x) (0x4 + (x) * 0x4)
2100fa3461SClaudiu Beznea #define MCHP_EIC_SCFG_EN BIT(16)
2200fa3461SClaudiu Beznea #define MCHP_EIC_SCFG_LVL BIT(9)
2300fa3461SClaudiu Beznea #define MCHP_EIC_SCFG_POL BIT(8)
2400fa3461SClaudiu Beznea
2500fa3461SClaudiu Beznea #define MCHP_EIC_NIRQ (2)
2600fa3461SClaudiu Beznea
2700fa3461SClaudiu Beznea /*
2800fa3461SClaudiu Beznea * struct mchp_eic - EIC private data structure
2900fa3461SClaudiu Beznea * @base: base address
3000fa3461SClaudiu Beznea * @clk: peripheral clock
3100fa3461SClaudiu Beznea * @domain: irq domain
3200fa3461SClaudiu Beznea * @irqs: irqs b/w eic and gic
3300fa3461SClaudiu Beznea * @scfg: backup for scfg registers (necessary for backup and self-refresh mode)
3400fa3461SClaudiu Beznea * @wakeup_source: wakeup source mask
3500fa3461SClaudiu Beznea */
3600fa3461SClaudiu Beznea struct mchp_eic {
3700fa3461SClaudiu Beznea void __iomem *base;
3800fa3461SClaudiu Beznea struct clk *clk;
3900fa3461SClaudiu Beznea struct irq_domain *domain;
4000fa3461SClaudiu Beznea u32 irqs[MCHP_EIC_NIRQ];
4100fa3461SClaudiu Beznea u32 scfg[MCHP_EIC_NIRQ];
4200fa3461SClaudiu Beznea u32 wakeup_source;
4300fa3461SClaudiu Beznea };
4400fa3461SClaudiu Beznea
4500fa3461SClaudiu Beznea static struct mchp_eic *eic;
4600fa3461SClaudiu Beznea
mchp_eic_irq_mask(struct irq_data * d)4700fa3461SClaudiu Beznea static void mchp_eic_irq_mask(struct irq_data *d)
4800fa3461SClaudiu Beznea {
4900fa3461SClaudiu Beznea unsigned int tmp;
5000fa3461SClaudiu Beznea
5100fa3461SClaudiu Beznea tmp = readl_relaxed(eic->base + MCHP_EIC_SCFG(d->hwirq));
5200fa3461SClaudiu Beznea tmp &= ~MCHP_EIC_SCFG_EN;
5300fa3461SClaudiu Beznea writel_relaxed(tmp, eic->base + MCHP_EIC_SCFG(d->hwirq));
5400fa3461SClaudiu Beznea
5500fa3461SClaudiu Beznea irq_chip_mask_parent(d);
5600fa3461SClaudiu Beznea }
5700fa3461SClaudiu Beznea
mchp_eic_irq_unmask(struct irq_data * d)5800fa3461SClaudiu Beznea static void mchp_eic_irq_unmask(struct irq_data *d)
5900fa3461SClaudiu Beznea {
6000fa3461SClaudiu Beznea unsigned int tmp;
6100fa3461SClaudiu Beznea
6200fa3461SClaudiu Beznea tmp = readl_relaxed(eic->base + MCHP_EIC_SCFG(d->hwirq));
6300fa3461SClaudiu Beznea tmp |= MCHP_EIC_SCFG_EN;
6400fa3461SClaudiu Beznea writel_relaxed(tmp, eic->base + MCHP_EIC_SCFG(d->hwirq));
6500fa3461SClaudiu Beznea
6600fa3461SClaudiu Beznea irq_chip_unmask_parent(d);
6700fa3461SClaudiu Beznea }
6800fa3461SClaudiu Beznea
mchp_eic_irq_set_type(struct irq_data * d,unsigned int type)6900fa3461SClaudiu Beznea static int mchp_eic_irq_set_type(struct irq_data *d, unsigned int type)
7000fa3461SClaudiu Beznea {
7100fa3461SClaudiu Beznea unsigned int parent_irq_type;
7200fa3461SClaudiu Beznea unsigned int tmp;
7300fa3461SClaudiu Beznea
7400fa3461SClaudiu Beznea tmp = readl_relaxed(eic->base + MCHP_EIC_SCFG(d->hwirq));
7500fa3461SClaudiu Beznea tmp &= ~(MCHP_EIC_SCFG_POL | MCHP_EIC_SCFG_LVL);
7600fa3461SClaudiu Beznea switch (type) {
7700fa3461SClaudiu Beznea case IRQ_TYPE_LEVEL_HIGH:
7800fa3461SClaudiu Beznea tmp |= MCHP_EIC_SCFG_POL | MCHP_EIC_SCFG_LVL;
7900fa3461SClaudiu Beznea parent_irq_type = IRQ_TYPE_LEVEL_HIGH;
8000fa3461SClaudiu Beznea break;
8100fa3461SClaudiu Beznea case IRQ_TYPE_LEVEL_LOW:
8200fa3461SClaudiu Beznea tmp |= MCHP_EIC_SCFG_LVL;
8300fa3461SClaudiu Beznea parent_irq_type = IRQ_TYPE_LEVEL_HIGH;
8400fa3461SClaudiu Beznea break;
8500fa3461SClaudiu Beznea case IRQ_TYPE_EDGE_RISING:
8600fa3461SClaudiu Beznea parent_irq_type = IRQ_TYPE_EDGE_RISING;
8700fa3461SClaudiu Beznea break;
8800fa3461SClaudiu Beznea case IRQ_TYPE_EDGE_FALLING:
8900fa3461SClaudiu Beznea tmp |= MCHP_EIC_SCFG_POL;
9000fa3461SClaudiu Beznea parent_irq_type = IRQ_TYPE_EDGE_RISING;
9100fa3461SClaudiu Beznea break;
9200fa3461SClaudiu Beznea default:
9300fa3461SClaudiu Beznea return -EINVAL;
9400fa3461SClaudiu Beznea }
9500fa3461SClaudiu Beznea
9600fa3461SClaudiu Beznea writel_relaxed(tmp, eic->base + MCHP_EIC_SCFG(d->hwirq));
9700fa3461SClaudiu Beznea
9800fa3461SClaudiu Beznea return irq_chip_set_type_parent(d, parent_irq_type);
9900fa3461SClaudiu Beznea }
10000fa3461SClaudiu Beznea
mchp_eic_irq_set_wake(struct irq_data * d,unsigned int on)10100fa3461SClaudiu Beznea static int mchp_eic_irq_set_wake(struct irq_data *d, unsigned int on)
10200fa3461SClaudiu Beznea {
10300fa3461SClaudiu Beznea irq_set_irq_wake(eic->irqs[d->hwirq], on);
10400fa3461SClaudiu Beznea if (on)
10500fa3461SClaudiu Beznea eic->wakeup_source |= BIT(d->hwirq);
10600fa3461SClaudiu Beznea else
10700fa3461SClaudiu Beznea eic->wakeup_source &= ~BIT(d->hwirq);
10800fa3461SClaudiu Beznea
10900fa3461SClaudiu Beznea return 0;
11000fa3461SClaudiu Beznea }
11100fa3461SClaudiu Beznea
mchp_eic_irq_suspend(void)11200fa3461SClaudiu Beznea static int mchp_eic_irq_suspend(void)
11300fa3461SClaudiu Beznea {
11400fa3461SClaudiu Beznea unsigned int hwirq;
11500fa3461SClaudiu Beznea
11600fa3461SClaudiu Beznea for (hwirq = 0; hwirq < MCHP_EIC_NIRQ; hwirq++)
11700fa3461SClaudiu Beznea eic->scfg[hwirq] = readl_relaxed(eic->base +
11800fa3461SClaudiu Beznea MCHP_EIC_SCFG(hwirq));
11900fa3461SClaudiu Beznea
12000fa3461SClaudiu Beznea if (!eic->wakeup_source)
12100fa3461SClaudiu Beznea clk_disable_unprepare(eic->clk);
12200fa3461SClaudiu Beznea
12300fa3461SClaudiu Beznea return 0;
12400fa3461SClaudiu Beznea }
12500fa3461SClaudiu Beznea
mchp_eic_irq_resume(void)12600fa3461SClaudiu Beznea static void mchp_eic_irq_resume(void)
12700fa3461SClaudiu Beznea {
12800fa3461SClaudiu Beznea unsigned int hwirq;
12900fa3461SClaudiu Beznea
13000fa3461SClaudiu Beznea if (!eic->wakeup_source)
13100fa3461SClaudiu Beznea clk_prepare_enable(eic->clk);
13200fa3461SClaudiu Beznea
13300fa3461SClaudiu Beznea for (hwirq = 0; hwirq < MCHP_EIC_NIRQ; hwirq++)
13400fa3461SClaudiu Beznea writel_relaxed(eic->scfg[hwirq], eic->base +
13500fa3461SClaudiu Beznea MCHP_EIC_SCFG(hwirq));
13600fa3461SClaudiu Beznea }
13700fa3461SClaudiu Beznea
13800fa3461SClaudiu Beznea static struct syscore_ops mchp_eic_syscore_ops = {
13900fa3461SClaudiu Beznea .suspend = mchp_eic_irq_suspend,
14000fa3461SClaudiu Beznea .resume = mchp_eic_irq_resume,
14100fa3461SClaudiu Beznea };
14200fa3461SClaudiu Beznea
14300fa3461SClaudiu Beznea static struct irq_chip mchp_eic_chip = {
14400fa3461SClaudiu Beznea .name = "eic",
14500fa3461SClaudiu Beznea .flags = IRQCHIP_MASK_ON_SUSPEND | IRQCHIP_SET_TYPE_MASKED,
14600fa3461SClaudiu Beznea .irq_mask = mchp_eic_irq_mask,
14700fa3461SClaudiu Beznea .irq_unmask = mchp_eic_irq_unmask,
14800fa3461SClaudiu Beznea .irq_set_type = mchp_eic_irq_set_type,
14900fa3461SClaudiu Beznea .irq_ack = irq_chip_ack_parent,
15000fa3461SClaudiu Beznea .irq_eoi = irq_chip_eoi_parent,
15100fa3461SClaudiu Beznea .irq_retrigger = irq_chip_retrigger_hierarchy,
15200fa3461SClaudiu Beznea .irq_set_wake = mchp_eic_irq_set_wake,
15300fa3461SClaudiu Beznea };
15400fa3461SClaudiu Beznea
mchp_eic_domain_alloc(struct irq_domain * domain,unsigned int virq,unsigned int nr_irqs,void * data)15500fa3461SClaudiu Beznea static int mchp_eic_domain_alloc(struct irq_domain *domain, unsigned int virq,
15600fa3461SClaudiu Beznea unsigned int nr_irqs, void *data)
15700fa3461SClaudiu Beznea {
15800fa3461SClaudiu Beznea struct irq_fwspec *fwspec = data;
15900fa3461SClaudiu Beznea struct irq_fwspec parent_fwspec;
16000fa3461SClaudiu Beznea irq_hw_number_t hwirq;
16100fa3461SClaudiu Beznea unsigned int type;
16200fa3461SClaudiu Beznea int ret;
16300fa3461SClaudiu Beznea
16400fa3461SClaudiu Beznea if (WARN_ON(nr_irqs != 1))
16500fa3461SClaudiu Beznea return -EINVAL;
16600fa3461SClaudiu Beznea
16700fa3461SClaudiu Beznea ret = irq_domain_translate_twocell(domain, fwspec, &hwirq, &type);
16800fa3461SClaudiu Beznea if (ret || hwirq >= MCHP_EIC_NIRQ)
16900fa3461SClaudiu Beznea return ret;
17000fa3461SClaudiu Beznea
17100fa3461SClaudiu Beznea switch (type) {
17200fa3461SClaudiu Beznea case IRQ_TYPE_EDGE_RISING:
17300fa3461SClaudiu Beznea case IRQ_TYPE_LEVEL_HIGH:
17400fa3461SClaudiu Beznea break;
17500fa3461SClaudiu Beznea case IRQ_TYPE_EDGE_FALLING:
17600fa3461SClaudiu Beznea type = IRQ_TYPE_EDGE_RISING;
17700fa3461SClaudiu Beznea break;
17800fa3461SClaudiu Beznea case IRQ_TYPE_LEVEL_LOW:
17900fa3461SClaudiu Beznea type = IRQ_TYPE_LEVEL_HIGH;
18000fa3461SClaudiu Beznea break;
18100fa3461SClaudiu Beznea default:
18200fa3461SClaudiu Beznea return -EINVAL;
18300fa3461SClaudiu Beznea }
18400fa3461SClaudiu Beznea
18500fa3461SClaudiu Beznea irq_domain_set_hwirq_and_chip(domain, virq, hwirq, &mchp_eic_chip, eic);
18600fa3461SClaudiu Beznea
18700fa3461SClaudiu Beznea parent_fwspec.fwnode = domain->parent->fwnode;
18800fa3461SClaudiu Beznea parent_fwspec.param_count = 3;
18900fa3461SClaudiu Beznea parent_fwspec.param[0] = GIC_SPI;
19000fa3461SClaudiu Beznea parent_fwspec.param[1] = eic->irqs[hwirq];
19100fa3461SClaudiu Beznea parent_fwspec.param[2] = type;
19200fa3461SClaudiu Beznea
19300fa3461SClaudiu Beznea return irq_domain_alloc_irqs_parent(domain, virq, 1, &parent_fwspec);
19400fa3461SClaudiu Beznea }
19500fa3461SClaudiu Beznea
19600fa3461SClaudiu Beznea static const struct irq_domain_ops mchp_eic_domain_ops = {
19700fa3461SClaudiu Beznea .translate = irq_domain_translate_twocell,
19800fa3461SClaudiu Beznea .alloc = mchp_eic_domain_alloc,
19900fa3461SClaudiu Beznea .free = irq_domain_free_irqs_common,
20000fa3461SClaudiu Beznea };
20100fa3461SClaudiu Beznea
mchp_eic_init(struct device_node * node,struct device_node * parent)20200fa3461SClaudiu Beznea static int mchp_eic_init(struct device_node *node, struct device_node *parent)
20300fa3461SClaudiu Beznea {
20400fa3461SClaudiu Beznea struct irq_domain *parent_domain = NULL;
20500fa3461SClaudiu Beznea int ret, i;
20600fa3461SClaudiu Beznea
20700fa3461SClaudiu Beznea eic = kzalloc(sizeof(*eic), GFP_KERNEL);
20800fa3461SClaudiu Beznea if (!eic)
20900fa3461SClaudiu Beznea return -ENOMEM;
21000fa3461SClaudiu Beznea
21100fa3461SClaudiu Beznea eic->base = of_iomap(node, 0);
212*68a6e0c6SYang Yingliang if (!eic->base) {
21300fa3461SClaudiu Beznea ret = -ENOMEM;
21400fa3461SClaudiu Beznea goto free;
21500fa3461SClaudiu Beznea }
21600fa3461SClaudiu Beznea
21700fa3461SClaudiu Beznea parent_domain = irq_find_host(parent);
21800fa3461SClaudiu Beznea if (!parent_domain) {
21900fa3461SClaudiu Beznea ret = -ENODEV;
22000fa3461SClaudiu Beznea goto unmap;
22100fa3461SClaudiu Beznea }
22200fa3461SClaudiu Beznea
22300fa3461SClaudiu Beznea eic->clk = of_clk_get_by_name(node, "pclk");
22400fa3461SClaudiu Beznea if (IS_ERR(eic->clk)) {
22500fa3461SClaudiu Beznea ret = PTR_ERR(eic->clk);
22600fa3461SClaudiu Beznea goto unmap;
22700fa3461SClaudiu Beznea }
22800fa3461SClaudiu Beznea
22900fa3461SClaudiu Beznea ret = clk_prepare_enable(eic->clk);
23000fa3461SClaudiu Beznea if (ret)
23100fa3461SClaudiu Beznea goto unmap;
23200fa3461SClaudiu Beznea
23300fa3461SClaudiu Beznea for (i = 0; i < MCHP_EIC_NIRQ; i++) {
23400fa3461SClaudiu Beznea struct of_phandle_args irq;
23500fa3461SClaudiu Beznea
23600fa3461SClaudiu Beznea /* Disable it, if any. */
23700fa3461SClaudiu Beznea writel_relaxed(0UL, eic->base + MCHP_EIC_SCFG(i));
23800fa3461SClaudiu Beznea
23900fa3461SClaudiu Beznea ret = of_irq_parse_one(node, i, &irq);
24000fa3461SClaudiu Beznea if (ret)
24100fa3461SClaudiu Beznea goto clk_unprepare;
24200fa3461SClaudiu Beznea
24300fa3461SClaudiu Beznea if (WARN_ON(irq.args_count != 3)) {
24400fa3461SClaudiu Beznea ret = -EINVAL;
24500fa3461SClaudiu Beznea goto clk_unprepare;
24600fa3461SClaudiu Beznea }
24700fa3461SClaudiu Beznea
24800fa3461SClaudiu Beznea eic->irqs[i] = irq.args[1];
24900fa3461SClaudiu Beznea }
25000fa3461SClaudiu Beznea
25100fa3461SClaudiu Beznea eic->domain = irq_domain_add_hierarchy(parent_domain, 0, MCHP_EIC_NIRQ,
25200fa3461SClaudiu Beznea node, &mchp_eic_domain_ops, eic);
25300fa3461SClaudiu Beznea if (!eic->domain) {
25400fa3461SClaudiu Beznea pr_err("%pOF: Failed to add domain\n", node);
25500fa3461SClaudiu Beznea ret = -ENODEV;
25600fa3461SClaudiu Beznea goto clk_unprepare;
25700fa3461SClaudiu Beznea }
25800fa3461SClaudiu Beznea
25900fa3461SClaudiu Beznea register_syscore_ops(&mchp_eic_syscore_ops);
26000fa3461SClaudiu Beznea
26100fa3461SClaudiu Beznea pr_info("%pOF: EIC registered, nr_irqs %u\n", node, MCHP_EIC_NIRQ);
26200fa3461SClaudiu Beznea
26300fa3461SClaudiu Beznea return 0;
26400fa3461SClaudiu Beznea
26500fa3461SClaudiu Beznea clk_unprepare:
26600fa3461SClaudiu Beznea clk_disable_unprepare(eic->clk);
26700fa3461SClaudiu Beznea unmap:
26800fa3461SClaudiu Beznea iounmap(eic->base);
26900fa3461SClaudiu Beznea free:
27000fa3461SClaudiu Beznea kfree(eic);
27100fa3461SClaudiu Beznea return ret;
27200fa3461SClaudiu Beznea }
27300fa3461SClaudiu Beznea
27400fa3461SClaudiu Beznea IRQCHIP_PLATFORM_DRIVER_BEGIN(mchp_eic)
27500fa3461SClaudiu Beznea IRQCHIP_MATCH("microchip,sama7g5-eic", mchp_eic_init)
27600fa3461SClaudiu Beznea IRQCHIP_PLATFORM_DRIVER_END(mchp_eic)
27700fa3461SClaudiu Beznea
27800fa3461SClaudiu Beznea MODULE_DESCRIPTION("Microchip External Interrupt Controller");
27900fa3461SClaudiu Beznea MODULE_AUTHOR("Claudiu Beznea <claudiu.beznea@microchip.com>");
280