Commit | Line | Data |
---|---|---|
0494e11a SA |
1 | /* |
2 | * Copyright (C) 2014-2015 Toradex AG | |
3 | * Author: Stefan Agner <stefan@agner.ch> | |
4 | * | |
5 | * This program is free software; you can redistribute it and/or modify | |
6 | * it under the terms of the GNU General Public License version 2 as | |
7 | * published by the Free Software Foundation. | |
8 | * | |
9 | * | |
10 | * IRQ chip driver for MSCM interrupt router available on Vybrid SoC's. | |
11 | * The interrupt router is between the CPU's interrupt controller and the | |
12 | * peripheral. The router allows to route the peripheral interrupts to | |
13 | * one of the two available CPU's on Vybrid VF6xx SoC's (Cortex-A5 or | |
14 | * Cortex-M4). The router will be configured transparently on a IRQ | |
15 | * request. | |
16 | * | |
17 | * o All peripheral interrupts of the Vybrid SoC can be routed to | |
18 | * CPU 0, CPU 1 or both. The routing is useful for dual-core | |
19 | * variants of Vybrid SoC such as VF6xx. This driver routes the | |
20 | * requested interrupt to the CPU currently running on. | |
21 | * | |
22 | * o It is required to setup the interrupt router even on single-core | |
23 | * variants of Vybrid. | |
24 | */ | |
25 | ||
26 | #include <linux/cpu_pm.h> | |
27 | #include <linux/io.h> | |
28 | #include <linux/irq.h> | |
29 | #include <linux/irqdomain.h> | |
30 | #include <linux/mfd/syscon.h> | |
31 | #include <dt-bindings/interrupt-controller/arm-gic.h> | |
32 | #include <linux/of.h> | |
33 | #include <linux/of_address.h> | |
34 | #include <linux/slab.h> | |
35 | #include <linux/regmap.h> | |
36 | ||
37 | #include "irqchip.h" | |
38 | ||
39 | #define MSCM_CPxNUM 0x4 | |
40 | ||
41 | #define MSCM_IRSPRC(n) (0x80 + 2 * (n)) | |
42 | #define MSCM_IRSPRC_CPEN_MASK 0x3 | |
43 | ||
44 | #define MSCM_IRSPRC_NUM 112 | |
45 | ||
46 | struct vf610_mscm_ir_chip_data { | |
47 | void __iomem *mscm_ir_base; | |
48 | u16 cpu_mask; | |
49 | u16 saved_irsprc[MSCM_IRSPRC_NUM]; | |
50 | }; | |
51 | ||
52 | static struct vf610_mscm_ir_chip_data *mscm_ir_data; | |
53 | ||
54 | static inline void vf610_mscm_ir_save(struct vf610_mscm_ir_chip_data *data) | |
55 | { | |
56 | int i; | |
57 | ||
58 | for (i = 0; i < MSCM_IRSPRC_NUM; i++) | |
59 | data->saved_irsprc[i] = readw_relaxed(data->mscm_ir_base + MSCM_IRSPRC(i)); | |
60 | } | |
61 | ||
62 | static inline void vf610_mscm_ir_restore(struct vf610_mscm_ir_chip_data *data) | |
63 | { | |
64 | int i; | |
65 | ||
66 | for (i = 0; i < MSCM_IRSPRC_NUM; i++) | |
67 | writew_relaxed(data->saved_irsprc[i], data->mscm_ir_base + MSCM_IRSPRC(i)); | |
68 | } | |
69 | ||
70 | static int vf610_mscm_ir_notifier(struct notifier_block *self, | |
71 | unsigned long cmd, void *v) | |
72 | { | |
73 | switch (cmd) { | |
74 | case CPU_CLUSTER_PM_ENTER: | |
75 | vf610_mscm_ir_save(mscm_ir_data); | |
76 | break; | |
77 | case CPU_CLUSTER_PM_ENTER_FAILED: | |
78 | case CPU_CLUSTER_PM_EXIT: | |
79 | vf610_mscm_ir_restore(mscm_ir_data); | |
80 | break; | |
81 | } | |
82 | ||
83 | return NOTIFY_OK; | |
84 | } | |
85 | ||
86 | static struct notifier_block mscm_ir_notifier_block = { | |
87 | .notifier_call = vf610_mscm_ir_notifier, | |
88 | }; | |
89 | ||
90 | static void vf610_mscm_ir_enable(struct irq_data *data) | |
91 | { | |
92 | irq_hw_number_t hwirq = data->hwirq; | |
93 | struct vf610_mscm_ir_chip_data *chip_data = data->chip_data; | |
94 | u16 irsprc; | |
95 | ||
96 | irsprc = readw_relaxed(chip_data->mscm_ir_base + MSCM_IRSPRC(hwirq)); | |
97 | irsprc &= MSCM_IRSPRC_CPEN_MASK; | |
98 | ||
99 | WARN_ON(irsprc & ~chip_data->cpu_mask); | |
100 | ||
101 | writew_relaxed(chip_data->cpu_mask, | |
102 | chip_data->mscm_ir_base + MSCM_IRSPRC(hwirq)); | |
103 | ||
104 | irq_chip_unmask_parent(data); | |
105 | } | |
106 | ||
107 | static void vf610_mscm_ir_disable(struct irq_data *data) | |
108 | { | |
109 | irq_hw_number_t hwirq = data->hwirq; | |
110 | struct vf610_mscm_ir_chip_data *chip_data = data->chip_data; | |
111 | ||
112 | writew_relaxed(0x0, chip_data->mscm_ir_base + MSCM_IRSPRC(hwirq)); | |
113 | ||
114 | irq_chip_mask_parent(data); | |
115 | } | |
116 | ||
117 | static struct irq_chip vf610_mscm_ir_irq_chip = { | |
118 | .name = "mscm-ir", | |
119 | .irq_mask = irq_chip_mask_parent, | |
120 | .irq_unmask = irq_chip_unmask_parent, | |
121 | .irq_eoi = irq_chip_eoi_parent, | |
122 | .irq_enable = vf610_mscm_ir_enable, | |
123 | .irq_disable = vf610_mscm_ir_disable, | |
124 | .irq_retrigger = irq_chip_retrigger_hierarchy, | |
125 | .irq_set_affinity = irq_chip_set_affinity_parent, | |
126 | }; | |
127 | ||
128 | static int vf610_mscm_ir_domain_alloc(struct irq_domain *domain, unsigned int virq, | |
129 | unsigned int nr_irqs, void *arg) | |
130 | { | |
131 | int i; | |
132 | irq_hw_number_t hwirq; | |
133 | struct of_phandle_args *irq_data = arg; | |
134 | struct of_phandle_args gic_data; | |
135 | ||
136 | if (irq_data->args_count != 2) | |
137 | return -EINVAL; | |
138 | ||
139 | hwirq = irq_data->args[0]; | |
140 | for (i = 0; i < nr_irqs; i++) | |
141 | irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq + i, | |
142 | &vf610_mscm_ir_irq_chip, | |
143 | domain->host_data); | |
144 | ||
145 | gic_data.np = domain->parent->of_node; | |
146 | gic_data.args_count = 3; | |
147 | gic_data.args[0] = GIC_SPI; | |
148 | gic_data.args[1] = irq_data->args[0]; | |
149 | gic_data.args[2] = irq_data->args[1]; | |
150 | return irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, &gic_data); | |
151 | } | |
152 | ||
153 | static const struct irq_domain_ops mscm_irq_domain_ops = { | |
154 | .xlate = irq_domain_xlate_twocell, | |
155 | .alloc = vf610_mscm_ir_domain_alloc, | |
156 | .free = irq_domain_free_irqs_common, | |
157 | }; | |
158 | ||
159 | static int __init vf610_mscm_ir_of_init(struct device_node *node, | |
160 | struct device_node *parent) | |
161 | { | |
162 | struct irq_domain *domain, *domain_parent; | |
163 | struct regmap *mscm_cp_regmap; | |
164 | int ret, cpuid; | |
165 | ||
166 | domain_parent = irq_find_host(parent); | |
167 | if (!domain_parent) { | |
168 | pr_err("vf610_mscm_ir: interrupt-parent not found\n"); | |
169 | return -EINVAL; | |
170 | } | |
171 | ||
172 | mscm_ir_data = kzalloc(sizeof(*mscm_ir_data), GFP_KERNEL); | |
173 | if (!mscm_ir_data) | |
174 | return -ENOMEM; | |
175 | ||
176 | mscm_ir_data->mscm_ir_base = of_io_request_and_map(node, 0, "mscm-ir"); | |
177 | ||
178 | if (!mscm_ir_data->mscm_ir_base) { | |
179 | pr_err("vf610_mscm_ir: unable to map mscm register\n"); | |
180 | ret = -ENOMEM; | |
181 | goto out_free; | |
182 | } | |
183 | ||
184 | mscm_cp_regmap = syscon_regmap_lookup_by_phandle(node, "fsl,cpucfg"); | |
185 | if (IS_ERR(mscm_cp_regmap)) { | |
186 | ret = PTR_ERR(mscm_cp_regmap); | |
187 | pr_err("vf610_mscm_ir: regmap lookup for cpucfg failed\n"); | |
188 | goto out_unmap; | |
189 | } | |
190 | ||
191 | regmap_read(mscm_cp_regmap, MSCM_CPxNUM, &cpuid); | |
192 | mscm_ir_data->cpu_mask = 0x1 << cpuid; | |
193 | ||
194 | domain = irq_domain_add_hierarchy(domain_parent, 0, | |
195 | MSCM_IRSPRC_NUM, node, | |
196 | &mscm_irq_domain_ops, mscm_ir_data); | |
197 | if (!domain) { | |
198 | ret = -ENOMEM; | |
199 | goto out_unmap; | |
200 | } | |
201 | ||
202 | cpu_pm_register_notifier(&mscm_ir_notifier_block); | |
203 | ||
204 | return 0; | |
205 | ||
206 | out_unmap: | |
207 | iounmap(mscm_ir_data->mscm_ir_base); | |
208 | out_free: | |
209 | kfree(mscm_ir_data); | |
210 | return ret; | |
211 | } | |
212 | IRQCHIP_DECLARE(vf610_mscm_ir, "fsl,vf610-mscm-ir", vf610_mscm_ir_of_init); |