Пример #1
0
int __ispccdc_enable_lsc(u8 enable)
{
	if (!is_isplsc_activated())
		return -ENODEV;

	if (enable) {
		if (!ispccdc_busy()) {
			isp_reg_or(OMAP3_ISP_IOMEM_MAIN, ISP_CTRL,
				   ISPCTRL_SBL_SHARED_RPORTB
				   | ISPCTRL_SBL_RD_RAM_EN);

			isp_reg_or(OMAP3_ISP_IOMEM_CCDC,
				   ISPCCDC_LSC_CONFIG, 0x1);

			ispccdc_obj.lsc_state = 1;
		} else {
			/* Postpone enabling LSC */
			ispccdc_obj.lsc_enable = 1;
			return -EBUSY;
		}
	} else {
		isp_reg_and(OMAP3_ISP_IOMEM_CCDC, ISPCCDC_LSC_CONFIG, 0xFFFE);
		ispccdc_obj.lsc_state = ispccdc_obj.lsc_enable = 0;
	}

	return 0;
}
Пример #2
0
/*
 * ispresizer_set_intype - Input type select
 * @isp_res: Device context.
 * @type: Pixel format type.
 */
static void ispresizer_set_intype(struct isp_res_device *res,
				  enum resizer_colors_type type)
{
	struct isp_device *isp = to_isp_device(res);

	if (type == RSZ_COLOR8)
		isp_reg_or(isp, OMAP3_ISP_IOMEM_RESZ, ISPRSZ_CNT,
			   ISPRSZ_CNT_INPTYP);
	else
		isp_reg_and(isp, OMAP3_ISP_IOMEM_RESZ, ISPRSZ_CNT,
			    ~ISPRSZ_CNT_INPTYP);
}
Пример #3
0
/*
 * ispresizer_set_source - Input source select
 * @isp_res: Device context.
 * @source: Input source type
 *
 * If this field is set to RESIZER_INPUT_VP, the resizer input is fed from
 * Preview/CCDC engine, otherwise from memory.
 */
static void ispresizer_set_source(struct isp_res_device *res,
				  enum resizer_input_entity source)
{
	struct isp_device *isp = to_isp_device(res);

	if (source == RESIZER_INPUT_MEMORY)
		isp_reg_or(isp, OMAP3_ISP_IOMEM_RESZ, ISPRSZ_CNT,
			   ISPRSZ_CNT_INPSRC);
	else
		isp_reg_and(isp, OMAP3_ISP_IOMEM_RESZ, ISPRSZ_CNT,
			    ~ISPRSZ_CNT_INPSRC);
}
Пример #4
0
/*
 * ispresizer_set_bilinear - Chrominance horizontal algorithm select
 * @isp_res: Device context.
 * @type: Filtering interpolation type.
 *
 * Filtering that is same as luminance processing is
 * intended only for downsampling, and bilinear interpolation
 * is intended only for upsampling.
 */
static void ispresizer_set_bilinear(struct isp_res_device *res,
				    enum resizer_chroma_algo type)
{
	struct isp_device *isp = to_isp_device(res);

	if (type == RSZ_BILINEAR)
		isp_reg_or(isp, OMAP3_ISP_IOMEM_RESZ, ISPRSZ_CNT,
			   ISPRSZ_CNT_CBILIN);
	else
		isp_reg_and(isp, OMAP3_ISP_IOMEM_RESZ, ISPRSZ_CNT,
			    ~ISPRSZ_CNT_CBILIN);
}
Пример #5
0
/**
 * ispccdc_config_outlineoffset - Configures the output line offset
 * @offset: Must be twice the Output width and aligned on 32 byte boundary
 * @oddeven: Specifies the odd/even line pattern to be chosen to store the
 *           output.
 * @numlines: Set the value 0-3 for +1-4lines, 4-7 for -1-4lines.
 *
 * - Configures the output line offset when stored in memory
 * - Sets the odd/even line pattern to store the output
 *    (EVENEVEN (1), ODDEVEN (2), EVENODD (3), ODDODD (4))
 * - Configures the number of even and odd line fields in case of rearranging
 * the lines.
 *
 * Returns 0 if successful, or -EINVAL if the offset is not in 32 byte
 * boundary.
 **/
