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> | |
41a83e06 | 29 | #include <linux/irqchip.h> |
0494e11a SA |
30 | #include <linux/irqdomain.h> |
31 | #include <linux/mfd/syscon.h> | |
32 | #include <dt-bindings/interrupt-controller/arm-gic.h> | |
33 | #include <linux/of.h> | |
34 | #include <linux/of_address.h> | |
35 | #include <linux/slab.h> | |
36 | #include <linux/regmap.h> | |
37 | ||
0494e11a SA |
38 | #define MSCM_CPxNUM 0x4 |
39 | ||
40 | #define MSCM_IRSPRC(n) (0x80 + 2 * (n)) | |
41 | #define MSCM_IRSPRC_CPEN_MASK 0x3 | |
42 | ||
43 | #define MSCM_IRSPRC_NUM 112 | |
44 | ||
45 | struct vf610_mscm_ir_chip_data { | |
46 | void __iomem *mscm_ir_base; | |
47 | u16 cpu_mask; | |
48 | u16 saved_irsprc[MSCM_IRSPRC_NUM]; | |
b5cc5cbc | 49 | bool is_nvic; |
0494e11a SA |
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 | ||
b5cc5cbc | 104 | irq_chip_enable_parent(data); |
0494e11a SA |
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 | ||
b5cc5cbc | 114 | irq_chip_disable_parent(data); |
0494e11a SA |
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; | |
f833f57f MZ |
133 | struct irq_fwspec *fwspec = arg; |
134 | struct irq_fwspec parent_fwspec; | |
0494e11a | 135 | |
f833f57f | 136 | if (!irq_domain_get_of_node(domain->parent)) |
0494e11a SA |
137 | return -EINVAL; |
138 | ||
f833f57f MZ |
139 | if (fwspec->param_count != 2) |
140 | return -EINVAL; | |
141 | ||
142 | hwirq = fwspec->param[0]; | |
0494e11a SA |
143 | for (i = 0; i < nr_irqs; i++) |
144 | irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq + i, | |
145 | &vf610_mscm_ir_irq_chip, | |
146 | domain->host_data); | |
147 | ||
f833f57f | 148 | parent_fwspec.fwnode = domain->parent->fwnode; |
b5cc5cbc SA |
149 | |
150 | if (mscm_ir_data->is_nvic) { | |
f833f57f MZ |
151 | parent_fwspec.param_count = 1; |
152 | parent_fwspec.param[0] = fwspec->param[0]; | |
b5cc5cbc | 153 | } else { |
f833f57f MZ |
154 | parent_fwspec.param_count = 3; |
155 | parent_fwspec.param[0] = GIC_SPI; | |
156 | parent_fwspec.param[1] = fwspec->param[0]; | |
157 | parent_fwspec.param[2] = fwspec->param[1]; | |
b5cc5cbc SA |
158 | } |
159 | ||
f833f57f MZ |
160 | return irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, |
161 | &parent_fwspec); | |
162 | } | |
163 | ||
164 | static int vf610_mscm_ir_domain_translate(struct irq_domain *d, | |
165 | struct irq_fwspec *fwspec, | |
166 | unsigned long *hwirq, | |
167 | unsigned int *type) | |
168 | { | |
169 | if (WARN_ON(fwspec->param_count < 2)) | |
170 | return -EINVAL; | |
171 | *hwirq = fwspec->param[0]; | |
172 | *type = fwspec->param[1] & IRQ_TYPE_SENSE_MASK; | |
173 | return 0; | |
0494e11a SA |
174 | } |
175 | ||
176 | static const struct irq_domain_ops mscm_irq_domain_ops = { | |
f833f57f | 177 | .translate = vf610_mscm_ir_domain_translate, |
0494e11a SA |
178 | .alloc = vf610_mscm_ir_domain_alloc, |
179 | .free = irq_domain_free_irqs_common, | |
180 | }; | |
181 | ||
182 | static int __init vf610_mscm_ir_of_init(struct device_node *node, | |
183 | struct device_node *parent) | |
184 | { | |
185 | struct irq_domain *domain, *domain_parent; | |
186 | struct regmap *mscm_cp_regmap; | |
187 | int ret, cpuid; | |
188 | ||
189 | domain_parent = irq_find_host(parent); | |
190 | if (!domain_parent) { | |
191 | pr_err("vf610_mscm_ir: interrupt-parent not found\n"); | |
192 | return -EINVAL; | |
193 | } | |
194 | ||
195 | mscm_ir_data = kzalloc(sizeof(*mscm_ir_data), GFP_KERNEL); | |
196 | if (!mscm_ir_data) | |
197 | return -ENOMEM; | |
198 | ||
199 | mscm_ir_data->mscm_ir_base = of_io_request_and_map(node, 0, "mscm-ir"); | |
dbf07cf0 | 200 | if (IS_ERR(mscm_ir_data->mscm_ir_base)) { |
0494e11a | 201 | pr_err("vf610_mscm_ir: unable to map mscm register\n"); |
dbf07cf0 | 202 | ret = PTR_ERR(mscm_ir_data->mscm_ir_base); |
0494e11a SA |
203 | goto out_free; |
204 | } | |
205 | ||
206 | mscm_cp_regmap = syscon_regmap_lookup_by_phandle(node, "fsl,cpucfg"); | |
207 | if (IS_ERR(mscm_cp_regmap)) { | |
208 | ret = PTR_ERR(mscm_cp_regmap); | |
209 | pr_err("vf610_mscm_ir: regmap lookup for cpucfg failed\n"); | |
210 | goto out_unmap; | |
211 | } | |
212 | ||
213 | regmap_read(mscm_cp_regmap, MSCM_CPxNUM, &cpuid); | |
214 | mscm_ir_data->cpu_mask = 0x1 << cpuid; | |
215 | ||
216 | domain = irq_domain_add_hierarchy(domain_parent, 0, | |
217 | MSCM_IRSPRC_NUM, node, | |
218 | &mscm_irq_domain_ops, mscm_ir_data); | |
219 | if (!domain) { | |
220 | ret = -ENOMEM; | |
221 | goto out_unmap; | |
222 | } | |
223 | ||
5d4c9bc7 MZ |
224 | if (of_device_is_compatible(irq_domain_get_of_node(domain->parent), |
225 | "arm,armv7m-nvic")) | |
b5cc5cbc SA |
226 | mscm_ir_data->is_nvic = true; |
227 | ||
0494e11a SA |
228 | cpu_pm_register_notifier(&mscm_ir_notifier_block); |
229 | ||
230 | return 0; | |
231 | ||
232 | out_unmap: | |
233 | iounmap(mscm_ir_data->mscm_ir_base); | |
234 | out_free: | |
235 | kfree(mscm_ir_data); | |
236 | return ret; | |
237 | } | |
238 | IRQCHIP_DECLARE(vf610_mscm_ir, "fsl,vf610-mscm-ir", vf610_mscm_ir_of_init); |