1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * V4L2 Capture IC Preprocess Subdev for Freescale i.MX5/6 SOC |
4 | * |
5 | * This subdevice handles capture of video frames from the CSI or VDIC, |
6 | * which are routed directly to the Image Converter preprocess tasks, |
7 | * for resizing, colorspace conversion, and rotation. |
8 | * |
9 | * Copyright (c) 2012-2017 Mentor Graphics Inc. |
10 | */ |
11 | #include <linux/delay.h> |
12 | #include <linux/interrupt.h> |
13 | #include <linux/module.h> |
14 | #include <linux/sched.h> |
15 | #include <linux/slab.h> |
16 | #include <linux/spinlock.h> |
17 | #include <linux/timer.h> |
18 | #include <media/v4l2-ctrls.h> |
19 | #include <media/v4l2-device.h> |
20 | #include <media/v4l2-ioctl.h> |
21 | #include <media/v4l2-subdev.h> |
22 | #include <media/imx.h> |
23 | #include "imx-media.h" |
24 | #include "imx-ic.h" |
25 | |
26 | /* |
27 | * Min/Max supported width and heights. |
28 | */ |
29 | #define MIN_W 32 |
30 | #define MIN_H 32 |
31 | #define MAX_W 4096 |
32 | #define MAX_H 4096 |
33 | #define W_ALIGN 4 /* multiple of 16 pixels */ |
34 | #define H_ALIGN 1 /* multiple of 2 lines */ |
35 | #define S_ALIGN 1 /* multiple of 2 */ |
36 | |
37 | struct prp_priv { |
38 | struct imx_ic_priv *ic_priv; |
39 | struct media_pad pad[PRP_NUM_PADS]; |
40 | |
41 | /* lock to protect all members below */ |
42 | struct mutex lock; |
43 | |
44 | struct v4l2_subdev *src_sd; |
45 | struct v4l2_subdev *sink_sd_prpenc; |
46 | struct v4l2_subdev *sink_sd_prpvf; |
47 | |
48 | /* the CSI id at link validate */ |
49 | int csi_id; |
50 | |
51 | struct v4l2_mbus_framefmt format_mbus; |
52 | struct v4l2_fract frame_interval; |
53 | |
54 | int stream_count; |
55 | }; |
56 | |
57 | static inline struct prp_priv *sd_to_priv(struct v4l2_subdev *sd) |
58 | { |
59 | struct imx_ic_priv *ic_priv = v4l2_get_subdevdata(sd); |
60 | |
61 | return ic_priv->task_priv; |
62 | } |
63 | |
64 | static int prp_start(struct prp_priv *priv) |
65 | { |
66 | struct imx_ic_priv *ic_priv = priv->ic_priv; |
67 | bool src_is_vdic; |
68 | |
69 | /* set IC to receive from CSI or VDI depending on source */ |
70 | src_is_vdic = !!(priv->src_sd->grp_id & IMX_MEDIA_GRP_ID_IPU_VDIC); |
71 | |
72 | ipu_set_ic_src_mux(ipu: ic_priv->ipu, csi_id: priv->csi_id, vdi: src_is_vdic); |
73 | |
74 | return 0; |
75 | } |
76 | |
77 | static void prp_stop(struct prp_priv *priv) |
78 | { |
79 | } |
80 | |
81 | static struct v4l2_mbus_framefmt * |
82 | __prp_get_fmt(struct prp_priv *priv, struct v4l2_subdev_state *sd_state, |
83 | unsigned int pad, enum v4l2_subdev_format_whence which) |
84 | { |
85 | if (which == V4L2_SUBDEV_FORMAT_TRY) |
86 | return v4l2_subdev_state_get_format(sd_state, pad); |
87 | else |
88 | return &priv->format_mbus; |
89 | } |
90 | |
91 | /* |
92 | * V4L2 subdev operations. |
93 | */ |
94 | |
95 | static int prp_enum_mbus_code(struct v4l2_subdev *sd, |
96 | struct v4l2_subdev_state *sd_state, |
97 | struct v4l2_subdev_mbus_code_enum *code) |
98 | { |
99 | struct prp_priv *priv = sd_to_priv(sd); |
100 | struct v4l2_mbus_framefmt *infmt; |
101 | int ret = 0; |
102 | |
103 | mutex_lock(&priv->lock); |
104 | |
105 | switch (code->pad) { |
106 | case PRP_SINK_PAD: |
107 | ret = imx_media_enum_ipu_formats(code: &code->code, index: code->index, |
108 | fmt_sel: PIXFMT_SEL_YUV_RGB); |
109 | break; |
110 | case PRP_SRC_PAD_PRPENC: |
111 | case PRP_SRC_PAD_PRPVF: |
112 | if (code->index != 0) { |
113 | ret = -EINVAL; |
114 | goto out; |
115 | } |
116 | infmt = __prp_get_fmt(priv, sd_state, pad: PRP_SINK_PAD, |
117 | which: code->which); |
118 | code->code = infmt->code; |
119 | break; |
120 | default: |
121 | ret = -EINVAL; |
122 | } |
123 | out: |
124 | mutex_unlock(lock: &priv->lock); |
125 | return ret; |
126 | } |
127 | |
128 | static int prp_get_fmt(struct v4l2_subdev *sd, |
129 | struct v4l2_subdev_state *sd_state, |
130 | struct v4l2_subdev_format *sdformat) |
131 | { |
132 | struct prp_priv *priv = sd_to_priv(sd); |
133 | struct v4l2_mbus_framefmt *fmt; |
134 | int ret = 0; |
135 | |
136 | if (sdformat->pad >= PRP_NUM_PADS) |
137 | return -EINVAL; |
138 | |
139 | mutex_lock(&priv->lock); |
140 | |
141 | fmt = __prp_get_fmt(priv, sd_state, pad: sdformat->pad, which: sdformat->which); |
142 | if (!fmt) { |
143 | ret = -EINVAL; |
144 | goto out; |
145 | } |
146 | |
147 | sdformat->format = *fmt; |
148 | out: |
149 | mutex_unlock(lock: &priv->lock); |
150 | return ret; |
151 | } |
152 | |
153 | static int prp_set_fmt(struct v4l2_subdev *sd, |
154 | struct v4l2_subdev_state *sd_state, |
155 | struct v4l2_subdev_format *sdformat) |
156 | { |
157 | struct prp_priv *priv = sd_to_priv(sd); |
158 | struct v4l2_mbus_framefmt *fmt, *infmt; |
159 | const struct imx_media_pixfmt *cc; |
160 | int ret = 0; |
161 | u32 code; |
162 | |
163 | if (sdformat->pad >= PRP_NUM_PADS) |
164 | return -EINVAL; |
165 | |
166 | mutex_lock(&priv->lock); |
167 | |
168 | if (priv->stream_count > 0) { |
169 | ret = -EBUSY; |
170 | goto out; |
171 | } |
172 | |
173 | infmt = __prp_get_fmt(priv, sd_state, pad: PRP_SINK_PAD, which: sdformat->which); |
174 | |
175 | switch (sdformat->pad) { |
176 | case PRP_SINK_PAD: |
177 | v4l_bound_align_image(width: &sdformat->format.width, MIN_W, MAX_W, |
178 | W_ALIGN, height: &sdformat->format.height, |
179 | MIN_H, MAX_H, H_ALIGN, S_ALIGN); |
180 | |
181 | cc = imx_media_find_ipu_format(code: sdformat->format.code, |
182 | fmt_sel: PIXFMT_SEL_YUV_RGB); |
183 | if (!cc) { |
184 | imx_media_enum_ipu_formats(code: &code, index: 0, |
185 | fmt_sel: PIXFMT_SEL_YUV_RGB); |
186 | cc = imx_media_find_ipu_format(code, |
187 | fmt_sel: PIXFMT_SEL_YUV_RGB); |
188 | sdformat->format.code = cc->codes[0]; |
189 | } |
190 | |
191 | if (sdformat->format.field == V4L2_FIELD_ANY) |
192 | sdformat->format.field = V4L2_FIELD_NONE; |
193 | break; |
194 | case PRP_SRC_PAD_PRPENC: |
195 | case PRP_SRC_PAD_PRPVF: |
196 | /* Output pads mirror input pad */ |
197 | sdformat->format = *infmt; |
198 | break; |
199 | } |
200 | |
201 | imx_media_try_colorimetry(tryfmt: &sdformat->format, ic_route: true); |
202 | |
203 | fmt = __prp_get_fmt(priv, sd_state, pad: sdformat->pad, which: sdformat->which); |
204 | *fmt = sdformat->format; |
205 | out: |
206 | mutex_unlock(lock: &priv->lock); |
207 | return ret; |
208 | } |
209 | |
210 | static int prp_link_setup(struct media_entity *entity, |
211 | const struct media_pad *local, |
212 | const struct media_pad *remote, u32 flags) |
213 | { |
214 | struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity); |
215 | struct imx_ic_priv *ic_priv = v4l2_get_subdevdata(sd); |
216 | struct prp_priv *priv = ic_priv->task_priv; |
217 | struct v4l2_subdev *remote_sd; |
218 | int ret = 0; |
219 | |
220 | dev_dbg(ic_priv->ipu_dev, "%s: link setup %s -> %s" , |
221 | ic_priv->sd.name, remote->entity->name, local->entity->name); |
222 | |
223 | remote_sd = media_entity_to_v4l2_subdev(remote->entity); |
224 | |
225 | mutex_lock(&priv->lock); |
226 | |
227 | if (local->flags & MEDIA_PAD_FL_SINK) { |
228 | if (flags & MEDIA_LNK_FL_ENABLED) { |
229 | if (priv->src_sd) { |
230 | ret = -EBUSY; |
231 | goto out; |
232 | } |
233 | if (priv->sink_sd_prpenc && |
234 | (remote_sd->grp_id & IMX_MEDIA_GRP_ID_IPU_VDIC)) { |
235 | ret = -EINVAL; |
236 | goto out; |
237 | } |
238 | priv->src_sd = remote_sd; |
239 | } else { |
240 | priv->src_sd = NULL; |
241 | } |
242 | |
243 | goto out; |
244 | } |
245 | |
246 | /* this is a source pad */ |
247 | if (flags & MEDIA_LNK_FL_ENABLED) { |
248 | switch (local->index) { |
249 | case PRP_SRC_PAD_PRPENC: |
250 | if (priv->sink_sd_prpenc) { |
251 | ret = -EBUSY; |
252 | goto out; |
253 | } |
254 | if (priv->src_sd && (priv->src_sd->grp_id & |
255 | IMX_MEDIA_GRP_ID_IPU_VDIC)) { |
256 | ret = -EINVAL; |
257 | goto out; |
258 | } |
259 | priv->sink_sd_prpenc = remote_sd; |
260 | break; |
261 | case PRP_SRC_PAD_PRPVF: |
262 | if (priv->sink_sd_prpvf) { |
263 | ret = -EBUSY; |
264 | goto out; |
265 | } |
266 | priv->sink_sd_prpvf = remote_sd; |
267 | break; |
268 | default: |
269 | ret = -EINVAL; |
270 | } |
271 | } else { |
272 | switch (local->index) { |
273 | case PRP_SRC_PAD_PRPENC: |
274 | priv->sink_sd_prpenc = NULL; |
275 | break; |
276 | case PRP_SRC_PAD_PRPVF: |
277 | priv->sink_sd_prpvf = NULL; |
278 | break; |
279 | default: |
280 | ret = -EINVAL; |
281 | } |
282 | } |
283 | |
284 | out: |
285 | mutex_unlock(lock: &priv->lock); |
286 | return ret; |
287 | } |
288 | |
289 | static int prp_link_validate(struct v4l2_subdev *sd, |
290 | struct media_link *link, |
291 | struct v4l2_subdev_format *source_fmt, |
292 | struct v4l2_subdev_format *sink_fmt) |
293 | { |
294 | struct imx_ic_priv *ic_priv = v4l2_get_subdevdata(sd); |
295 | struct prp_priv *priv = ic_priv->task_priv; |
296 | struct v4l2_subdev *csi; |
297 | int ret; |
298 | |
299 | ret = v4l2_subdev_link_validate_default(sd, link, |
300 | source_fmt, sink_fmt); |
301 | if (ret) |
302 | return ret; |
303 | |
304 | csi = imx_media_pipeline_subdev(start_entity: &ic_priv->sd.entity, |
305 | IMX_MEDIA_GRP_ID_IPU_CSI, upstream: true); |
306 | if (IS_ERR(ptr: csi)) |
307 | csi = NULL; |
308 | |
309 | mutex_lock(&priv->lock); |
310 | |
311 | if (priv->src_sd->grp_id & IMX_MEDIA_GRP_ID_IPU_VDIC) { |
312 | /* |
313 | * the ->PRPENC link cannot be enabled if the source |
314 | * is the VDIC |
315 | */ |
316 | if (priv->sink_sd_prpenc) { |
317 | ret = -EINVAL; |
318 | goto out; |
319 | } |
320 | } else { |
321 | /* the source is a CSI */ |
322 | if (!csi) { |
323 | ret = -EINVAL; |
324 | goto out; |
325 | } |
326 | } |
327 | |
328 | if (csi) { |
329 | switch (csi->grp_id) { |
330 | case IMX_MEDIA_GRP_ID_IPU_CSI0: |
331 | priv->csi_id = 0; |
332 | break; |
333 | case IMX_MEDIA_GRP_ID_IPU_CSI1: |
334 | priv->csi_id = 1; |
335 | break; |
336 | default: |
337 | ret = -EINVAL; |
338 | } |
339 | } else { |
340 | priv->csi_id = 0; |
341 | } |
342 | |
343 | out: |
344 | mutex_unlock(lock: &priv->lock); |
345 | return ret; |
346 | } |
347 | |
348 | static int prp_s_stream(struct v4l2_subdev *sd, int enable) |
349 | { |
350 | struct imx_ic_priv *ic_priv = v4l2_get_subdevdata(sd); |
351 | struct prp_priv *priv = ic_priv->task_priv; |
352 | int ret = 0; |
353 | |
354 | mutex_lock(&priv->lock); |
355 | |
356 | if (!priv->src_sd || (!priv->sink_sd_prpenc && !priv->sink_sd_prpvf)) { |
357 | ret = -EPIPE; |
358 | goto out; |
359 | } |
360 | |
361 | /* |
362 | * enable/disable streaming only if stream_count is |
363 | * going from 0 to 1 / 1 to 0. |
364 | */ |
365 | if (priv->stream_count != !enable) |
366 | goto update_count; |
367 | |
368 | dev_dbg(ic_priv->ipu_dev, "%s: stream %s\n" , sd->name, |
369 | enable ? "ON" : "OFF" ); |
370 | |
371 | if (enable) |
372 | ret = prp_start(priv); |
373 | else |
374 | prp_stop(priv); |
375 | if (ret) |
376 | goto out; |
377 | |
378 | /* start/stop upstream */ |
379 | ret = v4l2_subdev_call(priv->src_sd, video, s_stream, enable); |
380 | ret = (ret && ret != -ENOIOCTLCMD) ? ret : 0; |
381 | if (ret) { |
382 | if (enable) |
383 | prp_stop(priv); |
384 | goto out; |
385 | } |
386 | |
387 | update_count: |
388 | priv->stream_count += enable ? 1 : -1; |
389 | if (priv->stream_count < 0) |
390 | priv->stream_count = 0; |
391 | out: |
392 | mutex_unlock(lock: &priv->lock); |
393 | return ret; |
394 | } |
395 | |
396 | static int prp_get_frame_interval(struct v4l2_subdev *sd, |
397 | struct v4l2_subdev_state *sd_state, |
398 | struct v4l2_subdev_frame_interval *fi) |
399 | { |
400 | struct prp_priv *priv = sd_to_priv(sd); |
401 | |
402 | /* |
403 | * FIXME: Implement support for V4L2_SUBDEV_FORMAT_TRY, using the V4L2 |
404 | * subdev active state API. |
405 | */ |
406 | if (fi->which != V4L2_SUBDEV_FORMAT_ACTIVE) |
407 | return -EINVAL; |
408 | |
409 | if (fi->pad >= PRP_NUM_PADS) |
410 | return -EINVAL; |
411 | |
412 | mutex_lock(&priv->lock); |
413 | fi->interval = priv->frame_interval; |
414 | mutex_unlock(lock: &priv->lock); |
415 | |
416 | return 0; |
417 | } |
418 | |
419 | static int prp_set_frame_interval(struct v4l2_subdev *sd, |
420 | struct v4l2_subdev_state *sd_state, |
421 | struct v4l2_subdev_frame_interval *fi) |
422 | { |
423 | struct prp_priv *priv = sd_to_priv(sd); |
424 | |
425 | /* |
426 | * FIXME: Implement support for V4L2_SUBDEV_FORMAT_TRY, using the V4L2 |
427 | * subdev active state API. |
428 | */ |
429 | if (fi->which != V4L2_SUBDEV_FORMAT_ACTIVE) |
430 | return -EINVAL; |
431 | |
432 | if (fi->pad >= PRP_NUM_PADS) |
433 | return -EINVAL; |
434 | |
435 | mutex_lock(&priv->lock); |
436 | |
437 | /* No limits on valid frame intervals */ |
438 | if (fi->interval.numerator == 0 || fi->interval.denominator == 0) |
439 | fi->interval = priv->frame_interval; |
440 | else |
441 | priv->frame_interval = fi->interval; |
442 | |
443 | mutex_unlock(lock: &priv->lock); |
444 | |
445 | return 0; |
446 | } |
447 | |
448 | static int prp_registered(struct v4l2_subdev *sd) |
449 | { |
450 | struct prp_priv *priv = sd_to_priv(sd); |
451 | u32 code; |
452 | |
453 | /* init default frame interval */ |
454 | priv->frame_interval.numerator = 1; |
455 | priv->frame_interval.denominator = 30; |
456 | |
457 | /* set a default mbus format */ |
458 | imx_media_enum_ipu_formats(code: &code, index: 0, fmt_sel: PIXFMT_SEL_YUV); |
459 | |
460 | return imx_media_init_mbus_fmt(mbus: &priv->format_mbus, |
461 | IMX_MEDIA_DEF_PIX_WIDTH, |
462 | IMX_MEDIA_DEF_PIX_HEIGHT, code, |
463 | field: V4L2_FIELD_NONE, NULL); |
464 | } |
465 | |
466 | static const struct v4l2_subdev_pad_ops prp_pad_ops = { |
467 | .enum_mbus_code = prp_enum_mbus_code, |
468 | .get_fmt = prp_get_fmt, |
469 | .set_fmt = prp_set_fmt, |
470 | .get_frame_interval = prp_get_frame_interval, |
471 | .set_frame_interval = prp_set_frame_interval, |
472 | .link_validate = prp_link_validate, |
473 | }; |
474 | |
475 | static const struct v4l2_subdev_video_ops prp_video_ops = { |
476 | .s_stream = prp_s_stream, |
477 | }; |
478 | |
479 | static const struct media_entity_operations prp_entity_ops = { |
480 | .link_setup = prp_link_setup, |
481 | .link_validate = v4l2_subdev_link_validate, |
482 | }; |
483 | |
484 | static const struct v4l2_subdev_ops prp_subdev_ops = { |
485 | .video = &prp_video_ops, |
486 | .pad = &prp_pad_ops, |
487 | }; |
488 | |
489 | static const struct v4l2_subdev_internal_ops prp_internal_ops = { |
490 | .init_state = imx_media_init_state, |
491 | .registered = prp_registered, |
492 | }; |
493 | |
494 | static int prp_init(struct imx_ic_priv *ic_priv) |
495 | { |
496 | struct prp_priv *priv; |
497 | int i; |
498 | |
499 | priv = devm_kzalloc(dev: ic_priv->ipu_dev, size: sizeof(*priv), GFP_KERNEL); |
500 | if (!priv) |
501 | return -ENOMEM; |
502 | |
503 | mutex_init(&priv->lock); |
504 | ic_priv->task_priv = priv; |
505 | priv->ic_priv = ic_priv; |
506 | |
507 | for (i = 0; i < PRP_NUM_PADS; i++) |
508 | priv->pad[i].flags = (i == PRP_SINK_PAD) ? |
509 | MEDIA_PAD_FL_SINK : MEDIA_PAD_FL_SOURCE; |
510 | |
511 | return media_entity_pads_init(entity: &ic_priv->sd.entity, num_pads: PRP_NUM_PADS, |
512 | pads: priv->pad); |
513 | } |
514 | |
515 | static void prp_remove(struct imx_ic_priv *ic_priv) |
516 | { |
517 | struct prp_priv *priv = ic_priv->task_priv; |
518 | |
519 | mutex_destroy(lock: &priv->lock); |
520 | } |
521 | |
522 | struct imx_ic_ops imx_ic_prp_ops = { |
523 | .subdev_ops = &prp_subdev_ops, |
524 | .internal_ops = &prp_internal_ops, |
525 | .entity_ops = &prp_entity_ops, |
526 | .init = prp_init, |
527 | .remove = prp_remove, |
528 | }; |
529 | |