summaryrefslogtreecommitdiff
path: root/drivers/irqchip/irq-vf610-gpc.c
blob: 5527e103f9b097d96060dd64ca3f580a04270d1b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
/*
 * Copyright (C) 2015 Toradex AG
 * Author: Stefan Agner <stefan@agner.ch>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 *
 * The GPC (General Power Controller) irqchip driver takes care of the
 * interrupt wakeup functionality.
 *
 * o All peripheral interrupts of the Vybrid SoC can be used as wakeup
 *   source from STOP mode. In LPSTOP mode however, the GPC is unpowered
 *   too and cannot be used to as a wakeup source. The WKPU (Wakeup Unit)
 *   is responsible for wakeups from LPSTOP modes.
 */

#include <linux/cpu_pm.h>
#include <linux/io.h>
#include <linux/irq.h>
#include <linux/irqdomain.h>
#include <linux/mfd/syscon.h>
#include <dt-bindings/interrupt-controller/arm-gic.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/slab.h>
#include <linux/regmap.h>

#include "irqchip.h"

#define IMR_NUM			4
#define VF610_GPC_IMR1		0x044
#define VF610_GPC_MAX_IRQS	(IMR_NUM * 32)

static void __iomem *gpc_base;

static int vf610_gpc_irq_set_wake(struct irq_data *d, unsigned int on)
{
	unsigned int idx = d->hwirq / 32;
	void __iomem *reg_imr = gpc_base + VF610_GPC_IMR1 + (idx * 4);
	u32 mask = 1 << d->hwirq % 32;

	if (on)
		writel_relaxed(readl_relaxed(reg_imr) & ~mask, reg_imr);
	else
		writel_relaxed(readl_relaxed(reg_imr) | mask, reg_imr);

	/*
	 * Do *not* call into the parent, as the GIC doesn't have any
	 * wake-up facility...
	 */
	return 0;
}

static struct irq_chip vf610_gpc_chip = {
	.name			= "vf610-gpc",
	.irq_mask		= irq_chip_mask_parent,
	.irq_unmask		= irq_chip_unmask_parent,
	.irq_enable		= irq_chip_enable_parent,
	.irq_disable		= irq_chip_disable_parent,
	.irq_eoi		= irq_chip_eoi_parent,
	.irq_retrigger		= irq_chip_retrigger_hierarchy,
	.irq_set_wake		= vf610_gpc_irq_set_wake,
};

static int vf610_gpc_domain_alloc(struct irq_domain *domain, unsigned int virq,
				  unsigned int nr_irqs, void *data)
{
	struct of_phandle_args *args = data;
	struct of_phandle_args parent_args;
	irq_hw_number_t hwirq;
	int i;

	if (args->args_count != 2)
		return -EINVAL;

	hwirq = args->args[0];
	for (i = 0; i < nr_irqs; i++)
		irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq + i,
					      &vf610_gpc_chip, NULL);

	parent_args = *args;
	parent_args.np = domain->parent->of_node;
	return irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, &parent_args);
}

static const struct irq_domain_ops gpc_irq_domain_ops = {
	.xlate = irq_domain_xlate_twocell,
	.alloc = vf610_gpc_domain_alloc,
	.free = irq_domain_free_irqs_common,
};

static int __init vf610_gpc_of_init(struct device_node *node,
			       struct device_node *parent)
{
	struct irq_domain *domain, *domain_parent;
	int i;

	domain_parent = irq_find_host(parent);
	if (!domain_parent) {
		pr_err("vf610_gpc: interrupt-parent not found\n");
		return -EINVAL;
	}

	gpc_base = of_io_request_and_map(node, 0, "gpc");
	if (WARN_ON(!gpc_base))
	        return -ENOMEM;

	domain = irq_domain_add_hierarchy(domain_parent, 0, VF610_GPC_MAX_IRQS,
					  node, &gpc_irq_domain_ops, NULL);
	if (!domain) {
		iounmap(gpc_base);
		return -ENOMEM;
	}

	/* Initially mask all interrupts for wakeup */
	for (i = 0; i < IMR_NUM; i++)
		writel_relaxed(~0, gpc_base + VF610_GPC_IMR1 + i * 4);

	return 0;
}
IRQCHIP_DECLARE(vf610_gpc, "fsl,vf610-gpc", vf610_gpc_of_init);