/*
 * v4l2_common.c
 *
 * Copyright (C) 2012 by Digi International Inc.
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 as published by
 * the Free Software Foundation.
 *
 * Description: V4L2 common library.
 *
 * V4L2 API @ http://v4l2spec.bytesex.org/
 *
 */

#include "v4l2_defs.h"

#define _MAKE_CHAN(num, v_in, g_in, a_in, out) \
        ((num << 24) | (v_in << 18) | (g_in << 12) | (a_in << 6) | out)
#define IPU_MAX_CH      32
#define _MAKE_ALT_CHAN(ch)              (ch | (IPU_MAX_CH << 24))
#define NO_DMA 0x3F
/*!
 * Enumeration of IPU logical channels. An IPU logical channel is defined as a
 * combination of an input (memory to IPU), output (IPU to memory), and/or
 * secondary input IDMA channels and in some cases an Image Converter task.
 * Some channels consist of only an input or output.
 */
typedef enum {
        CHAN_NONE = -1,
        MEM_ROT_ENC_MEM = _MAKE_CHAN(1, 45, NO_DMA, NO_DMA, 48),
        MEM_ROT_VF_MEM = _MAKE_CHAN(2, 46, NO_DMA, NO_DMA, 49),
        MEM_ROT_PP_MEM = _MAKE_CHAN(3, 47, NO_DMA, NO_DMA, 50),

        MEM_PRP_ENC_MEM = _MAKE_CHAN(4, 12, 14, 17, 20),
        MEM_PRP_VF_MEM = _MAKE_CHAN(5, 12, 14, 17, 21),
        MEM_PP_MEM = _MAKE_CHAN(6, 11, 15, 18, 22),

        MEM_DC_SYNC = _MAKE_CHAN(7, 28, NO_DMA, NO_DMA, NO_DMA),
        MEM_DC_ASYNC = _MAKE_CHAN(8, 41, NO_DMA, NO_DMA, NO_DMA),
        MEM_BG_SYNC = _MAKE_CHAN(9, 23, NO_DMA, 51, NO_DMA),
        MEM_FG_SYNC = _MAKE_CHAN(10, 27, NO_DMA, 31, NO_DMA),

        MEM_BG_ASYNC0 = _MAKE_CHAN(11, 24, NO_DMA, 52, NO_DMA),
        MEM_FG_ASYNC0 = _MAKE_CHAN(12, 29, NO_DMA, 33, NO_DMA),
        MEM_BG_ASYNC1 = _MAKE_ALT_CHAN(MEM_BG_ASYNC0),
        MEM_FG_ASYNC1 = _MAKE_ALT_CHAN(MEM_FG_ASYNC0),

        DIRECT_ASYNC0 = _MAKE_CHAN(13, NO_DMA, NO_DMA, NO_DMA, NO_DMA),
        DIRECT_ASYNC1 = _MAKE_CHAN(14, NO_DMA, NO_DMA, NO_DMA, NO_DMA),

        CSI_MEM0 = _MAKE_CHAN(15, NO_DMA, NO_DMA, NO_DMA, 0),
        CSI_MEM1 = _MAKE_CHAN(16, NO_DMA, NO_DMA, NO_DMA, 1),
        CSI_MEM2 = _MAKE_CHAN(17, NO_DMA, NO_DMA, NO_DMA, 2),
        CSI_MEM3 = _MAKE_CHAN(18, NO_DMA, NO_DMA, NO_DMA, 3),

        CSI_MEM = CSI_MEM0,

        CSI_PRP_ENC_MEM = _MAKE_CHAN(19, NO_DMA, NO_DMA, NO_DMA, 20),
        CSI_PRP_VF_MEM = _MAKE_CHAN(20, NO_DMA, NO_DMA, NO_DMA, 21),

        /* for vdi mem->vdi->ic->mem , add graphics plane and alpha*/
        MEM_VDI_PRP_VF_MEM_P = _MAKE_CHAN(21, 8, 14, 17, 21),
        MEM_VDI_PRP_VF_MEM = _MAKE_CHAN(22, 9, 14, 17, 21),
        MEM_VDI_PRP_VF_MEM_N = _MAKE_CHAN(23, 10, 14, 17, 21),

        /* for vdi mem->vdi->mem */
        MEM_VDI_MEM_P = _MAKE_CHAN(24, 8, NO_DMA, NO_DMA, 5),
        MEM_VDI_MEM = _MAKE_CHAN(25, 9, NO_DMA, NO_DMA, 5),
        MEM_VDI_MEM_N = _MAKE_CHAN(26, 10, NO_DMA, NO_DMA, 5),

        /* fake channel for vdoa to link with IPU */
        MEM_VDOA_MEM =  _MAKE_CHAN(27, NO_DMA, NO_DMA, NO_DMA, NO_DMA),

        MEM_PP_ADC = CHAN_NONE,
        ADC_SYS2 = CHAN_NONE,

} ipu_channel_t;

