Subject: [E3-hacking] [PATCH v2 0/6] Add camera support to the OMAP1 Amstrad Delta videophone

From: Janusz Krzysztofik <[email-masked]>
Date: Sat, 11 Sep 2010 03:17:53 +0200

This set consists of the following patches:

  1/6	SoC Camera: add driver for OMAP1 camera interface
  2/6	OMAP1: Add support for SoC camera interface
  3/6	SoC Camera: add driver for OV6650 sensor
  4/6	SoC Camera: add support for g_parm / s_parm operations
  5/6	OMAP1: Amstrad Delta: add support for on-board camera
  6/6	OMAP1: Amstrad Delta: add camera controlled LEDS trigger

 arch/arm/mach-omap1/board-ams-delta.c     |   69 +
 arch/arm/mach-omap1/devices.c             |   43
 arch/arm/mach-omap1/include/mach/camera.h |    8
 drivers/media/video/Kconfig               |   14
 drivers/media/video/Makefile              |    2
 drivers/media/video/omap1_camera.c        | 1781 ++++++++++++++++++++++++++++++
 drivers/media/video/ov6650.c              | 1242 ++++++++++++++++++++
 drivers/media/video/soc_camera.c          |   18
 include/media/omap1_camera.h              |   35
 include/media/v4l2-chip-ident.h           |    1
 10 files changed, 3213 insertions(+)

---

From: Janusz Krzysztofik <[email-masked]>
Date: Sat, 11 Sep 2010 03:23:08 +0200

This patch adds support for SoC camera interface to OMAP1 devices.

Created and tested against linux-2.6.36-rc3 on Amstrad Delta.

For successfull compilation, requires a header file provided by PATCH 1/6 from 
this series, "SoC Camera: add driver for OMAP1 camera interface".

Signed-off-by: Janusz Krzysztofik <[email-masked]>
---
v1->v2 changes:
- no functional changes,
- refreshed against linux-2.6.36-rc3

 arch/arm/mach-omap1/devices.c             |   43 
++++++++++++++++++++++++++++++
 arch/arm/mach-omap1/include/mach/camera.h |    8 +++++
 2 files changed, 51 insertions(+)

diff -upr linux-2.6.36-rc3.orig/arch/arm/mach-omap1/devices.c 
linux-2.6.36-rc3/arch/arm/mach-omap1/devices.c
--- linux-2.6.36-rc3.orig/arch/arm/mach-omap1/devices.c	2010-09-03 
22:29:00.000000000 +0200
+++ linux-2.6.36-rc3/arch/arm/mach-omap1/devices.c	2010-09-09 
18:42:30.000000000 +0200
@@ -15,6 +15,7 @@
 #include <linux/platform_device.h>
 #include <linux/io.h>
 #include <linux/spi/spi.h>
+#include <linux/dma-mapping.h>
 
 #include <mach/hardware.h>
 #include <asm/mach/map.h>
@@ -25,6 +26,7 @@
 #include <mach/gpio.h>
 #include <plat/mmc.h>
 #include <plat/omap7xx.h>
+#include <mach/camera.h>
 
 /*-------------------------------------------------------------------------*/
 
@@ -191,6 +193,47 @@ static inline void omap_init_spi100k(voi
 }
 #endif
 
+
+#define OMAP1_CAMERA_BASE	0xfffb6800
+
+static struct resource omap1_camera_resources[] = {
+	[0] = {
+		.start	= OMAP1_CAMERA_BASE,
+		.end	= OMAP1_CAMERA_BASE + OMAP1_CAMERA_IOSIZE - 1,
+		.flags	= IORESOURCE_MEM,
+	},
+	[1] = {
+		.start	= INT_CAMERA,
+		.flags	= IORESOURCE_IRQ,
+	},
+};
+
+static u64 omap1_camera_dma_mask = DMA_BIT_MASK(32);
+
+static struct platform_device omap1_camera_device = {
+	.name		= "omap1-camera",
+	.id		= 0, /* This is used to put cameras on this interface */
+	.dev		= {
+		.dma_mask		= &omap1_camera_dma_mask,
+		.coherent_dma_mask	= DMA_BIT_MASK(32),
+	},
+	.num_resources	= ARRAY_SIZE(omap1_camera_resources),
+	.resource	= omap1_camera_resources,
+};
+
+void __init omap1_set_camera_info(struct omap1_cam_platform_data *info)
+{
+	struct platform_device *dev = &omap1_camera_device;
+	int ret;
+
+	dev->dev.platform_data = info;
+
+	ret = platform_device_register(dev);
+	if (ret)
+		dev_err(&dev->dev, "unable to register device: %d\n", ret);
+}
+
+
 /*-------------------------------------------------------------------------*/
 
 static inline void omap_init_sti(void) {}
diff -upr linux-2.6.36-rc3.orig/arch/arm/mach-omap1/include/mach/camera.h 
linux-2.6.36-rc3/arch/arm/mach-omap1/include/mach/camera.h
--- linux-2.6.36-rc3.orig/arch/arm/mach-omap1/include/mach/camera.h	2010-09-03 
22:34:03.000000000 +0200
+++ linux-2.6.36-rc3/arch/arm/mach-omap1/include/mach/camera.h	2010-09-09 
18:42:30.000000000 +0200
@@ -0,0 +1,8 @@
+#ifndef __ASM_ARCH_CAMERA_H_
+#define __ASM_ARCH_CAMERA_H_
+
+#include <media/omap1_camera.h>
+
+extern void omap1_set_camera_info(struct omap1_cam_platform_data *);
+
+#endif /* __ASM_ARCH_CAMERA_H_ */

---

From: Janusz Krzysztofik <[email-masked]>
Date: Sat, 11 Sep 2010 03:25:06 +0200

This patch provides a V4L2 SoC Camera driver for OV6650 camera sensor, found 
on OMAP1 SoC based Amstrad Delta videophone.

Since I have no experience with camera sensors, and the sensor documentation I 
was able to find was not very comprehensive, I left most settings at their 
default (reset) values, except for:
- those required for proper mediabus parameters and picture format setup,
- those used by controls.
Resulting picture quality is far from perfect, but better than nothing.

In order to be able to get / set the sensor frame rate from userspace, I 
decided to provide two not yet SoC camera supported operations, g_parm and 
s_parm. These can be used after applying patch 4/6 from this series, 
"SoC Camera: add support for g_parm / s_parm operations".

requested by Guennadi Liakhovetski (thanks!):
- include <linux/bitops.h> if using BIT() macro,
- sort headers alphabetically,
- don't mix tabs with spaces (preferred) when separating symbols from 
  #define keywords,
- drop unused NUM_REGS definition,
- optimize SET_SAT() and SAT_MASK macros,
- reuse no longer needed function argument instead of declaring a new local 
  variable,
- don't touch auto controls when changing their correspondig manual settings, 
  and vice versa,
- drop probably unsupported auto-hue control,
- initialize sensor by writing registers explicitly instead of using a "magic" 
  initialization array,
- avoid gotos, don't use them other than in failure cases,
- make pclk computation more readable,
- implement g_mbus_fmt() callback,
- correct a few obvious mistakes,
- remove a few extra whitespaces,

suggested by Ralph Corderoy (thanks!):
- use one common format when hex printing register addresses and values,
- optimize if(ret) vs. if(!ret) constructs usage,
- replace a few if-else constructs with more compact, conditional 
  expression based, when translating controls to register bits,
- optimize ov6650_res_roundup(),
- drop redundant cast of index from ov6650_enum_fmt(),
- use variable identifiers rather than their types as sizeof() arguments,

other:
- disable band filter, auto exposure control seems working more effectively 
  without it,
- refresh against linux-2.6.36-rc3.

 drivers/media/video/Kconfig     |    6
 drivers/media/video/Makefile    |    1
 drivers/media/video/ov6650.c    | 1242 ++++++++++++++++++++++++++++++++++++++++
 include/media/v4l2-chip-ident.h |    1
 4 files changed, 1250 insertions(+)

diff -upr linux-2.6.36-rc3.orig/drivers/media/video/Kconfig linux-2.6.36-rc3/drivers/media/video/Kconfig
--- linux-2.6.36-rc3.orig/drivers/media/video/Kconfig	2010-09-03 22:34:02.000000000 +0200
+++ linux-2.6.36-rc3/drivers/media/video/Kconfig	2010-09-03 22:34:02.000000000 +0200
@@ -835,6 +835,12 @@ config SOC_CAMERA_PLATFORM
 	help
 	  This is a generic SoC camera platform driver, useful for testing
 
+config SOC_CAMERA_OV6650
+	tristate "ov6650 sensor support"
+	depends on SOC_CAMERA && I2C
+	---help---
+	  This is a V4L2 SoC camera driver for the OmniVision OV6650 sensor
+
 config SOC_CAMERA_OV772X
 	tristate "ov772x camera support"
 	depends on SOC_CAMERA && I2C
diff -upr linux-2.6.36-rc3.orig/drivers/media/video/Makefile linux-2.6.36-rc3/drivers/media/video/Makefile
--- linux-2.6.36-rc3.orig/drivers/media/video/Makefile	2010-09-03 22:34:02.000000000 +0200
+++ linux-2.6.36-rc3/drivers/media/video/Makefile	2010-09-03 22:34:02.000000000 +0200
@@ -79,6 +79,7 @@ obj-$(CONFIG_SOC_CAMERA_MT9M111)	+= mt9m
 obj-$(CONFIG_SOC_CAMERA_MT9T031)	+= mt9t031.o
 obj-$(CONFIG_SOC_CAMERA_MT9T112)	+= mt9t112.o
 obj-$(CONFIG_SOC_CAMERA_MT9V022)	+= mt9v022.o
+obj-$(CONFIG_SOC_CAMERA_OV6650)		+= ov6650.o
 obj-$(CONFIG_SOC_CAMERA_OV772X)		+= ov772x.o
 obj-$(CONFIG_SOC_CAMERA_OV9640)		+= ov9640.o
 obj-$(CONFIG_SOC_CAMERA_RJ54N1)		+= rj54n1cb0c.o
