/* 2009-08-20: File added and changed by Sony Corporation */
/*
 * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved.
 */

/*
 * The code contained herein is licensed under the GNU General Public
 * License. You may obtain a copy of the GNU General Public License
 * Version 2 or later at the following locations:
 *
 * http://www.opensource.org/licenses/gpl-license.html
 * http://www.gnu.org/copyleft/gpl.html
 */

/*!
 * @file mx27_pp.c
 *
 * @brief MX27 V4L2 Video Output Driver
 *
 * Video4Linux2 Output Device using MX27 eMMA Post-processing functionality.
 *
 * @ingroup MXC_V4L2_OUTPUT
 */
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/module.h>
#include <linux/fb.h>
#include <linux/clk.h>
#include <linux/interrupt.h>
#include <asm/io.h>

#include "mx27_pp.h"
#include "mxc_v4l2_output.h"

#define SCALE_RETRY	32	/* to be more relax, less precise */
#define PP_SKIP		1
#define PP_TBL_MAX	40

static unsigned short scale_tbl[PP_TBL_MAX];
static int g_hlen, g_vlen;

static emma_pp_cfg g_pp_cfg;
static int g_disp_num = 0;
static char pp_dev[] = "emma_pp";

/*!
 * @brief PP resizing routines
 */
static int gcd(int x, int y);
static int ratio(int x, int y, int *den);
static int scale_0d(int k, int coeff, int base, int nxt);
static int scale_1d(int inv, int outv, int k);
static int scale_1d_smart(int *inv, int *outv, int index);
static int scale_2d(emma_pp_scale * sz);

static irqreturn_t pp_isr(int irq, void *dev_id);
static int set_output_addr(emma_pp_cfg * cfg, vout_data * vout);
static int pphw_reset(void);
static int pphw_enable(int flag);
static int pphw_ptr(emma_pp_cfg * cfg);
static int pphw_outptr(emma_pp_cfg * cfg);
static int pphw_cfg(emma_pp_cfg * cfg);
static int pphw_isr(void);
static void pphw_init(void);
static void pphw_exit(void);

#define PP_DUMP(reg)	pr_debug("%s\t = 0x%08X\n", #reg, __raw_readl(reg))
void pp_dump(void)
{
	PP_DUMP(PP_CNTL);
	PP_DUMP(PP_INTRCNTL);
	PP_DUMP(PP_INTRSTATUS);
	PP_DUMP(PP_SOURCE_Y_PTR);
	PP_DUMP(PP_SOURCE_CB_PTR);
	PP_DUMP(PP_SOURCE_CR_PTR);
	PP_DUMP(PP_DEST_RGB_PTR);
	PP_DUMP(PP_QUANTIZER_PTR);
	PP_DUMP(PP_PROCESS_FRAME_PARA);
	PP_DUMP(PP_SOURCE_FRAME_WIDTH);
	PP_DUMP(PP_DEST_DISPLAY_WIDTH);
	PP_DUMP(PP_DEST_IMAGE_SIZE);
	PP_DUMP(PP_DEST_FRAME_FMT_CNTL);
	PP_DUMP(PP_RESIZE_INDEX);
	PP_DUMP(PP_CSC_COEF_0123);
	PP_DUMP(PP_CSC_COEF_4);
}