#define BRIGHTNESS_MAX_VALUE	0xFF
#define BRIGTHNESS_MIN_VALUE	0
#define SATURATION_MAX_VALUE	0xFF
#define SATURATION_MIN_VALUE	0
#define GLOBAL_ALPHA_MAX_VALUE	0xFF
/* This is confusing, because they are inverted */
#define LOCAL_ALPHA_MAX_VALUE	0x0
#define LOCAL_ALPHA_MIN_VALUE	0xFF

/* Returns the open file descriptor if the fb is an MXC fb overlay,
 * error otherwise. */
static int v4l2_mxc_is_overlay(char *fb_device)
{
	int fd, ret;
	struct fb_fix_screeninfo fb_fix;

	fd = ret = -1;

	if ((fd = open(fb_device, O_RDWR)) < 0) {
		log("Unable to open frame buffer 0\n");
		return ret;
	}

	if ((ret = ioctl(fd, FBIOGET_FSCREENINFO, &fb_fix)) < 0) {
		close(fd);
		return ret;
	}

	if (!strcmp(fb_fix.id, "DISP3 FG"))
		return fd;

	close(fd);
	return -1;
}

/* Return overlay background framebuffer device path, error otherwise */
int v4l2_get_overlay_bg(char *fb_device)
{
	static const char *fb_path = "/dev/fb";
	int ipu_ch, i;
	int fd = -1;

	for (i = 0; i < 2; i++) {
		sprintf(fb_device, "%s%d", fb_path, i);
		if ((fd = open(fb_device, O_RDWR)) < 0) {
			log("Unable to open frame buffer %s\n", fb_device);
			goto error;
		}
		if (ioctl(fd, MXCFB_GET_FB_IPU_CHAN, &ipu_ch) < 0) {
			log("ioctl MXCFB_GET_FB_IPU_CHAN error\n");
			close(fd);
			goto error;
		}
		close(fd);
		if (ipu_ch == MEM_BG_SYNC) {
			log("Overlay background frame buffer device: %s\n", fb_device);
			return 0;
		}
	}

error:
	return -1;
}

/* Enables or disables the V4L2 previewing */
int v4l2_overlay_control(int fd_overlay, int start)
{
	/* Start overlay */
	return ioctl(fd_overlay, VIDIOC_OVERLAY, &start);
}

/* Returns a string representation of some v4L2 pixel formats, or its
 * numerical code string otherwise. */
char *v4l2_fmt_str(int pixelformat)
{
	static char tmp[64] = "";

	switch (pixelformat) {
		case IPU_PIX_FMT_RGB332:
			return "RGB332";
		case IPU_PIX_FMT_RGB555:
			return "RGB555";
		case IPU_PIX_FMT_RGB565:
			return "RGB565";
		case IPU_PIX_FMT_RGB666:
			return "RGB666";
		case IPU_PIX_FMT_RGB24:
			return "RGB24";
		case IPU_PIX_FMT_BGR32:
			return "BGR32";
		case IPU_PIX_FMT_BGRA32:
			return "BGRA32";
		case IPU_PIX_FMT_RGB32:
			return "RGB32";
		case IPU_PIX_FMT_RGBA32:
			return "RGBA32";
		case IPU_PIX_FMT_ABGR32:
			return "ABGR32";
		case IPU_PIX_FMT_BGR24:
			return "BGR24";
		case IPU_PIX_FMT_YUYV:
			return "YUYV";
		case IPU_PIX_FMT_UYVY:
			return "UYVY";
		case IPU_PIX_FMT_YVYU:
			return "YVYU";
		case IPU_PIX_FMT_VYUY:
			return "VYUY";
		case IPU_PIX_FMT_Y41P:
			return "Y41P";
		case IPU_PIX_FMT_YUV444:
			return "YUV444";
		case IPU_PIX_FMT_VYU444:
			return "VYU444";
		case IPU_PIX_FMT_NV12:
			return "NV12";
		// Missing formats
		default:
			sprintf(tmp, "Undecoded format: %x\n", pixelformat);
			return tmp;
	}
}

/* Returns true if the specified V4L2 capability is supported by the
 * device, false or error otherwise.*/
int v4l2_is_capability_supported(int fd, int cap)
{
	struct v4l2_capability capability;
	int ret = 0;

	/* Check the device capabilities */
	memset(&capability, 0, sizeof(capability));
	ret = ioctl(fd, VIDIOC_QUERYCAP, &capability);
	if (ret < 0) {
		if (errno == -EINVAL)
			log("Not a V4L2 device.\n");
		return ret;
	}

	if (capability.capabilities & cap) {
		return TRUE;
	}
	return FALSE;
}

/* Returns true if the specified V4L2 video standard is supported by the
 * device, false or error otherwise.*/
int v4l2_is_video_std_supported(int fd, int standard)
{
	v4l2_std_id std_id;
	int ret = 0;

	/* Check video standard */
	memset(&std_id, 0, sizeof(std_id));
	if ((ret = ioctl(fd, VIDIOC_G_STD, &std_id)) < 0) {
		log("VIDIOC_G_STD failed with %d\n", ret);
		return ret;
	}

	if (std_id & standard)
		return TRUE;
	return FALSE;
}

