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