static const unsigned char pp_coeftab[] = {
	2, 1,
	19, 10,
	17, 9,
	15, 8,
	13, 7,
	11, 6,
	20, 11,
	9, 5,
	16, 9,
	7, 4,
	19, 11,
	12, 7,
	17, 10,
	5, 3,
	18, 11,
	13, 8,
	8, 5,
	19, 12,
	11, 7,
	14, 9,
	17, 11,
	20, 13,
	3, 2,
	19, 13,
	16, 11,
	13, 9,
	10, 7,
	17, 12,
	7, 5,
	18, 13,
	11, 8,
	15, 11,
	19, 14,
	4, 3,
	17, 13,
	13, 10,
	9, 7,
	14, 11,
	19, 15,
	5, 4,
	16, 13,
	11, 9,
	17, 14,
	6, 5,
	19, 16,
	13, 11,
	20, 17,
	7, 6,
	15, 13,
	8, 7,
	17, 15,
	9, 8,
	19, 17,
	10, 9,
	11, 10,
	12, 11,
	13, 12,
	14, 13,
	15, 14,
	16, 15,
	17, 16,
	18, 17,
	19, 18,
	20, 19,
	1, 1,
	19, 20,
	18, 19,
	17, 18,
	16, 17,
	15, 16,
	14, 15,
	13, 14,
	12, 13,
	11, 12,
	10, 11,
	9, 10,
	17, 19,
	8, 9,
	15, 17,
	7, 8,
	13, 15,
	6, 7,
	17, 20,
	11, 13,
	16, 19,
	5, 6,
	14, 17,
	9, 11,
	13, 16,
	4, 5,
	15, 19,
	11, 14,
	7, 9,
	10, 13,
	13, 17,
	3, 4,
	14, 19,
	11, 15,
	8, 11,
	13, 18,
	5, 7,
	12, 17,
	7, 10,
	9, 13,
	11, 16,
	13, 19,
	2, 3,
	13, 20,
	11, 17,
	9, 14,
	7, 11,
	12, 19,
	5, 8,
	8, 13,
	11, 18,
	3, 5,
	10, 17,
	7, 12,
	11, 19,
	4, 7,
	9, 16,
	5, 9,
	11, 20,
	6, 11,
	7, 13,
	8, 15,
	9, 17,
	10, 19,
	1, 2,
	9, 19,
	8, 17,
	7, 15,
	6, 13,
	5, 11,
	9, 20,
	4, 9,
	7, 16,
	3, 7,
	8, 19,
	5, 12,
	7, 17,
	2, 5,
	7, 18,
	5, 13,
	3, 8,
	7, 19,
	4, 11,
	5, 14,
	6, 17,
	7, 20,
	1, 3,
	6, 19,
	5, 16,
	4, 13,
	3, 10,
	5, 17,
	2, 7,
	5, 18,
	3, 11,
	4, 15,
	5, 19,
	1, 4
};

/*!
 * @brief Set PP input address.
 * @param ptr	The pointer to the Y value of input
 * @return	Zero on success, others on failure
 */
int pp_ptr(unsigned long ptr)
{
	g_pp_cfg.ptr.y = ptr;
	g_pp_cfg.ptr.u = g_pp_cfg.ptr.v = g_pp_cfg.ptr.qp = 0;

	return pphw_ptr(&g_pp_cfg);
}

/*!
 * @brief Enable or disable PP.
 * @param flag	Zero to disable PP, others to enable PP
 * @return	Zero on success, others on failure
 */
int pp_enable(int flag)
{
	return pphw_enable(flag);
}

/*!
 * @brief Get the display No. of last completed PP frame.
 * @return	The display No. of last completed PP frame.
 */
int pp_num_last(void)
{
	return (g_disp_num ? 0 : 1);
}

/*!
 * @brief Initialize PP.
 * @param vout	Pointer to _vout_data structure
 * @return	Zero on success, others on failure
 */
int pp_init(vout_data * vout)
{
	pphw_init();
	pphw_enable(0);
	enable_irq(INT_EMMAPP);
	return request_irq(INT_EMMAPP, pp_isr, 0, pp_dev, vout);
}

/*!
 * @brief Deinitialize PP.
 * @param vout	Pointer to _vout_data structure
 */
void pp_exit(vout_data * vout)
{
	disable_irq(INT_EMMAPP);
	free_irq(INT_EMMAPP, vout);
	pphw_enable(0);
	pphw_exit();
}

/*!
 * @brief Configure PP.
 * @param vout	Pointer to _vout_data structure
 * @return	Zero on success, others on failure
 */