/* Returns true if the specified V4L2 capture pixel format is supported by the
 * device, false or error otherwise.*/
int v4l2_is_format_supported(int fd, int format)
{
	struct v4l2_fmtdesc fmtdesc;
	int ret = 0;

	/* Check whether the requested format is supported by the device */
	memset(&fmtdesc, 0, sizeof(fmtdesc));
	fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

	do {
		ret = ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc);
		fmtdesc.index++;
		if (fmtdesc.pixelformat == format)
			break;
	} while (ret >= 0);

	if (ret == 0)
		return TRUE;

	if (ret < 0 && errno != EINVAL)
		log("VIDIOC_ENUM_FMT failed with %d\n", errno);
	return FALSE;
}

/* Check whether a specified framerate is configured in the device */
int v4l2_check_frame_rate(int fd, int framerate)
{
	struct v4l2_streamparm streamparm;

	memset(&streamparm, 0, sizeof(streamparm));
	streamparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	streamparm.parm.capture.timeperframe.numerator = 0;
	streamparm.parm.capture.timeperframe.denominator = 0;
	if (ioctl(fd, VIDIOC_G_PARM, &streamparm) < 0) {
		log("get frame rate failed\n");
		return FALSE;
	}
	log("Frame rate is %d\n", streamparm.parm.capture.timeperframe.denominator);

	if (streamparm.parm.capture.timeperframe.denominator == framerate)
		return TRUE;
	return FALSE;
}

/* Set the specified framerate on the V4L2 device */
int v4l2_set_frame_rate(int fd, int framerate)
{
	struct v4l2_streamparm streamparm;

	memset(&streamparm, 0, sizeof(streamparm));

	/* Set stream parameters */
	streamparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	streamparm.parm.capture.timeperframe.numerator = 1;
	streamparm.parm.capture.timeperframe.denominator = framerate;

	return ioctl(fd, VIDIOC_S_PARM, &streamparm);
}

/* Set the specified capture mode on the V4L2 device */
int v4l2_set_capture_mode(int fd, int capturemode)
{
	struct v4l2_streamparm streamparm;

	memset(&streamparm, 0, sizeof(streamparm));
	streamparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	if (ioctl(fd, VIDIOC_G_PARM, &streamparm) < 0) {
		log("get frame rate failed\n");
		return FALSE;
	}

	/* Set capture mode */
	streamparm.parm.capture.capturemode = capturemode;

	return ioctl(fd, VIDIOC_S_PARM, &streamparm);
}

/* Return true if the output specified by name is supported by the device,
 * false or error otherwise */
int v4l2_check_output(int fd, char *name)
{
	struct v4l2_output output;
	int ret = 0;

	/* Check what are the available video outputs */
	memset(&output, 0, sizeof(output));

	do {
		ret = ioctl(fd, VIDIOC_ENUMOUTPUT, &output);
		output.index++;
		if (ret >= 0)
			log("Supported output %s\n", output.name);
		if (!strcmp((char *)output.name, name))
			return TRUE;
	} while (ret >= 0);

	if (ret != 0 && errno != EINVAL) {
		log("VIDIOC_ENUMOUTPUT failed with %d\n", errno);
		return ret;
	}
	return FALSE;
}

/* Set the current video output */
int v4l2_set_output(int fd, struct fb_fix_screeninfo *fb_fix)
{
	struct v4l2_output output;
	int ret = 0;

	memset(&output, 0, sizeof(output));
	while ((ret = ioctl(fd, VIDIOC_ENUMOUTPUT, &output)) >= 0) {
		if (!strcmp((char *)output.name, fb_fix->id)) {
			if ((ret = ioctl(fd, VIDIOC_S_OUTPUT, &(output.index))) < 0) {
				log("Set output failed with %d\n", ret);
			}
			break;
		}
		output.index++;
	}

	return ret;
}

/* Sets a specified pixel format and scale to the V4L2 device overlay */
int v4l2_set_format_capture(int fd, int pixelformat, int height, int width)
{
	struct v4l2_format format;
	int ret = 0;

	/* Get the data format */
	if ((ret = ioctl(fd, VIDIOC_G_FMT, &format)) < 0) {
		log("Get format failed with %d\n", ret);
		return ret;
	}

	format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	format.fmt.pix.width = width;
	format.fmt.pix.height = height;
	format.fmt.pix.pixelformat = pixelformat;

	if ((ret = ioctl(fd, VIDIOC_S_FMT, &format)) < 0) {
		log("Set format failed with %d\n", ret);
	}
	log("Pixel format set to %s\n", v4l2_fmt_str(pixelformat));
	log("Sensor cropping dimensions set to H %d W %d\n", height, width);
	return ret;
}