diff -upr linux-2.6.36-rc3.orig/drivers/media/video/ov6650.c linux-2.6.36-rc3/drivers/media/video/ov6650.c
--- linux-2.6.36-rc3.orig/drivers/media/video/ov6650.c	2010-09-03 22:34:02.000000000 +0200
+++ linux-2.6.36-rc3/drivers/media/video/ov6650.c	2010-09-04 17:50:32.000000000 +0200
@@ -0,0 +1,1242 @@
+/*
+ * V4L2 SoC Camera driver for OmniVision OV6650 Camera Sensor
+ *
+ * Copyright (C) 2010 Janusz Krzysztofik <[email-masked]>
+ *
+ * Based on OmniVision OV96xx Camera Driver
+ * Copyright (C) 2009 Marek Vasut <[email-masked]>
+ *
+ * Based on ov772x camera driver:
+ * Copyright (C) 2008 Renesas Solutions Corp.
+ * Kuninori Morimoto <[email-masked]>
+ *
+ * Based on ov7670 and soc_camera_platform driver,
+ * Copyright 2006-7 Jonathan Corbet <[email-masked]>
+ * Copyright (C) 2008 Magnus Damm
+ * Copyright (C) 2008, Guennadi Liakhovetski <[email-masked]>
+ *
+ * Hardware specific bits initialy based on former work by Matt Callow
+ * drivers/media/video/omap/sensor_ov6650.c
+ * Copyright (C) 2006 Matt Callow
+ *
+ * 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.
+ */
+
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/slab.h>
+#include <media/soc_camera.h>
+#include <media/v4l2-chip-ident.h>
+
+
+/* Register definitions */
+#define REG_GAIN		0x00	/* range 00 - 3F */
+#define REG_BLUE		0x01
+#define REG_RED			0x02
+#define REG_SAT			0x03	/* [7:4] saturation [0:3] reserved */
+#define REG_HUE			0x04	/* [7:6] rsrvd [5] hue en [4:0] hue */
+
+#define REG_BRT			0x06
+
+#define REG_PIDH		0x0a
+#define REG_PIDL		0x0b
+
+#define REG_AECH		0x10
+#define REG_CLKRC		0x11	/* Data Format and Internal Clock */
+					/* [7:6] Input system clock (MHz)*/
+					/*   00=8, 01=12, 10=16, 11=24 */
+					/* [5:0]: Internal Clock Pre-Scaler */
+#define REG_COMA		0x12	/* [7] Reset */
+#define REG_COMB		0x13
+#define REG_COMC		0x14
+#define REG_COMD		0x15
+#define REG_COML		0x16
+#define REG_HSTRT		0x17
+#define REG_HSTOP		0x18
+#define REG_VSTRT		0x19
+#define REG_VSTOP		0x1a
+#define REG_PSHFT		0x1b
+#define REG_MIDH		0x1c
+#define REG_MIDL		0x1d
+#define REG_HSYNS		0x1e
+#define REG_HSYNE		0x1f
+#define REG_COME		0x20
+#define REG_YOFF		0x21
+#define REG_UOFF		0x22
+#define REG_VOFF		0x23
+#define REG_AEW			0x24
+#define REG_AEB			0x25
+#define REG_COMF		0x26
+#define REG_COMG		0x27
+#define REG_COMH		0x28
+#define REG_COMI		0x29
+
+#define REG_FRARL		0x2b
+#define REG_COMJ		0x2c
+#define REG_COMK		0x2d
+#define REG_AVGY		0x2e
+#define REG_REF0		0x2f
+#define REG_REF1		0x30
+#define REG_REF2		0x31
+#define REG_FRAJH		0x32
+#define REG_FRAJL		0x33
+#define REG_FACT		0x34
+#define REG_L1AEC		0x35
+#define REG_AVGU		0x36
+#define REG_AVGV		0x37
+
+#define REG_SPCB		0x60
+#define REG_SPCC		0x61
+#define REG_GAM1		0x62
+#define REG_GAM2		0x63
+#define REG_GAM3		0x64
+#define REG_SPCD		0x65
+
+#define REG_SPCE		0x68
+#define REG_ADCL		0x69
+
+#define REG_RMCO		0x6c
+#define REG_GMCO		0x6d
+#define REG_BMCO		0x6e
+
+
+/* Register bits, values, etc. */
+#define OV6650_PIDH		0x66	/* high byte of product ID number */
+#define OV6650_PIDL		0x50	/* low byte of product ID number */
+#define OV6650_MIDH		0x7F	/* high byte of mfg ID */
+#define OV6650_MIDL		0xA2	/* low byte of mfg ID */
+
+#define DEF_GAIN		0x00
+#define DEF_BLUE		0x80
+#define DEF_RED			0x80
+
+#define SAT_SHIFT		4
+#define SAT_MASK		(0x0f << SAT_SHIFT)
+#define SET_SAT(x)		(((x) << SAT_SHIFT) & SAT_MASK)
+
+#define HUE_EN			BIT(5)
+#define HUE_MASK		0x1f
+#define DEF_HUE			0x10
+#define SET_HUE(x)		(HUE_EN | ((x) & HUE_MASK))
+
+#define DEF_AECH		0x4D
+
+#define CLKRC_6MHz		0x00
+#define CLKRC_12MHz		0x40
+#define CLKRC_16MHz		0x80
+#define CLKRC_24MHz		0xc0
+#define CLKRC_DIV_MASK		0x3f
+#define GET_CLKRC_DIV(x)	(((x) & CLKRC_DIV_MASK) + 1)
+
+#define COMA_RESET		BIT(7)
+#define COMA_QCIF		BIT(5)
+#define COMA_RAW_RGB		BIT(4)
+#define COMA_RGB		BIT(3)
+#define COMA_BW			BIT(2)
+#define COMA_WORD_SWAP		BIT(1)
+#define COMA_BYTE_SWAP		BIT(0)
+#define DEF_COMA		0x00
+
+#define COMB_FLIP_V		BIT(7)
+#define COMB_FLIP_H		BIT(5)
+#define COMB_BAND_FILTER	BIT(4)
+#define COMB_AWB		BIT(2)
+#define COMB_AGC		BIT(1)
+#define COMB_AEC		BIT(0)
+#define DEF_COMB		0x5f
+
+#define COML_ONE_CHANNEL	BIT(7)
+
+#define DEF_HSTRT		0x24
+#define DEF_HSTOP		0xd4
+#define DEF_VSTRT		0x04
+#define DEF_VSTOP		0x94
+
+#define COMF_HREF_LOW		BIT(4)
+
+#define COMJ_PCLK_RISING	BIT(4)
+#define COMJ_VSYNC_HIGH		BIT(0)
+
+/* supported resolutions */
+#define W_QCIF			(DEF_HSTOP - DEF_HSTRT)
+#define W_CIF			(W_QCIF << 1)
+#define H_QCIF			(DEF_VSTOP - DEF_VSTRT)
+#define H_CIF			(H_QCIF << 1)
+
+#define FRAME_RATE_MAX		30
+
+
+struct ov6650_reg {
+	u8	reg;
+	u8	val;
+};
+
+struct ov6650 {
+	struct v4l2_subdev	subdev;
+
+	int			gain;
+	int			blue;
+	int			red;
+	int			saturation;
+	int			hue;
+	int			brightness;
+	int			exposure;
+	int			gamma;
+	bool			vflip;
+	bool			hflip;
+	bool			awb;
+	bool			agc;
+	int			aec;
+	bool			qcif;
+	unsigned long		pclk_max;	/* from resolution and format */
+	unsigned long		pclk_limit;	/* from host */
+	struct v4l2_fract	timeperframe;	/* as requested with s_parm */
+	struct v4l2_rect	rect;
+	enum v4l2_mbus_pixelcode code;
+	enum v4l2_colorspace	colorspace;
+};
+
+
+static enum v4l2_mbus_pixelcode ov6650_codes[] = {
+	V4L2_MBUS_FMT_YUYV8_2X8,
+	V4L2_MBUS_FMT_UYVY8_2X8,
+	V4L2_MBUS_FMT_YVYU8_2X8,
+	V4L2_MBUS_FMT_VYUY8_2X8,
+	V4L2_MBUS_FMT_SBGGR8_1X8,
+	V4L2_MBUS_FMT_GREY8_1X8,
+};
+
+static const struct v4l2_queryctrl ov6650_controls[] = {
+	{
+		.id		= V4L2_CID_AUTOGAIN,
+		.type		= V4L2_CTRL_TYPE_BOOLEAN,
+		.name		= "AGC",
+		.minimum	= 0,
+		.maximum	= 1,
+		.step		= 1,
+		.default_value	= 1,
+	},
+	{
+		.id		= V4L2_CID_GAIN,
+		.type		= V4L2_CTRL_TYPE_INTEGER,
+		.name		= "Gain",
+		.minimum	= 0,
+		.maximum	= 0x3f,
+		.step		= 1,
+		.default_value	= DEF_GAIN,
+	},
+	{
+		.id		= V4L2_CID_AUTO_WHITE_BALANCE,
+		.type		= V4L2_CTRL_TYPE_BOOLEAN,
+		.name		= "AWB",
+		.minimum	= 0,
+		.maximum	= 1,
+		.step		= 1,
+		.default_value	= 1,
+	},
+	{
+		.id		= V4L2_CID_BLUE_BALANCE,
+		.type		= V4L2_CTRL_TYPE_INTEGER,
+		.name		= "Blue",
+		.minimum	= 0,
+		.maximum	= 0xff,
+		.step		= 1,
+		.default_value	= DEF_BLUE,
+	},
+	{
+		.id		= V4L2_CID_RED_BALANCE,
+		.type		= V4L2_CTRL_TYPE_INTEGER,
+		.name		= "Red",
+		.minimum	= 0,
+		.maximum	= 0xff,
+		.step		= 1,
+		.default_value	= DEF_RED,
+	},
+	{
+		.id		= V4L2_CID_SATURATION,
+		.type		= V4L2_CTRL_TYPE_INTEGER,
+		.name		= "Saturation",
+		.minimum	= 0,
+		.maximum	= 0xf,
+		.step		= 1,
+		.default_value	= 0x8,
+	},
+	{
+		.id		= V4L2_CID_HUE,
+		.type		= V4L2_CTRL_TYPE_INTEGER,
+		.name		= "Hue",
+		.minimum	= 0,
+		.maximum	= 0x1f,
+		.step		= 1,
+		.default_value	= DEF_HUE,
+	},
+	{
+		.id		= V4L2_CID_BRIGHTNESS,
+		.type		= V4L2_CTRL_TYPE_INTEGER,
+		.name		= "Brightness",
+		.minimum	= 0,
+		.maximum	= 0xff,
+		.step		= 1,
+		.default_value	= 0x80,
+	},
+	{
+		.id		= V4L2_CID_EXPOSURE_AUTO,
+		.type		= V4L2_CTRL_TYPE_INTEGER,
+		.name		= "AEC",
+		.minimum	= 0,
+		.maximum	= 3,
+		.step		= 1,
+		.default_value	= 0,
+	},
+	{
+		.id		= V4L2_CID_EXPOSURE,
+		.type		= V4L2_CTRL_TYPE_INTEGER,
+		.name		= "Exposure",
+		.minimum	= 0,
+		.maximum	= 0xff,
+		.step		= 1,
+		.default_value	= DEF_AECH,
+	},
+	{
+		.id		= V4L2_CID_GAMMA,
+		.type		= V4L2_CTRL_TYPE_INTEGER,
+		.name		= "Gamma",
+		.minimum	= 0,
+		.maximum	= 0xff,
+		.step		= 1,
+		.default_value	= 0x12,
+	},
+	{
+		.id		= V4L2_CID_VFLIP,
+		.type		= V4L2_CTRL_TYPE_BOOLEAN,
+		.name		= "Flip Vertically",
+		.minimum	= 0,
+		.maximum	= 1,
+		.step		= 1,
+		.default_value	= 0,
+	},
+	{
+		.id		= V4L2_CID_HFLIP,
+		.type		= V4L2_CTRL_TYPE_BOOLEAN,
+		.name		= "Flip Horizontally",
+		.minimum	= 0,
+		.maximum	= 1,
+		.step		= 1,
+		.default_value	= 0,
+	},
+};
+
+/* read a register */
+static int ov6650_reg_read(struct i2c_client *client, u8 reg, u8 *val)
+{
+	int ret;
+	u8 data = reg;
+	struct i2c_msg msg = {
+		.addr	= client->addr,
+		.flags	= 0,
+		.len	= 1,
+		.buf	= &data,
+	};
+
+	ret = i2c_transfer(client->adapter, &msg, 1);
+	if (ret < 0)
+		goto err;
+
+	msg.flags = I2C_M_RD;
+	ret = i2c_transfer(client->adapter, &msg, 1);
+	if (ret < 0)
+		goto err;
+
+	*val = data;
+	return 0;
+
+err:
+	dev_err(&client->dev, "Failed reading register 0x%02x!\n", reg);
+	return ret;
+}
+
+/* write a register */
+static int ov6650_reg_write(struct i2c_client *client, u8 reg, u8 val)
+{
+	int ret;
+	unsigned char data[2] = { reg, val };
+	struct i2c_msg msg = {
+		.addr	= client->addr,
+		.flags	= 0,
+		.len	= 2,
+		.buf	= data,
+	};
+
+	ret = i2c_transfer(client->adapter, &msg, 1);
+	msleep_interruptible(1);
+
+	if (ret < 0) {
+		dev_err(&client->dev, "Failed writing register 0x%02x!\n", reg);
+		return ret;
+	}
+	return 0;
+}
+
+
+/* Read a register, alter its bits, write it back */
+static int ov6650_reg_rmw(struct i2c_client *client, u8 reg, u8 set, u8 mask)
+{
+	u8 val;
+	int ret;
+
+	ret = ov6650_reg_read(client, reg, &val);
+	if (ret) {
+		dev_err(&client->dev,
+			"[Read]-Modify-Write of register 0x%02x failed!\n",
+			reg);
+		return ret;
+	}
+
+	val &= ~mask;
+	val |= set;
+
+	ret = ov6650_reg_write(client, reg, val);
+	if (ret)
+		dev_err(&client->dev,
+			"Read-Modify-[Write] of register 0x%02x failed!\n",
+			reg);
+
+	return ret;
+}
+
+/* Soft reset the camera. This has nothing to do with the RESET pin! */
+static int ov6650_reset(struct i2c_client *client)
+{
+	int ret;
+
+	dev_dbg(&client->dev, "reset\n");
+
+	ret = ov6650_reg_rmw(client, REG_COMA, COMA_RESET, 0);
+	if (ret)
+		dev_err(&client->dev,
+			"An error occured while entering soft reset!\n");
+
+	return ret;
+}
+
+static struct ov6650 *to_ov6650(const struct i2c_client *client)
+{
+	return container_of(i2c_get_clientdata(client), struct ov6650, subdev);
+}
+
+/* Start/Stop streaming from the device */
+static int ov6650_s_stream(struct v4l2_subdev *sd, int enable)
+{
+	return 0;
+}
+
+/* Alter bus settings on camera side */
+static int ov6650_set_bus_param(struct soc_camera_device *icd,
+				unsigned long flags)
+{
+	struct soc_camera_link *icl = to_soc_camera_link(icd);
+	struct i2c_client *client = to_i2c_client(to_soc_camera_control(icd));
+	int ret;
+
+	flags = soc_camera_apply_sensor_flags(icl, flags);
+
+	if (flags & SOCAM_PCLK_SAMPLE_RISING)
+		ret = ov6650_reg_rmw(client, REG_COMJ, COMJ_PCLK_RISING, 0);
+	else
+		ret = ov6650_reg_rmw(client, REG_COMJ, 0, COMJ_PCLK_RISING);
+	if (ret)
+		return ret;
+
+	if (flags & SOCAM_HSYNC_ACTIVE_LOW)
+		ret = ov6650_reg_rmw(client, REG_COMF, COMF_HREF_LOW, 0);
+	else
+		ret = ov6650_reg_rmw(client, REG_COMF, 0, COMF_HREF_LOW);
+	if (ret)
+		return ret;
+
+	if (flags & SOCAM_VSYNC_ACTIVE_HIGH)
+		ret = ov6650_reg_rmw(client, REG_COMJ, COMJ_VSYNC_HIGH, 0);
+	else
+		ret = ov6650_reg_rmw(client, REG_COMJ, 0, COMJ_VSYNC_HIGH);
+
+	return ret;
+}
+
+/* Request bus settings on camera side */
+static unsigned long ov6650_query_bus_param(struct soc_camera_device *icd)
+{
+	struct soc_camera_link *icl = to_soc_camera_link(icd);
+
+	unsigned long flags = SOCAM_MASTER | \
+		SOCAM_PCLK_SAMPLE_RISING | SOCAM_PCLK_SAMPLE_FALLING | \
+		SOCAM_HSYNC_ACTIVE_HIGH | SOCAM_HSYNC_ACTIVE_LOW | \
+		SOCAM_VSYNC_ACTIVE_HIGH | SOCAM_VSYNC_ACTIVE_LOW | \
+		SOCAM_DATA_ACTIVE_HIGH | SOCAM_DATAWIDTH_8;
+
+	return soc_camera_apply_sensor_flags(icl, flags);
+}
+
+/* Get status of additional camera capabilities */
+static int ov6650_g_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl)
+{
+	struct i2c_client *client = sd->priv;
+	struct ov6650 *priv = to_ov6650(client);
+	uint8_t reg;
+	int ret = 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_AUTOGAIN:
+		ctrl->value = priv->agc;
+		break;
+	case V4L2_CID_GAIN:
+		if (priv->agc) {
+			ret = ov6650_reg_read(client, REG_GAIN, &reg);
+			ctrl->value = reg;
+		} else {
+			ctrl->value = priv->gain;
+		}
+		break;
+	case V4L2_CID_AUTO_WHITE_BALANCE:
+		ctrl->value = priv->awb;
+		break;
+	case V4L2_CID_BLUE_BALANCE:
+		if (priv->awb) {
+			ret = ov6650_reg_read(client, REG_BLUE, &reg);
+			ctrl->value = reg;
+		} else {
+			ctrl->value = priv->blue;
+		}
+		break;
+	case V4L2_CID_RED_BALANCE:
+		if (priv->awb) {
+			ret = ov6650_reg_read(client, REG_RED, &reg);
+			ctrl->value = reg;
+		} else {
+			ctrl->value = priv->red;
+		}
+		break;
+	case V4L2_CID_SATURATION:
+		ctrl->value = priv->saturation;
+		break;
+	case V4L2_CID_HUE:
+		ctrl->value = priv->hue;
+		break;
+	case V4L2_CID_BRIGHTNESS:
+		ctrl->value = priv->brightness;
+		break;
+	case V4L2_CID_EXPOSURE_AUTO:
+		ctrl->value = priv->aec;
+		break;
+	case V4L2_CID_EXPOSURE:
+		if (priv->aec) {
+			ret = ov6650_reg_read(client, REG_AECH, &reg);
+			ctrl->value = reg;
+		} else {
+			ctrl->value = priv->exposure;
+		}
+		break;
+	case V4L2_CID_GAMMA:
+		ctrl->value = priv->gamma;
+		break;
+	case V4L2_CID_VFLIP:
+		ctrl->value = priv->vflip;
+		break;
+	case V4L2_CID_HFLIP:
+		ctrl->value = priv->hflip;
+		break;
+	}
+	return ret;
+}
+
+/* Set status of additional camera capabilities */
+static int ov6650_s_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl)
+{
+	struct i2c_client *client = sd->priv;
+	struct ov6650 *priv = to_ov6650(client);
+	int ret = 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_AUTOGAIN:
+		ret = ov6650_reg_rmw(client, REG_COMB,
+				ctrl->value ? COMB_AGC : 0, COMB_AGC);
+		if (!ret)
+			priv->agc = ctrl->value;
+		break;
+	case V4L2_CID_GAIN:
+		ret = ov6650_reg_write(client, REG_GAIN, ctrl->value);
+		if (!ret)
+			priv->gain = ctrl->value;
+		break;
+	case V4L2_CID_AUTO_WHITE_BALANCE:
+		ret = ov6650_reg_rmw(client, REG_COMB,
+				ctrl->value ? COMB_AWB : 0, COMB_AWB);
+		if (!ret)
+			priv->awb = ctrl->value;
+		break;
+	case V4L2_CID_BLUE_BALANCE:
+		ret = ov6650_reg_write(client, REG_BLUE, ctrl->value);
+		if (!ret)
+			priv->blue = ctrl->value;
+		break;
+	case V4L2_CID_RED_BALANCE:
+		ret = ov6650_reg_write(client, REG_RED, ctrl->value);
+		if (!ret)
+			priv->red = ctrl->value;
+		break;
+	case V4L2_CID_SATURATION:
+		ret = ov6650_reg_rmw(client, REG_SAT, SET_SAT(ctrl->value),
+				SAT_MASK);
+		if (!ret)
+			priv->saturation = ctrl->value;
+		break;
+	case V4L2_CID_HUE:
+		ret = ov6650_reg_rmw(client, REG_HUE, SET_HUE(ctrl->value),
+				HUE_MASK);
+		if (!ret)
+			priv->hue = ctrl->value;
+		break;
+	case V4L2_CID_BRIGHTNESS:
+		ret = ov6650_reg_write(client, REG_BRT, ctrl->value);
+		if (!ret)
+			priv->brightness = ctrl->value;
+		break;
+	case V4L2_CID_EXPOSURE_AUTO:
+		switch (ctrl->value) {
+		case V4L2_EXPOSURE_AUTO:
+			ret = ov6650_reg_rmw(client, REG_COMB, COMB_AEC, 0);
+			break;
+		default:
+			ret = ov6650_reg_rmw(client, REG_COMB, 0, COMB_AEC);
+			break;
+		}
+		if (!ret)
+			priv->aec = ctrl->value;
+		break;
+	case V4L2_CID_EXPOSURE:
+		ret = ov6650_reg_write(client, REG_AECH, ctrl->value);
+		if (!ret)
+			priv->exposure = ctrl->value;
+		break;
+	case V4L2_CID_GAMMA:
+		ret = ov6650_reg_write(client, REG_GAM1, ctrl->value);
+		if (!ret)
+			priv->gamma = ctrl->value;
+		break;
+	case V4L2_CID_VFLIP:
+		ret = ov6650_reg_rmw(client, REG_COMB,
+				ctrl->value ? COMB_FLIP_V : 0, COMB_FLIP_V);
+		if (!ret)
+			priv->vflip = ctrl->value;
+		break;
+	case V4L2_CID_HFLIP:
+		ret = ov6650_reg_rmw(client, REG_COMB,
+				ctrl->value ? COMB_FLIP_H : 0, COMB_FLIP_H);
+		if (!ret)
+			priv->hflip = ctrl->value;
+		break;
+	}
+
+	return ret;
+}
+
+/* Get chip identification */
+static int ov6650_g_chip_ident(struct v4l2_subdev *sd,
+				struct v4l2_dbg_chip_ident *id)
+{
+	id->ident	= V4L2_IDENT_OV6650;
+	id->revision	= 0;
+
+	return 0;
+}
+
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+static int ov6650_get_register(struct v4l2_subdev *sd,
+				struct v4l2_dbg_register *reg)
+{
+	struct i2c_client *client = sd->priv;
+	int ret;
+	u8 val;
+
+	if (reg->reg & ~0xff)
+		return -EINVAL;
+
+	reg->size = 1;
+
+	ret = ov6650_reg_read(client, reg->reg, &val);
+	if (!ret)
+		reg->val = (__u64)val;
+
+	return ret;
+}
+
+static int ov6650_set_register(struct v4l2_subdev *sd,
+				struct v4l2_dbg_register *reg)
+{
+	struct i2c_client *client = sd->priv;
+
+	if (reg->reg & ~0xff || reg->val & ~0xff)
+		return -EINVAL;
+
+	return ov6650_reg_write(client, reg->reg, reg->val);
+}
+#endif
+
+/*
+ * Select nearest higher resolution for capture.
+ * To be dropped once a common function for doing this is implemented.
+ */
+static void ov6650_res_roundup(u32 *width, u32 *height)
+{
+	int i, lasti;
+	int res_x[] = { W_QCIF, W_CIF };
+	int res_y[] = { H_QCIF, H_CIF };
+
+	for (i = 0; i < ARRAY_SIZE(res_x); i++) {
+		lasti = i;
+		if (res_x[i] >= *width && res_y[i] >= *height)
+			break;
+	}
+
+	*width = res_x[lasti];
+	*height = res_y[lasti];
+}
+
+/* program default register values */
+static int ov6650_prog_dflt(struct i2c_client *client)
+{
+	int ret;
+
+	dev_dbg(&client->dev, "reinitializing\n");
+
+	ret = ov6650_reg_write(client, REG_COMA, 0);	/* ~COMA_RESET */
+	if (!ret)
+		ret = ov6650_reg_rmw(client, REG_COMB, 0, COMB_BAND_FILTER);
+
+	return ret;
+}
+
+static int ov6650_g_fmt(struct v4l2_subdev *sd,
+			 struct v4l2_mbus_framefmt *mf)
+{
+	struct i2c_client *client = sd->priv;
+	struct ov6650 *priv = to_ov6650(client);
+
+	mf->width	= priv->rect.width;
+	mf->height	= priv->rect.height;
+	mf->code	= priv->code;
+	mf->colorspace	= priv->colorspace;
+	mf->field	= V4L2_FIELD_NONE;
+
+	return 0;
+}
+
+/* set the format we will capture in */
+static int ov6650_s_fmt(struct v4l2_subdev *sd,
+			struct v4l2_mbus_framefmt *mf)
+{
+	struct i2c_client *client = sd->priv;
+	struct soc_camera_device *icd	= client->dev.platform_data;
+	struct soc_camera_sense *sense = icd->sense;
+	struct ov6650 *priv = to_ov6650(client);
+	enum v4l2_mbus_pixelcode code = mf->code;
+	unsigned long pclk;
+	u8 coma_set = 0, coma_mask = 0, coml_set = 0, coml_mask = 0;
+	u8 clkrc, clkrc_div;
+	int ret;
+
+	/* select color matrix configuration for given color encoding */
+	switch (code) {
+	case V4L2_MBUS_FMT_GREY8_1X8:
+		dev_dbg(&client->dev, "pixel format GREY8_1X8\n");
+		coma_set |= COMA_BW;
+		coma_mask |= COMA_RGB | COMA_WORD_SWAP | COMA_BYTE_SWAP;
+		break;
+	case V4L2_MBUS_FMT_YUYV8_2X8:
+		dev_dbg(&client->dev, "pixel format YUYV8_2X8_LE\n");
+		coma_set |= COMA_WORD_SWAP;
+		coma_mask |= COMA_RGB | COMA_BW | COMA_BYTE_SWAP;
+		break;
+	case V4L2_MBUS_FMT_YVYU8_2X8:
+		dev_dbg(&client->dev, "pixel format YVYU8_2X8_LE (untested)\n");
+		coma_mask |= COMA_RGB | COMA_BW | COMA_WORD_SWAP |
+				COMA_BYTE_SWAP;
+		break;
+	case V4L2_MBUS_FMT_UYVY8_2X8:
+		dev_dbg(&client->dev, "pixel format YUYV8_2X8_BE\n");
+		if (mf->width == W_CIF) {
+			coma_set |= COMA_BYTE_SWAP | COMA_WORD_SWAP;
+			coma_mask |= COMA_RGB | COMA_BW;
+		} else {
+			coma_set |= COMA_BYTE_SWAP;
+			coma_mask |= COMA_RGB | COMA_BW | COMA_WORD_SWAP;
+		}
+		break;
+	case V4L2_MBUS_FMT_VYUY8_2X8:
+		dev_dbg(&client->dev, "pixel format YVYU8_2X8_BE (untested)\n");
+		if (mf->width == W_CIF) {
+			coma_set |= COMA_BYTE_SWAP;
+			coma_mask |= COMA_RGB | COMA_BW | COMA_WORD_SWAP;
+		} else {
+			coma_set |= COMA_BYTE_SWAP | COMA_WORD_SWAP;
+			coma_mask |= COMA_RGB | COMA_BW;
+		}
+		break;
+	case V4L2_MBUS_FMT_SBGGR8_1X8:
+		dev_dbg(&client->dev, "pixel format SBGGR8_1X8 (untested)\n");
+		coma_set |= COMA_RAW_RGB | COMA_RGB;
+		coma_mask |= COMA_BW | COMA_BYTE_SWAP | COMA_WORD_SWAP;
+		break;
+	default:
+		dev_err(&client->dev, "Pixel format not handled: 0x%x\n", code);
+		return -EINVAL;
+	}
+	priv->code = code;
+
+	if ((code == V4L2_MBUS_FMT_GREY8_1X8) ||
+			(code == V4L2_MBUS_FMT_SBGGR8_1X8)) {
+		coml_mask |= COML_ONE_CHANNEL;
+		priv->pclk_max = 4000000;
+	} else {
+		coml_set |= COML_ONE_CHANNEL;
+		priv->pclk_max = 8000000;
+	}
+
+	if (code == V4L2_MBUS_FMT_SBGGR8_1X8)
+		priv->colorspace = V4L2_COLORSPACE_SRGB;
+	else
+		priv->colorspace = V4L2_COLORSPACE_JPEG;
+
+	/*
+	 * Select register configuration for given resolution.
+	 * To be replaced with a common function that does it, once available.
+	 */
+	ov6650_res_roundup(&mf->width, &mf->height);
+
+	switch (mf->width) {
+	case W_QCIF:
+		dev_dbg(&client->dev, "resolution QCIF\n");
+		priv->qcif = 1;
+		coma_set |= COMA_QCIF;
+		priv->pclk_max /= 2;
+		break;
+	case W_CIF:
+		dev_dbg(&client->dev, "resolution CIF\n");
+		priv->qcif = 0;
+		coma_mask |= COMA_QCIF;
+		break;
+	default:
+		dev_err(&client->dev, "unspported resolution!\n");
+		return -EINVAL;
+	}
+
+	priv->rect.left	  = DEF_HSTRT << !priv->qcif;
+	priv->rect.top	  = DEF_VSTRT << !priv->qcif;
+	priv->rect.width  = mf->width;
+	priv->rect.height = mf->height;
+
+	if (priv->timeperframe.numerator && priv->timeperframe.denominator)
+		pclk = priv->pclk_max * priv->timeperframe.denominator /
+				(FRAME_RATE_MAX * priv->timeperframe.numerator);
+	else
+		pclk = priv->pclk_max;
+
+	if (sense) {
+		if (sense->master_clock == 8000000) {
+			dev_dbg(&client->dev, "8MHz input clock\n");
+			clkrc = CLKRC_6MHz;
+		} else if (sense->master_clock == 12000000) {
+			dev_dbg(&client->dev, "12MHz input clock\n");
+			clkrc = CLKRC_12MHz;
+		} else if (sense->master_clock == 16000000) {
+			dev_dbg(&client->dev, "16MHz input clock\n");
+			clkrc = CLKRC_16MHz;
+		} else if (sense->master_clock == 24000000) {
+			dev_dbg(&client->dev, "24MHz input clock\n");
+			clkrc = CLKRC_24MHz;
+		} else {
+			dev_err(&client->dev,
+				"unspported input clock, check platform data\n");
+			return -EINVAL;
+		}
+		priv->pclk_limit = sense->pixel_clock_max;
+		if (priv->pclk_limit && (priv->pclk_limit < pclk))
+			pclk = priv->pclk_limit;
+	} else {
+		priv->pclk_limit = 0;
+		clkrc = 0xc0;
+		dev_dbg(&client->dev, "using default 24MHz input clock\n");
+	}
+
+	clkrc_div = (priv->pclk_max - 1) / pclk;
+	clkrc |= clkrc_div;
+	pclk = priv->pclk_max / clkrc_div;
+	dev_dbg(&client->dev, "pixel clock divider: %ld.%ld\n",
+			sense->master_clock / pclk,
+			10 * sense->master_clock % pclk / pclk);
+
+	ov6650_reset(client);
+
+	ret = ov6650_prog_dflt(client);
+	if (!ret)
+		ret = ov6650_reg_rmw(client, REG_COMA, coma_set, coma_mask);
+	if (!ret)
+		ret = ov6650_reg_write(client, REG_CLKRC, clkrc);
+	if (!ret)
+		ret = ov6650_reg_rmw(client, REG_COML, coml_set, coml_mask);
+
+	if (!ret) {
+		mf->code	= code;
+		mf->colorspace	= priv->colorspace;
+	}
+
+	return ret;
+}
+
+static int ov6650_try_fmt(struct v4l2_subdev *sd,
+			  struct v4l2_mbus_framefmt *mf)
+{
+	/*
+	 * Select register configuration for given resolution.
+	 * To be replaced with a common function that does it, once available.
+	 */
+	ov6650_res_roundup(&mf->width, &mf->height);
+
+	mf->field = V4L2_FIELD_NONE;
+
+	switch (mf->code) {
+	case V4L2_MBUS_FMT_Y10_1X10:
+		mf->code = V4L2_MBUS_FMT_GREY8_1X8;
+	case V4L2_MBUS_FMT_GREY8_1X8:
+	case V4L2_MBUS_FMT_YVYU8_2X8:
+	case V4L2_MBUS_FMT_YUYV8_2X8:
+	case V4L2_MBUS_FMT_VYUY8_2X8:
+	case V4L2_MBUS_FMT_UYVY8_2X8:
+		mf->colorspace = V4L2_COLORSPACE_JPEG;
+		break;
+	default:
+		mf->code = V4L2_MBUS_FMT_SBGGR8_1X8;
+	case V4L2_MBUS_FMT_SBGGR8_1X8:
+		mf->colorspace = V4L2_COLORSPACE_SRGB;
+		break;
+	}
+
+	return 0;
+}
+
+static int ov6650_enum_fmt(struct v4l2_subdev *sd, unsigned int index,
+			   enum v4l2_mbus_pixelcode *code)
+{
+	if (index >= ARRAY_SIZE(ov6650_codes))
+		return -EINVAL;
+
+	*code = ov6650_codes[index];
+	return 0;
+}
+
+static int ov6650_cropcap(struct v4l2_subdev *sd, struct v4l2_cropcap *a)
+{
+	struct i2c_client *client = sd->priv;
+	struct ov6650 *priv = to_ov6650(client);
+	int shift = !priv->qcif;
+
+	if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
+
+	/* Crop limits depend on selected frame format (CIF/QCIF) */
+	a->bounds.left			= DEF_HSTRT << shift;
+	a->bounds.top			= DEF_VSTRT << shift;
+	a->bounds.width			= W_QCIF << shift;
+	a->bounds.height		= H_QCIF << shift;
+	a->defrect			= a->bounds;
+	a->pixelaspect.numerator	= 1;
+	a->pixelaspect.denominator	= 1;
+
+	return 0;
+}
+
+static int ov6650_g_crop(struct v4l2_subdev *sd, struct v4l2_crop *a)
+{
+	struct i2c_client *client = sd->priv;
+	struct ov6650 *priv = to_ov6650(client);
+	struct v4l2_rect *rect = &a->c;
+
+	if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
+
+	rect->left   = priv->rect.left;
+	rect->top    = priv->rect.top;
+	rect->width  = priv->rect.width;
+	rect->height = priv->rect.height;
+
+	return 0;
+}
+
+static int ov6650_s_crop(struct v4l2_subdev *sd, struct v4l2_crop *a)
+{
+	struct i2c_client *client = sd->priv;
+	struct ov6650 *priv = to_ov6650(client);
+	struct v4l2_rect *rect = &a->c;
+	int shift = !priv->qcif;
+	u8 hstrt, vstrt, hstop, vstop;
+	int ret;
+
+	if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
+
+	hstrt = rect->left >> shift;
+	vstrt = rect->top >> shift;
+	hstop = hstrt + (rect->width >> shift);
+	vstop = vstrt + (rect->height >> shift);
+
+	if ((hstop > DEF_HSTOP) || (vstop > DEF_VSTOP)) {
+		dev_err(&client->dev, "Invalid window geometry!\n");
+		return -EINVAL;
+	}
+
+	ret = ov6650_reg_write(client, REG_HSTRT, hstrt);
+	if (!ret) {
+		priv->rect.left = rect->left;
+		ret = ov6650_reg_write(client, REG_HSTOP, hstop);
+	}
+	if (!ret) {
+		priv->rect.width = rect->width;
+		ret = ov6650_reg_write(client, REG_VSTRT, vstrt);
+	}
+	if (!ret) {
+		priv->rect.top = rect->top;
+		ret = ov6650_reg_write(client, REG_VSTOP, vstop);
+	}
+	if (!ret)
+		priv->rect.height = rect->height;
+
+	return ret;
+}
+
+static int ov6650_g_parm(struct v4l2_subdev *sd, struct v4l2_streamparm *parms)
+{
+	struct i2c_client *client = sd->priv;
+	struct v4l2_captureparm *cp = &parms->parm.capture;
+	u8 clkrc;
+	int ret;
+
+	if (parms->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
+
+	ret = ov6650_reg_read(client, REG_CLKRC, &clkrc);
+	if (ret < 0)
+		return ret;
+
+	memset(cp, 0, sizeof(*cp));
+	cp->capability = V4L2_CAP_TIMEPERFRAME;
+	cp->timeperframe.numerator = GET_CLKRC_DIV(clkrc);
+	cp->timeperframe.denominator = FRAME_RATE_MAX;
+
+	dev_dbg(&client->dev, "Frame interval: %u/%u\n",
+		cp->timeperframe.numerator, cp->timeperframe.denominator);
+
+	return 0;
+}
+
+static int ov6650_s_parm(struct v4l2_subdev *sd, struct v4l2_streamparm *parms)
+{
+	struct i2c_client *client = sd->priv;
+	struct ov6650 *priv = to_ov6650(client);
+	struct v4l2_captureparm *cp = &parms->parm.capture;
+	struct v4l2_fract *tpf = &cp->timeperframe;
+	int div, ret;
+
+	if (parms->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
+
+	if (cp->extendedmode != 0)
+		return -EINVAL;
+
+	if (tpf->numerator == 0 || tpf->denominator == 0)
+		div = 1;  /* Reset to full rate */
+	else
+		div = (tpf->numerator * FRAME_RATE_MAX) / tpf->denominator;
+
+	if (div == 0)
+		div = 1;
+	else if (div > CLKRC_DIV_MASK + 1)
+		div = CLKRC_DIV_MASK + 1;
+
+	if (priv->pclk_max && priv->pclk_limit) {
+		ret = (priv->pclk_max - 1) / priv->pclk_limit;
+		if (div < ret)
+			div = ret;
+	}
+
+	ret = ov6650_reg_rmw(client, REG_CLKRC, div - 1, CLKRC_DIV_MASK);
+	if (!ret) {
+		tpf->numerator = FRAME_RATE_MAX;
+		tpf->denominator = div;
+		priv->timeperframe = *tpf;
+	}
+
+	return ret;
+}
+
+static int ov6650_video_probe(struct soc_camera_device *icd,
+				struct i2c_client *client)
+{
+	u8		pidh, pidl, midh, midl;
+	int		ret = 0;
+
+	/*
+	 * check and show product ID and manufacturer ID
+	 */
+	ret = ov6650_reg_read(client, REG_PIDH, &pidh);
+	if (!ret)
+		ret = ov6650_reg_read(client, REG_PIDL, &pidl);
+	if (!ret)
+		ret = ov6650_reg_read(client, REG_MIDH, &midh);
+	if (!ret)
+		ret = ov6650_reg_read(client, REG_MIDL, &midl);
+
+	if (ret)
+		return ret;
+
+	if ((pidh != OV6650_PIDH) || (pidl != OV6650_PIDL)) {
+		dev_err(&client->dev, "Product ID error 0x%02x:0x%02x\n",
+				pidh, pidl);
+		return -ENODEV;
+	}
+
+	dev_info(&client->dev,
+		"ov6650 Product ID 0x%02x:0x%02x Manufacturer ID 0x%02x:0x%02x\n",
+		pidh, pidl, midh, midl);
+
+	return 0;
+}
+
+static struct soc_camera_ops ov6650_ops = {
+	.set_bus_param		= ov6650_set_bus_param,
+	.query_bus_param	= ov6650_query_bus_param,
+	.controls		= ov6650_controls,
+	.num_controls		= ARRAY_SIZE(ov6650_controls),
+};
+
+static struct v4l2_subdev_core_ops ov6650_core_ops = {
+	.g_ctrl			= ov6650_g_ctrl,
+	.s_ctrl			= ov6650_s_ctrl,
+	.g_chip_ident		= ov6650_g_chip_ident,
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+	.g_register		= ov6650_get_register,
+	.s_register		= ov6650_set_register,
+#endif
+};
+
+static struct v4l2_subdev_video_ops ov6650_video_ops = {
+	.s_stream	= ov6650_s_stream,
+	.g_mbus_fmt	= ov6650_g_fmt,
+	.s_mbus_fmt	= ov6650_s_fmt,
+	.try_mbus_fmt	= ov6650_try_fmt,
+	.enum_mbus_fmt	= ov6650_enum_fmt,
+	.cropcap	= ov6650_cropcap,
+	.g_crop		= ov6650_g_crop,
+	.s_crop		= ov6650_s_crop,
+	.g_parm		= ov6650_g_parm,
+	.s_parm		= ov6650_s_parm,
+};
+
+static struct v4l2_subdev_ops ov6650_subdev_ops = {
+	.core	= &ov6650_core_ops,
+	.video	= &ov6650_video_ops,
+};
+
+/*
+ * i2c_driver function
+ */
+static int ov6650_probe(struct i2c_client *client,
+			const struct i2c_device_id *did)
+{
+	struct ov6650 *priv;
+	struct soc_camera_device *icd = client->dev.platform_data;
+	struct soc_camera_link *icl;
+	int ret;
+
+	if (!icd) {
+		dev_err(&client->dev, "Missing soc-camera data!\n");
+		return -EINVAL;
+	}
+
+	icl = to_soc_camera_link(icd);
+	if (!icl) {
+		dev_err(&client->dev, "Missing platform_data for driver\n");
+		return -EINVAL;
+	}
+
+	priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+	if (!priv) {
+		dev_err(&client->dev,
+			"Failed to allocate memory for private data!\n");
+		return -ENOMEM;
+	}
+
+	v4l2_i2c_subdev_init(&priv->subdev, client, &ov6650_subdev_ops);
+
+	icd->ops = &ov6650_ops;
+
+	priv->qcif	  = false;
+	priv->rect.left	  = DEF_HSTRT << !false;
+	priv->rect.top	  = DEF_VSTRT << !false;
+	priv->rect.width  = W_CIF;
+	priv->rect.height = H_CIF;
+	priv->code	  = V4L2_MBUS_FMT_YUYV8_2X8;
+	priv->colorspace  = V4L2_COLORSPACE_JPEG;
+
+	ret = ov6650_video_probe(icd, client);
+
+	if (ret) {
+		icd->ops = NULL;
+		i2c_set_clientdata(client, NULL);
+		kfree(priv);
+	}
+
+	return ret;
+}
+
+static int ov6650_remove(struct i2c_client *client)
+{
+	struct ov6650 *priv = to_ov6650(client);
+
+	i2c_set_clientdata(client, NULL);
+	kfree(priv);
+	return 0;
+}
+
+static const struct i2c_device_id ov6650_id[] = {
+	{ "ov6650", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, ov6650_id);
+
+static struct i2c_driver ov6650_i2c_driver = {
+	.driver = {
+		.name = "ov6650",
+	},
+	.probe    = ov6650_probe,
+	.remove   = ov6650_remove,
+	.id_table = ov6650_id,
+};
+
+static int __init ov6650_module_init(void)
+{
+	return i2c_add_driver(&ov6650_i2c_driver);
+}
+
+static void __exit ov6650_module_exit(void)
+{
+	i2c_del_driver(&ov6650_i2c_driver);
+}
+
+module_init(ov6650_module_init);
+module_exit(ov6650_module_exit);
+
+MODULE_DESCRIPTION("SoC Camera driver for OmniVision OV6650");
+MODULE_AUTHOR("Janusz Krzysztofik <[email-masked]>");
+MODULE_LICENSE("GPL v2");
diff -upr linux-2.6.36-rc3.orig/include/media/v4l2-chip-ident.h linux-2.6.36-rc3/include/media/v4l2-chip-ident.h
--- linux-2.6.36-rc3.orig/include/media/v4l2-chip-ident.h	2010-09-03 22:30:25.000000000 +0200
+++ linux-2.6.36-rc3/include/media/v4l2-chip-ident.h	2010-09-03 22:34:02.000000000 +0200
@@ -70,6 +70,7 @@ enum {
 	V4L2_IDENT_OV9655 = 255,
 	V4L2_IDENT_SOI968 = 256,
 	V4L2_IDENT_OV9640 = 257,
+	V4L2_IDENT_OV6650 = 258,
 
 	/* module saa7146: reserved range 300-309 */
 	V4L2_IDENT_SAA7146 = 300,

---

From: Janusz Krzysztofik <[email-masked]>
Date: Sat, 11 Sep 2010 03:26:16 +0200

This patch adds support for g_parm / s_parm operations to the SoC Camera 
framework. It is usefull for checking/setting camera frame rate.

Example usage can be found in the previous patch from this series, 
"SoC Camera: add driver for OV6650 sensor".

Signed-off-by: Janusz Krzysztofik <[email-masked]>
---
v1->v2 changes:
- no functional changes,
- refreshed against linux-2.6.36-rc3.

 drivers/media/video/soc_camera.c |   18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

diff -upr linux-2.6.36-rc3.orig/drivers/media/video/soc_camera.c linux-2.6.36-rc3/drivers/media/video/soc_camera.c
--- linux-2.6.36-rc3.orig/drivers/media/video/soc_camera.c	2010-09-03 22:29:44.000000000 +0200
+++ linux-2.6.36-rc3/drivers/media/video/soc_camera.c	2010-09-03 22:34:03.000000000 +0200
@@ -1148,6 +1148,20 @@ static int default_s_crop(struct soc_cam
 	return v4l2_subdev_call(sd, video, s_crop, a);
 }
 
+static int default_g_parm(struct soc_camera_device *icd,
+			  struct v4l2_streamparm *parm)
+{
+	struct v4l2_subdev *sd = soc_camera_to_subdev(icd);
+	return v4l2_subdev_call(sd, video, g_parm, parm);
+}
+
+static int default_s_parm(struct soc_camera_device *icd,
+			  struct v4l2_streamparm *parm)
+{
+	struct v4l2_subdev *sd = soc_camera_to_subdev(icd);
+	return v4l2_subdev_call(sd, video, s_parm, parm);
+}
+
 static void soc_camera_device_init(struct device *dev, void *pdata)
 {
 	dev->platform_data	= pdata;
@@ -1179,6 +1193,10 @@ int soc_camera_host_register(struct soc_
 		ici->ops->get_crop = default_g_crop;
 	if (!ici->ops->cropcap)
 		ici->ops->cropcap = default_cropcap;
+	if (!ici->ops->set_parm)
+		ici->ops->set_parm = default_s_parm;
+	if (!ici->ops->get_parm)
+		ici->ops->get_parm = default_g_parm;
 
 	mutex_lock(&list_lock);
 	list_for_each_entry(ix, &hosts, list) {

---

From: Janusz Krzysztofik <[email-masked]>
Date: Sat, 11 Sep 2010 03:27:29 +0200

This patch adds configuration data and initialization code required for camera 
support to the Amstrad Delta board.

Three devices are declared: SoC camera, OMAP1 camera interface and OV6650 
sensor.

Default 12MHz clock has been selected for driving the sensor. Pixel clock has 
been limited to get reasonable frame rates, not exceeding the board 
capabilities. Since both devices (interface and sensor) support both pixel 
clock polarities, decision on polarity selection has been left to drivers.
Interface GPIO line has been found not functional, thus not configured.

Created and tested against linux-2.6.36-rc3.

Works on top of previous patches from the series, at least 1/6, 2/6 and 3/6.

 arch/arm/mach-omap1/board-ams-delta.c |   45 ++++++++++++++++++++++++++++++++++
 1 file changed, 45 insertions(+)

diff -upr linux-2.6.36-rc3.orig/arch/arm/mach-omap1/board-ams-delta.c 
linux-2.6.36-rc3/arch/arm/mach-omap1/board-ams-delta.c
--- linux-2.6.36-rc3.orig/arch/arm/mach-omap1/board-ams-delta.c	2010-09-03 22:29:00.000000000 +0200
+++ linux-2.6.36-rc3/arch/arm/mach-omap1/board-ams-delta.c	2010-09-10 22:01:24.000000000 +0200
@@ -19,6 +19,8 @@
 #include <linux/platform_device.h>
 #include <linux/serial_8250.h>
 
+#include <media/soc_camera.h>
+
 #include <asm/serial.h>
 #include <mach/hardware.h>
 #include <asm/mach-types.h>
@@ -32,6 +34,7 @@
 #include <plat/usb.h>
 #include <plat/board.h>
 #include <plat/common.h>
+#include <mach/camera.h>
 
 #include <mach/ams-delta-fiq.h>
 
@@ -213,10 +216,37 @@ static struct platform_device ams_delta_
 	.id	= -1
 };
 
+static struct i2c_board_info ams_delta_camera_board_info[] = {
+	{
+		I2C_BOARD_INFO("ov6650", 0x60),
+	},
+};
+
+static struct soc_camera_link __initdata ams_delta_iclink = {
+	.bus_id         = 0,	/* OMAP1 SoC camera bus */
+	.i2c_adapter_id = 1,
+	.board_info     = &ams_delta_camera_board_info[0],
+	.module_name    = "ov6650",
+};
+
+static struct platform_device ams_delta_camera_device = {
+	.name   = "soc-camera-pdrv",
+	.id     = 0,
+	.dev    = {
+		.platform_data = &ams_delta_iclink,
+	},
+};
+
+static struct omap1_cam_platform_data ams_delta_camera_platform_data = {
+	.camexclk_khz	= 12000,	/* default 12MHz clock, no extra DPLL */
+	.lclk_khz_max	= 1334,		/* results in 5fps CIF, 10fps QCIF */
+};
+
 static struct platform_device *ams_delta_devices[] __initdata = {
 	&ams_delta_kp_device,
 	&ams_delta_lcd_device,
 	&ams_delta_led_device,
+	&ams_delta_camera_device,
 };
 
 static void __init ams_delta_init(void)
@@ -225,6 +255,20 @@ static void __init ams_delta_init(void)
 	omap_cfg_reg(UART1_TX);
 	omap_cfg_reg(UART1_RTS);
 
+	/* parallel camera interface */
+	omap_cfg_reg(H19_1610_CAM_EXCLK);
+	omap_cfg_reg(J15_1610_CAM_LCLK);
+	omap_cfg_reg(L18_1610_CAM_VS);
+	omap_cfg_reg(L15_1610_CAM_HS);
+	omap_cfg_reg(L19_1610_CAM_D0);
+	omap_cfg_reg(K14_1610_CAM_D1);
+	omap_cfg_reg(K15_1610_CAM_D2);
+	omap_cfg_reg(K19_1610_CAM_D3);
+	omap_cfg_reg(K18_1610_CAM_D4);
+	omap_cfg_reg(J14_1610_CAM_D5);
+	omap_cfg_reg(J19_1610_CAM_D6);
+	omap_cfg_reg(J18_1610_CAM_D7);
+
 	iotable_init(ams_delta_io_desc, ARRAY_SIZE(ams_delta_io_desc));
 
 	omap_board_config = ams_delta_config;
@@ -236,6 +280,7 @@ static void __init ams_delta_init(void)
 	ams_delta_latch2_write(~0, 0);
 
 	omap1_usb_init(&ams_delta_usb_config);
+	omap1_set_camera_info(&ams_delta_camera_platform_data);
 	platform_add_devices(ams_delta_devices, ARRAY_SIZE(ams_delta_devices));
 
 #ifdef CONFIG_AMS_DELTA_FIQ

---

From: Janusz Krzysztofik <[email-masked]>
Date: Sat, 11 Sep 2010 03:28:58 +0200

This patch extends the Amstrad Delta camera support with LEDS trigger that can 
be used for automatic control of the on-board camera LED. The led turns on 
automatically on camera device open and turns off on camera device close.

Works on top of patch 5/6, "OMAP1: Amstrad Delta: add support for on-board 
camera"

Signed-off-by: Janusz Krzysztofik <[email-masked]>
---
Having no comments received on v1 of this patch, I assume nobody could see any 
benefit if I implemented this idea at the SoC camera or OMAP1 camera level, 
so I'm leaveing it as an Amstrad Delta only feature, as it originally was for 
v1.

Thanks,
Janusz

v1->v2 changes:
- no functional changes,

 arch/arm/mach-omap1/board-ams-delta.c |   24 ++++++++++++++++++++++++
 1 file changed, 24 insertions(+)

diff -upr linux-2.6.36-rc3.orig/arch/arm/mach-omap1/board-ams-delta.c 
linux-2.6.36-rc3/arch/arm/mach-omap1/board-ams-delta.c
--- linux-2.6.36-rc3.orig/arch/arm/mach-omap1/board-ams-delta.c	2010-09-10 22:01:24.000000000 +0200
+++ linux-2.6.36-rc3/arch/arm/mach-omap1/board-ams-delta.c	2010-09-10 22:08:29.000000000 +0200
@@ -16,6 +16,7 @@
 #include <linux/init.h>
 #include <linux/input.h>
 #include <linux/interrupt.h>
+#include <linux/leds.h>
 #include <linux/platform_device.h>
 #include <linux/serial_8250.h>
 
@@ -222,11 +223,30 @@ static struct i2c_board_info ams_delta_c
 	},
 };
 
+#ifdef CONFIG_LEDS_TRIGGERS
+DEFINE_LED_TRIGGER(ams_delta_camera_led_trigger);
+
+static int ams_delta_camera_power(struct device *dev, int power)
+{
+	/*
+	 * turn on camera LED
+	 */
+	if (power)
+		led_trigger_event(ams_delta_camera_led_trigger, LED_FULL);
+	else
+		led_trigger_event(ams_delta_camera_led_trigger, LED_OFF);
+	return 0;
+}
+#else
+#define ams_delta_camera_power	NULL
+#endif
+
 static struct soc_camera_link __initdata ams_delta_iclink = {
 	.bus_id         = 0,	/* OMAP1 SoC camera bus */
 	.i2c_adapter_id = 1,
 	.board_info     = &ams_delta_camera_board_info[0],
 	.module_name    = "ov6650",
+	.power		= ams_delta_camera_power,
 };
 
 static struct platform_device ams_delta_camera_device = {
@@ -281,6 +301,10 @@ static void __init ams_delta_init(void)
 
 	omap1_usb_init(&ams_delta_usb_config);
 	omap1_set_camera_info(&ams_delta_camera_platform_data);
+#ifdef CONFIG_LEDS_TRIGGERS
+	led_trigger_register_simple("ams_delta_camera",
+			&ams_delta_camera_led_trigger);
+#endif
 	platform_add_devices(ams_delta_devices, ARRAY_SIZE(ams_delta_devices));

---

From: Janusz Krzysztofik <[email-masked]>
Date: Sat, 11 Sep 2010 03:34:02 +0200

Signed-off-by: Janusz Krzysztofik <[email-masked]>
---
Resend because of wrapped lines, sorry.
Janusz

v1->v2 changes:
- no functional changes,

 arch/arm/mach-omap1/devices.c             |   43 ++++++++++++++++++++++++++++++
 arch/arm/mach-omap1/include/mach/camera.h |    8 +++++
 2 files changed, 51 insertions(+)

diff -upr linux-2.6.36-rc3.orig/arch/arm/mach-omap1/devices.c linux-2.6.36-rc3/arch/arm/mach-omap1/devices.c
--- linux-2.6.36-rc3.orig/arch/arm/mach-omap1/devices.c	2010-09-03 22:29:00.000000000 +0200
+++ linux-2.6.36-rc3/arch/arm/mach-omap1/devices.c	2010-09-09 18:42:30.000000000 +0200
@@ -15,6 +15,7 @@
 #include <linux/platform_device.h>
 #include <linux/io.h>
 #include <linux/spi/spi.h>
+#include <linux/dma-mapping.h>

 
 
+
 
--- linux-2.6.36-rc3.orig/arch/arm/mach-omap1/include/mach/camera.h	2010-09-03 22:34:03.000000000 +0200
+++ linux-2.6.36-rc3/arch/arm/mach-omap1/include/mach/camera.h	2010-09-09 18:42:30.000000000 +0200

---

From: Janusz Krzysztofik <[email-masked]>
Date: Sat, 11 Sep 2010 03:21:22 +0200

This is a V4L2 driver for TI OMAP1 SoC camera interface.

Both videobuf-dma versions are supported, contig and sg, selectable with a 
module option. The former uses less processing power, but often fails to 
allocate contignuous buffer memory. The latter is free of this problem, but 
generates tens of DMA interrupts per frame. If contig memory allocation ever 
fails, the driver falls back to sg automatically on next open, but still can 
be switched back to contig manually. Both paths work stable for me, even 
under heavy load, on my OMAP1510 based Amstrad Delta videophone, that is the 
oldest, least powerfull OMAP1 implementation.

The interface generally works in pass-through mode. Since input data byte 
endianess can be swapped, it provides up to two v4l2 pixel formats per each of 
several soc_mbus formats that have their swapped endian counterparts.

Boards using this driver can provide it with the followning information:
- if and what freqency clock is expected by an on-board camera sensor,
- what is the maximum pixel clock that should be accepted from the sensor,
- what is the polarity of the sensor provided pixel clock,
- if the interface GPIO line is connected to a sensor reset/powerdown input 
  and what is the input polarity.

Signed-off-by: Janusz Krzysztofik <[email-masked]>
---
Friday 30 July 2010 13:07:42 Guennadi Liakhovetski wrote:
> So, I think, a very welcome improvement to the driver would be a 
> cleaner separation between the two cases. Don't try that much to reuse the
> code as much as possible. Would be much better to have clean separation
> between the two implementations - whether dynamically switchable at
> runtime or at config time - would be best to separate the two
> implementations to the point, where each of them becomes understandable
> and maintainable.

Guennadi,
I've tried to rearrange them spearated, as you requested, but finally decided 
to keep them together, as before, only better documented and cleaned up as 
much as possible. I'm rather satisfied with the result, but if you think it 
is still not enough understandable and maintainable, I'll take one more 
iteration and split both paths.

Besides, there are a few my not yet answered questions or suggestions (see 
http://www.spinics.net/lists/linux-media/msg21615.html for original message):

Friday 30 July 2010 20:49:05 Janusz Krzysztofik wrote:
> Friday 30 July 2010 13:07:42 Guennadi Liakhovetski wrote:
> > On Sun, 18 Jul 2010, Janusz Krzysztofik wrote:
> > > This is a V4L2 driver for TI OMAP1 SoC camera interface.

...
> > > +
> > > +	u32			reg_cache[OMAP1_CAMERA_IOSIZE / sizeof(u32)];
> >
> > Don't think you'd lose much performance without cache, for that the code
> > would become less error-prone.
>
> The reason is my bad experience with my hardware audio interface. For my
> rationale regarding OMAP McBSP register caching, please look at this
> commit:
> http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=commitdiff;h=96fbd74551e9cae8fd5e9cb62a0a19336a3064fa 
> or the foregoing discussions (links both to my initial and final submitions):
> http://thread.gmane.org/gmane.linux.alsa.devel/65675
> http://thread.gmane.org/gmane.linux.ports.arm.omap/29708
>
> However, I haven't noticed any similiar flaws in camera interface yet, so
> if you think that caching would not be helpful in any possible future
> (context save/restore) enhancements here, then I can drop it.
...
> > Don't you have to update the cache on a direct read?
>
> If I do it unconditionally, I'd loose the read error prevention function of
> register caching idea.

I've kept register caching for now, only replaced a few not intentional reads 
from hardware with more sensible reads from cache. Please confirm if you 
prefere me dropping register caching anyways.

...
> > > +static void videobuf_done(struct omap1_cam_dev *pcdev,
> > > +		enum videobuf_state result)
> > > +{
> > > +	...
> > > +	if (waitqueue_active(&vb->done)) {
> >
> > I'm not sure this is a good idea. Why are you reusing the buffer, if
> > noone is waiting for it _now_? It can well be, that the task is doing
> > something else atm. Like processing the previous frame. Or it has been
> > preempted by another process, before it called poll() or select() on your
> > device?
>
> I've introduced this way of videobuf handling after learning, form the
> Documentation/video4linux/videobuf, what the first step of filling a buffer
> should be:
>
> "- Obtain the next available buffer and make sure that somebody is actually
>    waiting for it."
>
> Since I had no idea how I could implement it, I decided to do it a bit
> differently: start filling a buffer unconditionally, then drop its contents
> if not awaited by any user when done
> 
> > > ...
> > > +	} else if (pcdev->ready) {
> > > +		dev_dbg(dev, "%s: nobody waiting on videobuf, swap with next\n",
> > > +				__func__);
> >
> > Not sure this is a wise decision... If there are more buffers in the
> > queue, they stay there waiting. Best remove this waitqueue_active() check
> > entirely, just complete buffers and wake up the user regardless. If no
> > more buffers - go to sleep.
>
> If you think I don't have to follow the above mentioned requirement for not
> servicing a buffer unless someone is waiting for it, then yes, I'll drop
> these overcomplicated bits. Please confirm.

One more reference to Documentation/video4linux/videobuf:

"It is equally possible that nobody is yet interested in the
buffer; the driver should not remove it from the list or fill it until a
process is waiting on it."

Please review my now better commented code again, and confirm if you still 
expect me to rearrange it, ignoring the documentation provided requirements.

...
> > > +static int omap1_cam_set_crop(struct soc_camera_device *icd,
> > > +			       struct v4l2_crop *crop)
> > > +{
> > > ...
> > > +	icd->user_width	 = rect->width;
> > > +	icd->user_height = rect->height;
> >
> > No, this is wrong. user_width and user_height are _user_ sizes, i.e., a
> > result sensor cropping and scaling and host cropping and scaling. Whereas
> > set_crop sets sensor input rectangle.
>
> I'm not sure if I understand what you mean. Should I drop these
> assignments? Or correct them? If yes, how they should be calculated?
>
> In Documentation/video4linux/soc-camera.txt, you say:
> "The core updates these fields upon successful completion of a .s_fmt()
> call, but if these fields change elsewhere, e.g., during .s_crop()
> processing, the host driver is responsible for updating them."
>
> I thought this is the case we're dealing with here.

Please confirm if I my understanding of what the documentation says is wrong 
and I should really correct the above.

Thanks,
Janusz

v1->v2 changes:

requested by Guennadi Liakhovetski (thanks!):
- first try contig, and if it fails - fall back to sg; invalidates next two,
- invalidated: Kconfig: VIDEO_OMAP1_SG: not need "if", the "depends on" should 
  suffice,
- invalidated: include both <media/videobuf-dma-contig.h> and 
  <media/videobuf-dma-sg.h> headers,
- extensively document buffer manipulations, better yet clean it up,
- a copyright / licence header was missing form a header file, 
- need to #include <linux/bitops.h> if using BIT() macro,
- don't need macros representing frequencies - use numbers directly,
- add a few missing "static" qualifiers,
- use u32 type for register content handling,
- some cached registers were unnecessarily read from the hardware directly,
- use true/false constants instead of 0/1 for booleans,
- avoid assigning variables inside other constructs,
- don't need to test if RAZ_FIFO is cleared,
- no need to split "\n" to a new line, don't worry about > 80 characters,
- don't increment field_count in case of a VIDEOBUF_ERROR,
- adjust mbus format codes to the new names,
- make is_dma_aligned() return value a bool,
- no need to align frame line lenghts to integer number of DMA elements,
- drop a few superflous whitespace, braces, etc.,
- use correct multiline comment style,
- follow some good-style rules when defining a multi-line function-like macro,
- drop tests for impossible bool < 0,
- that's not an error when S_FMT sets a format different from what the user
  has requested, just use whatever you managed to configure,
- as a general rule, try_fmt shouldn't fail,
- use sensor_reset() instead of duplicating its code,
- first shut down the hardware, and only then unconfigure software,

suggested by Ralph Corderoy (thanks!):
- include vb->state's value in the debug messages instead of just "unknown",
- correct a few print formats,
- don't use goto if return is all that needs to be done,

other:
- correct a few issues reported with "checkpatch.pl --strict".
- refreshed against linux-2.6.36-rc3

 drivers/media/video/Kconfig        |    8
 drivers/media/video/Makefile       |    1
 drivers/media/video/omap1_camera.c | 1781 +++++++++++++++++++++++++++++++++++++
 include/media/omap1_camera.h       |   35
 4 files changed, 1825 insertions(+)

diff -upr linux-2.6.36-rc3.orig/drivers/media/video/Kconfig linux-2.6.36-rc3/drivers/media/video/Kconfig
--- linux-2.6.36-rc3.orig/drivers/media/video/Kconfig	2010-09-03 22:29:37.000000000 +0200
+++ linux-2.6.36-rc3/drivers/media/video/Kconfig	2010-09-09 17:55:52.000000000 +0200
@@ -890,6 +890,14 @@ config VIDEO_SH_MOBILE_CEU
 	---help---
 	  This is a v4l2 driver for the SuperH Mobile CEU Interface
 
+config VIDEO_OMAP1
+	tristate "OMAP1 Camera Interface driver"
+	depends on VIDEO_DEV && ARCH_OMAP1 && SOC_CAMERA
+	select VIDEOBUF_DMA_CONTIG
+	select VIDEOBUF_DMA_SG
+	---help---
+	  This is a v4l2 driver for the TI OMAP1 camera interface
+
 config VIDEO_OMAP2
 	tristate "OMAP2 Camera Capture Interface driver"
 	depends on VIDEO_DEV && ARCH_OMAP2
diff -upr linux-2.6.36-rc3.orig/drivers/media/video/Makefile linux-2.6.36-rc3/drivers/media/video/Makefile
--- linux-2.6.36-rc3.orig/drivers/media/video/Makefile	2010-09-03 22:29:37.000000000 +0200
+++ linux-2.6.36-rc3/drivers/media/video/Makefile	2010-09-09 17:55:52.000000000 +0200
@@ -163,6 +163,7 @@ obj-$(CONFIG_VIDEO_MX3)			+= mx3_camera.
 obj-$(CONFIG_VIDEO_PXA27x)		+= pxa_camera.o
 obj-$(CONFIG_VIDEO_SH_MOBILE_CSI2)	+= sh_mobile_csi2.o
 obj-$(CONFIG_VIDEO_SH_MOBILE_CEU)	+= sh_mobile_ceu_camera.o
+obj-$(CONFIG_VIDEO_OMAP1)		+= omap1_camera.o
 obj-$(CONFIG_VIDEO_SAMSUNG_S5P_FIMC) 	+= s5p-fimc/
 
 obj-$(CONFIG_ARCH_DAVINCI)		+= davinci/
diff -upr linux-2.6.36-rc3.orig/drivers/media/video/omap1_camera.c linux-2.6.36-rc3/drivers/media/video/omap1_camera.c
--- linux-2.6.36-rc3.orig/drivers/media/video/omap1_camera.c	2010-09-03 22:34:02.000000000 +0200
+++ linux-2.6.36-rc3/drivers/media/video/omap1_camera.c	2010-09-09 15:58:22.000000000 +0200
@@ -0,0 +1,1781 @@
+/*
+ * V4L2 SoC Camera driver for OMAP1 Camera Interface
+ *
+ * Copyright (C) 2010, Janusz Krzysztofik <[email-masked]>
+ *
+ * Based on V4L2 Driver for i.MXL/i.MXL camera (CSI) host
+ * Copyright (C) 2008, Paulius Zaleckas <[email-masked]>
+ * Copyright (C) 2009, Darius Augulis <[email-masked]>
+ *
+ * Based on PXA SoC camera driver
+ * Copyright (C) 2006, Sascha Hauer, Pengutronix
+ * Copyright (C) 2008, Guennadi Liakhovetski <[email-masked]>
+ *
+ * Hardware specific bits initialy based on former work by Matt Callow
+ * drivers/media/video/omap/omap1510cam.c
+ * Copyright (C) 2006 Matt Callow
+ *
+ * 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.
+ */
+
+
+#include <linux/clk.h>
+#include <linux/dma-mapping.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/version.h>
+
+#include <media/omap1_camera.h>
+#include <media/soc_camera.h>
+#include <media/soc_mediabus.h>
+#include <media/videobuf-dma-contig.h>
+#include <media/videobuf-dma-sg.h>
+
+#include <plat/dma.h>
+
+
+#define DRIVER_NAME		"omap1-camera"
+#define VERSION_CODE		KERNEL_VERSION(0, 0, 1)
+
+
+/*
+ * ---------------------------------------------------------------------------
+ *  OMAP1 Camera Interface registers
+ * ---------------------------------------------------------------------------
+ */
+
+#define REG_CTRLCLOCK		0x00
+#define REG_IT_STATUS		0x04
+#define REG_MODE		0x08
+#define REG_STATUS		0x0C
+#define REG_CAMDATA		0x10
+#define REG_GPIO		0x14
+#define REG_PEAK_COUNTER	0x18
+
+/* CTRLCLOCK bit shifts */
+#define LCLK_EN			BIT(7)
+#define DPLL_EN			BIT(6)
+#define MCLK_EN			BIT(5)
+#define CAMEXCLK_EN		BIT(4)
+#define POLCLK			BIT(3)
+#define FOSCMOD_SHIFT		0
+#define FOSCMOD_MASK		(0x7 << FOSCMOD_SHIFT)
+#define FOSCMOD_12MHz		0x0
+#define FOSCMOD_6MHz		0x2
+#define FOSCMOD_9_6MHz		0x4
+#define FOSCMOD_24MHz		0x5
+#define FOSCMOD_8MHz		0x6
+
+/* IT_STATUS bit shifts */
+#define DATA_TRANSFER		BIT(5)
+#define FIFO_FULL		BIT(4)
+#define H_DOWN			BIT(3)
+#define H_UP			BIT(2)
+#define V_DOWN			BIT(1)
+#define V_UP			BIT(0)
+
+/* MODE bit shifts */
+#define RAZ_FIFO		BIT(18)
+#define EN_FIFO_FULL		BIT(17)
+#define EN_NIRQ			BIT(16)
+#define THRESHOLD_SHIFT		9
+#define THRESHOLD_MASK		(0x7f << THRESHOLD_SHIFT)
+#define DMA			BIT(8)
+#define EN_H_DOWN		BIT(7)
+#define EN_H_UP			BIT(6)
+#define EN_V_DOWN		BIT(5)
+#define EN_V_UP			BIT(4)
+#define ORDERCAMD		BIT(3)
+
+#define IRQ_MASK		(EN_V_UP | EN_V_DOWN | EN_H_UP | EN_H_DOWN | \
+				 EN_NIRQ | EN_FIFO_FULL)
+
+/* STATUS bit shifts */
+#define HSTATUS			BIT(1)
+#define VSTATUS			BIT(0)
+
+/* GPIO bit shifts */
+#define CAM_RST			BIT(0)
+
+/* end of OMAP1 Camera Interface registers */
+
+
+#define SOCAM_BUS_FLAGS	(SOCAM_MASTER | \
+			SOCAM_HSYNC_ACTIVE_HIGH | SOCAM_VSYNC_ACTIVE_HIGH | \
+			SOCAM_PCLK_SAMPLE_RISING | SOCAM_PCLK_SAMPLE_FALLING | \
+			SOCAM_DATA_ACTIVE_HIGH | SOCAM_DATAWIDTH_8)
+
+
+#define FIFO_SIZE		((THRESHOLD_MASK >> THRESHOLD_SHIFT) + 1)
+#define FIFO_SHIFT		__fls(FIFO_SIZE)
+
+#define DMA_BURST_SHIFT		(1 + OMAP_DMA_DATA_BURST_4)
+#define DMA_BURST_SIZE		(1 << DMA_BURST_SHIFT)
+
+#define DMA_ELEMENT_SHIFT	OMAP_DMA_DATA_TYPE_S32
+#define DMA_ELEMENT_SIZE	(1 << DMA_ELEMENT_SHIFT)
+
+#define DMA_FRAME_SHIFT_CONTIG	(FIFO_SHIFT - 1)
+#define DMA_FRAME_SHIFT_SG	DMA_BURST_SHIFT
+
+#define DMA_FRAME_SHIFT(x)	(x ? DMA_FRAME_SHIFT_SG : \
+						DMA_FRAME_SHIFT_CONTIG)
+#define DMA_FRAME_SIZE(x)	(1 << DMA_FRAME_SHIFT(x))
+#define DMA_SYNC		OMAP_DMA_SYNC_FRAME
+#define THRESHOLD_LEVEL		DMA_FRAME_SIZE
+
+
+#define MAX_VIDEO_MEM		4	/* arbitrary video memory limit in MB */
+
+
+/*
+ * Structures
+ */
+
+/* buffer for one video frame */
+struct omap1_cam_buf {
+	struct videobuf_buffer		vb;
+	enum v4l2_mbus_pixelcode	code;
+	int				inwork;
+	struct scatterlist		*sgbuf;
+	int				sgcount;
+	int				bytes_left;
+	enum videobuf_state		result;
+};
+
+struct omap1_cam_dev {
+	struct soc_camera_host		soc_host;
+	struct soc_camera_device	*icd;
+	struct clk			*clk;
+
+	unsigned int			irq;
+	void __iomem			*base;
+
+	int				dma_ch;
+
+	struct omap1_cam_platform_data	*pdata;
+	struct resource			*res;
+	unsigned long			pflags;
+	unsigned long			camexclk;
+
+	struct list_head		capture;
+
+	/* lock used to protect videobuf */
+	spinlock_t			lock;
+
+	u32				reg_cache[OMAP1_CAMERA_IOSIZE /
+							sizeof(u32)];
+
+	/* Pointers to DMA buffers */
+	struct omap1_cam_buf		*active;
+	struct omap1_cam_buf		*ready;
+
+	enum omap1_cam_vb_mode		vb_mode;
+	int				(*mmap_mapper)(struct videobuf_queue *q,
+						struct videobuf_buffer *buf,
+						struct vm_area_struct *vma);
+};
+
+
+static void cam_write(struct omap1_cam_dev *pcdev, u16 reg, u32 val)
+{
+	pcdev->reg_cache[reg / sizeof(u32)] = val;
+	__raw_writel(val, pcdev->base + reg);
+}
+
+static u32 cam_read(struct omap1_cam_dev *pcdev, u16 reg, bool from_cache)
+{
+	return !from_cache ? __raw_readl(pcdev->base + reg) :
+			pcdev->reg_cache[reg / sizeof(u32)];
+}
+
+#define CAM_READ(pcdev, reg) \
+		cam_read(pcdev, REG_##reg, false)
+#define CAM_WRITE(pcdev, reg, val) \
+		cam_write(pcdev, REG_##reg, val)
+#define CAM_READ_CACHE(pcdev, reg) \
+		cam_read(pcdev, REG_##reg, true)
+
+/*
+ *  Videobuf operations
+ */
+static int omap1_videobuf_setup(struct videobuf_queue *vq, unsigned int *count,
+		unsigned int *size)
+{
+	struct soc_camera_device *icd = vq->priv_data;
+	int bytes_per_line = soc_mbus_bytes_per_line(icd->user_width,
+			icd->current_fmt->host_fmt);
+	struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent);
+	struct omap1_cam_dev *pcdev = ici->priv;
+
+	if (bytes_per_line < 0)
+		return bytes_per_line;
+
+	*size = bytes_per_line * icd->user_height;
+
+	if (!*count || *count < OMAP1_CAMERA_MIN_BUF_COUNT(pcdev->vb_mode))
+		*count = OMAP1_CAMERA_MIN_BUF_COUNT(pcdev->vb_mode);
+
+	if (*size * *count > MAX_VIDEO_MEM * 1024 * 1024)
+		*count = (MAX_VIDEO_MEM * 1024 * 1024) / *size;
+
+	dev_dbg(icd->dev.parent,
+			"%s: count=%d, size=%d\n", __func__, *count, *size);
+
+	return 0;
+}
+
+static void free_buffer(struct videobuf_queue *vq, struct omap1_cam_buf *buf,
+		enum omap1_cam_vb_mode vb_mode)
+{
+	struct videobuf_buffer *vb = &buf->vb;
+
+	BUG_ON(in_interrupt());
+
+	videobuf_waiton(vb, 0, 0);
+
+	if (vb_mode == CONTIG) {
+		videobuf_dma_contig_free(vq, vb);
+	} else {
+		struct soc_camera_device *icd = vq->priv_data;
+		struct device *dev = icd->dev.parent;
+		struct videobuf_dmabuf *dma = videobuf_to_dma(vb);
+
+		videobuf_dma_unmap(dev, dma);
+		videobuf_dma_free(dma);
+	}
+
+	vb->state = VIDEOBUF_NEEDS_INIT;
+}
+
+static int omap1_videobuf_prepare(struct videobuf_queue *vq,
+		struct videobuf_buffer *vb, enum v4l2_field field)
+{
+	struct soc_camera_device *icd = vq->priv_data;
+	struct omap1_cam_buf *buf = container_of(vb, struct omap1_cam_buf, vb);
+	int bytes_per_line = soc_mbus_bytes_per_line(icd->user_width,
+			icd->current_fmt->host_fmt);
+	struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent);
+	struct omap1_cam_dev *pcdev = ici->priv;
+	int ret;
+
+	if (bytes_per_line < 0)
+		return bytes_per_line;
+
+	WARN_ON(!list_empty(&vb->queue));
+
+	BUG_ON(NULL == icd->current_fmt);
+
+	buf->inwork = 1;
+
+	if (buf->code != icd->current_fmt->code || vb->field != field ||
+			vb->width  != icd->user_width ||
+			vb->height != icd->user_height) {
+		buf->code  = icd->current_fmt->code;
+		vb->width  = icd->user_width;
+		vb->height = icd->user_height;
+		vb->field  = field;
+		vb->state  = VIDEOBUF_NEEDS_INIT;
+	}
+
+	vb->size = bytes_per_line * vb->height;
+
+	if (vb->baddr && vb->bsize < vb->size) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	if (vb->state == VIDEOBUF_NEEDS_INIT) {
+		ret = videobuf_iolock(vq, vb, NULL);
+		if (ret)
+			goto fail;
+
+		vb->state = VIDEOBUF_PREPARED;
+	}
+	buf->inwork = 0;
+
+	return 0;
+fail:
+	free_buffer(vq, buf, pcdev->vb_mode);
+out:
+	buf->inwork = 0;
+	return ret;
+}
+
+static void set_dma_dest_params(int dma_ch, struct omap1_cam_buf *buf,
+		enum omap1_cam_vb_mode vb_mode)
+{
+	dma_addr_t dma_addr;
+	unsigned int block_size;
+
+	if (vb_mode == CONTIG) {
+		dma_addr = videobuf_to_dma_contig(&buf->vb);
+		block_size = buf->vb.size;
+	} else {
+		if (WARN_ON(!buf->sgbuf)) {
+			buf->result = VIDEOBUF_ERROR;
+			return;
+		}
+		dma_addr = sg_dma_address(buf->sgbuf);
+		if (WARN_ON(!dma_addr)) {
+			buf->sgbuf = NULL;
+			buf->result = VIDEOBUF_ERROR;
+			return;
+		}
+		block_size = sg_dma_len(buf->sgbuf);
+		if (WARN_ON(!block_size)) {
+			buf->sgbuf = NULL;
+			buf->result = VIDEOBUF_ERROR;
+			return;
+		}
+		if (unlikely(buf->bytes_left < block_size))
+			block_size = buf->bytes_left;
+		if (WARN_ON(dma_addr & (DMA_FRAME_SIZE(vb_mode) *
+				DMA_ELEMENT_SIZE - 1))) {
+			dma_addr = ALIGN(dma_addr, DMA_FRAME_SIZE(vb_mode) *
+					DMA_ELEMENT_SIZE);
+			block_size &= ~(DMA_FRAME_SIZE(vb_mode) *
+					DMA_ELEMENT_SIZE - 1);
+		}
+		buf->bytes_left -= block_size;
+		buf->sgcount++;
+	}
+
+	omap_set_dma_dest_params(dma_ch,
+		OMAP_DMA_PORT_EMIFF, OMAP_DMA_AMODE_POST_INC, dma_addr, 0, 0);
+	omap_set_dma_transfer_params(dma_ch,
+		OMAP_DMA_DATA_TYPE_S32, DMA_FRAME_SIZE(vb_mode),
+		block_size >> (DMA_FRAME_SHIFT(vb_mode) + DMA_ELEMENT_SHIFT),
+		DMA_SYNC, 0, 0);
+}
+
+static struct omap1_cam_buf *prepare_next_vb(struct omap1_cam_dev *pcdev)
+{
+	struct omap1_cam_buf *buf;
+
+	/*
+	 * If there is already a buffer pointed out by the pcdev->ready,
+	 * (re)use it, otherwise try to fetch and configure a new one.
+	 */
+	buf = pcdev->ready;
+	if (!buf) {
+		if (list_empty(&pcdev->capture))
+			return buf;
+		buf = list_entry(pcdev->capture.next,
+				struct omap1_cam_buf, vb.queue);
+		buf->vb.state = VIDEOBUF_ACTIVE;
+		pcdev->ready = buf;
+		list_del_init(&buf->vb.queue);
+	}
+
+	if (pcdev->vb_mode == CONTIG) {
+		/*
+		 * In CONTIG mode, we can safely enter next buffer parameters
+		 * into the DMA programming register set after the DMA
+		 * has already been activated on the previous buffer
+		 */
+		set_dma_dest_params(pcdev->dma_ch, buf, pcdev->vb_mode);
+	} else {
+		/*
+		 * In SG mode, the above is not safe since there are probably
+		 * a bunch of sgbufs from previous sglist still pending.
+		 * Instead, mark the sglist fresh for the upcoming
+		 * try_next_sgbuf().
+		 */
+		buf->sgbuf = NULL;
+	}
+
+	return buf;
+}
+
+static struct scatterlist *try_next_sgbuf(int dma_ch, struct omap1_cam_buf *buf)
+{
+	struct scatterlist *sgbuf;
+
+	if (likely(buf->sgbuf)) {
+		/* current sglist is active */
+		if (unlikely(!buf->bytes_left)) {
+			/* indicate sglist complete */
+			sgbuf = NULL;
+		} else {
+			/* process next sgbuf */
+			sgbuf = sg_next(buf->sgbuf);
+			if (WARN_ON(!sgbuf)) {
+				buf->result = VIDEOBUF_ERROR;
+			} else if (WARN_ON(!sg_dma_len(sgbuf))) {
+				sgbuf = NULL;
+				buf->result = VIDEOBUF_ERROR;
+			}
+		}
+		buf->sgbuf = sgbuf;
+	} else {
+		/* sglist is fresh, initialize it before using */
+		struct videobuf_dmabuf *dma = videobuf_to_dma(&buf->vb);
+
+		sgbuf = dma->sglist;
+		if (!(WARN_ON(!sgbuf))) {
+			buf->sgbuf = sgbuf;
+			buf->sgcount = 0;
+			buf->bytes_left = buf->vb.size;
+			buf->result = VIDEOBUF_DONE;
+		}
+	}
+	if (sgbuf)
+		/*
+		 * Put our next sgbuf parameters (address, size)
+		 * into the DMA programming register set.
+		 */
+		set_dma_dest_params(dma_ch, buf, SG);
+
+	return sgbuf;
+}
+
+static void start_capture(struct omap1_cam_dev *pcdev)
+{
+	struct omap1_cam_buf *buf = pcdev->active;
+	u32 ctrlclock = CAM_READ_CACHE(pcdev, CTRLCLOCK);
+	u32 mode = CAM_READ_CACHE(pcdev, MODE) & ~EN_V_DOWN;
+
+	if (WARN_ON(!buf))
+		return;
+
+	/*
+	 * Enable start of frame interrupt, which we will use for activating
+	 * our end of frame watchdog when capture actually starts.
+	 */
+	mode |= EN_V_UP;
+
+	if (unlikely(ctrlclock & LCLK_EN))
+		/* stop pixel clock before FIFO reset */
+		CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock & ~LCLK_EN);
+	/* reset FIFO */
+	CAM_WRITE(pcdev, MODE, mode | RAZ_FIFO);
+
+	omap_start_dma(pcdev->dma_ch);
+
+	if (pcdev->vb_mode == SG) {
+		/*
+		 * In SG mode, it's a good moment for fetching next sgbuf
+		 * from the current sglist and, if available, already putting
+		 * its parameters into the DMA programming register set.
+		 */
+		try_next_sgbuf(pcdev->dma_ch, buf);
+	}
+
+	/* (re)enable pixel clock */
+	CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock | LCLK_EN);
+	/* release FIFO reset */
+	CAM_WRITE(pcdev, MODE, mode);
+}
+
+static void suspend_capture(struct omap1_cam_dev *pcdev)
+{
+	u32 ctrlclock = CAM_READ_CACHE(pcdev, CTRLCLOCK);
+
+	CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock & ~LCLK_EN);
+	omap_stop_dma(pcdev->dma_ch);
+}
+
+static void disable_capture(struct omap1_cam_dev *pcdev)
+{
+	u32 mode = CAM_READ_CACHE(pcdev, MODE);
+
+	CAM_WRITE(pcdev, MODE, mode & ~(IRQ_MASK | DMA));
+}
+
+static void omap1_videobuf_queue(struct videobuf_queue *vq,
+						struct videobuf_buffer *vb)
+{
+	struct soc_camera_device *icd = vq->priv_data;
+	struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent);
+	struct omap1_cam_dev *pcdev = ici->priv;
+	struct omap1_cam_buf *buf;
+	u32 mode;
+
+	list_add_tail(&vb->queue, &pcdev->capture);
+	vb->state = VIDEOBUF_QUEUED;
+
+	if (pcdev->active)
+		return;
+
+	WARN_ON(pcdev->ready);
+
+	buf = prepare_next_vb(pcdev);
+	if (WARN_ON(!buf))
+		return;
+
+	pcdev->active = buf;
+	pcdev->ready = NULL;
+
+	dev_dbg(icd->dev.parent,
+		"%s: capture not active, setup FIFO, start DMA\n", __func__);
+	mode = CAM_READ_CACHE(pcdev, MODE) & ~THRESHOLD_MASK;
+	mode |= THRESHOLD_LEVEL(pcdev->vb_mode) << THRESHOLD_SHIFT;
+	CAM_WRITE(pcdev, MODE, mode | EN_FIFO_FULL | DMA);
+
+	if (pcdev->vb_mode == SG) {
+		/*
+		 * In SG mode, the above prepare_next_vb() didn't actually
+		 * put anything into the DMA programming register set,
+		 * so we have to do it now, before activating DMA.
+		 */
+		try_next_sgbuf(pcdev->dma_ch, buf);
+	}
+
+	start_capture(pcdev);
+}
+
+static void omap1_videobuf_release(struct videobuf_queue *vq,
+				 struct videobuf_buffer *vb)
+{
+	struct omap1_cam_buf *buf =
+			container_of(vb, struct omap1_cam_buf, vb);
+	struct soc_camera_device *icd = vq->priv_data;
+	struct device *dev = icd->dev.parent;
+	struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent);
+	struct omap1_cam_dev *pcdev = ici->priv;
+
+	switch (vb->state) {
+	case VIDEOBUF_DONE:
+		dev_dbg(dev, "%s (done)\n", __func__);
+		break;
+	case VIDEOBUF_ACTIVE:
+		dev_dbg(dev, "%s (active)\n", __func__);
+		break;
+	case VIDEOBUF_QUEUED:
+		dev_dbg(dev, "%s (queued)\n", __func__);
+		break;
+	case VIDEOBUF_PREPARED:
+		dev_dbg(dev, "%s (prepared)\n", __func__);
+		break;
+	default:
+		dev_dbg(dev, "%s (unknown %d)\n", __func__, vb->state);
+		break;
+	}
+
+	free_buffer(vq, buf, pcdev->vb_mode);
+}
+
+static void videobuf_done(struct omap1_cam_dev *pcdev,
+		enum videobuf_state result)
+{
+	struct omap1_cam_buf *buf = pcdev->active;
+	struct videobuf_buffer *vb;
+	struct device *dev = pcdev->icd->dev.parent;
+
+	if (WARN_ON(!buf)) {
+		suspend_capture(pcdev);
+		disable_capture(pcdev);
+		return;
+	}
+
+	if (result == VIDEOBUF_ERROR)
+		suspend_capture(pcdev);
+
+	vb = &buf->vb;
+	if (waitqueue_active(&vb->done)) {
+		if (!pcdev->ready && result != VIDEOBUF_ERROR)
+			/*
+			 * No next buffer has been entered into the DMA
+			 * programming register set on time, so best we can do
+			 * is stopping the capture before last DMA block,
+			 * whether our CONTIG mode whole buffer or its last
+			 * sgbuf in SG mode, gets overwritten by next frame.
+			 */
+			suspend_capture(pcdev);
+		vb->state = result;
+		do_gettimeofday(&vb->ts);
+		if (result != VIDEOBUF_ERROR)
+			vb->field_count++;
+		wake_up(&vb->done);
+
+		/* shift in next buffer */
+		buf = pcdev->ready;
+		pcdev->active = buf;
+		pcdev->ready = NULL;
+
+		if (!buf) {
+			/*
+			 * No next buffer was ready on time (see above), so
+			 * indicate error condition to force capture restart or
+			 * stop, depending on next buffer already queued or not.
+			 */
+			result = VIDEOBUF_ERROR;
+			prepare_next_vb(pcdev);
+
+			buf = pcdev->ready;
+			pcdev->active = buf;
+			pcdev->ready = NULL;
+		}
+	} else if (pcdev->ready) {
+		/*
+		 * In both CONTIG and SG mode, the DMA engine has (might)
+		 * already been autoreinitialized with the preprogrammed
+		 * pcdev->ready buffer.  We can either accept this fact
+		 * and just swap the buffers, or provoke an error condition
+		 * and restart capture.  The former seems less intrusive.
+		 */
+		dev_dbg(dev, "%s: nobody waiting on videobuf, swap with next\n",
+				__func__);
+		pcdev->active = pcdev->ready;
+
+		if (pcdev->vb_mode == SG) {
+			/*
+			 * In SG mode, we have to make sure that the buffer we
+			 * are putting back into the pcdev->ready is marked
+			 * fresh.
+			 */
+			buf->sgbuf = NULL;
+		}
+		pcdev->ready = buf;
+
+		buf = pcdev->active;
+	} else {
+		/*
+		 * No next buffer has been entered into
+		 * the DMA programming register set on time.
+		 */
+		if (pcdev->vb_mode == CONTIG) {
+			/*
+			 * In CONTIG mode, the DMA engine has already been
+			 * reinitialized with the current buffer. Best we can do
+			 * is not touching it.
+			 */
+			dev_dbg(dev,
+				"%s: nobody waiting on videobuf, reuse it\n",
+				__func__);
+		} else {
+			/*
+			 * In SG mode, the DMA engine has just been
+			 * autoreinitialized with the last sgbuf from the
+			 * current list. Restart capture in order to transfer
+			 * next frame start into the first sgbuf, not the last
+			 * one.
+			 */
+			if (result != VIDEOBUF_ERROR) {
+				suspend_capture(pcdev);
+				result = VIDEOBUF_ERROR;
+			}
+		}
+	}
+
+	if (!buf) {
+		dev_dbg(dev, "%s: no more videobufs, stop capture\n", __func__);
+		disable_capture(pcdev);
+		return;
+	}
+
+	if (pcdev->vb_mode == CONTIG) {
+		/*
+		 * In CONTIG mode, the current buffer parameters had already
+		 * been entered into the DMA programming register set while the
+		 * buffer was fetched with prepare_next_vb(), they may have also
+		 * been transfered into the runtime set and already active if
+		 * the DMA still running.
+		 */
+	} else {
+		/* In SG mode, extra steps are required */
+		if (result == VIDEOBUF_ERROR)
+			/* make sure we (re)use sglist from start on error */
+			buf->sgbuf = NULL;
+
+		/*
+		 * In any case, enter the next sgbuf parameters into the DMA
+		 * programming register set.  They will be used either during
+		 * nearest DMA autoreinitialization or, in case of an error,
+		 * on DMA startup below.
+		 */
+		try_next_sgbuf(pcdev->dma_ch, buf);
+	}
+
+	if (result == VIDEOBUF_ERROR) {
+		dev_dbg(dev, "%s: videobuf error; reset FIFO, restart DMA\n",
+				__func__);
+		start_capture(pcdev);
+		/*
+		 * In SG mode, the above also resulted in the next sgbuf
+		 * parameters being entered into the DMA programming register
+		 * set, making them ready for next DMA autoreinitialization.
+		 */
+	}
+
+	/*
+	 * Finally, try fetching next buffer.  In CONTIG mode, it will also
+	 * get entered it into the DMA programming register set,
+	 * making it ready for next DMA autoreinitialization.
+	 */
+	prepare_next_vb(pcdev);
+}
+
+static void dma_isr(int channel, unsigned short status, void *data)
+{
+	struct omap1_cam_dev *pcdev = data;
+	struct omap1_cam_buf *buf = pcdev->active;
+	unsigned long flags;
+
+	spin_lock_irqsave(&pcdev->lock, flags);
+
+	if (WARN_ON(!buf)) {
+		suspend_capture(pcdev);
+		disable_capture(pcdev);
+		goto out;
+	}
+
+	if (pcdev->vb_mode == CONTIG) {
+		/*
+		 * In CONTIG mode, assume we have just managed to collect the
+		 * whole frame, hopefully before our end of frame watchdog is
+		 * triggered. Then, all we have to do is disabling the watchdog
+		 * for this frame, and calling videobuf_done() with success
+		 * indicated.
+		 */
+		CAM_WRITE(pcdev, MODE,
+				CAM_READ_CACHE(pcdev, MODE) & ~EN_V_DOWN);
+		videobuf_done(pcdev, VIDEOBUF_DONE);
+	} else {
+		/*
+		 * In SG mode, we have to process every sgbuf from the current
+		 * sglist, one after another.
+		 */
+		if (buf->sgbuf) {
+			/*
+			 * Current sglist not completed yet, try fetching next
+			 * sgbuf, hopefully putting it into the DMA programming
+			 * register set, making it ready for next DMA
+			 * autoreinitialization.
+			 */
+			try_next_sgbuf(pcdev->dma_ch, buf);
+			if (buf->sgbuf)
+				goto out;
+
+			/* no more sgbufs in the current sglist */
+			if (buf->result != VIDEOBUF_ERROR) {
+				/*
+				 * Video frame collected without errors, we can
+				 * prepare for collecting a next one as soon as
+				 * DMA gets autoreinitialized after the currennt
+				 * (last) sgbuf is completed.
+				 */
+				buf = prepare_next_vb(pcdev);
+				if (!buf)
+					goto out;
+
+				try_next_sgbuf(pcdev->dma_ch, buf);
+				goto out;
+			}
+		}
+		/* end of videobuf */
+		videobuf_done(pcdev, buf->result);
+	}
+
+out:
+	spin_unlock_irqrestore(&pcdev->lock, flags);
+}
+
+static irqreturn_t cam_isr(int irq, void *data)
+{
+	struct omap1_cam_dev *pcdev = data;
+	struct device *dev = pcdev->icd->dev.parent;
+	struct omap1_cam_buf *buf = pcdev->active;
+	u32 it_status;
+	unsigned long flags;
+
+	it_status = CAM_READ(pcdev, IT_STATUS);
+	if (!it_status)
+		return IRQ_NONE;
+
+	spin_lock_irqsave(&pcdev->lock, flags);
+
+	if (WARN_ON(!buf)) {
+		dev_warn(dev, "%s: unhandled camera interrupt, status == "
+				"0x%0x\n", __func__, it_status);
+		suspend_capture(pcdev);
+		disable_capture(pcdev);
+		goto out;
+	}
+
+	if (unlikely(it_status & FIFO_FULL)) {
+		dev_warn(dev, "%s: FIFO overflow\n", __func__);
+
+	} else if (it_status & V_DOWN) {
+		/* end of video frame watchdog */
+		if (pcdev->vb_mode == CONTIG) {
+			/*
+			 * In CONTIG mode, the watchdog is disabled with
+			 * successful DMA end of block interrupt, and reenabled
+			 * on next frame start. If we get here, there is nothing
+			 * to check, we must be out of sync.
+			 */
+		} else {
+			if (buf->sgcount == 2) {
+				/*
+				 * If exactly 2 sgbufs from the next sglist has
+				 * been programmed into the DMA engine (the
+				 * frist one already transfered into the DMA
+				 * runtime register set, the second one still
+				 * in the programming set), then we are in sync.
+				 */
+				goto out;
+			}
+		}
+		dev_notice(dev, "%s: unexpected end of video frame\n",
+				__func__);
+
+	} else if (it_status & V_UP) {
+		u32 mode;
+
+		if (pcdev->vb_mode == CONTIG) {
+			/*
+			 * In CONTIG mode, we need this interrupt every frame
+			 * in oredr to reenable our end of frame watchdog.
+			 */
+			mode = CAM_READ_CACHE(pcdev, MODE);
+		} else {
+			/*
+			 * In SG mode, the below enabled end of frame watchdog
+			 * is kept on permanently, so we can turn this one shot
+			 * setup off.
+			 */
+			mode = CAM_READ_CACHE(pcdev, MODE) & ~EN_V_UP;
+		}
+
+		if (!(mode & EN_V_DOWN)) {
+			/* (re)enable end of frame watchdog interrupt */
+			mode |= EN_V_DOWN;
+		}
+		CAM_WRITE(pcdev, MODE, mode);
+		goto out;
+
+	} else {
+		dev_warn(dev, "%s: unhandled camera interrupt, status == "
+				"0x%0x\n", __func__, it_status);
+		goto out;
+	}
+
+	videobuf_done(pcdev, VIDEOBUF_ERROR);
+out:
+	spin_unlock_irqrestore(&pcdev->lock, flags);
+	return IRQ_HANDLED;
+}
+
+static struct videobuf_queue_ops omap1_videobuf_ops = {
+	.buf_setup	= omap1_videobuf_setup,
+	.buf_prepare	= omap1_videobuf_prepare,
+	.buf_queue	= omap1_videobuf_queue,
+	.buf_release	= omap1_videobuf_release,
+};
+
+
+/*
+ * SOC Camera host operations
+ */
+
+static void sensor_reset(struct omap1_cam_dev *pcdev, bool reset)
+{
+	/* apply/release camera sensor reset if requested by platform data */
+	if (pcdev->pflags & OMAP1_CAMERA_RST_HIGH)
+		CAM_WRITE(pcdev, GPIO, reset);
+	else if (pcdev->pflags & OMAP1_CAMERA_RST_LOW)
+		CAM_WRITE(pcdev, GPIO, !reset);
+}
+
+/*
+ * The following two functions absolutely depend on the fact, that
+ * there can be only one camera on OMAP1 camera sensor interface
+ */
+static int omap1_cam_add_device(struct soc_camera_device *icd)
+{
+	struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent);
+	struct omap1_cam_dev *pcdev = ici->priv;
+	u32 ctrlclock;
+
+	if (pcdev->icd)
+		return -EBUSY;
+
+	clk_enable(pcdev->clk);
+
+	/* setup sensor clock */
+	ctrlclock = CAM_READ(pcdev, CTRLCLOCK);
+	ctrlclock &= ~(CAMEXCLK_EN | MCLK_EN | DPLL_EN);
+	CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock);
+
+	ctrlclock &= ~FOSCMOD_MASK;
+	switch (pcdev->camexclk) {
+	case 6000000:
+		ctrlclock |= CAMEXCLK_EN | FOSCMOD_6MHz;
+		break;
+	case 8000000:
+		ctrlclock |= CAMEXCLK_EN | FOSCMOD_8MHz | DPLL_EN;
+		break;
+	case 9600000:
+		ctrlclock |= CAMEXCLK_EN | FOSCMOD_9_6MHz | DPLL_EN;
+		break;
+	case 12000000:
+		ctrlclock |= CAMEXCLK_EN | FOSCMOD_12MHz;
+		break;
+	case 24000000:
+		ctrlclock |= CAMEXCLK_EN | FOSCMOD_24MHz | DPLL_EN;
+	default:
+		break;
+	}
+	CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock & ~DPLL_EN);
+
+	/* enable internal clock */
+	ctrlclock |= MCLK_EN;
+	CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock);
+
+	sensor_reset(pcdev, false);
+
+	pcdev->icd = icd;
+
+	dev_info(icd->dev.parent, "OMAP1 Camera driver attached to camera %d\n",
+			icd->devnum);
+	return 0;
+}
+
+static void omap1_cam_remove_device(struct soc_camera_device *icd)
+{
+	struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent);
+	struct omap1_cam_dev *pcdev = ici->priv;
+	u32 ctrlclock;
+
+	BUG_ON(icd != pcdev->icd);
+
+	suspend_capture(pcdev);
+	disable_capture(pcdev);
+
+	sensor_reset(pcdev, true);
+
+	/* disable and release system clocks */
+	ctrlclock = CAM_READ_CACHE(pcdev, CTRLCLOCK);
+	ctrlclock &= ~(MCLK_EN | DPLL_EN | CAMEXCLK_EN);
+	CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock);
+
+	ctrlclock = (ctrlclock & ~FOSCMOD_MASK) | FOSCMOD_12MHz;
+	CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock);
+	CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock | MCLK_EN);
+
+	CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock & ~MCLK_EN);
+
+	clk_disable(pcdev->clk);
+
+	pcdev->icd = NULL;
+
+	dev_info(icd->dev.parent,
+		"OMAP1 Camera driver detached from camera %d\n", icd->devnum);
+}
+
+/* Duplicate standard formats based on host capability of byte swapping */
+static const struct soc_mbus_pixelfmt omap1_cam_formats[] = {
+	[V4L2_MBUS_FMT_UYVY8_2X8] = {
+		.fourcc			= V4L2_PIX_FMT_YUYV,
+		.name			= "YUYV",
+		.bits_per_sample	= 8,
+		.packing		= SOC_MBUS_PACKING_2X8_PADHI,
+		.order			= SOC_MBUS_ORDER_BE,
+	},
+	[V4L2_MBUS_FMT_VYUY8_2X8] = {
+		.fourcc			= V4L2_PIX_FMT_YVYU,
+		.name			= "YVYU",
+		.bits_per_sample	= 8,
+		.packing		= SOC_MBUS_PACKING_2X8_PADHI,
+		.order			= SOC_MBUS_ORDER_BE,
+	},
+	[V4L2_MBUS_FMT_YUYV8_2X8] = {
+		.fourcc			= V4L2_PIX_FMT_UYVY,
+		.name			= "UYVY",
+		.bits_per_sample	= 8,
+		.packing		= SOC_MBUS_PACKING_2X8_PADHI,
+		.order			= SOC_MBUS_ORDER_BE,
+	},
+	[V4L2_MBUS_FMT_YVYU8_2X8] = {
+		.fourcc			= V4L2_PIX_FMT_VYUY,
+		.name			= "VYUY",
+		.bits_per_sample	= 8,
+		.packing		= SOC_MBUS_PACKING_2X8_PADHI,
+		.order			= SOC_MBUS_ORDER_BE,
+	},
+	[V4L2_MBUS_FMT_RGB555_2X8_PADHI_BE] = {
+		.fourcc			= V4L2_PIX_FMT_RGB555,
+		.name			= "RGB555",
+		.bits_per_sample	= 8,
+		.packing		= SOC_MBUS_PACKING_2X8_PADHI,
+		.order			= SOC_MBUS_ORDER_BE,
+	},
+	[V4L2_MBUS_FMT_RGB555_2X8_PADHI_LE] = {
+		.fourcc			= V4L2_PIX_FMT_RGB555X,
+		.name			= "RGB555X",
+		.bits_per_sample	= 8,
+		.packing		= SOC_MBUS_PACKING_2X8_PADHI,
+		.order			= SOC_MBUS_ORDER_BE,
+	},
+	[V4L2_MBUS_FMT_RGB565_2X8_BE] = {
+		.fourcc			= V4L2_PIX_FMT_RGB565,
+		.name			= "RGB565",
+		.bits_per_sample	= 8,
+		.packing		= SOC_MBUS_PACKING_2X8_PADHI,
+		.order			= SOC_MBUS_ORDER_BE,
+	},
+	[V4L2_MBUS_FMT_RGB565_2X8_LE] = {
+		.fourcc			= V4L2_PIX_FMT_RGB565X,
+		.name			= "RGB565X",
+		.bits_per_sample	= 8,
+		.packing		= SOC_MBUS_PACKING_2X8_PADHI,
+		.order			= SOC_MBUS_ORDER_BE,
+	},
+};
+
+static int omap1_cam_get_formats(struct soc_camera_device *icd,
+		unsigned int idx, struct soc_camera_format_xlate *xlate)
+{
+	struct v4l2_subdev *sd = soc_camera_to_subdev(icd);
+	struct device *dev = icd->dev.parent;
+	int formats = 0, ret;
+	enum v4l2_mbus_pixelcode code;
+	const struct soc_mbus_pixelfmt *fmt;
+
+	ret = v4l2_subdev_call(sd, video, enum_mbus_fmt, idx, &code);
+	if (ret < 0)
+		/* No more formats */
+		return 0;
+
+	fmt = soc_mbus_get_fmtdesc(code);
+	if (!fmt) {
+		dev_err(dev, "%s: invalid format code #%d: %d\n", __func__,
+				idx, code);
+		return 0;
+	}
+
+	/* Check support for the requested bits-per-sample */
+	if (fmt->bits_per_sample != 8)
+		return 0;
+
+	switch (code) {
+	case V4L2_MBUS_FMT_YUYV8_2X8:
+	case V4L2_MBUS_FMT_YVYU8_2X8:
+	case V4L2_MBUS_FMT_UYVY8_2X8:
+	case V4L2_MBUS_FMT_VYUY8_2X8:
+	case V4L2_MBUS_FMT_RGB555_2X8_PADHI_BE:
+	case V4L2_MBUS_FMT_RGB555_2X8_PADHI_LE:
+	case V4L2_MBUS_FMT_RGB565_2X8_BE:
+	case V4L2_MBUS_FMT_RGB565_2X8_LE:
+		formats++;
+		if (xlate) {
+			xlate->host_fmt	= &omap1_cam_formats[code];
+			xlate->code	= code;
+			xlate++;
+			dev_dbg(dev, "%s: providing format %s "
+					"as byte swapped code #%d\n", __func__,
+					omap1_cam_formats[code].name, code);
+		}
+	default:
+		if (xlate)
+			dev_dbg(dev, "%s: providing format %s "
+					"in pass-through mode\n", __func__,
+					fmt->name);
+	}
+	formats++;
+	if (xlate) {
+		xlate->host_fmt	= fmt;
+		xlate->code	= code;
+		xlate++;
+	}
+
+	return formats;
+}
+
+static bool is_dma_aligned(s32 bytes_per_line, unsigned int height)
+{
+	int size = bytes_per_line * height;
+
+	return IS_ALIGNED(bytes_per_line, DMA_ELEMENT_SIZE) &&
+			IS_ALIGNED(size, DMA_FRAME_SIZE(CONTIG) *
+			DMA_ELEMENT_SIZE);
+}
+
+static int dma_align(int *width, int *height,
+		const struct soc_mbus_pixelfmt *fmt, bool enlarge)
+{
+	s32 bytes_per_line = soc_mbus_bytes_per_line(*width, fmt);
+
+	if (bytes_per_line < 0)
+		return bytes_per_line;
+
+	if (!is_dma_aligned(bytes_per_line, *height)) {
+		unsigned int pxalign = __fls(bytes_per_line / *width);
+		unsigned int salign  = DMA_FRAME_SHIFT(CONTIG) +
+				DMA_ELEMENT_SHIFT - pxalign;
+		unsigned int incr    = enlarge << salign;
+
+		v4l_bound_align_image(width, 1, *width + incr, 0,
+				height, 1, *height + incr, 0, salign);
+		return 0;
+	}
+	return 1;
+}
+
+/* returns 1 on g_crop() success, 0 on cropcap() success, <0 on error */
+static int get_crop(struct soc_camera_device *icd, struct v4l2_rect *rect)
+{
+	struct v4l2_subdev *sd = soc_camera_to_subdev(icd);
+	struct device *dev = icd->dev.parent;
+	struct v4l2_crop crop;
+	int ret;
+
+	crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+	ret = v4l2_subdev_call(sd, video, g_crop, &crop);
+	if (ret == -ENOIOCTLCMD) {
+		struct v4l2_cropcap cc;
+
+		dev_dbg(dev, "%s: g_crop() missing, trying cropcap() instead\n",
+				__func__);
+		cc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+		ret = v4l2_subdev_call(sd, video, cropcap, &cc);
+		if (ret < 0)
+			return ret;
+		*rect = cc.defrect;
+		return 0;
+	} else if (ret < 0) {
+		return ret;
+	}
+	*rect = crop.c;
+	return 1;
+}
+
+/*
+ * returns 1 on g_mbus_fmt() or g_crop() success, 0 on cropcap() success,
+ * <0 on error
+ */
+static int get_geometry(struct soc_camera_device *icd, struct v4l2_rect *rect,
+		enum v4l2_mbus_pixelcode code)
+{
+	struct v4l2_subdev *sd = soc_camera_to_subdev(icd);
+	struct device *dev = icd->dev.parent;
+	struct v4l2_mbus_framefmt mf;
+	int ret;
+
+	ret = v4l2_subdev_call(sd, video, g_mbus_fmt, &mf);
+	if (ret == -ENOIOCTLCMD) {
+		struct v4l2_rect c;
+
+		dev_dbg(dev,
+			"%s: g_mbus_fmt() missing, trying g_crop() instead\n",
+			__func__);
+		ret = get_crop(icd, &c);
+		if (ret < 0)
+			return ret;
+
+		if (ret) {
+			*rect = c;	/* use g_crop() result */
+		} else {
+			dev_warn(dev, "%s: current geometry not available\n",
+					__func__);
+			return 0;
+		}
+	} else if (ret < 0) {
+		return ret;
+	} else if (mf.code != code) {
+		return -EINVAL;
+	} else {
+		rect->width  = mf.width;
+		rect->height = mf.height;
+	}
+	return 1;
+}
+
+#define subdev_call_with_sense(pcdev, dev, icd, sd, function, args...)	   \
+({									   \
+	struct soc_camera_sense sense = {				   \
+		.master_clock		= pcdev->camexclk,		   \
+		.pixel_clock_max	= 0,				   \
+	};								   \
+	int __ret;							   \
+									   \
+	if (pcdev->pdata)						   \
+		sense.pixel_clock_max = pcdev->pdata->lclk_khz_max * 1000; \
+	icd->sense = &sense;						   \
+	__ret = v4l2_subdev_call(sd, video, function, ##args);		   \
+	icd->sense = NULL;						   \
+									   \
+	if (sense.flags & SOCAM_SENSE_PCLK_CHANGED) {			   \
+		if (sense.pixel_clock > sense.pixel_clock_max) {	   \
+			dev_err(dev, "%s: pixel clock %lu "		   \
+					"set by the camera too high!\n",   \
+					__func__, sense.pixel_clock);	   \
+			__ret = -EINVAL;				   \
+		}							   \
+	}								   \
+	__ret;								   \
+})
+
+static int omap1_cam_set_crop(struct soc_camera_device *icd,
+			       struct v4l2_crop *crop)
+{
+	struct v4l2_rect *rect = &crop->c;
+	struct v4l2_subdev *sd = soc_camera_to_subdev(icd);
+	struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent);
+	struct omap1_cam_dev *pcdev = ici->priv;
+	struct device *dev = icd->dev.parent;
+	s32 bytes_per_line;
+	int ret;
+
+	ret = dma_align(&rect->width, &rect->height, icd->current_fmt->host_fmt,
+			false);
+	if (ret < 0) {
+		dev_err(dev, "%s: failed to align %ux%u %s with DMA\n",
+				__func__, rect->width, rect->height,
+				icd->current_fmt->host_fmt->name);
+		return ret;
+	}
+
+	subdev_call_with_sense(pcdev, dev, icd, sd, s_crop, crop);
+	if (ret < 0) {
+		dev_warn(dev, "%s: failed to crop to %ux%u@%u:%u\n", __func__,
+			 rect->width, rect->height, rect->left, rect->top);
+		return ret;
+	}
+
+	ret = get_geometry(icd, rect, icd->current_fmt->code);
+	if (ret < 0) {
+		dev_err(dev, "%s: get_geometry() failed\n", __func__);
+		return ret;
+	}
+	if (!ret)
+		dev_warn(dev, "%s: unable to verify s_crop() results\n",
+				__func__);
+
+	bytes_per_line = soc_mbus_bytes_per_line(rect->width,
+			icd->current_fmt->host_fmt);
+	if (bytes_per_line < 0) {
+		dev_err(dev, "%s: soc_mbus_bytes_per_line() failed\n",
+				__func__);
+		return bytes_per_line;
+	}
+
+	ret = is_dma_aligned(bytes_per_line, rect->height);
+	if (!ret) {
+		dev_err(dev, "%s: resulting geometry %ux%u not DMA aligned\n",
+				__func__, rect->width, rect->height);
+		return -EINVAL;
+	}
+
+	icd->user_width	 = rect->width;
+	icd->user_height = rect->height;
+
+	return ret;
+}
+
+static int omap1_cam_set_fmt(struct soc_camera_device *icd,
+			      struct v4l2_format *f)
+{
+	struct v4l2_subdev *sd = soc_camera_to_subdev(icd);
+	const struct soc_camera_format_xlate *xlate;
+	struct device *dev = icd->dev.parent;
+	struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent);
+	struct omap1_cam_dev *pcdev = ici->priv;
+	struct v4l2_pix_format *pix = &f->fmt.pix;
+	struct v4l2_mbus_framefmt mf;
+	struct v4l2_crop crop;
+	struct v4l2_rect *rect = &crop.c;
+	int bytes_per_line;
+	int ret;
+
+	xlate = soc_camera_xlate_by_fourcc(icd, pix->pixelformat);
+	if (!xlate) {
+		dev_warn(dev, "%s: format %#x not found\n", __func__,
+				pix->pixelformat);
+		return -EINVAL;
+	}
+
+	mf.width	= pix->width;
+	mf.height	= pix->height;
+	mf.field	= pix->field;
+	mf.colorspace	= pix->colorspace;
+	mf.code		= xlate->code;
+
+	ret = subdev_call_with_sense(pcdev, dev, icd, sd, s_mbus_fmt, &mf);
+	if (ret < 0) {
+		dev_err(dev, "%s: failed to set format\n", __func__);
+		return ret;
+	}
+
+	if (mf.code != xlate->code) {
+		dev_err(dev, "%s: unexpected pixel code change\n", __func__);
+		return -EINVAL;
+	}
+
+	pix->width		= mf.width;
+	pix->height		= mf.height;
+	pix->field		= mf.field;
+	pix->colorspace		= mf.colorspace;
+	icd->current_fmt	= xlate;
+
+	ret = dma_align(&pix->width, &pix->height, xlate->host_fmt, false);
+	if (ret < 0) {
+		dev_err(dev, "%s: failed to align %ux%u %s with DMA\n",
+				__func__, pix->width, pix->height,
+				xlate->host_fmt->name);
+		return ret;
+	}
+
+	if (ret == 1) {
+		/* sensor returned geometry was already DMA aligned */
+		return 0;
+	}
+
+	dev_notice(dev, "%s: sensor geometry not DMA aligned, trying to crop to"
+			" %ux%u\n", __func__, pix->width, pix->height);
+	ret = get_crop(icd, rect);
+	if (ret < 0) {
+		dev_err(dev, "%s: get_crop() failed\n", __func__);
+		return ret;
+	}
+
+	rect->width  = pix->width;
+	rect->height = pix->height;
+
+	crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+	subdev_call_with_sense(pcdev, dev, icd, sd, s_crop, &crop);
+	if (ret < 0) {
+		dev_err(dev, "%s: failed to crop to %ux%u@%u:%u\n", __func__,
+			 rect->width, rect->height, rect->left, rect->top);
+		return ret;
+	}
+
+	ret = get_geometry(icd, rect, xlate->code);
+	if (ret < 0) {
+		dev_err(dev, "%s: get_geometry() failed\n", __func__);
+		return ret;
+	}
+
+	if (!ret)
+		dev_warn(dev, "%s: s_crop() results not confirmed\n", __func__);
+
+	bytes_per_line = soc_mbus_bytes_per_line(rect->width, xlate->host_fmt);
+	if (bytes_per_line < 0) {
+		dev_err(dev, "%s: soc_mbus_bytes_per_line() failed\n",
+				__func__);
+		return bytes_per_line;
+	}
+
+	ret = is_dma_aligned(bytes_per_line, rect->height);
+	if (!ret) {
+		dev_err(dev, "%s: resulting geometry %ux%u not DMA aligned\n",
+				__func__, rect->width, rect->height);
+		return -EINVAL;
+	}
+
+	pix->width  = rect->width;
+	pix->height = rect->height;
+
+	return 0;
+}
+
+static int omap1_cam_try_fmt(struct soc_camera_device *icd,
+			      struct v4l2_format *f)
+{
+	struct v4l2_subdev *sd = soc_camera_to_subdev(icd);
+	const struct soc_camera_format_xlate *xlate;
+	struct v4l2_pix_format *pix = &f->fmt.pix;
+	struct v4l2_mbus_framefmt mf;
+	int ret;
+	/* TODO: limit to mx1 hardware capabilities */
+
+	xlate = soc_camera_xlate_by_fourcc(icd, pix->pixelformat);
+	if (!xlate) {
+		dev_warn(icd->dev.parent, "Format %#x not found\n",
+			 pix->pixelformat);
+		return -EINVAL;
+	}
+
+	mf.width	= pix->width;
+	mf.height	= pix->height;
+	mf.field	= pix->field;
+	mf.colorspace	= pix->colorspace;
+	mf.code		= xlate->code;
+
+	/* limit to sensor capabilities */
+	ret = v4l2_subdev_call(sd, video, try_mbus_fmt, &mf);
+	if (ret < 0)
+		return ret;
+
+	pix->width	= mf.width;
+	pix->height	= mf.height;
+	pix->field	= mf.field;
+	pix->colorspace	= mf.colorspace;
+
+	return 0;
+}
+
+static bool sg_mode;
+
+/*
+ * Local mmap_mapper wrapper,
+ * used for detecting videobuf-dma-contig buffer allocation failures
+ * and switching to videobuf-dma-sg automatically for future attempts.
+ */
+static int omap1_cam_mmap_mapper(struct videobuf_queue *q,
+				  struct videobuf_buffer *buf,
+				  struct vm_area_struct *vma)
+{
+	struct soc_camera_device *icd = q->priv_data;
+	struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent);
+	struct omap1_cam_dev *pcdev = ici->priv;
+	int ret;
+
+	ret = pcdev->mmap_mapper(q, buf, vma);
+
+	if (ret == -ENOMEM)
+		sg_mode = 1;
+
+	return ret;
+}
+
+static void omap1_cam_init_videobuf(struct videobuf_queue *q,
+				     struct soc_camera_device *icd)
+{
+	struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent);
+	struct omap1_cam_dev *pcdev = ici->priv;
+
+	if (!sg_mode)
+		videobuf_queue_dma_contig_init(q, &omap1_videobuf_ops,
+				icd->dev.parent, &pcdev->lock,
+				V4L2_BUF_TYPE_VIDEO_CAPTURE, V4L2_FIELD_NONE,
+				sizeof(struct omap1_cam_buf), icd);
+	else
+		videobuf_queue_sg_init(q, &omap1_videobuf_ops,
+				icd->dev.parent, &pcdev->lock,
+				V4L2_BUF_TYPE_VIDEO_CAPTURE, V4L2_FIELD_NONE,
+				sizeof(struct omap1_cam_buf), icd);
+
+	/* use videobuf mode (auto)selected with the module parameter */
+	pcdev->vb_mode = sg_mode ? SG : CONTIG;
+
+	/*
+	 * Ensure we substitute the videobuf-dma-contig version of the
+	 * mmap_mapper() callback with our own wrapper, used for switching
+	 * automatically to videobuf-dma-sg on buffer allocation failure.
+	 */
+	if (!sg_mode && q->int_ops->mmap_mapper != omap1_cam_mmap_mapper) {
+		pcdev->mmap_mapper = q->int_ops->mmap_mapper;
+		q->int_ops->mmap_mapper = omap1_cam_mmap_mapper;
+	}
+}
+
+static int omap1_cam_reqbufs(struct soc_camera_file *icf,
+			      struct v4l2_requestbuffers *p)
+{
+	int i;
+
+	/*
+	 * This is for locking debugging only. I removed spinlocks and now I
+	 * check whether .prepare is ever called on a linked buffer, or whether
+	 * a dma IRQ can occur for an in-work or unlinked buffer. Until now
+	 * it hadn't triggered
+	 */
+	for (i = 0; i < p->count; i++) {
+		struct omap1_cam_buf *buf = container_of(icf->vb_vidq.bufs[i],
+						      struct omap1_cam_buf, vb);
+		buf->inwork = 0;
+		INIT_LIST_HEAD(&buf->vb.queue);
+	}
+
+	return 0;
+}
+
+static int omap1_cam_querycap(struct soc_camera_host *ici,
+			       struct v4l2_capability *cap)
+{
+	/* cap->name is set by the friendly caller:-> */
+	strlcpy(cap->card, "OMAP1 Camera", sizeof(cap->card));
+	cap->version = VERSION_CODE;
+	cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
+
+	return 0;
+}
+
+static int omap1_cam_set_bus_param(struct soc_camera_device *icd,
+		__u32 pixfmt)
+{
+	struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent);
+	struct omap1_cam_dev *pcdev = ici->priv;
+	struct device *dev = icd->dev.parent;
+	const struct soc_camera_format_xlate *xlate;
+	const struct soc_mbus_pixelfmt *fmt;
+	unsigned long camera_flags, common_flags;
+	u32 ctrlclock, mode;
+	int ret;
+
+	camera_flags = icd->ops->query_bus_param(icd);
+
+	common_flags = soc_camera_bus_param_compatible(camera_flags,
+			SOCAM_BUS_FLAGS);
+	if (!common_flags)
+		return -EINVAL;
+
+	/* Make choices, possibly based on platform configuration */
+	if ((common_flags & SOCAM_PCLK_SAMPLE_RISING) &&
+			(common_flags & SOCAM_PCLK_SAMPLE_FALLING)) {
+		if (!pcdev->pdata ||
+				pcdev->pdata->flags & OMAP1_CAMERA_LCLK_RISING)
+			common_flags &= ~SOCAM_PCLK_SAMPLE_FALLING;
+		else
+			common_flags &= ~SOCAM_PCLK_SAMPLE_RISING;
+	}
+
+	ret = icd->ops->set_bus_param(icd, common_flags);
+	if (ret < 0)
+		return ret;
+
+	ctrlclock = CAM_READ_CACHE(pcdev, CTRLCLOCK);
+	if (ctrlclock & LCLK_EN)
+		CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock & ~LCLK_EN);
+
+	if (common_flags & SOCAM_PCLK_SAMPLE_RISING) {
+		dev_dbg(dev, "CTRLCLOCK_REG |= POLCLK\n");
+		ctrlclock |= POLCLK;
+	} else {
+		dev_dbg(dev, "CTRLCLOCK_REG &= ~POLCLK\n");
+		ctrlclock &= ~POLCLK;
+	}
+	CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock & ~LCLK_EN);
+
+	if (ctrlclock & LCLK_EN)
+		CAM_WRITE(pcdev, CTRLCLOCK, ctrlclock);
+
+	/* select bus endianess */
+	xlate = soc_camera_xlate_by_fourcc(icd, pixfmt);
+	fmt = xlate->host_fmt;
+
+	mode = CAM_READ(pcdev, MODE) & ~(RAZ_FIFO | IRQ_MASK | DMA);
+	if (fmt->order == SOC_MBUS_ORDER_LE) {
+		dev_dbg(dev, "MODE_REG &= ~ORDERCAMD\n");
+		CAM_WRITE(pcdev, MODE, mode & ~ORDERCAMD);
+	} else {
+		dev_dbg(dev, "MODE_REG |= ORDERCAMD\n");
+		CAM_WRITE(pcdev, MODE, mode | ORDERCAMD);
+	}
+
+	return 0;
+}
+
+static unsigned int omap1_cam_poll(struct file *file, poll_table *pt)
+{
+	struct soc_camera_file *icf = file->private_data;
+	struct omap1_cam_buf *buf;
+
+	buf = list_entry(icf->vb_vidq.stream.next, struct omap1_cam_buf,
+			 vb.stream);
+
+	poll_wait(file, &buf->vb.done, pt);
+
+	if (buf->vb.state == VIDEOBUF_DONE ||
+	    buf->vb.state == VIDEOBUF_ERROR)
+		return POLLIN | POLLRDNORM;
+
+	return 0;
+}
+
+static struct soc_camera_host_ops omap1_host_ops = {
+	.owner		= THIS_MODULE,
+	.add		= omap1_cam_add_device,
+	.remove		= omap1_cam_remove_device,
+	.get_formats	= omap1_cam_get_formats,
+	.set_crop	= omap1_cam_set_crop,
+	.set_fmt	= omap1_cam_set_fmt,
+	.try_fmt	= omap1_cam_try_fmt,
+	.init_videobuf	= omap1_cam_init_videobuf,
+	.reqbufs	= omap1_cam_reqbufs,
+	.querycap	= omap1_cam_querycap,
+	.set_bus_param	= omap1_cam_set_bus_param,
+	.poll		= omap1_cam_poll,
+};
+
+static int __init omap1_cam_probe(struct platform_device *pdev)
+{
+	struct omap1_cam_dev *pcdev;
+	struct resource *res;
+	struct clk *clk;
+	void __iomem *base;
+	unsigned int irq;
+	int err = 0;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	irq = platform_get_irq(pdev, 0);
+	if (!res || (int)irq <= 0) {
+		err = -ENODEV;
+		goto exit;
+	}
+
+	clk = clk_get(&pdev->dev, "armper_ck");
+	if (IS_ERR(clk)) {
+		err = PTR_ERR(clk);
+		goto exit;
+	}
+
+	pcdev = kzalloc(sizeof(*pcdev), GFP_KERNEL);
+	if (!pcdev) {
+		dev_err(&pdev->dev, "Could not allocate pcdev\n");
+		err = -ENOMEM;
+		goto exit_put_clk;
+	}
+
+	pcdev->res = res;
+	pcdev->clk = clk;
+
+	pcdev->pdata = pdev->dev.platform_data;
+	pcdev->pflags = pcdev->pdata->flags;
+
+	if (pcdev->pdata)
+		pcdev->camexclk = pcdev->pdata->camexclk_khz * 1000;
+
+	switch (pcdev->camexclk) {
+	case 6000000:
+	case 8000000:
+	case 9600000:
+	case 12000000:
+	case 24000000:
+		break;
+	default:
+		dev_warn(&pdev->dev,
+				"Incorrect sensor clock frequency %ld kHz, "
+				"should be one of 0, 6, 8, 9.6, 12 or 24 MHz, "
+				"please correct your platform data\n",
+				pcdev->pdata->camexclk_khz);
+		pcdev->camexclk = 0;
+	case 0:
+		dev_info(&pdev->dev,
+				"Not providing sensor clock\n");
+	}
+
+	INIT_LIST_HEAD(&pcdev->capture);
+	spin_lock_init(&pcdev->lock);
+
+	/*
+	 * Request the region.
+	 */
+	if (!request_mem_region(res->start, resource_size(res), DRIVER_NAME)) {
+		err = -EBUSY;
+		goto exit_kfree;
+	}
+
+	base = ioremap(res->start, resource_size(res));
+	if (!base) {
+		err = -ENOMEM;
+		goto exit_release;
+	}
+	pcdev->irq = irq;
+	pcdev->base = base;
+
+	sensor_reset(pcdev, true);
+
+	err = omap_request_dma(OMAP_DMA_CAMERA_IF_RX, DRIVER_NAME,
+			dma_isr, (void *)pcdev, &pcdev->dma_ch);
+	if (err < 0) {
+		dev_err(&pdev->dev, "Can't request DMA for OMAP1 Camera\n");
+		err = -EBUSY;
+		goto exit_iounmap;
+	}
+	dev_dbg(&pdev->dev, "got DMA channel %d\n", pcdev->dma_ch);
+
+	/* preconfigure DMA */
+	omap_set_dma_src_params(pcdev->dma_ch, OMAP_DMA_PORT_TIPB,
+			OMAP_DMA_AMODE_CONSTANT, res->start + REG_CAMDATA,
+			0, 0);
+	omap_set_dma_dest_burst_mode(pcdev->dma_ch, OMAP_DMA_DATA_BURST_4);
+	/* setup DMA autoinitialization */
+	omap_dma_link_lch(pcdev->dma_ch, pcdev->dma_ch);
+
+	err = request_irq(pcdev->irq, cam_isr, 0, DRIVER_NAME, pcdev);
+	if (err) {
+		dev_err(&pdev->dev, "Camera interrupt register failed\n");
+		goto exit_free_dma;
+	}
+
+	pcdev->soc_host.drv_name	= DRIVER_NAME;
+	pcdev->soc_host.ops		= &omap1_host_ops;
+	pcdev->soc_host.priv		= pcdev;
+	pcdev->soc_host.v4l2_dev.dev	= &pdev->dev;
+	pcdev->soc_host.nr		= pdev->id;
+
+	err = soc_camera_host_register(&pcdev->soc_host);
+	if (err)
+		goto exit_free_irq;
+
+	dev_info(&pdev->dev, "OMAP1 Camera Interface driver loaded\n");
+
+	return 0;
+
+exit_free_irq:
+	free_irq(pcdev->irq, pcdev);
+exit_free_dma:
+	omap_free_dma(pcdev->dma_ch);
+exit_iounmap:
+	iounmap(base);
+exit_release:
+	release_mem_region(res->start, resource_size(res));
+exit_kfree:
+	kfree(pcdev);
+exit_put_clk:
+	clk_put(clk);
+exit:
+	return err;
+}
+
+static int __exit omap1_cam_remove(struct platform_device *pdev)
+{
+	struct soc_camera_host *soc_host = to_soc_camera_host(&pdev->dev);
+	struct omap1_cam_dev *pcdev = container_of(soc_host,
+					struct omap1_cam_dev, soc_host);
+	struct resource *res;
+
+	free_irq(pcdev->irq, pcdev);
+
+	omap_free_dma(pcdev->dma_ch);
+
+	soc_camera_host_unregister(soc_host);
+
+	iounmap(pcdev->base);
+
+	res = pcdev->res;
+	release_mem_region(res->start, resource_size(res));
+
+	kfree(pcdev);
+
+	clk_put(pcdev->clk);
+
+	dev_info(&pdev->dev, "OMAP1 Camera Interface driver unloaded\n");
+
+	return 0;
+}
+
+static struct platform_driver omap1_cam_driver = {
+	.driver		= {
+		.name	= DRIVER_NAME,
+	},
+	.probe		= omap1_cam_probe,
+	.remove		= __exit_p(omap1_cam_remove),
+};
+
+static int __init omap1_cam_init(void)
+{
+	return platform_driver_register(&omap1_cam_driver);
+}
+module_init(omap1_cam_init);
+
+static void __exit omap1_cam_exit(void)
+{
+	platform_driver_unregister(&omap1_cam_driver);
+}
+module_exit(omap1_cam_exit);
+
+module_param(sg_mode, bool, 0644);
+MODULE_PARM_DESC(sg_mode, "videobuf mode, 0: dma-contig (default), 1: dma-sg");
+
+MODULE_DESCRIPTION("OMAP1 Camera Interface driver");
+MODULE_AUTHOR("Janusz Krzysztofik <[email-masked]>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" DRIVER_NAME);
diff -upr linux-2.6.36-rc3.orig/include/media/omap1_camera.h linux-2.6.36-rc3/include/media/omap1_camera.h
--- linux-2.6.36-rc3.orig/include/media/omap1_camera.h	2010-09-03 22:34:02.000000000 +0200
+++ linux-2.6.36-rc3/include/media/omap1_camera.h	2010-09-08 23:41:12.000000000 +0200
@@ -0,0 +1,35 @@
+/*
+ * Header for V4L2 SoC Camera driver for OMAP1 Camera Interface
+ *
+ * Copyright (C) 2010, Janusz Krzysztofik <[email-masked]>
+ *
+ * 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.
+ */
+
+#ifndef __MEDIA_OMAP1_CAMERA_H_
+#define __MEDIA_OMAP1_CAMERA_H_
+
+#include <linux/bitops.h>
+
+#define OMAP1_CAMERA_IOSIZE		0x1c
+
+enum omap1_cam_vb_mode {
+	CONTIG = 0,
+	SG,
+};
+
+#define OMAP1_CAMERA_MIN_BUF_COUNT(x)	((x) == CONTIG ? 3 : 2)
+
+struct omap1_cam_platform_data {
+	unsigned long	camexclk_khz;
+	unsigned long	lclk_khz_max;
+	unsigned long	flags;
+};
+
+#define OMAP1_CAMERA_LCLK_RISING	BIT(0)
+#define OMAP1_CAMERA_RST_LOW		BIT(1)
+#define OMAP1_CAMERA_RST_HIGH		BIT(2)
+
+#endif /* __MEDIA_OMAP1_CAMERA_H_ */