int pp_cfg(vout_data * vout)
{
	if (!vout)
		return -1;

	/* PP accepts YUV420 input only */
	if (vout->v2f.fmt.pix.pixelformat != V4L2_PIX_FMT_YUV420) {
		pr_debug("unsupported pixel format.\n");
		return -1;
	}

	g_pp_cfg.operation = 0;

	memset(g_pp_cfg.csc_table, 0, sizeof(g_pp_cfg.csc_table));

	/* Convert output pixel format to PP required format */
	switch (vout->v4l2_fb.fmt.pixelformat) {
	case V4L2_PIX_FMT_BGR32:
		g_pp_cfg.red_width = 8;
		g_pp_cfg.green_width = 8;
		g_pp_cfg.blue_width = 8;
		g_pp_cfg.red_offset = 8;
		g_pp_cfg.green_offset = 16;
		g_pp_cfg.blue_offset = 24;
		g_pp_cfg.rgb_resolution = 32;
		break;
	case V4L2_PIX_FMT_RGB32:
		g_pp_cfg.red_width = 8;
		g_pp_cfg.green_width = 8;
		g_pp_cfg.blue_width = 8;
		g_pp_cfg.red_offset = 24;
		g_pp_cfg.green_offset = 16;
		g_pp_cfg.blue_offset = 8;
		g_pp_cfg.rgb_resolution = 32;
		break;
	case V4L2_PIX_FMT_YUYV:
		g_pp_cfg.red_width = 0;
		g_pp_cfg.green_width = 0;
		g_pp_cfg.blue_width = 0;
		g_pp_cfg.red_offset = 0;
		g_pp_cfg.green_offset = 0;
		g_pp_cfg.blue_offset = PP_PIX_YUYV;
		g_pp_cfg.rgb_resolution = 16;
		break;
	case V4L2_PIX_FMT_UYVY:
		g_pp_cfg.red_width = 0;
		g_pp_cfg.green_width = 0;
		g_pp_cfg.blue_width = 0;
		g_pp_cfg.red_offset = 0;
		g_pp_cfg.green_offset = 0;
		g_pp_cfg.blue_offset = PP_PIX_UYVY;
		g_pp_cfg.rgb_resolution = 16;
		break;
	case V4L2_PIX_FMT_RGB565:
	default:
		g_pp_cfg.red_width = 5;
		g_pp_cfg.green_width = 6;
		g_pp_cfg.blue_width = 5;
		g_pp_cfg.red_offset = 11;
		g_pp_cfg.green_offset = 5;
		g_pp_cfg.blue_offset = 0;
		g_pp_cfg.rgb_resolution = 16;
		break;
	}

	if (vout->ipu_buf[0] != -1)
		g_pp_cfg.ptr.y =
		    (unsigned int)vout->queue_buf_paddr[vout->ipu_buf[0]];
	else
		g_pp_cfg.ptr.y = 0;

	g_pp_cfg.ptr.u = g_pp_cfg.ptr.v = g_pp_cfg.ptr.qp = 0;

	g_pp_cfg.dim.in.width = vout->v2f.fmt.pix.width;
	g_pp_cfg.dim.in.height = vout->v2f.fmt.pix.height;
	g_pp_cfg.dim.out.width = vout->crop_current.width;
	g_pp_cfg.dim.out.height = vout->crop_current.height;
	g_pp_cfg.dim.num.width = 0;
	g_pp_cfg.dim.num.height = 0;
	g_pp_cfg.dim.den.width = 0;
	g_pp_cfg.dim.den.height = 0;

	if (scale_2d(&g_pp_cfg.dim)) {
		pr_debug("unsupported resize ratio.\n");
		return -1;
	}

	g_pp_cfg.dim.out.width = vout->crop_current.width;
	g_pp_cfg.dim.out.height = vout->crop_current.height;

	g_pp_cfg.in_y_stride = 0;
	if (set_output_addr(&g_pp_cfg, vout)) {
		pr_debug("failed to set pp output address.\n");
		return -1;
	}

	return pphw_cfg(&g_pp_cfg);
}

irqreturn_t mxc_v4l2out_pp_in_irq_handler(int irq, void *dev_id);

/*!
 * @brief PP IRQ handler.
 */