int ispccdc_config_outlineoffset(u32 offset, u8 oddeven, u8 numlines)
{
	if ((offset & ISP_32B_BOUNDARY_OFFSET) == offset) {
		isp_reg_writel((offset & 0xFFFF), OMAP3_ISP_IOMEM_CCDC,
			       ISPCCDC_HSIZE_OFF);
	} else {
		DPRINTK_ISPCCDC("ISP_ERR : Offset should be in 32 byte"
				" boundary\n");
		return -EINVAL;
	}

	isp_reg_and(OMAP3_ISP_IOMEM_CCDC, ISPCCDC_SDOFST,
		    ~ISPCCDC_SDOFST_FINV);

	isp_reg_and(OMAP3_ISP_IOMEM_CCDC, ISPCCDC_SDOFST,
		    ~ISPCCDC_SDOFST_FOFST_4L);

	switch (oddeven) {
	case EVENEVEN:
		isp_reg_or(OMAP3_ISP_IOMEM_CCDC, ISPCCDC_SDOFST,
			   (numlines & 0x7) << ISPCCDC_SDOFST_LOFST0_SHIFT);
		break;
	case ODDEVEN:
		isp_reg_or(OMAP3_ISP_IOMEM_CCDC, ISPCCDC_SDOFST,
			   (numlines & 0x7) << ISPCCDC_SDOFST_LOFST1_SHIFT);
		break;
	case EVENODD:
		isp_reg_or(OMAP3_ISP_IOMEM_CCDC, ISPCCDC_SDOFST,
			   (numlines & 0x7) << ISPCCDC_SDOFST_LOFST2_SHIFT);
		break;
	case ODDODD:
		isp_reg_or(OMAP3_ISP_IOMEM_CCDC, ISPCCDC_SDOFST,
			   (numlines & 0x7) << ISPCCDC_SDOFST_LOFST3_SHIFT);
		break;
	default:
		break;
	}
	return 0;
}
Пример #6
0
/**
 * ispresizer_set_intype - Input type select
 * @isp_res: Device context.
 * @type: Pixel format type.
 */
static inline void ispresizer_set_intype(struct isp_res_device *isp_res,
					 enum resizer_input type)
{
	struct device *dev = to_device(isp_res);

	if (type == RSZ_MEM_COL8)
		isp_reg_or(dev, OMAP3_ISP_IOMEM_RESZ, ISPRSZ_CNT,
			   ISPRSZ_CNT_INPTYP);
	else
		isp_reg_and(dev, OMAP3_ISP_IOMEM_RESZ, ISPRSZ_CNT,
			    ~ISPRSZ_CNT_INPTYP);

	dev_dbg(dev, "%s: In type: %u\n", __func__, type);
}
Пример #7
0
/**
 * ispresizer_set_source - Input source select
 * @isp_res: Device context.
 * @source: Input source type
 *
 * If this field is set to RSZ_OTFLY_YUV, the resizer input is fed from
 * Preview/CCDC engine, otherwise from memory.
 */
static inline void ispresizer_set_source(struct isp_res_device *isp_res,
					 enum resizer_input source)
{
	struct device *dev = to_device(isp_res);

	if (source != RSZ_OTFLY_YUV)
		isp_reg_or(dev, OMAP3_ISP_IOMEM_RESZ, ISPRSZ_CNT,
			   ISPRSZ_CNT_INPSRC);
	else
		isp_reg_and(dev, OMAP3_ISP_IOMEM_RESZ, ISPRSZ_CNT,
			    ~ISPRSZ_CNT_INPSRC);

	dev_dbg(dev, "%s: In source: %u\n", __func__, source);
}
Пример #8
0
/**
 * ispresizer_free - Makes Resizer module free.
 *
 * Returns 0 if successful, or -EINVAL if resizer module was already freed.
 **/
int ispresizer_free()
{
	mutex_lock(&ispres_obj.ispres_mutex);
	if (ispres_obj.res_inuse) {
		ispres_obj.res_inuse = 0;
		mutex_unlock(&ispres_obj.ispres_mutex);
		isp_reg_and(OMAP3_ISP_IOMEM_MAIN, ISP_CTRL,
			    ~(ISPCTRL_RSZ_CLK_EN | ISPCTRL_SBL_WR0_RAM_EN));
		return 0;
	} else {
		mutex_unlock(&ispres_obj.ispres_mutex);
		DPRINTK_ISPRESZ("ISP_ERR : Resizer Module already freed\n");
		return -EINVAL;
	}
}
Пример #9
0
/**
 * ispccdc_free - Frees the CCDC module.
 *
 * Frees the CCDC module so it can be used by another process.
 *
 * Returns 0 if successful, or -EINVAL if module has been already freed.
 **/
