]>
Commit | Line | Data |
---|---|---|
256bf813 HV |
1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* | |
3 | * A virtual codec example device. | |
4 | * | |
5 | * Copyright 2018 Cisco Systems, Inc. and/or its affiliates. All rights reserved. | |
6 | * | |
7 | * This is a virtual codec device driver for testing the codec framework. | |
8 | * It simulates a device that uses memory buffers for both source and | |
9 | * destination and encodes or decodes the data. | |
10 | */ | |
11 | ||
12 | #include <linux/module.h> | |
13 | #include <linux/delay.h> | |
14 | #include <linux/fs.h> | |
15 | #include <linux/sched.h> | |
16 | #include <linux/slab.h> | |
17 | ||
18 | #include <linux/platform_device.h> | |
19 | #include <media/v4l2-mem2mem.h> | |
20 | #include <media/v4l2-device.h> | |
21 | #include <media/v4l2-ioctl.h> | |
22 | #include <media/v4l2-ctrls.h> | |
23 | #include <media/v4l2-event.h> | |
24 | #include <media/videobuf2-vmalloc.h> | |
25 | ||
cd12b401 | 26 | #include "codec-v4l2-fwht.h" |
256bf813 HV |
27 | |
28 | MODULE_DESCRIPTION("Virtual codec device"); | |
29 | MODULE_AUTHOR("Hans Verkuil <[email protected]>"); | |
30 | MODULE_LICENSE("GPL v2"); | |
31 | ||
32 | static bool multiplanar; | |
33 | module_param(multiplanar, bool, 0444); | |
34 | MODULE_PARM_DESC(multiplanar, | |
35 | " use multi-planar API instead of single-planar API"); | |
36 | ||
37 | static unsigned int debug; | |
38 | module_param(debug, uint, 0644); | |
39 | MODULE_PARM_DESC(debug, " activates debug info"); | |
40 | ||
41 | #define VICODEC_NAME "vicodec" | |
42 | #define MAX_WIDTH 4096U | |
43 | #define MIN_WIDTH 640U | |
44 | #define MAX_HEIGHT 2160U | |
7cf7b2e9 | 45 | #define MIN_HEIGHT 360U |
256bf813 HV |
46 | |
47 | #define dprintk(dev, fmt, arg...) \ | |
48 | v4l2_dbg(1, debug, &dev->v4l2_dev, "%s: " fmt, __func__, ## arg) | |
49 | ||
50 | ||
29a7a5e9 HV |
51 | struct pixfmt_info { |
52 | u32 id; | |
53 | unsigned int bytesperline_mult; | |
54 | unsigned int sizeimage_mult; | |
55 | unsigned int sizeimage_div; | |
56 | unsigned int luma_step; | |
57 | unsigned int chroma_step; | |
58 | /* Chroma plane subsampling */ | |
59 | unsigned int width_div; | |
60 | unsigned int height_div; | |
61 | }; | |
62 | ||
cd12b401 | 63 | static const struct v4l2_fwht_pixfmt_info pixfmt_fwht = { |
29a7a5e9 HV |
64 | V4L2_PIX_FMT_FWHT, 0, 3, 1, 1, 1, 1, 1 |
65 | }; | |
66 | ||
256bf813 HV |
67 | static void vicodec_dev_release(struct device *dev) |
68 | { | |
69 | } | |
70 | ||
71 | static struct platform_device vicodec_pdev = { | |
72 | .name = VICODEC_NAME, | |
73 | .dev.release = vicodec_dev_release, | |
74 | }; | |
75 | ||
76 | /* Per-queue, driver-specific private data */ | |
77 | struct vicodec_q_data { | |
78 | unsigned int width; | |
79 | unsigned int height; | |
256bf813 HV |
80 | unsigned int sizeimage; |
81 | unsigned int sequence; | |
cd12b401 | 82 | const struct v4l2_fwht_pixfmt_info *info; |
256bf813 HV |
83 | }; |
84 | ||
85 | enum { | |
86 | V4L2_M2M_SRC = 0, | |
87 | V4L2_M2M_DST = 1, | |
88 | }; | |
89 | ||
90 | struct vicodec_dev { | |
91 | struct v4l2_device v4l2_dev; | |
92 | struct video_device enc_vfd; | |
93 | struct video_device dec_vfd; | |
94 | #ifdef CONFIG_MEDIA_CONTROLLER | |
95 | struct media_device mdev; | |
96 | #endif | |
97 | ||
98 | struct mutex enc_mutex; | |
99 | struct mutex dec_mutex; | |
100 | spinlock_t enc_lock; | |
101 | spinlock_t dec_lock; | |
102 | ||
103 | struct v4l2_m2m_dev *enc_dev; | |
104 | struct v4l2_m2m_dev *dec_dev; | |
105 | }; | |
106 | ||
107 | struct vicodec_ctx { | |
108 | struct v4l2_fh fh; | |
109 | struct vicodec_dev *dev; | |
110 | bool is_enc; | |
111 | spinlock_t *lock; | |
112 | ||
113 | struct v4l2_ctrl_handler hdl; | |
256bf813 | 114 | |
256bf813 HV |
115 | struct vb2_v4l2_buffer *last_src_buf; |
116 | struct vb2_v4l2_buffer *last_dst_buf; | |
117 | ||
256bf813 HV |
118 | /* Source and destination queue data */ |
119 | struct vicodec_q_data q_data[2]; | |
cd12b401 HV |
120 | struct v4l2_fwht_state state; |
121 | ||
256bf813 HV |
122 | u32 cur_buf_offset; |
123 | u32 comp_max_size; | |
124 | u32 comp_size; | |
125 | u32 comp_magic_cnt; | |
126 | u32 comp_frame_size; | |
127 | bool comp_has_frame; | |
128 | bool comp_has_next_frame; | |
129 | }; | |
130 | ||
256bf813 HV |
131 | static inline struct vicodec_ctx *file2ctx(struct file *file) |
132 | { | |
133 | return container_of(file->private_data, struct vicodec_ctx, fh); | |
134 | } | |
135 | ||
136 | static struct vicodec_q_data *get_q_data(struct vicodec_ctx *ctx, | |
137 | enum v4l2_buf_type type) | |
138 | { | |
139 | switch (type) { | |
140 | case V4L2_BUF_TYPE_VIDEO_OUTPUT: | |
141 | case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: | |
142 | return &ctx->q_data[V4L2_M2M_SRC]; | |
143 | case V4L2_BUF_TYPE_VIDEO_CAPTURE: | |
144 | case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: | |
145 | return &ctx->q_data[V4L2_M2M_DST]; | |
146 | default: | |
147 | WARN_ON(1); | |
148 | break; | |
149 | } | |
150 | return NULL; | |
151 | } | |
152 | ||
256bf813 HV |
153 | static int device_process(struct vicodec_ctx *ctx, |
154 | struct vb2_v4l2_buffer *in_vb, | |
155 | struct vb2_v4l2_buffer *out_vb) | |
156 | { | |
157 | struct vicodec_dev *dev = ctx->dev; | |
703fe34b | 158 | struct vicodec_q_data *q_cap; |
cd12b401 | 159 | struct v4l2_fwht_state *state = &ctx->state; |
256bf813 HV |
160 | u8 *p_in, *p_out; |
161 | int ret; | |
162 | ||
256bf813 HV |
163 | q_cap = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE); |
164 | if (ctx->is_enc) | |
165 | p_in = vb2_plane_vaddr(&in_vb->vb2_buf, 0); | |
166 | else | |
cd12b401 | 167 | p_in = state->compressed_frame; |
256bf813 HV |
168 | p_out = vb2_plane_vaddr(&out_vb->vb2_buf, 0); |
169 | if (!p_in || !p_out) { | |
170 | v4l2_err(&dev->v4l2_dev, | |
171 | "Acquiring kernel pointers to buffers failed\n"); | |
172 | return -EFAULT; | |
173 | } | |
174 | ||
175 | if (ctx->is_enc) { | |
b09d8b25 | 176 | struct vicodec_q_data *q_out; |
256bf813 | 177 | |
b09d8b25 HV |
178 | q_out = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); |
179 | state->info = q_out->info; | |
180 | ret = v4l2_fwht_encode(state, p_in, p_out); | |
181 | if (ret < 0) | |
182 | return ret; | |
183 | vb2_set_plane_payload(&out_vb->vb2_buf, 0, ret); | |
256bf813 | 184 | } else { |
b09d8b25 | 185 | state->info = q_cap->info; |
cd12b401 | 186 | ret = v4l2_fwht_decode(state, p_in, p_out); |
b09d8b25 | 187 | if (ret < 0) |
256bf813 | 188 | return ret; |
29a7a5e9 | 189 | vb2_set_plane_payload(&out_vb->vb2_buf, 0, q_cap->sizeimage); |
256bf813 HV |
190 | } |
191 | ||
192 | out_vb->sequence = q_cap->sequence++; | |
193 | out_vb->vb2_buf.timestamp = in_vb->vb2_buf.timestamp; | |
194 | ||
195 | if (in_vb->flags & V4L2_BUF_FLAG_TIMECODE) | |
196 | out_vb->timecode = in_vb->timecode; | |
197 | out_vb->field = in_vb->field; | |
198 | out_vb->flags &= ~V4L2_BUF_FLAG_LAST; | |
199 | out_vb->flags |= in_vb->flags & | |
200 | (V4L2_BUF_FLAG_TIMECODE | | |
201 | V4L2_BUF_FLAG_KEYFRAME | | |
202 | V4L2_BUF_FLAG_PFRAME | | |
203 | V4L2_BUF_FLAG_BFRAME | | |
204 | V4L2_BUF_FLAG_TSTAMP_SRC_MASK); | |
205 | ||
206 | return 0; | |
207 | } | |
208 | ||
209 | /* | |
210 | * mem2mem callbacks | |
211 | */ | |
212 | ||
213 | /* device_run() - prepares and starts the device */ | |
214 | static void device_run(void *priv) | |
215 | { | |
216 | static const struct v4l2_event eos_event = { | |
217 | .type = V4L2_EVENT_EOS | |
218 | }; | |
219 | struct vicodec_ctx *ctx = priv; | |
220 | struct vicodec_dev *dev = ctx->dev; | |
221 | struct vb2_v4l2_buffer *src_buf, *dst_buf; | |
222 | struct vicodec_q_data *q_out; | |
223 | u32 state; | |
224 | ||
225 | src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); | |
226 | dst_buf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); | |
227 | q_out = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); | |
228 | ||
229 | state = VB2_BUF_STATE_DONE; | |
230 | if (device_process(ctx, src_buf, dst_buf)) | |
231 | state = VB2_BUF_STATE_ERROR; | |
232 | ctx->last_dst_buf = dst_buf; | |
233 | ||
234 | spin_lock(ctx->lock); | |
235 | if (!ctx->comp_has_next_frame && src_buf == ctx->last_src_buf) { | |
236 | dst_buf->flags |= V4L2_BUF_FLAG_LAST; | |
237 | v4l2_event_queue_fh(&ctx->fh, &eos_event); | |
238 | } | |
239 | if (ctx->is_enc) { | |
240 | src_buf->sequence = q_out->sequence++; | |
241 | src_buf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); | |
242 | v4l2_m2m_buf_done(src_buf, state); | |
243 | } else if (vb2_get_plane_payload(&src_buf->vb2_buf, 0) == ctx->cur_buf_offset) { | |
244 | src_buf->sequence = q_out->sequence++; | |
245 | src_buf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); | |
246 | v4l2_m2m_buf_done(src_buf, state); | |
247 | ctx->cur_buf_offset = 0; | |
248 | ctx->comp_has_next_frame = false; | |
249 | } | |
250 | v4l2_m2m_buf_done(dst_buf, state); | |
251 | ctx->comp_size = 0; | |
252 | ctx->comp_magic_cnt = 0; | |
253 | ctx->comp_has_frame = false; | |
254 | spin_unlock(ctx->lock); | |
255 | ||
256 | if (ctx->is_enc) | |
257 | v4l2_m2m_job_finish(dev->enc_dev, ctx->fh.m2m_ctx); | |
258 | else | |
259 | v4l2_m2m_job_finish(dev->dec_dev, ctx->fh.m2m_ctx); | |
260 | } | |
261 | ||
262 | static void job_remove_out_buf(struct vicodec_ctx *ctx, u32 state) | |
263 | { | |
264 | struct vb2_v4l2_buffer *src_buf; | |
265 | struct vicodec_q_data *q_out; | |
266 | ||
267 | q_out = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); | |
268 | spin_lock(ctx->lock); | |
269 | src_buf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); | |
270 | src_buf->sequence = q_out->sequence++; | |
271 | v4l2_m2m_buf_done(src_buf, state); | |
272 | ctx->cur_buf_offset = 0; | |
273 | spin_unlock(ctx->lock); | |
274 | } | |
275 | ||
276 | static int job_ready(void *priv) | |
277 | { | |
278 | static const u8 magic[] = { | |
279 | 0x4f, 0x4f, 0x4f, 0x4f, 0xff, 0xff, 0xff, 0xff | |
280 | }; | |
281 | struct vicodec_ctx *ctx = priv; | |
282 | struct vb2_v4l2_buffer *src_buf; | |
283 | u8 *p_out; | |
284 | u8 *p; | |
285 | u32 sz; | |
286 | u32 state; | |
287 | ||
288 | if (ctx->is_enc || ctx->comp_has_frame) | |
289 | return 1; | |
290 | ||
291 | restart: | |
292 | ctx->comp_has_next_frame = false; | |
293 | src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); | |
294 | if (!src_buf) | |
295 | return 0; | |
296 | p_out = vb2_plane_vaddr(&src_buf->vb2_buf, 0); | |
297 | sz = vb2_get_plane_payload(&src_buf->vb2_buf, 0); | |
298 | p = p_out + ctx->cur_buf_offset; | |
299 | ||
300 | state = VB2_BUF_STATE_DONE; | |
301 | ||
302 | if (!ctx->comp_size) { | |
303 | state = VB2_BUF_STATE_ERROR; | |
304 | for (; p < p_out + sz; p++) { | |
305 | u32 copy; | |
306 | ||
307 | p = memchr(p, magic[ctx->comp_magic_cnt], sz); | |
308 | if (!p) { | |
309 | ctx->comp_magic_cnt = 0; | |
310 | break; | |
311 | } | |
312 | copy = sizeof(magic) - ctx->comp_magic_cnt; | |
313 | if (p_out + sz - p < copy) | |
314 | copy = p_out + sz - p; | |
cd12b401 | 315 | memcpy(ctx->state.compressed_frame + ctx->comp_magic_cnt, |
256bf813 HV |
316 | p, copy); |
317 | ctx->comp_magic_cnt += copy; | |
cd12b401 HV |
318 | if (!memcmp(ctx->state.compressed_frame, magic, |
319 | ctx->comp_magic_cnt)) { | |
256bf813 HV |
320 | p += copy; |
321 | state = VB2_BUF_STATE_DONE; | |
322 | break; | |
323 | } | |
324 | ctx->comp_magic_cnt = 0; | |
325 | } | |
326 | if (ctx->comp_magic_cnt < sizeof(magic)) { | |
327 | job_remove_out_buf(ctx, state); | |
328 | goto restart; | |
329 | } | |
330 | ctx->comp_size = sizeof(magic); | |
331 | } | |
21abebf0 HV |
332 | if (ctx->comp_size < sizeof(struct fwht_cframe_hdr)) { |
333 | struct fwht_cframe_hdr *p_hdr = | |
cd12b401 | 334 | (struct fwht_cframe_hdr *)ctx->state.compressed_frame; |
21abebf0 | 335 | u32 copy = sizeof(struct fwht_cframe_hdr) - ctx->comp_size; |
256bf813 HV |
336 | |
337 | if (copy > p_out + sz - p) | |
338 | copy = p_out + sz - p; | |
cd12b401 | 339 | memcpy(ctx->state.compressed_frame + ctx->comp_size, |
256bf813 HV |
340 | p, copy); |
341 | p += copy; | |
342 | ctx->comp_size += copy; | |
21abebf0 | 343 | if (ctx->comp_size < sizeof(struct fwht_cframe_hdr)) { |
256bf813 HV |
344 | job_remove_out_buf(ctx, state); |
345 | goto restart; | |
346 | } | |
347 | ctx->comp_frame_size = ntohl(p_hdr->size) + sizeof(*p_hdr); | |
348 | if (ctx->comp_frame_size > ctx->comp_max_size) | |
349 | ctx->comp_frame_size = ctx->comp_max_size; | |
350 | } | |
351 | if (ctx->comp_size < ctx->comp_frame_size) { | |
352 | u32 copy = ctx->comp_frame_size - ctx->comp_size; | |
353 | ||
354 | if (copy > p_out + sz - p) | |
355 | copy = p_out + sz - p; | |
cd12b401 | 356 | memcpy(ctx->state.compressed_frame + ctx->comp_size, |
256bf813 HV |
357 | p, copy); |
358 | p += copy; | |
359 | ctx->comp_size += copy; | |
360 | if (ctx->comp_size < ctx->comp_frame_size) { | |
361 | job_remove_out_buf(ctx, state); | |
362 | goto restart; | |
363 | } | |
364 | } | |
365 | ctx->cur_buf_offset = p - p_out; | |
366 | ctx->comp_has_frame = true; | |
367 | ctx->comp_has_next_frame = false; | |
21abebf0 HV |
368 | if (sz - ctx->cur_buf_offset >= sizeof(struct fwht_cframe_hdr)) { |
369 | struct fwht_cframe_hdr *p_hdr = (struct fwht_cframe_hdr *)p; | |
256bf813 HV |
370 | u32 frame_size = ntohl(p_hdr->size); |
371 | u32 remaining = sz - ctx->cur_buf_offset - sizeof(*p_hdr); | |
372 | ||
373 | if (!memcmp(p, magic, sizeof(magic))) | |
374 | ctx->comp_has_next_frame = remaining >= frame_size; | |
375 | } | |
376 | return 1; | |
377 | } | |
378 | ||
256bf813 HV |
379 | /* |
380 | * video ioctls | |
381 | */ | |
382 | ||
cd12b401 | 383 | static const struct v4l2_fwht_pixfmt_info *find_fmt(u32 fmt) |
256bf813 | 384 | { |
cd12b401 HV |
385 | const struct v4l2_fwht_pixfmt_info *info = |
386 | v4l2_fwht_find_pixfmt(fmt); | |
256bf813 | 387 | |
cd12b401 HV |
388 | if (!info) |
389 | info = v4l2_fwht_get_pixfmt(0); | |
390 | return info; | |
256bf813 HV |
391 | } |
392 | ||
393 | static int vidioc_querycap(struct file *file, void *priv, | |
394 | struct v4l2_capability *cap) | |
395 | { | |
396 | strncpy(cap->driver, VICODEC_NAME, sizeof(cap->driver) - 1); | |
397 | strncpy(cap->card, VICODEC_NAME, sizeof(cap->card) - 1); | |
398 | snprintf(cap->bus_info, sizeof(cap->bus_info), | |
399 | "platform:%s", VICODEC_NAME); | |
400 | cap->device_caps = V4L2_CAP_STREAMING | | |
401 | (multiplanar ? | |
402 | V4L2_CAP_VIDEO_M2M_MPLANE : | |
403 | V4L2_CAP_VIDEO_M2M); | |
404 | cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; | |
405 | return 0; | |
406 | } | |
407 | ||
408 | static int enum_fmt(struct v4l2_fmtdesc *f, bool is_enc, bool is_out) | |
409 | { | |
29a7a5e9 | 410 | bool is_uncomp = (is_enc && is_out) || (!is_enc && !is_out); |
256bf813 HV |
411 | |
412 | if (V4L2_TYPE_IS_MULTIPLANAR(f->type) && !multiplanar) | |
413 | return -EINVAL; | |
414 | if (!V4L2_TYPE_IS_MULTIPLANAR(f->type) && multiplanar) | |
415 | return -EINVAL; | |
256bf813 | 416 | |
cd12b401 HV |
417 | if (is_uncomp) { |
418 | const struct v4l2_fwht_pixfmt_info *info = | |
419 | v4l2_fwht_get_pixfmt(f->index); | |
420 | ||
421 | if (!info) | |
422 | return -EINVAL; | |
423 | f->pixelformat = info->id; | |
424 | } else { | |
425 | if (f->index) | |
426 | return -EINVAL; | |
256bf813 | 427 | f->pixelformat = V4L2_PIX_FMT_FWHT; |
cd12b401 | 428 | } |
256bf813 HV |
429 | return 0; |
430 | } | |
431 | ||
432 | static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv, | |
433 | struct v4l2_fmtdesc *f) | |
434 | { | |
435 | struct vicodec_ctx *ctx = file2ctx(file); | |
436 | ||
437 | return enum_fmt(f, ctx->is_enc, false); | |
438 | } | |
439 | ||
440 | static int vidioc_enum_fmt_vid_out(struct file *file, void *priv, | |
441 | struct v4l2_fmtdesc *f) | |
442 | { | |
443 | struct vicodec_ctx *ctx = file2ctx(file); | |
444 | ||
445 | return enum_fmt(f, ctx->is_enc, true); | |
446 | } | |
447 | ||
448 | static int vidioc_g_fmt(struct vicodec_ctx *ctx, struct v4l2_format *f) | |
449 | { | |
450 | struct vb2_queue *vq; | |
451 | struct vicodec_q_data *q_data; | |
452 | struct v4l2_pix_format_mplane *pix_mp; | |
453 | struct v4l2_pix_format *pix; | |
cd12b401 | 454 | const struct v4l2_fwht_pixfmt_info *info; |
256bf813 HV |
455 | |
456 | vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); | |
457 | if (!vq) | |
458 | return -EINVAL; | |
459 | ||
460 | q_data = get_q_data(ctx, f->type); | |
29a7a5e9 | 461 | info = q_data->info; |
256bf813 HV |
462 | |
463 | switch (f->type) { | |
464 | case V4L2_BUF_TYPE_VIDEO_CAPTURE: | |
465 | case V4L2_BUF_TYPE_VIDEO_OUTPUT: | |
466 | if (multiplanar) | |
467 | return -EINVAL; | |
468 | pix = &f->fmt.pix; | |
469 | pix->width = q_data->width; | |
470 | pix->height = q_data->height; | |
471 | pix->field = V4L2_FIELD_NONE; | |
29a7a5e9 HV |
472 | pix->pixelformat = info->id; |
473 | pix->bytesperline = q_data->width * info->bytesperline_mult; | |
256bf813 | 474 | pix->sizeimage = q_data->sizeimage; |
cd12b401 HV |
475 | pix->colorspace = ctx->state.colorspace; |
476 | pix->xfer_func = ctx->state.xfer_func; | |
477 | pix->ycbcr_enc = ctx->state.ycbcr_enc; | |
478 | pix->quantization = ctx->state.quantization; | |
256bf813 HV |
479 | break; |
480 | ||
481 | case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: | |
482 | case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: | |
483 | if (!multiplanar) | |
484 | return -EINVAL; | |
485 | pix_mp = &f->fmt.pix_mp; | |
486 | pix_mp->width = q_data->width; | |
487 | pix_mp->height = q_data->height; | |
488 | pix_mp->field = V4L2_FIELD_NONE; | |
29a7a5e9 | 489 | pix_mp->pixelformat = info->id; |
256bf813 | 490 | pix_mp->num_planes = 1; |
29a7a5e9 HV |
491 | pix_mp->plane_fmt[0].bytesperline = |
492 | q_data->width * info->bytesperline_mult; | |
256bf813 | 493 | pix_mp->plane_fmt[0].sizeimage = q_data->sizeimage; |
cd12b401 HV |
494 | pix_mp->colorspace = ctx->state.colorspace; |
495 | pix_mp->xfer_func = ctx->state.xfer_func; | |
496 | pix_mp->ycbcr_enc = ctx->state.ycbcr_enc; | |
497 | pix_mp->quantization = ctx->state.quantization; | |
256bf813 HV |
498 | memset(pix_mp->reserved, 0, sizeof(pix_mp->reserved)); |
499 | memset(pix_mp->plane_fmt[0].reserved, 0, | |
500 | sizeof(pix_mp->plane_fmt[0].reserved)); | |
501 | break; | |
502 | default: | |
503 | return -EINVAL; | |
504 | } | |
505 | return 0; | |
506 | } | |
507 | ||
508 | static int vidioc_g_fmt_vid_out(struct file *file, void *priv, | |
509 | struct v4l2_format *f) | |
510 | { | |
511 | return vidioc_g_fmt(file2ctx(file), f); | |
512 | } | |
513 | ||
514 | static int vidioc_g_fmt_vid_cap(struct file *file, void *priv, | |
515 | struct v4l2_format *f) | |
516 | { | |
517 | return vidioc_g_fmt(file2ctx(file), f); | |
518 | } | |
519 | ||
520 | static int vidioc_try_fmt(struct vicodec_ctx *ctx, struct v4l2_format *f) | |
521 | { | |
522 | struct v4l2_pix_format_mplane *pix_mp; | |
523 | struct v4l2_pix_format *pix; | |
29a7a5e9 | 524 | struct v4l2_plane_pix_format *plane; |
cd12b401 | 525 | const struct v4l2_fwht_pixfmt_info *info = &pixfmt_fwht; |
256bf813 HV |
526 | |
527 | switch (f->type) { | |
528 | case V4L2_BUF_TYPE_VIDEO_CAPTURE: | |
529 | case V4L2_BUF_TYPE_VIDEO_OUTPUT: | |
530 | pix = &f->fmt.pix; | |
29a7a5e9 HV |
531 | if (pix->pixelformat != V4L2_PIX_FMT_FWHT) |
532 | info = find_fmt(pix->pixelformat); | |
256bf813 HV |
533 | pix->width = clamp(pix->width, MIN_WIDTH, MAX_WIDTH) & ~7; |
534 | pix->height = clamp(pix->height, MIN_HEIGHT, MAX_HEIGHT) & ~7; | |
256bf813 | 535 | pix->field = V4L2_FIELD_NONE; |
29a7a5e9 HV |
536 | pix->bytesperline = |
537 | pix->width * info->bytesperline_mult; | |
538 | pix->sizeimage = pix->width * pix->height * | |
539 | info->sizeimage_mult / info->sizeimage_div; | |
540 | if (pix->pixelformat == V4L2_PIX_FMT_FWHT) | |
21abebf0 | 541 | pix->sizeimage += sizeof(struct fwht_cframe_hdr); |
256bf813 HV |
542 | break; |
543 | case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: | |
544 | case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: | |
545 | pix_mp = &f->fmt.pix_mp; | |
29a7a5e9 HV |
546 | plane = pix_mp->plane_fmt; |
547 | if (pix_mp->pixelformat != V4L2_PIX_FMT_FWHT) | |
548 | info = find_fmt(pix_mp->pixelformat); | |
549 | pix_mp->num_planes = 1; | |
256bf813 HV |
550 | pix_mp->width = clamp(pix_mp->width, MIN_WIDTH, MAX_WIDTH) & ~7; |
551 | pix_mp->height = | |
552 | clamp(pix_mp->height, MIN_HEIGHT, MAX_HEIGHT) & ~7; | |
256bf813 | 553 | pix_mp->field = V4L2_FIELD_NONE; |
29a7a5e9 HV |
554 | plane->bytesperline = |
555 | pix_mp->width * info->bytesperline_mult; | |
556 | plane->sizeimage = pix_mp->width * pix_mp->height * | |
557 | info->sizeimage_mult / info->sizeimage_div; | |
558 | if (pix_mp->pixelformat == V4L2_PIX_FMT_FWHT) | |
21abebf0 | 559 | plane->sizeimage += sizeof(struct fwht_cframe_hdr); |
256bf813 | 560 | memset(pix_mp->reserved, 0, sizeof(pix_mp->reserved)); |
29a7a5e9 | 561 | memset(plane->reserved, 0, sizeof(plane->reserved)); |
256bf813 HV |
562 | break; |
563 | default: | |
564 | return -EINVAL; | |
565 | } | |
566 | ||
567 | return 0; | |
568 | } | |
569 | ||
570 | static int vidioc_try_fmt_vid_cap(struct file *file, void *priv, | |
571 | struct v4l2_format *f) | |
572 | { | |
573 | struct vicodec_ctx *ctx = file2ctx(file); | |
574 | struct v4l2_pix_format_mplane *pix_mp; | |
575 | struct v4l2_pix_format *pix; | |
576 | ||
577 | switch (f->type) { | |
578 | case V4L2_BUF_TYPE_VIDEO_CAPTURE: | |
579 | if (multiplanar) | |
580 | return -EINVAL; | |
581 | pix = &f->fmt.pix; | |
582 | pix->pixelformat = ctx->is_enc ? V4L2_PIX_FMT_FWHT : | |
29a7a5e9 | 583 | find_fmt(f->fmt.pix.pixelformat)->id; |
cd12b401 HV |
584 | pix->colorspace = ctx->state.colorspace; |
585 | pix->xfer_func = ctx->state.xfer_func; | |
586 | pix->ycbcr_enc = ctx->state.ycbcr_enc; | |
587 | pix->quantization = ctx->state.quantization; | |
256bf813 HV |
588 | break; |
589 | case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: | |
590 | if (!multiplanar) | |
591 | return -EINVAL; | |
592 | pix_mp = &f->fmt.pix_mp; | |
593 | pix_mp->pixelformat = ctx->is_enc ? V4L2_PIX_FMT_FWHT : | |
29a7a5e9 | 594 | find_fmt(pix_mp->pixelformat)->id; |
cd12b401 HV |
595 | pix_mp->colorspace = ctx->state.colorspace; |
596 | pix_mp->xfer_func = ctx->state.xfer_func; | |
597 | pix_mp->ycbcr_enc = ctx->state.ycbcr_enc; | |
598 | pix_mp->quantization = ctx->state.quantization; | |
256bf813 HV |
599 | break; |
600 | default: | |
601 | return -EINVAL; | |
602 | } | |
603 | ||
604 | return vidioc_try_fmt(ctx, f); | |
605 | } | |
606 | ||
607 | static int vidioc_try_fmt_vid_out(struct file *file, void *priv, | |
608 | struct v4l2_format *f) | |
609 | { | |
610 | struct vicodec_ctx *ctx = file2ctx(file); | |
611 | struct v4l2_pix_format_mplane *pix_mp; | |
612 | struct v4l2_pix_format *pix; | |
613 | ||
614 | switch (f->type) { | |
615 | case V4L2_BUF_TYPE_VIDEO_OUTPUT: | |
616 | if (multiplanar) | |
617 | return -EINVAL; | |
618 | pix = &f->fmt.pix; | |
619 | pix->pixelformat = !ctx->is_enc ? V4L2_PIX_FMT_FWHT : | |
29a7a5e9 | 620 | find_fmt(pix->pixelformat)->id; |
256bf813 HV |
621 | if (!pix->colorspace) |
622 | pix->colorspace = V4L2_COLORSPACE_REC709; | |
623 | break; | |
624 | case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: | |
625 | if (!multiplanar) | |
626 | return -EINVAL; | |
627 | pix_mp = &f->fmt.pix_mp; | |
628 | pix_mp->pixelformat = !ctx->is_enc ? V4L2_PIX_FMT_FWHT : | |
29a7a5e9 | 629 | find_fmt(pix_mp->pixelformat)->id; |
256bf813 HV |
630 | if (!pix_mp->colorspace) |
631 | pix_mp->colorspace = V4L2_COLORSPACE_REC709; | |
632 | break; | |
633 | default: | |
634 | return -EINVAL; | |
635 | } | |
636 | ||
637 | return vidioc_try_fmt(ctx, f); | |
638 | } | |
639 | ||
640 | static int vidioc_s_fmt(struct vicodec_ctx *ctx, struct v4l2_format *f) | |
641 | { | |
642 | struct vicodec_q_data *q_data; | |
643 | struct vb2_queue *vq; | |
644 | bool fmt_changed = true; | |
645 | struct v4l2_pix_format_mplane *pix_mp; | |
646 | struct v4l2_pix_format *pix; | |
647 | ||
648 | vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); | |
649 | if (!vq) | |
650 | return -EINVAL; | |
651 | ||
652 | q_data = get_q_data(ctx, f->type); | |
653 | if (!q_data) | |
654 | return -EINVAL; | |
655 | ||
656 | switch (f->type) { | |
657 | case V4L2_BUF_TYPE_VIDEO_CAPTURE: | |
658 | case V4L2_BUF_TYPE_VIDEO_OUTPUT: | |
659 | pix = &f->fmt.pix; | |
660 | if (ctx->is_enc && V4L2_TYPE_IS_OUTPUT(f->type)) | |
661 | fmt_changed = | |
29a7a5e9 | 662 | q_data->info->id != pix->pixelformat || |
256bf813 HV |
663 | q_data->width != pix->width || |
664 | q_data->height != pix->height; | |
665 | ||
666 | if (vb2_is_busy(vq) && fmt_changed) | |
667 | return -EBUSY; | |
668 | ||
29a7a5e9 HV |
669 | if (pix->pixelformat == V4L2_PIX_FMT_FWHT) |
670 | q_data->info = &pixfmt_fwht; | |
671 | else | |
672 | q_data->info = find_fmt(pix->pixelformat); | |
256bf813 HV |
673 | q_data->width = pix->width; |
674 | q_data->height = pix->height; | |
675 | q_data->sizeimage = pix->sizeimage; | |
676 | break; | |
677 | case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: | |
678 | case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: | |
679 | pix_mp = &f->fmt.pix_mp; | |
680 | if (ctx->is_enc && V4L2_TYPE_IS_OUTPUT(f->type)) | |
681 | fmt_changed = | |
29a7a5e9 | 682 | q_data->info->id != pix_mp->pixelformat || |
256bf813 HV |
683 | q_data->width != pix_mp->width || |
684 | q_data->height != pix_mp->height; | |
685 | ||
686 | if (vb2_is_busy(vq) && fmt_changed) | |
687 | return -EBUSY; | |
688 | ||
29a7a5e9 HV |
689 | if (pix_mp->pixelformat == V4L2_PIX_FMT_FWHT) |
690 | q_data->info = &pixfmt_fwht; | |
691 | else | |
692 | q_data->info = find_fmt(pix_mp->pixelformat); | |
256bf813 HV |
693 | q_data->width = pix_mp->width; |
694 | q_data->height = pix_mp->height; | |
695 | q_data->sizeimage = pix_mp->plane_fmt[0].sizeimage; | |
696 | break; | |
697 | default: | |
698 | return -EINVAL; | |
699 | } | |
700 | ||
701 | dprintk(ctx->dev, | |
702 | "Setting format for type %d, wxh: %dx%d, fourcc: %08x\n", | |
29a7a5e9 | 703 | f->type, q_data->width, q_data->height, q_data->info->id); |
256bf813 HV |
704 | |
705 | return 0; | |
706 | } | |
707 | ||
708 | static int vidioc_s_fmt_vid_cap(struct file *file, void *priv, | |
709 | struct v4l2_format *f) | |
710 | { | |
711 | int ret; | |
712 | ||
713 | ret = vidioc_try_fmt_vid_cap(file, priv, f); | |
714 | if (ret) | |
715 | return ret; | |
716 | ||
717 | return vidioc_s_fmt(file2ctx(file), f); | |
718 | } | |
719 | ||
720 | static int vidioc_s_fmt_vid_out(struct file *file, void *priv, | |
721 | struct v4l2_format *f) | |
722 | { | |
723 | struct vicodec_ctx *ctx = file2ctx(file); | |
724 | struct v4l2_pix_format_mplane *pix_mp; | |
725 | struct v4l2_pix_format *pix; | |
726 | int ret; | |
727 | ||
728 | ret = vidioc_try_fmt_vid_out(file, priv, f); | |
729 | if (ret) | |
730 | return ret; | |
731 | ||
732 | ret = vidioc_s_fmt(file2ctx(file), f); | |
733 | if (!ret) { | |
734 | switch (f->type) { | |
735 | case V4L2_BUF_TYPE_VIDEO_CAPTURE: | |
736 | case V4L2_BUF_TYPE_VIDEO_OUTPUT: | |
737 | pix = &f->fmt.pix; | |
cd12b401 HV |
738 | ctx->state.colorspace = pix->colorspace; |
739 | ctx->state.xfer_func = pix->xfer_func; | |
740 | ctx->state.ycbcr_enc = pix->ycbcr_enc; | |
741 | ctx->state.quantization = pix->quantization; | |
256bf813 HV |
742 | break; |
743 | case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: | |
744 | case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: | |
745 | pix_mp = &f->fmt.pix_mp; | |
cd12b401 HV |
746 | ctx->state.colorspace = pix_mp->colorspace; |
747 | ctx->state.xfer_func = pix_mp->xfer_func; | |
748 | ctx->state.ycbcr_enc = pix_mp->ycbcr_enc; | |
749 | ctx->state.quantization = pix_mp->quantization; | |
256bf813 HV |
750 | break; |
751 | default: | |
752 | break; | |
753 | } | |
754 | } | |
755 | return ret; | |
756 | } | |
757 | ||
758 | static void vicodec_mark_last_buf(struct vicodec_ctx *ctx) | |
759 | { | |
760 | static const struct v4l2_event eos_event = { | |
761 | .type = V4L2_EVENT_EOS | |
762 | }; | |
763 | ||
764 | spin_lock(ctx->lock); | |
765 | ctx->last_src_buf = v4l2_m2m_last_src_buf(ctx->fh.m2m_ctx); | |
766 | if (!ctx->last_src_buf && ctx->last_dst_buf) { | |
767 | ctx->last_dst_buf->flags |= V4L2_BUF_FLAG_LAST; | |
768 | v4l2_event_queue_fh(&ctx->fh, &eos_event); | |
769 | } | |
770 | spin_unlock(ctx->lock); | |
771 | } | |
772 | ||
773 | static int vicodec_try_encoder_cmd(struct file *file, void *fh, | |
774 | struct v4l2_encoder_cmd *ec) | |
775 | { | |
776 | if (ec->cmd != V4L2_ENC_CMD_STOP) | |
777 | return -EINVAL; | |
778 | ||
779 | if (ec->flags & V4L2_ENC_CMD_STOP_AT_GOP_END) | |
780 | return -EINVAL; | |
781 | ||
782 | return 0; | |
783 | } | |
784 | ||
785 | static int vicodec_encoder_cmd(struct file *file, void *fh, | |
786 | struct v4l2_encoder_cmd *ec) | |
787 | { | |
788 | struct vicodec_ctx *ctx = file2ctx(file); | |
789 | int ret; | |
790 | ||
791 | ret = vicodec_try_encoder_cmd(file, fh, ec); | |
792 | if (ret < 0) | |
793 | return ret; | |
794 | ||
795 | vicodec_mark_last_buf(ctx); | |
796 | return 0; | |
797 | } | |
798 | ||
799 | static int vicodec_try_decoder_cmd(struct file *file, void *fh, | |
800 | struct v4l2_decoder_cmd *dc) | |
801 | { | |
802 | if (dc->cmd != V4L2_DEC_CMD_STOP) | |
803 | return -EINVAL; | |
804 | ||
805 | if (dc->flags & V4L2_DEC_CMD_STOP_TO_BLACK) | |
806 | return -EINVAL; | |
807 | ||
808 | if (!(dc->flags & V4L2_DEC_CMD_STOP_IMMEDIATELY) && (dc->stop.pts != 0)) | |
809 | return -EINVAL; | |
810 | ||
811 | return 0; | |
812 | } | |
813 | ||
814 | static int vicodec_decoder_cmd(struct file *file, void *fh, | |
815 | struct v4l2_decoder_cmd *dc) | |
816 | { | |
817 | struct vicodec_ctx *ctx = file2ctx(file); | |
818 | int ret; | |
819 | ||
820 | ret = vicodec_try_decoder_cmd(file, fh, dc); | |
821 | if (ret < 0) | |
822 | return ret; | |
823 | ||
824 | vicodec_mark_last_buf(ctx); | |
825 | return 0; | |
826 | } | |
827 | ||
828 | static int vicodec_enum_framesizes(struct file *file, void *fh, | |
829 | struct v4l2_frmsizeenum *fsize) | |
830 | { | |
831 | switch (fsize->pixel_format) { | |
832 | case V4L2_PIX_FMT_FWHT: | |
833 | break; | |
834 | default: | |
29a7a5e9 | 835 | if (find_fmt(fsize->pixel_format)->id == fsize->pixel_format) |
256bf813 HV |
836 | break; |
837 | return -EINVAL; | |
838 | } | |
839 | ||
840 | if (fsize->index) | |
841 | return -EINVAL; | |
842 | ||
843 | fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE; | |
844 | ||
845 | fsize->stepwise.min_width = MIN_WIDTH; | |
846 | fsize->stepwise.max_width = MAX_WIDTH; | |
847 | fsize->stepwise.step_width = 8; | |
848 | fsize->stepwise.min_height = MIN_HEIGHT; | |
849 | fsize->stepwise.max_height = MAX_HEIGHT; | |
850 | fsize->stepwise.step_height = 8; | |
851 | ||
852 | return 0; | |
853 | } | |
854 | ||
855 | static int vicodec_subscribe_event(struct v4l2_fh *fh, | |
856 | const struct v4l2_event_subscription *sub) | |
857 | { | |
858 | switch (sub->type) { | |
859 | case V4L2_EVENT_EOS: | |
860 | return v4l2_event_subscribe(fh, sub, 0, NULL); | |
861 | default: | |
862 | return v4l2_ctrl_subscribe_event(fh, sub); | |
863 | } | |
864 | } | |
865 | ||
866 | static const struct v4l2_ioctl_ops vicodec_ioctl_ops = { | |
867 | .vidioc_querycap = vidioc_querycap, | |
868 | ||
869 | .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap, | |
870 | .vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap, | |
871 | .vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap, | |
872 | .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap, | |
873 | ||
874 | .vidioc_enum_fmt_vid_cap_mplane = vidioc_enum_fmt_vid_cap, | |
875 | .vidioc_g_fmt_vid_cap_mplane = vidioc_g_fmt_vid_cap, | |
876 | .vidioc_try_fmt_vid_cap_mplane = vidioc_try_fmt_vid_cap, | |
877 | .vidioc_s_fmt_vid_cap_mplane = vidioc_s_fmt_vid_cap, | |
878 | ||
879 | .vidioc_enum_fmt_vid_out = vidioc_enum_fmt_vid_out, | |
880 | .vidioc_g_fmt_vid_out = vidioc_g_fmt_vid_out, | |
881 | .vidioc_try_fmt_vid_out = vidioc_try_fmt_vid_out, | |
882 | .vidioc_s_fmt_vid_out = vidioc_s_fmt_vid_out, | |
883 | ||
884 | .vidioc_enum_fmt_vid_out_mplane = vidioc_enum_fmt_vid_out, | |
885 | .vidioc_g_fmt_vid_out_mplane = vidioc_g_fmt_vid_out, | |
886 | .vidioc_try_fmt_vid_out_mplane = vidioc_try_fmt_vid_out, | |
887 | .vidioc_s_fmt_vid_out_mplane = vidioc_s_fmt_vid_out, | |
888 | ||
889 | .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs, | |
890 | .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, | |
891 | .vidioc_qbuf = v4l2_m2m_ioctl_qbuf, | |
892 | .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf, | |
893 | .vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf, | |
894 | .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs, | |
895 | .vidioc_expbuf = v4l2_m2m_ioctl_expbuf, | |
896 | ||
897 | .vidioc_streamon = v4l2_m2m_ioctl_streamon, | |
898 | .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, | |
899 | ||
900 | .vidioc_try_encoder_cmd = vicodec_try_encoder_cmd, | |
901 | .vidioc_encoder_cmd = vicodec_encoder_cmd, | |
902 | .vidioc_try_decoder_cmd = vicodec_try_decoder_cmd, | |
903 | .vidioc_decoder_cmd = vicodec_decoder_cmd, | |
904 | .vidioc_enum_framesizes = vicodec_enum_framesizes, | |
905 | ||
906 | .vidioc_subscribe_event = vicodec_subscribe_event, | |
907 | .vidioc_unsubscribe_event = v4l2_event_unsubscribe, | |
908 | }; | |
909 | ||
910 | ||
911 | /* | |
912 | * Queue operations | |
913 | */ | |
914 | ||
915 | static int vicodec_queue_setup(struct vb2_queue *vq, unsigned int *nbuffers, | |
916 | unsigned int *nplanes, unsigned int sizes[], | |
917 | struct device *alloc_devs[]) | |
918 | { | |
919 | struct vicodec_ctx *ctx = vb2_get_drv_priv(vq); | |
920 | struct vicodec_q_data *q_data = get_q_data(ctx, vq->type); | |
921 | unsigned int size = q_data->sizeimage; | |
922 | ||
923 | if (*nplanes) | |
924 | return sizes[0] < size ? -EINVAL : 0; | |
925 | ||
926 | *nplanes = 1; | |
927 | sizes[0] = size; | |
928 | return 0; | |
929 | } | |
930 | ||
931 | static int vicodec_buf_prepare(struct vb2_buffer *vb) | |
932 | { | |
933 | struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); | |
934 | struct vicodec_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); | |
935 | struct vicodec_q_data *q_data; | |
936 | ||
937 | dprintk(ctx->dev, "type: %d\n", vb->vb2_queue->type); | |
938 | ||
939 | q_data = get_q_data(ctx, vb->vb2_queue->type); | |
940 | if (V4L2_TYPE_IS_OUTPUT(vb->vb2_queue->type)) { | |
941 | if (vbuf->field == V4L2_FIELD_ANY) | |
942 | vbuf->field = V4L2_FIELD_NONE; | |
943 | if (vbuf->field != V4L2_FIELD_NONE) { | |
944 | dprintk(ctx->dev, "%s field isn't supported\n", | |
945 | __func__); | |
946 | return -EINVAL; | |
947 | } | |
948 | } | |
949 | ||
950 | if (vb2_plane_size(vb, 0) < q_data->sizeimage) { | |
951 | dprintk(ctx->dev, | |
952 | "%s data will not fit into plane (%lu < %lu)\n", | |
953 | __func__, vb2_plane_size(vb, 0), | |
954 | (long)q_data->sizeimage); | |
955 | return -EINVAL; | |
956 | } | |
957 | ||
958 | return 0; | |
959 | } | |
960 | ||
961 | static void vicodec_buf_queue(struct vb2_buffer *vb) | |
962 | { | |
963 | struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); | |
964 | struct vicodec_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); | |
965 | ||
966 | v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf); | |
967 | } | |
968 | ||
969 | static void vicodec_return_bufs(struct vb2_queue *q, u32 state) | |
970 | { | |
971 | struct vicodec_ctx *ctx = vb2_get_drv_priv(q); | |
972 | struct vb2_v4l2_buffer *vbuf; | |
973 | ||
974 | for (;;) { | |
975 | if (V4L2_TYPE_IS_OUTPUT(q->type)) | |
976 | vbuf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); | |
977 | else | |
978 | vbuf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); | |
979 | if (vbuf == NULL) | |
980 | return; | |
981 | spin_lock(ctx->lock); | |
982 | v4l2_m2m_buf_done(vbuf, state); | |
983 | spin_unlock(ctx->lock); | |
984 | } | |
985 | } | |
986 | ||
987 | static int vicodec_start_streaming(struct vb2_queue *q, | |
988 | unsigned int count) | |
989 | { | |
990 | struct vicodec_ctx *ctx = vb2_get_drv_priv(q); | |
991 | struct vicodec_q_data *q_data = get_q_data(ctx, q->type); | |
cd12b401 | 992 | struct v4l2_fwht_state *state = &ctx->state; |
256bf813 | 993 | unsigned int size = q_data->width * q_data->height; |
cd12b401 | 994 | const struct v4l2_fwht_pixfmt_info *info = q_data->info; |
29a7a5e9 | 995 | unsigned int chroma_div = info->width_div * info->height_div; |
256bf813 HV |
996 | |
997 | q_data->sequence = 0; | |
998 | ||
999 | if (!V4L2_TYPE_IS_OUTPUT(q->type)) | |
1000 | return 0; | |
1001 | ||
cd12b401 HV |
1002 | state->width = q_data->width; |
1003 | state->height = q_data->height; | |
1004 | state->ref_frame.width = state->ref_frame.height = 0; | |
1005 | state->ref_frame.luma = kvmalloc(size + 2 * size / chroma_div, | |
1006 | GFP_KERNEL); | |
29a7a5e9 | 1007 | ctx->comp_max_size = size + 2 * size / chroma_div + |
21abebf0 | 1008 | sizeof(struct fwht_cframe_hdr); |
cd12b401 HV |
1009 | state->compressed_frame = kvmalloc(ctx->comp_max_size, GFP_KERNEL); |
1010 | if (!state->ref_frame.luma || !state->compressed_frame) { | |
1011 | kvfree(state->ref_frame.luma); | |
1012 | kvfree(state->compressed_frame); | |
256bf813 HV |
1013 | vicodec_return_bufs(q, VB2_BUF_STATE_QUEUED); |
1014 | return -ENOMEM; | |
1015 | } | |
cd12b401 HV |
1016 | state->ref_frame.cb = state->ref_frame.luma + size; |
1017 | state->ref_frame.cr = state->ref_frame.cb + size / chroma_div; | |
256bf813 HV |
1018 | ctx->last_src_buf = NULL; |
1019 | ctx->last_dst_buf = NULL; | |
cd12b401 | 1020 | state->gop_cnt = 0; |
256bf813 HV |
1021 | ctx->cur_buf_offset = 0; |
1022 | ctx->comp_size = 0; | |
1023 | ctx->comp_magic_cnt = 0; | |
1024 | ctx->comp_has_frame = false; | |
1025 | ||
1026 | return 0; | |
1027 | } | |
1028 | ||
1029 | static void vicodec_stop_streaming(struct vb2_queue *q) | |
1030 | { | |
1031 | struct vicodec_ctx *ctx = vb2_get_drv_priv(q); | |
1032 | ||
1033 | vicodec_return_bufs(q, VB2_BUF_STATE_ERROR); | |
1034 | ||
1035 | if (!V4L2_TYPE_IS_OUTPUT(q->type)) | |
1036 | return; | |
1037 | ||
cd12b401 HV |
1038 | kvfree(ctx->state.ref_frame.luma); |
1039 | kvfree(ctx->state.compressed_frame); | |
256bf813 HV |
1040 | } |
1041 | ||
1042 | static const struct vb2_ops vicodec_qops = { | |
1043 | .queue_setup = vicodec_queue_setup, | |
1044 | .buf_prepare = vicodec_buf_prepare, | |
1045 | .buf_queue = vicodec_buf_queue, | |
1046 | .start_streaming = vicodec_start_streaming, | |
1047 | .stop_streaming = vicodec_stop_streaming, | |
1048 | .wait_prepare = vb2_ops_wait_prepare, | |
1049 | .wait_finish = vb2_ops_wait_finish, | |
1050 | }; | |
1051 | ||
1052 | static int queue_init(void *priv, struct vb2_queue *src_vq, | |
1053 | struct vb2_queue *dst_vq) | |
1054 | { | |
1055 | struct vicodec_ctx *ctx = priv; | |
1056 | int ret; | |
1057 | ||
1058 | src_vq->type = (multiplanar ? | |
1059 | V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE : | |
1060 | V4L2_BUF_TYPE_VIDEO_OUTPUT); | |
1061 | src_vq->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF; | |
1062 | src_vq->drv_priv = ctx; | |
1063 | src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); | |
1064 | src_vq->ops = &vicodec_qops; | |
1065 | src_vq->mem_ops = &vb2_vmalloc_memops; | |
1066 | src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; | |
1067 | src_vq->lock = ctx->is_enc ? &ctx->dev->enc_mutex : | |
1068 | &ctx->dev->dec_mutex; | |
1069 | ||
1070 | ret = vb2_queue_init(src_vq); | |
1071 | if (ret) | |
1072 | return ret; | |
1073 | ||
1074 | dst_vq->type = (multiplanar ? | |
1075 | V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE : | |
1076 | V4L2_BUF_TYPE_VIDEO_CAPTURE); | |
1077 | dst_vq->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF; | |
1078 | dst_vq->drv_priv = ctx; | |
1079 | dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); | |
1080 | dst_vq->ops = &vicodec_qops; | |
1081 | dst_vq->mem_ops = &vb2_vmalloc_memops; | |
1082 | dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; | |
1083 | dst_vq->lock = src_vq->lock; | |
1084 | ||
1085 | return vb2_queue_init(dst_vq); | |
1086 | } | |
1087 | ||
48568b0c HV |
1088 | #define VICODEC_CID_CUSTOM_BASE (V4L2_CID_MPEG_BASE | 0xf000) |
1089 | #define VICODEC_CID_I_FRAME_QP (VICODEC_CID_CUSTOM_BASE + 0) | |
1090 | #define VICODEC_CID_P_FRAME_QP (VICODEC_CID_CUSTOM_BASE + 1) | |
1091 | ||
1092 | static int vicodec_s_ctrl(struct v4l2_ctrl *ctrl) | |
1093 | { | |
1094 | struct vicodec_ctx *ctx = container_of(ctrl->handler, | |
1095 | struct vicodec_ctx, hdl); | |
1096 | ||
1097 | switch (ctrl->id) { | |
1098 | case V4L2_CID_MPEG_VIDEO_GOP_SIZE: | |
cd12b401 | 1099 | ctx->state.gop_size = ctrl->val; |
48568b0c HV |
1100 | return 0; |
1101 | case VICODEC_CID_I_FRAME_QP: | |
cd12b401 | 1102 | ctx->state.i_frame_qp = ctrl->val; |
48568b0c HV |
1103 | return 0; |
1104 | case VICODEC_CID_P_FRAME_QP: | |
cd12b401 | 1105 | ctx->state.p_frame_qp = ctrl->val; |
48568b0c HV |
1106 | return 0; |
1107 | } | |
1108 | return -EINVAL; | |
1109 | } | |
1110 | ||
cd12b401 | 1111 | static struct v4l2_ctrl_ops vicodec_ctrl_ops = { |
48568b0c HV |
1112 | .s_ctrl = vicodec_s_ctrl, |
1113 | }; | |
1114 | ||
1115 | static const struct v4l2_ctrl_config vicodec_ctrl_i_frame = { | |
1116 | .ops = &vicodec_ctrl_ops, | |
1117 | .id = VICODEC_CID_I_FRAME_QP, | |
1118 | .name = "FWHT I-Frame QP Value", | |
1119 | .type = V4L2_CTRL_TYPE_INTEGER, | |
1120 | .min = 1, | |
1121 | .max = 31, | |
1122 | .def = 20, | |
1123 | .step = 1, | |
1124 | }; | |
1125 | ||
1126 | static const struct v4l2_ctrl_config vicodec_ctrl_p_frame = { | |
1127 | .ops = &vicodec_ctrl_ops, | |
1128 | .id = VICODEC_CID_P_FRAME_QP, | |
1129 | .name = "FWHT P-Frame QP Value", | |
1130 | .type = V4L2_CTRL_TYPE_INTEGER, | |
1131 | .min = 1, | |
1132 | .max = 31, | |
1133 | .def = 20, | |
1134 | .step = 1, | |
1135 | }; | |
1136 | ||
256bf813 HV |
1137 | /* |
1138 | * File operations | |
1139 | */ | |
1140 | static int vicodec_open(struct file *file) | |
1141 | { | |
1142 | struct video_device *vfd = video_devdata(file); | |
1143 | struct vicodec_dev *dev = video_drvdata(file); | |
1144 | struct vicodec_ctx *ctx = NULL; | |
1145 | struct v4l2_ctrl_handler *hdl; | |
1146 | unsigned int size; | |
1147 | int rc = 0; | |
1148 | ||
1149 | if (mutex_lock_interruptible(vfd->lock)) | |
1150 | return -ERESTARTSYS; | |
1151 | ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); | |
1152 | if (!ctx) { | |
1153 | rc = -ENOMEM; | |
1154 | goto open_unlock; | |
1155 | } | |
1156 | ||
1157 | if (vfd == &dev->enc_vfd) | |
1158 | ctx->is_enc = true; | |
1159 | ||
1160 | v4l2_fh_init(&ctx->fh, video_devdata(file)); | |
1161 | file->private_data = &ctx->fh; | |
1162 | ctx->dev = dev; | |
1163 | hdl = &ctx->hdl; | |
1164 | v4l2_ctrl_handler_init(hdl, 4); | |
48568b0c HV |
1165 | v4l2_ctrl_new_std(hdl, &vicodec_ctrl_ops, V4L2_CID_MPEG_VIDEO_GOP_SIZE, |
1166 | 1, 16, 1, 10); | |
1167 | v4l2_ctrl_new_custom(hdl, &vicodec_ctrl_i_frame, NULL); | |
1168 | v4l2_ctrl_new_custom(hdl, &vicodec_ctrl_p_frame, NULL); | |
256bf813 HV |
1169 | if (hdl->error) { |
1170 | rc = hdl->error; | |
1171 | v4l2_ctrl_handler_free(hdl); | |
1172 | kfree(ctx); | |
1173 | goto open_unlock; | |
1174 | } | |
1175 | ctx->fh.ctrl_handler = hdl; | |
1176 | v4l2_ctrl_handler_setup(hdl); | |
1177 | ||
29a7a5e9 | 1178 | ctx->q_data[V4L2_M2M_SRC].info = |
cd12b401 | 1179 | ctx->is_enc ? v4l2_fwht_get_pixfmt(0) : &pixfmt_fwht; |
256bf813 HV |
1180 | ctx->q_data[V4L2_M2M_SRC].width = 1280; |
1181 | ctx->q_data[V4L2_M2M_SRC].height = 720; | |
29a7a5e9 HV |
1182 | size = 1280 * 720 * ctx->q_data[V4L2_M2M_SRC].info->sizeimage_mult / |
1183 | ctx->q_data[V4L2_M2M_SRC].info->sizeimage_div; | |
55f6fe09 HV |
1184 | if (ctx->is_enc) |
1185 | ctx->q_data[V4L2_M2M_SRC].sizeimage = size; | |
1186 | else | |
1187 | ctx->q_data[V4L2_M2M_SRC].sizeimage = | |
1188 | size + sizeof(struct fwht_cframe_hdr); | |
256bf813 | 1189 | ctx->q_data[V4L2_M2M_DST] = ctx->q_data[V4L2_M2M_SRC]; |
29a7a5e9 | 1190 | ctx->q_data[V4L2_M2M_DST].info = |
cd12b401 | 1191 | ctx->is_enc ? &pixfmt_fwht : v4l2_fwht_get_pixfmt(0); |
29a7a5e9 HV |
1192 | size = 1280 * 720 * ctx->q_data[V4L2_M2M_DST].info->sizeimage_mult / |
1193 | ctx->q_data[V4L2_M2M_DST].info->sizeimage_div; | |
55f6fe09 HV |
1194 | if (ctx->is_enc) |
1195 | ctx->q_data[V4L2_M2M_DST].sizeimage = | |
1196 | size + sizeof(struct fwht_cframe_hdr); | |
1197 | else | |
1198 | ctx->q_data[V4L2_M2M_DST].sizeimage = size; | |
cd12b401 | 1199 | ctx->state.colorspace = V4L2_COLORSPACE_REC709; |
256bf813 | 1200 | |
256bf813 | 1201 | if (ctx->is_enc) { |
256bf813 HV |
1202 | ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(dev->enc_dev, ctx, |
1203 | &queue_init); | |
1204 | ctx->lock = &dev->enc_lock; | |
1205 | } else { | |
256bf813 HV |
1206 | ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(dev->dec_dev, ctx, |
1207 | &queue_init); | |
1208 | ctx->lock = &dev->dec_lock; | |
1209 | } | |
1210 | ||
1211 | if (IS_ERR(ctx->fh.m2m_ctx)) { | |
1212 | rc = PTR_ERR(ctx->fh.m2m_ctx); | |
1213 | ||
1214 | v4l2_ctrl_handler_free(hdl); | |
1215 | v4l2_fh_exit(&ctx->fh); | |
1216 | kfree(ctx); | |
1217 | goto open_unlock; | |
1218 | } | |
1219 | ||
1220 | v4l2_fh_add(&ctx->fh); | |
1221 | ||
1222 | open_unlock: | |
1223 | mutex_unlock(vfd->lock); | |
1224 | return rc; | |
1225 | } | |
1226 | ||
1227 | static int vicodec_release(struct file *file) | |
1228 | { | |
1229 | struct video_device *vfd = video_devdata(file); | |
1230 | struct vicodec_ctx *ctx = file2ctx(file); | |
1231 | ||
1232 | v4l2_fh_del(&ctx->fh); | |
1233 | v4l2_fh_exit(&ctx->fh); | |
1234 | v4l2_ctrl_handler_free(&ctx->hdl); | |
1235 | mutex_lock(vfd->lock); | |
1236 | v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); | |
1237 | mutex_unlock(vfd->lock); | |
1238 | kfree(ctx); | |
1239 | ||
1240 | return 0; | |
1241 | } | |
1242 | ||
1243 | static const struct v4l2_file_operations vicodec_fops = { | |
1244 | .owner = THIS_MODULE, | |
1245 | .open = vicodec_open, | |
1246 | .release = vicodec_release, | |
1247 | .poll = v4l2_m2m_fop_poll, | |
1248 | .unlocked_ioctl = video_ioctl2, | |
1249 | .mmap = v4l2_m2m_fop_mmap, | |
1250 | }; | |
1251 | ||
1252 | static const struct video_device vicodec_videodev = { | |
1253 | .name = VICODEC_NAME, | |
1254 | .vfl_dir = VFL_DIR_M2M, | |
1255 | .fops = &vicodec_fops, | |
1256 | .ioctl_ops = &vicodec_ioctl_ops, | |
1257 | .minor = -1, | |
1258 | .release = video_device_release_empty, | |
1259 | }; | |
1260 | ||
1261 | static const struct v4l2_m2m_ops m2m_ops = { | |
1262 | .device_run = device_run, | |
256bf813 HV |
1263 | .job_ready = job_ready, |
1264 | }; | |
1265 | ||
1266 | static int vicodec_probe(struct platform_device *pdev) | |
1267 | { | |
1268 | struct vicodec_dev *dev; | |
1269 | struct video_device *vfd; | |
1270 | int ret; | |
1271 | ||
1272 | dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); | |
1273 | if (!dev) | |
1274 | return -ENOMEM; | |
1275 | ||
1276 | spin_lock_init(&dev->enc_lock); | |
1277 | spin_lock_init(&dev->dec_lock); | |
1278 | ||
1279 | ret = v4l2_device_register(&pdev->dev, &dev->v4l2_dev); | |
1280 | if (ret) | |
1281 | return ret; | |
1282 | ||
1283 | #ifdef CONFIG_MEDIA_CONTROLLER | |
1284 | dev->mdev.dev = &pdev->dev; | |
c0decac1 | 1285 | strscpy(dev->mdev.model, "vicodec", sizeof(dev->mdev.model)); |
256bf813 HV |
1286 | media_device_init(&dev->mdev); |
1287 | dev->v4l2_dev.mdev = &dev->mdev; | |
1288 | #endif | |
1289 | ||
1290 | mutex_init(&dev->enc_mutex); | |
1291 | mutex_init(&dev->dec_mutex); | |
1292 | ||
1293 | platform_set_drvdata(pdev, dev); | |
1294 | ||
1295 | dev->enc_dev = v4l2_m2m_init(&m2m_ops); | |
1296 | if (IS_ERR(dev->enc_dev)) { | |
1297 | v4l2_err(&dev->v4l2_dev, "Failed to init vicodec device\n"); | |
1298 | ret = PTR_ERR(dev->enc_dev); | |
1299 | goto unreg_dev; | |
1300 | } | |
1301 | ||
1302 | dev->dec_dev = v4l2_m2m_init(&m2m_ops); | |
1303 | if (IS_ERR(dev->dec_dev)) { | |
1304 | v4l2_err(&dev->v4l2_dev, "Failed to init vicodec device\n"); | |
1305 | ret = PTR_ERR(dev->dec_dev); | |
1306 | goto err_enc_m2m; | |
1307 | } | |
1308 | ||
1309 | dev->enc_vfd = vicodec_videodev; | |
1310 | vfd = &dev->enc_vfd; | |
1311 | vfd->lock = &dev->enc_mutex; | |
1312 | vfd->v4l2_dev = &dev->v4l2_dev; | |
c0decac1 | 1313 | strscpy(vfd->name, "vicodec-enc", sizeof(vfd->name)); |
256bf813 HV |
1314 | v4l2_disable_ioctl(vfd, VIDIOC_DECODER_CMD); |
1315 | v4l2_disable_ioctl(vfd, VIDIOC_TRY_DECODER_CMD); | |
1316 | video_set_drvdata(vfd, dev); | |
1317 | ||
1318 | ret = video_register_device(vfd, VFL_TYPE_GRABBER, 0); | |
1319 | if (ret) { | |
1320 | v4l2_err(&dev->v4l2_dev, "Failed to register video device\n"); | |
1321 | goto err_dec_m2m; | |
1322 | } | |
1323 | v4l2_info(&dev->v4l2_dev, | |
1324 | "Device registered as /dev/video%d\n", vfd->num); | |
1325 | ||
1326 | dev->dec_vfd = vicodec_videodev; | |
1327 | vfd = &dev->dec_vfd; | |
1328 | vfd->lock = &dev->dec_mutex; | |
1329 | vfd->v4l2_dev = &dev->v4l2_dev; | |
c0decac1 | 1330 | strscpy(vfd->name, "vicodec-dec", sizeof(vfd->name)); |
256bf813 HV |
1331 | v4l2_disable_ioctl(vfd, VIDIOC_ENCODER_CMD); |
1332 | v4l2_disable_ioctl(vfd, VIDIOC_TRY_ENCODER_CMD); | |
1333 | video_set_drvdata(vfd, dev); | |
1334 | ||
1335 | ret = video_register_device(vfd, VFL_TYPE_GRABBER, 0); | |
1336 | if (ret) { | |
1337 | v4l2_err(&dev->v4l2_dev, "Failed to register video device\n"); | |
1338 | goto unreg_enc; | |
1339 | } | |
1340 | v4l2_info(&dev->v4l2_dev, | |
1341 | "Device registered as /dev/video%d\n", vfd->num); | |
1342 | ||
1343 | #ifdef CONFIG_MEDIA_CONTROLLER | |
1344 | ret = v4l2_m2m_register_media_controller(dev->enc_dev, | |
1345 | &dev->enc_vfd, MEDIA_ENT_F_PROC_VIDEO_ENCODER); | |
1346 | if (ret) { | |
1347 | v4l2_err(&dev->v4l2_dev, "Failed to init mem2mem media controller\n"); | |
1348 | goto unreg_m2m; | |
1349 | } | |
1350 | ||
1351 | ret = v4l2_m2m_register_media_controller(dev->dec_dev, | |
1352 | &dev->dec_vfd, MEDIA_ENT_F_PROC_VIDEO_DECODER); | |
1353 | if (ret) { | |
1354 | v4l2_err(&dev->v4l2_dev, "Failed to init mem2mem media controller\n"); | |
1355 | goto unreg_m2m_enc_mc; | |
1356 | } | |
1357 | ||
1358 | ret = media_device_register(&dev->mdev); | |
1359 | if (ret) { | |
1360 | v4l2_err(&dev->v4l2_dev, "Failed to register mem2mem media device\n"); | |
1361 | goto unreg_m2m_dec_mc; | |
1362 | } | |
1363 | #endif | |
1364 | return 0; | |
1365 | ||
1366 | #ifdef CONFIG_MEDIA_CONTROLLER | |
1367 | unreg_m2m_dec_mc: | |
1368 | v4l2_m2m_unregister_media_controller(dev->dec_dev); | |
1369 | unreg_m2m_enc_mc: | |
1370 | v4l2_m2m_unregister_media_controller(dev->enc_dev); | |
1371 | unreg_m2m: | |
1372 | video_unregister_device(&dev->dec_vfd); | |
1373 | #endif | |
1374 | unreg_enc: | |
1375 | video_unregister_device(&dev->enc_vfd); | |
1376 | err_dec_m2m: | |
1377 | v4l2_m2m_release(dev->dec_dev); | |
1378 | err_enc_m2m: | |
1379 | v4l2_m2m_release(dev->enc_dev); | |
1380 | unreg_dev: | |
1381 | v4l2_device_unregister(&dev->v4l2_dev); | |
1382 | ||
1383 | return ret; | |
1384 | } | |
1385 | ||
1386 | static int vicodec_remove(struct platform_device *pdev) | |
1387 | { | |
1388 | struct vicodec_dev *dev = platform_get_drvdata(pdev); | |
1389 | ||
1390 | v4l2_info(&dev->v4l2_dev, "Removing " VICODEC_NAME); | |
1391 | ||
1392 | #ifdef CONFIG_MEDIA_CONTROLLER | |
1393 | media_device_unregister(&dev->mdev); | |
1394 | v4l2_m2m_unregister_media_controller(dev->enc_dev); | |
1395 | v4l2_m2m_unregister_media_controller(dev->dec_dev); | |
1396 | media_device_cleanup(&dev->mdev); | |
1397 | #endif | |
1398 | ||
1399 | v4l2_m2m_release(dev->enc_dev); | |
1400 | v4l2_m2m_release(dev->dec_dev); | |
1401 | video_unregister_device(&dev->enc_vfd); | |
1402 | video_unregister_device(&dev->dec_vfd); | |
1403 | v4l2_device_unregister(&dev->v4l2_dev); | |
1404 | ||
1405 | return 0; | |
1406 | } | |
1407 | ||
1408 | static struct platform_driver vicodec_pdrv = { | |
1409 | .probe = vicodec_probe, | |
1410 | .remove = vicodec_remove, | |
1411 | .driver = { | |
1412 | .name = VICODEC_NAME, | |
1413 | }, | |
1414 | }; | |
1415 | ||
1416 | static void __exit vicodec_exit(void) | |
1417 | { | |
1418 | platform_driver_unregister(&vicodec_pdrv); | |
1419 | platform_device_unregister(&vicodec_pdev); | |
1420 | } | |
1421 | ||
1422 | static int __init vicodec_init(void) | |
1423 | { | |
1424 | int ret; | |
1425 | ||
1426 | ret = platform_device_register(&vicodec_pdev); | |
1427 | if (ret) | |
1428 | return ret; | |
1429 | ||
1430 | ret = platform_driver_register(&vicodec_pdrv); | |
1431 | if (ret) | |
1432 | platform_device_unregister(&vicodec_pdev); | |
1433 | ||
1434 | return ret; | |
1435 | } | |
1436 | ||
1437 | module_init(vicodec_init); | |
1438 | module_exit(vicodec_exit); |