static irqreturn_t pp_isr(int irq, void *dev_id)
{
	int status;
	vout_data *vout = dev_id;

	status = pphw_isr();
	if ((status & 0x1) == 0) {	/* Not frame complete interrupt */
		pr_debug("not pp frame complete interrupt\n");
		return IRQ_HANDLED;
	}

	if (vout->v4l2_fb.flags == V4L2_FBUF_FLAG_OVERLAY) {
		g_disp_num = g_disp_num ? 0 : 1;
		g_pp_cfg.outptr = (unsigned int)vout->display_bufs[g_disp_num];
		pphw_outptr(&g_pp_cfg);
	}

	return mxc_v4l2out_pp_in_irq_handler(irq, dev_id);
}

/*!
 * @brief Set PP output address.
 * @param cfg	Pointer to emma_pp_cfg structure
 * @param vout	Pointer to _vout_data structure
 * @return	Zero on success, others on failure
 */
static int set_output_addr(emma_pp_cfg * cfg, vout_data * vout)
{
	if (vout->v4l2_fb.flags == V4L2_FBUF_FLAG_OVERLAY) {
		g_disp_num = 0;
		cfg->outptr = (unsigned int)vout->display_bufs[g_disp_num];
		cfg->out_stride = vout->crop_current.width;
		return 0;
	} else {
		struct fb_info *fb;

		fb = registered_fb[vout->output_fb_num[vout->cur_disp_output]];
		if (!fb)
			return -1;

		cfg->outptr = fb->fix.smem_start;
		cfg->outptr += vout->crop_current.top * fb->var.xres_virtual
		    * (fb->var.bits_per_pixel >> 3)
		    + vout->crop_current.left * (fb->var.bits_per_pixel >> 3);
		cfg->out_stride = fb->var.xres_virtual;

		return 0;
	}
}

/*!
 * @brief Get maximum common divisor.
 * @param x	First input value
 * @param y	Second input value
 * @return	Maximum common divisor of x and y
 */
static int gcd(int x, int y)
{
	int k;

	if (x < y) {
		k = x;
		x = y;
		y = k;
	}

	while ((k = x % y)) {
		x = y;
		y = k;
	}

	return y;
}

/*!
 * @brief Get ratio.
 * @param x	First input value
 * @param y	Second input value
 * @param den	Denominator of the ratio (corresponding to y)
 * @return	Numerator of the ratio (corresponding to x)
 */
static int ratio(int x, int y, int *den)
{
	int g;

	if (!x || !y)
		return 0;

	g = gcd(x, y);
	*den = y / g;

	return x / g;
}

/*!
 * @brief Build PP coefficient entry
 * Build one or more coefficient entries for PP coefficient table based
 * on given coefficient.
 *
 * @param k	The index of the coefficient in coefficient table
 * @param coeff	The weighting coefficient
 * @param base	The base of the coefficient
 * @param nxt	Number of pixels to be read
 *
 * @return	The index of the next coefficient entry on success
 *		-1 on failure
 */
static int scale_0d(int k, int coeff, int base, int nxt)
{
	if (k >= PP_TBL_MAX) {
		/* no more space in table */
		pr_debug("no space in scale table, k = %d\n", k);
		return -1;
	}

	coeff = ((coeff << BC_COEF) + (base >> 1)) / base;

	/*
	 * Valid values for weighting coefficient are 0, 2 to 30, and 31.
	 * A value of 31 is treated as 32 and therefore 31 is an
	 * invalid co-efficient.
	 */
	if (coeff >= SZ_COEF - 1)
		coeff--;
	else if (coeff == 1)
		coeff++;
	coeff = coeff << BC_NXT;

	if (nxt < SZ_NXT) {
		coeff |= nxt;
		coeff <<= 1;
		coeff |= 1;
	} else {
		/*
		 * src inc field is 2 bit wide, for 4+, use special
		 * code 0:0:1 to prevent dest inc
		 */
		coeff |= PP_SKIP;
		coeff <<= 1;
		coeff |= 1;
		nxt -= PP_SKIP;
		do {
			pr_debug("tbl = %03X\n", coeff);
			scale_tbl[k++] = coeff;
			coeff = (nxt > PP_SKIP) ? PP_SKIP : nxt;
			coeff <<= 1;
		} while ((nxt -= PP_SKIP) > 0);
	}
	pr_debug("tbl = %03X\n", coeff);
	scale_tbl[k++] = coeff;

	return k;
}

