Commit | Line | Data |
---|---|---|
5fd8f738 SN |
1 | /* |
2 | * S5P camera interface (video postprocessor) driver | |
3 | * | |
5f3cc447 | 4 | * Copyright (c) 2010 Samsung Electronics Co., Ltd |
5fd8f738 SN |
5 | * |
6 | * Sylwester Nawrocki, <s.nawrocki@samsung.com> | |
7 | * | |
8 | * This program is free software; you can redistribute it and/or modify | |
9 | * it under the terms of the GNU General Public License as published | |
10 | * by the Free Software Foundation, either version 2 of the License, | |
11 | * or (at your option) any later version. | |
12 | */ | |
13 | ||
14 | #include <linux/module.h> | |
15 | #include <linux/kernel.h> | |
16 | #include <linux/version.h> | |
17 | #include <linux/types.h> | |
18 | #include <linux/errno.h> | |
19 | #include <linux/bug.h> | |
20 | #include <linux/interrupt.h> | |
21 | #include <linux/device.h> | |
22 | #include <linux/platform_device.h> | |
23 | #include <linux/list.h> | |
24 | #include <linux/io.h> | |
25 | #include <linux/slab.h> | |
26 | #include <linux/clk.h> | |
27 | #include <media/v4l2-ioctl.h> | |
28 | #include <media/videobuf-dma-contig.h> | |
29 | ||
30 | #include "fimc-core.h" | |
31 | ||
32 | static char *fimc_clock_name[NUM_FIMC_CLOCKS] = { "sclk_fimc", "fimc" }; | |
33 | ||
34 | static struct fimc_fmt fimc_formats[] = { | |
35 | { | |
36 | .name = "RGB565", | |
37 | .fourcc = V4L2_PIX_FMT_RGB565X, | |
38 | .depth = 16, | |
39 | .color = S5P_FIMC_RGB565, | |
40 | .buff_cnt = 1, | |
5f3cc447 SN |
41 | .planes_cnt = 1, |
42 | .mbus_code = V4L2_MBUS_FMT_RGB565_2X8_BE, | |
43 | .flags = FMT_FLAGS_M2M, | |
5fd8f738 SN |
44 | }, { |
45 | .name = "BGR666", | |
46 | .fourcc = V4L2_PIX_FMT_BGR666, | |
47 | .depth = 32, | |
48 | .color = S5P_FIMC_RGB666, | |
49 | .buff_cnt = 1, | |
5f3cc447 SN |
50 | .planes_cnt = 1, |
51 | .flags = FMT_FLAGS_M2M, | |
5fd8f738 SN |
52 | }, { |
53 | .name = "XRGB-8-8-8-8, 24 bpp", | |
54 | .fourcc = V4L2_PIX_FMT_RGB24, | |
55 | .depth = 32, | |
56 | .color = S5P_FIMC_RGB888, | |
57 | .buff_cnt = 1, | |
5f3cc447 SN |
58 | .planes_cnt = 1, |
59 | .flags = FMT_FLAGS_M2M, | |
5fd8f738 SN |
60 | }, { |
61 | .name = "YUV 4:2:2 packed, YCbYCr", | |
62 | .fourcc = V4L2_PIX_FMT_YUYV, | |
63 | .depth = 16, | |
64 | .color = S5P_FIMC_YCBYCR422, | |
65 | .buff_cnt = 1, | |
5f3cc447 SN |
66 | .planes_cnt = 1, |
67 | .mbus_code = V4L2_MBUS_FMT_YUYV8_2X8, | |
68 | .flags = FMT_FLAGS_M2M | FMT_FLAGS_CAM, | |
69 | }, { | |
5fd8f738 SN |
70 | .name = "YUV 4:2:2 packed, CbYCrY", |
71 | .fourcc = V4L2_PIX_FMT_UYVY, | |
72 | .depth = 16, | |
73 | .color = S5P_FIMC_CBYCRY422, | |
74 | .buff_cnt = 1, | |
5f3cc447 SN |
75 | .planes_cnt = 1, |
76 | .mbus_code = V4L2_MBUS_FMT_UYVY8_2X8, | |
77 | .flags = FMT_FLAGS_M2M | FMT_FLAGS_CAM, | |
5fd8f738 SN |
78 | }, { |
79 | .name = "YUV 4:2:2 packed, CrYCbY", | |
80 | .fourcc = V4L2_PIX_FMT_VYUY, | |
81 | .depth = 16, | |
82 | .color = S5P_FIMC_CRYCBY422, | |
83 | .buff_cnt = 1, | |
5f3cc447 SN |
84 | .planes_cnt = 1, |
85 | .mbus_code = V4L2_MBUS_FMT_VYUY8_2X8, | |
86 | .flags = FMT_FLAGS_M2M | FMT_FLAGS_CAM, | |
5fd8f738 SN |
87 | }, { |
88 | .name = "YUV 4:2:2 packed, YCrYCb", | |
89 | .fourcc = V4L2_PIX_FMT_YVYU, | |
90 | .depth = 16, | |
91 | .color = S5P_FIMC_YCRYCB422, | |
92 | .buff_cnt = 1, | |
5f3cc447 SN |
93 | .planes_cnt = 1, |
94 | .mbus_code = V4L2_MBUS_FMT_YVYU8_2X8, | |
95 | .flags = FMT_FLAGS_M2M | FMT_FLAGS_CAM, | |
5fd8f738 SN |
96 | }, { |
97 | .name = "YUV 4:2:2 planar, Y/Cb/Cr", | |
98 | .fourcc = V4L2_PIX_FMT_YUV422P, | |
99 | .depth = 12, | |
100 | .color = S5P_FIMC_YCBCR422, | |
101 | .buff_cnt = 1, | |
5f3cc447 SN |
102 | .planes_cnt = 3, |
103 | .flags = FMT_FLAGS_M2M, | |
5fd8f738 SN |
104 | }, { |
105 | .name = "YUV 4:2:2 planar, Y/CbCr", | |
106 | .fourcc = V4L2_PIX_FMT_NV16, | |
107 | .depth = 16, | |
108 | .color = S5P_FIMC_YCBCR422, | |
109 | .buff_cnt = 1, | |
5f3cc447 SN |
110 | .planes_cnt = 2, |
111 | .flags = FMT_FLAGS_M2M, | |
5fd8f738 SN |
112 | }, { |
113 | .name = "YUV 4:2:2 planar, Y/CrCb", | |
114 | .fourcc = V4L2_PIX_FMT_NV61, | |
115 | .depth = 16, | |
116 | .color = S5P_FIMC_RGB565, | |
117 | .buff_cnt = 1, | |
5f3cc447 SN |
118 | .planes_cnt = 2, |
119 | .flags = FMT_FLAGS_M2M, | |
5fd8f738 SN |
120 | }, { |
121 | .name = "YUV 4:2:0 planar, YCbCr", | |
122 | .fourcc = V4L2_PIX_FMT_YUV420, | |
123 | .depth = 12, | |
124 | .color = S5P_FIMC_YCBCR420, | |
125 | .buff_cnt = 1, | |
5f3cc447 SN |
126 | .planes_cnt = 3, |
127 | .flags = FMT_FLAGS_M2M, | |
5fd8f738 SN |
128 | }, { |
129 | .name = "YUV 4:2:0 planar, Y/CbCr", | |
130 | .fourcc = V4L2_PIX_FMT_NV12, | |
131 | .depth = 12, | |
132 | .color = S5P_FIMC_YCBCR420, | |
133 | .buff_cnt = 1, | |
5f3cc447 SN |
134 | .planes_cnt = 2, |
135 | .flags = FMT_FLAGS_M2M, | |
136 | }, | |
548aafcd | 137 | }; |
5fd8f738 SN |
138 | |
139 | static struct v4l2_queryctrl fimc_ctrls[] = { | |
140 | { | |
141 | .id = V4L2_CID_HFLIP, | |
142 | .type = V4L2_CTRL_TYPE_BOOLEAN, | |
143 | .name = "Horizontal flip", | |
144 | .minimum = 0, | |
145 | .maximum = 1, | |
146 | .default_value = 0, | |
548aafcd | 147 | }, { |
5fd8f738 SN |
148 | .id = V4L2_CID_VFLIP, |
149 | .type = V4L2_CTRL_TYPE_BOOLEAN, | |
150 | .name = "Vertical flip", | |
151 | .minimum = 0, | |
152 | .maximum = 1, | |
153 | .default_value = 0, | |
548aafcd | 154 | }, { |
5fd8f738 SN |
155 | .id = V4L2_CID_ROTATE, |
156 | .type = V4L2_CTRL_TYPE_INTEGER, | |
157 | .name = "Rotation (CCW)", | |
158 | .minimum = 0, | |
159 | .maximum = 270, | |
160 | .step = 90, | |
161 | .default_value = 0, | |
162 | }, | |
163 | }; | |
164 | ||
165 | ||
166 | static struct v4l2_queryctrl *get_ctrl(int id) | |
167 | { | |
168 | int i; | |
169 | ||
170 | for (i = 0; i < ARRAY_SIZE(fimc_ctrls); ++i) | |
171 | if (id == fimc_ctrls[i].id) | |
172 | return &fimc_ctrls[i]; | |
173 | return NULL; | |
174 | } | |
175 | ||
5f3cc447 | 176 | int fimc_check_scaler_ratio(struct v4l2_rect *r, struct fimc_frame *f) |
5fd8f738 SN |
177 | { |
178 | if (r->width > f->width) { | |
179 | if (f->width > (r->width * SCALER_MAX_HRATIO)) | |
180 | return -EINVAL; | |
181 | } else { | |
182 | if ((f->width * SCALER_MAX_HRATIO) < r->width) | |
183 | return -EINVAL; | |
184 | } | |
185 | ||
186 | if (r->height > f->height) { | |
187 | if (f->height > (r->height * SCALER_MAX_VRATIO)) | |
188 | return -EINVAL; | |
189 | } else { | |
190 | if ((f->height * SCALER_MAX_VRATIO) < r->height) | |
191 | return -EINVAL; | |
192 | } | |
193 | ||
194 | return 0; | |
195 | } | |
196 | ||
197 | static int fimc_get_scaler_factor(u32 src, u32 tar, u32 *ratio, u32 *shift) | |
198 | { | |
548aafcd SN |
199 | u32 sh = 6; |
200 | ||
201 | if (src >= 64 * tar) | |
5fd8f738 | 202 | return -EINVAL; |
548aafcd SN |
203 | |
204 | while (sh--) { | |
205 | u32 tmp = 1 << sh; | |
206 | if (src >= tar * tmp) { | |
207 | *shift = sh, *ratio = tmp; | |
208 | return 0; | |
209 | } | |
5fd8f738 SN |
210 | } |
211 | ||
548aafcd SN |
212 | *shift = 0, *ratio = 1; |
213 | ||
214 | dbg("s: %d, t: %d, shift: %d, ratio: %d", | |
215 | src, tar, *shift, *ratio); | |
5fd8f738 SN |
216 | return 0; |
217 | } | |
218 | ||
5f3cc447 | 219 | int fimc_set_scaler_info(struct fimc_ctx *ctx) |
5fd8f738 SN |
220 | { |
221 | struct fimc_scaler *sc = &ctx->scaler; | |
222 | struct fimc_frame *s_frame = &ctx->s_frame; | |
223 | struct fimc_frame *d_frame = &ctx->d_frame; | |
224 | int tx, ty, sx, sy; | |
225 | int ret; | |
226 | ||
47654df8 SN |
227 | if (ctx->rotation == 90 || ctx->rotation == 270) { |
228 | ty = d_frame->width; | |
229 | tx = d_frame->height; | |
230 | } else { | |
231 | tx = d_frame->width; | |
232 | ty = d_frame->height; | |
233 | } | |
5fd8f738 SN |
234 | if (tx <= 0 || ty <= 0) { |
235 | v4l2_err(&ctx->fimc_dev->m2m.v4l2_dev, | |
236 | "invalid target size: %d x %d", tx, ty); | |
237 | return -EINVAL; | |
238 | } | |
239 | ||
240 | sx = s_frame->width; | |
241 | sy = s_frame->height; | |
242 | if (sx <= 0 || sy <= 0) { | |
243 | err("invalid source size: %d x %d", sx, sy); | |
244 | return -EINVAL; | |
245 | } | |
246 | ||
247 | sc->real_width = sx; | |
248 | sc->real_height = sy; | |
249 | dbg("sx= %d, sy= %d, tx= %d, ty= %d", sx, sy, tx, ty); | |
250 | ||
251 | ret = fimc_get_scaler_factor(sx, tx, &sc->pre_hratio, &sc->hfactor); | |
252 | if (ret) | |
253 | return ret; | |
254 | ||
255 | ret = fimc_get_scaler_factor(sy, ty, &sc->pre_vratio, &sc->vfactor); | |
256 | if (ret) | |
257 | return ret; | |
258 | ||
259 | sc->pre_dst_width = sx / sc->pre_hratio; | |
260 | sc->pre_dst_height = sy / sc->pre_vratio; | |
261 | ||
262 | sc->main_hratio = (sx << 8) / (tx << sc->hfactor); | |
263 | sc->main_vratio = (sy << 8) / (ty << sc->vfactor); | |
264 | ||
265 | sc->scaleup_h = (tx >= sx) ? 1 : 0; | |
266 | sc->scaleup_v = (ty >= sy) ? 1 : 0; | |
267 | ||
268 | /* check to see if input and output size/format differ */ | |
269 | if (s_frame->fmt->color == d_frame->fmt->color | |
270 | && s_frame->width == d_frame->width | |
271 | && s_frame->height == d_frame->height) | |
272 | sc->copy_mode = 1; | |
273 | else | |
274 | sc->copy_mode = 0; | |
275 | ||
276 | return 0; | |
277 | } | |
278 | ||
5f3cc447 SN |
279 | static void fimc_capture_handler(struct fimc_dev *fimc) |
280 | { | |
281 | struct fimc_vid_cap *cap = &fimc->vid_cap; | |
282 | struct fimc_vid_buffer *v_buf = NULL; | |
283 | ||
284 | if (!list_empty(&cap->active_buf_q)) { | |
285 | v_buf = active_queue_pop(cap); | |
286 | fimc_buf_finish(fimc, v_buf); | |
287 | } | |
288 | ||
289 | if (test_and_clear_bit(ST_CAPT_SHUT, &fimc->state)) { | |
290 | wake_up(&fimc->irq_queue); | |
291 | return; | |
292 | } | |
293 | ||
294 | if (!list_empty(&cap->pending_buf_q)) { | |
295 | ||
296 | v_buf = pending_queue_pop(cap); | |
297 | fimc_hw_set_output_addr(fimc, &v_buf->paddr, cap->buf_index); | |
298 | v_buf->index = cap->buf_index; | |
299 | ||
300 | dbg("hw ptr: %d, sw ptr: %d", | |
301 | fimc_hw_get_frame_index(fimc), cap->buf_index); | |
302 | ||
303 | spin_lock(&fimc->irqlock); | |
304 | v_buf->vb.state = VIDEOBUF_ACTIVE; | |
305 | spin_unlock(&fimc->irqlock); | |
306 | ||
307 | /* Move the buffer to the capture active queue */ | |
308 | active_queue_add(cap, v_buf); | |
309 | ||
310 | dbg("next frame: %d, done frame: %d", | |
311 | fimc_hw_get_frame_index(fimc), v_buf->index); | |
312 | ||
313 | if (++cap->buf_index >= FIMC_MAX_OUT_BUFS) | |
314 | cap->buf_index = 0; | |
315 | ||
316 | } else if (test_and_clear_bit(ST_CAPT_STREAM, &fimc->state) && | |
317 | cap->active_buf_cnt <= 1) { | |
318 | fimc_deactivate_capture(fimc); | |
319 | } | |
320 | ||
321 | dbg("frame: %d, active_buf_cnt= %d", | |
322 | fimc_hw_get_frame_index(fimc), cap->active_buf_cnt); | |
323 | } | |
5fd8f738 SN |
324 | |
325 | static irqreturn_t fimc_isr(int irq, void *priv) | |
326 | { | |
327 | struct fimc_vid_buffer *src_buf, *dst_buf; | |
5fd8f738 | 328 | struct fimc_ctx *ctx; |
548aafcd | 329 | struct fimc_dev *fimc = priv; |
5fd8f738 SN |
330 | |
331 | BUG_ON(!fimc); | |
332 | fimc_hw_clear_irq(fimc); | |
333 | ||
334 | spin_lock(&fimc->slock); | |
335 | ||
336 | if (test_and_clear_bit(ST_M2M_PEND, &fimc->state)) { | |
337 | ctx = v4l2_m2m_get_curr_priv(fimc->m2m.m2m_dev); | |
338 | if (!ctx || !ctx->m2m_ctx) | |
339 | goto isr_unlock; | |
340 | src_buf = v4l2_m2m_src_buf_remove(ctx->m2m_ctx); | |
341 | dst_buf = v4l2_m2m_dst_buf_remove(ctx->m2m_ctx); | |
342 | if (src_buf && dst_buf) { | |
343 | spin_lock(&fimc->irqlock); | |
548aafcd | 344 | src_buf->vb.state = dst_buf->vb.state = VIDEOBUF_DONE; |
5fd8f738 SN |
345 | wake_up(&src_buf->vb.done); |
346 | wake_up(&dst_buf->vb.done); | |
347 | spin_unlock(&fimc->irqlock); | |
348 | v4l2_m2m_job_finish(fimc->m2m.m2m_dev, ctx->m2m_ctx); | |
349 | } | |
5f3cc447 SN |
350 | goto isr_unlock; |
351 | ||
352 | } | |
353 | ||
354 | if (test_bit(ST_CAPT_RUN, &fimc->state)) | |
355 | fimc_capture_handler(fimc); | |
356 | ||
357 | if (test_and_clear_bit(ST_CAPT_PEND, &fimc->state)) { | |
358 | set_bit(ST_CAPT_RUN, &fimc->state); | |
359 | wake_up(&fimc->irq_queue); | |
5fd8f738 SN |
360 | } |
361 | ||
362 | isr_unlock: | |
363 | spin_unlock(&fimc->slock); | |
364 | return IRQ_HANDLED; | |
365 | } | |
366 | ||
367 | /* The color format (planes_cnt, buff_cnt) must be already configured. */ | |
548aafcd SN |
368 | int fimc_prepare_addr(struct fimc_ctx *ctx, struct fimc_vid_buffer *buf, |
369 | struct fimc_frame *frame, struct fimc_addr *paddr) | |
5fd8f738 | 370 | { |
5fd8f738 | 371 | int ret = 0; |
548aafcd | 372 | u32 pix_size; |
5fd8f738 | 373 | |
548aafcd | 374 | if (buf == NULL || frame == NULL) |
5fd8f738 SN |
375 | return -EINVAL; |
376 | ||
377 | pix_size = frame->width * frame->height; | |
378 | ||
379 | dbg("buff_cnt= %d, planes_cnt= %d, frame->size= %d, pix_size= %d", | |
380 | frame->fmt->buff_cnt, frame->fmt->planes_cnt, | |
381 | frame->size, pix_size); | |
382 | ||
383 | if (frame->fmt->buff_cnt == 1) { | |
384 | paddr->y = videobuf_to_dma_contig(&buf->vb); | |
385 | switch (frame->fmt->planes_cnt) { | |
386 | case 1: | |
387 | paddr->cb = 0; | |
388 | paddr->cr = 0; | |
389 | break; | |
390 | case 2: | |
391 | /* decompose Y into Y/Cb */ | |
392 | paddr->cb = (u32)(paddr->y + pix_size); | |
393 | paddr->cr = 0; | |
394 | break; | |
395 | case 3: | |
396 | paddr->cb = (u32)(paddr->y + pix_size); | |
397 | /* decompose Y into Y/Cb/Cr */ | |
398 | if (S5P_FIMC_YCBCR420 == frame->fmt->color) | |
399 | paddr->cr = (u32)(paddr->cb | |
400 | + (pix_size >> 2)); | |
401 | else /* 422 */ | |
402 | paddr->cr = (u32)(paddr->cb | |
403 | + (pix_size >> 1)); | |
404 | break; | |
405 | default: | |
406 | return -EINVAL; | |
407 | } | |
408 | } | |
409 | ||
548aafcd SN |
410 | dbg("PHYS_ADDR: y= 0x%X cb= 0x%X cr= 0x%X ret= %d", |
411 | paddr->y, paddr->cb, paddr->cr, ret); | |
5fd8f738 SN |
412 | |
413 | return ret; | |
414 | } | |
415 | ||
416 | /* Set order for 1 and 2 plane YCBCR 4:2:2 formats. */ | |
417 | static void fimc_set_yuv_order(struct fimc_ctx *ctx) | |
418 | { | |
419 | /* The one only mode supported in SoC. */ | |
420 | ctx->in_order_2p = S5P_FIMC_LSB_CRCB; | |
421 | ctx->out_order_2p = S5P_FIMC_LSB_CRCB; | |
422 | ||
423 | /* Set order for 1 plane input formats. */ | |
424 | switch (ctx->s_frame.fmt->color) { | |
425 | case S5P_FIMC_YCRYCB422: | |
426 | ctx->in_order_1p = S5P_FIMC_IN_YCRYCB; | |
427 | break; | |
428 | case S5P_FIMC_CBYCRY422: | |
429 | ctx->in_order_1p = S5P_FIMC_IN_CBYCRY; | |
430 | break; | |
431 | case S5P_FIMC_CRYCBY422: | |
432 | ctx->in_order_1p = S5P_FIMC_IN_CRYCBY; | |
433 | break; | |
434 | case S5P_FIMC_YCBYCR422: | |
435 | default: | |
436 | ctx->in_order_1p = S5P_FIMC_IN_YCBYCR; | |
437 | break; | |
438 | } | |
439 | dbg("ctx->in_order_1p= %d", ctx->in_order_1p); | |
440 | ||
441 | switch (ctx->d_frame.fmt->color) { | |
442 | case S5P_FIMC_YCRYCB422: | |
443 | ctx->out_order_1p = S5P_FIMC_OUT_YCRYCB; | |
444 | break; | |
445 | case S5P_FIMC_CBYCRY422: | |
446 | ctx->out_order_1p = S5P_FIMC_OUT_CBYCRY; | |
447 | break; | |
448 | case S5P_FIMC_CRYCBY422: | |
449 | ctx->out_order_1p = S5P_FIMC_OUT_CRYCBY; | |
450 | break; | |
451 | case S5P_FIMC_YCBYCR422: | |
452 | default: | |
453 | ctx->out_order_1p = S5P_FIMC_OUT_YCBYCR; | |
454 | break; | |
455 | } | |
456 | dbg("ctx->out_order_1p= %d", ctx->out_order_1p); | |
457 | } | |
458 | ||
ddc79e0f SN |
459 | static void fimc_prepare_dma_offset(struct fimc_ctx *ctx, struct fimc_frame *f) |
460 | { | |
461 | struct samsung_fimc_variant *variant = ctx->fimc_dev->variant; | |
462 | ||
463 | f->dma_offset.y_h = f->offs_h; | |
464 | if (!variant->pix_hoff) | |
465 | f->dma_offset.y_h *= (f->fmt->depth >> 3); | |
466 | ||
467 | f->dma_offset.y_v = f->offs_v; | |
468 | ||
469 | f->dma_offset.cb_h = f->offs_h; | |
470 | f->dma_offset.cb_v = f->offs_v; | |
471 | ||
472 | f->dma_offset.cr_h = f->offs_h; | |
473 | f->dma_offset.cr_v = f->offs_v; | |
474 | ||
475 | if (!variant->pix_hoff) { | |
476 | if (f->fmt->planes_cnt == 3) { | |
477 | f->dma_offset.cb_h >>= 1; | |
478 | f->dma_offset.cr_h >>= 1; | |
479 | } | |
480 | if (f->fmt->color == S5P_FIMC_YCBCR420) { | |
481 | f->dma_offset.cb_v >>= 1; | |
482 | f->dma_offset.cr_v >>= 1; | |
483 | } | |
484 | } | |
485 | ||
486 | dbg("in_offset: color= %d, y_h= %d, y_v= %d", | |
487 | f->fmt->color, f->dma_offset.y_h, f->dma_offset.y_v); | |
488 | } | |
489 | ||
5fd8f738 SN |
490 | /** |
491 | * fimc_prepare_config - check dimensions, operation and color mode | |
492 | * and pre-calculate offset and the scaling coefficients. | |
493 | * | |
494 | * @ctx: hardware context information | |
495 | * @flags: flags indicating which parameters to check/update | |
496 | * | |
497 | * Return: 0 if dimensions are valid or non zero otherwise. | |
498 | */ | |
5f3cc447 | 499 | int fimc_prepare_config(struct fimc_ctx *ctx, u32 flags) |
5fd8f738 SN |
500 | { |
501 | struct fimc_frame *s_frame, *d_frame; | |
502 | struct fimc_vid_buffer *buf = NULL; | |
5fd8f738 SN |
503 | int ret = 0; |
504 | ||
505 | s_frame = &ctx->s_frame; | |
506 | d_frame = &ctx->d_frame; | |
507 | ||
508 | if (flags & FIMC_PARAMS) { | |
ddc79e0f SN |
509 | /* Prepare the DMA offset ratios for scaler. */ |
510 | fimc_prepare_dma_offset(ctx, &ctx->s_frame); | |
511 | fimc_prepare_dma_offset(ctx, &ctx->d_frame); | |
5fd8f738 | 512 | |
5fd8f738 SN |
513 | if (s_frame->height > (SCALER_MAX_VRATIO * d_frame->height) || |
514 | s_frame->width > (SCALER_MAX_HRATIO * d_frame->width)) { | |
515 | err("out of scaler range"); | |
516 | return -EINVAL; | |
517 | } | |
ddc79e0f | 518 | fimc_set_yuv_order(ctx); |
5fd8f738 SN |
519 | } |
520 | ||
521 | /* Input DMA mode is not allowed when the scaler is disabled. */ | |
522 | ctx->scaler.enabled = 1; | |
523 | ||
524 | if (flags & FIMC_SRC_ADDR) { | |
525 | buf = v4l2_m2m_next_src_buf(ctx->m2m_ctx); | |
548aafcd | 526 | ret = fimc_prepare_addr(ctx, buf, s_frame, &s_frame->paddr); |
5fd8f738 SN |
527 | if (ret) |
528 | return ret; | |
529 | } | |
530 | ||
531 | if (flags & FIMC_DST_ADDR) { | |
532 | buf = v4l2_m2m_next_dst_buf(ctx->m2m_ctx); | |
548aafcd | 533 | ret = fimc_prepare_addr(ctx, buf, d_frame, &d_frame->paddr); |
5fd8f738 SN |
534 | } |
535 | ||
536 | return ret; | |
537 | } | |
538 | ||
539 | static void fimc_dma_run(void *priv) | |
540 | { | |
541 | struct fimc_ctx *ctx = priv; | |
542 | struct fimc_dev *fimc; | |
543 | unsigned long flags; | |
544 | u32 ret; | |
545 | ||
546 | if (WARN(!ctx, "null hardware context")) | |
547 | return; | |
548 | ||
549 | fimc = ctx->fimc_dev; | |
550 | ||
551 | spin_lock_irqsave(&ctx->slock, flags); | |
552 | set_bit(ST_M2M_PEND, &fimc->state); | |
553 | ||
554 | ctx->state |= (FIMC_SRC_ADDR | FIMC_DST_ADDR); | |
555 | ret = fimc_prepare_config(ctx, ctx->state); | |
556 | if (ret) { | |
548aafcd | 557 | err("Wrong parameters"); |
5fd8f738 SN |
558 | goto dma_unlock; |
559 | } | |
548aafcd SN |
560 | /* Reconfigure hardware if the context has changed. */ |
561 | if (fimc->m2m.ctx != ctx) { | |
5fd8f738 | 562 | ctx->state |= FIMC_PARAMS; |
548aafcd SN |
563 | fimc->m2m.ctx = ctx; |
564 | } | |
5fd8f738 SN |
565 | |
566 | fimc_hw_set_input_addr(fimc, &ctx->s_frame.paddr); | |
567 | ||
568 | if (ctx->state & FIMC_PARAMS) { | |
569 | fimc_hw_set_input_path(ctx); | |
570 | fimc_hw_set_in_dma(ctx); | |
571 | if (fimc_set_scaler_info(ctx)) { | |
548aafcd | 572 | err("Scaler setup error"); |
5fd8f738 SN |
573 | goto dma_unlock; |
574 | } | |
5fd8f738 SN |
575 | fimc_hw_set_scaler(ctx); |
576 | fimc_hw_set_target_format(ctx); | |
577 | fimc_hw_set_rotation(ctx); | |
578 | fimc_hw_set_effect(ctx); | |
579 | } | |
580 | ||
581 | fimc_hw_set_output_path(ctx); | |
582 | if (ctx->state & (FIMC_DST_ADDR | FIMC_PARAMS)) | |
548aafcd | 583 | fimc_hw_set_output_addr(fimc, &ctx->d_frame.paddr, -1); |
5fd8f738 SN |
584 | |
585 | if (ctx->state & FIMC_PARAMS) | |
586 | fimc_hw_set_out_dma(ctx); | |
587 | ||
548aafcd | 588 | fimc_activate_capture(ctx); |
5fd8f738 | 589 | |
5f3cc447 | 590 | ctx->state &= (FIMC_CTX_M2M | FIMC_CTX_CAP); |
548aafcd | 591 | fimc_hw_activate_input_dma(fimc, true); |
5fd8f738 SN |
592 | |
593 | dma_unlock: | |
594 | spin_unlock_irqrestore(&ctx->slock, flags); | |
595 | } | |
596 | ||
03e30ca5 PO |
597 | static void fimc_job_abort(void *priv) |
598 | { | |
599 | /* Nothing done in job_abort. */ | |
600 | } | |
5fd8f738 SN |
601 | |
602 | static void fimc_buf_release(struct videobuf_queue *vq, | |
603 | struct videobuf_buffer *vb) | |
604 | { | |
605 | videobuf_dma_contig_free(vq, vb); | |
606 | vb->state = VIDEOBUF_NEEDS_INIT; | |
607 | } | |
608 | ||
609 | static int fimc_buf_setup(struct videobuf_queue *vq, unsigned int *count, | |
610 | unsigned int *size) | |
611 | { | |
612 | struct fimc_ctx *ctx = vq->priv_data; | |
613 | struct fimc_frame *frame; | |
614 | ||
548aafcd | 615 | frame = ctx_get_frame(ctx, vq->type); |
03e30ca5 PO |
616 | if (IS_ERR(frame)) |
617 | return PTR_ERR(frame); | |
5fd8f738 SN |
618 | |
619 | *size = (frame->width * frame->height * frame->fmt->depth) >> 3; | |
620 | if (0 == *count) | |
621 | *count = 1; | |
622 | return 0; | |
623 | } | |
624 | ||
625 | static int fimc_buf_prepare(struct videobuf_queue *vq, | |
626 | struct videobuf_buffer *vb, enum v4l2_field field) | |
627 | { | |
628 | struct fimc_ctx *ctx = vq->priv_data; | |
629 | struct v4l2_device *v4l2_dev = &ctx->fimc_dev->m2m.v4l2_dev; | |
630 | struct fimc_frame *frame; | |
631 | int ret; | |
632 | ||
548aafcd | 633 | frame = ctx_get_frame(ctx, vq->type); |
03e30ca5 PO |
634 | if (IS_ERR(frame)) |
635 | return PTR_ERR(frame); | |
5fd8f738 SN |
636 | |
637 | if (vb->baddr) { | |
638 | if (vb->bsize < frame->size) { | |
639 | v4l2_err(v4l2_dev, | |
640 | "User-provided buffer too small (%d < %d)\n", | |
641 | vb->bsize, frame->size); | |
642 | WARN_ON(1); | |
643 | return -EINVAL; | |
644 | } | |
645 | } else if (vb->state != VIDEOBUF_NEEDS_INIT | |
646 | && vb->bsize < frame->size) { | |
647 | return -EINVAL; | |
648 | } | |
649 | ||
650 | vb->width = frame->width; | |
651 | vb->height = frame->height; | |
652 | vb->bytesperline = (frame->width * frame->fmt->depth) >> 3; | |
653 | vb->size = frame->size; | |
654 | vb->field = field; | |
655 | ||
656 | if (VIDEOBUF_NEEDS_INIT == vb->state) { | |
657 | ret = videobuf_iolock(vq, vb, NULL); | |
658 | if (ret) { | |
659 | v4l2_err(v4l2_dev, "Iolock failed\n"); | |
660 | fimc_buf_release(vq, vb); | |
661 | return ret; | |
662 | } | |
663 | } | |
664 | vb->state = VIDEOBUF_PREPARED; | |
665 | ||
666 | return 0; | |
667 | } | |
668 | ||
669 | static void fimc_buf_queue(struct videobuf_queue *vq, | |
670 | struct videobuf_buffer *vb) | |
671 | { | |
672 | struct fimc_ctx *ctx = vq->priv_data; | |
5f3cc447 SN |
673 | struct fimc_dev *fimc = ctx->fimc_dev; |
674 | struct fimc_vid_cap *cap = &fimc->vid_cap; | |
675 | unsigned long flags; | |
676 | ||
677 | dbg("ctx: %p, ctx->state: 0x%x", ctx, ctx->state); | |
678 | ||
679 | if ((ctx->state & FIMC_CTX_M2M) && ctx->m2m_ctx) { | |
680 | v4l2_m2m_buf_queue(ctx->m2m_ctx, vq, vb); | |
681 | } else if (ctx->state & FIMC_CTX_CAP) { | |
682 | spin_lock_irqsave(&fimc->slock, flags); | |
683 | fimc_vid_cap_buf_queue(fimc, (struct fimc_vid_buffer *)vb); | |
684 | ||
685 | dbg("fimc->cap.active_buf_cnt: %d", | |
686 | fimc->vid_cap.active_buf_cnt); | |
687 | ||
688 | if (cap->active_buf_cnt >= cap->reqbufs_count || | |
689 | cap->active_buf_cnt >= FIMC_MAX_OUT_BUFS) { | |
690 | if (!test_and_set_bit(ST_CAPT_STREAM, &fimc->state)) | |
691 | fimc_activate_capture(ctx); | |
692 | } | |
693 | spin_unlock_irqrestore(&fimc->slock, flags); | |
694 | } | |
5fd8f738 SN |
695 | } |
696 | ||
5f3cc447 | 697 | struct videobuf_queue_ops fimc_qops = { |
5fd8f738 SN |
698 | .buf_setup = fimc_buf_setup, |
699 | .buf_prepare = fimc_buf_prepare, | |
700 | .buf_queue = fimc_buf_queue, | |
701 | .buf_release = fimc_buf_release, | |
702 | }; | |
703 | ||
704 | static int fimc_m2m_querycap(struct file *file, void *priv, | |
705 | struct v4l2_capability *cap) | |
706 | { | |
707 | struct fimc_ctx *ctx = file->private_data; | |
708 | struct fimc_dev *fimc = ctx->fimc_dev; | |
709 | ||
710 | strncpy(cap->driver, fimc->pdev->name, sizeof(cap->driver) - 1); | |
711 | strncpy(cap->card, fimc->pdev->name, sizeof(cap->card) - 1); | |
712 | cap->bus_info[0] = 0; | |
713 | cap->version = KERNEL_VERSION(1, 0, 0); | |
714 | cap->capabilities = V4L2_CAP_STREAMING | | |
715 | V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_OUTPUT; | |
716 | ||
717 | return 0; | |
718 | } | |
719 | ||
5f3cc447 | 720 | int fimc_vidioc_enum_fmt(struct file *file, void *priv, |
5fd8f738 SN |
721 | struct v4l2_fmtdesc *f) |
722 | { | |
723 | struct fimc_fmt *fmt; | |
724 | ||
725 | if (f->index >= ARRAY_SIZE(fimc_formats)) | |
726 | return -EINVAL; | |
727 | ||
728 | fmt = &fimc_formats[f->index]; | |
729 | strncpy(f->description, fmt->name, sizeof(f->description) - 1); | |
730 | f->pixelformat = fmt->fourcc; | |
5f3cc447 | 731 | |
5fd8f738 SN |
732 | return 0; |
733 | } | |
734 | ||
5f3cc447 | 735 | int fimc_vidioc_g_fmt(struct file *file, void *priv, struct v4l2_format *f) |
5fd8f738 SN |
736 | { |
737 | struct fimc_ctx *ctx = priv; | |
5f3cc447 | 738 | struct fimc_dev *fimc = ctx->fimc_dev; |
5fd8f738 SN |
739 | struct fimc_frame *frame; |
740 | ||
548aafcd | 741 | frame = ctx_get_frame(ctx, f->type); |
03e30ca5 PO |
742 | if (IS_ERR(frame)) |
743 | return PTR_ERR(frame); | |
5fd8f738 | 744 | |
5f3cc447 SN |
745 | if (mutex_lock_interruptible(&fimc->lock)) |
746 | return -ERESTARTSYS; | |
747 | ||
5fd8f738 SN |
748 | f->fmt.pix.width = frame->width; |
749 | f->fmt.pix.height = frame->height; | |
750 | f->fmt.pix.field = V4L2_FIELD_NONE; | |
751 | f->fmt.pix.pixelformat = frame->fmt->fourcc; | |
752 | ||
5f3cc447 | 753 | mutex_unlock(&fimc->lock); |
5fd8f738 SN |
754 | return 0; |
755 | } | |
756 | ||
5f3cc447 | 757 | struct fimc_fmt *find_format(struct v4l2_format *f, unsigned int mask) |
5fd8f738 SN |
758 | { |
759 | struct fimc_fmt *fmt; | |
760 | unsigned int i; | |
761 | ||
762 | for (i = 0; i < ARRAY_SIZE(fimc_formats); ++i) { | |
763 | fmt = &fimc_formats[i]; | |
5f3cc447 SN |
764 | if (fmt->fourcc == f->fmt.pix.pixelformat && |
765 | (fmt->flags & mask)) | |
5fd8f738 SN |
766 | break; |
767 | } | |
5fd8f738 | 768 | |
5f3cc447 | 769 | return (i == ARRAY_SIZE(fimc_formats)) ? NULL : fmt; |
5fd8f738 SN |
770 | } |
771 | ||
5f3cc447 SN |
772 | struct fimc_fmt *find_mbus_format(struct v4l2_mbus_framefmt *f, |
773 | unsigned int mask) | |
5fd8f738 SN |
774 | { |
775 | struct fimc_fmt *fmt; | |
5f3cc447 SN |
776 | unsigned int i; |
777 | ||
778 | for (i = 0; i < ARRAY_SIZE(fimc_formats); ++i) { | |
779 | fmt = &fimc_formats[i]; | |
780 | if (fmt->mbus_code == f->code && (fmt->flags & mask)) | |
781 | break; | |
782 | } | |
783 | ||
784 | return (i == ARRAY_SIZE(fimc_formats)) ? NULL : fmt; | |
785 | } | |
786 | ||
787 | ||
788 | int fimc_vidioc_try_fmt(struct file *file, void *priv, struct v4l2_format *f) | |
789 | { | |
5fd8f738 SN |
790 | struct fimc_ctx *ctx = priv; |
791 | struct fimc_dev *fimc = ctx->fimc_dev; | |
5fd8f738 | 792 | struct samsung_fimc_variant *variant = fimc->variant; |
5f3cc447 SN |
793 | struct v4l2_pix_format *pix = &f->fmt.pix; |
794 | struct fimc_fmt *fmt; | |
795 | u32 max_width, mod_x, mod_y, mask; | |
796 | int ret = -EINVAL, is_output = 0; | |
5fd8f738 | 797 | |
5f3cc447 SN |
798 | if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) { |
799 | if (ctx->state & FIMC_CTX_CAP) | |
800 | return -EINVAL; | |
801 | is_output = 1; | |
802 | } else if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { | |
5fd8f738 SN |
803 | return -EINVAL; |
804 | } | |
805 | ||
5f3cc447 SN |
806 | dbg("w: %d, h: %d, bpl: %d", |
807 | pix->width, pix->height, pix->bytesperline); | |
808 | ||
809 | if (mutex_lock_interruptible(&fimc->lock)) | |
810 | return -ERESTARTSYS; | |
811 | ||
812 | mask = is_output ? FMT_FLAGS_M2M : FMT_FLAGS_M2M | FMT_FLAGS_CAM; | |
813 | fmt = find_format(f, mask); | |
814 | if (!fmt) { | |
815 | v4l2_err(&fimc->m2m.v4l2_dev, "Fourcc format (0x%X) invalid.\n", | |
816 | pix->pixelformat); | |
817 | goto tf_out; | |
818 | } | |
819 | ||
5fd8f738 SN |
820 | if (pix->field == V4L2_FIELD_ANY) |
821 | pix->field = V4L2_FIELD_NONE; | |
822 | else if (V4L2_FIELD_NONE != pix->field) | |
5f3cc447 | 823 | goto tf_out; |
5fd8f738 | 824 | |
5f3cc447 | 825 | if (is_output) { |
5fd8f738 | 826 | max_width = variant->scaler_dis_w; |
5f3cc447 | 827 | mod_x = ffs(variant->min_inp_pixsize) - 1; |
5fd8f738 | 828 | } else { |
5f3cc447 SN |
829 | max_width = variant->out_rot_dis_w; |
830 | mod_x = ffs(variant->min_out_pixsize) - 1; | |
5fd8f738 SN |
831 | } |
832 | ||
5fd8f738 | 833 | if (tiled_fmt(fmt)) { |
5f3cc447 SN |
834 | mod_x = 6; /* 64 x 32 pixels tile */ |
835 | mod_y = 5; | |
836 | } else { | |
837 | if (fimc->id == 1 && fimc->variant->pix_hoff) | |
838 | mod_y = fimc_fmt_is_rgb(fmt->color) ? 0 : 1; | |
839 | else | |
840 | mod_y = mod_x; | |
5fd8f738 SN |
841 | } |
842 | ||
5f3cc447 | 843 | dbg("mod_x: %d, mod_y: %d, max_w: %d", mod_x, mod_y, max_width); |
5fd8f738 | 844 | |
5f3cc447 SN |
845 | v4l_bound_align_image(&pix->width, 16, max_width, mod_x, |
846 | &pix->height, 8, variant->scaler_dis_w, mod_y, 0); | |
5fd8f738 SN |
847 | |
848 | if (pix->bytesperline == 0 || | |
5f3cc447 | 849 | (pix->bytesperline * 8 / fmt->depth) > pix->width) |
5fd8f738 SN |
850 | pix->bytesperline = (pix->width * fmt->depth) >> 3; |
851 | ||
852 | if (pix->sizeimage == 0) | |
853 | pix->sizeimage = pix->height * pix->bytesperline; | |
854 | ||
5f3cc447 SN |
855 | dbg("w: %d, h: %d, bpl: %d, depth: %d", |
856 | pix->width, pix->height, pix->bytesperline, fmt->depth); | |
5fd8f738 | 857 | |
5f3cc447 | 858 | ret = 0; |
5fd8f738 | 859 | |
5f3cc447 SN |
860 | tf_out: |
861 | mutex_unlock(&fimc->lock); | |
862 | return ret; | |
863 | } | |
5fd8f738 SN |
864 | |
865 | static int fimc_m2m_s_fmt(struct file *file, void *priv, struct v4l2_format *f) | |
866 | { | |
867 | struct fimc_ctx *ctx = priv; | |
28f06ff4 SN |
868 | struct fimc_dev *fimc = ctx->fimc_dev; |
869 | struct v4l2_device *v4l2_dev = &fimc->m2m.v4l2_dev; | |
870 | struct videobuf_queue *vq; | |
5fd8f738 SN |
871 | struct fimc_frame *frame; |
872 | struct v4l2_pix_format *pix; | |
873 | unsigned long flags; | |
874 | int ret = 0; | |
875 | ||
5f3cc447 | 876 | ret = fimc_vidioc_try_fmt(file, priv, f); |
5fd8f738 SN |
877 | if (ret) |
878 | return ret; | |
879 | ||
28f06ff4 SN |
880 | if (mutex_lock_interruptible(&fimc->lock)) |
881 | return -ERESTARTSYS; | |
5fd8f738 | 882 | |
28f06ff4 SN |
883 | vq = v4l2_m2m_get_vq(ctx->m2m_ctx, f->type); |
884 | mutex_lock(&vq->vb_lock); | |
5fd8f738 | 885 | |
28f06ff4 SN |
886 | if (videobuf_queue_is_busy(vq)) { |
887 | v4l2_err(v4l2_dev, "%s: queue (%d) busy\n", __func__, f->type); | |
888 | ret = -EBUSY; | |
889 | goto sf_out; | |
890 | } | |
5fd8f738 | 891 | |
28f06ff4 | 892 | spin_lock_irqsave(&ctx->slock, flags); |
5fd8f738 | 893 | if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) { |
5fd8f738 | 894 | frame = &ctx->s_frame; |
5fd8f738 | 895 | ctx->state |= FIMC_SRC_FMT; |
5fd8f738 | 896 | } else if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { |
5fd8f738 | 897 | frame = &ctx->d_frame; |
5fd8f738 | 898 | ctx->state |= FIMC_DST_FMT; |
5fd8f738 | 899 | } else { |
28f06ff4 | 900 | spin_unlock_irqrestore(&ctx->slock, flags); |
5fd8f738 SN |
901 | v4l2_err(&ctx->fimc_dev->m2m.v4l2_dev, |
902 | "Wrong buffer/video queue type (%d)\n", f->type); | |
590a58d1 | 903 | ret = -EINVAL; |
28f06ff4 | 904 | goto sf_out; |
5fd8f738 | 905 | } |
28f06ff4 | 906 | spin_unlock_irqrestore(&ctx->slock, flags); |
5fd8f738 SN |
907 | |
908 | pix = &f->fmt.pix; | |
5f3cc447 | 909 | frame->fmt = find_format(f, FMT_FLAGS_M2M); |
5fd8f738 SN |
910 | if (!frame->fmt) { |
911 | ret = -EINVAL; | |
28f06ff4 | 912 | goto sf_out; |
5fd8f738 SN |
913 | } |
914 | ||
28f06ff4 SN |
915 | frame->f_width = pix->bytesperline * 8 / frame->fmt->depth; |
916 | frame->f_height = pix->height; | |
917 | frame->width = pix->width; | |
918 | frame->height = pix->height; | |
919 | frame->o_width = pix->width; | |
5fd8f738 | 920 | frame->o_height = pix->height; |
28f06ff4 SN |
921 | frame->offs_h = 0; |
922 | frame->offs_v = 0; | |
923 | frame->size = (pix->width * pix->height * frame->fmt->depth) >> 3; | |
924 | vq->field = pix->field; | |
925 | ||
5fd8f738 SN |
926 | spin_lock_irqsave(&ctx->slock, flags); |
927 | ctx->state |= FIMC_PARAMS; | |
928 | spin_unlock_irqrestore(&ctx->slock, flags); | |
929 | ||
28f06ff4 | 930 | dbg("f_w: %d, f_h: %d", frame->f_width, frame->f_height); |
5fd8f738 | 931 | |
28f06ff4 SN |
932 | sf_out: |
933 | mutex_unlock(&vq->vb_lock); | |
934 | mutex_unlock(&fimc->lock); | |
5fd8f738 SN |
935 | return ret; |
936 | } | |
937 | ||
938 | static int fimc_m2m_reqbufs(struct file *file, void *priv, | |
939 | struct v4l2_requestbuffers *reqbufs) | |
940 | { | |
941 | struct fimc_ctx *ctx = priv; | |
942 | return v4l2_m2m_reqbufs(file, ctx->m2m_ctx, reqbufs); | |
943 | } | |
944 | ||
945 | static int fimc_m2m_querybuf(struct file *file, void *priv, | |
946 | struct v4l2_buffer *buf) | |
947 | { | |
948 | struct fimc_ctx *ctx = priv; | |
949 | return v4l2_m2m_querybuf(file, ctx->m2m_ctx, buf); | |
950 | } | |
951 | ||
952 | static int fimc_m2m_qbuf(struct file *file, void *priv, | |
953 | struct v4l2_buffer *buf) | |
954 | { | |
955 | struct fimc_ctx *ctx = priv; | |
956 | ||
957 | return v4l2_m2m_qbuf(file, ctx->m2m_ctx, buf); | |
958 | } | |
959 | ||
960 | static int fimc_m2m_dqbuf(struct file *file, void *priv, | |
961 | struct v4l2_buffer *buf) | |
962 | { | |
963 | struct fimc_ctx *ctx = priv; | |
964 | return v4l2_m2m_dqbuf(file, ctx->m2m_ctx, buf); | |
965 | } | |
966 | ||
967 | static int fimc_m2m_streamon(struct file *file, void *priv, | |
968 | enum v4l2_buf_type type) | |
969 | { | |
970 | struct fimc_ctx *ctx = priv; | |
971 | return v4l2_m2m_streamon(file, ctx->m2m_ctx, type); | |
972 | } | |
973 | ||
974 | static int fimc_m2m_streamoff(struct file *file, void *priv, | |
975 | enum v4l2_buf_type type) | |
976 | { | |
977 | struct fimc_ctx *ctx = priv; | |
978 | return v4l2_m2m_streamoff(file, ctx->m2m_ctx, type); | |
979 | } | |
980 | ||
5f3cc447 | 981 | int fimc_vidioc_queryctrl(struct file *file, void *priv, |
5fd8f738 SN |
982 | struct v4l2_queryctrl *qc) |
983 | { | |
5f3cc447 | 984 | struct fimc_ctx *ctx = priv; |
5fd8f738 | 985 | struct v4l2_queryctrl *c; |
5f3cc447 | 986 | |
5fd8f738 | 987 | c = get_ctrl(qc->id); |
5f3cc447 SN |
988 | if (c) { |
989 | *qc = *c; | |
990 | return 0; | |
991 | } | |
992 | ||
993 | if (ctx->state & FIMC_CTX_CAP) | |
994 | return v4l2_subdev_call(ctx->fimc_dev->vid_cap.sd, | |
995 | core, queryctrl, qc); | |
996 | return -EINVAL; | |
5fd8f738 SN |
997 | } |
998 | ||
5f3cc447 | 999 | int fimc_vidioc_g_ctrl(struct file *file, void *priv, |
5fd8f738 SN |
1000 | struct v4l2_control *ctrl) |
1001 | { | |
1002 | struct fimc_ctx *ctx = priv; | |
5f3cc447 SN |
1003 | struct fimc_dev *fimc = ctx->fimc_dev; |
1004 | int ret = 0; | |
1005 | ||
1006 | if (mutex_lock_interruptible(&fimc->lock)) | |
1007 | return -ERESTARTSYS; | |
5fd8f738 SN |
1008 | |
1009 | switch (ctrl->id) { | |
1010 | case V4L2_CID_HFLIP: | |
1011 | ctrl->value = (FLIP_X_AXIS & ctx->flip) ? 1 : 0; | |
1012 | break; | |
1013 | case V4L2_CID_VFLIP: | |
1014 | ctrl->value = (FLIP_Y_AXIS & ctx->flip) ? 1 : 0; | |
1015 | break; | |
1016 | case V4L2_CID_ROTATE: | |
1017 | ctrl->value = ctx->rotation; | |
1018 | break; | |
1019 | default: | |
5f3cc447 SN |
1020 | if (ctx->state & FIMC_CTX_CAP) { |
1021 | ret = v4l2_subdev_call(fimc->vid_cap.sd, core, | |
1022 | g_ctrl, ctrl); | |
1023 | } else { | |
1024 | v4l2_err(&fimc->m2m.v4l2_dev, | |
1025 | "Invalid control\n"); | |
1026 | ret = -EINVAL; | |
1027 | } | |
5fd8f738 SN |
1028 | } |
1029 | dbg("ctrl->value= %d", ctrl->value); | |
5f3cc447 SN |
1030 | |
1031 | mutex_unlock(&fimc->lock); | |
1032 | return ret; | |
5fd8f738 SN |
1033 | } |
1034 | ||
5f3cc447 | 1035 | int check_ctrl_val(struct fimc_ctx *ctx, struct v4l2_control *ctrl) |
5fd8f738 SN |
1036 | { |
1037 | struct v4l2_queryctrl *c; | |
1038 | c = get_ctrl(ctrl->id); | |
1039 | if (!c) | |
1040 | return -EINVAL; | |
1041 | ||
1042 | if (ctrl->value < c->minimum || ctrl->value > c->maximum | |
1043 | || (c->step != 0 && ctrl->value % c->step != 0)) { | |
1044 | v4l2_err(&ctx->fimc_dev->m2m.v4l2_dev, | |
1045 | "Invalid control value\n"); | |
1046 | return -ERANGE; | |
1047 | } | |
1048 | ||
1049 | return 0; | |
1050 | } | |
1051 | ||
5f3cc447 | 1052 | int fimc_s_ctrl(struct fimc_ctx *ctx, struct v4l2_control *ctrl) |
5fd8f738 | 1053 | { |
5fd8f738 | 1054 | struct samsung_fimc_variant *variant = ctx->fimc_dev->variant; |
5f3cc447 | 1055 | struct fimc_dev *fimc = ctx->fimc_dev; |
5fd8f738 | 1056 | unsigned long flags; |
5fd8f738 | 1057 | |
5f3cc447 SN |
1058 | if (ctx->rotation != 0 && |
1059 | (ctrl->id == V4L2_CID_HFLIP || ctrl->id == V4L2_CID_VFLIP)) { | |
1060 | v4l2_err(&fimc->m2m.v4l2_dev, | |
1061 | "Simultaneous flip and rotation is not supported\n"); | |
1062 | return -EINVAL; | |
1063 | } | |
1064 | ||
1065 | spin_lock_irqsave(&ctx->slock, flags); | |
5fd8f738 SN |
1066 | |
1067 | switch (ctrl->id) { | |
1068 | case V4L2_CID_HFLIP: | |
5fd8f738 SN |
1069 | if (ctrl->value) |
1070 | ctx->flip |= FLIP_X_AXIS; | |
1071 | else | |
1072 | ctx->flip &= ~FLIP_X_AXIS; | |
1073 | break; | |
1074 | ||
1075 | case V4L2_CID_VFLIP: | |
5fd8f738 SN |
1076 | if (ctrl->value) |
1077 | ctx->flip |= FLIP_Y_AXIS; | |
1078 | else | |
1079 | ctx->flip &= ~FLIP_Y_AXIS; | |
1080 | break; | |
1081 | ||
1082 | case V4L2_CID_ROTATE: | |
5f3cc447 SN |
1083 | /* Check for the output rotator availability */ |
1084 | if ((ctrl->value == 90 || ctrl->value == 270) && | |
1085 | (ctx->in_path == FIMC_DMA && !variant->has_out_rot)) { | |
1086 | spin_unlock_irqrestore(&ctx->slock, flags); | |
1087 | return -EINVAL; | |
1088 | } else { | |
1089 | ctx->rotation = ctrl->value; | |
5fd8f738 | 1090 | } |
5fd8f738 SN |
1091 | break; |
1092 | ||
1093 | default: | |
5f3cc447 SN |
1094 | spin_unlock_irqrestore(&ctx->slock, flags); |
1095 | v4l2_err(&fimc->m2m.v4l2_dev, "Invalid control\n"); | |
5fd8f738 SN |
1096 | return -EINVAL; |
1097 | } | |
5fd8f738 SN |
1098 | ctx->state |= FIMC_PARAMS; |
1099 | spin_unlock_irqrestore(&ctx->slock, flags); | |
5f3cc447 | 1100 | |
5fd8f738 SN |
1101 | return 0; |
1102 | } | |
1103 | ||
5f3cc447 SN |
1104 | static int fimc_m2m_s_ctrl(struct file *file, void *priv, |
1105 | struct v4l2_control *ctrl) | |
1106 | { | |
1107 | struct fimc_ctx *ctx = priv; | |
1108 | int ret = 0; | |
1109 | ||
1110 | ret = check_ctrl_val(ctx, ctrl); | |
1111 | if (ret) | |
1112 | return ret; | |
1113 | ||
1114 | ret = fimc_s_ctrl(ctx, ctrl); | |
1115 | return 0; | |
1116 | } | |
5fd8f738 | 1117 | |
5f3cc447 SN |
1118 | int fimc_vidioc_cropcap(struct file *file, void *fh, |
1119 | struct v4l2_cropcap *cr) | |
5fd8f738 SN |
1120 | { |
1121 | struct fimc_frame *frame; | |
1122 | struct fimc_ctx *ctx = fh; | |
5f3cc447 | 1123 | struct fimc_dev *fimc = ctx->fimc_dev; |
5fd8f738 | 1124 | |
548aafcd | 1125 | frame = ctx_get_frame(ctx, cr->type); |
03e30ca5 PO |
1126 | if (IS_ERR(frame)) |
1127 | return PTR_ERR(frame); | |
5fd8f738 | 1128 | |
5f3cc447 SN |
1129 | if (mutex_lock_interruptible(&fimc->lock)) |
1130 | return -ERESTARTSYS; | |
1131 | ||
1132 | cr->bounds.left = 0; | |
1133 | cr->bounds.top = 0; | |
1134 | cr->bounds.width = frame->f_width; | |
1135 | cr->bounds.height = frame->f_height; | |
1136 | cr->defrect = cr->bounds; | |
1137 | ||
1138 | mutex_unlock(&fimc->lock); | |
5fd8f738 SN |
1139 | return 0; |
1140 | } | |
1141 | ||
5f3cc447 | 1142 | int fimc_vidioc_g_crop(struct file *file, void *fh, struct v4l2_crop *cr) |
5fd8f738 SN |
1143 | { |
1144 | struct fimc_frame *frame; | |
1145 | struct fimc_ctx *ctx = file->private_data; | |
5f3cc447 | 1146 | struct fimc_dev *fimc = ctx->fimc_dev; |
5fd8f738 | 1147 | |
548aafcd | 1148 | frame = ctx_get_frame(ctx, cr->type); |
03e30ca5 PO |
1149 | if (IS_ERR(frame)) |
1150 | return PTR_ERR(frame); | |
5fd8f738 | 1151 | |
5f3cc447 SN |
1152 | if (mutex_lock_interruptible(&fimc->lock)) |
1153 | return -ERESTARTSYS; | |
1154 | ||
5fd8f738 SN |
1155 | cr->c.left = frame->offs_h; |
1156 | cr->c.top = frame->offs_v; | |
1157 | cr->c.width = frame->width; | |
1158 | cr->c.height = frame->height; | |
1159 | ||
5f3cc447 | 1160 | mutex_unlock(&fimc->lock); |
5fd8f738 SN |
1161 | return 0; |
1162 | } | |
1163 | ||
5f3cc447 | 1164 | int fimc_try_crop(struct fimc_ctx *ctx, struct v4l2_crop *cr) |
5fd8f738 | 1165 | { |
5fd8f738 | 1166 | struct fimc_dev *fimc = ctx->fimc_dev; |
5fd8f738 | 1167 | struct fimc_frame *f; |
5f3cc447 SN |
1168 | u32 min_size, halign; |
1169 | ||
1170 | f = (cr->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) ? | |
1171 | &ctx->s_frame : &ctx->d_frame; | |
5fd8f738 SN |
1172 | |
1173 | if (cr->c.top < 0 || cr->c.left < 0) { | |
1174 | v4l2_err(&fimc->m2m.v4l2_dev, | |
1175 | "doesn't support negative values for top & left\n"); | |
1176 | return -EINVAL; | |
1177 | } | |
1178 | ||
548aafcd | 1179 | f = ctx_get_frame(ctx, cr->type); |
03e30ca5 PO |
1180 | if (IS_ERR(f)) |
1181 | return PTR_ERR(f); | |
5fd8f738 | 1182 | |
5f3cc447 SN |
1183 | min_size = (cr->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) |
1184 | ? fimc->variant->min_inp_pixsize | |
1185 | : fimc->variant->min_out_pixsize; | |
5fd8f738 | 1186 | |
5f3cc447 SN |
1187 | if (ctx->state & FIMC_CTX_M2M) { |
1188 | if (fimc->id == 1 && fimc->variant->pix_hoff) | |
1189 | halign = fimc_fmt_is_rgb(f->fmt->color) ? 0 : 1; | |
1190 | else | |
1191 | halign = ffs(min_size) - 1; | |
1192 | /* there are more strict aligment requirements at camera interface */ | |
1193 | } else { | |
1194 | min_size = 16; | |
1195 | halign = 4; | |
5fd8f738 SN |
1196 | } |
1197 | ||
5f3cc447 SN |
1198 | v4l_bound_align_image(&cr->c.width, min_size, f->o_width, |
1199 | ffs(min_size) - 1, | |
1200 | &cr->c.height, min_size, f->o_height, | |
1201 | halign, 64/(ALIGN(f->fmt->depth, 8))); | |
1202 | ||
1203 | /* adjust left/top if cropping rectangle is out of bounds */ | |
1204 | if (cr->c.left + cr->c.width > f->o_width) | |
1205 | cr->c.left = f->o_width - cr->c.width; | |
1206 | if (cr->c.top + cr->c.height > f->o_height) | |
1207 | cr->c.top = f->o_height - cr->c.height; | |
1208 | ||
1209 | cr->c.left = round_down(cr->c.left, min_size); | |
1210 | cr->c.top = round_down(cr->c.top, | |
1211 | ctx->state & FIMC_CTX_M2M ? 8 : 16); | |
1212 | ||
1213 | dbg("l:%d, t:%d, w:%d, h:%d, f_w: %d, f_h: %d", | |
1214 | cr->c.left, cr->c.top, cr->c.width, cr->c.height, | |
1215 | f->f_width, f->f_height); | |
1216 | ||
1217 | return 0; | |
1218 | } | |
1219 | ||
1220 | ||
1221 | static int fimc_m2m_s_crop(struct file *file, void *fh, struct v4l2_crop *cr) | |
1222 | { | |
1223 | struct fimc_ctx *ctx = file->private_data; | |
1224 | struct fimc_dev *fimc = ctx->fimc_dev; | |
1225 | unsigned long flags; | |
1226 | struct fimc_frame *f; | |
1227 | int ret; | |
1228 | ||
1229 | ret = fimc_try_crop(ctx, cr); | |
1230 | if (ret) | |
1231 | return ret; | |
1232 | ||
1233 | f = (cr->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) ? | |
1234 | &ctx->s_frame : &ctx->d_frame; | |
1235 | ||
5fd8f738 | 1236 | spin_lock_irqsave(&ctx->slock, flags); |
5f3cc447 SN |
1237 | if (~ctx->state & (FIMC_SRC_FMT | FIMC_DST_FMT)) { |
1238 | /* Check to see if scaling ratio is within supported range */ | |
5fd8f738 SN |
1239 | if (cr->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) |
1240 | ret = fimc_check_scaler_ratio(&cr->c, &ctx->d_frame); | |
5f3cc447 | 1241 | else |
5fd8f738 | 1242 | ret = fimc_check_scaler_ratio(&cr->c, &ctx->s_frame); |
5fd8f738 SN |
1243 | if (ret) { |
1244 | spin_unlock_irqrestore(&ctx->slock, flags); | |
5f3cc447 | 1245 | v4l2_err(&fimc->m2m.v4l2_dev, "Out of scaler range"); |
5fd8f738 SN |
1246 | return -EINVAL; |
1247 | } | |
1248 | } | |
1249 | ctx->state |= FIMC_PARAMS; | |
5fd8f738 SN |
1250 | |
1251 | f->offs_h = cr->c.left; | |
1252 | f->offs_v = cr->c.top; | |
5f3cc447 | 1253 | f->width = cr->c.width; |
5fd8f738 | 1254 | f->height = cr->c.height; |
5f3cc447 SN |
1255 | |
1256 | spin_unlock_irqrestore(&ctx->slock, flags); | |
5fd8f738 SN |
1257 | return 0; |
1258 | } | |
1259 | ||
1260 | static const struct v4l2_ioctl_ops fimc_m2m_ioctl_ops = { | |
1261 | .vidioc_querycap = fimc_m2m_querycap, | |
1262 | ||
5f3cc447 SN |
1263 | .vidioc_enum_fmt_vid_cap = fimc_vidioc_enum_fmt, |
1264 | .vidioc_enum_fmt_vid_out = fimc_vidioc_enum_fmt, | |
5fd8f738 | 1265 | |
5f3cc447 SN |
1266 | .vidioc_g_fmt_vid_cap = fimc_vidioc_g_fmt, |
1267 | .vidioc_g_fmt_vid_out = fimc_vidioc_g_fmt, | |
5fd8f738 | 1268 | |
5f3cc447 SN |
1269 | .vidioc_try_fmt_vid_cap = fimc_vidioc_try_fmt, |
1270 | .vidioc_try_fmt_vid_out = fimc_vidioc_try_fmt, | |
5fd8f738 SN |
1271 | |
1272 | .vidioc_s_fmt_vid_cap = fimc_m2m_s_fmt, | |
1273 | .vidioc_s_fmt_vid_out = fimc_m2m_s_fmt, | |
1274 | ||
1275 | .vidioc_reqbufs = fimc_m2m_reqbufs, | |
1276 | .vidioc_querybuf = fimc_m2m_querybuf, | |
1277 | ||
1278 | .vidioc_qbuf = fimc_m2m_qbuf, | |
1279 | .vidioc_dqbuf = fimc_m2m_dqbuf, | |
1280 | ||
1281 | .vidioc_streamon = fimc_m2m_streamon, | |
1282 | .vidioc_streamoff = fimc_m2m_streamoff, | |
1283 | ||
5f3cc447 SN |
1284 | .vidioc_queryctrl = fimc_vidioc_queryctrl, |
1285 | .vidioc_g_ctrl = fimc_vidioc_g_ctrl, | |
5fd8f738 SN |
1286 | .vidioc_s_ctrl = fimc_m2m_s_ctrl, |
1287 | ||
5f3cc447 | 1288 | .vidioc_g_crop = fimc_vidioc_g_crop, |
5fd8f738 | 1289 | .vidioc_s_crop = fimc_m2m_s_crop, |
5f3cc447 | 1290 | .vidioc_cropcap = fimc_vidioc_cropcap |
5fd8f738 SN |
1291 | |
1292 | }; | |
1293 | ||
1294 | static void queue_init(void *priv, struct videobuf_queue *vq, | |
1295 | enum v4l2_buf_type type) | |
1296 | { | |
1297 | struct fimc_ctx *ctx = priv; | |
1298 | struct fimc_dev *fimc = ctx->fimc_dev; | |
1299 | ||
1300 | videobuf_queue_dma_contig_init(vq, &fimc_qops, | |
548aafcd | 1301 | &fimc->pdev->dev, |
5fd8f738 | 1302 | &fimc->irqlock, type, V4L2_FIELD_NONE, |
e3cfd447 | 1303 | sizeof(struct fimc_vid_buffer), priv, NULL); |
5fd8f738 SN |
1304 | } |
1305 | ||
1306 | static int fimc_m2m_open(struct file *file) | |
1307 | { | |
1308 | struct fimc_dev *fimc = video_drvdata(file); | |
1309 | struct fimc_ctx *ctx = NULL; | |
1310 | int err = 0; | |
1311 | ||
5f3cc447 SN |
1312 | if (mutex_lock_interruptible(&fimc->lock)) |
1313 | return -ERESTARTSYS; | |
1314 | ||
1315 | dbg("pid: %d, state: 0x%lx, refcnt: %d", | |
1316 | task_pid_nr(current), fimc->state, fimc->vid_cap.refcnt); | |
1317 | ||
1318 | /* | |
1319 | * Return if the corresponding video capture node | |
1320 | * is already opened. | |
1321 | */ | |
1322 | if (fimc->vid_cap.refcnt > 0) { | |
1323 | mutex_unlock(&fimc->lock); | |
1324 | return -EBUSY; | |
1325 | } | |
1326 | ||
5fd8f738 SN |
1327 | fimc->m2m.refcnt++; |
1328 | set_bit(ST_OUTDMA_RUN, &fimc->state); | |
5fd8f738 SN |
1329 | |
1330 | ctx = kzalloc(sizeof *ctx, GFP_KERNEL); | |
1331 | if (!ctx) | |
1332 | return -ENOMEM; | |
1333 | ||
1334 | file->private_data = ctx; | |
1335 | ctx->fimc_dev = fimc; | |
548aafcd | 1336 | /* Default color format */ |
5fd8f738 SN |
1337 | ctx->s_frame.fmt = &fimc_formats[0]; |
1338 | ctx->d_frame.fmt = &fimc_formats[0]; | |
5f3cc447 SN |
1339 | /* Setup the device context for mem2mem mode. */ |
1340 | ctx->state = FIMC_CTX_M2M; | |
5fd8f738 | 1341 | ctx->flags = 0; |
5fd8f738 SN |
1342 | ctx->in_path = FIMC_DMA; |
1343 | ctx->out_path = FIMC_DMA; | |
1344 | spin_lock_init(&ctx->slock); | |
1345 | ||
1346 | ctx->m2m_ctx = v4l2_m2m_ctx_init(ctx, fimc->m2m.m2m_dev, queue_init); | |
1347 | if (IS_ERR(ctx->m2m_ctx)) { | |
1348 | err = PTR_ERR(ctx->m2m_ctx); | |
1349 | kfree(ctx); | |
1350 | } | |
5f3cc447 SN |
1351 | |
1352 | mutex_unlock(&fimc->lock); | |
5fd8f738 SN |
1353 | return err; |
1354 | } | |
1355 | ||
1356 | static int fimc_m2m_release(struct file *file) | |
1357 | { | |
1358 | struct fimc_ctx *ctx = file->private_data; | |
1359 | struct fimc_dev *fimc = ctx->fimc_dev; | |
1360 | ||
5f3cc447 SN |
1361 | mutex_lock(&fimc->lock); |
1362 | ||
1363 | dbg("pid: %d, state: 0x%lx, refcnt= %d", | |
1364 | task_pid_nr(current), fimc->state, fimc->m2m.refcnt); | |
1365 | ||
5fd8f738 SN |
1366 | v4l2_m2m_ctx_release(ctx->m2m_ctx); |
1367 | kfree(ctx); | |
5fd8f738 SN |
1368 | if (--fimc->m2m.refcnt <= 0) |
1369 | clear_bit(ST_OUTDMA_RUN, &fimc->state); | |
5f3cc447 | 1370 | |
5fd8f738 SN |
1371 | mutex_unlock(&fimc->lock); |
1372 | return 0; | |
1373 | } | |
1374 | ||
1375 | static unsigned int fimc_m2m_poll(struct file *file, | |
1376 | struct poll_table_struct *wait) | |
1377 | { | |
1378 | struct fimc_ctx *ctx = file->private_data; | |
5f3cc447 | 1379 | |
5fd8f738 SN |
1380 | return v4l2_m2m_poll(file, ctx->m2m_ctx, wait); |
1381 | } | |
1382 | ||
1383 | ||
1384 | static int fimc_m2m_mmap(struct file *file, struct vm_area_struct *vma) | |
1385 | { | |
1386 | struct fimc_ctx *ctx = file->private_data; | |
5f3cc447 | 1387 | |
5fd8f738 SN |
1388 | return v4l2_m2m_mmap(file, ctx->m2m_ctx, vma); |
1389 | } | |
1390 | ||
1391 | static const struct v4l2_file_operations fimc_m2m_fops = { | |
1392 | .owner = THIS_MODULE, | |
1393 | .open = fimc_m2m_open, | |
1394 | .release = fimc_m2m_release, | |
1395 | .poll = fimc_m2m_poll, | |
1396 | .ioctl = video_ioctl2, | |
1397 | .mmap = fimc_m2m_mmap, | |
1398 | }; | |
1399 | ||
1400 | static struct v4l2_m2m_ops m2m_ops = { | |
1401 | .device_run = fimc_dma_run, | |
1402 | .job_abort = fimc_job_abort, | |
1403 | }; | |
1404 | ||
1405 | ||
1406 | static int fimc_register_m2m_device(struct fimc_dev *fimc) | |
1407 | { | |
1408 | struct video_device *vfd; | |
1409 | struct platform_device *pdev; | |
1410 | struct v4l2_device *v4l2_dev; | |
1411 | int ret = 0; | |
1412 | ||
1413 | if (!fimc) | |
1414 | return -ENODEV; | |
1415 | ||
1416 | pdev = fimc->pdev; | |
1417 | v4l2_dev = &fimc->m2m.v4l2_dev; | |
1418 | ||
1419 | /* set name if it is empty */ | |
1420 | if (!v4l2_dev->name[0]) | |
1421 | snprintf(v4l2_dev->name, sizeof(v4l2_dev->name), | |
1422 | "%s.m2m", dev_name(&pdev->dev)); | |
1423 | ||
1424 | ret = v4l2_device_register(&pdev->dev, v4l2_dev); | |
1425 | if (ret) | |
548aafcd | 1426 | goto err_m2m_r1; |
5fd8f738 SN |
1427 | |
1428 | vfd = video_device_alloc(); | |
1429 | if (!vfd) { | |
1430 | v4l2_err(v4l2_dev, "Failed to allocate video device\n"); | |
1431 | goto err_m2m_r1; | |
1432 | } | |
1433 | ||
1434 | vfd->fops = &fimc_m2m_fops; | |
1435 | vfd->ioctl_ops = &fimc_m2m_ioctl_ops; | |
1436 | vfd->minor = -1; | |
1437 | vfd->release = video_device_release; | |
1438 | ||
1439 | snprintf(vfd->name, sizeof(vfd->name), "%s:m2m", dev_name(&pdev->dev)); | |
1440 | ||
1441 | video_set_drvdata(vfd, fimc); | |
1442 | platform_set_drvdata(pdev, fimc); | |
1443 | ||
1444 | fimc->m2m.vfd = vfd; | |
1445 | fimc->m2m.m2m_dev = v4l2_m2m_init(&m2m_ops); | |
1446 | if (IS_ERR(fimc->m2m.m2m_dev)) { | |
1447 | v4l2_err(v4l2_dev, "failed to initialize v4l2-m2m device\n"); | |
1448 | ret = PTR_ERR(fimc->m2m.m2m_dev); | |
1449 | goto err_m2m_r2; | |
1450 | } | |
1451 | ||
1452 | ret = video_register_device(vfd, VFL_TYPE_GRABBER, -1); | |
1453 | if (ret) { | |
1454 | v4l2_err(v4l2_dev, | |
1455 | "%s(): failed to register video device\n", __func__); | |
1456 | goto err_m2m_r3; | |
1457 | } | |
1458 | v4l2_info(v4l2_dev, | |
1459 | "FIMC m2m driver registered as /dev/video%d\n", vfd->num); | |
1460 | ||
1461 | return 0; | |
1462 | ||
1463 | err_m2m_r3: | |
1464 | v4l2_m2m_release(fimc->m2m.m2m_dev); | |
1465 | err_m2m_r2: | |
1466 | video_device_release(fimc->m2m.vfd); | |
1467 | err_m2m_r1: | |
1468 | v4l2_device_unregister(v4l2_dev); | |
1469 | ||
1470 | return ret; | |
1471 | } | |
1472 | ||
1473 | static void fimc_unregister_m2m_device(struct fimc_dev *fimc) | |
1474 | { | |
1475 | if (fimc) { | |
1476 | v4l2_m2m_release(fimc->m2m.m2m_dev); | |
1477 | video_unregister_device(fimc->m2m.vfd); | |
548aafcd | 1478 | |
5fd8f738 SN |
1479 | v4l2_device_unregister(&fimc->m2m.v4l2_dev); |
1480 | } | |
1481 | } | |
1482 | ||
1483 | static void fimc_clk_release(struct fimc_dev *fimc) | |
1484 | { | |
1485 | int i; | |
1486 | for (i = 0; i < NUM_FIMC_CLOCKS; i++) { | |
1487 | if (fimc->clock[i]) { | |
1488 | clk_disable(fimc->clock[i]); | |
1489 | clk_put(fimc->clock[i]); | |
1490 | } | |
1491 | } | |
1492 | } | |
1493 | ||
1494 | static int fimc_clk_get(struct fimc_dev *fimc) | |
1495 | { | |
1496 | int i; | |
1497 | for (i = 0; i < NUM_FIMC_CLOCKS; i++) { | |
1498 | fimc->clock[i] = clk_get(&fimc->pdev->dev, fimc_clock_name[i]); | |
1499 | if (IS_ERR(fimc->clock[i])) { | |
1500 | dev_err(&fimc->pdev->dev, | |
1501 | "failed to get fimc clock: %s\n", | |
1502 | fimc_clock_name[i]); | |
1503 | return -ENXIO; | |
1504 | } | |
1505 | clk_enable(fimc->clock[i]); | |
1506 | } | |
1507 | return 0; | |
1508 | } | |
1509 | ||
1510 | static int fimc_probe(struct platform_device *pdev) | |
1511 | { | |
1512 | struct fimc_dev *fimc; | |
1513 | struct resource *res; | |
1514 | struct samsung_fimc_driverdata *drv_data; | |
1515 | int ret = 0; | |
1516 | ||
1517 | dev_dbg(&pdev->dev, "%s():\n", __func__); | |
1518 | ||
1519 | drv_data = (struct samsung_fimc_driverdata *) | |
1520 | platform_get_device_id(pdev)->driver_data; | |
1521 | ||
1522 | if (pdev->id >= drv_data->devs_cnt) { | |
1523 | dev_err(&pdev->dev, "Invalid platform device id: %d\n", | |
1524 | pdev->id); | |
1525 | return -EINVAL; | |
1526 | } | |
1527 | ||
1528 | fimc = kzalloc(sizeof(struct fimc_dev), GFP_KERNEL); | |
1529 | if (!fimc) | |
1530 | return -ENOMEM; | |
1531 | ||
1532 | fimc->id = pdev->id; | |
1533 | fimc->variant = drv_data->variant[fimc->id]; | |
1534 | fimc->pdev = pdev; | |
5f3cc447 | 1535 | fimc->pdata = pdev->dev.platform_data; |
5fd8f738 SN |
1536 | fimc->state = ST_IDLE; |
1537 | ||
1538 | spin_lock_init(&fimc->irqlock); | |
5f3cc447 | 1539 | init_waitqueue_head(&fimc->irq_queue); |
5fd8f738 SN |
1540 | spin_lock_init(&fimc->slock); |
1541 | ||
1542 | mutex_init(&fimc->lock); | |
1543 | ||
1544 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
1545 | if (!res) { | |
1546 | dev_err(&pdev->dev, "failed to find the registers\n"); | |
1547 | ret = -ENOENT; | |
1548 | goto err_info; | |
1549 | } | |
1550 | ||
1551 | fimc->regs_res = request_mem_region(res->start, resource_size(res), | |
1552 | dev_name(&pdev->dev)); | |
1553 | if (!fimc->regs_res) { | |
1554 | dev_err(&pdev->dev, "failed to obtain register region\n"); | |
1555 | ret = -ENOENT; | |
1556 | goto err_info; | |
1557 | } | |
1558 | ||
1559 | fimc->regs = ioremap(res->start, resource_size(res)); | |
1560 | if (!fimc->regs) { | |
1561 | dev_err(&pdev->dev, "failed to map registers\n"); | |
1562 | ret = -ENXIO; | |
1563 | goto err_req_region; | |
1564 | } | |
1565 | ||
1566 | ret = fimc_clk_get(fimc); | |
1567 | if (ret) | |
1568 | goto err_regs_unmap; | |
5f3cc447 | 1569 | clk_set_rate(fimc->clock[0], drv_data->lclk_frequency); |
5fd8f738 SN |
1570 | |
1571 | res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); | |
1572 | if (!res) { | |
1573 | dev_err(&pdev->dev, "failed to get IRQ resource\n"); | |
1574 | ret = -ENXIO; | |
1575 | goto err_clk; | |
1576 | } | |
1577 | fimc->irq = res->start; | |
1578 | ||
1579 | fimc_hw_reset(fimc); | |
1580 | ||
1581 | ret = request_irq(fimc->irq, fimc_isr, 0, pdev->name, fimc); | |
1582 | if (ret) { | |
1583 | dev_err(&pdev->dev, "failed to install irq (%d)\n", ret); | |
1584 | goto err_clk; | |
1585 | } | |
1586 | ||
5fd8f738 SN |
1587 | ret = fimc_register_m2m_device(fimc); |
1588 | if (ret) | |
548aafcd | 1589 | goto err_irq; |
5fd8f738 | 1590 | |
5f3cc447 SN |
1591 | /* At least one camera sensor is required to register capture node */ |
1592 | if (fimc->pdata) { | |
1593 | int i; | |
1594 | for (i = 0; i < FIMC_MAX_CAMIF_CLIENTS; ++i) | |
1595 | if (fimc->pdata->isp_info[i]) | |
1596 | break; | |
1597 | ||
1598 | if (i < FIMC_MAX_CAMIF_CLIENTS) { | |
1599 | ret = fimc_register_capture_device(fimc); | |
1600 | if (ret) | |
1601 | goto err_m2m; | |
1602 | } | |
1603 | } | |
1604 | ||
5fd8f738 SN |
1605 | dev_dbg(&pdev->dev, "%s(): fimc-%d registered successfully\n", |
1606 | __func__, fimc->id); | |
1607 | ||
1608 | return 0; | |
1609 | ||
5f3cc447 SN |
1610 | err_m2m: |
1611 | fimc_unregister_m2m_device(fimc); | |
5fd8f738 SN |
1612 | err_irq: |
1613 | free_irq(fimc->irq, fimc); | |
1614 | err_clk: | |
1615 | fimc_clk_release(fimc); | |
1616 | err_regs_unmap: | |
1617 | iounmap(fimc->regs); | |
1618 | err_req_region: | |
1619 | release_resource(fimc->regs_res); | |
1620 | kfree(fimc->regs_res); | |
1621 | err_info: | |
1622 | kfree(fimc); | |
548aafcd | 1623 | |
5fd8f738 SN |
1624 | return ret; |
1625 | } | |
1626 | ||
1627 | static int __devexit fimc_remove(struct platform_device *pdev) | |
1628 | { | |
1629 | struct fimc_dev *fimc = | |
1630 | (struct fimc_dev *)platform_get_drvdata(pdev); | |
1631 | ||
5fd8f738 | 1632 | free_irq(fimc->irq, fimc); |
5fd8f738 SN |
1633 | fimc_hw_reset(fimc); |
1634 | ||
1635 | fimc_unregister_m2m_device(fimc); | |
5f3cc447 SN |
1636 | fimc_unregister_capture_device(fimc); |
1637 | ||
5fd8f738 SN |
1638 | fimc_clk_release(fimc); |
1639 | iounmap(fimc->regs); | |
1640 | release_resource(fimc->regs_res); | |
1641 | kfree(fimc->regs_res); | |
1642 | kfree(fimc); | |
548aafcd SN |
1643 | |
1644 | dev_info(&pdev->dev, "%s driver unloaded\n", pdev->name); | |
5fd8f738 SN |
1645 | return 0; |
1646 | } | |
1647 | ||
1648 | static struct samsung_fimc_variant fimc01_variant_s5p = { | |
1649 | .has_inp_rot = 1, | |
1650 | .has_out_rot = 1, | |
1651 | .min_inp_pixsize = 16, | |
1652 | .min_out_pixsize = 16, | |
1653 | ||
1654 | .scaler_en_w = 3264, | |
1655 | .scaler_dis_w = 8192, | |
1656 | .in_rot_en_h = 1920, | |
1657 | .in_rot_dis_w = 8192, | |
1658 | .out_rot_en_w = 1920, | |
1659 | .out_rot_dis_w = 4224, | |
1660 | }; | |
1661 | ||
1662 | static struct samsung_fimc_variant fimc2_variant_s5p = { | |
1663 | .min_inp_pixsize = 16, | |
1664 | .min_out_pixsize = 16, | |
1665 | ||
1666 | .scaler_en_w = 4224, | |
1667 | .scaler_dis_w = 8192, | |
1668 | .in_rot_en_h = 1920, | |
1669 | .in_rot_dis_w = 8192, | |
1670 | .out_rot_en_w = 1920, | |
1671 | .out_rot_dis_w = 4224, | |
1672 | }; | |
1673 | ||
1674 | static struct samsung_fimc_variant fimc01_variant_s5pv210 = { | |
ddc79e0f | 1675 | .pix_hoff = 1, |
5fd8f738 SN |
1676 | .has_inp_rot = 1, |
1677 | .has_out_rot = 1, | |
1678 | .min_inp_pixsize = 16, | |
548aafcd | 1679 | .min_out_pixsize = 16, |
5fd8f738 SN |
1680 | |
1681 | .scaler_en_w = 4224, | |
1682 | .scaler_dis_w = 8192, | |
1683 | .in_rot_en_h = 1920, | |
1684 | .in_rot_dis_w = 8192, | |
1685 | .out_rot_en_w = 1920, | |
1686 | .out_rot_dis_w = 4224, | |
1687 | }; | |
1688 | ||
1689 | static struct samsung_fimc_variant fimc2_variant_s5pv210 = { | |
ddc79e0f | 1690 | .pix_hoff = 1, |
5fd8f738 | 1691 | .min_inp_pixsize = 16, |
548aafcd | 1692 | .min_out_pixsize = 16, |
5fd8f738 SN |
1693 | |
1694 | .scaler_en_w = 1920, | |
1695 | .scaler_dis_w = 8192, | |
1696 | .in_rot_en_h = 1280, | |
1697 | .in_rot_dis_w = 8192, | |
1698 | .out_rot_en_w = 1280, | |
1699 | .out_rot_dis_w = 1920, | |
1700 | }; | |
1701 | ||
1702 | static struct samsung_fimc_driverdata fimc_drvdata_s5p = { | |
1703 | .variant = { | |
1704 | [0] = &fimc01_variant_s5p, | |
1705 | [1] = &fimc01_variant_s5p, | |
1706 | [2] = &fimc2_variant_s5p, | |
1707 | }, | |
5f3cc447 SN |
1708 | .devs_cnt = 3, |
1709 | .lclk_frequency = 133000000UL, | |
5fd8f738 SN |
1710 | }; |
1711 | ||
1712 | static struct samsung_fimc_driverdata fimc_drvdata_s5pv210 = { | |
1713 | .variant = { | |
1714 | [0] = &fimc01_variant_s5pv210, | |
1715 | [1] = &fimc01_variant_s5pv210, | |
1716 | [2] = &fimc2_variant_s5pv210, | |
1717 | }, | |
5f3cc447 SN |
1718 | .devs_cnt = 3, |
1719 | .lclk_frequency = 166000000UL, | |
5fd8f738 SN |
1720 | }; |
1721 | ||
1722 | static struct platform_device_id fimc_driver_ids[] = { | |
1723 | { | |
1724 | .name = "s5p-fimc", | |
1725 | .driver_data = (unsigned long)&fimc_drvdata_s5p, | |
1726 | }, { | |
1727 | .name = "s5pv210-fimc", | |
1728 | .driver_data = (unsigned long)&fimc_drvdata_s5pv210, | |
1729 | }, | |
1730 | {}, | |
1731 | }; | |
1732 | MODULE_DEVICE_TABLE(platform, fimc_driver_ids); | |
1733 | ||
1734 | static struct platform_driver fimc_driver = { | |
1735 | .probe = fimc_probe, | |
1736 | .remove = __devexit_p(fimc_remove), | |
1737 | .id_table = fimc_driver_ids, | |
1738 | .driver = { | |
1739 | .name = MODULE_NAME, | |
1740 | .owner = THIS_MODULE, | |
1741 | } | |
1742 | }; | |
1743 | ||
5fd8f738 SN |
1744 | static int __init fimc_init(void) |
1745 | { | |
548aafcd SN |
1746 | int ret = platform_driver_register(&fimc_driver); |
1747 | if (ret) | |
1748 | err("platform_driver_register failed: %d\n", ret); | |
1749 | return ret; | |
5fd8f738 SN |
1750 | } |
1751 | ||
1752 | static void __exit fimc_exit(void) | |
1753 | { | |
1754 | platform_driver_unregister(&fimc_driver); | |
1755 | } | |
1756 | ||
1757 | module_init(fimc_init); | |
1758 | module_exit(fimc_exit); | |
1759 | ||
5f3cc447 SN |
1760 | MODULE_AUTHOR("Sylwester Nawrocki <s.nawrocki@samsung.com>"); |
1761 | MODULE_DESCRIPTION("S5P FIMC camera host interface/video postprocessor driver"); | |
5fd8f738 | 1762 | MODULE_LICENSE("GPL"); |