---

From: Jasmine Strong <[email-masked]>
Date: Fri, 10 Sep 2010 19:06:02 -0700

On 10 Sep 2010, at 18:21, Janusz Krzysztofik wrote:

>  Both paths work stable for me, even 

You say that, but the ARM925 in OMAP1510 is known not to exhibit the bug in OMAP1610, which causes severe slowdown to DRAM writes when the first address of an STM instruction is not aligned to a d-cache line boundary.  This means that at least last time I looked, the Linux ARM memcpy() implementation is often faster on 1510 than 1610.

-J.

---

From: Janusz Krzysztofik <[email-masked]>
Date: Sat, 11 Sep 2010 11:32:47 +0200

Saturday 11 September 2010 04:06:02 Jasmine Strong wrote:
> > under heavy load, on my OMAP1510 based Amstrad Delta videophone, that is
> > the oldest, least powerfull OMAP1 implementation.
>
> You say that, but the ARM925 in OMAP1510 is known not to exhibit the bug 

Then, lucky me ;)

> in  
> OMAP1610, which causes severe slowdown to DRAM writes when the first
> address of an STM instruction is not aligned to a d-cache line boundary. 
> This means that at least last time I looked, the Linux ARM memcpy()
> implementation is often faster on 1510 than 1610.

This sounds like a problem that should be solved at a machine support level 
rather than a camera driver. I don't follow general OMAP development closely 
enough to know if this bug has ever been addressed or is going to be.