int ispccdc_free(void)
{
	mutex_lock(&ispccdc_obj.mutexlock);
	if (!ispccdc_obj.ccdc_inuse) {
		mutex_unlock(&ispccdc_obj.mutexlock);
		DPRINTK_ISPCCDC("ISP_ERR: CCDC Module already freed\n");
		return -EINVAL;
	}

	ispccdc_obj.ccdc_inuse = 0;
	mutex_unlock(&ispccdc_obj.mutexlock);
	isp_reg_and(OMAP3_ISP_IOMEM_MAIN, ISP_CTRL,
		    ~(ISPCTRL_CCDC_CLK_EN |
		      ISPCTRL_CCDC_RAM_EN |
		      ISPCTRL_SBL_WR1_RAM_EN));
	return 0;
}
Пример #10
0
/**
 * ispresizer_free - Makes Resizer module free.
 *
 * Returns 0 if successful, or -EINVAL if resizer module was already freed.
 **/
int ispresizer_free(struct isp_res_device *isp_res)
{
	struct device *dev = to_device(isp_res);

	mutex_lock(&isp_res->ispres_mutex);
	if (isp_res->res_inuse) {
		isp_res->res_inuse = 0;
		mutex_unlock(&isp_res->ispres_mutex);
		isp_reg_and(dev, OMAP3_ISP_IOMEM_MAIN, ISP_CTRL,
			    ~(ISPCTRL_RSZ_CLK_EN | ISPCTRL_SBL_WR0_RAM_EN));
		return 0;
	} else {
		mutex_unlock(&isp_res->ispres_mutex);
		dev_err(dev, "Resizer Module already freed\n");
		return -EINVAL;
	}
}
Пример #11
0
/*
 * ispresizer_set_ycpos - Luminance and chrominance order
 * @isp_res: Device context.
 * @order: order type.
 */
static void ispresizer_set_ycpos(struct isp_res_device *res,
				 enum v4l2_mbus_pixelcode pixelcode)
{
	struct isp_device *isp = to_isp_device(res);

	switch (pixelcode) {
	case V4L2_MBUS_FMT_YUYV16_1X16:
		isp_reg_or(isp, OMAP3_ISP_IOMEM_RESZ, ISPRSZ_CNT,
			   ISPRSZ_CNT_YCPOS);
		break;
	case V4L2_MBUS_FMT_UYVY16_1X16:
		isp_reg_and(isp, OMAP3_ISP_IOMEM_RESZ, ISPRSZ_CNT,
			    ~ISPRSZ_CNT_YCPOS);
		break;
	default:
		return;
	}
}
Пример #12
0
/**
 * ispresizer_set_start_phase - Sets the horizontal and vertical start phase.
 * @phase: horizontal and vertical start phase (0 - 7).
 *
 * This API just updates the isp_res struct. Actual register write happens in
 * ispresizer_config_size.
 **/
void ispresizer_set_start_phase(struct device *dev, struct isprsz_phase *phase)
{
	/* clear bits */
	isp_reg_and(dev, OMAP3_ISP_IOMEM_RESZ, ISPRSZ_CNT,
		    ~(ISPRSZ_CNT_HSTPH_MASK | ISPRSZ_CNT_VSTPH_MASK));

	if (phase != NULL) {
		isp_reg_or(dev, OMAP3_ISP_IOMEM_RESZ, ISPRSZ_CNT,
			   ((phase->h_phase << ISPRSZ_CNT_HSTPH_SHIFT)
			     & ISPRSZ_CNT_HSTPH_MASK) |
			   ((phase->v_phase << ISPRSZ_CNT_VSTPH_SHIFT)
			     & ISPRSZ_CNT_VSTPH_MASK));
	} else {
		isp_reg_or(dev, OMAP3_ISP_IOMEM_RESZ, ISPRSZ_CNT,
			   ((DEFAULTSTPHASE << ISPRSZ_CNT_HSTPH_SHIFT)
			     & ISPRSZ_CNT_HSTPH_MASK) |
			   ((DEFAULTSTPHASE  << ISPRSZ_CNT_VSTPH_SHIFT)
			     & ISPRSZ_CNT_VSTPH_MASK));
	}
}
Пример #13
0
/**
 * ispccdc_config_sync_if - Sets the sync i/f params between sensor and CCDC.
 * @syncif: Structure containing the sync parameters like field state, CCDC in
 *          master/slave mode, raw/yuv data, polarity of data, field, hs, vs
 *          signals.
 **/
