diff options
Diffstat (limited to 'drivers/media/platform/omap3isp')
29 files changed, 19260 insertions, 0 deletions
diff --git a/drivers/media/platform/omap3isp/Makefile b/drivers/media/platform/omap3isp/Makefile new file mode 100644 index 00000000000..254975a9174 --- /dev/null +++ b/drivers/media/platform/omap3isp/Makefile @@ -0,0 +1,11 @@ +# Makefile for OMAP3 ISP driver + +ccflags-$(CONFIG_VIDEO_OMAP3_DEBUG) += -DDEBUG + +omap3-isp-objs += \ +	isp.o ispvideo.o \ +	ispcsiphy.o ispccp2.o ispcsi2.o \ +	ispccdc.o isppreview.o ispresizer.o \ +	ispstat.o isph3a_aewb.o isph3a_af.o isphist.o + +obj-$(CONFIG_VIDEO_OMAP3) += omap3-isp.o diff --git a/drivers/media/platform/omap3isp/cfa_coef_table.h b/drivers/media/platform/omap3isp/cfa_coef_table.h new file mode 100644 index 00000000000..c84df0706f3 --- /dev/null +++ b/drivers/media/platform/omap3isp/cfa_coef_table.h @@ -0,0 +1,61 @@ +/* + * cfa_coef_table.h + * + * TI OMAP3 ISP - CFA coefficients table + * + * Copyright (C) 2009-2010 Nokia Corporation + * + * 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 + */ + +{ 244, 0, 247,   0,  12,  27,  36, 247, 250,   0,  27,   0,   4, 250,  12, 244, +248,   0,   0,   0,   0,  40,   0,   0, 244,  12, 250,   4,   0,  27,   0, 250, +247,  36,  27,  12,   0, 247,   0, 244,   0,   0,  40,   0,   0,   0,   0, 248, +244,   0, 247,   0,  12,  27,  36, 247, 250,   0,  27,   0,   4, 250,  12, 244, +248,   0,   0,   0,   0,  40,   0,   0, 244,  12, 250,   4,   0,  27,   0, 250, +247,  36,  27,  12,   0, 247,   0, 244,   0,   0,  40,   0,   0,   0,   0, 248, +244,   0, 247,   0,  12,  27,  36, 247, 250,   0,  27,   0,   4, 250,  12, 244, +248,   0,   0,   0,   0,  40,   0,   0, 244,  12, 250,   4,   0,  27,   0, 250, +247,  36,  27,  12,   0, 247,   0, 244,   0,   0,  40,   0,   0,   0,   0, 248 }, +{ 0, 247,   0, 244, 247,  36,  27,  12,   0,  27,   0, 250, 244,  12, 250,   4, +  0,   0,   0, 248,   0,   0,  40,   0,   4, 250,  12, 244, 250,   0,  27,   0, + 12,  27,  36, 247, 244,   0, 247,   0,   0,  40,   0,   0, 248,   0,   0,   0, +  0, 247,   0, 244, 247,  36,  27,  12,   0,  27,   0, 250, 244,  12, 250,   4, +  0,   0,   0, 248,   0,   0,  40,   0,   4, 250,  12, 244, 250,   0,  27,   0, + 12,  27,  36, 247, 244,   0, 247,   0,   0,  40,   0,   0, 248,   0,   0,   0, +  0, 247,   0, 244, 247,  36,  27,  12,   0,  27,   0, 250, 244,  12, 250,   4, +  0,   0,   0, 248,   0,   0,  40,   0,   4, 250,  12, 244, 250,   0,  27,   0, + 12,  27,  36, 247, 244,   0, 247,   0,   0,  40,   0,   0, 248,   0,   0,   0 }, +{ 4, 250,  12, 244, 250,   0,  27,   0,  12,  27,  36, 247, 244,   0, 247,   0, +  0,   0,   0, 248,   0,   0,  40,   0,   0, 247,   0, 244, 247,  36,  27,  12, +  0,  27,   0, 250, 244,  12, 250,   4,   0,  40,   0,   0, 248,   0,   0,   0, +  4, 250,  12, 244, 250,   0,  27,   0,  12,  27,  36, 247, 244,   0, 247,   0, +  0,   0,   0, 248,   0,   0,  40,   0,   0, 247,   0, 244, 247,  36,  27,  12, +  0,  27,   0, 250, 244,  12, 250,   4,   0,  40,   0,   0, 248,   0,   0,   0, +  4, 250,  12, 244, 250,   0,  27,   0,  12,  27,  36, 247, 244,   0, 247,   0, +  0,   0,   0, 248,   0,   0,  40,   0,   0, 247,   0, 244, 247,  36,  27,  12, +  0,  27,   0, 250, 244,  12, 250,   4,   0,  40,   0,   0, 248,   0,   0,   0 }, +{ 244,12, 250,   4,   0,  27,   0, 250, 247,  36,  27,  12,   0, 247,   0, 244, +248,   0,   0,   0,   0,  40,   0,   0, 244,   0, 247,   0,  12,  27,  36, 247, +250,   0,  27,   0,   4, 250,  12, 244,   0,   0,  40,   0,   0,   0,   0, 248, +244,  12, 250,   4,   0,  27,   0, 250, 247,  36,  27,  12,   0, 247,   0, 244, +248,   0,   0,   0,   0,  40,   0,   0, 244,   0, 247,   0,  12,  27,  36, 247, +250,   0,  27,   0,   4, 250,  12, 244,   0,   0,  40,   0,   0,   0,   0, 248, +244,  12, 250,   4,   0,  27,   0, 250, 247,  36,  27,  12,   0, 247,   0, 244, +248,   0,   0,   0,   0,  40,   0,   0, 244,   0, 247,   0,  12,  27,  36, 247, +250,   0,  27,   0,   4, 250,  12, 244,   0,   0,  40,   0,   0,   0,   0, 248 }, diff --git a/drivers/media/platform/omap3isp/gamma_table.h b/drivers/media/platform/omap3isp/gamma_table.h new file mode 100644 index 00000000000..78deebf7d96 --- /dev/null +++ b/drivers/media/platform/omap3isp/gamma_table.h @@ -0,0 +1,90 @@ +/* + * gamma_table.h + * + * TI OMAP3 ISP - Default gamma table for all components + * + * Copyright (C) 2010 Nokia Corporation + * Copyright (C) 2009 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 + */ + +  0,   0,   1,   2,   3,   3,   4,   5,   6,   8,  10,  12,  14,  16,  18,  20, + 22,  23,  25,  26,  28,  29,  31,  32,  34,  35,  36,  37,  39,  40,  41,  42, + 43,  44,  45,  46,  47,  48,  49,  50,  51,  52,  52,  53,  54,  55,  56,  57, + 58,  59,  60,  61,  62,  63,  63,  64,  65,  66,  66,  67,  68,  69,  69,  70, + 71,  72,  72,  73,  74,  75,  75,  76,  77,  78,  78,  79,  80,  81,  81,  82, + 83,  84,  84,  85,  86,  87,  88,  88,  89,  90,  91,  91,  92,  93,  94,  94, + 95,  96,  97,  97,  98,  98,  99,  99, 100, 100, 101, 101, 102, 103, 104, 104, +105, 106, 107, 108, 108, 109, 110, 111, 111, 112, 113, 114, 114, 115, 116, 117, +117, 118, 119, 119, 120, 120, 121, 121, 122, 122, 123, 123, 124, 124, 125, 125, +126, 126, 127, 127, 128, 128, 129, 129, 130, 130, 131, 131, 132, 132, 133, 133, +134, 134, 135, 135, 136, 136, 137, 137, 138, 138, 139, 139, 140, 140, 141, 141, +142, 142, 143, 143, 144, 144, 145, 145, 146, 146, 147, 147, 148, 148, 149, 149, +150, 150, 151, 151, 152, 152, 153, 153, 153, 153, 154, 154, 154, 154, 155, 155, +156, 156, 157, 157, 158, 158, 158, 159, 159, 159, 160, 160, 160, 161, 161, 162, +162, 163, 163, 164, 164, 164, 164, 165, 165, 165, 165, 166, 166, 167, 167, 168, +168, 169, 169, 170, 170, 170, 170, 171, 171, 171, 171, 172, 172, 173, 173, 174, +174, 175, 175, 176, 176, 176, 176, 177, 177, 177, 177, 178, 178, 178, 178, 179, +179, 179, 179, 180, 180, 180, 180, 181, 181, 181, 181, 182, 182, 182, 182, 183, +183, 183, 183, 184, 184, 184, 184, 185, 185, 185, 185, 186, 186, 186, 186, 187, +187, 187, 187, 188, 188, 188, 188, 189, 189, 189, 189, 190, 190, 190, 190, 191, +191, 191, 191, 192, 192, 192, 192, 193, 193, 193, 193, 194, 194, 194, 194, 195, +195, 195, 195, 196, 196, 196, 196, 197, 197, 197, 197, 198, 198, 198, 198, 199, +199, 199, 199, 200, 200, 200, 200, 201, 201, 201, 201, 202, 202, 202, 203, 203, +203, 203, 204, 204, 204, 204, 205, 205, 205, 205, 206, 206, 206, 206, 207, 207, +207, 207, 208, 208, 208, 208, 209, 209, 209, 209, 210, 210, 210, 210, 210, 210, +210, 210, 210, 210, 210, 210, 211, 211, 211, 211, 211, 211, 211, 211, 211, 211, +211, 212, 212, 212, 212, 213, 213, 213, 213, 213, 213, 213, 213, 213, 213, 213, +213, 214, 214, 214, 214, 215, 215, 215, 215, 215, 215, 215, 215, 215, 215, 215, +216, 216, 216, 216, 217, 217, 217, 217, 218, 218, 218, 218, 219, 219, 219, 219, +219, 219, 219, 219, 219, 219, 219, 219, 220, 220, 220, 220, 221, 221, 221, 221, +221, 221, 221, 221, 221, 221, 221, 222, 222, 222, 222, 223, 223, 223, 223, 223, +223, 223, 223, 223, 223, 223, 223, 224, 224, 224, 224, 225, 225, 225, 225, 225, +225, 225, 225, 225, 225, 225, 225, 225, 225, 225, 225, 225, 225, 225, 226, 226, +226, 226, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 228, 228, +228, 229, 229, 229, 229, 229, 229, 229, 229, 229, 229, 229, 229, 230, 230, 230, +230, 231, 231, 231, 231, 231, 231, 231, 231, 231, 231, 231, 231, 232, 232, 232, +232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, +233, 233, 233, 233, 234, 234, 234, 234, 234, 234, 234, 234, 234, 234, 234, 235, +235, 235, 235, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, 236, +236, 236, 236, 236, 236, 236, 237, 237, 237, 237, 238, 238, 238, 238, 238, 238, +238, 238, 238, 238, 238, 238, 238, 238, 238, 238, 238, 238, 238, 238, 238, 238, +238, 238, 238, 238, 238, 239, 239, 239, 239, 240, 240, 240, 240, 240, 240, 240, +240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, +240, 240, 240, 240, 241, 241, 241, 241, 242, 242, 242, 242, 242, 242, 242, 242, +242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, +242, 242, 243, 243, 243, 243, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, +244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, +244, 245, 245, 245, 245, 246, 246, 246, 246, 246, 246, 246, 246, 246, 246, 246, +246, 246, 246, 246, 246, 246, 246, 246, 246, 246, 246, 246, 246, 246, 246, 246, +246, 246, 246, 246, 246, 246, 246, 247, 247, 247, 247, 248, 248, 248, 248, 248, +248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, 248, +248, 248, 248, 248, 248, 248, 249, 249, 249, 249, 250, 250, 250, 250, 250, 250, +250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, +250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, +250, 250, 250, 250, 251, 251, 251, 251, 252, 252, 252, 252, 252, 252, 252, 252, +252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, +252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, +252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, +252, 252, 252, 252, 252, 252, 252, 252, 253, 253, 253, 253, 253, 253, 253, 253, +253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, +253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, +253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, +253, 254, 254, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, +255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, diff --git a/drivers/media/platform/omap3isp/isp.c b/drivers/media/platform/omap3isp/isp.c new file mode 100644 index 00000000000..2c7aa672056 --- /dev/null +++ b/drivers/media/platform/omap3isp/isp.c @@ -0,0 +1,2406 @@ +/* + * isp.c + * + * TI OMAP3 ISP - Core + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2007-2009 Texas Instruments, Inc. + * + * Contacts: Laurent Pinchart <laurent.pinchart@ideasonboard.com> + *	     Sakari Ailus <sakari.ailus@iki.fi> + * + * Contributors: + *	Laurent Pinchart <laurent.pinchart@ideasonboard.com> + *	Sakari Ailus <sakari.ailus@iki.fi> + *	David Cohen <dacohen@gmail.com> + *	Stanimir Varbanov <svarbanov@mm-sol.com> + *	Vimarsh Zutshi <vimarsh.zutshi@gmail.com> + *	Tuukka Toivonen <tuukkat76@gmail.com> + *	Sergio Aguirre <saaguirre@ti.com> + *	Antti Koskipaa <akoskipa@gmail.com> + *	Ivan T. Ivanov <iivanov@mm-sol.com> + *	RaniSuneela <r-m@ti.com> + *	Atanas Filipov <afilipov@mm-sol.com> + *	Gjorgji Rosikopulos <grosikopulos@mm-sol.com> + *	Hiroshi DOYU <hiroshi.doyu@nokia.com> + *	Nayden Kanchev <nkanchev@mm-sol.com> + *	Phil Carmody <ext-phil.2.carmody@nokia.com> + *	Artem Bityutskiy <artem.bityutskiy@nokia.com> + *	Dominic Curran <dcurran@ti.com> + *	Ilkka Myllyperkio <ilkka.myllyperkio@sofica.fi> + *	Pallavi Kulkarni <p-kulkarni@ti.com> + *	Vaibhav Hiremath <hvaibhav@ti.com> + *	Mohit Jalori <mjalori@ti.com> + *	Sameer Venkatraman <sameerv@ti.com> + *	Senthilvadivu Guruswamy <svadivu@ti.com> + *	Thara Gopinath <thara@ti.com> + *	Toni Leinonen <toni.leinonen@nokia.com> + *	Troy Laramy <t-laramy@ti.com> + * + * 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 <asm/cacheflush.h> + +#include <linux/clk.h> +#include <linux/clkdev.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/omap-iommu.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> +#include <linux/sched.h> +#include <linux/vmalloc.h> + +#include <asm/dma-iommu.h> + +#include <media/v4l2-common.h> +#include <media/v4l2-device.h> + +#include "isp.h" +#include "ispreg.h" +#include "ispccdc.h" +#include "isppreview.h" +#include "ispresizer.h" +#include "ispcsi2.h" +#include "ispccp2.h" +#include "isph3a.h" +#include "isphist.h" + +static unsigned int autoidle; +module_param(autoidle, int, 0444); +MODULE_PARM_DESC(autoidle, "Enable OMAP3ISP AUTOIDLE support"); + +static void isp_save_ctx(struct isp_device *isp); + +static void isp_restore_ctx(struct isp_device *isp); + +static const struct isp_res_mapping isp_res_maps[] = { +	{ +		.isp_rev = ISP_REVISION_2_0, +		.map = 1 << OMAP3_ISP_IOMEM_MAIN | +		       1 << OMAP3_ISP_IOMEM_CCP2 | +		       1 << OMAP3_ISP_IOMEM_CCDC | +		       1 << OMAP3_ISP_IOMEM_HIST | +		       1 << OMAP3_ISP_IOMEM_H3A | +		       1 << OMAP3_ISP_IOMEM_PREV | +		       1 << OMAP3_ISP_IOMEM_RESZ | +		       1 << OMAP3_ISP_IOMEM_SBL | +		       1 << OMAP3_ISP_IOMEM_CSI2A_REGS1 | +		       1 << OMAP3_ISP_IOMEM_CSIPHY2 | +		       1 << OMAP3_ISP_IOMEM_343X_CONTROL_CSIRXFE, +	}, +	{ +		.isp_rev = ISP_REVISION_15_0, +		.map = 1 << OMAP3_ISP_IOMEM_MAIN | +		       1 << OMAP3_ISP_IOMEM_CCP2 | +		       1 << OMAP3_ISP_IOMEM_CCDC | +		       1 << OMAP3_ISP_IOMEM_HIST | +		       1 << OMAP3_ISP_IOMEM_H3A | +		       1 << OMAP3_ISP_IOMEM_PREV | +		       1 << OMAP3_ISP_IOMEM_RESZ | +		       1 << OMAP3_ISP_IOMEM_SBL | +		       1 << OMAP3_ISP_IOMEM_CSI2A_REGS1 | +		       1 << OMAP3_ISP_IOMEM_CSIPHY2 | +		       1 << OMAP3_ISP_IOMEM_CSI2A_REGS2 | +		       1 << OMAP3_ISP_IOMEM_CSI2C_REGS1 | +		       1 << OMAP3_ISP_IOMEM_CSIPHY1 | +		       1 << OMAP3_ISP_IOMEM_CSI2C_REGS2 | +		       1 << OMAP3_ISP_IOMEM_3630_CONTROL_CAMERA_PHY_CTRL, +	}, +}; + +/* Structure for saving/restoring ISP module registers */ +static struct isp_reg isp_reg_list[] = { +	{OMAP3_ISP_IOMEM_MAIN, ISP_SYSCONFIG, 0}, +	{OMAP3_ISP_IOMEM_MAIN, ISP_CTRL, 0}, +	{OMAP3_ISP_IOMEM_MAIN, ISP_TCTRL_CTRL, 0}, +	{0, ISP_TOK_TERM, 0} +}; + +/* + * omap3isp_flush - Post pending L3 bus writes by doing a register readback + * @isp: OMAP3 ISP device + * + * In order to force posting of pending writes, we need to write and + * readback the same register, in this case the revision register. + * + * See this link for reference: + *   http://www.mail-archive.com/linux-omap@vger.kernel.org/msg08149.html + */ +void omap3isp_flush(struct isp_device *isp) +{ +	isp_reg_writel(isp, 0, OMAP3_ISP_IOMEM_MAIN, ISP_REVISION); +	isp_reg_readl(isp, OMAP3_ISP_IOMEM_MAIN, ISP_REVISION); +} + +/* ----------------------------------------------------------------------------- + * XCLK + */ + +#define to_isp_xclk(_hw)	container_of(_hw, struct isp_xclk, hw) + +static void isp_xclk_update(struct isp_xclk *xclk, u32 divider) +{ +	switch (xclk->id) { +	case ISP_XCLK_A: +		isp_reg_clr_set(xclk->isp, OMAP3_ISP_IOMEM_MAIN, ISP_TCTRL_CTRL, +				ISPTCTRL_CTRL_DIVA_MASK, +				divider << ISPTCTRL_CTRL_DIVA_SHIFT); +		break; +	case ISP_XCLK_B: +		isp_reg_clr_set(xclk->isp, OMAP3_ISP_IOMEM_MAIN, ISP_TCTRL_CTRL, +				ISPTCTRL_CTRL_DIVB_MASK, +				divider << ISPTCTRL_CTRL_DIVB_SHIFT); +		break; +	} +} + +static int isp_xclk_prepare(struct clk_hw *hw) +{ +	struct isp_xclk *xclk = to_isp_xclk(hw); + +	omap3isp_get(xclk->isp); + +	return 0; +} + +static void isp_xclk_unprepare(struct clk_hw *hw) +{ +	struct isp_xclk *xclk = to_isp_xclk(hw); + +	omap3isp_put(xclk->isp); +} + +static int isp_xclk_enable(struct clk_hw *hw) +{ +	struct isp_xclk *xclk = to_isp_xclk(hw); +	unsigned long flags; + +	spin_lock_irqsave(&xclk->lock, flags); +	isp_xclk_update(xclk, xclk->divider); +	xclk->enabled = true; +	spin_unlock_irqrestore(&xclk->lock, flags); + +	return 0; +} + +static void isp_xclk_disable(struct clk_hw *hw) +{ +	struct isp_xclk *xclk = to_isp_xclk(hw); +	unsigned long flags; + +	spin_lock_irqsave(&xclk->lock, flags); +	isp_xclk_update(xclk, 0); +	xclk->enabled = false; +	spin_unlock_irqrestore(&xclk->lock, flags); +} + +static unsigned long isp_xclk_recalc_rate(struct clk_hw *hw, +					  unsigned long parent_rate) +{ +	struct isp_xclk *xclk = to_isp_xclk(hw); + +	return parent_rate / xclk->divider; +} + +static u32 isp_xclk_calc_divider(unsigned long *rate, unsigned long parent_rate) +{ +	u32 divider; + +	if (*rate >= parent_rate) { +		*rate = parent_rate; +		return ISPTCTRL_CTRL_DIV_BYPASS; +	} + +	divider = DIV_ROUND_CLOSEST(parent_rate, *rate); +	if (divider >= ISPTCTRL_CTRL_DIV_BYPASS) +		divider = ISPTCTRL_CTRL_DIV_BYPASS - 1; + +	*rate = parent_rate / divider; +	return divider; +} + +static long isp_xclk_round_rate(struct clk_hw *hw, unsigned long rate, +				unsigned long *parent_rate) +{ +	isp_xclk_calc_divider(&rate, *parent_rate); +	return rate; +} + +static int isp_xclk_set_rate(struct clk_hw *hw, unsigned long rate, +			     unsigned long parent_rate) +{ +	struct isp_xclk *xclk = to_isp_xclk(hw); +	unsigned long flags; +	u32 divider; + +	divider = isp_xclk_calc_divider(&rate, parent_rate); + +	spin_lock_irqsave(&xclk->lock, flags); + +	xclk->divider = divider; +	if (xclk->enabled) +		isp_xclk_update(xclk, divider); + +	spin_unlock_irqrestore(&xclk->lock, flags); + +	dev_dbg(xclk->isp->dev, "%s: cam_xclk%c set to %lu Hz (div %u)\n", +		__func__, xclk->id == ISP_XCLK_A ? 'a' : 'b', rate, divider); +	return 0; +} + +static const struct clk_ops isp_xclk_ops = { +	.prepare = isp_xclk_prepare, +	.unprepare = isp_xclk_unprepare, +	.enable = isp_xclk_enable, +	.disable = isp_xclk_disable, +	.recalc_rate = isp_xclk_recalc_rate, +	.round_rate = isp_xclk_round_rate, +	.set_rate = isp_xclk_set_rate, +}; + +static const char *isp_xclk_parent_name = "cam_mclk"; + +static const struct clk_init_data isp_xclk_init_data = { +	.name = "cam_xclk", +	.ops = &isp_xclk_ops, +	.parent_names = &isp_xclk_parent_name, +	.num_parents = 1, +}; + +static int isp_xclk_init(struct isp_device *isp) +{ +	struct isp_platform_data *pdata = isp->pdata; +	struct clk_init_data init; +	unsigned int i; + +	for (i = 0; i < ARRAY_SIZE(isp->xclks); ++i) +		isp->xclks[i].clk = ERR_PTR(-EINVAL); + +	for (i = 0; i < ARRAY_SIZE(isp->xclks); ++i) { +		struct isp_xclk *xclk = &isp->xclks[i]; + +		xclk->isp = isp; +		xclk->id = i == 0 ? ISP_XCLK_A : ISP_XCLK_B; +		xclk->divider = 1; +		spin_lock_init(&xclk->lock); + +		init.name = i == 0 ? "cam_xclka" : "cam_xclkb"; +		init.ops = &isp_xclk_ops; +		init.parent_names = &isp_xclk_parent_name; +		init.num_parents = 1; + +		xclk->hw.init = &init; +		/* +		 * The first argument is NULL in order to avoid circular +		 * reference, as this driver takes reference on the +		 * sensor subdevice modules and the sensors would take +		 * reference on this module through clk_get(). +		 */ +		xclk->clk = clk_register(NULL, &xclk->hw); +		if (IS_ERR(xclk->clk)) +			return PTR_ERR(xclk->clk); + +		if (pdata->xclks[i].con_id == NULL && +		    pdata->xclks[i].dev_id == NULL) +			continue; + +		xclk->lookup = kzalloc(sizeof(*xclk->lookup), GFP_KERNEL); +		if (xclk->lookup == NULL) +			return -ENOMEM; + +		xclk->lookup->con_id = pdata->xclks[i].con_id; +		xclk->lookup->dev_id = pdata->xclks[i].dev_id; +		xclk->lookup->clk = xclk->clk; + +		clkdev_add(xclk->lookup); +	} + +	return 0; +} + +static void isp_xclk_cleanup(struct isp_device *isp) +{ +	unsigned int i; + +	for (i = 0; i < ARRAY_SIZE(isp->xclks); ++i) { +		struct isp_xclk *xclk = &isp->xclks[i]; + +		if (!IS_ERR(xclk->clk)) +			clk_unregister(xclk->clk); + +		if (xclk->lookup) +			clkdev_drop(xclk->lookup); +	} +} + +/* ----------------------------------------------------------------------------- + * Interrupts + */ + +/* + * isp_enable_interrupts - Enable ISP interrupts. + * @isp: OMAP3 ISP device + */ +static void isp_enable_interrupts(struct isp_device *isp) +{ +	static const u32 irq = IRQ0ENABLE_CSIA_IRQ +			     | IRQ0ENABLE_CSIB_IRQ +			     | IRQ0ENABLE_CCDC_LSC_PREF_ERR_IRQ +			     | IRQ0ENABLE_CCDC_LSC_DONE_IRQ +			     | IRQ0ENABLE_CCDC_VD0_IRQ +			     | IRQ0ENABLE_CCDC_VD1_IRQ +			     | IRQ0ENABLE_HS_VS_IRQ +			     | IRQ0ENABLE_HIST_DONE_IRQ +			     | IRQ0ENABLE_H3A_AWB_DONE_IRQ +			     | IRQ0ENABLE_H3A_AF_DONE_IRQ +			     | IRQ0ENABLE_PRV_DONE_IRQ +			     | IRQ0ENABLE_RSZ_DONE_IRQ; + +	isp_reg_writel(isp, irq, OMAP3_ISP_IOMEM_MAIN, ISP_IRQ0STATUS); +	isp_reg_writel(isp, irq, OMAP3_ISP_IOMEM_MAIN, ISP_IRQ0ENABLE); +} + +/* + * isp_disable_interrupts - Disable ISP interrupts. + * @isp: OMAP3 ISP device + */ +static void isp_disable_interrupts(struct isp_device *isp) +{ +	isp_reg_writel(isp, 0, OMAP3_ISP_IOMEM_MAIN, ISP_IRQ0ENABLE); +} + +/* + * isp_core_init - ISP core settings + * @isp: OMAP3 ISP device + * @idle: Consider idle state. + * + * Set the power settings for the ISP and SBL bus and configure the HS/VS + * interrupt source. + * + * We need to configure the HS/VS interrupt source before interrupts get + * enabled, as the sensor might be free-running and the ISP default setting + * (HS edge) would put an unnecessary burden on the CPU. + */ +static void isp_core_init(struct isp_device *isp, int idle) +{ +	isp_reg_writel(isp, +		       ((idle ? ISP_SYSCONFIG_MIDLEMODE_SMARTSTANDBY : +				ISP_SYSCONFIG_MIDLEMODE_FORCESTANDBY) << +			ISP_SYSCONFIG_MIDLEMODE_SHIFT) | +			((isp->revision == ISP_REVISION_15_0) ? +			  ISP_SYSCONFIG_AUTOIDLE : 0), +		       OMAP3_ISP_IOMEM_MAIN, ISP_SYSCONFIG); + +	isp_reg_writel(isp, +		       (isp->autoidle ? ISPCTRL_SBL_AUTOIDLE : 0) | +		       ISPCTRL_SYNC_DETECT_VSRISE, +		       OMAP3_ISP_IOMEM_MAIN, ISP_CTRL); +} + +/* + * Configure the bridge and lane shifter. Valid inputs are + * + * CCDC_INPUT_PARALLEL: Parallel interface + * CCDC_INPUT_CSI2A: CSI2a receiver + * CCDC_INPUT_CCP2B: CCP2b receiver + * CCDC_INPUT_CSI2C: CSI2c receiver + * + * The bridge and lane shifter are configured according to the selected input + * and the ISP platform data. + */ +void omap3isp_configure_bridge(struct isp_device *isp, +			       enum ccdc_input_entity input, +			       const struct isp_parallel_platform_data *pdata, +			       unsigned int shift, unsigned int bridge) +{ +	u32 ispctrl_val; + +	ispctrl_val  = isp_reg_readl(isp, OMAP3_ISP_IOMEM_MAIN, ISP_CTRL); +	ispctrl_val &= ~ISPCTRL_SHIFT_MASK; +	ispctrl_val &= ~ISPCTRL_PAR_CLK_POL_INV; +	ispctrl_val &= ~ISPCTRL_PAR_SER_CLK_SEL_MASK; +	ispctrl_val &= ~ISPCTRL_PAR_BRIDGE_MASK; +	ispctrl_val |= bridge; + +	switch (input) { +	case CCDC_INPUT_PARALLEL: +		ispctrl_val |= ISPCTRL_PAR_SER_CLK_SEL_PARALLEL; +		ispctrl_val |= pdata->clk_pol << ISPCTRL_PAR_CLK_POL_SHIFT; +		shift += pdata->data_lane_shift * 2; +		break; + +	case CCDC_INPUT_CSI2A: +		ispctrl_val |= ISPCTRL_PAR_SER_CLK_SEL_CSIA; +		break; + +	case CCDC_INPUT_CCP2B: +		ispctrl_val |= ISPCTRL_PAR_SER_CLK_SEL_CSIB; +		break; + +	case CCDC_INPUT_CSI2C: +		ispctrl_val |= ISPCTRL_PAR_SER_CLK_SEL_CSIC; +		break; + +	default: +		return; +	} + +	ispctrl_val |= ((shift/2) << ISPCTRL_SHIFT_SHIFT) & ISPCTRL_SHIFT_MASK; + +	isp_reg_writel(isp, ispctrl_val, OMAP3_ISP_IOMEM_MAIN, ISP_CTRL); +} + +void omap3isp_hist_dma_done(struct isp_device *isp) +{ +	if (omap3isp_ccdc_busy(&isp->isp_ccdc) || +	    omap3isp_stat_pcr_busy(&isp->isp_hist)) { +		/* Histogram cannot be enabled in this frame anymore */ +		atomic_set(&isp->isp_hist.buf_err, 1); +		dev_dbg(isp->dev, "hist: Out of synchronization with " +				  "CCDC. Ignoring next buffer.\n"); +	} +} + +static inline void isp_isr_dbg(struct isp_device *isp, u32 irqstatus) +{ +	static const char *name[] = { +		"CSIA_IRQ", +		"res1", +		"res2", +		"CSIB_LCM_IRQ", +		"CSIB_IRQ", +		"res5", +		"res6", +		"res7", +		"CCDC_VD0_IRQ", +		"CCDC_VD1_IRQ", +		"CCDC_VD2_IRQ", +		"CCDC_ERR_IRQ", +		"H3A_AF_DONE_IRQ", +		"H3A_AWB_DONE_IRQ", +		"res14", +		"res15", +		"HIST_DONE_IRQ", +		"CCDC_LSC_DONE", +		"CCDC_LSC_PREFETCH_COMPLETED", +		"CCDC_LSC_PREFETCH_ERROR", +		"PRV_DONE_IRQ", +		"CBUFF_IRQ", +		"res22", +		"res23", +		"RSZ_DONE_IRQ", +		"OVF_IRQ", +		"res26", +		"res27", +		"MMU_ERR_IRQ", +		"OCP_ERR_IRQ", +		"SEC_ERR_IRQ", +		"HS_VS_IRQ", +	}; +	int i; + +	dev_dbg(isp->dev, "ISP IRQ: "); + +	for (i = 0; i < ARRAY_SIZE(name); i++) { +		if ((1 << i) & irqstatus) +			printk(KERN_CONT "%s ", name[i]); +	} +	printk(KERN_CONT "\n"); +} + +static void isp_isr_sbl(struct isp_device *isp) +{ +	struct device *dev = isp->dev; +	struct isp_pipeline *pipe; +	u32 sbl_pcr; + +	/* +	 * Handle shared buffer logic overflows for video buffers. +	 * ISPSBL_PCR_CCDCPRV_2_RSZ_OVF can be safely ignored. +	 */ +	sbl_pcr = isp_reg_readl(isp, OMAP3_ISP_IOMEM_SBL, ISPSBL_PCR); +	isp_reg_writel(isp, sbl_pcr, OMAP3_ISP_IOMEM_SBL, ISPSBL_PCR); +	sbl_pcr &= ~ISPSBL_PCR_CCDCPRV_2_RSZ_OVF; + +	if (sbl_pcr) +		dev_dbg(dev, "SBL overflow (PCR = 0x%08x)\n", sbl_pcr); + +	if (sbl_pcr & ISPSBL_PCR_CSIB_WBL_OVF) { +		pipe = to_isp_pipeline(&isp->isp_ccp2.subdev.entity); +		if (pipe != NULL) +			pipe->error = true; +	} + +	if (sbl_pcr & ISPSBL_PCR_CSIA_WBL_OVF) { +		pipe = to_isp_pipeline(&isp->isp_csi2a.subdev.entity); +		if (pipe != NULL) +			pipe->error = true; +	} + +	if (sbl_pcr & ISPSBL_PCR_CCDC_WBL_OVF) { +		pipe = to_isp_pipeline(&isp->isp_ccdc.subdev.entity); +		if (pipe != NULL) +			pipe->error = true; +	} + +	if (sbl_pcr & ISPSBL_PCR_PRV_WBL_OVF) { +		pipe = to_isp_pipeline(&isp->isp_prev.subdev.entity); +		if (pipe != NULL) +			pipe->error = true; +	} + +	if (sbl_pcr & (ISPSBL_PCR_RSZ1_WBL_OVF +		       | ISPSBL_PCR_RSZ2_WBL_OVF +		       | ISPSBL_PCR_RSZ3_WBL_OVF +		       | ISPSBL_PCR_RSZ4_WBL_OVF)) { +		pipe = to_isp_pipeline(&isp->isp_res.subdev.entity); +		if (pipe != NULL) +			pipe->error = true; +	} + +	if (sbl_pcr & ISPSBL_PCR_H3A_AF_WBL_OVF) +		omap3isp_stat_sbl_overflow(&isp->isp_af); + +	if (sbl_pcr & ISPSBL_PCR_H3A_AEAWB_WBL_OVF) +		omap3isp_stat_sbl_overflow(&isp->isp_aewb); +} + +/* + * isp_isr - Interrupt Service Routine for Camera ISP module. + * @irq: Not used currently. + * @_isp: Pointer to the OMAP3 ISP device + * + * Handles the corresponding callback if plugged in. + */ +static irqreturn_t isp_isr(int irq, void *_isp) +{ +	static const u32 ccdc_events = IRQ0STATUS_CCDC_LSC_PREF_ERR_IRQ | +				       IRQ0STATUS_CCDC_LSC_DONE_IRQ | +				       IRQ0STATUS_CCDC_VD0_IRQ | +				       IRQ0STATUS_CCDC_VD1_IRQ | +				       IRQ0STATUS_HS_VS_IRQ; +	struct isp_device *isp = _isp; +	u32 irqstatus; + +	irqstatus = isp_reg_readl(isp, OMAP3_ISP_IOMEM_MAIN, ISP_IRQ0STATUS); +	isp_reg_writel(isp, irqstatus, OMAP3_ISP_IOMEM_MAIN, ISP_IRQ0STATUS); + +	isp_isr_sbl(isp); + +	if (irqstatus & IRQ0STATUS_CSIA_IRQ) +		omap3isp_csi2_isr(&isp->isp_csi2a); + +	if (irqstatus & IRQ0STATUS_CSIB_IRQ) +		omap3isp_ccp2_isr(&isp->isp_ccp2); + +	if (irqstatus & IRQ0STATUS_CCDC_VD0_IRQ) { +		if (isp->isp_ccdc.output & CCDC_OUTPUT_PREVIEW) +			omap3isp_preview_isr_frame_sync(&isp->isp_prev); +		if (isp->isp_ccdc.output & CCDC_OUTPUT_RESIZER) +			omap3isp_resizer_isr_frame_sync(&isp->isp_res); +		omap3isp_stat_isr_frame_sync(&isp->isp_aewb); +		omap3isp_stat_isr_frame_sync(&isp->isp_af); +		omap3isp_stat_isr_frame_sync(&isp->isp_hist); +	} + +	if (irqstatus & ccdc_events) +		omap3isp_ccdc_isr(&isp->isp_ccdc, irqstatus & ccdc_events); + +	if (irqstatus & IRQ0STATUS_PRV_DONE_IRQ) { +		if (isp->isp_prev.output & PREVIEW_OUTPUT_RESIZER) +			omap3isp_resizer_isr_frame_sync(&isp->isp_res); +		omap3isp_preview_isr(&isp->isp_prev); +	} + +	if (irqstatus & IRQ0STATUS_RSZ_DONE_IRQ) +		omap3isp_resizer_isr(&isp->isp_res); + +	if (irqstatus & IRQ0STATUS_H3A_AWB_DONE_IRQ) +		omap3isp_stat_isr(&isp->isp_aewb); + +	if (irqstatus & IRQ0STATUS_H3A_AF_DONE_IRQ) +		omap3isp_stat_isr(&isp->isp_af); + +	if (irqstatus & IRQ0STATUS_HIST_DONE_IRQ) +		omap3isp_stat_isr(&isp->isp_hist); + +	omap3isp_flush(isp); + +#if defined(DEBUG) && defined(ISP_ISR_DEBUG) +	isp_isr_dbg(isp, irqstatus); +#endif + +	return IRQ_HANDLED; +} + +/* ----------------------------------------------------------------------------- + * Pipeline power management + * + * Entities must be powered up when part of a pipeline that contains at least + * one open video device node. + * + * To achieve this use the entity use_count field to track the number of users. + * For entities corresponding to video device nodes the use_count field stores + * the users count of the node. For entities corresponding to subdevs the + * use_count field stores the total number of users of all video device nodes + * in the pipeline. + * + * The omap3isp_pipeline_pm_use() function must be called in the open() and + * close() handlers of video device nodes. It increments or decrements the use + * count of all subdev entities in the pipeline. + * + * To react to link management on powered pipelines, the link setup notification + * callback updates the use count of all entities in the source and sink sides + * of the link. + */ + +/* + * isp_pipeline_pm_use_count - Count the number of users of a pipeline + * @entity: The entity + * + * Return the total number of users of all video device nodes in the pipeline. + */ +static int isp_pipeline_pm_use_count(struct media_entity *entity) +{ +	struct media_entity_graph graph; +	int use = 0; + +	media_entity_graph_walk_start(&graph, entity); + +	while ((entity = media_entity_graph_walk_next(&graph))) { +		if (media_entity_type(entity) == MEDIA_ENT_T_DEVNODE) +			use += entity->use_count; +	} + +	return use; +} + +/* + * isp_pipeline_pm_power_one - Apply power change to an entity + * @entity: The entity + * @change: Use count change + * + * Change the entity use count by @change. If the entity is a subdev update its + * power state by calling the core::s_power operation when the use count goes + * from 0 to != 0 or from != 0 to 0. + * + * Return 0 on success or a negative error code on failure. + */ +static int isp_pipeline_pm_power_one(struct media_entity *entity, int change) +{ +	struct v4l2_subdev *subdev; +	int ret; + +	subdev = media_entity_type(entity) == MEDIA_ENT_T_V4L2_SUBDEV +	       ? media_entity_to_v4l2_subdev(entity) : NULL; + +	if (entity->use_count == 0 && change > 0 && subdev != NULL) { +		ret = v4l2_subdev_call(subdev, core, s_power, 1); +		if (ret < 0 && ret != -ENOIOCTLCMD) +			return ret; +	} + +	entity->use_count += change; +	WARN_ON(entity->use_count < 0); + +	if (entity->use_count == 0 && change < 0 && subdev != NULL) +		v4l2_subdev_call(subdev, core, s_power, 0); + +	return 0; +} + +/* + * isp_pipeline_pm_power - Apply power change to all entities in a pipeline + * @entity: The entity + * @change: Use count change + * + * Walk the pipeline to update the use count and the power state of all non-node + * entities. + * + * Return 0 on success or a negative error code on failure. + */ +static int isp_pipeline_pm_power(struct media_entity *entity, int change) +{ +	struct media_entity_graph graph; +	struct media_entity *first = entity; +	int ret = 0; + +	if (!change) +		return 0; + +	media_entity_graph_walk_start(&graph, entity); + +	while (!ret && (entity = media_entity_graph_walk_next(&graph))) +		if (media_entity_type(entity) != MEDIA_ENT_T_DEVNODE) +			ret = isp_pipeline_pm_power_one(entity, change); + +	if (!ret) +		return 0; + +	media_entity_graph_walk_start(&graph, first); + +	while ((first = media_entity_graph_walk_next(&graph)) +	       && first != entity) +		if (media_entity_type(first) != MEDIA_ENT_T_DEVNODE) +			isp_pipeline_pm_power_one(first, -change); + +	return ret; +} + +/* + * omap3isp_pipeline_pm_use - Update the use count of an entity + * @entity: The entity + * @use: Use (1) or stop using (0) the entity + * + * Update the use count of all entities in the pipeline and power entities on or + * off accordingly. + * + * Return 0 on success or a negative error code on failure. Powering entities + * off is assumed to never fail. No failure can occur when the use parameter is + * set to 0. + */ +int omap3isp_pipeline_pm_use(struct media_entity *entity, int use) +{ +	int change = use ? 1 : -1; +	int ret; + +	mutex_lock(&entity->parent->graph_mutex); + +	/* Apply use count to node. */ +	entity->use_count += change; +	WARN_ON(entity->use_count < 0); + +	/* Apply power change to connected non-nodes. */ +	ret = isp_pipeline_pm_power(entity, change); +	if (ret < 0) +		entity->use_count -= change; + +	mutex_unlock(&entity->parent->graph_mutex); + +	return ret; +} + +/* + * isp_pipeline_link_notify - Link management notification callback + * @link: The link + * @flags: New link flags that will be applied + * @notification: The link's state change notification type (MEDIA_DEV_NOTIFY_*) + * + * React to link management on powered pipelines by updating the use count of + * all entities in the source and sink sides of the link. Entities are powered + * on or off accordingly. + * + * Return 0 on success or a negative error code on failure. Powering entities + * off is assumed to never fail. This function will not fail for disconnection + * events. + */ +static int isp_pipeline_link_notify(struct media_link *link, u32 flags, +				    unsigned int notification) +{ +	struct media_entity *source = link->source->entity; +	struct media_entity *sink = link->sink->entity; +	int source_use = isp_pipeline_pm_use_count(source); +	int sink_use = isp_pipeline_pm_use_count(sink); +	int ret; + +	if (notification == MEDIA_DEV_NOTIFY_POST_LINK_CH && +	    !(link->flags & MEDIA_LNK_FL_ENABLED)) { +		/* Powering off entities is assumed to never fail. */ +		isp_pipeline_pm_power(source, -sink_use); +		isp_pipeline_pm_power(sink, -source_use); +		return 0; +	} + +	if (notification == MEDIA_DEV_NOTIFY_POST_LINK_CH && +		(flags & MEDIA_LNK_FL_ENABLED)) { + +		ret = isp_pipeline_pm_power(source, sink_use); +		if (ret < 0) +			return ret; + +		ret = isp_pipeline_pm_power(sink, source_use); +		if (ret < 0) +			isp_pipeline_pm_power(source, -sink_use); + +		return ret; +	} + +	return 0; +} + +/* ----------------------------------------------------------------------------- + * Pipeline stream management + */ + +/* + * isp_pipeline_enable - Enable streaming on a pipeline + * @pipe: ISP pipeline + * @mode: Stream mode (single shot or continuous) + * + * Walk the entities chain starting at the pipeline output video node and start + * all modules in the chain in the given mode. + * + * Return 0 if successful, or the return value of the failed video::s_stream + * operation otherwise. + */ +static int isp_pipeline_enable(struct isp_pipeline *pipe, +			       enum isp_pipeline_stream_state mode) +{ +	struct isp_device *isp = pipe->output->isp; +	struct media_entity *entity; +	struct media_pad *pad; +	struct v4l2_subdev *subdev; +	unsigned long flags; +	int ret; + +	/* Refuse to start streaming if an entity included in the pipeline has +	 * crashed. This check must be performed before the loop below to avoid +	 * starting entities if the pipeline won't start anyway (those entities +	 * would then likely fail to stop, making the problem worse). +	 */ +	if (pipe->entities & isp->crashed) +		return -EIO; + +	spin_lock_irqsave(&pipe->lock, flags); +	pipe->state &= ~(ISP_PIPELINE_IDLE_INPUT | ISP_PIPELINE_IDLE_OUTPUT); +	spin_unlock_irqrestore(&pipe->lock, flags); + +	pipe->do_propagation = false; + +	entity = &pipe->output->video.entity; +	while (1) { +		pad = &entity->pads[0]; +		if (!(pad->flags & MEDIA_PAD_FL_SINK)) +			break; + +		pad = media_entity_remote_pad(pad); +		if (pad == NULL || +		    media_entity_type(pad->entity) != MEDIA_ENT_T_V4L2_SUBDEV) +			break; + +		entity = pad->entity; +		subdev = media_entity_to_v4l2_subdev(entity); + +		ret = v4l2_subdev_call(subdev, video, s_stream, mode); +		if (ret < 0 && ret != -ENOIOCTLCMD) +			return ret; + +		if (subdev == &isp->isp_ccdc.subdev) { +			v4l2_subdev_call(&isp->isp_aewb.subdev, video, +					s_stream, mode); +			v4l2_subdev_call(&isp->isp_af.subdev, video, +					s_stream, mode); +			v4l2_subdev_call(&isp->isp_hist.subdev, video, +					s_stream, mode); +			pipe->do_propagation = true; +		} +	} + +	return 0; +} + +static int isp_pipeline_wait_resizer(struct isp_device *isp) +{ +	return omap3isp_resizer_busy(&isp->isp_res); +} + +static int isp_pipeline_wait_preview(struct isp_device *isp) +{ +	return omap3isp_preview_busy(&isp->isp_prev); +} + +static int isp_pipeline_wait_ccdc(struct isp_device *isp) +{ +	return omap3isp_stat_busy(&isp->isp_af) +	    || omap3isp_stat_busy(&isp->isp_aewb) +	    || omap3isp_stat_busy(&isp->isp_hist) +	    || omap3isp_ccdc_busy(&isp->isp_ccdc); +} + +#define ISP_STOP_TIMEOUT	msecs_to_jiffies(1000) + +static int isp_pipeline_wait(struct isp_device *isp, +			     int(*busy)(struct isp_device *isp)) +{ +	unsigned long timeout = jiffies + ISP_STOP_TIMEOUT; + +	while (!time_after(jiffies, timeout)) { +		if (!busy(isp)) +			return 0; +	} + +	return 1; +} + +/* + * isp_pipeline_disable - Disable streaming on a pipeline + * @pipe: ISP pipeline + * + * Walk the entities chain starting at the pipeline output video node and stop + * all modules in the chain. Wait synchronously for the modules to be stopped if + * necessary. + * + * Return 0 if all modules have been properly stopped, or -ETIMEDOUT if a module + * can't be stopped (in which case a software reset of the ISP is probably + * necessary). + */ +static int isp_pipeline_disable(struct isp_pipeline *pipe) +{ +	struct isp_device *isp = pipe->output->isp; +	struct media_entity *entity; +	struct media_pad *pad; +	struct v4l2_subdev *subdev; +	int failure = 0; +	int ret; + +	/* +	 * We need to stop all the modules after CCDC first or they'll +	 * never stop since they may not get a full frame from CCDC. +	 */ +	entity = &pipe->output->video.entity; +	while (1) { +		pad = &entity->pads[0]; +		if (!(pad->flags & MEDIA_PAD_FL_SINK)) +			break; + +		pad = media_entity_remote_pad(pad); +		if (pad == NULL || +		    media_entity_type(pad->entity) != MEDIA_ENT_T_V4L2_SUBDEV) +			break; + +		entity = pad->entity; +		subdev = media_entity_to_v4l2_subdev(entity); + +		if (subdev == &isp->isp_ccdc.subdev) { +			v4l2_subdev_call(&isp->isp_aewb.subdev, +					 video, s_stream, 0); +			v4l2_subdev_call(&isp->isp_af.subdev, +					 video, s_stream, 0); +			v4l2_subdev_call(&isp->isp_hist.subdev, +					 video, s_stream, 0); +		} + +		v4l2_subdev_call(subdev, video, s_stream, 0); + +		if (subdev == &isp->isp_res.subdev) +			ret = isp_pipeline_wait(isp, isp_pipeline_wait_resizer); +		else if (subdev == &isp->isp_prev.subdev) +			ret = isp_pipeline_wait(isp, isp_pipeline_wait_preview); +		else if (subdev == &isp->isp_ccdc.subdev) +			ret = isp_pipeline_wait(isp, isp_pipeline_wait_ccdc); +		else +			ret = 0; + +		/* Handle stop failures. An entity that fails to stop can +		 * usually just be restarted. Flag the stop failure nonetheless +		 * to trigger an ISP reset the next time the device is released, +		 * just in case. +		 * +		 * The preview engine is a special case. A failure to stop can +		 * mean a hardware crash. When that happens the preview engine +		 * won't respond to read/write operations on the L4 bus anymore, +		 * resulting in a bus fault and a kernel oops next time it gets +		 * accessed. Mark it as crashed to prevent pipelines including +		 * it from being started. +		 */ +		if (ret) { +			dev_info(isp->dev, "Unable to stop %s\n", subdev->name); +			isp->stop_failure = true; +			if (subdev == &isp->isp_prev.subdev) +				isp->crashed |= 1U << subdev->entity.id; +			failure = -ETIMEDOUT; +		} +	} + +	return failure; +} + +/* + * omap3isp_pipeline_set_stream - Enable/disable streaming on a pipeline + * @pipe: ISP pipeline + * @state: Stream state (stopped, single shot or continuous) + * + * Set the pipeline to the given stream state. Pipelines can be started in + * single-shot or continuous mode. + * + * Return 0 if successful, or the return value of the failed video::s_stream + * operation otherwise. The pipeline state is not updated when the operation + * fails, except when stopping the pipeline. + */ +int omap3isp_pipeline_set_stream(struct isp_pipeline *pipe, +				 enum isp_pipeline_stream_state state) +{ +	int ret; + +	if (state == ISP_PIPELINE_STREAM_STOPPED) +		ret = isp_pipeline_disable(pipe); +	else +		ret = isp_pipeline_enable(pipe, state); + +	if (ret == 0 || state == ISP_PIPELINE_STREAM_STOPPED) +		pipe->stream_state = state; + +	return ret; +} + +/* + * omap3isp_pipeline_cancel_stream - Cancel stream on a pipeline + * @pipe: ISP pipeline + * + * Cancelling a stream mark all buffers on all video nodes in the pipeline as + * erroneous and makes sure no new buffer can be queued. This function is called + * when a fatal error that prevents any further operation on the pipeline + * occurs. + */ +void omap3isp_pipeline_cancel_stream(struct isp_pipeline *pipe) +{ +	if (pipe->input) +		omap3isp_video_cancel_stream(pipe->input); +	if (pipe->output) +		omap3isp_video_cancel_stream(pipe->output); +} + +/* + * isp_pipeline_resume - Resume streaming on a pipeline + * @pipe: ISP pipeline + * + * Resume video output and input and re-enable pipeline. + */ +static void isp_pipeline_resume(struct isp_pipeline *pipe) +{ +	int singleshot = pipe->stream_state == ISP_PIPELINE_STREAM_SINGLESHOT; + +	omap3isp_video_resume(pipe->output, !singleshot); +	if (singleshot) +		omap3isp_video_resume(pipe->input, 0); +	isp_pipeline_enable(pipe, pipe->stream_state); +} + +/* + * isp_pipeline_suspend - Suspend streaming on a pipeline + * @pipe: ISP pipeline + * + * Suspend pipeline. + */ +static void isp_pipeline_suspend(struct isp_pipeline *pipe) +{ +	isp_pipeline_disable(pipe); +} + +/* + * isp_pipeline_is_last - Verify if entity has an enabled link to the output + * 			  video node + * @me: ISP module's media entity + * + * Returns 1 if the entity has an enabled link to the output video node or 0 + * otherwise. It's true only while pipeline can have no more than one output + * node. + */ +static int isp_pipeline_is_last(struct media_entity *me) +{ +	struct isp_pipeline *pipe; +	struct media_pad *pad; + +	if (!me->pipe) +		return 0; +	pipe = to_isp_pipeline(me); +	if (pipe->stream_state == ISP_PIPELINE_STREAM_STOPPED) +		return 0; +	pad = media_entity_remote_pad(&pipe->output->pad); +	return pad->entity == me; +} + +/* + * isp_suspend_module_pipeline - Suspend pipeline to which belongs the module + * @me: ISP module's media entity + * + * Suspend the whole pipeline if module's entity has an enabled link to the + * output video node. It works only while pipeline can have no more than one + * output node. + */ +static void isp_suspend_module_pipeline(struct media_entity *me) +{ +	if (isp_pipeline_is_last(me)) +		isp_pipeline_suspend(to_isp_pipeline(me)); +} + +/* + * isp_resume_module_pipeline - Resume pipeline to which belongs the module + * @me: ISP module's media entity + * + * Resume the whole pipeline if module's entity has an enabled link to the + * output video node. It works only while pipeline can have no more than one + * output node. + */ +static void isp_resume_module_pipeline(struct media_entity *me) +{ +	if (isp_pipeline_is_last(me)) +		isp_pipeline_resume(to_isp_pipeline(me)); +} + +/* + * isp_suspend_modules - Suspend ISP submodules. + * @isp: OMAP3 ISP device + * + * Returns 0 if suspend left in idle state all the submodules properly, + * or returns 1 if a general Reset is required to suspend the submodules. + */ +static int isp_suspend_modules(struct isp_device *isp) +{ +	unsigned long timeout; + +	omap3isp_stat_suspend(&isp->isp_aewb); +	omap3isp_stat_suspend(&isp->isp_af); +	omap3isp_stat_suspend(&isp->isp_hist); +	isp_suspend_module_pipeline(&isp->isp_res.subdev.entity); +	isp_suspend_module_pipeline(&isp->isp_prev.subdev.entity); +	isp_suspend_module_pipeline(&isp->isp_ccdc.subdev.entity); +	isp_suspend_module_pipeline(&isp->isp_csi2a.subdev.entity); +	isp_suspend_module_pipeline(&isp->isp_ccp2.subdev.entity); + +	timeout = jiffies + ISP_STOP_TIMEOUT; +	while (omap3isp_stat_busy(&isp->isp_af) +	    || omap3isp_stat_busy(&isp->isp_aewb) +	    || omap3isp_stat_busy(&isp->isp_hist) +	    || omap3isp_preview_busy(&isp->isp_prev) +	    || omap3isp_resizer_busy(&isp->isp_res) +	    || omap3isp_ccdc_busy(&isp->isp_ccdc)) { +		if (time_after(jiffies, timeout)) { +			dev_info(isp->dev, "can't stop modules.\n"); +			return 1; +		} +		msleep(1); +	} + +	return 0; +} + +/* + * isp_resume_modules - Resume ISP submodules. + * @isp: OMAP3 ISP device + */ +static void isp_resume_modules(struct isp_device *isp) +{ +	omap3isp_stat_resume(&isp->isp_aewb); +	omap3isp_stat_resume(&isp->isp_af); +	omap3isp_stat_resume(&isp->isp_hist); +	isp_resume_module_pipeline(&isp->isp_res.subdev.entity); +	isp_resume_module_pipeline(&isp->isp_prev.subdev.entity); +	isp_resume_module_pipeline(&isp->isp_ccdc.subdev.entity); +	isp_resume_module_pipeline(&isp->isp_csi2a.subdev.entity); +	isp_resume_module_pipeline(&isp->isp_ccp2.subdev.entity); +} + +/* + * isp_reset - Reset ISP with a timeout wait for idle. + * @isp: OMAP3 ISP device + */ +static int isp_reset(struct isp_device *isp) +{ +	unsigned long timeout = 0; + +	isp_reg_writel(isp, +		       isp_reg_readl(isp, OMAP3_ISP_IOMEM_MAIN, ISP_SYSCONFIG) +		       | ISP_SYSCONFIG_SOFTRESET, +		       OMAP3_ISP_IOMEM_MAIN, ISP_SYSCONFIG); +	while (!(isp_reg_readl(isp, OMAP3_ISP_IOMEM_MAIN, +			       ISP_SYSSTATUS) & 0x1)) { +		if (timeout++ > 10000) { +			dev_alert(isp->dev, "cannot reset ISP\n"); +			return -ETIMEDOUT; +		} +		udelay(1); +	} + +	isp->stop_failure = false; +	isp->crashed = 0; +	return 0; +} + +/* + * isp_save_context - Saves the values of the ISP module registers. + * @isp: OMAP3 ISP device + * @reg_list: Structure containing pairs of register address and value to + *            modify on OMAP. + */ +static void +isp_save_context(struct isp_device *isp, struct isp_reg *reg_list) +{ +	struct isp_reg *next = reg_list; + +	for (; next->reg != ISP_TOK_TERM; next++) +		next->val = isp_reg_readl(isp, next->mmio_range, next->reg); +} + +/* + * isp_restore_context - Restores the values of the ISP module registers. + * @isp: OMAP3 ISP device + * @reg_list: Structure containing pairs of register address and value to + *            modify on OMAP. + */ +static void +isp_restore_context(struct isp_device *isp, struct isp_reg *reg_list) +{ +	struct isp_reg *next = reg_list; + +	for (; next->reg != ISP_TOK_TERM; next++) +		isp_reg_writel(isp, next->val, next->mmio_range, next->reg); +} + +/* + * isp_save_ctx - Saves ISP, CCDC, HIST, H3A, PREV, RESZ & MMU context. + * @isp: OMAP3 ISP device + * + * Routine for saving the context of each module in the ISP. + * CCDC, HIST, H3A, PREV, RESZ and MMU. + */ +static void isp_save_ctx(struct isp_device *isp) +{ +	isp_save_context(isp, isp_reg_list); +	omap_iommu_save_ctx(isp->dev); +} + +/* + * isp_restore_ctx - Restores ISP, CCDC, HIST, H3A, PREV, RESZ & MMU context. + * @isp: OMAP3 ISP device + * + * Routine for restoring the context of each module in the ISP. + * CCDC, HIST, H3A, PREV, RESZ and MMU. + */ +static void isp_restore_ctx(struct isp_device *isp) +{ +	isp_restore_context(isp, isp_reg_list); +	omap_iommu_restore_ctx(isp->dev); +	omap3isp_ccdc_restore_context(isp); +	omap3isp_preview_restore_context(isp); +} + +/* ----------------------------------------------------------------------------- + * SBL resources management + */ +#define OMAP3_ISP_SBL_READ	(OMAP3_ISP_SBL_CSI1_READ | \ +				 OMAP3_ISP_SBL_CCDC_LSC_READ | \ +				 OMAP3_ISP_SBL_PREVIEW_READ | \ +				 OMAP3_ISP_SBL_RESIZER_READ) +#define OMAP3_ISP_SBL_WRITE	(OMAP3_ISP_SBL_CSI1_WRITE | \ +				 OMAP3_ISP_SBL_CSI2A_WRITE | \ +				 OMAP3_ISP_SBL_CSI2C_WRITE | \ +				 OMAP3_ISP_SBL_CCDC_WRITE | \ +				 OMAP3_ISP_SBL_PREVIEW_WRITE) + +void omap3isp_sbl_enable(struct isp_device *isp, enum isp_sbl_resource res) +{ +	u32 sbl = 0; + +	isp->sbl_resources |= res; + +	if (isp->sbl_resources & OMAP3_ISP_SBL_CSI1_READ) +		sbl |= ISPCTRL_SBL_SHARED_RPORTA; + +	if (isp->sbl_resources & OMAP3_ISP_SBL_CCDC_LSC_READ) +		sbl |= ISPCTRL_SBL_SHARED_RPORTB; + +	if (isp->sbl_resources & OMAP3_ISP_SBL_CSI2C_WRITE) +		sbl |= ISPCTRL_SBL_SHARED_WPORTC; + +	if (isp->sbl_resources & OMAP3_ISP_SBL_RESIZER_WRITE) +		sbl |= ISPCTRL_SBL_WR0_RAM_EN; + +	if (isp->sbl_resources & OMAP3_ISP_SBL_WRITE) +		sbl |= ISPCTRL_SBL_WR1_RAM_EN; + +	if (isp->sbl_resources & OMAP3_ISP_SBL_READ) +		sbl |= ISPCTRL_SBL_RD_RAM_EN; + +	isp_reg_set(isp, OMAP3_ISP_IOMEM_MAIN, ISP_CTRL, sbl); +} + +void omap3isp_sbl_disable(struct isp_device *isp, enum isp_sbl_resource res) +{ +	u32 sbl = 0; + +	isp->sbl_resources &= ~res; + +	if (!(isp->sbl_resources & OMAP3_ISP_SBL_CSI1_READ)) +		sbl |= ISPCTRL_SBL_SHARED_RPORTA; + +	if (!(isp->sbl_resources & OMAP3_ISP_SBL_CCDC_LSC_READ)) +		sbl |= ISPCTRL_SBL_SHARED_RPORTB; + +	if (!(isp->sbl_resources & OMAP3_ISP_SBL_CSI2C_WRITE)) +		sbl |= ISPCTRL_SBL_SHARED_WPORTC; + +	if (!(isp->sbl_resources & OMAP3_ISP_SBL_RESIZER_WRITE)) +		sbl |= ISPCTRL_SBL_WR0_RAM_EN; + +	if (!(isp->sbl_resources & OMAP3_ISP_SBL_WRITE)) +		sbl |= ISPCTRL_SBL_WR1_RAM_EN; + +	if (!(isp->sbl_resources & OMAP3_ISP_SBL_READ)) +		sbl |= ISPCTRL_SBL_RD_RAM_EN; + +	isp_reg_clr(isp, OMAP3_ISP_IOMEM_MAIN, ISP_CTRL, sbl); +} + +/* + * isp_module_sync_idle - Helper to sync module with its idle state + * @me: ISP submodule's media entity + * @wait: ISP submodule's wait queue for streamoff/interrupt synchronization + * @stopping: flag which tells module wants to stop + * + * This function checks if ISP submodule needs to wait for next interrupt. If + * yes, makes the caller to sleep while waiting for such event. + */ +int omap3isp_module_sync_idle(struct media_entity *me, wait_queue_head_t *wait, +			      atomic_t *stopping) +{ +	struct isp_pipeline *pipe = to_isp_pipeline(me); + +	if (pipe->stream_state == ISP_PIPELINE_STREAM_STOPPED || +	    (pipe->stream_state == ISP_PIPELINE_STREAM_SINGLESHOT && +	     !isp_pipeline_ready(pipe))) +		return 0; + +	/* +	 * atomic_set() doesn't include memory barrier on ARM platform for SMP +	 * scenario. We'll call it here to avoid race conditions. +	 */ +	atomic_set(stopping, 1); +	smp_mb(); + +	/* +	 * If module is the last one, it's writing to memory. In this case, +	 * it's necessary to check if the module is already paused due to +	 * DMA queue underrun or if it has to wait for next interrupt to be +	 * idle. +	 * If it isn't the last one, the function won't sleep but *stopping +	 * will still be set to warn next submodule caller's interrupt the +	 * module wants to be idle. +	 */ +	if (isp_pipeline_is_last(me)) { +		struct isp_video *video = pipe->output; +		unsigned long flags; +		spin_lock_irqsave(&video->irqlock, flags); +		if (video->dmaqueue_flags & ISP_VIDEO_DMAQUEUE_UNDERRUN) { +			spin_unlock_irqrestore(&video->irqlock, flags); +			atomic_set(stopping, 0); +			smp_mb(); +			return 0; +		} +		spin_unlock_irqrestore(&video->irqlock, flags); +		if (!wait_event_timeout(*wait, !atomic_read(stopping), +					msecs_to_jiffies(1000))) { +			atomic_set(stopping, 0); +			smp_mb(); +			return -ETIMEDOUT; +		} +	} + +	return 0; +} + +/* + * omap3isp_module_sync_is_stopping - Helper to verify if module was stopping + * @wait: ISP submodule's wait queue for streamoff/interrupt synchronization + * @stopping: flag which tells module wants to stop + * + * This function checks if ISP submodule was stopping. In case of yes, it + * notices the caller by setting stopping to 0 and waking up the wait queue. + * Returns 1 if it was stopping or 0 otherwise. + */ +int omap3isp_module_sync_is_stopping(wait_queue_head_t *wait, +				     atomic_t *stopping) +{ +	if (atomic_cmpxchg(stopping, 1, 0)) { +		wake_up(wait); +		return 1; +	} + +	return 0; +} + +/* -------------------------------------------------------------------------- + * Clock management + */ + +#define ISPCTRL_CLKS_MASK	(ISPCTRL_H3A_CLK_EN | \ +				 ISPCTRL_HIST_CLK_EN | \ +				 ISPCTRL_RSZ_CLK_EN | \ +				 (ISPCTRL_CCDC_CLK_EN | ISPCTRL_CCDC_RAM_EN) | \ +				 (ISPCTRL_PREV_CLK_EN | ISPCTRL_PREV_RAM_EN)) + +static void __isp_subclk_update(struct isp_device *isp) +{ +	u32 clk = 0; + +	/* AEWB and AF share the same clock. */ +	if (isp->subclk_resources & +	    (OMAP3_ISP_SUBCLK_AEWB | OMAP3_ISP_SUBCLK_AF)) +		clk |= ISPCTRL_H3A_CLK_EN; + +	if (isp->subclk_resources & OMAP3_ISP_SUBCLK_HIST) +		clk |= ISPCTRL_HIST_CLK_EN; + +	if (isp->subclk_resources & OMAP3_ISP_SUBCLK_RESIZER) +		clk |= ISPCTRL_RSZ_CLK_EN; + +	/* NOTE: For CCDC & Preview submodules, we need to affect internal +	 *       RAM as well. +	 */ +	if (isp->subclk_resources & OMAP3_ISP_SUBCLK_CCDC) +		clk |= ISPCTRL_CCDC_CLK_EN | ISPCTRL_CCDC_RAM_EN; + +	if (isp->subclk_resources & OMAP3_ISP_SUBCLK_PREVIEW) +		clk |= ISPCTRL_PREV_CLK_EN | ISPCTRL_PREV_RAM_EN; + +	isp_reg_clr_set(isp, OMAP3_ISP_IOMEM_MAIN, ISP_CTRL, +			ISPCTRL_CLKS_MASK, clk); +} + +void omap3isp_subclk_enable(struct isp_device *isp, +			    enum isp_subclk_resource res) +{ +	isp->subclk_resources |= res; + +	__isp_subclk_update(isp); +} + +void omap3isp_subclk_disable(struct isp_device *isp, +			     enum isp_subclk_resource res) +{ +	isp->subclk_resources &= ~res; + +	__isp_subclk_update(isp); +} + +/* + * isp_enable_clocks - Enable ISP clocks + * @isp: OMAP3 ISP device + * + * Return 0 if successful, or clk_prepare_enable return value if any of them + * fails. + */ +static int isp_enable_clocks(struct isp_device *isp) +{ +	int r; +	unsigned long rate; + +	r = clk_prepare_enable(isp->clock[ISP_CLK_CAM_ICK]); +	if (r) { +		dev_err(isp->dev, "failed to enable cam_ick clock\n"); +		goto out_clk_enable_ick; +	} +	r = clk_set_rate(isp->clock[ISP_CLK_CAM_MCLK], CM_CAM_MCLK_HZ); +	if (r) { +		dev_err(isp->dev, "clk_set_rate for cam_mclk failed\n"); +		goto out_clk_enable_mclk; +	} +	r = clk_prepare_enable(isp->clock[ISP_CLK_CAM_MCLK]); +	if (r) { +		dev_err(isp->dev, "failed to enable cam_mclk clock\n"); +		goto out_clk_enable_mclk; +	} +	rate = clk_get_rate(isp->clock[ISP_CLK_CAM_MCLK]); +	if (rate != CM_CAM_MCLK_HZ) +		dev_warn(isp->dev, "unexpected cam_mclk rate:\n" +				   " expected : %d\n" +				   " actual   : %ld\n", CM_CAM_MCLK_HZ, rate); +	r = clk_prepare_enable(isp->clock[ISP_CLK_CSI2_FCK]); +	if (r) { +		dev_err(isp->dev, "failed to enable csi2_fck clock\n"); +		goto out_clk_enable_csi2_fclk; +	} +	return 0; + +out_clk_enable_csi2_fclk: +	clk_disable_unprepare(isp->clock[ISP_CLK_CAM_MCLK]); +out_clk_enable_mclk: +	clk_disable_unprepare(isp->clock[ISP_CLK_CAM_ICK]); +out_clk_enable_ick: +	return r; +} + +/* + * isp_disable_clocks - Disable ISP clocks + * @isp: OMAP3 ISP device + */ +static void isp_disable_clocks(struct isp_device *isp) +{ +	clk_disable_unprepare(isp->clock[ISP_CLK_CAM_ICK]); +	clk_disable_unprepare(isp->clock[ISP_CLK_CAM_MCLK]); +	clk_disable_unprepare(isp->clock[ISP_CLK_CSI2_FCK]); +} + +static const char *isp_clocks[] = { +	"cam_ick", +	"cam_mclk", +	"csi2_96m_fck", +	"l3_ick", +}; + +static int isp_get_clocks(struct isp_device *isp) +{ +	struct clk *clk; +	unsigned int i; + +	for (i = 0; i < ARRAY_SIZE(isp_clocks); ++i) { +		clk = devm_clk_get(isp->dev, isp_clocks[i]); +		if (IS_ERR(clk)) { +			dev_err(isp->dev, "clk_get %s failed\n", isp_clocks[i]); +			return PTR_ERR(clk); +		} + +		isp->clock[i] = clk; +	} + +	return 0; +} + +/* + * omap3isp_get - Acquire the ISP resource. + * + * Initializes the clocks for the first acquire. + * + * Increment the reference count on the ISP. If the first reference is taken, + * enable clocks and power-up all submodules. + * + * Return a pointer to the ISP device structure, or NULL if an error occurred. + */ +static struct isp_device *__omap3isp_get(struct isp_device *isp, bool irq) +{ +	struct isp_device *__isp = isp; + +	if (isp == NULL) +		return NULL; + +	mutex_lock(&isp->isp_mutex); +	if (isp->ref_count > 0) +		goto out; + +	if (isp_enable_clocks(isp) < 0) { +		__isp = NULL; +		goto out; +	} + +	/* We don't want to restore context before saving it! */ +	if (isp->has_context) +		isp_restore_ctx(isp); + +	if (irq) +		isp_enable_interrupts(isp); + +out: +	if (__isp != NULL) +		isp->ref_count++; +	mutex_unlock(&isp->isp_mutex); + +	return __isp; +} + +struct isp_device *omap3isp_get(struct isp_device *isp) +{ +	return __omap3isp_get(isp, true); +} + +/* + * omap3isp_put - Release the ISP + * + * Decrement the reference count on the ISP. If the last reference is released, + * power-down all submodules, disable clocks and free temporary buffers. + */ +static void __omap3isp_put(struct isp_device *isp, bool save_ctx) +{ +	if (isp == NULL) +		return; + +	mutex_lock(&isp->isp_mutex); +	BUG_ON(isp->ref_count == 0); +	if (--isp->ref_count == 0) { +		isp_disable_interrupts(isp); +		if (save_ctx) { +			isp_save_ctx(isp); +			isp->has_context = 1; +		} +		/* Reset the ISP if an entity has failed to stop. This is the +		 * only way to recover from such conditions. +		 */ +		if (isp->crashed || isp->stop_failure) +			isp_reset(isp); +		isp_disable_clocks(isp); +	} +	mutex_unlock(&isp->isp_mutex); +} + +void omap3isp_put(struct isp_device *isp) +{ +	__omap3isp_put(isp, true); +} + +/* -------------------------------------------------------------------------- + * Platform device driver + */ + +/* + * omap3isp_print_status - Prints the values of the ISP Control Module registers + * @isp: OMAP3 ISP device + */ +#define ISP_PRINT_REGISTER(isp, name)\ +	dev_dbg(isp->dev, "###ISP " #name "=0x%08x\n", \ +		isp_reg_readl(isp, OMAP3_ISP_IOMEM_MAIN, ISP_##name)) +#define SBL_PRINT_REGISTER(isp, name)\ +	dev_dbg(isp->dev, "###SBL " #name "=0x%08x\n", \ +		isp_reg_readl(isp, OMAP3_ISP_IOMEM_SBL, ISPSBL_##name)) + +void omap3isp_print_status(struct isp_device *isp) +{ +	dev_dbg(isp->dev, "-------------ISP Register dump--------------\n"); + +	ISP_PRINT_REGISTER(isp, SYSCONFIG); +	ISP_PRINT_REGISTER(isp, SYSSTATUS); +	ISP_PRINT_REGISTER(isp, IRQ0ENABLE); +	ISP_PRINT_REGISTER(isp, IRQ0STATUS); +	ISP_PRINT_REGISTER(isp, TCTRL_GRESET_LENGTH); +	ISP_PRINT_REGISTER(isp, TCTRL_PSTRB_REPLAY); +	ISP_PRINT_REGISTER(isp, CTRL); +	ISP_PRINT_REGISTER(isp, TCTRL_CTRL); +	ISP_PRINT_REGISTER(isp, TCTRL_FRAME); +	ISP_PRINT_REGISTER(isp, TCTRL_PSTRB_DELAY); +	ISP_PRINT_REGISTER(isp, TCTRL_STRB_DELAY); +	ISP_PRINT_REGISTER(isp, TCTRL_SHUT_DELAY); +	ISP_PRINT_REGISTER(isp, TCTRL_PSTRB_LENGTH); +	ISP_PRINT_REGISTER(isp, TCTRL_STRB_LENGTH); +	ISP_PRINT_REGISTER(isp, TCTRL_SHUT_LENGTH); + +	SBL_PRINT_REGISTER(isp, PCR); +	SBL_PRINT_REGISTER(isp, SDR_REQ_EXP); + +	dev_dbg(isp->dev, "--------------------------------------------\n"); +} + +#ifdef CONFIG_PM + +/* + * Power management support. + * + * As the ISP can't properly handle an input video stream interruption on a non + * frame boundary, the ISP pipelines need to be stopped before sensors get + * suspended. However, as suspending the sensors can require a running clock, + * which can be provided by the ISP, the ISP can't be completely suspended + * before the sensor. + * + * To solve this problem power management support is split into prepare/complete + * and suspend/resume operations. The pipelines are stopped in prepare() and the + * ISP clocks get disabled in suspend(). Similarly, the clocks are reenabled in + * resume(), and the the pipelines are restarted in complete(). + * + * TODO: PM dependencies between the ISP and sensors are not modelled explicitly + * yet. + */ +static int isp_pm_prepare(struct device *dev) +{ +	struct isp_device *isp = dev_get_drvdata(dev); +	int reset; + +	WARN_ON(mutex_is_locked(&isp->isp_mutex)); + +	if (isp->ref_count == 0) +		return 0; + +	reset = isp_suspend_modules(isp); +	isp_disable_interrupts(isp); +	isp_save_ctx(isp); +	if (reset) +		isp_reset(isp); + +	return 0; +} + +static int isp_pm_suspend(struct device *dev) +{ +	struct isp_device *isp = dev_get_drvdata(dev); + +	WARN_ON(mutex_is_locked(&isp->isp_mutex)); + +	if (isp->ref_count) +		isp_disable_clocks(isp); + +	return 0; +} + +static int isp_pm_resume(struct device *dev) +{ +	struct isp_device *isp = dev_get_drvdata(dev); + +	if (isp->ref_count == 0) +		return 0; + +	return isp_enable_clocks(isp); +} + +static void isp_pm_complete(struct device *dev) +{ +	struct isp_device *isp = dev_get_drvdata(dev); + +	if (isp->ref_count == 0) +		return; + +	isp_restore_ctx(isp); +	isp_enable_interrupts(isp); +	isp_resume_modules(isp); +} + +#else + +#define isp_pm_prepare	NULL +#define isp_pm_suspend	NULL +#define isp_pm_resume	NULL +#define isp_pm_complete	NULL + +#endif /* CONFIG_PM */ + +static void isp_unregister_entities(struct isp_device *isp) +{ +	omap3isp_csi2_unregister_entities(&isp->isp_csi2a); +	omap3isp_ccp2_unregister_entities(&isp->isp_ccp2); +	omap3isp_ccdc_unregister_entities(&isp->isp_ccdc); +	omap3isp_preview_unregister_entities(&isp->isp_prev); +	omap3isp_resizer_unregister_entities(&isp->isp_res); +	omap3isp_stat_unregister_entities(&isp->isp_aewb); +	omap3isp_stat_unregister_entities(&isp->isp_af); +	omap3isp_stat_unregister_entities(&isp->isp_hist); + +	v4l2_device_unregister(&isp->v4l2_dev); +	media_device_unregister(&isp->media_dev); +} + +/* + * isp_register_subdev_group - Register a group of subdevices + * @isp: OMAP3 ISP device + * @board_info: I2C subdevs board information array + * + * Register all I2C subdevices in the board_info array. The array must be + * terminated by a NULL entry, and the first entry must be the sensor. + * + * Return a pointer to the sensor media entity if it has been successfully + * registered, or NULL otherwise. + */ +static struct v4l2_subdev * +isp_register_subdev_group(struct isp_device *isp, +		     struct isp_subdev_i2c_board_info *board_info) +{ +	struct v4l2_subdev *sensor = NULL; +	unsigned int first; + +	if (board_info->board_info == NULL) +		return NULL; + +	for (first = 1; board_info->board_info; ++board_info, first = 0) { +		struct v4l2_subdev *subdev; +		struct i2c_adapter *adapter; + +		adapter = i2c_get_adapter(board_info->i2c_adapter_id); +		if (adapter == NULL) { +			dev_err(isp->dev, "%s: Unable to get I2C adapter %d for " +				"device %s\n", __func__, +				board_info->i2c_adapter_id, +				board_info->board_info->type); +			continue; +		} + +		subdev = v4l2_i2c_new_subdev_board(&isp->v4l2_dev, adapter, +				board_info->board_info, NULL); +		if (subdev == NULL) { +			dev_err(isp->dev, "%s: Unable to register subdev %s\n", +				__func__, board_info->board_info->type); +			continue; +		} + +		if (first) +			sensor = subdev; +	} + +	return sensor; +} + +static int isp_register_entities(struct isp_device *isp) +{ +	struct isp_platform_data *pdata = isp->pdata; +	struct isp_v4l2_subdevs_group *subdevs; +	int ret; + +	isp->media_dev.dev = isp->dev; +	strlcpy(isp->media_dev.model, "TI OMAP3 ISP", +		sizeof(isp->media_dev.model)); +	isp->media_dev.hw_revision = isp->revision; +	isp->media_dev.link_notify = isp_pipeline_link_notify; +	ret = media_device_register(&isp->media_dev); +	if (ret < 0) { +		dev_err(isp->dev, "%s: Media device registration failed (%d)\n", +			__func__, ret); +		return ret; +	} + +	isp->v4l2_dev.mdev = &isp->media_dev; +	ret = v4l2_device_register(isp->dev, &isp->v4l2_dev); +	if (ret < 0) { +		dev_err(isp->dev, "%s: V4L2 device registration failed (%d)\n", +			__func__, ret); +		goto done; +	} + +	/* Register internal entities */ +	ret = omap3isp_ccp2_register_entities(&isp->isp_ccp2, &isp->v4l2_dev); +	if (ret < 0) +		goto done; + +	ret = omap3isp_csi2_register_entities(&isp->isp_csi2a, &isp->v4l2_dev); +	if (ret < 0) +		goto done; + +	ret = omap3isp_ccdc_register_entities(&isp->isp_ccdc, &isp->v4l2_dev); +	if (ret < 0) +		goto done; + +	ret = omap3isp_preview_register_entities(&isp->isp_prev, +						 &isp->v4l2_dev); +	if (ret < 0) +		goto done; + +	ret = omap3isp_resizer_register_entities(&isp->isp_res, &isp->v4l2_dev); +	if (ret < 0) +		goto done; + +	ret = omap3isp_stat_register_entities(&isp->isp_aewb, &isp->v4l2_dev); +	if (ret < 0) +		goto done; + +	ret = omap3isp_stat_register_entities(&isp->isp_af, &isp->v4l2_dev); +	if (ret < 0) +		goto done; + +	ret = omap3isp_stat_register_entities(&isp->isp_hist, &isp->v4l2_dev); +	if (ret < 0) +		goto done; + +	/* Register external entities */ +	for (subdevs = pdata->subdevs; subdevs && subdevs->subdevs; ++subdevs) { +		struct v4l2_subdev *sensor; +		struct media_entity *input; +		unsigned int flags; +		unsigned int pad; +		unsigned int i; + +		sensor = isp_register_subdev_group(isp, subdevs->subdevs); +		if (sensor == NULL) +			continue; + +		sensor->host_priv = subdevs; + +		/* Connect the sensor to the correct interface module. Parallel +		 * sensors are connected directly to the CCDC, while serial +		 * sensors are connected to the CSI2a, CCP2b or CSI2c receiver +		 * through CSIPHY1 or CSIPHY2. +		 */ +		switch (subdevs->interface) { +		case ISP_INTERFACE_PARALLEL: +			input = &isp->isp_ccdc.subdev.entity; +			pad = CCDC_PAD_SINK; +			flags = 0; +			break; + +		case ISP_INTERFACE_CSI2A_PHY2: +			input = &isp->isp_csi2a.subdev.entity; +			pad = CSI2_PAD_SINK; +			flags = MEDIA_LNK_FL_IMMUTABLE +			      | MEDIA_LNK_FL_ENABLED; +			break; + +		case ISP_INTERFACE_CCP2B_PHY1: +		case ISP_INTERFACE_CCP2B_PHY2: +			input = &isp->isp_ccp2.subdev.entity; +			pad = CCP2_PAD_SINK; +			flags = 0; +			break; + +		case ISP_INTERFACE_CSI2C_PHY1: +			input = &isp->isp_csi2c.subdev.entity; +			pad = CSI2_PAD_SINK; +			flags = MEDIA_LNK_FL_IMMUTABLE +			      | MEDIA_LNK_FL_ENABLED; +			break; + +		default: +			dev_err(isp->dev, "%s: invalid interface type %u\n", +				__func__, subdevs->interface); +			ret = -EINVAL; +			goto done; +		} + +		for (i = 0; i < sensor->entity.num_pads; i++) { +			if (sensor->entity.pads[i].flags & MEDIA_PAD_FL_SOURCE) +				break; +		} +		if (i == sensor->entity.num_pads) { +			dev_err(isp->dev, +				"%s: no source pad in external entity\n", +				__func__); +			ret = -EINVAL; +			goto done; +		} + +		ret = media_entity_create_link(&sensor->entity, i, input, pad, +					       flags); +		if (ret < 0) +			goto done; +	} + +	ret = v4l2_device_register_subdev_nodes(&isp->v4l2_dev); + +done: +	if (ret < 0) +		isp_unregister_entities(isp); + +	return ret; +} + +static void isp_cleanup_modules(struct isp_device *isp) +{ +	omap3isp_h3a_aewb_cleanup(isp); +	omap3isp_h3a_af_cleanup(isp); +	omap3isp_hist_cleanup(isp); +	omap3isp_resizer_cleanup(isp); +	omap3isp_preview_cleanup(isp); +	omap3isp_ccdc_cleanup(isp); +	omap3isp_ccp2_cleanup(isp); +	omap3isp_csi2_cleanup(isp); +} + +static int isp_initialize_modules(struct isp_device *isp) +{ +	int ret; + +	ret = omap3isp_csiphy_init(isp); +	if (ret < 0) { +		dev_err(isp->dev, "CSI PHY initialization failed\n"); +		goto error_csiphy; +	} + +	ret = omap3isp_csi2_init(isp); +	if (ret < 0) { +		dev_err(isp->dev, "CSI2 initialization failed\n"); +		goto error_csi2; +	} + +	ret = omap3isp_ccp2_init(isp); +	if (ret < 0) { +		dev_err(isp->dev, "CCP2 initialization failed\n"); +		goto error_ccp2; +	} + +	ret = omap3isp_ccdc_init(isp); +	if (ret < 0) { +		dev_err(isp->dev, "CCDC initialization failed\n"); +		goto error_ccdc; +	} + +	ret = omap3isp_preview_init(isp); +	if (ret < 0) { +		dev_err(isp->dev, "Preview initialization failed\n"); +		goto error_preview; +	} + +	ret = omap3isp_resizer_init(isp); +	if (ret < 0) { +		dev_err(isp->dev, "Resizer initialization failed\n"); +		goto error_resizer; +	} + +	ret = omap3isp_hist_init(isp); +	if (ret < 0) { +		dev_err(isp->dev, "Histogram initialization failed\n"); +		goto error_hist; +	} + +	ret = omap3isp_h3a_aewb_init(isp); +	if (ret < 0) { +		dev_err(isp->dev, "H3A AEWB initialization failed\n"); +		goto error_h3a_aewb; +	} + +	ret = omap3isp_h3a_af_init(isp); +	if (ret < 0) { +		dev_err(isp->dev, "H3A AF initialization failed\n"); +		goto error_h3a_af; +	} + +	/* Connect the submodules. */ +	ret = media_entity_create_link( +			&isp->isp_csi2a.subdev.entity, CSI2_PAD_SOURCE, +			&isp->isp_ccdc.subdev.entity, CCDC_PAD_SINK, 0); +	if (ret < 0) +		goto error_link; + +	ret = media_entity_create_link( +			&isp->isp_ccp2.subdev.entity, CCP2_PAD_SOURCE, +			&isp->isp_ccdc.subdev.entity, CCDC_PAD_SINK, 0); +	if (ret < 0) +		goto error_link; + +	ret = media_entity_create_link( +			&isp->isp_ccdc.subdev.entity, CCDC_PAD_SOURCE_VP, +			&isp->isp_prev.subdev.entity, PREV_PAD_SINK, 0); +	if (ret < 0) +		goto error_link; + +	ret = media_entity_create_link( +			&isp->isp_ccdc.subdev.entity, CCDC_PAD_SOURCE_OF, +			&isp->isp_res.subdev.entity, RESZ_PAD_SINK, 0); +	if (ret < 0) +		goto error_link; + +	ret = media_entity_create_link( +			&isp->isp_prev.subdev.entity, PREV_PAD_SOURCE, +			&isp->isp_res.subdev.entity, RESZ_PAD_SINK, 0); +	if (ret < 0) +		goto error_link; + +	ret = media_entity_create_link( +			&isp->isp_ccdc.subdev.entity, CCDC_PAD_SOURCE_VP, +			&isp->isp_aewb.subdev.entity, 0, +			MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE); +	if (ret < 0) +		goto error_link; + +	ret = media_entity_create_link( +			&isp->isp_ccdc.subdev.entity, CCDC_PAD_SOURCE_VP, +			&isp->isp_af.subdev.entity, 0, +			MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE); +	if (ret < 0) +		goto error_link; + +	ret = media_entity_create_link( +			&isp->isp_ccdc.subdev.entity, CCDC_PAD_SOURCE_VP, +			&isp->isp_hist.subdev.entity, 0, +			MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE); +	if (ret < 0) +		goto error_link; + +	return 0; + +error_link: +	omap3isp_h3a_af_cleanup(isp); +error_h3a_af: +	omap3isp_h3a_aewb_cleanup(isp); +error_h3a_aewb: +	omap3isp_hist_cleanup(isp); +error_hist: +	omap3isp_resizer_cleanup(isp); +error_resizer: +	omap3isp_preview_cleanup(isp); +error_preview: +	omap3isp_ccdc_cleanup(isp); +error_ccdc: +	omap3isp_ccp2_cleanup(isp); +error_ccp2: +	omap3isp_csi2_cleanup(isp); +error_csi2: +error_csiphy: +	return ret; +} + +static void isp_detach_iommu(struct isp_device *isp) +{ +	arm_iommu_release_mapping(isp->mapping); +	isp->mapping = NULL; +	iommu_group_remove_device(isp->dev); +} + +static int isp_attach_iommu(struct isp_device *isp) +{ +	struct dma_iommu_mapping *mapping; +	struct iommu_group *group; +	int ret; + +	/* Create a device group and add the device to it. */ +	group = iommu_group_alloc(); +	if (IS_ERR(group)) { +		dev_err(isp->dev, "failed to allocate IOMMU group\n"); +		return PTR_ERR(group); +	} + +	ret = iommu_group_add_device(group, isp->dev); +	iommu_group_put(group); + +	if (ret < 0) { +		dev_err(isp->dev, "failed to add device to IPMMU group\n"); +		return ret; +	} + +	/* +	 * Create the ARM mapping, used by the ARM DMA mapping core to allocate +	 * VAs. This will allocate a corresponding IOMMU domain. +	 */ +	mapping = arm_iommu_create_mapping(&platform_bus_type, SZ_1G, SZ_2G); +	if (IS_ERR(mapping)) { +		dev_err(isp->dev, "failed to create ARM IOMMU mapping\n"); +		ret = PTR_ERR(mapping); +		goto error; +	} + +	isp->mapping = mapping; + +	/* Attach the ARM VA mapping to the device. */ +	ret = arm_iommu_attach_device(isp->dev, mapping); +	if (ret < 0) { +		dev_err(isp->dev, "failed to attach device to VA mapping\n"); +		goto error; +	} + +	return 0; + +error: +	isp_detach_iommu(isp); +	return ret; +} + +/* + * isp_remove - Remove ISP platform device + * @pdev: Pointer to ISP platform device + * + * Always returns 0. + */ +static int isp_remove(struct platform_device *pdev) +{ +	struct isp_device *isp = platform_get_drvdata(pdev); + +	isp_unregister_entities(isp); +	isp_cleanup_modules(isp); +	isp_xclk_cleanup(isp); + +	__omap3isp_get(isp, false); +	isp_detach_iommu(isp); +	__omap3isp_put(isp, false); + +	return 0; +} + +static int isp_map_mem_resource(struct platform_device *pdev, +				struct isp_device *isp, +				enum isp_mem_resources res) +{ +	struct resource *mem; + +	/* request the mem region for the camera registers */ + +	mem = platform_get_resource(pdev, IORESOURCE_MEM, res); + +	/* map the region */ +	isp->mmio_base[res] = devm_ioremap_resource(isp->dev, mem); +	if (IS_ERR(isp->mmio_base[res])) +		return PTR_ERR(isp->mmio_base[res]); + +	isp->mmio_base_phys[res] = mem->start; + +	return 0; +} + +/* + * isp_probe - Probe ISP platform device + * @pdev: Pointer to ISP platform device + * + * Returns 0 if successful, + *   -ENOMEM if no memory available, + *   -ENODEV if no platform device resources found + *     or no space for remapping registers, + *   -EINVAL if couldn't install ISR, + *   or clk_get return error value. + */ +static int isp_probe(struct platform_device *pdev) +{ +	struct isp_platform_data *pdata = pdev->dev.platform_data; +	struct isp_device *isp; +	int ret; +	int i, m; + +	if (pdata == NULL) +		return -EINVAL; + +	isp = devm_kzalloc(&pdev->dev, sizeof(*isp), GFP_KERNEL); +	if (!isp) { +		dev_err(&pdev->dev, "could not allocate memory\n"); +		return -ENOMEM; +	} + +	isp->autoidle = autoidle; + +	mutex_init(&isp->isp_mutex); +	spin_lock_init(&isp->stat_lock); + +	isp->dev = &pdev->dev; +	isp->pdata = pdata; +	isp->ref_count = 0; + +	ret = dma_coerce_mask_and_coherent(isp->dev, DMA_BIT_MASK(32)); +	if (ret) +		return ret; + +	platform_set_drvdata(pdev, isp); + +	/* Regulators */ +	isp->isp_csiphy1.vdd = devm_regulator_get(&pdev->dev, "VDD_CSIPHY1"); +	isp->isp_csiphy2.vdd = devm_regulator_get(&pdev->dev, "VDD_CSIPHY2"); + +	/* Clocks +	 * +	 * The ISP clock tree is revision-dependent. We thus need to enable ICLK +	 * manually to read the revision before calling __omap3isp_get(). +	 */ +	ret = isp_map_mem_resource(pdev, isp, OMAP3_ISP_IOMEM_MAIN); +	if (ret < 0) +		goto error; + +	ret = isp_get_clocks(isp); +	if (ret < 0) +		goto error; + +	ret = clk_enable(isp->clock[ISP_CLK_CAM_ICK]); +	if (ret < 0) +		goto error; + +	isp->revision = isp_reg_readl(isp, OMAP3_ISP_IOMEM_MAIN, ISP_REVISION); +	dev_info(isp->dev, "Revision %d.%d found\n", +		 (isp->revision & 0xf0) >> 4, isp->revision & 0x0f); + +	clk_disable(isp->clock[ISP_CLK_CAM_ICK]); + +	if (__omap3isp_get(isp, false) == NULL) { +		ret = -ENODEV; +		goto error; +	} + +	ret = isp_reset(isp); +	if (ret < 0) +		goto error_isp; + +	ret = isp_xclk_init(isp); +	if (ret < 0) +		goto error_isp; + +	/* Memory resources */ +	for (m = 0; m < ARRAY_SIZE(isp_res_maps); m++) +		if (isp->revision == isp_res_maps[m].isp_rev) +			break; + +	if (m == ARRAY_SIZE(isp_res_maps)) { +		dev_err(isp->dev, "No resource map found for ISP rev %d.%d\n", +			(isp->revision & 0xf0) >> 4, isp->revision & 0xf); +		ret = -ENODEV; +		goto error_isp; +	} + +	for (i = 1; i < OMAP3_ISP_IOMEM_LAST; i++) { +		if (isp_res_maps[m].map & 1 << i) { +			ret = isp_map_mem_resource(pdev, isp, i); +			if (ret) +				goto error_isp; +		} +	} + +	/* IOMMU */ +	ret = isp_attach_iommu(isp); +	if (ret < 0) { +		dev_err(&pdev->dev, "unable to attach to IOMMU\n"); +		goto error_isp; +	} + +	/* Interrupt */ +	isp->irq_num = platform_get_irq(pdev, 0); +	if (isp->irq_num <= 0) { +		dev_err(isp->dev, "No IRQ resource\n"); +		ret = -ENODEV; +		goto error_iommu; +	} + +	if (devm_request_irq(isp->dev, isp->irq_num, isp_isr, IRQF_SHARED, +			     "OMAP3 ISP", isp)) { +		dev_err(isp->dev, "Unable to request IRQ\n"); +		ret = -EINVAL; +		goto error_iommu; +	} + +	/* Entities */ +	ret = isp_initialize_modules(isp); +	if (ret < 0) +		goto error_iommu; + +	ret = isp_register_entities(isp); +	if (ret < 0) +		goto error_modules; + +	isp_core_init(isp, 1); +	omap3isp_put(isp); + +	return 0; + +error_modules: +	isp_cleanup_modules(isp); +error_iommu: +	isp_detach_iommu(isp); +error_isp: +	isp_xclk_cleanup(isp); +	__omap3isp_put(isp, false); +error: +	mutex_destroy(&isp->isp_mutex); + +	return ret; +} + +static const struct dev_pm_ops omap3isp_pm_ops = { +	.prepare = isp_pm_prepare, +	.suspend = isp_pm_suspend, +	.resume = isp_pm_resume, +	.complete = isp_pm_complete, +}; + +static struct platform_device_id omap3isp_id_table[] = { +	{ "omap3isp", 0 }, +	{ }, +}; +MODULE_DEVICE_TABLE(platform, omap3isp_id_table); + +static struct platform_driver omap3isp_driver = { +	.probe = isp_probe, +	.remove = isp_remove, +	.id_table = omap3isp_id_table, +	.driver = { +		.owner = THIS_MODULE, +		.name = "omap3isp", +		.pm	= &omap3isp_pm_ops, +	}, +}; + +module_platform_driver(omap3isp_driver); + +MODULE_AUTHOR("Nokia Corporation"); +MODULE_DESCRIPTION("TI OMAP3 ISP driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(ISP_VIDEO_DRIVER_VERSION); diff --git a/drivers/media/platform/omap3isp/isp.h b/drivers/media/platform/omap3isp/isp.h new file mode 100644 index 00000000000..2c314eea125 --- /dev/null +++ b/drivers/media/platform/omap3isp/isp.h @@ -0,0 +1,356 @@ +/* + * isp.h + * + * TI OMAP3 ISP - Core + * + * Copyright (C) 2009-2010 Nokia Corporation + * Copyright (C) 2009 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 + */ + +#ifndef OMAP3_ISP_CORE_H +#define OMAP3_ISP_CORE_H + +#include <media/omap3isp.h> +#include <media/v4l2-device.h> +#include <linux/clk-provider.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/iommu.h> +#include <linux/platform_device.h> +#include <linux/wait.h> + +#include "ispstat.h" +#include "ispccdc.h" +#include "ispreg.h" +#include "ispresizer.h" +#include "isppreview.h" +#include "ispcsiphy.h" +#include "ispcsi2.h" +#include "ispccp2.h" + +#define ISP_TOK_TERM		0xFFFFFFFF	/* +						 * terminating token for ISP +						 * modules reg list +						 */ +#define to_isp_device(ptr_module)				\ +	container_of(ptr_module, struct isp_device, isp_##ptr_module) +#define to_device(ptr_module)						\ +	(to_isp_device(ptr_module)->dev) + +enum isp_mem_resources { +	OMAP3_ISP_IOMEM_MAIN, +	OMAP3_ISP_IOMEM_CCP2, +	OMAP3_ISP_IOMEM_CCDC, +	OMAP3_ISP_IOMEM_HIST, +	OMAP3_ISP_IOMEM_H3A, +	OMAP3_ISP_IOMEM_PREV, +	OMAP3_ISP_IOMEM_RESZ, +	OMAP3_ISP_IOMEM_SBL, +	OMAP3_ISP_IOMEM_CSI2A_REGS1, +	OMAP3_ISP_IOMEM_CSIPHY2, +	OMAP3_ISP_IOMEM_CSI2A_REGS2, +	OMAP3_ISP_IOMEM_CSI2C_REGS1, +	OMAP3_ISP_IOMEM_CSIPHY1, +	OMAP3_ISP_IOMEM_CSI2C_REGS2, +	OMAP3_ISP_IOMEM_343X_CONTROL_CSIRXFE, +	OMAP3_ISP_IOMEM_3630_CONTROL_CAMERA_PHY_CTRL, +	OMAP3_ISP_IOMEM_LAST +}; + +enum isp_sbl_resource { +	OMAP3_ISP_SBL_CSI1_READ		= 0x1, +	OMAP3_ISP_SBL_CSI1_WRITE	= 0x2, +	OMAP3_ISP_SBL_CSI2A_WRITE	= 0x4, +	OMAP3_ISP_SBL_CSI2C_WRITE	= 0x8, +	OMAP3_ISP_SBL_CCDC_LSC_READ	= 0x10, +	OMAP3_ISP_SBL_CCDC_WRITE	= 0x20, +	OMAP3_ISP_SBL_PREVIEW_READ	= 0x40, +	OMAP3_ISP_SBL_PREVIEW_WRITE	= 0x80, +	OMAP3_ISP_SBL_RESIZER_READ	= 0x100, +	OMAP3_ISP_SBL_RESIZER_WRITE	= 0x200, +}; + +enum isp_subclk_resource { +	OMAP3_ISP_SUBCLK_CCDC		= (1 << 0), +	OMAP3_ISP_SUBCLK_AEWB		= (1 << 1), +	OMAP3_ISP_SUBCLK_AF		= (1 << 2), +	OMAP3_ISP_SUBCLK_HIST		= (1 << 3), +	OMAP3_ISP_SUBCLK_PREVIEW	= (1 << 4), +	OMAP3_ISP_SUBCLK_RESIZER	= (1 << 5), +}; + +/* ISP: OMAP 34xx ES 1.0 */ +#define ISP_REVISION_1_0		0x10 +/* ISP2: OMAP 34xx ES 2.0, 2.1 and 3.0 */ +#define ISP_REVISION_2_0		0x20 +/* ISP2P: OMAP 36xx */ +#define ISP_REVISION_15_0		0xF0 + +/* + * struct isp_res_mapping - Map ISP io resources to ISP revision. + * @isp_rev: ISP_REVISION_x_x + * @map: bitmap for enum isp_mem_resources + */ +struct isp_res_mapping { +	u32 isp_rev; +	u32 map; +}; + +/* + * struct isp_reg - Structure for ISP register values. + * @reg: 32-bit Register address. + * @val: 32-bit Register value. + */ +struct isp_reg { +	enum isp_mem_resources mmio_range; +	u32 reg; +	u32 val; +}; + +enum isp_xclk_id { +	ISP_XCLK_A, +	ISP_XCLK_B, +}; + +struct isp_xclk { +	struct isp_device *isp; +	struct clk_hw hw; +	struct clk_lookup *lookup; +	struct clk *clk; +	enum isp_xclk_id id; + +	spinlock_t lock;	/* Protects enabled and divider */ +	bool enabled; +	unsigned int divider; +}; + +/* + * struct isp_device - ISP device structure. + * @dev: Device pointer specific to the OMAP3 ISP. + * @revision: Stores current ISP module revision. + * @irq_num: Currently used IRQ number. + * @mmio_base: Array with kernel base addresses for ioremapped ISP register + *             regions. + * @mmio_base_phys: Array with physical L4 bus addresses for ISP register + *                  regions. + * @mapping: IOMMU mapping + * @stat_lock: Spinlock for handling statistics + * @isp_mutex: Mutex for serializing requests to ISP. + * @stop_failure: Indicates that an entity failed to stop. + * @crashed: Bitmask of crashed entities (indexed by entity ID) + * @has_context: Context has been saved at least once and can be restored. + * @ref_count: Reference count for handling multiple ISP requests. + * @cam_ick: Pointer to camera interface clock structure. + * @cam_mclk: Pointer to camera functional clock structure. + * @csi2_fck: Pointer to camera CSI2 complexIO clock structure. + * @l3_ick: Pointer to OMAP3 L3 bus interface clock. + * @xclks: External clocks provided by the ISP + * @irq: Currently attached ISP ISR callbacks information structure. + * @isp_af: Pointer to current settings for ISP AutoFocus SCM. + * @isp_hist: Pointer to current settings for ISP Histogram SCM. + * @isp_h3a: Pointer to current settings for ISP Auto Exposure and + *           White Balance SCM. + * @isp_res: Pointer to current settings for ISP Resizer. + * @isp_prev: Pointer to current settings for ISP Preview. + * @isp_ccdc: Pointer to current settings for ISP CCDC. + * @platform_cb: ISP driver callback function pointers for platform code + * + * This structure is used to store the OMAP ISP Information. + */ +struct isp_device { +	struct v4l2_device v4l2_dev; +	struct media_device media_dev; +	struct device *dev; +	u32 revision; + +	/* platform HW resources */ +	struct isp_platform_data *pdata; +	unsigned int irq_num; + +	void __iomem *mmio_base[OMAP3_ISP_IOMEM_LAST]; +	unsigned long mmio_base_phys[OMAP3_ISP_IOMEM_LAST]; + +	struct dma_iommu_mapping *mapping; + +	/* ISP Obj */ +	spinlock_t stat_lock;	/* common lock for statistic drivers */ +	struct mutex isp_mutex;	/* For handling ref_count field */ +	bool stop_failure; +	u32 crashed; +	int has_context; +	int ref_count; +	unsigned int autoidle; +#define ISP_CLK_CAM_ICK		0 +#define ISP_CLK_CAM_MCLK	1 +#define ISP_CLK_CSI2_FCK	2 +#define ISP_CLK_L3_ICK		3 +	struct clk *clock[4]; +	struct isp_xclk xclks[2]; + +	/* ISP modules */ +	struct ispstat isp_af; +	struct ispstat isp_aewb; +	struct ispstat isp_hist; +	struct isp_res_device isp_res; +	struct isp_prev_device isp_prev; +	struct isp_ccdc_device isp_ccdc; +	struct isp_csi2_device isp_csi2a; +	struct isp_csi2_device isp_csi2c; +	struct isp_ccp2_device isp_ccp2; +	struct isp_csiphy isp_csiphy1; +	struct isp_csiphy isp_csiphy2; + +	unsigned int sbl_resources; +	unsigned int subclk_resources; +}; + +#define v4l2_dev_to_isp_device(dev) \ +	container_of(dev, struct isp_device, v4l2_dev) + +void omap3isp_hist_dma_done(struct isp_device *isp); + +void omap3isp_flush(struct isp_device *isp); + +int omap3isp_module_sync_idle(struct media_entity *me, wait_queue_head_t *wait, +			      atomic_t *stopping); + +int omap3isp_module_sync_is_stopping(wait_queue_head_t *wait, +				     atomic_t *stopping); + +int omap3isp_pipeline_set_stream(struct isp_pipeline *pipe, +				 enum isp_pipeline_stream_state state); +void omap3isp_pipeline_cancel_stream(struct isp_pipeline *pipe); +void omap3isp_configure_bridge(struct isp_device *isp, +			       enum ccdc_input_entity input, +			       const struct isp_parallel_platform_data *pdata, +			       unsigned int shift, unsigned int bridge); + +struct isp_device *omap3isp_get(struct isp_device *isp); +void omap3isp_put(struct isp_device *isp); + +void omap3isp_print_status(struct isp_device *isp); + +void omap3isp_sbl_enable(struct isp_device *isp, enum isp_sbl_resource res); +void omap3isp_sbl_disable(struct isp_device *isp, enum isp_sbl_resource res); + +void omap3isp_subclk_enable(struct isp_device *isp, +			    enum isp_subclk_resource res); +void omap3isp_subclk_disable(struct isp_device *isp, +			     enum isp_subclk_resource res); + +int omap3isp_pipeline_pm_use(struct media_entity *entity, int use); + +int omap3isp_register_entities(struct platform_device *pdev, +			       struct v4l2_device *v4l2_dev); +void omap3isp_unregister_entities(struct platform_device *pdev); + +/* + * isp_reg_readl - Read value of an OMAP3 ISP register + * @isp: Device pointer specific to the OMAP3 ISP. + * @isp_mmio_range: Range to which the register offset refers to. + * @reg_offset: Register offset to read from. + * + * Returns an unsigned 32 bit value with the required register contents. + */ +static inline +u32 isp_reg_readl(struct isp_device *isp, enum isp_mem_resources isp_mmio_range, +		  u32 reg_offset) +{ +	return __raw_readl(isp->mmio_base[isp_mmio_range] + reg_offset); +} + +/* + * isp_reg_writel - Write value to an OMAP3 ISP register + * @isp: Device pointer specific to the OMAP3 ISP. + * @reg_value: 32 bit value to write to the register. + * @isp_mmio_range: Range to which the register offset refers to. + * @reg_offset: Register offset to write into. + */ +static inline +void isp_reg_writel(struct isp_device *isp, u32 reg_value, +		    enum isp_mem_resources isp_mmio_range, u32 reg_offset) +{ +	__raw_writel(reg_value, isp->mmio_base[isp_mmio_range] + reg_offset); +} + +/* + * isp_reg_clr - Clear individual bits in an OMAP3 ISP register + * @isp: Device pointer specific to the OMAP3 ISP. + * @mmio_range: Range to which the register offset refers to. + * @reg: Register offset to work on. + * @clr_bits: 32 bit value which would be cleared in the register. + */ +static inline +void isp_reg_clr(struct isp_device *isp, enum isp_mem_resources mmio_range, +		 u32 reg, u32 clr_bits) +{ +	u32 v = isp_reg_readl(isp, mmio_range, reg); + +	isp_reg_writel(isp, v & ~clr_bits, mmio_range, reg); +} + +/* + * isp_reg_set - Set individual bits in an OMAP3 ISP register + * @isp: Device pointer specific to the OMAP3 ISP. + * @mmio_range: Range to which the register offset refers to. + * @reg: Register offset to work on. + * @set_bits: 32 bit value which would be set in the register. + */ +static inline +void isp_reg_set(struct isp_device *isp, enum isp_mem_resources mmio_range, +		 u32 reg, u32 set_bits) +{ +	u32 v = isp_reg_readl(isp, mmio_range, reg); + +	isp_reg_writel(isp, v | set_bits, mmio_range, reg); +} + +/* + * isp_reg_clr_set - Clear and set invidial bits in an OMAP3 ISP register + * @isp: Device pointer specific to the OMAP3 ISP. + * @mmio_range: Range to which the register offset refers to. + * @reg: Register offset to work on. + * @clr_bits: 32 bit value which would be cleared in the register. + * @set_bits: 32 bit value which would be set in the register. + * + * The clear operation is done first, and then the set operation. + */ +static inline +void isp_reg_clr_set(struct isp_device *isp, enum isp_mem_resources mmio_range, +		     u32 reg, u32 clr_bits, u32 set_bits) +{ +	u32 v = isp_reg_readl(isp, mmio_range, reg); + +	isp_reg_writel(isp, (v & ~clr_bits) | set_bits, mmio_range, reg); +} + +static inline enum v4l2_buf_type +isp_pad_buffer_type(const struct v4l2_subdev *subdev, int pad) +{ +	if (pad >= subdev->entity.num_pads) +		return 0; + +	if (subdev->entity.pads[pad].flags & MEDIA_PAD_FL_SINK) +		return V4L2_BUF_TYPE_VIDEO_OUTPUT; +	else +		return V4L2_BUF_TYPE_VIDEO_CAPTURE; +} + +#endif	/* OMAP3_ISP_CORE_H */ diff --git a/drivers/media/platform/omap3isp/ispccdc.c b/drivers/media/platform/omap3isp/ispccdc.c new file mode 100644 index 00000000000..9f727d20f06 --- /dev/null +++ b/drivers/media/platform/omap3isp/ispccdc.c @@ -0,0 +1,2586 @@ +/* + * ispccdc.c + * + * TI OMAP3 ISP - CCDC module + * + * Copyright (C) 2009-2010 Nokia Corporation + * Copyright (C) 2009 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/module.h> +#include <linux/uaccess.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <linux/mm.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <media/v4l2-event.h> + +#include "isp.h" +#include "ispreg.h" +#include "ispccdc.h" + +#define CCDC_MIN_WIDTH		32 +#define CCDC_MIN_HEIGHT		32 + +static struct v4l2_mbus_framefmt * +__ccdc_get_format(struct isp_ccdc_device *ccdc, struct v4l2_subdev_fh *fh, +		  unsigned int pad, enum v4l2_subdev_format_whence which); + +static const unsigned int ccdc_fmts[] = { +	V4L2_MBUS_FMT_Y8_1X8, +	V4L2_MBUS_FMT_Y10_1X10, +	V4L2_MBUS_FMT_Y12_1X12, +	V4L2_MBUS_FMT_SGRBG8_1X8, +	V4L2_MBUS_FMT_SRGGB8_1X8, +	V4L2_MBUS_FMT_SBGGR8_1X8, +	V4L2_MBUS_FMT_SGBRG8_1X8, +	V4L2_MBUS_FMT_SGRBG10_1X10, +	V4L2_MBUS_FMT_SRGGB10_1X10, +	V4L2_MBUS_FMT_SBGGR10_1X10, +	V4L2_MBUS_FMT_SGBRG10_1X10, +	V4L2_MBUS_FMT_SGRBG12_1X12, +	V4L2_MBUS_FMT_SRGGB12_1X12, +	V4L2_MBUS_FMT_SBGGR12_1X12, +	V4L2_MBUS_FMT_SGBRG12_1X12, +	V4L2_MBUS_FMT_YUYV8_2X8, +	V4L2_MBUS_FMT_UYVY8_2X8, +}; + +/* + * ccdc_print_status - Print current CCDC Module register values. + * @ccdc: Pointer to ISP CCDC device. + * + * Also prints other debug information stored in the CCDC module. + */ +#define CCDC_PRINT_REGISTER(isp, name)\ +	dev_dbg(isp->dev, "###CCDC " #name "=0x%08x\n", \ +		isp_reg_readl(isp, OMAP3_ISP_IOMEM_CCDC, ISPCCDC_##name)) + +static void ccdc_print_status(struct isp_ccdc_device *ccdc) +{ +	struct isp_device *isp = to_isp_device(ccdc); + +	dev_dbg(isp->dev, "-------------CCDC Register dump-------------\n"); + +	CCDC_PRINT_REGISTER(isp, PCR); +	CCDC_PRINT_REGISTER(isp, SYN_MODE); +	CCDC_PRINT_REGISTER(isp, HD_VD_WID); +	CCDC_PRINT_REGISTER(isp, PIX_LINES); +	CCDC_PRINT_REGISTER(isp, HORZ_INFO); +	CCDC_PRINT_REGISTER(isp, VERT_START); +	CCDC_PRINT_REGISTER(isp, VERT_LINES); +	CCDC_PRINT_REGISTER(isp, CULLING); +	CCDC_PRINT_REGISTER(isp, HSIZE_OFF); +	CCDC_PRINT_REGISTER(isp, SDOFST); +	CCDC_PRINT_REGISTER(isp, SDR_ADDR); +	CCDC_PRINT_REGISTER(isp, CLAMP); +	CCDC_PRINT_REGISTER(isp, DCSUB); +	CCDC_PRINT_REGISTER(isp, COLPTN); +	CCDC_PRINT_REGISTER(isp, BLKCMP); +	CCDC_PRINT_REGISTER(isp, FPC); +	CCDC_PRINT_REGISTER(isp, FPC_ADDR); +	CCDC_PRINT_REGISTER(isp, VDINT); +	CCDC_PRINT_REGISTER(isp, ALAW); +	CCDC_PRINT_REGISTER(isp, REC656IF); +	CCDC_PRINT_REGISTER(isp, CFG); +	CCDC_PRINT_REGISTER(isp, FMTCFG); +	CCDC_PRINT_REGISTER(isp, FMT_HORZ); +	CCDC_PRINT_REGISTER(isp, FMT_VERT); +	CCDC_PRINT_REGISTER(isp, PRGEVEN0); +	CCDC_PRINT_REGISTER(isp, PRGEVEN1); +	CCDC_PRINT_REGISTER(isp, PRGODD0); +	CCDC_PRINT_REGISTER(isp, PRGODD1); +	CCDC_PRINT_REGISTER(isp, VP_OUT); +	CCDC_PRINT_REGISTER(isp, LSC_CONFIG); +	CCDC_PRINT_REGISTER(isp, LSC_INITIAL); +	CCDC_PRINT_REGISTER(isp, LSC_TABLE_BASE); +	CCDC_PRINT_REGISTER(isp, LSC_TABLE_OFFSET); + +	dev_dbg(isp->dev, "--------------------------------------------\n"); +} + +/* + * omap3isp_ccdc_busy - Get busy state of the CCDC. + * @ccdc: Pointer to ISP CCDC device. + */ +int omap3isp_ccdc_busy(struct isp_ccdc_device *ccdc) +{ +	struct isp_device *isp = to_isp_device(ccdc); + +	return isp_reg_readl(isp, OMAP3_ISP_IOMEM_CCDC, ISPCCDC_PCR) & +		ISPCCDC_PCR_BUSY; +} + +/* ----------------------------------------------------------------------------- + * Lens Shading Compensation + */ + +/* + * ccdc_lsc_validate_config - Check that LSC configuration is valid. + * @ccdc: Pointer to ISP CCDC device. + * @lsc_cfg: the LSC configuration to check. + * + * Returns 0 if the LSC configuration is valid, or -EINVAL if invalid. + */ +static int ccdc_lsc_validate_config(struct isp_ccdc_device *ccdc, +				    struct omap3isp_ccdc_lsc_config *lsc_cfg) +{ +	struct isp_device *isp = to_isp_device(ccdc); +	struct v4l2_mbus_framefmt *format; +	unsigned int paxel_width, paxel_height; +	unsigned int paxel_shift_x, paxel_shift_y; +	unsigned int min_width, min_height, min_size; +	unsigned int input_width, input_height; + +	paxel_shift_x = lsc_cfg->gain_mode_m; +	paxel_shift_y = lsc_cfg->gain_mode_n; + +	if ((paxel_shift_x < 2) || (paxel_shift_x > 6) || +	    (paxel_shift_y < 2) || (paxel_shift_y > 6)) { +		dev_dbg(isp->dev, "CCDC: LSC: Invalid paxel size\n"); +		return -EINVAL; +	} + +	if (lsc_cfg->offset & 3) { +		dev_dbg(isp->dev, "CCDC: LSC: Offset must be a multiple of " +			"4\n"); +		return -EINVAL; +	} + +	if ((lsc_cfg->initial_x & 1) || (lsc_cfg->initial_y & 1)) { +		dev_dbg(isp->dev, "CCDC: LSC: initial_x and y must be even\n"); +		return -EINVAL; +	} + +	format = __ccdc_get_format(ccdc, NULL, CCDC_PAD_SINK, +				   V4L2_SUBDEV_FORMAT_ACTIVE); +	input_width = format->width; +	input_height = format->height; + +	/* Calculate minimum bytesize for validation */ +	paxel_width = 1 << paxel_shift_x; +	min_width = ((input_width + lsc_cfg->initial_x + paxel_width - 1) +		     >> paxel_shift_x) + 1; + +	paxel_height = 1 << paxel_shift_y; +	min_height = ((input_height + lsc_cfg->initial_y + paxel_height - 1) +		     >> paxel_shift_y) + 1; + +	min_size = 4 * min_width * min_height; +	if (min_size > lsc_cfg->size) { +		dev_dbg(isp->dev, "CCDC: LSC: too small table\n"); +		return -EINVAL; +	} +	if (lsc_cfg->offset < (min_width * 4)) { +		dev_dbg(isp->dev, "CCDC: LSC: Offset is too small\n"); +		return -EINVAL; +	} +	if ((lsc_cfg->size / lsc_cfg->offset) < min_height) { +		dev_dbg(isp->dev, "CCDC: LSC: Wrong size/offset combination\n"); +		return -EINVAL; +	} +	return 0; +} + +/* + * ccdc_lsc_program_table - Program Lens Shading Compensation table address. + * @ccdc: Pointer to ISP CCDC device. + */ +static void ccdc_lsc_program_table(struct isp_ccdc_device *ccdc, +				   dma_addr_t addr) +{ +	isp_reg_writel(to_isp_device(ccdc), addr, +		       OMAP3_ISP_IOMEM_CCDC, ISPCCDC_LSC_TABLE_BASE); +} + +/* + * ccdc_lsc_setup_regs - Configures the lens shading compensation module + * @ccdc: Pointer to ISP CCDC device. + */ +static void ccdc_lsc_setup_regs(struct isp_ccdc_device *ccdc, +				struct omap3isp_ccdc_lsc_config *cfg) +{ +	struct isp_device *isp = to_isp_device(ccdc); +	int reg; + +	isp_reg_writel(isp, cfg->offset, OMAP3_ISP_IOMEM_CCDC, +		       ISPCCDC_LSC_TABLE_OFFSET); + +	reg = 0; +	reg |= cfg->gain_mode_n << ISPCCDC_LSC_GAIN_MODE_N_SHIFT; +	reg |= cfg->gain_mode_m << ISPCCDC_LSC_GAIN_MODE_M_SHIFT; +	reg |= cfg->gain_format << ISPCCDC_LSC_GAIN_FORMAT_SHIFT; +	isp_reg_writel(isp, reg, OMAP3_ISP_IOMEM_CCDC, ISPCCDC_LSC_CONFIG); + +	reg = 0; +	reg &= ~ISPCCDC_LSC_INITIAL_X_MASK; +	reg |= cfg->initial_x << ISPCCDC_LSC_INITIAL_X_SHIFT; +	reg &= ~ISPCCDC_LSC_INITIAL_Y_MASK; +	reg |= cfg->initial_y << ISPCCDC_LSC_INITIAL_Y_SHIFT; +	isp_reg_writel(isp, reg, OMAP3_ISP_IOMEM_CCDC, +		       ISPCCDC_LSC_INITIAL); +} + +static int ccdc_lsc_wait_prefetch(struct isp_ccdc_device *ccdc) +{ +	struct isp_device *isp = to_isp_device(ccdc); +	unsigned int wait; + +	isp_reg_writel(isp, IRQ0STATUS_CCDC_LSC_PREF_COMP_IRQ, +		       OMAP3_ISP_IOMEM_MAIN, ISP_IRQ0STATUS); + +	/* timeout 1 ms */ +	for (wait = 0; wait < 1000; wait++) { +		if (isp_reg_readl(isp, OMAP3_ISP_IOMEM_MAIN, ISP_IRQ0STATUS) & +				  IRQ0STATUS_CCDC_LSC_PREF_COMP_IRQ) { +			isp_reg_writel(isp, IRQ0STATUS_CCDC_LSC_PREF_COMP_IRQ, +				       OMAP3_ISP_IOMEM_MAIN, ISP_IRQ0STATUS); +			return 0; +		} + +		rmb(); +		udelay(1); +	} + +	return -ETIMEDOUT; +} + +/* + * __ccdc_lsc_enable - Enables/Disables the Lens Shading Compensation module. + * @ccdc: Pointer to ISP CCDC device. + * @enable: 0 Disables LSC, 1 Enables LSC. + */ +static int __ccdc_lsc_enable(struct isp_ccdc_device *ccdc, int enable) +{ +	struct isp_device *isp = to_isp_device(ccdc); +	const struct v4l2_mbus_framefmt *format = +		__ccdc_get_format(ccdc, NULL, CCDC_PAD_SINK, +				  V4L2_SUBDEV_FORMAT_ACTIVE); + +	if ((format->code != V4L2_MBUS_FMT_SGRBG10_1X10) && +	    (format->code != V4L2_MBUS_FMT_SRGGB10_1X10) && +	    (format->code != V4L2_MBUS_FMT_SBGGR10_1X10) && +	    (format->code != V4L2_MBUS_FMT_SGBRG10_1X10)) +		return -EINVAL; + +	if (enable) +		omap3isp_sbl_enable(isp, OMAP3_ISP_SBL_CCDC_LSC_READ); + +	isp_reg_clr_set(isp, OMAP3_ISP_IOMEM_CCDC, ISPCCDC_LSC_CONFIG, +			ISPCCDC_LSC_ENABLE, enable ? ISPCCDC_LSC_ENABLE : 0); + +	if (enable) { +		if (ccdc_lsc_wait_prefetch(ccdc) < 0) { +			isp_reg_clr(isp, OMAP3_ISP_IOMEM_CCDC, +				    ISPCCDC_LSC_CONFIG, ISPCCDC_LSC_ENABLE); +			ccdc->lsc.state = LSC_STATE_STOPPED; +			dev_warn(to_device(ccdc), "LSC prefetch timeout\n"); +			return -ETIMEDOUT; +		} +		ccdc->lsc.state = LSC_STATE_RUNNING; +	} else { +		ccdc->lsc.state = LSC_STATE_STOPPING; +	} + +	return 0; +} + +static int ccdc_lsc_busy(struct isp_ccdc_device *ccdc) +{ +	struct isp_device *isp = to_isp_device(ccdc); + +	return isp_reg_readl(isp, OMAP3_ISP_IOMEM_CCDC, ISPCCDC_LSC_CONFIG) & +			     ISPCCDC_LSC_BUSY; +} + +/* __ccdc_lsc_configure - Apply a new configuration to the LSC engine + * @ccdc: Pointer to ISP CCDC device + * @req: New configuration request + * + * context: in_interrupt() + */ +static int __ccdc_lsc_configure(struct isp_ccdc_device *ccdc, +				struct ispccdc_lsc_config_req *req) +{ +	if (!req->enable) +		return -EINVAL; + +	if (ccdc_lsc_validate_config(ccdc, &req->config) < 0) { +		dev_dbg(to_device(ccdc), "Discard LSC configuration\n"); +		return -EINVAL; +	} + +	if (ccdc_lsc_busy(ccdc)) +		return -EBUSY; + +	ccdc_lsc_setup_regs(ccdc, &req->config); +	ccdc_lsc_program_table(ccdc, req->table.dma); +	return 0; +} + +/* + * ccdc_lsc_error_handler - Handle LSC prefetch error scenario. + * @ccdc: Pointer to ISP CCDC device. + * + * Disables LSC, and defers enablement to shadow registers update time. + */ +static void ccdc_lsc_error_handler(struct isp_ccdc_device *ccdc) +{ +	struct isp_device *isp = to_isp_device(ccdc); +	/* +	 * From OMAP3 TRM: When this event is pending, the module +	 * goes into transparent mode (output =input). Normal +	 * operation can be resumed at the start of the next frame +	 * after: +	 *  1) Clearing this event +	 *  2) Disabling the LSC module +	 *  3) Enabling it +	 */ +	isp_reg_clr(isp, OMAP3_ISP_IOMEM_CCDC, ISPCCDC_LSC_CONFIG, +		    ISPCCDC_LSC_ENABLE); +	ccdc->lsc.state = LSC_STATE_STOPPED; +} + +static void ccdc_lsc_free_request(struct isp_ccdc_device *ccdc, +				  struct ispccdc_lsc_config_req *req) +{ +	struct isp_device *isp = to_isp_device(ccdc); + +	if (req == NULL) +		return; + +	if (req->table.addr) { +		sg_free_table(&req->table.sgt); +		dma_free_coherent(isp->dev, req->config.size, req->table.addr, +				  req->table.dma); +	} + +	kfree(req); +} + +static void ccdc_lsc_free_queue(struct isp_ccdc_device *ccdc, +				struct list_head *queue) +{ +	struct ispccdc_lsc_config_req *req, *n; +	unsigned long flags; + +	spin_lock_irqsave(&ccdc->lsc.req_lock, flags); +	list_for_each_entry_safe(req, n, queue, list) { +		list_del(&req->list); +		spin_unlock_irqrestore(&ccdc->lsc.req_lock, flags); +		ccdc_lsc_free_request(ccdc, req); +		spin_lock_irqsave(&ccdc->lsc.req_lock, flags); +	} +	spin_unlock_irqrestore(&ccdc->lsc.req_lock, flags); +} + +static void ccdc_lsc_free_table_work(struct work_struct *work) +{ +	struct isp_ccdc_device *ccdc; +	struct ispccdc_lsc *lsc; + +	lsc = container_of(work, struct ispccdc_lsc, table_work); +	ccdc = container_of(lsc, struct isp_ccdc_device, lsc); + +	ccdc_lsc_free_queue(ccdc, &lsc->free_queue); +} + +/* + * ccdc_lsc_config - Configure the LSC module from a userspace request + * + * Store the request LSC configuration in the LSC engine request pointer. The + * configuration will be applied to the hardware when the CCDC will be enabled, + * or at the next LSC interrupt if the CCDC is already running. + */ +static int ccdc_lsc_config(struct isp_ccdc_device *ccdc, +			   struct omap3isp_ccdc_update_config *config) +{ +	struct isp_device *isp = to_isp_device(ccdc); +	struct ispccdc_lsc_config_req *req; +	unsigned long flags; +	u16 update; +	int ret; + +	update = config->update & +		 (OMAP3ISP_CCDC_CONFIG_LSC | OMAP3ISP_CCDC_TBL_LSC); +	if (!update) +		return 0; + +	if (update != (OMAP3ISP_CCDC_CONFIG_LSC | OMAP3ISP_CCDC_TBL_LSC)) { +		dev_dbg(to_device(ccdc), "%s: Both LSC configuration and table " +			"need to be supplied\n", __func__); +		return -EINVAL; +	} + +	req = kzalloc(sizeof(*req), GFP_KERNEL); +	if (req == NULL) +		return -ENOMEM; + +	if (config->flag & OMAP3ISP_CCDC_CONFIG_LSC) { +		if (copy_from_user(&req->config, config->lsc_cfg, +				   sizeof(req->config))) { +			ret = -EFAULT; +			goto done; +		} + +		req->enable = 1; + +		req->table.addr = dma_alloc_coherent(isp->dev, req->config.size, +						     &req->table.dma, +						     GFP_KERNEL); +		if (req->table.addr == NULL) { +			ret = -ENOMEM; +			goto done; +		} + +		ret = dma_get_sgtable(isp->dev, &req->table.sgt, +				      req->table.addr, req->table.dma, +				      req->config.size); +		if (ret < 0) +			goto done; + +		dma_sync_sg_for_cpu(isp->dev, req->table.sgt.sgl, +				    req->table.sgt.nents, DMA_TO_DEVICE); + +		if (copy_from_user(req->table.addr, config->lsc, +				   req->config.size)) { +			ret = -EFAULT; +			goto done; +		} + +		dma_sync_sg_for_device(isp->dev, req->table.sgt.sgl, +				       req->table.sgt.nents, DMA_TO_DEVICE); +	} + +	spin_lock_irqsave(&ccdc->lsc.req_lock, flags); +	if (ccdc->lsc.request) { +		list_add_tail(&ccdc->lsc.request->list, &ccdc->lsc.free_queue); +		schedule_work(&ccdc->lsc.table_work); +	} +	ccdc->lsc.request = req; +	spin_unlock_irqrestore(&ccdc->lsc.req_lock, flags); + +	ret = 0; + +done: +	if (ret < 0) +		ccdc_lsc_free_request(ccdc, req); + +	return ret; +} + +static inline int ccdc_lsc_is_configured(struct isp_ccdc_device *ccdc) +{ +	unsigned long flags; + +	spin_lock_irqsave(&ccdc->lsc.req_lock, flags); +	if (ccdc->lsc.active) { +		spin_unlock_irqrestore(&ccdc->lsc.req_lock, flags); +		return 1; +	} +	spin_unlock_irqrestore(&ccdc->lsc.req_lock, flags); +	return 0; +} + +static int ccdc_lsc_enable(struct isp_ccdc_device *ccdc) +{ +	struct ispccdc_lsc *lsc = &ccdc->lsc; + +	if (lsc->state != LSC_STATE_STOPPED) +		return -EINVAL; + +	if (lsc->active) { +		list_add_tail(&lsc->active->list, &lsc->free_queue); +		lsc->active = NULL; +	} + +	if (__ccdc_lsc_configure(ccdc, lsc->request) < 0) { +		omap3isp_sbl_disable(to_isp_device(ccdc), +				OMAP3_ISP_SBL_CCDC_LSC_READ); +		list_add_tail(&lsc->request->list, &lsc->free_queue); +		lsc->request = NULL; +		goto done; +	} + +	lsc->active = lsc->request; +	lsc->request = NULL; +	__ccdc_lsc_enable(ccdc, 1); + +done: +	if (!list_empty(&lsc->free_queue)) +		schedule_work(&lsc->table_work); + +	return 0; +} + +/* ----------------------------------------------------------------------------- + * Parameters configuration + */ + +/* + * ccdc_configure_clamp - Configure optical-black or digital clamping + * @ccdc: Pointer to ISP CCDC device. + * + * The CCDC performs either optical-black or digital clamp. Configure and enable + * the selected clamp method. + */ +static void ccdc_configure_clamp(struct isp_ccdc_device *ccdc) +{ +	struct isp_device *isp = to_isp_device(ccdc); +	u32 clamp; + +	if (ccdc->obclamp) { +		clamp  = ccdc->clamp.obgain << ISPCCDC_CLAMP_OBGAIN_SHIFT; +		clamp |= ccdc->clamp.oblen << ISPCCDC_CLAMP_OBSLEN_SHIFT; +		clamp |= ccdc->clamp.oblines << ISPCCDC_CLAMP_OBSLN_SHIFT; +		clamp |= ccdc->clamp.obstpixel << ISPCCDC_CLAMP_OBST_SHIFT; +		isp_reg_writel(isp, clamp, OMAP3_ISP_IOMEM_CCDC, ISPCCDC_CLAMP); +	} else { +		isp_reg_writel(isp, ccdc->clamp.dcsubval, +			       OMAP3_ISP_IOMEM_CCDC, ISPCCDC_DCSUB); +	} + +	isp_reg_clr_set(isp, OMAP3_ISP_IOMEM_CCDC, ISPCCDC_CLAMP, +			ISPCCDC_CLAMP_CLAMPEN, +			ccdc->obclamp ? ISPCCDC_CLAMP_CLAMPEN : 0); +} + +/* + * ccdc_configure_fpc - Configure Faulty Pixel Correction + * @ccdc: Pointer to ISP CCDC device. + */ +static void ccdc_configure_fpc(struct isp_ccdc_device *ccdc) +{ +	struct isp_device *isp = to_isp_device(ccdc); + +	isp_reg_clr(isp, OMAP3_ISP_IOMEM_CCDC, ISPCCDC_FPC, ISPCCDC_FPC_FPCEN); + +	if (!ccdc->fpc_en) +		return; + +	isp_reg_writel(isp, ccdc->fpc.dma, OMAP3_ISP_IOMEM_CCDC, +		       ISPCCDC_FPC_ADDR); +	/* The FPNUM field must be set before enabling FPC. */ +	isp_reg_writel(isp, (ccdc->fpc.fpnum << ISPCCDC_FPC_FPNUM_SHIFT), +		       OMAP3_ISP_IOMEM_CCDC, ISPCCDC_FPC); +	isp_reg_writel(isp, (ccdc->fpc.fpnum << ISPCCDC_FPC_FPNUM_SHIFT) | +		       ISPCCDC_FPC_FPCEN, OMAP3_ISP_IOMEM_CCDC, ISPCCDC_FPC); +} + +/* + * ccdc_configure_black_comp - Configure Black Level Compensation. + * @ccdc: Pointer to ISP CCDC device. + */ +static void ccdc_configure_black_comp(struct isp_ccdc_device *ccdc) +{ +	struct isp_device *isp = to_isp_device(ccdc); +	u32 blcomp; + +	blcomp  = ccdc->blcomp.b_mg << ISPCCDC_BLKCMP_B_MG_SHIFT; +	blcomp |= ccdc->blcomp.gb_g << ISPCCDC_BLKCMP_GB_G_SHIFT; +	blcomp |= ccdc->blcomp.gr_cy << ISPCCDC_BLKCMP_GR_CY_SHIFT; +	blcomp |= ccdc->blcomp.r_ye << ISPCCDC_BLKCMP_R_YE_SHIFT; + +	isp_reg_writel(isp, blcomp, OMAP3_ISP_IOMEM_CCDC, ISPCCDC_BLKCMP); +} + +/* + * ccdc_configure_lpf - Configure Low-Pass Filter (LPF). + * @ccdc: Pointer to ISP CCDC device. + */ +static void ccdc_configure_lpf(struct isp_ccdc_device *ccdc) +{ +	struct isp_device *isp = to_isp_device(ccdc); + +	isp_reg_clr_set(isp, OMAP3_ISP_IOMEM_CCDC, ISPCCDC_SYN_MODE, +			ISPCCDC_SYN_MODE_LPF, +			ccdc->lpf ? ISPCCDC_SYN_MODE_LPF : 0); +} + +/* + * ccdc_configure_alaw - Configure A-law compression. + * @ccdc: Pointer to ISP CCDC device. + */ +static void ccdc_configure_alaw(struct isp_ccdc_device *ccdc) +{ +	struct isp_device *isp = to_isp_device(ccdc); +	const struct isp_format_info *info; +	u32 alaw = 0; + +	info = omap3isp_video_format_info(ccdc->formats[CCDC_PAD_SINK].code); + +	switch (info->width) { +	case 8: +		return; + +	case 10: +		alaw = ISPCCDC_ALAW_GWDI_9_0; +		break; +	case 11: +		alaw = ISPCCDC_ALAW_GWDI_10_1; +		break; +	case 12: +		alaw = ISPCCDC_ALAW_GWDI_11_2; +		break; +	case 13: +		alaw = ISPCCDC_ALAW_GWDI_12_3; +		break; +	} + +	if (ccdc->alaw) +		alaw |= ISPCCDC_ALAW_CCDTBL; + +	isp_reg_writel(isp, alaw, OMAP3_ISP_IOMEM_CCDC, ISPCCDC_ALAW); +} + +/* + * ccdc_config_imgattr - Configure sensor image specific attributes. + * @ccdc: Pointer to ISP CCDC device. + * @colptn: Color pattern of the sensor. + */ +static void ccdc_config_imgattr(struct isp_ccdc_device *ccdc, u32 colptn) +{ +	struct isp_device *isp = to_isp_device(ccdc); + +	isp_reg_writel(isp, colptn, OMAP3_ISP_IOMEM_CCDC, ISPCCDC_COLPTN); +} + +/* + * ccdc_config - Set CCDC configuration from userspace + * @ccdc: Pointer to ISP CCDC device. + * @ccdc_struct: Structure containing CCDC configuration sent from userspace. + * + * Returns 0 if successful, -EINVAL if the pointer to the configuration + * structure is null, or the copy_from_user function fails to copy user space + * memory to kernel space memory. + */ +static int ccdc_config(struct isp_ccdc_device *ccdc, +		       struct omap3isp_ccdc_update_config *ccdc_struct) +{ +	struct isp_device *isp = to_isp_device(ccdc); +	unsigned long flags; + +	spin_lock_irqsave(&ccdc->lock, flags); +	ccdc->shadow_update = 1; +	spin_unlock_irqrestore(&ccdc->lock, flags); + +	if (OMAP3ISP_CCDC_ALAW & ccdc_struct->update) { +		ccdc->alaw = !!(OMAP3ISP_CCDC_ALAW & ccdc_struct->flag); +		ccdc->update |= OMAP3ISP_CCDC_ALAW; +	} + +	if (OMAP3ISP_CCDC_LPF & ccdc_struct->update) { +		ccdc->lpf = !!(OMAP3ISP_CCDC_LPF & ccdc_struct->flag); +		ccdc->update |= OMAP3ISP_CCDC_LPF; +	} + +	if (OMAP3ISP_CCDC_BLCLAMP & ccdc_struct->update) { +		if (copy_from_user(&ccdc->clamp, ccdc_struct->bclamp, +				   sizeof(ccdc->clamp))) { +			ccdc->shadow_update = 0; +			return -EFAULT; +		} + +		ccdc->obclamp = !!(OMAP3ISP_CCDC_BLCLAMP & ccdc_struct->flag); +		ccdc->update |= OMAP3ISP_CCDC_BLCLAMP; +	} + +	if (OMAP3ISP_CCDC_BCOMP & ccdc_struct->update) { +		if (copy_from_user(&ccdc->blcomp, ccdc_struct->blcomp, +				   sizeof(ccdc->blcomp))) { +			ccdc->shadow_update = 0; +			return -EFAULT; +		} + +		ccdc->update |= OMAP3ISP_CCDC_BCOMP; +	} + +	ccdc->shadow_update = 0; + +	if (OMAP3ISP_CCDC_FPC & ccdc_struct->update) { +		struct omap3isp_ccdc_fpc fpc; +		struct ispccdc_fpc fpc_old = { .addr = NULL, }; +		struct ispccdc_fpc fpc_new; +		u32 size; + +		if (ccdc->state != ISP_PIPELINE_STREAM_STOPPED) +			return -EBUSY; + +		ccdc->fpc_en = !!(OMAP3ISP_CCDC_FPC & ccdc_struct->flag); + +		if (ccdc->fpc_en) { +			if (copy_from_user(&fpc, ccdc_struct->fpc, sizeof(fpc))) +				return -EFAULT; + +			size = fpc.fpnum * 4; + +			/* +			 * The table address must be 64-bytes aligned, which is +			 * guaranteed by dma_alloc_coherent(). +			 */ +			fpc_new.fpnum = fpc.fpnum; +			fpc_new.addr = dma_alloc_coherent(isp->dev, size, +							  &fpc_new.dma, +							  GFP_KERNEL); +			if (fpc_new.addr == NULL) +				return -ENOMEM; + +			if (copy_from_user(fpc_new.addr, +					   (__force void __user *)fpc.fpcaddr, +					   size)) { +				dma_free_coherent(isp->dev, size, fpc_new.addr, +						  fpc_new.dma); +				return -EFAULT; +			} + +			fpc_old = ccdc->fpc; +			ccdc->fpc = fpc_new; +		} + +		ccdc_configure_fpc(ccdc); + +		if (fpc_old.addr != NULL) +			dma_free_coherent(isp->dev, fpc_old.fpnum * 4, +					  fpc_old.addr, fpc_old.dma); +	} + +	return ccdc_lsc_config(ccdc, ccdc_struct); +} + +static void ccdc_apply_controls(struct isp_ccdc_device *ccdc) +{ +	if (ccdc->update & OMAP3ISP_CCDC_ALAW) { +		ccdc_configure_alaw(ccdc); +		ccdc->update &= ~OMAP3ISP_CCDC_ALAW; +	} + +	if (ccdc->update & OMAP3ISP_CCDC_LPF) { +		ccdc_configure_lpf(ccdc); +		ccdc->update &= ~OMAP3ISP_CCDC_LPF; +	} + +	if (ccdc->update & OMAP3ISP_CCDC_BLCLAMP) { +		ccdc_configure_clamp(ccdc); +		ccdc->update &= ~OMAP3ISP_CCDC_BLCLAMP; +	} + +	if (ccdc->update & OMAP3ISP_CCDC_BCOMP) { +		ccdc_configure_black_comp(ccdc); +		ccdc->update &= ~OMAP3ISP_CCDC_BCOMP; +	} +} + +/* + * omap3isp_ccdc_restore_context - Restore values of the CCDC module registers + * @isp: Pointer to ISP device + */ +void omap3isp_ccdc_restore_context(struct isp_device *isp) +{ +	struct isp_ccdc_device *ccdc = &isp->isp_ccdc; + +	isp_reg_set(isp, OMAP3_ISP_IOMEM_CCDC, ISPCCDC_CFG, ISPCCDC_CFG_VDLC); + +	ccdc->update = OMAP3ISP_CCDC_ALAW | OMAP3ISP_CCDC_LPF +		     | OMAP3ISP_CCDC_BLCLAMP | OMAP3ISP_CCDC_BCOMP; +	ccdc_apply_controls(ccdc); +	ccdc_configure_fpc(ccdc); +} + +/* ----------------------------------------------------------------------------- + * Format- and pipeline-related configuration helpers + */ + +/* + * ccdc_config_vp - Configure the Video Port. + * @ccdc: Pointer to ISP CCDC device. + */ +static void ccdc_config_vp(struct isp_ccdc_device *ccdc) +{ +	struct isp_pipeline *pipe = to_isp_pipeline(&ccdc->subdev.entity); +	struct isp_device *isp = to_isp_device(ccdc); +	const struct isp_format_info *info; +	unsigned long l3_ick = pipe->l3_ick; +	unsigned int max_div = isp->revision == ISP_REVISION_15_0 ? 64 : 8; +	unsigned int div = 0; +	u32 fmtcfg_vp; + +	fmtcfg_vp = isp_reg_readl(isp, OMAP3_ISP_IOMEM_CCDC, ISPCCDC_FMTCFG) +		  & ~(ISPCCDC_FMTCFG_VPIN_MASK | ISPCCDC_FMTCFG_VPIF_FRQ_MASK); + +	info = omap3isp_video_format_info(ccdc->formats[CCDC_PAD_SINK].code); + +	switch (info->width) { +	case 8: +	case 10: +		fmtcfg_vp |= ISPCCDC_FMTCFG_VPIN_9_0; +		break; +	case 11: +		fmtcfg_vp |= ISPCCDC_FMTCFG_VPIN_10_1; +		break; +	case 12: +		fmtcfg_vp |= ISPCCDC_FMTCFG_VPIN_11_2; +		break; +	case 13: +		fmtcfg_vp |= ISPCCDC_FMTCFG_VPIN_12_3; +		break; +	} + +	if (pipe->input) +		div = DIV_ROUND_UP(l3_ick, pipe->max_rate); +	else if (pipe->external_rate) +		div = l3_ick / pipe->external_rate; + +	div = clamp(div, 2U, max_div); +	fmtcfg_vp |= (div - 2) << ISPCCDC_FMTCFG_VPIF_FRQ_SHIFT; + +	isp_reg_writel(isp, fmtcfg_vp, OMAP3_ISP_IOMEM_CCDC, ISPCCDC_FMTCFG); +} + +/* + * ccdc_enable_vp - Enable Video Port. + * @ccdc: Pointer to ISP CCDC device. + * @enable: 0 Disables VP, 1 Enables VP + * + * This is needed for outputting image to Preview, H3A and HIST ISP submodules. + */ +static void ccdc_enable_vp(struct isp_ccdc_device *ccdc, u8 enable) +{ +	struct isp_device *isp = to_isp_device(ccdc); + +	isp_reg_clr_set(isp, OMAP3_ISP_IOMEM_CCDC, ISPCCDC_FMTCFG, +			ISPCCDC_FMTCFG_VPEN, enable ? ISPCCDC_FMTCFG_VPEN : 0); +} + +/* + * ccdc_config_outlineoffset - Configure memory saving output line offset + * @ccdc: Pointer to ISP CCDC device. + * @offset: Address offset to start a new line. 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. + */ +static void ccdc_config_outlineoffset(struct isp_ccdc_device *ccdc, +					u32 offset, u8 oddeven, u8 numlines) +{ +	struct isp_device *isp = to_isp_device(ccdc); + +	isp_reg_writel(isp, offset & 0xffff, +		       OMAP3_ISP_IOMEM_CCDC, ISPCCDC_HSIZE_OFF); + +	isp_reg_clr(isp, OMAP3_ISP_IOMEM_CCDC, ISPCCDC_SDOFST, +		    ISPCCDC_SDOFST_FINV); + +	isp_reg_clr(isp, OMAP3_ISP_IOMEM_CCDC, ISPCCDC_SDOFST, +		    ISPCCDC_SDOFST_FOFST_4L); + +	switch (oddeven) { +	case EVENEVEN: +		isp_reg_set(isp, OMAP3_ISP_IOMEM_CCDC, ISPCCDC_SDOFST, +			    (numlines & 0x7) << ISPCCDC_SDOFST_LOFST0_SHIFT); +		break; +	case ODDEVEN: +		isp_reg_set(isp, OMAP3_ISP_IOMEM_CCDC, ISPCCDC_SDOFST, +			    (numlines & 0x7) << ISPCCDC_SDOFST_LOFST1_SHIFT); +		break; +	case EVENODD: +		isp_reg_set(isp, OMAP3_ISP_IOMEM_CCDC, ISPCCDC_SDOFST, +			    (numlines & 0x7) << ISPCCDC_SDOFST_LOFST2_SHIFT); +		break; +	case ODDODD: +		isp_reg_set(isp, OMAP3_ISP_IOMEM_CCDC, ISPCCDC_SDOFST, +			    (numlines & 0x7) << ISPCCDC_SDOFST_LOFST3_SHIFT); +		break; +	default: +		break; +	} +} + +/* + * ccdc_set_outaddr - Set memory address to save output image + * @ccdc: Pointer to ISP CCDC device. + * @addr: ISP MMU Mapped 32-bit memory address aligned on 32 byte boundary. + * + * Sets the memory address where the output will be saved. + */ +static void ccdc_set_outaddr(struct isp_ccdc_device *ccdc, u32 addr) +{ +	struct isp_device *isp = to_isp_device(ccdc); + +	isp_reg_writel(isp, addr, OMAP3_ISP_IOMEM_CCDC, ISPCCDC_SDR_ADDR); +} + +/* + * omap3isp_ccdc_max_rate - Calculate maximum input data rate based on the input + * @ccdc: Pointer to ISP CCDC device. + * @max_rate: Maximum calculated data rate. + * + * Returns in *max_rate less value between calculated and passed + */ +void omap3isp_ccdc_max_rate(struct isp_ccdc_device *ccdc, +			    unsigned int *max_rate) +{ +	struct isp_pipeline *pipe = to_isp_pipeline(&ccdc->subdev.entity); +	unsigned int rate; + +	if (pipe == NULL) +		return; + +	/* +	 * TRM says that for parallel sensors the maximum data rate +	 * should be 90% form L3/2 clock, otherwise just L3/2. +	 */ +	if (ccdc->input == CCDC_INPUT_PARALLEL) +		rate = pipe->l3_ick / 2 * 9 / 10; +	else +		rate = pipe->l3_ick / 2; + +	*max_rate = min(*max_rate, rate); +} + +/* + * ccdc_config_sync_if - Set CCDC sync interface configuration + * @ccdc: Pointer to ISP CCDC device. + * @pdata: Parallel interface platform data (may be NULL) + * @data_size: Data size + */ +static void ccdc_config_sync_if(struct isp_ccdc_device *ccdc, +				struct isp_parallel_platform_data *pdata, +				unsigned int data_size) +{ +	struct isp_device *isp = to_isp_device(ccdc); +	const struct v4l2_mbus_framefmt *format; +	u32 syn_mode = ISPCCDC_SYN_MODE_VDHDEN; + +	format = &ccdc->formats[CCDC_PAD_SINK]; + +	if (format->code == V4L2_MBUS_FMT_YUYV8_2X8 || +	    format->code == V4L2_MBUS_FMT_UYVY8_2X8) { +		/* The bridge is enabled for YUV8 formats. Configure the input +		 * mode accordingly. +		 */ +		syn_mode |= ISPCCDC_SYN_MODE_INPMOD_YCBCR16; +	} + +	switch (data_size) { +	case 8: +		syn_mode |= ISPCCDC_SYN_MODE_DATSIZ_8; +		break; +	case 10: +		syn_mode |= ISPCCDC_SYN_MODE_DATSIZ_10; +		break; +	case 11: +		syn_mode |= ISPCCDC_SYN_MODE_DATSIZ_11; +		break; +	case 12: +		syn_mode |= ISPCCDC_SYN_MODE_DATSIZ_12; +		break; +	} + +	if (pdata && pdata->data_pol) +		syn_mode |= ISPCCDC_SYN_MODE_DATAPOL; + +	if (pdata && pdata->hs_pol) +		syn_mode |= ISPCCDC_SYN_MODE_HDPOL; + +	if (pdata && pdata->vs_pol) +		syn_mode |= ISPCCDC_SYN_MODE_VDPOL; + +	isp_reg_writel(isp, syn_mode, OMAP3_ISP_IOMEM_CCDC, ISPCCDC_SYN_MODE); + +	/* The CCDC_CFG.Y8POS bit is used in YCbCr8 input mode only. The +	 * hardware seems to ignore it in all other input modes. +	 */ +	if (format->code == V4L2_MBUS_FMT_UYVY8_2X8) +		isp_reg_set(isp, OMAP3_ISP_IOMEM_CCDC, ISPCCDC_CFG, +			    ISPCCDC_CFG_Y8POS); +	else +		isp_reg_clr(isp, OMAP3_ISP_IOMEM_CCDC, ISPCCDC_CFG, +			    ISPCCDC_CFG_Y8POS); + +	isp_reg_clr(isp, OMAP3_ISP_IOMEM_CCDC, ISPCCDC_REC656IF, +		    ISPCCDC_REC656IF_R656ON); +} + +/* CCDC formats descriptions */ +static const u32 ccdc_sgrbg_pattern = +	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; + +static const u32 ccdc_srggb_pattern = +	ISPCCDC_COLPTN_R_Ye  << ISPCCDC_COLPTN_CP0PLC0_SHIFT | +	ISPCCDC_COLPTN_Gr_Cy << ISPCCDC_COLPTN_CP0PLC1_SHIFT | +	ISPCCDC_COLPTN_R_Ye  << ISPCCDC_COLPTN_CP0PLC2_SHIFT | +	ISPCCDC_COLPTN_Gr_Cy << ISPCCDC_COLPTN_CP0PLC3_SHIFT | +	ISPCCDC_COLPTN_Gb_G  << ISPCCDC_COLPTN_CP1PLC0_SHIFT | +	ISPCCDC_COLPTN_B_Mg  << ISPCCDC_COLPTN_CP1PLC1_SHIFT | +	ISPCCDC_COLPTN_Gb_G  << ISPCCDC_COLPTN_CP1PLC2_SHIFT | +	ISPCCDC_COLPTN_B_Mg  << ISPCCDC_COLPTN_CP1PLC3_SHIFT | +	ISPCCDC_COLPTN_R_Ye  << ISPCCDC_COLPTN_CP2PLC0_SHIFT | +	ISPCCDC_COLPTN_Gr_Cy << ISPCCDC_COLPTN_CP2PLC1_SHIFT | +	ISPCCDC_COLPTN_R_Ye  << ISPCCDC_COLPTN_CP2PLC2_SHIFT | +	ISPCCDC_COLPTN_Gr_Cy << ISPCCDC_COLPTN_CP2PLC3_SHIFT | +	ISPCCDC_COLPTN_Gb_G  << ISPCCDC_COLPTN_CP3PLC0_SHIFT | +	ISPCCDC_COLPTN_B_Mg  << ISPCCDC_COLPTN_CP3PLC1_SHIFT | +	ISPCCDC_COLPTN_Gb_G  << ISPCCDC_COLPTN_CP3PLC2_SHIFT | +	ISPCCDC_COLPTN_B_Mg  << ISPCCDC_COLPTN_CP3PLC3_SHIFT; + +static const u32 ccdc_sbggr_pattern = +	ISPCCDC_COLPTN_B_Mg  << ISPCCDC_COLPTN_CP0PLC0_SHIFT | +	ISPCCDC_COLPTN_Gb_G  << ISPCCDC_COLPTN_CP0PLC1_SHIFT | +	ISPCCDC_COLPTN_B_Mg  << ISPCCDC_COLPTN_CP0PLC2_SHIFT | +	ISPCCDC_COLPTN_Gb_G  << ISPCCDC_COLPTN_CP0PLC3_SHIFT | +	ISPCCDC_COLPTN_Gr_Cy << ISPCCDC_COLPTN_CP1PLC0_SHIFT | +	ISPCCDC_COLPTN_R_Ye  << ISPCCDC_COLPTN_CP1PLC1_SHIFT | +	ISPCCDC_COLPTN_Gr_Cy << ISPCCDC_COLPTN_CP1PLC2_SHIFT | +	ISPCCDC_COLPTN_R_Ye  << ISPCCDC_COLPTN_CP1PLC3_SHIFT | +	ISPCCDC_COLPTN_B_Mg  << ISPCCDC_COLPTN_CP2PLC0_SHIFT | +	ISPCCDC_COLPTN_Gb_G  << ISPCCDC_COLPTN_CP2PLC1_SHIFT | +	ISPCCDC_COLPTN_B_Mg  << ISPCCDC_COLPTN_CP2PLC2_SHIFT | +	ISPCCDC_COLPTN_Gb_G  << ISPCCDC_COLPTN_CP2PLC3_SHIFT | +	ISPCCDC_COLPTN_Gr_Cy << ISPCCDC_COLPTN_CP3PLC0_SHIFT | +	ISPCCDC_COLPTN_R_Ye  << ISPCCDC_COLPTN_CP3PLC1_SHIFT | +	ISPCCDC_COLPTN_Gr_Cy << ISPCCDC_COLPTN_CP3PLC2_SHIFT | +	ISPCCDC_COLPTN_R_Ye  << ISPCCDC_COLPTN_CP3PLC3_SHIFT; + +static const u32 ccdc_sgbrg_pattern = +	ISPCCDC_COLPTN_Gb_G  << ISPCCDC_COLPTN_CP0PLC0_SHIFT | +	ISPCCDC_COLPTN_B_Mg  << ISPCCDC_COLPTN_CP0PLC1_SHIFT | +	ISPCCDC_COLPTN_Gb_G  << ISPCCDC_COLPTN_CP0PLC2_SHIFT | +	ISPCCDC_COLPTN_B_Mg  << ISPCCDC_COLPTN_CP0PLC3_SHIFT | +	ISPCCDC_COLPTN_R_Ye  << ISPCCDC_COLPTN_CP1PLC0_SHIFT | +	ISPCCDC_COLPTN_Gr_Cy << ISPCCDC_COLPTN_CP1PLC1_SHIFT | +	ISPCCDC_COLPTN_R_Ye  << ISPCCDC_COLPTN_CP1PLC2_SHIFT | +	ISPCCDC_COLPTN_Gr_Cy << ISPCCDC_COLPTN_CP1PLC3_SHIFT | +	ISPCCDC_COLPTN_Gb_G  << ISPCCDC_COLPTN_CP2PLC0_SHIFT | +	ISPCCDC_COLPTN_B_Mg  << ISPCCDC_COLPTN_CP2PLC1_SHIFT | +	ISPCCDC_COLPTN_Gb_G  << ISPCCDC_COLPTN_CP2PLC2_SHIFT | +	ISPCCDC_COLPTN_B_Mg  << ISPCCDC_COLPTN_CP2PLC3_SHIFT | +	ISPCCDC_COLPTN_R_Ye  << ISPCCDC_COLPTN_CP3PLC0_SHIFT | +	ISPCCDC_COLPTN_Gr_Cy << ISPCCDC_COLPTN_CP3PLC1_SHIFT | +	ISPCCDC_COLPTN_R_Ye  << ISPCCDC_COLPTN_CP3PLC2_SHIFT | +	ISPCCDC_COLPTN_Gr_Cy << ISPCCDC_COLPTN_CP3PLC3_SHIFT; + +static void ccdc_configure(struct isp_ccdc_device *ccdc) +{ +	struct isp_device *isp = to_isp_device(ccdc); +	struct isp_parallel_platform_data *pdata = NULL; +	struct v4l2_subdev *sensor; +	struct v4l2_mbus_framefmt *format; +	const struct v4l2_rect *crop; +	const struct isp_format_info *fmt_info; +	struct v4l2_subdev_format fmt_src; +	unsigned int depth_out; +	unsigned int depth_in = 0; +	struct media_pad *pad; +	unsigned long flags; +	unsigned int bridge; +	unsigned int shift; +	u32 syn_mode; +	u32 ccdc_pattern; + +	pad = media_entity_remote_pad(&ccdc->pads[CCDC_PAD_SINK]); +	sensor = media_entity_to_v4l2_subdev(pad->entity); +	if (ccdc->input == CCDC_INPUT_PARALLEL) +		pdata = &((struct isp_v4l2_subdevs_group *)sensor->host_priv) +			->bus.parallel; + +	/* Compute the lane shifter shift value and enable the bridge when the +	 * input format is YUV. +	 */ +	fmt_src.pad = pad->index; +	fmt_src.which = V4L2_SUBDEV_FORMAT_ACTIVE; +	if (!v4l2_subdev_call(sensor, pad, get_fmt, NULL, &fmt_src)) { +		fmt_info = omap3isp_video_format_info(fmt_src.format.code); +		depth_in = fmt_info->width; +	} + +	fmt_info = omap3isp_video_format_info +		(isp->isp_ccdc.formats[CCDC_PAD_SINK].code); +	depth_out = fmt_info->width; +	shift = depth_in - depth_out; + +	if (fmt_info->code == V4L2_MBUS_FMT_YUYV8_2X8) +		bridge = ISPCTRL_PAR_BRIDGE_LENDIAN; +	else if (fmt_info->code == V4L2_MBUS_FMT_UYVY8_2X8) +		bridge = ISPCTRL_PAR_BRIDGE_BENDIAN; +	else +		bridge = ISPCTRL_PAR_BRIDGE_DISABLE; + +	omap3isp_configure_bridge(isp, ccdc->input, pdata, shift, bridge); + +	ccdc_config_sync_if(ccdc, pdata, depth_out); + +	syn_mode = isp_reg_readl(isp, OMAP3_ISP_IOMEM_CCDC, ISPCCDC_SYN_MODE); + +	/* Use the raw, unprocessed data when writing to memory. The H3A and +	 * histogram modules are still fed with lens shading corrected data. +	 */ +	syn_mode &= ~ISPCCDC_SYN_MODE_VP2SDR; + +	if (ccdc->output & CCDC_OUTPUT_MEMORY) +		syn_mode |= ISPCCDC_SYN_MODE_WEN; +	else +		syn_mode &= ~ISPCCDC_SYN_MODE_WEN; + +	if (ccdc->output & CCDC_OUTPUT_RESIZER) +		syn_mode |= ISPCCDC_SYN_MODE_SDR2RSZ; +	else +		syn_mode &= ~ISPCCDC_SYN_MODE_SDR2RSZ; + +	/* CCDC_PAD_SINK */ +	format = &ccdc->formats[CCDC_PAD_SINK]; + +	/* Mosaic filter */ +	switch (format->code) { +	case V4L2_MBUS_FMT_SRGGB10_1X10: +	case V4L2_MBUS_FMT_SRGGB12_1X12: +		ccdc_pattern = ccdc_srggb_pattern; +		break; +	case V4L2_MBUS_FMT_SBGGR10_1X10: +	case V4L2_MBUS_FMT_SBGGR12_1X12: +		ccdc_pattern = ccdc_sbggr_pattern; +		break; +	case V4L2_MBUS_FMT_SGBRG10_1X10: +	case V4L2_MBUS_FMT_SGBRG12_1X12: +		ccdc_pattern = ccdc_sgbrg_pattern; +		break; +	default: +		/* Use GRBG */ +		ccdc_pattern = ccdc_sgrbg_pattern; +		break; +	} +	ccdc_config_imgattr(ccdc, ccdc_pattern); + +	/* Generate VD0 on the last line of the image and VD1 on the +	 * 2/3 height line. +	 */ +	isp_reg_writel(isp, ((format->height - 2) << ISPCCDC_VDINT_0_SHIFT) | +		       ((format->height * 2 / 3) << ISPCCDC_VDINT_1_SHIFT), +		       OMAP3_ISP_IOMEM_CCDC, ISPCCDC_VDINT); + +	/* CCDC_PAD_SOURCE_OF */ +	format = &ccdc->formats[CCDC_PAD_SOURCE_OF]; +	crop = &ccdc->crop; + +	isp_reg_writel(isp, (crop->left << ISPCCDC_HORZ_INFO_SPH_SHIFT) | +		       ((crop->width - 1) << ISPCCDC_HORZ_INFO_NPH_SHIFT), +		       OMAP3_ISP_IOMEM_CCDC, ISPCCDC_HORZ_INFO); +	isp_reg_writel(isp, crop->top << ISPCCDC_VERT_START_SLV0_SHIFT, +		       OMAP3_ISP_IOMEM_CCDC, ISPCCDC_VERT_START); +	isp_reg_writel(isp, (crop->height - 1) +			<< ISPCCDC_VERT_LINES_NLV_SHIFT, +		       OMAP3_ISP_IOMEM_CCDC, ISPCCDC_VERT_LINES); + +	ccdc_config_outlineoffset(ccdc, ccdc->video_out.bpl_value, 0, 0); + +	/* The CCDC outputs data in UYVY order by default. Swap bytes to get +	 * YUYV. +	 */ +	if (format->code == V4L2_MBUS_FMT_YUYV8_1X16) +		isp_reg_set(isp, OMAP3_ISP_IOMEM_CCDC, ISPCCDC_CFG, +			    ISPCCDC_CFG_BSWD); +	else +		isp_reg_clr(isp, OMAP3_ISP_IOMEM_CCDC, ISPCCDC_CFG, +			    ISPCCDC_CFG_BSWD); + +	/* Use PACK8 mode for 1byte per pixel formats. */ +	if (omap3isp_video_format_info(format->code)->width <= 8) +		syn_mode |= ISPCCDC_SYN_MODE_PACK8; +	else +		syn_mode &= ~ISPCCDC_SYN_MODE_PACK8; + +	isp_reg_writel(isp, syn_mode, OMAP3_ISP_IOMEM_CCDC, ISPCCDC_SYN_MODE); + +	/* CCDC_PAD_SOURCE_VP */ +	format = &ccdc->formats[CCDC_PAD_SOURCE_VP]; + +	isp_reg_writel(isp, (0 << ISPCCDC_FMT_HORZ_FMTSPH_SHIFT) | +		       (format->width << ISPCCDC_FMT_HORZ_FMTLNH_SHIFT), +		       OMAP3_ISP_IOMEM_CCDC, ISPCCDC_FMT_HORZ); +	isp_reg_writel(isp, (0 << ISPCCDC_FMT_VERT_FMTSLV_SHIFT) | +		       ((format->height + 1) << ISPCCDC_FMT_VERT_FMTLNV_SHIFT), +		       OMAP3_ISP_IOMEM_CCDC, ISPCCDC_FMT_VERT); + +	isp_reg_writel(isp, (format->width << ISPCCDC_VP_OUT_HORZ_NUM_SHIFT) | +		       (format->height << ISPCCDC_VP_OUT_VERT_NUM_SHIFT), +		       OMAP3_ISP_IOMEM_CCDC, ISPCCDC_VP_OUT); + +	/* Lens shading correction. */ +	spin_lock_irqsave(&ccdc->lsc.req_lock, flags); +	if (ccdc->lsc.request == NULL) +		goto unlock; + +	WARN_ON(ccdc->lsc.active); + +	/* Get last good LSC configuration. If it is not supported for +	 * the current active resolution discard it. +	 */ +	if (ccdc->lsc.active == NULL && +	    __ccdc_lsc_configure(ccdc, ccdc->lsc.request) == 0) { +		ccdc->lsc.active = ccdc->lsc.request; +	} else { +		list_add_tail(&ccdc->lsc.request->list, &ccdc->lsc.free_queue); +		schedule_work(&ccdc->lsc.table_work); +	} + +	ccdc->lsc.request = NULL; + +unlock: +	spin_unlock_irqrestore(&ccdc->lsc.req_lock, flags); + +	ccdc_apply_controls(ccdc); +} + +static void __ccdc_enable(struct isp_ccdc_device *ccdc, int enable) +{ +	struct isp_device *isp = to_isp_device(ccdc); + +	isp_reg_clr_set(isp, OMAP3_ISP_IOMEM_CCDC, ISPCCDC_PCR, +			ISPCCDC_PCR_EN, enable ? ISPCCDC_PCR_EN : 0); +} + +static int ccdc_disable(struct isp_ccdc_device *ccdc) +{ +	unsigned long flags; +	int ret = 0; + +	spin_lock_irqsave(&ccdc->lock, flags); +	if (ccdc->state == ISP_PIPELINE_STREAM_CONTINUOUS) +		ccdc->stopping = CCDC_STOP_REQUEST; +	spin_unlock_irqrestore(&ccdc->lock, flags); + +	ret = wait_event_timeout(ccdc->wait, +				 ccdc->stopping == CCDC_STOP_FINISHED, +				 msecs_to_jiffies(2000)); +	if (ret == 0) { +		ret = -ETIMEDOUT; +		dev_warn(to_device(ccdc), "CCDC stop timeout!\n"); +	} + +	omap3isp_sbl_disable(to_isp_device(ccdc), OMAP3_ISP_SBL_CCDC_LSC_READ); + +	mutex_lock(&ccdc->ioctl_lock); +	ccdc_lsc_free_request(ccdc, ccdc->lsc.request); +	ccdc->lsc.request = ccdc->lsc.active; +	ccdc->lsc.active = NULL; +	cancel_work_sync(&ccdc->lsc.table_work); +	ccdc_lsc_free_queue(ccdc, &ccdc->lsc.free_queue); +	mutex_unlock(&ccdc->ioctl_lock); + +	ccdc->stopping = CCDC_STOP_NOT_REQUESTED; + +	return ret > 0 ? 0 : ret; +} + +static void ccdc_enable(struct isp_ccdc_device *ccdc) +{ +	if (ccdc_lsc_is_configured(ccdc)) +		__ccdc_lsc_enable(ccdc, 1); +	__ccdc_enable(ccdc, 1); +} + +/* ----------------------------------------------------------------------------- + * Interrupt handling + */ + +/* + * ccdc_sbl_busy - Poll idle state of CCDC and related SBL memory write bits + * @ccdc: Pointer to ISP CCDC device. + * + * Returns zero if the CCDC is idle and the image has been written to + * memory, too. + */ +static int ccdc_sbl_busy(struct isp_ccdc_device *ccdc) +{ +	struct isp_device *isp = to_isp_device(ccdc); + +	return omap3isp_ccdc_busy(ccdc) +		| (isp_reg_readl(isp, OMAP3_ISP_IOMEM_SBL, ISPSBL_CCDC_WR_0) & +		   ISPSBL_CCDC_WR_0_DATA_READY) +		| (isp_reg_readl(isp, OMAP3_ISP_IOMEM_SBL, ISPSBL_CCDC_WR_1) & +		   ISPSBL_CCDC_WR_0_DATA_READY) +		| (isp_reg_readl(isp, OMAP3_ISP_IOMEM_SBL, ISPSBL_CCDC_WR_2) & +		   ISPSBL_CCDC_WR_0_DATA_READY) +		| (isp_reg_readl(isp, OMAP3_ISP_IOMEM_SBL, ISPSBL_CCDC_WR_3) & +		   ISPSBL_CCDC_WR_0_DATA_READY); +} + +/* + * ccdc_sbl_wait_idle - Wait until the CCDC and related SBL are idle + * @ccdc: Pointer to ISP CCDC device. + * @max_wait: Max retry count in us for wait for idle/busy transition. + */ +static int ccdc_sbl_wait_idle(struct isp_ccdc_device *ccdc, +			      unsigned int max_wait) +{ +	unsigned int wait = 0; + +	if (max_wait == 0) +		max_wait = 10000; /* 10 ms */ + +	for (wait = 0; wait <= max_wait; wait++) { +		if (!ccdc_sbl_busy(ccdc)) +			return 0; + +		rmb(); +		udelay(1); +	} + +	return -EBUSY; +} + +/* __ccdc_handle_stopping - Handle CCDC and/or LSC stopping sequence + * @ccdc: Pointer to ISP CCDC device. + * @event: Pointing which event trigger handler + * + * Return 1 when the event and stopping request combination is satisfied, + * zero otherwise. + */ +static int __ccdc_handle_stopping(struct isp_ccdc_device *ccdc, u32 event) +{ +	int rval = 0; + +	switch ((ccdc->stopping & 3) | event) { +	case CCDC_STOP_REQUEST | CCDC_EVENT_VD1: +		if (ccdc->lsc.state != LSC_STATE_STOPPED) +			__ccdc_lsc_enable(ccdc, 0); +		__ccdc_enable(ccdc, 0); +		ccdc->stopping = CCDC_STOP_EXECUTED; +		return 1; + +	case CCDC_STOP_EXECUTED | CCDC_EVENT_VD0: +		ccdc->stopping |= CCDC_STOP_CCDC_FINISHED; +		if (ccdc->lsc.state == LSC_STATE_STOPPED) +			ccdc->stopping |= CCDC_STOP_LSC_FINISHED; +		rval = 1; +		break; + +	case CCDC_STOP_EXECUTED | CCDC_EVENT_LSC_DONE: +		ccdc->stopping |= CCDC_STOP_LSC_FINISHED; +		rval = 1; +		break; + +	case CCDC_STOP_EXECUTED | CCDC_EVENT_VD1: +		return 1; +	} + +	if (ccdc->stopping == CCDC_STOP_FINISHED) { +		wake_up(&ccdc->wait); +		rval = 1; +	} + +	return rval; +} + +static void ccdc_hs_vs_isr(struct isp_ccdc_device *ccdc) +{ +	struct isp_pipeline *pipe = to_isp_pipeline(&ccdc->subdev.entity); +	struct video_device *vdev = ccdc->subdev.devnode; +	struct v4l2_event event; + +	/* Frame number propagation */ +	atomic_inc(&pipe->frame_number); + +	memset(&event, 0, sizeof(event)); +	event.type = V4L2_EVENT_FRAME_SYNC; +	event.u.frame_sync.frame_sequence = atomic_read(&pipe->frame_number); + +	v4l2_event_queue(vdev, &event); +} + +/* + * ccdc_lsc_isr - Handle LSC events + * @ccdc: Pointer to ISP CCDC device. + * @events: LSC events + */ +static void ccdc_lsc_isr(struct isp_ccdc_device *ccdc, u32 events) +{ +	unsigned long flags; + +	if (events & IRQ0STATUS_CCDC_LSC_PREF_ERR_IRQ) { +		struct isp_pipeline *pipe = +			to_isp_pipeline(&ccdc->subdev.entity); + +		ccdc_lsc_error_handler(ccdc); +		pipe->error = true; +		dev_dbg(to_device(ccdc), "lsc prefetch error\n"); +	} + +	if (!(events & IRQ0STATUS_CCDC_LSC_DONE_IRQ)) +		return; + +	/* LSC_DONE interrupt occur, there are two cases +	 * 1. stopping for reconfiguration +	 * 2. stopping because of STREAM OFF command +	 */ +	spin_lock_irqsave(&ccdc->lsc.req_lock, flags); + +	if (ccdc->lsc.state == LSC_STATE_STOPPING) +		ccdc->lsc.state = LSC_STATE_STOPPED; + +	if (__ccdc_handle_stopping(ccdc, CCDC_EVENT_LSC_DONE)) +		goto done; + +	if (ccdc->lsc.state != LSC_STATE_RECONFIG) +		goto done; + +	/* LSC is in STOPPING state, change to the new state */ +	ccdc->lsc.state = LSC_STATE_STOPPED; + +	/* This is an exception. Start of frame and LSC_DONE interrupt +	 * have been received on the same time. Skip this event and wait +	 * for better times. +	 */ +	if (events & IRQ0STATUS_HS_VS_IRQ) +		goto done; + +	/* The LSC engine is stopped at this point. Enable it if there's a +	 * pending request. +	 */ +	if (ccdc->lsc.request == NULL) +		goto done; + +	ccdc_lsc_enable(ccdc); + +done: +	spin_unlock_irqrestore(&ccdc->lsc.req_lock, flags); +} + +static int ccdc_isr_buffer(struct isp_ccdc_device *ccdc) +{ +	struct isp_pipeline *pipe = to_isp_pipeline(&ccdc->subdev.entity); +	struct isp_device *isp = to_isp_device(ccdc); +	struct isp_buffer *buffer; +	int restart = 0; + +	/* The CCDC generates VD0 interrupts even when disabled (the datasheet +	 * doesn't explicitly state if that's supposed to happen or not, so it +	 * can be considered as a hardware bug or as a feature, but we have to +	 * deal with it anyway). Disabling the CCDC when no buffer is available +	 * would thus not be enough, we need to handle the situation explicitly. +	 */ +	if (list_empty(&ccdc->video_out.dmaqueue)) +		goto done; + +	/* We're in continuous mode, and memory writes were disabled due to a +	 * buffer underrun. Reenable them now that we have a buffer. The buffer +	 * address has been set in ccdc_video_queue. +	 */ +	if (ccdc->state == ISP_PIPELINE_STREAM_CONTINUOUS && ccdc->underrun) { +		restart = 1; +		ccdc->underrun = 0; +		goto done; +	} + +	if (ccdc_sbl_wait_idle(ccdc, 1000)) { +		dev_info(isp->dev, "CCDC won't become idle!\n"); +		isp->crashed |= 1U << ccdc->subdev.entity.id; +		omap3isp_pipeline_cancel_stream(pipe); +		goto done; +	} + +	buffer = omap3isp_video_buffer_next(&ccdc->video_out); +	if (buffer != NULL) { +		ccdc_set_outaddr(ccdc, buffer->dma); +		restart = 1; +	} + +	pipe->state |= ISP_PIPELINE_IDLE_OUTPUT; + +	if (ccdc->state == ISP_PIPELINE_STREAM_SINGLESHOT && +	    isp_pipeline_ready(pipe)) +		omap3isp_pipeline_set_stream(pipe, +					ISP_PIPELINE_STREAM_SINGLESHOT); + +done: +	return restart; +} + +/* + * ccdc_vd0_isr - Handle VD0 event + * @ccdc: Pointer to ISP CCDC device. + * + * Executes LSC deferred enablement before next frame starts. + */ +static void ccdc_vd0_isr(struct isp_ccdc_device *ccdc) +{ +	unsigned long flags; +	int restart = 0; + +	if (ccdc->output & CCDC_OUTPUT_MEMORY) +		restart = ccdc_isr_buffer(ccdc); + +	spin_lock_irqsave(&ccdc->lock, flags); +	if (__ccdc_handle_stopping(ccdc, CCDC_EVENT_VD0)) { +		spin_unlock_irqrestore(&ccdc->lock, flags); +		return; +	} + +	if (!ccdc->shadow_update) +		ccdc_apply_controls(ccdc); +	spin_unlock_irqrestore(&ccdc->lock, flags); + +	if (restart) +		ccdc_enable(ccdc); +} + +/* + * ccdc_vd1_isr - Handle VD1 event + * @ccdc: Pointer to ISP CCDC device. + */ +static void ccdc_vd1_isr(struct isp_ccdc_device *ccdc) +{ +	unsigned long flags; + +	spin_lock_irqsave(&ccdc->lsc.req_lock, flags); + +	/* +	 * Depending on the CCDC pipeline state, CCDC stopping should be +	 * handled differently. In SINGLESHOT we emulate an internal CCDC +	 * stopping because the CCDC hw works only in continuous mode. +	 * When CONTINUOUS pipeline state is used and the CCDC writes it's +	 * data to memory the CCDC and LSC are stopped immediately but +	 * without change the CCDC stopping state machine. The CCDC +	 * stopping state machine should be used only when user request +	 * for stopping is received (SINGLESHOT is an exeption). +	 */ +	switch (ccdc->state) { +	case ISP_PIPELINE_STREAM_SINGLESHOT: +		ccdc->stopping = CCDC_STOP_REQUEST; +		break; + +	case ISP_PIPELINE_STREAM_CONTINUOUS: +		if (ccdc->output & CCDC_OUTPUT_MEMORY) { +			if (ccdc->lsc.state != LSC_STATE_STOPPED) +				__ccdc_lsc_enable(ccdc, 0); +			__ccdc_enable(ccdc, 0); +		} +		break; + +	case ISP_PIPELINE_STREAM_STOPPED: +		break; +	} + +	if (__ccdc_handle_stopping(ccdc, CCDC_EVENT_VD1)) +		goto done; + +	if (ccdc->lsc.request == NULL) +		goto done; + +	/* +	 * LSC need to be reconfigured. Stop it here and on next LSC_DONE IRQ +	 * do the appropriate changes in registers +	 */ +	if (ccdc->lsc.state == LSC_STATE_RUNNING) { +		__ccdc_lsc_enable(ccdc, 0); +		ccdc->lsc.state = LSC_STATE_RECONFIG; +		goto done; +	} + +	/* LSC has been in STOPPED state, enable it */ +	if (ccdc->lsc.state == LSC_STATE_STOPPED) +		ccdc_lsc_enable(ccdc); + +done: +	spin_unlock_irqrestore(&ccdc->lsc.req_lock, flags); +} + +/* + * omap3isp_ccdc_isr - Configure CCDC during interframe time. + * @ccdc: Pointer to ISP CCDC device. + * @events: CCDC events + */ +int omap3isp_ccdc_isr(struct isp_ccdc_device *ccdc, u32 events) +{ +	if (ccdc->state == ISP_PIPELINE_STREAM_STOPPED) +		return 0; + +	if (events & IRQ0STATUS_CCDC_VD1_IRQ) +		ccdc_vd1_isr(ccdc); + +	ccdc_lsc_isr(ccdc, events); + +	if (events & IRQ0STATUS_CCDC_VD0_IRQ) +		ccdc_vd0_isr(ccdc); + +	if (events & IRQ0STATUS_HS_VS_IRQ) +		ccdc_hs_vs_isr(ccdc); + +	return 0; +} + +/* ----------------------------------------------------------------------------- + * ISP video operations + */ + +static int ccdc_video_queue(struct isp_video *video, struct isp_buffer *buffer) +{ +	struct isp_ccdc_device *ccdc = &video->isp->isp_ccdc; + +	if (!(ccdc->output & CCDC_OUTPUT_MEMORY)) +		return -ENODEV; + +	ccdc_set_outaddr(ccdc, buffer->dma); + +	/* We now have a buffer queued on the output, restart the pipeline +	 * on the next CCDC interrupt if running in continuous mode (or when +	 * starting the stream). +	 */ +	ccdc->underrun = 1; + +	return 0; +} + +static const struct isp_video_operations ccdc_video_ops = { +	.queue = ccdc_video_queue, +}; + +/* ----------------------------------------------------------------------------- + * V4L2 subdev operations + */ + +/* + * ccdc_ioctl - CCDC module private ioctl's + * @sd: ISP CCDC V4L2 subdevice + * @cmd: ioctl command + * @arg: ioctl argument + * + * Return 0 on success or a negative error code otherwise. + */ +static long ccdc_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg) +{ +	struct isp_ccdc_device *ccdc = v4l2_get_subdevdata(sd); +	int ret; + +	switch (cmd) { +	case VIDIOC_OMAP3ISP_CCDC_CFG: +		mutex_lock(&ccdc->ioctl_lock); +		ret = ccdc_config(ccdc, arg); +		mutex_unlock(&ccdc->ioctl_lock); +		break; + +	default: +		return -ENOIOCTLCMD; +	} + +	return ret; +} + +static int ccdc_subscribe_event(struct v4l2_subdev *sd, struct v4l2_fh *fh, +				struct v4l2_event_subscription *sub) +{ +	if (sub->type != V4L2_EVENT_FRAME_SYNC) +		return -EINVAL; + +	/* line number is zero at frame start */ +	if (sub->id != 0) +		return -EINVAL; + +	return v4l2_event_subscribe(fh, sub, OMAP3ISP_CCDC_NEVENTS, NULL); +} + +static int ccdc_unsubscribe_event(struct v4l2_subdev *sd, struct v4l2_fh *fh, +				  struct v4l2_event_subscription *sub) +{ +	return v4l2_event_unsubscribe(fh, sub); +} + +/* + * ccdc_set_stream - Enable/Disable streaming on the CCDC module + * @sd: ISP CCDC V4L2 subdevice + * @enable: Enable/disable stream + * + * When writing to memory, the CCDC hardware can't be enabled without a memory + * buffer to write to. As the s_stream operation is called in response to a + * STREAMON call without any buffer queued yet, just update the enabled field + * and return immediately. The CCDC will be enabled in ccdc_isr_buffer(). + * + * When not writing to memory enable the CCDC immediately. + */ +static int ccdc_set_stream(struct v4l2_subdev *sd, int enable) +{ +	struct isp_ccdc_device *ccdc = v4l2_get_subdevdata(sd); +	struct isp_device *isp = to_isp_device(ccdc); +	int ret = 0; + +	if (ccdc->state == ISP_PIPELINE_STREAM_STOPPED) { +		if (enable == ISP_PIPELINE_STREAM_STOPPED) +			return 0; + +		omap3isp_subclk_enable(isp, OMAP3_ISP_SUBCLK_CCDC); +		isp_reg_set(isp, OMAP3_ISP_IOMEM_CCDC, ISPCCDC_CFG, +			    ISPCCDC_CFG_VDLC); + +		ccdc_configure(ccdc); + +		/* TODO: Don't configure the video port if all of its output +		 * links are inactive. +		 */ +		ccdc_config_vp(ccdc); +		ccdc_enable_vp(ccdc, 1); +		ccdc_print_status(ccdc); +	} + +	switch (enable) { +	case ISP_PIPELINE_STREAM_CONTINUOUS: +		if (ccdc->output & CCDC_OUTPUT_MEMORY) +			omap3isp_sbl_enable(isp, OMAP3_ISP_SBL_CCDC_WRITE); + +		if (ccdc->underrun || !(ccdc->output & CCDC_OUTPUT_MEMORY)) +			ccdc_enable(ccdc); + +		ccdc->underrun = 0; +		break; + +	case ISP_PIPELINE_STREAM_SINGLESHOT: +		if (ccdc->output & CCDC_OUTPUT_MEMORY && +		    ccdc->state != ISP_PIPELINE_STREAM_SINGLESHOT) +			omap3isp_sbl_enable(isp, OMAP3_ISP_SBL_CCDC_WRITE); + +		ccdc_enable(ccdc); +		break; + +	case ISP_PIPELINE_STREAM_STOPPED: +		ret = ccdc_disable(ccdc); +		if (ccdc->output & CCDC_OUTPUT_MEMORY) +			omap3isp_sbl_disable(isp, OMAP3_ISP_SBL_CCDC_WRITE); +		omap3isp_subclk_disable(isp, OMAP3_ISP_SUBCLK_CCDC); +		ccdc->underrun = 0; +		break; +	} + +	ccdc->state = enable; +	return ret; +} + +static struct v4l2_mbus_framefmt * +__ccdc_get_format(struct isp_ccdc_device *ccdc, 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 &ccdc->formats[pad]; +} + +static struct v4l2_rect * +__ccdc_get_crop(struct isp_ccdc_device *ccdc, struct v4l2_subdev_fh *fh, +		enum v4l2_subdev_format_whence which) +{ +	if (which == V4L2_SUBDEV_FORMAT_TRY) +		return v4l2_subdev_get_try_crop(fh, CCDC_PAD_SOURCE_OF); +	else +		return &ccdc->crop; +} + +/* + * ccdc_try_format - Try video format on a pad + * @ccdc: ISP CCDC device + * @fh : V4L2 subdev file handle + * @pad: Pad number + * @fmt: Format + */ +static void +ccdc_try_format(struct isp_ccdc_device *ccdc, struct v4l2_subdev_fh *fh, +		unsigned int pad, struct v4l2_mbus_framefmt *fmt, +		enum v4l2_subdev_format_whence which) +{ +	const struct isp_format_info *info; +	enum v4l2_mbus_pixelcode pixelcode; +	unsigned int width = fmt->width; +	unsigned int height = fmt->height; +	struct v4l2_rect *crop; +	unsigned int i; + +	switch (pad) { +	case CCDC_PAD_SINK: +		for (i = 0; i < ARRAY_SIZE(ccdc_fmts); i++) { +			if (fmt->code == ccdc_fmts[i]) +				break; +		} + +		/* If not found, use SGRBG10 as default */ +		if (i >= ARRAY_SIZE(ccdc_fmts)) +			fmt->code = V4L2_MBUS_FMT_SGRBG10_1X10; + +		/* Clamp the input size. */ +		fmt->width = clamp_t(u32, width, 32, 4096); +		fmt->height = clamp_t(u32, height, 32, 4096); +		break; + +	case CCDC_PAD_SOURCE_OF: +		pixelcode = fmt->code; +		*fmt = *__ccdc_get_format(ccdc, fh, CCDC_PAD_SINK, which); + +		/* YUV formats are converted from 2X8 to 1X16 by the bridge and +		 * can be byte-swapped. +		 */ +		if (fmt->code == V4L2_MBUS_FMT_YUYV8_2X8 || +		    fmt->code == V4L2_MBUS_FMT_UYVY8_2X8) { +			/* Use the user requested format if YUV. */ +			if (pixelcode == V4L2_MBUS_FMT_YUYV8_2X8 || +			    pixelcode == V4L2_MBUS_FMT_UYVY8_2X8 || +			    pixelcode == V4L2_MBUS_FMT_YUYV8_1X16 || +			    pixelcode == V4L2_MBUS_FMT_UYVY8_1X16) +				fmt->code = pixelcode; + +			if (fmt->code == V4L2_MBUS_FMT_YUYV8_2X8) +				fmt->code = V4L2_MBUS_FMT_YUYV8_1X16; +			else if (fmt->code == V4L2_MBUS_FMT_UYVY8_2X8) +				fmt->code = V4L2_MBUS_FMT_UYVY8_1X16; +		} + +		/* Hardcode the output size to the crop rectangle size. */ +		crop = __ccdc_get_crop(ccdc, fh, which); +		fmt->width = crop->width; +		fmt->height = crop->height; +		break; + +	case CCDC_PAD_SOURCE_VP: +		*fmt = *__ccdc_get_format(ccdc, fh, CCDC_PAD_SINK, which); + +		/* The video port interface truncates the data to 10 bits. */ +		info = omap3isp_video_format_info(fmt->code); +		fmt->code = info->truncated; + +		/* YUV formats are not supported by the video port. */ +		if (fmt->code == V4L2_MBUS_FMT_YUYV8_2X8 || +		    fmt->code == V4L2_MBUS_FMT_UYVY8_2X8) +			fmt->code = 0; + +		/* The number of lines that can be clocked out from the video +		 * port output must be at least one line less than the number +		 * of input lines. +		 */ +		fmt->width = clamp_t(u32, width, 32, fmt->width); +		fmt->height = clamp_t(u32, height, 32, fmt->height - 1); +		break; +	} + +	/* Data is written to memory unpacked, each 10-bit or 12-bit pixel is +	 * stored on 2 bytes. +	 */ +	fmt->colorspace = V4L2_COLORSPACE_SRGB; +	fmt->field = V4L2_FIELD_NONE; +} + +/* + * ccdc_try_crop - Validate a crop rectangle + * @ccdc: ISP CCDC device + * @sink: format on the sink pad + * @crop: crop rectangle to be validated + */ +static void ccdc_try_crop(struct isp_ccdc_device *ccdc, +			  const struct v4l2_mbus_framefmt *sink, +			  struct v4l2_rect *crop) +{ +	const struct isp_format_info *info; +	unsigned int max_width; + +	/* For Bayer formats, restrict left/top and width/height to even values +	 * to keep the Bayer pattern. +	 */ +	info = omap3isp_video_format_info(sink->code); +	if (info->flavor != V4L2_MBUS_FMT_Y8_1X8) { +		crop->left &= ~1; +		crop->top &= ~1; +	} + +	crop->left = clamp_t(u32, crop->left, 0, sink->width - CCDC_MIN_WIDTH); +	crop->top = clamp_t(u32, crop->top, 0, sink->height - CCDC_MIN_HEIGHT); + +	/* The data formatter truncates the number of horizontal output pixels +	 * to a multiple of 16. To avoid clipping data, allow callers to request +	 * an output size bigger than the input size up to the nearest multiple +	 * of 16. +	 */ +	max_width = (sink->width - crop->left + 15) & ~15; +	crop->width = clamp_t(u32, crop->width, CCDC_MIN_WIDTH, max_width) +		    & ~15; +	crop->height = clamp_t(u32, crop->height, CCDC_MIN_HEIGHT, +			       sink->height - crop->top); + +	/* Odd width/height values don't make sense for Bayer formats. */ +	if (info->flavor != V4L2_MBUS_FMT_Y8_1X8) { +		crop->width &= ~1; +		crop->height &= ~1; +	} +} + +/* + * ccdc_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 ccdc_enum_mbus_code(struct v4l2_subdev *sd, +			       struct v4l2_subdev_fh *fh, +			       struct v4l2_subdev_mbus_code_enum *code) +{ +	struct isp_ccdc_device *ccdc = v4l2_get_subdevdata(sd); +	struct v4l2_mbus_framefmt *format; + +	switch (code->pad) { +	case CCDC_PAD_SINK: +		if (code->index >= ARRAY_SIZE(ccdc_fmts)) +			return -EINVAL; + +		code->code = ccdc_fmts[code->index]; +		break; + +	case CCDC_PAD_SOURCE_OF: +		format = __ccdc_get_format(ccdc, fh, code->pad, +					   V4L2_SUBDEV_FORMAT_TRY); + +		if (format->code == V4L2_MBUS_FMT_YUYV8_2X8 || +		    format->code == V4L2_MBUS_FMT_UYVY8_2X8) { +			/* In YUV mode the CCDC can swap bytes. */ +			if (code->index == 0) +				code->code = V4L2_MBUS_FMT_YUYV8_1X16; +			else if (code->index == 1) +				code->code = V4L2_MBUS_FMT_UYVY8_1X16; +			else +				return -EINVAL; +		} else { +			/* In raw mode, no configurable format confversion is +			 * available. +			 */ +			if (code->index == 0) +				code->code = format->code; +			else +				return -EINVAL; +		} +		break; + +	case CCDC_PAD_SOURCE_VP: +		/* The CCDC supports no configurable format conversion +		 * compatible with the video port. Enumerate a single output +		 * format code. +		 */ +		if (code->index != 0) +			return -EINVAL; + +		format = __ccdc_get_format(ccdc, fh, code->pad, +					   V4L2_SUBDEV_FORMAT_TRY); + +		/* A pixel code equal to 0 means that the video port doesn't +		 * support the input format. Don't enumerate any pixel code. +		 */ +		if (format->code == 0) +			return -EINVAL; + +		code->code = format->code; +		break; + +	default: +		return -EINVAL; +	} + +	return 0; +} + +static int ccdc_enum_frame_size(struct v4l2_subdev *sd, +				struct v4l2_subdev_fh *fh, +				struct v4l2_subdev_frame_size_enum *fse) +{ +	struct isp_ccdc_device *ccdc = 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; +	ccdc_try_format(ccdc, 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; +	ccdc_try_format(ccdc, fh, fse->pad, &format, V4L2_SUBDEV_FORMAT_TRY); +	fse->max_width = format.width; +	fse->max_height = format.height; + +	return 0; +} + +/* + * ccdc_get_selection - Retrieve a selection rectangle on a pad + * @sd: ISP CCDC V4L2 subdevice + * @fh: V4L2 subdev file handle + * @sel: Selection rectangle + * + * The only supported rectangles are the crop rectangles on the output formatter + * source pad. + * + * Return 0 on success or a negative error code otherwise. + */ +static int ccdc_get_selection(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, +			      struct v4l2_subdev_selection *sel) +{ +	struct isp_ccdc_device *ccdc = v4l2_get_subdevdata(sd); +	struct v4l2_mbus_framefmt *format; + +	if (sel->pad != CCDC_PAD_SOURCE_OF) +		return -EINVAL; + +	switch (sel->target) { +	case V4L2_SEL_TGT_CROP_BOUNDS: +		sel->r.left = 0; +		sel->r.top = 0; +		sel->r.width = INT_MAX; +		sel->r.height = INT_MAX; + +		format = __ccdc_get_format(ccdc, fh, CCDC_PAD_SINK, sel->which); +		ccdc_try_crop(ccdc, format, &sel->r); +		break; + +	case V4L2_SEL_TGT_CROP: +		sel->r = *__ccdc_get_crop(ccdc, fh, sel->which); +		break; + +	default: +		return -EINVAL; +	} + +	return 0; +} + +/* + * ccdc_set_selection - Set a selection rectangle on a pad + * @sd: ISP CCDC V4L2 subdevice + * @fh: V4L2 subdev file handle + * @sel: Selection rectangle + * + * The only supported rectangle is the actual crop rectangle on the output + * formatter source pad. + * + * Return 0 on success or a negative error code otherwise. + */ +static int ccdc_set_selection(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, +			      struct v4l2_subdev_selection *sel) +{ +	struct isp_ccdc_device *ccdc = v4l2_get_subdevdata(sd); +	struct v4l2_mbus_framefmt *format; + +	if (sel->target != V4L2_SEL_TGT_CROP || +	    sel->pad != CCDC_PAD_SOURCE_OF) +		return -EINVAL; + +	/* The crop rectangle can't be changed while streaming. */ +	if (ccdc->state != ISP_PIPELINE_STREAM_STOPPED) +		return -EBUSY; + +	/* Modifying the crop rectangle always changes the format on the source +	 * pad. If the KEEP_CONFIG flag is set, just return the current crop +	 * rectangle. +	 */ +	if (sel->flags & V4L2_SEL_FLAG_KEEP_CONFIG) { +		sel->r = *__ccdc_get_crop(ccdc, fh, sel->which); +		return 0; +	} + +	format = __ccdc_get_format(ccdc, fh, CCDC_PAD_SINK, sel->which); +	ccdc_try_crop(ccdc, format, &sel->r); +	*__ccdc_get_crop(ccdc, fh, sel->which) = sel->r; + +	/* Update the source format. */ +	format = __ccdc_get_format(ccdc, fh, CCDC_PAD_SOURCE_OF, sel->which); +	ccdc_try_format(ccdc, fh, CCDC_PAD_SOURCE_OF, format, sel->which); + +	return 0; +} + +/* + * ccdc_get_format - Retrieve the video format on a pad + * @sd : ISP CCDC V4L2 subdevice + * @fh : V4L2 subdev file handle + * @fmt: Format + * + * Return 0 on success or -EINVAL if the pad is invalid or doesn't correspond + * to the format type. + */ +static int ccdc_get_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, +			   struct v4l2_subdev_format *fmt) +{ +	struct isp_ccdc_device *ccdc = v4l2_get_subdevdata(sd); +	struct v4l2_mbus_framefmt *format; + +	format = __ccdc_get_format(ccdc, fh, fmt->pad, fmt->which); +	if (format == NULL) +		return -EINVAL; + +	fmt->format = *format; +	return 0; +} + +/* + * ccdc_set_format - Set the video format on a pad + * @sd : ISP CCDC V4L2 subdevice + * @fh : V4L2 subdev file handle + * @fmt: Format + * + * Return 0 on success or -EINVAL if the pad is invalid or doesn't correspond + * to the format type. + */ +static int ccdc_set_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, +			   struct v4l2_subdev_format *fmt) +{ +	struct isp_ccdc_device *ccdc = v4l2_get_subdevdata(sd); +	struct v4l2_mbus_framefmt *format; +	struct v4l2_rect *crop; + +	format = __ccdc_get_format(ccdc, fh, fmt->pad, fmt->which); +	if (format == NULL) +		return -EINVAL; + +	ccdc_try_format(ccdc, fh, fmt->pad, &fmt->format, fmt->which); +	*format = fmt->format; + +	/* Propagate the format from sink to source */ +	if (fmt->pad == CCDC_PAD_SINK) { +		/* Reset the crop rectangle. */ +		crop = __ccdc_get_crop(ccdc, fh, fmt->which); +		crop->left = 0; +		crop->top = 0; +		crop->width = fmt->format.width; +		crop->height = fmt->format.height; + +		ccdc_try_crop(ccdc, &fmt->format, crop); + +		/* Update the source formats. */ +		format = __ccdc_get_format(ccdc, fh, CCDC_PAD_SOURCE_OF, +					   fmt->which); +		*format = fmt->format; +		ccdc_try_format(ccdc, fh, CCDC_PAD_SOURCE_OF, format, +				fmt->which); + +		format = __ccdc_get_format(ccdc, fh, CCDC_PAD_SOURCE_VP, +					   fmt->which); +		*format = fmt->format; +		ccdc_try_format(ccdc, fh, CCDC_PAD_SOURCE_VP, format, +				fmt->which); +	} + +	return 0; +} + +/* + * Decide whether desired output pixel code can be obtained with + * the lane shifter by shifting the input pixel code. + * @in: input pixelcode to shifter + * @out: output pixelcode from shifter + * @additional_shift: # of bits the sensor's LSB is offset from CAMEXT[0] + * + * return true if the combination is possible + * return false otherwise + */ +static bool ccdc_is_shiftable(enum v4l2_mbus_pixelcode in, +			      enum v4l2_mbus_pixelcode out, +			      unsigned int additional_shift) +{ +	const struct isp_format_info *in_info, *out_info; + +	if (in == out) +		return true; + +	in_info = omap3isp_video_format_info(in); +	out_info = omap3isp_video_format_info(out); + +	if ((in_info->flavor == 0) || (out_info->flavor == 0)) +		return false; + +	if (in_info->flavor != out_info->flavor) +		return false; + +	return in_info->width - out_info->width + additional_shift <= 6; +} + +static int ccdc_link_validate(struct v4l2_subdev *sd, +			      struct media_link *link, +			      struct v4l2_subdev_format *source_fmt, +			      struct v4l2_subdev_format *sink_fmt) +{ +	struct isp_ccdc_device *ccdc = v4l2_get_subdevdata(sd); +	unsigned long parallel_shift; + +	/* Check if the two ends match */ +	if (source_fmt->format.width != sink_fmt->format.width || +	    source_fmt->format.height != sink_fmt->format.height) +		return -EPIPE; + +	/* We've got a parallel sensor here. */ +	if (ccdc->input == CCDC_INPUT_PARALLEL) { +		struct isp_parallel_platform_data *pdata = +			&((struct isp_v4l2_subdevs_group *) +			  media_entity_to_v4l2_subdev(link->source->entity) +			  ->host_priv)->bus.parallel; +		parallel_shift = pdata->data_lane_shift * 2; +	} else { +		parallel_shift = 0; +	} + +	/* Lane shifter may be used to drop bits on CCDC sink pad */ +	if (!ccdc_is_shiftable(source_fmt->format.code, +			       sink_fmt->format.code, parallel_shift)) +		return -EPIPE; + +	return 0; +} + +/* + * ccdc_init_formats - Initialize formats on all pads + * @sd: ISP CCDC 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 ccdc_init_formats(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ +	struct v4l2_subdev_format format; + +	memset(&format, 0, sizeof(format)); +	format.pad = CCDC_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; +	ccdc_set_format(sd, fh, &format); + +	return 0; +} + +/* V4L2 subdev core operations */ +static const struct v4l2_subdev_core_ops ccdc_v4l2_core_ops = { +	.ioctl = ccdc_ioctl, +	.subscribe_event = ccdc_subscribe_event, +	.unsubscribe_event = ccdc_unsubscribe_event, +}; + +/* V4L2 subdev video operations */ +static const struct v4l2_subdev_video_ops ccdc_v4l2_video_ops = { +	.s_stream = ccdc_set_stream, +}; + +/* V4L2 subdev pad operations */ +static const struct v4l2_subdev_pad_ops ccdc_v4l2_pad_ops = { +	.enum_mbus_code = ccdc_enum_mbus_code, +	.enum_frame_size = ccdc_enum_frame_size, +	.get_fmt = ccdc_get_format, +	.set_fmt = ccdc_set_format, +	.get_selection = ccdc_get_selection, +	.set_selection = ccdc_set_selection, +	.link_validate = ccdc_link_validate, +}; + +/* V4L2 subdev operations */ +static const struct v4l2_subdev_ops ccdc_v4l2_ops = { +	.core = &ccdc_v4l2_core_ops, +	.video = &ccdc_v4l2_video_ops, +	.pad = &ccdc_v4l2_pad_ops, +}; + +/* V4L2 subdev internal operations */ +static const struct v4l2_subdev_internal_ops ccdc_v4l2_internal_ops = { +	.open = ccdc_init_formats, +}; + +/* ----------------------------------------------------------------------------- + * Media entity operations + */ + +/* + * ccdc_link_setup - Setup CCDC connections + * @entity: CCDC media entity + * @local: Pad at the local end of the link + * @remote: Pad at the remote end of the link + * @flags: Link flags + * + * return -EINVAL or zero on success + */ +static int ccdc_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_ccdc_device *ccdc = v4l2_get_subdevdata(sd); +	struct isp_device *isp = to_isp_device(ccdc); + +	switch (local->index | media_entity_type(remote->entity)) { +	case CCDC_PAD_SINK | MEDIA_ENT_T_V4L2_SUBDEV: +		/* Read from the sensor (parallel interface), CCP2, CSI2a or +		 * CSI2c. +		 */ +		if (!(flags & MEDIA_LNK_FL_ENABLED)) { +			ccdc->input = CCDC_INPUT_NONE; +			break; +		} + +		if (ccdc->input != CCDC_INPUT_NONE) +			return -EBUSY; + +		if (remote->entity == &isp->isp_ccp2.subdev.entity) +			ccdc->input = CCDC_INPUT_CCP2B; +		else if (remote->entity == &isp->isp_csi2a.subdev.entity) +			ccdc->input = CCDC_INPUT_CSI2A; +		else if (remote->entity == &isp->isp_csi2c.subdev.entity) +			ccdc->input = CCDC_INPUT_CSI2C; +		else +			ccdc->input = CCDC_INPUT_PARALLEL; + +		break; + +	/* +	 * The ISP core doesn't support pipelines with multiple video outputs. +	 * Revisit this when it will be implemented, and return -EBUSY for now. +	 */ + +	case CCDC_PAD_SOURCE_VP | MEDIA_ENT_T_V4L2_SUBDEV: +		/* Write to preview engine, histogram and H3A. When none of +		 * those links are active, the video port can be disabled. +		 */ +		if (flags & MEDIA_LNK_FL_ENABLED) { +			if (ccdc->output & ~CCDC_OUTPUT_PREVIEW) +				return -EBUSY; +			ccdc->output |= CCDC_OUTPUT_PREVIEW; +		} else { +			ccdc->output &= ~CCDC_OUTPUT_PREVIEW; +		} +		break; + +	case CCDC_PAD_SOURCE_OF | MEDIA_ENT_T_DEVNODE: +		/* Write to memory */ +		if (flags & MEDIA_LNK_FL_ENABLED) { +			if (ccdc->output & ~CCDC_OUTPUT_MEMORY) +				return -EBUSY; +			ccdc->output |= CCDC_OUTPUT_MEMORY; +		} else { +			ccdc->output &= ~CCDC_OUTPUT_MEMORY; +		} +		break; + +	case CCDC_PAD_SOURCE_OF | MEDIA_ENT_T_V4L2_SUBDEV: +		/* Write to resizer */ +		if (flags & MEDIA_LNK_FL_ENABLED) { +			if (ccdc->output & ~CCDC_OUTPUT_RESIZER) +				return -EBUSY; +			ccdc->output |= CCDC_OUTPUT_RESIZER; +		} else { +			ccdc->output &= ~CCDC_OUTPUT_RESIZER; +		} +		break; + +	default: +		return -EINVAL; +	} + +	return 0; +} + +/* media operations */ +static const struct media_entity_operations ccdc_media_ops = { +	.link_setup = ccdc_link_setup, +	.link_validate = v4l2_subdev_link_validate, +}; + +void omap3isp_ccdc_unregister_entities(struct isp_ccdc_device *ccdc) +{ +	v4l2_device_unregister_subdev(&ccdc->subdev); +	omap3isp_video_unregister(&ccdc->video_out); +} + +int omap3isp_ccdc_register_entities(struct isp_ccdc_device *ccdc, +	struct v4l2_device *vdev) +{ +	int ret; + +	/* Register the subdev and video node. */ +	ret = v4l2_device_register_subdev(vdev, &ccdc->subdev); +	if (ret < 0) +		goto error; + +	ret = omap3isp_video_register(&ccdc->video_out, vdev); +	if (ret < 0) +		goto error; + +	return 0; + +error: +	omap3isp_ccdc_unregister_entities(ccdc); +	return ret; +} + +/* ----------------------------------------------------------------------------- + * ISP CCDC initialisation and cleanup + */ + +/* + * ccdc_init_entities - Initialize V4L2 subdev and media entity + * @ccdc: ISP CCDC module + * + * Return 0 on success and a negative error code on failure. + */ +static int ccdc_init_entities(struct isp_ccdc_device *ccdc) +{ +	struct v4l2_subdev *sd = &ccdc->subdev; +	struct media_pad *pads = ccdc->pads; +	struct media_entity *me = &sd->entity; +	int ret; + +	ccdc->input = CCDC_INPUT_NONE; + +	v4l2_subdev_init(sd, &ccdc_v4l2_ops); +	sd->internal_ops = &ccdc_v4l2_internal_ops; +	strlcpy(sd->name, "OMAP3 ISP CCDC", sizeof(sd->name)); +	sd->grp_id = 1 << 16;	/* group ID for isp subdevs */ +	v4l2_set_subdevdata(sd, ccdc); +	sd->flags |= V4L2_SUBDEV_FL_HAS_EVENTS | V4L2_SUBDEV_FL_HAS_DEVNODE; + +	pads[CCDC_PAD_SINK].flags = MEDIA_PAD_FL_SINK +				    | MEDIA_PAD_FL_MUST_CONNECT; +	pads[CCDC_PAD_SOURCE_VP].flags = MEDIA_PAD_FL_SOURCE; +	pads[CCDC_PAD_SOURCE_OF].flags = MEDIA_PAD_FL_SOURCE; + +	me->ops = &ccdc_media_ops; +	ret = media_entity_init(me, CCDC_PADS_NUM, pads, 0); +	if (ret < 0) +		return ret; + +	ccdc_init_formats(sd, NULL); + +	ccdc->video_out.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; +	ccdc->video_out.ops = &ccdc_video_ops; +	ccdc->video_out.isp = to_isp_device(ccdc); +	ccdc->video_out.capture_mem = PAGE_ALIGN(4096 * 4096) * 3; +	ccdc->video_out.bpl_alignment = 32; + +	ret = omap3isp_video_init(&ccdc->video_out, "CCDC"); +	if (ret < 0) +		goto error_video; + +	/* Connect the CCDC subdev to the video node. */ +	ret = media_entity_create_link(&ccdc->subdev.entity, CCDC_PAD_SOURCE_OF, +			&ccdc->video_out.video.entity, 0, 0); +	if (ret < 0) +		goto error_link; + +	return 0; + +error_link: +	omap3isp_video_cleanup(&ccdc->video_out); +error_video: +	media_entity_cleanup(me); +	return ret; +} + +/* + * omap3isp_ccdc_init - CCDC module initialization. + * @isp: Device pointer specific to the OMAP3 ISP. + * + * TODO: Get the initialisation values from platform data. + * + * Return 0 on success or a negative error code otherwise. + */ +int omap3isp_ccdc_init(struct isp_device *isp) +{ +	struct isp_ccdc_device *ccdc = &isp->isp_ccdc; +	int ret; + +	spin_lock_init(&ccdc->lock); +	init_waitqueue_head(&ccdc->wait); +	mutex_init(&ccdc->ioctl_lock); + +	ccdc->stopping = CCDC_STOP_NOT_REQUESTED; + +	INIT_WORK(&ccdc->lsc.table_work, ccdc_lsc_free_table_work); +	ccdc->lsc.state = LSC_STATE_STOPPED; +	INIT_LIST_HEAD(&ccdc->lsc.free_queue); +	spin_lock_init(&ccdc->lsc.req_lock); + +	ccdc->clamp.oblen = 0; +	ccdc->clamp.dcsubval = 0; + +	ccdc->update = OMAP3ISP_CCDC_BLCLAMP; +	ccdc_apply_controls(ccdc); + +	ret = ccdc_init_entities(ccdc); +	if (ret < 0) { +		mutex_destroy(&ccdc->ioctl_lock); +		return ret; +	} + +	return 0; +} + +/* + * omap3isp_ccdc_cleanup - CCDC module cleanup. + * @isp: Device pointer specific to the OMAP3 ISP. + */ +void omap3isp_ccdc_cleanup(struct isp_device *isp) +{ +	struct isp_ccdc_device *ccdc = &isp->isp_ccdc; + +	omap3isp_video_cleanup(&ccdc->video_out); +	media_entity_cleanup(&ccdc->subdev.entity); + +	/* Free LSC requests. As the CCDC is stopped there's no active request, +	 * so only the pending request and the free queue need to be handled. +	 */ +	ccdc_lsc_free_request(ccdc, ccdc->lsc.request); +	cancel_work_sync(&ccdc->lsc.table_work); +	ccdc_lsc_free_queue(ccdc, &ccdc->lsc.free_queue); + +	if (ccdc->fpc.addr != NULL) +		dma_free_coherent(isp->dev, ccdc->fpc.fpnum * 4, ccdc->fpc.addr, +				  ccdc->fpc.dma); + +	mutex_destroy(&ccdc->ioctl_lock); +} diff --git a/drivers/media/platform/omap3isp/ispccdc.h b/drivers/media/platform/omap3isp/ispccdc.h new file mode 100644 index 00000000000..f65061602c7 --- /dev/null +++ b/drivers/media/platform/omap3isp/ispccdc.h @@ -0,0 +1,176 @@ +/* + * ispccdc.h + * + * TI OMAP3 ISP - CCDC module + * + * Copyright (C) 2009-2010 Nokia Corporation + * Copyright (C) 2009 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 + */ + +#ifndef OMAP3_ISP_CCDC_H +#define OMAP3_ISP_CCDC_H + +#include <linux/omap3isp.h> +#include <linux/workqueue.h> + +#include "ispvideo.h" + +enum ccdc_input_entity { +	CCDC_INPUT_NONE, +	CCDC_INPUT_PARALLEL, +	CCDC_INPUT_CSI2A, +	CCDC_INPUT_CCP2B, +	CCDC_INPUT_CSI2C +}; + +#define CCDC_OUTPUT_MEMORY	(1 << 0) +#define CCDC_OUTPUT_PREVIEW	(1 << 1) +#define CCDC_OUTPUT_RESIZER	(1 << 2) + +#define	OMAP3ISP_CCDC_NEVENTS	16 + +struct ispccdc_fpc { +	void *addr; +	dma_addr_t dma; +	unsigned int fpnum; +}; + +enum ispccdc_lsc_state { +	LSC_STATE_STOPPED = 0, +	LSC_STATE_STOPPING = 1, +	LSC_STATE_RUNNING = 2, +	LSC_STATE_RECONFIG = 3, +}; + +struct ispccdc_lsc_config_req { +	struct list_head list; +	struct omap3isp_ccdc_lsc_config config; +	unsigned char enable; + +	struct { +		void *addr; +		dma_addr_t dma; +		struct sg_table sgt; +	} table; +}; + +/* + * ispccdc_lsc - CCDC LSC parameters + */ +struct ispccdc_lsc { +	enum ispccdc_lsc_state state; +	struct work_struct table_work; + +	/* LSC queue of configurations */ +	spinlock_t req_lock; +	struct ispccdc_lsc_config_req *request;	/* requested configuration */ +	struct ispccdc_lsc_config_req *active;	/* active configuration */ +	struct list_head free_queue;	/* configurations for freeing */ +}; + +#define CCDC_STOP_NOT_REQUESTED		0x00 +#define CCDC_STOP_REQUEST		0x01 +#define CCDC_STOP_EXECUTED		(0x02 | CCDC_STOP_REQUEST) +#define CCDC_STOP_CCDC_FINISHED		0x04 +#define CCDC_STOP_LSC_FINISHED		0x08 +#define CCDC_STOP_FINISHED		\ +	(CCDC_STOP_EXECUTED | CCDC_STOP_CCDC_FINISHED | CCDC_STOP_LSC_FINISHED) + +#define CCDC_EVENT_VD1			0x10 +#define CCDC_EVENT_VD0			0x20 +#define CCDC_EVENT_LSC_DONE		0x40 + +/* Sink and source CCDC pads */ +#define CCDC_PAD_SINK			0 +#define CCDC_PAD_SOURCE_OF		1 +#define CCDC_PAD_SOURCE_VP		2 +#define CCDC_PADS_NUM			3 + +/* + * struct isp_ccdc_device - Structure for the CCDC module to store its own + *			    information + * @subdev: V4L2 subdevice + * @pads: Sink and source media entity pads + * @formats: Active video formats + * @crop: Active crop rectangle on the OF source pad + * @input: Active input + * @output: Active outputs + * @video_out: Output video node + * @alaw: A-law compression enabled (1) or disabled (0) + * @lpf: Low pass filter enabled (1) or disabled (0) + * @obclamp: Optical-black clamp enabled (1) or disabled (0) + * @fpc_en: Faulty pixels correction enabled (1) or disabled (0) + * @blcomp: Black level compensation configuration + * @clamp: Optical-black or digital clamp configuration + * @fpc: Faulty pixels correction configuration + * @lsc: Lens shading compensation configuration + * @update: Bitmask of controls to update during the next interrupt + * @shadow_update: Controls update in progress by userspace + * @underrun: A buffer underrun occurred and a new buffer has been queued + * @state: Streaming state + * @lock: Serializes shadow_update with interrupt handler + * @wait: Wait queue used to stop the module + * @stopping: Stopping state + * @ioctl_lock: Serializes ioctl calls and LSC requests freeing + */ +struct isp_ccdc_device { +	struct v4l2_subdev subdev; +	struct media_pad pads[CCDC_PADS_NUM]; +	struct v4l2_mbus_framefmt formats[CCDC_PADS_NUM]; +	struct v4l2_rect crop; + +	enum ccdc_input_entity input; +	unsigned int output; +	struct isp_video video_out; + +	unsigned int alaw:1, +		     lpf:1, +		     obclamp:1, +		     fpc_en:1; +	struct omap3isp_ccdc_blcomp blcomp; +	struct omap3isp_ccdc_bclamp clamp; +	struct ispccdc_fpc fpc; +	struct ispccdc_lsc lsc; +	unsigned int update; +	unsigned int shadow_update; + +	unsigned int underrun:1; +	enum isp_pipeline_stream_state state; +	spinlock_t lock; +	wait_queue_head_t wait; +	unsigned int stopping; +	struct mutex ioctl_lock; +}; + +struct isp_device; + +int omap3isp_ccdc_init(struct isp_device *isp); +void omap3isp_ccdc_cleanup(struct isp_device *isp); +int omap3isp_ccdc_register_entities(struct isp_ccdc_device *ccdc, +	struct v4l2_device *vdev); +void omap3isp_ccdc_unregister_entities(struct isp_ccdc_device *ccdc); + +int omap3isp_ccdc_busy(struct isp_ccdc_device *isp_ccdc); +int omap3isp_ccdc_isr(struct isp_ccdc_device *isp_ccdc, u32 events); +void omap3isp_ccdc_restore_context(struct isp_device *isp); +void omap3isp_ccdc_max_rate(struct isp_ccdc_device *ccdc, +	unsigned int *max_rate); + +#endif	/* OMAP3_ISP_CCDC_H */ 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); +} diff --git a/drivers/media/platform/omap3isp/ispccp2.h b/drivers/media/platform/omap3isp/ispccp2.h new file mode 100644 index 00000000000..76d65f4576e --- /dev/null +++ b/drivers/media/platform/omap3isp/ispccp2.h @@ -0,0 +1,98 @@ +/* + * ispccp2.h + * + * 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 + */ + +#ifndef OMAP3_ISP_CCP2_H +#define OMAP3_ISP_CCP2_H + +#include <linux/videodev2.h> + +struct isp_device; +struct isp_csiphy; + +/* Sink and source ccp2 pads */ +#define CCP2_PAD_SINK			0 +#define CCP2_PAD_SOURCE			1 +#define CCP2_PADS_NUM			2 + +/* CCP2 input media entity */ +enum ccp2_input_entity { +	CCP2_INPUT_NONE, +	CCP2_INPUT_SENSOR, +	CCP2_INPUT_MEMORY, +}; + +/* CCP2 output media entity */ +enum ccp2_output_entity { +	CCP2_OUTPUT_NONE, +	CCP2_OUTPUT_CCDC, +	CCP2_OUTPUT_MEMORY, +}; + + +/* Logical channel configuration */ +struct isp_interface_lcx_config { +	int crc; +	u32 data_start; +	u32 data_size; +	u32 format; +}; + +/* Memory channel configuration */ +struct isp_interface_mem_config { +	u32 dst_port; +	u32 vsize_count; +	u32 hsize_count; +	u32 src_ofst; +	u32 dst_ofst; +}; + +/* CCP2 device */ +struct isp_ccp2_device { +	struct v4l2_subdev subdev; +	struct v4l2_mbus_framefmt formats[CCP2_PADS_NUM]; +	struct media_pad pads[CCP2_PADS_NUM]; + +	enum ccp2_input_entity input; +	enum ccp2_output_entity output; +	struct isp_interface_lcx_config if_cfg; +	struct isp_interface_mem_config mem_cfg; +	struct isp_video video_in; +	struct isp_csiphy *phy; +	struct regulator *vdds_csib; +	enum isp_pipeline_stream_state state; +	wait_queue_head_t wait; +	atomic_t stopping; +}; + +/* Function declarations */ +int omap3isp_ccp2_init(struct isp_device *isp); +void omap3isp_ccp2_cleanup(struct isp_device *isp); +int omap3isp_ccp2_register_entities(struct isp_ccp2_device *ccp2, +			struct v4l2_device *vdev); +void omap3isp_ccp2_unregister_entities(struct isp_ccp2_device *ccp2); +void omap3isp_ccp2_isr(struct isp_ccp2_device *ccp2); + +#endif	/* OMAP3_ISP_CCP2_H */ diff --git a/drivers/media/platform/omap3isp/ispcsi2.c b/drivers/media/platform/omap3isp/ispcsi2.c new file mode 100644 index 00000000000..5a2e47e58b8 --- /dev/null +++ b/drivers/media/platform/omap3isp/ispcsi2.c @@ -0,0 +1,1329 @@ +/* + * ispcsi2.c + * + * TI OMAP3 ISP - CSI2 module + * + * Copyright (C) 2010 Nokia Corporation + * Copyright (C) 2009 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 <media/v4l2-common.h> +#include <linux/v4l2-mediabus.h> +#include <linux/mm.h> + +#include "isp.h" +#include "ispreg.h" +#include "ispcsi2.h" + +/* + * csi2_if_enable - Enable CSI2 Receiver interface. + * @enable: enable flag + * + */ +static void csi2_if_enable(struct isp_device *isp, +			   struct isp_csi2_device *csi2, u8 enable) +{ +	struct isp_csi2_ctrl_cfg *currctrl = &csi2->ctrl; + +	isp_reg_clr_set(isp, csi2->regs1, ISPCSI2_CTRL, ISPCSI2_CTRL_IF_EN, +			enable ? ISPCSI2_CTRL_IF_EN : 0); + +	currctrl->if_enable = enable; +} + +/* + * csi2_recv_config - CSI2 receiver module configuration. + * @currctrl: isp_csi2_ctrl_cfg structure + * + */ +static void csi2_recv_config(struct isp_device *isp, +			     struct isp_csi2_device *csi2, +			     struct isp_csi2_ctrl_cfg *currctrl) +{ +	u32 reg; + +	reg = isp_reg_readl(isp, csi2->regs1, ISPCSI2_CTRL); + +	if (currctrl->frame_mode) +		reg |= ISPCSI2_CTRL_FRAME; +	else +		reg &= ~ISPCSI2_CTRL_FRAME; + +	if (currctrl->vp_clk_enable) +		reg |= ISPCSI2_CTRL_VP_CLK_EN; +	else +		reg &= ~ISPCSI2_CTRL_VP_CLK_EN; + +	if (currctrl->vp_only_enable) +		reg |= ISPCSI2_CTRL_VP_ONLY_EN; +	else +		reg &= ~ISPCSI2_CTRL_VP_ONLY_EN; + +	reg &= ~ISPCSI2_CTRL_VP_OUT_CTRL_MASK; +	reg |= currctrl->vp_out_ctrl << ISPCSI2_CTRL_VP_OUT_CTRL_SHIFT; + +	if (currctrl->ecc_enable) +		reg |= ISPCSI2_CTRL_ECC_EN; +	else +		reg &= ~ISPCSI2_CTRL_ECC_EN; + +	isp_reg_writel(isp, reg, csi2->regs1, ISPCSI2_CTRL); +} + +static const unsigned int csi2_input_fmts[] = { +	V4L2_MBUS_FMT_SGRBG10_1X10, +	V4L2_MBUS_FMT_SGRBG10_DPCM8_1X8, +	V4L2_MBUS_FMT_SRGGB10_1X10, +	V4L2_MBUS_FMT_SRGGB10_DPCM8_1X8, +	V4L2_MBUS_FMT_SBGGR10_1X10, +	V4L2_MBUS_FMT_SBGGR10_DPCM8_1X8, +	V4L2_MBUS_FMT_SGBRG10_1X10, +	V4L2_MBUS_FMT_SGBRG10_DPCM8_1X8, +	V4L2_MBUS_FMT_YUYV8_2X8, +}; + +/* To set the format on the CSI2 requires a mapping function that takes + * the following inputs: + * - 3 different formats (at this time) + * - 2 destinations (mem, vp+mem) (vp only handled separately) + * - 2 decompression options (on, off) + * - 2 isp revisions (certain format must be handled differently on OMAP3630) + * Output should be CSI2 frame format code + * Array indices as follows: [format][dest][decompr][is_3630] + * Not all combinations are valid. 0 means invalid. + */ +static const u16 __csi2_fmt_map[3][2][2][2] = { +	/* RAW10 formats */ +	{ +		/* Output to memory */ +		{ +			/* No DPCM decompression */ +			{ CSI2_PIX_FMT_RAW10_EXP16, CSI2_PIX_FMT_RAW10_EXP16 }, +			/* DPCM decompression */ +			{ 0, 0 }, +		}, +		/* Output to both */ +		{ +			/* No DPCM decompression */ +			{ CSI2_PIX_FMT_RAW10_EXP16_VP, +			  CSI2_PIX_FMT_RAW10_EXP16_VP }, +			/* DPCM decompression */ +			{ 0, 0 }, +		}, +	}, +	/* RAW10 DPCM8 formats */ +	{ +		/* Output to memory */ +		{ +			/* No DPCM decompression */ +			{ CSI2_PIX_FMT_RAW8, CSI2_USERDEF_8BIT_DATA1 }, +			/* DPCM decompression */ +			{ CSI2_PIX_FMT_RAW8_DPCM10_EXP16, +			  CSI2_USERDEF_8BIT_DATA1_DPCM10 }, +		}, +		/* Output to both */ +		{ +			/* No DPCM decompression */ +			{ CSI2_PIX_FMT_RAW8_VP, +			  CSI2_PIX_FMT_RAW8_VP }, +			/* DPCM decompression */ +			{ CSI2_PIX_FMT_RAW8_DPCM10_VP, +			  CSI2_USERDEF_8BIT_DATA1_DPCM10_VP }, +		}, +	}, +	/* YUYV8 2X8 formats */ +	{ +		/* Output to memory */ +		{ +			/* No DPCM decompression */ +			{ CSI2_PIX_FMT_YUV422_8BIT, +			  CSI2_PIX_FMT_YUV422_8BIT }, +			/* DPCM decompression */ +			{ 0, 0 }, +		}, +		/* Output to both */ +		{ +			/* No DPCM decompression */ +			{ CSI2_PIX_FMT_YUV422_8BIT_VP, +			  CSI2_PIX_FMT_YUV422_8BIT_VP }, +			/* DPCM decompression */ +			{ 0, 0 }, +		}, +	}, +}; + +/* + * csi2_ctx_map_format - Map CSI2 sink media bus format to CSI2 format ID + * @csi2: ISP CSI2 device + * + * Returns CSI2 physical format id + */ +static u16 csi2_ctx_map_format(struct isp_csi2_device *csi2) +{ +	const struct v4l2_mbus_framefmt *fmt = &csi2->formats[CSI2_PAD_SINK]; +	int fmtidx, destidx, is_3630; + +	switch (fmt->code) { +	case V4L2_MBUS_FMT_SGRBG10_1X10: +	case V4L2_MBUS_FMT_SRGGB10_1X10: +	case V4L2_MBUS_FMT_SBGGR10_1X10: +	case V4L2_MBUS_FMT_SGBRG10_1X10: +		fmtidx = 0; +		break; +	case V4L2_MBUS_FMT_SGRBG10_DPCM8_1X8: +	case V4L2_MBUS_FMT_SRGGB10_DPCM8_1X8: +	case V4L2_MBUS_FMT_SBGGR10_DPCM8_1X8: +	case V4L2_MBUS_FMT_SGBRG10_DPCM8_1X8: +		fmtidx = 1; +		break; +	case V4L2_MBUS_FMT_YUYV8_2X8: +		fmtidx = 2; +		break; +	default: +		WARN(1, KERN_ERR "CSI2: pixel format %08x unsupported!\n", +		     fmt->code); +		return 0; +	} + +	if (!(csi2->output & CSI2_OUTPUT_CCDC) && +	    !(csi2->output & CSI2_OUTPUT_MEMORY)) { +		/* Neither output enabled is a valid combination */ +		return CSI2_PIX_FMT_OTHERS; +	} + +	/* If we need to skip frames at the beginning of the stream disable the +	 * video port to avoid sending the skipped frames to the CCDC. +	 */ +	destidx = csi2->frame_skip ? 0 : !!(csi2->output & CSI2_OUTPUT_CCDC); +	is_3630 = csi2->isp->revision == ISP_REVISION_15_0; + +	return __csi2_fmt_map[fmtidx][destidx][csi2->dpcm_decompress][is_3630]; +} + +/* + * csi2_set_outaddr - Set memory address to save output image + * @csi2: Pointer to ISP CSI2a device. + * @addr: ISP MMU Mapped 32-bit memory address aligned on 32 byte boundary. + * + * Sets the memory address where the output will be saved. + * + * Returns 0 if successful, or -EINVAL if the address is not in the 32 byte + * boundary. + */ +static void csi2_set_outaddr(struct isp_csi2_device *csi2, u32 addr) +{ +	struct isp_device *isp = csi2->isp; +	struct isp_csi2_ctx_cfg *ctx = &csi2->contexts[0]; + +	ctx->ping_addr = addr; +	ctx->pong_addr = addr; +	isp_reg_writel(isp, ctx->ping_addr, +		       csi2->regs1, ISPCSI2_CTX_DAT_PING_ADDR(ctx->ctxnum)); +	isp_reg_writel(isp, ctx->pong_addr, +		       csi2->regs1, ISPCSI2_CTX_DAT_PONG_ADDR(ctx->ctxnum)); +} + +/* + * is_usr_def_mapping - Checks whether USER_DEF_MAPPING should + *			be enabled by CSI2. + * @format_id: mapped format id + * + */ +static inline int is_usr_def_mapping(u32 format_id) +{ +	return (format_id & 0x40) ? 1 : 0; +} + +/* + * csi2_ctx_enable - Enable specified CSI2 context + * @ctxnum: Context number, valid between 0 and 7 values. + * @enable: enable + * + */ +static void csi2_ctx_enable(struct isp_device *isp, +			    struct isp_csi2_device *csi2, u8 ctxnum, u8 enable) +{ +	struct isp_csi2_ctx_cfg *ctx = &csi2->contexts[ctxnum]; +	unsigned int skip = 0; +	u32 reg; + +	reg = isp_reg_readl(isp, csi2->regs1, ISPCSI2_CTX_CTRL1(ctxnum)); + +	if (enable) { +		if (csi2->frame_skip) +			skip = csi2->frame_skip; +		else if (csi2->output & CSI2_OUTPUT_MEMORY) +			skip = 1; + +		reg &= ~ISPCSI2_CTX_CTRL1_COUNT_MASK; +		reg |= ISPCSI2_CTX_CTRL1_COUNT_UNLOCK +		    |  (skip << ISPCSI2_CTX_CTRL1_COUNT_SHIFT) +		    |  ISPCSI2_CTX_CTRL1_CTX_EN; +	} else { +		reg &= ~ISPCSI2_CTX_CTRL1_CTX_EN; +	} + +	isp_reg_writel(isp, reg, csi2->regs1, ISPCSI2_CTX_CTRL1(ctxnum)); +	ctx->enabled = enable; +} + +/* + * csi2_ctx_config - CSI2 context configuration. + * @ctx: context configuration + * + */ +static void csi2_ctx_config(struct isp_device *isp, +			    struct isp_csi2_device *csi2, +			    struct isp_csi2_ctx_cfg *ctx) +{ +	u32 reg; + +	/* Set up CSI2_CTx_CTRL1 */ +	reg = isp_reg_readl(isp, csi2->regs1, ISPCSI2_CTX_CTRL1(ctx->ctxnum)); + +	if (ctx->eof_enabled) +		reg |= ISPCSI2_CTX_CTRL1_EOF_EN; +	else +		reg &= ~ISPCSI2_CTX_CTRL1_EOF_EN; + +	if (ctx->eol_enabled) +		reg |= ISPCSI2_CTX_CTRL1_EOL_EN; +	else +		reg &= ~ISPCSI2_CTX_CTRL1_EOL_EN; + +	if (ctx->checksum_enabled) +		reg |= ISPCSI2_CTX_CTRL1_CS_EN; +	else +		reg &= ~ISPCSI2_CTX_CTRL1_CS_EN; + +	isp_reg_writel(isp, reg, csi2->regs1, ISPCSI2_CTX_CTRL1(ctx->ctxnum)); + +	/* Set up CSI2_CTx_CTRL2 */ +	reg = isp_reg_readl(isp, csi2->regs1, ISPCSI2_CTX_CTRL2(ctx->ctxnum)); + +	reg &= ~(ISPCSI2_CTX_CTRL2_VIRTUAL_ID_MASK); +	reg |= ctx->virtual_id << ISPCSI2_CTX_CTRL2_VIRTUAL_ID_SHIFT; + +	reg &= ~(ISPCSI2_CTX_CTRL2_FORMAT_MASK); +	reg |= ctx->format_id << ISPCSI2_CTX_CTRL2_FORMAT_SHIFT; + +	if (ctx->dpcm_decompress) { +		if (ctx->dpcm_predictor) +			reg |= ISPCSI2_CTX_CTRL2_DPCM_PRED; +		else +			reg &= ~ISPCSI2_CTX_CTRL2_DPCM_PRED; +	} + +	if (is_usr_def_mapping(ctx->format_id)) { +		reg &= ~ISPCSI2_CTX_CTRL2_USER_DEF_MAP_MASK; +		reg |= 2 << ISPCSI2_CTX_CTRL2_USER_DEF_MAP_SHIFT; +	} + +	isp_reg_writel(isp, reg, csi2->regs1, ISPCSI2_CTX_CTRL2(ctx->ctxnum)); + +	/* Set up CSI2_CTx_CTRL3 */ +	reg = isp_reg_readl(isp, csi2->regs1, ISPCSI2_CTX_CTRL3(ctx->ctxnum)); +	reg &= ~(ISPCSI2_CTX_CTRL3_ALPHA_MASK); +	reg |= (ctx->alpha << ISPCSI2_CTX_CTRL3_ALPHA_SHIFT); + +	isp_reg_writel(isp, reg, csi2->regs1, ISPCSI2_CTX_CTRL3(ctx->ctxnum)); + +	/* Set up CSI2_CTx_DAT_OFST */ +	reg = isp_reg_readl(isp, csi2->regs1, +			    ISPCSI2_CTX_DAT_OFST(ctx->ctxnum)); +	reg &= ~ISPCSI2_CTX_DAT_OFST_OFST_MASK; +	reg |= ctx->data_offset << ISPCSI2_CTX_DAT_OFST_OFST_SHIFT; +	isp_reg_writel(isp, reg, csi2->regs1, +		       ISPCSI2_CTX_DAT_OFST(ctx->ctxnum)); + +	isp_reg_writel(isp, ctx->ping_addr, +		       csi2->regs1, ISPCSI2_CTX_DAT_PING_ADDR(ctx->ctxnum)); + +	isp_reg_writel(isp, ctx->pong_addr, +		       csi2->regs1, ISPCSI2_CTX_DAT_PONG_ADDR(ctx->ctxnum)); +} + +/* + * csi2_timing_config - CSI2 timing configuration. + * @timing: csi2_timing_cfg structure + */ +static void csi2_timing_config(struct isp_device *isp, +			       struct isp_csi2_device *csi2, +			       struct isp_csi2_timing_cfg *timing) +{ +	u32 reg; + +	reg = isp_reg_readl(isp, csi2->regs1, ISPCSI2_TIMING); + +	if (timing->force_rx_mode) +		reg |= ISPCSI2_TIMING_FORCE_RX_MODE_IO(timing->ionum); +	else +		reg &= ~ISPCSI2_TIMING_FORCE_RX_MODE_IO(timing->ionum); + +	if (timing->stop_state_16x) +		reg |= ISPCSI2_TIMING_STOP_STATE_X16_IO(timing->ionum); +	else +		reg &= ~ISPCSI2_TIMING_STOP_STATE_X16_IO(timing->ionum); + +	if (timing->stop_state_4x) +		reg |= ISPCSI2_TIMING_STOP_STATE_X4_IO(timing->ionum); +	else +		reg &= ~ISPCSI2_TIMING_STOP_STATE_X4_IO(timing->ionum); + +	reg &= ~ISPCSI2_TIMING_STOP_STATE_COUNTER_IO_MASK(timing->ionum); +	reg |= timing->stop_state_counter << +	       ISPCSI2_TIMING_STOP_STATE_COUNTER_IO_SHIFT(timing->ionum); + +	isp_reg_writel(isp, reg, csi2->regs1, ISPCSI2_TIMING); +} + +/* + * csi2_irq_ctx_set - Enables CSI2 Context IRQs. + * @enable: Enable/disable CSI2 Context interrupts + */ +static void csi2_irq_ctx_set(struct isp_device *isp, +			     struct isp_csi2_device *csi2, int enable) +{ +	int i; + +	for (i = 0; i < 8; i++) { +		isp_reg_writel(isp, ISPCSI2_CTX_IRQSTATUS_FE_IRQ, csi2->regs1, +			       ISPCSI2_CTX_IRQSTATUS(i)); +		if (enable) +			isp_reg_set(isp, csi2->regs1, ISPCSI2_CTX_IRQENABLE(i), +				    ISPCSI2_CTX_IRQSTATUS_FE_IRQ); +		else +			isp_reg_clr(isp, csi2->regs1, ISPCSI2_CTX_IRQENABLE(i), +				    ISPCSI2_CTX_IRQSTATUS_FE_IRQ); +	} +} + +/* + * csi2_irq_complexio1_set - Enables CSI2 ComplexIO IRQs. + * @enable: Enable/disable CSI2 ComplexIO #1 interrupts + */ +static void csi2_irq_complexio1_set(struct isp_device *isp, +				    struct isp_csi2_device *csi2, int enable) +{ +	u32 reg; +	reg = ISPCSI2_PHY_IRQENABLE_STATEALLULPMEXIT | +		ISPCSI2_PHY_IRQENABLE_STATEALLULPMENTER | +		ISPCSI2_PHY_IRQENABLE_STATEULPM5 | +		ISPCSI2_PHY_IRQENABLE_ERRCONTROL5 | +		ISPCSI2_PHY_IRQENABLE_ERRESC5 | +		ISPCSI2_PHY_IRQENABLE_ERRSOTSYNCHS5 | +		ISPCSI2_PHY_IRQENABLE_ERRSOTHS5 | +		ISPCSI2_PHY_IRQENABLE_STATEULPM4 | +		ISPCSI2_PHY_IRQENABLE_ERRCONTROL4 | +		ISPCSI2_PHY_IRQENABLE_ERRESC4 | +		ISPCSI2_PHY_IRQENABLE_ERRSOTSYNCHS4 | +		ISPCSI2_PHY_IRQENABLE_ERRSOTHS4 | +		ISPCSI2_PHY_IRQENABLE_STATEULPM3 | +		ISPCSI2_PHY_IRQENABLE_ERRCONTROL3 | +		ISPCSI2_PHY_IRQENABLE_ERRESC3 | +		ISPCSI2_PHY_IRQENABLE_ERRSOTSYNCHS3 | +		ISPCSI2_PHY_IRQENABLE_ERRSOTHS3 | +		ISPCSI2_PHY_IRQENABLE_STATEULPM2 | +		ISPCSI2_PHY_IRQENABLE_ERRCONTROL2 | +		ISPCSI2_PHY_IRQENABLE_ERRESC2 | +		ISPCSI2_PHY_IRQENABLE_ERRSOTSYNCHS2 | +		ISPCSI2_PHY_IRQENABLE_ERRSOTHS2 | +		ISPCSI2_PHY_IRQENABLE_STATEULPM1 | +		ISPCSI2_PHY_IRQENABLE_ERRCONTROL1 | +		ISPCSI2_PHY_IRQENABLE_ERRESC1 | +		ISPCSI2_PHY_IRQENABLE_ERRSOTSYNCHS1 | +		ISPCSI2_PHY_IRQENABLE_ERRSOTHS1; +	isp_reg_writel(isp, reg, csi2->regs1, ISPCSI2_PHY_IRQSTATUS); +	if (enable) +		reg |= isp_reg_readl(isp, csi2->regs1, ISPCSI2_PHY_IRQENABLE); +	else +		reg = 0; +	isp_reg_writel(isp, reg, csi2->regs1, ISPCSI2_PHY_IRQENABLE); +} + +/* + * csi2_irq_status_set - Enables CSI2 Status IRQs. + * @enable: Enable/disable CSI2 Status interrupts + */ +static void csi2_irq_status_set(struct isp_device *isp, +				struct isp_csi2_device *csi2, int enable) +{ +	u32 reg; +	reg = ISPCSI2_IRQSTATUS_OCP_ERR_IRQ | +		ISPCSI2_IRQSTATUS_SHORT_PACKET_IRQ | +		ISPCSI2_IRQSTATUS_ECC_CORRECTION_IRQ | +		ISPCSI2_IRQSTATUS_ECC_NO_CORRECTION_IRQ | +		ISPCSI2_IRQSTATUS_COMPLEXIO2_ERR_IRQ | +		ISPCSI2_IRQSTATUS_COMPLEXIO1_ERR_IRQ | +		ISPCSI2_IRQSTATUS_FIFO_OVF_IRQ | +		ISPCSI2_IRQSTATUS_CONTEXT(0); +	isp_reg_writel(isp, reg, csi2->regs1, ISPCSI2_IRQSTATUS); +	if (enable) +		reg |= isp_reg_readl(isp, csi2->regs1, ISPCSI2_IRQENABLE); +	else +		reg = 0; + +	isp_reg_writel(isp, reg, csi2->regs1, ISPCSI2_IRQENABLE); +} + +/* + * omap3isp_csi2_reset - Resets the CSI2 module. + * + * Must be called with the phy lock held. + * + * Returns 0 if successful, or -EBUSY if power command didn't respond. + */ +int omap3isp_csi2_reset(struct isp_csi2_device *csi2) +{ +	struct isp_device *isp = csi2->isp; +	u8 soft_reset_retries = 0; +	u32 reg; +	int i; + +	if (!csi2->available) +		return -ENODEV; + +	if (csi2->phy->phy_in_use) +		return -EBUSY; + +	isp_reg_set(isp, csi2->regs1, ISPCSI2_SYSCONFIG, +		    ISPCSI2_SYSCONFIG_SOFT_RESET); + +	do { +		reg = isp_reg_readl(isp, csi2->regs1, ISPCSI2_SYSSTATUS) & +				    ISPCSI2_SYSSTATUS_RESET_DONE; +		if (reg == ISPCSI2_SYSSTATUS_RESET_DONE) +			break; +		soft_reset_retries++; +		if (soft_reset_retries < 5) +			udelay(100); +	} while (soft_reset_retries < 5); + +	if (soft_reset_retries == 5) { +		dev_err(isp->dev, "CSI2: Soft reset try count exceeded!\n"); +		return -EBUSY; +	} + +	if (isp->revision == ISP_REVISION_15_0) +		isp_reg_set(isp, csi2->regs1, ISPCSI2_PHY_CFG, +			    ISPCSI2_PHY_CFG_RESET_CTRL); + +	i = 100; +	do { +		reg = isp_reg_readl(isp, csi2->phy->phy_regs, ISPCSIPHY_REG1) +		    & ISPCSIPHY_REG1_RESET_DONE_CTRLCLK; +		if (reg == ISPCSIPHY_REG1_RESET_DONE_CTRLCLK) +			break; +		udelay(100); +	} while (--i > 0); + +	if (i == 0) { +		dev_err(isp->dev, +			"CSI2: Reset for CSI2_96M_FCLK domain Failed!\n"); +		return -EBUSY; +	} + +	if (isp->autoidle) +		isp_reg_clr_set(isp, csi2->regs1, ISPCSI2_SYSCONFIG, +				ISPCSI2_SYSCONFIG_MSTANDBY_MODE_MASK | +				ISPCSI2_SYSCONFIG_AUTO_IDLE, +				ISPCSI2_SYSCONFIG_MSTANDBY_MODE_SMART | +				((isp->revision == ISP_REVISION_15_0) ? +				 ISPCSI2_SYSCONFIG_AUTO_IDLE : 0)); +	else +		isp_reg_clr_set(isp, csi2->regs1, ISPCSI2_SYSCONFIG, +				ISPCSI2_SYSCONFIG_MSTANDBY_MODE_MASK | +				ISPCSI2_SYSCONFIG_AUTO_IDLE, +				ISPCSI2_SYSCONFIG_MSTANDBY_MODE_NO); + +	return 0; +} + +static int csi2_configure(struct isp_csi2_device *csi2) +{ +	const struct isp_v4l2_subdevs_group *pdata; +	struct isp_device *isp = csi2->isp; +	struct isp_csi2_timing_cfg *timing = &csi2->timing[0]; +	struct v4l2_subdev *sensor; +	struct media_pad *pad; + +	/* +	 * CSI2 fields that can be updated while the context has +	 * been enabled or the interface has been enabled are not +	 * updated dynamically currently. So we do not allow to +	 * reconfigure if either has been enabled +	 */ +	if (csi2->contexts[0].enabled || csi2->ctrl.if_enable) +		return -EBUSY; + +	pad = media_entity_remote_pad(&csi2->pads[CSI2_PAD_SINK]); +	sensor = media_entity_to_v4l2_subdev(pad->entity); +	pdata = sensor->host_priv; + +	csi2->frame_skip = 0; +	v4l2_subdev_call(sensor, sensor, g_skip_frames, &csi2->frame_skip); + +	csi2->ctrl.vp_out_ctrl = pdata->bus.csi2.vpclk_div; +	csi2->ctrl.frame_mode = ISP_CSI2_FRAME_IMMEDIATE; +	csi2->ctrl.ecc_enable = pdata->bus.csi2.crc; + +	timing->ionum = 1; +	timing->force_rx_mode = 1; +	timing->stop_state_16x = 1; +	timing->stop_state_4x = 1; +	timing->stop_state_counter = 0x1FF; + +	/* +	 * The CSI2 receiver can't do any format conversion except DPCM +	 * decompression, so every set_format call configures both pads +	 * and enables DPCM decompression as a special case: +	 */ +	if (csi2->formats[CSI2_PAD_SINK].code != +	    csi2->formats[CSI2_PAD_SOURCE].code) +		csi2->dpcm_decompress = true; +	else +		csi2->dpcm_decompress = false; + +	csi2->contexts[0].format_id = csi2_ctx_map_format(csi2); + +	if (csi2->video_out.bpl_padding == 0) +		csi2->contexts[0].data_offset = 0; +	else +		csi2->contexts[0].data_offset = csi2->video_out.bpl_value; + +	/* +	 * Enable end of frame and end of line signals generation for +	 * context 0. These signals are generated from CSI2 receiver to +	 * qualify the last pixel of a frame and the last pixel of a line. +	 * Without enabling the signals CSI2 receiver writes data to memory +	 * beyond buffer size and/or data line offset is not handled correctly. +	 */ +	csi2->contexts[0].eof_enabled = 1; +	csi2->contexts[0].eol_enabled = 1; + +	csi2_irq_complexio1_set(isp, csi2, 1); +	csi2_irq_ctx_set(isp, csi2, 1); +	csi2_irq_status_set(isp, csi2, 1); + +	/* Set configuration (timings, format and links) */ +	csi2_timing_config(isp, csi2, timing); +	csi2_recv_config(isp, csi2, &csi2->ctrl); +	csi2_ctx_config(isp, csi2, &csi2->contexts[0]); + +	return 0; +} + +/* + * csi2_print_status - Prints CSI2 debug information. + */ +#define CSI2_PRINT_REGISTER(isp, regs, name)\ +	dev_dbg(isp->dev, "###CSI2 " #name "=0x%08x\n", \ +		isp_reg_readl(isp, regs, ISPCSI2_##name)) + +static void csi2_print_status(struct isp_csi2_device *csi2) +{ +	struct isp_device *isp = csi2->isp; + +	if (!csi2->available) +		return; + +	dev_dbg(isp->dev, "-------------CSI2 Register dump-------------\n"); + +	CSI2_PRINT_REGISTER(isp, csi2->regs1, SYSCONFIG); +	CSI2_PRINT_REGISTER(isp, csi2->regs1, SYSSTATUS); +	CSI2_PRINT_REGISTER(isp, csi2->regs1, IRQENABLE); +	CSI2_PRINT_REGISTER(isp, csi2->regs1, IRQSTATUS); +	CSI2_PRINT_REGISTER(isp, csi2->regs1, CTRL); +	CSI2_PRINT_REGISTER(isp, csi2->regs1, DBG_H); +	CSI2_PRINT_REGISTER(isp, csi2->regs1, GNQ); +	CSI2_PRINT_REGISTER(isp, csi2->regs1, PHY_CFG); +	CSI2_PRINT_REGISTER(isp, csi2->regs1, PHY_IRQSTATUS); +	CSI2_PRINT_REGISTER(isp, csi2->regs1, SHORT_PACKET); +	CSI2_PRINT_REGISTER(isp, csi2->regs1, PHY_IRQENABLE); +	CSI2_PRINT_REGISTER(isp, csi2->regs1, DBG_P); +	CSI2_PRINT_REGISTER(isp, csi2->regs1, TIMING); +	CSI2_PRINT_REGISTER(isp, csi2->regs1, CTX_CTRL1(0)); +	CSI2_PRINT_REGISTER(isp, csi2->regs1, CTX_CTRL2(0)); +	CSI2_PRINT_REGISTER(isp, csi2->regs1, CTX_DAT_OFST(0)); +	CSI2_PRINT_REGISTER(isp, csi2->regs1, CTX_DAT_PING_ADDR(0)); +	CSI2_PRINT_REGISTER(isp, csi2->regs1, CTX_DAT_PONG_ADDR(0)); +	CSI2_PRINT_REGISTER(isp, csi2->regs1, CTX_IRQENABLE(0)); +	CSI2_PRINT_REGISTER(isp, csi2->regs1, CTX_IRQSTATUS(0)); +	CSI2_PRINT_REGISTER(isp, csi2->regs1, CTX_CTRL3(0)); + +	dev_dbg(isp->dev, "--------------------------------------------\n"); +} + +/* ----------------------------------------------------------------------------- + * Interrupt handling + */ + +/* + * csi2_isr_buffer - Does buffer handling at end-of-frame + * when writing to memory. + */ +static void csi2_isr_buffer(struct isp_csi2_device *csi2) +{ +	struct isp_device *isp = csi2->isp; +	struct isp_buffer *buffer; + +	csi2_ctx_enable(isp, csi2, 0, 0); + +	buffer = omap3isp_video_buffer_next(&csi2->video_out); + +	/* +	 * Let video queue operation restart engine if there is an underrun +	 * condition. +	 */ +	if (buffer == NULL) +		return; + +	csi2_set_outaddr(csi2, buffer->dma); +	csi2_ctx_enable(isp, csi2, 0, 1); +} + +static void csi2_isr_ctx(struct isp_csi2_device *csi2, +			 struct isp_csi2_ctx_cfg *ctx) +{ +	struct isp_device *isp = csi2->isp; +	unsigned int n = ctx->ctxnum; +	u32 status; + +	status = isp_reg_readl(isp, csi2->regs1, ISPCSI2_CTX_IRQSTATUS(n)); +	isp_reg_writel(isp, status, csi2->regs1, ISPCSI2_CTX_IRQSTATUS(n)); + +	if (!(status & ISPCSI2_CTX_IRQSTATUS_FE_IRQ)) +		return; + +	/* Skip interrupts until we reach the frame skip count. The CSI2 will be +	 * automatically disabled, as the frame skip count has been programmed +	 * in the CSI2_CTx_CTRL1::COUNT field, so reenable it. +	 * +	 * It would have been nice to rely on the FRAME_NUMBER interrupt instead +	 * but it turned out that the interrupt is only generated when the CSI2 +	 * writes to memory (the CSI2_CTx_CTRL1::COUNT field is decreased +	 * correctly and reaches 0 when data is forwarded to the video port only +	 * but no interrupt arrives). Maybe a CSI2 hardware bug. +	 */ +	if (csi2->frame_skip) { +		csi2->frame_skip--; +		if (csi2->frame_skip == 0) { +			ctx->format_id = csi2_ctx_map_format(csi2); +			csi2_ctx_config(isp, csi2, ctx); +			csi2_ctx_enable(isp, csi2, n, 1); +		} +		return; +	} + +	if (csi2->output & CSI2_OUTPUT_MEMORY) +		csi2_isr_buffer(csi2); +} + +/* + * omap3isp_csi2_isr - CSI2 interrupt handling. + */ +void omap3isp_csi2_isr(struct isp_csi2_device *csi2) +{ +	struct isp_pipeline *pipe = to_isp_pipeline(&csi2->subdev.entity); +	u32 csi2_irqstatus, cpxio1_irqstatus; +	struct isp_device *isp = csi2->isp; + +	if (!csi2->available) +		return; + +	csi2_irqstatus = isp_reg_readl(isp, csi2->regs1, ISPCSI2_IRQSTATUS); +	isp_reg_writel(isp, csi2_irqstatus, csi2->regs1, ISPCSI2_IRQSTATUS); + +	/* Failure Cases */ +	if (csi2_irqstatus & ISPCSI2_IRQSTATUS_COMPLEXIO1_ERR_IRQ) { +		cpxio1_irqstatus = isp_reg_readl(isp, csi2->regs1, +						 ISPCSI2_PHY_IRQSTATUS); +		isp_reg_writel(isp, cpxio1_irqstatus, +			       csi2->regs1, ISPCSI2_PHY_IRQSTATUS); +		dev_dbg(isp->dev, "CSI2: ComplexIO Error IRQ " +			"%x\n", cpxio1_irqstatus); +		pipe->error = true; +	} + +	if (csi2_irqstatus & (ISPCSI2_IRQSTATUS_OCP_ERR_IRQ | +			      ISPCSI2_IRQSTATUS_SHORT_PACKET_IRQ | +			      ISPCSI2_IRQSTATUS_ECC_NO_CORRECTION_IRQ | +			      ISPCSI2_IRQSTATUS_COMPLEXIO2_ERR_IRQ | +			      ISPCSI2_IRQSTATUS_FIFO_OVF_IRQ)) { +		dev_dbg(isp->dev, "CSI2 Err:" +			" OCP:%d," +			" Short_pack:%d," +			" ECC:%d," +			" CPXIO2:%d," +			" FIFO_OVF:%d," +			"\n", +			(csi2_irqstatus & +			 ISPCSI2_IRQSTATUS_OCP_ERR_IRQ) ? 1 : 0, +			(csi2_irqstatus & +			 ISPCSI2_IRQSTATUS_SHORT_PACKET_IRQ) ? 1 : 0, +			(csi2_irqstatus & +			 ISPCSI2_IRQSTATUS_ECC_NO_CORRECTION_IRQ) ? 1 : 0, +			(csi2_irqstatus & +			 ISPCSI2_IRQSTATUS_COMPLEXIO2_ERR_IRQ) ? 1 : 0, +			(csi2_irqstatus & +			 ISPCSI2_IRQSTATUS_FIFO_OVF_IRQ) ? 1 : 0); +		pipe->error = true; +	} + +	if (omap3isp_module_sync_is_stopping(&csi2->wait, &csi2->stopping)) +		return; + +	/* Successful cases */ +	if (csi2_irqstatus & ISPCSI2_IRQSTATUS_CONTEXT(0)) +		csi2_isr_ctx(csi2, &csi2->contexts[0]); + +	if (csi2_irqstatus & ISPCSI2_IRQSTATUS_ECC_CORRECTION_IRQ) +		dev_dbg(isp->dev, "CSI2: ECC correction done\n"); +} + +/* ----------------------------------------------------------------------------- + * ISP video operations + */ + +/* + * csi2_queue - Queues the first buffer when using memory output + * @video: The video node + * @buffer: buffer to queue + */ +static int csi2_queue(struct isp_video *video, struct isp_buffer *buffer) +{ +	struct isp_device *isp = video->isp; +	struct isp_csi2_device *csi2 = &isp->isp_csi2a; + +	csi2_set_outaddr(csi2, buffer->dma); + +	/* +	 * If streaming was enabled before there was a buffer queued +	 * or underrun happened in the ISR, the hardware was not enabled +	 * and DMA queue flag ISP_VIDEO_DMAQUEUE_UNDERRUN is still set. +	 * Enable it now. +	 */ +	if (csi2->video_out.dmaqueue_flags & ISP_VIDEO_DMAQUEUE_UNDERRUN) { +		/* Enable / disable context 0 and IRQs */ +		csi2_if_enable(isp, csi2, 1); +		csi2_ctx_enable(isp, csi2, 0, 1); +		isp_video_dmaqueue_flags_clr(&csi2->video_out); +	} + +	return 0; +} + +static const struct isp_video_operations csi2_ispvideo_ops = { +	.queue = csi2_queue, +}; + +/* ----------------------------------------------------------------------------- + * V4L2 subdev operations + */ + +static struct v4l2_mbus_framefmt * +__csi2_get_format(struct isp_csi2_device *csi2, 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 &csi2->formats[pad]; +} + +static void +csi2_try_format(struct isp_csi2_device *csi2, struct v4l2_subdev_fh *fh, +		unsigned int pad, struct v4l2_mbus_framefmt *fmt, +		enum v4l2_subdev_format_whence which) +{ +	enum v4l2_mbus_pixelcode pixelcode; +	struct v4l2_mbus_framefmt *format; +	const struct isp_format_info *info; +	unsigned int i; + +	switch (pad) { +	case CSI2_PAD_SINK: +		/* Clamp the width and height to valid range (1-8191). */ +		for (i = 0; i < ARRAY_SIZE(csi2_input_fmts); i++) { +			if (fmt->code == csi2_input_fmts[i]) +				break; +		} + +		/* If not found, use SGRBG10 as default */ +		if (i >= ARRAY_SIZE(csi2_input_fmts)) +			fmt->code = V4L2_MBUS_FMT_SGRBG10_1X10; + +		fmt->width = clamp_t(u32, fmt->width, 1, 8191); +		fmt->height = clamp_t(u32, fmt->height, 1, 8191); +		break; + +	case CSI2_PAD_SOURCE: +		/* Source format same as sink format, except for DPCM +		 * compression. +		 */ +		pixelcode = fmt->code; +		format = __csi2_get_format(csi2, fh, CSI2_PAD_SINK, which); +		memcpy(fmt, format, sizeof(*fmt)); + +		/* +		 * Only Allow DPCM decompression, and check that the +		 * pattern is preserved +		 */ +		info = omap3isp_video_format_info(fmt->code); +		if (info->uncompressed == pixelcode) +			fmt->code = pixelcode; +		break; +	} + +	/* RGB, non-interlaced */ +	fmt->colorspace = V4L2_COLORSPACE_SRGB; +	fmt->field = V4L2_FIELD_NONE; +} + +/* + * csi2_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 csi2_enum_mbus_code(struct v4l2_subdev *sd, +			       struct v4l2_subdev_fh *fh, +			       struct v4l2_subdev_mbus_code_enum *code) +{ +	struct isp_csi2_device *csi2 = v4l2_get_subdevdata(sd); +	struct v4l2_mbus_framefmt *format; +	const struct isp_format_info *info; + +	if (code->pad == CSI2_PAD_SINK) { +		if (code->index >= ARRAY_SIZE(csi2_input_fmts)) +			return -EINVAL; + +		code->code = csi2_input_fmts[code->index]; +	} else { +		format = __csi2_get_format(csi2, fh, CSI2_PAD_SINK, +					   V4L2_SUBDEV_FORMAT_TRY); +		switch (code->index) { +		case 0: +			/* Passthrough sink pad code */ +			code->code = format->code; +			break; +		case 1: +			/* Uncompressed code */ +			info = omap3isp_video_format_info(format->code); +			if (info->uncompressed == format->code) +				return -EINVAL; + +			code->code = info->uncompressed; +			break; +		default: +			return -EINVAL; +		} +	} + +	return 0; +} + +static int csi2_enum_frame_size(struct v4l2_subdev *sd, +				struct v4l2_subdev_fh *fh, +				struct v4l2_subdev_frame_size_enum *fse) +{ +	struct isp_csi2_device *csi2 = 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; +	csi2_try_format(csi2, 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; +	csi2_try_format(csi2, fh, fse->pad, &format, V4L2_SUBDEV_FORMAT_TRY); +	fse->max_width = format.width; +	fse->max_height = format.height; + +	return 0; +} + +/* + * csi2_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 csi2_get_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, +			   struct v4l2_subdev_format *fmt) +{ +	struct isp_csi2_device *csi2 = v4l2_get_subdevdata(sd); +	struct v4l2_mbus_framefmt *format; + +	format = __csi2_get_format(csi2, fh, fmt->pad, fmt->which); +	if (format == NULL) +		return -EINVAL; + +	fmt->format = *format; +	return 0; +} + +/* + * csi2_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 + * return -EINVAL or zero on success + */ +static int csi2_set_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, +			   struct v4l2_subdev_format *fmt) +{ +	struct isp_csi2_device *csi2 = v4l2_get_subdevdata(sd); +	struct v4l2_mbus_framefmt *format; + +	format = __csi2_get_format(csi2, fh, fmt->pad, fmt->which); +	if (format == NULL) +		return -EINVAL; + +	csi2_try_format(csi2, fh, fmt->pad, &fmt->format, fmt->which); +	*format = fmt->format; + +	/* Propagate the format from sink to source */ +	if (fmt->pad == CSI2_PAD_SINK) { +		format = __csi2_get_format(csi2, fh, CSI2_PAD_SOURCE, +					   fmt->which); +		*format = fmt->format; +		csi2_try_format(csi2, fh, CSI2_PAD_SOURCE, format, fmt->which); +	} + +	return 0; +} + +/* + * csi2_init_formats - Initialize formats on all pads + * @sd: ISP CSI2 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 csi2_init_formats(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ +	struct v4l2_subdev_format format; + +	memset(&format, 0, sizeof(format)); +	format.pad = CSI2_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; +	csi2_set_format(sd, fh, &format); + +	return 0; +} + +/* + * csi2_set_stream - Enable/Disable streaming on the CSI2 module + * @sd: ISP CSI2 V4L2 subdevice + * @enable: ISP pipeline stream state + * + * Return 0 on success or a negative error code otherwise. + */ +static int csi2_set_stream(struct v4l2_subdev *sd, int enable) +{ +	struct isp_csi2_device *csi2 = v4l2_get_subdevdata(sd); +	struct isp_device *isp = csi2->isp; +	struct isp_video *video_out = &csi2->video_out; + +	switch (enable) { +	case ISP_PIPELINE_STREAM_CONTINUOUS: +		if (omap3isp_csiphy_acquire(csi2->phy) < 0) +			return -ENODEV; +		if (csi2->output & CSI2_OUTPUT_MEMORY) +			omap3isp_sbl_enable(isp, OMAP3_ISP_SBL_CSI2A_WRITE); +		csi2_configure(csi2); +		csi2_print_status(csi2); + +		/* +		 * When outputting to memory with no buffer available, let the +		 * buffer queue handler start the hardware. A DMA queue flag +		 * ISP_VIDEO_DMAQUEUE_QUEUED will be set as soon as there is +		 * a buffer available. +		 */ +		if (csi2->output & CSI2_OUTPUT_MEMORY && +		    !(video_out->dmaqueue_flags & ISP_VIDEO_DMAQUEUE_QUEUED)) +			break; +		/* Enable context 0 and IRQs */ +		atomic_set(&csi2->stopping, 0); +		csi2_ctx_enable(isp, csi2, 0, 1); +		csi2_if_enable(isp, csi2, 1); +		isp_video_dmaqueue_flags_clr(video_out); +		break; + +	case ISP_PIPELINE_STREAM_STOPPED: +		if (csi2->state == ISP_PIPELINE_STREAM_STOPPED) +			return 0; +		if (omap3isp_module_sync_idle(&sd->entity, &csi2->wait, +					      &csi2->stopping)) +			dev_dbg(isp->dev, "%s: module stop timeout.\n", +				sd->name); +		csi2_ctx_enable(isp, csi2, 0, 0); +		csi2_if_enable(isp, csi2, 0); +		csi2_irq_ctx_set(isp, csi2, 0); +		omap3isp_csiphy_release(csi2->phy); +		isp_video_dmaqueue_flags_clr(video_out); +		omap3isp_sbl_disable(isp, OMAP3_ISP_SBL_CSI2A_WRITE); +		break; +	} + +	csi2->state = enable; +	return 0; +} + +/* subdev video operations */ +static const struct v4l2_subdev_video_ops csi2_video_ops = { +	.s_stream = csi2_set_stream, +}; + +/* subdev pad operations */ +static const struct v4l2_subdev_pad_ops csi2_pad_ops = { +	.enum_mbus_code = csi2_enum_mbus_code, +	.enum_frame_size = csi2_enum_frame_size, +	.get_fmt = csi2_get_format, +	.set_fmt = csi2_set_format, +}; + +/* subdev operations */ +static const struct v4l2_subdev_ops csi2_ops = { +	.video = &csi2_video_ops, +	.pad = &csi2_pad_ops, +}; + +/* subdev internal operations */ +static const struct v4l2_subdev_internal_ops csi2_internal_ops = { +	.open = csi2_init_formats, +}; + +/* ----------------------------------------------------------------------------- + * Media entity operations + */ + +/* + * csi2_link_setup - Setup CSI2 connections. + * @entity : Pointer to media entity structure + * @local  : Pointer to local pad array + * @remote : Pointer to remote pad array + * @flags  : Link flags + * return -EINVAL or zero on success + */ +static int csi2_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_csi2_device *csi2 = v4l2_get_subdevdata(sd); +	struct isp_csi2_ctrl_cfg *ctrl = &csi2->ctrl; + +	/* +	 * The ISP core doesn't support pipelines with multiple video outputs. +	 * Revisit this when it will be implemented, and return -EBUSY for now. +	 */ + +	switch (local->index | media_entity_type(remote->entity)) { +	case CSI2_PAD_SOURCE | MEDIA_ENT_T_DEVNODE: +		if (flags & MEDIA_LNK_FL_ENABLED) { +			if (csi2->output & ~CSI2_OUTPUT_MEMORY) +				return -EBUSY; +			csi2->output |= CSI2_OUTPUT_MEMORY; +		} else { +			csi2->output &= ~CSI2_OUTPUT_MEMORY; +		} +		break; + +	case CSI2_PAD_SOURCE | MEDIA_ENT_T_V4L2_SUBDEV: +		if (flags & MEDIA_LNK_FL_ENABLED) { +			if (csi2->output & ~CSI2_OUTPUT_CCDC) +				return -EBUSY; +			csi2->output |= CSI2_OUTPUT_CCDC; +		} else { +			csi2->output &= ~CSI2_OUTPUT_CCDC; +		} +		break; + +	default: +		/* Link from camera to CSI2 is fixed... */ +		return -EINVAL; +	} + +	ctrl->vp_only_enable = +		(csi2->output & CSI2_OUTPUT_MEMORY) ? false : true; +	ctrl->vp_clk_enable = !!(csi2->output & CSI2_OUTPUT_CCDC); + +	return 0; +} + +/* media operations */ +static const struct media_entity_operations csi2_media_ops = { +	.link_setup = csi2_link_setup, +	.link_validate = v4l2_subdev_link_validate, +}; + +void omap3isp_csi2_unregister_entities(struct isp_csi2_device *csi2) +{ +	v4l2_device_unregister_subdev(&csi2->subdev); +	omap3isp_video_unregister(&csi2->video_out); +} + +int omap3isp_csi2_register_entities(struct isp_csi2_device *csi2, +				    struct v4l2_device *vdev) +{ +	int ret; + +	/* Register the subdev and video nodes. */ +	ret = v4l2_device_register_subdev(vdev, &csi2->subdev); +	if (ret < 0) +		goto error; + +	ret = omap3isp_video_register(&csi2->video_out, vdev); +	if (ret < 0) +		goto error; + +	return 0; + +error: +	omap3isp_csi2_unregister_entities(csi2); +	return ret; +} + +/* ----------------------------------------------------------------------------- + * ISP CSI2 initialisation and cleanup + */ + +/* + * csi2_init_entities - Initialize subdev and media entity. + * @csi2: Pointer to csi2 structure. + * return -ENOMEM or zero on success + */ +static int csi2_init_entities(struct isp_csi2_device *csi2) +{ +	struct v4l2_subdev *sd = &csi2->subdev; +	struct media_pad *pads = csi2->pads; +	struct media_entity *me = &sd->entity; +	int ret; + +	v4l2_subdev_init(sd, &csi2_ops); +	sd->internal_ops = &csi2_internal_ops; +	strlcpy(sd->name, "OMAP3 ISP CSI2a", sizeof(sd->name)); + +	sd->grp_id = 1 << 16;	/* group ID for isp subdevs */ +	v4l2_set_subdevdata(sd, csi2); +	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + +	pads[CSI2_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; +	pads[CSI2_PAD_SINK].flags = MEDIA_PAD_FL_SINK +				    | MEDIA_PAD_FL_MUST_CONNECT; + +	me->ops = &csi2_media_ops; +	ret = media_entity_init(me, CSI2_PADS_NUM, pads, 0); +	if (ret < 0) +		return ret; + +	csi2_init_formats(sd, NULL); + +	/* Video device node */ +	csi2->video_out.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; +	csi2->video_out.ops = &csi2_ispvideo_ops; +	csi2->video_out.bpl_alignment = 32; +	csi2->video_out.bpl_zero_padding = 1; +	csi2->video_out.bpl_max = 0x1ffe0; +	csi2->video_out.isp = csi2->isp; +	csi2->video_out.capture_mem = PAGE_ALIGN(4096 * 4096) * 3; + +	ret = omap3isp_video_init(&csi2->video_out, "CSI2a"); +	if (ret < 0) +		goto error_video; + +	/* Connect the CSI2 subdev to the video node. */ +	ret = media_entity_create_link(&csi2->subdev.entity, CSI2_PAD_SOURCE, +				       &csi2->video_out.video.entity, 0, 0); +	if (ret < 0) +		goto error_link; + +	return 0; + +error_link: +	omap3isp_video_cleanup(&csi2->video_out); +error_video: +	media_entity_cleanup(&csi2->subdev.entity); +	return ret; +} + +/* + * omap3isp_csi2_init - Routine for module driver init + */ +int omap3isp_csi2_init(struct isp_device *isp) +{ +	struct isp_csi2_device *csi2a = &isp->isp_csi2a; +	struct isp_csi2_device *csi2c = &isp->isp_csi2c; +	int ret; + +	csi2a->isp = isp; +	csi2a->available = 1; +	csi2a->regs1 = OMAP3_ISP_IOMEM_CSI2A_REGS1; +	csi2a->regs2 = OMAP3_ISP_IOMEM_CSI2A_REGS2; +	csi2a->phy = &isp->isp_csiphy2; +	csi2a->state = ISP_PIPELINE_STREAM_STOPPED; +	init_waitqueue_head(&csi2a->wait); + +	ret = csi2_init_entities(csi2a); +	if (ret < 0) +		return ret; + +	if (isp->revision == ISP_REVISION_15_0) { +		csi2c->isp = isp; +		csi2c->available = 1; +		csi2c->regs1 = OMAP3_ISP_IOMEM_CSI2C_REGS1; +		csi2c->regs2 = OMAP3_ISP_IOMEM_CSI2C_REGS2; +		csi2c->phy = &isp->isp_csiphy1; +		csi2c->state = ISP_PIPELINE_STREAM_STOPPED; +		init_waitqueue_head(&csi2c->wait); +	} + +	return 0; +} + +/* + * omap3isp_csi2_cleanup - Routine for module driver cleanup + */ +void omap3isp_csi2_cleanup(struct isp_device *isp) +{ +	struct isp_csi2_device *csi2a = &isp->isp_csi2a; + +	omap3isp_video_cleanup(&csi2a->video_out); +	media_entity_cleanup(&csi2a->subdev.entity); +} diff --git a/drivers/media/platform/omap3isp/ispcsi2.h b/drivers/media/platform/omap3isp/ispcsi2.h new file mode 100644 index 00000000000..c57729b7e86 --- /dev/null +++ b/drivers/media/platform/omap3isp/ispcsi2.h @@ -0,0 +1,165 @@ +/* + * ispcsi2.h + * + * TI OMAP3 ISP - CSI2 module + * + * Copyright (C) 2010 Nokia Corporation + * Copyright (C) 2009 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 + */ + +#ifndef OMAP3_ISP_CSI2_H +#define OMAP3_ISP_CSI2_H + +#include <linux/types.h> +#include <linux/videodev2.h> + +struct isp_csiphy; + +/* This is not an exhaustive list */ +enum isp_csi2_pix_formats { +	CSI2_PIX_FMT_OTHERS = 0, +	CSI2_PIX_FMT_YUV422_8BIT = 0x1e, +	CSI2_PIX_FMT_YUV422_8BIT_VP = 0x9e, +	CSI2_PIX_FMT_RAW10_EXP16 = 0xab, +	CSI2_PIX_FMT_RAW10_EXP16_VP = 0x12f, +	CSI2_PIX_FMT_RAW8 = 0x2a, +	CSI2_PIX_FMT_RAW8_DPCM10_EXP16 = 0x2aa, +	CSI2_PIX_FMT_RAW8_DPCM10_VP = 0x32a, +	CSI2_PIX_FMT_RAW8_VP = 0x12a, +	CSI2_USERDEF_8BIT_DATA1_DPCM10_VP = 0x340, +	CSI2_USERDEF_8BIT_DATA1_DPCM10 = 0x2c0, +	CSI2_USERDEF_8BIT_DATA1 = 0x40, +}; + +enum isp_csi2_irqevents { +	OCP_ERR_IRQ = 0x4000, +	SHORT_PACKET_IRQ = 0x2000, +	ECC_CORRECTION_IRQ = 0x1000, +	ECC_NO_CORRECTION_IRQ = 0x800, +	COMPLEXIO2_ERR_IRQ = 0x400, +	COMPLEXIO1_ERR_IRQ = 0x200, +	FIFO_OVF_IRQ = 0x100, +	CONTEXT7 = 0x80, +	CONTEXT6 = 0x40, +	CONTEXT5 = 0x20, +	CONTEXT4 = 0x10, +	CONTEXT3 = 0x8, +	CONTEXT2 = 0x4, +	CONTEXT1 = 0x2, +	CONTEXT0 = 0x1, +}; + +enum isp_csi2_ctx_irqevents { +	CTX_ECC_CORRECTION = 0x100, +	CTX_LINE_NUMBER = 0x80, +	CTX_FRAME_NUMBER = 0x40, +	CTX_CS = 0x20, +	CTX_LE = 0x8, +	CTX_LS = 0x4, +	CTX_FE = 0x2, +	CTX_FS = 0x1, +}; + +enum isp_csi2_frame_mode { +	ISP_CSI2_FRAME_IMMEDIATE, +	ISP_CSI2_FRAME_AFTERFEC, +}; + +#define ISP_CSI2_MAX_CTX_NUM	7 + +struct isp_csi2_ctx_cfg { +	u8 ctxnum;		/* context number 0 - 7 */ +	u8 dpcm_decompress; + +	/* Fields in CSI2_CTx_CTRL2 - locked by CSI2_CTx_CTRL1.CTX_EN */ +	u8 virtual_id; +	u16 format_id;		/* as in CSI2_CTx_CTRL2[9:0] */ +	u8 dpcm_predictor;	/* 1: simple, 0: advanced */ + +	/* Fields in CSI2_CTx_CTRL1/3 - Shadowed */ +	u16 alpha; +	u16 data_offset; +	u32 ping_addr; +	u32 pong_addr; +	u8 eof_enabled; +	u8 eol_enabled; +	u8 checksum_enabled; +	u8 enabled; +}; + +struct isp_csi2_timing_cfg { +	u8 ionum;			/* IO1 or IO2 as in CSI2_TIMING */ +	unsigned force_rx_mode:1; +	unsigned stop_state_16x:1; +	unsigned stop_state_4x:1; +	u16 stop_state_counter; +}; + +struct isp_csi2_ctrl_cfg { +	bool vp_clk_enable; +	bool vp_only_enable; +	u8 vp_out_ctrl; +	enum isp_csi2_frame_mode frame_mode; +	bool ecc_enable; +	bool if_enable; +}; + +#define CSI2_PAD_SINK		0 +#define CSI2_PAD_SOURCE		1 +#define CSI2_PADS_NUM		2 + +#define CSI2_OUTPUT_CCDC	(1 << 0) +#define CSI2_OUTPUT_MEMORY	(1 << 1) + +struct isp_csi2_device { +	struct v4l2_subdev subdev; +	struct media_pad pads[CSI2_PADS_NUM]; +	struct v4l2_mbus_framefmt formats[CSI2_PADS_NUM]; + +	struct isp_video video_out; +	struct isp_device *isp; + +	u8 available;		/* Is the IP present on the silicon? */ + +	/* mem resources - enums as defined in enum isp_mem_resources */ +	u8 regs1; +	u8 regs2; + +	u32 output; /* output to CCDC, memory or both? */ +	bool dpcm_decompress; +	unsigned int frame_skip; + +	struct isp_csiphy *phy; +	struct isp_csi2_ctx_cfg contexts[ISP_CSI2_MAX_CTX_NUM + 1]; +	struct isp_csi2_timing_cfg timing[2]; +	struct isp_csi2_ctrl_cfg ctrl; +	enum isp_pipeline_stream_state state; +	wait_queue_head_t wait; +	atomic_t stopping; +}; + +void omap3isp_csi2_isr(struct isp_csi2_device *csi2); +int omap3isp_csi2_reset(struct isp_csi2_device *csi2); +int omap3isp_csi2_init(struct isp_device *isp); +void omap3isp_csi2_cleanup(struct isp_device *isp); +void omap3isp_csi2_unregister_entities(struct isp_csi2_device *csi2); +int omap3isp_csi2_register_entities(struct isp_csi2_device *csi2, +				    struct v4l2_device *vdev); +#endif	/* OMAP3_ISP_CSI2_H */ diff --git a/drivers/media/platform/omap3isp/ispcsiphy.c b/drivers/media/platform/omap3isp/ispcsiphy.c new file mode 100644 index 00000000000..c09de32f986 --- /dev/null +++ b/drivers/media/platform/omap3isp/ispcsiphy.c @@ -0,0 +1,353 @@ +/* + * ispcsiphy.c + * + * TI OMAP3 ISP - CSI PHY module + * + * Copyright (C) 2010 Nokia Corporation + * Copyright (C) 2009 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/regulator/consumer.h> + +#include "isp.h" +#include "ispreg.h" +#include "ispcsiphy.h" + +static void csiphy_routing_cfg_3630(struct isp_csiphy *phy, +				    enum isp_interface_type iface, +				    bool ccp2_strobe) +{ +	u32 reg = isp_reg_readl( +		phy->isp, OMAP3_ISP_IOMEM_3630_CONTROL_CAMERA_PHY_CTRL, 0); +	u32 shift, mode; + +	switch (iface) { +	default: +	/* Should not happen in practice, but let's keep the compiler happy. */ +	case ISP_INTERFACE_CCP2B_PHY1: +		reg &= ~OMAP3630_CONTROL_CAMERA_PHY_CTRL_CSI1_RX_SEL_PHY2; +		shift = OMAP3630_CONTROL_CAMERA_PHY_CTRL_CAMMODE_PHY1_SHIFT; +		break; +	case ISP_INTERFACE_CSI2C_PHY1: +		shift = OMAP3630_CONTROL_CAMERA_PHY_CTRL_CAMMODE_PHY1_SHIFT; +		mode = OMAP3630_CONTROL_CAMERA_PHY_CTRL_CAMMODE_DPHY; +		break; +	case ISP_INTERFACE_CCP2B_PHY2: +		reg |= OMAP3630_CONTROL_CAMERA_PHY_CTRL_CSI1_RX_SEL_PHY2; +		shift = OMAP3630_CONTROL_CAMERA_PHY_CTRL_CAMMODE_PHY2_SHIFT; +		break; +	case ISP_INTERFACE_CSI2A_PHY2: +		shift = OMAP3630_CONTROL_CAMERA_PHY_CTRL_CAMMODE_PHY2_SHIFT; +		mode = OMAP3630_CONTROL_CAMERA_PHY_CTRL_CAMMODE_DPHY; +		break; +	} + +	/* Select data/clock or data/strobe mode for CCP2 */ +	if (iface == ISP_INTERFACE_CCP2B_PHY1 || +	    iface == ISP_INTERFACE_CCP2B_PHY2) { +		if (ccp2_strobe) +			mode = OMAP3630_CONTROL_CAMERA_PHY_CTRL_CAMMODE_CCP2_DATA_STROBE; +		else +			mode = OMAP3630_CONTROL_CAMERA_PHY_CTRL_CAMMODE_CCP2_DATA_CLOCK; +	} + +	reg &= ~(OMAP3630_CONTROL_CAMERA_PHY_CTRL_CAMMODE_MASK << shift); +	reg |= mode << shift; + +	isp_reg_writel(phy->isp, reg, +		       OMAP3_ISP_IOMEM_3630_CONTROL_CAMERA_PHY_CTRL, 0); +} + +static void csiphy_routing_cfg_3430(struct isp_csiphy *phy, u32 iface, bool on, +				    bool ccp2_strobe) +{ +	u32 csirxfe = OMAP343X_CONTROL_CSIRXFE_PWRDNZ +		| OMAP343X_CONTROL_CSIRXFE_RESET; + +	/* Only the CCP2B on PHY1 is configurable. */ +	if (iface != ISP_INTERFACE_CCP2B_PHY1) +		return; + +	if (!on) { +		isp_reg_writel(phy->isp, 0, +			       OMAP3_ISP_IOMEM_343X_CONTROL_CSIRXFE, 0); +		return; +	} + +	if (ccp2_strobe) +		csirxfe |= OMAP343X_CONTROL_CSIRXFE_SELFORM; + +	isp_reg_writel(phy->isp, csirxfe, +		       OMAP3_ISP_IOMEM_343X_CONTROL_CSIRXFE, 0); +} + +/* + * Configure OMAP 3 CSI PHY routing. + * @phy: relevant phy device + * @iface: ISP_INTERFACE_* + * @on: power on or off + * @ccp2_strobe: false: data/clock, true: data/strobe + * + * Note that the underlying routing configuration registers are part of the + * control (SCM) register space and part of the CORE power domain on both 3430 + * and 3630, so they will not hold their contents in off-mode. This isn't an + * issue since the MPU power domain is forced on whilst the ISP is in use. + */ +static void csiphy_routing_cfg(struct isp_csiphy *phy, +			       enum isp_interface_type iface, bool on, +			       bool ccp2_strobe) +{ +	if (phy->isp->mmio_base[OMAP3_ISP_IOMEM_3630_CONTROL_CAMERA_PHY_CTRL] +	    && on) +		return csiphy_routing_cfg_3630(phy, iface, ccp2_strobe); +	if (phy->isp->mmio_base[OMAP3_ISP_IOMEM_343X_CONTROL_CSIRXFE]) +		return csiphy_routing_cfg_3430(phy, iface, on, ccp2_strobe); +} + +/* + * csiphy_power_autoswitch_enable + * @enable: Sets or clears the autoswitch function enable flag. + */ +static void csiphy_power_autoswitch_enable(struct isp_csiphy *phy, bool enable) +{ +	isp_reg_clr_set(phy->isp, phy->cfg_regs, ISPCSI2_PHY_CFG, +			ISPCSI2_PHY_CFG_PWR_AUTO, +			enable ? ISPCSI2_PHY_CFG_PWR_AUTO : 0); +} + +/* + * csiphy_set_power + * @power: Power state to be set. + * + * Returns 0 if successful, or -EBUSY if the retry count is exceeded. + */ +static int csiphy_set_power(struct isp_csiphy *phy, u32 power) +{ +	u32 reg; +	u8 retry_count; + +	isp_reg_clr_set(phy->isp, phy->cfg_regs, ISPCSI2_PHY_CFG, +			ISPCSI2_PHY_CFG_PWR_CMD_MASK, power); + +	retry_count = 0; +	do { +		udelay(50); +		reg = isp_reg_readl(phy->isp, phy->cfg_regs, ISPCSI2_PHY_CFG) & +				    ISPCSI2_PHY_CFG_PWR_STATUS_MASK; + +		if (reg != power >> 2) +			retry_count++; + +	} while ((reg != power >> 2) && (retry_count < 100)); + +	if (retry_count == 100) { +		dev_err(phy->isp->dev, "CSI2 CIO set power failed!\n"); +		return -EBUSY; +	} + +	return 0; +} + +/* + * TCLK values are OK at their reset values + */ +#define TCLK_TERM	0 +#define TCLK_MISS	1 +#define TCLK_SETTLE	14 + +static int omap3isp_csiphy_config(struct isp_csiphy *phy) +{ +	struct isp_csi2_device *csi2 = phy->csi2; +	struct isp_pipeline *pipe = to_isp_pipeline(&csi2->subdev.entity); +	struct isp_v4l2_subdevs_group *subdevs = pipe->external->host_priv; +	struct isp_csiphy_lanes_cfg *lanes; +	int csi2_ddrclk_khz; +	unsigned int used_lanes = 0; +	unsigned int i; +	u32 reg; + +	if (subdevs->interface == ISP_INTERFACE_CCP2B_PHY1 +	    || subdevs->interface == ISP_INTERFACE_CCP2B_PHY2) +		lanes = &subdevs->bus.ccp2.lanecfg; +	else +		lanes = &subdevs->bus.csi2.lanecfg; + +	/* Clock and data lanes verification */ +	for (i = 0; i < phy->num_data_lanes; i++) { +		if (lanes->data[i].pol > 1 || lanes->data[i].pos > 3) +			return -EINVAL; + +		if (used_lanes & (1 << lanes->data[i].pos)) +			return -EINVAL; + +		used_lanes |= 1 << lanes->data[i].pos; +	} + +	if (lanes->clk.pol > 1 || lanes->clk.pos > 3) +		return -EINVAL; + +	if (lanes->clk.pos == 0 || used_lanes & (1 << lanes->clk.pos)) +		return -EINVAL; + +	/* +	 * The PHY configuration is lost in off mode, that's not an +	 * issue since the MPU power domain is forced on whilst the +	 * ISP is in use. +	 */ +	csiphy_routing_cfg(phy, subdevs->interface, true, +			   subdevs->bus.ccp2.phy_layer); + +	/* DPHY timing configuration */ +	/* CSI-2 is DDR and we only count used lanes. */ +	csi2_ddrclk_khz = pipe->external_rate / 1000 +		/ (2 * hweight32(used_lanes)) * pipe->external_width; + +	reg = isp_reg_readl(csi2->isp, phy->phy_regs, ISPCSIPHY_REG0); + +	reg &= ~(ISPCSIPHY_REG0_THS_TERM_MASK | +		 ISPCSIPHY_REG0_THS_SETTLE_MASK); +	/* THS_TERM: Programmed value = ceil(12.5 ns/DDRClk period) - 1. */ +	reg |= (DIV_ROUND_UP(25 * csi2_ddrclk_khz, 2000000) - 1) +		<< ISPCSIPHY_REG0_THS_TERM_SHIFT; +	/* THS_SETTLE: Programmed value = ceil(90 ns/DDRClk period) + 3. */ +	reg |= (DIV_ROUND_UP(90 * csi2_ddrclk_khz, 1000000) + 3) +		<< ISPCSIPHY_REG0_THS_SETTLE_SHIFT; + +	isp_reg_writel(csi2->isp, reg, phy->phy_regs, ISPCSIPHY_REG0); + +	reg = isp_reg_readl(csi2->isp, phy->phy_regs, ISPCSIPHY_REG1); + +	reg &= ~(ISPCSIPHY_REG1_TCLK_TERM_MASK | +		 ISPCSIPHY_REG1_TCLK_MISS_MASK | +		 ISPCSIPHY_REG1_TCLK_SETTLE_MASK); +	reg |= TCLK_TERM << ISPCSIPHY_REG1_TCLK_TERM_SHIFT; +	reg |= TCLK_MISS << ISPCSIPHY_REG1_TCLK_MISS_SHIFT; +	reg |= TCLK_SETTLE << ISPCSIPHY_REG1_TCLK_SETTLE_SHIFT; + +	isp_reg_writel(csi2->isp, reg, phy->phy_regs, ISPCSIPHY_REG1); + +	/* DPHY lane configuration */ +	reg = isp_reg_readl(csi2->isp, phy->cfg_regs, ISPCSI2_PHY_CFG); + +	for (i = 0; i < phy->num_data_lanes; i++) { +		reg &= ~(ISPCSI2_PHY_CFG_DATA_POL_MASK(i + 1) | +			 ISPCSI2_PHY_CFG_DATA_POSITION_MASK(i + 1)); +		reg |= (lanes->data[i].pol << +			ISPCSI2_PHY_CFG_DATA_POL_SHIFT(i + 1)); +		reg |= (lanes->data[i].pos << +			ISPCSI2_PHY_CFG_DATA_POSITION_SHIFT(i + 1)); +	} + +	reg &= ~(ISPCSI2_PHY_CFG_CLOCK_POL_MASK | +		 ISPCSI2_PHY_CFG_CLOCK_POSITION_MASK); +	reg |= lanes->clk.pol << ISPCSI2_PHY_CFG_CLOCK_POL_SHIFT; +	reg |= lanes->clk.pos << ISPCSI2_PHY_CFG_CLOCK_POSITION_SHIFT; + +	isp_reg_writel(csi2->isp, reg, phy->cfg_regs, ISPCSI2_PHY_CFG); + +	return 0; +} + +int omap3isp_csiphy_acquire(struct isp_csiphy *phy) +{ +	int rval; + +	if (phy->vdd == NULL) { +		dev_err(phy->isp->dev, "Power regulator for CSI PHY not " +			"available\n"); +		return -ENODEV; +	} + +	mutex_lock(&phy->mutex); + +	rval = regulator_enable(phy->vdd); +	if (rval < 0) +		goto done; + +	rval = omap3isp_csi2_reset(phy->csi2); +	if (rval < 0) +		goto done; + +	rval = omap3isp_csiphy_config(phy); +	if (rval < 0) +		goto done; + +	rval = csiphy_set_power(phy, ISPCSI2_PHY_CFG_PWR_CMD_ON); +	if (rval) { +		regulator_disable(phy->vdd); +		goto done; +	} + +	csiphy_power_autoswitch_enable(phy, true); +	phy->phy_in_use = 1; + +done: +	mutex_unlock(&phy->mutex); +	return rval; +} + +void omap3isp_csiphy_release(struct isp_csiphy *phy) +{ +	mutex_lock(&phy->mutex); +	if (phy->phy_in_use) { +		struct isp_csi2_device *csi2 = phy->csi2; +		struct isp_pipeline *pipe = +			to_isp_pipeline(&csi2->subdev.entity); +		struct isp_v4l2_subdevs_group *subdevs = +			pipe->external->host_priv; + +		csiphy_routing_cfg(phy, subdevs->interface, false, +				   subdevs->bus.ccp2.phy_layer); +		csiphy_power_autoswitch_enable(phy, false); +		csiphy_set_power(phy, ISPCSI2_PHY_CFG_PWR_CMD_OFF); +		regulator_disable(phy->vdd); +		phy->phy_in_use = 0; +	} +	mutex_unlock(&phy->mutex); +} + +/* + * omap3isp_csiphy_init - Initialize the CSI PHY frontends + */ +int omap3isp_csiphy_init(struct isp_device *isp) +{ +	struct isp_csiphy *phy1 = &isp->isp_csiphy1; +	struct isp_csiphy *phy2 = &isp->isp_csiphy2; + +	phy2->isp = isp; +	phy2->csi2 = &isp->isp_csi2a; +	phy2->num_data_lanes = ISP_CSIPHY2_NUM_DATA_LANES; +	phy2->cfg_regs = OMAP3_ISP_IOMEM_CSI2A_REGS1; +	phy2->phy_regs = OMAP3_ISP_IOMEM_CSIPHY2; +	mutex_init(&phy2->mutex); + +	if (isp->revision == ISP_REVISION_15_0) { +		phy1->isp = isp; +		phy1->csi2 = &isp->isp_csi2c; +		phy1->num_data_lanes = ISP_CSIPHY1_NUM_DATA_LANES; +		phy1->cfg_regs = OMAP3_ISP_IOMEM_CSI2C_REGS1; +		phy1->phy_regs = OMAP3_ISP_IOMEM_CSIPHY1; +		mutex_init(&phy1->mutex); +	} + +	return 0; +} diff --git a/drivers/media/platform/omap3isp/ispcsiphy.h b/drivers/media/platform/omap3isp/ispcsiphy.h new file mode 100644 index 00000000000..14551fd7769 --- /dev/null +++ b/drivers/media/platform/omap3isp/ispcsiphy.h @@ -0,0 +1,53 @@ +/* + * ispcsiphy.h + * + * TI OMAP3 ISP - CSI PHY module + * + * Copyright (C) 2010 Nokia Corporation + * Copyright (C) 2009 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 + */ + +#ifndef OMAP3_ISP_CSI_PHY_H +#define OMAP3_ISP_CSI_PHY_H + +#include <media/omap3isp.h> + +struct isp_csi2_device; +struct regulator; + +struct isp_csiphy { +	struct isp_device *isp; +	struct mutex mutex;	/* serialize csiphy configuration */ +	u8 phy_in_use; +	struct isp_csi2_device *csi2; +	struct regulator *vdd; + +	/* mem resources - enums as defined in enum isp_mem_resources */ +	unsigned int cfg_regs; +	unsigned int phy_regs; + +	u8 num_data_lanes;	/* number of CSI2 Data Lanes supported */ +}; + +int omap3isp_csiphy_acquire(struct isp_csiphy *phy); +void omap3isp_csiphy_release(struct isp_csiphy *phy); +int omap3isp_csiphy_init(struct isp_device *isp); + +#endif	/* OMAP3_ISP_CSI_PHY_H */ diff --git a/drivers/media/platform/omap3isp/isph3a.h b/drivers/media/platform/omap3isp/isph3a.h new file mode 100644 index 00000000000..fb09fd4ca75 --- /dev/null +++ b/drivers/media/platform/omap3isp/isph3a.h @@ -0,0 +1,117 @@ +/* + * isph3a.h + * + * TI OMAP3 ISP - H3A AF module + * + * Copyright (C) 2010 Nokia Corporation + * Copyright (C) 2009 Texas Instruments, Inc. + * + * Contacts: David Cohen <dacohen@gmail.com> + *	     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 + */ + +#ifndef OMAP3_ISP_H3A_H +#define OMAP3_ISP_H3A_H + +#include <linux/omap3isp.h> + +/* + * ---------- + * -H3A AEWB- + * ---------- + */ + +#define AEWB_PACKET_SIZE	16 +#define AEWB_SATURATION_LIMIT	0x3ff + +/* Flags for changed registers */ +#define PCR_CHNG		(1 << 0) +#define AEWWIN1_CHNG		(1 << 1) +#define AEWINSTART_CHNG		(1 << 2) +#define AEWINBLK_CHNG		(1 << 3) +#define AEWSUBWIN_CHNG		(1 << 4) +#define PRV_WBDGAIN_CHNG	(1 << 5) +#define PRV_WBGAIN_CHNG		(1 << 6) + +/* ISPH3A REGISTERS bits */ +#define ISPH3A_PCR_AF_EN	(1 << 0) +#define ISPH3A_PCR_AF_ALAW_EN	(1 << 1) +#define ISPH3A_PCR_AF_MED_EN	(1 << 2) +#define ISPH3A_PCR_AF_BUSY	(1 << 15) +#define ISPH3A_PCR_AEW_EN	(1 << 16) +#define ISPH3A_PCR_AEW_ALAW_EN	(1 << 17) +#define ISPH3A_PCR_AEW_BUSY	(1 << 18) +#define ISPH3A_PCR_AEW_MASK	(ISPH3A_PCR_AEW_ALAW_EN | \ +				 ISPH3A_PCR_AEW_AVE2LMT_MASK) + +/* + * -------- + * -H3A AF- + * -------- + */ + +/* Peripheral Revision */ +#define AFPID				0x0 + +#define AFCOEF_OFFSET			0x00000004	/* COEF base address */ + +/* PCR fields */ +#define AF_BUSYAF			(1 << 15) +#define AF_FVMODE			(1 << 14) +#define AF_RGBPOS			(0x7 << 11) +#define AF_MED_TH			(0xFF << 3) +#define AF_MED_EN			(1 << 2) +#define AF_ALAW_EN			(1 << 1) +#define AF_EN				(1 << 0) +#define AF_PCR_MASK			(AF_FVMODE | AF_RGBPOS | AF_MED_TH | \ +					 AF_MED_EN | AF_ALAW_EN) + +/* AFPAX1 fields */ +#define AF_PAXW				(0x7F << 16) +#define AF_PAXH				0x7F + +/* AFPAX2 fields */ +#define AF_AFINCV			(0xF << 13) +#define AF_PAXVC			(0x7F << 6) +#define AF_PAXHC			0x3F + +/* AFPAXSTART fields */ +#define AF_PAXSH			(0xFFF<<16) +#define AF_PAXSV			0xFFF + +/* COEFFICIENT MASK */ +#define AF_COEF_MASK0			0xFFF +#define AF_COEF_MASK1			(0xFFF<<16) + +/* BIT SHIFTS */ +#define AF_RGBPOS_SHIFT			11 +#define AF_MED_TH_SHIFT			3 +#define AF_PAXW_SHIFT			16 +#define AF_LINE_INCR_SHIFT		13 +#define AF_VT_COUNT_SHIFT		6 +#define AF_HZ_START_SHIFT		16 +#define AF_COEF_SHIFT			16 + +/* Init and cleanup functions */ +int omap3isp_h3a_aewb_init(struct isp_device *isp); +int omap3isp_h3a_af_init(struct isp_device *isp); + +void omap3isp_h3a_aewb_cleanup(struct isp_device *isp); +void omap3isp_h3a_af_cleanup(struct isp_device *isp); + +#endif /* OMAP3_ISP_H3A_H */ diff --git a/drivers/media/platform/omap3isp/isph3a_aewb.c b/drivers/media/platform/omap3isp/isph3a_aewb.c new file mode 100644 index 00000000000..d6811ce263e --- /dev/null +++ b/drivers/media/platform/omap3isp/isph3a_aewb.c @@ -0,0 +1,352 @@ +/* + * isph3a.c + * + * TI OMAP3 ISP - H3A module + * + * Copyright (C) 2010 Nokia Corporation + * Copyright (C) 2009 Texas Instruments, Inc. + * + * Contacts: David Cohen <dacohen@gmail.com> + *	     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/slab.h> +#include <linux/uaccess.h> + +#include "isp.h" +#include "isph3a.h" +#include "ispstat.h" + +/* + * h3a_aewb_update_regs - Helper function to update h3a registers. + */ +static void h3a_aewb_setup_regs(struct ispstat *aewb, void *priv) +{ +	struct omap3isp_h3a_aewb_config *conf = priv; +	u32 pcr; +	u32 win1; +	u32 start; +	u32 blk; +	u32 subwin; + +	if (aewb->state == ISPSTAT_DISABLED) +		return; + +	isp_reg_writel(aewb->isp, aewb->active_buf->dma_addr, +		       OMAP3_ISP_IOMEM_H3A, ISPH3A_AEWBUFST); + +	if (!aewb->update) +		return; + +	/* Converting config metadata into reg values */ +	pcr = conf->saturation_limit << ISPH3A_PCR_AEW_AVE2LMT_SHIFT; +	pcr |= !!conf->alaw_enable << ISPH3A_PCR_AEW_ALAW_EN_SHIFT; + +	win1 = ((conf->win_height >> 1) - 1) << ISPH3A_AEWWIN1_WINH_SHIFT; +	win1 |= ((conf->win_width >> 1) - 1) << ISPH3A_AEWWIN1_WINW_SHIFT; +	win1 |= (conf->ver_win_count - 1) << ISPH3A_AEWWIN1_WINVC_SHIFT; +	win1 |= (conf->hor_win_count - 1) << ISPH3A_AEWWIN1_WINHC_SHIFT; + +	start = conf->hor_win_start << ISPH3A_AEWINSTART_WINSH_SHIFT; +	start |= conf->ver_win_start << ISPH3A_AEWINSTART_WINSV_SHIFT; + +	blk = conf->blk_ver_win_start << ISPH3A_AEWINBLK_WINSV_SHIFT; +	blk |= ((conf->blk_win_height >> 1) - 1) << ISPH3A_AEWINBLK_WINH_SHIFT; + +	subwin = ((conf->subsample_ver_inc >> 1) - 1) << +		 ISPH3A_AEWSUBWIN_AEWINCV_SHIFT; +	subwin |= ((conf->subsample_hor_inc >> 1) - 1) << +		  ISPH3A_AEWSUBWIN_AEWINCH_SHIFT; + +	isp_reg_writel(aewb->isp, win1, OMAP3_ISP_IOMEM_H3A, ISPH3A_AEWWIN1); +	isp_reg_writel(aewb->isp, start, OMAP3_ISP_IOMEM_H3A, +		       ISPH3A_AEWINSTART); +	isp_reg_writel(aewb->isp, blk, OMAP3_ISP_IOMEM_H3A, ISPH3A_AEWINBLK); +	isp_reg_writel(aewb->isp, subwin, OMAP3_ISP_IOMEM_H3A, +		       ISPH3A_AEWSUBWIN); +	isp_reg_clr_set(aewb->isp, OMAP3_ISP_IOMEM_H3A, ISPH3A_PCR, +			ISPH3A_PCR_AEW_MASK, pcr); + +	aewb->update = 0; +	aewb->config_counter += aewb->inc_config; +	aewb->inc_config = 0; +	aewb->buf_size = conf->buf_size; +} + +static void h3a_aewb_enable(struct ispstat *aewb, int enable) +{ +	if (enable) { +		isp_reg_set(aewb->isp, OMAP3_ISP_IOMEM_H3A, ISPH3A_PCR, +			    ISPH3A_PCR_AEW_EN); +		omap3isp_subclk_enable(aewb->isp, OMAP3_ISP_SUBCLK_AEWB); +	} else { +		isp_reg_clr(aewb->isp, OMAP3_ISP_IOMEM_H3A, ISPH3A_PCR, +			    ISPH3A_PCR_AEW_EN); +		omap3isp_subclk_disable(aewb->isp, OMAP3_ISP_SUBCLK_AEWB); +	} +} + +static int h3a_aewb_busy(struct ispstat *aewb) +{ +	return isp_reg_readl(aewb->isp, OMAP3_ISP_IOMEM_H3A, ISPH3A_PCR) +						& ISPH3A_PCR_BUSYAEAWB; +} + +static u32 h3a_aewb_get_buf_size(struct omap3isp_h3a_aewb_config *conf) +{ +	/* Number of configured windows + extra row for black data */ +	u32 win_count = (conf->ver_win_count + 1) * conf->hor_win_count; + +	/* +	 * Unsaturated block counts for each 8 windows. +	 * 1 extra for the last (win_count % 8) windows if win_count is not +	 * divisible by 8. +	 */ +	win_count += (win_count + 7) / 8; + +	return win_count * AEWB_PACKET_SIZE; +} + +static int h3a_aewb_validate_params(struct ispstat *aewb, void *new_conf) +{ +	struct omap3isp_h3a_aewb_config *user_cfg = new_conf; +	u32 buf_size; + +	if (unlikely(user_cfg->saturation_limit > +		     OMAP3ISP_AEWB_MAX_SATURATION_LIM)) +		return -EINVAL; + +	if (unlikely(user_cfg->win_height < OMAP3ISP_AEWB_MIN_WIN_H || +		     user_cfg->win_height > OMAP3ISP_AEWB_MAX_WIN_H || +		     user_cfg->win_height & 0x01)) +		return -EINVAL; + +	if (unlikely(user_cfg->win_width < OMAP3ISP_AEWB_MIN_WIN_W || +		     user_cfg->win_width > OMAP3ISP_AEWB_MAX_WIN_W || +		     user_cfg->win_width & 0x01)) +		return -EINVAL; + +	if (unlikely(user_cfg->ver_win_count < OMAP3ISP_AEWB_MIN_WINVC || +		     user_cfg->ver_win_count > OMAP3ISP_AEWB_MAX_WINVC)) +		return -EINVAL; + +	if (unlikely(user_cfg->hor_win_count < OMAP3ISP_AEWB_MIN_WINHC || +		     user_cfg->hor_win_count > OMAP3ISP_AEWB_MAX_WINHC)) +		return -EINVAL; + +	if (unlikely(user_cfg->ver_win_start > OMAP3ISP_AEWB_MAX_WINSTART)) +		return -EINVAL; + +	if (unlikely(user_cfg->hor_win_start > OMAP3ISP_AEWB_MAX_WINSTART)) +		return -EINVAL; + +	if (unlikely(user_cfg->blk_ver_win_start > OMAP3ISP_AEWB_MAX_WINSTART)) +		return -EINVAL; + +	if (unlikely(user_cfg->blk_win_height < OMAP3ISP_AEWB_MIN_WIN_H || +		     user_cfg->blk_win_height > OMAP3ISP_AEWB_MAX_WIN_H || +		     user_cfg->blk_win_height & 0x01)) +		return -EINVAL; + +	if (unlikely(user_cfg->subsample_ver_inc < OMAP3ISP_AEWB_MIN_SUB_INC || +		     user_cfg->subsample_ver_inc > OMAP3ISP_AEWB_MAX_SUB_INC || +		     user_cfg->subsample_ver_inc & 0x01)) +		return -EINVAL; + +	if (unlikely(user_cfg->subsample_hor_inc < OMAP3ISP_AEWB_MIN_SUB_INC || +		     user_cfg->subsample_hor_inc > OMAP3ISP_AEWB_MAX_SUB_INC || +		     user_cfg->subsample_hor_inc & 0x01)) +		return -EINVAL; + +	buf_size = h3a_aewb_get_buf_size(user_cfg); +	if (buf_size > user_cfg->buf_size) +		user_cfg->buf_size = buf_size; +	else if (user_cfg->buf_size > OMAP3ISP_AEWB_MAX_BUF_SIZE) +		user_cfg->buf_size = OMAP3ISP_AEWB_MAX_BUF_SIZE; + +	return 0; +} + +/* + * h3a_aewb_set_params - Helper function to check & store user given params. + * @new_conf: Pointer to AE and AWB parameters struct. + * + * As most of them are busy-lock registers, need to wait until AEW_BUSY = 0 to + * program them during ISR. + */ +static void h3a_aewb_set_params(struct ispstat *aewb, void *new_conf) +{ +	struct omap3isp_h3a_aewb_config *user_cfg = new_conf; +	struct omap3isp_h3a_aewb_config *cur_cfg = aewb->priv; +	int update = 0; + +	if (cur_cfg->saturation_limit != user_cfg->saturation_limit) { +		cur_cfg->saturation_limit = user_cfg->saturation_limit; +		update = 1; +	} +	if (cur_cfg->alaw_enable != user_cfg->alaw_enable) { +		cur_cfg->alaw_enable = user_cfg->alaw_enable; +		update = 1; +	} +	if (cur_cfg->win_height != user_cfg->win_height) { +		cur_cfg->win_height = user_cfg->win_height; +		update = 1; +	} +	if (cur_cfg->win_width != user_cfg->win_width) { +		cur_cfg->win_width = user_cfg->win_width; +		update = 1; +	} +	if (cur_cfg->ver_win_count != user_cfg->ver_win_count) { +		cur_cfg->ver_win_count = user_cfg->ver_win_count; +		update = 1; +	} +	if (cur_cfg->hor_win_count != user_cfg->hor_win_count) { +		cur_cfg->hor_win_count = user_cfg->hor_win_count; +		update = 1; +	} +	if (cur_cfg->ver_win_start != user_cfg->ver_win_start) { +		cur_cfg->ver_win_start = user_cfg->ver_win_start; +		update = 1; +	} +	if (cur_cfg->hor_win_start != user_cfg->hor_win_start) { +		cur_cfg->hor_win_start = user_cfg->hor_win_start; +		update = 1; +	} +	if (cur_cfg->blk_ver_win_start != user_cfg->blk_ver_win_start) { +		cur_cfg->blk_ver_win_start = user_cfg->blk_ver_win_start; +		update = 1; +	} +	if (cur_cfg->blk_win_height != user_cfg->blk_win_height) { +		cur_cfg->blk_win_height = user_cfg->blk_win_height; +		update = 1; +	} +	if (cur_cfg->subsample_ver_inc != user_cfg->subsample_ver_inc) { +		cur_cfg->subsample_ver_inc = user_cfg->subsample_ver_inc; +		update = 1; +	} +	if (cur_cfg->subsample_hor_inc != user_cfg->subsample_hor_inc) { +		cur_cfg->subsample_hor_inc = user_cfg->subsample_hor_inc; +		update = 1; +	} + +	if (update || !aewb->configured) { +		aewb->inc_config++; +		aewb->update = 1; +		cur_cfg->buf_size = h3a_aewb_get_buf_size(cur_cfg); +	} +} + +static long h3a_aewb_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg) +{ +	struct ispstat *stat = v4l2_get_subdevdata(sd); + +	switch (cmd) { +	case VIDIOC_OMAP3ISP_AEWB_CFG: +		return omap3isp_stat_config(stat, arg); +	case VIDIOC_OMAP3ISP_STAT_REQ: +		return omap3isp_stat_request_statistics(stat, arg); +	case VIDIOC_OMAP3ISP_STAT_EN: { +		unsigned long *en = arg; +		return omap3isp_stat_enable(stat, !!*en); +	} +	} + +	return -ENOIOCTLCMD; +} + +static const struct ispstat_ops h3a_aewb_ops = { +	.validate_params	= h3a_aewb_validate_params, +	.set_params		= h3a_aewb_set_params, +	.setup_regs		= h3a_aewb_setup_regs, +	.enable			= h3a_aewb_enable, +	.busy			= h3a_aewb_busy, +}; + +static const struct v4l2_subdev_core_ops h3a_aewb_subdev_core_ops = { +	.ioctl = h3a_aewb_ioctl, +	.subscribe_event = omap3isp_stat_subscribe_event, +	.unsubscribe_event = omap3isp_stat_unsubscribe_event, +}; + +static const struct v4l2_subdev_video_ops h3a_aewb_subdev_video_ops = { +	.s_stream = omap3isp_stat_s_stream, +}; + +static const struct v4l2_subdev_ops h3a_aewb_subdev_ops = { +	.core = &h3a_aewb_subdev_core_ops, +	.video = &h3a_aewb_subdev_video_ops, +}; + +/* + * omap3isp_h3a_aewb_init - Module Initialisation. + */ +int omap3isp_h3a_aewb_init(struct isp_device *isp) +{ +	struct ispstat *aewb = &isp->isp_aewb; +	struct omap3isp_h3a_aewb_config *aewb_cfg; +	struct omap3isp_h3a_aewb_config *aewb_recover_cfg; + +	aewb_cfg = devm_kzalloc(isp->dev, sizeof(*aewb_cfg), GFP_KERNEL); +	if (!aewb_cfg) +		return -ENOMEM; + +	aewb->ops = &h3a_aewb_ops; +	aewb->priv = aewb_cfg; +	aewb->dma_ch = -1; +	aewb->event_type = V4L2_EVENT_OMAP3ISP_AEWB; +	aewb->isp = isp; + +	/* Set recover state configuration */ +	aewb_recover_cfg = devm_kzalloc(isp->dev, sizeof(*aewb_recover_cfg), +					GFP_KERNEL); +	if (!aewb_recover_cfg) { +		dev_err(aewb->isp->dev, "AEWB: cannot allocate memory for " +					"recover configuration.\n"); +		return -ENOMEM; +	} + +	aewb_recover_cfg->saturation_limit = OMAP3ISP_AEWB_MAX_SATURATION_LIM; +	aewb_recover_cfg->win_height = OMAP3ISP_AEWB_MIN_WIN_H; +	aewb_recover_cfg->win_width = OMAP3ISP_AEWB_MIN_WIN_W; +	aewb_recover_cfg->ver_win_count = OMAP3ISP_AEWB_MIN_WINVC; +	aewb_recover_cfg->hor_win_count = OMAP3ISP_AEWB_MIN_WINHC; +	aewb_recover_cfg->blk_ver_win_start = aewb_recover_cfg->ver_win_start + +		aewb_recover_cfg->win_height * aewb_recover_cfg->ver_win_count; +	aewb_recover_cfg->blk_win_height = OMAP3ISP_AEWB_MIN_WIN_H; +	aewb_recover_cfg->subsample_ver_inc = OMAP3ISP_AEWB_MIN_SUB_INC; +	aewb_recover_cfg->subsample_hor_inc = OMAP3ISP_AEWB_MIN_SUB_INC; + +	if (h3a_aewb_validate_params(aewb, aewb_recover_cfg)) { +		dev_err(aewb->isp->dev, "AEWB: recover configuration is " +					"invalid.\n"); +		return -EINVAL; +	} + +	aewb_recover_cfg->buf_size = h3a_aewb_get_buf_size(aewb_recover_cfg); +	aewb->recover_priv = aewb_recover_cfg; + +	return omap3isp_stat_init(aewb, "AEWB", &h3a_aewb_subdev_ops); +} + +/* + * omap3isp_h3a_aewb_cleanup - Module exit. + */ +void omap3isp_h3a_aewb_cleanup(struct isp_device *isp) +{ +	omap3isp_stat_cleanup(&isp->isp_aewb); +} diff --git a/drivers/media/platform/omap3isp/isph3a_af.c b/drivers/media/platform/omap3isp/isph3a_af.c new file mode 100644 index 00000000000..6fc960cd30f --- /dev/null +++ b/drivers/media/platform/omap3isp/isph3a_af.c @@ -0,0 +1,407 @@ +/* + * isph3a_af.c + * + * TI OMAP3 ISP - H3A AF module + * + * Copyright (C) 2010 Nokia Corporation + * Copyright (C) 2009 Texas Instruments, Inc. + * + * Contacts: David Cohen <dacohen@gmail.com> + *	     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 + */ + +/* Linux specific include files */ +#include <linux/device.h> +#include <linux/slab.h> + +#include "isp.h" +#include "isph3a.h" +#include "ispstat.h" + +#define IS_OUT_OF_BOUNDS(value, min, max)		\ +	(((value) < (min)) || ((value) > (max))) + +static void h3a_af_setup_regs(struct ispstat *af, void *priv) +{ +	struct omap3isp_h3a_af_config *conf = priv; +	u32 pcr; +	u32 pax1; +	u32 pax2; +	u32 paxstart; +	u32 coef; +	u32 base_coef_set0; +	u32 base_coef_set1; +	int index; + +	if (af->state == ISPSTAT_DISABLED) +		return; + +	isp_reg_writel(af->isp, af->active_buf->dma_addr, OMAP3_ISP_IOMEM_H3A, +		       ISPH3A_AFBUFST); + +	if (!af->update) +		return; + +	/* Configure Hardware Registers */ +	pax1 = ((conf->paxel.width >> 1) - 1) << AF_PAXW_SHIFT; +	/* Set height in AFPAX1 */ +	pax1 |= (conf->paxel.height >> 1) - 1; +	isp_reg_writel(af->isp, pax1, OMAP3_ISP_IOMEM_H3A, ISPH3A_AFPAX1); + +	/* Configure AFPAX2 Register */ +	/* Set Line Increment in AFPAX2 Register */ +	pax2 = ((conf->paxel.line_inc >> 1) - 1) << AF_LINE_INCR_SHIFT; +	/* Set Vertical Count */ +	pax2 |= (conf->paxel.v_cnt - 1) << AF_VT_COUNT_SHIFT; +	/* Set Horizontal Count */ +	pax2 |= (conf->paxel.h_cnt - 1); +	isp_reg_writel(af->isp, pax2, OMAP3_ISP_IOMEM_H3A, ISPH3A_AFPAX2); + +	/* Configure PAXSTART Register */ +	/*Configure Horizontal Start */ +	paxstart = conf->paxel.h_start << AF_HZ_START_SHIFT; +	/* Configure Vertical Start */ +	paxstart |= conf->paxel.v_start; +	isp_reg_writel(af->isp, paxstart, OMAP3_ISP_IOMEM_H3A, +		       ISPH3A_AFPAXSTART); + +	/*SetIIRSH Register */ +	isp_reg_writel(af->isp, conf->iir.h_start, +		       OMAP3_ISP_IOMEM_H3A, ISPH3A_AFIIRSH); + +	base_coef_set0 = ISPH3A_AFCOEF010; +	base_coef_set1 = ISPH3A_AFCOEF110; +	for (index = 0; index <= 8; index += 2) { +		/*Set IIR Filter0 Coefficients */ +		coef = 0; +		coef |= conf->iir.coeff_set0[index]; +		coef |= conf->iir.coeff_set0[index + 1] << +			AF_COEF_SHIFT; +		isp_reg_writel(af->isp, coef, OMAP3_ISP_IOMEM_H3A, +			       base_coef_set0); +		base_coef_set0 += AFCOEF_OFFSET; + +		/*Set IIR Filter1 Coefficients */ +		coef = 0; +		coef |= conf->iir.coeff_set1[index]; +		coef |= conf->iir.coeff_set1[index + 1] << +			AF_COEF_SHIFT; +		isp_reg_writel(af->isp, coef, OMAP3_ISP_IOMEM_H3A, +			       base_coef_set1); +		base_coef_set1 += AFCOEF_OFFSET; +	} +	/* set AFCOEF0010 Register */ +	isp_reg_writel(af->isp, conf->iir.coeff_set0[10], +		       OMAP3_ISP_IOMEM_H3A, ISPH3A_AFCOEF0010); +	/* set AFCOEF1010 Register */ +	isp_reg_writel(af->isp, conf->iir.coeff_set1[10], +		       OMAP3_ISP_IOMEM_H3A, ISPH3A_AFCOEF1010); + +	/* PCR Register */ +	/* Set RGB Position */ +	pcr = conf->rgb_pos << AF_RGBPOS_SHIFT; +	/* Set Accumulator Mode */ +	if (conf->fvmode == OMAP3ISP_AF_MODE_PEAK) +		pcr |= AF_FVMODE; +	/* Set A-law */ +	if (conf->alaw_enable) +		pcr |= AF_ALAW_EN; +	/* HMF Configurations */ +	if (conf->hmf.enable) { +		/* Enable HMF */ +		pcr |= AF_MED_EN; +		/* Set Median Threshold */ +		pcr |= conf->hmf.threshold << AF_MED_TH_SHIFT; +	} +	/* Set PCR Register */ +	isp_reg_clr_set(af->isp, OMAP3_ISP_IOMEM_H3A, ISPH3A_PCR, +			AF_PCR_MASK, pcr); + +	af->update = 0; +	af->config_counter += af->inc_config; +	af->inc_config = 0; +	af->buf_size = conf->buf_size; +} + +static void h3a_af_enable(struct ispstat *af, int enable) +{ +	if (enable) { +		isp_reg_set(af->isp, OMAP3_ISP_IOMEM_H3A, ISPH3A_PCR, +			    ISPH3A_PCR_AF_EN); +		omap3isp_subclk_enable(af->isp, OMAP3_ISP_SUBCLK_AF); +	} else { +		isp_reg_clr(af->isp, OMAP3_ISP_IOMEM_H3A, ISPH3A_PCR, +			    ISPH3A_PCR_AF_EN); +		omap3isp_subclk_disable(af->isp, OMAP3_ISP_SUBCLK_AF); +	} +} + +static int h3a_af_busy(struct ispstat *af) +{ +	return isp_reg_readl(af->isp, OMAP3_ISP_IOMEM_H3A, ISPH3A_PCR) +						& ISPH3A_PCR_BUSYAF; +} + +static u32 h3a_af_get_buf_size(struct omap3isp_h3a_af_config *conf) +{ +	return conf->paxel.h_cnt * conf->paxel.v_cnt * OMAP3ISP_AF_PAXEL_SIZE; +} + +/* Function to check paxel parameters */ +static int h3a_af_validate_params(struct ispstat *af, void *new_conf) +{ +	struct omap3isp_h3a_af_config *user_cfg = new_conf; +	struct omap3isp_h3a_af_paxel *paxel_cfg = &user_cfg->paxel; +	struct omap3isp_h3a_af_iir *iir_cfg = &user_cfg->iir; +	int index; +	u32 buf_size; + +	/* Check horizontal Count */ +	if (IS_OUT_OF_BOUNDS(paxel_cfg->h_cnt, +			     OMAP3ISP_AF_PAXEL_HORIZONTAL_COUNT_MIN, +			     OMAP3ISP_AF_PAXEL_HORIZONTAL_COUNT_MAX)) +		return -EINVAL; + +	/* Check Vertical Count */ +	if (IS_OUT_OF_BOUNDS(paxel_cfg->v_cnt, +			     OMAP3ISP_AF_PAXEL_VERTICAL_COUNT_MIN, +			     OMAP3ISP_AF_PAXEL_VERTICAL_COUNT_MAX)) +		return -EINVAL; + +	if (IS_OUT_OF_BOUNDS(paxel_cfg->height, OMAP3ISP_AF_PAXEL_HEIGHT_MIN, +			     OMAP3ISP_AF_PAXEL_HEIGHT_MAX) || +	    paxel_cfg->height % 2) +		return -EINVAL; + +	/* Check width */ +	if (IS_OUT_OF_BOUNDS(paxel_cfg->width, OMAP3ISP_AF_PAXEL_WIDTH_MIN, +			     OMAP3ISP_AF_PAXEL_WIDTH_MAX) || +	    paxel_cfg->width % 2) +		return -EINVAL; + +	/* Check Line Increment */ +	if (IS_OUT_OF_BOUNDS(paxel_cfg->line_inc, +			     OMAP3ISP_AF_PAXEL_INCREMENT_MIN, +			     OMAP3ISP_AF_PAXEL_INCREMENT_MAX) || +	    paxel_cfg->line_inc % 2) +		return -EINVAL; + +	/* Check Horizontal Start */ +	if ((paxel_cfg->h_start < iir_cfg->h_start) || +	    IS_OUT_OF_BOUNDS(paxel_cfg->h_start, +			     OMAP3ISP_AF_PAXEL_HZSTART_MIN, +			     OMAP3ISP_AF_PAXEL_HZSTART_MAX)) +		return -EINVAL; + +	/* Check IIR */ +	for (index = 0; index < OMAP3ISP_AF_NUM_COEF; index++) { +		if ((iir_cfg->coeff_set0[index]) > OMAP3ISP_AF_COEF_MAX) +			return -EINVAL; + +		if ((iir_cfg->coeff_set1[index]) > OMAP3ISP_AF_COEF_MAX) +			return -EINVAL; +	} + +	if (IS_OUT_OF_BOUNDS(iir_cfg->h_start, OMAP3ISP_AF_IIRSH_MIN, +			     OMAP3ISP_AF_IIRSH_MAX)) +		return -EINVAL; + +	/* Hack: If paxel size is 12, the 10th AF window may be corrupted */ +	if ((paxel_cfg->h_cnt * paxel_cfg->v_cnt > 9) && +	    (paxel_cfg->width * paxel_cfg->height == 12)) +		return -EINVAL; + +	buf_size = h3a_af_get_buf_size(user_cfg); +	if (buf_size > user_cfg->buf_size) +		/* User buf_size request wasn't enough */ +		user_cfg->buf_size = buf_size; +	else if (user_cfg->buf_size > OMAP3ISP_AF_MAX_BUF_SIZE) +		user_cfg->buf_size = OMAP3ISP_AF_MAX_BUF_SIZE; + +	return 0; +} + +/* Update local parameters */ +static void h3a_af_set_params(struct ispstat *af, void *new_conf) +{ +	struct omap3isp_h3a_af_config *user_cfg = new_conf; +	struct omap3isp_h3a_af_config *cur_cfg = af->priv; +	int update = 0; +	int index; + +	/* alaw */ +	if (cur_cfg->alaw_enable != user_cfg->alaw_enable) { +		update = 1; +		goto out; +	} + +	/* hmf */ +	if (cur_cfg->hmf.enable != user_cfg->hmf.enable) { +		update = 1; +		goto out; +	} +	if (cur_cfg->hmf.threshold != user_cfg->hmf.threshold) { +		update = 1; +		goto out; +	} + +	/* rgbpos */ +	if (cur_cfg->rgb_pos != user_cfg->rgb_pos) { +		update = 1; +		goto out; +	} + +	/* iir */ +	if (cur_cfg->iir.h_start != user_cfg->iir.h_start) { +		update = 1; +		goto out; +	} +	for (index = 0; index < OMAP3ISP_AF_NUM_COEF; index++) { +		if (cur_cfg->iir.coeff_set0[index] != +				user_cfg->iir.coeff_set0[index]) { +			update = 1; +			goto out; +		} +		if (cur_cfg->iir.coeff_set1[index] != +				user_cfg->iir.coeff_set1[index]) { +			update = 1; +			goto out; +		} +	} + +	/* paxel */ +	if ((cur_cfg->paxel.width != user_cfg->paxel.width) || +	    (cur_cfg->paxel.height != user_cfg->paxel.height) || +	    (cur_cfg->paxel.h_start != user_cfg->paxel.h_start) || +	    (cur_cfg->paxel.v_start != user_cfg->paxel.v_start) || +	    (cur_cfg->paxel.h_cnt != user_cfg->paxel.h_cnt) || +	    (cur_cfg->paxel.v_cnt != user_cfg->paxel.v_cnt) || +	    (cur_cfg->paxel.line_inc != user_cfg->paxel.line_inc)) { +		update = 1; +		goto out; +	} + +	/* af_mode */ +	if (cur_cfg->fvmode != user_cfg->fvmode) +		update = 1; + +out: +	if (update || !af->configured) { +		memcpy(cur_cfg, user_cfg, sizeof(*cur_cfg)); +		af->inc_config++; +		af->update = 1; +		/* +		 * User might be asked for a bigger buffer than necessary for +		 * this configuration. In order to return the right amount of +		 * data during buffer request, let's calculate the size here +		 * instead of stick with user_cfg->buf_size. +		 */ +		cur_cfg->buf_size = h3a_af_get_buf_size(cur_cfg); +	} +} + +static long h3a_af_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg) +{ +	struct ispstat *stat = v4l2_get_subdevdata(sd); + +	switch (cmd) { +	case VIDIOC_OMAP3ISP_AF_CFG: +		return omap3isp_stat_config(stat, arg); +	case VIDIOC_OMAP3ISP_STAT_REQ: +		return omap3isp_stat_request_statistics(stat, arg); +	case VIDIOC_OMAP3ISP_STAT_EN: { +		int *en = arg; +		return omap3isp_stat_enable(stat, !!*en); +	} +	} + +	return -ENOIOCTLCMD; + +} + +static const struct ispstat_ops h3a_af_ops = { +	.validate_params	= h3a_af_validate_params, +	.set_params		= h3a_af_set_params, +	.setup_regs		= h3a_af_setup_regs, +	.enable			= h3a_af_enable, +	.busy			= h3a_af_busy, +}; + +static const struct v4l2_subdev_core_ops h3a_af_subdev_core_ops = { +	.ioctl = h3a_af_ioctl, +	.subscribe_event = omap3isp_stat_subscribe_event, +	.unsubscribe_event = omap3isp_stat_unsubscribe_event, +}; + +static const struct v4l2_subdev_video_ops h3a_af_subdev_video_ops = { +	.s_stream = omap3isp_stat_s_stream, +}; + +static const struct v4l2_subdev_ops h3a_af_subdev_ops = { +	.core = &h3a_af_subdev_core_ops, +	.video = &h3a_af_subdev_video_ops, +}; + +/* Function to register the AF character device driver. */ +int omap3isp_h3a_af_init(struct isp_device *isp) +{ +	struct ispstat *af = &isp->isp_af; +	struct omap3isp_h3a_af_config *af_cfg; +	struct omap3isp_h3a_af_config *af_recover_cfg; + +	af_cfg = devm_kzalloc(isp->dev, sizeof(*af_cfg), GFP_KERNEL); +	if (af_cfg == NULL) +		return -ENOMEM; + +	af->ops = &h3a_af_ops; +	af->priv = af_cfg; +	af->dma_ch = -1; +	af->event_type = V4L2_EVENT_OMAP3ISP_AF; +	af->isp = isp; + +	/* Set recover state configuration */ +	af_recover_cfg = devm_kzalloc(isp->dev, sizeof(*af_recover_cfg), +				      GFP_KERNEL); +	if (!af_recover_cfg) { +		dev_err(af->isp->dev, "AF: cannot allocate memory for recover " +				      "configuration.\n"); +		return -ENOMEM; +	} + +	af_recover_cfg->paxel.h_start = OMAP3ISP_AF_PAXEL_HZSTART_MIN; +	af_recover_cfg->paxel.width = OMAP3ISP_AF_PAXEL_WIDTH_MIN; +	af_recover_cfg->paxel.height = OMAP3ISP_AF_PAXEL_HEIGHT_MIN; +	af_recover_cfg->paxel.h_cnt = OMAP3ISP_AF_PAXEL_HORIZONTAL_COUNT_MIN; +	af_recover_cfg->paxel.v_cnt = OMAP3ISP_AF_PAXEL_VERTICAL_COUNT_MIN; +	af_recover_cfg->paxel.line_inc = OMAP3ISP_AF_PAXEL_INCREMENT_MIN; +	if (h3a_af_validate_params(af, af_recover_cfg)) { +		dev_err(af->isp->dev, "AF: recover configuration is " +				      "invalid.\n"); +		return -EINVAL; +	} + +	af_recover_cfg->buf_size = h3a_af_get_buf_size(af_recover_cfg); +	af->recover_priv = af_recover_cfg; + +	return omap3isp_stat_init(af, "AF", &h3a_af_subdev_ops); +} + +void omap3isp_h3a_af_cleanup(struct isp_device *isp) +{ +	omap3isp_stat_cleanup(&isp->isp_af); +} diff --git a/drivers/media/platform/omap3isp/isphist.c b/drivers/media/platform/omap3isp/isphist.c new file mode 100644 index 00000000000..06a5f8164ea --- /dev/null +++ b/drivers/media/platform/omap3isp/isphist.c @@ -0,0 +1,521 @@ +/* + * isphist.c + * + * TI OMAP3 ISP - Histogram module + * + * Copyright (C) 2010 Nokia Corporation + * Copyright (C) 2009 Texas Instruments, Inc. + * + * Contacts: David Cohen <dacohen@gmail.com> + *	     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/slab.h> +#include <linux/uaccess.h> +#include <linux/device.h> + +#include "isp.h" +#include "ispreg.h" +#include "isphist.h" + +#define OMAP24XX_DMA_NO_DEVICE		0 + +#define HIST_CONFIG_DMA	1 + +#define HIST_USING_DMA(hist) ((hist)->dma_ch >= 0) + +/* + * hist_reset_mem - clear Histogram memory before start stats engine. + */ +static void hist_reset_mem(struct ispstat *hist) +{ +	struct isp_device *isp = hist->isp; +	struct omap3isp_hist_config *conf = hist->priv; +	unsigned int i; + +	isp_reg_writel(isp, 0, OMAP3_ISP_IOMEM_HIST, ISPHIST_ADDR); + +	/* +	 * By setting it, the histogram internal buffer is being cleared at the +	 * same time it's being read. This bit must be cleared afterwards. +	 */ +	isp_reg_set(isp, OMAP3_ISP_IOMEM_HIST, ISPHIST_CNT, ISPHIST_CNT_CLEAR); + +	/* +	 * We'll clear 4 words at each iteration for optimization. It avoids +	 * 3/4 of the jumps. We also know HIST_MEM_SIZE is divisible by 4. +	 */ +	for (i = OMAP3ISP_HIST_MEM_SIZE / 4; i > 0; i--) { +		isp_reg_readl(isp, OMAP3_ISP_IOMEM_HIST, ISPHIST_DATA); +		isp_reg_readl(isp, OMAP3_ISP_IOMEM_HIST, ISPHIST_DATA); +		isp_reg_readl(isp, OMAP3_ISP_IOMEM_HIST, ISPHIST_DATA); +		isp_reg_readl(isp, OMAP3_ISP_IOMEM_HIST, ISPHIST_DATA); +	} +	isp_reg_clr(isp, OMAP3_ISP_IOMEM_HIST, ISPHIST_CNT, ISPHIST_CNT_CLEAR); + +	hist->wait_acc_frames = conf->num_acc_frames; +} + +static void hist_dma_config(struct ispstat *hist) +{ +	struct isp_device *isp = hist->isp; + +	hist->dma_config.data_type = OMAP_DMA_DATA_TYPE_S32; +	hist->dma_config.sync_mode = OMAP_DMA_SYNC_ELEMENT; +	hist->dma_config.frame_count = 1; +	hist->dma_config.src_amode = OMAP_DMA_AMODE_CONSTANT; +	hist->dma_config.src_start = isp->mmio_base_phys[OMAP3_ISP_IOMEM_HIST] +				   + ISPHIST_DATA; +	hist->dma_config.dst_amode = OMAP_DMA_AMODE_POST_INC; +	hist->dma_config.src_or_dst_synch = OMAP_DMA_SRC_SYNC; +} + +/* + * hist_setup_regs - Helper function to update Histogram registers. + */ +static void hist_setup_regs(struct ispstat *hist, void *priv) +{ +	struct isp_device *isp = hist->isp; +	struct omap3isp_hist_config *conf = priv; +	int c; +	u32 cnt; +	u32 wb_gain; +	u32 reg_hor[OMAP3ISP_HIST_MAX_REGIONS]; +	u32 reg_ver[OMAP3ISP_HIST_MAX_REGIONS]; + +	if (!hist->update || hist->state == ISPSTAT_DISABLED || +	    hist->state == ISPSTAT_DISABLING) +		return; + +	cnt = conf->cfa << ISPHIST_CNT_CFA_SHIFT; + +	wb_gain = conf->wg[0] << ISPHIST_WB_GAIN_WG00_SHIFT; +	wb_gain |= conf->wg[1] << ISPHIST_WB_GAIN_WG01_SHIFT; +	wb_gain |= conf->wg[2] << ISPHIST_WB_GAIN_WG02_SHIFT; +	if (conf->cfa == OMAP3ISP_HIST_CFA_BAYER) +		wb_gain |= conf->wg[3] << ISPHIST_WB_GAIN_WG03_SHIFT; + +	/* Regions size and position */ +	for (c = 0; c < OMAP3ISP_HIST_MAX_REGIONS; c++) { +		if (c < conf->num_regions) { +			reg_hor[c] = (conf->region[c].h_start << +				     ISPHIST_REG_START_SHIFT) +				   | (conf->region[c].h_end << +				     ISPHIST_REG_END_SHIFT); +			reg_ver[c] = (conf->region[c].v_start << +				     ISPHIST_REG_START_SHIFT) +				   | (conf->region[c].v_end << +				     ISPHIST_REG_END_SHIFT); +		} else { +			reg_hor[c] = 0; +			reg_ver[c] = 0; +		} +	} + +	cnt |= conf->hist_bins << ISPHIST_CNT_BINS_SHIFT; +	switch (conf->hist_bins) { +	case OMAP3ISP_HIST_BINS_256: +		cnt |= (ISPHIST_IN_BIT_WIDTH_CCDC - 8) << +			ISPHIST_CNT_SHIFT_SHIFT; +		break; +	case OMAP3ISP_HIST_BINS_128: +		cnt |= (ISPHIST_IN_BIT_WIDTH_CCDC - 7) << +			ISPHIST_CNT_SHIFT_SHIFT; +		break; +	case OMAP3ISP_HIST_BINS_64: +		cnt |= (ISPHIST_IN_BIT_WIDTH_CCDC - 6) << +			ISPHIST_CNT_SHIFT_SHIFT; +		break; +	default: /* OMAP3ISP_HIST_BINS_32 */ +		cnt |= (ISPHIST_IN_BIT_WIDTH_CCDC - 5) << +			ISPHIST_CNT_SHIFT_SHIFT; +		break; +	} + +	hist_reset_mem(hist); + +	isp_reg_writel(isp, cnt, OMAP3_ISP_IOMEM_HIST, ISPHIST_CNT); +	isp_reg_writel(isp, wb_gain,  OMAP3_ISP_IOMEM_HIST, ISPHIST_WB_GAIN); +	isp_reg_writel(isp, reg_hor[0], OMAP3_ISP_IOMEM_HIST, ISPHIST_R0_HORZ); +	isp_reg_writel(isp, reg_ver[0], OMAP3_ISP_IOMEM_HIST, ISPHIST_R0_VERT); +	isp_reg_writel(isp, reg_hor[1], OMAP3_ISP_IOMEM_HIST, ISPHIST_R1_HORZ); +	isp_reg_writel(isp, reg_ver[1], OMAP3_ISP_IOMEM_HIST, ISPHIST_R1_VERT); +	isp_reg_writel(isp, reg_hor[2], OMAP3_ISP_IOMEM_HIST, ISPHIST_R2_HORZ); +	isp_reg_writel(isp, reg_ver[2], OMAP3_ISP_IOMEM_HIST, ISPHIST_R2_VERT); +	isp_reg_writel(isp, reg_hor[3], OMAP3_ISP_IOMEM_HIST, ISPHIST_R3_HORZ); +	isp_reg_writel(isp, reg_ver[3], OMAP3_ISP_IOMEM_HIST, ISPHIST_R3_VERT); + +	hist->update = 0; +	hist->config_counter += hist->inc_config; +	hist->inc_config = 0; +	hist->buf_size = conf->buf_size; +} + +static void hist_enable(struct ispstat *hist, int enable) +{ +	if (enable) { +		isp_reg_set(hist->isp, OMAP3_ISP_IOMEM_HIST, ISPHIST_PCR, +			    ISPHIST_PCR_ENABLE); +		omap3isp_subclk_enable(hist->isp, OMAP3_ISP_SUBCLK_HIST); +	} else { +		isp_reg_clr(hist->isp, OMAP3_ISP_IOMEM_HIST, ISPHIST_PCR, +			    ISPHIST_PCR_ENABLE); +		omap3isp_subclk_disable(hist->isp, OMAP3_ISP_SUBCLK_HIST); +	} +} + +static int hist_busy(struct ispstat *hist) +{ +	return isp_reg_readl(hist->isp, OMAP3_ISP_IOMEM_HIST, ISPHIST_PCR) +						& ISPHIST_PCR_BUSY; +} + +static void hist_dma_cb(int lch, u16 ch_status, void *data) +{ +	struct ispstat *hist = data; + +	if (ch_status & ~OMAP_DMA_BLOCK_IRQ) { +		dev_dbg(hist->isp->dev, "hist: DMA error. status = 0x%04x\n", +			ch_status); +		omap_stop_dma(lch); +		hist_reset_mem(hist); +		atomic_set(&hist->buf_err, 1); +	} +	isp_reg_clr(hist->isp, OMAP3_ISP_IOMEM_HIST, ISPHIST_CNT, +		    ISPHIST_CNT_CLEAR); + +	omap3isp_stat_dma_isr(hist); +	if (hist->state != ISPSTAT_DISABLED) +		omap3isp_hist_dma_done(hist->isp); +} + +static int hist_buf_dma(struct ispstat *hist) +{ +	dma_addr_t dma_addr = hist->active_buf->dma_addr; + +	if (unlikely(!dma_addr)) { +		dev_dbg(hist->isp->dev, "hist: invalid DMA buffer address\n"); +		hist_reset_mem(hist); +		return STAT_NO_BUF; +	} + +	isp_reg_writel(hist->isp, 0, OMAP3_ISP_IOMEM_HIST, ISPHIST_ADDR); +	isp_reg_set(hist->isp, OMAP3_ISP_IOMEM_HIST, ISPHIST_CNT, +		    ISPHIST_CNT_CLEAR); +	omap3isp_flush(hist->isp); +	hist->dma_config.dst_start = dma_addr; +	hist->dma_config.elem_count = hist->buf_size / sizeof(u32); +	omap_set_dma_params(hist->dma_ch, &hist->dma_config); + +	omap_start_dma(hist->dma_ch); + +	return STAT_BUF_WAITING_DMA; +} + +static int hist_buf_pio(struct ispstat *hist) +{ +	struct isp_device *isp = hist->isp; +	u32 *buf = hist->active_buf->virt_addr; +	unsigned int i; + +	if (!buf) { +		dev_dbg(isp->dev, "hist: invalid PIO buffer address\n"); +		hist_reset_mem(hist); +		return STAT_NO_BUF; +	} + +	isp_reg_writel(isp, 0, OMAP3_ISP_IOMEM_HIST, ISPHIST_ADDR); + +	/* +	 * By setting it, the histogram internal buffer is being cleared at the +	 * same time it's being read. This bit must be cleared just after all +	 * data is acquired. +	 */ +	isp_reg_set(isp, OMAP3_ISP_IOMEM_HIST, ISPHIST_CNT, ISPHIST_CNT_CLEAR); + +	/* +	 * We'll read 4 times a 4-bytes-word at each iteration for +	 * optimization. It avoids 3/4 of the jumps. We also know buf_size is +	 * divisible by 16. +	 */ +	for (i = hist->buf_size / 16; i > 0; i--) { +		*buf++ = isp_reg_readl(isp, OMAP3_ISP_IOMEM_HIST, ISPHIST_DATA); +		*buf++ = isp_reg_readl(isp, OMAP3_ISP_IOMEM_HIST, ISPHIST_DATA); +		*buf++ = isp_reg_readl(isp, OMAP3_ISP_IOMEM_HIST, ISPHIST_DATA); +		*buf++ = isp_reg_readl(isp, OMAP3_ISP_IOMEM_HIST, ISPHIST_DATA); +	} +	isp_reg_clr(hist->isp, OMAP3_ISP_IOMEM_HIST, ISPHIST_CNT, +		    ISPHIST_CNT_CLEAR); + +	return STAT_BUF_DONE; +} + +/* + * hist_buf_process - Callback from ISP driver for HIST interrupt. + */ +static int hist_buf_process(struct ispstat *hist) +{ +	struct omap3isp_hist_config *user_cfg = hist->priv; +	int ret; + +	if (atomic_read(&hist->buf_err) || hist->state != ISPSTAT_ENABLED) { +		hist_reset_mem(hist); +		return STAT_NO_BUF; +	} + +	if (--(hist->wait_acc_frames)) +		return STAT_NO_BUF; + +	if (HIST_USING_DMA(hist)) +		ret = hist_buf_dma(hist); +	else +		ret = hist_buf_pio(hist); + +	hist->wait_acc_frames = user_cfg->num_acc_frames; + +	return ret; +} + +static u32 hist_get_buf_size(struct omap3isp_hist_config *conf) +{ +	return OMAP3ISP_HIST_MEM_SIZE_BINS(conf->hist_bins) * conf->num_regions; +} + +/* + * hist_validate_params - Helper function to check user given params. + * @new_conf: Pointer to user configuration structure. + * + * Returns 0 on success configuration. + */ +static int hist_validate_params(struct ispstat *hist, void *new_conf) +{ +	struct omap3isp_hist_config *user_cfg = new_conf; +	int c; +	u32 buf_size; + +	if (user_cfg->cfa > OMAP3ISP_HIST_CFA_FOVEONX3) +		return -EINVAL; + +	/* Regions size and position */ + +	if ((user_cfg->num_regions < OMAP3ISP_HIST_MIN_REGIONS) || +	    (user_cfg->num_regions > OMAP3ISP_HIST_MAX_REGIONS)) +		return -EINVAL; + +	/* Regions */ +	for (c = 0; c < user_cfg->num_regions; c++) { +		if (user_cfg->region[c].h_start & ~ISPHIST_REG_START_END_MASK) +			return -EINVAL; +		if (user_cfg->region[c].h_end & ~ISPHIST_REG_START_END_MASK) +			return -EINVAL; +		if (user_cfg->region[c].v_start & ~ISPHIST_REG_START_END_MASK) +			return -EINVAL; +		if (user_cfg->region[c].v_end & ~ISPHIST_REG_START_END_MASK) +			return -EINVAL; +		if (user_cfg->region[c].h_start > user_cfg->region[c].h_end) +			return -EINVAL; +		if (user_cfg->region[c].v_start > user_cfg->region[c].v_end) +			return -EINVAL; +	} + +	switch (user_cfg->num_regions) { +	case 1: +		if (user_cfg->hist_bins > OMAP3ISP_HIST_BINS_256) +			return -EINVAL; +		break; +	case 2: +		if (user_cfg->hist_bins > OMAP3ISP_HIST_BINS_128) +			return -EINVAL; +		break; +	default: /* 3 or 4 */ +		if (user_cfg->hist_bins > OMAP3ISP_HIST_BINS_64) +			return -EINVAL; +		break; +	} + +	buf_size = hist_get_buf_size(user_cfg); +	if (buf_size > user_cfg->buf_size) +		/* User's buf_size request wasn't enough */ +		user_cfg->buf_size = buf_size; +	else if (user_cfg->buf_size > OMAP3ISP_HIST_MAX_BUF_SIZE) +		user_cfg->buf_size = OMAP3ISP_HIST_MAX_BUF_SIZE; + +	return 0; +} + +static int hist_comp_params(struct ispstat *hist, +			    struct omap3isp_hist_config *user_cfg) +{ +	struct omap3isp_hist_config *cur_cfg = hist->priv; +	int c; + +	if (cur_cfg->cfa != user_cfg->cfa) +		return 1; + +	if (cur_cfg->num_acc_frames != user_cfg->num_acc_frames) +		return 1; + +	if (cur_cfg->hist_bins != user_cfg->hist_bins) +		return 1; + +	for (c = 0; c < OMAP3ISP_HIST_MAX_WG; c++) { +		if (c == 3 && user_cfg->cfa == OMAP3ISP_HIST_CFA_FOVEONX3) +			break; +		else if (cur_cfg->wg[c] != user_cfg->wg[c]) +			return 1; +	} + +	if (cur_cfg->num_regions != user_cfg->num_regions) +		return 1; + +	/* Regions */ +	for (c = 0; c < user_cfg->num_regions; c++) { +		if (cur_cfg->region[c].h_start != user_cfg->region[c].h_start) +			return 1; +		if (cur_cfg->region[c].h_end != user_cfg->region[c].h_end) +			return 1; +		if (cur_cfg->region[c].v_start != user_cfg->region[c].v_start) +			return 1; +		if (cur_cfg->region[c].v_end != user_cfg->region[c].v_end) +			return 1; +	} + +	return 0; +} + +/* + * hist_update_params - Helper function to check and store user given params. + * @new_conf: Pointer to user configuration structure. + */ +static void hist_set_params(struct ispstat *hist, void *new_conf) +{ +	struct omap3isp_hist_config *user_cfg = new_conf; +	struct omap3isp_hist_config *cur_cfg = hist->priv; + +	if (!hist->configured || hist_comp_params(hist, user_cfg)) { +		memcpy(cur_cfg, user_cfg, sizeof(*user_cfg)); +		if (user_cfg->num_acc_frames == 0) +			user_cfg->num_acc_frames = 1; +		hist->inc_config++; +		hist->update = 1; +		/* +		 * User might be asked for a bigger buffer than necessary for +		 * this configuration. In order to return the right amount of +		 * data during buffer request, let's calculate the size here +		 * instead of stick with user_cfg->buf_size. +		 */ +		cur_cfg->buf_size = hist_get_buf_size(cur_cfg); + +	} +} + +static long hist_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg) +{ +	struct ispstat *stat = v4l2_get_subdevdata(sd); + +	switch (cmd) { +	case VIDIOC_OMAP3ISP_HIST_CFG: +		return omap3isp_stat_config(stat, arg); +	case VIDIOC_OMAP3ISP_STAT_REQ: +		return omap3isp_stat_request_statistics(stat, arg); +	case VIDIOC_OMAP3ISP_STAT_EN: { +		int *en = arg; +		return omap3isp_stat_enable(stat, !!*en); +	} +	} + +	return -ENOIOCTLCMD; + +} + +static const struct ispstat_ops hist_ops = { +	.validate_params	= hist_validate_params, +	.set_params		= hist_set_params, +	.setup_regs		= hist_setup_regs, +	.enable			= hist_enable, +	.busy			= hist_busy, +	.buf_process		= hist_buf_process, +}; + +static const struct v4l2_subdev_core_ops hist_subdev_core_ops = { +	.ioctl = hist_ioctl, +	.subscribe_event = omap3isp_stat_subscribe_event, +	.unsubscribe_event = omap3isp_stat_unsubscribe_event, +}; + +static const struct v4l2_subdev_video_ops hist_subdev_video_ops = { +	.s_stream = omap3isp_stat_s_stream, +}; + +static const struct v4l2_subdev_ops hist_subdev_ops = { +	.core = &hist_subdev_core_ops, +	.video = &hist_subdev_video_ops, +}; + +/* + * omap3isp_hist_init - Module Initialization. + */ +int omap3isp_hist_init(struct isp_device *isp) +{ +	struct ispstat *hist = &isp->isp_hist; +	struct omap3isp_hist_config *hist_cfg; +	int ret = -1; + +	hist_cfg = devm_kzalloc(isp->dev, sizeof(*hist_cfg), GFP_KERNEL); +	if (hist_cfg == NULL) +		return -ENOMEM; + +	hist->isp = isp; + +	if (HIST_CONFIG_DMA) +		ret = omap_request_dma(OMAP24XX_DMA_NO_DEVICE, "DMA_ISP_HIST", +				       hist_dma_cb, hist, &hist->dma_ch); +	if (ret) { +		if (HIST_CONFIG_DMA) +			dev_warn(isp->dev, "hist: DMA request channel failed. " +					   "Using PIO only.\n"); +		hist->dma_ch = -1; +	} else { +		dev_dbg(isp->dev, "hist: DMA channel = %d\n", hist->dma_ch); +		hist_dma_config(hist); +		omap_enable_dma_irq(hist->dma_ch, OMAP_DMA_BLOCK_IRQ); +	} + +	hist->ops = &hist_ops; +	hist->priv = hist_cfg; +	hist->event_type = V4L2_EVENT_OMAP3ISP_HIST; + +	ret = omap3isp_stat_init(hist, "histogram", &hist_subdev_ops); +	if (ret) { +		if (HIST_USING_DMA(hist)) +			omap_free_dma(hist->dma_ch); +	} + +	return ret; +} + +/* + * omap3isp_hist_cleanup - Module cleanup. + */ +void omap3isp_hist_cleanup(struct isp_device *isp) +{ +	if (HIST_USING_DMA(&isp->isp_hist)) +		omap_free_dma(isp->isp_hist.dma_ch); +	omap3isp_stat_cleanup(&isp->isp_hist); +} diff --git a/drivers/media/platform/omap3isp/isphist.h b/drivers/media/platform/omap3isp/isphist.h new file mode 100644 index 00000000000..0b2a38ec94c --- /dev/null +++ b/drivers/media/platform/omap3isp/isphist.h @@ -0,0 +1,40 @@ +/* + * isphist.h + * + * TI OMAP3 ISP - Histogram module + * + * Copyright (C) 2010 Nokia Corporation + * Copyright (C) 2009 Texas Instruments, Inc. + * + * Contacts: David Cohen <dacohen@gmail.com> + *	     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 + */ + +#ifndef OMAP3_ISP_HIST_H +#define OMAP3_ISP_HIST_H + +#include <linux/omap3isp.h> + +#define ISPHIST_IN_BIT_WIDTH_CCDC	10 + +struct isp_device; + +int omap3isp_hist_init(struct isp_device *isp); +void omap3isp_hist_cleanup(struct isp_device *isp); + +#endif /* OMAP3_ISP_HIST */ diff --git a/drivers/media/platform/omap3isp/isppreview.c b/drivers/media/platform/omap3isp/isppreview.c new file mode 100644 index 00000000000..720809b07e7 --- /dev/null +++ b/drivers/media/platform/omap3isp/isppreview.c @@ -0,0 +1,2376 @@ +/* + * isppreview.c + * + * TI OMAP3 ISP driver - Preview module + * + * Copyright (C) 2010 Nokia Corporation + * Copyright (C) 2009 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/device.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/uaccess.h> + +#include "isp.h" +#include "ispreg.h" +#include "isppreview.h" + +/* Default values in Office Fluorescent Light for RGBtoRGB Blending */ +static struct omap3isp_prev_rgbtorgb flr_rgb2rgb = { +	{	/* RGB-RGB Matrix */ +		{0x01E2, 0x0F30, 0x0FEE}, +		{0x0F9B, 0x01AC, 0x0FB9}, +		{0x0FE0, 0x0EC0, 0x0260} +	},	/* RGB Offset */ +	{0x0000, 0x0000, 0x0000} +}; + +/* Default values in Office Fluorescent Light for RGB to YUV Conversion*/ +static struct omap3isp_prev_csc flr_prev_csc = { +	{	/* CSC Coef Matrix */ +		{66, 129, 25}, +		{-38, -75, 112}, +		{112, -94 , -18} +	},	/* CSC Offset */ +	{0x0, 0x0, 0x0} +}; + +/* Default values in Office Fluorescent Light for CFA Gradient*/ +#define FLR_CFA_GRADTHRS_HORZ	0x28 +#define FLR_CFA_GRADTHRS_VERT	0x28 + +/* Default values in Office Fluorescent Light for Chroma Suppression*/ +#define FLR_CSUP_GAIN		0x0D +#define FLR_CSUP_THRES		0xEB + +/* Default values in Office Fluorescent Light for Noise Filter*/ +#define FLR_NF_STRGTH		0x03 + +/* Default values for White Balance */ +#define FLR_WBAL_DGAIN		0x100 +#define FLR_WBAL_COEF		0x20 + +/* Default values in Office Fluorescent Light for Black Adjustment*/ +#define FLR_BLKADJ_BLUE		0x0 +#define FLR_BLKADJ_GREEN	0x0 +#define FLR_BLKADJ_RED		0x0 + +#define DEF_DETECT_CORRECT_VAL	0xe + +/* + * Margins and image size limits. + * + * The preview engine crops several rows and columns internally depending on + * which filters are enabled. To avoid format changes when the filters are + * enabled or disabled (which would prevent them from being turned on or off + * during streaming), the driver assumes all filters that can be configured + * during streaming are enabled when computing sink crop and source format + * limits. + * + * If a filter is disabled, additional cropping is automatically added at the + * preview engine input by the driver to avoid overflow at line and frame end. + * This is completely transparent for applications. + * + * Median filter		4 pixels + * Noise filter, + * Faulty pixels correction	4 pixels, 4 lines + * Color suppression		2 pixels + * or luma enhancement + * ------------------------------------------------------------- + * Maximum total		10 pixels, 4 lines + * + * The color suppression and luma enhancement filters are applied after bayer to + * YUV conversion. They thus can crop one pixel on the left and one pixel on the + * right side of the image without changing the color pattern. When both those + * filters are disabled, the driver must crop the two pixels on the same side of + * the image to avoid changing the bayer pattern. The left margin is thus set to + * 6 pixels and the right margin to 4 pixels. + */ + +#define PREV_MARGIN_LEFT	6 +#define PREV_MARGIN_RIGHT	4 +#define PREV_MARGIN_TOP		2 +#define PREV_MARGIN_BOTTOM	2 + +#define PREV_MIN_IN_WIDTH	64 +#define PREV_MIN_IN_HEIGHT	8 +#define PREV_MAX_IN_HEIGHT	16384 + +#define PREV_MIN_OUT_WIDTH		0 +#define PREV_MIN_OUT_HEIGHT		0 +#define PREV_MAX_OUT_WIDTH_REV_1	1280 +#define PREV_MAX_OUT_WIDTH_REV_2	3300 +#define PREV_MAX_OUT_WIDTH_REV_15	4096 + +/* + * Coefficient Tables for the submodules in Preview. + * Array is initialised with the values from.the tables text file. + */ + +/* + * CFA Filter Coefficient Table + * + */ +static u32 cfa_coef_table[4][OMAP3ISP_PREV_CFA_BLK_SIZE] = { +#include "cfa_coef_table.h" +}; + +/* + * Default Gamma Correction Table - All components + */ +static u32 gamma_table[] = { +#include "gamma_table.h" +}; + +/* + * Noise Filter Threshold table + */ +static u32 noise_filter_table[] = { +#include "noise_filter_table.h" +}; + +/* + * Luminance Enhancement Table + */ +static u32 luma_enhance_table[] = { +#include "luma_enhance_table.h" +}; + +/* + * preview_config_luma_enhancement - Configure the Luminance Enhancement table + */ +static void +preview_config_luma_enhancement(struct isp_prev_device *prev, +				const struct prev_params *params) +{ +	struct isp_device *isp = to_isp_device(prev); +	const struct omap3isp_prev_luma *yt = ¶ms->luma; +	unsigned int i; + +	isp_reg_writel(isp, ISPPRV_YENH_TABLE_ADDR, +		       OMAP3_ISP_IOMEM_PREV, ISPPRV_SET_TBL_ADDR); +	for (i = 0; i < OMAP3ISP_PREV_YENH_TBL_SIZE; i++) { +		isp_reg_writel(isp, yt->table[i], +			       OMAP3_ISP_IOMEM_PREV, ISPPRV_SET_TBL_DATA); +	} +} + +/* + * preview_enable_luma_enhancement - Enable/disable Luminance Enhancement + */ +static void +preview_enable_luma_enhancement(struct isp_prev_device *prev, bool enable) +{ +	struct isp_device *isp = to_isp_device(prev); + +	if (enable) +		isp_reg_set(isp, OMAP3_ISP_IOMEM_PREV, ISPPRV_PCR, +			    ISPPRV_PCR_YNENHEN); +	else +		isp_reg_clr(isp, OMAP3_ISP_IOMEM_PREV, ISPPRV_PCR, +			    ISPPRV_PCR_YNENHEN); +} + +/* + * preview_enable_invalaw - Enable/disable Inverse A-Law decompression + */ +static void preview_enable_invalaw(struct isp_prev_device *prev, bool enable) +{ +	struct isp_device *isp = to_isp_device(prev); + +	if (enable) +		isp_reg_set(isp, OMAP3_ISP_IOMEM_PREV, ISPPRV_PCR, +			    ISPPRV_PCR_INVALAW); +	else +		isp_reg_clr(isp, OMAP3_ISP_IOMEM_PREV, ISPPRV_PCR, +			    ISPPRV_PCR_INVALAW); +} + +/* + * preview_config_hmed - Configure the Horizontal Median Filter + */ +static void preview_config_hmed(struct isp_prev_device *prev, +				const struct prev_params *params) +{ +	struct isp_device *isp = to_isp_device(prev); +	const struct omap3isp_prev_hmed *hmed = ¶ms->hmed; + +	isp_reg_writel(isp, (hmed->odddist == 1 ? 0 : ISPPRV_HMED_ODDDIST) | +		       (hmed->evendist == 1 ? 0 : ISPPRV_HMED_EVENDIST) | +		       (hmed->thres << ISPPRV_HMED_THRESHOLD_SHIFT), +		       OMAP3_ISP_IOMEM_PREV, ISPPRV_HMED); +} + +/* + * preview_enable_hmed - Enable/disable the Horizontal Median Filter + */ +static void preview_enable_hmed(struct isp_prev_device *prev, bool enable) +{ +	struct isp_device *isp = to_isp_device(prev); + +	if (enable) +		isp_reg_set(isp, OMAP3_ISP_IOMEM_PREV, ISPPRV_PCR, +			    ISPPRV_PCR_HMEDEN); +	else +		isp_reg_clr(isp, OMAP3_ISP_IOMEM_PREV, ISPPRV_PCR, +			    ISPPRV_PCR_HMEDEN); +} + +/* + * preview_config_cfa - Configure CFA Interpolation for Bayer formats + * + * The CFA table is organised in four blocks, one per Bayer component. The + * hardware expects blocks to follow the Bayer order of the input data, while + * the driver stores the table in GRBG order in memory. The blocks need to be + * reordered to support non-GRBG Bayer patterns. + */ +static void preview_config_cfa(struct isp_prev_device *prev, +			       const struct prev_params *params) +{ +	static const unsigned int cfa_coef_order[4][4] = { +		{ 0, 1, 2, 3 }, /* GRBG */ +		{ 1, 0, 3, 2 }, /* RGGB */ +		{ 2, 3, 0, 1 }, /* BGGR */ +		{ 3, 2, 1, 0 }, /* GBRG */ +	}; +	const unsigned int *order = cfa_coef_order[prev->params.cfa_order]; +	const struct omap3isp_prev_cfa *cfa = ¶ms->cfa; +	struct isp_device *isp = to_isp_device(prev); +	unsigned int i; +	unsigned int j; + +	isp_reg_writel(isp, +		(cfa->gradthrs_vert << ISPPRV_CFA_GRADTH_VER_SHIFT) | +		(cfa->gradthrs_horz << ISPPRV_CFA_GRADTH_HOR_SHIFT), +		OMAP3_ISP_IOMEM_PREV, ISPPRV_CFA); + +	isp_reg_writel(isp, ISPPRV_CFA_TABLE_ADDR, +		       OMAP3_ISP_IOMEM_PREV, ISPPRV_SET_TBL_ADDR); + +	for (i = 0; i < 4; ++i) { +		const __u32 *block = cfa->table[order[i]]; + +		for (j = 0; j < OMAP3ISP_PREV_CFA_BLK_SIZE; ++j) +			isp_reg_writel(isp, block[j], OMAP3_ISP_IOMEM_PREV, +				       ISPPRV_SET_TBL_DATA); +	} +} + +/* + * preview_config_chroma_suppression - Configure Chroma Suppression + */ +static void +preview_config_chroma_suppression(struct isp_prev_device *prev, +				  const struct prev_params *params) +{ +	struct isp_device *isp = to_isp_device(prev); +	const struct omap3isp_prev_csup *cs = ¶ms->csup; + +	isp_reg_writel(isp, +		       cs->gain | (cs->thres << ISPPRV_CSUP_THRES_SHIFT) | +		       (cs->hypf_en << ISPPRV_CSUP_HPYF_SHIFT), +		       OMAP3_ISP_IOMEM_PREV, ISPPRV_CSUP); +} + +/* + * preview_enable_chroma_suppression - Enable/disable Chrominance Suppression + */ +static void +preview_enable_chroma_suppression(struct isp_prev_device *prev, bool enable) +{ +	struct isp_device *isp = to_isp_device(prev); + +	if (enable) +		isp_reg_set(isp, OMAP3_ISP_IOMEM_PREV, ISPPRV_PCR, +			    ISPPRV_PCR_SUPEN); +	else +		isp_reg_clr(isp, OMAP3_ISP_IOMEM_PREV, ISPPRV_PCR, +			    ISPPRV_PCR_SUPEN); +} + +/* + * preview_config_whitebalance - Configure White Balance parameters + * + * Coefficient matrix always with default values. + */ +static void +preview_config_whitebalance(struct isp_prev_device *prev, +			    const struct prev_params *params) +{ +	struct isp_device *isp = to_isp_device(prev); +	const struct omap3isp_prev_wbal *wbal = ¶ms->wbal; +	u32 val; + +	isp_reg_writel(isp, wbal->dgain, OMAP3_ISP_IOMEM_PREV, ISPPRV_WB_DGAIN); + +	val = wbal->coef0 << ISPPRV_WBGAIN_COEF0_SHIFT; +	val |= wbal->coef1 << ISPPRV_WBGAIN_COEF1_SHIFT; +	val |= wbal->coef2 << ISPPRV_WBGAIN_COEF2_SHIFT; +	val |= wbal->coef3 << ISPPRV_WBGAIN_COEF3_SHIFT; +	isp_reg_writel(isp, val, OMAP3_ISP_IOMEM_PREV, ISPPRV_WBGAIN); + +	isp_reg_writel(isp, +		       ISPPRV_WBSEL_COEF0 << ISPPRV_WBSEL_N0_0_SHIFT | +		       ISPPRV_WBSEL_COEF1 << ISPPRV_WBSEL_N0_1_SHIFT | +		       ISPPRV_WBSEL_COEF0 << ISPPRV_WBSEL_N0_2_SHIFT | +		       ISPPRV_WBSEL_COEF1 << ISPPRV_WBSEL_N0_3_SHIFT | +		       ISPPRV_WBSEL_COEF2 << ISPPRV_WBSEL_N1_0_SHIFT | +		       ISPPRV_WBSEL_COEF3 << ISPPRV_WBSEL_N1_1_SHIFT | +		       ISPPRV_WBSEL_COEF2 << ISPPRV_WBSEL_N1_2_SHIFT | +		       ISPPRV_WBSEL_COEF3 << ISPPRV_WBSEL_N1_3_SHIFT | +		       ISPPRV_WBSEL_COEF0 << ISPPRV_WBSEL_N2_0_SHIFT | +		       ISPPRV_WBSEL_COEF1 << ISPPRV_WBSEL_N2_1_SHIFT | +		       ISPPRV_WBSEL_COEF0 << ISPPRV_WBSEL_N2_2_SHIFT | +		       ISPPRV_WBSEL_COEF1 << ISPPRV_WBSEL_N2_3_SHIFT | +		       ISPPRV_WBSEL_COEF2 << ISPPRV_WBSEL_N3_0_SHIFT | +		       ISPPRV_WBSEL_COEF3 << ISPPRV_WBSEL_N3_1_SHIFT | +		       ISPPRV_WBSEL_COEF2 << ISPPRV_WBSEL_N3_2_SHIFT | +		       ISPPRV_WBSEL_COEF3 << ISPPRV_WBSEL_N3_3_SHIFT, +		       OMAP3_ISP_IOMEM_PREV, ISPPRV_WBSEL); +} + +/* + * preview_config_blkadj - Configure Black Adjustment + */ +static void +preview_config_blkadj(struct isp_prev_device *prev, +		      const struct prev_params *params) +{ +	struct isp_device *isp = to_isp_device(prev); +	const struct omap3isp_prev_blkadj *blkadj = ¶ms->blkadj; + +	isp_reg_writel(isp, (blkadj->blue << ISPPRV_BLKADJOFF_B_SHIFT) | +		       (blkadj->green << ISPPRV_BLKADJOFF_G_SHIFT) | +		       (blkadj->red << ISPPRV_BLKADJOFF_R_SHIFT), +		       OMAP3_ISP_IOMEM_PREV, ISPPRV_BLKADJOFF); +} + +/* + * preview_config_rgb_blending - Configure RGB-RGB Blending + */ +static void +preview_config_rgb_blending(struct isp_prev_device *prev, +			    const struct prev_params *params) +{ +	struct isp_device *isp = to_isp_device(prev); +	const struct omap3isp_prev_rgbtorgb *rgbrgb = ¶ms->rgb2rgb; +	u32 val; + +	val = (rgbrgb->matrix[0][0] & 0xfff) << ISPPRV_RGB_MAT1_MTX_RR_SHIFT; +	val |= (rgbrgb->matrix[0][1] & 0xfff) << ISPPRV_RGB_MAT1_MTX_GR_SHIFT; +	isp_reg_writel(isp, val, OMAP3_ISP_IOMEM_PREV, ISPPRV_RGB_MAT1); + +	val = (rgbrgb->matrix[0][2] & 0xfff) << ISPPRV_RGB_MAT2_MTX_BR_SHIFT; +	val |= (rgbrgb->matrix[1][0] & 0xfff) << ISPPRV_RGB_MAT2_MTX_RG_SHIFT; +	isp_reg_writel(isp, val, OMAP3_ISP_IOMEM_PREV, ISPPRV_RGB_MAT2); + +	val = (rgbrgb->matrix[1][1] & 0xfff) << ISPPRV_RGB_MAT3_MTX_GG_SHIFT; +	val |= (rgbrgb->matrix[1][2] & 0xfff) << ISPPRV_RGB_MAT3_MTX_BG_SHIFT; +	isp_reg_writel(isp, val, OMAP3_ISP_IOMEM_PREV, ISPPRV_RGB_MAT3); + +	val = (rgbrgb->matrix[2][0] & 0xfff) << ISPPRV_RGB_MAT4_MTX_RB_SHIFT; +	val |= (rgbrgb->matrix[2][1] & 0xfff) << ISPPRV_RGB_MAT4_MTX_GB_SHIFT; +	isp_reg_writel(isp, val, OMAP3_ISP_IOMEM_PREV, ISPPRV_RGB_MAT4); + +	val = (rgbrgb->matrix[2][2] & 0xfff) << ISPPRV_RGB_MAT5_MTX_BB_SHIFT; +	isp_reg_writel(isp, val, OMAP3_ISP_IOMEM_PREV, ISPPRV_RGB_MAT5); + +	val = (rgbrgb->offset[0] & 0x3ff) << ISPPRV_RGB_OFF1_MTX_OFFR_SHIFT; +	val |= (rgbrgb->offset[1] & 0x3ff) << ISPPRV_RGB_OFF1_MTX_OFFG_SHIFT; +	isp_reg_writel(isp, val, OMAP3_ISP_IOMEM_PREV, ISPPRV_RGB_OFF1); + +	val = (rgbrgb->offset[2] & 0x3ff) << ISPPRV_RGB_OFF2_MTX_OFFB_SHIFT; +	isp_reg_writel(isp, val, OMAP3_ISP_IOMEM_PREV, ISPPRV_RGB_OFF2); +} + +/* + * preview_config_csc - Configure Color Space Conversion (RGB to YCbYCr) + */ +static void +preview_config_csc(struct isp_prev_device *prev, +		   const struct prev_params *params) +{ +	struct isp_device *isp = to_isp_device(prev); +	const struct omap3isp_prev_csc *csc = ¶ms->csc; +	u32 val; + +	val = (csc->matrix[0][0] & 0x3ff) << ISPPRV_CSC0_RY_SHIFT; +	val |= (csc->matrix[0][1] & 0x3ff) << ISPPRV_CSC0_GY_SHIFT; +	val |= (csc->matrix[0][2] & 0x3ff) << ISPPRV_CSC0_BY_SHIFT; +	isp_reg_writel(isp, val, OMAP3_ISP_IOMEM_PREV, ISPPRV_CSC0); + +	val = (csc->matrix[1][0] & 0x3ff) << ISPPRV_CSC1_RCB_SHIFT; +	val |= (csc->matrix[1][1] & 0x3ff) << ISPPRV_CSC1_GCB_SHIFT; +	val |= (csc->matrix[1][2] & 0x3ff) << ISPPRV_CSC1_BCB_SHIFT; +	isp_reg_writel(isp, val, OMAP3_ISP_IOMEM_PREV, ISPPRV_CSC1); + +	val = (csc->matrix[2][0] & 0x3ff) << ISPPRV_CSC2_RCR_SHIFT; +	val |= (csc->matrix[2][1] & 0x3ff) << ISPPRV_CSC2_GCR_SHIFT; +	val |= (csc->matrix[2][2] & 0x3ff) << ISPPRV_CSC2_BCR_SHIFT; +	isp_reg_writel(isp, val, OMAP3_ISP_IOMEM_PREV, ISPPRV_CSC2); + +	val = (csc->offset[0] & 0xff) << ISPPRV_CSC_OFFSET_Y_SHIFT; +	val |= (csc->offset[1] & 0xff) << ISPPRV_CSC_OFFSET_CB_SHIFT; +	val |= (csc->offset[2] & 0xff) << ISPPRV_CSC_OFFSET_CR_SHIFT; +	isp_reg_writel(isp, val, OMAP3_ISP_IOMEM_PREV, ISPPRV_CSC_OFFSET); +} + +/* + * preview_config_yc_range - Configure the max and min Y and C values + */ +static void +preview_config_yc_range(struct isp_prev_device *prev, +			const struct prev_params *params) +{ +	struct isp_device *isp = to_isp_device(prev); +	const struct omap3isp_prev_yclimit *yc = ¶ms->yclimit; + +	isp_reg_writel(isp, +		       yc->maxC << ISPPRV_SETUP_YC_MAXC_SHIFT | +		       yc->maxY << ISPPRV_SETUP_YC_MAXY_SHIFT | +		       yc->minC << ISPPRV_SETUP_YC_MINC_SHIFT | +		       yc->minY << ISPPRV_SETUP_YC_MINY_SHIFT, +		       OMAP3_ISP_IOMEM_PREV, ISPPRV_SETUP_YC); +} + +/* + * preview_config_dcor - Configure Couplet Defect Correction + */ +static void +preview_config_dcor(struct isp_prev_device *prev, +		    const struct prev_params *params) +{ +	struct isp_device *isp = to_isp_device(prev); +	const struct omap3isp_prev_dcor *dcor = ¶ms->dcor; + +	isp_reg_writel(isp, dcor->detect_correct[0], +		       OMAP3_ISP_IOMEM_PREV, ISPPRV_CDC_THR0); +	isp_reg_writel(isp, dcor->detect_correct[1], +		       OMAP3_ISP_IOMEM_PREV, ISPPRV_CDC_THR1); +	isp_reg_writel(isp, dcor->detect_correct[2], +		       OMAP3_ISP_IOMEM_PREV, ISPPRV_CDC_THR2); +	isp_reg_writel(isp, dcor->detect_correct[3], +		       OMAP3_ISP_IOMEM_PREV, ISPPRV_CDC_THR3); +	isp_reg_clr_set(isp, OMAP3_ISP_IOMEM_PREV, ISPPRV_PCR, +			ISPPRV_PCR_DCCOUP, +			dcor->couplet_mode_en ? ISPPRV_PCR_DCCOUP : 0); +} + +/* + * preview_enable_dcor - Enable/disable Couplet Defect Correction + */ +static void preview_enable_dcor(struct isp_prev_device *prev, bool enable) +{ +	struct isp_device *isp = to_isp_device(prev); + +	if (enable) +		isp_reg_set(isp, OMAP3_ISP_IOMEM_PREV, ISPPRV_PCR, +			    ISPPRV_PCR_DCOREN); +	else +		isp_reg_clr(isp, OMAP3_ISP_IOMEM_PREV, ISPPRV_PCR, +			    ISPPRV_PCR_DCOREN); +} + +/* + * preview_enable_drkframe_capture - Enable/disable Dark Frame Capture + */ +static void +preview_enable_drkframe_capture(struct isp_prev_device *prev, bool enable) +{ +	struct isp_device *isp = to_isp_device(prev); + +	if (enable) +		isp_reg_set(isp, OMAP3_ISP_IOMEM_PREV, ISPPRV_PCR, +			    ISPPRV_PCR_DRKFCAP); +	else +		isp_reg_clr(isp, OMAP3_ISP_IOMEM_PREV, ISPPRV_PCR, +			    ISPPRV_PCR_DRKFCAP); +} + +/* + * preview_enable_drkframe - Enable/disable Dark Frame Subtraction + */ +static void preview_enable_drkframe(struct isp_prev_device *prev, bool enable) +{ +	struct isp_device *isp = to_isp_device(prev); + +	if (enable) +		isp_reg_set(isp, OMAP3_ISP_IOMEM_PREV, ISPPRV_PCR, +			    ISPPRV_PCR_DRKFEN); +	else +		isp_reg_clr(isp, OMAP3_ISP_IOMEM_PREV, ISPPRV_PCR, +			    ISPPRV_PCR_DRKFEN); +} + +/* + * preview_config_noisefilter - Configure the Noise Filter + */ +static void +preview_config_noisefilter(struct isp_prev_device *prev, +			   const struct prev_params *params) +{ +	struct isp_device *isp = to_isp_device(prev); +	const struct omap3isp_prev_nf *nf = ¶ms->nf; +	unsigned int i; + +	isp_reg_writel(isp, nf->spread, OMAP3_ISP_IOMEM_PREV, ISPPRV_NF); +	isp_reg_writel(isp, ISPPRV_NF_TABLE_ADDR, +		       OMAP3_ISP_IOMEM_PREV, ISPPRV_SET_TBL_ADDR); +	for (i = 0; i < OMAP3ISP_PREV_NF_TBL_SIZE; i++) { +		isp_reg_writel(isp, nf->table[i], +			       OMAP3_ISP_IOMEM_PREV, ISPPRV_SET_TBL_DATA); +	} +} + +/* + * preview_enable_noisefilter - Enable/disable the Noise Filter + */ +static void +preview_enable_noisefilter(struct isp_prev_device *prev, bool enable) +{ +	struct isp_device *isp = to_isp_device(prev); + +	if (enable) +		isp_reg_set(isp, OMAP3_ISP_IOMEM_PREV, ISPPRV_PCR, +			    ISPPRV_PCR_NFEN); +	else +		isp_reg_clr(isp, OMAP3_ISP_IOMEM_PREV, ISPPRV_PCR, +			    ISPPRV_PCR_NFEN); +} + +/* + * preview_config_gammacorrn - Configure the Gamma Correction tables + */ +static void +preview_config_gammacorrn(struct isp_prev_device *prev, +			  const struct prev_params *params) +{ +	struct isp_device *isp = to_isp_device(prev); +	const struct omap3isp_prev_gtables *gt = ¶ms->gamma; +	unsigned int i; + +	isp_reg_writel(isp, ISPPRV_REDGAMMA_TABLE_ADDR, +		       OMAP3_ISP_IOMEM_PREV, ISPPRV_SET_TBL_ADDR); +	for (i = 0; i < OMAP3ISP_PREV_GAMMA_TBL_SIZE; i++) +		isp_reg_writel(isp, gt->red[i], OMAP3_ISP_IOMEM_PREV, +			       ISPPRV_SET_TBL_DATA); + +	isp_reg_writel(isp, ISPPRV_GREENGAMMA_TABLE_ADDR, +		       OMAP3_ISP_IOMEM_PREV, ISPPRV_SET_TBL_ADDR); +	for (i = 0; i < OMAP3ISP_PREV_GAMMA_TBL_SIZE; i++) +		isp_reg_writel(isp, gt->green[i], OMAP3_ISP_IOMEM_PREV, +			       ISPPRV_SET_TBL_DATA); + +	isp_reg_writel(isp, ISPPRV_BLUEGAMMA_TABLE_ADDR, +		       OMAP3_ISP_IOMEM_PREV, ISPPRV_SET_TBL_ADDR); +	for (i = 0; i < OMAP3ISP_PREV_GAMMA_TBL_SIZE; i++) +		isp_reg_writel(isp, gt->blue[i], OMAP3_ISP_IOMEM_PREV, +			       ISPPRV_SET_TBL_DATA); +} + +/* + * preview_enable_gammacorrn - Enable/disable Gamma Correction + * + * When gamma correction is disabled, the module is bypassed and its output is + * the 8 MSB of the 10-bit input . + */ +static void +preview_enable_gammacorrn(struct isp_prev_device *prev, bool enable) +{ +	struct isp_device *isp = to_isp_device(prev); + +	if (enable) +		isp_reg_clr(isp, OMAP3_ISP_IOMEM_PREV, ISPPRV_PCR, +			    ISPPRV_PCR_GAMMA_BYPASS); +	else +		isp_reg_set(isp, OMAP3_ISP_IOMEM_PREV, ISPPRV_PCR, +			    ISPPRV_PCR_GAMMA_BYPASS); +} + +/* + * preview_config_contrast - Configure the Contrast + * + * Value should be programmed before enabling the module. + */ +static void +preview_config_contrast(struct isp_prev_device *prev, +			const struct prev_params *params) +{ +	struct isp_device *isp = to_isp_device(prev); + +	isp_reg_clr_set(isp, OMAP3_ISP_IOMEM_PREV, ISPPRV_CNT_BRT, +			0xff << ISPPRV_CNT_BRT_CNT_SHIFT, +			params->contrast << ISPPRV_CNT_BRT_CNT_SHIFT); +} + +/* + * preview_config_brightness - Configure the Brightness + */ +static void +preview_config_brightness(struct isp_prev_device *prev, +			  const struct prev_params *params) +{ +	struct isp_device *isp = to_isp_device(prev); + +	isp_reg_clr_set(isp, OMAP3_ISP_IOMEM_PREV, ISPPRV_CNT_BRT, +			0xff << ISPPRV_CNT_BRT_BRT_SHIFT, +			params->brightness << ISPPRV_CNT_BRT_BRT_SHIFT); +} + +/* + * preview_update_contrast - Updates the contrast. + * @contrast: Pointer to hold the current programmed contrast value. + * + * Value should be programmed before enabling the module. + */ +static void +preview_update_contrast(struct isp_prev_device *prev, u8 contrast) +{ +	struct prev_params *params; +	unsigned long flags; + +	spin_lock_irqsave(&prev->params.lock, flags); +	params = (prev->params.active & OMAP3ISP_PREV_CONTRAST) +	       ? &prev->params.params[0] : &prev->params.params[1]; + +	if (params->contrast != (contrast * ISPPRV_CONTRAST_UNITS)) { +		params->contrast = contrast * ISPPRV_CONTRAST_UNITS; +		params->update |= OMAP3ISP_PREV_CONTRAST; +	} +	spin_unlock_irqrestore(&prev->params.lock, flags); +} + +/* + * preview_update_brightness - Updates the brightness in preview module. + * @brightness: Pointer to hold the current programmed brightness value. + * + */ +static void +preview_update_brightness(struct isp_prev_device *prev, u8 brightness) +{ +	struct prev_params *params; +	unsigned long flags; + +	spin_lock_irqsave(&prev->params.lock, flags); +	params = (prev->params.active & OMAP3ISP_PREV_BRIGHTNESS) +	       ? &prev->params.params[0] : &prev->params.params[1]; + +	if (params->brightness != (brightness * ISPPRV_BRIGHT_UNITS)) { +		params->brightness = brightness * ISPPRV_BRIGHT_UNITS; +		params->update |= OMAP3ISP_PREV_BRIGHTNESS; +	} +	spin_unlock_irqrestore(&prev->params.lock, flags); +} + +static u32 +preview_params_lock(struct isp_prev_device *prev, u32 update, bool shadow) +{ +	u32 active = prev->params.active; + +	if (shadow) { +		/* Mark all shadow parameters we are going to touch as busy. */ +		prev->params.params[0].busy |= ~active & update; +		prev->params.params[1].busy |= active & update; +	} else { +		/* Mark all active parameters we are going to touch as busy. */ +		update = (prev->params.params[0].update & active) +		       | (prev->params.params[1].update & ~active); + +		prev->params.params[0].busy |= active & update; +		prev->params.params[1].busy |= ~active & update; +	} + +	return update; +} + +static void +preview_params_unlock(struct isp_prev_device *prev, u32 update, bool shadow) +{ +	u32 active = prev->params.active; + +	if (shadow) { +		/* Set the update flag for shadow parameters that have been +		 * updated and clear the busy flag for all shadow parameters. +		 */ +		prev->params.params[0].update |= (~active & update); +		prev->params.params[1].update |= (active & update); +		prev->params.params[0].busy &= active; +		prev->params.params[1].busy &= ~active; +	} else { +		/* Clear the update flag for active parameters that have been +		 * applied and the busy flag for all active parameters. +		 */ +		prev->params.params[0].update &= ~(active & update); +		prev->params.params[1].update &= ~(~active & update); +		prev->params.params[0].busy &= ~active; +		prev->params.params[1].busy &= active; +	} +} + +static void preview_params_switch(struct isp_prev_device *prev) +{ +	u32 to_switch; + +	/* Switch active parameters with updated shadow parameters when the +	 * shadow parameter has been updated and neither the active not the +	 * shadow parameter is busy. +	 */ +	to_switch = (prev->params.params[0].update & ~prev->params.active) +		  | (prev->params.params[1].update & prev->params.active); +	to_switch &= ~(prev->params.params[0].busy | +		       prev->params.params[1].busy); +	if (to_switch == 0) +		return; + +	prev->params.active ^= to_switch; + +	/* Remove the update flag for the shadow copy of parameters we have +	 * switched. +	 */ +	prev->params.params[0].update &= ~(~prev->params.active & to_switch); +	prev->params.params[1].update &= ~(prev->params.active & to_switch); +} + +/* preview parameters update structure */ +struct preview_update { +	void (*config)(struct isp_prev_device *, const struct prev_params *); +	void (*enable)(struct isp_prev_device *, bool); +	unsigned int param_offset; +	unsigned int param_size; +	unsigned int config_offset; +	bool skip; +}; + +/* Keep the array indexed by the OMAP3ISP_PREV_* bit number. */ +static const struct preview_update update_attrs[] = { +	/* OMAP3ISP_PREV_LUMAENH */ { +		preview_config_luma_enhancement, +		preview_enable_luma_enhancement, +		offsetof(struct prev_params, luma), +		FIELD_SIZEOF(struct prev_params, luma), +		offsetof(struct omap3isp_prev_update_config, luma), +	}, /* OMAP3ISP_PREV_INVALAW */ { +		NULL, +		preview_enable_invalaw, +	}, /* OMAP3ISP_PREV_HRZ_MED */ { +		preview_config_hmed, +		preview_enable_hmed, +		offsetof(struct prev_params, hmed), +		FIELD_SIZEOF(struct prev_params, hmed), +		offsetof(struct omap3isp_prev_update_config, hmed), +	}, /* OMAP3ISP_PREV_CFA */ { +		preview_config_cfa, +		NULL, +		offsetof(struct prev_params, cfa), +		FIELD_SIZEOF(struct prev_params, cfa), +		offsetof(struct omap3isp_prev_update_config, cfa), +	}, /* OMAP3ISP_PREV_CHROMA_SUPP */ { +		preview_config_chroma_suppression, +		preview_enable_chroma_suppression, +		offsetof(struct prev_params, csup), +		FIELD_SIZEOF(struct prev_params, csup), +		offsetof(struct omap3isp_prev_update_config, csup), +	}, /* OMAP3ISP_PREV_WB */ { +		preview_config_whitebalance, +		NULL, +		offsetof(struct prev_params, wbal), +		FIELD_SIZEOF(struct prev_params, wbal), +		offsetof(struct omap3isp_prev_update_config, wbal), +	}, /* OMAP3ISP_PREV_BLKADJ */ { +		preview_config_blkadj, +		NULL, +		offsetof(struct prev_params, blkadj), +		FIELD_SIZEOF(struct prev_params, blkadj), +		offsetof(struct omap3isp_prev_update_config, blkadj), +	}, /* OMAP3ISP_PREV_RGB2RGB */ { +		preview_config_rgb_blending, +		NULL, +		offsetof(struct prev_params, rgb2rgb), +		FIELD_SIZEOF(struct prev_params, rgb2rgb), +		offsetof(struct omap3isp_prev_update_config, rgb2rgb), +	}, /* OMAP3ISP_PREV_COLOR_CONV */ { +		preview_config_csc, +		NULL, +		offsetof(struct prev_params, csc), +		FIELD_SIZEOF(struct prev_params, csc), +		offsetof(struct omap3isp_prev_update_config, csc), +	}, /* OMAP3ISP_PREV_YC_LIMIT */ { +		preview_config_yc_range, +		NULL, +		offsetof(struct prev_params, yclimit), +		FIELD_SIZEOF(struct prev_params, yclimit), +		offsetof(struct omap3isp_prev_update_config, yclimit), +	}, /* OMAP3ISP_PREV_DEFECT_COR */ { +		preview_config_dcor, +		preview_enable_dcor, +		offsetof(struct prev_params, dcor), +		FIELD_SIZEOF(struct prev_params, dcor), +		offsetof(struct omap3isp_prev_update_config, dcor), +	}, /* Previously OMAP3ISP_PREV_GAMMABYPASS, not used anymore */ { +		NULL, +		NULL, +	}, /* OMAP3ISP_PREV_DRK_FRM_CAPTURE */ { +		NULL, +		preview_enable_drkframe_capture, +	}, /* OMAP3ISP_PREV_DRK_FRM_SUBTRACT */ { +		NULL, +		preview_enable_drkframe, +	}, /* OMAP3ISP_PREV_LENS_SHADING */ { +		NULL, +		preview_enable_drkframe, +	}, /* OMAP3ISP_PREV_NF */ { +		preview_config_noisefilter, +		preview_enable_noisefilter, +		offsetof(struct prev_params, nf), +		FIELD_SIZEOF(struct prev_params, nf), +		offsetof(struct omap3isp_prev_update_config, nf), +	}, /* OMAP3ISP_PREV_GAMMA */ { +		preview_config_gammacorrn, +		preview_enable_gammacorrn, +		offsetof(struct prev_params, gamma), +		FIELD_SIZEOF(struct prev_params, gamma), +		offsetof(struct omap3isp_prev_update_config, gamma), +	}, /* OMAP3ISP_PREV_CONTRAST */ { +		preview_config_contrast, +		NULL, +		0, 0, 0, true, +	}, /* OMAP3ISP_PREV_BRIGHTNESS */ { +		preview_config_brightness, +		NULL, +		0, 0, 0, true, +	}, +}; + +/* + * preview_config - Copy and update local structure with userspace preview + *                  configuration. + * @prev: ISP preview engine + * @cfg: Configuration + * + * Return zero if success or -EFAULT if the configuration can't be copied from + * userspace. + */ +static int preview_config(struct isp_prev_device *prev, +			  struct omap3isp_prev_update_config *cfg) +{ +	unsigned long flags; +	unsigned int i; +	int rval = 0; +	u32 update; +	u32 active; + +	if (cfg->update == 0) +		return 0; + +	/* Mark the shadow parameters we're going to update as busy. */ +	spin_lock_irqsave(&prev->params.lock, flags); +	preview_params_lock(prev, cfg->update, true); +	active = prev->params.active; +	spin_unlock_irqrestore(&prev->params.lock, flags); + +	update = 0; + +	for (i = 0; i < ARRAY_SIZE(update_attrs); i++) { +		const struct preview_update *attr = &update_attrs[i]; +		struct prev_params *params; +		unsigned int bit = 1 << i; + +		if (attr->skip || !(cfg->update & bit)) +			continue; + +		params = &prev->params.params[!!(active & bit)]; + +		if (cfg->flag & bit) { +			void __user *from = *(void * __user *) +				((void *)cfg + attr->config_offset); +			void *to = (void *)params + attr->param_offset; +			size_t size = attr->param_size; + +			if (to && from && size) { +				if (copy_from_user(to, from, size)) { +					rval = -EFAULT; +					break; +				} +			} +			params->features |= bit; +		} else { +			params->features &= ~bit; +		} + +		update |= bit; +	} + +	spin_lock_irqsave(&prev->params.lock, flags); +	preview_params_unlock(prev, update, true); +	preview_params_switch(prev); +	spin_unlock_irqrestore(&prev->params.lock, flags); + +	return rval; +} + +/* + * preview_setup_hw - Setup preview registers and/or internal memory + * @prev: pointer to preview private structure + * @update: Bitmask of parameters to setup + * @active: Bitmask of parameters active in set 0 + * Note: can be called from interrupt context + * Return none + */ +static void preview_setup_hw(struct isp_prev_device *prev, u32 update, +			     u32 active) +{ +	unsigned int i; +	u32 features; + +	if (update == 0) +		return; + +	features = (prev->params.params[0].features & active) +		 | (prev->params.params[1].features & ~active); + +	for (i = 0; i < ARRAY_SIZE(update_attrs); i++) { +		const struct preview_update *attr = &update_attrs[i]; +		struct prev_params *params; +		unsigned int bit = 1 << i; + +		if (!(update & bit)) +			continue; + +		params = &prev->params.params[!(active & bit)]; + +		if (params->features & bit) { +			if (attr->config) +				attr->config(prev, params); +			if (attr->enable) +				attr->enable(prev, true); +		} else { +			if (attr->enable) +				attr->enable(prev, false); +		} +	} +} + +/* + * preview_config_ycpos - Configure byte layout of YUV image. + * @prev: pointer to previewer private structure + * @pixelcode: pixel code + */ +static void +preview_config_ycpos(struct isp_prev_device *prev, +		     enum v4l2_mbus_pixelcode pixelcode) +{ +	struct isp_device *isp = to_isp_device(prev); +	enum preview_ycpos_mode mode; + +	switch (pixelcode) { +	case V4L2_MBUS_FMT_YUYV8_1X16: +		mode = YCPOS_CrYCbY; +		break; +	case V4L2_MBUS_FMT_UYVY8_1X16: +		mode = YCPOS_YCrYCb; +		break; +	default: +		return; +	} + +	isp_reg_clr_set(isp, OMAP3_ISP_IOMEM_PREV, ISPPRV_PCR, +			ISPPRV_PCR_YCPOS_CrYCbY, +			mode << ISPPRV_PCR_YCPOS_SHIFT); +} + +/* + * preview_config_averager - Enable / disable / configure averager + * @average: Average value to be configured. + */ +static void preview_config_averager(struct isp_prev_device *prev, u8 average) +{ +	struct isp_device *isp = to_isp_device(prev); + +	isp_reg_writel(isp, ISPPRV_AVE_EVENDIST_2 << ISPPRV_AVE_EVENDIST_SHIFT | +		       ISPPRV_AVE_ODDDIST_2 << ISPPRV_AVE_ODDDIST_SHIFT | +		       average, OMAP3_ISP_IOMEM_PREV, ISPPRV_AVE); +} + + +/* + * preview_config_input_format - Configure the input format + * @prev: The preview engine + * @info: Sink pad format information + * + * Enable and configure CFA interpolation for Bayer formats and disable it for + * greyscale formats. + * + * The CFA table is organised in four blocks, one per Bayer component. The + * hardware expects blocks to follow the Bayer order of the input data, while + * the driver stores the table in GRBG order in memory. The blocks need to be + * reordered to support non-GRBG Bayer patterns. + */ +static void preview_config_input_format(struct isp_prev_device *prev, +					const struct isp_format_info *info) +{ +	struct isp_device *isp = to_isp_device(prev); +	struct prev_params *params; + +	if (info->width == 8) +		isp_reg_set(isp, OMAP3_ISP_IOMEM_PREV, ISPPRV_PCR, +			    ISPPRV_PCR_WIDTH); +	else +		isp_reg_clr(isp, OMAP3_ISP_IOMEM_PREV, ISPPRV_PCR, +			    ISPPRV_PCR_WIDTH); + +	switch (info->flavor) { +	case V4L2_MBUS_FMT_SGRBG8_1X8: +		prev->params.cfa_order = 0; +		break; +	case V4L2_MBUS_FMT_SRGGB8_1X8: +		prev->params.cfa_order = 1; +		break; +	case V4L2_MBUS_FMT_SBGGR8_1X8: +		prev->params.cfa_order = 2; +		break; +	case V4L2_MBUS_FMT_SGBRG8_1X8: +		prev->params.cfa_order = 3; +		break; +	default: +		/* Disable CFA for non-Bayer formats. */ +		isp_reg_clr(isp, OMAP3_ISP_IOMEM_PREV, ISPPRV_PCR, +			    ISPPRV_PCR_CFAEN); +		return; +	} + +	isp_reg_set(isp, OMAP3_ISP_IOMEM_PREV, ISPPRV_PCR, ISPPRV_PCR_CFAEN); +	isp_reg_clr_set(isp, OMAP3_ISP_IOMEM_PREV, ISPPRV_PCR, +			ISPPRV_PCR_CFAFMT_MASK, ISPPRV_PCR_CFAFMT_BAYER); + +	params = (prev->params.active & OMAP3ISP_PREV_CFA) +	       ? &prev->params.params[0] : &prev->params.params[1]; + +	preview_config_cfa(prev, params); +} + +/* + * preview_config_input_size - Configure the input frame size + * + * The preview engine crops several rows and columns internally depending on + * which processing blocks are enabled. The driver assumes all those blocks are + * enabled when reporting source pad formats to userspace. If this assumption is + * not true, rows and columns must be manually cropped at the preview engine + * input to avoid overflows at the end of lines and frames. + * + * See the explanation at the PREV_MARGIN_* definitions for more details. + */ +static void preview_config_input_size(struct isp_prev_device *prev, u32 active) +{ +	const struct v4l2_mbus_framefmt *format = &prev->formats[PREV_PAD_SINK]; +	struct isp_device *isp = to_isp_device(prev); +	unsigned int sph = prev->crop.left; +	unsigned int eph = prev->crop.left + prev->crop.width - 1; +	unsigned int slv = prev->crop.top; +	unsigned int elv = prev->crop.top + prev->crop.height - 1; +	u32 features; + +	if (format->code != V4L2_MBUS_FMT_Y8_1X8 && +	    format->code != V4L2_MBUS_FMT_Y10_1X10) { +		sph -= 2; +		eph += 2; +		slv -= 2; +		elv += 2; +	} + +	features = (prev->params.params[0].features & active) +		 | (prev->params.params[1].features & ~active); + +	if (features & (OMAP3ISP_PREV_DEFECT_COR | OMAP3ISP_PREV_NF)) { +		sph -= 2; +		eph += 2; +		slv -= 2; +		elv += 2; +	} +	if (features & OMAP3ISP_PREV_HRZ_MED) { +		sph -= 2; +		eph += 2; +	} +	if (features & (OMAP3ISP_PREV_CHROMA_SUPP | OMAP3ISP_PREV_LUMAENH)) +		sph -= 2; + +	isp_reg_writel(isp, (sph << ISPPRV_HORZ_INFO_SPH_SHIFT) | eph, +		       OMAP3_ISP_IOMEM_PREV, ISPPRV_HORZ_INFO); +	isp_reg_writel(isp, (slv << ISPPRV_VERT_INFO_SLV_SHIFT) | elv, +		       OMAP3_ISP_IOMEM_PREV, ISPPRV_VERT_INFO); +} + +/* + * preview_config_inlineoffset - Configures the Read address line offset. + * @prev: Preview module + * @offset: Line offset + * + * According to the TRM, the line offset must be aligned on a 32 bytes boundary. + * However, a hardware bug requires the memory start address to be aligned on a + * 64 bytes boundary, so the offset probably should be aligned on 64 bytes as + * well. + */ +static void +preview_config_inlineoffset(struct isp_prev_device *prev, u32 offset) +{ +	struct isp_device *isp = to_isp_device(prev); + +	isp_reg_writel(isp, offset & 0xffff, OMAP3_ISP_IOMEM_PREV, +		       ISPPRV_RADR_OFFSET); +} + +/* + * preview_set_inaddr - Sets memory address of input frame. + * @addr: 32bit memory address aligned on 32byte boundary. + * + * Configures the memory address from which the input frame is to be read. + */ +static void preview_set_inaddr(struct isp_prev_device *prev, u32 addr) +{ +	struct isp_device *isp = to_isp_device(prev); + +	isp_reg_writel(isp, addr, OMAP3_ISP_IOMEM_PREV, ISPPRV_RSDR_ADDR); +} + +/* + * preview_config_outlineoffset - Configures the Write address line offset. + * @offset: Line Offset for the preview output. + * + * The offset must be a multiple of 32 bytes. + */ +static void preview_config_outlineoffset(struct isp_prev_device *prev, +				    u32 offset) +{ +	struct isp_device *isp = to_isp_device(prev); + +	isp_reg_writel(isp, offset & 0xffff, OMAP3_ISP_IOMEM_PREV, +		       ISPPRV_WADD_OFFSET); +} + +/* + * preview_set_outaddr - Sets the memory address to store output frame + * @addr: 32bit memory address aligned on 32byte boundary. + * + * Configures the memory address to which the output frame is written. + */ +static void preview_set_outaddr(struct isp_prev_device *prev, u32 addr) +{ +	struct isp_device *isp = to_isp_device(prev); + +	isp_reg_writel(isp, addr, OMAP3_ISP_IOMEM_PREV, ISPPRV_WSDR_ADDR); +} + +static void preview_adjust_bandwidth(struct isp_prev_device *prev) +{ +	struct isp_pipeline *pipe = to_isp_pipeline(&prev->subdev.entity); +	struct isp_device *isp = to_isp_device(prev); +	const struct v4l2_mbus_framefmt *ifmt = &prev->formats[PREV_PAD_SINK]; +	unsigned long l3_ick = pipe->l3_ick; +	struct v4l2_fract *timeperframe; +	unsigned int cycles_per_frame; +	unsigned int requests_per_frame; +	unsigned int cycles_per_request; +	unsigned int minimum; +	unsigned int maximum; +	unsigned int value; + +	if (prev->input != PREVIEW_INPUT_MEMORY) { +		isp_reg_clr(isp, OMAP3_ISP_IOMEM_SBL, ISPSBL_SDR_REQ_EXP, +			    ISPSBL_SDR_REQ_PRV_EXP_MASK); +		return; +	} + +	/* Compute the minimum number of cycles per request, 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. +	 */ +	cycles_per_request = div_u64((u64)l3_ick / 2 * 256 + pipe->max_rate - 1, +				     pipe->max_rate); +	minimum = DIV_ROUND_UP(cycles_per_request, 32); + +	/* Compute the maximum number of cycles per request, based on the +	 * requested frame rate. This is a soft upper bound to achieve a frame +	 * rate equal or higher than the requested value, so round the value +	 * down. +	 */ +	timeperframe = &pipe->max_timeperframe; + +	requests_per_frame = DIV_ROUND_UP(ifmt->width * 2, 256) * ifmt->height; +	cycles_per_frame = div_u64((u64)l3_ick * timeperframe->numerator, +				   timeperframe->denominator); +	cycles_per_request = cycles_per_frame / requests_per_frame; + +	maximum = cycles_per_request / 32; + +	value = max(minimum, maximum); + +	dev_dbg(isp->dev, "%s: cycles per request = %u\n", __func__, value); +	isp_reg_clr_set(isp, OMAP3_ISP_IOMEM_SBL, ISPSBL_SDR_REQ_EXP, +			ISPSBL_SDR_REQ_PRV_EXP_MASK, +			value << ISPSBL_SDR_REQ_PRV_EXP_SHIFT); +} + +/* + * omap3isp_preview_busy - Gets busy state of preview module. + */ +int omap3isp_preview_busy(struct isp_prev_device *prev) +{ +	struct isp_device *isp = to_isp_device(prev); + +	return isp_reg_readl(isp, OMAP3_ISP_IOMEM_PREV, ISPPRV_PCR) +		& ISPPRV_PCR_BUSY; +} + +/* + * omap3isp_preview_restore_context - Restores the values of preview registers + */ +void omap3isp_preview_restore_context(struct isp_device *isp) +{ +	struct isp_prev_device *prev = &isp->isp_prev; +	const u32 update = OMAP3ISP_PREV_FEATURES_END - 1; + +	prev->params.params[0].update = prev->params.active & update; +	prev->params.params[1].update = ~prev->params.active & update; + +	preview_setup_hw(prev, update, prev->params.active); + +	prev->params.params[0].update = 0; +	prev->params.params[1].update = 0; +} + +/* + * preview_print_status - Dump preview module registers to the kernel log + */ +#define PREV_PRINT_REGISTER(isp, name)\ +	dev_dbg(isp->dev, "###PRV " #name "=0x%08x\n", \ +		isp_reg_readl(isp, OMAP3_ISP_IOMEM_PREV, ISPPRV_##name)) + +static void preview_print_status(struct isp_prev_device *prev) +{ +	struct isp_device *isp = to_isp_device(prev); + +	dev_dbg(isp->dev, "-------------Preview Register dump----------\n"); + +	PREV_PRINT_REGISTER(isp, PCR); +	PREV_PRINT_REGISTER(isp, HORZ_INFO); +	PREV_PRINT_REGISTER(isp, VERT_INFO); +	PREV_PRINT_REGISTER(isp, RSDR_ADDR); +	PREV_PRINT_REGISTER(isp, RADR_OFFSET); +	PREV_PRINT_REGISTER(isp, DSDR_ADDR); +	PREV_PRINT_REGISTER(isp, DRKF_OFFSET); +	PREV_PRINT_REGISTER(isp, WSDR_ADDR); +	PREV_PRINT_REGISTER(isp, WADD_OFFSET); +	PREV_PRINT_REGISTER(isp, AVE); +	PREV_PRINT_REGISTER(isp, HMED); +	PREV_PRINT_REGISTER(isp, NF); +	PREV_PRINT_REGISTER(isp, WB_DGAIN); +	PREV_PRINT_REGISTER(isp, WBGAIN); +	PREV_PRINT_REGISTER(isp, WBSEL); +	PREV_PRINT_REGISTER(isp, CFA); +	PREV_PRINT_REGISTER(isp, BLKADJOFF); +	PREV_PRINT_REGISTER(isp, RGB_MAT1); +	PREV_PRINT_REGISTER(isp, RGB_MAT2); +	PREV_PRINT_REGISTER(isp, RGB_MAT3); +	PREV_PRINT_REGISTER(isp, RGB_MAT4); +	PREV_PRINT_REGISTER(isp, RGB_MAT5); +	PREV_PRINT_REGISTER(isp, RGB_OFF1); +	PREV_PRINT_REGISTER(isp, RGB_OFF2); +	PREV_PRINT_REGISTER(isp, CSC0); +	PREV_PRINT_REGISTER(isp, CSC1); +	PREV_PRINT_REGISTER(isp, CSC2); +	PREV_PRINT_REGISTER(isp, CSC_OFFSET); +	PREV_PRINT_REGISTER(isp, CNT_BRT); +	PREV_PRINT_REGISTER(isp, CSUP); +	PREV_PRINT_REGISTER(isp, SETUP_YC); +	PREV_PRINT_REGISTER(isp, SET_TBL_ADDR); +	PREV_PRINT_REGISTER(isp, CDC_THR0); +	PREV_PRINT_REGISTER(isp, CDC_THR1); +	PREV_PRINT_REGISTER(isp, CDC_THR2); +	PREV_PRINT_REGISTER(isp, CDC_THR3); + +	dev_dbg(isp->dev, "--------------------------------------------\n"); +} + +/* + * preview_init_params - init image processing parameters. + * @prev: pointer to previewer private structure + */ +static void preview_init_params(struct isp_prev_device *prev) +{ +	struct prev_params *params; +	unsigned int i; + +	spin_lock_init(&prev->params.lock); + +	prev->params.active = ~0; +	prev->params.params[0].busy = 0; +	prev->params.params[0].update = OMAP3ISP_PREV_FEATURES_END - 1; +	prev->params.params[1].busy = 0; +	prev->params.params[1].update = 0; + +	params = &prev->params.params[0]; + +	/* Init values */ +	params->contrast = ISPPRV_CONTRAST_DEF * ISPPRV_CONTRAST_UNITS; +	params->brightness = ISPPRV_BRIGHT_DEF * ISPPRV_BRIGHT_UNITS; +	params->cfa.format = OMAP3ISP_CFAFMT_BAYER; +	memcpy(params->cfa.table, cfa_coef_table, +	       sizeof(params->cfa.table)); +	params->cfa.gradthrs_horz = FLR_CFA_GRADTHRS_HORZ; +	params->cfa.gradthrs_vert = FLR_CFA_GRADTHRS_VERT; +	params->csup.gain = FLR_CSUP_GAIN; +	params->csup.thres = FLR_CSUP_THRES; +	params->csup.hypf_en = 0; +	memcpy(params->luma.table, luma_enhance_table, +	       sizeof(params->luma.table)); +	params->nf.spread = FLR_NF_STRGTH; +	memcpy(params->nf.table, noise_filter_table, sizeof(params->nf.table)); +	params->dcor.couplet_mode_en = 1; +	for (i = 0; i < OMAP3ISP_PREV_DETECT_CORRECT_CHANNELS; i++) +		params->dcor.detect_correct[i] = DEF_DETECT_CORRECT_VAL; +	memcpy(params->gamma.blue, gamma_table, sizeof(params->gamma.blue)); +	memcpy(params->gamma.green, gamma_table, sizeof(params->gamma.green)); +	memcpy(params->gamma.red, gamma_table, sizeof(params->gamma.red)); +	params->wbal.dgain = FLR_WBAL_DGAIN; +	params->wbal.coef0 = FLR_WBAL_COEF; +	params->wbal.coef1 = FLR_WBAL_COEF; +	params->wbal.coef2 = FLR_WBAL_COEF; +	params->wbal.coef3 = FLR_WBAL_COEF; +	params->blkadj.red = FLR_BLKADJ_RED; +	params->blkadj.green = FLR_BLKADJ_GREEN; +	params->blkadj.blue = FLR_BLKADJ_BLUE; +	params->rgb2rgb = flr_rgb2rgb; +	params->csc = flr_prev_csc; +	params->yclimit.minC = ISPPRV_YC_MIN; +	params->yclimit.maxC = ISPPRV_YC_MAX; +	params->yclimit.minY = ISPPRV_YC_MIN; +	params->yclimit.maxY = ISPPRV_YC_MAX; + +	params->features = OMAP3ISP_PREV_CFA | OMAP3ISP_PREV_DEFECT_COR +			 | OMAP3ISP_PREV_NF | OMAP3ISP_PREV_GAMMA +			 | OMAP3ISP_PREV_BLKADJ | OMAP3ISP_PREV_YC_LIMIT +			 | OMAP3ISP_PREV_RGB2RGB | OMAP3ISP_PREV_COLOR_CONV +			 | OMAP3ISP_PREV_WB | OMAP3ISP_PREV_BRIGHTNESS +			 | OMAP3ISP_PREV_CONTRAST; +} + +/* + * preview_max_out_width - Handle previewer hardware output limitations + * @prev: pointer to previewer private structure + * returns maximum width output for current isp revision + */ +static unsigned int preview_max_out_width(struct isp_prev_device *prev) +{ +	struct isp_device *isp = to_isp_device(prev); + +	switch (isp->revision) { +	case ISP_REVISION_1_0: +		return PREV_MAX_OUT_WIDTH_REV_1; + +	case ISP_REVISION_2_0: +	default: +		return PREV_MAX_OUT_WIDTH_REV_2; + +	case ISP_REVISION_15_0: +		return PREV_MAX_OUT_WIDTH_REV_15; +	} +} + +static void preview_configure(struct isp_prev_device *prev) +{ +	struct isp_device *isp = to_isp_device(prev); +	const struct isp_format_info *info; +	struct v4l2_mbus_framefmt *format; +	unsigned long flags; +	u32 update; +	u32 active; + +	spin_lock_irqsave(&prev->params.lock, flags); +	/* Mark all active parameters we are going to touch as busy. */ +	update = preview_params_lock(prev, 0, false); +	active = prev->params.active; +	spin_unlock_irqrestore(&prev->params.lock, flags); + +	/* PREV_PAD_SINK */ +	format = &prev->formats[PREV_PAD_SINK]; +	info = omap3isp_video_format_info(format->code); + +	preview_adjust_bandwidth(prev); + +	preview_config_input_format(prev, info); +	preview_config_input_size(prev, active); + +	if (prev->input == PREVIEW_INPUT_CCDC) +		preview_config_inlineoffset(prev, 0); +	else +		preview_config_inlineoffset(prev, ALIGN(format->width, 0x20) * +					    info->bpp); + +	preview_setup_hw(prev, update, active); + +	/* PREV_PAD_SOURCE */ +	format = &prev->formats[PREV_PAD_SOURCE]; + +	if (prev->output & PREVIEW_OUTPUT_MEMORY) +		isp_reg_set(isp, OMAP3_ISP_IOMEM_PREV, ISPPRV_PCR, +			    ISPPRV_PCR_SDRPORT); +	else +		isp_reg_clr(isp, OMAP3_ISP_IOMEM_PREV, ISPPRV_PCR, +			    ISPPRV_PCR_SDRPORT); + +	if (prev->output & PREVIEW_OUTPUT_RESIZER) +		isp_reg_set(isp, OMAP3_ISP_IOMEM_PREV, ISPPRV_PCR, +			    ISPPRV_PCR_RSZPORT); +	else +		isp_reg_clr(isp, OMAP3_ISP_IOMEM_PREV, ISPPRV_PCR, +			    ISPPRV_PCR_RSZPORT); + +	if (prev->output & PREVIEW_OUTPUT_MEMORY) +		preview_config_outlineoffset(prev, +				ALIGN(format->width, 0x10) * 2); + +	preview_config_averager(prev, 0); +	preview_config_ycpos(prev, format->code); + +	spin_lock_irqsave(&prev->params.lock, flags); +	preview_params_unlock(prev, update, false); +	spin_unlock_irqrestore(&prev->params.lock, flags); +} + +/* ----------------------------------------------------------------------------- + * Interrupt handling + */ + +static void preview_enable_oneshot(struct isp_prev_device *prev) +{ +	struct isp_device *isp = to_isp_device(prev); + +	/* The PCR.SOURCE bit is automatically reset to 0 when the PCR.ENABLE +	 * bit is set. As the preview engine is used in single-shot mode, we +	 * need to set PCR.SOURCE before enabling the preview engine. +	 */ +	if (prev->input == PREVIEW_INPUT_MEMORY) +		isp_reg_set(isp, OMAP3_ISP_IOMEM_PREV, ISPPRV_PCR, +			    ISPPRV_PCR_SOURCE); + +	isp_reg_set(isp, OMAP3_ISP_IOMEM_PREV, ISPPRV_PCR, +		    ISPPRV_PCR_EN | ISPPRV_PCR_ONESHOT); +} + +void omap3isp_preview_isr_frame_sync(struct isp_prev_device *prev) +{ +	/* +	 * If ISP_VIDEO_DMAQUEUE_QUEUED is set, DMA queue had an underrun +	 * condition, the module was paused and now we have a buffer queued +	 * on the output again. Restart the pipeline if running in continuous +	 * mode. +	 */ +	if (prev->state == ISP_PIPELINE_STREAM_CONTINUOUS && +	    prev->video_out.dmaqueue_flags & ISP_VIDEO_DMAQUEUE_QUEUED) { +		preview_enable_oneshot(prev); +		isp_video_dmaqueue_flags_clr(&prev->video_out); +	} +} + +static void preview_isr_buffer(struct isp_prev_device *prev) +{ +	struct isp_pipeline *pipe = to_isp_pipeline(&prev->subdev.entity); +	struct isp_buffer *buffer; +	int restart = 0; + +	if (prev->input == PREVIEW_INPUT_MEMORY) { +		buffer = omap3isp_video_buffer_next(&prev->video_in); +		if (buffer != NULL) +			preview_set_inaddr(prev, buffer->dma); +		pipe->state |= ISP_PIPELINE_IDLE_INPUT; +	} + +	if (prev->output & PREVIEW_OUTPUT_MEMORY) { +		buffer = omap3isp_video_buffer_next(&prev->video_out); +		if (buffer != NULL) { +			preview_set_outaddr(prev, buffer->dma); +			restart = 1; +		} +		pipe->state |= ISP_PIPELINE_IDLE_OUTPUT; +	} + +	switch (prev->state) { +	case ISP_PIPELINE_STREAM_SINGLESHOT: +		if (isp_pipeline_ready(pipe)) +			omap3isp_pipeline_set_stream(pipe, +						ISP_PIPELINE_STREAM_SINGLESHOT); +		break; + +	case ISP_PIPELINE_STREAM_CONTINUOUS: +		/* If an underrun occurs, the video queue operation handler will +		 * restart the preview engine. Otherwise restart it immediately. +		 */ +		if (restart) +			preview_enable_oneshot(prev); +		break; + +	case ISP_PIPELINE_STREAM_STOPPED: +	default: +		return; +	} +} + +/* + * omap3isp_preview_isr - ISP preview engine interrupt handler + * + * Manage the preview engine video buffers and configure shadowed registers. + */ +void omap3isp_preview_isr(struct isp_prev_device *prev) +{ +	unsigned long flags; +	u32 update; +	u32 active; + +	if (omap3isp_module_sync_is_stopping(&prev->wait, &prev->stopping)) +		return; + +	spin_lock_irqsave(&prev->params.lock, flags); +	preview_params_switch(prev); +	update = preview_params_lock(prev, 0, false); +	active = prev->params.active; +	spin_unlock_irqrestore(&prev->params.lock, flags); + +	preview_setup_hw(prev, update, active); +	preview_config_input_size(prev, active); + +	if (prev->input == PREVIEW_INPUT_MEMORY || +	    prev->output & PREVIEW_OUTPUT_MEMORY) +		preview_isr_buffer(prev); +	else if (prev->state == ISP_PIPELINE_STREAM_CONTINUOUS) +		preview_enable_oneshot(prev); + +	spin_lock_irqsave(&prev->params.lock, flags); +	preview_params_unlock(prev, update, false); +	spin_unlock_irqrestore(&prev->params.lock, flags); +} + +/* ----------------------------------------------------------------------------- + * ISP video operations + */ + +static int preview_video_queue(struct isp_video *video, +			       struct isp_buffer *buffer) +{ +	struct isp_prev_device *prev = &video->isp->isp_prev; + +	if (video->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) +		preview_set_inaddr(prev, buffer->dma); + +	if (video->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) +		preview_set_outaddr(prev, buffer->dma); + +	return 0; +} + +static const struct isp_video_operations preview_video_ops = { +	.queue = preview_video_queue, +}; + +/* ----------------------------------------------------------------------------- + * V4L2 subdev operations + */ + +/* + * preview_s_ctrl - Handle set control subdev method + * @ctrl: pointer to v4l2 control structure + */ +static int preview_s_ctrl(struct v4l2_ctrl *ctrl) +{ +	struct isp_prev_device *prev = +		container_of(ctrl->handler, struct isp_prev_device, ctrls); + +	switch (ctrl->id) { +	case V4L2_CID_BRIGHTNESS: +		preview_update_brightness(prev, ctrl->val); +		break; +	case V4L2_CID_CONTRAST: +		preview_update_contrast(prev, ctrl->val); +		break; +	} + +	return 0; +} + +static const struct v4l2_ctrl_ops preview_ctrl_ops = { +	.s_ctrl = preview_s_ctrl, +}; + +/* + * preview_ioctl - Handle preview module private ioctl's + * @sd: pointer to v4l2 subdev structure + * @cmd: configuration command + * @arg: configuration argument + * return -EINVAL or zero on success + */ +static long preview_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg) +{ +	struct isp_prev_device *prev = v4l2_get_subdevdata(sd); + +	switch (cmd) { +	case VIDIOC_OMAP3ISP_PRV_CFG: +		return preview_config(prev, arg); + +	default: +		return -ENOIOCTLCMD; +	} +} + +/* + * preview_set_stream - Enable/Disable streaming on preview subdev + * @sd    : pointer to v4l2 subdev structure + * @enable: 1 == Enable, 0 == Disable + * return -EINVAL or zero on success + */ +static int preview_set_stream(struct v4l2_subdev *sd, int enable) +{ +	struct isp_prev_device *prev = v4l2_get_subdevdata(sd); +	struct isp_video *video_out = &prev->video_out; +	struct isp_device *isp = to_isp_device(prev); +	struct device *dev = to_device(prev); + +	if (prev->state == ISP_PIPELINE_STREAM_STOPPED) { +		if (enable == ISP_PIPELINE_STREAM_STOPPED) +			return 0; + +		omap3isp_subclk_enable(isp, OMAP3_ISP_SUBCLK_PREVIEW); +		preview_configure(prev); +		atomic_set(&prev->stopping, 0); +		preview_print_status(prev); +	} + +	switch (enable) { +	case ISP_PIPELINE_STREAM_CONTINUOUS: +		if (prev->output & PREVIEW_OUTPUT_MEMORY) +			omap3isp_sbl_enable(isp, OMAP3_ISP_SBL_PREVIEW_WRITE); + +		if (video_out->dmaqueue_flags & ISP_VIDEO_DMAQUEUE_QUEUED || +		    !(prev->output & PREVIEW_OUTPUT_MEMORY)) +			preview_enable_oneshot(prev); + +		isp_video_dmaqueue_flags_clr(video_out); +		break; + +	case ISP_PIPELINE_STREAM_SINGLESHOT: +		if (prev->input == PREVIEW_INPUT_MEMORY) +			omap3isp_sbl_enable(isp, OMAP3_ISP_SBL_PREVIEW_READ); +		if (prev->output & PREVIEW_OUTPUT_MEMORY) +			omap3isp_sbl_enable(isp, OMAP3_ISP_SBL_PREVIEW_WRITE); + +		preview_enable_oneshot(prev); +		break; + +	case ISP_PIPELINE_STREAM_STOPPED: +		if (omap3isp_module_sync_idle(&sd->entity, &prev->wait, +					      &prev->stopping)) +			dev_dbg(dev, "%s: stop timeout.\n", sd->name); +		omap3isp_sbl_disable(isp, OMAP3_ISP_SBL_PREVIEW_READ); +		omap3isp_sbl_disable(isp, OMAP3_ISP_SBL_PREVIEW_WRITE); +		omap3isp_subclk_disable(isp, OMAP3_ISP_SUBCLK_PREVIEW); +		isp_video_dmaqueue_flags_clr(video_out); +		break; +	} + +	prev->state = enable; +	return 0; +} + +static struct v4l2_mbus_framefmt * +__preview_get_format(struct isp_prev_device *prev, 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 &prev->formats[pad]; +} + +static struct v4l2_rect * +__preview_get_crop(struct isp_prev_device *prev, struct v4l2_subdev_fh *fh, +		   enum v4l2_subdev_format_whence which) +{ +	if (which == V4L2_SUBDEV_FORMAT_TRY) +		return v4l2_subdev_get_try_crop(fh, PREV_PAD_SINK); +	else +		return &prev->crop; +} + +/* previewer format descriptions */ +static const unsigned int preview_input_fmts[] = { +	V4L2_MBUS_FMT_Y8_1X8, +	V4L2_MBUS_FMT_SGRBG8_1X8, +	V4L2_MBUS_FMT_SRGGB8_1X8, +	V4L2_MBUS_FMT_SBGGR8_1X8, +	V4L2_MBUS_FMT_SGBRG8_1X8, +	V4L2_MBUS_FMT_Y10_1X10, +	V4L2_MBUS_FMT_SGRBG10_1X10, +	V4L2_MBUS_FMT_SRGGB10_1X10, +	V4L2_MBUS_FMT_SBGGR10_1X10, +	V4L2_MBUS_FMT_SGBRG10_1X10, +}; + +static const unsigned int preview_output_fmts[] = { +	V4L2_MBUS_FMT_UYVY8_1X16, +	V4L2_MBUS_FMT_YUYV8_1X16, +}; + +/* + * preview_try_format - Validate a format + * @prev: ISP preview engine + * @fh: V4L2 subdev file handle + * @pad: pad number + * @fmt: format to be validated + * @which: try/active format selector + * + * Validate and adjust the given format for the given pad based on the preview + * engine limits and the format and crop rectangles on other pads. + */ +static void preview_try_format(struct isp_prev_device *prev, +			       struct v4l2_subdev_fh *fh, unsigned int pad, +			       struct v4l2_mbus_framefmt *fmt, +			       enum v4l2_subdev_format_whence which) +{ +	enum v4l2_mbus_pixelcode pixelcode; +	struct v4l2_rect *crop; +	unsigned int i; + +	switch (pad) { +	case PREV_PAD_SINK: +		/* When reading data from the CCDC, the input size has already +		 * been mangled by the CCDC output pad so it can be accepted +		 * as-is. +		 * +		 * When reading data from memory, clamp the requested width and +		 * height. The TRM doesn't specify a minimum input height, make +		 * sure we got enough lines to enable the noise filter and color +		 * filter array interpolation. +		 */ +		if (prev->input == PREVIEW_INPUT_MEMORY) { +			fmt->width = clamp_t(u32, fmt->width, PREV_MIN_IN_WIDTH, +					     preview_max_out_width(prev)); +			fmt->height = clamp_t(u32, fmt->height, +					      PREV_MIN_IN_HEIGHT, +					      PREV_MAX_IN_HEIGHT); +		} + +		fmt->colorspace = V4L2_COLORSPACE_SRGB; + +		for (i = 0; i < ARRAY_SIZE(preview_input_fmts); i++) { +			if (fmt->code == preview_input_fmts[i]) +				break; +		} + +		/* If not found, use SGRBG10 as default */ +		if (i >= ARRAY_SIZE(preview_input_fmts)) +			fmt->code = V4L2_MBUS_FMT_SGRBG10_1X10; +		break; + +	case PREV_PAD_SOURCE: +		pixelcode = fmt->code; +		*fmt = *__preview_get_format(prev, fh, PREV_PAD_SINK, which); + +		switch (pixelcode) { +		case V4L2_MBUS_FMT_YUYV8_1X16: +		case V4L2_MBUS_FMT_UYVY8_1X16: +			fmt->code = pixelcode; +			break; + +		default: +			fmt->code = V4L2_MBUS_FMT_YUYV8_1X16; +			break; +		} + +		/* The preview module output size is configurable through the +		 * averager (horizontal scaling by 1/1, 1/2, 1/4 or 1/8). This +		 * is not supported yet, hardcode the output size to the crop +		 * rectangle size. +		 */ +		crop = __preview_get_crop(prev, fh, which); +		fmt->width = crop->width; +		fmt->height = crop->height; + +		fmt->colorspace = V4L2_COLORSPACE_JPEG; +		break; +	} + +	fmt->field = V4L2_FIELD_NONE; +} + +/* + * preview_try_crop - Validate a crop rectangle + * @prev: ISP preview engine + * @sink: format on the sink pad + * @crop: crop rectangle to be validated + * + * The preview engine crops lines and columns for its internal operation, + * depending on which filters are enabled. Enforce minimum crop margins to + * handle that transparently for userspace. + * + * See the explanation at the PREV_MARGIN_* definitions for more details. + */ +static void preview_try_crop(struct isp_prev_device *prev, +			     const struct v4l2_mbus_framefmt *sink, +			     struct v4l2_rect *crop) +{ +	unsigned int left = PREV_MARGIN_LEFT; +	unsigned int right = sink->width - PREV_MARGIN_RIGHT; +	unsigned int top = PREV_MARGIN_TOP; +	unsigned int bottom = sink->height - PREV_MARGIN_BOTTOM; + +	/* When processing data on-the-fly from the CCDC, at least 2 pixels must +	 * be cropped from the left and right sides of the image. As we don't +	 * know which filters will be enabled, increase the left and right +	 * margins by two. +	 */ +	if (prev->input == PREVIEW_INPUT_CCDC) { +		left += 2; +		right -= 2; +	} + +	/* The CFA filter crops 4 lines and 4 columns in Bayer mode, and 2 lines +	 * and no columns in other modes. Increase the margins based on the sink +	 * format. +	 */ +	if (sink->code != V4L2_MBUS_FMT_Y8_1X8 && +	    sink->code != V4L2_MBUS_FMT_Y10_1X10) { +		left += 2; +		right -= 2; +		top += 2; +		bottom -= 2; +	} + +	/* Restrict left/top to even values to keep the Bayer pattern. */ +	crop->left &= ~1; +	crop->top &= ~1; + +	crop->left = clamp_t(u32, crop->left, left, right - PREV_MIN_OUT_WIDTH); +	crop->top = clamp_t(u32, crop->top, top, bottom - PREV_MIN_OUT_HEIGHT); +	crop->width = clamp_t(u32, crop->width, PREV_MIN_OUT_WIDTH, +			      right - crop->left); +	crop->height = clamp_t(u32, crop->height, PREV_MIN_OUT_HEIGHT, +			       bottom - crop->top); +} + +/* + * preview_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 preview_enum_mbus_code(struct v4l2_subdev *sd, +				  struct v4l2_subdev_fh *fh, +				  struct v4l2_subdev_mbus_code_enum *code) +{ +	switch (code->pad) { +	case PREV_PAD_SINK: +		if (code->index >= ARRAY_SIZE(preview_input_fmts)) +			return -EINVAL; + +		code->code = preview_input_fmts[code->index]; +		break; +	case PREV_PAD_SOURCE: +		if (code->index >= ARRAY_SIZE(preview_output_fmts)) +			return -EINVAL; + +		code->code = preview_output_fmts[code->index]; +		break; +	default: +		return -EINVAL; +	} + +	return 0; +} + +static int preview_enum_frame_size(struct v4l2_subdev *sd, +				   struct v4l2_subdev_fh *fh, +				   struct v4l2_subdev_frame_size_enum *fse) +{ +	struct isp_prev_device *prev = 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; +	preview_try_format(prev, 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; +	preview_try_format(prev, fh, fse->pad, &format, V4L2_SUBDEV_FORMAT_TRY); +	fse->max_width = format.width; +	fse->max_height = format.height; + +	return 0; +} + +/* + * preview_get_selection - Retrieve a selection rectangle on a pad + * @sd: ISP preview V4L2 subdevice + * @fh: V4L2 subdev file handle + * @sel: Selection rectangle + * + * The only supported rectangles are the crop rectangles on the sink pad. + * + * Return 0 on success or a negative error code otherwise. + */ +static int preview_get_selection(struct v4l2_subdev *sd, +				 struct v4l2_subdev_fh *fh, +				 struct v4l2_subdev_selection *sel) +{ +	struct isp_prev_device *prev = v4l2_get_subdevdata(sd); +	struct v4l2_mbus_framefmt *format; + +	if (sel->pad != PREV_PAD_SINK) +		return -EINVAL; + +	switch (sel->target) { +	case V4L2_SEL_TGT_CROP_BOUNDS: +		sel->r.left = 0; +		sel->r.top = 0; +		sel->r.width = INT_MAX; +		sel->r.height = INT_MAX; + +		format = __preview_get_format(prev, fh, PREV_PAD_SINK, +					      sel->which); +		preview_try_crop(prev, format, &sel->r); +		break; + +	case V4L2_SEL_TGT_CROP: +		sel->r = *__preview_get_crop(prev, fh, sel->which); +		break; + +	default: +		return -EINVAL; +	} + +	return 0; +} + +/* + * preview_set_selection - Set a selection rectangle on a pad + * @sd: ISP preview V4L2 subdevice + * @fh: V4L2 subdev file handle + * @sel: Selection rectangle + * + * The only supported rectangle is the actual crop rectangle on the sink pad. + * + * Return 0 on success or a negative error code otherwise. + */ +static int preview_set_selection(struct v4l2_subdev *sd, +				 struct v4l2_subdev_fh *fh, +				 struct v4l2_subdev_selection *sel) +{ +	struct isp_prev_device *prev = v4l2_get_subdevdata(sd); +	struct v4l2_mbus_framefmt *format; + +	if (sel->target != V4L2_SEL_TGT_CROP || +	    sel->pad != PREV_PAD_SINK) +		return -EINVAL; + +	/* The crop rectangle can't be changed while streaming. */ +	if (prev->state != ISP_PIPELINE_STREAM_STOPPED) +		return -EBUSY; + +	/* Modifying the crop rectangle always changes the format on the source +	 * pad. If the KEEP_CONFIG flag is set, just return the current crop +	 * rectangle. +	 */ +	if (sel->flags & V4L2_SEL_FLAG_KEEP_CONFIG) { +		sel->r = *__preview_get_crop(prev, fh, sel->which); +		return 0; +	} + +	format = __preview_get_format(prev, fh, PREV_PAD_SINK, sel->which); +	preview_try_crop(prev, format, &sel->r); +	*__preview_get_crop(prev, fh, sel->which) = sel->r; + +	/* Update the source format. */ +	format = __preview_get_format(prev, fh, PREV_PAD_SOURCE, sel->which); +	preview_try_format(prev, fh, PREV_PAD_SOURCE, format, sel->which); + +	return 0; +} + +/* + * preview_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 preview_get_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, +			      struct v4l2_subdev_format *fmt) +{ +	struct isp_prev_device *prev = v4l2_get_subdevdata(sd); +	struct v4l2_mbus_framefmt *format; + +	format = __preview_get_format(prev, fh, fmt->pad, fmt->which); +	if (format == NULL) +		return -EINVAL; + +	fmt->format = *format; +	return 0; +} + +/* + * preview_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 + * return -EINVAL or zero on success + */ +static int preview_set_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, +			      struct v4l2_subdev_format *fmt) +{ +	struct isp_prev_device *prev = v4l2_get_subdevdata(sd); +	struct v4l2_mbus_framefmt *format; +	struct v4l2_rect *crop; + +	format = __preview_get_format(prev, fh, fmt->pad, fmt->which); +	if (format == NULL) +		return -EINVAL; + +	preview_try_format(prev, fh, fmt->pad, &fmt->format, fmt->which); +	*format = fmt->format; + +	/* Propagate the format from sink to source */ +	if (fmt->pad == PREV_PAD_SINK) { +		/* Reset the crop rectangle. */ +		crop = __preview_get_crop(prev, fh, fmt->which); +		crop->left = 0; +		crop->top = 0; +		crop->width = fmt->format.width; +		crop->height = fmt->format.height; + +		preview_try_crop(prev, &fmt->format, crop); + +		/* Update the source format. */ +		format = __preview_get_format(prev, fh, PREV_PAD_SOURCE, +					      fmt->which); +		preview_try_format(prev, fh, PREV_PAD_SOURCE, format, +				   fmt->which); +	} + +	return 0; +} + +/* + * preview_init_formats - Initialize formats on all pads + * @sd: ISP preview 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 preview_init_formats(struct v4l2_subdev *sd, +				struct v4l2_subdev_fh *fh) +{ +	struct v4l2_subdev_format format; + +	memset(&format, 0, sizeof(format)); +	format.pad = PREV_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; +	preview_set_format(sd, fh, &format); + +	return 0; +} + +/* subdev core operations */ +static const struct v4l2_subdev_core_ops preview_v4l2_core_ops = { +	.ioctl = preview_ioctl, +}; + +/* subdev video operations */ +static const struct v4l2_subdev_video_ops preview_v4l2_video_ops = { +	.s_stream = preview_set_stream, +}; + +/* subdev pad operations */ +static const struct v4l2_subdev_pad_ops preview_v4l2_pad_ops = { +	.enum_mbus_code = preview_enum_mbus_code, +	.enum_frame_size = preview_enum_frame_size, +	.get_fmt = preview_get_format, +	.set_fmt = preview_set_format, +	.get_selection = preview_get_selection, +	.set_selection = preview_set_selection, +}; + +/* subdev operations */ +static const struct v4l2_subdev_ops preview_v4l2_ops = { +	.core = &preview_v4l2_core_ops, +	.video = &preview_v4l2_video_ops, +	.pad = &preview_v4l2_pad_ops, +}; + +/* subdev internal operations */ +static const struct v4l2_subdev_internal_ops preview_v4l2_internal_ops = { +	.open = preview_init_formats, +}; + +/* ----------------------------------------------------------------------------- + * Media entity operations + */ + +/* + * preview_link_setup - Setup previewer connections. + * @entity : Pointer to media entity structure + * @local  : Pointer to local pad array + * @remote : Pointer to remote pad array + * @flags  : Link flags + * return -EINVAL or zero on success + */ +static int preview_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_prev_device *prev = v4l2_get_subdevdata(sd); + +	switch (local->index | media_entity_type(remote->entity)) { +	case PREV_PAD_SINK | MEDIA_ENT_T_DEVNODE: +		/* read from memory */ +		if (flags & MEDIA_LNK_FL_ENABLED) { +			if (prev->input == PREVIEW_INPUT_CCDC) +				return -EBUSY; +			prev->input = PREVIEW_INPUT_MEMORY; +		} else { +			if (prev->input == PREVIEW_INPUT_MEMORY) +				prev->input = PREVIEW_INPUT_NONE; +		} +		break; + +	case PREV_PAD_SINK | MEDIA_ENT_T_V4L2_SUBDEV: +		/* read from ccdc */ +		if (flags & MEDIA_LNK_FL_ENABLED) { +			if (prev->input == PREVIEW_INPUT_MEMORY) +				return -EBUSY; +			prev->input = PREVIEW_INPUT_CCDC; +		} else { +			if (prev->input == PREVIEW_INPUT_CCDC) +				prev->input = PREVIEW_INPUT_NONE; +		} +		break; + +	/* +	 * The ISP core doesn't support pipelines with multiple video outputs. +	 * Revisit this when it will be implemented, and return -EBUSY for now. +	 */ + +	case PREV_PAD_SOURCE | MEDIA_ENT_T_DEVNODE: +		/* write to memory */ +		if (flags & MEDIA_LNK_FL_ENABLED) { +			if (prev->output & ~PREVIEW_OUTPUT_MEMORY) +				return -EBUSY; +			prev->output |= PREVIEW_OUTPUT_MEMORY; +		} else { +			prev->output &= ~PREVIEW_OUTPUT_MEMORY; +		} +		break; + +	case PREV_PAD_SOURCE | MEDIA_ENT_T_V4L2_SUBDEV: +		/* write to resizer */ +		if (flags & MEDIA_LNK_FL_ENABLED) { +			if (prev->output & ~PREVIEW_OUTPUT_RESIZER) +				return -EBUSY; +			prev->output |= PREVIEW_OUTPUT_RESIZER; +		} else { +			prev->output &= ~PREVIEW_OUTPUT_RESIZER; +		} +		break; + +	default: +		return -EINVAL; +	} + +	return 0; +} + +/* media operations */ +static const struct media_entity_operations preview_media_ops = { +	.link_setup = preview_link_setup, +	.link_validate = v4l2_subdev_link_validate, +}; + +void omap3isp_preview_unregister_entities(struct isp_prev_device *prev) +{ +	v4l2_device_unregister_subdev(&prev->subdev); +	omap3isp_video_unregister(&prev->video_in); +	omap3isp_video_unregister(&prev->video_out); +} + +int omap3isp_preview_register_entities(struct isp_prev_device *prev, +	struct v4l2_device *vdev) +{ +	int ret; + +	/* Register the subdev and video nodes. */ +	ret = v4l2_device_register_subdev(vdev, &prev->subdev); +	if (ret < 0) +		goto error; + +	ret = omap3isp_video_register(&prev->video_in, vdev); +	if (ret < 0) +		goto error; + +	ret = omap3isp_video_register(&prev->video_out, vdev); +	if (ret < 0) +		goto error; + +	return 0; + +error: +	omap3isp_preview_unregister_entities(prev); +	return ret; +} + +/* ----------------------------------------------------------------------------- + * ISP previewer initialisation and cleanup + */ + +/* + * preview_init_entities - Initialize subdev and media entity. + * @prev : Pointer to preview structure + * return -ENOMEM or zero on success + */ +static int preview_init_entities(struct isp_prev_device *prev) +{ +	struct v4l2_subdev *sd = &prev->subdev; +	struct media_pad *pads = prev->pads; +	struct media_entity *me = &sd->entity; +	int ret; + +	prev->input = PREVIEW_INPUT_NONE; + +	v4l2_subdev_init(sd, &preview_v4l2_ops); +	sd->internal_ops = &preview_v4l2_internal_ops; +	strlcpy(sd->name, "OMAP3 ISP preview", sizeof(sd->name)); +	sd->grp_id = 1 << 16;	/* group ID for isp subdevs */ +	v4l2_set_subdevdata(sd, prev); +	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + +	v4l2_ctrl_handler_init(&prev->ctrls, 2); +	v4l2_ctrl_new_std(&prev->ctrls, &preview_ctrl_ops, V4L2_CID_BRIGHTNESS, +			  ISPPRV_BRIGHT_LOW, ISPPRV_BRIGHT_HIGH, +			  ISPPRV_BRIGHT_STEP, ISPPRV_BRIGHT_DEF); +	v4l2_ctrl_new_std(&prev->ctrls, &preview_ctrl_ops, V4L2_CID_CONTRAST, +			  ISPPRV_CONTRAST_LOW, ISPPRV_CONTRAST_HIGH, +			  ISPPRV_CONTRAST_STEP, ISPPRV_CONTRAST_DEF); +	v4l2_ctrl_handler_setup(&prev->ctrls); +	sd->ctrl_handler = &prev->ctrls; + +	pads[PREV_PAD_SINK].flags = MEDIA_PAD_FL_SINK +				    | MEDIA_PAD_FL_MUST_CONNECT; +	pads[PREV_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; + +	me->ops = &preview_media_ops; +	ret = media_entity_init(me, PREV_PADS_NUM, pads, 0); +	if (ret < 0) +		return ret; + +	preview_init_formats(sd, NULL); + +	/* According to the OMAP34xx TRM, video buffers need to be aligned on a +	 * 32 bytes boundary. However, an undocumented hardware bug requires a +	 * 64 bytes boundary at the preview engine input. +	 */ +	prev->video_in.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; +	prev->video_in.ops = &preview_video_ops; +	prev->video_in.isp = to_isp_device(prev); +	prev->video_in.capture_mem = PAGE_ALIGN(4096 * 4096) * 2 * 3; +	prev->video_in.bpl_alignment = 64; +	prev->video_out.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; +	prev->video_out.ops = &preview_video_ops; +	prev->video_out.isp = to_isp_device(prev); +	prev->video_out.capture_mem = PAGE_ALIGN(4096 * 4096) * 2 * 3; +	prev->video_out.bpl_alignment = 32; + +	ret = omap3isp_video_init(&prev->video_in, "preview"); +	if (ret < 0) +		goto error_video_in; + +	ret = omap3isp_video_init(&prev->video_out, "preview"); +	if (ret < 0) +		goto error_video_out; + +	/* Connect the video nodes to the previewer subdev. */ +	ret = media_entity_create_link(&prev->video_in.video.entity, 0, +			&prev->subdev.entity, PREV_PAD_SINK, 0); +	if (ret < 0) +		goto error_link; + +	ret = media_entity_create_link(&prev->subdev.entity, PREV_PAD_SOURCE, +			&prev->video_out.video.entity, 0, 0); +	if (ret < 0) +		goto error_link; + +	return 0; + +error_link: +	omap3isp_video_cleanup(&prev->video_out); +error_video_out: +	omap3isp_video_cleanup(&prev->video_in); +error_video_in: +	media_entity_cleanup(&prev->subdev.entity); +	return ret; +} + +/* + * omap3isp_preview_init - Previewer initialization. + * @isp : Pointer to ISP device + * return -ENOMEM or zero on success + */ +int omap3isp_preview_init(struct isp_device *isp) +{ +	struct isp_prev_device *prev = &isp->isp_prev; + +	init_waitqueue_head(&prev->wait); + +	preview_init_params(prev); + +	return preview_init_entities(prev); +} + +void omap3isp_preview_cleanup(struct isp_device *isp) +{ +	struct isp_prev_device *prev = &isp->isp_prev; + +	v4l2_ctrl_handler_free(&prev->ctrls); +	omap3isp_video_cleanup(&prev->video_in); +	omap3isp_video_cleanup(&prev->video_out); +	media_entity_cleanup(&prev->subdev.entity); +} diff --git a/drivers/media/platform/omap3isp/isppreview.h b/drivers/media/platform/omap3isp/isppreview.h new file mode 100644 index 00000000000..f66923407f8 --- /dev/null +++ b/drivers/media/platform/omap3isp/isppreview.h @@ -0,0 +1,174 @@ +/* + * isppreview.h + * + * TI OMAP3 ISP - Preview module + * + * Copyright (C) 2010 Nokia Corporation + * Copyright (C) 2009 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 + */ + +#ifndef OMAP3_ISP_PREVIEW_H +#define OMAP3_ISP_PREVIEW_H + +#include <linux/omap3isp.h> +#include <linux/types.h> +#include <media/v4l2-ctrls.h> + +#include "ispvideo.h" + +#define ISPPRV_BRIGHT_STEP		0x1 +#define ISPPRV_BRIGHT_DEF		0x0 +#define ISPPRV_BRIGHT_LOW		0x0 +#define ISPPRV_BRIGHT_HIGH		0xFF +#define ISPPRV_BRIGHT_UNITS		0x1 + +#define ISPPRV_CONTRAST_STEP		0x1 +#define ISPPRV_CONTRAST_DEF		0x10 +#define ISPPRV_CONTRAST_LOW		0x0 +#define ISPPRV_CONTRAST_HIGH		0xFF +#define ISPPRV_CONTRAST_UNITS		0x1 + +/* Additional features not listed in linux/omap3isp.h */ +#define OMAP3ISP_PREV_CONTRAST		(1 << 17) +#define OMAP3ISP_PREV_BRIGHTNESS	(1 << 18) +#define OMAP3ISP_PREV_FEATURES_END	(1 << 19) + +enum preview_input_entity { +	PREVIEW_INPUT_NONE, +	PREVIEW_INPUT_CCDC, +	PREVIEW_INPUT_MEMORY, +}; + +#define PREVIEW_OUTPUT_RESIZER		(1 << 1) +#define PREVIEW_OUTPUT_MEMORY		(1 << 2) + +/* Configure byte layout of YUV image */ +enum preview_ycpos_mode { +	YCPOS_YCrYCb = 0, +	YCPOS_YCbYCr = 1, +	YCPOS_CbYCrY = 2, +	YCPOS_CrYCbY = 3 +}; + +/* + * struct prev_params - Structure for all configuration + * @busy: Bitmask of busy parameters (being updated or used) + * @update: Bitmask of the parameters to be updated + * @features: Set of features enabled. + * @cfa: CFA coefficients. + * @csup: Chroma suppression coefficients. + * @luma: Luma enhancement coefficients. + * @nf: Noise filter coefficients. + * @dcor: Noise filter coefficients. + * @gamma: Gamma coefficients. + * @wbal: White Balance parameters. + * @blkadj: Black adjustment parameters. + * @rgb2rgb: RGB blending parameters. + * @csc: Color space conversion (RGB to YCbCr) parameters. + * @hmed: Horizontal median filter. + * @yclimit: YC limits parameters. + * @contrast: Contrast. + * @brightness: Brightness. + */ +struct prev_params { +	u32 busy; +	u32 update; +	u32 features; +	struct omap3isp_prev_cfa cfa; +	struct omap3isp_prev_csup csup; +	struct omap3isp_prev_luma luma; +	struct omap3isp_prev_nf nf; +	struct omap3isp_prev_dcor dcor; +	struct omap3isp_prev_gtables gamma; +	struct omap3isp_prev_wbal wbal; +	struct omap3isp_prev_blkadj blkadj; +	struct omap3isp_prev_rgbtorgb rgb2rgb; +	struct omap3isp_prev_csc csc; +	struct omap3isp_prev_hmed hmed; +	struct omap3isp_prev_yclimit yclimit; +	u8 contrast; +	u8 brightness; +}; + +/* Sink and source previewer pads */ +#define PREV_PAD_SINK			0 +#define PREV_PAD_SOURCE			1 +#define PREV_PADS_NUM			2 + +/* + * struct isp_prev_device - Structure for storing ISP Preview module information + * @subdev: V4L2 subdevice + * @pads: Media entity pads + * @formats: Active formats at the subdev pad + * @crop: Active crop rectangle + * @input: Module currently connected to the input pad + * @output: Bitmask of the active output + * @video_in: Input video entity + * @video_out: Output video entity + * @params.params : Active and shadow parameters sets + * @params.active: Bitmask of parameters active in set 0 + * @params.lock: Parameters lock, protects params.active and params.shadow + * @underrun: Whether the preview entity has queued buffers on the output + * @state: Current preview pipeline state + * + * This structure is used to store the OMAP ISP Preview module Information. + */ +struct isp_prev_device { +	struct v4l2_subdev subdev; +	struct media_pad pads[PREV_PADS_NUM]; +	struct v4l2_mbus_framefmt formats[PREV_PADS_NUM]; +	struct v4l2_rect crop; + +	struct v4l2_ctrl_handler ctrls; + +	enum preview_input_entity input; +	unsigned int output; +	struct isp_video video_in; +	struct isp_video video_out; + +	struct { +		unsigned int cfa_order; +		struct prev_params params[2]; +		u32 active; +		spinlock_t lock; +	} params; + +	enum isp_pipeline_stream_state state; +	wait_queue_head_t wait; +	atomic_t stopping; +}; + +struct isp_device; + +int omap3isp_preview_init(struct isp_device *isp); +void omap3isp_preview_cleanup(struct isp_device *isp); + +int omap3isp_preview_register_entities(struct isp_prev_device *prv, +				       struct v4l2_device *vdev); +void omap3isp_preview_unregister_entities(struct isp_prev_device *prv); + +void omap3isp_preview_isr_frame_sync(struct isp_prev_device *prev); +void omap3isp_preview_isr(struct isp_prev_device *prev); + +int omap3isp_preview_busy(struct isp_prev_device *isp_prev); + +void omap3isp_preview_restore_context(struct isp_device *isp); + +#endif	/* OMAP3_ISP_PREVIEW_H */ diff --git a/drivers/media/platform/omap3isp/ispreg.h b/drivers/media/platform/omap3isp/ispreg.h new file mode 100644 index 00000000000..b7d90e6fb01 --- /dev/null +++ b/drivers/media/platform/omap3isp/ispreg.h @@ -0,0 +1,1531 @@ +/* + * ispreg.h + * + * TI OMAP3 ISP - Registers definitions + * + * Copyright (C) 2010 Nokia Corporation + * Copyright (C) 2009 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 + */ + +#ifndef OMAP3_ISP_REG_H +#define OMAP3_ISP_REG_H + +#define CM_CAM_MCLK_HZ			172800000	/* Hz */ + +/* ISP module register offset */ + +#define ISP_REVISION			(0x000) +#define ISP_SYSCONFIG			(0x004) +#define ISP_SYSSTATUS			(0x008) +#define ISP_IRQ0ENABLE			(0x00C) +#define ISP_IRQ0STATUS			(0x010) +#define ISP_IRQ1ENABLE			(0x014) +#define ISP_IRQ1STATUS			(0x018) +#define ISP_TCTRL_GRESET_LENGTH		(0x030) +#define ISP_TCTRL_PSTRB_REPLAY		(0x034) +#define ISP_CTRL			(0x040) +#define ISP_SECURE			(0x044) +#define ISP_TCTRL_CTRL			(0x050) +#define ISP_TCTRL_FRAME			(0x054) +#define ISP_TCTRL_PSTRB_DELAY		(0x058) +#define ISP_TCTRL_STRB_DELAY		(0x05C) +#define ISP_TCTRL_SHUT_DELAY		(0x060) +#define ISP_TCTRL_PSTRB_LENGTH		(0x064) +#define ISP_TCTRL_STRB_LENGTH		(0x068) +#define ISP_TCTRL_SHUT_LENGTH		(0x06C) +#define ISP_PING_PONG_ADDR		(0x070) +#define ISP_PING_PONG_MEM_RANGE		(0x074) +#define ISP_PING_PONG_BUF_SIZE		(0x078) + +/* CCP2 receiver registers */ + +#define ISPCCP2_REVISION		(0x000) +#define ISPCCP2_SYSCONFIG		(0x004) +#define ISPCCP2_SYSCONFIG_SOFT_RESET	(1 << 1) +#define ISPCCP2_SYSCONFIG_AUTO_IDLE		0x1 +#define ISPCCP2_SYSCONFIG_MSTANDBY_MODE_SHIFT	12 +#define ISPCCP2_SYSCONFIG_MSTANDBY_MODE_FORCE	\ +	(0x0 << ISPCCP2_SYSCONFIG_MSTANDBY_MODE_SHIFT) +#define ISPCCP2_SYSCONFIG_MSTANDBY_MODE_NO	\ +	(0x1 << ISPCCP2_SYSCONFIG_MSTANDBY_MODE_SHIFT) +#define ISPCCP2_SYSCONFIG_MSTANDBY_MODE_SMART	\ +	(0x2 << ISPCCP2_SYSCONFIG_MSTANDBY_MODE_SHIFT) +#define ISPCCP2_SYSSTATUS		(0x008) +#define ISPCCP2_SYSSTATUS_RESET_DONE	(1 << 0) +#define ISPCCP2_LC01_IRQENABLE		(0x00C) +#define ISPCCP2_LC01_IRQSTATUS		(0x010) +#define ISPCCP2_LC01_IRQSTATUS_LC0_FS_IRQ	(1 << 11) +#define ISPCCP2_LC01_IRQSTATUS_LC0_LE_IRQ	(1 << 10) +#define ISPCCP2_LC01_IRQSTATUS_LC0_LS_IRQ	(1 << 9) +#define ISPCCP2_LC01_IRQSTATUS_LC0_FE_IRQ	(1 << 8) +#define ISPCCP2_LC01_IRQSTATUS_LC0_COUNT_IRQ	(1 << 7) +#define ISPCCP2_LC01_IRQSTATUS_LC0_FIFO_OVF_IRQ	(1 << 5) +#define ISPCCP2_LC01_IRQSTATUS_LC0_CRC_IRQ	(1 << 4) +#define ISPCCP2_LC01_IRQSTATUS_LC0_FSP_IRQ	(1 << 3) +#define ISPCCP2_LC01_IRQSTATUS_LC0_FW_IRQ	(1 << 2) +#define ISPCCP2_LC01_IRQSTATUS_LC0_FSC_IRQ	(1 << 1) +#define ISPCCP2_LC01_IRQSTATUS_LC0_SSC_IRQ	(1 << 0) + +#define ISPCCP2_LC23_IRQENABLE		(0x014) +#define ISPCCP2_LC23_IRQSTATUS		(0x018) +#define ISPCCP2_LCM_IRQENABLE		(0x02C) +#define ISPCCP2_LCM_IRQSTATUS_EOF_IRQ		(1 << 0) +#define ISPCCP2_LCM_IRQSTATUS_OCPERROR_IRQ	(1 << 1) +#define ISPCCP2_LCM_IRQSTATUS		(0x030) +#define ISPCCP2_CTRL			(0x040) +#define ISPCCP2_CTRL_IF_EN		(1 << 0) +#define ISPCCP2_CTRL_PHY_SEL		(1 << 1) +#define ISPCCP2_CTRL_PHY_SEL_CLOCK	(0 << 1) +#define ISPCCP2_CTRL_PHY_SEL_STROBE	(1 << 1) +#define ISPCCP2_CTRL_PHY_SEL_MASK	0x1 +#define ISPCCP2_CTRL_PHY_SEL_SHIFT	1 +#define ISPCCP2_CTRL_IO_OUT_SEL		(1 << 2) +#define ISPCCP2_CTRL_MODE		(1 << 4) +#define ISPCCP2_CTRL_VP_CLK_FORCE_ON	(1 << 9) +#define ISPCCP2_CTRL_INV		(1 << 10) +#define ISPCCP2_CTRL_INV_MASK		0x1 +#define ISPCCP2_CTRL_INV_SHIFT		10 +#define ISPCCP2_CTRL_VP_ONLY_EN		(1 << 11) +#define ISPCCP2_CTRL_VP_CLK_POL		(1 << 12) +#define ISPCCP2_CTRL_VPCLK_DIV_SHIFT	15 +#define ISPCCP2_CTRL_VPCLK_DIV_MASK	0x1ffff /* [31:15] */ +#define ISPCCP2_CTRL_VP_OUT_CTRL_SHIFT	8 /* 3430 bits */ +#define ISPCCP2_CTRL_VP_OUT_CTRL_MASK	0x3 /* 3430 bits */ +#define ISPCCP2_DBG			(0x044) +#define ISPCCP2_GNQ			(0x048) +#define ISPCCP2_LCx_CTRL(x)			((0x050)+0x30*(x)) +#define ISPCCP2_LCx_CTRL_CHAN_EN		(1 << 0) +#define ISPCCP2_LCx_CTRL_CRC_EN			(1 << 19) +#define ISPCCP2_LCx_CTRL_CRC_MASK		0x1 +#define ISPCCP2_LCx_CTRL_CRC_SHIFT		2 +#define ISPCCP2_LCx_CTRL_CRC_SHIFT_15_0		19 +#define ISPCCP2_LCx_CTRL_REGION_EN		(1 << 1) +#define ISPCCP2_LCx_CTRL_REGION_MASK		0x1 +#define ISPCCP2_LCx_CTRL_REGION_SHIFT		1 +#define ISPCCP2_LCx_CTRL_FORMAT_MASK_15_0	0x3f +#define ISPCCP2_LCx_CTRL_FORMAT_SHIFT_15_0	0x2 +#define ISPCCP2_LCx_CTRL_FORMAT_MASK		0x1f +#define ISPCCP2_LCx_CTRL_FORMAT_SHIFT		0x3 +#define ISPCCP2_LCx_CODE(x)		((0x054)+0x30*(x)) +#define ISPCCP2_LCx_STAT_START(x)	((0x058)+0x30*(x)) +#define ISPCCP2_LCx_STAT_SIZE(x)	((0x05C)+0x30*(x)) +#define ISPCCP2_LCx_SOF_ADDR(x)		((0x060)+0x30*(x)) +#define ISPCCP2_LCx_EOF_ADDR(x)		((0x064)+0x30*(x)) +#define ISPCCP2_LCx_DAT_START(x)	((0x068)+0x30*(x)) +#define ISPCCP2_LCx_DAT_SIZE(x)		((0x06C)+0x30*(x)) +#define ISPCCP2_LCx_DAT_MASK		0xFFF +#define ISPCCP2_LCx_DAT_SHIFT		16 +#define ISPCCP2_LCx_DAT_PING_ADDR(x)	((0x070)+0x30*(x)) +#define ISPCCP2_LCx_DAT_PONG_ADDR(x)	((0x074)+0x30*(x)) +#define ISPCCP2_LCx_DAT_OFST(x)		((0x078)+0x30*(x)) +#define ISPCCP2_LCM_CTRL		(0x1D0) +#define ISPCCP2_LCM_CTRL_CHAN_EN               (1 << 0) +#define ISPCCP2_LCM_CTRL_DST_PORT              (1 << 2) +#define ISPCCP2_LCM_CTRL_DST_PORT_SHIFT		2 +#define ISPCCP2_LCM_CTRL_READ_THROTTLE_SHIFT	3 +#define ISPCCP2_LCM_CTRL_READ_THROTTLE_MASK	0x11 +#define ISPCCP2_LCM_CTRL_BURST_SIZE_SHIFT	5 +#define ISPCCP2_LCM_CTRL_BURST_SIZE_MASK	0x7 +#define ISPCCP2_LCM_CTRL_SRC_FORMAT_SHIFT	16 +#define ISPCCP2_LCM_CTRL_SRC_FORMAT_MASK	0x7 +#define ISPCCP2_LCM_CTRL_SRC_DECOMPR_SHIFT	20 +#define ISPCCP2_LCM_CTRL_SRC_DECOMPR_MASK	0x3 +#define ISPCCP2_LCM_CTRL_SRC_DPCM_PRED		(1 << 22) +#define ISPCCP2_LCM_CTRL_SRC_PACK		(1 << 23) +#define ISPCCP2_LCM_CTRL_DST_FORMAT_SHIFT	24 +#define ISPCCP2_LCM_CTRL_DST_FORMAT_MASK	0x7 +#define ISPCCP2_LCM_VSIZE		(0x1D4) +#define ISPCCP2_LCM_VSIZE_SHIFT		16 +#define ISPCCP2_LCM_HSIZE		(0x1D8) +#define ISPCCP2_LCM_HSIZE_SHIFT		16 +#define ISPCCP2_LCM_PREFETCH		(0x1DC) +#define ISPCCP2_LCM_PREFETCH_SHIFT	3 +#define ISPCCP2_LCM_SRC_ADDR		(0x1E0) +#define ISPCCP2_LCM_SRC_OFST		(0x1E4) +#define ISPCCP2_LCM_DST_ADDR		(0x1E8) +#define ISPCCP2_LCM_DST_OFST		(0x1EC) + +/* CCDC module register offset */ + +#define ISPCCDC_PID			(0x000) +#define ISPCCDC_PCR			(0x004) +#define ISPCCDC_SYN_MODE		(0x008) +#define ISPCCDC_HD_VD_WID		(0x00C) +#define ISPCCDC_PIX_LINES		(0x010) +#define ISPCCDC_HORZ_INFO		(0x014) +#define ISPCCDC_VERT_START		(0x018) +#define ISPCCDC_VERT_LINES		(0x01C) +#define ISPCCDC_CULLING			(0x020) +#define ISPCCDC_HSIZE_OFF		(0x024) +#define ISPCCDC_SDOFST			(0x028) +#define ISPCCDC_SDR_ADDR		(0x02C) +#define ISPCCDC_CLAMP			(0x030) +#define ISPCCDC_DCSUB			(0x034) +#define ISPCCDC_COLPTN			(0x038) +#define ISPCCDC_BLKCMP			(0x03C) +#define ISPCCDC_FPC			(0x040) +#define ISPCCDC_FPC_ADDR		(0x044) +#define ISPCCDC_VDINT			(0x048) +#define ISPCCDC_ALAW			(0x04C) +#define ISPCCDC_REC656IF		(0x050) +#define ISPCCDC_CFG			(0x054) +#define ISPCCDC_FMTCFG			(0x058) +#define ISPCCDC_FMT_HORZ		(0x05C) +#define ISPCCDC_FMT_VERT		(0x060) +#define ISPCCDC_FMT_ADDR0		(0x064) +#define ISPCCDC_FMT_ADDR1		(0x068) +#define ISPCCDC_FMT_ADDR2		(0x06C) +#define ISPCCDC_FMT_ADDR3		(0x070) +#define ISPCCDC_FMT_ADDR4		(0x074) +#define ISPCCDC_FMT_ADDR5		(0x078) +#define ISPCCDC_FMT_ADDR6		(0x07C) +#define ISPCCDC_FMT_ADDR7		(0x080) +#define ISPCCDC_PRGEVEN0		(0x084) +#define ISPCCDC_PRGEVEN1		(0x088) +#define ISPCCDC_PRGODD0			(0x08C) +#define ISPCCDC_PRGODD1			(0x090) +#define ISPCCDC_VP_OUT			(0x094) + +#define ISPCCDC_LSC_CONFIG		(0x098) +#define ISPCCDC_LSC_INITIAL		(0x09C) +#define ISPCCDC_LSC_TABLE_BASE		(0x0A0) +#define ISPCCDC_LSC_TABLE_OFFSET	(0x0A4) + +/* SBL */ +#define ISPSBL_PCR			0x4 +#define ISPSBL_PCR_H3A_AEAWB_WBL_OVF	(1 << 16) +#define ISPSBL_PCR_H3A_AF_WBL_OVF	(1 << 17) +#define ISPSBL_PCR_RSZ4_WBL_OVF		(1 << 18) +#define ISPSBL_PCR_RSZ3_WBL_OVF		(1 << 19) +#define ISPSBL_PCR_RSZ2_WBL_OVF		(1 << 20) +#define ISPSBL_PCR_RSZ1_WBL_OVF		(1 << 21) +#define ISPSBL_PCR_PRV_WBL_OVF		(1 << 22) +#define ISPSBL_PCR_CCDC_WBL_OVF		(1 << 23) +#define ISPSBL_PCR_CCDCPRV_2_RSZ_OVF	(1 << 24) +#define ISPSBL_PCR_CSIA_WBL_OVF		(1 << 25) +#define ISPSBL_PCR_CSIB_WBL_OVF		(1 << 26) +#define ISPSBL_CCDC_WR_0		(0x028) +#define ISPSBL_CCDC_WR_0_DATA_READY	(1 << 21) +#define ISPSBL_CCDC_WR_1		(0x02C) +#define ISPSBL_CCDC_WR_2		(0x030) +#define ISPSBL_CCDC_WR_3		(0x034) + +#define ISPSBL_SDR_REQ_EXP		0xF8 +#define ISPSBL_SDR_REQ_HIST_EXP_SHIFT	0 +#define ISPSBL_SDR_REQ_HIST_EXP_MASK	(0x3FF) +#define ISPSBL_SDR_REQ_RSZ_EXP_SHIFT	10 +#define ISPSBL_SDR_REQ_RSZ_EXP_MASK	(0x3FF << ISPSBL_SDR_REQ_RSZ_EXP_SHIFT) +#define ISPSBL_SDR_REQ_PRV_EXP_SHIFT	20 +#define ISPSBL_SDR_REQ_PRV_EXP_MASK	(0x3FF << ISPSBL_SDR_REQ_PRV_EXP_SHIFT) + +/* Histogram registers */ +#define ISPHIST_PID			(0x000) +#define ISPHIST_PCR			(0x004) +#define ISPHIST_CNT			(0x008) +#define ISPHIST_WB_GAIN			(0x00C) +#define ISPHIST_R0_HORZ			(0x010) +#define ISPHIST_R0_VERT			(0x014) +#define ISPHIST_R1_HORZ			(0x018) +#define ISPHIST_R1_VERT			(0x01C) +#define ISPHIST_R2_HORZ			(0x020) +#define ISPHIST_R2_VERT			(0x024) +#define ISPHIST_R3_HORZ			(0x028) +#define ISPHIST_R3_VERT			(0x02C) +#define ISPHIST_ADDR			(0x030) +#define ISPHIST_DATA			(0x034) +#define ISPHIST_RADD			(0x038) +#define ISPHIST_RADD_OFF		(0x03C) +#define ISPHIST_H_V_INFO		(0x040) + +/* H3A module registers */ +#define ISPH3A_PID			(0x000) +#define ISPH3A_PCR			(0x004) +#define ISPH3A_AEWWIN1			(0x04C) +#define ISPH3A_AEWINSTART		(0x050) +#define ISPH3A_AEWINBLK			(0x054) +#define ISPH3A_AEWSUBWIN		(0x058) +#define ISPH3A_AEWBUFST			(0x05C) +#define ISPH3A_AFPAX1			(0x008) +#define ISPH3A_AFPAX2			(0x00C) +#define ISPH3A_AFPAXSTART		(0x010) +#define ISPH3A_AFIIRSH			(0x014) +#define ISPH3A_AFBUFST			(0x018) +#define ISPH3A_AFCOEF010		(0x01C) +#define ISPH3A_AFCOEF032		(0x020) +#define ISPH3A_AFCOEF054		(0x024) +#define ISPH3A_AFCOEF076		(0x028) +#define ISPH3A_AFCOEF098		(0x02C) +#define ISPH3A_AFCOEF0010		(0x030) +#define ISPH3A_AFCOEF110		(0x034) +#define ISPH3A_AFCOEF132		(0x038) +#define ISPH3A_AFCOEF154		(0x03C) +#define ISPH3A_AFCOEF176		(0x040) +#define ISPH3A_AFCOEF198		(0x044) +#define ISPH3A_AFCOEF1010		(0x048) + +#define ISPPRV_PCR			(0x004) +#define ISPPRV_HORZ_INFO		(0x008) +#define ISPPRV_VERT_INFO		(0x00C) +#define ISPPRV_RSDR_ADDR		(0x010) +#define ISPPRV_RADR_OFFSET		(0x014) +#define ISPPRV_DSDR_ADDR		(0x018) +#define ISPPRV_DRKF_OFFSET		(0x01C) +#define ISPPRV_WSDR_ADDR		(0x020) +#define ISPPRV_WADD_OFFSET		(0x024) +#define ISPPRV_AVE			(0x028) +#define ISPPRV_HMED			(0x02C) +#define ISPPRV_NF			(0x030) +#define ISPPRV_WB_DGAIN			(0x034) +#define ISPPRV_WBGAIN			(0x038) +#define ISPPRV_WBSEL			(0x03C) +#define ISPPRV_CFA			(0x040) +#define ISPPRV_BLKADJOFF		(0x044) +#define ISPPRV_RGB_MAT1			(0x048) +#define ISPPRV_RGB_MAT2			(0x04C) +#define ISPPRV_RGB_MAT3			(0x050) +#define ISPPRV_RGB_MAT4			(0x054) +#define ISPPRV_RGB_MAT5			(0x058) +#define ISPPRV_RGB_OFF1			(0x05C) +#define ISPPRV_RGB_OFF2			(0x060) +#define ISPPRV_CSC0			(0x064) +#define ISPPRV_CSC1			(0x068) +#define ISPPRV_CSC2			(0x06C) +#define ISPPRV_CSC_OFFSET		(0x070) +#define ISPPRV_CNT_BRT			(0x074) +#define ISPPRV_CSUP			(0x078) +#define ISPPRV_SETUP_YC			(0x07C) +#define ISPPRV_SET_TBL_ADDR		(0x080) +#define ISPPRV_SET_TBL_DATA		(0x084) +#define ISPPRV_CDC_THR0			(0x090) +#define ISPPRV_CDC_THR1			(ISPPRV_CDC_THR0 + (0x4)) +#define ISPPRV_CDC_THR2			(ISPPRV_CDC_THR0 + (0x4) * 2) +#define ISPPRV_CDC_THR3			(ISPPRV_CDC_THR0 + (0x4) * 3) + +#define ISPPRV_REDGAMMA_TABLE_ADDR	0x0000 +#define ISPPRV_GREENGAMMA_TABLE_ADDR	0x0400 +#define ISPPRV_BLUEGAMMA_TABLE_ADDR	0x0800 +#define ISPPRV_NF_TABLE_ADDR		0x0C00 +#define ISPPRV_YENH_TABLE_ADDR		0x1000 +#define ISPPRV_CFA_TABLE_ADDR		0x1400 + +#define ISPRSZ_MIN_OUTPUT		64 +#define ISPRSZ_MAX_OUTPUT		3312 + +/* Resizer module register offset */ +#define ISPRSZ_PID			(0x000) +#define ISPRSZ_PCR			(0x004) +#define ISPRSZ_CNT			(0x008) +#define ISPRSZ_OUT_SIZE			(0x00C) +#define ISPRSZ_IN_START			(0x010) +#define ISPRSZ_IN_SIZE			(0x014) +#define ISPRSZ_SDR_INADD		(0x018) +#define ISPRSZ_SDR_INOFF		(0x01C) +#define ISPRSZ_SDR_OUTADD		(0x020) +#define ISPRSZ_SDR_OUTOFF		(0x024) +#define ISPRSZ_HFILT10			(0x028) +#define ISPRSZ_HFILT32			(0x02C) +#define ISPRSZ_HFILT54			(0x030) +#define ISPRSZ_HFILT76			(0x034) +#define ISPRSZ_HFILT98			(0x038) +#define ISPRSZ_HFILT1110		(0x03C) +#define ISPRSZ_HFILT1312		(0x040) +#define ISPRSZ_HFILT1514		(0x044) +#define ISPRSZ_HFILT1716		(0x048) +#define ISPRSZ_HFILT1918		(0x04C) +#define ISPRSZ_HFILT2120		(0x050) +#define ISPRSZ_HFILT2322		(0x054) +#define ISPRSZ_HFILT2524		(0x058) +#define ISPRSZ_HFILT2726		(0x05C) +#define ISPRSZ_HFILT2928		(0x060) +#define ISPRSZ_HFILT3130		(0x064) +#define ISPRSZ_VFILT10			(0x068) +#define ISPRSZ_VFILT32			(0x06C) +#define ISPRSZ_VFILT54			(0x070) +#define ISPRSZ_VFILT76			(0x074) +#define ISPRSZ_VFILT98			(0x078) +#define ISPRSZ_VFILT1110		(0x07C) +#define ISPRSZ_VFILT1312		(0x080) +#define ISPRSZ_VFILT1514		(0x084) +#define ISPRSZ_VFILT1716		(0x088) +#define ISPRSZ_VFILT1918		(0x08C) +#define ISPRSZ_VFILT2120		(0x090) +#define ISPRSZ_VFILT2322		(0x094) +#define ISPRSZ_VFILT2524		(0x098) +#define ISPRSZ_VFILT2726		(0x09C) +#define ISPRSZ_VFILT2928		(0x0A0) +#define ISPRSZ_VFILT3130		(0x0A4) +#define ISPRSZ_YENH			(0x0A8) + +#define ISP_INT_CLR			0xFF113F11 +#define ISPPRV_PCR_EN			1 +#define ISPPRV_PCR_BUSY			(1 << 1) +#define ISPPRV_PCR_SOURCE		(1 << 2) +#define ISPPRV_PCR_ONESHOT		(1 << 3) +#define ISPPRV_PCR_WIDTH		(1 << 4) +#define ISPPRV_PCR_INVALAW		(1 << 5) +#define ISPPRV_PCR_DRKFEN		(1 << 6) +#define ISPPRV_PCR_DRKFCAP		(1 << 7) +#define ISPPRV_PCR_HMEDEN		(1 << 8) +#define ISPPRV_PCR_NFEN			(1 << 9) +#define ISPPRV_PCR_CFAEN		(1 << 10) +#define ISPPRV_PCR_CFAFMT_SHIFT		11 +#define ISPPRV_PCR_CFAFMT_MASK		0x7800 +#define ISPPRV_PCR_CFAFMT_BAYER		(0 << 11) +#define ISPPRV_PCR_CFAFMT_SONYVGA	(1 << 11) +#define ISPPRV_PCR_CFAFMT_RGBFOVEON	(2 << 11) +#define ISPPRV_PCR_CFAFMT_DNSPL		(3 << 11) +#define ISPPRV_PCR_CFAFMT_HONEYCOMB	(4 << 11) +#define ISPPRV_PCR_CFAFMT_RRGGBBFOVEON	(5 << 11) +#define ISPPRV_PCR_YNENHEN		(1 << 15) +#define ISPPRV_PCR_SUPEN		(1 << 16) +#define ISPPRV_PCR_YCPOS_SHIFT		17 +#define ISPPRV_PCR_YCPOS_YCrYCb		(0 << 17) +#define ISPPRV_PCR_YCPOS_YCbYCr		(1 << 17) +#define ISPPRV_PCR_YCPOS_CbYCrY		(2 << 17) +#define ISPPRV_PCR_YCPOS_CrYCbY		(3 << 17) +#define ISPPRV_PCR_RSZPORT		(1 << 19) +#define ISPPRV_PCR_SDRPORT		(1 << 20) +#define ISPPRV_PCR_SCOMP_EN		(1 << 21) +#define ISPPRV_PCR_SCOMP_SFT_SHIFT	(22) +#define ISPPRV_PCR_SCOMP_SFT_MASK	(7 << 22) +#define ISPPRV_PCR_GAMMA_BYPASS		(1 << 26) +#define ISPPRV_PCR_DCOREN		(1 << 27) +#define ISPPRV_PCR_DCCOUP		(1 << 28) +#define ISPPRV_PCR_DRK_FAIL		(1 << 31) + +#define ISPPRV_HORZ_INFO_EPH_SHIFT	0 +#define ISPPRV_HORZ_INFO_EPH_MASK	0x3fff +#define ISPPRV_HORZ_INFO_SPH_SHIFT	16 +#define ISPPRV_HORZ_INFO_SPH_MASK	0x3fff0 + +#define ISPPRV_VERT_INFO_ELV_SHIFT	0 +#define ISPPRV_VERT_INFO_ELV_MASK	0x3fff +#define ISPPRV_VERT_INFO_SLV_SHIFT	16 +#define ISPPRV_VERT_INFO_SLV_MASK	0x3fff0 + +#define ISPPRV_AVE_EVENDIST_SHIFT	2 +#define ISPPRV_AVE_EVENDIST_1		0x0 +#define ISPPRV_AVE_EVENDIST_2		0x1 +#define ISPPRV_AVE_EVENDIST_3		0x2 +#define ISPPRV_AVE_EVENDIST_4		0x3 +#define ISPPRV_AVE_ODDDIST_SHIFT	4 +#define ISPPRV_AVE_ODDDIST_1		0x0 +#define ISPPRV_AVE_ODDDIST_2		0x1 +#define ISPPRV_AVE_ODDDIST_3		0x2 +#define ISPPRV_AVE_ODDDIST_4		0x3 + +#define ISPPRV_HMED_THRESHOLD_SHIFT	0 +#define ISPPRV_HMED_EVENDIST		(1 << 8) +#define ISPPRV_HMED_ODDDIST		(1 << 9) + +#define ISPPRV_WBGAIN_COEF0_SHIFT	0 +#define ISPPRV_WBGAIN_COEF1_SHIFT	8 +#define ISPPRV_WBGAIN_COEF2_SHIFT	16 +#define ISPPRV_WBGAIN_COEF3_SHIFT	24 + +#define ISPPRV_WBSEL_COEF0		0x0 +#define ISPPRV_WBSEL_COEF1		0x1 +#define ISPPRV_WBSEL_COEF2		0x2 +#define ISPPRV_WBSEL_COEF3		0x3 + +#define ISPPRV_WBSEL_N0_0_SHIFT		0 +#define ISPPRV_WBSEL_N0_1_SHIFT		2 +#define ISPPRV_WBSEL_N0_2_SHIFT		4 +#define ISPPRV_WBSEL_N0_3_SHIFT		6 +#define ISPPRV_WBSEL_N1_0_SHIFT		8 +#define ISPPRV_WBSEL_N1_1_SHIFT		10 +#define ISPPRV_WBSEL_N1_2_SHIFT		12 +#define ISPPRV_WBSEL_N1_3_SHIFT		14 +#define ISPPRV_WBSEL_N2_0_SHIFT		16 +#define ISPPRV_WBSEL_N2_1_SHIFT		18 +#define ISPPRV_WBSEL_N2_2_SHIFT		20 +#define ISPPRV_WBSEL_N2_3_SHIFT		22 +#define ISPPRV_WBSEL_N3_0_SHIFT		24 +#define ISPPRV_WBSEL_N3_1_SHIFT		26 +#define ISPPRV_WBSEL_N3_2_SHIFT		28 +#define ISPPRV_WBSEL_N3_3_SHIFT		30 + +#define ISPPRV_CFA_GRADTH_HOR_SHIFT	0 +#define ISPPRV_CFA_GRADTH_VER_SHIFT	8 + +#define ISPPRV_BLKADJOFF_B_SHIFT	0 +#define ISPPRV_BLKADJOFF_G_SHIFT	8 +#define ISPPRV_BLKADJOFF_R_SHIFT	16 + +#define ISPPRV_RGB_MAT1_MTX_RR_SHIFT	0 +#define ISPPRV_RGB_MAT1_MTX_GR_SHIFT	16 + +#define ISPPRV_RGB_MAT2_MTX_BR_SHIFT	0 +#define ISPPRV_RGB_MAT2_MTX_RG_SHIFT	16 + +#define ISPPRV_RGB_MAT3_MTX_GG_SHIFT	0 +#define ISPPRV_RGB_MAT3_MTX_BG_SHIFT	16 + +#define ISPPRV_RGB_MAT4_MTX_RB_SHIFT	0 +#define ISPPRV_RGB_MAT4_MTX_GB_SHIFT	16 + +#define ISPPRV_RGB_MAT5_MTX_BB_SHIFT	0 + +#define ISPPRV_RGB_OFF1_MTX_OFFG_SHIFT	0 +#define ISPPRV_RGB_OFF1_MTX_OFFR_SHIFT	16 + +#define ISPPRV_RGB_OFF2_MTX_OFFB_SHIFT	0 + +#define ISPPRV_CSC0_RY_SHIFT		0 +#define ISPPRV_CSC0_GY_SHIFT		10 +#define ISPPRV_CSC0_BY_SHIFT		20 + +#define ISPPRV_CSC1_RCB_SHIFT		0 +#define ISPPRV_CSC1_GCB_SHIFT		10 +#define ISPPRV_CSC1_BCB_SHIFT		20 + +#define ISPPRV_CSC2_RCR_SHIFT		0 +#define ISPPRV_CSC2_GCR_SHIFT		10 +#define ISPPRV_CSC2_BCR_SHIFT		20 + +#define ISPPRV_CSC_OFFSET_CR_SHIFT	0 +#define ISPPRV_CSC_OFFSET_CB_SHIFT	8 +#define ISPPRV_CSC_OFFSET_Y_SHIFT	16 + +#define ISPPRV_CNT_BRT_BRT_SHIFT	0 +#define ISPPRV_CNT_BRT_CNT_SHIFT	8 + +#define ISPPRV_CONTRAST_MAX		0x10 +#define ISPPRV_CONTRAST_MIN		0xFF +#define ISPPRV_BRIGHT_MIN		0x00 +#define ISPPRV_BRIGHT_MAX		0xFF + +#define ISPPRV_CSUP_CSUPG_SHIFT		0 +#define ISPPRV_CSUP_THRES_SHIFT		8 +#define ISPPRV_CSUP_HPYF_SHIFT		16 + +#define ISPPRV_SETUP_YC_MINC_SHIFT	0 +#define ISPPRV_SETUP_YC_MAXC_SHIFT	8 +#define ISPPRV_SETUP_YC_MINY_SHIFT	16 +#define ISPPRV_SETUP_YC_MAXY_SHIFT	24 +#define ISPPRV_YC_MAX			0xFF +#define ISPPRV_YC_MIN			0x0 + +/* Define bit fields within selected registers */ +#define ISP_REVISION_SHIFT			0 + +#define ISP_SYSCONFIG_AUTOIDLE			(1 << 0) +#define ISP_SYSCONFIG_SOFTRESET			(1 << 1) +#define ISP_SYSCONFIG_MIDLEMODE_SHIFT		12 +#define ISP_SYSCONFIG_MIDLEMODE_FORCESTANDBY	0x0 +#define ISP_SYSCONFIG_MIDLEMODE_NOSTANBY	0x1 +#define ISP_SYSCONFIG_MIDLEMODE_SMARTSTANDBY	0x2 + +#define ISP_SYSSTATUS_RESETDONE			0 + +#define IRQ0ENABLE_CSIA_IRQ			(1 << 0) +#define IRQ0ENABLE_CSIC_IRQ			(1 << 1) +#define IRQ0ENABLE_CCP2_LCM_IRQ			(1 << 3) +#define IRQ0ENABLE_CCP2_LC0_IRQ			(1 << 4) +#define IRQ0ENABLE_CCP2_LC1_IRQ			(1 << 5) +#define IRQ0ENABLE_CCP2_LC2_IRQ			(1 << 6) +#define IRQ0ENABLE_CCP2_LC3_IRQ			(1 << 7) +#define IRQ0ENABLE_CSIB_IRQ			(IRQ0ENABLE_CCP2_LCM_IRQ | \ +						IRQ0ENABLE_CCP2_LC0_IRQ | \ +						IRQ0ENABLE_CCP2_LC1_IRQ | \ +						IRQ0ENABLE_CCP2_LC2_IRQ | \ +						IRQ0ENABLE_CCP2_LC3_IRQ) + +#define IRQ0ENABLE_CCDC_VD0_IRQ			(1 << 8) +#define IRQ0ENABLE_CCDC_VD1_IRQ			(1 << 9) +#define IRQ0ENABLE_CCDC_VD2_IRQ			(1 << 10) +#define IRQ0ENABLE_CCDC_ERR_IRQ			(1 << 11) +#define IRQ0ENABLE_H3A_AF_DONE_IRQ		(1 << 12) +#define IRQ0ENABLE_H3A_AWB_DONE_IRQ		(1 << 13) +#define IRQ0ENABLE_HIST_DONE_IRQ		(1 << 16) +#define IRQ0ENABLE_CCDC_LSC_DONE_IRQ		(1 << 17) +#define IRQ0ENABLE_CCDC_LSC_PREF_COMP_IRQ	(1 << 18) +#define IRQ0ENABLE_CCDC_LSC_PREF_ERR_IRQ	(1 << 19) +#define IRQ0ENABLE_PRV_DONE_IRQ			(1 << 20) +#define IRQ0ENABLE_RSZ_DONE_IRQ			(1 << 24) +#define IRQ0ENABLE_OVF_IRQ			(1 << 25) +#define IRQ0ENABLE_PING_IRQ			(1 << 26) +#define IRQ0ENABLE_PONG_IRQ			(1 << 27) +#define IRQ0ENABLE_MMU_ERR_IRQ			(1 << 28) +#define IRQ0ENABLE_OCP_ERR_IRQ			(1 << 29) +#define IRQ0ENABLE_SEC_ERR_IRQ			(1 << 30) +#define IRQ0ENABLE_HS_VS_IRQ			(1 << 31) + +#define IRQ0STATUS_CSIA_IRQ			(1 << 0) +#define IRQ0STATUS_CSI2C_IRQ			(1 << 1) +#define IRQ0STATUS_CCP2_LCM_IRQ			(1 << 3) +#define IRQ0STATUS_CCP2_LC0_IRQ			(1 << 4) +#define IRQ0STATUS_CSIB_IRQ			(IRQ0STATUS_CCP2_LCM_IRQ | \ +						IRQ0STATUS_CCP2_LC0_IRQ) + +#define IRQ0STATUS_CSIB_LC1_IRQ			(1 << 5) +#define IRQ0STATUS_CSIB_LC2_IRQ			(1 << 6) +#define IRQ0STATUS_CSIB_LC3_IRQ			(1 << 7) +#define IRQ0STATUS_CCDC_VD0_IRQ			(1 << 8) +#define IRQ0STATUS_CCDC_VD1_IRQ			(1 << 9) +#define IRQ0STATUS_CCDC_VD2_IRQ			(1 << 10) +#define IRQ0STATUS_CCDC_ERR_IRQ			(1 << 11) +#define IRQ0STATUS_H3A_AF_DONE_IRQ		(1 << 12) +#define IRQ0STATUS_H3A_AWB_DONE_IRQ		(1 << 13) +#define IRQ0STATUS_HIST_DONE_IRQ		(1 << 16) +#define IRQ0STATUS_CCDC_LSC_DONE_IRQ		(1 << 17) +#define IRQ0STATUS_CCDC_LSC_PREF_COMP_IRQ	(1 << 18) +#define IRQ0STATUS_CCDC_LSC_PREF_ERR_IRQ	(1 << 19) +#define IRQ0STATUS_PRV_DONE_IRQ			(1 << 20) +#define IRQ0STATUS_RSZ_DONE_IRQ			(1 << 24) +#define IRQ0STATUS_OVF_IRQ			(1 << 25) +#define IRQ0STATUS_PING_IRQ			(1 << 26) +#define IRQ0STATUS_PONG_IRQ			(1 << 27) +#define IRQ0STATUS_MMU_ERR_IRQ			(1 << 28) +#define IRQ0STATUS_OCP_ERR_IRQ			(1 << 29) +#define IRQ0STATUS_SEC_ERR_IRQ			(1 << 30) +#define IRQ0STATUS_HS_VS_IRQ			(1 << 31) + +#define TCTRL_GRESET_LEN			0 + +#define TCTRL_PSTRB_REPLAY_DELAY		0 +#define TCTRL_PSTRB_REPLAY_COUNTER_SHIFT	25 + +#define ISPCTRL_PAR_SER_CLK_SEL_PARALLEL	0x0 +#define ISPCTRL_PAR_SER_CLK_SEL_CSIA		0x1 +#define ISPCTRL_PAR_SER_CLK_SEL_CSIB		0x2 +#define ISPCTRL_PAR_SER_CLK_SEL_CSIC		0x3 +#define ISPCTRL_PAR_SER_CLK_SEL_MASK		0x3 + +#define ISPCTRL_PAR_BRIDGE_SHIFT		2 +#define ISPCTRL_PAR_BRIDGE_DISABLE		(0x0 << 2) +#define ISPCTRL_PAR_BRIDGE_LENDIAN		(0x2 << 2) +#define ISPCTRL_PAR_BRIDGE_BENDIAN		(0x3 << 2) +#define ISPCTRL_PAR_BRIDGE_MASK			(0x3 << 2) + +#define ISPCTRL_PAR_CLK_POL_SHIFT		4 +#define ISPCTRL_PAR_CLK_POL_INV			(1 << 4) +#define ISPCTRL_PING_PONG_EN			(1 << 5) +#define ISPCTRL_SHIFT_SHIFT			6 +#define ISPCTRL_SHIFT_0				(0x0 << 6) +#define ISPCTRL_SHIFT_2				(0x1 << 6) +#define ISPCTRL_SHIFT_4				(0x2 << 6) +#define ISPCTRL_SHIFT_MASK			(0x3 << 6) + +#define ISPCTRL_CCDC_CLK_EN			(1 << 8) +#define ISPCTRL_SCMP_CLK_EN			(1 << 9) +#define ISPCTRL_H3A_CLK_EN			(1 << 10) +#define ISPCTRL_HIST_CLK_EN			(1 << 11) +#define ISPCTRL_PREV_CLK_EN			(1 << 12) +#define ISPCTRL_RSZ_CLK_EN			(1 << 13) +#define ISPCTRL_SYNC_DETECT_SHIFT		14 +#define ISPCTRL_SYNC_DETECT_HSFALL	(0x0 << ISPCTRL_SYNC_DETECT_SHIFT) +#define ISPCTRL_SYNC_DETECT_HSRISE	(0x1 << ISPCTRL_SYNC_DETECT_SHIFT) +#define ISPCTRL_SYNC_DETECT_VSFALL	(0x2 << ISPCTRL_SYNC_DETECT_SHIFT) +#define ISPCTRL_SYNC_DETECT_VSRISE	(0x3 << ISPCTRL_SYNC_DETECT_SHIFT) +#define ISPCTRL_SYNC_DETECT_MASK	(0x3 << ISPCTRL_SYNC_DETECT_SHIFT) + +#define ISPCTRL_CCDC_RAM_EN		(1 << 16) +#define ISPCTRL_PREV_RAM_EN		(1 << 17) +#define ISPCTRL_SBL_RD_RAM_EN		(1 << 18) +#define ISPCTRL_SBL_WR1_RAM_EN		(1 << 19) +#define ISPCTRL_SBL_WR0_RAM_EN		(1 << 20) +#define ISPCTRL_SBL_AUTOIDLE		(1 << 21) +#define ISPCTRL_SBL_SHARED_WPORTC	(1 << 26) +#define ISPCTRL_SBL_SHARED_RPORTA	(1 << 27) +#define ISPCTRL_SBL_SHARED_RPORTB	(1 << 28) +#define ISPCTRL_JPEG_FLUSH		(1 << 30) +#define ISPCTRL_CCDC_FLUSH		(1 << 31) + +#define ISPSECURE_SECUREMODE		0 + +#define ISPTCTRL_CTRL_DIV_LOW		0x0 +#define ISPTCTRL_CTRL_DIV_HIGH		0x1 +#define ISPTCTRL_CTRL_DIV_BYPASS	0x1F + +#define ISPTCTRL_CTRL_DIVA_SHIFT	0 +#define ISPTCTRL_CTRL_DIVA_MASK		(0x1F << ISPTCTRL_CTRL_DIVA_SHIFT) + +#define ISPTCTRL_CTRL_DIVB_SHIFT	5 +#define ISPTCTRL_CTRL_DIVB_MASK		(0x1F << ISPTCTRL_CTRL_DIVB_SHIFT) + +#define ISPTCTRL_CTRL_DIVC_SHIFT	10 +#define ISPTCTRL_CTRL_DIVC_NOCLOCK	(0x0 << 10) + +#define ISPTCTRL_CTRL_SHUTEN		(1 << 21) +#define ISPTCTRL_CTRL_PSTRBEN		(1 << 22) +#define ISPTCTRL_CTRL_STRBEN		(1 << 23) +#define ISPTCTRL_CTRL_SHUTPOL		(1 << 24) +#define ISPTCTRL_CTRL_STRBPSTRBPOL	(1 << 26) + +#define ISPTCTRL_CTRL_INSEL_SHIFT	27 +#define ISPTCTRL_CTRL_INSEL_PARALLEL	(0x0 << 27) +#define ISPTCTRL_CTRL_INSEL_CSIA	(0x1 << 27) +#define ISPTCTRL_CTRL_INSEL_CSIB	(0x2 << 27) + +#define ISPTCTRL_CTRL_GRESETEn		(1 << 29) +#define ISPTCTRL_CTRL_GRESETPOL		(1 << 30) +#define ISPTCTRL_CTRL_GRESETDIR		(1 << 31) + +#define ISPTCTRL_FRAME_SHUT_SHIFT		0 +#define ISPTCTRL_FRAME_PSTRB_SHIFT		6 +#define ISPTCTRL_FRAME_STRB_SHIFT		12 + +#define ISPCCDC_PID_PREV_SHIFT			0 +#define ISPCCDC_PID_CID_SHIFT			8 +#define ISPCCDC_PID_TID_SHIFT			16 + +#define ISPCCDC_PCR_EN				1 +#define ISPCCDC_PCR_BUSY			(1 << 1) + +#define ISPCCDC_SYN_MODE_VDHDOUT		0x1 +#define ISPCCDC_SYN_MODE_FLDOUT			(1 << 1) +#define ISPCCDC_SYN_MODE_VDPOL			(1 << 2) +#define ISPCCDC_SYN_MODE_HDPOL			(1 << 3) +#define ISPCCDC_SYN_MODE_FLDPOL			(1 << 4) +#define ISPCCDC_SYN_MODE_EXWEN			(1 << 5) +#define ISPCCDC_SYN_MODE_DATAPOL		(1 << 6) +#define ISPCCDC_SYN_MODE_FLDMODE		(1 << 7) +#define ISPCCDC_SYN_MODE_DATSIZ_MASK		(0x7 << 8) +#define ISPCCDC_SYN_MODE_DATSIZ_8_16		(0x0 << 8) +#define ISPCCDC_SYN_MODE_DATSIZ_12		(0x4 << 8) +#define ISPCCDC_SYN_MODE_DATSIZ_11		(0x5 << 8) +#define ISPCCDC_SYN_MODE_DATSIZ_10		(0x6 << 8) +#define ISPCCDC_SYN_MODE_DATSIZ_8		(0x7 << 8) +#define ISPCCDC_SYN_MODE_PACK8			(1 << 11) +#define ISPCCDC_SYN_MODE_INPMOD_MASK		(3 << 12) +#define ISPCCDC_SYN_MODE_INPMOD_RAW		(0 << 12) +#define ISPCCDC_SYN_MODE_INPMOD_YCBCR16		(1 << 12) +#define ISPCCDC_SYN_MODE_INPMOD_YCBCR8		(2 << 12) +#define ISPCCDC_SYN_MODE_LPF			(1 << 14) +#define ISPCCDC_SYN_MODE_FLDSTAT		(1 << 15) +#define ISPCCDC_SYN_MODE_VDHDEN			(1 << 16) +#define ISPCCDC_SYN_MODE_WEN			(1 << 17) +#define ISPCCDC_SYN_MODE_VP2SDR			(1 << 18) +#define ISPCCDC_SYN_MODE_SDR2RSZ		(1 << 19) + +#define ISPCCDC_HD_VD_WID_VDW_SHIFT		0 +#define ISPCCDC_HD_VD_WID_HDW_SHIFT		16 + +#define ISPCCDC_PIX_LINES_HLPRF_SHIFT		0 +#define ISPCCDC_PIX_LINES_PPLN_SHIFT		16 + +#define ISPCCDC_HORZ_INFO_NPH_SHIFT		0 +#define ISPCCDC_HORZ_INFO_NPH_MASK		0x00007fff +#define ISPCCDC_HORZ_INFO_SPH_SHIFT		16 +#define ISPCCDC_HORZ_INFO_SPH_MASK		0x7fff0000 + +#define ISPCCDC_VERT_START_SLV1_SHIFT		0 +#define ISPCCDC_VERT_START_SLV0_SHIFT		16 +#define ISPCCDC_VERT_START_SLV0_MASK		0x7fff0000 + +#define ISPCCDC_VERT_LINES_NLV_SHIFT		0 +#define ISPCCDC_VERT_LINES_NLV_MASK		0x00007fff + +#define ISPCCDC_CULLING_CULV_SHIFT		0 +#define ISPCCDC_CULLING_CULHODD_SHIFT		16 +#define ISPCCDC_CULLING_CULHEVN_SHIFT		24 + +#define ISPCCDC_HSIZE_OFF_SHIFT			0 + +#define ISPCCDC_SDOFST_FINV			(1 << 14) +#define ISPCCDC_SDOFST_FOFST_1L			0 +#define ISPCCDC_SDOFST_FOFST_4L			(3 << 12) +#define ISPCCDC_SDOFST_LOFST3_SHIFT		0 +#define ISPCCDC_SDOFST_LOFST2_SHIFT		3 +#define ISPCCDC_SDOFST_LOFST1_SHIFT		6 +#define ISPCCDC_SDOFST_LOFST0_SHIFT		9 +#define EVENEVEN				1 +#define ODDEVEN					2 +#define EVENODD					3 +#define ODDODD					4 + +#define ISPCCDC_CLAMP_OBGAIN_SHIFT		0 +#define ISPCCDC_CLAMP_OBST_SHIFT		10 +#define ISPCCDC_CLAMP_OBSLN_SHIFT		25 +#define ISPCCDC_CLAMP_OBSLEN_SHIFT		28 +#define ISPCCDC_CLAMP_CLAMPEN			(1 << 31) + +#define ISPCCDC_COLPTN_R_Ye			0x0 +#define ISPCCDC_COLPTN_Gr_Cy			0x1 +#define ISPCCDC_COLPTN_Gb_G			0x2 +#define ISPCCDC_COLPTN_B_Mg			0x3 +#define ISPCCDC_COLPTN_CP0PLC0_SHIFT		0 +#define ISPCCDC_COLPTN_CP0PLC1_SHIFT		2 +#define ISPCCDC_COLPTN_CP0PLC2_SHIFT		4 +#define ISPCCDC_COLPTN_CP0PLC3_SHIFT		6 +#define ISPCCDC_COLPTN_CP1PLC0_SHIFT		8 +#define ISPCCDC_COLPTN_CP1PLC1_SHIFT		10 +#define ISPCCDC_COLPTN_CP1PLC2_SHIFT		12 +#define ISPCCDC_COLPTN_CP1PLC3_SHIFT		14 +#define ISPCCDC_COLPTN_CP2PLC0_SHIFT		16 +#define ISPCCDC_COLPTN_CP2PLC1_SHIFT		18 +#define ISPCCDC_COLPTN_CP2PLC2_SHIFT		20 +#define ISPCCDC_COLPTN_CP2PLC3_SHIFT		22 +#define ISPCCDC_COLPTN_CP3PLC0_SHIFT		24 +#define ISPCCDC_COLPTN_CP3PLC1_SHIFT		26 +#define ISPCCDC_COLPTN_CP3PLC2_SHIFT		28 +#define ISPCCDC_COLPTN_CP3PLC3_SHIFT		30 + +#define ISPCCDC_BLKCMP_B_MG_SHIFT		0 +#define ISPCCDC_BLKCMP_GB_G_SHIFT		8 +#define ISPCCDC_BLKCMP_GR_CY_SHIFT		16 +#define ISPCCDC_BLKCMP_R_YE_SHIFT		24 + +#define ISPCCDC_FPC_FPNUM_SHIFT			0 +#define ISPCCDC_FPC_FPCEN			(1 << 15) +#define ISPCCDC_FPC_FPERR			(1 << 16) + +#define ISPCCDC_VDINT_1_SHIFT			0 +#define ISPCCDC_VDINT_1_MASK			0x00007fff +#define ISPCCDC_VDINT_0_SHIFT			16 +#define ISPCCDC_VDINT_0_MASK			0x7fff0000 + +#define ISPCCDC_ALAW_GWDI_12_3			(0x3 << 0) +#define ISPCCDC_ALAW_GWDI_11_2			(0x4 << 0) +#define ISPCCDC_ALAW_GWDI_10_1			(0x5 << 0) +#define ISPCCDC_ALAW_GWDI_9_0			(0x6 << 0) +#define ISPCCDC_ALAW_CCDTBL			(1 << 3) + +#define ISPCCDC_REC656IF_R656ON			1 +#define ISPCCDC_REC656IF_ECCFVH			(1 << 1) + +#define ISPCCDC_CFG_BW656			(1 << 5) +#define ISPCCDC_CFG_FIDMD_SHIFT			6 +#define ISPCCDC_CFG_WENLOG			(1 << 8) +#define ISPCCDC_CFG_WENLOG_AND			(0 << 8) +#define ISPCCDC_CFG_WENLOG_OR			(1 << 8) +#define ISPCCDC_CFG_Y8POS			(1 << 11) +#define ISPCCDC_CFG_BSWD			(1 << 12) +#define ISPCCDC_CFG_MSBINVI			(1 << 13) +#define ISPCCDC_CFG_VDLC			(1 << 15) + +#define ISPCCDC_FMTCFG_FMTEN			0x1 +#define ISPCCDC_FMTCFG_LNALT			(1 << 1) +#define ISPCCDC_FMTCFG_LNUM_SHIFT		2 +#define ISPCCDC_FMTCFG_PLEN_ODD_SHIFT		4 +#define ISPCCDC_FMTCFG_PLEN_EVEN_SHIFT		8 +#define ISPCCDC_FMTCFG_VPIN_MASK		0x00007000 +#define ISPCCDC_FMTCFG_VPIN_12_3		(0x3 << 12) +#define ISPCCDC_FMTCFG_VPIN_11_2		(0x4 << 12) +#define ISPCCDC_FMTCFG_VPIN_10_1		(0x5 << 12) +#define ISPCCDC_FMTCFG_VPIN_9_0			(0x6 << 12) +#define ISPCCDC_FMTCFG_VPEN			(1 << 15) + +#define ISPCCDC_FMTCFG_VPIF_FRQ_MASK		0x003f0000 +#define ISPCCDC_FMTCFG_VPIF_FRQ_SHIFT		16 +#define ISPCCDC_FMTCFG_VPIF_FRQ_BY2		(0x0 << 16) +#define ISPCCDC_FMTCFG_VPIF_FRQ_BY3		(0x1 << 16) +#define ISPCCDC_FMTCFG_VPIF_FRQ_BY4		(0x2 << 16) +#define ISPCCDC_FMTCFG_VPIF_FRQ_BY5		(0x3 << 16) +#define ISPCCDC_FMTCFG_VPIF_FRQ_BY6		(0x4 << 16) + +#define ISPCCDC_FMT_HORZ_FMTLNH_SHIFT		0 +#define ISPCCDC_FMT_HORZ_FMTSPH_SHIFT		16 + +#define ISPCCDC_FMT_VERT_FMTLNV_SHIFT		0 +#define ISPCCDC_FMT_VERT_FMTSLV_SHIFT		16 + +#define ISPCCDC_FMT_HORZ_FMTSPH_MASK		0x1fff0000 +#define ISPCCDC_FMT_HORZ_FMTLNH_MASK		0x00001fff + +#define ISPCCDC_FMT_VERT_FMTSLV_MASK		0x1fff0000 +#define ISPCCDC_FMT_VERT_FMTLNV_MASK		0x00001fff + +#define ISPCCDC_VP_OUT_HORZ_ST_SHIFT		0 +#define ISPCCDC_VP_OUT_HORZ_NUM_SHIFT		4 +#define ISPCCDC_VP_OUT_VERT_NUM_SHIFT		17 + +#define ISPRSZ_PID_PREV_SHIFT			0 +#define ISPRSZ_PID_CID_SHIFT			8 +#define ISPRSZ_PID_TID_SHIFT			16 + +#define ISPRSZ_PCR_ENABLE			(1 << 0) +#define ISPRSZ_PCR_BUSY				(1 << 1) +#define ISPRSZ_PCR_ONESHOT			(1 << 2) + +#define ISPRSZ_CNT_HRSZ_SHIFT			0 +#define ISPRSZ_CNT_HRSZ_MASK			\ +	(0x3FF << ISPRSZ_CNT_HRSZ_SHIFT) +#define ISPRSZ_CNT_VRSZ_SHIFT			10 +#define ISPRSZ_CNT_VRSZ_MASK			\ +	(0x3FF << ISPRSZ_CNT_VRSZ_SHIFT) +#define ISPRSZ_CNT_HSTPH_SHIFT			20 +#define ISPRSZ_CNT_HSTPH_MASK			(0x7 << ISPRSZ_CNT_HSTPH_SHIFT) +#define ISPRSZ_CNT_VSTPH_SHIFT			23 +#define ISPRSZ_CNT_VSTPH_MASK			(0x7 << ISPRSZ_CNT_VSTPH_SHIFT) +#define ISPRSZ_CNT_YCPOS			(1 << 26) +#define ISPRSZ_CNT_INPTYP			(1 << 27) +#define ISPRSZ_CNT_INPSRC			(1 << 28) +#define ISPRSZ_CNT_CBILIN			(1 << 29) + +#define ISPRSZ_OUT_SIZE_HORZ_SHIFT		0 +#define ISPRSZ_OUT_SIZE_HORZ_MASK		\ +	(0xFFF << ISPRSZ_OUT_SIZE_HORZ_SHIFT) +#define ISPRSZ_OUT_SIZE_VERT_SHIFT		16 +#define ISPRSZ_OUT_SIZE_VERT_MASK		\ +	(0xFFF << ISPRSZ_OUT_SIZE_VERT_SHIFT) + +#define ISPRSZ_IN_START_HORZ_ST_SHIFT		0 +#define ISPRSZ_IN_START_HORZ_ST_MASK		\ +	(0x1FFF << ISPRSZ_IN_START_HORZ_ST_SHIFT) +#define ISPRSZ_IN_START_VERT_ST_SHIFT		16 +#define ISPRSZ_IN_START_VERT_ST_MASK		\ +	(0x1FFF << ISPRSZ_IN_START_VERT_ST_SHIFT) + +#define ISPRSZ_IN_SIZE_HORZ_SHIFT		0 +#define ISPRSZ_IN_SIZE_HORZ_MASK		\ +	(0x1FFF << ISPRSZ_IN_SIZE_HORZ_SHIFT) +#define ISPRSZ_IN_SIZE_VERT_SHIFT		16 +#define ISPRSZ_IN_SIZE_VERT_MASK		\ +	(0x1FFF << ISPRSZ_IN_SIZE_VERT_SHIFT) + +#define ISPRSZ_SDR_INADD_ADDR_SHIFT		0 +#define ISPRSZ_SDR_INADD_ADDR_MASK		0xFFFFFFFF + +#define ISPRSZ_SDR_INOFF_OFFSET_SHIFT		0 +#define ISPRSZ_SDR_INOFF_OFFSET_MASK		\ +	(0xFFFF << ISPRSZ_SDR_INOFF_OFFSET_SHIFT) + +#define ISPRSZ_SDR_OUTADD_ADDR_SHIFT		0 +#define ISPRSZ_SDR_OUTADD_ADDR_MASK		0xFFFFFFFF + + +#define ISPRSZ_SDR_OUTOFF_OFFSET_SHIFT		0 +#define ISPRSZ_SDR_OUTOFF_OFFSET_MASK		\ +	(0xFFFF << ISPRSZ_SDR_OUTOFF_OFFSET_SHIFT) + +#define ISPRSZ_HFILT_COEF0_SHIFT		0 +#define ISPRSZ_HFILT_COEF0_MASK			\ +	(0x3FF << ISPRSZ_HFILT_COEF0_SHIFT) +#define ISPRSZ_HFILT_COEF1_SHIFT		16 +#define ISPRSZ_HFILT_COEF1_MASK			\ +	(0x3FF << ISPRSZ_HFILT_COEF1_SHIFT) + +#define ISPRSZ_HFILT32_COEF2_SHIFT		0 +#define ISPRSZ_HFILT32_COEF2_MASK		0x3FF +#define ISPRSZ_HFILT32_COEF3_SHIFT		16 +#define ISPRSZ_HFILT32_COEF3_MASK		0x3FF0000 + +#define ISPRSZ_HFILT54_COEF4_SHIFT		0 +#define ISPRSZ_HFILT54_COEF4_MASK		0x3FF +#define ISPRSZ_HFILT54_COEF5_SHIFT		16 +#define ISPRSZ_HFILT54_COEF5_MASK		0x3FF0000 + +#define ISPRSZ_HFILT76_COEFF6_SHIFT		0 +#define ISPRSZ_HFILT76_COEFF6_MASK		0x3FF +#define ISPRSZ_HFILT76_COEFF7_SHIFT		16 +#define ISPRSZ_HFILT76_COEFF7_MASK		0x3FF0000 + +#define ISPRSZ_HFILT98_COEFF8_SHIFT		0 +#define ISPRSZ_HFILT98_COEFF8_MASK		0x3FF +#define ISPRSZ_HFILT98_COEFF9_SHIFT		16 +#define ISPRSZ_HFILT98_COEFF9_MASK		0x3FF0000 + +#define ISPRSZ_HFILT1110_COEF10_SHIFT		0 +#define ISPRSZ_HFILT1110_COEF10_MASK		0x3FF +#define ISPRSZ_HFILT1110_COEF11_SHIFT		16 +#define ISPRSZ_HFILT1110_COEF11_MASK		0x3FF0000 + +#define ISPRSZ_HFILT1312_COEFF12_SHIFT		0 +#define ISPRSZ_HFILT1312_COEFF12_MASK		0x3FF +#define ISPRSZ_HFILT1312_COEFF13_SHIFT		16 +#define ISPRSZ_HFILT1312_COEFF13_MASK		0x3FF0000 + +#define ISPRSZ_HFILT1514_COEFF14_SHIFT		0 +#define ISPRSZ_HFILT1514_COEFF14_MASK		0x3FF +#define ISPRSZ_HFILT1514_COEFF15_SHIFT		16 +#define ISPRSZ_HFILT1514_COEFF15_MASK		0x3FF0000 + +#define ISPRSZ_HFILT1716_COEF16_SHIFT		0 +#define ISPRSZ_HFILT1716_COEF16_MASK		0x3FF +#define ISPRSZ_HFILT1716_COEF17_SHIFT		16 +#define ISPRSZ_HFILT1716_COEF17_MASK		0x3FF0000 + +#define ISPRSZ_HFILT1918_COEF18_SHIFT		0 +#define ISPRSZ_HFILT1918_COEF18_MASK		0x3FF +#define ISPRSZ_HFILT1918_COEF19_SHIFT		16 +#define ISPRSZ_HFILT1918_COEF19_MASK		0x3FF0000 + +#define ISPRSZ_HFILT2120_COEF20_SHIFT		0 +#define ISPRSZ_HFILT2120_COEF20_MASK		0x3FF +#define ISPRSZ_HFILT2120_COEF21_SHIFT		16 +#define ISPRSZ_HFILT2120_COEF21_MASK		0x3FF0000 + +#define ISPRSZ_HFILT2322_COEF22_SHIFT		0 +#define ISPRSZ_HFILT2322_COEF22_MASK		0x3FF +#define ISPRSZ_HFILT2322_COEF23_SHIFT		16 +#define ISPRSZ_HFILT2322_COEF23_MASK		0x3FF0000 + +#define ISPRSZ_HFILT2524_COEF24_SHIFT		0 +#define ISPRSZ_HFILT2524_COEF24_MASK		0x3FF +#define ISPRSZ_HFILT2524_COEF25_SHIFT		16 +#define ISPRSZ_HFILT2524_COEF25_MASK		0x3FF0000 + +#define ISPRSZ_HFILT2726_COEF26_SHIFT		0 +#define ISPRSZ_HFILT2726_COEF26_MASK		0x3FF +#define ISPRSZ_HFILT2726_COEF27_SHIFT		16 +#define ISPRSZ_HFILT2726_COEF27_MASK		0x3FF0000 + +#define ISPRSZ_HFILT2928_COEF28_SHIFT		0 +#define ISPRSZ_HFILT2928_COEF28_MASK		0x3FF +#define ISPRSZ_HFILT2928_COEF29_SHIFT		16 +#define ISPRSZ_HFILT2928_COEF29_MASK		0x3FF0000 + +#define ISPRSZ_HFILT3130_COEF30_SHIFT		0 +#define ISPRSZ_HFILT3130_COEF30_MASK		0x3FF +#define ISPRSZ_HFILT3130_COEF31_SHIFT		16 +#define ISPRSZ_HFILT3130_COEF31_MASK		0x3FF0000 + +#define ISPRSZ_VFILT_COEF0_SHIFT		0 +#define ISPRSZ_VFILT_COEF0_MASK			\ +	(0x3FF << ISPRSZ_VFILT_COEF0_SHIFT) +#define ISPRSZ_VFILT_COEF1_SHIFT		16 +#define ISPRSZ_VFILT_COEF1_MASK			\ +	(0x3FF << ISPRSZ_VFILT_COEF1_SHIFT) + +#define ISPRSZ_VFILT10_COEF0_SHIFT		0 +#define ISPRSZ_VFILT10_COEF0_MASK		0x3FF +#define ISPRSZ_VFILT10_COEF1_SHIFT		16 +#define ISPRSZ_VFILT10_COEF1_MASK		0x3FF0000 + +#define ISPRSZ_VFILT32_COEF2_SHIFT		0 +#define ISPRSZ_VFILT32_COEF2_MASK		0x3FF +#define ISPRSZ_VFILT32_COEF3_SHIFT		16 +#define ISPRSZ_VFILT32_COEF3_MASK		0x3FF0000 + +#define ISPRSZ_VFILT54_COEF4_SHIFT		0 +#define ISPRSZ_VFILT54_COEF4_MASK		0x3FF +#define ISPRSZ_VFILT54_COEF5_SHIFT		16 +#define ISPRSZ_VFILT54_COEF5_MASK		0x3FF0000 + +#define ISPRSZ_VFILT76_COEFF6_SHIFT		0 +#define ISPRSZ_VFILT76_COEFF6_MASK		0x3FF +#define ISPRSZ_VFILT76_COEFF7_SHIFT		16 +#define ISPRSZ_VFILT76_COEFF7_MASK		0x3FF0000 + +#define ISPRSZ_VFILT98_COEFF8_SHIFT		0 +#define ISPRSZ_VFILT98_COEFF8_MASK		0x3FF +#define ISPRSZ_VFILT98_COEFF9_SHIFT		16 +#define ISPRSZ_VFILT98_COEFF9_MASK		0x3FF0000 + +#define ISPRSZ_VFILT1110_COEF10_SHIFT		0 +#define ISPRSZ_VFILT1110_COEF10_MASK		0x3FF +#define ISPRSZ_VFILT1110_COEF11_SHIFT		16 +#define ISPRSZ_VFILT1110_COEF11_MASK		0x3FF0000 + +#define ISPRSZ_VFILT1312_COEFF12_SHIFT		0 +#define ISPRSZ_VFILT1312_COEFF12_MASK		0x3FF +#define ISPRSZ_VFILT1312_COEFF13_SHIFT		16 +#define ISPRSZ_VFILT1312_COEFF13_MASK		0x3FF0000 + +#define ISPRSZ_VFILT1514_COEFF14_SHIFT		0 +#define ISPRSZ_VFILT1514_COEFF14_MASK		0x3FF +#define ISPRSZ_VFILT1514_COEFF15_SHIFT		16 +#define ISPRSZ_VFILT1514_COEFF15_MASK		0x3FF0000 + +#define ISPRSZ_VFILT1716_COEF16_SHIFT		0 +#define ISPRSZ_VFILT1716_COEF16_MASK		0x3FF +#define ISPRSZ_VFILT1716_COEF17_SHIFT		16 +#define ISPRSZ_VFILT1716_COEF17_MASK		0x3FF0000 + +#define ISPRSZ_VFILT1918_COEF18_SHIFT		0 +#define ISPRSZ_VFILT1918_COEF18_MASK		0x3FF +#define ISPRSZ_VFILT1918_COEF19_SHIFT		16 +#define ISPRSZ_VFILT1918_COEF19_MASK		0x3FF0000 + +#define ISPRSZ_VFILT2120_COEF20_SHIFT		0 +#define ISPRSZ_VFILT2120_COEF20_MASK		0x3FF +#define ISPRSZ_VFILT2120_COEF21_SHIFT		16 +#define ISPRSZ_VFILT2120_COEF21_MASK		0x3FF0000 + +#define ISPRSZ_VFILT2322_COEF22_SHIFT		0 +#define ISPRSZ_VFILT2322_COEF22_MASK		0x3FF +#define ISPRSZ_VFILT2322_COEF23_SHIFT		16 +#define ISPRSZ_VFILT2322_COEF23_MASK		0x3FF0000 + +#define ISPRSZ_VFILT2524_COEF24_SHIFT		0 +#define ISPRSZ_VFILT2524_COEF24_MASK		0x3FF +#define ISPRSZ_VFILT2524_COEF25_SHIFT		16 +#define ISPRSZ_VFILT2524_COEF25_MASK		0x3FF0000 + +#define ISPRSZ_VFILT2726_COEF26_SHIFT		0 +#define ISPRSZ_VFILT2726_COEF26_MASK		0x3FF +#define ISPRSZ_VFILT2726_COEF27_SHIFT		16 +#define ISPRSZ_VFILT2726_COEF27_MASK		0x3FF0000 + +#define ISPRSZ_VFILT2928_COEF28_SHIFT		0 +#define ISPRSZ_VFILT2928_COEF28_MASK		0x3FF +#define ISPRSZ_VFILT2928_COEF29_SHIFT		16 +#define ISPRSZ_VFILT2928_COEF29_MASK		0x3FF0000 + +#define ISPRSZ_VFILT3130_COEF30_SHIFT		0 +#define ISPRSZ_VFILT3130_COEF30_MASK		0x3FF +#define ISPRSZ_VFILT3130_COEF31_SHIFT		16 +#define ISPRSZ_VFILT3130_COEF31_MASK		0x3FF0000 + +#define ISPRSZ_YENH_CORE_SHIFT			0 +#define ISPRSZ_YENH_CORE_MASK			\ +	(0xFF << ISPRSZ_YENH_CORE_SHIFT) +#define ISPRSZ_YENH_SLOP_SHIFT			8 +#define ISPRSZ_YENH_SLOP_MASK			\ +	(0xF << ISPRSZ_YENH_SLOP_SHIFT) +#define ISPRSZ_YENH_GAIN_SHIFT			12 +#define ISPRSZ_YENH_GAIN_MASK			\ +	(0xF << ISPRSZ_YENH_GAIN_SHIFT) +#define ISPRSZ_YENH_ALGO_SHIFT			16 +#define ISPRSZ_YENH_ALGO_MASK			\ +	(0x3 << ISPRSZ_YENH_ALGO_SHIFT) + +#define ISPH3A_PCR_AEW_ALAW_EN_SHIFT		1 +#define ISPH3A_PCR_AF_MED_TH_SHIFT		3 +#define ISPH3A_PCR_AF_RGBPOS_SHIFT		11 +#define ISPH3A_PCR_AEW_AVE2LMT_SHIFT		22 +#define ISPH3A_PCR_AEW_AVE2LMT_MASK		0xFFC00000 +#define ISPH3A_PCR_BUSYAF			(1 << 15) +#define ISPH3A_PCR_BUSYAEAWB			(1 << 18) + +#define ISPH3A_AEWWIN1_WINHC_SHIFT		0 +#define ISPH3A_AEWWIN1_WINHC_MASK		0x3F +#define ISPH3A_AEWWIN1_WINVC_SHIFT		6 +#define ISPH3A_AEWWIN1_WINVC_MASK		0x1FC0 +#define ISPH3A_AEWWIN1_WINW_SHIFT		13 +#define ISPH3A_AEWWIN1_WINW_MASK		0xFE000 +#define ISPH3A_AEWWIN1_WINH_SHIFT		24 +#define ISPH3A_AEWWIN1_WINH_MASK		0x7F000000 + +#define ISPH3A_AEWINSTART_WINSH_SHIFT		0 +#define ISPH3A_AEWINSTART_WINSH_MASK		0x0FFF +#define ISPH3A_AEWINSTART_WINSV_SHIFT		16 +#define ISPH3A_AEWINSTART_WINSV_MASK		0x0FFF0000 + +#define ISPH3A_AEWINBLK_WINH_SHIFT		0 +#define ISPH3A_AEWINBLK_WINH_MASK		0x7F +#define ISPH3A_AEWINBLK_WINSV_SHIFT		16 +#define ISPH3A_AEWINBLK_WINSV_MASK		0x0FFF0000 + +#define ISPH3A_AEWSUBWIN_AEWINCH_SHIFT		0 +#define ISPH3A_AEWSUBWIN_AEWINCH_MASK		0x0F +#define ISPH3A_AEWSUBWIN_AEWINCV_SHIFT		8 +#define ISPH3A_AEWSUBWIN_AEWINCV_MASK		0x0F00 + +#define ISPHIST_PCR_ENABLE_SHIFT	0 +#define ISPHIST_PCR_ENABLE_MASK		0x01 +#define ISPHIST_PCR_ENABLE		(1 << ISPHIST_PCR_ENABLE_SHIFT) +#define ISPHIST_PCR_BUSY		0x02 + +#define ISPHIST_CNT_DATASIZE_SHIFT	8 +#define ISPHIST_CNT_DATASIZE_MASK	0x0100 +#define ISPHIST_CNT_CLEAR_SHIFT		7 +#define ISPHIST_CNT_CLEAR_MASK		0x080 +#define ISPHIST_CNT_CLEAR		(1 << ISPHIST_CNT_CLEAR_SHIFT) +#define ISPHIST_CNT_CFA_SHIFT		6 +#define ISPHIST_CNT_CFA_MASK		0x040 +#define ISPHIST_CNT_BINS_SHIFT		4 +#define ISPHIST_CNT_BINS_MASK		0x030 +#define ISPHIST_CNT_SOURCE_SHIFT	3 +#define ISPHIST_CNT_SOURCE_MASK		0x08 +#define ISPHIST_CNT_SHIFT_SHIFT		0 +#define ISPHIST_CNT_SHIFT_MASK		0x07 + +#define ISPHIST_WB_GAIN_WG00_SHIFT	24 +#define ISPHIST_WB_GAIN_WG00_MASK	0xFF000000 +#define ISPHIST_WB_GAIN_WG01_SHIFT	16 +#define ISPHIST_WB_GAIN_WG01_MASK	0xFF0000 +#define ISPHIST_WB_GAIN_WG02_SHIFT	8 +#define ISPHIST_WB_GAIN_WG02_MASK	0xFF00 +#define ISPHIST_WB_GAIN_WG03_SHIFT	0 +#define ISPHIST_WB_GAIN_WG03_MASK	0xFF + +#define ISPHIST_REG_START_END_MASK		0x3FFF +#define ISPHIST_REG_START_SHIFT			16 +#define ISPHIST_REG_END_SHIFT			0 +#define ISPHIST_REG_START_MASK			(ISPHIST_REG_START_END_MASK << \ +						 ISPHIST_REG_START_SHIFT) +#define ISPHIST_REG_END_MASK			(ISPHIST_REG_START_END_MASK << \ +						 ISPHIST_REG_END_SHIFT) + +#define ISPHIST_REG_MASK			(ISPHIST_REG_START_MASK | \ +						 ISPHIST_REG_END_MASK) + +#define ISPHIST_ADDR_SHIFT			0 +#define ISPHIST_ADDR_MASK			0x3FF + +#define ISPHIST_DATA_SHIFT			0 +#define ISPHIST_DATA_MASK			0xFFFFF + +#define ISPHIST_RADD_SHIFT			0 +#define ISPHIST_RADD_MASK			0xFFFFFFFF + +#define ISPHIST_RADD_OFF_SHIFT			0 +#define ISPHIST_RADD_OFF_MASK			0xFFFF + +#define ISPHIST_HV_INFO_HSIZE_SHIFT		16 +#define ISPHIST_HV_INFO_HSIZE_MASK		0x3FFF0000 +#define ISPHIST_HV_INFO_VSIZE_SHIFT		0 +#define ISPHIST_HV_INFO_VSIZE_MASK		0x3FFF + +#define ISPHIST_HV_INFO_MASK			0x3FFF3FFF + +#define ISPCCDC_LSC_ENABLE			1 +#define ISPCCDC_LSC_BUSY			(1 << 7) +#define ISPCCDC_LSC_GAIN_MODE_N_MASK		0x700 +#define ISPCCDC_LSC_GAIN_MODE_N_SHIFT		8 +#define ISPCCDC_LSC_GAIN_MODE_M_MASK		0x3800 +#define ISPCCDC_LSC_GAIN_MODE_M_SHIFT		12 +#define ISPCCDC_LSC_GAIN_FORMAT_MASK		0xE +#define ISPCCDC_LSC_GAIN_FORMAT_SHIFT		1 +#define ISPCCDC_LSC_AFTER_REFORMATTER_MASK	(1<<6) + +#define ISPCCDC_LSC_INITIAL_X_MASK		0x3F +#define ISPCCDC_LSC_INITIAL_X_SHIFT		0 +#define ISPCCDC_LSC_INITIAL_Y_MASK		0x3F0000 +#define ISPCCDC_LSC_INITIAL_Y_SHIFT		16 + +/* ----------------------------------------------------------------------------- + * CSI2 receiver registers (ES2.0) + */ + +#define ISPCSI2_REVISION			(0x000) +#define ISPCSI2_SYSCONFIG			(0x010) +#define ISPCSI2_SYSCONFIG_MSTANDBY_MODE_SHIFT	12 +#define ISPCSI2_SYSCONFIG_MSTANDBY_MODE_MASK	\ +	(0x3 << ISPCSI2_SYSCONFIG_MSTANDBY_MODE_SHIFT) +#define ISPCSI2_SYSCONFIG_MSTANDBY_MODE_FORCE	\ +	(0x0 << ISPCSI2_SYSCONFIG_MSTANDBY_MODE_SHIFT) +#define ISPCSI2_SYSCONFIG_MSTANDBY_MODE_NO	\ +	(0x1 << ISPCSI2_SYSCONFIG_MSTANDBY_MODE_SHIFT) +#define ISPCSI2_SYSCONFIG_MSTANDBY_MODE_SMART	\ +	(0x2 << ISPCSI2_SYSCONFIG_MSTANDBY_MODE_SHIFT) +#define ISPCSI2_SYSCONFIG_SOFT_RESET		(1 << 1) +#define ISPCSI2_SYSCONFIG_AUTO_IDLE		(1 << 0) + +#define ISPCSI2_SYSSTATUS			(0x014) +#define ISPCSI2_SYSSTATUS_RESET_DONE		(1 << 0) + +#define ISPCSI2_IRQSTATUS			(0x018) +#define ISPCSI2_IRQSTATUS_OCP_ERR_IRQ		(1 << 14) +#define ISPCSI2_IRQSTATUS_SHORT_PACKET_IRQ	(1 << 13) +#define ISPCSI2_IRQSTATUS_ECC_CORRECTION_IRQ	(1 << 12) +#define ISPCSI2_IRQSTATUS_ECC_NO_CORRECTION_IRQ	(1 << 11) +#define ISPCSI2_IRQSTATUS_COMPLEXIO2_ERR_IRQ	(1 << 10) +#define ISPCSI2_IRQSTATUS_COMPLEXIO1_ERR_IRQ	(1 << 9) +#define ISPCSI2_IRQSTATUS_FIFO_OVF_IRQ		(1 << 8) +#define ISPCSI2_IRQSTATUS_CONTEXT(n)		(1 << (n)) + +#define ISPCSI2_IRQENABLE			(0x01c) +#define ISPCSI2_CTRL				(0x040) +#define ISPCSI2_CTRL_VP_CLK_EN			(1 << 15) +#define ISPCSI2_CTRL_VP_ONLY_EN			(1 << 11) +#define ISPCSI2_CTRL_VP_OUT_CTRL_SHIFT		8 +#define ISPCSI2_CTRL_VP_OUT_CTRL_MASK		\ +	(3 << ISPCSI2_CTRL_VP_OUT_CTRL_SHIFT) +#define ISPCSI2_CTRL_DBG_EN			(1 << 7) +#define ISPCSI2_CTRL_BURST_SIZE_SHIFT		5 +#define ISPCSI2_CTRL_BURST_SIZE_MASK		\ +	(3 << ISPCSI2_CTRL_BURST_SIZE_SHIFT) +#define ISPCSI2_CTRL_FRAME			(1 << 3) +#define ISPCSI2_CTRL_ECC_EN			(1 << 2) +#define ISPCSI2_CTRL_SECURE			(1 << 1) +#define ISPCSI2_CTRL_IF_EN			(1 << 0) + +#define ISPCSI2_DBG_H				(0x044) +#define ISPCSI2_GNQ				(0x048) +#define ISPCSI2_PHY_CFG				(0x050) +#define ISPCSI2_PHY_CFG_RESET_CTRL		(1 << 30) +#define ISPCSI2_PHY_CFG_RESET_DONE		(1 << 29) +#define ISPCSI2_PHY_CFG_PWR_CMD_SHIFT		27 +#define ISPCSI2_PHY_CFG_PWR_CMD_MASK		\ +	(0x3 << ISPCSI2_PHY_CFG_PWR_CMD_SHIFT) +#define ISPCSI2_PHY_CFG_PWR_CMD_OFF		\ +	(0x0 << ISPCSI2_PHY_CFG_PWR_CMD_SHIFT) +#define ISPCSI2_PHY_CFG_PWR_CMD_ON		\ +	(0x1 << ISPCSI2_PHY_CFG_PWR_CMD_SHIFT) +#define ISPCSI2_PHY_CFG_PWR_CMD_ULPW		\ +	(0x2 << ISPCSI2_PHY_CFG_PWR_CMD_SHIFT) +#define ISPCSI2_PHY_CFG_PWR_STATUS_SHIFT	25 +#define ISPCSI2_PHY_CFG_PWR_STATUS_MASK		\ +	(0x3 << ISPCSI2_PHY_CFG_PWR_STATUS_SHIFT) +#define ISPCSI2_PHY_CFG_PWR_STATUS_OFF		\ +	(0x0 << ISPCSI2_PHY_CFG_PWR_STATUS_SHIFT) +#define ISPCSI2_PHY_CFG_PWR_STATUS_ON		\ +	(0x1 << ISPCSI2_PHY_CFG_PWR_STATUS_SHIFT) +#define ISPCSI2_PHY_CFG_PWR_STATUS_ULPW		\ +	(0x2 << ISPCSI2_PHY_CFG_PWR_STATUS_SHIFT) +#define ISPCSI2_PHY_CFG_PWR_AUTO		(1 << 24) + +#define ISPCSI2_PHY_CFG_DATA_POL_SHIFT(n)	(3 + ((n) * 4)) +#define ISPCSI2_PHY_CFG_DATA_POL_MASK(n)	\ +	(0x1 << ISPCSI2_PHY_CFG_DATA_POL_SHIFT(n)) +#define ISPCSI2_PHY_CFG_DATA_POL_PN(n)		\ +	(0x0 << ISPCSI2_PHY_CFG_DATA_POL_SHIFT(n)) +#define ISPCSI2_PHY_CFG_DATA_POL_NP(n)		\ +	(0x1 << ISPCSI2_PHY_CFG_DATA_POL_SHIFT(n)) + +#define ISPCSI2_PHY_CFG_DATA_POSITION_SHIFT(n)	((n) * 4) +#define ISPCSI2_PHY_CFG_DATA_POSITION_MASK(n)	\ +	(0x7 << ISPCSI2_PHY_CFG_DATA_POSITION_SHIFT(n)) +#define ISPCSI2_PHY_CFG_DATA_POSITION_NC(n)	\ +	(0x0 << ISPCSI2_PHY_CFG_DATA_POSITION_SHIFT(n)) +#define ISPCSI2_PHY_CFG_DATA_POSITION_1(n)	\ +	(0x1 << ISPCSI2_PHY_CFG_DATA_POSITION_SHIFT(n)) +#define ISPCSI2_PHY_CFG_DATA_POSITION_2(n)	\ +	(0x2 << ISPCSI2_PHY_CFG_DATA_POSITION_SHIFT(n)) +#define ISPCSI2_PHY_CFG_DATA_POSITION_3(n)	\ +	(0x3 << ISPCSI2_PHY_CFG_DATA_POSITION_SHIFT(n)) +#define ISPCSI2_PHY_CFG_DATA_POSITION_4(n)	\ +	(0x4 << ISPCSI2_PHY_CFG_DATA_POSITION_SHIFT(n)) +#define ISPCSI2_PHY_CFG_DATA_POSITION_5(n)	\ +	(0x5 << ISPCSI2_PHY_CFG_DATA_POSITION_SHIFT(n)) + +#define ISPCSI2_PHY_CFG_CLOCK_POL_SHIFT		3 +#define ISPCSI2_PHY_CFG_CLOCK_POL_MASK		\ +	(0x1 << ISPCSI2_PHY_CFG_CLOCK_POL_SHIFT) +#define ISPCSI2_PHY_CFG_CLOCK_POL_PN		\ +	(0x0 << ISPCSI2_PHY_CFG_CLOCK_POL_SHIFT) +#define ISPCSI2_PHY_CFG_CLOCK_POL_NP		\ +	(0x1 << ISPCSI2_PHY_CFG_CLOCK_POL_SHIFT) + +#define ISPCSI2_PHY_CFG_CLOCK_POSITION_SHIFT	0 +#define ISPCSI2_PHY_CFG_CLOCK_POSITION_MASK	\ +	(0x7 << ISPCSI2_PHY_CFG_CLOCK_POSITION_SHIFT) +#define ISPCSI2_PHY_CFG_CLOCK_POSITION_1	\ +	(0x1 << ISPCSI2_PHY_CFG_CLOCK_POSITION_SHIFT) +#define ISPCSI2_PHY_CFG_CLOCK_POSITION_2	\ +	(0x2 << ISPCSI2_PHY_CFG_CLOCK_POSITION_SHIFT) +#define ISPCSI2_PHY_CFG_CLOCK_POSITION_3	\ +	(0x3 << ISPCSI2_PHY_CFG_CLOCK_POSITION_SHIFT) +#define ISPCSI2_PHY_CFG_CLOCK_POSITION_4	\ +	(0x4 << ISPCSI2_PHY_CFG_CLOCK_POSITION_SHIFT) +#define ISPCSI2_PHY_CFG_CLOCK_POSITION_5	\ +	(0x5 << ISPCSI2_PHY_CFG_CLOCK_POSITION_SHIFT) + +#define ISPCSI2_PHY_IRQSTATUS			(0x054) +#define ISPCSI2_PHY_IRQSTATUS_STATEALLULPMEXIT	(1 << 26) +#define ISPCSI2_PHY_IRQSTATUS_STATEALLULPMENTER	(1 << 25) +#define ISPCSI2_PHY_IRQSTATUS_STATEULPM5	(1 << 24) +#define ISPCSI2_PHY_IRQSTATUS_STATEULPM4	(1 << 23) +#define ISPCSI2_PHY_IRQSTATUS_STATEULPM3	(1 << 22) +#define ISPCSI2_PHY_IRQSTATUS_STATEULPM2	(1 << 21) +#define ISPCSI2_PHY_IRQSTATUS_STATEULPM1	(1 << 20) +#define ISPCSI2_PHY_IRQSTATUS_ERRCONTROL5	(1 << 19) +#define ISPCSI2_PHY_IRQSTATUS_ERRCONTROL4	(1 << 18) +#define ISPCSI2_PHY_IRQSTATUS_ERRCONTROL3	(1 << 17) +#define ISPCSI2_PHY_IRQSTATUS_ERRCONTROL2	(1 << 16) +#define ISPCSI2_PHY_IRQSTATUS_ERRCONTROL1	(1 << 15) +#define ISPCSI2_PHY_IRQSTATUS_ERRESC5		(1 << 14) +#define ISPCSI2_PHY_IRQSTATUS_ERRESC4		(1 << 13) +#define ISPCSI2_PHY_IRQSTATUS_ERRESC3		(1 << 12) +#define ISPCSI2_PHY_IRQSTATUS_ERRESC2		(1 << 11) +#define ISPCSI2_PHY_IRQSTATUS_ERRESC1		(1 << 10) +#define ISPCSI2_PHY_IRQSTATUS_ERRSOTSYNCHS5	(1 << 9) +#define ISPCSI2_PHY_IRQSTATUS_ERRSOTSYNCHS4	(1 << 8) +#define ISPCSI2_PHY_IRQSTATUS_ERRSOTSYNCHS3	(1 << 7) +#define ISPCSI2_PHY_IRQSTATUS_ERRSOTSYNCHS2	(1 << 6) +#define ISPCSI2_PHY_IRQSTATUS_ERRSOTSYNCHS1	(1 << 5) +#define ISPCSI2_PHY_IRQSTATUS_ERRSOTHS5		(1 << 4) +#define ISPCSI2_PHY_IRQSTATUS_ERRSOTHS4		(1 << 3) +#define ISPCSI2_PHY_IRQSTATUS_ERRSOTHS3		(1 << 2) +#define ISPCSI2_PHY_IRQSTATUS_ERRSOTHS2		(1 << 1) +#define ISPCSI2_PHY_IRQSTATUS_ERRSOTHS1		1 + +#define ISPCSI2_SHORT_PACKET			(0x05c) +#define ISPCSI2_PHY_IRQENABLE			(0x060) +#define ISPCSI2_PHY_IRQENABLE_STATEALLULPMEXIT	(1 << 26) +#define ISPCSI2_PHY_IRQENABLE_STATEALLULPMENTER	(1 << 25) +#define ISPCSI2_PHY_IRQENABLE_STATEULPM5	(1 << 24) +#define ISPCSI2_PHY_IRQENABLE_STATEULPM4	(1 << 23) +#define ISPCSI2_PHY_IRQENABLE_STATEULPM3	(1 << 22) +#define ISPCSI2_PHY_IRQENABLE_STATEULPM2	(1 << 21) +#define ISPCSI2_PHY_IRQENABLE_STATEULPM1	(1 << 20) +#define ISPCSI2_PHY_IRQENABLE_ERRCONTROL5	(1 << 19) +#define ISPCSI2_PHY_IRQENABLE_ERRCONTROL4	(1 << 18) +#define ISPCSI2_PHY_IRQENABLE_ERRCONTROL3	(1 << 17) +#define ISPCSI2_PHY_IRQENABLE_ERRCONTROL2	(1 << 16) +#define ISPCSI2_PHY_IRQENABLE_ERRCONTROL1	(1 << 15) +#define ISPCSI2_PHY_IRQENABLE_ERRESC5		(1 << 14) +#define ISPCSI2_PHY_IRQENABLE_ERRESC4		(1 << 13) +#define ISPCSI2_PHY_IRQENABLE_ERRESC3		(1 << 12) +#define ISPCSI2_PHY_IRQENABLE_ERRESC2		(1 << 11) +#define ISPCSI2_PHY_IRQENABLE_ERRESC1		(1 << 10) +#define ISPCSI2_PHY_IRQENABLE_ERRSOTSYNCHS5	(1 << 9) +#define ISPCSI2_PHY_IRQENABLE_ERRSOTSYNCHS4	(1 << 8) +#define ISPCSI2_PHY_IRQENABLE_ERRSOTSYNCHS3	(1 << 7) +#define ISPCSI2_PHY_IRQENABLE_ERRSOTSYNCHS2	(1 << 6) +#define ISPCSI2_PHY_IRQENABLE_ERRSOTSYNCHS1	(1 << 5) +#define ISPCSI2_PHY_IRQENABLE_ERRSOTHS5		(1 << 4) +#define ISPCSI2_PHY_IRQENABLE_ERRSOTHS4		(1 << 3) +#define ISPCSI2_PHY_IRQENABLE_ERRSOTHS3		(1 << 2) +#define ISPCSI2_PHY_IRQENABLE_ERRSOTHS2		(1 << 1) +#define ISPCSI2_PHY_IRQENABLE_ERRSOTHS1		(1 << 0) + +#define ISPCSI2_DBG_P				(0x068) +#define ISPCSI2_TIMING				(0x06c) +#define ISPCSI2_TIMING_FORCE_RX_MODE_IO(n)	(1 << ((16 * ((n) - 1)) + 15)) +#define ISPCSI2_TIMING_STOP_STATE_X16_IO(n)	(1 << ((16 * ((n) - 1)) + 14)) +#define ISPCSI2_TIMING_STOP_STATE_X4_IO(n)	(1 << ((16 * ((n) - 1)) + 13)) +#define ISPCSI2_TIMING_STOP_STATE_COUNTER_IO_SHIFT(n)	(16 * ((n) - 1)) +#define ISPCSI2_TIMING_STOP_STATE_COUNTER_IO_MASK(n)	\ +	(0x1fff << ISPCSI2_TIMING_STOP_STATE_COUNTER_IO_SHIFT(n)) + +#define ISPCSI2_CTX_CTRL1(n)			((0x070) + 0x20 * (n)) +#define ISPCSI2_CTX_CTRL1_COUNT_SHIFT		8 +#define ISPCSI2_CTX_CTRL1_COUNT_MASK		\ +	(0xff << ISPCSI2_CTX_CTRL1_COUNT_SHIFT) +#define ISPCSI2_CTX_CTRL1_EOF_EN		(1 << 7) +#define ISPCSI2_CTX_CTRL1_EOL_EN		(1 << 6) +#define ISPCSI2_CTX_CTRL1_CS_EN			(1 << 5) +#define ISPCSI2_CTX_CTRL1_COUNT_UNLOCK		(1 << 4) +#define ISPCSI2_CTX_CTRL1_PING_PONG		(1 << 3) +#define ISPCSI2_CTX_CTRL1_CTX_EN		(1 << 0) + +#define ISPCSI2_CTX_CTRL2(n)			((0x074) + 0x20 * (n)) +#define ISPCSI2_CTX_CTRL2_USER_DEF_MAP_SHIFT	13 +#define ISPCSI2_CTX_CTRL2_USER_DEF_MAP_MASK	\ +	(0x3 << ISPCSI2_CTX_CTRL2_USER_DEF_MAP_SHIFT) +#define ISPCSI2_CTX_CTRL2_VIRTUAL_ID_SHIFT	11 +#define ISPCSI2_CTX_CTRL2_VIRTUAL_ID_MASK	\ +	(0x3 <<	ISPCSI2_CTX_CTRL2_VIRTUAL_ID_SHIFT) +#define ISPCSI2_CTX_CTRL2_DPCM_PRED		(1 << 10) +#define ISPCSI2_CTX_CTRL2_FORMAT_SHIFT		0 +#define ISPCSI2_CTX_CTRL2_FORMAT_MASK		\ +	(0x3ff << ISPCSI2_CTX_CTRL2_FORMAT_SHIFT) +#define ISPCSI2_CTX_CTRL2_FRAME_SHIFT		16 +#define ISPCSI2_CTX_CTRL2_FRAME_MASK		\ +	(0xffff << ISPCSI2_CTX_CTRL2_FRAME_SHIFT) + +#define ISPCSI2_CTX_DAT_OFST(n)			((0x078) + 0x20 * (n)) +#define ISPCSI2_CTX_DAT_OFST_OFST_SHIFT		0 +#define ISPCSI2_CTX_DAT_OFST_OFST_MASK		\ +	(0x1ffe0 << ISPCSI2_CTX_DAT_OFST_OFST_SHIFT) + +#define ISPCSI2_CTX_DAT_PING_ADDR(n)		((0x07c) + 0x20 * (n)) +#define ISPCSI2_CTX_DAT_PONG_ADDR(n)		((0x080) + 0x20 * (n)) +#define ISPCSI2_CTX_IRQENABLE(n)		((0x084) + 0x20 * (n)) +#define ISPCSI2_CTX_IRQENABLE_ECC_CORRECTION_IRQ	(1 << 8) +#define ISPCSI2_CTX_IRQENABLE_LINE_NUMBER_IRQ	(1 << 7) +#define ISPCSI2_CTX_IRQENABLE_FRAME_NUMBER_IRQ	(1 << 6) +#define ISPCSI2_CTX_IRQENABLE_CS_IRQ		(1 << 5) +#define ISPCSI2_CTX_IRQENABLE_LE_IRQ		(1 << 3) +#define ISPCSI2_CTX_IRQENABLE_LS_IRQ		(1 << 2) +#define ISPCSI2_CTX_IRQENABLE_FE_IRQ		(1 << 1) +#define ISPCSI2_CTX_IRQENABLE_FS_IRQ		(1 << 0) + +#define ISPCSI2_CTX_IRQSTATUS(n)		((0x088) + 0x20 * (n)) +#define ISPCSI2_CTX_IRQSTATUS_ECC_CORRECTION_IRQ	(1 << 8) +#define ISPCSI2_CTX_IRQSTATUS_LINE_NUMBER_IRQ	(1 << 7) +#define ISPCSI2_CTX_IRQSTATUS_FRAME_NUMBER_IRQ	(1 << 6) +#define ISPCSI2_CTX_IRQSTATUS_CS_IRQ		(1 << 5) +#define ISPCSI2_CTX_IRQSTATUS_LE_IRQ		(1 << 3) +#define ISPCSI2_CTX_IRQSTATUS_LS_IRQ		(1 << 2) +#define ISPCSI2_CTX_IRQSTATUS_FE_IRQ		(1 << 1) +#define ISPCSI2_CTX_IRQSTATUS_FS_IRQ		(1 << 0) + +#define ISPCSI2_CTX_CTRL3(n)			((0x08c) + 0x20 * (n)) +#define ISPCSI2_CTX_CTRL3_ALPHA_SHIFT		5 +#define ISPCSI2_CTX_CTRL3_ALPHA_MASK		\ +	(0x3fff << ISPCSI2_CTX_CTRL3_ALPHA_SHIFT) + +/* This instance is for OMAP3630 only */ +#define ISPCSI2_CTX_TRANSCODEH(n)		(0x000 + 0x8 * (n)) +#define ISPCSI2_CTX_TRANSCODEH_HCOUNT_SHIFT	16 +#define ISPCSI2_CTX_TRANSCODEH_HCOUNT_MASK	\ +	(0x1fff << ISPCSI2_CTX_TRANSCODEH_HCOUNT_SHIFT) +#define ISPCSI2_CTX_TRANSCODEH_HSKIP_SHIFT	0 +#define ISPCSI2_CTX_TRANSCODEH_HSKIP_MASK	\ +	(0x1fff << ISPCSI2_CTX_TRANSCODEH_HCOUNT_SHIFT) +#define ISPCSI2_CTX_TRANSCODEV(n)		(0x004 + 0x8 * (n)) +#define ISPCSI2_CTX_TRANSCODEV_VCOUNT_SHIFT	16 +#define ISPCSI2_CTX_TRANSCODEV_VCOUNT_MASK	\ +	(0x1fff << ISPCSI2_CTX_TRANSCODEV_VCOUNT_SHIFT) +#define ISPCSI2_CTX_TRANSCODEV_VSKIP_SHIFT	0 +#define ISPCSI2_CTX_TRANSCODEV_VSKIP_MASK	\ +	(0x1fff << ISPCSI2_CTX_TRANSCODEV_VCOUNT_SHIFT) + +/* ----------------------------------------------------------------------------- + * CSI PHY registers + */ + +#define ISPCSIPHY_REG0				(0x000) +#define ISPCSIPHY_REG0_THS_TERM_SHIFT		8 +#define ISPCSIPHY_REG0_THS_TERM_MASK		\ +	(0xff << ISPCSIPHY_REG0_THS_TERM_SHIFT) +#define ISPCSIPHY_REG0_THS_SETTLE_SHIFT		0 +#define ISPCSIPHY_REG0_THS_SETTLE_MASK		\ +	(0xff << ISPCSIPHY_REG0_THS_SETTLE_SHIFT) + +#define ISPCSIPHY_REG1					(0x004) +#define ISPCSIPHY_REG1_RESET_DONE_CTRLCLK		(1 << 29) +/* This field is for OMAP3630 only */ +#define ISPCSIPHY_REG1_CLOCK_MISS_DETECTOR_STATUS	(1 << 25) +#define ISPCSIPHY_REG1_TCLK_TERM_SHIFT			18 +#define ISPCSIPHY_REG1_TCLK_TERM_MASK			\ +	(0x7f << ISPCSIPHY_REG1_TCLK_TERM_SHIFT) +#define ISPCSIPHY_REG1_DPHY_HS_SYNC_PATTERN_SHIFT	10 +#define ISPCSIPHY_REG1_DPHY_HS_SYNC_PATTERN_MASK	\ +	(0xff << ISPCSIPHY_REG1_DPHY_HS_SYNC_PATTERN) +/* This field is for OMAP3430 only */ +#define ISPCSIPHY_REG1_TCLK_MISS_SHIFT			8 +#define ISPCSIPHY_REG1_TCLK_MISS_MASK			\ +	(0x3 << ISPCSIPHY_REG1_TCLK_MISS_SHIFT) +/* This field is for OMAP3630 only */ +#define ISPCSIPHY_REG1_CTRLCLK_DIV_FACTOR_SHIFT		8 +#define ISPCSIPHY_REG1_CTRLCLK_DIV_FACTOR_MASK		\ +	(0x3 << ISPCSIPHY_REG1_CTRLCLK_DIV_FACTOR_SHIFT) +#define ISPCSIPHY_REG1_TCLK_SETTLE_SHIFT		0 +#define ISPCSIPHY_REG1_TCLK_SETTLE_MASK			\ +	(0xff << ISPCSIPHY_REG1_TCLK_SETTLE_SHIFT) + +/* This register is for OMAP3630 only */ +#define ISPCSIPHY_REG2					(0x008) +#define ISPCSIPHY_REG2_TRIGGER_CMD_RXTRIGESC0_SHIFT	30 +#define ISPCSIPHY_REG2_TRIGGER_CMD_RXTRIGESC0_MASK	\ +	(0x3 << ISPCSIPHY_REG2_TRIGGER_CMD_RXTRIGESC0_SHIFT) +#define ISPCSIPHY_REG2_TRIGGER_CMD_RXTRIGESC1_SHIFT	28 +#define ISPCSIPHY_REG2_TRIGGER_CMD_RXTRIGESC1_MASK	\ +	(0x3 << ISPCSIPHY_REG2_TRIGGER_CMD_RXTRIGESC1_SHIFT) +#define ISPCSIPHY_REG2_TRIGGER_CMD_RXTRIGESC2_SHIFT	26 +#define ISPCSIPHY_REG2_TRIGGER_CMD_RXTRIGESC2_MASK	\ +	(0x3 << ISPCSIPHY_REG2_TRIGGER_CMD_RXTRIGESC2_SHIFT) +#define ISPCSIPHY_REG2_TRIGGER_CMD_RXTRIGESC3_SHIFT	24 +#define ISPCSIPHY_REG2_TRIGGER_CMD_RXTRIGESC3_MASK	\ +	(0x3 << ISPCSIPHY_REG2_TRIGGER_CMD_RXTRIGESC3_SHIFT) +#define ISPCSIPHY_REG2_CCP2_SYNC_PATTERN_SHIFT		0 +#define ISPCSIPHY_REG2_CCP2_SYNC_PATTERN_MASK		\ +	(0x7fffff << ISPCSIPHY_REG2_CCP2_SYNC_PATTERN_SHIFT) + +/* ----------------------------------------------------------------------------- + * CONTROL registers for CSI-2 phy routing + */ + +/* OMAP343X_CONTROL_CSIRXFE */ +#define OMAP343X_CONTROL_CSIRXFE_CSIB_INV	(1 << 7) +#define OMAP343X_CONTROL_CSIRXFE_RESENABLE	(1 << 8) +#define OMAP343X_CONTROL_CSIRXFE_SELFORM	(1 << 10) +#define OMAP343X_CONTROL_CSIRXFE_PWRDNZ		(1 << 12) +#define OMAP343X_CONTROL_CSIRXFE_RESET		(1 << 13) + +/* OMAP3630_CONTROL_CAMERA_PHY_CTRL */ +#define OMAP3630_CONTROL_CAMERA_PHY_CTRL_CAMMODE_PHY1_SHIFT	2 +#define OMAP3630_CONTROL_CAMERA_PHY_CTRL_CAMMODE_PHY2_SHIFT	0 +#define OMAP3630_CONTROL_CAMERA_PHY_CTRL_CAMMODE_DPHY		0x0 +#define OMAP3630_CONTROL_CAMERA_PHY_CTRL_CAMMODE_CCP2_DATA_STROBE 0x1 +#define OMAP3630_CONTROL_CAMERA_PHY_CTRL_CAMMODE_CCP2_DATA_CLOCK 0x2 +#define OMAP3630_CONTROL_CAMERA_PHY_CTRL_CAMMODE_GPI		0x3 +#define OMAP3630_CONTROL_CAMERA_PHY_CTRL_CAMMODE_MASK		0x3 +/* CCP2B: set to receive data from PHY2 instead of PHY1 */ +#define OMAP3630_CONTROL_CAMERA_PHY_CTRL_CSI1_RX_SEL_PHY2	(1 << 4) + +#endif	/* OMAP3_ISP_REG_H */ diff --git a/drivers/media/platform/omap3isp/ispresizer.c b/drivers/media/platform/omap3isp/ispresizer.c new file mode 100644 index 00000000000..6f077c2377d --- /dev/null +++ b/drivers/media/platform/omap3isp/ispresizer.c @@ -0,0 +1,1794 @@ +/* + * ispresizer.c + * + * TI OMAP3 ISP - Resizer module + * + * Copyright (C) 2010 Nokia Corporation + * Copyright (C) 2009 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/device.h> +#include <linux/mm.h> +#include <linux/module.h> + +#include "isp.h" +#include "ispreg.h" +#include "ispresizer.h" + +/* + * Resizer Constants + */ +#define MIN_RESIZE_VALUE		64 +#define MID_RESIZE_VALUE		512 +#define MAX_RESIZE_VALUE		1024 + +#define MIN_IN_WIDTH			32 +#define MIN_IN_HEIGHT			32 +#define MAX_IN_WIDTH_MEMORY_MODE	4095 +#define MAX_IN_WIDTH_ONTHEFLY_MODE_ES1	1280 +#define MAX_IN_WIDTH_ONTHEFLY_MODE_ES2	4095 +#define MAX_IN_HEIGHT			4095 + +#define MIN_OUT_WIDTH			16 +#define MIN_OUT_HEIGHT			2 +#define MAX_OUT_HEIGHT			4095 + +/* + * Resizer Use Constraints + * "TRM ES3.1, table 12-46" + */ +#define MAX_4TAP_OUT_WIDTH_ES1		1280 +#define MAX_7TAP_OUT_WIDTH_ES1		640 +#define MAX_4TAP_OUT_WIDTH_ES2		3312 +#define MAX_7TAP_OUT_WIDTH_ES2		1650 +#define MAX_4TAP_OUT_WIDTH_3630		4096 +#define MAX_7TAP_OUT_WIDTH_3630		2048 + +/* + * Constants for ratio calculation + */ +#define RESIZE_DIVISOR			256 +#define DEFAULT_PHASE			1 + +/* + * Default (and only) configuration of filter coefficients. + * 7-tap mode is for scale factors 0.25x to 0.5x. + * 4-tap mode is for scale factors 0.5x to 4.0x. + * There shouldn't be any reason to recalculate these, EVER. + */ +static const struct isprsz_coef filter_coefs = { +	/* For 8-phase 4-tap horizontal filter: */ +	{ +		0x0000, 0x0100, 0x0000, 0x0000, +		0x03FA, 0x00F6, 0x0010, 0x0000, +		0x03F9, 0x00DB, 0x002C, 0x0000, +		0x03FB, 0x00B3, 0x0053, 0x03FF, +		0x03FD, 0x0082, 0x0084, 0x03FD, +		0x03FF, 0x0053, 0x00B3, 0x03FB, +		0x0000, 0x002C, 0x00DB, 0x03F9, +		0x0000, 0x0010, 0x00F6, 0x03FA +	}, +	/* For 8-phase 4-tap vertical filter: */ +	{ +		0x0000, 0x0100, 0x0000, 0x0000, +		0x03FA, 0x00F6, 0x0010, 0x0000, +		0x03F9, 0x00DB, 0x002C, 0x0000, +		0x03FB, 0x00B3, 0x0053, 0x03FF, +		0x03FD, 0x0082, 0x0084, 0x03FD, +		0x03FF, 0x0053, 0x00B3, 0x03FB, +		0x0000, 0x002C, 0x00DB, 0x03F9, +		0x0000, 0x0010, 0x00F6, 0x03FA +	}, +	/* For 4-phase 7-tap horizontal filter: */ +	#define DUMMY 0 +	{ +		0x0004, 0x0023, 0x005A, 0x0058, 0x0023, 0x0004, 0x0000, DUMMY, +		0x0002, 0x0018, 0x004d, 0x0060, 0x0031, 0x0008, 0x0000, DUMMY, +		0x0001, 0x000f, 0x003f, 0x0062, 0x003f, 0x000f, 0x0001, DUMMY, +		0x0000, 0x0008, 0x0031, 0x0060, 0x004d, 0x0018, 0x0002, DUMMY +	}, +	/* For 4-phase 7-tap vertical filter: */ +	{ +		0x0004, 0x0023, 0x005A, 0x0058, 0x0023, 0x0004, 0x0000, DUMMY, +		0x0002, 0x0018, 0x004d, 0x0060, 0x0031, 0x0008, 0x0000, DUMMY, +		0x0001, 0x000f, 0x003f, 0x0062, 0x003f, 0x000f, 0x0001, DUMMY, +		0x0000, 0x0008, 0x0031, 0x0060, 0x004d, 0x0018, 0x0002, DUMMY +	} +	/* +	 * The dummy padding is required in 7-tap mode because of how the +	 * registers are arranged physically. +	 */ +	#undef DUMMY +}; + +/* + * __resizer_get_format - helper function for getting resizer format + * @res   : pointer to resizer private structure + * @pad   : pad number + * @fh    : V4L2 subdev file handle + * @which : wanted subdev format + * return zero + */ +static struct v4l2_mbus_framefmt * +__resizer_get_format(struct isp_res_device *res, 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 &res->formats[pad]; +} + +/* + * __resizer_get_crop - helper function for getting resizer crop rectangle + * @res   : pointer to resizer private structure + * @fh    : V4L2 subdev file handle + * @which : wanted subdev crop rectangle + */ +static struct v4l2_rect * +__resizer_get_crop(struct isp_res_device *res, struct v4l2_subdev_fh *fh, +		   enum v4l2_subdev_format_whence which) +{ +	if (which == V4L2_SUBDEV_FORMAT_TRY) +		return v4l2_subdev_get_try_crop(fh, RESZ_PAD_SINK); +	else +		return &res->crop.request; +} + +/* + * resizer_set_filters - Set resizer filters + * @res: Device context. + * @h_coeff: horizontal coefficient + * @v_coeff: vertical coefficient + * Return none + */ +static void resizer_set_filters(struct isp_res_device *res, const u16 *h_coeff, +				const u16 *v_coeff) +{ +	struct isp_device *isp = to_isp_device(res); +	u32 startaddr_h, startaddr_v, tmp_h, tmp_v; +	int i; + +	startaddr_h = ISPRSZ_HFILT10; +	startaddr_v = ISPRSZ_VFILT10; + +	for (i = 0; i < COEFF_CNT; i += 2) { +		tmp_h = h_coeff[i] | +			(h_coeff[i + 1] << ISPRSZ_HFILT_COEF1_SHIFT); +		tmp_v = v_coeff[i] | +			(v_coeff[i + 1] << ISPRSZ_VFILT_COEF1_SHIFT); +		isp_reg_writel(isp, tmp_h, OMAP3_ISP_IOMEM_RESZ, startaddr_h); +		isp_reg_writel(isp, tmp_v, OMAP3_ISP_IOMEM_RESZ, startaddr_v); +		startaddr_h += 4; +		startaddr_v += 4; +	} +} + +/* + * resizer_set_bilinear - Chrominance horizontal algorithm select + * @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 resizer_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_set(isp, OMAP3_ISP_IOMEM_RESZ, ISPRSZ_CNT, +			    ISPRSZ_CNT_CBILIN); +	else +		isp_reg_clr(isp, OMAP3_ISP_IOMEM_RESZ, ISPRSZ_CNT, +			    ISPRSZ_CNT_CBILIN); +} + +/* + * resizer_set_ycpos - Luminance and chrominance order + * @res: Device context. + * @pixelcode: pixel code. + */ +static void resizer_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_YUYV8_1X16: +		isp_reg_set(isp, OMAP3_ISP_IOMEM_RESZ, ISPRSZ_CNT, +			    ISPRSZ_CNT_YCPOS); +		break; +	case V4L2_MBUS_FMT_UYVY8_1X16: +		isp_reg_clr(isp, OMAP3_ISP_IOMEM_RESZ, ISPRSZ_CNT, +			    ISPRSZ_CNT_YCPOS); +		break; +	default: +		return; +	} +} + +/* + * resizer_set_phase - Setup horizontal and vertical starting phase + * @res: Device context. + * @h_phase: horizontal phase parameters. + * @v_phase: vertical phase parameters. + * + * Horizontal and vertical phase range is 0 to 7 + */ +static void resizer_set_phase(struct isp_res_device *res, u32 h_phase, +			      u32 v_phase) +{ +	struct isp_device *isp = to_isp_device(res); +	u32 rgval = 0; + +	rgval = isp_reg_readl(isp, OMAP3_ISP_IOMEM_RESZ, ISPRSZ_CNT) & +	      ~(ISPRSZ_CNT_HSTPH_MASK | ISPRSZ_CNT_VSTPH_MASK); +	rgval |= (h_phase << ISPRSZ_CNT_HSTPH_SHIFT) & ISPRSZ_CNT_HSTPH_MASK; +	rgval |= (v_phase << ISPRSZ_CNT_VSTPH_SHIFT) & ISPRSZ_CNT_VSTPH_MASK; + +	isp_reg_writel(isp, rgval, OMAP3_ISP_IOMEM_RESZ, ISPRSZ_CNT); +} + +/* + * resizer_set_luma - Setup luminance enhancer parameters + * @res: Device context. + * @luma: Structure for luminance enhancer parameters. + * + * Algorithm select: + *  0x0: Disable + *  0x1: [-1  2 -1]/2 high-pass filter + *  0x2: [-1 -2  6 -2 -1]/4 high-pass filter + * + * Maximum gain: + *  The data is coded in U4Q4 representation. + * + * Slope: + *  The data is coded in U4Q4 representation. + * + * Coring offset: + *  The data is coded in U8Q0 representation. + * + * The new luminance value is computed as: + *  Y += HPF(Y) x max(GAIN, (HPF(Y) - CORE) x SLOP + 8) >> 4. + */ +static void resizer_set_luma(struct isp_res_device *res, +			     struct resizer_luma_yenh *luma) +{ +	struct isp_device *isp = to_isp_device(res); +	u32 rgval = 0; + +	rgval  = (luma->algo << ISPRSZ_YENH_ALGO_SHIFT) +		  & ISPRSZ_YENH_ALGO_MASK; +	rgval |= (luma->gain << ISPRSZ_YENH_GAIN_SHIFT) +		  & ISPRSZ_YENH_GAIN_MASK; +	rgval |= (luma->slope << ISPRSZ_YENH_SLOP_SHIFT) +		  & ISPRSZ_YENH_SLOP_MASK; +	rgval |= (luma->core << ISPRSZ_YENH_CORE_SHIFT) +		  & ISPRSZ_YENH_CORE_MASK; + +	isp_reg_writel(isp, rgval, OMAP3_ISP_IOMEM_RESZ, ISPRSZ_YENH); +} + +/* + * resizer_set_source - Input source select + * @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 resizer_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_set(isp, OMAP3_ISP_IOMEM_RESZ, ISPRSZ_CNT, +			    ISPRSZ_CNT_INPSRC); +	else +		isp_reg_clr(isp, OMAP3_ISP_IOMEM_RESZ, ISPRSZ_CNT, +			    ISPRSZ_CNT_INPSRC); +} + +/* + * resizer_set_ratio - Setup horizontal and vertical resizing value + * @res: Device context. + * @ratio: Structure for ratio parameters. + * + * Resizing range from 64 to 1024 + */ +static void resizer_set_ratio(struct isp_res_device *res, +			      const struct resizer_ratio *ratio) +{ +	struct isp_device *isp = to_isp_device(res); +	const u16 *h_filter, *v_filter; +	u32 rgval = 0; + +	rgval = isp_reg_readl(isp, OMAP3_ISP_IOMEM_RESZ, ISPRSZ_CNT) & +			      ~(ISPRSZ_CNT_HRSZ_MASK | ISPRSZ_CNT_VRSZ_MASK); +	rgval |= ((ratio->horz - 1) << ISPRSZ_CNT_HRSZ_SHIFT) +		  & ISPRSZ_CNT_HRSZ_MASK; +	rgval |= ((ratio->vert - 1) << ISPRSZ_CNT_VRSZ_SHIFT) +		  & ISPRSZ_CNT_VRSZ_MASK; +	isp_reg_writel(isp, rgval, OMAP3_ISP_IOMEM_RESZ, ISPRSZ_CNT); + +	/* prepare horizontal filter coefficients */ +	if (ratio->horz > MID_RESIZE_VALUE) +		h_filter = &filter_coefs.h_filter_coef_7tap[0]; +	else +		h_filter = &filter_coefs.h_filter_coef_4tap[0]; + +	/* prepare vertical filter coefficients */ +	if (ratio->vert > MID_RESIZE_VALUE) +		v_filter = &filter_coefs.v_filter_coef_7tap[0]; +	else +		v_filter = &filter_coefs.v_filter_coef_4tap[0]; + +	resizer_set_filters(res, h_filter, v_filter); +} + +/* + * resizer_set_dst_size - Setup the output height and width + * @res: Device context. + * @width: Output width. + * @height: Output height. + * + * Width : + *  The value must be EVEN. + * + * Height: + *  The number of bytes written to SDRAM must be + *  a multiple of 16-bytes if the vertical resizing factor + *  is greater than 1x (upsizing) + */ +static void resizer_set_output_size(struct isp_res_device *res, +				    u32 width, u32 height) +{ +	struct isp_device *isp = to_isp_device(res); +	u32 rgval = 0; + +	dev_dbg(isp->dev, "Output size[w/h]: %dx%d\n", width, height); +	rgval  = (width << ISPRSZ_OUT_SIZE_HORZ_SHIFT) +		 & ISPRSZ_OUT_SIZE_HORZ_MASK; +	rgval |= (height << ISPRSZ_OUT_SIZE_VERT_SHIFT) +		 & ISPRSZ_OUT_SIZE_VERT_MASK; +	isp_reg_writel(isp, rgval, OMAP3_ISP_IOMEM_RESZ, ISPRSZ_OUT_SIZE); +} + +/* + * resizer_set_output_offset - Setup memory offset for the output lines. + * @res: Device context. + * @offset: Memory offset. + * + * The 5 LSBs are forced to be zeros by the hardware to align on a 32-byte + * boundary; the 5 LSBs are read-only. For optimal use of SDRAM bandwidth, + * the SDRAM line offset must be set on a 256-byte boundary + */ +static void resizer_set_output_offset(struct isp_res_device *res, u32 offset) +{ +	struct isp_device *isp = to_isp_device(res); + +	isp_reg_writel(isp, offset, OMAP3_ISP_IOMEM_RESZ, ISPRSZ_SDR_OUTOFF); +} + +/* + * resizer_set_start - Setup vertical and horizontal start position + * @res: Device context. + * @left: Horizontal start position. + * @top: Vertical start position. + * + * Vertical start line: + *  This field makes sense only when the resizer obtains its input + *  from the preview engine/CCDC + * + * Horizontal start pixel: + *  Pixels are coded on 16 bits for YUV and 8 bits for color separate data. + *  When the resizer gets its input from SDRAM, this field must be set + *  to <= 15 for YUV 16-bit data and <= 31 for 8-bit color separate data + */ +static void resizer_set_start(struct isp_res_device *res, u32 left, u32 top) +{ +	struct isp_device *isp = to_isp_device(res); +	u32 rgval = 0; + +	rgval = (left << ISPRSZ_IN_START_HORZ_ST_SHIFT) +		& ISPRSZ_IN_START_HORZ_ST_MASK; +	rgval |= (top << ISPRSZ_IN_START_VERT_ST_SHIFT) +		 & ISPRSZ_IN_START_VERT_ST_MASK; + +	isp_reg_writel(isp, rgval, OMAP3_ISP_IOMEM_RESZ, ISPRSZ_IN_START); +} + +/* + * resizer_set_input_size - Setup the input size + * @res: Device context. + * @width: The range is 0 to 4095 pixels + * @height: The range is 0 to 4095 lines + */ +static void resizer_set_input_size(struct isp_res_device *res, +				   u32 width, u32 height) +{ +	struct isp_device *isp = to_isp_device(res); +	u32 rgval = 0; + +	dev_dbg(isp->dev, "Input size[w/h]: %dx%d\n", width, height); + +	rgval = (width << ISPRSZ_IN_SIZE_HORZ_SHIFT) +		& ISPRSZ_IN_SIZE_HORZ_MASK; +	rgval |= (height << ISPRSZ_IN_SIZE_VERT_SHIFT) +		 & ISPRSZ_IN_SIZE_VERT_MASK; + +	isp_reg_writel(isp, rgval, OMAP3_ISP_IOMEM_RESZ, ISPRSZ_IN_SIZE); +} + +/* + * resizer_set_src_offs - Setup the memory offset for the input lines + * @res: Device context. + * @offset: Memory offset. + * + * The 5 LSBs are forced to be zeros by the hardware to align on a 32-byte + * boundary; the 5 LSBs are read-only. This field must be programmed to be + * 0x0 if the resizer input is from preview engine/CCDC. + */ +static void resizer_set_input_offset(struct isp_res_device *res, u32 offset) +{ +	struct isp_device *isp = to_isp_device(res); + +	isp_reg_writel(isp, offset, OMAP3_ISP_IOMEM_RESZ, ISPRSZ_SDR_INOFF); +} + +/* + * resizer_set_intype - Input type select + * @res: Device context. + * @type: Pixel format type. + */ +static void resizer_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_set(isp, OMAP3_ISP_IOMEM_RESZ, ISPRSZ_CNT, +			    ISPRSZ_CNT_INPTYP); +	else +		isp_reg_clr(isp, OMAP3_ISP_IOMEM_RESZ, ISPRSZ_CNT, +			    ISPRSZ_CNT_INPTYP); +} + +/* + * __resizer_set_inaddr - Helper function for set input address + * @res : pointer to resizer private data structure + * @addr: input address + * return none + */ +static void __resizer_set_inaddr(struct isp_res_device *res, u32 addr) +{ +	struct isp_device *isp = to_isp_device(res); + +	isp_reg_writel(isp, addr, OMAP3_ISP_IOMEM_RESZ, ISPRSZ_SDR_INADD); +} + +/* + * The data rate at the horizontal resizer output must not exceed half the + * functional clock or 100 MP/s, whichever is lower. According to the TRM + * there's no similar requirement for the vertical resizer output. However + * experience showed that vertical upscaling by 4 leads to SBL overflows (with + * data rates at the resizer output exceeding 300 MP/s). Limiting the resizer + * output data rate to the functional clock or 200 MP/s, whichever is lower, + * seems to get rid of SBL overflows. + * + * The maximum data rate at the output of the horizontal resizer can thus be + * computed with + * + * max intermediate rate <= L3 clock * input height / output height + * max intermediate rate <= L3 clock / 2 + * + * The maximum data rate at the resizer input is then + * + * max input rate <= max intermediate rate * input width / output width + * + * where the input width and height are the resizer input crop rectangle size. + * The TRM doesn't clearly explain if that's a maximum instant data rate or a + * maximum average data rate. + */ +void omap3isp_resizer_max_rate(struct isp_res_device *res, +			       unsigned int *max_rate) +{ +	struct isp_pipeline *pipe = to_isp_pipeline(&res->subdev.entity); +	const struct v4l2_mbus_framefmt *ofmt = &res->formats[RESZ_PAD_SOURCE]; +	unsigned long limit = min(pipe->l3_ick, 200000000UL); +	unsigned long clock; + +	clock = div_u64((u64)limit * res->crop.active.height, ofmt->height); +	clock = min(clock, limit / 2); +	*max_rate = div_u64((u64)clock * res->crop.active.width, ofmt->width); +} + +/* + * When the resizer processes images from memory, the driver must slow down read + * requests on the input to at least comply with the internal data rate + * requirements. If the application real-time requirements can cope with slower + * processing, the resizer can be slowed down even more to put less pressure on + * the overall system. + * + * When the resizer processes images on the fly (either from the CCDC or the + * preview module), the same data rate requirements apply but they can't be + * enforced at the resizer level. The image input module (sensor, CCP2 or + * preview module) must not provide image data faster than the resizer can + * process. + * + * For live image pipelines, the data rate is set by the frame format, size and + * rate. The sensor output frame rate must not exceed the maximum resizer data + * rate. + * + * The resizer slows down read requests by inserting wait cycles in the SBL + * requests. The maximum number of 256-byte requests per second can be computed + * as (the data rate is multiplied by 2 to convert from pixels per second to + * bytes per second) + * + * request per second = data rate * 2 / 256 + * cycles per request = cycles per second / requests per second + * + * The number of cycles per second is controlled by the L3 clock, leading to + * + * cycles per request = L3 frequency / 2 * 256 / data rate + */ +static void resizer_adjust_bandwidth(struct isp_res_device *res) +{ +	struct isp_pipeline *pipe = to_isp_pipeline(&res->subdev.entity); +	struct isp_device *isp = to_isp_device(res); +	unsigned long l3_ick = pipe->l3_ick; +	struct v4l2_fract *timeperframe; +	unsigned int cycles_per_frame; +	unsigned int requests_per_frame; +	unsigned int cycles_per_request; +	unsigned int granularity; +	unsigned int minimum; +	unsigned int maximum; +	unsigned int value; + +	if (res->input != RESIZER_INPUT_MEMORY) { +		isp_reg_clr(isp, OMAP3_ISP_IOMEM_SBL, ISPSBL_SDR_REQ_EXP, +			    ISPSBL_SDR_REQ_RSZ_EXP_MASK); +		return; +	} + +	switch (isp->revision) { +	case ISP_REVISION_1_0: +	case ISP_REVISION_2_0: +	default: +		granularity = 1024; +		break; + +	case ISP_REVISION_15_0: +		granularity = 32; +		break; +	} + +	/* Compute the minimum number of cycles per request, 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. +	 */ +	cycles_per_request = div_u64((u64)l3_ick / 2 * 256 + pipe->max_rate - 1, +				     pipe->max_rate); +	minimum = DIV_ROUND_UP(cycles_per_request, granularity); + +	/* Compute the maximum number of cycles per request, based on the +	 * requested frame rate. This is a soft upper bound to achieve a frame +	 * rate equal or higher than the requested value, so round the value +	 * down. +	 */ +	timeperframe = &pipe->max_timeperframe; + +	requests_per_frame = DIV_ROUND_UP(res->crop.active.width * 2, 256) +			   * res->crop.active.height; +	cycles_per_frame = div_u64((u64)l3_ick * timeperframe->numerator, +				   timeperframe->denominator); +	cycles_per_request = cycles_per_frame / requests_per_frame; + +	maximum = cycles_per_request / granularity; + +	value = max(minimum, maximum); + +	dev_dbg(isp->dev, "%s: cycles per request = %u\n", __func__, value); +	isp_reg_clr_set(isp, OMAP3_ISP_IOMEM_SBL, ISPSBL_SDR_REQ_EXP, +			ISPSBL_SDR_REQ_RSZ_EXP_MASK, +			value << ISPSBL_SDR_REQ_RSZ_EXP_SHIFT); +} + +/* + * omap3isp_resizer_busy - Checks if ISP resizer is busy. + * + * Returns busy field from ISPRSZ_PCR register. + */ +int omap3isp_resizer_busy(struct isp_res_device *res) +{ +	struct isp_device *isp = to_isp_device(res); + +	return isp_reg_readl(isp, OMAP3_ISP_IOMEM_RESZ, ISPRSZ_PCR) & +			     ISPRSZ_PCR_BUSY; +} + +/* + * resizer_set_inaddr - Sets the memory address of the input frame. + * @addr: 32bit memory address aligned on 32byte boundary. + */ +static void resizer_set_inaddr(struct isp_res_device *res, u32 addr) +{ +	res->addr_base = addr; + +	/* This will handle crop settings in stream off state */ +	if (res->crop_offset) +		addr += res->crop_offset & ~0x1f; + +	__resizer_set_inaddr(res, addr); +} + +/* + * Configures the memory address to which the output frame is written. + * @addr: 32bit memory address aligned on 32byte boundary. + * Note: For SBL efficiency reasons the address should be on a 256-byte + * boundary. + */ +static void resizer_set_outaddr(struct isp_res_device *res, u32 addr) +{ +	struct isp_device *isp = to_isp_device(res); + +	/* +	 * Set output address. This needs to be in its own function +	 * because it changes often. +	 */ +	isp_reg_writel(isp, addr << ISPRSZ_SDR_OUTADD_ADDR_SHIFT, +		       OMAP3_ISP_IOMEM_RESZ, ISPRSZ_SDR_OUTADD); +} + +/* + * resizer_print_status - Prints the values of the resizer module registers. + */ +#define RSZ_PRINT_REGISTER(isp, name)\ +	dev_dbg(isp->dev, "###RSZ " #name "=0x%08x\n", \ +		isp_reg_readl(isp, OMAP3_ISP_IOMEM_RESZ, ISPRSZ_##name)) + +static void resizer_print_status(struct isp_res_device *res) +{ +	struct isp_device *isp = to_isp_device(res); + +	dev_dbg(isp->dev, "-------------Resizer Register dump----------\n"); + +	RSZ_PRINT_REGISTER(isp, PCR); +	RSZ_PRINT_REGISTER(isp, CNT); +	RSZ_PRINT_REGISTER(isp, OUT_SIZE); +	RSZ_PRINT_REGISTER(isp, IN_START); +	RSZ_PRINT_REGISTER(isp, IN_SIZE); +	RSZ_PRINT_REGISTER(isp, SDR_INADD); +	RSZ_PRINT_REGISTER(isp, SDR_INOFF); +	RSZ_PRINT_REGISTER(isp, SDR_OUTADD); +	RSZ_PRINT_REGISTER(isp, SDR_OUTOFF); +	RSZ_PRINT_REGISTER(isp, YENH); + +	dev_dbg(isp->dev, "--------------------------------------------\n"); +} + +/* + * resizer_calc_ratios - Helper function for calculating resizer ratios + * @res: pointer to resizer private data structure + * @input: input frame size + * @output: output frame size + * @ratio : return calculated ratios + * return none + * + * The resizer uses a polyphase sample rate converter. The upsampling filter + * has a fixed number of phases that depend on the resizing ratio. As the ratio + * computation depends on the number of phases, we need to compute a first + * approximation and then refine it. + * + * The input/output/ratio relationship is given by the OMAP34xx TRM: + * + * - 8-phase, 4-tap mode (RSZ = 64 ~ 512) + *	iw = (32 * sph + (ow - 1) * hrsz + 16) >> 8 + 7 + *	ih = (32 * spv + (oh - 1) * vrsz + 16) >> 8 + 4 + * - 4-phase, 7-tap mode (RSZ = 513 ~ 1024) + *	iw = (64 * sph + (ow - 1) * hrsz + 32) >> 8 + 7 + *	ih = (64 * spv + (oh - 1) * vrsz + 32) >> 8 + 7 + * + * iw and ih are the input width and height after cropping. Those equations need + * to be satisfied exactly for the resizer to work correctly. + * + * The equations can't be easily reverted, as the >> 8 operation is not linear. + * In addition, not all input sizes can be achieved for a given output size. To + * get the highest input size lower than or equal to the requested input size, + * we need to compute the highest resizing ratio that satisfies the following + * inequality (taking the 4-tap mode width equation as an example) + * + *	iw >= (32 * sph + (ow - 1) * hrsz + 16) >> 8 - 7 + * + * (where iw is the requested input width) which can be rewritten as + * + *	  iw - 7            >= (32 * sph + (ow - 1) * hrsz + 16) >> 8 + *	 (iw - 7) << 8      >=  32 * sph + (ow - 1) * hrsz + 16 - b + *	((iw - 7) << 8) + b >=  32 * sph + (ow - 1) * hrsz + 16 + * + * where b is the value of the 8 least significant bits of the right hand side + * expression of the last inequality. The highest resizing ratio value will be + * achieved when b is equal to its maximum value of 255. That resizing ratio + * value will still satisfy the original inequality, as b will disappear when + * the expression will be shifted right by 8. + * + * The reverted equations thus become + * + * - 8-phase, 4-tap mode + *	hrsz = ((iw - 7) * 256 + 255 - 16 - 32 * sph) / (ow - 1) + *	vrsz = ((ih - 4) * 256 + 255 - 16 - 32 * spv) / (oh - 1) + * - 4-phase, 7-tap mode + *	hrsz = ((iw - 7) * 256 + 255 - 32 - 64 * sph) / (ow - 1) + *	vrsz = ((ih - 7) * 256 + 255 - 32 - 64 * spv) / (oh - 1) + * + * The ratios are integer values, and are rounded down to ensure that the + * cropped input size is not bigger than the uncropped input size. + * + * As the number of phases/taps, used to select the correct equations to compute + * the ratio, depends on the ratio, we start with the 4-tap mode equations to + * compute an approximation of the ratio, and switch to the 7-tap mode equations + * if the approximation is higher than the ratio threshold. + * + * As the 7-tap mode equations will return a ratio smaller than or equal to the + * 4-tap mode equations, the resulting ratio could become lower than or equal to + * the ratio threshold. This 'equations loop' isn't an issue as long as the + * correct equations are used to compute the final input size. Starting with the + * 4-tap mode equations ensure that, in case of values resulting in a 'ratio + * loop', the smallest of the ratio values will be used, never exceeding the + * requested input size. + * + * We first clamp the output size according to the hardware capability to avoid + * auto-cropping the input more than required to satisfy the TRM equations. The + * minimum output size is achieved with a scaling factor of 1024. It is thus + * computed using the 7-tap equations. + * + *	min ow = ((iw - 7) * 256 - 32 - 64 * sph) / 1024 + 1 + *	min oh = ((ih - 7) * 256 - 32 - 64 * spv) / 1024 + 1 + * + * Similarly, the maximum output size is achieved with a scaling factor of 64 + * and computed using the 4-tap equations. + * + *	max ow = ((iw - 7) * 256 + 255 - 16 - 32 * sph) / 64 + 1 + *	max oh = ((ih - 4) * 256 + 255 - 16 - 32 * spv) / 64 + 1 + * + * The additional +255 term compensates for the round down operation performed + * by the TRM equations when shifting the value right by 8 bits. + * + * We then compute and clamp the ratios (x1/4 ~ x4). Clamping the output size to + * the maximum value guarantees that the ratio value will never be smaller than + * the minimum, but it could still slightly exceed the maximum. Clamping the + * ratio will thus result in a resizing factor slightly larger than the + * requested value. + * + * To accommodate that, and make sure the TRM equations are satisfied exactly, we + * compute the input crop rectangle as the last step. + * + * As if the situation wasn't complex enough, the maximum output width depends + * on the vertical resizing ratio.  Fortunately, the output height doesn't + * depend on the horizontal resizing ratio. We can then start by computing the + * output height and the vertical ratio, and then move to computing the output + * width and the horizontal ratio. + */ +static void resizer_calc_ratios(struct isp_res_device *res, +				struct v4l2_rect *input, +				struct v4l2_mbus_framefmt *output, +				struct resizer_ratio *ratio) +{ +	struct isp_device *isp = to_isp_device(res); +	const unsigned int spv = DEFAULT_PHASE; +	const unsigned int sph = DEFAULT_PHASE; +	unsigned int upscaled_width; +	unsigned int upscaled_height; +	unsigned int min_width; +	unsigned int min_height; +	unsigned int max_width; +	unsigned int max_height; +	unsigned int width_alignment; +	unsigned int width; +	unsigned int height; + +	/* +	 * Clamp the output height based on the hardware capabilities and +	 * compute the vertical resizing ratio. +	 */ +	min_height = ((input->height - 7) * 256 - 32 - 64 * spv) / 1024 + 1; +	min_height = max_t(unsigned int, min_height, MIN_OUT_HEIGHT); +	max_height = ((input->height - 4) * 256 + 255 - 16 - 32 * spv) / 64 + 1; +	max_height = min_t(unsigned int, max_height, MAX_OUT_HEIGHT); +	output->height = clamp(output->height, min_height, max_height); + +	ratio->vert = ((input->height - 4) * 256 + 255 - 16 - 32 * spv) +		    / (output->height - 1); +	if (ratio->vert > MID_RESIZE_VALUE) +		ratio->vert = ((input->height - 7) * 256 + 255 - 32 - 64 * spv) +			    / (output->height - 1); +	ratio->vert = clamp_t(unsigned int, ratio->vert, +			      MIN_RESIZE_VALUE, MAX_RESIZE_VALUE); + +	if (ratio->vert <= MID_RESIZE_VALUE) { +		upscaled_height = (output->height - 1) * ratio->vert +				+ 32 * spv + 16; +		height = (upscaled_height >> 8) + 4; +	} else { +		upscaled_height = (output->height - 1) * ratio->vert +				+ 64 * spv + 32; +		height = (upscaled_height >> 8) + 7; +	} + +	/* +	 * Compute the minimum and maximum output widths based on the hardware +	 * capabilities. The maximum depends on the vertical resizing ratio. +	 */ +	min_width = ((input->width - 7) * 256 - 32 - 64 * sph) / 1024 + 1; +	min_width = max_t(unsigned int, min_width, MIN_OUT_WIDTH); + +	if (ratio->vert <= MID_RESIZE_VALUE) { +		switch (isp->revision) { +		case ISP_REVISION_1_0: +			max_width = MAX_4TAP_OUT_WIDTH_ES1; +			break; + +		case ISP_REVISION_2_0: +		default: +			max_width = MAX_4TAP_OUT_WIDTH_ES2; +			break; + +		case ISP_REVISION_15_0: +			max_width = MAX_4TAP_OUT_WIDTH_3630; +			break; +		} +	} else { +		switch (isp->revision) { +		case ISP_REVISION_1_0: +			max_width = MAX_7TAP_OUT_WIDTH_ES1; +			break; + +		case ISP_REVISION_2_0: +		default: +			max_width = MAX_7TAP_OUT_WIDTH_ES2; +			break; + +		case ISP_REVISION_15_0: +			max_width = MAX_7TAP_OUT_WIDTH_3630; +			break; +		} +	} +	max_width = min(((input->width - 7) * 256 + 255 - 16 - 32 * sph) / 64 +			+ 1, max_width); + +	/* +	 * The output width must be even, and must be a multiple of 16 bytes +	 * when upscaling vertically. Clamp the output width to the valid range. +	 * Take the alignment into account (the maximum width in 7-tap mode on +	 * ES2 isn't a multiple of 8) and align the result up to make sure it +	 * won't be smaller than the minimum. +	 */ +	width_alignment = ratio->vert < 256 ? 8 : 2; +	output->width = clamp(output->width, min_width, +			      max_width & ~(width_alignment - 1)); +	output->width = ALIGN(output->width, width_alignment); + +	ratio->horz = ((input->width - 7) * 256 + 255 - 16 - 32 * sph) +		    / (output->width - 1); +	if (ratio->horz > MID_RESIZE_VALUE) +		ratio->horz = ((input->width - 7) * 256 + 255 - 32 - 64 * sph) +			    / (output->width - 1); +	ratio->horz = clamp_t(unsigned int, ratio->horz, +			      MIN_RESIZE_VALUE, MAX_RESIZE_VALUE); + +	if (ratio->horz <= MID_RESIZE_VALUE) { +		upscaled_width = (output->width - 1) * ratio->horz +			       + 32 * sph + 16; +		width = (upscaled_width >> 8) + 7; +	} else { +		upscaled_width = (output->width - 1) * ratio->horz +			       + 64 * sph + 32; +		width = (upscaled_width >> 8) + 7; +	} + +	/* Center the new crop rectangle. */ +	input->left += (input->width - width) / 2; +	input->top += (input->height - height) / 2; +	input->width = width; +	input->height = height; +} + +/* + * resizer_set_crop_params - Setup hardware with cropping parameters + * @res : resizer private structure + * @input : format on sink pad + * @output : format on source pad + * return none + */ +static void resizer_set_crop_params(struct isp_res_device *res, +				    const struct v4l2_mbus_framefmt *input, +				    const struct v4l2_mbus_framefmt *output) +{ +	resizer_set_ratio(res, &res->ratio); + +	/* Set chrominance horizontal algorithm */ +	if (res->ratio.horz >= RESIZE_DIVISOR) +		resizer_set_bilinear(res, RSZ_THE_SAME); +	else +		resizer_set_bilinear(res, RSZ_BILINEAR); + +	resizer_adjust_bandwidth(res); + +	if (res->input == RESIZER_INPUT_MEMORY) { +		/* Calculate additional offset for crop */ +		res->crop_offset = (res->crop.active.top * input->width + +				    res->crop.active.left) * 2; +		/* +		 * Write lowest 4 bits of horizontal pixel offset (in pixels), +		 * vertical start must be 0. +		 */ +		resizer_set_start(res, (res->crop_offset / 2) & 0xf, 0); + +		/* +		 * Set start (read) address for cropping, in bytes. +		 * Lowest 5 bits must be zero. +		 */ +		__resizer_set_inaddr(res, +				res->addr_base + (res->crop_offset & ~0x1f)); +	} else { +		/* +		 * Set vertical start line and horizontal starting pixel. +		 * If the input is from CCDC/PREV, horizontal start field is +		 * in bytes (twice number of pixels). +		 */ +		resizer_set_start(res, res->crop.active.left * 2, +				  res->crop.active.top); +		/* Input address and offset must be 0 for preview/ccdc input */ +		__resizer_set_inaddr(res, 0); +		resizer_set_input_offset(res, 0); +	} + +	/* Set the input size */ +	resizer_set_input_size(res, res->crop.active.width, +			       res->crop.active.height); +} + +static void resizer_configure(struct isp_res_device *res) +{ +	struct v4l2_mbus_framefmt *informat, *outformat; +	struct resizer_luma_yenh luma = {0, 0, 0, 0}; + +	resizer_set_source(res, res->input); + +	informat = &res->formats[RESZ_PAD_SINK]; +	outformat = &res->formats[RESZ_PAD_SOURCE]; + +	/* RESZ_PAD_SINK */ +	if (res->input == RESIZER_INPUT_VP) +		resizer_set_input_offset(res, 0); +	else +		resizer_set_input_offset(res, ALIGN(informat->width, 0x10) * 2); + +	/* YUV422 interleaved, default phase, no luma enhancement */ +	resizer_set_intype(res, RSZ_YUV422); +	resizer_set_ycpos(res, informat->code); +	resizer_set_phase(res, DEFAULT_PHASE, DEFAULT_PHASE); +	resizer_set_luma(res, &luma); + +	/* RESZ_PAD_SOURCE */ +	resizer_set_output_offset(res, ALIGN(outformat->width * 2, 32)); +	resizer_set_output_size(res, outformat->width, outformat->height); + +	resizer_set_crop_params(res, informat, outformat); +} + +/* ----------------------------------------------------------------------------- + * Interrupt handling + */ + +static void resizer_enable_oneshot(struct isp_res_device *res) +{ +	struct isp_device *isp = to_isp_device(res); + +	isp_reg_set(isp, OMAP3_ISP_IOMEM_RESZ, ISPRSZ_PCR, +		    ISPRSZ_PCR_ENABLE | ISPRSZ_PCR_ONESHOT); +} + +void omap3isp_resizer_isr_frame_sync(struct isp_res_device *res) +{ +	/* +	 * If ISP_VIDEO_DMAQUEUE_QUEUED is set, DMA queue had an underrun +	 * condition, the module was paused and now we have a buffer queued +	 * on the output again. Restart the pipeline if running in continuous +	 * mode. +	 */ +	if (res->state == ISP_PIPELINE_STREAM_CONTINUOUS && +	    res->video_out.dmaqueue_flags & ISP_VIDEO_DMAQUEUE_QUEUED) { +		resizer_enable_oneshot(res); +		isp_video_dmaqueue_flags_clr(&res->video_out); +	} +} + +static void resizer_isr_buffer(struct isp_res_device *res) +{ +	struct isp_pipeline *pipe = to_isp_pipeline(&res->subdev.entity); +	struct isp_buffer *buffer; +	int restart = 0; + +	if (res->state == ISP_PIPELINE_STREAM_STOPPED) +		return; + +	/* Complete the output buffer and, if reading from memory, the input +	 * buffer. +	 */ +	buffer = omap3isp_video_buffer_next(&res->video_out); +	if (buffer != NULL) { +		resizer_set_outaddr(res, buffer->dma); +		restart = 1; +	} + +	pipe->state |= ISP_PIPELINE_IDLE_OUTPUT; + +	if (res->input == RESIZER_INPUT_MEMORY) { +		buffer = omap3isp_video_buffer_next(&res->video_in); +		if (buffer != NULL) +			resizer_set_inaddr(res, buffer->dma); +		pipe->state |= ISP_PIPELINE_IDLE_INPUT; +	} + +	if (res->state == ISP_PIPELINE_STREAM_SINGLESHOT) { +		if (isp_pipeline_ready(pipe)) +			omap3isp_pipeline_set_stream(pipe, +						ISP_PIPELINE_STREAM_SINGLESHOT); +	} else { +		/* If an underrun occurs, the video queue operation handler will +		 * restart the resizer. Otherwise restart it immediately. +		 */ +		if (restart) +			resizer_enable_oneshot(res); +	} +} + +/* + * omap3isp_resizer_isr - ISP resizer interrupt handler + * + * Manage the resizer video buffers and configure shadowed and busy-locked + * registers. + */ +void omap3isp_resizer_isr(struct isp_res_device *res) +{ +	struct v4l2_mbus_framefmt *informat, *outformat; + +	if (omap3isp_module_sync_is_stopping(&res->wait, &res->stopping)) +		return; + +	if (res->applycrop) { +		outformat = __resizer_get_format(res, NULL, RESZ_PAD_SOURCE, +					      V4L2_SUBDEV_FORMAT_ACTIVE); +		informat = __resizer_get_format(res, NULL, RESZ_PAD_SINK, +					      V4L2_SUBDEV_FORMAT_ACTIVE); +		resizer_set_crop_params(res, informat, outformat); +		res->applycrop = 0; +	} + +	resizer_isr_buffer(res); +} + +/* ----------------------------------------------------------------------------- + * ISP video operations + */ + +static int resizer_video_queue(struct isp_video *video, +			       struct isp_buffer *buffer) +{ +	struct isp_res_device *res = &video->isp->isp_res; + +	if (video->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) +		resizer_set_inaddr(res, buffer->dma); + +	/* +	 * We now have a buffer queued on the output. Despite what the +	 * TRM says, the resizer can't be restarted immediately. +	 * Enabling it in one shot mode in the middle of a frame (or at +	 * least asynchronously to the frame) results in the output +	 * being shifted randomly left/right and up/down, as if the +	 * hardware didn't synchronize itself to the beginning of the +	 * frame correctly. +	 * +	 * Restart the resizer on the next sync interrupt if running in +	 * continuous mode or when starting the stream. +	 */ +	if (video->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) +		resizer_set_outaddr(res, buffer->dma); + +	return 0; +} + +static const struct isp_video_operations resizer_video_ops = { +	.queue = resizer_video_queue, +}; + +/* ----------------------------------------------------------------------------- + * V4L2 subdev operations + */ + +/* + * resizer_set_stream - Enable/Disable streaming on resizer subdev + * @sd: ISP resizer V4L2 subdev + * @enable: 1 == Enable, 0 == Disable + * + * The resizer hardware can't be enabled without a memory buffer to write to. + * As the s_stream operation is called in response to a STREAMON call without + * any buffer queued yet, just update the state field and return immediately. + * The resizer will be enabled in resizer_video_queue(). + */ +static int resizer_set_stream(struct v4l2_subdev *sd, int enable) +{ +	struct isp_res_device *res = v4l2_get_subdevdata(sd); +	struct isp_video *video_out = &res->video_out; +	struct isp_device *isp = to_isp_device(res); +	struct device *dev = to_device(res); + +	if (res->state == ISP_PIPELINE_STREAM_STOPPED) { +		if (enable == ISP_PIPELINE_STREAM_STOPPED) +			return 0; + +		omap3isp_subclk_enable(isp, OMAP3_ISP_SUBCLK_RESIZER); +		resizer_configure(res); +		resizer_print_status(res); +	} + +	switch (enable) { +	case ISP_PIPELINE_STREAM_CONTINUOUS: +		omap3isp_sbl_enable(isp, OMAP3_ISP_SBL_RESIZER_WRITE); +		if (video_out->dmaqueue_flags & ISP_VIDEO_DMAQUEUE_QUEUED) { +			resizer_enable_oneshot(res); +			isp_video_dmaqueue_flags_clr(video_out); +		} +		break; + +	case ISP_PIPELINE_STREAM_SINGLESHOT: +		if (res->input == RESIZER_INPUT_MEMORY) +			omap3isp_sbl_enable(isp, OMAP3_ISP_SBL_RESIZER_READ); +		omap3isp_sbl_enable(isp, OMAP3_ISP_SBL_RESIZER_WRITE); + +		resizer_enable_oneshot(res); +		break; + +	case ISP_PIPELINE_STREAM_STOPPED: +		if (omap3isp_module_sync_idle(&sd->entity, &res->wait, +					      &res->stopping)) +			dev_dbg(dev, "%s: module stop timeout.\n", sd->name); +		omap3isp_sbl_disable(isp, OMAP3_ISP_SBL_RESIZER_READ | +				OMAP3_ISP_SBL_RESIZER_WRITE); +		omap3isp_subclk_disable(isp, OMAP3_ISP_SUBCLK_RESIZER); +		isp_video_dmaqueue_flags_clr(video_out); +		break; +	} + +	res->state = enable; +	return 0; +} + +/* + * resizer_try_crop - mangles crop parameters. + */ +static void resizer_try_crop(const struct v4l2_mbus_framefmt *sink, +			     const struct v4l2_mbus_framefmt *source, +			     struct v4l2_rect *crop) +{ +	const unsigned int spv = DEFAULT_PHASE; +	const unsigned int sph = DEFAULT_PHASE; + +	/* Crop rectangle is constrained by the output size so that zoom ratio +	 * cannot exceed +/-4.0. +	 */ +	unsigned int min_width = +		((32 * sph + (source->width - 1) * 64 + 16) >> 8) + 7; +	unsigned int min_height = +		((32 * spv + (source->height - 1) * 64 + 16) >> 8) + 4; +	unsigned int max_width = +		((64 * sph + (source->width - 1) * 1024 + 32) >> 8) + 7; +	unsigned int max_height = +		((64 * spv + (source->height - 1) * 1024 + 32) >> 8) + 7; + +	crop->width = clamp_t(u32, crop->width, min_width, max_width); +	crop->height = clamp_t(u32, crop->height, min_height, max_height); + +	/* Crop can not go beyond of the input rectangle */ +	crop->left = clamp_t(u32, crop->left, 0, sink->width - MIN_IN_WIDTH); +	crop->width = clamp_t(u32, crop->width, MIN_IN_WIDTH, +			      sink->width - crop->left); +	crop->top = clamp_t(u32, crop->top, 0, sink->height - MIN_IN_HEIGHT); +	crop->height = clamp_t(u32, crop->height, MIN_IN_HEIGHT, +			       sink->height - crop->top); +} + +/* + * resizer_get_selection - Retrieve a selection rectangle on a pad + * @sd: ISP resizer V4L2 subdevice + * @fh: V4L2 subdev file handle + * @sel: Selection rectangle + * + * The only supported rectangles are the crop rectangles on the sink pad. + * + * Return 0 on success or a negative error code otherwise. + */ +static int resizer_get_selection(struct v4l2_subdev *sd, +				 struct v4l2_subdev_fh *fh, +				 struct v4l2_subdev_selection *sel) +{ +	struct isp_res_device *res = v4l2_get_subdevdata(sd); +	struct v4l2_mbus_framefmt *format_source; +	struct v4l2_mbus_framefmt *format_sink; +	struct resizer_ratio ratio; + +	if (sel->pad != RESZ_PAD_SINK) +		return -EINVAL; + +	format_sink = __resizer_get_format(res, fh, RESZ_PAD_SINK, +					   sel->which); +	format_source = __resizer_get_format(res, fh, RESZ_PAD_SOURCE, +					     sel->which); + +	switch (sel->target) { +	case V4L2_SEL_TGT_CROP_BOUNDS: +		sel->r.left = 0; +		sel->r.top = 0; +		sel->r.width = INT_MAX; +		sel->r.height = INT_MAX; + +		resizer_try_crop(format_sink, format_source, &sel->r); +		resizer_calc_ratios(res, &sel->r, format_source, &ratio); +		break; + +	case V4L2_SEL_TGT_CROP: +		sel->r = *__resizer_get_crop(res, fh, sel->which); +		resizer_calc_ratios(res, &sel->r, format_source, &ratio); +		break; + +	default: +		return -EINVAL; +	} + +	return 0; +} + +/* + * resizer_set_selection - Set a selection rectangle on a pad + * @sd: ISP resizer V4L2 subdevice + * @fh: V4L2 subdev file handle + * @sel: Selection rectangle + * + * The only supported rectangle is the actual crop rectangle on the sink pad. + * + * FIXME: This function currently behaves as if the KEEP_CONFIG selection flag + * was always set. + * + * Return 0 on success or a negative error code otherwise. + */ +static int resizer_set_selection(struct v4l2_subdev *sd, +				 struct v4l2_subdev_fh *fh, +				 struct v4l2_subdev_selection *sel) +{ +	struct isp_res_device *res = v4l2_get_subdevdata(sd); +	struct isp_device *isp = to_isp_device(res); +	struct v4l2_mbus_framefmt *format_sink, *format_source; +	struct resizer_ratio ratio; + +	if (sel->target != V4L2_SEL_TGT_CROP || +	    sel->pad != RESZ_PAD_SINK) +		return -EINVAL; + +	format_sink = __resizer_get_format(res, fh, RESZ_PAD_SINK, +					   sel->which); +	format_source = __resizer_get_format(res, fh, RESZ_PAD_SOURCE, +					     sel->which); + +	dev_dbg(isp->dev, "%s: L=%d,T=%d,W=%d,H=%d,which=%d\n", __func__, +		sel->r.left, sel->r.top, sel->r.width, sel->r.height, +		sel->which); + +	dev_dbg(isp->dev, "%s: input=%dx%d, output=%dx%d\n", __func__, +		format_sink->width, format_sink->height, +		format_source->width, format_source->height); + +	/* Clamp the crop rectangle to the bounds, and then mangle it further to +	 * fulfill the TRM equations. Store the clamped but otherwise unmangled +	 * rectangle to avoid cropping the input multiple times: when an +	 * application sets the output format, the current crop rectangle is +	 * mangled during crop rectangle computation, which would lead to a new, +	 * smaller input crop rectangle every time the output size is set if we +	 * stored the mangled rectangle. +	 */ +	resizer_try_crop(format_sink, format_source, &sel->r); +	*__resizer_get_crop(res, fh, sel->which) = sel->r; +	resizer_calc_ratios(res, &sel->r, format_source, &ratio); + +	if (sel->which == V4L2_SUBDEV_FORMAT_TRY) +		return 0; + +	res->ratio = ratio; +	res->crop.active = sel->r; + +	/* +	 * set_selection can be called while streaming is on. In this case the +	 * crop values will be set in the next IRQ. +	 */ +	if (res->state != ISP_PIPELINE_STREAM_STOPPED) +		res->applycrop = 1; + +	return 0; +} + +/* resizer pixel formats */ +static const unsigned int resizer_formats[] = { +	V4L2_MBUS_FMT_UYVY8_1X16, +	V4L2_MBUS_FMT_YUYV8_1X16, +}; + +static unsigned int resizer_max_in_width(struct isp_res_device *res) +{ +	struct isp_device *isp = to_isp_device(res); + +	if (res->input == RESIZER_INPUT_MEMORY) { +		return MAX_IN_WIDTH_MEMORY_MODE; +	} else { +		if (isp->revision == ISP_REVISION_1_0) +			return MAX_IN_WIDTH_ONTHEFLY_MODE_ES1; +		else +			return MAX_IN_WIDTH_ONTHEFLY_MODE_ES2; +	} +} + +/* + * resizer_try_format - Handle try format by pad subdev method + * @res   : ISP resizer device + * @fh    : V4L2 subdev file handle + * @pad   : pad num + * @fmt   : pointer to v4l2 format structure + * @which : wanted subdev format + */ +static void resizer_try_format(struct isp_res_device *res, +			       struct v4l2_subdev_fh *fh, unsigned int pad, +			       struct v4l2_mbus_framefmt *fmt, +			       enum v4l2_subdev_format_whence which) +{ +	struct v4l2_mbus_framefmt *format; +	struct resizer_ratio ratio; +	struct v4l2_rect crop; + +	switch (pad) { +	case RESZ_PAD_SINK: +		if (fmt->code != V4L2_MBUS_FMT_YUYV8_1X16 && +		    fmt->code != V4L2_MBUS_FMT_UYVY8_1X16) +			fmt->code = V4L2_MBUS_FMT_YUYV8_1X16; + +		fmt->width = clamp_t(u32, fmt->width, MIN_IN_WIDTH, +				     resizer_max_in_width(res)); +		fmt->height = clamp_t(u32, fmt->height, MIN_IN_HEIGHT, +				      MAX_IN_HEIGHT); +		break; + +	case RESZ_PAD_SOURCE: +		format = __resizer_get_format(res, fh, RESZ_PAD_SINK, which); +		fmt->code = format->code; + +		crop = *__resizer_get_crop(res, fh, which); +		resizer_calc_ratios(res, &crop, fmt, &ratio); +		break; +	} + +	fmt->colorspace = V4L2_COLORSPACE_JPEG; +	fmt->field = V4L2_FIELD_NONE; +} + +/* + * resizer_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 resizer_enum_mbus_code(struct v4l2_subdev *sd, +				  struct v4l2_subdev_fh *fh, +				  struct v4l2_subdev_mbus_code_enum *code) +{ +	struct isp_res_device *res = v4l2_get_subdevdata(sd); +	struct v4l2_mbus_framefmt *format; + +	if (code->pad == RESZ_PAD_SINK) { +		if (code->index >= ARRAY_SIZE(resizer_formats)) +			return -EINVAL; + +		code->code = resizer_formats[code->index]; +	} else { +		if (code->index != 0) +			return -EINVAL; + +		format = __resizer_get_format(res, fh, RESZ_PAD_SINK, +					      V4L2_SUBDEV_FORMAT_TRY); +		code->code = format->code; +	} + +	return 0; +} + +static int resizer_enum_frame_size(struct v4l2_subdev *sd, +				   struct v4l2_subdev_fh *fh, +				   struct v4l2_subdev_frame_size_enum *fse) +{ +	struct isp_res_device *res = 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; +	resizer_try_format(res, 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; +	resizer_try_format(res, fh, fse->pad, &format, V4L2_SUBDEV_FORMAT_TRY); +	fse->max_width = format.width; +	fse->max_height = format.height; + +	return 0; +} + +/* + * resizer_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 resizer_get_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, +			      struct v4l2_subdev_format *fmt) +{ +	struct isp_res_device *res = v4l2_get_subdevdata(sd); +	struct v4l2_mbus_framefmt *format; + +	format = __resizer_get_format(res, fh, fmt->pad, fmt->which); +	if (format == NULL) +		return -EINVAL; + +	fmt->format = *format; +	return 0; +} + +/* + * resizer_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 + * return -EINVAL or zero on success + */ +static int resizer_set_format(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh, +			      struct v4l2_subdev_format *fmt) +{ +	struct isp_res_device *res = v4l2_get_subdevdata(sd); +	struct v4l2_mbus_framefmt *format; +	struct v4l2_rect *crop; + +	format = __resizer_get_format(res, fh, fmt->pad, fmt->which); +	if (format == NULL) +		return -EINVAL; + +	resizer_try_format(res, fh, fmt->pad, &fmt->format, fmt->which); +	*format = fmt->format; + +	if (fmt->pad == RESZ_PAD_SINK) { +		/* reset crop rectangle */ +		crop = __resizer_get_crop(res, fh, fmt->which); +		crop->left = 0; +		crop->top = 0; +		crop->width = fmt->format.width; +		crop->height = fmt->format.height; + +		/* Propagate the format from sink to source */ +		format = __resizer_get_format(res, fh, RESZ_PAD_SOURCE, +					      fmt->which); +		*format = fmt->format; +		resizer_try_format(res, fh, RESZ_PAD_SOURCE, format, +				   fmt->which); +	} + +	if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) { +		/* Compute and store the active crop rectangle and resizer +		 * ratios. format already points to the source pad active +		 * format. +		 */ +		res->crop.active = res->crop.request; +		resizer_calc_ratios(res, &res->crop.active, format, +				       &res->ratio); +	} + +	return 0; +} + +static int resizer_link_validate(struct v4l2_subdev *sd, +				 struct media_link *link, +				 struct v4l2_subdev_format *source_fmt, +				 struct v4l2_subdev_format *sink_fmt) +{ +	struct isp_res_device *res = v4l2_get_subdevdata(sd); +	struct isp_pipeline *pipe = to_isp_pipeline(&sd->entity); + +	omap3isp_resizer_max_rate(res, &pipe->max_rate); + +	return v4l2_subdev_link_validate_default(sd, link, +						 source_fmt, sink_fmt); +} + +/* + * resizer_init_formats - Initialize formats on all pads + * @sd: ISP resizer 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 resizer_init_formats(struct v4l2_subdev *sd, +				struct v4l2_subdev_fh *fh) +{ +	struct v4l2_subdev_format format; + +	memset(&format, 0, sizeof(format)); +	format.pad = RESZ_PAD_SINK; +	format.which = fh ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE; +	format.format.code = V4L2_MBUS_FMT_YUYV8_1X16; +	format.format.width = 4096; +	format.format.height = 4096; +	resizer_set_format(sd, fh, &format); + +	return 0; +} + +/* subdev video operations */ +static const struct v4l2_subdev_video_ops resizer_v4l2_video_ops = { +	.s_stream = resizer_set_stream, +}; + +/* subdev pad operations */ +static const struct v4l2_subdev_pad_ops resizer_v4l2_pad_ops = { +	.enum_mbus_code = resizer_enum_mbus_code, +	.enum_frame_size = resizer_enum_frame_size, +	.get_fmt = resizer_get_format, +	.set_fmt = resizer_set_format, +	.get_selection = resizer_get_selection, +	.set_selection = resizer_set_selection, +	.link_validate = resizer_link_validate, +}; + +/* subdev operations */ +static const struct v4l2_subdev_ops resizer_v4l2_ops = { +	.video = &resizer_v4l2_video_ops, +	.pad = &resizer_v4l2_pad_ops, +}; + +/* subdev internal operations */ +static const struct v4l2_subdev_internal_ops resizer_v4l2_internal_ops = { +	.open = resizer_init_formats, +}; + +/* ----------------------------------------------------------------------------- + * Media entity operations + */ + +/* + * resizer_link_setup - Setup resizer connections. + * @entity : Pointer to media entity structure + * @local  : Pointer to local pad array + * @remote : Pointer to remote pad array + * @flags  : Link flags + * return -EINVAL or zero on success + */ +static int resizer_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_res_device *res = v4l2_get_subdevdata(sd); + +	switch (local->index | media_entity_type(remote->entity)) { +	case RESZ_PAD_SINK | MEDIA_ENT_T_DEVNODE: +		/* read from memory */ +		if (flags & MEDIA_LNK_FL_ENABLED) { +			if (res->input == RESIZER_INPUT_VP) +				return -EBUSY; +			res->input = RESIZER_INPUT_MEMORY; +		} else { +			if (res->input == RESIZER_INPUT_MEMORY) +				res->input = RESIZER_INPUT_NONE; +		} +		break; + +	case RESZ_PAD_SINK | MEDIA_ENT_T_V4L2_SUBDEV: +		/* read from ccdc or previewer */ +		if (flags & MEDIA_LNK_FL_ENABLED) { +			if (res->input == RESIZER_INPUT_MEMORY) +				return -EBUSY; +			res->input = RESIZER_INPUT_VP; +		} else { +			if (res->input == RESIZER_INPUT_VP) +				res->input = RESIZER_INPUT_NONE; +		} +		break; + +	case RESZ_PAD_SOURCE | MEDIA_ENT_T_DEVNODE: +		/* resizer always write to memory */ +		break; + +	default: +		return -EINVAL; +	} + +	return 0; +} + +/* media operations */ +static const struct media_entity_operations resizer_media_ops = { +	.link_setup = resizer_link_setup, +	.link_validate = v4l2_subdev_link_validate, +}; + +void omap3isp_resizer_unregister_entities(struct isp_res_device *res) +{ +	v4l2_device_unregister_subdev(&res->subdev); +	omap3isp_video_unregister(&res->video_in); +	omap3isp_video_unregister(&res->video_out); +} + +int omap3isp_resizer_register_entities(struct isp_res_device *res, +				       struct v4l2_device *vdev) +{ +	int ret; + +	/* Register the subdev and video nodes. */ +	ret = v4l2_device_register_subdev(vdev, &res->subdev); +	if (ret < 0) +		goto error; + +	ret = omap3isp_video_register(&res->video_in, vdev); +	if (ret < 0) +		goto error; + +	ret = omap3isp_video_register(&res->video_out, vdev); +	if (ret < 0) +		goto error; + +	return 0; + +error: +	omap3isp_resizer_unregister_entities(res); +	return ret; +} + +/* ----------------------------------------------------------------------------- + * ISP resizer initialization and cleanup + */ + +/* + * resizer_init_entities - Initialize resizer subdev and media entity. + * @res : Pointer to resizer device structure + * return -ENOMEM or zero on success + */ +static int resizer_init_entities(struct isp_res_device *res) +{ +	struct v4l2_subdev *sd = &res->subdev; +	struct media_pad *pads = res->pads; +	struct media_entity *me = &sd->entity; +	int ret; + +	res->input = RESIZER_INPUT_NONE; + +	v4l2_subdev_init(sd, &resizer_v4l2_ops); +	sd->internal_ops = &resizer_v4l2_internal_ops; +	strlcpy(sd->name, "OMAP3 ISP resizer", sizeof(sd->name)); +	sd->grp_id = 1 << 16;	/* group ID for isp subdevs */ +	v4l2_set_subdevdata(sd, res); +	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + +	pads[RESZ_PAD_SINK].flags = MEDIA_PAD_FL_SINK +				    | MEDIA_PAD_FL_MUST_CONNECT; +	pads[RESZ_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; + +	me->ops = &resizer_media_ops; +	ret = media_entity_init(me, RESZ_PADS_NUM, pads, 0); +	if (ret < 0) +		return ret; + +	resizer_init_formats(sd, NULL); + +	res->video_in.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; +	res->video_in.ops = &resizer_video_ops; +	res->video_in.isp = to_isp_device(res); +	res->video_in.capture_mem = PAGE_ALIGN(4096 * 4096) * 2 * 3; +	res->video_in.bpl_alignment = 32; +	res->video_out.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; +	res->video_out.ops = &resizer_video_ops; +	res->video_out.isp = to_isp_device(res); +	res->video_out.capture_mem = PAGE_ALIGN(4096 * 4096) * 2 * 3; +	res->video_out.bpl_alignment = 32; + +	ret = omap3isp_video_init(&res->video_in, "resizer"); +	if (ret < 0) +		goto error_video_in; + +	ret = omap3isp_video_init(&res->video_out, "resizer"); +	if (ret < 0) +		goto error_video_out; + +	res->video_out.video.entity.flags |= MEDIA_ENT_FL_DEFAULT; + +	/* Connect the video nodes to the resizer subdev. */ +	ret = media_entity_create_link(&res->video_in.video.entity, 0, +			&res->subdev.entity, RESZ_PAD_SINK, 0); +	if (ret < 0) +		goto error_link; + +	ret = media_entity_create_link(&res->subdev.entity, RESZ_PAD_SOURCE, +			&res->video_out.video.entity, 0, 0); +	if (ret < 0) +		goto error_link; + +	return 0; + +error_link: +	omap3isp_video_cleanup(&res->video_out); +error_video_out: +	omap3isp_video_cleanup(&res->video_in); +error_video_in: +	media_entity_cleanup(&res->subdev.entity); +	return ret; +} + +/* + * isp_resizer_init - Resizer initialization. + * @isp : Pointer to ISP device + * return -ENOMEM or zero on success + */ +int omap3isp_resizer_init(struct isp_device *isp) +{ +	struct isp_res_device *res = &isp->isp_res; + +	init_waitqueue_head(&res->wait); +	atomic_set(&res->stopping, 0); +	return resizer_init_entities(res); +} + +void omap3isp_resizer_cleanup(struct isp_device *isp) +{ +	struct isp_res_device *res = &isp->isp_res; + +	omap3isp_video_cleanup(&res->video_in); +	omap3isp_video_cleanup(&res->video_out); +	media_entity_cleanup(&res->subdev.entity); +} diff --git a/drivers/media/platform/omap3isp/ispresizer.h b/drivers/media/platform/omap3isp/ispresizer.h new file mode 100644 index 00000000000..9b01e9047c1 --- /dev/null +++ b/drivers/media/platform/omap3isp/ispresizer.h @@ -0,0 +1,146 @@ +/* + * ispresizer.h + * + * TI OMAP3 ISP - Resizer module + * + * Copyright (C) 2010 Nokia Corporation + * Copyright (C) 2009 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 + */ + +#ifndef OMAP3_ISP_RESIZER_H +#define OMAP3_ISP_RESIZER_H + +#include <linux/types.h> + +/* + * Constants for filter coefficients count + */ +#define COEFF_CNT		32 + +/* + * struct isprsz_coef - Structure for resizer filter coefficients. + * @h_filter_coef_4tap: Horizontal filter coefficients for 8-phase/4-tap + *			mode (.5x-4x) + * @v_filter_coef_4tap: Vertical filter coefficients for 8-phase/4-tap + *			mode (.5x-4x) + * @h_filter_coef_7tap: Horizontal filter coefficients for 4-phase/7-tap + *			mode (.25x-.5x) + * @v_filter_coef_7tap: Vertical filter coefficients for 4-phase/7-tap + *			mode (.25x-.5x) + */ +struct isprsz_coef { +	u16 h_filter_coef_4tap[32]; +	u16 v_filter_coef_4tap[32]; +	/* Every 8th value is a dummy value in the following arrays: */ +	u16 h_filter_coef_7tap[32]; +	u16 v_filter_coef_7tap[32]; +}; + +/* Chrominance horizontal algorithm */ +enum resizer_chroma_algo { +	RSZ_THE_SAME = 0,	/* Chrominance the same as Luminance */ +	RSZ_BILINEAR = 1,	/* Chrominance uses bilinear interpolation */ +}; + +/* Resizer input type select */ +enum resizer_colors_type { +	RSZ_YUV422 = 0,		/* YUV422 color is interleaved */ +	RSZ_COLOR8 = 1,		/* Color separate data on 8 bits */ +}; + +/* + * Structure for horizontal and vertical resizing value + */ +struct resizer_ratio { +	u32 horz; +	u32 vert; +}; + +/* + * Structure for luminance enhancer parameters. + */ +struct resizer_luma_yenh { +	u8 algo;		/* algorithm select. */ +	u8 gain;		/* maximum gain. */ +	u8 slope;		/* slope. */ +	u8 core;		/* core offset. */ +}; + +enum resizer_input_entity { +	RESIZER_INPUT_NONE, +	RESIZER_INPUT_VP,	/* input video port - prev or ccdc */ +	RESIZER_INPUT_MEMORY, +}; + +/* Sink and source resizer pads */ +#define RESZ_PAD_SINK			0 +#define RESZ_PAD_SOURCE			1 +#define RESZ_PADS_NUM			2 + +/* + * struct isp_res_device - OMAP3 ISP resizer module + * @crop.request: Crop rectangle requested by the user + * @crop.active: Active crop rectangle (based on hardware requirements) + */ +struct isp_res_device { +	struct v4l2_subdev subdev; +	struct media_pad pads[RESZ_PADS_NUM]; +	struct v4l2_mbus_framefmt formats[RESZ_PADS_NUM]; + +	enum resizer_input_entity input; +	struct isp_video video_in; +	struct isp_video video_out; + +	u32 addr_base;   /* stored source buffer address in memory mode */ +	u32 crop_offset; /* additional offset for crop in memory mode */ +	struct resizer_ratio ratio; +	int pm_state; +	unsigned int applycrop:1; +	enum isp_pipeline_stream_state state; +	wait_queue_head_t wait; +	atomic_t stopping; + +	struct { +		struct v4l2_rect request; +		struct v4l2_rect active; +	} crop; +}; + +struct isp_device; + +int omap3isp_resizer_init(struct isp_device *isp); +void omap3isp_resizer_cleanup(struct isp_device *isp); + +int omap3isp_resizer_register_entities(struct isp_res_device *res, +				       struct v4l2_device *vdev); +void omap3isp_resizer_unregister_entities(struct isp_res_device *res); +void omap3isp_resizer_isr_frame_sync(struct isp_res_device *res); +void omap3isp_resizer_isr(struct isp_res_device *isp_res); + +void omap3isp_resizer_max_rate(struct isp_res_device *res, +			       unsigned int *max_rate); + +void omap3isp_resizer_suspend(struct isp_res_device *isp_res); + +void omap3isp_resizer_resume(struct isp_res_device *isp_res); + +int omap3isp_resizer_busy(struct isp_res_device *isp_res); + +#endif	/* OMAP3_ISP_RESIZER_H */ diff --git a/drivers/media/platform/omap3isp/ispstat.c b/drivers/media/platform/omap3isp/ispstat.c new file mode 100644 index 00000000000..e6cbc1eaf4c --- /dev/null +++ b/drivers/media/platform/omap3isp/ispstat.c @@ -0,0 +1,1073 @@ +/* + * ispstat.c + * + * TI OMAP3 ISP - Statistics core + * + * Copyright (C) 2010 Nokia Corporation + * Copyright (C) 2009 Texas Instruments, Inc + * + * Contacts: David Cohen <dacohen@gmail.com> + *	     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/dma-mapping.h> +#include <linux/slab.h> +#include <linux/uaccess.h> + +#include "isp.h" + +#define ISP_STAT_USES_DMAENGINE(stat)	((stat)->dma_ch >= 0) + +/* + * MAGIC_SIZE must always be the greatest common divisor of + * AEWB_PACKET_SIZE and AF_PAXEL_SIZE. + */ +#define MAGIC_SIZE		16 +#define MAGIC_NUM		0x55 + +/* HACK: AF module seems to be writing one more paxel data than it should. */ +#define AF_EXTRA_DATA		OMAP3ISP_AF_PAXEL_SIZE + +/* + * HACK: H3A modules go to an invalid state after have a SBL overflow. It makes + * the next buffer to start to be written in the same point where the overflow + * occurred instead of the configured address. The only known way to make it to + * go back to a valid state is having a valid buffer processing. Of course it + * requires at least a doubled buffer size to avoid an access to invalid memory + * region. But it does not fix everything. It may happen more than one + * consecutive SBL overflows. In that case, it might be unpredictable how many + * buffers the allocated memory should fit. For that case, a recover + * configuration was created. It produces the minimum buffer size for each H3A + * module and decrease the change for more SBL overflows. This recover state + * will be enabled every time a SBL overflow occur. As the output buffer size + * isn't big, it's possible to have an extra size able to fit many recover + * buffers making it extreamily unlikely to have an access to invalid memory + * region. + */ +#define NUM_H3A_RECOVER_BUFS	10 + +/* + * HACK: Because of HW issues the generic layer sometimes need to have + * different behaviour for different statistic modules. + */ +#define IS_H3A_AF(stat)		((stat) == &(stat)->isp->isp_af) +#define IS_H3A_AEWB(stat)	((stat) == &(stat)->isp->isp_aewb) +#define IS_H3A(stat)		(IS_H3A_AF(stat) || IS_H3A_AEWB(stat)) + +static void __isp_stat_buf_sync_magic(struct ispstat *stat, +				      struct ispstat_buffer *buf, +				      u32 buf_size, enum dma_data_direction dir, +				      void (*dma_sync)(struct device *, +					dma_addr_t, unsigned long, size_t, +					enum dma_data_direction)) +{ +	/* Sync the initial and final magic words. */ +	dma_sync(stat->isp->dev, buf->dma_addr, 0, MAGIC_SIZE, dir); +	dma_sync(stat->isp->dev, buf->dma_addr + (buf_size & PAGE_MASK), +		 buf_size & ~PAGE_MASK, MAGIC_SIZE, dir); +} + +static void isp_stat_buf_sync_magic_for_device(struct ispstat *stat, +					       struct ispstat_buffer *buf, +					       u32 buf_size, +					       enum dma_data_direction dir) +{ +	if (ISP_STAT_USES_DMAENGINE(stat)) +		return; + +	__isp_stat_buf_sync_magic(stat, buf, buf_size, dir, +				  dma_sync_single_range_for_device); +} + +static void isp_stat_buf_sync_magic_for_cpu(struct ispstat *stat, +					    struct ispstat_buffer *buf, +					    u32 buf_size, +					    enum dma_data_direction dir) +{ +	if (ISP_STAT_USES_DMAENGINE(stat)) +		return; + +	__isp_stat_buf_sync_magic(stat, buf, buf_size, dir, +				  dma_sync_single_range_for_cpu); +} + +static int isp_stat_buf_check_magic(struct ispstat *stat, +				    struct ispstat_buffer *buf) +{ +	const u32 buf_size = IS_H3A_AF(stat) ? +			     buf->buf_size + AF_EXTRA_DATA : buf->buf_size; +	u8 *w; +	u8 *end; +	int ret = -EINVAL; + +	isp_stat_buf_sync_magic_for_cpu(stat, buf, buf_size, DMA_FROM_DEVICE); + +	/* Checking initial magic numbers. They shouldn't be here anymore. */ +	for (w = buf->virt_addr, end = w + MAGIC_SIZE; w < end; w++) +		if (likely(*w != MAGIC_NUM)) +			ret = 0; + +	if (ret) { +		dev_dbg(stat->isp->dev, "%s: beginning magic check does not " +					"match.\n", stat->subdev.name); +		return ret; +	} + +	/* Checking magic numbers at the end. They must be still here. */ +	for (w = buf->virt_addr + buf_size, end = w + MAGIC_SIZE; +	     w < end; w++) { +		if (unlikely(*w != MAGIC_NUM)) { +			dev_dbg(stat->isp->dev, "%s: ending magic check does " +				"not match.\n", stat->subdev.name); +			return -EINVAL; +		} +	} + +	isp_stat_buf_sync_magic_for_device(stat, buf, buf_size, +					   DMA_FROM_DEVICE); + +	return 0; +} + +static void isp_stat_buf_insert_magic(struct ispstat *stat, +				      struct ispstat_buffer *buf) +{ +	const u32 buf_size = IS_H3A_AF(stat) ? +			     stat->buf_size + AF_EXTRA_DATA : stat->buf_size; + +	isp_stat_buf_sync_magic_for_cpu(stat, buf, buf_size, DMA_FROM_DEVICE); + +	/* +	 * Inserting MAGIC_NUM at the beginning and end of the buffer. +	 * buf->buf_size is set only after the buffer is queued. For now the +	 * right buf_size for the current configuration is pointed by +	 * stat->buf_size. +	 */ +	memset(buf->virt_addr, MAGIC_NUM, MAGIC_SIZE); +	memset(buf->virt_addr + buf_size, MAGIC_NUM, MAGIC_SIZE); + +	isp_stat_buf_sync_magic_for_device(stat, buf, buf_size, +					   DMA_BIDIRECTIONAL); +} + +static void isp_stat_buf_sync_for_device(struct ispstat *stat, +					 struct ispstat_buffer *buf) +{ +	if (ISP_STAT_USES_DMAENGINE(stat)) +		return; + +	dma_sync_sg_for_device(stat->isp->dev, buf->sgt.sgl, +			       buf->sgt.nents, DMA_FROM_DEVICE); +} + +static void isp_stat_buf_sync_for_cpu(struct ispstat *stat, +				      struct ispstat_buffer *buf) +{ +	if (ISP_STAT_USES_DMAENGINE(stat)) +		return; + +	dma_sync_sg_for_cpu(stat->isp->dev, buf->sgt.sgl, +			    buf->sgt.nents, DMA_FROM_DEVICE); +} + +static void isp_stat_buf_clear(struct ispstat *stat) +{ +	int i; + +	for (i = 0; i < STAT_MAX_BUFS; i++) +		stat->buf[i].empty = 1; +} + +static struct ispstat_buffer * +__isp_stat_buf_find(struct ispstat *stat, int look_empty) +{ +	struct ispstat_buffer *found = NULL; +	int i; + +	for (i = 0; i < STAT_MAX_BUFS; i++) { +		struct ispstat_buffer *curr = &stat->buf[i]; + +		/* +		 * Don't select the buffer which is being copied to +		 * userspace or used by the module. +		 */ +		if (curr == stat->locked_buf || curr == stat->active_buf) +			continue; + +		/* Don't select uninitialised buffers if it's not required */ +		if (!look_empty && curr->empty) +			continue; + +		/* Pick uninitialised buffer over anything else if look_empty */ +		if (curr->empty) { +			found = curr; +			break; +		} + +		/* Choose the oldest buffer */ +		if (!found || +		    (s32)curr->frame_number - (s32)found->frame_number < 0) +			found = curr; +	} + +	return found; +} + +static inline struct ispstat_buffer * +isp_stat_buf_find_oldest(struct ispstat *stat) +{ +	return __isp_stat_buf_find(stat, 0); +} + +static inline struct ispstat_buffer * +isp_stat_buf_find_oldest_or_empty(struct ispstat *stat) +{ +	return __isp_stat_buf_find(stat, 1); +} + +static int isp_stat_buf_queue(struct ispstat *stat) +{ +	if (!stat->active_buf) +		return STAT_NO_BUF; + +	ktime_get_ts(&stat->active_buf->ts); + +	stat->active_buf->buf_size = stat->buf_size; +	if (isp_stat_buf_check_magic(stat, stat->active_buf)) { +		dev_dbg(stat->isp->dev, "%s: data wasn't properly written.\n", +			stat->subdev.name); +		return STAT_NO_BUF; +	} +	stat->active_buf->config_counter = stat->config_counter; +	stat->active_buf->frame_number = stat->frame_number; +	stat->active_buf->empty = 0; +	stat->active_buf = NULL; + +	return STAT_BUF_DONE; +} + +/* Get next free buffer to write the statistics to and mark it active. */ +static void isp_stat_buf_next(struct ispstat *stat) +{ +	if (unlikely(stat->active_buf)) +		/* Overwriting unused active buffer */ +		dev_dbg(stat->isp->dev, "%s: new buffer requested without " +					"queuing active one.\n", +					stat->subdev.name); +	else +		stat->active_buf = isp_stat_buf_find_oldest_or_empty(stat); +} + +static void isp_stat_buf_release(struct ispstat *stat) +{ +	unsigned long flags; + +	isp_stat_buf_sync_for_device(stat, stat->locked_buf); +	spin_lock_irqsave(&stat->isp->stat_lock, flags); +	stat->locked_buf = NULL; +	spin_unlock_irqrestore(&stat->isp->stat_lock, flags); +} + +/* Get buffer to userspace. */ +static struct ispstat_buffer *isp_stat_buf_get(struct ispstat *stat, +					       struct omap3isp_stat_data *data) +{ +	int rval = 0; +	unsigned long flags; +	struct ispstat_buffer *buf; + +	spin_lock_irqsave(&stat->isp->stat_lock, flags); + +	while (1) { +		buf = isp_stat_buf_find_oldest(stat); +		if (!buf) { +			spin_unlock_irqrestore(&stat->isp->stat_lock, flags); +			dev_dbg(stat->isp->dev, "%s: cannot find a buffer.\n", +				stat->subdev.name); +			return ERR_PTR(-EBUSY); +		} +		if (isp_stat_buf_check_magic(stat, buf)) { +			dev_dbg(stat->isp->dev, "%s: current buffer has " +				"corrupted data\n.", stat->subdev.name); +			/* Mark empty because it doesn't have valid data. */ +			buf->empty = 1; +		} else { +			/* Buffer isn't corrupted. */ +			break; +		} +	} + +	stat->locked_buf = buf; + +	spin_unlock_irqrestore(&stat->isp->stat_lock, flags); + +	if (buf->buf_size > data->buf_size) { +		dev_warn(stat->isp->dev, "%s: userspace's buffer size is " +					 "not enough.\n", stat->subdev.name); +		isp_stat_buf_release(stat); +		return ERR_PTR(-EINVAL); +	} + +	isp_stat_buf_sync_for_cpu(stat, buf); + +	rval = copy_to_user(data->buf, +			    buf->virt_addr, +			    buf->buf_size); + +	if (rval) { +		dev_info(stat->isp->dev, +			 "%s: failed copying %d bytes of stat data\n", +			 stat->subdev.name, rval); +		buf = ERR_PTR(-EFAULT); +		isp_stat_buf_release(stat); +	} + +	return buf; +} + +static void isp_stat_bufs_free(struct ispstat *stat) +{ +	struct device *dev = ISP_STAT_USES_DMAENGINE(stat) +			   ? NULL : stat->isp->dev; +	unsigned int i; + +	for (i = 0; i < STAT_MAX_BUFS; i++) { +		struct ispstat_buffer *buf = &stat->buf[i]; + +		if (!buf->virt_addr) +			continue; + +		sg_free_table(&buf->sgt); + +		dma_free_coherent(dev, stat->buf_alloc_size, buf->virt_addr, +				  buf->dma_addr); + +		buf->dma_addr = 0; +		buf->virt_addr = NULL; +		buf->empty = 1; +	} + +	dev_dbg(stat->isp->dev, "%s: all buffers were freed.\n", +		stat->subdev.name); + +	stat->buf_alloc_size = 0; +	stat->active_buf = NULL; +} + +static int isp_stat_bufs_alloc_one(struct device *dev, +				   struct ispstat_buffer *buf, +				   unsigned int size) +{ +	int ret; + +	buf->virt_addr = dma_alloc_coherent(dev, size, &buf->dma_addr, +					    GFP_KERNEL | GFP_DMA); +	if (!buf->virt_addr) +		return -ENOMEM; + +	ret = dma_get_sgtable(dev, &buf->sgt, buf->virt_addr, buf->dma_addr, +			      size); +	if (ret < 0) { +		dma_free_coherent(dev, size, buf->virt_addr, buf->dma_addr); +		buf->virt_addr = NULL; +		buf->dma_addr = 0; +		return ret; +	} + +	return 0; +} + +/* + * The device passed to the DMA API depends on whether the statistics block uses + * ISP DMA, external DMA or PIO to transfer data. + * + * The first case (for the AEWB and AF engines) passes the ISP device, resulting + * in the DMA buffers being mapped through the ISP IOMMU. + * + * The second case (for the histogram engine) should pass the DMA engine device. + * As that device isn't accessible through the OMAP DMA engine API the driver + * passes NULL instead, resulting in the buffers being mapped directly as + * physical pages. + * + * The third case (for the histogram engine) doesn't require any mapping. The + * buffers could be allocated with kmalloc/vmalloc, but we still use + * dma_alloc_coherent() for consistency purpose. + */ +static int isp_stat_bufs_alloc(struct ispstat *stat, u32 size) +{ +	struct device *dev = ISP_STAT_USES_DMAENGINE(stat) +			   ? NULL : stat->isp->dev; +	unsigned long flags; +	unsigned int i; + +	spin_lock_irqsave(&stat->isp->stat_lock, flags); + +	BUG_ON(stat->locked_buf != NULL); + +	/* Are the old buffers big enough? */ +	if (stat->buf_alloc_size >= size) { +		spin_unlock_irqrestore(&stat->isp->stat_lock, flags); +		return 0; +	} + +	if (stat->state != ISPSTAT_DISABLED || stat->buf_processing) { +		dev_info(stat->isp->dev, +			 "%s: trying to allocate memory when busy\n", +			 stat->subdev.name); +		spin_unlock_irqrestore(&stat->isp->stat_lock, flags); +		return -EBUSY; +	} + +	spin_unlock_irqrestore(&stat->isp->stat_lock, flags); + +	isp_stat_bufs_free(stat); + +	stat->buf_alloc_size = size; + +	for (i = 0; i < STAT_MAX_BUFS; i++) { +		struct ispstat_buffer *buf = &stat->buf[i]; +		int ret; + +		ret = isp_stat_bufs_alloc_one(dev, buf, size); +		if (ret < 0) { +			dev_err(stat->isp->dev, +				"%s: Failed to allocate DMA buffer %u\n", +				stat->subdev.name, i); +			isp_stat_bufs_free(stat); +			return ret; +		} + +		buf->empty = 1; + +		dev_dbg(stat->isp->dev, +			"%s: buffer[%u] allocated. dma=0x%08lx virt=0x%08lx", +			stat->subdev.name, i, +			(unsigned long)buf->dma_addr, +			(unsigned long)buf->virt_addr); +	} + +	return 0; +} + +static void isp_stat_queue_event(struct ispstat *stat, int err) +{ +	struct video_device *vdev = stat->subdev.devnode; +	struct v4l2_event event; +	struct omap3isp_stat_event_status *status = (void *)event.u.data; + +	memset(&event, 0, sizeof(event)); +	if (!err) { +		status->frame_number = stat->frame_number; +		status->config_counter = stat->config_counter; +	} else { +		status->buf_err = 1; +	} +	event.type = stat->event_type; +	v4l2_event_queue(vdev, &event); +} + + +/* + * omap3isp_stat_request_statistics - Request statistics. + * @data: Pointer to return statistics data. + * + * Returns 0 if successful. + */ +int omap3isp_stat_request_statistics(struct ispstat *stat, +				     struct omap3isp_stat_data *data) +{ +	struct ispstat_buffer *buf; + +	if (stat->state != ISPSTAT_ENABLED) { +		dev_dbg(stat->isp->dev, "%s: engine not enabled.\n", +			stat->subdev.name); +		return -EINVAL; +	} + +	mutex_lock(&stat->ioctl_lock); +	buf = isp_stat_buf_get(stat, data); +	if (IS_ERR(buf)) { +		mutex_unlock(&stat->ioctl_lock); +		return PTR_ERR(buf); +	} + +	data->ts.tv_sec = buf->ts.tv_sec; +	data->ts.tv_usec = buf->ts.tv_nsec / NSEC_PER_USEC; +	data->config_counter = buf->config_counter; +	data->frame_number = buf->frame_number; +	data->buf_size = buf->buf_size; + +	buf->empty = 1; +	isp_stat_buf_release(stat); +	mutex_unlock(&stat->ioctl_lock); + +	return 0; +} + +/* + * omap3isp_stat_config - Receives new statistic engine configuration. + * @new_conf: Pointer to config structure. + * + * Returns 0 if successful, -EINVAL if new_conf pointer is NULL, -ENOMEM if + * was unable to allocate memory for the buffer, or other errors if parameters + * are invalid. + */ +int omap3isp_stat_config(struct ispstat *stat, void *new_conf) +{ +	int ret; +	unsigned long irqflags; +	struct ispstat_generic_config *user_cfg = new_conf; +	u32 buf_size = user_cfg->buf_size; + +	if (!new_conf) { +		dev_dbg(stat->isp->dev, "%s: configuration is NULL\n", +			stat->subdev.name); +		return -EINVAL; +	} + +	mutex_lock(&stat->ioctl_lock); + +	dev_dbg(stat->isp->dev, "%s: configuring module with buffer " +		"size=0x%08lx\n", stat->subdev.name, (unsigned long)buf_size); + +	ret = stat->ops->validate_params(stat, new_conf); +	if (ret) { +		mutex_unlock(&stat->ioctl_lock); +		dev_dbg(stat->isp->dev, "%s: configuration values are " +					"invalid.\n", stat->subdev.name); +		return ret; +	} + +	if (buf_size != user_cfg->buf_size) +		dev_dbg(stat->isp->dev, "%s: driver has corrected buffer size " +			"request to 0x%08lx\n", stat->subdev.name, +			(unsigned long)user_cfg->buf_size); + +	/* +	 * Hack: H3A modules may need a doubled buffer size to avoid access +	 * to a invalid memory address after a SBL overflow. +	 * The buffer size is always PAGE_ALIGNED. +	 * Hack 2: MAGIC_SIZE is added to buf_size so a magic word can be +	 * inserted at the end to data integrity check purpose. +	 * Hack 3: AF module writes one paxel data more than it should, so +	 * the buffer allocation must consider it to avoid invalid memory +	 * access. +	 * Hack 4: H3A need to allocate extra space for the recover state. +	 */ +	if (IS_H3A(stat)) { +		buf_size = user_cfg->buf_size * 2 + MAGIC_SIZE; +		if (IS_H3A_AF(stat)) +			/* +			 * Adding one extra paxel data size for each recover +			 * buffer + 2 regular ones. +			 */ +			buf_size += AF_EXTRA_DATA * (NUM_H3A_RECOVER_BUFS + 2); +		if (stat->recover_priv) { +			struct ispstat_generic_config *recover_cfg = +				stat->recover_priv; +			buf_size += recover_cfg->buf_size * +				    NUM_H3A_RECOVER_BUFS; +		} +		buf_size = PAGE_ALIGN(buf_size); +	} else { /* Histogram */ +		buf_size = PAGE_ALIGN(user_cfg->buf_size + MAGIC_SIZE); +	} + +	ret = isp_stat_bufs_alloc(stat, buf_size); +	if (ret) { +		mutex_unlock(&stat->ioctl_lock); +		return ret; +	} + +	spin_lock_irqsave(&stat->isp->stat_lock, irqflags); +	stat->ops->set_params(stat, new_conf); +	spin_unlock_irqrestore(&stat->isp->stat_lock, irqflags); + +	/* +	 * Returning the right future config_counter for this setup, so +	 * userspace can *know* when it has been applied. +	 */ +	user_cfg->config_counter = stat->config_counter + stat->inc_config; + +	/* Module has a valid configuration. */ +	stat->configured = 1; +	dev_dbg(stat->isp->dev, "%s: module has been successfully " +		"configured.\n", stat->subdev.name); + +	mutex_unlock(&stat->ioctl_lock); + +	return 0; +} + +/* + * isp_stat_buf_process - Process statistic buffers. + * @buf_state: points out if buffer is ready to be processed. It's necessary + *	       because histogram needs to copy the data from internal memory + *	       before be able to process the buffer. + */ +static int isp_stat_buf_process(struct ispstat *stat, int buf_state) +{ +	int ret = STAT_NO_BUF; + +	if (!atomic_add_unless(&stat->buf_err, -1, 0) && +	    buf_state == STAT_BUF_DONE && stat->state == ISPSTAT_ENABLED) { +		ret = isp_stat_buf_queue(stat); +		isp_stat_buf_next(stat); +	} + +	return ret; +} + +int omap3isp_stat_pcr_busy(struct ispstat *stat) +{ +	return stat->ops->busy(stat); +} + +int omap3isp_stat_busy(struct ispstat *stat) +{ +	return omap3isp_stat_pcr_busy(stat) | stat->buf_processing | +		(stat->state != ISPSTAT_DISABLED); +} + +/* + * isp_stat_pcr_enable - Disables/Enables statistic engines. + * @pcr_enable: 0/1 - Disables/Enables the engine. + * + * Must be called from ISP driver when the module is idle and synchronized + * with CCDC. + */ +static void isp_stat_pcr_enable(struct ispstat *stat, u8 pcr_enable) +{ +	if ((stat->state != ISPSTAT_ENABLING && +	     stat->state != ISPSTAT_ENABLED) && pcr_enable) +		/* Userspace has disabled the module. Aborting. */ +		return; + +	stat->ops->enable(stat, pcr_enable); +	if (stat->state == ISPSTAT_DISABLING && !pcr_enable) +		stat->state = ISPSTAT_DISABLED; +	else if (stat->state == ISPSTAT_ENABLING && pcr_enable) +		stat->state = ISPSTAT_ENABLED; +} + +void omap3isp_stat_suspend(struct ispstat *stat) +{ +	unsigned long flags; + +	spin_lock_irqsave(&stat->isp->stat_lock, flags); + +	if (stat->state != ISPSTAT_DISABLED) +		stat->ops->enable(stat, 0); +	if (stat->state == ISPSTAT_ENABLED) +		stat->state = ISPSTAT_SUSPENDED; + +	spin_unlock_irqrestore(&stat->isp->stat_lock, flags); +} + +void omap3isp_stat_resume(struct ispstat *stat) +{ +	/* Module will be re-enabled with its pipeline */ +	if (stat->state == ISPSTAT_SUSPENDED) +		stat->state = ISPSTAT_ENABLING; +} + +static void isp_stat_try_enable(struct ispstat *stat) +{ +	unsigned long irqflags; + +	if (stat->priv == NULL) +		/* driver wasn't initialised */ +		return; + +	spin_lock_irqsave(&stat->isp->stat_lock, irqflags); +	if (stat->state == ISPSTAT_ENABLING && !stat->buf_processing && +	    stat->buf_alloc_size) { +		/* +		 * Userspace's requested to enable the engine but it wasn't yet. +		 * Let's do that now. +		 */ +		stat->update = 1; +		isp_stat_buf_next(stat); +		stat->ops->setup_regs(stat, stat->priv); +		isp_stat_buf_insert_magic(stat, stat->active_buf); + +		/* +		 * H3A module has some hw issues which forces the driver to +		 * ignore next buffers even if it was disabled in the meantime. +		 * On the other hand, Histogram shouldn't ignore buffers anymore +		 * if it's being enabled. +		 */ +		if (!IS_H3A(stat)) +			atomic_set(&stat->buf_err, 0); + +		isp_stat_pcr_enable(stat, 1); +		spin_unlock_irqrestore(&stat->isp->stat_lock, irqflags); +		dev_dbg(stat->isp->dev, "%s: module is enabled.\n", +			stat->subdev.name); +	} else { +		spin_unlock_irqrestore(&stat->isp->stat_lock, irqflags); +	} +} + +void omap3isp_stat_isr_frame_sync(struct ispstat *stat) +{ +	isp_stat_try_enable(stat); +} + +void omap3isp_stat_sbl_overflow(struct ispstat *stat) +{ +	unsigned long irqflags; + +	spin_lock_irqsave(&stat->isp->stat_lock, irqflags); +	/* +	 * Due to a H3A hw issue which prevents the next buffer to start from +	 * the correct memory address, 2 buffers must be ignored. +	 */ +	atomic_set(&stat->buf_err, 2); + +	/* +	 * If more than one SBL overflow happen in a row, H3A module may access +	 * invalid memory region. +	 * stat->sbl_ovl_recover is set to tell to the driver to temporarily use +	 * a soft configuration which helps to avoid consecutive overflows. +	 */ +	if (stat->recover_priv) +		stat->sbl_ovl_recover = 1; +	spin_unlock_irqrestore(&stat->isp->stat_lock, irqflags); +} + +/* + * omap3isp_stat_enable - Disable/Enable statistic engine as soon as possible + * @enable: 0/1 - Disables/Enables the engine. + * + * Client should configure all the module registers before this. + * This function can be called from a userspace request. + */ +int omap3isp_stat_enable(struct ispstat *stat, u8 enable) +{ +	unsigned long irqflags; + +	dev_dbg(stat->isp->dev, "%s: user wants to %s module.\n", +		stat->subdev.name, enable ? "enable" : "disable"); + +	/* Prevent enabling while configuring */ +	mutex_lock(&stat->ioctl_lock); + +	spin_lock_irqsave(&stat->isp->stat_lock, irqflags); + +	if (!stat->configured && enable) { +		spin_unlock_irqrestore(&stat->isp->stat_lock, irqflags); +		mutex_unlock(&stat->ioctl_lock); +		dev_dbg(stat->isp->dev, "%s: cannot enable module as it's " +			"never been successfully configured so far.\n", +			stat->subdev.name); +		return -EINVAL; +	} + +	if (enable) { +		if (stat->state == ISPSTAT_DISABLING) +			/* Previous disabling request wasn't done yet */ +			stat->state = ISPSTAT_ENABLED; +		else if (stat->state == ISPSTAT_DISABLED) +			/* Module is now being enabled */ +			stat->state = ISPSTAT_ENABLING; +	} else { +		if (stat->state == ISPSTAT_ENABLING) { +			/* Previous enabling request wasn't done yet */ +			stat->state = ISPSTAT_DISABLED; +		} else if (stat->state == ISPSTAT_ENABLED) { +			/* Module is now being disabled */ +			stat->state = ISPSTAT_DISABLING; +			isp_stat_buf_clear(stat); +		} +	} + +	spin_unlock_irqrestore(&stat->isp->stat_lock, irqflags); +	mutex_unlock(&stat->ioctl_lock); + +	return 0; +} + +int omap3isp_stat_s_stream(struct v4l2_subdev *subdev, int enable) +{ +	struct ispstat *stat = v4l2_get_subdevdata(subdev); + +	if (enable) { +		/* +		 * Only set enable PCR bit if the module was previously +		 * enabled through ioctl. +		 */ +		isp_stat_try_enable(stat); +	} else { +		unsigned long flags; +		/* Disable PCR bit and config enable field */ +		omap3isp_stat_enable(stat, 0); +		spin_lock_irqsave(&stat->isp->stat_lock, flags); +		stat->ops->enable(stat, 0); +		spin_unlock_irqrestore(&stat->isp->stat_lock, flags); + +		/* +		 * If module isn't busy, a new interrupt may come or not to +		 * set the state to DISABLED. As Histogram needs to read its +		 * internal memory to clear it, let interrupt handler +		 * responsible of changing state to DISABLED. If the last +		 * interrupt is coming, it's still safe as the handler will +		 * ignore the second time when state is already set to DISABLED. +		 * It's necessary to synchronize Histogram with streamoff, once +		 * the module may be considered idle before last SDMA transfer +		 * starts if we return here. +		 */ +		if (!omap3isp_stat_pcr_busy(stat)) +			omap3isp_stat_isr(stat); + +		dev_dbg(stat->isp->dev, "%s: module is being disabled\n", +			stat->subdev.name); +	} + +	return 0; +} + +/* + * __stat_isr - Interrupt handler for statistic drivers + */ +static void __stat_isr(struct ispstat *stat, int from_dma) +{ +	int ret = STAT_BUF_DONE; +	int buf_processing; +	unsigned long irqflags; +	struct isp_pipeline *pipe; + +	/* +	 * stat->buf_processing must be set before disable module. It's +	 * necessary to not inform too early the buffers aren't busy in case +	 * of SDMA is going to be used. +	 */ +	spin_lock_irqsave(&stat->isp->stat_lock, irqflags); +	if (stat->state == ISPSTAT_DISABLED) { +		spin_unlock_irqrestore(&stat->isp->stat_lock, irqflags); +		return; +	} +	buf_processing = stat->buf_processing; +	stat->buf_processing = 1; +	stat->ops->enable(stat, 0); + +	if (buf_processing && !from_dma) { +		if (stat->state == ISPSTAT_ENABLED) { +			spin_unlock_irqrestore(&stat->isp->stat_lock, irqflags); +			dev_err(stat->isp->dev, +				"%s: interrupt occurred when module was still " +				"processing a buffer.\n", stat->subdev.name); +			ret = STAT_NO_BUF; +			goto out; +		} else { +			/* +			 * Interrupt handler was called from streamoff when +			 * the module wasn't busy anymore to ensure it is being +			 * disabled after process last buffer. If such buffer +			 * processing has already started, no need to do +			 * anything else. +			 */ +			spin_unlock_irqrestore(&stat->isp->stat_lock, irqflags); +			return; +		} +	} +	spin_unlock_irqrestore(&stat->isp->stat_lock, irqflags); + +	/* If it's busy we can't process this buffer anymore */ +	if (!omap3isp_stat_pcr_busy(stat)) { +		if (!from_dma && stat->ops->buf_process) +			/* Module still need to copy data to buffer. */ +			ret = stat->ops->buf_process(stat); +		if (ret == STAT_BUF_WAITING_DMA) +			/* Buffer is not ready yet */ +			return; + +		spin_lock_irqsave(&stat->isp->stat_lock, irqflags); + +		/* +		 * Histogram needs to read its internal memory to clear it +		 * before be disabled. For that reason, common statistic layer +		 * can return only after call stat's buf_process() operator. +		 */ +		if (stat->state == ISPSTAT_DISABLING) { +			stat->state = ISPSTAT_DISABLED; +			spin_unlock_irqrestore(&stat->isp->stat_lock, irqflags); +			stat->buf_processing = 0; +			return; +		} +		pipe = to_isp_pipeline(&stat->subdev.entity); +		stat->frame_number = atomic_read(&pipe->frame_number); + +		/* +		 * Before this point, 'ret' stores the buffer's status if it's +		 * ready to be processed. Afterwards, it holds the status if +		 * it was processed successfully. +		 */ +		ret = isp_stat_buf_process(stat, ret); + +		if (likely(!stat->sbl_ovl_recover)) { +			stat->ops->setup_regs(stat, stat->priv); +		} else { +			/* +			 * Using recover config to increase the chance to have +			 * a good buffer processing and make the H3A module to +			 * go back to a valid state. +			 */ +			stat->update = 1; +			stat->ops->setup_regs(stat, stat->recover_priv); +			stat->sbl_ovl_recover = 0; + +			/* +			 * Set 'update' in case of the module needs to use +			 * regular configuration after next buffer. +			 */ +			stat->update = 1; +		} + +		isp_stat_buf_insert_magic(stat, stat->active_buf); + +		/* +		 * Hack: H3A modules may access invalid memory address or send +		 * corrupted data to userspace if more than 1 SBL overflow +		 * happens in a row without re-writing its buffer's start memory +		 * address in the meantime. Such situation is avoided if the +		 * module is not immediately re-enabled when the ISR misses the +		 * timing to process the buffer and to setup the registers. +		 * Because of that, pcr_enable(1) was moved to inside this 'if' +		 * block. But the next interruption will still happen as during +		 * pcr_enable(0) the module was busy. +		 */ +		isp_stat_pcr_enable(stat, 1); +		spin_unlock_irqrestore(&stat->isp->stat_lock, irqflags); +	} else { +		/* +		 * If a SBL overflow occurs and the H3A driver misses the timing +		 * to process the buffer, stat->buf_err is set and won't be +		 * cleared now. So the next buffer will be correctly ignored. +		 * It's necessary due to a hw issue which makes the next H3A +		 * buffer to start from the memory address where the previous +		 * one stopped, instead of start where it was configured to. +		 * Do not "stat->buf_err = 0" here. +		 */ + +		if (stat->ops->buf_process) +			/* +			 * Driver may need to erase current data prior to +			 * process a new buffer. If it misses the timing, the +			 * next buffer might be wrong. So should be ignored. +			 * It happens only for Histogram. +			 */ +			atomic_set(&stat->buf_err, 1); + +		ret = STAT_NO_BUF; +		dev_dbg(stat->isp->dev, "%s: cannot process buffer, " +					"device is busy.\n", stat->subdev.name); +	} + +out: +	stat->buf_processing = 0; +	isp_stat_queue_event(stat, ret != STAT_BUF_DONE); +} + +void omap3isp_stat_isr(struct ispstat *stat) +{ +	__stat_isr(stat, 0); +} + +void omap3isp_stat_dma_isr(struct ispstat *stat) +{ +	__stat_isr(stat, 1); +} + +int omap3isp_stat_subscribe_event(struct v4l2_subdev *subdev, +				  struct v4l2_fh *fh, +				  struct v4l2_event_subscription *sub) +{ +	struct ispstat *stat = v4l2_get_subdevdata(subdev); + +	if (sub->type != stat->event_type) +		return -EINVAL; + +	return v4l2_event_subscribe(fh, sub, STAT_NEVENTS, NULL); +} + +int omap3isp_stat_unsubscribe_event(struct v4l2_subdev *subdev, +				    struct v4l2_fh *fh, +				    struct v4l2_event_subscription *sub) +{ +	return v4l2_event_unsubscribe(fh, sub); +} + +void omap3isp_stat_unregister_entities(struct ispstat *stat) +{ +	v4l2_device_unregister_subdev(&stat->subdev); +} + +int omap3isp_stat_register_entities(struct ispstat *stat, +				    struct v4l2_device *vdev) +{ +	return v4l2_device_register_subdev(vdev, &stat->subdev); +} + +static int isp_stat_init_entities(struct ispstat *stat, const char *name, +				  const struct v4l2_subdev_ops *sd_ops) +{ +	struct v4l2_subdev *subdev = &stat->subdev; +	struct media_entity *me = &subdev->entity; + +	v4l2_subdev_init(subdev, sd_ops); +	snprintf(subdev->name, V4L2_SUBDEV_NAME_SIZE, "OMAP3 ISP %s", name); +	subdev->grp_id = 1 << 16;	/* group ID for isp subdevs */ +	subdev->flags |= V4L2_SUBDEV_FL_HAS_EVENTS | V4L2_SUBDEV_FL_HAS_DEVNODE; +	v4l2_set_subdevdata(subdev, stat); + +	stat->pad.flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT; +	me->ops = NULL; + +	return media_entity_init(me, 1, &stat->pad, 0); +} + +int omap3isp_stat_init(struct ispstat *stat, const char *name, +		       const struct v4l2_subdev_ops *sd_ops) +{ +	int ret; + +	stat->buf = kcalloc(STAT_MAX_BUFS, sizeof(*stat->buf), GFP_KERNEL); +	if (!stat->buf) +		return -ENOMEM; + +	isp_stat_buf_clear(stat); +	mutex_init(&stat->ioctl_lock); +	atomic_set(&stat->buf_err, 0); + +	ret = isp_stat_init_entities(stat, name, sd_ops); +	if (ret < 0) { +		mutex_destroy(&stat->ioctl_lock); +		kfree(stat->buf); +	} + +	return ret; +} + +void omap3isp_stat_cleanup(struct ispstat *stat) +{ +	media_entity_cleanup(&stat->subdev.entity); +	mutex_destroy(&stat->ioctl_lock); +	isp_stat_bufs_free(stat); +	kfree(stat->buf); +} diff --git a/drivers/media/platform/omap3isp/ispstat.h b/drivers/media/platform/omap3isp/ispstat.h new file mode 100644 index 00000000000..58d6ac7cb66 --- /dev/null +++ b/drivers/media/platform/omap3isp/ispstat.h @@ -0,0 +1,168 @@ +/* + * ispstat.h + * + * TI OMAP3 ISP - Statistics core + * + * Copyright (C) 2010 Nokia Corporation + * Copyright (C) 2009 Texas Instruments, Inc + * + * Contacts: David Cohen <dacohen@gmail.com> + *	     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 + */ + +#ifndef OMAP3_ISP_STAT_H +#define OMAP3_ISP_STAT_H + +#include <linux/types.h> +#include <linux/omap3isp.h> +#include <linux/omap-dma.h> +#include <media/v4l2-event.h> + +#include "isp.h" +#include "ispvideo.h" + +#define STAT_MAX_BUFS		5 +#define STAT_NEVENTS		8 + +#define STAT_BUF_DONE		0	/* Buffer is ready */ +#define STAT_NO_BUF		1	/* An error has occurred */ +#define STAT_BUF_WAITING_DMA	2	/* Histogram only: DMA is running */ + +struct ispstat; + +struct ispstat_buffer { +	struct sg_table sgt; +	void *virt_addr; +	dma_addr_t dma_addr; +	struct timespec ts; +	u32 buf_size; +	u32 frame_number; +	u16 config_counter; +	u8 empty; +}; + +struct ispstat_ops { +	/* +	 * Validate new params configuration. +	 * new_conf->buf_size value must be changed to the exact buffer size +	 * necessary for the new configuration if it's smaller. +	 */ +	int (*validate_params)(struct ispstat *stat, void *new_conf); + +	/* +	 * Save new params configuration. +	 * stat->priv->buf_size value must be set to the exact buffer size for +	 * the new configuration. +	 * stat->update is set to 1 if new configuration is different than +	 * current one. +	 */ +	void (*set_params)(struct ispstat *stat, void *new_conf); + +	/* Apply stored configuration. */ +	void (*setup_regs)(struct ispstat *stat, void *priv); + +	/* Enable/Disable module. */ +	void (*enable)(struct ispstat *stat, int enable); + +	/* Verify is module is busy. */ +	int (*busy)(struct ispstat *stat); + +	/* Used for specific operations during generic buf process task. */ +	int (*buf_process)(struct ispstat *stat); +}; + +enum ispstat_state_t { +	ISPSTAT_DISABLED = 0, +	ISPSTAT_DISABLING, +	ISPSTAT_ENABLED, +	ISPSTAT_ENABLING, +	ISPSTAT_SUSPENDED, +}; + +struct ispstat { +	struct v4l2_subdev subdev; +	struct media_pad pad;	/* sink pad */ + +	/* Control */ +	unsigned configured:1; +	unsigned update:1; +	unsigned buf_processing:1; +	unsigned sbl_ovl_recover:1; +	u8 inc_config; +	atomic_t buf_err; +	enum ispstat_state_t state;	/* enabling/disabling state */ +	struct omap_dma_channel_params dma_config; +	struct isp_device *isp; +	void *priv;		/* pointer to priv config struct */ +	void *recover_priv;	/* pointer to recover priv configuration */ +	struct mutex ioctl_lock; /* serialize private ioctl */ + +	const struct ispstat_ops *ops; + +	/* Buffer */ +	u8 wait_acc_frames; +	u16 config_counter; +	u32 frame_number; +	u32 buf_size; +	u32 buf_alloc_size; +	int dma_ch; +	unsigned long event_type; +	struct ispstat_buffer *buf; +	struct ispstat_buffer *active_buf; +	struct ispstat_buffer *locked_buf; +}; + +struct ispstat_generic_config { +	/* +	 * Fields must be in the same order as in: +	 *  - omap3isp_h3a_aewb_config +	 *  - omap3isp_h3a_af_config +	 *  - omap3isp_hist_config +	 */ +	u32 buf_size; +	u16 config_counter; +}; + +int omap3isp_stat_config(struct ispstat *stat, void *new_conf); +int omap3isp_stat_request_statistics(struct ispstat *stat, +				     struct omap3isp_stat_data *data); +int omap3isp_stat_init(struct ispstat *stat, const char *name, +		       const struct v4l2_subdev_ops *sd_ops); +void omap3isp_stat_cleanup(struct ispstat *stat); +int omap3isp_stat_subscribe_event(struct v4l2_subdev *subdev, +				  struct v4l2_fh *fh, +				  struct v4l2_event_subscription *sub); +int omap3isp_stat_unsubscribe_event(struct v4l2_subdev *subdev, +				    struct v4l2_fh *fh, +				    struct v4l2_event_subscription *sub); +int omap3isp_stat_s_stream(struct v4l2_subdev *subdev, int enable); + +int omap3isp_stat_busy(struct ispstat *stat); +int omap3isp_stat_pcr_busy(struct ispstat *stat); +void omap3isp_stat_suspend(struct ispstat *stat); +void omap3isp_stat_resume(struct ispstat *stat); +int omap3isp_stat_enable(struct ispstat *stat, u8 enable); +void omap3isp_stat_sbl_overflow(struct ispstat *stat); +void omap3isp_stat_isr(struct ispstat *stat); +void omap3isp_stat_isr_frame_sync(struct ispstat *stat); +void omap3isp_stat_dma_isr(struct ispstat *stat); +int omap3isp_stat_register_entities(struct ispstat *stat, +				    struct v4l2_device *vdev); +void omap3isp_stat_unregister_entities(struct ispstat *stat); + +#endif /* OMAP3_ISP_STAT_H */ diff --git a/drivers/media/platform/omap3isp/ispvideo.c b/drivers/media/platform/omap3isp/ispvideo.c new file mode 100644 index 00000000000..e36bac26476 --- /dev/null +++ b/drivers/media/platform/omap3isp/ispvideo.c @@ -0,0 +1,1407 @@ +/* + * ispvideo.c + * + * TI OMAP3 ISP - Generic video node + * + * Copyright (C) 2009-2010 Nokia Corporation + * + * 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 <asm/cacheflush.h> +#include <linux/clk.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/pagemap.h> +#include <linux/scatterlist.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <media/v4l2-dev.h> +#include <media/v4l2-ioctl.h> +#include <media/videobuf2-dma-contig.h> + +#include "ispvideo.h" +#include "isp.h" + + +/* ----------------------------------------------------------------------------- + * Helper functions + */ + +/* + * NOTE: When adding new media bus codes, always remember to add + * corresponding in-memory formats to the table below!!! + */ +static struct isp_format_info formats[] = { +	{ V4L2_MBUS_FMT_Y8_1X8, V4L2_MBUS_FMT_Y8_1X8, +	  V4L2_MBUS_FMT_Y8_1X8, V4L2_MBUS_FMT_Y8_1X8, +	  V4L2_PIX_FMT_GREY, 8, 1, }, +	{ V4L2_MBUS_FMT_Y10_1X10, V4L2_MBUS_FMT_Y10_1X10, +	  V4L2_MBUS_FMT_Y10_1X10, V4L2_MBUS_FMT_Y8_1X8, +	  V4L2_PIX_FMT_Y10, 10, 2, }, +	{ V4L2_MBUS_FMT_Y12_1X12, V4L2_MBUS_FMT_Y10_1X10, +	  V4L2_MBUS_FMT_Y12_1X12, V4L2_MBUS_FMT_Y8_1X8, +	  V4L2_PIX_FMT_Y12, 12, 2, }, +	{ V4L2_MBUS_FMT_SBGGR8_1X8, V4L2_MBUS_FMT_SBGGR8_1X8, +	  V4L2_MBUS_FMT_SBGGR8_1X8, V4L2_MBUS_FMT_SBGGR8_1X8, +	  V4L2_PIX_FMT_SBGGR8, 8, 1, }, +	{ V4L2_MBUS_FMT_SGBRG8_1X8, V4L2_MBUS_FMT_SGBRG8_1X8, +	  V4L2_MBUS_FMT_SGBRG8_1X8, V4L2_MBUS_FMT_SGBRG8_1X8, +	  V4L2_PIX_FMT_SGBRG8, 8, 1, }, +	{ V4L2_MBUS_FMT_SGRBG8_1X8, V4L2_MBUS_FMT_SGRBG8_1X8, +	  V4L2_MBUS_FMT_SGRBG8_1X8, V4L2_MBUS_FMT_SGRBG8_1X8, +	  V4L2_PIX_FMT_SGRBG8, 8, 1, }, +	{ V4L2_MBUS_FMT_SRGGB8_1X8, V4L2_MBUS_FMT_SRGGB8_1X8, +	  V4L2_MBUS_FMT_SRGGB8_1X8, V4L2_MBUS_FMT_SRGGB8_1X8, +	  V4L2_PIX_FMT_SRGGB8, 8, 1, }, +	{ V4L2_MBUS_FMT_SBGGR10_DPCM8_1X8, V4L2_MBUS_FMT_SBGGR10_DPCM8_1X8, +	  V4L2_MBUS_FMT_SBGGR10_1X10, 0, +	  V4L2_PIX_FMT_SBGGR10DPCM8, 8, 1, }, +	{ V4L2_MBUS_FMT_SGBRG10_DPCM8_1X8, V4L2_MBUS_FMT_SGBRG10_DPCM8_1X8, +	  V4L2_MBUS_FMT_SGBRG10_1X10, 0, +	  V4L2_PIX_FMT_SGBRG10DPCM8, 8, 1, }, +	{ V4L2_MBUS_FMT_SGRBG10_DPCM8_1X8, V4L2_MBUS_FMT_SGRBG10_DPCM8_1X8, +	  V4L2_MBUS_FMT_SGRBG10_1X10, 0, +	  V4L2_PIX_FMT_SGRBG10DPCM8, 8, 1, }, +	{ V4L2_MBUS_FMT_SRGGB10_DPCM8_1X8, V4L2_MBUS_FMT_SRGGB10_DPCM8_1X8, +	  V4L2_MBUS_FMT_SRGGB10_1X10, 0, +	  V4L2_PIX_FMT_SRGGB10DPCM8, 8, 1, }, +	{ V4L2_MBUS_FMT_SBGGR10_1X10, V4L2_MBUS_FMT_SBGGR10_1X10, +	  V4L2_MBUS_FMT_SBGGR10_1X10, V4L2_MBUS_FMT_SBGGR8_1X8, +	  V4L2_PIX_FMT_SBGGR10, 10, 2, }, +	{ V4L2_MBUS_FMT_SGBRG10_1X10, V4L2_MBUS_FMT_SGBRG10_1X10, +	  V4L2_MBUS_FMT_SGBRG10_1X10, V4L2_MBUS_FMT_SGBRG8_1X8, +	  V4L2_PIX_FMT_SGBRG10, 10, 2, }, +	{ V4L2_MBUS_FMT_SGRBG10_1X10, V4L2_MBUS_FMT_SGRBG10_1X10, +	  V4L2_MBUS_FMT_SGRBG10_1X10, V4L2_MBUS_FMT_SGRBG8_1X8, +	  V4L2_PIX_FMT_SGRBG10, 10, 2, }, +	{ V4L2_MBUS_FMT_SRGGB10_1X10, V4L2_MBUS_FMT_SRGGB10_1X10, +	  V4L2_MBUS_FMT_SRGGB10_1X10, V4L2_MBUS_FMT_SRGGB8_1X8, +	  V4L2_PIX_FMT_SRGGB10, 10, 2, }, +	{ V4L2_MBUS_FMT_SBGGR12_1X12, V4L2_MBUS_FMT_SBGGR10_1X10, +	  V4L2_MBUS_FMT_SBGGR12_1X12, V4L2_MBUS_FMT_SBGGR8_1X8, +	  V4L2_PIX_FMT_SBGGR12, 12, 2, }, +	{ V4L2_MBUS_FMT_SGBRG12_1X12, V4L2_MBUS_FMT_SGBRG10_1X10, +	  V4L2_MBUS_FMT_SGBRG12_1X12, V4L2_MBUS_FMT_SGBRG8_1X8, +	  V4L2_PIX_FMT_SGBRG12, 12, 2, }, +	{ V4L2_MBUS_FMT_SGRBG12_1X12, V4L2_MBUS_FMT_SGRBG10_1X10, +	  V4L2_MBUS_FMT_SGRBG12_1X12, V4L2_MBUS_FMT_SGRBG8_1X8, +	  V4L2_PIX_FMT_SGRBG12, 12, 2, }, +	{ V4L2_MBUS_FMT_SRGGB12_1X12, V4L2_MBUS_FMT_SRGGB10_1X10, +	  V4L2_MBUS_FMT_SRGGB12_1X12, V4L2_MBUS_FMT_SRGGB8_1X8, +	  V4L2_PIX_FMT_SRGGB12, 12, 2, }, +	{ V4L2_MBUS_FMT_UYVY8_1X16, V4L2_MBUS_FMT_UYVY8_1X16, +	  V4L2_MBUS_FMT_UYVY8_1X16, 0, +	  V4L2_PIX_FMT_UYVY, 16, 2, }, +	{ V4L2_MBUS_FMT_YUYV8_1X16, V4L2_MBUS_FMT_YUYV8_1X16, +	  V4L2_MBUS_FMT_YUYV8_1X16, 0, +	  V4L2_PIX_FMT_YUYV, 16, 2, }, +	{ V4L2_MBUS_FMT_UYVY8_2X8, V4L2_MBUS_FMT_UYVY8_2X8, +	  V4L2_MBUS_FMT_UYVY8_2X8, 0, +	  V4L2_PIX_FMT_UYVY, 8, 2, }, +	{ V4L2_MBUS_FMT_YUYV8_2X8, V4L2_MBUS_FMT_YUYV8_2X8, +	  V4L2_MBUS_FMT_YUYV8_2X8, 0, +	  V4L2_PIX_FMT_YUYV, 8, 2, }, +	/* Empty entry to catch the unsupported pixel code (0) used by the CCDC +	 * module and avoid NULL pointer dereferences. +	 */ +	{ 0, } +}; + +const struct isp_format_info * +omap3isp_video_format_info(enum v4l2_mbus_pixelcode code) +{ +	unsigned int i; + +	for (i = 0; i < ARRAY_SIZE(formats); ++i) { +		if (formats[i].code == code) +			return &formats[i]; +	} + +	return NULL; +} + +/* + * isp_video_mbus_to_pix - Convert v4l2_mbus_framefmt to v4l2_pix_format + * @video: ISP video instance + * @mbus: v4l2_mbus_framefmt format (input) + * @pix: v4l2_pix_format format (output) + * + * Fill the output pix structure with information from the input mbus format. + * The bytesperline and sizeimage fields are computed from the requested bytes + * per line value in the pix format and information from the video instance. + * + * Return the number of padding bytes at end of line. + */ +static unsigned int isp_video_mbus_to_pix(const struct isp_video *video, +					  const struct v4l2_mbus_framefmt *mbus, +					  struct v4l2_pix_format *pix) +{ +	unsigned int bpl = pix->bytesperline; +	unsigned int min_bpl; +	unsigned int i; + +	memset(pix, 0, sizeof(*pix)); +	pix->width = mbus->width; +	pix->height = mbus->height; + +	for (i = 0; i < ARRAY_SIZE(formats); ++i) { +		if (formats[i].code == mbus->code) +			break; +	} + +	if (WARN_ON(i == ARRAY_SIZE(formats))) +		return 0; + +	min_bpl = pix->width * formats[i].bpp; + +	/* Clamp the requested bytes per line value. If the maximum bytes per +	 * line value is zero, the module doesn't support user configurable line +	 * sizes. Override the requested value with the minimum in that case. +	 */ +	if (video->bpl_max) +		bpl = clamp(bpl, min_bpl, video->bpl_max); +	else +		bpl = min_bpl; + +	if (!video->bpl_zero_padding || bpl != min_bpl) +		bpl = ALIGN(bpl, video->bpl_alignment); + +	pix->pixelformat = formats[i].pixelformat; +	pix->bytesperline = bpl; +	pix->sizeimage = pix->bytesperline * pix->height; +	pix->colorspace = mbus->colorspace; +	pix->field = mbus->field; + +	return bpl - min_bpl; +} + +static void isp_video_pix_to_mbus(const struct v4l2_pix_format *pix, +				  struct v4l2_mbus_framefmt *mbus) +{ +	unsigned int i; + +	memset(mbus, 0, sizeof(*mbus)); +	mbus->width = pix->width; +	mbus->height = pix->height; + +	/* Skip the last format in the loop so that it will be selected if no +	 * match is found. +	 */ +	for (i = 0; i < ARRAY_SIZE(formats) - 1; ++i) { +		if (formats[i].pixelformat == pix->pixelformat) +			break; +	} + +	mbus->code = formats[i].code; +	mbus->colorspace = pix->colorspace; +	mbus->field = pix->field; +} + +static struct v4l2_subdev * +isp_video_remote_subdev(struct isp_video *video, u32 *pad) +{ +	struct media_pad *remote; + +	remote = media_entity_remote_pad(&video->pad); + +	if (remote == NULL || +	    media_entity_type(remote->entity) != MEDIA_ENT_T_V4L2_SUBDEV) +		return NULL; + +	if (pad) +		*pad = remote->index; + +	return media_entity_to_v4l2_subdev(remote->entity); +} + +/* Return a pointer to the ISP video instance at the far end of the pipeline. */ +static int isp_video_get_graph_data(struct isp_video *video, +				    struct isp_pipeline *pipe) +{ +	struct media_entity_graph graph; +	struct media_entity *entity = &video->video.entity; +	struct media_device *mdev = entity->parent; +	struct isp_video *far_end = NULL; + +	mutex_lock(&mdev->graph_mutex); +	media_entity_graph_walk_start(&graph, entity); + +	while ((entity = media_entity_graph_walk_next(&graph))) { +		struct isp_video *__video; + +		pipe->entities |= 1 << entity->id; + +		if (far_end != NULL) +			continue; + +		if (entity == &video->video.entity) +			continue; + +		if (media_entity_type(entity) != MEDIA_ENT_T_DEVNODE) +			continue; + +		__video = to_isp_video(media_entity_to_video_device(entity)); +		if (__video->type != video->type) +			far_end = __video; +	} + +	mutex_unlock(&mdev->graph_mutex); + +	if (video->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { +		pipe->input = far_end; +		pipe->output = video; +	} else { +		if (far_end == NULL) +			return -EPIPE; + +		pipe->input = video; +		pipe->output = far_end; +	} + +	return 0; +} + +static int +__isp_video_get_format(struct isp_video *video, struct v4l2_format *format) +{ +	struct v4l2_subdev_format fmt; +	struct v4l2_subdev *subdev; +	u32 pad; +	int ret; + +	subdev = isp_video_remote_subdev(video, &pad); +	if (subdev == NULL) +		return -EINVAL; + +	fmt.pad = pad; +	fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; + +	mutex_lock(&video->mutex); +	ret = v4l2_subdev_call(subdev, pad, get_fmt, NULL, &fmt); +	mutex_unlock(&video->mutex); + +	if (ret) +		return ret; + +	format->type = video->type; +	return isp_video_mbus_to_pix(video, &fmt.format, &format->fmt.pix); +} + +static int +isp_video_check_format(struct isp_video *video, struct isp_video_fh *vfh) +{ +	struct v4l2_format format; +	int ret; + +	memcpy(&format, &vfh->format, sizeof(format)); +	ret = __isp_video_get_format(video, &format); +	if (ret < 0) +		return ret; + +	if (vfh->format.fmt.pix.pixelformat != format.fmt.pix.pixelformat || +	    vfh->format.fmt.pix.height != format.fmt.pix.height || +	    vfh->format.fmt.pix.width != format.fmt.pix.width || +	    vfh->format.fmt.pix.bytesperline != format.fmt.pix.bytesperline || +	    vfh->format.fmt.pix.sizeimage != format.fmt.pix.sizeimage) +		return -EINVAL; + +	return ret; +} + +/* ----------------------------------------------------------------------------- + * Video queue operations + */ + +static int isp_video_queue_setup(struct vb2_queue *queue, +				 const struct v4l2_format *fmt, +				 unsigned int *count, unsigned int *num_planes, +				 unsigned int sizes[], void *alloc_ctxs[]) +{ +	struct isp_video_fh *vfh = vb2_get_drv_priv(queue); +	struct isp_video *video = vfh->video; + +	*num_planes = 1; + +	sizes[0] = vfh->format.fmt.pix.sizeimage; +	if (sizes[0] == 0) +		return -EINVAL; + +	alloc_ctxs[0] = video->alloc_ctx; + +	*count = min(*count, video->capture_mem / PAGE_ALIGN(sizes[0])); + +	return 0; +} + +static int isp_video_buffer_prepare(struct vb2_buffer *buf) +{ +	struct isp_video_fh *vfh = vb2_get_drv_priv(buf->vb2_queue); +	struct isp_buffer *buffer = to_isp_buffer(buf); +	struct isp_video *video = vfh->video; +	dma_addr_t addr; + +	/* Refuse to prepare the buffer is the video node has registered an +	 * error. We don't need to take any lock here as the operation is +	 * inherently racy. The authoritative check will be performed in the +	 * queue handler, which can't return an error, this check is just a best +	 * effort to notify userspace as early as possible. +	 */ +	if (unlikely(video->error)) +		return -EIO; + +	addr = vb2_dma_contig_plane_dma_addr(buf, 0); +	if (!IS_ALIGNED(addr, 32)) { +		dev_dbg(video->isp->dev, +			"Buffer address must be aligned to 32 bytes boundary.\n"); +		return -EINVAL; +	} + +	vb2_set_plane_payload(&buffer->vb, 0, vfh->format.fmt.pix.sizeimage); +	buffer->dma = addr; + +	return 0; +} + +/* + * isp_video_buffer_queue - Add buffer to streaming queue + * @buf: Video buffer + * + * In memory-to-memory mode, start streaming on the pipeline if buffers are + * queued on both the input and the output, if the pipeline isn't already busy. + * If the pipeline is busy, it will be restarted in the output module interrupt + * handler. + */ +static void isp_video_buffer_queue(struct vb2_buffer *buf) +{ +	struct isp_video_fh *vfh = vb2_get_drv_priv(buf->vb2_queue); +	struct isp_buffer *buffer = to_isp_buffer(buf); +	struct isp_video *video = vfh->video; +	struct isp_pipeline *pipe = to_isp_pipeline(&video->video.entity); +	enum isp_pipeline_state state; +	unsigned long flags; +	unsigned int empty; +	unsigned int start; + +	spin_lock_irqsave(&video->irqlock, flags); + +	if (unlikely(video->error)) { +		vb2_buffer_done(&buffer->vb, VB2_BUF_STATE_ERROR); +		spin_unlock_irqrestore(&video->irqlock, flags); +		return; +	} + +	empty = list_empty(&video->dmaqueue); +	list_add_tail(&buffer->irqlist, &video->dmaqueue); + +	spin_unlock_irqrestore(&video->irqlock, flags); + +	if (empty) { +		if (video->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) +			state = ISP_PIPELINE_QUEUE_OUTPUT; +		else +			state = ISP_PIPELINE_QUEUE_INPUT; + +		spin_lock_irqsave(&pipe->lock, flags); +		pipe->state |= state; +		video->ops->queue(video, buffer); +		video->dmaqueue_flags |= ISP_VIDEO_DMAQUEUE_QUEUED; + +		start = isp_pipeline_ready(pipe); +		if (start) +			pipe->state |= ISP_PIPELINE_STREAM; +		spin_unlock_irqrestore(&pipe->lock, flags); + +		if (start) +			omap3isp_pipeline_set_stream(pipe, +						ISP_PIPELINE_STREAM_SINGLESHOT); +	} +} + +static const struct vb2_ops isp_video_queue_ops = { +	.queue_setup = isp_video_queue_setup, +	.buf_prepare = isp_video_buffer_prepare, +	.buf_queue = isp_video_buffer_queue, +}; + +/* + * omap3isp_video_buffer_next - Complete the current buffer and return the next + * @video: ISP video object + * + * Remove the current video buffer from the DMA queue and fill its timestamp and + * field count before handing it back to videobuf2. + * + * For capture video nodes the buffer state is set to VB2_BUF_STATE_DONE if no + * error has been flagged in the pipeline, or to VB2_BUF_STATE_ERROR otherwise. + * For video output nodes the buffer state is always set to VB2_BUF_STATE_DONE. + * + * The DMA queue is expected to contain at least one buffer. + * + * Return a pointer to the next buffer in the DMA queue, or NULL if the queue is + * empty. + */ +struct isp_buffer *omap3isp_video_buffer_next(struct isp_video *video) +{ +	struct isp_pipeline *pipe = to_isp_pipeline(&video->video.entity); +	enum isp_pipeline_state state; +	struct isp_buffer *buf; +	unsigned long flags; +	struct timespec ts; + +	spin_lock_irqsave(&video->irqlock, flags); +	if (WARN_ON(list_empty(&video->dmaqueue))) { +		spin_unlock_irqrestore(&video->irqlock, flags); +		return NULL; +	} + +	buf = list_first_entry(&video->dmaqueue, struct isp_buffer, +			       irqlist); +	list_del(&buf->irqlist); +	spin_unlock_irqrestore(&video->irqlock, flags); + +	ktime_get_ts(&ts); +	buf->vb.v4l2_buf.timestamp.tv_sec = ts.tv_sec; +	buf->vb.v4l2_buf.timestamp.tv_usec = ts.tv_nsec / NSEC_PER_USEC; + +	/* Do frame number propagation only if this is the output video node. +	 * Frame number either comes from the CSI receivers or it gets +	 * incremented here if H3A is not active. +	 * Note: There is no guarantee that the output buffer will finish +	 * first, so the input number might lag behind by 1 in some cases. +	 */ +	if (video == pipe->output && !pipe->do_propagation) +		buf->vb.v4l2_buf.sequence = +			atomic_inc_return(&pipe->frame_number); +	else +		buf->vb.v4l2_buf.sequence = atomic_read(&pipe->frame_number); + +	/* Report pipeline errors to userspace on the capture device side. */ +	if (video->type == V4L2_BUF_TYPE_VIDEO_CAPTURE && pipe->error) { +		state = VB2_BUF_STATE_ERROR; +		pipe->error = false; +	} else { +		state = VB2_BUF_STATE_DONE; +	} + +	vb2_buffer_done(&buf->vb, state); + +	spin_lock_irqsave(&video->irqlock, flags); + +	if (list_empty(&video->dmaqueue)) { +		spin_unlock_irqrestore(&video->irqlock, flags); + +		if (video->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) +			state = ISP_PIPELINE_QUEUE_OUTPUT +			      | ISP_PIPELINE_STREAM; +		else +			state = ISP_PIPELINE_QUEUE_INPUT +			      | ISP_PIPELINE_STREAM; + +		spin_lock_irqsave(&pipe->lock, flags); +		pipe->state &= ~state; +		if (video->pipe.stream_state == ISP_PIPELINE_STREAM_CONTINUOUS) +			video->dmaqueue_flags |= ISP_VIDEO_DMAQUEUE_UNDERRUN; +		spin_unlock_irqrestore(&pipe->lock, flags); +		return NULL; +	} + +	if (video->type == V4L2_BUF_TYPE_VIDEO_CAPTURE && pipe->input != NULL) { +		spin_lock(&pipe->lock); +		pipe->state &= ~ISP_PIPELINE_STREAM; +		spin_unlock(&pipe->lock); +	} + +	buf = list_first_entry(&video->dmaqueue, struct isp_buffer, +			       irqlist); +	buf->vb.state = VB2_BUF_STATE_ACTIVE; + +	spin_unlock_irqrestore(&video->irqlock, flags); + +	return buf; +} + +/* + * omap3isp_video_cancel_stream - Cancel stream on a video node + * @video: ISP video object + * + * Cancelling a stream mark all buffers on the video node as erroneous and makes + * sure no new buffer can be queued. + */ +void omap3isp_video_cancel_stream(struct isp_video *video) +{ +	unsigned long flags; + +	spin_lock_irqsave(&video->irqlock, flags); + +	while (!list_empty(&video->dmaqueue)) { +		struct isp_buffer *buf; + +		buf = list_first_entry(&video->dmaqueue, +				       struct isp_buffer, irqlist); +		list_del(&buf->irqlist); +		vb2_buffer_done(&buf->vb, VB2_BUF_STATE_ERROR); +	} + +	video->error = true; + +	spin_unlock_irqrestore(&video->irqlock, flags); +} + +/* + * omap3isp_video_resume - Perform resume operation on the buffers + * @video: ISP video object + * @continuous: Pipeline is in single shot mode if 0 or continuous mode otherwise + * + * This function is intended to be used on suspend/resume scenario. It + * requests video queue layer to discard buffers marked as DONE if it's in + * continuous mode and requests ISP modules to queue again the ACTIVE buffer + * if there's any. + */ +void omap3isp_video_resume(struct isp_video *video, int continuous) +{ +	struct isp_buffer *buf = NULL; + +	if (continuous && video->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { +		mutex_lock(&video->queue_lock); +		vb2_discard_done(video->queue); +		mutex_unlock(&video->queue_lock); +	} + +	if (!list_empty(&video->dmaqueue)) { +		buf = list_first_entry(&video->dmaqueue, +				       struct isp_buffer, irqlist); +		video->ops->queue(video, buf); +		video->dmaqueue_flags |= ISP_VIDEO_DMAQUEUE_QUEUED; +	} else { +		if (continuous) +			video->dmaqueue_flags |= ISP_VIDEO_DMAQUEUE_UNDERRUN; +	} +} + +/* ----------------------------------------------------------------------------- + * V4L2 ioctls + */ + +static int +isp_video_querycap(struct file *file, void *fh, struct v4l2_capability *cap) +{ +	struct isp_video *video = video_drvdata(file); + +	strlcpy(cap->driver, ISP_VIDEO_DRIVER_NAME, sizeof(cap->driver)); +	strlcpy(cap->card, video->video.name, sizeof(cap->card)); +	strlcpy(cap->bus_info, "media", sizeof(cap->bus_info)); + +	if (video->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) +		cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING; +	else +		cap->capabilities = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING; + +	return 0; +} + +static int +isp_video_get_format(struct file *file, void *fh, struct v4l2_format *format) +{ +	struct isp_video_fh *vfh = to_isp_video_fh(fh); +	struct isp_video *video = video_drvdata(file); + +	if (format->type != video->type) +		return -EINVAL; + +	mutex_lock(&video->mutex); +	*format = vfh->format; +	mutex_unlock(&video->mutex); + +	return 0; +} + +static int +isp_video_set_format(struct file *file, void *fh, struct v4l2_format *format) +{ +	struct isp_video_fh *vfh = to_isp_video_fh(fh); +	struct isp_video *video = video_drvdata(file); +	struct v4l2_mbus_framefmt fmt; + +	if (format->type != video->type) +		return -EINVAL; + +	mutex_lock(&video->mutex); + +	/* Fill the bytesperline and sizeimage fields by converting to media bus +	 * format and back to pixel format. +	 */ +	isp_video_pix_to_mbus(&format->fmt.pix, &fmt); +	isp_video_mbus_to_pix(video, &fmt, &format->fmt.pix); + +	vfh->format = *format; + +	mutex_unlock(&video->mutex); +	return 0; +} + +static int +isp_video_try_format(struct file *file, void *fh, struct v4l2_format *format) +{ +	struct isp_video *video = video_drvdata(file); +	struct v4l2_subdev_format fmt; +	struct v4l2_subdev *subdev; +	u32 pad; +	int ret; + +	if (format->type != video->type) +		return -EINVAL; + +	subdev = isp_video_remote_subdev(video, &pad); +	if (subdev == NULL) +		return -EINVAL; + +	isp_video_pix_to_mbus(&format->fmt.pix, &fmt.format); + +	fmt.pad = pad; +	fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; +	ret = v4l2_subdev_call(subdev, pad, get_fmt, NULL, &fmt); +	if (ret) +		return ret == -ENOIOCTLCMD ? -ENOTTY : ret; + +	isp_video_mbus_to_pix(video, &fmt.format, &format->fmt.pix); +	return 0; +} + +static int +isp_video_cropcap(struct file *file, void *fh, struct v4l2_cropcap *cropcap) +{ +	struct isp_video *video = video_drvdata(file); +	struct v4l2_subdev *subdev; +	int ret; + +	subdev = isp_video_remote_subdev(video, NULL); +	if (subdev == NULL) +		return -EINVAL; + +	mutex_lock(&video->mutex); +	ret = v4l2_subdev_call(subdev, video, cropcap, cropcap); +	mutex_unlock(&video->mutex); + +	return ret == -ENOIOCTLCMD ? -ENOTTY : ret; +} + +static int +isp_video_get_crop(struct file *file, void *fh, struct v4l2_crop *crop) +{ +	struct isp_video *video = video_drvdata(file); +	struct v4l2_subdev_format format; +	struct v4l2_subdev *subdev; +	u32 pad; +	int ret; + +	subdev = isp_video_remote_subdev(video, &pad); +	if (subdev == NULL) +		return -EINVAL; + +	/* Try the get crop operation first and fallback to get format if not +	 * implemented. +	 */ +	ret = v4l2_subdev_call(subdev, video, g_crop, crop); +	if (ret != -ENOIOCTLCMD) +		return ret; + +	format.pad = pad; +	format.which = V4L2_SUBDEV_FORMAT_ACTIVE; +	ret = v4l2_subdev_call(subdev, pad, get_fmt, NULL, &format); +	if (ret < 0) +		return ret == -ENOIOCTLCMD ? -ENOTTY : ret; + +	crop->c.left = 0; +	crop->c.top = 0; +	crop->c.width = format.format.width; +	crop->c.height = format.format.height; + +	return 0; +} + +static int +isp_video_set_crop(struct file *file, void *fh, const struct v4l2_crop *crop) +{ +	struct isp_video *video = video_drvdata(file); +	struct v4l2_subdev *subdev; +	int ret; + +	subdev = isp_video_remote_subdev(video, NULL); +	if (subdev == NULL) +		return -EINVAL; + +	mutex_lock(&video->mutex); +	ret = v4l2_subdev_call(subdev, video, s_crop, crop); +	mutex_unlock(&video->mutex); + +	return ret == -ENOIOCTLCMD ? -ENOTTY : ret; +} + +static int +isp_video_get_param(struct file *file, void *fh, struct v4l2_streamparm *a) +{ +	struct isp_video_fh *vfh = to_isp_video_fh(fh); +	struct isp_video *video = video_drvdata(file); + +	if (video->type != V4L2_BUF_TYPE_VIDEO_OUTPUT || +	    video->type != a->type) +		return -EINVAL; + +	memset(a, 0, sizeof(*a)); +	a->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; +	a->parm.output.capability = V4L2_CAP_TIMEPERFRAME; +	a->parm.output.timeperframe = vfh->timeperframe; + +	return 0; +} + +static int +isp_video_set_param(struct file *file, void *fh, struct v4l2_streamparm *a) +{ +	struct isp_video_fh *vfh = to_isp_video_fh(fh); +	struct isp_video *video = video_drvdata(file); + +	if (video->type != V4L2_BUF_TYPE_VIDEO_OUTPUT || +	    video->type != a->type) +		return -EINVAL; + +	if (a->parm.output.timeperframe.denominator == 0) +		a->parm.output.timeperframe.denominator = 1; + +	vfh->timeperframe = a->parm.output.timeperframe; + +	return 0; +} + +static int +isp_video_reqbufs(struct file *file, void *fh, struct v4l2_requestbuffers *rb) +{ +	struct isp_video_fh *vfh = to_isp_video_fh(fh); +	struct isp_video *video = video_drvdata(file); +	int ret; + +	mutex_lock(&video->queue_lock); +	ret = vb2_reqbufs(&vfh->queue, rb); +	mutex_unlock(&video->queue_lock); + +	return ret; +} + +static int +isp_video_querybuf(struct file *file, void *fh, struct v4l2_buffer *b) +{ +	struct isp_video_fh *vfh = to_isp_video_fh(fh); +	struct isp_video *video = video_drvdata(file); +	int ret; + +	mutex_lock(&video->queue_lock); +	ret = vb2_querybuf(&vfh->queue, b); +	mutex_unlock(&video->queue_lock); + +	return ret; +} + +static int +isp_video_qbuf(struct file *file, void *fh, struct v4l2_buffer *b) +{ +	struct isp_video_fh *vfh = to_isp_video_fh(fh); +	struct isp_video *video = video_drvdata(file); +	int ret; + +	mutex_lock(&video->queue_lock); +	ret = vb2_qbuf(&vfh->queue, b); +	mutex_unlock(&video->queue_lock); + +	return ret; +} + +static int +isp_video_dqbuf(struct file *file, void *fh, struct v4l2_buffer *b) +{ +	struct isp_video_fh *vfh = to_isp_video_fh(fh); +	struct isp_video *video = video_drvdata(file); +	int ret; + +	mutex_lock(&video->queue_lock); +	ret = vb2_dqbuf(&vfh->queue, b, file->f_flags & O_NONBLOCK); +	mutex_unlock(&video->queue_lock); + +	return ret; +} + +static int isp_video_check_external_subdevs(struct isp_video *video, +					    struct isp_pipeline *pipe) +{ +	struct isp_device *isp = video->isp; +	struct media_entity *ents[] = { +		&isp->isp_csi2a.subdev.entity, +		&isp->isp_csi2c.subdev.entity, +		&isp->isp_ccp2.subdev.entity, +		&isp->isp_ccdc.subdev.entity +	}; +	struct media_pad *source_pad; +	struct media_entity *source = NULL; +	struct media_entity *sink; +	struct v4l2_subdev_format fmt; +	struct v4l2_ext_controls ctrls; +	struct v4l2_ext_control ctrl; +	unsigned int i; +	int ret; + +	/* Memory-to-memory pipelines have no external subdev. */ +	if (pipe->input != NULL) +		return 0; + +	for (i = 0; i < ARRAY_SIZE(ents); i++) { +		/* Is the entity part of the pipeline? */ +		if (!(pipe->entities & (1 << ents[i]->id))) +			continue; + +		/* ISP entities have always sink pad == 0. Find source. */ +		source_pad = media_entity_remote_pad(&ents[i]->pads[0]); +		if (source_pad == NULL) +			continue; + +		source = source_pad->entity; +		sink = ents[i]; +		break; +	} + +	if (!source) { +		dev_warn(isp->dev, "can't find source, failing now\n"); +		return -EINVAL; +	} + +	if (media_entity_type(source) != MEDIA_ENT_T_V4L2_SUBDEV) +		return 0; + +	pipe->external = media_entity_to_v4l2_subdev(source); + +	fmt.pad = source_pad->index; +	fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; +	ret = v4l2_subdev_call(media_entity_to_v4l2_subdev(sink), +			       pad, get_fmt, NULL, &fmt); +	if (unlikely(ret < 0)) { +		dev_warn(isp->dev, "get_fmt returned null!\n"); +		return ret; +	} + +	pipe->external_width = +		omap3isp_video_format_info(fmt.format.code)->width; + +	memset(&ctrls, 0, sizeof(ctrls)); +	memset(&ctrl, 0, sizeof(ctrl)); + +	ctrl.id = V4L2_CID_PIXEL_RATE; + +	ctrls.count = 1; +	ctrls.controls = &ctrl; + +	ret = v4l2_g_ext_ctrls(pipe->external->ctrl_handler, &ctrls); +	if (ret < 0) { +		dev_warn(isp->dev, "no pixel rate control in subdev %s\n", +			 pipe->external->name); +		return ret; +	} + +	pipe->external_rate = ctrl.value64; + +	if (pipe->entities & (1 << isp->isp_ccdc.subdev.entity.id)) { +		unsigned int rate = UINT_MAX; +		/* +		 * Check that maximum allowed CCDC pixel rate isn't +		 * exceeded by the pixel rate. +		 */ +		omap3isp_ccdc_max_rate(&isp->isp_ccdc, &rate); +		if (pipe->external_rate > rate) +			return -ENOSPC; +	} + +	return 0; +} + +/* + * Stream management + * + * Every ISP pipeline has a single input and a single output. The input can be + * either a sensor or a video node. The output is always a video node. + * + * As every pipeline has an output video node, the ISP video objects at the + * pipeline output stores the pipeline state. It tracks the streaming state of + * both the input and output, as well as the availability of buffers. + * + * In sensor-to-memory mode, frames are always available at the pipeline input. + * Starting the sensor usually requires I2C transfers and must be done in + * interruptible context. The pipeline is started and stopped synchronously + * to the stream on/off commands. All modules in the pipeline will get their + * subdev set stream handler called. The module at the end of the pipeline must + * delay starting the hardware until buffers are available at its output. + * + * In memory-to-memory mode, starting/stopping the stream requires + * synchronization between the input and output. ISP modules can't be stopped + * in the middle of a frame, and at least some of the modules seem to become + * busy as soon as they're started, even if they don't receive a frame start + * event. For that reason frames need to be processed in single-shot mode. The + * driver needs to wait until a frame is completely processed and written to + * memory before restarting the pipeline for the next frame. Pipelined + * processing might be possible but requires more testing. + * + * Stream start must be delayed until buffers are available at both the input + * and output. The pipeline must be started in the videobuf queue callback with + * the buffers queue spinlock held. The modules subdev set stream operation must + * not sleep. + */ +static int +isp_video_streamon(struct file *file, void *fh, enum v4l2_buf_type type) +{ +	struct isp_video_fh *vfh = to_isp_video_fh(fh); +	struct isp_video *video = video_drvdata(file); +	enum isp_pipeline_state state; +	struct isp_pipeline *pipe; +	unsigned long flags; +	int ret; + +	if (type != video->type) +		return -EINVAL; + +	mutex_lock(&video->stream_lock); + +	/* Start streaming on the pipeline. No link touching an entity in the +	 * pipeline can be activated or deactivated once streaming is started. +	 */ +	pipe = video->video.entity.pipe +	     ? to_isp_pipeline(&video->video.entity) : &video->pipe; + +	pipe->entities = 0; + +	if (video->isp->pdata->set_constraints) +		video->isp->pdata->set_constraints(video->isp, true); +	pipe->l3_ick = clk_get_rate(video->isp->clock[ISP_CLK_L3_ICK]); +	pipe->max_rate = pipe->l3_ick; + +	ret = media_entity_pipeline_start(&video->video.entity, &pipe->pipe); +	if (ret < 0) +		goto err_pipeline_start; + +	/* Verify that the currently configured format matches the output of +	 * the connected subdev. +	 */ +	ret = isp_video_check_format(video, vfh); +	if (ret < 0) +		goto err_check_format; + +	video->bpl_padding = ret; +	video->bpl_value = vfh->format.fmt.pix.bytesperline; + +	ret = isp_video_get_graph_data(video, pipe); +	if (ret < 0) +		goto err_check_format; + +	if (video->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) +		state = ISP_PIPELINE_STREAM_OUTPUT | ISP_PIPELINE_IDLE_OUTPUT; +	else +		state = ISP_PIPELINE_STREAM_INPUT | ISP_PIPELINE_IDLE_INPUT; + +	ret = isp_video_check_external_subdevs(video, pipe); +	if (ret < 0) +		goto err_check_format; + +	pipe->error = false; + +	spin_lock_irqsave(&pipe->lock, flags); +	pipe->state &= ~ISP_PIPELINE_STREAM; +	pipe->state |= state; +	spin_unlock_irqrestore(&pipe->lock, flags); + +	/* Set the maximum time per frame as the value requested by userspace. +	 * This is a soft limit that can be overridden if the hardware doesn't +	 * support the request limit. +	 */ +	if (video->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) +		pipe->max_timeperframe = vfh->timeperframe; + +	video->queue = &vfh->queue; +	INIT_LIST_HEAD(&video->dmaqueue); +	atomic_set(&pipe->frame_number, -1); + +	mutex_lock(&video->queue_lock); +	ret = vb2_streamon(&vfh->queue, type); +	mutex_unlock(&video->queue_lock); +	if (ret < 0) +		goto err_check_format; + +	/* In sensor-to-memory mode, the stream can be started synchronously +	 * to the stream on command. In memory-to-memory mode, it will be +	 * started when buffers are queued on both the input and output. +	 */ +	if (pipe->input == NULL) { +		ret = omap3isp_pipeline_set_stream(pipe, +					      ISP_PIPELINE_STREAM_CONTINUOUS); +		if (ret < 0) +			goto err_set_stream; +		spin_lock_irqsave(&video->irqlock, flags); +		if (list_empty(&video->dmaqueue)) +			video->dmaqueue_flags |= ISP_VIDEO_DMAQUEUE_UNDERRUN; +		spin_unlock_irqrestore(&video->irqlock, flags); +	} + +	mutex_unlock(&video->stream_lock); +	return 0; + +err_set_stream: +	mutex_lock(&video->queue_lock); +	vb2_streamoff(&vfh->queue, type); +	mutex_unlock(&video->queue_lock); +err_check_format: +	media_entity_pipeline_stop(&video->video.entity); +err_pipeline_start: +	if (video->isp->pdata->set_constraints) +		video->isp->pdata->set_constraints(video->isp, false); +	/* The DMA queue must be emptied here, otherwise CCDC interrupts that +	 * will get triggered the next time the CCDC is powered up will try to +	 * access buffers that might have been freed but still present in the +	 * DMA queue. This can easily get triggered if the above +	 * omap3isp_pipeline_set_stream() call fails on a system with a +	 * free-running sensor. +	 */ +	INIT_LIST_HEAD(&video->dmaqueue); +	video->queue = NULL; + +	mutex_unlock(&video->stream_lock); +	return ret; +} + +static int +isp_video_streamoff(struct file *file, void *fh, enum v4l2_buf_type type) +{ +	struct isp_video_fh *vfh = to_isp_video_fh(fh); +	struct isp_video *video = video_drvdata(file); +	struct isp_pipeline *pipe = to_isp_pipeline(&video->video.entity); +	enum isp_pipeline_state state; +	unsigned int streaming; +	unsigned long flags; + +	if (type != video->type) +		return -EINVAL; + +	mutex_lock(&video->stream_lock); + +	/* Make sure we're not streaming yet. */ +	mutex_lock(&video->queue_lock); +	streaming = vb2_is_streaming(&vfh->queue); +	mutex_unlock(&video->queue_lock); + +	if (!streaming) +		goto done; + +	/* Update the pipeline state. */ +	if (video->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) +		state = ISP_PIPELINE_STREAM_OUTPUT +		      | ISP_PIPELINE_QUEUE_OUTPUT; +	else +		state = ISP_PIPELINE_STREAM_INPUT +		      | ISP_PIPELINE_QUEUE_INPUT; + +	spin_lock_irqsave(&pipe->lock, flags); +	pipe->state &= ~state; +	spin_unlock_irqrestore(&pipe->lock, flags); + +	/* Stop the stream. */ +	omap3isp_pipeline_set_stream(pipe, ISP_PIPELINE_STREAM_STOPPED); +	omap3isp_video_cancel_stream(video); + +	mutex_lock(&video->queue_lock); +	vb2_streamoff(&vfh->queue, type); +	mutex_unlock(&video->queue_lock); +	video->queue = NULL; +	video->error = false; + +	if (video->isp->pdata->set_constraints) +		video->isp->pdata->set_constraints(video->isp, false); +	media_entity_pipeline_stop(&video->video.entity); + +done: +	mutex_unlock(&video->stream_lock); +	return 0; +} + +static int +isp_video_enum_input(struct file *file, void *fh, struct v4l2_input *input) +{ +	if (input->index > 0) +		return -EINVAL; + +	strlcpy(input->name, "camera", sizeof(input->name)); +	input->type = V4L2_INPUT_TYPE_CAMERA; + +	return 0; +} + +static int +isp_video_g_input(struct file *file, void *fh, unsigned int *input) +{ +	*input = 0; + +	return 0; +} + +static int +isp_video_s_input(struct file *file, void *fh, unsigned int input) +{ +	return input == 0 ? 0 : -EINVAL; +} + +static const struct v4l2_ioctl_ops isp_video_ioctl_ops = { +	.vidioc_querycap		= isp_video_querycap, +	.vidioc_g_fmt_vid_cap		= isp_video_get_format, +	.vidioc_s_fmt_vid_cap		= isp_video_set_format, +	.vidioc_try_fmt_vid_cap		= isp_video_try_format, +	.vidioc_g_fmt_vid_out		= isp_video_get_format, +	.vidioc_s_fmt_vid_out		= isp_video_set_format, +	.vidioc_try_fmt_vid_out		= isp_video_try_format, +	.vidioc_cropcap			= isp_video_cropcap, +	.vidioc_g_crop			= isp_video_get_crop, +	.vidioc_s_crop			= isp_video_set_crop, +	.vidioc_g_parm			= isp_video_get_param, +	.vidioc_s_parm			= isp_video_set_param, +	.vidioc_reqbufs			= isp_video_reqbufs, +	.vidioc_querybuf		= isp_video_querybuf, +	.vidioc_qbuf			= isp_video_qbuf, +	.vidioc_dqbuf			= isp_video_dqbuf, +	.vidioc_streamon		= isp_video_streamon, +	.vidioc_streamoff		= isp_video_streamoff, +	.vidioc_enum_input		= isp_video_enum_input, +	.vidioc_g_input			= isp_video_g_input, +	.vidioc_s_input			= isp_video_s_input, +}; + +/* ----------------------------------------------------------------------------- + * V4L2 file operations + */ + +static int isp_video_open(struct file *file) +{ +	struct isp_video *video = video_drvdata(file); +	struct isp_video_fh *handle; +	struct vb2_queue *queue; +	int ret = 0; + +	handle = kzalloc(sizeof(*handle), GFP_KERNEL); +	if (handle == NULL) +		return -ENOMEM; + +	v4l2_fh_init(&handle->vfh, &video->video); +	v4l2_fh_add(&handle->vfh); + +	/* If this is the first user, initialise the pipeline. */ +	if (omap3isp_get(video->isp) == NULL) { +		ret = -EBUSY; +		goto done; +	} + +	ret = omap3isp_pipeline_pm_use(&video->video.entity, 1); +	if (ret < 0) { +		omap3isp_put(video->isp); +		goto done; +	} + +	queue = &handle->queue; +	queue->type = video->type; +	queue->io_modes = VB2_MMAP | VB2_USERPTR; +	queue->drv_priv = handle; +	queue->ops = &isp_video_queue_ops; +	queue->mem_ops = &vb2_dma_contig_memops; +	queue->buf_struct_size = sizeof(struct isp_buffer); +	queue->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + +	ret = vb2_queue_init(&handle->queue); +	if (ret < 0) { +		omap3isp_put(video->isp); +		goto done; +	} + +	memset(&handle->format, 0, sizeof(handle->format)); +	handle->format.type = video->type; +	handle->timeperframe.denominator = 1; + +	handle->video = video; +	file->private_data = &handle->vfh; + +done: +	if (ret < 0) { +		v4l2_fh_del(&handle->vfh); +		kfree(handle); +	} + +	return ret; +} + +static int isp_video_release(struct file *file) +{ +	struct isp_video *video = video_drvdata(file); +	struct v4l2_fh *vfh = file->private_data; +	struct isp_video_fh *handle = to_isp_video_fh(vfh); + +	/* Disable streaming and free the buffers queue resources. */ +	isp_video_streamoff(file, vfh, video->type); + +	mutex_lock(&video->queue_lock); +	vb2_queue_release(&handle->queue); +	mutex_unlock(&video->queue_lock); + +	omap3isp_pipeline_pm_use(&video->video.entity, 0); + +	/* Release the file handle. */ +	v4l2_fh_del(vfh); +	kfree(handle); +	file->private_data = NULL; + +	omap3isp_put(video->isp); + +	return 0; +} + +static unsigned int isp_video_poll(struct file *file, poll_table *wait) +{ +	struct isp_video_fh *vfh = to_isp_video_fh(file->private_data); +	struct isp_video *video = video_drvdata(file); +	int ret; + +	mutex_lock(&video->queue_lock); +	ret = vb2_poll(&vfh->queue, file, wait); +	mutex_unlock(&video->queue_lock); + +	return ret; +} + +static int isp_video_mmap(struct file *file, struct vm_area_struct *vma) +{ +	struct isp_video_fh *vfh = to_isp_video_fh(file->private_data); +	struct isp_video *video = video_drvdata(file); +	int ret; + +	mutex_lock(&video->queue_lock); +	ret = vb2_mmap(&vfh->queue, vma); +	mutex_unlock(&video->queue_lock); + +	return ret; +} + +static struct v4l2_file_operations isp_video_fops = { +	.owner = THIS_MODULE, +	.unlocked_ioctl = video_ioctl2, +	.open = isp_video_open, +	.release = isp_video_release, +	.poll = isp_video_poll, +	.mmap = isp_video_mmap, +}; + +/* ----------------------------------------------------------------------------- + * ISP video core + */ + +static const struct isp_video_operations isp_video_dummy_ops = { +}; + +int omap3isp_video_init(struct isp_video *video, const char *name) +{ +	const char *direction; +	int ret; + +	switch (video->type) { +	case V4L2_BUF_TYPE_VIDEO_CAPTURE: +		direction = "output"; +		video->pad.flags = MEDIA_PAD_FL_SINK +				   | MEDIA_PAD_FL_MUST_CONNECT; +		break; +	case V4L2_BUF_TYPE_VIDEO_OUTPUT: +		direction = "input"; +		video->pad.flags = MEDIA_PAD_FL_SOURCE +				   | MEDIA_PAD_FL_MUST_CONNECT; +		video->video.vfl_dir = VFL_DIR_TX; +		break; + +	default: +		return -EINVAL; +	} + +	video->alloc_ctx = vb2_dma_contig_init_ctx(video->isp->dev); +	if (IS_ERR(video->alloc_ctx)) +		return PTR_ERR(video->alloc_ctx); + +	ret = media_entity_init(&video->video.entity, 1, &video->pad, 0); +	if (ret < 0) { +		vb2_dma_contig_cleanup_ctx(video->alloc_ctx); +		return ret; +	} + +	mutex_init(&video->mutex); +	atomic_set(&video->active, 0); + +	spin_lock_init(&video->pipe.lock); +	mutex_init(&video->stream_lock); +	mutex_init(&video->queue_lock); +	spin_lock_init(&video->irqlock); + +	/* Initialize the video device. */ +	if (video->ops == NULL) +		video->ops = &isp_video_dummy_ops; + +	video->video.fops = &isp_video_fops; +	snprintf(video->video.name, sizeof(video->video.name), +		 "OMAP3 ISP %s %s", name, direction); +	video->video.vfl_type = VFL_TYPE_GRABBER; +	video->video.release = video_device_release_empty; +	video->video.ioctl_ops = &isp_video_ioctl_ops; +	video->pipe.stream_state = ISP_PIPELINE_STREAM_STOPPED; + +	video_set_drvdata(&video->video, video); + +	return 0; +} + +void omap3isp_video_cleanup(struct isp_video *video) +{ +	vb2_dma_contig_cleanup_ctx(video->alloc_ctx); +	media_entity_cleanup(&video->video.entity); +	mutex_destroy(&video->queue_lock); +	mutex_destroy(&video->stream_lock); +	mutex_destroy(&video->mutex); +} + +int omap3isp_video_register(struct isp_video *video, struct v4l2_device *vdev) +{ +	int ret; + +	video->video.v4l2_dev = vdev; + +	ret = video_register_device(&video->video, VFL_TYPE_GRABBER, -1); +	if (ret < 0) +		dev_err(video->isp->dev, +			"%s: could not register video device (%d)\n", +			__func__, ret); + +	return ret; +} + +void omap3isp_video_unregister(struct isp_video *video) +{ +	if (video_is_registered(&video->video)) +		video_unregister_device(&video->video); +} diff --git a/drivers/media/platform/omap3isp/ispvideo.h b/drivers/media/platform/omap3isp/ispvideo.h new file mode 100644 index 00000000000..7d2e82122ec --- /dev/null +++ b/drivers/media/platform/omap3isp/ispvideo.h @@ -0,0 +1,219 @@ +/* + * ispvideo.h + * + * TI OMAP3 ISP - Generic video node + * + * Copyright (C) 2009-2010 Nokia Corporation + * + * 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 + */ + +#ifndef OMAP3_ISP_VIDEO_H +#define OMAP3_ISP_VIDEO_H + +#include <linux/v4l2-mediabus.h> +#include <media/media-entity.h> +#include <media/v4l2-dev.h> +#include <media/v4l2-fh.h> +#include <media/videobuf2-core.h> + +#define ISP_VIDEO_DRIVER_NAME		"ispvideo" +#define ISP_VIDEO_DRIVER_VERSION	"0.0.2" + +struct isp_device; +struct isp_video; +struct v4l2_mbus_framefmt; +struct v4l2_pix_format; + +/* + * struct isp_format_info - ISP media bus format information + * @code: V4L2 media bus format code + * @truncated: V4L2 media bus format code for the same format truncated to 10 + *	bits. Identical to @code if the format is 10 bits wide or less. + * @uncompressed: V4L2 media bus format code for the corresponding uncompressed + *	format. Identical to @code if the format is not DPCM compressed. + * @flavor: V4L2 media bus format code for the same pixel layout but + *	shifted to be 8 bits per pixel. =0 if format is not shiftable. + * @pixelformat: V4L2 pixel format FCC identifier + * @width: Bits per pixel (when transferred over a bus) + * @bpp: Bytes per pixel (when stored in memory) + */ +struct isp_format_info { +	enum v4l2_mbus_pixelcode code; +	enum v4l2_mbus_pixelcode truncated; +	enum v4l2_mbus_pixelcode uncompressed; +	enum v4l2_mbus_pixelcode flavor; +	u32 pixelformat; +	unsigned int width; +	unsigned int bpp; +}; + +enum isp_pipeline_stream_state { +	ISP_PIPELINE_STREAM_STOPPED = 0, +	ISP_PIPELINE_STREAM_CONTINUOUS = 1, +	ISP_PIPELINE_STREAM_SINGLESHOT = 2, +}; + +enum isp_pipeline_state { +	/* The stream has been started on the input video node. */ +	ISP_PIPELINE_STREAM_INPUT = 1, +	/* The stream has been started on the output video node. */ +	ISP_PIPELINE_STREAM_OUTPUT = 2, +	/* At least one buffer is queued on the input video node. */ +	ISP_PIPELINE_QUEUE_INPUT = 4, +	/* At least one buffer is queued on the output video node. */ +	ISP_PIPELINE_QUEUE_OUTPUT = 8, +	/* The input entity is idle, ready to be started. */ +	ISP_PIPELINE_IDLE_INPUT = 16, +	/* The output entity is idle, ready to be started. */ +	ISP_PIPELINE_IDLE_OUTPUT = 32, +	/* The pipeline is currently streaming. */ +	ISP_PIPELINE_STREAM = 64, +}; + +/* + * struct isp_pipeline - An ISP hardware pipeline + * @error: A hardware error occurred during capture + * @entities: Bitmask of entities in the pipeline (indexed by entity ID) + */ +struct isp_pipeline { +	struct media_pipeline pipe; +	spinlock_t lock;		/* Pipeline state and queue flags */ +	unsigned int state; +	enum isp_pipeline_stream_state stream_state; +	struct isp_video *input; +	struct isp_video *output; +	u32 entities; +	unsigned long l3_ick; +	unsigned int max_rate; +	atomic_t frame_number; +	bool do_propagation; /* of frame number */ +	bool error; +	struct v4l2_fract max_timeperframe; +	struct v4l2_subdev *external; +	unsigned int external_rate; +	unsigned int external_width; +}; + +#define to_isp_pipeline(__e) \ +	container_of((__e)->pipe, struct isp_pipeline, pipe) + +static inline int isp_pipeline_ready(struct isp_pipeline *pipe) +{ +	return pipe->state == (ISP_PIPELINE_STREAM_INPUT | +			       ISP_PIPELINE_STREAM_OUTPUT | +			       ISP_PIPELINE_QUEUE_INPUT | +			       ISP_PIPELINE_QUEUE_OUTPUT | +			       ISP_PIPELINE_IDLE_INPUT | +			       ISP_PIPELINE_IDLE_OUTPUT); +} + +/** + * struct isp_buffer - ISP video buffer + * @vb: videobuf2 buffer + * @irqlist: List head for insertion into IRQ queue + * @dma: DMA address + */ +struct isp_buffer { +	struct vb2_buffer vb; +	struct list_head irqlist; +	dma_addr_t dma; +}; + +#define to_isp_buffer(buf)	container_of(buf, struct isp_buffer, vb) + +enum isp_video_dmaqueue_flags { +	/* Set if DMA queue becomes empty when ISP_PIPELINE_STREAM_CONTINUOUS */ +	ISP_VIDEO_DMAQUEUE_UNDERRUN = (1 << 0), +	/* Set when queuing buffer to an empty DMA queue */ +	ISP_VIDEO_DMAQUEUE_QUEUED = (1 << 1), +}; + +#define isp_video_dmaqueue_flags_clr(video)	\ +			({ (video)->dmaqueue_flags = 0; }) + +/* + * struct isp_video_operations - ISP video operations + * @queue:	Resume streaming when a buffer is queued. Called on VIDIOC_QBUF + *		if there was no buffer previously queued. + */ +struct isp_video_operations { +	int(*queue)(struct isp_video *video, struct isp_buffer *buffer); +}; + +struct isp_video { +	struct video_device video; +	enum v4l2_buf_type type; +	struct media_pad pad; + +	struct mutex mutex;		/* format and crop settings */ +	atomic_t active; + +	struct isp_device *isp; + +	unsigned int capture_mem; +	unsigned int bpl_alignment;	/* alignment value */ +	unsigned int bpl_zero_padding;	/* whether the alignment is optional */ +	unsigned int bpl_max;		/* maximum bytes per line value */ +	unsigned int bpl_value;		/* bytes per line value */ +	unsigned int bpl_padding;	/* padding at end of line */ + +	/* Pipeline state */ +	struct isp_pipeline pipe; +	struct mutex stream_lock;	/* pipeline and stream states */ +	bool error; + +	/* Video buffers queue */ +	void *alloc_ctx; +	struct vb2_queue *queue; +	struct mutex queue_lock;	/* protects the queue */ +	spinlock_t irqlock;		/* protects dmaqueue */ +	struct list_head dmaqueue; +	enum isp_video_dmaqueue_flags dmaqueue_flags; + +	const struct isp_video_operations *ops; +}; + +#define to_isp_video(vdev)	container_of(vdev, struct isp_video, video) + +struct isp_video_fh { +	struct v4l2_fh vfh; +	struct isp_video *video; +	struct vb2_queue queue; +	struct v4l2_format format; +	struct v4l2_fract timeperframe; +}; + +#define to_isp_video_fh(fh)	container_of(fh, struct isp_video_fh, vfh) +#define isp_video_queue_to_isp_video_fh(q) \ +				container_of(q, struct isp_video_fh, queue) + +int omap3isp_video_init(struct isp_video *video, const char *name); +void omap3isp_video_cleanup(struct isp_video *video); +int omap3isp_video_register(struct isp_video *video, +			    struct v4l2_device *vdev); +void omap3isp_video_unregister(struct isp_video *video); +struct isp_buffer *omap3isp_video_buffer_next(struct isp_video *video); +void omap3isp_video_cancel_stream(struct isp_video *video); +void omap3isp_video_resume(struct isp_video *video, int continuous); +struct media_pad *omap3isp_video_remote_pad(struct isp_video *video); + +const struct isp_format_info * +omap3isp_video_format_info(enum v4l2_mbus_pixelcode code); + +#endif /* OMAP3_ISP_VIDEO_H */ diff --git a/drivers/media/platform/omap3isp/luma_enhance_table.h b/drivers/media/platform/omap3isp/luma_enhance_table.h new file mode 100644 index 00000000000..098b45e2280 --- /dev/null +++ b/drivers/media/platform/omap3isp/luma_enhance_table.h @@ -0,0 +1,42 @@ +/* + * luma_enhance_table.h + * + * TI OMAP3 ISP - Luminance enhancement table + * + * Copyright (C) 2010 Nokia Corporation + * Copyright (C) 2009 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 + */ + +1047552, 1047552, 1047552, 1047552, 1047552, 1047552, 1047552, 1047552, +1047552, 1047552, 1047552, 1047552, 1047552, 1047552, 1047552, 1047552, +1047552, 1047552, 1047552, 1047552, 1047552, 1047552, 1047552, 1047552, +1047552, 1047552, 1047552, 1047552, 1048575, 1047551, 1046527, 1045503, +1044479, 1043455, 1042431, 1041407, 1040383, 1039359, 1038335, 1037311, +1036287, 1035263, 1034239, 1033215, 1032191, 1031167, 1030143, 1028096, +1028096, 1028096, 1028096, 1028096, 1028096, 1028096, 1028096, 1028096, +1028096, 1028100, 1032196, 1036292, 1040388, 1044484,       0,       0, +      0,       5,    5125,   10245,   15365,   20485,   25605,   30720, +  30720,   30720,   30720,   30720,   30720,   30720,   30720,   30720, +  30720,   30720,   31743,   30719,   29695,   28671,   27647,   26623, +  25599,   24575,   23551,   22527,   21503,   20479,   19455,   18431, +  17407,   16383,   15359,   14335,   13311,   12287,   11263,   10239, +   9215,    8191,    7167,    6143,    5119,    4095,    3071,    1024, +   1024,    1024,    1024,    1024,    1024,    1024,    1024,    1024, +   1024,    1024,    1024,    1024,    1024,    1024,    1024,    1024 diff --git a/drivers/media/platform/omap3isp/noise_filter_table.h b/drivers/media/platform/omap3isp/noise_filter_table.h new file mode 100644 index 00000000000..d50451a4a24 --- /dev/null +++ b/drivers/media/platform/omap3isp/noise_filter_table.h @@ -0,0 +1,30 @@ +/* + * noise_filter_table.h + * + * TI OMAP3 ISP - Noise filter table + * + * Copyright (C) 2010 Nokia Corporation + * Copyright (C) 2009 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 + */ + +16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, +16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, +31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, +31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31  | 