/* Sets a specified pixel format and scale to the V4L2 device overlay */
int v4l2_set_format_overlay(int fd, int top, int left, int height, int width)
{
	struct v4l2_format format;
	int ret = 0;

	/* Get the data format */
	format.type = V4L2_BUF_TYPE_VIDEO_OVERLAY;
	if ((ret = ioctl(fd, VIDIOC_G_FMT, &format)) < 0) {
		log("Get format failed with %d\n", ret);
		return ret;
	}

	format.type = V4L2_BUF_TYPE_VIDEO_OVERLAY;
	format.fmt.win.w.top = top;
	format.fmt.win.w.left = left;
	format.fmt.win.w.width = width;
	format.fmt.win.w.height = height;

	if ((ret = ioctl(fd, VIDIOC_S_FMT, &format)) < 0) {
		log("Set format failed with %d\n", ret);
	}
	log("Overlay dimensions set to H %d W %d L %d T %d\n", height, width, left, top);
	return ret;
}

int v4l2_fb_blank(int fd)
{
	return (ioctl(fd, FBIOBLANK, FB_BLANK_NORMAL));
}

int v4l2_fb_unblank(int fd)
{
	return (ioctl(fd, FBIOBLANK, FB_BLANK_UNBLANK));
}

static int v4l2_fb_put_vinfo(int fd, struct fb_var_screeninfo *fb_var)
{
	return (ioctl(fd, FBIOPUT_VSCREENINFO, fb_var));
}

static int v4l2_fb_get_vinfo(int fd, struct fb_var_screeninfo *fb_var)
{
	return (ioctl(fd, FBIOGET_VSCREENINFO, fb_var));
}

static int v4l2_fb_get_finfo(int fd, struct fb_fix_screeninfo *fb_fix)
{
	return (ioctl(fd, FBIOGET_FSCREENINFO, fb_fix));
}

/* Retrieves fixed and variable framebuffer information */
int v4l2_fb_get_info(char *fb_device, struct fb_fix_screeninfo *fb_fix,
		     struct fb_var_screeninfo *fb_var)
{
	int ret = 0;
	int fd = 0;

	/* Open framebuffer device */
	if ((fd = open(fb_device, O_RDWR)) < 0) {
		log("Unable to open frame buffer %s\n", fb_device);
		return -EIO;
	}

	log("Opened %s\n", fb_device);

	/* Obtain fixed and variable screen info for framebuffer */
	if ((ret = v4l2_fb_get_vinfo(fd, fb_var)) < 0)
		goto out;

	if ((ret = v4l2_fb_get_finfo(fd, fb_fix)) < 0)
		goto out;

out:
	return fd;
}

/* Identifies the available outputs for a framebuffer device.
 *
 * MXC hardcoded ID strings are:
 * DISP3 BG: For background framebuffer.
 * DISP3 BG - DI1: For secondary background framebuffer.
 * DISP3 FG: For overlay (foreground) framebuffer.
 */
void v4l2_ident_outputs(int fd)
{
	struct v4l2_output output;
	int ret = 0;

	/* Identify available outputs */
	memset(&output, 0, sizeof(output));
	do {
		ret = ioctl(fd, VIDIOC_ENUMOUTPUT, &output);
		output.index++;
		log("Supported output %s\n", output.name);
	} while (ret >= 0);
}

/* Returns an opened file descriptor for the MXC overlay framebuffer device. */
int v4l2_mxc_find_overlay(struct fb_fix_screeninfo *fb_fix, int *fb_fg_fd)
{
	/* Look for overlay (foreground) framebuffer */
	if (!strcmp(fb_fix->id, "DISP3 BG")) {
		/* Overlay can be /dev/fb1 or /dev/fb2 */
		if ((*fb_fg_fd = v4l2_mxc_is_overlay("/dev/fb1")) < 0) {
			*fb_fg_fd = v4l2_mxc_is_overlay("/dev/fb2");
			log("Overlay framebuffer is /dev/fb2\n");
		} else {
			log("Overlay framebuffer is /dev/fb1\n");
		}

		if (*fb_fg_fd < 0) {
			log("No overlay framebuffer found.\n");
			return -1;
		}
	} else if (!strcmp(fb_fix->id, "DISP3 BG - DI1")) {
		/* Overlay must be /dev/fb2 */
		if ((*fb_fg_fd = v4l2_mxc_is_overlay("/dev/fb2")) < 0) {
			log("No overlay framebuffer found.\n");
			return -1;
		} else {
			log("Overlay framebuffer is /dev/fb2\n");
		}
	}

	return 0;
}

/* Sets the V4L2 device overlay framebuffer format, dimensions and type */
int v4l2_overlay_set_framebuffer(int fd, struct fb_fix_screeninfo *fb_fix,
				 struct fb_var_screeninfo *fb_var, int non_destructive)
{
	struct v4l2_framebuffer framebuffer;
	int ret = 0;

	memset(&framebuffer, 0, sizeof(framebuffer));

	framebuffer.fmt.width = fb_var->xres;
	framebuffer.fmt.height = fb_var->yres;

