• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 #include <linux/pm.h>
20 
21 #include <media/v4l2-device.h>
22 #include <media/v4l2-ioctl.h>
23 #include <media/v4l2-ctrls.h>
24 #include <media/v4l2-mem2mem.h>
25 
26 #include "cedrus.h"
27 #include "cedrus_video.h"
28 #include "cedrus_dec.h"
29 #include "cedrus_hw.h"
30 
31 static const struct cedrus_control cedrus_controls[] = {
32 	{
33 		.cfg = {
34 			.id	= V4L2_CID_STATELESS_MPEG2_SEQUENCE,
35 		},
36 		.codec		= CEDRUS_CODEC_MPEG2,
37 	},
38 	{
39 		.cfg = {
40 			.id	= V4L2_CID_STATELESS_MPEG2_PICTURE,
41 		},
42 		.codec		= CEDRUS_CODEC_MPEG2,
43 	},
44 	{
45 		.cfg = {
46 			.id	= V4L2_CID_STATELESS_MPEG2_QUANTISATION,
47 		},
48 		.codec		= CEDRUS_CODEC_MPEG2,
49 	},
50 	{
51 		.cfg = {
52 			.id	= V4L2_CID_STATELESS_H264_DECODE_PARAMS,
53 		},
54 		.codec		= CEDRUS_CODEC_H264,
55 	},
56 	{
57 		.cfg = {
58 			.id	= V4L2_CID_STATELESS_H264_SLICE_PARAMS,
59 		},
60 		.codec		= CEDRUS_CODEC_H264,
61 	},
62 	{
63 		.cfg = {
64 			.id	= V4L2_CID_STATELESS_H264_SPS,
65 		},
66 		.codec		= CEDRUS_CODEC_H264,
67 	},
68 	{
69 		.cfg = {
70 			.id	= V4L2_CID_STATELESS_H264_PPS,
71 		},
72 		.codec		= CEDRUS_CODEC_H264,
73 	},
74 	{
75 		.cfg = {
76 			.id	= V4L2_CID_STATELESS_H264_SCALING_MATRIX,
77 		},
78 		.codec		= CEDRUS_CODEC_H264,
79 	},
80 	{
81 		.cfg = {
82 			.id	= V4L2_CID_STATELESS_H264_PRED_WEIGHTS,
83 		},
84 		.codec		= CEDRUS_CODEC_H264,
85 	},
86 	{
87 		.cfg = {
88 			.id	= V4L2_CID_STATELESS_H264_DECODE_MODE,
89 			.max	= V4L2_STATELESS_H264_DECODE_MODE_SLICE_BASED,
90 			.def	= V4L2_STATELESS_H264_DECODE_MODE_SLICE_BASED,
91 		},
92 		.codec		= CEDRUS_CODEC_H264,
93 	},
94 	{
95 		.cfg = {
96 			.id	= V4L2_CID_STATELESS_H264_START_CODE,
97 			.max	= V4L2_STATELESS_H264_START_CODE_NONE,
98 			.def	= V4L2_STATELESS_H264_START_CODE_NONE,
99 		},
100 		.codec		= CEDRUS_CODEC_H264,
101 	},
102 	/*
103 	 * We only expose supported profiles information,
104 	 * and not levels as it's not clear what is supported
105 	 * for each hardware/core version.
106 	 * In any case, TRY/S_FMT will clamp the format resolution
107 	 * to the maximum supported.
108 	 */
109 	{
110 		.cfg = {
111 			.id	= V4L2_CID_MPEG_VIDEO_H264_PROFILE,
112 			.min	= V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE,
113 			.def	= V4L2_MPEG_VIDEO_H264_PROFILE_MAIN,
114 			.max	= V4L2_MPEG_VIDEO_H264_PROFILE_HIGH,
115 			.menu_skip_mask =
116 				BIT(V4L2_MPEG_VIDEO_H264_PROFILE_EXTENDED),
117 		},
118 		.codec		= CEDRUS_CODEC_H264,
119 	},
120 	{
121 		.cfg = {
122 			.id	= V4L2_CID_MPEG_VIDEO_HEVC_SPS,
123 		},
124 		.codec		= CEDRUS_CODEC_H265,
125 	},
126 	{
127 		.cfg = {
128 			.id	= V4L2_CID_MPEG_VIDEO_HEVC_PPS,
129 		},
130 		.codec		= CEDRUS_CODEC_H265,
131 	},
132 	{
133 		.cfg = {
134 			.id	= V4L2_CID_MPEG_VIDEO_HEVC_SLICE_PARAMS,
135 		},
136 		.codec		= CEDRUS_CODEC_H265,
137 	},
138 	{
139 		.cfg = {
140 			.id	= V4L2_CID_MPEG_VIDEO_HEVC_DECODE_MODE,
141 			.max	= V4L2_MPEG_VIDEO_HEVC_DECODE_MODE_SLICE_BASED,
142 			.def	= V4L2_MPEG_VIDEO_HEVC_DECODE_MODE_SLICE_BASED,
143 		},
144 		.codec		= CEDRUS_CODEC_H265,
145 	},
146 	{
147 		.cfg = {
148 			.id	= V4L2_CID_MPEG_VIDEO_HEVC_START_CODE,
149 			.max	= V4L2_MPEG_VIDEO_HEVC_START_CODE_NONE,
150 			.def	= V4L2_MPEG_VIDEO_HEVC_START_CODE_NONE,
151 		},
152 		.codec		= CEDRUS_CODEC_H265,
153 	},
154 	{
155 		.cfg = {
156 			.id	= V4L2_CID_STATELESS_VP8_FRAME,
157 		},
158 		.codec		= CEDRUS_CODEC_VP8,
159 	},
160 	{
161 		.cfg = {
162 			.id = V4L2_CID_MPEG_VIDEO_HEVC_DECODE_PARAMS,
163 		},
164 		.codec		= CEDRUS_CODEC_H265,
165 	},
166 };
167 
168 #define CEDRUS_CONTROLS_COUNT	ARRAY_SIZE(cedrus_controls)
169 
cedrus_find_control_data(struct cedrus_ctx * ctx,u32 id)170 void *cedrus_find_control_data(struct cedrus_ctx *ctx, u32 id)
171 {
172 	unsigned int i;
173 
174 	for (i = 0; ctx->ctrls[i]; i++)
175 		if (ctx->ctrls[i]->id == id)
176 			return ctx->ctrls[i]->p_cur.p;
177 
178 	return NULL;
179 }
180 
cedrus_init_ctrls(struct cedrus_dev * dev,struct cedrus_ctx * ctx)181 static int cedrus_init_ctrls(struct cedrus_dev *dev, struct cedrus_ctx *ctx)
182 {
183 	struct v4l2_ctrl_handler *hdl = &ctx->hdl;
184 	struct v4l2_ctrl *ctrl;
185 	unsigned int ctrl_size;
186 	unsigned int i;
187 
188 	v4l2_ctrl_handler_init(hdl, CEDRUS_CONTROLS_COUNT);
189 	if (hdl->error) {
190 		v4l2_err(&dev->v4l2_dev,
191 			 "Failed to initialize control handler\n");
192 		return hdl->error;
193 	}
194 
195 	ctrl_size = sizeof(ctrl) * CEDRUS_CONTROLS_COUNT + 1;
196 
197 	ctx->ctrls = kzalloc(ctrl_size, GFP_KERNEL);
198 	if (!ctx->ctrls)
199 		return -ENOMEM;
200 
201 	for (i = 0; i < CEDRUS_CONTROLS_COUNT; i++) {
202 		ctrl = v4l2_ctrl_new_custom(hdl, &cedrus_controls[i].cfg,
203 					    NULL);
204 		if (hdl->error) {
205 			v4l2_err(&dev->v4l2_dev,
206 				 "Failed to create new custom control\n");
207 
208 			v4l2_ctrl_handler_free(hdl);
209 			kfree(ctx->ctrls);
210 			return hdl->error;
211 		}
212 
213 		ctx->ctrls[i] = ctrl;
214 	}
215 
216 	ctx->fh.ctrl_handler = hdl;
217 	v4l2_ctrl_handler_setup(hdl);
218 
219 	return 0;
220 }
221 
cedrus_request_validate(struct media_request * req)222 static int cedrus_request_validate(struct media_request *req)
223 {
224 	struct media_request_object *obj;
225 	struct cedrus_ctx *ctx = NULL;
226 	unsigned int count;
227 
228 	list_for_each_entry(obj, &req->objects, list) {
229 		struct vb2_buffer *vb;
230 
231 		if (vb2_request_object_is_buffer(obj)) {
232 			vb = container_of(obj, struct vb2_buffer, req_obj);
233 			ctx = vb2_get_drv_priv(vb->vb2_queue);
234 
235 			break;
236 		}
237 	}
238 
239 	if (!ctx)
240 		return -ENOENT;
241 
242 	count = vb2_request_buffer_cnt(req);
243 	if (!count) {
244 		v4l2_info(&ctx->dev->v4l2_dev,
245 			  "No buffer was provided with the request\n");
246 		return -ENOENT;
247 	} else if (count > 1) {
248 		v4l2_info(&ctx->dev->v4l2_dev,
249 			  "More than one buffer was provided with the request\n");
250 		return -EINVAL;
251 	}
252 
253 	return vb2_request_validate(req);
254 }
255 
cedrus_open(struct file * file)256 static int cedrus_open(struct file *file)
257 {
258 	struct cedrus_dev *dev = video_drvdata(file);
259 	struct cedrus_ctx *ctx = NULL;
260 	int ret;
261 
262 	if (mutex_lock_interruptible(&dev->dev_mutex))
263 		return -ERESTARTSYS;
264 
265 	ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
266 	if (!ctx) {
267 		mutex_unlock(&dev->dev_mutex);
268 		return -ENOMEM;
269 	}
270 
271 	v4l2_fh_init(&ctx->fh, video_devdata(file));
272 	file->private_data = &ctx->fh;
273 	ctx->dev = dev;
274 
275 	ret = cedrus_init_ctrls(dev, ctx);
276 	if (ret)
277 		goto err_free;
278 
279 	ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(dev->m2m_dev, ctx,
280 					    &cedrus_queue_init);
281 	if (IS_ERR(ctx->fh.m2m_ctx)) {
282 		ret = PTR_ERR(ctx->fh.m2m_ctx);
283 		goto err_ctrls;
284 	}
285 	ctx->dst_fmt.pixelformat = V4L2_PIX_FMT_SUNXI_TILED_NV12;
286 	cedrus_prepare_format(&ctx->dst_fmt);
287 	ctx->src_fmt.pixelformat = V4L2_PIX_FMT_MPEG2_SLICE;
288 	/*
289 	 * TILED_NV12 has more strict requirements, so copy the width and
290 	 * height to src_fmt to ensure that is matches the dst_fmt resolution.
291 	 */
292 	ctx->src_fmt.width = ctx->dst_fmt.width;
293 	ctx->src_fmt.height = ctx->dst_fmt.height;
294 	cedrus_prepare_format(&ctx->src_fmt);
295 
296 	v4l2_fh_add(&ctx->fh);
297 
298 	mutex_unlock(&dev->dev_mutex);
299 
300 	return 0;
301 
302 err_ctrls:
303 	v4l2_ctrl_handler_free(&ctx->hdl);
304 err_free:
305 	kfree(ctx);
306 	mutex_unlock(&dev->dev_mutex);
307 
308 	return ret;
309 }
310 
cedrus_release(struct file * file)311 static int cedrus_release(struct file *file)
312 {
313 	struct cedrus_dev *dev = video_drvdata(file);
314 	struct cedrus_ctx *ctx = container_of(file->private_data,
315 					      struct cedrus_ctx, fh);
316 
317 	mutex_lock(&dev->dev_mutex);
318 
319 	v4l2_fh_del(&ctx->fh);
320 	v4l2_m2m_ctx_release(ctx->fh.m2m_ctx);
321 
322 	v4l2_ctrl_handler_free(&ctx->hdl);
323 	kfree(ctx->ctrls);
324 
325 	v4l2_fh_exit(&ctx->fh);
326 
327 	kfree(ctx);
328 
329 	mutex_unlock(&dev->dev_mutex);
330 
331 	return 0;
332 }
333 
334 static const struct v4l2_file_operations cedrus_fops = {
335 	.owner		= THIS_MODULE,
336 	.open		= cedrus_open,
337 	.release	= cedrus_release,
338 	.poll		= v4l2_m2m_fop_poll,
339 	.unlocked_ioctl	= video_ioctl2,
340 	.mmap		= v4l2_m2m_fop_mmap,
341 };
342 
343 static const struct video_device cedrus_video_device = {
344 	.name		= CEDRUS_NAME,
345 	.vfl_dir	= VFL_DIR_M2M,
346 	.fops		= &cedrus_fops,
347 	.ioctl_ops	= &cedrus_ioctl_ops,
348 	.minor		= -1,
349 	.release	= video_device_release_empty,
350 	.device_caps	= V4L2_CAP_VIDEO_M2M | V4L2_CAP_STREAMING,
351 };
352 
353 static const struct v4l2_m2m_ops cedrus_m2m_ops = {
354 	.device_run	= cedrus_device_run,
355 };
356 
357 static const struct media_device_ops cedrus_m2m_media_ops = {
358 	.req_validate	= cedrus_request_validate,
359 	.req_queue	= v4l2_m2m_request_queue,
360 };
361 
cedrus_probe(struct platform_device * pdev)362 static int cedrus_probe(struct platform_device *pdev)
363 {
364 	struct cedrus_dev *dev;
365 	struct video_device *vfd;
366 	int ret;
367 
368 	dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
369 	if (!dev)
370 		return -ENOMEM;
371 
372 	platform_set_drvdata(pdev, dev);
373 
374 	dev->vfd = cedrus_video_device;
375 	dev->dev = &pdev->dev;
376 	dev->pdev = pdev;
377 
378 	ret = cedrus_hw_probe(dev);
379 	if (ret) {
380 		dev_err(&pdev->dev, "Failed to probe hardware\n");
381 		return ret;
382 	}
383 
384 	dev->dec_ops[CEDRUS_CODEC_MPEG2] = &cedrus_dec_ops_mpeg2;
385 	dev->dec_ops[CEDRUS_CODEC_H264] = &cedrus_dec_ops_h264;
386 	dev->dec_ops[CEDRUS_CODEC_H265] = &cedrus_dec_ops_h265;
387 	dev->dec_ops[CEDRUS_CODEC_VP8] = &cedrus_dec_ops_vp8;
388 
389 	mutex_init(&dev->dev_mutex);
390 
391 	ret = v4l2_device_register(&pdev->dev, &dev->v4l2_dev);
392 	if (ret) {
393 		dev_err(&pdev->dev, "Failed to register V4L2 device\n");
394 		return ret;
395 	}
396 
397 	vfd = &dev->vfd;
398 	vfd->lock = &dev->dev_mutex;
399 	vfd->v4l2_dev = &dev->v4l2_dev;
400 
401 	snprintf(vfd->name, sizeof(vfd->name), "%s", cedrus_video_device.name);
402 	video_set_drvdata(vfd, dev);
403 
404 	dev->m2m_dev = v4l2_m2m_init(&cedrus_m2m_ops);
405 	if (IS_ERR(dev->m2m_dev)) {
406 		v4l2_err(&dev->v4l2_dev,
407 			 "Failed to initialize V4L2 M2M device\n");
408 		ret = PTR_ERR(dev->m2m_dev);
409 
410 		goto err_v4l2;
411 	}
412 
413 	dev->mdev.dev = &pdev->dev;
414 	strscpy(dev->mdev.model, CEDRUS_NAME, sizeof(dev->mdev.model));
415 	strscpy(dev->mdev.bus_info, "platform:" CEDRUS_NAME,
416 		sizeof(dev->mdev.bus_info));
417 
418 	media_device_init(&dev->mdev);
419 	dev->mdev.ops = &cedrus_m2m_media_ops;
420 	dev->v4l2_dev.mdev = &dev->mdev;
421 
422 	ret = video_register_device(vfd, VFL_TYPE_VIDEO, 0);
423 	if (ret) {
424 		v4l2_err(&dev->v4l2_dev, "Failed to register video device\n");
425 		goto err_m2m;
426 	}
427 
428 	v4l2_info(&dev->v4l2_dev,
429 		  "Device registered as /dev/video%d\n", vfd->num);
430 
431 	ret = v4l2_m2m_register_media_controller(dev->m2m_dev, vfd,
432 						 MEDIA_ENT_F_PROC_VIDEO_DECODER);
433 	if (ret) {
434 		v4l2_err(&dev->v4l2_dev,
435 			 "Failed to initialize V4L2 M2M media controller\n");
436 		goto err_video;
437 	}
438 
439 	ret = media_device_register(&dev->mdev);
440 	if (ret) {
441 		v4l2_err(&dev->v4l2_dev, "Failed to register media device\n");
442 		goto err_m2m_mc;
443 	}
444 
445 	return 0;
446 
447 err_m2m_mc:
448 	v4l2_m2m_unregister_media_controller(dev->m2m_dev);
449 err_video:
450 	video_unregister_device(&dev->vfd);
451 err_m2m:
452 	v4l2_m2m_release(dev->m2m_dev);
453 err_v4l2:
454 	v4l2_device_unregister(&dev->v4l2_dev);
455 
456 	return ret;
457 }
458 
cedrus_remove(struct platform_device * pdev)459 static int cedrus_remove(struct platform_device *pdev)
460 {
461 	struct cedrus_dev *dev = platform_get_drvdata(pdev);
462 
463 	if (media_devnode_is_registered(dev->mdev.devnode)) {
464 		media_device_unregister(&dev->mdev);
465 		v4l2_m2m_unregister_media_controller(dev->m2m_dev);
466 		media_device_cleanup(&dev->mdev);
467 	}
468 
469 	v4l2_m2m_release(dev->m2m_dev);
470 	video_unregister_device(&dev->vfd);
471 	v4l2_device_unregister(&dev->v4l2_dev);
472 
473 	cedrus_hw_remove(dev);
474 
475 	return 0;
476 }
477 
478 static const struct cedrus_variant sun4i_a10_cedrus_variant = {
479 	.capabilities	= CEDRUS_CAPABILITY_MPEG2_DEC |
480 			  CEDRUS_CAPABILITY_H264_DEC |
481 			  CEDRUS_CAPABILITY_VP8_DEC,
482 	.mod_rate	= 320000000,
483 };
484 
485 static const struct cedrus_variant sun5i_a13_cedrus_variant = {
486 	.capabilities	= CEDRUS_CAPABILITY_MPEG2_DEC |
487 			  CEDRUS_CAPABILITY_H264_DEC |
488 			  CEDRUS_CAPABILITY_VP8_DEC,
489 	.mod_rate	= 320000000,
490 };
491 
492 static const struct cedrus_variant sun7i_a20_cedrus_variant = {
493 	.capabilities	= CEDRUS_CAPABILITY_MPEG2_DEC |
494 			  CEDRUS_CAPABILITY_H264_DEC |
495 			  CEDRUS_CAPABILITY_VP8_DEC,
496 	.mod_rate	= 320000000,
497 };
498 
499 static const struct cedrus_variant sun8i_a33_cedrus_variant = {
500 	.capabilities	= CEDRUS_CAPABILITY_UNTILED |
501 			  CEDRUS_CAPABILITY_MPEG2_DEC |
502 			  CEDRUS_CAPABILITY_H264_DEC |
503 			  CEDRUS_CAPABILITY_VP8_DEC,
504 	.mod_rate	= 320000000,
505 };
506 
507 static const struct cedrus_variant sun8i_h3_cedrus_variant = {
508 	.capabilities	= CEDRUS_CAPABILITY_UNTILED |
509 			  CEDRUS_CAPABILITY_MPEG2_DEC |
510 			  CEDRUS_CAPABILITY_H264_DEC |
511 			  CEDRUS_CAPABILITY_H265_DEC |
512 			  CEDRUS_CAPABILITY_VP8_DEC,
513 	.mod_rate	= 402000000,
514 };
515 
516 static const struct cedrus_variant sun8i_v3s_cedrus_variant = {
517 	.capabilities	= CEDRUS_CAPABILITY_UNTILED |
518 			  CEDRUS_CAPABILITY_H264_DEC,
519 	.mod_rate	= 297000000,
520 };
521 
522 static const struct cedrus_variant sun8i_r40_cedrus_variant = {
523 	.capabilities	= CEDRUS_CAPABILITY_UNTILED |
524 			  CEDRUS_CAPABILITY_MPEG2_DEC |
525 			  CEDRUS_CAPABILITY_H264_DEC |
526 			  CEDRUS_CAPABILITY_VP8_DEC,
527 	.mod_rate	= 297000000,
528 };
529 
530 static const struct cedrus_variant sun50i_a64_cedrus_variant = {
531 	.capabilities	= CEDRUS_CAPABILITY_UNTILED |
532 			  CEDRUS_CAPABILITY_MPEG2_DEC |
533 			  CEDRUS_CAPABILITY_H264_DEC |
534 			  CEDRUS_CAPABILITY_H265_DEC |
535 			  CEDRUS_CAPABILITY_VP8_DEC,
536 	.mod_rate	= 402000000,
537 };
538 
539 static const struct cedrus_variant sun50i_h5_cedrus_variant = {
540 	.capabilities	= CEDRUS_CAPABILITY_UNTILED |
541 			  CEDRUS_CAPABILITY_MPEG2_DEC |
542 			  CEDRUS_CAPABILITY_H264_DEC |
543 			  CEDRUS_CAPABILITY_H265_DEC |
544 			  CEDRUS_CAPABILITY_VP8_DEC,
545 	.mod_rate	= 402000000,
546 };
547 
548 static const struct cedrus_variant sun50i_h6_cedrus_variant = {
549 	.capabilities	= CEDRUS_CAPABILITY_UNTILED |
550 			  CEDRUS_CAPABILITY_MPEG2_DEC |
551 			  CEDRUS_CAPABILITY_H264_DEC |
552 			  CEDRUS_CAPABILITY_H265_DEC |
553 			  CEDRUS_CAPABILITY_VP8_DEC,
554 	.mod_rate	= 600000000,
555 };
556 
557 static const struct of_device_id cedrus_dt_match[] = {
558 	{
559 		.compatible = "allwinner,sun4i-a10-video-engine",
560 		.data = &sun4i_a10_cedrus_variant,
561 	},
562 	{
563 		.compatible = "allwinner,sun5i-a13-video-engine",
564 		.data = &sun5i_a13_cedrus_variant,
565 	},
566 	{
567 		.compatible = "allwinner,sun7i-a20-video-engine",
568 		.data = &sun7i_a20_cedrus_variant,
569 	},
570 	{
571 		.compatible = "allwinner,sun8i-a33-video-engine",
572 		.data = &sun8i_a33_cedrus_variant,
573 	},
574 	{
575 		.compatible = "allwinner,sun8i-h3-video-engine",
576 		.data = &sun8i_h3_cedrus_variant,
577 	},
578 	{
579 		.compatible = "allwinner,sun8i-v3s-video-engine",
580 		.data = &sun8i_v3s_cedrus_variant,
581 	},
582 	{
583 		.compatible = "allwinner,sun8i-r40-video-engine",
584 		.data = &sun8i_r40_cedrus_variant,
585 	},
586 	{
587 		.compatible = "allwinner,sun50i-a64-video-engine",
588 		.data = &sun50i_a64_cedrus_variant,
589 	},
590 	{
591 		.compatible = "allwinner,sun50i-h5-video-engine",
592 		.data = &sun50i_h5_cedrus_variant,
593 	},
594 	{
595 		.compatible = "allwinner,sun50i-h6-video-engine",
596 		.data = &sun50i_h6_cedrus_variant,
597 	},
598 	{ /* sentinel */ }
599 };
600 MODULE_DEVICE_TABLE(of, cedrus_dt_match);
601 
602 static const struct dev_pm_ops cedrus_dev_pm_ops = {
603 	SET_RUNTIME_PM_OPS(cedrus_hw_suspend,
604 			   cedrus_hw_resume, NULL)
605 };
606 
607 static struct platform_driver cedrus_driver = {
608 	.probe		= cedrus_probe,
609 	.remove		= cedrus_remove,
610 	.driver		= {
611 		.name		= CEDRUS_NAME,
612 		.of_match_table	= of_match_ptr(cedrus_dt_match),
613 		.pm		= &cedrus_dev_pm_ops,
614 	},
615 };
616 module_platform_driver(cedrus_driver);
617 
618 MODULE_LICENSE("GPL v2");
619 MODULE_AUTHOR("Florent Revest <florent.revest@free-electrons.com>");
620 MODULE_AUTHOR("Paul Kocialkowski <paul.kocialkowski@bootlin.com>");
621 MODULE_AUTHOR("Maxime Ripard <maxime.ripard@bootlin.com>");
622 MODULE_DESCRIPTION("Cedrus VPU driver");
623