Unfortunatelly, I have no access to any OMAP machine other than Amstrad Delta, 
so I'm not able to test the driver, including its performance, on other OMAP1 
implementations.

Anyways, I think there is always a room for improvement, either in my 
omap1_camera or maybe in the omap24xxcam driver, if someone tries to add 
support for a camera to an OMAP1 board other than 1510, and identifies a more 
optimal, 1610 or higher specific way of handling the OMAP camera interface.

Do you think I should rename the driver to something like "omap1510cam" rather 
than "omap1_camera" then?

Thanks,
Janusz

>
> -J.
> _______________________________________________
> e3-hacking mailing list
> [email-masked]
> http://www.earth.li/cgi-bin/mailman/listinfo/e3-hacking

---

From: Ralph Corderoy <[email-masked]>
Date: Sun, 19 Sep 2010 16:06:58 +0100

Hi Janusz,

> +		dev_warn(icd->dev.parent, "Format %#x not found\n",

Two bytes could be saved with consistent use of %#x.  :-)  Does it make
sense to say `%0x' without giving a field width to pad to?  That's
another two bytes saved.  A veritable word!

Cheers,
Ralph.

---

From: Janusz Krzysztofik <[email-masked]>
Date: Sun, 19 Sep 2010 21:24:25 +0200

> Hi Janusz,

Hi Ralph,
Thanks for your time.

> Two bytes could be saved with consistent use of %#x.  :-) 

My "man 3 printf" says:

"The flag characters
	The character % is followed by zero or more of the following flags:

 #	The value should be converted to an  "alternate  form"...  For x and
	X conversions,  a non-zero result has the string "0x" (or "0X" for X
	conversions) prepended to it."

In the first two cases, I intended to keep the "0x" prepended even if 0. Am I 
missing something?

> Does it make 
> sense to say `%0x' without giving a field width to pad to? 

My bad.

In next iteration, I'll probably use "0x%.8x" when printing 32-bit register 
values. What do you think?

Cheers,
Janusz

> That's

---

From: Ralph Corderoy <[email-masked]>
Date: Tue, 21 Sep 2010 14:48:33 +0100

Hi Janusz,

> In the first two cases, I intended to keep the "0x" prepended even if
> 0. Am I missing something?

No, I didn't realise that was the intent.  I agree, if you want zero to
come out as "0x0" then you have to supply your own "0x".

> In next iteration, I'll probably use "0x%.8x" when printing 32-bit
> register values. What do you think?

I'm probably more used to seeing "0x%08x", which is what I used to use
myself, but either gives the same result.  I stopped using it because I
found that the shape of the word is as relevant with hex as it is with
English.

    0 0x80 0x800080
    0x00000000 0x00000080 0x00800080

With the first line I can instantly see it's

    Zero.
    128 with the top-three bytes all zero.
    Three bytes with the top byte zero.

With the second line I have to scan the 0 digits carefully, looking for
non-zeroes.

Cheers,
Ralph.

---

From: Guennadi Liakhovetski <[email-masked]>
Date: Wed, 22 Sep 2010 01:23:22 +0200 (CEST)

> iteration and split both paths.

Well, I think, I'll move a bit towards the "if it breaks - someone gets to 
fix it, or it gets dropped" policy, i.e., I'll give you more freedom 
(don't know what's wrong with me today;))