/*!
 * @brief Get approximate ratio
 *
 * @param pscale	The pointer to scale_t structure which holdes
 * 			coefficient tables
 * @param mt		Scale ratio numerator
 * @param nt		Scale ratio denominator
 * @param *n		denominator of approximate ratio
 * @return		numerator of approximate ratio
 */
static int approx_ratio(int mt, int nt, int *n)
{
	int index = sizeof(pp_coeftab) / sizeof(pp_coeftab[0]) / 2;
	int left = 0;
	int right = index - 1;
	int nom = 0, den = 0;
	while (index > 0) {
		nom = pp_coeftab[(((right + left) >> 1) << 1)];
		den = pp_coeftab[(((right + left) >> 1) << 1) + 1];
		if ((nom * nt - mt * den) > 0) {
			left = (right + left) >> 1;
		} else {
			right = (right + left) >> 1;
		}
		index = index >> 1;
	}
	*n = pp_coeftab[right * 2 + 1];
	nom = pp_coeftab[right * 2];
	return nom;
}

/*
 * @brief Build PP coefficient table
 * Build PP coefficient table for one dimension (width or height)
 * based on given input and output resolution
 *
 * @param inv	input resolution
 * @param outv	output resolution
 * @param k	index of free table entry
 *
 * @return	The index of the next free coefficient entry on success
 *		-1 on failure
 */
static int scale_1d(int inv, int outv, int k)
{
	int v;			/* overflow counter */
	int coeff, nxt;		/* table output */

	if (inv == outv)
		return scale_0d(k, 1, 1, 1);	/* force scaling */

	v = 0;
	if (inv < outv) {
		/* upscale: mix <= 2 input pixels per output pixel */
		do {
			coeff = outv - v;
			v += inv;
			if (v >= outv) {
				v -= outv;
				nxt = 1;
			} else
				nxt = 0;
			pr_debug("upscale: coeff = %d/%d nxt = %d\n", coeff,
				 outv, nxt);
			k = scale_0d(k, coeff, outv, nxt);
			if (k < 0)
				return -1;
		} while (v);
	} else if (inv >= 2 * outv) {
		/* PP doesn't support resize ratio > 2:1 except 4:1. */
		if ((inv != 2 * outv) && (inv != 4 * outv))
			return -1;
		/* downscale: >=2:1 bilinear approximation */
		coeff = inv - 2 * outv;
		v = 0;
		nxt = 0;
		do {
			v += coeff;
			nxt = 2;
			while (v >= outv) {
				v -= outv;
				nxt++;
			}
			pr_debug("downscale: coeff = 1/2 nxt = %d\n", nxt);
			k = scale_0d(k, 1, 2, nxt);
			if (k < 0)
				return -1;
		} while (v);
	} else {
		/* downscale: bilinear */
		int in_pos_inc = 2 * outv;
		int out_pos = inv;
		int out_pos_inc = 2 * inv;
		int init_carry = inv - outv;
		int carry = init_carry;

		v = outv + in_pos_inc;
		do {
			coeff = v - out_pos;
			out_pos += out_pos_inc;
			carry += out_pos_inc;
			for (nxt = 0; v < out_pos; nxt++) {
				v += in_pos_inc;
				carry -= in_pos_inc;
			}
			pr_debug("downscale: coeff = %d/%d nxt = %d\n", coeff,
				 in_pos_inc, nxt);
			k = scale_0d(k, coeff, in_pos_inc, nxt);
			if (k < 0)
				return -1;
		} while (carry != init_carry);
	}
	return k;
}

/*
 * @brief Build PP coefficient table
 * Build PP coefficient table for one dimension (width or height)
 * based on given input and output resolution. The given input
 * and output resolution might be not supported due to hardware
 * limits. In this case this functin rounds the input and output
 * to closest possible values and return them to caller.
 *
 * @param inv	input resolution, might be modified after the call
 * @param outv	output resolution, might be modified after the call
 * @param k	index of free table entry
 *
 * @return	The index of the next free coefficient entry on success
 *		-1 on failure
 */