void ispccdc_config_sync_if(struct ispccdc_syncif syncif)
{
	u32 syn_mode = isp_reg_readl(OMAP3_ISP_IOMEM_CCDC, ISPCCDC_SYN_MODE);

	syn_mode |= ISPCCDC_SYN_MODE_VDHDEN;

	if (syncif.fldstat)
		syn_mode |= ISPCCDC_SYN_MODE_FLDSTAT;
	else
		syn_mode &= ~ISPCCDC_SYN_MODE_FLDSTAT;

	syn_mode &= ISPCCDC_SYN_MODE_INPMOD_MASK;
	ispccdc_obj.syncif_ipmod = syncif.ipmod;

	switch (syncif.ipmod) {
	case RAW:
		break;
	case YUV16:
		syn_mode |= ISPCCDC_SYN_MODE_INPMOD_YCBCR16;
		break;
	case YUV8:
		syn_mode |= ISPCCDC_SYN_MODE_INPMOD_YCBCR8;
		break;
	};

	syn_mode &= ISPCCDC_SYN_MODE_DATSIZ_MASK;
	switch (syncif.datsz) {
	case DAT8:
		syn_mode |= ISPCCDC_SYN_MODE_DATSIZ_8;
		break;
	case DAT10:
		syn_mode |= ISPCCDC_SYN_MODE_DATSIZ_10;
		break;
	case DAT11:
		syn_mode |= ISPCCDC_SYN_MODE_DATSIZ_11;
		break;
	case DAT12:
		syn_mode |= ISPCCDC_SYN_MODE_DATSIZ_12;
		break;
	};

	if (syncif.fldmode)
		syn_mode |= ISPCCDC_SYN_MODE_FLDMODE;
	else
		syn_mode &= ~ISPCCDC_SYN_MODE_FLDMODE;

	if (syncif.datapol)
		syn_mode |= ISPCCDC_SYN_MODE_DATAPOL;
	else
		syn_mode &= ~ISPCCDC_SYN_MODE_DATAPOL;

	if (syncif.fldpol)
		syn_mode |= ISPCCDC_SYN_MODE_FLDPOL;
	else
		syn_mode &= ~ISPCCDC_SYN_MODE_FLDPOL;

	if (syncif.hdpol)
		syn_mode |= ISPCCDC_SYN_MODE_HDPOL;
	else
		syn_mode &= ~ISPCCDC_SYN_MODE_HDPOL;

	if (syncif.vdpol)
		syn_mode |= ISPCCDC_SYN_MODE_VDPOL;
	else
		syn_mode &= ~ISPCCDC_SYN_MODE_VDPOL;

	if (syncif.ccdc_mastermode) {
		syn_mode |= ISPCCDC_SYN_MODE_FLDOUT | ISPCCDC_SYN_MODE_VDHDOUT;
		isp_reg_writel(syncif.hs_width << ISPCCDC_HD_VD_WID_HDW_SHIFT
			       | syncif.vs_width << ISPCCDC_HD_VD_WID_VDW_SHIFT,
			       OMAP3_ISP_IOMEM_CCDC,
			       ISPCCDC_HD_VD_WID);

		isp_reg_writel(syncif.ppln << ISPCCDC_PIX_LINES_PPLN_SHIFT
			       | syncif.hlprf << ISPCCDC_PIX_LINES_HLPRF_SHIFT,
			       OMAP3_ISP_IOMEM_CCDC,
			       ISPCCDC_PIX_LINES);
	} else
		syn_mode &= ~(ISPCCDC_SYN_MODE_FLDOUT |
			      ISPCCDC_SYN_MODE_VDHDOUT);

	isp_reg_writel(syn_mode, OMAP3_ISP_IOMEM_CCDC, ISPCCDC_SYN_MODE);

	if (!(syncif.bt_r656_en)) {
		isp_reg_and(OMAP3_ISP_IOMEM_CCDC, ISPCCDC_REC656IF,
			    ~ISPCCDC_REC656IF_R656ON);
	}
}
Пример #14
0
/**
 * ispccdc_config_datapath - Specifies the input and output modules for CCDC.
 * @input: Indicates the module that inputs the image to the CCDC.
 * @output: Indicates the module to which the CCDC outputs the image.
 *
 * Configures the default configuration for the CCDC to work with.
 *
 * The valid values for the input are CCDC_RAW (0), CCDC_YUV_SYNC (1),
 * CCDC_YUV_BT (2), and CCDC_OTHERS (3).
 *
 * The valid values for the output are CCDC_YUV_RSZ (0), CCDC_YUV_MEM_RSZ (1),
 * CCDC_OTHERS_VP (2), CCDC_OTHERS_MEM (3), CCDC_OTHERS_VP_MEM (4).
 *
 * Returns 0 if successful, or -EINVAL if wrong I/O combination or wrong input
 * or output values.
 **/
