blob: e229eb7958655ac449bcce6e92edd2008c2ce23d [file] [log] [blame]
Petr Havlenac9288142012-11-15 14:07:10 +05301/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include <fcntl.h>
18#include <errno.h>
19#include <stdlib.h>
20#include <sys/ioctl.h>
21#include <linux/videodev.h>
22
23#include <hardware/hwcomposer.h>
24
25#include <cutils/log.h>
26
27#include "fimc.h"
28
29typedef struct sec_img sec_img;
30typedef struct sec_rect sec_rect;
31
32int fimc_v4l2_set_src(int fd, unsigned int hw_ver, s5p_fimc_img_info *src)
33{
34 struct v4l2_format fmt;
35 struct v4l2_cropcap cropcap;
36 struct v4l2_crop crop;
37 struct v4l2_requestbuffers req;
38
39 /*
40 * To set size & format for source image (DMA-INPUT)
41 */
42 fmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
43 fmt.fmt.pix.width = src->full_width;
44 fmt.fmt.pix.height = src->full_height;
45 fmt.fmt.pix.pixelformat = src->color_space;
46 fmt.fmt.pix.field = V4L2_FIELD_NONE;
47
48 if (ioctl (fd, VIDIOC_S_FMT, &fmt) < 0) {
49 ALOGE("VIDIOC_S_FMT failed : errno=%d (%s) : fd=%d", errno,
50 strerror(errno), fd);
51 return -1;
52 }
53
54 /*
55 * crop input size
56 */
57 crop.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
58 if (0x50 == hw_ver) {
59 crop.c.left = src->start_x;
60 crop.c.top = src->start_y;
61 } else {
62 crop.c.left = 0;
63 crop.c.top = 0;
64 }
65 crop.c.width = src->width;
66 crop.c.height = src->height;
67 if (ioctl(fd, VIDIOC_S_CROP, &crop) < 0) {
68 ALOGE("Error in video VIDIOC_S_CROP (%d, %d, %d, %d)",
69 crop.c.left, crop.c.top, crop.c.width, crop.c.height);
70 return -1;
71 }
72
73 /*
74 * input buffer type
75 */
76 req.count = 1;
77 req.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
78 req.memory = V4L2_MEMORY_USERPTR;
79
80 if (ioctl (fd, VIDIOC_REQBUFS, &req) < 0) {
81 ALOGE("Error in VIDIOC_REQBUFS");
82 return -1;
83 }
84
85 return 0;
86}
87
88int fimc_v4l2_set_dst(int fd,
89 s5p_fimc_img_info *dst,
90 int rotation,
91 int flag_h_flip,
92 int flag_v_flip,
93 unsigned int addr)
94{
95 struct v4l2_format fmt;
96 struct v4l2_control vc;
97 struct v4l2_framebuffer fbuf;
98
99 /*
100 * set rotation configuration
101 */
102 vc.id = V4L2_CID_HFLIP;
103 vc.value = flag_h_flip;
104 if (ioctl(fd, VIDIOC_S_CTRL, &vc) < 0) {
105 ALOGE("Error in video VIDIOC_S_CTRL - flag_h_flip (%d)", flag_h_flip);
106 return -1;
107 }
108
109 vc.id = V4L2_CID_VFLIP;
110 vc.value = flag_v_flip;
111 if (ioctl(fd, VIDIOC_S_CTRL, &vc) < 0) {
112 ALOGE("Error in video VIDIOC_S_CTRL - flag_v_flip (%d)", flag_v_flip);
113 return -1;
114 }
115
116 vc.id = V4L2_CID_ROTATION;
117 vc.value = rotation;
118 if (ioctl(fd, VIDIOC_S_CTRL, &vc) < 0) {
119 ALOGE("Error in video VIDIOC_S_CTRL - rotation (%d)", rotation);
120 return -1;
121 }
122
123 /*
124 * set size, format & address for destination image (DMA-OUTPUT)
125 */
126 if (ioctl (fd, VIDIOC_G_FBUF, &fbuf) < 0) {
127 ALOGE("Error in video VIDIOC_G_FBUF");
128 return -1;
129 }
130
131 fbuf.base = (void *)addr;
132 fbuf.fmt.width = dst->full_width;
133 fbuf.fmt.height = dst->full_height;
134 fbuf.fmt.pixelformat = dst->color_space;
135 if (ioctl (fd, VIDIOC_S_FBUF, &fbuf) < 0) {
136 ALOGE("Error in video VIDIOC_S_FBUF 0x%x %d %d %d",
137 (void *)addr, dst->full_width, dst->full_height,
138 dst->color_space);
139 return -1;
140 }
141
142 /*
143 * set destination window
144 */
145 fmt.type = V4L2_BUF_TYPE_VIDEO_OVERLAY;
146 fmt.fmt.win.w.left = dst->start_x;
147 fmt.fmt.win.w.top = dst->start_y;
148 fmt.fmt.win.w.width = dst->width;
149 fmt.fmt.win.w.height = dst->height;
150 if (ioctl(fd, VIDIOC_S_FMT, &fmt) < 0) {
151 ALOGE("Error in video VIDIOC_S_FMT %d %d %d %d",
152 dst->start_x, dst->start_y, dst->width, dst->height);
153 return -1;
154 }
155
156 return 0;
157}
158
159static int fimc_v4l2_stream_on(int fd, enum v4l2_buf_type type)
160{
161 if (ioctl (fd, VIDIOC_STREAMON, &type) < 0) {
162 ALOGE("Error in VIDIOC_STREAMON");
163 return -1;
164 }
165
166 return 0;
167}
168
169static int fimc_v4l2_queue(int fd, struct fimc_buf *fimc_buf)
170{
171 struct v4l2_buffer buf;
172
173 buf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
174 buf.memory = V4L2_MEMORY_USERPTR;
175 buf.m.userptr = (unsigned long)fimc_buf;
176 buf.length = 0;
177 buf.index = 0;
178
179 if (ioctl (fd, VIDIOC_QBUF, &buf) < 0) {
180 ALOGE("Error in VIDIOC_QBUF");
181 return -1;
182 }
183
184 return 0;
185}
186
187static int fimc_v4l2_dequeue(int fd)
188{
189 struct v4l2_buffer buf;
190
191 buf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
192 buf.memory = V4L2_MEMORY_USERPTR;
193
194 if (ioctl (fd, VIDIOC_DQBUF, &buf) < 0) {
195 ALOGE("Error in VIDIOC_DQBUF");
196 return -1;
197 }
198
199 return buf.index;
200}
201
202static int fimc_v4l2_stream_off(int fd)
203{
204 enum v4l2_buf_type type;
205 type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
206
207 if (ioctl (fd, VIDIOC_STREAMOFF, &type) < 0) {
208 ALOGE("Error in VIDIOC_STREAMOFF");
209 return -1;
210 }
211
212 return 0;
213}
214
215static int fimc_v4l2_clr_buf(int fd)
216{
217 struct v4l2_requestbuffers req;
218
219 req.count = 0;
220 req.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
221 req.memory = V4L2_MEMORY_USERPTR;
222
223 if (ioctl (fd, VIDIOC_REQBUFS, &req) < 0) {
224 ALOGE("Error in VIDIOC_REQBUFS");
225 }
226
227 return 0;
228}
229
230static int fimc_handle_oneshot(int fd, struct fimc_buf *fimc_buf)
231{
232 int ret =0;
233
234 if (fimc_v4l2_stream_on(fd, V4L2_BUF_TYPE_VIDEO_OUTPUT) < 0) {
235 ALOGE("Fail : v4l2_stream_on()");
236 return -1;
237 }
238
239 if (fimc_v4l2_queue(fd, fimc_buf) < 0) {
240 ALOGE("Fail : v4l2_queue()");
241 ret = -1;
242 goto stream_off;
243 }
244
245 if (fimc_v4l2_dequeue(fd) < 0) {
246 ALOGE("Fail : v4l2_dequeue()");
247 ret = -1;
248 goto stream_off;
249 }
250
251stream_off:
252 if (fimc_v4l2_stream_off(fd) < 0) {
253 ALOGE("Fail : v4l2_stream_off()");
254 return -1;
255 }
256
257 if (fimc_v4l2_clr_buf(fd) < 0) {
258 ALOGE("Fail : v4l2_clr_buf()");
259 return -1;
260 }
261
262 return ret;
263}
264
265static int get_src_phys_addr(s5p_fimc_t *fimc,
266 sec_img *src_img,
267 unsigned int *phyAddr)
268{
269 if(src_img->mem_type == FIMC_MEM_TYPE_PHYS) {
270 switch(src_img->format) {
271 case HAL_PIXEL_FORMAT_YCbCr_420_SP:
272 fimc->params.src.buf_addr_phy_rgb_y = phyAddr[0];
273 fimc->params.src.buf_addr_phy_cb = phyAddr[1];
274 break;
275 default:
276 ALOGE("%s format error (format=0x%x)", __func__,
277 src_img->format);
278 return -1;
279 }
280 } else {
281 ALOGE("%s mem_type error (mem_type=%d)", __func__, src_img->mem_type);
282 return -1;
283 }
284
285 return 0;
286}
287
288static int get_dst_phys_addr(s5p_fimc_t *fimc,
289 sec_img *dst_img)
290{
291 unsigned int dst_phys_addr = 0;
292
293 if (FIMC_MEM_TYPE_PHYS == dst_img->mem_type && 0 != dst_img->base)
294 dst_phys_addr = dst_img->base;
295 else {
296 ALOGE("%s::get_dst_phys_addr fail ", __func__);
297 dst_phys_addr = 0;
298 }
299 return dst_phys_addr;
300}
301
302static inline int rotateValueHAL2PP(unsigned char transform,
303 int *flag_h_flip,
304 int *flag_v_flip)
305{
306 int rotate_result = 0;
307 int rotate_flag = transform & 0x7;
308
309 switch (rotate_flag) {
310 case HAL_TRANSFORM_ROT_90:
311 rotate_result = 90;
312 break;
313 case HAL_TRANSFORM_ROT_180:
314 rotate_result = 180;
315 break;
316 case HAL_TRANSFORM_ROT_270:
317 rotate_result = 270;
318 break;
319 }
320
321 switch (rotate_flag) {
322 case HAL_TRANSFORM_FLIP_H:
323 *flag_h_flip = 1;
324 *flag_v_flip = 0;
325 break;
326 case HAL_TRANSFORM_FLIP_V:
327 *flag_h_flip = 0;
328 *flag_v_flip = 1;
329 break;
330 default:
331 *flag_h_flip = 0;
332 *flag_v_flip = 0;
333 break;
334 }
335
336 return rotate_result;
337}
338
339static inline int multipleOfN(int number, int N)
340{
341 int result = number;
342 switch (N) {
343 case 1:
344 case 2:
345 case 4:
346 case 8:
347 case 16:
348 case 32:
349 case 64:
350 case 128:
351 case 256:
352 result = (number - (number & (N-1)));
353 break;
354 default:
355 result = number - (number % N);
356 break;
357 }
358 return result;
359}
360
361static inline int widthOfPP(unsigned int ver,
362 int pp_color_format,
363 int number)
364{
365 if (0x50 == ver) {
366 switch(pp_color_format) {
367 /* 422 1/2/3 plane */
368 case V4L2_PIX_FMT_YUYV:
369 case V4L2_PIX_FMT_UYVY:
370 case V4L2_PIX_FMT_NV61:
371 case V4L2_PIX_FMT_NV16:
372 case V4L2_PIX_FMT_YUV422P:
373
374 /* 420 2/3 plane */
375 case V4L2_PIX_FMT_NV21:
376 case V4L2_PIX_FMT_NV12:
377 case V4L2_PIX_FMT_NV12T:
378 case V4L2_PIX_FMT_YUV420:
379 return multipleOfN(number, 2);
380
381 default :
382 return number;
383 }
384 } else {
385 switch(pp_color_format) {
386 case V4L2_PIX_FMT_RGB565:
387 return multipleOfN(number, 8);
388
389 case V4L2_PIX_FMT_RGB32:
390 return multipleOfN(number, 4);
391
392 case V4L2_PIX_FMT_YUYV:
393 case V4L2_PIX_FMT_UYVY:
394 return multipleOfN(number, 4);
395
396 case V4L2_PIX_FMT_NV61:
397 case V4L2_PIX_FMT_NV16:
398 return multipleOfN(number, 8);
399
400 case V4L2_PIX_FMT_YUV422P:
401 return multipleOfN(number, 16);
402
403 case V4L2_PIX_FMT_NV21:
404 case V4L2_PIX_FMT_NV12:
405 case V4L2_PIX_FMT_NV12T:
406 return multipleOfN(number, 8);
407
408 case V4L2_PIX_FMT_YUV420:
409 return multipleOfN(number, 16);
410
411 default :
412 return number;
413 }
414 }
415 return number;
416}
417
418static inline int heightOfPP(int pp_color_format,
419 int number)
420{
421 switch(pp_color_format) {
422 case V4L2_PIX_FMT_NV21:
423 case V4L2_PIX_FMT_NV12:
424 case V4L2_PIX_FMT_NV12T:
425 case V4L2_PIX_FMT_YUV420:
426 return multipleOfN(number, 2);
427
428 default :
429 return number;
430 }
431 return number;
432}
433
434static int fimc_core(s5p_fimc_t *fimc,
435 sec_img *src_img,
436 sec_rect *src_rect,
437 uint32_t src_color_space,
438 unsigned int dst_phys_addr,
439 sec_img *dst_img,
440 sec_rect *dst_rect,
441 uint32_t dst_color_space,
442 int transform)
443{
444 s5p_fimc_params_t * params = &(fimc->params);
445
446 unsigned int frame_size = 0;
447 struct fimc_buf fimc_src_buf;
448
449 int src_bpp, src_planes;
450 int flag_h_flip = 0;
451 int flag_v_flip = 0;
452 int rotate_value = rotateValueHAL2PP(transform, &flag_h_flip, &flag_v_flip);
453
454 /* set post processor configuration */
455 params->src.full_width = src_img->w;
456 params->src.full_height = src_img->h;
457 params->src.start_x = src_rect->x;
458 params->src.start_y = src_rect->y;
459 params->src.width = widthOfPP(fimc->hw_ver, src_color_space, src_rect->w);
460 params->src.height = heightOfPP(src_color_space, src_rect->h);
461 params->src.color_space = src_color_space;
462
463
464 /* check minimum */
465 if (src_rect->w < 16 || src_rect->h < 8) {
466 ALOGE("%s src size is not supported by fimc : f_w=%d f_h=%d x=%d y=%d \
467 w=%d h=%d (ow=%d oh=%d) format=0x%x", __func__,
468 params->src.full_width, params->src.full_height,
469 params->src.start_x, params->src.start_y, params->src.width,
470 params->src.height, src_rect->w, src_rect->h,
471 params->src.color_space);
472 return -1;
473 }
474
475 switch (rotate_value) {
476 case 0:
477 params->dst.full_width = dst_img->w;
478 params->dst.full_height = dst_img->h;
479
480 params->dst.start_x = dst_rect->x;
481 params->dst.start_y = dst_rect->y;
482
483 params->dst.width =
484 widthOfPP(fimc->hw_ver, dst_color_space, dst_rect->w);
485 params->dst.height = heightOfPP(dst_color_space, dst_rect->h);
486 break;
487 case 90:
488 params->dst.full_width = dst_img->h;
489 params->dst.full_height = dst_img->w;
490
491 params->dst.start_x = dst_rect->y;
492 params->dst.start_y = dst_img->w - (dst_rect->x + dst_rect->w);
493
494 params->dst.width =
495 widthOfPP(fimc->hw_ver, dst_color_space, dst_rect->h);
496 params->dst.height =
497 widthOfPP(fimc->hw_ver, dst_color_space, dst_rect->w);
498
499 if (0x50 > fimc->hw_ver)
500 params->dst.start_y += (dst_rect->w - params->dst.height);
501 break;
502 case 180:
503 params->dst.full_width = dst_img->w;
504 params->dst.full_height = dst_img->h;
505
506 params->dst.start_x = dst_img->w - (dst_rect->x + dst_rect->w);
507 params->dst.start_y = dst_img->h - (dst_rect->y + dst_rect->h);
508
509 params->dst.width =
510 widthOfPP(fimc->hw_ver, dst_color_space, dst_rect->w);
511 params->dst.height = heightOfPP(dst_color_space, dst_rect->h);
512 break;
513 case 270:
514 params->dst.full_width = dst_img->h;
515 params->dst.full_height = dst_img->w;
516
517 params->dst.start_x = dst_img->h - (dst_rect->y + dst_rect->h);
518 params->dst.start_y = dst_rect->x;
519
520 params->dst.width =
521 widthOfPP(fimc->hw_ver, dst_color_space, dst_rect->h);
522 params->dst.height =
523 widthOfPP(fimc->hw_ver, dst_color_space, dst_rect->w);
524
525 if (0x50 > fimc->hw_ver)
526 params->dst.start_y += (dst_rect->w - params->dst.height);
527 break;
528 }
529
530 params->dst.color_space = dst_color_space;
531
532 /* check minimum */
533 if (dst_rect->w < 8 || dst_rect->h < 4) {
534 ALOGE("%s dst size is not supported by fimc : \
535 f_w=%d f_h=%d x=%d y=%d w=%d h=%d (ow=%d oh=%d) format=0x%x",
536 __func__, params->dst.full_width, params->dst.full_height,
537 params->dst.start_x, params->dst.start_y, params->dst.width,
538 params->dst.height, dst_rect->w, dst_rect->h,
539 params->dst.color_space);
540 return -1;
541 }
542
543 /* check scaling limit
544 * the scaling limie must not be more than MAX_RESIZING_RATIO_LIMIT
545 */
546 if (((src_rect->w > dst_rect->w) &&
547 ((src_rect->w / dst_rect->w) > MAX_RESIZING_RATIO_LIMIT)) ||
548 ((dst_rect->w > src_rect->w) &&
549 ((dst_rect->w / src_rect->w) > MAX_RESIZING_RATIO_LIMIT))) {
550 ALOGE("%s over scaling limit : src.w=%d dst.w=%d (limit=%d)",
551 __func__, src_rect->w, dst_rect->w, MAX_RESIZING_RATIO_LIMIT);
552 return -1;
553 }
554
555
556 /* set configuration related to destination (DMA-OUT)
557 * - set input format & size
558 * - crop input size
559 * - set input buffer
560 * - set buffer type (V4L2_MEMORY_USERPTR)
561 */
562 if (fimc_v4l2_set_dst(fimc->dev_fd,
563 &params->dst,
564 rotate_value,
565 flag_h_flip,
566 flag_v_flip,
567 dst_phys_addr) < 0) {
568 return -1;
569 }
570
571 /* set configuration related to source (DMA-INPUT)
572 * - set input format & size
573 * - crop input size
574 * - set input buffer
575 * - set buffer type (V4L2_MEMORY_USERPTR)
576 */
577 if (fimc_v4l2_set_src(fimc->dev_fd, fimc->hw_ver, &params->src) < 0)
578 return -1;
579
580 /* set input dma address (Y/RGB, Cb, Cr) */
581 switch (src_img->format) {
582 case HAL_PIXEL_FORMAT_YCbCr_420_SP:
583 /* for video display zero copy case */
584 fimc_src_buf.base[0] = params->src.buf_addr_phy_rgb_y;
585 fimc_src_buf.base[1] = params->src.buf_addr_phy_cb;
586 break;
587
588 default:
589 /* set source image */
590 fimc_src_buf.base[0] = params->src.buf_addr_phy_rgb_y;
591 break;
592 }
593
594 if (fimc_handle_oneshot(fimc->dev_fd, &fimc_src_buf) < 0) {
595 fimc_v4l2_clr_buf(fimc->dev_fd);
596 return -1;
597 }
598
599 return 0;
600}
601
602static
603void* fimc_get_reserved_mem_addr(s5p_fimc_t *fimc)
604{
605 int ret;
606 struct v4l2_control vc;
607
608 vc.id = V4L2_CID_RESERVED_MEM_BASE_ADDR;
609 vc.value = 0;
610
611 ret = ioctl(fimc->dev_fd, VIDIOC_G_CTRL, &vc);
612 if (ret < 0) {
613 ALOGE("Err(%s) in video VIDIOC_G_CTRL (%d)",ret);
614 return NULL;
615 }
616
617 return vc.value;
618}
619
620int fimc_open(s5p_fimc_t *fimc, const char* dev)
621{
622 struct v4l2_capability cap;
623 struct v4l2_format fmt;
624 struct v4l2_control vc;
625
626 /* open device file */
627 if(fimc->dev_fd < 0) {
628 fimc->dev_fd = open(dev, O_RDWR);
629 if (fimc->dev_fd < 0) {
630 ALOGE("%s::Post processor open error (%d)", __func__, errno);
631 goto err;
632 }
633 }
634
635 /* check capability */
636 if (ioctl(fimc->dev_fd, VIDIOC_QUERYCAP, &cap) < 0) {
637 ALOGE("VIDIOC_QUERYCAP failed");
638 goto err;
639 }
640
641 if (!(cap.capabilities & V4L2_CAP_STREAMING)) {
642 ALOGE("%d has no streaming support", fimc->dev_fd);
643 goto err;
644 }
645
646 if (!(cap.capabilities & V4L2_CAP_VIDEO_OUTPUT)) {
647 ALOGE("%d is no video output", fimc->dev_fd);
648 goto err;
649 }
650
651 /*
652 * malloc fimc_outinfo structure
653 */
654 fmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
655 if (ioctl(fimc->dev_fd, VIDIOC_G_FMT, &fmt) < 0) {
656 ALOGE("%s::Error in video VIDIOC_G_FMT", __func__);
657 goto err;
658 }
659
660 fimc->out_buf.phys_addr = fimc_get_reserved_mem_addr(fimc);
661
662 vc.id = V4L2_CID_FIMC_VERSION;
663 vc.value = 0;
664
665 if (ioctl(fimc->dev_fd, VIDIOC_G_CTRL, &vc) < 0) {
666 ALOGE("%s::Error in video VIDIOC_G_CTRL", __func__);
667 goto err;
668 }
669 fimc->hw_ver = vc.value;
670
671 return 0;
672
673err:
674 if (0 <= fimc->dev_fd)
675 close(fimc->dev_fd);
676 fimc->dev_fd = -1;
677
678 return -1;
679}
680
681void fimc_close(s5p_fimc_t *fimc)
682{
683 /* close */
684 if (0 <= fimc->dev_fd)
685 close(fimc->dev_fd);
686 fimc->dev_fd = -1;
687}
688
689int fimc_flush(s5p_fimc_t *fimc,
690 struct sec_img *src_img,
691 struct sec_rect *src_rect,
692 struct sec_img *dst_img,
693 struct sec_rect *dst_rect,
694 unsigned int *phyAddr,
695 uint32_t transform)
696{
697 unsigned int dst_phys_addr = 0;
698 int32_t src_color_space;
699 int32_t dst_color_space;
700
701 /* 1 : source address and size */
702
703 if(0 > get_src_phys_addr(fimc, src_img, phyAddr))
704 return -1;
705
706 /* 2 : destination address and size */
707 if(0 == (dst_phys_addr = get_dst_phys_addr(fimc, dst_img)))
708 return -2;
709
710 /* check whether fimc supports the src format */
711 if (0 > (src_color_space = HAL_PIXEL_FORMAT_2_V4L2_PIX(src_img->format)))
712 return -3;
713
714 if (0 > (dst_color_space = HAL_PIXEL_FORMAT_2_V4L2_PIX(dst_img->format)))
715 return -4;
716
717 if(fimc_core(fimc, src_img, src_rect, (uint32_t)src_color_space,
718 dst_phys_addr, dst_img, dst_rect, (uint32_t)dst_color_space, transform) < 0)
719 return -5;
720
721 return 0;
722}