diff options
Diffstat (limited to 'drivers/media/platform/omap3isp/ispccp2.c')
| -rw-r--r-- | drivers/media/platform/omap3isp/ispccp2.c | 1179 | 
1 files changed, 1179 insertions, 0 deletions
diff --git a/drivers/media/platform/omap3isp/ispccp2.c b/drivers/media/platform/omap3isp/ispccp2.c new file mode 100644 index 00000000000..f3801db9095 --- /dev/null +++ b/drivers/media/platform/omap3isp/ispccp2.c @@ -0,0 +1,1179 @@ +/* + * ispccp2.c + * + * TI OMAP3 ISP - CCP2 module + * + * Copyright (C) 2010 Nokia Corporation + * Copyright (C) 2010 Texas Instruments, Inc. + * + * Contacts: Laurent Pinchart <laurent.pinchart@ideasonboard.com> + *	     Sakari Ailus <sakari.ailus@iki.fi> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/uaccess.h> +#include <linux/regulator/consumer.h> + +#include "isp.h" +#include "ispreg.h" +#include "ispccp2.h" + +/* Number of LCX channels */ +#define CCP2_LCx_CHANS_NUM			3 +/* Max/Min size for CCP2 video port */ +#define ISPCCP2_DAT_START_MIN			0 +#define ISPCCP2_DAT_START_MAX			4095 +#define ISPCCP2_DAT_SIZE_MIN			0 +#define ISPCCP2_DAT_SIZE_MAX			4095 +#define ISPCCP2_VPCLK_FRACDIV			65536 +#define ISPCCP2_LCx_CTRL_FORMAT_RAW8_DPCM10_VP	0x12 +#define ISPCCP2_LCx_CTRL_FORMAT_RAW10_VP	0x16 +/* Max/Min size for CCP2 memory channel */ +#define ISPCCP2_LCM_HSIZE_COUNT_MIN		16 +#define ISPCCP2_LCM_HSIZE_COUNT_MAX		8191 +#define ISPCCP2_LCM_HSIZE_SKIP_MIN		0 +#define ISPCCP2_LCM_HSIZE_SKIP_MAX		8191 +#define ISPCCP2_LCM_VSIZE_MIN			1 +#define ISPCCP2_LCM_VSIZE_MAX			8191 +#define ISPCCP2_LCM_HWORDS_MIN			1 +#define ISPCCP2_LCM_HWORDS_MAX			4095 +#define ISPCCP2_LCM_CTRL_BURST_SIZE_32X		5 +#define ISPCCP2_LCM_CTRL_READ_THROTTLE_FULL	0 +#define ISPCCP2_LCM_CTRL_SRC_DECOMPR_DPCM10	2 +#define ISPCCP2_LCM_CTRL_SRC_FORMAT_RAW8	2 +#define ISPCCP2_LCM_CTRL_SRC_FORMAT_RAW10	3 +#define ISPCCP2_LCM_CTRL_DST_FORMAT_RAW10	3 +#define ISPCCP2_LCM_CTRL_DST_PORT_VP		0 +#define ISPCCP2_LCM_CTRL_DST_PORT_MEM		1 + +/* Set only the required bits */ +#define BIT_SET(var, shift, mask, val)			\ +	do {						\ +		var = ((var) & ~((mask) << (shift)))	\ +			| ((val) << (shift));		\ +	} while (0) + +/* + * ccp2_print_status - Print current CCP2 module register values. + */ +#define CCP2_PRINT_REGISTER(isp, name)\ +	dev_dbg(isp->dev, "###CCP2 " #name "=0x%08x\n", \ +		isp_reg_readl(isp, OMAP3_ISP_IOMEM_CCP2, ISPCCP2_##name)) + +static void ccp2_print_status(struct isp_ccp2_device *ccp2) +{ +	struct isp_device *isp = to_isp_device(ccp2); + +	dev_dbg(isp->dev, "-------------CCP2 Register dump-------------\n"); + +	CCP2_PRINT_REGISTER(isp, SYSCONFIG); +	CCP2_PRINT_REGISTER(isp, SYSSTATUS); +	CCP2_PRINT_REGISTER(isp, LC01_IRQENABLE); +	CCP2_PRINT_REGISTER(isp, LC01_IRQSTATUS); +	CCP2_PRINT_REGISTER(isp, LC23_IRQENABLE); +	CCP2_PRINT_REGISTER(isp, LC23_IRQSTATUS); +	CCP2_PRINT_REGISTER(isp, LCM_IRQENABLE); +	CCP2_PRINT_REGISTER(isp, LCM_IRQSTATUS); +	CCP2_PRINT_REGISTER(isp, CTRL); +	CCP2_PRINT_REGISTER(isp, LCx_CTRL(0)); +	CCP2_PRINT_REGISTER(isp, LCx_CODE(0)); +	CCP2_PRINT_REGISTER(isp, LCx_STAT_START(0)); +	CCP2_PRINT_REGISTER(isp, LCx_STAT_SIZE(0)); +	CCP2_PRINT_REGISTER(isp, LCx_SOF_ADDR(0)); +	CCP2_PRINT_REGISTER(isp, LCx_EOF_ADDR(0)); +	CCP2_PRINT_REGISTER(isp, LCx_DAT_START(0)); +	CCP2_PRINT_REGISTER(isp, LCx_DAT_SIZE(0)); +	CCP2_PRINT_REGISTER(isp, LCx_DAT_PING_ADDR(0)); +	CCP2_PRINT_REGISTER(isp, LCx_DAT_PONG_ADDR(0)); +	CCP2_PRINT_REGISTER(isp, LCx_DAT_OFST(0)); +	CCP2_PRINT_REGISTER(isp, LCM_CTRL); +	CCP2_PRINT_REGISTER(isp, LCM_VSIZE); +	CCP2_PRINT_REGISTER(isp, LCM_HSIZE); +	CCP2_PRINT_REGISTER(isp, LCM_PREFETCH); +	CCP2_PRINT_REGISTER(isp, LCM_SRC_ADDR); +	CCP2_PRINT_REGISTER(isp, LCM_SRC_OFST); +	CCP2_PRINT_REGISTER(isp, LCM_DST_ADDR); +	CCP2_PRINT_REGISTER(isp, LCM_DST_OFST); + +	dev_dbg(isp->dev, "--------------------------------------------\n"); +} + +/* + * ccp2_reset - Reset the CCP2 + * @ccp2: pointer to ISP CCP2 device + */ +static void ccp2_reset(struct isp_ccp2_device *ccp2) +{ +	struct isp_device *isp = to_isp_device(ccp2); +	int i = 0; + +	/* Reset the CSI1/CCP2B and wait for reset to complete */ +	isp_reg_set(isp, OMAP3_ISP_IOMEM_CCP2, ISPCCP2_SYSCONFIG, +		    ISPCCP2_SYSCONFIG_SOFT_RESET); +	while (!(isp_reg_readl(isp, OMAP3_ISP_IOMEM_CCP2, ISPCCP2_SYSSTATUS) & +		 ISPCCP2_SYSSTATUS_RESET_DONE)) { +		udelay(10); +		if (i++ > 10) {  /* try read 10 times */ +			dev_warn(isp->dev, +				"omap3_isp: timeout waiting for ccp2 reset\n"); +			break; +		} +	} +} + +/* + * ccp2_pwr_cfg - Configure the power mode settings + * @ccp2: pointer to ISP CCP2 device + */ +static void ccp2_pwr_cfg(struct isp_ccp2_device *ccp2) +{ +	struct isp_device *isp = to_isp_device(ccp2); + +	isp_reg_writel(isp, ISPCCP2_SYSCONFIG_MSTANDBY_MODE_SMART | +			((isp->revision == ISP_REVISION_15_0 && isp->autoidle) ? +			  ISPCCP2_SYSCONFIG_AUTO_IDLE : 0), +		       OMAP3_ISP_IOMEM_CCP2, ISPCCP2_SYSCONFIG); +} + +/* + * ccp2_if_enable - Enable CCP2 interface. + * @ccp2: pointer to ISP CCP2 device + * @enable: enable/disable flag + */ +static int ccp2_if_enable(struct isp_ccp2_device *ccp2, u8 enable) +{ +	struct isp_device *isp = to_isp_device(ccp2); +	int ret; +	int i; + +	if (enable && ccp2->vdds_csib) { +		ret = regulator_enable(ccp2->vdds_csib); +		if (ret < 0) +			return ret; +	} + +	/* Enable/Disable all the LCx channels */ +	for (i = 0; i < CCP2_LCx_CHANS_NUM; i++) +		isp_reg_clr_set(isp, OMAP3_ISP_IOMEM_CCP2, ISPCCP2_LCx_CTRL(i), +				ISPCCP2_LCx_CTRL_CHAN_EN, +				enable ? ISPCCP2_LCx_CTRL_CHAN_EN : 0); + +	/* Enable/Disable ccp2 interface in ccp2 mode */ +	isp_reg_clr_set(isp, OMAP3_ISP_IOMEM_CCP2, ISPCCP2_CTRL, +			ISPCCP2_CTRL_MODE | ISPCCP2_CTRL_IF_EN, +			enable ? (ISPCCP2_CTRL_MODE | ISPCCP2_CTRL_IF_EN) : 0); + +	if (!enable && ccp2->vdds_csib) +		regulator_disable(ccp2->vdds_csib); + +	return 0; +} + +/* + * ccp2_mem_enable - Enable CCP2 memory interface. + * @ccp2: pointer to ISP CCP2 device + * @enable: enable/disable flag + */ +static void ccp2_mem_enable(struct isp_ccp2_device *ccp2, u8 enable) +{ +	struct isp_device *isp = to_isp_device(ccp2); + +	if (enable) +		ccp2_if_enable(ccp2, 0); + +	/* Enable/Disable ccp2 interface in ccp2 mode */ +	isp_reg_clr_set(isp, OMAP3_ISP_IOMEM_CCP2, ISPCCP2_CTRL, +			ISPCCP2_CTRL_MODE, enable ? ISPCCP2_CTRL_MODE : 0); + +	isp_reg_clr_set(isp, OMAP3_ISP_IOMEM_CCP2, ISPCCP2_LCM_CTRL, +			ISPCCP2_LCM_CTRL_CHAN_EN, +			enable ? ISPCCP2_LCM_CTRL_CHAN_EN : 0); +} + +/* + * ccp2_phyif_config - Initialize CCP2 phy interface config + * @ccp2: Pointer to ISP CCP2 device + * @pdata: CCP2 platform data + * + * Configure the CCP2 physical interface module from platform data. + * + * Returns -EIO if strobe is chosen in CSI1 mode, or 0 on success. + */ +static int ccp2_phyif_config(struct isp_ccp2_device *ccp2, +			     const struct isp_ccp2_platform_data *pdata) +{ +	struct isp_device *isp = to_isp_device(ccp2); +	u32 val; + +	/* CCP2B mode */ +	val = isp_reg_readl(isp, OMAP3_ISP_IOMEM_CCP2, ISPCCP2_CTRL) | +			    ISPCCP2_CTRL_IO_OUT_SEL | ISPCCP2_CTRL_MODE; +	/* Data/strobe physical layer */ +	BIT_SET(val, ISPCCP2_CTRL_PHY_SEL_SHIFT, ISPCCP2_CTRL_PHY_SEL_MASK, +		pdata->phy_layer); +	BIT_SET(val, ISPCCP2_CTRL_INV_SHIFT, ISPCCP2_CTRL_INV_MASK, +		pdata->strobe_clk_pol); +	isp_reg_writel(isp, val, OMAP3_ISP_IOMEM_CCP2, ISPCCP2_CTRL); + +	val = isp_reg_readl(isp, OMAP3_ISP_IOMEM_CCP2, ISPCCP2_CTRL); +	if (!(val & ISPCCP2_CTRL_MODE)) { +		if (pdata->ccp2_mode == ISP_CCP2_MODE_CCP2) +			dev_warn(isp->dev, "OMAP3 CCP2 bus not available\n"); +		if (pdata->phy_layer == ISP_CCP2_PHY_DATA_STROBE) +			/* Strobe mode requires CCP2 */ +			return -EIO; +	} + +	return 0; +} + +/* + * ccp2_vp_config - Initialize CCP2 video port interface. + * @ccp2: Pointer to ISP CCP2 device + * @vpclk_div: Video port divisor + * + * Configure the CCP2 video port with the given clock divisor. The valid divisor + * values depend on the ISP revision: + * + * - revision 1.0 and 2.0	1 to 4 + * - revision 15.0		1 to 65536 + * + * The exact divisor value used might differ from the requested value, as ISP + * revision 15.0 represent the divisor by 65536 divided by an integer. + */ +static void ccp2_vp_config(struct isp_ccp2_device *ccp2, +			   unsigned int vpclk_div) +{ +	struct isp_device *isp = to_isp_device(ccp2); +	u32 val; + +	/* ISPCCP2_CTRL Video port */ +	val = isp_reg_readl(isp, OMAP3_ISP_IOMEM_CCP2, ISPCCP2_CTRL); +	val |= ISPCCP2_CTRL_VP_ONLY_EN;	/* Disable the memory write port */ + +	if (isp->revision == ISP_REVISION_15_0) { +		vpclk_div = clamp_t(unsigned int, vpclk_div, 1, 65536); +		vpclk_div = min(ISPCCP2_VPCLK_FRACDIV / vpclk_div, 65535U); +		BIT_SET(val, ISPCCP2_CTRL_VPCLK_DIV_SHIFT, +			ISPCCP2_CTRL_VPCLK_DIV_MASK, vpclk_div); +	} else { +		vpclk_div = clamp_t(unsigned int, vpclk_div, 1, 4); +		BIT_SET(val, ISPCCP2_CTRL_VP_OUT_CTRL_SHIFT, +			ISPCCP2_CTRL_VP_OUT_CTRL_MASK, vpclk_div - 1); +	} + +	isp_reg_writel(isp, val, OMAP3_ISP_IOMEM_CCP2, ISPCCP2_CTRL); +} + +/* + * ccp2_lcx_config - Initialize CCP2 logical channel interface. + * @ccp2: Pointer to ISP CCP2 device + * @config: Pointer to ISP LCx config structure. + * + * This will analyze the parameters passed by the interface config + * and configure CSI1/CCP2 logical channel + * + */ +static void ccp2_lcx_config(struct isp_ccp2_device *ccp2, +			    struct isp_interface_lcx_config *config) +{ +	struct isp_device *isp = to_isp_device(ccp2); +	u32 val, format; + +	switch (config->format) { +	case V4L2_MBUS_FMT_SGRBG10_DPCM8_1X8: +		format = ISPCCP2_LCx_CTRL_FORMAT_RAW8_DPCM10_VP; +		break; +	case V4L2_MBUS_FMT_SGRBG10_1X10: +	default: +		format = ISPCCP2_LCx_CTRL_FORMAT_RAW10_VP;	/* RAW10+VP */ +		break; +	} +	/* ISPCCP2_LCx_CTRL logical channel #0 */ +	val = isp_reg_readl(isp, OMAP3_ISP_IOMEM_CCP2, ISPCCP2_LCx_CTRL(0)) +			    | (ISPCCP2_LCx_CTRL_REGION_EN); /* Region */ + +	if (isp->revision == ISP_REVISION_15_0) { +		/* CRC */ +		BIT_SET(val, ISPCCP2_LCx_CTRL_CRC_SHIFT_15_0, +			ISPCCP2_LCx_CTRL_CRC_MASK, +			config->crc); +		/* Format = RAW10+VP or RAW8+DPCM10+VP*/ +		BIT_SET(val, ISPCCP2_LCx_CTRL_FORMAT_SHIFT_15_0, +			ISPCCP2_LCx_CTRL_FORMAT_MASK_15_0, format); +	} else { +		BIT_SET(val, ISPCCP2_LCx_CTRL_CRC_SHIFT, +			ISPCCP2_LCx_CTRL_CRC_MASK, +			config->crc); + +		BIT_SET(val, ISPCCP2_LCx_CTRL_FORMAT_SHIFT, +			ISPCCP2_LCx_CTRL_FORMAT_MASK, format); +	} +	isp_reg_writel(isp, val, OMAP3_ISP_IOMEM_CCP2, ISPCCP2_LCx_CTRL(0)); + +	/* ISPCCP2_DAT_START for logical channel #0 */ +	isp_reg_writel(isp, config->data_start << ISPCCP2_LCx_DAT_SHIFT, +		       OMAP3_ISP_IOMEM_CCP2, ISPCCP2_LCx_DAT_START(0)); + +	/* ISPCCP2_DAT_SIZE for logical channel #0 */ +	isp_reg_writel(isp, config->data_size << ISPCCP2_LCx_DAT_SHIFT, +		       OMAP3_ISP_IOMEM_CCP2, ISPCCP2_LCx_DAT_SIZE(0)); + +	/* Enable error IRQs for logical channel #0 */ +	val = ISPCCP2_LC01_IRQSTATUS_LC0_FIFO_OVF_IRQ | +	      ISPCCP2_LC01_IRQSTATUS_LC0_CRC_IRQ | +	      ISPCCP2_LC01_IRQSTATUS_LC0_FSP_IRQ | +	      ISPCCP2_LC01_IRQSTATUS_LC0_FW_IRQ | +	      ISPCCP2_LC01_IRQSTATUS_LC0_FSC_IRQ | +	      ISPCCP2_LC01_IRQSTATUS_LC0_SSC_IRQ; + +	isp_reg_writel(isp, val, OMAP3_ISP_IOMEM_CCP2, ISPCCP2_LC01_IRQSTATUS); +	isp_reg_set(isp, OMAP3_ISP_IOMEM_CCP2, ISPCCP2_LC01_IRQENABLE, val); +} + +/* + * ccp2_if_configure - Configure ccp2 with data from sensor + * @ccp2: Pointer to ISP CCP2 device + * + * Return 0 on success or a negative error code + */ +static int ccp2_if_configure(struct isp_ccp2_device *ccp2) +{ +	const struct isp_v4l2_subdevs_group *pdata; +	struct v4l2_mbus_framefmt *format; +	struct media_pad *pad; +	struct v4l2_subdev *sensor; +	u32 lines = 0; +	int ret; + +	ccp2_pwr_cfg(ccp2); + +	pad = media_entity_remote_pad(&ccp2->pads[CCP2_PAD_SINK]); +	sensor = media_entity_to_v4l2_subdev(pad->entity); +	pdata = sensor->host_priv; + +	ret = ccp2_phyif_config(ccp2, &pdata->bus.ccp2); +	if (ret < 0) +		return ret; + +	ccp2_vp_config(ccp2, pdata->bus.ccp2.vpclk_div + 1); + +	v4l2_subdev_call(sensor, sensor, g_skip_top_lines, &lines); + +	format = &ccp2->formats[CCP2_PAD_SINK]; + +	ccp2->if_cfg.data_start = lines; +	ccp2->if_cfg.crc = pdata->bus.ccp2.crc; +	ccp2->if_cfg.format = format->code; +	ccp2->if_cfg.data_size = format->height; + +	ccp2_lcx_config(ccp2, &ccp2->if_cfg); + +	return 0; +} + +static int ccp2_adjust_bandwidth(struct isp_ccp2_device *ccp2) +{ +	struct isp_pipeline *pipe = to_isp_pipeline(&ccp2->subdev.entity); +	struct isp_device *isp = to_isp_device(ccp2); +	const struct v4l2_mbus_framefmt *ofmt = &ccp2->formats[CCP2_PAD_SOURCE]; +	unsigned long l3_ick = pipe->l3_ick; +	struct v4l2_fract *timeperframe; +	unsigned int vpclk_div = 2; +	unsigned int value; +	u64 bound; +	u64 area; + +	/* Compute the minimum clock divisor, based on the pipeline maximum +	 * data rate. This is an absolute lower bound if we don't want SBL +	 * overflows, so round the value up. +	 */ +	vpclk_div = max_t(unsigned int, DIV_ROUND_UP(l3_ick, pipe->max_rate), +			  vpclk_div); + +	/* Compute the maximum clock divisor, based on the requested frame rate. +	 * This is a soft lower bound to achieve a frame rate equal or higher +	 * than the requested value, so round the value down. +	 */ +	timeperframe = &pipe->max_timeperframe; + +	if (timeperframe->numerator) { +		area = ofmt->width * ofmt->height; +		bound = div_u64(area * timeperframe->denominator, +				timeperframe->numerator); +		value = min_t(u64, bound, l3_ick); +		vpclk_div = max_t(unsigned int, l3_ick / value, vpclk_div); +	} + +	dev_dbg(isp->dev, "%s: minimum clock divisor = %u\n", __func__, +		vpclk_div); + +	return vpclk_div; +} + +/* + * ccp2_mem_configure - Initialize CCP2 memory input/output interface + * @ccp2: Pointer to ISP CCP2 device + * @config: Pointer to ISP mem interface config structure + * + * This will analyze the parameters passed by the interface config + * structure, and configure the respective registers for proper + * CSI1/CCP2 memory input. + */ +static void ccp2_mem_configure(struct isp_ccp2_device *ccp2, +			       struct isp_interface_mem_config *config) +{ +	struct isp_device *isp = to_isp_device(ccp2); +	u32 sink_pixcode = ccp2->formats[CCP2_PAD_SINK].code; +	u32 source_pixcode = ccp2->formats[CCP2_PAD_SOURCE].code; +	unsigned int dpcm_decompress = 0; +	u32 val, hwords; + +	if (sink_pixcode != source_pixcode && +	    sink_pixcode == V4L2_MBUS_FMT_SGRBG10_DPCM8_1X8) +		dpcm_decompress = 1; + +	ccp2_pwr_cfg(ccp2); + +	/* Hsize, Skip */ +	isp_reg_writel(isp, ISPCCP2_LCM_HSIZE_SKIP_MIN | +		       (config->hsize_count << ISPCCP2_LCM_HSIZE_SHIFT), +		       OMAP3_ISP_IOMEM_CCP2, ISPCCP2_LCM_HSIZE); + +	/* Vsize, no. of lines */ +	isp_reg_writel(isp, config->vsize_count << ISPCCP2_LCM_VSIZE_SHIFT, +		       OMAP3_ISP_IOMEM_CCP2, ISPCCP2_LCM_VSIZE); + +	if (ccp2->video_in.bpl_padding == 0) +		config->src_ofst = 0; +	else +		config->src_ofst = ccp2->video_in.bpl_value; + +	isp_reg_writel(isp, config->src_ofst, OMAP3_ISP_IOMEM_CCP2, +		       ISPCCP2_LCM_SRC_OFST); + +	/* Source and Destination formats */ +	val = ISPCCP2_LCM_CTRL_DST_FORMAT_RAW10 << +	      ISPCCP2_LCM_CTRL_DST_FORMAT_SHIFT; + +	if (dpcm_decompress) { +		/* source format is RAW8 */ +		val |= ISPCCP2_LCM_CTRL_SRC_FORMAT_RAW8 << +		       ISPCCP2_LCM_CTRL_SRC_FORMAT_SHIFT; + +		/* RAW8 + DPCM10 - simple predictor */ +		val |= ISPCCP2_LCM_CTRL_SRC_DPCM_PRED; + +		/* enable source DPCM decompression */ +		val |= ISPCCP2_LCM_CTRL_SRC_DECOMPR_DPCM10 << +		       ISPCCP2_LCM_CTRL_SRC_DECOMPR_SHIFT; +	} else { +		/* source format is RAW10 */ +		val |= ISPCCP2_LCM_CTRL_SRC_FORMAT_RAW10 << +		       ISPCCP2_LCM_CTRL_SRC_FORMAT_SHIFT; +	} + +	/* Burst size to 32x64 */ +	val |= ISPCCP2_LCM_CTRL_BURST_SIZE_32X << +	       ISPCCP2_LCM_CTRL_BURST_SIZE_SHIFT; + +	isp_reg_writel(isp, val, OMAP3_ISP_IOMEM_CCP2, ISPCCP2_LCM_CTRL); + +	/* Prefetch setup */ +	if (dpcm_decompress) +		hwords = (ISPCCP2_LCM_HSIZE_SKIP_MIN + +			  config->hsize_count) >> 3; +	else +		hwords = (ISPCCP2_LCM_HSIZE_SKIP_MIN + +			  config->hsize_count) >> 2; + +	isp_reg_writel(isp, hwords << ISPCCP2_LCM_PREFETCH_SHIFT, +		       OMAP3_ISP_IOMEM_CCP2, ISPCCP2_LCM_PREFETCH); + +	/* Video port */ +	isp_reg_set(isp, OMAP3_ISP_IOMEM_CCP2, ISPCCP2_CTRL, +		    ISPCCP2_CTRL_IO_OUT_SEL | ISPCCP2_CTRL_MODE); +	ccp2_vp_config(ccp2, ccp2_adjust_bandwidth(ccp2)); + +	/* Clear LCM interrupts */ +	isp_reg_writel(isp, ISPCCP2_LCM_IRQSTATUS_OCPERROR_IRQ | +		       ISPCCP2_LCM_IRQSTATUS_EOF_IRQ, +		       OMAP3_ISP_IOMEM_CCP2, ISPCCP2_LCM_IRQSTATUS); + +	/* Enable LCM interrupts */ +	isp_reg_set(isp, OMAP3_ISP_IOMEM_CCP2, ISPCCP2_LCM_IRQENABLE, +		    ISPCCP2_LCM_IRQSTATUS_EOF_IRQ | +		    ISPCCP2_LCM_IRQSTATUS_OCPERROR_IRQ); +} + +/* + * ccp2_set_inaddr - Sets memory address of input frame. + * @ccp2: Pointer to ISP CCP2 device + * @addr: 32bit memory address aligned on 32byte boundary. + * + * Configures the memory address from which the input frame is to be read. + */ +static void ccp2_set_inaddr(struct isp_ccp2_device *ccp2, u32 addr) +{ +	struct isp_device *isp = to_isp_device(ccp2); + +	isp_reg_writel(isp, addr, OMAP3_ISP_IOMEM_CCP2, ISPCCP2_LCM_SRC_ADDR); +} + +/* ----------------------------------------------------------------------------- + * Interrupt handling + */ + +static void ccp2_isr_buffer(struct isp_ccp2_device *ccp2) +{ +	struct isp_pipeline *pipe = to_isp_pipeline(&ccp2->subdev.entity); +	struct isp_buffer *buffer; + +	buffer = omap3isp_video_buffer_next(&ccp2->video_in); +	if (buffer != NULL) +		ccp2_set_inaddr(ccp2, buffer->dma); + +	pipe->state |= ISP_PIPELINE_IDLE_INPUT; + +	if (ccp2->state == ISP_PIPELINE_STREAM_SINGLESHOT) { +		if (isp_pipeline_ready(pipe)) +			omap3isp_pipeline_set_stream(pipe, +						ISP_PIPELINE_STREAM_SINGLESHOT); +	} +} + +/* + * omap3isp_ccp2_isr - Handle ISP CCP2 interrupts + * @ccp2: Pointer to ISP CCP2 device + * + * This will handle the CCP2 interrupts + */ +void omap3isp_ccp2_isr(struct isp_ccp2_device *ccp2) +{ +	struct isp_pipeline *pipe = to_isp_pipeline(&ccp2->subdev.entity); +	struct isp_device *isp = to_isp_device(ccp2); +	static const u32 ISPCCP2_LC01_ERROR = +		ISPCCP2_LC01_IRQSTATUS_LC0_FIFO_OVF_IRQ | +		ISPCCP2_LC01_IRQSTATUS_LC0_CRC_IRQ | +		ISPCCP2_LC01_IRQSTATUS_LC0_FSP_IRQ | +		ISPCCP2_LC01_IRQSTATUS_LC0_FW_IRQ | +		ISPCCP2_LC01_IRQSTATUS_LC0_FSC_IRQ | +		ISPCCP2_LC01_IRQSTATUS_LC0_SSC_IRQ; +	u32 lcx_irqstatus, lcm_irqstatus; + +	/* First clear the interrupts */ +	lcx_irqstatus = isp_reg_readl(isp, OMAP3_ISP_IOMEM_CCP2, +				      ISPCCP2_LC01_IRQSTATUS); +	isp_reg_writel(isp, lcx_irqstatus, OMAP3_ISP_IOMEM_CCP2, +		       ISPCCP2_LC01_IRQSTATUS); + +	lcm_irqstatus = isp_reg_readl(isp, OMAP3_ISP_IOMEM_CCP2, +				      ISPCCP2_LCM_IRQSTATUS); +	isp_reg_writel(isp, lcm_irqstatus, OMAP3_ISP_IOMEM_CCP2, +		       ISPCCP2_LCM_IRQSTATUS); +	/* Errors */ +	if (lcx_irqstatus & ISPCCP2_LC01_ERROR) { +		pipe->error = true; +		dev_dbg(isp->dev, "CCP2 err:%x\n", lcx_irqstatus); +		return; +	} + +	if (lcm_irqstatus & ISPCCP2_LCM_IRQSTATUS_OCPERROR_IRQ) { +		pipe->error = true; +		dev_dbg(isp->dev, "CCP2 OCP err:%x\n", lcm_irqstatus); +	} + +	if (omap3isp_module_sync_is_stopping(&ccp2->wait, &ccp2->stopping)) +		return; + +	/* Handle queued buffers on frame end interrupts */ +	if (lcm_irqstatus & ISPCCP2_LCM_IRQSTATUS_EOF_IRQ) +		ccp2_isr_buffer(ccp2); +} + +/* ----------------------------------------------------------------------------- + * V4L2 subdev operations + */ + +static const unsigned int ccp2_fmts[] = { +	V4L2_MBUS_FMT_SGRBG10_1X10, +	V4L2_MBUS_FMT_SGRBG10_DPCM8_1X8, +}; + +/* + * __ccp2_get_format - helper function for getting ccp2 format + * @ccp2  : Pointer to ISP CCP2 device + * @fh    : V4L2 subdev file handle + * @pad   : pad number + * @which : wanted subdev format + * return format structure or NULL on error + */ +static struct v4l2_mbus_framefmt * +__ccp2_get_format(struct isp_ccp2_device *ccp2, struct v4l2_subdev_fh *fh, +		     unsigned int pad, enum v4l2_subdev_format_whence which) +{ +	if (which == V4L2_SUBDEV_FORMAT_TRY) +		return v4l2_subdev_get_try_format(fh, pad); +	else +		return &ccp2->formats[pad]; +} + +/* + * ccp2_try_format - Handle try format by pad subdev method + * @ccp2  : Pointer to ISP CCP2 device + * @fh    : V4L2 subdev file handle + * @pad   : pad num + * @fmt   : pointer to v4l2 mbus format structure + * @which : wanted subdev format + */ +static void ccp2_try_format(struct isp_ccp2_device *ccp2, +			       struct v4l2_subdev_fh *fh, unsigned int pad, +			       struct v4l2_mbus_framefmt *fmt, +			       enum v4l2_subdev_format_whence which) +{ +	struct v4l2_mbus_framefmt *format; + +	switch (pad) { +	case CCP2_PAD_SINK: +		if (fmt->code != V4L2_MBUS_FMT_SGRBG10_DPCM8_1X8) +			fmt->code = V4L2_MBUS_FMT_SGRBG10_1X10; + +		if (ccp2->input == CCP2_INPUT_SENSOR) { +			fmt->width = clamp_t(u32, fmt->width, +					     ISPCCP2_DAT_START_MIN, +					     ISPCCP2_DAT_START_MAX); +			fmt->height = clamp_t(u32, fmt->height, +					      ISPCCP2_DAT_SIZE_MIN, +					      ISPCCP2_DAT_SIZE_MAX); +		} else if (ccp2->input == CCP2_INPUT_MEMORY) { +			fmt->width = clamp_t(u32, fmt->width, +					     ISPCCP2_LCM_HSIZE_COUNT_MIN, +					     ISPCCP2_LCM_HSIZE_COUNT_MAX); +			fmt->height = clamp_t(u32, fmt->height, +					      ISPCCP2_LCM_VSIZE_MIN, +					      ISPCCP2_LCM_VSIZE_MAX); +		} +		break; + +	case CCP2_PAD_SOURCE: +		/* Source format - copy sink format and change pixel code +		 * to SGRBG10_1X10 as we don't support CCP2 write to memory. +		 * When CCP2 write to memory feature will be added this +		 * should be changed properly. +		 */ +		format = __ccp2_get_format(ccp2, fh, CCP2_PAD_SINK, which); +		memcpy(fmt, format, sizeof(*fmt)); +		fmt->code = V4L2_MBUS_FMT_SGRBG10_1X10; +		break; +	} + +	fmt->field = V4L2_FIELD_NONE; +	fmt->colorspace = V4L2_COLORSPACE_SRGB; +} + +/* + * ccp2_enum_mbus_code - Handle pixel format enumeration + * @sd     : pointer to v4l2 subdev structure + * @fh     : V4L2 subdev file handle + * @code   : pointer to v4l2_subdev_mbus_code_enum structure + * return -EINVAL or zero on success + */ +static int ccp2_enum_mbus_code(struct v4l2_subdev *sd, +				  struct v4l2_subdev_fh *fh, +				  struct v4l2_subdev_mbus_code_enum *code) +{ +	struct isp_ccp2_device *ccp2 = v4l2_get_subdevdata(sd); +	struct v4l2_mbus_framefmt *format; + +	if (code->pad == CCP2_PAD_SINK) { +		if (code->index >= ARRAY_SIZE(ccp2_fmts)) +			return -EINVAL; + +		code->code = ccp2_fmts[code->index]; +	} else { +		if (code->index != 0) +			return -EINVAL; + +		format = __ccp2_get_format(ccp2, fh, CCP2_PAD_SINK, +					      V4L2_SUBDEV_FORMAT_TRY); +		code->code = format->code; +	} + +	return 0; +} + +static int ccp2_enum_frame_size(struct v4l2_subdev *sd, +				   struct v4l2_subdev_fh *fh, +				   struct v4l2_subdev_frame_size_enum *fse) +{ +	struct isp_ccp2_device *ccp2 = v4l2_get_subdevdata(sd); +	struct v4l2_mbus_framefmt format; + +	if (fse->index != 0) +		return -EINVAL; + +	format.code = fse->code; +	format.width = 1; +	format.height = 1; +	ccp2_try_format(ccp2, fh, fse->pad, &format, V4L2_SUBDEV_FORMAT_TRY); +	fse->min_width = format.width; +	fse->min_height = format.height; + +	if (format.code != fse->code) +		return -EINVAL; + +	format.code = fse->code; +	format.width = -1; +	format.height = -1; +	ccp2_try_format(ccp2, fh, fse->pad, &format, V4L2_SUBDEV_FORMAT_TRY); +	fse->max_width = format.width; +	fse->max_height = format.height; + +	return 0; +} + +/* + * ccp2_get_format - Handle get format by pads subdev method + * @sd    : pointer to v4l2 subdev structure + * @fh    : V4L2 subdev file handle + * @fmt   : pointer to v4l2 subdev format structure + * return -EINVAL or zero on success + */ +static int ccp2_get_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, +			      struct v4l2_subdev_format *fmt) +{ +	struct isp_ccp2_device *ccp2 = v4l2_get_subdevdata(sd); +	struct v4l2_mbus_framefmt *format; + +	format = __ccp2_get_format(ccp2, fh, fmt->pad, fmt->which); +	if (format == NULL) +		return -EINVAL; + +	fmt->format = *format; +	return 0; +} + +/* + * ccp2_set_format - Handle set format by pads subdev method + * @sd    : pointer to v4l2 subdev structure + * @fh    : V4L2 subdev file handle + * @fmt   : pointer to v4l2 subdev format structure + * returns zero + */ +static int ccp2_set_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, +			      struct v4l2_subdev_format *fmt) +{ +	struct isp_ccp2_device *ccp2 = v4l2_get_subdevdata(sd); +	struct v4l2_mbus_framefmt *format; + +	format = __ccp2_get_format(ccp2, fh, fmt->pad, fmt->which); +	if (format == NULL) +		return -EINVAL; + +	ccp2_try_format(ccp2, fh, fmt->pad, &fmt->format, fmt->which); +	*format = fmt->format; + +	/* Propagate the format from sink to source */ +	if (fmt->pad == CCP2_PAD_SINK) { +		format = __ccp2_get_format(ccp2, fh, CCP2_PAD_SOURCE, +					   fmt->which); +		*format = fmt->format; +		ccp2_try_format(ccp2, fh, CCP2_PAD_SOURCE, format, fmt->which); +	} + +	return 0; +} + +/* + * ccp2_init_formats - Initialize formats on all pads + * @sd: ISP CCP2 V4L2 subdevice + * @fh: V4L2 subdev file handle + * + * Initialize all pad formats with default values. If fh is not NULL, try + * formats are initialized on the file handle. Otherwise active formats are + * initialized on the device. + */ +static int ccp2_init_formats(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ +	struct v4l2_subdev_format format; + +	memset(&format, 0, sizeof(format)); +	format.pad = CCP2_PAD_SINK; +	format.which = fh ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE; +	format.format.code = V4L2_MBUS_FMT_SGRBG10_1X10; +	format.format.width = 4096; +	format.format.height = 4096; +	ccp2_set_format(sd, fh, &format); + +	return 0; +} + +/* + * ccp2_s_stream - Enable/Disable streaming on ccp2 subdev + * @sd    : pointer to v4l2 subdev structure + * @enable: 1 == Enable, 0 == Disable + * return zero + */ +static int ccp2_s_stream(struct v4l2_subdev *sd, int enable) +{ +	struct isp_ccp2_device *ccp2 = v4l2_get_subdevdata(sd); +	struct isp_device *isp = to_isp_device(ccp2); +	struct device *dev = to_device(ccp2); +	int ret; + +	if (ccp2->state == ISP_PIPELINE_STREAM_STOPPED) { +		if (enable == ISP_PIPELINE_STREAM_STOPPED) +			return 0; +		atomic_set(&ccp2->stopping, 0); +	} + +	switch (enable) { +	case ISP_PIPELINE_STREAM_CONTINUOUS: +		if (ccp2->phy) { +			ret = omap3isp_csiphy_acquire(ccp2->phy); +			if (ret < 0) +				return ret; +		} + +		ccp2_if_configure(ccp2); +		ccp2_print_status(ccp2); + +		/* Enable CSI1/CCP2 interface */ +		ret = ccp2_if_enable(ccp2, 1); +		if (ret < 0) { +			if (ccp2->phy) +				omap3isp_csiphy_release(ccp2->phy); +			return ret; +		} +		break; + +	case ISP_PIPELINE_STREAM_SINGLESHOT: +		if (ccp2->state != ISP_PIPELINE_STREAM_SINGLESHOT) { +			struct v4l2_mbus_framefmt *format; + +			format = &ccp2->formats[CCP2_PAD_SINK]; + +			ccp2->mem_cfg.hsize_count = format->width; +			ccp2->mem_cfg.vsize_count = format->height; +			ccp2->mem_cfg.src_ofst = 0; + +			ccp2_mem_configure(ccp2, &ccp2->mem_cfg); +			omap3isp_sbl_enable(isp, OMAP3_ISP_SBL_CSI1_READ); +			ccp2_print_status(ccp2); +		} +		ccp2_mem_enable(ccp2, 1); +		break; + +	case ISP_PIPELINE_STREAM_STOPPED: +		if (omap3isp_module_sync_idle(&sd->entity, &ccp2->wait, +					      &ccp2->stopping)) +			dev_dbg(dev, "%s: module stop timeout.\n", sd->name); +		if (ccp2->input == CCP2_INPUT_MEMORY) { +			ccp2_mem_enable(ccp2, 0); +			omap3isp_sbl_disable(isp, OMAP3_ISP_SBL_CSI1_READ); +		} else if (ccp2->input == CCP2_INPUT_SENSOR) { +			/* Disable CSI1/CCP2 interface */ +			ccp2_if_enable(ccp2, 0); +			if (ccp2->phy) +				omap3isp_csiphy_release(ccp2->phy); +		} +		break; +	} + +	ccp2->state = enable; +	return 0; +} + +/* subdev video operations */ +static const struct v4l2_subdev_video_ops ccp2_sd_video_ops = { +	.s_stream = ccp2_s_stream, +}; + +/* subdev pad operations */ +static const struct v4l2_subdev_pad_ops ccp2_sd_pad_ops = { +	.enum_mbus_code = ccp2_enum_mbus_code, +	.enum_frame_size = ccp2_enum_frame_size, +	.get_fmt = ccp2_get_format, +	.set_fmt = ccp2_set_format, +}; + +/* subdev operations */ +static const struct v4l2_subdev_ops ccp2_sd_ops = { +	.video = &ccp2_sd_video_ops, +	.pad = &ccp2_sd_pad_ops, +}; + +/* subdev internal operations */ +static const struct v4l2_subdev_internal_ops ccp2_sd_internal_ops = { +	.open = ccp2_init_formats, +}; + +/* -------------------------------------------------------------------------- + * ISP ccp2 video device node + */ + +/* + * ccp2_video_queue - Queue video buffer. + * @video : Pointer to isp video structure + * @buffer: Pointer to isp_buffer structure + * return -EIO or zero on success + */ +static int ccp2_video_queue(struct isp_video *video, struct isp_buffer *buffer) +{ +	struct isp_ccp2_device *ccp2 = &video->isp->isp_ccp2; + +	ccp2_set_inaddr(ccp2, buffer->dma); +	return 0; +} + +static const struct isp_video_operations ccp2_video_ops = { +	.queue = ccp2_video_queue, +}; + +/* ----------------------------------------------------------------------------- + * Media entity operations + */ + +/* + * ccp2_link_setup - Setup ccp2 connections. + * @entity : Pointer to media entity structure + * @local  : Pointer to local pad array + * @remote : Pointer to remote pad array + * @flags  : Link flags + * return -EINVAL on error or zero on success + */ +static int ccp2_link_setup(struct media_entity *entity, +			   const struct media_pad *local, +			   const struct media_pad *remote, u32 flags) +{ +	struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity); +	struct isp_ccp2_device *ccp2 = v4l2_get_subdevdata(sd); + +	switch (local->index | media_entity_type(remote->entity)) { +	case CCP2_PAD_SINK | MEDIA_ENT_T_DEVNODE: +		/* read from memory */ +		if (flags & MEDIA_LNK_FL_ENABLED) { +			if (ccp2->input == CCP2_INPUT_SENSOR) +				return -EBUSY; +			ccp2->input = CCP2_INPUT_MEMORY; +		} else { +			if (ccp2->input == CCP2_INPUT_MEMORY) +				ccp2->input = CCP2_INPUT_NONE; +		} +		break; + +	case CCP2_PAD_SINK | MEDIA_ENT_T_V4L2_SUBDEV: +		/* read from sensor/phy */ +		if (flags & MEDIA_LNK_FL_ENABLED) { +			if (ccp2->input == CCP2_INPUT_MEMORY) +				return -EBUSY; +			ccp2->input = CCP2_INPUT_SENSOR; +		} else { +			if (ccp2->input == CCP2_INPUT_SENSOR) +				ccp2->input = CCP2_INPUT_NONE; +		} break; + +	case CCP2_PAD_SOURCE | MEDIA_ENT_T_V4L2_SUBDEV: +		/* write to video port/ccdc */ +		if (flags & MEDIA_LNK_FL_ENABLED) +			ccp2->output = CCP2_OUTPUT_CCDC; +		else +			ccp2->output = CCP2_OUTPUT_NONE; +		break; + +	default: +		return -EINVAL; +	} + +	return 0; +} + +/* media operations */ +static const struct media_entity_operations ccp2_media_ops = { +	.link_setup = ccp2_link_setup, +	.link_validate = v4l2_subdev_link_validate, +}; + +/* + * omap3isp_ccp2_unregister_entities - Unregister media entities: subdev + * @ccp2: Pointer to ISP CCP2 device + */ +void omap3isp_ccp2_unregister_entities(struct isp_ccp2_device *ccp2) +{ +	v4l2_device_unregister_subdev(&ccp2->subdev); +	omap3isp_video_unregister(&ccp2->video_in); +} + +/* + * omap3isp_ccp2_register_entities - Register the subdev media entity + * @ccp2: Pointer to ISP CCP2 device + * @vdev: Pointer to v4l device + * return negative error code or zero on success + */ + +int omap3isp_ccp2_register_entities(struct isp_ccp2_device *ccp2, +				    struct v4l2_device *vdev) +{ +	int ret; + +	/* Register the subdev and video nodes. */ +	ret = v4l2_device_register_subdev(vdev, &ccp2->subdev); +	if (ret < 0) +		goto error; + +	ret = omap3isp_video_register(&ccp2->video_in, vdev); +	if (ret < 0) +		goto error; + +	return 0; + +error: +	omap3isp_ccp2_unregister_entities(ccp2); +	return ret; +} + +/* ----------------------------------------------------------------------------- + * ISP ccp2 initialisation and cleanup + */ + +/* + * ccp2_init_entities - Initialize ccp2 subdev and media entity. + * @ccp2: Pointer to ISP CCP2 device + * return negative error code or zero on success + */ +static int ccp2_init_entities(struct isp_ccp2_device *ccp2) +{ +	struct v4l2_subdev *sd = &ccp2->subdev; +	struct media_pad *pads = ccp2->pads; +	struct media_entity *me = &sd->entity; +	int ret; + +	ccp2->input = CCP2_INPUT_NONE; +	ccp2->output = CCP2_OUTPUT_NONE; + +	v4l2_subdev_init(sd, &ccp2_sd_ops); +	sd->internal_ops = &ccp2_sd_internal_ops; +	strlcpy(sd->name, "OMAP3 ISP CCP2", sizeof(sd->name)); +	sd->grp_id = 1 << 16;   /* group ID for isp subdevs */ +	v4l2_set_subdevdata(sd, ccp2); +	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + +	pads[CCP2_PAD_SINK].flags = MEDIA_PAD_FL_SINK +				    | MEDIA_PAD_FL_MUST_CONNECT; +	pads[CCP2_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; + +	me->ops = &ccp2_media_ops; +	ret = media_entity_init(me, CCP2_PADS_NUM, pads, 0); +	if (ret < 0) +		return ret; + +	ccp2_init_formats(sd, NULL); + +	/* +	 * The CCP2 has weird line alignment requirements, possibly caused by +	 * DPCM8 decompression. Line length for data read from memory must be a +	 * multiple of 128 bits (16 bytes) in continuous mode (when no padding +	 * is present at end of lines). Additionally, if padding is used, the +	 * padded line length must be a multiple of 32 bytes. To simplify the +	 * implementation we use a fixed 32 bytes alignment regardless of the +	 * input format and width. If strict 128 bits alignment support is +	 * required ispvideo will need to be made aware of this special dual +	 * alignment requirements. +	 */ +	ccp2->video_in.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; +	ccp2->video_in.bpl_alignment = 32; +	ccp2->video_in.bpl_max = 0xffffffe0; +	ccp2->video_in.isp = to_isp_device(ccp2); +	ccp2->video_in.ops = &ccp2_video_ops; +	ccp2->video_in.capture_mem = PAGE_ALIGN(4096 * 4096) * 3; + +	ret = omap3isp_video_init(&ccp2->video_in, "CCP2"); +	if (ret < 0) +		goto error_video; + +	/* Connect the video node to the ccp2 subdev. */ +	ret = media_entity_create_link(&ccp2->video_in.video.entity, 0, +				       &ccp2->subdev.entity, CCP2_PAD_SINK, 0); +	if (ret < 0) +		goto error_link; + +	return 0; + +error_link: +	omap3isp_video_cleanup(&ccp2->video_in); +error_video: +	media_entity_cleanup(&ccp2->subdev.entity); +	return ret; +} + +/* + * omap3isp_ccp2_init - CCP2 initialization. + * @isp : Pointer to ISP device + * return negative error code or zero on success + */ +int omap3isp_ccp2_init(struct isp_device *isp) +{ +	struct isp_ccp2_device *ccp2 = &isp->isp_ccp2; +	int ret; + +	init_waitqueue_head(&ccp2->wait); + +	/* +	 * On the OMAP34xx the CSI1 receiver is operated in the CSIb IO +	 * complex, which is powered by vdds_csib power rail. Hence the +	 * request for the regulator. +	 * +	 * On the OMAP36xx, the CCP2 uses the CSI PHY1 or PHY2, shared with +	 * the CSI2c or CSI2a receivers. The PHY then needs to be explicitly +	 * configured. +	 * +	 * TODO: Don't hardcode the usage of PHY1 (shared with CSI2c). +	 */ +	if (isp->revision == ISP_REVISION_2_0) { +		ccp2->vdds_csib = devm_regulator_get(isp->dev, "vdds_csib"); +		if (IS_ERR(ccp2->vdds_csib)) { +			dev_dbg(isp->dev, +				"Could not get regulator vdds_csib\n"); +			ccp2->vdds_csib = NULL; +		} +	} else if (isp->revision == ISP_REVISION_15_0) { +		ccp2->phy = &isp->isp_csiphy1; +	} + +	ret = ccp2_init_entities(ccp2); +	if (ret < 0) +		return ret; + +	ccp2_reset(ccp2); +	return 0; +} + +/* + * omap3isp_ccp2_cleanup - CCP2 un-initialization + * @isp : Pointer to ISP device + */ +void omap3isp_ccp2_cleanup(struct isp_device *isp) +{ +	struct isp_ccp2_device *ccp2 = &isp->isp_ccp2; + +	omap3isp_video_cleanup(&ccp2->video_in); +	media_entity_cleanup(&ccp2->subdev.entity); +}  | 