int ispccdc_config_datapath(enum ccdc_input input, enum ccdc_output output)
{
	u32 syn_mode = 0;
	struct ispccdc_vp vpcfg;
	struct ispccdc_syncif syncif;
	struct ispccdc_bclamp blkcfg;

	u32 colptn = ISPCCDC_COLPTN_Gr_Cy << ISPCCDC_COLPTN_CP0PLC0_SHIFT |
		ISPCCDC_COLPTN_R_Ye << ISPCCDC_COLPTN_CP0PLC1_SHIFT |
		ISPCCDC_COLPTN_Gr_Cy << ISPCCDC_COLPTN_CP0PLC2_SHIFT |
		ISPCCDC_COLPTN_R_Ye << ISPCCDC_COLPTN_CP0PLC3_SHIFT |
		ISPCCDC_COLPTN_B_Mg << ISPCCDC_COLPTN_CP1PLC0_SHIFT |
		ISPCCDC_COLPTN_Gb_G << ISPCCDC_COLPTN_CP1PLC1_SHIFT |
		ISPCCDC_COLPTN_B_Mg << ISPCCDC_COLPTN_CP1PLC2_SHIFT |
		ISPCCDC_COLPTN_Gb_G << ISPCCDC_COLPTN_CP1PLC3_SHIFT |
		ISPCCDC_COLPTN_Gr_Cy << ISPCCDC_COLPTN_CP2PLC0_SHIFT |
		ISPCCDC_COLPTN_R_Ye << ISPCCDC_COLPTN_CP2PLC1_SHIFT |
		ISPCCDC_COLPTN_Gr_Cy << ISPCCDC_COLPTN_CP2PLC2_SHIFT |
		ISPCCDC_COLPTN_R_Ye << ISPCCDC_COLPTN_CP2PLC3_SHIFT |
		ISPCCDC_COLPTN_B_Mg << ISPCCDC_COLPTN_CP3PLC0_SHIFT |
		ISPCCDC_COLPTN_Gb_G << ISPCCDC_COLPTN_CP3PLC1_SHIFT |
		ISPCCDC_COLPTN_B_Mg << ISPCCDC_COLPTN_CP3PLC2_SHIFT |
		ISPCCDC_COLPTN_Gb_G << ISPCCDC_COLPTN_CP3PLC3_SHIFT;

	/* CCDC does not convert the image format */
	if ((input == CCDC_RAW || input == CCDC_OTHERS) &&
	    output == CCDC_YUV_RSZ) {
		DPRINTK_ISPCCDC("ISP_ERR: Wrong CCDC I/O Combination\n");
		return -EINVAL;
	}

	syn_mode = isp_reg_readl(OMAP3_ISP_IOMEM_CCDC, ISPCCDC_SYN_MODE);

	switch (output) {
	case CCDC_YUV_RSZ:
		syn_mode |= ISPCCDC_SYN_MODE_SDR2RSZ;
		syn_mode &= ~ISPCCDC_SYN_MODE_WEN;
		break;

	case CCDC_YUV_MEM_RSZ:
		syn_mode |= ISPCCDC_SYN_MODE_SDR2RSZ;
		ispccdc_obj.wen = 1;
		syn_mode |= ISPCCDC_SYN_MODE_WEN;
		break;

	case CCDC_OTHERS_VP:
		syn_mode &= ~ISPCCDC_SYN_MODE_VP2SDR;
		syn_mode &= ~ISPCCDC_SYN_MODE_SDR2RSZ;
		syn_mode &= ~ISPCCDC_SYN_MODE_WEN;
		vpcfg.bitshift_sel = BIT9_0;
		vpcfg.freq_sel = PIXCLKBY2;
		ispccdc_config_vp(vpcfg);
		ispccdc_enable_vp(1);
		break;

	case CCDC_OTHERS_MEM:
		syn_mode &= ~ISPCCDC_SYN_MODE_VP2SDR;
		syn_mode &= ~ISPCCDC_SYN_MODE_SDR2RSZ;
		syn_mode |= ISPCCDC_SYN_MODE_WEN;
		syn_mode &= ~ISPCCDC_SYN_MODE_EXWEN;
		isp_reg_and(OMAP3_ISP_IOMEM_CCDC, ISPCCDC_CFG,
			    ~ISPCCDC_CFG_WENLOG);
		vpcfg.bitshift_sel = BIT11_2;
		vpcfg.freq_sel = PIXCLKBY2;
		ispccdc_config_vp(vpcfg);
		ispccdc_enable_vp(0);
		break;

	case CCDC_OTHERS_VP_MEM:
		syn_mode &= ~ISPCCDC_SYN_MODE_VP2SDR;
		syn_mode &= ~ISPCCDC_SYN_MODE_SDR2RSZ;
		syn_mode |= ISPCCDC_SYN_MODE_WEN;
		syn_mode &= ~ISPCCDC_SYN_MODE_EXWEN;

		isp_reg_and_or(OMAP3_ISP_IOMEM_CCDC, ISPCCDC_CFG,
			       ~ISPCCDC_CFG_WENLOG,
			       ispccdc_obj.wenlog);
		vpcfg.bitshift_sel = BIT9_0;
		vpcfg.freq_sel = PIXCLKBY2;
		ispccdc_config_vp(vpcfg);
		ispccdc_enable_vp(1);
		break;
	default:
		DPRINTK_ISPCCDC("ISP_ERR: Wrong CCDC Output\n");
		return -EINVAL;
	};

	isp_reg_writel(syn_mode, OMAP3_ISP_IOMEM_CCDC, ISPCCDC_SYN_MODE);

	switch (input) {
	case CCDC_RAW:
		syncif.ccdc_mastermode = 0;
		syncif.datapol = 0;
		syncif.datsz = DAT10;
		syncif.fldmode = 0;
		syncif.fldout = 0;
		syncif.fldpol = 0;
		syncif.fldstat = 0;
		syncif.hdpol = 0;
		syncif.ipmod = RAW;
		syncif.vdpol = 0;
		ispccdc_config_sync_if(syncif);
		ispccdc_config_imgattr(colptn);
		blkcfg.dcsubval = 64;
		ispccdc_config_black_clamp(blkcfg);
		if (is_isplsc_activated()) {
			ispccdc_config_lsc(&lsc_config);
			ispccdc_load_lsc(lsc_gain_table_tmp,
					 LSC_TABLE_INIT_SIZE);
		}

		break;
	case CCDC_YUV_SYNC:
		syncif.ccdc_mastermode = 0;
		syncif.datapol = 0;
		syncif.datsz = DAT8;
		syncif.fldmode = 0;
		syncif.fldout = 0;
		syncif.fldpol = 0;
		syncif.fldstat = 0;
		syncif.hdpol = 0;
		syncif.ipmod = YUV16;
		syncif.vdpol = 1;
		ispccdc_config_imgattr(0);
		ispccdc_config_sync_if(syncif);
		blkcfg.dcsubval = 0;
		ispccdc_config_black_clamp(blkcfg);
		break;
	case CCDC_YUV_BT:
		break;
	case CCDC_OTHERS:
		break;
	default:
		DPRINTK_ISPCCDC("ISP_ERR: Wrong CCDC Input\n");
		return -EINVAL;
	}

	ispccdc_obj.ccdc_inpfmt = input;
	ispccdc_obj.ccdc_outfmt = output;
	ispccdc_print_status();
	isp_print_status();
	return 0;
}