Commit | Line | Data |
---|---|---|
50e76151 PK |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * Cedrus VPU driver | |
4 | * | |
5 | * Copyright (C) 2016 Florent Revest <florent.revest@free-electrons.com> | |
6 | * Copyright (C) 2018 Paul Kocialkowski <paul.kocialkowski@bootlin.com> | |
7 | * Copyright (C) 2018 Bootlin | |
8 | * | |
9 | * Based on the vim2m driver, that is: | |
10 | * | |
11 | * Copyright (c) 2009-2010 Samsung Electronics Co., Ltd. | |
12 | * Pawel Osciak, <pawel@osciak.com> | |
13 | * Marek Szyprowski, <m.szyprowski@samsung.com> | |
14 | */ | |
15 | ||
16 | #include <linux/platform_device.h> | |
17 | #include <linux/module.h> | |
18 | #include <linux/of.h> | |
19 | ||
20 | #include <media/v4l2-device.h> | |
21 | #include <media/v4l2-ioctl.h> | |
22 | #include <media/v4l2-ctrls.h> | |
23 | #include <media/v4l2-mem2mem.h> | |
24 | ||
25 | #include "cedrus.h" | |
26 | #include "cedrus_video.h" | |
27 | #include "cedrus_dec.h" | |
28 | #include "cedrus_hw.h" | |
29 | ||
30 | static const struct cedrus_control cedrus_controls[] = { | |
31 | { | |
32 | .id = V4L2_CID_MPEG_VIDEO_MPEG2_SLICE_PARAMS, | |
33 | .elem_size = sizeof(struct v4l2_ctrl_mpeg2_slice_params), | |
34 | .codec = CEDRUS_CODEC_MPEG2, | |
35 | .required = true, | |
36 | }, | |
37 | { | |
38 | .id = V4L2_CID_MPEG_VIDEO_MPEG2_QUANTIZATION, | |
39 | .elem_size = sizeof(struct v4l2_ctrl_mpeg2_quantization), | |
40 | .codec = CEDRUS_CODEC_MPEG2, | |
41 | .required = false, | |
42 | }, | |
43 | }; | |
44 | ||
45 | #define CEDRUS_CONTROLS_COUNT ARRAY_SIZE(cedrus_controls) | |
46 | ||
47 | void *cedrus_find_control_data(struct cedrus_ctx *ctx, u32 id) | |
48 | { | |
49 | unsigned int i; | |
50 | ||
51 | for (i = 0; ctx->ctrls[i]; i++) | |
52 | if (ctx->ctrls[i]->id == id) | |
53 | return ctx->ctrls[i]->p_cur.p; | |
54 | ||
55 | return NULL; | |
56 | } | |
57 | ||
58 | static int cedrus_init_ctrls(struct cedrus_dev *dev, struct cedrus_ctx *ctx) | |
59 | { | |
60 | struct v4l2_ctrl_handler *hdl = &ctx->hdl; | |
61 | struct v4l2_ctrl *ctrl; | |
62 | unsigned int ctrl_size; | |
63 | unsigned int i; | |
64 | ||
65 | v4l2_ctrl_handler_init(hdl, CEDRUS_CONTROLS_COUNT); | |
66 | if (hdl->error) { | |
67 | v4l2_err(&dev->v4l2_dev, | |
68 | "Failed to initialize control handler\n"); | |
69 | return hdl->error; | |
70 | } | |
71 | ||
72 | ctrl_size = sizeof(ctrl) * CEDRUS_CONTROLS_COUNT + 1; | |
73 | ||
74 | ctx->ctrls = kzalloc(ctrl_size, GFP_KERNEL); | |
75 | memset(ctx->ctrls, 0, ctrl_size); | |
76 | ||
77 | for (i = 0; i < CEDRUS_CONTROLS_COUNT; i++) { | |
78 | struct v4l2_ctrl_config cfg = { 0 }; | |
79 | ||
80 | cfg.elem_size = cedrus_controls[i].elem_size; | |
81 | cfg.id = cedrus_controls[i].id; | |
82 | ||
83 | ctrl = v4l2_ctrl_new_custom(hdl, &cfg, NULL); | |
84 | if (hdl->error) { | |
85 | v4l2_err(&dev->v4l2_dev, | |
86 | "Failed to create new custom control\n"); | |
87 | ||
88 | v4l2_ctrl_handler_free(hdl); | |
89 | kfree(ctx->ctrls); | |
90 | return hdl->error; | |
91 | } | |
92 | ||
93 | ctx->ctrls[i] = ctrl; | |
94 | } | |
95 | ||
96 | ctx->fh.ctrl_handler = hdl; | |
97 | v4l2_ctrl_handler_setup(hdl); | |
98 | ||
99 | return 0; | |
100 | } | |
101 | ||
102 | static int cedrus_request_validate(struct media_request *req) | |
103 | { | |
104 | struct media_request_object *obj; | |
105 | struct v4l2_ctrl_handler *parent_hdl, *hdl; | |
106 | struct cedrus_ctx *ctx = NULL; | |
107 | struct v4l2_ctrl *ctrl_test; | |
108 | unsigned int count; | |
109 | unsigned int i; | |
110 | ||
50e76151 PK |
111 | list_for_each_entry(obj, &req->objects, list) { |
112 | struct vb2_buffer *vb; | |
113 | ||
114 | if (vb2_request_object_is_buffer(obj)) { | |
115 | vb = container_of(obj, struct vb2_buffer, req_obj); | |
116 | ctx = vb2_get_drv_priv(vb->vb2_queue); | |
117 | ||
118 | break; | |
119 | } | |
120 | } | |
121 | ||
122 | if (!ctx) | |
123 | return -ENOENT; | |
124 | ||
b7c56d7b CIK |
125 | count = vb2_request_buffer_cnt(req); |
126 | if (!count) { | |
127 | v4l2_info(&ctx->dev->v4l2_dev, | |
128 | "No buffer was provided with the request\n"); | |
129 | return -ENOENT; | |
130 | } else if (count > 1) { | |
131 | v4l2_info(&ctx->dev->v4l2_dev, | |
132 | "More than one buffer was provided with the request\n"); | |
133 | return -EINVAL; | |
134 | } | |
135 | ||
50e76151 PK |
136 | parent_hdl = &ctx->hdl; |
137 | ||
138 | hdl = v4l2_ctrl_request_hdl_find(req, parent_hdl); | |
139 | if (!hdl) { | |
140 | v4l2_info(&ctx->dev->v4l2_dev, "Missing codec control(s)\n"); | |
141 | return -ENOENT; | |
142 | } | |
143 | ||
144 | for (i = 0; i < CEDRUS_CONTROLS_COUNT; i++) { | |
145 | if (cedrus_controls[i].codec != ctx->current_codec || | |
146 | !cedrus_controls[i].required) | |
147 | continue; | |
148 | ||
149 | ctrl_test = v4l2_ctrl_request_hdl_ctrl_find(hdl, | |
150 | cedrus_controls[i].id); | |
151 | if (!ctrl_test) { | |
152 | v4l2_info(&ctx->dev->v4l2_dev, | |
153 | "Missing required codec control\n"); | |
154 | return -ENOENT; | |
155 | } | |
156 | } | |
157 | ||
158 | v4l2_ctrl_request_hdl_put(hdl); | |
159 | ||
160 | return vb2_request_validate(req); | |
161 | } | |
162 | ||
163 | static int cedrus_open(struct file *file) | |
164 | { | |
165 | struct cedrus_dev *dev = video_drvdata(file); | |
166 | struct cedrus_ctx *ctx = NULL; | |
167 | int ret; | |
168 | ||
169 | if (mutex_lock_interruptible(&dev->dev_mutex)) | |
170 | return -ERESTARTSYS; | |
171 | ||
172 | ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); | |
173 | if (!ctx) { | |
174 | mutex_unlock(&dev->dev_mutex); | |
175 | return -ENOMEM; | |
176 | } | |
177 | ||
178 | v4l2_fh_init(&ctx->fh, video_devdata(file)); | |
179 | file->private_data = &ctx->fh; | |
180 | ctx->dev = dev; | |
181 | ||
182 | ret = cedrus_init_ctrls(dev, ctx); | |
183 | if (ret) | |
184 | goto err_free; | |
185 | ||
186 | ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(dev->m2m_dev, ctx, | |
187 | &cedrus_queue_init); | |
188 | if (IS_ERR(ctx->fh.m2m_ctx)) { | |
189 | ret = PTR_ERR(ctx->fh.m2m_ctx); | |
190 | goto err_ctrls; | |
191 | } | |
192 | ||
193 | v4l2_fh_add(&ctx->fh); | |
194 | ||
195 | mutex_unlock(&dev->dev_mutex); | |
196 | ||
197 | return 0; | |
198 | ||
199 | err_ctrls: | |
200 | v4l2_ctrl_handler_free(&ctx->hdl); | |
201 | err_free: | |
202 | kfree(ctx); | |
203 | mutex_unlock(&dev->dev_mutex); | |
204 | ||
205 | return ret; | |
206 | } | |
207 | ||
208 | static int cedrus_release(struct file *file) | |
209 | { | |
210 | struct cedrus_dev *dev = video_drvdata(file); | |
211 | struct cedrus_ctx *ctx = container_of(file->private_data, | |
212 | struct cedrus_ctx, fh); | |
213 | ||
214 | mutex_lock(&dev->dev_mutex); | |
215 | ||
216 | v4l2_fh_del(&ctx->fh); | |
217 | v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); | |
218 | ||
219 | v4l2_ctrl_handler_free(&ctx->hdl); | |
220 | kfree(ctx->ctrls); | |
221 | ||
222 | v4l2_fh_exit(&ctx->fh); | |
223 | ||
224 | kfree(ctx); | |
225 | ||
226 | mutex_unlock(&dev->dev_mutex); | |
227 | ||
228 | return 0; | |
229 | } | |
230 | ||
231 | static const struct v4l2_file_operations cedrus_fops = { | |
232 | .owner = THIS_MODULE, | |
233 | .open = cedrus_open, | |
234 | .release = cedrus_release, | |
235 | .poll = v4l2_m2m_fop_poll, | |
236 | .unlocked_ioctl = video_ioctl2, | |
237 | .mmap = v4l2_m2m_fop_mmap, | |
238 | }; | |
239 | ||
240 | static const struct video_device cedrus_video_device = { | |
241 | .name = CEDRUS_NAME, | |
242 | .vfl_dir = VFL_DIR_M2M, | |
243 | .fops = &cedrus_fops, | |
244 | .ioctl_ops = &cedrus_ioctl_ops, | |
245 | .minor = -1, | |
246 | .release = video_device_release_empty, | |
247 | .device_caps = V4L2_CAP_VIDEO_M2M | V4L2_CAP_STREAMING, | |
248 | }; | |
249 | ||
250 | static const struct v4l2_m2m_ops cedrus_m2m_ops = { | |
251 | .device_run = cedrus_device_run, | |
252 | }; | |
253 | ||
254 | static const struct media_device_ops cedrus_m2m_media_ops = { | |
255 | .req_validate = cedrus_request_validate, | |
ef86eaf9 | 256 | .req_queue = v4l2_m2m_request_queue, |
50e76151 PK |
257 | }; |
258 | ||
259 | static int cedrus_probe(struct platform_device *pdev) | |
260 | { | |
261 | struct cedrus_dev *dev; | |
262 | struct video_device *vfd; | |
263 | int ret; | |
264 | ||
265 | dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); | |
266 | if (!dev) | |
267 | return -ENOMEM; | |
268 | ||
269 | dev->vfd = cedrus_video_device; | |
270 | dev->dev = &pdev->dev; | |
271 | dev->pdev = pdev; | |
272 | ||
273 | ret = cedrus_hw_probe(dev); | |
274 | if (ret) { | |
275 | dev_err(&pdev->dev, "Failed to probe hardware\n"); | |
276 | return ret; | |
277 | } | |
278 | ||
279 | dev->dec_ops[CEDRUS_CODEC_MPEG2] = &cedrus_dec_ops_mpeg2; | |
280 | ||
281 | mutex_init(&dev->dev_mutex); | |
50e76151 PK |
282 | |
283 | ret = v4l2_device_register(&pdev->dev, &dev->v4l2_dev); | |
284 | if (ret) { | |
285 | dev_err(&pdev->dev, "Failed to register V4L2 device\n"); | |
286 | return ret; | |
287 | } | |
288 | ||
289 | vfd = &dev->vfd; | |
290 | vfd->lock = &dev->dev_mutex; | |
291 | vfd->v4l2_dev = &dev->v4l2_dev; | |
292 | ||
293 | snprintf(vfd->name, sizeof(vfd->name), "%s", cedrus_video_device.name); | |
294 | video_set_drvdata(vfd, dev); | |
295 | ||
296 | dev->m2m_dev = v4l2_m2m_init(&cedrus_m2m_ops); | |
297 | if (IS_ERR(dev->m2m_dev)) { | |
298 | v4l2_err(&dev->v4l2_dev, | |
299 | "Failed to initialize V4L2 M2M device\n"); | |
300 | ret = PTR_ERR(dev->m2m_dev); | |
301 | ||
302 | goto err_video; | |
303 | } | |
304 | ||
305 | dev->mdev.dev = &pdev->dev; | |
306 | strscpy(dev->mdev.model, CEDRUS_NAME, sizeof(dev->mdev.model)); | |
307 | ||
308 | media_device_init(&dev->mdev); | |
309 | dev->mdev.ops = &cedrus_m2m_media_ops; | |
310 | dev->v4l2_dev.mdev = &dev->mdev; | |
311 | ||
312 | ret = v4l2_m2m_register_media_controller(dev->m2m_dev, vfd, | |
313 | MEDIA_ENT_F_PROC_VIDEO_DECODER); | |
314 | if (ret) { | |
315 | v4l2_err(&dev->v4l2_dev, | |
316 | "Failed to initialize V4L2 M2M media controller\n"); | |
317 | goto err_m2m; | |
318 | } | |
319 | ||
320 | ret = video_register_device(vfd, VFL_TYPE_GRABBER, 0); | |
321 | if (ret) { | |
322 | v4l2_err(&dev->v4l2_dev, "Failed to register video device\n"); | |
323 | goto err_v4l2; | |
324 | } | |
325 | ||
326 | v4l2_info(&dev->v4l2_dev, | |
327 | "Device registered as /dev/video%d\n", vfd->num); | |
328 | ||
329 | ret = media_device_register(&dev->mdev); | |
330 | if (ret) { | |
331 | v4l2_err(&dev->v4l2_dev, "Failed to register media device\n"); | |
332 | goto err_m2m_mc; | |
333 | } | |
334 | ||
335 | platform_set_drvdata(pdev, dev); | |
336 | ||
337 | return 0; | |
338 | ||
339 | err_m2m_mc: | |
340 | v4l2_m2m_unregister_media_controller(dev->m2m_dev); | |
341 | err_m2m: | |
342 | v4l2_m2m_release(dev->m2m_dev); | |
343 | err_video: | |
344 | video_unregister_device(&dev->vfd); | |
345 | err_v4l2: | |
346 | v4l2_device_unregister(&dev->v4l2_dev); | |
347 | ||
348 | return ret; | |
349 | } | |
350 | ||
351 | static int cedrus_remove(struct platform_device *pdev) | |
352 | { | |
353 | struct cedrus_dev *dev = platform_get_drvdata(pdev); | |
354 | ||
355 | if (media_devnode_is_registered(dev->mdev.devnode)) { | |
356 | media_device_unregister(&dev->mdev); | |
357 | v4l2_m2m_unregister_media_controller(dev->m2m_dev); | |
358 | media_device_cleanup(&dev->mdev); | |
359 | } | |
360 | ||
361 | v4l2_m2m_release(dev->m2m_dev); | |
362 | video_unregister_device(&dev->vfd); | |
363 | v4l2_device_unregister(&dev->v4l2_dev); | |
364 | ||
365 | cedrus_hw_remove(dev); | |
366 | ||
367 | return 0; | |
368 | } | |
369 | ||
370 | static const struct cedrus_variant sun4i_a10_cedrus_variant = { | |
371 | /* No particular capability. */ | |
372 | }; | |
373 | ||
374 | static const struct cedrus_variant sun5i_a13_cedrus_variant = { | |
375 | /* No particular capability. */ | |
376 | }; | |
377 | ||
378 | static const struct cedrus_variant sun7i_a20_cedrus_variant = { | |
379 | /* No particular capability. */ | |
380 | }; | |
381 | ||
382 | static const struct cedrus_variant sun8i_a33_cedrus_variant = { | |
383 | .capabilities = CEDRUS_CAPABILITY_UNTILED, | |
384 | }; | |
385 | ||
386 | static const struct cedrus_variant sun8i_h3_cedrus_variant = { | |
387 | .capabilities = CEDRUS_CAPABILITY_UNTILED, | |
388 | }; | |
389 | ||
f7fa2b6a PK |
390 | static const struct cedrus_variant sun50i_h5_cedrus_variant = { |
391 | .capabilities = CEDRUS_CAPABILITY_UNTILED, | |
392 | }; | |
393 | ||
50e76151 PK |
394 | static const struct of_device_id cedrus_dt_match[] = { |
395 | { | |
396 | .compatible = "allwinner,sun4i-a10-video-engine", | |
397 | .data = &sun4i_a10_cedrus_variant, | |
398 | }, | |
399 | { | |
400 | .compatible = "allwinner,sun5i-a13-video-engine", | |
401 | .data = &sun5i_a13_cedrus_variant, | |
402 | }, | |
403 | { | |
404 | .compatible = "allwinner,sun7i-a20-video-engine", | |
405 | .data = &sun7i_a20_cedrus_variant, | |
406 | }, | |
407 | { | |
408 | .compatible = "allwinner,sun8i-a33-video-engine", | |
409 | .data = &sun8i_a33_cedrus_variant, | |
410 | }, | |
411 | { | |
412 | .compatible = "allwinner,sun8i-h3-video-engine", | |
413 | .data = &sun8i_h3_cedrus_variant, | |
414 | }, | |
f7fa2b6a PK |
415 | { |
416 | .compatible = "allwinner,sun50i-h5-video-engine", | |
417 | .data = &sun50i_h5_cedrus_variant, | |
418 | }, | |
50e76151 PK |
419 | { /* sentinel */ } |
420 | }; | |
421 | MODULE_DEVICE_TABLE(of, cedrus_dt_match); | |
422 | ||
423 | static struct platform_driver cedrus_driver = { | |
424 | .probe = cedrus_probe, | |
425 | .remove = cedrus_remove, | |
426 | .driver = { | |
427 | .name = CEDRUS_NAME, | |
50e76151 PK |
428 | .of_match_table = of_match_ptr(cedrus_dt_match), |
429 | }, | |
430 | }; | |
431 | module_platform_driver(cedrus_driver); | |
432 | ||
433 | MODULE_LICENSE("GPL v2"); | |
434 | MODULE_AUTHOR("Florent Revest <florent.revest@free-electrons.com>"); | |
435 | MODULE_AUTHOR("Paul Kocialkowski <paul.kocialkowski@bootlin.com>"); | |
436 | MODULE_AUTHOR("Maxime Ripard <maxime.ripard@bootlin.com>"); | |
437 | MODULE_DESCRIPTION("Cedrus VPU driver"); |