	if (fb_var->bits_per_pixel == 32) {
		framebuffer.fmt.pixelformat = IPU_PIX_FMT_BGR32;
		framebuffer.fmt.bytesperline = 4 * framebuffer.fmt.width;
		log("Framebuffer: BGR32\n");
	} else if (fb_var->bits_per_pixel == 24) {
		framebuffer.fmt.pixelformat = IPU_PIX_FMT_BGR24;
		framebuffer.fmt.bytesperline = 3 * framebuffer.fmt.width;
		log("Framebuffer: BGR24\n");
	} else if (fb_var->bits_per_pixel == 16) {
		framebuffer.fmt.pixelformat = IPU_PIX_FMT_RGB565;
		framebuffer.fmt.bytesperline = 2 * framebuffer.fmt.width;
		log("Framebuffer: RGB565\n");
	}

	/* Draw over the framebuffer, destructive overlay */
	if (!non_destructive) {
		framebuffer.flags = V4L2_FBUF_FLAG_PRIMARY;
		/* Physical base address of the framebuffer, that is the address of the
		 * pixel in the top left corner of the framebuffer. Only relevant
		 * for destructive video overlay. */
		framebuffer.base = (void *)fb_fix->smem_start
		    + fb_fix->line_length * fb_var->yoffset;
	} else {
		framebuffer.flags = V4L2_FBUF_FLAG_OVERLAY;
	}

	if ((ret = ioctl(fd, VIDIOC_S_FBUF, &framebuffer)) < 0) {
		log("set framebuffer failed with %d\n", errno);
		return ret;
	}

	return ret;
}

/* Reset V4L2 device cropping rectangle to its default. */
int v4l2_reset_cropping_rectangle(int fd)
{
	struct v4l2_crop crop;
	struct v4l2_cropcap cropcap;
	int ret = 0;

	memset(&cropcap, 0, sizeof(cropcap));
	memset(&crop, 0, sizeof(crop));

	cropcap.type = V4L2_BUF_TYPE_VIDEO_OVERLAY;
	if ((ret = ioctl(fd, VIDIOC_CROPCAP, &cropcap)) < 0) {
		log("VIDIOC_CROPCAP failed with %d\n", ret);
		return ret;
	}

	crop.type = V4L2_BUF_TYPE_VIDEO_OVERLAY;
	crop.c = cropcap.defrect;
	/* Ignore if cropping is not supported (EINVAL). */
	if (((ret = ioctl(fd, VIDIOC_S_CROP, &crop)) < 0)
	    && errno != EINVAL) {
		log("VIDIOC_S_CROP failed with %d\n", ret);
		return ret;
	}
	return ret;
}

int v4l2_get_cropping_limits(int fd, struct v4l2_rect *limits)
{
	int retval;
	struct v4l2_cropcap cropcap;

	cropcap.type = V4L2_BUF_TYPE_VIDEO_OVERLAY;
	if ((retval = ioctl(fd, VIDIOC_CROPCAP, &cropcap)) < 0) {
		log("VIDIOC_CROPCAP failed: %d", retval);
		return retval;
	}

	memcpy(limits, &cropcap.bounds, sizeof(cropcap.bounds));
	return 0;
}

int v4l2_crop_input(int fd, struct v4l2_rect *crop_rectangle)
{
	int retval;
	struct v4l2_crop crop;
	struct v4l2_rect crop_bounds;

	/* Sanity check */
	retval = v4l2_get_cropping_limits(fd, &crop_bounds);
	if (retval < 0)
		return retval;

	if (crop_rectangle->left + crop_rectangle->width > crop_bounds.left + crop_bounds.width ||
	    crop_rectangle->top + crop_rectangle->height > crop_bounds.top + crop_bounds.height ||
	    crop_rectangle->width < 8 || crop_rectangle->height < 8) {
		log("Invalid input\n");
		return -EINVAL;
	}

	crop.type = V4L2_BUF_TYPE_VIDEO_OVERLAY;
	memcpy(&crop.c, crop_rectangle, sizeof(crop.c));

	retval = ioctl(fd, VIDIOC_S_CROP, &crop);
	if (retval < 0) {
		log("VIDIOC_S_CROP failed: %d", retval);
		return retval;

	}

	return 0;
}

int v4l2_area_zoom(int fd, struct v4l2_rect *crop_rectangle)
{
	int retval;
	struct v4l2_rect crop_bounds;

	retval = v4l2_overlay_control(fd, 0);
	if (retval < 0)
		return retval;

	retval = v4l2_crop_input(fd, crop_rectangle);
	if (retval < 0) {
		if (retval == -EINVAL) {
			v4l2_get_cropping_limits(fd, &crop_bounds);
			printf("\nInvalid rectangle!\n");
			printf("Input bounds are:\tTop: %d\tLeft: %d\tWidth: %d\tHeight: %d",
			       crop_bounds.top, crop_bounds.left, crop_bounds.width,
			       crop_bounds.height);
			printf("\nRequested rectangle: \tTop: %d\tLeft: %d"
			       "\tWidth: %d\tHeight: %d",
			       crop_rectangle->top, crop_rectangle->left,
			       crop_rectangle->width, crop_rectangle->height);
			printf("\nValid rectangles are those which:\n"
			       "  Height >= 8\n"
			       "  Width >= 8\n"
			       "  Top + Height < Bound_Top + Bound_Height\n"
			       "  Left + Width < Bound_Left + Bound_Width\n\n");
		} else {
			return retval;
		}
	}

	retval = v4l2_overlay_control(fd, 1);
	if (retval < 0)
		return retval;

	return 0;
}