> +++ linux-2.6.36-rc3/drivers/media/video/omap1_camera.c	2010-09-09 15:58:22.000000000 +0200

Don't you want to compare (x) against CONTIG and you want to put x in 
parenthesis in the DMA_FRAME_SHIFT macro. Besides, CONTIG and SG are not 
good enough names to be defined in a header under include/... Looks like 
you don't need them at all in the header? You only use them in this file, 
so, just move them inside.

> +			 * sgbuf in SG mode, gets overwritten by next frame.

Hm, why do you think it's a good idea? This specific buffer completed 
successfully, but you want to fail it just because the next buffer is 
missing? Any specific reason for this? Besides, you seem to also be 
considering the possibility of your ->ready == NULL, but the queue 
non-empty, in which case you just take the next buffer from the queue and 
continue with it. Why error out in this case? And even if also the queue 
is empty - still not sure, why.

> +		 * In both CONTIG and SG mode, the DMA engine has (might)

s/might/possibly/

> +	 * get entered it into the DMA programming register set,

s/get entered/enter/

> +				try_next_sgbuf(pcdev->dma_ch, buf);

Uh, sorry, maybe I cannot quite follow the logic here, but it doesn't seem 
quite correct to me. Looks like you do not complete the current 
(successfully completed) buffer, if you failed to prepare the next one?

