]>
Commit | Line | Data |
---|---|---|
af2c3834 SV |
1 | /* |
2 | * Copyright (c) 2012-2016, The Linux Foundation. All rights reserved. | |
3 | * Copyright (C) 2017 Linaro Ltd. | |
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 and | |
7 | * only version 2 as published by the Free Software Foundation. | |
8 | * | |
9 | * This program is distributed in the hope that it will be useful, | |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | * GNU General Public License for more details. | |
13 | * | |
14 | */ | |
15 | #include <linux/clk.h> | |
16 | #include <linux/init.h> | |
17 | #include <linux/ioctl.h> | |
18 | #include <linux/list.h> | |
19 | #include <linux/module.h> | |
20 | #include <linux/of_device.h> | |
21 | #include <linux/platform_device.h> | |
22 | #include <linux/slab.h> | |
23 | #include <linux/types.h> | |
24 | #include <linux/pm_runtime.h> | |
25 | #include <media/videobuf2-v4l2.h> | |
26 | #include <media/v4l2-mem2mem.h> | |
27 | #include <media/v4l2-ioctl.h> | |
28 | ||
29 | #include "core.h" | |
30 | #include "vdec.h" | |
31 | #include "venc.h" | |
32 | #include "firmware.h" | |
33 | ||
34 | static void venus_event_notify(struct venus_core *core, u32 event) | |
35 | { | |
36 | struct venus_inst *inst; | |
37 | ||
38 | switch (event) { | |
39 | case EVT_SYS_WATCHDOG_TIMEOUT: | |
40 | case EVT_SYS_ERROR: | |
41 | break; | |
42 | default: | |
43 | return; | |
44 | } | |
45 | ||
46 | mutex_lock(&core->lock); | |
47 | core->sys_error = true; | |
48 | list_for_each_entry(inst, &core->instances, list) | |
49 | inst->ops->event_notify(inst, EVT_SESSION_ERROR, NULL); | |
50 | mutex_unlock(&core->lock); | |
51 | ||
52 | disable_irq_nosync(core->irq); | |
53 | ||
54 | /* | |
55 | * Delay recovery to ensure venus has completed any pending cache | |
56 | * operations. Without this sleep, we see device reset when firmware is | |
57 | * unloaded after a system error. | |
58 | */ | |
59 | schedule_delayed_work(&core->work, msecs_to_jiffies(100)); | |
60 | } | |
61 | ||
62 | static const struct hfi_core_ops venus_core_ops = { | |
63 | .event_notify = venus_event_notify, | |
64 | }; | |
65 | ||
66 | static void venus_sys_error_handler(struct work_struct *work) | |
67 | { | |
68 | struct venus_core *core = | |
69 | container_of(work, struct venus_core, work.work); | |
70 | int ret = 0; | |
71 | ||
72 | dev_warn(core->dev, "system error has occurred, starting recovery!\n"); | |
73 | ||
74 | pm_runtime_get_sync(core->dev); | |
75 | ||
76 | hfi_core_deinit(core, true); | |
77 | hfi_destroy(core); | |
78 | mutex_lock(&core->lock); | |
377a22d3 | 79 | venus_shutdown(core->dev); |
af2c3834 SV |
80 | |
81 | pm_runtime_put_sync(core->dev); | |
82 | ||
83 | ret |= hfi_create(core, &venus_core_ops); | |
84 | ||
85 | pm_runtime_get_sync(core->dev); | |
86 | ||
377a22d3 | 87 | ret |= venus_boot(core->dev, core->res->fwname); |
af2c3834 SV |
88 | |
89 | ret |= hfi_core_resume(core, true); | |
90 | ||
91 | enable_irq(core->irq); | |
92 | ||
93 | mutex_unlock(&core->lock); | |
94 | ||
95 | ret |= hfi_core_init(core); | |
96 | ||
97 | pm_runtime_put_sync(core->dev); | |
98 | ||
99 | if (ret) { | |
100 | disable_irq_nosync(core->irq); | |
101 | dev_warn(core->dev, "recovery failed (%d)\n", ret); | |
102 | schedule_delayed_work(&core->work, msecs_to_jiffies(10)); | |
103 | return; | |
104 | } | |
105 | ||
106 | mutex_lock(&core->lock); | |
107 | core->sys_error = false; | |
108 | mutex_unlock(&core->lock); | |
109 | } | |
110 | ||
111 | static int venus_clks_get(struct venus_core *core) | |
112 | { | |
113 | const struct venus_resources *res = core->res; | |
114 | struct device *dev = core->dev; | |
115 | unsigned int i; | |
116 | ||
117 | for (i = 0; i < res->clks_num; i++) { | |
118 | core->clks[i] = devm_clk_get(dev, res->clks[i]); | |
119 | if (IS_ERR(core->clks[i])) | |
120 | return PTR_ERR(core->clks[i]); | |
121 | } | |
122 | ||
123 | return 0; | |
124 | } | |
125 | ||
126 | static int venus_clks_enable(struct venus_core *core) | |
127 | { | |
128 | const struct venus_resources *res = core->res; | |
129 | unsigned int i; | |
130 | int ret; | |
131 | ||
132 | for (i = 0; i < res->clks_num; i++) { | |
133 | ret = clk_prepare_enable(core->clks[i]); | |
134 | if (ret) | |
135 | goto err; | |
136 | } | |
137 | ||
138 | return 0; | |
139 | err: | |
bc8e2d62 | 140 | while (i--) |
af2c3834 SV |
141 | clk_disable_unprepare(core->clks[i]); |
142 | ||
143 | return ret; | |
144 | } | |
145 | ||
146 | static void venus_clks_disable(struct venus_core *core) | |
147 | { | |
148 | const struct venus_resources *res = core->res; | |
149 | unsigned int i = res->clks_num; | |
150 | ||
151 | while (i--) | |
152 | clk_disable_unprepare(core->clks[i]); | |
153 | } | |
154 | ||
155 | static int venus_probe(struct platform_device *pdev) | |
156 | { | |
157 | struct device *dev = &pdev->dev; | |
158 | struct venus_core *core; | |
159 | struct resource *r; | |
160 | int ret; | |
161 | ||
162 | core = devm_kzalloc(dev, sizeof(*core), GFP_KERNEL); | |
163 | if (!core) | |
164 | return -ENOMEM; | |
165 | ||
166 | core->dev = dev; | |
167 | platform_set_drvdata(pdev, core); | |
168 | ||
169 | r = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
170 | core->base = devm_ioremap_resource(dev, r); | |
171 | if (IS_ERR(core->base)) | |
172 | return PTR_ERR(core->base); | |
173 | ||
174 | core->irq = platform_get_irq(pdev, 0); | |
175 | if (core->irq < 0) | |
176 | return core->irq; | |
177 | ||
178 | core->res = of_device_get_match_data(dev); | |
179 | if (!core->res) | |
180 | return -ENODEV; | |
181 | ||
182 | ret = venus_clks_get(core); | |
183 | if (ret) | |
184 | return ret; | |
185 | ||
186 | ret = dma_set_mask_and_coherent(dev, core->res->dma_mask); | |
187 | if (ret) | |
188 | return ret; | |
189 | ||
190 | INIT_LIST_HEAD(&core->instances); | |
191 | mutex_init(&core->lock); | |
192 | INIT_DELAYED_WORK(&core->work, venus_sys_error_handler); | |
193 | ||
194 | ret = devm_request_threaded_irq(dev, core->irq, hfi_isr, hfi_isr_thread, | |
195 | IRQF_TRIGGER_HIGH | IRQF_ONESHOT, | |
196 | "venus", core); | |
197 | if (ret) | |
198 | return ret; | |
199 | ||
200 | ret = hfi_create(core, &venus_core_ops); | |
201 | if (ret) | |
202 | return ret; | |
203 | ||
204 | pm_runtime_enable(dev); | |
205 | ||
206 | ret = pm_runtime_get_sync(dev); | |
207 | if (ret < 0) | |
208 | goto err_runtime_disable; | |
209 | ||
377a22d3 | 210 | ret = venus_boot(dev, core->res->fwname); |
af2c3834 SV |
211 | if (ret) |
212 | goto err_runtime_disable; | |
213 | ||
214 | ret = hfi_core_resume(core, true); | |
215 | if (ret) | |
216 | goto err_venus_shutdown; | |
217 | ||
218 | ret = hfi_core_init(core); | |
219 | if (ret) | |
220 | goto err_venus_shutdown; | |
221 | ||
222 | ret = v4l2_device_register(dev, &core->v4l2_dev); | |
223 | if (ret) | |
224 | goto err_core_deinit; | |
225 | ||
226 | ret = of_platform_populate(dev->of_node, NULL, NULL, dev); | |
227 | if (ret) | |
228 | goto err_dev_unregister; | |
229 | ||
230 | ret = pm_runtime_put_sync(dev); | |
231 | if (ret) | |
232 | goto err_dev_unregister; | |
233 | ||
234 | return 0; | |
235 | ||
236 | err_dev_unregister: | |
237 | v4l2_device_unregister(&core->v4l2_dev); | |
238 | err_core_deinit: | |
239 | hfi_core_deinit(core, false); | |
240 | err_venus_shutdown: | |
377a22d3 | 241 | venus_shutdown(dev); |
af2c3834 SV |
242 | err_runtime_disable: |
243 | pm_runtime_set_suspended(dev); | |
244 | pm_runtime_disable(dev); | |
245 | hfi_destroy(core); | |
246 | return ret; | |
247 | } | |
248 | ||
249 | static int venus_remove(struct platform_device *pdev) | |
250 | { | |
251 | struct venus_core *core = platform_get_drvdata(pdev); | |
252 | struct device *dev = core->dev; | |
253 | int ret; | |
254 | ||
255 | ret = pm_runtime_get_sync(dev); | |
256 | WARN_ON(ret < 0); | |
257 | ||
258 | ret = hfi_core_deinit(core, true); | |
259 | WARN_ON(ret); | |
260 | ||
261 | hfi_destroy(core); | |
377a22d3 | 262 | venus_shutdown(dev); |
af2c3834 SV |
263 | of_platform_depopulate(dev); |
264 | ||
265 | pm_runtime_put_sync(dev); | |
266 | pm_runtime_disable(dev); | |
267 | ||
268 | v4l2_device_unregister(&core->v4l2_dev); | |
269 | ||
270 | return ret; | |
271 | } | |
272 | ||
eb918f91 | 273 | static __maybe_unused int venus_runtime_suspend(struct device *dev) |
af2c3834 SV |
274 | { |
275 | struct venus_core *core = dev_get_drvdata(dev); | |
276 | int ret; | |
277 | ||
278 | ret = hfi_core_suspend(core); | |
279 | ||
280 | venus_clks_disable(core); | |
281 | ||
282 | return ret; | |
283 | } | |
284 | ||
eb918f91 | 285 | static __maybe_unused int venus_runtime_resume(struct device *dev) |
af2c3834 SV |
286 | { |
287 | struct venus_core *core = dev_get_drvdata(dev); | |
288 | int ret; | |
289 | ||
290 | ret = venus_clks_enable(core); | |
291 | if (ret) | |
292 | return ret; | |
293 | ||
294 | ret = hfi_core_resume(core, false); | |
295 | if (ret) | |
296 | goto err_clks_disable; | |
297 | ||
298 | return 0; | |
299 | ||
300 | err_clks_disable: | |
301 | venus_clks_disable(core); | |
302 | return ret; | |
303 | } | |
af2c3834 SV |
304 | |
305 | static const struct dev_pm_ops venus_pm_ops = { | |
306 | SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, | |
307 | pm_runtime_force_resume) | |
308 | SET_RUNTIME_PM_OPS(venus_runtime_suspend, venus_runtime_resume, NULL) | |
309 | }; | |
310 | ||
311 | static const struct freq_tbl msm8916_freq_table[] = { | |
312 | { 352800, 228570000 }, /* 1920x1088 @ 30 + 1280x720 @ 30 */ | |
313 | { 244800, 160000000 }, /* 1920x1088 @ 30 */ | |
314 | { 108000, 100000000 }, /* 1280x720 @ 30 */ | |
315 | }; | |
316 | ||
317 | static const struct reg_val msm8916_reg_preset[] = { | |
318 | { 0xe0020, 0x05555556 }, | |
319 | { 0xe0024, 0x05555556 }, | |
320 | { 0x80124, 0x00000003 }, | |
321 | }; | |
322 | ||
323 | static const struct venus_resources msm8916_res = { | |
324 | .freq_tbl = msm8916_freq_table, | |
325 | .freq_tbl_size = ARRAY_SIZE(msm8916_freq_table), | |
326 | .reg_tbl = msm8916_reg_preset, | |
327 | .reg_tbl_size = ARRAY_SIZE(msm8916_reg_preset), | |
328 | .clks = { "core", "iface", "bus", }, | |
329 | .clks_num = 3, | |
330 | .max_load = 352800, /* 720p@30 + 1080p@30 */ | |
331 | .hfi_version = HFI_VERSION_1XX, | |
332 | .vmem_id = VIDC_RESOURCE_NONE, | |
333 | .vmem_size = 0, | |
334 | .vmem_addr = 0, | |
335 | .dma_mask = 0xddc00000 - 1, | |
50058a9a | 336 | .fwname = "qcom/venus-1.8/venus.mdt", |
af2c3834 SV |
337 | }; |
338 | ||
339 | static const struct freq_tbl msm8996_freq_table[] = { | |
340 | { 1944000, 490000000 }, /* 4k UHD @ 60 */ | |
341 | { 972000, 320000000 }, /* 4k UHD @ 30 */ | |
342 | { 489600, 150000000 }, /* 1080p @ 60 */ | |
343 | { 244800, 75000000 }, /* 1080p @ 30 */ | |
344 | }; | |
345 | ||
346 | static const struct reg_val msm8996_reg_preset[] = { | |
347 | { 0x80010, 0xffffffff }, | |
348 | { 0x80018, 0x00001556 }, | |
349 | { 0x8001C, 0x00001556 }, | |
350 | }; | |
351 | ||
352 | static const struct venus_resources msm8996_res = { | |
353 | .freq_tbl = msm8996_freq_table, | |
354 | .freq_tbl_size = ARRAY_SIZE(msm8996_freq_table), | |
355 | .reg_tbl = msm8996_reg_preset, | |
356 | .reg_tbl_size = ARRAY_SIZE(msm8996_reg_preset), | |
357 | .clks = {"core", "iface", "bus", "mbus" }, | |
358 | .clks_num = 4, | |
359 | .max_load = 2563200, | |
360 | .hfi_version = HFI_VERSION_3XX, | |
361 | .vmem_id = VIDC_RESOURCE_NONE, | |
362 | .vmem_size = 0, | |
363 | .vmem_addr = 0, | |
364 | .dma_mask = 0xddc00000 - 1, | |
50058a9a | 365 | .fwname = "qcom/venus-4.2/venus.mdt", |
af2c3834 SV |
366 | }; |
367 | ||
368 | static const struct of_device_id venus_dt_match[] = { | |
369 | { .compatible = "qcom,msm8916-venus", .data = &msm8916_res, }, | |
370 | { .compatible = "qcom,msm8996-venus", .data = &msm8996_res, }, | |
371 | { } | |
372 | }; | |
373 | MODULE_DEVICE_TABLE(of, venus_dt_match); | |
374 | ||
375 | static struct platform_driver qcom_venus_driver = { | |
376 | .probe = venus_probe, | |
377 | .remove = venus_remove, | |
378 | .driver = { | |
379 | .name = "qcom-venus", | |
380 | .of_match_table = venus_dt_match, | |
381 | .pm = &venus_pm_ops, | |
382 | }, | |
383 | }; | |
384 | module_platform_driver(qcom_venus_driver); | |
385 | ||
386 | MODULE_ALIAS("platform:qcom-venus"); | |
387 | MODULE_DESCRIPTION("Qualcomm Venus video encoder and decoder driver"); | |
388 | MODULE_LICENSE("GPL v2"); |