]>
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]; | |
b5cc5cbc | 50 | bool is_nvic; |
0494e11a SA |
51 | }; |
52 | ||
53 | static struct vf610_mscm_ir_chip_data *mscm_ir_data; | |
54 | ||
55 | static inline void vf610_mscm_ir_save(struct vf610_mscm_ir_chip_data *data) | |
56 | { | |
57 | int i; | |
58 | ||
59 | for (i = 0; i < MSCM_IRSPRC_NUM; i++) | |
60 | data->saved_irsprc[i] = readw_relaxed(data->mscm_ir_base + MSCM_IRSPRC(i)); | |
61 | } | |
62 | ||
63 | static inline void vf610_mscm_ir_restore(struct vf610_mscm_ir_chip_data *data) | |
64 | { | |
65 | int i; | |
66 | ||
67 | for (i = 0; i < MSCM_IRSPRC_NUM; i++) | |
68 | writew_relaxed(data->saved_irsprc[i], data->mscm_ir_base + MSCM_IRSPRC(i)); | |
69 | } | |
70 | ||
71 | static int vf610_mscm_ir_notifier(struct notifier_block *self, | |
72 | unsigned long cmd, void *v) | |
73 | { | |
74 | switch (cmd) { | |
75 | case CPU_CLUSTER_PM_ENTER: | |
76 | vf610_mscm_ir_save(mscm_ir_data); | |
77 | break; | |
78 | case CPU_CLUSTER_PM_ENTER_FAILED: | |
79 | case CPU_CLUSTER_PM_EXIT: | |
80 | vf610_mscm_ir_restore(mscm_ir_data); | |
81 | break; | |
82 | } | |
83 | ||
84 | return NOTIFY_OK; | |
85 | } | |
86 | ||
87 | static struct notifier_block mscm_ir_notifier_block = { | |
88 | .notifier_call = vf610_mscm_ir_notifier, | |
89 | }; | |
90 | ||
91 | static void vf610_mscm_ir_enable(struct irq_data *data) | |
92 | { | |
93 | irq_hw_number_t hwirq = data->hwirq; | |
94 | struct vf610_mscm_ir_chip_data *chip_data = data->chip_data; | |
95 | u16 irsprc; | |
96 | ||
97 | irsprc = readw_relaxed(chip_data->mscm_ir_base + MSCM_IRSPRC(hwirq)); | |
98 | irsprc &= MSCM_IRSPRC_CPEN_MASK; | |
99 | ||
100 | WARN_ON(irsprc & ~chip_data->cpu_mask); | |
101 | ||
102 | writew_relaxed(chip_data->cpu_mask, | |
103 | chip_data->mscm_ir_base + MSCM_IRSPRC(hwirq)); | |
104 | ||
b5cc5cbc | 105 | irq_chip_enable_parent(data); |
0494e11a SA |
106 | } |
107 | ||
108 | static void vf610_mscm_ir_disable(struct irq_data *data) | |
109 | { | |
110 | irq_hw_number_t hwirq = data->hwirq; | |
111 | struct vf610_mscm_ir_chip_data *chip_data = data->chip_data; | |
112 | ||
113 | writew_relaxed(0x0, chip_data->mscm_ir_base + MSCM_IRSPRC(hwirq)); | |
114 | ||
b5cc5cbc | 115 | irq_chip_disable_parent(data); |
0494e11a SA |
116 | } |
117 | ||
118 | static struct irq_chip vf610_mscm_ir_irq_chip = { | |
119 | .name = "mscm-ir", | |
120 | .irq_mask = irq_chip_mask_parent, | |
121 | .irq_unmask = irq_chip_unmask_parent, | |
122 | .irq_eoi = irq_chip_eoi_parent, | |
123 | .irq_enable = vf610_mscm_ir_enable, | |
124 | .irq_disable = vf610_mscm_ir_disable, | |
125 | .irq_retrigger = irq_chip_retrigger_hierarchy, | |
126 | .irq_set_affinity = irq_chip_set_affinity_parent, | |
127 | }; | |
128 | ||
129 | static int vf610_mscm_ir_domain_alloc(struct irq_domain *domain, unsigned int virq, | |
130 | unsigned int nr_irqs, void *arg) | |
131 | { | |
132 | int i; | |
133 | irq_hw_number_t hwirq; | |
134 | struct of_phandle_args *irq_data = arg; | |
135 | struct of_phandle_args gic_data; | |
136 | ||
137 | if (irq_data->args_count != 2) | |
138 | return -EINVAL; | |
139 | ||
140 | hwirq = irq_data->args[0]; | |
141 | for (i = 0; i < nr_irqs; i++) | |
142 | irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq + i, | |
143 | &vf610_mscm_ir_irq_chip, | |
144 | domain->host_data); | |
145 | ||
146 | gic_data.np = domain->parent->of_node; | |
b5cc5cbc SA |
147 | |
148 | if (mscm_ir_data->is_nvic) { | |
149 | gic_data.args_count = 1; | |
150 | gic_data.args[0] = irq_data->args[0]; | |
151 | } else { | |
152 | gic_data.args_count = 3; | |
153 | gic_data.args[0] = GIC_SPI; | |
154 | gic_data.args[1] = irq_data->args[0]; | |
155 | gic_data.args[2] = irq_data->args[1]; | |
156 | } | |
157 | ||
0494e11a SA |
158 | return irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, &gic_data); |
159 | } | |
160 | ||
161 | static const struct irq_domain_ops mscm_irq_domain_ops = { | |
162 | .xlate = irq_domain_xlate_twocell, | |
163 | .alloc = vf610_mscm_ir_domain_alloc, | |
164 | .free = irq_domain_free_irqs_common, | |
165 | }; | |
166 | ||
167 | static int __init vf610_mscm_ir_of_init(struct device_node *node, | |
168 | struct device_node *parent) | |
169 | { | |
170 | struct irq_domain *domain, *domain_parent; | |
171 | struct regmap *mscm_cp_regmap; | |
172 | int ret, cpuid; | |
173 | ||
174 | domain_parent = irq_find_host(parent); | |
175 | if (!domain_parent) { | |
176 | pr_err("vf610_mscm_ir: interrupt-parent not found\n"); | |
177 | return -EINVAL; | |
178 | } | |
179 | ||
180 | mscm_ir_data = kzalloc(sizeof(*mscm_ir_data), GFP_KERNEL); | |
181 | if (!mscm_ir_data) | |
182 | return -ENOMEM; | |
183 | ||
184 | mscm_ir_data->mscm_ir_base = of_io_request_and_map(node, 0, "mscm-ir"); | |
dbf07cf0 | 185 | if (IS_ERR(mscm_ir_data->mscm_ir_base)) { |
0494e11a | 186 | pr_err("vf610_mscm_ir: unable to map mscm register\n"); |
dbf07cf0 | 187 | ret = PTR_ERR(mscm_ir_data->mscm_ir_base); |
0494e11a SA |
188 | goto out_free; |
189 | } | |
190 | ||
191 | mscm_cp_regmap = syscon_regmap_lookup_by_phandle(node, "fsl,cpucfg"); | |
192 | if (IS_ERR(mscm_cp_regmap)) { | |
193 | ret = PTR_ERR(mscm_cp_regmap); | |
194 | pr_err("vf610_mscm_ir: regmap lookup for cpucfg failed\n"); | |
195 | goto out_unmap; | |
196 | } | |
197 | ||
198 | regmap_read(mscm_cp_regmap, MSCM_CPxNUM, &cpuid); | |
199 | mscm_ir_data->cpu_mask = 0x1 << cpuid; | |
200 | ||
201 | domain = irq_domain_add_hierarchy(domain_parent, 0, | |
202 | MSCM_IRSPRC_NUM, node, | |
203 | &mscm_irq_domain_ops, mscm_ir_data); | |
204 | if (!domain) { | |
205 | ret = -ENOMEM; | |
206 | goto out_unmap; | |
207 | } | |
208 | ||
b5cc5cbc SA |
209 | if (of_device_is_compatible(domain->parent->of_node, "arm,armv7m-nvic")) |
210 | mscm_ir_data->is_nvic = true; | |
211 | ||
0494e11a SA |
212 | cpu_pm_register_notifier(&mscm_ir_notifier_block); |
213 | ||
214 | return 0; | |
215 | ||
216 | out_unmap: | |
217 | iounmap(mscm_ir_data->mscm_ir_base); | |
218 | out_free: | |
219 | kfree(mscm_ir_data); | |
220 | return ret; | |
221 | } | |
222 | IRQCHIP_DECLARE(vf610_mscm_ir, "fsl,vf610-mscm-ir", vf610_mscm_ir_of_init); |