int v4l2_zoom(int fd, int zoom_percentage)
{
	int retval;
	float zoom;
	struct v4l2_rect crop_rectangle;
	struct v4l2_rect crop_bounds;

	/* Get cropping capabilities */
	retval = v4l2_get_cropping_limits(fd, &crop_bounds);
	if (retval < 0)
		return retval;

	/* Calculate new values */
	zoom = (float)zoom_percentage / 100;
	crop_rectangle.height = crop_bounds.height / zoom;
	crop_rectangle.width = crop_bounds.width / zoom;
	crop_rectangle.left = (crop_bounds.width - crop_rectangle.width) / 2;
	crop_rectangle.top = (crop_bounds.height - crop_rectangle.height) / 2;

	return v4l2_area_zoom(fd, &crop_rectangle);
}

int v4l2_set_control(int fd, int id, int value)
{
	int retval;
	struct v4l2_control control;

	control.id = id;
	control.value = value;

	retval = ioctl(fd, VIDIOC_S_CTRL, &control);
	if (retval < 0) {
		log("VIDIOC_S_CTRL failed. Id: %d\tValue: %d\n", id, value);
	}

	return retval;
}

int v4l2_get_control(int fd, int id, int *value)
{
	int retval;
	struct v4l2_control control;

	control.id = id;
	control.value = 0;
	retval = ioctl(fd, VIDIOC_G_CTRL, &control);
	if (retval < 0) {
		log("VIDIOC_G_CTRL failed. Id: %d\tValue: %d\n", id, *value);
	} else {
		*value = control.value;
	}

	return retval;
}

int v4l2_set_rotate(int fd, int rotate)
{
	/* This is not supported by V4L2, it's done by the IPUv3 driver */
	return v4l2_set_control(fd, V4L2_CID_MXC_VF_ROT, rotate);
}

int v4l2_rotate(int fd, int rotate)
{
	int retval;

	/* Stop overlay */
	retval = v4l2_overlay_control(fd, 0);
	if (retval < 0)
		return retval;

	/* Set new rotation value */
	retval = v4l2_set_rotate(fd, rotate);
	if (retval < 0 && errno != EINVAL)
		return retval;

	/* Start overlay */
	return v4l2_overlay_control(fd, 1);
}

int v4l2_set_brightness(int fd, int percentage)
{
	int retval;
	int brightness = percentage * BRIGHTNESS_MAX_VALUE / 100;

	retval = v4l2_set_control(fd, V4L2_CID_BRIGHTNESS, brightness);
	if (retval < 0) {
		log("Set control V4L2_CID_BRIGHTNESS failed. Error: %d\n", retval);
	}

	return retval;
}

int v4l2_get_brightness(int fd, int *percentage)
{
	int retval;
	int brightness = 0;

	retval = v4l2_get_control(fd, V4L2_CID_BRIGHTNESS, &brightness);
	if (retval < 0) {
		log("Get control V4L2_CID_BRIGHTNESS failed. Error: %s\n", strerror(errno));
	} else {
		/* (+ 0.5 * BRIGHTNESS_MAX_VALUE) to round integer not using libm */
		*percentage = (brightness * 100 + 0.5 * BRIGHTNESS_MAX_VALUE) / BRIGHTNESS_MAX_VALUE;
	}

	return retval;
}

int v4l2_set_saturation(int fd, int percentage)
{
	int retval;
	int saturation = 0;

	/* Valid values are 0, 25, 37, 50, 75, 100 and 150 (hardware limitation) */
	if (percentage < 0 || percentage > 150) {
		log("Invalid parameter\n");
		saturation = 50;
	} else if (percentage == 0) {
		saturation = 0;
	} else if (percentage <= 25) {
		saturation = 25;
	} else if (percentage <= 37) {
		saturation = 37;
	} else if (percentage <= 50) {
		saturation = 50;
	} else if (percentage <= 75) {
		saturation = 75;
	} else if (percentage <= 100) {
		saturation = 100;
	} else if (percentage <= 150) {
		saturation = 150;
	}

	retval = v4l2_set_control(fd, V4L2_CID_SATURATION, saturation);
	if (retval < 0) {
		log("Set control V4L2_CID_SATURATION failed. Error: %s\n", strerror(errno));
	}

	return retval;
}

int v4l2_get_saturation(int fd, int *percentage)
{
	int retval;
	int saturation = 0;

	retval = v4l2_get_control(fd, V4L2_CID_SATURATION, &saturation);
	if (retval < 0) {
		log("Get control V4L2_CID_SATURATION failed. Error: %s\n", strerror(errno));
	} else {
		*percentage = saturation;
	}

	return retval;
}