> +				 * If exactly 2 sgbufs from the next sglist has

s/has/have/

> +				"0x%0x\n", __func__, it_status);

Please, don't split strings

> +		ctrlclock |= CAMEXCLK_EN | FOSCMOD_24MHz | DPLL_EN;

This default doesn't make much sense here, maybe put it to one of these 
values, that you think is a reasonable fall back. Ah, right, you wanted to 
check, whether this can work...

> +	subdev_call_with_sense(pcdev, dev, icd, sd, s_crop, crop);

Missing "ret = "?

> +			" %ux%u\n", __func__, pix->width, pix->height);

One line

> +	subdev_call_with_sense(pcdev, dev, icd, sd, s_crop, &crop);

'ret = '?

> +	ret = pcdev->mmap_mapper(q, buf, vma);

"true"

> +				pcdev->pdata->flags & OMAP1_CAMERA_LCLK_RISING)

Out of curiosity - do you need to force a specific clock polarity on your 
platform or do you know of any, where this is necessary (beyond the 
natural restriction, coming from the sensor - if any). I.e., I'm not at 
all sure you need this flag, but if you do - I would apply it earlier - to 
mask the respective bit from SOCAM_BUS_FLAGS.

> +++ linux-2.6.36-rc3/include/media/omap1_camera.h	2010-09-08 23:41:12.000000000 +0200

