summaryrefslogtreecommitdiff
path: root/arch/arm/mach-imx/mscm-vf610.c
blob: 6dc45b52b8118ea273e1bca1056a8c49de9f091f (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
124
125
126
127
128
129
130
131
132
133
/*
 * Copyright 2014 Stefan Agner
 *
 * The code contained herein is licensed under the GNU General Public
 * License. You may obtain a copy of the GNU General Public License
 * Version 2 or later at the following locations:
 *
 * http://www.opensource.org/licenses/gpl-license.html
 * http://www.gnu.org/copyleft/gpl.html
 */

#include <linux/cpu_pm.h>
#include <linux/io.h>
#include <linux/irq.h>
#include <linux/irqdomain.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/irqchip/arm-gic.h>
#include "common.h"

#define MSCM_CPxNUM		0x4
#define MSCM_IRSPRC(n)		(0x880 + 2 * (n))
#define MSCM_IRSPRC_CPEN_MASK	0x3

#define MSCM_IRSPRC_NUM		112

#define MSCM_IRQ_OFFSET		32

static void __iomem *mscm_base;
static u16 mscm_saved_irsprc[MSCM_IRSPRC_NUM];
static u16 cpu_id;

static int vf610_mscm_notifier(struct notifier_block *self, unsigned long cmd,
			       void *v)
{
	int i;

	/* Only the primary (boot CPU) should do suspend/resume */
	if (cpu_id > 0)
		return NOTIFY_OK;

	switch (cmd) {
	case CPU_CLUSTER_PM_ENTER:
		for (i = 0; i < MSCM_IRSPRC_NUM; i++)
			mscm_saved_irsprc[i] =
				readw_relaxed(mscm_base + MSCM_IRSPRC(i));
		break;
	case CPU_CLUSTER_PM_ENTER_FAILED:
	case CPU_CLUSTER_PM_EXIT:
		for (i = 0; i < MSCM_IRSPRC_NUM; i++)
			writew_relaxed(mscm_saved_irsprc[i],
				       mscm_base + MSCM_IRSPRC(i));
		break;
	}

	return NOTIFY_OK;
}

static struct notifier_block mscm_notifier_block = {
	.notifier_call = vf610_mscm_notifier,
};

static int vf610_mscm_domain_map(struct irq_domain *d, unsigned int irq,
			       irq_hw_number_t hw)
{
	u16 irsprc;

	/* Do not handle non Interrupt Router IRQs */
	if (hw < MSCM_IRQ_OFFSET)
		return 0;

	hw -= MSCM_IRQ_OFFSET;
	irsprc = readw_relaxed(mscm_base + MSCM_IRSPRC(hw));
	irsprc &= MSCM_IRSPRC_CPEN_MASK;

	/* Warn if interrupt is enabled on another CPU */
	WARN_ON(irsprc & ~(0x1 << cpu_id));

	writew_relaxed(0x1 << cpu_id, mscm_base + MSCM_IRSPRC(hw));

	return 0;
}

static void vf610_mscm_domain_unmap(struct irq_domain *d, unsigned int irq)
{
	irq_hw_number_t hw = irq_get_irq_data(irq)->hwirq;
	u16 irsprc;

	/* Do not handle non Interrupt Router IRQs */
	if (hw < MSCM_IRQ_OFFSET)
		return;

	hw -= MSCM_IRQ_OFFSET;
	irsprc = readw_relaxed(mscm_base + MSCM_IRSPRC(hw));
	irsprc &= MSCM_IRSPRC_CPEN_MASK;

	writew_relaxed(0x1 << cpu_id, mscm_base + MSCM_IRSPRC(hw));
}

static int vf610_mscm_domain_xlate(struct irq_domain *d,
				struct device_node *controller,
				const u32 *intspec, unsigned int intsize,
				unsigned long *out_hwirq,
				unsigned int *out_type)
{
	*out_hwirq += 16;
	return 0;
}
static const struct irq_domain_ops routable_irq_domain_ops = {
	.map = vf610_mscm_domain_map,
	.unmap = vf610_mscm_domain_unmap,
	.xlate = vf610_mscm_domain_xlate,
};

void __init vf610_mscm_init(void)
{
	struct device_node *np;

	np = of_find_compatible_node(NULL, NULL, "fsl,vf610-mscm");
	mscm_base = of_iomap(np, 0);

	if (!mscm_base) {
		WARN_ON(1);
		return;
	}

	cpu_id = readl_relaxed(mscm_base + MSCM_CPxNUM);

	/* Register MSCM as interrupt router */
	register_routable_domain_ops(&routable_irq_domain_ops);

	cpu_pm_register_notifier(&mscm_notifier_block);
}