int v4l2_set_red_balance(int fd, int red)
{
	int retval;

	/* Sanity check */
	if (red > 0xFF) {
		log("Invalid parameter\n");
		errno = EINVAL;
		return -1;
	}
	retval = v4l2_set_control(fd, V4L2_CID_RED_BALANCE, red);
	if (retval < 0) {
		log("Set control V4L2_CID_RED_BALANCE failed. Error: %s\n", strerror(errno));
	}
	return retval;
}

int v4l2_set_blue_balance(int fd, int blue)
{
	int retval;

	/* Sanity check */
	if (blue > 0xFF) {
		log("Invalid parameter\n");
		errno = EINVAL;
		return -1;
	}

	/* Set blue value */
	retval = v4l2_set_control(fd, V4L2_CID_BLUE_BALANCE, blue);
	if (retval < 0) {
		log("Set control V4L2_CID_BLUE_BALANCE failed. Error: %d\n", retval);
	}

	return retval;
}

int v4l2_set_black_level(int fd, int black)
{
	int retval;

	/* Sanity check */
	if (black > 0xFF) {
		log("Invalid parameter\n");
		errno = EINVAL;
		return -1;
	}

	/* Set black value */
	retval = v4l2_set_control(fd, V4L2_CID_BLACK_LEVEL, black);
	if (retval < 0) {
		log("Set control V4L2_CID_BLACK_LEVEL failed. Error: %d\n", retval);
	}

	return retval;
}

int v4l2_get_blue_balance(int fd, int *blue)
{
	int retval;

	/* Sanity check */
	if (blue == NULL) {
		log("Invalid parameter\n");
		errno = EINVAL;
		return -1;
	}

	retval = v4l2_get_control(fd, V4L2_CID_BLUE_BALANCE, blue);
	if (retval < 0) {
		log("Get control V4L2_CID_BLUE_BALANCE failed. Error: %d\n", retval);
	}

	return retval;
}

int v4l2_get_red_balance(int fd, int *red)
{
	int retval;

	/* Sanity check */
	if (red == NULL) {
		log("Invalid parameter\n");
		errno = EINVAL;
		return -1;
	}

	retval = v4l2_get_control(fd, V4L2_CID_RED_BALANCE, red);
	if (retval < 0) {
		log("Get control V4L2_CID_RED_BALANCE failed. Error: %d\n", retval);
	}

	return retval;
}

int v4l2_get_black_level(int fd, int *black)
{
	int retval;

	/* Sanity check */
	if (black == NULL) {
		log("Invalid parameter\n");
		errno = EINVAL;
		return -1;
	}

	retval = v4l2_get_control(fd, V4L2_CID_BLACK_LEVEL, black);
	if (retval < 0) {
		log("Get control V4L2_CID_BLACK_LEVEL failed. Error: %d\n", retval);
	}

	return retval;
}

int v4l2_global_alpha_set(int fd, int enable, int percentage)
{
	int retval;
	struct mxcfb_gbl_alpha global_alpha;

	global_alpha.enable = enable;
	global_alpha.alpha = percentage * GLOBAL_ALPHA_MAX_VALUE / 100;

	retval = ioctl(fd, MXCFB_SET_GBL_ALPHA, &global_alpha);
	if (retval < 0)
		log("Set global alpha failed MXCFB_SET_GBL_ALPHA. Error: %s\n",
		    strerror(errno));

	return retval;
}

/* Set overlay background framebuffer through sysfs */
int v4l2_sysfs_set_overlay_bg(char *fb_device)
{
	int ret = 0;
	FILE *fp = NULL;

	/* +5 to remove '/dev/' */
	if (!strncmp(fb_device + 5, "fb0", 3)) {
		fp = fopen(SYSFS_FSL_DISP_PROPERTY_FB0, "w");
	} else if (!strncmp(fb_device + 5, "fb1", 3)) {
		fp = fopen(SYSFS_FSL_DISP_PROPERTY_FB1, "w");
	}

	if (fp) {
		fprintf(fp, "%s", "2-layer-fb-bg");
		fclose(fp);
	} else {
		ret = -1;
	}

	return ret;
}


void fill_alpha_buffer(char *alpha_buf, int left, int top, int right,
		       int bottom, char alpha_val, int alpha_fb_w)
{
	char *pPointAlphaValue;
	int x, y;

	for (y = top; y < bottom; y++) {
		for (x = left; x < right; x++) {
			pPointAlphaValue = (char *)(alpha_buf + alpha_fb_w * y + x);
			*pPointAlphaValue = alpha_val;
		}
	}
}

void fill_alpha_rect(char *alpha_buf, struct v4l2_rect *rect, char alpha_val, int alpha_fb_w)
{
	char *pPointAlphaValue;
	int x, y;

	for (y = rect->top; y < rect->height; y++) {
		for (x = rect->left; x < rect->width; x++) {
			pPointAlphaValue = (char *)(alpha_buf + alpha_fb_w * y + x);
			*pPointAlphaValue = alpha_val;
		}
	}
}