See above - are these needed here?

> +#define OMAP1_CAMERA_MIN_BUF_COUNT(x)	((x) == CONTIG ? 3 : 2)

ditto

> +#endif /* __MEDIA_OMAP1_CAMERA_H_ */

Thanks
Guennadi
---
Guennadi Liakhovetski, Ph.D.
Freelance Open-Source Software Developer
http://www.open-technology.de/

---

From: hermann pitton <[email-masked]>
Date: Wed, 22 Sep 2010 02:10:43 +0200

Hi,

> > This is a V4L2 driver for TI OMAP1 SoC camera interface.

[snip]

> Please, don't split strings

sorry for any OT interference.

But, are there any new rules out?

Maybe I missed them.

Either way, the above was forced during the last three years.

Not at all ?

Cheers,
Hermann

---

From: Guennadi Liakhovetski <[email-masked]>
Date: Wed, 22 Sep 2010 08:08:21 +0200 (CEST)

> Either way, the above was forced during the last three years.

No. Splitting print strings has always been discouraged, because it makes 
tracking back kernel logs difficult. The reason for this has been the 80 
character line length limit, which has now been effectively lifted. I'm 
sure you can find enough links on the internet for any of these topics.

Thanks
Guennadi
---

---

From: Guennadi Liakhovetski <[email-masked]>
Date: Wed, 22 Sep 2010 08:53:19 +0200 (CEST)

That's up to the platform maintainer to review / apply this one, but if 
you like, a couple of nit-picks from me:

>  arch/arm/mach-omap1/devices.c             |   43 

You might want to try to group headers according to their location, i.e., 
put <mach/...> higher - together with <mach/gpio.h>, also it helps to have 
headers alphabetically ordered.

function declarations don't need an "extern" - something I've also been 
doing needlessly for a few years...

Thanks
Guennadi
---

---

From: Guennadi Liakhovetski <[email-masked]>
Date: Wed, 22 Sep 2010 11:12:46 +0200 (CEST)

Ok, just a couple more comments, all looking quite good so far, if we get 
a new version soon enough, we still might manage it for 2.6.37

[snip]

> +	msleep_interruptible(1);

Why do you want _interruptible here? Firstly it's just 1ms, secondly - 
why?...

...

