diff options
Diffstat (limited to 'drivers/media/platform/davinci')
23 files changed, 17032 insertions, 0 deletions
diff --git a/drivers/media/platform/davinci/Kconfig b/drivers/media/platform/davinci/Kconfig new file mode 100644 index 00000000000..afb3aec1320 --- /dev/null +++ b/drivers/media/platform/davinci/Kconfig @@ -0,0 +1,78 @@ +config VIDEO_DAVINCI_VPIF_DISPLAY +	tristate "TI DaVinci VPIF V4L2-Display driver" +	depends on VIDEO_DEV && ARCH_DAVINCI +	select VIDEOBUF2_DMA_CONTIG +	select VIDEO_ADV7343 if MEDIA_SUBDRV_AUTOSELECT +	select VIDEO_THS7303 if MEDIA_SUBDRV_AUTOSELECT +	help +	  Enables Davinci VPIF module used for display devices. +	  This module is used for display on TI DM6467/DA850/OMAPL138 +	  SoCs. + +	  To compile this driver as a module, choose M here. There will +	  be two modules called vpif.ko and vpif_display.ko + +config VIDEO_DAVINCI_VPIF_CAPTURE +	tristate "TI DaVinci VPIF video capture driver" +	depends on VIDEO_DEV && ARCH_DAVINCI +	select VIDEOBUF2_DMA_CONTIG +	help +	  Enables Davinci VPIF module used for capture devices. +	  This module is used for capture on TI DM6467/DA850/OMAPL138 +	  SoCs. + +	  To compile this driver as a module, choose M here. There will +	  be two modules called vpif.ko and vpif_capture.ko + +config VIDEO_DM6446_CCDC +	tristate "TI DM6446 CCDC video capture driver" +	depends on VIDEO_V4L2 && (ARCH_DAVINCI || ARCH_OMAP3) +	select VIDEOBUF_DMA_CONTIG +	help +	   Enables DaVinci CCD hw module. DaVinci CCDC hw interfaces +	   with decoder modules such as TVP5146 over BT656 or +	   sensor module such as MT9T001 over a raw interface. This +	   module configures the interface and CCDC/ISIF to do +	   video frame capture from slave decoders. + +	   To compile this driver as a module, choose M here. There will +	   be three modules called vpfe_capture.ko, vpss.ko and dm644x_ccdc.ko + +config VIDEO_DM355_CCDC +	tristate "TI DM355 CCDC video capture driver" +	depends on VIDEO_V4L2 && ARCH_DAVINCI +	select VIDEOBUF_DMA_CONTIG +	help +	   Enables DM355 CCD hw module. DM355 CCDC hw interfaces +	   with decoder modules such as TVP5146 over BT656 or +	   sensor module such as MT9T001 over a raw interface. This +	   module configures the interface and CCDC/ISIF to do +	   video frame capture from a slave decoders + +	   To compile this driver as a module, choose M here. There will +	   be three modules called vpfe_capture.ko, vpss.ko and dm355_ccdc.ko + +config VIDEO_DM365_ISIF +	tristate "TI DM365 ISIF video capture driver" +	depends on VIDEO_V4L2 && ARCH_DAVINCI +	select VIDEOBUF_DMA_CONTIG +	help +	   Enables ISIF hw module. This is the hardware module for +	   configuring ISIF in VPFE to capture Raw Bayer RGB data from +	   a image sensor or YUV data from a YUV source. + +	   To compile this driver as a module, choose M here. There will +	   be three modules called vpfe_capture.ko, vpss.ko and isif.ko + +config VIDEO_DAVINCI_VPBE_DISPLAY +	tristate "TI DaVinci VPBE V4L2-Display driver" +	depends on ARCH_DAVINCI +	select VIDEOBUF2_DMA_CONTIG +	help +	    Enables Davinci VPBE module used for display devices. +	    This module is used for display on TI DM644x/DM365/DM355 +	    based display devices. + +	    To compile this driver as a module, choose M here. There will +	    be five modules created called vpss.ko, vpbe.ko, vpbe_osd.ko, +	    vpbe_venc.ko and vpbe_display.ko diff --git a/drivers/media/platform/davinci/Makefile b/drivers/media/platform/davinci/Makefile new file mode 100644 index 00000000000..d74d9eeb0e9 --- /dev/null +++ b/drivers/media/platform/davinci/Makefile @@ -0,0 +1,15 @@ +# +# Makefile for the davinci video device drivers. +# + +#VPIF Display driver +obj-$(CONFIG_VIDEO_DAVINCI_VPIF_DISPLAY) += vpif.o vpif_display.o +#VPIF Capture driver +obj-$(CONFIG_VIDEO_DAVINCI_VPIF_CAPTURE) += vpif.o vpif_capture.o + +# Capture: DM6446 and DM355 +obj-$(CONFIG_VIDEO_DM6446_CCDC) += vpfe_capture.o vpss.o dm644x_ccdc.o +obj-$(CONFIG_VIDEO_DM355_CCDC) += vpfe_capture.o vpss.o dm355_ccdc.o +obj-$(CONFIG_VIDEO_DM365_ISIF) += vpfe_capture.o vpss.o isif.o +obj-$(CONFIG_VIDEO_DAVINCI_VPBE_DISPLAY) += vpss.o vpbe.o vpbe_osd.o \ +	vpbe_venc.o vpbe_display.o diff --git a/drivers/media/platform/davinci/ccdc_hw_device.h b/drivers/media/platform/davinci/ccdc_hw_device.h new file mode 100644 index 00000000000..86b9b351896 --- /dev/null +++ b/drivers/media/platform/davinci/ccdc_hw_device.h @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2008-2009 Texas Instruments Inc + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA + * + * ccdc device API + */ +#ifndef _CCDC_HW_DEVICE_H +#define _CCDC_HW_DEVICE_H + +#ifdef __KERNEL__ +#include <linux/videodev2.h> +#include <linux/device.h> +#include <media/davinci/vpfe_types.h> +#include <media/davinci/ccdc_types.h> + +/* + * ccdc hw operations + */ +struct ccdc_hw_ops { +	/* Pointer to initialize function to initialize ccdc device */ +	int (*open) (struct device *dev); +	/* Pointer to deinitialize function */ +	int (*close) (struct device *dev); +	/* set ccdc base address */ +	void (*set_ccdc_base)(void *base, int size); +	/* Pointer to function to enable or disable ccdc */ +	void (*enable) (int en); +	/* reset sbl. only for 6446 */ +	void (*reset) (void); +	/* enable output to sdram */ +	void (*enable_out_to_sdram) (int en); +	/* Pointer to function to set hw parameters */ +	int (*set_hw_if_params) (struct vpfe_hw_if_param *param); +	/* get interface parameters */ +	int (*get_hw_if_params) (struct vpfe_hw_if_param *param); +	/* +	 * Pointer to function to set parameters. Used +	 * for implementing VPFE_S_CCDC_PARAMS +	 */ +	int (*set_params) (void *params); +	/* +	 * Pointer to function to get parameter. Used +	 * for implementing VPFE_G_CCDC_PARAMS +	 */ +	int (*get_params) (void *params); +	/* Pointer to function to configure ccdc */ +	int (*configure) (void); + +	/* Pointer to function to set buffer type */ +	int (*set_buftype) (enum ccdc_buftype buf_type); +	/* Pointer to function to get buffer type */ +	enum ccdc_buftype (*get_buftype) (void); +	/* Pointer to function to set frame format */ +	int (*set_frame_format) (enum ccdc_frmfmt frm_fmt); +	/* Pointer to function to get frame format */ +	enum ccdc_frmfmt (*get_frame_format) (void); +	/* enumerate hw pix formats */ +	int (*enum_pix)(u32 *hw_pix, int i); +	/* Pointer to function to set buffer type */ +	u32 (*get_pixel_format) (void); +	/* Pointer to function to get pixel format. */ +	int (*set_pixel_format) (u32 pixfmt); +	/* Pointer to function to set image window */ +	int (*set_image_window) (struct v4l2_rect *win); +	/* Pointer to function to set image window */ +	void (*get_image_window) (struct v4l2_rect *win); +	/* Pointer to function to get line length */ +	unsigned int (*get_line_length) (void); + +	/* Query CCDC control IDs */ +	int (*queryctrl)(struct v4l2_queryctrl *qctrl); +	/* Set CCDC control */ +	int (*set_control)(struct v4l2_control *ctrl); +	/* Get CCDC control */ +	int (*get_control)(struct v4l2_control *ctrl); + +	/* Pointer to function to set frame buffer address */ +	void (*setfbaddr) (unsigned long addr); +	/* Pointer to function to get field id */ +	int (*getfid) (void); +}; + +struct ccdc_hw_device { +	/* ccdc device name */ +	char name[32]; +	/* module owner */ +	struct module *owner; +	/* hw ops */ +	struct ccdc_hw_ops hw_ops; +}; + +/* Used by CCDC module to register & unregister with vpfe capture driver */ +int vpfe_register_ccdc_device(struct ccdc_hw_device *dev); +void vpfe_unregister_ccdc_device(struct ccdc_hw_device *dev); + +#endif +#endif diff --git a/drivers/media/platform/davinci/dm355_ccdc.c b/drivers/media/platform/davinci/dm355_ccdc.c new file mode 100644 index 00000000000..05f8fb7f7b7 --- /dev/null +++ b/drivers/media/platform/davinci/dm355_ccdc.c @@ -0,0 +1,1039 @@ +/* + * Copyright (C) 2005-2009 Texas Instruments Inc + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA + * + * CCDC hardware module for DM355 + * ------------------------------ + * + * This module is for configuring DM355 CCD controller of VPFE to capture + * Raw yuv or Bayer RGB data from a decoder. CCDC has several modules + * such as Defect Pixel Correction, Color Space Conversion etc to + * pre-process the Bayer RGB data, before writing it to SDRAM. This + * module also allows application to configure individual + * module parameters through VPFE_CMD_S_CCDC_RAW_PARAMS IOCTL. + * To do so, application include dm355_ccdc.h and vpfe_capture.h header + * files. The setparams() API is called by vpfe_capture driver + * to configure module parameters + * + * TODO: 1) Raw bayer parameter settings and bayer capture + * 	 2) Split module parameter structure to module specific ioctl structs + *	 3) add support for lense shading correction + *	 4) investigate if enum used for user space type definition + * 	    to be replaced by #defines or integer + */ +#include <linux/platform_device.h> +#include <linux/uaccess.h> +#include <linux/videodev2.h> +#include <linux/err.h> +#include <linux/module.h> + +#include <media/davinci/dm355_ccdc.h> +#include <media/davinci/vpss.h> + +#include "dm355_ccdc_regs.h" +#include "ccdc_hw_device.h" + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("CCDC Driver for DM355"); +MODULE_AUTHOR("Texas Instruments"); + +static struct ccdc_oper_config { +	struct device *dev; +	/* CCDC interface type */ +	enum vpfe_hw_if_type if_type; +	/* Raw Bayer configuration */ +	struct ccdc_params_raw bayer; +	/* YCbCr configuration */ +	struct ccdc_params_ycbcr ycbcr; +	/* ccdc base address */ +	void __iomem *base_addr; +} ccdc_cfg = { +	/* Raw configurations */ +	.bayer = { +		.pix_fmt = CCDC_PIXFMT_RAW, +		.frm_fmt = CCDC_FRMFMT_PROGRESSIVE, +		.win = CCDC_WIN_VGA, +		.fid_pol = VPFE_PINPOL_POSITIVE, +		.vd_pol = VPFE_PINPOL_POSITIVE, +		.hd_pol = VPFE_PINPOL_POSITIVE, +		.gain = { +			.r_ye = 256, +			.gb_g = 256, +			.gr_cy = 256, +			.b_mg = 256 +		}, +		.config_params = { +			.datasft = 2, +			.mfilt1 = CCDC_NO_MEDIAN_FILTER1, +			.mfilt2 = CCDC_NO_MEDIAN_FILTER2, +			.alaw = { +				.gamma_wd = 2, +			}, +			.blk_clamp = { +				.sample_pixel = 1, +				.dc_sub = 25 +			}, +			.col_pat_field0 = { +				.olop = CCDC_GREEN_BLUE, +				.olep = CCDC_BLUE, +				.elop = CCDC_RED, +				.elep = CCDC_GREEN_RED +			}, +			.col_pat_field1 = { +				.olop = CCDC_GREEN_BLUE, +				.olep = CCDC_BLUE, +				.elop = CCDC_RED, +				.elep = CCDC_GREEN_RED +			}, +		}, +	}, +	/* YCbCr configuration */ +	.ycbcr = { +		.win = CCDC_WIN_PAL, +		.pix_fmt = CCDC_PIXFMT_YCBCR_8BIT, +		.frm_fmt = CCDC_FRMFMT_INTERLACED, +		.fid_pol = VPFE_PINPOL_POSITIVE, +		.vd_pol = VPFE_PINPOL_POSITIVE, +		.hd_pol = VPFE_PINPOL_POSITIVE, +		.bt656_enable = 1, +		.pix_order = CCDC_PIXORDER_CBYCRY, +		.buf_type = CCDC_BUFTYPE_FLD_INTERLEAVED +	}, +}; + + +/* Raw Bayer formats */ +static u32 ccdc_raw_bayer_pix_formats[] = +		{V4L2_PIX_FMT_SBGGR8, V4L2_PIX_FMT_SBGGR16}; + +/* Raw YUV formats */ +static u32 ccdc_raw_yuv_pix_formats[] = +		{V4L2_PIX_FMT_UYVY, V4L2_PIX_FMT_YUYV}; + +/* register access routines */ +static inline u32 regr(u32 offset) +{ +	return __raw_readl(ccdc_cfg.base_addr + offset); +} + +static inline void regw(u32 val, u32 offset) +{ +	__raw_writel(val, ccdc_cfg.base_addr + offset); +} + +static void ccdc_enable(int en) +{ +	unsigned int temp; +	temp = regr(SYNCEN); +	temp &= (~CCDC_SYNCEN_VDHDEN_MASK); +	temp |= (en & CCDC_SYNCEN_VDHDEN_MASK); +	regw(temp, SYNCEN); +} + +static void ccdc_enable_output_to_sdram(int en) +{ +	unsigned int temp; +	temp = regr(SYNCEN); +	temp &= (~(CCDC_SYNCEN_WEN_MASK)); +	temp |= ((en << CCDC_SYNCEN_WEN_SHIFT) & CCDC_SYNCEN_WEN_MASK); +	regw(temp, SYNCEN); +} + +static void ccdc_config_gain_offset(void) +{ +	/* configure gain */ +	regw(ccdc_cfg.bayer.gain.r_ye, RYEGAIN); +	regw(ccdc_cfg.bayer.gain.gr_cy, GRCYGAIN); +	regw(ccdc_cfg.bayer.gain.gb_g, GBGGAIN); +	regw(ccdc_cfg.bayer.gain.b_mg, BMGGAIN); +	/* configure offset */ +	regw(ccdc_cfg.bayer.ccdc_offset, OFFSET); +} + +/* + * ccdc_restore_defaults() + * This function restore power on defaults in the ccdc registers + */ +static int ccdc_restore_defaults(void) +{ +	int i; + +	dev_dbg(ccdc_cfg.dev, "\nstarting ccdc_restore_defaults..."); +	/* set all registers to zero */ +	for (i = 0; i <= CCDC_REG_LAST; i += 4) +		regw(0, i); + +	/* now override the values with power on defaults in registers */ +	regw(MODESET_DEFAULT, MODESET); +	/* no culling support */ +	regw(CULH_DEFAULT, CULH); +	regw(CULV_DEFAULT, CULV); +	/* Set default Gain and Offset */ +	ccdc_cfg.bayer.gain.r_ye = GAIN_DEFAULT; +	ccdc_cfg.bayer.gain.gb_g = GAIN_DEFAULT; +	ccdc_cfg.bayer.gain.gr_cy = GAIN_DEFAULT; +	ccdc_cfg.bayer.gain.b_mg = GAIN_DEFAULT; +	ccdc_config_gain_offset(); +	regw(OUTCLIP_DEFAULT, OUTCLIP); +	regw(LSCCFG2_DEFAULT, LSCCFG2); +	/* select ccdc input */ +	if (vpss_select_ccdc_source(VPSS_CCDCIN)) { +		dev_dbg(ccdc_cfg.dev, "\ncouldn't select ccdc input source"); +		return -EFAULT; +	} +	/* select ccdc clock */ +	if (vpss_enable_clock(VPSS_CCDC_CLOCK, 1) < 0) { +		dev_dbg(ccdc_cfg.dev, "\ncouldn't enable ccdc clock"); +		return -EFAULT; +	} +	dev_dbg(ccdc_cfg.dev, "\nEnd of ccdc_restore_defaults..."); +	return 0; +} + +static int ccdc_open(struct device *device) +{ +	return ccdc_restore_defaults(); +} + +static int ccdc_close(struct device *device) +{ +	/* disable clock */ +	vpss_enable_clock(VPSS_CCDC_CLOCK, 0); +	/* do nothing for now */ +	return 0; +} +/* + * ccdc_setwin() + * This function will configure the window size to + * be capture in CCDC reg. + */ +static void ccdc_setwin(struct v4l2_rect *image_win, +			enum ccdc_frmfmt frm_fmt, int ppc) +{ +	int horz_start, horz_nr_pixels; +	int vert_start, vert_nr_lines; +	int mid_img = 0; + +	dev_dbg(ccdc_cfg.dev, "\nStarting ccdc_setwin..."); + +	/* +	 * ppc - per pixel count. indicates how many pixels per cell +	 * output to SDRAM. example, for ycbcr, it is one y and one c, so 2. +	 * raw capture this is 1 +	 */ +	horz_start = image_win->left << (ppc - 1); +	horz_nr_pixels = ((image_win->width) << (ppc - 1)) - 1; + +	/* Writing the horizontal info into the registers */ +	regw(horz_start, SPH); +	regw(horz_nr_pixels, NPH); +	vert_start = image_win->top; + +	if (frm_fmt == CCDC_FRMFMT_INTERLACED) { +		vert_nr_lines = (image_win->height >> 1) - 1; +		vert_start >>= 1; +		/* Since first line doesn't have any data */ +		vert_start += 1; +		/* configure VDINT0 and VDINT1 */ +		regw(vert_start, VDINT0); +	} else { +		/* Since first line doesn't have any data */ +		vert_start += 1; +		vert_nr_lines = image_win->height - 1; +		/* configure VDINT0 and VDINT1 */ +		mid_img = vert_start + (image_win->height / 2); +		regw(vert_start, VDINT0); +		regw(mid_img, VDINT1); +	} +	regw(vert_start & CCDC_START_VER_ONE_MASK, SLV0); +	regw(vert_start & CCDC_START_VER_TWO_MASK, SLV1); +	regw(vert_nr_lines & CCDC_NUM_LINES_VER, NLV); +	dev_dbg(ccdc_cfg.dev, "\nEnd of ccdc_setwin..."); +} + +static int validate_ccdc_param(struct ccdc_config_params_raw *ccdcparam) +{ +	if (ccdcparam->datasft < CCDC_DATA_NO_SHIFT || +	    ccdcparam->datasft > CCDC_DATA_SHIFT_6BIT) { +		dev_dbg(ccdc_cfg.dev, "Invalid value of data shift\n"); +		return -EINVAL; +	} + +	if (ccdcparam->mfilt1 < CCDC_NO_MEDIAN_FILTER1 || +	    ccdcparam->mfilt1 > CCDC_MEDIAN_FILTER1) { +		dev_dbg(ccdc_cfg.dev, "Invalid value of median filter1\n"); +		return -EINVAL; +	} + +	if (ccdcparam->mfilt2 < CCDC_NO_MEDIAN_FILTER2 || +	    ccdcparam->mfilt2 > CCDC_MEDIAN_FILTER2) { +		dev_dbg(ccdc_cfg.dev, "Invalid value of median filter2\n"); +		return -EINVAL; +	} + +	if ((ccdcparam->med_filt_thres < 0) || +	   (ccdcparam->med_filt_thres > CCDC_MED_FILT_THRESH)) { +		dev_dbg(ccdc_cfg.dev, +			"Invalid value of median filter threshold\n"); +		return -EINVAL; +	} + +	if (ccdcparam->data_sz < CCDC_DATA_16BITS || +	    ccdcparam->data_sz > CCDC_DATA_8BITS) { +		dev_dbg(ccdc_cfg.dev, "Invalid value of data size\n"); +		return -EINVAL; +	} + +	if (ccdcparam->alaw.enable) { +		if (ccdcparam->alaw.gamma_wd < CCDC_GAMMA_BITS_13_4 || +		    ccdcparam->alaw.gamma_wd > CCDC_GAMMA_BITS_09_0) { +			dev_dbg(ccdc_cfg.dev, "Invalid value of ALAW\n"); +			return -EINVAL; +		} +	} + +	if (ccdcparam->blk_clamp.b_clamp_enable) { +		if (ccdcparam->blk_clamp.sample_pixel < CCDC_SAMPLE_1PIXELS || +		    ccdcparam->blk_clamp.sample_pixel > CCDC_SAMPLE_16PIXELS) { +			dev_dbg(ccdc_cfg.dev, +				"Invalid value of sample pixel\n"); +			return -EINVAL; +		} +		if (ccdcparam->blk_clamp.sample_ln < CCDC_SAMPLE_1LINES || +		    ccdcparam->blk_clamp.sample_ln > CCDC_SAMPLE_16LINES) { +			dev_dbg(ccdc_cfg.dev, +				"Invalid value of sample lines\n"); +			return -EINVAL; +		} +	} +	return 0; +} + +/* Parameter operations */ +static int ccdc_set_params(void __user *params) +{ +	struct ccdc_config_params_raw ccdc_raw_params; +	int x; + +	/* only raw module parameters can be set through the IOCTL */ +	if (ccdc_cfg.if_type != VPFE_RAW_BAYER) +		return -EINVAL; + +	x = copy_from_user(&ccdc_raw_params, params, sizeof(ccdc_raw_params)); +	if (x) { +		dev_dbg(ccdc_cfg.dev, "ccdc_set_params: error in copying ccdc" +			"params, %d\n", x); +		return -EFAULT; +	} + +	if (!validate_ccdc_param(&ccdc_raw_params)) { +		memcpy(&ccdc_cfg.bayer.config_params, +			&ccdc_raw_params, +			sizeof(ccdc_raw_params)); +		return 0; +	} +	return -EINVAL; +} + +/* This function will configure CCDC for YCbCr video capture */ +static void ccdc_config_ycbcr(void) +{ +	struct ccdc_params_ycbcr *params = &ccdc_cfg.ycbcr; +	u32 temp; + +	/* first set the CCDC power on defaults values in all registers */ +	dev_dbg(ccdc_cfg.dev, "\nStarting ccdc_config_ycbcr..."); +	ccdc_restore_defaults(); + +	/* configure pixel format & video frame format */ +	temp = (((params->pix_fmt & CCDC_INPUT_MODE_MASK) << +		CCDC_INPUT_MODE_SHIFT) | +		((params->frm_fmt & CCDC_FRM_FMT_MASK) << +		CCDC_FRM_FMT_SHIFT)); + +	/* setup BT.656 sync mode */ +	if (params->bt656_enable) { +		regw(CCDC_REC656IF_BT656_EN, REC656IF); +		/* +		 * configure the FID, VD, HD pin polarity fld,hd pol positive, +		 * vd negative, 8-bit pack mode +		 */ +		temp |= CCDC_VD_POL_NEGATIVE; +	} else {		/* y/c external sync mode */ +		temp |= (((params->fid_pol & CCDC_FID_POL_MASK) << +			CCDC_FID_POL_SHIFT) | +			((params->hd_pol & CCDC_HD_POL_MASK) << +			CCDC_HD_POL_SHIFT) | +			((params->vd_pol & CCDC_VD_POL_MASK) << +			CCDC_VD_POL_SHIFT)); +	} + +	/* pack the data to 8-bit */ +	temp |= CCDC_DATA_PACK_ENABLE; + +	regw(temp, MODESET); + +	/* configure video window */ +	ccdc_setwin(¶ms->win, params->frm_fmt, 2); + +	/* configure the order of y cb cr in SD-RAM */ +	temp = (params->pix_order << CCDC_Y8POS_SHIFT); +	temp |= CCDC_LATCH_ON_VSYNC_DISABLE | CCDC_CCDCFG_FIDMD_NO_LATCH_VSYNC; +	regw(temp, CCDCFG); + +	/* +	 * configure the horizontal line offset. This is done by rounding up +	 * width to a multiple of 16 pixels and multiply by two to account for +	 * y:cb:cr 4:2:2 data +	 */ +	regw(((params->win.width * 2 + 31) >> 5), HSIZE); + +	/* configure the memory line offset */ +	if (params->buf_type == CCDC_BUFTYPE_FLD_INTERLEAVED) { +		/* two fields are interleaved in memory */ +		regw(CCDC_SDOFST_FIELD_INTERLEAVED, SDOFST); +	} + +	dev_dbg(ccdc_cfg.dev, "\nEnd of ccdc_config_ycbcr...\n"); +} + +/* + * ccdc_config_black_clamp() + * configure parameters for Optical Black Clamp + */ +static void ccdc_config_black_clamp(struct ccdc_black_clamp *bclamp) +{ +	u32 val; + +	if (!bclamp->b_clamp_enable) { +		/* configure DCSub */ +		regw(bclamp->dc_sub & CCDC_BLK_DC_SUB_MASK, DCSUB); +		regw(0x0000, CLAMP); +		return; +	} +	/* Enable the Black clamping, set sample lines and pixels */ +	val = (bclamp->start_pixel & CCDC_BLK_ST_PXL_MASK) | +	      ((bclamp->sample_pixel & CCDC_BLK_SAMPLE_LN_MASK) << +		CCDC_BLK_SAMPLE_LN_SHIFT) | CCDC_BLK_CLAMP_ENABLE; +	regw(val, CLAMP); + +	/* If Black clamping is enable then make dcsub 0 */ +	val = (bclamp->sample_ln & CCDC_NUM_LINE_CALC_MASK) +			<< CCDC_NUM_LINE_CALC_SHIFT; +	regw(val, DCSUB); +} + +/* + * ccdc_config_black_compense() + * configure parameters for Black Compensation + */ +static void ccdc_config_black_compense(struct ccdc_black_compensation *bcomp) +{ +	u32 val; + +	val = (bcomp->b & CCDC_BLK_COMP_MASK) | +		((bcomp->gb & CCDC_BLK_COMP_MASK) << +		CCDC_BLK_COMP_GB_COMP_SHIFT); +	regw(val, BLKCMP1); + +	val = ((bcomp->gr & CCDC_BLK_COMP_MASK) << +		CCDC_BLK_COMP_GR_COMP_SHIFT) | +		((bcomp->r & CCDC_BLK_COMP_MASK) << +		CCDC_BLK_COMP_R_COMP_SHIFT); +	regw(val, BLKCMP0); +} + +/* + * ccdc_write_dfc_entry() + * write an entry in the dfc table. + */ +int ccdc_write_dfc_entry(int index, struct ccdc_vertical_dft *dfc) +{ +/* TODO This is to be re-visited and adjusted */ +#define DFC_WRITE_WAIT_COUNT	1000 +	u32 val, count = DFC_WRITE_WAIT_COUNT; + +	regw(dfc->dft_corr_vert[index], DFCMEM0); +	regw(dfc->dft_corr_horz[index], DFCMEM1); +	regw(dfc->dft_corr_sub1[index], DFCMEM2); +	regw(dfc->dft_corr_sub2[index], DFCMEM3); +	regw(dfc->dft_corr_sub3[index], DFCMEM4); +	/* set WR bit to write */ +	val = regr(DFCMEMCTL) | CCDC_DFCMEMCTL_DFCMWR_MASK; +	regw(val, DFCMEMCTL); + +	/* +	 * Assume, it is very short. If we get an error, we need to +	 * adjust this value +	 */ +	while (regr(DFCMEMCTL) & CCDC_DFCMEMCTL_DFCMWR_MASK) +		count--; +	/* +	 * TODO We expect the count to be non-zero to be successful. Adjust +	 * the count if write requires more time +	 */ + +	if (count) { +		dev_err(ccdc_cfg.dev, "defect table write timeout !!!\n"); +		return -1; +	} +	return 0; +} + +/* + * ccdc_config_vdfc() + * configure parameters for Vertical Defect Correction + */ +static int ccdc_config_vdfc(struct ccdc_vertical_dft *dfc) +{ +	u32 val; +	int i; + +	/* Configure General Defect Correction. The table used is from IPIPE */ +	val = dfc->gen_dft_en & CCDC_DFCCTL_GDFCEN_MASK; + +	/* Configure Vertical Defect Correction if needed */ +	if (!dfc->ver_dft_en) { +		/* Enable only General Defect Correction */ +		regw(val, DFCCTL); +		return 0; +	} + +	if (dfc->table_size > CCDC_DFT_TABLE_SIZE) +		return -EINVAL; + +	val |= CCDC_DFCCTL_VDFC_DISABLE; +	val |= (dfc->dft_corr_ctl.vdfcsl & CCDC_DFCCTL_VDFCSL_MASK) << +		CCDC_DFCCTL_VDFCSL_SHIFT; +	val |= (dfc->dft_corr_ctl.vdfcuda & CCDC_DFCCTL_VDFCUDA_MASK) << +		CCDC_DFCCTL_VDFCUDA_SHIFT; +	val |= (dfc->dft_corr_ctl.vdflsft & CCDC_DFCCTL_VDFLSFT_MASK) << +		CCDC_DFCCTL_VDFLSFT_SHIFT; +	regw(val , DFCCTL); + +	/* clear address ptr to offset 0 */ +	val = CCDC_DFCMEMCTL_DFCMARST_MASK << CCDC_DFCMEMCTL_DFCMARST_SHIFT; + +	/* write defect table entries */ +	for (i = 0; i < dfc->table_size; i++) { +		/* increment address for non zero index */ +		if (i != 0) +			val = CCDC_DFCMEMCTL_INC_ADDR; +		regw(val, DFCMEMCTL); +		if (ccdc_write_dfc_entry(i, dfc) < 0) +			return -EFAULT; +	} + +	/* update saturation level and enable dfc */ +	regw(dfc->saturation_ctl & CCDC_VDC_DFCVSAT_MASK, DFCVSAT); +	val = regr(DFCCTL) | (CCDC_DFCCTL_VDFCEN_MASK << +			CCDC_DFCCTL_VDFCEN_SHIFT); +	regw(val, DFCCTL); +	return 0; +} + +/* + * ccdc_config_csc() + * configure parameters for color space conversion + * Each register CSCM0-7 has two values in S8Q5 format. + */ +static void ccdc_config_csc(struct ccdc_csc *csc) +{ +	u32 val1 = 0, val2; +	int i; + +	if (!csc->enable) +		return; + +	/* Enable the CSC sub-module */ +	regw(CCDC_CSC_ENABLE, CSCCTL); + +	/* Converting the co-eff as per the format of the register */ +	for (i = 0; i < CCDC_CSC_COEFF_TABLE_SIZE; i++) { +		if ((i % 2) == 0) { +			/* CSCM - LSB */ +			val1 = (csc->coeff[i].integer & +				CCDC_CSC_COEF_INTEG_MASK) +				<< CCDC_CSC_COEF_INTEG_SHIFT; +			/* +			 * convert decimal part to binary. Use 2 decimal +			 * precision, user values range from .00 - 0.99 +			 */ +			val1 |= (((csc->coeff[i].decimal & +				CCDC_CSC_COEF_DECIMAL_MASK) * +				CCDC_CSC_DEC_MAX) / 100); +		} else { + +			/* CSCM - MSB */ +			val2 = (csc->coeff[i].integer & +				CCDC_CSC_COEF_INTEG_MASK) +				<< CCDC_CSC_COEF_INTEG_SHIFT; +			val2 |= (((csc->coeff[i].decimal & +				 CCDC_CSC_COEF_DECIMAL_MASK) * +				 CCDC_CSC_DEC_MAX) / 100); +			val2 <<= CCDC_CSCM_MSB_SHIFT; +			val2 |= val1; +			regw(val2, (CSCM0 + ((i - 1) << 1))); +		} +	} +} + +/* + * ccdc_config_color_patterns() + * configure parameters for color patterns + */ +static void ccdc_config_color_patterns(struct ccdc_col_pat *pat0, +				       struct ccdc_col_pat *pat1) +{ +	u32 val; + +	val = (pat0->olop | (pat0->olep << 2) | (pat0->elop << 4) | +		(pat0->elep << 6) | (pat1->olop << 8) | (pat1->olep << 10) | +		(pat1->elop << 12) | (pat1->elep << 14)); +	regw(val, COLPTN); +} + +/* This function will configure CCDC for Raw mode image capture */ +static int ccdc_config_raw(void) +{ +	struct ccdc_params_raw *params = &ccdc_cfg.bayer; +	struct ccdc_config_params_raw *config_params = +					&ccdc_cfg.bayer.config_params; +	unsigned int val; + +	dev_dbg(ccdc_cfg.dev, "\nStarting ccdc_config_raw..."); + +	/* restore power on defaults to register */ +	ccdc_restore_defaults(); + +	/* CCDCFG register: +	 * set CCD Not to swap input since input is RAW data +	 * set FID detection function to Latch at V-Sync +	 * set WENLOG - ccdc valid area to AND +	 * set TRGSEL to WENBIT +	 * set EXTRG to DISABLE +	 * disable latching function on VSYNC - shadowed registers +	 */ +	regw(CCDC_YCINSWP_RAW | CCDC_CCDCFG_FIDMD_LATCH_VSYNC | +	     CCDC_CCDCFG_WENLOG_AND | CCDC_CCDCFG_TRGSEL_WEN | +	     CCDC_CCDCFG_EXTRG_DISABLE | CCDC_LATCH_ON_VSYNC_DISABLE, CCDCFG); + +	/* +	 * Set VDHD direction to input,  input type to raw input +	 * normal data polarity, do not use external WEN +	 */ +	val = (CCDC_VDHDOUT_INPUT | CCDC_RAW_IP_MODE | CCDC_DATAPOL_NORMAL | +		CCDC_EXWEN_DISABLE); + +	/* +	 * Configure the vertical sync polarity (MODESET.VDPOL), horizontal +	 * sync polarity (MODESET.HDPOL), field id polarity (MODESET.FLDPOL), +	 * frame format(progressive or interlace), & pixel format (Input mode) +	 */ +	val |= (((params->vd_pol & CCDC_VD_POL_MASK) << CCDC_VD_POL_SHIFT) | +		((params->hd_pol & CCDC_HD_POL_MASK) << CCDC_HD_POL_SHIFT) | +		((params->fid_pol & CCDC_FID_POL_MASK) << CCDC_FID_POL_SHIFT) | +		((params->frm_fmt & CCDC_FRM_FMT_MASK) << CCDC_FRM_FMT_SHIFT) | +		((params->pix_fmt & CCDC_PIX_FMT_MASK) << CCDC_PIX_FMT_SHIFT)); + +	/* set pack for alaw compression */ +	if ((config_params->data_sz == CCDC_DATA_8BITS) || +	     config_params->alaw.enable) +		val |= CCDC_DATA_PACK_ENABLE; + +	/* Configure for LPF */ +	if (config_params->lpf_enable) +		val |= (config_params->lpf_enable & CCDC_LPF_MASK) << +			CCDC_LPF_SHIFT; + +	/* Configure the data shift */ +	val |= (config_params->datasft & CCDC_DATASFT_MASK) << +		CCDC_DATASFT_SHIFT; +	regw(val , MODESET); +	dev_dbg(ccdc_cfg.dev, "\nWriting 0x%x to MODESET...\n", val); + +	/* Configure the Median Filter threshold */ +	regw((config_params->med_filt_thres) & CCDC_MED_FILT_THRESH, MEDFILT); + +	/* Configure GAMMAWD register. defaur 11-2, and Mosaic cfa pattern */ +	val = CCDC_GAMMA_BITS_11_2 << CCDC_GAMMAWD_INPUT_SHIFT | +		CCDC_CFA_MOSAIC; + +	/* Enable and configure aLaw register if needed */ +	if (config_params->alaw.enable) { +		val |= (CCDC_ALAW_ENABLE | +			((config_params->alaw.gamma_wd & +			CCDC_ALAW_GAMMA_WD_MASK) << +			CCDC_GAMMAWD_INPUT_SHIFT)); +	} + +	/* Configure Median filter1 & filter2 */ +	val |= ((config_params->mfilt1 << CCDC_MFILT1_SHIFT) | +		(config_params->mfilt2 << CCDC_MFILT2_SHIFT)); + +	regw(val, GAMMAWD); +	dev_dbg(ccdc_cfg.dev, "\nWriting 0x%x to GAMMAWD...\n", val); + +	/* configure video window */ +	ccdc_setwin(¶ms->win, params->frm_fmt, 1); + +	/* Optical Clamp Averaging */ +	ccdc_config_black_clamp(&config_params->blk_clamp); + +	/* Black level compensation */ +	ccdc_config_black_compense(&config_params->blk_comp); + +	/* Vertical Defect Correction if needed */ +	if (ccdc_config_vdfc(&config_params->vertical_dft) < 0) +		return -EFAULT; + +	/* color space conversion */ +	ccdc_config_csc(&config_params->csc); + +	/* color pattern */ +	ccdc_config_color_patterns(&config_params->col_pat_field0, +				   &config_params->col_pat_field1); + +	/* Configure the Gain  & offset control */ +	ccdc_config_gain_offset(); + +	dev_dbg(ccdc_cfg.dev, "\nWriting %x to COLPTN...\n", val); + +	/* Configure DATAOFST  register */ +	val = (config_params->data_offset.horz_offset & CCDC_DATAOFST_MASK) << +		CCDC_DATAOFST_H_SHIFT; +	val |= (config_params->data_offset.vert_offset & CCDC_DATAOFST_MASK) << +		CCDC_DATAOFST_V_SHIFT; +	regw(val, DATAOFST); + +	/* configuring HSIZE register */ +	val = (params->horz_flip_enable & CCDC_HSIZE_FLIP_MASK) << +		CCDC_HSIZE_FLIP_SHIFT; + +	/* If pack 8 is enable then 1 pixel will take 1 byte */ +	if ((config_params->data_sz == CCDC_DATA_8BITS) || +	     config_params->alaw.enable) { +		val |= (((params->win.width) + 31) >> 5) & +			CCDC_HSIZE_VAL_MASK; + +		/* adjust to multiple of 32 */ +		dev_dbg(ccdc_cfg.dev, "\nWriting 0x%x to HSIZE...\n", +		       (((params->win.width) + 31) >> 5) & +			CCDC_HSIZE_VAL_MASK); +	} else { +		/* else one pixel will take 2 byte */ +		val |= (((params->win.width * 2) + 31) >> 5) & +			CCDC_HSIZE_VAL_MASK; + +		dev_dbg(ccdc_cfg.dev, "\nWriting 0x%x to HSIZE...\n", +		       (((params->win.width * 2) + 31) >> 5) & +			CCDC_HSIZE_VAL_MASK); +	} +	regw(val, HSIZE); + +	/* Configure SDOFST register */ +	if (params->frm_fmt == CCDC_FRMFMT_INTERLACED) { +		if (params->image_invert_enable) { +			/* For interlace inverse mode */ +			regw(CCDC_SDOFST_INTERLACE_INVERSE, SDOFST); +			dev_dbg(ccdc_cfg.dev, "\nWriting %x to SDOFST...\n", +				CCDC_SDOFST_INTERLACE_INVERSE); +		} else { +			/* For interlace non inverse mode */ +			regw(CCDC_SDOFST_INTERLACE_NORMAL, SDOFST); +			dev_dbg(ccdc_cfg.dev, "\nWriting %x to SDOFST...\n", +				CCDC_SDOFST_INTERLACE_NORMAL); +		} +	} else if (params->frm_fmt == CCDC_FRMFMT_PROGRESSIVE) { +		if (params->image_invert_enable) { +			/* For progessive inverse mode */ +			regw(CCDC_SDOFST_PROGRESSIVE_INVERSE, SDOFST); +			dev_dbg(ccdc_cfg.dev, "\nWriting %x to SDOFST...\n", +				CCDC_SDOFST_PROGRESSIVE_INVERSE); +		} else { +			/* For progessive non inverse mode */ +			regw(CCDC_SDOFST_PROGRESSIVE_NORMAL, SDOFST); +			dev_dbg(ccdc_cfg.dev, "\nWriting %x to SDOFST...\n", +				CCDC_SDOFST_PROGRESSIVE_NORMAL); +		} +	} +	dev_dbg(ccdc_cfg.dev, "\nend of ccdc_config_raw..."); +	return 0; +} + +static int ccdc_configure(void) +{ +	if (ccdc_cfg.if_type == VPFE_RAW_BAYER) +		return ccdc_config_raw(); +	else +		ccdc_config_ycbcr(); +	return 0; +} + +static int ccdc_set_buftype(enum ccdc_buftype buf_type) +{ +	if (ccdc_cfg.if_type == VPFE_RAW_BAYER) +		ccdc_cfg.bayer.buf_type = buf_type; +	else +		ccdc_cfg.ycbcr.buf_type = buf_type; +	return 0; +} +static enum ccdc_buftype ccdc_get_buftype(void) +{ +	if (ccdc_cfg.if_type == VPFE_RAW_BAYER) +		return ccdc_cfg.bayer.buf_type; +	return ccdc_cfg.ycbcr.buf_type; +} + +static int ccdc_enum_pix(u32 *pix, int i) +{ +	int ret = -EINVAL; +	if (ccdc_cfg.if_type == VPFE_RAW_BAYER) { +		if (i < ARRAY_SIZE(ccdc_raw_bayer_pix_formats)) { +			*pix = ccdc_raw_bayer_pix_formats[i]; +			ret = 0; +		} +	} else { +		if (i < ARRAY_SIZE(ccdc_raw_yuv_pix_formats)) { +			*pix = ccdc_raw_yuv_pix_formats[i]; +			ret = 0; +		} +	} +	return ret; +} + +static int ccdc_set_pixel_format(u32 pixfmt) +{ +	struct ccdc_a_law *alaw = &ccdc_cfg.bayer.config_params.alaw; + +	if (ccdc_cfg.if_type == VPFE_RAW_BAYER) { +		ccdc_cfg.bayer.pix_fmt = CCDC_PIXFMT_RAW; +		if (pixfmt == V4L2_PIX_FMT_SBGGR8) +			alaw->enable = 1; +		else if (pixfmt != V4L2_PIX_FMT_SBGGR16) +			return -EINVAL; +	} else { +		if (pixfmt == V4L2_PIX_FMT_YUYV) +			ccdc_cfg.ycbcr.pix_order = CCDC_PIXORDER_YCBYCR; +		else if (pixfmt == V4L2_PIX_FMT_UYVY) +			ccdc_cfg.ycbcr.pix_order = CCDC_PIXORDER_CBYCRY; +		else +			return -EINVAL; +	} +	return 0; +} +static u32 ccdc_get_pixel_format(void) +{ +	struct ccdc_a_law *alaw = &ccdc_cfg.bayer.config_params.alaw; +	u32 pixfmt; + +	if (ccdc_cfg.if_type == VPFE_RAW_BAYER) +		if (alaw->enable) +			pixfmt = V4L2_PIX_FMT_SBGGR8; +		else +			pixfmt = V4L2_PIX_FMT_SBGGR16; +	else { +		if (ccdc_cfg.ycbcr.pix_order == CCDC_PIXORDER_YCBYCR) +			pixfmt = V4L2_PIX_FMT_YUYV; +		else +			pixfmt = V4L2_PIX_FMT_UYVY; +	} +	return pixfmt; +} +static int ccdc_set_image_window(struct v4l2_rect *win) +{ +	if (ccdc_cfg.if_type == VPFE_RAW_BAYER) +		ccdc_cfg.bayer.win = *win; +	else +		ccdc_cfg.ycbcr.win = *win; +	return 0; +} + +static void ccdc_get_image_window(struct v4l2_rect *win) +{ +	if (ccdc_cfg.if_type == VPFE_RAW_BAYER) +		*win = ccdc_cfg.bayer.win; +	else +		*win = ccdc_cfg.ycbcr.win; +} + +static unsigned int ccdc_get_line_length(void) +{ +	struct ccdc_config_params_raw *config_params = +				&ccdc_cfg.bayer.config_params; +	unsigned int len; + +	if (ccdc_cfg.if_type == VPFE_RAW_BAYER) { +		if ((config_params->alaw.enable) || +		    (config_params->data_sz == CCDC_DATA_8BITS)) +			len = ccdc_cfg.bayer.win.width; +		else +			len = ccdc_cfg.bayer.win.width * 2; +	} else +		len = ccdc_cfg.ycbcr.win.width * 2; +	return ALIGN(len, 32); +} + +static int ccdc_set_frame_format(enum ccdc_frmfmt frm_fmt) +{ +	if (ccdc_cfg.if_type == VPFE_RAW_BAYER) +		ccdc_cfg.bayer.frm_fmt = frm_fmt; +	else +		ccdc_cfg.ycbcr.frm_fmt = frm_fmt; +	return 0; +} + +static enum ccdc_frmfmt ccdc_get_frame_format(void) +{ +	if (ccdc_cfg.if_type == VPFE_RAW_BAYER) +		return ccdc_cfg.bayer.frm_fmt; +	else +		return ccdc_cfg.ycbcr.frm_fmt; +} + +static int ccdc_getfid(void) +{ +	return  (regr(MODESET) >> 15) & 1; +} + +/* misc operations */ +static inline void ccdc_setfbaddr(unsigned long addr) +{ +	regw((addr >> 21) & 0x007f, STADRH); +	regw((addr >> 5) & 0x0ffff, STADRL); +} + +static int ccdc_set_hw_if_params(struct vpfe_hw_if_param *params) +{ +	ccdc_cfg.if_type = params->if_type; + +	switch (params->if_type) { +	case VPFE_BT656: +	case VPFE_YCBCR_SYNC_16: +	case VPFE_YCBCR_SYNC_8: +		ccdc_cfg.ycbcr.vd_pol = params->vdpol; +		ccdc_cfg.ycbcr.hd_pol = params->hdpol; +		break; +	default: +		/* TODO add support for raw bayer here */ +		return -EINVAL; +	} +	return 0; +} + +static struct ccdc_hw_device ccdc_hw_dev = { +	.name = "DM355 CCDC", +	.owner = THIS_MODULE, +	.hw_ops = { +		.open = ccdc_open, +		.close = ccdc_close, +		.enable = ccdc_enable, +		.enable_out_to_sdram = ccdc_enable_output_to_sdram, +		.set_hw_if_params = ccdc_set_hw_if_params, +		.set_params = ccdc_set_params, +		.configure = ccdc_configure, +		.set_buftype = ccdc_set_buftype, +		.get_buftype = ccdc_get_buftype, +		.enum_pix = ccdc_enum_pix, +		.set_pixel_format = ccdc_set_pixel_format, +		.get_pixel_format = ccdc_get_pixel_format, +		.set_frame_format = ccdc_set_frame_format, +		.get_frame_format = ccdc_get_frame_format, +		.set_image_window = ccdc_set_image_window, +		.get_image_window = ccdc_get_image_window, +		.get_line_length = ccdc_get_line_length, +		.setfbaddr = ccdc_setfbaddr, +		.getfid = ccdc_getfid, +	}, +}; + +static int dm355_ccdc_probe(struct platform_device *pdev) +{ +	void (*setup_pinmux)(void); +	struct resource	*res; +	int status = 0; + +	/* +	 * first try to register with vpfe. If not correct platform, then we +	 * don't have to iomap +	 */ +	status = vpfe_register_ccdc_device(&ccdc_hw_dev); +	if (status < 0) +		return status; + +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +	if (!res) { +		status = -ENODEV; +		goto fail_nores; +	} + +	res = request_mem_region(res->start, resource_size(res), res->name); +	if (!res) { +		status = -EBUSY; +		goto fail_nores; +	} + +	ccdc_cfg.base_addr = ioremap_nocache(res->start, resource_size(res)); +	if (!ccdc_cfg.base_addr) { +		status = -ENOMEM; +		goto fail_nomem; +	} + +	/* Platform data holds setup_pinmux function ptr */ +	if (NULL == pdev->dev.platform_data) { +		status = -ENODEV; +		goto fail_nomap; +	} +	setup_pinmux = pdev->dev.platform_data; +	/* +	 * setup Mux configuration for ccdc which may be different for +	 * different SoCs using this CCDC +	 */ +	setup_pinmux(); +	ccdc_cfg.dev = &pdev->dev; +	printk(KERN_NOTICE "%s is registered with vpfe.\n", ccdc_hw_dev.name); +	return 0; +fail_nomap: +	iounmap(ccdc_cfg.base_addr); +fail_nomem: +	release_mem_region(res->start, resource_size(res)); +fail_nores: +	vpfe_unregister_ccdc_device(&ccdc_hw_dev); +	return status; +} + +static int dm355_ccdc_remove(struct platform_device *pdev) +{ +	struct resource	*res; + +	iounmap(ccdc_cfg.base_addr); +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +	if (res) +		release_mem_region(res->start, resource_size(res)); +	vpfe_unregister_ccdc_device(&ccdc_hw_dev); +	return 0; +} + +static struct platform_driver dm355_ccdc_driver = { +	.driver = { +		.name	= "dm355_ccdc", +		.owner = THIS_MODULE, +	}, +	.remove = dm355_ccdc_remove, +	.probe = dm355_ccdc_probe, +}; + +module_platform_driver(dm355_ccdc_driver); diff --git a/drivers/media/platform/davinci/dm355_ccdc_regs.h b/drivers/media/platform/davinci/dm355_ccdc_regs.h new file mode 100644 index 00000000000..2e1946e0b99 --- /dev/null +++ b/drivers/media/platform/davinci/dm355_ccdc_regs.h @@ -0,0 +1,310 @@ +/* + * Copyright (C) 2005-2009 Texas Instruments Inc + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA + */ +#ifndef _DM355_CCDC_REGS_H +#define _DM355_CCDC_REGS_H + +/**************************************************************************\ +* Register OFFSET Definitions +\**************************************************************************/ +#define SYNCEN				0x00 +#define MODESET				0x04 +#define HDWIDTH				0x08 +#define VDWIDTH				0x0c +#define PPLN				0x10 +#define LPFR				0x14 +#define SPH				0x18 +#define NPH				0x1c +#define SLV0				0x20 +#define SLV1				0x24 +#define NLV				0x28 +#define CULH				0x2c +#define CULV				0x30 +#define HSIZE				0x34 +#define SDOFST				0x38 +#define STADRH				0x3c +#define STADRL				0x40 +#define CLAMP				0x44 +#define DCSUB				0x48 +#define COLPTN				0x4c +#define BLKCMP0				0x50 +#define BLKCMP1				0x54 +#define MEDFILT				0x58 +#define RYEGAIN				0x5c +#define GRCYGAIN			0x60 +#define GBGGAIN				0x64 +#define BMGGAIN				0x68 +#define OFFSET				0x6c +#define OUTCLIP				0x70 +#define VDINT0				0x74 +#define VDINT1				0x78 +#define RSV0				0x7c +#define GAMMAWD				0x80 +#define REC656IF			0x84 +#define CCDCFG				0x88 +#define FMTCFG				0x8c +#define FMTPLEN				0x90 +#define FMTSPH				0x94 +#define FMTLNH				0x98 +#define FMTSLV				0x9c +#define FMTLNV				0xa0 +#define FMTRLEN				0xa4 +#define FMTHCNT				0xa8 +#define FMT_ADDR_PTR_B			0xac +#define FMT_ADDR_PTR(i)			(FMT_ADDR_PTR_B + (i * 4)) +#define FMTPGM_VF0			0xcc +#define FMTPGM_VF1			0xd0 +#define FMTPGM_AP0			0xd4 +#define FMTPGM_AP1			0xd8 +#define FMTPGM_AP2			0xdc +#define FMTPGM_AP3                      0xe0 +#define FMTPGM_AP4                      0xe4 +#define FMTPGM_AP5                      0xe8 +#define FMTPGM_AP6                      0xec +#define FMTPGM_AP7                      0xf0 +#define LSCCFG1                         0xf4 +#define LSCCFG2                         0xf8 +#define LSCH0                           0xfc +#define LSCV0                           0x100 +#define LSCKH                           0x104 +#define LSCKV                           0x108 +#define LSCMEMCTL                       0x10c +#define LSCMEMD                         0x110 +#define LSCMEMQ                         0x114 +#define DFCCTL                          0x118 +#define DFCVSAT                         0x11c +#define DFCMEMCTL                       0x120 +#define DFCMEM0                         0x124 +#define DFCMEM1                         0x128 +#define DFCMEM2                         0x12c +#define DFCMEM3                         0x130 +#define DFCMEM4                         0x134 +#define CSCCTL                          0x138 +#define CSCM0                           0x13c +#define CSCM1                           0x140 +#define CSCM2                           0x144 +#define CSCM3                           0x148 +#define CSCM4                           0x14c +#define CSCM5                           0x150 +#define CSCM6                           0x154 +#define CSCM7                           0x158 +#define DATAOFST			0x15c +#define CCDC_REG_LAST			DATAOFST +/************************************************************** +*	Define for various register bit mask and shifts for CCDC +* +**************************************************************/ +#define CCDC_RAW_IP_MODE			0 +#define CCDC_VDHDOUT_INPUT			0 +#define CCDC_YCINSWP_RAW			(0 << 4) +#define CCDC_EXWEN_DISABLE 			0 +#define CCDC_DATAPOL_NORMAL			0 +#define CCDC_CCDCFG_FIDMD_LATCH_VSYNC		0 +#define CCDC_CCDCFG_FIDMD_NO_LATCH_VSYNC	(1 << 6) +#define CCDC_CCDCFG_WENLOG_AND			0 +#define CCDC_CCDCFG_TRGSEL_WEN			0 +#define CCDC_CCDCFG_EXTRG_DISABLE		0 +#define CCDC_CFA_MOSAIC				0 +#define CCDC_Y8POS_SHIFT			11 + +#define CCDC_VDC_DFCVSAT_MASK			0x3fff +#define CCDC_DATAOFST_MASK			0x0ff +#define CCDC_DATAOFST_H_SHIFT			0 +#define CCDC_DATAOFST_V_SHIFT			8 +#define CCDC_GAMMAWD_CFA_MASK			1 +#define CCDC_GAMMAWD_CFA_SHIFT			5 +#define CCDC_GAMMAWD_INPUT_SHIFT		2 +#define CCDC_FID_POL_MASK			1 +#define CCDC_FID_POL_SHIFT			4 +#define CCDC_HD_POL_MASK			1 +#define CCDC_HD_POL_SHIFT			3 +#define CCDC_VD_POL_MASK			1 +#define CCDC_VD_POL_SHIFT			2 +#define CCDC_VD_POL_NEGATIVE			(1 << 2) +#define CCDC_FRM_FMT_MASK			1 +#define CCDC_FRM_FMT_SHIFT			7 +#define CCDC_DATA_SZ_MASK			7 +#define CCDC_DATA_SZ_SHIFT			8 +#define CCDC_VDHDOUT_MASK			1 +#define CCDC_VDHDOUT_SHIFT			0 +#define CCDC_EXWEN_MASK				1 +#define CCDC_EXWEN_SHIFT			5 +#define CCDC_INPUT_MODE_MASK			3 +#define CCDC_INPUT_MODE_SHIFT			12 +#define CCDC_PIX_FMT_MASK			3 +#define CCDC_PIX_FMT_SHIFT			12 +#define CCDC_DATAPOL_MASK			1 +#define CCDC_DATAPOL_SHIFT			6 +#define CCDC_WEN_ENABLE				(1 << 1) +#define CCDC_VDHDEN_ENABLE			(1 << 16) +#define CCDC_LPF_ENABLE				(1 << 14) +#define CCDC_ALAW_ENABLE			1 +#define CCDC_ALAW_GAMMA_WD_MASK			7 +#define CCDC_REC656IF_BT656_EN			3 + +#define CCDC_FMTCFG_FMTMODE_MASK 		3 +#define CCDC_FMTCFG_FMTMODE_SHIFT		1 +#define CCDC_FMTCFG_LNUM_MASK			3 +#define CCDC_FMTCFG_LNUM_SHIFT			4 +#define CCDC_FMTCFG_ADDRINC_MASK		7 +#define CCDC_FMTCFG_ADDRINC_SHIFT		8 + +#define CCDC_CCDCFG_FIDMD_SHIFT			6 +#define	CCDC_CCDCFG_WENLOG_SHIFT		8 +#define CCDC_CCDCFG_TRGSEL_SHIFT		9 +#define CCDC_CCDCFG_EXTRG_SHIFT			10 +#define CCDC_CCDCFG_MSBINVI_SHIFT		13 + +#define CCDC_HSIZE_FLIP_SHIFT			12 +#define CCDC_HSIZE_FLIP_MASK			1 +#define CCDC_HSIZE_VAL_MASK			0xFFF +#define CCDC_SDOFST_FIELD_INTERLEAVED		0x249 +#define CCDC_SDOFST_INTERLACE_INVERSE		0x4B6D +#define CCDC_SDOFST_INTERLACE_NORMAL		0x0B6D +#define CCDC_SDOFST_PROGRESSIVE_INVERSE		0x4000 +#define CCDC_SDOFST_PROGRESSIVE_NORMAL		0 +#define CCDC_START_PX_HOR_MASK			0x7FFF +#define CCDC_NUM_PX_HOR_MASK			0x7FFF +#define CCDC_START_VER_ONE_MASK			0x7FFF +#define CCDC_START_VER_TWO_MASK			0x7FFF +#define CCDC_NUM_LINES_VER			0x7FFF + +#define CCDC_BLK_CLAMP_ENABLE			(1 << 15) +#define CCDC_BLK_SGAIN_MASK			0x1F +#define CCDC_BLK_ST_PXL_MASK			0x1FFF +#define CCDC_BLK_SAMPLE_LN_MASK			3 +#define CCDC_BLK_SAMPLE_LN_SHIFT		13 + +#define CCDC_NUM_LINE_CALC_MASK			3 +#define CCDC_NUM_LINE_CALC_SHIFT		14 + +#define CCDC_BLK_DC_SUB_MASK			0x3FFF +#define CCDC_BLK_COMP_MASK			0xFF +#define CCDC_BLK_COMP_GB_COMP_SHIFT		8 +#define CCDC_BLK_COMP_GR_COMP_SHIFT		0 +#define CCDC_BLK_COMP_R_COMP_SHIFT		8 +#define CCDC_LATCH_ON_VSYNC_DISABLE		(1 << 15) +#define CCDC_LATCH_ON_VSYNC_ENABLE		(0 << 15) +#define CCDC_FPC_ENABLE				(1 << 15) +#define CCDC_FPC_FPC_NUM_MASK 			0x7FFF +#define CCDC_DATA_PACK_ENABLE			(1 << 11) +#define CCDC_FMT_HORZ_FMTLNH_MASK		0x1FFF +#define CCDC_FMT_HORZ_FMTSPH_MASK		0x1FFF +#define CCDC_FMT_HORZ_FMTSPH_SHIFT		16 +#define CCDC_FMT_VERT_FMTLNV_MASK		0x1FFF +#define CCDC_FMT_VERT_FMTSLV_MASK		0x1FFF +#define CCDC_FMT_VERT_FMTSLV_SHIFT		16 +#define CCDC_VP_OUT_VERT_NUM_MASK		0x3FFF +#define CCDC_VP_OUT_VERT_NUM_SHIFT		17 +#define CCDC_VP_OUT_HORZ_NUM_MASK		0x1FFF +#define CCDC_VP_OUT_HORZ_NUM_SHIFT		4 +#define CCDC_VP_OUT_HORZ_ST_MASK		0xF + +#define CCDC_CSC_COEF_INTEG_MASK		7 +#define CCDC_CSC_COEF_DECIMAL_MASK		0x1f +#define CCDC_CSC_COEF_INTEG_SHIFT		5 +#define CCDC_CSCM_MSB_SHIFT			8 +#define CCDC_CSC_ENABLE				1 +#define CCDC_CSC_DEC_MAX			32 + +#define CCDC_MFILT1_SHIFT			10 +#define CCDC_MFILT2_SHIFT			8 +#define CCDC_MED_FILT_THRESH			0x3FFF +#define CCDC_LPF_MASK				1 +#define CCDC_LPF_SHIFT				14 +#define CCDC_OFFSET_MASK			0x3FF +#define CCDC_DATASFT_MASK			7 +#define CCDC_DATASFT_SHIFT			8 + +#define CCDC_DF_ENABLE				1 + +#define CCDC_FMTPLEN_P0_MASK			0xF +#define CCDC_FMTPLEN_P1_MASK			0xF +#define CCDC_FMTPLEN_P2_MASK			7 +#define CCDC_FMTPLEN_P3_MASK			7 +#define CCDC_FMTPLEN_P0_SHIFT			0 +#define CCDC_FMTPLEN_P1_SHIFT			4 +#define CCDC_FMTPLEN_P2_SHIFT			8 +#define CCDC_FMTPLEN_P3_SHIFT			12 + +#define CCDC_FMTSPH_MASK			0x1FFF +#define CCDC_FMTLNH_MASK			0x1FFF +#define CCDC_FMTSLV_MASK			0x1FFF +#define CCDC_FMTLNV_MASK			0x7FFF +#define CCDC_FMTRLEN_MASK			0x1FFF +#define CCDC_FMTHCNT_MASK			0x1FFF + +#define CCDC_ADP_INIT_MASK			0x1FFF +#define CCDC_ADP_LINE_SHIFT			13 +#define CCDC_ADP_LINE_MASK			3 +#define CCDC_FMTPGN_APTR_MASK			7 + +#define CCDC_DFCCTL_GDFCEN_MASK			1 +#define CCDC_DFCCTL_VDFCEN_MASK			1 +#define CCDC_DFCCTL_VDFC_DISABLE		(0 << 4) +#define CCDC_DFCCTL_VDFCEN_SHIFT		4 +#define CCDC_DFCCTL_VDFCSL_MASK			3 +#define CCDC_DFCCTL_VDFCSL_SHIFT		5 +#define CCDC_DFCCTL_VDFCUDA_MASK		1 +#define CCDC_DFCCTL_VDFCUDA_SHIFT		7 +#define CCDC_DFCCTL_VDFLSFT_MASK		3 +#define CCDC_DFCCTL_VDFLSFT_SHIFT		8 +#define CCDC_DFCMEMCTL_DFCMARST_MASK		1 +#define CCDC_DFCMEMCTL_DFCMARST_SHIFT		2 +#define CCDC_DFCMEMCTL_DFCMWR_MASK		1 +#define CCDC_DFCMEMCTL_DFCMWR_SHIFT		0 +#define CCDC_DFCMEMCTL_INC_ADDR			(0 << 2) + +#define CCDC_LSCCFG_GFTSF_MASK			7 +#define CCDC_LSCCFG_GFTSF_SHIFT			1 +#define CCDC_LSCCFG_GFTINV_MASK			0xf +#define CCDC_LSCCFG_GFTINV_SHIFT		4 +#define CCDC_LSC_GFTABLE_SEL_MASK		3 +#define CCDC_LSC_GFTABLE_EPEL_SHIFT		8 +#define CCDC_LSC_GFTABLE_OPEL_SHIFT		10 +#define CCDC_LSC_GFTABLE_EPOL_SHIFT		12 +#define CCDC_LSC_GFTABLE_OPOL_SHIFT		14 +#define CCDC_LSC_GFMODE_MASK			3 +#define CCDC_LSC_GFMODE_SHIFT			4 +#define CCDC_LSC_DISABLE			0 +#define CCDC_LSC_ENABLE				1 +#define CCDC_LSC_TABLE1_SLC			0 +#define CCDC_LSC_TABLE2_SLC			1 +#define CCDC_LSC_TABLE3_SLC			2 +#define CCDC_LSC_MEMADDR_RESET			(1 << 2) +#define CCDC_LSC_MEMADDR_INCR			(0 << 2) +#define CCDC_LSC_FRAC_MASK_T1			0xFF +#define CCDC_LSC_INT_MASK			3 +#define CCDC_LSC_FRAC_MASK			0x3FFF +#define CCDC_LSC_CENTRE_MASK			0x3FFF +#define CCDC_LSC_COEF_MASK			0xff +#define CCDC_LSC_COEFL_SHIFT			0 +#define CCDC_LSC_COEFU_SHIFT			8 +#define CCDC_GAIN_MASK				0x7FF +#define CCDC_SYNCEN_VDHDEN_MASK			(1 << 0) +#define CCDC_SYNCEN_WEN_MASK			(1 << 1) +#define CCDC_SYNCEN_WEN_SHIFT			1 + +/* Power on Defaults in hardware */ +#define MODESET_DEFAULT				0x200 +#define CULH_DEFAULT				0xFFFF +#define CULV_DEFAULT				0xFF +#define GAIN_DEFAULT				256 +#define OUTCLIP_DEFAULT				0x3FFF +#define LSCCFG2_DEFAULT				0xE + +#endif diff --git a/drivers/media/platform/davinci/dm644x_ccdc.c b/drivers/media/platform/davinci/dm644x_ccdc.c new file mode 100644 index 00000000000..30fa08405d6 --- /dev/null +++ b/drivers/media/platform/davinci/dm644x_ccdc.c @@ -0,0 +1,1044 @@ +/* + * Copyright (C) 2006-2009 Texas Instruments Inc + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA + * + * CCDC hardware module for DM6446 + * ------------------------------ + * + * This module is for configuring CCD controller of DM6446 VPFE to capture + * Raw yuv or Bayer RGB data from a decoder. CCDC has several modules + * such as Defect Pixel Correction, Color Space Conversion etc to + * pre-process the Raw Bayer RGB data, before writing it to SDRAM. This + * module also allows application to configure individual + * module parameters through VPFE_CMD_S_CCDC_RAW_PARAMS IOCTL. + * To do so, application includes dm644x_ccdc.h and vpfe_capture.h header + * files.  The setparams() API is called by vpfe_capture driver + * to configure module parameters. This file is named DM644x so that other + * variants such DM6443 may be supported using the same module. + * + * TODO: Test Raw bayer parameter settings and bayer capture + * 	 Split module parameter structure to module specific ioctl structs + * 	 investigate if enum used for user space type definition + * 	 to be replaced by #defines or integer + */ +#include <linux/platform_device.h> +#include <linux/uaccess.h> +#include <linux/videodev2.h> +#include <linux/gfp.h> +#include <linux/err.h> +#include <linux/module.h> + +#include <media/davinci/dm644x_ccdc.h> +#include <media/davinci/vpss.h> + +#include "dm644x_ccdc_regs.h" +#include "ccdc_hw_device.h" + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("CCDC Driver for DM6446"); +MODULE_AUTHOR("Texas Instruments"); + +static struct ccdc_oper_config { +	struct device *dev; +	/* CCDC interface type */ +	enum vpfe_hw_if_type if_type; +	/* Raw Bayer configuration */ +	struct ccdc_params_raw bayer; +	/* YCbCr configuration */ +	struct ccdc_params_ycbcr ycbcr; +	/* ccdc base address */ +	void __iomem *base_addr; +} ccdc_cfg = { +	/* Raw configurations */ +	.bayer = { +		.pix_fmt = CCDC_PIXFMT_RAW, +		.frm_fmt = CCDC_FRMFMT_PROGRESSIVE, +		.win = CCDC_WIN_VGA, +		.fid_pol = VPFE_PINPOL_POSITIVE, +		.vd_pol = VPFE_PINPOL_POSITIVE, +		.hd_pol = VPFE_PINPOL_POSITIVE, +		.config_params = { +			.data_sz = CCDC_DATA_10BITS, +		}, +	}, +	.ycbcr = { +		.pix_fmt = CCDC_PIXFMT_YCBCR_8BIT, +		.frm_fmt = CCDC_FRMFMT_INTERLACED, +		.win = CCDC_WIN_PAL, +		.fid_pol = VPFE_PINPOL_POSITIVE, +		.vd_pol = VPFE_PINPOL_POSITIVE, +		.hd_pol = VPFE_PINPOL_POSITIVE, +		.bt656_enable = 1, +		.pix_order = CCDC_PIXORDER_CBYCRY, +		.buf_type = CCDC_BUFTYPE_FLD_INTERLEAVED +	}, +}; + +#define CCDC_MAX_RAW_YUV_FORMATS	2 + +/* Raw Bayer formats */ +static u32 ccdc_raw_bayer_pix_formats[] = +	{V4L2_PIX_FMT_SBGGR8, V4L2_PIX_FMT_SBGGR16}; + +/* Raw YUV formats */ +static u32 ccdc_raw_yuv_pix_formats[] = +	{V4L2_PIX_FMT_UYVY, V4L2_PIX_FMT_YUYV}; + +/* CCDC Save/Restore context */ +static u32 ccdc_ctx[CCDC_REG_END / sizeof(u32)]; + +/* register access routines */ +static inline u32 regr(u32 offset) +{ +	return __raw_readl(ccdc_cfg.base_addr + offset); +} + +static inline void regw(u32 val, u32 offset) +{ +	__raw_writel(val, ccdc_cfg.base_addr + offset); +} + +static void ccdc_enable(int flag) +{ +	regw(flag, CCDC_PCR); +} + +static void ccdc_enable_vport(int flag) +{ +	if (flag) +		/* enable video port */ +		regw(CCDC_ENABLE_VIDEO_PORT, CCDC_FMTCFG); +	else +		regw(CCDC_DISABLE_VIDEO_PORT, CCDC_FMTCFG); +} + +/* + * ccdc_setwin() + * This function will configure the window size + * to be capture in CCDC reg + */ +void ccdc_setwin(struct v4l2_rect *image_win, +		enum ccdc_frmfmt frm_fmt, +		int ppc) +{ +	int horz_start, horz_nr_pixels; +	int vert_start, vert_nr_lines; +	int val = 0, mid_img = 0; + +	dev_dbg(ccdc_cfg.dev, "\nStarting ccdc_setwin..."); +	/* +	 * ppc - per pixel count. indicates how many pixels per cell +	 * output to SDRAM. example, for ycbcr, it is one y and one c, so 2. +	 * raw capture this is 1 +	 */ +	horz_start = image_win->left << (ppc - 1); +	horz_nr_pixels = (image_win->width << (ppc - 1)) - 1; +	regw((horz_start << CCDC_HORZ_INFO_SPH_SHIFT) | horz_nr_pixels, +	     CCDC_HORZ_INFO); + +	vert_start = image_win->top; + +	if (frm_fmt == CCDC_FRMFMT_INTERLACED) { +		vert_nr_lines = (image_win->height >> 1) - 1; +		vert_start >>= 1; +		/* Since first line doesn't have any data */ +		vert_start += 1; +		/* configure VDINT0 */ +		val = (vert_start << CCDC_VDINT_VDINT0_SHIFT); +		regw(val, CCDC_VDINT); + +	} else { +		/* Since first line doesn't have any data */ +		vert_start += 1; +		vert_nr_lines = image_win->height - 1; +		/* +		 * configure VDINT0 and VDINT1. VDINT1 will be at half +		 * of image height +		 */ +		mid_img = vert_start + (image_win->height / 2); +		val = (vert_start << CCDC_VDINT_VDINT0_SHIFT) | +		    (mid_img & CCDC_VDINT_VDINT1_MASK); +		regw(val, CCDC_VDINT); + +	} +	regw((vert_start << CCDC_VERT_START_SLV0_SHIFT) | vert_start, +	     CCDC_VERT_START); +	regw(vert_nr_lines, CCDC_VERT_LINES); +	dev_dbg(ccdc_cfg.dev, "\nEnd of ccdc_setwin..."); +} + +static void ccdc_readregs(void) +{ +	unsigned int val = 0; + +	val = regr(CCDC_ALAW); +	dev_notice(ccdc_cfg.dev, "\nReading 0x%x to ALAW...\n", val); +	val = regr(CCDC_CLAMP); +	dev_notice(ccdc_cfg.dev, "\nReading 0x%x to CLAMP...\n", val); +	val = regr(CCDC_DCSUB); +	dev_notice(ccdc_cfg.dev, "\nReading 0x%x to DCSUB...\n", val); +	val = regr(CCDC_BLKCMP); +	dev_notice(ccdc_cfg.dev, "\nReading 0x%x to BLKCMP...\n", val); +	val = regr(CCDC_FPC_ADDR); +	dev_notice(ccdc_cfg.dev, "\nReading 0x%x to FPC_ADDR...\n", val); +	val = regr(CCDC_FPC); +	dev_notice(ccdc_cfg.dev, "\nReading 0x%x to FPC...\n", val); +	val = regr(CCDC_FMTCFG); +	dev_notice(ccdc_cfg.dev, "\nReading 0x%x to FMTCFG...\n", val); +	val = regr(CCDC_COLPTN); +	dev_notice(ccdc_cfg.dev, "\nReading 0x%x to COLPTN...\n", val); +	val = regr(CCDC_FMT_HORZ); +	dev_notice(ccdc_cfg.dev, "\nReading 0x%x to FMT_HORZ...\n", val); +	val = regr(CCDC_FMT_VERT); +	dev_notice(ccdc_cfg.dev, "\nReading 0x%x to FMT_VERT...\n", val); +	val = regr(CCDC_HSIZE_OFF); +	dev_notice(ccdc_cfg.dev, "\nReading 0x%x to HSIZE_OFF...\n", val); +	val = regr(CCDC_SDOFST); +	dev_notice(ccdc_cfg.dev, "\nReading 0x%x to SDOFST...\n", val); +	val = regr(CCDC_VP_OUT); +	dev_notice(ccdc_cfg.dev, "\nReading 0x%x to VP_OUT...\n", val); +	val = regr(CCDC_SYN_MODE); +	dev_notice(ccdc_cfg.dev, "\nReading 0x%x to SYN_MODE...\n", val); +	val = regr(CCDC_HORZ_INFO); +	dev_notice(ccdc_cfg.dev, "\nReading 0x%x to HORZ_INFO...\n", val); +	val = regr(CCDC_VERT_START); +	dev_notice(ccdc_cfg.dev, "\nReading 0x%x to VERT_START...\n", val); +	val = regr(CCDC_VERT_LINES); +	dev_notice(ccdc_cfg.dev, "\nReading 0x%x to VERT_LINES...\n", val); +} + +static int validate_ccdc_param(struct ccdc_config_params_raw *ccdcparam) +{ +	if (ccdcparam->alaw.enable) { +		u8 max_gamma = ccdc_gamma_width_max_bit(ccdcparam->alaw.gamma_wd); +		u8 max_data = ccdc_data_size_max_bit(ccdcparam->data_sz); + +		if ((ccdcparam->alaw.gamma_wd > CCDC_GAMMA_BITS_09_0) || +		    (ccdcparam->alaw.gamma_wd < CCDC_GAMMA_BITS_15_6) || +		    (max_gamma > max_data)) { +			dev_dbg(ccdc_cfg.dev, "\nInvalid data line select"); +			return -1; +		} +	} +	return 0; +} + +static int ccdc_update_raw_params(struct ccdc_config_params_raw *raw_params) +{ +	struct ccdc_config_params_raw *config_params = +				&ccdc_cfg.bayer.config_params; +	unsigned int *fpc_virtaddr = NULL; +	unsigned int *fpc_physaddr = NULL; + +	memcpy(config_params, raw_params, sizeof(*raw_params)); +	/* +	 * allocate memory for fault pixel table and copy the user +	 * values to the table +	 */ +	if (!config_params->fault_pxl.enable) +		return 0; + +	fpc_physaddr = (unsigned int *)config_params->fault_pxl.fpc_table_addr; +	fpc_virtaddr = (unsigned int *)phys_to_virt( +				(unsigned long)fpc_physaddr); +	/* +	 * Allocate memory for FPC table if current +	 * FPC table buffer is not big enough to +	 * accommodate FPC Number requested +	 */ +	if (raw_params->fault_pxl.fp_num != config_params->fault_pxl.fp_num) { +		if (fpc_physaddr != NULL) { +			free_pages((unsigned long)fpc_physaddr, +				   get_order +				   (config_params->fault_pxl.fp_num * +				   FP_NUM_BYTES)); +		} + +		/* Allocate memory for FPC table */ +		fpc_virtaddr = +			(unsigned int *)__get_free_pages(GFP_KERNEL | GFP_DMA, +							 get_order(raw_params-> +							 fault_pxl.fp_num * +							 FP_NUM_BYTES)); + +		if (fpc_virtaddr == NULL) { +			dev_dbg(ccdc_cfg.dev, +				"\nUnable to allocate memory for FPC"); +			return -EFAULT; +		} +		fpc_physaddr = +		    (unsigned int *)virt_to_phys((void *)fpc_virtaddr); +	} + +	/* Copy number of fault pixels and FPC table */ +	config_params->fault_pxl.fp_num = raw_params->fault_pxl.fp_num; +	if (copy_from_user(fpc_virtaddr, +			(void __user *)raw_params->fault_pxl.fpc_table_addr, +			config_params->fault_pxl.fp_num * FP_NUM_BYTES)) { +		dev_dbg(ccdc_cfg.dev, "\n copy_from_user failed"); +		return -EFAULT; +	} +	config_params->fault_pxl.fpc_table_addr = (unsigned int)fpc_physaddr; +	return 0; +} + +static int ccdc_close(struct device *dev) +{ +	struct ccdc_config_params_raw *config_params = +				&ccdc_cfg.bayer.config_params; +	unsigned int *fpc_physaddr = NULL, *fpc_virtaddr = NULL; + +	fpc_physaddr = (unsigned int *)config_params->fault_pxl.fpc_table_addr; + +	if (fpc_physaddr != NULL) { +		fpc_virtaddr = (unsigned int *) +		    phys_to_virt((unsigned long)fpc_physaddr); +		free_pages((unsigned long)fpc_virtaddr, +			   get_order(config_params->fault_pxl.fp_num * +			   FP_NUM_BYTES)); +	} +	return 0; +} + +/* + * ccdc_restore_defaults() + * This function will write defaults to all CCDC registers + */ +static void ccdc_restore_defaults(void) +{ +	int i; + +	/* disable CCDC */ +	ccdc_enable(0); +	/* set all registers to default value */ +	for (i = 4; i <= 0x94; i += 4) +		regw(0,  i); +	regw(CCDC_NO_CULLING, CCDC_CULLING); +	regw(CCDC_GAMMA_BITS_11_2, CCDC_ALAW); +} + +static int ccdc_open(struct device *device) +{ +	ccdc_restore_defaults(); +	if (ccdc_cfg.if_type == VPFE_RAW_BAYER) +		ccdc_enable_vport(1); +	return 0; +} + +static void ccdc_sbl_reset(void) +{ +	vpss_clear_wbl_overflow(VPSS_PCR_CCDC_WBL_O); +} + +/* Parameter operations */ +static int ccdc_set_params(void __user *params) +{ +	struct ccdc_config_params_raw ccdc_raw_params; +	int x; + +	if (ccdc_cfg.if_type != VPFE_RAW_BAYER) +		return -EINVAL; + +	x = copy_from_user(&ccdc_raw_params, params, sizeof(ccdc_raw_params)); +	if (x) { +		dev_dbg(ccdc_cfg.dev, "ccdc_set_params: error in copying" +			   "ccdc params, %d\n", x); +		return -EFAULT; +	} + +	if (!validate_ccdc_param(&ccdc_raw_params)) { +		if (!ccdc_update_raw_params(&ccdc_raw_params)) +			return 0; +	} +	return -EINVAL; +} + +/* + * ccdc_config_ycbcr() + * This function will configure CCDC for YCbCr video capture + */ +void ccdc_config_ycbcr(void) +{ +	struct ccdc_params_ycbcr *params = &ccdc_cfg.ycbcr; +	u32 syn_mode; + +	dev_dbg(ccdc_cfg.dev, "\nStarting ccdc_config_ycbcr..."); +	/* +	 * first restore the CCDC registers to default values +	 * This is important since we assume default values to be set in +	 * a lot of registers that we didn't touch +	 */ +	ccdc_restore_defaults(); + +	/* +	 * configure pixel format, frame format, configure video frame +	 * format, enable output to SDRAM, enable internal timing generator +	 * and 8bit pack mode +	 */ +	syn_mode = (((params->pix_fmt & CCDC_SYN_MODE_INPMOD_MASK) << +		    CCDC_SYN_MODE_INPMOD_SHIFT) | +		    ((params->frm_fmt & CCDC_SYN_FLDMODE_MASK) << +		    CCDC_SYN_FLDMODE_SHIFT) | CCDC_VDHDEN_ENABLE | +		    CCDC_WEN_ENABLE | CCDC_DATA_PACK_ENABLE); + +	/* setup BT.656 sync mode */ +	if (params->bt656_enable) { +		regw(CCDC_REC656IF_BT656_EN, CCDC_REC656IF); + +		/* +		 * configure the FID, VD, HD pin polarity, +		 * fld,hd pol positive, vd negative, 8-bit data +		 */ +		syn_mode |= CCDC_SYN_MODE_VD_POL_NEGATIVE; +		if (ccdc_cfg.if_type == VPFE_BT656_10BIT) +			syn_mode |= CCDC_SYN_MODE_10BITS; +		else +			syn_mode |= CCDC_SYN_MODE_8BITS; +	} else { +		/* y/c external sync mode */ +		syn_mode |= (((params->fid_pol & CCDC_FID_POL_MASK) << +			     CCDC_FID_POL_SHIFT) | +			     ((params->hd_pol & CCDC_HD_POL_MASK) << +			     CCDC_HD_POL_SHIFT) | +			     ((params->vd_pol & CCDC_VD_POL_MASK) << +			     CCDC_VD_POL_SHIFT)); +	} +	regw(syn_mode, CCDC_SYN_MODE); + +	/* configure video window */ +	ccdc_setwin(¶ms->win, params->frm_fmt, 2); + +	/* +	 * configure the order of y cb cr in SDRAM, and disable latch +	 * internal register on vsync +	 */ +	if (ccdc_cfg.if_type == VPFE_BT656_10BIT) +		regw((params->pix_order << CCDC_CCDCFG_Y8POS_SHIFT) | +			CCDC_LATCH_ON_VSYNC_DISABLE | CCDC_CCDCFG_BW656_10BIT, +			CCDC_CCDCFG); +	else +		regw((params->pix_order << CCDC_CCDCFG_Y8POS_SHIFT) | +			CCDC_LATCH_ON_VSYNC_DISABLE, CCDC_CCDCFG); + +	/* +	 * configure the horizontal line offset. This should be a +	 * on 32 byte boundary. So clear LSB 5 bits +	 */ +	regw(((params->win.width * 2  + 31) & ~0x1f), CCDC_HSIZE_OFF); + +	/* configure the memory line offset */ +	if (params->buf_type == CCDC_BUFTYPE_FLD_INTERLEAVED) +		/* two fields are interleaved in memory */ +		regw(CCDC_SDOFST_FIELD_INTERLEAVED, CCDC_SDOFST); + +	ccdc_sbl_reset(); +	dev_dbg(ccdc_cfg.dev, "\nEnd of ccdc_config_ycbcr...\n"); +} + +static void ccdc_config_black_clamp(struct ccdc_black_clamp *bclamp) +{ +	u32 val; + +	if (!bclamp->enable) { +		/* configure DCSub */ +		val = (bclamp->dc_sub) & CCDC_BLK_DC_SUB_MASK; +		regw(val, CCDC_DCSUB); +		dev_dbg(ccdc_cfg.dev, "\nWriting 0x%x to DCSUB...\n", val); +		regw(CCDC_CLAMP_DEFAULT_VAL, CCDC_CLAMP); +		dev_dbg(ccdc_cfg.dev, "\nWriting 0x0000 to CLAMP...\n"); +		return; +	} +	/* +	 * Configure gain,  Start pixel, No of line to be avg, +	 * No of pixel/line to be avg, & Enable the Black clamping +	 */ +	val = ((bclamp->sgain & CCDC_BLK_SGAIN_MASK) | +	       ((bclamp->start_pixel & CCDC_BLK_ST_PXL_MASK) << +		CCDC_BLK_ST_PXL_SHIFT) | +	       ((bclamp->sample_ln & CCDC_BLK_SAMPLE_LINE_MASK) << +		CCDC_BLK_SAMPLE_LINE_SHIFT) | +	       ((bclamp->sample_pixel & CCDC_BLK_SAMPLE_LN_MASK) << +		CCDC_BLK_SAMPLE_LN_SHIFT) | CCDC_BLK_CLAMP_ENABLE); +	regw(val, CCDC_CLAMP); +	dev_dbg(ccdc_cfg.dev, "\nWriting 0x%x to CLAMP...\n", val); +	/* If Black clamping is enable then make dcsub 0 */ +	regw(CCDC_DCSUB_DEFAULT_VAL, CCDC_DCSUB); +	dev_dbg(ccdc_cfg.dev, "\nWriting 0x00000000 to DCSUB...\n"); +} + +static void ccdc_config_black_compense(struct ccdc_black_compensation *bcomp) +{ +	u32 val; + +	val = ((bcomp->b & CCDC_BLK_COMP_MASK) | +	      ((bcomp->gb & CCDC_BLK_COMP_MASK) << +	       CCDC_BLK_COMP_GB_COMP_SHIFT) | +	      ((bcomp->gr & CCDC_BLK_COMP_MASK) << +	       CCDC_BLK_COMP_GR_COMP_SHIFT) | +	      ((bcomp->r & CCDC_BLK_COMP_MASK) << +	       CCDC_BLK_COMP_R_COMP_SHIFT)); +	regw(val, CCDC_BLKCMP); +} + +static void ccdc_config_fpc(struct ccdc_fault_pixel *fpc) +{ +	u32 val; + +	/* Initially disable FPC */ +	val = CCDC_FPC_DISABLE; +	regw(val, CCDC_FPC); + +	if (!fpc->enable) +		return; + +	/* Configure Fault pixel if needed */ +	regw(fpc->fpc_table_addr, CCDC_FPC_ADDR); +	dev_dbg(ccdc_cfg.dev, "\nWriting 0x%x to FPC_ADDR...\n", +		       (fpc->fpc_table_addr)); +	/* Write the FPC params with FPC disable */ +	val = fpc->fp_num & CCDC_FPC_FPC_NUM_MASK; +	regw(val, CCDC_FPC); + +	dev_dbg(ccdc_cfg.dev, "\nWriting 0x%x to FPC...\n", val); +	/* read the FPC register */ +	val = regr(CCDC_FPC) | CCDC_FPC_ENABLE; +	regw(val, CCDC_FPC); +	dev_dbg(ccdc_cfg.dev, "\nWriting 0x%x to FPC...\n", val); +} + +/* + * ccdc_config_raw() + * This function will configure CCDC for Raw capture mode + */ +void ccdc_config_raw(void) +{ +	struct ccdc_params_raw *params = &ccdc_cfg.bayer; +	struct ccdc_config_params_raw *config_params = +				&ccdc_cfg.bayer.config_params; +	unsigned int syn_mode = 0; +	unsigned int val; + +	dev_dbg(ccdc_cfg.dev, "\nStarting ccdc_config_raw..."); + +	/*      Reset CCDC */ +	ccdc_restore_defaults(); + +	/* Disable latching function registers on VSYNC  */ +	regw(CCDC_LATCH_ON_VSYNC_DISABLE, CCDC_CCDCFG); + +	/* +	 * Configure the vertical sync polarity(SYN_MODE.VDPOL), +	 * horizontal sync polarity (SYN_MODE.HDPOL), frame id polarity +	 * (SYN_MODE.FLDPOL), frame format(progressive or interlace), +	 * data size(SYNMODE.DATSIZ), &pixel format (Input mode), output +	 * SDRAM, enable internal timing generator +	 */ +	syn_mode = +		(((params->vd_pol & CCDC_VD_POL_MASK) << CCDC_VD_POL_SHIFT) | +		((params->hd_pol & CCDC_HD_POL_MASK) << CCDC_HD_POL_SHIFT) | +		((params->fid_pol & CCDC_FID_POL_MASK) << CCDC_FID_POL_SHIFT) | +		((params->frm_fmt & CCDC_FRM_FMT_MASK) << CCDC_FRM_FMT_SHIFT) | +		((config_params->data_sz & CCDC_DATA_SZ_MASK) << +		CCDC_DATA_SZ_SHIFT) | +		((params->pix_fmt & CCDC_PIX_FMT_MASK) << CCDC_PIX_FMT_SHIFT) | +		CCDC_WEN_ENABLE | CCDC_VDHDEN_ENABLE); + +	/* Enable and configure aLaw register if needed */ +	if (config_params->alaw.enable) { +		val = ((config_params->alaw.gamma_wd & +		      CCDC_ALAW_GAMMA_WD_MASK) | CCDC_ALAW_ENABLE); +		regw(val, CCDC_ALAW); +		dev_dbg(ccdc_cfg.dev, "\nWriting 0x%x to ALAW...\n", val); +	} + +	/* Configure video window */ +	ccdc_setwin(¶ms->win, params->frm_fmt, CCDC_PPC_RAW); + +	/* Configure Black Clamp */ +	ccdc_config_black_clamp(&config_params->blk_clamp); + +	/* Configure Black level compensation */ +	ccdc_config_black_compense(&config_params->blk_comp); + +	/* Configure Fault Pixel Correction */ +	ccdc_config_fpc(&config_params->fault_pxl); + +	/* If data size is 8 bit then pack the data */ +	if ((config_params->data_sz == CCDC_DATA_8BITS) || +	     config_params->alaw.enable) +		syn_mode |= CCDC_DATA_PACK_ENABLE; + +#ifdef CONFIG_DM644X_VIDEO_PORT_ENABLE +	/* enable video port */ +	val = CCDC_ENABLE_VIDEO_PORT; +#else +	/* disable video port */ +	val = CCDC_DISABLE_VIDEO_PORT; +#endif + +	if (config_params->data_sz == CCDC_DATA_8BITS) +		val |= (CCDC_DATA_10BITS & CCDC_FMTCFG_VPIN_MASK) +		    << CCDC_FMTCFG_VPIN_SHIFT; +	else +		val |= (config_params->data_sz & CCDC_FMTCFG_VPIN_MASK) +		    << CCDC_FMTCFG_VPIN_SHIFT; +	/* Write value in FMTCFG */ +	regw(val, CCDC_FMTCFG); + +	dev_dbg(ccdc_cfg.dev, "\nWriting 0x%x to FMTCFG...\n", val); +	/* Configure the color pattern according to mt9t001 sensor */ +	regw(CCDC_COLPTN_VAL, CCDC_COLPTN); + +	dev_dbg(ccdc_cfg.dev, "\nWriting 0xBB11BB11 to COLPTN...\n"); +	/* +	 * Configure Data formatter(Video port) pixel selection +	 * (FMT_HORZ, FMT_VERT) +	 */ +	val = ((params->win.left & CCDC_FMT_HORZ_FMTSPH_MASK) << +	      CCDC_FMT_HORZ_FMTSPH_SHIFT) | +	      (params->win.width & CCDC_FMT_HORZ_FMTLNH_MASK); +	regw(val, CCDC_FMT_HORZ); + +	dev_dbg(ccdc_cfg.dev, "\nWriting 0x%x to FMT_HORZ...\n", val); +	val = (params->win.top & CCDC_FMT_VERT_FMTSLV_MASK) +	    << CCDC_FMT_VERT_FMTSLV_SHIFT; +	if (params->frm_fmt == CCDC_FRMFMT_PROGRESSIVE) +		val |= (params->win.height) & CCDC_FMT_VERT_FMTLNV_MASK; +	else +		val |= (params->win.height >> 1) & CCDC_FMT_VERT_FMTLNV_MASK; + +	dev_dbg(ccdc_cfg.dev, "\nparams->win.height  0x%x ...\n", +	       params->win.height); +	regw(val, CCDC_FMT_VERT); + +	dev_dbg(ccdc_cfg.dev, "\nWriting 0x%x to FMT_VERT...\n", val); + +	dev_dbg(ccdc_cfg.dev, "\nbelow regw(val, FMT_VERT)..."); + +	/* +	 * Configure Horizontal offset register. If pack 8 is enabled then +	 * 1 pixel will take 1 byte +	 */ +	if ((config_params->data_sz == CCDC_DATA_8BITS) || +	    config_params->alaw.enable) +		regw((params->win.width + CCDC_32BYTE_ALIGN_VAL) & +		    CCDC_HSIZE_OFF_MASK, CCDC_HSIZE_OFF); +	else +		/* else one pixel will take 2 byte */ +		regw(((params->win.width * CCDC_TWO_BYTES_PER_PIXEL) + +		    CCDC_32BYTE_ALIGN_VAL) & CCDC_HSIZE_OFF_MASK, +		    CCDC_HSIZE_OFF); + +	/* Set value for SDOFST */ +	if (params->frm_fmt == CCDC_FRMFMT_INTERLACED) { +		if (params->image_invert_enable) { +			/* For intelace inverse mode */ +			regw(CCDC_INTERLACED_IMAGE_INVERT, CCDC_SDOFST); +			dev_dbg(ccdc_cfg.dev, "\nWriting 0x4B6D to SDOFST..\n"); +		} + +		else { +			/* For intelace non inverse mode */ +			regw(CCDC_INTERLACED_NO_IMAGE_INVERT, CCDC_SDOFST); +			dev_dbg(ccdc_cfg.dev, "\nWriting 0x0249 to SDOFST..\n"); +		} +	} else if (params->frm_fmt == CCDC_FRMFMT_PROGRESSIVE) { +		regw(CCDC_PROGRESSIVE_NO_IMAGE_INVERT, CCDC_SDOFST); +		dev_dbg(ccdc_cfg.dev, "\nWriting 0x0000 to SDOFST...\n"); +	} + +	/* +	 * Configure video port pixel selection (VPOUT) +	 * Here -1 is to make the height value less than FMT_VERT.FMTLNV +	 */ +	if (params->frm_fmt == CCDC_FRMFMT_PROGRESSIVE) +		val = (((params->win.height - 1) & CCDC_VP_OUT_VERT_NUM_MASK)) +		    << CCDC_VP_OUT_VERT_NUM_SHIFT; +	else +		val = +		    ((((params->win.height >> CCDC_INTERLACED_HEIGHT_SHIFT) - +		     1) & CCDC_VP_OUT_VERT_NUM_MASK)) << +		    CCDC_VP_OUT_VERT_NUM_SHIFT; + +	val |= ((((params->win.width))) & CCDC_VP_OUT_HORZ_NUM_MASK) +	    << CCDC_VP_OUT_HORZ_NUM_SHIFT; +	val |= (params->win.left) & CCDC_VP_OUT_HORZ_ST_MASK; +	regw(val, CCDC_VP_OUT); + +	dev_dbg(ccdc_cfg.dev, "\nWriting 0x%x to VP_OUT...\n", val); +	regw(syn_mode, CCDC_SYN_MODE); +	dev_dbg(ccdc_cfg.dev, "\nWriting 0x%x to SYN_MODE...\n", syn_mode); + +	ccdc_sbl_reset(); +	dev_dbg(ccdc_cfg.dev, "\nend of ccdc_config_raw..."); +	ccdc_readregs(); +} + +static int ccdc_configure(void) +{ +	if (ccdc_cfg.if_type == VPFE_RAW_BAYER) +		ccdc_config_raw(); +	else +		ccdc_config_ycbcr(); +	return 0; +} + +static int ccdc_set_buftype(enum ccdc_buftype buf_type) +{ +	if (ccdc_cfg.if_type == VPFE_RAW_BAYER) +		ccdc_cfg.bayer.buf_type = buf_type; +	else +		ccdc_cfg.ycbcr.buf_type = buf_type; +	return 0; +} + +static enum ccdc_buftype ccdc_get_buftype(void) +{ +	if (ccdc_cfg.if_type == VPFE_RAW_BAYER) +		return ccdc_cfg.bayer.buf_type; +	return ccdc_cfg.ycbcr.buf_type; +} + +static int ccdc_enum_pix(u32 *pix, int i) +{ +	int ret = -EINVAL; +	if (ccdc_cfg.if_type == VPFE_RAW_BAYER) { +		if (i < ARRAY_SIZE(ccdc_raw_bayer_pix_formats)) { +			*pix = ccdc_raw_bayer_pix_formats[i]; +			ret = 0; +		} +	} else { +		if (i < ARRAY_SIZE(ccdc_raw_yuv_pix_formats)) { +			*pix = ccdc_raw_yuv_pix_formats[i]; +			ret = 0; +		} +	} +	return ret; +} + +static int ccdc_set_pixel_format(u32 pixfmt) +{ +	if (ccdc_cfg.if_type == VPFE_RAW_BAYER) { +		ccdc_cfg.bayer.pix_fmt = CCDC_PIXFMT_RAW; +		if (pixfmt == V4L2_PIX_FMT_SBGGR8) +			ccdc_cfg.bayer.config_params.alaw.enable = 1; +		else if (pixfmt != V4L2_PIX_FMT_SBGGR16) +			return -EINVAL; +	} else { +		if (pixfmt == V4L2_PIX_FMT_YUYV) +			ccdc_cfg.ycbcr.pix_order = CCDC_PIXORDER_YCBYCR; +		else if (pixfmt == V4L2_PIX_FMT_UYVY) +			ccdc_cfg.ycbcr.pix_order = CCDC_PIXORDER_CBYCRY; +		else +			return -EINVAL; +	} +	return 0; +} + +static u32 ccdc_get_pixel_format(void) +{ +	struct ccdc_a_law *alaw = &ccdc_cfg.bayer.config_params.alaw; +	u32 pixfmt; + +	if (ccdc_cfg.if_type == VPFE_RAW_BAYER) +		if (alaw->enable) +			pixfmt = V4L2_PIX_FMT_SBGGR8; +		else +			pixfmt = V4L2_PIX_FMT_SBGGR16; +	else { +		if (ccdc_cfg.ycbcr.pix_order == CCDC_PIXORDER_YCBYCR) +			pixfmt = V4L2_PIX_FMT_YUYV; +		else +			pixfmt = V4L2_PIX_FMT_UYVY; +	} +	return pixfmt; +} + +static int ccdc_set_image_window(struct v4l2_rect *win) +{ +	if (ccdc_cfg.if_type == VPFE_RAW_BAYER) +		ccdc_cfg.bayer.win = *win; +	else +		ccdc_cfg.ycbcr.win = *win; +	return 0; +} + +static void ccdc_get_image_window(struct v4l2_rect *win) +{ +	if (ccdc_cfg.if_type == VPFE_RAW_BAYER) +		*win = ccdc_cfg.bayer.win; +	else +		*win = ccdc_cfg.ycbcr.win; +} + +static unsigned int ccdc_get_line_length(void) +{ +	struct ccdc_config_params_raw *config_params = +				&ccdc_cfg.bayer.config_params; +	unsigned int len; + +	if (ccdc_cfg.if_type == VPFE_RAW_BAYER) { +		if ((config_params->alaw.enable) || +		    (config_params->data_sz == CCDC_DATA_8BITS)) +			len = ccdc_cfg.bayer.win.width; +		else +			len = ccdc_cfg.bayer.win.width * 2; +	} else +		len = ccdc_cfg.ycbcr.win.width * 2; +	return ALIGN(len, 32); +} + +static int ccdc_set_frame_format(enum ccdc_frmfmt frm_fmt) +{ +	if (ccdc_cfg.if_type == VPFE_RAW_BAYER) +		ccdc_cfg.bayer.frm_fmt = frm_fmt; +	else +		ccdc_cfg.ycbcr.frm_fmt = frm_fmt; +	return 0; +} + +static enum ccdc_frmfmt ccdc_get_frame_format(void) +{ +	if (ccdc_cfg.if_type == VPFE_RAW_BAYER) +		return ccdc_cfg.bayer.frm_fmt; +	else +		return ccdc_cfg.ycbcr.frm_fmt; +} + +static int ccdc_getfid(void) +{ +	return (regr(CCDC_SYN_MODE) >> 15) & 1; +} + +/* misc operations */ +static inline void ccdc_setfbaddr(unsigned long addr) +{ +	regw(addr & 0xffffffe0, CCDC_SDR_ADDR); +} + +static int ccdc_set_hw_if_params(struct vpfe_hw_if_param *params) +{ +	ccdc_cfg.if_type = params->if_type; + +	switch (params->if_type) { +	case VPFE_BT656: +	case VPFE_YCBCR_SYNC_16: +	case VPFE_YCBCR_SYNC_8: +	case VPFE_BT656_10BIT: +		ccdc_cfg.ycbcr.vd_pol = params->vdpol; +		ccdc_cfg.ycbcr.hd_pol = params->hdpol; +		break; +	default: +		/* TODO add support for raw bayer here */ +		return -EINVAL; +	} +	return 0; +} + +static void ccdc_save_context(void) +{ +	ccdc_ctx[CCDC_PCR >> 2] = regr(CCDC_PCR); +	ccdc_ctx[CCDC_SYN_MODE >> 2] = regr(CCDC_SYN_MODE); +	ccdc_ctx[CCDC_HD_VD_WID >> 2] = regr(CCDC_HD_VD_WID); +	ccdc_ctx[CCDC_PIX_LINES >> 2] = regr(CCDC_PIX_LINES); +	ccdc_ctx[CCDC_HORZ_INFO >> 2] = regr(CCDC_HORZ_INFO); +	ccdc_ctx[CCDC_VERT_START >> 2] = regr(CCDC_VERT_START); +	ccdc_ctx[CCDC_VERT_LINES >> 2] = regr(CCDC_VERT_LINES); +	ccdc_ctx[CCDC_CULLING >> 2] = regr(CCDC_CULLING); +	ccdc_ctx[CCDC_HSIZE_OFF >> 2] = regr(CCDC_HSIZE_OFF); +	ccdc_ctx[CCDC_SDOFST >> 2] = regr(CCDC_SDOFST); +	ccdc_ctx[CCDC_SDR_ADDR >> 2] = regr(CCDC_SDR_ADDR); +	ccdc_ctx[CCDC_CLAMP >> 2] = regr(CCDC_CLAMP); +	ccdc_ctx[CCDC_DCSUB >> 2] = regr(CCDC_DCSUB); +	ccdc_ctx[CCDC_COLPTN >> 2] = regr(CCDC_COLPTN); +	ccdc_ctx[CCDC_BLKCMP >> 2] = regr(CCDC_BLKCMP); +	ccdc_ctx[CCDC_FPC >> 2] = regr(CCDC_FPC); +	ccdc_ctx[CCDC_FPC_ADDR >> 2] = regr(CCDC_FPC_ADDR); +	ccdc_ctx[CCDC_VDINT >> 2] = regr(CCDC_VDINT); +	ccdc_ctx[CCDC_ALAW >> 2] = regr(CCDC_ALAW); +	ccdc_ctx[CCDC_REC656IF >> 2] = regr(CCDC_REC656IF); +	ccdc_ctx[CCDC_CCDCFG >> 2] = regr(CCDC_CCDCFG); +	ccdc_ctx[CCDC_FMTCFG >> 2] = regr(CCDC_FMTCFG); +	ccdc_ctx[CCDC_FMT_HORZ >> 2] = regr(CCDC_FMT_HORZ); +	ccdc_ctx[CCDC_FMT_VERT >> 2] = regr(CCDC_FMT_VERT); +	ccdc_ctx[CCDC_FMT_ADDR0 >> 2] = regr(CCDC_FMT_ADDR0); +	ccdc_ctx[CCDC_FMT_ADDR1 >> 2] = regr(CCDC_FMT_ADDR1); +	ccdc_ctx[CCDC_FMT_ADDR2 >> 2] = regr(CCDC_FMT_ADDR2); +	ccdc_ctx[CCDC_FMT_ADDR3 >> 2] = regr(CCDC_FMT_ADDR3); +	ccdc_ctx[CCDC_FMT_ADDR4 >> 2] = regr(CCDC_FMT_ADDR4); +	ccdc_ctx[CCDC_FMT_ADDR5 >> 2] = regr(CCDC_FMT_ADDR5); +	ccdc_ctx[CCDC_FMT_ADDR6 >> 2] = regr(CCDC_FMT_ADDR6); +	ccdc_ctx[CCDC_FMT_ADDR7 >> 2] = regr(CCDC_FMT_ADDR7); +	ccdc_ctx[CCDC_PRGEVEN_0 >> 2] = regr(CCDC_PRGEVEN_0); +	ccdc_ctx[CCDC_PRGEVEN_1 >> 2] = regr(CCDC_PRGEVEN_1); +	ccdc_ctx[CCDC_PRGODD_0 >> 2] = regr(CCDC_PRGODD_0); +	ccdc_ctx[CCDC_PRGODD_1 >> 2] = regr(CCDC_PRGODD_1); +	ccdc_ctx[CCDC_VP_OUT >> 2] = regr(CCDC_VP_OUT); +} + +static void ccdc_restore_context(void) +{ +	regw(ccdc_ctx[CCDC_SYN_MODE >> 2], CCDC_SYN_MODE); +	regw(ccdc_ctx[CCDC_HD_VD_WID >> 2], CCDC_HD_VD_WID); +	regw(ccdc_ctx[CCDC_PIX_LINES >> 2], CCDC_PIX_LINES); +	regw(ccdc_ctx[CCDC_HORZ_INFO >> 2], CCDC_HORZ_INFO); +	regw(ccdc_ctx[CCDC_VERT_START >> 2], CCDC_VERT_START); +	regw(ccdc_ctx[CCDC_VERT_LINES >> 2], CCDC_VERT_LINES); +	regw(ccdc_ctx[CCDC_CULLING >> 2], CCDC_CULLING); +	regw(ccdc_ctx[CCDC_HSIZE_OFF >> 2], CCDC_HSIZE_OFF); +	regw(ccdc_ctx[CCDC_SDOFST >> 2], CCDC_SDOFST); +	regw(ccdc_ctx[CCDC_SDR_ADDR >> 2], CCDC_SDR_ADDR); +	regw(ccdc_ctx[CCDC_CLAMP >> 2], CCDC_CLAMP); +	regw(ccdc_ctx[CCDC_DCSUB >> 2], CCDC_DCSUB); +	regw(ccdc_ctx[CCDC_COLPTN >> 2], CCDC_COLPTN); +	regw(ccdc_ctx[CCDC_BLKCMP >> 2], CCDC_BLKCMP); +	regw(ccdc_ctx[CCDC_FPC >> 2], CCDC_FPC); +	regw(ccdc_ctx[CCDC_FPC_ADDR >> 2], CCDC_FPC_ADDR); +	regw(ccdc_ctx[CCDC_VDINT >> 2], CCDC_VDINT); +	regw(ccdc_ctx[CCDC_ALAW >> 2], CCDC_ALAW); +	regw(ccdc_ctx[CCDC_REC656IF >> 2], CCDC_REC656IF); +	regw(ccdc_ctx[CCDC_CCDCFG >> 2], CCDC_CCDCFG); +	regw(ccdc_ctx[CCDC_FMTCFG >> 2], CCDC_FMTCFG); +	regw(ccdc_ctx[CCDC_FMT_HORZ >> 2], CCDC_FMT_HORZ); +	regw(ccdc_ctx[CCDC_FMT_VERT >> 2], CCDC_FMT_VERT); +	regw(ccdc_ctx[CCDC_FMT_ADDR0 >> 2], CCDC_FMT_ADDR0); +	regw(ccdc_ctx[CCDC_FMT_ADDR1 >> 2], CCDC_FMT_ADDR1); +	regw(ccdc_ctx[CCDC_FMT_ADDR2 >> 2], CCDC_FMT_ADDR2); +	regw(ccdc_ctx[CCDC_FMT_ADDR3 >> 2], CCDC_FMT_ADDR3); +	regw(ccdc_ctx[CCDC_FMT_ADDR4 >> 2], CCDC_FMT_ADDR4); +	regw(ccdc_ctx[CCDC_FMT_ADDR5 >> 2], CCDC_FMT_ADDR5); +	regw(ccdc_ctx[CCDC_FMT_ADDR6 >> 2], CCDC_FMT_ADDR6); +	regw(ccdc_ctx[CCDC_FMT_ADDR7 >> 2], CCDC_FMT_ADDR7); +	regw(ccdc_ctx[CCDC_PRGEVEN_0 >> 2], CCDC_PRGEVEN_0); +	regw(ccdc_ctx[CCDC_PRGEVEN_1 >> 2], CCDC_PRGEVEN_1); +	regw(ccdc_ctx[CCDC_PRGODD_0 >> 2], CCDC_PRGODD_0); +	regw(ccdc_ctx[CCDC_PRGODD_1 >> 2], CCDC_PRGODD_1); +	regw(ccdc_ctx[CCDC_VP_OUT >> 2], CCDC_VP_OUT); +	regw(ccdc_ctx[CCDC_PCR >> 2], CCDC_PCR); +} +static struct ccdc_hw_device ccdc_hw_dev = { +	.name = "DM6446 CCDC", +	.owner = THIS_MODULE, +	.hw_ops = { +		.open = ccdc_open, +		.close = ccdc_close, +		.reset = ccdc_sbl_reset, +		.enable = ccdc_enable, +		.set_hw_if_params = ccdc_set_hw_if_params, +		.set_params = ccdc_set_params, +		.configure = ccdc_configure, +		.set_buftype = ccdc_set_buftype, +		.get_buftype = ccdc_get_buftype, +		.enum_pix = ccdc_enum_pix, +		.set_pixel_format = ccdc_set_pixel_format, +		.get_pixel_format = ccdc_get_pixel_format, +		.set_frame_format = ccdc_set_frame_format, +		.get_frame_format = ccdc_get_frame_format, +		.set_image_window = ccdc_set_image_window, +		.get_image_window = ccdc_get_image_window, +		.get_line_length = ccdc_get_line_length, +		.setfbaddr = ccdc_setfbaddr, +		.getfid = ccdc_getfid, +	}, +}; + +static int dm644x_ccdc_probe(struct platform_device *pdev) +{ +	struct resource	*res; +	int status = 0; + +	/* +	 * first try to register with vpfe. If not correct platform, then we +	 * don't have to iomap +	 */ +	status = vpfe_register_ccdc_device(&ccdc_hw_dev); +	if (status < 0) +		return status; + +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +	if (!res) { +		status = -ENODEV; +		goto fail_nores; +	} + +	res = request_mem_region(res->start, resource_size(res), res->name); +	if (!res) { +		status = -EBUSY; +		goto fail_nores; +	} + +	ccdc_cfg.base_addr = ioremap_nocache(res->start, resource_size(res)); +	if (!ccdc_cfg.base_addr) { +		status = -ENOMEM; +		goto fail_nomem; +	} + +	ccdc_cfg.dev = &pdev->dev; +	printk(KERN_NOTICE "%s is registered with vpfe.\n", ccdc_hw_dev.name); +	return 0; +fail_nomem: +	release_mem_region(res->start, resource_size(res)); +fail_nores: +	vpfe_unregister_ccdc_device(&ccdc_hw_dev); +	return status; +} + +static int dm644x_ccdc_remove(struct platform_device *pdev) +{ +	struct resource	*res; + +	iounmap(ccdc_cfg.base_addr); +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +	if (res) +		release_mem_region(res->start, resource_size(res)); +	vpfe_unregister_ccdc_device(&ccdc_hw_dev); +	return 0; +} + +static int dm644x_ccdc_suspend(struct device *dev) +{ +	/* Save CCDC context */ +	ccdc_save_context(); +	/* Disable CCDC */ +	ccdc_enable(0); + +	return 0; +} + +static int dm644x_ccdc_resume(struct device *dev) +{ +	/* Restore CCDC context */ +	ccdc_restore_context(); + +	return 0; +} + +static const struct dev_pm_ops dm644x_ccdc_pm_ops = { +	.suspend = dm644x_ccdc_suspend, +	.resume = dm644x_ccdc_resume, +}; + +static struct platform_driver dm644x_ccdc_driver = { +	.driver = { +		.name	= "dm644x_ccdc", +		.owner = THIS_MODULE, +		.pm = &dm644x_ccdc_pm_ops, +	}, +	.remove = dm644x_ccdc_remove, +	.probe = dm644x_ccdc_probe, +}; + +module_platform_driver(dm644x_ccdc_driver); diff --git a/drivers/media/platform/davinci/dm644x_ccdc_regs.h b/drivers/media/platform/davinci/dm644x_ccdc_regs.h new file mode 100644 index 00000000000..2b0aca5383f --- /dev/null +++ b/drivers/media/platform/davinci/dm644x_ccdc_regs.h @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2006-2009 Texas Instruments Inc + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA + */ +#ifndef _DM644X_CCDC_REGS_H +#define _DM644X_CCDC_REGS_H + +/**************************************************************************\ +* Register OFFSET Definitions +\**************************************************************************/ +#define CCDC_PID				0x0 +#define CCDC_PCR				0x4 +#define CCDC_SYN_MODE				0x8 +#define CCDC_HD_VD_WID				0xc +#define CCDC_PIX_LINES				0x10 +#define CCDC_HORZ_INFO				0x14 +#define CCDC_VERT_START				0x18 +#define CCDC_VERT_LINES				0x1c +#define CCDC_CULLING				0x20 +#define CCDC_HSIZE_OFF				0x24 +#define CCDC_SDOFST				0x28 +#define CCDC_SDR_ADDR				0x2c +#define CCDC_CLAMP				0x30 +#define CCDC_DCSUB				0x34 +#define CCDC_COLPTN				0x38 +#define CCDC_BLKCMP				0x3c +#define CCDC_FPC				0x40 +#define CCDC_FPC_ADDR				0x44 +#define CCDC_VDINT				0x48 +#define CCDC_ALAW				0x4c +#define CCDC_REC656IF				0x50 +#define CCDC_CCDCFG				0x54 +#define CCDC_FMTCFG				0x58 +#define CCDC_FMT_HORZ				0x5c +#define CCDC_FMT_VERT				0x60 +#define CCDC_FMT_ADDR0				0x64 +#define CCDC_FMT_ADDR1				0x68 +#define CCDC_FMT_ADDR2				0x6c +#define CCDC_FMT_ADDR3				0x70 +#define CCDC_FMT_ADDR4				0x74 +#define CCDC_FMT_ADDR5				0x78 +#define CCDC_FMT_ADDR6				0x7c +#define CCDC_FMT_ADDR7				0x80 +#define CCDC_PRGEVEN_0				0x84 +#define CCDC_PRGEVEN_1				0x88 +#define CCDC_PRGODD_0				0x8c +#define CCDC_PRGODD_1				0x90 +#define CCDC_VP_OUT				0x94 +#define CCDC_REG_END				0x98 + +/*************************************************************** +*	Define for various register bit mask and shifts for CCDC +****************************************************************/ +#define CCDC_FID_POL_MASK			1 +#define CCDC_FID_POL_SHIFT			4 +#define CCDC_HD_POL_MASK			1 +#define CCDC_HD_POL_SHIFT			3 +#define CCDC_VD_POL_MASK			1 +#define CCDC_VD_POL_SHIFT			2 +#define CCDC_HSIZE_OFF_MASK			0xffffffe0 +#define CCDC_32BYTE_ALIGN_VAL			31 +#define CCDC_FRM_FMT_MASK			0x1 +#define CCDC_FRM_FMT_SHIFT			7 +#define CCDC_DATA_SZ_MASK			7 +#define CCDC_DATA_SZ_SHIFT			8 +#define CCDC_PIX_FMT_MASK			3 +#define CCDC_PIX_FMT_SHIFT			12 +#define CCDC_VP2SDR_DISABLE			0xFFFBFFFF +#define CCDC_WEN_ENABLE				(1 << 17) +#define CCDC_SDR2RSZ_DISABLE			0xFFF7FFFF +#define CCDC_VDHDEN_ENABLE			(1 << 16) +#define CCDC_LPF_ENABLE				(1 << 14) +#define CCDC_ALAW_ENABLE			(1 << 3) +#define CCDC_ALAW_GAMMA_WD_MASK			7 +#define CCDC_BLK_CLAMP_ENABLE			(1 << 31) +#define CCDC_BLK_SGAIN_MASK			0x1F +#define CCDC_BLK_ST_PXL_MASK			0x7FFF +#define CCDC_BLK_ST_PXL_SHIFT			10 +#define CCDC_BLK_SAMPLE_LN_MASK			7 +#define CCDC_BLK_SAMPLE_LN_SHIFT		28 +#define CCDC_BLK_SAMPLE_LINE_MASK		7 +#define CCDC_BLK_SAMPLE_LINE_SHIFT		25 +#define CCDC_BLK_DC_SUB_MASK			0x03FFF +#define CCDC_BLK_COMP_MASK			0xFF +#define CCDC_BLK_COMP_GB_COMP_SHIFT		8 +#define CCDC_BLK_COMP_GR_COMP_SHIFT		16 +#define CCDC_BLK_COMP_R_COMP_SHIFT		24 +#define CCDC_LATCH_ON_VSYNC_DISABLE		(1 << 15) +#define CCDC_FPC_ENABLE				(1 << 15) +#define CCDC_FPC_DISABLE			0 +#define CCDC_FPC_FPC_NUM_MASK 			0x7FFF +#define CCDC_DATA_PACK_ENABLE			(1 << 11) +#define CCDC_FMTCFG_VPIN_MASK			7 +#define CCDC_FMTCFG_VPIN_SHIFT			12 +#define CCDC_FMT_HORZ_FMTLNH_MASK		0x1FFF +#define CCDC_FMT_HORZ_FMTSPH_MASK		0x1FFF +#define CCDC_FMT_HORZ_FMTSPH_SHIFT		16 +#define CCDC_FMT_VERT_FMTLNV_MASK		0x1FFF +#define CCDC_FMT_VERT_FMTSLV_MASK		0x1FFF +#define CCDC_FMT_VERT_FMTSLV_SHIFT		16 +#define CCDC_VP_OUT_VERT_NUM_MASK		0x3FFF +#define CCDC_VP_OUT_VERT_NUM_SHIFT		17 +#define CCDC_VP_OUT_HORZ_NUM_MASK		0x1FFF +#define CCDC_VP_OUT_HORZ_NUM_SHIFT		4 +#define CCDC_VP_OUT_HORZ_ST_MASK		0xF +#define CCDC_HORZ_INFO_SPH_SHIFT		16 +#define CCDC_VERT_START_SLV0_SHIFT		16 +#define CCDC_VDINT_VDINT0_SHIFT			16 +#define CCDC_VDINT_VDINT1_MASK			0xFFFF +#define CCDC_PPC_RAW				1 +#define CCDC_DCSUB_DEFAULT_VAL			0 +#define CCDC_CLAMP_DEFAULT_VAL			0 +#define CCDC_ENABLE_VIDEO_PORT			0x8000 +#define CCDC_DISABLE_VIDEO_PORT			0 +#define CCDC_COLPTN_VAL				0xBB11BB11 +#define CCDC_TWO_BYTES_PER_PIXEL		2 +#define CCDC_INTERLACED_IMAGE_INVERT		0x4B6D +#define CCDC_INTERLACED_NO_IMAGE_INVERT		0x0249 +#define CCDC_PROGRESSIVE_IMAGE_INVERT		0x4000 +#define CCDC_PROGRESSIVE_NO_IMAGE_INVERT	0 +#define CCDC_INTERLACED_HEIGHT_SHIFT		1 +#define CCDC_SYN_MODE_INPMOD_SHIFT		12 +#define CCDC_SYN_MODE_INPMOD_MASK		3 +#define CCDC_SYN_MODE_8BITS			(7 << 8) +#define CCDC_SYN_MODE_10BITS			(6 << 8) +#define CCDC_SYN_MODE_11BITS			(5 << 8) +#define CCDC_SYN_MODE_12BITS			(4 << 8) +#define CCDC_SYN_MODE_13BITS			(3 << 8) +#define CCDC_SYN_MODE_14BITS			(2 << 8) +#define CCDC_SYN_MODE_15BITS			(1 << 8) +#define CCDC_SYN_MODE_16BITS			(0 << 8) +#define CCDC_SYN_FLDMODE_MASK			1 +#define CCDC_SYN_FLDMODE_SHIFT			7 +#define CCDC_REC656IF_BT656_EN			3 +#define CCDC_SYN_MODE_VD_POL_NEGATIVE		(1 << 2) +#define CCDC_CCDCFG_Y8POS_SHIFT			11 +#define CCDC_CCDCFG_BW656_10BIT 		(1 << 5) +#define CCDC_SDOFST_FIELD_INTERLEAVED		0x249 +#define CCDC_NO_CULLING				0xffff00ff +#endif diff --git a/drivers/media/platform/davinci/isif.c b/drivers/media/platform/davinci/isif.c new file mode 100644 index 00000000000..3332cca632e --- /dev/null +++ b/drivers/media/platform/davinci/isif.c @@ -0,0 +1,1145 @@ +/* + * Copyright (C) 2008-2009 Texas Instruments Inc + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA + * + * Image Sensor Interface (ISIF) driver + * + * This driver is for configuring the ISIF IP available on DM365 or any other + * TI SoCs. This is used for capturing yuv or bayer video or image data + * from a decoder or sensor. This IP is similar to the CCDC IP on DM355 + * and DM6446, but with enhanced or additional ip blocks. The driver + * configures the ISIF upon commands from the vpfe bridge driver through + * ccdc_hw_device interface. + * + * TODO: 1) Raw bayer parameter settings and bayer capture + *	 2) Add support for control ioctl + */ +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/uaccess.h> +#include <linux/io.h> +#include <linux/videodev2.h> +#include <linux/err.h> +#include <linux/module.h> + +#include <mach/mux.h> + +#include <media/davinci/isif.h> +#include <media/davinci/vpss.h> + +#include "isif_regs.h" +#include "ccdc_hw_device.h" + +/* Defaults for module configuration parameters */ +static struct isif_config_params_raw isif_config_defaults = { +	.linearize = { +		.en = 0, +		.corr_shft = ISIF_NO_SHIFT, +		.scale_fact = {1, 0}, +	}, +	.df_csc = { +		.df_or_csc = 0, +		.csc = { +			.en = 0, +		}, +	}, +	.dfc = { +		.en = 0, +	}, +	.bclamp = { +		.en = 0, +	}, +	.gain_offset = { +		.gain = { +			.r_ye = {1, 0}, +			.gr_cy = {1, 0}, +			.gb_g = {1, 0}, +			.b_mg = {1, 0}, +		}, +	}, +	.culling = { +		.hcpat_odd = 0xff, +		.hcpat_even = 0xff, +		.vcpat = 0xff, +	}, +	.compress = { +		.alg = ISIF_ALAW, +	}, +}; + +/* ISIF operation configuration */ +static struct isif_oper_config { +	struct device *dev; +	enum vpfe_hw_if_type if_type; +	struct isif_ycbcr_config ycbcr; +	struct isif_params_raw bayer; +	enum isif_data_pack data_pack; +	/* ISIF base address */ +	void __iomem *base_addr; +	/* ISIF Linear Table 0 */ +	void __iomem *linear_tbl0_addr; +	/* ISIF Linear Table 1 */ +	void __iomem *linear_tbl1_addr; +} isif_cfg = { +	.ycbcr = { +		.pix_fmt = CCDC_PIXFMT_YCBCR_8BIT, +		.frm_fmt = CCDC_FRMFMT_INTERLACED, +		.win = ISIF_WIN_NTSC, +		.fid_pol = VPFE_PINPOL_POSITIVE, +		.vd_pol = VPFE_PINPOL_POSITIVE, +		.hd_pol = VPFE_PINPOL_POSITIVE, +		.pix_order = CCDC_PIXORDER_CBYCRY, +		.buf_type = CCDC_BUFTYPE_FLD_INTERLEAVED, +	}, +	.bayer = { +		.pix_fmt = CCDC_PIXFMT_RAW, +		.frm_fmt = CCDC_FRMFMT_PROGRESSIVE, +		.win = ISIF_WIN_VGA, +		.fid_pol = VPFE_PINPOL_POSITIVE, +		.vd_pol = VPFE_PINPOL_POSITIVE, +		.hd_pol = VPFE_PINPOL_POSITIVE, +		.gain = { +			.r_ye = {1, 0}, +			.gr_cy = {1, 0}, +			.gb_g = {1, 0}, +			.b_mg = {1, 0}, +		}, +		.cfa_pat = ISIF_CFA_PAT_MOSAIC, +		.data_msb = ISIF_BIT_MSB_11, +		.config_params = { +			.data_shift = ISIF_NO_SHIFT, +			.col_pat_field0 = { +				.olop = ISIF_GREEN_BLUE, +				.olep = ISIF_BLUE, +				.elop = ISIF_RED, +				.elep = ISIF_GREEN_RED, +			}, +			.col_pat_field1 = { +				.olop = ISIF_GREEN_BLUE, +				.olep = ISIF_BLUE, +				.elop = ISIF_RED, +				.elep = ISIF_GREEN_RED, +			}, +			.test_pat_gen = 0, +		}, +	}, +	.data_pack = ISIF_DATA_PACK8, +}; + +/* Raw Bayer formats */ +static const u32 isif_raw_bayer_pix_formats[] = { +	V4L2_PIX_FMT_SBGGR8, V4L2_PIX_FMT_SBGGR16}; + +/* Raw YUV formats */ +static const u32 isif_raw_yuv_pix_formats[] = { +	V4L2_PIX_FMT_UYVY, V4L2_PIX_FMT_YUYV}; + +/* register access routines */ +static inline u32 regr(u32 offset) +{ +	return __raw_readl(isif_cfg.base_addr + offset); +} + +static inline void regw(u32 val, u32 offset) +{ +	__raw_writel(val, isif_cfg.base_addr + offset); +} + +/* reg_modify() - read, modify and write register */ +static inline u32 reg_modify(u32 mask, u32 val, u32 offset) +{ +	u32 new_val = (regr(offset) & ~mask) | (val & mask); + +	regw(new_val, offset); +	return new_val; +} + +static inline void regw_lin_tbl(u32 val, u32 offset, int i) +{ +	if (!i) +		__raw_writel(val, isif_cfg.linear_tbl0_addr + offset); +	else +		__raw_writel(val, isif_cfg.linear_tbl1_addr + offset); +} + +static void isif_disable_all_modules(void) +{ +	/* disable BC */ +	regw(0, CLAMPCFG); +	/* disable vdfc */ +	regw(0, DFCCTL); +	/* disable CSC */ +	regw(0, CSCCTL); +	/* disable linearization */ +	regw(0, LINCFG0); +	/* disable other modules here as they are supported */ +} + +static void isif_enable(int en) +{ +	if (!en) { +		/* Before disable isif, disable all ISIF modules */ +		isif_disable_all_modules(); +		/* +		 * wait for next VD. Assume lowest scan rate is 12 Hz. So +		 * 100 msec delay is good enough +		 */ +		msleep(100); +	} +	reg_modify(ISIF_SYNCEN_VDHDEN_MASK, en, SYNCEN); +} + +static void isif_enable_output_to_sdram(int en) +{ +	reg_modify(ISIF_SYNCEN_WEN_MASK, en << ISIF_SYNCEN_WEN_SHIFT, SYNCEN); +} + +static void isif_config_culling(struct isif_cul *cul) +{ +	u32 val; + +	/* Horizontal pattern */ +	val = (cul->hcpat_even << CULL_PAT_EVEN_LINE_SHIFT) | cul->hcpat_odd; +	regw(val, CULH); + +	/* vertical pattern */ +	regw(cul->vcpat, CULV); + +	/* LPF */ +	reg_modify(ISIF_LPF_MASK << ISIF_LPF_SHIFT, +		  cul->en_lpf << ISIF_LPF_SHIFT, MODESET); +} + +static void isif_config_gain_offset(void) +{ +	struct isif_gain_offsets_adj *gain_off_p = +		&isif_cfg.bayer.config_params.gain_offset; +	u32 val; + +	val = (!!gain_off_p->gain_sdram_en << GAIN_SDRAM_EN_SHIFT) | +	      (!!gain_off_p->gain_ipipe_en << GAIN_IPIPE_EN_SHIFT) | +	      (!!gain_off_p->gain_h3a_en << GAIN_H3A_EN_SHIFT) | +	      (!!gain_off_p->offset_sdram_en << OFST_SDRAM_EN_SHIFT) | +	      (!!gain_off_p->offset_ipipe_en << OFST_IPIPE_EN_SHIFT) | +	      (!!gain_off_p->offset_h3a_en << OFST_H3A_EN_SHIFT); + +	reg_modify(GAIN_OFFSET_EN_MASK, val, CGAMMAWD); + +	val = (gain_off_p->gain.r_ye.integer << GAIN_INTEGER_SHIFT) | +	       gain_off_p->gain.r_ye.decimal; +	regw(val, CRGAIN); + +	val = (gain_off_p->gain.gr_cy.integer << GAIN_INTEGER_SHIFT) | +	       gain_off_p->gain.gr_cy.decimal; +	regw(val, CGRGAIN); + +	val = (gain_off_p->gain.gb_g.integer << GAIN_INTEGER_SHIFT) | +	       gain_off_p->gain.gb_g.decimal; +	regw(val, CGBGAIN); + +	val = (gain_off_p->gain.b_mg.integer << GAIN_INTEGER_SHIFT) | +	       gain_off_p->gain.b_mg.decimal; +	regw(val, CBGAIN); + +	regw(gain_off_p->offset, COFSTA); +} + +static void isif_restore_defaults(void) +{ +	enum vpss_ccdc_source_sel source = VPSS_CCDCIN; + +	dev_dbg(isif_cfg.dev, "\nstarting isif_restore_defaults..."); +	isif_cfg.bayer.config_params = isif_config_defaults; +	/* Enable clock to ISIF, IPIPEIF and BL */ +	vpss_enable_clock(VPSS_CCDC_CLOCK, 1); +	vpss_enable_clock(VPSS_IPIPEIF_CLOCK, 1); +	vpss_enable_clock(VPSS_BL_CLOCK, 1); +	/* Set default offset and gain */ +	isif_config_gain_offset(); +	vpss_select_ccdc_source(source); +	dev_dbg(isif_cfg.dev, "\nEnd of isif_restore_defaults..."); +} + +static int isif_open(struct device *device) +{ +	isif_restore_defaults(); +	return 0; +} + +/* This function will configure the window size to be capture in ISIF reg */ +static void isif_setwin(struct v4l2_rect *image_win, +			enum ccdc_frmfmt frm_fmt, int ppc) +{ +	int horz_start, horz_nr_pixels; +	int vert_start, vert_nr_lines; +	int mid_img = 0; + +	dev_dbg(isif_cfg.dev, "\nStarting isif_setwin..."); +	/* +	 * ppc - per pixel count. indicates how many pixels per cell +	 * output to SDRAM. example, for ycbcr, it is one y and one c, so 2. +	 * raw capture this is 1 +	 */ +	horz_start = image_win->left << (ppc - 1); +	horz_nr_pixels = ((image_win->width) << (ppc - 1)) - 1; + +	/* Writing the horizontal info into the registers */ +	regw(horz_start & START_PX_HOR_MASK, SPH); +	regw(horz_nr_pixels & NUM_PX_HOR_MASK, LNH); +	vert_start = image_win->top; + +	if (frm_fmt == CCDC_FRMFMT_INTERLACED) { +		vert_nr_lines = (image_win->height >> 1) - 1; +		vert_start >>= 1; +		/* To account for VD since line 0 doesn't have any data */ +		vert_start += 1; +	} else { +		/* To account for VD since line 0 doesn't have any data */ +		vert_start += 1; +		vert_nr_lines = image_win->height - 1; +		/* configure VDINT0 and VDINT1 */ +		mid_img = vert_start + (image_win->height / 2); +		regw(mid_img, VDINT1); +	} + +	regw(0, VDINT0); +	regw(vert_start & START_VER_ONE_MASK, SLV0); +	regw(vert_start & START_VER_TWO_MASK, SLV1); +	regw(vert_nr_lines & NUM_LINES_VER, LNV); +} + +static void isif_config_bclamp(struct isif_black_clamp *bc) +{ +	u32 val; + +	/* +	 * DC Offset is always added to image data irrespective of bc enable +	 * status +	 */ +	regw(bc->dc_offset, CLDCOFST); + +	if (bc->en) { +		val = bc->bc_mode_color << ISIF_BC_MODE_COLOR_SHIFT; + +		/* Enable BC and horizontal clamp caculation paramaters */ +		val = val | 1 | (bc->horz.mode << ISIF_HORZ_BC_MODE_SHIFT); + +		regw(val, CLAMPCFG); + +		if (bc->horz.mode != ISIF_HORZ_BC_DISABLE) { +			/* +			 * Window count for calculation +			 * Base window selection +			 * pixel limit +			 * Horizontal size of window +			 * vertical size of the window +			 * Horizontal start position of the window +			 * Vertical start position of the window +			 */ +			val = bc->horz.win_count_calc | +			      ((!!bc->horz.base_win_sel_calc) << +				ISIF_HORZ_BC_WIN_SEL_SHIFT) | +			      ((!!bc->horz.clamp_pix_limit) << +				ISIF_HORZ_BC_PIX_LIMIT_SHIFT) | +			      (bc->horz.win_h_sz_calc << +				ISIF_HORZ_BC_WIN_H_SIZE_SHIFT) | +			      (bc->horz.win_v_sz_calc << +				ISIF_HORZ_BC_WIN_V_SIZE_SHIFT); +			regw(val, CLHWIN0); + +			regw(bc->horz.win_start_h_calc, CLHWIN1); +			regw(bc->horz.win_start_v_calc, CLHWIN2); +		} + +		/* vertical clamp caculation paramaters */ + +		/* Reset clamp value sel for previous line */ +		val |= +		(bc->vert.reset_val_sel << ISIF_VERT_BC_RST_VAL_SEL_SHIFT) | +		(bc->vert.line_ave_coef << ISIF_VERT_BC_LINE_AVE_COEF_SHIFT); +		regw(val, CLVWIN0); + +		/* Optical Black horizontal start position */ +		regw(bc->vert.ob_start_h, CLVWIN1); +		/* Optical Black vertical start position */ +		regw(bc->vert.ob_start_v, CLVWIN2); +		/* Optical Black vertical size for calculation */ +		regw(bc->vert.ob_v_sz_calc, CLVWIN3); +		/* Vertical start position for BC subtraction */ +		regw(bc->vert_start_sub, CLSV); +	} +} + +static void isif_config_linearization(struct isif_linearize *linearize) +{ +	u32 val, i; + +	if (!linearize->en) { +		regw(0, LINCFG0); +		return; +	} + +	/* shift value for correction & enable linearization (set lsb) */ +	val = (linearize->corr_shft << ISIF_LIN_CORRSFT_SHIFT) | 1; +	regw(val, LINCFG0); + +	/* Scale factor */ +	val = ((!!linearize->scale_fact.integer) << +	       ISIF_LIN_SCALE_FACT_INTEG_SHIFT) | +	       linearize->scale_fact.decimal; +	regw(val, LINCFG1); + +	for (i = 0; i < ISIF_LINEAR_TAB_SIZE; i++) { +		if (i % 2) +			regw_lin_tbl(linearize->table[i], ((i >> 1) << 2), 1); +		else +			regw_lin_tbl(linearize->table[i], ((i >> 1) << 2), 0); +	} +} + +static int isif_config_dfc(struct isif_dfc *vdfc) +{ +	/* initialize retries to loop for max ~ 250 usec */ +	u32 val, count, retries = loops_per_jiffy / (4000/HZ); +	int i; + +	if (!vdfc->en) +		return 0; + +	/* Correction mode */ +	val = (vdfc->corr_mode << ISIF_VDFC_CORR_MOD_SHIFT); + +	/* Correct whole line or partial */ +	if (vdfc->corr_whole_line) +		val |= 1 << ISIF_VDFC_CORR_WHOLE_LN_SHIFT; + +	/* level shift value */ +	val |= vdfc->def_level_shift << ISIF_VDFC_LEVEL_SHFT_SHIFT; + +	regw(val, DFCCTL); + +	/* Defect saturation level */ +	regw(vdfc->def_sat_level, VDFSATLV); + +	regw(vdfc->table[0].pos_vert, DFCMEM0); +	regw(vdfc->table[0].pos_horz, DFCMEM1); +	if (vdfc->corr_mode == ISIF_VDFC_NORMAL || +	    vdfc->corr_mode == ISIF_VDFC_HORZ_INTERPOL_IF_SAT) { +		regw(vdfc->table[0].level_at_pos, DFCMEM2); +		regw(vdfc->table[0].level_up_pixels, DFCMEM3); +		regw(vdfc->table[0].level_low_pixels, DFCMEM4); +	} + +	/* set DFCMARST and set DFCMWR */ +	val = regr(DFCMEMCTL) | (1 << ISIF_DFCMEMCTL_DFCMARST_SHIFT) | 1; +	regw(val, DFCMEMCTL); + +	count = retries; +	while (count && (regr(DFCMEMCTL) & 0x1)) +		count--; + +	if (!count) { +		dev_dbg(isif_cfg.dev, "defect table write timeout !!!\n"); +		return -1; +	} + +	for (i = 1; i < vdfc->num_vdefects; i++) { +		regw(vdfc->table[i].pos_vert, DFCMEM0); +		regw(vdfc->table[i].pos_horz, DFCMEM1); +		if (vdfc->corr_mode == ISIF_VDFC_NORMAL || +		    vdfc->corr_mode == ISIF_VDFC_HORZ_INTERPOL_IF_SAT) { +			regw(vdfc->table[i].level_at_pos, DFCMEM2); +			regw(vdfc->table[i].level_up_pixels, DFCMEM3); +			regw(vdfc->table[i].level_low_pixels, DFCMEM4); +		} +		val = regr(DFCMEMCTL); +		/* clear DFCMARST and set DFCMWR */ +		val &= ~BIT(ISIF_DFCMEMCTL_DFCMARST_SHIFT); +		val |= 1; +		regw(val, DFCMEMCTL); + +		count = retries; +		while (count && (regr(DFCMEMCTL) & 0x1)) +			count--; + +		if (!count) { +			dev_err(isif_cfg.dev, +				"defect table write timeout !!!\n"); +			return -1; +		} +	} +	if (vdfc->num_vdefects < ISIF_VDFC_TABLE_SIZE) { +		/* Extra cycle needed */ +		regw(0, DFCMEM0); +		regw(0x1FFF, DFCMEM1); +		regw(1, DFCMEMCTL); +	} + +	/* enable VDFC */ +	reg_modify((1 << ISIF_VDFC_EN_SHIFT), (1 << ISIF_VDFC_EN_SHIFT), +		   DFCCTL); +	return 0; +} + +static void isif_config_csc(struct isif_df_csc *df_csc) +{ +	u32 val1 = 0, val2 = 0, i; + +	if (!df_csc->csc.en) { +		regw(0, CSCCTL); +		return; +	} +	for (i = 0; i < ISIF_CSC_NUM_COEFF; i++) { +		if ((i % 2) == 0) { +			/* CSCM - LSB */ +			val1 = (df_csc->csc.coeff[i].integer << +				ISIF_CSC_COEF_INTEG_SHIFT) | +				df_csc->csc.coeff[i].decimal; +		} else { + +			/* CSCM - MSB */ +			val2 = (df_csc->csc.coeff[i].integer << +				ISIF_CSC_COEF_INTEG_SHIFT) | +				df_csc->csc.coeff[i].decimal; +			val2 <<= ISIF_CSCM_MSB_SHIFT; +			val2 |= val1; +			regw(val2, (CSCM0 + ((i - 1) << 1))); +		} +	} + +	/* program the active area */ +	regw(df_csc->start_pix, FMTSPH); +	/* +	 * one extra pixel as required for CSC. Actually number of +	 * pixel - 1 should be configured in this register. So we +	 * need to subtract 1 before writing to FMTSPH, but we will +	 * not do this since csc requires one extra pixel +	 */ +	regw(df_csc->num_pixels, FMTLNH); +	regw(df_csc->start_line, FMTSLV); +	/* +	 * one extra line as required for CSC. See reason documented for +	 * num_pixels +	 */ +	regw(df_csc->num_lines, FMTLNV); + +	/* Enable CSC */ +	regw(1, CSCCTL); +} + +static int isif_config_raw(void) +{ +	struct isif_params_raw *params = &isif_cfg.bayer; +	struct isif_config_params_raw *module_params = +		&isif_cfg.bayer.config_params; +	struct vpss_pg_frame_size frame_size; +	struct vpss_sync_pol sync; +	u32 val; + +	dev_dbg(isif_cfg.dev, "\nStarting isif_config_raw..\n"); + +	/* +	 * Configure CCDCFG register:- +	 * Set CCD Not to swap input since input is RAW data +	 * Set FID detection function to Latch at V-Sync +	 * Set WENLOG - isif valid area +	 * Set TRGSEL +	 * Set EXTRG +	 * Packed to 8 or 16 bits +	 */ + +	val = ISIF_YCINSWP_RAW | ISIF_CCDCFG_FIDMD_LATCH_VSYNC | +		ISIF_CCDCFG_WENLOG_AND | ISIF_CCDCFG_TRGSEL_WEN | +		ISIF_CCDCFG_EXTRG_DISABLE | isif_cfg.data_pack; + +	dev_dbg(isif_cfg.dev, "Writing 0x%x to ...CCDCFG \n", val); +	regw(val, CCDCFG); + +	/* +	 * Configure the vertical sync polarity(MODESET.VDPOL) +	 * Configure the horizontal sync polarity (MODESET.HDPOL) +	 * Configure frame id polarity (MODESET.FLDPOL) +	 * Configure data polarity +	 * Configure External WEN Selection +	 * Configure frame format(progressive or interlace) +	 * Configure pixel format (Input mode) +	 * Configure the data shift +	 */ + +	val = ISIF_VDHDOUT_INPUT | (params->vd_pol << ISIF_VD_POL_SHIFT) | +		(params->hd_pol << ISIF_HD_POL_SHIFT) | +		(params->fid_pol << ISIF_FID_POL_SHIFT) | +		(ISIF_DATAPOL_NORMAL << ISIF_DATAPOL_SHIFT) | +		(ISIF_EXWEN_DISABLE << ISIF_EXWEN_SHIFT) | +		(params->frm_fmt << ISIF_FRM_FMT_SHIFT) | +		(params->pix_fmt << ISIF_INPUT_SHIFT) | +		(params->config_params.data_shift << ISIF_DATASFT_SHIFT); + +	regw(val, MODESET); +	dev_dbg(isif_cfg.dev, "Writing 0x%x to MODESET...\n", val); + +	/* +	 * Configure GAMMAWD register +	 * CFA pattern setting +	 */ +	val = params->cfa_pat << ISIF_GAMMAWD_CFA_SHIFT; + +	/* Gamma msb */ +	if (module_params->compress.alg == ISIF_ALAW) +		val |= ISIF_ALAW_ENABLE; + +	val |= (params->data_msb << ISIF_ALAW_GAMMA_WD_SHIFT); +	regw(val, CGAMMAWD); + +	/* Configure DPCM compression settings */ +	if (module_params->compress.alg == ISIF_DPCM) { +		val =  BIT(ISIF_DPCM_EN_SHIFT) | +		       (module_params->compress.pred << +		       ISIF_DPCM_PREDICTOR_SHIFT); +	} + +	regw(val, MISC); + +	/* Configure Gain & Offset */ +	isif_config_gain_offset(); + +	/* Configure Color pattern */ +	val = (params->config_params.col_pat_field0.olop) | +	      (params->config_params.col_pat_field0.olep << 2) | +	      (params->config_params.col_pat_field0.elop << 4) | +	      (params->config_params.col_pat_field0.elep << 6) | +	      (params->config_params.col_pat_field1.olop << 8) | +	      (params->config_params.col_pat_field1.olep << 10) | +	      (params->config_params.col_pat_field1.elop << 12) | +	      (params->config_params.col_pat_field1.elep << 14); +	regw(val, CCOLP); +	dev_dbg(isif_cfg.dev, "Writing %x to CCOLP ...\n", val); + +	/* Configure HSIZE register  */ +	val = (!!params->horz_flip_en) << ISIF_HSIZE_FLIP_SHIFT; + +	/* calculate line offset in 32 bytes based on pack value */ +	if (isif_cfg.data_pack == ISIF_PACK_8BIT) +		val |= ((params->win.width + 31) >> 5); +	else if (isif_cfg.data_pack == ISIF_PACK_12BIT) +		val |= (((params->win.width + +		       (params->win.width >> 2)) + 31) >> 5); +	else +		val |= (((params->win.width * 2) + 31) >> 5); +	regw(val, HSIZE); + +	/* Configure SDOFST register  */ +	if (params->frm_fmt == CCDC_FRMFMT_INTERLACED) { +		if (params->image_invert_en) { +			/* For interlace inverse mode */ +			regw(0x4B6D, SDOFST); +			dev_dbg(isif_cfg.dev, "Writing 0x4B6D to SDOFST...\n"); +		} else { +			/* For interlace non inverse mode */ +			regw(0x0B6D, SDOFST); +			dev_dbg(isif_cfg.dev, "Writing 0x0B6D to SDOFST...\n"); +		} +	} else if (params->frm_fmt == CCDC_FRMFMT_PROGRESSIVE) { +		if (params->image_invert_en) { +			/* For progressive inverse mode */ +			regw(0x4000, SDOFST); +			dev_dbg(isif_cfg.dev, "Writing 0x4000 to SDOFST...\n"); +		} else { +			/* For progressive non inverse mode */ +			regw(0x0000, SDOFST); +			dev_dbg(isif_cfg.dev, "Writing 0x0000 to SDOFST...\n"); +		} +	} + +	/* Configure video window */ +	isif_setwin(¶ms->win, params->frm_fmt, 1); + +	/* Configure Black Clamp */ +	isif_config_bclamp(&module_params->bclamp); + +	/* Configure Vertical Defection Pixel Correction */ +	if (isif_config_dfc(&module_params->dfc) < 0) +		return -EFAULT; + +	if (!module_params->df_csc.df_or_csc) +		/* Configure Color Space Conversion */ +		isif_config_csc(&module_params->df_csc); + +	isif_config_linearization(&module_params->linearize); + +	/* Configure Culling */ +	isif_config_culling(&module_params->culling); + +	/* Configure horizontal and vertical offsets(DFC,LSC,Gain) */ +	regw(module_params->horz_offset, DATAHOFST); +	regw(module_params->vert_offset, DATAVOFST); + +	/* Setup test pattern if enabled */ +	if (params->config_params.test_pat_gen) { +		/* Use the HD/VD pol settings from user */ +		sync.ccdpg_hdpol = params->hd_pol; +		sync.ccdpg_vdpol = params->vd_pol; +		dm365_vpss_set_sync_pol(sync); +		frame_size.hlpfr = isif_cfg.bayer.win.width; +		frame_size.pplen = isif_cfg.bayer.win.height; +		dm365_vpss_set_pg_frame_size(frame_size); +		vpss_select_ccdc_source(VPSS_PGLPBK); +	} + +	dev_dbg(isif_cfg.dev, "\nEnd of isif_config_ycbcr...\n"); +	return 0; +} + +static int isif_set_buftype(enum ccdc_buftype buf_type) +{ +	if (isif_cfg.if_type == VPFE_RAW_BAYER) +		isif_cfg.bayer.buf_type = buf_type; +	else +		isif_cfg.ycbcr.buf_type = buf_type; + +	return 0; + +} +static enum ccdc_buftype isif_get_buftype(void) +{ +	if (isif_cfg.if_type == VPFE_RAW_BAYER) +		return isif_cfg.bayer.buf_type; + +	return isif_cfg.ycbcr.buf_type; +} + +static int isif_enum_pix(u32 *pix, int i) +{ +	int ret = -EINVAL; + +	if (isif_cfg.if_type == VPFE_RAW_BAYER) { +		if (i < ARRAY_SIZE(isif_raw_bayer_pix_formats)) { +			*pix = isif_raw_bayer_pix_formats[i]; +			ret = 0; +		} +	} else { +		if (i < ARRAY_SIZE(isif_raw_yuv_pix_formats)) { +			*pix = isif_raw_yuv_pix_formats[i]; +			ret = 0; +		} +	} + +	return ret; +} + +static int isif_set_pixel_format(unsigned int pixfmt) +{ +	if (isif_cfg.if_type == VPFE_RAW_BAYER) { +		if (pixfmt == V4L2_PIX_FMT_SBGGR8) { +			if ((isif_cfg.bayer.config_params.compress.alg != +			     ISIF_ALAW) && +			    (isif_cfg.bayer.config_params.compress.alg != +			     ISIF_DPCM)) { +				dev_dbg(isif_cfg.dev, +					"Either configure A-Law or DPCM\n"); +				return -EINVAL; +			} +			isif_cfg.data_pack = ISIF_PACK_8BIT; +		} else if (pixfmt == V4L2_PIX_FMT_SBGGR16) { +			isif_cfg.bayer.config_params.compress.alg = +					ISIF_NO_COMPRESSION; +			isif_cfg.data_pack = ISIF_PACK_16BIT; +		} else +			return -EINVAL; +		isif_cfg.bayer.pix_fmt = CCDC_PIXFMT_RAW; +	} else { +		if (pixfmt == V4L2_PIX_FMT_YUYV) +			isif_cfg.ycbcr.pix_order = CCDC_PIXORDER_YCBYCR; +		else if (pixfmt == V4L2_PIX_FMT_UYVY) +			isif_cfg.ycbcr.pix_order = CCDC_PIXORDER_CBYCRY; +		else +			return -EINVAL; +		isif_cfg.data_pack = ISIF_PACK_8BIT; +	} +	return 0; +} + +static u32 isif_get_pixel_format(void) +{ +	u32 pixfmt; + +	if (isif_cfg.if_type == VPFE_RAW_BAYER) +		if (isif_cfg.bayer.config_params.compress.alg == ISIF_ALAW || +		    isif_cfg.bayer.config_params.compress.alg == ISIF_DPCM) +			pixfmt = V4L2_PIX_FMT_SBGGR8; +		else +			pixfmt = V4L2_PIX_FMT_SBGGR16; +	else { +		if (isif_cfg.ycbcr.pix_order == CCDC_PIXORDER_YCBYCR) +			pixfmt = V4L2_PIX_FMT_YUYV; +		else +			pixfmt = V4L2_PIX_FMT_UYVY; +	} +	return pixfmt; +} + +static int isif_set_image_window(struct v4l2_rect *win) +{ +	if (isif_cfg.if_type == VPFE_RAW_BAYER) { +		isif_cfg.bayer.win.top = win->top; +		isif_cfg.bayer.win.left = win->left; +		isif_cfg.bayer.win.width = win->width; +		isif_cfg.bayer.win.height = win->height; +	} else { +		isif_cfg.ycbcr.win.top = win->top; +		isif_cfg.ycbcr.win.left = win->left; +		isif_cfg.ycbcr.win.width = win->width; +		isif_cfg.ycbcr.win.height = win->height; +	} +	return 0; +} + +static void isif_get_image_window(struct v4l2_rect *win) +{ +	if (isif_cfg.if_type == VPFE_RAW_BAYER) +		*win = isif_cfg.bayer.win; +	else +		*win = isif_cfg.ycbcr.win; +} + +static unsigned int isif_get_line_length(void) +{ +	unsigned int len; + +	if (isif_cfg.if_type == VPFE_RAW_BAYER) { +		if (isif_cfg.data_pack == ISIF_PACK_8BIT) +			len = ((isif_cfg.bayer.win.width)); +		else if (isif_cfg.data_pack == ISIF_PACK_12BIT) +			len = (((isif_cfg.bayer.win.width * 2) + +				 (isif_cfg.bayer.win.width >> 2))); +		else +			len = (((isif_cfg.bayer.win.width * 2))); +	} else +		len = (((isif_cfg.ycbcr.win.width * 2))); +	return ALIGN(len, 32); +} + +static int isif_set_frame_format(enum ccdc_frmfmt frm_fmt) +{ +	if (isif_cfg.if_type == VPFE_RAW_BAYER) +		isif_cfg.bayer.frm_fmt = frm_fmt; +	else +		isif_cfg.ycbcr.frm_fmt = frm_fmt; +	return 0; +} +static enum ccdc_frmfmt isif_get_frame_format(void) +{ +	if (isif_cfg.if_type == VPFE_RAW_BAYER) +		return isif_cfg.bayer.frm_fmt; +	return isif_cfg.ycbcr.frm_fmt; +} + +static int isif_getfid(void) +{ +	return (regr(MODESET) >> 15) & 0x1; +} + +/* misc operations */ +static void isif_setfbaddr(unsigned long addr) +{ +	regw((addr >> 21) & 0x07ff, CADU); +	regw((addr >> 5) & 0x0ffff, CADL); +} + +static int isif_set_hw_if_params(struct vpfe_hw_if_param *params) +{ +	isif_cfg.if_type = params->if_type; + +	switch (params->if_type) { +	case VPFE_BT656: +	case VPFE_BT656_10BIT: +	case VPFE_YCBCR_SYNC_8: +		isif_cfg.ycbcr.pix_fmt = CCDC_PIXFMT_YCBCR_8BIT; +		isif_cfg.ycbcr.pix_order = CCDC_PIXORDER_CBYCRY; +		break; +	case VPFE_BT1120: +	case VPFE_YCBCR_SYNC_16: +		isif_cfg.ycbcr.pix_fmt = CCDC_PIXFMT_YCBCR_16BIT; +		isif_cfg.ycbcr.pix_order = CCDC_PIXORDER_CBYCRY; +		break; +	case VPFE_RAW_BAYER: +		isif_cfg.bayer.pix_fmt = CCDC_PIXFMT_RAW; +		break; +	default: +		dev_dbg(isif_cfg.dev, "Invalid interface type\n"); +		return -EINVAL; +	} + +	return 0; +} + +/* This function will configure ISIF for YCbCr parameters. */ +static int isif_config_ycbcr(void) +{ +	struct isif_ycbcr_config *params = &isif_cfg.ycbcr; +	struct vpss_pg_frame_size frame_size; +	u32 modeset = 0, ccdcfg = 0; +	struct vpss_sync_pol sync; + +	dev_dbg(isif_cfg.dev, "\nStarting isif_config_ycbcr..."); + +	/* configure pixel format or input mode */ +	modeset = modeset | (params->pix_fmt << ISIF_INPUT_SHIFT) | +		  (params->frm_fmt << ISIF_FRM_FMT_SHIFT) | +		  (params->fid_pol << ISIF_FID_POL_SHIFT) | +		  (params->hd_pol << ISIF_HD_POL_SHIFT) | +		  (params->vd_pol << ISIF_VD_POL_SHIFT); + +	/* pack the data to 8-bit ISIFCFG */ +	switch (isif_cfg.if_type) { +	case VPFE_BT656: +		if (params->pix_fmt != CCDC_PIXFMT_YCBCR_8BIT) { +			dev_dbg(isif_cfg.dev, "Invalid pix_fmt(input mode)\n"); +			return -EINVAL; +		} +		modeset |= (VPFE_PINPOL_NEGATIVE << ISIF_VD_POL_SHIFT); +		regw(3, REC656IF); +		ccdcfg = ccdcfg | ISIF_DATA_PACK8 | ISIF_YCINSWP_YCBCR; +		break; +	case VPFE_BT656_10BIT: +		if (params->pix_fmt != CCDC_PIXFMT_YCBCR_8BIT) { +			dev_dbg(isif_cfg.dev, "Invalid pix_fmt(input mode)\n"); +			return -EINVAL; +		} +		/* setup BT.656, embedded sync  */ +		regw(3, REC656IF); +		/* enable 10 bit mode in ccdcfg */ +		ccdcfg = ccdcfg | ISIF_DATA_PACK8 | ISIF_YCINSWP_YCBCR | +			ISIF_BW656_ENABLE; +		break; +	case VPFE_BT1120: +		if (params->pix_fmt != CCDC_PIXFMT_YCBCR_16BIT) { +			dev_dbg(isif_cfg.dev, "Invalid pix_fmt(input mode)\n"); +			return -EINVAL; +		} +		regw(3, REC656IF); +		break; + +	case VPFE_YCBCR_SYNC_8: +		ccdcfg |= ISIF_DATA_PACK8; +		ccdcfg |= ISIF_YCINSWP_YCBCR; +		if (params->pix_fmt != CCDC_PIXFMT_YCBCR_8BIT) { +			dev_dbg(isif_cfg.dev, "Invalid pix_fmt(input mode)\n"); +			return -EINVAL; +		} +		break; +	case VPFE_YCBCR_SYNC_16: +		if (params->pix_fmt != CCDC_PIXFMT_YCBCR_16BIT) { +			dev_dbg(isif_cfg.dev, "Invalid pix_fmt(input mode)\n"); +			return -EINVAL; +		} +		break; +	default: +		/* should never come here */ +		dev_dbg(isif_cfg.dev, "Invalid interface type\n"); +		return -EINVAL; +	} + +	regw(modeset, MODESET); + +	/* Set up pix order */ +	ccdcfg |= params->pix_order << ISIF_PIX_ORDER_SHIFT; + +	regw(ccdcfg, CCDCFG); + +	/* configure video window */ +	if ((isif_cfg.if_type == VPFE_BT1120) || +	    (isif_cfg.if_type == VPFE_YCBCR_SYNC_16)) +		isif_setwin(¶ms->win, params->frm_fmt, 1); +	else +		isif_setwin(¶ms->win, params->frm_fmt, 2); + +	/* +	 * configure the horizontal line offset +	 * this is done by rounding up width to a multiple of 16 pixels +	 * and multiply by two to account for y:cb:cr 4:2:2 data +	 */ +	regw(((((params->win.width * 2) + 31) & 0xffffffe0) >> 5), HSIZE); + +	/* configure the memory line offset */ +	if ((params->frm_fmt == CCDC_FRMFMT_INTERLACED) && +	    (params->buf_type == CCDC_BUFTYPE_FLD_INTERLEAVED)) +		/* two fields are interleaved in memory */ +		regw(0x00000249, SDOFST); + +	/* Setup test pattern if enabled */ +	if (isif_cfg.bayer.config_params.test_pat_gen) { +		sync.ccdpg_hdpol = params->hd_pol; +		sync.ccdpg_vdpol = params->vd_pol; +		dm365_vpss_set_sync_pol(sync); +		dm365_vpss_set_pg_frame_size(frame_size); +	} +	return 0; +} + +static int isif_configure(void) +{ +	if (isif_cfg.if_type == VPFE_RAW_BAYER) +		return isif_config_raw(); +	return isif_config_ycbcr(); +} + +static int isif_close(struct device *device) +{ +	/* copy defaults to module params */ +	isif_cfg.bayer.config_params = isif_config_defaults; +	return 0; +} + +static struct ccdc_hw_device isif_hw_dev = { +	.name = "ISIF", +	.owner = THIS_MODULE, +	.hw_ops = { +		.open = isif_open, +		.close = isif_close, +		.enable = isif_enable, +		.enable_out_to_sdram = isif_enable_output_to_sdram, +		.set_hw_if_params = isif_set_hw_if_params, +		.configure = isif_configure, +		.set_buftype = isif_set_buftype, +		.get_buftype = isif_get_buftype, +		.enum_pix = isif_enum_pix, +		.set_pixel_format = isif_set_pixel_format, +		.get_pixel_format = isif_get_pixel_format, +		.set_frame_format = isif_set_frame_format, +		.get_frame_format = isif_get_frame_format, +		.set_image_window = isif_set_image_window, +		.get_image_window = isif_get_image_window, +		.get_line_length = isif_get_line_length, +		.setfbaddr = isif_setfbaddr, +		.getfid = isif_getfid, +	}, +}; + +static int isif_probe(struct platform_device *pdev) +{ +	void (*setup_pinmux)(void); +	struct resource	*res; +	void *__iomem addr; +	int status = 0, i; + +	/* Platform data holds setup_pinmux function ptr */ +	if (!pdev->dev.platform_data) +		return -ENODEV; + +	/* +	 * first try to register with vpfe. If not correct platform, then we +	 * don't have to iomap +	 */ +	status = vpfe_register_ccdc_device(&isif_hw_dev); +	if (status < 0) +		return status; + +	setup_pinmux = pdev->dev.platform_data; +	/* +	 * setup Mux configuration for ccdc which may be different for +	 * different SoCs using this CCDC +	 */ +	setup_pinmux(); + +	i = 0; +	/* Get the ISIF base address, linearization table0 and table1 addr. */ +	while (i < 3) { +		res = platform_get_resource(pdev, IORESOURCE_MEM, i); +		if (!res) { +			status = -ENODEV; +			goto fail_nobase_res; +		} +		res = request_mem_region(res->start, resource_size(res), +					 res->name); +		if (!res) { +			status = -EBUSY; +			goto fail_nobase_res; +		} +		addr = ioremap_nocache(res->start, resource_size(res)); +		if (!addr) { +			status = -ENOMEM; +			goto fail_base_iomap; +		} +		switch (i) { +		case 0: +			/* ISIF base address */ +			isif_cfg.base_addr = addr; +			break; +		case 1: +			/* ISIF linear tbl0 address */ +			isif_cfg.linear_tbl0_addr = addr; +			break; +		default: +			/* ISIF linear tbl0 address */ +			isif_cfg.linear_tbl1_addr = addr; +			break; +		} +		i++; +	} +	isif_cfg.dev = &pdev->dev; + +	printk(KERN_NOTICE "%s is registered with vpfe.\n", +		isif_hw_dev.name); +	return 0; +fail_base_iomap: +	release_mem_region(res->start, resource_size(res)); +	i--; +fail_nobase_res: +	if (isif_cfg.base_addr) +		iounmap(isif_cfg.base_addr); +	if (isif_cfg.linear_tbl0_addr) +		iounmap(isif_cfg.linear_tbl0_addr); + +	while (i >= 0) { +		res = platform_get_resource(pdev, IORESOURCE_MEM, i); +		release_mem_region(res->start, resource_size(res)); +		i--; +	} +	vpfe_unregister_ccdc_device(&isif_hw_dev); +	return status; +} + +static int isif_remove(struct platform_device *pdev) +{ +	struct resource	*res; +	int i = 0; + +	iounmap(isif_cfg.base_addr); +	iounmap(isif_cfg.linear_tbl0_addr); +	iounmap(isif_cfg.linear_tbl1_addr); +	while (i < 3) { +		res = platform_get_resource(pdev, IORESOURCE_MEM, i); +		if (res) +			release_mem_region(res->start, resource_size(res)); +		i++; +	} +	vpfe_unregister_ccdc_device(&isif_hw_dev); +	return 0; +} + +static struct platform_driver isif_driver = { +	.driver = { +		.name	= "isif", +		.owner = THIS_MODULE, +	}, +	.remove = isif_remove, +	.probe = isif_probe, +}; + +module_platform_driver(isif_driver); + +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/davinci/isif_regs.h b/drivers/media/platform/davinci/isif_regs.h new file mode 100644 index 00000000000..3993aece821 --- /dev/null +++ b/drivers/media/platform/davinci/isif_regs.h @@ -0,0 +1,269 @@ +/* + * Copyright (C) 2008-2009 Texas Instruments Inc + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA + */ +#ifndef _ISIF_REGS_H +#define _ISIF_REGS_H + +/* ISIF registers relative offsets */ +#define SYNCEN					0x00 +#define MODESET					0x04 +#define HDW					0x08 +#define VDW					0x0c +#define PPLN					0x10 +#define LPFR					0x14 +#define SPH					0x18 +#define LNH					0x1c +#define SLV0					0x20 +#define SLV1					0x24 +#define LNV					0x28 +#define CULH					0x2c +#define CULV					0x30 +#define HSIZE					0x34 +#define SDOFST					0x38 +#define CADU					0x3c +#define CADL					0x40 +#define LINCFG0					0x44 +#define LINCFG1					0x48 +#define CCOLP					0x4c +#define CRGAIN 					0x50 +#define CGRGAIN					0x54 +#define CGBGAIN					0x58 +#define CBGAIN					0x5c +#define COFSTA					0x60 +#define FLSHCFG0				0x64 +#define FLSHCFG1				0x68 +#define FLSHCFG2				0x6c +#define VDINT0					0x70 +#define VDINT1					0x74 +#define VDINT2					0x78 +#define MISC 					0x7c +#define CGAMMAWD				0x80 +#define REC656IF				0x84 +#define CCDCFG					0x88 +/***************************************************** +* Defect Correction registers +*****************************************************/ +#define DFCCTL					0x8c +#define VDFSATLV				0x90 +#define DFCMEMCTL				0x94 +#define DFCMEM0					0x98 +#define DFCMEM1					0x9c +#define DFCMEM2					0xa0 +#define DFCMEM3					0xa4 +#define DFCMEM4					0xa8 +/**************************************************** +* Black Clamp registers +****************************************************/ +#define CLAMPCFG				0xac +#define CLDCOFST				0xb0 +#define CLSV					0xb4 +#define CLHWIN0					0xb8 +#define CLHWIN1					0xbc +#define CLHWIN2					0xc0 +#define CLVRV					0xc4 +#define CLVWIN0					0xc8 +#define CLVWIN1					0xcc +#define CLVWIN2					0xd0 +#define CLVWIN3					0xd4 +/**************************************************** +* Lense Shading Correction +****************************************************/ +#define DATAHOFST				0xd8 +#define DATAVOFST				0xdc +#define LSCHVAL					0xe0 +#define LSCVVAL					0xe4 +#define TWODLSCCFG				0xe8 +#define TWODLSCOFST				0xec +#define TWODLSCINI				0xf0 +#define TWODLSCGRBU				0xf4 +#define TWODLSCGRBL				0xf8 +#define TWODLSCGROF				0xfc +#define TWODLSCORBU				0x100 +#define TWODLSCORBL				0x104 +#define TWODLSCOROF				0x108 +#define TWODLSCIRQEN				0x10c +#define TWODLSCIRQST				0x110 +/**************************************************** +* Data formatter +****************************************************/ +#define FMTCFG					0x114 +#define FMTPLEN					0x118 +#define FMTSPH					0x11c +#define FMTLNH					0x120 +#define FMTSLV					0x124 +#define FMTLNV					0x128 +#define FMTRLEN					0x12c +#define FMTHCNT					0x130 +#define FMTAPTR_BASE				0x134 +/* Below macro for addresses FMTAPTR0 - FMTAPTR15 */ +#define FMTAPTR(i)			(FMTAPTR_BASE + (i * 4)) +#define FMTPGMVF0				0x174 +#define FMTPGMVF1				0x178 +#define FMTPGMAPU0				0x17c +#define FMTPGMAPU1				0x180 +#define FMTPGMAPS0				0x184 +#define FMTPGMAPS1				0x188 +#define FMTPGMAPS2				0x18c +#define FMTPGMAPS3				0x190 +#define FMTPGMAPS4				0x194 +#define FMTPGMAPS5				0x198 +#define FMTPGMAPS6				0x19c +#define FMTPGMAPS7				0x1a0 +/************************************************ +* Color Space Converter +************************************************/ +#define CSCCTL					0x1a4 +#define CSCM0					0x1a8 +#define CSCM1					0x1ac +#define CSCM2					0x1b0 +#define CSCM3					0x1b4 +#define CSCM4					0x1b8 +#define CSCM5					0x1bc +#define CSCM6					0x1c0 +#define CSCM7					0x1c4 +#define OBWIN0					0x1c8 +#define OBWIN1					0x1cc +#define OBWIN2					0x1d0 +#define OBWIN3					0x1d4 +#define OBVAL0					0x1d8 +#define OBVAL1					0x1dc +#define OBVAL2					0x1e0 +#define OBVAL3					0x1e4 +#define OBVAL4					0x1e8 +#define OBVAL5					0x1ec +#define OBVAL6					0x1f0 +#define OBVAL7					0x1f4 +#define CLKCTL					0x1f8 + +/* Masks & Shifts below */ +#define START_PX_HOR_MASK			0x7FFF +#define NUM_PX_HOR_MASK				0x7FFF +#define START_VER_ONE_MASK			0x7FFF +#define START_VER_TWO_MASK			0x7FFF +#define NUM_LINES_VER				0x7FFF + +/* gain - offset masks */ +#define GAIN_INTEGER_SHIFT			9 +#define OFFSET_MASK				0xFFF +#define GAIN_SDRAM_EN_SHIFT			12 +#define GAIN_IPIPE_EN_SHIFT			13 +#define GAIN_H3A_EN_SHIFT			14 +#define OFST_SDRAM_EN_SHIFT			8 +#define OFST_IPIPE_EN_SHIFT			9 +#define OFST_H3A_EN_SHIFT			10 +#define GAIN_OFFSET_EN_MASK			0x7700 + +/* Culling */ +#define CULL_PAT_EVEN_LINE_SHIFT		8 + +/* CCDCFG register */ +#define ISIF_YCINSWP_RAW			(0x00 << 4) +#define ISIF_YCINSWP_YCBCR			(0x01 << 4) +#define ISIF_CCDCFG_FIDMD_LATCH_VSYNC		(0x00 << 6) +#define ISIF_CCDCFG_WENLOG_AND			(0x00 << 8) +#define ISIF_CCDCFG_TRGSEL_WEN			(0x00 << 9) +#define ISIF_CCDCFG_EXTRG_DISABLE		(0x00 << 10) +#define ISIF_LATCH_ON_VSYNC_DISABLE		(0x01 << 15) +#define ISIF_LATCH_ON_VSYNC_ENABLE		(0x00 << 15) +#define ISIF_DATA_PACK_MASK			3 +#define ISIF_DATA_PACK16			0 +#define ISIF_DATA_PACK12			1 +#define ISIF_DATA_PACK8				2 +#define ISIF_PIX_ORDER_SHIFT			11 +#define ISIF_BW656_ENABLE			(0x01 << 5) + +/* MODESET registers */ +#define ISIF_VDHDOUT_INPUT			(0x00 << 0) +#define ISIF_INPUT_SHIFT			12 +#define ISIF_RAW_INPUT_MODE			0 +#define ISIF_FID_POL_SHIFT			4 +#define ISIF_HD_POL_SHIFT			3 +#define ISIF_VD_POL_SHIFT			2 +#define ISIF_DATAPOL_NORMAL			0 +#define ISIF_DATAPOL_SHIFT			6 +#define ISIF_EXWEN_DISABLE 			0 +#define ISIF_EXWEN_SHIFT			5 +#define ISIF_FRM_FMT_SHIFT			7 +#define ISIF_DATASFT_SHIFT			8 +#define ISIF_LPF_SHIFT				14 +#define ISIF_LPF_MASK				1 + +/* GAMMAWD registers */ +#define ISIF_ALAW_GAMMA_WD_MASK			0xF +#define ISIF_ALAW_GAMMA_WD_SHIFT		1 +#define ISIF_ALAW_ENABLE			1 +#define ISIF_GAMMAWD_CFA_SHIFT			5 + +/* HSIZE registers */ +#define ISIF_HSIZE_FLIP_MASK			1 +#define ISIF_HSIZE_FLIP_SHIFT			12 + +/* MISC registers */ +#define ISIF_DPCM_EN_SHIFT			12 +#define ISIF_DPCM_PREDICTOR_SHIFT		13 + +/* Black clamp related */ +#define ISIF_BC_MODE_COLOR_SHIFT		4 +#define ISIF_HORZ_BC_MODE_SHIFT			1 +#define ISIF_HORZ_BC_WIN_SEL_SHIFT		5 +#define ISIF_HORZ_BC_PIX_LIMIT_SHIFT		6 +#define ISIF_HORZ_BC_WIN_H_SIZE_SHIFT		8 +#define ISIF_HORZ_BC_WIN_V_SIZE_SHIFT		12 +#define	ISIF_VERT_BC_RST_VAL_SEL_SHIFT		4 +#define ISIF_VERT_BC_LINE_AVE_COEF_SHIFT	8 + +/* VDFC registers */ +#define ISIF_VDFC_EN_SHIFT			4 +#define ISIF_VDFC_CORR_MOD_SHIFT		5 +#define ISIF_VDFC_CORR_WHOLE_LN_SHIFT		7 +#define ISIF_VDFC_LEVEL_SHFT_SHIFT		8 +#define ISIF_VDFC_POS_MASK			0x1FFF +#define ISIF_DFCMEMCTL_DFCMARST_SHIFT		2 + +/* CSC registers */ +#define ISIF_CSC_COEF_INTEG_MASK		7 +#define ISIF_CSC_COEF_DECIMAL_MASK		0x1f +#define ISIF_CSC_COEF_INTEG_SHIFT		5 +#define ISIF_CSCM_MSB_SHIFT			8 +#define ISIF_DF_CSC_SPH_MASK			0x1FFF +#define ISIF_DF_CSC_LNH_MASK			0x1FFF +#define ISIF_DF_CSC_SLV_MASK			0x1FFF +#define ISIF_DF_CSC_LNV_MASK			0x1FFF +#define ISIF_DF_NUMLINES			0x7FFF +#define ISIF_DF_NUMPIX				0x1FFF + +/* Offsets for LSC/DFC/Gain */ +#define ISIF_DATA_H_OFFSET_MASK			0x1FFF +#define ISIF_DATA_V_OFFSET_MASK			0x1FFF + +/* Linearization */ +#define ISIF_LIN_CORRSFT_SHIFT			4 +#define ISIF_LIN_SCALE_FACT_INTEG_SHIFT		10 + + +/* Pattern registers */ +#define ISIF_PG_EN				(1 << 3) +#define ISIF_SEL_PG_SRC				(3 << 4) +#define ISIF_PG_VD_POL_SHIFT			0 +#define ISIF_PG_HD_POL_SHIFT			1 + +/*random other junk*/ +#define ISIF_SYNCEN_VDHDEN_MASK			(1 << 0) +#define ISIF_SYNCEN_WEN_MASK			(1 << 1) +#define ISIF_SYNCEN_WEN_SHIFT			1 + +#endif diff --git a/drivers/media/platform/davinci/vpbe.c b/drivers/media/platform/davinci/vpbe.c new file mode 100644 index 00000000000..33b9660b7f7 --- /dev/null +++ b/drivers/media/platform/davinci/vpbe.c @@ -0,0 +1,872 @@ +/* + * Copyright (C) 2010 Texas Instruments Inc + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation version 2. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/string.h> +#include <linux/wait.h> +#include <linux/time.h> +#include <linux/platform_device.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/clk.h> +#include <linux/err.h> + +#include <media/v4l2-device.h> +#include <media/davinci/vpbe_types.h> +#include <media/davinci/vpbe.h> +#include <media/davinci/vpss.h> +#include <media/davinci/vpbe_venc.h> + +#define VPBE_DEFAULT_OUTPUT	"Composite" +#define VPBE_DEFAULT_MODE	"ntsc" + +static char *def_output = VPBE_DEFAULT_OUTPUT; +static char *def_mode = VPBE_DEFAULT_MODE; +static int debug; + +module_param(def_output, charp, S_IRUGO); +module_param(def_mode, charp, S_IRUGO); +module_param(debug, int, 0644); + +MODULE_PARM_DESC(def_output, "vpbe output name (default:Composite)"); +MODULE_PARM_DESC(def_mode, "vpbe output mode name (default:ntsc"); +MODULE_PARM_DESC(debug, "Debug level 0-1"); + +MODULE_DESCRIPTION("TI DMXXX VPBE Display controller"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Texas Instruments"); + +/** + * vpbe_current_encoder_info - Get config info for current encoder + * @vpbe_dev - vpbe device ptr + * + * Return ptr to current encoder config info + */ +static struct encoder_config_info* +vpbe_current_encoder_info(struct vpbe_device *vpbe_dev) +{ +	struct vpbe_config *cfg = vpbe_dev->cfg; +	int index = vpbe_dev->current_sd_index; + +	return ((index == 0) ? &cfg->venc : +				&cfg->ext_encoders[index-1]); +} + +/** + * vpbe_find_encoder_sd_index - Given a name find encoder sd index + * + * @vpbe_config - ptr to vpbe cfg + * @output_index - index used by application + * + * Return sd index of the encoder + */ +static int vpbe_find_encoder_sd_index(struct vpbe_config *cfg, +			     int index) +{ +	char *encoder_name = cfg->outputs[index].subdev_name; +	int i; + +	/* Venc is always first	*/ +	if (!strcmp(encoder_name, cfg->venc.module_name)) +		return 0; + +	for (i = 0; i < cfg->num_ext_encoders; i++) { +		if (!strcmp(encoder_name, +		     cfg->ext_encoders[i].module_name)) +			return i+1; +	} + +	return -EINVAL; +} + +/** + * vpbe_g_cropcap - Get crop capabilities of the display + * @vpbe_dev - vpbe device ptr + * @cropcap - cropcap is a ptr to struct v4l2_cropcap + * + * Update the crop capabilities in crop cap for current + * mode + */ +static int vpbe_g_cropcap(struct vpbe_device *vpbe_dev, +			  struct v4l2_cropcap *cropcap) +{ +	if (NULL == cropcap) +		return -EINVAL; +	cropcap->bounds.left = 0; +	cropcap->bounds.top = 0; +	cropcap->bounds.width = vpbe_dev->current_timings.xres; +	cropcap->bounds.height = vpbe_dev->current_timings.yres; +	cropcap->defrect = cropcap->bounds; + +	return 0; +} + +/** + * vpbe_enum_outputs - enumerate outputs + * @vpbe_dev - vpbe device ptr + * @output - ptr to v4l2_output structure + * + * Enumerates the outputs available at the vpbe display + * returns the status, -EINVAL if end of output list + */ +static int vpbe_enum_outputs(struct vpbe_device *vpbe_dev, +			     struct v4l2_output *output) +{ +	struct vpbe_config *cfg = vpbe_dev->cfg; +	int temp_index = output->index; + +	if (temp_index >= cfg->num_outputs) +		return -EINVAL; + +	*output = cfg->outputs[temp_index].output; +	output->index = temp_index; + +	return 0; +} + +static int vpbe_get_mode_info(struct vpbe_device *vpbe_dev, char *mode, +			      int output_index) +{ +	struct vpbe_config *cfg = vpbe_dev->cfg; +	struct vpbe_enc_mode_info var; +	int curr_output = output_index; +	int i; + +	if (NULL == mode) +		return -EINVAL; + +	for (i = 0; i < cfg->outputs[curr_output].num_modes; i++) { +		var = cfg->outputs[curr_output].modes[i]; +		if (!strcmp(mode, var.name)) { +			vpbe_dev->current_timings = var; +			return 0; +		} +	} + +	return -EINVAL; +} + +static int vpbe_get_current_mode_info(struct vpbe_device *vpbe_dev, +				      struct vpbe_enc_mode_info *mode_info) +{ +	if (NULL == mode_info) +		return -EINVAL; + +	*mode_info = vpbe_dev->current_timings; + +	return 0; +} + +/* Get std by std id */ +static int vpbe_get_std_info(struct vpbe_device *vpbe_dev, +			     v4l2_std_id std_id) +{ +	struct vpbe_config *cfg = vpbe_dev->cfg; +	struct vpbe_enc_mode_info var; +	int curr_output = vpbe_dev->current_out_index; +	int i; + +	for (i = 0; i < vpbe_dev->cfg->outputs[curr_output].num_modes; i++) { +		var = cfg->outputs[curr_output].modes[i]; +		if ((var.timings_type & VPBE_ENC_STD) && +		  (var.std_id & std_id)) { +			vpbe_dev->current_timings = var; +			return 0; +		} +	} + +	return -EINVAL; +} + +static int vpbe_get_std_info_by_name(struct vpbe_device *vpbe_dev, +				char *std_name) +{ +	struct vpbe_config *cfg = vpbe_dev->cfg; +	struct vpbe_enc_mode_info var; +	int curr_output = vpbe_dev->current_out_index; +	int i; + +	for (i = 0; i < vpbe_dev->cfg->outputs[curr_output].num_modes; i++) { +		var = cfg->outputs[curr_output].modes[i]; +		if (!strcmp(var.name, std_name)) { +			vpbe_dev->current_timings = var; +			return 0; +		} +	} + +	return -EINVAL; +} + +/** + * vpbe_set_output - Set output + * @vpbe_dev - vpbe device ptr + * @index - index of output + * + * Set vpbe output to the output specified by the index + */ +static int vpbe_set_output(struct vpbe_device *vpbe_dev, int index) +{ +	struct encoder_config_info *curr_enc_info = +			vpbe_current_encoder_info(vpbe_dev); +	struct vpbe_config *cfg = vpbe_dev->cfg; +	struct venc_platform_data *venc_device = vpbe_dev->venc_device; +	enum v4l2_mbus_pixelcode if_params; +	int enc_out_index; +	int sd_index; +	int ret = 0; + +	if (index >= cfg->num_outputs) +		return -EINVAL; + +	mutex_lock(&vpbe_dev->lock); + +	sd_index = vpbe_dev->current_sd_index; +	enc_out_index = cfg->outputs[index].output.index; +	/* +	 * Currently we switch the encoder based on output selected +	 * by the application. If media controller is implemented later +	 * there is will be an API added to setup_link between venc +	 * and external encoder. So in that case below comparison always +	 * match and encoder will not be switched. But if application +	 * chose not to use media controller, then this provides current +	 * way of switching encoder at the venc output. +	 */ +	if (strcmp(curr_enc_info->module_name, +		   cfg->outputs[index].subdev_name)) { +		/* Need to switch the encoder at the output */ +		sd_index = vpbe_find_encoder_sd_index(cfg, index); +		if (sd_index < 0) { +			ret = -EINVAL; +			goto out; +		} + +		if_params = cfg->outputs[index].if_params; +		venc_device->setup_if_config(if_params); +		if (ret) +			goto out; +	} + +	/* Set output at the encoder */ +	ret = v4l2_subdev_call(vpbe_dev->encoders[sd_index], video, +				       s_routing, 0, enc_out_index, 0); +	if (ret) +		goto out; + +	/* +	 * It is assumed that venc or extenal encoder will set a default +	 * mode in the sub device. For external encoder or LCD pannel output, +	 * we also need to set up the lcd port for the required mode. So setup +	 * the lcd port for the default mode that is configured in the board +	 * arch/arm/mach-davinci/board-dm355-evm.setup file for the external +	 * encoder. +	 */ +	ret = vpbe_get_mode_info(vpbe_dev, +				 cfg->outputs[index].default_mode, index); +	if (!ret) { +		struct osd_state *osd_device = vpbe_dev->osd_device; + +		osd_device->ops.set_left_margin(osd_device, +			vpbe_dev->current_timings.left_margin); +		osd_device->ops.set_top_margin(osd_device, +		vpbe_dev->current_timings.upper_margin); +		vpbe_dev->current_sd_index = sd_index; +		vpbe_dev->current_out_index = index; +	} +out: +	mutex_unlock(&vpbe_dev->lock); +	return ret; +} + +static int vpbe_set_default_output(struct vpbe_device *vpbe_dev) +{ +	struct vpbe_config *cfg = vpbe_dev->cfg; +	int ret = 0; +	int i; + +	for (i = 0; i < cfg->num_outputs; i++) { +		if (!strcmp(def_output, +			    cfg->outputs[i].output.name)) { +			ret = vpbe_set_output(vpbe_dev, i); +			if (!ret) +				vpbe_dev->current_out_index = i; +			return ret; +		} +	} +	return ret; +} + +/** + * vpbe_get_output - Get output + * @vpbe_dev - vpbe device ptr + * + * return current vpbe output to the the index + */ +static unsigned int vpbe_get_output(struct vpbe_device *vpbe_dev) +{ +	return vpbe_dev->current_out_index; +} + +/** + * vpbe_s_dv_timings - Set the given preset timings in the encoder + * + * Sets the timings if supported by the current encoder. Return the status. + * 0 - success & -EINVAL on error + */ +static int vpbe_s_dv_timings(struct vpbe_device *vpbe_dev, +		    struct v4l2_dv_timings *dv_timings) +{ +	struct vpbe_config *cfg = vpbe_dev->cfg; +	int out_index = vpbe_dev->current_out_index; +	struct vpbe_output *output = &cfg->outputs[out_index]; +	int sd_index = vpbe_dev->current_sd_index; +	int ret, i; + + +	if (!(cfg->outputs[out_index].output.capabilities & +	    V4L2_OUT_CAP_DV_TIMINGS)) +		return -EINVAL; + +	for (i = 0; i < output->num_modes; i++) { +		if (output->modes[i].timings_type == VPBE_ENC_DV_TIMINGS && +		    !memcmp(&output->modes[i].dv_timings, +				dv_timings, sizeof(*dv_timings))) +			break; +	} +	if (i >= output->num_modes) +		return -EINVAL; +	vpbe_dev->current_timings = output->modes[i]; +	mutex_lock(&vpbe_dev->lock); + +	ret = v4l2_subdev_call(vpbe_dev->encoders[sd_index], video, +					s_dv_timings, dv_timings); +	if (!ret && (vpbe_dev->amp != NULL)) { +		/* Call amplifier subdevice */ +		ret = v4l2_subdev_call(vpbe_dev->amp, video, +				s_dv_timings, dv_timings); +	} +	/* set the lcd controller output for the given mode */ +	if (!ret) { +		struct osd_state *osd_device = vpbe_dev->osd_device; + +		osd_device->ops.set_left_margin(osd_device, +		vpbe_dev->current_timings.left_margin); +		osd_device->ops.set_top_margin(osd_device, +		vpbe_dev->current_timings.upper_margin); +	} +	mutex_unlock(&vpbe_dev->lock); + +	return ret; +} + +/** + * vpbe_g_dv_timings - Get the timings in the current encoder + * + * Get the timings in the current encoder. Return the status. 0 - success + * -EINVAL on error + */ +static int vpbe_g_dv_timings(struct vpbe_device *vpbe_dev, +		     struct v4l2_dv_timings *dv_timings) +{ +	if (vpbe_dev->current_timings.timings_type & +	  VPBE_ENC_DV_TIMINGS) { +		*dv_timings = vpbe_dev->current_timings.dv_timings; +		return 0; +	} + +	return -EINVAL; +} + +/** + * vpbe_enum_dv_timings - Enumerate the dv timings in the current encoder + * + * Get the timings in the current encoder. Return the status. 0 - success + * -EINVAL on error + */ +static int vpbe_enum_dv_timings(struct vpbe_device *vpbe_dev, +			 struct v4l2_enum_dv_timings *timings) +{ +	struct vpbe_config *cfg = vpbe_dev->cfg; +	int out_index = vpbe_dev->current_out_index; +	struct vpbe_output *output = &cfg->outputs[out_index]; +	int j = 0; +	int i; + +	if (!(output->output.capabilities & V4L2_OUT_CAP_DV_TIMINGS)) +		return -EINVAL; + +	for (i = 0; i < output->num_modes; i++) { +		if (output->modes[i].timings_type == VPBE_ENC_DV_TIMINGS) { +			if (j == timings->index) +				break; +			j++; +		} +	} + +	if (i == output->num_modes) +		return -EINVAL; +	timings->timings = output->modes[i].dv_timings; +	return 0; +} + +/** + * vpbe_s_std - Set the given standard in the encoder + * + * Sets the standard if supported by the current encoder. Return the status. + * 0 - success & -EINVAL on error + */ +static int vpbe_s_std(struct vpbe_device *vpbe_dev, v4l2_std_id std_id) +{ +	struct vpbe_config *cfg = vpbe_dev->cfg; +	int out_index = vpbe_dev->current_out_index; +	int sd_index = vpbe_dev->current_sd_index; +	int ret; + +	if (!(cfg->outputs[out_index].output.capabilities & +		V4L2_OUT_CAP_STD)) +		return -EINVAL; + +	ret = vpbe_get_std_info(vpbe_dev, std_id); +	if (ret) +		return ret; + +	mutex_lock(&vpbe_dev->lock); + +	ret = v4l2_subdev_call(vpbe_dev->encoders[sd_index], video, +			       s_std_output, std_id); +	/* set the lcd controller output for the given mode */ +	if (!ret) { +		struct osd_state *osd_device = vpbe_dev->osd_device; + +		osd_device->ops.set_left_margin(osd_device, +		vpbe_dev->current_timings.left_margin); +		osd_device->ops.set_top_margin(osd_device, +		vpbe_dev->current_timings.upper_margin); +	} +	mutex_unlock(&vpbe_dev->lock); + +	return ret; +} + +/** + * vpbe_g_std - Get the standard in the current encoder + * + * Get the standard in the current encoder. Return the status. 0 - success + * -EINVAL on error + */ +static int vpbe_g_std(struct vpbe_device *vpbe_dev, v4l2_std_id *std_id) +{ +	struct vpbe_enc_mode_info *cur_timings = &vpbe_dev->current_timings; + +	if (cur_timings->timings_type & VPBE_ENC_STD) { +		*std_id = cur_timings->std_id; +		return 0; +	} + +	return -EINVAL; +} + +/** + * vpbe_set_mode - Set mode in the current encoder using mode info + * + * Use the mode string to decide what timings to set in the encoder + * This is typically useful when fbset command is used to change the current + * timings by specifying a string to indicate the timings. + */ +static int vpbe_set_mode(struct vpbe_device *vpbe_dev, +			 struct vpbe_enc_mode_info *mode_info) +{ +	struct vpbe_enc_mode_info *preset_mode = NULL; +	struct vpbe_config *cfg = vpbe_dev->cfg; +	struct v4l2_dv_timings dv_timings; +	struct osd_state *osd_device; +	int out_index = vpbe_dev->current_out_index; +	int ret = 0; +	int i; + +	if ((NULL == mode_info) || (NULL == mode_info->name)) +		return -EINVAL; + +	for (i = 0; i < cfg->outputs[out_index].num_modes; i++) { +		if (!strcmp(mode_info->name, +		     cfg->outputs[out_index].modes[i].name)) { +			preset_mode = &cfg->outputs[out_index].modes[i]; +			/* +			 * it may be one of the 3 timings type. Check and +			 * invoke right API +			 */ +			if (preset_mode->timings_type & VPBE_ENC_STD) +				return vpbe_s_std(vpbe_dev, +						 preset_mode->std_id); +			if (preset_mode->timings_type & +						VPBE_ENC_DV_TIMINGS) { +				dv_timings = +					preset_mode->dv_timings; +				return vpbe_s_dv_timings(vpbe_dev, &dv_timings); +			} +		} +	} + +	/* Only custom timing should reach here */ +	if (preset_mode == NULL) +		return -EINVAL; + +	mutex_lock(&vpbe_dev->lock); + +	osd_device = vpbe_dev->osd_device; +	vpbe_dev->current_timings = *preset_mode; +	osd_device->ops.set_left_margin(osd_device, +		vpbe_dev->current_timings.left_margin); +	osd_device->ops.set_top_margin(osd_device, +		vpbe_dev->current_timings.upper_margin); + +	mutex_unlock(&vpbe_dev->lock); + +	return ret; +} + +static int vpbe_set_default_mode(struct vpbe_device *vpbe_dev) +{ +	int ret; + +	ret = vpbe_get_std_info_by_name(vpbe_dev, def_mode); +	if (ret) +		return ret; + +	/* set the default mode in the encoder */ +	return vpbe_set_mode(vpbe_dev, &vpbe_dev->current_timings); +} + +static int platform_device_get(struct device *dev, void *data) +{ +	struct platform_device *pdev = to_platform_device(dev); +	struct vpbe_device *vpbe_dev = data; + +	if (strstr(pdev->name, "vpbe-osd") != NULL) +		vpbe_dev->osd_device = platform_get_drvdata(pdev); +	if (strstr(pdev->name, "vpbe-venc") != NULL) +		vpbe_dev->venc_device = dev_get_platdata(&pdev->dev); + +	return 0; +} + +/** + * vpbe_initialize() - Initialize the vpbe display controller + * @vpbe_dev - vpbe device ptr + * + * Master frame buffer device drivers calls this to initialize vpbe + * display controller. This will then registers v4l2 device and the sub + * devices and sets a current encoder sub device for display. v4l2 display + * device driver is the master and frame buffer display device driver is + * the slave. Frame buffer display driver checks the initialized during + * probe and exit if not initialized. Returns status. + */ +static int vpbe_initialize(struct device *dev, struct vpbe_device *vpbe_dev) +{ +	struct encoder_config_info *enc_info; +	struct amp_config_info *amp_info; +	struct v4l2_subdev **enc_subdev; +	struct osd_state *osd_device; +	struct i2c_adapter *i2c_adap; +	int num_encoders; +	int ret = 0; +	int err; +	int i; + +	/* +	 * v4l2 abd FBDev frame buffer devices will get the vpbe_dev pointer +	 * from the platform device by iteration of platform drivers and +	 * matching with device name +	 */ +	if (NULL == vpbe_dev || NULL == dev) { +		printk(KERN_ERR "Null device pointers.\n"); +		return -ENODEV; +	} + +	if (vpbe_dev->initialized) +		return 0; + +	mutex_lock(&vpbe_dev->lock); + +	if (strcmp(vpbe_dev->cfg->module_name, "dm644x-vpbe-display") != 0) { +		/* We have dac clock available for platform */ +		vpbe_dev->dac_clk = clk_get(vpbe_dev->pdev, "vpss_dac"); +		if (IS_ERR(vpbe_dev->dac_clk)) { +			ret =  PTR_ERR(vpbe_dev->dac_clk); +			goto fail_mutex_unlock; +		} +		if (clk_prepare_enable(vpbe_dev->dac_clk)) { +			ret =  -ENODEV; +			goto fail_mutex_unlock; +		} +	} + +	/* first enable vpss clocks */ +	vpss_enable_clock(VPSS_VPBE_CLOCK, 1); + +	/* First register a v4l2 device */ +	ret = v4l2_device_register(dev, &vpbe_dev->v4l2_dev); +	if (ret) { +		v4l2_err(dev->driver, +			"Unable to register v4l2 device.\n"); +		goto fail_clk_put; +	} +	v4l2_info(&vpbe_dev->v4l2_dev, "vpbe v4l2 device registered\n"); + +	err = bus_for_each_dev(&platform_bus_type, NULL, vpbe_dev, +			       platform_device_get); +	if (err < 0) { +		ret = err; +		goto fail_dev_unregister; +	} + +	vpbe_dev->venc = venc_sub_dev_init(&vpbe_dev->v4l2_dev, +					   vpbe_dev->cfg->venc.module_name); +	/* register venc sub device */ +	if (vpbe_dev->venc == NULL) { +		v4l2_err(&vpbe_dev->v4l2_dev, +			"vpbe unable to init venc sub device\n"); +		ret = -ENODEV; +		goto fail_dev_unregister; +	} +	/* initialize osd device */ +	osd_device = vpbe_dev->osd_device; + +	if (NULL != osd_device->ops.initialize) { +		err = osd_device->ops.initialize(osd_device); +		if (err) { +			v4l2_err(&vpbe_dev->v4l2_dev, +				 "unable to initialize the OSD device"); +			err = -ENOMEM; +			goto fail_dev_unregister; +		} +	} + +	/* +	 * Register any external encoders that are configured. At index 0 we +	 * store venc sd index. +	 */ +	num_encoders = vpbe_dev->cfg->num_ext_encoders + 1; +	vpbe_dev->encoders = kmalloc( +				sizeof(struct v4l2_subdev *)*num_encoders, +				GFP_KERNEL); +	if (NULL == vpbe_dev->encoders) { +		v4l2_err(&vpbe_dev->v4l2_dev, +			"unable to allocate memory for encoders sub devices"); +		ret = -ENOMEM; +		goto fail_dev_unregister; +	} + +	i2c_adap = i2c_get_adapter(vpbe_dev->cfg->i2c_adapter_id); +	for (i = 0; i < (vpbe_dev->cfg->num_ext_encoders + 1); i++) { +		if (i == 0) { +			/* venc is at index 0 */ +			enc_subdev = &vpbe_dev->encoders[i]; +			*enc_subdev = vpbe_dev->venc; +			continue; +		} +		enc_info = &vpbe_dev->cfg->ext_encoders[i]; +		if (enc_info->is_i2c) { +			enc_subdev = &vpbe_dev->encoders[i]; +			*enc_subdev = v4l2_i2c_new_subdev_board( +						&vpbe_dev->v4l2_dev, i2c_adap, +						&enc_info->board_info, NULL); +			if (*enc_subdev) +				v4l2_info(&vpbe_dev->v4l2_dev, +					  "v4l2 sub device %s registered\n", +					  enc_info->module_name); +			else { +				v4l2_err(&vpbe_dev->v4l2_dev, "encoder %s" +					 " failed to register", +					 enc_info->module_name); +				ret = -ENODEV; +				goto fail_kfree_encoders; +			} +		} else +			v4l2_warn(&vpbe_dev->v4l2_dev, "non-i2c encoders" +				 " currently not supported"); +	} +	/* Add amplifier subdevice for dm365 */ +	if ((strcmp(vpbe_dev->cfg->module_name, "dm365-vpbe-display") == 0) && +			vpbe_dev->cfg->amp != NULL) { +		amp_info = vpbe_dev->cfg->amp; +		if (amp_info->is_i2c) { +			vpbe_dev->amp = v4l2_i2c_new_subdev_board( +			&vpbe_dev->v4l2_dev, i2c_adap, +			&_info->board_info, NULL); +			if (!vpbe_dev->amp) { +				v4l2_err(&vpbe_dev->v4l2_dev, +					 "amplifier %s failed to register", +					 amp_info->module_name); +				ret = -ENODEV; +				goto fail_kfree_encoders; +			} +			v4l2_info(&vpbe_dev->v4l2_dev, +					  "v4l2 sub device %s registered\n", +					  amp_info->module_name); +		} else { +			    vpbe_dev->amp = NULL; +			    v4l2_warn(&vpbe_dev->v4l2_dev, "non-i2c amplifiers" +			    " currently not supported"); +		} +	} else { +	    vpbe_dev->amp = NULL; +	} + +	/* set the current encoder and output to that of venc by default */ +	vpbe_dev->current_sd_index = 0; +	vpbe_dev->current_out_index = 0; + +	mutex_unlock(&vpbe_dev->lock); + +	printk(KERN_NOTICE "Setting default output to %s\n", def_output); +	ret = vpbe_set_default_output(vpbe_dev); +	if (ret) { +		v4l2_err(&vpbe_dev->v4l2_dev, "Failed to set default output %s", +			 def_output); +		return ret; +	} + +	printk(KERN_NOTICE "Setting default mode to %s\n", def_mode); +	ret = vpbe_set_default_mode(vpbe_dev); +	if (ret) { +		v4l2_err(&vpbe_dev->v4l2_dev, "Failed to set default mode %s", +			 def_mode); +		return ret; +	} +	vpbe_dev->initialized = 1; +	/* TBD handling of bootargs for default output and mode */ +	return 0; + +fail_kfree_encoders: +	kfree(vpbe_dev->encoders); +fail_dev_unregister: +	v4l2_device_unregister(&vpbe_dev->v4l2_dev); +fail_clk_put: +	if (strcmp(vpbe_dev->cfg->module_name, "dm644x-vpbe-display") != 0) { +		clk_disable_unprepare(vpbe_dev->dac_clk); +		clk_put(vpbe_dev->dac_clk); +	} +fail_mutex_unlock: +	mutex_unlock(&vpbe_dev->lock); +	return ret; +} + +/** + * vpbe_deinitialize() - de-initialize the vpbe display controller + * @dev - Master and slave device ptr + * + * vpbe_master and slave frame buffer devices calls this to de-initialize + * the display controller. It is called when master and slave device + * driver modules are removed and no longer requires the display controller. + */ +static void vpbe_deinitialize(struct device *dev, struct vpbe_device *vpbe_dev) +{ +	v4l2_device_unregister(&vpbe_dev->v4l2_dev); +	if (strcmp(vpbe_dev->cfg->module_name, "dm644x-vpbe-display") != 0) { +		clk_disable_unprepare(vpbe_dev->dac_clk); +		clk_put(vpbe_dev->dac_clk); +	} + +	kfree(vpbe_dev->amp); +	kfree(vpbe_dev->encoders); +	vpbe_dev->initialized = 0; +	/* disable vpss clocks */ +	vpss_enable_clock(VPSS_VPBE_CLOCK, 0); +} + +static struct vpbe_device_ops vpbe_dev_ops = { +	.g_cropcap = vpbe_g_cropcap, +	.enum_outputs = vpbe_enum_outputs, +	.set_output = vpbe_set_output, +	.get_output = vpbe_get_output, +	.s_dv_timings = vpbe_s_dv_timings, +	.g_dv_timings = vpbe_g_dv_timings, +	.enum_dv_timings = vpbe_enum_dv_timings, +	.s_std = vpbe_s_std, +	.g_std = vpbe_g_std, +	.initialize = vpbe_initialize, +	.deinitialize = vpbe_deinitialize, +	.get_mode_info = vpbe_get_current_mode_info, +	.set_mode = vpbe_set_mode, +}; + +static int vpbe_probe(struct platform_device *pdev) +{ +	struct vpbe_device *vpbe_dev; +	struct vpbe_config *cfg; +	int ret = -EINVAL; + +	if (pdev->dev.platform_data == NULL) { +		v4l2_err(pdev->dev.driver, "No platform data\n"); +		return -ENODEV; +	} +	cfg = pdev->dev.platform_data; + +	if (!cfg->module_name[0] || +	    !cfg->osd.module_name[0] || +	    !cfg->venc.module_name[0]) { +		v4l2_err(pdev->dev.driver, "vpbe display module names not" +			 " defined\n"); +		return ret; +	} + +	vpbe_dev = kzalloc(sizeof(*vpbe_dev), GFP_KERNEL); +	if (vpbe_dev == NULL) { +		v4l2_err(pdev->dev.driver, "Unable to allocate memory" +			 " for vpbe_device\n"); +		return -ENOMEM; +	} +	vpbe_dev->cfg = cfg; +	vpbe_dev->ops = vpbe_dev_ops; +	vpbe_dev->pdev = &pdev->dev; + +	if (cfg->outputs->num_modes > 0) +		vpbe_dev->current_timings = vpbe_dev->cfg->outputs[0].modes[0]; +	else { +		kfree(vpbe_dev); +		return -ENODEV; +	} + +	/* set the driver data in platform device */ +	platform_set_drvdata(pdev, vpbe_dev); +	mutex_init(&vpbe_dev->lock); + +	return 0; +} + +static int vpbe_remove(struct platform_device *device) +{ +	struct vpbe_device *vpbe_dev = platform_get_drvdata(device); + +	kfree(vpbe_dev); + +	return 0; +} + +static struct platform_driver vpbe_driver = { +	.driver	= { +		.name	= "vpbe_controller", +		.owner	= THIS_MODULE, +	}, +	.probe = vpbe_probe, +	.remove = vpbe_remove, +}; + +module_platform_driver(vpbe_driver); diff --git a/drivers/media/platform/davinci/vpbe_display.c b/drivers/media/platform/davinci/vpbe_display.c new file mode 100644 index 00000000000..bf5eff99452 --- /dev/null +++ b/drivers/media/platform/davinci/vpbe_display.c @@ -0,0 +1,1858 @@ +/* + * Copyright (C) 2010 Texas Instruments Incorporated - http://www.ti.com/ + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + */ +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/string.h> +#include <linux/wait.h> +#include <linux/time.h> +#include <linux/platform_device.h> +#include <linux/irq.h> +#include <linux/mm.h> +#include <linux/mutex.h> +#include <linux/videodev2.h> +#include <linux/slab.h> + +#include <asm/pgtable.h> +#include <mach/cputype.h> + +#include <media/v4l2-dev.h> +#include <media/v4l2-common.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-device.h> +#include <media/davinci/vpbe_display.h> +#include <media/davinci/vpbe_types.h> +#include <media/davinci/vpbe.h> +#include <media/davinci/vpbe_venc.h> +#include <media/davinci/vpbe_osd.h> +#include "vpbe_venc_regs.h" + +#define VPBE_DISPLAY_DRIVER "vpbe-v4l2" + +static int debug; + +#define VPBE_DEFAULT_NUM_BUFS 3 + +module_param(debug, int, 0644); + +static int vpbe_set_osd_display_params(struct vpbe_display *disp_dev, +			struct vpbe_layer *layer); + +static int venc_is_second_field(struct vpbe_display *disp_dev) +{ +	struct vpbe_device *vpbe_dev = disp_dev->vpbe_dev; +	int ret; +	int val; + +	ret = v4l2_subdev_call(vpbe_dev->venc, +			       core, +			       ioctl, +			       VENC_GET_FLD, +			       &val); +	if (ret < 0) { +		v4l2_err(&vpbe_dev->v4l2_dev, +			 "Error in getting Field ID 0\n"); +	} +	return val; +} + +static void vpbe_isr_even_field(struct vpbe_display *disp_obj, +				struct vpbe_layer *layer) +{ +	struct timespec timevalue; + +	if (layer->cur_frm == layer->next_frm) +		return; +	ktime_get_ts(&timevalue); +	layer->cur_frm->vb.v4l2_buf.timestamp.tv_sec = +		timevalue.tv_sec; +	layer->cur_frm->vb.v4l2_buf.timestamp.tv_usec = +		timevalue.tv_nsec / NSEC_PER_USEC; +	vb2_buffer_done(&layer->cur_frm->vb, VB2_BUF_STATE_DONE); +	/* Make cur_frm pointing to next_frm */ +	layer->cur_frm = layer->next_frm; +} + +static void vpbe_isr_odd_field(struct vpbe_display *disp_obj, +				struct vpbe_layer *layer) +{ +	struct osd_state *osd_device = disp_obj->osd_device; +	unsigned long addr; + +	spin_lock(&disp_obj->dma_queue_lock); +	if (list_empty(&layer->dma_queue) || +		(layer->cur_frm != layer->next_frm)) { +		spin_unlock(&disp_obj->dma_queue_lock); +		return; +	} +	/* +	 * one field is displayed configure +	 * the next frame if it is available +	 * otherwise hold on current frame +	 * Get next from the buffer queue +	 */ +	layer->next_frm = list_entry(layer->dma_queue.next, +			  struct  vpbe_disp_buffer, list); +	/* Remove that from the buffer queue */ +	list_del(&layer->next_frm->list); +	spin_unlock(&disp_obj->dma_queue_lock); +	/* Mark state of the frame to active */ +	layer->next_frm->vb.state = VB2_BUF_STATE_ACTIVE; +	addr = vb2_dma_contig_plane_dma_addr(&layer->next_frm->vb, 0); +	osd_device->ops.start_layer(osd_device, +			layer->layer_info.id, +			addr, +			disp_obj->cbcr_ofst); +} + +/* interrupt service routine */ +static irqreturn_t venc_isr(int irq, void *arg) +{ +	struct vpbe_display *disp_dev = (struct vpbe_display *)arg; +	struct vpbe_layer *layer; +	static unsigned last_event; +	unsigned event = 0; +	int fid; +	int i; + +	if ((NULL == arg) || (NULL == disp_dev->dev[0])) +		return IRQ_HANDLED; + +	if (venc_is_second_field(disp_dev)) +		event |= VENC_SECOND_FIELD; +	else +		event |= VENC_FIRST_FIELD; + +	if (event == (last_event & ~VENC_END_OF_FRAME)) { +		/* +		* If the display is non-interlaced, then we need to flag the +		* end-of-frame event at every interrupt regardless of the +		* value of the FIDST bit.  We can conclude that the display is +		* non-interlaced if the value of the FIDST bit is unchanged +		* from the previous interrupt. +		*/ +		event |= VENC_END_OF_FRAME; +	} else if (event == VENC_SECOND_FIELD) { +		/* end-of-frame for interlaced display */ +		event |= VENC_END_OF_FRAME; +	} +	last_event = event; + +	for (i = 0; i < VPBE_DISPLAY_MAX_DEVICES; i++) { +		layer = disp_dev->dev[i]; +		/* If streaming is started in this layer */ +		if (!layer->started) +			continue; + +		if (layer->layer_first_int) { +			layer->layer_first_int = 0; +			continue; +		} +		/* Check the field format */ +		if ((V4L2_FIELD_NONE == layer->pix_fmt.field) && +			(event & VENC_END_OF_FRAME)) { +			/* Progressive mode */ + +			vpbe_isr_even_field(disp_dev, layer); +			vpbe_isr_odd_field(disp_dev, layer); +		} else { +		/* Interlaced mode */ + +			layer->field_id ^= 1; +			if (event & VENC_FIRST_FIELD) +				fid = 0; +			else +				fid = 1; + +			/* +			* If field id does not match with store +			* field id +			*/ +			if (fid != layer->field_id) { +				/* Make them in sync */ +				layer->field_id = fid; +				continue; +			} +			/* +			* device field id and local field id are +			* in sync. If this is even field +			*/ +			if (0 == fid) +				vpbe_isr_even_field(disp_dev, layer); +			else  /* odd field */ +				vpbe_isr_odd_field(disp_dev, layer); +		} +	} + +	return IRQ_HANDLED; +} + +/* + * vpbe_buffer_prepare() + * This is the callback function called from vb2_qbuf() function + * the buffer is prepared and user space virtual address is converted into + * physical address + */ +static int vpbe_buffer_prepare(struct vb2_buffer *vb) +{ +	struct vpbe_fh *fh = vb2_get_drv_priv(vb->vb2_queue); +	struct vb2_queue *q = vb->vb2_queue; +	struct vpbe_layer *layer = fh->layer; +	struct vpbe_device *vpbe_dev = fh->disp_dev->vpbe_dev; +	unsigned long addr; + +	v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, +				"vpbe_buffer_prepare\n"); + +	if (vb->state != VB2_BUF_STATE_ACTIVE && +		vb->state != VB2_BUF_STATE_PREPARED) { +		vb2_set_plane_payload(vb, 0, layer->pix_fmt.sizeimage); +		if (vb2_plane_vaddr(vb, 0) && +		vb2_get_plane_payload(vb, 0) > vb2_plane_size(vb, 0)) +			return -EINVAL; + +		addr = vb2_dma_contig_plane_dma_addr(vb, 0); +		if (q->streaming) { +			if (!IS_ALIGNED(addr, 8)) { +				v4l2_err(&vpbe_dev->v4l2_dev, +					"buffer_prepare:offset is \ +					not aligned to 32 bytes\n"); +				return -EINVAL; +			} +		} +	} +	return 0; +} + +/* + * vpbe_buffer_setup() + * This function allocates memory for the buffers + */ +static int +vpbe_buffer_queue_setup(struct vb2_queue *vq, const struct v4l2_format *fmt, +			unsigned int *nbuffers, unsigned int *nplanes, +			unsigned int sizes[], void *alloc_ctxs[]) + +{ +	/* Get the file handle object and layer object */ +	struct vpbe_fh *fh = vb2_get_drv_priv(vq); +	struct vpbe_layer *layer = fh->layer; +	struct vpbe_device *vpbe_dev = fh->disp_dev->vpbe_dev; + +	v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "vpbe_buffer_setup\n"); + +	/* Store number of buffers allocated in numbuffer member */ +	if (*nbuffers < VPBE_DEFAULT_NUM_BUFS) +		*nbuffers = layer->numbuffers = VPBE_DEFAULT_NUM_BUFS; + +	*nplanes = 1; +	sizes[0] = layer->pix_fmt.sizeimage; +	alloc_ctxs[0] = layer->alloc_ctx; + +	return 0; +} + +/* + * vpbe_buffer_queue() + * This function adds the buffer to DMA queue + */ +static void vpbe_buffer_queue(struct vb2_buffer *vb) +{ +	/* Get the file handle object and layer object */ +	struct vpbe_fh *fh = vb2_get_drv_priv(vb->vb2_queue); +	struct vpbe_disp_buffer *buf = container_of(vb, +				struct vpbe_disp_buffer, vb); +	struct vpbe_layer *layer = fh->layer; +	struct vpbe_display *disp = fh->disp_dev; +	struct vpbe_device *vpbe_dev = fh->disp_dev->vpbe_dev; +	unsigned long flags; + +	v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, +			"vpbe_buffer_queue\n"); + +	/* add the buffer to the DMA queue */ +	spin_lock_irqsave(&disp->dma_queue_lock, flags); +	list_add_tail(&buf->list, &layer->dma_queue); +	spin_unlock_irqrestore(&disp->dma_queue_lock, flags); +} + +/* + * vpbe_buf_cleanup() + * This function is called from the vb2 layer to free memory allocated to + * the buffers + */ +static void vpbe_buf_cleanup(struct vb2_buffer *vb) +{ +	/* Get the file handle object and layer object */ +	struct vpbe_fh *fh = vb2_get_drv_priv(vb->vb2_queue); +	struct vpbe_layer *layer = fh->layer; +	struct vpbe_device *vpbe_dev = fh->disp_dev->vpbe_dev; +	struct vpbe_disp_buffer *buf = container_of(vb, +					struct vpbe_disp_buffer, vb); +	unsigned long flags; + +	v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, +			"vpbe_buf_cleanup\n"); + +	spin_lock_irqsave(&layer->irqlock, flags); +	if (vb->state == VB2_BUF_STATE_ACTIVE) +		list_del_init(&buf->list); +	spin_unlock_irqrestore(&layer->irqlock, flags); +} + +static void vpbe_wait_prepare(struct vb2_queue *vq) +{ +	struct vpbe_fh *fh = vb2_get_drv_priv(vq); +	struct vpbe_layer *layer = fh->layer; + +	mutex_unlock(&layer->opslock); +} + +static void vpbe_wait_finish(struct vb2_queue *vq) +{ +	struct vpbe_fh *fh = vb2_get_drv_priv(vq); +	struct vpbe_layer *layer = fh->layer; + +	mutex_lock(&layer->opslock); +} + +static int vpbe_buffer_init(struct vb2_buffer *vb) +{ +	struct vpbe_disp_buffer *buf = container_of(vb, +					struct vpbe_disp_buffer, vb); + +	INIT_LIST_HEAD(&buf->list); +	return 0; +} + +static int vpbe_start_streaming(struct vb2_queue *vq, unsigned int count) +{ +	struct vpbe_fh *fh = vb2_get_drv_priv(vq); +	struct vpbe_layer *layer = fh->layer; +	int ret; + +	/* Get the next frame from the buffer queue */ +	layer->next_frm = layer->cur_frm = list_entry(layer->dma_queue.next, +				struct vpbe_disp_buffer, list); +	/* Remove buffer from the buffer queue */ +	list_del(&layer->cur_frm->list); +	/* Mark state of the current frame to active */ +	layer->cur_frm->vb.state = VB2_BUF_STATE_ACTIVE; +	/* Initialize field_id and started member */ +	layer->field_id = 0; + +	/* Set parameters in OSD and VENC */ +	ret = vpbe_set_osd_display_params(fh->disp_dev, layer); +	if (ret < 0) { +		struct vpbe_disp_buffer *buf, *tmp; + +		vb2_buffer_done(&layer->cur_frm->vb, VB2_BUF_STATE_QUEUED); +		list_for_each_entry_safe(buf, tmp, &layer->dma_queue, list) { +			list_del(&buf->list); +			vb2_buffer_done(&buf->vb, VB2_BUF_STATE_QUEUED); +		} + +		return ret; +	} + +	/* +	 * if request format is yuv420 semiplanar, need to +	 * enable both video windows +	 */ +	layer->started = 1; +	layer->layer_first_int = 1; + +	return ret; +} + +static void vpbe_stop_streaming(struct vb2_queue *vq) +{ +	struct vpbe_fh *fh = vb2_get_drv_priv(vq); +	struct vpbe_layer *layer = fh->layer; +	struct vpbe_display *disp = fh->disp_dev; +	unsigned long flags; + +	if (!vb2_is_streaming(vq)) +		return; + +	/* release all active buffers */ +	spin_lock_irqsave(&disp->dma_queue_lock, flags); +	if (layer->cur_frm == layer->next_frm) { +		vb2_buffer_done(&layer->cur_frm->vb, VB2_BUF_STATE_ERROR); +	} else { +		if (layer->cur_frm != NULL) +			vb2_buffer_done(&layer->cur_frm->vb, +					VB2_BUF_STATE_ERROR); +		if (layer->next_frm != NULL) +			vb2_buffer_done(&layer->next_frm->vb, +					VB2_BUF_STATE_ERROR); +	} + +	while (!list_empty(&layer->dma_queue)) { +		layer->next_frm = list_entry(layer->dma_queue.next, +						struct vpbe_disp_buffer, list); +		list_del(&layer->next_frm->list); +		vb2_buffer_done(&layer->next_frm->vb, VB2_BUF_STATE_ERROR); +	} +	spin_unlock_irqrestore(&disp->dma_queue_lock, flags); +} + +static struct vb2_ops video_qops = { +	.queue_setup = vpbe_buffer_queue_setup, +	.wait_prepare = vpbe_wait_prepare, +	.wait_finish = vpbe_wait_finish, +	.buf_init = vpbe_buffer_init, +	.buf_prepare = vpbe_buffer_prepare, +	.start_streaming = vpbe_start_streaming, +	.stop_streaming = vpbe_stop_streaming, +	.buf_cleanup = vpbe_buf_cleanup, +	.buf_queue = vpbe_buffer_queue, +}; + +static +struct vpbe_layer* +_vpbe_display_get_other_win_layer(struct vpbe_display *disp_dev, +			struct vpbe_layer *layer) +{ +	enum vpbe_display_device_id thiswin, otherwin; +	thiswin = layer->device_id; + +	otherwin = (thiswin == VPBE_DISPLAY_DEVICE_0) ? +	VPBE_DISPLAY_DEVICE_1 : VPBE_DISPLAY_DEVICE_0; +	return disp_dev->dev[otherwin]; +} + +static int vpbe_set_osd_display_params(struct vpbe_display *disp_dev, +			struct vpbe_layer *layer) +{ +	struct osd_layer_config *cfg  = &layer->layer_info.config; +	struct osd_state *osd_device = disp_dev->osd_device; +	struct vpbe_device *vpbe_dev = disp_dev->vpbe_dev; +	unsigned long addr; +	int ret; + +	addr = vb2_dma_contig_plane_dma_addr(&layer->cur_frm->vb, 0); +	/* Set address in the display registers */ +	osd_device->ops.start_layer(osd_device, +				    layer->layer_info.id, +				    addr, +				    disp_dev->cbcr_ofst); + +	ret = osd_device->ops.enable_layer(osd_device, +				layer->layer_info.id, 0); +	if (ret < 0) { +		v4l2_err(&vpbe_dev->v4l2_dev, +			"Error in enabling osd window layer 0\n"); +		return -1; +	} + +	/* Enable the window */ +	layer->layer_info.enable = 1; +	if (cfg->pixfmt == PIXFMT_NV12) { +		struct vpbe_layer *otherlayer = +			_vpbe_display_get_other_win_layer(disp_dev, layer); + +		ret = osd_device->ops.enable_layer(osd_device, +				otherlayer->layer_info.id, 1); +		if (ret < 0) { +			v4l2_err(&vpbe_dev->v4l2_dev, +				"Error in enabling osd window layer 1\n"); +			return -1; +		} +		otherlayer->layer_info.enable = 1; +	} +	return 0; +} + +static void +vpbe_disp_calculate_scale_factor(struct vpbe_display *disp_dev, +			struct vpbe_layer *layer, +			int expected_xsize, int expected_ysize) +{ +	struct display_layer_info *layer_info = &layer->layer_info; +	struct v4l2_pix_format *pixfmt = &layer->pix_fmt; +	struct osd_layer_config *cfg  = &layer->layer_info.config; +	struct vpbe_device *vpbe_dev = disp_dev->vpbe_dev; +	int calculated_xsize; +	int h_exp = 0; +	int v_exp = 0; +	int h_scale; +	int v_scale; + +	v4l2_std_id standard_id = vpbe_dev->current_timings.std_id; + +	/* +	 * Application initially set the image format. Current display +	 * size is obtained from the vpbe display controller. expected_xsize +	 * and expected_ysize are set through S_CROP ioctl. Based on this, +	 * driver will calculate the scale factors for vertical and +	 * horizontal direction so that the image is displayed scaled +	 * and expanded. Application uses expansion to display the image +	 * in a square pixel. Otherwise it is displayed using displays +	 * pixel aspect ratio.It is expected that application chooses +	 * the crop coordinates for cropped or scaled display. if crop +	 * size is less than the image size, it is displayed cropped or +	 * it is displayed scaled and/or expanded. +	 * +	 * to begin with, set the crop window same as expected. Later we +	 * will override with scaled window size +	 */ + +	cfg->xsize = pixfmt->width; +	cfg->ysize = pixfmt->height; +	layer_info->h_zoom = ZOOM_X1;	/* no horizontal zoom */ +	layer_info->v_zoom = ZOOM_X1;	/* no horizontal zoom */ +	layer_info->h_exp = H_EXP_OFF;	/* no horizontal zoom */ +	layer_info->v_exp = V_EXP_OFF;	/* no horizontal zoom */ + +	if (pixfmt->width < expected_xsize) { +		h_scale = vpbe_dev->current_timings.xres / pixfmt->width; +		if (h_scale < 2) +			h_scale = 1; +		else if (h_scale >= 4) +			h_scale = 4; +		else +			h_scale = 2; +		cfg->xsize *= h_scale; +		if (cfg->xsize < expected_xsize) { +			if ((standard_id & V4L2_STD_525_60) || +			(standard_id & V4L2_STD_625_50)) { +				calculated_xsize = (cfg->xsize * +					VPBE_DISPLAY_H_EXP_RATIO_N) / +					VPBE_DISPLAY_H_EXP_RATIO_D; +				if (calculated_xsize <= expected_xsize) { +					h_exp = 1; +					cfg->xsize = calculated_xsize; +				} +			} +		} +		if (h_scale == 2) +			layer_info->h_zoom = ZOOM_X2; +		else if (h_scale == 4) +			layer_info->h_zoom = ZOOM_X4; +		if (h_exp) +			layer_info->h_exp = H_EXP_9_OVER_8; +	} else { +		/* no scaling, only cropping. Set display area to crop area */ +		cfg->xsize = expected_xsize; +	} + +	if (pixfmt->height < expected_ysize) { +		v_scale = expected_ysize / pixfmt->height; +		if (v_scale < 2) +			v_scale = 1; +		else if (v_scale >= 4) +			v_scale = 4; +		else +			v_scale = 2; +		cfg->ysize *= v_scale; +		if (cfg->ysize < expected_ysize) { +			if ((standard_id & V4L2_STD_625_50)) { +				calculated_xsize = (cfg->ysize * +					VPBE_DISPLAY_V_EXP_RATIO_N) / +					VPBE_DISPLAY_V_EXP_RATIO_D; +				if (calculated_xsize <= expected_ysize) { +					v_exp = 1; +					cfg->ysize = calculated_xsize; +				} +			} +		} +		if (v_scale == 2) +			layer_info->v_zoom = ZOOM_X2; +		else if (v_scale == 4) +			layer_info->v_zoom = ZOOM_X4; +		if (v_exp) +			layer_info->h_exp = V_EXP_6_OVER_5; +	} else { +		/* no scaling, only cropping. Set display area to crop area */ +		cfg->ysize = expected_ysize; +	} +	v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, +		"crop display xsize = %d, ysize = %d\n", +		cfg->xsize, cfg->ysize); +} + +static void vpbe_disp_adj_position(struct vpbe_display *disp_dev, +			struct vpbe_layer *layer, +			int top, int left) +{ +	struct osd_layer_config *cfg = &layer->layer_info.config; +	struct vpbe_device *vpbe_dev = disp_dev->vpbe_dev; + +	cfg->xpos = min((unsigned int)left, +			vpbe_dev->current_timings.xres - cfg->xsize); +	cfg->ypos = min((unsigned int)top, +			vpbe_dev->current_timings.yres - cfg->ysize); + +	v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, +		"new xpos = %d, ypos = %d\n", +		cfg->xpos, cfg->ypos); +} + +static void vpbe_disp_check_window_params(struct vpbe_display *disp_dev, +			struct v4l2_rect *c) +{ +	struct vpbe_device *vpbe_dev = disp_dev->vpbe_dev; + +	if ((c->width == 0) || +	  ((c->width + c->left) > vpbe_dev->current_timings.xres)) +		c->width = vpbe_dev->current_timings.xres - c->left; + +	if ((c->height == 0) || ((c->height + c->top) > +	  vpbe_dev->current_timings.yres)) +		c->height = vpbe_dev->current_timings.yres - c->top; + +	/* window height must be even for interlaced display */ +	if (vpbe_dev->current_timings.interlaced) +		c->height &= (~0x01); + +} + +/** + * vpbe_try_format() + * If user application provides width and height, and have bytesperline set + * to zero, driver calculates bytesperline and sizeimage based on hardware + * limits. + */ +static int vpbe_try_format(struct vpbe_display *disp_dev, +			struct v4l2_pix_format *pixfmt, int check) +{ +	struct vpbe_device *vpbe_dev = disp_dev->vpbe_dev; +	int min_height = 1; +	int min_width = 32; +	int max_height; +	int max_width; +	int bpp; + +	if ((pixfmt->pixelformat != V4L2_PIX_FMT_UYVY) && +	    (pixfmt->pixelformat != V4L2_PIX_FMT_NV12)) +		/* choose default as V4L2_PIX_FMT_UYVY */ +		pixfmt->pixelformat = V4L2_PIX_FMT_UYVY; + +	/* Check the field format */ +	if ((pixfmt->field != V4L2_FIELD_INTERLACED) && +		(pixfmt->field != V4L2_FIELD_NONE)) { +		if (vpbe_dev->current_timings.interlaced) +			pixfmt->field = V4L2_FIELD_INTERLACED; +		else +			pixfmt->field = V4L2_FIELD_NONE; +	} + +	if (pixfmt->field == V4L2_FIELD_INTERLACED) +		min_height = 2; + +	if (pixfmt->pixelformat == V4L2_PIX_FMT_NV12) +		bpp = 1; +	else +		bpp = 2; + +	max_width = vpbe_dev->current_timings.xres; +	max_height = vpbe_dev->current_timings.yres; + +	min_width /= bpp; + +	if (!pixfmt->width || (pixfmt->width < min_width) || +		(pixfmt->width > max_width)) { +		pixfmt->width = vpbe_dev->current_timings.xres; +	} + +	if (!pixfmt->height || (pixfmt->height  < min_height) || +		(pixfmt->height  > max_height)) { +		pixfmt->height = vpbe_dev->current_timings.yres; +	} + +	if (pixfmt->bytesperline < (pixfmt->width * bpp)) +		pixfmt->bytesperline = pixfmt->width * bpp; + +	/* Make the bytesperline 32 byte aligned */ +	pixfmt->bytesperline = ((pixfmt->width * bpp + 31) & ~31); + +	if (pixfmt->pixelformat == V4L2_PIX_FMT_NV12) +		pixfmt->sizeimage = pixfmt->bytesperline * pixfmt->height + +				(pixfmt->bytesperline * pixfmt->height >> 1); +	else +		pixfmt->sizeimage = pixfmt->bytesperline * pixfmt->height; + +	return 0; +} + +static int vpbe_display_querycap(struct file *file, void  *priv, +			       struct v4l2_capability *cap) +{ +	struct vpbe_fh *fh = file->private_data; +	struct vpbe_device *vpbe_dev = fh->disp_dev->vpbe_dev; + +	cap->version = VPBE_DISPLAY_VERSION_CODE; +	cap->device_caps = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING; +	cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; +	snprintf(cap->driver, sizeof(cap->driver), "%s", +		dev_name(vpbe_dev->pdev)); +	snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s", +		 dev_name(vpbe_dev->pdev)); +	strlcpy(cap->card, vpbe_dev->cfg->module_name, sizeof(cap->card)); + +	return 0; +} + +static int vpbe_display_s_crop(struct file *file, void *priv, +			     const struct v4l2_crop *crop) +{ +	struct vpbe_fh *fh = file->private_data; +	struct vpbe_layer *layer = fh->layer; +	struct vpbe_display *disp_dev = fh->disp_dev; +	struct vpbe_device *vpbe_dev = disp_dev->vpbe_dev; +	struct osd_layer_config *cfg = &layer->layer_info.config; +	struct osd_state *osd_device = disp_dev->osd_device; +	struct v4l2_rect rect = crop->c; +	int ret; + +	v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, +		"VIDIOC_S_CROP, layer id = %d\n", layer->device_id); + +	if (crop->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { +		v4l2_err(&vpbe_dev->v4l2_dev, "Invalid buf type\n"); +		return -EINVAL; +	} + +	if (rect.top < 0) +		rect.top = 0; +	if (rect.left < 0) +		rect.left = 0; + +	vpbe_disp_check_window_params(disp_dev, &rect); + +	osd_device->ops.get_layer_config(osd_device, +			layer->layer_info.id, cfg); + +	vpbe_disp_calculate_scale_factor(disp_dev, layer, +					rect.width, +					rect.height); +	vpbe_disp_adj_position(disp_dev, layer, rect.top, +					rect.left); +	ret = osd_device->ops.set_layer_config(osd_device, +				layer->layer_info.id, cfg); +	if (ret < 0) { +		v4l2_err(&vpbe_dev->v4l2_dev, +			"Error in set layer config:\n"); +		return -EINVAL; +	} + +	/* apply zooming and h or v expansion */ +	osd_device->ops.set_zoom(osd_device, +			layer->layer_info.id, +			layer->layer_info.h_zoom, +			layer->layer_info.v_zoom); +	ret = osd_device->ops.set_vid_expansion(osd_device, +			layer->layer_info.h_exp, +			layer->layer_info.v_exp); +	if (ret < 0) { +		v4l2_err(&vpbe_dev->v4l2_dev, +		"Error in set vid expansion:\n"); +		return -EINVAL; +	} + +	if ((layer->layer_info.h_zoom != ZOOM_X1) || +		(layer->layer_info.v_zoom != ZOOM_X1) || +		(layer->layer_info.h_exp != H_EXP_OFF) || +		(layer->layer_info.v_exp != V_EXP_OFF)) +		/* Enable expansion filter */ +		osd_device->ops.set_interpolation_filter(osd_device, 1); +	else +		osd_device->ops.set_interpolation_filter(osd_device, 0); + +	return 0; +} + +static int vpbe_display_g_crop(struct file *file, void *priv, +			     struct v4l2_crop *crop) +{ +	struct vpbe_fh *fh = file->private_data; +	struct vpbe_layer *layer = fh->layer; +	struct osd_layer_config *cfg = &layer->layer_info.config; +	struct vpbe_device *vpbe_dev = fh->disp_dev->vpbe_dev; +	struct osd_state *osd_device = fh->disp_dev->osd_device; +	struct v4l2_rect *rect = &crop->c; + +	v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, +			"VIDIOC_G_CROP, layer id = %d\n", +			layer->device_id); + +	if (crop->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { +		v4l2_err(&vpbe_dev->v4l2_dev, "Invalid buf type\n"); +		return -EINVAL; +	} +	osd_device->ops.get_layer_config(osd_device, +				layer->layer_info.id, cfg); +	rect->top = cfg->ypos; +	rect->left = cfg->xpos; +	rect->width = cfg->xsize; +	rect->height = cfg->ysize; + +	return 0; +} + +static int vpbe_display_cropcap(struct file *file, void *priv, +			      struct v4l2_cropcap *cropcap) +{ +	struct vpbe_fh *fh = file->private_data; +	struct vpbe_device *vpbe_dev = fh->disp_dev->vpbe_dev; + +	v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "VIDIOC_CROPCAP ioctl\n"); + +	cropcap->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; +	cropcap->bounds.left = 0; +	cropcap->bounds.top = 0; +	cropcap->bounds.width = vpbe_dev->current_timings.xres; +	cropcap->bounds.height = vpbe_dev->current_timings.yres; +	cropcap->pixelaspect = vpbe_dev->current_timings.aspect; +	cropcap->defrect = cropcap->bounds; +	return 0; +} + +static int vpbe_display_g_fmt(struct file *file, void *priv, +				struct v4l2_format *fmt) +{ +	struct vpbe_fh *fh = file->private_data; +	struct vpbe_layer *layer = fh->layer; +	struct vpbe_device *vpbe_dev = fh->disp_dev->vpbe_dev; + +	v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, +			"VIDIOC_G_FMT, layer id = %d\n", +			layer->device_id); + +	/* If buffer type is video output */ +	if (V4L2_BUF_TYPE_VIDEO_OUTPUT != fmt->type) { +		v4l2_err(&vpbe_dev->v4l2_dev, "invalid type\n"); +		return -EINVAL; +	} +	/* Fill in the information about format */ +	fmt->fmt.pix = layer->pix_fmt; + +	return 0; +} + +static int vpbe_display_enum_fmt(struct file *file, void  *priv, +				   struct v4l2_fmtdesc *fmt) +{ +	struct vpbe_fh *fh = file->private_data; +	struct vpbe_layer *layer = fh->layer; +	struct vpbe_device *vpbe_dev = fh->disp_dev->vpbe_dev; +	unsigned int index = 0; + +	v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, +				"VIDIOC_ENUM_FMT, layer id = %d\n", +				layer->device_id); +	if (fmt->index > 1) { +		v4l2_err(&vpbe_dev->v4l2_dev, "Invalid format index\n"); +		return -EINVAL; +	} + +	/* Fill in the information about format */ +	index = fmt->index; +	memset(fmt, 0, sizeof(*fmt)); +	fmt->index = index; +	fmt->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; +	if (index == 0) { +		strcpy(fmt->description, "YUV 4:2:2 - UYVY"); +		fmt->pixelformat = V4L2_PIX_FMT_UYVY; +	} else { +		strcpy(fmt->description, "Y/CbCr 4:2:0"); +		fmt->pixelformat = V4L2_PIX_FMT_NV12; +	} + +	return 0; +} + +static int vpbe_display_s_fmt(struct file *file, void *priv, +				struct v4l2_format *fmt) +{ +	struct vpbe_fh *fh = file->private_data; +	struct vpbe_layer *layer = fh->layer; +	struct vpbe_display *disp_dev = fh->disp_dev; +	struct vpbe_device *vpbe_dev = disp_dev->vpbe_dev; +	struct osd_layer_config *cfg  = &layer->layer_info.config; +	struct v4l2_pix_format *pixfmt = &fmt->fmt.pix; +	struct osd_state *osd_device = disp_dev->osd_device; +	int ret; + +	v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, +			"VIDIOC_S_FMT, layer id = %d\n", +			layer->device_id); + +	/* If streaming is started, return error */ +	if (layer->started) { +		v4l2_err(&vpbe_dev->v4l2_dev, "Streaming is started\n"); +		return -EBUSY; +	} +	if (V4L2_BUF_TYPE_VIDEO_OUTPUT != fmt->type) { +		v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "invalid type\n"); +		return -EINVAL; +	} +	/* Check for valid pixel format */ +	ret = vpbe_try_format(disp_dev, pixfmt, 1); +	if (ret) +		return ret; + +	/* YUV420 is requested, check availability of the +	other video window */ + +	layer->pix_fmt = *pixfmt; +	if (pixfmt->pixelformat == V4L2_PIX_FMT_NV12) { +		struct vpbe_layer *otherlayer; + +		otherlayer = _vpbe_display_get_other_win_layer(disp_dev, layer); +		/* if other layer is available, only +		 * claim it, do not configure it +		 */ +		ret = osd_device->ops.request_layer(osd_device, +						    otherlayer->layer_info.id); +		if (ret < 0) { +			v4l2_err(&vpbe_dev->v4l2_dev, +				 "Display Manager failed to allocate layer\n"); +			return -EBUSY; +		} +	} + +	/* Get osd layer config */ +	osd_device->ops.get_layer_config(osd_device, +			layer->layer_info.id, cfg); +	/* Store the pixel format in the layer object */ +	cfg->xsize = pixfmt->width; +	cfg->ysize = pixfmt->height; +	cfg->line_length = pixfmt->bytesperline; +	cfg->ypos = 0; +	cfg->xpos = 0; +	cfg->interlaced = vpbe_dev->current_timings.interlaced; + +	if (V4L2_PIX_FMT_UYVY == pixfmt->pixelformat) +		cfg->pixfmt = PIXFMT_YCBCRI; + +	/* Change of the default pixel format for both video windows */ +	if (V4L2_PIX_FMT_NV12 == pixfmt->pixelformat) { +		struct vpbe_layer *otherlayer; +		cfg->pixfmt = PIXFMT_NV12; +		otherlayer = _vpbe_display_get_other_win_layer(disp_dev, +								layer); +		otherlayer->layer_info.config.pixfmt = PIXFMT_NV12; +	} + +	/* Set the layer config in the osd window */ +	ret = osd_device->ops.set_layer_config(osd_device, +				layer->layer_info.id, cfg); +	if (ret < 0) { +		v4l2_err(&vpbe_dev->v4l2_dev, +				"Error in S_FMT params:\n"); +		return -EINVAL; +	} + +	/* Readback and fill the local copy of current pix format */ +	osd_device->ops.get_layer_config(osd_device, +			layer->layer_info.id, cfg); + +	return 0; +} + +static int vpbe_display_try_fmt(struct file *file, void *priv, +				  struct v4l2_format *fmt) +{ +	struct vpbe_fh *fh = file->private_data; +	struct vpbe_display *disp_dev = fh->disp_dev; +	struct vpbe_device *vpbe_dev = fh->disp_dev->vpbe_dev; +	struct v4l2_pix_format *pixfmt = &fmt->fmt.pix; + +	v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "VIDIOC_TRY_FMT\n"); + +	if (V4L2_BUF_TYPE_VIDEO_OUTPUT != fmt->type) { +		v4l2_err(&vpbe_dev->v4l2_dev, "invalid type\n"); +		return -EINVAL; +	} + +	/* Check for valid field format */ +	return  vpbe_try_format(disp_dev, pixfmt, 0); + +} + +/** + * vpbe_display_s_std - Set the given standard in the encoder + * + * Sets the standard if supported by the current encoder. Return the status. + * 0 - success & -EINVAL on error + */ +static int vpbe_display_s_std(struct file *file, void *priv, +				v4l2_std_id std_id) +{ +	struct vpbe_fh *fh = priv; +	struct vpbe_layer *layer = fh->layer; +	struct vpbe_device *vpbe_dev = fh->disp_dev->vpbe_dev; +	int ret; + +	v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "VIDIOC_S_STD\n"); + +	/* If streaming is started, return error */ +	if (layer->started) { +		v4l2_err(&vpbe_dev->v4l2_dev, "Streaming is started\n"); +		return -EBUSY; +	} +	if (NULL != vpbe_dev->ops.s_std) { +		ret = vpbe_dev->ops.s_std(vpbe_dev, std_id); +		if (ret) { +			v4l2_err(&vpbe_dev->v4l2_dev, +			"Failed to set standard for sub devices\n"); +			return -EINVAL; +		} +	} else { +		return -EINVAL; +	} + +	return 0; +} + +/** + * vpbe_display_g_std - Get the standard in the current encoder + * + * Get the standard in the current encoder. Return the status. 0 - success + * -EINVAL on error + */ +static int vpbe_display_g_std(struct file *file, void *priv, +				v4l2_std_id *std_id) +{ +	struct vpbe_fh *fh = priv; +	struct vpbe_device *vpbe_dev = fh->disp_dev->vpbe_dev; + +	v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev,	"VIDIOC_G_STD\n"); + +	/* Get the standard from the current encoder */ +	if (vpbe_dev->current_timings.timings_type & VPBE_ENC_STD) { +		*std_id = vpbe_dev->current_timings.std_id; +		return 0; +	} + +	return -EINVAL; +} + +/** + * vpbe_display_enum_output - enumerate outputs + * + * Enumerates the outputs available at the vpbe display + * returns the status, -EINVAL if end of output list + */ +static int vpbe_display_enum_output(struct file *file, void *priv, +				    struct v4l2_output *output) +{ +	struct vpbe_fh *fh = priv; +	struct vpbe_device *vpbe_dev = fh->disp_dev->vpbe_dev; +	int ret; + +	v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev,	"VIDIOC_ENUM_OUTPUT\n"); + +	/* Enumerate outputs */ + +	if (NULL == vpbe_dev->ops.enum_outputs) +		return -EINVAL; + +	ret = vpbe_dev->ops.enum_outputs(vpbe_dev, output); +	if (ret) { +		v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, +			"Failed to enumerate outputs\n"); +		return -EINVAL; +	} + +	return 0; +} + +/** + * vpbe_display_s_output - Set output to + * the output specified by the index + */ +static int vpbe_display_s_output(struct file *file, void *priv, +				unsigned int i) +{ +	struct vpbe_fh *fh = priv; +	struct vpbe_layer *layer = fh->layer; +	struct vpbe_device *vpbe_dev = fh->disp_dev->vpbe_dev; +	int ret; + +	v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev,	"VIDIOC_S_OUTPUT\n"); +	/* If streaming is started, return error */ +	if (layer->started) { +		v4l2_err(&vpbe_dev->v4l2_dev, "Streaming is started\n"); +		return -EBUSY; +	} +	if (NULL == vpbe_dev->ops.set_output) +		return -EINVAL; + +	ret = vpbe_dev->ops.set_output(vpbe_dev, i); +	if (ret) { +		v4l2_err(&vpbe_dev->v4l2_dev, +			"Failed to set output for sub devices\n"); +		return -EINVAL; +	} + +	return 0; +} + +/** + * vpbe_display_g_output - Get output from subdevice + * for a given by the index + */ +static int vpbe_display_g_output(struct file *file, void *priv, +				unsigned int *i) +{ +	struct vpbe_fh *fh = priv; +	struct vpbe_device *vpbe_dev = fh->disp_dev->vpbe_dev; + +	v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "VIDIOC_G_OUTPUT\n"); +	/* Get the standard from the current encoder */ +	*i = vpbe_dev->current_out_index; + +	return 0; +} + +/** + * vpbe_display_enum_dv_timings - Enumerate the dv timings + * + * enum the timings in the current encoder. Return the status. 0 - success + * -EINVAL on error + */ +static int +vpbe_display_enum_dv_timings(struct file *file, void *priv, +			struct v4l2_enum_dv_timings *timings) +{ +	struct vpbe_fh *fh = priv; +	struct vpbe_device *vpbe_dev = fh->disp_dev->vpbe_dev; +	int ret; + +	v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "VIDIOC_ENUM_DV_TIMINGS\n"); + +	/* Enumerate outputs */ +	if (NULL == vpbe_dev->ops.enum_dv_timings) +		return -EINVAL; + +	ret = vpbe_dev->ops.enum_dv_timings(vpbe_dev, timings); +	if (ret) { +		v4l2_err(&vpbe_dev->v4l2_dev, +			"Failed to enumerate dv timings info\n"); +		return -EINVAL; +	} + +	return 0; +} + +/** + * vpbe_display_s_dv_timings - Set the dv timings + * + * Set the timings in the current encoder. Return the status. 0 - success + * -EINVAL on error + */ +static int +vpbe_display_s_dv_timings(struct file *file, void *priv, +				struct v4l2_dv_timings *timings) +{ +	struct vpbe_fh *fh = priv; +	struct vpbe_layer *layer = fh->layer; +	struct vpbe_device *vpbe_dev = fh->disp_dev->vpbe_dev; +	int ret; + +	v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "VIDIOC_S_DV_TIMINGS\n"); + + +	/* If streaming is started, return error */ +	if (layer->started) { +		v4l2_err(&vpbe_dev->v4l2_dev, "Streaming is started\n"); +		return -EBUSY; +	} + +	/* Set the given standard in the encoder */ +	if (!vpbe_dev->ops.s_dv_timings) +		return -EINVAL; + +	ret = vpbe_dev->ops.s_dv_timings(vpbe_dev, timings); +	if (ret) { +		v4l2_err(&vpbe_dev->v4l2_dev, +			"Failed to set the dv timings info\n"); +		return -EINVAL; +	} + +	return 0; +} + +/** + * vpbe_display_g_dv_timings - Set the dv timings + * + * Get the timings in the current encoder. Return the status. 0 - success + * -EINVAL on error + */ +static int +vpbe_display_g_dv_timings(struct file *file, void *priv, +				struct v4l2_dv_timings *dv_timings) +{ +	struct vpbe_fh *fh = priv; +	struct vpbe_device *vpbe_dev = fh->disp_dev->vpbe_dev; + +	v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "VIDIOC_G_DV_TIMINGS\n"); + +	/* Get the given standard in the encoder */ + +	if (vpbe_dev->current_timings.timings_type & +				VPBE_ENC_DV_TIMINGS) { +		*dv_timings = vpbe_dev->current_timings.dv_timings; +	} else { +		return -EINVAL; +	} + +	return 0; +} + +static int vpbe_display_streamoff(struct file *file, void *priv, +				enum v4l2_buf_type buf_type) +{ +	struct vpbe_fh *fh = file->private_data; +	struct vpbe_layer *layer = fh->layer; +	struct vpbe_device *vpbe_dev = fh->disp_dev->vpbe_dev; +	struct osd_state *osd_device = fh->disp_dev->osd_device; +	int ret; + +	v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, +			"VIDIOC_STREAMOFF,layer id = %d\n", +			layer->device_id); + +	if (V4L2_BUF_TYPE_VIDEO_OUTPUT != buf_type) { +		v4l2_err(&vpbe_dev->v4l2_dev, "Invalid buffer type\n"); +		return -EINVAL; +	} + +	/* If io is allowed for this file handle, return error */ +	if (!fh->io_allowed) { +		v4l2_err(&vpbe_dev->v4l2_dev, "No io_allowed\n"); +		return -EACCES; +	} + +	/* If streaming is not started, return error */ +	if (!layer->started) { +		v4l2_err(&vpbe_dev->v4l2_dev, "streaming not started in layer" +			" id = %d\n", layer->device_id); +		return -EINVAL; +	} + +	osd_device->ops.disable_layer(osd_device, +			layer->layer_info.id); +	layer->started = 0; +	ret = vb2_streamoff(&layer->buffer_queue, buf_type); + +	return ret; +} + +static int vpbe_display_streamon(struct file *file, void *priv, +			 enum v4l2_buf_type buf_type) +{ +	struct vpbe_fh *fh = file->private_data; +	struct vpbe_layer *layer = fh->layer; +	struct vpbe_display *disp_dev = fh->disp_dev; +	struct vpbe_device *vpbe_dev = fh->disp_dev->vpbe_dev; +	struct osd_state *osd_device = disp_dev->osd_device; +	int ret; + +	osd_device->ops.disable_layer(osd_device, +			layer->layer_info.id); + +	v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "VIDIOC_STREAMON, layerid=%d\n", +						layer->device_id); + +	if (V4L2_BUF_TYPE_VIDEO_OUTPUT != buf_type) { +		v4l2_err(&vpbe_dev->v4l2_dev, "Invalid buffer type\n"); +		return -EINVAL; +	} + +	/* If file handle is not allowed IO, return error */ +	if (!fh->io_allowed) { +		v4l2_err(&vpbe_dev->v4l2_dev, "No io_allowed\n"); +		return -EACCES; +	} +	/* If Streaming is already started, return error */ +	if (layer->started) { +		v4l2_err(&vpbe_dev->v4l2_dev, "layer is already streaming\n"); +		return -EBUSY; +	} + +	/* +	 * Call vb2_streamon to start streaming +	 * in videobuf +	 */ +	ret = vb2_streamon(&layer->buffer_queue, buf_type); +	if (ret) { +		v4l2_err(&vpbe_dev->v4l2_dev, +		"error in vb2_streamon\n"); +		return ret; +	} +	return ret; +} + +static int vpbe_display_dqbuf(struct file *file, void *priv, +		      struct v4l2_buffer *buf) +{ +	struct vpbe_fh *fh = file->private_data; +	struct vpbe_layer *layer = fh->layer; +	struct vpbe_device *vpbe_dev = fh->disp_dev->vpbe_dev; +	int ret; + +	v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, +		"VIDIOC_DQBUF, layer id = %d\n", +		layer->device_id); + +	if (V4L2_BUF_TYPE_VIDEO_OUTPUT != buf->type) { +		v4l2_err(&vpbe_dev->v4l2_dev, "Invalid buffer type\n"); +		return -EINVAL; +	} +	/* If this file handle is not allowed to do IO, return error */ +	if (!fh->io_allowed) { +		v4l2_err(&vpbe_dev->v4l2_dev, "No io_allowed\n"); +		return -EACCES; +	} +	if (file->f_flags & O_NONBLOCK) +		/* Call videobuf_dqbuf for non blocking mode */ +		ret = vb2_dqbuf(&layer->buffer_queue, buf, 1); +	else +		/* Call videobuf_dqbuf for blocking mode */ +		ret = vb2_dqbuf(&layer->buffer_queue, buf, 0); + +	return ret; +} + +static int vpbe_display_qbuf(struct file *file, void *priv, +		     struct v4l2_buffer *p) +{ +	struct vpbe_fh *fh = file->private_data; +	struct vpbe_layer *layer = fh->layer; +	struct vpbe_device *vpbe_dev = fh->disp_dev->vpbe_dev; + +	v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, +		"VIDIOC_QBUF, layer id = %d\n", +		layer->device_id); + +	if (V4L2_BUF_TYPE_VIDEO_OUTPUT != p->type) { +		v4l2_err(&vpbe_dev->v4l2_dev, "Invalid buffer type\n"); +		return -EINVAL; +	} + +	/* If this file handle is not allowed to do IO, return error */ +	if (!fh->io_allowed) { +		v4l2_err(&vpbe_dev->v4l2_dev, "No io_allowed\n"); +		return -EACCES; +	} + +	return vb2_qbuf(&layer->buffer_queue, p); +} + +static int vpbe_display_querybuf(struct file *file, void *priv, +			 struct v4l2_buffer *buf) +{ +	struct vpbe_fh *fh = file->private_data; +	struct vpbe_layer *layer = fh->layer; +	struct vpbe_device *vpbe_dev = fh->disp_dev->vpbe_dev; + +	v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, +		"VIDIOC_QUERYBUF, layer id = %d\n", +		layer->device_id); + +	if (V4L2_BUF_TYPE_VIDEO_OUTPUT != buf->type) { +		v4l2_err(&vpbe_dev->v4l2_dev, "Invalid buffer type\n"); +		return -EINVAL; +	} +	/* Call vb2_querybuf to get information */ +	return vb2_querybuf(&layer->buffer_queue, buf); +} + +static int vpbe_display_reqbufs(struct file *file, void *priv, +			struct v4l2_requestbuffers *req_buf) +{ +	struct vpbe_fh *fh = file->private_data; +	struct vpbe_layer *layer = fh->layer; +	struct vpbe_device *vpbe_dev = fh->disp_dev->vpbe_dev; +	struct vb2_queue *q; +	int ret; +	v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "vpbe_display_reqbufs\n"); + +	if (V4L2_BUF_TYPE_VIDEO_OUTPUT != req_buf->type) { +		v4l2_err(&vpbe_dev->v4l2_dev, "Invalid buffer type\n"); +		return -EINVAL; +	} + +	/* If io users of the layer is not zero, return error */ +	if (0 != layer->io_usrs) { +		v4l2_err(&vpbe_dev->v4l2_dev, "not IO user\n"); +		return -EBUSY; +	} +	/* Initialize videobuf queue as per the buffer type */ +	layer->alloc_ctx = vb2_dma_contig_init_ctx(vpbe_dev->pdev); +	if (IS_ERR(layer->alloc_ctx)) { +		v4l2_err(&vpbe_dev->v4l2_dev, "Failed to get the context\n"); +		return PTR_ERR(layer->alloc_ctx); +	} +	q = &layer->buffer_queue; +	memset(q, 0, sizeof(*q)); +	q->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; +	q->io_modes = VB2_MMAP | VB2_USERPTR; +	q->drv_priv = fh; +	q->ops = &video_qops; +	q->mem_ops = &vb2_dma_contig_memops; +	q->buf_struct_size = sizeof(struct vpbe_disp_buffer); +	q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; +	q->min_buffers_needed = 1; + +	ret = vb2_queue_init(q); +	if (ret) { +		v4l2_err(&vpbe_dev->v4l2_dev, "vb2_queue_init() failed\n"); +		vb2_dma_contig_cleanup_ctx(layer->alloc_ctx); +		return ret; +	} +	/* Set io allowed member of file handle to TRUE */ +	fh->io_allowed = 1; +	/* Increment io usrs member of layer object to 1 */ +	layer->io_usrs = 1; +	/* Store type of memory requested in layer object */ +	layer->memory = req_buf->memory; +	/* Initialize buffer queue */ +	INIT_LIST_HEAD(&layer->dma_queue); +	/* Allocate buffers */ +	return vb2_reqbufs(q, req_buf); +} + +/* + * vpbe_display_mmap() + * It is used to map kernel space buffers into user spaces + */ +static int vpbe_display_mmap(struct file *filep, struct vm_area_struct *vma) +{ +	/* Get the layer object and file handle object */ +	struct vpbe_fh *fh = filep->private_data; +	struct vpbe_layer *layer = fh->layer; +	struct vpbe_device *vpbe_dev = fh->disp_dev->vpbe_dev; +	int ret; + +	v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "vpbe_display_mmap\n"); + +	if (mutex_lock_interruptible(&layer->opslock)) +		return -ERESTARTSYS; +	ret = vb2_mmap(&layer->buffer_queue, vma); +	mutex_unlock(&layer->opslock); +	return ret; +} + +/* vpbe_display_poll(): It is used for select/poll system call + */ +static unsigned int vpbe_display_poll(struct file *filep, poll_table *wait) +{ +	struct vpbe_fh *fh = filep->private_data; +	struct vpbe_layer *layer = fh->layer; +	struct vpbe_device *vpbe_dev = fh->disp_dev->vpbe_dev; +	unsigned int err = 0; + +	v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "vpbe_display_poll\n"); +	if (layer->started) { +		mutex_lock(&layer->opslock); +		err = vb2_poll(&layer->buffer_queue, filep, wait); +		mutex_unlock(&layer->opslock); +	} +	return err; +} + +/* + * vpbe_display_open() + * It creates object of file handle structure and stores it in private_data + * member of filepointer + */ +static int vpbe_display_open(struct file *file) +{ +	struct vpbe_fh *fh = NULL; +	struct vpbe_layer *layer = video_drvdata(file); +	struct video_device *vdev = video_devdata(file); +	struct vpbe_display *disp_dev = layer->disp_dev; +	struct vpbe_device *vpbe_dev = disp_dev->vpbe_dev; +	struct osd_state *osd_device = disp_dev->osd_device; +	int err; + +	/* Allocate memory for the file handle object */ +	fh = kmalloc(sizeof(struct vpbe_fh), GFP_KERNEL); +	if (fh == NULL) { +		v4l2_err(&vpbe_dev->v4l2_dev, +			"unable to allocate memory for file handle object\n"); +		return -ENOMEM; +	} +	v4l2_fh_init(&fh->fh, vdev); +	v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, +			"vpbe display open plane = %d\n", +			layer->device_id); + +	/* store pointer to fh in private_data member of filep */ +	file->private_data = fh; +	fh->layer = layer; +	fh->disp_dev = disp_dev; + +	if (!layer->usrs) { +		if (mutex_lock_interruptible(&layer->opslock)) +			return -ERESTARTSYS; +		/* First claim the layer for this device */ +		err = osd_device->ops.request_layer(osd_device, +						layer->layer_info.id); +		mutex_unlock(&layer->opslock); +		if (err < 0) { +			/* Couldn't get layer */ +			v4l2_err(&vpbe_dev->v4l2_dev, +				"Display Manager failed to allocate layer\n"); +			kfree(fh); +			return -EINVAL; +		} +	} +	/* Increment layer usrs counter */ +	layer->usrs++; +	/* Set io_allowed member to false */ +	fh->io_allowed = 0; +	v4l2_fh_add(&fh->fh); +	v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, +			"vpbe display device opened successfully\n"); +	return 0; +} + +/* + * vpbe_display_release() + * This function deletes buffer queue, frees the buffers and the davinci + * display file * handle + */ +static int vpbe_display_release(struct file *file) +{ +	/* Get the layer object and file handle object */ +	struct vpbe_fh *fh = file->private_data; +	struct vpbe_layer *layer = fh->layer; +	struct osd_layer_config *cfg  = &layer->layer_info.config; +	struct vpbe_display *disp_dev = fh->disp_dev; +	struct vpbe_device *vpbe_dev = disp_dev->vpbe_dev; +	struct osd_state *osd_device = disp_dev->osd_device; + +	v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "vpbe_display_release\n"); + +	mutex_lock(&layer->opslock); +	/* if this instance is doing IO */ +	if (fh->io_allowed) { +		/* Reset io_usrs member of layer object */ +		layer->io_usrs = 0; + +		osd_device->ops.disable_layer(osd_device, +				layer->layer_info.id); +		layer->started = 0; +		/* Free buffers allocated */ +		vb2_queue_release(&layer->buffer_queue); +		vb2_dma_contig_cleanup_ctx(&layer->buffer_queue); +	} + +	/* Decrement layer usrs counter */ +	layer->usrs--; +	/* If this file handle has initialize encoder device, reset it */ +	if (!layer->usrs) { +		if (cfg->pixfmt == PIXFMT_NV12) { +			struct vpbe_layer *otherlayer; +			otherlayer = +			_vpbe_display_get_other_win_layer(disp_dev, layer); +			osd_device->ops.disable_layer(osd_device, +					otherlayer->layer_info.id); +			osd_device->ops.release_layer(osd_device, +					otherlayer->layer_info.id); +		} +		osd_device->ops.disable_layer(osd_device, +				layer->layer_info.id); +		osd_device->ops.release_layer(osd_device, +				layer->layer_info.id); +	} + +	v4l2_fh_del(&fh->fh); +	v4l2_fh_exit(&fh->fh); +	file->private_data = NULL; +	mutex_unlock(&layer->opslock); + +	/* Free memory allocated to file handle object */ +	kfree(fh); + +	disp_dev->cbcr_ofst = 0; + +	return 0; +} + +/* vpbe capture ioctl operations */ +static const struct v4l2_ioctl_ops vpbe_ioctl_ops = { +	.vidioc_querycap	 = vpbe_display_querycap, +	.vidioc_g_fmt_vid_out    = vpbe_display_g_fmt, +	.vidioc_enum_fmt_vid_out = vpbe_display_enum_fmt, +	.vidioc_s_fmt_vid_out    = vpbe_display_s_fmt, +	.vidioc_try_fmt_vid_out  = vpbe_display_try_fmt, +	.vidioc_reqbufs		 = vpbe_display_reqbufs, +	.vidioc_querybuf	 = vpbe_display_querybuf, +	.vidioc_qbuf		 = vpbe_display_qbuf, +	.vidioc_dqbuf		 = vpbe_display_dqbuf, +	.vidioc_streamon	 = vpbe_display_streamon, +	.vidioc_streamoff	 = vpbe_display_streamoff, +	.vidioc_cropcap		 = vpbe_display_cropcap, +	.vidioc_g_crop		 = vpbe_display_g_crop, +	.vidioc_s_crop		 = vpbe_display_s_crop, +	.vidioc_s_std		 = vpbe_display_s_std, +	.vidioc_g_std		 = vpbe_display_g_std, +	.vidioc_enum_output	 = vpbe_display_enum_output, +	.vidioc_s_output	 = vpbe_display_s_output, +	.vidioc_g_output	 = vpbe_display_g_output, +	.vidioc_s_dv_timings	 = vpbe_display_s_dv_timings, +	.vidioc_g_dv_timings	 = vpbe_display_g_dv_timings, +	.vidioc_enum_dv_timings	 = vpbe_display_enum_dv_timings, +}; + +static struct v4l2_file_operations vpbe_fops = { +	.owner = THIS_MODULE, +	.open = vpbe_display_open, +	.release = vpbe_display_release, +	.unlocked_ioctl = video_ioctl2, +	.mmap = vpbe_display_mmap, +	.poll = vpbe_display_poll +}; + +static int vpbe_device_get(struct device *dev, void *data) +{ +	struct platform_device *pdev = to_platform_device(dev); +	struct vpbe_display *vpbe_disp  = data; + +	if (strcmp("vpbe_controller", pdev->name) == 0) +		vpbe_disp->vpbe_dev = platform_get_drvdata(pdev); + +	if (strstr(pdev->name, "vpbe-osd") != NULL) +		vpbe_disp->osd_device = platform_get_drvdata(pdev); + +	return 0; +} + +static int init_vpbe_layer(int i, struct vpbe_display *disp_dev, +			   struct platform_device *pdev) +{ +	struct vpbe_layer *vpbe_display_layer = NULL; +	struct video_device *vbd = NULL; + +	/* Allocate memory for four plane display objects */ + +	disp_dev->dev[i] = +		kzalloc(sizeof(struct vpbe_layer), GFP_KERNEL); + +	/* If memory allocation fails, return error */ +	if (!disp_dev->dev[i]) { +		printk(KERN_ERR "ran out of memory\n"); +		return  -ENOMEM; +	} +	spin_lock_init(&disp_dev->dev[i]->irqlock); +	mutex_init(&disp_dev->dev[i]->opslock); + +	/* Get the pointer to the layer object */ +	vpbe_display_layer = disp_dev->dev[i]; +	vbd = &vpbe_display_layer->video_dev; +	/* Initialize field of video device */ +	vbd->release	= video_device_release_empty; +	vbd->fops	= &vpbe_fops; +	vbd->ioctl_ops	= &vpbe_ioctl_ops; +	vbd->minor	= -1; +	vbd->v4l2_dev   = &disp_dev->vpbe_dev->v4l2_dev; +	vbd->lock	= &vpbe_display_layer->opslock; +	vbd->vfl_dir	= VFL_DIR_TX; + +	if (disp_dev->vpbe_dev->current_timings.timings_type & +			VPBE_ENC_STD) +		vbd->tvnorms = (V4L2_STD_525_60 | V4L2_STD_625_50); + +	snprintf(vbd->name, sizeof(vbd->name), +			"DaVinci_VPBE Display_DRIVER_V%d.%d.%d", +			(VPBE_DISPLAY_VERSION_CODE >> 16) & 0xff, +			(VPBE_DISPLAY_VERSION_CODE >> 8) & 0xff, +			(VPBE_DISPLAY_VERSION_CODE) & 0xff); + +	vpbe_display_layer->device_id = i; + +	vpbe_display_layer->layer_info.id = +		((i == VPBE_DISPLAY_DEVICE_0) ? WIN_VID0 : WIN_VID1); + + +	return 0; +} + +static int register_device(struct vpbe_layer *vpbe_display_layer, +			   struct vpbe_display *disp_dev, +			   struct platform_device *pdev) +{ +	int err; + +	v4l2_info(&disp_dev->vpbe_dev->v4l2_dev, +		  "Trying to register VPBE display device.\n"); +	v4l2_info(&disp_dev->vpbe_dev->v4l2_dev, +		  "layer=%x,layer->video_dev=%x\n", +		  (int)vpbe_display_layer, +		  (int)&vpbe_display_layer->video_dev); + +	err = video_register_device(&vpbe_display_layer->video_dev, +				    VFL_TYPE_GRABBER, +				    -1); +	if (err) +		return -ENODEV; + +	vpbe_display_layer->disp_dev = disp_dev; +	/* set the driver data in platform device */ +	platform_set_drvdata(pdev, disp_dev); +	set_bit(V4L2_FL_USE_FH_PRIO, &vpbe_display_layer->video_dev.flags); +	video_set_drvdata(&vpbe_display_layer->video_dev, +			  vpbe_display_layer); + +	return 0; +} + + + +/* + * vpbe_display_probe() + * This function creates device entries by register itself to the V4L2 driver + * and initializes fields of each layer objects + */ +static int vpbe_display_probe(struct platform_device *pdev) +{ +	struct vpbe_layer *vpbe_display_layer; +	struct vpbe_display *disp_dev; +	struct resource *res = NULL; +	int k; +	int i; +	int err; +	int irq; + +	printk(KERN_DEBUG "vpbe_display_probe\n"); +	/* Allocate memory for vpbe_display */ +	disp_dev = devm_kzalloc(&pdev->dev, sizeof(struct vpbe_display), +				GFP_KERNEL); +	if (!disp_dev) +		return -ENOMEM; + +	spin_lock_init(&disp_dev->dma_queue_lock); +	/* +	 * Scan all the platform devices to find the vpbe +	 * controller device and get the vpbe_dev object +	 */ +	err = bus_for_each_dev(&platform_bus_type, NULL, disp_dev, +			vpbe_device_get); +	if (err < 0) +		return err; +	/* Initialize the vpbe display controller */ +	if (NULL != disp_dev->vpbe_dev->ops.initialize) { +		err = disp_dev->vpbe_dev->ops.initialize(&pdev->dev, +							 disp_dev->vpbe_dev); +		if (err) { +			v4l2_err(&disp_dev->vpbe_dev->v4l2_dev, +					"Error initing vpbe\n"); +			err = -ENOMEM; +			goto probe_out; +		} +	} + +	for (i = 0; i < VPBE_DISPLAY_MAX_DEVICES; i++) { +		if (init_vpbe_layer(i, disp_dev, pdev)) { +			err = -ENODEV; +			goto probe_out; +		} +	} + +	res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); +	if (!res) { +		v4l2_err(&disp_dev->vpbe_dev->v4l2_dev, +			 "Unable to get VENC interrupt resource\n"); +		err = -ENODEV; +		goto probe_out; +	} + +	irq = res->start; +	err = devm_request_irq(&pdev->dev, irq, venc_isr, 0, +			       VPBE_DISPLAY_DRIVER, disp_dev); +	if (err) { +		v4l2_err(&disp_dev->vpbe_dev->v4l2_dev, +				"Unable to request interrupt\n"); +		goto probe_out; +	} + +	for (i = 0; i < VPBE_DISPLAY_MAX_DEVICES; i++) { +		if (register_device(disp_dev->dev[i], disp_dev, pdev)) { +			err = -ENODEV; +			goto probe_out; +		} +	} + +	printk(KERN_DEBUG "Successfully completed the probing of vpbe v4l2 device\n"); +	return 0; + +probe_out: +	for (k = 0; k < VPBE_DISPLAY_MAX_DEVICES; k++) { +		/* Get the pointer to the layer object */ +		vpbe_display_layer = disp_dev->dev[k]; +		/* Unregister video device */ +		if (vpbe_display_layer) { +			video_unregister_device( +				&vpbe_display_layer->video_dev); +				kfree(disp_dev->dev[k]); +		} +	} +	return err; +} + +/* + * vpbe_display_remove() + * It un-register hardware layer from V4L2 driver + */ +static int vpbe_display_remove(struct platform_device *pdev) +{ +	struct vpbe_layer *vpbe_display_layer; +	struct vpbe_display *disp_dev = platform_get_drvdata(pdev); +	struct vpbe_device *vpbe_dev = disp_dev->vpbe_dev; +	int i; + +	v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "vpbe_display_remove\n"); + +	/* deinitialize the vpbe display controller */ +	if (NULL != vpbe_dev->ops.deinitialize) +		vpbe_dev->ops.deinitialize(&pdev->dev, vpbe_dev); +	/* un-register device */ +	for (i = 0; i < VPBE_DISPLAY_MAX_DEVICES; i++) { +		/* Get the pointer to the layer object */ +		vpbe_display_layer = disp_dev->dev[i]; +		/* Unregister video device */ +		video_unregister_device(&vpbe_display_layer->video_dev); + +	} +	for (i = 0; i < VPBE_DISPLAY_MAX_DEVICES; i++) { +		kfree(disp_dev->dev[i]); +		disp_dev->dev[i] = NULL; +	} + +	return 0; +} + +static struct platform_driver vpbe_display_driver = { +	.driver = { +		.name = VPBE_DISPLAY_DRIVER, +		.owner = THIS_MODULE, +		.bus = &platform_bus_type, +	}, +	.probe = vpbe_display_probe, +	.remove = vpbe_display_remove, +}; + +module_platform_driver(vpbe_display_driver); + +MODULE_DESCRIPTION("TI DM644x/DM355/DM365 VPBE Display controller"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Texas Instruments"); diff --git a/drivers/media/platform/davinci/vpbe_osd.c b/drivers/media/platform/davinci/vpbe_osd.c new file mode 100644 index 00000000000..d053c2669c1 --- /dev/null +++ b/drivers/media/platform/davinci/vpbe_osd.c @@ -0,0 +1,1597 @@ +/* + * Copyright (C) 2007-2010 Texas Instruments Inc + * Copyright (C) 2007 MontaVista Software, Inc. + * + * Andy Lowe (alowe@mvista.com), MontaVista Software + * - Initial version + * Murali Karicheri (mkaricheri@gmail.com), Texas Instruments Ltd. + * - ported to sub device interface + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation version 2. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA + * + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/slab.h> + +#include <mach/cputype.h> +#include <mach/hardware.h> + +#include <media/davinci/vpss.h> +#include <media/v4l2-device.h> +#include <media/davinci/vpbe_types.h> +#include <media/davinci/vpbe_osd.h> + +#include <linux/io.h> +#include "vpbe_osd_regs.h" + +#define MODULE_NAME	"davinci-vpbe-osd" + +static struct platform_device_id vpbe_osd_devtype[] = { +	{ +		.name = DM644X_VPBE_OSD_SUBDEV_NAME, +		.driver_data = VPBE_VERSION_1, +	}, { +		.name = DM365_VPBE_OSD_SUBDEV_NAME, +		.driver_data = VPBE_VERSION_2, +	}, { +		.name = DM355_VPBE_OSD_SUBDEV_NAME, +		.driver_data = VPBE_VERSION_3, +	}, +	{ +		/* sentinel */ +	} +}; + +MODULE_DEVICE_TABLE(platform, vpbe_osd_devtype); + +/* register access routines */ +static inline u32 osd_read(struct osd_state *sd, u32 offset) +{ +	struct osd_state *osd = sd; + +	return readl(osd->osd_base + offset); +} + +static inline u32 osd_write(struct osd_state *sd, u32 val, u32 offset) +{ +	struct osd_state *osd = sd; + +	writel(val, osd->osd_base + offset); + +	return val; +} + +static inline u32 osd_set(struct osd_state *sd, u32 mask, u32 offset) +{ +	struct osd_state *osd = sd; + +	void __iomem *addr = osd->osd_base + offset; +	u32 val = readl(addr) | mask; + +	writel(val, addr); + +	return val; +} + +static inline u32 osd_clear(struct osd_state *sd, u32 mask, u32 offset) +{ +	struct osd_state *osd = sd; + +	void __iomem *addr = osd->osd_base + offset; +	u32 val = readl(addr) & ~mask; + +	writel(val, addr); + +	return val; +} + +static inline u32 osd_modify(struct osd_state *sd, u32 mask, u32 val, +				 u32 offset) +{ +	struct osd_state *osd = sd; + +	void __iomem *addr = osd->osd_base + offset; +	u32 new_val = (readl(addr) & ~mask) | (val & mask); + +	writel(new_val, addr); + +	return new_val; +} + +/* define some macros for layer and pixfmt classification */ +#define is_osd_win(layer) (((layer) == WIN_OSD0) || ((layer) == WIN_OSD1)) +#define is_vid_win(layer) (((layer) == WIN_VID0) || ((layer) == WIN_VID1)) +#define is_rgb_pixfmt(pixfmt) \ +	(((pixfmt) == PIXFMT_RGB565) || ((pixfmt) == PIXFMT_RGB888)) +#define is_yc_pixfmt(pixfmt) \ +	(((pixfmt) == PIXFMT_YCBCRI) || ((pixfmt) == PIXFMT_YCRCBI) || \ +	((pixfmt) == PIXFMT_NV12)) +#define MAX_WIN_SIZE OSD_VIDWIN0XP_V0X +#define MAX_LINE_LENGTH (OSD_VIDWIN0OFST_V0LO << 5) + +/** + * _osd_dm6446_vid0_pingpong() - field inversion fix for DM6446 + * @sd - ptr to struct osd_state + * @field_inversion - inversion flag + * @fb_base_phys - frame buffer address + * @lconfig - ptr to layer config + * + * This routine implements a workaround for the field signal inversion silicon + * erratum described in Advisory 1.3.8 for the DM6446.  The fb_base_phys and + * lconfig parameters apply to the vid0 window.  This routine should be called + * whenever the vid0 layer configuration or start address is modified, or when + * the OSD field inversion setting is modified. + * Returns: 1 if the ping-pong buffers need to be toggled in the vsync isr, or + *          0 otherwise + */ +static int _osd_dm6446_vid0_pingpong(struct osd_state *sd, +				     int field_inversion, +				     unsigned long fb_base_phys, +				     const struct osd_layer_config *lconfig) +{ +	struct osd_platform_data *pdata; + +	pdata = (struct osd_platform_data *)sd->dev->platform_data; +	if (pdata != NULL && pdata->field_inv_wa_enable) { + +		if (!field_inversion || !lconfig->interlaced) { +			osd_write(sd, fb_base_phys & ~0x1F, OSD_VIDWIN0ADR); +			osd_write(sd, fb_base_phys & ~0x1F, OSD_PPVWIN0ADR); +			osd_modify(sd, OSD_MISCCTL_PPSW | OSD_MISCCTL_PPRV, 0, +				   OSD_MISCCTL); +			return 0; +		} else { +			unsigned miscctl = OSD_MISCCTL_PPRV; + +			osd_write(sd, +				(fb_base_phys & ~0x1F) - lconfig->line_length, +				OSD_VIDWIN0ADR); +			osd_write(sd, +				(fb_base_phys & ~0x1F) + lconfig->line_length, +				OSD_PPVWIN0ADR); +			osd_modify(sd, +				OSD_MISCCTL_PPSW | OSD_MISCCTL_PPRV, miscctl, +				OSD_MISCCTL); + +			return 1; +		} +	} + +	return 0; +} + +static void _osd_set_field_inversion(struct osd_state *sd, int enable) +{ +	unsigned fsinv = 0; + +	if (enable) +		fsinv = OSD_MODE_FSINV; + +	osd_modify(sd, OSD_MODE_FSINV, fsinv, OSD_MODE); +} + +static void _osd_set_blink_attribute(struct osd_state *sd, int enable, +				     enum osd_blink_interval blink) +{ +	u32 osdatrmd = 0; + +	if (enable) { +		osdatrmd |= OSD_OSDATRMD_BLNK; +		osdatrmd |= blink << OSD_OSDATRMD_BLNKINT_SHIFT; +	} +	/* caller must ensure that OSD1 is configured in attribute mode */ +	osd_modify(sd, OSD_OSDATRMD_BLNKINT | OSD_OSDATRMD_BLNK, osdatrmd, +		  OSD_OSDATRMD); +} + +static void _osd_set_rom_clut(struct osd_state *sd, +			      enum osd_rom_clut rom_clut) +{ +	if (rom_clut == ROM_CLUT0) +		osd_clear(sd, OSD_MISCCTL_RSEL, OSD_MISCCTL); +	else +		osd_set(sd, OSD_MISCCTL_RSEL, OSD_MISCCTL); +} + +static void _osd_set_palette_map(struct osd_state *sd, +				 enum osd_win_layer osdwin, +				 unsigned char pixel_value, +				 unsigned char clut_index, +				 enum osd_pix_format pixfmt) +{ +	static const int map_2bpp[] = { 0, 5, 10, 15 }; +	static const int map_1bpp[] = { 0, 15 }; +	int bmp_offset; +	int bmp_shift; +	int bmp_mask; +	int bmp_reg; + +	switch (pixfmt) { +	case PIXFMT_1BPP: +		bmp_reg = map_1bpp[pixel_value & 0x1]; +		break; +	case PIXFMT_2BPP: +		bmp_reg = map_2bpp[pixel_value & 0x3]; +		break; +	case PIXFMT_4BPP: +		bmp_reg = pixel_value & 0xf; +		break; +	default: +		return; +	} + +	switch (osdwin) { +	case OSDWIN_OSD0: +		bmp_offset = OSD_W0BMP01 + (bmp_reg >> 1) * sizeof(u32); +		break; +	case OSDWIN_OSD1: +		bmp_offset = OSD_W1BMP01 + (bmp_reg >> 1) * sizeof(u32); +		break; +	default: +		return; +	} + +	if (bmp_reg & 1) { +		bmp_shift = 8; +		bmp_mask = 0xff << 8; +	} else { +		bmp_shift = 0; +		bmp_mask = 0xff; +	} + +	osd_modify(sd, bmp_mask, clut_index << bmp_shift, bmp_offset); +} + +static void _osd_set_rec601_attenuation(struct osd_state *sd, +					enum osd_win_layer osdwin, int enable) +{ +	switch (osdwin) { +	case OSDWIN_OSD0: +		osd_modify(sd, OSD_OSDWIN0MD_ATN0E, +			  enable ? OSD_OSDWIN0MD_ATN0E : 0, +			  OSD_OSDWIN0MD); +		if (sd->vpbe_type == VPBE_VERSION_1) +			osd_modify(sd, OSD_OSDWIN0MD_ATN0E, +				  enable ? OSD_OSDWIN0MD_ATN0E : 0, +				  OSD_OSDWIN0MD); +		else if ((sd->vpbe_type == VPBE_VERSION_3) || +			   (sd->vpbe_type == VPBE_VERSION_2)) +			osd_modify(sd, OSD_EXTMODE_ATNOSD0EN, +				  enable ? OSD_EXTMODE_ATNOSD0EN : 0, +				  OSD_EXTMODE); +		break; +	case OSDWIN_OSD1: +		osd_modify(sd, OSD_OSDWIN1MD_ATN1E, +			  enable ? OSD_OSDWIN1MD_ATN1E : 0, +			  OSD_OSDWIN1MD); +		if (sd->vpbe_type == VPBE_VERSION_1) +			osd_modify(sd, OSD_OSDWIN1MD_ATN1E, +				  enable ? OSD_OSDWIN1MD_ATN1E : 0, +				  OSD_OSDWIN1MD); +		else if ((sd->vpbe_type == VPBE_VERSION_3) || +			   (sd->vpbe_type == VPBE_VERSION_2)) +			osd_modify(sd, OSD_EXTMODE_ATNOSD1EN, +				  enable ? OSD_EXTMODE_ATNOSD1EN : 0, +				  OSD_EXTMODE); +		break; +	} +} + +static void _osd_set_blending_factor(struct osd_state *sd, +				     enum osd_win_layer osdwin, +				     enum osd_blending_factor blend) +{ +	switch (osdwin) { +	case OSDWIN_OSD0: +		osd_modify(sd, OSD_OSDWIN0MD_BLND0, +			  blend << OSD_OSDWIN0MD_BLND0_SHIFT, OSD_OSDWIN0MD); +		break; +	case OSDWIN_OSD1: +		osd_modify(sd, OSD_OSDWIN1MD_BLND1, +			  blend << OSD_OSDWIN1MD_BLND1_SHIFT, OSD_OSDWIN1MD); +		break; +	} +} + +static void _osd_enable_rgb888_pixblend(struct osd_state *sd, +					enum osd_win_layer osdwin) +{ + +	osd_modify(sd, OSD_MISCCTL_BLDSEL, 0, OSD_MISCCTL); +	switch (osdwin) { +	case OSDWIN_OSD0: +		osd_modify(sd, OSD_EXTMODE_OSD0BLDCHR, +			  OSD_EXTMODE_OSD0BLDCHR, OSD_EXTMODE); +		break; +	case OSDWIN_OSD1: +		osd_modify(sd, OSD_EXTMODE_OSD1BLDCHR, +			  OSD_EXTMODE_OSD1BLDCHR, OSD_EXTMODE); +		break; +	} +} + +static void _osd_enable_color_key(struct osd_state *sd, +				  enum osd_win_layer osdwin, +				  unsigned colorkey, +				  enum osd_pix_format pixfmt) +{ +	switch (pixfmt) { +	case PIXFMT_1BPP: +	case PIXFMT_2BPP: +	case PIXFMT_4BPP: +	case PIXFMT_8BPP: +		if (sd->vpbe_type == VPBE_VERSION_3) { +			switch (osdwin) { +			case OSDWIN_OSD0: +				osd_modify(sd, OSD_TRANSPBMPIDX_BMP0, +					  colorkey << +					  OSD_TRANSPBMPIDX_BMP0_SHIFT, +					  OSD_TRANSPBMPIDX); +				break; +			case OSDWIN_OSD1: +				osd_modify(sd, OSD_TRANSPBMPIDX_BMP1, +					  colorkey << +					  OSD_TRANSPBMPIDX_BMP1_SHIFT, +					  OSD_TRANSPBMPIDX); +				break; +			} +		} +		break; +	case PIXFMT_RGB565: +		if (sd->vpbe_type == VPBE_VERSION_1) +			osd_write(sd, colorkey & OSD_TRANSPVAL_RGBTRANS, +				  OSD_TRANSPVAL); +		else if (sd->vpbe_type == VPBE_VERSION_3) +			osd_write(sd, colorkey & OSD_TRANSPVALL_RGBL, +				  OSD_TRANSPVALL); +		break; +	case PIXFMT_YCBCRI: +	case PIXFMT_YCRCBI: +		if (sd->vpbe_type == VPBE_VERSION_3) +			osd_modify(sd, OSD_TRANSPVALU_Y, colorkey, +				   OSD_TRANSPVALU); +		break; +	case PIXFMT_RGB888: +		if (sd->vpbe_type == VPBE_VERSION_3) { +			osd_write(sd, colorkey & OSD_TRANSPVALL_RGBL, +				  OSD_TRANSPVALL); +			osd_modify(sd, OSD_TRANSPVALU_RGBU, colorkey >> 16, +				  OSD_TRANSPVALU); +		} +		break; +	default: +		break; +	} + +	switch (osdwin) { +	case OSDWIN_OSD0: +		osd_set(sd, OSD_OSDWIN0MD_TE0, OSD_OSDWIN0MD); +		break; +	case OSDWIN_OSD1: +		osd_set(sd, OSD_OSDWIN1MD_TE1, OSD_OSDWIN1MD); +		break; +	} +} + +static void _osd_disable_color_key(struct osd_state *sd, +				   enum osd_win_layer osdwin) +{ +	switch (osdwin) { +	case OSDWIN_OSD0: +		osd_clear(sd, OSD_OSDWIN0MD_TE0, OSD_OSDWIN0MD); +		break; +	case OSDWIN_OSD1: +		osd_clear(sd, OSD_OSDWIN1MD_TE1, OSD_OSDWIN1MD); +		break; +	} +} + +static void _osd_set_osd_clut(struct osd_state *sd, +			      enum osd_win_layer osdwin, +			      enum osd_clut clut) +{ +	u32 winmd = 0; + +	switch (osdwin) { +	case OSDWIN_OSD0: +		if (clut == RAM_CLUT) +			winmd |= OSD_OSDWIN0MD_CLUTS0; +		osd_modify(sd, OSD_OSDWIN0MD_CLUTS0, winmd, OSD_OSDWIN0MD); +		break; +	case OSDWIN_OSD1: +		if (clut == RAM_CLUT) +			winmd |= OSD_OSDWIN1MD_CLUTS1; +		osd_modify(sd, OSD_OSDWIN1MD_CLUTS1, winmd, OSD_OSDWIN1MD); +		break; +	} +} + +static void _osd_set_zoom(struct osd_state *sd, enum osd_layer layer, +			  enum osd_zoom_factor h_zoom, +			  enum osd_zoom_factor v_zoom) +{ +	u32 winmd = 0; + +	switch (layer) { +	case WIN_OSD0: +		winmd |= (h_zoom << OSD_OSDWIN0MD_OHZ0_SHIFT); +		winmd |= (v_zoom << OSD_OSDWIN0MD_OVZ0_SHIFT); +		osd_modify(sd, OSD_OSDWIN0MD_OHZ0 | OSD_OSDWIN0MD_OVZ0, winmd, +			  OSD_OSDWIN0MD); +		break; +	case WIN_VID0: +		winmd |= (h_zoom << OSD_VIDWINMD_VHZ0_SHIFT); +		winmd |= (v_zoom << OSD_VIDWINMD_VVZ0_SHIFT); +		osd_modify(sd, OSD_VIDWINMD_VHZ0 | OSD_VIDWINMD_VVZ0, winmd, +			  OSD_VIDWINMD); +		break; +	case WIN_OSD1: +		winmd |= (h_zoom << OSD_OSDWIN1MD_OHZ1_SHIFT); +		winmd |= (v_zoom << OSD_OSDWIN1MD_OVZ1_SHIFT); +		osd_modify(sd, OSD_OSDWIN1MD_OHZ1 | OSD_OSDWIN1MD_OVZ1, winmd, +			  OSD_OSDWIN1MD); +		break; +	case WIN_VID1: +		winmd |= (h_zoom << OSD_VIDWINMD_VHZ1_SHIFT); +		winmd |= (v_zoom << OSD_VIDWINMD_VVZ1_SHIFT); +		osd_modify(sd, OSD_VIDWINMD_VHZ1 | OSD_VIDWINMD_VVZ1, winmd, +			  OSD_VIDWINMD); +		break; +	} +} + +static void _osd_disable_layer(struct osd_state *sd, enum osd_layer layer) +{ +	switch (layer) { +	case WIN_OSD0: +		osd_clear(sd, OSD_OSDWIN0MD_OACT0, OSD_OSDWIN0MD); +		break; +	case WIN_VID0: +		osd_clear(sd, OSD_VIDWINMD_ACT0, OSD_VIDWINMD); +		break; +	case WIN_OSD1: +		/* disable attribute mode as well as disabling the window */ +		osd_clear(sd, OSD_OSDWIN1MD_OASW | OSD_OSDWIN1MD_OACT1, +			  OSD_OSDWIN1MD); +		break; +	case WIN_VID1: +		osd_clear(sd, OSD_VIDWINMD_ACT1, OSD_VIDWINMD); +		break; +	} +} + +static void osd_disable_layer(struct osd_state *sd, enum osd_layer layer) +{ +	struct osd_state *osd = sd; +	struct osd_window_state *win = &osd->win[layer]; +	unsigned long flags; + +	spin_lock_irqsave(&osd->lock, flags); + +	if (!win->is_enabled) { +		spin_unlock_irqrestore(&osd->lock, flags); +		return; +	} +	win->is_enabled = 0; + +	_osd_disable_layer(sd, layer); + +	spin_unlock_irqrestore(&osd->lock, flags); +} + +static void _osd_enable_attribute_mode(struct osd_state *sd) +{ +	/* enable attribute mode for OSD1 */ +	osd_set(sd, OSD_OSDWIN1MD_OASW, OSD_OSDWIN1MD); +} + +static void _osd_enable_layer(struct osd_state *sd, enum osd_layer layer) +{ +	switch (layer) { +	case WIN_OSD0: +		osd_set(sd, OSD_OSDWIN0MD_OACT0, OSD_OSDWIN0MD); +		break; +	case WIN_VID0: +		osd_set(sd, OSD_VIDWINMD_ACT0, OSD_VIDWINMD); +		break; +	case WIN_OSD1: +		/* enable OSD1 and disable attribute mode */ +		osd_modify(sd, OSD_OSDWIN1MD_OASW | OSD_OSDWIN1MD_OACT1, +			  OSD_OSDWIN1MD_OACT1, OSD_OSDWIN1MD); +		break; +	case WIN_VID1: +		osd_set(sd, OSD_VIDWINMD_ACT1, OSD_VIDWINMD); +		break; +	} +} + +static int osd_enable_layer(struct osd_state *sd, enum osd_layer layer, +			    int otherwin) +{ +	struct osd_state *osd = sd; +	struct osd_window_state *win = &osd->win[layer]; +	struct osd_layer_config *cfg = &win->lconfig; +	unsigned long flags; + +	spin_lock_irqsave(&osd->lock, flags); + +	/* +	 * use otherwin flag to know this is the other vid window +	 * in YUV420 mode, if is, skip this check +	 */ +	if (!otherwin && (!win->is_allocated || +			!win->fb_base_phys || +			!cfg->line_length || +			!cfg->xsize || +			!cfg->ysize)) { +		spin_unlock_irqrestore(&osd->lock, flags); +		return -1; +	} + +	if (win->is_enabled) { +		spin_unlock_irqrestore(&osd->lock, flags); +		return 0; +	} +	win->is_enabled = 1; + +	if (cfg->pixfmt != PIXFMT_OSD_ATTR) +		_osd_enable_layer(sd, layer); +	else { +		_osd_enable_attribute_mode(sd); +		_osd_set_blink_attribute(sd, osd->is_blinking, osd->blink); +	} + +	spin_unlock_irqrestore(&osd->lock, flags); + +	return 0; +} + +#define OSD_SRC_ADDR_HIGH4	0x7800000 +#define OSD_SRC_ADDR_HIGH7	0x7F0000 +#define OSD_SRCADD_OFSET_SFT	23 +#define OSD_SRCADD_ADD_SFT	16 +#define OSD_WINADL_MASK		0xFFFF +#define OSD_WINOFST_MASK	0x1000 +#define VPBE_REG_BASE		0x80000000 + +static void _osd_start_layer(struct osd_state *sd, enum osd_layer layer, +			     unsigned long fb_base_phys, +			     unsigned long cbcr_ofst) +{ + +	if (sd->vpbe_type == VPBE_VERSION_1) { +		switch (layer) { +		case WIN_OSD0: +			osd_write(sd, fb_base_phys & ~0x1F, OSD_OSDWIN0ADR); +			break; +		case WIN_VID0: +			osd_write(sd, fb_base_phys & ~0x1F, OSD_VIDWIN0ADR); +			break; +		case WIN_OSD1: +			osd_write(sd, fb_base_phys & ~0x1F, OSD_OSDWIN1ADR); +			break; +		case WIN_VID1: +			osd_write(sd, fb_base_phys & ~0x1F, OSD_VIDWIN1ADR); +			break; +	      } +	} else if (sd->vpbe_type == VPBE_VERSION_3) { +		unsigned long fb_offset_32 = +		    (fb_base_phys - VPBE_REG_BASE) >> 5; + +		switch (layer) { +		case WIN_OSD0: +			osd_modify(sd, OSD_OSDWINADH_O0AH, +				  fb_offset_32 >> (OSD_SRCADD_ADD_SFT - +						   OSD_OSDWINADH_O0AH_SHIFT), +				  OSD_OSDWINADH); +			osd_write(sd, fb_offset_32 & OSD_OSDWIN0ADL_O0AL, +				  OSD_OSDWIN0ADL); +			break; +		case WIN_VID0: +			osd_modify(sd, OSD_VIDWINADH_V0AH, +				  fb_offset_32 >> (OSD_SRCADD_ADD_SFT - +						   OSD_VIDWINADH_V0AH_SHIFT), +				  OSD_VIDWINADH); +			osd_write(sd, fb_offset_32 & OSD_VIDWIN0ADL_V0AL, +				  OSD_VIDWIN0ADL); +			break; +		case WIN_OSD1: +			osd_modify(sd, OSD_OSDWINADH_O1AH, +				  fb_offset_32 >> (OSD_SRCADD_ADD_SFT - +						   OSD_OSDWINADH_O1AH_SHIFT), +				  OSD_OSDWINADH); +			osd_write(sd, fb_offset_32 & OSD_OSDWIN1ADL_O1AL, +				  OSD_OSDWIN1ADL); +			break; +		case WIN_VID1: +			osd_modify(sd, OSD_VIDWINADH_V1AH, +				  fb_offset_32 >> (OSD_SRCADD_ADD_SFT - +						   OSD_VIDWINADH_V1AH_SHIFT), +				  OSD_VIDWINADH); +			osd_write(sd, fb_offset_32 & OSD_VIDWIN1ADL_V1AL, +				  OSD_VIDWIN1ADL); +			break; +		} +	} else if (sd->vpbe_type == VPBE_VERSION_2) { +		struct osd_window_state *win = &sd->win[layer]; +		unsigned long fb_offset_32, cbcr_offset_32; + +		fb_offset_32 = fb_base_phys - VPBE_REG_BASE; +		if (cbcr_ofst) +			cbcr_offset_32 = cbcr_ofst; +		else +			cbcr_offset_32 = win->lconfig.line_length * +					 win->lconfig.ysize; +		cbcr_offset_32 += fb_offset_32; +		fb_offset_32 = fb_offset_32 >> 5; +		cbcr_offset_32 = cbcr_offset_32 >> 5; +		/* +		 * DM365: start address is 27-bit long address b26 - b23 are +		 * in offset register b12 - b9, and * bit 26 has to be '1' +		 */ +		if (win->lconfig.pixfmt == PIXFMT_NV12) { +			switch (layer) { +			case WIN_VID0: +			case WIN_VID1: +				/* Y is in VID0 */ +				osd_modify(sd, OSD_VIDWIN0OFST_V0AH, +					 ((fb_offset_32 & OSD_SRC_ADDR_HIGH4) >> +					 (OSD_SRCADD_OFSET_SFT - +					 OSD_WINOFST_AH_SHIFT)) | +					 OSD_WINOFST_MASK, OSD_VIDWIN0OFST); +				osd_modify(sd, OSD_VIDWINADH_V0AH, +					  (fb_offset_32 & OSD_SRC_ADDR_HIGH7) >> +					  (OSD_SRCADD_ADD_SFT - +					  OSD_VIDWINADH_V0AH_SHIFT), +					   OSD_VIDWINADH); +				osd_write(sd, fb_offset_32 & OSD_WINADL_MASK, +					  OSD_VIDWIN0ADL); +				/* CbCr is in VID1 */ +				osd_modify(sd, OSD_VIDWIN1OFST_V1AH, +					 ((cbcr_offset_32 & +					 OSD_SRC_ADDR_HIGH4) >> +					 (OSD_SRCADD_OFSET_SFT - +					 OSD_WINOFST_AH_SHIFT)) | +					 OSD_WINOFST_MASK, OSD_VIDWIN1OFST); +				osd_modify(sd, OSD_VIDWINADH_V1AH, +					  (cbcr_offset_32 & +					  OSD_SRC_ADDR_HIGH7) >> +					  (OSD_SRCADD_ADD_SFT - +					  OSD_VIDWINADH_V1AH_SHIFT), +					  OSD_VIDWINADH); +				osd_write(sd, cbcr_offset_32 & OSD_WINADL_MASK, +					  OSD_VIDWIN1ADL); +				break; +			default: +				break; +			} +		} + +		switch (layer) { +		case WIN_OSD0: +			osd_modify(sd, OSD_OSDWIN0OFST_O0AH, +				 ((fb_offset_32 & OSD_SRC_ADDR_HIGH4) >> +				 (OSD_SRCADD_OFSET_SFT - +				 OSD_WINOFST_AH_SHIFT)) | OSD_WINOFST_MASK, +				  OSD_OSDWIN0OFST); +			osd_modify(sd, OSD_OSDWINADH_O0AH, +				 (fb_offset_32 & OSD_SRC_ADDR_HIGH7) >> +				 (OSD_SRCADD_ADD_SFT - +				 OSD_OSDWINADH_O0AH_SHIFT), OSD_OSDWINADH); +			osd_write(sd, fb_offset_32 & OSD_WINADL_MASK, +					OSD_OSDWIN0ADL); +			break; +		case WIN_VID0: +			if (win->lconfig.pixfmt != PIXFMT_NV12) { +				osd_modify(sd, OSD_VIDWIN0OFST_V0AH, +					 ((fb_offset_32 & OSD_SRC_ADDR_HIGH4) >> +					 (OSD_SRCADD_OFSET_SFT - +					 OSD_WINOFST_AH_SHIFT)) | +					 OSD_WINOFST_MASK, OSD_VIDWIN0OFST); +				osd_modify(sd, OSD_VIDWINADH_V0AH, +					  (fb_offset_32 & OSD_SRC_ADDR_HIGH7) >> +					  (OSD_SRCADD_ADD_SFT - +					  OSD_VIDWINADH_V0AH_SHIFT), +					  OSD_VIDWINADH); +				osd_write(sd, fb_offset_32 & OSD_WINADL_MASK, +					  OSD_VIDWIN0ADL); +			} +			break; +		case WIN_OSD1: +			osd_modify(sd, OSD_OSDWIN1OFST_O1AH, +				 ((fb_offset_32 & OSD_SRC_ADDR_HIGH4) >> +				 (OSD_SRCADD_OFSET_SFT - +				 OSD_WINOFST_AH_SHIFT)) | OSD_WINOFST_MASK, +				  OSD_OSDWIN1OFST); +			osd_modify(sd, OSD_OSDWINADH_O1AH, +				  (fb_offset_32 & OSD_SRC_ADDR_HIGH7) >> +				  (OSD_SRCADD_ADD_SFT - +				  OSD_OSDWINADH_O1AH_SHIFT), +				  OSD_OSDWINADH); +			osd_write(sd, fb_offset_32 & OSD_WINADL_MASK, +					OSD_OSDWIN1ADL); +			break; +		case WIN_VID1: +			if (win->lconfig.pixfmt != PIXFMT_NV12) { +				osd_modify(sd, OSD_VIDWIN1OFST_V1AH, +					 ((fb_offset_32 & OSD_SRC_ADDR_HIGH4) >> +					 (OSD_SRCADD_OFSET_SFT - +					 OSD_WINOFST_AH_SHIFT)) | +					 OSD_WINOFST_MASK, OSD_VIDWIN1OFST); +				osd_modify(sd, OSD_VIDWINADH_V1AH, +					  (fb_offset_32 & OSD_SRC_ADDR_HIGH7) >> +					  (OSD_SRCADD_ADD_SFT - +					  OSD_VIDWINADH_V1AH_SHIFT), +					  OSD_VIDWINADH); +				osd_write(sd, fb_offset_32 & OSD_WINADL_MASK, +					  OSD_VIDWIN1ADL); +			} +			break; +		} +	} +} + +static void osd_start_layer(struct osd_state *sd, enum osd_layer layer, +			    unsigned long fb_base_phys, +			    unsigned long cbcr_ofst) +{ +	struct osd_state *osd = sd; +	struct osd_window_state *win = &osd->win[layer]; +	struct osd_layer_config *cfg = &win->lconfig; +	unsigned long flags; + +	spin_lock_irqsave(&osd->lock, flags); + +	win->fb_base_phys = fb_base_phys & ~0x1F; +	_osd_start_layer(sd, layer, fb_base_phys, cbcr_ofst); + +	if (layer == WIN_VID0) { +		osd->pingpong = +		    _osd_dm6446_vid0_pingpong(sd, osd->field_inversion, +						       win->fb_base_phys, +						       cfg); +	} + +	spin_unlock_irqrestore(&osd->lock, flags); +} + +static void osd_get_layer_config(struct osd_state *sd, enum osd_layer layer, +				 struct osd_layer_config *lconfig) +{ +	struct osd_state *osd = sd; +	struct osd_window_state *win = &osd->win[layer]; +	unsigned long flags; + +	spin_lock_irqsave(&osd->lock, flags); + +	*lconfig = win->lconfig; + +	spin_unlock_irqrestore(&osd->lock, flags); +} + +/** + * try_layer_config() - Try a specific configuration for the layer + * @sd  - ptr to struct osd_state + * @layer - layer to configure + * @lconfig - layer configuration to try + * + * If the requested lconfig is completely rejected and the value of lconfig on + * exit is the current lconfig, then try_layer_config() returns 1.  Otherwise, + * try_layer_config() returns 0.  A return value of 0 does not necessarily mean + * that the value of lconfig on exit is identical to the value of lconfig on + * entry, but merely that it represents a change from the current lconfig. + */ +static int try_layer_config(struct osd_state *sd, enum osd_layer layer, +			    struct osd_layer_config *lconfig) +{ +	struct osd_state *osd = sd; +	struct osd_window_state *win = &osd->win[layer]; +	int bad_config = 0; + +	/* verify that the pixel format is compatible with the layer */ +	switch (lconfig->pixfmt) { +	case PIXFMT_1BPP: +	case PIXFMT_2BPP: +	case PIXFMT_4BPP: +	case PIXFMT_8BPP: +	case PIXFMT_RGB565: +		if (osd->vpbe_type == VPBE_VERSION_1) +			bad_config = !is_vid_win(layer); +		break; +	case PIXFMT_YCBCRI: +	case PIXFMT_YCRCBI: +		bad_config = !is_vid_win(layer); +		break; +	case PIXFMT_RGB888: +		if (osd->vpbe_type == VPBE_VERSION_1) +			bad_config = !is_vid_win(layer); +		else if ((osd->vpbe_type == VPBE_VERSION_3) || +			 (osd->vpbe_type == VPBE_VERSION_2)) +			bad_config = !is_osd_win(layer); +		break; +	case PIXFMT_NV12: +		if (osd->vpbe_type != VPBE_VERSION_2) +			bad_config = 1; +		else +			bad_config = is_osd_win(layer); +		break; +	case PIXFMT_OSD_ATTR: +		bad_config = (layer != WIN_OSD1); +		break; +	default: +		bad_config = 1; +		break; +	} +	if (bad_config) { +		/* +		 * The requested pixel format is incompatible with the layer, +		 * so keep the current layer configuration. +		 */ +		*lconfig = win->lconfig; +		return bad_config; +	} + +	/* DM6446: */ +	/* only one OSD window at a time can use RGB pixel formats */ +	  if ((osd->vpbe_type == VPBE_VERSION_1) && +		  is_osd_win(layer) && is_rgb_pixfmt(lconfig->pixfmt)) { +		enum osd_pix_format pixfmt; +		if (layer == WIN_OSD0) +			pixfmt = osd->win[WIN_OSD1].lconfig.pixfmt; +		else +			pixfmt = osd->win[WIN_OSD0].lconfig.pixfmt; + +		if (is_rgb_pixfmt(pixfmt)) { +			/* +			 * The other OSD window is already configured for an +			 * RGB, so keep the current layer configuration. +			 */ +			*lconfig = win->lconfig; +			return 1; +		} +	} + +	/* DM6446: only one video window at a time can use RGB888 */ +	if ((osd->vpbe_type == VPBE_VERSION_1) && is_vid_win(layer) && +		lconfig->pixfmt == PIXFMT_RGB888) { +		enum osd_pix_format pixfmt; + +		if (layer == WIN_VID0) +			pixfmt = osd->win[WIN_VID1].lconfig.pixfmt; +		else +			pixfmt = osd->win[WIN_VID0].lconfig.pixfmt; + +		if (pixfmt == PIXFMT_RGB888) { +			/* +			 * The other video window is already configured for +			 * RGB888, so keep the current layer configuration. +			 */ +			*lconfig = win->lconfig; +			return 1; +		} +	} + +	/* window dimensions must be non-zero */ +	if (!lconfig->line_length || !lconfig->xsize || !lconfig->ysize) { +		*lconfig = win->lconfig; +		return 1; +	} + +	/* round line_length up to a multiple of 32 */ +	lconfig->line_length = ((lconfig->line_length + 31) / 32) * 32; +	lconfig->line_length = +	    min(lconfig->line_length, (unsigned)MAX_LINE_LENGTH); +	lconfig->xsize = min(lconfig->xsize, (unsigned)MAX_WIN_SIZE); +	lconfig->ysize = min(lconfig->ysize, (unsigned)MAX_WIN_SIZE); +	lconfig->xpos = min(lconfig->xpos, (unsigned)MAX_WIN_SIZE); +	lconfig->ypos = min(lconfig->ypos, (unsigned)MAX_WIN_SIZE); +	lconfig->interlaced = (lconfig->interlaced != 0); +	if (lconfig->interlaced) { +		/* ysize and ypos must be even for interlaced displays */ +		lconfig->ysize &= ~1; +		lconfig->ypos &= ~1; +	} + +	return 0; +} + +static void _osd_disable_vid_rgb888(struct osd_state *sd) +{ +	/* +	 * The DM6446 supports RGB888 pixel format in a single video window. +	 * This routine disables RGB888 pixel format for both video windows. +	 * The caller must ensure that neither video window is currently +	 * configured for RGB888 pixel format. +	 */ +	if (sd->vpbe_type == VPBE_VERSION_1) +		osd_clear(sd, OSD_MISCCTL_RGBEN, OSD_MISCCTL); +} + +static void _osd_enable_vid_rgb888(struct osd_state *sd, +				   enum osd_layer layer) +{ +	/* +	 * The DM6446 supports RGB888 pixel format in a single video window. +	 * This routine enables RGB888 pixel format for the specified video +	 * window.  The caller must ensure that the other video window is not +	 * currently configured for RGB888 pixel format, as this routine will +	 * disable RGB888 pixel format for the other window. +	 */ +	if (sd->vpbe_type == VPBE_VERSION_1) { +		if (layer == WIN_VID0) +			osd_modify(sd, OSD_MISCCTL_RGBEN | OSD_MISCCTL_RGBWIN, +				  OSD_MISCCTL_RGBEN, OSD_MISCCTL); +		else if (layer == WIN_VID1) +			osd_modify(sd, OSD_MISCCTL_RGBEN | OSD_MISCCTL_RGBWIN, +				  OSD_MISCCTL_RGBEN | OSD_MISCCTL_RGBWIN, +				  OSD_MISCCTL); +	} +} + +static void _osd_set_cbcr_order(struct osd_state *sd, +				enum osd_pix_format pixfmt) +{ +	/* +	 * The caller must ensure that all windows using YC pixfmt use the same +	 * Cb/Cr order. +	 */ +	if (pixfmt == PIXFMT_YCBCRI) +		osd_clear(sd, OSD_MODE_CS, OSD_MODE); +	else if (pixfmt == PIXFMT_YCRCBI) +		osd_set(sd, OSD_MODE_CS, OSD_MODE); +} + +static void _osd_set_layer_config(struct osd_state *sd, enum osd_layer layer, +				  const struct osd_layer_config *lconfig) +{ +	u32 winmd = 0, winmd_mask = 0, bmw = 0; + +	_osd_set_cbcr_order(sd, lconfig->pixfmt); + +	switch (layer) { +	case WIN_OSD0: +		if (sd->vpbe_type == VPBE_VERSION_1) { +			winmd_mask |= OSD_OSDWIN0MD_RGB0E; +			if (lconfig->pixfmt == PIXFMT_RGB565) +				winmd |= OSD_OSDWIN0MD_RGB0E; +		} else if ((sd->vpbe_type == VPBE_VERSION_3) || +		  (sd->vpbe_type == VPBE_VERSION_2)) { +			winmd_mask |= OSD_OSDWIN0MD_BMP0MD; +			switch (lconfig->pixfmt) { +			case PIXFMT_RGB565: +					winmd |= (1 << +					OSD_OSDWIN0MD_BMP0MD_SHIFT); +					break; +			case PIXFMT_RGB888: +				winmd |= (2 << OSD_OSDWIN0MD_BMP0MD_SHIFT); +				_osd_enable_rgb888_pixblend(sd, OSDWIN_OSD0); +				break; +			case PIXFMT_YCBCRI: +			case PIXFMT_YCRCBI: +				winmd |= (3 << OSD_OSDWIN0MD_BMP0MD_SHIFT); +				break; +			default: +				break; +			} +		} + +		winmd_mask |= OSD_OSDWIN0MD_BMW0 | OSD_OSDWIN0MD_OFF0; + +		switch (lconfig->pixfmt) { +		case PIXFMT_1BPP: +			bmw = 0; +			break; +		case PIXFMT_2BPP: +			bmw = 1; +			break; +		case PIXFMT_4BPP: +			bmw = 2; +			break; +		case PIXFMT_8BPP: +			bmw = 3; +			break; +		default: +			break; +		} +		winmd |= (bmw << OSD_OSDWIN0MD_BMW0_SHIFT); + +		if (lconfig->interlaced) +			winmd |= OSD_OSDWIN0MD_OFF0; + +		osd_modify(sd, winmd_mask, winmd, OSD_OSDWIN0MD); +		osd_write(sd, lconfig->line_length >> 5, OSD_OSDWIN0OFST); +		osd_write(sd, lconfig->xpos, OSD_OSDWIN0XP); +		osd_write(sd, lconfig->xsize, OSD_OSDWIN0XL); +		if (lconfig->interlaced) { +			osd_write(sd, lconfig->ypos >> 1, OSD_OSDWIN0YP); +			osd_write(sd, lconfig->ysize >> 1, OSD_OSDWIN0YL); +		} else { +			osd_write(sd, lconfig->ypos, OSD_OSDWIN0YP); +			osd_write(sd, lconfig->ysize, OSD_OSDWIN0YL); +		} +		break; +	case WIN_VID0: +		winmd_mask |= OSD_VIDWINMD_VFF0; +		if (lconfig->interlaced) +			winmd |= OSD_VIDWINMD_VFF0; + +		osd_modify(sd, winmd_mask, winmd, OSD_VIDWINMD); +		osd_write(sd, lconfig->line_length >> 5, OSD_VIDWIN0OFST); +		osd_write(sd, lconfig->xpos, OSD_VIDWIN0XP); +		osd_write(sd, lconfig->xsize, OSD_VIDWIN0XL); +		/* +		 * For YUV420P format the register contents are +		 * duplicated in both VID registers +		 */ +		if ((sd->vpbe_type == VPBE_VERSION_2) && +				(lconfig->pixfmt == PIXFMT_NV12)) { +			/* other window also */ +			if (lconfig->interlaced) { +				winmd_mask |= OSD_VIDWINMD_VFF1; +				winmd |= OSD_VIDWINMD_VFF1; +				osd_modify(sd, winmd_mask, winmd, +					  OSD_VIDWINMD); +			} + +			osd_modify(sd, OSD_MISCCTL_S420D, +				    OSD_MISCCTL_S420D, OSD_MISCCTL); +			osd_write(sd, lconfig->line_length >> 5, +				  OSD_VIDWIN1OFST); +			osd_write(sd, lconfig->xpos, OSD_VIDWIN1XP); +			osd_write(sd, lconfig->xsize, OSD_VIDWIN1XL); +			/* +			  * if NV21 pixfmt and line length not 32B +			  * aligned (e.g. NTSC), Need to set window +			  * X pixel size to be 32B aligned as well +			  */ +			if (lconfig->xsize % 32) { +				osd_write(sd, +					  ((lconfig->xsize + 31) & ~31), +					  OSD_VIDWIN1XL); +				osd_write(sd, +					  ((lconfig->xsize + 31) & ~31), +					  OSD_VIDWIN0XL); +			} +		} else if ((sd->vpbe_type == VPBE_VERSION_2) && +				(lconfig->pixfmt != PIXFMT_NV12)) { +			osd_modify(sd, OSD_MISCCTL_S420D, ~OSD_MISCCTL_S420D, +						OSD_MISCCTL); +		} + +		if (lconfig->interlaced) { +			osd_write(sd, lconfig->ypos >> 1, OSD_VIDWIN0YP); +			osd_write(sd, lconfig->ysize >> 1, OSD_VIDWIN0YL); +			if ((sd->vpbe_type == VPBE_VERSION_2) && +				lconfig->pixfmt == PIXFMT_NV12) { +				osd_write(sd, lconfig->ypos >> 1, +					  OSD_VIDWIN1YP); +				osd_write(sd, lconfig->ysize >> 1, +					  OSD_VIDWIN1YL); +			} +		} else { +			osd_write(sd, lconfig->ypos, OSD_VIDWIN0YP); +			osd_write(sd, lconfig->ysize, OSD_VIDWIN0YL); +			if ((sd->vpbe_type == VPBE_VERSION_2) && +				lconfig->pixfmt == PIXFMT_NV12) { +				osd_write(sd, lconfig->ypos, OSD_VIDWIN1YP); +				osd_write(sd, lconfig->ysize, OSD_VIDWIN1YL); +			} +		} +		break; +	case WIN_OSD1: +		/* +		 * The caller must ensure that OSD1 is disabled prior to +		 * switching from a normal mode to attribute mode or from +		 * attribute mode to a normal mode. +		 */ +		if (lconfig->pixfmt == PIXFMT_OSD_ATTR) { +			if (sd->vpbe_type == VPBE_VERSION_1) { +				winmd_mask |= OSD_OSDWIN1MD_ATN1E | +				OSD_OSDWIN1MD_RGB1E | OSD_OSDWIN1MD_CLUTS1 | +				OSD_OSDWIN1MD_BLND1 | OSD_OSDWIN1MD_TE1; +			} else { +				winmd_mask |= OSD_OSDWIN1MD_BMP1MD | +				OSD_OSDWIN1MD_CLUTS1 | OSD_OSDWIN1MD_BLND1 | +				OSD_OSDWIN1MD_TE1; +			} +		} else { +			if (sd->vpbe_type == VPBE_VERSION_1) { +				winmd_mask |= OSD_OSDWIN1MD_RGB1E; +				if (lconfig->pixfmt == PIXFMT_RGB565) +					winmd |= OSD_OSDWIN1MD_RGB1E; +			} else if ((sd->vpbe_type == VPBE_VERSION_3) +				   || (sd->vpbe_type == VPBE_VERSION_2)) { +				winmd_mask |= OSD_OSDWIN1MD_BMP1MD; +				switch (lconfig->pixfmt) { +				case PIXFMT_RGB565: +					winmd |= +					    (1 << OSD_OSDWIN1MD_BMP1MD_SHIFT); +					break; +				case PIXFMT_RGB888: +					winmd |= +					    (2 << OSD_OSDWIN1MD_BMP1MD_SHIFT); +					_osd_enable_rgb888_pixblend(sd, +							OSDWIN_OSD1); +					break; +				case PIXFMT_YCBCRI: +				case PIXFMT_YCRCBI: +					winmd |= +					    (3 << OSD_OSDWIN1MD_BMP1MD_SHIFT); +					break; +				default: +					break; +				} +			} + +			winmd_mask |= OSD_OSDWIN1MD_BMW1; +			switch (lconfig->pixfmt) { +			case PIXFMT_1BPP: +				bmw = 0; +				break; +			case PIXFMT_2BPP: +				bmw = 1; +				break; +			case PIXFMT_4BPP: +				bmw = 2; +				break; +			case PIXFMT_8BPP: +				bmw = 3; +				break; +			default: +				break; +			} +			winmd |= (bmw << OSD_OSDWIN1MD_BMW1_SHIFT); +		} + +		winmd_mask |= OSD_OSDWIN1MD_OFF1; +		if (lconfig->interlaced) +			winmd |= OSD_OSDWIN1MD_OFF1; + +		osd_modify(sd, winmd_mask, winmd, OSD_OSDWIN1MD); +		osd_write(sd, lconfig->line_length >> 5, OSD_OSDWIN1OFST); +		osd_write(sd, lconfig->xpos, OSD_OSDWIN1XP); +		osd_write(sd, lconfig->xsize, OSD_OSDWIN1XL); +		if (lconfig->interlaced) { +			osd_write(sd, lconfig->ypos >> 1, OSD_OSDWIN1YP); +			osd_write(sd, lconfig->ysize >> 1, OSD_OSDWIN1YL); +		} else { +			osd_write(sd, lconfig->ypos, OSD_OSDWIN1YP); +			osd_write(sd, lconfig->ysize, OSD_OSDWIN1YL); +		} +		break; +	case WIN_VID1: +		winmd_mask |= OSD_VIDWINMD_VFF1; +		if (lconfig->interlaced) +			winmd |= OSD_VIDWINMD_VFF1; + +		osd_modify(sd, winmd_mask, winmd, OSD_VIDWINMD); +		osd_write(sd, lconfig->line_length >> 5, OSD_VIDWIN1OFST); +		osd_write(sd, lconfig->xpos, OSD_VIDWIN1XP); +		osd_write(sd, lconfig->xsize, OSD_VIDWIN1XL); +		/* +		 * For YUV420P format the register contents are +		 * duplicated in both VID registers +		 */ +		if (sd->vpbe_type == VPBE_VERSION_2) { +			if (lconfig->pixfmt == PIXFMT_NV12) { +				/* other window also */ +				if (lconfig->interlaced) { +					winmd_mask |= OSD_VIDWINMD_VFF0; +					winmd |= OSD_VIDWINMD_VFF0; +					osd_modify(sd, winmd_mask, winmd, +						  OSD_VIDWINMD); +				} +				osd_modify(sd, OSD_MISCCTL_S420D, +					   OSD_MISCCTL_S420D, OSD_MISCCTL); +				osd_write(sd, lconfig->line_length >> 5, +					  OSD_VIDWIN0OFST); +				osd_write(sd, lconfig->xpos, OSD_VIDWIN0XP); +				osd_write(sd, lconfig->xsize, OSD_VIDWIN0XL); +			} else { +				osd_modify(sd, OSD_MISCCTL_S420D, +					   ~OSD_MISCCTL_S420D, OSD_MISCCTL); +			} +		} + +		if (lconfig->interlaced) { +			osd_write(sd, lconfig->ypos >> 1, OSD_VIDWIN1YP); +			osd_write(sd, lconfig->ysize >> 1, OSD_VIDWIN1YL); +			if ((sd->vpbe_type == VPBE_VERSION_2) && +				lconfig->pixfmt == PIXFMT_NV12) { +				osd_write(sd, lconfig->ypos >> 1, +					  OSD_VIDWIN0YP); +				osd_write(sd, lconfig->ysize >> 1, +					  OSD_VIDWIN0YL); +			} +		} else { +			osd_write(sd, lconfig->ypos, OSD_VIDWIN1YP); +			osd_write(sd, lconfig->ysize, OSD_VIDWIN1YL); +			if ((sd->vpbe_type == VPBE_VERSION_2) && +				lconfig->pixfmt == PIXFMT_NV12) { +				osd_write(sd, lconfig->ypos, OSD_VIDWIN0YP); +				osd_write(sd, lconfig->ysize, OSD_VIDWIN0YL); +			} +		} +		break; +	} +} + +static int osd_set_layer_config(struct osd_state *sd, enum osd_layer layer, +				struct osd_layer_config *lconfig) +{ +	struct osd_state *osd = sd; +	struct osd_window_state *win = &osd->win[layer]; +	struct osd_layer_config *cfg = &win->lconfig; +	unsigned long flags; +	int reject_config; + +	spin_lock_irqsave(&osd->lock, flags); + +	reject_config = try_layer_config(sd, layer, lconfig); +	if (reject_config) { +		spin_unlock_irqrestore(&osd->lock, flags); +		return reject_config; +	} + +	/* update the current Cb/Cr order */ +	if (is_yc_pixfmt(lconfig->pixfmt)) +		osd->yc_pixfmt = lconfig->pixfmt; + +	/* +	 * If we are switching OSD1 from normal mode to attribute mode or from +	 * attribute mode to normal mode, then we must disable the window. +	 */ +	if (layer == WIN_OSD1) { +		if (((lconfig->pixfmt == PIXFMT_OSD_ATTR) && +		  (cfg->pixfmt != PIXFMT_OSD_ATTR)) || +		  ((lconfig->pixfmt != PIXFMT_OSD_ATTR) && +		  (cfg->pixfmt == PIXFMT_OSD_ATTR))) { +			win->is_enabled = 0; +			_osd_disable_layer(sd, layer); +		} +	} + +	_osd_set_layer_config(sd, layer, lconfig); + +	if (layer == WIN_OSD1) { +		struct osd_osdwin_state *osdwin_state = +		    &osd->osdwin[OSDWIN_OSD1]; + +		if ((lconfig->pixfmt != PIXFMT_OSD_ATTR) && +		  (cfg->pixfmt == PIXFMT_OSD_ATTR)) { +			/* +			 * We just switched OSD1 from attribute mode to normal +			 * mode, so we must initialize the CLUT select, the +			 * blend factor, transparency colorkey enable, and +			 * attenuation enable (DM6446 only) bits in the +			 * OSDWIN1MD register. +			 */ +			_osd_set_osd_clut(sd, OSDWIN_OSD1, +						   osdwin_state->clut); +			_osd_set_blending_factor(sd, OSDWIN_OSD1, +							  osdwin_state->blend); +			if (osdwin_state->colorkey_blending) { +				_osd_enable_color_key(sd, OSDWIN_OSD1, +							       osdwin_state-> +							       colorkey, +							       lconfig->pixfmt); +			} else +				_osd_disable_color_key(sd, OSDWIN_OSD1); +			_osd_set_rec601_attenuation(sd, OSDWIN_OSD1, +						    osdwin_state-> +						    rec601_attenuation); +		} else if ((lconfig->pixfmt == PIXFMT_OSD_ATTR) && +		  (cfg->pixfmt != PIXFMT_OSD_ATTR)) { +			/* +			 * We just switched OSD1 from normal mode to attribute +			 * mode, so we must initialize the blink enable and +			 * blink interval bits in the OSDATRMD register. +			 */ +			_osd_set_blink_attribute(sd, osd->is_blinking, +							  osd->blink); +		} +	} + +	/* +	 * If we just switched to a 1-, 2-, or 4-bits-per-pixel bitmap format +	 * then configure a default palette map. +	 */ +	if ((lconfig->pixfmt != cfg->pixfmt) && +	  ((lconfig->pixfmt == PIXFMT_1BPP) || +	  (lconfig->pixfmt == PIXFMT_2BPP) || +	  (lconfig->pixfmt == PIXFMT_4BPP))) { +		enum osd_win_layer osdwin = +		    ((layer == WIN_OSD0) ? OSDWIN_OSD0 : OSDWIN_OSD1); +		struct osd_osdwin_state *osdwin_state = +		    &osd->osdwin[osdwin]; +		unsigned char clut_index; +		unsigned char clut_entries = 0; + +		switch (lconfig->pixfmt) { +		case PIXFMT_1BPP: +			clut_entries = 2; +			break; +		case PIXFMT_2BPP: +			clut_entries = 4; +			break; +		case PIXFMT_4BPP: +			clut_entries = 16; +			break; +		default: +			break; +		} +		/* +		 * The default palette map maps the pixel value to the clut +		 * index, i.e. pixel value 0 maps to clut entry 0, pixel value +		 * 1 maps to clut entry 1, etc. +		 */ +		for (clut_index = 0; clut_index < 16; clut_index++) { +			osdwin_state->palette_map[clut_index] = clut_index; +			if (clut_index < clut_entries) { +				_osd_set_palette_map(sd, osdwin, clut_index, +						     clut_index, +						     lconfig->pixfmt); +			} +		} +	} + +	*cfg = *lconfig; +	/* DM6446: configure the RGB888 enable and window selection */ +	if (osd->win[WIN_VID0].lconfig.pixfmt == PIXFMT_RGB888) +		_osd_enable_vid_rgb888(sd, WIN_VID0); +	else if (osd->win[WIN_VID1].lconfig.pixfmt == PIXFMT_RGB888) +		_osd_enable_vid_rgb888(sd, WIN_VID1); +	else +		_osd_disable_vid_rgb888(sd); + +	if (layer == WIN_VID0) { +		osd->pingpong = +		    _osd_dm6446_vid0_pingpong(sd, osd->field_inversion, +						       win->fb_base_phys, +						       cfg); +	} + +	spin_unlock_irqrestore(&osd->lock, flags); + +	return 0; +} + +static void osd_init_layer(struct osd_state *sd, enum osd_layer layer) +{ +	struct osd_state *osd = sd; +	struct osd_window_state *win = &osd->win[layer]; +	enum osd_win_layer osdwin; +	struct osd_osdwin_state *osdwin_state; +	struct osd_layer_config *cfg = &win->lconfig; +	unsigned long flags; + +	spin_lock_irqsave(&osd->lock, flags); + +	win->is_enabled = 0; +	_osd_disable_layer(sd, layer); + +	win->h_zoom = ZOOM_X1; +	win->v_zoom = ZOOM_X1; +	_osd_set_zoom(sd, layer, win->h_zoom, win->v_zoom); + +	win->fb_base_phys = 0; +	_osd_start_layer(sd, layer, win->fb_base_phys, 0); + +	cfg->line_length = 0; +	cfg->xsize = 0; +	cfg->ysize = 0; +	cfg->xpos = 0; +	cfg->ypos = 0; +	cfg->interlaced = 0; +	switch (layer) { +	case WIN_OSD0: +	case WIN_OSD1: +		osdwin = (layer == WIN_OSD0) ? OSDWIN_OSD0 : OSDWIN_OSD1; +		osdwin_state = &osd->osdwin[osdwin]; +		/* +		 * Other code relies on the fact that OSD windows default to a +		 * bitmap pixel format when they are deallocated, so don't +		 * change this default pixel format. +		 */ +		cfg->pixfmt = PIXFMT_8BPP; +		_osd_set_layer_config(sd, layer, cfg); +		osdwin_state->clut = RAM_CLUT; +		_osd_set_osd_clut(sd, osdwin, osdwin_state->clut); +		osdwin_state->colorkey_blending = 0; +		_osd_disable_color_key(sd, osdwin); +		osdwin_state->blend = OSD_8_VID_0; +		_osd_set_blending_factor(sd, osdwin, osdwin_state->blend); +		osdwin_state->rec601_attenuation = 0; +		_osd_set_rec601_attenuation(sd, osdwin, +						     osdwin_state-> +						     rec601_attenuation); +		if (osdwin == OSDWIN_OSD1) { +			osd->is_blinking = 0; +			osd->blink = BLINK_X1; +		} +		break; +	case WIN_VID0: +	case WIN_VID1: +		cfg->pixfmt = osd->yc_pixfmt; +		_osd_set_layer_config(sd, layer, cfg); +		break; +	} + +	spin_unlock_irqrestore(&osd->lock, flags); +} + +static void osd_release_layer(struct osd_state *sd, enum osd_layer layer) +{ +	struct osd_state *osd = sd; +	struct osd_window_state *win = &osd->win[layer]; +	unsigned long flags; + +	spin_lock_irqsave(&osd->lock, flags); + +	if (!win->is_allocated) { +		spin_unlock_irqrestore(&osd->lock, flags); +		return; +	} + +	spin_unlock_irqrestore(&osd->lock, flags); +	osd_init_layer(sd, layer); +	spin_lock_irqsave(&osd->lock, flags); + +	win->is_allocated = 0; + +	spin_unlock_irqrestore(&osd->lock, flags); +} + +static int osd_request_layer(struct osd_state *sd, enum osd_layer layer) +{ +	struct osd_state *osd = sd; +	struct osd_window_state *win = &osd->win[layer]; +	unsigned long flags; + +	spin_lock_irqsave(&osd->lock, flags); + +	if (win->is_allocated) { +		spin_unlock_irqrestore(&osd->lock, flags); +		return -1; +	} +	win->is_allocated = 1; + +	spin_unlock_irqrestore(&osd->lock, flags); + +	return 0; +} + +static void _osd_init(struct osd_state *sd) +{ +	osd_write(sd, 0, OSD_MODE); +	osd_write(sd, 0, OSD_VIDWINMD); +	osd_write(sd, 0, OSD_OSDWIN0MD); +	osd_write(sd, 0, OSD_OSDWIN1MD); +	osd_write(sd, 0, OSD_RECTCUR); +	osd_write(sd, 0, OSD_MISCCTL); +	if (sd->vpbe_type == VPBE_VERSION_3) { +		osd_write(sd, 0, OSD_VBNDRY); +		osd_write(sd, 0, OSD_EXTMODE); +		osd_write(sd, OSD_MISCCTL_DMANG, OSD_MISCCTL); +	} +} + +static void osd_set_left_margin(struct osd_state *sd, u32 val) +{ +	osd_write(sd, val, OSD_BASEPX); +} + +static void osd_set_top_margin(struct osd_state *sd, u32 val) +{ +	osd_write(sd, val, OSD_BASEPY); +} + +static int osd_initialize(struct osd_state *osd) +{ +	if (osd == NULL) +		return -ENODEV; +	_osd_init(osd); + +	/* set default Cb/Cr order */ +	osd->yc_pixfmt = PIXFMT_YCBCRI; + +	if (osd->vpbe_type == VPBE_VERSION_3) { +		/* +		 * ROM CLUT1 on the DM355 is similar (identical?) to ROM CLUT0 +		 * on the DM6446, so make ROM_CLUT1 the default on the DM355. +		 */ +		osd->rom_clut = ROM_CLUT1; +	} + +	_osd_set_field_inversion(osd, osd->field_inversion); +	_osd_set_rom_clut(osd, osd->rom_clut); + +	osd_init_layer(osd, WIN_OSD0); +	osd_init_layer(osd, WIN_VID0); +	osd_init_layer(osd, WIN_OSD1); +	osd_init_layer(osd, WIN_VID1); + +	return 0; +} + +static const struct vpbe_osd_ops osd_ops = { +	.initialize = osd_initialize, +	.request_layer = osd_request_layer, +	.release_layer = osd_release_layer, +	.enable_layer = osd_enable_layer, +	.disable_layer = osd_disable_layer, +	.set_layer_config = osd_set_layer_config, +	.get_layer_config = osd_get_layer_config, +	.start_layer = osd_start_layer, +	.set_left_margin = osd_set_left_margin, +	.set_top_margin = osd_set_top_margin, +}; + +static int osd_probe(struct platform_device *pdev) +{ +	const struct platform_device_id *pdev_id; +	struct osd_state *osd; +	struct resource *res; + +	pdev_id = platform_get_device_id(pdev); +	if (!pdev_id) +		return -EINVAL; + +	osd = devm_kzalloc(&pdev->dev, sizeof(struct osd_state), GFP_KERNEL); +	if (osd == NULL) +		return -ENOMEM; + + +	osd->dev = &pdev->dev; +	osd->vpbe_type = pdev_id->driver_data; + +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +	osd->osd_base = devm_ioremap_resource(&pdev->dev, res); +	if (IS_ERR(osd->osd_base)) +		return PTR_ERR(osd->osd_base); + +	osd->osd_base_phys = res->start; +	osd->osd_size = resource_size(res); +	spin_lock_init(&osd->lock); +	osd->ops = osd_ops; +	platform_set_drvdata(pdev, osd); +	dev_notice(osd->dev, "OSD sub device probe success\n"); + +	return 0; +} + +static int osd_remove(struct platform_device *pdev) +{ +	return 0; +} + +static struct platform_driver osd_driver = { +	.probe		= osd_probe, +	.remove		= osd_remove, +	.driver		= { +		.name	= MODULE_NAME, +		.owner	= THIS_MODULE, +	}, +	.id_table	= vpbe_osd_devtype +}; + +module_platform_driver(osd_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("DaVinci OSD Manager Driver"); +MODULE_AUTHOR("Texas Instruments"); diff --git a/drivers/media/platform/davinci/vpbe_osd_regs.h b/drivers/media/platform/davinci/vpbe_osd_regs.h new file mode 100644 index 00000000000..584520f3af6 --- /dev/null +++ b/drivers/media/platform/davinci/vpbe_osd_regs.h @@ -0,0 +1,364 @@ +/* + * Copyright (C) 2006-2010 Texas Instruments Inc + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation version 2. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef _VPBE_OSD_REGS_H +#define _VPBE_OSD_REGS_H + +/* VPBE Global Registers */ +#define VPBE_PID				0x0 +#define VPBE_PCR				0x4 + +/* VPSS CLock Registers */ +#define VPSSCLK_PID				0x00 +#define VPSSCLK_CLKCTRL				0x04 + +/* VPSS Buffer Logic Registers */ +#define VPSSBL_PID				0x00 +#define VPSSBL_PCR				0x04 +#define VPSSBL_BCR				0x08 +#define VPSSBL_INTSTAT				0x0C +#define VPSSBL_INTSEL				0x10 +#define VPSSBL_EVTSEL				0x14 +#define VPSSBL_MEMCTRL				0x18 +#define VPSSBL_CCDCMUX				0x1C + +/* DM365 ISP5 system configuration */ +#define ISP5_PID				0x0 +#define ISP5_PCCR				0x4 +#define ISP5_BCR				0x8 +#define ISP5_INTSTAT				0xC +#define ISP5_INTSEL1				0x10 +#define ISP5_INTSEL2				0x14 +#define ISP5_INTSEL3				0x18 +#define ISP5_EVTSEL				0x1c +#define ISP5_CCDCMUX				0x20 + +/* VPBE On-Screen Display Subsystem Registers (OSD) */ +#define OSD_MODE				0x00 +#define OSD_VIDWINMD				0x04 +#define OSD_OSDWIN0MD				0x08 +#define OSD_OSDWIN1MD				0x0C +#define OSD_OSDATRMD				0x0C +#define OSD_RECTCUR				0x10 +#define OSD_VIDWIN0OFST				0x18 +#define OSD_VIDWIN1OFST				0x1C +#define OSD_OSDWIN0OFST				0x20 +#define OSD_OSDWIN1OFST				0x24 +#define OSD_VIDWINADH				0x28 +#define OSD_VIDWIN0ADL				0x2C +#define OSD_VIDWIN0ADR				0x2C +#define OSD_VIDWIN1ADL				0x30 +#define OSD_VIDWIN1ADR				0x30 +#define OSD_OSDWINADH				0x34 +#define OSD_OSDWIN0ADL				0x38 +#define OSD_OSDWIN0ADR				0x38 +#define OSD_OSDWIN1ADL				0x3C +#define OSD_OSDWIN1ADR				0x3C +#define OSD_BASEPX				0x40 +#define OSD_BASEPY				0x44 +#define OSD_VIDWIN0XP				0x48 +#define OSD_VIDWIN0YP				0x4C +#define OSD_VIDWIN0XL				0x50 +#define OSD_VIDWIN0YL				0x54 +#define OSD_VIDWIN1XP				0x58 +#define OSD_VIDWIN1YP				0x5C +#define OSD_VIDWIN1XL				0x60 +#define OSD_VIDWIN1YL				0x64 +#define OSD_OSDWIN0XP				0x68 +#define OSD_OSDWIN0YP				0x6C +#define OSD_OSDWIN0XL				0x70 +#define OSD_OSDWIN0YL				0x74 +#define OSD_OSDWIN1XP				0x78 +#define OSD_OSDWIN1YP				0x7C +#define OSD_OSDWIN1XL				0x80 +#define OSD_OSDWIN1YL				0x84 +#define OSD_CURXP				0x88 +#define OSD_CURYP				0x8C +#define OSD_CURXL				0x90 +#define OSD_CURYL				0x94 +#define OSD_W0BMP01				0xA0 +#define OSD_W0BMP23				0xA4 +#define OSD_W0BMP45				0xA8 +#define OSD_W0BMP67				0xAC +#define OSD_W0BMP89				0xB0 +#define OSD_W0BMPAB				0xB4 +#define OSD_W0BMPCD				0xB8 +#define OSD_W0BMPEF				0xBC +#define OSD_W1BMP01				0xC0 +#define OSD_W1BMP23				0xC4 +#define OSD_W1BMP45				0xC8 +#define OSD_W1BMP67				0xCC +#define OSD_W1BMP89				0xD0 +#define OSD_W1BMPAB				0xD4 +#define OSD_W1BMPCD				0xD8 +#define OSD_W1BMPEF				0xDC +#define OSD_VBNDRY				0xE0 +#define OSD_EXTMODE				0xE4 +#define OSD_MISCCTL				0xE8 +#define OSD_CLUTRAMYCB				0xEC +#define OSD_CLUTRAMCR				0xF0 +#define OSD_TRANSPVAL				0xF4 +#define OSD_TRANSPVALL				0xF4 +#define OSD_TRANSPVALU				0xF8 +#define OSD_TRANSPBMPIDX			0xFC +#define OSD_PPVWIN0ADR				0xFC + +/* bit definitions */ +#define VPBE_PCR_VENC_DIV			(1 << 1) +#define VPBE_PCR_CLK_OFF			(1 << 0) + +#define VPSSBL_INTSTAT_HSSIINT			(1 << 14) +#define VPSSBL_INTSTAT_CFALDINT			(1 << 13) +#define VPSSBL_INTSTAT_IPIPE_INT5		(1 << 12) +#define VPSSBL_INTSTAT_IPIPE_INT4		(1 << 11) +#define VPSSBL_INTSTAT_IPIPE_INT3		(1 << 10) +#define VPSSBL_INTSTAT_IPIPE_INT2		(1 << 9) +#define VPSSBL_INTSTAT_IPIPE_INT1		(1 << 8) +#define VPSSBL_INTSTAT_IPIPE_INT0		(1 << 7) +#define VPSSBL_INTSTAT_IPIPEIFINT		(1 << 6) +#define VPSSBL_INTSTAT_OSDINT			(1 << 5) +#define VPSSBL_INTSTAT_VENCINT			(1 << 4) +#define VPSSBL_INTSTAT_H3AINT			(1 << 3) +#define VPSSBL_INTSTAT_CCDC_VDINT2		(1 << 2) +#define VPSSBL_INTSTAT_CCDC_VDINT1		(1 << 1) +#define VPSSBL_INTSTAT_CCDC_VDINT0		(1 << 0) + +/* DM365 ISP5 bit definitions */ +#define ISP5_INTSTAT_VENCINT			(1 << 21) +#define ISP5_INTSTAT_OSDINT			(1 << 20) + +/* VMOD TVTYP options for HDMD=0 */ +#define SDTV_NTSC				0 +#define SDTV_PAL				1 +/* VMOD TVTYP options for HDMD=1 */ +#define HDTV_525P				0 +#define HDTV_625P				1 +#define HDTV_1080I				2 +#define HDTV_720P				3 + +#define OSD_MODE_CS				(1 << 15) +#define OSD_MODE_OVRSZ				(1 << 14) +#define OSD_MODE_OHRSZ				(1 << 13) +#define OSD_MODE_EF				(1 << 12) +#define OSD_MODE_VVRSZ				(1 << 11) +#define OSD_MODE_VHRSZ				(1 << 10) +#define OSD_MODE_FSINV				(1 << 9) +#define OSD_MODE_BCLUT				(1 << 8) +#define OSD_MODE_CABG_SHIFT			0 +#define OSD_MODE_CABG				(0xff << 0) + +#define OSD_VIDWINMD_VFINV			(1 << 15) +#define OSD_VIDWINMD_V1EFC			(1 << 14) +#define OSD_VIDWINMD_VHZ1_SHIFT			12 +#define OSD_VIDWINMD_VHZ1			(3 << 12) +#define OSD_VIDWINMD_VVZ1_SHIFT			10 +#define OSD_VIDWINMD_VVZ1			(3 << 10) +#define OSD_VIDWINMD_VFF1			(1 << 9) +#define OSD_VIDWINMD_ACT1			(1 << 8) +#define OSD_VIDWINMD_V0EFC			(1 << 6) +#define OSD_VIDWINMD_VHZ0_SHIFT			4 +#define OSD_VIDWINMD_VHZ0			(3 << 4) +#define OSD_VIDWINMD_VVZ0_SHIFT			2 +#define OSD_VIDWINMD_VVZ0			(3 << 2) +#define OSD_VIDWINMD_VFF0			(1 << 1) +#define OSD_VIDWINMD_ACT0			(1 << 0) + +#define OSD_OSDWIN0MD_ATN0E			(1 << 14) +#define OSD_OSDWIN0MD_RGB0E			(1 << 13) +#define OSD_OSDWIN0MD_BMP0MD_SHIFT		13 +#define OSD_OSDWIN0MD_BMP0MD			(3 << 13) +#define OSD_OSDWIN0MD_CLUTS0			(1 << 12) +#define OSD_OSDWIN0MD_OHZ0_SHIFT		10 +#define OSD_OSDWIN0MD_OHZ0			(3 << 10) +#define OSD_OSDWIN0MD_OVZ0_SHIFT		8 +#define OSD_OSDWIN0MD_OVZ0			(3 << 8) +#define OSD_OSDWIN0MD_BMW0_SHIFT		6 +#define OSD_OSDWIN0MD_BMW0			(3 << 6) +#define OSD_OSDWIN0MD_BLND0_SHIFT		3 +#define OSD_OSDWIN0MD_BLND0			(7 << 3) +#define OSD_OSDWIN0MD_TE0			(1 << 2) +#define OSD_OSDWIN0MD_OFF0			(1 << 1) +#define OSD_OSDWIN0MD_OACT0			(1 << 0) + +#define OSD_OSDWIN1MD_OASW			(1 << 15) +#define OSD_OSDWIN1MD_ATN1E			(1 << 14) +#define OSD_OSDWIN1MD_RGB1E			(1 << 13) +#define OSD_OSDWIN1MD_BMP1MD_SHIFT		13 +#define OSD_OSDWIN1MD_BMP1MD			(3 << 13) +#define OSD_OSDWIN1MD_CLUTS1			(1 << 12) +#define OSD_OSDWIN1MD_OHZ1_SHIFT		10 +#define OSD_OSDWIN1MD_OHZ1			(3 << 10) +#define OSD_OSDWIN1MD_OVZ1_SHIFT		8 +#define OSD_OSDWIN1MD_OVZ1			(3 << 8) +#define OSD_OSDWIN1MD_BMW1_SHIFT		6 +#define OSD_OSDWIN1MD_BMW1			(3 << 6) +#define OSD_OSDWIN1MD_BLND1_SHIFT		3 +#define OSD_OSDWIN1MD_BLND1			(7 << 3) +#define OSD_OSDWIN1MD_TE1			(1 << 2) +#define OSD_OSDWIN1MD_OFF1			(1 << 1) +#define OSD_OSDWIN1MD_OACT1			(1 << 0) + +#define OSD_OSDATRMD_OASW			(1 << 15) +#define OSD_OSDATRMD_OHZA_SHIFT			10 +#define OSD_OSDATRMD_OHZA			(3 << 10) +#define OSD_OSDATRMD_OVZA_SHIFT			8 +#define OSD_OSDATRMD_OVZA			(3 << 8) +#define OSD_OSDATRMD_BLNKINT_SHIFT		6 +#define OSD_OSDATRMD_BLNKINT			(3 << 6) +#define OSD_OSDATRMD_OFFA			(1 << 1) +#define OSD_OSDATRMD_BLNK			(1 << 0) + +#define OSD_RECTCUR_RCAD_SHIFT			8 +#define OSD_RECTCUR_RCAD			(0xff << 8) +#define OSD_RECTCUR_CLUTSR			(1 << 7) +#define OSD_RECTCUR_RCHW_SHIFT			4 +#define OSD_RECTCUR_RCHW			(7 << 4) +#define OSD_RECTCUR_RCVW_SHIFT			1 +#define OSD_RECTCUR_RCVW			(7 << 1) +#define OSD_RECTCUR_RCACT			(1 << 0) + +#define OSD_VIDWIN0OFST_V0LO			(0x1ff << 0) + +#define OSD_VIDWIN1OFST_V1LO			(0x1ff << 0) + +#define OSD_OSDWIN0OFST_O0LO			(0x1ff << 0) + +#define OSD_OSDWIN1OFST_O1LO			(0x1ff << 0) + +#define OSD_WINOFST_AH_SHIFT			9 + +#define OSD_VIDWIN0OFST_V0AH			(0xf << 9) +#define OSD_VIDWIN1OFST_V1AH			(0xf << 9) +#define OSD_OSDWIN0OFST_O0AH			(0xf << 9) +#define OSD_OSDWIN1OFST_O1AH			(0xf << 9) + +#define OSD_VIDWINADH_V1AH_SHIFT		8 +#define OSD_VIDWINADH_V1AH			(0x7f << 8) +#define OSD_VIDWINADH_V0AH_SHIFT		0 +#define OSD_VIDWINADH_V0AH			(0x7f << 0) + +#define OSD_VIDWIN0ADL_V0AL			(0xffff << 0) + +#define OSD_VIDWIN1ADL_V1AL			(0xffff << 0) + +#define OSD_OSDWINADH_O1AH_SHIFT		8 +#define OSD_OSDWINADH_O1AH			(0x7f << 8) +#define OSD_OSDWINADH_O0AH_SHIFT		0 +#define OSD_OSDWINADH_O0AH			(0x7f << 0) + +#define OSD_OSDWIN0ADL_O0AL			(0xffff << 0) + +#define OSD_OSDWIN1ADL_O1AL			(0xffff << 0) + +#define OSD_BASEPX_BPX				(0x3ff << 0) + +#define OSD_BASEPY_BPY				(0x1ff << 0) + +#define OSD_VIDWIN0XP_V0X			(0x7ff << 0) + +#define OSD_VIDWIN0YP_V0Y			(0x7ff << 0) + +#define OSD_VIDWIN0XL_V0W			(0x7ff << 0) + +#define OSD_VIDWIN0YL_V0H			(0x7ff << 0) + +#define OSD_VIDWIN1XP_V1X			(0x7ff << 0) + +#define OSD_VIDWIN1YP_V1Y			(0x7ff << 0) + +#define OSD_VIDWIN1XL_V1W			(0x7ff << 0) + +#define OSD_VIDWIN1YL_V1H			(0x7ff << 0) + +#define OSD_OSDWIN0XP_W0X			(0x7ff << 0) + +#define OSD_OSDWIN0YP_W0Y			(0x7ff << 0) + +#define OSD_OSDWIN0XL_W0W			(0x7ff << 0) + +#define OSD_OSDWIN0YL_W0H			(0x7ff << 0) + +#define OSD_OSDWIN1XP_W1X			(0x7ff << 0) + +#define OSD_OSDWIN1YP_W1Y			(0x7ff << 0) + +#define OSD_OSDWIN1XL_W1W			(0x7ff << 0) + +#define OSD_OSDWIN1YL_W1H			(0x7ff << 0) + +#define OSD_CURXP_RCSX				(0x7ff << 0) + +#define OSD_CURYP_RCSY				(0x7ff << 0) + +#define OSD_CURXL_RCSW				(0x7ff << 0) + +#define OSD_CURYL_RCSH				(0x7ff << 0) + +#define OSD_EXTMODE_EXPMDSEL			(1 << 15) +#define OSD_EXTMODE_SCRNHEXP_SHIFT		13 +#define OSD_EXTMODE_SCRNHEXP			(3 << 13) +#define OSD_EXTMODE_SCRNVEXP			(1 << 12) +#define OSD_EXTMODE_OSD1BLDCHR			(1 << 11) +#define OSD_EXTMODE_OSD0BLDCHR			(1 << 10) +#define OSD_EXTMODE_ATNOSD1EN			(1 << 9) +#define OSD_EXTMODE_ATNOSD0EN			(1 << 8) +#define OSD_EXTMODE_OSDHRSZ15			(1 << 7) +#define OSD_EXTMODE_VIDHRSZ15			(1 << 6) +#define OSD_EXTMODE_ZMFILV1HEN			(1 << 5) +#define OSD_EXTMODE_ZMFILV1VEN			(1 << 4) +#define OSD_EXTMODE_ZMFILV0HEN			(1 << 3) +#define OSD_EXTMODE_ZMFILV0VEN			(1 << 2) +#define OSD_EXTMODE_EXPFILHEN			(1 << 1) +#define OSD_EXTMODE_EXPFILVEN			(1 << 0) + +#define OSD_MISCCTL_BLDSEL			(1 << 15) +#define OSD_MISCCTL_S420D			(1 << 14) +#define OSD_MISCCTL_BMAPT			(1 << 13) +#define OSD_MISCCTL_DM365M			(1 << 12) +#define OSD_MISCCTL_RGBEN			(1 << 7) +#define OSD_MISCCTL_RGBWIN			(1 << 6) +#define OSD_MISCCTL_DMANG			(1 << 6) +#define OSD_MISCCTL_TMON			(1 << 5) +#define OSD_MISCCTL_RSEL			(1 << 4) +#define OSD_MISCCTL_CPBSY			(1 << 3) +#define OSD_MISCCTL_PPSW			(1 << 2) +#define OSD_MISCCTL_PPRV			(1 << 1) + +#define OSD_CLUTRAMYCB_Y_SHIFT			8 +#define OSD_CLUTRAMYCB_Y			(0xff << 8) +#define OSD_CLUTRAMYCB_CB_SHIFT			0 +#define OSD_CLUTRAMYCB_CB			(0xff << 0) + +#define OSD_CLUTRAMCR_CR_SHIFT			8 +#define OSD_CLUTRAMCR_CR			(0xff << 8) +#define OSD_CLUTRAMCR_CADDR_SHIFT		0 +#define OSD_CLUTRAMCR_CADDR			(0xff << 0) + +#define OSD_TRANSPVAL_RGBTRANS			(0xffff << 0) + +#define OSD_TRANSPVALL_RGBL			(0xffff << 0) + +#define OSD_TRANSPVALU_Y_SHIFT			8 +#define OSD_TRANSPVALU_Y			(0xff << 8) +#define OSD_TRANSPVALU_RGBU_SHIFT		0 +#define OSD_TRANSPVALU_RGBU			(0xff << 0) + +#define OSD_TRANSPBMPIDX_BMP1_SHIFT		8 +#define OSD_TRANSPBMPIDX_BMP1			(0xff << 8) +#define OSD_TRANSPBMPIDX_BMP0_SHIFT		0 +#define OSD_TRANSPBMPIDX_BMP0			0xff + +#endif				/* _DAVINCI_VPBE_H_ */ diff --git a/drivers/media/platform/davinci/vpbe_venc.c b/drivers/media/platform/davinci/vpbe_venc.c new file mode 100644 index 00000000000..14a023a75d2 --- /dev/null +++ b/drivers/media/platform/davinci/vpbe_venc.c @@ -0,0 +1,699 @@ +/* + * Copyright (C) 2010 Texas Instruments Inc + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation version 2. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/ctype.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/videodev2.h> +#include <linux/slab.h> + +#include <mach/hardware.h> +#include <mach/mux.h> +#include <linux/platform_data/i2c-davinci.h> + +#include <linux/io.h> + +#include <media/davinci/vpbe_types.h> +#include <media/davinci/vpbe_venc.h> +#include <media/davinci/vpss.h> +#include <media/v4l2-device.h> + +#include "vpbe_venc_regs.h" + +#define MODULE_NAME	"davinci-vpbe-venc" + +static struct platform_device_id vpbe_venc_devtype[] = { +	{ +		.name = DM644X_VPBE_VENC_SUBDEV_NAME, +		.driver_data = VPBE_VERSION_1, +	}, { +		.name = DM365_VPBE_VENC_SUBDEV_NAME, +		.driver_data = VPBE_VERSION_2, +	}, { +		.name = DM355_VPBE_VENC_SUBDEV_NAME, +		.driver_data = VPBE_VERSION_3, +	}, +	{ +		/* sentinel */ +	} +}; + +MODULE_DEVICE_TABLE(platform, vpbe_venc_devtype); + +static int debug = 2; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Debug level 0-2"); + +struct venc_state { +	struct v4l2_subdev sd; +	struct venc_callback *callback; +	struct venc_platform_data *pdata; +	struct device *pdev; +	u32 output; +	v4l2_std_id std; +	spinlock_t lock; +	void __iomem *venc_base; +	void __iomem *vdaccfg_reg; +	enum vpbe_version venc_type; +}; + +static inline struct venc_state *to_state(struct v4l2_subdev *sd) +{ +	return container_of(sd, struct venc_state, sd); +} + +static inline u32 venc_read(struct v4l2_subdev *sd, u32 offset) +{ +	struct venc_state *venc = to_state(sd); + +	return readl(venc->venc_base + offset); +} + +static inline u32 venc_write(struct v4l2_subdev *sd, u32 offset, u32 val) +{ +	struct venc_state *venc = to_state(sd); + +	writel(val, (venc->venc_base + offset)); + +	return val; +} + +static inline u32 venc_modify(struct v4l2_subdev *sd, u32 offset, +				 u32 val, u32 mask) +{ +	u32 new_val = (venc_read(sd, offset) & ~mask) | (val & mask); + +	venc_write(sd, offset, new_val); + +	return new_val; +} + +static inline u32 vdaccfg_write(struct v4l2_subdev *sd, u32 val) +{ +	struct venc_state *venc = to_state(sd); + +	writel(val, venc->vdaccfg_reg); + +	val = readl(venc->vdaccfg_reg); + +	return val; +} + +#define VDAC_COMPONENT	0x543 +#define VDAC_S_VIDEO	0x210 +/* This function sets the dac of the VPBE for various outputs + */ +static int venc_set_dac(struct v4l2_subdev *sd, u32 out_index) +{ +	switch (out_index) { +	case 0: +		v4l2_dbg(debug, 1, sd, "Setting output to Composite\n"); +		venc_write(sd, VENC_DACSEL, 0); +		break; +	case 1: +		v4l2_dbg(debug, 1, sd, "Setting output to Component\n"); +		venc_write(sd, VENC_DACSEL, VDAC_COMPONENT); +		break; +	case 2: +		v4l2_dbg(debug, 1, sd, "Setting output to S-video\n"); +		venc_write(sd, VENC_DACSEL, VDAC_S_VIDEO); +		break; +	default: +		return -EINVAL; +	} + +	return 0; +} + +static void venc_enabledigitaloutput(struct v4l2_subdev *sd, int benable) +{ +	struct venc_state *venc = to_state(sd); + +	v4l2_dbg(debug, 2, sd, "venc_enabledigitaloutput\n"); + +	if (benable) { +		venc_write(sd, VENC_VMOD, 0); +		venc_write(sd, VENC_CVBS, 0); +		venc_write(sd, VENC_LCDOUT, 0); +		venc_write(sd, VENC_HSPLS, 0); +		venc_write(sd, VENC_HSTART, 0); +		venc_write(sd, VENC_HVALID, 0); +		venc_write(sd, VENC_HINT, 0); +		venc_write(sd, VENC_VSPLS, 0); +		venc_write(sd, VENC_VSTART, 0); +		venc_write(sd, VENC_VVALID, 0); +		venc_write(sd, VENC_VINT, 0); +		venc_write(sd, VENC_YCCCTL, 0); +		venc_write(sd, VENC_DACSEL, 0); + +	} else { +		venc_write(sd, VENC_VMOD, 0); +		/* disable VCLK output pin enable */ +		venc_write(sd, VENC_VIDCTL, 0x141); + +		/* Disable output sync pins */ +		venc_write(sd, VENC_SYNCCTL, 0); + +		/* Disable DCLOCK */ +		venc_write(sd, VENC_DCLKCTL, 0); +		venc_write(sd, VENC_DRGBX1, 0x0000057C); + +		/* Disable LCD output control (accepting default polarity) */ +		venc_write(sd, VENC_LCDOUT, 0); +		if (venc->venc_type != VPBE_VERSION_3) +			venc_write(sd, VENC_CMPNT, 0x100); +		venc_write(sd, VENC_HSPLS, 0); +		venc_write(sd, VENC_HINT, 0); +		venc_write(sd, VENC_HSTART, 0); +		venc_write(sd, VENC_HVALID, 0); + +		venc_write(sd, VENC_VSPLS, 0); +		venc_write(sd, VENC_VINT, 0); +		venc_write(sd, VENC_VSTART, 0); +		venc_write(sd, VENC_VVALID, 0); + +		venc_write(sd, VENC_HSDLY, 0); +		venc_write(sd, VENC_VSDLY, 0); + +		venc_write(sd, VENC_YCCCTL, 0); +		venc_write(sd, VENC_VSTARTA, 0); + +		/* Set OSD clock and OSD Sync Adavance registers */ +		venc_write(sd, VENC_OSDCLK0, 1); +		venc_write(sd, VENC_OSDCLK1, 2); +	} +} + +static void +venc_enable_vpss_clock(int venc_type, +		       enum vpbe_enc_timings_type type, +		       unsigned int pclock) +{ +	if (venc_type == VPBE_VERSION_1) +		return; + +	if (venc_type == VPBE_VERSION_2 && (type == VPBE_ENC_STD || (type == +	    VPBE_ENC_DV_TIMINGS && pclock <= 27000000))) { +		vpss_enable_clock(VPSS_VENC_CLOCK_SEL, 1); +		vpss_enable_clock(VPSS_VPBE_CLOCK, 1); +		return; +	} + +	if (venc_type == VPBE_VERSION_3 && type == VPBE_ENC_STD) +		vpss_enable_clock(VPSS_VENC_CLOCK_SEL, 0); +} + +#define VDAC_CONFIG_SD_V3	0x0E21A6B6 +#define VDAC_CONFIG_SD_V2	0x081141CF +/* + * setting NTSC mode + */ +static int venc_set_ntsc(struct v4l2_subdev *sd) +{ +	u32 val; +	struct venc_state *venc = to_state(sd); +	struct venc_platform_data *pdata = venc->pdata; + +	v4l2_dbg(debug, 2, sd, "venc_set_ntsc\n"); + +	/* Setup clock at VPSS & VENC for SD */ +	vpss_enable_clock(VPSS_VENC_CLOCK_SEL, 1); +	if (pdata->setup_clock(VPBE_ENC_STD, V4L2_STD_525_60) < 0) +		return -EINVAL; + +	venc_enable_vpss_clock(venc->venc_type, VPBE_ENC_STD, V4L2_STD_525_60); +	venc_enabledigitaloutput(sd, 0); + +	if (venc->venc_type == VPBE_VERSION_3) { +		venc_write(sd, VENC_CLKCTL, 0x01); +		venc_write(sd, VENC_VIDCTL, 0); +		val = vdaccfg_write(sd, VDAC_CONFIG_SD_V3); +	} else if (venc->venc_type == VPBE_VERSION_2) { +		venc_write(sd, VENC_CLKCTL, 0x01); +		venc_write(sd, VENC_VIDCTL, 0); +		vdaccfg_write(sd, VDAC_CONFIG_SD_V2); +	} else { +		/* to set VENC CLK DIV to 1 - final clock is 54 MHz */ +		venc_modify(sd, VENC_VIDCTL, 0, 1 << 1); +		/* Set REC656 Mode */ +		venc_write(sd, VENC_YCCCTL, 0x1); +		venc_modify(sd, VENC_VDPRO, 0, VENC_VDPRO_DAFRQ); +		venc_modify(sd, VENC_VDPRO, 0, VENC_VDPRO_DAUPS); +	} + +	venc_write(sd, VENC_VMOD, 0); +	venc_modify(sd, VENC_VMOD, (1 << VENC_VMOD_VIE_SHIFT), +			VENC_VMOD_VIE); +	venc_modify(sd, VENC_VMOD, (0 << VENC_VMOD_VMD), VENC_VMOD_VMD); +	venc_modify(sd, VENC_VMOD, (0 << VENC_VMOD_TVTYP_SHIFT), +			VENC_VMOD_TVTYP); +	venc_write(sd, VENC_DACTST, 0x0); +	venc_modify(sd, VENC_VMOD, VENC_VMOD_VENC, VENC_VMOD_VENC); + +	return 0; +} + +/* + * setting PAL mode + */ +static int venc_set_pal(struct v4l2_subdev *sd) +{ +	struct venc_state *venc = to_state(sd); + +	v4l2_dbg(debug, 2, sd, "venc_set_pal\n"); + +	/* Setup clock at VPSS & VENC for SD */ +	vpss_enable_clock(VPSS_VENC_CLOCK_SEL, 1); +	if (venc->pdata->setup_clock(VPBE_ENC_STD, V4L2_STD_625_50) < 0) +		return -EINVAL; + +	venc_enable_vpss_clock(venc->venc_type, VPBE_ENC_STD, V4L2_STD_625_50); +	venc_enabledigitaloutput(sd, 0); + +	if (venc->venc_type == VPBE_VERSION_3) { +		venc_write(sd, VENC_CLKCTL, 0x1); +		venc_write(sd, VENC_VIDCTL, 0); +		vdaccfg_write(sd, VDAC_CONFIG_SD_V3); +	} else if (venc->venc_type == VPBE_VERSION_2) { +		venc_write(sd, VENC_CLKCTL, 0x1); +		venc_write(sd, VENC_VIDCTL, 0); +		vdaccfg_write(sd, VDAC_CONFIG_SD_V2); +	} else { +		/* to set VENC CLK DIV to 1 - final clock is 54 MHz */ +		venc_modify(sd, VENC_VIDCTL, 0, 1 << 1); +		/* Set REC656 Mode */ +		venc_write(sd, VENC_YCCCTL, 0x1); +	} + +	venc_modify(sd, VENC_SYNCCTL, 1 << VENC_SYNCCTL_OVD_SHIFT, +			VENC_SYNCCTL_OVD); +	venc_write(sd, VENC_VMOD, 0); +	venc_modify(sd, VENC_VMOD, +			(1 << VENC_VMOD_VIE_SHIFT), +			VENC_VMOD_VIE); +	venc_modify(sd, VENC_VMOD, +			(0 << VENC_VMOD_VMD), VENC_VMOD_VMD); +	venc_modify(sd, VENC_VMOD, +			(1 << VENC_VMOD_TVTYP_SHIFT), +			VENC_VMOD_TVTYP); +	venc_write(sd, VENC_DACTST, 0x0); +	venc_modify(sd, VENC_VMOD, VENC_VMOD_VENC, VENC_VMOD_VENC); + +	return 0; +} + +#define VDAC_CONFIG_HD_V2	0x081141EF +/* + * venc_set_480p59_94 + * + * This function configures the video encoder to EDTV(525p) component setting. + */ +static int venc_set_480p59_94(struct v4l2_subdev *sd) +{ +	struct venc_state *venc = to_state(sd); +	struct venc_platform_data *pdata = venc->pdata; + +	v4l2_dbg(debug, 2, sd, "venc_set_480p59_94\n"); +	if (venc->venc_type != VPBE_VERSION_1 && +	    venc->venc_type != VPBE_VERSION_2) +		return -EINVAL; + +	/* Setup clock at VPSS & VENC for SD */ +	if (pdata->setup_clock(VPBE_ENC_DV_TIMINGS, 27000000) < 0) +		return -EINVAL; + +	venc_enable_vpss_clock(venc->venc_type, VPBE_ENC_DV_TIMINGS, 27000000); +	venc_enabledigitaloutput(sd, 0); + +	if (venc->venc_type == VPBE_VERSION_2) +		vdaccfg_write(sd, VDAC_CONFIG_HD_V2); +	venc_write(sd, VENC_OSDCLK0, 0); +	venc_write(sd, VENC_OSDCLK1, 1); + +	if (venc->venc_type == VPBE_VERSION_1) { +		venc_modify(sd, VENC_VDPRO, VENC_VDPRO_DAFRQ, +			    VENC_VDPRO_DAFRQ); +		venc_modify(sd, VENC_VDPRO, VENC_VDPRO_DAUPS, +			    VENC_VDPRO_DAUPS); +	} + +	venc_write(sd, VENC_VMOD, 0); +	venc_modify(sd, VENC_VMOD, (1 << VENC_VMOD_VIE_SHIFT), +		    VENC_VMOD_VIE); +	venc_modify(sd, VENC_VMOD, VENC_VMOD_HDMD, VENC_VMOD_HDMD); +	venc_modify(sd, VENC_VMOD, (HDTV_525P << VENC_VMOD_TVTYP_SHIFT), +		    VENC_VMOD_TVTYP); +	venc_modify(sd, VENC_VMOD, VENC_VMOD_VDMD_YCBCR8 << +		    VENC_VMOD_VDMD_SHIFT, VENC_VMOD_VDMD); + +	venc_modify(sd, VENC_VMOD, VENC_VMOD_VENC, VENC_VMOD_VENC); + +	return 0; +} + +/* + * venc_set_625p + * + * This function configures the video encoder to HDTV(625p) component setting + */ +static int venc_set_576p50(struct v4l2_subdev *sd) +{ +	struct venc_state *venc = to_state(sd); +	struct venc_platform_data *pdata = venc->pdata; + +	v4l2_dbg(debug, 2, sd, "venc_set_576p50\n"); + +	if (venc->venc_type != VPBE_VERSION_1 && +	    venc->venc_type != VPBE_VERSION_2) +		return -EINVAL; +	/* Setup clock at VPSS & VENC for SD */ +	if (pdata->setup_clock(VPBE_ENC_DV_TIMINGS, 27000000) < 0) +		return -EINVAL; + +	venc_enable_vpss_clock(venc->venc_type, VPBE_ENC_DV_TIMINGS, 27000000); +	venc_enabledigitaloutput(sd, 0); + +	if (venc->venc_type == VPBE_VERSION_2) +		vdaccfg_write(sd, VDAC_CONFIG_HD_V2); + +	venc_write(sd, VENC_OSDCLK0, 0); +	venc_write(sd, VENC_OSDCLK1, 1); + +	if (venc->venc_type == VPBE_VERSION_1) { +		venc_modify(sd, VENC_VDPRO, VENC_VDPRO_DAFRQ, +			    VENC_VDPRO_DAFRQ); +		venc_modify(sd, VENC_VDPRO, VENC_VDPRO_DAUPS, +			    VENC_VDPRO_DAUPS); +	} + +	venc_write(sd, VENC_VMOD, 0); +	venc_modify(sd, VENC_VMOD, (1 << VENC_VMOD_VIE_SHIFT), +		    VENC_VMOD_VIE); +	venc_modify(sd, VENC_VMOD, VENC_VMOD_HDMD, VENC_VMOD_HDMD); +	venc_modify(sd, VENC_VMOD, (HDTV_625P << VENC_VMOD_TVTYP_SHIFT), +		    VENC_VMOD_TVTYP); + +	venc_modify(sd, VENC_VMOD, VENC_VMOD_VDMD_YCBCR8 << +		    VENC_VMOD_VDMD_SHIFT, VENC_VMOD_VDMD); +	venc_modify(sd, VENC_VMOD, VENC_VMOD_VENC, VENC_VMOD_VENC); + +	return 0; +} + +/* + * venc_set_720p60_internal - Setup 720p60 in venc for dm365 only + */ +static int venc_set_720p60_internal(struct v4l2_subdev *sd) +{ +	struct venc_state *venc = to_state(sd); +	struct venc_platform_data *pdata = venc->pdata; + +	if (pdata->setup_clock(VPBE_ENC_DV_TIMINGS, 74250000) < 0) +		return -EINVAL; + +	venc_enable_vpss_clock(venc->venc_type, VPBE_ENC_DV_TIMINGS, 74250000); +	venc_enabledigitaloutput(sd, 0); + +	venc_write(sd, VENC_OSDCLK0, 0); +	venc_write(sd, VENC_OSDCLK1, 1); + +	venc_write(sd, VENC_VMOD, 0); +	/* DM365 component HD mode */ +	venc_modify(sd, VENC_VMOD, (1 << VENC_VMOD_VIE_SHIFT), +	    VENC_VMOD_VIE); +	venc_modify(sd, VENC_VMOD, VENC_VMOD_HDMD, VENC_VMOD_HDMD); +	venc_modify(sd, VENC_VMOD, (HDTV_720P << VENC_VMOD_TVTYP_SHIFT), +		    VENC_VMOD_TVTYP); +	venc_modify(sd, VENC_VMOD, VENC_VMOD_VENC, VENC_VMOD_VENC); +	venc_write(sd, VENC_XHINTVL, 0); +	return 0; +} + +/* + * venc_set_1080i30_internal - Setup 1080i30 in venc for dm365 only + */ +static int venc_set_1080i30_internal(struct v4l2_subdev *sd) +{ +	struct venc_state *venc = to_state(sd); +	struct venc_platform_data *pdata = venc->pdata; + +	if (pdata->setup_clock(VPBE_ENC_DV_TIMINGS, 74250000) < 0) +		return -EINVAL; + +	venc_enable_vpss_clock(venc->venc_type, VPBE_ENC_DV_TIMINGS, 74250000); +	venc_enabledigitaloutput(sd, 0); + +	venc_write(sd, VENC_OSDCLK0, 0); +	venc_write(sd, VENC_OSDCLK1, 1); + + +	venc_write(sd, VENC_VMOD, 0); +	/* DM365 component HD mode */ +	venc_modify(sd, VENC_VMOD, (1 << VENC_VMOD_VIE_SHIFT), +		    VENC_VMOD_VIE); +	venc_modify(sd, VENC_VMOD, VENC_VMOD_HDMD, VENC_VMOD_HDMD); +	venc_modify(sd, VENC_VMOD, (HDTV_1080I << VENC_VMOD_TVTYP_SHIFT), +		    VENC_VMOD_TVTYP); +	venc_modify(sd, VENC_VMOD, VENC_VMOD_VENC, VENC_VMOD_VENC); +	venc_write(sd, VENC_XHINTVL, 0); +	return 0; +} + +static int venc_s_std_output(struct v4l2_subdev *sd, v4l2_std_id norm) +{ +	v4l2_dbg(debug, 1, sd, "venc_s_std_output\n"); + +	if (norm & V4L2_STD_525_60) +		return venc_set_ntsc(sd); +	else if (norm & V4L2_STD_625_50) +		return venc_set_pal(sd); + +	return -EINVAL; +} + +static int venc_s_dv_timings(struct v4l2_subdev *sd, +			    struct v4l2_dv_timings *dv_timings) +{ +	struct venc_state *venc = to_state(sd); +	u32 height = dv_timings->bt.height; +	int ret; + +	v4l2_dbg(debug, 1, sd, "venc_s_dv_timings\n"); + +	if (height == 576) +		return venc_set_576p50(sd); +	else if (height == 480) +		return venc_set_480p59_94(sd); +	else if ((height == 720) && +			(venc->venc_type == VPBE_VERSION_2)) { +		/* TBD setup internal 720p mode here */ +		ret = venc_set_720p60_internal(sd); +		/* for DM365 VPBE, there is DAC inside */ +		vdaccfg_write(sd, VDAC_CONFIG_HD_V2); +		return ret; +	} else if ((height == 1080) && +		(venc->venc_type == VPBE_VERSION_2)) { +		/* TBD setup internal 1080i mode here */ +		ret = venc_set_1080i30_internal(sd); +		/* for DM365 VPBE, there is DAC inside */ +		vdaccfg_write(sd, VDAC_CONFIG_HD_V2); +		return ret; +	} +	return -EINVAL; +} + +static int venc_s_routing(struct v4l2_subdev *sd, u32 input, u32 output, +			  u32 config) +{ +	struct venc_state *venc = to_state(sd); +	int ret; + +	v4l2_dbg(debug, 1, sd, "venc_s_routing\n"); + +	ret = venc_set_dac(sd, output); +	if (!ret) +		venc->output = output; + +	return ret; +} + +static long venc_ioctl(struct v4l2_subdev *sd, +			unsigned int cmd, +			void *arg) +{ +	u32 val; + +	switch (cmd) { +	case VENC_GET_FLD: +		val = venc_read(sd, VENC_VSTAT); +		*((int *)arg) = ((val & VENC_VSTAT_FIDST) == +		VENC_VSTAT_FIDST); +		break; +	default: +		v4l2_err(sd, "Wrong IOCTL cmd\n"); +		break; +	} + +	return 0; +} + +static const struct v4l2_subdev_core_ops venc_core_ops = { +	.ioctl      = venc_ioctl, +}; + +static const struct v4l2_subdev_video_ops venc_video_ops = { +	.s_routing = venc_s_routing, +	.s_std_output = venc_s_std_output, +	.s_dv_timings = venc_s_dv_timings, +}; + +static const struct v4l2_subdev_ops venc_ops = { +	.core = &venc_core_ops, +	.video = &venc_video_ops, +}; + +static int venc_initialize(struct v4l2_subdev *sd) +{ +	struct venc_state *venc = to_state(sd); +	int ret; + +	/* Set default to output to composite and std to NTSC */ +	venc->output = 0; +	venc->std = V4L2_STD_525_60; + +	ret = venc_s_routing(sd, 0, venc->output, 0); +	if (ret < 0) { +		v4l2_err(sd, "Error setting output during init\n"); +		return -EINVAL; +	} + +	ret = venc_s_std_output(sd, venc->std); +	if (ret < 0) { +		v4l2_err(sd, "Error setting std during init\n"); +		return -EINVAL; +	} + +	return ret; +} + +static int venc_device_get(struct device *dev, void *data) +{ +	struct platform_device *pdev = to_platform_device(dev); +	struct venc_state **venc = data; + +	if (strstr(pdev->name, "vpbe-venc") != NULL) +		*venc = platform_get_drvdata(pdev); + +	return 0; +} + +struct v4l2_subdev *venc_sub_dev_init(struct v4l2_device *v4l2_dev, +		const char *venc_name) +{ +	struct venc_state *venc; +	int err; + +	err = bus_for_each_dev(&platform_bus_type, NULL, &venc, +			venc_device_get); +	if (venc == NULL) +		return NULL; + +	v4l2_subdev_init(&venc->sd, &venc_ops); + +	strcpy(venc->sd.name, venc_name); +	if (v4l2_device_register_subdev(v4l2_dev, &venc->sd) < 0) { +		v4l2_err(v4l2_dev, +			"vpbe unable to register venc sub device\n"); +		return NULL; +	} +	if (venc_initialize(&venc->sd)) { +		v4l2_err(v4l2_dev, +			"vpbe venc initialization failed\n"); +		return NULL; +	} + +	return &venc->sd; +} +EXPORT_SYMBOL(venc_sub_dev_init); + +static int venc_probe(struct platform_device *pdev) +{ +	const struct platform_device_id *pdev_id; +	struct venc_state *venc; +	struct resource *res; + +	if (!pdev->dev.platform_data) { +		dev_err(&pdev->dev, "No platform data for VENC sub device"); +		return -EINVAL; +	} + +	pdev_id = platform_get_device_id(pdev); +	if (!pdev_id) +		return -EINVAL; + +	venc = devm_kzalloc(&pdev->dev, sizeof(struct venc_state), GFP_KERNEL); +	if (venc == NULL) +		return -ENOMEM; + +	venc->venc_type = pdev_id->driver_data; +	venc->pdev = &pdev->dev; +	venc->pdata = pdev->dev.platform_data; + +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + +	venc->venc_base = devm_ioremap_resource(&pdev->dev, res); +	if (IS_ERR(venc->venc_base)) +		return PTR_ERR(venc->venc_base); + +	if (venc->venc_type != VPBE_VERSION_1) { +		res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + +		venc->vdaccfg_reg = devm_ioremap_resource(&pdev->dev, res); +		if (IS_ERR(venc->vdaccfg_reg)) +			return PTR_ERR(venc->vdaccfg_reg); +	} +	spin_lock_init(&venc->lock); +	platform_set_drvdata(pdev, venc); +	dev_notice(venc->pdev, "VENC sub device probe success\n"); + +	return 0; +} + +static int venc_remove(struct platform_device *pdev) +{ +	return 0; +} + +static struct platform_driver venc_driver = { +	.probe		= venc_probe, +	.remove		= venc_remove, +	.driver		= { +		.name	= MODULE_NAME, +		.owner	= THIS_MODULE, +	}, +	.id_table	= vpbe_venc_devtype +}; + +module_platform_driver(venc_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("VPBE VENC Driver"); +MODULE_AUTHOR("Texas Instruments"); diff --git a/drivers/media/platform/davinci/vpbe_venc_regs.h b/drivers/media/platform/davinci/vpbe_venc_regs.h new file mode 100644 index 00000000000..947cb151077 --- /dev/null +++ b/drivers/media/platform/davinci/vpbe_venc_regs.h @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2006-2010 Texas Instruments Inc + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation version 2.. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef _VPBE_VENC_REGS_H +#define _VPBE_VENC_REGS_H + +/* VPBE Video Encoder / Digital LCD Subsystem Registers (VENC) */ +#define VENC_VMOD				0x00 +#define VENC_VIDCTL				0x04 +#define VENC_VDPRO				0x08 +#define VENC_SYNCCTL				0x0C +#define VENC_HSPLS				0x10 +#define VENC_VSPLS				0x14 +#define VENC_HINT				0x18 +#define VENC_HSTART				0x1C +#define VENC_HVALID				0x20 +#define VENC_VINT				0x24 +#define VENC_VSTART				0x28 +#define VENC_VVALID				0x2C +#define VENC_HSDLY				0x30 +#define VENC_VSDLY				0x34 +#define VENC_YCCCTL				0x38 +#define VENC_RGBCTL				0x3C +#define VENC_RGBCLP				0x40 +#define VENC_LINECTL				0x44 +#define VENC_CULLLINE				0x48 +#define VENC_LCDOUT				0x4C +#define VENC_BRTS				0x50 +#define VENC_BRTW				0x54 +#define VENC_ACCTL				0x58 +#define VENC_PWMP				0x5C +#define VENC_PWMW				0x60 +#define VENC_DCLKCTL				0x64 +#define VENC_DCLKPTN0				0x68 +#define VENC_DCLKPTN1				0x6C +#define VENC_DCLKPTN2				0x70 +#define VENC_DCLKPTN3				0x74 +#define VENC_DCLKPTN0A				0x78 +#define VENC_DCLKPTN1A				0x7C +#define VENC_DCLKPTN2A				0x80 +#define VENC_DCLKPTN3A				0x84 +#define VENC_DCLKHS				0x88 +#define VENC_DCLKHSA				0x8C +#define VENC_DCLKHR				0x90 +#define VENC_DCLKVS				0x94 +#define VENC_DCLKVR				0x98 +#define VENC_CAPCTL				0x9C +#define VENC_CAPDO				0xA0 +#define VENC_CAPDE				0xA4 +#define VENC_ATR0				0xA8 +#define VENC_ATR1				0xAC +#define VENC_ATR2				0xB0 +#define VENC_VSTAT				0xB8 +#define VENC_RAMADR				0xBC +#define VENC_RAMPORT				0xC0 +#define VENC_DACTST				0xC4 +#define VENC_YCOLVL				0xC8 +#define VENC_SCPROG				0xCC +#define VENC_CVBS				0xDC +#define VENC_CMPNT				0xE0 +#define VENC_ETMG0				0xE4 +#define VENC_ETMG1				0xE8 +#define VENC_ETMG2				0xEC +#define VENC_ETMG3				0xF0 +#define VENC_DACSEL				0xF4 +#define VENC_ARGBX0				0x100 +#define VENC_ARGBX1				0x104 +#define VENC_ARGBX2				0x108 +#define VENC_ARGBX3				0x10C +#define VENC_ARGBX4				0x110 +#define VENC_DRGBX0				0x114 +#define VENC_DRGBX1				0x118 +#define VENC_DRGBX2				0x11C +#define VENC_DRGBX3				0x120 +#define VENC_DRGBX4				0x124 +#define VENC_VSTARTA				0x128 +#define VENC_OSDCLK0				0x12C +#define VENC_OSDCLK1				0x130 +#define VENC_HVLDCL0				0x134 +#define VENC_HVLDCL1				0x138 +#define VENC_OSDHADV				0x13C +#define VENC_CLKCTL				0x140 +#define VENC_GAMCTL				0x144 +#define VENC_XHINTVL				0x174 + +/* bit definitions */ +#define VPBE_PCR_VENC_DIV			(1 << 1) +#define VPBE_PCR_CLK_OFF			(1 << 0) + +#define VENC_VMOD_VDMD_SHIFT			12 +#define VENC_VMOD_VDMD_YCBCR16			0 +#define VENC_VMOD_VDMD_YCBCR8			1 +#define VENC_VMOD_VDMD_RGB666			2 +#define VENC_VMOD_VDMD_RGB8			3 +#define VENC_VMOD_VDMD_EPSON			4 +#define VENC_VMOD_VDMD_CASIO			5 +#define VENC_VMOD_VDMD_UDISPQVGA		6 +#define VENC_VMOD_VDMD_STNLCD			7 +#define VENC_VMOD_VIE_SHIFT			1 +#define VENC_VMOD_VDMD				(7 << 12) +#define VENC_VMOD_ITLCL				(1 << 11) +#define VENC_VMOD_ITLC				(1 << 10) +#define VENC_VMOD_NSIT				(1 << 9) +#define VENC_VMOD_HDMD				(1 << 8) +#define VENC_VMOD_TVTYP_SHIFT			6 +#define VENC_VMOD_TVTYP				(3 << 6) +#define VENC_VMOD_SLAVE				(1 << 5) +#define VENC_VMOD_VMD				(1 << 4) +#define VENC_VMOD_BLNK				(1 << 3) +#define VENC_VMOD_VIE				(1 << 1) +#define VENC_VMOD_VENC				(1 << 0) + +/* VMOD TVTYP options for HDMD=0 */ +#define SDTV_NTSC				0 +#define SDTV_PAL				1 +/* VMOD TVTYP options for HDMD=1 */ +#define HDTV_525P				0 +#define HDTV_625P				1 +#define HDTV_1080I				2 +#define HDTV_720P				3 + +#define VENC_VIDCTL_VCLKP			(1 << 14) +#define VENC_VIDCTL_VCLKE_SHIFT			13 +#define VENC_VIDCTL_VCLKE			(1 << 13) +#define VENC_VIDCTL_VCLKZ_SHIFT			12 +#define VENC_VIDCTL_VCLKZ			(1 << 12) +#define VENC_VIDCTL_SYDIR_SHIFT			8 +#define VENC_VIDCTL_SYDIR			(1 << 8) +#define VENC_VIDCTL_DOMD_SHIFT			4 +#define VENC_VIDCTL_DOMD			(3 << 4) +#define VENC_VIDCTL_YCDIR_SHIFT			0 +#define VENC_VIDCTL_YCDIR			(1 << 0) + +#define VENC_VDPRO_ATYCC_SHIFT			5 +#define VENC_VDPRO_ATYCC			(1 << 5) +#define VENC_VDPRO_ATCOM_SHIFT			4 +#define VENC_VDPRO_ATCOM			(1 << 4) +#define VENC_VDPRO_DAFRQ			(1 << 3) +#define VENC_VDPRO_DAUPS			(1 << 2) +#define VENC_VDPRO_CUPS				(1 << 1) +#define VENC_VDPRO_YUPS				(1 << 0) + +#define VENC_SYNCCTL_VPL_SHIFT			3 +#define VENC_SYNCCTL_VPL			(1 << 3) +#define VENC_SYNCCTL_HPL_SHIFT			2 +#define VENC_SYNCCTL_HPL			(1 << 2) +#define VENC_SYNCCTL_SYEV_SHIFT			1 +#define VENC_SYNCCTL_SYEV			(1 << 1) +#define VENC_SYNCCTL_SYEH_SHIFT			0 +#define VENC_SYNCCTL_SYEH			(1 << 0) +#define VENC_SYNCCTL_OVD_SHIFT			14 +#define VENC_SYNCCTL_OVD			(1 << 14) + +#define VENC_DCLKCTL_DCKEC_SHIFT		11 +#define VENC_DCLKCTL_DCKEC			(1 << 11) +#define VENC_DCLKCTL_DCKPW_SHIFT		0 +#define VENC_DCLKCTL_DCKPW			(0x3f << 0) + +#define VENC_VSTAT_FIDST			(1 << 4) + +#define VENC_CMPNT_MRGB_SHIFT			14 +#define VENC_CMPNT_MRGB				(1 << 14) + +#endif				/* _VPBE_VENC_REGS_H */ diff --git a/drivers/media/platform/davinci/vpfe_capture.c b/drivers/media/platform/davinci/vpfe_capture.c new file mode 100644 index 00000000000..a51bda2fb63 --- /dev/null +++ b/drivers/media/platform/davinci/vpfe_capture.c @@ -0,0 +1,2044 @@ +/* + * Copyright (C) 2008-2009 Texas Instruments Inc + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA + * + * Driver name : VPFE Capture driver + *    VPFE Capture driver allows applications to capture and stream video + *    frames on DaVinci SoCs (DM6446, DM355 etc) from a YUV source such as + *    TVP5146 or  Raw Bayer RGB image data from an image sensor + *    such as Microns' MT9T001, MT9T031 etc. + * + *    These SoCs have, in common, a Video Processing Subsystem (VPSS) that + *    consists of a Video Processing Front End (VPFE) for capturing + *    video/raw image data and Video Processing Back End (VPBE) for displaying + *    YUV data through an in-built analog encoder or Digital LCD port. This + *    driver is for capture through VPFE. A typical EVM using these SoCs have + *    following high level configuration. + * + * + *    decoder(TVP5146/		YUV/ + * 	     MT9T001)   -->  Raw Bayer RGB ---> MUX -> VPFE (CCDC/ISIF) + *    				data input              |      | + *							V      | + *						      SDRAM    | + *							       V + *							   Image Processor + *							       | + *							       V + *							     SDRAM + *    The data flow happens from a decoder connected to the VPFE over a + *    YUV embedded (BT.656/BT.1120) or separate sync or raw bayer rgb interface + *    and to the input of VPFE through an optional MUX (if more inputs are + *    to be interfaced on the EVM). The input data is first passed through + *    CCDC (CCD Controller, a.k.a Image Sensor Interface, ISIF). The CCDC + *    does very little or no processing on YUV data and does pre-process Raw + *    Bayer RGB data through modules such as Defect Pixel Correction (DFC) + *    Color Space Conversion (CSC), data gain/offset etc. After this, data + *    can be written to SDRAM or can be connected to the image processing + *    block such as IPIPE (on DM355 only). + * + *    Features supported + *  		- MMAP IO + *		- Capture using TVP5146 over BT.656 + *		- support for interfacing decoders using sub device model + *		- Work with DM355 or DM6446 CCDC to do Raw Bayer RGB/YUV + *		  data capture to SDRAM. + *    TODO list + *		- Support multiple REQBUF after open + *		- Support for de-allocating buffers through REQBUF + *		- Support for Raw Bayer RGB capture + *		- Support for chaining Image Processor + *		- Support for static allocation of buffers + *		- Support for USERPTR IO + *		- Support for STREAMON before QBUF + *		- Support for control ioctls + */ +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <media/v4l2-common.h> +#include <linux/io.h> +#include <media/davinci/vpfe_capture.h> +#include "ccdc_hw_device.h" + +static int debug; +static u32 numbuffers = 3; +static u32 bufsize = (720 * 576 * 2); + +module_param(numbuffers, uint, S_IRUGO); +module_param(bufsize, uint, S_IRUGO); +module_param(debug, int, 0644); + +MODULE_PARM_DESC(numbuffers, "buffer count (default:3)"); +MODULE_PARM_DESC(bufsize, "buffer size in bytes (default:720 x 576 x 2)"); +MODULE_PARM_DESC(debug, "Debug level 0-1"); + +MODULE_DESCRIPTION("VPFE Video for Linux Capture Driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Texas Instruments"); + +/* standard information */ +struct vpfe_standard { +	v4l2_std_id std_id; +	unsigned int width; +	unsigned int height; +	struct v4l2_fract pixelaspect; +	/* 0 - progressive, 1 - interlaced */ +	int frame_format; +}; + +/* ccdc configuration */ +struct ccdc_config { +	/* This make sure vpfe is probed and ready to go */ +	int vpfe_probed; +	/* name of ccdc device */ +	char name[32]; +}; + +/* data structures */ +static struct vpfe_config_params config_params = { +	.min_numbuffers = 3, +	.numbuffers = 3, +	.min_bufsize = 720 * 480 * 2, +	.device_bufsize = 720 * 576 * 2, +}; + +/* ccdc device registered */ +static struct ccdc_hw_device *ccdc_dev; +/* lock for accessing ccdc information */ +static DEFINE_MUTEX(ccdc_lock); +/* ccdc configuration */ +static struct ccdc_config *ccdc_cfg; + +const struct vpfe_standard vpfe_standards[] = { +	{V4L2_STD_525_60, 720, 480, {11, 10}, 1}, +	{V4L2_STD_625_50, 720, 576, {54, 59}, 1}, +}; + +/* Used when raw Bayer image from ccdc is directly captured to SDRAM */ +static const struct vpfe_pixel_format vpfe_pix_fmts[] = { +	{ +		.fmtdesc = { +			.index = 0, +			.type = V4L2_BUF_TYPE_VIDEO_CAPTURE, +			.description = "Bayer GrRBGb 8bit A-Law compr.", +			.pixelformat = V4L2_PIX_FMT_SBGGR8, +		}, +		.bpp = 1, +	}, +	{ +		.fmtdesc = { +			.index = 1, +			.type = V4L2_BUF_TYPE_VIDEO_CAPTURE, +			.description = "Bayer GrRBGb - 16bit", +			.pixelformat = V4L2_PIX_FMT_SBGGR16, +		}, +		.bpp = 2, +	}, +	{ +		.fmtdesc = { +			.index = 2, +			.type = V4L2_BUF_TYPE_VIDEO_CAPTURE, +			.description = "Bayer GrRBGb 8bit DPCM compr.", +			.pixelformat = V4L2_PIX_FMT_SGRBG10DPCM8, +		}, +		.bpp = 1, +	}, +	{ +		.fmtdesc = { +			.index = 3, +			.type = V4L2_BUF_TYPE_VIDEO_CAPTURE, +			.description = "YCbCr 4:2:2 Interleaved UYVY", +			.pixelformat = V4L2_PIX_FMT_UYVY, +		}, +		.bpp = 2, +	}, +	{ +		.fmtdesc = { +			.index = 4, +			.type = V4L2_BUF_TYPE_VIDEO_CAPTURE, +			.description = "YCbCr 4:2:2 Interleaved YUYV", +			.pixelformat = V4L2_PIX_FMT_YUYV, +		}, +		.bpp = 2, +	}, +	{ +		.fmtdesc = { +			.index = 5, +			.type = V4L2_BUF_TYPE_VIDEO_CAPTURE, +			.description = "Y/CbCr 4:2:0 - Semi planar", +			.pixelformat = V4L2_PIX_FMT_NV12, +		}, +		.bpp = 1, +	}, +}; + +/* + * vpfe_lookup_pix_format() + * lookup an entry in the vpfe pix format table based on pix_format + */ +static const struct vpfe_pixel_format *vpfe_lookup_pix_format(u32 pix_format) +{ +	int i; + +	for (i = 0; i < ARRAY_SIZE(vpfe_pix_fmts); i++) { +		if (pix_format == vpfe_pix_fmts[i].fmtdesc.pixelformat) +			return &vpfe_pix_fmts[i]; +	} +	return NULL; +} + +/* + * vpfe_register_ccdc_device. CCDC module calls this to + * register with vpfe capture + */ +int vpfe_register_ccdc_device(struct ccdc_hw_device *dev) +{ +	int ret = 0; +	printk(KERN_NOTICE "vpfe_register_ccdc_device: %s\n", dev->name); + +	BUG_ON(!dev->hw_ops.open); +	BUG_ON(!dev->hw_ops.enable); +	BUG_ON(!dev->hw_ops.set_hw_if_params); +	BUG_ON(!dev->hw_ops.configure); +	BUG_ON(!dev->hw_ops.set_buftype); +	BUG_ON(!dev->hw_ops.get_buftype); +	BUG_ON(!dev->hw_ops.enum_pix); +	BUG_ON(!dev->hw_ops.set_frame_format); +	BUG_ON(!dev->hw_ops.get_frame_format); +	BUG_ON(!dev->hw_ops.get_pixel_format); +	BUG_ON(!dev->hw_ops.set_pixel_format); +	BUG_ON(!dev->hw_ops.set_image_window); +	BUG_ON(!dev->hw_ops.get_image_window); +	BUG_ON(!dev->hw_ops.get_line_length); +	BUG_ON(!dev->hw_ops.getfid); + +	mutex_lock(&ccdc_lock); +	if (NULL == ccdc_cfg) { +		/* +		 * TODO. Will this ever happen? if so, we need to fix it. +		 * Proabably we need to add the request to a linked list and +		 * walk through it during vpfe probe +		 */ +		printk(KERN_ERR "vpfe capture not initialized\n"); +		ret = -EFAULT; +		goto unlock; +	} + +	if (strcmp(dev->name, ccdc_cfg->name)) { +		/* ignore this ccdc */ +		ret = -EINVAL; +		goto unlock; +	} + +	if (ccdc_dev) { +		printk(KERN_ERR "ccdc already registered\n"); +		ret = -EINVAL; +		goto unlock; +	} + +	ccdc_dev = dev; +unlock: +	mutex_unlock(&ccdc_lock); +	return ret; +} +EXPORT_SYMBOL(vpfe_register_ccdc_device); + +/* + * vpfe_unregister_ccdc_device. CCDC module calls this to + * unregister with vpfe capture + */ +void vpfe_unregister_ccdc_device(struct ccdc_hw_device *dev) +{ +	if (NULL == dev) { +		printk(KERN_ERR "invalid ccdc device ptr\n"); +		return; +	} + +	printk(KERN_NOTICE "vpfe_unregister_ccdc_device, dev->name = %s\n", +		dev->name); + +	if (strcmp(dev->name, ccdc_cfg->name)) { +		/* ignore this ccdc */ +		return; +	} + +	mutex_lock(&ccdc_lock); +	ccdc_dev = NULL; +	mutex_unlock(&ccdc_lock); +	return; +} +EXPORT_SYMBOL(vpfe_unregister_ccdc_device); + +/* + * vpfe_get_ccdc_image_format - Get image parameters based on CCDC settings + */ +static int vpfe_get_ccdc_image_format(struct vpfe_device *vpfe_dev, +				 struct v4l2_format *f) +{ +	struct v4l2_rect image_win; +	enum ccdc_buftype buf_type; +	enum ccdc_frmfmt frm_fmt; + +	memset(f, 0, sizeof(*f)); +	f->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; +	ccdc_dev->hw_ops.get_image_window(&image_win); +	f->fmt.pix.width = image_win.width; +	f->fmt.pix.height = image_win.height; +	f->fmt.pix.bytesperline = ccdc_dev->hw_ops.get_line_length(); +	f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * +				f->fmt.pix.height; +	buf_type = ccdc_dev->hw_ops.get_buftype(); +	f->fmt.pix.pixelformat = ccdc_dev->hw_ops.get_pixel_format(); +	frm_fmt = ccdc_dev->hw_ops.get_frame_format(); +	if (frm_fmt == CCDC_FRMFMT_PROGRESSIVE) +		f->fmt.pix.field = V4L2_FIELD_NONE; +	else if (frm_fmt == CCDC_FRMFMT_INTERLACED) { +		if (buf_type == CCDC_BUFTYPE_FLD_INTERLEAVED) +			f->fmt.pix.field = V4L2_FIELD_INTERLACED; +		else if (buf_type == CCDC_BUFTYPE_FLD_SEPARATED) +			f->fmt.pix.field = V4L2_FIELD_SEQ_TB; +		else { +			v4l2_err(&vpfe_dev->v4l2_dev, "Invalid buf_type\n"); +			return -EINVAL; +		} +	} else { +		v4l2_err(&vpfe_dev->v4l2_dev, "Invalid frm_fmt\n"); +		return -EINVAL; +	} +	return 0; +} + +/* + * vpfe_config_ccdc_image_format() + * For a pix format, configure ccdc to setup the capture + */ +static int vpfe_config_ccdc_image_format(struct vpfe_device *vpfe_dev) +{ +	enum ccdc_frmfmt frm_fmt = CCDC_FRMFMT_INTERLACED; +	int ret = 0; + +	if (ccdc_dev->hw_ops.set_pixel_format( +			vpfe_dev->fmt.fmt.pix.pixelformat) < 0) { +		v4l2_err(&vpfe_dev->v4l2_dev, +			"couldn't set pix format in ccdc\n"); +		return -EINVAL; +	} +	/* configure the image window */ +	ccdc_dev->hw_ops.set_image_window(&vpfe_dev->crop); + +	switch (vpfe_dev->fmt.fmt.pix.field) { +	case V4L2_FIELD_INTERLACED: +		/* do nothing, since it is default */ +		ret = ccdc_dev->hw_ops.set_buftype( +				CCDC_BUFTYPE_FLD_INTERLEAVED); +		break; +	case V4L2_FIELD_NONE: +		frm_fmt = CCDC_FRMFMT_PROGRESSIVE; +		/* buffer type only applicable for interlaced scan */ +		break; +	case V4L2_FIELD_SEQ_TB: +		ret = ccdc_dev->hw_ops.set_buftype( +				CCDC_BUFTYPE_FLD_SEPARATED); +		break; +	default: +		return -EINVAL; +	} + +	/* set the frame format */ +	if (!ret) +		ret = ccdc_dev->hw_ops.set_frame_format(frm_fmt); +	return ret; +} +/* + * vpfe_config_image_format() + * For a given standard, this functions sets up the default + * pix format & crop values in the vpfe device and ccdc.  It first + * starts with defaults based values from the standard table. + * It then checks if sub device support g_mbus_fmt and then override the + * values based on that.Sets crop values to match with scan resolution + * starting at 0,0. It calls vpfe_config_ccdc_image_format() set the + * values in ccdc + */ +static int vpfe_config_image_format(struct vpfe_device *vpfe_dev, +				    v4l2_std_id std_id) +{ +	struct vpfe_subdev_info *sdinfo = vpfe_dev->current_subdev; +	struct v4l2_mbus_framefmt mbus_fmt; +	struct v4l2_pix_format *pix = &vpfe_dev->fmt.fmt.pix; +	int i, ret = 0; + +	for (i = 0; i < ARRAY_SIZE(vpfe_standards); i++) { +		if (vpfe_standards[i].std_id & std_id) { +			vpfe_dev->std_info.active_pixels = +					vpfe_standards[i].width; +			vpfe_dev->std_info.active_lines = +					vpfe_standards[i].height; +			vpfe_dev->std_info.frame_format = +					vpfe_standards[i].frame_format; +			vpfe_dev->std_index = i; +			break; +		} +	} + +	if (i ==  ARRAY_SIZE(vpfe_standards)) { +		v4l2_err(&vpfe_dev->v4l2_dev, "standard not supported\n"); +		return -EINVAL; +	} + +	vpfe_dev->crop.top = 0; +	vpfe_dev->crop.left = 0; +	vpfe_dev->crop.width = vpfe_dev->std_info.active_pixels; +	vpfe_dev->crop.height = vpfe_dev->std_info.active_lines; +	pix->width = vpfe_dev->crop.width; +	pix->height = vpfe_dev->crop.height; + +	/* first field and frame format based on standard frame format */ +	if (vpfe_dev->std_info.frame_format) { +		pix->field = V4L2_FIELD_INTERLACED; +		/* assume V4L2_PIX_FMT_UYVY as default */ +		pix->pixelformat = V4L2_PIX_FMT_UYVY; +		v4l2_fill_mbus_format(&mbus_fmt, pix, +				V4L2_MBUS_FMT_YUYV10_2X10); +	} else { +		pix->field = V4L2_FIELD_NONE; +		/* assume V4L2_PIX_FMT_SBGGR8 */ +		pix->pixelformat = V4L2_PIX_FMT_SBGGR8; +		v4l2_fill_mbus_format(&mbus_fmt, pix, +				V4L2_MBUS_FMT_SBGGR8_1X8); +	} + +	/* if sub device supports g_mbus_fmt, override the defaults */ +	ret = v4l2_device_call_until_err(&vpfe_dev->v4l2_dev, +			sdinfo->grp_id, video, g_mbus_fmt, &mbus_fmt); + +	if (ret && ret != -ENOIOCTLCMD) { +		v4l2_err(&vpfe_dev->v4l2_dev, +			"error in getting g_mbus_fmt from sub device\n"); +		return ret; +	} +	v4l2_fill_pix_format(pix, &mbus_fmt); +	pix->bytesperline = pix->width * 2; +	pix->sizeimage = pix->bytesperline * pix->height; + +	/* Sets the values in CCDC */ +	ret = vpfe_config_ccdc_image_format(vpfe_dev); +	if (ret) +		return ret; + +	/* Update the values of sizeimage and bytesperline */ +	if (!ret) { +		pix->bytesperline = ccdc_dev->hw_ops.get_line_length(); +		pix->sizeimage = pix->bytesperline * pix->height; +	} +	return ret; +} + +static int vpfe_initialize_device(struct vpfe_device *vpfe_dev) +{ +	int ret = 0; + +	/* set first input of current subdevice as the current input */ +	vpfe_dev->current_input = 0; + +	/* set default standard */ +	vpfe_dev->std_index = 0; + +	/* Configure the default format information */ +	ret = vpfe_config_image_format(vpfe_dev, +				vpfe_standards[vpfe_dev->std_index].std_id); +	if (ret) +		return ret; + +	/* now open the ccdc device to initialize it */ +	mutex_lock(&ccdc_lock); +	if (NULL == ccdc_dev) { +		v4l2_err(&vpfe_dev->v4l2_dev, "ccdc device not registered\n"); +		ret = -ENODEV; +		goto unlock; +	} + +	if (!try_module_get(ccdc_dev->owner)) { +		v4l2_err(&vpfe_dev->v4l2_dev, "Couldn't lock ccdc module\n"); +		ret = -ENODEV; +		goto unlock; +	} +	ret = ccdc_dev->hw_ops.open(vpfe_dev->pdev); +	if (!ret) +		vpfe_dev->initialized = 1; + +	/* Clear all VPFE/CCDC interrupts */ +	if (vpfe_dev->cfg->clr_intr) +		vpfe_dev->cfg->clr_intr(-1); + +unlock: +	mutex_unlock(&ccdc_lock); +	return ret; +} + +/* + * vpfe_open : It creates object of file handle structure and + * stores it in private_data  member of filepointer + */ +static int vpfe_open(struct file *file) +{ +	struct vpfe_device *vpfe_dev = video_drvdata(file); +	struct video_device *vdev = video_devdata(file); +	struct vpfe_fh *fh; + +	v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_open\n"); + +	if (!vpfe_dev->cfg->num_subdevs) { +		v4l2_err(&vpfe_dev->v4l2_dev, "No decoder registered\n"); +		return -ENODEV; +	} + +	/* Allocate memory for the file handle object */ +	fh = kmalloc(sizeof(struct vpfe_fh), GFP_KERNEL); +	if (NULL == fh) { +		v4l2_err(&vpfe_dev->v4l2_dev, +			"unable to allocate memory for file handle object\n"); +		return -ENOMEM; +	} +	/* store pointer to fh in private_data member of file */ +	file->private_data = fh; +	fh->vpfe_dev = vpfe_dev; +	v4l2_fh_init(&fh->fh, vdev); +	mutex_lock(&vpfe_dev->lock); +	/* If decoder is not initialized. initialize it */ +	if (!vpfe_dev->initialized) { +		if (vpfe_initialize_device(vpfe_dev)) { +			mutex_unlock(&vpfe_dev->lock); +			return -ENODEV; +		} +	} +	/* Increment device usrs counter */ +	vpfe_dev->usrs++; +	/* Set io_allowed member to false */ +	fh->io_allowed = 0; +	v4l2_fh_add(&fh->fh); +	mutex_unlock(&vpfe_dev->lock); +	return 0; +} + +static void vpfe_schedule_next_buffer(struct vpfe_device *vpfe_dev) +{ +	unsigned long addr; + +	vpfe_dev->next_frm = list_entry(vpfe_dev->dma_queue.next, +					struct videobuf_buffer, queue); +	list_del(&vpfe_dev->next_frm->queue); +	vpfe_dev->next_frm->state = VIDEOBUF_ACTIVE; +	addr = videobuf_to_dma_contig(vpfe_dev->next_frm); + +	ccdc_dev->hw_ops.setfbaddr(addr); +} + +static void vpfe_schedule_bottom_field(struct vpfe_device *vpfe_dev) +{ +	unsigned long addr; + +	addr = videobuf_to_dma_contig(vpfe_dev->cur_frm); +	addr += vpfe_dev->field_off; +	ccdc_dev->hw_ops.setfbaddr(addr); +} + +static void vpfe_process_buffer_complete(struct vpfe_device *vpfe_dev) +{ +	v4l2_get_timestamp(&vpfe_dev->cur_frm->ts); +	vpfe_dev->cur_frm->state = VIDEOBUF_DONE; +	vpfe_dev->cur_frm->size = vpfe_dev->fmt.fmt.pix.sizeimage; +	wake_up_interruptible(&vpfe_dev->cur_frm->done); +	vpfe_dev->cur_frm = vpfe_dev->next_frm; +} + +/* ISR for VINT0*/ +static irqreturn_t vpfe_isr(int irq, void *dev_id) +{ +	struct vpfe_device *vpfe_dev = dev_id; +	enum v4l2_field field; +	int fid; + +	v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "\nStarting vpfe_isr...\n"); +	field = vpfe_dev->fmt.fmt.pix.field; + +	/* if streaming not started, don't do anything */ +	if (!vpfe_dev->started) +		goto clear_intr; + +	/* only for 6446 this will be applicable */ +	if (NULL != ccdc_dev->hw_ops.reset) +		ccdc_dev->hw_ops.reset(); + +	if (field == V4L2_FIELD_NONE) { +		/* handle progressive frame capture */ +		v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, +			"frame format is progressive...\n"); +		if (vpfe_dev->cur_frm != vpfe_dev->next_frm) +			vpfe_process_buffer_complete(vpfe_dev); +		goto clear_intr; +	} + +	/* interlaced or TB capture check which field we are in hardware */ +	fid = ccdc_dev->hw_ops.getfid(); + +	/* switch the software maintained field id */ +	vpfe_dev->field_id ^= 1; +	v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "field id = %x:%x.\n", +		fid, vpfe_dev->field_id); +	if (fid == vpfe_dev->field_id) { +		/* we are in-sync here,continue */ +		if (fid == 0) { +			/* +			 * One frame is just being captured. If the next frame +			 * is available, release the current frame and move on +			 */ +			if (vpfe_dev->cur_frm != vpfe_dev->next_frm) +				vpfe_process_buffer_complete(vpfe_dev); +			/* +			 * based on whether the two fields are stored +			 * interleavely or separately in memory, reconfigure +			 * the CCDC memory address +			 */ +			if (field == V4L2_FIELD_SEQ_TB) { +				vpfe_schedule_bottom_field(vpfe_dev); +			} +			goto clear_intr; +		} +		/* +		 * if one field is just being captured configure +		 * the next frame get the next frame from the empty +		 * queue if no frame is available hold on to the +		 * current buffer +		 */ +		spin_lock(&vpfe_dev->dma_queue_lock); +		if (!list_empty(&vpfe_dev->dma_queue) && +		    vpfe_dev->cur_frm == vpfe_dev->next_frm) +			vpfe_schedule_next_buffer(vpfe_dev); +		spin_unlock(&vpfe_dev->dma_queue_lock); +	} else if (fid == 0) { +		/* +		 * out of sync. Recover from any hardware out-of-sync. +		 * May loose one frame +		 */ +		vpfe_dev->field_id = fid; +	} +clear_intr: +	if (vpfe_dev->cfg->clr_intr) +		vpfe_dev->cfg->clr_intr(irq); + +	return IRQ_HANDLED; +} + +/* vdint1_isr - isr handler for VINT1 interrupt */ +static irqreturn_t vdint1_isr(int irq, void *dev_id) +{ +	struct vpfe_device *vpfe_dev = dev_id; + +	v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "\nInside vdint1_isr...\n"); + +	/* if streaming not started, don't do anything */ +	if (!vpfe_dev->started) { +		if (vpfe_dev->cfg->clr_intr) +			vpfe_dev->cfg->clr_intr(irq); +		return IRQ_HANDLED; +	} + +	spin_lock(&vpfe_dev->dma_queue_lock); +	if ((vpfe_dev->fmt.fmt.pix.field == V4L2_FIELD_NONE) && +	    !list_empty(&vpfe_dev->dma_queue) && +	    vpfe_dev->cur_frm == vpfe_dev->next_frm) +		vpfe_schedule_next_buffer(vpfe_dev); +	spin_unlock(&vpfe_dev->dma_queue_lock); + +	if (vpfe_dev->cfg->clr_intr) +		vpfe_dev->cfg->clr_intr(irq); + +	return IRQ_HANDLED; +} + +static void vpfe_detach_irq(struct vpfe_device *vpfe_dev) +{ +	enum ccdc_frmfmt frame_format; + +	frame_format = ccdc_dev->hw_ops.get_frame_format(); +	if (frame_format == CCDC_FRMFMT_PROGRESSIVE) +		free_irq(vpfe_dev->ccdc_irq1, vpfe_dev); +} + +static int vpfe_attach_irq(struct vpfe_device *vpfe_dev) +{ +	enum ccdc_frmfmt frame_format; + +	frame_format = ccdc_dev->hw_ops.get_frame_format(); +	if (frame_format == CCDC_FRMFMT_PROGRESSIVE) { +		return request_irq(vpfe_dev->ccdc_irq1, vdint1_isr, +				    0, "vpfe_capture1", +				    vpfe_dev); +	} +	return 0; +} + +/* vpfe_stop_ccdc_capture: stop streaming in ccdc/isif */ +static void vpfe_stop_ccdc_capture(struct vpfe_device *vpfe_dev) +{ +	vpfe_dev->started = 0; +	ccdc_dev->hw_ops.enable(0); +	if (ccdc_dev->hw_ops.enable_out_to_sdram) +		ccdc_dev->hw_ops.enable_out_to_sdram(0); +} + +/* + * vpfe_release : This function deletes buffer queue, frees the + * buffers and the vpfe file  handle + */ +static int vpfe_release(struct file *file) +{ +	struct vpfe_device *vpfe_dev = video_drvdata(file); +	struct vpfe_fh *fh = file->private_data; +	struct vpfe_subdev_info *sdinfo; +	int ret; + +	v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_release\n"); + +	/* Get the device lock */ +	mutex_lock(&vpfe_dev->lock); +	/* if this instance is doing IO */ +	if (fh->io_allowed) { +		if (vpfe_dev->started) { +			sdinfo = vpfe_dev->current_subdev; +			ret = v4l2_device_call_until_err(&vpfe_dev->v4l2_dev, +							 sdinfo->grp_id, +							 video, s_stream, 0); +			if (ret && (ret != -ENOIOCTLCMD)) +				v4l2_err(&vpfe_dev->v4l2_dev, +				"stream off failed in subdev\n"); +			vpfe_stop_ccdc_capture(vpfe_dev); +			vpfe_detach_irq(vpfe_dev); +			videobuf_streamoff(&vpfe_dev->buffer_queue); +		} +		vpfe_dev->io_usrs = 0; +		vpfe_dev->numbuffers = config_params.numbuffers; +		videobuf_stop(&vpfe_dev->buffer_queue); +		videobuf_mmap_free(&vpfe_dev->buffer_queue); +	} + +	/* Decrement device usrs counter */ +	vpfe_dev->usrs--; +	v4l2_fh_del(&fh->fh); +	v4l2_fh_exit(&fh->fh); +	/* If this is the last file handle */ +	if (!vpfe_dev->usrs) { +		vpfe_dev->initialized = 0; +		if (ccdc_dev->hw_ops.close) +			ccdc_dev->hw_ops.close(vpfe_dev->pdev); +		module_put(ccdc_dev->owner); +	} +	mutex_unlock(&vpfe_dev->lock); +	file->private_data = NULL; +	/* Free memory allocated to file handle object */ +	kfree(fh); +	return 0; +} + +/* + * vpfe_mmap : It is used to map kernel space buffers + * into user spaces + */ +static int vpfe_mmap(struct file *file, struct vm_area_struct *vma) +{ +	/* Get the device object and file handle object */ +	struct vpfe_device *vpfe_dev = video_drvdata(file); + +	v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_mmap\n"); + +	return videobuf_mmap_mapper(&vpfe_dev->buffer_queue, vma); +} + +/* + * vpfe_poll: It is used for select/poll system call + */ +static unsigned int vpfe_poll(struct file *file, poll_table *wait) +{ +	struct vpfe_device *vpfe_dev = video_drvdata(file); + +	v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_poll\n"); + +	if (vpfe_dev->started) +		return videobuf_poll_stream(file, +					    &vpfe_dev->buffer_queue, wait); +	return 0; +} + +/* vpfe capture driver file operations */ +static const struct v4l2_file_operations vpfe_fops = { +	.owner = THIS_MODULE, +	.open = vpfe_open, +	.release = vpfe_release, +	.unlocked_ioctl = video_ioctl2, +	.mmap = vpfe_mmap, +	.poll = vpfe_poll +}; + +/* + * vpfe_check_format() + * This function adjust the input pixel format as per hardware + * capabilities and update the same in pixfmt. + * Following algorithm used :- + * + *	If given pixformat is not in the vpfe list of pix formats or not + *	supported by the hardware, current value of pixformat in the device + *	is used + *	If given field is not supported, then current field is used. If field + *	is different from current, then it is matched with that from sub device. + *	Minimum height is 2 lines for interlaced or tb field and 1 line for + *	progressive. Maximum height is clamped to active active lines of scan + *	Minimum width is 32 bytes in memory and width is clamped to active + *	pixels of scan. + *	bytesperline is a multiple of 32. + */ +static const struct vpfe_pixel_format * +	vpfe_check_format(struct vpfe_device *vpfe_dev, +			  struct v4l2_pix_format *pixfmt) +{ +	u32 min_height = 1, min_width = 32, max_width, max_height; +	const struct vpfe_pixel_format *vpfe_pix_fmt; +	u32 pix; +	int temp, found; + +	vpfe_pix_fmt = vpfe_lookup_pix_format(pixfmt->pixelformat); +	if (NULL == vpfe_pix_fmt) { +		/* +		 * use current pixel format in the vpfe device. We +		 * will find this pix format in the table +		 */ +		pixfmt->pixelformat = vpfe_dev->fmt.fmt.pix.pixelformat; +		vpfe_pix_fmt = vpfe_lookup_pix_format(pixfmt->pixelformat); +	} + +	/* check if hw supports it */ +	temp = 0; +	found = 0; +	while (ccdc_dev->hw_ops.enum_pix(&pix, temp) >= 0) { +		if (vpfe_pix_fmt->fmtdesc.pixelformat == pix) { +			found = 1; +			break; +		} +		temp++; +	} + +	if (!found) { +		/* use current pixel format */ +		pixfmt->pixelformat = vpfe_dev->fmt.fmt.pix.pixelformat; +		/* +		 * Since this is currently used in the vpfe device, we +		 * will find this pix format in the table +		 */ +		vpfe_pix_fmt = vpfe_lookup_pix_format(pixfmt->pixelformat); +	} + +	/* check what field format is supported */ +	if (pixfmt->field == V4L2_FIELD_ANY) { +		/* if field is any, use current value as default */ +		pixfmt->field = vpfe_dev->fmt.fmt.pix.field; +	} + +	/* +	 * if field is not same as current field in the vpfe device +	 * try matching the field with the sub device field +	 */ +	if (vpfe_dev->fmt.fmt.pix.field != pixfmt->field) { +		/* +		 * If field value is not in the supported fields, use current +		 * field used in the device as default +		 */ +		switch (pixfmt->field) { +		case V4L2_FIELD_INTERLACED: +		case V4L2_FIELD_SEQ_TB: +			/* if sub device is supporting progressive, use that */ +			if (!vpfe_dev->std_info.frame_format) +				pixfmt->field = V4L2_FIELD_NONE; +			break; +		case V4L2_FIELD_NONE: +			if (vpfe_dev->std_info.frame_format) +				pixfmt->field = V4L2_FIELD_INTERLACED; +			break; + +		default: +			/* use current field as default */ +			pixfmt->field = vpfe_dev->fmt.fmt.pix.field; +			break; +		} +	} + +	/* Now adjust image resolutions supported */ +	if (pixfmt->field == V4L2_FIELD_INTERLACED || +	    pixfmt->field == V4L2_FIELD_SEQ_TB) +		min_height = 2; + +	max_width = vpfe_dev->std_info.active_pixels; +	max_height = vpfe_dev->std_info.active_lines; +	min_width /= vpfe_pix_fmt->bpp; + +	v4l2_info(&vpfe_dev->v4l2_dev, "width = %d, height = %d, bpp = %d\n", +		  pixfmt->width, pixfmt->height, vpfe_pix_fmt->bpp); + +	pixfmt->width = clamp((pixfmt->width), min_width, max_width); +	pixfmt->height = clamp((pixfmt->height), min_height, max_height); + +	/* If interlaced, adjust height to be a multiple of 2 */ +	if (pixfmt->field == V4L2_FIELD_INTERLACED) +		pixfmt->height &= (~1); +	/* +	 * recalculate bytesperline and sizeimage since width +	 * and height might have changed +	 */ +	pixfmt->bytesperline = (((pixfmt->width * vpfe_pix_fmt->bpp) + 31) +				& ~31); +	if (pixfmt->pixelformat == V4L2_PIX_FMT_NV12) +		pixfmt->sizeimage = +			pixfmt->bytesperline * pixfmt->height + +			((pixfmt->bytesperline * pixfmt->height) >> 1); +	else +		pixfmt->sizeimage = pixfmt->bytesperline * pixfmt->height; + +	v4l2_info(&vpfe_dev->v4l2_dev, "adjusted width = %d, height =" +		 " %d, bpp = %d, bytesperline = %d, sizeimage = %d\n", +		 pixfmt->width, pixfmt->height, vpfe_pix_fmt->bpp, +		 pixfmt->bytesperline, pixfmt->sizeimage); +	return vpfe_pix_fmt; +} + +static int vpfe_querycap(struct file *file, void  *priv, +			       struct v4l2_capability *cap) +{ +	struct vpfe_device *vpfe_dev = video_drvdata(file); + +	v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_querycap\n"); + +	cap->version = VPFE_CAPTURE_VERSION_CODE; +	cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING; +	strlcpy(cap->driver, CAPTURE_DRV_NAME, sizeof(cap->driver)); +	strlcpy(cap->bus_info, "VPFE", sizeof(cap->bus_info)); +	strlcpy(cap->card, vpfe_dev->cfg->card_name, sizeof(cap->card)); +	return 0; +} + +static int vpfe_g_fmt_vid_cap(struct file *file, void *priv, +				struct v4l2_format *fmt) +{ +	struct vpfe_device *vpfe_dev = video_drvdata(file); +	int ret = 0; + +	v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_g_fmt_vid_cap\n"); +	/* Fill in the information about format */ +	*fmt = vpfe_dev->fmt; +	return ret; +} + +static int vpfe_enum_fmt_vid_cap(struct file *file, void  *priv, +				   struct v4l2_fmtdesc *fmt) +{ +	struct vpfe_device *vpfe_dev = video_drvdata(file); +	const struct vpfe_pixel_format *pix_fmt; +	int temp_index; +	u32 pix; + +	v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_enum_fmt_vid_cap\n"); + +	if (ccdc_dev->hw_ops.enum_pix(&pix, fmt->index) < 0) +		return -EINVAL; + +	/* Fill in the information about format */ +	pix_fmt = vpfe_lookup_pix_format(pix); +	if (NULL != pix_fmt) { +		temp_index = fmt->index; +		*fmt = pix_fmt->fmtdesc; +		fmt->index = temp_index; +		return 0; +	} +	return -EINVAL; +} + +static int vpfe_s_fmt_vid_cap(struct file *file, void *priv, +				struct v4l2_format *fmt) +{ +	struct vpfe_device *vpfe_dev = video_drvdata(file); +	const struct vpfe_pixel_format *pix_fmts; +	int ret = 0; + +	v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_s_fmt_vid_cap\n"); + +	/* If streaming is started, return error */ +	if (vpfe_dev->started) { +		v4l2_err(&vpfe_dev->v4l2_dev, "Streaming is started\n"); +		return -EBUSY; +	} + +	/* Check for valid frame format */ +	pix_fmts = vpfe_check_format(vpfe_dev, &fmt->fmt.pix); + +	if (NULL == pix_fmts) +		return -EINVAL; + +	/* store the pixel format in the device  object */ +	ret = mutex_lock_interruptible(&vpfe_dev->lock); +	if (ret) +		return ret; + +	/* First detach any IRQ if currently attached */ +	vpfe_detach_irq(vpfe_dev); +	vpfe_dev->fmt = *fmt; +	/* set image capture parameters in the ccdc */ +	ret = vpfe_config_ccdc_image_format(vpfe_dev); +	mutex_unlock(&vpfe_dev->lock); +	return ret; +} + +static int vpfe_try_fmt_vid_cap(struct file *file, void *priv, +				  struct v4l2_format *f) +{ +	struct vpfe_device *vpfe_dev = video_drvdata(file); +	const struct vpfe_pixel_format *pix_fmts; + +	v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_try_fmt_vid_cap\n"); + +	pix_fmts = vpfe_check_format(vpfe_dev, &f->fmt.pix); +	if (NULL == pix_fmts) +		return -EINVAL; +	return 0; +} + +/* + * vpfe_get_subdev_input_index - Get subdev index and subdev input index for a + * given app input index + */ +static int vpfe_get_subdev_input_index(struct vpfe_device *vpfe_dev, +					int *subdev_index, +					int *subdev_input_index, +					int app_input_index) +{ +	struct vpfe_config *cfg = vpfe_dev->cfg; +	struct vpfe_subdev_info *sdinfo; +	int i, j = 0; + +	for (i = 0; i < cfg->num_subdevs; i++) { +		sdinfo = &cfg->sub_devs[i]; +		if (app_input_index < (j + sdinfo->num_inputs)) { +			*subdev_index = i; +			*subdev_input_index = app_input_index - j; +			return 0; +		} +		j += sdinfo->num_inputs; +	} +	return -EINVAL; +} + +/* + * vpfe_get_app_input - Get app input index for a given subdev input index + * driver stores the input index of the current sub device and translate it + * when application request the current input + */ +static int vpfe_get_app_input_index(struct vpfe_device *vpfe_dev, +				    int *app_input_index) +{ +	struct vpfe_config *cfg = vpfe_dev->cfg; +	struct vpfe_subdev_info *sdinfo; +	int i, j = 0; + +	for (i = 0; i < cfg->num_subdevs; i++) { +		sdinfo = &cfg->sub_devs[i]; +		if (!strcmp(sdinfo->name, vpfe_dev->current_subdev->name)) { +			if (vpfe_dev->current_input >= sdinfo->num_inputs) +				return -1; +			*app_input_index = j + vpfe_dev->current_input; +			return 0; +		} +		j += sdinfo->num_inputs; +	} +	return -EINVAL; +} + +static int vpfe_enum_input(struct file *file, void *priv, +				 struct v4l2_input *inp) +{ +	struct vpfe_device *vpfe_dev = video_drvdata(file); +	struct vpfe_subdev_info *sdinfo; +	int subdev, index ; + +	v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_enum_input\n"); + +	if (vpfe_get_subdev_input_index(vpfe_dev, +					&subdev, +					&index, +					inp->index) < 0) { +		v4l2_err(&vpfe_dev->v4l2_dev, "input information not found" +			 " for the subdev\n"); +		return -EINVAL; +	} +	sdinfo = &vpfe_dev->cfg->sub_devs[subdev]; +	memcpy(inp, &sdinfo->inputs[index], sizeof(struct v4l2_input)); +	return 0; +} + +static int vpfe_g_input(struct file *file, void *priv, unsigned int *index) +{ +	struct vpfe_device *vpfe_dev = video_drvdata(file); + +	v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_g_input\n"); + +	return vpfe_get_app_input_index(vpfe_dev, index); +} + + +static int vpfe_s_input(struct file *file, void *priv, unsigned int index) +{ +	struct vpfe_device *vpfe_dev = video_drvdata(file); +	struct v4l2_subdev *sd; +	struct vpfe_subdev_info *sdinfo; +	int subdev_index, inp_index; +	struct vpfe_route *route; +	u32 input = 0, output = 0; +	int ret = -EINVAL; + +	v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_s_input\n"); + +	ret = mutex_lock_interruptible(&vpfe_dev->lock); +	if (ret) +		return ret; + +	/* +	 * If streaming is started return device busy +	 * error +	 */ +	if (vpfe_dev->started) { +		v4l2_err(&vpfe_dev->v4l2_dev, "Streaming is on\n"); +		ret = -EBUSY; +		goto unlock_out; +	} +	ret = vpfe_get_subdev_input_index(vpfe_dev, +					  &subdev_index, +					  &inp_index, +					  index); +	if (ret < 0) { +		v4l2_err(&vpfe_dev->v4l2_dev, "invalid input index\n"); +		goto unlock_out; +	} + +	sdinfo = &vpfe_dev->cfg->sub_devs[subdev_index]; +	sd = vpfe_dev->sd[subdev_index]; +	route = &sdinfo->routes[inp_index]; +	if (route && sdinfo->can_route) { +		input = route->input; +		output = route->output; +	} + +	if (sd) +		ret = v4l2_subdev_call(sd, video, s_routing, input, output, 0); + +	if (ret) { +		v4l2_err(&vpfe_dev->v4l2_dev, +			"vpfe_doioctl:error in setting input in decoder\n"); +		ret = -EINVAL; +		goto unlock_out; +	} +	vpfe_dev->current_subdev = sdinfo; +	if (sd) +		vpfe_dev->v4l2_dev.ctrl_handler = sd->ctrl_handler; +	vpfe_dev->current_input = index; +	vpfe_dev->std_index = 0; + +	/* set the bus/interface parameter for the sub device in ccdc */ +	ret = ccdc_dev->hw_ops.set_hw_if_params(&sdinfo->ccdc_if_params); +	if (ret) +		goto unlock_out; + +	/* set the default image parameters in the device */ +	ret = vpfe_config_image_format(vpfe_dev, +				vpfe_standards[vpfe_dev->std_index].std_id); +unlock_out: +	mutex_unlock(&vpfe_dev->lock); +	return ret; +} + +static int vpfe_querystd(struct file *file, void *priv, v4l2_std_id *std_id) +{ +	struct vpfe_device *vpfe_dev = video_drvdata(file); +	struct vpfe_subdev_info *sdinfo; +	int ret = 0; + +	v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_querystd\n"); + +	ret = mutex_lock_interruptible(&vpfe_dev->lock); +	sdinfo = vpfe_dev->current_subdev; +	if (ret) +		return ret; +	/* Call querystd function of decoder device */ +	ret = v4l2_device_call_until_err(&vpfe_dev->v4l2_dev, sdinfo->grp_id, +					 video, querystd, std_id); +	mutex_unlock(&vpfe_dev->lock); +	return ret; +} + +static int vpfe_s_std(struct file *file, void *priv, v4l2_std_id std_id) +{ +	struct vpfe_device *vpfe_dev = video_drvdata(file); +	struct vpfe_subdev_info *sdinfo; +	int ret = 0; + +	v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_s_std\n"); + +	/* Call decoder driver function to set the standard */ +	ret = mutex_lock_interruptible(&vpfe_dev->lock); +	if (ret) +		return ret; + +	sdinfo = vpfe_dev->current_subdev; +	/* If streaming is started, return device busy error */ +	if (vpfe_dev->started) { +		v4l2_err(&vpfe_dev->v4l2_dev, "streaming is started\n"); +		ret = -EBUSY; +		goto unlock_out; +	} + +	ret = v4l2_device_call_until_err(&vpfe_dev->v4l2_dev, sdinfo->grp_id, +					 video, s_std, std_id); +	if (ret < 0) { +		v4l2_err(&vpfe_dev->v4l2_dev, "Failed to set standard\n"); +		goto unlock_out; +	} +	ret = vpfe_config_image_format(vpfe_dev, std_id); + +unlock_out: +	mutex_unlock(&vpfe_dev->lock); +	return ret; +} + +static int vpfe_g_std(struct file *file, void *priv, v4l2_std_id *std_id) +{ +	struct vpfe_device *vpfe_dev = video_drvdata(file); + +	v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_g_std\n"); + +	*std_id = vpfe_standards[vpfe_dev->std_index].std_id; +	return 0; +} +/* + *  Videobuf operations + */ +static int vpfe_videobuf_setup(struct videobuf_queue *vq, +				unsigned int *count, +				unsigned int *size) +{ +	struct vpfe_fh *fh = vq->priv_data; +	struct vpfe_device *vpfe_dev = fh->vpfe_dev; + +	v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_buffer_setup\n"); +	*size = vpfe_dev->fmt.fmt.pix.sizeimage; +	if (vpfe_dev->memory == V4L2_MEMORY_MMAP && +		vpfe_dev->fmt.fmt.pix.sizeimage > config_params.device_bufsize) +		*size = config_params.device_bufsize; + +	if (*count < config_params.min_numbuffers) +		*count = config_params.min_numbuffers; +	v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, +		"count=%d, size=%d\n", *count, *size); +	return 0; +} + +static int vpfe_videobuf_prepare(struct videobuf_queue *vq, +				struct videobuf_buffer *vb, +				enum v4l2_field field) +{ +	struct vpfe_fh *fh = vq->priv_data; +	struct vpfe_device *vpfe_dev = fh->vpfe_dev; +	unsigned long addr; +	int ret; + +	v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_buffer_prepare\n"); + +	/* If buffer is not initialized, initialize it */ +	if (VIDEOBUF_NEEDS_INIT == vb->state) { +		vb->width = vpfe_dev->fmt.fmt.pix.width; +		vb->height = vpfe_dev->fmt.fmt.pix.height; +		vb->size = vpfe_dev->fmt.fmt.pix.sizeimage; +		vb->field = field; + +		ret = videobuf_iolock(vq, vb, NULL); +		if (ret < 0) +			return ret; + +		addr = videobuf_to_dma_contig(vb); +		/* Make sure user addresses are aligned to 32 bytes */ +		if (!ALIGN(addr, 32)) +			return -EINVAL; + +		vb->state = VIDEOBUF_PREPARED; +	} +	return 0; +} + +static void vpfe_videobuf_queue(struct videobuf_queue *vq, +				struct videobuf_buffer *vb) +{ +	/* Get the file handle object and device object */ +	struct vpfe_fh *fh = vq->priv_data; +	struct vpfe_device *vpfe_dev = fh->vpfe_dev; +	unsigned long flags; + +	v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_buffer_queue\n"); + +	/* add the buffer to the DMA queue */ +	spin_lock_irqsave(&vpfe_dev->dma_queue_lock, flags); +	list_add_tail(&vb->queue, &vpfe_dev->dma_queue); +	spin_unlock_irqrestore(&vpfe_dev->dma_queue_lock, flags); + +	/* Change state of the buffer */ +	vb->state = VIDEOBUF_QUEUED; +} + +static void vpfe_videobuf_release(struct videobuf_queue *vq, +				  struct videobuf_buffer *vb) +{ +	struct vpfe_fh *fh = vq->priv_data; +	struct vpfe_device *vpfe_dev = fh->vpfe_dev; +	unsigned long flags; + +	v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_videobuf_release\n"); + +	/* +	 * We need to flush the buffer from the dma queue since +	 * they are de-allocated +	 */ +	spin_lock_irqsave(&vpfe_dev->dma_queue_lock, flags); +	INIT_LIST_HEAD(&vpfe_dev->dma_queue); +	spin_unlock_irqrestore(&vpfe_dev->dma_queue_lock, flags); +	videobuf_dma_contig_free(vq, vb); +	vb->state = VIDEOBUF_NEEDS_INIT; +} + +static struct videobuf_queue_ops vpfe_videobuf_qops = { +	.buf_setup      = vpfe_videobuf_setup, +	.buf_prepare    = vpfe_videobuf_prepare, +	.buf_queue      = vpfe_videobuf_queue, +	.buf_release    = vpfe_videobuf_release, +}; + +/* + * vpfe_reqbufs. currently support REQBUF only once opening + * the device. + */ +static int vpfe_reqbufs(struct file *file, void *priv, +			struct v4l2_requestbuffers *req_buf) +{ +	struct vpfe_device *vpfe_dev = video_drvdata(file); +	struct vpfe_fh *fh = file->private_data; +	int ret = 0; + +	v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_reqbufs\n"); + +	if (V4L2_BUF_TYPE_VIDEO_CAPTURE != req_buf->type) { +		v4l2_err(&vpfe_dev->v4l2_dev, "Invalid buffer type\n"); +		return -EINVAL; +	} + +	ret = mutex_lock_interruptible(&vpfe_dev->lock); +	if (ret) +		return ret; + +	if (vpfe_dev->io_usrs != 0) { +		v4l2_err(&vpfe_dev->v4l2_dev, "Only one IO user allowed\n"); +		ret = -EBUSY; +		goto unlock_out; +	} + +	vpfe_dev->memory = req_buf->memory; +	videobuf_queue_dma_contig_init(&vpfe_dev->buffer_queue, +				&vpfe_videobuf_qops, +				vpfe_dev->pdev, +				&vpfe_dev->irqlock, +				req_buf->type, +				vpfe_dev->fmt.fmt.pix.field, +				sizeof(struct videobuf_buffer), +				fh, NULL); + +	fh->io_allowed = 1; +	vpfe_dev->io_usrs = 1; +	INIT_LIST_HEAD(&vpfe_dev->dma_queue); +	ret = videobuf_reqbufs(&vpfe_dev->buffer_queue, req_buf); +unlock_out: +	mutex_unlock(&vpfe_dev->lock); +	return ret; +} + +static int vpfe_querybuf(struct file *file, void *priv, +			 struct v4l2_buffer *buf) +{ +	struct vpfe_device *vpfe_dev = video_drvdata(file); + +	v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_querybuf\n"); + +	if (V4L2_BUF_TYPE_VIDEO_CAPTURE != buf->type) { +		v4l2_err(&vpfe_dev->v4l2_dev, "Invalid buf type\n"); +		return  -EINVAL; +	} + +	if (vpfe_dev->memory != V4L2_MEMORY_MMAP) { +		v4l2_err(&vpfe_dev->v4l2_dev, "Invalid memory\n"); +		return -EINVAL; +	} +	/* Call videobuf_querybuf to get information */ +	return videobuf_querybuf(&vpfe_dev->buffer_queue, buf); +} + +static int vpfe_qbuf(struct file *file, void *priv, +		     struct v4l2_buffer *p) +{ +	struct vpfe_device *vpfe_dev = video_drvdata(file); +	struct vpfe_fh *fh = file->private_data; + +	v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_qbuf\n"); + +	if (V4L2_BUF_TYPE_VIDEO_CAPTURE != p->type) { +		v4l2_err(&vpfe_dev->v4l2_dev, "Invalid buf type\n"); +		return -EINVAL; +	} + +	/* +	 * If this file handle is not allowed to do IO, +	 * return error +	 */ +	if (!fh->io_allowed) { +		v4l2_err(&vpfe_dev->v4l2_dev, "fh->io_allowed\n"); +		return -EACCES; +	} +	return videobuf_qbuf(&vpfe_dev->buffer_queue, p); +} + +static int vpfe_dqbuf(struct file *file, void *priv, +		      struct v4l2_buffer *buf) +{ +	struct vpfe_device *vpfe_dev = video_drvdata(file); + +	v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_dqbuf\n"); + +	if (V4L2_BUF_TYPE_VIDEO_CAPTURE != buf->type) { +		v4l2_err(&vpfe_dev->v4l2_dev, "Invalid buf type\n"); +		return -EINVAL; +	} +	return videobuf_dqbuf(&vpfe_dev->buffer_queue, +				      buf, file->f_flags & O_NONBLOCK); +} + +/* + * vpfe_calculate_offsets : This function calculates buffers offset + * for top and bottom field + */ +static void vpfe_calculate_offsets(struct vpfe_device *vpfe_dev) +{ +	struct v4l2_rect image_win; + +	v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_calculate_offsets\n"); + +	ccdc_dev->hw_ops.get_image_window(&image_win); +	vpfe_dev->field_off = image_win.height * image_win.width; +} + +/* vpfe_start_ccdc_capture: start streaming in ccdc/isif */ +static void vpfe_start_ccdc_capture(struct vpfe_device *vpfe_dev) +{ +	ccdc_dev->hw_ops.enable(1); +	if (ccdc_dev->hw_ops.enable_out_to_sdram) +		ccdc_dev->hw_ops.enable_out_to_sdram(1); +	vpfe_dev->started = 1; +} + +/* + * vpfe_streamon. Assume the DMA queue is not empty. + * application is expected to call QBUF before calling + * this ioctl. If not, driver returns error + */ +static int vpfe_streamon(struct file *file, void *priv, +			 enum v4l2_buf_type buf_type) +{ +	struct vpfe_device *vpfe_dev = video_drvdata(file); +	struct vpfe_fh *fh = file->private_data; +	struct vpfe_subdev_info *sdinfo; +	unsigned long addr; +	int ret = 0; + +	v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_streamon\n"); + +	if (V4L2_BUF_TYPE_VIDEO_CAPTURE != buf_type) { +		v4l2_err(&vpfe_dev->v4l2_dev, "Invalid buf type\n"); +		return -EINVAL; +	} + +	/* If file handle is not allowed IO, return error */ +	if (!fh->io_allowed) { +		v4l2_err(&vpfe_dev->v4l2_dev, "fh->io_allowed\n"); +		return -EACCES; +	} + +	sdinfo = vpfe_dev->current_subdev; +	ret = v4l2_device_call_until_err(&vpfe_dev->v4l2_dev, sdinfo->grp_id, +					video, s_stream, 1); + +	if (ret && (ret != -ENOIOCTLCMD)) { +		v4l2_err(&vpfe_dev->v4l2_dev, "stream on failed in subdev\n"); +		return -EINVAL; +	} + +	/* If buffer queue is empty, return error */ +	if (list_empty(&vpfe_dev->buffer_queue.stream)) { +		v4l2_err(&vpfe_dev->v4l2_dev, "buffer queue is empty\n"); +		return -EIO; +	} + +	/* Call videobuf_streamon to start streaming * in videobuf */ +	ret = videobuf_streamon(&vpfe_dev->buffer_queue); +	if (ret) +		return ret; + + +	ret = mutex_lock_interruptible(&vpfe_dev->lock); +	if (ret) +		goto streamoff; +	/* Get the next frame from the buffer queue */ +	vpfe_dev->next_frm = list_entry(vpfe_dev->dma_queue.next, +					struct videobuf_buffer, queue); +	vpfe_dev->cur_frm = vpfe_dev->next_frm; +	/* Remove buffer from the buffer queue */ +	list_del(&vpfe_dev->cur_frm->queue); +	/* Mark state of the current frame to active */ +	vpfe_dev->cur_frm->state = VIDEOBUF_ACTIVE; +	/* Initialize field_id and started member */ +	vpfe_dev->field_id = 0; +	addr = videobuf_to_dma_contig(vpfe_dev->cur_frm); + +	/* Calculate field offset */ +	vpfe_calculate_offsets(vpfe_dev); + +	if (vpfe_attach_irq(vpfe_dev) < 0) { +		v4l2_err(&vpfe_dev->v4l2_dev, +			 "Error in attaching interrupt handle\n"); +		ret = -EFAULT; +		goto unlock_out; +	} +	if (ccdc_dev->hw_ops.configure() < 0) { +		v4l2_err(&vpfe_dev->v4l2_dev, +			 "Error in configuring ccdc\n"); +		ret = -EINVAL; +		goto unlock_out; +	} +	ccdc_dev->hw_ops.setfbaddr((unsigned long)(addr)); +	vpfe_start_ccdc_capture(vpfe_dev); +	mutex_unlock(&vpfe_dev->lock); +	return ret; +unlock_out: +	mutex_unlock(&vpfe_dev->lock); +streamoff: +	ret = videobuf_streamoff(&vpfe_dev->buffer_queue); +	return ret; +} + +static int vpfe_streamoff(struct file *file, void *priv, +			  enum v4l2_buf_type buf_type) +{ +	struct vpfe_device *vpfe_dev = video_drvdata(file); +	struct vpfe_fh *fh = file->private_data; +	struct vpfe_subdev_info *sdinfo; +	int ret = 0; + +	v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_streamoff\n"); + +	if (V4L2_BUF_TYPE_VIDEO_CAPTURE != buf_type) { +		v4l2_err(&vpfe_dev->v4l2_dev, "Invalid buf type\n"); +		return -EINVAL; +	} + +	/* If io is allowed for this file handle, return error */ +	if (!fh->io_allowed) { +		v4l2_err(&vpfe_dev->v4l2_dev, "fh->io_allowed\n"); +		return -EACCES; +	} + +	/* If streaming is not started, return error */ +	if (!vpfe_dev->started) { +		v4l2_err(&vpfe_dev->v4l2_dev, "device started\n"); +		return -EINVAL; +	} + +	ret = mutex_lock_interruptible(&vpfe_dev->lock); +	if (ret) +		return ret; + +	vpfe_stop_ccdc_capture(vpfe_dev); +	vpfe_detach_irq(vpfe_dev); + +	sdinfo = vpfe_dev->current_subdev; +	ret = v4l2_device_call_until_err(&vpfe_dev->v4l2_dev, sdinfo->grp_id, +					video, s_stream, 0); + +	if (ret && (ret != -ENOIOCTLCMD)) +		v4l2_err(&vpfe_dev->v4l2_dev, "stream off failed in subdev\n"); +	ret = videobuf_streamoff(&vpfe_dev->buffer_queue); +	mutex_unlock(&vpfe_dev->lock); +	return ret; +} + +static int vpfe_cropcap(struct file *file, void *priv, +			      struct v4l2_cropcap *crop) +{ +	struct vpfe_device *vpfe_dev = video_drvdata(file); + +	v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_cropcap\n"); + +	if (vpfe_dev->std_index >= ARRAY_SIZE(vpfe_standards)) +		return -EINVAL; + +	memset(crop, 0, sizeof(struct v4l2_cropcap)); +	crop->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; +	crop->bounds.width = crop->defrect.width = +		vpfe_standards[vpfe_dev->std_index].width; +	crop->bounds.height = crop->defrect.height = +		vpfe_standards[vpfe_dev->std_index].height; +	crop->pixelaspect = vpfe_standards[vpfe_dev->std_index].pixelaspect; +	return 0; +} + +static int vpfe_g_crop(struct file *file, void *priv, +			     struct v4l2_crop *crop) +{ +	struct vpfe_device *vpfe_dev = video_drvdata(file); + +	v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_g_crop\n"); + +	crop->c = vpfe_dev->crop; +	return 0; +} + +static int vpfe_s_crop(struct file *file, void *priv, +			     const struct v4l2_crop *crop) +{ +	struct vpfe_device *vpfe_dev = video_drvdata(file); +	struct v4l2_rect rect = crop->c; +	int ret = 0; + +	v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, "vpfe_s_crop\n"); + +	if (vpfe_dev->started) { +		/* make sure streaming is not started */ +		v4l2_err(&vpfe_dev->v4l2_dev, +			"Cannot change crop when streaming is ON\n"); +		return -EBUSY; +	} + +	ret = mutex_lock_interruptible(&vpfe_dev->lock); +	if (ret) +		return ret; + +	if (rect.top < 0 || rect.left < 0) { +		v4l2_err(&vpfe_dev->v4l2_dev, +			"doesn't support negative values for top & left\n"); +		ret = -EINVAL; +		goto unlock_out; +	} + +	/* adjust the width to 16 pixel boundary */ +	rect.width = ((rect.width + 15) & ~0xf); + +	/* make sure parameters are valid */ +	if ((rect.left + rect.width > +		vpfe_dev->std_info.active_pixels) || +	    (rect.top + rect.height > +		vpfe_dev->std_info.active_lines)) { +		v4l2_err(&vpfe_dev->v4l2_dev, "Error in S_CROP params\n"); +		ret = -EINVAL; +		goto unlock_out; +	} +	ccdc_dev->hw_ops.set_image_window(&rect); +	vpfe_dev->fmt.fmt.pix.width = rect.width; +	vpfe_dev->fmt.fmt.pix.height = rect.height; +	vpfe_dev->fmt.fmt.pix.bytesperline = +		ccdc_dev->hw_ops.get_line_length(); +	vpfe_dev->fmt.fmt.pix.sizeimage = +		vpfe_dev->fmt.fmt.pix.bytesperline * +		vpfe_dev->fmt.fmt.pix.height; +	vpfe_dev->crop = rect; +unlock_out: +	mutex_unlock(&vpfe_dev->lock); +	return ret; +} + + +static long vpfe_param_handler(struct file *file, void *priv, +		bool valid_prio, unsigned int cmd, void *param) +{ +	struct vpfe_device *vpfe_dev = video_drvdata(file); +	int ret = 0; + +	v4l2_dbg(2, debug, &vpfe_dev->v4l2_dev, "vpfe_param_handler\n"); + +	if (vpfe_dev->started) { +		/* only allowed if streaming is not started */ +		v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, +			"device already started\n"); +		return -EBUSY; +	} + +	ret = mutex_lock_interruptible(&vpfe_dev->lock); +	if (ret) +		return ret; + +	switch (cmd) { +	case VPFE_CMD_S_CCDC_RAW_PARAMS: +		v4l2_warn(&vpfe_dev->v4l2_dev, +			  "VPFE_CMD_S_CCDC_RAW_PARAMS: experimental ioctl\n"); +		if (ccdc_dev->hw_ops.set_params) { +			ret = ccdc_dev->hw_ops.set_params(param); +			if (ret) { +				v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, +					"Error setting parameters in CCDC\n"); +				goto unlock_out; +			} +			ret = vpfe_get_ccdc_image_format(vpfe_dev, +							 &vpfe_dev->fmt); +			if (ret < 0) { +				v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, +					"Invalid image format at CCDC\n"); +				goto unlock_out; +			} +		} else { +			ret = -EINVAL; +			v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, +				"VPFE_CMD_S_CCDC_RAW_PARAMS not supported\n"); +		} +		break; +	default: +		ret = -ENOTTY; +	} +unlock_out: +	mutex_unlock(&vpfe_dev->lock); +	return ret; +} + + +/* vpfe capture ioctl operations */ +static const struct v4l2_ioctl_ops vpfe_ioctl_ops = { +	.vidioc_querycap	 = vpfe_querycap, +	.vidioc_g_fmt_vid_cap    = vpfe_g_fmt_vid_cap, +	.vidioc_enum_fmt_vid_cap = vpfe_enum_fmt_vid_cap, +	.vidioc_s_fmt_vid_cap    = vpfe_s_fmt_vid_cap, +	.vidioc_try_fmt_vid_cap  = vpfe_try_fmt_vid_cap, +	.vidioc_enum_input	 = vpfe_enum_input, +	.vidioc_g_input		 = vpfe_g_input, +	.vidioc_s_input		 = vpfe_s_input, +	.vidioc_querystd	 = vpfe_querystd, +	.vidioc_s_std		 = vpfe_s_std, +	.vidioc_g_std		 = vpfe_g_std, +	.vidioc_reqbufs		 = vpfe_reqbufs, +	.vidioc_querybuf	 = vpfe_querybuf, +	.vidioc_qbuf		 = vpfe_qbuf, +	.vidioc_dqbuf		 = vpfe_dqbuf, +	.vidioc_streamon	 = vpfe_streamon, +	.vidioc_streamoff	 = vpfe_streamoff, +	.vidioc_cropcap		 = vpfe_cropcap, +	.vidioc_g_crop		 = vpfe_g_crop, +	.vidioc_s_crop		 = vpfe_s_crop, +	.vidioc_default		 = vpfe_param_handler, +}; + +static struct vpfe_device *vpfe_initialize(void) +{ +	struct vpfe_device *vpfe_dev; + +	/* Default number of buffers should be 3 */ +	if ((numbuffers > 0) && +	    (numbuffers < config_params.min_numbuffers)) +		numbuffers = config_params.min_numbuffers; + +	/* +	 * Set buffer size to min buffers size if invalid buffer size is +	 * given +	 */ +	if (bufsize < config_params.min_bufsize) +		bufsize = config_params.min_bufsize; + +	config_params.numbuffers = numbuffers; + +	if (numbuffers) +		config_params.device_bufsize = bufsize; + +	/* Allocate memory for device objects */ +	vpfe_dev = kzalloc(sizeof(*vpfe_dev), GFP_KERNEL); + +	return vpfe_dev; +} + +/* + * vpfe_probe : This function creates device entries by register + * itself to the V4L2 driver and initializes fields of each + * device objects + */ +static int vpfe_probe(struct platform_device *pdev) +{ +	struct vpfe_subdev_info *sdinfo; +	struct vpfe_config *vpfe_cfg; +	struct resource *res1; +	struct vpfe_device *vpfe_dev; +	struct i2c_adapter *i2c_adap; +	struct video_device *vfd; +	int ret = -ENOMEM, i, j; +	int num_subdevs = 0; + +	/* Get the pointer to the device object */ +	vpfe_dev = vpfe_initialize(); + +	if (!vpfe_dev) { +		v4l2_err(pdev->dev.driver, +			"Failed to allocate memory for vpfe_dev\n"); +		return ret; +	} + +	vpfe_dev->pdev = &pdev->dev; + +	if (NULL == pdev->dev.platform_data) { +		v4l2_err(pdev->dev.driver, "Unable to get vpfe config\n"); +		ret = -ENODEV; +		goto probe_free_dev_mem; +	} + +	vpfe_cfg = pdev->dev.platform_data; +	vpfe_dev->cfg = vpfe_cfg; +	if (NULL == vpfe_cfg->ccdc || +	    NULL == vpfe_cfg->card_name || +	    NULL == vpfe_cfg->sub_devs) { +		v4l2_err(pdev->dev.driver, "null ptr in vpfe_cfg\n"); +		ret = -ENOENT; +		goto probe_free_dev_mem; +	} + +	/* Allocate memory for ccdc configuration */ +	ccdc_cfg = kmalloc(sizeof(struct ccdc_config), GFP_KERNEL); +	if (NULL == ccdc_cfg) { +		v4l2_err(pdev->dev.driver, +			 "Memory allocation failed for ccdc_cfg\n"); +		goto probe_free_dev_mem; +	} + +	mutex_lock(&ccdc_lock); + +	strncpy(ccdc_cfg->name, vpfe_cfg->ccdc, 32); +	/* Get VINT0 irq resource */ +	res1 = platform_get_resource(pdev, IORESOURCE_IRQ, 0); +	if (!res1) { +		v4l2_err(pdev->dev.driver, +			 "Unable to get interrupt for VINT0\n"); +		ret = -ENODEV; +		goto probe_free_ccdc_cfg_mem; +	} +	vpfe_dev->ccdc_irq0 = res1->start; + +	/* Get VINT1 irq resource */ +	res1 = platform_get_resource(pdev, IORESOURCE_IRQ, 1); +	if (!res1) { +		v4l2_err(pdev->dev.driver, +			 "Unable to get interrupt for VINT1\n"); +		ret = -ENODEV; +		goto probe_free_ccdc_cfg_mem; +	} +	vpfe_dev->ccdc_irq1 = res1->start; + +	ret = request_irq(vpfe_dev->ccdc_irq0, vpfe_isr, 0, +			  "vpfe_capture0", vpfe_dev); + +	if (0 != ret) { +		v4l2_err(pdev->dev.driver, "Unable to request interrupt\n"); +		goto probe_free_ccdc_cfg_mem; +	} + +	/* Allocate memory for video device */ +	vfd = video_device_alloc(); +	if (NULL == vfd) { +		ret = -ENOMEM; +		v4l2_err(pdev->dev.driver, "Unable to alloc video device\n"); +		goto probe_out_release_irq; +	} + +	/* Initialize field of video device */ +	vfd->release		= video_device_release; +	vfd->fops		= &vpfe_fops; +	vfd->ioctl_ops		= &vpfe_ioctl_ops; +	vfd->tvnorms		= 0; +	vfd->v4l2_dev 		= &vpfe_dev->v4l2_dev; +	snprintf(vfd->name, sizeof(vfd->name), +		 "%s_V%d.%d.%d", +		 CAPTURE_DRV_NAME, +		 (VPFE_CAPTURE_VERSION_CODE >> 16) & 0xff, +		 (VPFE_CAPTURE_VERSION_CODE >> 8) & 0xff, +		 (VPFE_CAPTURE_VERSION_CODE) & 0xff); +	/* Set video_dev to the video device */ +	vpfe_dev->video_dev	= vfd; + +	ret = v4l2_device_register(&pdev->dev, &vpfe_dev->v4l2_dev); +	if (ret) { +		v4l2_err(pdev->dev.driver, +			"Unable to register v4l2 device.\n"); +		goto probe_out_video_release; +	} +	v4l2_info(&vpfe_dev->v4l2_dev, "v4l2 device registered\n"); +	spin_lock_init(&vpfe_dev->irqlock); +	spin_lock_init(&vpfe_dev->dma_queue_lock); +	mutex_init(&vpfe_dev->lock); + +	/* Initialize field of the device objects */ +	vpfe_dev->numbuffers = config_params.numbuffers; + +	/* register video device */ +	v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, +		"trying to register vpfe device.\n"); +	v4l2_dbg(1, debug, &vpfe_dev->v4l2_dev, +		"video_dev=%x\n", (int)&vpfe_dev->video_dev); +	vpfe_dev->fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; +	set_bit(V4L2_FL_USE_FH_PRIO, &vpfe_dev->video_dev->flags); +	ret = video_register_device(vpfe_dev->video_dev, +				    VFL_TYPE_GRABBER, -1); + +	if (ret) { +		v4l2_err(pdev->dev.driver, +			"Unable to register video device.\n"); +		goto probe_out_v4l2_unregister; +	} + +	v4l2_info(&vpfe_dev->v4l2_dev, "video device registered\n"); +	/* set the driver data in platform device */ +	platform_set_drvdata(pdev, vpfe_dev); +	/* set driver private data */ +	video_set_drvdata(vpfe_dev->video_dev, vpfe_dev); +	i2c_adap = i2c_get_adapter(vpfe_cfg->i2c_adapter_id); +	num_subdevs = vpfe_cfg->num_subdevs; +	vpfe_dev->sd = kmalloc(sizeof(struct v4l2_subdev *) * num_subdevs, +				GFP_KERNEL); +	if (NULL == vpfe_dev->sd) { +		v4l2_err(&vpfe_dev->v4l2_dev, +			"unable to allocate memory for subdevice pointers\n"); +		ret = -ENOMEM; +		goto probe_out_video_unregister; +	} + +	for (i = 0; i < num_subdevs; i++) { +		struct v4l2_input *inps; + +		sdinfo = &vpfe_cfg->sub_devs[i]; + +		/* Load up the subdevice */ +		vpfe_dev->sd[i] = +			v4l2_i2c_new_subdev_board(&vpfe_dev->v4l2_dev, +						  i2c_adap, +						  &sdinfo->board_info, +						  NULL); +		if (vpfe_dev->sd[i]) { +			v4l2_info(&vpfe_dev->v4l2_dev, +				  "v4l2 sub device %s registered\n", +				  sdinfo->name); +			vpfe_dev->sd[i]->grp_id = sdinfo->grp_id; +			/* update tvnorms from the sub devices */ +			for (j = 0; j < sdinfo->num_inputs; j++) { +				inps = &sdinfo->inputs[j]; +				vfd->tvnorms |= inps->std; +			} +		} else { +			v4l2_info(&vpfe_dev->v4l2_dev, +				  "v4l2 sub device %s register fails\n", +				  sdinfo->name); +			goto probe_sd_out; +		} +	} + +	/* set first sub device as current one */ +	vpfe_dev->current_subdev = &vpfe_cfg->sub_devs[0]; +	vpfe_dev->v4l2_dev.ctrl_handler = vpfe_dev->sd[0]->ctrl_handler; + +	/* We have at least one sub device to work with */ +	mutex_unlock(&ccdc_lock); +	return 0; + +probe_sd_out: +	kfree(vpfe_dev->sd); +probe_out_video_unregister: +	video_unregister_device(vpfe_dev->video_dev); +probe_out_v4l2_unregister: +	v4l2_device_unregister(&vpfe_dev->v4l2_dev); +probe_out_video_release: +	if (!video_is_registered(vpfe_dev->video_dev)) +		video_device_release(vpfe_dev->video_dev); +probe_out_release_irq: +	free_irq(vpfe_dev->ccdc_irq0, vpfe_dev); +probe_free_ccdc_cfg_mem: +	kfree(ccdc_cfg); +	mutex_unlock(&ccdc_lock); +probe_free_dev_mem: +	kfree(vpfe_dev); +	return ret; +} + +/* + * vpfe_remove : It un-register device from V4L2 driver + */ +static int vpfe_remove(struct platform_device *pdev) +{ +	struct vpfe_device *vpfe_dev = platform_get_drvdata(pdev); + +	v4l2_info(pdev->dev.driver, "vpfe_remove\n"); + +	free_irq(vpfe_dev->ccdc_irq0, vpfe_dev); +	kfree(vpfe_dev->sd); +	v4l2_device_unregister(&vpfe_dev->v4l2_dev); +	video_unregister_device(vpfe_dev->video_dev); +	kfree(vpfe_dev); +	kfree(ccdc_cfg); +	return 0; +} + +static int vpfe_suspend(struct device *dev) +{ +	return 0; +} + +static int vpfe_resume(struct device *dev) +{ +	return 0; +} + +static const struct dev_pm_ops vpfe_dev_pm_ops = { +	.suspend = vpfe_suspend, +	.resume = vpfe_resume, +}; + +static struct platform_driver vpfe_driver = { +	.driver = { +		.name = CAPTURE_DRV_NAME, +		.owner = THIS_MODULE, +		.pm = &vpfe_dev_pm_ops, +	}, +	.probe = vpfe_probe, +	.remove = vpfe_remove, +}; + +module_platform_driver(vpfe_driver); diff --git a/drivers/media/platform/davinci/vpif.c b/drivers/media/platform/davinci/vpif.c new file mode 100644 index 00000000000..cd08e524838 --- /dev/null +++ b/drivers/media/platform/davinci/vpif.c @@ -0,0 +1,487 @@ +/* + * vpif - Video Port Interface driver + * VPIF is a receiver and transmitter for video data. It has two channels(0, 1) + * that receiveing video byte stream and two channels(2, 3) for video output. + * The hardware supports SDTV, HDTV formats, raw data capture. + * Currently, the driver supports NTSC and PAL standards. + * + * Copyright (C) 2009 Texas Instruments Incorporated - http://www.ti.com/ + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed .as is. WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + */ + +#include <linux/err.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/spinlock.h> +#include <linux/v4l2-dv-timings.h> + +#include "vpif.h" + +MODULE_DESCRIPTION("TI DaVinci Video Port Interface driver"); +MODULE_LICENSE("GPL"); + +#define VPIF_CH0_MAX_MODES	22 +#define VPIF_CH1_MAX_MODES	2 +#define VPIF_CH2_MAX_MODES	15 +#define VPIF_CH3_MAX_MODES	2 + +spinlock_t vpif_lock; + +void __iomem *vpif_base; +EXPORT_SYMBOL_GPL(vpif_base); + +/** + * vpif_ch_params: video standard configuration parameters for vpif + * The table must include all presets from supported subdevices. + */ +const struct vpif_channel_config_params vpif_ch_params[] = { +	/* HDTV formats */ +	{ +		.name = "480p59_94", +		.width = 720, +		.height = 480, +		.frm_fmt = 1, +		.ycmux_mode = 0, +		.eav2sav = 138-8, +		.sav2eav = 720, +		.l1 = 1, +		.l3 = 43, +		.l5 = 523, +		.vsize = 525, +		.capture_format = 0, +		.vbi_supported = 0, +		.hd_sd = 1, +		.dv_timings = V4L2_DV_BT_CEA_720X480P59_94, +	}, +	{ +		.name = "576p50", +		.width = 720, +		.height = 576, +		.frm_fmt = 1, +		.ycmux_mode = 0, +		.eav2sav = 144-8, +		.sav2eav = 720, +		.l1 = 1, +		.l3 = 45, +		.l5 = 621, +		.vsize = 625, +		.capture_format = 0, +		.vbi_supported = 0, +		.hd_sd = 1, +		.dv_timings = V4L2_DV_BT_CEA_720X576P50, +	}, +	{ +		.name = "720p50", +		.width = 1280, +		.height = 720, +		.frm_fmt = 1, +		.ycmux_mode = 0, +		.eav2sav = 700-8, +		.sav2eav = 1280, +		.l1 = 1, +		.l3 = 26, +		.l5 = 746, +		.vsize = 750, +		.capture_format = 0, +		.vbi_supported = 0, +		.hd_sd = 1, +		.dv_timings = V4L2_DV_BT_CEA_1280X720P50, +	}, +	{ +		.name = "720p60", +		.width = 1280, +		.height = 720, +		.frm_fmt = 1, +		.ycmux_mode = 0, +		.eav2sav = 370 - 8, +		.sav2eav = 1280, +		.l1 = 1, +		.l3 = 26, +		.l5 = 746, +		.vsize = 750, +		.capture_format = 0, +		.vbi_supported = 0, +		.hd_sd = 1, +		.dv_timings = V4L2_DV_BT_CEA_1280X720P60, +	}, +	{ +		.name = "1080I50", +		.width = 1920, +		.height = 1080, +		.frm_fmt = 0, +		.ycmux_mode = 0, +		.eav2sav = 720 - 8, +		.sav2eav = 1920, +		.l1 = 1, +		.l3 = 21, +		.l5 = 561, +		.l7 = 563, +		.l9 = 584, +		.l11 = 1124, +		.vsize = 1125, +		.capture_format = 0, +		.vbi_supported = 0, +		.hd_sd = 1, +		.dv_timings = V4L2_DV_BT_CEA_1920X1080I50, +	}, +	{ +		.name = "1080I60", +		.width = 1920, +		.height = 1080, +		.frm_fmt = 0, +		.ycmux_mode = 0, +		.eav2sav = 280 - 8, +		.sav2eav = 1920, +		.l1 = 1, +		.l3 = 21, +		.l5 = 561, +		.l7 = 563, +		.l9 = 584, +		.l11 = 1124, +		.vsize = 1125, +		.capture_format = 0, +		.vbi_supported = 0, +		.hd_sd = 1, +		.dv_timings = V4L2_DV_BT_CEA_1920X1080I60, +	}, +	{ +		.name = "1080p60", +		.width = 1920, +		.height = 1080, +		.frm_fmt = 1, +		.ycmux_mode = 0, +		.eav2sav = 280 - 8, +		.sav2eav = 1920, +		.l1 = 1, +		.l3 = 42, +		.l5 = 1122, +		.vsize = 1125, +		.capture_format = 0, +		.vbi_supported = 0, +		.hd_sd = 1, +		.dv_timings = V4L2_DV_BT_CEA_1920X1080P60, +	}, + +	/* SDTV formats */ +	{ +		.name = "NTSC_M", +		.width = 720, +		.height = 480, +		.frm_fmt = 0, +		.ycmux_mode = 1, +		.eav2sav = 268, +		.sav2eav = 1440, +		.l1 = 1, +		.l3 = 23, +		.l5 = 263, +		.l7 = 266, +		.l9 = 286, +		.l11 = 525, +		.vsize = 525, +		.capture_format = 0, +		.vbi_supported = 1, +		.hd_sd = 0, +		.stdid = V4L2_STD_525_60, +	}, +	{ +		.name = "PAL_BDGHIK", +		.width = 720, +		.height = 576, +		.frm_fmt = 0, +		.ycmux_mode = 1, +		.eav2sav = 280, +		.sav2eav = 1440, +		.l1 = 1, +		.l3 = 23, +		.l5 = 311, +		.l7 = 313, +		.l9 = 336, +		.l11 = 624, +		.vsize = 625, +		.capture_format = 0, +		.vbi_supported = 1, +		.hd_sd = 0, +		.stdid = V4L2_STD_625_50, +	}, +}; +EXPORT_SYMBOL_GPL(vpif_ch_params); + +const unsigned int vpif_ch_params_count = ARRAY_SIZE(vpif_ch_params); +EXPORT_SYMBOL_GPL(vpif_ch_params_count); + +static inline void vpif_wr_bit(u32 reg, u32 bit, u32 val) +{ +	if (val) +		vpif_set_bit(reg, bit); +	else +		vpif_clr_bit(reg, bit); +} + +/* This structure is used to keep track of VPIF size register's offsets */ +struct vpif_registers { +	u32 h_cfg, v_cfg_00, v_cfg_01, v_cfg_02, v_cfg, ch_ctrl; +	u32 line_offset, vanc0_strt, vanc0_size, vanc1_strt; +	u32 vanc1_size, width_mask, len_mask; +	u8 max_modes; +}; + +static const struct vpif_registers vpifregs[VPIF_NUM_CHANNELS] = { +	/* Channel0 */ +	{ +		VPIF_CH0_H_CFG, VPIF_CH0_V_CFG_00, VPIF_CH0_V_CFG_01, +		VPIF_CH0_V_CFG_02, VPIF_CH0_V_CFG_03, VPIF_CH0_CTRL, +		VPIF_CH0_IMG_ADD_OFST, 0, 0, 0, 0, 0x1FFF, 0xFFF, +		VPIF_CH0_MAX_MODES, +	}, +	/* Channel1 */ +	{ +		VPIF_CH1_H_CFG, VPIF_CH1_V_CFG_00, VPIF_CH1_V_CFG_01, +		VPIF_CH1_V_CFG_02, VPIF_CH1_V_CFG_03, VPIF_CH1_CTRL, +		VPIF_CH1_IMG_ADD_OFST, 0, 0, 0, 0, 0x1FFF, 0xFFF, +		VPIF_CH1_MAX_MODES, +	}, +	/* Channel2 */ +	{ +		VPIF_CH2_H_CFG, VPIF_CH2_V_CFG_00, VPIF_CH2_V_CFG_01, +		VPIF_CH2_V_CFG_02, VPIF_CH2_V_CFG_03, VPIF_CH2_CTRL, +		VPIF_CH2_IMG_ADD_OFST, VPIF_CH2_VANC0_STRT, VPIF_CH2_VANC0_SIZE, +		VPIF_CH2_VANC1_STRT, VPIF_CH2_VANC1_SIZE, 0x7FF, 0x7FF, +		VPIF_CH2_MAX_MODES +	}, +	/* Channel3 */ +	{ +		VPIF_CH3_H_CFG, VPIF_CH3_V_CFG_00, VPIF_CH3_V_CFG_01, +		VPIF_CH3_V_CFG_02, VPIF_CH3_V_CFG_03, VPIF_CH3_CTRL, +		VPIF_CH3_IMG_ADD_OFST, VPIF_CH3_VANC0_STRT, VPIF_CH3_VANC0_SIZE, +		VPIF_CH3_VANC1_STRT, VPIF_CH3_VANC1_SIZE, 0x7FF, 0x7FF, +		VPIF_CH3_MAX_MODES +	}, +}; + +/* vpif_set_mode_info: + * This function is used to set horizontal and vertical config parameters + * As per the standard in the channel, configure the values of L1, L3, + * L5, L7  L9, L11 in VPIF Register , also write width and height + */ +static void vpif_set_mode_info(const struct vpif_channel_config_params *config, +				u8 channel_id, u8 config_channel_id) +{ +	u32 value; + +	value = (config->eav2sav & vpifregs[config_channel_id].width_mask); +	value <<= VPIF_CH_LEN_SHIFT; +	value |= (config->sav2eav & vpifregs[config_channel_id].width_mask); +	regw(value, vpifregs[channel_id].h_cfg); + +	value = (config->l1 & vpifregs[config_channel_id].len_mask); +	value <<= VPIF_CH_LEN_SHIFT; +	value |= (config->l3 & vpifregs[config_channel_id].len_mask); +	regw(value, vpifregs[channel_id].v_cfg_00); + +	value = (config->l5 & vpifregs[config_channel_id].len_mask); +	value <<= VPIF_CH_LEN_SHIFT; +	value |= (config->l7 & vpifregs[config_channel_id].len_mask); +	regw(value, vpifregs[channel_id].v_cfg_01); + +	value = (config->l9 & vpifregs[config_channel_id].len_mask); +	value <<= VPIF_CH_LEN_SHIFT; +	value |= (config->l11 & vpifregs[config_channel_id].len_mask); +	regw(value, vpifregs[channel_id].v_cfg_02); + +	value = (config->vsize & vpifregs[config_channel_id].len_mask); +	regw(value, vpifregs[channel_id].v_cfg); +} + +/* config_vpif_params + * Function to set the parameters of a channel + * Mainly modifies the channel ciontrol register + * It sets frame format, yc mux mode + */ +static void config_vpif_params(struct vpif_params *vpifparams, +				u8 channel_id, u8 found) +{ +	const struct vpif_channel_config_params *config = &vpifparams->std_info; +	u32 value, ch_nip, reg; +	u8 start, end; +	int i; + +	start = channel_id; +	end = channel_id + found; + +	for (i = start; i < end; i++) { +		reg = vpifregs[i].ch_ctrl; +		if (channel_id < 2) +			ch_nip = VPIF_CAPTURE_CH_NIP; +		else +			ch_nip = VPIF_DISPLAY_CH_NIP; + +		vpif_wr_bit(reg, ch_nip, config->frm_fmt); +		vpif_wr_bit(reg, VPIF_CH_YC_MUX_BIT, config->ycmux_mode); +		vpif_wr_bit(reg, VPIF_CH_INPUT_FIELD_FRAME_BIT, +					vpifparams->video_params.storage_mode); + +		/* Set raster scanning SDR Format */ +		vpif_clr_bit(reg, VPIF_CH_SDR_FMT_BIT); +		vpif_wr_bit(reg, VPIF_CH_DATA_MODE_BIT, config->capture_format); + +		if (channel_id > 1)	/* Set the Pixel enable bit */ +			vpif_set_bit(reg, VPIF_DISPLAY_PIX_EN_BIT); +		else if (config->capture_format) { +			/* Set the polarity of various pins */ +			vpif_wr_bit(reg, VPIF_CH_FID_POLARITY_BIT, +					vpifparams->iface.fid_pol); +			vpif_wr_bit(reg, VPIF_CH_V_VALID_POLARITY_BIT, +					vpifparams->iface.vd_pol); +			vpif_wr_bit(reg, VPIF_CH_H_VALID_POLARITY_BIT, +					vpifparams->iface.hd_pol); + +			value = regr(reg); +			/* Set data width */ +			value &= ~(0x3u << +					VPIF_CH_DATA_WIDTH_BIT); +			value |= ((vpifparams->params.data_sz) << +						     VPIF_CH_DATA_WIDTH_BIT); +			regw(value, reg); +		} + +		/* Write the pitch in the driver */ +		regw((vpifparams->video_params.hpitch), +						vpifregs[i].line_offset); +	} +} + +/* vpif_set_video_params + * This function is used to set video parameters in VPIF register + */ +int vpif_set_video_params(struct vpif_params *vpifparams, u8 channel_id) +{ +	const struct vpif_channel_config_params *config = &vpifparams->std_info; +	int found = 1; + +	vpif_set_mode_info(config, channel_id, channel_id); +	if (!config->ycmux_mode) { +		/* YC are on separate channels (HDTV formats) */ +		vpif_set_mode_info(config, channel_id + 1, channel_id); +		found = 2; +	} + +	config_vpif_params(vpifparams, channel_id, found); + +	regw(0x80, VPIF_REQ_SIZE); +	regw(0x01, VPIF_EMULATION_CTRL); + +	return found; +} +EXPORT_SYMBOL(vpif_set_video_params); + +void vpif_set_vbi_display_params(struct vpif_vbi_params *vbiparams, +				u8 channel_id) +{ +	u32 value; + +	value = 0x3F8 & (vbiparams->hstart0); +	value |= 0x3FFFFFF & ((vbiparams->vstart0) << 16); +	regw(value, vpifregs[channel_id].vanc0_strt); + +	value = 0x3F8 & (vbiparams->hstart1); +	value |= 0x3FFFFFF & ((vbiparams->vstart1) << 16); +	regw(value, vpifregs[channel_id].vanc1_strt); + +	value = 0x3F8 & (vbiparams->hsize0); +	value |= 0x3FFFFFF & ((vbiparams->vsize0) << 16); +	regw(value, vpifregs[channel_id].vanc0_size); + +	value = 0x3F8 & (vbiparams->hsize1); +	value |= 0x3FFFFFF & ((vbiparams->vsize1) << 16); +	regw(value, vpifregs[channel_id].vanc1_size); + +} +EXPORT_SYMBOL(vpif_set_vbi_display_params); + +int vpif_channel_getfid(u8 channel_id) +{ +	return (regr(vpifregs[channel_id].ch_ctrl) & VPIF_CH_FID_MASK) +					>> VPIF_CH_FID_SHIFT; +} +EXPORT_SYMBOL(vpif_channel_getfid); + +static int vpif_probe(struct platform_device *pdev) +{ +	static struct resource	*res; + +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +	vpif_base = devm_ioremap_resource(&pdev->dev, res); +	if (IS_ERR(vpif_base)) +		return PTR_ERR(vpif_base); + +	pm_runtime_enable(&pdev->dev); +	pm_runtime_get(&pdev->dev); + +	spin_lock_init(&vpif_lock); +	dev_info(&pdev->dev, "vpif probe success\n"); +	return 0; +} + +static int vpif_remove(struct platform_device *pdev) +{ +	pm_runtime_disable(&pdev->dev); +	return 0; +} + +#ifdef CONFIG_PM +static int vpif_suspend(struct device *dev) +{ +	pm_runtime_put(dev); +	return 0; +} + +static int vpif_resume(struct device *dev) +{ +	pm_runtime_get(dev); +	return 0; +} + +static const struct dev_pm_ops vpif_pm = { +	.suspend        = vpif_suspend, +	.resume         = vpif_resume, +}; + +#define vpif_pm_ops (&vpif_pm) +#else +#define vpif_pm_ops NULL +#endif + +static struct platform_driver vpif_driver = { +	.driver = { +		.name	= "vpif", +		.owner = THIS_MODULE, +		.pm	= vpif_pm_ops, +	}, +	.remove = vpif_remove, +	.probe = vpif_probe, +}; + +static void vpif_exit(void) +{ +	platform_driver_unregister(&vpif_driver); +} + +static int __init vpif_init(void) +{ +	return platform_driver_register(&vpif_driver); +} +subsys_initcall(vpif_init); +module_exit(vpif_exit); + diff --git a/drivers/media/platform/davinci/vpif.h b/drivers/media/platform/davinci/vpif.h new file mode 100644 index 00000000000..9956e678869 --- /dev/null +++ b/drivers/media/platform/davinci/vpif.h @@ -0,0 +1,688 @@ +/* + * VPIF header file + * + * Copyright (C) 2009 Texas Instruments Incorporated - http://www.ti.com/ + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed .as is. WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + */ + +#ifndef VPIF_H +#define VPIF_H + +#include <linux/io.h> +#include <linux/videodev2.h> +#include <media/davinci/vpif_types.h> + +/* Maximum channel allowed */ +#define VPIF_NUM_CHANNELS		(4) +#define VPIF_CAPTURE_NUM_CHANNELS	(2) +#define VPIF_DISPLAY_NUM_CHANNELS	(2) + +/* Macros to read/write registers */ +extern void __iomem *vpif_base; +extern spinlock_t vpif_lock; + +#define regr(reg)               readl((reg) + vpif_base) +#define regw(value, reg)        writel(value, (reg + vpif_base)) + +/* Register Address Offsets */ +#define VPIF_PID			(0x0000) +#define VPIF_CH0_CTRL			(0x0004) +#define VPIF_CH1_CTRL			(0x0008) +#define VPIF_CH2_CTRL			(0x000C) +#define VPIF_CH3_CTRL			(0x0010) + +#define VPIF_INTEN			(0x0020) +#define VPIF_INTEN_SET			(0x0024) +#define VPIF_INTEN_CLR			(0x0028) +#define VPIF_STATUS			(0x002C) +#define VPIF_STATUS_CLR			(0x0030) +#define VPIF_EMULATION_CTRL		(0x0034) +#define VPIF_REQ_SIZE			(0x0038) + +#define VPIF_CH0_TOP_STRT_ADD_LUMA	(0x0040) +#define VPIF_CH0_BTM_STRT_ADD_LUMA	(0x0044) +#define VPIF_CH0_TOP_STRT_ADD_CHROMA	(0x0048) +#define VPIF_CH0_BTM_STRT_ADD_CHROMA	(0x004c) +#define VPIF_CH0_TOP_STRT_ADD_HANC	(0x0050) +#define VPIF_CH0_BTM_STRT_ADD_HANC	(0x0054) +#define VPIF_CH0_TOP_STRT_ADD_VANC	(0x0058) +#define VPIF_CH0_BTM_STRT_ADD_VANC	(0x005c) +#define VPIF_CH0_SP_CFG			(0x0060) +#define VPIF_CH0_IMG_ADD_OFST		(0x0064) +#define VPIF_CH0_HANC_ADD_OFST		(0x0068) +#define VPIF_CH0_H_CFG			(0x006c) +#define VPIF_CH0_V_CFG_00		(0x0070) +#define VPIF_CH0_V_CFG_01		(0x0074) +#define VPIF_CH0_V_CFG_02		(0x0078) +#define VPIF_CH0_V_CFG_03		(0x007c) + +#define VPIF_CH1_TOP_STRT_ADD_LUMA	(0x0080) +#define VPIF_CH1_BTM_STRT_ADD_LUMA	(0x0084) +#define VPIF_CH1_TOP_STRT_ADD_CHROMA	(0x0088) +#define VPIF_CH1_BTM_STRT_ADD_CHROMA	(0x008c) +#define VPIF_CH1_TOP_STRT_ADD_HANC	(0x0090) +#define VPIF_CH1_BTM_STRT_ADD_HANC	(0x0094) +#define VPIF_CH1_TOP_STRT_ADD_VANC	(0x0098) +#define VPIF_CH1_BTM_STRT_ADD_VANC	(0x009c) +#define VPIF_CH1_SP_CFG			(0x00a0) +#define VPIF_CH1_IMG_ADD_OFST		(0x00a4) +#define VPIF_CH1_HANC_ADD_OFST		(0x00a8) +#define VPIF_CH1_H_CFG			(0x00ac) +#define VPIF_CH1_V_CFG_00		(0x00b0) +#define VPIF_CH1_V_CFG_01		(0x00b4) +#define VPIF_CH1_V_CFG_02		(0x00b8) +#define VPIF_CH1_V_CFG_03		(0x00bc) + +#define VPIF_CH2_TOP_STRT_ADD_LUMA	(0x00c0) +#define VPIF_CH2_BTM_STRT_ADD_LUMA	(0x00c4) +#define VPIF_CH2_TOP_STRT_ADD_CHROMA	(0x00c8) +#define VPIF_CH2_BTM_STRT_ADD_CHROMA	(0x00cc) +#define VPIF_CH2_TOP_STRT_ADD_HANC	(0x00d0) +#define VPIF_CH2_BTM_STRT_ADD_HANC	(0x00d4) +#define VPIF_CH2_TOP_STRT_ADD_VANC	(0x00d8) +#define VPIF_CH2_BTM_STRT_ADD_VANC	(0x00dc) +#define VPIF_CH2_SP_CFG			(0x00e0) +#define VPIF_CH2_IMG_ADD_OFST		(0x00e4) +#define VPIF_CH2_HANC_ADD_OFST		(0x00e8) +#define VPIF_CH2_H_CFG			(0x00ec) +#define VPIF_CH2_V_CFG_00		(0x00f0) +#define VPIF_CH2_V_CFG_01		(0x00f4) +#define VPIF_CH2_V_CFG_02		(0x00f8) +#define VPIF_CH2_V_CFG_03		(0x00fc) +#define VPIF_CH2_HANC0_STRT		(0x0100) +#define VPIF_CH2_HANC0_SIZE		(0x0104) +#define VPIF_CH2_HANC1_STRT		(0x0108) +#define VPIF_CH2_HANC1_SIZE		(0x010c) +#define VPIF_CH2_VANC0_STRT		(0x0110) +#define VPIF_CH2_VANC0_SIZE		(0x0114) +#define VPIF_CH2_VANC1_STRT		(0x0118) +#define VPIF_CH2_VANC1_SIZE		(0x011c) + +#define VPIF_CH3_TOP_STRT_ADD_LUMA	(0x0140) +#define VPIF_CH3_BTM_STRT_ADD_LUMA	(0x0144) +#define VPIF_CH3_TOP_STRT_ADD_CHROMA	(0x0148) +#define VPIF_CH3_BTM_STRT_ADD_CHROMA	(0x014c) +#define VPIF_CH3_TOP_STRT_ADD_HANC	(0x0150) +#define VPIF_CH3_BTM_STRT_ADD_HANC	(0x0154) +#define VPIF_CH3_TOP_STRT_ADD_VANC	(0x0158) +#define VPIF_CH3_BTM_STRT_ADD_VANC	(0x015c) +#define VPIF_CH3_SP_CFG			(0x0160) +#define VPIF_CH3_IMG_ADD_OFST		(0x0164) +#define VPIF_CH3_HANC_ADD_OFST		(0x0168) +#define VPIF_CH3_H_CFG			(0x016c) +#define VPIF_CH3_V_CFG_00		(0x0170) +#define VPIF_CH3_V_CFG_01		(0x0174) +#define VPIF_CH3_V_CFG_02		(0x0178) +#define VPIF_CH3_V_CFG_03		(0x017c) +#define VPIF_CH3_HANC0_STRT		(0x0180) +#define VPIF_CH3_HANC0_SIZE		(0x0184) +#define VPIF_CH3_HANC1_STRT		(0x0188) +#define VPIF_CH3_HANC1_SIZE		(0x018c) +#define VPIF_CH3_VANC0_STRT		(0x0190) +#define VPIF_CH3_VANC0_SIZE		(0x0194) +#define VPIF_CH3_VANC1_STRT		(0x0198) +#define VPIF_CH3_VANC1_SIZE		(0x019c) + +#define VPIF_IODFT_CTRL			(0x01c0) + +/* Functions for bit Manipulation */ +static inline void vpif_set_bit(u32 reg, u32 bit) +{ +	regw((regr(reg)) | (0x01 << bit), reg); +} + +static inline void vpif_clr_bit(u32 reg, u32 bit) +{ +	regw(((regr(reg)) & ~(0x01 << bit)), reg); +} + +/* Macro for Generating mask */ +#ifdef GENERATE_MASK +#undef GENERATE_MASK +#endif + +#define GENERATE_MASK(bits, pos) \ +		((((0xFFFFFFFF) << (32 - bits)) >> (32 - bits)) << pos) + +/* Bit positions in the channel control registers */ +#define VPIF_CH_DATA_MODE_BIT	(2) +#define VPIF_CH_YC_MUX_BIT	(3) +#define VPIF_CH_SDR_FMT_BIT	(4) +#define VPIF_CH_HANC_EN_BIT	(8) +#define VPIF_CH_VANC_EN_BIT	(9) + +#define VPIF_CAPTURE_CH_NIP	(10) +#define VPIF_DISPLAY_CH_NIP	(11) + +#define VPIF_DISPLAY_PIX_EN_BIT	(10) + +#define VPIF_CH_INPUT_FIELD_FRAME_BIT	(12) + +#define VPIF_CH_FID_POLARITY_BIT	(15) +#define VPIF_CH_V_VALID_POLARITY_BIT	(14) +#define VPIF_CH_H_VALID_POLARITY_BIT	(13) +#define VPIF_CH_DATA_WIDTH_BIT		(28) + +#define VPIF_CH_CLK_EDGE_CTRL_BIT	(31) + +/* Mask various length */ +#define VPIF_CH_EAVSAV_MASK	GENERATE_MASK(13, 0) +#define VPIF_CH_LEN_MASK	GENERATE_MASK(12, 0) +#define VPIF_CH_WIDTH_MASK	GENERATE_MASK(13, 0) +#define VPIF_CH_LEN_SHIFT	(16) + +/* VPIF masks for registers */ +#define VPIF_REQ_SIZE_MASK	(0x1ff) + +/* bit posotion of interrupt vpif_ch_intr register */ +#define VPIF_INTEN_FRAME_CH0	(0x00000001) +#define VPIF_INTEN_FRAME_CH1	(0x00000002) +#define VPIF_INTEN_FRAME_CH2	(0x00000004) +#define VPIF_INTEN_FRAME_CH3	(0x00000008) + +/* bit position of clock and channel enable in vpif_chn_ctrl register */ + +#define VPIF_CH0_CLK_EN		(0x00000002) +#define VPIF_CH0_EN		(0x00000001) +#define VPIF_CH1_CLK_EN		(0x00000002) +#define VPIF_CH1_EN		(0x00000001) +#define VPIF_CH2_CLK_EN		(0x00000002) +#define VPIF_CH2_EN		(0x00000001) +#define VPIF_CH3_CLK_EN		(0x00000002) +#define VPIF_CH3_EN		(0x00000001) +#define VPIF_CH_CLK_EN		(0x00000002) +#define VPIF_CH_EN		(0x00000001) + +#define VPIF_INT_TOP	(0x00) +#define VPIF_INT_BOTTOM	(0x01) +#define VPIF_INT_BOTH	(0x02) + +#define VPIF_CH0_INT_CTRL_SHIFT	(6) +#define VPIF_CH1_INT_CTRL_SHIFT	(6) +#define VPIF_CH2_INT_CTRL_SHIFT	(6) +#define VPIF_CH3_INT_CTRL_SHIFT	(6) +#define VPIF_CH_INT_CTRL_SHIFT	(6) + +#define VPIF_CH2_CLIP_ANC_EN	14 +#define VPIF_CH2_CLIP_ACTIVE_EN	13 + +#define VPIF_CH3_CLIP_ANC_EN	14 +#define VPIF_CH3_CLIP_ACTIVE_EN	13 + +/* enabled interrupt on both the fields on vpid_ch0_ctrl register */ +#define channel0_intr_assert()	(regw((regr(VPIF_CH0_CTRL)|\ +	(VPIF_INT_BOTH << VPIF_CH0_INT_CTRL_SHIFT)), VPIF_CH0_CTRL)) + +/* enabled interrupt on both the fields on vpid_ch1_ctrl register */ +#define channel1_intr_assert()	(regw((regr(VPIF_CH1_CTRL)|\ +	(VPIF_INT_BOTH << VPIF_CH1_INT_CTRL_SHIFT)), VPIF_CH1_CTRL)) + +/* enabled interrupt on both the fields on vpid_ch0_ctrl register */ +#define channel2_intr_assert() 	(regw((regr(VPIF_CH2_CTRL)|\ +	(VPIF_INT_BOTH << VPIF_CH2_INT_CTRL_SHIFT)), VPIF_CH2_CTRL)) + +/* enabled interrupt on both the fields on vpid_ch1_ctrl register */ +#define channel3_intr_assert() 	(regw((regr(VPIF_CH3_CTRL)|\ +	(VPIF_INT_BOTH << VPIF_CH3_INT_CTRL_SHIFT)), VPIF_CH3_CTRL)) + +#define VPIF_CH_FID_MASK	(0x20) +#define VPIF_CH_FID_SHIFT	(5) + +#define VPIF_NTSC_VBI_START_FIELD0	(1) +#define VPIF_NTSC_VBI_START_FIELD1	(263) +#define VPIF_PAL_VBI_START_FIELD0	(624) +#define VPIF_PAL_VBI_START_FIELD1	(311) + +#define VPIF_NTSC_HBI_START_FIELD0	(1) +#define VPIF_NTSC_HBI_START_FIELD1	(263) +#define VPIF_PAL_HBI_START_FIELD0	(624) +#define VPIF_PAL_HBI_START_FIELD1	(311) + +#define VPIF_NTSC_VBI_COUNT_FIELD0	(20) +#define VPIF_NTSC_VBI_COUNT_FIELD1	(19) +#define VPIF_PAL_VBI_COUNT_FIELD0	(24) +#define VPIF_PAL_VBI_COUNT_FIELD1	(25) + +#define VPIF_NTSC_HBI_COUNT_FIELD0	(263) +#define VPIF_NTSC_HBI_COUNT_FIELD1	(262) +#define VPIF_PAL_HBI_COUNT_FIELD0	(312) +#define VPIF_PAL_HBI_COUNT_FIELD1	(313) + +#define VPIF_NTSC_VBI_SAMPLES_PER_LINE	(720) +#define VPIF_PAL_VBI_SAMPLES_PER_LINE	(720) +#define VPIF_NTSC_HBI_SAMPLES_PER_LINE	(268) +#define VPIF_PAL_HBI_SAMPLES_PER_LINE	(280) + +#define VPIF_CH_VANC_EN			(0x20) +#define VPIF_DMA_REQ_SIZE		(0x080) +#define VPIF_EMULATION_DISABLE		(0x01) + +extern u8 irq_vpif_capture_channel[VPIF_NUM_CHANNELS]; + +/* inline function to enable/disable channel0 */ +static inline void enable_channel0(int enable) +{ +	if (enable) +		regw((regr(VPIF_CH0_CTRL) | (VPIF_CH0_EN)), VPIF_CH0_CTRL); +	else +		regw((regr(VPIF_CH0_CTRL) & (~VPIF_CH0_EN)), VPIF_CH0_CTRL); +} + +/* inline function to enable/disable channel1 */ +static inline void enable_channel1(int enable) +{ +	if (enable) +		regw((regr(VPIF_CH1_CTRL) | (VPIF_CH1_EN)), VPIF_CH1_CTRL); +	else +		regw((regr(VPIF_CH1_CTRL) & (~VPIF_CH1_EN)), VPIF_CH1_CTRL); +} + +/* inline function to enable interrupt for channel0 */ +static inline void channel0_intr_enable(int enable) +{ +	unsigned long flags; + +	spin_lock_irqsave(&vpif_lock, flags); + +	if (enable) { +		regw((regr(VPIF_INTEN) | 0x10), VPIF_INTEN); +		regw((regr(VPIF_INTEN_SET) | 0x10), VPIF_INTEN_SET); + +		regw((regr(VPIF_INTEN) | VPIF_INTEN_FRAME_CH0), VPIF_INTEN); +		regw((regr(VPIF_INTEN_SET) | VPIF_INTEN_FRAME_CH0), +							VPIF_INTEN_SET); +	} else { +		regw((regr(VPIF_INTEN) & (~VPIF_INTEN_FRAME_CH0)), VPIF_INTEN); +		regw((regr(VPIF_INTEN_SET) | VPIF_INTEN_FRAME_CH0), +							VPIF_INTEN_SET); +	} +	spin_unlock_irqrestore(&vpif_lock, flags); +} + +/* inline function to enable interrupt for channel1 */ +static inline void channel1_intr_enable(int enable) +{ +	unsigned long flags; + +	spin_lock_irqsave(&vpif_lock, flags); + +	if (enable) { +		regw((regr(VPIF_INTEN) | 0x10), VPIF_INTEN); +		regw((regr(VPIF_INTEN_SET) | 0x10), VPIF_INTEN_SET); + +		regw((regr(VPIF_INTEN) | VPIF_INTEN_FRAME_CH1), VPIF_INTEN); +		regw((regr(VPIF_INTEN_SET) | VPIF_INTEN_FRAME_CH1), +							VPIF_INTEN_SET); +	} else { +		regw((regr(VPIF_INTEN) & (~VPIF_INTEN_FRAME_CH1)), VPIF_INTEN); +		regw((regr(VPIF_INTEN_SET) | VPIF_INTEN_FRAME_CH1), +							VPIF_INTEN_SET); +	} +	spin_unlock_irqrestore(&vpif_lock, flags); +} + +/* inline function to set buffer addresses in case of Y/C non mux mode */ +static inline void ch0_set_videobuf_addr_yc_nmux(unsigned long top_strt_luma, +						 unsigned long btm_strt_luma, +						 unsigned long top_strt_chroma, +						 unsigned long btm_strt_chroma) +{ +	regw(top_strt_luma, VPIF_CH0_TOP_STRT_ADD_LUMA); +	regw(btm_strt_luma, VPIF_CH0_BTM_STRT_ADD_LUMA); +	regw(top_strt_chroma, VPIF_CH1_TOP_STRT_ADD_CHROMA); +	regw(btm_strt_chroma, VPIF_CH1_BTM_STRT_ADD_CHROMA); +} + +/* inline function to set buffer addresses in VPIF registers for video data */ +static inline void ch0_set_videobuf_addr(unsigned long top_strt_luma, +					 unsigned long btm_strt_luma, +					 unsigned long top_strt_chroma, +					 unsigned long btm_strt_chroma) +{ +	regw(top_strt_luma, VPIF_CH0_TOP_STRT_ADD_LUMA); +	regw(btm_strt_luma, VPIF_CH0_BTM_STRT_ADD_LUMA); +	regw(top_strt_chroma, VPIF_CH0_TOP_STRT_ADD_CHROMA); +	regw(btm_strt_chroma, VPIF_CH0_BTM_STRT_ADD_CHROMA); +} + +static inline void ch1_set_videobuf_addr(unsigned long top_strt_luma, +					 unsigned long btm_strt_luma, +					 unsigned long top_strt_chroma, +					 unsigned long btm_strt_chroma) +{ + +	regw(top_strt_luma, VPIF_CH1_TOP_STRT_ADD_LUMA); +	regw(btm_strt_luma, VPIF_CH1_BTM_STRT_ADD_LUMA); +	regw(top_strt_chroma, VPIF_CH1_TOP_STRT_ADD_CHROMA); +	regw(btm_strt_chroma, VPIF_CH1_BTM_STRT_ADD_CHROMA); +} + +static inline void ch0_set_vbi_addr(unsigned long top_vbi, +	unsigned long btm_vbi, unsigned long a, unsigned long b) +{ +	regw(top_vbi, VPIF_CH0_TOP_STRT_ADD_VANC); +	regw(btm_vbi, VPIF_CH0_BTM_STRT_ADD_VANC); +} + +static inline void ch0_set_hbi_addr(unsigned long top_vbi, +	unsigned long btm_vbi, unsigned long a, unsigned long b) +{ +	regw(top_vbi, VPIF_CH0_TOP_STRT_ADD_HANC); +	regw(btm_vbi, VPIF_CH0_BTM_STRT_ADD_HANC); +} + +static inline void ch1_set_vbi_addr(unsigned long top_vbi, +	unsigned long btm_vbi, unsigned long a, unsigned long b) +{ +	regw(top_vbi, VPIF_CH1_TOP_STRT_ADD_VANC); +	regw(btm_vbi, VPIF_CH1_BTM_STRT_ADD_VANC); +} + +static inline void ch1_set_hbi_addr(unsigned long top_vbi, +	unsigned long btm_vbi, unsigned long a, unsigned long b) +{ +	regw(top_vbi, VPIF_CH1_TOP_STRT_ADD_HANC); +	regw(btm_vbi, VPIF_CH1_BTM_STRT_ADD_HANC); +} + +/* Inline function to enable raw vbi in the given channel */ +static inline void disable_raw_feature(u8 channel_id, u8 index) +{ +	u32 ctrl_reg; +	if (0 == channel_id) +		ctrl_reg = VPIF_CH0_CTRL; +	else +		ctrl_reg = VPIF_CH1_CTRL; + +	if (1 == index) +		vpif_clr_bit(ctrl_reg, VPIF_CH_VANC_EN_BIT); +	else +		vpif_clr_bit(ctrl_reg, VPIF_CH_HANC_EN_BIT); +} + +static inline void enable_raw_feature(u8 channel_id, u8 index) +{ +	u32 ctrl_reg; +	if (0 == channel_id) +		ctrl_reg = VPIF_CH0_CTRL; +	else +		ctrl_reg = VPIF_CH1_CTRL; + +	if (1 == index) +		vpif_set_bit(ctrl_reg, VPIF_CH_VANC_EN_BIT); +	else +		vpif_set_bit(ctrl_reg, VPIF_CH_HANC_EN_BIT); +} + +/* inline function to enable/disable channel2 */ +static inline void enable_channel2(int enable) +{ +	if (enable) { +		regw((regr(VPIF_CH2_CTRL) | (VPIF_CH2_CLK_EN)), VPIF_CH2_CTRL); +		regw((regr(VPIF_CH2_CTRL) | (VPIF_CH2_EN)), VPIF_CH2_CTRL); +	} else { +		regw((regr(VPIF_CH2_CTRL) & (~VPIF_CH2_CLK_EN)), VPIF_CH2_CTRL); +		regw((regr(VPIF_CH2_CTRL) & (~VPIF_CH2_EN)), VPIF_CH2_CTRL); +	} +} + +/* inline function to enable/disable channel3 */ +static inline void enable_channel3(int enable) +{ +	if (enable) { +		regw((regr(VPIF_CH3_CTRL) | (VPIF_CH3_CLK_EN)), VPIF_CH3_CTRL); +		regw((regr(VPIF_CH3_CTRL) | (VPIF_CH3_EN)), VPIF_CH3_CTRL); +	} else { +		regw((regr(VPIF_CH3_CTRL) & (~VPIF_CH3_CLK_EN)), VPIF_CH3_CTRL); +		regw((regr(VPIF_CH3_CTRL) & (~VPIF_CH3_EN)), VPIF_CH3_CTRL); +	} +} + +/* inline function to enable interrupt for channel2 */ +static inline void channel2_intr_enable(int enable) +{ +	unsigned long flags; + +	spin_lock_irqsave(&vpif_lock, flags); + +	if (enable) { +		regw((regr(VPIF_INTEN) | 0x10), VPIF_INTEN); +		regw((regr(VPIF_INTEN_SET) | 0x10), VPIF_INTEN_SET); +		regw((regr(VPIF_INTEN) | VPIF_INTEN_FRAME_CH2), VPIF_INTEN); +		regw((regr(VPIF_INTEN_SET) | VPIF_INTEN_FRAME_CH2), +							VPIF_INTEN_SET); +	} else { +		regw((regr(VPIF_INTEN) & (~VPIF_INTEN_FRAME_CH2)), VPIF_INTEN); +		regw((regr(VPIF_INTEN_SET) | VPIF_INTEN_FRAME_CH2), +							VPIF_INTEN_SET); +	} +	spin_unlock_irqrestore(&vpif_lock, flags); +} + +/* inline function to enable interrupt for channel3 */ +static inline void channel3_intr_enable(int enable) +{ +	unsigned long flags; + +	spin_lock_irqsave(&vpif_lock, flags); + +	if (enable) { +		regw((regr(VPIF_INTEN) | 0x10), VPIF_INTEN); +		regw((regr(VPIF_INTEN_SET) | 0x10), VPIF_INTEN_SET); + +		regw((regr(VPIF_INTEN) | VPIF_INTEN_FRAME_CH3), VPIF_INTEN); +		regw((regr(VPIF_INTEN_SET) | VPIF_INTEN_FRAME_CH3), +							VPIF_INTEN_SET); +	} else { +		regw((regr(VPIF_INTEN) & (~VPIF_INTEN_FRAME_CH3)), VPIF_INTEN); +		regw((regr(VPIF_INTEN_SET) | VPIF_INTEN_FRAME_CH3), +							VPIF_INTEN_SET); +	} +	spin_unlock_irqrestore(&vpif_lock, flags); +} + +/* inline function to enable raw vbi data for channel2 */ +static inline void channel2_raw_enable(int enable, u8 index) +{ +	u32 mask; + +	if (1 == index) +		mask = VPIF_CH_VANC_EN_BIT; +	else +		mask = VPIF_CH_HANC_EN_BIT; + +	if (enable) +		vpif_set_bit(VPIF_CH2_CTRL, mask); +	else +		vpif_clr_bit(VPIF_CH2_CTRL, mask); +} + +/* inline function to enable raw vbi data for channel3*/ +static inline void channel3_raw_enable(int enable, u8 index) +{ +	u32 mask; + +	if (1 == index) +		mask = VPIF_CH_VANC_EN_BIT; +	else +		mask = VPIF_CH_HANC_EN_BIT; + +	if (enable) +		vpif_set_bit(VPIF_CH3_CTRL, mask); +	else +		vpif_clr_bit(VPIF_CH3_CTRL, mask); +} + +/* function to enable clipping (for both active and blanking regions) on ch 2 */ +static inline void channel2_clipping_enable(int enable) +{ +	if (enable) { +		vpif_set_bit(VPIF_CH2_CTRL, VPIF_CH2_CLIP_ANC_EN); +		vpif_set_bit(VPIF_CH2_CTRL, VPIF_CH2_CLIP_ACTIVE_EN); +	} else { +		vpif_clr_bit(VPIF_CH2_CTRL, VPIF_CH2_CLIP_ANC_EN); +		vpif_clr_bit(VPIF_CH2_CTRL, VPIF_CH2_CLIP_ACTIVE_EN); +	} +} + +/* function to enable clipping (for both active and blanking regions) on ch 3 */ +static inline void channel3_clipping_enable(int enable) +{ +	if (enable) { +		vpif_set_bit(VPIF_CH3_CTRL, VPIF_CH3_CLIP_ANC_EN); +		vpif_set_bit(VPIF_CH3_CTRL, VPIF_CH3_CLIP_ACTIVE_EN); +	} else { +		vpif_clr_bit(VPIF_CH3_CTRL, VPIF_CH3_CLIP_ANC_EN); +		vpif_clr_bit(VPIF_CH3_CTRL, VPIF_CH3_CLIP_ACTIVE_EN); +	} +} + +/* inline function to set buffer addresses in case of Y/C non mux mode */ +static inline void ch2_set_videobuf_addr_yc_nmux(unsigned long top_strt_luma, +						 unsigned long btm_strt_luma, +						 unsigned long top_strt_chroma, +						 unsigned long btm_strt_chroma) +{ +	regw(top_strt_luma, VPIF_CH2_TOP_STRT_ADD_LUMA); +	regw(btm_strt_luma, VPIF_CH2_BTM_STRT_ADD_LUMA); +	regw(top_strt_chroma, VPIF_CH3_TOP_STRT_ADD_CHROMA); +	regw(btm_strt_chroma, VPIF_CH3_BTM_STRT_ADD_CHROMA); +} + +/* inline function to set buffer addresses in VPIF registers for video data */ +static inline void ch2_set_videobuf_addr(unsigned long top_strt_luma, +					 unsigned long btm_strt_luma, +					 unsigned long top_strt_chroma, +					 unsigned long btm_strt_chroma) +{ +	regw(top_strt_luma, VPIF_CH2_TOP_STRT_ADD_LUMA); +	regw(btm_strt_luma, VPIF_CH2_BTM_STRT_ADD_LUMA); +	regw(top_strt_chroma, VPIF_CH2_TOP_STRT_ADD_CHROMA); +	regw(btm_strt_chroma, VPIF_CH2_BTM_STRT_ADD_CHROMA); +} + +static inline void ch3_set_videobuf_addr(unsigned long top_strt_luma, +					 unsigned long btm_strt_luma, +					 unsigned long top_strt_chroma, +					 unsigned long btm_strt_chroma) +{ +	regw(top_strt_luma, VPIF_CH3_TOP_STRT_ADD_LUMA); +	regw(btm_strt_luma, VPIF_CH3_BTM_STRT_ADD_LUMA); +	regw(top_strt_chroma, VPIF_CH3_TOP_STRT_ADD_CHROMA); +	regw(btm_strt_chroma, VPIF_CH3_BTM_STRT_ADD_CHROMA); +} + +/* inline function to set buffer addresses in VPIF registers for vbi data */ +static inline void ch2_set_vbi_addr(unsigned long top_strt_luma, +					 unsigned long btm_strt_luma, +					 unsigned long top_strt_chroma, +					 unsigned long btm_strt_chroma) +{ +	regw(top_strt_luma, VPIF_CH2_TOP_STRT_ADD_VANC); +	regw(btm_strt_luma, VPIF_CH2_BTM_STRT_ADD_VANC); +} + +static inline void ch3_set_vbi_addr(unsigned long top_strt_luma, +					 unsigned long btm_strt_luma, +					 unsigned long top_strt_chroma, +					 unsigned long btm_strt_chroma) +{ +	regw(top_strt_luma, VPIF_CH3_TOP_STRT_ADD_VANC); +	regw(btm_strt_luma, VPIF_CH3_BTM_STRT_ADD_VANC); +} + +static inline int vpif_intr_status(int channel) +{ +	int status = 0; +	int mask; + +	if (channel < 0 || channel > 3) +		return 0; + +	mask = 1 << channel; +	status = regr(VPIF_STATUS) & mask; +	regw(status, VPIF_STATUS_CLR); + +	return status; +} + +#define VPIF_MAX_NAME	(30) + +/* This structure will store size parameters as per the mode selected by user */ +struct vpif_channel_config_params { +	char name[VPIF_MAX_NAME];	/* Name of the mode */ +	u16 width;			/* Indicates width of the image */ +	u16 height;			/* Indicates height of the image */ +	u8 frm_fmt;			/* Interlaced (0) or progressive (1) */ +	u8 ycmux_mode;			/* This mode requires one (0) or two (1) +					   channels */ +	u16 eav2sav;			/* length of eav 2 sav */ +	u16 sav2eav;			/* length of sav 2 eav */ +	u16 l1, l3, l5, l7, l9, l11;	/* Other parameter configurations */ +	u16 vsize;			/* Vertical size of the image */ +	u8 capture_format;		/* Indicates whether capture format +					 * is in BT or in CCD/CMOS */ +	u8  vbi_supported;		/* Indicates whether this mode +					 * supports capturing vbi or not */ +	u8 hd_sd;			/* HDTV (1) or SDTV (0) format */ +	v4l2_std_id stdid;		/* SDTV format */ +	struct v4l2_dv_timings dv_timings;	/* HDTV format */ +}; + +extern const unsigned int vpif_ch_params_count; +extern const struct vpif_channel_config_params vpif_ch_params[]; + +struct vpif_video_params; +struct vpif_params; +struct vpif_vbi_params; + +int vpif_set_video_params(struct vpif_params *vpifparams, u8 channel_id); +void vpif_set_vbi_display_params(struct vpif_vbi_params *vbiparams, +							u8 channel_id); +int vpif_channel_getfid(u8 channel_id); + +enum data_size { +	_8BITS = 0, +	_10BITS, +	_12BITS, +}; + +/* Structure for vpif parameters for raw vbi data */ +struct vpif_vbi_params { +	__u32 hstart0;  /* Horizontal start of raw vbi data for first field */ +	__u32 vstart0;  /* Vertical start of raw vbi data for first field */ +	__u32 hsize0;   /* Horizontal size of raw vbi data for first field */ +	__u32 vsize0;   /* Vertical size of raw vbi data for first field */ +	__u32 hstart1;  /* Horizontal start of raw vbi data for second field */ +	__u32 vstart1;  /* Vertical start of raw vbi data for second field */ +	__u32 hsize1;   /* Horizontal size of raw vbi data for second field */ +	__u32 vsize1;   /* Vertical size of raw vbi data for second field */ +}; + +/* structure for vpif parameters */ +struct vpif_video_params { +	__u8 storage_mode;	/* Indicates field or frame mode */ +	unsigned long hpitch; +	v4l2_std_id stdid; +}; + +struct vpif_params { +	struct vpif_interface iface; +	struct vpif_video_params video_params; +	struct vpif_channel_config_params std_info; +	union param { +		struct vpif_vbi_params	vbi_params; +		enum data_size data_sz; +	} params; +}; + +#endif				/* End of #ifndef VPIF_H */ + diff --git a/drivers/media/platform/davinci/vpif_capture.c b/drivers/media/platform/davinci/vpif_capture.c new file mode 100644 index 00000000000..1e4ec697fb1 --- /dev/null +++ b/drivers/media/platform/davinci/vpif_capture.c @@ -0,0 +1,1809 @@ +/* + * Copyright (C) 2009 Texas Instruments Inc + * Copyright (C) 2014 Lad, Prabhakar <prabhakar.csengg@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA + * + * TODO : add support for VBI & HBI data service + *	  add static buffer allocation + */ + +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#include <media/v4l2-ioctl.h> + +#include "vpif.h" +#include "vpif_capture.h" + +MODULE_DESCRIPTION("TI DaVinci VPIF Capture driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(VPIF_CAPTURE_VERSION); + +#define vpif_err(fmt, arg...)	v4l2_err(&vpif_obj.v4l2_dev, fmt, ## arg) +#define vpif_dbg(level, debug, fmt, arg...)	\ +		v4l2_dbg(level, debug, &vpif_obj.v4l2_dev, fmt, ## arg) + +static int debug = 1; +static u32 ch0_numbuffers = 3; +static u32 ch1_numbuffers = 3; +static u32 ch0_bufsize = 1920 * 1080 * 2; +static u32 ch1_bufsize = 720 * 576 * 2; + +module_param(debug, int, 0644); +module_param(ch0_numbuffers, uint, S_IRUGO); +module_param(ch1_numbuffers, uint, S_IRUGO); +module_param(ch0_bufsize, uint, S_IRUGO); +module_param(ch1_bufsize, uint, S_IRUGO); + +MODULE_PARM_DESC(debug, "Debug level 0-1"); +MODULE_PARM_DESC(ch2_numbuffers, "Channel0 buffer count (default:3)"); +MODULE_PARM_DESC(ch3_numbuffers, "Channel1 buffer count (default:3)"); +MODULE_PARM_DESC(ch2_bufsize, "Channel0 buffer size (default:1920 x 1080 x 2)"); +MODULE_PARM_DESC(ch3_bufsize, "Channel1 buffer size (default:720 x 576 x 2)"); + +static struct vpif_config_params config_params = { +	.min_numbuffers = 3, +	.numbuffers[0] = 3, +	.numbuffers[1] = 3, +	.min_bufsize[0] = 720 * 480 * 2, +	.min_bufsize[1] = 720 * 480 * 2, +	.channel_bufsize[0] = 1920 * 1080 * 2, +	.channel_bufsize[1] = 720 * 576 * 2, +}; + +#define VPIF_DRIVER_NAME	"vpif_capture" + +/* global variables */ +static struct vpif_device vpif_obj = { {NULL} }; +static struct device *vpif_dev; +static void vpif_calculate_offsets(struct channel_obj *ch); +static void vpif_config_addr(struct channel_obj *ch, int muxmode); + +static u8 channel_first_int[VPIF_NUMBER_OF_OBJECTS][2] = { {1, 1} }; + +/* Is set to 1 in case of SDTV formats, 2 in case of HDTV formats. */ +static int ycmux_mode; + +static inline struct vpif_cap_buffer *to_vpif_buffer(struct vb2_buffer *vb) +{ +	return container_of(vb, struct vpif_cap_buffer, vb); +} + +/** + * vpif_buffer_prepare :  callback function for buffer prepare + * @vb: ptr to vb2_buffer + * + * This is the callback function for buffer prepare when vb2_qbuf() + * function is called. The buffer is prepared and user space virtual address + * or user address is converted into  physical address + */ +static int vpif_buffer_prepare(struct vb2_buffer *vb) +{ +	struct vb2_queue *q = vb->vb2_queue; +	struct channel_obj *ch = vb2_get_drv_priv(q); +	struct common_obj *common; +	unsigned long addr; + +	vpif_dbg(2, debug, "vpif_buffer_prepare\n"); + +	common = &ch->common[VPIF_VIDEO_INDEX]; + +	vb2_set_plane_payload(vb, 0, common->fmt.fmt.pix.sizeimage); +	if (vb2_get_plane_payload(vb, 0) > vb2_plane_size(vb, 0)) +		return -EINVAL; + +	vb->v4l2_buf.field = common->fmt.fmt.pix.field; + +	addr = vb2_dma_contig_plane_dma_addr(vb, 0); +	if (!IS_ALIGNED((addr + common->ytop_off), 8) || +		!IS_ALIGNED((addr + common->ybtm_off), 8) || +		!IS_ALIGNED((addr + common->ctop_off), 8) || +		!IS_ALIGNED((addr + common->cbtm_off), 8)) { +		vpif_dbg(1, debug, "offset is not aligned\n"); +		return -EINVAL; +	} + +	return 0; +} + +/** + * vpif_buffer_queue_setup : Callback function for buffer setup. + * @vq: vb2_queue ptr + * @fmt: v4l2 format + * @nbuffers: ptr to number of buffers requested by application + * @nplanes:: contains number of distinct video planes needed to hold a frame + * @sizes[]: contains the size (in bytes) of each plane. + * @alloc_ctxs: ptr to allocation context + * + * This callback function is called when reqbuf() is called to adjust + * the buffer count and buffer size + */ +static int vpif_buffer_queue_setup(struct vb2_queue *vq, +				const struct v4l2_format *fmt, +				unsigned int *nbuffers, unsigned int *nplanes, +				unsigned int sizes[], void *alloc_ctxs[]) +{ +	struct channel_obj *ch = vb2_get_drv_priv(vq); +	struct common_obj *common; + +	common = &ch->common[VPIF_VIDEO_INDEX]; + +	vpif_dbg(2, debug, "vpif_buffer_setup\n"); + +	if (fmt && fmt->fmt.pix.sizeimage < common->fmt.fmt.pix.sizeimage) +		return -EINVAL; + +	if (vq->num_buffers + *nbuffers < 3) +		*nbuffers = 3 - vq->num_buffers; + +	*nplanes = 1; +	sizes[0] = fmt ? fmt->fmt.pix.sizeimage : common->fmt.fmt.pix.sizeimage; +	alloc_ctxs[0] = common->alloc_ctx; + +	/* Calculate the offset for Y and C data in the buffer */ +	vpif_calculate_offsets(ch); + +	return 0; +} + +/** + * vpif_buffer_queue : Callback function to add buffer to DMA queue + * @vb: ptr to vb2_buffer + */ +static void vpif_buffer_queue(struct vb2_buffer *vb) +{ +	struct channel_obj *ch = vb2_get_drv_priv(vb->vb2_queue); +	struct vpif_cap_buffer *buf = to_vpif_buffer(vb); +	struct common_obj *common; +	unsigned long flags; + +	common = &ch->common[VPIF_VIDEO_INDEX]; + +	vpif_dbg(2, debug, "vpif_buffer_queue\n"); + +	spin_lock_irqsave(&common->irqlock, flags); +	/* add the buffer to the DMA queue */ +	list_add_tail(&buf->list, &common->dma_queue); +	spin_unlock_irqrestore(&common->irqlock, flags); +} + +/** + * vpif_start_streaming : Starts the DMA engine for streaming + * @vb: ptr to vb2_buffer + * @count: number of buffers + */ +static int vpif_start_streaming(struct vb2_queue *vq, unsigned int count) +{ +	struct vpif_capture_config *vpif_config_data = +					vpif_dev->platform_data; +	struct channel_obj *ch = vb2_get_drv_priv(vq); +	struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; +	struct vpif_params *vpif = &ch->vpifparams; +	struct vpif_cap_buffer *buf, *tmp; +	unsigned long addr, flags; +	int ret; + +	spin_lock_irqsave(&common->irqlock, flags); + +	/* Initialize field_id */ +	ch->field_id = 0; + +	/* configure 1 or 2 channel mode */ +	if (vpif_config_data->setup_input_channel_mode) { +		ret = vpif_config_data-> +			setup_input_channel_mode(vpif->std_info.ycmux_mode); +		if (ret < 0) { +			vpif_dbg(1, debug, "can't set vpif channel mode\n"); +			goto err; +		} +	} + +	ret = v4l2_subdev_call(ch->sd, video, s_stream, 1); +	if (ret && ret != -ENOIOCTLCMD && ret != -ENODEV) { +		vpif_dbg(1, debug, "stream on failed in subdev\n"); +		goto err; +	} + +	/* Call vpif_set_params function to set the parameters and addresses */ +	ret = vpif_set_video_params(vpif, ch->channel_id); +	if (ret < 0) { +		vpif_dbg(1, debug, "can't set video params\n"); +		goto err; +	} + +	ycmux_mode = ret; +	vpif_config_addr(ch, ret); + +	/* Get the next frame from the buffer queue */ +	common->cur_frm = common->next_frm = list_entry(common->dma_queue.next, +				    struct vpif_cap_buffer, list); +	/* Remove buffer from the buffer queue */ +	list_del(&common->cur_frm->list); +	spin_unlock_irqrestore(&common->irqlock, flags); +	/* Mark state of the current frame to active */ +	common->cur_frm->vb.state = VB2_BUF_STATE_ACTIVE; + +	addr = vb2_dma_contig_plane_dma_addr(&common->cur_frm->vb, 0); + +	common->set_addr(addr + common->ytop_off, +			 addr + common->ybtm_off, +			 addr + common->ctop_off, +			 addr + common->cbtm_off); + +	/** +	 * Set interrupt for both the fields in VPIF Register enable channel in +	 * VPIF register +	 */ +	channel_first_int[VPIF_VIDEO_INDEX][ch->channel_id] = 1; +	if (VPIF_CHANNEL0_VIDEO == ch->channel_id) { +		channel0_intr_assert(); +		channel0_intr_enable(1); +		enable_channel0(1); +	} +	if (VPIF_CHANNEL1_VIDEO == ch->channel_id || +		ycmux_mode == 2) { +		channel1_intr_assert(); +		channel1_intr_enable(1); +		enable_channel1(1); +	} + +	return 0; + +err: +	list_for_each_entry_safe(buf, tmp, &common->dma_queue, list) { +		list_del(&buf->list); +		vb2_buffer_done(&buf->vb, VB2_BUF_STATE_QUEUED); +	} +	spin_unlock_irqrestore(&common->irqlock, flags); + +	return ret; +} + +/** + * vpif_stop_streaming : Stop the DMA engine + * @vq: ptr to vb2_queue + * + * This callback stops the DMA engine and any remaining buffers + * in the DMA queue are released. + */ +static void vpif_stop_streaming(struct vb2_queue *vq) +{ +	struct channel_obj *ch = vb2_get_drv_priv(vq); +	struct common_obj *common; +	unsigned long flags; +	int ret; + +	common = &ch->common[VPIF_VIDEO_INDEX]; + +	/* Disable channel as per its device type and channel id */ +	if (VPIF_CHANNEL0_VIDEO == ch->channel_id) { +		enable_channel0(0); +		channel0_intr_enable(0); +	} +	if (VPIF_CHANNEL1_VIDEO == ch->channel_id || +		ycmux_mode == 2) { +		enable_channel1(0); +		channel1_intr_enable(0); +	} + +	ycmux_mode = 0; + +	ret = v4l2_subdev_call(ch->sd, video, s_stream, 0); +	if (ret && ret != -ENOIOCTLCMD && ret != -ENODEV) +		vpif_dbg(1, debug, "stream off failed in subdev\n"); + +	/* release all active buffers */ +	spin_lock_irqsave(&common->irqlock, flags); +	if (common->cur_frm == common->next_frm) { +		vb2_buffer_done(&common->cur_frm->vb, VB2_BUF_STATE_ERROR); +	} else { +		if (common->cur_frm != NULL) +			vb2_buffer_done(&common->cur_frm->vb, +					VB2_BUF_STATE_ERROR); +		if (common->next_frm != NULL) +			vb2_buffer_done(&common->next_frm->vb, +					VB2_BUF_STATE_ERROR); +	} + +	while (!list_empty(&common->dma_queue)) { +		common->next_frm = list_entry(common->dma_queue.next, +						struct vpif_cap_buffer, list); +		list_del(&common->next_frm->list); +		vb2_buffer_done(&common->next_frm->vb, VB2_BUF_STATE_ERROR); +	} +	spin_unlock_irqrestore(&common->irqlock, flags); +} + +static struct vb2_ops video_qops = { +	.queue_setup		= vpif_buffer_queue_setup, +	.buf_prepare		= vpif_buffer_prepare, +	.start_streaming	= vpif_start_streaming, +	.stop_streaming		= vpif_stop_streaming, +	.buf_queue		= vpif_buffer_queue, +}; + +/** + * vpif_process_buffer_complete: process a completed buffer + * @common: ptr to common channel object + * + * This function time stamp the buffer and mark it as DONE. It also + * wake up any process waiting on the QUEUE and set the next buffer + * as current + */ +static void vpif_process_buffer_complete(struct common_obj *common) +{ +	v4l2_get_timestamp(&common->cur_frm->vb.v4l2_buf.timestamp); +	vb2_buffer_done(&common->cur_frm->vb, +					    VB2_BUF_STATE_DONE); +	/* Make curFrm pointing to nextFrm */ +	common->cur_frm = common->next_frm; +} + +/** + * vpif_schedule_next_buffer: set next buffer address for capture + * @common : ptr to common channel object + * + * This function will get next buffer from the dma queue and + * set the buffer address in the vpif register for capture. + * the buffer is marked active + */ +static void vpif_schedule_next_buffer(struct common_obj *common) +{ +	unsigned long addr = 0; + +	spin_lock(&common->irqlock); +	common->next_frm = list_entry(common->dma_queue.next, +				     struct vpif_cap_buffer, list); +	/* Remove that buffer from the buffer queue */ +	list_del(&common->next_frm->list); +	spin_unlock(&common->irqlock); +	common->next_frm->vb.state = VB2_BUF_STATE_ACTIVE; +	addr = vb2_dma_contig_plane_dma_addr(&common->next_frm->vb, 0); + +	/* Set top and bottom field addresses in VPIF registers */ +	common->set_addr(addr + common->ytop_off, +			 addr + common->ybtm_off, +			 addr + common->ctop_off, +			 addr + common->cbtm_off); +} + +/** + * vpif_channel_isr : ISR handler for vpif capture + * @irq: irq number + * @dev_id: dev_id ptr + * + * It changes status of the captured buffer, takes next buffer from the queue + * and sets its address in VPIF registers + */ +static irqreturn_t vpif_channel_isr(int irq, void *dev_id) +{ +	struct vpif_device *dev = &vpif_obj; +	struct common_obj *common; +	struct channel_obj *ch; +	enum v4l2_field field; +	int channel_id = 0; +	int fid = -1, i; + +	channel_id = *(int *)(dev_id); +	if (!vpif_intr_status(channel_id)) +		return IRQ_NONE; + +	ch = dev->dev[channel_id]; + +	field = ch->common[VPIF_VIDEO_INDEX].fmt.fmt.pix.field; + +	for (i = 0; i < VPIF_NUMBER_OF_OBJECTS; i++) { +		common = &ch->common[i]; +		/* skip If streaming is not started in this channel */ +		/* Check the field format */ +		if (1 == ch->vpifparams.std_info.frm_fmt) { +			/* Progressive mode */ +			spin_lock(&common->irqlock); +			if (list_empty(&common->dma_queue)) { +				spin_unlock(&common->irqlock); +				continue; +			} +			spin_unlock(&common->irqlock); + +			if (!channel_first_int[i][channel_id]) +				vpif_process_buffer_complete(common); + +			channel_first_int[i][channel_id] = 0; + +			vpif_schedule_next_buffer(common); + + +			channel_first_int[i][channel_id] = 0; +		} else { +			/** +			 * Interlaced mode. If it is first interrupt, ignore +			 * it +			 */ +			if (channel_first_int[i][channel_id]) { +				channel_first_int[i][channel_id] = 0; +				continue; +			} +			if (0 == i) { +				ch->field_id ^= 1; +				/* Get field id from VPIF registers */ +				fid = vpif_channel_getfid(ch->channel_id); +				if (fid != ch->field_id) { +					/** +					 * If field id does not match stored +					 * field id, make them in sync +					 */ +					if (0 == fid) +						ch->field_id = fid; +					return IRQ_HANDLED; +				} +			} +			/* device field id and local field id are in sync */ +			if (0 == fid) { +				/* this is even field */ +				if (common->cur_frm == common->next_frm) +					continue; + +				/* mark the current buffer as done */ +				vpif_process_buffer_complete(common); +			} else if (1 == fid) { +				/* odd field */ +				spin_lock(&common->irqlock); +				if (list_empty(&common->dma_queue) || +				    (common->cur_frm != common->next_frm)) { +					spin_unlock(&common->irqlock); +					continue; +				} +				spin_unlock(&common->irqlock); + +				vpif_schedule_next_buffer(common); +			} +		} +	} +	return IRQ_HANDLED; +} + +/** + * vpif_update_std_info() - update standard related info + * @ch: ptr to channel object + * + * For a given standard selected by application, update values + * in the device data structures + */ +static int vpif_update_std_info(struct channel_obj *ch) +{ +	struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; +	struct vpif_params *vpifparams = &ch->vpifparams; +	const struct vpif_channel_config_params *config; +	struct vpif_channel_config_params *std_info = &vpifparams->std_info; +	struct video_obj *vid_ch = &ch->video; +	int index; + +	vpif_dbg(2, debug, "vpif_update_std_info\n"); + +	for (index = 0; index < vpif_ch_params_count; index++) { +		config = &vpif_ch_params[index]; +		if (config->hd_sd == 0) { +			vpif_dbg(2, debug, "SD format\n"); +			if (config->stdid & vid_ch->stdid) { +				memcpy(std_info, config, sizeof(*config)); +				break; +			} +		} else { +			vpif_dbg(2, debug, "HD format\n"); +			if (!memcmp(&config->dv_timings, &vid_ch->dv_timings, +				sizeof(vid_ch->dv_timings))) { +				memcpy(std_info, config, sizeof(*config)); +				break; +			} +		} +	} + +	/* standard not found */ +	if (index == vpif_ch_params_count) +		return -EINVAL; + +	common->fmt.fmt.pix.width = std_info->width; +	common->width = std_info->width; +	common->fmt.fmt.pix.height = std_info->height; +	common->height = std_info->height; +	common->fmt.fmt.pix.bytesperline = std_info->width; +	vpifparams->video_params.hpitch = std_info->width; +	vpifparams->video_params.storage_mode = std_info->frm_fmt; + +	return 0; +} + +/** + * vpif_calculate_offsets : This function calculates buffers offsets + * @ch : ptr to channel object + * + * This function calculates buffer offsets for Y and C in the top and + * bottom field + */ +static void vpif_calculate_offsets(struct channel_obj *ch) +{ +	unsigned int hpitch, vpitch, sizeimage; +	struct video_obj *vid_ch = &(ch->video); +	struct vpif_params *vpifparams = &ch->vpifparams; +	struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; +	enum v4l2_field field = common->fmt.fmt.pix.field; + +	vpif_dbg(2, debug, "vpif_calculate_offsets\n"); + +	if (V4L2_FIELD_ANY == field) { +		if (vpifparams->std_info.frm_fmt) +			vid_ch->buf_field = V4L2_FIELD_NONE; +		else +			vid_ch->buf_field = V4L2_FIELD_INTERLACED; +	} else +		vid_ch->buf_field = common->fmt.fmt.pix.field; + +	sizeimage = common->fmt.fmt.pix.sizeimage; + +	hpitch = common->fmt.fmt.pix.bytesperline; +	vpitch = sizeimage / (hpitch * 2); + +	if ((V4L2_FIELD_NONE == vid_ch->buf_field) || +	    (V4L2_FIELD_INTERLACED == vid_ch->buf_field)) { +		/* Calculate offsets for Y top, Y Bottom, C top and C Bottom */ +		common->ytop_off = 0; +		common->ybtm_off = hpitch; +		common->ctop_off = sizeimage / 2; +		common->cbtm_off = sizeimage / 2 + hpitch; +	} else if (V4L2_FIELD_SEQ_TB == vid_ch->buf_field) { +		/* Calculate offsets for Y top, Y Bottom, C top and C Bottom */ +		common->ytop_off = 0; +		common->ybtm_off = sizeimage / 4; +		common->ctop_off = sizeimage / 2; +		common->cbtm_off = common->ctop_off + sizeimage / 4; +	} else if (V4L2_FIELD_SEQ_BT == vid_ch->buf_field) { +		/* Calculate offsets for Y top, Y Bottom, C top and C Bottom */ +		common->ybtm_off = 0; +		common->ytop_off = sizeimage / 4; +		common->cbtm_off = sizeimage / 2; +		common->ctop_off = common->cbtm_off + sizeimage / 4; +	} +	if ((V4L2_FIELD_NONE == vid_ch->buf_field) || +	    (V4L2_FIELD_INTERLACED == vid_ch->buf_field)) +		vpifparams->video_params.storage_mode = 1; +	else +		vpifparams->video_params.storage_mode = 0; + +	if (1 == vpifparams->std_info.frm_fmt) +		vpifparams->video_params.hpitch = +		    common->fmt.fmt.pix.bytesperline; +	else { +		if ((field == V4L2_FIELD_ANY) +		    || (field == V4L2_FIELD_INTERLACED)) +			vpifparams->video_params.hpitch = +			    common->fmt.fmt.pix.bytesperline * 2; +		else +			vpifparams->video_params.hpitch = +			    common->fmt.fmt.pix.bytesperline; +	} + +	ch->vpifparams.video_params.stdid = vpifparams->std_info.stdid; +} + +/** + * vpif_config_format: configure default frame format in the device + * ch : ptr to channel object + */ +static void vpif_config_format(struct channel_obj *ch) +{ +	struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + +	vpif_dbg(2, debug, "vpif_config_format\n"); + +	common->fmt.fmt.pix.field = V4L2_FIELD_ANY; +	common->fmt.fmt.pix.sizeimage +	    = config_params.channel_bufsize[ch->channel_id]; + +	if (ch->vpifparams.iface.if_type == VPIF_IF_RAW_BAYER) +		common->fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_SBGGR8; +	else +		common->fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUV422P; +	common->fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; +} + +/** + * vpif_get_default_field() - Get default field type based on interface + * @vpif_params - ptr to vpif params + */ +static inline enum v4l2_field vpif_get_default_field( +				struct vpif_interface *iface) +{ +	return (iface->if_type == VPIF_IF_RAW_BAYER) ? V4L2_FIELD_NONE : +						V4L2_FIELD_INTERLACED; +} + +/** + * vpif_check_format()  - check given pixel format for compatibility + * @ch - channel  ptr + * @pixfmt - Given pixel format + * @update - update the values as per hardware requirement + * + * Check the application pixel format for S_FMT and update the input + * values as per hardware limits for TRY_FMT. The default pixel and + * field format is selected based on interface type. + */ +static int vpif_check_format(struct channel_obj *ch, +			     struct v4l2_pix_format *pixfmt, +			     int update) +{ +	struct common_obj *common = &(ch->common[VPIF_VIDEO_INDEX]); +	struct vpif_params *vpif_params = &ch->vpifparams; +	enum v4l2_field field = pixfmt->field; +	u32 sizeimage, hpitch, vpitch; +	int ret = -EINVAL; + +	vpif_dbg(2, debug, "vpif_check_format\n"); +	/** +	 * first check for the pixel format. If if_type is Raw bayer, +	 * only V4L2_PIX_FMT_SBGGR8 format is supported. Otherwise only +	 * V4L2_PIX_FMT_YUV422P is supported +	 */ +	if (vpif_params->iface.if_type == VPIF_IF_RAW_BAYER) { +		if (pixfmt->pixelformat != V4L2_PIX_FMT_SBGGR8) { +			if (!update) { +				vpif_dbg(2, debug, "invalid pix format\n"); +				goto exit; +			} +			pixfmt->pixelformat = V4L2_PIX_FMT_SBGGR8; +		} +	} else { +		if (pixfmt->pixelformat != V4L2_PIX_FMT_YUV422P) { +			if (!update) { +				vpif_dbg(2, debug, "invalid pixel format\n"); +				goto exit; +			} +			pixfmt->pixelformat = V4L2_PIX_FMT_YUV422P; +		} +	} + +	if (!(VPIF_VALID_FIELD(field))) { +		if (!update) { +			vpif_dbg(2, debug, "invalid field format\n"); +			goto exit; +		} +		/** +		 * By default use FIELD_NONE for RAW Bayer capture +		 * and FIELD_INTERLACED for other interfaces +		 */ +		field = vpif_get_default_field(&vpif_params->iface); +	} else if (field == V4L2_FIELD_ANY) +		/* unsupported field. Use default */ +		field = vpif_get_default_field(&vpif_params->iface); + +	/* validate the hpitch */ +	hpitch = pixfmt->bytesperline; +	if (hpitch < vpif_params->std_info.width) { +		if (!update) { +			vpif_dbg(2, debug, "invalid hpitch\n"); +			goto exit; +		} +		hpitch = vpif_params->std_info.width; +	} + +	sizeimage = pixfmt->sizeimage; + +	vpitch = sizeimage / (hpitch * 2); + +	/* validate the vpitch */ +	if (vpitch < vpif_params->std_info.height) { +		if (!update) { +			vpif_dbg(2, debug, "Invalid vpitch\n"); +			goto exit; +		} +		vpitch = vpif_params->std_info.height; +	} + +	/* Check for 8 byte alignment */ +	if (!ALIGN(hpitch, 8)) { +		if (!update) { +			vpif_dbg(2, debug, "invalid pitch alignment\n"); +			goto exit; +		} +		/* adjust to next 8 byte boundary */ +		hpitch = (((hpitch + 7) / 8) * 8); +	} +	/* if update is set, modify the bytesperline and sizeimage */ +	if (update) { +		pixfmt->bytesperline = hpitch; +		pixfmt->sizeimage = hpitch * vpitch * 2; +	} +	/** +	 * Image width and height is always based on current standard width and +	 * height +	 */ +	pixfmt->width = common->fmt.fmt.pix.width; +	pixfmt->height = common->fmt.fmt.pix.height; +	return 0; +exit: +	return ret; +} + +/** + * vpif_config_addr() - function to configure buffer address in vpif + * @ch - channel ptr + * @muxmode - channel mux mode + */ +static void vpif_config_addr(struct channel_obj *ch, int muxmode) +{ +	struct common_obj *common; + +	vpif_dbg(2, debug, "vpif_config_addr\n"); + +	common = &(ch->common[VPIF_VIDEO_INDEX]); + +	if (VPIF_CHANNEL1_VIDEO == ch->channel_id) +		common->set_addr = ch1_set_videobuf_addr; +	else if (2 == muxmode) +		common->set_addr = ch0_set_videobuf_addr_yc_nmux; +	else +		common->set_addr = ch0_set_videobuf_addr; +} + +/** + * vpif_input_to_subdev() - Maps input to sub device + * @vpif_cfg - global config ptr + * @chan_cfg - channel config ptr + * @input_index - Given input index from application + * + * lookup the sub device information for a given input index. + * we report all the inputs to application. inputs table also + * has sub device name for the each input + */ +static int vpif_input_to_subdev( +		struct vpif_capture_config *vpif_cfg, +		struct vpif_capture_chan_config *chan_cfg, +		int input_index) +{ +	struct vpif_subdev_info *subdev_info; +	const char *subdev_name; +	int i; + +	vpif_dbg(2, debug, "vpif_input_to_subdev\n"); + +	subdev_name = chan_cfg->inputs[input_index].subdev_name; +	if (subdev_name == NULL) +		return -1; + +	/* loop through the sub device list to get the sub device info */ +	for (i = 0; i < vpif_cfg->subdev_count; i++) { +		subdev_info = &vpif_cfg->subdev_info[i]; +		if (!strcmp(subdev_info->name, subdev_name)) +			return i; +	} +	return -1; +} + +/** + * vpif_set_input() - Select an input + * @vpif_cfg - global config ptr + * @ch - channel + * @_index - Given input index from application + * + * Select the given input. + */ +static int vpif_set_input( +		struct vpif_capture_config *vpif_cfg, +		struct channel_obj *ch, +		int index) +{ +	struct vpif_capture_chan_config *chan_cfg = +			&vpif_cfg->chan_config[ch->channel_id]; +	struct vpif_subdev_info *subdev_info = NULL; +	struct v4l2_subdev *sd = NULL; +	u32 input = 0, output = 0; +	int sd_index; +	int ret; + +	sd_index = vpif_input_to_subdev(vpif_cfg, chan_cfg, index); +	if (sd_index >= 0) { +		sd = vpif_obj.sd[sd_index]; +		subdev_info = &vpif_cfg->subdev_info[sd_index]; +	} + +	/* first setup input path from sub device to vpif */ +	if (sd && vpif_cfg->setup_input_path) { +		ret = vpif_cfg->setup_input_path(ch->channel_id, +				       subdev_info->name); +		if (ret < 0) { +			vpif_dbg(1, debug, "couldn't setup input path for the" \ +			" sub device %s, for input index %d\n", +			subdev_info->name, index); +			return ret; +		} +	} + +	if (sd) { +		input = chan_cfg->inputs[index].input_route; +		output = chan_cfg->inputs[index].output_route; +		ret = v4l2_subdev_call(sd, video, s_routing, +				input, output, 0); +		if (ret < 0 && ret != -ENOIOCTLCMD) { +			vpif_dbg(1, debug, "Failed to set input\n"); +			return ret; +		} +	} +	ch->input_idx = index; +	ch->sd = sd; +	/* copy interface parameters to vpif */ +	ch->vpifparams.iface = chan_cfg->vpif_if; + +	/* update tvnorms from the sub device input info */ +	ch->video_dev->tvnorms = chan_cfg->inputs[index].input.std; +	return 0; +} + +/** + * vpif_querystd() - querystd handler + * @file: file ptr + * @priv: file handle + * @std_id: ptr to std id + * + * This function is called to detect standard at the selected input + */ +static int vpif_querystd(struct file *file, void *priv, v4l2_std_id *std_id) +{ +	struct video_device *vdev = video_devdata(file); +	struct channel_obj *ch = video_get_drvdata(vdev); +	int ret = 0; + +	vpif_dbg(2, debug, "vpif_querystd\n"); + +	/* Call querystd function of decoder device */ +	ret = v4l2_subdev_call(ch->sd, video, querystd, std_id); + +	if (ret == -ENOIOCTLCMD || ret == -ENODEV) +		return -ENODATA; +	if (ret) { +		vpif_dbg(1, debug, "Failed to query standard for sub devices\n"); +		return ret; +	} + +	return 0; +} + +/** + * vpif_g_std() - get STD handler + * @file: file ptr + * @priv: file handle + * @std_id: ptr to std id + */ +static int vpif_g_std(struct file *file, void *priv, v4l2_std_id *std) +{ +	struct vpif_capture_config *config = vpif_dev->platform_data; +	struct video_device *vdev = video_devdata(file); +	struct channel_obj *ch = video_get_drvdata(vdev); +	struct vpif_capture_chan_config *chan_cfg; +	struct v4l2_input input; + +	vpif_dbg(2, debug, "vpif_g_std\n"); + +	if (config->chan_config[ch->channel_id].inputs == NULL) +		return -ENODATA; + +	chan_cfg = &config->chan_config[ch->channel_id]; +	input = chan_cfg->inputs[ch->input_idx].input; +	if (input.capabilities != V4L2_IN_CAP_STD) +		return -ENODATA; + +	*std = ch->video.stdid; +	return 0; +} + +/** + * vpif_s_std() - set STD handler + * @file: file ptr + * @priv: file handle + * @std_id: ptr to std id + */ +static int vpif_s_std(struct file *file, void *priv, v4l2_std_id std_id) +{ +	struct vpif_capture_config *config = vpif_dev->platform_data; +	struct video_device *vdev = video_devdata(file); +	struct channel_obj *ch = video_get_drvdata(vdev); +	struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; +	struct vpif_capture_chan_config *chan_cfg; +	struct v4l2_input input; +	int ret; + +	vpif_dbg(2, debug, "vpif_s_std\n"); + +	if (config->chan_config[ch->channel_id].inputs == NULL) +		return -ENODATA; + +	chan_cfg = &config->chan_config[ch->channel_id]; +	input = chan_cfg->inputs[ch->input_idx].input; +	if (input.capabilities != V4L2_IN_CAP_STD) +		return -ENODATA; + +	if (vb2_is_busy(&common->buffer_queue)) +		return -EBUSY; + +	/* Call encoder subdevice function to set the standard */ +	ch->video.stdid = std_id; +	memset(&ch->video.dv_timings, 0, sizeof(ch->video.dv_timings)); + +	/* Get the information about the standard */ +	if (vpif_update_std_info(ch)) { +		vpif_err("Error getting the standard info\n"); +		return -EINVAL; +	} + +	/* Configure the default format information */ +	vpif_config_format(ch); + +	/* set standard in the sub device */ +	ret = v4l2_subdev_call(ch->sd, video, s_std, std_id); +	if (ret && ret != -ENOIOCTLCMD && ret != -ENODEV) { +		vpif_dbg(1, debug, "Failed to set standard for sub devices\n"); +		return ret; +	} +	return 0; +} + +/** + * vpif_enum_input() - ENUMINPUT handler + * @file: file ptr + * @priv: file handle + * @input: ptr to input structure + */ +static int vpif_enum_input(struct file *file, void *priv, +				struct v4l2_input *input) +{ + +	struct vpif_capture_config *config = vpif_dev->platform_data; +	struct video_device *vdev = video_devdata(file); +	struct channel_obj *ch = video_get_drvdata(vdev); +	struct vpif_capture_chan_config *chan_cfg; + +	chan_cfg = &config->chan_config[ch->channel_id]; + +	if (input->index >= chan_cfg->input_count) { +		vpif_dbg(1, debug, "Invalid input index\n"); +		return -EINVAL; +	} + +	memcpy(input, &chan_cfg->inputs[input->index].input, +		sizeof(*input)); +	return 0; +} + +/** + * vpif_g_input() - Get INPUT handler + * @file: file ptr + * @priv: file handle + * @index: ptr to input index + */ +static int vpif_g_input(struct file *file, void *priv, unsigned int *index) +{ +	struct video_device *vdev = video_devdata(file); +	struct channel_obj *ch = video_get_drvdata(vdev); + +	*index = ch->input_idx; +	return 0; +} + +/** + * vpif_s_input() - Set INPUT handler + * @file: file ptr + * @priv: file handle + * @index: input index + */ +static int vpif_s_input(struct file *file, void *priv, unsigned int index) +{ +	struct vpif_capture_config *config = vpif_dev->platform_data; +	struct video_device *vdev = video_devdata(file); +	struct channel_obj *ch = video_get_drvdata(vdev); +	struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; +	struct vpif_capture_chan_config *chan_cfg; + +	chan_cfg = &config->chan_config[ch->channel_id]; + +	if (index >= chan_cfg->input_count) +		return -EINVAL; + +	if (vb2_is_busy(&common->buffer_queue)) +		return -EBUSY; + +	return vpif_set_input(config, ch, index); +} + +/** + * vpif_enum_fmt_vid_cap() - ENUM_FMT handler + * @file: file ptr + * @priv: file handle + * @index: input index + */ +static int vpif_enum_fmt_vid_cap(struct file *file, void  *priv, +					struct v4l2_fmtdesc *fmt) +{ +	struct video_device *vdev = video_devdata(file); +	struct channel_obj *ch = video_get_drvdata(vdev); + +	if (fmt->index != 0) { +		vpif_dbg(1, debug, "Invalid format index\n"); +		return -EINVAL; +	} + +	/* Fill in the information about format */ +	if (ch->vpifparams.iface.if_type == VPIF_IF_RAW_BAYER) { +		fmt->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; +		strcpy(fmt->description, "Raw Mode -Bayer Pattern GrRBGb"); +		fmt->pixelformat = V4L2_PIX_FMT_SBGGR8; +	} else { +		fmt->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; +		strcpy(fmt->description, "YCbCr4:2:2 YC Planar"); +		fmt->pixelformat = V4L2_PIX_FMT_YUV422P; +	} +	return 0; +} + +/** + * vpif_try_fmt_vid_cap() - TRY_FMT handler + * @file: file ptr + * @priv: file handle + * @fmt: ptr to v4l2 format structure + */ +static int vpif_try_fmt_vid_cap(struct file *file, void *priv, +				struct v4l2_format *fmt) +{ +	struct video_device *vdev = video_devdata(file); +	struct channel_obj *ch = video_get_drvdata(vdev); +	struct v4l2_pix_format *pixfmt = &fmt->fmt.pix; + +	return vpif_check_format(ch, pixfmt, 1); +} + + +/** + * vpif_g_fmt_vid_cap() - Set INPUT handler + * @file: file ptr + * @priv: file handle + * @fmt: ptr to v4l2 format structure + */ +static int vpif_g_fmt_vid_cap(struct file *file, void *priv, +				struct v4l2_format *fmt) +{ +	struct video_device *vdev = video_devdata(file); +	struct channel_obj *ch = video_get_drvdata(vdev); +	struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + +	/* Check the validity of the buffer type */ +	if (common->fmt.type != fmt->type) +		return -EINVAL; + +	/* Fill in the information about format */ +	*fmt = common->fmt; +	return 0; +} + +/** + * vpif_s_fmt_vid_cap() - Set FMT handler + * @file: file ptr + * @priv: file handle + * @fmt: ptr to v4l2 format structure + */ +static int vpif_s_fmt_vid_cap(struct file *file, void *priv, +				struct v4l2_format *fmt) +{ +	struct video_device *vdev = video_devdata(file); +	struct channel_obj *ch = video_get_drvdata(vdev); +	struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; +	struct v4l2_pix_format *pixfmt; +	int ret = 0; + +	vpif_dbg(2, debug, "%s\n", __func__); + +	if (vb2_is_busy(&common->buffer_queue)) +		return -EBUSY; + +	pixfmt = &fmt->fmt.pix; +	/* Check for valid field format */ +	ret = vpif_check_format(ch, pixfmt, 0); + +	if (ret) +		return ret; +	/* store the format in the channel object */ +	common->fmt = *fmt; +	return 0; +} + +/** + * vpif_querycap() - QUERYCAP handler + * @file: file ptr + * @priv: file handle + * @cap: ptr to v4l2_capability structure + */ +static int vpif_querycap(struct file *file, void  *priv, +				struct v4l2_capability *cap) +{ +	struct vpif_capture_config *config = vpif_dev->platform_data; + +	cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING; +	cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; +	strlcpy(cap->driver, VPIF_DRIVER_NAME, sizeof(cap->driver)); +	snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s", +		 dev_name(vpif_dev)); +	strlcpy(cap->card, config->card_name, sizeof(cap->card)); + +	return 0; +} + +/** + * vpif_enum_dv_timings() - ENUM_DV_TIMINGS handler + * @file: file ptr + * @priv: file handle + * @timings: input timings + */ +static int +vpif_enum_dv_timings(struct file *file, void *priv, +		     struct v4l2_enum_dv_timings *timings) +{ +	struct vpif_capture_config *config = vpif_dev->platform_data; +	struct video_device *vdev = video_devdata(file); +	struct channel_obj *ch = video_get_drvdata(vdev); +	struct vpif_capture_chan_config *chan_cfg; +	struct v4l2_input input; +	int ret; + +	if (config->chan_config[ch->channel_id].inputs == NULL) +		return -ENODATA; + +	chan_cfg = &config->chan_config[ch->channel_id]; +	input = chan_cfg->inputs[ch->input_idx].input; +	if (input.capabilities != V4L2_IN_CAP_DV_TIMINGS) +		return -ENODATA; + +	timings->pad = 0; + +	ret = v4l2_subdev_call(ch->sd, pad, enum_dv_timings, timings); +	if (ret == -ENOIOCTLCMD || ret == -ENODEV) +		return -EINVAL; + +	return ret; +} + +/** + * vpif_query_dv_timings() - QUERY_DV_TIMINGS handler + * @file: file ptr + * @priv: file handle + * @timings: input timings + */ +static int +vpif_query_dv_timings(struct file *file, void *priv, +		      struct v4l2_dv_timings *timings) +{ +	struct vpif_capture_config *config = vpif_dev->platform_data; +	struct video_device *vdev = video_devdata(file); +	struct channel_obj *ch = video_get_drvdata(vdev); +	struct vpif_capture_chan_config *chan_cfg; +	struct v4l2_input input; +	int ret; + +	if (config->chan_config[ch->channel_id].inputs == NULL) +		return -ENODATA; + +	chan_cfg = &config->chan_config[ch->channel_id]; +	input = chan_cfg->inputs[ch->input_idx].input; +	if (input.capabilities != V4L2_IN_CAP_DV_TIMINGS) +		return -ENODATA; + +	ret = v4l2_subdev_call(ch->sd, video, query_dv_timings, timings); +	if (ret == -ENOIOCTLCMD || ret == -ENODEV) +		return -ENODATA; + +	return ret; +} + +/** + * vpif_s_dv_timings() - S_DV_TIMINGS handler + * @file: file ptr + * @priv: file handle + * @timings: digital video timings + */ +static int vpif_s_dv_timings(struct file *file, void *priv, +		struct v4l2_dv_timings *timings) +{ +	struct vpif_capture_config *config = vpif_dev->platform_data; +	struct video_device *vdev = video_devdata(file); +	struct channel_obj *ch = video_get_drvdata(vdev); +	struct vpif_params *vpifparams = &ch->vpifparams; +	struct vpif_channel_config_params *std_info = &vpifparams->std_info; +	struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; +	struct video_obj *vid_ch = &ch->video; +	struct v4l2_bt_timings *bt = &vid_ch->dv_timings.bt; +	struct vpif_capture_chan_config *chan_cfg; +	struct v4l2_input input; +	int ret; + +	if (config->chan_config[ch->channel_id].inputs == NULL) +		return -ENODATA; + +	chan_cfg = &config->chan_config[ch->channel_id]; +	input = chan_cfg->inputs[ch->input_idx].input; +	if (input.capabilities != V4L2_IN_CAP_DV_TIMINGS) +		return -ENODATA; + +	if (timings->type != V4L2_DV_BT_656_1120) { +		vpif_dbg(2, debug, "Timing type not defined\n"); +		return -EINVAL; +	} + +	if (vb2_is_busy(&common->buffer_queue)) +		return -EBUSY; + +	/* Configure subdevice timings, if any */ +	ret = v4l2_subdev_call(ch->sd, video, s_dv_timings, timings); +	if (ret == -ENOIOCTLCMD || ret == -ENODEV) +		ret = 0; +	if (ret < 0) { +		vpif_dbg(2, debug, "Error setting custom DV timings\n"); +		return ret; +	} + +	if (!(timings->bt.width && timings->bt.height && +				(timings->bt.hbackporch || +				 timings->bt.hfrontporch || +				 timings->bt.hsync) && +				timings->bt.vfrontporch && +				(timings->bt.vbackporch || +				 timings->bt.vsync))) { +		vpif_dbg(2, debug, "Timings for width, height, " +				"horizontal back porch, horizontal sync, " +				"horizontal front porch, vertical back porch, " +				"vertical sync and vertical back porch " +				"must be defined\n"); +		return -EINVAL; +	} + +	vid_ch->dv_timings = *timings; + +	/* Configure video port timings */ + +	std_info->eav2sav = V4L2_DV_BT_BLANKING_WIDTH(bt) - 8; +	std_info->sav2eav = bt->width; + +	std_info->l1 = 1; +	std_info->l3 = bt->vsync + bt->vbackporch + 1; + +	std_info->vsize = V4L2_DV_BT_FRAME_HEIGHT(bt); +	if (bt->interlaced) { +		if (bt->il_vbackporch || bt->il_vfrontporch || bt->il_vsync) { +			std_info->l5 = std_info->vsize/2 - +				(bt->vfrontporch - 1); +			std_info->l7 = std_info->vsize/2 + 1; +			std_info->l9 = std_info->l7 + bt->il_vsync + +				bt->il_vbackporch + 1; +			std_info->l11 = std_info->vsize - +				(bt->il_vfrontporch - 1); +		} else { +			vpif_dbg(2, debug, "Required timing values for " +					"interlaced BT format missing\n"); +			return -EINVAL; +		} +	} else { +		std_info->l5 = std_info->vsize - (bt->vfrontporch - 1); +	} +	strncpy(std_info->name, "Custom timings BT656/1120", VPIF_MAX_NAME); +	std_info->width = bt->width; +	std_info->height = bt->height; +	std_info->frm_fmt = bt->interlaced ? 0 : 1; +	std_info->ycmux_mode = 0; +	std_info->capture_format = 0; +	std_info->vbi_supported = 0; +	std_info->hd_sd = 1; +	std_info->stdid = 0; + +	vid_ch->stdid = 0; +	return 0; +} + +/** + * vpif_g_dv_timings() - G_DV_TIMINGS handler + * @file: file ptr + * @priv: file handle + * @timings: digital video timings + */ +static int vpif_g_dv_timings(struct file *file, void *priv, +		struct v4l2_dv_timings *timings) +{ +	struct vpif_capture_config *config = vpif_dev->platform_data; +	struct video_device *vdev = video_devdata(file); +	struct channel_obj *ch = video_get_drvdata(vdev); +	struct video_obj *vid_ch = &ch->video; +	struct vpif_capture_chan_config *chan_cfg; +	struct v4l2_input input; + +	if (config->chan_config[ch->channel_id].inputs == NULL) +		return -ENODATA; + +	chan_cfg = &config->chan_config[ch->channel_id]; +	input = chan_cfg->inputs[ch->input_idx].input; +	if (input.capabilities != V4L2_IN_CAP_DV_TIMINGS) +		return -ENODATA; + +	*timings = vid_ch->dv_timings; + +	return 0; +} + +/* + * vpif_log_status() - Status information + * @file: file ptr + * @priv: file handle + * + * Returns zero. + */ +static int vpif_log_status(struct file *filep, void *priv) +{ +	/* status for sub devices */ +	v4l2_device_call_all(&vpif_obj.v4l2_dev, 0, core, log_status); + +	return 0; +} + +/* vpif capture ioctl operations */ +static const struct v4l2_ioctl_ops vpif_ioctl_ops = { +	.vidioc_querycap		= vpif_querycap, +	.vidioc_enum_fmt_vid_cap	= vpif_enum_fmt_vid_cap, +	.vidioc_g_fmt_vid_cap		= vpif_g_fmt_vid_cap, +	.vidioc_s_fmt_vid_cap		= vpif_s_fmt_vid_cap, +	.vidioc_try_fmt_vid_cap		= vpif_try_fmt_vid_cap, + +	.vidioc_enum_input		= vpif_enum_input, +	.vidioc_s_input			= vpif_s_input, +	.vidioc_g_input			= vpif_g_input, + +	.vidioc_reqbufs			= vb2_ioctl_reqbufs, +	.vidioc_create_bufs		= vb2_ioctl_create_bufs, +	.vidioc_querybuf		= vb2_ioctl_querybuf, +	.vidioc_qbuf			= vb2_ioctl_qbuf, +	.vidioc_dqbuf			= vb2_ioctl_dqbuf, +	.vidioc_expbuf			= vb2_ioctl_expbuf, +	.vidioc_streamon		= vb2_ioctl_streamon, +	.vidioc_streamoff		= vb2_ioctl_streamoff, + +	.vidioc_querystd		= vpif_querystd, +	.vidioc_s_std			= vpif_s_std, +	.vidioc_g_std			= vpif_g_std, + +	.vidioc_enum_dv_timings		= vpif_enum_dv_timings, +	.vidioc_query_dv_timings	= vpif_query_dv_timings, +	.vidioc_s_dv_timings		= vpif_s_dv_timings, +	.vidioc_g_dv_timings		= vpif_g_dv_timings, + +	.vidioc_log_status		= vpif_log_status, +}; + +/* vpif file operations */ +static struct v4l2_file_operations vpif_fops = { +	.owner = THIS_MODULE, +	.open = v4l2_fh_open, +	.release = vb2_fop_release, +	.unlocked_ioctl = video_ioctl2, +	.mmap = vb2_fop_mmap, +	.poll = vb2_fop_poll +}; + +/** + * initialize_vpif() - Initialize vpif data structures + * + * Allocate memory for data structures and initialize them + */ +static int initialize_vpif(void) +{ +	int err = 0, i, j; +	int free_channel_objects_index; + +	/* Default number of buffers should be 3 */ +	if ((ch0_numbuffers > 0) && +	    (ch0_numbuffers < config_params.min_numbuffers)) +		ch0_numbuffers = config_params.min_numbuffers; +	if ((ch1_numbuffers > 0) && +	    (ch1_numbuffers < config_params.min_numbuffers)) +		ch1_numbuffers = config_params.min_numbuffers; + +	/* Set buffer size to min buffers size if it is invalid */ +	if (ch0_bufsize < config_params.min_bufsize[VPIF_CHANNEL0_VIDEO]) +		ch0_bufsize = +		    config_params.min_bufsize[VPIF_CHANNEL0_VIDEO]; +	if (ch1_bufsize < config_params.min_bufsize[VPIF_CHANNEL1_VIDEO]) +		ch1_bufsize = +		    config_params.min_bufsize[VPIF_CHANNEL1_VIDEO]; + +	config_params.numbuffers[VPIF_CHANNEL0_VIDEO] = ch0_numbuffers; +	config_params.numbuffers[VPIF_CHANNEL1_VIDEO] = ch1_numbuffers; +	if (ch0_numbuffers) { +		config_params.channel_bufsize[VPIF_CHANNEL0_VIDEO] +		    = ch0_bufsize; +	} +	if (ch1_numbuffers) { +		config_params.channel_bufsize[VPIF_CHANNEL1_VIDEO] +		    = ch1_bufsize; +	} + +	/* Allocate memory for six channel objects */ +	for (i = 0; i < VPIF_CAPTURE_MAX_DEVICES; i++) { +		vpif_obj.dev[i] = +		    kzalloc(sizeof(*vpif_obj.dev[i]), GFP_KERNEL); +		/* If memory allocation fails, return error */ +		if (!vpif_obj.dev[i]) { +			free_channel_objects_index = i; +			err = -ENOMEM; +			goto vpif_init_free_channel_objects; +		} +	} +	return 0; + +vpif_init_free_channel_objects: +	for (j = 0; j < free_channel_objects_index; j++) +		kfree(vpif_obj.dev[j]); +	return err; +} + +static int vpif_async_bound(struct v4l2_async_notifier *notifier, +			    struct v4l2_subdev *subdev, +			    struct v4l2_async_subdev *asd) +{ +	int i; + +	for (i = 0; i < vpif_obj.config->subdev_count; i++) +		if (!strcmp(vpif_obj.config->subdev_info[i].name, +			    subdev->name)) { +			vpif_obj.sd[i] = subdev; +			return 0; +		} + +	return -EINVAL; +} + +static int vpif_probe_complete(void) +{ +	struct common_obj *common; +	struct video_device *vdev; +	struct channel_obj *ch; +	struct vb2_queue *q; +	int i, j, err, k; + +	for (j = 0; j < VPIF_CAPTURE_MAX_DEVICES; j++) { +		ch = vpif_obj.dev[j]; +		ch->channel_id = j; +		common = &(ch->common[VPIF_VIDEO_INDEX]); +		spin_lock_init(&common->irqlock); +		mutex_init(&common->lock); + +		/* select input 0 */ +		err = vpif_set_input(vpif_obj.config, ch, 0); +		if (err) +			goto probe_out; + +		/* Initialize vb2 queue */ +		q = &common->buffer_queue; +		q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; +		q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF; +		q->drv_priv = ch; +		q->ops = &video_qops; +		q->mem_ops = &vb2_dma_contig_memops; +		q->buf_struct_size = sizeof(struct vpif_cap_buffer); +		q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; +		q->min_buffers_needed = 1; +		q->lock = &common->lock; + +		err = vb2_queue_init(q); +		if (err) { +			vpif_err("vpif_capture: vb2_queue_init() failed\n"); +			goto probe_out; +		} + +		common->alloc_ctx = vb2_dma_contig_init_ctx(vpif_dev); +		if (IS_ERR(common->alloc_ctx)) { +			vpif_err("Failed to get the context\n"); +			err = PTR_ERR(common->alloc_ctx); +			goto probe_out; +		} + +		INIT_LIST_HEAD(&common->dma_queue); + +		/* Initialize the video_device structure */ +		vdev = ch->video_dev; +		strlcpy(vdev->name, VPIF_DRIVER_NAME, sizeof(vdev->name)); +		vdev->release = video_device_release; +		vdev->fops = &vpif_fops; +		vdev->ioctl_ops = &vpif_ioctl_ops; +		vdev->v4l2_dev = &vpif_obj.v4l2_dev; +		vdev->vfl_dir = VFL_DIR_RX; +		vdev->queue = q; +		vdev->lock = &common->lock; +		set_bit(V4L2_FL_USE_FH_PRIO, &vdev->flags); +		video_set_drvdata(ch->video_dev, ch); +		err = video_register_device(vdev, +					    VFL_TYPE_GRABBER, (j ? 1 : 0)); +		if (err) +			goto probe_out; +	} + +	v4l2_info(&vpif_obj.v4l2_dev, "VPIF capture driver initialized\n"); +	return 0; + +probe_out: +	for (k = 0; k < j; k++) { +		/* Get the pointer to the channel object */ +		ch = vpif_obj.dev[k]; +		common = &ch->common[k]; +		vb2_dma_contig_cleanup_ctx(common->alloc_ctx); +		/* Unregister video device */ +		video_unregister_device(ch->video_dev); +	} +	kfree(vpif_obj.sd); +	for (i = 0; i < VPIF_CAPTURE_MAX_DEVICES; i++) { +		ch = vpif_obj.dev[i]; +		/* Note: does nothing if ch->video_dev == NULL */ +		video_device_release(ch->video_dev); +	} +	v4l2_device_unregister(&vpif_obj.v4l2_dev); + +	return err; +} + +static int vpif_async_complete(struct v4l2_async_notifier *notifier) +{ +	return vpif_probe_complete(); +} + +/** + * vpif_probe : This function probes the vpif capture driver + * @pdev: platform device pointer + * + * This creates device entries by register itself to the V4L2 driver and + * initializes fields of each channel objects + */ +static __init int vpif_probe(struct platform_device *pdev) +{ +	struct vpif_subdev_info *subdevdata; +	int i, j, err; +	int res_idx = 0; +	struct i2c_adapter *i2c_adap; +	struct channel_obj *ch; +	struct video_device *vfd; +	struct resource *res; +	int subdev_count; + +	vpif_dev = &pdev->dev; + +	err = initialize_vpif(); +	if (err) { +		v4l2_err(vpif_dev->driver, "Error initializing vpif\n"); +		return err; +	} + +	err = v4l2_device_register(vpif_dev, &vpif_obj.v4l2_dev); +	if (err) { +		v4l2_err(vpif_dev->driver, "Error registering v4l2 device\n"); +		return err; +	} + +	while ((res = platform_get_resource(pdev, IORESOURCE_IRQ, res_idx))) { +		err = devm_request_irq(&pdev->dev, res->start, vpif_channel_isr, +					IRQF_SHARED, VPIF_DRIVER_NAME, +					(void *)(&vpif_obj.dev[res_idx]-> +					channel_id)); +		if (err) { +			err = -EINVAL; +			goto vpif_unregister; +		} +		res_idx++; +	} + +	for (i = 0; i < VPIF_CAPTURE_MAX_DEVICES; i++) { +		/* Get the pointer to the channel object */ +		ch = vpif_obj.dev[i]; +		/* Allocate memory for video device */ +		vfd = video_device_alloc(); +		if (NULL == vfd) { +			for (j = 0; j < i; j++) { +				ch = vpif_obj.dev[j]; +				video_device_release(ch->video_dev); +			} +			err = -ENOMEM; +			goto vpif_unregister; +		} + +		/* Set video_dev to the video device */ +		ch->video_dev = vfd; +	} + +	vpif_obj.config = pdev->dev.platform_data; + +	subdev_count = vpif_obj.config->subdev_count; +	vpif_obj.sd = kzalloc(sizeof(struct v4l2_subdev *) * subdev_count, +				GFP_KERNEL); +	if (vpif_obj.sd == NULL) { +		vpif_err("unable to allocate memory for subdevice pointers\n"); +		err = -ENOMEM; +		goto vpif_sd_error; +	} + +	if (!vpif_obj.config->asd_sizes) { +		i2c_adap = i2c_get_adapter(1); +		for (i = 0; i < subdev_count; i++) { +			subdevdata = &vpif_obj.config->subdev_info[i]; +			vpif_obj.sd[i] = +				v4l2_i2c_new_subdev_board(&vpif_obj.v4l2_dev, +							  i2c_adap, +							  &subdevdata-> +							  board_info, +							  NULL); + +			if (!vpif_obj.sd[i]) { +				vpif_err("Error registering v4l2 subdevice\n"); +				err = -ENODEV; +				goto probe_subdev_out; +			} +			v4l2_info(&vpif_obj.v4l2_dev, +				  "registered sub device %s\n", +				   subdevdata->name); +		} +		vpif_probe_complete(); +	} else { +		vpif_obj.notifier.subdevs = vpif_obj.config->asd; +		vpif_obj.notifier.num_subdevs = vpif_obj.config->asd_sizes[0]; +		vpif_obj.notifier.bound = vpif_async_bound; +		vpif_obj.notifier.complete = vpif_async_complete; +		err = v4l2_async_notifier_register(&vpif_obj.v4l2_dev, +						   &vpif_obj.notifier); +		if (err) { +			vpif_err("Error registering async notifier\n"); +			err = -EINVAL; +			goto probe_subdev_out; +		} +	} + +	return 0; + +probe_subdev_out: +	/* free sub devices memory */ +	kfree(vpif_obj.sd); + +vpif_sd_error: +	for (i = 0; i < VPIF_CAPTURE_MAX_DEVICES; i++) { +		ch = vpif_obj.dev[i]; +		/* Note: does nothing if ch->video_dev == NULL */ +		video_device_release(ch->video_dev); +	} +vpif_unregister: +	v4l2_device_unregister(&vpif_obj.v4l2_dev); + +	return err; +} + +/** + * vpif_remove() - driver remove handler + * @device: ptr to platform device structure + * + * The vidoe device is unregistered + */ +static int vpif_remove(struct platform_device *device) +{ +	struct common_obj *common; +	struct channel_obj *ch; +	int i; + +	v4l2_device_unregister(&vpif_obj.v4l2_dev); + +	kfree(vpif_obj.sd); +	/* un-register device */ +	for (i = 0; i < VPIF_CAPTURE_MAX_DEVICES; i++) { +		/* Get the pointer to the channel object */ +		ch = vpif_obj.dev[i]; +		common = &ch->common[i]; +		vb2_dma_contig_cleanup_ctx(common->alloc_ctx); +		/* Unregister video device */ +		video_unregister_device(ch->video_dev); +		kfree(vpif_obj.dev[i]); +	} +	return 0; +} + +#ifdef CONFIG_PM_SLEEP +/** + * vpif_suspend: vpif device suspend + */ +static int vpif_suspend(struct device *dev) +{ + +	struct common_obj *common; +	struct channel_obj *ch; +	int i; + +	for (i = 0; i < VPIF_CAPTURE_MAX_DEVICES; i++) { +		/* Get the pointer to the channel object */ +		ch = vpif_obj.dev[i]; +		common = &ch->common[VPIF_VIDEO_INDEX]; + +		if (!vb2_is_streaming(&common->buffer_queue)) +			continue; + +		mutex_lock(&common->lock); +		/* Disable channel */ +		if (ch->channel_id == VPIF_CHANNEL0_VIDEO) { +			enable_channel0(0); +			channel0_intr_enable(0); +		} +		if (ch->channel_id == VPIF_CHANNEL1_VIDEO || +			ycmux_mode == 2) { +			enable_channel1(0); +			channel1_intr_enable(0); +		} +		mutex_unlock(&common->lock); +	} + +	return 0; +} + +/* + * vpif_resume: vpif device suspend + */ +static int vpif_resume(struct device *dev) +{ +	struct common_obj *common; +	struct channel_obj *ch; +	int i; + +	for (i = 0; i < VPIF_CAPTURE_MAX_DEVICES; i++) { +		/* Get the pointer to the channel object */ +		ch = vpif_obj.dev[i]; +		common = &ch->common[VPIF_VIDEO_INDEX]; + +		if (!vb2_is_streaming(&common->buffer_queue)) +			continue; + +		mutex_lock(&common->lock); +		/* Enable channel */ +		if (ch->channel_id == VPIF_CHANNEL0_VIDEO) { +			enable_channel0(1); +			channel0_intr_enable(1); +		} +		if (ch->channel_id == VPIF_CHANNEL1_VIDEO || +			ycmux_mode == 2) { +			enable_channel1(1); +			channel1_intr_enable(1); +		} +		mutex_unlock(&common->lock); +	} + +	return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(vpif_pm_ops, vpif_suspend, vpif_resume); + +static __refdata struct platform_driver vpif_driver = { +	.driver	= { +		.name	= VPIF_DRIVER_NAME, +		.owner	= THIS_MODULE, +		.pm	= &vpif_pm_ops, +	}, +	.probe = vpif_probe, +	.remove = vpif_remove, +}; + +module_platform_driver(vpif_driver); diff --git a/drivers/media/platform/davinci/vpif_capture.h b/drivers/media/platform/davinci/vpif_capture.h new file mode 100644 index 00000000000..1ee17824f48 --- /dev/null +++ b/drivers/media/platform/davinci/vpif_capture.h @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2009 Texas Instruments Inc + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA + */ + +#ifndef VPIF_CAPTURE_H +#define VPIF_CAPTURE_H + +/* Header files */ +#include <media/videobuf2-dma-contig.h> +#include <media/v4l2-device.h> + +#include "vpif.h" + +/* Macros */ +#define VPIF_CAPTURE_VERSION		"0.0.2" + +#define VPIF_VALID_FIELD(field)		(((V4L2_FIELD_ANY == field) || \ +	(V4L2_FIELD_NONE == field)) || \ +	(((V4L2_FIELD_INTERLACED == field) || \ +	(V4L2_FIELD_SEQ_TB == field)) || \ +	(V4L2_FIELD_SEQ_BT == field))) + +#define VPIF_CAPTURE_MAX_DEVICES	2 +#define VPIF_VIDEO_INDEX		0 +#define VPIF_NUMBER_OF_OBJECTS		1 + +/* Enumerated data type to give id to each device per channel */ +enum vpif_channel_id { +	VPIF_CHANNEL0_VIDEO = 0, +	VPIF_CHANNEL1_VIDEO, +}; + +struct video_obj { +	enum v4l2_field buf_field; +	/* Currently selected or default standard */ +	v4l2_std_id stdid; +	struct v4l2_dv_timings dv_timings; +}; + +struct vpif_cap_buffer { +	struct vb2_buffer vb; +	struct list_head list; +}; + +struct common_obj { +	/* Pointer pointing to current v4l2_buffer */ +	struct vpif_cap_buffer *cur_frm; +	/* Pointer pointing to current v4l2_buffer */ +	struct vpif_cap_buffer *next_frm; +	/* Used to store pixel format */ +	struct v4l2_format fmt; +	/* Buffer queue used in video-buf */ +	struct vb2_queue buffer_queue; +	/* allocator-specific contexts for each plane */ +	struct vb2_alloc_ctx *alloc_ctx; +	/* Queue of filled frames */ +	struct list_head dma_queue; +	/* Used in video-buf */ +	spinlock_t irqlock; +	/* lock used to access this structure */ +	struct mutex lock; +	/* Function pointer to set the addresses */ +	void (*set_addr) (unsigned long, unsigned long, unsigned long, +			  unsigned long); +	/* offset where Y top starts from the starting of the buffer */ +	u32 ytop_off; +	/* offset where Y bottom starts from the starting of the buffer */ +	u32 ybtm_off; +	/* offset where C top starts from the starting of the buffer */ +	u32 ctop_off; +	/* offset where C bottom starts from the starting of the buffer */ +	u32 cbtm_off; +	/* Indicates width of the image data */ +	u32 width; +	/* Indicates height of the image data */ +	u32 height; +}; + +struct channel_obj { +	/* Identifies video device for this channel */ +	struct video_device *video_dev; +	/* Indicates id of the field which is being displayed */ +	u32 field_id; +	/* flag to indicate whether decoder is initialized */ +	u8 initialized; +	/* Identifies channel */ +	enum vpif_channel_id channel_id; +	/* Current input */ +	u32 input_idx; +	/* subdev corresponding to the current input, may be NULL */ +	struct v4l2_subdev *sd; +	/* vpif configuration params */ +	struct vpif_params vpifparams; +	/* common object array */ +	struct common_obj common[VPIF_NUMBER_OF_OBJECTS]; +	/* video object */ +	struct video_obj video; +}; + +struct vpif_device { +	struct v4l2_device v4l2_dev; +	struct channel_obj *dev[VPIF_CAPTURE_NUM_CHANNELS]; +	struct v4l2_subdev **sd; +	struct v4l2_async_notifier notifier; +	struct vpif_capture_config *config; +}; + +struct vpif_config_params { +	u8 min_numbuffers; +	u8 numbuffers[VPIF_CAPTURE_NUM_CHANNELS]; +	s8 device_type; +	u32 min_bufsize[VPIF_CAPTURE_NUM_CHANNELS]; +	u32 channel_bufsize[VPIF_CAPTURE_NUM_CHANNELS]; +	u8 default_device[VPIF_CAPTURE_NUM_CHANNELS]; +	u32 video_limit[VPIF_CAPTURE_NUM_CHANNELS]; +	u8 max_device_type; +}; + +#endif				/* VPIF_CAPTURE_H */ diff --git a/drivers/media/platform/davinci/vpif_display.c b/drivers/media/platform/davinci/vpif_display.c new file mode 100644 index 00000000000..b431b58f39e --- /dev/null +++ b/drivers/media/platform/davinci/vpif_display.c @@ -0,0 +1,1481 @@ +/* + * vpif-display - VPIF display driver + * Display driver for TI DaVinci VPIF + * + * Copyright (C) 2009 Texas Instruments Incorporated - http://www.ti.com/ + * Copyright (C) 2014 Lad, Prabhakar <prabhakar.csengg@gmail.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed .as is. WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + */ + +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#include <media/v4l2-ioctl.h> + +#include "vpif.h" +#include "vpif_display.h" + +MODULE_DESCRIPTION("TI DaVinci VPIF Display driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(VPIF_DISPLAY_VERSION); + +#define VPIF_V4L2_STD (V4L2_STD_525_60 | V4L2_STD_625_50) + +#define vpif_err(fmt, arg...)	v4l2_err(&vpif_obj.v4l2_dev, fmt, ## arg) +#define vpif_dbg(level, debug, fmt, arg...)	\ +		v4l2_dbg(level, debug, &vpif_obj.v4l2_dev, fmt, ## arg) + +static int debug = 1; + +module_param(debug, int, 0644); + +MODULE_PARM_DESC(debug, "Debug level 0-1"); + +#define VPIF_DRIVER_NAME	"vpif_display" + +/* Is set to 1 in case of SDTV formats, 2 in case of HDTV formats. */ +static int ycmux_mode; + +static u8 channel_first_int[VPIF_NUMOBJECTS][2] = { {1, 1} }; + +static struct vpif_device vpif_obj = { {NULL} }; +static struct device *vpif_dev; +static void vpif_calculate_offsets(struct channel_obj *ch); +static void vpif_config_addr(struct channel_obj *ch, int muxmode); + +static inline struct vpif_disp_buffer *to_vpif_buffer(struct vb2_buffer *vb) +{ +	return container_of(vb, struct vpif_disp_buffer, vb); +} + +/** + * vpif_buffer_prepare :  callback function for buffer prepare + * @vb: ptr to vb2_buffer + * + * This is the callback function for buffer prepare when vb2_qbuf() + * function is called. The buffer is prepared and user space virtual address + * or user address is converted into  physical address + */ +static int vpif_buffer_prepare(struct vb2_buffer *vb) +{ +	struct channel_obj *ch = vb2_get_drv_priv(vb->vb2_queue); +	struct common_obj *common; + +	common = &ch->common[VPIF_VIDEO_INDEX]; + +	vb2_set_plane_payload(vb, 0, common->fmt.fmt.pix.sizeimage); +	if (vb2_get_plane_payload(vb, 0) > vb2_plane_size(vb, 0)) +		return -EINVAL; + +	vb->v4l2_buf.field = common->fmt.fmt.pix.field; + +	if (vb->vb2_queue->type != V4L2_BUF_TYPE_SLICED_VBI_OUTPUT) { +		unsigned long addr = vb2_dma_contig_plane_dma_addr(vb, 0); + +		if (!ISALIGNED(addr + common->ytop_off) || +			!ISALIGNED(addr + common->ybtm_off) || +			!ISALIGNED(addr + common->ctop_off) || +			!ISALIGNED(addr + common->cbtm_off)) { +			vpif_err("buffer offset not aligned to 8 bytes\n"); +			return -EINVAL; +		} +	} + +	return 0; +} + +/** + * vpif_buffer_queue_setup : Callback function for buffer setup. + * @vq: vb2_queue ptr + * @fmt: v4l2 format + * @nbuffers: ptr to number of buffers requested by application + * @nplanes:: contains number of distinct video planes needed to hold a frame + * @sizes[]: contains the size (in bytes) of each plane. + * @alloc_ctxs: ptr to allocation context + * + * This callback function is called when reqbuf() is called to adjust + * the buffer count and buffer size + */ +static int vpif_buffer_queue_setup(struct vb2_queue *vq, +				const struct v4l2_format *fmt, +				unsigned int *nbuffers, unsigned int *nplanes, +				unsigned int sizes[], void *alloc_ctxs[]) +{ +	struct channel_obj *ch = vb2_get_drv_priv(vq); +	struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + +	if (fmt && fmt->fmt.pix.sizeimage < common->fmt.fmt.pix.sizeimage) +		return -EINVAL; + +	if (vq->num_buffers + *nbuffers < 3) +		*nbuffers = 3 - vq->num_buffers; + +	*nplanes = 1; +	sizes[0] = fmt ? fmt->fmt.pix.sizeimage : common->fmt.fmt.pix.sizeimage; +	alloc_ctxs[0] = common->alloc_ctx; + +	/* Calculate the offset for Y and C data  in the buffer */ +	vpif_calculate_offsets(ch); + +	return 0; +} + +/** + * vpif_buffer_queue : Callback function to add buffer to DMA queue + * @vb: ptr to vb2_buffer + * + * This callback fucntion queues the buffer to DMA engine + */ +static void vpif_buffer_queue(struct vb2_buffer *vb) +{ +	struct vpif_disp_buffer *buf = to_vpif_buffer(vb); +	struct channel_obj *ch = vb2_get_drv_priv(vb->vb2_queue); +	struct common_obj *common; +	unsigned long flags; + +	common = &ch->common[VPIF_VIDEO_INDEX]; + +	/* add the buffer to the DMA queue */ +	spin_lock_irqsave(&common->irqlock, flags); +	list_add_tail(&buf->list, &common->dma_queue); +	spin_unlock_irqrestore(&common->irqlock, flags); +} + +/** + * vpif_start_streaming : Starts the DMA engine for streaming + * @vb: ptr to vb2_buffer + * @count: number of buffers + */ +static int vpif_start_streaming(struct vb2_queue *vq, unsigned int count) +{ +	struct vpif_display_config *vpif_config_data = +					vpif_dev->platform_data; +	struct channel_obj *ch = vb2_get_drv_priv(vq); +	struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; +	struct vpif_params *vpif = &ch->vpifparams; +	struct vpif_disp_buffer *buf, *tmp; +	unsigned long addr, flags; +	int ret; + +	spin_lock_irqsave(&common->irqlock, flags); + +	/* Initialize field_id */ +	ch->field_id = 0; + +	/* clock settings */ +	if (vpif_config_data->set_clock) { +		ret = vpif_config_data->set_clock(ch->vpifparams.std_info. +		ycmux_mode, ch->vpifparams.std_info.hd_sd); +		if (ret < 0) { +			vpif_err("can't set clock\n"); +			goto err; +		} +	} + +	/* set the parameters and addresses */ +	ret = vpif_set_video_params(vpif, ch->channel_id + 2); +	if (ret < 0) +		goto err; + +	ycmux_mode = ret; +	vpif_config_addr(ch, ret); +	/* Get the next frame from the buffer queue */ +	common->next_frm = common->cur_frm = +			    list_entry(common->dma_queue.next, +				       struct vpif_disp_buffer, list); + +	list_del(&common->cur_frm->list); +	spin_unlock_irqrestore(&common->irqlock, flags); +	/* Mark state of the current frame to active */ +	common->cur_frm->vb.state = VB2_BUF_STATE_ACTIVE; + +	addr = vb2_dma_contig_plane_dma_addr(&common->cur_frm->vb, 0); +	common->set_addr((addr + common->ytop_off), +			    (addr + common->ybtm_off), +			    (addr + common->ctop_off), +			    (addr + common->cbtm_off)); + +	/* +	 * Set interrupt for both the fields in VPIF +	 * Register enable channel in VPIF register +	 */ +	channel_first_int[VPIF_VIDEO_INDEX][ch->channel_id] = 1; +	if (VPIF_CHANNEL2_VIDEO == ch->channel_id) { +		channel2_intr_assert(); +		channel2_intr_enable(1); +		enable_channel2(1); +		if (vpif_config_data->chan_config[VPIF_CHANNEL2_VIDEO].clip_en) +			channel2_clipping_enable(1); +	} + +	if (VPIF_CHANNEL3_VIDEO == ch->channel_id || ycmux_mode == 2) { +		channel3_intr_assert(); +		channel3_intr_enable(1); +		enable_channel3(1); +		if (vpif_config_data->chan_config[VPIF_CHANNEL3_VIDEO].clip_en) +			channel3_clipping_enable(1); +	} + +	return 0; + +err: +	list_for_each_entry_safe(buf, tmp, &common->dma_queue, list) { +		list_del(&buf->list); +		vb2_buffer_done(&buf->vb, VB2_BUF_STATE_QUEUED); +	} +	spin_unlock_irqrestore(&common->irqlock, flags); + +	return ret; +} + +/** + * vpif_stop_streaming : Stop the DMA engine + * @vq: ptr to vb2_queue + * + * This callback stops the DMA engine and any remaining buffers + * in the DMA queue are released. + */ +static void vpif_stop_streaming(struct vb2_queue *vq) +{ +	struct channel_obj *ch = vb2_get_drv_priv(vq); +	struct common_obj *common; +	unsigned long flags; + +	common = &ch->common[VPIF_VIDEO_INDEX]; + +	/* Disable channel */ +	if (VPIF_CHANNEL2_VIDEO == ch->channel_id) { +		enable_channel2(0); +		channel2_intr_enable(0); +	} +	if (VPIF_CHANNEL3_VIDEO == ch->channel_id || ycmux_mode == 2) { +		enable_channel3(0); +		channel3_intr_enable(0); +	} + +	/* release all active buffers */ +	spin_lock_irqsave(&common->irqlock, flags); +	if (common->cur_frm == common->next_frm) { +		vb2_buffer_done(&common->cur_frm->vb, VB2_BUF_STATE_ERROR); +	} else { +		if (common->cur_frm != NULL) +			vb2_buffer_done(&common->cur_frm->vb, +					VB2_BUF_STATE_ERROR); +		if (common->next_frm != NULL) +			vb2_buffer_done(&common->next_frm->vb, +					VB2_BUF_STATE_ERROR); +	} + +	while (!list_empty(&common->dma_queue)) { +		common->next_frm = list_entry(common->dma_queue.next, +						struct vpif_disp_buffer, list); +		list_del(&common->next_frm->list); +		vb2_buffer_done(&common->next_frm->vb, VB2_BUF_STATE_ERROR); +	} +	spin_unlock_irqrestore(&common->irqlock, flags); +} + +static struct vb2_ops video_qops = { +	.queue_setup		= vpif_buffer_queue_setup, +	.wait_prepare		= vb2_ops_wait_prepare, +	.wait_finish		= vb2_ops_wait_finish, +	.buf_prepare		= vpif_buffer_prepare, +	.start_streaming	= vpif_start_streaming, +	.stop_streaming		= vpif_stop_streaming, +	.buf_queue		= vpif_buffer_queue, +}; + +static void process_progressive_mode(struct common_obj *common) +{ +	unsigned long addr = 0; + +	spin_lock(&common->irqlock); +	/* Get the next buffer from buffer queue */ +	common->next_frm = list_entry(common->dma_queue.next, +				struct vpif_disp_buffer, list); +	/* Remove that buffer from the buffer queue */ +	list_del(&common->next_frm->list); +	spin_unlock(&common->irqlock); +	/* Mark status of the buffer as active */ +	common->next_frm->vb.state = VB2_BUF_STATE_ACTIVE; + +	/* Set top and bottom field addrs in VPIF registers */ +	addr = vb2_dma_contig_plane_dma_addr(&common->next_frm->vb, 0); +	common->set_addr(addr + common->ytop_off, +				 addr + common->ybtm_off, +				 addr + common->ctop_off, +				 addr + common->cbtm_off); +} + +static void process_interlaced_mode(int fid, struct common_obj *common) +{ +	/* device field id and local field id are in sync */ +	/* If this is even field */ +	if (0 == fid) { +		if (common->cur_frm == common->next_frm) +			return; + +		/* one frame is displayed If next frame is +		 *  available, release cur_frm and move on */ +		/* Copy frame display time */ +		v4l2_get_timestamp(&common->cur_frm->vb.v4l2_buf.timestamp); +		/* Change status of the cur_frm */ +		vb2_buffer_done(&common->cur_frm->vb, +					    VB2_BUF_STATE_DONE); +		/* Make cur_frm pointing to next_frm */ +		common->cur_frm = common->next_frm; + +	} else if (1 == fid) {	/* odd field */ +		spin_lock(&common->irqlock); +		if (list_empty(&common->dma_queue) +		    || (common->cur_frm != common->next_frm)) { +			spin_unlock(&common->irqlock); +			return; +		} +		spin_unlock(&common->irqlock); +		/* one field is displayed configure the next +		 * frame if it is available else hold on current +		 * frame */ +		/* Get next from the buffer queue */ +		process_progressive_mode(common); +	} +} + +/* + * vpif_channel_isr: It changes status of the displayed buffer, takes next + * buffer from the queue and sets its address in VPIF registers + */ +static irqreturn_t vpif_channel_isr(int irq, void *dev_id) +{ +	struct vpif_device *dev = &vpif_obj; +	struct channel_obj *ch; +	struct common_obj *common; +	enum v4l2_field field; +	int fid = -1, i; +	int channel_id = 0; + +	channel_id = *(int *)(dev_id); +	if (!vpif_intr_status(channel_id + 2)) +		return IRQ_NONE; + +	ch = dev->dev[channel_id]; +	field = ch->common[VPIF_VIDEO_INDEX].fmt.fmt.pix.field; +	for (i = 0; i < VPIF_NUMOBJECTS; i++) { +		common = &ch->common[i]; +		/* If streaming is started in this channel */ + +		if (1 == ch->vpifparams.std_info.frm_fmt) { +			spin_lock(&common->irqlock); +			if (list_empty(&common->dma_queue)) { +				spin_unlock(&common->irqlock); +				continue; +			} +			spin_unlock(&common->irqlock); + +			/* Progressive mode */ +			if (!channel_first_int[i][channel_id]) { +				/* Mark status of the cur_frm to +				 * done and unlock semaphore on it */ +				v4l2_get_timestamp(&common->cur_frm->vb. +						   v4l2_buf.timestamp); +				vb2_buffer_done(&common->cur_frm->vb, +					    VB2_BUF_STATE_DONE); +				/* Make cur_frm pointing to next_frm */ +				common->cur_frm = common->next_frm; +			} + +			channel_first_int[i][channel_id] = 0; +			process_progressive_mode(common); +		} else { +			/* Interlaced mode */ +			/* If it is first interrupt, ignore it */ + +			if (channel_first_int[i][channel_id]) { +				channel_first_int[i][channel_id] = 0; +				continue; +			} + +			if (0 == i) { +				ch->field_id ^= 1; +				/* Get field id from VPIF registers */ +				fid = vpif_channel_getfid(ch->channel_id + 2); +				/* If fid does not match with stored field id */ +				if (fid != ch->field_id) { +					/* Make them in sync */ +					if (0 == fid) +						ch->field_id = fid; + +					return IRQ_HANDLED; +				} +			} +			process_interlaced_mode(fid, common); +		} +	} + +	return IRQ_HANDLED; +} + +static int vpif_update_std_info(struct channel_obj *ch) +{ +	struct video_obj *vid_ch = &ch->video; +	struct vpif_params *vpifparams = &ch->vpifparams; +	struct vpif_channel_config_params *std_info = &vpifparams->std_info; +	const struct vpif_channel_config_params *config; + +	int i; + +	for (i = 0; i < vpif_ch_params_count; i++) { +		config = &vpif_ch_params[i]; +		if (config->hd_sd == 0) { +			vpif_dbg(2, debug, "SD format\n"); +			if (config->stdid & vid_ch->stdid) { +				memcpy(std_info, config, sizeof(*config)); +				break; +			} +		} +	} + +	if (i == vpif_ch_params_count) { +		vpif_dbg(1, debug, "Format not found\n"); +		return -EINVAL; +	} + +	return 0; +} + +static int vpif_update_resolution(struct channel_obj *ch) +{ +	struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; +	struct video_obj *vid_ch = &ch->video; +	struct vpif_params *vpifparams = &ch->vpifparams; +	struct vpif_channel_config_params *std_info = &vpifparams->std_info; + +	if (!vid_ch->stdid && !vid_ch->dv_timings.bt.height) +		return -EINVAL; + +	if (vid_ch->stdid) { +		if (vpif_update_std_info(ch)) +			return -EINVAL; +	} + +	common->fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUV422P; +	common->fmt.fmt.pix.width = std_info->width; +	common->fmt.fmt.pix.height = std_info->height; +	vpif_dbg(1, debug, "Pixel details: Width = %d,Height = %d\n", +			common->fmt.fmt.pix.width, common->fmt.fmt.pix.height); + +	/* Set height and width paramateres */ +	common->height = std_info->height; +	common->width = std_info->width; +	common->fmt.fmt.pix.sizeimage = common->height * common->width * 2; + +	if (vid_ch->stdid) +		common->fmt.fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; +	else +		common->fmt.fmt.pix.colorspace = V4L2_COLORSPACE_REC709; + +	if (ch->vpifparams.std_info.frm_fmt) +		common->fmt.fmt.pix.field = V4L2_FIELD_NONE; +	else +		common->fmt.fmt.pix.field = V4L2_FIELD_INTERLACED; + +	return 0; +} + +/* + * vpif_calculate_offsets: This function calculates buffers offset for Y and C + * in the top and bottom field + */ +static void vpif_calculate_offsets(struct channel_obj *ch) +{ +	struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; +	struct vpif_params *vpifparams = &ch->vpifparams; +	enum v4l2_field field = common->fmt.fmt.pix.field; +	struct video_obj *vid_ch = &ch->video; +	unsigned int hpitch, vpitch, sizeimage; + +	if (V4L2_FIELD_ANY == common->fmt.fmt.pix.field) { +		if (ch->vpifparams.std_info.frm_fmt) +			vid_ch->buf_field = V4L2_FIELD_NONE; +		else +			vid_ch->buf_field = V4L2_FIELD_INTERLACED; +	} else { +		vid_ch->buf_field = common->fmt.fmt.pix.field; +	} + +	sizeimage = common->fmt.fmt.pix.sizeimage; + +	hpitch = common->fmt.fmt.pix.bytesperline; +	vpitch = sizeimage / (hpitch * 2); +	if ((V4L2_FIELD_NONE == vid_ch->buf_field) || +	    (V4L2_FIELD_INTERLACED == vid_ch->buf_field)) { +		common->ytop_off = 0; +		common->ybtm_off = hpitch; +		common->ctop_off = sizeimage / 2; +		common->cbtm_off = sizeimage / 2 + hpitch; +	} else if (V4L2_FIELD_SEQ_TB == vid_ch->buf_field) { +		common->ytop_off = 0; +		common->ybtm_off = sizeimage / 4; +		common->ctop_off = sizeimage / 2; +		common->cbtm_off = common->ctop_off + sizeimage / 4; +	} else if (V4L2_FIELD_SEQ_BT == vid_ch->buf_field) { +		common->ybtm_off = 0; +		common->ytop_off = sizeimage / 4; +		common->cbtm_off = sizeimage / 2; +		common->ctop_off = common->cbtm_off + sizeimage / 4; +	} + +	if ((V4L2_FIELD_NONE == vid_ch->buf_field) || +	    (V4L2_FIELD_INTERLACED == vid_ch->buf_field)) { +		vpifparams->video_params.storage_mode = 1; +	} else { +		vpifparams->video_params.storage_mode = 0; +	} + +	if (ch->vpifparams.std_info.frm_fmt == 1) { +		vpifparams->video_params.hpitch = +		    common->fmt.fmt.pix.bytesperline; +	} else { +		if ((field == V4L2_FIELD_ANY) || +			(field == V4L2_FIELD_INTERLACED)) +			vpifparams->video_params.hpitch = +			    common->fmt.fmt.pix.bytesperline * 2; +		else +			vpifparams->video_params.hpitch = +			    common->fmt.fmt.pix.bytesperline; +	} + +	ch->vpifparams.video_params.stdid = ch->vpifparams.std_info.stdid; +} + +static void vpif_config_addr(struct channel_obj *ch, int muxmode) +{ +	struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + +	if (VPIF_CHANNEL3_VIDEO == ch->channel_id) { +		common->set_addr = ch3_set_videobuf_addr; +	} else { +		if (2 == muxmode) +			common->set_addr = ch2_set_videobuf_addr_yc_nmux; +		else +			common->set_addr = ch2_set_videobuf_addr; +	} +} + +/* functions implementing ioctls */ +/** + * vpif_querycap() - QUERYCAP handler + * @file: file ptr + * @priv: file handle + * @cap: ptr to v4l2_capability structure + */ +static int vpif_querycap(struct file *file, void  *priv, +				struct v4l2_capability *cap) +{ +	struct vpif_display_config *config = vpif_dev->platform_data; + +	cap->device_caps = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING; +	cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; +	strlcpy(cap->driver, VPIF_DRIVER_NAME, sizeof(cap->driver)); +	snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s", +		 dev_name(vpif_dev)); +	strlcpy(cap->card, config->card_name, sizeof(cap->card)); + +	return 0; +} + +static int vpif_enum_fmt_vid_out(struct file *file, void  *priv, +					struct v4l2_fmtdesc *fmt) +{ +	if (fmt->index != 0) +		return -EINVAL; + +	/* Fill in the information about format */ +	fmt->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; +	strcpy(fmt->description, "YCbCr4:2:2 YC Planar"); +	fmt->pixelformat = V4L2_PIX_FMT_YUV422P; +	fmt->flags = 0; +	return 0; +} + +static int vpif_g_fmt_vid_out(struct file *file, void *priv, +				struct v4l2_format *fmt) +{ +	struct video_device *vdev = video_devdata(file); +	struct channel_obj *ch = video_get_drvdata(vdev); +	struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + +	/* Check the validity of the buffer type */ +	if (common->fmt.type != fmt->type) +		return -EINVAL; + +	if (vpif_update_resolution(ch)) +		return -EINVAL; +	*fmt = common->fmt; +	return 0; +} + +static int vpif_try_fmt_vid_out(struct file *file, void *priv, +				struct v4l2_format *fmt) +{ +	struct video_device *vdev = video_devdata(file); +	struct channel_obj *ch = video_get_drvdata(vdev); +	struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; +	struct v4l2_pix_format *pixfmt = &fmt->fmt.pix; + +	/* +	 * to supress v4l-compliance warnings silently correct +	 * the pixelformat +	 */ +	if (pixfmt->pixelformat != V4L2_PIX_FMT_YUV422P) +		pixfmt->pixelformat = common->fmt.fmt.pix.pixelformat; + +	if (vpif_update_resolution(ch)) +		return -EINVAL; + +	pixfmt->colorspace = common->fmt.fmt.pix.colorspace; +	pixfmt->field = common->fmt.fmt.pix.field; +	pixfmt->bytesperline = common->fmt.fmt.pix.width; +	pixfmt->width = common->fmt.fmt.pix.width; +	pixfmt->height = common->fmt.fmt.pix.height; +	pixfmt->sizeimage = pixfmt->bytesperline * pixfmt->height * 2; +	pixfmt->priv = 0; + +	return 0; +} + +static int vpif_s_fmt_vid_out(struct file *file, void *priv, +				struct v4l2_format *fmt) +{ +	struct video_device *vdev = video_devdata(file); +	struct channel_obj *ch = video_get_drvdata(vdev); +	struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; +	struct v4l2_pix_format *pixfmt = &fmt->fmt.pix; +	int ret; + +	if (vb2_is_busy(&common->buffer_queue)) +		return -EBUSY; + +	ret = vpif_try_fmt_vid_out(file, priv, fmt); +	if (ret) +		return ret; + +	/* store the pix format in the channel object */ +	common->fmt.fmt.pix = *pixfmt; + +	/* store the format in the channel object */ +	common->fmt = *fmt; +	return 0; +} + +static int vpif_s_std(struct file *file, void *priv, v4l2_std_id std_id) +{ +	struct vpif_display_config *config = vpif_dev->platform_data; +	struct video_device *vdev = video_devdata(file); +	struct channel_obj *ch = video_get_drvdata(vdev); +	struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; +	struct vpif_display_chan_config *chan_cfg; +	struct v4l2_output output; +	int ret; + +	if (config->chan_config[ch->channel_id].outputs == NULL) +		return -ENODATA; + +	chan_cfg = &config->chan_config[ch->channel_id]; +	output = chan_cfg->outputs[ch->output_idx].output; +	if (output.capabilities != V4L2_OUT_CAP_STD) +		return -ENODATA; + +	if (vb2_is_busy(&common->buffer_queue)) +		return -EBUSY; + + +	if (!(std_id & VPIF_V4L2_STD)) +		return -EINVAL; + +	/* Call encoder subdevice function to set the standard */ +	ch->video.stdid = std_id; +	memset(&ch->video.dv_timings, 0, sizeof(ch->video.dv_timings)); +	/* Get the information about the standard */ +	if (vpif_update_resolution(ch)) +		return -EINVAL; + +	common->fmt.fmt.pix.bytesperline = common->fmt.fmt.pix.width; + +	ret = v4l2_device_call_until_err(&vpif_obj.v4l2_dev, 1, video, +						s_std_output, std_id); +	if (ret < 0) { +		vpif_err("Failed to set output standard\n"); +		return ret; +	} + +	ret = v4l2_device_call_until_err(&vpif_obj.v4l2_dev, 1, video, +							s_std, std_id); +	if (ret < 0) +		vpif_err("Failed to set standard for sub devices\n"); +	return ret; +} + +static int vpif_g_std(struct file *file, void *priv, v4l2_std_id *std) +{ +	struct vpif_display_config *config = vpif_dev->platform_data; +	struct video_device *vdev = video_devdata(file); +	struct channel_obj *ch = video_get_drvdata(vdev); +	struct vpif_display_chan_config *chan_cfg; +	struct v4l2_output output; + +	if (config->chan_config[ch->channel_id].outputs == NULL) +		return -ENODATA; + +	chan_cfg = &config->chan_config[ch->channel_id]; +	output = chan_cfg->outputs[ch->output_idx].output; +	if (output.capabilities != V4L2_OUT_CAP_STD) +		return -ENODATA; + +	*std = ch->video.stdid; +	return 0; +} + +static int vpif_enum_output(struct file *file, void *fh, +				struct v4l2_output *output) +{ + +	struct vpif_display_config *config = vpif_dev->platform_data; +	struct video_device *vdev = video_devdata(file); +	struct channel_obj *ch = video_get_drvdata(vdev); +	struct vpif_display_chan_config *chan_cfg; + +	chan_cfg = &config->chan_config[ch->channel_id]; +	if (output->index >= chan_cfg->output_count) { +		vpif_dbg(1, debug, "Invalid output index\n"); +		return -EINVAL; +	} + +	*output = chan_cfg->outputs[output->index].output; +	return 0; +} + +/** + * vpif_output_to_subdev() - Maps output to sub device + * @vpif_cfg - global config ptr + * @chan_cfg - channel config ptr + * @index - Given output index from application + * + * lookup the sub device information for a given output index. + * we report all the output to application. output table also + * has sub device name for the each output + */ +static int +vpif_output_to_subdev(struct vpif_display_config *vpif_cfg, +		      struct vpif_display_chan_config *chan_cfg, int index) +{ +	struct vpif_subdev_info *subdev_info; +	const char *subdev_name; +	int i; + +	vpif_dbg(2, debug, "vpif_output_to_subdev\n"); + +	if (chan_cfg->outputs == NULL) +		return -1; + +	subdev_name = chan_cfg->outputs[index].subdev_name; +	if (subdev_name == NULL) +		return -1; + +	/* loop through the sub device list to get the sub device info */ +	for (i = 0; i < vpif_cfg->subdev_count; i++) { +		subdev_info = &vpif_cfg->subdevinfo[i]; +		if (!strcmp(subdev_info->name, subdev_name)) +			return i; +	} +	return -1; +} + +/** + * vpif_set_output() - Select an output + * @vpif_cfg - global config ptr + * @ch - channel + * @index - Given output index from application + * + * Select the given output. + */ +static int vpif_set_output(struct vpif_display_config *vpif_cfg, +		      struct channel_obj *ch, int index) +{ +	struct vpif_display_chan_config *chan_cfg = +		&vpif_cfg->chan_config[ch->channel_id]; +	struct vpif_subdev_info *subdev_info = NULL; +	struct v4l2_subdev *sd = NULL; +	u32 input = 0, output = 0; +	int sd_index; +	int ret; + +	sd_index = vpif_output_to_subdev(vpif_cfg, chan_cfg, index); +	if (sd_index >= 0) { +		sd = vpif_obj.sd[sd_index]; +		subdev_info = &vpif_cfg->subdevinfo[sd_index]; +	} + +	if (sd) { +		input = chan_cfg->outputs[index].input_route; +		output = chan_cfg->outputs[index].output_route; +		ret = v4l2_subdev_call(sd, video, s_routing, input, output, 0); +		if (ret < 0 && ret != -ENOIOCTLCMD) { +			vpif_err("Failed to set output\n"); +			return ret; +		} + +	} +	ch->output_idx = index; +	ch->sd = sd; +	if (chan_cfg->outputs != NULL) +		/* update tvnorms from the sub device output info */ +		ch->video_dev->tvnorms = chan_cfg->outputs[index].output.std; +	return 0; +} + +static int vpif_s_output(struct file *file, void *priv, unsigned int i) +{ +	struct vpif_display_config *config = vpif_dev->platform_data; +	struct video_device *vdev = video_devdata(file); +	struct channel_obj *ch = video_get_drvdata(vdev); +	struct vpif_display_chan_config *chan_cfg; +	struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + +	if (vb2_is_busy(&common->buffer_queue)) +		return -EBUSY; + +	chan_cfg = &config->chan_config[ch->channel_id]; + +	if (i >= chan_cfg->output_count) +		return -EINVAL; + +	return vpif_set_output(config, ch, i); +} + +static int vpif_g_output(struct file *file, void *priv, unsigned int *i) +{ +	struct video_device *vdev = video_devdata(file); +	struct channel_obj *ch = video_get_drvdata(vdev); + +	*i = ch->output_idx; + +	return 0; +} + +/** + * vpif_enum_dv_timings() - ENUM_DV_TIMINGS handler + * @file: file ptr + * @priv: file handle + * @timings: input timings + */ +static int +vpif_enum_dv_timings(struct file *file, void *priv, +		     struct v4l2_enum_dv_timings *timings) +{ +	struct vpif_display_config *config = vpif_dev->platform_data; +	struct video_device *vdev = video_devdata(file); +	struct channel_obj *ch = video_get_drvdata(vdev); +	struct vpif_display_chan_config *chan_cfg; +	struct v4l2_output output; +	int ret; + +	if (config->chan_config[ch->channel_id].outputs == NULL) +		return -ENODATA; + +	chan_cfg = &config->chan_config[ch->channel_id]; +	output = chan_cfg->outputs[ch->output_idx].output; +	if (output.capabilities != V4L2_OUT_CAP_DV_TIMINGS) +		return -ENODATA; + +	timings->pad = 0; + +	ret = v4l2_subdev_call(ch->sd, pad, enum_dv_timings, timings); +	if (ret == -ENOIOCTLCMD || ret == -ENODEV) +		return -EINVAL; +	return ret; +} + +/** + * vpif_s_dv_timings() - S_DV_TIMINGS handler + * @file: file ptr + * @priv: file handle + * @timings: digital video timings + */ +static int vpif_s_dv_timings(struct file *file, void *priv, +		struct v4l2_dv_timings *timings) +{ +	struct vpif_display_config *config = vpif_dev->platform_data; +	struct video_device *vdev = video_devdata(file); +	struct channel_obj *ch = video_get_drvdata(vdev); +	struct vpif_params *vpifparams = &ch->vpifparams; +	struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; +	struct vpif_channel_config_params *std_info = &vpifparams->std_info; +	struct video_obj *vid_ch = &ch->video; +	struct v4l2_bt_timings *bt = &vid_ch->dv_timings.bt; +	struct vpif_display_chan_config *chan_cfg; +	struct v4l2_output output; +	int ret; + +	if (config->chan_config[ch->channel_id].outputs == NULL) +		return -ENODATA; + +	chan_cfg = &config->chan_config[ch->channel_id]; +	output = chan_cfg->outputs[ch->output_idx].output; +	if (output.capabilities != V4L2_OUT_CAP_DV_TIMINGS) +		return -ENODATA; + +	if (vb2_is_busy(&common->buffer_queue)) +		return -EBUSY; + +	if (timings->type != V4L2_DV_BT_656_1120) { +		vpif_dbg(2, debug, "Timing type not defined\n"); +		return -EINVAL; +	} + +	/* Configure subdevice timings, if any */ +	ret = v4l2_subdev_call(ch->sd, video, s_dv_timings, timings); +	if (ret == -ENOIOCTLCMD || ret == -ENODEV) +		ret = 0; +	if (ret < 0) { +		vpif_dbg(2, debug, "Error setting custom DV timings\n"); +		return ret; +	} + +	if (!(timings->bt.width && timings->bt.height && +				(timings->bt.hbackporch || +				 timings->bt.hfrontporch || +				 timings->bt.hsync) && +				timings->bt.vfrontporch && +				(timings->bt.vbackporch || +				 timings->bt.vsync))) { +		vpif_dbg(2, debug, "Timings for width, height, " +				"horizontal back porch, horizontal sync, " +				"horizontal front porch, vertical back porch, " +				"vertical sync and vertical back porch " +				"must be defined\n"); +		return -EINVAL; +	} + +	vid_ch->dv_timings = *timings; + +	/* Configure video port timings */ + +	std_info->eav2sav = V4L2_DV_BT_BLANKING_WIDTH(bt) - 8; +	std_info->sav2eav = bt->width; + +	std_info->l1 = 1; +	std_info->l3 = bt->vsync + bt->vbackporch + 1; + +	std_info->vsize = V4L2_DV_BT_FRAME_HEIGHT(bt); +	if (bt->interlaced) { +		if (bt->il_vbackporch || bt->il_vfrontporch || bt->il_vsync) { +			std_info->l5 = std_info->vsize/2 - +				(bt->vfrontporch - 1); +			std_info->l7 = std_info->vsize/2 + 1; +			std_info->l9 = std_info->l7 + bt->il_vsync + +				bt->il_vbackporch + 1; +			std_info->l11 = std_info->vsize - +				(bt->il_vfrontporch - 1); +		} else { +			vpif_dbg(2, debug, "Required timing values for " +					"interlaced BT format missing\n"); +			return -EINVAL; +		} +	} else { +		std_info->l5 = std_info->vsize - (bt->vfrontporch - 1); +	} +	strncpy(std_info->name, "Custom timings BT656/1120", +			VPIF_MAX_NAME); +	std_info->width = bt->width; +	std_info->height = bt->height; +	std_info->frm_fmt = bt->interlaced ? 0 : 1; +	std_info->ycmux_mode = 0; +	std_info->capture_format = 0; +	std_info->vbi_supported = 0; +	std_info->hd_sd = 1; +	std_info->stdid = 0; +	vid_ch->stdid = 0; + +	return 0; +} + +/** + * vpif_g_dv_timings() - G_DV_TIMINGS handler + * @file: file ptr + * @priv: file handle + * @timings: digital video timings + */ +static int vpif_g_dv_timings(struct file *file, void *priv, +		struct v4l2_dv_timings *timings) +{ +	struct vpif_display_config *config = vpif_dev->platform_data; +	struct video_device *vdev = video_devdata(file); +	struct channel_obj *ch = video_get_drvdata(vdev); +	struct vpif_display_chan_config *chan_cfg; +	struct video_obj *vid_ch = &ch->video; +	struct v4l2_output output; + +	if (config->chan_config[ch->channel_id].outputs == NULL) +		goto error; + +	chan_cfg = &config->chan_config[ch->channel_id]; +	output = chan_cfg->outputs[ch->output_idx].output; + +	if (output.capabilities != V4L2_OUT_CAP_DV_TIMINGS) +		goto error; + +	*timings = vid_ch->dv_timings; + +	return 0; +error: +	return -ENODATA; +} + +/* + * vpif_log_status() - Status information + * @file: file ptr + * @priv: file handle + * + * Returns zero. + */ +static int vpif_log_status(struct file *filep, void *priv) +{ +	/* status for sub devices */ +	v4l2_device_call_all(&vpif_obj.v4l2_dev, 0, core, log_status); + +	return 0; +} + +/* vpif display ioctl operations */ +static const struct v4l2_ioctl_ops vpif_ioctl_ops = { +	.vidioc_querycap		= vpif_querycap, +	.vidioc_enum_fmt_vid_out	= vpif_enum_fmt_vid_out, +	.vidioc_g_fmt_vid_out		= vpif_g_fmt_vid_out, +	.vidioc_s_fmt_vid_out		= vpif_s_fmt_vid_out, +	.vidioc_try_fmt_vid_out		= vpif_try_fmt_vid_out, + +	.vidioc_reqbufs			= vb2_ioctl_reqbufs, +	.vidioc_create_bufs		= vb2_ioctl_create_bufs, +	.vidioc_querybuf		= vb2_ioctl_querybuf, +	.vidioc_qbuf			= vb2_ioctl_qbuf, +	.vidioc_dqbuf			= vb2_ioctl_dqbuf, +	.vidioc_expbuf			= vb2_ioctl_expbuf, +	.vidioc_streamon		= vb2_ioctl_streamon, +	.vidioc_streamoff		= vb2_ioctl_streamoff, + +	.vidioc_s_std			= vpif_s_std, +	.vidioc_g_std			= vpif_g_std, + +	.vidioc_enum_output		= vpif_enum_output, +	.vidioc_s_output		= vpif_s_output, +	.vidioc_g_output		= vpif_g_output, + +	.vidioc_enum_dv_timings		= vpif_enum_dv_timings, +	.vidioc_s_dv_timings		= vpif_s_dv_timings, +	.vidioc_g_dv_timings		= vpif_g_dv_timings, + +	.vidioc_log_status		= vpif_log_status, +}; + +static const struct v4l2_file_operations vpif_fops = { +	.owner		= THIS_MODULE, +	.open		= v4l2_fh_open, +	.release	= vb2_fop_release, +	.unlocked_ioctl	= video_ioctl2, +	.mmap		= vb2_fop_mmap, +	.poll		= vb2_fop_poll +}; + +/*Configure the channels, buffer sizei, request irq */ +static int initialize_vpif(void) +{ +	int free_channel_objects_index; +	int err, i, j; + +	/* Allocate memory for six channel objects */ +	for (i = 0; i < VPIF_DISPLAY_MAX_DEVICES; i++) { +		vpif_obj.dev[i] = +		    kzalloc(sizeof(struct channel_obj), GFP_KERNEL); +		/* If memory allocation fails, return error */ +		if (!vpif_obj.dev[i]) { +			free_channel_objects_index = i; +			err = -ENOMEM; +			goto vpif_init_free_channel_objects; +		} +	} + +	return 0; + +vpif_init_free_channel_objects: +	for (j = 0; j < free_channel_objects_index; j++) +		kfree(vpif_obj.dev[j]); +	return err; +} + +static int vpif_async_bound(struct v4l2_async_notifier *notifier, +			    struct v4l2_subdev *subdev, +			    struct v4l2_async_subdev *asd) +{ +	int i; + +	for (i = 0; i < vpif_obj.config->subdev_count; i++) +		if (!strcmp(vpif_obj.config->subdevinfo[i].name, +			    subdev->name)) { +			vpif_obj.sd[i] = subdev; +			vpif_obj.sd[i]->grp_id = 1 << i; +			return 0; +		} + +	return -EINVAL; +} + +static int vpif_probe_complete(void) +{ +	struct common_obj *common; +	struct video_device *vdev; +	struct channel_obj *ch; +	struct vb2_queue *q; +	int j, err, k; + +	for (j = 0; j < VPIF_DISPLAY_MAX_DEVICES; j++) { +		ch = vpif_obj.dev[j]; +		/* Initialize field of the channel objects */ +		for (k = 0; k < VPIF_NUMOBJECTS; k++) { +			common = &ch->common[k]; +			spin_lock_init(&common->irqlock); +			mutex_init(&common->lock); +			common->set_addr = NULL; +			common->ytop_off = 0; +			common->ybtm_off = 0; +			common->ctop_off = 0; +			common->cbtm_off = 0; +			common->cur_frm = NULL; +			common->next_frm = NULL; +			memset(&common->fmt, 0, sizeof(common->fmt)); +		} +		ch->initialized = 0; +		if (vpif_obj.config->subdev_count) +			ch->sd = vpif_obj.sd[0]; +		ch->channel_id = j; + +		memset(&ch->vpifparams, 0, sizeof(ch->vpifparams)); + +		ch->common[VPIF_VIDEO_INDEX].fmt.type = +						V4L2_BUF_TYPE_VIDEO_OUTPUT; + +		/* select output 0 */ +		err = vpif_set_output(vpif_obj.config, ch, 0); +		if (err) +			goto probe_out; + +		/* set initial format */ +		ch->video.stdid = V4L2_STD_525_60; +		memset(&ch->video.dv_timings, 0, sizeof(ch->video.dv_timings)); +		vpif_update_resolution(ch); + +		/* Initialize vb2 queue */ +		q = &common->buffer_queue; +		q->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; +		q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF; +		q->drv_priv = ch; +		q->ops = &video_qops; +		q->mem_ops = &vb2_dma_contig_memops; +		q->buf_struct_size = sizeof(struct vpif_disp_buffer); +		q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; +		q->min_buffers_needed = 1; +		q->lock = &common->lock; +		err = vb2_queue_init(q); +		if (err) { +			vpif_err("vpif_display: vb2_queue_init() failed\n"); +			goto probe_out; +		} + +		common->alloc_ctx = vb2_dma_contig_init_ctx(vpif_dev); +		if (IS_ERR(common->alloc_ctx)) { +			vpif_err("Failed to get the context\n"); +			err = PTR_ERR(common->alloc_ctx); +			goto probe_out; +		} + +		INIT_LIST_HEAD(&common->dma_queue); + +		/* register video device */ +		vpif_dbg(1, debug, "channel=%x,channel->video_dev=%x\n", +			 (int)ch, (int)&ch->video_dev); + +		/* Initialize the video_device structure */ +		vdev = ch->video_dev; +		strlcpy(vdev->name, VPIF_DRIVER_NAME, sizeof(vdev->name)); +		vdev->release = video_device_release; +		vdev->fops = &vpif_fops; +		vdev->ioctl_ops = &vpif_ioctl_ops; +		vdev->v4l2_dev = &vpif_obj.v4l2_dev; +		vdev->vfl_dir = VFL_DIR_TX; +		vdev->queue = q; +		vdev->lock = &common->lock; +		set_bit(V4L2_FL_USE_FH_PRIO, &vdev->flags); +		video_set_drvdata(ch->video_dev, ch); +		err = video_register_device(vdev, VFL_TYPE_GRABBER, +					    (j ? 3 : 2)); +		if (err < 0) +			goto probe_out; +	} + +	return 0; + +probe_out: +	for (k = 0; k < j; k++) { +		ch = vpif_obj.dev[k]; +		common = &ch->common[k]; +		vb2_dma_contig_cleanup_ctx(common->alloc_ctx); +		video_unregister_device(ch->video_dev); +		video_device_release(ch->video_dev); +		ch->video_dev = NULL; +	} +	return err; +} + +static int vpif_async_complete(struct v4l2_async_notifier *notifier) +{ +	return vpif_probe_complete(); +} + +/* + * vpif_probe: This function creates device entries by register itself to the + * V4L2 driver and initializes fields of each channel objects + */ +static __init int vpif_probe(struct platform_device *pdev) +{ +	struct vpif_subdev_info *subdevdata; +	int i, j = 0, err = 0; +	int res_idx = 0; +	struct i2c_adapter *i2c_adap; +	struct channel_obj *ch; +	struct video_device *vfd; +	struct resource *res; +	int subdev_count; + +	vpif_dev = &pdev->dev; +	err = initialize_vpif(); + +	if (err) { +		v4l2_err(vpif_dev->driver, "Error initializing vpif\n"); +		return err; +	} + +	err = v4l2_device_register(vpif_dev, &vpif_obj.v4l2_dev); +	if (err) { +		v4l2_err(vpif_dev->driver, "Error registering v4l2 device\n"); +		return err; +	} + +	while ((res = platform_get_resource(pdev, IORESOURCE_IRQ, res_idx))) { +		err = devm_request_irq(&pdev->dev, res->start, vpif_channel_isr, +					IRQF_SHARED, VPIF_DRIVER_NAME, +					(void *)(&vpif_obj.dev[res_idx]-> +					channel_id)); +		if (err) { +			err = -EINVAL; +			vpif_err("VPIF IRQ request failed\n"); +			goto vpif_unregister; +		} +		res_idx++; +	} + +	for (i = 0; i < VPIF_DISPLAY_MAX_DEVICES; i++) { +		/* Get the pointer to the channel object */ +		ch = vpif_obj.dev[i]; + +		/* Allocate memory for video device */ +		vfd = video_device_alloc(); +		if (vfd == NULL) { +			for (j = 0; j < i; j++) { +				ch = vpif_obj.dev[j]; +				video_device_release(ch->video_dev); +			} +			err = -ENOMEM; +			goto vpif_unregister; +		} + +		/* Set video_dev to the video device */ +		ch->video_dev = vfd; +	} + +	vpif_obj.config = pdev->dev.platform_data; +	subdev_count = vpif_obj.config->subdev_count; +	subdevdata = vpif_obj.config->subdevinfo; +	vpif_obj.sd = kzalloc(sizeof(struct v4l2_subdev *) * subdev_count, +								GFP_KERNEL); +	if (vpif_obj.sd == NULL) { +		vpif_err("unable to allocate memory for subdevice pointers\n"); +		err = -ENOMEM; +		goto vpif_sd_error; +	} + +	if (!vpif_obj.config->asd_sizes) { +		i2c_adap = i2c_get_adapter(1); +		for (i = 0; i < subdev_count; i++) { +			vpif_obj.sd[i] = +				v4l2_i2c_new_subdev_board(&vpif_obj.v4l2_dev, +							  i2c_adap, +							  &subdevdata[i]. +							  board_info, +							  NULL); +			if (!vpif_obj.sd[i]) { +				vpif_err("Error registering v4l2 subdevice\n"); +				err = -ENODEV; +				goto probe_subdev_out; +			} + +			if (vpif_obj.sd[i]) +				vpif_obj.sd[i]->grp_id = 1 << i; +		} +		vpif_probe_complete(); +	} else { +		vpif_obj.notifier.subdevs = vpif_obj.config->asd; +		vpif_obj.notifier.num_subdevs = vpif_obj.config->asd_sizes[0]; +		vpif_obj.notifier.bound = vpif_async_bound; +		vpif_obj.notifier.complete = vpif_async_complete; +		err = v4l2_async_notifier_register(&vpif_obj.v4l2_dev, +						   &vpif_obj.notifier); +		if (err) { +			vpif_err("Error registering async notifier\n"); +			err = -EINVAL; +			goto probe_subdev_out; +		} +	} + +	return 0; + +probe_subdev_out: +	kfree(vpif_obj.sd); +vpif_sd_error: +	for (i = 0; i < VPIF_DISPLAY_MAX_DEVICES; i++) { +		ch = vpif_obj.dev[i]; +		/* Note: does nothing if ch->video_dev == NULL */ +		video_device_release(ch->video_dev); +	} +vpif_unregister: +	v4l2_device_unregister(&vpif_obj.v4l2_dev); + +	return err; +} + +/* + * vpif_remove: It un-register channels from V4L2 driver + */ +static int vpif_remove(struct platform_device *device) +{ +	struct common_obj *common; +	struct channel_obj *ch; +	int i; + +	v4l2_device_unregister(&vpif_obj.v4l2_dev); + +	kfree(vpif_obj.sd); +	/* un-register device */ +	for (i = 0; i < VPIF_DISPLAY_MAX_DEVICES; i++) { +		/* Get the pointer to the channel object */ +		ch = vpif_obj.dev[i]; +		common = &ch->common[i]; +		vb2_dma_contig_cleanup_ctx(common->alloc_ctx); +		/* Unregister video device */ +		video_unregister_device(ch->video_dev); + +		ch->video_dev = NULL; +		kfree(vpif_obj.dev[i]); +	} + +	return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int vpif_suspend(struct device *dev) +{ +	struct common_obj *common; +	struct channel_obj *ch; +	int i; + +	for (i = 0; i < VPIF_DISPLAY_MAX_DEVICES; i++) { +		/* Get the pointer to the channel object */ +		ch = vpif_obj.dev[i]; +		common = &ch->common[VPIF_VIDEO_INDEX]; + +		if (!vb2_is_streaming(&common->buffer_queue)) +			continue; + +		mutex_lock(&common->lock); +		/* Disable channel */ +		if (ch->channel_id == VPIF_CHANNEL2_VIDEO) { +			enable_channel2(0); +			channel2_intr_enable(0); +		} +		if (ch->channel_id == VPIF_CHANNEL3_VIDEO || +			ycmux_mode == 2) { +			enable_channel3(0); +			channel3_intr_enable(0); +		} +		mutex_unlock(&common->lock); +	} + +	return 0; +} + +static int vpif_resume(struct device *dev) +{ + +	struct common_obj *common; +	struct channel_obj *ch; +	int i; + +	for (i = 0; i < VPIF_DISPLAY_MAX_DEVICES; i++) { +		/* Get the pointer to the channel object */ +		ch = vpif_obj.dev[i]; +		common = &ch->common[VPIF_VIDEO_INDEX]; + +		if (!vb2_is_streaming(&common->buffer_queue)) +			continue; + +		mutex_lock(&common->lock); +		/* Enable channel */ +		if (ch->channel_id == VPIF_CHANNEL2_VIDEO) { +			enable_channel2(1); +			channel2_intr_enable(1); +		} +		if (ch->channel_id == VPIF_CHANNEL3_VIDEO || +				ycmux_mode == 2) { +			enable_channel3(1); +			channel3_intr_enable(1); +		} +		mutex_unlock(&common->lock); +	} + +	return 0; +} + +#endif + +static SIMPLE_DEV_PM_OPS(vpif_pm_ops, vpif_suspend, vpif_resume); + +static __refdata struct platform_driver vpif_driver = { +	.driver	= { +			.name	= VPIF_DRIVER_NAME, +			.owner	= THIS_MODULE, +			.pm	= &vpif_pm_ops, +	}, +	.probe	= vpif_probe, +	.remove	= vpif_remove, +}; + +module_platform_driver(vpif_driver); diff --git a/drivers/media/platform/davinci/vpif_display.h b/drivers/media/platform/davinci/vpif_display.h new file mode 100644 index 00000000000..7b21a760767 --- /dev/null +++ b/drivers/media/platform/davinci/vpif_display.h @@ -0,0 +1,127 @@ +/* + * VPIF display header file + * + * Copyright (C) 2009 Texas Instruments Incorporated - http://www.ti.com/ + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed .as is. WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + */ + +#ifndef VPIF_DISPLAY_H +#define VPIF_DISPLAY_H + +/* Header files */ +#include <media/videobuf2-dma-contig.h> +#include <media/v4l2-device.h> + +#include "vpif.h" + +/* Macros */ +#define VPIF_DISPLAY_VERSION	"0.0.2" + +#define VPIF_VALID_FIELD(field) \ +	(((V4L2_FIELD_ANY == field) || (V4L2_FIELD_NONE == field)) || \ +	(((V4L2_FIELD_INTERLACED == field) || (V4L2_FIELD_SEQ_TB == field)) || \ +	(V4L2_FIELD_SEQ_BT == field))) + +#define VPIF_DISPLAY_MAX_DEVICES	(2) +#define VPIF_SLICED_BUF_SIZE		(256) +#define VPIF_SLICED_MAX_SERVICES	(3) +#define VPIF_VIDEO_INDEX		(0) +#define VPIF_VBI_INDEX			(1) +#define VPIF_HBI_INDEX			(2) + +/* Setting it to 1 as HBI/VBI support yet to be added , else 3*/ +#define VPIF_NUMOBJECTS	(1) + +/* Macros */ +#define ISALIGNED(a)    (0 == ((a) & 7)) + +/* enumerated data types */ +/* Enumerated data type to give id to each device per channel */ +enum vpif_channel_id { +	VPIF_CHANNEL2_VIDEO = 0,	/* Channel2 Video */ +	VPIF_CHANNEL3_VIDEO,		/* Channel3 Video */ +}; + +/* structures */ + +struct video_obj { +	enum v4l2_field buf_field; +	u32 latest_only;		/* indicate whether to return +					 * most recent displayed frame only */ +	v4l2_std_id stdid;		/* Currently selected or default +					 * standard */ +	struct v4l2_dv_timings dv_timings; +}; + +struct vpif_disp_buffer { +	struct vb2_buffer vb; +	struct list_head list; +}; + +struct common_obj { +	struct vpif_disp_buffer *cur_frm;	/* Pointer pointing to current +						 * vb2_buffer */ +	struct vpif_disp_buffer *next_frm;	/* Pointer pointing to next +						 * vb2_buffer */ +	struct v4l2_format fmt;			/* Used to store the format */ +	struct vb2_queue buffer_queue;		/* Buffer queue used in +						 * video-buf */ +	/* allocator-specific contexts for each plane */ +	struct vb2_alloc_ctx *alloc_ctx; + +	struct list_head dma_queue;		/* Queue of filled frames */ +	spinlock_t irqlock;			/* Used in video-buf */ + +	/* channel specific parameters */ +	struct mutex lock;			/* lock used to access this +						 * structure */ +	u32 ytop_off;				/* offset of Y top from the +						 * starting of the buffer */ +	u32 ybtm_off;				/* offset of Y bottom from the +						 * starting of the buffer */ +	u32 ctop_off;				/* offset of C top from the +						 * starting of the buffer */ +	u32 cbtm_off;				/* offset of C bottom from the +						 * starting of the buffer */ +	/* Function pointer to set the addresses */ +	void (*set_addr)(unsigned long, unsigned long, +				unsigned long, unsigned long); +	u32 height; +	u32 width; +}; + +struct channel_obj { +	/* V4l2 specific parameters */ +	struct video_device *video_dev;	/* Identifies video device for +					 * this channel */ +	u32 field_id;			/* Indicates id of the field +					 * which is being displayed */ +	u8 initialized;			/* flag to indicate whether +					 * encoder is initialized */ +	u32 output_idx;			/* Current output index */ +	struct v4l2_subdev *sd;		/* Current output subdev(may be NULL) */ + +	enum vpif_channel_id channel_id;/* Identifies channel */ +	struct vpif_params vpifparams; +	struct common_obj common[VPIF_NUMOBJECTS]; +	struct video_obj video; +}; + +/* vpif device structure */ +struct vpif_device { +	struct v4l2_device v4l2_dev; +	struct channel_obj *dev[VPIF_DISPLAY_NUM_CHANNELS]; +	struct v4l2_subdev **sd; +	struct v4l2_async_notifier notifier; +	struct vpif_display_config *config; +}; + +#endif				/* VPIF_DISPLAY_H */ diff --git a/drivers/media/platform/davinci/vpss.c b/drivers/media/platform/davinci/vpss.c new file mode 100644 index 00000000000..31120b4a4a3 --- /dev/null +++ b/drivers/media/platform/davinci/vpss.c @@ -0,0 +1,533 @@ +/* + * Copyright (C) 2009 Texas Instruments. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * common vpss system module platform driver for all video drivers. + */ +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/io.h> +#include <linux/pm_runtime.h> +#include <linux/err.h> + +#include <media/davinci/vpss.h> + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("VPSS Driver"); +MODULE_AUTHOR("Texas Instruments"); + +/* DM644x defines */ +#define DM644X_SBL_PCR_VPSS		(4) + +#define DM355_VPSSBL_INTSEL		0x10 +#define DM355_VPSSBL_EVTSEL		0x14 +/* vpss BL register offsets */ +#define DM355_VPSSBL_CCDCMUX		0x1c +/* vpss CLK register offsets */ +#define DM355_VPSSCLK_CLKCTRL		0x04 +/* masks and shifts */ +#define VPSS_HSSISEL_SHIFT		4 +/* + * VDINT0 - vpss_int0, VDINT1 - vpss_int1, H3A - vpss_int4, + * IPIPE_INT1_SDR - vpss_int5 + */ +#define DM355_VPSSBL_INTSEL_DEFAULT	0xff83ff10 +/* VENCINT - vpss_int8 */ +#define DM355_VPSSBL_EVTSEL_DEFAULT	0x4 + +#define DM365_ISP5_PCCR				0x04 +#define DM365_ISP5_PCCR_BL_CLK_ENABLE		BIT(0) +#define DM365_ISP5_PCCR_ISIF_CLK_ENABLE		BIT(1) +#define DM365_ISP5_PCCR_H3A_CLK_ENABLE		BIT(2) +#define DM365_ISP5_PCCR_RSZ_CLK_ENABLE		BIT(3) +#define DM365_ISP5_PCCR_IPIPE_CLK_ENABLE	BIT(4) +#define DM365_ISP5_PCCR_IPIPEIF_CLK_ENABLE	BIT(5) +#define DM365_ISP5_PCCR_RSV			BIT(6) + +#define DM365_ISP5_BCR			0x08 +#define DM365_ISP5_BCR_ISIF_OUT_ENABLE	BIT(1) + +#define DM365_ISP5_INTSEL1		0x10 +#define DM365_ISP5_INTSEL2		0x14 +#define DM365_ISP5_INTSEL3		0x18 +#define DM365_ISP5_CCDCMUX 		0x20 +#define DM365_ISP5_PG_FRAME_SIZE 	0x28 +#define DM365_VPBE_CLK_CTRL 		0x00 + +#define VPSS_CLK_CTRL			0x01c40044 +#define VPSS_CLK_CTRL_VENCCLKEN		BIT(3) +#define VPSS_CLK_CTRL_DACCLKEN		BIT(4) + +/* + * vpss interrupts. VDINT0 - vpss_int0, VDINT1 - vpss_int1, + * AF - vpss_int3 + */ +#define DM365_ISP5_INTSEL1_DEFAULT	0x0b1f0100 +/* AEW - vpss_int6, RSZ_INT_DMA - vpss_int5 */ +#define DM365_ISP5_INTSEL2_DEFAULT	0x1f0a0f1f +/* VENC - vpss_int8 */ +#define DM365_ISP5_INTSEL3_DEFAULT	0x00000015 + +/* masks and shifts for DM365*/ +#define DM365_CCDC_PG_VD_POL_SHIFT 	0 +#define DM365_CCDC_PG_HD_POL_SHIFT 	1 + +#define CCD_SRC_SEL_MASK		(BIT_MASK(5) | BIT_MASK(4)) +#define CCD_SRC_SEL_SHIFT		4 + +/* Different SoC platforms supported by this driver */ +enum vpss_platform_type { +	DM644X, +	DM355, +	DM365, +}; + +/* + * vpss operations. Depends on platform. Not all functions are available + * on all platforms. The api, first check if a function is available before + * invoking it. In the probe, the function ptrs are initialized based on + * vpss name. vpss name can be "dm355_vpss", "dm644x_vpss" etc. + */ +struct vpss_hw_ops { +	/* enable clock */ +	int (*enable_clock)(enum vpss_clock_sel clock_sel, int en); +	/* select input to ccdc */ +	void (*select_ccdc_source)(enum vpss_ccdc_source_sel src_sel); +	/* clear wbl overflow bit */ +	int (*clear_wbl_overflow)(enum vpss_wbl_sel wbl_sel); +	/* set sync polarity */ +	void (*set_sync_pol)(struct vpss_sync_pol); +	/* set the PG_FRAME_SIZE register*/ +	void (*set_pg_frame_size)(struct vpss_pg_frame_size); +	/* check and clear interrupt if occurred */ +	int (*dma_complete_interrupt)(void); +}; + +/* vpss configuration */ +struct vpss_oper_config { +	__iomem void *vpss_regs_base0; +	__iomem void *vpss_regs_base1; +	resource_size_t *vpss_regs_base2; +	enum vpss_platform_type platform; +	spinlock_t vpss_lock; +	struct vpss_hw_ops hw_ops; +}; + +static struct vpss_oper_config oper_cfg; + +/* register access routines */ +static inline u32 bl_regr(u32 offset) +{ +	return __raw_readl(oper_cfg.vpss_regs_base0 + offset); +} + +static inline void bl_regw(u32 val, u32 offset) +{ +	__raw_writel(val, oper_cfg.vpss_regs_base0 + offset); +} + +static inline u32 vpss_regr(u32 offset) +{ +	return __raw_readl(oper_cfg.vpss_regs_base1 + offset); +} + +static inline void vpss_regw(u32 val, u32 offset) +{ +	__raw_writel(val, oper_cfg.vpss_regs_base1 + offset); +} + +/* For DM365 only */ +static inline u32 isp5_read(u32 offset) +{ +	return __raw_readl(oper_cfg.vpss_regs_base0 + offset); +} + +/* For DM365 only */ +static inline void isp5_write(u32 val, u32 offset) +{ +	__raw_writel(val, oper_cfg.vpss_regs_base0 + offset); +} + +static void dm365_select_ccdc_source(enum vpss_ccdc_source_sel src_sel) +{ +	u32 temp = isp5_read(DM365_ISP5_CCDCMUX) & ~CCD_SRC_SEL_MASK; + +	/* if we are using pattern generator, enable it */ +	if (src_sel == VPSS_PGLPBK || src_sel == VPSS_CCDCPG) +		temp |= 0x08; + +	temp |= (src_sel << CCD_SRC_SEL_SHIFT); +	isp5_write(temp, DM365_ISP5_CCDCMUX); +} + +static void dm355_select_ccdc_source(enum vpss_ccdc_source_sel src_sel) +{ +	bl_regw(src_sel << VPSS_HSSISEL_SHIFT, DM355_VPSSBL_CCDCMUX); +} + +int vpss_dma_complete_interrupt(void) +{ +	if (!oper_cfg.hw_ops.dma_complete_interrupt) +		return 2; +	return oper_cfg.hw_ops.dma_complete_interrupt(); +} +EXPORT_SYMBOL(vpss_dma_complete_interrupt); + +int vpss_select_ccdc_source(enum vpss_ccdc_source_sel src_sel) +{ +	if (!oper_cfg.hw_ops.select_ccdc_source) +		return -EINVAL; + +	oper_cfg.hw_ops.select_ccdc_source(src_sel); +	return 0; +} +EXPORT_SYMBOL(vpss_select_ccdc_source); + +static int dm644x_clear_wbl_overflow(enum vpss_wbl_sel wbl_sel) +{ +	u32 mask = 1, val; + +	if (wbl_sel < VPSS_PCR_AEW_WBL_0 || +	    wbl_sel > VPSS_PCR_CCDC_WBL_O) +		return -EINVAL; + +	/* writing a 0 clear the overflow */ +	mask = ~(mask << wbl_sel); +	val = bl_regr(DM644X_SBL_PCR_VPSS) & mask; +	bl_regw(val, DM644X_SBL_PCR_VPSS); +	return 0; +} + +void vpss_set_sync_pol(struct vpss_sync_pol sync) +{ +	if (!oper_cfg.hw_ops.set_sync_pol) +		return; + +	oper_cfg.hw_ops.set_sync_pol(sync); +} +EXPORT_SYMBOL(vpss_set_sync_pol); + +int vpss_clear_wbl_overflow(enum vpss_wbl_sel wbl_sel) +{ +	if (!oper_cfg.hw_ops.clear_wbl_overflow) +		return -EINVAL; + +	return oper_cfg.hw_ops.clear_wbl_overflow(wbl_sel); +} +EXPORT_SYMBOL(vpss_clear_wbl_overflow); + +/* + *  dm355_enable_clock - Enable VPSS Clock + *  @clock_sel: Clock to be enabled/disabled + *  @en: enable/disable flag + * + *  This is called to enable or disable a vpss clock + */ +static int dm355_enable_clock(enum vpss_clock_sel clock_sel, int en) +{ +	unsigned long flags; +	u32 utemp, mask = 0x1, shift = 0; + +	switch (clock_sel) { +	case VPSS_VPBE_CLOCK: +		/* nothing since lsb */ +		break; +	case VPSS_VENC_CLOCK_SEL: +		shift = 2; +		break; +	case VPSS_CFALD_CLOCK: +		shift = 3; +		break; +	case VPSS_H3A_CLOCK: +		shift = 4; +		break; +	case VPSS_IPIPE_CLOCK: +		shift = 5; +		break; +	case VPSS_CCDC_CLOCK: +		shift = 6; +		break; +	default: +		printk(KERN_ERR "dm355_enable_clock:" +				" Invalid selector: %d\n", clock_sel); +		return -EINVAL; +	} + +	spin_lock_irqsave(&oper_cfg.vpss_lock, flags); +	utemp = vpss_regr(DM355_VPSSCLK_CLKCTRL); +	if (!en) +		utemp &= ~(mask << shift); +	else +		utemp |= (mask << shift); + +	vpss_regw(utemp, DM355_VPSSCLK_CLKCTRL); +	spin_unlock_irqrestore(&oper_cfg.vpss_lock, flags); +	return 0; +} + +static int dm365_enable_clock(enum vpss_clock_sel clock_sel, int en) +{ +	unsigned long flags; +	u32 utemp, mask = 0x1, shift = 0, offset = DM365_ISP5_PCCR; +	u32 (*read)(u32 offset) = isp5_read; +	void(*write)(u32 val, u32 offset) = isp5_write; + +	switch (clock_sel) { +	case VPSS_BL_CLOCK: +		break; +	case VPSS_CCDC_CLOCK: +		shift = 1; +		break; +	case VPSS_H3A_CLOCK: +		shift = 2; +		break; +	case VPSS_RSZ_CLOCK: +		shift = 3; +		break; +	case VPSS_IPIPE_CLOCK: +		shift = 4; +		break; +	case VPSS_IPIPEIF_CLOCK: +		shift = 5; +		break; +	case VPSS_PCLK_INTERNAL: +		shift = 6; +		break; +	case VPSS_PSYNC_CLOCK_SEL: +		shift = 7; +		break; +	case VPSS_VPBE_CLOCK: +		read = vpss_regr; +		write = vpss_regw; +		offset = DM365_VPBE_CLK_CTRL; +		break; +	case VPSS_VENC_CLOCK_SEL: +		shift = 2; +		read = vpss_regr; +		write = vpss_regw; +		offset = DM365_VPBE_CLK_CTRL; +		break; +	case VPSS_LDC_CLOCK: +		shift = 3; +		read = vpss_regr; +		write = vpss_regw; +		offset = DM365_VPBE_CLK_CTRL; +		break; +	case VPSS_FDIF_CLOCK: +		shift = 4; +		read = vpss_regr; +		write = vpss_regw; +		offset = DM365_VPBE_CLK_CTRL; +		break; +	case VPSS_OSD_CLOCK_SEL: +		shift = 6; +		read = vpss_regr; +		write = vpss_regw; +		offset = DM365_VPBE_CLK_CTRL; +		break; +	case VPSS_LDC_CLOCK_SEL: +		shift = 7; +		read = vpss_regr; +		write = vpss_regw; +		offset = DM365_VPBE_CLK_CTRL; +		break; +	default: +		printk(KERN_ERR "dm365_enable_clock: Invalid selector: %d\n", +		       clock_sel); +		return -1; +	} + +	spin_lock_irqsave(&oper_cfg.vpss_lock, flags); +	utemp = read(offset); +	if (!en) { +		mask = ~mask; +		utemp &= (mask << shift); +	} else +		utemp |= (mask << shift); + +	write(utemp, offset); +	spin_unlock_irqrestore(&oper_cfg.vpss_lock, flags); + +	return 0; +} + +int vpss_enable_clock(enum vpss_clock_sel clock_sel, int en) +{ +	if (!oper_cfg.hw_ops.enable_clock) +		return -EINVAL; + +	return oper_cfg.hw_ops.enable_clock(clock_sel, en); +} +EXPORT_SYMBOL(vpss_enable_clock); + +void dm365_vpss_set_sync_pol(struct vpss_sync_pol sync) +{ +	int val = 0; +	val = isp5_read(DM365_ISP5_CCDCMUX); + +	val |= (sync.ccdpg_hdpol << DM365_CCDC_PG_HD_POL_SHIFT); +	val |= (sync.ccdpg_vdpol << DM365_CCDC_PG_VD_POL_SHIFT); + +	isp5_write(val, DM365_ISP5_CCDCMUX); +} +EXPORT_SYMBOL(dm365_vpss_set_sync_pol); + +void vpss_set_pg_frame_size(struct vpss_pg_frame_size frame_size) +{ +	if (!oper_cfg.hw_ops.set_pg_frame_size) +		return; + +	oper_cfg.hw_ops.set_pg_frame_size(frame_size); +} +EXPORT_SYMBOL(vpss_set_pg_frame_size); + +void dm365_vpss_set_pg_frame_size(struct vpss_pg_frame_size frame_size) +{ +	int current_reg = ((frame_size.hlpfr >> 1) - 1) << 16; + +	current_reg |= (frame_size.pplen - 1); +	isp5_write(current_reg, DM365_ISP5_PG_FRAME_SIZE); +} +EXPORT_SYMBOL(dm365_vpss_set_pg_frame_size); + +static int vpss_probe(struct platform_device *pdev) +{ +	struct resource *res; +	char *platform_name; + +	if (!pdev->dev.platform_data) { +		dev_err(&pdev->dev, "no platform data\n"); +		return -ENOENT; +	} + +	platform_name = pdev->dev.platform_data; +	if (!strcmp(platform_name, "dm355_vpss")) +		oper_cfg.platform = DM355; +	else if (!strcmp(platform_name, "dm365_vpss")) +		oper_cfg.platform = DM365; +	else if (!strcmp(platform_name, "dm644x_vpss")) +		oper_cfg.platform = DM644X; +	else { +		dev_err(&pdev->dev, "vpss driver not supported on" +			" this platform\n"); +		return -ENODEV; +	} + +	dev_info(&pdev->dev, "%s vpss probed\n", platform_name); +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + +	oper_cfg.vpss_regs_base0 = devm_ioremap_resource(&pdev->dev, res); +	if (IS_ERR(oper_cfg.vpss_regs_base0)) +		return PTR_ERR(oper_cfg.vpss_regs_base0); + +	if (oper_cfg.platform == DM355 || oper_cfg.platform == DM365) { +		res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + +		oper_cfg.vpss_regs_base1 = devm_ioremap_resource(&pdev->dev, +								 res); +		if (IS_ERR(oper_cfg.vpss_regs_base1)) +			return PTR_ERR(oper_cfg.vpss_regs_base1); +	} + +	if (oper_cfg.platform == DM355) { +		oper_cfg.hw_ops.enable_clock = dm355_enable_clock; +		oper_cfg.hw_ops.select_ccdc_source = dm355_select_ccdc_source; +		/* Setup vpss interrupts */ +		bl_regw(DM355_VPSSBL_INTSEL_DEFAULT, DM355_VPSSBL_INTSEL); +		bl_regw(DM355_VPSSBL_EVTSEL_DEFAULT, DM355_VPSSBL_EVTSEL); +	} else if (oper_cfg.platform == DM365) { +		oper_cfg.hw_ops.enable_clock = dm365_enable_clock; +		oper_cfg.hw_ops.select_ccdc_source = dm365_select_ccdc_source; +		/* Setup vpss interrupts */ +		isp5_write((isp5_read(DM365_ISP5_PCCR) | +				      DM365_ISP5_PCCR_BL_CLK_ENABLE | +				      DM365_ISP5_PCCR_ISIF_CLK_ENABLE | +				      DM365_ISP5_PCCR_H3A_CLK_ENABLE | +				      DM365_ISP5_PCCR_RSZ_CLK_ENABLE | +				      DM365_ISP5_PCCR_IPIPE_CLK_ENABLE | +				      DM365_ISP5_PCCR_IPIPEIF_CLK_ENABLE | +				      DM365_ISP5_PCCR_RSV), DM365_ISP5_PCCR); +		isp5_write((isp5_read(DM365_ISP5_BCR) | +			    DM365_ISP5_BCR_ISIF_OUT_ENABLE), DM365_ISP5_BCR); +		isp5_write(DM365_ISP5_INTSEL1_DEFAULT, DM365_ISP5_INTSEL1); +		isp5_write(DM365_ISP5_INTSEL2_DEFAULT, DM365_ISP5_INTSEL2); +		isp5_write(DM365_ISP5_INTSEL3_DEFAULT, DM365_ISP5_INTSEL3); +	} else +		oper_cfg.hw_ops.clear_wbl_overflow = dm644x_clear_wbl_overflow; + +	pm_runtime_enable(&pdev->dev); + +	pm_runtime_get(&pdev->dev); + +	spin_lock_init(&oper_cfg.vpss_lock); +	dev_info(&pdev->dev, "%s vpss probe success\n", platform_name); + +	return 0; +} + +static int vpss_remove(struct platform_device *pdev) +{ +	pm_runtime_disable(&pdev->dev); +	return 0; +} + +static int vpss_suspend(struct device *dev) +{ +	pm_runtime_put(dev); +	return 0; +} + +static int vpss_resume(struct device *dev) +{ +	pm_runtime_get(dev); +	return 0; +} + +static const struct dev_pm_ops vpss_pm_ops = { +	.suspend = vpss_suspend, +	.resume = vpss_resume, +}; + +static struct platform_driver vpss_driver = { +	.driver = { +		.name	= "vpss", +		.owner = THIS_MODULE, +		.pm = &vpss_pm_ops, +	}, +	.remove = vpss_remove, +	.probe = vpss_probe, +}; + +static void vpss_exit(void) +{ +	iounmap(oper_cfg.vpss_regs_base2); +	release_mem_region(VPSS_CLK_CTRL, 4); +	platform_driver_unregister(&vpss_driver); +} + +static int __init vpss_init(void) +{ +	if (!request_mem_region(VPSS_CLK_CTRL, 4, "vpss_clock_control")) +		return -EBUSY; + +	oper_cfg.vpss_regs_base2 = ioremap(VPSS_CLK_CTRL, 4); +	writel(VPSS_CLK_CTRL_VENCCLKEN | +		     VPSS_CLK_CTRL_DACCLKEN, oper_cfg.vpss_regs_base2); + +	return platform_driver_register(&vpss_driver); +} +subsys_initcall(vpss_init); +module_exit(vpss_exit);  | 