int v4l2_local_alpha_set(ARGUMENTS * args, int fd_fb_fg,
			 struct v4l2_rect *alpha_rectangle, int alpha_percentage)
{
	struct fb_var_screeninfo fb_fg_var;
	struct mxcfb_loc_alpha l_alpha;
	struct v4l2_rect aux_rect;	/* Used for setting the default alpha */
	unsigned long loc_alpha_phy_addr0;
	unsigned long loc_alpha_phy_addr1;
	char *alpha_buf0 = NULL;
	char *alpha_buf1 = NULL;
	int alpha_buf_size;
	int alpha_value = (100 - alpha_percentage) * LOCAL_ALPHA_MIN_VALUE / 100;

	/* Sanity check */
	if (alpha_rectangle->left + alpha_rectangle->width > args->options.width ||
	    alpha_rectangle->top + alpha_rectangle->height > args->options.height) {
		log("Invalid alpha_rectangle\n");
		return -1;
	}

	/* Switch off overlay */
	v4l2_overlay_control(args->fd_in, 0);

	/* Fetch current fb var info */
	if (v4l2_fb_get_vinfo(fd_fb_fg, &fb_fg_var) < 0) {
		log("Failed get varinfo %s\n", strerror(errno));
		close(fd_fb_fg);
		return -1;
	}

	/* Set dimensions */
	fb_fg_var.xres = args->options.width;
	fb_fg_var.yres = args->options.height;
	fb_fg_var.xres_virtual = args->options.width;
	fb_fg_var.yres_virtual = args->options.height * 2;

	if (v4l2_fb_put_vinfo(fd_fb_fg, &fb_fg_var) < 0) {
		log("Failed get varinfo %s\n", strerror(errno));
		close(fd_fb_fg);
		return -1;
	}

	/* Assure we have the correct var info */
	if (v4l2_fb_get_vinfo(fd_fb_fg, &fb_fg_var) < 0) {
		log("Failed get varinfo %s\n", strerror(errno));
		close(fd_fb_fg);
		return -1;
	}

	/* Enable local alpha */
	memset(&l_alpha, 0, sizeof(struct mxcfb_loc_alpha));
	l_alpha.enable = 1;
	l_alpha.alpha_phy_addr0 = 0;
	l_alpha.alpha_phy_addr1 = 0;
	l_alpha.alpha_in_pixel = 0;

	if (ioctl(fd_fb_fg, MXCFB_SET_LOC_ALPHA, &l_alpha) < 0) {
		log("Set local alpha failed: %s\n", strerror(errno));
		close(fd_fb_fg);
		return -1;
	}

	/* mmap the memory */
	loc_alpha_phy_addr0 = (unsigned long)(l_alpha.alpha_phy_addr0);
	loc_alpha_phy_addr1 = (unsigned long)(l_alpha.alpha_phy_addr1);

	alpha_buf_size = fb_fg_var.xres * fb_fg_var.yres;

	alpha_buf0 = (char *)mmap(0, alpha_buf_size, PROT_READ | PROT_WRITE,
				  MAP_SHARED, fd_fb_fg, loc_alpha_phy_addr0);
	if ((int)alpha_buf0 == -1) {
		log("\nError: failed to map alpha buffer 0 to memory: %s\n", strerror(errno));
		close(fd_fb_fg);
		return -1;
	}

	alpha_buf1 = (char *)mmap(0, alpha_buf_size, PROT_READ | PROT_WRITE,
				  MAP_SHARED, fd_fb_fg, loc_alpha_phy_addr1);
	if ((int)alpha_buf1 == -1) {
		log("\nError: failed to map alpha buffer 1 to memory: %s\n", strerror(errno));
		munmap((void *)alpha_buf0, alpha_buf_size);
		close(fd_fb_fg);
		return -1;
	}

	/* Switch on overlay */
	v4l2_overlay_control(args->fd_in, 1);

	/* Initialize the rectangle */
	aux_rect.top = 0;
	aux_rect.left = 0;
	aux_rect.width = args->options.width;
	aux_rect.height = args->options.height;

	fill_alpha_rect(alpha_buf0, &aux_rect, 0xFF, args->options.width);

	if (ioctl(fd_fb_fg, MXCFB_SET_LOC_ALP_BUF, &loc_alpha_phy_addr0) < 0) {
		printf("Set local alpha buf failed\n");
		close(fd_fb_fg);
		return -1;
	}

	/* Fill and display the rectangle */
	fill_alpha_rect(alpha_buf0, alpha_rectangle, alpha_value, args->options.width);

	if (ioctl(fd_fb_fg, MXCFB_SET_LOC_ALP_BUF, &loc_alpha_phy_addr0) < 0) {
		printf("Set local alpha buf failed\n");
		close(fd_fb_fg);
		return -1;
	}

	return 0;
}
