diff options
author | Arto Merilainen <amerilainen@nvidia.com> | 2016-12-14 06:16:13 -0500 |
---|---|---|
committer | Thierry Reding <treding@nvidia.com> | 2017-04-05 12:11:48 -0400 |
commit | 0ae797a8ba05a2354db5e81c1d7df04671dd1c25 (patch) | |
tree | cd5a0fe4e47593fdafb7555f63003c89870e99b1 /drivers/gpu/drm/tegra/vic.c | |
parent | 6cc5f00f34c4c1a4891f976b71c0fb1c54a820ab (diff) |
drm/tegra: Add VIC support
This patch adds support for Video Image Compositor engine which
can be used for 2d operations.
Signed-off-by: Andrew Chew <achew@nvidia.com>
Signed-off-by: Arto Merilainen <amerilainen@nvidia.com>
Signed-off-by: Mikko Perttunen <mperttunen@nvidia.com>
Signed-off-by: Thierry Reding <treding@nvidia.com>
Diffstat (limited to 'drivers/gpu/drm/tegra/vic.c')
-rw-r--r-- | drivers/gpu/drm/tegra/vic.c | 396 |
1 files changed, 396 insertions, 0 deletions
diff --git a/drivers/gpu/drm/tegra/vic.c b/drivers/gpu/drm/tegra/vic.c new file mode 100644 index 000000000000..cd804e404a11 --- /dev/null +++ b/drivers/gpu/drm/tegra/vic.c | |||
@@ -0,0 +1,396 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2015, NVIDIA Corporation. | ||
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 as | ||
6 | * published by the Free Software Foundation. | ||
7 | */ | ||
8 | |||
9 | #include <linux/clk.h> | ||
10 | #include <linux/host1x.h> | ||
11 | #include <linux/iommu.h> | ||
12 | #include <linux/module.h> | ||
13 | #include <linux/of.h> | ||
14 | #include <linux/of_device.h> | ||
15 | #include <linux/of_platform.h> | ||
16 | #include <linux/platform_device.h> | ||
17 | #include <linux/pm_runtime.h> | ||
18 | #include <linux/reset.h> | ||
19 | |||
20 | #include <soc/tegra/pmc.h> | ||
21 | |||
22 | #include "drm.h" | ||
23 | #include "falcon.h" | ||
24 | #include "vic.h" | ||
25 | |||
26 | struct vic_config { | ||
27 | const char *firmware; | ||
28 | }; | ||
29 | |||
30 | struct vic { | ||
31 | struct falcon falcon; | ||
32 | bool booted; | ||
33 | |||
34 | void __iomem *regs; | ||
35 | struct tegra_drm_client client; | ||
36 | struct host1x_channel *channel; | ||
37 | struct iommu_domain *domain; | ||
38 | struct device *dev; | ||
39 | struct clk *clk; | ||
40 | |||
41 | /* Platform configuration */ | ||
42 | const struct vic_config *config; | ||
43 | }; | ||
44 | |||
45 | static inline struct vic *to_vic(struct tegra_drm_client *client) | ||
46 | { | ||
47 | return container_of(client, struct vic, client); | ||
48 | } | ||
49 | |||
50 | static void vic_writel(struct vic *vic, u32 value, unsigned int offset) | ||
51 | { | ||
52 | writel(value, vic->regs + offset); | ||
53 | } | ||
54 | |||
55 | static int vic_runtime_resume(struct device *dev) | ||
56 | { | ||
57 | struct vic *vic = dev_get_drvdata(dev); | ||
58 | |||
59 | return clk_prepare_enable(vic->clk); | ||
60 | } | ||
61 | |||
62 | static int vic_runtime_suspend(struct device *dev) | ||
63 | { | ||
64 | struct vic *vic = dev_get_drvdata(dev); | ||
65 | |||
66 | clk_disable_unprepare(vic->clk); | ||
67 | |||
68 | vic->booted = false; | ||
69 | |||
70 | return 0; | ||
71 | } | ||
72 | |||
73 | static int vic_boot(struct vic *vic) | ||
74 | { | ||
75 | u32 fce_ucode_size, fce_bin_data_offset; | ||
76 | void *hdr; | ||
77 | int err = 0; | ||
78 | |||
79 | if (vic->booted) | ||
80 | return 0; | ||
81 | |||
82 | /* setup clockgating registers */ | ||
83 | vic_writel(vic, CG_IDLE_CG_DLY_CNT(4) | | ||
84 | CG_IDLE_CG_EN | | ||
85 | CG_WAKEUP_DLY_CNT(4), | ||
86 | NV_PVIC_MISC_PRI_VIC_CG); | ||
87 | |||
88 | err = falcon_boot(&vic->falcon); | ||
89 | if (err < 0) | ||
90 | return err; | ||
91 | |||
92 | hdr = vic->falcon.firmware.vaddr; | ||
93 | fce_bin_data_offset = *(u32 *)(hdr + VIC_UCODE_FCE_DATA_OFFSET); | ||
94 | hdr = vic->falcon.firmware.vaddr + | ||
95 | *(u32 *)(hdr + VIC_UCODE_FCE_HEADER_OFFSET); | ||
96 | fce_ucode_size = *(u32 *)(hdr + FCE_UCODE_SIZE_OFFSET); | ||
97 | |||
98 | falcon_execute_method(&vic->falcon, VIC_SET_APPLICATION_ID, 1); | ||
99 | falcon_execute_method(&vic->falcon, VIC_SET_FCE_UCODE_SIZE, | ||
100 | fce_ucode_size); | ||
101 | falcon_execute_method(&vic->falcon, VIC_SET_FCE_UCODE_OFFSET, | ||
102 | (vic->falcon.firmware.paddr + fce_bin_data_offset) | ||
103 | >> 8); | ||
104 | |||
105 | err = falcon_wait_idle(&vic->falcon); | ||
106 | if (err < 0) { | ||
107 | dev_err(vic->dev, | ||
108 | "failed to set application ID and FCE base\n"); | ||
109 | return err; | ||
110 | } | ||
111 | |||
112 | vic->booted = true; | ||
113 | |||
114 | return 0; | ||
115 | } | ||
116 | |||
117 | static void *vic_falcon_alloc(struct falcon *falcon, size_t size, | ||
118 | dma_addr_t *iova) | ||
119 | { | ||
120 | struct tegra_drm *tegra = falcon->data; | ||
121 | |||
122 | return tegra_drm_alloc(tegra, size, iova); | ||
123 | } | ||
124 | |||
125 | static void vic_falcon_free(struct falcon *falcon, size_t size, | ||
126 | dma_addr_t iova, void *va) | ||
127 | { | ||
128 | struct tegra_drm *tegra = falcon->data; | ||
129 | |||
130 | return tegra_drm_free(tegra, size, va, iova); | ||
131 | } | ||
132 | |||
133 | static const struct falcon_ops vic_falcon_ops = { | ||
134 | .alloc = vic_falcon_alloc, | ||
135 | .free = vic_falcon_free | ||
136 | }; | ||
137 | |||
138 | static int vic_init(struct host1x_client *client) | ||
139 | { | ||
140 | struct tegra_drm_client *drm = host1x_to_drm_client(client); | ||
141 | struct drm_device *dev = dev_get_drvdata(client->parent); | ||
142 | struct tegra_drm *tegra = dev->dev_private; | ||
143 | struct vic *vic = to_vic(drm); | ||
144 | int err; | ||
145 | |||
146 | if (tegra->domain) { | ||
147 | err = iommu_attach_device(tegra->domain, vic->dev); | ||
148 | if (err < 0) { | ||
149 | dev_err(vic->dev, "failed to attach to domain: %d\n", | ||
150 | err); | ||
151 | return err; | ||
152 | } | ||
153 | |||
154 | vic->domain = tegra->domain; | ||
155 | } | ||
156 | |||
157 | if (!vic->falcon.data) { | ||
158 | vic->falcon.data = tegra; | ||
159 | err = falcon_load_firmware(&vic->falcon); | ||
160 | if (err < 0) | ||
161 | goto detach_device; | ||
162 | } | ||
163 | |||
164 | vic->channel = host1x_channel_request(client->dev); | ||
165 | if (!vic->channel) { | ||
166 | err = -ENOMEM; | ||
167 | goto detach_device; | ||
168 | } | ||
169 | |||
170 | client->syncpts[0] = host1x_syncpt_request(client->dev, 0); | ||
171 | if (!client->syncpts[0]) { | ||
172 | err = -ENOMEM; | ||
173 | goto free_channel; | ||
174 | } | ||
175 | |||
176 | err = tegra_drm_register_client(tegra, drm); | ||
177 | if (err < 0) | ||
178 | goto free_syncpt; | ||
179 | |||
180 | return 0; | ||
181 | |||
182 | free_syncpt: | ||
183 | host1x_syncpt_free(client->syncpts[0]); | ||
184 | free_channel: | ||
185 | host1x_channel_free(vic->channel); | ||
186 | detach_device: | ||
187 | if (tegra->domain) | ||
188 | iommu_detach_device(tegra->domain, vic->dev); | ||
189 | |||
190 | return err; | ||
191 | } | ||
192 | |||
193 | static int vic_exit(struct host1x_client *client) | ||
194 | { | ||
195 | struct tegra_drm_client *drm = host1x_to_drm_client(client); | ||
196 | struct drm_device *dev = dev_get_drvdata(client->parent); | ||
197 | struct tegra_drm *tegra = dev->dev_private; | ||
198 | struct vic *vic = to_vic(drm); | ||
199 | int err; | ||
200 | |||
201 | err = tegra_drm_unregister_client(tegra, drm); | ||
202 | if (err < 0) | ||
203 | return err; | ||
204 | |||
205 | host1x_syncpt_free(client->syncpts[0]); | ||
206 | host1x_channel_free(vic->channel); | ||
207 | |||
208 | if (vic->domain) { | ||
209 | iommu_detach_device(vic->domain, vic->dev); | ||
210 | vic->domain = NULL; | ||
211 | } | ||
212 | |||
213 | return 0; | ||
214 | } | ||
215 | |||
216 | static const struct host1x_client_ops vic_client_ops = { | ||
217 | .init = vic_init, | ||
218 | .exit = vic_exit, | ||
219 | }; | ||
220 | |||
221 | static int vic_open_channel(struct tegra_drm_client *client, | ||
222 | struct tegra_drm_context *context) | ||
223 | { | ||
224 | struct vic *vic = to_vic(client); | ||
225 | int err; | ||
226 | |||
227 | err = pm_runtime_get_sync(vic->dev); | ||
228 | if (err < 0) | ||
229 | return err; | ||
230 | |||
231 | err = vic_boot(vic); | ||
232 | if (err < 0) { | ||
233 | pm_runtime_put(vic->dev); | ||
234 | return err; | ||
235 | } | ||
236 | |||
237 | context->channel = host1x_channel_get(vic->channel); | ||
238 | if (!context->channel) { | ||
239 | pm_runtime_put(vic->dev); | ||
240 | return -ENOMEM; | ||
241 | } | ||
242 | |||
243 | return 0; | ||
244 | } | ||
245 | |||
246 | static void vic_close_channel(struct tegra_drm_context *context) | ||
247 | { | ||
248 | struct vic *vic = to_vic(context->client); | ||
249 | |||
250 | host1x_channel_put(context->channel); | ||
251 | |||
252 | pm_runtime_put(vic->dev); | ||
253 | } | ||
254 | |||
255 | static const struct tegra_drm_client_ops vic_ops = { | ||
256 | .open_channel = vic_open_channel, | ||
257 | .close_channel = vic_close_channel, | ||
258 | .submit = tegra_drm_submit, | ||
259 | }; | ||
260 | |||
261 | static const struct vic_config vic_t124_config = { | ||
262 | .firmware = "nvidia/tegra124/vic03_ucode.bin", | ||
263 | }; | ||
264 | |||
265 | static const struct vic_config vic_t210_config = { | ||
266 | .firmware = "nvidia/tegra210/vic04_ucode.bin", | ||
267 | }; | ||
268 | |||
269 | static const struct of_device_id vic_match[] = { | ||
270 | { .compatible = "nvidia,tegra124-vic", .data = &vic_t124_config }, | ||
271 | { .compatible = "nvidia,tegra210-vic", .data = &vic_t210_config }, | ||
272 | { }, | ||
273 | }; | ||
274 | |||
275 | static int vic_probe(struct platform_device *pdev) | ||
276 | { | ||
277 | struct vic_config *vic_config = NULL; | ||
278 | struct device *dev = &pdev->dev; | ||
279 | struct host1x_syncpt **syncpts; | ||
280 | struct resource *regs; | ||
281 | const struct of_device_id *match; | ||
282 | struct vic *vic; | ||
283 | int err; | ||
284 | |||
285 | match = of_match_device(vic_match, dev); | ||
286 | vic_config = (struct vic_config *)match->data; | ||
287 | |||
288 | vic = devm_kzalloc(dev, sizeof(*vic), GFP_KERNEL); | ||
289 | if (!vic) | ||
290 | return -ENOMEM; | ||
291 | |||
292 | syncpts = devm_kzalloc(dev, sizeof(*syncpts), GFP_KERNEL); | ||
293 | if (!syncpts) | ||
294 | return -ENOMEM; | ||
295 | |||
296 | regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
297 | if (!regs) { | ||
298 | dev_err(&pdev->dev, "failed to get registers\n"); | ||
299 | return -ENXIO; | ||
300 | } | ||
301 | |||
302 | vic->regs = devm_ioremap_resource(dev, regs); | ||
303 | if (IS_ERR(vic->regs)) | ||
304 | return PTR_ERR(vic->regs); | ||
305 | |||
306 | vic->clk = devm_clk_get(dev, NULL); | ||
307 | if (IS_ERR(vic->clk)) { | ||
308 | dev_err(&pdev->dev, "failed to get clock\n"); | ||
309 | return PTR_ERR(vic->clk); | ||
310 | } | ||
311 | |||
312 | vic->falcon.dev = dev; | ||
313 | vic->falcon.regs = vic->regs; | ||
314 | vic->falcon.ops = &vic_falcon_ops; | ||
315 | |||
316 | err = falcon_init(&vic->falcon); | ||
317 | if (err < 0) | ||
318 | return err; | ||
319 | |||
320 | err = falcon_read_firmware(&vic->falcon, vic_config->firmware); | ||
321 | if (err < 0) | ||
322 | goto exit_falcon; | ||
323 | |||
324 | platform_set_drvdata(pdev, vic); | ||
325 | |||
326 | INIT_LIST_HEAD(&vic->client.base.list); | ||
327 | vic->client.base.ops = &vic_client_ops; | ||
328 | vic->client.base.dev = dev; | ||
329 | vic->client.base.class = HOST1X_CLASS_VIC; | ||
330 | vic->client.base.syncpts = syncpts; | ||
331 | vic->client.base.num_syncpts = 1; | ||
332 | vic->dev = dev; | ||
333 | vic->config = vic_config; | ||
334 | |||
335 | INIT_LIST_HEAD(&vic->client.list); | ||
336 | vic->client.ops = &vic_ops; | ||
337 | |||
338 | err = host1x_client_register(&vic->client.base); | ||
339 | if (err < 0) { | ||
340 | dev_err(dev, "failed to register host1x client: %d\n", err); | ||
341 | platform_set_drvdata(pdev, NULL); | ||
342 | goto exit_falcon; | ||
343 | } | ||
344 | |||
345 | pm_runtime_enable(&pdev->dev); | ||
346 | if (!pm_runtime_enabled(&pdev->dev)) { | ||
347 | err = vic_runtime_resume(&pdev->dev); | ||
348 | if (err < 0) | ||
349 | goto unregister_client; | ||
350 | } | ||
351 | |||
352 | return 0; | ||
353 | |||
354 | unregister_client: | ||
355 | host1x_client_unregister(&vic->client.base); | ||
356 | exit_falcon: | ||
357 | falcon_exit(&vic->falcon); | ||
358 | |||
359 | return err; | ||
360 | } | ||
361 | |||
362 | static int vic_remove(struct platform_device *pdev) | ||
363 | { | ||
364 | struct vic *vic = platform_get_drvdata(pdev); | ||
365 | int err; | ||
366 | |||
367 | err = host1x_client_unregister(&vic->client.base); | ||
368 | if (err < 0) { | ||
369 | dev_err(&pdev->dev, "failed to unregister host1x client: %d\n", | ||
370 | err); | ||
371 | return err; | ||
372 | } | ||
373 | |||
374 | if (pm_runtime_enabled(&pdev->dev)) | ||
375 | pm_runtime_disable(&pdev->dev); | ||
376 | else | ||
377 | vic_runtime_suspend(&pdev->dev); | ||
378 | |||
379 | falcon_exit(&vic->falcon); | ||
380 | |||
381 | return 0; | ||
382 | } | ||
383 | |||
384 | static const struct dev_pm_ops vic_pm_ops = { | ||
385 | SET_RUNTIME_PM_OPS(vic_runtime_suspend, vic_runtime_resume, NULL) | ||
386 | }; | ||
387 | |||
388 | struct platform_driver tegra_vic_driver = { | ||
389 | .driver = { | ||
390 | .name = "tegra-vic", | ||
391 | .of_match_table = vic_match, | ||
392 | .pm = &vic_pm_ops | ||
393 | }, | ||
394 | .probe = vic_probe, | ||
395 | .remove = vic_remove, | ||
396 | }; | ||