> +			(code == V4L2_MBUS_FMT_SBGGR8_1X8)) {

Superfluous parenthesis

> +		priv->pclk_max = 8000000;

coml_mask and coml_set are only set here and only used once below, so, 
dropping initialisation to 0 in variable definitions and just doing

+	if (code == V4L2_MBUS_FMT_GREY8_1X8 ||
+			code == V4L2_MBUS_FMT_SBGGR8_1X8) {
+		coml_mask = COML_ONE_CHANNEL;
+		coml_set = 0;
+		priv->pclk_max = 4000000;
+	} else {
+		coml_mask = 0;
+		coml_set = COML_ONE_CHANNEL;
+		priv->pclk_max = 8000000;
+	}

would work too.

> +	priv->rect.height = mf->height;

Sorry, don't understand. The sensor can do both - cropping per HSTRT, 
HSTOP, VSTRT and VSTOP and scaling per COMA_CIF / COMA_QCIF, right? But 
which of them is stored in your priv->rect? Is this the input window 
(cropping) or the output one (scaling)? You overwrite it in .s_fmt and 
.s_crop...

> +		if (priv->pclk_limit && (priv->pclk_limit < pclk))

Don't think the compiler would complain without the internal parenthesis 
here?

Thanks
Guennadi
---

---

From: Janusz Krzysztofik <[email-masked]>
Date: Wed, 22 Sep 2010 20:13:56 +0200

Wednesday 22 September 2010 01:23:22 Guennadi Liakhovetski napisał(a):
> > Both videobuf-dma versions are supported, contig and sg, selectable with
> > a module option. The former uses less processing power, but often fails
> > to allocate contignuous buffer memory. The latter is free of this
> > problem, but generates tens of DMA interrupts per frame. If contig memory
> > allocation ever fails, the driver falls back to sg automatically on next
> > open, but still can be switched back to contig manually. Both paths work
> > stable for me, even under heavy load, on my OMAP1510 based Amstrad Delta
> > videophone, that is the oldest, least powerfull OMAP1 implementation.
> >
> > endianess can be swapped, it provides up to two v4l2 pixel formats per
> > each of several soc_mbus formats that have their swapped endian
> > counterparts.
> >
> > - what is the maximum pixel clock that should be accepted from the
> > sensor, - what is the polarity of the sensor provided pixel clock,
> > - if the interface GPIO line is connected to a sensor reset/powerdown
> > input and what is the input polarity.
> >
> > > cleaner separation between the two cases. Don't try that much to reuse
> > > the code as much as possible. Would be much better to have clean
> > > separation between the two implementations - whether dynamically
> > > switchable at runtime or at config time - would be best to separate the
> > > two
> > I've tried to rearrange them spearated, as you requested, but finally
> > decided to keep them together, as before, only better documented and
> > cleaned up as much as possible. I'm rather satisfied with the result, but
> > if you think it is still not enough understandable and maintainable, I'll
> > take one more iteration and split both paths.
>

Hi Guennadi,
Thanks!

...
> parenthesis in the DMA_FRAME_SHIFT macro. 

Sure.

> Besides, CONTIG and SG are not 

Please see my very last comment below.

...
> missing? Any specific reason for this? 

Maybe my comment is not clear enough, but the below suspend_capture() doesn't 
indicate any failure on a frame just captured. It only prevents the frame 
from being overwritten by the already autoreinitialized DMA engine, pointing 
back to the same buffer once again.

> Besides, you seem to also be 
> continue with it. Why error out in this case? 

pcdev->ready == NULL means no buffer was available when it was time to put it 
into the DMA programming register set. As a result, a next DMA transfer has 
just been autoreinitialized with the same buffer parameters as before. To 
protect the buffer from being overwriten unintentionally, we have to stop the 
DMA transfer as soon as possible, hopefully before the sensor starts sending 
out next frame data.

If a new buffer has been queued meanwhile, best we can do is stopping 
everything, programming the DMA with the new buffer, and setting up for a new 
transfer hardware auto startup on nearest frame start, be it the next one if 
we are lucky enough, or one after the next if we are too slow.

> And even if also the queue 

I hope the above explanation clarifies why.

I'll try to rework the above comment to be more clear, OK? Any hints?

> > +		 * In both CONTIG and SG mode, the DMA engine has (might)

OK

> > +	 * get entered it into the DMA programming register set,

OK.

> (successfully completed) buffer, if you failed to prepare the next one?

Looks like there is also some space for improvement in my documentation here.

In short, until the DMA ISR is called with the buf->sgbuf reset back to NULL, 
which should happen while the previous DMA ISR's called try_next_sgbuf() has 
retuned NULL, the frame is still not ready, ie. still being filled with DMA. 
IOW, only the DMA interrupt with buf->sgbuf == NULL means that the last sgbuf 
DMA transfer has just been completed.

Meanwhile, a next sglist could already be put in advanvce into the DMA 
programming register set and just activated (copied to the DMA working 
register set) automatically.

...
> > +				 * If exactly 2 sgbufs from the next sglist has

Thanks.

...
> Please, don't split strings

OK.

...
> check, whether this can work...

Sorry, I forgot to answer your previously asked question about the MCLK_EN 
(master clock enable) requirement if not rising the CAMEXCLK_EN (sensor clock 
enable).

The documentation (http://focus.ti.com/lit/ug/spru684/spru684.pdf) says:

	"the MCLK_EN bit must first be set before any camera-interface registers 
	 access"

so the answer is yes, the MCLK is required not only for providing a sensor 
with a clock, but also for the host interface itself to do its job.

The default case above (no CAMEXCLK_EN) is intended to be used on a board 
whith a sensor that is driven from a clock source other than the interface's 
CAM_EXCLK pin, which is keept idle in this case. Am I missing something?

My general assumption was to provide support for all interface hardware 
features which are supported by the existing soc_camera framework, not only 
those required by my board or sensor. Could it be that this approach breaks a 
general rule of not putting any code that is not (yet) used by any consumer?

> > +	dev_info(icd->dev.parent, "OMAP1 Camera driver attached to camera
> > %d\n", +			icd->devnum);
> > +	return 0;
> > +}

...
> > +	ret = dma_align(&rect->width, &rect->height,
> > icd->current_fmt->host_fmt, +			false);
> > +	if (ret < 0) {

Yes, thanks!

...
> > +	dev_notice(dev, "%s: sensor geometry not DMA aligned, trying to crop
> > to" +			" %ux%u\n", __func__, pix->width, pix->height);
>
> One line

OK.

> > +	subdev_call_with_sense(pcdev, dev, icd, sd, s_crop, &crop);

Of course.

...
> "true"

Right.

...
> platform 

If you mean my board - no, I don't think so.

> or do you know of any, where this is necessary (beyond the 
> natural restriction, coming from the sensor - if any). 

No, neither.

> I.e., I'm not at 

Maybe my understanding of what is actually going on here is not enough. TBH, 
I've just copy-pasted this piece of code from mx1_camera.c in order to 
provide support for a hardware feature that is available on my platform, 
regardles of any boards actually using it or not. Almost all existing 
soc_camera host drivers, ie. the pxa_camera.c and all of mx?_camera.c, follow 
the same or very similiar pattern, and the only exception, 
sh_mobile_ceu_camera.c, looks for me like just missing this hardware feature. 
Then, having no example implementation to follow and not ehough understanding 
of the matter, I'm not sure if I'm able to make you happy. Please give me 
more details if you think this is important.

...
> > linux-2.6.36-rc3.orig/include/media/omap1_camera.h	2010-09-03
> > 22:34:02.000000000 +0200 +++
> > linux-2.6.36-rc3/include/media/omap1_camera.h	2010-09-08
> > 23:41:12.000000000 +0200 @@ -0,0 +1,35 @@
> > +/*

I moved them both over to the header file because I was using the 
OMAP1_CAMERA_MIN_BUF_COUNT(CONTIG) macro once from the platform code in order 
to calculate the buffer size when calling the then NAKed 
dma_preallocate_coherent_memory(). Now I could put them back into the driver 
code, but if we ever get back to the concept of preallocating a contignuos 
piece of memory from the platform init code, we might need them back here, so 
maybe I should rather keep them, only rename the two enum values using a 
distinct name space. What do you think is better for now?

Thanks,
Janusz

---

From: Janusz Krzysztofik <[email-masked]>
Date: Wed, 22 Sep 2010 20:20:52 +0200

> you like, a couple of nit-picks from me:

Guennadi,
Thanks for also looking at this!

> > For successfull compilation, requires a header file provided by PATCH 1/6
> > from this series, "SoC Camera: add driver for OMAP1 camera interface".
> >

Yes, I will, thank you.

> > linux-2.6.36-rc3.orig/arch/arm/mach-omap1/include/mach/camera.h	2010-09-0
> >3 22:34:03.000000000 +0200

Good to know. I'll drop it.

Tony,
Any comments from you before I submit a hopefully final version?

Thanks,
Janusz

> --
> To unsubscribe from this list: send the line "unsubscribe linux-media" in
> the body of a message to [email-masked]
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

---

From: Janusz Krzysztofik <[email-masked]>
Date: Wed, 22 Sep 2010 20:23:12 +0200

> Why do you want _interruptible here? Firstly it's just 1ms, secondly -

My bad. I didn't verified what a real difference between msleep() and 
msleep_interruptible() is, only found that msleep_interruptible(1) makes 
checkpatch.pl more happy than msleep(1), sorry.

What I can be sure is that a short delay is required here, otherwise the 
driver doesn't work correctly. To prevent the checkpatch.pl from complying 
against msleep(1), I think I could just replace it with msleep(20). What do 
you think?

> > +			(code == V4L2_MBUS_FMT_SBGGR8_1X8)) {

Will be dropped.

> +		priv->pclk_max = 8000000;

OK, I'll use your prefered pattern.

> HSTOP, VSTRT and VSTOP and scaling per COMA_CIF / COMA_QCIF, right? 

Right.

> But 
> (cropping) or the output one (scaling)? 

I'm not sure how I can follow your input/output concept here.
Default (reset) values of HSTRT, HSTOP, VSTRT and VSTOP registers are the same 
for both CIF and QCIF, giving a 176x144 picture area in both cases. Than, 
when in CIF (reset default) mode, which actual size is double of that 
(352x288), I scale them by 2 when converting to priv->rect elements.

> You overwrite it in .s_fmt and 
> .s_crop...

I added the priv->rect to be returned by g_crop() instead of recalculating it 
from the register values. Then, I think I have to overwrite it on every 
geometry change, whether s_crop or s_fmt caused. Am I missing something?

> Don't think the compiler would complain without the internal parenthesis

OK, I'll drop them.

Thanks,
Janusz

---

From: hermann pitton <[email-masked]>
Date: Thu, 23 Sep 2010 01:31:15 +0200

Guennadi,

thanks for the update!

If somebody ever would like to waste time on it, lots of patches and
whole drivers have been forced into this limitations for strings too.

In fact, fixing only a few lines, including the offset, you almost
always did hit it.

I'm pleased to hear now, that this problem never did exist ;))

Cheers,
Hermann

---

From: Janusz Krzysztofik <[email-masked]>
Date: Thu, 23 Sep 2010 16:51:35 +0200

Thursday 23 September 2010 15:33:54 Guennadi Liakhovetski napisał(a):
> On Wed, 22 Sep 2010, Janusz Krzysztofik wrote:
> > Maybe my comment is not clear enough, but the below suspend_capture()
> > doesn't indicate any failure on a frame just captured. It only prevents
> > the frame from being overwritten by the already autoreinitialized DMA
> > engine, pointing back to the same buffer once again.
> >
> > > non-empty, in which case you just take the next buffer from the queue
> > > and continue with it. Why error out in this case?
> >
> > pcdev->ready == NULL means no buffer was available when it was time to
> > put it into the DMA programming register set.
>
> But how? Buffers are added onto the list in omap1_videobuf_queue() under
> spin_lock_irqsave(); and there you also check ->ready and fill it in. 

Guennadi,
Yes, but only if pcdev->active is NULL, ie. both DMA and FIFO are idle, never 
if active:

Since the transfer of the DMA programming register set content to the DMA 
working register set is done automatically by the DMA hardware, this can 
pretty well happen while I keep the lock here, so I can't be sure if it's not 
too late for entering new data into the programming register set. Then, I 
decided that this operation should be done only just after the DMA interrupt 
occured, ie. the current DMA programming register set content has just been 
used and can be overwriten.

I'll emphasize the above return; with a comment.

> In 
> your completion you set ->ready = NULL, but then also call
> prepare_next_vb() to get the next buffer from the list - if there are any,
> so, how can it be NULL with a non-empty list?

It happens after the above mentioned prepare_next_vb() gets nothing from an 
empty queue, so nothing is entered into the DMA programming register set, 
only the last, just activated, buffer is processed, then 
omap1_videobuf_queue() puts a new buffer into the queue while the active 
buffer is still filled in, and finally the DMA ISR is called on this last 
active buffer completion.

I hope this helps.

> > As a result, a next DMA transfer has
> > protect the buffer from being overwriten unintentionally, we have to stop
> > the DMA transfer as soon as possible, hopefully before the sensor starts
> > sending out next frame data.
> >
> > everything, programming the DMA with the new buffer, and setting up for a
> > new transfer hardware auto startup on nearest frame start, be it the next
> > one if we are lucky enough, or one after the next if we are too slow.
> >
> > > > + * 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.
> > > > + */
> > > > +
> > OMAP1_CAMERA_MIN_BUF_COUNT(CONTIG) macro once from the platform code in
> > order to calculate the buffer size when calling the then NAKed
> > dma_preallocate_coherent_memory(). Now I could put them back into the
> > driver code, but if we ever get back to the concept of preallocating a
> > contignuos piece of memory from the platform init code, we might need
> > them back here, so maybe I should rather keep them, only rename the two
> > enum values using a distinct name space. What do you think is better for
> > now?
>
> Yeah, up to you, I'd say, but if you decide to keep them in the header,
> please, use a namespace.

OK, I'll use a namespace then.

> I'm satisfied with your answers to the rest of my questions / comments:)

Glad to hear :)

Thanks,
Janusz

> To unsubscribe from this list: send the line "unsubscribe linux-omap" in

---

From: Guennadi Liakhovetski <[email-masked]>
Date: Thu, 23 Sep 2010 15:33:54 +0200 (CEST)

On Wed, 22 Sep 2010, Janusz Krzysztofik wrote:

> into the DMA programming register set.

spin_lock_irqsave(); and there you also check ->ready and fill it in. In 

Thanks
Guennadi
---

---

From: Guennadi Liakhovetski <[email-masked]>
Date: Thu, 23 Sep 2010 18:06:15 +0200 (CEST)

On Wed, 22 Sep 2010, Janusz Krzysztofik wrote:

> Wednesday 22 September 2010 11:12:46 Guennadi Liakhovetski napisaÅ(a):

oh, no, don't think replacing msleep(1) with msleep(20) just to silence a 
compiler warning is a god idea...;) Well, use a udelay(1000). Or maybe 
try, whether a udelay(100) suffices too.

> geometry change, whether s_crop or s_fmt caused. Am I missing something?

If I understand your sensor correctly, HSTRT etc. registers configure 
pretty much any (input) sensor window, whereas COMA and COML select 
whether to scale it to a CIF or to a QCIF output. So, these are two 
different things. Hence your ->rect can hold only one of the two - the 
sensor window or the output image. Since output image has only two options 
- CIF or QCIF, you don't need to store it in rect, you already have 
priv->qcif.

Oh, and one more thing - didn't notice before: in your cropcap you do

Don't think this is right. cropcap shouldn't depend on any dynamic 
(configured by S_FMT) setting, it contains absolute limits of your 
hardware. I know I might have produced a couple of bad examples in the 
past - before I eventually settled with this definition... So, I think, 
it's best to put the full sensor resolution in cropcap.

...and, please, replace

+		priv->qcif = 1;
with
+		priv->qcif = true;
and
+		priv->qcif = 0;
with
+		priv->qcif = false;
in ov6650_s_fmt().

But I think your driver might have a problem with its cropping / scaling 
handling. Let's see if I understand it right:

1. as cropcap you currently return QCIF or CIF, depending on the last 
S_FMT, but let's say, you fix it to always return CIF.

2. in your s_fmt you accept only two output sizes: CIF or QCIF, that's ok, 
if that's all you can configure with your driver.

3. in s_crop you accept anything with left + width <= HSTOP and top + 
height <= VSTOP. This I don't understand. Your HSTOP and VSTOP are fixed 
values, based on QCIF plus some offsets. So, you would accept widths up to 
"a little larger than QCIF width" and similar heights. Then, without 
changing COMA and COML you assume, that the output size changed equally, 
because that's what you return in g_fmt.

Anyway, I think, there is some misunderstanding of the v4l2 cropping and 
scaling procedures. Please, have a look here: 
http://v4l2spec.bytesex.org/spec/x1904.htm. Do you agree, that what your 
driver is implementing doesn't reflect that correctly?;)

Thanks
Guennadi
---

---

From: Guennadi Liakhovetski <[email-masked]>
Date: Thu, 23 Sep 2010 18:10:44 +0200 (CEST)

> I'll emphasize the above return; with a comment.

Ok

> active buffer completion.

Let's assume it does:) You seem to really understand how this is working 
and even be willing to document the driver, thus making it possibly the 
best documented soc-camera related piece of software;)

Thanks
Guennadi
---

---

From: Janusz Krzysztofik <[email-masked]>
Date: Fri, 24 Sep 2010 00:45:23 +0200

Thursday 23 September 2010 18:06:15 Guennadi Liakhovetski napisał(a):
> > > Ok, just a couple more comments, all looking quite good so far, if we
> > > get a new version soon enough, we still might manage it for 2.6.37
> > >
> > > > +static int ov6650_reg_write(struct i2c_client *client, u8 reg, u8
> > > > val) +{
> > > > +	int ret;
> > driver doesn't work correctly. To prevent the checkpatch.pl from
> > complying against msleep(1), I think I could just replace it with
> > msleep(20). What do you think?
>

OK. Thanks for the hints.

> > Default (reset) values of HSTRT, HSTOP, VSTRT and VSTOP registers are the
> > same for both CIF and QCIF, giving a 176x144 picture area in both cases.
> > Than, when in CIF (reset default) mode, which actual size is double of
> > that (352x288), I scale them by 2 when converting to priv->rect elements.
> >
> > I added the priv->rect to be returned by g_crop() instead of
> > recalculating it from the register values. Then, I think I have to
> > overwrite it on every geometry change, whether s_crop or s_fmt caused. Am
> > I missing something?
>
> pretty much any (input) sensor window, 

Let's say, not exceeding CIF geometry (352x288).

> whereas COMA and COML select 
> whether to scale it to a CIF or to a QCIF output. 

I think the answer is: not exactly. AFAICT, the COMA_QCIF bit selects whether 
to scale it down by 2 (QCIF selection) or not (CIF selection).

> So, these are two 

With the above reservation - yes, I could use priv->qcif to scale priv->rect 
down by 2 or not in g_fmt.

OK.

Sure.

> S_FMT, 

Yes.

BTW, my S_FMT always calls ov6650_reset(), which resets the current crop to 
defaults. This behaviour doesn't follow the requirement of this operation 
being done only once, when the driver is first loaded, but not later. Should 
I restore the last crop after every reset? If yes, what is the purpose of 
defrect if applied only at driver first load?

> but let's say, you fix it to always return CIF. 

OK.

> if that's all you can configure with your driver.

Not any longer :). I'm able to configure using current crop geometry only, 
either unscaled or scaled down by 2. I'm not able to configure neither exact 
CIF nor QCIF if my current crop window doesn't match, unless I'm allowed to 
change the crop from here.

> "a little larger than QCIF width" and similar heights. 

Do you mean I should also verify if (rect->left >= DEF_HSTRT) and (rect->top 
>= DEF_VSTRT)? I should probably.

> Then, without 

I think I've verified, to the extent possible using v4l2-dbg, that it works 
like this. If I change the input height by overwriting VSTOP with a slightly 
higher value, the output seems to change proportionally and starts rolling 
down, since the host is not updated to handle the so changed frame size.

> driver is implementing doesn't reflect that correctly?;)

Yes, I do. And I think that that the reason of this misunderstanding is my 
sensor just not matching the v4l2 model with its limited, probably uncommon 
scaling capability, or me still not being able to map the sensor to the v4l2 
model correctly. What do you think?

Thanks,
Janusz

---

From: Tony Lindgren <[email-masked]>
Date: Thu, 23 Sep 2010 16:26:17 -0700

* Tony Lindgren <[email-masked]> [100923 16:06]:
> * Janusz Krzysztofik <[email-masked]> [100910 18:20]:
> Queuing these last two patches of the series (5/6 and 6/6) for the upcoming
> merge window.

BTW, these still depend on updated 2/6 to make compile happy.

Tony

---

From: Tony Lindgren <[email-masked]>
Date: Thu, 23 Sep 2010 16:14:15 -0700

Tony

---

From: Janusz Krzysztofik <[email-masked]>
Date: Fri, 24 Sep 2010 01:44:45 +0200

Friday 24 September 2010 01:23:10 Tony Lindgren napisał(a):
> * Janusz Krzysztofik <[email-masked]> [100910 18:26]:
> <snip>
> >3 22:34:03.000000000 +0200 +++
> > linux-2.6.36-rc3/arch/arm/mach-omap1/include/mach/camera.h	2010-09-09
> > 18:42:30.000000000 +0200 @@ -0,0 +1,8 @@
> Care to refresh this patch so it does not include media/omap1_camera.h?
>
> That way things keep building if I merge this one along with the omap
> patches and the drivers/media patches can get merged their via media.
>
> I think you can just move the OMAP1_CAMERA_IOSIZE to the devices.c or
> someplace like that?

Tony,
Not exactly. I use the OMAP1_CAMERA_IOSIZE inside the driver when reserving 
space for register cache.

I think that I could just duplicate its definition in the devices.c for now, 
than clean things up with a folloup patch when both parts already get merged. 
Would this be acceptable?

Thanks,
Janusz

> Regards,

---

From: Tony Lindgren <[email-masked]>
Date: Thu, 23 Sep 2010 16:54:45 -0700

> Would this be acceptable?

Yeah, that sounds good to me.

Tony

---

From: Janusz Krzysztofik <[email-masked]>
Date: Fri, 24 Sep 2010 02:00:19 +0200

Friday 24 September 2010 01:26:17 Tony Lindgren napisał(a):
> > > This patch adds configuration data and initialization code required for
> > > camera support to the Amstrad Delta board.
> > >
> > > Three devices are declared: SoC camera, OMAP1 camera interface and
> > > OV6650 sensor.
> > >
> > > Default 12MHz clock has been selected for driving the sensor. Pixel
> > > clock has been limited to get reasonable frame rates, not exceeding the
> > > board capabilities. Since both devices (interface and sensor) support
> > > both pixel clock polarities, decision on polarity selection has been
> > > left to drivers. Interface GPIO line has been found not functional,
> > > thus not configured.
> > >
> > Queuing these last two patches of the series (5/6 and 6/6) for the
> > upcoming merge window.
>

Not so simple: still depends on struct omap1_cam_platform_data definition from 
<media/omap1_camera.h>, included from <mach/camera.h>. Are you ready to 
accept another temporary workaround?

Thanks,
Janusz

---

From: Tony Lindgren <[email-masked]>
Date: Thu, 23 Sep 2010 16:23:10 -0700

<snip>

Regards,

Tony

---

From: Tony Lindgren <[email-masked]>
Date: Thu, 23 Sep 2010 17:49:36 -0700

> accept another temporary workaround?

Heh I guess so. Or do you want to queue everything via linux-media?

Tony

---

From: Janusz Krzysztofik <[email-masked]>
Date: Fri, 24 Sep 2010 12:28:24 +0200

Friday 24 September 2010 08:54:20 Guennadi Liakhovetski napisał(a):
> On Thu, 23 Sep 2010, Tony Lindgren wrote:
> > > Not exactly. I use the OMAP1_CAMERA_IOSIZE inside the driver when
> > > reserving space for register cache.
> > >
> > > I think that I could just duplicate its definition in the devices.c for
> > > now, than clean things up with a folloup patch when both parts already
> > > get merged. Would this be acceptable?
> >
> ...better yet put a zero-length cache array at the end of your struct
> omap1_cam_dev and allocate it dynamically, calculated from the resource
> size.

Guennadi,
Yes, this seems the best solution, thank you.

Tony,
You'll soon get it as you ask: <media/camera.h> no longer included from 
<mach/camera.h>.

Thanks,
Janusz

---

From: Janusz Krzysztofik <[email-masked]>
Date: Fri, 24 Sep 2010 13:08:38 +0200

Friday 24 September 2010 08:57:06 Guennadi Liakhovetski napisał(a):
> > > > > > This patch adds configuration data and initialization code
> > > > > > required for camera support to the Amstrad Delta board.
> > > > > >
> > > > > > Three devices are declared: SoC camera, OMAP1 camera interface
> > > > > > and OV6650 sensor.
> > > > > >
> > > > > > Default 12MHz clock has been selected for driving the sensor.
> > > > > > Pixel clock has been limited to get reasonable frame rates, not
> > > > > > exceeding the board capabilities. Since both devices (interface
> > > > > > and sensor) support both pixel clock polarities, decision on
> > > > > > polarity selection has been left to drivers. Interface GPIO line
> > > > > > has been found not functional, thus not configured.
> > > > > >
> > > Not so simple: still depends on struct omap1_cam_platform_data
> > > definition from <media/omap1_camera.h>, included from <mach/camera.h>.
> > > Are you ready to accept another temporary workaround?
> >

AFAIK we can expect my arch/arm/mach-omap1/devices.c changes already resulting 
in a confilct with some ASoC OMAP related changes going via the sound tree, 
so the 2/6 should be better queued via the OMAP tree for Tony to keep control 
over it, with the rest of the seriers going either way.

> Yes, we often have to select via which tree to go, then the maintainer of
> the other tree just acks the patches. Sometimes we push them via different
> trees and try to enforce a specific merge order...

What about

+ void omap1_set_camera_info(struct omap1_cam_platform_data *);

put temporarily into to the arch/arm/mach-omap1/board-ams-delta.c instead of 
including <mach/camera.h>, that could be replaced with <media/omap1_camera.h> 
then? May sound better than redefining struct omap1_cam_platform_data there, 
and should be safe to push everything except 2/6 via the media tree.

Then, replace the above hack back with #include <mach/camera.h> as a fix after 
both are merged.

Thanks,
Janusz

---

From: Janusz Krzysztofik <[email-masked]>
Date: Fri, 24 Sep 2010 13:36:33 +0200

Friday 24 September 2010 08:52:32 Guennadi Liakhovetski napisał(a):
> On Fri, 24 Sep 2010, Janusz Krzysztofik wrote:

...
> > I think the answer is: not exactly. AFAICT, the COMA_QCIF bit selects
> > whether to scale it down by 2 (QCIF selection) or not (CIF selection).
>
> Ah! Ok, that it would be better to select different names for those bits.

I was trying to keep all names more or less consistent with the wording used 
in the sensor documentation (which doesn't follow the v4l2 specification 
unfortunately :). In this case we have:

COMA  Common Control A
	...
	Bit[5]: Output format – resolution
		0: CIF (352 x 288)
		1: QCIF (176 x 144)

So I could rename it to something like COMA_OUTFMT or COMA_RESOLUTION. Which 
one sounds better for you?

...
> > > But I think your driver might have a problem with its cropping /
> > > scaling handling. Let's see if I understand it right:
> > >
> > BTW, my S_FMT always calls ov6650_reset(), which resets the current crop
> > to defaults.
>
> Oh, does it mean all registers are reset to their defaults? That'd be not
> good - no v4l(2) ioctl, AFAIK, should affect parameters, not directly
> related to it. Even closing and reopening the video device node shouldn't
> reset values. So, maybe you should drop that reset completely.

Shouldn't I rather move it over into the ov6650_video_probe()?

...
> > > 2. in your s_fmt you accept only two output sizes: CIF or QCIF, that's
> > > ok, if that's all you can configure with your driver.
> >
> > Not any longer :). I'm able to configure using current crop geometry
> > only, either unscaled or scaled down by 2. I'm not able to configure
> > neither exact CIF nor QCIF if my current crop window doesn't match,
> > unless I'm allowed to change the crop from here.
>
> Hm, but in your s_fmt you do:
>
> +	switch (mf->width) {
> +	case W_QCIF:
> So, you accept only CIF or QCIF as your output window. Or do you mean a v3
> by your "not any longer?" 

Exactly!

> And yes, you are allowed to change your input 
> sensor window, if that lets you configure your output format more
> precisely. And v.v. The rule is - the most recent command wins.

I see.

...
> No, there's nothing wrong with your sensor:) So, what I would do is:
>
> 1. in your struct ov6650:
>
> +	struct v4l2_rect	rect;		/* sensor cropping window */
> +	bool			half_scale;	/* scale down output by 2 */
>
> 2. in s_crop verify left, width, top, height, program them into the chip
> and store in ->rect
>
> 3. in g_crop just return values from ->rect
>
> 4. in s_fmt you have to select an input rectangle, that would allow you to
> possibly exactly configure the requested output format. Say, if you have a
> 320x240 cropping configured and you get an s_fmt request for 120x90. You
> can either set your input rectangle to 240x180 and scale it down by 2, or
> set the rectangle directly to 120x90. Obviously, it's better to use
> 240x180 and scale down, because that's closer to the current cropping of
> 320x240. So, in s_fmt you select a new input rectangle _closest_ to the
> currently configured one, that would allow you to configure the correct
> output format. Then you set your ->rect with the new values and your
> ->half_scale
>
> 5. in g_fmt you return ->rect scaled with ->half_scale
>
> Makes sense?

Absolutely. Hope to submit v3 soon.

Thanks,
Janusz

---

From: Janusz Krzysztofik <[email-masked]>
Date: Fri, 24 Sep 2010 16:25:14 +0200

> With the second line I have to scan the 0 digits carefully, looking for

Fair enough, I'll convert them to "%#x".

Thanks,
Janusz

---

From: Tony Lindgren <[email-masked]>
Date: Fri, 24 Sep 2010 10:43:21 -0700

> You'll soon get it as you ask: <media/camera.h> no longer included from 

OK, sounds good to me.

Tony

---

From: Guennadi Liakhovetski <[email-masked]>
Date: Fri, 24 Sep 2010 08:52:32 +0200 (CEST)

> Let's say, not exceeding CIF geometry (352x288).

Yes, sorry, forgot to mention that.

> down by 2 or not in g_fmt.

...and for the ->qcif member.

> defaults.

> This behaviour doesn't follow the requirement of this operation 

that's exactly the purpose, I think.

+	switch (mf->width) {
+	case W_QCIF:

So, you accept only CIF or QCIF as your output window. Or do you mean a v3 
by your "not any longer?" And yes, you are allowed to change your input 
sensor window, if that lets you configure your output format more 
precisely. And v.v. The rule is - the most recent command wins.

> >= DEF_VSTRT)? I should probably.

Yes, if you cannot handle smaller values.

> down, since the host is not updated to handle the so changed frame size.

Yes, now that I know, that that flag is actually a scale-down-by-2 switch.

Makes sense?

Thanks
Guennadi
---

---

From: Guennadi Liakhovetski <[email-masked]>
Date: Fri, 24 Sep 2010 08:54:20 +0200 (CEST)

Thanks
Guennadi
---

---

From: Guennadi Liakhovetski <[email-masked]>
Date: Fri, 24 Sep 2010 08:57:06 +0200 (CEST)

Thanks
Guennadi
---

---

From: Guennadi Liakhovetski <[email-masked]>
Date: Fri, 24 Sep 2010 13:51:05 +0200 (CEST)

> one sounds better for you?

ok, so, it means to which output window the _complete_ sensor area is 
mapped. And if you get a smaller sensor rectangle, you get a smaller 
output image, right? Ok, then you can just leave it as is.

> Shouldn't I rather move it over into the ov6650_video_probe()?

Good idea!:)

> Absolutely. Hope to submit v3 soon.

Good! Looking forward:)

Thanks
Guennadi
---