]>
Commit | Line | Data |
---|---|---|
a0ff4aa6 OR |
1 | /* |
2 | * Copyright (c) 2017 Pengutronix, Oleksij Rempel <[email protected]> | |
3 | * | |
4 | * This program is free software; you can redistribute it and/or modify | |
5 | * it under the terms of the GNU General Public License version 2 | |
6 | * as published by the Free Software Foundation. | |
7 | */ | |
8 | ||
9 | #include <linux/clk.h> | |
10 | #include <linux/err.h> | |
11 | #include <linux/interrupt.h> | |
12 | #include <linux/kernel.h> | |
13 | #include <linux/mfd/syscon.h> | |
14 | #include <linux/module.h> | |
15 | #include <linux/of_address.h> | |
16 | #include <linux/of_device.h> | |
17 | #include <linux/platform_device.h> | |
18 | #include <linux/regmap.h> | |
19 | #include <linux/remoteproc.h> | |
20 | ||
21 | #define IMX7D_SRC_SCR 0x0C | |
22 | #define IMX7D_ENABLE_M4 BIT(3) | |
23 | #define IMX7D_SW_M4P_RST BIT(2) | |
24 | #define IMX7D_SW_M4C_RST BIT(1) | |
25 | #define IMX7D_SW_M4C_NON_SCLR_RST BIT(0) | |
26 | ||
27 | #define IMX7D_M4_RST_MASK (IMX7D_ENABLE_M4 | IMX7D_SW_M4P_RST \ | |
28 | | IMX7D_SW_M4C_RST \ | |
29 | | IMX7D_SW_M4C_NON_SCLR_RST) | |
30 | ||
31 | #define IMX7D_M4_START (IMX7D_ENABLE_M4 | IMX7D_SW_M4P_RST \ | |
32 | | IMX7D_SW_M4C_RST) | |
33 | #define IMX7D_M4_STOP IMX7D_SW_M4C_NON_SCLR_RST | |
34 | ||
35 | /* Address: 0x020D8000 */ | |
36 | #define IMX6SX_SRC_SCR 0x00 | |
37 | #define IMX6SX_ENABLE_M4 BIT(22) | |
38 | #define IMX6SX_SW_M4P_RST BIT(12) | |
39 | #define IMX6SX_SW_M4C_NON_SCLR_RST BIT(4) | |
40 | #define IMX6SX_SW_M4C_RST BIT(3) | |
41 | ||
42 | #define IMX6SX_M4_START (IMX6SX_ENABLE_M4 | IMX6SX_SW_M4P_RST \ | |
43 | | IMX6SX_SW_M4C_RST) | |
44 | #define IMX6SX_M4_STOP IMX6SX_SW_M4C_NON_SCLR_RST | |
45 | #define IMX6SX_M4_RST_MASK (IMX6SX_ENABLE_M4 | IMX6SX_SW_M4P_RST \ | |
46 | | IMX6SX_SW_M4C_NON_SCLR_RST \ | |
47 | | IMX6SX_SW_M4C_RST) | |
48 | ||
49 | #define IMX7D_RPROC_MEM_MAX 8 | |
50 | ||
51 | /** | |
52 | * struct imx_rproc_mem - slim internal memory structure | |
53 | * @cpu_addr: MPU virtual address of the memory region | |
54 | * @sys_addr: Bus address used to access the memory region | |
55 | * @size: Size of the memory region | |
56 | */ | |
57 | struct imx_rproc_mem { | |
58 | void __iomem *cpu_addr; | |
59 | phys_addr_t sys_addr; | |
60 | size_t size; | |
61 | }; | |
62 | ||
63 | /* att flags */ | |
64 | /* M4 own area. Can be mapped at probe */ | |
65 | #define ATT_OWN BIT(1) | |
66 | ||
67 | /* address translation table */ | |
68 | struct imx_rproc_att { | |
69 | u32 da; /* device address (From Cortex M4 view)*/ | |
70 | u32 sa; /* system bus address */ | |
71 | u32 size; /* size of reg range */ | |
72 | int flags; | |
73 | }; | |
74 | ||
75 | struct imx_rproc_dcfg { | |
76 | u32 src_reg; | |
77 | u32 src_mask; | |
78 | u32 src_start; | |
79 | u32 src_stop; | |
80 | const struct imx_rproc_att *att; | |
81 | size_t att_size; | |
82 | }; | |
83 | ||
84 | struct imx_rproc { | |
85 | struct device *dev; | |
86 | struct regmap *regmap; | |
87 | struct rproc *rproc; | |
88 | const struct imx_rproc_dcfg *dcfg; | |
89 | struct imx_rproc_mem mem[IMX7D_RPROC_MEM_MAX]; | |
90 | struct clk *clk; | |
91 | }; | |
92 | ||
93 | static const struct imx_rproc_att imx_rproc_att_imx7d[] = { | |
94 | /* dev addr , sys addr , size , flags */ | |
95 | /* OCRAM_S (M4 Boot code) - alias */ | |
96 | { 0x00000000, 0x00180000, 0x00008000, 0 }, | |
97 | /* OCRAM_S (Code) */ | |
98 | { 0x00180000, 0x00180000, 0x00008000, ATT_OWN }, | |
99 | /* OCRAM (Code) - alias */ | |
100 | { 0x00900000, 0x00900000, 0x00020000, 0 }, | |
101 | /* OCRAM_EPDC (Code) - alias */ | |
102 | { 0x00920000, 0x00920000, 0x00020000, 0 }, | |
103 | /* OCRAM_PXP (Code) - alias */ | |
104 | { 0x00940000, 0x00940000, 0x00008000, 0 }, | |
105 | /* TCML (Code) */ | |
106 | { 0x1FFF8000, 0x007F8000, 0x00008000, ATT_OWN }, | |
107 | /* DDR (Code) - alias, first part of DDR (Data) */ | |
108 | { 0x10000000, 0x80000000, 0x0FFF0000, 0 }, | |
109 | ||
110 | /* TCMU (Data) */ | |
111 | { 0x20000000, 0x00800000, 0x00008000, ATT_OWN }, | |
112 | /* OCRAM (Data) */ | |
113 | { 0x20200000, 0x00900000, 0x00020000, 0 }, | |
114 | /* OCRAM_EPDC (Data) */ | |
115 | { 0x20220000, 0x00920000, 0x00020000, 0 }, | |
116 | /* OCRAM_PXP (Data) */ | |
117 | { 0x20240000, 0x00940000, 0x00008000, 0 }, | |
118 | /* DDR (Data) */ | |
119 | { 0x80000000, 0x80000000, 0x60000000, 0 }, | |
120 | }; | |
121 | ||
122 | static const struct imx_rproc_att imx_rproc_att_imx6sx[] = { | |
123 | /* dev addr , sys addr , size , flags */ | |
124 | /* TCML (M4 Boot Code) - alias */ | |
125 | { 0x00000000, 0x007F8000, 0x00008000, 0 }, | |
126 | /* OCRAM_S (Code) */ | |
127 | { 0x00180000, 0x008F8000, 0x00004000, 0 }, | |
128 | /* OCRAM_S (Code) - alias */ | |
129 | { 0x00180000, 0x008FC000, 0x00004000, 0 }, | |
130 | /* TCML (Code) */ | |
131 | { 0x1FFF8000, 0x007F8000, 0x00008000, ATT_OWN }, | |
132 | /* DDR (Code) - alias, first part of DDR (Data) */ | |
133 | { 0x10000000, 0x80000000, 0x0FFF8000, 0 }, | |
134 | ||
135 | /* TCMU (Data) */ | |
136 | { 0x20000000, 0x00800000, 0x00008000, ATT_OWN }, | |
137 | /* OCRAM_S (Data) - alias? */ | |
138 | { 0x208F8000, 0x008F8000, 0x00004000, 0 }, | |
139 | /* DDR (Data) */ | |
140 | { 0x80000000, 0x80000000, 0x60000000, 0 }, | |
141 | }; | |
142 | ||
143 | static const struct imx_rproc_dcfg imx_rproc_cfg_imx7d = { | |
144 | .src_reg = IMX7D_SRC_SCR, | |
145 | .src_mask = IMX7D_M4_RST_MASK, | |
146 | .src_start = IMX7D_M4_START, | |
147 | .src_stop = IMX7D_M4_STOP, | |
148 | .att = imx_rproc_att_imx7d, | |
149 | .att_size = ARRAY_SIZE(imx_rproc_att_imx7d), | |
150 | }; | |
151 | ||
152 | static const struct imx_rproc_dcfg imx_rproc_cfg_imx6sx = { | |
153 | .src_reg = IMX6SX_SRC_SCR, | |
154 | .src_mask = IMX6SX_M4_RST_MASK, | |
155 | .src_start = IMX6SX_M4_START, | |
156 | .src_stop = IMX6SX_M4_STOP, | |
157 | .att = imx_rproc_att_imx6sx, | |
158 | .att_size = ARRAY_SIZE(imx_rproc_att_imx6sx), | |
159 | }; | |
160 | ||
161 | static int imx_rproc_start(struct rproc *rproc) | |
162 | { | |
163 | struct imx_rproc *priv = rproc->priv; | |
164 | const struct imx_rproc_dcfg *dcfg = priv->dcfg; | |
165 | struct device *dev = priv->dev; | |
166 | int ret; | |
167 | ||
168 | ret = regmap_update_bits(priv->regmap, dcfg->src_reg, | |
169 | dcfg->src_mask, dcfg->src_start); | |
170 | if (ret) | |
171 | dev_err(dev, "Filed to enable M4!\n"); | |
172 | ||
173 | return ret; | |
174 | } | |
175 | ||
176 | static int imx_rproc_stop(struct rproc *rproc) | |
177 | { | |
178 | struct imx_rproc *priv = rproc->priv; | |
179 | const struct imx_rproc_dcfg *dcfg = priv->dcfg; | |
180 | struct device *dev = priv->dev; | |
181 | int ret; | |
182 | ||
183 | ret = regmap_update_bits(priv->regmap, dcfg->src_reg, | |
184 | dcfg->src_mask, dcfg->src_stop); | |
185 | if (ret) | |
186 | dev_err(dev, "Filed to stop M4!\n"); | |
187 | ||
188 | return ret; | |
189 | } | |
190 | ||
191 | static int imx_rproc_da_to_sys(struct imx_rproc *priv, u64 da, | |
192 | int len, u64 *sys) | |
193 | { | |
194 | const struct imx_rproc_dcfg *dcfg = priv->dcfg; | |
195 | int i; | |
196 | ||
197 | /* parse address translation table */ | |
198 | for (i = 0; i < dcfg->att_size; i++) { | |
199 | const struct imx_rproc_att *att = &dcfg->att[i]; | |
200 | ||
201 | if (da >= att->da && da + len < att->da + att->size) { | |
202 | unsigned int offset = da - att->da; | |
203 | ||
204 | *sys = att->sa + offset; | |
205 | return 0; | |
206 | } | |
207 | } | |
208 | ||
209 | dev_warn(priv->dev, "Translation filed: da = 0x%llx len = 0x%x\n", | |
210 | da, len); | |
211 | return -ENOENT; | |
212 | } | |
213 | ||
214 | static void *imx_rproc_da_to_va(struct rproc *rproc, u64 da, int len) | |
215 | { | |
216 | struct imx_rproc *priv = rproc->priv; | |
217 | void *va = NULL; | |
218 | u64 sys; | |
219 | int i; | |
220 | ||
221 | if (len <= 0) | |
222 | return NULL; | |
223 | ||
224 | /* | |
225 | * On device side we have many aliases, so we need to convert device | |
226 | * address (M4) to system bus address first. | |
227 | */ | |
228 | if (imx_rproc_da_to_sys(priv, da, len, &sys)) | |
229 | return NULL; | |
230 | ||
231 | for (i = 0; i < IMX7D_RPROC_MEM_MAX; i++) { | |
232 | if (sys >= priv->mem[i].sys_addr && sys + len < | |
233 | priv->mem[i].sys_addr + priv->mem[i].size) { | |
234 | unsigned int offset = sys - priv->mem[i].sys_addr; | |
235 | /* __force to make sparse happy with type conversion */ | |
236 | va = (__force void *)(priv->mem[i].cpu_addr + offset); | |
237 | break; | |
238 | } | |
239 | } | |
240 | ||
241 | dev_dbg(&rproc->dev, "da = 0x%llx len = 0x%x va = 0x%p\n", da, len, va); | |
242 | ||
243 | return va; | |
244 | } | |
245 | ||
246 | static const struct rproc_ops imx_rproc_ops = { | |
247 | .start = imx_rproc_start, | |
248 | .stop = imx_rproc_stop, | |
249 | .da_to_va = imx_rproc_da_to_va, | |
250 | }; | |
251 | ||
252 | static int imx_rproc_addr_init(struct imx_rproc *priv, | |
253 | struct platform_device *pdev) | |
254 | { | |
255 | const struct imx_rproc_dcfg *dcfg = priv->dcfg; | |
256 | struct device *dev = &pdev->dev; | |
257 | struct device_node *np = dev->of_node; | |
258 | int a, b = 0, err, nph; | |
259 | ||
260 | /* remap required addresses */ | |
261 | for (a = 0; a < dcfg->att_size; a++) { | |
262 | const struct imx_rproc_att *att = &dcfg->att[a]; | |
263 | ||
264 | if (!(att->flags & ATT_OWN)) | |
265 | continue; | |
266 | ||
68c2d645 | 267 | if (b >= IMX7D_RPROC_MEM_MAX) |
a0ff4aa6 OR |
268 | break; |
269 | ||
270 | priv->mem[b].cpu_addr = devm_ioremap(&pdev->dev, | |
271 | att->sa, att->size); | |
68a39a3e | 272 | if (!priv->mem[b].cpu_addr) { |
a0ff4aa6 | 273 | dev_err(dev, "devm_ioremap_resource failed\n"); |
68a39a3e | 274 | return -ENOMEM; |
a0ff4aa6 OR |
275 | } |
276 | priv->mem[b].sys_addr = att->sa; | |
277 | priv->mem[b].size = att->size; | |
278 | b++; | |
279 | } | |
280 | ||
281 | /* memory-region is optional property */ | |
282 | nph = of_count_phandle_with_args(np, "memory-region", NULL); | |
283 | if (nph <= 0) | |
284 | return 0; | |
285 | ||
286 | /* remap optional addresses */ | |
287 | for (a = 0; a < nph; a++) { | |
288 | struct device_node *node; | |
289 | struct resource res; | |
290 | ||
291 | node = of_parse_phandle(np, "memory-region", a); | |
292 | err = of_address_to_resource(node, 0, &res); | |
293 | if (err) { | |
294 | dev_err(dev, "unable to resolve memory region\n"); | |
295 | return err; | |
296 | } | |
297 | ||
68c2d645 | 298 | if (b >= IMX7D_RPROC_MEM_MAX) |
a0ff4aa6 OR |
299 | break; |
300 | ||
301 | priv->mem[b].cpu_addr = devm_ioremap_resource(&pdev->dev, &res); | |
302 | if (IS_ERR(priv->mem[b].cpu_addr)) { | |
303 | dev_err(dev, "devm_ioremap_resource failed\n"); | |
304 | err = PTR_ERR(priv->mem[b].cpu_addr); | |
305 | return err; | |
306 | } | |
307 | priv->mem[b].sys_addr = res.start; | |
308 | priv->mem[b].size = resource_size(&res); | |
309 | b++; | |
310 | } | |
311 | ||
312 | return 0; | |
313 | } | |
314 | ||
315 | static int imx_rproc_probe(struct platform_device *pdev) | |
316 | { | |
317 | struct device *dev = &pdev->dev; | |
318 | struct device_node *np = dev->of_node; | |
319 | struct imx_rproc *priv; | |
320 | struct rproc *rproc; | |
321 | struct regmap_config config = { .name = "imx-rproc" }; | |
322 | const struct imx_rproc_dcfg *dcfg; | |
323 | struct regmap *regmap; | |
324 | int ret; | |
325 | ||
326 | regmap = syscon_regmap_lookup_by_phandle(np, "syscon"); | |
327 | if (IS_ERR(regmap)) { | |
328 | dev_err(dev, "failed to find syscon\n"); | |
329 | return PTR_ERR(regmap); | |
330 | } | |
331 | regmap_attach_dev(dev, regmap, &config); | |
332 | ||
333 | /* set some other name then imx */ | |
334 | rproc = rproc_alloc(dev, "imx-rproc", &imx_rproc_ops, | |
335 | NULL, sizeof(*priv)); | |
99a31adf CJ |
336 | if (!rproc) |
337 | return -ENOMEM; | |
a0ff4aa6 OR |
338 | |
339 | dcfg = of_device_get_match_data(dev); | |
de6f83f8 CJ |
340 | if (!dcfg) { |
341 | ret = -EINVAL; | |
342 | goto err_put_rproc; | |
343 | } | |
a0ff4aa6 OR |
344 | |
345 | priv = rproc->priv; | |
346 | priv->rproc = rproc; | |
347 | priv->regmap = regmap; | |
348 | priv->dcfg = dcfg; | |
349 | priv->dev = dev; | |
350 | ||
351 | dev_set_drvdata(dev, rproc); | |
352 | ||
353 | ret = imx_rproc_addr_init(priv, pdev); | |
354 | if (ret) { | |
355 | dev_err(dev, "filed on imx_rproc_addr_init\n"); | |
356 | goto err_put_rproc; | |
357 | } | |
358 | ||
359 | priv->clk = devm_clk_get(dev, NULL); | |
360 | if (IS_ERR(priv->clk)) { | |
361 | dev_err(dev, "Failed to get clock\n"); | |
96a30d7f CJ |
362 | ret = PTR_ERR(priv->clk); |
363 | goto err_put_rproc; | |
a0ff4aa6 OR |
364 | } |
365 | ||
366 | /* | |
367 | * clk for M4 block including memory. Should be | |
368 | * enabled before .start for FW transfer. | |
369 | */ | |
370 | ret = clk_prepare_enable(priv->clk); | |
371 | if (ret) { | |
372 | dev_err(&rproc->dev, "Failed to enable clock\n"); | |
96a30d7f | 373 | goto err_put_rproc; |
a0ff4aa6 OR |
374 | } |
375 | ||
376 | ret = rproc_add(rproc); | |
377 | if (ret) { | |
378 | dev_err(dev, "rproc_add failed\n"); | |
379 | goto err_put_clk; | |
380 | } | |
381 | ||
99a31adf | 382 | return 0; |
a0ff4aa6 OR |
383 | |
384 | err_put_clk: | |
385 | clk_disable_unprepare(priv->clk); | |
386 | err_put_rproc: | |
387 | rproc_free(rproc); | |
99a31adf | 388 | |
a0ff4aa6 OR |
389 | return ret; |
390 | } | |
391 | ||
392 | static int imx_rproc_remove(struct platform_device *pdev) | |
393 | { | |
394 | struct rproc *rproc = platform_get_drvdata(pdev); | |
395 | struct imx_rproc *priv = rproc->priv; | |
396 | ||
397 | clk_disable_unprepare(priv->clk); | |
398 | rproc_del(rproc); | |
399 | rproc_free(rproc); | |
400 | ||
401 | return 0; | |
402 | } | |
403 | ||
404 | static const struct of_device_id imx_rproc_of_match[] = { | |
405 | { .compatible = "fsl,imx7d-cm4", .data = &imx_rproc_cfg_imx7d }, | |
406 | { .compatible = "fsl,imx6sx-cm4", .data = &imx_rproc_cfg_imx6sx }, | |
407 | {}, | |
408 | }; | |
409 | MODULE_DEVICE_TABLE(of, imx_rproc_of_match); | |
410 | ||
411 | static struct platform_driver imx_rproc_driver = { | |
412 | .probe = imx_rproc_probe, | |
413 | .remove = imx_rproc_remove, | |
414 | .driver = { | |
415 | .name = "imx-rproc", | |
416 | .of_match_table = imx_rproc_of_match, | |
417 | }, | |
418 | }; | |
419 | ||
420 | module_platform_driver(imx_rproc_driver); | |
421 | ||
422 | MODULE_LICENSE("GPL v2"); | |
423 | MODULE_DESCRIPTION("IMX6SX/7D remote processor control driver"); | |
424 | MODULE_AUTHOR("Oleksij Rempel <[email protected]>"); |