static int scale_1d_smart(int *inv, int *outv, int index)
{
	int len, num, den, approx_num, approx_den;
	static int num1, den1;

	if (!inv || !outv)
		return -1;

	/* Both should be non-zero */
	if (!(*inv) || !(*outv))
		return -1;

	if ((*outv > 4 * (*inv))
	    || ((*inv > 2 * (*outv)) && (*inv != 4 * (*outv)))) {
		pr_debug("unsupported pp resize ration: inv/outv = %d/%d\n",
			 *inv, *outv);
		return -1;
	}

	num = ratio(*inv, *outv, &den);

	if (index == 0) {
		if ((num > 20) || (den > 20)) {
			approx_num = approx_ratio(num, den, &approx_den);
			num = approx_num;
			den = approx_den;
		}
	} else {
		if ((num > (40 - index)) || (den > (40 - index))) {
			approx_num = approx_ratio(num, den, &approx_den);
			num = approx_num;
			den = approx_den;
		}
		if ((num == num1) && (den == den1)) {
			return index;
		}
	}

	if ((len = scale_1d(num, den, index)) < 0) {
		return -1;
	} else {
		if (index == 0) {
			num1 = num;
			den1 = den;
		}
		return len;
	}

}

/*
 * @brief Build PP coefficient table for both width and height
 * Build PP coefficient table for both width and height based on
 * given resizing ratios.
 *
 * @param sz	Structure contains resizing ratio informations
 *
 * @return	0 on success, others on failure
 */
static int scale_2d(emma_pp_scale * sz)
{
	int inv, outv;

	/* horizontal resizing. parameter check - must provide in size */
	if (!sz->in.width)
		return -1;

	/* Resizing based on num:den */
	inv = sz->num.width;
	outv = sz->den.width;

	if ((g_hlen = scale_1d_smart(&inv, &outv, 0)) > 0) {
		/* Resizing succeeded */
		sz->den.width = outv;
		sz->out.width = (sz->in.width * outv) / inv;
	} else {
		/* Resizing based on in:out */
		inv = sz->in.width;
		outv = sz->out.width;

		if ((g_hlen = scale_1d_smart(&inv, &outv, 0)) > 0) {
			/* Resizing succeeded */
			sz->out.width = outv;
			sz->num.width = ratio(sz->in.width, sz->out.width,
					      &sz->den.width);
		} else
			return -1;
	}

	sz->out.width &= ~1;

	/* vertical resizing. parameter check - must provide in size */
	if (!sz->in.height)
		return -1;

	/* Resizing based on num:den */
	inv = sz->num.height;
	outv = sz->den.height;

	if ((g_vlen = scale_1d_smart(&inv, &outv, g_hlen)) > 0) {
		/* Resizing succeeded */
		sz->den.height = outv;
		sz->out.height = (sz->in.height * outv) / inv;
	} else {
		/* Resizing based on in:out */
		inv = sz->in.height;
		outv = sz->out.height;

		if ((g_vlen = scale_1d_smart(&inv, &outv, g_hlen)) > 0) {
			/* Resizing succeeded */
			sz->out.height = outv;
			sz->num.height = ratio(sz->in.height, sz->out.height,
					       &sz->den.height);
		} else
			return -1;
	}

	return 0;
}

/*!
 * @brief Set PP resizing registers.
 * @param sz	Pointer to pp scaling structure
 * @return	Zero on success, others on failure
 */
static int pphw_scale(emma_pp_scale * sz)
{
	__raw_writel((sz->out.width << 16) | sz->out.height,
		     PP_DEST_IMAGE_SIZE);
	__raw_writel(((g_hlen - 1) << 16) | (g_vlen ==
					     g_hlen ? 0 : (g_hlen << 8)) |
		     (g_vlen - 1), PP_RESIZE_INDEX);
	for (g_hlen = 0; g_hlen < g_vlen; g_hlen++)
		__raw_writel(scale_tbl[g_hlen],
			     PP_RESIZE_COEF_TBL + g_hlen * 4);

	return 0;
}

/*!
 * @brief Reset PP.
 * @return	Zero on success, others on failure
 */
static int pphw_reset(void)
{
	int i;

	__raw_writel(0x100, PP_CNTL);

	/* timeout */
	for (i = 0; i < 1000; i++) {
		if (!(__raw_readl(PP_CNTL) & 0x100)) {
			pr_debug("pp reset over\n");
			break;
		}
	}

	/* check reset value */
	if (__raw_readl(PP_CNTL) != 0x876) {
		pr_debug("pp reset value err = 0x%08X\n", __raw_readl(PP_CNTL));
		return -1;
	}

	return 0;
}

/*!
 * @brief Enable or disable PP.
 * @param flag	Zero to disable PP, others to enable PP
 * @return	Zero on success, others on failure
 */
static int pphw_enable(int flag)
{
	int ret = 0;

	if (flag)
		__raw_writel(__raw_readl(PP_CNTL) | 1, PP_CNTL);
	else
		ret = pphw_reset();

	return ret;
}

/*!
 * @brief Set PP input address.
 * @param cfg	The pointer to PP configuration parameter
 * @return	Zero on success, others on failure
 */
static int pphw_ptr(emma_pp_cfg * cfg)
{
	if (!cfg->ptr.u) {
		int size;

		/* yuv - packed */
		size = PP_CALC_Y_SIZE(cfg);
		cfg->ptr.u = cfg->ptr.y + size;
		cfg->ptr.v = cfg->ptr.u + (size >> 2);

		/* yuv packed with qp appended */
		if (!cfg->ptr.qp)
			cfg->ptr.qp = cfg->ptr.v + (size >> 2);
	}
	__raw_writel(cfg->ptr.y, PP_SOURCE_Y_PTR);
	__raw_writel(cfg->ptr.u, PP_SOURCE_CB_PTR);
	__raw_writel(cfg->ptr.v, PP_SOURCE_CR_PTR);
	__raw_writel(cfg->ptr.qp, PP_QUANTIZER_PTR);

	return 0;
}

/*!
 * @brief Set PP output address.
 * @param cfg	The pointer to PP configuration parameter
 * @return	Zero on success, others on failure
 */
static int pphw_outptr(emma_pp_cfg * cfg)
{
	__raw_writel(cfg->outptr, PP_DEST_RGB_PTR);
	return 0;
}

/*!
 * @brief Configuration PP.
 * @param cfg	The pointer to PP configuration parameter
 * @return	Zero on success, others on failure
 */
static int pphw_cfg(emma_pp_cfg * cfg)
{
	int rt;
	register int r;

	pphw_scale(&cfg->dim);

	if (!cfg->in_y_stride)
		cfg->in_y_stride = cfg->dim.in.width;

	if (!cfg->out_stride)
		cfg->out_stride = cfg->dim.out.width;

	r = __raw_readl(PP_CNTL) & ~EN_MASK;

	/* config parms */
	r |= cfg->operation & EN_MASK;
	if (cfg->operation & EN_MACROBLOCK) {
		/* Macroblock Mode */
		r |= 0x0200;
		__raw_writel(0x06, PP_INTRCNTL);
	} else {
		/* Frame mode */
		__raw_writel(0x05, PP_INTRCNTL);
	}

	if (cfg->red_width | cfg->green_width | cfg->blue_width) {
		/* color conversion to be performed */
		r |= EN_CSC;
		if (!(cfg->red_offset | cfg->green_offset)) {
			/* auto offset B:G:R LSb to Msb */
			cfg->green_offset = cfg->blue_offset + cfg->blue_width;
			cfg->red_offset = cfg->green_offset + cfg->green_width;
		}
		if (!cfg->rgb_resolution) {
			/* derive minimum resolution required */
			int w, w2;

			w = cfg->red_offset + cfg->red_width;
			w2 = cfg->blue_offset + cfg->blue_width;
			if (w < w2)
				w = w2;
			w2 = cfg->green_offset + cfg->green_width;
			if (w < w2)
				w = w2;
			if (w > 16)
				w = 24;
			else if (w > 8)
				w = 16;
			else
				w = 8;
			cfg->rgb_resolution = w;
		}
		/* 00,11 - 32 bpp, 10 - 16 bpp, 01 - 8 bpp */
		r &= ~0xC00;
		if (cfg->rgb_resolution < 32)
			r |= (cfg->rgb_resolution << 7);
		__raw_writel((cfg->red_offset << 26) |
			     (cfg->green_offset << 21) |
			     (cfg->blue_offset << 16) |
			     (cfg->red_width << 8) |
			     (cfg->green_width << 4) |
			     cfg->blue_width, PP_DEST_FRAME_FMT_CNTL);
	} else {
		/* add YUV422 formatting */
		static const unsigned int _422[] = {
			0x62000888,
			0x60100888,
			0x43080888,
			0x41180888
		};

		__raw_writel(_422[(cfg->blue_offset >> 3) & 3],
			     PP_DEST_FRAME_FMT_CNTL);
		cfg->rgb_resolution = 16;
		r &= ~0xC00;
		r |= (cfg->rgb_resolution << 7);
	}

	/* add csc formatting */
	if (!cfg->csc_table[1]) {
		static const unsigned short _csc[][6] = {
			{0x80, 0xb4, 0x2c, 0x5b, 0x0e4, 0},
			{0x95, 0xcc, 0x32, 0x68, 0x104, 1},
			{0x80, 0xca, 0x18, 0x3c, 0x0ec, 0},
			{0x95, 0xe5, 0x1b, 0x44, 0x10e, 1},
		};
		memcpy(cfg->csc_table, _csc[cfg->csc_table[0]],
		       sizeof(_csc[0]));
	}
	__raw_writel((cfg->csc_table[0] << 24) |
		     (cfg->csc_table[1] << 16) |
		     (cfg->csc_table[2] << 8) |
		     cfg->csc_table[3], PP_CSC_COEF_0123);
	__raw_writel((cfg->csc_table[5] ? (1 << 9) : 0) | cfg->csc_table[4],
		     PP_CSC_COEF_4);

	__raw_writel(r, PP_CNTL);

	pphw_ptr(cfg);
	pphw_outptr(cfg);

	/*
	 * #MB in a row = input_width / 16pix
	 * 1 byte per QP per MB
	 * QP must be formatted to be 4-byte aligned
	 * YUV lines are to be 4-byte aligned as well
	 * So Y is 8 byte aligned, as U = V = Y/2 for 420
	 * MPEG MBs are 16x16 anyway
	 */
	__raw_writel((cfg->dim.in.width << 16) | cfg->dim.in.height,
		     PP_PROCESS_FRAME_PARA);
	__raw_writel(cfg->in_y_stride | (PP_CALC_QP_WIDTH(cfg) << 16),
		     PP_SOURCE_FRAME_WIDTH);

	/* in bytes */
	rt = cfg->rgb_resolution >> 3;
	if (rt == 3)
		rt = 4;
	__raw_writel(cfg->out_stride * rt, PP_DEST_DISPLAY_WIDTH);

	pp_dump();
	return 0;
}

/*!
 * @brief Check PP interrupt status.
 * @return	PP interrupt status
 */
static int pphw_isr(void)
{
	unsigned long status;

	pr_debug("pp: in isr.\n");
	status = __raw_readl(PP_INTRSTATUS) & 7;
	if (!status) {
		pr_debug("pp: not my isr err.\n");
		return status;
	}

	if (status & 4)
		pr_debug("pp: isr state error.\n");

	/* clear interrupt status */
	__raw_writel(status, PP_INTRSTATUS);

	return status;
}

static struct clk *emma_clk;

/*!
 * @brief PP module clock enable
 */
static void pphw_init(void)
{
	emma_clk = clk_get(NULL, "emma_clk");
	clk_enable(emma_clk);
}

/*!
 * @brief PP module clock disable
 */
static void pphw_exit(void)
{
	clk_disable(emma_clk);
	clk_put(emma_clk);
}
